summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/dockerfiles/Dockerfile.32-bit2
-rw-r--r--.github/dockerfiles/Dockerfile.64-bit41
-rw-r--r--.github/dockerfiles/Dockerfile.clang2
-rw-r--r--.github/dockerfiles/Dockerfile.cross-compile2
-rw-r--r--.github/dockerfiles/Dockerfile.ubuntu-base96
-rwxr-xr-x.github/dockerfiles/init.sh2
-rwxr-xr-x.github/scripts/build-base-image.sh14
-rwxr-xr-x.github/scripts/build-macos.sh21
-rwxr-xr-x.github/scripts/get-pr-number.es35
-rwxr-xr-x.github/scripts/init-pre-release.sh31
-rwxr-xr-x.github/scripts/restore-from-prebuilt.sh162
-rwxr-xr-x.github/scripts/restore-otp-image.sh13
-rwxr-xr-x.github/scripts/sync-github-prs.es55
-rwxr-xr-x.github/scripts/sync-github-releases.sh6
-rw-r--r--.github/workflows/actions-updater.yaml22
-rw-r--r--.github/workflows/add-to-project.yaml26
-rw-r--r--.github/workflows/main.yaml456
-rw-r--r--.github/workflows/pr-comment.yaml10
-rw-r--r--.github/workflows/sync-github-releases.yaml10
-rw-r--r--.github/workflows/update-base.yaml4
-rw-r--r--.gitignore6
-rw-r--r--CONTRIBUTING.md14
-rw-r--r--HOWTO/DEVELOPMENT.md8
-rw-r--r--HOWTO/INSTALL-RASPBERRYPI3.md6
-rw-r--r--HOWTO/INSTALL-WIN32.md4
-rw-r--r--HOWTO/TESTING.md2
-rw-r--r--Makefile.in387
-rw-r--r--OTP_VERSION2
-rw-r--r--README.md4
-rw-r--r--bootstrap/bin/no_dot_erlang.bootbin7007 -> 6644 bytes
-rw-r--r--bootstrap/bin/start.bootbin7007 -> 6644 bytes
-rw-r--r--bootstrap/bin/start_clean.bootbin7007 -> 6644 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_a.beambin3220 -> 3508 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_asm.beambin12076 -> 12648 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_block.beambin4944 -> 4952 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_bounds.beambin5248 -> 9456 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_call_types.beambin18140 -> 20868 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_clean.beambin7136 -> 6976 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_dict.beambin5552 -> 5348 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_digraph.beambin3696 -> 3644 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_disasm.beambin23532 -> 26664 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_flatten.beambin1720 -> 1684 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_jump.beambin11092 -> 11256 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beambin26016 -> 25816 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_listing.beambin2200 -> 2188 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_opcodes.beambin8056 -> 8132 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa.beambin15476 -> 15444 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_alias.beambin0 -> 14296 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_bc_size.beambin10412 -> 10152 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_bool.beambin23256 -> 23036 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_bsm.beambin17608 -> 17516 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_check.beambin0 -> 6936 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_codegen.beambin40128 -> 44612 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_dead.beambin16132 -> 15820 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_lint.beambin9508 -> 9484 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_opt.beambin50320 -> 55920 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_pp.beambin9172 -> 10088 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beambin45204 -> 46004 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_private_append.beambin0 -> 10796 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_recv.beambin14396 -> 15012 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_share.beambin5912 -> 5716 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_throw.beambin7704 -> 7548 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_type.beambin35968 -> 40752 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_trim.beambin11964 -> 12116 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_types.beambin17432 -> 21440 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_utils.beambin3524 -> 3584 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_validator.beambin52224 -> 57592 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_z.beambin4044 -> 3992 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl.beambin28112 -> 28224 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_clauses.beambin2824 -> 2756 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_inline.beambin33172 -> 33016 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_trees.beambin20040 -> 20052 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/compile.beambin38004 -> 38084 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/compiler.app2
-rw-r--r--bootstrap/lib/compiler/ebin/compiler.appup2
-rw-r--r--bootstrap/lib/compiler/ebin/core_lib.beambin3780 -> 3736 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_lint.beambin12696 -> 12492 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_parse.beambin83776 -> 82136 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_pp.beambin10492 -> 10744 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_scan.beambin6372 -> 7396 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/erl_bifs.beambin2180 -> 2160 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/rec_env.beambin4456 -> 4364 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_alias.beambin5352 -> 5160 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_bsm.beambin1668 -> 1608 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_fold.beambin42344 -> 39696 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_fold_lists.beambin4080 -> 4060 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_inline.beambin3500 -> 3360 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_prepare.beambin1744 -> 1696 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_messages.beambin3972 -> 4004 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_pre_attributes.beambin2360 -> 2228 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_core.beambin61640 -> 64628 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_kernel.beambin43808 -> 44060 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_kernel_pp.beambin10020 -> 10052 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application.beambin4512 -> 6108 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application_controller.beambin35008 -> 34684 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application_master.beambin6368 -> 6264 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application_starter.beambin1308 -> 1256 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/auth.beambin7496 -> 7372 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/code.beambin15728 -> 16676 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/code_server.beambin22168 -> 23116 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log.beambin28560 -> 27744 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log_1.beambin22644 -> 23036 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log_server.beambin4004 -> 3928 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log_sup.beambin624 -> 600 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/dist_ac.beambin23328 -> 22624 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/dist_util.beambin15768 -> 16724 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_boot_server.beambin5724 -> 5604 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_compile_server.beambin5144 -> 5020 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_ddll.beambin2832 -> 2796 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_distribution.beambin2124 -> 2084 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_epmd.beambin7940 -> 7816 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_erts_errors.beambin22840 -> 23528 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_kernel_errors.beambin2944 -> 3120 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_reply.beambin932 -> 912 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_signal_handler.beambin1168 -> 1148 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erpc.beambin14112 -> 13920 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/error_handler.beambin1652 -> 1620 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/error_logger.beambin6368 -> 6292 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erts_debug.beambin9340 -> 9184 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file.beambin14768 -> 14720 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file_io_server.beambin15536 -> 15544 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file_server.beambin5060 -> 4812 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_sctp.beambin5564 -> 5492 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_tcp.beambin3200 -> 3164 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_tcp_socket.beambin32944 -> 33212 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_udp.beambin2416 -> 2380 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_udp_socket.beambin24348 -> 24196 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/global.beambin38196 -> 37988 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/global_group.beambin19084 -> 19044 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/global_search.beambin3020 -> 3000 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/group.beambin14128 -> 15436 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/group_history.beambin7224 -> 7104 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/heart.beambin5412 -> 5192 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet.beambin28680 -> 27956 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_sctp.beambin1656 -> 1636 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_tcp.beambin3892 -> 3840 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_tcp_dist.beambin1008 -> 988 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_udp.beambin2648 -> 2580 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_config.beambin7476 -> 7360 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_db.beambin26432 -> 26416 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_dns.beambin18416 -> 20764 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_epmd_dist.beambin0 -> 10816 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_epmd_socket.beambin0 -> 7584 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_gethost_native.beambin10584 -> 10584 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_hosts.beambin1768 -> 1704 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_parse.beambin13436 -> 13952 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_res.beambin14328 -> 14344 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_sctp.beambin2640 -> 2604 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_tcp.beambin3528 -> 3476 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_tcp_dist.beambin8748 -> 8992 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_udp.beambin2656 -> 2588 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.app7
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.appup36
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.beambin4084 -> 4272 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel_config.beambin2788 -> 2716 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel_refc.beambin2316 -> 2240 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/local_tcp.beambin2244 -> 2208 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/local_udp.beambin1444 -> 1424 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger.beambin16292 -> 16164 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_backend.beambin2544 -> 2516 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_config.beambin3948 -> 3932 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_disk_log_h.beambin3344 -> 3276 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_filters.beambin1872 -> 1812 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_formatter.beambin9196 -> 9076 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_h_common.beambin7748 -> 7680 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_handler_watcher.beambin1420 -> 1408 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_olp.beambin8288 -> 8184 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_proxy.beambin2892 -> 2864 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_server.beambin11552 -> 11404 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_simple_h.beambin4804 -> 5144 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_std_h.beambin10172 -> 10132 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_sup.beambin704 -> 680 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/net.beambin12204 -> 11984 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/net_adm.beambin2908 -> 2892 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/net_kernel.beambin31480 -> 31988 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/os.beambin6036 -> 5952 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/pg.beambin10972 -> 10652 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/pg2.beambin416 -> 392 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/prim_tty.beambin0 -> 14492 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/ram_file.beambin5620 -> 5532 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io.beambin1464 -> 1412 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_compressed.beambin2572 -> 2540 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_deflate.beambin2712 -> 2632 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_delayed.beambin5564 -> 5476 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_inflate.beambin4440 -> 4384 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_list.beambin2532 -> 2512 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/rpc.beambin11268 -> 11128 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/seq_trace.beambin1804 -> 1768 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/socket.beambin25704 -> 25812 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/standard_error.beambin3876 -> 3948 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/user.beambin11312 -> 0 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/user_drv.beambin11400 -> 16884 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/user_sup.beambin1836 -> 1940 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/wrap_log_reader.beambin3024 -> 2920 bytes
-rw-r--r--bootstrap/lib/kernel/include/dist.hrl8
-rw-r--r--bootstrap/lib/stdlib/ebin/array.beambin12108 -> 12268 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/base64.beambin7320 -> 10876 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/beam_lib.beambin19112 -> 19240 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/binary.beambin12180 -> 6328 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/c.beambin18132 -> 18052 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/calendar.beambin9020 -> 9360 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets.beambin45084 -> 45676 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_server.beambin6488 -> 6380 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_sup.beambin612 -> 588 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_utils.beambin26252 -> 27096 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_v9.beambin46200 -> 46832 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dict.beambin8932 -> 8876 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/digraph.beambin7660 -> 7516 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/digraph_utils.beambin6452 -> 6336 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/edlin.beambin10768 -> 11216 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/edlin_context.beambin0 -> 11832 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/edlin_expand.beambin4484 -> 26740 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/edlin_type_suggestion.beambin0 -> 15856 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/epp.beambin32476 -> 32420 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_abstract_code.beambin1116 -> 1080 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_anno.beambin3740 -> 3740 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_bits.beambin2512 -> 2456 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_compile.beambin8932 -> 8608 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_error.beambin11044 -> 10980 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_eval.beambin34864 -> 35688 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_expand_records.beambin19712 -> 19628 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_features.beambin9188 -> 8812 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_internal.beambin7004 -> 6952 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_lint.beambin91980 -> 90192 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_parse.beambin145716 -> 177280 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_posix_msg.beambin5256 -> 5320 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_pp.beambin28460 -> 28740 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_scan.beambin27676 -> 30568 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_stdlib_errors.beambin17328 -> 18020 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_tar.beambin31416 -> 31160 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/error_logger_file_h.beambin4112 -> 4012 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/error_logger_tty_h.beambin4932 -> 4832 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/escript.beambin15812 -> 15436 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/ets.beambin21096 -> 20968 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/eval_bits.beambin8656 -> 8660 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/file_sorter.beambin27640 -> 27372 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/filelib.beambin11560 -> 11528 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/filename.beambin14100 -> 14304 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gb_sets.beambin7808 -> 7940 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gb_trees.beambin5316 -> 5300 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen.beambin9980 -> 9992 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_event.beambin17048 -> 16860 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_fsm.beambin14196 -> 14064 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_server.beambin20340 -> 19352 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_statem.beambin26732 -> 26216 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io.beambin8168 -> 8052 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib.beambin13608 -> 14232 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_format.beambin12608 -> 13184 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_fread.beambin6552 -> 6684 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_pretty.beambin21980 -> 22396 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/lists.beambin30644 -> 34200 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/log_mf_h.beambin2520 -> 2392 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/maps.beambin6180 -> 7476 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/math.beambin1360 -> 1380 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/ms_transform.beambin19068 -> 19096 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/orddict.beambin2948 -> 2928 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/ordsets.beambin1900 -> 1880 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/otp_internal.beambin5948 -> 6120 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/peer.beambin19136 -> 20436 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/pool.beambin3660 -> 3616 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/proc_lib.beambin15432 -> 15848 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/proplists.beambin5040 -> 4960 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/qlc.beambin63836 -> 63744 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/qlc_pt.beambin67828 -> 67612 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/queue.beambin8924 -> 8908 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/rand.beambin32776 -> 32996 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/random.beambin2044 -> 1992 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/re.beambin12744 -> 13292 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/sets.beambin8968 -> 8964 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/shell.beambin29440 -> 33812 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/shell_default.beambin4648 -> 4916 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/shell_docs.beambin18384 -> 18372 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/slave.beambin4924 -> 4876 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/sofs.beambin35144 -> 35116 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/stdlib.app6
-rw-r--r--bootstrap/lib/stdlib/ebin/stdlib.appup38
-rw-r--r--bootstrap/lib/stdlib/ebin/string.beambin35080 -> 36180 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/supervisor.beambin25268 -> 24996 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/supervisor_bridge.beambin5496 -> 5416 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/sys.beambin9280 -> 9192 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/timer.beambin5744 -> 7112 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/unicode.beambin13444 -> 13848 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/unicode_util.beambin205340 -> 296268 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/uri_string.beambin27532 -> 28648 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/win32reg.beambin5592 -> 5456 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/zip.beambin24352 -> 24368 bytes
-rw-r--r--erts/.gitignore1
-rw-r--r--erts/Makefile4
-rw-r--r--erts/config.h.in6
-rwxr-xr-xerts/configure1056
-rw-r--r--erts/configure.ac59
-rw-r--r--erts/doc/src/absform.xml17
-rw-r--r--erts/doc/src/crash_dump.xml4
-rw-r--r--erts/doc/src/erl_cmd.xml24
-rw-r--r--erts/doc/src/erl_dist_protocol.xml382
-rw-r--r--erts/doc/src/erl_driver.xml8
-rw-r--r--erts/doc/src/erl_ext_dist.xml69
-rw-r--r--erts/doc/src/erl_nif.xml357
-rw-r--r--erts/doc/src/erlang.xml657
-rw-r--r--erts/doc/src/erlsrv_cmd.xml5
-rw-r--r--erts/doc/src/erts_alloc.xml26
-rw-r--r--erts/doc/src/escript_cmd.xml5
-rw-r--r--erts/doc/src/match_spec.xml61
-rw-r--r--erts/doc/src/notes.xml29
-rw-r--r--erts/doc/src/time_correction.xml62
-rw-r--r--erts/doc/src/tty.xml20
-rw-r--r--erts/emulator/Makefile.in29
-rw-r--r--erts/emulator/asmjit.version2
-rw-r--r--erts/emulator/asmjit/arm/a64assembler.cpp30
-rw-r--r--erts/emulator/asmjit/arm/a64compiler.h12
-rw-r--r--erts/emulator/asmjit/arm/a64emithelper.cpp2
-rw-r--r--erts/emulator/asmjit/arm/a64formatter.cpp4
-rw-r--r--erts/emulator/asmjit/arm/a64instapi.cpp41
-rw-r--r--erts/emulator/asmjit/arm/a64instdb.cpp2388
-rw-r--r--erts/emulator/asmjit/arm/a64instdb.h4
-rw-r--r--erts/emulator/asmjit/arm/a64instdb_p.h3
-rw-r--r--erts/emulator/asmjit/arm/a64rapass.cpp52
-rw-r--r--erts/emulator/asmjit/arm/armoperand.h95
-rw-r--r--erts/emulator/asmjit/core.h56
-rw-r--r--erts/emulator/asmjit/core/api-config.h6
-rw-r--r--erts/emulator/asmjit/core/archtraits.h2
-rw-r--r--erts/emulator/asmjit/core/builder.cpp8
-rw-r--r--erts/emulator/asmjit/core/builder_p.h35
-rw-r--r--erts/emulator/asmjit/core/codebuffer.h2
-rw-r--r--erts/emulator/asmjit/core/codeholder.cpp7
-rw-r--r--erts/emulator/asmjit/core/codeholder.h9
-rw-r--r--erts/emulator/asmjit/core/compiler.cpp32
-rw-r--r--erts/emulator/asmjit/core/cpuinfo.cpp39
-rw-r--r--erts/emulator/asmjit/core/cpuinfo.h29
-rw-r--r--erts/emulator/asmjit/core/emitter.h24
-rw-r--r--erts/emulator/asmjit/core/emitterutils.cpp8
-rw-r--r--erts/emulator/asmjit/core/emitterutils_p.h4
-rw-r--r--erts/emulator/asmjit/core/errorhandler.h6
-rw-r--r--erts/emulator/asmjit/core/formatter.cpp5
-rw-r--r--erts/emulator/asmjit/core/formatter.h4
-rw-r--r--erts/emulator/asmjit/core/func.h11
-rw-r--r--erts/emulator/asmjit/core/inst.h32
-rw-r--r--erts/emulator/asmjit/core/jitallocator.cpp176
-rw-r--r--erts/emulator/asmjit/core/jitallocator.h4
-rw-r--r--erts/emulator/asmjit/core/jitruntime.cpp4
-rw-r--r--erts/emulator/asmjit/core/operand.h38
-rw-r--r--erts/emulator/asmjit/core/osutils.cpp2
-rw-r--r--erts/emulator/asmjit/core/raassignment_p.h28
-rw-r--r--erts/emulator/asmjit/core/rabuilders_p.h2
-rw-r--r--erts/emulator/asmjit/core/radefs_p.h10
-rw-r--r--erts/emulator/asmjit/core/ralocal.cpp98
-rw-r--r--erts/emulator/asmjit/core/ralocal_p.h14
-rw-r--r--erts/emulator/asmjit/core/rapass.cpp99
-rw-r--r--erts/emulator/asmjit/core/rapass_p.h62
-rw-r--r--erts/emulator/asmjit/core/rastack.cpp2
-rw-r--r--erts/emulator/asmjit/core/support.h41
-rw-r--r--erts/emulator/asmjit/core/support_p.h64
-rw-r--r--erts/emulator/asmjit/core/target.cpp4
-rw-r--r--erts/emulator/asmjit/core/target.h6
-rw-r--r--erts/emulator/asmjit/core/virtmem.cpp453
-rw-r--r--erts/emulator/asmjit/core/virtmem.h15
-rw-r--r--erts/emulator/asmjit/core/zonevector.h2
-rw-r--r--erts/emulator/asmjit/x86/x86assembler.cpp31
-rw-r--r--erts/emulator/asmjit/x86/x86assembler.h15
-rw-r--r--erts/emulator/asmjit/x86/x86builder.h7
-rw-r--r--erts/emulator/asmjit/x86/x86compiler.h15
-rw-r--r--erts/emulator/asmjit/x86/x86emithelper.cpp2
-rw-r--r--erts/emulator/asmjit/x86/x86formatter.cpp78
-rw-r--r--erts/emulator/asmjit/x86/x86globals.h2
-rw-r--r--erts/emulator/asmjit/x86/x86instapi.cpp178
-rw-r--r--erts/emulator/asmjit/x86/x86instdb.cpp6190
-rw-r--r--erts/emulator/asmjit/x86/x86instdb.h6
-rw-r--r--erts/emulator/asmjit/x86/x86instdb_p.h20
-rw-r--r--erts/emulator/asmjit/x86/x86operand.h50
-rw-r--r--erts/emulator/asmjit/x86/x86rapass.cpp54
-rw-r--r--erts/emulator/beam/atom.c5
-rw-r--r--erts/emulator/beam/atom.names18
-rw-r--r--erts/emulator/beam/beam_bif_load.c434
-rw-r--r--erts/emulator/beam/beam_bp.c786
-rw-r--r--erts/emulator/beam/beam_bp.h66
-rw-r--r--erts/emulator/beam/beam_common.c123
-rw-r--r--erts/emulator/beam/beam_common.h7
-rw-r--r--erts/emulator/beam/beam_debug.c13
-rw-r--r--erts/emulator/beam/beam_file.c120
-rw-r--r--erts/emulator/beam/beam_file.h10
-rw-r--r--erts/emulator/beam/beam_load.c22
-rw-r--r--erts/emulator/beam/beam_transform_helpers.c4
-rw-r--r--erts/emulator/beam/beam_types.c85
-rw-r--r--erts/emulator/beam/beam_types.h58
-rw-r--r--erts/emulator/beam/bif.c165
-rw-r--r--erts/emulator/beam/bif.tab10
-rw-r--r--erts/emulator/beam/break.c26
-rw-r--r--erts/emulator/beam/code_ix.c317
-rw-r--r--erts/emulator/beam/code_ix.h74
-rw-r--r--erts/emulator/beam/copy.c52
-rw-r--r--erts/emulator/beam/dist.c67
-rw-r--r--erts/emulator/beam/dist.h29
-rw-r--r--erts/emulator/beam/emu/beam_emu.c12
-rw-r--r--erts/emulator/beam/emu/bif_instrs.tab4
-rw-r--r--erts/emulator/beam/emu/bs_instrs.tab288
-rw-r--r--erts/emulator/beam/emu/emu_load.c54
-rw-r--r--erts/emulator/beam/emu/generators.tab413
-rw-r--r--erts/emulator/beam/emu/instrs.tab24
-rw-r--r--erts/emulator/beam/emu/load.h7
-rw-r--r--erts/emulator/beam/emu/ops.tab79
-rw-r--r--erts/emulator/beam/emu/predicates.tab13
-rw-r--r--erts/emulator/beam/emu/trace_instrs.tab12
-rw-r--r--erts/emulator/beam/erl_alloc.c45
-rw-r--r--erts/emulator/beam/erl_arith.c2
-rw-r--r--erts/emulator/beam/erl_async.c4
-rw-r--r--erts/emulator/beam/erl_bif_guard.c17
-rw-r--r--erts/emulator/beam/erl_bif_info.c57
-rw-r--r--erts/emulator/beam/erl_bif_trace.c132
-rw-r--r--erts/emulator/beam/erl_bif_unique.c290
-rw-r--r--erts/emulator/beam/erl_bif_unique.h25
-rw-r--r--erts/emulator/beam/erl_bits.h25
-rw-r--r--erts/emulator/beam/erl_db.c84
-rw-r--r--erts/emulator/beam/erl_db_hash.c1
-rw-r--r--erts/emulator/beam/erl_db_tree.c1
-rw-r--r--erts/emulator/beam/erl_db_util.c408
-rw-r--r--erts/emulator/beam/erl_db_util.h10
-rw-r--r--erts/emulator/beam/erl_dirty_bif.tab3
-rw-r--r--erts/emulator/beam/erl_drv_thread.c6
-rw-r--r--erts/emulator/beam/erl_fun.c232
-rw-r--r--erts/emulator/beam/erl_fun.h17
-rw-r--r--erts/emulator/beam/erl_gc.c109
-rw-r--r--erts/emulator/beam/erl_gc.h6
-rw-r--r--erts/emulator/beam/erl_init.c84
-rw-r--r--erts/emulator/beam/erl_lock_check.c8
-rw-r--r--erts/emulator/beam/erl_lock_count.h16
-rw-r--r--erts/emulator/beam/erl_map.c173
-rw-r--r--erts/emulator/beam/erl_map.h2
-rw-r--r--erts/emulator/beam/erl_md5.c26
-rw-r--r--erts/emulator/beam/erl_message.c2
-rw-r--r--erts/emulator/beam/erl_monitor_link.c12
-rw-r--r--erts/emulator/beam/erl_nif.c713
-rw-r--r--erts/emulator/beam/erl_nif.h18
-rw-r--r--erts/emulator/beam/erl_nif_api_funcs.h9
-rw-r--r--erts/emulator/beam/erl_node_container_utils.h18
-rw-r--r--erts/emulator/beam/erl_node_tables.c2
-rw-r--r--erts/emulator/beam/erl_posix_str.c3
-rw-r--r--erts/emulator/beam/erl_printf_term.c67
-rw-r--r--erts/emulator/beam/erl_proc_sig_queue.c4
-rw-r--r--erts/emulator/beam/erl_process.c33
-rw-r--r--erts/emulator/beam/erl_process.h35
-rw-r--r--erts/emulator/beam/erl_ptab.c62
-rw-r--r--erts/emulator/beam/erl_ptab.h75
-rw-r--r--erts/emulator/beam/erl_term.h14
-rw-r--r--erts/emulator/beam/erl_term_hashing.c2014
-rw-r--r--erts/emulator/beam/erl_term_hashing.h79
-rw-r--r--erts/emulator/beam/erl_threads.h8
-rw-r--r--erts/emulator/beam/erl_trace.c8
-rw-r--r--erts/emulator/beam/erl_trace.h3
-rw-r--r--erts/emulator/beam/erl_utils.h25
-rw-r--r--erts/emulator/beam/erl_vm.h15
-rw-r--r--erts/emulator/beam/export.c14
-rw-r--r--erts/emulator/beam/external.c945
-rw-r--r--erts/emulator/beam/external.h5
-rw-r--r--erts/emulator/beam/generators.tab194
-rw-r--r--erts/emulator/beam/global.h35
-rw-r--r--erts/emulator/beam/hash.c3
-rw-r--r--erts/emulator/beam/io.c5
-rw-r--r--erts/emulator/beam/jit/arm/beam_asm.hpp622
-rw-r--r--erts/emulator/beam/jit/arm/beam_asm_global.cpp24
-rw-r--r--erts/emulator/beam/jit/arm/beam_asm_global.hpp.pl16
-rw-r--r--erts/emulator/beam/jit/arm/beam_asm_module.cpp101
-rw-r--r--erts/emulator/beam/jit/arm/generators.tab141
-rw-r--r--erts/emulator/beam/jit/arm/instr_arith.cpp340
-rw-r--r--erts/emulator/beam/jit/arm/instr_bif.cpp26
-rw-r--r--erts/emulator/beam/jit/arm/instr_bs.cpp2579
-rw-r--r--erts/emulator/beam/jit/arm/instr_call.cpp52
-rw-r--r--erts/emulator/beam/jit/arm/instr_common.cpp1072
-rw-r--r--erts/emulator/beam/jit/arm/instr_float.cpp4
-rw-r--r--erts/emulator/beam/jit/arm/instr_fun.cpp35
-rw-r--r--erts/emulator/beam/jit/arm/instr_guard_bifs.cpp684
-rw-r--r--erts/emulator/beam/jit/arm/instr_map.cpp90
-rw-r--r--erts/emulator/beam/jit/arm/instr_msg.cpp9
-rw-r--r--erts/emulator/beam/jit/arm/instr_select.cpp54
-rw-r--r--erts/emulator/beam/jit/arm/instr_trace.cpp43
-rw-r--r--erts/emulator/beam/jit/arm/ops.tab126
-rw-r--r--erts/emulator/beam/jit/arm/predicates.tab27
-rw-r--r--erts/emulator/beam/jit/asm_load.c146
-rw-r--r--erts/emulator/beam/jit/beam_asm.h39
-rw-r--r--erts/emulator/beam/jit/beam_jit_args.hpp5
-rw-r--r--erts/emulator/beam/jit/beam_jit_common.cpp197
-rw-r--r--erts/emulator/beam/jit/beam_jit_common.hpp386
-rw-r--r--erts/emulator/beam/jit/beam_jit_main.cpp142
-rw-r--r--erts/emulator/beam/jit/beam_jit_metadata.cpp28
-rw-r--r--erts/emulator/beam/jit/beam_jit_types.hpp150
-rw-r--r--erts/emulator/beam/jit/load.h11
-rw-r--r--erts/emulator/beam/jit/x86/beam_asm.hpp720
-rw-r--r--erts/emulator/beam/jit/x86/beam_asm_global.cpp22
-rwxr-xr-xerts/emulator/beam/jit/x86/beam_asm_global.hpp.pl17
-rw-r--r--erts/emulator/beam/jit/x86/beam_asm_module.cpp75
-rw-r--r--erts/emulator/beam/jit/x86/generators.tab175
-rw-r--r--erts/emulator/beam/jit/x86/instr_arith.cpp400
-rw-r--r--erts/emulator/beam/jit/x86/instr_bif.cpp26
-rw-r--r--erts/emulator/beam/jit/x86/instr_bs.cpp3066
-rw-r--r--erts/emulator/beam/jit/x86/instr_call.cpp12
-rw-r--r--erts/emulator/beam/jit/x86/instr_common.cpp934
-rw-r--r--erts/emulator/beam/jit/x86/instr_float.cpp67
-rw-r--r--erts/emulator/beam/jit/x86/instr_fun.cpp56
-rw-r--r--erts/emulator/beam/jit/x86/instr_guard_bifs.cpp759
-rw-r--r--erts/emulator/beam/jit/x86/instr_map.cpp117
-rw-r--r--erts/emulator/beam/jit/x86/instr_msg.cpp10
-rw-r--r--erts/emulator/beam/jit/x86/instr_select.cpp4
-rw-r--r--erts/emulator/beam/jit/x86/instr_trace.cpp58
-rw-r--r--erts/emulator/beam/jit/x86/ops.tab153
-rw-r--r--erts/emulator/beam/jit/x86/predicates.tab26
-rw-r--r--erts/emulator/beam/module.c96
-rw-r--r--erts/emulator/beam/module.h30
-rw-r--r--erts/emulator/beam/packet_parser.c41
-rw-r--r--erts/emulator/beam/predicates.tab12
-rw-r--r--erts/emulator/beam/sys.h27
-rw-r--r--erts/emulator/beam/utils.c2101
-rw-r--r--erts/emulator/drivers/common/inet_drv.c705
-rw-r--r--erts/emulator/drivers/unix/ttsl_drv.c1606
-rw-r--r--erts/emulator/drivers/win32/ttsl_drv.c786
-rw-r--r--erts/emulator/drivers/win32/win_con.c2355
-rw-r--r--erts/emulator/drivers/win32/win_con.h40
-rw-r--r--erts/emulator/internal_doc/BeamAsm.md28
-rw-r--r--erts/emulator/internal_doc/CodeLoading.md8
-rw-r--r--erts/emulator/internal_doc/Tracing.md23
-rw-r--r--erts/emulator/nifs/common/prim_socket_int.h853
-rw-r--r--erts/emulator/nifs/common/prim_socket_nif.c12650
-rw-r--r--erts/emulator/nifs/common/prim_tty_nif.c1036
-rw-r--r--erts/emulator/nifs/common/socket_asyncio.h172
-rw-r--r--erts/emulator/nifs/common/socket_int.h114
-rw-r--r--erts/emulator/nifs/common/socket_io.h232
-rw-r--r--erts/emulator/nifs/common/socket_syncio.h174
-rw-r--r--erts/emulator/nifs/common/socket_util.c267
-rw-r--r--erts/emulator/nifs/common/socket_util.h53
-rw-r--r--erts/emulator/nifs/unix/unix_socket_syncio.c7390
-rw-r--r--erts/emulator/nifs/win32/win_socket_asyncio.c10250
-rw-r--r--erts/emulator/sys/common/erl_check_io.c1
-rw-r--r--erts/emulator/sys/unix/erl_child_setup.c22
-rw-r--r--erts/emulator/sys/unix/erl_unix_sys.h2
-rw-r--r--erts/emulator/sys/unix/sys.c10
-rw-r--r--erts/emulator/sys/unix/sys_env.c10
-rw-r--r--erts/emulator/sys/win32/sys.c73
-rw-r--r--erts/emulator/sys/win32/sys_interrupt.c20
-rw-r--r--erts/emulator/test/Makefile24
-rw-r--r--erts/emulator/test/a_SUITE.erl14
-rw-r--r--erts/emulator/test/bif_SUITE.erl223
-rw-r--r--erts/emulator/test/binary_SUITE.erl244
-rw-r--r--erts/emulator/test/binary_SUITE_data/Makefile.src33
-rw-r--r--erts/emulator/test/binary_SUITE_data/call_local_drv.c204
-rw-r--r--erts/emulator/test/binary_SUITE_data/send_term_local_drv.c96
-rw-r--r--erts/emulator/test/bs_construct_SUITE.erl530
-rw-r--r--erts/emulator/test/bs_match_bin_SUITE.erl58
-rw-r--r--erts/emulator/test/bs_match_int_SUITE.erl729
-rw-r--r--erts/emulator/test/bs_match_misc_SUITE.erl37
-rw-r--r--erts/emulator/test/bs_utf_SUITE.erl136
-rw-r--r--erts/emulator/test/bs_utf_SUITE_data/NormalizationTest.txt19047
-rw-r--r--erts/emulator/test/code_SUITE.erl32
-rw-r--r--erts/emulator/test/code_SUITE_data/call_fun_before_load.erl45
-rw-r--r--erts/emulator/test/decode_packet_SUITE.erl23
-rw-r--r--erts/emulator/test/dirty_nif_SUITE.erl446
-rw-r--r--erts/emulator/test/dirty_nif_SUITE_data/Makefile.src2
-rw-r--r--erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c283
-rw-r--r--erts/emulator/test/dirty_nif_SUITE_data/on_halt_a.c23
-rw-r--r--erts/emulator/test/dirty_nif_SUITE_data/on_halt_b.c23
-rw-r--r--erts/emulator/test/dirty_nif_SUITE_data/on_halt_c.c23
-rw-r--r--erts/emulator/test/dirty_nif_SUITE_data/on_halt_d.c23
-rw-r--r--erts/emulator/test/dirty_nif_SUITE_data/on_halt_e.c23
-rw-r--r--erts/emulator/test/dirty_nif_SUITE_data/on_halt_f.c23
-rw-r--r--erts/emulator/test/dirty_nif_SUITE_data/on_halt_nif.c96
-rw-r--r--erts/emulator/test/distribution_SUITE.erl57
-rw-r--r--erts/emulator/test/erl_link_SUITE.erl47
-rw-r--r--erts/emulator/test/erts_test_utils.erl63
-rw-r--r--erts/emulator/test/exception_SUITE.erl2
-rw-r--r--erts/emulator/test/fun_SUITE.erl8
-rw-r--r--erts/emulator/test/hash_SUITE.erl4
-rw-r--r--erts/emulator/test/jit_SUITE.erl62
-rw-r--r--erts/emulator/test/map_SUITE.erl246
-rw-r--r--erts/emulator/test/match_spec_SUITE.erl156
-rw-r--r--erts/emulator/test/monitor_SUITE.erl6
-rw-r--r--erts/emulator/test/nif_SUITE.erl351
-rw-r--r--erts/emulator/test/nif_SUITE_data/nif_SUITE.c131
-rw-r--r--erts/emulator/test/node_container_SUITE.erl107
-rw-r--r--erts/emulator/test/op_SUITE.erl255
-rw-r--r--erts/emulator/test/persistent_term_SUITE.erl153
-rw-r--r--erts/emulator/test/process_SUITE.erl228
-rw-r--r--erts/emulator/test/sensitive_SUITE.erl4
-rw-r--r--erts/emulator/test/small_SUITE.erl306
-rw-r--r--erts/emulator/test/statistics_SUITE.erl4
-rw-r--r--erts/emulator/test/trace_call_memory_SUITE.erl366
-rw-r--r--erts/emulator/test/trace_call_time_SUITE.erl76
-rw-r--r--erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.S2
-rwxr-xr-xerts/emulator/utils/beam_makeops1
-rwxr-xr-xerts/emulator/utils/make_driver_tab2
-rw-r--r--erts/emulator/zlib/compress.c6
-rw-r--r--erts/emulator/zlib/crc32.c33
-rw-r--r--erts/emulator/zlib/deflate.c218
-rw-r--r--erts/emulator/zlib/deflate.h4
-rw-r--r--erts/emulator/zlib/inflate.c7
-rw-r--r--erts/emulator/zlib/inftrees.c6
-rw-r--r--erts/emulator/zlib/inftrees.h2
-rw-r--r--erts/emulator/zlib/trees.c123
-rw-r--r--erts/emulator/zlib/uncompr.c4
-rw-r--r--erts/emulator/zlib/zconf.h19
-rw-r--r--erts/emulator/zlib/zlib.h20
-rw-r--r--erts/emulator/zlib/zutil.c16
-rw-r--r--erts/emulator/zlib/zutil.h1
-rw-r--r--erts/epmd/src/epmd_srv.c7
-rw-r--r--erts/etc/common/Makefile.in17
-rw-r--r--erts/etc/common/ct_run.c26
-rw-r--r--erts/etc/common/erlc.c9
-rw-r--r--erts/etc/common/erlexec.c66
-rw-r--r--erts/etc/common/escript.c6
-rw-r--r--erts/etc/common/etc_common.h3
-rw-r--r--erts/etc/common/heart.c22
-rw-r--r--erts/etc/common/inet_gethost.c5
-rw-r--r--erts/etc/unix/etp.py10
-rw-r--r--erts/etc/unix/to_erl.c26
-rw-r--r--erts/etc/win32/Install.c33
-rw-r--r--erts/etc/win32/Makefile3
-rw-r--r--erts/etc/win32/erl.c24
-rw-r--r--erts/etc/win32/nsis/erlang20.nsi2
-rw-r--r--erts/etc/win32/win_erlexec.c123
-rw-r--r--erts/include/internal/ethread.h3
-rw-r--r--erts/lib_src/Makefile.in13
-rw-r--r--erts/lib_src/pthread/ethread.c24
-rw-r--r--erts/lib_src/yielding_c_fun/main_target.mk12
-rw-r--r--erts/prebuild.keep2
-rw-r--r--erts/preloaded/ebin/atomics.beambin4692 -> 4664 bytes
-rw-r--r--erts/preloaded/ebin/counters.beambin4952 -> 4920 bytes
-rw-r--r--erts/preloaded/ebin/erl_init.beambin2912 -> 2880 bytes
-rw-r--r--erts/preloaded/ebin/erl_prim_loader.beambin60064 -> 59772 bytes
-rw-r--r--erts/preloaded/ebin/erl_tracer.beambin2524 -> 2504 bytes
-rw-r--r--erts/preloaded/ebin/erlang.beambin132640 -> 130784 bytes
-rw-r--r--erts/preloaded/ebin/erts_code_purger.beambin14336 -> 14212 bytes
-rw-r--r--erts/preloaded/ebin/erts_dirty_process_signal_handler.beambin3108 -> 3104 bytes
-rw-r--r--erts/preloaded/ebin/erts_internal.beambin28628 -> 29848 bytes
-rw-r--r--erts/preloaded/ebin/erts_literal_area_collector.beambin5932 -> 5892 bytes
-rw-r--r--erts/preloaded/ebin/init.beambin61584 -> 64256 bytes
-rw-r--r--erts/preloaded/ebin/persistent_term.beambin2052 -> 2036 bytes
-rw-r--r--erts/preloaded/ebin/prim_buffer.beambin4208 -> 4228 bytes
-rw-r--r--erts/preloaded/ebin/prim_eval.beambin1652 -> 1632 bytes
-rw-r--r--erts/preloaded/ebin/prim_file.beambin33336 -> 33348 bytes
-rw-r--r--erts/preloaded/ebin/prim_inet.beambin102424 -> 101892 bytes
-rw-r--r--erts/preloaded/ebin/prim_net.beambin11812 -> 11768 bytes
-rw-r--r--erts/preloaded/ebin/prim_socket.beambin37004 -> 36556 bytes
-rw-r--r--erts/preloaded/ebin/prim_zip.beambin26256 -> 26320 bytes
-rw-r--r--erts/preloaded/ebin/socket_registry.beambin20080 -> 19908 bytes
-rw-r--r--erts/preloaded/ebin/zlib.beambin22660 -> 22664 bytes
-rw-r--r--erts/preloaded/src/erl_prim_loader.erl8
-rw-r--r--erts/preloaded/src/erlang.erl116
-rw-r--r--erts/preloaded/src/erts.app.src4
-rw-r--r--erts/preloaded/src/erts_internal.erl60
-rw-r--r--erts/preloaded/src/init.erl156
-rw-r--r--erts/preloaded/src/prim_file.erl6
-rw-r--r--erts/preloaded/src/prim_inet.erl90
-rw-r--r--erts/preloaded/src/prim_socket.erl18
-rw-r--r--erts/test/Makefile7
-rw-r--r--erts/test/erl_print_SUITE.erl118
-rw-r--r--erts/test/erlc_SUITE.erl38
-rw-r--r--erts/test/erlc_SUITE_data/src/😀/erl_test_unicode.erl25
-rw-r--r--erts/test/ethread_SUITE.erl12
-rw-r--r--erts/test/ethread_SUITE_data/ethread_tests.c121
-rw-r--r--erts/test/otp_SUITE.erl557
-rw-r--r--erts/vsn.mk2
-rw-r--r--lib/Makefile29
-rw-r--r--lib/asn1/c_src/asn1_erl_nif.c2
-rw-r--r--lib/asn1/src/asn1_db.erl13
-rw-r--r--lib/asn1/src/asn1ct.erl17
-rw-r--r--lib/asn1/src/asn1ct_check.erl11
-rw-r--r--lib/asn1/src/asn1ct_gen_check.erl6
-rw-r--r--lib/asn1/src/asn1ct_gen_jer.erl9
-rw-r--r--lib/asn1/src/asn1rt_nif.erl12
-rw-r--r--lib/asn1/src/asn1rtt_ext.erl6
-rw-r--r--lib/asn1/test/asn1_SUITE_data/ValueTest.asn4
-rw-r--r--lib/asn1/test/error_SUITE.erl6
-rw-r--r--lib/common_test/doc/src/ct.xml531
-rw-r--r--lib/common_test/doc/src/ct_hooks_chapter.xml205
-rw-r--r--lib/common_test/doc/src/ct_suite.xml8
-rw-r--r--lib/common_test/doc/src/run_test_chapter.xml2
-rw-r--r--lib/common_test/doc/src/write_test_chapter.xml6
-rw-r--r--lib/common_test/src/ct.erl445
-rw-r--r--lib/common_test/src/ct_framework.erl4
-rw-r--r--lib/common_test/src/ct_groups.erl9
-rw-r--r--lib/common_test/src/ct_netconfc.erl20
-rw-r--r--lib/common_test/src/ct_property_test.erl5
-rw-r--r--lib/common_test/src/ct_release_test.erl4
-rw-r--r--lib/common_test/src/ct_run.erl2
-rw-r--r--lib/common_test/src/ct_ssh.erl6
-rw-r--r--lib/common_test/src/ct_suite.erl4
-rw-r--r--lib/common_test/src/ct_testspec.erl6
-rw-r--r--lib/common_test/src/ct_util.erl4
-rw-r--r--lib/common_test/src/cth_conn_log.erl4
-rw-r--r--lib/common_test/src/test_server.erl17
-rw-r--r--lib/common_test/src/test_server_ctrl.erl25
-rw-r--r--lib/common_test/src/test_server_node.erl28
-rw-r--r--lib/common_test/test/test_server_SUITE.erl4
-rw-r--r--lib/common_test/test_server/.gitignore4
-rw-r--r--lib/compiler/Makefile1
-rw-r--r--lib/compiler/doc/src/Makefile4
-rw-r--r--lib/compiler/doc/src/compile.xml32
-rw-r--r--lib/compiler/doc/src/internal.xml3
-rw-r--r--lib/compiler/doc/src/notes.xml15
-rw-r--r--lib/compiler/internal_doc/ssa_checks.md124
-rwxr-xr-xlib/compiler/scripts/smoke2
-rw-r--r--lib/compiler/src/.gitignore1
-rw-r--r--lib/compiler/src/Makefile29
-rw-r--r--lib/compiler/src/beam_a.erl22
-rw-r--r--lib/compiler/src/beam_asm.erl17
-rw-r--r--lib/compiler/src/beam_block.erl4
-rw-r--r--lib/compiler/src/beam_bounds.erl476
-rw-r--r--lib/compiler/src/beam_call_types.erl1035
-rw-r--r--lib/compiler/src/beam_clean.erl12
-rw-r--r--lib/compiler/src/beam_disasm.erl200
-rw-r--r--lib/compiler/src/beam_jump.erl58
-rw-r--r--lib/compiler/src/beam_kernel_to_ssa.erl16
-rw-r--r--lib/compiler/src/beam_listing.erl13
-rw-r--r--lib/compiler/src/beam_ssa.erl61
-rw-r--r--lib/compiler/src/beam_ssa_alias.erl926
-rw-r--r--lib/compiler/src/beam_ssa_bc_size.erl43
-rw-r--r--lib/compiler/src/beam_ssa_bool.erl11
-rw-r--r--lib/compiler/src/beam_ssa_bsm.erl25
-rw-r--r--lib/compiler/src/beam_ssa_check.erl378
-rw-r--r--lib/compiler/src/beam_ssa_codegen.erl329
-rw-r--r--lib/compiler/src/beam_ssa_dead.erl8
-rw-r--r--lib/compiler/src/beam_ssa_lint.erl6
-rw-r--r--lib/compiler/src/beam_ssa_opt.erl482
-rw-r--r--lib/compiler/src/beam_ssa_pp.erl71
-rw-r--r--lib/compiler/src/beam_ssa_pre_codegen.erl570
-rw-r--r--lib/compiler/src/beam_ssa_private_append.erl652
-rw-r--r--lib/compiler/src/beam_ssa_recv.erl152
-rw-r--r--lib/compiler/src/beam_ssa_share.erl4
-rw-r--r--lib/compiler/src/beam_ssa_throw.erl2
-rw-r--r--lib/compiler/src/beam_ssa_type.erl866
-rw-r--r--lib/compiler/src/beam_trim.erl17
-rw-r--r--lib/compiler/src/beam_types.erl558
-rw-r--r--lib/compiler/src/beam_types.hrl70
-rw-r--r--lib/compiler/src/beam_utils.erl5
-rw-r--r--lib/compiler/src/beam_validator.erl618
-rw-r--r--lib/compiler/src/cerl.erl48
-rw-r--r--lib/compiler/src/cerl_trees.erl25
-rw-r--r--lib/compiler/src/compile.erl35
-rw-r--r--lib/compiler/src/compiler.app.src7
-rw-r--r--lib/compiler/src/core_parse.hrl4
-rw-r--r--lib/compiler/src/core_pp.erl9
-rw-r--r--lib/compiler/src/core_scan.erl47
-rwxr-xr-xlib/compiler/src/genop.tab28
-rw-r--r--lib/compiler/src/sys_core_fold.erl202
-rw-r--r--lib/compiler/src/v3_core.erl408
-rw-r--r--lib/compiler/src/v3_kernel.erl86
-rw-r--r--lib/compiler/src/v3_kernel.hrl4
-rw-r--r--lib/compiler/src/v3_kernel_pp.erl6
-rw-r--r--lib/compiler/test/Makefile34
-rw-r--r--lib/compiler/test/andor_SUITE.erl11
-rw-r--r--lib/compiler/test/beam_block_SUITE.erl35
-rw-r--r--lib/compiler/test/beam_bounds_SUITE.erl342
-rw-r--r--lib/compiler/test/beam_except_SUITE.erl20
-rw-r--r--lib/compiler/test/beam_jump_SUITE.erl42
-rw-r--r--lib/compiler/test/beam_ssa_SUITE.erl84
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE.erl138
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/alias.erl674
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/alias_chain.erl117
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/alias_non_convergence.erl40
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/annotations.erl40
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/appendable.erl162
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/gen_bs_size_unit_checks.erl242
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/private_append.erl1004
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/ret_annotation.erl46
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl341
-rw-r--r--lib/compiler/test/beam_type_SUITE.erl515
-rw-r--r--lib/compiler/test/beam_types_SUITE.erl12
-rw-r--r--lib/compiler/test/beam_validator_SUITE.erl122
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/bad_tuples.S55
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/no_exception_in_catch.S26
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S32
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/singleton_inference.erl7
-rw-r--r--lib/compiler/test/bif_SUITE.erl43
-rw-r--r--lib/compiler/test/bs_bincomp_SUITE.erl91
-rw-r--r--lib/compiler/test/bs_construct_SUITE.erl42
-rw-r--r--lib/compiler/test/bs_match_SUITE.erl740
-rw-r--r--lib/compiler/test/bs_utf_SUITE.erl33
-rw-r--r--lib/compiler/test/compilation_SUITE.erl4
-rw-r--r--lib/compiler/test/compilation_SUITE_data/infinite_loop.erl10
-rw-r--r--lib/compiler/test/compilation_SUITE_data/string_table.erl8
-rw-r--r--lib/compiler/test/compile_SUITE.erl100
-rw-r--r--lib/compiler/test/compile_SUITE_data/annotations_pp.erl31
-rw-r--r--lib/compiler/test/compile_SUITE_data/bs_init_writable.erl45
-rw-r--r--lib/compiler/test/core_SUITE.erl56
-rw-r--r--lib/compiler/test/core_SUITE_data/nif.erl17
-rw-r--r--lib/compiler/test/core_alias_SUITE.erl16
-rw-r--r--lib/compiler/test/core_fold_SUITE.erl165
-rw-r--r--lib/compiler/test/fun_SUITE.erl66
-rw-r--r--lib/compiler/test/guard_SUITE.erl69
-rw-r--r--lib/compiler/test/lc_SUITE.erl10
-rw-r--r--lib/compiler/test/map_SUITE.erl81
-rw-r--r--lib/compiler/test/match_SUITE.erl182
-rw-r--r--lib/compiler/test/mc_SUITE.erl281
-rw-r--r--lib/compiler/test/property_test/beam_types_prop.erl37
-rw-r--r--lib/compiler/test/receive_SUITE.erl7
-rw-r--r--lib/compiler/test/record_SUITE.erl38
-rw-r--r--lib/compiler/test/test_lib.erl3
-rw-r--r--lib/compiler/test/warnings_SUITE.erl9
-rw-r--r--lib/compiler/vsn.mk2
-rw-r--r--lib/crypto/c_src/algorithms.c8
-rw-r--r--lib/crypto/c_src/check_openssl.cocci6
-rw-r--r--lib/crypto/c_src/crypto.c1
-rw-r--r--lib/crypto/c_src/digest.c18
-rw-r--r--lib/crypto/c_src/engine.c4
-rw-r--r--lib/crypto/c_src/hash.c37
-rw-r--r--lib/crypto/c_src/hash.h3
-rw-r--r--lib/crypto/c_src/info.c11
-rw-r--r--lib/crypto/c_src/openssl_config.h7
-rw-r--r--lib/crypto/c_src/otp_test_engine.c2
-rw-r--r--lib/crypto/doc/src/algorithm_details.xml6
-rw-r--r--lib/crypto/doc/src/crypto.xml19
-rw-r--r--lib/crypto/doc/src/new_api.xml4
-rw-r--r--lib/crypto/src/crypto.erl54
-rw-r--r--lib/crypto/test/crypto_SUITE.erl158
-rw-r--r--lib/dialyzer/README5
-rw-r--r--lib/dialyzer/doc/src/dialyzer.xml98
-rw-r--r--lib/dialyzer/doc/src/dialyzer_chapter.xml135
-rw-r--r--lib/dialyzer/src/Makefile9
-rw-r--r--lib/dialyzer/src/dialyzer.app.src5
-rw-r--r--lib/dialyzer/src/dialyzer.erl188
-rw-r--r--lib/dialyzer/src/dialyzer.hrl78
-rw-r--r--lib/dialyzer/src/dialyzer_analysis_callgraph.erl84
-rw-r--r--lib/dialyzer/src/dialyzer_behaviours.erl34
-rw-r--r--lib/dialyzer/src/dialyzer_callgraph.erl153
-rw-r--r--lib/dialyzer/src/dialyzer_cl.erl225
-rw-r--r--lib/dialyzer/src/dialyzer_cl_parse.erl808
-rw-r--r--lib/dialyzer/src/dialyzer_codeserver.erl78
-rw-r--r--lib/dialyzer/src/dialyzer_contracts.erl62
-rw-r--r--lib/dialyzer/src/dialyzer_coordinator.erl122
-rw-r--r--lib/dialyzer/src/dialyzer_cplt.erl545
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl80
-rw-r--r--lib/dialyzer/src/dialyzer_gui_wx.erl144
-rw-r--r--lib/dialyzer/src/dialyzer_incremental.erl817
-rw-r--r--lib/dialyzer/src/dialyzer_iplt.erl594
-rw-r--r--lib/dialyzer/src/dialyzer_options.erl247
-rw-r--r--lib/dialyzer/src/dialyzer_plt.erl516
-rw-r--r--lib/dialyzer/src/dialyzer_succ_typings.erl17
-rw-r--r--lib/dialyzer/src/dialyzer_typegraph.erl17
-rw-r--r--lib/dialyzer/src/dialyzer_typesig.erl44
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl133
-rw-r--r--lib/dialyzer/src/dialyzer_worker.erl35
-rw-r--r--lib/dialyzer/src/erl_types.erl725
-rw-r--r--lib/dialyzer/src/typer.erl19
-rw-r--r--lib/dialyzer/src/typer_core.erl27
-rw-r--r--lib/dialyzer/test/Makefile5
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs7
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args2
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/results/otp_62210
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour4
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour_old2
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl6
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/src/otp_6221/my_behaviour.erl3
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/src/otp_6221/my_callbacks_correct.erl17
-rw-r--r--lib/dialyzer/test/cplt_SUITE.erl (renamed from lib/dialyzer/test/plt_SUITE.erl)132
-rw-r--r--lib/dialyzer/test/cplt_SUITE_data/type_deps.erl (renamed from lib/dialyzer/test/plt_SUITE_data/type_deps.erl)0
-rw-r--r--lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/dialyzer_options1
-rw-r--r--lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/results/default_ignore_overlapping_contract0
-rw-r--r--lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/src/default_ignore_overlapping_contract.erl13
-rw-r--r--lib/dialyzer/test/dialyzer_SUITE.erl117
-rw-r--r--lib/dialyzer/test/dialyzer_cl_SUITE.erl31
-rw-r--r--lib/dialyzer/test/dialyzer_common.erl22
-rw-r--r--lib/dialyzer/test/dialyzer_utils_SUITE.erl52
-rw-r--r--lib/dialyzer/test/incremental_SUITE.erl1134
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/extra_modules/ebin/.gitignore5
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_module.erl14
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_modules.app.src8
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/fix.erl10
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/got_fixed.erl6
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/m1.erl14
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/m2.erl14
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/m3.erl14
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/m4.erl14
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/m5.erl14
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/m6.erl14
-rw-r--r--lib/dialyzer/test/indent2_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/callbacks_and_specs12
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/contract34
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/contracts_with_subtypes12
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/record_update6
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/sample_behaviour4
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/simple24
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl4
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/map_galore.erl4
-rw-r--r--lib/dialyzer/test/iplt_SUITE.erl841
-rw-r--r--lib/dialyzer/test/iplt_SUITE_data/type_deps.erl18
-rw-r--r--lib/dialyzer/test/line_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/map_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/contract_violation5
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/opaque_key25
-rw-r--r--lib/dialyzer/test/map_SUITE_data/src/map_galore.erl4
-rw-r--r--lib/dialyzer/test/nowarn_function_SUITE_data/dialyzer_options1
-rw-r--r--lib/dialyzer/test/nowarn_function_SUITE_data/results/warn_function12
-rw-r--r--lib/dialyzer/test/nowarn_function_SUITE_data/src/nowarn_function.erl34
-rw-r--r--lib/dialyzer/test/nowarn_function_SUITE_data/src/warn_function.erl30
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/int10
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques5
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/para15
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/simple20
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/recrec/dialyzer.hrl4
-rw-r--r--lib/dialyzer/test/options1_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/options2_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/options2_SUITE_data/results/unknown_function3
-rw-r--r--lib/dialyzer/test/options2_SUITE_data/src/unknown_function.erl14
-rw-r--r--lib/dialyzer/test/options3_SUITE_data/dialyzer_options1
-rw-r--r--lib/dialyzer/test/options3_SUITE_data/results/bad_specs0
-rw-r--r--lib/dialyzer/test/options3_SUITE_data/src/bad_specs.erl8
-rw-r--r--lib/dialyzer/test/overlapping_contract_SUITE_data/dialyzer_options1
-rw-r--r--lib/dialyzer/test/overlapping_contract_SUITE_data/results/overlapping_contract2
-rw-r--r--lib/dialyzer/test/overlapping_contract_SUITE_data/src/overlapping_contract.erl12
-rw-r--r--lib/dialyzer/test/r9c_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/small_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/behaviour_info2
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/binary_nonempty35
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/binary_redef210
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/bs_segments3
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/chars5
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/contract34
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/contract55
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes10
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/empty_list_infimum5
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/gh_71533
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/invalid_spec_25
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/invalid_specs5
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/maps_sum5
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/predef35
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/record_update5
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/redefine_builtin_type0
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/redefine_builtins5
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/stack0
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/tuple_set_crash30
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/types_arity5
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/bs_segments.erl7
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/gh_7153.erl5
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/list_none.erl5
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/redefine_builtin_type.erl24
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/a.erl14
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/b.erl6
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/stack.erl15
-rw-r--r--lib/dialyzer/test/typer_SUITE.erl117
-rw-r--r--lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/user_SUITE_data/dialyzer_options4
-rw-r--r--lib/diameter/src/base/diameter_codec.erl8
-rw-r--r--lib/diameter/src/base/diameter_config.erl53
-rw-r--r--lib/diameter/src/base/diameter_lib.erl8
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl12
-rw-r--r--lib/diameter/src/base/diameter_types.erl6
-rw-r--r--lib/diameter/src/diameter.app.src4
-rw-r--r--lib/diameter/src/diameter.appup.src56
-rw-r--r--lib/diameter/src/info/diameter_dbg.erl14
-rw-r--r--lib/diameter/src/info/diameter_info.erl6
-rw-r--r--lib/diameter/test/diameter_codec_test.erl4
-rw-r--r--lib/diameter/test/diameter_config_SUITE.erl2
-rw-r--r--lib/diameter/test/diameter_gen_tcp_SUITE.erl6
-rw-r--r--lib/diameter/test/diameter_tls_SUITE.erl6
-rw-r--r--lib/edoc/test/edoc_SUITE.erl15
-rw-r--r--lib/edoc/test/edoc_SUITE_data/module_with_feature.erl2
-rw-r--r--lib/eldap/test/eldap_basic_SUITE.erl2
-rw-r--r--lib/erl_docgen/priv/css/otp_doc.css14
-rw-r--r--lib/erl_docgen/priv/dtd/application.dtd2
-rw-r--r--lib/erl_docgen/priv/dtd/book.dtd4
-rw-r--r--lib/erl_docgen/priv/dtd/chapter.dtd4
-rw-r--r--lib/erl_docgen/priv/dtd/common.dtd8
-rw-r--r--lib/erl_docgen/priv/dtd/common.refs.dtd8
-rw-r--r--lib/erl_docgen/priv/dtd/part.dtd2
-rw-r--r--lib/erl_docgen/priv/xsl/db_html.xsl24
-rw-r--r--lib/erl_docgen/priv/xsl/db_man.xsl15
-rw-r--r--lib/erl_docgen/priv/xsl/db_pdf.xsl19
-rw-r--r--lib/erl_docgen/priv/xsl/db_pdf_params.xsl19
-rw-r--r--lib/erl_docgen/src/docgen_xml_to_chunk.erl22
-rwxr-xr-xlib/erl_interface/configure20
-rw-r--r--lib/erl_interface/configure.ac22
-rw-r--r--lib/erl_interface/doc/src/ei_users_guide.xml14
-rw-r--r--lib/erl_interface/src/connect/ei_connect.c20
-rw-r--r--lib/erl_interface/src/connect/ei_connect_int.h13
-rw-r--r--lib/erl_interface/src/prog/erl_call.c8
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE.erl23
-rw-r--r--lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c18
-rw-r--r--lib/erl_interface/test/ei_decode_encode_SUITE.erl7
-rw-r--r--lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c28
-rw-r--r--lib/erl_interface/test/ei_tmo_SUITE.erl21
-rw-r--r--lib/et/doc/src/et_intro.xml4
-rw-r--r--lib/et/src/et_collector.erl2
-rw-r--r--lib/ftp/doc/specs/.gitignore1
-rw-r--r--lib/ftp/doc/src/Makefile6
-rw-r--r--lib/ftp/doc/src/ftp.xml754
-rw-r--r--lib/ftp/doc/src/specs.xml4
-rw-r--r--lib/ftp/src/Makefile3
-rw-r--r--lib/ftp/src/ftp.app.src1
-rw-r--r--lib/ftp/src/ftp.erl2382
-rw-r--r--lib/ftp/src/ftp_internal.erl2467
-rw-r--r--lib/ftp/src/ftp_sup.erl4
-rw-r--r--lib/ftp/test/ftp_SUITE.erl8
-rw-r--r--lib/inets/doc/src/http_uri.xml51
-rw-r--r--lib/inets/doc/src/httpc.xml8
-rw-r--r--lib/inets/doc/src/httpd_util.xml87
-rw-r--r--lib/inets/examples/server_root/Makefile3
-rw-r--r--lib/inets/examples/server_root/conf/8080.conf146
-rw-r--r--lib/inets/examples/server_root/conf/8888.conf63
-rw-r--r--lib/inets/examples/server_root/conf/httpd.conf472
-rw-r--r--lib/inets/examples/server_root/conf/ssl.conf117
-rw-r--r--lib/inets/src/http_client/httpc.erl22
-rw-r--r--lib/inets/src/http_client/httpc_handler.erl132
-rw-r--r--lib/inets/src/http_lib/http_internal.hrl7
-rw-r--r--lib/inets/src/http_lib/http_transport.erl235
-rw-r--r--lib/inets/src/http_lib/http_uri.erl11
-rw-r--r--lib/inets/src/http_server/httpd.erl4
-rw-r--r--lib/inets/src/http_server/httpd_acceptor.erl5
-rw-r--r--lib/inets/src/http_server/httpd_conf.erl164
-rw-r--r--lib/inets/src/http_server/httpd_example.erl4
-rw-r--r--lib/inets/src/http_server/httpd_request_handler.erl14
-rw-r--r--lib/inets/src/http_server/httpd_script_env.erl7
-rw-r--r--lib/inets/src/http_server/httpd_sup.erl93
-rw-r--r--lib/inets/src/http_server/httpd_util.erl129
-rw-r--r--lib/inets/src/http_server/mod_alias.erl5
-rw-r--r--lib/inets/src/inets_app/inets.app.src8
-rw-r--r--lib/inets/test/http_test_lib.erl1
-rw-r--r--lib/inets/test/httpc_SUITE.erl421
-rw-r--r--lib/inets/test/httpc_proxy_SUITE.erl54
-rw-r--r--lib/inets/test/httpd_SUITE.erl91
-rw-r--r--lib/inets/test/httpd_bench_SUITE.erl2
-rw-r--r--lib/inets/test/httpd_time_test.erl2
-rw-r--r--lib/inets/test/inets_socketwrap_SUITE.erl34
-rw-r--r--lib/inets/test/inets_test_lib.erl33
-rw-r--r--lib/inets/test/make_certs.erl1
-rw-r--r--lib/inets/test/rules.mk9
-rw-r--r--lib/jinterface/doc/src/jinterface_users_guide.xml6
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java60
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java9
-rw-r--r--lib/jinterface/prebuild.keep1
-rw-r--r--lib/jinterface/test/jinterface_SUITE.erl14
-rw-r--r--lib/jinterface/test/nc_SUITE.erl6
-rw-r--r--lib/kernel/Makefile2
-rw-r--r--lib/kernel/doc/src/Makefile3
-rw-r--r--lib/kernel/doc/src/application.xml27
-rw-r--r--lib/kernel/doc/src/code.xml88
-rw-r--r--lib/kernel/doc/src/disk_log.xml121
-rw-r--r--lib/kernel/doc/src/erpc.xml10
-rw-r--r--lib/kernel/doc/src/file.xml5
-rw-r--r--lib/kernel/doc/src/global.xml1
-rw-r--r--lib/kernel/doc/src/inet.xml127
-rw-r--r--lib/kernel/doc/src/kernel_app.xml1
-rw-r--r--lib/kernel/doc/src/os.xml6
-rw-r--r--lib/kernel/doc/src/ref_man.xml3
-rw-r--r--lib/kernel/doc/src/rpc.xml10
-rw-r--r--lib/kernel/doc/src/socket.xml783
-rw-r--r--lib/kernel/doc/src/socket_usage.xml114
-rw-r--r--lib/kernel/doc/src/specs.xml1
-rw-r--r--lib/kernel/doc/src/user.xml41
-rw-r--r--lib/kernel/include/dist.hrl8
-rw-r--r--lib/kernel/src/Makefile4
-rw-r--r--lib/kernel/src/application.erl188
-rw-r--r--lib/kernel/src/application_controller.erl30
-rw-r--r--lib/kernel/src/application_master.erl4
-rw-r--r--lib/kernel/src/code.erl356
-rw-r--r--lib/kernel/src/code_server.erl395
-rw-r--r--lib/kernel/src/disk_log.erl173
-rw-r--r--lib/kernel/src/disk_log.hrl25
-rw-r--r--lib/kernel/src/disk_log_1.erl300
-rw-r--r--lib/kernel/src/dist_util.erl139
-rw-r--r--lib/kernel/src/erl_compile_server.erl12
-rw-r--r--lib/kernel/src/erl_erts_errors.erl32
-rw-r--r--lib/kernel/src/erl_kernel_errors.erl4
-rw-r--r--lib/kernel/src/error_logger.erl12
-rw-r--r--lib/kernel/src/erts_debug.erl4
-rw-r--r--lib/kernel/src/file.erl16
-rw-r--r--lib/kernel/src/file_io_server.erl14
-rw-r--r--lib/kernel/src/file_server.erl17
-rw-r--r--lib/kernel/src/gen_sctp.erl6
-rw-r--r--lib/kernel/src/gen_tcp.erl8
-rw-r--r--lib/kernel/src/gen_tcp_socket.erl47
-rw-r--r--lib/kernel/src/gen_udp.erl6
-rw-r--r--lib/kernel/src/global.erl27
-rw-r--r--lib/kernel/src/global_group.erl7
-rw-r--r--lib/kernel/src/group.erl592
-rw-r--r--lib/kernel/src/group_history.erl7
-rw-r--r--lib/kernel/src/inet.erl42
-rw-r--r--lib/kernel/src/inet_db.erl29
-rw-r--r--lib/kernel/src/inet_dns.erl109
-rw-r--r--lib/kernel/src/inet_dns.hrl23
-rw-r--r--lib/kernel/src/inet_dns_record_adts.pl4
-rw-r--r--lib/kernel/src/inet_epmd_dist.erl822
-rw-r--r--lib/kernel/src/inet_epmd_socket.erl485
-rw-r--r--lib/kernel/src/inet_int.hrl3
-rw-r--r--lib/kernel/src/inet_parse.erl13
-rw-r--r--lib/kernel/src/inet_res.erl23
-rw-r--r--lib/kernel/src/inet_tcp_dist.erl492
-rw-r--r--lib/kernel/src/kernel.app.src9
-rw-r--r--lib/kernel/src/kernel.erl78
-rw-r--r--lib/kernel/src/logger_formatter.erl24
-rw-r--r--lib/kernel/src/logger_h_common.erl2
-rw-r--r--lib/kernel/src/logger_olp.erl10
-rw-r--r--lib/kernel/src/logger_simple_h.erl79
-rw-r--r--lib/kernel/src/logger_std_h.erl9
-rw-r--r--lib/kernel/src/net_kernel.erl150
-rw-r--r--lib/kernel/src/pg.erl77
-rw-r--r--lib/kernel/src/prim_tty.erl1265
-rw-r--r--lib/kernel/src/seq_trace.erl4
-rw-r--r--lib/kernel/src/socket.erl1270
-rw-r--r--lib/kernel/src/standard_error.erl43
-rw-r--r--lib/kernel/src/user.erl769
-rw-r--r--lib/kernel/src/user_drv.erl1458
-rw-r--r--lib/kernel/src/user_sup.erl40
-rw-r--r--lib/kernel/src/wrap_log_reader.erl8
-rw-r--r--lib/kernel/test/Makefile2
-rw-r--r--lib/kernel/test/application_SUITE.erl82
-rw-r--r--lib/kernel/test/code_SUITE.erl118
-rw-r--r--lib/kernel/test/disk_log_SUITE.erl268
-rw-r--r--lib/kernel/test/erl_distribution_wb_SUITE.erl24
-rw-r--r--lib/kernel/test/file_SUITE.erl6
-rw-r--r--lib/kernel/test/gen_sctp_SUITE.erl13
-rw-r--r--lib/kernel/test/gen_tcp_api_SUITE.erl44
-rw-r--r--lib/kernel/test/gen_udp_SUITE.erl51
-rw-r--r--lib/kernel/test/inet_SUITE.erl18
-rw-r--r--lib/kernel/test/inet_res_SUITE.erl5
-rw-r--r--lib/kernel/test/inet_res_SUITE_data/otptest/otptest.zone3
-rw-r--r--lib/kernel/test/inet_sockopt_SUITE.erl196
-rw-r--r--lib/kernel/test/inet_sockopt_SUITE_data/sockopt_helper.c6
-rw-r--r--lib/kernel/test/init_SUITE.erl19
-rw-r--r--lib/kernel/test/interactive_shell_SUITE.erl2740
-rw-r--r--lib/kernel/test/logger_SUITE.erl4
-rw-r--r--lib/kernel/test/logger_disk_log_h_SUITE.erl22
-rw-r--r--lib/kernel/test/logger_formatter_SUITE.erl18
-rw-r--r--lib/kernel/test/logger_simple_h_SUITE.erl18
-rw-r--r--lib/kernel/test/logger_std_h_SUITE.erl2
-rw-r--r--lib/kernel/test/logger_stress_SUITE.erl2
-rw-r--r--lib/kernel/test/os_SUITE.erl23
-rw-r--r--lib/kernel/test/pg_SUITE.erl29
-rw-r--r--lib/kernel/test/rtnode.erl575
-rw-r--r--lib/kernel/test/socket_SUITE.erl2311
-rw-r--r--lib/kernel/test/socket_test_evaluator.erl42
-rw-r--r--lib/kernel/test/socket_test_lib.erl144
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_socket.erl160
-rw-r--r--lib/kernel/test/standard_error_SUITE.erl6
-rwxr-xr-xlib/megaco/configure20
-rw-r--r--lib/megaco/configure.ac22
-rw-r--r--lib/megaco/doc/src/megaco_intro.xml4
-rw-r--r--lib/mnesia/src/mnesia.app.src2
-rw-r--r--lib/mnesia/src/mnesia_checkpoint.erl5
-rw-r--r--lib/mnesia/src/mnesia_tm.erl13
-rw-r--r--lib/mnesia/test/mnesia_trans_access_test.erl28
-rw-r--r--lib/observer/doc/src/observer.xml39
-rw-r--r--lib/observer/src/crashdump_viewer.erl4
-rw-r--r--lib/observer/src/observer.app.src6
-rw-r--r--lib/observer/src/observer.erl44
-rw-r--r--lib/observer/src/observer_wx.erl23
-rw-r--r--lib/observer/src/ttb.erl6
-rw-r--r--lib/observer/test/observer_SUITE.erl43
-rw-r--r--lib/odbc/c_src/odbcserver.c4
-rwxr-xr-xlib/odbc/configure25
-rw-r--r--lib/odbc/configure.ac22
-rw-r--r--lib/os_mon/c_src/cpu_sup.c93
-rw-r--r--lib/os_mon/c_src/win32sysinfo.c47
-rw-r--r--lib/os_mon/doc/src/cpu_sup.xml2
-rw-r--r--lib/os_mon/doc/src/disksup.xml51
-rw-r--r--lib/os_mon/doc/src/notes.xml17
-rw-r--r--lib/os_mon/src/cpu_sup.erl5
-rw-r--r--lib/os_mon/src/disksup.erl210
-rw-r--r--lib/os_mon/src/memsup.erl2
-rw-r--r--lib/os_mon/src/os_mon.app.src4
-rw-r--r--lib/os_mon/src/os_mon_sysinfo.erl12
-rw-r--r--lib/os_mon/test/cpu_sup_SUITE.erl2
-rw-r--r--lib/os_mon/test/disksup_SUITE.erl30
-rw-r--r--lib/os_mon/vsn.mk2
-rw-r--r--lib/parsetools/doc/src/leex.xml55
-rw-r--r--lib/parsetools/include/leexinc.hrl377
-rw-r--r--lib/parsetools/src/leex.erl190
-rw-r--r--lib/parsetools/test/leex_SUITE.erl338
-rw-r--r--lib/parsetools/test/yecc_SUITE.erl2
-rw-r--r--lib/public_key/.gitignore1
-rw-r--r--lib/public_key/c_src/Makefile2
-rw-r--r--lib/public_key/src/pubkey_ocsp.erl360
-rw-r--r--lib/public_key/src/pubkey_os_cacerts.erl5
-rw-r--r--lib/public_key/src/public_key.erl93
-rw-r--r--lib/reltool/doc/src/reltool.xml76
-rw-r--r--lib/reltool/doc/src/reltool_examples.xml75
-rw-r--r--lib/reltool/doc/src/reltool_intro.xml4
-rw-r--r--lib/reltool/src/reltool.app.src8
-rw-r--r--lib/reltool/src/reltool.hrl25
-rw-r--r--lib/reltool/src/reltool_server.erl44
-rw-r--r--lib/reltool/src/reltool_sys_win.erl6
-rw-r--r--lib/reltool/src/reltool_target.erl119
-rw-r--r--lib/reltool/test/reltool_server_SUITE.erl45
-rw-r--r--lib/runtime_tools/doc/src/Makefile6
-rw-r--r--lib/runtime_tools/doc/src/dbg.xml5
-rw-r--r--lib/runtime_tools/doc/src/erts_alloc_config.xml197
-rw-r--r--lib/runtime_tools/doc/src/instrument.xml (renamed from lib/tools/doc/src/instrument.xml)2
-rw-r--r--lib/runtime_tools/doc/src/part.xml3
-rw-r--r--lib/runtime_tools/doc/src/ref_man.xml4
-rw-r--r--lib/runtime_tools/doc/src/specs.xml1
-rw-r--r--lib/runtime_tools/src/Makefile3
-rw-r--r--lib/runtime_tools/src/dbg.erl24
-rw-r--r--lib/runtime_tools/src/dyntrace.erl24
-rw-r--r--lib/runtime_tools/src/erts_alloc_config.erl733
-rw-r--r--lib/runtime_tools/src/instrument.erl (renamed from lib/tools/src/instrument.erl)8
-rw-r--r--lib/runtime_tools/src/runtime_tools.app.src4
-rw-r--r--lib/runtime_tools/src/system_information.erl40
-rw-r--r--lib/runtime_tools/test/Makefile2
-rw-r--r--lib/runtime_tools/test/dbg_SUITE.erl67
-rw-r--r--lib/runtime_tools/test/erts_alloc_config_SUITE.erl193
-rw-r--r--lib/runtime_tools/test/instrument_SUITE.erl (renamed from lib/tools/test/instrument_SUITE.erl)2
-rw-r--r--lib/runtime_tools/test/system_information_SUITE.erl1
-rw-r--r--lib/sasl/src/sasl.app.src4
-rw-r--r--lib/sasl/src/systools_make.erl12
-rw-r--r--lib/sasl/src/systools_rc.erl6
-rw-r--r--lib/sasl/test/Makefile1
-rw-r--r--lib/sasl/test/release_handler_SUITE.erl3
-rw-r--r--lib/sasl/test/systools_SUITE.erl47
-rw-r--r--lib/snmp/doc/src/snmp_intro.xml4
-rw-r--r--lib/snmp/src/agent/snmpa_net_if.erl8
-rw-r--r--lib/snmp/src/app/snmp.app.src7
-rw-r--r--lib/ssh/src/.gitignore1
-rw-r--r--lib/ssh/src/ssh.app.src6
-rw-r--r--lib/ssh/src/ssh_acceptor.erl6
-rw-r--r--lib/ssh/src/ssh_cli.erl20
-rw-r--r--lib/ssh/src/ssh_dbg.erl2
-rw-r--r--lib/ssl/Makefile4
-rw-r--r--lib/ssl/doc/src/ssl.xml204
-rw-r--r--lib/ssl/doc/src/standards_compliance.xml52
-rw-r--r--lib/ssl/doc/src/using_ssl.xml2
-rw-r--r--lib/ssl/src/.gitignore1
-rw-r--r--lib/ssl/src/Makefile7
-rw-r--r--lib/ssl/src/dtls_connection.erl83
-rw-r--r--lib/ssl/src/dtls_gen_connection.erl13
-rw-r--r--lib/ssl/src/dtls_handshake.erl42
-rw-r--r--lib/ssl/src/dtls_handshake.hrl10
-rw-r--r--lib/ssl/src/dtls_record.erl120
-rw-r--r--lib/ssl/src/dtls_v1.erl48
-rw-r--r--lib/ssl/src/inet6_tls_dist.erl20
-rw-r--r--lib/ssl/src/inet_tls_dist.erl825
-rw-r--r--lib/ssl/src/ssl.app.src9
-rw-r--r--lib/ssl/src/ssl.erl2420
-rw-r--r--lib/ssl/src/ssl_certificate.erl72
-rw-r--r--lib/ssl/src/ssl_cipher.erl315
-rw-r--r--lib/ssl/src/ssl_cipher.hrl3
-rw-r--r--lib/ssl/src/ssl_config.erl220
-rw-r--r--lib/ssl/src/ssl_gen_statem.erl309
-rw-r--r--lib/ssl/src/ssl_handshake.erl641
-rw-r--r--lib/ssl/src/ssl_handshake.hrl22
-rw-r--r--lib/ssl/src/ssl_internal.hrl118
-rw-r--r--lib/ssl/src/ssl_logger.erl17
-rw-r--r--lib/ssl/src/ssl_record.erl11
-rw-r--r--lib/ssl/src/ssl_record.hrl34
-rw-r--r--lib/ssl/src/ssl_session.erl26
-rw-r--r--lib/ssl/src/ssl_trace.erl512
-rw-r--r--lib/ssl/src/tls_client_connection_1_3.erl1013
-rw-r--r--lib/ssl/src/tls_connection.erl57
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl740
-rw-r--r--lib/ssl/src/tls_dtls_connection.erl81
-rw-r--r--lib/ssl/src/tls_dyn_connection_sup.erl9
-rw-r--r--lib/ssl/src/tls_gen_connection.erl30
-rw-r--r--lib/ssl/src/tls_gen_connection_1_3.erl382
-rw-r--r--lib/ssl/src/tls_handshake.erl74
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl1852
-rw-r--r--lib/ssl/src/tls_record.erl219
-rw-r--r--lib/ssl/src/tls_record.hrl4
-rw-r--r--lib/ssl/src/tls_record_1_3.erl19
-rw-r--r--lib/ssl/src/tls_record_1_3.hrl6
-rw-r--r--lib/ssl/src/tls_sender.erl78
-rw-r--r--lib/ssl/src/tls_server_connection_1_3.erl914
-rw-r--r--lib/ssl/src/tls_server_session_ticket.erl241
-rw-r--r--lib/ssl/src/tls_socket.erl51
-rw-r--r--lib/ssl/src/tls_v1.erl399
-rw-r--r--lib/ssl/test/Makefile11
-rw-r--r--lib/ssl/test/cryptcookie.erl747
-rw-r--r--lib/ssl/test/dist_cryptcookie.erl595
-rw-r--r--lib/ssl/test/dtls_api_SUITE.erl6
-rw-r--r--lib/ssl/test/inet_crypto_dist.erl1746
-rw-r--r--lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl230
-rw-r--r--lib/ssl/test/inet_epmd_cryptcookie_socket_ktls.erl276
-rw-r--r--lib/ssl/test/inet_epmd_dist_cryptcookie_inet.erl200
-rw-r--r--lib/ssl/test/inet_epmd_dist_cryptcookie_socket.erl241
-rw-r--r--lib/ssl/test/openssl_alpn_SUITE.erl6
-rw-r--r--lib/ssl/test/openssl_cipher_suite_SUITE.erl22
-rw-r--r--lib/ssl/test/openssl_client_cert_SUITE.erl130
-rw-r--r--lib/ssl/test/openssl_mfl_SUITE.erl10
-rw-r--r--lib/ssl/test/openssl_npn_SUITE.erl22
-rw-r--r--lib/ssl/test/openssl_ocsp_SUITE.erl118
-rw-r--r--lib/ssl/test/openssl_renegotiate_SUITE.erl2
-rw-r--r--lib/ssl/test/openssl_server_cert_SUITE.erl82
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_chain.erl211
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_handshake.erl87
-rw-r--r--lib/ssl/test/ssl_ECC_SUITE.erl35
-rw-r--r--lib/ssl/test/ssl_api_SUITE.erl1504
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl345
-rw-r--r--lib/ssl/test/ssl_bench_test_lib.erl37
-rw-r--r--lib/ssl/test/ssl_cert_SUITE.erl183
-rw-r--r--lib/ssl/test/ssl_cert_tests.erl37
-rw-r--r--lib/ssl/test/ssl_cipher_SUITE.erl43
-rw-r--r--lib/ssl/test/ssl_cipher_suite_SUITE.erl98
-rw-r--r--lib/ssl/test/ssl_dist_SUITE.erl426
-rw-r--r--lib/ssl/test/ssl_dist_bench_SUITE.erl876
-rw-r--r--lib/ssl/test/ssl_dist_test_lib.erl27
-rw-r--r--lib/ssl/test/ssl_handshake_SUITE.erl85
-rw-r--r--lib/ssl/test/ssl_mfl_SUITE.erl10
-rw-r--r--lib/ssl/test/ssl_npn_SUITE.erl12
-rw-r--r--lib/ssl/test/ssl_npn_hello_SUITE.erl9
-rw-r--r--lib/ssl/test/ssl_pem_cache_SUITE.erl30
-rw-r--r--lib/ssl/test/ssl_reject_SUITE.erl30
-rw-r--r--lib/ssl/test/ssl_renegotiate_SUITE.erl27
-rw-r--r--lib/ssl/test/ssl_session_SUITE.erl6
-rw-r--r--lib/ssl/test/ssl_session_ticket_SUITE.erl172
-rw-r--r--lib/ssl/test/ssl_sni_SUITE.erl64
-rw-r--r--lib/ssl/test/ssl_test_lib.erl738
-rw-r--r--lib/ssl/test/ssl_test_lib.hrl22
-rw-r--r--lib/ssl/test/ssl_trace_SUITE.erl493
-rw-r--r--lib/ssl/test/ssl_use_srtp_SUITE.erl176
-rw-r--r--lib/ssl/test/tls_1_3_record_SUITE.erl18
-rw-r--r--lib/ssl/test/tls_1_3_version_SUITE.erl29
-rw-r--r--lib/ssl/test/tls_api_SUITE.erl45
-rw-r--r--lib/ssl/test/tls_server_session_ticket_SUITE.erl99
-rw-r--r--lib/stdlib/Makefile1
-rw-r--r--lib/stdlib/doc/src/Makefile2
-rw-r--r--lib/stdlib/doc/src/argparse.xml739
-rw-r--r--lib/stdlib/doc/src/base64.xml119
-rw-r--r--lib/stdlib/doc/src/binary.xml22
-rw-r--r--lib/stdlib/doc/src/edlin_expand.xml117
-rw-r--r--lib/stdlib/doc/src/epp.xml10
-rw-r--r--lib/stdlib/doc/src/erl_scan.xml18
-rw-r--r--lib/stdlib/doc/src/ets.xml36
-rw-r--r--lib/stdlib/doc/src/gb_sets.xml46
-rw-r--r--lib/stdlib/doc/src/gen_event.xml11
-rw-r--r--lib/stdlib/doc/src/gen_server.xml26
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml65
-rw-r--r--lib/stdlib/doc/src/io.xml78
-rw-r--r--lib/stdlib/doc/src/io_lib.xml6
-rw-r--r--lib/stdlib/doc/src/io_protocol.xml6
-rw-r--r--lib/stdlib/doc/src/lists.xml89
-rw-r--r--lib/stdlib/doc/src/maps.xml137
-rw-r--r--lib/stdlib/doc/src/math.xml17
-rw-r--r--lib/stdlib/doc/src/orddict.xml120
-rw-r--r--lib/stdlib/doc/src/ordsets.xml5
-rw-r--r--lib/stdlib/doc/src/peer.xml283
-rw-r--r--lib/stdlib/doc/src/proc_lib.xml235
-rw-r--r--lib/stdlib/doc/src/queue.xml203
-rw-r--r--lib/stdlib/doc/src/rand.xml16
-rw-r--r--lib/stdlib/doc/src/re.xml42
-rw-r--r--lib/stdlib/doc/src/ref_man.xml2
-rw-r--r--lib/stdlib/doc/src/sets.xml65
-rw-r--r--lib/stdlib/doc/src/shell.xml92
-rw-r--r--lib/stdlib/doc/src/specs.xml2
-rw-r--r--lib/stdlib/doc/src/stdlib_app.xml28
-rw-r--r--lib/stdlib/doc/src/string.xml4
-rw-r--r--lib/stdlib/doc/src/timer.xml98
-rw-r--r--lib/stdlib/doc/src/unicode.xml4
-rw-r--r--lib/stdlib/doc/src/zip.xml10
-rw-r--r--lib/stdlib/examples/erl_id_trans.erl28
-rw-r--r--lib/stdlib/src/Makefile6
-rw-r--r--lib/stdlib/src/argparse.erl1357
-rw-r--r--lib/stdlib/src/array.erl43
-rw-r--r--lib/stdlib/src/base64.erl743
-rw-r--r--lib/stdlib/src/beam_lib.erl6
-rw-r--r--lib/stdlib/src/binary.erl215
-rw-r--r--lib/stdlib/src/dets.erl98
-rw-r--r--lib/stdlib/src/edlin.erl795
-rw-r--r--lib/stdlib/src/edlin_context.erl649
-rw-r--r--lib/stdlib/src/edlin_expand.erl1192
-rw-r--r--lib/stdlib/src/edlin_type_suggestion.erl487
-rw-r--r--lib/stdlib/src/epp.erl18
-rw-r--r--lib/stdlib/src/erl_eval.erl111
-rw-r--r--lib/stdlib/src/erl_expand_records.erl32
-rw-r--r--lib/stdlib/src/erl_features.erl33
-rw-r--r--lib/stdlib/src/erl_internal.erl5
-rw-r--r--lib/stdlib/src/erl_lint.erl485
-rw-r--r--lib/stdlib/src/erl_parse.yrl190
-rw-r--r--lib/stdlib/src/erl_posix_msg.erl4
-rw-r--r--lib/stdlib/src/erl_pp.erl15
-rw-r--r--lib/stdlib/src/erl_scan.erl445
-rw-r--r--lib/stdlib/src/erl_stdlib_errors.erl55
-rw-r--r--lib/stdlib/src/ets.erl12
-rw-r--r--lib/stdlib/src/filename.erl12
-rw-r--r--lib/stdlib/src/gb_sets.erl28
-rw-r--r--lib/stdlib/src/gb_trees.erl8
-rw-r--r--lib/stdlib/src/gen.erl18
-rw-r--r--lib/stdlib/src/gen_fsm.erl26
-rw-r--r--lib/stdlib/src/gen_server.erl612
-rw-r--r--lib/stdlib/src/gen_statem.erl110
-rw-r--r--lib/stdlib/src/io.erl13
-rw-r--r--lib/stdlib/src/io_lib.erl152
-rw-r--r--lib/stdlib/src/io_lib_format.erl98
-rw-r--r--lib/stdlib/src/io_lib_pretty.erl194
-rw-r--r--lib/stdlib/src/lists.erl534
-rw-r--r--lib/stdlib/src/maps.erl269
-rw-r--r--lib/stdlib/src/math.erl8
-rw-r--r--lib/stdlib/src/ms_transform.erl7
-rw-r--r--lib/stdlib/src/orddict.erl4
-rw-r--r--lib/stdlib/src/otp_internal.erl72
-rw-r--r--lib/stdlib/src/peer.erl107
-rw-r--r--lib/stdlib/src/proc_lib.erl91
-rw-r--r--lib/stdlib/src/proplists.erl2
-rw-r--r--lib/stdlib/src/qlc.erl4
-rw-r--r--lib/stdlib/src/queue.erl2
-rw-r--r--lib/stdlib/src/rand.erl14
-rw-r--r--lib/stdlib/src/re.erl36
-rw-r--r--lib/stdlib/src/sets.erl71
-rw-r--r--lib/stdlib/src/shell.erl912
-rw-r--r--lib/stdlib/src/shell_default.erl52
-rw-r--r--lib/stdlib/src/shell_docs.erl24
-rw-r--r--lib/stdlib/src/stdlib.app.src9
-rw-r--r--lib/stdlib/src/string.erl83
-rw-r--r--lib/stdlib/src/supervisor.erl8
-rw-r--r--lib/stdlib/src/timer.erl244
-rw-r--r--lib/stdlib/src/unicode.erl12
-rw-r--r--lib/stdlib/src/zip.erl21
-rw-r--r--lib/stdlib/test/Makefile11
-rw-r--r--lib/stdlib/test/argparse_SUITE.erl1063
-rw-r--r--lib/stdlib/test/base64_SUITE.erl216
-rw-r--r--lib/stdlib/test/base64_property_test_SUITE.erl108
-rw-r--r--lib/stdlib/test/binary_module_SUITE.erl53
-rw-r--r--lib/stdlib/test/binary_property_test_SUITE.erl35
-rw-r--r--lib/stdlib/test/dets_SUITE.erl34
-rw-r--r--lib/stdlib/test/edlin_context_SUITE.erl189
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE.erl731
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/.hidden file0
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/.hidden_file0
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/.hidden😀_file0
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/complete_function_parameter.erl164
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/visible file0
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/visible_file0
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/visible😀_file0
-rw-r--r--lib/stdlib/test/erl_eval_SUITE.erl66
-rw-r--r--lib/stdlib/test/erl_expand_records_SUITE.erl63
-rw-r--r--lib/stdlib/test/erl_lint_SUITE.erl472
-rw-r--r--lib/stdlib/test/erl_pp_SUITE.erl27
-rw-r--r--lib/stdlib/test/erl_scan_SUITE.erl30
-rw-r--r--lib/stdlib/test/error_logger_h_SUITE.erl24
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/arg_overflow2
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/linebuf_overflow2
-rw-r--r--lib/stdlib/test/ets_SUITE.erl63
-rw-r--r--lib/stdlib/test/gen_server_SUITE.erl400
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl221
-rw-r--r--lib/stdlib/test/io_SUITE.erl114
-rw-r--r--lib/stdlib/test/io_proto_SUITE.erl1119
-rw-r--r--lib/stdlib/test/lists_SUITE.erl476
-rw-r--r--lib/stdlib/test/lists_property_test_SUITE.erl441
-rw-r--r--lib/stdlib/test/maps_SUITE.erl196
-rw-r--r--lib/stdlib/test/math_SUITE.erl11
-rw-r--r--lib/stdlib/test/ms_transform_SUITE.erl14
-rw-r--r--lib/stdlib/test/peer_SUITE.erl95
-rw-r--r--lib/stdlib/test/property_test/base64_prop.erl377
-rw-r--r--lib/stdlib/test/property_test/binary_prop.erl37
-rw-r--r--lib/stdlib/test/property_test/lists_prop.erl2146
-rw-r--r--lib/stdlib/test/property_test/uri_string_recompose.erl10
-rw-r--r--lib/stdlib/test/qlc_SUITE.erl11
-rw-r--r--lib/stdlib/test/re_SUITE.erl14
-rw-r--r--lib/stdlib/test/shell_SUITE.erl386
-rw-r--r--lib/stdlib/test/shell_docs_SUITE.erl7
-rw-r--r--lib/stdlib/test/supervisor_SUITE.erl59
-rw-r--r--lib/stdlib/test/timer_simple_SUITE.erl83
-rw-r--r--lib/stdlib/test/unicode_util_SUITE.erl47
-rw-r--r--lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt10
-rw-r--r--lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt10
-rw-r--r--lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt92
-rw-r--r--lib/stdlib/test/unicode_util_SUITE_data/unicode_table.binbin0 -> 134023 bytes
-rw-r--r--lib/stdlib/test/zip_SUITE.erl4
-rw-r--r--lib/stdlib/uc_spec/CaseFolding.txt10
-rw-r--r--lib/stdlib/uc_spec/CompositionExclusions.txt6
-rw-r--r--lib/stdlib/uc_spec/EastAsianWidth.txt2619
-rw-r--r--lib/stdlib/uc_spec/GraphemeBreakProperty.txt38
-rw-r--r--lib/stdlib/uc_spec/PropList.txt56
-rw-r--r--lib/stdlib/uc_spec/README-UPDATE.txt16
-rw-r--r--lib/stdlib/uc_spec/SpecialCasing.txt10
-rw-r--r--lib/stdlib/uc_spec/UnicodeData.txt300
-rw-r--r--lib/stdlib/uc_spec/emoji-data.txt85
-rw-r--r--lib/stdlib/uc_spec/gen_unicode_mod.escript468
-rw-r--r--lib/syntax_tools/src/epp_dodger.erl2
-rw-r--r--lib/syntax_tools/src/erl_prettypr.erl15
-rw-r--r--lib/syntax_tools/src/erl_syntax.erl153
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl11
-rw-r--r--lib/tftp/src/tftp.app.src2
-rw-r--r--lib/tftp/src/tftp_engine.erl8
-rw-r--r--lib/tools/doc/src/Makefile3
-rw-r--r--lib/tools/doc/src/part.xml5
-rw-r--r--lib/tools/doc/src/ref_man.xml7
-rw-r--r--lib/tools/doc/src/specs.xml1
-rw-r--r--lib/tools/emacs/erlang-skels.el105
-rw-r--r--lib/tools/emacs/erlang.el14
-rw-r--r--lib/tools/src/Makefile3
-rw-r--r--lib/tools/src/cover.erl15
-rw-r--r--lib/tools/src/lcnt.erl8
-rw-r--r--lib/tools/src/tools.app.src3
-rw-r--r--lib/tools/src/xref.erl8
-rw-r--r--lib/tools/src/xref_reader.erl6
-rw-r--r--lib/tools/test/Makefile3
-rw-r--r--lib/tools/test/cover_SUITE.erl21
-rw-r--r--lib/tools/test/emacs_SUITE_data/comprehensions15
-rw-r--r--lib/wx/.gitignore1
-rw-r--r--lib/wx/Makefile4
-rw-r--r--lib/wx/c_src/Makefile.in6
-rwxr-xr-xlib/wx/configure26
-rw-r--r--lib/wx/configure.ac23
-rw-r--r--lib/wx/src/Makefile2
-rw-r--r--lib/wx/src/wx.app.src4
-rw-r--r--lib/wx/src/wx_object.erl19
-rw-r--r--make/app_targets.mk2
-rw-r--r--make/autoconf/otp.m465
-rwxr-xr-xmake/autoconf/win32.config.cache.static278
-rwxr-xr-xmake/autoconf/win64.config.cache.static276
-rwxr-xr-xmake/configure22
-rw-r--r--make/configure.ac12
-rw-r--r--make/doc.mk7
-rw-r--r--make/otp.mk.in2
-rw-r--r--make/otp_version_tickets_in_merge18
-rwxr-xr-xmake/test_target_script.sh48
-rwxr-xr-xotp_build19
-rw-r--r--otp_versions.table1
-rw-r--r--prebuild.delete1
-rwxr-xr-xscripts/build-otp-tar10
-rwxr-xr-xscripts/otp_build_check344
-rwxr-xr-xscripts/pre-push6
-rwxr-xr-xscripts/valgrind_beamasm_update.escript114
-rw-r--r--system/doc/design_principles/Makefile6
-rw-r--r--system/doc/design_principles/spec_proc.xml9
-rw-r--r--system/doc/design_principles/statem.xml14
-rw-r--r--system/doc/efficiency_guide/Makefile6
-rw-r--r--system/doc/efficiency_guide/advanced.xml22
-rw-r--r--system/doc/efficiency_guide/binaryhandling.xml208
-rw-r--r--system/doc/efficiency_guide/maps.xml66
-rw-r--r--system/doc/efficiency_guide/profiling.xml4
-rw-r--r--system/doc/embedded/Makefile6
-rw-r--r--system/doc/general_info/DEPRECATIONS11
-rw-r--r--system/doc/general_info/Makefile6
-rw-r--r--system/doc/general_info/deprecations_24.inc11
-rw-r--r--system/doc/general_info/removed_26.inc27
-rw-r--r--system/doc/general_info/scheduled_for_removal_26.inc30
-rw-r--r--system/doc/general_info/scheduled_for_removal_27.inc14
-rw-r--r--system/doc/general_info/upcoming_incompatibilities.xml252
-rw-r--r--system/doc/getting_started/Makefile6
-rw-r--r--system/doc/installation_guide/Makefile13
-rw-r--r--system/doc/oam/Makefile6
-rw-r--r--system/doc/programming_examples/Makefile6
-rw-r--r--system/doc/reference_manual/Makefile6
-rw-r--r--system/doc/reference_manual/character_set.xml58
-rw-r--r--system/doc/reference_manual/code_loading.xml10
-rw-r--r--system/doc/reference_manual/data_types.xml95
-rw-r--r--system/doc/reference_manual/distributed.xml10
-rw-r--r--system/doc/reference_manual/expressions.xml830
-rw-r--r--system/doc/reference_manual/features.xml22
-rw-r--r--system/doc/reference_manual/introduction.xml11
-rw-r--r--system/doc/reference_manual/macros.xml21
-rw-r--r--system/doc/reference_manual/modules.xml35
-rw-r--r--system/doc/reference_manual/opaques.xml93
-rw-r--r--system/doc/reference_manual/ports.xml11
-rw-r--r--system/doc/reference_manual/processes.xml46
-rw-r--r--system/doc/reference_manual/records.xml24
-rw-r--r--system/doc/reference_manual/typespec.xml56
-rw-r--r--system/doc/system_architecture_intro/Makefile6
-rw-r--r--system/doc/system_principles/Makefile6
-rw-r--r--system/doc/top/Makefile81
-rw-r--r--system/doc/top/src/erl_html_tools.erl24
-rw-r--r--system/doc/top/templates/index.html.src67
-rw-r--r--system/doc/tutorial/Makefile6
1549 files changed, 155939 insertions, 61862 deletions
diff --git a/.github/dockerfiles/Dockerfile.32-bit b/.github/dockerfiles/Dockerfile.32-bit
index f1ea96d5fb..b1df0e996c 100644
--- a/.github/dockerfiles/Dockerfile.32-bit
+++ b/.github/dockerfiles/Dockerfile.32-bit
@@ -1,4 +1,4 @@
-ARG BASE=docker.pkg.github.com/erlang/otp/i386-debian-base
+ARG BASE=ghcr.io/erlang/otp/i386-debian-base
FROM $BASE
ARG MAKEFLAGS=-j4
diff --git a/.github/dockerfiles/Dockerfile.64-bit b/.github/dockerfiles/Dockerfile.64-bit
index 25aadc5e84..1cdb8cfd68 100644
--- a/.github/dockerfiles/Dockerfile.64-bit
+++ b/.github/dockerfiles/Dockerfile.64-bit
@@ -1,10 +1,10 @@
-ARG BASE=docker.pkg.github.com/erlang/otp/ubuntu-base
+ARG BASE=ghcr.io/erlang/otp/ubuntu-base
FROM $BASE
ARG MAKEFLAGS=$MAKEFLAGS
ENV MAKEFLAGS=$MAKEFLAGS \
ERL_TOP=/buildroot/otp \
- PATH=/otp/bin:/buildroot/otp/bin:$PATH
+ PATH="/Erlang ∅⊤℞/bin":/buildroot/otp/bin:$PATH
ARG ARCHIVE=./otp.tar.gz
COPY $ARCHIVE /buildroot/otp.tar.gz
@@ -14,35 +14,22 @@ WORKDIR /buildroot/otp/
ENV CFLAGS="-O2 -g -Werror"
-## Configure, check that no application are disabled and then make
-RUN ./configure --prefix=/otp && \
- if cat lib/*/CONF_INFO || cat lib/*/SKIP || cat lib/SKIP-APPLICATIONS; then exit 1; fi && \
- make && sudo make install
+## Configure (if not cached), check that no application are disabled and then make
+RUN if [ ! -f Makefile ]; then \
+ touch README.md && \
+ ./configure --prefix="/Erlang ∅⊤℞" && \
+ if cat lib/*/CONF_INFO || cat lib/*/SKIP || cat lib/SKIP-APPLICATIONS; then exit 1; fi && \
+ find . -type f -newer README.md | xargs tar --transform 's:^./:otp/:' -cf ../otp_cache.tar; \
+ fi && \
+ make && make docs DOC_TARGETS=chunks && \
+ sudo make install install-docs DOC_TARGETS=chunks
## Disable -Werror as testcases do not compile with it on
ENV CFLAGS="-O2 -g"
-WORKDIR /buildroot/
-
-## Install test tools rebar3, proper and jsx
-RUN latest () { \
- local VSN=$(curl -sL "https://api.github.com/repos/$1/tags" | jq -r ".[] | .name" | grep -E '^v?[0-9]' | sort -V | tail -1); \
- curl -sL "https://github.com/$1/archive/$VSN.tar.gz" > $(basename $1).tar.gz; \
- } && \
- latest erlang/rebar3 && ls -la && \
- (tar xzf rebar3.tar.gz && cd rebar3-* && ./bootstrap && sudo cp rebar3 /usr/bin) && \
- latest proper-testing/proper && \
- (tar xzf proper.tar.gz && mv proper-* proper && cd proper && make) && \
- latest talentdeficit/jsx && \
- (tar xzf jsx.tar.gz && mv jsx-* jsx && cd jsx && rebar3 compile)
-
-ENV ERL_LIBS=/buildroot/proper:/buildroot/jsx
-
-WORKDIR /buildroot/otp/
-
## Update init.sh with correct env vars
RUN echo "export MAKEFLAGS=$MAKEFLAGS" > /buildroot/env.sh && \
echo "export ERLC_USE_SERVER=$ERLC_USE_SERVER" >> /buildroot/env.sh && \
- echo "export ERL_TOP=$ERL_TOP" >> /buildroot/env.sh && \
- echo "export PATH=$PATH" >> /buildroot/env.sh && \
- echo "export ERL_LIBS=$ERL_LIBS" >> /buildroot/env.sh
+ echo "export ERL_TOP=\"$ERL_TOP\"" >> /buildroot/env.sh && \
+ echo "export PATH=\"$PATH\"" >> /buildroot/env.sh && \
+ echo "export ERL_LIBS=\"$ERL_LIBS\"" >> /buildroot/env.sh
diff --git a/.github/dockerfiles/Dockerfile.clang b/.github/dockerfiles/Dockerfile.clang
index 92607dd6bb..66a50ef68f 100644
--- a/.github/dockerfiles/Dockerfile.clang
+++ b/.github/dockerfiles/Dockerfile.clang
@@ -1,4 +1,4 @@
-ARG BASE=docker.pkg.github.com/erlang/otp/ubuntu-base
+ARG BASE=ghcr.io/erlang/otp/ubuntu-base
FROM $BASE
## We do a SSA lint check here
ENV ERL_COMPILER_OPTIONS=ssalint
diff --git a/.github/dockerfiles/Dockerfile.cross-compile b/.github/dockerfiles/Dockerfile.cross-compile
index 75045f2c76..c9e9c44855 100644
--- a/.github/dockerfiles/Dockerfile.cross-compile
+++ b/.github/dockerfiles/Dockerfile.cross-compile
@@ -1,7 +1,7 @@
##
## This docker file will build Erlang on 32-bit to 64-bit x86
##
-ARG BASE=docker.pkg.github.com/erlang/otp/i386-debian-base
+ARG BASE=ghcr.io/erlang/otp/i386-debian-base
FROM $BASE as build
ARG MAKEFLAGS=-j4
diff --git a/.github/dockerfiles/Dockerfile.ubuntu-base b/.github/dockerfiles/Dockerfile.ubuntu-base
index c19537ef2a..4457d62a1f 100644
--- a/.github/dockerfiles/Dockerfile.ubuntu-base
+++ b/.github/dockerfiles/Dockerfile.ubuntu-base
@@ -6,47 +6,22 @@ FROM $BASE
ENV INSTALL_LIBS="zlib1g-dev libncurses5-dev libssl-dev unixodbc-dev libsctp-dev lksctp-tools libgmp3-dev libwxbase3.0-dev libwxgtk3.0-gtk3-dev libwxgtk-webview3.0-gtk3-dev"
-ARG EXTRA_LIBS="erlang erlang-doc"
-
USER root
ENV DEBIAN_FRONTEND=noninteractive
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
+## Install build tools
RUN apt-get update && apt-get -y upgrade && \
apt-get install -y build-essential m4 autoconf fop xsltproc \
- default-jdk libxml2-utils flex pkg-config \
- unixodbc odbc-postgresql postgresql \
- tzdata ssh openssh-server groff-base sudo gdb tinyproxy bind9 nsd expect vsftpd python \
- linux-tools-common linux-tools-generic linux-tools-`uname -r` curl jq \
- xvfb libgl1-mesa-dri \
- ${INSTALL_LIBS} && \
- for lib in ${EXTRA_LIBS}; do apt-get install -y ${lib}; done && \
- if [ ! -f /etc/apache2/apache2.conf ]; then apt-get install -y apache2; fi && \
+ default-jdk libxml2-utils flex pkg-config locales tzdata sudo ${INSTALL_LIBS} && \
sed -i 's@# en_US.UTF-8@en_US.UTF-8@g' /etc/locale.gen && locale-gen && \
update-alternatives --set wx-config /usr/lib/x86_64-linux-gnu/wx/config/gtk3-unicode-3.0
-## EXTRA_LIBS are installed using a for loop because of bugs in the erlang-doc deb package
-## Apache2 may already be installed, if so we do not want to install it again
-
ARG MAKEFLAGS=-j4
ENV MAKEFLAGS=$MAKEFLAGS \
ERLC_USE_SERVER=yes
-## We install the latest version of the previous three releases in order to do
-## backwards compatability testing of the Erlang distribution.
-RUN apt-get install -y git && \
- curl -L https://raw.githubusercontent.com/kerl/kerl/master/kerl > /usr/bin/kerl && \
- chmod +x /usr/bin/kerl && \
- kerl update releases && \
- LATEST=$(kerl list releases | tail -1 | awk -F '.' '{print $1}') && \
- for release in $(seq $(( LATEST - 3 )) $(( LATEST - 1 ))); do \
- VSN=$(kerl list releases | grep "^$release" | tail -1); \
- kerl build ${VSN} ${VSN} && \
- kerl install ${VSN} /usr/local/lib/erlang-${VSN}; \
- done && \
- rm -rf ~/.kerl
-
ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
ARG USER=gitpod
@@ -56,11 +31,54 @@ ARG uid=421
RUN echo "Europe/Stockholm" > /etc/timezone && \
ln -snf /usr/share/zoneinfo/$(cat /etc/timezone) /etc/localtime && \
+ if ! grep ":${gid}:$" /etc/group; then groupadd -g ${gid} localgroup; fi && \
if [ ! -d /home/${USER} ]; then useradd -rm -d /home/${USER} -s /bin/sh -g ${gid} -G ${gid},sudo -u ${uid} ${USER}; fi && \
echo "${USER} ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/${USER} && \
echo "/buildroot/** r," >> /etc/apparmor.d/local/usr.sbin.named && \
echo "/tests/** r," >> /etc/apparmor.d/local/usr.sbin.named
+## Java and log4j are used by fop to build documentation
+COPY --chown=${USER}:${GROUP} dockerfiles/log4j.properties /home/${USER}/
+ENV JAVA_ARGS="-Dlog4j.configuration=file://home/${USER}/log4j.properties"
+
+ENV OTP_STRICT_INSTALL=yes
+
+RUN mkdir /buildroot /tests /otp && chown ${USER}:${GROUP} /buildroot /tests /otp
+
+## We install the latest version of the previous three releases in order to do
+## backwards compatability testing of Erlang.
+RUN apt-get install -y git curl && \
+ curl -L https://raw.githubusercontent.com/kerl/kerl/master/kerl > /usr/bin/kerl && \
+ chmod +x /usr/bin/kerl && \
+ kerl update releases && \
+ LATEST=$(kerl list releases | tail -1 | awk -F '.' '{print $1}') && \
+ echo "/usr/local/lib/erlang-${LATEST}/bin" > /home/${USER}/LATEST && \
+ for release in $(seq $(( LATEST - 2 )) $(( LATEST ))); do \
+ VSN=$(kerl list releases | grep "^$release" | tail -1); \
+ kerl build ${VSN} ${VSN} && \
+ kerl install ${VSN} /usr/local/lib/erlang-${VSN}; \
+ done && \
+ rm -rf ~/.kerl
+
+## Install test tools
+## EXTRA_LIBS are installed using a for loop because of bugs in the erlang-doc deb package
+## Apache2 may already be installed, if so we do not want to install it again
+ARG EXTRA_LIBS="erlang erlang-doc"
+RUN apt-get install -y \
+ unixodbc odbc-postgresql postgresql ssh openssh-server groff-base gdb \
+ tinyproxy bind9 nsd expect vsftpd python emacs nano vim \
+ linux-tools-common linux-tools-generic linux-tools-`uname -r` jq \
+ xvfb libgl1-mesa-dri && \
+ for lib in ${EXTRA_LIBS}; do apt-get install -y ${lib}; done && \
+ if [ ! -f /etc/apache2/apache2.conf ]; then apt-get install -y apache2; fi
+
+## We use tmux to test terminals
+RUN apt-get install -y libevent-dev libutf8proc-dev && \
+ cd /tmp && wget https://github.com/tmux/tmux/releases/download/3.2a/tmux-3.2a.tar.gz && \
+ tar xvzf tmux-3.2a.tar.gz && cd tmux-3.2a && \
+ ./configure --enable-static --enable-utf8proc && \
+ make && make install
+
## Setup progres so that the odbc test can run
USER postgres
@@ -79,15 +97,23 @@ ENV USER=${USER}
RUN ssh-keygen -q -t rsa -N '' -f $HOME/.ssh/id_rsa && \
cp $HOME/.ssh/id_rsa.pub $HOME/.ssh/authorized_keys
-## Java and log4j are used by fop to build documentation
-COPY --chown=${USER}:${GROUP} dockerfiles/log4j.properties /home/${USER}/
-ENV OTP_STRICT_INSTALL=yes \
- JAVA_ARGS="-Dlog4j.configuration=file://home/${USER}/log4j.properties"
-
-RUN sudo mkdir /buildroot /tests /otp && sudo chown ${USER}:${GROUP} /buildroot /tests /otp
-
COPY --chown=${USER}:${GROUP} dockerfiles/init.sh /buildroot/
-## TODO: Build Erlang versions N, N-1 and N-2 for compatability testing.
+WORKDIR /buildroot/
+
+## Install test tools rebar3, proper and jsx
+RUN export PATH="$(cat /home/${USER}/LATEST)/${PATH}" && \
+ latest () { \
+ local VSN=$(curl -sL "https://api.github.com/repos/$1/tags" | jq -r ".[] | .name" | grep -E '^v?[0-9]' | sort -V | tail -1); \
+ curl -sL "https://github.com/$1/archive/$VSN.tar.gz" > $(basename $1).tar.gz; \
+ } && \
+ latest erlang/rebar3 && ls -la && \
+ (tar xzf rebar3.tar.gz && cd rebar3-* && ./bootstrap && sudo cp rebar3 /usr/bin) && \
+ latest proper-testing/proper && \
+ (tar xzf proper.tar.gz && mv proper-* proper && cd proper && make) && \
+ latest talentdeficit/jsx && \
+ (tar xzf jsx.tar.gz && mv jsx-* jsx && cd jsx && rebar3 compile)
+
+ENV ERL_LIBS=/buildroot/proper:/buildroot/jsx
ENTRYPOINT ["/buildroot/init.sh"]
diff --git a/.github/dockerfiles/init.sh b/.github/dockerfiles/init.sh
index 8eb13abee2..af19cc097f 100755
--- a/.github/dockerfiles/init.sh
+++ b/.github/dockerfiles/init.sh
@@ -15,6 +15,6 @@ sudo -E bash -c "apt-get update && apt-get install -y linux-tools-common linux-t
sudo bash -c "Xvfb :99 -ac -screen 0 1920x1080x24 -nolisten tcp" &
export DISPLAY=:99
-PATH=$PATH:$(ls -1d /usr/local/lib/erlang-*/bin | tr '\n' ':')
+PATH="$PATH:$(ls -1d /usr/local/lib/erlang-*/bin | tr '\n' ':')"
exec /bin/bash -c "$1"
diff --git a/.github/scripts/build-base-image.sh b/.github/scripts/build-base-image.sh
index 543235e126..7069ef391a 100755
--- a/.github/scripts/build-base-image.sh
+++ b/.github/scripts/build-base-image.sh
@@ -29,16 +29,16 @@ case "${BASE_TAG}" in
;;
esac
-echo "::set-output name=BASE::${BASE}"
-echo "::set-output name=BASE_TAG::${BASE_TAG}"
-echo "::set-output name=BASE_TYPE::${BASE_TYPE}"
+echo "BASE=${BASE}" >> $GITHUB_OUTPUT
+echo "BASE_TAG=${BASE_TAG}" >> $GITHUB_OUTPUT
+echo "BASE_TYPE=${BASE_TYPE}" >> $GITHUB_OUTPUT
if [ -f "otp_docker_base.tar" ]; then
docker load -i "otp_docker_base.tar"
- echo "::set-output name=BASE_BUILD::loaded"
+ echo "BASE_BUILD=loaded" >> $GITHUB_OUTPUT
elif [ -f "otp_docker_base/otp_docker_base.tar" ]; then
docker load -i "otp_docker_base/otp_docker_base.tar"
- echo "::set-output name=BASE_BUILD::loaded"
+ echo "BASE_BUILD=loaded" >> $GITHUB_OUTPUT
else
if [ "${BASE_USE_CACHE}" != "false" ]; then
docker pull "${BASE_TAG}:${BASE_BRANCH}"
@@ -58,9 +58,9 @@ else
NEW_BASE_IMAGE_ID=$(docker images -q "${BASE_TAG}:latest")
if [ "${BASE_IMAGE_ID}" = "${NEW_BASE_IMAGE_ID}" ]; then
- echo "::set-output name=BASE_BUILD::cached"
+ echo "BASE_BUILD=cached" >> $GITHUB_OUTPUT
else
- echo "::set-output name=BASE_BUILD::re-built"
+ echo "BASE_BUILD=re-built" >> $GITHUB_OUTPUT
docker save "${BASE_TAG}:latest" > "otp_docker_base.tar"
fi
fi
diff --git a/.github/scripts/build-macos.sh b/.github/scripts/build-macos.sh
index 82b07bac7b..73c35a6a22 100755
--- a/.github/scripts/build-macos.sh
+++ b/.github/scripts/build-macos.sh
@@ -1,12 +1,19 @@
#!/bin/sh
-export MAKEFLAGS=-j$(getconf _NPROCESSORS_ONLN)
-export ERL_TOP=`pwd`
-export RELEASE_ROOT=$ERL_TOP/release
+export MAKEFLAGS="-j$(getconf _NPROCESSORS_ONLN)"
+export ERL_TOP="$(pwd)"
export ERLC_USE_SERVER=true
+export RELEASE_ROOT="$ERL_TOP/release"
+BUILD_DOCS=false
-./otp_build configure \
- --disable-dynamic-ssl-lib
+if [ "$1" = "build_docs" ]; then
+ BUILD_DOCS=true
+ shift
+fi
+
+./otp_build configure $*
./otp_build boot -a
-./otp_build release -a $RELEASE_ROOT
-make release_docs DOC_TARGETS=chunks
+./otp_build release -a "$RELEASE_ROOT"
+if $BUILD_DOCS; then
+ make release_docs DOC_TARGETS=chunks
+fi
diff --git a/.github/scripts/get-pr-number.es b/.github/scripts/get-pr-number.es
index e925f95625..a388e6107a 100755
--- a/.github/scripts/get-pr-number.es
+++ b/.github/scripts/get-pr-number.es
@@ -11,19 +11,46 @@ main([Repo, HeadSha]) ->
string:equal(HeadSha, Sha)
end, AllOpenPrs) of
{value, #{ <<"number">> := Number } } ->
- io:format("::set-output name=result::~p~n", [Number]);
+ append_to_github_output("result=~p~n", [Number]);
false ->
- io:format("::set-output name=result::~ts~n", [""])
+ append_to_github_output("result=~ts~n", [""])
+ end.
+
+append_to_github_output(Fmt, Args) ->
+ case os:getenv("GITHUB_OUTPUT") of
+ false ->
+ io:format(standard_error, "GITHUB_OUTPUT env var missing?~n", []);
+ GitHubOutputFile ->
+ {ok, F} = file:open(GitHubOutputFile, [write, append]),
+ ok = io:fwrite(F, Fmt, Args),
+ ok = file:close(F)
end.
ghapi(CMD) ->
- Data = cmd(CMD),
- try jsx:decode(Data, [{return_maps,true}])
+ decode(cmd(CMD)).
+
+decode(Data) ->
+ try jsx:decode(Data,[{return_maps, true}, return_tail]) of
+ {with_tail, Json, <<>>} ->
+ Json;
+ {with_tail, Json, Tail} ->
+ lists:concat([Json | decodeTail(Tail)])
catch E:R:ST ->
io:format("Failed to decode: ~ts",[Data]),
erlang:raise(E,R,ST)
end.
+decodeTail(Data) ->
+ try jsx:decode(Data,[{return_maps, true}, return_tail]) of
+ {with_tail, Json, <<>>} ->
+ [Json];
+ {with_tail, Json, Tail} ->
+ [Json | decodeTail(Tail)]
+ catch E:R:ST ->
+ io:format(standard_error, "Failed to decode: ~ts",[Data]),
+ erlang:raise(E,R,ST)
+ end.
+
cmd(CMD) ->
ListCmd = unicode:characters_to_list(CMD),
io:format("cmd: ~ts~n",[ListCmd]),
diff --git a/.github/scripts/init-pre-release.sh b/.github/scripts/init-pre-release.sh
index 7a86f3052c..c7b7cab617 100755
--- a/.github/scripts/init-pre-release.sh
+++ b/.github/scripts/init-pre-release.sh
@@ -3,4 +3,33 @@
## We create a tar ball that is used later by build-otp-tar
## to create the pre-built tar ball
-git archive --prefix otp/ -o otp_src.tar.gz HEAD
+AUTOCONF=0
+TARGET=otp_src.tar.gz
+
+if [ -n "$1" ]; then
+ TARGET="$1"
+fi
+
+## This script is used to create archives for older releases
+## so if configure does not exist in the git repo we need to
+## create it.
+if [ ! -f configure ]; then
+ ./otp_build autoconf
+ find . -name aclocal.m4 | xargs git add -f
+ find . -name configure | xargs git add -f
+ find . -name config.h.in | xargs git add -f
+ find . -name config.guess | xargs git add -f
+ find . -name config.sub | xargs git add -f
+ find . -name install-sh | xargs git add -f
+ if ! git config user.name; then
+ git config user.email "you@example.com"
+ git config user.name "Your Name"
+ fi
+ git commit --no-verify -m 'Add generated configure files'
+ AUTOCONF=1
+fi
+git archive --prefix otp/ -o "$TARGET" HEAD
+
+if [ "$AUTOCONF" = 1 ]; then
+ git reset --hard HEAD~1
+fi
diff --git a/.github/scripts/restore-from-prebuilt.sh b/.github/scripts/restore-from-prebuilt.sh
new file mode 100755
index 0000000000..6e620bf558
--- /dev/null
+++ b/.github/scripts/restore-from-prebuilt.sh
@@ -0,0 +1,162 @@
+#!/bin/bash
+
+set -xe
+
+CACHE_SOURCE_DIR="$1"
+TARGET="$2"
+ARCHIVE="$3"
+EVENT="$4"
+DELETED="$5"
+CHANGES="$9"
+
+if [ ! -f "${CACHE_SOURCE_DIR}/otp_src.tar.gz" ] || [ "${NO_CACHE}" = "true" ]; then
+ cp "${ARCHIVE}" "${TARGET}"
+ cp "${ARCHIVE}" "${CACHE_SOURCE_DIR}/otp_src.tar.gz"
+ exit 0
+fi
+
+TMP_DIR=$(mktemp -d)
+CACHE_DIR="${TMP_DIR}"
+ARCHIVE_DIR="${TMP_DIR}/archive"
+
+mkdir "${ARCHIVE_DIR}"
+
+#################################
+## START WORK ON THE CACHED FILES
+#################################
+echo "::group::{Restore cached files}"
+tar -C "${CACHE_DIR}/" -xzf "${CACHE_SOURCE_DIR}/otp_src.tar.gz"
+
+## If configure scripts have NOT changed, we can restore configure and other C/java programs
+if [ -z "${CONFIGURE}" ] || [ "${CONFIGURE}" = "false" ]; then
+ tar -C "${CACHE_DIR}/" -xzf "${CACHE_SOURCE_DIR}/otp_cache.tar.gz"
+fi
+
+## If bootstrap has been changed, we do not use the cached .beam files
+EXCLUDE_BOOTSTRAP=""
+if [ "${BOOTSTRAP}" = "true" ]; then
+ find "${CACHE_DIR}/otp/lib" -name "*.beam" -exec rm -f {} \;
+else
+ EXCLUDE_BOOTSTRAP=(--exclude "bootstrap")
+fi
+
+## Make a copy of the cache for debugging
+mkdir "${TMP_DIR}/cache"
+cp -rp "${CACHE_DIR}/otp" "${TMP_DIR}/cache/"
+
+CACHE_DIR="${CACHE_DIR}/otp"
+
+echo "::group::{Delete files from PR}"
+## Delete any files that this PR deletes
+for delete in $DELETED; do
+ if [ -d "${CACHE_DIR}/${delete}" ]; then
+ rm -r "${CACHE_DIR}/${delete}"
+ elif [ -f "${CACHE_DIR}/${delete}" ]; then
+ rm "${CACHE_DIR}/${delete}"
+ else
+ echo "Could not find $delete to delete"
+ exit 1
+ fi
+done
+
+##################################
+## START WORK ON THE UPDATED FILES
+##################################
+
+echo "::group::{Extract changed files}"
+if [ -n "${ARCHIVE}" ]; then
+ ## Extract with updated timestamp (the -m flag) so that any change will trigger a rebuild
+ tar -C "${ARCHIVE_DIR}/" -xzmf "${ARCHIVE}"
+
+ ## Directory permissions in the archive and cache are for some reason different...
+ chmod -R g-w "${ARCHIVE_DIR}/"
+
+ ## rlpgoD is the same as --archive, but without --times
+ RSYNC_ARGS=(-rlpgoD --itemize-changes --verbose --checksum --update "${EXCLUDE_BOOTSTRAP[@]}" "${ARCHIVE_DIR}/otp/" "${CACHE_DIR}/")
+
+ CHANGES="${TMP_DIR}/changes"
+ PREV_CHANGES="${TMP_DIR}/prev-changes"
+
+ touch "${PREV_CHANGES}"
+
+ ## Below follows some rules about when we do not want to use the cache
+ ## The rules are run multiple times so that if any rule triggeres a delte
+ ## we will re-run the rules again with the new changes.
+ for i in $(seq 1 10); do
+
+ echo "::group::{Run ${i} at pruning cache}"
+
+ ## First do a dry run to see if we need to delete anything from cache
+ rsync --dry-run "${RSYNC_ARGS[@]}" | grep '^\(>\|c\)' > "${TMP_DIR}/changes"
+ cat "${TMP_DIR}/changes"
+
+ if cmp -s "${CHANGES}" "${PREV_CHANGES}"; then
+ break;
+ fi
+
+ ### If any parse transform is changed we recompile everything as we have
+ ### no idea what it may change. If the parse transform calls any other
+ ### modules we really should delete the cache for those as well, but
+ ### it is impossible for us to know which modules are used by a pt so
+ ### this has to be somekind of best effort.
+ echo "::group::{Run ${i}: parse transforms}"
+ PARSE_TRANSFORMS=$(grep -r '^parse_transform(' "${CACHE_DIR}/" | grep "/lib/[^/]*/src/" | awk -F ':' '{print $1}' | uniq)
+ for pt in $PARSE_TRANSFORMS; do
+ if grep "$(basename "${pt}")" "${CHANGES}"; then
+ echo "Deleting entire cache as a parse transform has changed" >&2
+ rm -rf "${CACHE_DIR:?}/"
+ fi
+ done
+
+ echo "::group::{Run ${i}: yecc}"
+ ### if yecc has changed, need to recompile all .yrl files
+ if grep "yecc.erl$" "${CHANGES}"; then
+ echo "Deleting all .yrl files as yecc has changed" >&2
+ find "${CACHE_DIR}/" -name "*.yrl" -exec rm -f {} \;
+ fi
+
+ echo "::group::{Run ${i}: asn1}"
+ ### If asn1 has changed, need to re-compile all .asn1 files
+ if grep lib/asn1 "${CHANGES}"; then
+ echo "Deleting all .asn1 files as asn1 has changed" >&2
+ find "${CACHE_DIR}/" -name "*.asn1" -exec rm -f {} \;
+ fi
+
+ echo "::group::{Run ${i}: docs}"
+ ### If any of the doc generating tools change, we need to re-compile the docs
+ if grep "lib/\(xmerl\|erl_docgen\|edoc\)" "${CHANGES}"; then
+ echo "Deleting all docs as documentation tools have changed" >&2
+ rm -rf "${CACHE_DIR}"/lib/*/doc/ "${CACHE_DIR}/erts/doc/" "${CACHE_DIR}/system/"
+ fi
+
+ ### Find all behaviours in OTP and check if any them as changed, we need to
+ ### rebuild all files that use them.
+ echo "::group::{Run ${i}: behaviours}"
+ BEHAVIOURS=$(grep -r "^-callback" "${CACHE_DIR}/" | grep "/lib/[^/]*/src/" | awk -F ':' '{print $1}' | uniq | sed 's:.*/\([^/.]*\)[.]erl$:\1:')
+ for behaviour in $BEHAVIOURS; do
+ if grep "${behaviour}[.]erl\$" "${CHANGES}"; then
+ echo "Deleting files using ${behaviour} has it has changed" >&2
+ FILES=$(grep -r "^-behaviour(${behaviour})" "${CACHE_DIR}/" | grep "/lib/[^/]*/src/" | awk -F ':' '{print $1}')
+ rm -f $FILES
+ fi
+ done
+
+ if [ "$i" = "10" ]; then
+ echo "Deleting entire cache as it did not stabalize in trime" >&2
+ rm -rf "${CACHE_DIR:?}"
+ else
+ mv "${CHANGES}" "${PREV_CHANGES}"
+ fi
+ done
+
+ echo "::group::{Sync changes over cached data}"
+
+ ## Now we do the actual sync
+ rsync "${RSYNC_ARGS[@]}"
+fi
+
+tar -czf "${TARGET}" -C "${TMP_DIR}" otp
+
+rm -rf "${TMP_DIR}"
+
+echo "::endgroup::"
diff --git a/.github/scripts/restore-otp-image.sh b/.github/scripts/restore-otp-image.sh
deleted file mode 100755
index 57621c25d5..0000000000
--- a/.github/scripts/restore-otp-image.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-
-BASE_BRANCH="$1"
-BASE_TAG=$(grep "ARG BASE=" ".github/dockerfiles/Dockerfile.64-bit" | head -1 | tr '=' ' ' | awk '{print $3}')
-
-BASE_TAG="$BASE_TAG" .github/scripts/build-base-image.sh "${BASE_BRANCH}"
-
-cat > Dockerfile <<EOF
-FROM ${BASE_TAG}
-ADD otp-ubuntu-20.04.tar.gz /
-WORKDIR /buildroot/otp/
-EOF
-docker build -t otp .
diff --git a/.github/scripts/sync-github-prs.es b/.github/scripts/sync-github-prs.es
index bc033c3efe..6560ae5e02 100755
--- a/.github/scripts/sync-github-prs.es
+++ b/.github/scripts/sync-github-prs.es
@@ -62,7 +62,9 @@ handle_pr(_Repo, Target,
string:equal(HeadSha, Sha) andalso string:equal(Status, <<"completed">>)
end, maps:get(<<"workflow_runs">>, Runs)) of
{value, Run} ->
- Ident = integer_to_list(maps:get(<<"id">>,Run)),
+ Ident = integer_to_list(
+ erlang:phash2(
+ {maps:get(<<"id">>,Run), ?MODULE:module_info(md5)})),
io:format("Checking for ~ts~n", [filename:join(PRDir, Ident)]),
case file:read_file_info(filename:join(PRDir, Ident)) of
{error, enoent} ->
@@ -91,7 +93,9 @@ handle_pr(_Repo, Target,
end, Artifacts),
CTLogsIndex = filename:join([PRDir,"ct_logs","index.html"]),
case file:read_file_info(CTLogsIndex) of
- {ok, _} -> ok;
+ {ok, _} ->
+ CTSuiteFiles = filename:join([PRDir,"ct_logs","ct_run*","*.logs","run.*","suite.log"]),
+ lists:foreach(fun purge_suite/1, filelib:wildcard(CTSuiteFiles));
_ ->
ok = filelib:ensure_dir(CTLogsIndex),
ok = file:write_file(CTLogsIndex, ["No test logs found for ", Sha])
@@ -109,6 +113,47 @@ handle_pr(_Repo, Target,
ok
end.
+%% We truncate the logs of all testcases of any suite that did not have any failures
+purge_suite(SuiteFilePath) ->
+ {ok, SuiteFile} = file:read_file(SuiteFilePath),
+ SuiteDir = filename:dirname(SuiteFilePath),
+ Placeholder = "<html><body>github truncated successful testcase</body></html>",
+ case re:run(SuiteFile,"^=failed\s*\([0-9]+\)$",[multiline,{capture,all_but_first,binary}]) of
+ {match,[<<"0">>]} ->
+ io:format("Purging logs from: ~ts~n",[SuiteDir]),
+ ok = file:del_dir_r(filename:join(SuiteDir,"log_private")),
+ lists:foreach(
+ fun(File) ->
+ case filename:basename(File) of
+ "suite" ++ _ ->
+ ok;
+ "unexpected_io" ++_ ->
+ ok;
+ "cover.html" ->
+ ok;
+ _Else ->
+ file:write_file(File,Placeholder)
+ end
+ end, filelib:wildcard(filename:join(SuiteDir,"*.html")));
+ _FailedTestcases ->
+ io:format("Purging logs from: ~ts~n",[SuiteDir]),
+ lists:foreach(
+ fun(File) ->
+ {ok, B} = file:read_file(File),
+ case re:run(B,"^=== Config value:",[multiline]) of
+ {match,_} ->
+ case re:run(B,"^=== successfully completed test case",[multiline]) of
+ {match, _} ->
+ file:write_file(File,Placeholder);
+ nomatch ->
+ ok
+ end;
+ nomatch ->
+ ok
+ end
+ end, filelib:wildcard(filename:join(SuiteDir,"*.html")))
+ end.
+
ghapi(CMD) ->
decode(cmd(CMD)).
@@ -116,12 +161,14 @@ decode(Data) ->
try jsx:decode(Data,[{return_maps, true}, return_tail]) of
{with_tail, Json, <<>>} ->
Json;
- {with_tail, Json, Tail} ->
+ {with_tail, Json, Tail} when is_map(Json) ->
[Key] = maps:keys(maps:remove(<<"total_count">>, Json)),
#{ Key => lists:flatmap(
fun(J) -> maps:get(Key, J) end,
[Json | decodeTail(Tail)])
- }
+ };
+ {with_tail, Json, Tail} when is_list(Json) ->
+ lists:concat([Json | decodeTail(Tail)])
catch E:R:ST ->
io:format("Failed to decode: ~ts",[Data]),
erlang:raise(E,R,ST)
diff --git a/.github/scripts/sync-github-releases.sh b/.github/scripts/sync-github-releases.sh
index 0cb2042f01..b71d5b54a4 100755
--- a/.github/scripts/sync-github-releases.sh
+++ b/.github/scripts/sync-github-releases.sh
@@ -215,7 +215,11 @@ if [ ${UPLOADED} = false ] && [ ${#MISSING_PREBUILD[0]} != 0 ]; then
name="${MISSING_PREBUILD[0]}"
stripped_name=$(_strip_name "${name}")
git clone https://github.com/erlang/otp -b "${name}" otp_src
- (cd otp_src && ../.github/scripts/init-pre-release.sh)
+ if [ -f otp_src/.github/scripts/init-pre-release.sh ]; then
+ (cd otp_src && ERL_TOP=$(pwd) .github/scripts/init-pre-release.sh)
+ else
+ (cd otp_src && ERL_TOP=$(pwd) ../.github/scripts/init-pre-release.sh)
+ fi
case ${stripped_name} in
23.**)
## The 32-bit dockerfile build the doc chunks which we want
diff --git a/.github/workflows/actions-updater.yaml b/.github/workflows/actions-updater.yaml
new file mode 100644
index 0000000000..0e186cf4e1
--- /dev/null
+++ b/.github/workflows/actions-updater.yaml
@@ -0,0 +1,22 @@
+name: GitHub Actions Updater
+
+on:
+ schedule:
+ # Automatically run on the 1st of every month
+ - cron: '0 0 1 * *'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Run GitHub Actions Version Updater
+ uses: saadmk11/github-actions-version-updater@v0.7.3
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ commit_message: "Updating GitHub actions to their latest versions"
+ pull_request_labels: "team:IS"
diff --git a/.github/workflows/add-to-project.yaml b/.github/workflows/add-to-project.yaml
new file mode 100644
index 0000000000..d387e52f4a
--- /dev/null
+++ b/.github/workflows/add-to-project.yaml
@@ -0,0 +1,26 @@
+name: Add bugs to bugs project
+
+on:
+ issues:
+ types:
+ - opened
+ pull_request_target:
+ types:
+ - opened
+
+jobs:
+ add-to-project:
+ name: Add issue to project
+ runs-on: ubuntu-latest
+ if: github.repository == 'erlang/otp'
+ steps:
+ - name: Generate token
+ id: generate_token
+ uses: tibdex/github-app-token@v1.8.0
+ with:
+ app_id: ${{ secrets.APP_ID }}
+ private_key: ${{ secrets.APP_PEM }}
+ - uses: actions/add-to-project@v0.4.0
+ with:
+ project-url: https://github.com/orgs/erlang/projects/13
+ github-token: ${{ steps.generate_token.outputs.token }}
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index ce50c86a1e..ddb0b20361 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -7,11 +7,10 @@
## not possible so we need to rebuild all of Erlang/OTP multiple
## times.
##
-## When ghcr.io support using the GITHUB_TOKEN we should migrate
-## over to use it instead as that should allow us to use the
+## Now that we have migrated to ghcr.io we use the
## built-in caching mechanisms of docker/build-push-action@v2.
## However as things are now we use docker directly to make things
-## work.
+## work due to historical reasons.
##
name: Build and check Erlang/OTP
@@ -21,6 +20,7 @@ on:
pull_request:
env:
+ ## Equivalent to github.event_name == 'pull_request' ? github.base_ref : github.ref_name
BASE_BRANCH: ${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }}
jobs:
@@ -30,75 +30,111 @@ jobs:
runs-on: ubuntu-latest
outputs:
BASE_BUILD: ${{ steps.base-build.outputs.BASE_BUILD }}
+ changes: ${{ steps.changes.outputs.changes }}
+ all: ${{ steps.apps.outputs.all }}
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
+ - name: Get applications
+ id: apps
+ run: |
+ .github/scripts/path-filters.sh > .github/scripts/path-filters.yaml
+ ALL_APPS=$(grep '^[a-z_]*:' .github/scripts/path-filters.yaml | sed 's/:.*$//')
+ ALL_APPS=$(jq -n --arg inarr "${ALL_APPS}" '$inarr | split("\n")' | tr '\n' ' ')
+ echo "all=${ALL_APPS}" >> $GITHUB_OUTPUT
+ - uses: dorny/paths-filter@v2
+ id: changes
+ with:
+ filters: .github/scripts/path-filters.yaml
- name: Create initial pre-release tar
- run: .github/scripts/init-pre-release.sh
+ run: .github/scripts/init-pre-release.sh otp_archive.tar.gz
- name: Upload source tar archive
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: otp_git_archive
- path: otp_src.tar.gz
+ path: otp_archive.tar.gz
- name: Docker login
- uses: docker/login-action@v1
+ uses: docker/login-action@v2
with:
- registry: docker.pkg.github.com
+ registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- - name: Build BASE image
- id: base-build
- run: .github/scripts/build-base-image.sh "${BASE_BRANCH}" 64-bit
- - name: Save BASE image
- if: steps.base-build.outputs.BASE_BUILD == 're-built'
- uses: actions/upload-artifact@v2
+ - name: Cache BASE image
+ uses: actions/cache@v3
with:
- name: otp_docker_base
path: otp_docker_base.tar
+ key: ${{ runner.os }}-${{ hashFiles('.github/dockerfiles/Dockerfile.ubuntu-base', '.github/scripts/build-base-image.sh') }}
+ - name: Build BASE image
+ run: .github/scripts/build-base-image.sh "${BASE_BRANCH}" 64-bit
+ - name: Cache pre-built tar archives
+ id: pre-built-cache
+ uses: actions/cache@v3
+ with:
+ path: |
+ otp_src.tar.gz
+ otp_cache.tar.gz
+ key: prebuilt-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: |
+ prebuilt-${{ github.base_ref }}-${{ github.event.pull_request.base.sha }}
+ - uses: dorny/paths-filter@v2
+ id: cache
+ with:
+ filters: |
+ no-cache:
+ - '.github/**'
+ deleted:
+ - deleted: '**'
+ bootstrap:
+ - 'bootstrap/**'
+ configure:
+ - '**.ac'
+ - '**.in'
+ list-files: shell
+ - name: Restore from cache
+ env:
+ NO_CACHE: ${{ steps.cache.outputs.no-cache }}
+ BOOTSTRAP: ${{ steps.cache.outputs.bootstrap }}
+ CONFIGURE: ${{ steps.cache.outputs.configure }}
+ run: |
+ .github/scripts/restore-from-prebuilt.sh "`pwd`" \
+ "`pwd`/.github/otp.tar.gz" \
+ "`pwd`/otp_archive.tar.gz" \
+ '${{ github.event_name }}' \
+ '${{ steps.cache.outputs.deleted_files }}' \
+ '${{ steps.changes.outputs.changes }}'
+ - name: Upload restored cache
+ uses: actions/upload-artifact@v3
+ if: runner.debug == 1
+ with:
+ name: restored-cache
+ path: .github/otp.tar.gz
- name: Build image
run: |
- mv otp_src.tar.gz .github/otp.tar.gz
docker build --tag otp \
--build-arg MAKEFLAGS=-j$(($(nproc) + 2)) \
--file ".github/dockerfiles/Dockerfile.64-bit" \
.github/
- - name: Save Erlang/OTP image
- run: |
- docker run -v $PWD:/github --entrypoint "" otp \
- tar czf /github/otp-ubuntu-20.04.tar.gz /buildroot/ /otp/
- - name: Upload otp ubuntu image
- uses: actions/upload-artifact@v2
- with:
- name: otp-ubuntu-20.04
- path: otp-ubuntu-20.04.tar.gz
- name: Build pre-built tar archives
run: |
docker run -v $PWD:/github --entrypoint "" otp \
- scripts/build-otp-tar -o /github/otp_clean_src.tar.gz /github/otp_src.tar.gz -b /buildroot/otp/ /buildroot/otp.tar.gz
- - name: Upload pre-built tar archive
- uses: actions/upload-artifact@v2
- with:
- name: otp_prebuilt_no_chunks
- path: otp_src.tar.gz
-
- changed-apps:
- name: Calculate changed applications
- runs-on: ubuntu-latest
- outputs:
- changes: ${{ steps.changes.outputs.changes }}
- all: ${{ steps.apps.outputs.all }}
- steps:
- - uses: actions/checkout@v2
- - name: Get applications
- id: apps
+ scripts/build-otp-tar -o /github/otp_clean_src.tar.gz /github/otp_src.tar.gz -b /buildroot/otp/ /github/otp_src.tar.gz
+ - name: Build cache
run: |
- .github/scripts/path-filters.sh > .github/scripts/path-filters.yaml
- ALL_APPS=$(grep '^[a-z_]*:' .github/scripts/path-filters.yaml | sed 's/:.*$//')
- ALL_APPS=$(jq -n --arg inarr "${ALL_APPS}" '$inarr | split("\n")' | tr '\n' ' ')
- echo "::set-output name=all::${ALL_APPS}"
- - uses: dorny/paths-filter@v2
- id: changes
+ if [ -f otp_cache.tar.gz ]; then
+ gunzip otp_cache.tar.gz
+ else
+ docker run -v $PWD:/github --entrypoint "" otp \
+ bash -c 'cp ../otp_cache.tar /github/'
+ fi
+ docker run -v $PWD:/github --entrypoint "" otp \
+ bash -c 'set -x; C_APPS=$(ls -d ./lib/*/c_src); find Makefile ./make ./erts ./bin/`erts/autoconf/config.guess` ./lib/erl_interface ./lib/jinterface ${C_APPS} `echo "${C_APPS}" | sed -e 's:c_src$:priv:'` -type f -newer README.md \! -name "*.beam" \! -path "*/doc/*" | xargs tar --transform "s:^./:otp/:" -uvf /github/otp_cache.tar'
+ gzip otp_cache.tar
+ - name: Upload pre-built tar archive
+ uses: actions/upload-artifact@v3
with:
- filters: .github/scripts/path-filters.yaml
+ name: otp_prebuilt
+ path: |
+ otp_src.tar.gz
+ otp_cache.tar.gz
build-macos:
name: Build Erlang/OTP (macOS)
@@ -107,16 +143,16 @@ jobs:
env:
WXWIDGETS_VERSION: 3.1.5
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Download source archive
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
- name: otp_prebuilt_no_chunks
+ name: otp_prebuilt
- name: Cache wxWidgets
id: wxwidgets-cache
- uses: actions/cache@v2
+ uses: actions/cache@v3
with:
path: wxWidgets
key: wxWidgets-${{ env.WXWIDGETS_VERSION }}-${{ runner.os }}-12
@@ -130,7 +166,7 @@ jobs:
tar -xzf ./otp_src.tar.gz
export PATH=$PWD/wxWidgets/release/bin:$PATH
cd otp
- $GITHUB_WORKSPACE/.github/scripts/build-macos.sh
+ $GITHUB_WORKSPACE/.github/scripts/build-macos.sh build_docs --disable-dynamic-ssl-lib
tar -czf otp_macos_$(cat OTP_VERSION)_x86-64.tar.gz -C release .
- name: Test Erlang
@@ -142,7 +178,7 @@ jobs:
./bin/erl -noshell -eval '{wx_ref,_,_,_} = wx:new(), io:format("wx ok~n"), halt().'
- name: Upload tarball
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: otp_prebuilt_macos_x86-64
path: otp/otp_macos_*_x86-64.tar.gz
@@ -155,21 +191,17 @@ jobs:
runs-on: macos-12
needs: pack
steps:
+ - uses: actions/checkout@v3
- name: Download source archive
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
- name: otp_prebuilt_no_chunks
+ name: otp_prebuilt
- name: Compile Erlang
run: |
tar -xzf ./otp_src.tar.gz
cd otp
- export ERL_TOP=`pwd`
- export MAKEFLAGS="-j$(($(nproc) + 2)) -O"
- export ERLC_USE_SERVER=true
- ./otp_build configure --xcomp-conf=./xcomp/erl-xcomp-arm64-ios.conf --without-ssl
- ./otp_build boot -a
- ./otp_build release -a
+ $GITHUB_WORKSPACE/.github/scripts/build-macos.sh --xcomp-conf=./xcomp/erl-xcomp-arm64-ios.conf --without-ssl
- name: Package .xcframework
run: |
@@ -180,7 +212,7 @@ jobs:
xcodebuild -create-xcframework -output ./liberlang.xcframework -library liberlang.a
- name: Upload framework
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: ios_framework_${{ env.TARGET_ARCH }}
path: otp/liberlang.xcframework
@@ -195,7 +227,7 @@ jobs:
runs-on: windows-2022
needs: pack
steps:
- - uses: Vampire/setup-wsl@v1.2.1
+ - uses: Vampire/setup-wsl@v2
with:
distribution: Ubuntu-18.04
@@ -206,10 +238,10 @@ jobs:
shell: cmd
run: |
choco install openssl
- move "c:\\Program Files\\OpenSSL-Win64" "c:\\OpenSSL-Win64"
+ move "c:\\Program Files\\OpenSSL" "c:\\OpenSSL-Win64"
- name: Cache wxWidgets
- uses: actions/cache@v2
+ uses: actions/cache@v3
with:
path: wxWidgets
key: wxWidgets-${{ env.WXWIDGETS_VERSION }}-${{ runner.os }}
@@ -217,11 +249,13 @@ jobs:
# actions/cache on Windows sometimes does not set cache-hit even though there was one. Setting it manually.
- name: Set wxWidgets cache
id: wxwidgets-cache
+ env:
+ WSLENV: GITHUB_OUTPUT/p
run: |
if [ -d wxWidgets ]; then
- echo "::set-output name=cache-hit::true"
+ echo "cache-hit=true" >> $GITHUB_OUTPUT
else
- echo "::set-output name=cache-hit::false"
+ echo "cache-hit=false" >> $GITHUB_OUTPUT
fi
- name: Download wxWidgets
@@ -249,9 +283,9 @@ jobs:
nmake TARGET_CPU=amd64 BUILD=release SHARED=0 DIR_SUFFIX_CPU= -f makefile.vc
- name: Download source archive
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
- name: otp_prebuilt_no_chunks
+ name: otp_prebuilt
- name: Compile Erlang
run: |
@@ -272,11 +306,62 @@ jobs:
./otp_build installer_win32
- name: Upload installer
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: otp_win32_installer
path: otp/release/win32/otp*.exe
+ build-flavors:
+ name: Build Erlang/OTP (Types and Flavors)
+ runs-on: ubuntu-latest
+ needs: pack
+ if: contains(needs.pack.outputs.changes, 'emulator')
+
+ steps:
+ - uses: actions/checkout@v3
+ ## Download docker images
+ - name: Cache BASE image
+ uses: actions/cache@v3
+ with:
+ path: otp_docker_base.tar
+ key: ${{ runner.os }}-${{ hashFiles('.github/dockerfiles/Dockerfile.ubuntu-base', '.github/scripts/build-base-image.sh') }}
+ - name: Docker login
+ uses: docker/login-action@v2
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Build BASE image
+ run: .github/scripts/build-base-image.sh "${BASE_BRANCH}" 64-bit
+ - name: Cache pre-built tar archives
+ uses: actions/cache@v3
+ with:
+ path: |
+ otp_src.tar.gz
+ otp_cache.tar.gz
+ key: prebuilt-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: |
+ prebuilt-${{ github.ref_name }}-${{ github.sha }}
+ - name: Build image
+ run: |
+ .github/scripts/restore-from-prebuilt.sh `pwd` .github/otp.tar.gz
+ rm -f otp_{src,cache}.tar.gz
+ docker build --tag otp \
+ --build-arg MAKEFLAGS=-j$(($(nproc) + 2)) \
+ --file ".github/dockerfiles/Dockerfile.64-bit" \
+ .github/
+ - name: Build Erlang/OTP flavors and types
+ run: |
+ TYPES="opt debug lcnt"
+ FLAVORS="emu jit"
+ for TYPE in ${TYPES}; do
+ for FLAVOR in ${FLAVORS}; do
+ echo "::group::{TYPE=$TYPE FLAVOR=$FLAVOR}"
+ docker run otp "make TYPE=$TYPE FLAVOR=$FLAVOR"
+ echo "::endgroup::"
+ done
+ done
+
build:
name: Build Erlang/OTP
runs-on: ubuntu-latest
@@ -288,17 +373,23 @@ jobs:
fail-fast: false
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Download source archive
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
- name: otp_prebuilt_no_chunks
+ name: otp_prebuilt
- name: Docker login
- uses: docker/login-action@v1
+ uses: docker/login-action@v2
with:
- registry: docker.pkg.github.com
+ registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Cache BASE image
+ uses: actions/cache@v3
+ if: matrix.type == 'clang'
+ with:
+ path: otp_docker_base.tar
+ key: ${{ runner.os }}-${{ hashFiles('.github/dockerfiles/Dockerfile.ubuntu-base', '.github/scripts/build-base-image.sh') }}
- name: Build base image
run: .github/scripts/build-base-image.sh "${BASE_BRANCH}" ${{ matrix.type }}
- name: Build ${{ matrix.type }} image
@@ -312,65 +403,62 @@ jobs:
runs-on: ubuntu-latest
needs: pack
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
## Download docker images
- name: Docker login
- uses: docker/login-action@v1
+ uses: docker/login-action@v2
with:
- registry: docker.pkg.github.com
+ registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- - name: Download base build
- if: needs.pack.outputs.BASE_BUILD == 're-built'
- uses: actions/download-artifact@v2
+ - name: Cache BASE image
+ uses: actions/cache@v3
with:
- name: otp_docker_base
- - name: Download otp build
- uses: actions/download-artifact@v2
- with:
- name: otp-ubuntu-20.04
- - name: Restore docker image
- run: .github/scripts/restore-otp-image.sh "${BASE_BRANCH}"
-
- ## Build pre-built tar with chunks
- - name: Build doc chunks
- run: |
- docker build -t otp - <<EOF
- FROM otp
- RUN make docs DOC_TARGETS=chunks
- EOF
- - name: Build pre-built tar archives
+ path: otp_docker_base.tar
+ key: ${{ runner.os }}-${{ hashFiles('.github/dockerfiles/Dockerfile.ubuntu-base', '.github/scripts/build-base-image.sh') }}
+ - name: Build BASE image
+ run: .github/scripts/build-base-image.sh "${BASE_BRANCH}" 64-bit
+ - name: Cache pre-built tar archives
+ uses: actions/cache@v3
+ with:
+ path: |
+ otp_src.tar.gz
+ otp_cache.tar.gz
+ key: prebuilt-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: |
+ prebuilt-${{ github.ref_name }}-${{ github.sha }}
+ - name: Build image
run: |
- docker run -v $PWD:/github otp \
- "scripts/build-otp-tar -o /github/otp_clean_src.tar.gz /github/otp_src.tar.gz -b /buildroot/otp/ /buildroot/otp.tar.gz"
- - name: Upload pre-built tar archive
- uses: actions/upload-artifact@v2
- with:
- name: otp_prebuilt
- path: otp_src.tar.gz
+ .github/scripts/restore-from-prebuilt.sh `pwd` .github/otp.tar.gz
+ rm -f otp_{src,cache}.tar.gz
+ docker build --tag otp \
+ --build-arg MAKEFLAGS=-j$(($(nproc) + 2)) \
+ --file ".github/dockerfiles/Dockerfile.64-bit" \
+ .github/
+
## Build all the documentation
- name: Build documentation
run: |
docker build -t otp - <<EOF
FROM otp
- RUN make docs
+ RUN make release docs release_docs && sudo make install-docs
EOF
- name: Release docs to publish
run: |
docker run -v $PWD/:/github otp \
- "make release docs release_docs && make release_docs DOC_TARGETS='man html pdf' RELEASE_ROOT=/github/docs"
+ "make release_docs DOC_TARGETS='man html pdf' RELEASE_ROOT=/github/docs"
sudo chown -R `whoami` docs
cd docs
tar czf ../otp_doc_man.tar.gz man
rm -rf man
tar czf ../otp_doc_html.tar.gz *
- name: Upload html documentation archive
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: otp_doc_html
path: otp_doc_html.tar.gz
- name: Upload man documentation archive
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: otp_doc_man
path: otp_doc_man.tar.gz
@@ -378,32 +466,45 @@ jobs:
- name: Run xmllint
run: docker run otp "make xmllint"
- name: Run html link check
- run: docker run -v $PWD/:/github otp "scripts/otp_html_check /github/docs doc/index.html"
+ run: docker run -v $PWD/:/github otp "/github/scripts/otp_html_check /github/docs doc/index.html"
static:
name: Run static analysis
runs-on: ubuntu-latest
needs: pack
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
## Download docker images
- name: Docker login
- uses: docker/login-action@v1
+ uses: docker/login-action@v2
with:
- registry: docker.pkg.github.com
+ registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- - name: Download base build
- if: needs.pack.outputs.BASE_BUILD == 're-built'
- uses: actions/download-artifact@v2
- with:
- name: otp_docker_base
- - name: Download otp build
- uses: actions/download-artifact@v2
+ - name: Cache BASE image
+ uses: actions/cache@v3
with:
- name: otp-ubuntu-20.04
- - name: Restore docker image
- run: .github/scripts/restore-otp-image.sh "${BASE_BRANCH}"
+ path: otp_docker_base.tar
+ key: ${{ runner.os }}-${{ hashFiles('.github/dockerfiles/Dockerfile.ubuntu-base', '.github/scripts/build-base-image.sh') }}
+ - name: Build BASE image
+ run: .github/scripts/build-base-image.sh "${BASE_BRANCH}" 64-bit
+ - name: Cache pre-built tar archives
+ uses: actions/cache@v3
+ with:
+ path: |
+ otp_src.tar.gz
+ otp_cache.tar.gz
+ key: prebuilt-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: |
+ prebuilt-${{ github.ref_name }}-${{ github.sha }}
+ - name: Build image
+ run: |
+ .github/scripts/restore-from-prebuilt.sh `pwd` .github/otp.tar.gz
+ rm -f otp_{src,cache}.tar.gz
+ docker build --tag otp \
+ --build-arg MAKEFLAGS=-j$(($(nproc) + 2)) \
+ --file ".github/dockerfiles/Dockerfile.64-bit" \
+ .github/
- name: Install clang-format
run: |
docker build -t otp - <<EOF
@@ -415,39 +516,52 @@ jobs:
run: docker run otp "make format-check"
## Run dialyzer
- name: Run dialyzer
- run: docker run otp 'scripts/run-dialyzer'
+ run: docker run -v $PWD/:/github otp '/github/scripts/run-dialyzer'
test:
name: Test Erlang/OTP
runs-on: ubuntu-latest
- needs: [pack, changed-apps]
- if: needs.changed-apps.outputs.changes != '[]'
+ needs: pack
+ if: needs.pack.outputs.changes != '[]'
strategy:
matrix:
- # type: ${{ fromJson(needs.changed-apps.outputs.all) }}
- type: ${{ fromJson(needs.changed-apps.outputs.changes) }}
+ # type: ${{ fromJson(needs.pack.outputs.all) }}
+ type: ${{ fromJson(needs.pack.outputs.changes) }}
# type: ["os_mon","sasl"]
fail-fast: false
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
## Download docker images
- - name: Download base build
- if: needs.pack.outputs.BASE_BUILD == 're-built'
- uses: actions/download-artifact@v2
+ - name: Cache BASE image
+ uses: actions/cache@v3
with:
- name: otp_docker_base
- - name: Download otp build
- uses: actions/download-artifact@v2
- with:
- name: otp-ubuntu-20.04
+ path: otp_docker_base.tar
+ key: ${{ runner.os }}-${{ hashFiles('.github/dockerfiles/Dockerfile.ubuntu-base', '.github/scripts/build-base-image.sh') }}
- name: Docker login
- uses: docker/login-action@v1
+ uses: docker/login-action@v2
with:
- registry: docker.pkg.github.com
+ registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- - name: Restore docker image
- run: .github/scripts/restore-otp-image.sh "${BASE_BRANCH}"
+ - name: Build BASE image
+ run: .github/scripts/build-base-image.sh "${BASE_BRANCH}" 64-bit
+ - name: Cache pre-built tar archives
+ uses: actions/cache@v3
+ with:
+ path: |
+ otp_src.tar.gz
+ otp_cache.tar.gz
+ key: prebuilt-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: |
+ prebuilt-${{ github.ref_name }}-${{ github.sha }}
+ - name: Build image
+ run: |
+ .github/scripts/restore-from-prebuilt.sh `pwd` .github/otp.tar.gz
+ rm -f otp_{src,cache}.tar.gz
+ docker build --tag otp \
+ --build-arg MAKEFLAGS=-j$(($(nproc) + 2)) \
+ --file ".github/dockerfiles/Dockerfile.64-bit" \
+ .github/
- name: Run tests
id: run-tests
run: |
@@ -465,6 +579,7 @@ jobs:
sudo bash -c "echo 'core.%p' > /proc/sys/kernel/core_pattern"
docker run --ulimit core=-1 --ulimit nofile=5000:5000 --pids-limit 512 \
-e CTRUN_TIMEOUT=90 -e SPEC_POSTFIX=gh \
+ -e TEST_NEEDS_RELEASE=true -e "RELEASE_ROOT=/buildroot/otp/Erlang ∅⊤℞" \
-e EXTRA_ARGS="-ct_hooks cth_surefire [{path,\"/buildroot/otp/$DIR/make_test_dir/${{ matrix.type }}_junit.xml\"}]" \
-v "$PWD/make_test_dir:/buildroot/otp/$DIR/make_test_dir" \
otp "make TYPE=${TYPE} && make ${APP}_test TYPE=${TYPE}"
@@ -479,7 +594,7 @@ jobs:
sudo bash -c "chown -R `whoami` make_test_dir && chmod -R +r make_test_dir"
tar czf ${{ matrix.type }}_test_results.tar.gz make_test_dir
- name: Upload test results
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
if: always()
with:
name: ${{ matrix.type }}_test_results
@@ -491,18 +606,39 @@ jobs:
if: always() # Run even if the need has failed
needs: test
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
+ - name: Cache BASE image
+ uses: actions/cache@v3
+ with:
+ path: otp_docker_base.tar
+ key: ${{ runner.os }}-${{ hashFiles('.github/dockerfiles/Dockerfile.ubuntu-base', '.github/scripts/build-base-image.sh') }}
- name: Docker login
- uses: docker/login-action@v1
+ uses: docker/login-action@v2
with:
- registry: docker.pkg.github.com
+ registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Build BASE image
+ run: .github/scripts/build-base-image.sh "${BASE_BRANCH}" 64-bit
- name: Download test results
- uses: actions/download-artifact@v2
- - run: mv otp-ubuntu-20.04/otp-ubuntu-20.04.tar.gz .
- - name: Restore docker image
- run: .github/scripts/restore-otp-image.sh "${BASE_BRANCH}"
+ uses: actions/download-artifact@v3
+ - name: Cache pre-built tar archives
+ uses: actions/cache@v3
+ with:
+ path: |
+ otp_src.tar.gz
+ otp_cache.tar.gz
+ key: prebuilt-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: |
+ prebuilt-${{ github.ref_name }}-${{ github.sha }}
+ - name: Build image
+ run: |
+ .github/scripts/restore-from-prebuilt.sh `pwd` .github/otp.tar.gz
+ rm -f otp_{src,cache}.tar.gz
+ docker build --tag otp \
+ --build-arg MAKEFLAGS=-j$(($(nproc) + 2)) \
+ --file ".github/dockerfiles/Dockerfile.64-bit" \
+ .github/
- name: Merge test results
run: |
shopt -s nullglob
@@ -532,14 +668,14 @@ jobs:
-e 's:\(file="erts/\)make_test_dir/[^/]*:\1test:g' \
make_test_dir/*_junit.xml
- name: Upload test results
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
if: always()
with:
name: test_results
path: test_results.tar.gz
- name: Upload Test Results
if: always()
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: Unit Test Results
path: |
@@ -560,22 +696,22 @@ jobs:
run: |
TAG=${GITHUB_REF#refs/tags/}
VSN=${TAG#OTP-}
- echo "::set-output name=tag::${TAG}"
- echo "::set-output name=vsn::${VSN}"
+ echo "tag=${TAG}" >> $GITHUB_OUTPUT
+ echo "vsn=${VSN}" >> $GITHUB_OUTPUT
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
## Publish the pre-built archive and docs
- name: Download source archive
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: otp_prebuilt
- name: Download html docs
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: otp_doc_html
- name: Download man docs
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: otp_doc_man
@@ -613,7 +749,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Upload
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: Event File
path: ${{ github.event_path }}
diff --git a/.github/workflows/pr-comment.yaml b/.github/workflows/pr-comment.yaml
index b139d8232b..a72c98e09a 100644
--- a/.github/workflows/pr-comment.yaml
+++ b/.github/workflows/pr-comment.yaml
@@ -37,7 +37,7 @@ jobs:
steps:
- uses: actions/checkout@v2
## We create an initial comment with some useful help to the user
- - uses: actions/github-script@v5
+ - uses: actions/github-script@v5.1.1
with:
script: |
const script = require('./.github/scripts/pr-comment.js');
@@ -74,9 +74,9 @@ jobs:
done
if [ -d "Unit Test Results" ]; then
- echo "::set-output name=HAS_TEST_ARTIFACTS::true"
+ echo "HAS_TEST_ARTIFACTS=true" >> $GITHUB_OUTPUT
else
- echo "::set-output name=HAS_TEST_ARTIFACTS::false"
+ echo "HAS_TEST_ARTIFACTS=false" >> $GITHUB_OUTPUT
fi
- uses: actions/checkout@v2
@@ -107,7 +107,7 @@ jobs:
"${{ needs.pr-number.outputs.result }}"
- name: Deploy to github pages 🚀
- uses: JamesIves/github-pages-deploy-action@v4.2.2
+ uses: JamesIves/github-pages-deploy-action@v4.4.1
with:
token: ${{ secrets.ERLANG_TOKEN }}
branch: master # The branch the action should deploy to.
@@ -117,7 +117,7 @@ jobs:
## Append some usefull links and tips to the test results posted by
## Publish CT Test Results
- - uses: actions/github-script@v5
+ - uses: actions/github-script@v5.1.1
if: always()
with:
script: |
diff --git a/.github/workflows/sync-github-releases.yaml b/.github/workflows/sync-github-releases.yaml
index af3245f1ba..40dc72f684 100644
--- a/.github/workflows/sync-github-releases.yaml
+++ b/.github/workflows/sync-github-releases.yaml
@@ -15,11 +15,11 @@ jobs:
concurrency: sync-github-releases
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
## We need to login to the package registry in order to pull
## the base debian image.
- name: Docker login
- run: docker login https://docker.pkg.github.com -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
+ run: docker login https://ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
- name: Sync releases
env:
ERLANG_ORG_TOKEN: ${{ secrets.TRIGGER_ERLANG_ORG_BUILD }}
@@ -32,12 +32,12 @@ jobs:
concurrency: erlang.github.io-deploy
runs-on: ubuntu-20.04
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
token: ${{ secrets.ERLANG_TOKEN }}
repository: 'erlang/erlang.github.io'
path: erlang.github.io
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Update PRs
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
@@ -50,7 +50,7 @@ jobs:
.github/scripts/sync-github-prs.es erlang/otp "${GITHUB_WORKSPACE}/erlang.github.io/prs/"
- name: Deploy to github pages 🚀
- uses: JamesIves/github-pages-deploy-action@v4.2.2
+ uses: JamesIves/github-pages-deploy-action@v4.4.1
with:
token: ${{ secrets.ERLANG_TOKEN }}
branch: master # The branch the action should deploy to.
diff --git a/.github/workflows/update-base.yaml b/.github/workflows/update-base.yaml
index f043fab039..fa238c4803 100644
--- a/.github/workflows/update-base.yaml
+++ b/.github/workflows/update-base.yaml
@@ -27,13 +27,13 @@ jobs:
- name: Docker login
uses: docker/login-action@v2
with:
- registry: docker.pkg.github.com
+ registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
id: base
run: >-
- BASE_TAG=docker.pkg.github.com/${{ github.repository_owner }}/otp/${{ matrix.type }}
+ BASE_TAG=ghcr.io/${{ github.repository_owner }}/otp/${{ matrix.type }}
BASE_USE_CACHE=false
.github/scripts/build-base-image.sh "${{ matrix.branch }}"
- name: Push master image
diff --git a/.gitignore b/.gitignore
index c88056e46e..94664b47c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ TAGS
.idea
autom4te.cache
+configure.result.*
*.beam
*.asn1db
@@ -126,15 +127,12 @@ JAVADOC-GENERATED
/bootstrap/lib/edoc
/bootstrap/lib/erl_docgen
/bootstrap/lib/erl_interface
-/bootstrap/lib/hipe
-/bootstrap/lib/jinterface
/bootstrap/lib/parsetools
/bootstrap/lib/public_key
/bootstrap/lib/runtime_tools
/bootstrap/lib/sasl
/bootstrap/lib/snmp
/bootstrap/lib/syntax_tools
-/bootstrap/lib/test_server
/bootstrap/lib/wx
/bootstrap/lib/xmerl
@@ -235,11 +233,13 @@ JAVADOC-GENERATED
/lib/compiler/test/*_no_opt_SUITE.erl
/lib/compiler/test/*_no_copt_SUITE.erl
+/lib/compiler/test/*_no_copt_ssa_SUITE.erl
/lib/compiler/test/*_no_ssa_opt_SUITE.erl
/lib/compiler/test/*_post_opt_SUITE.erl
/lib/compiler/test/*_inline_SUITE.erl
/lib/compiler/test/*_r23_SUITE.erl
/lib/compiler/test/*_r24_SUITE.erl
+/lib/compiler/test/*_r25_SUITE.erl
/lib/compiler/test/*_no_module_opt_SUITE.erl
/lib/compiler/test/*_no_type_opt_SUITE.erl
/lib/compiler/test/*_dialyzer_SUITE.erl
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f78cef33db..0d7588d35c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,6 +6,7 @@
1. [Fixing a bug](#fixing-a-bug)
2. [Adding a new feature](#adding-a-new-feature)
3. [Before you submit your pull request](#before-you-submit-your-pull-request)
+ 4. [After you have submitted your pull request](#after-you-have-submitted-your-pull-request)
## License
@@ -149,3 +150,16 @@ If you want to change the setting only for the Erlang mode, you can use a hook l
(defun my-erlang-hook ()
(setq indent-tabs-mode nil))
```
+
+### After you have submitted your pull request
+
+* Follow the discussion following your pull request, answer questions, discuss and implement
+changes requested by reviewers.
+
+* If your pull requests introduces new public functions, they need to be tagged with the
+OTP release in which they _will_ appear in the `since` tag in the functions' documentation.
+As this is generally not yet certain at the time when your PR gets merged, the person assigned
+to your pull request should give you an internal ticket number (for example `OTP-12345`) to use
+as a placeholder in the respective `since` tags, like `since="OTP @OTP-12345@`. When your new
+functions are released with an OTP release, this placeholder will get replaced with the actual
+OTP version, leading to something like "OTP 26.0".
diff --git a/HOWTO/DEVELOPMENT.md b/HOWTO/DEVELOPMENT.md
index 2220d9ab71..b9be3026e1 100644
--- a/HOWTO/DEVELOPMENT.md
+++ b/HOWTO/DEVELOPMENT.md
@@ -1,7 +1,7 @@
# Developing Erlang/OTP
The Erlang/OTP development repository is quite large and the make system
-contains a lot of functionality to help when a developing. This howto
+contains a lot of functionality to help when developing. This howto
will try to showcase the most important features of the make system.
The guide is mostly aimed towards development on a Unix platform, but
@@ -539,7 +539,7 @@ build it locally if you want to.
Using the pre-built base you build an image like this:
```bash
-docker login docker.pkg.github.com
+docker login ghcr.io
git archive --prefix otp/ -o .github/otp.tar.gz HEAD
docker build -t my_otp_image -f .github/dockerfiles/Dockerfile.64-bit .github/
```
@@ -550,8 +550,8 @@ in order to fetch the base image. If you want to build the base image locally
you can do that like this:
```bash
-docker built -t docker.pkg.github.com/erlang/otp/ubuntu-base \
- --build-arg BASE=ubuntu --build-arg USER=otptest --build-arg uid=$(id -u) \
+docker build -t ghcr.io/erlang/otp/ubuntu-base \
+ --build-arg BASE=ubuntu:20.04 --build-arg USER=otptest --build-arg uid=$(id -u) \
--build-arg GROUP=uucp --build-arg gid=$(id -g) \
-f .github/dockerfiles/Dockerfile.ubuntu-base .github/
```
diff --git a/HOWTO/INSTALL-RASPBERRYPI3.md b/HOWTO/INSTALL-RASPBERRYPI3.md
index a32bad052b..77fdc67f55 100644
--- a/HOWTO/INSTALL-RASPBERRYPI3.md
+++ b/HOWTO/INSTALL-RASPBERRYPI3.md
@@ -257,9 +257,9 @@ Uncheck option:
(20)
- $ wget http://zlib.net/zlib-1.2.12.tar.gz
- $ tar xf zlib-1.2.12.tar.gz
- $ pushd zlib-1.2.12
+ $ wget http://zlib.net/zlib-1.2.13.tar.gz
+ $ tar xf zlib-1.2.13.tar.gz
+ $ pushd zlib-1.2.13
$ CHOST=armv8-rpi3-linux-gnueabihf ./configure --prefix=/Volumes/xtools-build-env/local
$ make
$ make install
diff --git a/HOWTO/INSTALL-WIN32.md b/HOWTO/INSTALL-WIN32.md
index 4ad0159bcd..bd8387aaae 100644
--- a/HOWTO/INSTALL-WIN32.md
+++ b/HOWTO/INSTALL-WIN32.md
@@ -68,7 +68,7 @@ This is the short story though, for the experienced and impatient:
<http://www.erlang.org/download.html>) and unpack with `tar`
to the windows disk for example to: /mnt/c/src/
- * Install mingw-gcc, and make: `sudo apt install g++-mingw-w64 gcc-mingw-w64 make`
+ * Install mingw-gcc, and make: `sudo apt update && sudo apt install g++-mingw-w64 gcc-mingw-w64 make`
* `$ cd UNPACK_DIR`
@@ -150,7 +150,7 @@ the different tools:
Install into `C:/OpenSSL-Win64` (or `C:/OpenSSL-Win32`)
* wxWidgets (optional)
- You need this to build wx and use gui's in debugger and observer.
+ You need this to build wx to use gui's in debugger and observer.
We recommend v3.1.4 or later.
Unpack into `c:/opt/local64/pgm/wxWidgets-3.1.4`
diff --git a/HOWTO/TESTING.md b/HOWTO/TESTING.md
index 66cc21f89a..c6bad4f65c 100644
--- a/HOWTO/TESTING.md
+++ b/HOWTO/TESTING.md
@@ -193,7 +193,7 @@ Examining the results
---------------------
Open the file `$ERL_TOP/release/tests/test_server/index.html` in a web browser. Or open
-`$ERL_TOP/release/tests/test_server/last_test.html` when a test suite is running to
+`$ERL_TOP/release/tests/test_server/suite.log.latest.html` when a test suite is running to
examine the results so far for the currently executing test suite.
diff --git a/Makefile.in b/Makefile.in
index cc92df3a21..72ae67524a 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -379,12 +379,12 @@ endif
# bootstrap is not included, it requires a pre built emulator...
ifeq ($(OTP_TINY_BUILD),true)
all_bootstraps: erl_interface emulator bootstrap_setup \
- tiny_secondary_bootstrap_build tiny_secondary_bootstrap_copy
+ tiny_secondary_bootstrap_copy
else
all_bootstraps: erl_interface emulator \
bootstrap_setup \
- secondary_bootstrap_build secondary_bootstrap_copy \
- tertiary_bootstrap_build tertiary_bootstrap_copy
+ secondary_bootstrap_copy \
+ tertiary_bootstrap_copy
endif
#
@@ -429,7 +429,7 @@ endif
# Target only used when building commercial ERTS patches
# ---------------------------------------------------------------
-release_docs docs: doc_bootstrap_build doc_bootstrap_copy mod2app
+release_docs docs: doc_bootstrap_copy mod2app
ifeq ($(OTP_SMALL_BUILD),true)
cd $(ERL_TOP)/lib && \
PATH=$(BOOT_PREFIX)"$${PATH}" ERL_TOP=$(ERL_TOP) \
@@ -464,7 +464,7 @@ endif
$(DOCGEN)/priv/bin/validate_links.escript $(ERL_TOP) make/$(TARGET)/mod2app.xml \
lib/*/doc/xml/*.xml erts/doc/xml/*.xml system/doc/xml/*/*.xml
-mod2app: doc_bootstrap_build doc_bootstrap_copy $(ERL_TOP)/make/$(TARGET)/mod2app.xml
+mod2app: doc_bootstrap_copy $(ERL_TOP)/make/$(TARGET)/mod2app.xml
$(ERL_TOP)/make/$(TARGET)/mod2app.xml: erts/doc/src/Makefile lib/*/doc/src/Makefile
PATH=$(BOOT_PREFIX)"$${PATH}" escript $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_docgen/priv/bin/xref_mod_app.escript -topdir $(ERL_TOP) -outfile $(ERL_TOP)/make/$(TARGET)/mod2app.xml
@@ -625,40 +625,47 @@ $(BOOTSTRAP_ROOT)/bootstrap/bin/$(TARGET)/inet_gethost: $(ERL_TOP)/bin/$(TARGET)
@cp $(ERL_TOP)/bin/$(TARGET)/inet_gethost $(BOOTSTRAP_ROOT)/bootstrap/bin/$(TARGET)/inet_gethost
@chmod 755 $(BOOTSTRAP_ROOT)/bootstrap/bin/$(TARGET)/inet_gethost
+$(BOOTSTRAP_ROOT)/bootstrap/bin/$(TARGET):
+ $(V_at)mkdir $@
-bootstrap_setup_target:
+bootstrap_setup_target: $(BOOTSTRAP_ROOT)/bootstrap/bin/$(TARGET)
@{ test -r $(BOOTSTRAP_ROOT)/bootstrap/target && \
test $(TARGET) = `cat $(BOOTSTRAP_ROOT)/bootstrap/target`; } || \
echo $(TARGET) > $(BOOTSTRAP_ROOT)/bootstrap/target
- if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/bin/$(TARGET) ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/bin/$(TARGET) ; fi
-tiny_secondary_bootstrap_build:
- $(make_verbose)cd lib && \
- ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) opt SECONDARY_BOOTSTRAP=true TINY_BUILD=true ERL_COMPILE_WARNINGS_AS_ERRORS=yes
-
-secondary_bootstrap_build:
- $(make_verbose)cd lib && \
- ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) opt SECONDARY_BOOTSTRAP=true ERL_COMPILE_WARNINGS_AS_ERRORS=yes
-
-tiny_secondary_bootstrap_copy:
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/ebin ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/include ; fi
- $(V_at)for x in lib/parsetools/ebin/*.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
- $(V_at)for x in lib/parsetools/include/*.hrl; do \
+# The TINY SECONDARY bootstrap
+# - sasl needed to create start.boot
+# - parsetools needed by anything with an yrl
+TINY_SECONDARY_BOOTSTRAP=parsetools sasl
+TINY_SECONDARY_BOOTSTRAP_BUILD=$(TINY_SECONDARY_BOOTSTRAP)
+# The SECONDARY bootstrap
+# - asn1 needed by public_key
+SECONDARY_BOOTSTRAP=$(TINY_SECONDARY_BOOTSTRAP) asn1
+SECONDARY_BOOTSTRAP_BUILD=$(TINY_SECONDARY_BOOTSTRAP_BUILD) asn1/src
+
+# The TERTIARY bootstrap
+# - wx is needed for wx_object behaviour in debugger, observer and et
+# - public_key is needed for include files in ssl and ssh
+# - erl_interface is needed by odbc
+# - syntax_tools is needed by diameter
+# - snmp is needed to compile tests
+# - runtime_tools includes are needed by observer
+# - xmerl includes are needed by ct, edoc, erl_docgen and wx
+# - common_test is needed to compile tests
+TERTIARY_BOOTSTRAP_BUILD=parsetools wx public_key erl_interface syntax_tools snmp
+TERTIARY_BOOTSTRAP=$(TERTIARY_BOOTSTRAP_BUILD) runtime_tools xmerl common_test
+
+# The DOC bootstrap
+DOC_BOOTSTRAP_BUILD=xmerl edoc erl_docgen
+DOC_BOOTSTRAP=$(DOC_BOOTSTRAP_BUILD)
+
+$(BOOTSTRAP_ROOT)/bootstrap/lib/%/update:
+ $(V_at)for x in "$(dir $@)" "$(dir $@)/ebin" "$(dir $@)/include"; do \
+ if [ ! -d "$$x" ]; then mkdir "$$x"; fi \
+ done
+ $(V_at)for x in $(wildcard lib/$*/ebin/*.beam); do \
BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/include/$$BN; \
+ TF=$(dir $@)/ebin/$$BN; \
test -f $$TF && \
test '!' -z "`find $$x -newer $$TF -print`" && \
${INSTALL_DATA} -p $$x $$TF; \
@@ -666,299 +673,39 @@ tiny_secondary_bootstrap_copy:
${INSTALL_DATA} -p $$x $$TF; \
true; \
done
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/ebin ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/include ; fi
- $(V_at)for x in lib/sasl/ebin/*.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
+ $(V_at)for x in $(wildcard lib/$*/include/*.hrl) $(wildcard lib/$*/include/*.h); do \
+ BN=`basename $$x`; \
+ TF=$(dir $@)/include/$$BN; \
+ test -f $$TF && \
+ test '!' -z "`find $$x -newer $$TF -print`" && \
+ ${INSTALL_DATA} -p $$x $$TF; \
+ test '!' -f $$TF && \
+ ${INSTALL_DATA} -p $$x $$TF; \
+ true; \
done
-secondary_bootstrap_copy:
- $(make_verbose)
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/ebin ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/include ; fi
- $(V_at)for x in lib/parsetools/ebin/*.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# $(V_at)${INSTALL_DATA} -p lib/parsetools/ebin/*.beam $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/ebin
- $(V_at)for x in lib/parsetools/include/*.hrl; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/include/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# $(V_at)${INSTALL_DATA} -p -f lib/parsetools/include/*.hrl $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools/include
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/asn1 ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/asn1 ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/asn1/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/asn1/ebin ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/asn1/src ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/asn1/src ; fi
- $(V_at)for x in lib/asn1/ebin/*.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/asn1/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# $(V_at)${INSTALL_DATA} -p lib/asn1/ebin/*.beam $(BOOTSTRAP_ROOT)/bootstrap/lib/asn1/ebin
- $(V_at)for x in lib/asn1/src/*.[eh]rl; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/asn1/src/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# $(V_at)${INSTALL_DATA} -p -f lib/asn1/src/*.erl lib/asn1/src/*.hrl $(BOOTSTRAP_ROOT)/bootstrap/lib/asn1/src
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/xmerl ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/xmerl ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/xmerl/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/xmerl/include ; fi
- $(V_at)for x in lib/xmerl/include/*.hrl; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/xmerl/include/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-
-tertiary_bootstrap_build:
+tiny_secondary_bootstrap:
$(make_verbose)cd lib && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) opt TERTIARY_BOOTSTRAP=true ERL_COMPILE_WARNINGS_AS_ERRORS=yes
+ $(MAKE) opt BOOTSTRAP="$(TINY_SECONDARY_BOOTSTRAP_BUILD)" ERL_COMPILE_WARNINGS_AS_ERRORS=yes
-tertiary_bootstrap_copy:
- $(make_verbose)
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/snmp ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/snmp ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/snmp/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/snmp/ebin ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/snmp/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/snmp/include ; fi
- $(V_at)for x in lib/snmp/ebin/*.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/snmp/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# $(V_at)${INSTALL_DATA} -p lib/snmp/ebin/*.beam $(BOOTSTRAP_ROOT)/bootstrap/lib/snmp/ebin
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/ebin ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/include ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/wx ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/wx ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/wx/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/wx/ebin ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/wx/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/wx/include ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/common_test ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/common_test ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/common_test/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/common_test/include ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/runtime_tools ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/runtime_tools ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/runtime_tools/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/runtime_tools/include ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/public_key ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/public_key ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/public_key/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/public_key/include ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_interface ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_interface ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_interface/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_interface/include ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/ ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/ ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv/com ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv/com ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv/com/ericsson ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv/com/ericsson ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv/com/ericsson/otp ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv/com/ericsson/otp ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv/com/ericsson/otp/erlang ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv/com/ericsson/otp/erlang ; fi
- $(V_at)for x in lib/sasl/ebin/*.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# $(V_at)${INSTALL_DATA} -p lib/sasl/ebin/*.beam $(BOOTSTRAP_ROOT)/bootstrap/lib/sasl/ebin
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/syntax_tools ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/syntax_tools ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/syntax_tools/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/syntax_tools/ebin ; fi
- $(V_at)for x in lib/syntax_tools/ebin/*.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/syntax_tools/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
- $(V_at)for x in lib/wx/include/*.hrl; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/wx/include/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# copy wx_object to remove undef behaviour warnings
- $(V_at)for x in lib/wx/ebin/wx_object.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/wx/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-
-# copy test includes to be able to compile tests with bootstrap compiler
- $(V_at)for x in lib/common_test/include/*.hrl; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/common_test/include/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-
-# copy runtime_tool includes to be able to compile with include_lib
- $(V_at)for x in lib/runtime_tools/include/*.hrl; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/runtime_tools/include/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# copy public_key includes to be able to compile with include_lib
- $(V_at)for x in lib/public_key/include/*.hrl; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/public_key/include/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# copy erl_interface includes
- $(V_at)for x in lib/erl_interface/include/*.h; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/erl_interface/include/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# copy jinterface priv directory
- $(V_at)if test -d lib/jinterface/priv; then \
- for x in lib/jinterface/priv/OtpErlang.jar; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done; \
- for x in lib/jinterface/priv/com/ericsson/otp/erlang/*; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/jinterface/priv/com/ericsson/otp/erlang/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done; \
- fi
-# $(V_at)${INSTALL_DATA} -p lib/syntax_tools/ebin/*.beam $(BOOTSTRAP_ROOT)/bootstrap/lib/syntax_tools/ebin
-
-doc_bootstrap_build:
+secondary_bootstrap:
$(make_verbose)cd lib && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) opt DOC_BOOTSTRAP=true
+ $(MAKE) opt BOOTSTRAP="$(SECONDARY_BOOTSTRAP_BUILD)" ERL_COMPILE_WARNINGS_AS_ERRORS=yes
-doc_bootstrap_copy:
- $(make_verbose)
-# XMERL
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/xmerl ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/xmerl ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/xmerl/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/xmerl/ebin ; fi
- $(V_at)for x in lib/xmerl/ebin/*.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/xmerl/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# xmerl/include already copied in secondary bootstrap
-# EDOC
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/edoc ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/edoc ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/edoc/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/edoc/ebin ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/edoc/include ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/edoc/include ; fi
- $(V_at)for x in lib/edoc/ebin/*.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/edoc/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
- $(V_at)for x in lib/edoc/include/*.hrl; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/edoc/include/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
-# ERL_DOCGEN
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_docgen ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_docgen ; fi
- $(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_docgen/ebin ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_docgen/ebin ; fi
- $(V_at)for x in lib/erl_docgen/ebin/*.beam; do \
- BN=`basename $$x`; \
- TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/erl_docgen/ebin/$$BN; \
- test -f $$TF && \
- test '!' -z "`find $$x -newer $$TF -print`" && \
- ${INSTALL_DATA} -p $$x $$TF; \
- test '!' -f $$TF && \
- ${INSTALL_DATA} -p $$x $$TF; \
- true; \
- done
+tiny_secondary_bootstrap_copy: tiny_secondary_bootstrap $(foreach app, $(TINY_SECONDARY_BOOTSTRAP), $(BOOTSTRAP_ROOT)/bootstrap/lib/$(app)/update)
+secondary_bootstrap_copy: secondary_bootstrap $(foreach app, $(SECONDARY_BOOTSTRAP), $(BOOTSTRAP_ROOT)/bootstrap/lib/$(app)/update)
+
+tertiary_bootstrap:
+ $(make_verbose) ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
+ $(MAKE) -C lib opt BOOTSTRAP="$(TERTIARY_BOOTSTRAP_BUILD)" ERL_COMPILE_WARNINGS_AS_ERRORS=yes
+tertiary_bootstrap_copy: tertiary_bootstrap $(foreach app, $(TERTIARY_BOOTSTRAP), $(BOOTSTRAP_ROOT)/bootstrap/lib/$(app)/update)
+
+doc_bootstrap:
+ $(make_verbose) ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
+ $(MAKE) -C lib opt BOOTSTRAP="$(DOC_BOOTSTRAP_BUILD)"
+doc_bootstrap_copy: secondary_bootstrap_copy doc_bootstrap tertiary_bootstrap_copy $(foreach app, $(DOC_BOOTSTRAP), $(BOOTSTRAP_ROOT)/bootstrap/lib/$(app)/update)
$(V_at)for d in priv priv/bin priv/css priv/dtd priv/dtd_html_entities priv/dtd_man_entities priv/images priv/js priv/js/flipmenu priv/xsl; do \
if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_docgen/$$d ; then mkdir -p $(BOOTSTRAP_ROOT)/bootstrap/lib/erl_docgen/$$d ; fi; \
for x in lib/erl_docgen/$$d/*; do \
@@ -1044,8 +791,8 @@ primary_bootstrap:
primary_bootstrap_build: primary_bootstrap_mkdirs primary_bootstrap_compiler \
primary_bootstrap_stdlib
$(make_verbose)cd lib && $(MAKE) ERLC_FLAGS='-pa $(BOOTSTRAP_COMPILER)/ebin $(DETERMINISM_FLAG)' \
- BOOTSTRAP_TOP=$(BOOTSTRAP_TOP) \
- BOOTSTRAP=1 opt
+ BOOTSTRAP_TOP=$(BOOTSTRAP_TOP) PRIMARY_BOOTSTRAP=1 \
+ BOOTSTRAP="kernel stdlib compiler" opt
primary_bootstrap_compiler:
$(make_verbose)cd lib/compiler && $(MAKE) \
diff --git a/OTP_VERSION b/OTP_VERSION
index be02e76e67..afd1a8c459 100644
--- a/OTP_VERSION
+++ b/OTP_VERSION
@@ -1 +1 @@
-25.3.1
+26.0-rc3
diff --git a/README.md b/README.md
index 7a588204fe..210b205200 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ cd otp
Checkout the branch or tag of your choice
```sh
-git checkout maint-24 # current latest stable version
+git checkout maint-25 # current latest stable version
```
Configure, build and install
@@ -97,7 +97,7 @@ Erlang/OTP is released under the [Apache License 2.0](http://www.apache.org/lice
> %CopyrightBegin%
>
-> Copyright Ericsson AB 2010-2021. All Rights Reserved.
+> Copyright Ericsson AB 2010-2023. All Rights Reserved.
>
> Licensed under the Apache License, Version 2.0 (the "License");
> you may not use this file except in compliance with the License.
diff --git a/bootstrap/bin/no_dot_erlang.boot b/bootstrap/bin/no_dot_erlang.boot
index 632e660dfd..40560ceae3 100644
--- a/bootstrap/bin/no_dot_erlang.boot
+++ b/bootstrap/bin/no_dot_erlang.boot
Binary files differ
diff --git a/bootstrap/bin/start.boot b/bootstrap/bin/start.boot
index 632e660dfd..40560ceae3 100644
--- a/bootstrap/bin/start.boot
+++ b/bootstrap/bin/start.boot
Binary files differ
diff --git a/bootstrap/bin/start_clean.boot b/bootstrap/bin/start_clean.boot
index 632e660dfd..40560ceae3 100644
--- a/bootstrap/bin/start_clean.boot
+++ b/bootstrap/bin/start_clean.boot
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_a.beam b/bootstrap/lib/compiler/ebin/beam_a.beam
index d81e1e3e3b..82f077ac4a 100644
--- a/bootstrap/lib/compiler/ebin/beam_a.beam
+++ b/bootstrap/lib/compiler/ebin/beam_a.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_asm.beam b/bootstrap/lib/compiler/ebin/beam_asm.beam
index 13b170ef05..e867a09433 100644
--- a/bootstrap/lib/compiler/ebin/beam_asm.beam
+++ b/bootstrap/lib/compiler/ebin/beam_asm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_block.beam b/bootstrap/lib/compiler/ebin/beam_block.beam
index 8587d9b27d..6691ba95f8 100644
--- a/bootstrap/lib/compiler/ebin/beam_block.beam
+++ b/bootstrap/lib/compiler/ebin/beam_block.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_bounds.beam b/bootstrap/lib/compiler/ebin/beam_bounds.beam
index b94142c660..8fa383080a 100644
--- a/bootstrap/lib/compiler/ebin/beam_bounds.beam
+++ b/bootstrap/lib/compiler/ebin/beam_bounds.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_call_types.beam b/bootstrap/lib/compiler/ebin/beam_call_types.beam
index f6411aa457..d0447aa582 100644
--- a/bootstrap/lib/compiler/ebin/beam_call_types.beam
+++ b/bootstrap/lib/compiler/ebin/beam_call_types.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_clean.beam b/bootstrap/lib/compiler/ebin/beam_clean.beam
index 91a536bf46..b15e63e206 100644
--- a/bootstrap/lib/compiler/ebin/beam_clean.beam
+++ b/bootstrap/lib/compiler/ebin/beam_clean.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_dict.beam b/bootstrap/lib/compiler/ebin/beam_dict.beam
index c57682576f..edfd03f4ab 100644
--- a/bootstrap/lib/compiler/ebin/beam_dict.beam
+++ b/bootstrap/lib/compiler/ebin/beam_dict.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_digraph.beam b/bootstrap/lib/compiler/ebin/beam_digraph.beam
index b522dac9c3..058880034d 100644
--- a/bootstrap/lib/compiler/ebin/beam_digraph.beam
+++ b/bootstrap/lib/compiler/ebin/beam_digraph.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_disasm.beam b/bootstrap/lib/compiler/ebin/beam_disasm.beam
index 9f591e9f70..342207cade 100644
--- a/bootstrap/lib/compiler/ebin/beam_disasm.beam
+++ b/bootstrap/lib/compiler/ebin/beam_disasm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_flatten.beam b/bootstrap/lib/compiler/ebin/beam_flatten.beam
index d98664ce03..cc6740a0ea 100644
--- a/bootstrap/lib/compiler/ebin/beam_flatten.beam
+++ b/bootstrap/lib/compiler/ebin/beam_flatten.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_jump.beam b/bootstrap/lib/compiler/ebin/beam_jump.beam
index 22dd3f82c3..a3ec46fb98 100644
--- a/bootstrap/lib/compiler/ebin/beam_jump.beam
+++ b/bootstrap/lib/compiler/ebin/beam_jump.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beam b/bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beam
index 97c218480c..aa94d58c93 100644
--- a/bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beam
+++ b/bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_listing.beam b/bootstrap/lib/compiler/ebin/beam_listing.beam
index f71f5d4bf5..b2c3a859b0 100644
--- a/bootstrap/lib/compiler/ebin/beam_listing.beam
+++ b/bootstrap/lib/compiler/ebin/beam_listing.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_opcodes.beam b/bootstrap/lib/compiler/ebin/beam_opcodes.beam
index 1f42b157ff..3479f1b6f9 100644
--- a/bootstrap/lib/compiler/ebin/beam_opcodes.beam
+++ b/bootstrap/lib/compiler/ebin/beam_opcodes.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa.beam b/bootstrap/lib/compiler/ebin/beam_ssa.beam
index f489f46b98..d035afa517 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_alias.beam b/bootstrap/lib/compiler/ebin/beam_ssa_alias.beam
new file mode 100644
index 0000000000..6b596a5dd8
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_alias.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_bc_size.beam b/bootstrap/lib/compiler/ebin/beam_ssa_bc_size.beam
index ba37502190..bd51e1ee79 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_bc_size.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_bc_size.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam b/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam
index e82b00aac9..fafa184c95 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam b/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam
index e0d4fac178..248c6936a4 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_check.beam b/bootstrap/lib/compiler/ebin/beam_ssa_check.beam
new file mode 100644
index 0000000000..a325f0795f
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_check.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam b/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
index 3f1ec45a9e..2356f5fe9a 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam b/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
index 690e96e2d6..9f66da455f 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_lint.beam b/bootstrap/lib/compiler/ebin/beam_ssa_lint.beam
index a1209d6fb5..fbbe42f4c7 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_lint.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_lint.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam b/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam
index ae54b0a0f6..3a8cdbc273 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam b/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam
index e4958e9b97..bd09f8bd51 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam b/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
index ed20850651..25455929a6 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_private_append.beam b/bootstrap/lib/compiler/ebin/beam_ssa_private_append.beam
new file mode 100644
index 0000000000..7b90707e9e
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_private_append.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam b/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam
index 8fdaf19b9b..23459e448d 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_share.beam b/bootstrap/lib/compiler/ebin/beam_ssa_share.beam
index 87861b519c..793d26d366 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_share.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_share.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_throw.beam b/bootstrap/lib/compiler/ebin/beam_ssa_throw.beam
index 65eba1cc55..121154129f 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_throw.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_throw.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_type.beam b/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
index 24f1357882..af9c0d86d9 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_trim.beam b/bootstrap/lib/compiler/ebin/beam_trim.beam
index 403d14af8a..6adca21ec0 100644
--- a/bootstrap/lib/compiler/ebin/beam_trim.beam
+++ b/bootstrap/lib/compiler/ebin/beam_trim.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_types.beam b/bootstrap/lib/compiler/ebin/beam_types.beam
index 50aa05ce7b..577c4afeb8 100644
--- a/bootstrap/lib/compiler/ebin/beam_types.beam
+++ b/bootstrap/lib/compiler/ebin/beam_types.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_utils.beam b/bootstrap/lib/compiler/ebin/beam_utils.beam
index 43f43219ca..76edd9e837 100644
--- a/bootstrap/lib/compiler/ebin/beam_utils.beam
+++ b/bootstrap/lib/compiler/ebin/beam_utils.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_validator.beam b/bootstrap/lib/compiler/ebin/beam_validator.beam
index 9dea06a177..53f0ccec2c 100644
--- a/bootstrap/lib/compiler/ebin/beam_validator.beam
+++ b/bootstrap/lib/compiler/ebin/beam_validator.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_z.beam b/bootstrap/lib/compiler/ebin/beam_z.beam
index 0ad3c95960..2350b5e3c0 100644
--- a/bootstrap/lib/compiler/ebin/beam_z.beam
+++ b/bootstrap/lib/compiler/ebin/beam_z.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/cerl.beam b/bootstrap/lib/compiler/ebin/cerl.beam
index c0e3654c1e..a493542a76 100644
--- a/bootstrap/lib/compiler/ebin/cerl.beam
+++ b/bootstrap/lib/compiler/ebin/cerl.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/cerl_clauses.beam b/bootstrap/lib/compiler/ebin/cerl_clauses.beam
index f1deadb265..652897a0f1 100644
--- a/bootstrap/lib/compiler/ebin/cerl_clauses.beam
+++ b/bootstrap/lib/compiler/ebin/cerl_clauses.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/cerl_inline.beam b/bootstrap/lib/compiler/ebin/cerl_inline.beam
index d3f8397584..b56d907bf8 100644
--- a/bootstrap/lib/compiler/ebin/cerl_inline.beam
+++ b/bootstrap/lib/compiler/ebin/cerl_inline.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/cerl_trees.beam b/bootstrap/lib/compiler/ebin/cerl_trees.beam
index 79c464e800..e25e3c7e81 100644
--- a/bootstrap/lib/compiler/ebin/cerl_trees.beam
+++ b/bootstrap/lib/compiler/ebin/cerl_trees.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/compile.beam b/bootstrap/lib/compiler/ebin/compile.beam
index 0057b1eea9..23fcc0a4f9 100644
--- a/bootstrap/lib/compiler/ebin/compile.beam
+++ b/bootstrap/lib/compiler/ebin/compile.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/compiler.app b/bootstrap/lib/compiler/ebin/compiler.app
index 0e749bb6cd..ea2723742d 100644
--- a/bootstrap/lib/compiler/ebin/compiler.app
+++ b/bootstrap/lib/compiler/ebin/compiler.app
@@ -19,7 +19,7 @@
{application, compiler,
[{description, "ERTS CXC 138 10"},
- {vsn, "8.2"},
+ {vsn, "8.2.2"},
{modules, [
beam_a,
beam_asm,
diff --git a/bootstrap/lib/compiler/ebin/compiler.appup b/bootstrap/lib/compiler/ebin/compiler.appup
index aa537986a5..2585c4a26d 100644
--- a/bootstrap/lib/compiler/ebin/compiler.appup
+++ b/bootstrap/lib/compiler/ebin/compiler.appup
@@ -16,7 +16,7 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
-{"8.1",
+{"8.2",
[{<<".*">>,[{restart_application, compiler}]}],
[{<<".*">>,[{restart_application, compiler}]}]
}.
diff --git a/bootstrap/lib/compiler/ebin/core_lib.beam b/bootstrap/lib/compiler/ebin/core_lib.beam
index e316333572..aff4a9d523 100644
--- a/bootstrap/lib/compiler/ebin/core_lib.beam
+++ b/bootstrap/lib/compiler/ebin/core_lib.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/core_lint.beam b/bootstrap/lib/compiler/ebin/core_lint.beam
index 11c1ad9c82..2237a901c8 100644
--- a/bootstrap/lib/compiler/ebin/core_lint.beam
+++ b/bootstrap/lib/compiler/ebin/core_lint.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/core_parse.beam b/bootstrap/lib/compiler/ebin/core_parse.beam
index 46ed9443dd..c46d70f2b2 100644
--- a/bootstrap/lib/compiler/ebin/core_parse.beam
+++ b/bootstrap/lib/compiler/ebin/core_parse.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/core_pp.beam b/bootstrap/lib/compiler/ebin/core_pp.beam
index f5b8955675..44236ea743 100644
--- a/bootstrap/lib/compiler/ebin/core_pp.beam
+++ b/bootstrap/lib/compiler/ebin/core_pp.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/core_scan.beam b/bootstrap/lib/compiler/ebin/core_scan.beam
index 5da4515384..f8e0ba5e34 100644
--- a/bootstrap/lib/compiler/ebin/core_scan.beam
+++ b/bootstrap/lib/compiler/ebin/core_scan.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/erl_bifs.beam b/bootstrap/lib/compiler/ebin/erl_bifs.beam
index c858cbbfcd..ab5659364f 100644
--- a/bootstrap/lib/compiler/ebin/erl_bifs.beam
+++ b/bootstrap/lib/compiler/ebin/erl_bifs.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/rec_env.beam b/bootstrap/lib/compiler/ebin/rec_env.beam
index 2147d61279..32f463d793 100644
--- a/bootstrap/lib/compiler/ebin/rec_env.beam
+++ b/bootstrap/lib/compiler/ebin/rec_env.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_alias.beam b/bootstrap/lib/compiler/ebin/sys_core_alias.beam
index c8c0c54442..999a21c5ac 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_alias.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_alias.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_bsm.beam b/bootstrap/lib/compiler/ebin/sys_core_bsm.beam
index d51f74ccb1..eda81d8e89 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_bsm.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_bsm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_fold.beam b/bootstrap/lib/compiler/ebin/sys_core_fold.beam
index bc2057d1c2..84f3c133ad 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_fold.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_fold.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_fold_lists.beam b/bootstrap/lib/compiler/ebin/sys_core_fold_lists.beam
index 714ff86f48..de0700d5ec 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_fold_lists.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_fold_lists.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_inline.beam b/bootstrap/lib/compiler/ebin/sys_core_inline.beam
index c142a3f22b..9311b2b080 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_inline.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_inline.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_prepare.beam b/bootstrap/lib/compiler/ebin/sys_core_prepare.beam
index 0e2debbfba..d297271752 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_prepare.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_prepare.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_messages.beam b/bootstrap/lib/compiler/ebin/sys_messages.beam
index 1ed2960660..e9a2d9480e 100644
--- a/bootstrap/lib/compiler/ebin/sys_messages.beam
+++ b/bootstrap/lib/compiler/ebin/sys_messages.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_pre_attributes.beam b/bootstrap/lib/compiler/ebin/sys_pre_attributes.beam
index ef7de3c3ae..d2889c2e7d 100644
--- a/bootstrap/lib/compiler/ebin/sys_pre_attributes.beam
+++ b/bootstrap/lib/compiler/ebin/sys_pre_attributes.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/v3_core.beam b/bootstrap/lib/compiler/ebin/v3_core.beam
index ff06839fba..8480cb89da 100644
--- a/bootstrap/lib/compiler/ebin/v3_core.beam
+++ b/bootstrap/lib/compiler/ebin/v3_core.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/v3_kernel.beam b/bootstrap/lib/compiler/ebin/v3_kernel.beam
index d8e5208989..55f97f8759 100644
--- a/bootstrap/lib/compiler/ebin/v3_kernel.beam
+++ b/bootstrap/lib/compiler/ebin/v3_kernel.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam b/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam
index cef39b84a4..c651e3f110 100644
--- a/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam
+++ b/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/application.beam b/bootstrap/lib/kernel/ebin/application.beam
index 47e4cb5a17..981ab53cb7 100644
--- a/bootstrap/lib/kernel/ebin/application.beam
+++ b/bootstrap/lib/kernel/ebin/application.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/application_controller.beam b/bootstrap/lib/kernel/ebin/application_controller.beam
index 5456b2be99..d98f199dbb 100644
--- a/bootstrap/lib/kernel/ebin/application_controller.beam
+++ b/bootstrap/lib/kernel/ebin/application_controller.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/application_master.beam b/bootstrap/lib/kernel/ebin/application_master.beam
index 2efaa70849..925cef0803 100644
--- a/bootstrap/lib/kernel/ebin/application_master.beam
+++ b/bootstrap/lib/kernel/ebin/application_master.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/application_starter.beam b/bootstrap/lib/kernel/ebin/application_starter.beam
index 30495729c3..6a039bd8ef 100644
--- a/bootstrap/lib/kernel/ebin/application_starter.beam
+++ b/bootstrap/lib/kernel/ebin/application_starter.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/auth.beam b/bootstrap/lib/kernel/ebin/auth.beam
index d20b71f629..4b92dd5f89 100644
--- a/bootstrap/lib/kernel/ebin/auth.beam
+++ b/bootstrap/lib/kernel/ebin/auth.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/code.beam b/bootstrap/lib/kernel/ebin/code.beam
index 3341d51436..33387493ae 100644
--- a/bootstrap/lib/kernel/ebin/code.beam
+++ b/bootstrap/lib/kernel/ebin/code.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/code_server.beam b/bootstrap/lib/kernel/ebin/code_server.beam
index 8ecba51c72..d064f03cfd 100644
--- a/bootstrap/lib/kernel/ebin/code_server.beam
+++ b/bootstrap/lib/kernel/ebin/code_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/disk_log.beam b/bootstrap/lib/kernel/ebin/disk_log.beam
index 55e6550c16..8be37cdf26 100644
--- a/bootstrap/lib/kernel/ebin/disk_log.beam
+++ b/bootstrap/lib/kernel/ebin/disk_log.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/disk_log_1.beam b/bootstrap/lib/kernel/ebin/disk_log_1.beam
index 16961789dd..0f1a6fc16d 100644
--- a/bootstrap/lib/kernel/ebin/disk_log_1.beam
+++ b/bootstrap/lib/kernel/ebin/disk_log_1.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/disk_log_server.beam b/bootstrap/lib/kernel/ebin/disk_log_server.beam
index cb13107464..78a5993751 100644
--- a/bootstrap/lib/kernel/ebin/disk_log_server.beam
+++ b/bootstrap/lib/kernel/ebin/disk_log_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/disk_log_sup.beam b/bootstrap/lib/kernel/ebin/disk_log_sup.beam
index 396c8e7b4c..b76cae08a7 100644
--- a/bootstrap/lib/kernel/ebin/disk_log_sup.beam
+++ b/bootstrap/lib/kernel/ebin/disk_log_sup.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/dist_ac.beam b/bootstrap/lib/kernel/ebin/dist_ac.beam
index c9c404ae10..a1c60611a9 100644
--- a/bootstrap/lib/kernel/ebin/dist_ac.beam
+++ b/bootstrap/lib/kernel/ebin/dist_ac.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/dist_util.beam b/bootstrap/lib/kernel/ebin/dist_util.beam
index 0098d533ea..625e480c1d 100644
--- a/bootstrap/lib/kernel/ebin/dist_util.beam
+++ b/bootstrap/lib/kernel/ebin/dist_util.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_boot_server.beam b/bootstrap/lib/kernel/ebin/erl_boot_server.beam
index 3d975d37a5..5311547118 100644
--- a/bootstrap/lib/kernel/ebin/erl_boot_server.beam
+++ b/bootstrap/lib/kernel/ebin/erl_boot_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_compile_server.beam b/bootstrap/lib/kernel/ebin/erl_compile_server.beam
index f00652d7f0..7b62a57d86 100644
--- a/bootstrap/lib/kernel/ebin/erl_compile_server.beam
+++ b/bootstrap/lib/kernel/ebin/erl_compile_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_ddll.beam b/bootstrap/lib/kernel/ebin/erl_ddll.beam
index 8910f69874..c005b736b4 100644
--- a/bootstrap/lib/kernel/ebin/erl_ddll.beam
+++ b/bootstrap/lib/kernel/ebin/erl_ddll.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_distribution.beam b/bootstrap/lib/kernel/ebin/erl_distribution.beam
index cadf5947ad..ccb9392d59 100644
--- a/bootstrap/lib/kernel/ebin/erl_distribution.beam
+++ b/bootstrap/lib/kernel/ebin/erl_distribution.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_epmd.beam b/bootstrap/lib/kernel/ebin/erl_epmd.beam
index 9672775cb8..7f6e5e6c87 100644
--- a/bootstrap/lib/kernel/ebin/erl_epmd.beam
+++ b/bootstrap/lib/kernel/ebin/erl_epmd.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_erts_errors.beam b/bootstrap/lib/kernel/ebin/erl_erts_errors.beam
index ae8bc9a019..cd95535e06 100644
--- a/bootstrap/lib/kernel/ebin/erl_erts_errors.beam
+++ b/bootstrap/lib/kernel/ebin/erl_erts_errors.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_kernel_errors.beam b/bootstrap/lib/kernel/ebin/erl_kernel_errors.beam
index a4afe1ee3d..3e58b02f2a 100644
--- a/bootstrap/lib/kernel/ebin/erl_kernel_errors.beam
+++ b/bootstrap/lib/kernel/ebin/erl_kernel_errors.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_reply.beam b/bootstrap/lib/kernel/ebin/erl_reply.beam
index 81ca50c86d..2ae87db895 100644
--- a/bootstrap/lib/kernel/ebin/erl_reply.beam
+++ b/bootstrap/lib/kernel/ebin/erl_reply.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_signal_handler.beam b/bootstrap/lib/kernel/ebin/erl_signal_handler.beam
index 6ed5a62960..580524d386 100644
--- a/bootstrap/lib/kernel/ebin/erl_signal_handler.beam
+++ b/bootstrap/lib/kernel/ebin/erl_signal_handler.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erpc.beam b/bootstrap/lib/kernel/ebin/erpc.beam
index c081fa7ff5..728b1586ba 100644
--- a/bootstrap/lib/kernel/ebin/erpc.beam
+++ b/bootstrap/lib/kernel/ebin/erpc.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/error_handler.beam b/bootstrap/lib/kernel/ebin/error_handler.beam
index 5e6481da1b..7c4bfbb86e 100644
--- a/bootstrap/lib/kernel/ebin/error_handler.beam
+++ b/bootstrap/lib/kernel/ebin/error_handler.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/error_logger.beam b/bootstrap/lib/kernel/ebin/error_logger.beam
index 2c4c9ae9e2..585416223a 100644
--- a/bootstrap/lib/kernel/ebin/error_logger.beam
+++ b/bootstrap/lib/kernel/ebin/error_logger.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erts_debug.beam b/bootstrap/lib/kernel/ebin/erts_debug.beam
index bba00e14cf..57d0012d55 100644
--- a/bootstrap/lib/kernel/ebin/erts_debug.beam
+++ b/bootstrap/lib/kernel/ebin/erts_debug.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/file.beam b/bootstrap/lib/kernel/ebin/file.beam
index d69b38b480..0fa2219389 100644
--- a/bootstrap/lib/kernel/ebin/file.beam
+++ b/bootstrap/lib/kernel/ebin/file.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/file_io_server.beam b/bootstrap/lib/kernel/ebin/file_io_server.beam
index cb8a5b7a37..ee3974e592 100644
--- a/bootstrap/lib/kernel/ebin/file_io_server.beam
+++ b/bootstrap/lib/kernel/ebin/file_io_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/file_server.beam b/bootstrap/lib/kernel/ebin/file_server.beam
index f4a6071ae3..ff9f619b4d 100644
--- a/bootstrap/lib/kernel/ebin/file_server.beam
+++ b/bootstrap/lib/kernel/ebin/file_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/gen_sctp.beam b/bootstrap/lib/kernel/ebin/gen_sctp.beam
index dfdbe10785..a2c440964b 100644
--- a/bootstrap/lib/kernel/ebin/gen_sctp.beam
+++ b/bootstrap/lib/kernel/ebin/gen_sctp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/gen_tcp.beam b/bootstrap/lib/kernel/ebin/gen_tcp.beam
index 5608de516d..0dcc48e96a 100644
--- a/bootstrap/lib/kernel/ebin/gen_tcp.beam
+++ b/bootstrap/lib/kernel/ebin/gen_tcp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam b/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam
index adcec4c7be..b8b179afab 100644
--- a/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam
+++ b/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/gen_udp.beam b/bootstrap/lib/kernel/ebin/gen_udp.beam
index 48781ba99e..f80ac01123 100644
--- a/bootstrap/lib/kernel/ebin/gen_udp.beam
+++ b/bootstrap/lib/kernel/ebin/gen_udp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/gen_udp_socket.beam b/bootstrap/lib/kernel/ebin/gen_udp_socket.beam
index a6a7d41527..21724c40d6 100644
--- a/bootstrap/lib/kernel/ebin/gen_udp_socket.beam
+++ b/bootstrap/lib/kernel/ebin/gen_udp_socket.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/global.beam b/bootstrap/lib/kernel/ebin/global.beam
index 400fe44990..2d716875db 100644
--- a/bootstrap/lib/kernel/ebin/global.beam
+++ b/bootstrap/lib/kernel/ebin/global.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/global_group.beam b/bootstrap/lib/kernel/ebin/global_group.beam
index bbd677f1e8..6433e5dcea 100644
--- a/bootstrap/lib/kernel/ebin/global_group.beam
+++ b/bootstrap/lib/kernel/ebin/global_group.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/global_search.beam b/bootstrap/lib/kernel/ebin/global_search.beam
index d5afafae8f..db26f907db 100644
--- a/bootstrap/lib/kernel/ebin/global_search.beam
+++ b/bootstrap/lib/kernel/ebin/global_search.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/group.beam b/bootstrap/lib/kernel/ebin/group.beam
index 27539c6a71..73c53767c4 100644
--- a/bootstrap/lib/kernel/ebin/group.beam
+++ b/bootstrap/lib/kernel/ebin/group.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/group_history.beam b/bootstrap/lib/kernel/ebin/group_history.beam
index 6bd7e769be..480c0ac288 100644
--- a/bootstrap/lib/kernel/ebin/group_history.beam
+++ b/bootstrap/lib/kernel/ebin/group_history.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/heart.beam b/bootstrap/lib/kernel/ebin/heart.beam
index c72c5b0282..6ee70ebb11 100644
--- a/bootstrap/lib/kernel/ebin/heart.beam
+++ b/bootstrap/lib/kernel/ebin/heart.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet.beam b/bootstrap/lib/kernel/ebin/inet.beam
index a1dfdf7386..0ec9cebcd2 100644
--- a/bootstrap/lib/kernel/ebin/inet.beam
+++ b/bootstrap/lib/kernel/ebin/inet.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet6_sctp.beam b/bootstrap/lib/kernel/ebin/inet6_sctp.beam
index 4efce5fba3..ef759ce7e1 100644
--- a/bootstrap/lib/kernel/ebin/inet6_sctp.beam
+++ b/bootstrap/lib/kernel/ebin/inet6_sctp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet6_tcp.beam b/bootstrap/lib/kernel/ebin/inet6_tcp.beam
index a96c63068e..8028e8a12b 100644
--- a/bootstrap/lib/kernel/ebin/inet6_tcp.beam
+++ b/bootstrap/lib/kernel/ebin/inet6_tcp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam b/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam
index a18f62b192..5f9df1a4d3 100644
--- a/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam
+++ b/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet6_udp.beam b/bootstrap/lib/kernel/ebin/inet6_udp.beam
index fd6e7f85c0..63b9b5f1ee 100644
--- a/bootstrap/lib/kernel/ebin/inet6_udp.beam
+++ b/bootstrap/lib/kernel/ebin/inet6_udp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_config.beam b/bootstrap/lib/kernel/ebin/inet_config.beam
index 0eaaa71602..3e306ebddb 100644
--- a/bootstrap/lib/kernel/ebin/inet_config.beam
+++ b/bootstrap/lib/kernel/ebin/inet_config.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_db.beam b/bootstrap/lib/kernel/ebin/inet_db.beam
index 4ce7dd6734..5682e597d2 100644
--- a/bootstrap/lib/kernel/ebin/inet_db.beam
+++ b/bootstrap/lib/kernel/ebin/inet_db.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_dns.beam b/bootstrap/lib/kernel/ebin/inet_dns.beam
index bddba494d9..4dc91fa511 100644
--- a/bootstrap/lib/kernel/ebin/inet_dns.beam
+++ b/bootstrap/lib/kernel/ebin/inet_dns.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_epmd_dist.beam b/bootstrap/lib/kernel/ebin/inet_epmd_dist.beam
new file mode 100644
index 0000000000..2389b4543c
--- /dev/null
+++ b/bootstrap/lib/kernel/ebin/inet_epmd_dist.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_epmd_socket.beam b/bootstrap/lib/kernel/ebin/inet_epmd_socket.beam
new file mode 100644
index 0000000000..e5f40ed4d4
--- /dev/null
+++ b/bootstrap/lib/kernel/ebin/inet_epmd_socket.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_gethost_native.beam b/bootstrap/lib/kernel/ebin/inet_gethost_native.beam
index 751f7c14f6..fb5256782b 100644
--- a/bootstrap/lib/kernel/ebin/inet_gethost_native.beam
+++ b/bootstrap/lib/kernel/ebin/inet_gethost_native.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_hosts.beam b/bootstrap/lib/kernel/ebin/inet_hosts.beam
index 5800de74dc..1ed316993f 100644
--- a/bootstrap/lib/kernel/ebin/inet_hosts.beam
+++ b/bootstrap/lib/kernel/ebin/inet_hosts.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_parse.beam b/bootstrap/lib/kernel/ebin/inet_parse.beam
index 397ec05343..2f87733a0d 100644
--- a/bootstrap/lib/kernel/ebin/inet_parse.beam
+++ b/bootstrap/lib/kernel/ebin/inet_parse.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_res.beam b/bootstrap/lib/kernel/ebin/inet_res.beam
index f034500a1a..6a537bbbe9 100644
--- a/bootstrap/lib/kernel/ebin/inet_res.beam
+++ b/bootstrap/lib/kernel/ebin/inet_res.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_sctp.beam b/bootstrap/lib/kernel/ebin/inet_sctp.beam
index 1f79fea238..2e7cd85a00 100644
--- a/bootstrap/lib/kernel/ebin/inet_sctp.beam
+++ b/bootstrap/lib/kernel/ebin/inet_sctp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_tcp.beam b/bootstrap/lib/kernel/ebin/inet_tcp.beam
index e46beb502c..681c66f40f 100644
--- a/bootstrap/lib/kernel/ebin/inet_tcp.beam
+++ b/bootstrap/lib/kernel/ebin/inet_tcp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam b/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam
index 8d9610fade..38e0f94687 100644
--- a/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam
+++ b/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_udp.beam b/bootstrap/lib/kernel/ebin/inet_udp.beam
index 0c04828317..51ff7c2b54 100644
--- a/bootstrap/lib/kernel/ebin/inet_udp.beam
+++ b/bootstrap/lib/kernel/ebin/inet_udp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/kernel.app b/bootstrap/lib/kernel/ebin/kernel.app
index 41e6a62a44..ce100930fb 100644
--- a/bootstrap/lib/kernel/ebin/kernel.app
+++ b/bootstrap/lib/kernel/ebin/kernel.app
@@ -22,7 +22,7 @@
{application, kernel,
[
{description, "ERTS CXC 138 10"},
- {vsn, "8.4.2"},
+ {vsn, "8.5.2"},
{modules, [application,
application_controller,
application_master,
@@ -83,9 +83,9 @@
os,
ram_file,
rpc,
- user,
user_drv,
user_sup,
+ prim_tty,
disk_log,
disk_log_1,
disk_log_server,
@@ -158,6 +158,7 @@
{shell_docs_ansi,auto}
]},
{mod, {kernel, []}},
- {runtime_dependencies, ["erts-@OTP-17934@", "stdlib-4.0", "sasl-3.0", "crypto-5.0"]}
+ {runtime_dependencies, ["erts-@OTP-18248@", "stdlib-@OTP-17932@",
+ "sasl-3.0", "crypto-5.0"]}
]
}.
diff --git a/bootstrap/lib/kernel/ebin/kernel.appup b/bootstrap/lib/kernel/ebin/kernel.appup
index cf88faffac..ccd0f7becd 100644
--- a/bootstrap/lib/kernel/ebin/kernel.appup
+++ b/bootstrap/lib/kernel/ebin/kernel.appup
@@ -1,7 +1,7 @@
%% -*- erlang -*-
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,23 +19,16 @@
%%
%% We allow upgrade from, and downgrade to all previous
%% versions from the following OTP releases:
-%% - OTP 22
%% - OTP 23
%% - OTP 24
+%% - OTP 25
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
%% stated previous versions.
%%
-{"8.3.1",
- [{<<"^6\\.4$">>,[restart_new_emulator]},
- {<<"^6\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^6\\.5$">>,[restart_new_emulator]},
- {<<"^6\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^6\\.5\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^7\\.0$">>,[restart_new_emulator]},
+{"8.4.1",
+ [{<<"^7\\.0$">>,[restart_new_emulator]},
{<<"^7\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^7\\.1$">>,[restart_new_emulator]},
{<<"^7\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
@@ -57,15 +50,12 @@
{<<"^8\\.2$">>,[restart_new_emulator]},
{<<"^8\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^8\\.3$">>,[restart_new_emulator]},
- {<<"^8\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
- [{<<"^6\\.4$">>,[restart_new_emulator]},
- {<<"^6\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^6\\.5$">>,[restart_new_emulator]},
- {<<"^6\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^6\\.5\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^7\\.0$">>,[restart_new_emulator]},
+ {<<"^8\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^8\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^8\\.3\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^8\\.4$">>,[restart_new_emulator]},
+ {<<"^8\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
+ [{<<"^7\\.0$">>,[restart_new_emulator]},
{<<"^7\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^7\\.1$">>,[restart_new_emulator]},
{<<"^7\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
@@ -87,4 +77,8 @@
{<<"^8\\.2$">>,[restart_new_emulator]},
{<<"^8\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^8\\.3$">>,[restart_new_emulator]},
- {<<"^8\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
+ {<<"^8\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^8\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^8\\.3\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^8\\.4$">>,[restart_new_emulator]},
+ {<<"^8\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
diff --git a/bootstrap/lib/kernel/ebin/kernel.beam b/bootstrap/lib/kernel/ebin/kernel.beam
index d7e2514c41..5438c88529 100644
--- a/bootstrap/lib/kernel/ebin/kernel.beam
+++ b/bootstrap/lib/kernel/ebin/kernel.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/kernel_config.beam b/bootstrap/lib/kernel/ebin/kernel_config.beam
index fc20a076c5..5724297c64 100644
--- a/bootstrap/lib/kernel/ebin/kernel_config.beam
+++ b/bootstrap/lib/kernel/ebin/kernel_config.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/kernel_refc.beam b/bootstrap/lib/kernel/ebin/kernel_refc.beam
index 85c941e15f..621ad37e8a 100644
--- a/bootstrap/lib/kernel/ebin/kernel_refc.beam
+++ b/bootstrap/lib/kernel/ebin/kernel_refc.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/local_tcp.beam b/bootstrap/lib/kernel/ebin/local_tcp.beam
index 57db9ec840..0c81fe3f94 100644
--- a/bootstrap/lib/kernel/ebin/local_tcp.beam
+++ b/bootstrap/lib/kernel/ebin/local_tcp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/local_udp.beam b/bootstrap/lib/kernel/ebin/local_udp.beam
index 67aadf1bc4..647413b3c9 100644
--- a/bootstrap/lib/kernel/ebin/local_udp.beam
+++ b/bootstrap/lib/kernel/ebin/local_udp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger.beam b/bootstrap/lib/kernel/ebin/logger.beam
index 72301c71e9..c08949167e 100644
--- a/bootstrap/lib/kernel/ebin/logger.beam
+++ b/bootstrap/lib/kernel/ebin/logger.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_backend.beam b/bootstrap/lib/kernel/ebin/logger_backend.beam
index 0c0a2720de..a68c16c3f0 100644
--- a/bootstrap/lib/kernel/ebin/logger_backend.beam
+++ b/bootstrap/lib/kernel/ebin/logger_backend.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_config.beam b/bootstrap/lib/kernel/ebin/logger_config.beam
index aeef730533..426d806acc 100644
--- a/bootstrap/lib/kernel/ebin/logger_config.beam
+++ b/bootstrap/lib/kernel/ebin/logger_config.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_disk_log_h.beam b/bootstrap/lib/kernel/ebin/logger_disk_log_h.beam
index 9b0de072dd..52064f9fb0 100644
--- a/bootstrap/lib/kernel/ebin/logger_disk_log_h.beam
+++ b/bootstrap/lib/kernel/ebin/logger_disk_log_h.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_filters.beam b/bootstrap/lib/kernel/ebin/logger_filters.beam
index d33846bd2a..5b49dc1eda 100644
--- a/bootstrap/lib/kernel/ebin/logger_filters.beam
+++ b/bootstrap/lib/kernel/ebin/logger_filters.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_formatter.beam b/bootstrap/lib/kernel/ebin/logger_formatter.beam
index 693d6d96ae..6b1fba6321 100644
--- a/bootstrap/lib/kernel/ebin/logger_formatter.beam
+++ b/bootstrap/lib/kernel/ebin/logger_formatter.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_h_common.beam b/bootstrap/lib/kernel/ebin/logger_h_common.beam
index 22b9bc16b4..d30f921a46 100644
--- a/bootstrap/lib/kernel/ebin/logger_h_common.beam
+++ b/bootstrap/lib/kernel/ebin/logger_h_common.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_handler_watcher.beam b/bootstrap/lib/kernel/ebin/logger_handler_watcher.beam
index 31c251072e..406cca06d2 100644
--- a/bootstrap/lib/kernel/ebin/logger_handler_watcher.beam
+++ b/bootstrap/lib/kernel/ebin/logger_handler_watcher.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_olp.beam b/bootstrap/lib/kernel/ebin/logger_olp.beam
index bf3dd071e4..f6b3d9add4 100644
--- a/bootstrap/lib/kernel/ebin/logger_olp.beam
+++ b/bootstrap/lib/kernel/ebin/logger_olp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_proxy.beam b/bootstrap/lib/kernel/ebin/logger_proxy.beam
index 886518885a..f7ca3b73a6 100644
--- a/bootstrap/lib/kernel/ebin/logger_proxy.beam
+++ b/bootstrap/lib/kernel/ebin/logger_proxy.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_server.beam b/bootstrap/lib/kernel/ebin/logger_server.beam
index 3b1f42dd85..239aa30b39 100644
--- a/bootstrap/lib/kernel/ebin/logger_server.beam
+++ b/bootstrap/lib/kernel/ebin/logger_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_simple_h.beam b/bootstrap/lib/kernel/ebin/logger_simple_h.beam
index cb5a4fb840..1bbd6b281a 100644
--- a/bootstrap/lib/kernel/ebin/logger_simple_h.beam
+++ b/bootstrap/lib/kernel/ebin/logger_simple_h.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_std_h.beam b/bootstrap/lib/kernel/ebin/logger_std_h.beam
index d2c7d0eec9..bcbc4379ba 100644
--- a/bootstrap/lib/kernel/ebin/logger_std_h.beam
+++ b/bootstrap/lib/kernel/ebin/logger_std_h.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_sup.beam b/bootstrap/lib/kernel/ebin/logger_sup.beam
index 45e372bd1e..1acbd3ab7a 100644
--- a/bootstrap/lib/kernel/ebin/logger_sup.beam
+++ b/bootstrap/lib/kernel/ebin/logger_sup.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/net.beam b/bootstrap/lib/kernel/ebin/net.beam
index 90bc08647f..59506ed92f 100644
--- a/bootstrap/lib/kernel/ebin/net.beam
+++ b/bootstrap/lib/kernel/ebin/net.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/net_adm.beam b/bootstrap/lib/kernel/ebin/net_adm.beam
index d75024222e..34f41ae964 100644
--- a/bootstrap/lib/kernel/ebin/net_adm.beam
+++ b/bootstrap/lib/kernel/ebin/net_adm.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/net_kernel.beam b/bootstrap/lib/kernel/ebin/net_kernel.beam
index 15a4a6cd13..d21cab3e2a 100644
--- a/bootstrap/lib/kernel/ebin/net_kernel.beam
+++ b/bootstrap/lib/kernel/ebin/net_kernel.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/os.beam b/bootstrap/lib/kernel/ebin/os.beam
index 133cdf6a1e..1fd8788387 100644
--- a/bootstrap/lib/kernel/ebin/os.beam
+++ b/bootstrap/lib/kernel/ebin/os.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/pg.beam b/bootstrap/lib/kernel/ebin/pg.beam
index 4f9a42e674..356e586177 100644
--- a/bootstrap/lib/kernel/ebin/pg.beam
+++ b/bootstrap/lib/kernel/ebin/pg.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/pg2.beam b/bootstrap/lib/kernel/ebin/pg2.beam
index fdfdb68586..a9533af680 100644
--- a/bootstrap/lib/kernel/ebin/pg2.beam
+++ b/bootstrap/lib/kernel/ebin/pg2.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/prim_tty.beam b/bootstrap/lib/kernel/ebin/prim_tty.beam
new file mode 100644
index 0000000000..24237adab2
--- /dev/null
+++ b/bootstrap/lib/kernel/ebin/prim_tty.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/ram_file.beam b/bootstrap/lib/kernel/ebin/ram_file.beam
index b228ce08ad..2ad1d08b9c 100644
--- a/bootstrap/lib/kernel/ebin/ram_file.beam
+++ b/bootstrap/lib/kernel/ebin/ram_file.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io.beam b/bootstrap/lib/kernel/ebin/raw_file_io.beam
index bdc921a5b7..81956af042 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_compressed.beam b/bootstrap/lib/kernel/ebin/raw_file_io_compressed.beam
index 7b524c62e6..4233f22faa 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_compressed.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_compressed.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam b/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam
index 69037c86d3..8d4bb23f4d 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_delayed.beam b/bootstrap/lib/kernel/ebin/raw_file_io_delayed.beam
index 51b0dd4e83..42bcfe10c4 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_delayed.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_delayed.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_inflate.beam b/bootstrap/lib/kernel/ebin/raw_file_io_inflate.beam
index 68cefab06f..8596ce8d17 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_inflate.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_inflate.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_list.beam b/bootstrap/lib/kernel/ebin/raw_file_io_list.beam
index 4f68c9f208..bde7b1313f 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_list.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_list.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/rpc.beam b/bootstrap/lib/kernel/ebin/rpc.beam
index 65985f0b84..0e8be38603 100644
--- a/bootstrap/lib/kernel/ebin/rpc.beam
+++ b/bootstrap/lib/kernel/ebin/rpc.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/seq_trace.beam b/bootstrap/lib/kernel/ebin/seq_trace.beam
index 3420f59316..cd3d5a3b13 100644
--- a/bootstrap/lib/kernel/ebin/seq_trace.beam
+++ b/bootstrap/lib/kernel/ebin/seq_trace.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/socket.beam b/bootstrap/lib/kernel/ebin/socket.beam
index 8d9c933cb8..0122be0f93 100644
--- a/bootstrap/lib/kernel/ebin/socket.beam
+++ b/bootstrap/lib/kernel/ebin/socket.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/standard_error.beam b/bootstrap/lib/kernel/ebin/standard_error.beam
index 6d8bb3ca9c..6a430b3c73 100644
--- a/bootstrap/lib/kernel/ebin/standard_error.beam
+++ b/bootstrap/lib/kernel/ebin/standard_error.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/user.beam b/bootstrap/lib/kernel/ebin/user.beam
deleted file mode 100644
index a75e13ad1f..0000000000
--- a/bootstrap/lib/kernel/ebin/user.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/user_drv.beam b/bootstrap/lib/kernel/ebin/user_drv.beam
index 94e7ecdf7e..6ddb0fd0ba 100644
--- a/bootstrap/lib/kernel/ebin/user_drv.beam
+++ b/bootstrap/lib/kernel/ebin/user_drv.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/user_sup.beam b/bootstrap/lib/kernel/ebin/user_sup.beam
index 40ee18f38d..1d4afba0dc 100644
--- a/bootstrap/lib/kernel/ebin/user_sup.beam
+++ b/bootstrap/lib/kernel/ebin/user_sup.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/wrap_log_reader.beam b/bootstrap/lib/kernel/ebin/wrap_log_reader.beam
index ce175874ec..5d1290bdc4 100644
--- a/bootstrap/lib/kernel/ebin/wrap_log_reader.beam
+++ b/bootstrap/lib/kernel/ebin/wrap_log_reader.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/include/dist.hrl b/bootstrap/lib/kernel/include/dist.hrl
index 16320b64e9..4eef2bb3fc 100644
--- a/bootstrap/lib/kernel/include/dist.hrl
+++ b/bootstrap/lib/kernel/include/dist.hrl
@@ -72,6 +72,14 @@
?DFLAG_BIG_CREATION bor
?DFLAG_HANDSHAKE_23)).
+%% New mandatory flags in OTP 26
+-define(MANDATORY_DFLAGS_26, (?DFLAG_V4_NC bor
+ ?DFLAG_UNLINK_ID)).
+
+%% All mandatory flags
+-define(DFLAGS_MANDATORY, (?MANDATORY_DFLAGS_25 bor
+ ?MANDATORY_DFLAGS_26)).
+
%% Also update dflag2str() in ../src/dist_util.erl
%% when adding flags...
diff --git a/bootstrap/lib/stdlib/ebin/array.beam b/bootstrap/lib/stdlib/ebin/array.beam
index 524748417f..b1eafbbac3 100644
--- a/bootstrap/lib/stdlib/ebin/array.beam
+++ b/bootstrap/lib/stdlib/ebin/array.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/base64.beam b/bootstrap/lib/stdlib/ebin/base64.beam
index 11518d9f17..2c1152c8cf 100644
--- a/bootstrap/lib/stdlib/ebin/base64.beam
+++ b/bootstrap/lib/stdlib/ebin/base64.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/beam_lib.beam b/bootstrap/lib/stdlib/ebin/beam_lib.beam
index 3a1295416a..54d339ca09 100644
--- a/bootstrap/lib/stdlib/ebin/beam_lib.beam
+++ b/bootstrap/lib/stdlib/ebin/beam_lib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/binary.beam b/bootstrap/lib/stdlib/ebin/binary.beam
index 5a66369e6f..7df0975cf2 100644
--- a/bootstrap/lib/stdlib/ebin/binary.beam
+++ b/bootstrap/lib/stdlib/ebin/binary.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/c.beam b/bootstrap/lib/stdlib/ebin/c.beam
index 7899bb9f36..b9bbddb64f 100644
--- a/bootstrap/lib/stdlib/ebin/c.beam
+++ b/bootstrap/lib/stdlib/ebin/c.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/calendar.beam b/bootstrap/lib/stdlib/ebin/calendar.beam
index cb9126dce9..6158eb9dc0 100644
--- a/bootstrap/lib/stdlib/ebin/calendar.beam
+++ b/bootstrap/lib/stdlib/ebin/calendar.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets.beam b/bootstrap/lib/stdlib/ebin/dets.beam
index d14c8463eb..336e92f167 100644
--- a/bootstrap/lib/stdlib/ebin/dets.beam
+++ b/bootstrap/lib/stdlib/ebin/dets.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets_server.beam b/bootstrap/lib/stdlib/ebin/dets_server.beam
index e971702773..964d15e653 100644
--- a/bootstrap/lib/stdlib/ebin/dets_server.beam
+++ b/bootstrap/lib/stdlib/ebin/dets_server.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets_sup.beam b/bootstrap/lib/stdlib/ebin/dets_sup.beam
index f2e6e08cc4..660f51eba4 100644
--- a/bootstrap/lib/stdlib/ebin/dets_sup.beam
+++ b/bootstrap/lib/stdlib/ebin/dets_sup.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets_utils.beam b/bootstrap/lib/stdlib/ebin/dets_utils.beam
index 952ea82f16..ffdffb9fc5 100644
--- a/bootstrap/lib/stdlib/ebin/dets_utils.beam
+++ b/bootstrap/lib/stdlib/ebin/dets_utils.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets_v9.beam b/bootstrap/lib/stdlib/ebin/dets_v9.beam
index a98b4fbeac..5658793668 100644
--- a/bootstrap/lib/stdlib/ebin/dets_v9.beam
+++ b/bootstrap/lib/stdlib/ebin/dets_v9.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dict.beam b/bootstrap/lib/stdlib/ebin/dict.beam
index e1a8cf4730..c74b71395c 100644
--- a/bootstrap/lib/stdlib/ebin/dict.beam
+++ b/bootstrap/lib/stdlib/ebin/dict.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/digraph.beam b/bootstrap/lib/stdlib/ebin/digraph.beam
index b74ee31cb7..8c1c9b2291 100644
--- a/bootstrap/lib/stdlib/ebin/digraph.beam
+++ b/bootstrap/lib/stdlib/ebin/digraph.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/digraph_utils.beam b/bootstrap/lib/stdlib/ebin/digraph_utils.beam
index 8e85e8c45b..549a93d965 100644
--- a/bootstrap/lib/stdlib/ebin/digraph_utils.beam
+++ b/bootstrap/lib/stdlib/ebin/digraph_utils.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/edlin.beam b/bootstrap/lib/stdlib/ebin/edlin.beam
index e043c8e023..325ecf45ef 100644
--- a/bootstrap/lib/stdlib/ebin/edlin.beam
+++ b/bootstrap/lib/stdlib/ebin/edlin.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/edlin_context.beam b/bootstrap/lib/stdlib/ebin/edlin_context.beam
new file mode 100644
index 0000000000..f229f29fd2
--- /dev/null
+++ b/bootstrap/lib/stdlib/ebin/edlin_context.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/edlin_expand.beam b/bootstrap/lib/stdlib/ebin/edlin_expand.beam
index c2c1b4d05d..bf7d093eed 100644
--- a/bootstrap/lib/stdlib/ebin/edlin_expand.beam
+++ b/bootstrap/lib/stdlib/ebin/edlin_expand.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/edlin_type_suggestion.beam b/bootstrap/lib/stdlib/ebin/edlin_type_suggestion.beam
new file mode 100644
index 0000000000..dd4a8101a0
--- /dev/null
+++ b/bootstrap/lib/stdlib/ebin/edlin_type_suggestion.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/epp.beam b/bootstrap/lib/stdlib/ebin/epp.beam
index 3a1b9b2c51..3f73e6073d 100644
--- a/bootstrap/lib/stdlib/ebin/epp.beam
+++ b/bootstrap/lib/stdlib/ebin/epp.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_abstract_code.beam b/bootstrap/lib/stdlib/ebin/erl_abstract_code.beam
index 1a93db5293..a1741be5df 100644
--- a/bootstrap/lib/stdlib/ebin/erl_abstract_code.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_abstract_code.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_anno.beam b/bootstrap/lib/stdlib/ebin/erl_anno.beam
index c454c5a045..fbd6517584 100644
--- a/bootstrap/lib/stdlib/ebin/erl_anno.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_anno.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_bits.beam b/bootstrap/lib/stdlib/ebin/erl_bits.beam
index 61783aa8ac..ab7cfbd584 100644
--- a/bootstrap/lib/stdlib/ebin/erl_bits.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_bits.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_compile.beam b/bootstrap/lib/stdlib/ebin/erl_compile.beam
index d099a6f6dd..aaf5c0b9d7 100644
--- a/bootstrap/lib/stdlib/ebin/erl_compile.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_compile.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_error.beam b/bootstrap/lib/stdlib/ebin/erl_error.beam
index 635538c302..669c81ad34 100644
--- a/bootstrap/lib/stdlib/ebin/erl_error.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_error.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_eval.beam b/bootstrap/lib/stdlib/ebin/erl_eval.beam
index 67b6e845c4..6fb1f52f0f 100644
--- a/bootstrap/lib/stdlib/ebin/erl_eval.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_eval.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_expand_records.beam b/bootstrap/lib/stdlib/ebin/erl_expand_records.beam
index 9ad674ee8d..7dc2066222 100644
--- a/bootstrap/lib/stdlib/ebin/erl_expand_records.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_expand_records.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_features.beam b/bootstrap/lib/stdlib/ebin/erl_features.beam
index 514b284b0c..bfe48d8ea5 100644
--- a/bootstrap/lib/stdlib/ebin/erl_features.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_features.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_internal.beam b/bootstrap/lib/stdlib/ebin/erl_internal.beam
index 3378f7da67..0b6ac1a6d6 100644
--- a/bootstrap/lib/stdlib/ebin/erl_internal.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_internal.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_lint.beam b/bootstrap/lib/stdlib/ebin/erl_lint.beam
index 04fc9beb5f..11dc39d2f9 100644
--- a/bootstrap/lib/stdlib/ebin/erl_lint.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_lint.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_parse.beam b/bootstrap/lib/stdlib/ebin/erl_parse.beam
index 87f83e0785..d726d48afd 100644
--- a/bootstrap/lib/stdlib/ebin/erl_parse.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_parse.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_posix_msg.beam b/bootstrap/lib/stdlib/ebin/erl_posix_msg.beam
index b6a8d36cc4..9ad008f367 100644
--- a/bootstrap/lib/stdlib/ebin/erl_posix_msg.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_posix_msg.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_pp.beam b/bootstrap/lib/stdlib/ebin/erl_pp.beam
index c92824793e..51867008bc 100644
--- a/bootstrap/lib/stdlib/ebin/erl_pp.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_pp.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_scan.beam b/bootstrap/lib/stdlib/ebin/erl_scan.beam
index 7539589cf2..b2c13060ba 100644
--- a/bootstrap/lib/stdlib/ebin/erl_scan.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_scan.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_stdlib_errors.beam b/bootstrap/lib/stdlib/ebin/erl_stdlib_errors.beam
index ebc80456ca..dbec403bb2 100644
--- a/bootstrap/lib/stdlib/ebin/erl_stdlib_errors.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_stdlib_errors.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_tar.beam b/bootstrap/lib/stdlib/ebin/erl_tar.beam
index cdc886065a..c3f25effd1 100644
--- a/bootstrap/lib/stdlib/ebin/erl_tar.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_tar.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam b/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam
index 0043d2ef08..49a74b38f8 100644
--- a/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam
+++ b/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam b/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam
index 21bd44dbad..dc153e80ba 100644
--- a/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam
+++ b/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/escript.beam b/bootstrap/lib/stdlib/ebin/escript.beam
index 04ec6a6950..09d8ade15d 100644
--- a/bootstrap/lib/stdlib/ebin/escript.beam
+++ b/bootstrap/lib/stdlib/ebin/escript.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/ets.beam b/bootstrap/lib/stdlib/ebin/ets.beam
index c48177baad..bf443282ed 100644
--- a/bootstrap/lib/stdlib/ebin/ets.beam
+++ b/bootstrap/lib/stdlib/ebin/ets.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/eval_bits.beam b/bootstrap/lib/stdlib/ebin/eval_bits.beam
index a91463ff94..ed90371d27 100644
--- a/bootstrap/lib/stdlib/ebin/eval_bits.beam
+++ b/bootstrap/lib/stdlib/ebin/eval_bits.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/file_sorter.beam b/bootstrap/lib/stdlib/ebin/file_sorter.beam
index fd5139a87f..2153273c86 100644
--- a/bootstrap/lib/stdlib/ebin/file_sorter.beam
+++ b/bootstrap/lib/stdlib/ebin/file_sorter.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/filelib.beam b/bootstrap/lib/stdlib/ebin/filelib.beam
index 22007daa9c..7b1cb086fe 100644
--- a/bootstrap/lib/stdlib/ebin/filelib.beam
+++ b/bootstrap/lib/stdlib/ebin/filelib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/filename.beam b/bootstrap/lib/stdlib/ebin/filename.beam
index 6720bb7dd1..5a84885258 100644
--- a/bootstrap/lib/stdlib/ebin/filename.beam
+++ b/bootstrap/lib/stdlib/ebin/filename.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gb_sets.beam b/bootstrap/lib/stdlib/ebin/gb_sets.beam
index 52d96aa43f..8f0b0baf37 100644
--- a/bootstrap/lib/stdlib/ebin/gb_sets.beam
+++ b/bootstrap/lib/stdlib/ebin/gb_sets.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gb_trees.beam b/bootstrap/lib/stdlib/ebin/gb_trees.beam
index 5b37aa2d76..8aa75b7d0b 100644
--- a/bootstrap/lib/stdlib/ebin/gb_trees.beam
+++ b/bootstrap/lib/stdlib/ebin/gb_trees.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen.beam b/bootstrap/lib/stdlib/ebin/gen.beam
index 6966526786..2b1db2c1f7 100644
--- a/bootstrap/lib/stdlib/ebin/gen.beam
+++ b/bootstrap/lib/stdlib/ebin/gen.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen_event.beam b/bootstrap/lib/stdlib/ebin/gen_event.beam
index 96713a520c..dc5a14ce28 100644
--- a/bootstrap/lib/stdlib/ebin/gen_event.beam
+++ b/bootstrap/lib/stdlib/ebin/gen_event.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen_fsm.beam b/bootstrap/lib/stdlib/ebin/gen_fsm.beam
index 5b4d9a3ebb..cdabaa74f0 100644
--- a/bootstrap/lib/stdlib/ebin/gen_fsm.beam
+++ b/bootstrap/lib/stdlib/ebin/gen_fsm.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen_server.beam b/bootstrap/lib/stdlib/ebin/gen_server.beam
index 7236b5869c..d092b48466 100644
--- a/bootstrap/lib/stdlib/ebin/gen_server.beam
+++ b/bootstrap/lib/stdlib/ebin/gen_server.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen_statem.beam b/bootstrap/lib/stdlib/ebin/gen_statem.beam
index fc6e8519fe..62aafddf73 100644
--- a/bootstrap/lib/stdlib/ebin/gen_statem.beam
+++ b/bootstrap/lib/stdlib/ebin/gen_statem.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io.beam b/bootstrap/lib/stdlib/ebin/io.beam
index 0f5568c606..497d7c6446 100644
--- a/bootstrap/lib/stdlib/ebin/io.beam
+++ b/bootstrap/lib/stdlib/ebin/io.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib.beam b/bootstrap/lib/stdlib/ebin/io_lib.beam
index db1f8aa987..8fac910ca5 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib_format.beam b/bootstrap/lib/stdlib/ebin/io_lib_format.beam
index 4f12310de2..fed1429264 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib_format.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib_format.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib_fread.beam b/bootstrap/lib/stdlib/ebin/io_lib_fread.beam
index f25a79f330..9c2357caf0 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib_fread.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib_fread.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam b/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam
index a8b6b41207..89a22cb1d7 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/lists.beam b/bootstrap/lib/stdlib/ebin/lists.beam
index d321ccd6eb..067134d28f 100644
--- a/bootstrap/lib/stdlib/ebin/lists.beam
+++ b/bootstrap/lib/stdlib/ebin/lists.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/log_mf_h.beam b/bootstrap/lib/stdlib/ebin/log_mf_h.beam
index 5a568cf644..57badfc8f0 100644
--- a/bootstrap/lib/stdlib/ebin/log_mf_h.beam
+++ b/bootstrap/lib/stdlib/ebin/log_mf_h.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/maps.beam b/bootstrap/lib/stdlib/ebin/maps.beam
index 0504de1ac9..3dbfa76f98 100644
--- a/bootstrap/lib/stdlib/ebin/maps.beam
+++ b/bootstrap/lib/stdlib/ebin/maps.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/math.beam b/bootstrap/lib/stdlib/ebin/math.beam
index e7c040cc4a..36d9d4b068 100644
--- a/bootstrap/lib/stdlib/ebin/math.beam
+++ b/bootstrap/lib/stdlib/ebin/math.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/ms_transform.beam b/bootstrap/lib/stdlib/ebin/ms_transform.beam
index 7cd3c22c07..8e69c195a1 100644
--- a/bootstrap/lib/stdlib/ebin/ms_transform.beam
+++ b/bootstrap/lib/stdlib/ebin/ms_transform.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/orddict.beam b/bootstrap/lib/stdlib/ebin/orddict.beam
index 9c642b7870..eae3adc346 100644
--- a/bootstrap/lib/stdlib/ebin/orddict.beam
+++ b/bootstrap/lib/stdlib/ebin/orddict.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/ordsets.beam b/bootstrap/lib/stdlib/ebin/ordsets.beam
index efa72b347e..c9eba0a35a 100644
--- a/bootstrap/lib/stdlib/ebin/ordsets.beam
+++ b/bootstrap/lib/stdlib/ebin/ordsets.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/otp_internal.beam b/bootstrap/lib/stdlib/ebin/otp_internal.beam
index f2a9b45765..4009644828 100644
--- a/bootstrap/lib/stdlib/ebin/otp_internal.beam
+++ b/bootstrap/lib/stdlib/ebin/otp_internal.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/peer.beam b/bootstrap/lib/stdlib/ebin/peer.beam
index 04b0178880..c48ae89fa6 100644
--- a/bootstrap/lib/stdlib/ebin/peer.beam
+++ b/bootstrap/lib/stdlib/ebin/peer.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/pool.beam b/bootstrap/lib/stdlib/ebin/pool.beam
index 566c6e90eb..97d7cc080c 100644
--- a/bootstrap/lib/stdlib/ebin/pool.beam
+++ b/bootstrap/lib/stdlib/ebin/pool.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/proc_lib.beam b/bootstrap/lib/stdlib/ebin/proc_lib.beam
index 1a1f08b08f..3aa850b241 100644
--- a/bootstrap/lib/stdlib/ebin/proc_lib.beam
+++ b/bootstrap/lib/stdlib/ebin/proc_lib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/proplists.beam b/bootstrap/lib/stdlib/ebin/proplists.beam
index aa386d7655..de05dc9f97 100644
--- a/bootstrap/lib/stdlib/ebin/proplists.beam
+++ b/bootstrap/lib/stdlib/ebin/proplists.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/qlc.beam b/bootstrap/lib/stdlib/ebin/qlc.beam
index 6663fb5c23..48713b270b 100644
--- a/bootstrap/lib/stdlib/ebin/qlc.beam
+++ b/bootstrap/lib/stdlib/ebin/qlc.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/qlc_pt.beam b/bootstrap/lib/stdlib/ebin/qlc_pt.beam
index 40cac7a2f9..39e3bc1162 100644
--- a/bootstrap/lib/stdlib/ebin/qlc_pt.beam
+++ b/bootstrap/lib/stdlib/ebin/qlc_pt.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/queue.beam b/bootstrap/lib/stdlib/ebin/queue.beam
index 0ffc140ea9..51035a965d 100644
--- a/bootstrap/lib/stdlib/ebin/queue.beam
+++ b/bootstrap/lib/stdlib/ebin/queue.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/rand.beam b/bootstrap/lib/stdlib/ebin/rand.beam
index a7938b8762..bbb3a4eb5c 100644
--- a/bootstrap/lib/stdlib/ebin/rand.beam
+++ b/bootstrap/lib/stdlib/ebin/rand.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/random.beam b/bootstrap/lib/stdlib/ebin/random.beam
index e1ba2deacb..914b337d28 100644
--- a/bootstrap/lib/stdlib/ebin/random.beam
+++ b/bootstrap/lib/stdlib/ebin/random.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/re.beam b/bootstrap/lib/stdlib/ebin/re.beam
index 3e577c8768..3358a74b0c 100644
--- a/bootstrap/lib/stdlib/ebin/re.beam
+++ b/bootstrap/lib/stdlib/ebin/re.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/sets.beam b/bootstrap/lib/stdlib/ebin/sets.beam
index a684a570bf..8f324170b2 100644
--- a/bootstrap/lib/stdlib/ebin/sets.beam
+++ b/bootstrap/lib/stdlib/ebin/sets.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/shell.beam b/bootstrap/lib/stdlib/ebin/shell.beam
index a6f9c3e3a4..5975ad2f7c 100644
--- a/bootstrap/lib/stdlib/ebin/shell.beam
+++ b/bootstrap/lib/stdlib/ebin/shell.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/shell_default.beam b/bootstrap/lib/stdlib/ebin/shell_default.beam
index 167512d9f1..8ebf595668 100644
--- a/bootstrap/lib/stdlib/ebin/shell_default.beam
+++ b/bootstrap/lib/stdlib/ebin/shell_default.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/shell_docs.beam b/bootstrap/lib/stdlib/ebin/shell_docs.beam
index 8fee06ffcd..22dfcb2e60 100644
--- a/bootstrap/lib/stdlib/ebin/shell_docs.beam
+++ b/bootstrap/lib/stdlib/ebin/shell_docs.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/slave.beam b/bootstrap/lib/stdlib/ebin/slave.beam
index 30f8d8d154..d497fcf133 100644
--- a/bootstrap/lib/stdlib/ebin/slave.beam
+++ b/bootstrap/lib/stdlib/ebin/slave.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/sofs.beam b/bootstrap/lib/stdlib/ebin/sofs.beam
index e99d617c91..d4c4383f76 100644
--- a/bootstrap/lib/stdlib/ebin/sofs.beam
+++ b/bootstrap/lib/stdlib/ebin/sofs.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/stdlib.app b/bootstrap/lib/stdlib/ebin/stdlib.app
index ce39d8fbdb..36d2e9b552 100644
--- a/bootstrap/lib/stdlib/ebin/stdlib.app
+++ b/bootstrap/lib/stdlib/ebin/stdlib.app
@@ -20,7 +20,7 @@
%%
{application, stdlib,
[{description, "ERTS CXC 138 10"},
- {vsn, "4.0.1"},
+ {vsn, "4.2"},
{modules, [array,
base64,
beam_lib,
@@ -36,7 +36,9 @@
digraph,
digraph_utils,
edlin,
+ edlin_context,
edlin_expand,
+ edlin_type_suggestion,
epp,
eval_bits,
erl_abstract_code,
@@ -112,6 +114,6 @@
dets]},
{applications, [kernel]},
{env, []},
- {runtime_dependencies, ["sasl-3.0","kernel-8.4","erts-@OTP-17934@","crypto-4.5",
+ {runtime_dependencies, ["sasl-3.0","kernel-@OTP-17932@","erts-13.1","crypto-4.5",
"compiler-5.0"]}
]}.
diff --git a/bootstrap/lib/stdlib/ebin/stdlib.appup b/bootstrap/lib/stdlib/ebin/stdlib.appup
index 5c7e569773..adebe16860 100644
--- a/bootstrap/lib/stdlib/ebin/stdlib.appup
+++ b/bootstrap/lib/stdlib/ebin/stdlib.appup
@@ -19,25 +19,16 @@
%%
%% We allow upgrade from, and downgrade to all previous
%% versions from the following OTP releases:
-%% - OTP 22
%% - OTP 23
%% - OTP 24
+%% - OTP 25
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
%% stated previous versions.
%%
-{"3.17.1",
- [{<<"^3\\.10$">>,[restart_new_emulator]},
- {<<"^3\\.10\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.11$">>,[restart_new_emulator]},
- {<<"^3\\.11\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.11\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.11\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.12$">>,[restart_new_emulator]},
- {<<"^3\\.12\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.12\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.13$">>,[restart_new_emulator]},
+{"4.0",
+ [{<<"^3\\.13$">>,[restart_new_emulator]},
{<<"^3\\.13\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.13\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.13\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -54,20 +45,9 @@
{<<"^3\\.16\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.17$">>,[restart_new_emulator]},
{<<"^3\\.17\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.9$">>,[restart_new_emulator]},
- {<<"^3\\.9\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.9\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.9\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
- [{<<"^3\\.10$">>,[restart_new_emulator]},
- {<<"^3\\.10\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.11$">>,[restart_new_emulator]},
- {<<"^3\\.11\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.11\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.11\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.12$">>,[restart_new_emulator]},
- {<<"^3\\.12\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.12\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.13$">>,[restart_new_emulator]},
+ {<<"^3\\.17\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.17\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ [{<<"^3\\.13$">>,[restart_new_emulator]},
{<<"^3\\.13\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.13\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.13\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -84,7 +64,5 @@
{<<"^3\\.16\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.17$">>,[restart_new_emulator]},
{<<"^3\\.17\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.9$">>,[restart_new_emulator]},
- {<<"^3\\.9\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.9\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.9\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
+ {<<"^3\\.17\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.17\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/bootstrap/lib/stdlib/ebin/string.beam b/bootstrap/lib/stdlib/ebin/string.beam
index 330c4dd4ad..29ab628073 100644
--- a/bootstrap/lib/stdlib/ebin/string.beam
+++ b/bootstrap/lib/stdlib/ebin/string.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/supervisor.beam b/bootstrap/lib/stdlib/ebin/supervisor.beam
index 8a8f890e79..f35c6998f0 100644
--- a/bootstrap/lib/stdlib/ebin/supervisor.beam
+++ b/bootstrap/lib/stdlib/ebin/supervisor.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam b/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam
index 007655f5cd..62daac1f34 100644
--- a/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam
+++ b/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/sys.beam b/bootstrap/lib/stdlib/ebin/sys.beam
index 509e06b790..91977cab88 100644
--- a/bootstrap/lib/stdlib/ebin/sys.beam
+++ b/bootstrap/lib/stdlib/ebin/sys.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/timer.beam b/bootstrap/lib/stdlib/ebin/timer.beam
index 0191b0d795..39dd41690a 100644
--- a/bootstrap/lib/stdlib/ebin/timer.beam
+++ b/bootstrap/lib/stdlib/ebin/timer.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/unicode.beam b/bootstrap/lib/stdlib/ebin/unicode.beam
index 2c3e157da6..461df9d336 100644
--- a/bootstrap/lib/stdlib/ebin/unicode.beam
+++ b/bootstrap/lib/stdlib/ebin/unicode.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/unicode_util.beam b/bootstrap/lib/stdlib/ebin/unicode_util.beam
index 476c314d42..953a8f00fb 100644
--- a/bootstrap/lib/stdlib/ebin/unicode_util.beam
+++ b/bootstrap/lib/stdlib/ebin/unicode_util.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/uri_string.beam b/bootstrap/lib/stdlib/ebin/uri_string.beam
index e8db2b5215..7222c93455 100644
--- a/bootstrap/lib/stdlib/ebin/uri_string.beam
+++ b/bootstrap/lib/stdlib/ebin/uri_string.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/win32reg.beam b/bootstrap/lib/stdlib/ebin/win32reg.beam
index 93c861c238..fa17c2ce1b 100644
--- a/bootstrap/lib/stdlib/ebin/win32reg.beam
+++ b/bootstrap/lib/stdlib/ebin/win32reg.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/zip.beam b/bootstrap/lib/stdlib/ebin/zip.beam
index 01fbc51291..d66c584415 100644
--- a/bootstrap/lib/stdlib/ebin/zip.beam
+++ b/bootstrap/lib/stdlib/ebin/zip.beam
Binary files differ
diff --git a/erts/.gitignore b/erts/.gitignore
index 954e922492..e26eca8917 100644
--- a/erts/.gitignore
+++ b/erts/.gitignore
@@ -18,6 +18,7 @@
/emulator/test/Emakefile
/emulator/test/*.beam
/emulator/test/*_no_opt_SUITE.erl
+/emulator/test/*_r25_SUITE.erl
/emulator/pcre/pcre_exec_loop_break_cases.inc
/emulator/beam/erl_db_insert_list.ycf.h
diff --git a/erts/Makefile b/erts/Makefile
index cbcdaa9163..99f4ee3a38 100644
--- a/erts/Makefile
+++ b/erts/Makefile
@@ -86,10 +86,8 @@ local_setup:
cp $(ERL_TOP)/bin/$(TARGET)/erlc.exe $(ERL_TOP)/bin/erlc.exe; \
cp $(ERL_TOP)/bin/$(TARGET)/erl.exe $(ERL_TOP)/bin/erl.exe; \
cp $(ERL_TOP)/bin/$(TARGET)/erl_call.exe $(ERL_TOP)/bin/erl_call.exe; \
- cp $(ERL_TOP)/bin/$(TARGET)/werl.exe $(ERL_TOP)/bin/werl.exe; \
cp $(ERL_TOP)/bin/$(TARGET)/escript.exe $(ERL_TOP)/bin/escript.exe; \
- chmod 755 $(ERL_TOP)/bin/erl.exe $(ERL_TOP)/bin/erlc.exe \
- $(ERL_TOP)/bin/werl.exe; \
+ chmod 755 $(ERL_TOP)/bin/erl.exe $(ERL_TOP)/bin/erlc.exe; \
make_local_ini.sh $(ERL_TOP); \
cp $(ERL_TOP)/bin/erl.ini $(ERL_TOP)/bin/$(TARGET)/erl.ini; \
else \
diff --git a/erts/config.h.in b/erts/config.h.in
index 0ec4a40761..d7fecf8631 100644
--- a/erts/config.h.in
+++ b/erts/config.h.in
@@ -89,6 +89,9 @@
/* Socket address dl length */
#undef ESOCK_SDL_LEN
+/* Use extended error info */
+#undef ESOCK_USE_EXTENDED_ERROR_INFO
+
/* Interface hwaddr supported */
#undef ESOCK_USE_HWADDR
@@ -824,6 +827,9 @@
/* Define to 1 if you have the `memmove' function. */
#undef HAVE_MEMMOVE
+/* Define to 1 if you have the `memrchr' function. */
+#undef HAVE_MEMRCHR
+
/* Define if the pthread.h header file is in pthread/mit directory. */
#undef HAVE_MIT_PTHREAD_H
diff --git a/erts/configure b/erts/configure
index fbdb6baba8..a09c84ff09 100755
--- a/erts/configure
+++ b/erts/configure
@@ -860,6 +860,7 @@ with_threadnames
enable_builtin_zlib
enable_esock
enable_esock_use_rcvsndtimeo
+enable_esock_extended_error_info
with_esock_counter_size
enable_esock_socket_registry
with_clock_resolution
@@ -868,7 +869,6 @@ with_clock_gettime_monotonic_id
enable_prefer_elapsed_monotonic_time_during_suspend
enable_gettimeofday_as_os_system_time
with_javac
-enable_sanitizers
enable_deterministic_build
'
ac_precious_vars='build_alias
@@ -1597,6 +1597,10 @@ Optional Features:
--disable-esock-rcvsndtimeo
disable use of the option(s) rcvtimeo and sndtimeo
(default)
+ --enable-esock-extended-error-info
+ enable use of extended error info
+ --disable-esock-extended-error-info
+ disable use of extended error info (default)
--enable-esock-socket-registry
enable use of the socket registry by default
(default)
@@ -1610,8 +1614,6 @@ Optional Features:
elapsed time during suspend
--enable-gettimeofday-as-os-system-time
Force usage of gettimeofday() for OS system time
- --enable-sanitizers[=comma-separated list of sanitizers]
- Default=address,undefined
--enable-deterministic-build
enable build determinism, stripping absolute paths
from build output
@@ -10358,31 +10360,35 @@ printf "%s\n" "#define ETHR_WIN32_THREADS 1" >>confdefs.h
else
ilckd="_InterlockedDecrement"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "1" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedDecrement+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedDecrement=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10390,43 +10396,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedDecrement=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedDecrement" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedDecrement" >&6; }
+ if [ "${ethr_cv_have__InterlockedDecrement}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDDECREMENT 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedDecrement_rel"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "1" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedDecrement_rel+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedDecrement_rel=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10434,43 +10449,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedDecrement_rel=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedDecrement_rel" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedDecrement_rel" >&6; }
+ if [ "${ethr_cv_have__InterlockedDecrement_rel}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDDECREMENT_REL 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedIncrement"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "1" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedIncrement+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedIncrement=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10478,43 +10502,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedIncrement=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedIncrement" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedIncrement" >&6; }
+ if [ "${ethr_cv_have__InterlockedIncrement}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDINCREMENT 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedIncrement_acq"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "1" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedIncrement_acq+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedIncrement_acq=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10522,43 +10555,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedIncrement_acq=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedIncrement_acq" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedIncrement_acq" >&6; }
+ if [ "${ethr_cv_have__InterlockedIncrement_acq}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDINCREMENT_ACQ 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedExchangeAdd"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedExchangeAdd+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedExchangeAdd=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10566,43 +10608,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedExchangeAdd=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedExchangeAdd" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedExchangeAdd" >&6; }
+ if [ "${ethr_cv_have__InterlockedExchangeAdd}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDEXCHANGEADD 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedExchangeAdd_acq"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedExchangeAdd_acq+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedExchangeAdd_acq=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10610,43 +10661,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedExchangeAdd_acq=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedExchangeAdd_acq" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedExchangeAdd_acq" >&6; }
+ if [ "${ethr_cv_have__InterlockedExchangeAdd_acq}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDEXCHANGEADD_ACQ 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedAnd"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedAnd+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedAnd=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10654,43 +10714,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedAnd=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedAnd" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedAnd" >&6; }
+ if [ "${ethr_cv_have__InterlockedAnd}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDAND 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedOr"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedOr+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedOr=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10698,43 +10767,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedOr=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedOr" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedOr" >&6; }
+ if [ "${ethr_cv_have__InterlockedOr}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDOR 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedExchange"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedExchange+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedExchange=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10742,43 +10820,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedExchange=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedExchange" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedExchange" >&6; }
+ if [ "${ethr_cv_have__InterlockedExchange}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDEXCHANGE 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedCompareExchange"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "3" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedCompareExchange+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedCompareExchange=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10786,44 +10873,53 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedCompareExchange=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedCompareExchange" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedCompareExchange" >&6; }
+ if [ "${ethr_cv_have__InterlockedCompareExchange}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ ethr_have_native_atomics=yes
+ else
+ :
+ fi
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
ilckd="_InterlockedCompareExchange_acq"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "3" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedCompareExchange_acq+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedCompareExchange_acq=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10831,44 +10927,53 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedCompareExchange_acq=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedCompareExchange_acq" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedCompareExchange_acq" >&6; }
+ if [ "${ethr_cv_have__InterlockedCompareExchange_acq}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE_ACQ 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ ethr_have_native_atomics=yes
+ else
+ :
+ fi
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
ilckd="_InterlockedCompareExchange_rel"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "3" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (long) 0);";;
"3") ilckd_call="${ilckd}(var, (long) 0, (long) 0);";;
"4") ilckd_call="${ilckd}(var, (long) 0, (long) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedCompareExchange_rel+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedCompareExchange_rel=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile long *var;
- volatile long arr[2];
+ volatile long *var;
+ volatile long arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10876,45 +10981,53 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedCompareExchange_rel=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
-printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE_REL 1" >>confdefs.h
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedCompareExchange_rel" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedCompareExchange_rel" >&6; }
+ if [ "${ethr_cv_have__InterlockedCompareExchange_rel}" = "yes" ]; then
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE_REL 1" >>confdefs.h
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
+ ethr_have_native_atomics=yes
+ else
+ :
+ fi
ilckd="_InterlockedDecrement64"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "1" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedDecrement64+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedDecrement64=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10922,43 +11035,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedDecrement64=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedDecrement64" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedDecrement64" >&6; }
+ if [ "${ethr_cv_have__InterlockedDecrement64}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDDECREMENT64 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedDecrement64_rel"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "1" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedDecrement64_rel+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedDecrement64_rel=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -10966,43 +11088,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedDecrement64_rel=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedDecrement64_rel" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedDecrement64_rel" >&6; }
+ if [ "${ethr_cv_have__InterlockedDecrement64_rel}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDDECREMENT64_REL 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedIncrement64"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "1" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedIncrement64+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedIncrement64=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11010,43 +11141,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedIncrement64=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedIncrement64" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedIncrement64" >&6; }
+ if [ "${ethr_cv_have__InterlockedIncrement64}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDINCREMENT64 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedIncrement64_acq"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "1" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedIncrement64_acq+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedIncrement64_acq=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11054,43 +11194,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedIncrement64_acq=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedIncrement64_acq" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedIncrement64_acq" >&6; }
+ if [ "${ethr_cv_have__InterlockedIncrement64_acq}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDINCREMENT64_ACQ 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedExchangeAdd64"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedExchangeAdd64+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedExchangeAdd64=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11098,43 +11247,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedExchangeAdd64=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedExchangeAdd64" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedExchangeAdd64" >&6; }
+ if [ "${ethr_cv_have__InterlockedExchangeAdd64}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDEXCHANGEADD64 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedExchangeAdd64_acq"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedExchangeAdd64_acq+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedExchangeAdd64_acq=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11142,43 +11300,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedExchangeAdd64_acq=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedExchangeAdd64_acq" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedExchangeAdd64_acq" >&6; }
+ if [ "${ethr_cv_have__InterlockedExchangeAdd64_acq}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDEXCHANGEADD64_ACQ 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedAnd64"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedAnd64+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedAnd64=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11186,43 +11353,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedAnd64=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedAnd64" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedAnd64" >&6; }
+ if [ "${ethr_cv_have__InterlockedAnd64}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDAND64 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedOr64"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedOr64+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedOr64=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11230,43 +11406,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedOr64=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedOr64" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedOr64" >&6; }
+ if [ "${ethr_cv_have__InterlockedOr64}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDOR64 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedExchange64"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedExchange64+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedExchange64=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11274,43 +11459,52 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedExchange64=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedExchange64" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedExchange64" >&6; }
+ if [ "${ethr_cv_have__InterlockedExchange64}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDEXCHANGE64 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
ilckd="_InterlockedCompareExchange64"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "3" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedCompareExchange64+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedCompareExchange64=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11318,44 +11512,53 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedCompareExchange64=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedCompareExchange64" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedCompareExchange64" >&6; }
+ if [ "${ethr_cv_have__InterlockedCompareExchange64}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE64 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ ethr_have_native_atomics=yes
+ else
+ :
+ fi
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
ilckd="_InterlockedCompareExchange64_acq"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "3" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedCompareExchange64_acq+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedCompareExchange64_acq=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11363,44 +11566,53 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedCompareExchange64_acq=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedCompareExchange64_acq" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedCompareExchange64_acq" >&6; }
+ if [ "${ethr_cv_have__InterlockedCompareExchange64_acq}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE64_ACQ 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ ethr_have_native_atomics=yes
+ else
+ :
+ fi
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
ilckd="_InterlockedCompareExchange64_rel"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "3" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedCompareExchange64_rel+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedCompareExchange64_rel=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11408,45 +11620,53 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedCompareExchange64_rel=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
-printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE64_REL 1" >>confdefs.h
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedCompareExchange64_rel" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedCompareExchange64_rel" >&6; }
+ if [ "${ethr_cv_have__InterlockedCompareExchange64_rel}" = "yes" ]; then
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE64_REL 1" >>confdefs.h
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
+ ethr_have_native_atomics=yes
+ else
+ :
+ fi
ilckd="_InterlockedCompareExchange128"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
-printf %s "checking for ${ilckd}()... " >&6; }
case "4" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, (__int64) 0);";;
"3") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0);";;
"4") ilckd_call="${ilckd}(var, (__int64) 0, (__int64) 0, arr);";;
esac
- have_interlocked_op=no
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ilckd}()" >&5
+printf %s "checking for ${ilckd}()... " >&6; }
+if test ${ethr_cv_have__InterlockedCompareExchange128+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ethr_cv_have__InterlockedCompareExchange128=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
int
main (void)
{
- volatile __int64 *var;
- volatile __int64 arr[2];
+ volatile __int64 *var;
+ volatile __int64 arr[2];
- $ilckd_call
- return 0;
+ $ilckd_call
+ return 0;
;
return 0;
@@ -11454,15 +11674,20 @@ main (void)
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
- have_interlocked_op=yes
+ ethr_cv_have__InterlockedCompareExchange128=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
- test $have_interlocked_op = yes &&
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ethr_cv_have__InterlockedCompareExchange128" >&5
+printf "%s\n" "$ethr_cv_have__InterlockedCompareExchange128" >&6; }
+ if [ "${ethr_cv_have__InterlockedCompareExchange128}" = "yes" ]; then
+
printf "%s\n" "#define ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE128 1" >>confdefs.h
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_interlocked_op" >&5
-printf "%s\n" "$have_interlocked_op" >&6; }
+ else
+ :
+ fi
fi
if test "$ethr_have_native_atomics" = "yes"; then
@@ -15850,6 +16075,21 @@ fi
+# Check whether --enable-esock_extended_error_info was given.
+if test ${enable_esock_extended_error_info+y}
+then :
+ enableval=$enable_esock_extended_error_info;
+fi
+
+
+if test "x$enable_esock_extended_error_info" != "xno"; then
+
+printf "%s\n" "#define ESOCK_USE_EXTENDED_ERROR_INFO 1" >>confdefs.h
+
+fi
+
+
+
# Check whether --with-esock-counter-size was given.
if test ${with_esock_counter_size+y}
@@ -17053,9 +17293,9 @@ then :
try_dlpi_lib=$erl_xcomp_sysroot/lib
if test x"$ac_cv_sizeof_void_p" = x"8"; then
if test -d $erl_xcomp_sysroot/lib64; then
- try_dlpi_lib= $erl_xcomp_sysroot/lib64
+ try_dlpi_lib=$erl_xcomp_sysroot/lib64
elif test -d $erl_xcomp_sysroot/lib/64; then
- try_dlpi_lib= $erl_xcomp_sysroot/lib/64
+ try_dlpi_lib=$erl_xcomp_sysroot/lib/64
fi
fi
if test ! -f "$try_dlpi_lib/libdlpi.so" && \
@@ -19786,6 +20026,12 @@ then :
printf "%s\n" "#define HAVE_MEMCPY 1" >>confdefs.h
fi
+ac_fn_c_check_func "$LINENO" "memrchr" "ac_cv_func_memrchr"
+if test "x$ac_cv_func_memrchr" = xyes
+then :
+ printf "%s\n" "#define HAVE_MEMRCHR 1" >>confdefs.h
+
+fi
ac_fn_c_check_func "$LINENO" "mallopt" "ac_cv_func_mallopt"
if test "x$ac_cv_func_mallopt" = xyes
then :
@@ -25513,23 +25759,6 @@ printf "%s\n" "#define ERTS_EMU_CMDLINE_FLAGS \"$STATIC_CFLAGS $CFLAGS $DEBUG_CF
-erts=${erl_top}/erts
-
-erts_dirs="
- $erts/obj $erts/obj.debug
-
- $erts/obj/$host
- $erts/obj.debug/$host
-
-"
-for d in ${erl_top}/bin ${erl_top}/bin/$host $erts_dirs ;
-do
- if test ! -d $d; then
- mkdir -p 1>/dev/null 2>&1 $d
- fi
-done
-
-
@@ -25574,23 +25803,6 @@ if test "x$GCC" = xyes; then
fi
-
-# Check whether --enable-sanitizers was given.
-if test ${enable_sanitizers+y}
-then :
- enableval=$enable_sanitizers;
-case "$enableval" in
- no) sanitizers= ;;
- yes) sanitizers="-fsanitize=address,undefined" ;;
- *) sanitizers="-fsanitize=$enableval" ;;
-esac
-CFLAGS="$CFLAGS $sanitizers"
-LDFLAGS="$LDFLAGS $sanitizers"
-
-fi
-
-
-
# Check whether --enable-deterministic-build was given.
if test ${enable_deterministic_build+y}
then :
diff --git a/erts/configure.ac b/erts/configure.ac
index 307be5042d..540cc4b3cb 100644
--- a/erts/configure.ac
+++ b/erts/configure.ac
@@ -1356,6 +1356,19 @@ if test "x$enable_esock_rcvsndtimeo" = "xyes"; then
fi
+dnl *** ESOCK_USE_EXTERNDED_ERROR_INFO ***
+
+AC_ARG_ENABLE(esock_extended_error_info,
+AS_HELP_STRING([--enable-esock-extended-error-info], [enable use of extended error info])
+AS_HELP_STRING([--disable-esock-extended-error-info], [disable use of extended error info (default)]))
+
+dnl Temporary! Currently we require eei to be *explicitly*
+dnl disabled (for debug reasons).
+if test "x$enable_esock_extended_error_info" != "xno"; then
+ AC_DEFINE(ESOCK_USE_EXTENDED_ERROR_INFO, [1], [Use extended error info])
+fi
+
+
dnl *** ESOCK_COUNTER_SIZE ***
AC_ARG_WITH(esock-counter-size,
@@ -1710,9 +1723,9 @@ AS_IF([test x"$ac_cv_lib_dlpi_dlpi_open" = x"no"],
try_dlpi_lib=$erl_xcomp_sysroot/lib
if test x"$ac_cv_sizeof_void_p" = x"8"; then
if test -d $erl_xcomp_sysroot/lib64; then
- try_dlpi_lib= $erl_xcomp_sysroot/lib64
+ try_dlpi_lib=$erl_xcomp_sysroot/lib64
elif test -d $erl_xcomp_sysroot/lib/64; then
- try_dlpi_lib= $erl_xcomp_sysroot/lib/64
+ try_dlpi_lib=$erl_xcomp_sysroot/lib/64
fi
fi
if test ! -f "$try_dlpi_lib/libdlpi.so" && \
@@ -2168,7 +2181,7 @@ AC_CHECK_FUNCS([getipnodebyname getipnodebyaddr gethostbyname2])
AC_CHECK_FUNCS([ieee_handler fpsetmask finite isnan isinf res_gethostbyname dlopen \
dlvsym pread pwrite memmove strerror strerror_r strncasecmp \
gethrtime localtime_r gmtime_r mprotect madvise posix_madvise \
- mmap mremap memcpy mallopt sbrk _sbrk __sbrk brk _brk __brk \
+ mmap mremap memcpy memrchr mallopt sbrk _sbrk __sbrk brk _brk __brk \
flockfile fstat strlcpy strlcat setsid posix2time time2posix \
setlocale nl_langinfo poll mlockall ppoll vsyslog])
@@ -3487,26 +3500,6 @@ AC_DEFINE_UNQUOTED(ERTS_EMU_CMDLINE_FLAGS,
AC_SUBST(STATIC_CFLAGS)
-dnl ----------------------------------------------------------------------
-dnl Directories needed for the build
-dnl ----------------------------------------------------------------------
-
-erts=${erl_top}/erts
-
-erts_dirs="
- $erts/obj $erts/obj.debug
-
- $erts/obj/$host
- $erts/obj.debug/$host
-
-"
-for d in ${erl_top}/bin ${erl_top}/bin/$host $erts_dirs ;
-do
- if test ! -d $d; then
- mkdir -p 1>/dev/null 2>&1 $d
- fi
-done
-
dnl ---------------------------------------------------------------------
dnl Autoheader macro for adding code at top and bottom of config.h.in
dnl ---------------------------------------------------------------------
@@ -3580,26 +3573,6 @@ if test "x$GCC" = xyes; then
fi
dnl ----------------------------------------------------------------------
-dnl Enable -fsanitize= flags.
-dnl ----------------------------------------------------------------------
-
-m4_define(DEFAULT_SANITIZERS, [address,undefined])
-AC_ARG_ENABLE(
- sanitizers,
- AS_HELP_STRING(
- [--enable-sanitizers@<:@=comma-separated list of sanitizers@:>@],
- [Default=DEFAULT_SANITIZERS]),
-[
-case "$enableval" in
- no) sanitizers= ;;
- yes) sanitizers="-fsanitize=DEFAULT_SANITIZERS" ;;
- *) sanitizers="-fsanitize=$enableval" ;;
-esac
-CFLAGS="$CFLAGS $sanitizers"
-LDFLAGS="$LDFLAGS $sanitizers"
-])
-
-dnl ----------------------------------------------------------------------
dnl Enable build determinism flag
dnl ----------------------------------------------------------------------
diff --git a/erts/doc/src/absform.xml b/erts/doc/src/absform.xml
index afdb2e7b70..d5c27bb200 100644
--- a/erts/doc/src/absform.xml
+++ b/erts/doc/src/absform.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2001</year><year>2021</year>
+ <year>2001</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -412,6 +412,13 @@
For Rep(Q), see below.</p>
</item>
<item>
+ <p>If E is a map comprehension <c>#{E_0 || Q_1, ..., Q_k}</c>,
+ where <c>E_0</c> is an association <c>K => V</c>
+ and each <c>Q_i</c> is a qualifier, then Rep(E) =
+ <c>{mc,ANNO,Rep(E_0),[Rep(Q_1), ..., Rep(Q_k)]}</c>.
+ For Rep(E_0) and Rep(Q), see below.</p>
+ </item>
+ <item>
<p>If E is a map creation <c>#{A_1, ..., A_k}</c>,
where each <c>A_i</c> is an association <c>E_i_1 => E_i_2</c>,
then Rep(E) = <c>{map,ANNO,[Rep(A_1), ..., Rep(A_k)]}</c>.
@@ -564,7 +571,7 @@
Rep(Q) = <c>Rep(E)</c>.</p>
</item>
<item>
- <p>If Q is a generator <c>P &lt;- E</c>, where <c>P</c> is
+ <p>If Q is a list generator <c>P &lt;- E</c>, where <c>P</c> is
a pattern and <c>E</c> is an expression, then Rep(Q) =
<c>{generate,ANNO,Rep(P),Rep(E)}</c>.</p>
</item>
@@ -573,6 +580,12 @@
a pattern and <c>E</c> is an expression, then Rep(Q) =
<c>{b_generate,ANNO,Rep(P),Rep(E)}</c>.</p>
</item>
+ <item>
+ <p>If Q is a map generator <c>P &lt;- E</c>, where <c>P</c> is
+ an association pattern <c>P_1 := P_2</c> and <c>E</c> is an expression, then Rep(Q) =
+ <c>{m_generate,ANNO,Rep(P),Rep(E)}</c>.
+ For Rep(P), see below.</p>
+ </item>
</list>
</section>
diff --git a/erts/doc/src/crash_dump.xml b/erts/doc/src/crash_dump.xml
index 4524401473..bc3e085099 100644
--- a/erts/doc/src/crash_dump.xml
+++ b/erts/doc/src/crash_dump.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1999</year><year>2022</year>
+ <year>1999</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -534,7 +534,7 @@ Slogan: &lt;reason&gt;</pre>
<tag><em>Type</em></tag>
<item>
<p>The table type, that is, <c>set</c>, <c>bag</c>,
- <c>dublicate_bag</c>, or <c>ordered_set</c>.</p>
+ <c>duplicate_bag</c>, or <c>ordered_set</c>.</p>
</item>
<tag><em>Compressed</em></tag>
<item>
diff --git a/erts/doc/src/erl_cmd.xml b/erts/doc/src/erl_cmd.xml
index ebfed75bf4..f6a36ecffd 100644
--- a/erts/doc/src/erl_cmd.xml
+++ b/erts/doc/src/erl_cmd.xml
@@ -540,12 +540,12 @@ $ <input>erl \
to implement an Alternative Carrier for the Erlang
Distribution</seeguide>.</p>
</item>
- <tag><c><![CDATA[-noinput]]></c></tag>
+ <tag><marker id="noinput"/><c><![CDATA[-noinput]]></c></tag>
<item>
<p>Ensures that the Erlang runtime system never tries to read
any input. Implies <c><![CDATA[-noshell]]></c>.</p>
</item>
- <tag><c><![CDATA[-noshell]]></c></tag>
+ <tag><marker id="noshell"/><c><![CDATA[-noshell]]></c></tag>
<item>
<p>Starts an Erlang runtime system with no shell. This flag
makes it possible to have the Erlang runtime system as a
@@ -949,6 +949,14 @@ $ <input>erl \
<seeerl marker="erlang#process_flag_max_heap_size">
<c>process_flag(max_heap_size, MaxHeapSize)</c></seeerl>.</p>
</item>
+ <tag><marker id="+hmaxib"/><c><![CDATA[+hmaxib true|false]]></c></tag>
+ <item>
+ <p>Sets whether to include the size of shared off-heap binaries
+ in the sum compared against the maximum heap size. Defaults to
+ <c>false</c>. For more information, see
+ <seeerl marker="erlang#process_flag_max_heap_size">
+ <c>process_flag(max_heap_size, MaxHeapSize)</c></seeerl>.</p>
+ </item>
<tag><marker id="+hmaxk"/><c><![CDATA[+hmaxk true|false]]></c></tag>
<item>
<p>Sets whether to kill processes reaching the maximum heap size or not.
@@ -1048,6 +1056,18 @@ $ <input>erl \
section in the BeamAsm internal documentation.
</p>
</item>
+ <tag><marker id="+JMsingle"/><c>+JMsingle true|false</c></tag>
+ <item>
+ <p>Enables or disables the use of single-mapped RWX memory for JIT
+ code. The default is to map JIT:ed machine code into two
+ regions sharing the same physical pages, where one region is
+ executable but not writable, and the other writable but not
+ executable. As some tools, such as QEMU user mode emulation,
+ cannot deal with the dual mapping, this flags allows it to be
+ disabled. This flag is automatically enabled by the
+ <seecom marker="#+JPperf"><c>+JPperf</c></seecom> flag.
+ </p>
+ </item>
<tag><c><![CDATA[+L]]></c></tag>
<item>
<p>Prevents loading information about source filenames and line
diff --git a/erts/doc/src/erl_dist_protocol.xml b/erts/doc/src/erl_dist_protocol.xml
index 400d3d896d..389e967f5f 100644
--- a/erts/doc/src/erl_dist_protocol.xml
+++ b/erts/doc/src/erl_dist_protocol.xml
@@ -1097,11 +1097,8 @@ DiB == gen_digest(ChA, ICA)?
</item>
<tag><marker id="DFLAG_UNLINK_ID"/><c>-define(DFLAG_UNLINK_ID, 16#2000000).</c></tag>
<item>
- <p>Use the <seeguide marker="#new_link_protocol">new link protocol</seeguide>.</p>
- <note><p>This flag will become mandatory in OTP 26.</p></note>
- <p>Unless both nodes have set the <c>DFLAG_UNLINK_ID</c> flag, the
- <seeguide marker="#old_link_protocol">old link protocol</seeguide>
- will be used as a fallback.</p>
+ <p>Use the <seeguide marker="#link_protocol">new link protocol</seeguide>.</p>
+ <note><p>This flag is mandatory as of OTP 26.</p></note>
</item>
<tag><c>-define(DFLAG_MANDATORY_25_DIGEST, (1 bsl 36)).</c></tag>
<item>
@@ -1133,8 +1130,7 @@ DiB == gen_digest(ChA, ICA)?
<seeguide marker="erl_ext_dist#V4_PORT_EXT"><c>V4_PORT_EXT</c></seeguide>,
and in the reference case up to 5 32-bit ID words are now accepted in
<seeguide marker="erl_ext_dist#NEWER_REFERENCE_EXT"><c>NEWER_REFERENCE_EXT</c></seeguide>.
- Introduced in OTP 24.</p>
- <note><p>This flag will become mandatory in OTP 26.</p></note>
+ This flag was introduced in OTP 24 and became mandatory in OTP 26.</p>
</item>
<tag><marker id="DFLAG_ALIAS"/><c>-define(DFLAG_ALIAS, (1 bsl 35)).</c></tag>
<item>
@@ -1273,17 +1269,12 @@ DiB == gen_digest(ChA, ICA)?
<p><c>{3, FromPid, ToPid, Reason}</c></p>
<p>This signal is sent when a link has been broken</p>
</item>
- <tag><marker id="UNLINK"/><c>UNLINK</c> (deprecated)</tag>
+ <tag><marker id="UNLINK"/><c>UNLINK</c> (obsolete)</tag>
<item>
<p><c>{4, FromPid, ToPid}</c></p>
- <p>This signal is sent by <c>FromPid</c> in order to remove
- a link between <c>FromPid</c> and <c>ToPid</c>, when using the
- <seeguide marker="#old_link_protocol">old link
- protocol</seeguide>.</p>
- <warning><p>This signal has been deprecated and will not
- be supported in OTP 26. For more information see the
- documentation of the
- <seeguide marker="#new_link_protocol">new link protocol</seeguide>.
+ <warning><p>This signal is obsolete and not supported as of
+ OTP 26. For more information see the documentation of the
+ <seeguide marker="#link_protocol">link protocol</seeguide>.
</p></warning>
</item>
<tag><c>NODE_LINK</c></tag>
@@ -1573,11 +1564,9 @@ DiB == gen_digest(ChA, ICA)?
among all not yet acknowledged <c>UNLINK_ID</c> signals from
<c>FromPid</c> to <c>ToPid</c>.</p>
<p>
- This signal is only passed when the
- <seeguide marker="#new_link_protocol">new link protocol</seeguide>
- has been negotiated using the
- <seeguide marker="erl_dist_protocol#DFLAG_UNLINK_ID"><c>DFLAG_UNLINK_ID</c></seeguide>
- <seeguide marker="erl_dist_protocol#dflags">distribution flag</seeguide>.
+ This signal is part of the
+ <seeguide marker="#link_protocol">new link protocol</seeguide>
+ which became mandatory as of OTP 26.
</p>
</item>
<tag><marker id="UNLINK_ID_ACK"/><c>UNLINK_ID_ACK</c></tag>
@@ -1592,11 +1581,9 @@ DiB == gen_digest(ChA, ICA)?
<c>ToPid</c> identifies the sender of the <c>UNLINK_ID</c>
signal.</p>
<p>
- This signal is only passed when the
- <seeguide marker="#new_link_protocol">new link protocol</seeguide>
- has been negotiated using the
- <seeguide marker="erl_dist_protocol#DFLAG_UNLINK_ID"><c>DFLAG_UNLINK_ID</c></seeguide>
- <seeguide marker="erl_dist_protocol#dflags">distribution flag</seeguide>.
+ This signal is part of the
+ <seeguide marker="#link_protocol">new link protocol</seeguide>
+ which became mandatory as of OTP 26.
</p>
</item>
</taglist>
@@ -1627,200 +1614,173 @@ DiB == gen_digest(ChA, ICA)?
</section>
<section>
<marker id="link_protocol"/>
+ <!-- The following markers kept in order not to break links
+ from the outside world... -->
+ <marker id="new_link_protocol"/>
+ <marker id="old_link_protocol"/>
<title>Link Protocol</title>
- <section>
- <marker id="new_link_protocol"/>
- <title>New Link Protocol</title>
-
- <p>
- The new link protocol will be used when both nodes flag that
- they understand it using the
- <seeguide marker="#DFLAG_UNLINK_ID"><c>DFLAG_UNLINK_ID</c></seeguide>
- <seeguide marker="#dflags">distribution flag</seeguide>. If
- one of the nodes does not understand the new link protocol, the
- <seeguide marker="#old_link_protocol">old link protocol</seeguide>
- will be used as a fallback.
- </p>
-
- <p>
- The new link protocol introduces two new signals,
- <seeguide marker="#UNLINK_ID"><c>UNLINK_ID</c></seeguide> and
- <seeguide marker="#UNLINK_ID"><c>UNLINK_ID_ACK</c></seeguide>,
- which replace the old
- <seeguide marker="#UNLINK"><c>UNLINK</c></seeguide>
- signal. The old <seeguide marker="#LINK"><c>LINK</c></seeguide>
- signal is still sent in order to set up a link, but handled
- differently upon reception.
- </p>
-
- <p>
- In order to set up a link, a <c>LINK</c> signal is sent, from
- the process initiating the operation, to the process that it
- wants to link to. In order to remove a link, an
- <c>UNLINK_ID</c> signal is sent, from the process initiating
- the operation, to the linked process. The receiver of an
- <c>UNLINK_ID</c> signal responds with an <c>UNLINK_ID_ACK</c>
- signal. Upon reception of an <c>UNLINK_ID</c> signal, the
- corresponding <c>UNLINK_ID_ACK</c> signal <em>must</em> be
- sent before any other signals are sent to the sender of the
- <c>UNLINK_ID</c> signal. Together with
- <seeguide marker="system/reference_manual:processes#signal-delivery">the
- signal ordering guarantee</seeguide> of Erlang this makes it
- possible for the sender of the <c>UNLINK_ID</c> signal to know
- the order of other signals which is essential for the protocol.
- The <c>UNLINK_ID_ACK</c> signal should contain the same
- <c>Id</c> as the <c>Id</c> contained in the <c>UNLINK_ID</c>
- signal being acknowledged.
- </p>
+ <p>
+ The new link protocol introduced in OTP 23.3 became mandatory
+ as of OTP 26. As of OTP 26, OTP nodes will therefor refuse to
+ connect to nodes that do not indicate that they support the
+ new link protocol using the
+ <seeguide marker="#DFLAG_UNLINK_ID"><c>DFLAG_UNLINK_ID</c></seeguide>
+ <seeguide marker="#dflags">distribution flag</seeguide>.
+ </p>
- <p>
- Processes also need to maintain process local information about
- links. The state of this process local information is changed
- when the signals above are sent and received. This process
- local information also determines if a signal should be sent
- when a process calls
- <seemfa marker="erlang#link/1"><c>link/1</c></seemfa> or
- <seemfa marker="erlang#unlink/1"><c>unlink/1</c></seemfa>.
- A <c>LINK</c> signal is only sent if there does not currently
- exist an active link between the processes according to the
- process local information and an <c>UNLINK_ID</c> signal is
- only sent if there currently exists an active link between the
- processes according to the process local information.
- </p>
+ <p>
+ The new link protocol introduced two new signals,
+ <seeguide marker="#UNLINK_ID"><c>UNLINK_ID</c></seeguide> and
+ <seeguide marker="#UNLINK_ID"><c>UNLINK_ID_ACK</c></seeguide>,
+ which replaced the old
+ <seeguide marker="#UNLINK"><c>UNLINK</c></seeguide>
+ signal. The old <seeguide marker="#LINK"><c>LINK</c></seeguide>
+ signal is still sent in order to set up a link, but handled
+ differently upon reception.
+ </p>
- <p>
- The process local information about a link contains:
- </p>
- <taglist>
- <tag>Pid</tag>
- <item>
- Process identifier of the linked process.
- </item>
- <tag>Active Flag</tag>
- <item>
- If set, the link is active and the process will react on
- <seeguide marker="system/reference_manual:processes#receiving_exit_signals">incoming
- exit signals</seeguide> issued due to the link. If not set,
- the link is inactive and incoming exit signals, issued due
- to the link, will be ignored. That is, the processes are
- considered as <em>not</em> linked.
- </item>
- <tag>Unlink Id</tag>
- <item>
- Identifier of an outstanding unlink operation. That is,
- an unlink operation that has not yet been acknowledged.
- This information is only used when the active flag is not
- set.
- </item>
- </taglist>
+ <p>
+ In order to set up a link, a <c>LINK</c> signal is sent, from
+ the process initiating the operation, to the process that it
+ wants to link to. In order to remove a link, an
+ <c>UNLINK_ID</c> signal is sent, from the process initiating
+ the operation, to the linked process. The receiver of an
+ <c>UNLINK_ID</c> signal responds with an <c>UNLINK_ID_ACK</c>
+ signal. Upon reception of an <c>UNLINK_ID</c> signal, the
+ corresponding <c>UNLINK_ID_ACK</c> signal <em>must</em> be
+ sent before any other signals are sent to the sender of the
+ <c>UNLINK_ID</c> signal. Together with
+ <seeguide marker="system/reference_manual:processes#signal-delivery">the
+ signal ordering guarantee</seeguide> of Erlang this makes it
+ possible for the sender of the <c>UNLINK_ID</c> signal to know
+ the order of other signals which is essential for the protocol.
+ The <c>UNLINK_ID_ACK</c> signal should contain the same
+ <c>Id</c> as the <c>Id</c> contained in the <c>UNLINK_ID</c>
+ signal being acknowledged.
+ </p>
- <p>
- A process is only considered linked to another process
- if it has process local information about the link
- containing the process identifier of the other process and
- with the active flag set.
- </p>
+ <p>
+ Processes also need to maintain process local information about
+ links. The state of this process local information is changed
+ when the signals above are sent and received. This process
+ local information also determines if a signal should be sent
+ when a process calls
+ <seemfa marker="erlang#link/1"><c>link/1</c></seemfa> or
+ <seemfa marker="erlang#unlink/1"><c>unlink/1</c></seemfa>.
+ A <c>LINK</c> signal is only sent if there does not currently
+ exist an active link between the processes according to the
+ process local information and an <c>UNLINK_ID</c> signal is
+ only sent if there currently exists an active link between the
+ processes according to the process local information.
+ </p>
- <p>
- The process local information about a link is updated as
- follows:
- </p>
- <taglist>
- <tag>A <c>LINK</c> signal is sent</tag>
- <item>
- Link information is created if not already existing. The
- active flag is set, and unlink id is cleared. That is,
- if we had an outstanding unlink operation we will ignore
- the result of that operation and enable the link.
- </item>
- <tag>A <c>LINK</c> signal is received</tag>
- <item>
- If no link information already exists, it is created, the
- active flag is set and unlink id is cleared. If the link
- information already exists, the signal is silently ignored,
- regardless of whether the active flag is set or not.
- That is, if we have an outstanding unlink operation we will
- <em>not</em> activate the link. In this scenario, the sender
- of the <c>LINK</c> signal has not yet sent an
- <c>UNLINK_ID_ACK</c> signal corresponding to our
- <c>UNLINK_ID</c> signal which means that it will receive
- our <c>UNLINK_ID</c> signal after it sent its
- <c>LINK</c> signal. This in turn means that both processes
- in the end will agree that there is no link between them.
- </item>
- <tag>An <c>UNLINK_ID</c> signal is sent</tag>
- <item>
- Link information already exists and the active flag is set
- (otherwise the signal would not be sent). The active flag
- is unset, and the unlink id of the signal is saved in the
- link information.
- </item>
- <tag>An <c>UNLINK_ID</c> signal is received</tag>
- <item>
- If the active flag is set, information about the link
- is removed. If the active flag is not set (that is, we have
- an outstanding unlink operation), the information about the
- link is left unchanged.
- </item>
- <tag>An <c>UNLINK_ID_ACK</c> signal is sent</tag>
- <item>
- This is done when an <c>UNLINK_ID</c> signal is received and
- causes no further changes of the link information.
- </item>
- <tag>An <c>UNLINK_ID_ACK</c> signal is received</tag>
- <item>
- If information about the link exists, the active flag is not
- set, and the unlink id in the link information equals the
- <c>Id</c> in the signal, the link information is removed;
- otherwise, the signal is ignored.
- </item>
- </taglist>
+ <p>
+ The process local information about a link contains:
+ </p>
+ <taglist>
+ <tag>Pid</tag>
+ <item>
+ Process identifier of the linked process.
+ </item>
+ <tag>Active Flag</tag>
+ <item>
+ If set, the link is active and the process will react on
+ <seeguide marker="system/reference_manual:processes#receiving_exit_signals">incoming
+ exit signals</seeguide> issued due to the link. If not set,
+ the link is inactive and incoming exit signals, issued due
+ to the link, will be ignored. That is, the processes are
+ considered as <em>not</em> linked.
+ </item>
+ <tag>Unlink Id</tag>
+ <item>
+ Identifier of an outstanding unlink operation. That is,
+ an unlink operation that has not yet been acknowledged.
+ This information is only used when the active flag is not
+ set.
+ </item>
+ </taglist>
- <p>
- When a process receives an exit signal due to a link, the
- process will first react to the exit signal if the link
- is active and then remove the process local information about
- the link.
- </p>
- <p>
- In case the connection is lost between two nodes, exit signals
- with exit reason <c>noconnection</c> are sent to all processes
- with links over the connection. This will cause all process
- local information about links over the connection to be
- removed.
- </p>
- <p>
- Exactly the same link protocol is also used internally on an
- Erlang node. The signals however have different formats since
- they do not have to be sent over the wire.
- </p>
- </section>
+ <p>
+ A process is only considered linked to another process
+ if it has process local information about the link
+ containing the process identifier of the other process and
+ with the active flag set.
+ </p>
- <section>
- <marker id="old_link_protocol"/>
- <title>Old Link Protocol</title>
+ <p>
+ The process local information about a link is updated as
+ follows:
+ </p>
+ <taglist>
+ <tag>A <c>LINK</c> signal is sent</tag>
+ <item>
+ Link information is created if not already existing. The
+ active flag is set, and unlink id is cleared. That is,
+ if we had an outstanding unlink operation we will ignore
+ the result of that operation and enable the link.
+ </item>
+ <tag>A <c>LINK</c> signal is received</tag>
+ <item>
+ If no link information already exists, it is created, the
+ active flag is set and unlink id is cleared. If the link
+ information already exists, the signal is silently ignored,
+ regardless of whether the active flag is set or not.
+ That is, if we have an outstanding unlink operation we will
+ <em>not</em> activate the link. In this scenario, the sender
+ of the <c>LINK</c> signal has not yet sent an
+ <c>UNLINK_ID_ACK</c> signal corresponding to our
+ <c>UNLINK_ID</c> signal which means that it will receive
+ our <c>UNLINK_ID</c> signal after it sent its
+ <c>LINK</c> signal. This in turn means that both processes
+ in the end will agree that there is no link between them.
+ </item>
+ <tag>An <c>UNLINK_ID</c> signal is sent</tag>
+ <item>
+ Link information already exists and the active flag is set
+ (otherwise the signal would not be sent). The active flag
+ is unset, and the unlink id of the signal is saved in the
+ link information.
+ </item>
+ <tag>An <c>UNLINK_ID</c> signal is received</tag>
+ <item>
+ If the active flag is set, information about the link
+ is removed. If the active flag is not set (that is, we have
+ an outstanding unlink operation), the information about the
+ link is left unchanged.
+ </item>
+ <tag>An <c>UNLINK_ID_ACK</c> signal is sent</tag>
+ <item>
+ This is done when an <c>UNLINK_ID</c> signal is received and
+ causes no further changes of the link information.
+ </item>
+ <tag>An <c>UNLINK_ID_ACK</c> signal is received</tag>
+ <item>
+ If information about the link exists, the active flag is not
+ set, and the unlink id in the link information equals the
+ <c>Id</c> in the signal, the link information is removed;
+ otherwise, the signal is ignored.
+ </item>
+ </taglist>
- <p>
- The old link protocol utilize two signals
- <seeguide marker="#LINK"><c>LINK</c></seeguide>, and
- <seeguide marker="#UNLINK"><c>UNLINK</c></seeguide>. The
- <c>LINK</c> signal informs the other process that a link
- should be set up, and the <c>UNLINK</c> signal informs the
- other process that a link should be removed. This protocol
- is however a bit too naive. If both processes operate on the
- link simultaneously, the link may end up in an inconsistent
- state where one process thinks it is linked while the other
- thinks it is not linked.
- </p>
- <p>
- This protocol is deprecated and support for it will be removed
- in OTP 26. Until then, it will be used as fallback when
- communicating with old nodes that do not understand the
- <seeguide marker="#new_link_protocol">new link
- protocol</seeguide>.
- </p>
- </section>
+ <p>
+ When a process receives an exit signal due to a link, the
+ process will first react to the exit signal if the link
+ is active and then remove the process local information about
+ the link.
+ </p>
+ <p>
+ In case the connection is lost between two nodes, exit signals
+ with exit reason <c>noconnection</c> are sent to all processes
+ with links over the connection. This will cause all process
+ local information about links over the connection to be
+ removed.
+ </p>
+ <p>
+ Exactly the same link protocol is also used internally on an
+ Erlang node. The signals however have different formats since
+ they do not have to be sent over the wire.
+ </p>
</section>
</section>
diff --git a/erts/doc/src/erl_driver.xml b/erts/doc/src/erl_driver.xml
index 2547b6e952..5e0e0349f3 100644
--- a/erts/doc/src/erl_driver.xml
+++ b/erts/doc/src/erl_driver.xml
@@ -4,7 +4,7 @@
<cref>
<header>
<copyright>
- <year>2001</year><year>2021</year>
+ <year>2001</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -2567,12 +2567,14 @@ ErlDrvTermData spec[] = {
ERL_DRV_STRING_CONS, (ErlDrvTermData)"abc", 3,
};
erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0])); ]]></code>
- <p>The <c>ERL_DRV_EXT2TERM</c> term type is used for passing a
+ <p>
+ <marker id="ERL_DRV_EXT2TERM"/>
+ The <c>ERL_DRV_EXT2TERM</c> term type is used for passing a
term encoded with the
<seeguide marker="erl_ext_dist">external format</seeguide>,
that is, a term that has been encoded by
<seemfa marker="erlang#term_to_binary/2">
- <c>erlang:term_to_binary</c></seemfa>,
+ <c>erlang:term_to_binary()</c></seemfa>,
<seecref marker="erl_interface:ei"><c>erl_interface:ei(3)</c></seecref>,
and so on.
For example, if <c>binp</c> is a pointer to an <c>ErlDrvBinary</c>
diff --git a/erts/doc/src/erl_ext_dist.xml b/erts/doc/src/erl_ext_dist.xml
index b4b245187d..712c30afc0 100644
--- a/erts/doc/src/erl_ext_dist.xml
+++ b/erts/doc/src/erl_ext_dist.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2007</year>
- <year>2021</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -655,6 +655,7 @@
encoded using <c>NEW_PORT_EXT</c>, even external ports received as <seeguide
marker="#PORT_EXT"><c>PORT_EXT</c></seeguide> from older nodes.
</p>
+
</section>
<section>
@@ -683,8 +684,10 @@
works just like in <seeguide marker="#NEW_PID_EXT"><c>NEW_PID_EXT</c></seeguide>.
Port operations are not allowed across node boundaries.
</p>
- <p><c>V4_PORT_EXT</c> was introduced in OTP 24, but only to be decoded
- and echoed back. Not encoded for local ports.
+ <p>In OTP 26 distribution flag
+ <seeguide marker="erl_dist_protocol#DFLAG_V4_NC"><c>DFLAG_V4_NC</c></seeguide>
+ as well as <c>V4_PORT_EXT</c> became mandatory accepting full
+ 64-bit ports to be decoded and echoed back.
</p>
</section>
@@ -742,14 +745,10 @@
<seeguide marker="#utf8_atoms">encoded as an atom</seeguide>.</p>
</item>
<tag><c>ID</c></tag>
- <item><p>A 32-bit big endian unsigned integer. If distribution flag
- <seeguide marker="erl_dist_protocol#DFLAG_V4_NC"><c>DFLAG_V4_NC</c></seeguide>
- is not set, only 15 bits may be used and the rest must be 0.</p>
+ <item><p>A 32-bit big endian unsigned integer.</p>
</item>
<tag><c>Serial</c></tag>
- <item><p>A 32-bit big endian unsigned integer. If distribution flag
- <seeguide marker="erl_dist_protocol#DFLAG_V4_NC"><c>DFLAG_V4_NC</c></seeguide>
- is not set, only 13 bits may be used and the rest must be 0.</p>
+ <item><p>A 32-bit big endian unsigned integer.</p>
</item>
<tag><c>Creation</c></tag>
<item><p>A 32-bit big endian unsigned integer. All identifiers
@@ -768,9 +767,10 @@
even external pids received as
<seeguide marker="#PID_EXT"><c>PID_EXT</c></seeguide> from older nodes.
</p>
- <p>In OTP 24 distribution flag
+ <p>In OTP 26 distribution flag
<seeguide marker="erl_dist_protocol#DFLAG_V4_NC"><c>DFLAG_V4_NC</c></seeguide>
- was introduced, accepting full 64-bit pids to be decoded and echoed back.
+ became mandatory accepting full 64-bit pids to be decoded
+ and echoed back.
</p>
</section>
@@ -1080,9 +1080,7 @@
<seeguide marker="#utf8_atoms">encoded as an atom</seeguide>.</p>
</item>
<tag><c>Len</c></tag>
- <item><p>A 16-bit big endian unsigned integer not larger than 5 when the
- <seeguide marker="erl_dist_protocol#DFLAG_V4_NC"><c>DFLAG_V4_NC</c></seeguide>
- has been set; otherwise not larger than 3.</p>
+ <item><p>A 16-bit big endian unsigned integer not larger than 5.</p>
</item>
<tag><c>ID</c></tag>
<item><p>A sequence of <c>Len</c> big-endian unsigned integers
@@ -1104,6 +1102,9 @@
<seeguide marker="#NEW_REFERENCE_EXT"><c>NEW_REFERENCE_EXT</c></seeguide>
from older nodes.
</p>
+ <p>In OTP 26 distribution flag
+ <seeguide marker="erl_dist_protocol#DFLAG_V4_NC"><c>DFLAG_V4_NC</c></seeguide>
+ became mandatory. References now can contain up to 5 <c>ID</c> words.</p>
</section>
<section>
@@ -1420,6 +1421,46 @@
</note>
</section>
+ <section>
+ <marker id="LOCAL_EXT"/>
+ <title>LOCAL_EXT</title>
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">...</cell>
+ </row>
+ <row>
+ <cell align="center"><c>121</c></cell>
+ <cell align="center">...</cell>
+ </row>
+ <tcaption>LOCAL_EXT</tcaption></table>
+ <p>
+ Marks that this is encoded on an alternative local external term
+ format intended to only be decoded by a specific local decoder.
+ The bytes following from here on may contain any unspecified type
+ of encoding of terms. It is the responsibility of the user to only
+ attempt to decode terms on the local external term format which has
+ been produced by a matching encoder.
+ </p>
+ <p>
+ This tag is used by the Erlang runtime system upon encoding the local
+ external term format when the
+ <seeerl marker="erts:erlang#term_to_binary_local"><c>local</c></seeerl>
+ option is passed to
+ <seemfa marker="erts:erlang#term_to_binary/2"><c>term_to_binary/2</c></seemfa>,
+ but can be used by other encoders as well providing similar
+ functionality. The Erlang runtime system adds a hash immediately
+ following the <c>LOCAL_EXT</c> tag which is verified on decoding in
+ order to verify that encoder and decoder match which might be a good
+ practice. This will very likely catch mistakes made by users, but
+ is not guaranteed to, and is not intended to, prevent decoding of an
+ intentionally forged encoding on the local external term format.
+ </p>
+ <p>
+ <c>LOCAL_EXT</c> was introduced in OTP @OTP-18477@.
+ </p>
+ </section>
+
</chapter>
diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml
index 91e9e68d16..1be152b601 100644
--- a/erts/doc/src/erl_nif.xml
+++ b/erts/doc/src/erl_nif.xml
@@ -4,7 +4,7 @@
<cref>
<header>
<copyright>
- <year>2001</year><year>2022</year>
+ <year>2001</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -171,11 +171,37 @@ $> erl
to query terms, like <c>enif_is_atom</c>, <c>enif_is_identical</c>,
and <c>enif_compare</c>.</p>
<p>All terms of type <c>ERL_NIF_TERM</c> belong to an environment of
- type <seecref marker="#ErlNifEnv"><c>ErlNifEnv</c></seecref>. The
- lifetime of a term is controlled by the lifetime of its environment
- object. All API functions that read or write terms has the
- environment that the term belongs to as the first function
- argument.</p>
+ type <seecref marker="#ErlNifEnv"><c>ErlNifEnv</c></seecref>,
+ except atoms created during loading (by callbacks
+ <seecref marker="#load"><c>load</c></seecref> or
+ <seecref marker="#upgrade"><c>upgrade</c></seecref>). The lifetime of
+ a term is controlled by the lifetime of its environment object. All
+ API functions that read or write terms have the environment that the
+ term belongs to as the first function argument. However, the atoms
+ created during loading can be referred as a term in any <c>ErlNifEnv</c>.
+ That is, the best practice it to create all your atoms during
+ loading and store them in static/global variables, for example:</p>
+ <code type="none"><![CDATA[
+#include <erl_nif.h>
+
+ERL_NIF_TERM world_atom;
+
+static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ world_atom = enif_make_atom(env, "world");
+ return 0;
+}
+
+static ERL_NIF_TERM hello(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ERL_NIF_TERM hello_string = enif_make_string(env, "Hello", ERL_NIF_LATIN1);
+ return enif_make_tuple2(env, hello_string, world_atom);
+}
+
+static ErlNifFunc nif_funcs[] = { { "hello", 0, hello } };
+
+ERL_NIF_INIT(niftest, nif_funcs, load, NULL, NULL, NULL)
+]]></code>
</item>
<tag>Binaries</tag>
<item>
@@ -785,6 +811,41 @@ typedef struct {
To compare two monitors, <seecref marker="#enif_compare_monitors">
<c>enif_compare_monitors</c></seecref> must be used.</p>
</item>
+ <tag><marker id="ErlNifOnHaltCallback"/><c>ErlNifOnHaltCallback</c></tag>
+ <item>
+ <code type="none">
+typedef void ErlNifOnHaltCallback(void *priv_data);</code>
+ <p>
+ The function prototype of an <i>on halt</i> callback function.
+ </p>
+ <p>
+ An <i>on halt</i> callback can be installed using
+ <seecref marker="#on_halt"><c>enif_set_option()</c></seecref>. Such
+ an installed callback will be called when the runtime system is
+ halting.
+ </p>
+ </item>
+ <tag><marker id="ErlNifOption"/><c>ErlNifOption</c></tag>
+ <item>
+ <p>
+ An enumeration of the options that can be set using
+ <seecref marker="#enif_set_option"><c>enif_set_option()</c></seecref>.
+ </p>
+ <p>Currently valid options:</p>
+ <taglist>
+ <tag><seecref marker="#delay_halt"><c>ERL_NIF_OPT_DELAY_HALT</c></seecref></tag>
+ <item><p>
+ Enable delay of runtime system halt with flushing enabled until
+ all calls to NIFs in the NIF library have returned.
+ </p></item>
+ <tag><seecref marker="#on_halt"><c>ERL_NIF_OPT_ON_HALT</c></seecref></tag>
+ <item><p>
+ Install a callback that will be called when the runtime system
+ halts with flushing enabled.
+ </p></item>
+ </taglist>
+ </item>
+
<tag><marker id="ErlNifPid"/><c>ErlNifPid</c></tag>
<item>
<p>A process identifier (pid). In contrast to pid terms (instances of
@@ -869,11 +930,12 @@ typedef void ErlNifResourceDynCall(ErlNifEnv* caller_env, void* obj, void* call_
<item>
<code type="none">
typedef enum {
- ERL_NIF_LATIN1
+ ERL_NIF_LATIN1,
+ ERL_NIF_UTF8,
}ErlNifCharEncoding;</code>
<p>The character encoding used in strings and atoms. The only
- supported encoding is <c>ERL_NIF_LATIN1</c> for
- ISO Latin-1 (8-bit ASCII).</p>
+ supported encodings are <c>ERL_NIF_LATIN1</c> for
+ ISO Latin-1 (8-bit ASCII) and <c>ERL_NIF_UTF8</c> for UTF-8.</p>
</item>
<tag><marker id="ErlNifSysInfo"/><c>ErlNifSysInfo</c></tag>
<item>
@@ -1395,32 +1457,32 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name since="OTP R13B04"><ret>int</ret><nametext>enif_get_atom(ErlNifEnv* env, ERL_NIF_TERM
- term, char* buf, unsigned size, ErlNifCharEncoding encode)</nametext>
+ <name since="OTP R13B04"><ret>int</ret><nametext>enif_get_atom(ErlNifEnv *env, ERL_NIF_TERM
+ term, char *buf, unsigned size, ErlNifCharEncoding encoding)</nametext>
</name>
<fsummary>Get the text representation of an atom term.</fsummary>
<desc>
<p>Writes a <c>NULL</c>-terminated string in the buffer pointed to by
- <c>buf</c> of size <c>size</c>, consisting of the string
- representation of the atom <c>term</c> with encoding
- <seecref marker="#ErlNifCharEncoding">encode</seecref>.</p>
+ <c>buf</c> of size <c>size</c> bytes, consisting of the string
+ representation of the atom <c>term</c> with
+ <seecref marker="#ErlNifCharEncoding">encoding</seecref>.</p>
<p>Returns the number of bytes written (including terminating
<c>NULL</c> character) or <c>0</c> if <c>term</c> is not an atom with
- maximum length of <c>size-1</c>.</p>
+ maximum length of <c>size-1</c> bytes in <c>encoding</c>.</p>
</desc>
</func>
<func>
- <name since="OTP R14B"><ret>int</ret><nametext>enif_get_atom_length(ErlNifEnv* env,
- ERL_NIF_TERM term, unsigned* len, ErlNifCharEncoding encode)</nametext>
+ <name since="OTP R14B"><ret>int</ret><nametext>enif_get_atom_length(ErlNifEnv *env,
+ ERL_NIF_TERM term, unsigned *len, ErlNifCharEncoding encoding)</nametext>
</name>
<fsummary>Get the length of atom <c>term</c>.</fsummary>
<desc>
<p>Sets <c>*len</c> to the length (number of bytes excluding
terminating <c>NULL</c> character) of the atom <c>term</c> with
- encoding <c>encode</c>.</p>
+ <seecref marker="#ErlNifCharEncoding">encoding</seecref>.</p>
<p>Returns <c>true</c> on success, or <c>false</c> if <c>term</c> is not
- an atom.</p>
+ an atom or if the atom cannot be encoded using <c>encoding</c>.</p>
</desc>
</func>
@@ -1562,13 +1624,13 @@ enif_free_iovec(iovec);]]></code>
<func>
<name since="OTP R13B04"><ret>int</ret><nametext>enif_get_string(ErlNifEnv* env,
ERL_NIF_TERM list, char* buf, unsigned size,
- ErlNifCharEncoding encode)</nametext></name>
+ ErlNifCharEncoding encoding)</nametext></name>
<fsummary>Get a C-string from a list.</fsummary>
<desc>
<p>Writes a <c>NULL</c>-terminated string in the buffer pointed to by
<c>buf</c> with size <c>size</c>, consisting of the characters
- in the string <c>list</c>. The characters are written using encoding
- <seecref marker="#ErlNifCharEncoding">encode</seecref>.</p>
+ in the string <c>list</c>. The characters are written using
+ <seecref marker="#ErlNifCharEncoding">encoding</seecref>.</p>
<p>Returns one of the following:</p>
<list type="bulleted">
<item>The number of bytes written (including terminating <c>NULL</c>
@@ -1576,13 +1638,28 @@ enif_free_iovec(iovec);]]></code>
<item><c>-size</c> if the string was truncated because of buffer
space</item>
<item><c>0</c> if <c>list</c> is not a string that can be encoded
- with <c>encode</c> or if <c>size</c> was &lt; <c>1</c>.</item>
+ with <c>encoding</c> or if <c>size</c> was &lt; <c>1</c>.</item>
</list>
<p>The written string is always <c>NULL</c>-terminated, unless buffer
<c>size</c> is &lt; <c>1</c>.</p>
</desc>
</func>
+
+ <func>
+ <name since="OTP @OTP-18334@"><ret>int</ret><nametext>enif_get_string_length(ErlNifEnv *env,
+ ERL_NIF_TERM list, unsigned *len, ErlNifCharEncoding encoding)</nametext>
+ </name>
+ <fsummary>Get the length of a C-string <c>list</c>.</fsummary>
+ <desc>
+ <p>Sets <c>*len</c> to the length (number of bytes excluding
+ terminating <c>NULL</c> character) of the string <c>list</c> with
+ <seecref marker="#ErlNifCharEncoding">encoding</seecref>.</p>
+ <p>Returns <c>true</c> on success, or <c>false</c> if <c>list</c> is not
+ a string that can be encoded with <c>encoding</c>.</p>
+ </desc>
+ </func>
+
<func>
<name since="OTP R13B04"><ret>int</ret><nametext>enif_get_tuple(ErlNifEnv* env, ERL_NIF_TERM
term, int* arity, const ERL_NIF_TERM** array)</nametext></name>
@@ -2029,7 +2106,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<func>
<name since=""><ret>ERL_NIF_TERM</ret>
- <nametext>enif_make_atom(ErlNifEnv* env, const char* name)</nametext>
+ <nametext>enif_make_atom(ErlNifEnv *env, const char *name)</nametext>
</name>
<fsummary>Create an atom term.</fsummary>
<desc>
@@ -2042,16 +2119,16 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name since="OTP R14B"><ret>ERL_NIF_TERM</ret><nametext>enif_make_atom_len(ErlNifEnv* env,
- const char* name, size_t len)</nametext></name>
+ <name since="OTP R14B"><ret>ERL_NIF_TERM</ret><nametext>enif_make_atom_len(ErlNifEnv *env,
+ const char *name, size_t len)</nametext></name>
<fsummary>Create an atom term.</fsummary>
<desc>
<p>Create an atom term from the string <c>name</c> with length
- <c>len</c>. <c>NULL</c> characters are treated as any other
- characters. If <c>len</c> exceeds the maximum length
- allowed for an atom (255 characters), <c>enif_make_atom</c> invokes
- <seecref marker="#enif_make_badarg">
- <c>enif_make_badarg</c></seecref>.</p>
+ <c>len</c> and ISO Latin-1 encoding. <c>NULL</c> characters are
+ treated as any other characters. If <c>len</c> exceeds the maximum
+ length allowed for an atom (255 characters), <c>enif_make_atom</c>
+ invokes <seecref marker="#enif_make_badarg"><c>enif_make_badarg</c>
+ </seecref>.</p>
</desc>
</func>
@@ -2120,35 +2197,37 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name since="OTP R13B04"><ret>int</ret><nametext>enif_make_existing_atom(ErlNifEnv* env,
- const char* name, ERL_NIF_TERM* atom, ErlNifCharEncoding
- encode)</nametext></name>
+ <name since="OTP R13B04"><ret>int</ret><nametext>enif_make_existing_atom(ErlNifEnv *env,
+ const char *name, ERL_NIF_TERM *atom, ErlNifCharEncoding
+ encoding)</nametext></name>
<fsummary>Create an existing atom term.</fsummary>
<desc>
<p>Tries to create the term of an already existing atom from
- the <c>NULL</c>-terminated C-string <c>name</c> with encoding
- <seecref marker="#ErlNifCharEncoding">encode</seecref>.</p>
+ the <c>NULL</c>-terminated C-string <c>name</c> with
+ <seecref marker="#ErlNifCharEncoding">encoding</seecref>.</p>
<p>If the atom already exists, this function stores the term in
- <c>*atom</c> and returns <c>true</c>, otherwise <c>false</c>.
- Also returns <c>false</c> if the length of <c>name</c> exceeds the
- maximum length allowed for an atom (255 characters).</p>
+ <c>*atom</c> and returns <c>true</c>, otherwise returns <c>false</c>.
+ It also returns <c>false</c> if the string <c>name</c> exceeds the
+ maximum length allowed for an atom (255 characters) or if <c>name</c>
+ is not correctly encoded.</p>
</desc>
</func>
<func>
- <name since="OTP R14B"><ret>int</ret><nametext>enif_make_existing_atom_len(ErlNifEnv* env,
- const char* name, size_t len, ERL_NIF_TERM* atom, ErlNifCharEncoding
+ <name since="OTP R14B"><ret>int</ret><nametext>enif_make_existing_atom_len(ErlNifEnv *env,
+ const char *name, size_t len, ERL_NIF_TERM *atom, ErlNifCharEncoding
encoding)</nametext></name>
<fsummary>Create an existing atom term.</fsummary>
<desc>
<p>Tries to create the term of an already existing atom from the
- string <c>name</c> with length <c>len</c> and encoding
- <seecref marker="#ErlNifCharEncoding">encode</seecref>. <c>NULL</c>
+ string <c>name</c> with length <c>len</c> bytes and
+ <seecref marker="#ErlNifCharEncoding">encoding</seecref>. <c>NULL</c>
characters are treated as any other characters.</p>
<p>If the atom already exists, this function stores the term in
- <c>*atom</c> and returns <c>true</c>, otherwise <c>false</c>.
- Also returns <c>false</c> if <c>len</c> exceeds the maximum length
- allowed for an atom (255 characters).</p>
+ <c>*atom</c> and returns <c>true</c>, otherwise returns <c>false</c>.
+ It also returns <c>false</c> if the string <c>name</c> exceeds the
+ maximum length allowed for an atom (255 characters) or if <c>name</c>
+ is not correctly encoded.</p>
</desc>
</func>
@@ -2316,6 +2395,40 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
+ <name since="OTP @OTP-18334@"><ret>int</ret><nametext>enif_make_new_atom(ErlNifEnv *env,
+ const char *name, ERL_NIF_TERM *atom, ErlNifCharEncoding
+ encoding)</nametext></name>
+ <fsummary>Create a new or existing atom term.</fsummary>
+ <desc>
+ <p>Creates an atom term from the <c>NULL</c>-terminated C-string
+ <c>name</c> with
+ <seecref marker="#ErlNifCharEncoding">encoding</seecref>.</p>
+ <p>If successful, <c>true</c> is returned and the atom term is stored in
+ <c>*atom</c>.</p>
+ <p>Otherwise, <c>false</c> is returned if the length of <c>name</c>
+ exceeds the maximum length allowed for an atom (255 characters)
+ or if <c>name</c> is not correctly encoded.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP @OTP-18334@"><ret>int</ret><nametext>enif_make_new_atom_len(ErlNifEnv *env,
+ const char *name, size_t len, ERL_NIF_TERM *atom, ErlNifCharEncoding
+ encoding)</nametext></name>
+ <fsummary>Create a new or existing atom term.</fsummary>
+ <desc>
+ <p>Create an atom term from string <c>name</c> with
+ length <c>len</c> bytes and
+ <seecref marker="#ErlNifCharEncoding">encoding</seecref>.</p>
+ <p>If successful, <c>true</c> is returned and atom term is stored in
+ <c>*atom</c>.</p>
+ <p>Otherwise, <c>false</c> is returned if the string exceeds the maximum
+ length allowed for an atom (255 characters) or if the string is not
+ correctly encoded.</p>
+ </desc>
+ </func>
+
+ <func>
<name since="OTP R14B"><ret>unsigned char *</ret><nametext>enif_make_new_binary(ErlNifEnv*
env, size_t size, ERL_NIF_TERM* termp)</nametext></name>
<fsummary>Allocate and create a new binary term.</fsummary>
@@ -2452,24 +2565,24 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name since=""><ret>ERL_NIF_TERM</ret><nametext>enif_make_string(ErlNifEnv* env,
- const char* string, ErlNifCharEncoding encoding)</nametext></name>
+ <name since=""><ret>ERL_NIF_TERM</ret><nametext>enif_make_string(ErlNifEnv *env,
+ const char *string, ErlNifCharEncoding encoding)</nametext></name>
<fsummary>Create a string.</fsummary>
<desc>
<p>Creates a list containing the characters of the
- <c>NULL</c>-terminated string <c>string</c> with encoding
+ <c>NULL</c>-terminated string <c>string</c> with
<seecref marker="#ErlNifCharEncoding">encoding</seecref>.</p>
</desc>
</func>
<func>
- <name since="OTP R14B"><ret>ERL_NIF_TERM</ret><nametext>enif_make_string_len(ErlNifEnv*
- env, const char* string, size_t len, ErlNifCharEncoding
+ <name since="OTP R14B"><ret>ERL_NIF_TERM</ret><nametext>enif_make_string_len(ErlNifEnv
+ *env, const char *string, size_t len, ErlNifCharEncoding
encoding)</nametext></name>
<fsummary>Create a string.</fsummary>
<desc>
<p>Creates a list containing the characters of the string <c>string</c>
- with length <c>len</c> and encoding
+ with length <c>len</c> and
<seecref marker="#ErlNifCharEncoding">encoding</seecref>.
<c>NULL</c> characters are treated as any other characters.</p>
</desc>
@@ -3415,6 +3528,146 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
+ <name since="OTP @OTP-17771@">
+ <ret>int</ret><nametext>enif_set_option(ErlNifEnv *env, ErlNifOption opt, ...)</nametext>
+ </name>
+ <fsummary>
+ Set an option.
+ </fsummary>
+ <desc>
+ <marker id="enif_set_option"/>
+ <p>
+ Set an option. On success, zero will be returned. On failure, a non
+ zero value will be returned. Currently the following options can be set:
+ </p>
+ <taglist>
+ <tag><marker id="delay_halt"/>
+ <c>int enif_set_option(ErlNifEnv *env,
+ </c><seecref marker="#ErlNifOption"><c>ERL_NIF_OPT_DELAY_HALT</c></seecref><c>)</c>
+ </tag>
+ <item>
+ <p>
+ Enable delay of runtime system halt with flushing enabled until
+ all calls to NIFs in the NIF library have returned. If the
+ <i>delay halt</i> feature has not been enabled, a halt with
+ flushing enabled may complete even though processes are still
+ executing inside NIFs in the NIF library. Note that by
+ <i>returning</i> we here mean the first point where the NIF
+ returns control back to the runtime system, and <em>not</em> the
+ point where a call to a NIF return a value back to the Erlang
+ code that called the NIF. That is, if you schedule execution of
+ a NIF, using <seecref marker="#enif_schedule_nif">
+ <c>enif_schedule_nif()</c></seecref>, from within a NIF while
+ the system is halting, the scheduled NIF call will <em>not</em>
+ be executed even though <i>delay halt</i> has been enabled for
+ the NIF library.
+ </p>
+ <p>
+ The runtime system halts when one of the
+ <seemfa marker="erlang#halt/0"><c>erlang:halt()</c></seemfa>
+ BIFs are called. By default flushing is enabled, but can be
+ disabled using the
+ <seemfa marker="erlang#halt/2"><c>erlang:halt/2</c></seemfa> BIF.
+ When flushing has been disabled, the <i>delay halt</i> setting
+ will have no effect. That is, the runtime system will halt without
+ waiting for NIFs to return even if the <i>delay halt</i> setting
+ has been enabled. See the
+ <seeerl marker="erlang#halt_flush"><c>{flush, boolean()}</c></seeerl>
+ option of <c>erlang:halt/2</c> for more information.
+ </p>
+ <p>
+ The <c>ERL_NIF_OPT_DELAY_HALT</c> option can only be set during
+ loading of a NIF library in a call to <c>enif_set_option()</c>
+ inside a NIF library
+ <seecref marker="#load"><c>load()</c></seecref> or
+ <seecref marker="#upgrade"><c>upgrade()</c></seecref> call,
+ and will fail if set somewhere else. The <c>env</c>
+ argument <i>must</i> be the callback environment passed to the
+ <c>load()</c> or the <c>upgrade()</c> call. This option can also
+ only be set once. That is, the <i>delay halt</i> setting cannot
+ be changed once it has been enabled. The <i>delay halt</i>
+ setting is tied to the module instance with which the NIF library
+ instance has been loaded. That is, in case both a new and old
+ version of a module using the NIF library are loaded, they can
+ have the same or different <i>delay halt</i> settings.
+ </p>
+ <p>
+ The <i>delay halt</i> feature can be used in combination with an
+ <seecref marker="#on_halt"><i>on halt</i></seecref> callback.
+ The <i>on halt</i> callback is in this case typically used to
+ notify processes blocked in NIFs in the library that it is time
+ to return in order to let the runtime system complete the
+ halting. Such NIFs should be dirty NIFs, since ordinary NIFs
+ should never block for a long time.
+ </p>
+ </item>
+ <tag><marker id="on_halt"/>
+ <c>int enif_set_option(ErlNifEnv *env,
+ </c><seecref marker="#ErlNifOption"><c>ERL_NIF_OPT_ON_HALT</c></seecref><c>,
+ </c><seecref marker="#ErlNifOnHaltCallback"><c>ErlNifOnHaltCallback</c></seecref><c>
+ *on_halt)</c>
+ </tag>
+ <item>
+ <p>
+ Install a callback that will be called when the runtime system
+ halts with flushing enabled.
+ </p>
+ <p>
+ The runtime system halts when one of the
+ <seemfa marker="erlang#halt/0"><c>erlang:halt()</c></seemfa> BIFs
+ are called. By default flushing is enabled, but can be disabled
+ using the
+ <seemfa marker="erlang#halt/2"><c>erlang:halt/2</c></seemfa> BIF.
+ When flushing has been disabled, the runtime system will halt
+ without calling any <i>on halt</i> callbacks even if such are
+ installed. See the
+ <seeerl marker="erlang#halt_flush"><c>{flush, boolean()}</c></seeerl>
+ option of <c>erlang:halt/2</c> for more information.
+ </p>
+ <p>
+ The <c>ERL_NIF_OPT_ON_HALT</c> option can only be set during
+ loading of a NIF library in a call to <c>enif_set_option()</c>
+ inside a NIF library
+ <seecref marker="#load"><c>load()</c></seecref> or
+ <seecref marker="#upgrade"><c>upgrade()</c></seecref> call,
+ and will fail if called somewhere else. The <c>env</c>
+ argument <i>must</i> be the callback environment passed to the
+ <c>load()</c> or the <c>upgrade()</c> call. The <c>on_halt</c>
+ argument should be a function pointer to the callback to install.
+ The <i>on halt</i> callback will be tied to the module instance
+ with which the NIF library instance has been loaded. That is, in
+ case both a new and old version of a module using the NIF library
+ are loaded, they can both have different, none, or the same
+ <i>on halt</i> callbacks installed. When unloading the NIF
+ library during a
+ <seemfa marker="kernel:code#purge/1">code purge</seemfa>, an
+ installed <i>on halt</i> callback will be uninstalled.
+ The <c>ERL_NIF_OPT_ON_HALT</c> option can also only be set
+ once. That is, the <i>on halt</i> callback cannot be changed
+ or removed once it has been installed by any other means than
+ purging the module instance that loaded the NIF library.
+ </p>
+ <p>
+ When the installed <i>on halt</i> callback is called, it will be
+ passed a pointer to <c>priv_data</c> as argument. The
+ <c>priv_data</c> pointer can be set when loading the NIF library.
+ </p>
+ <p>
+ The <i>on halt</i> callback can be used in combination with
+ <seecref marker="#delay_halt"><i>delay of halt</i></seecref> until
+ all calls into the library have returned. The <i>on halt</i>
+ callback is in this case typically used to notify processes
+ blocked in NIFs in the library that it is time to return in order
+ to let the runtime system complete the halting. Such NIFs should
+ be dirty NIFs, since ordinary NIFs should never block for a long
+ time.
+ </p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+
+ <func>
<name since="OTP 22.0"><ret>void</ret>
<nametext>enif_set_pid_undefined(ErlNifPid* pid)</nametext></name>
<fsummary>Set pid as undefined.</fsummary>
diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml
index b6e34a9332..d7fa7b8149 100644
--- a/erts/doc/src/erlang.xml
+++ b/erts/doc/src/erlang.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -790,12 +790,12 @@ client(ServerPid, Request) ->
<c>utf8</c> or
<c>unicode</c>, the characters are encoded using UTF-8 where
characters may require multiple bytes.</p>
- <note>
+ <change>
<p>As from Erlang/OTP 20, atoms can contain any Unicode character
and <c>atom_to_binary(<anno>Atom</anno>, latin1)</c> may fail if the
text representation for <c><anno>Atom</anno></c> contains a Unicode
character &gt; 255.</p>
- </note>
+ </change>
<p>Example:</p>
<pre>
> <input>atom_to_binary('Erlang', latin1).</input>
@@ -874,11 +874,11 @@ client(ServerPid, Request) ->
<c><anno>Binary</anno></c>. If <c><anno>Encoding</anno></c>
is <c>utf8</c> or <c>unicode</c>, the binary must contain
valid UTF-8 sequences.</p>
- <note>
+ <change>
<p>As from Erlang/OTP 20, <c>binary_to_atom(<anno>Binary</anno>, utf8)</c>
is capable of decoding any Unicode character. Earlier versions would
fail if the binary contained Unicode characters &gt; 255.</p>
- </note>
+ </change>
<note>
<p>The number of characters that are permitted in an atom
name is limited. The default limits can be found in the
@@ -1392,7 +1392,7 @@ hello
by passing option <c>{allow_gc, false}</c>.</p>
</item>
</taglist>
- <note>
+ <change>
<p>
Up until ERTS version 8.*, the check process code operation
checks for all types of references to the old code. That is,
@@ -1414,7 +1414,7 @@ hello
and will automatically be enabled if dirty scheduler
support is enabled.
</p>
- </note>
+ </change>
<p>See also <seeerl marker="kernel:code">
<c>code(3)</c></seeerl>.</p>
<p>Failures:</p>
@@ -1589,6 +1589,10 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
headers and the beginning of any following message body.</p>
<p>The variants <c>http_bin</c> and <c>httph_bin</c> return
strings (<c>HttpString</c>) as binaries instead of lists.</p>
+ <p>Since OTP 26.0, <c><anno>Host</anno></c> may be an IPv6
+ address enclosed in <c>[]</c>, as defined in
+ <url href="https://www.ietf.org/rfc/rfc2732.txt">RFC2732
+ </url>.</p>
</item>
</taglist>
<p>Options:</p>
@@ -1677,7 +1681,13 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
<c>demonitor(<anno>MonitorRef</anno>, [flush])</c></seemfa>
can be used instead of <c>demonitor(<anno>MonitorRef</anno>)</c>
if this cleanup is wanted.</p>
- <note>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ </p></note>
+ <change>
<p>Before Erlang/OTP R11B (ERTS 5.5) <c>demonitor/1</c>
behaved completely asynchronously, that is, the monitor was active
until the "demonitor signal" reached the monitored entity. This
@@ -1687,7 +1697,7 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
<p>The current behavior can be viewed as two combined operations:
asynchronously send a "demonitor signal" to the monitored entity
and ignore any future results of the monitor.</p>
- </note>
+ </change>
<p>Failure: It is an error if <c><anno>MonitorRef</anno></c> refers to a
monitoring started by another process. Not all such cases are
cheap to check. If checking is cheap, the call fails with
@@ -1745,9 +1755,9 @@ end</code>
otherwise <c>true</c>.</p>
</item>
</taglist>
- <note>
+ <change>
<p>More options can be added in a future release.</p>
- </note>
+ </change>
<p>Failures:</p>
<taglist>
<tag><c>badarg</c></tag>
@@ -2284,6 +2294,12 @@ example_fun(A1, A2) ->
reasons.
</p>
</warning>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ </p></note>
</desc>
</func>
@@ -2545,6 +2561,15 @@ of Floating Point Numbers</seeguide>.</p>
<c>Fun</c> was statically allocated when module was
loaded (this optimisation is performed for local
functions that do not capture the environment).</p>
+ <change>
+ <p>In Erlang/OTP 27, we plan to change the return value so that
+ it always points to the local <c>init</c> process, regardless
+ of which process or node the fun was originally created on. See
+ <seeguide marker="system/general_info:upcoming_incompatibilities#fun_creator_pid">
+ Upcoming Potential Incompatibilities
+ </seeguide>.
+ </p>
+ </change>
</item>
<tag><c>{index, Index}</c></tag>
<item>
@@ -2638,11 +2663,11 @@ of Floating Point Numbers</seeguide>.</p>
marker="#fun_info/1"><c>erlang:fun_info/1</c></seemfa> for how
to get the environment of a fun.</p>
</note>
- <note>
+ <change>
<p>The output of <c>fun_to_list/1</c> can differ between
Erlang implementations and may change in future
versions.</p>
- </note>
+ </change>
<p>Examples:</p>
<code>
-module(test).
@@ -2917,6 +2942,12 @@ uncompiled code with the same arity are mapped to the same list by
and <seeguide marker="system/design_principles:applications#stopping">OTP
design principles</seeguide> related to starting and stopping
applications.</p>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ </p></note>
</desc>
</func>
@@ -2925,7 +2956,7 @@ uncompiled code with the same arity are mapped to the same list by
<fsummary>Halt the Erlang runtime system and indicate normal exit to
the calling environment.</fsummary>
<desc>
- <p>The same as
+ <p>The same as calling
<seemfa marker="#halt/2"><c>halt(0, [])</c></seemfa>. Example:</p>
<pre>
> <input>halt().</input>
@@ -2934,10 +2965,11 @@ os_prompt%</pre>
</func>
<func>
- <name name="halt" arity="1" since=""/>
+ <name name="halt" arity="1" clause_i="1"
+ anchor="halt_status_code_1" since=""/>
<fsummary>Halt the Erlang runtime system.</fsummary>
<desc>
- <p>The same as <seemfa marker="#halt/2">
+ <p>The same as calling <seemfa marker="#halt/2">
<c>halt(<anno>Status</anno>, [])</c></seemfa>. Example:</p>
<pre>
> <input>halt(17).</input>
@@ -2948,44 +2980,156 @@ os_prompt%</pre>
</func>
<func>
- <name name="halt" arity="2" since="OTP R15B01"/>
+ <name name="halt" arity="1" clause_i="2"
+ anchor="halt_abort_1" since="OTP R15B01"/>
+ <fsummary>Halt the Erlang runtime system by aborting.</fsummary>
+ <desc>
+ <p>
+ The same as calling <seeerl marker="#halt_abort_2">
+ <c>halt(abort, [])</c></seeerl>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="halt" arity="1" clause_i="3"
+ anchor="halt_crash_dump_1" since=""/>
+ <fsummary>
+ Halt the Erlang runtime system and create an Erlang crash dump.
+ </fsummary>
+ <desc>
+ <p>
+ The same as calling <seeerl marker="#halt_crash_dump_2">
+ <c>halt(<anno>CrashDumpSlogan</anno>, [])</c></seeerl>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="halt" arity="2" clause_i="1"
+ anchor="halt_status_code_2" since="OTP R15B01"/>
<fsummary>Halt the Erlang runtime system.</fsummary>
+ <type name="halt_options"/>
<desc>
- <p><c><anno>Status</anno></c> must be a non-negative integer, a string,
- or the atom <c>abort</c>.
- Halts the Erlang runtime system. Has no return value.
- Depending on <c><anno>Status</anno></c>, the following occurs:</p>
+ <p>
+ Halt the runtime system with status code
+ <c><anno>Status</anno></c>.
+ </p>
+ <note>
+ <p>
+ On many platforms, the OS supports only status codes 0-255. A too
+ large status code is truncated by clearing the high bits.
+ </p>
+ </note>
+ <p>
+ Currently the following options are valid:
+ </p>
<taglist>
- <tag>integer()</tag>
- <item>The runtime system exits with integer value
- <c><anno>Status</anno></c>
- as status code to the calling environment (OS).
- <note>
- <p>On many platforms, the OS supports only status
- codes 0-255. A too large status code is truncated by clearing
- the high bits.</p>
- </note>
- </item>
- <tag>string()</tag>
- <item>An Erlang crash dump is produced with <c><anno>Status</anno></c>
- as slogan. Then the runtime system exits with status code <c>1</c>.
- The string will be truncated if longer than 200 characters.
- <note>
- <p>Before ERTS 9.1 (OTP-20.1) only code points in the range 0-255
- was accepted in the string. Now any unicode string is valid.</p>
- </note>
- </item>
- <tag><c>abort</c></tag>
- <item>The runtime system aborts producing a core dump, if that is
- enabled in the OS.
+ <tag><marker id="halt_flush"/><c>{flush, EnableFlushing}</c></tag>
+ <item>
+ <p>
+ If <c>EnableFlushing</c> equals <c>true</c>, which also is the
+ default behavior, the runtime system will perform the following
+ operations before terminating:
+ </p>
+ <list>
+ <item><p>
+ Flush all outstanding output.
+ </p></item>
+ <item><p>
+ Send all Erlang ports exit signals and wait for them
+ to exit.
+ </p></item>
+ <item><p>
+ Wait for all async threads to complete all outstanding
+ async jobs.
+ </p></item>
+ <item><p>
+ Call all installed <seecref marker="erl_nif#on_halt">NIF
+ <i>on halt</i> callbacks</seecref>.
+ </p></item>
+ <item><p>
+ Wait for all ongoing <seecref marker="erl_nif#delay_halt">NIF
+ calls with the <i>delay halt</i> setting</seecref> enabled to
+ return.
+ </p></item>
+ <item><p>
+ Call all installed <c>atexit</c>/<c>on_exit</c> callbacks.
+ </p></item>
+ </list>
+ <p>
+ If <c>EnableFlushing</c> equals <c>false</c>, the runtime system
+ will terminate immediately without performing any of the above
+ listed operations.
+ </p>
+ <change>
+ <p>
+ Runtime systems prior to OTP @OTP-17771@ called all installed
+ <c>atexit</c>/<c>on_exit</c> callbacks also when <c>flush</c>
+ was disabled, but as of OTP @OTP-17771@ this is no longer the case.
+ </p>
+ </change>
</item>
</taglist>
- <p>For integer <c><anno>Status</anno></c>, the Erlang runtime system
- closes all ports and allows async threads to finish their
- operations before exiting. To exit without such flushing, use
- <c><anno>Option</anno></c> as <c>{flush,false}</c>.</p>
- <p>For statuses <c>string()</c> and <c>abort</c>, option
- <c>flush</c> is ignored and flushing is <em>not</em> done.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="halt" arity="2" clause_i="2"
+ anchor="halt_abort_2" since="OTP R15B01"/>
+ <fsummary>Halt the Erlang runtime system by aborting.</fsummary>
+ <type name="halt_options"/>
+ <desc>
+ <p>
+ Halt the Erlang runtime system by aborting and produce a core dump
+ if core dumping has been enabled in the environment that the
+ runtime system is executing in.
+ </p>
+ <note><p>
+ The <seeerl marker="#halt_flush"><c>{flush, boolean()}</c></seeerl>
+ option will be ignored, and flushing will be disabled.
+ </p></note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="halt" arity="2" clause_i="3"
+ anchor="halt_crash_dump_2" since="OTP R15B01"/>
+ <fsummary>
+ Halt the Erlang runtime system and create an Erlang crash dump.
+ </fsummary>
+ <type name="halt_options"/>
+ <desc>
+ <p>
+ Halt the Erlang runtime system and generate an
+ <seeguide marker="crash_dump">Erlang crash dump</seeguide>. The
+ string <c><anno>CrashDumpSlogan</anno></c> will be used as slogan
+ in the Erlang crash dump created. The slogan will be trunkated if
+ <c><anno>CrashDumpSlogan</anno></c> is longer than 1023 characters.
+ </p>
+ <note><p>
+ The <seeerl marker="#halt_flush"><c>{flush, boolean()}</c></seeerl>
+ option will be ignored, and flushing will be disabled.
+ </p></note>
+ <p>
+ Behavior changes compared to earlier versions:
+ </p>
+ <list>
+ <item>
+ <p>
+ Before OTP 24.2, the slogan was truncated if
+ <c><anno>CrashDumpSlogan</anno></c> was longer than 200
+ characters. Now it will be truncated if longer than 1023
+ characters.
+ </p>
+ </item>
+ <item>
+ <p>
+ Before OTP 20.1, only code points in the range 0-255 were
+ accepted in the slogan. Now any Unicode string is valid.
+ </p>
+ </item>
+ </list>
</desc>
</func>
@@ -2994,12 +3138,17 @@ os_prompt%</pre>
<fsummary>Head of a list.</fsummary>
<desc>
<p>Returns the head of <c><anno>List</anno></c>, that is,
- the first element, for example:</p>
+ the first element.</p>
+ <p>It works with improper lists.</p>
+ <p>Examples:</p>
<pre>
> <input>hd([1,2,3,4,5]).</input>
1</pre>
+ <pre>
+> <input>hd([first, second, third, so_on | improper_end]).</input>
+first</pre>
<p>Allowed in guard tests.</p>
- <p>Failure: <c>badarg</c> if <c><anno>List</anno></c> is the empty
+ <p>Failure: <c>badarg</c> if <c><anno>List</anno></c> is an empty
list <c>[]</c>.</p>
</desc>
</func>
@@ -3197,7 +3346,7 @@ os_prompt%</pre>
</list>
<p>A node
can also be alive if it has got a name from a call to <seemfa
- marker="kernel:net_kernel#start/1"><c>net_kernel:start/1</c></seemfa>
+ marker="kernel:net_kernel#start/2"><c>net_kernel:start/2</c></seemfa>
and has not been stopped by a call to <seemfa
marker="kernel:net_kernel#stop/0"><c>net_kernel:stop/0</c></seemfa>.</p>
</desc>
@@ -3547,6 +3696,12 @@ is_process_alive(P2Pid),
chapter of the <i>ERTS User's Guide</i>.
</p>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ </p></note>
<p>Failure:</p>
<list>
<item><c>badarg</c> if <c><anno>PidOrPort</anno></c> does not identify
@@ -4111,6 +4266,8 @@ is_process_alive(P2Pid),
<pre>
> <input>max("abc", "b").</input>
"b"</pre>
+ <p>Allowed in guard tests.</p>
+ <change><p>Allowed in guards tests from Erlang/OTP 26.</p></change>
</desc>
</func>
@@ -4196,7 +4353,7 @@ is_process_alive(P2Pid),
<p>The total amount of memory currently allocated for
the emulator that is not directly related to any Erlang
process. Memory presented as <c>processes</c> is not
- included in this memory. <seeerl marker="tools:instrument">
+ included in this memory. <seeerl marker="runtime_tools:instrument">
<c>instrument(3)</c></seeerl> can be used to
get a more detailed breakdown of what memory is part
of this type.</p>
@@ -4238,7 +4395,7 @@ is_process_alive(P2Pid),
when the emulator is run with instrumentation.</p>
<p>For information on how to run the emulator with
instrumentation, see
- <seeerl marker="tools:instrument">
+ <seeerl marker="runtime_tools:instrument">
<c>instrument(3)</c></seeerl>
and/or <seecom marker="erl"><c>erl(1)</c></seecom>.</p>
</item>
@@ -4286,11 +4443,11 @@ RealSystem = system + MissedSystem</code>
larger than the total size of the dynamically allocated
memory blocks.</p>
</note>
- <note>
+ <change>
<p>As from ERTS 5.6.4, <c>erlang:memory/0</c> requires that
all <seecref marker="erts:erts_alloc"><c>erts_alloc(3)</c></seecref>
allocators are enabled (default behavior).</p>
- </note>
+ </change>
<p>Failure: <c>notsup</c> if an
<seecref marker="erts:erts_alloc"><c>erts_alloc(3)</c></seecref>
allocator has been disabled.</p>
@@ -4307,12 +4464,12 @@ RealSystem = system + MissedSystem</code>
<c><anno>Type</anno></c>. The argument can also be specified as a list
of <c>memory_type()</c> atoms, in which case a corresponding list of
<c>{memory_type(), Size :: integer >= 0}</c> tuples is returned.</p>
- <note>
+ <change>
<p>As from ERTS 5.6.4,
<c>erlang:memory/1</c> requires that
all <seecref marker="erts_alloc"><c>erts_alloc(3)</c></seecref>
allocators are enabled (default behavior).</p>
- </note>
+ </change>
<p>Failures:</p>
<taglist>
<tag><c>badarg</c></tag>
@@ -4363,6 +4520,8 @@ RealSystem = system + MissedSystem</code>
<pre>
> <input>min("abc", "b").</input>
"abc"</pre>
+ <p>Allowed in guard tests.</p>
+ <change><p>Allowed in guards tests from Erlang/OTP 26.</p></change>
</desc>
</func>
@@ -4456,7 +4615,7 @@ RealSystem = system + MissedSystem</code>
a tuple <c>{RegisteredName, Node}</c> for a registered process,
located elsewhere.</p>
- <note><p>Before ERTS 10.0 (OTP 21.0), monitoring a process could fail with
+ <change><p>Before ERTS 10.0 (OTP 21.0), monitoring a process could fail with
<c>badarg</c> if the monitored process resided on a primitive node
(such as erl_interface or jinterface), where remote process monitoring
is not implemented.</p>
@@ -4465,7 +4624,7 @@ RealSystem = system + MissedSystem</code>
connection. That is, a <c>{'DOWN', _, process, _, noconnection}</c> is
the only message that may be received, as the primitive node have no
way of reporting the status of the monitored process.</p>
- </note>
+ </change>
</item>
@@ -4553,6 +4712,12 @@ RealSystem = system + MissedSystem</code>
possible values for <c>Tag</c>, <c>Object</c>, and
<c>Info</c> in the monitor message will be introduced.</p>
</note>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ </p></note>
</desc>
</func>
@@ -5203,7 +5368,7 @@ receive_replies(ReqId, N, Acc) ->
<c><anno>Val</anno></c> must be strings. The one
exception is <c><anno>Val</anno></c> being the atom
<c>false</c> (in analogy with
- <seemfa marker="kernel:os#getenv/1"><c>os:getenv/1</c></seemfa>,
+ <seemfa marker="kernel:os#getenv/1"><c>os:getenv/1</c></seemfa>),
which removes the environment variable.
</p>
<p>
@@ -5691,9 +5856,9 @@ receive_replies(ReqId, N, Acc) ->
<c>false</c> is returned.
</item>
</taglist>
- <note>
+ <change>
<p>More options can be added in a future release.</p>
- </note>
+ </change>
<p>Failures:</p>
<taglist>
<tag><c>badarg</c></tag>
@@ -6184,7 +6349,7 @@ receive_replies(ReqId, N, Acc) ->
</p></note>
<p>
Blocking due to disabled <c>async_dist</c> can be monitored by
- <seemfa marker="#system_monitor/2"><c>erlang:system_montor()</c></seemfa>
+ <seemfa marker="#system_monitor/2"><c>erlang:system_monitor()</c></seemfa>
using the
<seeerl marker="#busy_dist_port"><c>busy_dist_port</c></seeerl>
option. Only data buffered by processes which (at the time of sending
@@ -6346,6 +6511,26 @@ receive_replies(ReqId, N, Acc) ->
or <seeerl marker="#system_flag_max_heap_size">
<c>erlang:system_flag(max_heap_size, MaxHeapSize)</c></seeerl>.</p>
</item>
+ <tag><c>include_shared_binaries</c></tag>
+ <item>
+ <p>
+ When set to <c>true</c>, off-heap binaries are included in the
+ total sum compared against the <c>size</c> limit. Off-heap binaries
+ are typically larger binaries that may be shared between
+ processes. The size of a shared binary is included by all
+ processes that are referring it. Also, the entire size of a large
+ binary may be included even if only a smaller part of it is
+ referred by the process.
+ </p>
+ <p>
+ If <c>include_shared_binaries</c> is not defined in the map, the
+ system default is used. The default system default is <c>false</c>.
+ It can be changed by either the option
+ <seecom marker="erl#+hmaxib">+hmaxib</seecom> in <c>erl(1)</c>,
+ or <seeerl marker="#system_flag_max_heap_size">
+ <c>erlang:system_flag(max_heap_size, MaxHeapSize)</c></seeerl>.
+ </p>
+ </item>
</taglist>
<p>The heap size of a process is quite hard to predict, especially the
amount of memory that is used during the garbage collection. When
@@ -6998,12 +7183,12 @@ receive_replies(ReqId, N, Acc) ->
<seeerl marker="kernel:code"><c>code(3)</c></seeerl>)
and is not to be used elsewhere.</p>
</warning>
- <note>
+ <change>
<p>As from ERTS 8.0 (Erlang/OTP 19), any lingering processes
that still execute the old code is killed by this function.
In earlier versions, such incorrect use could cause much
more fatal failures, like emulator crash.</p>
- </note>
+ </change>
<p>Failure: <c>badarg</c> if there is no old code for
<c><anno>Module</anno></c>.</p>
</desc>
@@ -7313,6 +7498,12 @@ true</pre>
<c><anno>Dest</anno></c> is an atom name, but this name is not
registered. This is the only case when <c>send</c> fails for an
unreachable destination <c><anno>Dest</anno></c> (of correct type).</p>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ </p></note>
</desc>
</func>
@@ -7340,6 +7531,12 @@ true</pre>
instead.
</item>
</taglist>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ </p></note>
<warning>
<p>As with <c>erlang:send_nosuspend/2,3</c>: use with extreme
care.</p>
@@ -8299,6 +8496,12 @@ true</pre>
A spawn request can be abandoned by calling
<seemfa marker="#spawn_request_abandon/1"><c>spawn_request_abandon/1</c></seemfa>.
</p>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ </p></note>
</desc>
</func>
@@ -8719,13 +8922,13 @@ lists:map(
<pre>
> <input>statistics(reductions).</input>
{2046,11}</pre>
- <note><p>As from ERTS 5.5 (Erlang/OTP R11B),
+ <change><p>As from ERTS 5.5 (Erlang/OTP R11B),
this value does not include reductions performed in current
time slices of currently scheduled processes. If an
exact value is wanted, use
<seeerl marker="#statistics_exact_reductions">
<c>statistics(exact_reductions)</c></seeerl>.</p>
- </note>
+ </change>
</desc>
</func>
@@ -9390,7 +9593,7 @@ ok
<p>
Sets the default maximum heap size settings for processes.
The size is specified in words. The new <c>max_heap_size</c>
- effects only processes spawned efter the change has been made.
+ effects only processes spawned after the change has been made.
<c>max_heap_size</c> can be set for individual processes using
<seemfa marker="#spawn_opt/4"><c>spawn_opt/2,3,4</c></seemfa> or
<seeerl marker="#process_flag_max_heap_size">
@@ -10293,8 +10496,10 @@ Metadata = #{ pid => pid(),
system-wide maximum heap size settings for spawned processes.
This setting can be set using the command-line flags
<seecom marker="erl#+hmax"><c>+hmax</c></seecom>,
- <seecom marker="erl#+hmaxk"><c>+hmaxk</c></seecom> and
- <seecom marker="erl#+hmaxel"><c>+hmaxel</c></seecom> in
+ <seecom marker="erl#+hmaxk"><c>+hmaxk</c></seecom>,
+ <seecom marker="erl#+hmaxel"><c>+hmaxel</c></seecom> and
+ <seecom marker="erl#+hmaxib"><c>+hmaxibl</c></seecom> in
+
<c>erl(1)</c>. It can also be changed at runtime using
<seeerl marker="#system_flag_max_heap_size">
<c>erlang:system_flag(max_heap_size, MaxHeapSize)</c></seeerl>.
@@ -10978,14 +11183,13 @@ Metadata = #{ pid => pid(),
<tag><marker id="system_info_creation"/>
<c>creation</c></tag>
<item>
- <p>Returns the creation of the local node as an integer.
+ <p>Returns the "creation" value of the local node as an integer.
The creation is changed when a node is restarted. The
creation of a node is stored in process identifiers, port
- identifiers, and references. This makes it (to some
- extent) possible to distinguish between identifiers from
- different incarnations of a node. The valid
- creations are integers in the range 1..3, but this will
- probably change in a future release. If the node is not
+ identifiers, and references. This makes it possible to distinguish
+ between identifiers from different incarnations of a node.
+ Creation values are currently 32-bit positive integers, but this
+ may change in future releases. If the node is not
alive, <c>0</c> is returned.</p>
</item>
<tag><marker id="system_info_delayed_node_table_gc"/>
@@ -11693,71 +11897,192 @@ hello
<desc>
<p>Returns a binary data object that is the result of encoding
<c><anno>Term</anno></c> according to the Erlang external
- term format.</p>
- <p>If option <c>compressed</c> is provided, the external term
- format is compressed. The compressed format is automatically
- recognized by <c>binary_to_term/1</c> as from Erlang/OTP R7B.</p>
- <p>A compression level can be specified by giving option
- <c>{compressed, <anno>Level</anno>}</c>.
- <c><anno>Level</anno></c> is an integer
- with range 0..9, where:</p>
- <list type="bulleted">
- <item><p><c>0</c> - No compression is done (it is the same as
- giving no <c>compressed</c> option).</p></item>
- <item><p><c>1</c> - Takes least time but may not compress
- as well as the higher levels.</p></item>
- <item><p><c>6</c> - Default level when option <c>compressed</c>
- is provided.</p></item>
- <item><p><c>9</c> - Takes most time and tries to produce a smaller
- result. Notice "tries" in the preceding sentence; depending
- on the input term, level 9 compression either does or does
- not produce a smaller result than level 1 compression.</p></item>
- </list>
- <p>Option <c>{minor_version, <anno>Version</anno>}</c>
- can be used to control some
- encoding details. This option was introduced in Erlang/OTP R11B-4.
- The valid values for <c><anno>Version</anno></c> are:</p>
- <taglist>
- <tag><c>0</c></tag>
- <item>
- <p>Floats are encoded using a textual representation.
- This option is useful to ensure that releases before Erlang/OTP
- R11B-4 can decode resulting binary.</p>
- <p>This version encode atoms that can be represented by a
- latin1 string using latin1 encoding while only atoms that
- cannot be represented by latin1 are encoded using utf8.</p>
- </item>
- <tag><c>1</c></tag>
- <item>
- <p>This is as of Erlang/OTP 17.0 the <em>default</em>. It forces any floats
- in the term to be encoded in a more space-efficient and exact way
- (namely in the 64-bit IEEE format, rather than converted to a
- textual representation). As from Erlang/OTP R11B-4,
- <c>binary_to_term/1</c> can decode this representation.</p>
- <p>This version encode atoms that can be represented by a
- latin1 string using latin1 encoding while only atoms that
- cannot be represented by latin1 are encoded using utf8.</p>
- </item>
- <tag><c>2</c></tag>
- <item>
- <p>Drops usage of the latin1 atom encoding and unconditionally
- use utf8 encoding for all atoms. Erlang/OTP
- systems as of R16B can decode this representation.</p>
- <note>
- <p>In Erlang/OTP 26, the default <c>minor_version</c> is planned
- to change from 1 to 2. See
- <seeguide marker="system/general_info:upcoming_incompatibilities#atoms_be_utf8">
- Upcoming Potential Incompatibilities
- </seeguide>.
- </p>
- </note>
- </item>
- </taglist>
- <p>Option <c>deterministic</c> (introduced in OTP 24.1) can
- be used to ensure that within the same major release of Erlang/OTP,
- the same encoded representation is returned for the same
- term. There is still no guarantee that the encoded representation
- remains the same between major releases of Erlang/OTP.</p>
+ term format.</p>
+ <p>Currently supported options:</p>
+ <taglist>
+ <tag><c>compressed</c></tag>
+ <item>
+ <p>
+ Compress the external term format. The compressed
+ format is automatically recognized by <c>binary_to_term/1</c>
+ as from Erlang/OTP R7B.
+ </p>
+ </item>
+ <tag><c>{compressed, <anno>Level</anno>}</c></tag>
+ <item>
+ <p>
+ Compress the external term format to a given level.
+ The compression level is specified by <c><anno>Level</anno></c>
+ which is an integer in the range 0..9, where:
+ </p>
+ <taglist>
+ <tag><c>0</c></tag>
+ <item><p>
+ No compression is done (it is the same as giving no
+ <c>compressed</c> option).
+ </p></item>
+ <tag><c>1</c></tag>
+ <item><p>
+ Takes least time but may not compress as well as the higher
+ levels.
+ </p></item>
+ <tag><c>6</c></tag>
+ <item><p>
+ Default level when option <c>compressed</c> is provided.
+ </p></item>
+ <tag><c>9</c></tag>
+ <item><p>
+ Takes most time and tries to produce a smaller result.
+ Notice "tries" in the preceding sentence; depending
+ on the input term, level 9 compression either does or does
+ not produce a smaller result than level 1 compression.
+ </p></item>
+ </taglist>
+ </item>
+ <tag since="R11B-4"><c>{minor_version, <anno>Version</anno>}</c></tag>
+ <item>
+ <p>
+ The option can be used to control some encoding details. Valid
+ values for <c><anno>Version</anno></c> are:
+ </p>
+ <taglist>
+ <tag><c>0</c></tag>
+ <item>
+ <p>Floats are encoded using a textual representation.</p>
+ <p>Atoms that can be represented by a latin1 string are encoded
+ using latin1 while only atoms that cannot be represented by latin1
+ are encoded using utf8.</p>
+ </item>
+ <tag><c>1</c></tag>
+ <item>
+ <p>Floats are encoded in a more space-efficient and exact way
+ (namely in the 64-bit IEEE format, rather than converted to a
+ textual representation). As from Erlang/OTP R11B-4,
+ <c>binary_to_term/1</c> can decode this representation.</p>
+ <p>Atoms that can be represented by a latin1 string are encoded
+ using latin1 while only atoms that cannot be represented by latin1
+ are encoded using utf8.</p>
+ </item>
+ <tag><c>2</c></tag>
+ <item>
+ <p>This is as of Erlang/OTP 26.0 the <em>default</em>. Atoms are
+ unconditionally encoded using utf8. Erlang/OTP systems as of R16B
+ can decode this representation.</p>
+ </item>
+ </taglist>
+ </item>
+ <tag since="OTP 24.1"><c>deterministic</c></tag>
+ <item>
+ <p>
+ This option can be used to ensure that, within the same major
+ release of Erlang/OTP, the same encoded representation is returned
+ for the same term. There is still no guarantee that the encoded
+ representation remains the same between major releases of
+ Erlang/OTP.
+ </p>
+ <p>
+ This option cannot be combined with the <c>local</c> option.
+ </p>
+ </item>
+ <tag since="OTP @OTP-18477@"><c>local</c>
+ <marker id="term_to_binary_local"/></tag>
+ <item>
+ <p>
+ This option will cause encoding of <c><anno>Term</anno></c>
+ to an alternative local version of the external term format which
+ when decoded by the same runtime system instance will produce a
+ term identical to the encoded term even when the node name and/or
+ <seeerl marker="#system_info_creation">creation</seeerl> of the
+ current runtime system instance have changed between encoding
+ and decoding. When encoding without the <c>local</c> option,
+ local identifiers such as <seetype marker="#pid">pids</seetype>,
+ <seetype marker="#port">ports</seetype> and
+ <seetype marker="#reference">references</seetype> will not
+ be the same if node name and/or creation of the current runtime
+ system instance changed between encoding and decoding. This since
+ such identifiers refer to a specific node by node name and
+ creation.
+ </p>
+ <p>
+ Node name and creation of a runtime system instance change
+ when the distribution is started or stopped. The distribution is
+ started when the runtime system is started using the
+ <seecom marker="erl#name"><c>-name</c></seecom> or
+ <seecom marker="erl#sname"><c>-sname</c></seecom> command line
+ arguments. Note that the actual start of the distribution happens
+ after other code in the startup phase has begun executing. The
+ distribution can also be started by calling
+ <seemfa marker="kernel:net_kernel#start/2"><c>net_kernel:start/2</c></seemfa>
+ and stopped by calling
+ <seemfa marker="kernel:net_kernel#stop/0"><c>net_kernel:stop/1</c></seemfa>
+ if it has not been started via the command line.
+ </p>
+ <p>
+ The decoding of a term encoded with the <c>local</c> option,
+ using for example
+ <seemfa marker="#term_to_binary/1"><c>binary_to_term()</c></seemfa>,
+ will try to verify that the term actually was encoded by the same
+ runtime system instance, and will in the vast majority of cases
+ fail if the encoding was performed by another runtime system
+ instance. You should however <em>not</em> trust that this
+ verification will work in all cases. You <em>should</em> make
+ sure to <em>only</em> decode terms encoded with the <c>local</c>
+ option on the same Erlang runtime system instance as the one that
+ encoded the terms.
+ </p>
+ <p>
+ Since it is only the runtime system that encoded a term using
+ the <c>local</c> option that can decode it, the local encoding
+ is typically pieced together with something else to produce a
+ reply to where the <c>local</c> encoding originates from.
+ If a term encoded using the <c>local</c> option is stripped of
+ its leading version number, it can be added as part
+ of a larger term (for example as an element in a tuple) when
+ encoding on the external term format using, for example,
+ <seecref marker="erl_interface:ei">ei</seecref>. In the <c>ei</c>
+ case, you would strip it of the version number using
+ <seecref marker="erl_interface:ei#ei_decode_version"><c>ei_decode_version()</c></seecref>
+ and then add the remaining local encoding to what you are encoding
+ using for example
+ <seecref marker="erl_interface:ei#ei_x_append_buf"><c>ei_x_append_buf()</c></seecref>.
+ </p>
+ <p>
+ A good example of when you want to use the <c>local</c> option, is
+ when you want to make a request from a process to a port
+ <seecref marker="erl_driver">driver</seecref> and utilize the
+ <seeguide marker="system/efficiency_guide:processes#receiving-messages">selective
+ receive optimization</seeguide> when receiving the reply. In
+ this scenario you want to create a reference, serialize the
+ reference on the external term format using the <c>local</c>
+ option, pass this to the driver in the request, and then wait
+ for the reply message in a selective receive matching on the
+ reference. The driver should send the reply using either
+ <seecref marker="erl_driver#erl_drv_output_term"><c>erl_drv_output_term()</c></seecref>
+ or
+ <seecref marker="erl_driver#erl_drv_send_term"><c>erl_drv_send_term()</c></seecref>
+ using the term type
+ <seecref marker="erl_driver#ERL_DRV_EXT2TERM"><c>ERL_DRV_EXT2TERM</c></seecref>
+ for the, in the request, previously received reference on the
+ external term format. Note that you should not strip the
+ leading version number from the local encoding when using the
+ term type <c>ERL_DRV_EXT2TERM</c> of this functionality. If you
+ in this example do not encode the reference using the <c>local</c>
+ option, and the distribution is started or stopped while the
+ request is ongoing, the process that made the request will hang
+ indefinitely since the reference in the reply message will never
+ match.
+ </p>
+ <p>
+ This option cannot be combined with the <c>deterministic</c>
+ option.
+ </p>
+ <p>
+ For more information see the
+ <seeguide marker="erl_ext_dist#LOCAL_EXT"><c>LOCAL_EXT</c></seeguide>
+ tag in the documentation of the external term format.
+ </p>
+ </item>
+ </taglist>
<p>See also <seemfa marker="#binary_to_term/1">
<c>binary_to_term/1</c></seemfa>.</p>
</desc>
@@ -11954,7 +12279,9 @@ timestamp() ->
<fsummary>Tail of a list.</fsummary>
<desc>
<p>Returns the tail of <c><anno>List</anno></c>, that is,
- the list minus the first element, for example:</p>
+ the list minus the first element</p>
+ <p>It works with improper lists.</p>
+ <p>Examples:</p>
<pre>
> <input>tl([geesties, guilies, beasties]).</input>
[guilies, beasties]</pre>
@@ -11969,7 +12296,7 @@ timestamp() ->
improper_end</pre>
<p>Allowed in guard tests.</p>
<p>Failure: <c>badarg</c> if <c><anno>List</anno></c>
- is the empty list <c>[]</c>.</p>
+ is an empty list <c>[]</c>.</p>
</desc>
</func>
@@ -12797,6 +13124,21 @@ improper_end</pre>
<seemfa marker="#trace_pattern/3">
<c>erlang:trace_pattern/3</c></seemfa>.</p>
</item>
+ <tag><c>call_memory</c></tag>
+ <item>
+ <p>Returns the accumulated number of words allocated by this function.
+ Accumulation stops at the next memory traced function: if there
+ are <c>outer</c>, <c>middle</c> and <c>inner</c> functions each
+ allocating 3 words, but only <c>outer</c> is traced, it will report
+ 9 allocated words. If <c>outer</c> and <c>inner</c> are traced,
+ 6 words are reported for <c>outer</c> and 3 for <c>inner</c>.
+ When function is not traced, <c>false</c> is returned.
+ Returned tuple is <c>[{Pid, Count, Words}]</c>,
+ for each process that executed the function.</p>
+ <p>See also
+ <seemfa marker="#trace_pattern/3">
+ <c>erlang:trace_pattern/3</c></seemfa>.</p>
+ </item>
<tag><c>all</c></tag>
<item>
<p>Returns a list containing the
@@ -13099,15 +13441,15 @@ improper_end</pre>
</item>
<tag><c>restart</c></tag>
<item>
- <p>For the <c><anno>FlagList</anno></c> options <c>call_count</c>
- and <c>call_time</c>: restarts
+ <p>For the <c><anno>FlagList</anno></c> options <c>call_count</c>,
+ <c>call_time</c> and <c>call_memory</c>: restarts
the existing counters. The behavior is undefined
for other <c><anno>FlagList</anno></c> options.</p>
</item>
<tag><c>pause</c></tag>
<item>
<p>For the <c><anno>FlagList</anno></c> options
- <c>call_count</c> and <c>call_time</c>: pauses
+ <c>call_count</c>, <c>call_time</c> and <c>call_memory</c>: pauses
the existing counters. The behavior is undefined for
other <c><anno>FlagList</anno></c> options.</p>
</item>
@@ -13183,6 +13525,20 @@ improper_end</pre>
<seemfa marker="#trace_info/2">
<c>erlang:trace_info/2</c></seemfa>.</p>
</item>
+ <tag><c>call_memory</c></tag>
+ <item>
+ <p>Starts (<c><anno>MatchSpec</anno> == true</c>) or stops
+ (<c><anno>MatchSpec</anno> == false</c>) call memory
+ tracing for all types of function calls.</p>
+ <p>If call memory tracing is started while already running,
+ counters and allocations restart from zero. To pause
+ running counters, use <c><anno>MatchSpec</anno> == pause</c>.
+ Paused and running counters can be restarted from zero with
+ <c><anno>MatchSpec</anno> == restart</c>.</p>
+ <p>To read the counter value, use
+ <seemfa marker="#trace_info/2">
+ <c>erlang:trace_info/2</c></seemfa>.</p>
+ </item>
</taglist>
<p>The options <c>global</c> and <c>local</c> are mutually
exclusive, and <c>global</c> is the default (if no options are
@@ -13504,7 +13860,12 @@ end</code>
protocol</seeguide> can be found in the <i>Distribution Protocol</i>
chapter of the <i>ERTS User's Guide</i>.
</p>
-
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ </p></note>
<p>
Failure: <c>badarg</c> if <c>Id</c> does not identify a process
or a node local port.
diff --git a/erts/doc/src/erlsrv_cmd.xml b/erts/doc/src/erlsrv_cmd.xml
index e8f066b21b..4973a163e4 100644
--- a/erts/doc/src/erlsrv_cmd.xml
+++ b/erts/doc/src/erlsrv_cmd.xml
@@ -4,7 +4,7 @@
<comref>
<header>
<copyright>
- <year>1998</year><year>2021</year>
+ <year>1998</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -112,8 +112,7 @@
<item>
<p>The location of the Erlang emulator.
The default is the <c><![CDATA[erl.exe]]></c> located in the same
- directory as <c>erlsrv.exe</c>. Do not specify
- <c><![CDATA[werl.exe]]></c> as this emulator, it will not work.</p>
+ directory as <c>erlsrv.exe</c>.</p>
<p>If the system uses release handling, this is to be set to a
program similar to <c><![CDATA[start_erl.exe]]></c>.</p>
</item>
diff --git a/erts/doc/src/erts_alloc.xml b/erts/doc/src/erts_alloc.xml
index da293f9c45..914c936095 100644
--- a/erts/doc/src/erts_alloc.xml
+++ b/erts/doc/src/erts_alloc.xml
@@ -4,7 +4,7 @@
<cref>
<header>
<copyright>
- <year>2002</year><year>2022</year>
+ <year>2002</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -573,7 +573,7 @@
<item>
<p>Adds a small tag to each allocated block that contains basic
information about what it is and who allocated it. Use the
- <seeerl marker="tools:instrument"><c>instrument</c></seeerl>
+ <seeerl marker="runtime_tools:instrument"><c>instrument</c></seeerl>
module to inspect this information.</p>
<p>The runtime overhead is one word per allocation when enabled. This
@@ -832,18 +832,6 @@
<p>Configures all allocators as they were configured in respective
Erlang/OTP release. These will eventually be removed.</p>
</item>
- <tag><c>config</c></tag>
- <item>
- <p>Disables features that cannot be enabled while creating an
- allocator configuration with
- <seeerl marker="runtime_tools:erts_alloc_config">
- <c>erts_alloc_config(3)</c></seeerl>.</p>
- <note>
- <p>This option is to be used only while running
- <c>erts_alloc_config(3)</c>, <em>not</em> when
- using the created configuration.</p>
- </note>
- </item>
</taglist>
</item>
<tag><marker id="Mlpm"/><c>+Mlpm all|no</c></tag>
@@ -899,21 +887,13 @@
<p><c>erts_alloc</c> is not obliged to strictly use the settings that
have been passed to it (it can even ignore them).</p>
</note>
-
- <p>The <seeerl marker="runtime_tools:erts_alloc_config">
- <c>erts_alloc_config(3)</c></seeerl>
- tool can be used to aid creation of an
- <c>erts_alloc</c> configuration that is suitable for a limited
- number of runtime scenarios.</p>
</section>
<section>
<title>See Also</title>
<p><seecom marker="erl"><c>erl(1)</c></seecom>,
<seeerl marker="erlang"><c>erlang(3)</c></seeerl>,
- <seeerl marker="runtime_tools:erts_alloc_config">
- <c>erts_alloc_config(3)</c></seeerl>,
- <seeerl marker="tools:instrument">
+ <seeerl marker="runtime_tools:instrument">
<c>instrument(3)</c></seeerl></p>
</section>
</cref>
diff --git a/erts/doc/src/escript_cmd.xml b/erts/doc/src/escript_cmd.xml
index 78b121ee97..06c245006b 100644
--- a/erts/doc/src/escript_cmd.xml
+++ b/erts/doc/src/escript_cmd.xml
@@ -118,9 +118,10 @@ $ <input>escript factorial 5</input></pre>
applies to the script itself. The encoding of the
I/O-server, however, must be set explicitly as follows:</p>
<code>
-io:setopts([{encoding, unicode}])</code>
+io:setopts([{encoding, latin1}])</code>
<p>The default encoding of the I/O-server for <c>standard_io</c>
- is <c>latin1</c>, as the script runs in a non-interactive terminal
+ is <c>unicode</c> if its supported, as the script runs in a
+ non-interactive terminal.
(see section
<seeguide marker="stdlib:unicode_usage#unicode_options_summary">
Summary of Options</seeguide>) in the STDLIB User's Guide.</p>
diff --git a/erts/doc/src/match_spec.xml b/erts/doc/src/match_spec.xml
index 988f895dde..a62eef43c4 100644
--- a/erts/doc/src/match_spec.xml
+++ b/erts/doc/src/match_spec.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1999</year><year>2022</year>
+ <year>1999</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -87,7 +87,8 @@
<c><![CDATA[is_pid]]></c> | <c><![CDATA[is_port]]></c> |
<c><![CDATA[is_reference]]></c> | <c><![CDATA[is_tuple]]></c> |
<c><![CDATA[is_map]]></c> | <c><![CDATA[is_map_key]]></c> |
- <c><![CDATA[is_binary]]></c> | <c><![CDATA[is_function]]></c> |
+ <c><![CDATA[is_binary]]></c> | <c><![CDATA[is_bitstring]]></c> |
+ <c><![CDATA[is_boolean]]></c> | <c><![CDATA[is_function]]></c> |
<c><![CDATA[is_record]]></c> | <c><![CDATA[is_seq_trace]]></c> |
<c><![CDATA['and']]></c> | <c><![CDATA['or']]></c> |
<c><![CDATA['not']]></c> | <c><![CDATA['xor']]></c> |
@@ -111,11 +112,14 @@
<item>GuardFunction ::= BoolFunction | <c><![CDATA[abs]]></c> |
<c><![CDATA[element]]></c> | <c><![CDATA[hd]]></c> |
<c><![CDATA[length]]></c> | <c><![CDATA[map_get]]></c> |
- <c><![CDATA[map_size]]></c> | <c><![CDATA[node]]></c> |
- <c><![CDATA[round]]></c> | <c><![CDATA[size]]></c> |
+ <c><![CDATA[map_size]]></c> |
+ <c><![CDATA[max]]></c> | <c><![CDATA[min]]></c> |
+ <c><![CDATA[node]]></c> | <c><![CDATA[float]]></c> |
+ <c><![CDATA[round]]></c> | <c><![CDATA[floor]]></c> |
+ <c><![CDATA[ceil]]></c> | <c><![CDATA[size]]></c> |
<c><![CDATA[bit_size]]></c> | <c><![CDATA[byte_size]]></c> |
- <c><![CDATA[tl]]></c> | <c><![CDATA[trunc]]></c> |
- <c><![CDATA[binary_part]]></c> |
+ <c><![CDATA[tuple_size]]></c> | <c><![CDATA[tl]]></c> |
+ <c><![CDATA[trunc]]></c> | <c><![CDATA[binary_part]]></c> |
<c><![CDATA['+']]></c> | <c><![CDATA['-']]></c> |
<c><![CDATA['*']]></c> | <c><![CDATA['div']]></c> |
<c><![CDATA['rem']]></c> | <c><![CDATA['band']]></c> |
@@ -140,7 +144,7 @@
<c><![CDATA[process_dump]]></c> | <c><![CDATA[enable_trace]]></c> |
<c><![CDATA[disable_trace]]></c> | <c><![CDATA[trace]]></c> |
<c><![CDATA[display]]></c> | <c><![CDATA[caller]]></c> |
- <c><![CDATA[caller_line]]></c> |
+ <c><![CDATA[caller_line]]></c> | <c><![CDATA[current_stacktrace]]></c> |
<c><![CDATA[set_tcw]]></c> | <c><![CDATA[silent]]></c>
</item>
</list>
@@ -171,8 +175,9 @@
<c><![CDATA[is_list]]></c> | <c><![CDATA[is_number]]></c> |
<c><![CDATA[is_pid]]></c> | <c><![CDATA[is_port]]></c> |
<c><![CDATA[is_reference]]></c> | <c><![CDATA[is_tuple]]></c> |
- <c><![CDATA[is_map]]></c> | <c><![CDATA[map_is_key]]></c> |
- <c><![CDATA[is_binary]]></c> | <c><![CDATA[is_function]]></c> |
+ <c><![CDATA[is_map]]></c> | <c><![CDATA[is_map_key]]></c> |
+ <c><![CDATA[is_binary]]></c> | <c><![CDATA[is_bitstring]]></c> |
+ <c><![CDATA[is_boolean]]></c> | <c><![CDATA[is_function]]></c> |
<c><![CDATA[is_record]]></c> | <c><![CDATA['and']]></c> |
<c><![CDATA['or']]></c> | <c><![CDATA['not']]></c> |
<c><![CDATA['xor']]></c> | <c><![CDATA['andalso']]></c> |
@@ -195,11 +200,14 @@
<item>GuardFunction ::= BoolFunction | <c><![CDATA[abs]]></c> |
<c><![CDATA[element]]></c> | <c><![CDATA[hd]]></c> |
<c><![CDATA[length]]></c> | <c><![CDATA[map_get]]></c> |
- <c><![CDATA[map_size]]></c> | <c><![CDATA[node]]></c> |
- <c><![CDATA[round]]></c> | <c><![CDATA[size]]></c> |
+ <c><![CDATA[map_size]]></c> |
+ <c><![CDATA[max]]></c> | <c><![CDATA[min]]></c> |
+ <c><![CDATA[node]]></c> | <c><![CDATA[float]]></c> |
+ <c><![CDATA[round]]></c> | <c><![CDATA[floor]]></c> |
+ <c><![CDATA[ceil]]></c> | <c><![CDATA[size]]></c> |
<c><![CDATA[bit_size]]></c> | <c><![CDATA[byte_size]]></c> |
- <c><![CDATA[tl]]></c> | <c><![CDATA[trunc]]></c> |
- <c><![CDATA[binary_part]]></c> |
+ <c><![CDATA[tuple_size]]></c> | <c><![CDATA[tl]]></c> |
+ <c><![CDATA[trunc]]></c> | <c><![CDATA[binary_part]]></c> |
<c><![CDATA['+']]></c> | <c><![CDATA['-']]></c> |
<c><![CDATA['*']]></c> | <c><![CDATA['div']]></c> |
<c><![CDATA['rem']]></c> | <c><![CDATA['band']]></c> |
@@ -224,9 +232,10 @@
follows:</p>
<taglist>
- <tag><c>is_atom</c>, <c>is_float</c>, <c>is_integer</c>, <c>is_list</c>,
- <c>is_number</c>, <c>is_pid</c>, <c>is_port</c>, <c>is_reference</c>,
- <c>is_tuple</c>, <c>is_map</c>, <c>is_binary</c>, <c>is_function</c>
+ <tag><c>is_atom</c>, <c>is_boolean</c>, <c>is_float</c>,
+ <c>is_integer</c>, <c>is_list</c>, <c>is_number</c>, <c>is_pid</c>,
+ <c>is_port</c>, <c>is_reference</c>, <c>is_tuple</c>, <c>is_map</c>,
+ <c>is_binary</c>, <c>is_bitstring</c>, <c>is_function</c>
</tag>
<item>
<p>Same as the corresponding guard tests in Erlang, return
@@ -275,8 +284,11 @@
<c><![CDATA['xor']]></c> returns false.</p>
</item>
<tag><c>abs</c>, <c>element</c>, <c>hd</c>, <c>length</c>,
- <c>map_get</c>, <c>map_size</c>, <c>node</c>, <c>round</c>,
- <c>size</c>, <c>bit_size</c>, <c>byte_size</c>, <c>tl</c>,
+ <c>map_get</c>, <c>map_size</c>,
+ <c>max</c>, <c>min</c>,
+ <c>node</c>, <c>round</c>, <c>ceil</c>, <c>floor</c>, <c>float</c>,
+ <c>size</c>, <c>bit_size</c>, <c>byte_size</c>, <c>tuple_size</c>,
+ <c>tl</c>,
<c>trunc</c>, <c>binary_part</c>, <c>'+'</c>,
<c>'-'</c>, <c>'*'</c>, <c>'div'</c>, <c>'rem'</c>, <c>'band'</c>,
<c>'bor'</c>, <c>'bxor'</c>, <c>'bnot'</c>, <c>'bsl'</c>,
@@ -457,6 +469,19 @@
<c><![CDATA[undefined]]></c>. The calling
Erlang function is not available during such calls.</p>
</item>
+ <tag><c>current_stacktrace</c></tag>
+ <item>
+ <p>Returns the current call stack back-trace
+ (<seetype marker="erts:erlang#stacktrace">stacktrace</seetype>)
+ of the calling function. The stack has the same format as in the
+ <c>catch</c> part of a <c>try</c>. See <seeguide
+ marker="system/reference_manual:errors#stacktrace">The call-stack back trace (stacktrace)</seeguide>.
+ The depth of the stacktrace is truncated according to the
+ <c>backtrace_depth</c> system flag setting.</p>
+
+ <p>Accepts a depth parameter. The depth value will be
+ <c>backtrace_depth</c> if the argument is greater.</p>
+ </item>
<tag><c>display</c></tag>
<item>
<p>For debugging purposes only. Displays the single argument as an
diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml
index d31a438ede..1f372823e8 100644
--- a/erts/doc/src/notes.xml
+++ b/erts/doc/src/notes.xml
@@ -31,6 +31,35 @@
</header>
<p>This document describes the changes made to the ERTS application.</p>
+<section><title>Erts 13.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ If a runtime system which was starting the distribution
+ already had existing pids, ports, or references referring
+ to a node with the same nodename/creation pair that the
+ runtime system was about to use, these already existing
+ pids, ports, or references would not work as expected in
+ various situations after the node had gone alive. This
+ could only occur if the runtime system was communicated
+ such pids, ports, or references prior to the distribution
+ was started. That is, it was extremely unlikely to happen
+ unless the distribution was started dynamically and was
+ even then very unlikely to happen. The runtime system now
+ checks for already existing pids, ports, and references
+ with the same nodename/creation pair that it is about to
+ use. If such are found another creation will be chosen in
+ order to avoid these issues.</p>
+ <p>
+ Own Id: OTP-18570 Aux Id: PR-7190 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 13.2.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/erts/doc/src/time_correction.xml b/erts/doc/src/time_correction.xml
index b3a36b907e..30c514c1a3 100644
--- a/erts/doc/src/time_correction.xml
+++ b/erts/doc/src/time_correction.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1999</year><year>2021</year>
+ <year>1999</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -34,29 +34,42 @@
</header>
<section>
- <title>New Extended Time Functionality</title>
- <note><p>As from Erlang/OTP 18 (ERTS 7.0) the time functionality
- has been extended. This includes a
+ <title>Extended Time Functionality</title>
+
+ <p>As of Erlang/OTP 18 (ERTS 7.0) the time functionality
+ was extended. This includes a
<seeguide marker="#The_New_Time_API">new API</seeguide>
for time and
<seeguide marker="#Time_Warp_Modes">time warp
modes</seeguide> that change the system behavior when
system time changes.</p>
- <p>The <seeguide marker="#No_Time_Warp_Mode">default
- time warp mode</seeguide> has the same behavior as before, and the
- old API still works. Thus, you are not required to change
- anything unless you want to. However, <em>you are strongly
- encouraged to use the new API</em> instead of the old API based
- on <seemfa marker="erlang#now/0"><c>erlang:now/0</c></seemfa>.
- <c>erlang:now/0</c> is deprecated, as it is and
- will be a scalability bottleneck.</p>
-
- <p>By using the new API, you
- automatically get scalability and performance improvements. This
- also enables you to use the
- <seeguide marker="#Multi_Time_Warp_Mode">multi-time warp mode</seeguide>
- that improves accuracy and precision of time measurements.</p>
+ <note>
+ <p>
+ As of Erlang/OTP 26 (ERTS 14.0) the
+ <seeguide marker="#Multi_Time_Warp_Mode">multi time warp
+ mode</seeguide> is enabled by default. This assumes that all
+ code executing on the system is
+ <seeguide marker="#Time_Warp_Safe_Code">time warp safe</seeguide>.
+ </p>
+ <p>
+ If you have old code in the system that is not time warp
+ safe, you now explicitly need to start the system in
+ <seeguide marker="#No_Time_Warp_Mode">no time warp
+ mode</seeguide> (or
+ <seeguide marker="#Single_Time_Warp_Mode">singe time warp
+ mode</seeguide> if it is partially time warp safe) in order
+ to avoid problems. When starting the system in no time warp
+ mode, the system behaves as it did prior to the introduction
+ of the extended time functionality introduced in OTP 18.
+ </p>
+ <p>
+ If you have code that is not time warp safe, you are strongly
+ encouraged to change this so that you can use multi time
+ warp mode. Compared to no time warp mode, multi time warp
+ mode improves scalability and performance as well as accuracy
+ and precision of time measurements.
+ </p>
</note>
</section>
@@ -405,13 +418,9 @@
<section>
<title>No Time Warp Mode</title>
<p>The time offset is determined at runtime system start
- and does not change later. This is the default behavior, but
- not because it is the best mode (which it is not). It is
- default <em>only</em> because this is how the runtime system
- behaved until ERTS 7.0.
- Ensure that your Erlang code that can execute during a time
- warp is <seeguide marker="#Time_Warp_Safe_Code">time warp
- safe</seeguide> before enabling other modes.</p>
+ and does not change later. This is the same behavior as was
+ default prior to OTP 26 (ERTS 14.0), and the only behavior
+ prior to OTP 18 (ERTS 7.0).</p>
<p>As the time offset is not allowed to change, time
correction must adjust the frequency of the Erlang
@@ -558,7 +567,8 @@
better, and behave better on almost all platforms.
Also, the accuracy and precision of time measurements
are better. Only Erlang runtime systems executing on
- ancient platforms benefit from another configuration.</p>
+ ancient platforms benefit from another configuration.
+ As of OTP 26 (ERTS 14.0) this is also the default.</p>
<p>The time offset can change at any time without limitations.
That is, Erlang system time can perform time warps both
diff --git a/erts/doc/src/tty.xml b/erts/doc/src/tty.xml
index e82e2cc807..7670b293cd 100644
--- a/erts/doc/src/tty.xml
+++ b/erts/doc/src/tty.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1996</year><year>2020</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -67,7 +67,8 @@ erl</pre>
<item><c>C-a</c> means pressing the <em>Ctrl</em> key and the letter
<c>a</c> simultaneously.</item>
<item><c>M-f</c> means pressing the <em>Esc</em> key and the letter
- <c>f</c> in sequence.</item>
+ <c>f</c> in sequence or pressing the <em>Alt</em> key and the letter
+ <c>f</c> simultaneously.</item>
<item><c>Home</c> and <c>End</c> represent the keys with the same
name on the keyboard.</item>
<item><c>Left</c> and <c>Right</c> represent the corresponding arrow
@@ -141,6 +142,10 @@ erl</pre>
</row>
<row>
<cell align="left" valign="middle">C-l</cell>
+ <cell align="left" valign="middle">Clears the screen</cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle">M-l</cell>
<cell align="left" valign="middle">Redraw line</cell>
</row>
<row>
@@ -149,11 +154,22 @@ erl</pre>
buffer</cell>
</row>
<row>
+ <cell align="left" valign="middle">C-o</cell>
+ <cell align="left" valign="middle">Edit the current line using the editor specified in
+ the environment variable <c>VISUAL</c> or <c>EDITOR</c>. The environment variables can
+ contain arguments to the editor if needed, for example <c>VISUAL="emacs -nw"</c>.
+ On Windows the editor cannot be a console based editor.</cell>
+ </row>
+ <row>
<cell align="left" valign="middle">C-p</cell>
<cell align="left" valign="middle">Fetch previous line from the history
buffer</cell>
</row>
<row>
+ <cell align="left" valign="middle">C-r</cell>
+ <cell align="left" valign="middle">Search the shell history</cell>
+ </row>
+ <row>
<cell align="left" valign="middle">C-t</cell>
<cell align="left" valign="middle">Transpose characters</cell>
</row>
diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in
index 979f76c4c6..5451bdf804 100644
--- a/erts/emulator/Makefile.in
+++ b/erts/emulator/Makefile.in
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2022. All Rights Reserved.
+# Copyright Ericsson AB 1996-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -432,6 +432,7 @@ CREATE_DIRS+=$(TTF_DIR)/asmjit $(TTF_DIR)/asmjit/core $(TTF_DIR)/asmjit/$(JIT_AR
endif
BINDIR = $(ERL_TOP)/bin/$(TARGET)
+CREATE_DIRS += $(BINDIR)
ERLANG_OSTYPE = @ERLANG_OSTYPE@
@@ -1123,11 +1124,13 @@ RUN_OBJS += \
$(OBJDIR)/erl_nfunc_sched.o \
$(OBJDIR)/erl_global_literals.o \
$(OBJDIR)/beam_file.o \
- $(OBJDIR)/beam_types.o
+ $(OBJDIR)/beam_types.o \
+ $(OBJDIR)/erl_term_hashing.o
LTTNG_OBJS = $(OBJDIR)/erlang_lttng.o
NIF_OBJS = \
+ $(OBJDIR)/prim_tty_nif.o \
$(OBJDIR)/erl_tracer_nif.o \
$(OBJDIR)/prim_buffer_nif.o \
$(OBJDIR)/prim_file_nif.o \
@@ -1138,10 +1141,8 @@ ifeq ($(TARGET),win32)
DRV_OBJS = \
$(OBJDIR)/registry_drv.o \
$(OBJDIR)/inet_drv.o \
- $(OBJDIR)/ram_file_drv.o \
- $(OBJDIR)/ttsl_drv.o
+ $(OBJDIR)/ram_file_drv.o
OS_OBJS = \
- $(OBJDIR)/win_con.o \
$(OBJDIR)/dll_sys.o \
$(OBJDIR)/driver_tab.o \
$(OBJDIR)/sys_float.o \
@@ -1149,7 +1150,8 @@ OS_OBJS = \
$(OBJDIR)/sys_interrupt.o \
$(OBJDIR)/sys_env.o \
$(OBJDIR)/dosmap.o \
- $(OBJDIR)/win_prim_file.o
+ $(OBJDIR)/win_prim_file.o \
+ $(OBJDIR)/win_socket_asyncio.o
else
OS_OBJS = \
@@ -1161,12 +1163,12 @@ OS_OBJS = \
$(OBJDIR)/unix_prim_file.o \
$(OBJDIR)/sys_float.o \
$(OBJDIR)/sys_time.o \
- $(OBJDIR)/sys_signal_stack.o
+ $(OBJDIR)/sys_signal_stack.o \
+ $(OBJDIR)/unix_socket_syncio.o
DRV_OBJS = \
$(OBJDIR)/inet_drv.o \
- $(OBJDIR)/ram_file_drv.o \
- $(OBJDIR)/ttsl_drv.o
+ $(OBJDIR)/ram_file_drv.o
endif
ifneq ($(STATIC_NIFS),no)
@@ -1329,9 +1331,9 @@ NIF_COMMON_SRC=$(wildcard nifs/common/*.c)
endif
NIF_OSTYPE_SRC=$(wildcard nifs/$(ERLANG_OSTYPE)/*.c)
ALL_SYS_SRC=$(wildcard sys/$(ERLANG_OSTYPE)/*.c) $(wildcard sys/common/*.c)
-# We use $(shell ls) here instead of wildcard as $(wildcard ) resolved at
+# We use $(shell find) here instead of wildcard as $(wildcard ) resolved at
# loadtime of the makefile and at that time these files are not generated yet.
-TARGET_SRC=$(shell ls $(TARGET)/*.c) $(shell ls $(TTF_DIR)/*.c)
+TARGET_SRC=$(shell find $(TARGET) $(TTF_DIR) -maxdepth 1 -name "*.c")
# I do not want the -MG flag on windows, it does not work properly for a
# windows build.
@@ -1434,8 +1436,9 @@ depend:
else
depend: $(TTF_DIR)/depend.mk
$(TTF_DIR)/depend.mk: $(foreach dep, $(DEPEND_DEPS), $(TTF_DIR)/$(dep).depend.mk)
- -rm $@
- for dep in $^; do cat $$dep >> $@; done
+ $(gen_verbose)
+ $(V_at)echo "" > "$@"
+ $(V_at)for dep in "$^"; do cat $$dep >> "$@"; done
$(V_at)cd $(ERTS_LIB_DIR) && $(MAKE) depend
endif
diff --git a/erts/emulator/asmjit.version b/erts/emulator/asmjit.version
index dfb88343e6..0220314b7a 100644
--- a/erts/emulator/asmjit.version
+++ b/erts/emulator/asmjit.version
@@ -1 +1 @@
-23ddf56b00f47d8aa0c82ad225e4b3a92661da7e
+915186f6c5c2f5a4638e5cb97ccc23d741521a64
diff --git a/erts/emulator/asmjit/arm/a64assembler.cpp b/erts/emulator/asmjit/arm/a64assembler.cpp
index 9f8c9b1a9f..ec698de767 100644
--- a/erts/emulator/asmjit/arm/a64assembler.cpp
+++ b/erts/emulator/asmjit/arm/a64assembler.cpp
@@ -698,25 +698,25 @@ static const Support::Array<uint8_t, 32> commonHiRegIdOfType = {{
#undef V
static inline bool checkValidRegs(const Operand_& o0) noexcept {
- return ((o0.id() < 31) | (o0.id() == commonHiRegIdOfType[o0.as<Reg>().type()]));
+ return bool(unsigned(o0.id() < 31) | unsigned(o0.id() == commonHiRegIdOfType[o0.as<Reg>().type()]));
}
static inline bool checkValidRegs(const Operand_& o0, const Operand_& o1) noexcept {
- return ((o0.id() < 31) | (o0.id() == commonHiRegIdOfType[o0.as<Reg>().type()])) &
- ((o1.id() < 31) | (o1.id() == commonHiRegIdOfType[o1.as<Reg>().type()])) ;
+ return bool((unsigned(o0.id() < 31) | unsigned(o0.id() == commonHiRegIdOfType[o0.as<Reg>().type()])) &
+ (unsigned(o1.id() < 31) | unsigned(o1.id() == commonHiRegIdOfType[o1.as<Reg>().type()])));
}
static inline bool checkValidRegs(const Operand_& o0, const Operand_& o1, const Operand_& o2) noexcept {
- return ((o0.id() < 31) | (o0.id() == commonHiRegIdOfType[o0.as<Reg>().type()])) &
- ((o1.id() < 31) | (o1.id() == commonHiRegIdOfType[o1.as<Reg>().type()])) &
- ((o2.id() < 31) | (o2.id() == commonHiRegIdOfType[o2.as<Reg>().type()])) ;
+ return bool((unsigned(o0.id() < 31) | unsigned(o0.id() == commonHiRegIdOfType[o0.as<Reg>().type()])) &
+ (unsigned(o1.id() < 31) | unsigned(o1.id() == commonHiRegIdOfType[o1.as<Reg>().type()])) &
+ (unsigned(o2.id() < 31) | unsigned(o2.id() == commonHiRegIdOfType[o2.as<Reg>().type()])));
}
static inline bool checkValidRegs(const Operand_& o0, const Operand_& o1, const Operand_& o2, const Operand_& o3) noexcept {
- return ((o0.id() < 31) | (o0.id() == commonHiRegIdOfType[o0.as<Reg>().type()])) &
- ((o1.id() < 31) | (o1.id() == commonHiRegIdOfType[o1.as<Reg>().type()])) &
- ((o2.id() < 31) | (o2.id() == commonHiRegIdOfType[o2.as<Reg>().type()])) &
- ((o3.id() < 31) | (o3.id() == commonHiRegIdOfType[o3.as<Reg>().type()])) ;
+ return bool((unsigned(o0.id() < 31) | unsigned(o0.id() == commonHiRegIdOfType[o0.as<Reg>().type()])) &
+ (unsigned(o1.id() < 31) | unsigned(o1.id() == commonHiRegIdOfType[o1.as<Reg>().type()])) &
+ (unsigned(o2.id() < 31) | unsigned(o2.id() == commonHiRegIdOfType[o2.as<Reg>().type()])) &
+ (unsigned(o3.id() < 31) | unsigned(o3.id() == commonHiRegIdOfType[o3.as<Reg>().type()])));
}
// a64::Assembler - Construction & Destruction
@@ -4993,13 +4993,11 @@ EmitDone:
if (Support::test(options, InstOptions::kReserved)) {
#ifndef ASMJIT_NO_LOGGING
if (_logger)
- EmitterUtils::logInstructionEmitted(this, instId, options, o0, o1, o2, opExt, 0, 0, writer.cursor());
+ EmitterUtils::logInstructionEmitted(this, BaseInst::composeARMInstId(instId, instCC), options, o0, o1, o2, opExt, 0, 0, writer.cursor());
#endif
}
- resetExtraReg();
- resetInstOptions();
- resetInlineComment();
+ resetState();
writer.done(this);
return kErrorOk;
@@ -5025,9 +5023,7 @@ Failed:
#ifndef ASMJIT_NO_LOGGING
return EmitterUtils::logInstructionFailed(this, err, instId, options, o0, o1, o2, opExt);
#else
- resetExtraReg();
- resetInstOptions();
- resetInlineComment();
+ resetState();
return reportError(err);
#endif
}
diff --git a/erts/emulator/asmjit/arm/a64compiler.h b/erts/emulator/asmjit/arm/a64compiler.h
index ebed549581..bed408a98f 100644
--- a/erts/emulator/asmjit/arm/a64compiler.h
+++ b/erts/emulator/asmjit/arm/a64compiler.h
@@ -169,6 +169,18 @@ public:
//! \}
+ //! \name Compiler specific
+ //! \{
+
+ //! Special pseudo-instruction that can be used to load a memory address into `o0` GP register.
+ //!
+ //! \note At the moment this instruction is only useful to load a stack allocated address into a GP register
+ //! for further use. It makes very little sense to use it for anything else. The semantics of this instruction
+ //! is the same as X86 `LEA` (load effective address) instruction.
+ inline Error loadAddressOf(const Gp& o0, const Mem& o1) { return _emitter()->_emitI(Inst::kIdAdr, o0, o1); }
+
+ //! \}
+
//! \name Function Call & Ret Intrinsics
//! \{
diff --git a/erts/emulator/asmjit/arm/a64emithelper.cpp b/erts/emulator/asmjit/arm/a64emithelper.cpp
index 2d8a5781cd..1e8da619a6 100644
--- a/erts/emulator/asmjit/arm/a64emithelper.cpp
+++ b/erts/emulator/asmjit/arm/a64emithelper.cpp
@@ -117,7 +117,7 @@ ASMJIT_FAVOR_SIZE Error EmitHelper::emitRegMove(
case TypeId::kUInt32:
case TypeId::kInt64:
case TypeId::kUInt64:
- return emitter->mov(src.as<Gp>().x(), dst.as<Gp>().x());
+ return emitter->mov(dst.as<Gp>().x(), src.as<Gp>().x());
default: {
if (TypeUtils::isFloat32(typeId) || TypeUtils::isVec32(typeId))
diff --git a/erts/emulator/asmjit/arm/a64formatter.cpp b/erts/emulator/asmjit/arm/a64formatter.cpp
index bccb68b99b..d6738ca8f8 100644
--- a/erts/emulator/asmjit/arm/a64formatter.cpp
+++ b/erts/emulator/asmjit/arm/a64formatter.cpp
@@ -4,7 +4,7 @@
// SPDX-License-Identifier: Zlib
#include "../core/api-build_p.h"
-#ifndef ASMJIT_NO_LOGGING
+#if !defined(ASMJIT_NO_AARCH64) && !defined(ASMJIT_NO_LOGGING)
#include "../core/misc_p.h"
#include "../core/support.h"
@@ -295,4 +295,4 @@ ASMJIT_FAVOR_SIZE Error FormatterInternal::formatInstruction(
ASMJIT_END_SUB_NAMESPACE
-#endif // !ASMJIT_NO_LOGGING
+#endif // !ASMJIT_NO_AARCH64 && !ASMJIT_NO_LOGGING
diff --git a/erts/emulator/asmjit/arm/a64instapi.cpp b/erts/emulator/asmjit/arm/a64instapi.cpp
index dc98bc8f73..82bed0e78e 100644
--- a/erts/emulator/asmjit/arm/a64instapi.cpp
+++ b/erts/emulator/asmjit/arm/a64instapi.cpp
@@ -8,7 +8,7 @@
#include "../core/cpuinfo.h"
#include "../core/misc_p.h"
-#include "../core/support.h"
+#include "../core/support_p.h"
#include "../arm/a64instapi_p.h"
#include "../arm/a64instdb_p.h"
#include "../arm/a64operand.h"
@@ -26,8 +26,11 @@ Error InstInternal::instIdToString(Arch arch, InstId instId, String& output) noe
if (ASMJIT_UNLIKELY(!Inst::isDefinedId(realId)))
return DebugUtils::errored(kErrorInvalidInstruction);
- const InstDB::InstInfo& info = InstDB::infoById(realId);
- return output.append(InstDB::_nameData + info._nameDataIndex);
+
+ char nameData[32];
+ size_t nameSize = Support::decodeInstName(nameData, InstDB::_instNameIndexTable[realId], InstDB::_instNameStringTable);
+
+ return output.append(nameData, nameSize);
}
InstId InstInternal::stringToInstId(Arch arch, const char* s, size_t len) noexcept {
@@ -46,30 +49,28 @@ InstId InstInternal::stringToInstId(Arch arch, const char* s, size_t len) noexce
if (ASMJIT_UNLIKELY(prefix > 'z' - 'a'))
return Inst::kIdNone;
- uint32_t index = InstDB::instNameIndex[prefix].start;
- if (ASMJIT_UNLIKELY(!index))
- return Inst::kIdNone;
+ size_t base = InstDB::instNameIndex[prefix].start;
+ size_t end = InstDB::instNameIndex[prefix].end;
- const char* nameData = InstDB::_nameData;
- const InstDB::InstInfo* table = InstDB::_instInfoTable;
+ if (ASMJIT_UNLIKELY(!base))
+ return Inst::kIdNone;
- const InstDB::InstInfo* base = table + index;
- const InstDB::InstInfo* end = table + InstDB::instNameIndex[prefix].end;
+ char nameData[32];
+ for (size_t lim = end - base; lim != 0; lim >>= 1) {
+ size_t instId = base + (lim >> 1);
+ size_t nameSize = Support::decodeInstName(nameData, InstDB::_instNameIndexTable[instId], InstDB::_instNameStringTable);
- for (size_t lim = (size_t)(end - base); lim != 0; lim >>= 1) {
- const InstDB::InstInfo* cur = base + (lim >> 1);
- int result = Support::cmpInstName(nameData + cur[0]._nameDataIndex, s, len);
+ int result = Support::compareStringViews(s, len, nameData, nameSize);
+ if (result < 0)
+ continue;
- if (result < 0) {
- base = cur + 1;
+ if (result > 0) {
+ base = instId + 1;
lim--;
continue;
}
- if (result > 0)
- continue;
-
- return uint32_t((size_t)(cur - table));
+ return InstId(instId);
}
return Inst::kIdNone;
@@ -139,7 +140,7 @@ Error InstInternal::queryRWInfo(Arch arch, const BaseInst& inst, const Operand_*
if (ASMJIT_UNLIKELY(!Inst::isDefinedId(realId)))
return DebugUtils::errored(kErrorInvalidInstruction);
- out->_instFlags = 0;
+ out->_instFlags = InstRWFlags::kNone;
out->_opCount = uint8_t(opCount);
out->_rmFeature = 0;
out->_extraReg.reset();
diff --git a/erts/emulator/asmjit/arm/a64instdb.cpp b/erts/emulator/asmjit/arm/a64instdb.cpp
index 64709b5db0..49be360e92 100644
--- a/erts/emulator/asmjit/arm/a64instdb.cpp
+++ b/erts/emulator/asmjit/arm/a64instdb.cpp
@@ -18,20 +18,11 @@ namespace InstDB {
// a64::InstDB - InstInfoTable
// ===========================
-// Don't store `_nameDataIndex` if instruction names are disabled. Since some
-// APIs can use `_nameDataIndex` it's much safer if it's zero if it's not used.
-#if defined(ASMJIT_NO_TEXT)
- #define NAME_DATA_INDEX(x) 0
-#else
- #define NAME_DATA_INDEX(x) x
-#endif
-
// Defines an ARM/AArch64 instruction.
-#define INST(id, opcodeEncoding, opcodeData, rwInfoIndex, flags, opcodeDataIndex, nameDataIndex) { \
+#define INST(id, opcodeEncoding, opcodeData, rwInfoIndex, flags, opcodeDataIndex) { \
uint32_t(kEncoding##opcodeEncoding), \
uint32_t(opcodeDataIndex), \
0, \
- uint32_t(NAME_DATA_INDEX(nameDataIndex)), \
uint16_t(rwInfoIndex), \
uint16_t(flags) \
}
@@ -63,774 +54,774 @@ IRG: Insert Random Tag.
INST_(Irg , BaseRRR , (0b1001101011000000000100, kX , kSP, kX , kSP, kX , kZR, true) , kRWI_W , 0 , 0 , 1 ), // #1
*/
const InstInfo _instInfoTable[] = {
- // +------------------+---------------------+--------------------------------------------------------------------------------------+-----------+---------------------------+----+-----+
- // | Instruction Id | Encoding | Opcode Data | RW Info | Instruction Flags |DatX|NameX|
- // +------------------+---------------------+--------------------------------------------------------------------------------------+-----------+---------------------------+----+-----+
+ // +------------------+---------------------+--------------------------------------------------------------------------------------+-----------+---------------------------+----+
+ // | Instruction Id | Encoding | Opcode Data | RW Info | Instruction Flags |DatX|
+ // +------------------+---------------------+--------------------------------------------------------------------------------------+-----------+---------------------------+----+
// ${InstInfo:Begin}
- INST(None , None , (_) , 0 , 0 , 0 , 0 ), // #0
- INST(Adc , BaseRRR , (0b0001101000000000000000, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 0 , 1 ), // #1
- INST(Adcs , BaseRRR , (0b0011101000000000000000, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 1 , 5 ), // #2
- INST(Add , BaseAddSub , (0b0001011000, 0b0001011001, 0b0010001) , kRWI_W , 0 , 0 , 978 ), // #3
- INST(Addg , BaseRRII , (0b1001000110000000000000, kX, kSP, kX, kSP, 6, 4, 16, 4, 0, 10) , kRWI_W , 0 , 0 , 10 ), // #4
- INST(Adds , BaseAddSub , (0b0101011000, 0b0101011001, 0b0110001) , kRWI_W , 0 , 1 , 15 ), // #5
- INST(Adr , BaseAdr , (0b0001000000000000000000, OffsetType::kAArch64_ADR) , kRWI_W , 0 , 0 , 25 ), // #6
- INST(Adrp , BaseAdr , (0b1001000000000000000000, OffsetType::kAArch64_ADRP) , kRWI_W , 0 , 1 , 29 ), // #7
- INST(And , BaseLogical , (0b0001010000, 0b00100100, 0) , kRWI_W , 0 , 0 , 57 ), // #8
- INST(Ands , BaseLogical , (0b1101010000, 0b11100100, 0) , kRWI_W , 0 , 1 , 61 ), // #9
- INST(Asr , BaseShift , (0b0001101011000000001010, 0b0001001100000000011111, 0) , kRWI_W , 0 , 0 , 66 ), // #10
- INST(Asrv , BaseShift , (0b0001101011000000001010, 0b0000000000000000000000, 0) , kRWI_W , 0 , 1 , 70 ), // #11
- INST(At , BaseAtDcIcTlbi , (0b00011111110000, 0b00001111000000, true) , kRWI_RX , 0 , 0 , 75 ), // #12
- INST(Autda , BaseRR , (0b11011010110000010001100000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 0 , 78 ), // #13
- INST(Autdza , BaseR , (0b11011010110000010011101111100000, kX, kZR, 0) , kRWI_X , 0 , 0 , 90 ), // #14
- INST(Autdb , BaseRR , (0b11011010110000010001110000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 1 , 84 ), // #15
- INST(Autdzb , BaseR , (0b11011010110000010011111111100000, kX, kZR, 0) , kRWI_X , 0 , 1 , 97 ), // #16
- INST(Autia , BaseRR , (0b11011010110000010001000000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 2 , 104 ), // #17
- INST(Autia1716 , BaseOp , (0b11010101000000110010000110011111) , 0 , 0 , 0 , 110 ), // #18
- INST(Autiasp , BaseOp , (0b11010101000000110010001110111111) , 0 , 0 , 1 , 120 ), // #19
- INST(Autiaz , BaseOp , (0b11010101000000110010001110011111) , 0 , 0 , 2 , 128 ), // #20
- INST(Autib , BaseRR , (0b11011010110000010001010000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 3 , 135 ), // #21
- INST(Autib1716 , BaseOp , (0b11010101000000110010000111011111) , 0 , 0 , 3 , 141 ), // #22
- INST(Autibsp , BaseOp , (0b11010101000000110010001111111111) , 0 , 0 , 4 , 151 ), // #23
- INST(Autibz , BaseOp , (0b11010101000000110010001111011111) , 0 , 0 , 5 , 159 ), // #24
- INST(Autiza , BaseR , (0b11011010110000010011001111100000, kX, kZR, 0) , kRWI_X , 0 , 2 , 166 ), // #25
- INST(Autizb , BaseR , (0b11011010110000010011011111100000, kX, kZR, 0) , kRWI_X , 0 , 3 , 173 ), // #26
- INST(Axflag , BaseOp , (0b11010101000000000100000001011111) , 0 , 0 , 6 , 180 ), // #27
- INST(B , BaseBranchRel , (0b00010100000000000000000000000000) , 0 , F(Cond) , 0 , 1738), // #28
- INST(Bfc , BaseBfc , (0b00110011000000000000001111100000) , kRWI_X , 0 , 0 , 192 ), // #29
- INST(Bfi , BaseBfi , (0b00110011000000000000000000000000) , kRWI_X , 0 , 0 , 223 ), // #30
- INST(Bfm , BaseBfm , (0b00110011000000000000000000000000) , kRWI_X , 0 , 0 , 2514), // #31
- INST(Bfxil , BaseBfx , (0b00110011000000000000000000000000) , kRWI_X , 0 , 0 , 250 ), // #32
- INST(Bic , BaseLogical , (0b0001010001, 0b00100100, 1) , kRWI_W , 0 , 2 , 256 ), // #33
- INST(Bics , BaseLogical , (0b1101010001, 0b11100100, 1) , kRWI_W , 0 , 3 , 260 ), // #34
- INST(Bl , BaseBranchRel , (0b10010100000000000000000000000000) , 0 , 0 , 1 , 2831), // #35
- INST(Blr , BaseBranchReg , (0b11010110001111110000000000000000) , kRWI_R , 0 , 0 , 269 ), // #36
- INST(Br , BaseBranchReg , (0b11010110000111110000000000000000) , kRWI_R , 0 , 1 , 273 ), // #37
- INST(Brk , BaseOpImm , (0b11010100001000000000000000000000, 16, 5) , 0 , 0 , 0 , 276 ), // #38
- INST(Cas , BaseAtomicOp , (0b1000100010100000011111, kWX, 30, 0) , kRWI_XRX , 0 , 0 , 284 ), // #39
- INST(Casa , BaseAtomicOp , (0b1000100011100000011111, kWX, 30, 1) , kRWI_XRX , 0 , 1 , 288 ), // #40
- INST(Casab , BaseAtomicOp , (0b0000100011100000011111, kW , 0 , 1) , kRWI_XRX , 0 , 2 , 293 ), // #41
- INST(Casah , BaseAtomicOp , (0b0100100011100000011111, kW , 0 , 1) , kRWI_XRX , 0 , 3 , 299 ), // #42
- INST(Casal , BaseAtomicOp , (0b1000100011100000111111, kWX, 30, 1) , kRWI_XRX , 0 , 4 , 305 ), // #43
- INST(Casalb , BaseAtomicOp , (0b0000100011100000111111, kW , 0 , 1) , kRWI_XRX , 0 , 5 , 311 ), // #44
- INST(Casalh , BaseAtomicOp , (0b0100100011100000111111, kW , 0 , 1) , kRWI_XRX , 0 , 6 , 318 ), // #45
- INST(Casb , BaseAtomicOp , (0b0000100010100000011111, kW , 0 , 0) , kRWI_XRX , 0 , 7 , 325 ), // #46
- INST(Cash , BaseAtomicOp , (0b0100100010100000011111, kW , 0 , 0) , kRWI_XRX , 0 , 8 , 330 ), // #47
- INST(Casl , BaseAtomicOp , (0b1000100010100000111111, kWX, 30, 0) , kRWI_XRX , 0 , 9 , 335 ), // #48
- INST(Caslb , BaseAtomicOp , (0b0000100010100000111111, kW , 0 , 0) , kRWI_XRX , 0 , 10 , 340 ), // #49
- INST(Caslh , BaseAtomicOp , (0b0100100010100000111111, kW , 0 , 0) , kRWI_XRX , 0 , 11 , 346 ), // #50
- INST(Casp , BaseAtomicCasp , (0b0000100000100000011111, kWX, 30) , kRWI_XXRRX, 0 , 0 , 352 ), // #51
- INST(Caspa , BaseAtomicCasp , (0b0000100001100000011111, kWX, 30) , kRWI_XXRRX, 0 , 1 , 357 ), // #52
- INST(Caspal , BaseAtomicCasp , (0b0000100001100000111111, kWX, 30) , kRWI_XXRRX, 0 , 2 , 363 ), // #53
- INST(Caspl , BaseAtomicCasp , (0b0000100000100000111111, kWX, 30) , kRWI_XXRRX, 0 , 3 , 370 ), // #54
- INST(Cbnz , BaseBranchCmp , (0b00110101000000000000000000000000) , kRWI_R , 0 , 0 , 376 ), // #55
- INST(Cbz , BaseBranchCmp , (0b00110100000000000000000000000000) , kRWI_R , 0 , 1 , 381 ), // #56
- INST(Ccmn , BaseCCmp , (0b00111010010000000000000000000000) , kRWI_R , 0 , 0 , 385 ), // #57
- INST(Ccmp , BaseCCmp , (0b01111010010000000000000000000000) , kRWI_R , 0 , 1 , 650 ), // #58
- INST(Cfinv , BaseOp , (0b11010101000000000100000000011111) , 0 , 0 , 7 , 390 ), // #59
- INST(Cinc , BaseCInc , (0b00011010100000000000010000000000) , kRWI_W , 0 , 0 , 396 ), // #60
- INST(Cinv , BaseCInc , (0b01011010100000000000000000000000) , kRWI_W , 0 , 1 , 401 ), // #61
- INST(Clrex , BaseOpImm , (0b11010101000000110011000001011111, 4, 8) , 0 , 0 , 1 , 406 ), // #62
- INST(Cls , BaseRR , (0b01011010110000000001010000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 4 , 412 ), // #63
- INST(Clz , BaseRR , (0b01011010110000000001000000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 5 , 416 ), // #64
- INST(Cmn , BaseCmpCmn , (0b0101011000, 0b0101011001, 0b0110001) , kRWI_R , 0 , 0 , 386 ), // #65
- INST(Cmp , BaseCmpCmn , (0b1101011000, 0b1101011001, 0b1110001) , kRWI_R , 0 , 1 , 651 ), // #66
- INST(Cmpp , BaseRR , (0b10111010110000000000000000011111, kX, kSP, 5, kX, kSP, 16, true) , kRWI_R , 0 , 6 , 430 ), // #67
- INST(Cneg , BaseCInc , (0b01011010100000000000010000000000) , kRWI_W , 0 , 2 , 441 ), // #68
- INST(Crc32b , BaseRRR , (0b0001101011000000010000, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 2 , 450 ), // #69
- INST(Crc32cb , BaseRRR , (0b0001101011000000010100, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 3 , 457 ), // #70
- INST(Crc32ch , BaseRRR , (0b0001101011000000010101, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 4 , 465 ), // #71
- INST(Crc32cw , BaseRRR , (0b0001101011000000010110, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 5 , 473 ), // #72
- INST(Crc32cx , BaseRRR , (0b1001101011000000010111, kW, kZR, kW, kZR, kX, kZR, false) , kRWI_W , 0 , 6 , 481 ), // #73
- INST(Crc32h , BaseRRR , (0b0001101011000000010001, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 7 , 489 ), // #74
- INST(Crc32w , BaseRRR , (0b0001101011000000010010, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 8 , 496 ), // #75
- INST(Crc32x , BaseRRR , (0b1001101011000000010011, kW, kZR, kW, kZR, kX, kZR, false) , kRWI_W , 0 , 9 , 503 ), // #76
- INST(Csdb , BaseOp , (0b11010101000000110010001010011111) , 0 , 0 , 8 , 510 ), // #77
- INST(Csel , BaseCSel , (0b00011010100000000000000000000000) , kRWI_W , 0 , 0 , 710 ), // #78
- INST(Cset , BaseCSet , (0b00011010100111110000011111100000) , kRWI_W , 0 , 0 , 515 ), // #79
- INST(Csetm , BaseCSet , (0b01011010100111110000001111100000) , kRWI_W , 0 , 1 , 520 ), // #80
- INST(Csinc , BaseCSel , (0b00011010100000000000010000000000) , kRWI_W , 0 , 1 , 526 ), // #81
- INST(Csinv , BaseCSel , (0b01011010100000000000000000000000) , kRWI_W , 0 , 2 , 532 ), // #82
- INST(Csneg , BaseCSel , (0b01011010100000000000010000000000) , kRWI_W , 0 , 3 , 538 ), // #83
- INST(Dc , BaseAtDcIcTlbi , (0b00011110000000, 0b00001110000000, true) , kRWI_RX , 0 , 1 , 2 ), // #84
- INST(Dcps1 , BaseOpImm , (0b11010100101000000000000000000001, 16, 5) , 0 , 0 , 2 , 544 ), // #85
- INST(Dcps2 , BaseOpImm , (0b11010100101000000000000000000010, 16, 5) , 0 , 0 , 3 , 550 ), // #86
- INST(Dcps3 , BaseOpImm , (0b11010100101000000000000000000011, 16, 5) , 0 , 0 , 4 , 556 ), // #87
- INST(Dgh , BaseOp , (0b11010101000000110010000011011111) , 0 , 0 , 9 , 562 ), // #88
- INST(Dmb , BaseOpImm , (0b11010101000000110011000010111111, 4, 8) , 0 , 0 , 5 , 566 ), // #89
- INST(Drps , BaseOp , (0b11010110101111110000001111100000) , 0 , 0 , 10 , 570 ), // #90
- INST(Dsb , BaseOpImm , (0b11010101000000110011000010011111, 4, 8) , 0 , 0 , 6 , 575 ), // #91
- INST(Eon , BaseLogical , (0b1001010001, 0b10100100, 1) , kRWI_W , 0 , 4 , 583 ), // #92
- INST(Eor , BaseLogical , (0b1001010000, 0b10100100, 0) , kRWI_W , 0 , 5 , 1418), // #93
- INST(Esb , BaseOp , (0b11010101000000110010001000011111) , 0 , 0 , 11 , 597 ), // #94
- INST(Extr , BaseExtract , (0b00010011100000000000000000000000) , kRWI_W , 0 , 0 , 605 ), // #95
- INST(Eret , BaseOp , (0b11010110100111110000001111100000) , 0 , 0 , 12 , 592 ), // #96
- INST(Gmi , BaseRRR , (0b1001101011000000000101, kX , kZR, kX , kSP, kX , kZR, true) , kRWI_W , 0 , 10 , 1128), // #97
- INST(Hint , BaseOpImm , (0b11010101000000110010000000011111, 7, 5) , 0 , 0 , 7 , 1132), // #98
- INST(Hlt , BaseOpImm , (0b11010100010000000000000000000000, 16, 5) , 0 , 0 , 8 , 1137), // #99
- INST(Hvc , BaseOpImm , (0b11010100000000000000000000000010, 16, 5) , 0 , 0 , 9 , 1141), // #100
- INST(Ic , BaseAtDcIcTlbi , (0b00011110000000, 0b00001110000000, false) , kRWI_RX , 0 , 2 , 257 ), // #101
- INST(Isb , BaseOpImm , (0b11010101000000110011000011011111, 4, 8) , 0 , 0 , 10 , 1149), // #102
- INST(Ldadd , BaseAtomicOp , (0b1011100000100000000000, kWX, 30, 0) , kRWI_WRX , 0 , 12 , 1189), // #103
- INST(Ldadda , BaseAtomicOp , (0b1011100010100000000000, kWX, 30, 1) , kRWI_WRX , 0 , 13 , 1195), // #104
- INST(Ldaddab , BaseAtomicOp , (0b0011100010100000000000, kW , 0 , 1) , kRWI_WRX , 0 , 14 , 1202), // #105
- INST(Ldaddah , BaseAtomicOp , (0b0111100010100000000000, kW , 0 , 1) , kRWI_WRX , 0 , 15 , 1210), // #106
- INST(Ldaddal , BaseAtomicOp , (0b1011100011100000000000, kWX, 30, 1) , kRWI_WRX , 0 , 16 , 1218), // #107
- INST(Ldaddalb , BaseAtomicOp , (0b0011100011100000000000, kW , 0 , 1) , kRWI_WRX , 0 , 17 , 1226), // #108
- INST(Ldaddalh , BaseAtomicOp , (0b0111100011100000000000, kW , 0 , 1) , kRWI_WRX , 0 , 18 , 1235), // #109
- INST(Ldaddb , BaseAtomicOp , (0b0011100000100000000000, kW , 0 , 0) , kRWI_WRX , 0 , 19 , 1244), // #110
- INST(Ldaddh , BaseAtomicOp , (0b0111100000100000000000, kW , 0 , 0) , kRWI_WRX , 0 , 20 , 1251), // #111
- INST(Ldaddl , BaseAtomicOp , (0b1011100001100000000000, kWX, 30, 0) , kRWI_WRX , 0 , 21 , 1258), // #112
- INST(Ldaddlb , BaseAtomicOp , (0b0011100001100000000000, kW , 0 , 0) , kRWI_WRX , 0 , 22 , 1265), // #113
- INST(Ldaddlh , BaseAtomicOp , (0b0111100001100000000000, kW , 0 , 0) , kRWI_WRX , 0 , 23 , 1273), // #114
- INST(Ldar , BaseRM_NoImm , (0b1000100011011111111111, kWX, kZR, 30) , kRWI_W , 0 , 0 , 1281), // #115
- INST(Ldarb , BaseRM_NoImm , (0b0000100011011111111111, kW , kZR, 0 ) , kRWI_W , 0 , 1 , 1286), // #116
- INST(Ldarh , BaseRM_NoImm , (0b0100100011011111111111, kW , kZR, 0 ) , kRWI_W , 0 , 2 , 1292), // #117
- INST(Ldaxp , BaseLdxp , (0b1000100001111111100000, kWX, 30) , kRWI_WW , 0 , 0 , 1298), // #118
- INST(Ldaxr , BaseRM_NoImm , (0b1000100001011111111111, kWX, kZR, 30) , kRWI_W , 0 , 3 , 1304), // #119
- INST(Ldaxrb , BaseRM_NoImm , (0b0000100001011111111111, kW , kZR, 0 ) , kRWI_W , 0 , 4 , 1310), // #120
- INST(Ldaxrh , BaseRM_NoImm , (0b0100100001011111111111, kW , kZR, 0 ) , kRWI_W , 0 , 5 , 1317), // #121
- INST(Ldclr , BaseAtomicOp , (0b1011100000100000000100, kWX, 30, 0) , kRWI_WRX , 0 , 24 , 1324), // #122
- INST(Ldclra , BaseAtomicOp , (0b1011100010100000000100, kWX, 30, 1) , kRWI_WRX , 0 , 25 , 1330), // #123
- INST(Ldclrab , BaseAtomicOp , (0b0011100010100000000100, kW , 0 , 1) , kRWI_WRX , 0 , 26 , 1337), // #124
- INST(Ldclrah , BaseAtomicOp , (0b0111100010100000000100, kW , 0 , 1) , kRWI_WRX , 0 , 27 , 1345), // #125
- INST(Ldclral , BaseAtomicOp , (0b1011100011100000000100, kWX, 30, 1) , kRWI_WRX , 0 , 28 , 1353), // #126
- INST(Ldclralb , BaseAtomicOp , (0b0011100011100000000100, kW , 0 , 1) , kRWI_WRX , 0 , 29 , 1361), // #127
- INST(Ldclralh , BaseAtomicOp , (0b0111100011100000000100, kW , 0 , 1) , kRWI_WRX , 0 , 30 , 1370), // #128
- INST(Ldclrb , BaseAtomicOp , (0b0011100000100000000100, kW , 0 , 0) , kRWI_WRX , 0 , 31 , 1379), // #129
- INST(Ldclrh , BaseAtomicOp , (0b0111100000100000000100, kW , 0 , 0) , kRWI_WRX , 0 , 32 , 1386), // #130
- INST(Ldclrl , BaseAtomicOp , (0b1011100001100000000100, kWX, 30, 0) , kRWI_WRX , 0 , 33 , 1393), // #131
- INST(Ldclrlb , BaseAtomicOp , (0b0011100001100000000100, kW , 0 , 0) , kRWI_WRX , 0 , 34 , 1400), // #132
- INST(Ldclrlh , BaseAtomicOp , (0b0111100001100000000100, kW , 0 , 0) , kRWI_WRX , 0 , 35 , 1408), // #133
- INST(Ldeor , BaseAtomicOp , (0b1011100000100000001000, kWX, 30, 0) , kRWI_WRX , 0 , 36 , 1416), // #134
- INST(Ldeora , BaseAtomicOp , (0b1011100010100000001000, kWX, 30, 1) , kRWI_WRX , 0 , 37 , 1422), // #135
- INST(Ldeorab , BaseAtomicOp , (0b0011100010100000001000, kW , 0 , 1) , kRWI_WRX , 0 , 38 , 1429), // #136
- INST(Ldeorah , BaseAtomicOp , (0b0111100010100000001000, kW , 0 , 1) , kRWI_WRX , 0 , 39 , 1437), // #137
- INST(Ldeoral , BaseAtomicOp , (0b1011100011100000001000, kWX, 30, 1) , kRWI_WRX , 0 , 40 , 1445), // #138
- INST(Ldeoralb , BaseAtomicOp , (0b0011100011100000001000, kW , 0 , 1) , kRWI_WRX , 0 , 41 , 1453), // #139
- INST(Ldeoralh , BaseAtomicOp , (0b0111100011100000001000, kW , 0 , 1) , kRWI_WRX , 0 , 42 , 1462), // #140
- INST(Ldeorb , BaseAtomicOp , (0b0011100000100000001000, kW , 0 , 0) , kRWI_WRX , 0 , 43 , 1471), // #141
- INST(Ldeorh , BaseAtomicOp , (0b0111100000100000001000, kW , 0 , 0) , kRWI_WRX , 0 , 44 , 1478), // #142
- INST(Ldeorl , BaseAtomicOp , (0b1011100001100000001000, kWX, 30, 0) , kRWI_WRX , 0 , 45 , 1485), // #143
- INST(Ldeorlb , BaseAtomicOp , (0b0011100001100000001000, kW , 0 , 0) , kRWI_WRX , 0 , 46 , 1492), // #144
- INST(Ldeorlh , BaseAtomicOp , (0b0111100001100000001000, kW , 0 , 0) , kRWI_WRX , 0 , 47 , 1500), // #145
- INST(Ldg , BaseRM_SImm9 , (0b1101100101100000000000, 0b0000000000000000000000, kX , kZR, 0, 4) , kRWI_W , 0 , 0 , 1508), // #146
- INST(Ldgm , BaseRM_NoImm , (0b1101100111100000000000, kX , kZR, 0 ) , kRWI_W , 0 , 6 , 1512), // #147
- INST(Ldlar , BaseRM_NoImm , (0b1000100011011111011111, kWX, kZR, 30) , kRWI_W , 0 , 7 , 1517), // #148
- INST(Ldlarb , BaseRM_NoImm , (0b0000100011011111011111, kW , kZR, 0 ) , kRWI_W , 0 , 8 , 1523), // #149
- INST(Ldlarh , BaseRM_NoImm , (0b0100100011011111011111, kW , kZR, 0 ) , kRWI_W , 0 , 9 , 1530), // #150
- INST(Ldnp , BaseLdpStp , (0b0010100001, 0 , kWX, 31, 2) , kRWI_WW , 0 , 0 , 1537), // #151
- INST(Ldp , BaseLdpStp , (0b0010100101, 0b0010100011, kWX, 31, 2) , kRWI_W , 0 , 1 , 1542), // #152
- INST(Ldpsw , BaseLdpStp , (0b0110100101, 0b0110100011, kX , 0 , 2) , kRWI_WW , 0 , 2 , 1546), // #153
- INST(Ldr , BaseLdSt , (0b1011100101, 0b10111000010, 0b10111000011, 0b00011000, kWX, 30, 2, Inst::kIdLdur) , kRWI_W , 0 , 0 , 1552), // #154
- INST(Ldraa , BaseRM_SImm10 , (0b1111100000100000000001, kX , kZR, 0, 3) , kRWI_W , 0 , 0 , 1556), // #155
- INST(Ldrab , BaseRM_SImm10 , (0b1111100010100000000001, kX , kZR, 0, 3) , kRWI_W , 0 , 1 , 1562), // #156
- INST(Ldrb , BaseLdSt , (0b0011100101, 0b00111000010, 0b00111000011, 0 , kW , 0 , 0, Inst::kIdLdurb) , kRWI_W , 0 , 1 , 1568), // #157
- INST(Ldrh , BaseLdSt , (0b0111100101, 0b01111000010, 0b01111000011, 0 , kW , 0 , 1, Inst::kIdLdurh) , kRWI_W , 0 , 2 , 1573), // #158
- INST(Ldrsb , BaseLdSt , (0b0011100111, 0b00111000100, 0b00111000101, 0 , kWX, 22, 0, Inst::kIdLdursb) , kRWI_W , 0 , 3 , 1578), // #159
- INST(Ldrsh , BaseLdSt , (0b0111100110, 0b01111000100, 0b01111000101, 0 , kWX, 22, 1, Inst::kIdLdursh) , kRWI_W , 0 , 4 , 1584), // #160
- INST(Ldrsw , BaseLdSt , (0b1011100110, 0b10111000100, 0b10111000101, 0b10011000, kX , 0 , 2, Inst::kIdLdursw) , kRWI_W , 0 , 5 , 1590), // #161
- INST(Ldset , BaseAtomicOp , (0b1011100000100000001100, kWX, 30, 0) , kRWI_WRX , 0 , 48 , 1596), // #162
- INST(Ldseta , BaseAtomicOp , (0b1011100010100000001100, kWX, 30, 1) , kRWI_WRX , 0 , 49 , 1602), // #163
- INST(Ldsetab , BaseAtomicOp , (0b0011100010100000001100, kW , 0 , 1) , kRWI_WRX , 0 , 50 , 1609), // #164
- INST(Ldsetah , BaseAtomicOp , (0b0111100010100000001100, kW , 0 , 1) , kRWI_WRX , 0 , 51 , 1617), // #165
- INST(Ldsetal , BaseAtomicOp , (0b1011100011100000001100, kWX, 30, 1) , kRWI_WRX , 0 , 52 , 1625), // #166
- INST(Ldsetalb , BaseAtomicOp , (0b0011100011100000001100, kW , 0 , 1) , kRWI_WRX , 0 , 53 , 1633), // #167
- INST(Ldsetalh , BaseAtomicOp , (0b0111100011100000001100, kW , 0 , 1) , kRWI_WRX , 0 , 54 , 1642), // #168
- INST(Ldsetb , BaseAtomicOp , (0b0011100000100000001100, kW , 0 , 0) , kRWI_WRX , 0 , 55 , 1651), // #169
- INST(Ldseth , BaseAtomicOp , (0b0111100000100000001100, kW , 0 , 0) , kRWI_WRX , 0 , 56 , 1658), // #170
- INST(Ldsetl , BaseAtomicOp , (0b1011100001100000001100, kWX, 30, 0) , kRWI_WRX , 0 , 57 , 1665), // #171
- INST(Ldsetlb , BaseAtomicOp , (0b0011100001100000001100, kW , 0 , 0) , kRWI_WRX , 0 , 58 , 1672), // #172
- INST(Ldsetlh , BaseAtomicOp , (0b0111100001100000001100, kW , 0 , 0) , kRWI_WRX , 0 , 59 , 1680), // #173
- INST(Ldsmax , BaseAtomicOp , (0b1011100000100000010000, kWX, 30, 0) , kRWI_WRX , 0 , 60 , 1688), // #174
- INST(Ldsmaxa , BaseAtomicOp , (0b1011100010100000010000, kWX, 30, 1) , kRWI_WRX , 0 , 61 , 1695), // #175
- INST(Ldsmaxab , BaseAtomicOp , (0b0011100010100000010000, kW , 0 , 1) , kRWI_WRX , 0 , 62 , 1703), // #176
- INST(Ldsmaxah , BaseAtomicOp , (0b0111100010100000010000, kW , 0 , 1) , kRWI_WRX , 0 , 63 , 1712), // #177
- INST(Ldsmaxal , BaseAtomicOp , (0b1011100011100000010000, kWX, 30, 1) , kRWI_WRX , 0 , 64 , 1721), // #178
- INST(Ldsmaxalb , BaseAtomicOp , (0b0011100011100000010000, kW , 0 , 1) , kRWI_WRX , 0 , 65 , 1730), // #179
- INST(Ldsmaxalh , BaseAtomicOp , (0b0111100011100000010000, kW , 0 , 1) , kRWI_WRX , 0 , 66 , 1740), // #180
- INST(Ldsmaxb , BaseAtomicOp , (0b0011100000100000010000, kW , 0 , 0) , kRWI_WRX , 0 , 67 , 1750), // #181
- INST(Ldsmaxh , BaseAtomicOp , (0b0111100000100000010000, kW , 0 , 0) , kRWI_WRX , 0 , 68 , 1758), // #182
- INST(Ldsmaxl , BaseAtomicOp , (0b1011100001100000010000, kWX, 30, 0) , kRWI_WRX , 0 , 69 , 1766), // #183
- INST(Ldsmaxlb , BaseAtomicOp , (0b0011100001100000010000, kW , 0 , 0) , kRWI_WRX , 0 , 70 , 1774), // #184
- INST(Ldsmaxlh , BaseAtomicOp , (0b0111100001100000010000, kW , 0 , 0) , kRWI_WRX , 0 , 71 , 1783), // #185
- INST(Ldsmin , BaseAtomicOp , (0b1011100000100000010100, kWX, 30, 0) , kRWI_WRX , 0 , 72 , 1792), // #186
- INST(Ldsmina , BaseAtomicOp , (0b1011100010100000010100, kWX, 30, 1) , kRWI_WRX , 0 , 73 , 1799), // #187
- INST(Ldsminab , BaseAtomicOp , (0b0011100010100000010100, kW , 0 , 1) , kRWI_WRX , 0 , 74 , 1807), // #188
- INST(Ldsminah , BaseAtomicOp , (0b0111100010100000010100, kW , 0 , 1) , kRWI_WRX , 0 , 75 , 1816), // #189
- INST(Ldsminal , BaseAtomicOp , (0b1011100011100000010100, kWX, 30, 1) , kRWI_WRX , 0 , 76 , 1825), // #190
- INST(Ldsminalb , BaseAtomicOp , (0b0011100011100000010100, kW , 0 , 1) , kRWI_WRX , 0 , 77 , 1834), // #191
- INST(Ldsminalh , BaseAtomicOp , (0b0111100011100000010100, kW , 0 , 1) , kRWI_WRX , 0 , 78 , 1844), // #192
- INST(Ldsminb , BaseAtomicOp , (0b0011100000100000010100, kW , 0 , 0) , kRWI_WRX , 0 , 79 , 1854), // #193
- INST(Ldsminh , BaseAtomicOp , (0b0111100000100000010100, kW , 0 , 0) , kRWI_WRX , 0 , 80 , 1862), // #194
- INST(Ldsminl , BaseAtomicOp , (0b1011100001100000010100, kWX, 30, 0) , kRWI_WRX , 0 , 81 , 1870), // #195
- INST(Ldsminlb , BaseAtomicOp , (0b0011100001100000010100, kW , 0 , 0) , kRWI_WRX , 0 , 82 , 1878), // #196
- INST(Ldsminlh , BaseAtomicOp , (0b0111100001100000010100, kW , 0 , 0) , kRWI_WRX , 0 , 83 , 1887), // #197
- INST(Ldtr , BaseRM_SImm9 , (0b1011100001000000000010, 0b0000000000000000000000, kWX, kZR, 30, 0) , kRWI_W , 0 , 1 , 1896), // #198
- INST(Ldtrb , BaseRM_SImm9 , (0b0011100001000000000010, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_W , 0 , 2 , 1901), // #199
- INST(Ldtrh , BaseRM_SImm9 , (0b0111100001000000000010, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_W , 0 , 3 , 1907), // #200
- INST(Ldtrsb , BaseRM_SImm9 , (0b0011100011000000000010, 0b0000000000000000000000, kWX, kZR, 22, 0) , kRWI_W , 0 , 4 , 1913), // #201
- INST(Ldtrsh , BaseRM_SImm9 , (0b0111100011000000000010, 0b0000000000000000000000, kWX, kZR, 22, 0) , kRWI_W , 0 , 5 , 1920), // #202
- INST(Ldtrsw , BaseRM_SImm9 , (0b1011100010000000000010, 0b0000000000000000000000, kX , kZR, 0 , 0) , kRWI_W , 0 , 6 , 1927), // #203
- INST(Ldumax , BaseAtomicOp , (0b1011100000100000011000, kWX, 30, 0) , kRWI_WRX , 0 , 84 , 1934), // #204
- INST(Ldumaxa , BaseAtomicOp , (0b1011100010100000011000, kWX, 30, 1) , kRWI_WRX , 0 , 85 , 1941), // #205
- INST(Ldumaxab , BaseAtomicOp , (0b0011100010100000011000, kW , 0 , 1) , kRWI_WRX , 0 , 86 , 1949), // #206
- INST(Ldumaxah , BaseAtomicOp , (0b0111100010100000011000, kW , 0 , 1) , kRWI_WRX , 0 , 87 , 1958), // #207
- INST(Ldumaxal , BaseAtomicOp , (0b1011100011100000011000, kWX, 30, 1) , kRWI_WRX , 0 , 88 , 1967), // #208
- INST(Ldumaxalb , BaseAtomicOp , (0b0011100011100000011000, kW , 0 , 1) , kRWI_WRX , 0 , 89 , 1976), // #209
- INST(Ldumaxalh , BaseAtomicOp , (0b0111100011100000011000, kW , 0 , 1) , kRWI_WRX , 0 , 90 , 1986), // #210
- INST(Ldumaxb , BaseAtomicOp , (0b0011100000100000011000, kW , 0 , 0) , kRWI_WRX , 0 , 91 , 1996), // #211
- INST(Ldumaxh , BaseAtomicOp , (0b0111100000100000011000, kW , 0 , 0) , kRWI_WRX , 0 , 92 , 2004), // #212
- INST(Ldumaxl , BaseAtomicOp , (0b1011100001100000011000, kWX, 30, 0) , kRWI_WRX , 0 , 93 , 2012), // #213
- INST(Ldumaxlb , BaseAtomicOp , (0b0011100001100000011000, kW , 0 , 0) , kRWI_WRX , 0 , 94 , 2020), // #214
- INST(Ldumaxlh , BaseAtomicOp , (0b0111100001100000011000, kW , 0 , 0) , kRWI_WRX , 0 , 95 , 2029), // #215
- INST(Ldumin , BaseAtomicOp , (0b1011100000100000011100, kWX, 30, 0) , kRWI_WRX , 0 , 96 , 2038), // #216
- INST(Ldumina , BaseAtomicOp , (0b1011100010100000011100, kWX, 30, 1) , kRWI_WRX , 0 , 97 , 2045), // #217
- INST(Lduminab , BaseAtomicOp , (0b0011100010100000011100, kW , 0 , 1) , kRWI_WRX , 0 , 98 , 2053), // #218
- INST(Lduminah , BaseAtomicOp , (0b0111100010100000011100, kW , 0 , 1) , kRWI_WRX , 0 , 99 , 2062), // #219
- INST(Lduminal , BaseAtomicOp , (0b1011100011100000011100, kWX, 30, 1) , kRWI_WRX , 0 , 100, 2071), // #220
- INST(Lduminalb , BaseAtomicOp , (0b0011100011100000011100, kW , 0 , 1) , kRWI_WRX , 0 , 101, 2080), // #221
- INST(Lduminalh , BaseAtomicOp , (0b0111100011100000011100, kW , 0 , 1) , kRWI_WRX , 0 , 102, 2090), // #222
- INST(Lduminb , BaseAtomicOp , (0b0011100000100000011100, kW , 0 , 0) , kRWI_WRX , 0 , 103, 2100), // #223
- INST(Lduminh , BaseAtomicOp , (0b0111100000100000011100, kW , 0 , 0) , kRWI_WRX , 0 , 104, 2108), // #224
- INST(Lduminl , BaseAtomicOp , (0b1011100001100000011100, kWX, 30, 0) , kRWI_WRX , 0 , 105, 2116), // #225
- INST(Lduminlb , BaseAtomicOp , (0b0011100001100000011100, kW , 0 , 0) , kRWI_WRX , 0 , 106, 2124), // #226
- INST(Lduminlh , BaseAtomicOp , (0b0111100001100000011100, kW , 0 , 0) , kRWI_WRX , 0 , 107, 2133), // #227
- INST(Ldur , BaseRM_SImm9 , (0b1011100001000000000000, 0b0000000000000000000000, kWX, kZR, 30, 0) , kRWI_W , 0 , 7 , 2142), // #228
- INST(Ldurb , BaseRM_SImm9 , (0b0011100001000000000000, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_W , 0 , 8 , 2147), // #229
- INST(Ldurh , BaseRM_SImm9 , (0b0111100001000000000000, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_W , 0 , 9 , 2153), // #230
- INST(Ldursb , BaseRM_SImm9 , (0b0011100011000000000000, 0b0000000000000000000000, kWX, kZR, 22, 0) , kRWI_W , 0 , 10 , 2159), // #231
- INST(Ldursh , BaseRM_SImm9 , (0b0111100011000000000000, 0b0000000000000000000000, kWX, kZR, 22, 0) , kRWI_W , 0 , 11 , 2166), // #232
- INST(Ldursw , BaseRM_SImm9 , (0b1011100010000000000000, 0b0000000000000000000000, kWX, kZR, 0 , 0) , kRWI_W , 0 , 12 , 2173), // #233
- INST(Ldxp , BaseLdxp , (0b1000100001111111000000, kWX, 30) , kRWI_WW , 0 , 1 , 2180), // #234
- INST(Ldxr , BaseRM_NoImm , (0b1000100001011111011111, kWX, kZR, 30) , kRWI_W , 0 , 10 , 2185), // #235
- INST(Ldxrb , BaseRM_NoImm , (0b0000100001011111011111, kW , kZR, 0 ) , kRWI_W , 0 , 11 , 2190), // #236
- INST(Ldxrh , BaseRM_NoImm , (0b0100100001011111011111, kW , kZR, 0 ) , kRWI_W , 0 , 12 , 2196), // #237
- INST(Lsl , BaseShift , (0b0001101011000000001000, 0b0101001100000000000000, 0) , kRWI_W , 0 , 2 , 2880), // #238
- INST(Lslv , BaseShift , (0b0001101011000000001000, 0b0000000000000000000000, 0) , kRWI_W , 0 , 3 , 2202), // #239
- INST(Lsr , BaseShift , (0b0001101011000000001001, 0b0101001100000000011111, 0) , kRWI_W , 0 , 4 , 2207), // #240
- INST(Lsrv , BaseShift , (0b0001101011000000001001, 0b0000000000000000000000, 0) , kRWI_W , 0 , 5 , 2211), // #241
- INST(Madd , BaseRRRR , (0b0001101100000000000000, kWX, kZR, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 0 , 977 ), // #242
- INST(Mneg , BaseRRR , (0b0001101100000000111111, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 11 , 2216), // #243
- INST(Mov , BaseMov , (_) , kRWI_W , 0 , 0 , 949 ), // #244
- INST(Movk , BaseMovKNZ , (0b01110010100000000000000000000000) , kRWI_X , 0 , 0 , 2226), // #245
- INST(Movn , BaseMovKNZ , (0b00010010100000000000000000000000) , kRWI_W , 0 , 1 , 2231), // #246
- INST(Movz , BaseMovKNZ , (0b01010010100000000000000000000000) , kRWI_W , 0 , 2 , 2236), // #247
- INST(Mrs , BaseMrs , (_) , kRWI_W , 0 , 0 , 2241), // #248
- INST(Msr , BaseMsr , (_) , kRWI_W , 0 , 0 , 2245), // #249
- INST(Msub , BaseRRRR , (0b0001101100000000100000, kWX, kZR, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 1 , 984 ), // #250
- INST(Mul , BaseRRR , (0b0001101100000000011111, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 12 , 991 ), // #251
- INST(Mvn , BaseMvnNeg , (0b00101010001000000000001111100000) , kRWI_W , 0 , 0 , 2249), // #252
- INST(Neg , BaseMvnNeg , (0b01001011000000000000001111100000) , kRWI_W , 0 , 1 , 540 ), // #253
- INST(Negs , BaseMvnNeg , (0b01101011000000000000001111100000) , kRWI_W , 0 , 2 , 2258), // #254
- INST(Ngc , BaseRR , (0b01011010000000000000001111100000, kWX, kZR, 0, kWX, kZR, 16, true) , kRWI_W , 0 , 7 , 2263), // #255
- INST(Ngcs , BaseRR , (0b01111010000000000000001111100000, kWX, kZR, 0, kWX, kZR, 16, true) , kRWI_W , 0 , 8 , 2267), // #256
- INST(Nop , BaseOp , (0b11010101000000110010000000011111) , 0 , 0 , 13 , 2272), // #257
- INST(Orn , BaseLogical , (0b0101010001, 0b01100100, 1) , kRWI_W , 0 , 6 , 2280), // #258
- INST(Orr , BaseLogical , (0b0101010000, 0b01100100, 0) , kRWI_W , 0 , 7 , 2284), // #259
- INST(Pacda , BaseRR , (0b11011010110000010000100000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 9 , 2288), // #260
- INST(Pacdb , BaseRR , (0b11011010110000010000110000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 10 , 2294), // #261
- INST(Pacdza , BaseR , (0b11011010110000010010101111100000, kX, kZR, 0) , kRWI_X , 0 , 4 , 2300), // #262
- INST(Pacdzb , BaseR , (0b11011010110000010010111111100000, kX, kZR, 0) , kRWI_X , 0 , 5 , 2307), // #263
- INST(Pacga , BaseRRR , (0b1001101011000000001100, kX, kZR, kX, kZR, kX, kSP, false) , kRWI_W , 0 , 13 , 2314), // #264
- INST(Pssbb , BaseOp , (0b11010101000000110011010010011111) , 0 , 0 , 14 , 2338), // #265
- INST(Rbit , BaseRR , (0b01011010110000000000000000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 11 , 2364), // #266
- INST(Ret , BaseBranchReg , (0b11010110010111110000000000000000) , kRWI_R , 0 , 2 , 593 ), // #267
- INST(Rev , BaseRev , (_) , kRWI_W , 0 , 0 , 2369), // #268
- INST(Rev16 , BaseRR , (0b01011010110000000000010000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 12 , 2373), // #269
- INST(Rev32 , BaseRR , (0b11011010110000000000100000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 13 , 2379), // #270
- INST(Rev64 , BaseRR , (0b11011010110000000000110000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 14 , 2385), // #271
- INST(Ror , BaseShift , (0b0001101011000000001011, 0b0001001110000000000000, 1) , kRWI_W , 0 , 6 , 2391), // #272
- INST(Rorv , BaseShift , (0b0001101011000000001011, 0b0000000000000000000000, 1) , kRWI_W , 0 , 7 , 2395), // #273
- INST(Sbc , BaseRRR , (0b0101101000000000000000, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 14 , 2498), // #274
- INST(Sbcs , BaseRRR , (0b0111101000000000000000, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 15 , 2502), // #275
- INST(Sbfiz , BaseBfi , (0b00010011000000000000000000000000) , kRWI_W , 0 , 1 , 2507), // #276
- INST(Sbfm , BaseBfm , (0b00010011000000000000000000000000) , kRWI_W , 0 , 1 , 2513), // #277
- INST(Sbfx , BaseBfx , (0b00010011000000000000000000000000) , kRWI_W , 0 , 1 , 2518), // #278
- INST(Sdiv , BaseRRR , (0b0001101011000000000011, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 16 , 2529), // #279
- INST(Setf8 , BaseR , (0b00111010000000000000100000001101, kW, kZR, 5) , 0 , 0 , 6 , 2541), // #280
- INST(Setf16 , BaseR , (0b00111010000000000100100000001101, kW, kZR, 5) , 0 , 0 , 7 , 2534), // #281
- INST(Sev , BaseOp , (0b11010101000000110010000010011111) , 0 , 0 , 15 , 2547), // #282
- INST(Sevl , BaseOp , (0b11010101000000110010000010111111) , 0 , 0 , 16 , 2551), // #283
- INST(Smaddl , BaseRRRR , (0b1001101100100000000000, kX , kZR, kW , kZR, kW , kZR, kX , kZR, false) , kRWI_W , 0 , 2 , 2758), // #284
- INST(Smc , BaseOpImm , (0b11010100000000000000000000000011, 16, 5) , 0 , 0 , 11 , 53 ), // #285
- INST(Smnegl , BaseRRR , (0b1001101100100000111111, kX , kZR, kW , kZR, kW , kZR, false) , kRWI_W , 0 , 17 , 2815), // #286
- INST(Smsubl , BaseRRRR , (0b1001101100100000100000, kX , kZR, kW , kZR, kW , kZR, kX , kZR, false) , kRWI_W , 0 , 3 , 2827), // #287
- INST(Smulh , BaseRRR , (0b1001101101000000011111, kX , kZR, kX , kZR, kX , kZR, true) , kRWI_W , 0 , 18 , 2834), // #288
- INST(Smull , BaseRRR , (0b1001101100100000011111, kX , kZR, kW , kZR, kW , kZR, false) , kRWI_W , 0 , 19 , 2840), // #289
- INST(Ssbb , BaseOp , (0b11010101000000110011000010011111) , 0 , 0 , 17 , 2339), // #290
- INST(St2g , BaseRM_SImm9 , (0b1101100110100000000010, 0b1101100110100000000001, kX, kSP, 0, 4) , kRWI_RW , 0 , 13 , 3164), // #291
- INST(Stadd , BaseAtomicSt , (0b1011100000100000000000, kWX, 30) , kRWI_RX , 0 , 0 , 3177), // #292
- INST(Staddl , BaseAtomicSt , (0b1011100001100000000000, kWX, 30) , kRWI_RX , 0 , 1 , 3197), // #293
- INST(Staddb , BaseAtomicSt , (0b0011100000100000000000, kW , 0 ) , kRWI_RX , 0 , 2 , 3183), // #294
- INST(Staddlb , BaseAtomicSt , (0b0011100001100000000000, kW , 0 ) , kRWI_RX , 0 , 3 , 3204), // #295
- INST(Staddh , BaseAtomicSt , (0b0111100000100000000000, kW , 0 ) , kRWI_RX , 0 , 4 , 3190), // #296
- INST(Staddlh , BaseAtomicSt , (0b0111100001100000000000, kW , 0 ) , kRWI_RX , 0 , 5 , 3212), // #297
- INST(Stclr , BaseAtomicSt , (0b1011100000100000000100, kWX, 30) , kRWI_RX , 0 , 6 , 3220), // #298
- INST(Stclrl , BaseAtomicSt , (0b1011100001100000000100, kWX, 30) , kRWI_RX , 0 , 7 , 3240), // #299
- INST(Stclrb , BaseAtomicSt , (0b0011100000100000000100, kW , 0 ) , kRWI_RX , 0 , 8 , 3226), // #300
- INST(Stclrlb , BaseAtomicSt , (0b0011100001100000000100, kW , 0 ) , kRWI_RX , 0 , 9 , 3247), // #301
- INST(Stclrh , BaseAtomicSt , (0b0111100000100000000100, kW , 0 ) , kRWI_RX , 0 , 10 , 3233), // #302
- INST(Stclrlh , BaseAtomicSt , (0b0111100001100000000100, kW , 0 ) , kRWI_RX , 0 , 11 , 3255), // #303
- INST(Steor , BaseAtomicSt , (0b1011100000100000001000, kWX, 30) , kRWI_RX , 0 , 12 , 3263), // #304
- INST(Steorl , BaseAtomicSt , (0b1011100001100000001000, kWX, 30) , kRWI_RX , 0 , 13 , 3283), // #305
- INST(Steorb , BaseAtomicSt , (0b0011100000100000001000, kW , 0 ) , kRWI_RX , 0 , 14 , 3269), // #306
- INST(Steorlb , BaseAtomicSt , (0b0011100001100000001000, kW , 0 ) , kRWI_RX , 0 , 15 , 3290), // #307
- INST(Steorh , BaseAtomicSt , (0b0111100000100000001000, kW , 0 ) , kRWI_RX , 0 , 16 , 3276), // #308
- INST(Steorlh , BaseAtomicSt , (0b0111100001100000001000, kW , 0 ) , kRWI_RX , 0 , 17 , 3298), // #309
- INST(Stg , BaseRM_SImm9 , (0b1101100100100000000010, 0b1101100100100000000001, kX, kSP, 0, 4) , kRWI_RW , 0 , 14 , 3306), // #310
- INST(Stgm , BaseRM_NoImm , (0b1101100110100000000000, kX , kZR, 0 ) , kRWI_RW , 0 , 13 , 3310), // #311
- INST(Stgp , BaseLdpStp , (0b0110100100, 0b0110100010, kX, 0, 4) , kRWI_RRW , 0 , 3 , 3315), // #312
- INST(Stllr , BaseRM_NoImm , (0b1000100010011111011111, kWX, kZR, 30) , kRWI_RW , 0 , 14 , 3320), // #313
- INST(Stllrb , BaseRM_NoImm , (0b0000100010011111011111, kW , kZR, 0 ) , kRWI_RW , 0 , 15 , 3326), // #314
- INST(Stllrh , BaseRM_NoImm , (0b0100100010011111011111, kW , kZR, 0 ) , kRWI_RW , 0 , 16 , 3333), // #315
- INST(Stlr , BaseRM_NoImm , (0b1000100010011111111111, kWX, kZR, 30) , kRWI_RW , 0 , 17 , 3340), // #316
- INST(Stlrb , BaseRM_NoImm , (0b0000100010011111111111, kW , kZR, 0 ) , kRWI_RW , 0 , 18 , 3345), // #317
- INST(Stlrh , BaseRM_NoImm , (0b0100100010011111111111, kW , kZR, 0 ) , kRWI_RW , 0 , 19 , 3351), // #318
- INST(Stlxp , BaseStxp , (0b1000100000100000100000, kWX, 30) , kRWI_WRRX , 0 , 0 , 3357), // #319
- INST(Stlxr , BaseAtomicOp , (0b1000100000000000111111, kWX, 30, 1) , kRWI_WRX , 0 , 108, 3363), // #320
- INST(Stlxrb , BaseAtomicOp , (0b0000100000000000111111, kW , 0 , 1) , kRWI_WRX , 0 , 109, 3369), // #321
- INST(Stlxrh , BaseAtomicOp , (0b0100100000000000111111, kW , 0 , 1) , kRWI_WRX , 0 , 110, 3376), // #322
- INST(Stnp , BaseLdpStp , (0b0010100000, 0 , kWX, 31, 2) , kRWI_RRW , 0 , 4 , 3383), // #323
- INST(Stp , BaseLdpStp , (0b0010100100, 0b0010100010, kWX, 31, 2) , kRWI_RRW , 0 , 5 , 3388), // #324
- INST(Str , BaseLdSt , (0b1011100100, 0b10111000000, 0b10111000001, 0 , kWX, 30, 2, Inst::kIdStur) , kRWI_RW , 0 , 6 , 3392), // #325
- INST(Strb , BaseLdSt , (0b0011100100, 0b00111000000, 0b00111000001, 0 , kW , 30, 0, Inst::kIdSturb) , kRWI_RW , 0 , 7 , 3396), // #326
- INST(Strh , BaseLdSt , (0b0111100100, 0b01111000000, 0b01111000001, 0 , kWX, 30, 1, Inst::kIdSturh) , kRWI_RW , 0 , 8 , 3401), // #327
- INST(Stset , BaseAtomicSt , (0b1011100000100000001100, kWX, 30) , kRWI_RX , 0 , 18 , 3406), // #328
- INST(Stsetl , BaseAtomicSt , (0b1011100001100000001100, kWX, 30) , kRWI_RX , 0 , 19 , 3426), // #329
- INST(Stsetb , BaseAtomicSt , (0b0011100000100000001100, kW , 0 ) , kRWI_RX , 0 , 20 , 3412), // #330
- INST(Stsetlb , BaseAtomicSt , (0b0011100001100000001100, kW , 0 ) , kRWI_RX , 0 , 21 , 3433), // #331
- INST(Stseth , BaseAtomicSt , (0b0111100000100000001100, kW , 0 ) , kRWI_RX , 0 , 22 , 3419), // #332
- INST(Stsetlh , BaseAtomicSt , (0b0111100001100000001100, kW , 0 ) , kRWI_RX , 0 , 23 , 3441), // #333
- INST(Stsmax , BaseAtomicSt , (0b1011100000100000010000, kWX, 30) , kRWI_RX , 0 , 24 , 3449), // #334
- INST(Stsmaxl , BaseAtomicSt , (0b1011100001100000010000, kWX, 30) , kRWI_RX , 0 , 25 , 3472), // #335
- INST(Stsmaxb , BaseAtomicSt , (0b0011100000100000010000, kW , 0 ) , kRWI_RX , 0 , 26 , 3456), // #336
- INST(Stsmaxlb , BaseAtomicSt , (0b0011100001100000010000, kW , 0 ) , kRWI_RX , 0 , 27 , 3480), // #337
- INST(Stsmaxh , BaseAtomicSt , (0b0111100000100000010000, kW , 0 ) , kRWI_RX , 0 , 28 , 3464), // #338
- INST(Stsmaxlh , BaseAtomicSt , (0b0111100001100000010000, kW , 0 ) , kRWI_RX , 0 , 29 , 3489), // #339
- INST(Stsmin , BaseAtomicSt , (0b1011100000100000010100, kWX, 30) , kRWI_RX , 0 , 30 , 3498), // #340
- INST(Stsminl , BaseAtomicSt , (0b1011100001100000010100, kWX, 30) , kRWI_RX , 0 , 31 , 3521), // #341
- INST(Stsminb , BaseAtomicSt , (0b0011100000100000010100, kW , 0 ) , kRWI_RX , 0 , 32 , 3505), // #342
- INST(Stsminlb , BaseAtomicSt , (0b0011100001100000010100, kW , 0 ) , kRWI_RX , 0 , 33 , 3529), // #343
- INST(Stsminh , BaseAtomicSt , (0b0111100000100000010100, kW , 0 ) , kRWI_RX , 0 , 34 , 3513), // #344
- INST(Stsminlh , BaseAtomicSt , (0b0111100001100000010100, kW , 0 ) , kRWI_RX , 0 , 35 , 3538), // #345
- INST(Sttr , BaseRM_SImm9 , (0b1011100000000000000010, 0b0000000000000000000000, kWX, kZR, 30, 0) , kRWI_RW , 0 , 15 , 3547), // #346
- INST(Sttrb , BaseRM_SImm9 , (0b0011100000000000000010, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_RW , 0 , 16 , 3552), // #347
- INST(Sttrh , BaseRM_SImm9 , (0b0111100000000000000010, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_RW , 0 , 17 , 3558), // #348
- INST(Stumax , BaseAtomicSt , (0b1011100000100000011000, kWX, 30) , kRWI_RX , 0 , 36 , 3564), // #349
- INST(Stumaxl , BaseAtomicSt , (0b1011100001100000011000, kWX, 30) , kRWI_RX , 0 , 37 , 3587), // #350
- INST(Stumaxb , BaseAtomicSt , (0b0011100000100000011000, kW , 0 ) , kRWI_RX , 0 , 38 , 3571), // #351
- INST(Stumaxlb , BaseAtomicSt , (0b0011100001100000011000, kW , 0 ) , kRWI_RX , 0 , 39 , 3595), // #352
- INST(Stumaxh , BaseAtomicSt , (0b0111100000100000011000, kW , 0 ) , kRWI_RX , 0 , 40 , 3579), // #353
- INST(Stumaxlh , BaseAtomicSt , (0b0111100001100000011000, kW , 0 ) , kRWI_RX , 0 , 41 , 3604), // #354
- INST(Stumin , BaseAtomicSt , (0b1011100000100000011100, kWX, 30) , kRWI_RX , 0 , 42 , 3613), // #355
- INST(Stuminl , BaseAtomicSt , (0b1011100001100000011100, kWX, 30) , kRWI_RX , 0 , 43 , 3636), // #356
- INST(Stuminb , BaseAtomicSt , (0b0011100000100000011100, kW , 0 ) , kRWI_RX , 0 , 44 , 3620), // #357
- INST(Stuminlb , BaseAtomicSt , (0b0011100001100000011100, kW , 0 ) , kRWI_RX , 0 , 45 , 3644), // #358
- INST(Stuminh , BaseAtomicSt , (0b0111100000100000011100, kW , 0 ) , kRWI_RX , 0 , 46 , 3628), // #359
- INST(Stuminlh , BaseAtomicSt , (0b0111100001100000011100, kW , 0 ) , kRWI_RX , 0 , 47 , 3653), // #360
- INST(Stur , BaseRM_SImm9 , (0b1011100000000000000000, 0b0000000000000000000000, kWX, kZR, 30, 0) , kRWI_RW , 0 , 18 , 3662), // #361
- INST(Sturb , BaseRM_SImm9 , (0b0011100000000000000000, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_RW , 0 , 19 , 3667), // #362
- INST(Sturh , BaseRM_SImm9 , (0b0111100000000000000000, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_RW , 0 , 20 , 3673), // #363
- INST(Stxp , BaseStxp , (0b1000100000100000000000, kWX, 30) , kRWI_WRRW , 0 , 1 , 3679), // #364
- INST(Stxr , BaseStx , (0b1000100000000000011111, kWX, 30) , kRWI_WRW , 0 , 0 , 3684), // #365
- INST(Stxrb , BaseStx , (0b0000100000000000011111, kW , 0 ) , kRWI_WRW , 0 , 1 , 3689), // #366
- INST(Stxrh , BaseStx , (0b0100100000000000011111, kW , 0 ) , kRWI_WRW , 0 , 2 , 3695), // #367
- INST(Stz2g , BaseRM_SImm9 , (0b1101100111100000000010, 0b1101100111100000000001, kX , kSP, 0, 4) , kRWI_RW , 0 , 21 , 3701), // #368
- INST(Stzg , BaseRM_SImm9 , (0b1101100101100000000010, 0b1101100101100000000001, kX , kSP, 0, 4) , kRWI_RW , 0 , 22 , 3707), // #369
- INST(Stzgm , BaseRM_NoImm , (0b1101100100100000000000, kX , kZR, 0) , kRWI_RW , 0 , 20 , 3712), // #370
- INST(Sub , BaseAddSub , (0b1001011000, 0b1001011001, 0b1010001) , kRWI_X , 0 , 2 , 985 ), // #371
- INST(Subg , BaseRRII , (0b1101000110000000000000, kX, kSP, kX, kSP, 6, 4, 16, 4, 0, 10) , kRWI_W , 0 , 1 , 3718), // #372
- INST(Subp , BaseRRR , (0b1001101011000000000000, kX, kZR, kX, kSP, kX, kSP, false) , kRWI_W , 0 , 20 , 3723), // #373
- INST(Subps , BaseRRR , (0b1011101011000000000000, kX, kZR, kX, kSP, kX, kSP, false) , kRWI_W , 0 , 21 , 3728), // #374
- INST(Subs , BaseAddSub , (0b1101011000, 0b1101011001, 0b1110001) , kRWI_X , 0 , 3 , 3734), // #375
- INST(Svc , BaseOpImm , (0b11010100000000000000000000000001, 16, 5) , 0 , 0 , 12 , 3752), // #376
- INST(Swp , BaseAtomicOp , (0b1011100000100000100000, kWX, 30, 1) , kRWI_RWX , 0 , 111, 3756), // #377
- INST(Swpa , BaseAtomicOp , (0b1011100010100000100000, kWX, 30, 1) , kRWI_RWX , 0 , 112, 3760), // #378
- INST(Swpab , BaseAtomicOp , (0b0011100010100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 113, 3765), // #379
- INST(Swpah , BaseAtomicOp , (0b0111100010100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 114, 3771), // #380
- INST(Swpal , BaseAtomicOp , (0b1011100011100000100000, kWX, 30, 1) , kRWI_RWX , 0 , 115, 3777), // #381
- INST(Swpalb , BaseAtomicOp , (0b0011100011100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 116, 3783), // #382
- INST(Swpalh , BaseAtomicOp , (0b0111100011100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 117, 3790), // #383
- INST(Swpb , BaseAtomicOp , (0b0011100000100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 118, 3797), // #384
- INST(Swph , BaseAtomicOp , (0b0111100000100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 119, 3802), // #385
- INST(Swpl , BaseAtomicOp , (0b1011100001100000100000, kWX, 30, 1) , kRWI_RWX , 0 , 120, 3807), // #386
- INST(Swplb , BaseAtomicOp , (0b0011100001100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 121, 3812), // #387
- INST(Swplh , BaseAtomicOp , (0b0111100001100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 122, 3818), // #388
- INST(Sxtb , BaseExtend , (0b0001001100000000000111, kWX, 0) , kRWI_W , 0 , 0 , 3824), // #389
- INST(Sxth , BaseExtend , (0b0001001100000000001111, kWX, 0) , kRWI_W , 0 , 1 , 3829), // #390
- INST(Sxtw , BaseExtend , (0b1001001101000000011111, kX , 0) , kRWI_W , 0 , 2 , 3845), // #391
- INST(Sys , BaseSys , (_) , kRWI_W , 0 , 0 , 3850), // #392
- INST(Tlbi , BaseAtDcIcTlbi , (0b00011110000000, 0b00010000000000, false) , kRWI_RX , 0 , 3 , 3871), // #393
- INST(Tst , BaseTst , (0b1101010000, 0b111001000) , kRWI_R , 0 , 0 , 437 ), // #394
- INST(Tbnz , BaseBranchTst , (0b00110111000000000000000000000000) , kRWI_R , 0 , 0 , 3858), // #395
- INST(Tbz , BaseBranchTst , (0b00110110000000000000000000000000) , kRWI_R , 0 , 1 , 3867), // #396
- INST(Ubfiz , BaseBfi , (0b01010011000000000000000000000000) , kRWI_W , 0 , 2 , 3969), // #397
- INST(Ubfm , BaseBfm , (0b01010011000000000000000000000000) , kRWI_W , 0 , 2 , 3975), // #398
- INST(Ubfx , BaseBfx , (0b01010011000000000000000000000000) , kRWI_W , 0 , 2 , 3980), // #399
- INST(Udf , BaseOpImm , (0b00000000000000000000000000000000, 16, 0) , 0 , 0 , 13 , 3991), // #400
- INST(Udiv , BaseRRR , (0b0001101011000000000010, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 22 , 3995), // #401
- INST(Umaddl , BaseRRRR , (0b1001101110100000000000, kX , kZR, kW , kZR, kW , kZR, kX , kZR, false) , kRWI_W , 0 , 4 , 4012), // #402
- INST(Umnegl , BaseRRR , (0b1001101110100000111111, kX , kZR, kW , kZR, kW , kZR, false) , kRWI_W , 0 , 23 , 4075), // #403
- INST(Umull , BaseRRR , (0b1001101110100000011111, kX , kZR, kW , kZR, kW , kZR, false) , kRWI_W , 0 , 24 , 4100), // #404
- INST(Umulh , BaseRRR , (0b1001101111000000011111, kX , kZR, kX , kZR, kX , kZR, false) , kRWI_W , 0 , 25 , 4094), // #405
- INST(Umsubl , BaseRRRR , (0b1001101110100000100000, kX , kZR, kW , kZR, kW , kZR, kX , kZR, false) , kRWI_W , 0 , 5 , 4087), // #406
- INST(Uxtb , BaseExtend , (0b0101001100000000000111, kW, 1) , kRWI_W , 0 , 3 , 4291), // #407
- INST(Uxth , BaseExtend , (0b0101001100000000001111, kW, 1) , kRWI_W , 0 , 4 , 4296), // #408
- INST(Wfe , BaseOp , (0b11010101000000110010000001011111) , 0 , 0 , 18 , 4322), // #409
- INST(Wfi , BaseOp , (0b11010101000000110010000001111111) , 0 , 0 , 19 , 4326), // #410
- INST(Xaflag , BaseOp , (0b11010101000000000100000000111111) , 0 , 0 , 20 , 4330), // #411
- INST(Xpacd , BaseR , (0b11011010110000010100011111100000, kX, kZR, 0) , kRWI_X , 0 , 8 , 4341), // #412
- INST(Xpaci , BaseR , (0b11011010110000010100001111100000, kX, kZR, 0) , kRWI_X , 0 , 9 , 4347), // #413
- INST(Xpaclri , BaseOp , (0b11010101000000110010000011111111) , kRWI_X , 0 , 21 , 4353), // #414
- INST(Yield , BaseOp , (0b11010101000000110010000000111111) , 0 , 0 , 22 , 4361), // #415
- INST(Abs_v , ISimdVV , (0b0000111000100000101110, kVO_V_Any) , kRWI_W , 0 , 0 , 2855), // #416
- INST(Add_v , ISimdVVV , (0b0000111000100000100001, kVO_V_Any) , kRWI_W , 0 , 0 , 978 ), // #417
- INST(Addhn_v , ISimdVVV , (0b0000111000100000010000, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 1 , 2345), // #418
- INST(Addhn2_v , ISimdVVV , (0b0100111000100000010000, kVO_V_B16H8S4) , kRWI_W , F(Narrow) , 2 , 2352), // #419
- INST(Addp_v , ISimdPair , (0b0101111000110001101110, 0b0000111000100000101111, kVO_V_Any) , kRWI_W , F(Pair) , 0 , 638 ), // #420
- INST(Addv_v , ISimdSV , (0b0000111000110001101110, kVO_V_BH_4S) , kRWI_W , 0 , 0 , 20 ), // #421
- INST(Aesd_v , ISimdVVx , (0b0100111000101000010110, kOp_V16B, kOp_V16B) , kRWI_X , 0 , 0 , 34 ), // #422
- INST(Aese_v , ISimdVVx , (0b0100111000101000010010, kOp_V16B, kOp_V16B) , kRWI_X , 0 , 1 , 39 ), // #423
- INST(Aesimc_v , ISimdVVx , (0b0100111000101000011110, kOp_V16B, kOp_V16B) , kRWI_W , 0 , 2 , 44 ), // #424
- INST(Aesmc_v , ISimdVVx , (0b0100111000101000011010, kOp_V16B, kOp_V16B) , kRWI_W , 0 , 3 , 51 ), // #425
- INST(And_v , ISimdVVV , (0b0000111000100000000111, kVO_V_B) , kRWI_W , 0 , 3 , 57 ), // #426
- INST(Bcax_v , ISimdVVVV , (0b1100111000100000000000, kVO_V_B16) , kRWI_W , 0 , 0 , 187 ), // #427
- INST(Bfcvt_v , ISimdVVx , (0b0001111001100011010000, kOp_H, kOp_S) , kRWI_W , 0 , 4 , 196 ), // #428
- INST(Bfcvtn_v , ISimdVVx , (0b0000111010100001011010, kOp_V4H, kOp_V4S) , kRWI_W , F(Narrow) , 5 , 202 ), // #429
- INST(Bfcvtn2_v , ISimdVVx , (0b0100111010100001011010, kOp_V8H, kOp_V4S) , kRWI_W , F(Narrow) , 6 , 209 ), // #430
- INST(Bfdot_v , SimdDot , (0b0010111001000000111111, 0b0000111101000000111100, kET_S, kET_H, kET_2H) , kRWI_X , 0 , 0 , 217 ), // #431
- INST(Bfmlalb_v , SimdFmlal , (0b0010111011000000111111, 0b0000111111000000111100, 0, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 0 , 227 ), // #432
- INST(Bfmlalt_v , SimdFmlal , (0b0110111011000000111111, 0b0100111111000000111100, 0, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 1 , 235 ), // #433
- INST(Bfmmla_v , ISimdVVVx , (0b0110111001000000111011, kOp_V4S, kOp_V8H, kOp_V8H) , kRWI_X , F(Long) , 0 , 243 ), // #434
- INST(Bic_v , SimdBicOrr , (0b0000111001100000000111, 0b0010111100000000000001) , kRWI_W , 0 , 0 , 256 ), // #435
- INST(Bif_v , ISimdVVV , (0b0010111011100000000111, kVO_V_B) , kRWI_X , 0 , 4 , 265 ), // #436
- INST(Bit_v , ISimdVVV , (0b0010111010100000000111, kVO_V_B) , kRWI_X , 0 , 5 , 2365), // #437
- INST(Bsl_v , ISimdVVV , (0b0010111001100000000111, kVO_V_B) , kRWI_X , 0 , 6 , 280 ), // #438
- INST(Cls_v , ISimdVV , (0b0000111000100000010010, kVO_V_BHS) , kRWI_W , 0 , 1 , 412 ), // #439
- INST(Clz_v , ISimdVV , (0b0010111000100000010010, kVO_V_BHS) , kRWI_W , 0 , 2 , 416 ), // #440
- INST(Cmeq_v , SimdCmp , (0b0010111000100000100011, 0b0000111000100000100110, kVO_V_Any) , kRWI_W , 0 , 0 , 663 ), // #441
- INST(Cmge_v , SimdCmp , (0b0000111000100000001111, 0b0010111000100000100010, kVO_V_Any) , kRWI_W , 0 , 1 , 669 ), // #442
- INST(Cmgt_v , SimdCmp , (0b0000111000100000001101, 0b0000111000100000100010, kVO_V_Any) , kRWI_W , 0 , 2 , 675 ), // #443
- INST(Cmhi_v , SimdCmp , (0b0010111000100000001101, 0b0000000000000000000000, kVO_V_Any) , kRWI_W , 0 , 3 , 420 ), // #444
- INST(Cmhs_v , SimdCmp , (0b0010111000100000001111, 0b0000000000000000000000, kVO_V_Any) , kRWI_W , 0 , 4 , 425 ), // #445
- INST(Cmle_v , SimdCmp , (0b0000000000000000000000, 0b0010111000100000100110, kVO_V_Any) , kRWI_W , 0 , 5 , 687 ), // #446
- INST(Cmlt_v , SimdCmp , (0b0000000000000000000000, 0b0000111000100000101010, kVO_V_Any) , kRWI_W , 0 , 6 , 693 ), // #447
- INST(Cmtst_v , ISimdVVV , (0b0000111000100000100011, kVO_V_Any) , kRWI_W , 0 , 7 , 435 ), // #448
- INST(Cnt_v , ISimdVV , (0b0000111000100000010110, kVO_V_B) , kRWI_W , 0 , 3 , 446 ), // #449
- INST(Dup_v , SimdDup , (_) , kRWI_W , 0 , 0 , 579 ), // #450
- INST(Eor_v , ISimdVVV , (0b0010111000100000000111, kVO_V_B) , kRWI_W , 0 , 8 , 1418), // #451
- INST(Eor3_v , ISimdVVVV , (0b1100111000000000000000, kVO_V_B16) , kRWI_W , 0 , 1 , 587 ), // #452
- INST(Ext_v , ISimdVVVI , (0b0010111000000000000000, kVO_V_B, 4, 11, 1) , kRWI_W , 0 , 0 , 601 ), // #453
- INST(Fabd_v , FSimdVVV , (0b0111111010100000110101, kHF_C, 0b0010111010100000110101, kHF_C) , kRWI_W , 0 , 0 , 610 ), // #454
- INST(Fabs_v , FSimdVV , (0b0001111000100000110000, kHF_A, 0b0000111010100000111110, kHF_B) , kRWI_W , 0 , 0 , 615 ), // #455
- INST(Facge_v , FSimdVVV , (0b0111111000100000111011, kHF_C, 0b0010111000100000111011, kHF_C) , kRWI_W , 0 , 1 , 620 ), // #456
- INST(Facgt_v , FSimdVVV , (0b0111111010100000111011, kHF_C, 0b0010111010100000111011, kHF_C) , kRWI_W , 0 , 2 , 626 ), // #457
- INST(Fadd_v , FSimdVVV , (0b0001111000100000001010, kHF_A, 0b0000111000100000110101, kHF_C) , kRWI_W , 0 , 3 , 632 ), // #458
- INST(Faddp_v , FSimdPair , (0b0111111000110000110110, 0b0010111000100000110101) , kRWI_W , 0 , 0 , 637 ), // #459
- INST(Fcadd_v , SimdFcadd , (0b0010111000000000111001) , kRWI_W , 0 , 0 , 643 ), // #460
- INST(Fccmp_v , SimdFccmpFccmpe , (0b00011110001000000000010000000000) , kRWI_R , 0 , 0 , 649 ), // #461
- INST(Fccmpe_v , SimdFccmpFccmpe , (0b00011110001000000000010000010000) , kRWI_R , 0 , 1 , 655 ), // #462
- INST(Fcmeq_v , SimdFcm , (0b0000111000100000111001, kHF_C, 0b0000111010100000110110) , kRWI_W , 0 , 0 , 662 ), // #463
- INST(Fcmge_v , SimdFcm , (0b0010111000100000111001, kHF_C, 0b0010111010100000110010) , kRWI_W , 0 , 1 , 668 ), // #464
- INST(Fcmgt_v , SimdFcm , (0b0010111010100000111001, kHF_C, 0b0000111010100000110010) , kRWI_W , 0 , 2 , 674 ), // #465
- INST(Fcmla_v , SimdFcmla , (0b0010111000000000110001, 0b0010111100000000000100) , kRWI_X , 0 , 0 , 680 ), // #466
- INST(Fcmle_v , SimdFcm , (0b0000000000000000000000, kHF_C, 0b0010111010100000110110) , kRWI_W , 0 , 3 , 686 ), // #467
- INST(Fcmlt_v , SimdFcm , (0b0000000000000000000000, kHF_C, 0b0000111010100000111010) , kRWI_W , 0 , 4 , 692 ), // #468
- INST(Fcmp_v , SimdFcmpFcmpe , (0b00011110001000000010000000000000) , kRWI_R , 0 , 0 , 698 ), // #469
- INST(Fcmpe_v , SimdFcmpFcmpe , (0b00011110001000000010000000010000) , kRWI_R , 0 , 1 , 703 ), // #470
- INST(Fcsel_v , SimdFcsel , (_) , kRWI_W , 0 , 0 , 709 ), // #471
- INST(Fcvt_v , SimdFcvt , (_) , kRWI_W , 0 , 0 , 197 ), // #472
- INST(Fcvtas_v , SimdFcvtSV , (0b0000111000100001110010, 0b0000000000000000000000, 0b0001111000100100000000, 1) , kRWI_W , 0 , 0 , 715 ), // #473
- INST(Fcvtau_v , SimdFcvtSV , (0b0010111000100001110010, 0b0000000000000000000000, 0b0001111000100101000000, 1) , kRWI_W , 0 , 1 , 722 ), // #474
- INST(Fcvtl_v , SimdFcvtLN , (0b0000111000100001011110, 0, 0) , kRWI_W , F(Long) , 0 , 729 ), // #475
- INST(Fcvtl2_v , SimdFcvtLN , (0b0100111000100001011110, 0, 0) , kRWI_W , F(Long) , 1 , 735 ), // #476
- INST(Fcvtms_v , SimdFcvtSV , (0b0000111000100001101110, 0b0000000000000000000000, 0b0001111000110000000000, 1) , kRWI_W , 0 , 2 , 742 ), // #477
- INST(Fcvtmu_v , SimdFcvtSV , (0b0010111000100001101110, 0b0000000000000000000000, 0b0001111000110001000000, 1) , kRWI_W , 0 , 3 , 749 ), // #478
- INST(Fcvtn_v , SimdFcvtLN , (0b0000111000100001011010, 0, 0) , kRWI_W , F(Narrow) , 2 , 203 ), // #479
- INST(Fcvtn2_v , SimdFcvtLN , (0b0100111000100001011010, 0, 0) , kRWI_X , F(Narrow) , 3 , 210 ), // #480
- INST(Fcvtns_v , SimdFcvtSV , (0b0000111000100001101010, 0b0000000000000000000000, 0b0001111000100000000000, 1) , kRWI_W , 0 , 4 , 756 ), // #481
- INST(Fcvtnu_v , SimdFcvtSV , (0b0010111000100001101010, 0b0000000000000000000000, 0b0001111000100001000000, 1) , kRWI_W , 0 , 5 , 763 ), // #482
- INST(Fcvtps_v , SimdFcvtSV , (0b0000111010100001101010, 0b0000000000000000000000, 0b0001111000101000000000, 1) , kRWI_W , 0 , 6 , 770 ), // #483
- INST(Fcvtpu_v , SimdFcvtSV , (0b0010111010100001101010, 0b0000000000000000000000, 0b0001111000101001000000, 1) , kRWI_W , 0 , 7 , 777 ), // #484
- INST(Fcvtxn_v , SimdFcvtLN , (0b0010111000100001011010, 1, 1) , kRWI_W , F(Narrow) , 4 , 784 ), // #485
- INST(Fcvtxn2_v , SimdFcvtLN , (0b0110111000100001011010, 1, 0) , kRWI_X , F(Narrow) , 5 , 791 ), // #486
- INST(Fcvtzs_v , SimdFcvtSV , (0b0000111010100001101110, 0b0000111100000000111111, 0b0001111000111000000000, 1) , kRWI_W , 0 , 8 , 799 ), // #487
- INST(Fcvtzu_v , SimdFcvtSV , (0b0010111010100001101110, 0b0010111100000000111111, 0b0001111000111001000000, 1) , kRWI_W , 0 , 9 , 806 ), // #488
- INST(Fdiv_v , FSimdVVV , (0b0001111000100000000110, kHF_A, 0b0010111000100000111111, kHF_C) , kRWI_W , 0 , 4 , 813 ), // #489
- INST(Fjcvtzs_v , ISimdVVx , (0b0001111001111110000000, kOp_GpW, kOp_D) , kRWI_W , 0 , 7 , 818 ), // #490
- INST(Fmadd_v , FSimdVVVV , (0b0001111100000000000000, kHF_A, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 0 , 826 ), // #491
- INST(Fmax_v , FSimdVVV , (0b0001111000100000010010, kHF_A, 0b0000111000100000111101, kHF_C) , kRWI_W , 0 , 5 , 832 ), // #492
- INST(Fmaxnm_v , FSimdVVV , (0b0001111000100000011010, kHF_A, 0b0000111000100000110001, kHF_C) , kRWI_W , 0 , 6 , 837 ), // #493
- INST(Fmaxnmp_v , FSimdPair , (0b0111111000110000110010, 0b0010111000100000110001) , kRWI_W , 0 , 1 , 844 ), // #494
- INST(Fmaxnmv_v , FSimdSV , (0b0010111000110000110010) , kRWI_W , 0 , 0 , 852 ), // #495
- INST(Fmaxp_v , FSimdPair , (0b0111111000110000111110, 0b0010111000100000111101) , kRWI_W , 0 , 2 , 860 ), // #496
- INST(Fmaxv_v , FSimdSV , (0b0010111000110000111110) , kRWI_W , 0 , 1 , 866 ), // #497
- INST(Fmin_v , FSimdVVV , (0b0001111000100000010110, kHF_A, 0b0000111010100000111101, kHF_C) , kRWI_W , 0 , 7 , 872 ), // #498
- INST(Fminnm_v , FSimdVVV , (0b0001111000100000011110, kHF_A, 0b0000111010100000110001, kHF_C) , kRWI_W , 0 , 8 , 877 ), // #499
- INST(Fminnmp_v , FSimdPair , (0b0111111010110000110010, 0b0010111010100000110001) , kRWI_W , 0 , 3 , 884 ), // #500
- INST(Fminnmv_v , FSimdSV , (0b0010111010110000110010) , kRWI_W , 0 , 2 , 892 ), // #501
- INST(Fminp_v , FSimdPair , (0b0111111010110000111110, 0b0010111010100000111101) , kRWI_W , 0 , 4 , 900 ), // #502
- INST(Fminv_v , FSimdSV , (0b0010111010110000111110) , kRWI_W , 0 , 3 , 906 ), // #503
- INST(Fmla_v , FSimdVVVe , (0b0000000000000000000000, kHF_N, 0b0000111000100000110011, 0b0000111110000000000100) , kRWI_X , F(VH0_15) , 0 , 912 ), // #504
- INST(Fmlal_v , SimdFmlal , (0b0000111000100000111011, 0b0000111110000000000000, 1, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 2 , 917 ), // #505
- INST(Fmlal2_v , SimdFmlal , (0b0010111000100000110011, 0b0010111110000000100000, 1, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 3 , 923 ), // #506
- INST(Fmls_v , FSimdVVVe , (0b0000000000000000000000, kHF_N, 0b0000111010100000110011, 0b0000111110000000010100) , kRWI_X , F(VH0_15) , 1 , 930 ), // #507
- INST(Fmlsl_v , SimdFmlal , (0b0000111010100000111011, 0b0000111110000000010000, 1, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 4 , 935 ), // #508
- INST(Fmlsl2_v , SimdFmlal , (0b0010111010100000110011, 0b0010111110000000110000, 1, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 5 , 941 ), // #509
- INST(Fmov_v , SimdFmov , (_) , kRWI_W , 0 , 0 , 948 ), // #510
- INST(Fmsub_v , FSimdVVVV , (0b0001111100000000100000, kHF_A, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 1 , 953 ), // #511
- INST(Fmul_v , FSimdVVVe , (0b0001111000100000000010, kHF_A, 0b0010111000100000110111, 0b0000111110000000100100) , kRWI_W , F(VH0_15) , 2 , 959 ), // #512
- INST(Fmulx_v , FSimdVVVe , (0b0101111000100000110111, kHF_C, 0b0000111000100000110111, 0b0010111110000000100100) , kRWI_W , F(VH0_15) , 3 , 964 ), // #513
- INST(Fneg_v , FSimdVV , (0b0001111000100001010000, kHF_A, 0b0010111010100000111110, kHF_B) , kRWI_W , 0 , 1 , 970 ), // #514
- INST(Fnmadd_v , FSimdVVVV , (0b0001111100100000000000, kHF_A, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 2 , 975 ), // #515
- INST(Fnmsub_v , FSimdVVVV , (0b0001111100100000100000, kHF_A, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 3 , 982 ), // #516
- INST(Fnmul_v , FSimdVVV , (0b0001111000100000100010, kHF_A, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 9 , 989 ), // #517
- INST(Frecpe_v , FSimdVV , (0b0101111010100001110110, kHF_B, 0b0000111010100001110110, kHF_B) , kRWI_W , 0 , 2 , 995 ), // #518
- INST(Frecps_v , FSimdVVV , (0b0101111000100000111111, kHF_C, 0b0000111000100000111111, kHF_C) , kRWI_W , 0 , 10 , 1002), // #519
- INST(Frecpx_v , FSimdVV , (0b0101111010100001111110, kHF_B, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 3 , 1009), // #520
- INST(Frint32x_v , FSimdVV , (0b0001111000101000110000, kHF_N, 0b0010111000100001111010, kHF_N) , kRWI_W , 0 , 4 , 1016), // #521
- INST(Frint32z_v , FSimdVV , (0b0001111000101000010000, kHF_N, 0b0000111000100001111010, kHF_N) , kRWI_W , 0 , 5 , 1025), // #522
- INST(Frint64x_v , FSimdVV , (0b0001111000101001110000, kHF_N, 0b0010111000100001111110, kHF_N) , kRWI_W , 0 , 6 , 1034), // #523
- INST(Frint64z_v , FSimdVV , (0b0001111000101001010000, kHF_N, 0b0000111000100001111110, kHF_N) , kRWI_W , 0 , 7 , 1043), // #524
- INST(Frinta_v , FSimdVV , (0b0001111000100110010000, kHF_A, 0b0010111000100001100010, kHF_B) , kRWI_W , 0 , 8 , 1052), // #525
- INST(Frinti_v , FSimdVV , (0b0001111000100111110000, kHF_A, 0b0010111010100001100110, kHF_B) , kRWI_W , 0 , 9 , 1059), // #526
- INST(Frintm_v , FSimdVV , (0b0001111000100101010000, kHF_A, 0b0000111000100001100110, kHF_B) , kRWI_W , 0 , 10 , 1066), // #527
- INST(Frintn_v , FSimdVV , (0b0001111000100100010000, kHF_A, 0b0000111000100001100010, kHF_B) , kRWI_W , 0 , 11 , 1073), // #528
- INST(Frintp_v , FSimdVV , (0b0001111000100100110000, kHF_A, 0b0000111010100001100010, kHF_B) , kRWI_W , 0 , 12 , 1080), // #529
- INST(Frintx_v , FSimdVV , (0b0001111000100111010000, kHF_A, 0b0010111000100001100110, kHF_B) , kRWI_W , 0 , 13 , 1087), // #530
- INST(Frintz_v , FSimdVV , (0b0001111000100101110000, kHF_A, 0b0000111010100001100110, kHF_B) , kRWI_W , 0 , 14 , 1094), // #531
- INST(Frsqrte_v , FSimdVV , (0b0111111010100001110110, kHF_B, 0b0010111010100001110110, kHF_B) , kRWI_W , 0 , 15 , 1101), // #532
- INST(Frsqrts_v , FSimdVVV , (0b0101111010100000111111, kHF_C, 0b0000111010100000111111, kHF_C) , kRWI_W , 0 , 11 , 1109), // #533
- INST(Fsqrt_v , FSimdVV , (0b0001111000100001110000, kHF_A, 0b0010111010100001111110, kHF_B) , kRWI_W , 0 , 16 , 1117), // #534
- INST(Fsub_v , FSimdVVV , (0b0001111000100000001110, kHF_A, 0b0000111010100000110101, kHF_C) , kRWI_W , 0 , 12 , 1123), // #535
- INST(Ins_v , SimdIns , (_) , kRWI_X , 0 , 0 , 1145), // #536
- INST(Ld1_v , SimdLdNStN , (0b0000110101000000000000, 0b0000110001000000001000, 1, 0) , kRWI_LDn , F(Consecutive) , 0 , 1153), // #537
- INST(Ld1r_v , SimdLdNStN , (0b0000110101000000110000, 0b0000000000000000000000, 1, 1) , kRWI_LDn , F(Consecutive) , 1 , 1157), // #538
- INST(Ld2_v , SimdLdNStN , (0b0000110101100000000000, 0b0000110001000000100000, 2, 0) , kRWI_LDn , F(Consecutive) , 2 , 1162), // #539
- INST(Ld2r_v , SimdLdNStN , (0b0000110101100000110000, 0b0000000000000000000000, 2, 1) , kRWI_LDn , F(Consecutive) , 3 , 1166), // #540
- INST(Ld3_v , SimdLdNStN , (0b0000110101000000001000, 0b0000110001000000010000, 3, 0) , kRWI_LDn , F(Consecutive) , 4 , 1171), // #541
- INST(Ld3r_v , SimdLdNStN , (0b0000110101000000111000, 0b0000000000000000000000, 3, 1) , kRWI_LDn , F(Consecutive) , 5 , 1175), // #542
- INST(Ld4_v , SimdLdNStN , (0b0000110101100000001000, 0b0000110001000000000000, 4, 0) , kRWI_LDn , F(Consecutive) , 6 , 1180), // #543
- INST(Ld4r_v , SimdLdNStN , (0b0000110101100000111000, 0b0000000000000000000000, 4, 1) , kRWI_LDn , F(Consecutive) , 7 , 1184), // #544
- INST(Ldnp_v , SimdLdpStp , (0b0010110001, 0b0000000000) , kRWI_WW , 0 , 0 , 1537), // #545
- INST(Ldp_v , SimdLdpStp , (0b0010110101, 0b0010110011) , kRWI_WW , 0 , 1 , 1542), // #546
- INST(Ldr_v , SimdLdSt , (0b0011110101, 0b00111100010, 0b00111100011, 0b00011100, Inst::kIdLdur_v) , kRWI_W , 0 , 0 , 1552), // #547
- INST(Ldur_v , SimdLdurStur , (0b0011110001000000000000) , kRWI_W , 0 , 0 , 2142), // #548
- INST(Mla_v , ISimdVVVe , (0b0000111000100000100101, kVO_V_BHS, 0b0010111100000000000000, kVO_V_HS) , kRWI_X , F(VH0_15) , 0 , 246 ), // #549
- INST(Mls_v , ISimdVVVe , (0b0010111000100000100101, kVO_V_BHS, 0b0010111100000000010000, kVO_V_HS) , kRWI_X , F(VH0_15) , 1 , 931 ), // #550
- INST(Mov_v , SimdMov , (_) , kRWI_W , 0 , 0 , 949 ), // #551
- INST(Movi_v , SimdMoviMvni , (0b0000111100000000000001, 0) , kRWI_W , 0 , 0 , 2221), // #552
- INST(Mul_v , ISimdVVVe , (0b0000111000100000100111, kVO_V_BHS, 0b0000111100000000100000, kVO_V_HS) , kRWI_W , F(VH0_15) , 2 , 991 ), // #553
- INST(Mvn_v , ISimdVV , (0b0010111000100000010110, kVO_V_B) , kRWI_W , 0 , 4 , 2249), // #554
- INST(Mvni_v , SimdMoviMvni , (0b0000111100000000000001, 1) , kRWI_W , 0 , 1 , 2253), // #555
- INST(Neg_v , ISimdVV , (0b0010111000100000101110, kVO_V_Any) , kRWI_W , 0 , 5 , 540 ), // #556
- INST(Not_v , ISimdVV , (0b0010111000100000010110, kVO_V_B) , kRWI_W , 0 , 6 , 2276), // #557
- INST(Orn_v , ISimdVVV , (0b0000111011100000000111, kVO_V_B) , kRWI_W , 0 , 9 , 2280), // #558
- INST(Orr_v , SimdBicOrr , (0b0000111010100000000111, 0b0000111100000000000001) , kRWI_W , 0 , 1 , 2284), // #559
- INST(Pmul_v , ISimdVVV , (0b0010111000100000100111, kVO_V_B) , kRWI_W , 0 , 10 , 2320), // #560
- INST(Pmull_v , ISimdVVV , (0b0000111000100000111000, kVO_V_B8D1) , kRWI_W , F(Long) , 11 , 2325), // #561
- INST(Pmull2_v , ISimdVVV , (0b0100111000100000111000, kVO_V_B16D2) , kRWI_W , F(Long) , 12 , 2331), // #562
- INST(Raddhn_v , ISimdVVV , (0b0010111000100000010000, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 13 , 2344), // #563
- INST(Raddhn2_v , ISimdVVV , (0b0110111000100000010000, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 14 , 2351), // #564
- INST(Rax1_v , ISimdVVV , (0b1100111001100000100011, kVO_V_D2) , kRWI_W , 0 , 15 , 2359), // #565
- INST(Rbit_v , ISimdVV , (0b0010111001100000010110, kVO_V_B) , kRWI_W , 0 , 7 , 2364), // #566
- INST(Rev16_v , ISimdVV , (0b0000111000100000000110, kVO_V_B) , kRWI_W , 0 , 8 , 2373), // #567
- INST(Rev32_v , ISimdVV , (0b0010111000100000000010, kVO_V_BH) , kRWI_W , 0 , 9 , 2379), // #568
- INST(Rev64_v , ISimdVV , (0b0000111000100000000010, kVO_V_BHS) , kRWI_W , 0 , 10 , 2385), // #569
- INST(Rshrn_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000100011, 1, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 0 , 2960), // #570
- INST(Rshrn2_v , SimdShift , (0b0000000000000000000000, 0b0100111100000000100011, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 1 , 2968), // #571
- INST(Rsubhn_v , ISimdVVV , (0b0010111000100000011000, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 16 , 2400), // #572
- INST(Rsubhn2_v , ISimdVVV , (0b0110111000100000011000, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 17 , 2407), // #573
- INST(Saba_v , ISimdVVV , (0b0000111000100000011111, kVO_V_BHS) , kRWI_X , 0 , 18 , 2415), // #574
- INST(Sabal_v , ISimdVVV , (0b0000111000100000010100, kVO_V_B8H4S2) , kRWI_X , F(Long) , 19 , 2420), // #575
- INST(Sabal2_v , ISimdVVV , (0b0100111000100000010100, kVO_V_B16H8S4) , kRWI_X , F(Long) , 20 , 2426), // #576
- INST(Sabd_v , ISimdVVV , (0b0000111000100000011101, kVO_V_BHS) , kRWI_W , 0 , 21 , 2433), // #577
- INST(Sabdl_v , ISimdVVV , (0b0000111000100000011100, kVO_V_B8H4S2) , kRWI_W , F(Long) , 22 , 2438), // #578
- INST(Sabdl2_v , ISimdVVV , (0b0100111000100000011100, kVO_V_B16H8S4) , kRWI_W , F(Long) , 23 , 2444), // #579
- INST(Sadalp_v , ISimdVV , (0b0000111000100000011010, kVO_V_BHS) , kRWI_X , F(Long) | F(Pair) , 11 , 2451), // #580
- INST(Saddl_v , ISimdVVV , (0b0000111000100000000000, kVO_V_B8H4S2) , kRWI_W , F(Long) , 24 , 2458), // #581
- INST(Saddl2_v , ISimdVVV , (0b0100111000100000000000, kVO_V_B16H8S4) , kRWI_W , F(Long) , 25 , 2464), // #582
- INST(Saddlp_v , ISimdVV , (0b0000111000100000001010, kVO_V_BHS) , kRWI_W , F(Long) | F(Pair) , 12 , 2471), // #583
- INST(Saddlv_v , ISimdSV , (0b0000111000110000001110, kVO_V_BH_4S) , kRWI_W , F(Long) , 1 , 2478), // #584
- INST(Saddw_v , ISimdWWV , (0b0000111000100000000100, kVO_V_B8H4S2) , kRWI_W , 0 , 0 , 2485), // #585
- INST(Saddw2_v , ISimdWWV , (0b0000111000100000000100, kVO_V_B16H8S4) , kRWI_W , 0 , 1 , 2491), // #586
- INST(Scvtf_v , SimdFcvtSV , (0b0000111000100001110110, 0b0000111100000000111001, 0b0001111000100010000000, 0) , kRWI_W , 0 , 10 , 2523), // #587
- INST(Sdot_v , SimdDot , (0b0000111010000000100101, 0b0000111110000000111000, kET_S, kET_B, kET_4B) , kRWI_X , 0 , 1 , 4218), // #588
- INST(Sha1c_v , ISimdVVVx , (0b0101111000000000000000, kOp_Q, kOp_S, kOp_V4S) , kRWI_X , 0 , 1 , 2556), // #589
- INST(Sha1h_v , ISimdVVx , (0b0101111000101000000010, kOp_S, kOp_S) , kRWI_W , 0 , 8 , 2562), // #590
- INST(Sha1m_v , ISimdVVVx , (0b0101111000000000001000, kOp_Q, kOp_S, kOp_V4S) , kRWI_X , 0 , 2 , 2568), // #591
- INST(Sha1p_v , ISimdVVVx , (0b0101111000000000000100, kOp_Q, kOp_S, kOp_V4S) , kRWI_X , 0 , 3 , 2574), // #592
- INST(Sha1su0_v , ISimdVVVx , (0b0101111000000000001100, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 4 , 2580), // #593
- INST(Sha1su1_v , ISimdVVx , (0b0101111000101000000110, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 9 , 2588), // #594
- INST(Sha256h_v , ISimdVVVx , (0b0101111000000000010000, kOp_Q, kOp_Q, kOp_V4S) , kRWI_X , 0 , 5 , 2596), // #595
- INST(Sha256h2_v , ISimdVVVx , (0b0101111000000000010100, kOp_Q, kOp_Q, kOp_V4S) , kRWI_X , 0 , 6 , 2604), // #596
- INST(Sha256su0_v , ISimdVVx , (0b0101111000101000001010, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 10 , 2613), // #597
- INST(Sha256su1_v , ISimdVVVx , (0b0101111000000000011000, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 7 , 2623), // #598
- INST(Sha512h_v , ISimdVVVx , (0b1100111001100000100000, kOp_Q, kOp_Q, kOp_V2D) , kRWI_X , 0 , 8 , 2633), // #599
- INST(Sha512h2_v , ISimdVVVx , (0b1100111001100000100001, kOp_Q, kOp_Q, kOp_V2D) , kRWI_X , 0 , 9 , 2641), // #600
- INST(Sha512su0_v , ISimdVVx , (0b1100111011000000100000, kOp_V2D, kOp_V2D) , kRWI_X , 0 , 11 , 2650), // #601
- INST(Sha512su1_v , ISimdVVVx , (0b1100111001100000100010, kOp_V2D, kOp_V2D, kOp_V2D) , kRWI_X , 0 , 10 , 2660), // #602
- INST(Shadd_v , ISimdVVV , (0b0000111000100000000001, kVO_V_BHS) , kRWI_W , 0 , 26 , 2670), // #603
- INST(Shl_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000010101, 0, kVO_V_Any) , kRWI_W , 0 , 2 , 2954), // #604
- INST(Shll_v , SimdShiftES , (0b0010111000100001001110, kVO_V_B8H4S2) , kRWI_W , F(Long) , 0 , 3108), // #605
- INST(Shll2_v , SimdShiftES , (0b0110111000100001001110, kVO_V_B16H8S4) , kRWI_W , F(Long) , 1 , 3114), // #606
- INST(Shrn_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000100001, 1, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 3 , 2961), // #607
- INST(Shrn2_v , SimdShift , (0b0000000000000000000000, 0b0100111100000000100001, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 4 , 2969), // #608
- INST(Shsub_v , ISimdVVV , (0b0000111000100000001001, kVO_V_BHS) , kRWI_W , 0 , 27 , 2676), // #609
- INST(Sli_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000010101, 0, kVO_V_Any) , kRWI_X , 0 , 5 , 2682), // #610
- INST(Sm3partw1_v , ISimdVVVx , (0b1100111001100000110000, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 11 , 2686), // #611
- INST(Sm3partw2_v , ISimdVVVx , (0b1100111001100000110001, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 12 , 2696), // #612
- INST(Sm3ss1_v , ISimdVVVVx , (0b1100111001000000000000, kOp_V4S, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_W , 0 , 0 , 2706), // #613
- INST(Sm3tt1a_v , SimdSm3tt , (0b1100111001000000100000) , kRWI_X , 0 , 0 , 2713), // #614
- INST(Sm3tt1b_v , SimdSm3tt , (0b1100111001000000100001) , kRWI_X , 0 , 1 , 2721), // #615
- INST(Sm3tt2a_v , SimdSm3tt , (0b1100111001000000100010) , kRWI_X , 0 , 2 , 2729), // #616
- INST(Sm3tt2b_v , SimdSm3tt , (0b1100111001000000100011) , kRWI_X , 0 , 3 , 2737), // #617
- INST(Sm4e_v , ISimdVVx , (0b1100111011000000100001, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 12 , 2745), // #618
- INST(Sm4ekey_v , ISimdVVVx , (0b1100111001100000110010, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 13 , 2750), // #619
- INST(Smax_v , ISimdVVV , (0b0000111000100000011001, kVO_V_BHS) , kRWI_W , 0 , 28 , 1690), // #620
- INST(Smaxp_v , ISimdVVV , (0b0000111000100000101001, kVO_V_BHS) , kRWI_W , 0 , 29 , 2765), // #621
- INST(Smaxv_v , ISimdSV , (0b0000111000110000101010, kVO_V_BH_4S) , kRWI_W , 0 , 2 , 2771), // #622
- INST(Smin_v , ISimdVVV , (0b0000111000100000011011, kVO_V_BHS) , kRWI_W , 0 , 30 , 1794), // #623
- INST(Sminp_v , ISimdVVV , (0b0000111000100000101011, kVO_V_BHS) , kRWI_W , 0 , 31 , 2777), // #624
- INST(Sminv_v , ISimdSV , (0b0000111000110001101010, kVO_V_BH_4S) , kRWI_W , 0 , 3 , 2783), // #625
- INST(Smlal_v , ISimdVVVe , (0b0000111000100000100000, kVO_V_B8H4S2, 0b0000111100000000001000, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 3 , 2789), // #626
- INST(Smlal2_v , ISimdVVVe , (0b0100111000100000100000, kVO_V_B16H8S4, 0b0100111100000000001000, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 4 , 2795), // #627
- INST(Smlsl_v , ISimdVVVe , (0b0000111000100000101000, kVO_V_B8H4S2, 0b0000111100000000011000, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 5 , 2802), // #628
- INST(Smlsl2_v , ISimdVVVe , (0b0100111000100000101000, kVO_V_B16H8S4, 0b0100111100000000011000, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 6 , 2808), // #629
- INST(Smmla_v , ISimdVVVx , (0b0100111010000000101001, kOp_V4S, kOp_V16B, kOp_V16B) , kRWI_X , 0 , 14 , 4247), // #630
- INST(Smov_v , SimdSmovUmov , (0b0000111000000000001011, kVO_V_BHS, 1) , kRWI_W , 0 , 0 , 2822), // #631
- INST(Smull_v , ISimdVVVe , (0b0000111000100000110000, kVO_V_B8H4S2, 0b0000111100000000101000, kVO_V_H4S2) , kRWI_W , F(Long) | F(VH0_15) , 7 , 2840), // #632
- INST(Smull2_v , ISimdVVVe , (0b0100111000100000110000, kVO_V_B16H8S4, 0b0100111100000000101000, kVO_V_H8S4) , kRWI_W , F(Long) | F(VH0_15) , 8 , 2846), // #633
- INST(Sqabs_v , ISimdVV , (0b0000111000100000011110, kVO_SV_Any) , kRWI_W , 0 , 13 , 2853), // #634
- INST(Sqadd_v , ISimdVVV , (0b0000111000100000000011, kVO_SV_Any) , kRWI_W , 0 , 32 , 4254), // #635
- INST(Sqdmlal_v , ISimdVVVe , (0b0000111000100000100100, kVO_SV_BHS, 0b0000111100000000001100, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 9 , 2859), // #636
- INST(Sqdmlal2_v , ISimdVVVe , (0b0100111000100000100100, kVO_V_B16H8S4, 0b0100111100000000001100, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 10 , 2867), // #637
- INST(Sqdmlsl_v , ISimdVVVe , (0b0000111000100000101100, kVO_SV_BHS, 0b0000111100000000011100, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 11 , 2876), // #638
- INST(Sqdmlsl2_v , ISimdVVVe , (0b0100111000100000101100, kVO_V_B16H8S4, 0b0100111100000000011100, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 12 , 2884), // #639
- INST(Sqdmulh_v , ISimdVVVe , (0b0000111000100000101101, kVO_SV_HS, 0b0000111100000000110000, kVO_SV_HS) , kRWI_W , F(VH0_15) , 13 , 2893), // #640
- INST(Sqdmull_v , ISimdVVVe , (0b0000111000100000110100, kVO_SV_BHS, 0b0000111100000000101100, kVO_V_H4S2) , kRWI_W , F(Long) | F(VH0_15) , 14 , 2901), // #641
- INST(Sqdmull2_v , ISimdVVVe , (0b0100111000100000110100, kVO_V_B16H8S4, 0b0100111100000000101100, kVO_V_H8S4) , kRWI_W , F(Long) | F(VH0_15) , 15 , 2909), // #642
- INST(Sqneg_v , ISimdVV , (0b0010111000100000011110, kVO_SV_Any) , kRWI_W , 0 , 14 , 2918), // #643
- INST(Sqrdmlah_v , ISimdVVVe , (0b0010111000000000100001, kVO_SV_HS, 0b0010111100000000110100, kVO_SV_HS) , kRWI_X , F(VH0_15) , 16 , 2924), // #644
- INST(Sqrdmlsh_v , ISimdVVVe , (0b0010111000000000100011, kVO_SV_HS, 0b0010111100000000111100, kVO_SV_HS) , kRWI_X , F(VH0_15) , 17 , 2933), // #645
- INST(Sqrdmulh_v , ISimdVVVe , (0b0010111000100000101101, kVO_SV_HS, 0b0000111100000000110100, kVO_SV_HS) , kRWI_W , F(VH0_15) , 18 , 2942), // #646
- INST(Sqrshl_v , SimdShift , (0b0000111000100000010111, 0b0000000000000000000000, 1, kVO_SV_Any) , kRWI_W , 0 , 6 , 2951), // #647
- INST(Sqrshrn_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000100111, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 7 , 2958), // #648
- INST(Sqrshrn2_v , SimdShift , (0b0000000000000000000000, 0b0100111100000000100111, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 8 , 2966), // #649
- INST(Sqrshrun_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000100011, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 9 , 2975), // #650
- INST(Sqrshrun2_v , SimdShift , (0b0000000000000000000000, 0b0110111100000000100011, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 10 , 2984), // #651
- INST(Sqshl_v , SimdShift , (0b0000111000100000010011, 0b0000111100000000011101, 0, kVO_SV_Any) , kRWI_W , 0 , 11 , 2994), // #652
- INST(Sqshlu_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000011001, 0, kVO_SV_Any) , kRWI_W , 0 , 12 , 3000), // #653
- INST(Sqshrn_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000100101, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 13 , 3007), // #654
- INST(Sqshrn2_v , SimdShift , (0b0000000000000000000000, 0b0100111100000000100101, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 14 , 3014), // #655
- INST(Sqshrun_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000100001, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 15 , 3022), // #656
- INST(Sqshrun2_v , SimdShift , (0b0000000000000000000000, 0b0110111100000000100001, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 16 , 3030), // #657
- INST(Sqsub_v , ISimdVVV , (0b0000111000100000001011, kVO_SV_Any) , kRWI_W , 0 , 33 , 3039), // #658
- INST(Sqxtn_v , ISimdVV , (0b0000111000100001010010, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 15 , 3045), // #659
- INST(Sqxtn2_v , ISimdVV , (0b0100111000100001010010, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 16 , 3051), // #660
- INST(Sqxtun_v , ISimdVV , (0b0010111000100001001010, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 17 , 3058), // #661
- INST(Sqxtun2_v , ISimdVV , (0b0110111000100001001010, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 18 , 3065), // #662
- INST(Srhadd_v , ISimdVVV , (0b0000111000100000000101, kVO_V_BHS) , kRWI_W , 0 , 34 , 3073), // #663
- INST(Sri_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000010001, 1, kVO_V_Any) , kRWI_W , 0 , 17 , 3080), // #664
- INST(Srshl_v , SimdShift , (0b0000111000100000010101, 0b0000000000000000000000, 0, kVO_V_Any) , kRWI_W , 0 , 18 , 3084), // #665
- INST(Srshr_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000001001, 1, kVO_V_Any) , kRWI_W , 0 , 19 , 3090), // #666
- INST(Srsra_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000001101, 1, kVO_V_Any) , kRWI_X , 0 , 20 , 3096), // #667
- INST(Sshl_v , SimdShift , (0b0000111000100000010001, 0b0000000000000000000000, 0, kVO_V_Any) , kRWI_W , 0 , 21 , 3102), // #668
- INST(Sshll_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000101001, 0, kVO_V_B8H4S2) , kRWI_W , F(Long) , 22 , 3107), // #669
- INST(Sshll2_v , SimdShift , (0b0000000000000000000000, 0b0100111100000000101001, 0, kVO_V_B16H8S4) , kRWI_W , F(Long) , 23 , 3113), // #670
- INST(Sshr_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000000001, 1, kVO_V_Any) , kRWI_W , 0 , 24 , 3120), // #671
- INST(Ssra_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000000101, 1, kVO_V_Any) , kRWI_X , 0 , 25 , 3125), // #672
- INST(Ssubl_v , ISimdVVV , (0b0000111000100000001000, kVO_V_B8H4S2) , kRWI_W , F(Long) , 35 , 3130), // #673
- INST(Ssubl2_v , ISimdVVV , (0b0100111000100000001000, kVO_V_B16H8S4) , kRWI_W , F(Long) , 36 , 3136), // #674
- INST(Ssubw_v , ISimdWWV , (0b0000111000100000001100, kVO_V_B8H4S2) , kRWI_W , 0 , 2 , 3143), // #675
- INST(Ssubw2_v , ISimdWWV , (0b0000111000100000001100, kVO_V_B16H8S4) , kRWI_X , 0 , 3 , 3149), // #676
- INST(St1_v , SimdLdNStN , (0b0000110100000000000000, 0b0000110000000000001000, 1, 0) , kRWI_STn , F(Consecutive) , 8 , 3156), // #677
- INST(St2_v , SimdLdNStN , (0b0000110100100000000000, 0b0000110000000000100000, 2, 0) , kRWI_STn , F(Consecutive) , 9 , 3160), // #678
- INST(St3_v , SimdLdNStN , (0b0000110100000000001000, 0b0000110000000000010000, 3, 0) , kRWI_STn , F(Consecutive) , 10 , 3169), // #679
- INST(St4_v , SimdLdNStN , (0b0000110100100000001000, 0b0000110000000000000000, 4, 0) , kRWI_STn , F(Consecutive) , 11 , 3173), // #680
- INST(Stnp_v , SimdLdpStp , (0b0010110000, 0b0000000000) , kRWI_RRW , 0 , 2 , 3383), // #681
- INST(Stp_v , SimdLdpStp , (0b0010110100, 0b0010110010) , kRWI_RRW , 0 , 3 , 3388), // #682
- INST(Str_v , SimdLdSt , (0b0011110100, 0b00111100000, 0b00111100001, 0b00000000, Inst::kIdStur_v) , kRWI_RW , 0 , 1 , 3392), // #683
- INST(Stur_v , SimdLdurStur , (0b0011110000000000000000) , kRWI_RW , 0 , 1 , 3662), // #684
- INST(Sub_v , ISimdVVV , (0b0010111000100000100001, kVO_V_Any) , kRWI_W , 0 , 37 , 985 ), // #685
- INST(Subhn_v , ISimdVVV , (0b0000111000100000011000, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 38 , 2401), // #686
- INST(Subhn2_v , ISimdVVV , (0b0000111000100000011000, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 39 , 2408), // #687
- INST(Sudot_v , SimdDot , (0b0000000000000000000000, 0b0000111100000000111100, kET_S, kET_B, kET_4B) , kRWI_X , 0 , 2 , 3739), // #688
- INST(Suqadd_v , ISimdVV , (0b0000111000100000001110, kVO_SV_Any) , kRWI_X , 0 , 19 , 3745), // #689
- INST(Sxtl_v , SimdSxtlUxtl , (0b0000111100000000101001, kVO_V_B8H4S2) , kRWI_W , F(Long) , 0 , 3834), // #690
- INST(Sxtl2_v , SimdSxtlUxtl , (0b0100111100000000101001, kVO_V_B16H8S4) , kRWI_W , F(Long) , 1 , 3839), // #691
- INST(Tbl_v , SimdTblTbx , (0b0000111000000000000000) , kRWI_W , 0 , 0 , 3854), // #692
- INST(Tbx_v , SimdTblTbx , (0b0000111000000000000100) , kRWI_W , 0 , 1 , 3863), // #693
- INST(Trn1_v , ISimdVVV , (0b0000111000000000001010, kVO_V_BHS_D2) , kRWI_W , 0 , 40 , 3876), // #694
- INST(Trn2_v , ISimdVVV , (0b0000111000000000011010, kVO_V_BHS_D2) , kRWI_W , 0 , 41 , 3881), // #695
- INST(Uaba_v , ISimdVVV , (0b0010111000100000011111, kVO_V_BHS) , kRWI_X , 0 , 42 , 3886), // #696
- INST(Uabal_v , ISimdVVV , (0b0010111000100000010100, kVO_V_B8H4S2) , kRWI_X , F(Long) , 43 , 3891), // #697
- INST(Uabal2_v , ISimdVVV , (0b0110111000100000010100, kVO_V_B16H8S4) , kRWI_X , F(Long) , 44 , 3897), // #698
- INST(Uabd_v , ISimdVVV , (0b0010111000100000011101, kVO_V_BHS) , kRWI_W , 0 , 45 , 3904), // #699
- INST(Uabdl_v , ISimdVVV , (0b0010111000100000011100, kVO_V_B8H4S2) , kRWI_W , F(Long) , 46 , 3909), // #700
- INST(Uabdl2_v , ISimdVVV , (0b0110111000100000011100, kVO_V_B16H8S4) , kRWI_W , F(Long) , 47 , 3915), // #701
- INST(Uadalp_v , ISimdVV , (0b0010111000100000011010, kVO_V_BHS) , kRWI_X , F(Long) | F(Pair) , 20 , 3922), // #702
- INST(Uaddl_v , ISimdVVV , (0b0010111000100000000000, kVO_V_B8H4S2) , kRWI_W , F(Long) , 48 , 3929), // #703
- INST(Uaddl2_v , ISimdVVV , (0b0110111000100000000000, kVO_V_B16H8S4) , kRWI_W , F(Long) , 49 , 3935), // #704
- INST(Uaddlp_v , ISimdVV , (0b0010111000100000001010, kVO_V_BHS) , kRWI_W , F(Long) | F(Pair) , 21 , 3942), // #705
- INST(Uaddlv_v , ISimdSV , (0b0010111000110000001110, kVO_V_BH_4S) , kRWI_W , F(Long) , 4 , 3949), // #706
- INST(Uaddw_v , ISimdWWV , (0b0010111000100000000100, kVO_V_B8H4S2) , kRWI_W , 0 , 4 , 3956), // #707
- INST(Uaddw2_v , ISimdWWV , (0b0010111000100000000100, kVO_V_B16H8S4) , kRWI_W , 0 , 5 , 3962), // #708
- INST(Ucvtf_v , SimdFcvtSV , (0b0010111000100001110110, 0b0010111100000000111001, 0b0001111000100011000000, 0) , kRWI_W , 0 , 11 , 3985), // #709
- INST(Udot_v , SimdDot , (0b0010111010000000100101, 0b0010111110000000111000, kET_S, kET_B, kET_4B) , kRWI_X , 0 , 3 , 3740), // #710
- INST(Uhadd_v , ISimdVVV , (0b0010111000100000000001, kVO_V_BHS) , kRWI_W , 0 , 50 , 4000), // #711
- INST(Uhsub_v , ISimdVVV , (0b0010111000100000001001, kVO_V_BHS) , kRWI_W , 0 , 51 , 4006), // #712
- INST(Umax_v , ISimdVVV , (0b0010111000100000011001, kVO_V_BHS) , kRWI_W , 0 , 52 , 1936), // #713
- INST(Umaxp_v , ISimdVVV , (0b0010111000100000101001, kVO_V_BHS) , kRWI_W , 0 , 53 , 4019), // #714
- INST(Umaxv_v , ISimdSV , (0b0010111000110000101010, kVO_V_BH_4S) , kRWI_W , 0 , 5 , 4025), // #715
- INST(Umin_v , ISimdVVV , (0b0010111000100000011011, kVO_V_BHS) , kRWI_W , 0 , 54 , 2040), // #716
- INST(Uminp_v , ISimdVVV , (0b0010111000100000101011, kVO_V_BHS) , kRWI_W , 0 , 55 , 4031), // #717
- INST(Uminv_v , ISimdSV , (0b0010111000110001101010, kVO_V_BH_4S) , kRWI_W , 0 , 6 , 4037), // #718
- INST(Umlal_v , ISimdVVVe , (0b0010111000100000100000, kVO_V_B8H4S2, 0b0010111100000000001000, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 19 , 4043), // #719
- INST(Umlal2_v , ISimdVVVe , (0b0110111000100000100000, kVO_V_B16H8S4, 0b0010111100000000001000, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 20 , 4049), // #720
- INST(Umlsl_v , ISimdVVVe , (0b0010111000100000101000, kVO_V_B8H4S2, 0b0010111100000000011000, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 21 , 4056), // #721
- INST(Umlsl2_v , ISimdVVVe , (0b0110111000100000101000, kVO_V_B16H8S4, 0b0110111100000000011000, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 22 , 4062), // #722
- INST(Ummla_v , ISimdVVVx , (0b0110111010000000101001, kOp_V4S, kOp_V16B, kOp_V16B) , kRWI_X , 0 , 15 , 4069), // #723
- INST(Umov_v , SimdSmovUmov , (0b0000111000000000001111, kVO_V_Any, 0) , kRWI_W , 0 , 1 , 4082), // #724
- INST(Umull_v , ISimdVVVe , (0b0010111000100000110000, kVO_V_B8H4S2, 0b0010111100000000101000, kVO_V_H4S2) , kRWI_W , F(Long) | F(VH0_15) , 23 , 4100), // #725
- INST(Umull2_v , ISimdVVVe , (0b0110111000100000110000, kVO_V_B16H8S4, 0b0110111100000000101000, kVO_V_H8S4) , kRWI_W , F(Long) | F(VH0_15) , 24 , 4106), // #726
- INST(Uqadd_v , ISimdVVV , (0b0010111000100000000011, kVO_SV_Any) , kRWI_W , 0 , 56 , 3746), // #727
- INST(Uqrshl_v , SimdShift , (0b0010111000100000010111, 0b0000000000000000000000, 0, kVO_SV_Any) , kRWI_W , 0 , 26 , 4113), // #728
- INST(Uqrshrn_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000100111, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 27 , 4120), // #729
- INST(Uqrshrn2_v , SimdShift , (0b0000000000000000000000, 0b0110111100000000100111, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 28 , 4128), // #730
- INST(Uqshl_v , SimdShift , (0b0010111000100000010011, 0b0010111100000000011101, 0, kVO_SV_Any) , kRWI_W , 0 , 29 , 4137), // #731
- INST(Uqshrn_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000100101, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 30 , 4143), // #732
- INST(Uqshrn2_v , SimdShift , (0b0000000000000000000000, 0b0110111100000000100101, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 31 , 4150), // #733
- INST(Uqsub_v , ISimdVVV , (0b0010111000100000001011, kVO_SV_Any) , kRWI_W , 0 , 57 , 4158), // #734
- INST(Uqxtn_v , ISimdVV , (0b0010111000100001010010, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 22 , 4164), // #735
- INST(Uqxtn2_v , ISimdVV , (0b0110111000100001010010, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 23 , 4170), // #736
- INST(Urecpe_v , ISimdVV , (0b0000111010100001110010, kVO_V_S) , kRWI_W , 0 , 24 , 4177), // #737
- INST(Urhadd_v , ISimdVVV , (0b0010111000100000000101, kVO_V_BHS) , kRWI_W , 0 , 58 , 4184), // #738
- INST(Urshl_v , SimdShift , (0b0010111000100000010101, 0b0000000000000000000000, 0, kVO_V_Any) , kRWI_W , 0 , 32 , 4191), // #739
- INST(Urshr_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000001001, 1, kVO_V_Any) , kRWI_W , 0 , 33 , 4197), // #740
- INST(Ursqrte_v , ISimdVV , (0b0010111010100001110010, kVO_V_S) , kRWI_W , 0 , 25 , 4203), // #741
- INST(Ursra_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000001101, 1, kVO_V_Any) , kRWI_X , 0 , 34 , 4211), // #742
- INST(Usdot_v , SimdDot , (0b0000111010000000100111, 0b0000111110000000111100, kET_S, kET_B, kET_4B) , kRWI_X , 0 , 4 , 4217), // #743
- INST(Ushl_v , SimdShift , (0b0010111000100000010001, 0b0000000000000000000000, 0, kVO_V_Any) , kRWI_W , 0 , 35 , 4223), // #744
- INST(Ushll_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000101001, 0, kVO_V_B8H4S2) , kRWI_W , F(Long) , 36 , 4228), // #745
- INST(Ushll2_v , SimdShift , (0b0000000000000000000000, 0b0110111100000000101001, 0, kVO_V_B16H8S4) , kRWI_W , F(Long) , 37 , 4234), // #746
- INST(Ushr_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000000001, 1, kVO_V_Any) , kRWI_W , 0 , 38 , 4241), // #747
- INST(Usmmla_v , ISimdVVVx , (0b0100111010000000101011, kOp_V4S, kOp_V16B, kOp_V16B) , kRWI_X , 0 , 16 , 4246), // #748
- INST(Usqadd_v , ISimdVV , (0b0010111000100000001110, kVO_SV_Any) , kRWI_X , 0 , 26 , 4253), // #749
- INST(Usra_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000000101, 1, kVO_V_Any) , kRWI_X , 0 , 39 , 4260), // #750
- INST(Usubl_v , ISimdVVV , (0b0010111000100000001000, kVO_V_B8H4S2) , kRWI_W , F(Long) , 59 , 4265), // #751
- INST(Usubl2_v , ISimdVVV , (0b0110111000100000001000, kVO_V_B16H8S4) , kRWI_W , F(Long) , 60 , 4271), // #752
- INST(Usubw_v , ISimdWWV , (0b0010111000100000001100, kVO_V_B8H4S2) , kRWI_W , 0 , 6 , 4278), // #753
- INST(Usubw2_v , ISimdWWV , (0b0010111000100000001100, kVO_V_B16H8S4) , kRWI_W , 0 , 7 , 4284), // #754
- INST(Uxtl_v , SimdSxtlUxtl , (0b0010111100000000101001, kVO_V_B8H4S2) , kRWI_W , F(Long) , 2 , 4301), // #755
- INST(Uxtl2_v , SimdSxtlUxtl , (0b0110111100000000101001, kVO_V_B16H8S4) , kRWI_W , F(Long) , 3 , 4306), // #756
- INST(Uzp1_v , ISimdVVV , (0b0000111000000000000110, kVO_V_BHS_D2) , kRWI_W , 0 , 61 , 4312), // #757
- INST(Uzp2_v , ISimdVVV , (0b0000111000000000010110, kVO_V_BHS_D2) , kRWI_W , 0 , 62 , 4317), // #758
- INST(Xar_v , ISimdVVVI , (0b1100111001100000100011, kVO_V_D2, 6, 10, 0) , kRWI_W , 0 , 1 , 4337), // #759
- INST(Xtn_v , ISimdVV , (0b0000111000100001001010, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 27 , 3047), // #760
- INST(Xtn2_v , ISimdVV , (0b0100111000100001001010, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 28 , 3053), // #761
- INST(Zip1_v , ISimdVVV , (0b0000111000000000001110, kVO_V_BHS_D2) , kRWI_W , 0 , 63 , 4367), // #762
- INST(Zip2_v , ISimdVVV , (0b0000111000000000011110, kVO_V_BHS_D2) , kRWI_W , 0 , 64 , 4372) // #763
+ INST(None , None , (_) , 0 , 0 , 0 ), // #0
+ INST(Adc , BaseRRR , (0b0001101000000000000000, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 0 ), // #1
+ INST(Adcs , BaseRRR , (0b0011101000000000000000, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 1 ), // #2
+ INST(Add , BaseAddSub , (0b0001011000, 0b0001011001, 0b0010001) , kRWI_W , 0 , 0 ), // #3
+ INST(Addg , BaseRRII , (0b1001000110000000000000, kX, kSP, kX, kSP, 6, 4, 16, 4, 0, 10) , kRWI_W , 0 , 0 ), // #4
+ INST(Adds , BaseAddSub , (0b0101011000, 0b0101011001, 0b0110001) , kRWI_W , 0 , 1 ), // #5
+ INST(Adr , BaseAdr , (0b0001000000000000000000, OffsetType::kAArch64_ADR) , kRWI_W , 0 , 0 ), // #6
+ INST(Adrp , BaseAdr , (0b1001000000000000000000, OffsetType::kAArch64_ADRP) , kRWI_W , 0 , 1 ), // #7
+ INST(And , BaseLogical , (0b0001010000, 0b00100100, 0) , kRWI_W , 0 , 0 ), // #8
+ INST(Ands , BaseLogical , (0b1101010000, 0b11100100, 0) , kRWI_W , 0 , 1 ), // #9
+ INST(Asr , BaseShift , (0b0001101011000000001010, 0b0001001100000000011111, 0) , kRWI_W , 0 , 0 ), // #10
+ INST(Asrv , BaseShift , (0b0001101011000000001010, 0b0000000000000000000000, 0) , kRWI_W , 0 , 1 ), // #11
+ INST(At , BaseAtDcIcTlbi , (0b00011111110000, 0b00001111000000, true) , kRWI_RX , 0 , 0 ), // #12
+ INST(Autda , BaseRR , (0b11011010110000010001100000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 0 ), // #13
+ INST(Autdza , BaseR , (0b11011010110000010011101111100000, kX, kZR, 0) , kRWI_X , 0 , 0 ), // #14
+ INST(Autdb , BaseRR , (0b11011010110000010001110000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 1 ), // #15
+ INST(Autdzb , BaseR , (0b11011010110000010011111111100000, kX, kZR, 0) , kRWI_X , 0 , 1 ), // #16
+ INST(Autia , BaseRR , (0b11011010110000010001000000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 2 ), // #17
+ INST(Autia1716 , BaseOp , (0b11010101000000110010000110011111) , 0 , 0 , 0 ), // #18
+ INST(Autiasp , BaseOp , (0b11010101000000110010001110111111) , 0 , 0 , 1 ), // #19
+ INST(Autiaz , BaseOp , (0b11010101000000110010001110011111) , 0 , 0 , 2 ), // #20
+ INST(Autib , BaseRR , (0b11011010110000010001010000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 3 ), // #21
+ INST(Autib1716 , BaseOp , (0b11010101000000110010000111011111) , 0 , 0 , 3 ), // #22
+ INST(Autibsp , BaseOp , (0b11010101000000110010001111111111) , 0 , 0 , 4 ), // #23
+ INST(Autibz , BaseOp , (0b11010101000000110010001111011111) , 0 , 0 , 5 ), // #24
+ INST(Autiza , BaseR , (0b11011010110000010011001111100000, kX, kZR, 0) , kRWI_X , 0 , 2 ), // #25
+ INST(Autizb , BaseR , (0b11011010110000010011011111100000, kX, kZR, 0) , kRWI_X , 0 , 3 ), // #26
+ INST(Axflag , BaseOp , (0b11010101000000000100000001011111) , 0 , 0 , 6 ), // #27
+ INST(B , BaseBranchRel , (0b00010100000000000000000000000000) , 0 , F(Cond) , 0 ), // #28
+ INST(Bfc , BaseBfc , (0b00110011000000000000001111100000) , kRWI_X , 0 , 0 ), // #29
+ INST(Bfi , BaseBfi , (0b00110011000000000000000000000000) , kRWI_X , 0 , 0 ), // #30
+ INST(Bfm , BaseBfm , (0b00110011000000000000000000000000) , kRWI_X , 0 , 0 ), // #31
+ INST(Bfxil , BaseBfx , (0b00110011000000000000000000000000) , kRWI_X , 0 , 0 ), // #32
+ INST(Bic , BaseLogical , (0b0001010001, 0b00100100, 1) , kRWI_W , 0 , 2 ), // #33
+ INST(Bics , BaseLogical , (0b1101010001, 0b11100100, 1) , kRWI_W , 0 , 3 ), // #34
+ INST(Bl , BaseBranchRel , (0b10010100000000000000000000000000) , 0 , 0 , 1 ), // #35
+ INST(Blr , BaseBranchReg , (0b11010110001111110000000000000000) , kRWI_R , 0 , 0 ), // #36
+ INST(Br , BaseBranchReg , (0b11010110000111110000000000000000) , kRWI_R , 0 , 1 ), // #37
+ INST(Brk , BaseOpImm , (0b11010100001000000000000000000000, 16, 5) , 0 , 0 , 0 ), // #38
+ INST(Cas , BaseAtomicOp , (0b1000100010100000011111, kWX, 30, 0) , kRWI_XRX , 0 , 0 ), // #39
+ INST(Casa , BaseAtomicOp , (0b1000100011100000011111, kWX, 30, 1) , kRWI_XRX , 0 , 1 ), // #40
+ INST(Casab , BaseAtomicOp , (0b0000100011100000011111, kW , 0 , 1) , kRWI_XRX , 0 , 2 ), // #41
+ INST(Casah , BaseAtomicOp , (0b0100100011100000011111, kW , 0 , 1) , kRWI_XRX , 0 , 3 ), // #42
+ INST(Casal , BaseAtomicOp , (0b1000100011100000111111, kWX, 30, 1) , kRWI_XRX , 0 , 4 ), // #43
+ INST(Casalb , BaseAtomicOp , (0b0000100011100000111111, kW , 0 , 1) , kRWI_XRX , 0 , 5 ), // #44
+ INST(Casalh , BaseAtomicOp , (0b0100100011100000111111, kW , 0 , 1) , kRWI_XRX , 0 , 6 ), // #45
+ INST(Casb , BaseAtomicOp , (0b0000100010100000011111, kW , 0 , 0) , kRWI_XRX , 0 , 7 ), // #46
+ INST(Cash , BaseAtomicOp , (0b0100100010100000011111, kW , 0 , 0) , kRWI_XRX , 0 , 8 ), // #47
+ INST(Casl , BaseAtomicOp , (0b1000100010100000111111, kWX, 30, 0) , kRWI_XRX , 0 , 9 ), // #48
+ INST(Caslb , BaseAtomicOp , (0b0000100010100000111111, kW , 0 , 0) , kRWI_XRX , 0 , 10 ), // #49
+ INST(Caslh , BaseAtomicOp , (0b0100100010100000111111, kW , 0 , 0) , kRWI_XRX , 0 , 11 ), // #50
+ INST(Casp , BaseAtomicCasp , (0b0000100000100000011111, kWX, 30) , kRWI_XXRRX, 0 , 0 ), // #51
+ INST(Caspa , BaseAtomicCasp , (0b0000100001100000011111, kWX, 30) , kRWI_XXRRX, 0 , 1 ), // #52
+ INST(Caspal , BaseAtomicCasp , (0b0000100001100000111111, kWX, 30) , kRWI_XXRRX, 0 , 2 ), // #53
+ INST(Caspl , BaseAtomicCasp , (0b0000100000100000111111, kWX, 30) , kRWI_XXRRX, 0 , 3 ), // #54
+ INST(Cbnz , BaseBranchCmp , (0b00110101000000000000000000000000) , kRWI_R , 0 , 0 ), // #55
+ INST(Cbz , BaseBranchCmp , (0b00110100000000000000000000000000) , kRWI_R , 0 , 1 ), // #56
+ INST(Ccmn , BaseCCmp , (0b00111010010000000000000000000000) , kRWI_R , 0 , 0 ), // #57
+ INST(Ccmp , BaseCCmp , (0b01111010010000000000000000000000) , kRWI_R , 0 , 1 ), // #58
+ INST(Cfinv , BaseOp , (0b11010101000000000100000000011111) , 0 , 0 , 7 ), // #59
+ INST(Cinc , BaseCInc , (0b00011010100000000000010000000000) , kRWI_W , 0 , 0 ), // #60
+ INST(Cinv , BaseCInc , (0b01011010100000000000000000000000) , kRWI_W , 0 , 1 ), // #61
+ INST(Clrex , BaseOpImm , (0b11010101000000110011000001011111, 4, 8) , 0 , 0 , 1 ), // #62
+ INST(Cls , BaseRR , (0b01011010110000000001010000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 4 ), // #63
+ INST(Clz , BaseRR , (0b01011010110000000001000000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 5 ), // #64
+ INST(Cmn , BaseCmpCmn , (0b0101011000, 0b0101011001, 0b0110001) , kRWI_R , 0 , 0 ), // #65
+ INST(Cmp , BaseCmpCmn , (0b1101011000, 0b1101011001, 0b1110001) , kRWI_R , 0 , 1 ), // #66
+ INST(Cmpp , BaseRR , (0b10111010110000000000000000011111, kX, kSP, 5, kX, kSP, 16, true) , kRWI_R , 0 , 6 ), // #67
+ INST(Cneg , BaseCInc , (0b01011010100000000000010000000000) , kRWI_W , 0 , 2 ), // #68
+ INST(Crc32b , BaseRRR , (0b0001101011000000010000, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 2 ), // #69
+ INST(Crc32cb , BaseRRR , (0b0001101011000000010100, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 3 ), // #70
+ INST(Crc32ch , BaseRRR , (0b0001101011000000010101, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 4 ), // #71
+ INST(Crc32cw , BaseRRR , (0b0001101011000000010110, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 5 ), // #72
+ INST(Crc32cx , BaseRRR , (0b1001101011000000010111, kW, kZR, kW, kZR, kX, kZR, false) , kRWI_W , 0 , 6 ), // #73
+ INST(Crc32h , BaseRRR , (0b0001101011000000010001, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 7 ), // #74
+ INST(Crc32w , BaseRRR , (0b0001101011000000010010, kW, kZR, kW, kZR, kW, kZR, false) , kRWI_W , 0 , 8 ), // #75
+ INST(Crc32x , BaseRRR , (0b1001101011000000010011, kW, kZR, kW, kZR, kX, kZR, false) , kRWI_W , 0 , 9 ), // #76
+ INST(Csdb , BaseOp , (0b11010101000000110010001010011111) , 0 , 0 , 8 ), // #77
+ INST(Csel , BaseCSel , (0b00011010100000000000000000000000) , kRWI_W , 0 , 0 ), // #78
+ INST(Cset , BaseCSet , (0b00011010100111110000011111100000) , kRWI_W , 0 , 0 ), // #79
+ INST(Csetm , BaseCSet , (0b01011010100111110000001111100000) , kRWI_W , 0 , 1 ), // #80
+ INST(Csinc , BaseCSel , (0b00011010100000000000010000000000) , kRWI_W , 0 , 1 ), // #81
+ INST(Csinv , BaseCSel , (0b01011010100000000000000000000000) , kRWI_W , 0 , 2 ), // #82
+ INST(Csneg , BaseCSel , (0b01011010100000000000010000000000) , kRWI_W , 0 , 3 ), // #83
+ INST(Dc , BaseAtDcIcTlbi , (0b00011110000000, 0b00001110000000, true) , kRWI_RX , 0 , 1 ), // #84
+ INST(Dcps1 , BaseOpImm , (0b11010100101000000000000000000001, 16, 5) , 0 , 0 , 2 ), // #85
+ INST(Dcps2 , BaseOpImm , (0b11010100101000000000000000000010, 16, 5) , 0 , 0 , 3 ), // #86
+ INST(Dcps3 , BaseOpImm , (0b11010100101000000000000000000011, 16, 5) , 0 , 0 , 4 ), // #87
+ INST(Dgh , BaseOp , (0b11010101000000110010000011011111) , 0 , 0 , 9 ), // #88
+ INST(Dmb , BaseOpImm , (0b11010101000000110011000010111111, 4, 8) , 0 , 0 , 5 ), // #89
+ INST(Drps , BaseOp , (0b11010110101111110000001111100000) , 0 , 0 , 10 ), // #90
+ INST(Dsb , BaseOpImm , (0b11010101000000110011000010011111, 4, 8) , 0 , 0 , 6 ), // #91
+ INST(Eon , BaseLogical , (0b1001010001, 0b10100100, 1) , kRWI_W , 0 , 4 ), // #92
+ INST(Eor , BaseLogical , (0b1001010000, 0b10100100, 0) , kRWI_W , 0 , 5 ), // #93
+ INST(Esb , BaseOp , (0b11010101000000110010001000011111) , 0 , 0 , 11 ), // #94
+ INST(Extr , BaseExtract , (0b00010011100000000000000000000000) , kRWI_W , 0 , 0 ), // #95
+ INST(Eret , BaseOp , (0b11010110100111110000001111100000) , 0 , 0 , 12 ), // #96
+ INST(Gmi , BaseRRR , (0b1001101011000000000101, kX , kZR, kX , kSP, kX , kZR, true) , kRWI_W , 0 , 10 ), // #97
+ INST(Hint , BaseOpImm , (0b11010101000000110010000000011111, 7, 5) , 0 , 0 , 7 ), // #98
+ INST(Hlt , BaseOpImm , (0b11010100010000000000000000000000, 16, 5) , 0 , 0 , 8 ), // #99
+ INST(Hvc , BaseOpImm , (0b11010100000000000000000000000010, 16, 5) , 0 , 0 , 9 ), // #100
+ INST(Ic , BaseAtDcIcTlbi , (0b00011110000000, 0b00001110000000, false) , kRWI_RX , 0 , 2 ), // #101
+ INST(Isb , BaseOpImm , (0b11010101000000110011000011011111, 4, 8) , 0 , 0 , 10 ), // #102
+ INST(Ldadd , BaseAtomicOp , (0b1011100000100000000000, kWX, 30, 0) , kRWI_WRX , 0 , 12 ), // #103
+ INST(Ldadda , BaseAtomicOp , (0b1011100010100000000000, kWX, 30, 1) , kRWI_WRX , 0 , 13 ), // #104
+ INST(Ldaddab , BaseAtomicOp , (0b0011100010100000000000, kW , 0 , 1) , kRWI_WRX , 0 , 14 ), // #105
+ INST(Ldaddah , BaseAtomicOp , (0b0111100010100000000000, kW , 0 , 1) , kRWI_WRX , 0 , 15 ), // #106
+ INST(Ldaddal , BaseAtomicOp , (0b1011100011100000000000, kWX, 30, 1) , kRWI_WRX , 0 , 16 ), // #107
+ INST(Ldaddalb , BaseAtomicOp , (0b0011100011100000000000, kW , 0 , 1) , kRWI_WRX , 0 , 17 ), // #108
+ INST(Ldaddalh , BaseAtomicOp , (0b0111100011100000000000, kW , 0 , 1) , kRWI_WRX , 0 , 18 ), // #109
+ INST(Ldaddb , BaseAtomicOp , (0b0011100000100000000000, kW , 0 , 0) , kRWI_WRX , 0 , 19 ), // #110
+ INST(Ldaddh , BaseAtomicOp , (0b0111100000100000000000, kW , 0 , 0) , kRWI_WRX , 0 , 20 ), // #111
+ INST(Ldaddl , BaseAtomicOp , (0b1011100001100000000000, kWX, 30, 0) , kRWI_WRX , 0 , 21 ), // #112
+ INST(Ldaddlb , BaseAtomicOp , (0b0011100001100000000000, kW , 0 , 0) , kRWI_WRX , 0 , 22 ), // #113
+ INST(Ldaddlh , BaseAtomicOp , (0b0111100001100000000000, kW , 0 , 0) , kRWI_WRX , 0 , 23 ), // #114
+ INST(Ldar , BaseRM_NoImm , (0b1000100011011111111111, kWX, kZR, 30) , kRWI_W , 0 , 0 ), // #115
+ INST(Ldarb , BaseRM_NoImm , (0b0000100011011111111111, kW , kZR, 0 ) , kRWI_W , 0 , 1 ), // #116
+ INST(Ldarh , BaseRM_NoImm , (0b0100100011011111111111, kW , kZR, 0 ) , kRWI_W , 0 , 2 ), // #117
+ INST(Ldaxp , BaseLdxp , (0b1000100001111111100000, kWX, 30) , kRWI_WW , 0 , 0 ), // #118
+ INST(Ldaxr , BaseRM_NoImm , (0b1000100001011111111111, kWX, kZR, 30) , kRWI_W , 0 , 3 ), // #119
+ INST(Ldaxrb , BaseRM_NoImm , (0b0000100001011111111111, kW , kZR, 0 ) , kRWI_W , 0 , 4 ), // #120
+ INST(Ldaxrh , BaseRM_NoImm , (0b0100100001011111111111, kW , kZR, 0 ) , kRWI_W , 0 , 5 ), // #121
+ INST(Ldclr , BaseAtomicOp , (0b1011100000100000000100, kWX, 30, 0) , kRWI_WRX , 0 , 24 ), // #122
+ INST(Ldclra , BaseAtomicOp , (0b1011100010100000000100, kWX, 30, 1) , kRWI_WRX , 0 , 25 ), // #123
+ INST(Ldclrab , BaseAtomicOp , (0b0011100010100000000100, kW , 0 , 1) , kRWI_WRX , 0 , 26 ), // #124
+ INST(Ldclrah , BaseAtomicOp , (0b0111100010100000000100, kW , 0 , 1) , kRWI_WRX , 0 , 27 ), // #125
+ INST(Ldclral , BaseAtomicOp , (0b1011100011100000000100, kWX, 30, 1) , kRWI_WRX , 0 , 28 ), // #126
+ INST(Ldclralb , BaseAtomicOp , (0b0011100011100000000100, kW , 0 , 1) , kRWI_WRX , 0 , 29 ), // #127
+ INST(Ldclralh , BaseAtomicOp , (0b0111100011100000000100, kW , 0 , 1) , kRWI_WRX , 0 , 30 ), // #128
+ INST(Ldclrb , BaseAtomicOp , (0b0011100000100000000100, kW , 0 , 0) , kRWI_WRX , 0 , 31 ), // #129
+ INST(Ldclrh , BaseAtomicOp , (0b0111100000100000000100, kW , 0 , 0) , kRWI_WRX , 0 , 32 ), // #130
+ INST(Ldclrl , BaseAtomicOp , (0b1011100001100000000100, kWX, 30, 0) , kRWI_WRX , 0 , 33 ), // #131
+ INST(Ldclrlb , BaseAtomicOp , (0b0011100001100000000100, kW , 0 , 0) , kRWI_WRX , 0 , 34 ), // #132
+ INST(Ldclrlh , BaseAtomicOp , (0b0111100001100000000100, kW , 0 , 0) , kRWI_WRX , 0 , 35 ), // #133
+ INST(Ldeor , BaseAtomicOp , (0b1011100000100000001000, kWX, 30, 0) , kRWI_WRX , 0 , 36 ), // #134
+ INST(Ldeora , BaseAtomicOp , (0b1011100010100000001000, kWX, 30, 1) , kRWI_WRX , 0 , 37 ), // #135
+ INST(Ldeorab , BaseAtomicOp , (0b0011100010100000001000, kW , 0 , 1) , kRWI_WRX , 0 , 38 ), // #136
+ INST(Ldeorah , BaseAtomicOp , (0b0111100010100000001000, kW , 0 , 1) , kRWI_WRX , 0 , 39 ), // #137
+ INST(Ldeoral , BaseAtomicOp , (0b1011100011100000001000, kWX, 30, 1) , kRWI_WRX , 0 , 40 ), // #138
+ INST(Ldeoralb , BaseAtomicOp , (0b0011100011100000001000, kW , 0 , 1) , kRWI_WRX , 0 , 41 ), // #139
+ INST(Ldeoralh , BaseAtomicOp , (0b0111100011100000001000, kW , 0 , 1) , kRWI_WRX , 0 , 42 ), // #140
+ INST(Ldeorb , BaseAtomicOp , (0b0011100000100000001000, kW , 0 , 0) , kRWI_WRX , 0 , 43 ), // #141
+ INST(Ldeorh , BaseAtomicOp , (0b0111100000100000001000, kW , 0 , 0) , kRWI_WRX , 0 , 44 ), // #142
+ INST(Ldeorl , BaseAtomicOp , (0b1011100001100000001000, kWX, 30, 0) , kRWI_WRX , 0 , 45 ), // #143
+ INST(Ldeorlb , BaseAtomicOp , (0b0011100001100000001000, kW , 0 , 0) , kRWI_WRX , 0 , 46 ), // #144
+ INST(Ldeorlh , BaseAtomicOp , (0b0111100001100000001000, kW , 0 , 0) , kRWI_WRX , 0 , 47 ), // #145
+ INST(Ldg , BaseRM_SImm9 , (0b1101100101100000000000, 0b0000000000000000000000, kX , kZR, 0, 4) , kRWI_W , 0 , 0 ), // #146
+ INST(Ldgm , BaseRM_NoImm , (0b1101100111100000000000, kX , kZR, 0 ) , kRWI_W , 0 , 6 ), // #147
+ INST(Ldlar , BaseRM_NoImm , (0b1000100011011111011111, kWX, kZR, 30) , kRWI_W , 0 , 7 ), // #148
+ INST(Ldlarb , BaseRM_NoImm , (0b0000100011011111011111, kW , kZR, 0 ) , kRWI_W , 0 , 8 ), // #149
+ INST(Ldlarh , BaseRM_NoImm , (0b0100100011011111011111, kW , kZR, 0 ) , kRWI_W , 0 , 9 ), // #150
+ INST(Ldnp , BaseLdpStp , (0b0010100001, 0 , kWX, 31, 2) , kRWI_WW , 0 , 0 ), // #151
+ INST(Ldp , BaseLdpStp , (0b0010100101, 0b0010100011, kWX, 31, 2) , kRWI_W , 0 , 1 ), // #152
+ INST(Ldpsw , BaseLdpStp , (0b0110100101, 0b0110100011, kX , 0 , 2) , kRWI_WW , 0 , 2 ), // #153
+ INST(Ldr , BaseLdSt , (0b1011100101, 0b10111000010, 0b10111000011, 0b00011000, kWX, 30, 2, Inst::kIdLdur) , kRWI_W , 0 , 0 ), // #154
+ INST(Ldraa , BaseRM_SImm10 , (0b1111100000100000000001, kX , kZR, 0, 3) , kRWI_W , 0 , 0 ), // #155
+ INST(Ldrab , BaseRM_SImm10 , (0b1111100010100000000001, kX , kZR, 0, 3) , kRWI_W , 0 , 1 ), // #156
+ INST(Ldrb , BaseLdSt , (0b0011100101, 0b00111000010, 0b00111000011, 0 , kW , 0 , 0, Inst::kIdLdurb) , kRWI_W , 0 , 1 ), // #157
+ INST(Ldrh , BaseLdSt , (0b0111100101, 0b01111000010, 0b01111000011, 0 , kW , 0 , 1, Inst::kIdLdurh) , kRWI_W , 0 , 2 ), // #158
+ INST(Ldrsb , BaseLdSt , (0b0011100111, 0b00111000100, 0b00111000111, 0 , kWX, 22, 0, Inst::kIdLdursb) , kRWI_W , 0 , 3 ), // #159
+ INST(Ldrsh , BaseLdSt , (0b0111100111, 0b01111000100, 0b01111000111, 0 , kWX, 22, 1, Inst::kIdLdursh) , kRWI_W , 0 , 4 ), // #160
+ INST(Ldrsw , BaseLdSt , (0b1011100110, 0b10111000100, 0b10111000101, 0b10011000, kX , 0 , 2, Inst::kIdLdursw) , kRWI_W , 0 , 5 ), // #161
+ INST(Ldset , BaseAtomicOp , (0b1011100000100000001100, kWX, 30, 0) , kRWI_WRX , 0 , 48 ), // #162
+ INST(Ldseta , BaseAtomicOp , (0b1011100010100000001100, kWX, 30, 1) , kRWI_WRX , 0 , 49 ), // #163
+ INST(Ldsetab , BaseAtomicOp , (0b0011100010100000001100, kW , 0 , 1) , kRWI_WRX , 0 , 50 ), // #164
+ INST(Ldsetah , BaseAtomicOp , (0b0111100010100000001100, kW , 0 , 1) , kRWI_WRX , 0 , 51 ), // #165
+ INST(Ldsetal , BaseAtomicOp , (0b1011100011100000001100, kWX, 30, 1) , kRWI_WRX , 0 , 52 ), // #166
+ INST(Ldsetalb , BaseAtomicOp , (0b0011100011100000001100, kW , 0 , 1) , kRWI_WRX , 0 , 53 ), // #167
+ INST(Ldsetalh , BaseAtomicOp , (0b0111100011100000001100, kW , 0 , 1) , kRWI_WRX , 0 , 54 ), // #168
+ INST(Ldsetb , BaseAtomicOp , (0b0011100000100000001100, kW , 0 , 0) , kRWI_WRX , 0 , 55 ), // #169
+ INST(Ldseth , BaseAtomicOp , (0b0111100000100000001100, kW , 0 , 0) , kRWI_WRX , 0 , 56 ), // #170
+ INST(Ldsetl , BaseAtomicOp , (0b1011100001100000001100, kWX, 30, 0) , kRWI_WRX , 0 , 57 ), // #171
+ INST(Ldsetlb , BaseAtomicOp , (0b0011100001100000001100, kW , 0 , 0) , kRWI_WRX , 0 , 58 ), // #172
+ INST(Ldsetlh , BaseAtomicOp , (0b0111100001100000001100, kW , 0 , 0) , kRWI_WRX , 0 , 59 ), // #173
+ INST(Ldsmax , BaseAtomicOp , (0b1011100000100000010000, kWX, 30, 0) , kRWI_WRX , 0 , 60 ), // #174
+ INST(Ldsmaxa , BaseAtomicOp , (0b1011100010100000010000, kWX, 30, 1) , kRWI_WRX , 0 , 61 ), // #175
+ INST(Ldsmaxab , BaseAtomicOp , (0b0011100010100000010000, kW , 0 , 1) , kRWI_WRX , 0 , 62 ), // #176
+ INST(Ldsmaxah , BaseAtomicOp , (0b0111100010100000010000, kW , 0 , 1) , kRWI_WRX , 0 , 63 ), // #177
+ INST(Ldsmaxal , BaseAtomicOp , (0b1011100011100000010000, kWX, 30, 1) , kRWI_WRX , 0 , 64 ), // #178
+ INST(Ldsmaxalb , BaseAtomicOp , (0b0011100011100000010000, kW , 0 , 1) , kRWI_WRX , 0 , 65 ), // #179
+ INST(Ldsmaxalh , BaseAtomicOp , (0b0111100011100000010000, kW , 0 , 1) , kRWI_WRX , 0 , 66 ), // #180
+ INST(Ldsmaxb , BaseAtomicOp , (0b0011100000100000010000, kW , 0 , 0) , kRWI_WRX , 0 , 67 ), // #181
+ INST(Ldsmaxh , BaseAtomicOp , (0b0111100000100000010000, kW , 0 , 0) , kRWI_WRX , 0 , 68 ), // #182
+ INST(Ldsmaxl , BaseAtomicOp , (0b1011100001100000010000, kWX, 30, 0) , kRWI_WRX , 0 , 69 ), // #183
+ INST(Ldsmaxlb , BaseAtomicOp , (0b0011100001100000010000, kW , 0 , 0) , kRWI_WRX , 0 , 70 ), // #184
+ INST(Ldsmaxlh , BaseAtomicOp , (0b0111100001100000010000, kW , 0 , 0) , kRWI_WRX , 0 , 71 ), // #185
+ INST(Ldsmin , BaseAtomicOp , (0b1011100000100000010100, kWX, 30, 0) , kRWI_WRX , 0 , 72 ), // #186
+ INST(Ldsmina , BaseAtomicOp , (0b1011100010100000010100, kWX, 30, 1) , kRWI_WRX , 0 , 73 ), // #187
+ INST(Ldsminab , BaseAtomicOp , (0b0011100010100000010100, kW , 0 , 1) , kRWI_WRX , 0 , 74 ), // #188
+ INST(Ldsminah , BaseAtomicOp , (0b0111100010100000010100, kW , 0 , 1) , kRWI_WRX , 0 , 75 ), // #189
+ INST(Ldsminal , BaseAtomicOp , (0b1011100011100000010100, kWX, 30, 1) , kRWI_WRX , 0 , 76 ), // #190
+ INST(Ldsminalb , BaseAtomicOp , (0b0011100011100000010100, kW , 0 , 1) , kRWI_WRX , 0 , 77 ), // #191
+ INST(Ldsminalh , BaseAtomicOp , (0b0111100011100000010100, kW , 0 , 1) , kRWI_WRX , 0 , 78 ), // #192
+ INST(Ldsminb , BaseAtomicOp , (0b0011100000100000010100, kW , 0 , 0) , kRWI_WRX , 0 , 79 ), // #193
+ INST(Ldsminh , BaseAtomicOp , (0b0111100000100000010100, kW , 0 , 0) , kRWI_WRX , 0 , 80 ), // #194
+ INST(Ldsminl , BaseAtomicOp , (0b1011100001100000010100, kWX, 30, 0) , kRWI_WRX , 0 , 81 ), // #195
+ INST(Ldsminlb , BaseAtomicOp , (0b0011100001100000010100, kW , 0 , 0) , kRWI_WRX , 0 , 82 ), // #196
+ INST(Ldsminlh , BaseAtomicOp , (0b0111100001100000010100, kW , 0 , 0) , kRWI_WRX , 0 , 83 ), // #197
+ INST(Ldtr , BaseRM_SImm9 , (0b1011100001000000000010, 0b0000000000000000000000, kWX, kZR, 30, 0) , kRWI_W , 0 , 1 ), // #198
+ INST(Ldtrb , BaseRM_SImm9 , (0b0011100001000000000010, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_W , 0 , 2 ), // #199
+ INST(Ldtrh , BaseRM_SImm9 , (0b0111100001000000000010, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_W , 0 , 3 ), // #200
+ INST(Ldtrsb , BaseRM_SImm9 , (0b0011100011000000000010, 0b0000000000000000000000, kWX, kZR, 22, 0) , kRWI_W , 0 , 4 ), // #201
+ INST(Ldtrsh , BaseRM_SImm9 , (0b0111100011000000000010, 0b0000000000000000000000, kWX, kZR, 22, 0) , kRWI_W , 0 , 5 ), // #202
+ INST(Ldtrsw , BaseRM_SImm9 , (0b1011100010000000000010, 0b0000000000000000000000, kX , kZR, 0 , 0) , kRWI_W , 0 , 6 ), // #203
+ INST(Ldumax , BaseAtomicOp , (0b1011100000100000011000, kWX, 30, 0) , kRWI_WRX , 0 , 84 ), // #204
+ INST(Ldumaxa , BaseAtomicOp , (0b1011100010100000011000, kWX, 30, 1) , kRWI_WRX , 0 , 85 ), // #205
+ INST(Ldumaxab , BaseAtomicOp , (0b0011100010100000011000, kW , 0 , 1) , kRWI_WRX , 0 , 86 ), // #206
+ INST(Ldumaxah , BaseAtomicOp , (0b0111100010100000011000, kW , 0 , 1) , kRWI_WRX , 0 , 87 ), // #207
+ INST(Ldumaxal , BaseAtomicOp , (0b1011100011100000011000, kWX, 30, 1) , kRWI_WRX , 0 , 88 ), // #208
+ INST(Ldumaxalb , BaseAtomicOp , (0b0011100011100000011000, kW , 0 , 1) , kRWI_WRX , 0 , 89 ), // #209
+ INST(Ldumaxalh , BaseAtomicOp , (0b0111100011100000011000, kW , 0 , 1) , kRWI_WRX , 0 , 90 ), // #210
+ INST(Ldumaxb , BaseAtomicOp , (0b0011100000100000011000, kW , 0 , 0) , kRWI_WRX , 0 , 91 ), // #211
+ INST(Ldumaxh , BaseAtomicOp , (0b0111100000100000011000, kW , 0 , 0) , kRWI_WRX , 0 , 92 ), // #212
+ INST(Ldumaxl , BaseAtomicOp , (0b1011100001100000011000, kWX, 30, 0) , kRWI_WRX , 0 , 93 ), // #213
+ INST(Ldumaxlb , BaseAtomicOp , (0b0011100001100000011000, kW , 0 , 0) , kRWI_WRX , 0 , 94 ), // #214
+ INST(Ldumaxlh , BaseAtomicOp , (0b0111100001100000011000, kW , 0 , 0) , kRWI_WRX , 0 , 95 ), // #215
+ INST(Ldumin , BaseAtomicOp , (0b1011100000100000011100, kWX, 30, 0) , kRWI_WRX , 0 , 96 ), // #216
+ INST(Ldumina , BaseAtomicOp , (0b1011100010100000011100, kWX, 30, 1) , kRWI_WRX , 0 , 97 ), // #217
+ INST(Lduminab , BaseAtomicOp , (0b0011100010100000011100, kW , 0 , 1) , kRWI_WRX , 0 , 98 ), // #218
+ INST(Lduminah , BaseAtomicOp , (0b0111100010100000011100, kW , 0 , 1) , kRWI_WRX , 0 , 99 ), // #219
+ INST(Lduminal , BaseAtomicOp , (0b1011100011100000011100, kWX, 30, 1) , kRWI_WRX , 0 , 100), // #220
+ INST(Lduminalb , BaseAtomicOp , (0b0011100011100000011100, kW , 0 , 1) , kRWI_WRX , 0 , 101), // #221
+ INST(Lduminalh , BaseAtomicOp , (0b0111100011100000011100, kW , 0 , 1) , kRWI_WRX , 0 , 102), // #222
+ INST(Lduminb , BaseAtomicOp , (0b0011100000100000011100, kW , 0 , 0) , kRWI_WRX , 0 , 103), // #223
+ INST(Lduminh , BaseAtomicOp , (0b0111100000100000011100, kW , 0 , 0) , kRWI_WRX , 0 , 104), // #224
+ INST(Lduminl , BaseAtomicOp , (0b1011100001100000011100, kWX, 30, 0) , kRWI_WRX , 0 , 105), // #225
+ INST(Lduminlb , BaseAtomicOp , (0b0011100001100000011100, kW , 0 , 0) , kRWI_WRX , 0 , 106), // #226
+ INST(Lduminlh , BaseAtomicOp , (0b0111100001100000011100, kW , 0 , 0) , kRWI_WRX , 0 , 107), // #227
+ INST(Ldur , BaseRM_SImm9 , (0b1011100001000000000000, 0b0000000000000000000000, kWX, kZR, 30, 0) , kRWI_W , 0 , 7 ), // #228
+ INST(Ldurb , BaseRM_SImm9 , (0b0011100001000000000000, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_W , 0 , 8 ), // #229
+ INST(Ldurh , BaseRM_SImm9 , (0b0111100001000000000000, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_W , 0 , 9 ), // #230
+ INST(Ldursb , BaseRM_SImm9 , (0b0011100011000000000000, 0b0000000000000000000000, kWX, kZR, 22, 0) , kRWI_W , 0 , 10 ), // #231
+ INST(Ldursh , BaseRM_SImm9 , (0b0111100011000000000000, 0b0000000000000000000000, kWX, kZR, 22, 0) , kRWI_W , 0 , 11 ), // #232
+ INST(Ldursw , BaseRM_SImm9 , (0b1011100010000000000000, 0b0000000000000000000000, kX , kZR, 0 , 0) , kRWI_W , 0 , 12 ), // #233
+ INST(Ldxp , BaseLdxp , (0b1000100001111111000000, kWX, 30) , kRWI_WW , 0 , 1 ), // #234
+ INST(Ldxr , BaseRM_NoImm , (0b1000100001011111011111, kWX, kZR, 30) , kRWI_W , 0 , 10 ), // #235
+ INST(Ldxrb , BaseRM_NoImm , (0b0000100001011111011111, kW , kZR, 0 ) , kRWI_W , 0 , 11 ), // #236
+ INST(Ldxrh , BaseRM_NoImm , (0b0100100001011111011111, kW , kZR, 0 ) , kRWI_W , 0 , 12 ), // #237
+ INST(Lsl , BaseShift , (0b0001101011000000001000, 0b0101001100000000000000, 0) , kRWI_W , 0 , 2 ), // #238
+ INST(Lslv , BaseShift , (0b0001101011000000001000, 0b0000000000000000000000, 0) , kRWI_W , 0 , 3 ), // #239
+ INST(Lsr , BaseShift , (0b0001101011000000001001, 0b0101001100000000011111, 0) , kRWI_W , 0 , 4 ), // #240
+ INST(Lsrv , BaseShift , (0b0001101011000000001001, 0b0000000000000000000000, 0) , kRWI_W , 0 , 5 ), // #241
+ INST(Madd , BaseRRRR , (0b0001101100000000000000, kWX, kZR, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 0 ), // #242
+ INST(Mneg , BaseRRR , (0b0001101100000000111111, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 11 ), // #243
+ INST(Mov , BaseMov , (_) , kRWI_W , 0 , 0 ), // #244
+ INST(Movk , BaseMovKNZ , (0b01110010100000000000000000000000) , kRWI_X , 0 , 0 ), // #245
+ INST(Movn , BaseMovKNZ , (0b00010010100000000000000000000000) , kRWI_W , 0 , 1 ), // #246
+ INST(Movz , BaseMovKNZ , (0b01010010100000000000000000000000) , kRWI_W , 0 , 2 ), // #247
+ INST(Mrs , BaseMrs , (_) , kRWI_W , 0 , 0 ), // #248
+ INST(Msr , BaseMsr , (_) , kRWI_W , 0 , 0 ), // #249
+ INST(Msub , BaseRRRR , (0b0001101100000000100000, kWX, kZR, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 1 ), // #250
+ INST(Mul , BaseRRR , (0b0001101100000000011111, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 12 ), // #251
+ INST(Mvn , BaseMvnNeg , (0b00101010001000000000001111100000) , kRWI_W , 0 , 0 ), // #252
+ INST(Neg , BaseMvnNeg , (0b01001011000000000000001111100000) , kRWI_W , 0 , 1 ), // #253
+ INST(Negs , BaseMvnNeg , (0b01101011000000000000001111100000) , kRWI_W , 0 , 2 ), // #254
+ INST(Ngc , BaseRR , (0b01011010000000000000001111100000, kWX, kZR, 0, kWX, kZR, 16, true) , kRWI_W , 0 , 7 ), // #255
+ INST(Ngcs , BaseRR , (0b01111010000000000000001111100000, kWX, kZR, 0, kWX, kZR, 16, true) , kRWI_W , 0 , 8 ), // #256
+ INST(Nop , BaseOp , (0b11010101000000110010000000011111) , 0 , 0 , 13 ), // #257
+ INST(Orn , BaseLogical , (0b0101010001, 0b01100100, 1) , kRWI_W , 0 , 6 ), // #258
+ INST(Orr , BaseLogical , (0b0101010000, 0b01100100, 0) , kRWI_W , 0 , 7 ), // #259
+ INST(Pacda , BaseRR , (0b11011010110000010000100000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 9 ), // #260
+ INST(Pacdb , BaseRR , (0b11011010110000010000110000000000, kX, kZR, 0, kX, kSP, 5, true) , kRWI_X , 0 , 10 ), // #261
+ INST(Pacdza , BaseR , (0b11011010110000010010101111100000, kX, kZR, 0) , kRWI_X , 0 , 4 ), // #262
+ INST(Pacdzb , BaseR , (0b11011010110000010010111111100000, kX, kZR, 0) , kRWI_X , 0 , 5 ), // #263
+ INST(Pacga , BaseRRR , (0b1001101011000000001100, kX, kZR, kX, kZR, kX, kSP, false) , kRWI_W , 0 , 13 ), // #264
+ INST(Pssbb , BaseOp , (0b11010101000000110011010010011111) , 0 , 0 , 14 ), // #265
+ INST(Rbit , BaseRR , (0b01011010110000000000000000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 11 ), // #266
+ INST(Ret , BaseBranchReg , (0b11010110010111110000000000000000) , kRWI_R , 0 , 2 ), // #267
+ INST(Rev , BaseRev , (_) , kRWI_W , 0 , 0 ), // #268
+ INST(Rev16 , BaseRR , (0b01011010110000000000010000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 12 ), // #269
+ INST(Rev32 , BaseRR , (0b11011010110000000000100000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 13 ), // #270
+ INST(Rev64 , BaseRR , (0b11011010110000000000110000000000, kWX, kZR, 0, kWX, kZR, 5, true) , kRWI_W , 0 , 14 ), // #271
+ INST(Ror , BaseShift , (0b0001101011000000001011, 0b0001001110000000000000, 1) , kRWI_W , 0 , 6 ), // #272
+ INST(Rorv , BaseShift , (0b0001101011000000001011, 0b0000000000000000000000, 1) , kRWI_W , 0 , 7 ), // #273
+ INST(Sbc , BaseRRR , (0b0101101000000000000000, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 14 ), // #274
+ INST(Sbcs , BaseRRR , (0b0111101000000000000000, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 15 ), // #275
+ INST(Sbfiz , BaseBfi , (0b00010011000000000000000000000000) , kRWI_W , 0 , 1 ), // #276
+ INST(Sbfm , BaseBfm , (0b00010011000000000000000000000000) , kRWI_W , 0 , 1 ), // #277
+ INST(Sbfx , BaseBfx , (0b00010011000000000000000000000000) , kRWI_W , 0 , 1 ), // #278
+ INST(Sdiv , BaseRRR , (0b0001101011000000000011, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 16 ), // #279
+ INST(Setf8 , BaseR , (0b00111010000000000000100000001101, kW, kZR, 5) , 0 , 0 , 6 ), // #280
+ INST(Setf16 , BaseR , (0b00111010000000000100100000001101, kW, kZR, 5) , 0 , 0 , 7 ), // #281
+ INST(Sev , BaseOp , (0b11010101000000110010000010011111) , 0 , 0 , 15 ), // #282
+ INST(Sevl , BaseOp , (0b11010101000000110010000010111111) , 0 , 0 , 16 ), // #283
+ INST(Smaddl , BaseRRRR , (0b1001101100100000000000, kX , kZR, kW , kZR, kW , kZR, kX , kZR, false) , kRWI_W , 0 , 2 ), // #284
+ INST(Smc , BaseOpImm , (0b11010100000000000000000000000011, 16, 5) , 0 , 0 , 11 ), // #285
+ INST(Smnegl , BaseRRR , (0b1001101100100000111111, kX , kZR, kW , kZR, kW , kZR, false) , kRWI_W , 0 , 17 ), // #286
+ INST(Smsubl , BaseRRRR , (0b1001101100100000100000, kX , kZR, kW , kZR, kW , kZR, kX , kZR, false) , kRWI_W , 0 , 3 ), // #287
+ INST(Smulh , BaseRRR , (0b1001101101000000011111, kX , kZR, kX , kZR, kX , kZR, true) , kRWI_W , 0 , 18 ), // #288
+ INST(Smull , BaseRRR , (0b1001101100100000011111, kX , kZR, kW , kZR, kW , kZR, false) , kRWI_W , 0 , 19 ), // #289
+ INST(Ssbb , BaseOp , (0b11010101000000110011000010011111) , 0 , 0 , 17 ), // #290
+ INST(St2g , BaseRM_SImm9 , (0b1101100110100000000010, 0b1101100110100000000001, kX, kSP, 0, 4) , kRWI_RW , 0 , 13 ), // #291
+ INST(Stadd , BaseAtomicSt , (0b1011100000100000000000, kWX, 30) , kRWI_RX , 0 , 0 ), // #292
+ INST(Staddl , BaseAtomicSt , (0b1011100001100000000000, kWX, 30) , kRWI_RX , 0 , 1 ), // #293
+ INST(Staddb , BaseAtomicSt , (0b0011100000100000000000, kW , 0 ) , kRWI_RX , 0 , 2 ), // #294
+ INST(Staddlb , BaseAtomicSt , (0b0011100001100000000000, kW , 0 ) , kRWI_RX , 0 , 3 ), // #295
+ INST(Staddh , BaseAtomicSt , (0b0111100000100000000000, kW , 0 ) , kRWI_RX , 0 , 4 ), // #296
+ INST(Staddlh , BaseAtomicSt , (0b0111100001100000000000, kW , 0 ) , kRWI_RX , 0 , 5 ), // #297
+ INST(Stclr , BaseAtomicSt , (0b1011100000100000000100, kWX, 30) , kRWI_RX , 0 , 6 ), // #298
+ INST(Stclrl , BaseAtomicSt , (0b1011100001100000000100, kWX, 30) , kRWI_RX , 0 , 7 ), // #299
+ INST(Stclrb , BaseAtomicSt , (0b0011100000100000000100, kW , 0 ) , kRWI_RX , 0 , 8 ), // #300
+ INST(Stclrlb , BaseAtomicSt , (0b0011100001100000000100, kW , 0 ) , kRWI_RX , 0 , 9 ), // #301
+ INST(Stclrh , BaseAtomicSt , (0b0111100000100000000100, kW , 0 ) , kRWI_RX , 0 , 10 ), // #302
+ INST(Stclrlh , BaseAtomicSt , (0b0111100001100000000100, kW , 0 ) , kRWI_RX , 0 , 11 ), // #303
+ INST(Steor , BaseAtomicSt , (0b1011100000100000001000, kWX, 30) , kRWI_RX , 0 , 12 ), // #304
+ INST(Steorl , BaseAtomicSt , (0b1011100001100000001000, kWX, 30) , kRWI_RX , 0 , 13 ), // #305
+ INST(Steorb , BaseAtomicSt , (0b0011100000100000001000, kW , 0 ) , kRWI_RX , 0 , 14 ), // #306
+ INST(Steorlb , BaseAtomicSt , (0b0011100001100000001000, kW , 0 ) , kRWI_RX , 0 , 15 ), // #307
+ INST(Steorh , BaseAtomicSt , (0b0111100000100000001000, kW , 0 ) , kRWI_RX , 0 , 16 ), // #308
+ INST(Steorlh , BaseAtomicSt , (0b0111100001100000001000, kW , 0 ) , kRWI_RX , 0 , 17 ), // #309
+ INST(Stg , BaseRM_SImm9 , (0b1101100100100000000010, 0b1101100100100000000001, kX, kSP, 0, 4) , kRWI_RW , 0 , 14 ), // #310
+ INST(Stgm , BaseRM_NoImm , (0b1101100110100000000000, kX , kZR, 0 ) , kRWI_RW , 0 , 13 ), // #311
+ INST(Stgp , BaseLdpStp , (0b0110100100, 0b0110100010, kX, 0, 4) , kRWI_RRW , 0 , 3 ), // #312
+ INST(Stllr , BaseRM_NoImm , (0b1000100010011111011111, kWX, kZR, 30) , kRWI_RW , 0 , 14 ), // #313
+ INST(Stllrb , BaseRM_NoImm , (0b0000100010011111011111, kW , kZR, 0 ) , kRWI_RW , 0 , 15 ), // #314
+ INST(Stllrh , BaseRM_NoImm , (0b0100100010011111011111, kW , kZR, 0 ) , kRWI_RW , 0 , 16 ), // #315
+ INST(Stlr , BaseRM_NoImm , (0b1000100010011111111111, kWX, kZR, 30) , kRWI_RW , 0 , 17 ), // #316
+ INST(Stlrb , BaseRM_NoImm , (0b0000100010011111111111, kW , kZR, 0 ) , kRWI_RW , 0 , 18 ), // #317
+ INST(Stlrh , BaseRM_NoImm , (0b0100100010011111111111, kW , kZR, 0 ) , kRWI_RW , 0 , 19 ), // #318
+ INST(Stlxp , BaseStxp , (0b1000100000100000100000, kWX, 30) , kRWI_WRRX , 0 , 0 ), // #319
+ INST(Stlxr , BaseAtomicOp , (0b1000100000000000111111, kWX, 30, 1) , kRWI_WRX , 0 , 108), // #320
+ INST(Stlxrb , BaseAtomicOp , (0b0000100000000000111111, kW , 0 , 1) , kRWI_WRX , 0 , 109), // #321
+ INST(Stlxrh , BaseAtomicOp , (0b0100100000000000111111, kW , 0 , 1) , kRWI_WRX , 0 , 110), // #322
+ INST(Stnp , BaseLdpStp , (0b0010100000, 0 , kWX, 31, 2) , kRWI_RRW , 0 , 4 ), // #323
+ INST(Stp , BaseLdpStp , (0b0010100100, 0b0010100010, kWX, 31, 2) , kRWI_RRW , 0 , 5 ), // #324
+ INST(Str , BaseLdSt , (0b1011100100, 0b10111000000, 0b10111000001, 0 , kWX, 30, 2, Inst::kIdStur) , kRWI_RW , 0 , 6 ), // #325
+ INST(Strb , BaseLdSt , (0b0011100100, 0b00111000000, 0b00111000001, 0 , kW , 30, 0, Inst::kIdSturb) , kRWI_RW , 0 , 7 ), // #326
+ INST(Strh , BaseLdSt , (0b0111100100, 0b01111000000, 0b01111000001, 0 , kWX, 30, 1, Inst::kIdSturh) , kRWI_RW , 0 , 8 ), // #327
+ INST(Stset , BaseAtomicSt , (0b1011100000100000001100, kWX, 30) , kRWI_RX , 0 , 18 ), // #328
+ INST(Stsetl , BaseAtomicSt , (0b1011100001100000001100, kWX, 30) , kRWI_RX , 0 , 19 ), // #329
+ INST(Stsetb , BaseAtomicSt , (0b0011100000100000001100, kW , 0 ) , kRWI_RX , 0 , 20 ), // #330
+ INST(Stsetlb , BaseAtomicSt , (0b0011100001100000001100, kW , 0 ) , kRWI_RX , 0 , 21 ), // #331
+ INST(Stseth , BaseAtomicSt , (0b0111100000100000001100, kW , 0 ) , kRWI_RX , 0 , 22 ), // #332
+ INST(Stsetlh , BaseAtomicSt , (0b0111100001100000001100, kW , 0 ) , kRWI_RX , 0 , 23 ), // #333
+ INST(Stsmax , BaseAtomicSt , (0b1011100000100000010000, kWX, 30) , kRWI_RX , 0 , 24 ), // #334
+ INST(Stsmaxl , BaseAtomicSt , (0b1011100001100000010000, kWX, 30) , kRWI_RX , 0 , 25 ), // #335
+ INST(Stsmaxb , BaseAtomicSt , (0b0011100000100000010000, kW , 0 ) , kRWI_RX , 0 , 26 ), // #336
+ INST(Stsmaxlb , BaseAtomicSt , (0b0011100001100000010000, kW , 0 ) , kRWI_RX , 0 , 27 ), // #337
+ INST(Stsmaxh , BaseAtomicSt , (0b0111100000100000010000, kW , 0 ) , kRWI_RX , 0 , 28 ), // #338
+ INST(Stsmaxlh , BaseAtomicSt , (0b0111100001100000010000, kW , 0 ) , kRWI_RX , 0 , 29 ), // #339
+ INST(Stsmin , BaseAtomicSt , (0b1011100000100000010100, kWX, 30) , kRWI_RX , 0 , 30 ), // #340
+ INST(Stsminl , BaseAtomicSt , (0b1011100001100000010100, kWX, 30) , kRWI_RX , 0 , 31 ), // #341
+ INST(Stsminb , BaseAtomicSt , (0b0011100000100000010100, kW , 0 ) , kRWI_RX , 0 , 32 ), // #342
+ INST(Stsminlb , BaseAtomicSt , (0b0011100001100000010100, kW , 0 ) , kRWI_RX , 0 , 33 ), // #343
+ INST(Stsminh , BaseAtomicSt , (0b0111100000100000010100, kW , 0 ) , kRWI_RX , 0 , 34 ), // #344
+ INST(Stsminlh , BaseAtomicSt , (0b0111100001100000010100, kW , 0 ) , kRWI_RX , 0 , 35 ), // #345
+ INST(Sttr , BaseRM_SImm9 , (0b1011100000000000000010, 0b0000000000000000000000, kWX, kZR, 30, 0) , kRWI_RW , 0 , 15 ), // #346
+ INST(Sttrb , BaseRM_SImm9 , (0b0011100000000000000010, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_RW , 0 , 16 ), // #347
+ INST(Sttrh , BaseRM_SImm9 , (0b0111100000000000000010, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_RW , 0 , 17 ), // #348
+ INST(Stumax , BaseAtomicSt , (0b1011100000100000011000, kWX, 30) , kRWI_RX , 0 , 36 ), // #349
+ INST(Stumaxl , BaseAtomicSt , (0b1011100001100000011000, kWX, 30) , kRWI_RX , 0 , 37 ), // #350
+ INST(Stumaxb , BaseAtomicSt , (0b0011100000100000011000, kW , 0 ) , kRWI_RX , 0 , 38 ), // #351
+ INST(Stumaxlb , BaseAtomicSt , (0b0011100001100000011000, kW , 0 ) , kRWI_RX , 0 , 39 ), // #352
+ INST(Stumaxh , BaseAtomicSt , (0b0111100000100000011000, kW , 0 ) , kRWI_RX , 0 , 40 ), // #353
+ INST(Stumaxlh , BaseAtomicSt , (0b0111100001100000011000, kW , 0 ) , kRWI_RX , 0 , 41 ), // #354
+ INST(Stumin , BaseAtomicSt , (0b1011100000100000011100, kWX, 30) , kRWI_RX , 0 , 42 ), // #355
+ INST(Stuminl , BaseAtomicSt , (0b1011100001100000011100, kWX, 30) , kRWI_RX , 0 , 43 ), // #356
+ INST(Stuminb , BaseAtomicSt , (0b0011100000100000011100, kW , 0 ) , kRWI_RX , 0 , 44 ), // #357
+ INST(Stuminlb , BaseAtomicSt , (0b0011100001100000011100, kW , 0 ) , kRWI_RX , 0 , 45 ), // #358
+ INST(Stuminh , BaseAtomicSt , (0b0111100000100000011100, kW , 0 ) , kRWI_RX , 0 , 46 ), // #359
+ INST(Stuminlh , BaseAtomicSt , (0b0111100001100000011100, kW , 0 ) , kRWI_RX , 0 , 47 ), // #360
+ INST(Stur , BaseRM_SImm9 , (0b1011100000000000000000, 0b0000000000000000000000, kWX, kZR, 30, 0) , kRWI_RW , 0 , 18 ), // #361
+ INST(Sturb , BaseRM_SImm9 , (0b0011100000000000000000, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_RW , 0 , 19 ), // #362
+ INST(Sturh , BaseRM_SImm9 , (0b0111100000000000000000, 0b0000000000000000000000, kW , kZR, 0 , 0) , kRWI_RW , 0 , 20 ), // #363
+ INST(Stxp , BaseStxp , (0b1000100000100000000000, kWX, 30) , kRWI_WRRW , 0 , 1 ), // #364
+ INST(Stxr , BaseStx , (0b1000100000000000011111, kWX, 30) , kRWI_WRW , 0 , 0 ), // #365
+ INST(Stxrb , BaseStx , (0b0000100000000000011111, kW , 0 ) , kRWI_WRW , 0 , 1 ), // #366
+ INST(Stxrh , BaseStx , (0b0100100000000000011111, kW , 0 ) , kRWI_WRW , 0 , 2 ), // #367
+ INST(Stz2g , BaseRM_SImm9 , (0b1101100111100000000010, 0b1101100111100000000001, kX , kSP, 0, 4) , kRWI_RW , 0 , 21 ), // #368
+ INST(Stzg , BaseRM_SImm9 , (0b1101100101100000000010, 0b1101100101100000000001, kX , kSP, 0, 4) , kRWI_RW , 0 , 22 ), // #369
+ INST(Stzgm , BaseRM_NoImm , (0b1101100100100000000000, kX , kZR, 0) , kRWI_RW , 0 , 20 ), // #370
+ INST(Sub , BaseAddSub , (0b1001011000, 0b1001011001, 0b1010001) , kRWI_X , 0 , 2 ), // #371
+ INST(Subg , BaseRRII , (0b1101000110000000000000, kX, kSP, kX, kSP, 6, 4, 16, 4, 0, 10) , kRWI_W , 0 , 1 ), // #372
+ INST(Subp , BaseRRR , (0b1001101011000000000000, kX, kZR, kX, kSP, kX, kSP, false) , kRWI_W , 0 , 20 ), // #373
+ INST(Subps , BaseRRR , (0b1011101011000000000000, kX, kZR, kX, kSP, kX, kSP, false) , kRWI_W , 0 , 21 ), // #374
+ INST(Subs , BaseAddSub , (0b1101011000, 0b1101011001, 0b1110001) , kRWI_X , 0 , 3 ), // #375
+ INST(Svc , BaseOpImm , (0b11010100000000000000000000000001, 16, 5) , 0 , 0 , 12 ), // #376
+ INST(Swp , BaseAtomicOp , (0b1011100000100000100000, kWX, 30, 1) , kRWI_RWX , 0 , 111), // #377
+ INST(Swpa , BaseAtomicOp , (0b1011100010100000100000, kWX, 30, 1) , kRWI_RWX , 0 , 112), // #378
+ INST(Swpab , BaseAtomicOp , (0b0011100010100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 113), // #379
+ INST(Swpah , BaseAtomicOp , (0b0111100010100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 114), // #380
+ INST(Swpal , BaseAtomicOp , (0b1011100011100000100000, kWX, 30, 1) , kRWI_RWX , 0 , 115), // #381
+ INST(Swpalb , BaseAtomicOp , (0b0011100011100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 116), // #382
+ INST(Swpalh , BaseAtomicOp , (0b0111100011100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 117), // #383
+ INST(Swpb , BaseAtomicOp , (0b0011100000100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 118), // #384
+ INST(Swph , BaseAtomicOp , (0b0111100000100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 119), // #385
+ INST(Swpl , BaseAtomicOp , (0b1011100001100000100000, kWX, 30, 1) , kRWI_RWX , 0 , 120), // #386
+ INST(Swplb , BaseAtomicOp , (0b0011100001100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 121), // #387
+ INST(Swplh , BaseAtomicOp , (0b0111100001100000100000, kW , 0 , 1) , kRWI_RWX , 0 , 122), // #388
+ INST(Sxtb , BaseExtend , (0b0001001100000000000111, kWX, 0) , kRWI_W , 0 , 0 ), // #389
+ INST(Sxth , BaseExtend , (0b0001001100000000001111, kWX, 0) , kRWI_W , 0 , 1 ), // #390
+ INST(Sxtw , BaseExtend , (0b1001001101000000011111, kX , 0) , kRWI_W , 0 , 2 ), // #391
+ INST(Sys , BaseSys , (_) , kRWI_W , 0 , 0 ), // #392
+ INST(Tlbi , BaseAtDcIcTlbi , (0b00011110000000, 0b00010000000000, false) , kRWI_RX , 0 , 3 ), // #393
+ INST(Tst , BaseTst , (0b1101010000, 0b111001000) , kRWI_R , 0 , 0 ), // #394
+ INST(Tbnz , BaseBranchTst , (0b00110111000000000000000000000000) , kRWI_R , 0 , 0 ), // #395
+ INST(Tbz , BaseBranchTst , (0b00110110000000000000000000000000) , kRWI_R , 0 , 1 ), // #396
+ INST(Ubfiz , BaseBfi , (0b01010011000000000000000000000000) , kRWI_W , 0 , 2 ), // #397
+ INST(Ubfm , BaseBfm , (0b01010011000000000000000000000000) , kRWI_W , 0 , 2 ), // #398
+ INST(Ubfx , BaseBfx , (0b01010011000000000000000000000000) , kRWI_W , 0 , 2 ), // #399
+ INST(Udf , BaseOpImm , (0b00000000000000000000000000000000, 16, 0) , 0 , 0 , 13 ), // #400
+ INST(Udiv , BaseRRR , (0b0001101011000000000010, kWX, kZR, kWX, kZR, kWX, kZR, true) , kRWI_W , 0 , 22 ), // #401
+ INST(Umaddl , BaseRRRR , (0b1001101110100000000000, kX , kZR, kW , kZR, kW , kZR, kX , kZR, false) , kRWI_W , 0 , 4 ), // #402
+ INST(Umnegl , BaseRRR , (0b1001101110100000111111, kX , kZR, kW , kZR, kW , kZR, false) , kRWI_W , 0 , 23 ), // #403
+ INST(Umull , BaseRRR , (0b1001101110100000011111, kX , kZR, kW , kZR, kW , kZR, false) , kRWI_W , 0 , 24 ), // #404
+ INST(Umulh , BaseRRR , (0b1001101111000000011111, kX , kZR, kX , kZR, kX , kZR, false) , kRWI_W , 0 , 25 ), // #405
+ INST(Umsubl , BaseRRRR , (0b1001101110100000100000, kX , kZR, kW , kZR, kW , kZR, kX , kZR, false) , kRWI_W , 0 , 5 ), // #406
+ INST(Uxtb , BaseExtend , (0b0101001100000000000111, kW, 1) , kRWI_W , 0 , 3 ), // #407
+ INST(Uxth , BaseExtend , (0b0101001100000000001111, kW, 1) , kRWI_W , 0 , 4 ), // #408
+ INST(Wfe , BaseOp , (0b11010101000000110010000001011111) , 0 , 0 , 18 ), // #409
+ INST(Wfi , BaseOp , (0b11010101000000110010000001111111) , 0 , 0 , 19 ), // #410
+ INST(Xaflag , BaseOp , (0b11010101000000000100000000111111) , 0 , 0 , 20 ), // #411
+ INST(Xpacd , BaseR , (0b11011010110000010100011111100000, kX, kZR, 0) , kRWI_X , 0 , 8 ), // #412
+ INST(Xpaci , BaseR , (0b11011010110000010100001111100000, kX, kZR, 0) , kRWI_X , 0 , 9 ), // #413
+ INST(Xpaclri , BaseOp , (0b11010101000000110010000011111111) , kRWI_X , 0 , 21 ), // #414
+ INST(Yield , BaseOp , (0b11010101000000110010000000111111) , 0 , 0 , 22 ), // #415
+ INST(Abs_v , ISimdVV , (0b0000111000100000101110, kVO_V_Any) , kRWI_W , 0 , 0 ), // #416
+ INST(Add_v , ISimdVVV , (0b0000111000100000100001, kVO_V_Any) , kRWI_W , 0 , 0 ), // #417
+ INST(Addhn_v , ISimdVVV , (0b0000111000100000010000, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 1 ), // #418
+ INST(Addhn2_v , ISimdVVV , (0b0100111000100000010000, kVO_V_B16H8S4) , kRWI_W , F(Narrow) , 2 ), // #419
+ INST(Addp_v , ISimdPair , (0b0101111000110001101110, 0b0000111000100000101111, kVO_V_Any) , kRWI_W , F(Pair) , 0 ), // #420
+ INST(Addv_v , ISimdSV , (0b0000111000110001101110, kVO_V_BH_4S) , kRWI_W , 0 , 0 ), // #421
+ INST(Aesd_v , ISimdVVx , (0b0100111000101000010110, kOp_V16B, kOp_V16B) , kRWI_X , 0 , 0 ), // #422
+ INST(Aese_v , ISimdVVx , (0b0100111000101000010010, kOp_V16B, kOp_V16B) , kRWI_X , 0 , 1 ), // #423
+ INST(Aesimc_v , ISimdVVx , (0b0100111000101000011110, kOp_V16B, kOp_V16B) , kRWI_W , 0 , 2 ), // #424
+ INST(Aesmc_v , ISimdVVx , (0b0100111000101000011010, kOp_V16B, kOp_V16B) , kRWI_W , 0 , 3 ), // #425
+ INST(And_v , ISimdVVV , (0b0000111000100000000111, kVO_V_B) , kRWI_W , 0 , 3 ), // #426
+ INST(Bcax_v , ISimdVVVV , (0b1100111000100000000000, kVO_V_B16) , kRWI_W , 0 , 0 ), // #427
+ INST(Bfcvt_v , ISimdVVx , (0b0001111001100011010000, kOp_H, kOp_S) , kRWI_W , 0 , 4 ), // #428
+ INST(Bfcvtn_v , ISimdVVx , (0b0000111010100001011010, kOp_V4H, kOp_V4S) , kRWI_W , F(Narrow) , 5 ), // #429
+ INST(Bfcvtn2_v , ISimdVVx , (0b0100111010100001011010, kOp_V8H, kOp_V4S) , kRWI_W , F(Narrow) , 6 ), // #430
+ INST(Bfdot_v , SimdDot , (0b0010111001000000111111, 0b0000111101000000111100, kET_S, kET_H, kET_2H) , kRWI_X , 0 , 0 ), // #431
+ INST(Bfmlalb_v , SimdFmlal , (0b0010111011000000111111, 0b0000111111000000111100, 0, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 0 ), // #432
+ INST(Bfmlalt_v , SimdFmlal , (0b0110111011000000111111, 0b0100111111000000111100, 0, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 1 ), // #433
+ INST(Bfmmla_v , ISimdVVVx , (0b0110111001000000111011, kOp_V4S, kOp_V8H, kOp_V8H) , kRWI_X , F(Long) , 0 ), // #434
+ INST(Bic_v , SimdBicOrr , (0b0000111001100000000111, 0b0010111100000000000001) , kRWI_W , 0 , 0 ), // #435
+ INST(Bif_v , ISimdVVV , (0b0010111011100000000111, kVO_V_B) , kRWI_X , 0 , 4 ), // #436
+ INST(Bit_v , ISimdVVV , (0b0010111010100000000111, kVO_V_B) , kRWI_X , 0 , 5 ), // #437
+ INST(Bsl_v , ISimdVVV , (0b0010111001100000000111, kVO_V_B) , kRWI_X , 0 , 6 ), // #438
+ INST(Cls_v , ISimdVV , (0b0000111000100000010010, kVO_V_BHS) , kRWI_W , 0 , 1 ), // #439
+ INST(Clz_v , ISimdVV , (0b0010111000100000010010, kVO_V_BHS) , kRWI_W , 0 , 2 ), // #440
+ INST(Cmeq_v , SimdCmp , (0b0010111000100000100011, 0b0000111000100000100110, kVO_V_Any) , kRWI_W , 0 , 0 ), // #441
+ INST(Cmge_v , SimdCmp , (0b0000111000100000001111, 0b0010111000100000100010, kVO_V_Any) , kRWI_W , 0 , 1 ), // #442
+ INST(Cmgt_v , SimdCmp , (0b0000111000100000001101, 0b0000111000100000100010, kVO_V_Any) , kRWI_W , 0 , 2 ), // #443
+ INST(Cmhi_v , SimdCmp , (0b0010111000100000001101, 0b0000000000000000000000, kVO_V_Any) , kRWI_W , 0 , 3 ), // #444
+ INST(Cmhs_v , SimdCmp , (0b0010111000100000001111, 0b0000000000000000000000, kVO_V_Any) , kRWI_W , 0 , 4 ), // #445
+ INST(Cmle_v , SimdCmp , (0b0000000000000000000000, 0b0010111000100000100110, kVO_V_Any) , kRWI_W , 0 , 5 ), // #446
+ INST(Cmlt_v , SimdCmp , (0b0000000000000000000000, 0b0000111000100000101010, kVO_V_Any) , kRWI_W , 0 , 6 ), // #447
+ INST(Cmtst_v , ISimdVVV , (0b0000111000100000100011, kVO_V_Any) , kRWI_W , 0 , 7 ), // #448
+ INST(Cnt_v , ISimdVV , (0b0000111000100000010110, kVO_V_B) , kRWI_W , 0 , 3 ), // #449
+ INST(Dup_v , SimdDup , (_) , kRWI_W , 0 , 0 ), // #450
+ INST(Eor_v , ISimdVVV , (0b0010111000100000000111, kVO_V_B) , kRWI_W , 0 , 8 ), // #451
+ INST(Eor3_v , ISimdVVVV , (0b1100111000000000000000, kVO_V_B16) , kRWI_W , 0 , 1 ), // #452
+ INST(Ext_v , ISimdVVVI , (0b0010111000000000000000, kVO_V_B, 4, 11, 1) , kRWI_W , 0 , 0 ), // #453
+ INST(Fabd_v , FSimdVVV , (0b0111111010100000110101, kHF_C, 0b0010111010100000110101, kHF_C) , kRWI_W , 0 , 0 ), // #454
+ INST(Fabs_v , FSimdVV , (0b0001111000100000110000, kHF_A, 0b0000111010100000111110, kHF_B) , kRWI_W , 0 , 0 ), // #455
+ INST(Facge_v , FSimdVVV , (0b0111111000100000111011, kHF_C, 0b0010111000100000111011, kHF_C) , kRWI_W , 0 , 1 ), // #456
+ INST(Facgt_v , FSimdVVV , (0b0111111010100000111011, kHF_C, 0b0010111010100000111011, kHF_C) , kRWI_W , 0 , 2 ), // #457
+ INST(Fadd_v , FSimdVVV , (0b0001111000100000001010, kHF_A, 0b0000111000100000110101, kHF_C) , kRWI_W , 0 , 3 ), // #458
+ INST(Faddp_v , FSimdPair , (0b0111111000110000110110, 0b0010111000100000110101) , kRWI_W , 0 , 0 ), // #459
+ INST(Fcadd_v , SimdFcadd , (0b0010111000000000111001) , kRWI_W , 0 , 0 ), // #460
+ INST(Fccmp_v , SimdFccmpFccmpe , (0b00011110001000000000010000000000) , kRWI_R , 0 , 0 ), // #461
+ INST(Fccmpe_v , SimdFccmpFccmpe , (0b00011110001000000000010000010000) , kRWI_R , 0 , 1 ), // #462
+ INST(Fcmeq_v , SimdFcm , (0b0000111000100000111001, kHF_C, 0b0000111010100000110110) , kRWI_W , 0 , 0 ), // #463
+ INST(Fcmge_v , SimdFcm , (0b0010111000100000111001, kHF_C, 0b0010111010100000110010) , kRWI_W , 0 , 1 ), // #464
+ INST(Fcmgt_v , SimdFcm , (0b0010111010100000111001, kHF_C, 0b0000111010100000110010) , kRWI_W , 0 , 2 ), // #465
+ INST(Fcmla_v , SimdFcmla , (0b0010111000000000110001, 0b0010111100000000000100) , kRWI_X , 0 , 0 ), // #466
+ INST(Fcmle_v , SimdFcm , (0b0000000000000000000000, kHF_C, 0b0010111010100000110110) , kRWI_W , 0 , 3 ), // #467
+ INST(Fcmlt_v , SimdFcm , (0b0000000000000000000000, kHF_C, 0b0000111010100000111010) , kRWI_W , 0 , 4 ), // #468
+ INST(Fcmp_v , SimdFcmpFcmpe , (0b00011110001000000010000000000000) , kRWI_R , 0 , 0 ), // #469
+ INST(Fcmpe_v , SimdFcmpFcmpe , (0b00011110001000000010000000010000) , kRWI_R , 0 , 1 ), // #470
+ INST(Fcsel_v , SimdFcsel , (_) , kRWI_W , 0 , 0 ), // #471
+ INST(Fcvt_v , SimdFcvt , (_) , kRWI_W , 0 , 0 ), // #472
+ INST(Fcvtas_v , SimdFcvtSV , (0b0000111000100001110010, 0b0000000000000000000000, 0b0001111000100100000000, 1) , kRWI_W , 0 , 0 ), // #473
+ INST(Fcvtau_v , SimdFcvtSV , (0b0010111000100001110010, 0b0000000000000000000000, 0b0001111000100101000000, 1) , kRWI_W , 0 , 1 ), // #474
+ INST(Fcvtl_v , SimdFcvtLN , (0b0000111000100001011110, 0, 0) , kRWI_W , F(Long) , 0 ), // #475
+ INST(Fcvtl2_v , SimdFcvtLN , (0b0100111000100001011110, 0, 0) , kRWI_W , F(Long) , 1 ), // #476
+ INST(Fcvtms_v , SimdFcvtSV , (0b0000111000100001101110, 0b0000000000000000000000, 0b0001111000110000000000, 1) , kRWI_W , 0 , 2 ), // #477
+ INST(Fcvtmu_v , SimdFcvtSV , (0b0010111000100001101110, 0b0000000000000000000000, 0b0001111000110001000000, 1) , kRWI_W , 0 , 3 ), // #478
+ INST(Fcvtn_v , SimdFcvtLN , (0b0000111000100001011010, 0, 0) , kRWI_W , F(Narrow) , 2 ), // #479
+ INST(Fcvtn2_v , SimdFcvtLN , (0b0100111000100001011010, 0, 0) , kRWI_X , F(Narrow) , 3 ), // #480
+ INST(Fcvtns_v , SimdFcvtSV , (0b0000111000100001101010, 0b0000000000000000000000, 0b0001111000100000000000, 1) , kRWI_W , 0 , 4 ), // #481
+ INST(Fcvtnu_v , SimdFcvtSV , (0b0010111000100001101010, 0b0000000000000000000000, 0b0001111000100001000000, 1) , kRWI_W , 0 , 5 ), // #482
+ INST(Fcvtps_v , SimdFcvtSV , (0b0000111010100001101010, 0b0000000000000000000000, 0b0001111000101000000000, 1) , kRWI_W , 0 , 6 ), // #483
+ INST(Fcvtpu_v , SimdFcvtSV , (0b0010111010100001101010, 0b0000000000000000000000, 0b0001111000101001000000, 1) , kRWI_W , 0 , 7 ), // #484
+ INST(Fcvtxn_v , SimdFcvtLN , (0b0010111000100001011010, 1, 1) , kRWI_W , F(Narrow) , 4 ), // #485
+ INST(Fcvtxn2_v , SimdFcvtLN , (0b0110111000100001011010, 1, 0) , kRWI_X , F(Narrow) , 5 ), // #486
+ INST(Fcvtzs_v , SimdFcvtSV , (0b0000111010100001101110, 0b0000111100000000111111, 0b0001111000111000000000, 1) , kRWI_W , 0 , 8 ), // #487
+ INST(Fcvtzu_v , SimdFcvtSV , (0b0010111010100001101110, 0b0010111100000000111111, 0b0001111000111001000000, 1) , kRWI_W , 0 , 9 ), // #488
+ INST(Fdiv_v , FSimdVVV , (0b0001111000100000000110, kHF_A, 0b0010111000100000111111, kHF_C) , kRWI_W , 0 , 4 ), // #489
+ INST(Fjcvtzs_v , ISimdVVx , (0b0001111001111110000000, kOp_GpW, kOp_D) , kRWI_W , 0 , 7 ), // #490
+ INST(Fmadd_v , FSimdVVVV , (0b0001111100000000000000, kHF_A, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 0 ), // #491
+ INST(Fmax_v , FSimdVVV , (0b0001111000100000010010, kHF_A, 0b0000111000100000111101, kHF_C) , kRWI_W , 0 , 5 ), // #492
+ INST(Fmaxnm_v , FSimdVVV , (0b0001111000100000011010, kHF_A, 0b0000111000100000110001, kHF_C) , kRWI_W , 0 , 6 ), // #493
+ INST(Fmaxnmp_v , FSimdPair , (0b0111111000110000110010, 0b0010111000100000110001) , kRWI_W , 0 , 1 ), // #494
+ INST(Fmaxnmv_v , FSimdSV , (0b0010111000110000110010) , kRWI_W , 0 , 0 ), // #495
+ INST(Fmaxp_v , FSimdPair , (0b0111111000110000111110, 0b0010111000100000111101) , kRWI_W , 0 , 2 ), // #496
+ INST(Fmaxv_v , FSimdSV , (0b0010111000110000111110) , kRWI_W , 0 , 1 ), // #497
+ INST(Fmin_v , FSimdVVV , (0b0001111000100000010110, kHF_A, 0b0000111010100000111101, kHF_C) , kRWI_W , 0 , 7 ), // #498
+ INST(Fminnm_v , FSimdVVV , (0b0001111000100000011110, kHF_A, 0b0000111010100000110001, kHF_C) , kRWI_W , 0 , 8 ), // #499
+ INST(Fminnmp_v , FSimdPair , (0b0111111010110000110010, 0b0010111010100000110001) , kRWI_W , 0 , 3 ), // #500
+ INST(Fminnmv_v , FSimdSV , (0b0010111010110000110010) , kRWI_W , 0 , 2 ), // #501
+ INST(Fminp_v , FSimdPair , (0b0111111010110000111110, 0b0010111010100000111101) , kRWI_W , 0 , 4 ), // #502
+ INST(Fminv_v , FSimdSV , (0b0010111010110000111110) , kRWI_W , 0 , 3 ), // #503
+ INST(Fmla_v , FSimdVVVe , (0b0000000000000000000000, kHF_N, 0b0000111000100000110011, 0b0000111110000000000100) , kRWI_X , F(VH0_15) , 0 ), // #504
+ INST(Fmlal_v , SimdFmlal , (0b0000111000100000111011, 0b0000111110000000000000, 1, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 2 ), // #505
+ INST(Fmlal2_v , SimdFmlal , (0b0010111000100000110011, 0b0010111110000000100000, 1, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 3 ), // #506
+ INST(Fmls_v , FSimdVVVe , (0b0000000000000000000000, kHF_N, 0b0000111010100000110011, 0b0000111110000000010100) , kRWI_X , F(VH0_15) , 1 ), // #507
+ INST(Fmlsl_v , SimdFmlal , (0b0000111010100000111011, 0b0000111110000000010000, 1, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 4 ), // #508
+ INST(Fmlsl2_v , SimdFmlal , (0b0010111010100000110011, 0b0010111110000000110000, 1, kET_S, kET_H, kET_H) , kRWI_X , F(VH0_15) , 5 ), // #509
+ INST(Fmov_v , SimdFmov , (_) , kRWI_W , 0 , 0 ), // #510
+ INST(Fmsub_v , FSimdVVVV , (0b0001111100000000100000, kHF_A, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 1 ), // #511
+ INST(Fmul_v , FSimdVVVe , (0b0001111000100000000010, kHF_A, 0b0010111000100000110111, 0b0000111110000000100100) , kRWI_W , F(VH0_15) , 2 ), // #512
+ INST(Fmulx_v , FSimdVVVe , (0b0101111000100000110111, kHF_C, 0b0000111000100000110111, 0b0010111110000000100100) , kRWI_W , F(VH0_15) , 3 ), // #513
+ INST(Fneg_v , FSimdVV , (0b0001111000100001010000, kHF_A, 0b0010111010100000111110, kHF_B) , kRWI_W , 0 , 1 ), // #514
+ INST(Fnmadd_v , FSimdVVVV , (0b0001111100100000000000, kHF_A, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 2 ), // #515
+ INST(Fnmsub_v , FSimdVVVV , (0b0001111100100000100000, kHF_A, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 3 ), // #516
+ INST(Fnmul_v , FSimdVVV , (0b0001111000100000100010, kHF_A, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 9 ), // #517
+ INST(Frecpe_v , FSimdVV , (0b0101111010100001110110, kHF_B, 0b0000111010100001110110, kHF_B) , kRWI_W , 0 , 2 ), // #518
+ INST(Frecps_v , FSimdVVV , (0b0101111000100000111111, kHF_C, 0b0000111000100000111111, kHF_C) , kRWI_W , 0 , 10 ), // #519
+ INST(Frecpx_v , FSimdVV , (0b0101111010100001111110, kHF_B, 0b0000000000000000000000, kHF_N) , kRWI_W , 0 , 3 ), // #520
+ INST(Frint32x_v , FSimdVV , (0b0001111000101000110000, kHF_N, 0b0010111000100001111010, kHF_N) , kRWI_W , 0 , 4 ), // #521
+ INST(Frint32z_v , FSimdVV , (0b0001111000101000010000, kHF_N, 0b0000111000100001111010, kHF_N) , kRWI_W , 0 , 5 ), // #522
+ INST(Frint64x_v , FSimdVV , (0b0001111000101001110000, kHF_N, 0b0010111000100001111110, kHF_N) , kRWI_W , 0 , 6 ), // #523
+ INST(Frint64z_v , FSimdVV , (0b0001111000101001010000, kHF_N, 0b0000111000100001111110, kHF_N) , kRWI_W , 0 , 7 ), // #524
+ INST(Frinta_v , FSimdVV , (0b0001111000100110010000, kHF_A, 0b0010111000100001100010, kHF_B) , kRWI_W , 0 , 8 ), // #525
+ INST(Frinti_v , FSimdVV , (0b0001111000100111110000, kHF_A, 0b0010111010100001100110, kHF_B) , kRWI_W , 0 , 9 ), // #526
+ INST(Frintm_v , FSimdVV , (0b0001111000100101010000, kHF_A, 0b0000111000100001100110, kHF_B) , kRWI_W , 0 , 10 ), // #527
+ INST(Frintn_v , FSimdVV , (0b0001111000100100010000, kHF_A, 0b0000111000100001100010, kHF_B) , kRWI_W , 0 , 11 ), // #528
+ INST(Frintp_v , FSimdVV , (0b0001111000100100110000, kHF_A, 0b0000111010100001100010, kHF_B) , kRWI_W , 0 , 12 ), // #529
+ INST(Frintx_v , FSimdVV , (0b0001111000100111010000, kHF_A, 0b0010111000100001100110, kHF_B) , kRWI_W , 0 , 13 ), // #530
+ INST(Frintz_v , FSimdVV , (0b0001111000100101110000, kHF_A, 0b0000111010100001100110, kHF_B) , kRWI_W , 0 , 14 ), // #531
+ INST(Frsqrte_v , FSimdVV , (0b0111111010100001110110, kHF_B, 0b0010111010100001110110, kHF_B) , kRWI_W , 0 , 15 ), // #532
+ INST(Frsqrts_v , FSimdVVV , (0b0101111010100000111111, kHF_C, 0b0000111010100000111111, kHF_C) , kRWI_W , 0 , 11 ), // #533
+ INST(Fsqrt_v , FSimdVV , (0b0001111000100001110000, kHF_A, 0b0010111010100001111110, kHF_B) , kRWI_W , 0 , 16 ), // #534
+ INST(Fsub_v , FSimdVVV , (0b0001111000100000001110, kHF_A, 0b0000111010100000110101, kHF_C) , kRWI_W , 0 , 12 ), // #535
+ INST(Ins_v , SimdIns , (_) , kRWI_X , 0 , 0 ), // #536
+ INST(Ld1_v , SimdLdNStN , (0b0000110101000000000000, 0b0000110001000000001000, 1, 0) , kRWI_LDn , F(Consecutive) , 0 ), // #537
+ INST(Ld1r_v , SimdLdNStN , (0b0000110101000000110000, 0b0000000000000000000000, 1, 1) , kRWI_LDn , F(Consecutive) , 1 ), // #538
+ INST(Ld2_v , SimdLdNStN , (0b0000110101100000000000, 0b0000110001000000100000, 2, 0) , kRWI_LDn , F(Consecutive) , 2 ), // #539
+ INST(Ld2r_v , SimdLdNStN , (0b0000110101100000110000, 0b0000000000000000000000, 2, 1) , kRWI_LDn , F(Consecutive) , 3 ), // #540
+ INST(Ld3_v , SimdLdNStN , (0b0000110101000000001000, 0b0000110001000000010000, 3, 0) , kRWI_LDn , F(Consecutive) , 4 ), // #541
+ INST(Ld3r_v , SimdLdNStN , (0b0000110101000000111000, 0b0000000000000000000000, 3, 1) , kRWI_LDn , F(Consecutive) , 5 ), // #542
+ INST(Ld4_v , SimdLdNStN , (0b0000110101100000001000, 0b0000110001000000000000, 4, 0) , kRWI_LDn , F(Consecutive) , 6 ), // #543
+ INST(Ld4r_v , SimdLdNStN , (0b0000110101100000111000, 0b0000000000000000000000, 4, 1) , kRWI_LDn , F(Consecutive) , 7 ), // #544
+ INST(Ldnp_v , SimdLdpStp , (0b0010110001, 0b0000000000) , kRWI_WW , 0 , 0 ), // #545
+ INST(Ldp_v , SimdLdpStp , (0b0010110101, 0b0010110011) , kRWI_WW , 0 , 1 ), // #546
+ INST(Ldr_v , SimdLdSt , (0b0011110101, 0b00111100010, 0b00111100011, 0b00011100, Inst::kIdLdur_v) , kRWI_W , 0 , 0 ), // #547
+ INST(Ldur_v , SimdLdurStur , (0b0011110001000000000000) , kRWI_W , 0 , 0 ), // #548
+ INST(Mla_v , ISimdVVVe , (0b0000111000100000100101, kVO_V_BHS, 0b0010111100000000000000, kVO_V_HS) , kRWI_X , F(VH0_15) , 0 ), // #549
+ INST(Mls_v , ISimdVVVe , (0b0010111000100000100101, kVO_V_BHS, 0b0010111100000000010000, kVO_V_HS) , kRWI_X , F(VH0_15) , 1 ), // #550
+ INST(Mov_v , SimdMov , (_) , kRWI_W , 0 , 0 ), // #551
+ INST(Movi_v , SimdMoviMvni , (0b0000111100000000000001, 0) , kRWI_W , 0 , 0 ), // #552
+ INST(Mul_v , ISimdVVVe , (0b0000111000100000100111, kVO_V_BHS, 0b0000111100000000100000, kVO_V_HS) , kRWI_W , F(VH0_15) , 2 ), // #553
+ INST(Mvn_v , ISimdVV , (0b0010111000100000010110, kVO_V_B) , kRWI_W , 0 , 4 ), // #554
+ INST(Mvni_v , SimdMoviMvni , (0b0000111100000000000001, 1) , kRWI_W , 0 , 1 ), // #555
+ INST(Neg_v , ISimdVV , (0b0010111000100000101110, kVO_V_Any) , kRWI_W , 0 , 5 ), // #556
+ INST(Not_v , ISimdVV , (0b0010111000100000010110, kVO_V_B) , kRWI_W , 0 , 6 ), // #557
+ INST(Orn_v , ISimdVVV , (0b0000111011100000000111, kVO_V_B) , kRWI_W , 0 , 9 ), // #558
+ INST(Orr_v , SimdBicOrr , (0b0000111010100000000111, 0b0000111100000000000001) , kRWI_W , 0 , 1 ), // #559
+ INST(Pmul_v , ISimdVVV , (0b0010111000100000100111, kVO_V_B) , kRWI_W , 0 , 10 ), // #560
+ INST(Pmull_v , ISimdVVV , (0b0000111000100000111000, kVO_V_B8D1) , kRWI_W , F(Long) , 11 ), // #561
+ INST(Pmull2_v , ISimdVVV , (0b0100111000100000111000, kVO_V_B16D2) , kRWI_W , F(Long) , 12 ), // #562
+ INST(Raddhn_v , ISimdVVV , (0b0010111000100000010000, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 13 ), // #563
+ INST(Raddhn2_v , ISimdVVV , (0b0110111000100000010000, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 14 ), // #564
+ INST(Rax1_v , ISimdVVV , (0b1100111001100000100011, kVO_V_D2) , kRWI_W , 0 , 15 ), // #565
+ INST(Rbit_v , ISimdVV , (0b0010111001100000010110, kVO_V_B) , kRWI_W , 0 , 7 ), // #566
+ INST(Rev16_v , ISimdVV , (0b0000111000100000000110, kVO_V_B) , kRWI_W , 0 , 8 ), // #567
+ INST(Rev32_v , ISimdVV , (0b0010111000100000000010, kVO_V_BH) , kRWI_W , 0 , 9 ), // #568
+ INST(Rev64_v , ISimdVV , (0b0000111000100000000010, kVO_V_BHS) , kRWI_W , 0 , 10 ), // #569
+ INST(Rshrn_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000100011, 1, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 0 ), // #570
+ INST(Rshrn2_v , SimdShift , (0b0000000000000000000000, 0b0100111100000000100011, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 1 ), // #571
+ INST(Rsubhn_v , ISimdVVV , (0b0010111000100000011000, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 16 ), // #572
+ INST(Rsubhn2_v , ISimdVVV , (0b0110111000100000011000, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 17 ), // #573
+ INST(Saba_v , ISimdVVV , (0b0000111000100000011111, kVO_V_BHS) , kRWI_X , 0 , 18 ), // #574
+ INST(Sabal_v , ISimdVVV , (0b0000111000100000010100, kVO_V_B8H4S2) , kRWI_X , F(Long) , 19 ), // #575
+ INST(Sabal2_v , ISimdVVV , (0b0100111000100000010100, kVO_V_B16H8S4) , kRWI_X , F(Long) , 20 ), // #576
+ INST(Sabd_v , ISimdVVV , (0b0000111000100000011101, kVO_V_BHS) , kRWI_W , 0 , 21 ), // #577
+ INST(Sabdl_v , ISimdVVV , (0b0000111000100000011100, kVO_V_B8H4S2) , kRWI_W , F(Long) , 22 ), // #578
+ INST(Sabdl2_v , ISimdVVV , (0b0100111000100000011100, kVO_V_B16H8S4) , kRWI_W , F(Long) , 23 ), // #579
+ INST(Sadalp_v , ISimdVV , (0b0000111000100000011010, kVO_V_BHS) , kRWI_X , F(Long) | F(Pair) , 11 ), // #580
+ INST(Saddl_v , ISimdVVV , (0b0000111000100000000000, kVO_V_B8H4S2) , kRWI_W , F(Long) , 24 ), // #581
+ INST(Saddl2_v , ISimdVVV , (0b0100111000100000000000, kVO_V_B16H8S4) , kRWI_W , F(Long) , 25 ), // #582
+ INST(Saddlp_v , ISimdVV , (0b0000111000100000001010, kVO_V_BHS) , kRWI_W , F(Long) | F(Pair) , 12 ), // #583
+ INST(Saddlv_v , ISimdSV , (0b0000111000110000001110, kVO_V_BH_4S) , kRWI_W , F(Long) , 1 ), // #584
+ INST(Saddw_v , ISimdWWV , (0b0000111000100000000100, kVO_V_B8H4S2) , kRWI_W , 0 , 0 ), // #585
+ INST(Saddw2_v , ISimdWWV , (0b0000111000100000000100, kVO_V_B16H8S4) , kRWI_W , 0 , 1 ), // #586
+ INST(Scvtf_v , SimdFcvtSV , (0b0000111000100001110110, 0b0000111100000000111001, 0b0001111000100010000000, 0) , kRWI_W , 0 , 10 ), // #587
+ INST(Sdot_v , SimdDot , (0b0000111010000000100101, 0b0000111110000000111000, kET_S, kET_B, kET_4B) , kRWI_X , 0 , 1 ), // #588
+ INST(Sha1c_v , ISimdVVVx , (0b0101111000000000000000, kOp_Q, kOp_S, kOp_V4S) , kRWI_X , 0 , 1 ), // #589
+ INST(Sha1h_v , ISimdVVx , (0b0101111000101000000010, kOp_S, kOp_S) , kRWI_W , 0 , 8 ), // #590
+ INST(Sha1m_v , ISimdVVVx , (0b0101111000000000001000, kOp_Q, kOp_S, kOp_V4S) , kRWI_X , 0 , 2 ), // #591
+ INST(Sha1p_v , ISimdVVVx , (0b0101111000000000000100, kOp_Q, kOp_S, kOp_V4S) , kRWI_X , 0 , 3 ), // #592
+ INST(Sha1su0_v , ISimdVVVx , (0b0101111000000000001100, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 4 ), // #593
+ INST(Sha1su1_v , ISimdVVx , (0b0101111000101000000110, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 9 ), // #594
+ INST(Sha256h_v , ISimdVVVx , (0b0101111000000000010000, kOp_Q, kOp_Q, kOp_V4S) , kRWI_X , 0 , 5 ), // #595
+ INST(Sha256h2_v , ISimdVVVx , (0b0101111000000000010100, kOp_Q, kOp_Q, kOp_V4S) , kRWI_X , 0 , 6 ), // #596
+ INST(Sha256su0_v , ISimdVVx , (0b0101111000101000001010, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 10 ), // #597
+ INST(Sha256su1_v , ISimdVVVx , (0b0101111000000000011000, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 7 ), // #598
+ INST(Sha512h_v , ISimdVVVx , (0b1100111001100000100000, kOp_Q, kOp_Q, kOp_V2D) , kRWI_X , 0 , 8 ), // #599
+ INST(Sha512h2_v , ISimdVVVx , (0b1100111001100000100001, kOp_Q, kOp_Q, kOp_V2D) , kRWI_X , 0 , 9 ), // #600
+ INST(Sha512su0_v , ISimdVVx , (0b1100111011000000100000, kOp_V2D, kOp_V2D) , kRWI_X , 0 , 11 ), // #601
+ INST(Sha512su1_v , ISimdVVVx , (0b1100111001100000100010, kOp_V2D, kOp_V2D, kOp_V2D) , kRWI_X , 0 , 10 ), // #602
+ INST(Shadd_v , ISimdVVV , (0b0000111000100000000001, kVO_V_BHS) , kRWI_W , 0 , 26 ), // #603
+ INST(Shl_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000010101, 0, kVO_V_Any) , kRWI_W , 0 , 2 ), // #604
+ INST(Shll_v , SimdShiftES , (0b0010111000100001001110, kVO_V_B8H4S2) , kRWI_W , F(Long) , 0 ), // #605
+ INST(Shll2_v , SimdShiftES , (0b0110111000100001001110, kVO_V_B16H8S4) , kRWI_W , F(Long) , 1 ), // #606
+ INST(Shrn_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000100001, 1, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 3 ), // #607
+ INST(Shrn2_v , SimdShift , (0b0000000000000000000000, 0b0100111100000000100001, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 4 ), // #608
+ INST(Shsub_v , ISimdVVV , (0b0000111000100000001001, kVO_V_BHS) , kRWI_W , 0 , 27 ), // #609
+ INST(Sli_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000010101, 0, kVO_V_Any) , kRWI_X , 0 , 5 ), // #610
+ INST(Sm3partw1_v , ISimdVVVx , (0b1100111001100000110000, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 11 ), // #611
+ INST(Sm3partw2_v , ISimdVVVx , (0b1100111001100000110001, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 12 ), // #612
+ INST(Sm3ss1_v , ISimdVVVVx , (0b1100111001000000000000, kOp_V4S, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_W , 0 , 0 ), // #613
+ INST(Sm3tt1a_v , SimdSm3tt , (0b1100111001000000100000) , kRWI_X , 0 , 0 ), // #614
+ INST(Sm3tt1b_v , SimdSm3tt , (0b1100111001000000100001) , kRWI_X , 0 , 1 ), // #615
+ INST(Sm3tt2a_v , SimdSm3tt , (0b1100111001000000100010) , kRWI_X , 0 , 2 ), // #616
+ INST(Sm3tt2b_v , SimdSm3tt , (0b1100111001000000100011) , kRWI_X , 0 , 3 ), // #617
+ INST(Sm4e_v , ISimdVVx , (0b1100111011000000100001, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 12 ), // #618
+ INST(Sm4ekey_v , ISimdVVVx , (0b1100111001100000110010, kOp_V4S, kOp_V4S, kOp_V4S) , kRWI_X , 0 , 13 ), // #619
+ INST(Smax_v , ISimdVVV , (0b0000111000100000011001, kVO_V_BHS) , kRWI_W , 0 , 28 ), // #620
+ INST(Smaxp_v , ISimdVVV , (0b0000111000100000101001, kVO_V_BHS) , kRWI_W , 0 , 29 ), // #621
+ INST(Smaxv_v , ISimdSV , (0b0000111000110000101010, kVO_V_BH_4S) , kRWI_W , 0 , 2 ), // #622
+ INST(Smin_v , ISimdVVV , (0b0000111000100000011011, kVO_V_BHS) , kRWI_W , 0 , 30 ), // #623
+ INST(Sminp_v , ISimdVVV , (0b0000111000100000101011, kVO_V_BHS) , kRWI_W , 0 , 31 ), // #624
+ INST(Sminv_v , ISimdSV , (0b0000111000110001101010, kVO_V_BH_4S) , kRWI_W , 0 , 3 ), // #625
+ INST(Smlal_v , ISimdVVVe , (0b0000111000100000100000, kVO_V_B8H4S2, 0b0000111100000000001000, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 3 ), // #626
+ INST(Smlal2_v , ISimdVVVe , (0b0100111000100000100000, kVO_V_B16H8S4, 0b0100111100000000001000, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 4 ), // #627
+ INST(Smlsl_v , ISimdVVVe , (0b0000111000100000101000, kVO_V_B8H4S2, 0b0000111100000000011000, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 5 ), // #628
+ INST(Smlsl2_v , ISimdVVVe , (0b0100111000100000101000, kVO_V_B16H8S4, 0b0100111100000000011000, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 6 ), // #629
+ INST(Smmla_v , ISimdVVVx , (0b0100111010000000101001, kOp_V4S, kOp_V16B, kOp_V16B) , kRWI_X , 0 , 14 ), // #630
+ INST(Smov_v , SimdSmovUmov , (0b0000111000000000001011, kVO_V_BHS, 1) , kRWI_W , 0 , 0 ), // #631
+ INST(Smull_v , ISimdVVVe , (0b0000111000100000110000, kVO_V_B8H4S2, 0b0000111100000000101000, kVO_V_H4S2) , kRWI_W , F(Long) | F(VH0_15) , 7 ), // #632
+ INST(Smull2_v , ISimdVVVe , (0b0100111000100000110000, kVO_V_B16H8S4, 0b0100111100000000101000, kVO_V_H8S4) , kRWI_W , F(Long) | F(VH0_15) , 8 ), // #633
+ INST(Sqabs_v , ISimdVV , (0b0000111000100000011110, kVO_SV_Any) , kRWI_W , 0 , 13 ), // #634
+ INST(Sqadd_v , ISimdVVV , (0b0000111000100000000011, kVO_SV_Any) , kRWI_W , 0 , 32 ), // #635
+ INST(Sqdmlal_v , ISimdVVVe , (0b0000111000100000100100, kVO_SV_BHS, 0b0000111100000000001100, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 9 ), // #636
+ INST(Sqdmlal2_v , ISimdVVVe , (0b0100111000100000100100, kVO_V_B16H8S4, 0b0100111100000000001100, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 10 ), // #637
+ INST(Sqdmlsl_v , ISimdVVVe , (0b0000111000100000101100, kVO_SV_BHS, 0b0000111100000000011100, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 11 ), // #638
+ INST(Sqdmlsl2_v , ISimdVVVe , (0b0100111000100000101100, kVO_V_B16H8S4, 0b0100111100000000011100, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 12 ), // #639
+ INST(Sqdmulh_v , ISimdVVVe , (0b0000111000100000101101, kVO_SV_HS, 0b0000111100000000110000, kVO_SV_HS) , kRWI_W , F(VH0_15) , 13 ), // #640
+ INST(Sqdmull_v , ISimdVVVe , (0b0000111000100000110100, kVO_SV_BHS, 0b0000111100000000101100, kVO_V_H4S2) , kRWI_W , F(Long) | F(VH0_15) , 14 ), // #641
+ INST(Sqdmull2_v , ISimdVVVe , (0b0100111000100000110100, kVO_V_B16H8S4, 0b0100111100000000101100, kVO_V_H8S4) , kRWI_W , F(Long) | F(VH0_15) , 15 ), // #642
+ INST(Sqneg_v , ISimdVV , (0b0010111000100000011110, kVO_SV_Any) , kRWI_W , 0 , 14 ), // #643
+ INST(Sqrdmlah_v , ISimdVVVe , (0b0010111000000000100001, kVO_SV_HS, 0b0010111100000000110100, kVO_SV_HS) , kRWI_X , F(VH0_15) , 16 ), // #644
+ INST(Sqrdmlsh_v , ISimdVVVe , (0b0010111000000000100011, kVO_SV_HS, 0b0010111100000000111100, kVO_SV_HS) , kRWI_X , F(VH0_15) , 17 ), // #645
+ INST(Sqrdmulh_v , ISimdVVVe , (0b0010111000100000101101, kVO_SV_HS, 0b0000111100000000110100, kVO_SV_HS) , kRWI_W , F(VH0_15) , 18 ), // #646
+ INST(Sqrshl_v , SimdShift , (0b0000111000100000010111, 0b0000000000000000000000, 1, kVO_SV_Any) , kRWI_W , 0 , 6 ), // #647
+ INST(Sqrshrn_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000100111, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 7 ), // #648
+ INST(Sqrshrn2_v , SimdShift , (0b0000000000000000000000, 0b0100111100000000100111, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 8 ), // #649
+ INST(Sqrshrun_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000100011, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 9 ), // #650
+ INST(Sqrshrun2_v , SimdShift , (0b0000000000000000000000, 0b0110111100000000100011, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 10 ), // #651
+ INST(Sqshl_v , SimdShift , (0b0000111000100000010011, 0b0000111100000000011101, 0, kVO_SV_Any) , kRWI_W , 0 , 11 ), // #652
+ INST(Sqshlu_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000011001, 0, kVO_SV_Any) , kRWI_W , 0 , 12 ), // #653
+ INST(Sqshrn_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000100101, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 13 ), // #654
+ INST(Sqshrn2_v , SimdShift , (0b0000000000000000000000, 0b0100111100000000100101, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 14 ), // #655
+ INST(Sqshrun_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000100001, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 15 ), // #656
+ INST(Sqshrun2_v , SimdShift , (0b0000000000000000000000, 0b0110111100000000100001, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 16 ), // #657
+ INST(Sqsub_v , ISimdVVV , (0b0000111000100000001011, kVO_SV_Any) , kRWI_W , 0 , 33 ), // #658
+ INST(Sqxtn_v , ISimdVV , (0b0000111000100001010010, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 15 ), // #659
+ INST(Sqxtn2_v , ISimdVV , (0b0100111000100001010010, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 16 ), // #660
+ INST(Sqxtun_v , ISimdVV , (0b0010111000100001001010, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 17 ), // #661
+ INST(Sqxtun2_v , ISimdVV , (0b0110111000100001001010, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 18 ), // #662
+ INST(Srhadd_v , ISimdVVV , (0b0000111000100000000101, kVO_V_BHS) , kRWI_W , 0 , 34 ), // #663
+ INST(Sri_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000010001, 1, kVO_V_Any) , kRWI_W , 0 , 17 ), // #664
+ INST(Srshl_v , SimdShift , (0b0000111000100000010101, 0b0000000000000000000000, 0, kVO_V_Any) , kRWI_W , 0 , 18 ), // #665
+ INST(Srshr_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000001001, 1, kVO_V_Any) , kRWI_W , 0 , 19 ), // #666
+ INST(Srsra_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000001101, 1, kVO_V_Any) , kRWI_X , 0 , 20 ), // #667
+ INST(Sshl_v , SimdShift , (0b0000111000100000010001, 0b0000000000000000000000, 0, kVO_V_Any) , kRWI_W , 0 , 21 ), // #668
+ INST(Sshll_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000101001, 0, kVO_V_B8H4S2) , kRWI_W , F(Long) , 22 ), // #669
+ INST(Sshll2_v , SimdShift , (0b0000000000000000000000, 0b0100111100000000101001, 0, kVO_V_B16H8S4) , kRWI_W , F(Long) , 23 ), // #670
+ INST(Sshr_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000000001, 1, kVO_V_Any) , kRWI_W , 0 , 24 ), // #671
+ INST(Ssra_v , SimdShift , (0b0000000000000000000000, 0b0000111100000000000101, 1, kVO_V_Any) , kRWI_X , 0 , 25 ), // #672
+ INST(Ssubl_v , ISimdVVV , (0b0000111000100000001000, kVO_V_B8H4S2) , kRWI_W , F(Long) , 35 ), // #673
+ INST(Ssubl2_v , ISimdVVV , (0b0100111000100000001000, kVO_V_B16H8S4) , kRWI_W , F(Long) , 36 ), // #674
+ INST(Ssubw_v , ISimdWWV , (0b0000111000100000001100, kVO_V_B8H4S2) , kRWI_W , 0 , 2 ), // #675
+ INST(Ssubw2_v , ISimdWWV , (0b0000111000100000001100, kVO_V_B16H8S4) , kRWI_X , 0 , 3 ), // #676
+ INST(St1_v , SimdLdNStN , (0b0000110100000000000000, 0b0000110000000000001000, 1, 0) , kRWI_STn , F(Consecutive) , 8 ), // #677
+ INST(St2_v , SimdLdNStN , (0b0000110100100000000000, 0b0000110000000000100000, 2, 0) , kRWI_STn , F(Consecutive) , 9 ), // #678
+ INST(St3_v , SimdLdNStN , (0b0000110100000000001000, 0b0000110000000000010000, 3, 0) , kRWI_STn , F(Consecutive) , 10 ), // #679
+ INST(St4_v , SimdLdNStN , (0b0000110100100000001000, 0b0000110000000000000000, 4, 0) , kRWI_STn , F(Consecutive) , 11 ), // #680
+ INST(Stnp_v , SimdLdpStp , (0b0010110000, 0b0000000000) , kRWI_RRW , 0 , 2 ), // #681
+ INST(Stp_v , SimdLdpStp , (0b0010110100, 0b0010110010) , kRWI_RRW , 0 , 3 ), // #682
+ INST(Str_v , SimdLdSt , (0b0011110100, 0b00111100000, 0b00111100001, 0b00000000, Inst::kIdStur_v) , kRWI_RW , 0 , 1 ), // #683
+ INST(Stur_v , SimdLdurStur , (0b0011110000000000000000) , kRWI_RW , 0 , 1 ), // #684
+ INST(Sub_v , ISimdVVV , (0b0010111000100000100001, kVO_V_Any) , kRWI_W , 0 , 37 ), // #685
+ INST(Subhn_v , ISimdVVV , (0b0000111000100000011000, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 38 ), // #686
+ INST(Subhn2_v , ISimdVVV , (0b0000111000100000011000, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 39 ), // #687
+ INST(Sudot_v , SimdDot , (0b0000000000000000000000, 0b0000111100000000111100, kET_S, kET_B, kET_4B) , kRWI_X , 0 , 2 ), // #688
+ INST(Suqadd_v , ISimdVV , (0b0000111000100000001110, kVO_SV_Any) , kRWI_X , 0 , 19 ), // #689
+ INST(Sxtl_v , SimdSxtlUxtl , (0b0000111100000000101001, kVO_V_B8H4S2) , kRWI_W , F(Long) , 0 ), // #690
+ INST(Sxtl2_v , SimdSxtlUxtl , (0b0100111100000000101001, kVO_V_B16H8S4) , kRWI_W , F(Long) , 1 ), // #691
+ INST(Tbl_v , SimdTblTbx , (0b0000111000000000000000) , kRWI_W , 0 , 0 ), // #692
+ INST(Tbx_v , SimdTblTbx , (0b0000111000000000000100) , kRWI_W , 0 , 1 ), // #693
+ INST(Trn1_v , ISimdVVV , (0b0000111000000000001010, kVO_V_BHS_D2) , kRWI_W , 0 , 40 ), // #694
+ INST(Trn2_v , ISimdVVV , (0b0000111000000000011010, kVO_V_BHS_D2) , kRWI_W , 0 , 41 ), // #695
+ INST(Uaba_v , ISimdVVV , (0b0010111000100000011111, kVO_V_BHS) , kRWI_X , 0 , 42 ), // #696
+ INST(Uabal_v , ISimdVVV , (0b0010111000100000010100, kVO_V_B8H4S2) , kRWI_X , F(Long) , 43 ), // #697
+ INST(Uabal2_v , ISimdVVV , (0b0110111000100000010100, kVO_V_B16H8S4) , kRWI_X , F(Long) , 44 ), // #698
+ INST(Uabd_v , ISimdVVV , (0b0010111000100000011101, kVO_V_BHS) , kRWI_W , 0 , 45 ), // #699
+ INST(Uabdl_v , ISimdVVV , (0b0010111000100000011100, kVO_V_B8H4S2) , kRWI_W , F(Long) , 46 ), // #700
+ INST(Uabdl2_v , ISimdVVV , (0b0110111000100000011100, kVO_V_B16H8S4) , kRWI_W , F(Long) , 47 ), // #701
+ INST(Uadalp_v , ISimdVV , (0b0010111000100000011010, kVO_V_BHS) , kRWI_X , F(Long) | F(Pair) , 20 ), // #702
+ INST(Uaddl_v , ISimdVVV , (0b0010111000100000000000, kVO_V_B8H4S2) , kRWI_W , F(Long) , 48 ), // #703
+ INST(Uaddl2_v , ISimdVVV , (0b0110111000100000000000, kVO_V_B16H8S4) , kRWI_W , F(Long) , 49 ), // #704
+ INST(Uaddlp_v , ISimdVV , (0b0010111000100000001010, kVO_V_BHS) , kRWI_W , F(Long) | F(Pair) , 21 ), // #705
+ INST(Uaddlv_v , ISimdSV , (0b0010111000110000001110, kVO_V_BH_4S) , kRWI_W , F(Long) , 4 ), // #706
+ INST(Uaddw_v , ISimdWWV , (0b0010111000100000000100, kVO_V_B8H4S2) , kRWI_W , 0 , 4 ), // #707
+ INST(Uaddw2_v , ISimdWWV , (0b0010111000100000000100, kVO_V_B16H8S4) , kRWI_W , 0 , 5 ), // #708
+ INST(Ucvtf_v , SimdFcvtSV , (0b0010111000100001110110, 0b0010111100000000111001, 0b0001111000100011000000, 0) , kRWI_W , 0 , 11 ), // #709
+ INST(Udot_v , SimdDot , (0b0010111010000000100101, 0b0010111110000000111000, kET_S, kET_B, kET_4B) , kRWI_X , 0 , 3 ), // #710
+ INST(Uhadd_v , ISimdVVV , (0b0010111000100000000001, kVO_V_BHS) , kRWI_W , 0 , 50 ), // #711
+ INST(Uhsub_v , ISimdVVV , (0b0010111000100000001001, kVO_V_BHS) , kRWI_W , 0 , 51 ), // #712
+ INST(Umax_v , ISimdVVV , (0b0010111000100000011001, kVO_V_BHS) , kRWI_W , 0 , 52 ), // #713
+ INST(Umaxp_v , ISimdVVV , (0b0010111000100000101001, kVO_V_BHS) , kRWI_W , 0 , 53 ), // #714
+ INST(Umaxv_v , ISimdSV , (0b0010111000110000101010, kVO_V_BH_4S) , kRWI_W , 0 , 5 ), // #715
+ INST(Umin_v , ISimdVVV , (0b0010111000100000011011, kVO_V_BHS) , kRWI_W , 0 , 54 ), // #716
+ INST(Uminp_v , ISimdVVV , (0b0010111000100000101011, kVO_V_BHS) , kRWI_W , 0 , 55 ), // #717
+ INST(Uminv_v , ISimdSV , (0b0010111000110001101010, kVO_V_BH_4S) , kRWI_W , 0 , 6 ), // #718
+ INST(Umlal_v , ISimdVVVe , (0b0010111000100000100000, kVO_V_B8H4S2, 0b0010111100000000001000, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 19 ), // #719
+ INST(Umlal2_v , ISimdVVVe , (0b0110111000100000100000, kVO_V_B16H8S4, 0b0010111100000000001000, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 20 ), // #720
+ INST(Umlsl_v , ISimdVVVe , (0b0010111000100000101000, kVO_V_B8H4S2, 0b0010111100000000011000, kVO_V_H4S2) , kRWI_X , F(Long) | F(VH0_15) , 21 ), // #721
+ INST(Umlsl2_v , ISimdVVVe , (0b0110111000100000101000, kVO_V_B16H8S4, 0b0110111100000000011000, kVO_V_H8S4) , kRWI_X , F(Long) | F(VH0_15) , 22 ), // #722
+ INST(Ummla_v , ISimdVVVx , (0b0110111010000000101001, kOp_V4S, kOp_V16B, kOp_V16B) , kRWI_X , 0 , 15 ), // #723
+ INST(Umov_v , SimdSmovUmov , (0b0000111000000000001111, kVO_V_Any, 0) , kRWI_W , 0 , 1 ), // #724
+ INST(Umull_v , ISimdVVVe , (0b0010111000100000110000, kVO_V_B8H4S2, 0b0010111100000000101000, kVO_V_H4S2) , kRWI_W , F(Long) | F(VH0_15) , 23 ), // #725
+ INST(Umull2_v , ISimdVVVe , (0b0110111000100000110000, kVO_V_B16H8S4, 0b0110111100000000101000, kVO_V_H8S4) , kRWI_W , F(Long) | F(VH0_15) , 24 ), // #726
+ INST(Uqadd_v , ISimdVVV , (0b0010111000100000000011, kVO_SV_Any) , kRWI_W , 0 , 56 ), // #727
+ INST(Uqrshl_v , SimdShift , (0b0010111000100000010111, 0b0000000000000000000000, 0, kVO_SV_Any) , kRWI_W , 0 , 26 ), // #728
+ INST(Uqrshrn_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000100111, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 27 ), // #729
+ INST(Uqrshrn2_v , SimdShift , (0b0000000000000000000000, 0b0110111100000000100111, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 28 ), // #730
+ INST(Uqshl_v , SimdShift , (0b0010111000100000010011, 0b0010111100000000011101, 0, kVO_SV_Any) , kRWI_W , 0 , 29 ), // #731
+ INST(Uqshrn_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000100101, 1, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 30 ), // #732
+ INST(Uqshrn2_v , SimdShift , (0b0000000000000000000000, 0b0110111100000000100101, 1, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 31 ), // #733
+ INST(Uqsub_v , ISimdVVV , (0b0010111000100000001011, kVO_SV_Any) , kRWI_W , 0 , 57 ), // #734
+ INST(Uqxtn_v , ISimdVV , (0b0010111000100001010010, kVO_SV_B8H4S2) , kRWI_W , F(Narrow) , 22 ), // #735
+ INST(Uqxtn2_v , ISimdVV , (0b0110111000100001010010, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 23 ), // #736
+ INST(Urecpe_v , ISimdVV , (0b0000111010100001110010, kVO_V_S) , kRWI_W , 0 , 24 ), // #737
+ INST(Urhadd_v , ISimdVVV , (0b0010111000100000000101, kVO_V_BHS) , kRWI_W , 0 , 58 ), // #738
+ INST(Urshl_v , SimdShift , (0b0010111000100000010101, 0b0000000000000000000000, 0, kVO_V_Any) , kRWI_W , 0 , 32 ), // #739
+ INST(Urshr_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000001001, 1, kVO_V_Any) , kRWI_W , 0 , 33 ), // #740
+ INST(Ursqrte_v , ISimdVV , (0b0010111010100001110010, kVO_V_S) , kRWI_W , 0 , 25 ), // #741
+ INST(Ursra_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000001101, 1, kVO_V_Any) , kRWI_X , 0 , 34 ), // #742
+ INST(Usdot_v , SimdDot , (0b0000111010000000100111, 0b0000111110000000111100, kET_S, kET_B, kET_4B) , kRWI_X , 0 , 4 ), // #743
+ INST(Ushl_v , SimdShift , (0b0010111000100000010001, 0b0000000000000000000000, 0, kVO_V_Any) , kRWI_W , 0 , 35 ), // #744
+ INST(Ushll_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000101001, 0, kVO_V_B8H4S2) , kRWI_W , F(Long) , 36 ), // #745
+ INST(Ushll2_v , SimdShift , (0b0000000000000000000000, 0b0110111100000000101001, 0, kVO_V_B16H8S4) , kRWI_W , F(Long) , 37 ), // #746
+ INST(Ushr_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000000001, 1, kVO_V_Any) , kRWI_W , 0 , 38 ), // #747
+ INST(Usmmla_v , ISimdVVVx , (0b0100111010000000101011, kOp_V4S, kOp_V16B, kOp_V16B) , kRWI_X , 0 , 16 ), // #748
+ INST(Usqadd_v , ISimdVV , (0b0010111000100000001110, kVO_SV_Any) , kRWI_X , 0 , 26 ), // #749
+ INST(Usra_v , SimdShift , (0b0000000000000000000000, 0b0010111100000000000101, 1, kVO_V_Any) , kRWI_X , 0 , 39 ), // #750
+ INST(Usubl_v , ISimdVVV , (0b0010111000100000001000, kVO_V_B8H4S2) , kRWI_W , F(Long) , 59 ), // #751
+ INST(Usubl2_v , ISimdVVV , (0b0110111000100000001000, kVO_V_B16H8S4) , kRWI_W , F(Long) , 60 ), // #752
+ INST(Usubw_v , ISimdWWV , (0b0010111000100000001100, kVO_V_B8H4S2) , kRWI_W , 0 , 6 ), // #753
+ INST(Usubw2_v , ISimdWWV , (0b0010111000100000001100, kVO_V_B16H8S4) , kRWI_W , 0 , 7 ), // #754
+ INST(Uxtl_v , SimdSxtlUxtl , (0b0010111100000000101001, kVO_V_B8H4S2) , kRWI_W , F(Long) , 2 ), // #755
+ INST(Uxtl2_v , SimdSxtlUxtl , (0b0110111100000000101001, kVO_V_B16H8S4) , kRWI_W , F(Long) , 3 ), // #756
+ INST(Uzp1_v , ISimdVVV , (0b0000111000000000000110, kVO_V_BHS_D2) , kRWI_W , 0 , 61 ), // #757
+ INST(Uzp2_v , ISimdVVV , (0b0000111000000000010110, kVO_V_BHS_D2) , kRWI_W , 0 , 62 ), // #758
+ INST(Xar_v , ISimdVVVI , (0b1100111001100000100011, kVO_V_D2, 6, 10, 0) , kRWI_W , 0 , 1 ), // #759
+ INST(Xtn_v , ISimdVV , (0b0000111000100001001010, kVO_V_B8H4S2) , kRWI_W , F(Narrow) , 27 ), // #760
+ INST(Xtn2_v , ISimdVV , (0b0100111000100001001010, kVO_V_B16H8S4) , kRWI_X , F(Narrow) , 28 ), // #761
+ INST(Zip1_v , ISimdVVV , (0b0000111000000000001110, kVO_V_BHS_D2) , kRWI_W , 0 , 63 ), // #762
+ INST(Zip2_v , ISimdVVV , (0b0000111000000000011110, kVO_V_BHS_D2) , kRWI_W , 0 , 64 ) // #763
// ${InstInfo:End}
};
@@ -1132,8 +1123,8 @@ const BaseLdSt baseLdSt[9] = {
{ 0b1011100101, 0b10111000010, 0b10111000011, 0b00011000, kWX, 30, 2, Inst::kIdLdur }, // ldr
{ 0b0011100101, 0b00111000010, 0b00111000011, 0 , kW , 0 , 0, Inst::kIdLdurb }, // ldrb
{ 0b0111100101, 0b01111000010, 0b01111000011, 0 , kW , 0 , 1, Inst::kIdLdurh }, // ldrh
- { 0b0011100111, 0b00111000100, 0b00111000101, 0 , kWX, 22, 0, Inst::kIdLdursb }, // ldrsb
- { 0b0111100110, 0b01111000100, 0b01111000101, 0 , kWX, 22, 1, Inst::kIdLdursh }, // ldrsh
+ { 0b0011100111, 0b00111000100, 0b00111000111, 0 , kWX, 22, 0, Inst::kIdLdursb }, // ldrsb
+ { 0b0111100111, 0b01111000100, 0b01111000111, 0 , kWX, 22, 1, Inst::kIdLdursh }, // ldrsh
{ 0b1011100110, 0b10111000100, 0b10111000101, 0b10011000, kX , 0 , 2, Inst::kIdLdursw }, // ldrsw
{ 0b1011100100, 0b10111000000, 0b10111000001, 0 , kWX, 30, 2, Inst::kIdStur }, // str
{ 0b0011100100, 0b00111000000, 0b00111000001, 0 , kW , 30, 0, Inst::kIdSturb }, // strb
@@ -1275,7 +1266,7 @@ const BaseRM_SImm9 baseRM_SImm9[23] = {
{ 0b0111100001000000000000, 0b0000000000000000000000, kW , kZR, 0 , 0 }, // ldurh
{ 0b0011100011000000000000, 0b0000000000000000000000, kWX, kZR, 22, 0 }, // ldursb
{ 0b0111100011000000000000, 0b0000000000000000000000, kWX, kZR, 22, 0 }, // ldursh
- { 0b1011100010000000000000, 0b0000000000000000000000, kWX, kZR, 0 , 0 }, // ldursw
+ { 0b1011100010000000000000, 0b0000000000000000000000, kX, kZR, 0 , 0 }, // ldursw
{ 0b1101100110100000000010, 0b1101100110100000000001, kX, kSP, 0, 4 }, // st2g
{ 0b1101100100100000000010, 0b1101100100100000000001, kX, kSP, 0, 4 }, // stg
{ 0b1011100000000000000010, 0b0000000000000000000000, kWX, kZR, 30, 0 }, // sttr
@@ -1855,70 +1846,779 @@ const InstDB::CommonInfo InstDB::commonData[] = {
#ifndef ASMJIT_DISABLE_TEXT
// ${NameData:Begin}
// ------------------- Automatically generated, do not edit -------------------
-const char InstDB::_nameData[] =
- "\0" "adc\0" "adcs\0" "addg\0" "adds\0" "addv\0" "adr\0" "adrp\0" "aesd\0" "aese\0" "aesimc\0" "aesmc\0" "and\0"
- "ands\0" "asr\0" "asrv\0" "at\0" "autda\0" "autdb\0" "autdza\0" "autdzb\0" "autia\0" "autia1716\0" "autiasp\0"
- "autiaz\0" "autib\0" "autib1716\0" "autibsp\0" "autibz\0" "autiza\0" "autizb\0" "axflag\0" "bcax\0" "bfc\0" "bfcvt\0"
- "bfcvtn\0" "bfcvtn2\0" "bfdot\0" "bfi\0" "bfmlalb\0" "bfmlalt\0" "bfmmla\0" "bfxil\0" "bic\0" "bics\0" "bif\0"
- "blr\0" "br\0" "brk\0" "bsl\0" "cas\0" "casa\0" "casab\0" "casah\0" "casal\0" "casalb\0" "casalh\0" "casb\0" "cash\0"
- "casl\0" "caslb\0" "caslh\0" "casp\0" "caspa\0" "caspal\0" "caspl\0" "cbnz\0" "cbz\0" "ccmn\0" "cfinv\0" "cinc\0"
- "cinv\0" "clrex\0" "cls\0" "clz\0" "cmhi\0" "cmhs\0" "cmpp\0" "cmtst\0" "cneg\0" "cnt\0" "crc32b\0" "crc32cb\0"
- "crc32ch\0" "crc32cw\0" "crc32cx\0" "crc32h\0" "crc32w\0" "crc32x\0" "csdb\0" "cset\0" "csetm\0" "csinc\0" "csinv\0"
- "csneg\0" "dcps1\0" "dcps2\0" "dcps3\0" "dgh\0" "dmb\0" "drps\0" "dsb\0" "dup\0" "eon\0" "eor3\0" "eret\0" "esb\0"
- "ext\0" "extr\0" "fabd\0" "fabs\0" "facge\0" "facgt\0" "fadd\0" "faddp\0" "fcadd\0" "fccmp\0" "fccmpe\0" "fcmeq\0"
- "fcmge\0" "fcmgt\0" "fcmla\0" "fcmle\0" "fcmlt\0" "fcmp\0" "fcmpe\0" "fcsel\0" "fcvtas\0" "fcvtau\0" "fcvtl\0"
- "fcvtl2\0" "fcvtms\0" "fcvtmu\0" "fcvtns\0" "fcvtnu\0" "fcvtps\0" "fcvtpu\0" "fcvtxn\0" "fcvtxn2\0" "fcvtzs\0"
- "fcvtzu\0" "fdiv\0" "fjcvtzs\0" "fmadd\0" "fmax\0" "fmaxnm\0" "fmaxnmp\0" "fmaxnmv\0" "fmaxp\0" "fmaxv\0" "fmin\0"
- "fminnm\0" "fminnmp\0" "fminnmv\0" "fminp\0" "fminv\0" "fmla\0" "fmlal\0" "fmlal2\0" "fmls\0" "fmlsl\0" "fmlsl2\0"
- "fmov\0" "fmsub\0" "fmul\0" "fmulx\0" "fneg\0" "fnmadd\0" "fnmsub\0" "fnmul\0" "frecpe\0" "frecps\0" "frecpx\0"
- "frint32x\0" "frint32z\0" "frint64x\0" "frint64z\0" "frinta\0" "frinti\0" "frintm\0" "frintn\0" "frintp\0" "frintx\0"
- "frintz\0" "frsqrte\0" "frsqrts\0" "fsqrt\0" "fsub\0" "gmi\0" "hint\0" "hlt\0" "hvc\0" "ins\0" "isb\0" "ld1\0"
- "ld1r\0" "ld2\0" "ld2r\0" "ld3\0" "ld3r\0" "ld4\0" "ld4r\0" "ldadd\0" "ldadda\0" "ldaddab\0" "ldaddah\0" "ldaddal\0"
- "ldaddalb\0" "ldaddalh\0" "ldaddb\0" "ldaddh\0" "ldaddl\0" "ldaddlb\0" "ldaddlh\0" "ldar\0" "ldarb\0" "ldarh\0"
- "ldaxp\0" "ldaxr\0" "ldaxrb\0" "ldaxrh\0" "ldclr\0" "ldclra\0" "ldclrab\0" "ldclrah\0" "ldclral\0" "ldclralb\0"
- "ldclralh\0" "ldclrb\0" "ldclrh\0" "ldclrl\0" "ldclrlb\0" "ldclrlh\0" "ldeor\0" "ldeora\0" "ldeorab\0" "ldeorah\0"
- "ldeoral\0" "ldeoralb\0" "ldeoralh\0" "ldeorb\0" "ldeorh\0" "ldeorl\0" "ldeorlb\0" "ldeorlh\0" "ldg\0" "ldgm\0"
- "ldlar\0" "ldlarb\0" "ldlarh\0" "ldnp\0" "ldp\0" "ldpsw\0" "ldr\0" "ldraa\0" "ldrab\0" "ldrb\0" "ldrh\0" "ldrsb\0"
- "ldrsh\0" "ldrsw\0" "ldset\0" "ldseta\0" "ldsetab\0" "ldsetah\0" "ldsetal\0" "ldsetalb\0" "ldsetalh\0" "ldsetb\0"
- "ldseth\0" "ldsetl\0" "ldsetlb\0" "ldsetlh\0" "ldsmax\0" "ldsmaxa\0" "ldsmaxab\0" "ldsmaxah\0" "ldsmaxal\0"
- "ldsmaxalb\0" "ldsmaxalh\0" "ldsmaxb\0" "ldsmaxh\0" "ldsmaxl\0" "ldsmaxlb\0" "ldsmaxlh\0" "ldsmin\0" "ldsmina\0"
- "ldsminab\0" "ldsminah\0" "ldsminal\0" "ldsminalb\0" "ldsminalh\0" "ldsminb\0" "ldsminh\0" "ldsminl\0" "ldsminlb\0"
- "ldsminlh\0" "ldtr\0" "ldtrb\0" "ldtrh\0" "ldtrsb\0" "ldtrsh\0" "ldtrsw\0" "ldumax\0" "ldumaxa\0" "ldumaxab\0"
- "ldumaxah\0" "ldumaxal\0" "ldumaxalb\0" "ldumaxalh\0" "ldumaxb\0" "ldumaxh\0" "ldumaxl\0" "ldumaxlb\0" "ldumaxlh\0"
- "ldumin\0" "ldumina\0" "lduminab\0" "lduminah\0" "lduminal\0" "lduminalb\0" "lduminalh\0" "lduminb\0" "lduminh\0"
- "lduminl\0" "lduminlb\0" "lduminlh\0" "ldur\0" "ldurb\0" "ldurh\0" "ldursb\0" "ldursh\0" "ldursw\0" "ldxp\0" "ldxr\0"
- "ldxrb\0" "ldxrh\0" "lslv\0" "lsr\0" "lsrv\0" "mneg\0" "movi\0" "movk\0" "movn\0" "movz\0" "mrs\0" "msr\0" "mvn\0"
- "mvni\0" "negs\0" "ngc\0" "ngcs\0" "nop\0" "not\0" "orn\0" "orr\0" "pacda\0" "pacdb\0" "pacdza\0" "pacdzb\0"
- "pacga\0" "pmul\0" "pmull\0" "pmull2\0" "pssbb\0" "raddhn\0" "raddhn2\0" "rax1\0" "rbit\0" "rev\0" "rev16\0"
- "rev32\0" "rev64\0" "ror\0" "rorv\0" "rsubhn\0" "rsubhn2\0" "saba\0" "sabal\0" "sabal2\0" "sabd\0" "sabdl\0"
- "sabdl2\0" "sadalp\0" "saddl\0" "saddl2\0" "saddlp\0" "saddlv\0" "saddw\0" "saddw2\0" "sbc\0" "sbcs\0" "sbfiz\0"
- "sbfm\0" "sbfx\0" "scvtf\0" "sdiv\0" "setf16\0" "setf8\0" "sev\0" "sevl\0" "sha1c\0" "sha1h\0" "sha1m\0" "sha1p\0"
- "sha1su0\0" "sha1su1\0" "sha256h\0" "sha256h2\0" "sha256su0\0" "sha256su1\0" "sha512h\0" "sha512h2\0" "sha512su0\0"
- "sha512su1\0" "shadd\0" "shsub\0" "sli\0" "sm3partw1\0" "sm3partw2\0" "sm3ss1\0" "sm3tt1a\0" "sm3tt1b\0" "sm3tt2a\0"
- "sm3tt2b\0" "sm4e\0" "sm4ekey\0" "smaddl\0" "smaxp\0" "smaxv\0" "sminp\0" "sminv\0" "smlal\0" "smlal2\0" "smlsl\0"
- "smlsl2\0" "smnegl\0" "smov\0" "smsubl\0" "smulh\0" "smull\0" "smull2\0" "sqabs\0" "sqdmlal\0" "sqdmlal2\0"
- "sqdmlsl\0" "sqdmlsl2\0" "sqdmulh\0" "sqdmull\0" "sqdmull2\0" "sqneg\0" "sqrdmlah\0" "sqrdmlsh\0" "sqrdmulh\0"
- "sqrshl\0" "sqrshrn\0" "sqrshrn2\0" "sqrshrun\0" "sqrshrun2\0" "sqshl\0" "sqshlu\0" "sqshrn\0" "sqshrn2\0"
- "sqshrun\0" "sqshrun2\0" "sqsub\0" "sqxtn\0" "sqxtn2\0" "sqxtun\0" "sqxtun2\0" "srhadd\0" "sri\0" "srshl\0" "srshr\0"
- "srsra\0" "sshl\0" "sshll\0" "sshll2\0" "sshr\0" "ssra\0" "ssubl\0" "ssubl2\0" "ssubw\0" "ssubw2\0" "st1\0" "st2\0"
- "st2g\0" "st3\0" "st4\0" "stadd\0" "staddb\0" "staddh\0" "staddl\0" "staddlb\0" "staddlh\0" "stclr\0" "stclrb\0"
- "stclrh\0" "stclrl\0" "stclrlb\0" "stclrlh\0" "steor\0" "steorb\0" "steorh\0" "steorl\0" "steorlb\0" "steorlh\0"
- "stg\0" "stgm\0" "stgp\0" "stllr\0" "stllrb\0" "stllrh\0" "stlr\0" "stlrb\0" "stlrh\0" "stlxp\0" "stlxr\0" "stlxrb\0"
- "stlxrh\0" "stnp\0" "stp\0" "str\0" "strb\0" "strh\0" "stset\0" "stsetb\0" "stseth\0" "stsetl\0" "stsetlb\0"
- "stsetlh\0" "stsmax\0" "stsmaxb\0" "stsmaxh\0" "stsmaxl\0" "stsmaxlb\0" "stsmaxlh\0" "stsmin\0" "stsminb\0"
- "stsminh\0" "stsminl\0" "stsminlb\0" "stsminlh\0" "sttr\0" "sttrb\0" "sttrh\0" "stumax\0" "stumaxb\0" "stumaxh\0"
- "stumaxl\0" "stumaxlb\0" "stumaxlh\0" "stumin\0" "stuminb\0" "stuminh\0" "stuminl\0" "stuminlb\0" "stuminlh\0"
- "stur\0" "sturb\0" "sturh\0" "stxp\0" "stxr\0" "stxrb\0" "stxrh\0" "stz2g\0" "stzg\0" "stzgm\0" "subg\0" "subp\0"
- "subps\0" "subs\0" "sudot\0" "suqadd\0" "svc\0" "swp\0" "swpa\0" "swpab\0" "swpah\0" "swpal\0" "swpalb\0" "swpalh\0"
- "swpb\0" "swph\0" "swpl\0" "swplb\0" "swplh\0" "sxtb\0" "sxth\0" "sxtl\0" "sxtl2\0" "sxtw\0" "sys\0" "tbl\0" "tbnz\0"
- "tbx\0" "tbz\0" "tlbi\0" "trn1\0" "trn2\0" "uaba\0" "uabal\0" "uabal2\0" "uabd\0" "uabdl\0" "uabdl2\0" "uadalp\0"
- "uaddl\0" "uaddl2\0" "uaddlp\0" "uaddlv\0" "uaddw\0" "uaddw2\0" "ubfiz\0" "ubfm\0" "ubfx\0" "ucvtf\0" "udf\0"
- "udiv\0" "uhadd\0" "uhsub\0" "umaddl\0" "umaxp\0" "umaxv\0" "uminp\0" "uminv\0" "umlal\0" "umlal2\0" "umlsl\0"
- "umlsl2\0" "ummla\0" "umnegl\0" "umov\0" "umsubl\0" "umulh\0" "umull\0" "umull2\0" "uqrshl\0" "uqrshrn\0"
- "uqrshrn2\0" "uqshl\0" "uqshrn\0" "uqshrn2\0" "uqsub\0" "uqxtn\0" "uqxtn2\0" "urecpe\0" "urhadd\0" "urshl\0"
- "urshr\0" "ursqrte\0" "ursra\0" "usdot\0" "ushl\0" "ushll\0" "ushll2\0" "ushr\0" "usmmla\0" "usqadd\0" "usra\0"
- "usubl\0" "usubl2\0" "usubw\0" "usubw2\0" "uxtb\0" "uxth\0" "uxtl\0" "uxtl2\0" "uzp1\0" "uzp2\0" "wfe\0" "wfi\0"
- "xaflag\0" "xar\0" "xpacd\0" "xpaci\0" "xpaclri\0" "yield\0" "zip1\0" "zip2";
+const uint32_t InstDB::_instNameIndexTable[] = {
+ 0x80000000, // Small ''.
+ 0x80000C81, // Small 'adc'.
+ 0x80098C81, // Small 'adcs'.
+ 0x80001081, // Small 'add'.
+ 0x80039081, // Small 'addg'.
+ 0x80099081, // Small 'adds'.
+ 0x80004881, // Small 'adr'.
+ 0x80084881, // Small 'adrp'.
+ 0x800011C1, // Small 'and'.
+ 0x800991C1, // Small 'ands'.
+ 0x80004A61, // Small 'asr'.
+ 0x800B4A61, // Small 'asrv'.
+ 0x80000281, // Small 'at'.
+ 0x801252A1, // Small 'autda'.
+ 0x83A252A1, // Small 'autdza'.
+ 0x802252A1, // Small 'autdb'.
+ 0x85A252A1, // Small 'autdzb'.
+ 0x8014D2A1, // Small 'autia'.
+ 0x00009000, // Large 'autia1716'.
+ 0x20BF5000, // Large 'autia|sp'.
+ 0xB414D2A1, // Small 'autiaz'.
+ 0x8024D2A1, // Small 'autib'.
+ 0x40055009, // Large 'autib|1716'.
+ 0x20BF5009, // Large 'autib|sp'.
+ 0xB424D2A1, // Small 'autibz'.
+ 0x83A4D2A1, // Small 'autiza'.
+ 0x85A4D2A1, // Small 'autizb'.
+ 0x8E161B01, // Small 'axflag'.
+ 0x80000002, // Small 'b'.
+ 0x80000CC2, // Small 'bfc'.
+ 0x800024C2, // Small 'bfi'.
+ 0x800034C2, // Small 'bfm'.
+ 0x80C4E0C2, // Small 'bfxil'.
+ 0x80000D22, // Small 'bic'.
+ 0x80098D22, // Small 'bics'.
+ 0x80000182, // Small 'bl'.
+ 0x80004982, // Small 'blr'.
+ 0x80000242, // Small 'br'.
+ 0x80002E42, // Small 'brk'.
+ 0x80004C23, // Small 'cas'.
+ 0x8000CC23, // Small 'casa'.
+ 0x8020CC23, // Small 'casab'.
+ 0x8080CC23, // Small 'casah'.
+ 0x80C0CC23, // Small 'casal'.
+ 0x84C0CC23, // Small 'casalb'.
+ 0x90C0CC23, // Small 'casalh'.
+ 0x80014C23, // Small 'casb'.
+ 0x80044C23, // Small 'cash'.
+ 0x80064C23, // Small 'casl'.
+ 0x80264C23, // Small 'caslb'.
+ 0x80864C23, // Small 'caslh'.
+ 0x80084C23, // Small 'casp'.
+ 0x80184C23, // Small 'caspa'.
+ 0x98184C23, // Small 'caspal'.
+ 0x80C84C23, // Small 'caspl'.
+ 0x800D3843, // Small 'cbnz'.
+ 0x80006843, // Small 'cbz'.
+ 0x80073463, // Small 'ccmn'.
+ 0x80083463, // Small 'ccmp'.
+ 0x816724C3, // Small 'cfinv'.
+ 0x8001B923, // Small 'cinc'.
+ 0x800B3923, // Small 'cinv'.
+ 0x8182C983, // Small 'clrex'.
+ 0x80004D83, // Small 'cls'.
+ 0x80006983, // Small 'clz'.
+ 0x800039A3, // Small 'cmn'.
+ 0x800041A3, // Small 'cmp'.
+ 0x800841A3, // Small 'cmpp'.
+ 0x800395C3, // Small 'cneg'.
+ 0x85DF0E43, // Small 'crc32b'.
+ 0x100D60C1, // Large 'crc32c|b'.
+ 0x101660C1, // Large 'crc32c|h'.
+ 0x104860C1, // Large 'crc32c|w'.
+ 0x101360C1, // Large 'crc32c|x'.
+ 0x91DF0E43, // Small 'crc32h'.
+ 0xAFDF0E43, // Small 'crc32w'.
+ 0xB1DF0E43, // Small 'crc32x'.
+ 0x80011263, // Small 'csdb'.
+ 0x80061663, // Small 'csel'.
+ 0x800A1663, // Small 'cset'.
+ 0x80DA1663, // Small 'csetm'.
+ 0x80372663, // Small 'csinc'.
+ 0x81672663, // Small 'csinv'.
+ 0x8072BA63, // Small 'csneg'.
+ 0x80000064, // Small 'dc'.
+ 0x81C9C064, // Small 'dcps1'.
+ 0x81D9C064, // Small 'dcps2'.
+ 0x81E9C064, // Small 'dcps3'.
+ 0x800020E4, // Small 'dgh'.
+ 0x800009A4, // Small 'dmb'.
+ 0x8009C244, // Small 'drps'.
+ 0x80000A64, // Small 'dsb'.
+ 0x800039E5, // Small 'eon'.
+ 0x800049E5, // Small 'eor'.
+ 0x80000A65, // Small 'esb'.
+ 0x80095305, // Small 'extr'.
+ 0x800A1645, // Small 'eret'.
+ 0x800025A7, // Small 'gmi'.
+ 0x800A3928, // Small 'hint'.
+ 0x80005188, // Small 'hlt'.
+ 0x80000EC8, // Small 'hvc'.
+ 0x80000069, // Small 'ic'.
+ 0x80000A69, // Small 'isb'.
+ 0x8042048C, // Small 'ldadd'.
+ 0x8242048C, // Small 'ldadda'.
+ 0x100D6051, // Large 'ldadda|b'.
+ 0x10166051, // Large 'ldadda|h'.
+ 0x00007051, // Large 'ldaddal'.
+ 0x100D7051, // Large 'ldaddal|b'.
+ 0x10167051, // Large 'ldaddal|h'.
+ 0x8442048C, // Small 'ldaddb'.
+ 0x9042048C, // Small 'ldaddh'.
+ 0x9842048C, // Small 'ldaddl'.
+ 0x206D5051, // Large 'ldadd|lb'.
+ 0x20155051, // Large 'ldadd|lh'.
+ 0x8009048C, // Small 'ldar'.
+ 0x8029048C, // Small 'ldarb'.
+ 0x8089048C, // Small 'ldarh'.
+ 0x810C048C, // Small 'ldaxp'.
+ 0x812C048C, // Small 'ldaxr'.
+ 0x852C048C, // Small 'ldaxrb'.
+ 0x912C048C, // Small 'ldaxrh'.
+ 0x81260C8C, // Small 'ldclr'.
+ 0x83260C8C, // Small 'ldclra'.
+ 0x100D6058, // Large 'ldclra|b'.
+ 0x10166058, // Large 'ldclra|h'.
+ 0x00007058, // Large 'ldclral'.
+ 0x100D7058, // Large 'ldclral|b'.
+ 0x10167058, // Large 'ldclral|h'.
+ 0x85260C8C, // Small 'ldclrb'.
+ 0x91260C8C, // Small 'ldclrh'.
+ 0x99260C8C, // Small 'ldclrl'.
+ 0x206D5058, // Large 'ldclr|lb'.
+ 0x20155058, // Large 'ldclr|lh'.
+ 0x8127948C, // Small 'ldeor'.
+ 0x8327948C, // Small 'ldeora'.
+ 0x100D605F, // Large 'ldeora|b'.
+ 0x1016605F, // Large 'ldeora|h'.
+ 0x0000705F, // Large 'ldeoral'.
+ 0x100D705F, // Large 'ldeoral|b'.
+ 0x1016705F, // Large 'ldeoral|h'.
+ 0x8527948C, // Small 'ldeorb'.
+ 0x9127948C, // Small 'ldeorh'.
+ 0x9927948C, // Small 'ldeorl'.
+ 0x206D505F, // Large 'ldeor|lb'.
+ 0x2015505F, // Large 'ldeor|lh'.
+ 0x80001C8C, // Small 'ldg'.
+ 0x80069C8C, // Small 'ldgm'.
+ 0x8120B08C, // Small 'ldlar'.
+ 0x8520B08C, // Small 'ldlarb'.
+ 0x9120B08C, // Small 'ldlarh'.
+ 0x8008388C, // Small 'ldnp'.
+ 0x8000408C, // Small 'ldp'.
+ 0x8179C08C, // Small 'ldpsw'.
+ 0x8000488C, // Small 'ldr'.
+ 0x8010C88C, // Small 'ldraa'.
+ 0x8020C88C, // Small 'ldrab'.
+ 0x8001488C, // Small 'ldrb'.
+ 0x8004488C, // Small 'ldrh'.
+ 0x8029C88C, // Small 'ldrsb'.
+ 0x8089C88C, // Small 'ldrsh'.
+ 0x8179C88C, // Small 'ldrsw'.
+ 0x8142CC8C, // Small 'ldset'.
+ 0x8342CC8C, // Small 'ldseta'.
+ 0x100D6066, // Large 'ldseta|b'.
+ 0x10166066, // Large 'ldseta|h'.
+ 0x00007066, // Large 'ldsetal'.
+ 0x100D7066, // Large 'ldsetal|b'.
+ 0x10167066, // Large 'ldsetal|h'.
+ 0x8542CC8C, // Small 'ldsetb'.
+ 0x9142CC8C, // Small 'ldseth'.
+ 0x9942CC8C, // Small 'ldsetl'.
+ 0x206D5066, // Large 'ldset|lb'.
+ 0x20155066, // Large 'ldset|lh'.
+ 0xB016CC8C, // Small 'ldsmax'.
+ 0x0000700E, // Large 'ldsmaxa'.
+ 0x100D700E, // Large 'ldsmaxa|b'.
+ 0x1016700E, // Large 'ldsmaxa|h'.
+ 0x0000800E, // Large 'ldsmaxal'.
+ 0x100D800E, // Large 'ldsmaxal|b'.
+ 0x1016800E, // Large 'ldsmaxal|h'.
+ 0x100D600E, // Large 'ldsmax|b'.
+ 0x1016600E, // Large 'ldsmax|h'.
+ 0x100E600E, // Large 'ldsmax|l'.
+ 0x206D600E, // Large 'ldsmax|lb'.
+ 0x2015600E, // Large 'ldsmax|lh'.
+ 0x9C96CC8C, // Small 'ldsmin'.
+ 0x00007017, // Large 'ldsmina'.
+ 0x100D7017, // Large 'ldsmina|b'.
+ 0x10167017, // Large 'ldsmina|h'.
+ 0x00008017, // Large 'ldsminal'.
+ 0x100D8017, // Large 'ldsminal|b'.
+ 0x10168017, // Large 'ldsminal|h'.
+ 0x100D6017, // Large 'ldsmin|b'.
+ 0x10166017, // Large 'ldsmin|h'.
+ 0x100E6017, // Large 'ldsmin|l'.
+ 0x206D6017, // Large 'ldsmin|lb'.
+ 0x20156017, // Large 'ldsmin|lh'.
+ 0x8009508C, // Small 'ldtr'.
+ 0x8029508C, // Small 'ldtrb'.
+ 0x8089508C, // Small 'ldtrh'.
+ 0x8539508C, // Small 'ldtrsb'.
+ 0x9139508C, // Small 'ldtrsh'.
+ 0xAF39508C, // Small 'ldtrsw'.
+ 0xB016D48C, // Small 'ldumax'.
+ 0x0000701F, // Large 'ldumaxa'.
+ 0x100D701F, // Large 'ldumaxa|b'.
+ 0x1016701F, // Large 'ldumaxa|h'.
+ 0x0000801F, // Large 'ldumaxal'.
+ 0x100D801F, // Large 'ldumaxal|b'.
+ 0x1016801F, // Large 'ldumaxal|h'.
+ 0x100D601F, // Large 'ldumax|b'.
+ 0x1016601F, // Large 'ldumax|h'.
+ 0x100E601F, // Large 'ldumax|l'.
+ 0x206D601F, // Large 'ldumax|lb'.
+ 0x2015601F, // Large 'ldumax|lh'.
+ 0x9C96D48C, // Small 'ldumin'.
+ 0x00007027, // Large 'ldumina'.
+ 0x100D7027, // Large 'ldumina|b'.
+ 0x10167027, // Large 'ldumina|h'.
+ 0x00008027, // Large 'lduminal'.
+ 0x100D8027, // Large 'lduminal|b'.
+ 0x10168027, // Large 'lduminal|h'.
+ 0x100D6027, // Large 'ldumin|b'.
+ 0x10166027, // Large 'ldumin|h'.
+ 0x100E6027, // Large 'ldumin|l'.
+ 0x206D6027, // Large 'ldumin|lb'.
+ 0x20156027, // Large 'ldumin|lh'.
+ 0x8009548C, // Small 'ldur'.
+ 0x8029548C, // Small 'ldurb'.
+ 0x8089548C, // Small 'ldurh'.
+ 0x8539548C, // Small 'ldursb'.
+ 0x9139548C, // Small 'ldursh'.
+ 0xAF39548C, // Small 'ldursw'.
+ 0x8008608C, // Small 'ldxp'.
+ 0x8009608C, // Small 'ldxr'.
+ 0x8029608C, // Small 'ldxrb'.
+ 0x8089608C, // Small 'ldxrh'.
+ 0x8000326C, // Small 'lsl'.
+ 0x800B326C, // Small 'lslv'.
+ 0x80004A6C, // Small 'lsr'.
+ 0x800B4A6C, // Small 'lsrv'.
+ 0x8002102D, // Small 'madd'.
+ 0x800395CD, // Small 'mneg'.
+ 0x800059ED, // Small 'mov'.
+ 0x8005D9ED, // Small 'movk'.
+ 0x800759ED, // Small 'movn'.
+ 0x800D59ED, // Small 'movz'.
+ 0x80004E4D, // Small 'mrs'.
+ 0x80004A6D, // Small 'msr'.
+ 0x8001566D, // Small 'msub'.
+ 0x800032AD, // Small 'mul'.
+ 0x80003ACD, // Small 'mvn'.
+ 0x80001CAE, // Small 'neg'.
+ 0x80099CAE, // Small 'negs'.
+ 0x80000CEE, // Small 'ngc'.
+ 0x80098CEE, // Small 'ngcs'.
+ 0x800041EE, // Small 'nop'.
+ 0x80003A4F, // Small 'orn'.
+ 0x80004A4F, // Small 'orr'.
+ 0x80120C30, // Small 'pacda'.
+ 0x80220C30, // Small 'pacdb'.
+ 0x83A20C30, // Small 'pacdza'.
+ 0x85A20C30, // Small 'pacdzb'.
+ 0x80138C30, // Small 'pacga'.
+ 0x80214E70, // Small 'pssbb'.
+ 0x800A2452, // Small 'rbit'.
+ 0x800050B2, // Small 'ret'.
+ 0x800058B2, // Small 'rev'.
+ 0x20073138, // Large 'rev|16'.
+ 0x81DF58B2, // Small 'rev32'.
+ 0x208F3138, // Large 'rev|64'.
+ 0x800049F2, // Small 'ror'.
+ 0x800B49F2, // Small 'rorv'.
+ 0x80000C53, // Small 'sbc'.
+ 0x80098C53, // Small 'sbcs'.
+ 0x81A49853, // Small 'sbfiz'.
+ 0x80069853, // Small 'sbfm'.
+ 0x800C1853, // Small 'sbfx'.
+ 0x800B2493, // Small 'sdiv'.
+ 0x113B4134, // Large 'setf|8'.
+ 0x20074134, // Large 'setf|16'.
+ 0x800058B3, // Small 'sev'.
+ 0x800658B3, // Small 'sevl'.
+ 0x984205B3, // Small 'smaddl'.
+ 0x80000DB3, // Small 'smc'.
+ 0x9872B9B3, // Small 'smnegl'.
+ 0x982ACDB3, // Small 'smsubl'.
+ 0x808655B3, // Small 'smulh'.
+ 0x80C655B3, // Small 'smull'.
+ 0x80010A73, // Small 'ssbb'.
+ 0x8003F693, // Small 'st2g'.
+ 0x80420693, // Small 'stadd'.
+ 0x98420693, // Small 'staddl'.
+ 0x84420693, // Small 'staddb'.
+ 0x206D50C7, // Large 'stadd|lb'.
+ 0x90420693, // Small 'staddh'.
+ 0x201550C7, // Large 'stadd|lh'.
+ 0x81260E93, // Small 'stclr'.
+ 0x99260E93, // Small 'stclrl'.
+ 0x85260E93, // Small 'stclrb'.
+ 0x206D50CC, // Large 'stclr|lb'.
+ 0x91260E93, // Small 'stclrh'.
+ 0x201550CC, // Large 'stclr|lh'.
+ 0x81279693, // Small 'steor'.
+ 0x99279693, // Small 'steorl'.
+ 0x85279693, // Small 'steorb'.
+ 0x206D50D1, // Large 'steor|lb'.
+ 0x91279693, // Small 'steorh'.
+ 0x201550D1, // Large 'steor|lh'.
+ 0x80001E93, // Small 'stg'.
+ 0x80069E93, // Small 'stgm'.
+ 0x80081E93, // Small 'stgp'.
+ 0x81263293, // Small 'stllr'.
+ 0x85263293, // Small 'stllrb'.
+ 0x91263293, // Small 'stllrh'.
+ 0x80093293, // Small 'stlr'.
+ 0x80293293, // Small 'stlrb'.
+ 0x80893293, // Small 'stlrh'.
+ 0x810C3293, // Small 'stlxp'.
+ 0x812C3293, // Small 'stlxr'.
+ 0x852C3293, // Small 'stlxrb'.
+ 0x912C3293, // Small 'stlxrh'.
+ 0x80083A93, // Small 'stnp'.
+ 0x80004293, // Small 'stp'.
+ 0x80004A93, // Small 'str'.
+ 0x80014A93, // Small 'strb'.
+ 0x80044A93, // Small 'strh'.
+ 0x8142CE93, // Small 'stset'.
+ 0x9942CE93, // Small 'stsetl'.
+ 0x8542CE93, // Small 'stsetb'.
+ 0x206D50D6, // Large 'stset|lb'.
+ 0x9142CE93, // Small 'stseth'.
+ 0x201550D6, // Large 'stset|lh'.
+ 0xB016CE93, // Small 'stsmax'.
+ 0x100E606F, // Large 'stsmax|l'.
+ 0x100D606F, // Large 'stsmax|b'.
+ 0x206D606F, // Large 'stsmax|lb'.
+ 0x1016606F, // Large 'stsmax|h'.
+ 0x2015606F, // Large 'stsmax|lh'.
+ 0x9C96CE93, // Small 'stsmin'.
+ 0x100E6075, // Large 'stsmin|l'.
+ 0x100D6075, // Large 'stsmin|b'.
+ 0x206D6075, // Large 'stsmin|lb'.
+ 0x10166075, // Large 'stsmin|h'.
+ 0x20156075, // Large 'stsmin|lh'.
+ 0x80095293, // Small 'sttr'.
+ 0x80295293, // Small 'sttrb'.
+ 0x80895293, // Small 'sttrh'.
+ 0xB016D693, // Small 'stumax'.
+ 0x100E607B, // Large 'stumax|l'.
+ 0x100D607B, // Large 'stumax|b'.
+ 0x206D607B, // Large 'stumax|lb'.
+ 0x1016607B, // Large 'stumax|h'.
+ 0x2015607B, // Large 'stumax|lh'.
+ 0x9C96D693, // Small 'stumin'.
+ 0x100E6081, // Large 'stumin|l'.
+ 0x100D6081, // Large 'stumin|b'.
+ 0x206D6081, // Large 'stumin|lb'.
+ 0x10166081, // Large 'stumin|h'.
+ 0x20156081, // Large 'stumin|lh'.
+ 0x80095693, // Small 'stur'.
+ 0x80295693, // Small 'sturb'.
+ 0x80895693, // Small 'sturh'.
+ 0x80086293, // Small 'stxp'.
+ 0x80096293, // Small 'stxr'.
+ 0x80296293, // Small 'stxrb'.
+ 0x80896293, // Small 'stxrh'.
+ 0x807EEA93, // Small 'stz2g'.
+ 0x8003EA93, // Small 'stzg'.
+ 0x80D3EA93, // Small 'stzgm'.
+ 0x80000AB3, // Small 'sub'.
+ 0x80038AB3, // Small 'subg'.
+ 0x80080AB3, // Small 'subp'.
+ 0x81380AB3, // Small 'subps'.
+ 0x80098AB3, // Small 'subs'.
+ 0x80000ED3, // Small 'svc'.
+ 0x800042F3, // Small 'swp'.
+ 0x8000C2F3, // Small 'swpa'.
+ 0x8020C2F3, // Small 'swpab'.
+ 0x8080C2F3, // Small 'swpah'.
+ 0x80C0C2F3, // Small 'swpal'.
+ 0x84C0C2F3, // Small 'swpalb'.
+ 0x90C0C2F3, // Small 'swpalh'.
+ 0x800142F3, // Small 'swpb'.
+ 0x800442F3, // Small 'swph'.
+ 0x800642F3, // Small 'swpl'.
+ 0x802642F3, // Small 'swplb'.
+ 0x808642F3, // Small 'swplh'.
+ 0x80015313, // Small 'sxtb'.
+ 0x80045313, // Small 'sxth'.
+ 0x800BD313, // Small 'sxtw'.
+ 0x80004F33, // Small 'sys'.
+ 0x80048994, // Small 'tlbi'.
+ 0x80005274, // Small 'tst'.
+ 0x800D3854, // Small 'tbnz'.
+ 0x80006854, // Small 'tbz'.
+ 0x81A49855, // Small 'ubfiz'.
+ 0x80069855, // Small 'ubfm'.
+ 0x800C1855, // Small 'ubfx'.
+ 0x80001895, // Small 'udf'.
+ 0x800B2495, // Small 'udiv'.
+ 0x984205B5, // Small 'umaddl'.
+ 0x9872B9B5, // Small 'umnegl'.
+ 0x80C655B5, // Small 'umull'.
+ 0x808655B5, // Small 'umulh'.
+ 0x982ACDB5, // Small 'umsubl'.
+ 0x80015315, // Small 'uxtb'.
+ 0x80045315, // Small 'uxth'.
+ 0x800014D7, // Small 'wfe'.
+ 0x800024D7, // Small 'wfi'.
+ 0x8E161838, // Small 'xaflag'.
+ 0x80418618, // Small 'xpacd'.
+ 0x80918618, // Small 'xpaci'.
+ 0x208850DB, // Large 'xpacl|ri'.
+ 0x80461539, // Small 'yield'.
+ 0x80004C41, // Small 'abs'.
+ 0x80001081, // Small 'add'.
+ 0x80E41081, // Small 'addhn'.
+ 0xBAE41081, // Small 'addhn2'.
+ 0x80081081, // Small 'addp'.
+ 0x800B1081, // Small 'addv'.
+ 0x80024CA1, // Small 'aesd'.
+ 0x8002CCA1, // Small 'aese'.
+ 0x86D4CCA1, // Small 'aesimc'.
+ 0x8036CCA1, // Small 'aesmc'.
+ 0x800011C1, // Small 'and'.
+ 0x800C0462, // Small 'bcax'.
+ 0x814B0CC2, // Small 'bfcvt'.
+ 0x9D4B0CC2, // Small 'bfcvtn'.
+ 0x20B150E0, // Large 'bfcvt|n2'.
+ 0x814790C2, // Small 'bfdot'.
+ 0x206D50E5, // Large 'bfmla|lb'.
+ 0x20EA50E5, // Large 'bfmla|lt'.
+ 0x82C6B4C2, // Small 'bfmmla'.
+ 0x80000D22, // Small 'bic'.
+ 0x80001922, // Small 'bif'.
+ 0x80005122, // Small 'bit'.
+ 0x80003262, // Small 'bsl'.
+ 0x80004D83, // Small 'cls'.
+ 0x80006983, // Small 'clz'.
+ 0x800895A3, // Small 'cmeq'.
+ 0x80029DA3, // Small 'cmge'.
+ 0x800A1DA3, // Small 'cmgt'.
+ 0x8004A1A3, // Small 'cmhi'.
+ 0x8009A1A3, // Small 'cmhs'.
+ 0x8002B1A3, // Small 'cmle'.
+ 0x800A31A3, // Small 'cmlt'.
+ 0x8149D1A3, // Small 'cmtst'.
+ 0x800051C3, // Small 'cnt'.
+ 0x800042A4, // Small 'dup'.
+ 0x800049E5, // Small 'eor'.
+ 0x800F49E5, // Small 'eor3'.
+ 0x80005305, // Small 'ext'.
+ 0x80020826, // Small 'fabd'.
+ 0x80098826, // Small 'fabs'.
+ 0x80538C26, // Small 'facge'.
+ 0x81438C26, // Small 'facgt'.
+ 0x80021026, // Small 'fadd'.
+ 0x81021026, // Small 'faddp'.
+ 0x80420466, // Small 'fcadd'.
+ 0x81068C66, // Small 'fccmp'.
+ 0x8B068C66, // Small 'fccmpe'.
+ 0x8112B466, // Small 'fcmeq'.
+ 0x8053B466, // Small 'fcmge'.
+ 0x8143B466, // Small 'fcmgt'.
+ 0x80163466, // Small 'fcmla'.
+ 0x80563466, // Small 'fcmle'.
+ 0x81463466, // Small 'fcmlt'.
+ 0x80083466, // Small 'fcmp'.
+ 0x80583466, // Small 'fcmpe'.
+ 0x80C2CC66, // Small 'fcsel'.
+ 0x800A5866, // Small 'fcvt'.
+ 0xA61A5866, // Small 'fcvtas'.
+ 0xAA1A5866, // Small 'fcvtau'.
+ 0x80CA5866, // Small 'fcvtl'.
+ 0xBACA5866, // Small 'fcvtl2'.
+ 0xA6DA5866, // Small 'fcvtms'.
+ 0xAADA5866, // Small 'fcvtmu'.
+ 0x80EA5866, // Small 'fcvtn'.
+ 0xBAEA5866, // Small 'fcvtn2'.
+ 0xA6EA5866, // Small 'fcvtns'.
+ 0xAAEA5866, // Small 'fcvtnu'.
+ 0xA70A5866, // Small 'fcvtps'.
+ 0xAB0A5866, // Small 'fcvtpu'.
+ 0x9D8A5866, // Small 'fcvtxn'.
+ 0x20B150EC, // Large 'fcvtx|n2'.
+ 0xA7AA5866, // Small 'fcvtzs'.
+ 0xABAA5866, // Small 'fcvtzu'.
+ 0x800B2486, // Small 'fdiv'.
+ 0x101060F1, // Large 'fjcvtz|s'.
+ 0x804205A6, // Small 'fmadd'.
+ 0x800C05A6, // Small 'fmax'.
+ 0x9AEC05A6, // Small 'fmaxnm'.
+ 0x104460F7, // Large 'fmaxnm|p'.
+ 0x10E360F7, // Large 'fmaxnm|v'.
+ 0x810C05A6, // Small 'fmaxp'.
+ 0x816C05A6, // Small 'fmaxv'.
+ 0x800725A6, // Small 'fmin'.
+ 0x9AE725A6, // Small 'fminnm'.
+ 0x104460FD, // Large 'fminnm|p'.
+ 0x10E360FD, // Large 'fminnm|v'.
+ 0x810725A6, // Small 'fminp'.
+ 0x816725A6, // Small 'fminv'.
+ 0x8000B1A6, // Small 'fmla'.
+ 0x80C0B1A6, // Small 'fmlal'.
+ 0xBAC0B1A6, // Small 'fmlal2'.
+ 0x8009B1A6, // Small 'fmls'.
+ 0x80C9B1A6, // Small 'fmlsl'.
+ 0xBAC9B1A6, // Small 'fmlsl2'.
+ 0x800B3DA6, // Small 'fmov'.
+ 0x802ACDA6, // Small 'fmsub'.
+ 0x800655A6, // Small 'fmul'.
+ 0x818655A6, // Small 'fmulx'.
+ 0x800395C6, // Small 'fneg'.
+ 0x8840B5C6, // Small 'fnmadd'.
+ 0x8559B5C6, // Small 'fnmsub'.
+ 0x80CAB5C6, // Small 'fnmul'.
+ 0x8B019646, // Small 'frecpe'.
+ 0xA7019646, // Small 'frecps'.
+ 0xB1019646, // Small 'frecpx'.
+ 0x10137087, // Large 'frint32|x'.
+ 0x108E7087, // Large 'frint32|z'.
+ 0x308F5087, // Large 'frint|64x'.
+ 0x30925087, // Large 'frint|64z'.
+ 0x83472646, // Small 'frinta'.
+ 0x93472646, // Small 'frinti'.
+ 0x9B472646, // Small 'frintm'.
+ 0x9D472646, // Small 'frintn'.
+ 0xA1472646, // Small 'frintp'.
+ 0xB1472646, // Small 'frintx'.
+ 0xB5472646, // Small 'frintz'.
+ 0x20D25103, // Large 'frsqr|te'.
+ 0x20705103, // Large 'frsqr|ts'.
+ 0x81494666, // Small 'fsqrt'.
+ 0x80015666, // Small 'fsub'.
+ 0x80004DC9, // Small 'ins'.
+ 0x8000708C, // Small 'ld1'.
+ 0x8009708C, // Small 'ld1r'.
+ 0x8000748C, // Small 'ld2'.
+ 0x8009748C, // Small 'ld2r'.
+ 0x8000788C, // Small 'ld3'.
+ 0x8009788C, // Small 'ld3r'.
+ 0x80007C8C, // Small 'ld4'.
+ 0x80097C8C, // Small 'ld4r'.
+ 0x8008388C, // Small 'ldnp'.
+ 0x8000408C, // Small 'ldp'.
+ 0x8000488C, // Small 'ldr'.
+ 0x8009548C, // Small 'ldur'.
+ 0x8000058D, // Small 'mla'.
+ 0x80004D8D, // Small 'mls'.
+ 0x800059ED, // Small 'mov'.
+ 0x8004D9ED, // Small 'movi'.
+ 0x800032AD, // Small 'mul'.
+ 0x80003ACD, // Small 'mvn'.
+ 0x8004BACD, // Small 'mvni'.
+ 0x80001CAE, // Small 'neg'.
+ 0x800051EE, // Small 'not'.
+ 0x80003A4F, // Small 'orn'.
+ 0x80004A4F, // Small 'orr'.
+ 0x800655B0, // Small 'pmul'.
+ 0x80C655B0, // Small 'pmull'.
+ 0xBAC655B0, // Small 'pmull2'.
+ 0x9C821032, // Small 'raddhn'.
+ 0x30B04108, // Large 'radd|hn2'.
+ 0x800E6032, // Small 'rax1'.
+ 0x800A2452, // Small 'rbit'.
+ 0x20073138, // Large 'rev|16'.
+ 0x81DF58B2, // Small 'rev32'.
+ 0x208F3138, // Large 'rev|64'.
+ 0x80E92272, // Small 'rshrn'.
+ 0xBAE92272, // Small 'rshrn2'.
+ 0x9C815672, // Small 'rsubhn'.
+ 0x30B0410C, // Large 'rsub|hn2'.
+ 0x80008833, // Small 'saba'.
+ 0x80C08833, // Small 'sabal'.
+ 0xBAC08833, // Small 'sabal2'.
+ 0x80020833, // Small 'sabd'.
+ 0x80C20833, // Small 'sabdl'.
+ 0xBAC20833, // Small 'sabdl2'.
+ 0xA0C09033, // Small 'sadalp'.
+ 0x80C21033, // Small 'saddl'.
+ 0xBAC21033, // Small 'saddl2'.
+ 0xA0C21033, // Small 'saddlp'.
+ 0xACC21033, // Small 'saddlv'.
+ 0x81721033, // Small 'saddw'.
+ 0xBB721033, // Small 'saddw2'.
+ 0x806A5873, // Small 'scvtf'.
+ 0x800A3C93, // Small 'sdot'.
+ 0x803E0513, // Small 'sha1c'.
+ 0x808E0513, // Small 'sha1h'.
+ 0x80DE0513, // Small 'sha1m'.
+ 0x810E0513, // Small 'sha1p'.
+ 0x30354110, // Large 'sha1|su0'.
+ 0x303E4110, // Large 'sha1|su1'.
+ 0x1016602F, // Large 'sha256|h'.
+ 0x2095602F, // Large 'sha256|h2'.
+ 0x0000902F, // Large 'sha256su0'.
+ 0x1005802F, // Large 'sha256su|1'.
+ 0x10166038, // Large 'sha512|h'.
+ 0x20956038, // Large 'sha512|h2'.
+ 0x30356038, // Large 'sha512|su0'.
+ 0x303E6038, // Large 'sha512|su1'.
+ 0x80420513, // Small 'shadd'.
+ 0x80003113, // Small 'shl'.
+ 0x80063113, // Small 'shll'.
+ 0x81D63113, // Small 'shll2'.
+ 0x80074913, // Small 'shrn'.
+ 0x81D74913, // Small 'shrn2'.
+ 0x802ACD13, // Small 'shsub'.
+ 0x80002593, // Small 'sli'.
+ 0x10058041, // Large 'sm3partw|1'.
+ 0x10328041, // Large 'sm3partw|2'.
+ 0xB939F9B3, // Small 'sm3ss1'.
+ 0x10006114, // Large 'sm3tt1|a'.
+ 0x100D6114, // Large 'sm3tt1|b'.
+ 0x211A5114, // Large 'sm3tt|2a'.
+ 0x211C5114, // Large 'sm3tt|2b'.
+ 0x8002FDB3, // Small 'sm4e'.
+ 0x0000711E, // Large 'sm4ekey'.
+ 0x800C05B3, // Small 'smax'.
+ 0x810C05B3, // Small 'smaxp'.
+ 0x816C05B3, // Small 'smaxv'.
+ 0x800725B3, // Small 'smin'.
+ 0x810725B3, // Small 'sminp'.
+ 0x816725B3, // Small 'sminv'.
+ 0x80C0B1B3, // Small 'smlal'.
+ 0xBAC0B1B3, // Small 'smlal2'.
+ 0x80C9B1B3, // Small 'smlsl'.
+ 0xBAC9B1B3, // Small 'smlsl2'.
+ 0x801635B3, // Small 'smmla'.
+ 0x800B3DB3, // Small 'smov'.
+ 0x80C655B3, // Small 'smull'.
+ 0xBAC655B3, // Small 'smull2'.
+ 0x81310633, // Small 'sqabs'.
+ 0x80420633, // Small 'sqadd'.
+ 0x00007097, // Large 'sqdmlal'.
+ 0x10327097, // Large 'sqdmlal|2'.
+ 0x209E5097, // Large 'sqdml|sl'.
+ 0x309E5097, // Large 'sqdml|sl2'.
+ 0x101660A1, // Large 'sqdmul|h'.
+ 0x100E60A1, // Large 'sqdmul|l'.
+ 0x209F60A1, // Large 'sqdmul|l2'.
+ 0x8072BA33, // Small 'sqneg'.
+ 0x101670A7, // Large 'sqrdmla|h'.
+ 0x202F60A7, // Large 'sqrdml|sh'.
+ 0x30AE50A7, // Large 'sqrdm|ulh'.
+ 0x9889CA33, // Small 'sqrshl'.
+ 0x101C6049, // Large 'sqrshr|n'.
+ 0x20B16049, // Large 'sqrshr|n2'.
+ 0x00008049, // Large 'sqrshrun'.
+ 0x10328049, // Large 'sqrshrun|2'.
+ 0x80C44E33, // Small 'sqshl'.
+ 0xAAC44E33, // Small 'sqshlu'.
+ 0x9D244E33, // Small 'sqshrn'.
+ 0x20B150B3, // Large 'sqshr|n2'.
+ 0x101C60B3, // Large 'sqshru|n'.
+ 0x20B160B3, // Large 'sqshru|n2'.
+ 0x802ACE33, // Small 'sqsub'.
+ 0x80EA6233, // Small 'sqxtn'.
+ 0xBAEA6233, // Small 'sqxtn2'.
+ 0x9D5A6233, // Small 'sqxtun'.
+ 0x20B15125, // Large 'sqxtu|n2'.
+ 0x8840A253, // Small 'srhadd'.
+ 0x80002653, // Small 'sri'.
+ 0x80C44E53, // Small 'srshl'.
+ 0x81244E53, // Small 'srshr'.
+ 0x80194E53, // Small 'srsra'.
+ 0x80062273, // Small 'sshl'.
+ 0x80C62273, // Small 'sshll'.
+ 0xBAC62273, // Small 'sshll2'.
+ 0x80092273, // Small 'sshr'.
+ 0x8000CA73, // Small 'ssra'.
+ 0x80C15673, // Small 'ssubl'.
+ 0xBAC15673, // Small 'ssubl2'.
+ 0x81715673, // Small 'ssubw'.
+ 0xBB715673, // Small 'ssubw2'.
+ 0x80007293, // Small 'st1'.
+ 0x80007693, // Small 'st2'.
+ 0x80007A93, // Small 'st3'.
+ 0x80007E93, // Small 'st4'.
+ 0x80083A93, // Small 'stnp'.
+ 0x80004293, // Small 'stp'.
+ 0x80004A93, // Small 'str'.
+ 0x80095693, // Small 'stur'.
+ 0x80000AB3, // Small 'sub'.
+ 0x80E40AB3, // Small 'subhn'.
+ 0xBAE40AB3, // Small 'subhn2'.
+ 0x814792B3, // Small 'sudot'.
+ 0x8840C6B3, // Small 'suqadd'.
+ 0x80065313, // Small 'sxtl'.
+ 0x81D65313, // Small 'sxtl2'.
+ 0x80003054, // Small 'tbl'.
+ 0x80006054, // Small 'tbx'.
+ 0x800E3A54, // Small 'trn1'.
+ 0x800EBA54, // Small 'trn2'.
+ 0x80008835, // Small 'uaba'.
+ 0x80C08835, // Small 'uabal'.
+ 0xBAC08835, // Small 'uabal2'.
+ 0x80020835, // Small 'uabd'.
+ 0x80C20835, // Small 'uabdl'.
+ 0xBAC20835, // Small 'uabdl2'.
+ 0xA0C09035, // Small 'uadalp'.
+ 0x80C21035, // Small 'uaddl'.
+ 0xBAC21035, // Small 'uaddl2'.
+ 0xA0C21035, // Small 'uaddlp'.
+ 0xACC21035, // Small 'uaddlv'.
+ 0x81721035, // Small 'uaddw'.
+ 0xBB721035, // Small 'uaddw2'.
+ 0x806A5875, // Small 'ucvtf'.
+ 0x800A3C95, // Small 'udot'.
+ 0x80420515, // Small 'uhadd'.
+ 0x802ACD15, // Small 'uhsub'.
+ 0x800C05B5, // Small 'umax'.
+ 0x810C05B5, // Small 'umaxp'.
+ 0x816C05B5, // Small 'umaxv'.
+ 0x800725B5, // Small 'umin'.
+ 0x810725B5, // Small 'uminp'.
+ 0x816725B5, // Small 'uminv'.
+ 0x80C0B1B5, // Small 'umlal'.
+ 0xBAC0B1B5, // Small 'umlal2'.
+ 0x80C9B1B5, // Small 'umlsl'.
+ 0xBAC9B1B5, // Small 'umlsl2'.
+ 0x801635B5, // Small 'ummla'.
+ 0x800B3DB5, // Small 'umov'.
+ 0x80C655B5, // Small 'umull'.
+ 0xBAC655B5, // Small 'umull2'.
+ 0x80420635, // Small 'uqadd'.
+ 0x9889CA35, // Small 'uqrshl'.
+ 0x101C60B9, // Large 'uqrshr|n'.
+ 0x20B160B9, // Large 'uqrshr|n2'.
+ 0x80C44E35, // Small 'uqshl'.
+ 0x9D244E35, // Small 'uqshrn'.
+ 0x20B1512A, // Large 'uqshr|n2'.
+ 0x802ACE35, // Small 'uqsub'.
+ 0x80EA6235, // Small 'uqxtn'.
+ 0xBAEA6235, // Small 'uqxtn2'.
+ 0x8B019655, // Small 'urecpe'.
+ 0x8840A255, // Small 'urhadd'.
+ 0x80C44E55, // Small 'urshl'.
+ 0x81244E55, // Small 'urshr'.
+ 0x20D2512F, // Large 'ursqr|te'.
+ 0x80194E55, // Small 'ursra'.
+ 0x81479275, // Small 'usdot'.
+ 0x80062275, // Small 'ushl'.
+ 0x80C62275, // Small 'ushll'.
+ 0xBAC62275, // Small 'ushll2'.
+ 0x80092275, // Small 'ushr'.
+ 0x82C6B675, // Small 'usmmla'.
+ 0x8840C675, // Small 'usqadd'.
+ 0x8000CA75, // Small 'usra'.
+ 0x80C15675, // Small 'usubl'.
+ 0xBAC15675, // Small 'usubl2'.
+ 0x81715675, // Small 'usubw'.
+ 0xBB715675, // Small 'usubw2'.
+ 0x80065315, // Small 'uxtl'.
+ 0x81D65315, // Small 'uxtl2'.
+ 0x800E4355, // Small 'uzp1'.
+ 0x800EC355, // Small 'uzp2'.
+ 0x80004838, // Small 'xar'.
+ 0x80003A98, // Small 'xtn'.
+ 0x800EBA98, // Small 'xtn2'.
+ 0x800E413A, // Small 'zip1'.
+ 0x800EC13A // Small 'zip2'.
+};
+
+const char InstDB::_instNameStringTable[] =
+ "autia1716autibldsmaxalhldsminalldumaxallduminalsha256su0sha512su1sm3partwsqrshru"
+ "nldaddalldclralldeoralldsetallbstsmaxstsminstumaxstuminfrint32z64x64zh2sqdmlalsl"
+ "2sqdmulsqrdmlaulhn2sqshruuqrshrspcrc32cstaddstclrsteorstsetxpaclbfcvtbfmlaltfcvt"
+ "xfjcvtzfmaxnmfminnmfrsqrraddrsubsha1sm3tt12a2bsm4ekeysqxtuuqshrursqrsetfrev8";
+
const InstDB::InstNameIndex InstDB::instNameIndex[26] = {
{ Inst::kIdAdc , Inst::kIdAnd_v + 1 },
diff --git a/erts/emulator/asmjit/arm/a64instdb.h b/erts/emulator/asmjit/arm/a64instdb.h
index 0575d1a2fa..fe9fcecae9 100644
--- a/erts/emulator/asmjit/arm/a64instdb.h
+++ b/erts/emulator/asmjit/arm/a64instdb.h
@@ -39,9 +39,7 @@ struct InstInfo {
uint32_t _encoding : 8;
//! Index to data specific to each encoding type.
uint32_t _encodingDataIndex : 8;
- uint32_t _reserved : 2;
- //! Index to \ref _nameData.
- uint32_t _nameDataIndex : 14;
+ uint32_t _reserved : 16;
uint16_t _rwInfoIndex;
uint16_t _flags;
diff --git a/erts/emulator/asmjit/arm/a64instdb_p.h b/erts/emulator/asmjit/arm/a64instdb_p.h
index eb4f3f8376..ef557d3c7a 100644
--- a/erts/emulator/asmjit/arm/a64instdb_p.h
+++ b/erts/emulator/asmjit/arm/a64instdb_p.h
@@ -861,7 +861,8 @@ struct InstNameIndex {
// ====================
#ifndef ASMJIT_NO_TEXT
-extern const char _nameData[];
+extern const uint32_t _instNameIndexTable[];
+extern const char _instNameStringTable[];
extern const InstNameIndex instNameIndex[26];
#endif // !ASMJIT_NO_TEXT
diff --git a/erts/emulator/asmjit/arm/a64rapass.cpp b/erts/emulator/asmjit/arm/a64rapass.cpp
index ad78369eaa..aaec1c90f9 100644
--- a/erts/emulator/asmjit/arm/a64rapass.cpp
+++ b/erts/emulator/asmjit/arm/a64rapass.cpp
@@ -102,7 +102,7 @@ public:
// TODO: [ARM] This is just a workaround...
static InstControlFlow getControlFlowType(InstId instId) noexcept {
- switch (instId) {
+ switch (BaseInst::extractRealId(instId)) {
case Inst::kIdB:
case Inst::kIdBr:
if (BaseInst::extractARMCondCode(instId) == CondCode::kAL)
@@ -127,8 +127,8 @@ static InstControlFlow getControlFlowType(InstId instId) noexcept {
Error RACFGBuilder::onInst(InstNode* inst, InstControlFlow& controlType, RAInstBuilder& ib) noexcept {
InstRWInfo rwInfo;
- InstId instId = inst->id();
- if (Inst::isDefinedId(instId)) {
+ if (Inst::isDefinedId(inst->realId())) {
+ InstId instId = inst->id();
uint32_t opCount = inst->opCount();
const Operand* opArray = inst->operands();
ASMJIT_PROPAGATE(InstInternal::queryRWInfo(_arch, inst->baseInst(), opArray, opCount, &rwInfo));
@@ -136,6 +136,8 @@ Error RACFGBuilder::onInst(InstNode* inst, InstControlFlow& controlType, RAInstB
const InstDB::InstInfo& instInfo = InstDB::infoById(instId);
uint32_t singleRegOps = 0;
+ ib.addInstRWFlags(rwInfo.instFlags());
+
if (opCount) {
uint32_t consecutiveOffset = 0xFFFFFFFFu;
uint32_t consecutiveParent = Globals::kInvalidId;
@@ -715,6 +717,50 @@ ASMJIT_FAVOR_SPEED Error ARMRAPass::_rewrite(BaseNode* first, BaseNode* stop) no
}
}
}
+
+ // Rewrite `loadAddressOf()` construct.
+ if (inst->realId() == Inst::kIdAdr && inst->opCount() == 2 && inst->op(1).isMem()) {
+ BaseMem mem = inst->op(1).as<BaseMem>();
+ int64_t offset = mem.offset();
+
+ if (!mem.hasBaseOrIndex()) {
+ inst->setId(Inst::kIdMov);
+ inst->setOp(1, Imm(offset));
+ }
+ else {
+ if (mem.hasIndex())
+ return DebugUtils::errored(kErrorInvalidAddressIndex);
+
+ GpX dst(inst->op(0).as<Gp>().id());
+ GpX base(mem.baseId());
+
+ InstId arithInstId = offset < 0 ? Inst::kIdSub : Inst::kIdAdd;
+ uint64_t absOffset = offset < 0 ? Support::neg(uint64_t(offset)) : uint64_t(offset);
+
+ inst->setId(arithInstId);
+ inst->setOpCount(3);
+ inst->setOp(1, base);
+ inst->setOp(2, Imm(absOffset));
+
+ // Use two operations if the offset cannot be encoded with ADD/SUB.
+ if (absOffset > 0xFFFu && (absOffset & ~uint64_t(0xFFF000u)) != 0) {
+ if (absOffset <= 0xFFFFFFu) {
+ cc()->_setCursor(inst->prev());
+ ASMJIT_PROPAGATE(cc()->emit(arithInstId, dst, base, Imm(absOffset & 0xFFFu)));
+
+ inst->setOp(1, dst);
+ inst->setOp(2, Imm(absOffset & 0xFFF000u));
+ }
+ else {
+ cc()->_setCursor(inst->prev());
+ ASMJIT_PROPAGATE(cc()->emit(Inst::kIdMov, inst->op(0), Imm(absOffset)));
+
+ inst->setOp(1, base);
+ inst->setOp(2, dst);
+ }
+ }
+ }
+ }
}
node = next;
diff --git a/erts/emulator/asmjit/arm/armoperand.h b/erts/emulator/asmjit/arm/armoperand.h
index e7803e952b..a6322a097d 100644
--- a/erts/emulator/asmjit/arm/armoperand.h
+++ b/erts/emulator/asmjit/arm/armoperand.h
@@ -110,14 +110,14 @@ public:
static inline bool isVecQ(const Operand_& op) noexcept { return op.as<Reg>().isVecQ(); }
static inline bool isVecV(const Operand_& op) noexcept { return op.as<Reg>().isVecV(); }
- static inline bool isGpW(const Operand_& op, uint32_t id) noexcept { return isGpW(op) & (op.id() == id); }
- static inline bool isGpX(const Operand_& op, uint32_t id) noexcept { return isGpX(op) & (op.id() == id); }
- static inline bool isVecB(const Operand_& op, uint32_t id) noexcept { return isVecB(op) & (op.id() == id); }
- static inline bool isVecH(const Operand_& op, uint32_t id) noexcept { return isVecH(op) & (op.id() == id); }
- static inline bool isVecS(const Operand_& op, uint32_t id) noexcept { return isVecS(op) & (op.id() == id); }
- static inline bool isVecD(const Operand_& op, uint32_t id) noexcept { return isVecD(op) & (op.id() == id); }
- static inline bool isVecQ(const Operand_& op, uint32_t id) noexcept { return isVecQ(op) & (op.id() == id); }
- static inline bool isVecV(const Operand_& op, uint32_t id) noexcept { return isVecV(op) & (op.id() == id); }
+ static inline bool isGpW(const Operand_& op, uint32_t id) noexcept { return bool(unsigned(isGpW(op)) & unsigned(op.id() == id)); }
+ static inline bool isGpX(const Operand_& op, uint32_t id) noexcept { return bool(unsigned(isGpX(op)) & unsigned(op.id() == id)); }
+ static inline bool isVecB(const Operand_& op, uint32_t id) noexcept { return bool(unsigned(isVecB(op)) & unsigned(op.id() == id)); }
+ static inline bool isVecH(const Operand_& op, uint32_t id) noexcept { return bool(unsigned(isVecH(op)) & unsigned(op.id() == id)); }
+ static inline bool isVecS(const Operand_& op, uint32_t id) noexcept { return bool(unsigned(isVecS(op)) & unsigned(op.id() == id)); }
+ static inline bool isVecD(const Operand_& op, uint32_t id) noexcept { return bool(unsigned(isVecD(op)) & unsigned(op.id() == id)); }
+ static inline bool isVecQ(const Operand_& op, uint32_t id) noexcept { return bool(unsigned(isVecQ(op)) & unsigned(op.id() == id)); }
+ static inline bool isVecV(const Operand_& op, uint32_t id) noexcept { return bool(unsigned(isVecV(op)) & unsigned(op.id() == id)); }
};
//! General purpose register (ARM).
@@ -455,11 +455,12 @@ public:
//! \}
- //! \name ARM Specific Features
+ //! \name Clone
//! \{
//! Clones the memory operand.
inline constexpr Mem clone() const noexcept { return Mem(*this); }
+
//! Gets new memory operand adjusted by `off`.
inline Mem cloneAdjusted(int64_t off) const noexcept {
Mem result(*this);
@@ -467,6 +468,51 @@ public:
return result;
}
+ //! Clones the memory operand and makes it pre-index.
+ inline Mem pre() const noexcept {
+ Mem result(*this);
+ result.setPredicate(kOffsetPreIndex);
+ return result;
+ }
+
+ //! Clones the memory operand, applies a given offset `off` and makes it pre-index.
+ inline Mem pre(int64_t off) const noexcept {
+ Mem result(*this);
+ result.setPredicate(kOffsetPreIndex);
+ result.addOffset(off);
+ return result;
+ }
+
+ //! Clones the memory operand and makes it post-index.
+ inline Mem post() const noexcept {
+ Mem result(*this);
+ result.setPredicate(kOffsetPostIndex);
+ return result;
+ }
+
+ //! Clones the memory operand, applies a given offset `off` and makes it post-index.
+ inline Mem post(int64_t off) const noexcept {
+ Mem result(*this);
+ result.setPredicate(kOffsetPostIndex);
+ result.addOffset(off);
+ return result;
+ }
+
+ //! \}
+
+ //! \name Base & Index
+ //! \{
+
+ //! Converts memory `baseType` and `baseId` to `arm::Reg` instance.
+ //!
+ //! The memory must have a valid base register otherwise the result will be wrong.
+ inline Reg baseReg() const noexcept { return Reg::fromTypeAndId(baseType(), baseId()); }
+
+ //! Converts memory `indexType` and `indexId` to `arm::Reg` instance.
+ //!
+ //! The memory must have a valid index register otherwise the result will be wrong.
+ inline Reg indexReg() const noexcept { return Reg::fromTypeAndId(indexType(), indexId()); }
+
using BaseMem::setIndex;
inline void setIndex(const BaseReg& index, uint32_t shift) noexcept {
@@ -474,6 +520,11 @@ public:
setShift(shift);
}
+ //! \}
+
+ //! \name ARM Specific Features
+ //! \{
+
//! Gets whether the memory operand has shift (aka scale) constant.
inline constexpr bool hasShift() const noexcept { return _signature.hasField<kSignatureMemShiftValueMask>(); }
//! Gets the memory operand's shift (aka scale) constant.
@@ -499,32 +550,6 @@ public:
inline void makePreIndex() noexcept { setPredicate(kOffsetPreIndex); }
inline void makePostIndex() noexcept { setPredicate(kOffsetPostIndex); }
- inline Mem pre() const noexcept {
- Mem result(*this);
- result.setPredicate(kOffsetPreIndex);
- return result;
- }
-
- inline Mem pre(int64_t off) const noexcept {
- Mem result(*this);
- result.setPredicate(kOffsetPreIndex);
- result.addOffset(off);
- return result;
- }
-
- inline Mem post() const noexcept {
- Mem result(*this);
- result.setPredicate(kOffsetPreIndex);
- return result;
- }
-
- inline Mem post(int64_t off) const noexcept {
- Mem result(*this);
- result.setPredicate(kOffsetPostIndex);
- result.addOffset(off);
- return result;
- }
-
//! \}
};
diff --git a/erts/emulator/asmjit/core.h b/erts/emulator/asmjit/core.h
index 4406ed89f3..e586734e49 100644
--- a/erts/emulator/asmjit/core.h
+++ b/erts/emulator/asmjit/core.h
@@ -105,40 +105,37 @@ namespace asmjit {
//!
//! - Tested:
//!
-//! - **Clang** - Tested by GitHub Actions - Clang 3.9+ (with C++11 enabled) is officially supported (older Clang
-//! versions having C++11 support are probably fine, but are not regularly tested).
+//! - **Clang** - Tested by GitHub Actions - Clang 10+ is officially supported and tested by CI, older Clang versions
+//! having C++11 should work, but are not tested anymore due to upgraded CI images.
//!
-//! - **GNU** - Tested by GitHub Actions - GCC 4.8+ (with C++11 enabled) is officially supported.
+//! - **GNU** - Tested by GitHub Actions - GCC 7+ is officially supported, older GCC versions from 4.8+ having C++11
+//! enabled should also work, but are not tested anymore due to upgraded CI images.
//!
-//! - **MINGW** - Should work, but it's not tested in our CI environment.
+//! - **MINGW** - Reported to work, but not tested in our CI environment (help welcome).
//!
-//! - **MSVC** - Tested by GitHub Actions - VS2017+ is officially supported, VS2015 is reported to work.
-//!
-//! - Untested:
-//!
-//! - **Intel** - No maintainers and no CI environment to regularly test this compiler.
-//!
-//! - **Other** C++ compilers would require basic support in
-//! [core/api-config.h](https://github.com/asmjit/asmjit/tree/master/src/asmjit/core/api-config.h).
+//! - **MSVC** - Tested by GitHub Actions - VS2019+ is officially supported, VS2015 and VS2017 is reported to work,
+//! but not tested by CI anymore.
//!
//! ### Supported Operating Systems and Platforms
//!
//! - Tested:
//!
-//! - **Linux** - Tested by GitHub Actions (any distribution is generally supported).
+//! - **BSD** - FreeBSD, NetBSD, and OpenBSD tested by GitHub Actions (only recent images are tested by CI). BSD
+//! runners only test BSD images with clang compiler.
//!
-//! - **Mac OS** - Tested by GitHub Actions (any version is supported).
+//! - **Linux** - Tested by GitHub Actions (only recent Ubuntu images are tested by CI, in general any distribution
+//! should be supported as AsmJit has no dependencies).
+//!
+//! - **Mac OS** - Tested by GitHub Actions.
//!
//! - **Windows** - Tested by GitHub Actions - (Windows 7+ is officially supported).
//!
//! - **Emscripten** - Works if compiled with \ref ASMJIT_NO_JIT. AsmJit cannot generate WASM code, but can be
-//! used to generate X86/X64 code within a browser, for example.
+//! used to generate X86/X64/AArch64 code within a browser, for example.
//!
//! - Untested:
//!
-//! - **BSDs** - No maintainers, no CI environment to regularly test BSDs, but they should work out of box.
-//!
-//! - **Haiku** - Not regularly tested, but reported to work.
+//! - **Haiku** - Reported to work, not tested by CI.
//!
//! - **Other** operating systems would require some testing and support in the following files:
//! - [core/api-config.h](https://github.com/asmjit/asmjit/tree/master/src/asmjit/core/api-config.h)
@@ -149,7 +146,7 @@ namespace asmjit {
//!
//! - **X86** and **X86_64** - Both 32-bit and 64-bit backends tested on CI.
//! - **AArch64** - AArch64 backend is currently only partially tested (there is no native AArch64 runner to test
-//! AsmJit Builder/Compiler)
+//! AsmJit Builder/Compiler).
//!
//! ### Static Builds and Embedding
//!
@@ -454,7 +451,8 @@ namespace asmjit {
//! JitRuntime rt; // Runtime specialized for JIT code execution.
//!
//! CodeHolder code; // Holds code and relocation information.
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//!
//! x86::Assembler a(&code); // Create and attach x86::Assembler to code.
//! a.mov(x86::eax, 1); // Move one to eax register.
@@ -599,9 +597,10 @@ namespace asmjit {
//! int main() {
//! // Create a custom environment that matches the current host environment.
//! Environment env = Environment::host();
+//! CpuFeatures cpuFeatures = CpuInfo::host().features();
//!
//! CodeHolder code; // Create a CodeHolder.
-//! code.init(env); // Initialize CodeHolder with environment.
+//! code.init(env, cpuFeatures); // Initialize CodeHolder with environment.
//!
//! x86::Assembler a(&code); // Create and attach x86::Assembler to `code`.
//!
@@ -722,10 +721,11 @@ namespace asmjit {
//!
//! void initializeCodeHolder(CodeHolder& code) {
//! Environment env = Environment::host();
+//! CpuFeatures cpuFeatures = CpuInfo::host().features();
//! uint64_t baseAddress = uint64_t(0x1234);
//!
//! // initialize CodeHolder with environment and custom base address.
-//! code.init(env, baseAddress);
+//! code.init(env, cpuFeatures, baseAddress);
//! }
//! ```
//!
@@ -1346,7 +1346,8 @@ namespace asmjit {
//! FileLogger logger(stdout); // Logger should always survive CodeHolder.
//!
//! CodeHolder code; // Holds code and relocation information.
-//! code.init(rt.environment()); // Initialize to the same arch as JIT runtime.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! code.setLogger(&logger); // Attach the `logger` to `code` holder.
//!
//! // ... code as usual, everything emitted will be logged to `stdout` ...
@@ -1369,7 +1370,8 @@ namespace asmjit {
//! StringLogger logger; // Logger should always survive CodeHolder.
//!
//! CodeHolder code; // Holds code and relocation information.
-//! code.init(rt.environment()); // Initialize to the same arch as JIT runtime.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! code.setLogger(&logger); // Attach the `logger` to `code` holder.
//!
//! // ... code as usual, logging will be concatenated to logger string ...
@@ -1494,7 +1496,7 @@ namespace asmjit {
//! using namespace asmjit;
//!
//! void formattingExample(BaseBuilder* builder) {
-//! FormatFlags formatFlags = FormatFlags::kNone;
+//! FormatOptions formatOptions {};
//!
//! // This also shows how temporary strings can be used.
//! StringTmp<512> sb;
@@ -1503,7 +1505,7 @@ namespace asmjit {
//! // were zero (no extra flags), and the builder instance, which we have
//! // provided. An overloaded version also exists, which accepts begin and
//! // and end nodes, which can be used to only format a range of nodes.
-//! Formatter::formatNodeList(sb, formatFlags, builder);
+//! Formatter::formatNodeList(sb, formatOptions, builder);
//!
//! // You can do whatever else with the string, it's always null terminated,
//! // so it can be passed to C functions like printf().
@@ -1560,7 +1562,7 @@ namespace asmjit {
//! MyErrorHandler myErrorHandler;
//! CodeHolder code;
//!
-//! code.init(rt.environment());
+//! code.init(rt.environment(), rt.cpuFeatures());
//! code.setErrorHandler(&myErrorHandler);
//!
//! x86::Assembler a(&code);
diff --git a/erts/emulator/asmjit/core/api-config.h b/erts/emulator/asmjit/core/api-config.h
index a0fb979eb3..60c9d5244e 100644
--- a/erts/emulator/asmjit/core/api-config.h
+++ b/erts/emulator/asmjit/core/api-config.h
@@ -13,7 +13,7 @@
//! \{
//! AsmJit library version in `(Major << 16) | (Minor << 8) | (Patch)` format.
-#define ASMJIT_LIBRARY_VERSION 0x010900 /* 1.9.0 */
+#define ASMJIT_LIBRARY_VERSION 0x010A00 /* 1.10.0 */
//! \def ASMJIT_ABI_NAMESPACE
//!
@@ -24,7 +24,7 @@
//! default, which makes it possible to use use multiple AsmJit libraries within a single project, totally controlled
//! by the users. This is useful especially in cases in which some of such library comes from a third party.
#ifndef ASMJIT_ABI_NAMESPACE
- #define ASMJIT_ABI_NAMESPACE _abi_1_9
+ #define ASMJIT_ABI_NAMESPACE _abi_1_10
#endif
//! \}
@@ -586,7 +586,7 @@ namespace asmjit {
//! \def ASMJIT_DEFINE_ENUM_COMPARE(T)
//!
//! Defines comparison operations for enumeration flags.
-#ifdef _DOXYGEN
+#if defined(_DOXYGEN) || (defined(_MSC_VER) && _MSC_VER <= 1900)
#define ASMJIT_DEFINE_ENUM_COMPARE(T)
#else
#define ASMJIT_DEFINE_ENUM_COMPARE(T) \
diff --git a/erts/emulator/asmjit/core/archtraits.h b/erts/emulator/asmjit/core/archtraits.h
index 4d05c11096..192a826e51 100644
--- a/erts/emulator/asmjit/core/archtraits.h
+++ b/erts/emulator/asmjit/core/archtraits.h
@@ -152,7 +152,7 @@ enum class InstHints : uint8_t {
//! No feature hints.
kNoHints = 0,
- //! Architecture supports a register swap by using a single instructio.
+ //! Architecture supports a register swap by using a single instruction.
kRegSwap = 0x01u,
//! Architecture provides push/pop instructions.
kPushPop = 0x02u
diff --git a/erts/emulator/asmjit/core/builder.cpp b/erts/emulator/asmjit/core/builder.cpp
index 5df243e7b8..77f94e7aeb 100644
--- a/erts/emulator/asmjit/core/builder.cpp
+++ b/erts/emulator/asmjit/core/builder.cpp
@@ -594,10 +594,12 @@ Error BaseBuilder::_emit(InstId instId, const Operand_& o0, const Operand_& o1,
Error err = _funcs.validate(arch(), BaseInst(instId, options, _extraReg), opArray, opCount, validationFlags);
if (ASMJIT_UNLIKELY(err)) {
- resetInstOptions();
- resetExtraReg();
- resetInlineComment();
+#ifndef ASMJIT_NO_LOGGING
+ return EmitterUtils::logInstructionFailed(this, err, instId, options, o0, o1, o2, opExt);
+#else
+ resetState();
return reportError(err);
+#endif
}
}
#endif
diff --git a/erts/emulator/asmjit/core/builder_p.h b/erts/emulator/asmjit/core/builder_p.h
new file mode 100644
index 0000000000..303358fc9b
--- /dev/null
+++ b/erts/emulator/asmjit/core/builder_p.h
@@ -0,0 +1,35 @@
+// This file is part of AsmJit project <https://asmjit.com>
+//
+// See asmjit.h or LICENSE.md for license and copyright information
+// SPDX-License-Identifier: Zlib
+
+#ifndef ASMJIT_CORE_BUILDER_P_H_INCLUDED
+#define ASMJIT_CORE_BUILDER_P_H_INCLUDED
+
+#include "../core/api-config.h"
+#ifndef ASMJIT_NO_BUILDER
+
+#include "../core/builder.h"
+
+ASMJIT_BEGIN_NAMESPACE
+
+//! \addtogroup asmjit_builder
+//! \{
+
+static inline void BaseBuilder_assignInlineComment(BaseBuilder* self, BaseNode* node, const char* comment) noexcept {
+ if (comment)
+ node->setInlineComment(static_cast<char*>(self->_dataZone.dup(comment, strlen(comment), true)));
+}
+
+static inline void BaseBuilder_assignInstState(BaseBuilder* self, InstNode* node, const BaseEmitter::State& state) noexcept {
+ node->setOptions(state.options);
+ node->setExtraReg(state.extraReg);
+ BaseBuilder_assignInlineComment(self, node, state.comment);
+}
+
+//! \}
+
+ASMJIT_END_NAMESPACE
+
+#endif // !ASMJIT_NO_BUILDER
+#endif // ASMJIT_CORE_BUILDER_P_H_INCLUDED
diff --git a/erts/emulator/asmjit/core/codebuffer.h b/erts/emulator/asmjit/core/codebuffer.h
index 4946e7a06a..2fe35a9772 100644
--- a/erts/emulator/asmjit/core/codebuffer.h
+++ b/erts/emulator/asmjit/core/codebuffer.h
@@ -44,7 +44,7 @@ struct CodeBuffer {
//! \name Overloaded Operators
//! \{
- //! Returns a referebce to the byte at the given `index`.
+ //! Returns a reference to the byte at the given `index`.
inline uint8_t& operator[](size_t index) noexcept {
ASMJIT_ASSERT(index < _size);
return _data[index];
diff --git a/erts/emulator/asmjit/core/codeholder.cpp b/erts/emulator/asmjit/core/codeholder.cpp
index cf763cfff1..3f7f5f5394 100644
--- a/erts/emulator/asmjit/core/codeholder.cpp
+++ b/erts/emulator/asmjit/core/codeholder.cpp
@@ -74,6 +74,7 @@ static void CodeHolder_resetInternal(CodeHolder* self, ResetPolicy resetPolicy)
// Reset everything into its construction state.
self->_environment.reset();
+ self->_cpuFeatures.reset();
self->_baseAddress = Globals::kNoBaseAddress;
self->_logger = nullptr;
self->_errorHandler = nullptr;
@@ -118,6 +119,7 @@ static void CodeHolder_onSettingsUpdated(CodeHolder* self) noexcept {
CodeHolder::CodeHolder(const Support::Temporary* temporary) noexcept
: _environment(),
+ _cpuFeatures{},
_baseAddress(Globals::kNoBaseAddress),
_logger(nullptr),
_errorHandler(nullptr),
@@ -143,6 +145,10 @@ inline void CodeHolder_setSectionDefaultName(
}
Error CodeHolder::init(const Environment& environment, uint64_t baseAddress) noexcept {
+ return init(environment, CpuFeatures{}, baseAddress);
+}
+
+Error CodeHolder::init(const Environment& environment, const CpuFeatures& cpuFeatures, uint64_t baseAddress) noexcept {
// Cannot reinitialize if it's locked or there is one or more emitter attached.
if (isInitialized())
return DebugUtils::errored(kErrorAlreadyInitialized);
@@ -172,6 +178,7 @@ Error CodeHolder::init(const Environment& environment, uint64_t baseAddress) noe
}
else {
_environment = environment;
+ _cpuFeatures = cpuFeatures;
_baseAddress = baseAddress;
return kErrorOk;
}
diff --git a/erts/emulator/asmjit/core/codeholder.h b/erts/emulator/asmjit/core/codeholder.h
index e3bd0d5923..d663d3e340 100644
--- a/erts/emulator/asmjit/core/codeholder.h
+++ b/erts/emulator/asmjit/core/codeholder.h
@@ -356,7 +356,7 @@ struct OffsetFormat {
//! Returns the size of the region/instruction where the offset is encoded.
inline uint32_t regionSize() const noexcept { return _regionSize; }
- //! Returns the the offset of the word relative to the start of the region where the offset is.
+ //! Returns the offset of the word relative to the start of the region where the offset is.
inline uint32_t valueOffset() const noexcept { return _valueOffset; }
//! Returns the size of the data-type (word) that contains the offset, in bytes.
@@ -640,6 +640,8 @@ public:
//! Environment information.
Environment _environment;
+ //! CPU features of the target architecture.
+ CpuFeatures _cpuFeatures;
//! Base address or \ref Globals::kNoBaseAddress.
uint64_t _baseAddress;
@@ -698,6 +700,8 @@ public:
//! Initializes CodeHolder to hold code described by the given `environment` and `baseAddress`.
ASMJIT_API Error init(const Environment& environment, uint64_t baseAddress = Globals::kNoBaseAddress) noexcept;
+ //! Initializes CodeHolder to hold code described by the given `environment`, `cpuFeatures`, and `baseAddress`.
+ ASMJIT_API Error init(const Environment& environment, const CpuFeatures& cpuFeatures, uint64_t baseAddress = Globals::kNoBaseAddress) noexcept;
//! Detaches all code-generators attached and resets the `CodeHolder`.
ASMJIT_API void reset(ResetPolicy resetPolicy = ResetPolicy::kSoft) noexcept;
@@ -736,6 +740,9 @@ public:
//! Returns the target sub-architecture.
inline SubArch subArch() const noexcept { return environment().subArch(); }
+ //! Returns the minimum CPU features of the target architecture.
+ inline const CpuFeatures& cpuFeatures() const noexcept { return _cpuFeatures; }
+
//! Tests whether a static base-address is set.
inline bool hasBaseAddress() const noexcept { return _baseAddress != Globals::kNoBaseAddress; }
//! Returns a static base-address or \ref Globals::kNoBaseAddress, if not set.
diff --git a/erts/emulator/asmjit/core/compiler.cpp b/erts/emulator/asmjit/core/compiler.cpp
index b1c6b803b2..ee959f74aa 100644
--- a/erts/emulator/asmjit/core/compiler.cpp
+++ b/erts/emulator/asmjit/core/compiler.cpp
@@ -7,6 +7,7 @@
#ifndef ASMJIT_NO_COMPILER
#include "../core/assembler.h"
+#include "../core/builder_p.h"
#include "../core/compiler.h"
#include "../core/cpuinfo.h"
#include "../core/logger.h"
@@ -103,9 +104,13 @@ Error BaseCompiler::newFuncNode(FuncNode** out, const FuncSignature& signature)
}
Error BaseCompiler::addFuncNode(FuncNode** out, const FuncSignature& signature) {
+ State state = _grabState();
+
ASMJIT_PROPAGATE(newFuncNode(out, signature));
ASMJIT_ASSUME(*out != nullptr);
+ BaseBuilder_assignInlineComment(this, *out, state.comment);
+
addFunc(*out);
return kErrorOk;
}
@@ -127,7 +132,13 @@ Error BaseCompiler::newFuncRetNode(FuncRetNode** out, const Operand_& o0, const
}
Error BaseCompiler::addFuncRetNode(FuncRetNode** out, const Operand_& o0, const Operand_& o1) {
+ State state = _grabState();
+
ASMJIT_PROPAGATE(newFuncRetNode(out, o0, o1));
+ ASMJIT_ASSUME(*out != nullptr);
+
+ BaseBuilder_assignInlineComment(this, *out, state.comment);
+
addNode(*out);
return kErrorOk;
}
@@ -146,6 +157,7 @@ FuncNode* BaseCompiler::addFunc(FuncNode* func) {
Error BaseCompiler::endFunc() {
FuncNode* func = _func;
+ resetState();
if (ASMJIT_UNLIKELY(!func))
return reportError(DebugUtils::errored(kErrorInvalidState));
@@ -196,7 +208,12 @@ Error BaseCompiler::newInvokeNode(InvokeNode** out, InstId instId, const Operand
}
Error BaseCompiler::addInvokeNode(InvokeNode** out, InstId instId, const Operand_& o0, const FuncSignature& signature) {
+ State state = _grabState();
+
ASMJIT_PROPAGATE(newInvokeNode(out, instId, o0, signature));
+ ASMJIT_ASSUME(*out != nullptr);
+
+ BaseBuilder_assignInstState(this, *out, state);
addNode(*out);
return kErrorOk;
}
@@ -481,20 +498,13 @@ Error BaseCompiler::newJumpNode(JumpNode** out, InstId instId, InstOptions instO
}
Error BaseCompiler::emitAnnotatedJump(InstId instId, const Operand_& o0, JumpAnnotation* annotation) {
- InstOptions options = instOptions() | forcedInstOptions();
- RegOnly extra = extraReg();
- const char* comment = inlineComment();
-
- resetInstOptions();
- resetInlineComment();
- resetExtraReg();
+ State state = _grabState();
JumpNode* node;
- ASMJIT_PROPAGATE(newJumpNode(&node, instId, options, o0, annotation));
+ ASMJIT_PROPAGATE(newJumpNode(&node, instId, state.options, o0, annotation));
- node->setExtraReg(extra);
- if (comment)
- node->setInlineComment(static_cast<char*>(_dataZone.dup(comment, strlen(comment), true)));
+ node->setExtraReg(state.extraReg);
+ BaseBuilder_assignInlineComment(this, node, state.comment);
addNode(node);
return kErrorOk;
diff --git a/erts/emulator/asmjit/core/cpuinfo.cpp b/erts/emulator/asmjit/core/cpuinfo.cpp
index 7bf7407f00..fb2acfc09b 100644
--- a/erts/emulator/asmjit/core/cpuinfo.cpp
+++ b/erts/emulator/asmjit/core/cpuinfo.cpp
@@ -295,6 +295,7 @@ static ASMJIT_FAVOR_SIZE void detectX86Cpu(CpuInfo& cpu) noexcept {
features.addIf(bitTest(regs.ecx, 27), CpuFeatures::X86::kMOVDIRI);
features.addIf(bitTest(regs.ecx, 28), CpuFeatures::X86::kMOVDIR64B);
features.addIf(bitTest(regs.ecx, 29), CpuFeatures::X86::kENQCMD);
+ features.addIf(bitTest(regs.edx, 4), CpuFeatures::X86::kFSRM);
features.addIf(bitTest(regs.edx, 5), CpuFeatures::X86::kUINTR);
features.addIf(bitTest(regs.edx, 14), CpuFeatures::X86::kSERIALIZE);
features.addIf(bitTest(regs.edx, 16), CpuFeatures::X86::kTSXLDTRK);
@@ -302,14 +303,14 @@ static ASMJIT_FAVOR_SIZE void detectX86Cpu(CpuInfo& cpu) noexcept {
features.addIf(bitTest(regs.edx, 20), CpuFeatures::X86::kCET_IBT);
// Detect 'TSX' - Requires at least one of `HLE` and `RTM` features.
- if (features.hasHLE() || features.hasRTM())
+ if (features.hasHLE() || features.hasRTM()) {
features.add(CpuFeatures::X86::kTSX);
+ }
- // Detect 'AVX2' - Requires AVX as well.
- if (bitTest(regs.ebx, 5) && features.hasAVX())
+ if (bitTest(regs.ebx, 5) && features.hasAVX()) {
features.add(CpuFeatures::X86::kAVX2);
+ }
- // Detect 'AVX512'.
if (avx512EnabledByOS && bitTest(regs.ebx, 16)) {
features.add(CpuFeatures::X86::kAVX512_F);
@@ -331,7 +332,6 @@ static ASMJIT_FAVOR_SIZE void detectX86Cpu(CpuInfo& cpu) noexcept {
features.addIf(bitTest(regs.edx, 23), CpuFeatures::X86::kAVX512_FP16);
}
- // Detect 'AMX'.
if (amxEnabledByOS) {
features.addIf(bitTest(regs.edx, 22), CpuFeatures::X86::kAMX_BF16);
features.addIf(bitTest(regs.edx, 24), CpuFeatures::X86::kAMX_TILE);
@@ -342,12 +342,35 @@ static ASMJIT_FAVOR_SIZE void detectX86Cpu(CpuInfo& cpu) noexcept {
// CPUID EAX=7 ECX=1
// -----------------
- if (features.hasAVX512_F() && maxSubLeafId_0x7 >= 1) {
+ if (maxSubLeafId_0x7 >= 1) {
cpuidQuery(&regs, 0x7, 1);
- features.addIf(bitTest(regs.eax, 3), CpuFeatures::X86::kAVX_VNNI);
- features.addIf(bitTest(regs.eax, 5), CpuFeatures::X86::kAVX512_BF16);
+ features.addIf(bitTest(regs.eax, 3), CpuFeatures::X86::kRAO_INT);
+ features.addIf(bitTest(regs.eax, 7), CpuFeatures::X86::kCMPCCXADD);
+ features.addIf(bitTest(regs.eax, 10), CpuFeatures::X86::kFZRM);
+ features.addIf(bitTest(regs.eax, 11), CpuFeatures::X86::kFSRS);
+ features.addIf(bitTest(regs.eax, 12), CpuFeatures::X86::kFSRC);
+ features.addIf(bitTest(regs.eax, 19), CpuFeatures::X86::kWRMSRNS);
features.addIf(bitTest(regs.eax, 22), CpuFeatures::X86::kHRESET);
+ features.addIf(bitTest(regs.eax, 26), CpuFeatures::X86::kLAM);
+ features.addIf(bitTest(regs.eax, 27), CpuFeatures::X86::kMSRLIST);
+ features.addIf(bitTest(regs.edx, 14), CpuFeatures::X86::kPREFETCHI);
+ features.addIf(bitTest(regs.edx, 18), CpuFeatures::X86::kCET_SSS);
+
+ if (features.hasAVX2()) {
+ features.addIf(bitTest(regs.eax, 4), CpuFeatures::X86::kAVX_VNNI);
+ features.addIf(bitTest(regs.eax, 23), CpuFeatures::X86::kAVX_IFMA);
+ features.addIf(bitTest(regs.edx, 4), CpuFeatures::X86::kAVX_VNNI_INT8);
+ features.addIf(bitTest(regs.edx, 5), CpuFeatures::X86::kAVX_NE_CONVERT);
+ }
+
+ if (features.hasAVX512_F()) {
+ features.addIf(bitTest(regs.eax, 5), CpuFeatures::X86::kAVX512_BF16);
+ }
+
+ if (amxEnabledByOS) {
+ features.addIf(bitTest(regs.eax, 21), CpuFeatures::X86::kAMX_FP16);
+ }
}
// CPUID EAX=13 ECX=0
diff --git a/erts/emulator/asmjit/core/cpuinfo.h b/erts/emulator/asmjit/core/cpuinfo.h
index 4af5c3a82f..f5437d4cc0 100644
--- a/erts/emulator/asmjit/core/cpuinfo.h
+++ b/erts/emulator/asmjit/core/cpuinfo.h
@@ -175,6 +175,7 @@ public:
kAESNI, //!< CPU has AESNI (AES encode/decode instructions).
kALTMOVCR8, //!< CPU has LOCK MOV R<->CR0 (supports `MOV R<->CR8` via `LOCK MOV R<->CR0` in 32-bit mode) [AMD].
kAMX_BF16, //!< CPU has AMX_BF16 (advanced matrix extensions - BF16 instructions).
+ kAMX_FP16, //!< CPU has AMX_FP16 (advanced matrix extensions - FP16 instructions).
kAMX_INT8, //!< CPU has AMX_INT8 (advanced matrix extensions - INT8 instructions).
kAMX_TILE, //!< CPU has AMX_TILE (advanced matrix extensions).
kAVX, //!< CPU has AVX (advanced vector extensions).
@@ -197,17 +198,22 @@ public:
kAVX512_VNNI, //!< CPU has AVX512_VNNI (vector neural network instructions).
kAVX512_VP2INTERSECT, //!< CPU has AVX512_VP2INTERSECT
kAVX512_VPOPCNTDQ, //!< CPU has AVX512_VPOPCNTDQ (VPOPCNT[D|Q] instructions).
+ kAVX_IFMA, //!< CPU has AVX_IFMA (VEX encoding of vpmadd52huq/vpmadd52luq).
+ kAVX_NE_CONVERT, //!< CPU has AVX_NE_CONVERT.
kAVX_VNNI, //!< CPU has AVX_VNNI (VEX encoding of vpdpbusd/vpdpbusds/vpdpwssd/vpdpwssds).
+ kAVX_VNNI_INT8, //!< CPU has AVX_VNNI_INT8.
kBMI, //!< CPU has BMI (bit manipulation instructions #1).
kBMI2, //!< CPU has BMI2 (bit manipulation instructions #2).
kCET_IBT, //!< CPU has CET-IBT (indirect branch tracking).
kCET_SS, //!< CPU has CET-SS.
+ kCET_SSS, //!< CPU has CET-SSS.
kCLDEMOTE, //!< CPU has CLDEMOTE (cache line demote).
kCLFLUSH, //!< CPU has CLFUSH (Cache Line flush).
kCLFLUSHOPT, //!< CPU has CLFUSHOPT (Cache Line flush - optimized).
kCLWB, //!< CPU has CLWB.
kCLZERO, //!< CPU has CLZERO.
kCMOV, //!< CPU has CMOV (CMOV and FCMOV instructions).
+ kCMPCCXADD, //!< CPU has CMPCCXADD.
kCMPXCHG16B, //!< CPU has CMPXCHG16B (compare-exchange 16 bytes) [X86_64].
kCMPXCHG8B, //!< CPU has CMPXCHG8B (compare-exchange 8 bytes).
kENCLV, //!< CPU has ENCLV.
@@ -218,14 +224,19 @@ public:
kFMA4, //!< CPU has FMA4 (fused-multiply-add 4 operand form).
kFPU, //!< CPU has FPU (FPU support).
kFSGSBASE, //!< CPU has FSGSBASE.
+ kFSRM, //!< CPU has FSRM (fast short REP MOVSB).
+ kFSRC, //!< CPU has FSRC (fast short REP CMPSB|SCASB).
+ kFSRS, //!< CPU has FSRS (fast short REP STOSB)
kFXSR, //!< CPU has FXSR (FXSAVE/FXRSTOR instructions).
kFXSROPT, //!< CPU has FXSROTP (FXSAVE/FXRSTOR is optimized).
+ kFZRM, //!< CPU has FZRM (fast zero-length REP MOVSB).
kGEODE, //!< CPU has GEODE extensions (3DNOW additions).
kGFNI, //!< CPU has GFNI (Galois field instructions).
kHLE, //!< CPU has HLE.
kHRESET, //!< CPU has HRESET.
kI486, //!< CPU has I486 features (I486+ support).
kLAHFSAHF, //!< CPU has LAHF/SAHF (LAHF/SAHF in 64-bit mode) [X86_64].
+ kLAM, //!< CPU has LAM (linear address masking) [X86_64].
kLWP, //!< CPU has LWP (lightweight profiling) [AMD].
kLZCNT, //!< CPU has LZCNT (LZCNT instruction).
kMCOMMIT, //!< CPU has MCOMMIT (MCOMMIT instruction).
@@ -238,15 +249,18 @@ public:
kMOVDIRI, //!< CPU has MOVDIRI (move dword/qword as direct store).
kMPX, //!< CPU has MPX (memory protection extensions).
kMSR, //!< CPU has MSR (RDMSR/WRMSR instructions).
+ kMSRLIST, //!< CPU has MSRLIST.
kMSSE, //!< CPU has MSSE (misaligned SSE support).
kOSXSAVE, //!< CPU has OSXSAVE (XSAVE enabled by OS).
kOSPKE, //!< CPU has OSPKE (PKE enabled by OS).
kPCLMULQDQ, //!< CPU has PCLMULQDQ (packed carry-less multiplication).
kPCONFIG, //!< CPU has PCONFIG (PCONFIG instruction).
kPOPCNT, //!< CPU has POPCNT (POPCNT instruction).
+ kPREFETCHI, //!< CPU has PREFETCHI.
kPREFETCHW, //!< CPU has PREFETCHW.
kPREFETCHWT1, //!< CPU has PREFETCHWT1.
kPTWRITE, //!< CPU has PTWRITE.
+ kRAO_INT, //!< CPU has RAO_INT.
kRDPID, //!< CPU has RDPID.
kRDPRU, //!< CPU has RDPRU.
kRDRAND, //!< CPU has RDRAND.
@@ -278,6 +292,7 @@ public:
kVPCLMULQDQ, //!< CPU has VPCLMULQDQ (vector PCLMULQDQ 256|512-bit support).
kWAITPKG, //!< CPU has WAITPKG (UMONITOR, UMWAIT, TPAUSE).
kWBNOINVD, //!< CPU has WBNOINVD.
+ kWRMSRNS, //!< CPU has WRMSRNS.
kXOP, //!< CPU has XOP (XOP instructions) [AMD].
kXSAVE, //!< CPU has XSAVE.
kXSAVEC, //!< CPU has XSAVEC.
@@ -299,6 +314,7 @@ public:
ASMJIT_X86_FEATURE(AESNI)
ASMJIT_X86_FEATURE(ALTMOVCR8)
ASMJIT_X86_FEATURE(AMX_BF16)
+ ASMJIT_X86_FEATURE(AMX_FP16)
ASMJIT_X86_FEATURE(AMX_INT8)
ASMJIT_X86_FEATURE(AMX_TILE)
ASMJIT_X86_FEATURE(AVX)
@@ -321,11 +337,15 @@ public:
ASMJIT_X86_FEATURE(AVX512_VNNI)
ASMJIT_X86_FEATURE(AVX512_VP2INTERSECT)
ASMJIT_X86_FEATURE(AVX512_VPOPCNTDQ)
+ ASMJIT_X86_FEATURE(AVX_IFMA)
+ ASMJIT_X86_FEATURE(AVX_NE_CONVERT)
ASMJIT_X86_FEATURE(AVX_VNNI)
+ ASMJIT_X86_FEATURE(AVX_VNNI_INT8)
ASMJIT_X86_FEATURE(BMI)
ASMJIT_X86_FEATURE(BMI2)
ASMJIT_X86_FEATURE(CET_IBT)
ASMJIT_X86_FEATURE(CET_SS)
+ ASMJIT_X86_FEATURE(CET_SSS)
ASMJIT_X86_FEATURE(CLDEMOTE)
ASMJIT_X86_FEATURE(CLFLUSH)
ASMJIT_X86_FEATURE(CLFLUSHOPT)
@@ -342,14 +362,19 @@ public:
ASMJIT_X86_FEATURE(FMA4)
ASMJIT_X86_FEATURE(FPU)
ASMJIT_X86_FEATURE(FSGSBASE)
+ ASMJIT_X86_FEATURE(FSRM)
+ ASMJIT_X86_FEATURE(FSRC)
+ ASMJIT_X86_FEATURE(FSRS)
ASMJIT_X86_FEATURE(FXSR)
ASMJIT_X86_FEATURE(FXSROPT)
+ ASMJIT_X86_FEATURE(FZRM)
ASMJIT_X86_FEATURE(GEODE)
ASMJIT_X86_FEATURE(GFNI)
ASMJIT_X86_FEATURE(HLE)
ASMJIT_X86_FEATURE(HRESET)
ASMJIT_X86_FEATURE(I486)
ASMJIT_X86_FEATURE(LAHFSAHF)
+ ASMJIT_X86_FEATURE(LAM)
ASMJIT_X86_FEATURE(LWP)
ASMJIT_X86_FEATURE(LZCNT)
ASMJIT_X86_FEATURE(MCOMMIT)
@@ -362,15 +387,18 @@ public:
ASMJIT_X86_FEATURE(MOVDIRI)
ASMJIT_X86_FEATURE(MPX)
ASMJIT_X86_FEATURE(MSR)
+ ASMJIT_X86_FEATURE(MSRLIST)
ASMJIT_X86_FEATURE(MSSE)
ASMJIT_X86_FEATURE(OSXSAVE)
ASMJIT_X86_FEATURE(OSPKE)
ASMJIT_X86_FEATURE(PCLMULQDQ)
ASMJIT_X86_FEATURE(PCONFIG)
ASMJIT_X86_FEATURE(POPCNT)
+ ASMJIT_X86_FEATURE(PREFETCHI)
ASMJIT_X86_FEATURE(PREFETCHW)
ASMJIT_X86_FEATURE(PREFETCHWT1)
ASMJIT_X86_FEATURE(PTWRITE)
+ ASMJIT_X86_FEATURE(RAO_INT)
ASMJIT_X86_FEATURE(RDPID)
ASMJIT_X86_FEATURE(RDPRU)
ASMJIT_X86_FEATURE(RDRAND)
@@ -402,6 +430,7 @@ public:
ASMJIT_X86_FEATURE(VPCLMULQDQ)
ASMJIT_X86_FEATURE(WAITPKG)
ASMJIT_X86_FEATURE(WBNOINVD)
+ ASMJIT_X86_FEATURE(WRMSRNS)
ASMJIT_X86_FEATURE(XOP)
ASMJIT_X86_FEATURE(XSAVE)
ASMJIT_X86_FEATURE(XSAVEC)
diff --git a/erts/emulator/asmjit/core/emitter.h b/erts/emulator/asmjit/core/emitter.h
index b8afd6b8e0..6499c071bd 100644
--- a/erts/emulator/asmjit/core/emitter.h
+++ b/erts/emulator/asmjit/core/emitter.h
@@ -233,6 +233,13 @@ public:
//! Native GP register signature and signature related information.
OperandSignature _gpSignature {};
+ //! Emitter state that can be used to specify options and inline comment of a next node or instruction.
+ struct State {
+ InstOptions options;
+ RegOnly extraReg;
+ const char* comment;
+ };
+
//! Next instruction options (affects the next instruction).
InstOptions _instOptions = InstOptions::kNone;
//! Extra register (op-mask {k} on AVX-512) (affects the next instruction).
@@ -530,6 +537,23 @@ public:
//! \}
+ //! \name Emitter State
+ //! \{
+
+ inline void resetState() noexcept {
+ resetInstOptions();
+ resetExtraReg();
+ resetInlineComment();
+ }
+
+ inline State _grabState() noexcept {
+ State s{_instOptions | _forcedInstOptions, _extraReg, _inlineComment};
+ resetState();
+ return s;
+ }
+
+ //! \}
+
//! \name Sections
//! \{
diff --git a/erts/emulator/asmjit/core/emitterutils.cpp b/erts/emulator/asmjit/core/emitterutils.cpp
index f36a1b3774..d0a687244b 100644
--- a/erts/emulator/asmjit/core/emitterutils.cpp
+++ b/erts/emulator/asmjit/core/emitterutils.cpp
@@ -96,7 +96,7 @@ void logInstructionEmitted(
}
Error logInstructionFailed(
- BaseAssembler* self,
+ BaseEmitter* self,
Error err,
InstId instId,
InstOptions options,
@@ -109,16 +109,14 @@ Error logInstructionFailed(
Operand_ opArray[Globals::kMaxOpCount];
opArrayFromEmitArgs(opArray, o0, o1, o2, opExt);
- self->_funcs.formatInstruction(sb, FormatFlags::kNone, self, self->arch(), BaseInst(instId, options, self->extraReg()), opArray, Globals::kMaxOpCount);
+ self->_funcs.formatInstruction(sb, FormatFlags::kRegType, self, self->arch(), BaseInst(instId, options, self->extraReg()), opArray, Globals::kMaxOpCount);
if (self->inlineComment()) {
sb.append(" ; ");
sb.append(self->inlineComment());
}
- self->resetInstOptions();
- self->resetExtraReg();
- self->resetInlineComment();
+ self->resetState();
return self->reportError(err, sb.data());
}
diff --git a/erts/emulator/asmjit/core/emitterutils_p.h b/erts/emulator/asmjit/core/emitterutils_p.h
index b7610e7026..8b6e1e0547 100644
--- a/erts/emulator/asmjit/core/emitterutils_p.h
+++ b/erts/emulator/asmjit/core/emitterutils_p.h
@@ -23,7 +23,7 @@ namespace EmitterUtils {
//! Default paddings used by Emitter utils and Formatter.
-static constexpr Operand noExt[3];
+static constexpr Operand noExt[3] = { {}, {}, {} };
enum kOpIndex : uint32_t {
kOp3 = 0,
@@ -71,7 +71,7 @@ void logInstructionEmitted(
uint32_t relSize, uint32_t immSize, uint8_t* afterCursor);
Error logInstructionFailed(
- BaseAssembler* self,
+ BaseEmitter* self,
Error err,
InstId instId,
InstOptions options,
diff --git a/erts/emulator/asmjit/core/errorhandler.h b/erts/emulator/asmjit/core/errorhandler.h
index 5151d43304..3b0096f05b 100644
--- a/erts/emulator/asmjit/core/errorhandler.h
+++ b/erts/emulator/asmjit/core/errorhandler.h
@@ -61,7 +61,7 @@ class BaseEmitter;
//! SimpleErrorHandler eh;
//!
//! CodeHolder code;
-//! code.init(rt.environment());
+//! code.init(rt.environment(), rt.cpuFeatures());
//! code.setErrorHandler(&eh);
//!
//! // Try to emit instruction that doesn't exist.
@@ -117,7 +117,7 @@ class BaseEmitter;
//! ThrowableErrorHandler eh;
//!
//! CodeHolder code;
-//! code.init(rt.environment());
+//! code.init(rt.environment(), rt.cpuFeatures());
//! code.setErrorHandler(&eh);
//!
//! x86::Assembler a(&code);
@@ -166,7 +166,7 @@ class BaseEmitter;
//! LongJmpErrorHandler eh;
//!
//! CodeHolder code;
-//! code.init(rt.rt.environment());
+//! code.init(rt.environment(), rt.cpuFeatures());
//! code.setErrorHandler(&eh);
//!
//! x86::Assembler a(&code);
diff --git a/erts/emulator/asmjit/core/formatter.cpp b/erts/emulator/asmjit/core/formatter.cpp
index 1c4b7b6c6f..56e9692662 100644
--- a/erts/emulator/asmjit/core/formatter.cpp
+++ b/erts/emulator/asmjit/core/formatter.cpp
@@ -143,7 +143,7 @@ Error formatLabel(
}
if (le->type() == LabelType::kAnonymous)
- ASMJIT_PROPAGATE(sb.append("L%u@", labelId));
+ ASMJIT_PROPAGATE(sb.appendFormat("L%u@", labelId));
return sb.append(le->name());
}
else {
@@ -471,8 +471,7 @@ Error formatNode(
case NodeType::kComment: {
const CommentNode* commentNode = node->as<CommentNode>();
- ASMJIT_PROPAGATE(sb.appendFormat("; %s", commentNode->inlineComment()));
- break;
+ return sb.appendFormat("; %s", commentNode->inlineComment());
}
case NodeType::kSentinel: {
diff --git a/erts/emulator/asmjit/core/formatter.h b/erts/emulator/asmjit/core/formatter.h
index d7a4b93476..0ee6bde107 100644
--- a/erts/emulator/asmjit/core/formatter.h
+++ b/erts/emulator/asmjit/core/formatter.h
@@ -37,7 +37,9 @@ enum class FormatFlags : uint32_t {
//! Show casts between virtual register types (Compiler output).
kRegCasts = 0x00000010u,
//! Show positions associated with nodes (Compiler output).
- kPositions = 0x00000020u
+ kPositions = 0x00000020u,
+ //! Always format a register type (Compiler output).
+ kRegType = 0x00000040u
};
ASMJIT_DEFINE_ENUM_FLAGS(FormatFlags)
diff --git a/erts/emulator/asmjit/core/func.h b/erts/emulator/asmjit/core/func.h
index 8ecf1487ad..d09955573a 100644
--- a/erts/emulator/asmjit/core/func.h
+++ b/erts/emulator/asmjit/core/func.h
@@ -1120,6 +1120,15 @@ public:
inline uint32_t redZoneSize() const noexcept { return _redZoneSize; }
//! Returns the size of 'SpillZone'.
inline uint32_t spillZoneSize() const noexcept { return _spillZoneSize; }
+
+ //! Resets the size of red zone, which would disable it entirely.
+ //!
+ //! \note Red zone is currently only used by an AMD64 SystemV calling convention, which expects 128
+ //! bytes of stack to be accessible below stack pointer. These bytes are then accessible within the
+ //! function and Compiler can use this space as a spill area. However, sometimes it's better to
+ //! disallow the use of red zone in case that a user wants to use this stack for a custom purpose.
+ inline void resetRedZone() noexcept { _redZoneSize = 0; }
+
//! Returns natural stack alignment (guaranteed stack alignment upon entry).
inline uint32_t naturalStackAlignment() const noexcept { return _naturalStackAlignment; }
//! Returns natural stack alignment (guaranteed stack alignment upon entry).
@@ -1127,7 +1136,7 @@ public:
//! Tests whether the callee must adjust SP before returning (X86-STDCALL only)
inline bool hasCalleeStackCleanup() const noexcept { return _calleeStackCleanup != 0; }
- //! Returns home many bytes of the stack the the callee must adjust before returning (X86-STDCALL only)
+ //! Returns home many bytes of the stack the callee must adjust before returning (X86-STDCALL only)
inline uint32_t calleeStackCleanup() const noexcept { return _calleeStackCleanup; }
//! Returns call stack alignment.
diff --git a/erts/emulator/asmjit/core/inst.h b/erts/emulator/asmjit/core/inst.h
index 2310631561..643678971a 100644
--- a/erts/emulator/asmjit/core/inst.h
+++ b/erts/emulator/asmjit/core/inst.h
@@ -312,6 +312,10 @@ public:
return id | (uint32_t(cc) << Support::ConstCTZ<uint32_t(InstIdParts::kARM_Cond)>::value);
}
+ static inline constexpr InstId extractRealId(uint32_t id) noexcept {
+ return id & uint32_t(InstIdParts::kRealId);
+ }
+
static inline constexpr arm::CondCode extractARMCondCode(uint32_t id) noexcept {
return (arm::CondCode)((uint32_t(id) & uint32_t(InstIdParts::kARM_Cond)) >> Support::ConstCTZ<uint32_t(InstIdParts::kARM_Cond)>::value);
}
@@ -614,13 +618,25 @@ struct OpRWInfo {
//! \}
};
+//! Flags used by \ref InstRWInfo.
+enum class InstRWFlags : uint32_t {
+ //! No flags.
+ kNone = 0x00000000u,
+
+ //! Describes a move operation.
+ //!
+ //! This flag is used by RA to eliminate moves that are guaranteed to be moves only.
+ kMovOp = 0x00000001u
+};
+ASMJIT_DEFINE_ENUM_FLAGS(InstRWFlags)
+
//! Read/Write information of an instruction.
struct InstRWInfo {
//! \name Members
//! \{
//! Instruction flags (there are no flags at the moment, this field is reserved).
- uint32_t _instFlags;
+ InstRWFlags _instFlags;
//! CPU flags read.
CpuRWFlags _readFlags;
//! CPU flags written.
@@ -646,6 +662,20 @@ struct InstRWInfo {
//! \}
+ //! \name Instruction Flags
+ //! \{
+
+ //! Returns flags associated with the instruction, see \ref InstRWFlags.
+ inline InstRWFlags instFlags() const noexcept { return _instFlags; }
+
+ //! Tests whether the instruction flags contain `flag`.
+ inline bool hasInstFlag(InstRWFlags flag) const noexcept { return Support::test(_instFlags, flag); }
+
+ //! Tests whether the instruction flags contain \ref InstRWFlags::kMovOp.
+ inline bool isMovOp() const noexcept { return hasInstFlag(InstRWFlags::kMovOp); }
+
+ //! \}
+
//! \name CPU Flags Information
//! \{
diff --git a/erts/emulator/asmjit/core/jitallocator.cpp b/erts/emulator/asmjit/core/jitallocator.cpp
index 19fbe4b233..2a4305f069 100644
--- a/erts/emulator/asmjit/core/jitallocator.cpp
+++ b/erts/emulator/asmjit/core/jitallocator.cpp
@@ -430,6 +430,15 @@ static inline JitAllocatorPrivateImpl* JitAllocatorImpl_new(const JitAllocator::
if (ASMJIT_UNLIKELY(!p))
return nullptr;
+ VirtMem::HardenedRuntimeInfo hardenedRtInfo = VirtMem::hardenedRuntimeInfo();
+ if (Support::test(hardenedRtInfo.flags, VirtMem::HardenedRuntimeFlags::kEnabled)) {
+ // If we are running within a hardened environment (mapping RWX is not allowed) then we have to use dual mapping
+ // or other runtime capabilities like Apple specific MAP_JIT. There is no point in not enabling these as otherwise
+ // the allocation would fail and JitAllocator would not be able to allocate memory.
+ if (!Support::test(hardenedRtInfo.flags, VirtMem::HardenedRuntimeFlags::kMapJit))
+ options |= JitAllocatorOptions::kUseDualMapping;
+ }
+
JitAllocatorPool* pools = reinterpret_cast<JitAllocatorPool*>((uint8_t*)p + sizeof(JitAllocatorPrivateImpl));
JitAllocatorPrivateImpl* impl = new(p) JitAllocatorPrivateImpl(pools, poolCount);
@@ -497,7 +506,7 @@ ASMJIT_FAVOR_SPEED static void JitAllocatorImpl_fillPattern(void* mem, uint32_t
//
// NOTE: The block doesn't have `kFlagEmpty` flag set, because the new block
// is only allocated when it's actually needed, so it would be cleared anyway.
-static JitAllocatorBlock* JitAllocatorImpl_newBlock(JitAllocatorPrivateImpl* impl, JitAllocatorPool* pool, size_t blockSize) noexcept {
+static Error JitAllocatorImpl_newBlock(JitAllocatorPrivateImpl* impl, JitAllocatorBlock** dst, JitAllocatorPool* pool, size_t blockSize) noexcept {
using Support::BitWord;
using Support::kBitWordSizeInBits;
@@ -532,7 +541,10 @@ static JitAllocatorBlock* JitAllocatorImpl_newBlock(JitAllocatorPrivateImpl* imp
if (block)
::free(block);
- return nullptr;
+ if (err)
+ return err;
+ else
+ return kErrorOutOfMemory;
}
// Fill the memory if the secure mode is enabled.
@@ -542,7 +554,8 @@ static JitAllocatorBlock* JitAllocatorImpl_newBlock(JitAllocatorPrivateImpl* imp
}
memset(bitWords, 0, size_t(numBitWords) * 2 * sizeof(BitWord));
- return new(block) JitAllocatorBlock(pool, virtMem, blockSize, blockFlags, bitWords, bitWords + numBitWords, areaSize);
+ *dst = new(block) JitAllocatorBlock(pool, virtMem, blockSize, blockFlags, bitWords, bitWords + numBitWords, areaSize);
+ return kErrorOk;
}
static void JitAllocatorImpl_deleteBlock(JitAllocatorPrivateImpl* impl, JitAllocatorBlock* block) noexcept {
@@ -789,12 +802,9 @@ Error JitAllocator::alloc(void** rxPtrOut, void** rwPtrOut, size_t size) noexcep
if (ASMJIT_UNLIKELY(!blockSize))
return DebugUtils::errored(kErrorOutOfMemory);
- block = JitAllocatorImpl_newBlock(impl, pool, blockSize);
+ ASMJIT_PROPAGATE(JitAllocatorImpl_newBlock(impl, &block, pool, blockSize));
areaIndex = 0;
- if (ASMJIT_UNLIKELY(!block))
- return DebugUtils::errored(kErrorOutOfMemory);
-
JitAllocatorImpl_insertBlock(impl, block);
block->_searchStart = areaSize;
block->_largestUnusedArea = block->areaSize() - areaSize;
@@ -889,8 +899,12 @@ Error JitAllocator::shrink(void* rxPtr, size_t newSize) noexcept {
// The first bit representing the allocated area and its size.
uint32_t areaStart = uint32_t(offset >> pool->granularityLog2);
- uint32_t areaEnd = uint32_t(Support::bitVectorIndexOf(block->_stopBitVector, areaStart, true)) + 1;
+ bool isUsed = Support::bitVectorGetBit(block->_usedBitVector, areaStart);
+ if (ASMJIT_UNLIKELY(!isUsed))
+ return DebugUtils::errored(kErrorInvalidArgument);
+
+ uint32_t areaEnd = uint32_t(Support::bitVectorIndexOf(block->_stopBitVector, areaStart, true)) + 1;
uint32_t areaPrevSize = areaEnd - areaStart;
uint32_t areaShrunkSize = pool->areaSizeFromByteSize(newSize);
@@ -902,13 +916,55 @@ Error JitAllocator::shrink(void* rxPtr, size_t newSize) noexcept {
block->markShrunkArea(areaStart + areaShrunkSize, areaEnd);
// Fill released memory if the secure mode is enabled.
- if (Support::test(impl->options, JitAllocatorOptions::kFillUnusedMemory))
- JitAllocatorImpl_fillPattern(block->rwPtr() + (areaStart + areaShrunkSize) * pool->granularity, fillPattern(), areaDiff * pool->granularity);
+ if (Support::test(impl->options, JitAllocatorOptions::kFillUnusedMemory)) {
+ uint8_t* spanPtr = block->rwPtr() + (areaStart + areaShrunkSize) * pool->granularity;
+ size_t spanSize = areaDiff * pool->granularity;
+
+ VirtMem::ProtectJitReadWriteScope scope(spanPtr, spanSize);
+ JitAllocatorImpl_fillPattern(spanPtr, fillPattern(), spanSize);
+ }
}
return kErrorOk;
}
+Error JitAllocator::query(void* rxPtr, void** rxPtrOut, void** rwPtrOut, size_t* sizeOut) const noexcept {
+ *rxPtrOut = nullptr;
+ *rwPtrOut = nullptr;
+ *sizeOut = 0u;
+
+ if (ASMJIT_UNLIKELY(_impl == &JitAllocatorImpl_none))
+ return DebugUtils::errored(kErrorNotInitialized);
+
+ JitAllocatorPrivateImpl* impl = static_cast<JitAllocatorPrivateImpl*>(_impl);
+ LockGuard guard(impl->lock);
+ JitAllocatorBlock* block = impl->tree.get(static_cast<uint8_t*>(rxPtr));
+
+ if (ASMJIT_UNLIKELY(!block))
+ return DebugUtils::errored(kErrorInvalidArgument);
+
+ // Offset relative to the start of the block.
+ JitAllocatorPool* pool = block->pool();
+ size_t offset = (size_t)((uint8_t*)rxPtr - block->rxPtr());
+
+ // The first bit representing the allocated area and its size.
+ uint32_t areaStart = uint32_t(offset >> pool->granularityLog2);
+
+ bool isUsed = Support::bitVectorGetBit(block->_usedBitVector, areaStart);
+ if (ASMJIT_UNLIKELY(!isUsed))
+ return DebugUtils::errored(kErrorInvalidArgument);
+
+ uint32_t areaEnd = uint32_t(Support::bitVectorIndexOf(block->_stopBitVector, areaStart, true)) + 1;
+ size_t byteOffset = pool->byteSizeFromAreaSize(areaStart);
+ size_t byteSize = pool->byteSizeFromAreaSize(areaEnd - areaStart);
+
+ *rxPtrOut = static_cast<uint8_t*>(block->_mapping.rx) + byteOffset;
+ *rwPtrOut = static_cast<uint8_t*>(block->_mapping.rw) + byteOffset;
+ *sizeOut = byteSize;
+
+ return kErrorOk;
+}
+
// JitAllocator - Tests
// ====================
@@ -963,6 +1019,34 @@ public:
uint64_t _state[2];
};
+namespace JitAllocatorUtils {
+ static void fillPattern64(void* p_, uint64_t pattern, size_t sizeInBytes) noexcept {
+ uint64_t* p = static_cast<uint64_t*>(p_);
+ size_t n = sizeInBytes / 8u;
+
+ for (size_t i = 0; i < n; i++)
+ p[i] = pattern;
+ }
+
+ static bool verifyPattern64(const void* p_, uint64_t pattern, size_t sizeInBytes) noexcept {
+ const uint64_t* p = static_cast<const uint64_t*>(p_);
+ size_t n = sizeInBytes / 8u;
+
+ for (size_t i = 0; i < n; i++) {
+ if (p[i] != pattern) {
+ INFO("Pattern verification failed at 0x%p [%zu * 8]: value(0x%016llX) != expected(0x%016llX)",
+ p,
+ i,
+ (unsigned long long)p[i],
+ (unsigned long long)pattern);
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
+
// Helper class to verify that JitAllocator doesn't return addresses that overlap.
class JitAllocatorWrapper {
public:
@@ -980,9 +1064,19 @@ public:
class Record : public ZoneTreeNodeT<Record>,
public Range {
public:
- inline Record(uint8_t* addr, size_t size)
+ //! Read/write address, in case this is a dual mapping.
+ void* _rw;
+ //! Describes a pattern used to fill the allocated memory.
+ uint64_t pattern;
+
+ inline Record(void* rx, void* rw, size_t size, uint64_t pattern)
: ZoneTreeNodeT<Record>(),
- Range(addr, size) {}
+ Range(static_cast<uint8_t*>(rx), size),
+ _rw(rw),
+ pattern(pattern) {}
+
+ inline void* rx() const noexcept { return addr; }
+ inline void* rw() const noexcept { return _rw; }
inline bool operator<(const Record& other) const noexcept { return addr < other.addr; }
inline bool operator>(const Record& other) const noexcept { return addr > other.addr; }
@@ -995,14 +1089,16 @@ public:
ZoneAllocator _heap;
ZoneTree<Record> _records;
JitAllocator _allocator;
+ Random _rng;
explicit JitAllocatorWrapper(const JitAllocator::CreateParams* params) noexcept
: _zone(1024 * 1024),
_heap(&_zone),
- _allocator(params) {}
+ _allocator(params),
+ _rng(0x123456789u) {}
- void _insert(void* p_, size_t size) noexcept {
- uint8_t* p = static_cast<uint8_t*>(p_);
+ void _insert(void* pRX, void* pRW, size_t size) noexcept {
+ uint8_t* p = static_cast<uint8_t*>(pRX);
uint8_t* pEnd = p + size - 1;
Record* record;
@@ -1015,9 +1111,18 @@ public:
if (record)
EXPECT(record == nullptr, "Address [%p:%p] collides with a newly allocated [%p:%p]\n", record->addr, record->addr + record->size, p, p + size);
- record = _heap.newT<Record>(p, size);
+ uint64_t pattern = _rng.nextUInt64();
+ record = _heap.newT<Record>(pRX, pRW, size, pattern);
EXPECT(record != nullptr, "Out of memory, cannot allocate 'Record'");
+ {
+ VirtMem::ProtectJitReadWriteScope scope(pRW, size);
+ JitAllocatorUtils::fillPattern64(pRW, pattern, size);
+ }
+
+ VirtMem::flushInstructionCache(pRX, size);
+ EXPECT(JitAllocatorUtils::verifyPattern64(pRX, pattern, size) == true);
+
_records.insert(record);
}
@@ -1025,6 +1130,9 @@ public:
Record* record = _records.get(static_cast<uint8_t*>(p));
EXPECT(record != nullptr, "Address [%p] doesn't exist\n", p);
+ EXPECT(JitAllocatorUtils::verifyPattern64(record->rx(), record->pattern, record->size) == true);
+ EXPECT(JitAllocatorUtils::verifyPattern64(record->rw(), record->pattern, record->size) == true);
+
_records.remove(record);
_heap.release(record, sizeof(Record));
}
@@ -1036,7 +1144,7 @@ public:
Error err = _allocator.alloc(&rxPtr, &rwPtr, size);
EXPECT(err == kErrorOk, "JitAllocator failed to allocate %zu bytes\n", size);
- _insert(rxPtr, size);
+ _insert(rxPtr, rwPtr, size);
return rxPtr;
}
@@ -1100,8 +1208,8 @@ static void BitVectorRangeIterator_testRandom(Random& rnd, size_t count) noexcep
}
}
-UNIT(jit_allocator) {
- size_t kCount = BrokenAPI::hasArg("--quick") ? 1000 : 100000;
+static void test_jit_allocator_alloc_release() noexcept {
+ size_t kCount = BrokenAPI::hasArg("--quick") ? 20000 : 100000;
struct TestParams {
const char* name;
@@ -1235,8 +1343,34 @@ UNIT(jit_allocator) {
::free(ptrArray);
}
}
-#endif
+
+static void test_jit_allocator_query() noexcept {
+ JitAllocator allocator;
+
+ void* rxPtr = nullptr;
+ void* rwPtr = nullptr;
+ size_t size = 100;
+
+ EXPECT(allocator.alloc(&rxPtr, &rwPtr, size) == kErrorOk);
+ EXPECT(rxPtr != nullptr);
+ EXPECT(rwPtr != nullptr);
+
+ void* rxPtrQueried = nullptr;
+ void* rwPtrQueried = nullptr;
+ size_t sizeQueried;
+
+ EXPECT(allocator.query(rxPtr, &rxPtrQueried, &rwPtrQueried, &sizeQueried) == kErrorOk);
+ EXPECT(rxPtrQueried == rxPtr);
+ EXPECT(rwPtrQueried == rwPtr);
+ EXPECT(sizeQueried == Support::alignUp(size, allocator.granularity()));
+}
+
+UNIT(jit_allocator) {
+ test_jit_allocator_alloc_release();
+ test_jit_allocator_query();
+}
+#endif // ASMJIT_TEST
ASMJIT_END_NAMESPACE
-#endif
+#endif // !ASMJIT_NO_JIT
diff --git a/erts/emulator/asmjit/core/jitallocator.h b/erts/emulator/asmjit/core/jitallocator.h
index e8fe69519e..40af92924c 100644
--- a/erts/emulator/asmjit/core/jitallocator.h
+++ b/erts/emulator/asmjit/core/jitallocator.h
@@ -26,6 +26,10 @@ enum class JitAllocatorOptions : uint32_t {
//! The first buffer has read and execute permissions and the second buffer has read+write permissions.
//!
//! See \ref VirtMem::allocDualMapping() for more details about this feature.
+ //!
+ //! \remarks Dual mapping would be automatically turned on by \ref JitAllocator in case of hardened runtime that
+ //! enforces `W^X` policy, so specifying this flag is essentually forcing to use dual mapped pages even when RWX
+ //! pages can be allocated and dual mapping is not necessary.
kUseDualMapping = 0x00000001u,
//! Enables the use of multiple pools with increasing granularity instead of a single pool. This flag would enable
diff --git a/erts/emulator/asmjit/core/jitruntime.cpp b/erts/emulator/asmjit/core/jitruntime.cpp
index 491c2040fb..814353f4b5 100644
--- a/erts/emulator/asmjit/core/jitruntime.cpp
+++ b/erts/emulator/asmjit/core/jitruntime.cpp
@@ -15,6 +15,7 @@ JitRuntime::JitRuntime(const JitAllocator::CreateParams* params) noexcept
: _allocator(params) {
_environment = Environment::host();
_environment.setObjectFormat(ObjectFormat::kJIT);
+ _cpuFeatures = CpuInfo::host().features();
}
JitRuntime::~JitRuntime() noexcept {}
@@ -46,9 +47,6 @@ Error JitRuntime::_add(void** dst, CodeHolder* code) noexcept {
if (codeSize < estimatedCodeSize)
_allocator.shrink(rx, codeSize);
- if (codeSize < estimatedCodeSize)
- _allocator.shrink(rx, codeSize);
-
{
VirtMem::ProtectJitReadWriteScope rwScope(rx, codeSize);
diff --git a/erts/emulator/asmjit/core/operand.h b/erts/emulator/asmjit/core/operand.h
index 2f81a217f1..634acec85f 100644
--- a/erts/emulator/asmjit/core/operand.h
+++ b/erts/emulator/asmjit/core/operand.h
@@ -994,9 +994,9 @@ public:
}
//! Tests whether the `op` is a general purpose register of the given `id`.
- static inline bool isGp(const Operand_& op, uint32_t id) noexcept { return isGp(op) & (op.id() == id); }
+ static inline bool isGp(const Operand_& op, uint32_t id) noexcept { return bool(unsigned(isGp(op)) & unsigned(op.id() == id)); }
//! Tests whether the `op` is a vector register of the given `id`.
- static inline bool isVec(const Operand_& op, uint32_t id) noexcept { return isVec(op) & (op.id() == id); }
+ static inline bool isVec(const Operand_& op, uint32_t id) noexcept { return bool(unsigned(isVec(op)) & unsigned(op.id() == id)); }
//! \}
};
@@ -1082,19 +1082,19 @@ template<> \
struct RegTraits<REG_TYPE> { \
typedef REG RegT; \
\
- enum : uint32_t { \
- kValid = uint32_t(true), \
- kCount = uint32_t(COUNT), \
- kType = uint32_t(REG_TYPE), \
- kGroup = uint32_t(GROUP), \
- kSize = uint32_t(SIZE), \
- kTypeId = uint32_t(TYPE_ID), \
+ static constexpr uint32_t kValid = 1; \
+ static constexpr uint32_t kCount = COUNT; \
+ static constexpr RegType kType = REG_TYPE; \
+ static constexpr RegGroup kGroup = GROUP; \
+ static constexpr uint32_t kSize = SIZE; \
+ static constexpr TypeId kTypeId = TYPE_ID; \
+ \
+ static constexpr uint32_t kSignature = \
+ (OperandSignature::fromOpType(OperandType::kReg) | \
+ OperandSignature::fromRegType(kType) | \
+ OperandSignature::fromRegGroup(kGroup) | \
+ OperandSignature::fromSize(kSize)).bits(); \
\
- kSignature = (OperandSignature::fromOpType(OperandType::kReg) | \
- OperandSignature::fromRegType(REG_TYPE) | \
- OperandSignature::fromRegGroup(GROUP) | \
- OperandSignature::fromSize(kSize)).bits(), \
- }; \
}
//! Adds constructors and member functions to a class that implements abstract register. Abstract register is register
@@ -1135,12 +1135,10 @@ public: \
//! signature.
#define ASMJIT_DEFINE_FINAL_REG(REG, BASE, TRAITS) \
public: \
- enum : uint32_t { \
- kThisType = TRAITS::kType, \
- kThisGroup = TRAITS::kGroup, \
- kThisSize = TRAITS::kSize, \
- kSignature = TRAITS::kSignature \
- }; \
+ static constexpr RegType kThisType = TRAITS::kType; \
+ static constexpr RegGroup kThisGroup = TRAITS::kGroup; \
+ static constexpr uint32_t kThisSize = TRAITS::kSize; \
+ static constexpr uint32_t kSignature = TRAITS::kSignature; \
\
ASMJIT_DEFINE_ABSTRACT_REG(REG, BASE) \
\
diff --git a/erts/emulator/asmjit/core/osutils.cpp b/erts/emulator/asmjit/core/osutils.cpp
index fa900bfbb4..18aa2a0c66 100644
--- a/erts/emulator/asmjit/core/osutils.cpp
+++ b/erts/emulator/asmjit/core/osutils.cpp
@@ -76,7 +76,7 @@ uint32_t OSUtils::getTickCount() noexcept {
uint64_t t = (uint64_t(ts.tv_sec ) * 1000u) + (uint64_t(ts.tv_nsec) / 1000000u);
return uint32_t(t & 0xFFFFFFFFu);
#else
- #pragma message("asmjit::OSUtils::getTickCount() doesn't have implementation for the target OS.")
+ #pragma message("[asmjit] OSUtils::getTickCount() doesn't have implementation for the target OS.")
return 0;
#endif
}
diff --git a/erts/emulator/asmjit/core/raassignment_p.h b/erts/emulator/asmjit/core/raassignment_p.h
index 22a97e2b36..5418329311 100644
--- a/erts/emulator/asmjit/core/raassignment_p.h
+++ b/erts/emulator/asmjit/core/raassignment_p.h
@@ -82,6 +82,12 @@ public:
size_t size = sizeOf(count);
memcpy(this, other, size);
}
+
+ inline void unassign(RegGroup group, uint32_t physId, uint32_t indexInWorkIds) noexcept {
+ assigned.clear(group, Support::bitMask(physId));
+ dirty.clear(group, Support::bitMask(physId));
+ workIds[indexInWorkIds] = kWorkNone;
+ }
};
struct WorkToPhysMap {
@@ -304,6 +310,28 @@ public:
_physToWorkIds.swap(other._physToWorkIds);
}
+ inline void assignWorkIdsFromPhysIds() noexcept {
+ memset(_workToPhysMap, uint8_t(BaseReg::kIdBad), WorkToPhysMap::sizeOf(_layout.workCount));
+
+ for (RegGroup group : RegGroupVirtValues{}) {
+ uint32_t physBaseIndex = _layout.physIndex[group];
+ Support::BitWordIterator<RegMask> it(_physToWorkMap->assigned[group]);
+
+ while (it.hasNext()) {
+ uint32_t physId = it.next();
+ uint32_t workId = _physToWorkMap->workIds[physBaseIndex + physId];
+
+ ASMJIT_ASSERT(workId != kWorkNone);
+ _workToPhysMap->physIds[workId] = uint8_t(physId);
+ }
+ }
+ }
+
+ inline void copyFrom(const PhysToWorkMap* physToWorkMap) noexcept {
+ memcpy(_physToWorkMap, physToWorkMap, PhysToWorkMap::sizeOf(_layout.physTotal));
+ assignWorkIdsFromPhysIds();
+ }
+
inline void copyFrom(const PhysToWorkMap* physToWorkMap, const WorkToPhysMap* workToPhysMap) noexcept {
memcpy(_physToWorkMap, physToWorkMap, PhysToWorkMap::sizeOf(_layout.physTotal));
memcpy(_workToPhysMap, workToPhysMap, WorkToPhysMap::sizeOf(_layout.workCount));
diff --git a/erts/emulator/asmjit/core/rabuilders_p.h b/erts/emulator/asmjit/core/rabuilders_p.h
index 1b763030c4..9d9b4282d2 100644
--- a/erts/emulator/asmjit/core/rabuilders_p.h
+++ b/erts/emulator/asmjit/core/rabuilders_p.h
@@ -401,7 +401,7 @@ public:
if (node->type() == NodeType::kSentinel) {
if (node == _funcNode->endNode()) {
// Make sure we didn't flow here if this is the end of the function sentinel.
- if (ASMJIT_UNLIKELY(_curBlock))
+ if (ASMJIT_UNLIKELY(_curBlock && _hasCode))
return DebugUtils::errored(kErrorInvalidState);
break;
}
diff --git a/erts/emulator/asmjit/core/radefs_p.h b/erts/emulator/asmjit/core/radefs_p.h
index 426ac2926d..091b682367 100644
--- a/erts/emulator/asmjit/core/radefs_p.h
+++ b/erts/emulator/asmjit/core/radefs_p.h
@@ -271,8 +271,12 @@ struct RARegMask {
}
template<class Operator>
- inline void op(RegGroup group, uint32_t input) noexcept {
- _masks[group] = Operator::op(_masks[group], input);
+ inline void op(RegGroup group, RegMask mask) noexcept {
+ _masks[group] = Operator::op(_masks[group], mask);
+ }
+
+ inline void clear(RegGroup group, RegMask mask) noexcept {
+ _masks[group] = _masks[group] & ~mask;
}
//! \}
@@ -662,7 +666,7 @@ struct LiveRegData {
uint32_t id;
inline explicit LiveRegData(uint32_t id = BaseReg::kIdBad) noexcept : id(id) {}
- inline LiveRegData(const LiveRegData& other) noexcept : id(other.id) {}
+ inline LiveRegData(const LiveRegData& other) noexcept = default;
inline void init(const LiveRegData& other) noexcept { id = other.id; }
diff --git a/erts/emulator/asmjit/core/ralocal.cpp b/erts/emulator/asmjit/core/ralocal.cpp
index 54bc524bf3..b4d92446b3 100644
--- a/erts/emulator/asmjit/core/ralocal.cpp
+++ b/erts/emulator/asmjit/core/ralocal.cpp
@@ -38,7 +38,9 @@ Error RALocalAllocator::init() noexcept {
physToWorkMap = _pass->newPhysToWorkMap();
workToPhysMap = _pass->newWorkToPhysMap();
- if (!physToWorkMap || !workToPhysMap)
+ _tmpWorkToPhysMap = _pass->newWorkToPhysMap();
+
+ if (!physToWorkMap || !workToPhysMap || !_tmpWorkToPhysMap)
return DebugUtils::errored(kErrorOutOfMemory);
_tmpAssignment.initLayout(_pass->_physRegCount, _pass->workRegs());
@@ -122,26 +124,18 @@ Error RALocalAllocator::makeInitialAssignment() noexcept {
return kErrorOk;
}
-Error RALocalAllocator::replaceAssignment(
- const PhysToWorkMap* physToWorkMap,
- const WorkToPhysMap* workToPhysMap) noexcept {
-
- _curAssignment.copyFrom(physToWorkMap, workToPhysMap);
+Error RALocalAllocator::replaceAssignment(const PhysToWorkMap* physToWorkMap) noexcept {
+ _curAssignment.copyFrom(physToWorkMap);
return kErrorOk;
}
-Error RALocalAllocator::switchToAssignment(
- PhysToWorkMap* dstPhysToWorkMap,
- WorkToPhysMap* dstWorkToPhysMap,
- const ZoneBitVector& liveIn,
- bool dstReadOnly,
- bool tryMode) noexcept {
-
+Error RALocalAllocator::switchToAssignment(PhysToWorkMap* dstPhysToWorkMap, const ZoneBitVector& liveIn, bool dstReadOnly, bool tryMode) noexcept {
RAAssignment dst;
RAAssignment& cur = _curAssignment;
dst.initLayout(_pass->_physRegCount, _pass->workRegs());
- dst.initMaps(dstPhysToWorkMap, dstWorkToPhysMap);
+ dst.initMaps(dstPhysToWorkMap, _tmpWorkToPhysMap);
+ dst.assignWorkIdsFromPhysIds();
if (tryMode)
return kErrorOk;
@@ -329,24 +323,27 @@ Cleared:
if (!tryMode) {
// Here is a code that dumps the conflicting part if something fails here:
- // if (!dst.equals(cur)) {
- // uint32_t physTotal = dst._layout.physTotal;
- // uint32_t workCount = dst._layout.workCount;
+ // if (!dst.equals(cur)) {
+ // uint32_t physTotal = dst._layout.physTotal;
+ // uint32_t workCount = dst._layout.workCount;
+ //
+ // fprintf(stderr, "Dirty DST=0x%08X CUR=0x%08X\n", dst.dirty(RegGroup::kGp), cur.dirty(RegGroup::kGp));
+ // fprintf(stderr, "Assigned DST=0x%08X CUR=0x%08X\n", dst.assigned(RegGroup::kGp), cur.assigned(RegGroup::kGp));
//
- // for (uint32_t physId = 0; physId < physTotal; physId++) {
- // uint32_t dstWorkId = dst._physToWorkMap->workIds[physId];
- // uint32_t curWorkId = cur._physToWorkMap->workIds[physId];
- // if (dstWorkId != curWorkId)
- // fprintf(stderr, "[PhysIdWork] PhysId=%u WorkId[DST(%u) != CUR(%u)]\n", physId, dstWorkId, curWorkId);
- // }
+ // for (uint32_t physId = 0; physId < physTotal; physId++) {
+ // uint32_t dstWorkId = dst._physToWorkMap->workIds[physId];
+ // uint32_t curWorkId = cur._physToWorkMap->workIds[physId];
+ // if (dstWorkId != curWorkId)
+ // fprintf(stderr, "[PhysIdWork] PhysId=%u WorkId[DST(%u) != CUR(%u)]\n", physId, dstWorkId, curWorkId);
+ // }
//
- // for (uint32_t workId = 0; workId < workCount; workId++) {
- // uint32_t dstPhysId = dst._workToPhysMap->physIds[workId];
- // uint32_t curPhysId = cur._workToPhysMap->physIds[workId];
- // if (dstPhysId != curPhysId)
- // fprintf(stderr, "[WorkToPhys] WorkId=%u PhysId[DST(%u) != CUR(%u)]\n", workId, dstPhysId, curPhysId);
- // }
+ // for (uint32_t workId = 0; workId < workCount; workId++) {
+ // uint32_t dstPhysId = dst._workToPhysMap->physIds[workId];
+ // uint32_t curPhysId = cur._workToPhysMap->physIds[workId];
+ // if (dstPhysId != curPhysId)
+ // fprintf(stderr, "[WorkToPhys] WorkId=%u PhysId[DST(%u) != CUR(%u)]\n", workId, dstPhysId, curPhysId);
// }
+ // }
ASMJIT_ASSERT(dst.equals(cur));
}
@@ -839,6 +836,34 @@ Error RALocalAllocator::allocInst(InstNode* node) noexcept {
// STEP 9
// ------
//
+ // Vector registers can be cloberred partially by invoke - find if that's the case and clobber when necessary.
+
+ if (node->isInvoke() && group == RegGroup::kVec) {
+ const InvokeNode* invokeNode = node->as<InvokeNode>();
+
+ RegMask maybeClobberedRegs = invokeNode->detail().callConv().preservedRegs(group) & _curAssignment.assigned(group);
+ if (maybeClobberedRegs) {
+ uint32_t saveRestoreVecSize = invokeNode->detail().callConv().saveRestoreRegSize(group);
+ Support::BitWordIterator<RegMask> it(maybeClobberedRegs);
+
+ do {
+ uint32_t physId = it.next();
+ uint32_t workId = _curAssignment.physToWorkId(group, physId);
+
+ RAWorkReg* workReg = workRegById(workId);
+ uint32_t virtSize = workReg->virtReg()->virtSize();
+
+ if (virtSize > saveRestoreVecSize) {
+ ASMJIT_PROPAGATE(onSpillReg(group, workId, physId));
+ }
+
+ } while (it.hasNext());
+ }
+ }
+
+ // STEP 10
+ // -------
+ //
// Assign OUT registers.
if (outPending) {
@@ -981,12 +1006,7 @@ Error RALocalAllocator::allocBranch(InstNode* node, RABlock* target, RABlock* co
// Use TryMode of `switchToAssignment()` if possible.
if (target->hasEntryAssignment()) {
- ASMJIT_PROPAGATE(switchToAssignment(
- target->entryPhysToWorkMap(),
- target->entryWorkToPhysMap(),
- target->liveIn(),
- target->isAllocated(),
- true));
+ ASMJIT_PROPAGATE(switchToAssignment(target->entryPhysToWorkMap(), target->liveIn(), target->isAllocated(), true));
}
ASMJIT_PROPAGATE(allocInst(node));
@@ -997,12 +1017,7 @@ Error RALocalAllocator::allocBranch(InstNode* node, RABlock* target, RABlock* co
BaseNode* prevCursor = _cc->setCursor(injectionPoint);
_tmpAssignment.copyFrom(_curAssignment);
- ASMJIT_PROPAGATE(switchToAssignment(
- target->entryPhysToWorkMap(),
- target->entryWorkToPhysMap(),
- target->liveIn(),
- target->isAllocated(),
- false));
+ ASMJIT_PROPAGATE(switchToAssignment(target->entryPhysToWorkMap(), target->liveIn(), target->isAllocated(), false));
BaseNode* curCursor = _cc->cursor();
if (curCursor != injectionPoint) {
@@ -1060,7 +1075,6 @@ Error RALocalAllocator::allocJumpTable(InstNode* node, const RABlocks& targets,
if (!sharedAssignment.empty()) {
ASMJIT_PROPAGATE(switchToAssignment(
sharedAssignment.physToWorkMap(),
- sharedAssignment.workToPhysMap(),
sharedAssignment.liveIn(),
true, // Read-only.
false // Try-mode.
diff --git a/erts/emulator/asmjit/core/ralocal_p.h b/erts/emulator/asmjit/core/ralocal_p.h
index 05467c5b26..b40e867427 100644
--- a/erts/emulator/asmjit/core/ralocal_p.h
+++ b/erts/emulator/asmjit/core/ralocal_p.h
@@ -57,6 +57,9 @@ public:
//! TiedReg's total counter.
RARegCount _tiedCount;
+ //! Temporary workToPhysMap that can be used freely by the allocator.
+ WorkToPhysMap* _tmpWorkToPhysMap;
+
//! \name Construction & Destruction
//! \{
@@ -113,9 +116,7 @@ public:
Error makeInitialAssignment() noexcept;
- Error replaceAssignment(
- const PhysToWorkMap* physToWorkMap,
- const WorkToPhysMap* workToPhysMap) noexcept;
+ Error replaceAssignment(const PhysToWorkMap* physToWorkMap) noexcept;
//! Switch to the given assignment by reassigning all register and emitting code that reassigns them.
//! This is always used to switch to a previously stored assignment.
@@ -123,12 +124,7 @@ public:
//! If `tryMode` is true then the final assignment doesn't have to be exactly same as specified by `dstPhysToWorkMap`
//! and `dstWorkToPhysMap`. This mode is only used before conditional jumps that already have assignment to generate
//! a code sequence that is always executed regardless of the flow.
- Error switchToAssignment(
- PhysToWorkMap* dstPhysToWorkMap,
- WorkToPhysMap* dstWorkToPhysMap,
- const ZoneBitVector& liveIn,
- bool dstReadOnly,
- bool tryMode) noexcept;
+ Error switchToAssignment(PhysToWorkMap* dstPhysToWorkMap, const ZoneBitVector& liveIn, bool dstReadOnly, bool tryMode) noexcept;
inline Error spillRegsBeforeEntry(RABlock* block) noexcept {
return spillScratchGpRegsBeforeEntry(block->entryScratchGpRegs());
diff --git a/erts/emulator/asmjit/core/rapass.cpp b/erts/emulator/asmjit/core/rapass.cpp
index 79709f69d6..29b7dea9f8 100644
--- a/erts/emulator/asmjit/core/rapass.cpp
+++ b/erts/emulator/asmjit/core/rapass.cpp
@@ -114,11 +114,14 @@ Error BaseRAPass::runOnFunction(Zone* zone, Logger* logger, FuncNode* func) {
#ifndef ASMJIT_NO_LOGGING
_logger = logger;
_formatOptions.reset();
- _diagnosticOptions = DiagnosticOptions::kNone;
+ _diagnosticOptions = _cb->diagnosticOptions();
if (logger) {
_formatOptions = logger->options();
- _diagnosticOptions = _cb->diagnosticOptions();
+ }
+ else {
+ _diagnosticOptions &= ~(DiagnosticOptions::kRADebugCFG |
+ DiagnosticOptions::kRADebugUnreachable);
}
#else
DebugUtils::unused(logger);
@@ -328,9 +331,14 @@ Error BaseRAPass::initSharedAssignments(const ZoneVector<uint32_t>& sharedAssign
RABlock* firstSuccessor = successors[0];
// NOTE: Shared assignments connect all possible successors so we only need the first to propagate exit scratch
// GP registers.
- ASMJIT_ASSERT(firstSuccessor->hasSharedAssignmentId());
- RASharedAssignment& sa = _sharedAssignments[firstSuccessor->sharedAssignmentId()];
- sa.addEntryScratchGpRegs(block->exitScratchGpRegs());
+ if (firstSuccessor->hasSharedAssignmentId()) {
+ RASharedAssignment& sa = _sharedAssignments[firstSuccessor->sharedAssignmentId()];
+ sa.addEntryScratchGpRegs(block->exitScratchGpRegs());
+ }
+ else {
+ // This is only allowed if there is a single successor - in that case shared assignment is not necessary.
+ ASMJIT_ASSERT(successors.size() == 1u);
+ }
}
}
if (block->hasSharedAssignmentId()) {
@@ -1483,18 +1491,12 @@ Error BaseRAPass::runLocalAllocator() noexcept {
cc()->_setCursor(unconditionalJump ? prev->prev() : prev);
if (consecutive->hasEntryAssignment()) {
- ASMJIT_PROPAGATE(
- lra.switchToAssignment(
- consecutive->entryPhysToWorkMap(),
- consecutive->entryWorkToPhysMap(),
- consecutive->liveIn(),
- consecutive->isAllocated(),
- false));
+ ASMJIT_PROPAGATE(lra.switchToAssignment(consecutive->entryPhysToWorkMap(), consecutive->liveIn(), consecutive->isAllocated(), false));
}
else {
ASMJIT_PROPAGATE(lra.spillRegsBeforeEntry(consecutive));
ASMJIT_PROPAGATE(setBlockEntryAssignment(consecutive, block, lra._curAssignment));
- lra._curAssignment.copyFrom(consecutive->entryPhysToWorkMap(), consecutive->entryWorkToPhysMap());
+ lra._curAssignment.copyFrom(consecutive->entryPhysToWorkMap());
}
}
@@ -1526,7 +1528,7 @@ Error BaseRAPass::runLocalAllocator() noexcept {
}
// If we switched to some block we have to update the local allocator.
- lra.replaceAssignment(block->entryPhysToWorkMap(), block->entryWorkToPhysMap());
+ lra.replaceAssignment(block->entryPhysToWorkMap());
}
_clobberedRegs.op<Support::Or>(lra._clobberedRegs);
@@ -1546,12 +1548,10 @@ Error BaseRAPass::setBlockEntryAssignment(RABlock* block, const RABlock* fromBlo
}
PhysToWorkMap* physToWorkMap = clonePhysToWorkMap(fromAssignment.physToWorkMap());
- WorkToPhysMap* workToPhysMap = cloneWorkToPhysMap(fromAssignment.workToPhysMap());
-
- if (ASMJIT_UNLIKELY(!physToWorkMap || !workToPhysMap))
+ if (ASMJIT_UNLIKELY(!physToWorkMap))
return DebugUtils::errored(kErrorOutOfMemory);
- block->setEntryAssignment(physToWorkMap, workToPhysMap);
+ block->setEntryAssignment(physToWorkMap);
// True if this is the first (entry) block, nothing to do in this case.
if (block == fromBlock) {
@@ -1562,10 +1562,6 @@ Error BaseRAPass::setBlockEntryAssignment(RABlock* block, const RABlock* fromBlo
return kErrorOk;
}
- RAAssignment as;
- as.initLayout(_physRegCount, workRegs());
- as.initMaps(physToWorkMap, workToPhysMap);
-
const ZoneBitVector& liveOut = fromBlock->liveOut();
const ZoneBitVector& liveIn = block->liveIn();
@@ -1578,94 +1574,85 @@ Error BaseRAPass::setBlockEntryAssignment(RABlock* block, const RABlock* fromBlo
RAWorkReg* workReg = workRegById(workId);
RegGroup group = workReg->group();
- uint32_t physId = as.workToPhysId(group, workId);
+ uint32_t physId = fromAssignment.workToPhysId(group, workId);
if (physId != RAAssignment::kPhysNone)
- as.unassign(group, workId, physId);
+ physToWorkMap->unassign(group, physId, _physRegIndex.get(group) + physId);
}
}
- return blockEntryAssigned(as);
+ return blockEntryAssigned(physToWorkMap);
}
Error BaseRAPass::setSharedAssignment(uint32_t sharedAssignmentId, const RAAssignment& fromAssignment) noexcept {
ASMJIT_ASSERT(_sharedAssignments[sharedAssignmentId].empty());
PhysToWorkMap* physToWorkMap = clonePhysToWorkMap(fromAssignment.physToWorkMap());
- WorkToPhysMap* workToPhysMap = cloneWorkToPhysMap(fromAssignment.workToPhysMap());
-
- if (ASMJIT_UNLIKELY(!physToWorkMap || !workToPhysMap))
+ if (ASMJIT_UNLIKELY(!physToWorkMap))
return DebugUtils::errored(kErrorOutOfMemory);
- _sharedAssignments[sharedAssignmentId].assignMaps(physToWorkMap, workToPhysMap);
+ _sharedAssignments[sharedAssignmentId].assignPhysToWorkMap(physToWorkMap);
+
ZoneBitVector& sharedLiveIn = _sharedAssignments[sharedAssignmentId]._liveIn;
ASMJIT_PROPAGATE(sharedLiveIn.resize(allocator(), workRegCount()));
- RAAssignment as;
- as.initLayout(_physRegCount, workRegs());
-
Support::Array<uint32_t, Globals::kNumVirtGroups> sharedAssigned {};
-
for (RABlock* block : blocks()) {
if (block->sharedAssignmentId() == sharedAssignmentId) {
ASMJIT_ASSERT(!block->hasEntryAssignment());
PhysToWorkMap* entryPhysToWorkMap = clonePhysToWorkMap(fromAssignment.physToWorkMap());
- WorkToPhysMap* entryWorkToPhysMap = cloneWorkToPhysMap(fromAssignment.workToPhysMap());
-
- if (ASMJIT_UNLIKELY(!entryPhysToWorkMap || !entryWorkToPhysMap))
+ if (ASMJIT_UNLIKELY(!entryPhysToWorkMap))
return DebugUtils::errored(kErrorOutOfMemory);
- block->setEntryAssignment(entryPhysToWorkMap, entryWorkToPhysMap);
- as.initMaps(entryPhysToWorkMap, entryWorkToPhysMap);
+ block->setEntryAssignment(entryPhysToWorkMap);
const ZoneBitVector& liveIn = block->liveIn();
sharedLiveIn.or_(liveIn);
for (RegGroup group : RegGroupVirtValues{}) {
sharedAssigned[group] |= entryPhysToWorkMap->assigned[group];
+
+ uint32_t physBaseIndex = _physRegIndex.get(group);
Support::BitWordIterator<RegMask> it(entryPhysToWorkMap->assigned[group]);
while (it.hasNext()) {
uint32_t physId = it.next();
- uint32_t workId = as.physToWorkId(group, physId);
+ uint32_t workId = entryPhysToWorkMap->workIds[physBaseIndex + physId];
if (!liveIn.bitAt(workId))
- as.unassign(group, workId, physId);
+ entryPhysToWorkMap->unassign(group, physId, physBaseIndex + physId);
}
}
}
}
- {
- as.initMaps(physToWorkMap, workToPhysMap);
-
- for (RegGroup group : RegGroupVirtValues{}) {
- Support::BitWordIterator<RegMask> it(_availableRegs[group] & ~sharedAssigned[group]);
+ for (RegGroup group : RegGroupVirtValues{}) {
+ uint32_t physBaseIndex = _physRegIndex.get(group);
+ Support::BitWordIterator<RegMask> it(_availableRegs[group] & ~sharedAssigned[group]);
- while (it.hasNext()) {
- uint32_t physId = it.next();
- if (as.isPhysAssigned(group, physId)) {
- uint32_t workId = as.physToWorkId(group, physId);
- as.unassign(group, workId, physId);
- }
- }
+ while (it.hasNext()) {
+ uint32_t physId = it.next();
+ if (Support::bitTest(physToWorkMap->assigned[group], physId))
+ physToWorkMap->unassign(group, physId, physBaseIndex + physId);
}
}
- return blockEntryAssigned(as);
+ return blockEntryAssigned(physToWorkMap);
}
-Error BaseRAPass::blockEntryAssigned(const RAAssignment& as) noexcept {
+Error BaseRAPass::blockEntryAssigned(const PhysToWorkMap* physToWorkMap) noexcept {
// Complex allocation strategy requires to record register assignments upon block entry (or per shared state).
for (RegGroup group : RegGroupVirtValues{}) {
if (!_strategy[group].isComplex())
continue;
- Support::BitWordIterator<RegMask> it(as.assigned(group));
+ uint32_t physBaseIndex = _physRegIndex[group];
+ Support::BitWordIterator<RegMask> it(physToWorkMap->assigned[group]);
+
while (it.hasNext()) {
uint32_t physId = it.next();
- uint32_t workId = as.physToWorkId(group, physId);
+ uint32_t workId = physToWorkMap->workIds[physBaseIndex + physId];
RAWorkReg* workReg = workRegById(workId);
workReg->addAllocatedMask(Support::bitMask(physId));
diff --git a/erts/emulator/asmjit/core/rapass_p.h b/erts/emulator/asmjit/core/rapass_p.h
index 098c5c9e1d..9473829366 100644
--- a/erts/emulator/asmjit/core/rapass_p.h
+++ b/erts/emulator/asmjit/core/rapass_p.h
@@ -129,10 +129,8 @@ public:
//! Scratch registers used at exit, by a terminator instruction.
RegMask _exitScratchGpRegs = 0;
- //! Register assignment (PhysToWork) on entry.
+ //! Register assignment on entry.
PhysToWorkMap* _entryPhysToWorkMap = nullptr;
- //! Register assignment (WorkToPhys) on entry.
- WorkToPhysMap* _entryWorkToPhysMap = nullptr;
//! \}
@@ -247,13 +245,8 @@ public:
}
inline bool hasEntryAssignment() const noexcept { return _entryPhysToWorkMap != nullptr; }
- inline WorkToPhysMap* entryWorkToPhysMap() const noexcept { return _entryWorkToPhysMap; }
inline PhysToWorkMap* entryPhysToWorkMap() const noexcept { return _entryPhysToWorkMap; }
-
- inline void setEntryAssignment(PhysToWorkMap* physToWorkMap, WorkToPhysMap* workToPhysMap) noexcept {
- _entryPhysToWorkMap = physToWorkMap;
- _entryWorkToPhysMap = workToPhysMap;
- }
+ inline void setEntryAssignment(PhysToWorkMap* physToWorkMap) noexcept { _entryPhysToWorkMap = physToWorkMap; }
//! \}
@@ -283,6 +276,8 @@ public:
//! Parent block.
RABlock* _block;
+ //! Instruction RW flags.
+ InstRWFlags _instRWFlags;
//! Aggregated RATiedFlags from all operands & instruction specific flags.
RATiedFlags _flags;
//! Total count of RATiedReg's.
@@ -305,9 +300,10 @@ public:
//! \name Construction & Destruction
//! \{
- inline RAInst(RABlock* block, RATiedFlags flags, uint32_t tiedTotal, const RARegMask& clobberedRegs) noexcept {
+ inline RAInst(RABlock* block, InstRWFlags instRWFlags, RATiedFlags tiedFlags, uint32_t tiedTotal, const RARegMask& clobberedRegs) noexcept {
_block = block;
- _flags = flags;
+ _instRWFlags = instRWFlags;
+ _flags = tiedFlags;
_tiedTotal = tiedTotal;
_tiedIndex.reset();
_tiedCount.reset();
@@ -321,6 +317,13 @@ public:
//! \name Accessors
//! \{
+ //! Returns instruction RW flags.
+ inline InstRWFlags instRWFlags() const noexcept { return _instRWFlags; };
+ //! Tests whether the given `flag` is present in instruction RW flags.
+ inline bool hasInstRWFlag(InstRWFlags flag) const noexcept { return Support::test(_instRWFlags, flag); }
+ //! Adds `flags` to instruction RW flags.
+ inline void addInstRWFlags(InstRWFlags flags) noexcept { _instRWFlags |= flags; }
+
//! Returns the instruction flags.
inline RATiedFlags flags() const noexcept { return _flags; }
//! Tests whether the instruction has flag `flag`.
@@ -383,6 +386,9 @@ public:
//! \name Members
//! \{
+ //! Instruction RW flags.
+ InstRWFlags _instRWFlags;
+
//! Flags combined from all RATiedReg's.
RATiedFlags _aggregatedFlags;
//! Flags that will be cleared before storing the aggregated flags to `RAInst`.
@@ -407,6 +413,7 @@ public:
inline void init() noexcept { reset(); }
inline void reset() noexcept {
+ _instRWFlags = InstRWFlags::kNone;
_aggregatedFlags = RATiedFlags::kNone;
_forbiddenFlags = RATiedFlags::kNone;
_count.reset();
@@ -421,10 +428,15 @@ public:
//! \name Accessors
//! \{
- inline RATiedFlags aggregatedFlags() const noexcept { return _aggregatedFlags; }
- inline RATiedFlags forbiddenFlags() const noexcept { return _forbiddenFlags; }
+ inline InstRWFlags instRWFlags() const noexcept { return _instRWFlags; }
+ inline bool hasInstRWFlag(InstRWFlags flag) const noexcept { return Support::test(_instRWFlags, flag); }
+ inline void addInstRWFlags(InstRWFlags flags) noexcept { _instRWFlags |= flags; }
+ inline void clearInstRWFlags(InstRWFlags flags) noexcept { _instRWFlags &= ~flags; }
+ inline RATiedFlags aggregatedFlags() const noexcept { return _aggregatedFlags; }
inline void addAggregatedFlags(RATiedFlags flags) noexcept { _aggregatedFlags |= flags; }
+
+ inline RATiedFlags forbiddenFlags() const noexcept { return _forbiddenFlags; }
inline void addForbiddenFlags(RATiedFlags flags) noexcept { _forbiddenFlags |= flags; }
//! Returns the number of tied registers added to the builder.
@@ -616,8 +628,6 @@ public:
ZoneBitVector _liveIn {};
//! Register assignment (PhysToWork).
PhysToWorkMap* _physToWorkMap = nullptr;
- //! Register assignment (WorkToPhys).
- WorkToPhysMap* _workToPhysMap = nullptr;
//! \}
@@ -632,12 +642,7 @@ public:
inline const ZoneBitVector& liveIn() const noexcept { return _liveIn; }
inline PhysToWorkMap* physToWorkMap() const noexcept { return _physToWorkMap; }
- inline WorkToPhysMap* workToPhysMap() const noexcept { return _workToPhysMap; }
-
- inline void assignMaps(PhysToWorkMap* physToWorkMap, WorkToPhysMap* workToPhysMap) noexcept {
- _physToWorkMap = physToWorkMap;
- _workToPhysMap = workToPhysMap;
- }
+ inline void assignPhysToWorkMap(PhysToWorkMap* physToWorkMap) noexcept { _physToWorkMap = physToWorkMap; }
//! \}
};
@@ -873,16 +878,16 @@ public:
return _exits.append(allocator(), block);
}
- ASMJIT_FORCE_INLINE RAInst* newRAInst(RABlock* block, RATiedFlags flags, uint32_t tiedRegCount, const RARegMask& clobberedRegs) noexcept {
+ ASMJIT_FORCE_INLINE RAInst* newRAInst(RABlock* block, InstRWFlags instRWFlags, RATiedFlags flags, uint32_t tiedRegCount, const RARegMask& clobberedRegs) noexcept {
void* p = zone()->alloc(RAInst::sizeOf(tiedRegCount));
if (ASMJIT_UNLIKELY(!p))
return nullptr;
- return new(p) RAInst(block, flags, tiedRegCount, clobberedRegs);
+ return new(p) RAInst(block, instRWFlags, flags, tiedRegCount, clobberedRegs);
}
ASMJIT_FORCE_INLINE Error assignRAInst(BaseNode* node, RABlock* block, RAInstBuilder& ib) noexcept {
uint32_t tiedRegCount = ib.tiedRegCount();
- RAInst* raInst = newRAInst(block, ib.aggregatedFlags(), tiedRegCount, ib._clobbered);
+ RAInst* raInst = newRAInst(block, ib.instRWFlags(), ib.aggregatedFlags(), tiedRegCount, ib._clobbered);
if (ASMJIT_UNLIKELY(!raInst))
return DebugUtils::errored(kErrorOutOfMemory);
@@ -1066,13 +1071,6 @@ public:
return static_cast<PhysToWorkMap*>(zone()->dupAligned(map, size, sizeof(uint32_t)));
}
- inline WorkToPhysMap* cloneWorkToPhysMap(const WorkToPhysMap* map) noexcept {
- size_t size = WorkToPhysMap::sizeOf(_workRegs.size());
- if (ASMJIT_UNLIKELY(size == 0))
- return const_cast<WorkToPhysMap*>(map);
- return static_cast<WorkToPhysMap*>(zone()->dup(map, size));
- }
-
//! \name Liveness Analysis & Statistics
//! \{
@@ -1110,7 +1108,7 @@ public:
//! Called after the RA assignment has been assigned to a block.
//!
//! This cannot change the assignment, but can examine it.
- Error blockEntryAssigned(const RAAssignment& as) noexcept;
+ Error blockEntryAssigned(const PhysToWorkMap* physToWorkMap) noexcept;
//! \}
diff --git a/erts/emulator/asmjit/core/rastack.cpp b/erts/emulator/asmjit/core/rastack.cpp
index 2b7ed592df..318fbded4b 100644
--- a/erts/emulator/asmjit/core/rastack.cpp
+++ b/erts/emulator/asmjit/core/rastack.cpp
@@ -62,7 +62,7 @@ Error RAStackAllocator::calculateStackFrame() noexcept {
// STEP 1:
//
- // Update usage based on the size of the slot. We boost smaller slots in a way that 32-bit register has higher
+ // Update usage based on the size of the slot. We boost smaller slots in a way that 32-bit register has a higher
// priority than a 128-bit register, however, if one 128-bit register is used 4 times more than some other 32-bit
// register it will overweight it.
for (RAStackSlot* slot : _slots) {
diff --git a/erts/emulator/asmjit/core/support.h b/erts/emulator/asmjit/core/support.h
index e55b8084db..b155cdfa90 100644
--- a/erts/emulator/asmjit/core/support.h
+++ b/erts/emulator/asmjit/core/support.h
@@ -939,6 +939,18 @@ static ASMJIT_FORCE_INLINE int cmpInstName(const char* a, const char* b, size_t
return int(uint8_t(a[size]));
}
+//! Compares two string views.
+static ASMJIT_FORCE_INLINE int compareStringViews(const char* aData, size_t aSize, const char* bData, size_t bSize) noexcept {
+ size_t size = Support::min(aSize, bSize);
+
+ for (size_t i = 0; i < size; i++) {
+ int c = int(uint8_t(aData[i])) - int(uint8_t(bData[i]));
+ if (c != 0)
+ return c;
+ }
+
+ return int(aSize) - int(bSize);
+}
// Support - Memory Read Access - 8 Bits
// =====================================
@@ -1227,34 +1239,11 @@ public:
ASMJIT_FORCE_INLINE uint32_t next() noexcept {
ASMJIT_ASSERT(_bitWord != 0);
uint32_t index = ctz(_bitWord);
- _bitWord ^= T(1u) << index;
- return index;
- }
-
- T _bitWord;
-};
-
-// Support - BitWordFlipIterator
-// =============================
-
-template<typename T>
-class BitWordFlipIterator {
-public:
- ASMJIT_FORCE_INLINE explicit BitWordFlipIterator(T bitWord) noexcept
- : _bitWord(bitWord) {}
-
- ASMJIT_FORCE_INLINE void init(T bitWord) noexcept { _bitWord = bitWord; }
- ASMJIT_FORCE_INLINE bool hasNext() const noexcept { return _bitWord != 0; }
-
- ASMJIT_FORCE_INLINE uint32_t nextAndFlip() noexcept {
- ASMJIT_ASSERT(_bitWord != 0);
- uint32_t index = ctz(_bitWord);
- _bitWord ^= T(1u) << index;
+ _bitWord &= T(_bitWord - 1);
return index;
}
T _bitWord;
- T _xorMask;
};
// Support - BitVectorOps
@@ -1406,7 +1395,7 @@ public:
ASMJIT_ASSERT(bitWord != T(0));
uint32_t bit = ctz(bitWord);
- bitWord ^= T(1u) << bit;
+ bitWord &= T(bitWord - 1u);
size_t n = _idx + bit;
while (!bitWord && (_idx += bitSizeOf<T>()) < _end)
@@ -1471,7 +1460,7 @@ public:
ASMJIT_ASSERT(bitWord != T(0));
uint32_t bit = ctz(bitWord);
- bitWord ^= T(1u) << bit;
+ bitWord &= T(bitWord - 1u);
size_t n = _idx + bit;
while (!bitWord && (_idx += kTSizeInBits) < _end)
diff --git a/erts/emulator/asmjit/core/support_p.h b/erts/emulator/asmjit/core/support_p.h
new file mode 100644
index 0000000000..a3de944dc0
--- /dev/null
+++ b/erts/emulator/asmjit/core/support_p.h
@@ -0,0 +1,64 @@
+// This file is part of AsmJit project <https://asmjit.com>
+//
+// See asmjit.h or LICENSE.md for license and copyright information
+// SPDX-License-Identifier: Zlib
+
+#ifndef ASMJIT_CORE_SUPPORT_P_H_INCLUDED
+#define ASMJIT_CORE_SUPPORT_P_H_INCLUDED
+
+#include "../core/support.h"
+
+ASMJIT_BEGIN_NAMESPACE
+
+//! \addtogroup asmjit_utilities
+//! \{
+
+namespace Support {
+
+//! \cond INTERNAL
+
+static ASMJIT_FORCE_INLINE char decode5BitChar(uint32_t c) noexcept {
+ uint32_t base = c <= 26 ? uint32_t('a') - 1u : uint32_t('0') - 27u;
+ return char(base + c);
+}
+
+static ASMJIT_FORCE_INLINE size_t decodeInstName(char nameOut[32], uint32_t index, const char* stringTable) noexcept {
+ size_t i;
+
+ if (index & 0x80000000u) {
+ // Small string of 5-bit characters.
+ for (i = 0; i < 6; i++, index >>= 5) {
+ uint32_t c = index & 0x1F;
+ if (c == 0)
+ break;
+ nameOut[i] = decode5BitChar(c);
+ }
+ return i;
+ }
+ else {
+ size_t prefixBase = index & 0xFFFu;
+ size_t prefixSize = (index >> 12) & 0xFu;
+
+ size_t suffixBase = (index >> 16) & 0xFFFu;
+ size_t suffixSize = (index >> 28) & 0x7u;
+
+ for (i = 0; i < prefixSize; i++)
+ nameOut[i] = stringTable[prefixBase + i];
+
+ char* suffixOut = nameOut + prefixSize;
+ for (i = 0; i < suffixSize; i++)
+ suffixOut[i] = stringTable[suffixBase + i];
+
+ return prefixSize + suffixSize;
+ }
+}
+
+//! \endcond
+
+} // {Support}
+
+//! \}
+
+ASMJIT_END_NAMESPACE
+
+#endif // ASMJIT_CORE_SUPPORT_P_H_INCLUDED
diff --git a/erts/emulator/asmjit/core/target.cpp b/erts/emulator/asmjit/core/target.cpp
index fef025d709..cbc6ab5109 100644
--- a/erts/emulator/asmjit/core/target.cpp
+++ b/erts/emulator/asmjit/core/target.cpp
@@ -8,7 +8,9 @@
ASMJIT_BEGIN_NAMESPACE
-Target::Target() noexcept : _environment() {}
+Target::Target() noexcept
+ : _environment{},
+ _cpuFeatures{} {}
Target::~Target() noexcept {}
ASMJIT_END_NAMESPACE
diff --git a/erts/emulator/asmjit/core/target.h b/erts/emulator/asmjit/core/target.h
index 23b0c6294c..322a338f97 100644
--- a/erts/emulator/asmjit/core/target.h
+++ b/erts/emulator/asmjit/core/target.h
@@ -7,6 +7,7 @@
#define ASMJIT_CORE_TARGET_H_INCLUDED
#include "../core/archtraits.h"
+#include "../core/cpuinfo.h"
#include "../core/func.h"
ASMJIT_BEGIN_NAMESPACE
@@ -22,6 +23,8 @@ public:
//! Target environment information.
Environment _environment;
+ //! Target CPU features.
+ CpuFeatures _cpuFeatures;
//! \name Construction & Destruction
//! \{
@@ -43,6 +46,9 @@ public:
//! Returns the target sub-architecture.
inline SubArch subArch() const noexcept { return _environment.subArch(); }
+ //! Returns target CPU features.
+ inline const CpuFeatures& cpuFeatures() const noexcept { return _cpuFeatures; }
+
//! \}
};
diff --git a/erts/emulator/asmjit/core/virtmem.cpp b/erts/emulator/asmjit/core/virtmem.cpp
index 43766ef2cd..103b51197b 100644
--- a/erts/emulator/asmjit/core/virtmem.cpp
+++ b/erts/emulator/asmjit/core/virtmem.cpp
@@ -42,33 +42,39 @@
#if !defined(MAP_ANONYMOUS)
#define MAP_ANONYMOUS MAP_ANON
#endif
-#endif
-#include <atomic>
+ #define ASMJIT_ANONYMOUS_MEMORY_USE_FD
-#if defined(__APPLE__) || defined(__BIONIC__)
- #define ASMJIT_VM_SHM_DETECT 0
-#else
- #define ASMJIT_VM_SHM_DETECT 1
-#endif
+ #if defined(__APPLE__) || defined(__BIONIC__)
+ #define ASMJIT_VM_SHM_DETECT 0
+ #else
+ #define ASMJIT_VM_SHM_DETECT 1
+ #endif
-// Android NDK doesn't provide `shm_open()` and `shm_unlink()`.
-#if defined(__BIONIC__)
- #define ASMJIT_VM_SHM_AVAILABLE 0
-#else
- #define ASMJIT_VM_SHM_AVAILABLE 1
-#endif
+ // Android NDK doesn't provide `shm_open()` and `shm_unlink()`.
+ #if !defined(__BIONIC__)
+ #define ASMJIT_HAS_SHM_OPEN_AND_UNLINK
+ #endif
-#if defined(__APPLE__) && ASMJIT_ARCH_ARM >= 64
- #define ASMJIT_HAS_PTHREAD_JIT_WRITE_PROTECT_NP
+ #if defined(__APPLE__) && TARGET_OS_OSX && ASMJIT_ARCH_ARM >= 64
+ #define ASMJIT_HAS_PTHREAD_JIT_WRITE_PROTECT_NP
+ #endif
+
+ #if defined(__NetBSD__) && defined(MAP_REMAPDUP) && defined(PROT_MPROTECT)
+ #undef ASMJIT_ANONYMOUS_MEMORY_USE_FD
+ #define ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP
+ #endif
#endif
+#include <atomic>
+
ASMJIT_BEGIN_SUB_NAMESPACE(VirtMem)
// Virtual Memory Utilities
// ========================
-static const MemoryFlags dualMappingFilter[2] = {
+ASMJIT_MAYBE_UNUSED
+static const constexpr MemoryFlags dualMappingFilter[2] = {
MemoryFlags::kAccessWrite | MemoryFlags::kMMapMaxAccessWrite,
MemoryFlags::kAccessExecute | MemoryFlags::kMMapMaxAccessExecute
};
@@ -219,19 +225,8 @@ Error releaseDualMapping(DualMapping* dm, size_t size) noexcept {
#if !defined(_WIN32)
-static void getVMInfo(Info& vmInfo) noexcept {
- uint32_t pageSize = uint32_t(::getpagesize());
-
- vmInfo.pageSize = pageSize;
- vmInfo.pageGranularity = Support::max<uint32_t>(pageSize, 65536);
-}
-
-#if !defined(SHM_ANON)
-static const char* getTmpDir() noexcept {
- const char* tmpDir = getenv("TMPDIR");
- return tmpDir ? tmpDir : "/tmp";
-}
-#endif
+// Virtual Memory [Posix] - Utilities
+// ==================================
// Translates libc errors specific to VirtualMemory mapping to `asmjit::Error`.
static Error asmjitErrorFromErrno(int e) noexcept {
@@ -256,16 +251,61 @@ static Error asmjitErrorFromErrno(int e) noexcept {
}
}
+static void getVMInfo(Info& vmInfo) noexcept {
+ uint32_t pageSize = uint32_t(::getpagesize());
+
+ vmInfo.pageSize = pageSize;
+ vmInfo.pageGranularity = Support::max<uint32_t>(pageSize, 65536);
+}
+
+#if defined(__APPLE__) && TARGET_OS_OSX
+static int getOSXVersion() noexcept {
+ // MAP_JIT flag required to run unsigned JIT code is only supported by kernel version 10.14+ (Mojave).
+ static std::atomic<int> globalVersion;
+
+ int ver = globalVersion.load();
+ if (!ver) {
+ struct utsname osname {};
+ uname(&osname);
+ ver = atoi(osname.release);
+ globalVersion.store(ver);
+ }
+
+ return ver;
+}
+#endif // __APPLE__ && TARGET_OS_OSX
+
+// Returns `mmap()` protection flags from \ref MemoryFlags.
+static int mmProtFromMemoryFlags(MemoryFlags memoryFlags) noexcept {
+ int protection = 0;
+ if (Support::test(memoryFlags, MemoryFlags::kAccessRead)) protection |= PROT_READ;
+ if (Support::test(memoryFlags, MemoryFlags::kAccessWrite)) protection |= PROT_READ | PROT_WRITE;
+ if (Support::test(memoryFlags, MemoryFlags::kAccessExecute)) protection |= PROT_READ | PROT_EXEC;
+ return protection;
+}
+
+// Virtual Memory [Posix] - Anonymus Memory
+// ========================================
+
+#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_FD)
+
// Some operating systems don't allow /dev/shm to be executable. On Linux this happens when /dev/shm is mounted with
// 'noexec', which is enforced by systemd. Other operating systems like MacOS also restrict executable permissions
// regarding /dev/shm, so we use a runtime detection before attempting to allocate executable memory. Sometimes we
-// don't need the detection as we know it would always result in `ShmStrategy::kTmpDir`.
-enum class ShmStrategy : uint32_t {
+// don't need the detection as we know it would always result in `AnonymousMemoryStrategy::kTmpDir`.
+enum class AnonymousMemoryStrategy : uint32_t {
kUnknown = 0,
kDevShm = 1,
kTmpDir = 2
};
+#if !defined(SHM_ANON)
+static const char* getTmpDir() noexcept {
+ const char* tmpDir = getenv("TMPDIR");
+ return tmpDir ? tmpDir : "/tmp";
+}
+#endif
+
class AnonymousMemory {
public:
enum FileType : uint32_t {
@@ -294,9 +334,13 @@ public:
#if defined(__linux__) && defined(__NR_memfd_create)
// Linux specific 'memfd_create' - if the syscall returns `ENOSYS` it means
// it's not available and we will never call it again (would be pointless).
+ //
+ // NOTE: There is also memfd_create() libc function in FreeBSD, but it internally
+ // uses `shm_open(SHM_ANON, ...)` so it's not needed to add support for it (it's
+ // not a syscall as in Linux).
// Zero initialized, if ever changed to '1' that would mean the syscall is not
- // available and we must use `shm_open()` and `shm_unlink()`.
+ // available and we must use `shm_open()` and `shm_unlink()` (or regular `open()`).
static volatile uint32_t memfd_create_not_supported;
if (!memfd_create_not_supported) {
@@ -347,7 +391,7 @@ public:
return kErrorOk;
}
}
-#if ASMJIT_VM_SHM_AVAILABLE
+#ifdef ASMJIT_HAS_SHM_OPEN_AND_UNLINK
else {
_tmpName.assignFormat(kShmFormat, (unsigned long long)bits);
_fd = ::shm_open(_tmpName.data(), O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
@@ -371,7 +415,7 @@ public:
FileType type = _fileType;
_fileType = kFileTypeNone;
-#if ASMJIT_VM_SHM_AVAILABLE
+#ifdef ASMJIT_HAS_SHM_OPEN_AND_UNLINK
if (type == kFileTypeShm) {
::shm_unlink(_tmpName.data());
return;
@@ -400,21 +444,61 @@ public:
}
};
-// Returns `mmap()` protection flags from \ref MemoryFlags.
-static int mmProtFromMemoryFlags(MemoryFlags memoryFlags) noexcept {
- int protection = 0;
- if (Support::test(memoryFlags, MemoryFlags::kAccessRead)) protection |= PROT_READ;
- if (Support::test(memoryFlags, MemoryFlags::kAccessWrite)) protection |= PROT_READ | PROT_WRITE;
- if (Support::test(memoryFlags, MemoryFlags::kAccessExecute)) protection |= PROT_READ | PROT_EXEC;
- return protection;
+#if ASMJIT_VM_SHM_DETECT
+static Error detectAnonymousMemoryStrategy(AnonymousMemoryStrategy* strategyOut) noexcept {
+ AnonymousMemory anonMem;
+ Info vmInfo = info();
+
+ ASMJIT_PROPAGATE(anonMem.open(false));
+ ASMJIT_PROPAGATE(anonMem.allocate(vmInfo.pageSize));
+
+ void* ptr = mmap(nullptr, vmInfo.pageSize, PROT_READ | PROT_EXEC, MAP_SHARED, anonMem.fd(), 0);
+ if (ptr == MAP_FAILED) {
+ int e = errno;
+ if (e == EINVAL) {
+ *strategyOut = AnonymousMemoryStrategy::kTmpDir;
+ return kErrorOk;
+ }
+ return DebugUtils::errored(asmjitErrorFromErrno(e));
+ }
+ else {
+ munmap(ptr, vmInfo.pageSize);
+ *strategyOut = AnonymousMemoryStrategy::kDevShm;
+ return kErrorOk;
+ }
}
+#endif
-#if defined(__APPLE__)
-// Detects whether the current process is hardened, which means that pages that have WRITE and EXECUTABLE flags cannot
-// be allocated without MAP_JIT flag.
-static inline bool hasHardenedRuntimeMacOS() noexcept {
-#if TARGET_OS_OSX && ASMJIT_ARCH_ARM >= 64
- // MacOS on AArch64 has always hardened runtime enabled.
+static Error getAnonymousMemoryStrategy(AnonymousMemoryStrategy* strategyOut) noexcept {
+#if ASMJIT_VM_SHM_DETECT
+ // Initially don't assume anything. It has to be tested whether '/dev/shm' was mounted with 'noexec' flag or not.
+ static std::atomic<uint32_t> globalStrategy;
+
+ AnonymousMemoryStrategy strategy = static_cast<AnonymousMemoryStrategy>(globalStrategy.load());
+ if (strategy == AnonymousMemoryStrategy::kUnknown) {
+ ASMJIT_PROPAGATE(detectAnonymousMemoryStrategy(&strategy));
+ globalStrategy.store(static_cast<uint32_t>(strategy));
+ }
+
+ *strategyOut = strategy;
+ return kErrorOk;
+#else
+ *strategyOut = AnonymousMemoryStrategy::kTmpDir;
+ return kErrorOk;
+#endif
+}
+
+#endif // ASMJIT_ANONYMOUS_MEMORY_USE_FD
+
+// Virtual Memory [Posix] - Hardened Runtime & MAP_JIT
+// ===================================================
+
+// Detects whether the current process is hardened, which means that pages that have WRITE and EXECUTABLE flags
+// cannot be normally allocated. On OSX + AArch64 such allocation requires MAP_JIT flag, other platforms don't
+// support this combination.
+static bool hasHardenedRuntime() noexcept {
+#if defined(__APPLE__) && TARGET_OS_OSX && ASMJIT_ARCH_ARM >= 64
+ // OSX on AArch64 has always hardened runtime enabled.
return true;
#else
static std::atomic<uint32_t> globalHardenedFlag;
@@ -427,9 +511,9 @@ static inline bool hasHardenedRuntimeMacOS() noexcept {
uint32_t flag = globalHardenedFlag.load();
if (flag == kHardenedFlagUnknown) {
- size_t pageSize = ::getpagesize();
+ uint32_t pageSize = uint32_t(::getpagesize());
- void* ptr = mmap(nullptr, pageSize, PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ void* ptr = mmap(nullptr, pageSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED) {
flag = kHardenedFlagEnabled;
}
@@ -444,45 +528,16 @@ static inline bool hasHardenedRuntimeMacOS() noexcept {
#endif
}
-static inline bool hasMapJitSupportMacOS() noexcept {
-#if TARGET_OS_OSX && ASMJIT_ARCH_ARM >= 64
- // MacOS for 64-bit AArch architecture always uses hardened runtime. Some documentation can be found here:
- // - https://developer.apple.com/documentation/apple_silicon/porting_just-in-time_compilers_to_apple_silicon
- return true;
-#elif TARGET_OS_OSX
- // MAP_JIT flag required to run unsigned JIT code is only supported by kernel version 10.14+ (Mojave) and IOS.
- static std::atomic<uint32_t> globalVersion;
-
- int ver = globalVersion.load();
- if (!ver) {
- struct utsname osname {};
- uname(&osname);
- ver = atoi(osname.release);
- globalVersion.store(ver);
- }
- return ver >= 18;
-#else
- // Assume it's available.
- return true;
-#endif
-}
-#endif // __APPLE__
-
-// Detects whether the current process is hardened, which means that pages that have WRITE and EXECUTABLE flags
-// cannot be normally allocated. On MacOS such allocation requires MAP_JIT flag.
-static inline bool hasHardenedRuntime() noexcept {
-#if defined(__APPLE__)
- return hasHardenedRuntimeMacOS();
-#else
- return false;
-#endif
-}
-
// Detects whether MAP_JIT is available.
static inline bool hasMapJitSupport() noexcept {
-#if defined(__APPLE__)
- return hasMapJitSupportMacOS();
+#if defined(__APPLE__) && TARGET_OS_OSX && ASMJIT_ARCH_ARM >= 64
+ // OSX on AArch64 always uses hardened runtime + MAP_JIT:
+ // - https://developer.apple.com/documentation/apple_silicon/porting_just-in-time_compilers_to_apple_silicon
+ return true;
+#elif defined(__APPLE__) && TARGET_OS_OSX
+ return getOSXVersion() >= 18;
#else
+ // MAP_JIT is not available (it's only available on OSX).
return false;
#endif
}
@@ -493,7 +548,8 @@ static inline int mmMapJitFromMemoryFlags(MemoryFlags memoryFlags) noexcept {
// Always use MAP_JIT flag if user asked for it (could be used for testing on non-hardened processes) and detect
// whether it must be used when the process is actually hardened (in that case it doesn't make sense to rely on
// user `memoryFlags`).
- bool useMapJit = Support::test(memoryFlags, MemoryFlags::kMMapEnableMapJit) || hasHardenedRuntime();
+ bool useMapJit = (Support::test(memoryFlags, MemoryFlags::kMMapEnableMapJit) || hasHardenedRuntime())
+ && !Support::test(memoryFlags, MemoryFlags::kMapShared);
if (useMapJit)
return hasMapJitSupport() ? int(MAP_JIT) : 0;
else
@@ -504,109 +560,144 @@ static inline int mmMapJitFromMemoryFlags(MemoryFlags memoryFlags) noexcept {
#endif
}
-// Returns BSD-specific `PROT_MAX()` flags.
-static inline int mmMaxProtFromMemoryFlags(MemoryFlags memoryFlags) noexcept {
-#if defined(PROT_MAX)
+ASMJIT_MAYBE_UNUSED
+static MemoryFlags maxAccessFlagsToRegularAccessFlags(MemoryFlags memoryFlags) noexcept {
static constexpr uint32_t kMaxProtShift = Support::ConstCTZ<uint32_t(MemoryFlags::kMMapMaxAccessRead)>::value;
-
- if (Support::test(memoryFlags, MemoryFlags::kMMapMaxAccessReadWrite | MemoryFlags::kMMapMaxAccessExecute))
- return PROT_MAX(mmProtFromMemoryFlags((MemoryFlags)(uint32_t(memoryFlags) >> kMaxProtShift)));
- else
- return 0;
-#else
- DebugUtils::unused(memoryFlags);
- return 0;
-#endif
+ return MemoryFlags(uint32_t(memoryFlags & MemoryFlags::kMMapMaxAccessRWX) >> kMaxProtShift);
}
-#if ASMJIT_VM_SHM_DETECT
-static Error detectShmStrategy(ShmStrategy* strategyOut) noexcept {
- AnonymousMemory anonMem;
- Info vmInfo = info();
-
- ASMJIT_PROPAGATE(anonMem.open(false));
- ASMJIT_PROPAGATE(anonMem.allocate(vmInfo.pageSize));
-
- void* ptr = mmap(nullptr, vmInfo.pageSize, PROT_READ | PROT_EXEC, MAP_SHARED, anonMem.fd(), 0);
- if (ptr == MAP_FAILED) {
- int e = errno;
- if (e == EINVAL) {
- *strategyOut = ShmStrategy::kTmpDir;
- return kErrorOk;
- }
- return DebugUtils::errored(asmjitErrorFromErrno(e));
- }
- else {
- munmap(ptr, vmInfo.pageSize);
- *strategyOut = ShmStrategy::kDevShm;
- return kErrorOk;
- }
+ASMJIT_MAYBE_UNUSED
+static MemoryFlags regularAccessFlagsToMaxAccessFlags(MemoryFlags memoryFlags) noexcept {
+ static constexpr uint32_t kMaxProtShift = Support::ConstCTZ<uint32_t(MemoryFlags::kMMapMaxAccessRead)>::value;
+ return MemoryFlags(uint32_t(memoryFlags & MemoryFlags::kAccessRWX) << kMaxProtShift);
}
-#endif
-static Error getShmStrategy(ShmStrategy* strategyOut) noexcept {
-#if ASMJIT_VM_SHM_DETECT
- // Initially don't assume anything. It has to be tested whether '/dev/shm' was mounted with 'noexec' flag or not.
- static std::atomic<uint32_t> globalShmStrategy;
-
- ShmStrategy strategy = static_cast<ShmStrategy>(globalShmStrategy.load());
- if (strategy == ShmStrategy::kUnknown) {
- ASMJIT_PROPAGATE(detectShmStrategy(&strategy));
- globalShmStrategy.store(static_cast<uint32_t>(strategy));
- }
-
- *strategyOut = strategy;
- return kErrorOk;
+// Returns maximum protection flags from `memoryFlags`.
+//
+// Uses:
+// - `PROT_MPROTECT()` on NetBSD.
+// - `PROT_MAX()` when available(BSD).
+ASMJIT_MAYBE_UNUSED
+static inline int mmMaxProtFromMemoryFlags(MemoryFlags memoryFlags) noexcept {
+ MemoryFlags acc = maxAccessFlagsToRegularAccessFlags(memoryFlags);
+ if (acc != MemoryFlags::kNone) {
+#if defined(__NetBSD__) && defined(PROT_MPROTECT)
+ return PROT_MPROTECT(mmProtFromMemoryFlags(acc));
+#elif defined(PROT_MAX)
+ return PROT_MAX(mmProtFromMemoryFlags(acc));
#else
- *strategyOut = ShmStrategy::kTmpDir;
- return kErrorOk;
+ return 0;
#endif
+ }
+
+ return 0;
}
static HardenedRuntimeFlags getHardenedRuntimeFlags() noexcept {
- HardenedRuntimeFlags hrFlags = HardenedRuntimeFlags::kNone;
+ HardenedRuntimeFlags flags = HardenedRuntimeFlags::kNone;
if (hasHardenedRuntime())
- hrFlags |= HardenedRuntimeFlags::kEnabled;
+ flags |= HardenedRuntimeFlags::kEnabled;
if (hasMapJitSupport())
- hrFlags |= HardenedRuntimeFlags::kMapJit;
+ flags |= HardenedRuntimeFlags::kMapJit;
- return hrFlags;
+ return flags;
}
-Error alloc(void** p, size_t size, MemoryFlags memoryFlags) noexcept {
+static Error mapMemory(void** p, size_t size, MemoryFlags memoryFlags, int fd = -1, off_t offset = 0) noexcept {
*p = nullptr;
if (size == 0)
return DebugUtils::errored(kErrorInvalidArgument);
int protection = mmProtFromMemoryFlags(memoryFlags) | mmMaxProtFromMemoryFlags(memoryFlags);
- int mmFlags = MAP_PRIVATE | MAP_ANONYMOUS | mmMapJitFromMemoryFlags(memoryFlags);
+ int mmFlags = mmMapJitFromMemoryFlags(memoryFlags);
+
+ mmFlags |= Support::test(memoryFlags, MemoryFlags::kMapShared) ? MAP_SHARED : MAP_PRIVATE;
+ if (fd == -1)
+ mmFlags |= MAP_ANONYMOUS;
- void* ptr = mmap(nullptr, size, protection, mmFlags, -1, 0);
+ void* ptr = mmap(nullptr, size, protection, mmFlags, fd, offset);
if (ptr == MAP_FAILED)
- return DebugUtils::errored(kErrorOutOfMemory);
+ return DebugUtils::errored(asmjitErrorFromErrno(errno));
*p = ptr;
return kErrorOk;
}
-Error release(void* p, size_t size) noexcept {
+static Error unmapMemory(void* p, size_t size) noexcept {
if (ASMJIT_UNLIKELY(munmap(p, size) != 0))
- return DebugUtils::errored(kErrorInvalidArgument);
+ return DebugUtils::errored(asmjitErrorFromErrno(errno));
return kErrorOk;
}
+Error alloc(void** p, size_t size, MemoryFlags memoryFlags) noexcept {
+ return mapMemory(p, size, memoryFlags);
+}
+
+Error release(void* p, size_t size) noexcept {
+ return unmapMemory(p, size);
+}
Error protect(void* p, size_t size, MemoryFlags memoryFlags) noexcept {
int protection = mmProtFromMemoryFlags(memoryFlags);
if (mprotect(p, size, protection) == 0)
return kErrorOk;
- return DebugUtils::errored(kErrorInvalidArgument);
+ return DebugUtils::errored(asmjitErrorFromErrno(errno));
}
+// Virtual Memory [Posix] - Dual Mapping
+// =====================================
+
+static Error unmapDualMapping(DualMapping* dm, size_t size) noexcept {
+ Error err1 = unmapMemory(dm->rx, size);
+ Error err2 = kErrorOk;
+
+ if (dm->rx != dm->rw)
+ err2 = unmapMemory(dm->rw, size);
+
+ // We can report only one error, so report the first...
+ if (err1 || err2)
+ return DebugUtils::errored(err1 ? err1 : err2);
+
+ dm->rx = nullptr;
+ dm->rw = nullptr;
+ return kErrorOk;
+}
+
+#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP)
+static Error allocDualMappingUsingRemapdup(DualMapping* dmOut, size_t size, MemoryFlags memoryFlags) noexcept {
+ MemoryFlags maxAccessFlags = regularAccessFlagsToMaxAccessFlags(memoryFlags);
+ MemoryFlags finalFlags = memoryFlags | maxAccessFlags | MemoryFlags::kMapShared;
+
+ MemoryFlags rxFlags = finalFlags & ~(MemoryFlags::kAccessWrite | MemoryFlags::kMMapMaxAccessWrite);
+ MemoryFlags rwFlags = finalFlags & ~(MemoryFlags::kAccessExecute);
+
+ // Allocate RW mapping.
+ DualMapping dm {};
+ ASMJIT_PROPAGATE(mapMemory(&dm.rw, size, rwFlags));
+
+ // Allocate RX mapping.
+ dm.rx = mremap(dm.rw, size, nullptr, size, MAP_REMAPDUP);
+ if (dm.rx == MAP_FAILED) {
+ int e = errno;
+ munmap(dm.rw, size);
+ return DebugUtils::errored(asmjitErrorFromErrno(e));
+ }
+
+ if (mprotect(dm.rx, size, mmProtFromMemoryFlags(rxFlags)) != 0) {
+ int e = errno;
+ unmapDualMapping(&dm, size);
+ return DebugUtils::errored(asmjitErrorFromErrno(e));
+ }
+
+ *dmOut = dm;
+ return kErrorOk;
+}
+#endif
+
Error allocDualMapping(DualMapping* dm, size_t size, MemoryFlags memoryFlags) noexcept {
dm->rx = nullptr;
dm->rw = nullptr;
@@ -614,11 +705,14 @@ Error allocDualMapping(DualMapping* dm, size_t size, MemoryFlags memoryFlags) no
if (off_t(size) <= 0)
return DebugUtils::errored(size == 0 ? kErrorInvalidArgument : kErrorTooLarge);
+#if defined(ASMJIT_ANONYMOUS_MEMORY_USE_REMAPDUP)
+ return allocDualMappingUsingRemapdup(dm, size, memoryFlags);
+#elif defined(ASMJIT_ANONYMOUS_MEMORY_USE_FD)
bool preferTmpOverDevShm = Support::test(memoryFlags, MemoryFlags::kMappingPreferTmp);
if (!preferTmpOverDevShm) {
- ShmStrategy strategy;
- ASMJIT_PROPAGATE(getShmStrategy(&strategy));
- preferTmpOverDevShm = (strategy == ShmStrategy::kTmpDir);
+ AnonymousMemoryStrategy strategy;
+ ASMJIT_PROPAGATE(getAnonymousMemoryStrategy(&strategy));
+ preferTmpOverDevShm = (strategy == AnonymousMemoryStrategy::kTmpDir);
}
AnonymousMemory anonMem;
@@ -627,35 +721,25 @@ Error allocDualMapping(DualMapping* dm, size_t size, MemoryFlags memoryFlags) no
void* ptr[2];
for (uint32_t i = 0; i < 2; i++) {
- MemoryFlags accessFlags = memoryFlags & ~dualMappingFilter[i];
- int protection = mmProtFromMemoryFlags(accessFlags) | mmMaxProtFromMemoryFlags(accessFlags);
-
- ptr[i] = mmap(nullptr, size, protection, MAP_SHARED, anonMem.fd(), 0);
- if (ptr[i] == MAP_FAILED) {
- // Get the error now before `munmap()` has a chance to clobber it.
- int e = errno;
+ MemoryFlags restrictedMemoryFlags = memoryFlags & ~dualMappingFilter[i];
+ Error err = mapMemory(&ptr[i], size, restrictedMemoryFlags | MemoryFlags::kMapShared, anonMem.fd(), 0);
+ if (err != kErrorOk) {
if (i == 1)
- munmap(ptr[0], size);
- return DebugUtils::errored(asmjitErrorFromErrno(e));
+ unmapMemory(ptr[0], size);
+ return err;
}
}
dm->rx = ptr[0];
dm->rw = ptr[1];
return kErrorOk;
+#else
+ #error "[asmjit] VirtMem::allocDualMapping() has no implementation"
+#endif
}
Error releaseDualMapping(DualMapping* dm, size_t size) noexcept {
- Error err = release(dm->rx, size);
- if (dm->rx != dm->rw)
- err |= release(dm->rw, size);
-
- if (err)
- return DebugUtils::errored(kErrorInvalidArgument);
-
- dm->rx = nullptr;
- dm->rw = nullptr;
- return kErrorOk;
+ return unmapDualMapping(dm, size);
}
#endif
@@ -676,7 +760,7 @@ void flushInstructionCache(void* p, size_t size) noexcept {
char* end = start + size;
__builtin___clear_cache(start, end);
#else
- #pragma message("asmjit::VirtMem::flushInstructionCache() doesn't have implementation for the target OS and compiler")
+ #pragma message("[asmjit] VirtMem::flushInstructionCache() doesn't have implementation for the target OS and compiler")
DebugUtils::unused(p, size);
#endif
}
@@ -711,7 +795,7 @@ HardenedRuntimeInfo hardenedRuntimeInfo() noexcept {
void protectJitMemory(ProtectJitAccess access) noexcept {
#if defined(ASMJIT_HAS_PTHREAD_JIT_WRITE_PROTECT_NP)
- pthread_jit_write_protect_np(static_cast<uint32_t>(access));
+ pthread_jit_write_protect_np(static_cast<int>(access));
#else
DebugUtils::unused(access);
#endif
@@ -719,4 +803,29 @@ void protectJitMemory(ProtectJitAccess access) noexcept {
ASMJIT_END_SUB_NAMESPACE
-#endif
+// JitAllocator - Tests
+// ====================
+
+#if defined(ASMJIT_TEST)
+ASMJIT_BEGIN_NAMESPACE
+
+UNIT(virt_mem) {
+ VirtMem::Info vmInfo = VirtMem::info();
+
+ INFO("VirtMem::info():");
+ INFO(" pageSize: %zu", size_t(vmInfo.pageSize));
+ INFO(" pageGranularity: %zu", size_t(vmInfo.pageGranularity));
+
+ VirtMem::HardenedRuntimeInfo hardenedRtInfo = VirtMem::hardenedRuntimeInfo();
+ VirtMem::HardenedRuntimeFlags hardenedFlags = hardenedRtInfo.flags;
+
+ INFO("VirtMem::hardenedRuntimeInfo():");
+ INFO(" flags:");
+ INFO(" kEnabled: %s", Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kEnabled) ? "true" : "false");
+ INFO(" kMapJit: %s", Support::test(hardenedFlags, VirtMem::HardenedRuntimeFlags::kMapJit) ? "true" : "false");
+}
+
+ASMJIT_END_NAMESPACE
+#endif // ASMJIT_TEST
+
+#endif // !ASMJIT_NO_JIT
diff --git a/erts/emulator/asmjit/core/virtmem.h b/erts/emulator/asmjit/core/virtmem.h
index 50f09457eb..a5633a2081 100644
--- a/erts/emulator/asmjit/core/virtmem.h
+++ b/erts/emulator/asmjit/core/virtmem.h
@@ -67,7 +67,7 @@ enum class MemoryFlags : uint32_t {
//! in MAC bundles. This flag is not turned on by default, because when a process uses `fork()` the child process
//! has no access to the pages mapped with `MAP_JIT`, which could break code that doesn't expect this behavior.
//!
- //! \note This flag can only be used with \ref VirtMem::alloc().
+ //! \note This flag can only be used with \ref VirtMem::alloc(), `MAP_JIT` only works on OSX and not on iOS.
kMMapEnableMapJit = 0x00000010u,
//! Pass `PROT_MAX(PROT_READ)` to mmap() on platforms that support `PROT_MAX`.
@@ -96,6 +96,13 @@ enum class MemoryFlags : uint32_t {
//! MemoryFlags::kMMapMaxAccessExecute.
kMMapMaxAccessRWX = kMMapMaxAccessRead | kMMapMaxAccessWrite | kMMapMaxAccessExecute,
+ //! Use `MAP_SHARED` when calling mmap().
+ //!
+ //! \note In some cases `MAP_SHARED` may be set automatically. For example when using dual mapping it's important to
+ //! to use `MAP_SHARED` instead of `MAP_PRIVATE` to ensure that the OS would not copy pages on write (that would mean
+ //! updating only the RW mapped region and not RX mapped one).
+ kMapShared = 0x00000100u,
+
//! Not an access flag, only used by `allocDualMapping()` to override the default allocation strategy to always use
//! a 'tmp' directory instead of "/dev/shm" (on POSIX platforms). Please note that this flag will be ignored if the
//! operating system allows to allocate an executable memory by a different API than `open()` or `shm_open()`. For
@@ -157,13 +164,13 @@ enum class HardenedRuntimeFlags : uint32_t {
//! Hardened runtime is enabled - it's not possible to have "Write & Execute" memory protection. The runtime
//! enforces W^X (either write or execute).
//!
- //! \note If the runtime is hardened it means that an operating system specific protection is used. For example on
- //! MacOS platform it's possible to allocate memory with MAP_JIT flag and then use `pthread_jit_write_protect_np()`
+ //! \note If the runtime is hardened it means that an operating system specific protection is used. For example
+ //! on Apple OSX it's possible to allocate memory with MAP_JIT flag and then use `pthread_jit_write_protect_np()`
//! to temporarily swap access permissions for the current thread. Dual mapping is also a possibility on X86/X64
//! architecture.
kEnabled = 0x00000001u,
- //! Read+Write+Execute can only be allocated with MAP_JIT flag (Apple specific).
+ //! Read+Write+Execute can only be allocated with MAP_JIT flag (Apple specific, only available on OSX).
kMapJit = 0x00000002u
};
ASMJIT_DEFINE_ENUM_FLAGS(HardenedRuntimeFlags)
diff --git a/erts/emulator/asmjit/core/zonevector.h b/erts/emulator/asmjit/core/zonevector.h
index 447c08cb92..9a655e6577 100644
--- a/erts/emulator/asmjit/core/zonevector.h
+++ b/erts/emulator/asmjit/core/zonevector.h
@@ -480,7 +480,7 @@ public:
//! Returns the capacity of the `BitWord[]` array in `BitWord` units.
inline uint32_t capacityInBitWords() const noexcept { return _wordsPerBits(_capacity); }
- //! REturns bit-vector data as `BitWord[]`.
+ //! Returns bit-vector data as `BitWord[]`.
inline BitWord* data() noexcept { return _data; }
//! \overload
inline const BitWord* data() const noexcept { return _data; }
diff --git a/erts/emulator/asmjit/x86/x86assembler.cpp b/erts/emulator/asmjit/x86/x86assembler.cpp
index f11fea0023..ee2f746f59 100644
--- a/erts/emulator/asmjit/x86/x86assembler.cpp
+++ b/erts/emulator/asmjit/x86/x86assembler.cpp
@@ -493,11 +493,11 @@ static ASMJIT_FORCE_INLINE uint32_t x86GetMovAbsInstSize64Bit(uint32_t regSize,
static ASMJIT_FORCE_INLINE bool x86ShouldUseMovabs(Assembler* self, X86BufferWriter& writer, uint32_t regSize, InstOptions options, const Mem& rmRel) noexcept {
if (self->is32Bit()) {
// There is no relative addressing, just decide whether to use MOV encoded with MOD R/M or absolute.
- return !Support::test(options, InstOptions::kX86_ModMR | InstOptions::kX86_ModMR);
+ return !Support::test(options, InstOptions::kX86_ModMR | InstOptions::kX86_ModRM);
}
else {
// If the addressing type is REL or MOD R/M was specified then absolute mov won't be used.
- if (rmRel.addrType() == Mem::AddrType::kRel || Support::test(options, InstOptions::kX86_ModMR))
+ if (rmRel.addrType() == Mem::AddrType::kRel || Support::test(options, InstOptions::kX86_ModMR | InstOptions::kX86_ModRM))
return false;
int64_t addrValue = rmRel.offset();
@@ -2259,7 +2259,7 @@ CaseX86PushPop_Gp:
goto EmitX86OpReg;
}
else {
- // Encode 'xchg eax, eax' by by using a generic path.
+ // Encode 'xchg eax, eax' by using a generic path.
}
}
else if (!Support::test(options, InstOptions::kLongForm)) {
@@ -2716,7 +2716,7 @@ CaseExtRm:
case InstDB::kEncodingExtRm_P:
if (isign3 == ENC_OPS2(Reg, Reg)) {
- opcode.add66hIf(Reg::isXmm(o0) | Reg::isXmm(o1));
+ opcode.add66hIf(unsigned(Reg::isXmm(o0)) | unsigned(Reg::isXmm(o1)));
opReg = o0.id();
rbReg = o1.id();
@@ -2760,7 +2760,7 @@ CaseExtRm:
case InstDB::kEncodingExtRmRi_P:
if (isign3 == ENC_OPS2(Reg, Reg)) {
- opcode.add66hIf(Reg::isXmm(o0) | Reg::isXmm(o1));
+ opcode.add66hIf(unsigned(Reg::isXmm(o0)) | unsigned(Reg::isXmm(o1)));
opReg = o0.id();
rbReg = o1.id();
@@ -2812,7 +2812,7 @@ CaseExtRm:
immSize = 1;
if (isign3 == ENC_OPS3(Reg, Reg, Imm)) {
- opcode.add66hIf(Reg::isXmm(o0) | Reg::isXmm(o1));
+ opcode.add66hIf(unsigned(Reg::isXmm(o0)) | unsigned(Reg::isXmm(o1)));
opReg = o0.id();
rbReg = o1.id();
@@ -3040,7 +3040,7 @@ CaseVexMri:
goto CaseVexRm;
case InstDB::kEncodingVexRm_Wx:
- opcode.addWIf(Reg::isGpq(o0) | Reg::isGpq(o1));
+ opcode.addWIf(unsigned(Reg::isGpq(o0)) | unsigned(Reg::isGpq(o1)));
goto CaseVexRm;
case InstDB::kEncodingVexRm_Lx_Narrow:
@@ -3110,7 +3110,7 @@ CaseVexRm:
}
case InstDB::kEncodingVexRmi_Wx:
- opcode.addWIf(Reg::isGpq(o0) | Reg::isGpq(o1));
+ opcode.addWIf(unsigned(Reg::isGpq(o0)) | unsigned(Reg::isGpq(o1)));
goto CaseVexRmi;
case InstDB::kEncodingVexRmi_Lx:
@@ -3159,7 +3159,7 @@ CaseVexRvm_R:
}
case InstDB::kEncodingVexRvm_Wx: {
- opcode.addWIf(Reg::isGpq(o0) | (o2.size() == 8));
+ opcode.addWIf(unsigned(Reg::isGpq(o0)) | unsigned((o2.size() == 8)));
goto CaseVexRvm;
}
@@ -3261,7 +3261,7 @@ VexRvmi:
}
case InstDB::kEncodingVexRmv_Wx:
- opcode.addWIf(Reg::isGpq(o0) | Reg::isGpq(o2));
+ opcode.addWIf(unsigned(Reg::isGpq(o0)) | unsigned(Reg::isGpq(o2)));
ASMJIT_FALLTHROUGH;
case InstDB::kEncodingVexRmv:
@@ -3614,7 +3614,7 @@ VexRvmi:
break;
case InstDB::kEncodingVexVm_Wx:
- opcode.addWIf(Reg::isGpq(o0) | Reg::isGpq(o1));
+ opcode.addWIf(unsigned(Reg::isGpq(o0)) | unsigned(Reg::isGpq(o1)));
ASMJIT_FALLTHROUGH;
case InstDB::kEncodingVexVm:
@@ -4950,10 +4950,7 @@ EmitDone:
#endif
}
- resetExtraReg();
- resetInstOptions();
- resetInlineComment();
-
+ resetState();
writer.done(this);
return kErrorOk;
@@ -4987,9 +4984,7 @@ Failed:
#ifndef ASMJIT_NO_LOGGING
return EmitterUtils::logInstructionFailed(this, err, instId, options, o0, o1, o2, opExt);
#else
- resetExtraReg();
- resetInstOptions();
- resetInlineComment();
+ resetState();
return reportError(err);
#endif
}
diff --git a/erts/emulator/asmjit/x86/x86assembler.h b/erts/emulator/asmjit/x86/x86assembler.h
index dbffae6289..dde27f40e7 100644
--- a/erts/emulator/asmjit/x86/x86assembler.h
+++ b/erts/emulator/asmjit/x86/x86assembler.h
@@ -38,7 +38,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Create a runtime specialized for JIT.
//! CodeHolder code; // Create a CodeHolder.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Assembler a(&code); // Create and attach x86::Assembler to code.
//!
//! // Decide between 32-bit CDECL, WIN64, and SysV64 calling conventions:
@@ -131,7 +132,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Create a runtime specialized for JIT.
//! CodeHolder code; // Create a CodeHolder.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Assembler a(&code); // Create and attach x86::Assembler to code.
//!
//! // Enable strict validation.
@@ -187,7 +189,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Create a runtime specialized for JIT.
//! CodeHolder code; // Create a CodeHolder.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Assembler a(&code); // Create and attach x86::Assembler to code.
//!
//! // Let's get these registers from x86::Assembler.
@@ -346,7 +349,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Create JIT Runtime.
//! CodeHolder code; // Create a CodeHolder.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Assembler a(&code); // Create and attach x86::Assembler to code.
//!
//! // Decide which registers will be mapped to function arguments. Try changing
@@ -453,7 +457,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Create a runtime specialized for JIT.
//! CodeHolder code; // Create a CodeHolder.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Assembler a(&code); // Create and attach x86::Assembler to code.
//!
//! // Let's get these registers from x86::Assembler.
diff --git a/erts/emulator/asmjit/x86/x86builder.h b/erts/emulator/asmjit/x86/x86builder.h
index f3bb11a0ca..4c56cba370 100644
--- a/erts/emulator/asmjit/x86/x86builder.h
+++ b/erts/emulator/asmjit/x86/x86builder.h
@@ -36,7 +36,9 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! // Small helper function to print the current content of `cb`.
//! static void dumpCode(BaseBuilder& builder, const char* phase) {
//! String sb;
-//! builder.dump(sb);
+//! formatOptions formatOptions {};
+//!
+//! Formatter::formatNodeList(sb, formatOptions, &builder);
//! printf("%s:\n%s\n", phase, sb.data());
//! }
//!
@@ -44,7 +46,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Create JIT Runtime.
//! CodeHolder code; // Create a CodeHolder.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Builder cb(&code); // Create and attach x86::Builder to `code`.
//!
//! // Decide which registers will be mapped to function arguments. Try changing registers
diff --git a/erts/emulator/asmjit/x86/x86compiler.h b/erts/emulator/asmjit/x86/x86compiler.h
index d89aea0251..8bb9a43cfb 100644
--- a/erts/emulator/asmjit/x86/x86compiler.h
+++ b/erts/emulator/asmjit/x86/x86compiler.h
@@ -38,7 +38,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Runtime specialized for JIT code execution.
//! CodeHolder code; // Holds code and relocation information.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Compiler cc(&code); // Create and attach x86::Compiler to code.
//!
//! cc.addFunc(FuncSignatureT<int>());// Begin a function of `int fn(void)` signature.
@@ -82,7 +83,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Runtime specialized for JIT code execution.
//! CodeHolder code; // Holds code and relocation information.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Compiler cc(&code); // Create and attach x86::Compiler to code.
//!
//! FuncNode* funcNode = cc.addFunc( // Begin the function of the following signature:
@@ -164,7 +166,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Runtime specialized for JIT code execution.
//! CodeHolder code; // Holds code and relocation information.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Compiler cc(&code); // Create and attach x86::Compiler to code.
//!
//! FuncNode* funcNode = cc.addFunc(FuncSignatureT<void, void*>());
@@ -222,7 +225,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Runtime specialized for JIT code execution.
//! CodeHolder code; // Holds code and relocation information.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Compiler cc(&code); // Create and attach x86::Compiler to code.
//!
//! FuncNode* funcNode = cc.addFunc( // Begin of the Fibonacci function, addFunc()
@@ -290,7 +294,8 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
//! JitRuntime rt; // Runtime specialized for JIT code execution.
//! CodeHolder code; // Holds code and relocation information.
//!
-//! code.init(rt.environment()); // Initialize code to match the JIT environment.
+//! code.init(rt.environment(), // Initialize code to match the JIT environment.
+//! rt.cpuFeatures());
//! x86::Compiler cc(&code); // Create and attach x86::Compiler to code.
//!
//! cc.addFunc(FuncSignatureT<int>());// Create a function that returns int.
diff --git a/erts/emulator/asmjit/x86/x86emithelper.cpp b/erts/emulator/asmjit/x86/x86emithelper.cpp
index cc558e07c5..b541c048b0 100644
--- a/erts/emulator/asmjit/x86/x86emithelper.cpp
+++ b/erts/emulator/asmjit/x86/x86emithelper.cpp
@@ -30,7 +30,7 @@ static inline uint32_t getXmmMovInst(const FuncFrame& frame) {
: (avx ? Inst::kIdVmovups : Inst::kIdMovups);
}
-//! Converts `size` to a 'kmov?' instructio.
+//! Converts `size` to a 'kmov?' instruction.
static inline uint32_t kmovInstFromSize(uint32_t size) noexcept {
switch (size) {
case 1: return Inst::kIdKmovb;
diff --git a/erts/emulator/asmjit/x86/x86formatter.cpp b/erts/emulator/asmjit/x86/x86formatter.cpp
index d62dd18b63..715432e0ee 100644
--- a/erts/emulator/asmjit/x86/x86formatter.cpp
+++ b/erts/emulator/asmjit/x86/x86formatter.cpp
@@ -4,7 +4,7 @@
// SPDX-License-Identifier: Zlib
#include "../core/api-build_p.h"
-#ifndef ASMJIT_NO_LOGGING
+#if !defined(ASMJIT_NO_X86) && !defined(ASMJIT_NO_LOGGING)
#include "../core/cpuinfo.h"
#include "../core/misc_p.h"
@@ -198,6 +198,7 @@ Error FormatterInternal::formatFeature(String& sb, uint32_t featureId) noexcept
"AESNI\0"
"ALTMOVCR8\0"
"AMX_BF16\0"
+ "AMX_FP16\0"
"AMX_INT8\0"
"AMX_TILE\0"
"AVX\0"
@@ -220,17 +221,22 @@ Error FormatterInternal::formatFeature(String& sb, uint32_t featureId) noexcept
"AVX512_VNNI\0"
"AVX512_VP2INTERSECT\0"
"AVX512_VPOPCNTDQ\0"
+ "AVX_IFMA\0"
+ "AVX_NE_CONVERT\0"
"AVX_VNNI\0"
+ "AVX_VNNI_INT8\0"
"BMI\0"
"BMI2\0"
"CET_IBT\0"
"CET_SS\0"
+ "CET_SSS\0"
"CLDEMOTE\0"
"CLFLUSH\0"
"CLFLUSHOPT\0"
"CLWB\0"
"CLZERO\0"
"CMOV\0"
+ "CMPCCXADD\0"
"CMPXCHG16B\0"
"CMPXCHG8B\0"
"ENCLV\0"
@@ -241,14 +247,19 @@ Error FormatterInternal::formatFeature(String& sb, uint32_t featureId) noexcept
"FMA4\0"
"FPU\0"
"FSGSBASE\0"
+ "FSRM\0"
+ "FSRC\0"
+ "FSRS\0"
"FXSR\0"
"FXSROPT\0"
+ "FZRM\0"
"GEODE\0"
"GFNI\0"
"HLE\0"
"HRESET\0"
"I486\0"
"LAHFSAHF\0"
+ "LAM\0"
"LWP\0"
"LZCNT\0"
"MCOMMIT\0"
@@ -261,15 +272,18 @@ Error FormatterInternal::formatFeature(String& sb, uint32_t featureId) noexcept
"MOVDIRI\0"
"MPX\0"
"MSR\0"
+ "MSRLIST\0"
"MSSE\0"
"OSXSAVE\0"
"OSPKE\0"
"PCLMULQDQ\0"
"PCONFIG\0"
"POPCNT\0"
+ "PREFETCHI\0"
"PREFETCHW\0"
"PREFETCHWT1\0"
"PTWRITE\0"
+ "RAO_INT\0"
"RDPID\0"
"RDPRU\0"
"RDRAND\0"
@@ -301,6 +315,7 @@ Error FormatterInternal::formatFeature(String& sb, uint32_t featureId) noexcept
"VPCLMULQDQ\0"
"WAITPKG\0"
"WBNOINVD\0"
+ "WRMSRNS\0"
"XOP\0"
"XSAVE\0"
"XSAVEC\0"
@@ -309,14 +324,15 @@ Error FormatterInternal::formatFeature(String& sb, uint32_t featureId) noexcept
"<Unknown>\0";
static const uint16_t sFeatureIndex[] = {
- 0, 5, 8, 11, 17, 24, 28, 34, 44, 53, 62, 71, 75, 80, 94, 108, 120, 134, 144,
- 155, 165, 176, 185, 197, 209, 220, 232, 245, 255, 267, 287, 304, 313, 317,
- 322, 330, 337, 346, 354, 365, 370, 377, 382, 393, 403, 409, 416, 421, 426,
- 430, 435, 439, 448, 453, 461, 467, 472, 476, 483, 488, 497, 501, 507, 515,
- 519, 524, 532, 541, 547, 557, 565, 569, 573, 578, 586, 592, 602, 610, 617,
- 627, 639, 647, 653, 659, 666, 673, 679, 686, 690, 700, 704, 711, 716, 721,
- 725, 729, 733, 738, 743, 750, 757, 763, 769, 773, 777, 781, 790, 796, 801,
- 805, 816, 824, 833, 837, 843, 850, 859, 866
+ 0, 5, 8, 11, 17, 24, 28, 34, 44, 53, 62, 71, 80, 84, 89, 103, 117, 129, 143,
+ 153, 164, 174, 185, 194, 206, 218, 229, 241, 254, 264, 276, 296, 313, 322,
+ 337, 346, 360, 364, 369, 377, 384, 392, 401, 409, 420, 425, 432, 437, 447,
+ 458, 468, 474, 481, 486, 491, 495, 500, 504, 513, 518, 523, 528, 533, 541,
+ 546, 552, 557, 561, 568, 573, 582, 586, 590, 596, 604, 608, 613, 621, 630,
+ 636, 646, 654, 658, 662, 670, 675, 683, 689, 699, 707, 714, 724, 734, 746,
+ 754, 762, 768, 774, 781, 788, 794, 801, 805, 815, 819, 826, 831, 836, 840,
+ 844, 848, 853, 858, 865, 872, 878, 884, 888, 892, 896, 905, 911, 916, 920,
+ 931, 939, 948, 956, 960, 966, 973, 982, 989
};
// @EnumStringEnd@
@@ -344,7 +360,10 @@ ASMJIT_FAVOR_SIZE Error FormatterInternal::formatRegister(String& sb, FormatFlag
else
ASMJIT_PROPAGATE(sb.appendFormat("%%%u", unsigned(Operand::virtIdToIndex(id))));
- if (vReg->type() != type && uint32_t(type) <= uint32_t(RegType::kMaxValue) && Support::test(formatFlags, FormatFlags::kRegCasts)) {
+ bool formatType = (Support::test(formatFlags, FormatFlags::kRegType)) ||
+ (Support::test(formatFlags, FormatFlags::kRegCasts) && vReg->type() != type);
+
+ if (formatType && uint32_t(type) <= uint32_t(RegType::kMaxValue)) {
const RegFormatInfo::TypeEntry& typeEntry = info.typeEntries[size_t(type)];
if (typeEntry.index)
ASMJIT_PROPAGATE(sb.appendFormat("@%s", info.typeStrings + typeEntry.index));
@@ -496,13 +515,14 @@ struct ImmBits {
char text[48 - 3];
};
-ASMJIT_FAVOR_SIZE static Error FormatterInternal_formatImmShuf(String& sb, uint32_t u8, uint32_t bits, uint32_t count) noexcept {
+ASMJIT_FAVOR_SIZE static Error FormatterInternal_formatImmShuf(String& sb, uint32_t imm8, uint32_t bits, uint32_t count) noexcept {
uint32_t mask = (1 << bits) - 1;
+ uint32_t lastPredicateShift = bits * (count - 1u);
- for (uint32_t i = 0; i < count; i++, u8 >>= bits) {
- uint32_t value = u8 & mask;
+ for (uint32_t i = 0; i < count; i++, imm8 <<= bits) {
+ uint32_t index = (imm8 >> lastPredicateShift) & mask;
ASMJIT_PROPAGATE(sb.append(i == 0 ? kImmCharStart : kImmCharOr));
- ASMJIT_PROPAGATE(sb.appendUInt(value));
+ ASMJIT_PROPAGATE(sb.appendUInt(index));
}
if (kImmCharEnd)
@@ -511,14 +531,14 @@ ASMJIT_FAVOR_SIZE static Error FormatterInternal_formatImmShuf(String& sb, uint3
return kErrorOk;
}
-ASMJIT_FAVOR_SIZE static Error FormatterInternal_formatImmBits(String& sb, uint32_t u8, const ImmBits* bits, uint32_t count) noexcept {
+ASMJIT_FAVOR_SIZE static Error FormatterInternal_formatImmBits(String& sb, uint32_t imm8, const ImmBits* bits, uint32_t count) noexcept {
uint32_t n = 0;
char buf[64];
for (uint32_t i = 0; i < count; i++) {
const ImmBits& spec = bits[i];
- uint32_t value = (u8 & uint32_t(spec.mask)) >> spec.shift;
+ uint32_t value = (imm8 & uint32_t(spec.mask)) >> spec.shift;
const char* str = nullptr;
switch (spec.mode) {
@@ -548,12 +568,12 @@ ASMJIT_FAVOR_SIZE static Error FormatterInternal_formatImmBits(String& sb, uint3
return kErrorOk;
}
-ASMJIT_FAVOR_SIZE static Error FormatterInternal_formatImmText(String& sb, uint32_t u8, uint32_t bits, uint32_t advance, const char* text, uint32_t count = 1) noexcept {
+ASMJIT_FAVOR_SIZE static Error FormatterInternal_formatImmText(String& sb, uint32_t imm8, uint32_t bits, uint32_t advance, const char* text, uint32_t count = 1) noexcept {
uint32_t mask = (1u << bits) - 1;
uint32_t pos = 0;
- for (uint32_t i = 0; i < count; i++, u8 >>= bits, pos += advance) {
- uint32_t value = (u8 & mask) + pos;
+ for (uint32_t i = 0; i < count; i++, imm8 >>= bits, pos += advance) {
+ uint32_t value = (imm8 & mask) + pos;
ASMJIT_PROPAGATE(sb.append(i == 0 ? kImmCharStart : kImmCharOr));
ASMJIT_PROPAGATE(sb.append(Support::findPackedString(text, value)));
}
@@ -608,25 +628,25 @@ ASMJIT_FAVOR_SIZE static Error FormatterInternal_explainConst(
};
static const ImmBits vmpsadbw[] = {
- { 0x04u, 2, ImmBits::kModeLookup, "BLK1[0]\0" "BLK1[1]\0" },
- { 0x03u, 0, ImmBits::kModeLookup, "BLK2[0]\0" "BLK2[1]\0" "BLK2[2]\0" "BLK2[3]\0" },
{ 0x40u, 6, ImmBits::kModeLookup, "BLK1[4]\0" "BLK1[5]\0" },
- { 0x30u, 4, ImmBits::kModeLookup, "BLK2[4]\0" "BLK2[5]\0" "BLK2[6]\0" "BLK2[7]\0" }
+ { 0x30u, 4, ImmBits::kModeLookup, "BLK2[4]\0" "BLK2[5]\0" "BLK2[6]\0" "BLK2[7]\0" },
+ { 0x04u, 2, ImmBits::kModeLookup, "BLK1[0]\0" "BLK1[1]\0" },
+ { 0x03u, 0, ImmBits::kModeLookup, "BLK2[0]\0" "BLK2[1]\0" "BLK2[2]\0" "BLK2[3]\0" }
};
static const ImmBits vpclmulqdq[] = {
- { 0x01u, 0, ImmBits::kModeLookup, "LQ\0" "HQ\0" },
- { 0x10u, 4, ImmBits::kModeLookup, "LQ\0" "HQ\0" }
+ { 0x10u, 4, ImmBits::kModeLookup, "LQ\0" "HQ\0" },
+ { 0x01u, 0, ImmBits::kModeLookup, "LQ\0" "HQ\0" }
};
static const ImmBits vperm2x128[] = {
- { 0x0Bu, 0, ImmBits::kModeLookup, "A0\0" "A1\0" "B0\0" "B1\0" "\0" "\0" "\0" "\0" "0\0" "0\0" "0\0" "0\0" },
- { 0xB0u, 4, ImmBits::kModeLookup, "A0\0" "A1\0" "B0\0" "B1\0" "\0" "\0" "\0" "\0" "0\0" "0\0" "0\0" "0\0" }
+ { 0xB0u, 4, ImmBits::kModeLookup, "A0\0" "A1\0" "B0\0" "B1\0" "\0" "\0" "\0" "\0" "0\0" "0\0" "0\0" "0\0" },
+ { 0x0Bu, 0, ImmBits::kModeLookup, "A0\0" "A1\0" "B0\0" "B1\0" "\0" "\0" "\0" "\0" "0\0" "0\0" "0\0" "0\0" }
};
static const ImmBits vrangexx[] = {
- { 0x03u, 0, ImmBits::kModeLookup, "MIN\0" "MAX\0" "MIN_ABS\0" "MAX_ABS\0" },
- { 0x0Cu, 2, ImmBits::kModeLookup, "SIGN_A\0" "SIGN_B\0" "SIGN_0\0" "SIGN_1\0" }
+ { 0x0Cu, 2, ImmBits::kModeLookup, "SIGN_A\0" "SIGN_B\0" "SIGN_0\0" "SIGN_1\0" },
+ { 0x03u, 0, ImmBits::kModeLookup, "MIN\0" "MAX\0" "MIN_ABS\0" "MAX_ABS\0" }
};
static const ImmBits vreducexx_vrndscalexx[] = {
@@ -941,4 +961,4 @@ ASMJIT_FAVOR_SIZE Error FormatterInternal::formatInstruction(
ASMJIT_END_SUB_NAMESPACE
-#endif // !ASMJIT_NO_LOGGING
+#endif // !ASMJIT_NO_X86 && !ASMJIT_NO_LOGGING
diff --git a/erts/emulator/asmjit/x86/x86globals.h b/erts/emulator/asmjit/x86/x86globals.h
index 803c813ac5..7f1566da3b 100644
--- a/erts/emulator/asmjit/x86/x86globals.h
+++ b/erts/emulator/asmjit/x86/x86globals.h
@@ -1933,7 +1933,7 @@ enum class RoundImm : uint8_t {
kUp = 0x02u, //!< Round to up toward +INF (ceil).
kTrunc = 0x03u, //!< Round toward zero (truncate).
kCurrent = 0x04u, //!< Round to the current rounding mode set (ignores other RC bits).
- kSuppress = 0x08u //!< Supress exceptions (avoids inexact exception, if set).
+ kSuppress = 0x08u //!< Suppress exceptions (avoids inexact exception, if set).
};
ASMJIT_DEFINE_ENUM_FLAGS(RoundImm)
diff --git a/erts/emulator/asmjit/x86/x86instapi.cpp b/erts/emulator/asmjit/x86/x86instapi.cpp
index 3580fe6f77..50d3e8393f 100644
--- a/erts/emulator/asmjit/x86/x86instapi.cpp
+++ b/erts/emulator/asmjit/x86/x86instapi.cpp
@@ -26,7 +26,7 @@
#include "../core/cpuinfo.h"
#include "../core/misc_p.h"
-#include "../core/support.h"
+#include "../core/support_p.h"
#include "../x86/x86instapi_p.h"
#include "../x86/x86instdb_p.h"
#include "../x86/x86opcode_p.h"
@@ -44,8 +44,10 @@ Error InstInternal::instIdToString(Arch arch, InstId instId, String& output) noe
if (ASMJIT_UNLIKELY(!Inst::isDefinedId(instId)))
return DebugUtils::errored(kErrorInvalidInstruction);
- const InstDB::InstInfo& info = InstDB::infoById(instId);
- return output.append(InstDB::_nameData + info._nameDataIndex);
+ char nameData[32];
+ size_t nameSize = Support::decodeInstName(nameData, InstDB::_instNameIndexTable[instId], InstDB::_instNameStringTable);
+
+ return output.append(nameData, nameSize);
}
InstId InstInternal::stringToInstId(Arch arch, const char* s, size_t len) noexcept {
@@ -64,30 +66,28 @@ InstId InstInternal::stringToInstId(Arch arch, const char* s, size_t len) noexce
if (ASMJIT_UNLIKELY(prefix > 'z' - 'a'))
return Inst::kIdNone;
- uint32_t index = InstDB::instNameIndex[prefix].start;
- if (ASMJIT_UNLIKELY(!index))
- return Inst::kIdNone;
+ size_t base = InstDB::instNameIndex[prefix].start;
+ size_t end = InstDB::instNameIndex[prefix].end;
- const char* nameData = InstDB::_nameData;
- const InstDB::InstInfo* table = InstDB::_instInfoTable;
+ if (ASMJIT_UNLIKELY(!base))
+ return Inst::kIdNone;
- const InstDB::InstInfo* base = table + index;
- const InstDB::InstInfo* end = table + InstDB::instNameIndex[prefix].end;
+ char nameData[32];
+ for (size_t lim = end - base; lim != 0; lim >>= 1) {
+ size_t instId = base + (lim >> 1);
+ size_t nameSize = Support::decodeInstName(nameData, InstDB::_instNameIndexTable[instId], InstDB::_instNameStringTable);
- for (size_t lim = (size_t)(end - base); lim != 0; lim >>= 1) {
- const InstDB::InstInfo* cur = base + (lim >> 1);
- int result = Support::cmpInstName(nameData + cur[0]._nameDataIndex, s, len);
+ int result = Support::compareStringViews(s, len, nameData, nameSize);
+ if (result < 0)
+ continue;
- if (result < 0) {
- base = cur + 1;
+ if (result > 0) {
+ base = instId + 1;
lim--;
continue;
}
- if (result > 0)
- continue;
-
- return InstId((size_t)(cur - table));
+ return InstId(instId);
}
return Inst::kIdNone;
@@ -776,6 +776,15 @@ static ASMJIT_FORCE_INLINE Error rwHandleAVX512(const BaseInst& inst, const Inst
return kErrorOk;
}
+static ASMJIT_FORCE_INLINE bool hasSameRegType(const BaseReg* regs, size_t opCount) noexcept {
+ ASMJIT_ASSERT(opCount > 0);
+ RegType regType = regs[0].type();
+ for (size_t i = 1; i < opCount; i++)
+ if (regs[i].type() != regType)
+ return false;
+ return true;
+}
+
Error InstInternal::queryRWInfo(Arch arch, const BaseInst& inst, const Operand_* operands, size_t opCount, InstRWInfo* out) noexcept {
// Only called when `arch` matches X86 family.
ASMJIT_ASSERT(Environment::isFamilyX86(arch));
@@ -801,13 +810,14 @@ Error InstInternal::queryRWInfo(Arch arch, const BaseInst& inst, const Operand_*
: InstDB::rwInfoB[InstDB::rwInfoIndexB[instId]];
const InstDB::RWInfoRm& instRmInfo = InstDB::rwInfoRm[instRwInfo.rmInfo];
- out->_instFlags = 0;
+ out->_instFlags = InstDB::_instFlagsTable[additionalInfo._instFlagsIndex];
out->_opCount = uint8_t(opCount);
out->_rmFeature = instRmInfo.rmFeature;
out->_extraReg.reset();
out->_readFlags = CpuRWFlags(rwFlags.readFlags);
out->_writeFlags = CpuRWFlags(rwFlags.writeFlags);
+ uint32_t opTypeMask = 0u;
uint32_t nativeGpSize = Environment::registerSizeFromArch(arch);
constexpr OpRWFlags R = OpRWFlags::kRead;
@@ -827,6 +837,8 @@ Error InstInternal::queryRWInfo(Arch arch, const BaseInst& inst, const Operand_*
const Operand_& srcOp = operands[i];
const InstDB::RWInfoOp& rwOpData = InstDB::rwInfoOp[instRwInfo.opInfoIndex[i]];
+ opTypeMask |= Support::bitMask(srcOp.opType());
+
if (!srcOp.isRegOrMem()) {
op.reset();
continue;
@@ -878,6 +890,35 @@ Error InstInternal::queryRWInfo(Arch arch, const BaseInst& inst, const Operand_*
}
}
+ // Only keep kMovOp if the instruction is actually register to register move of the same kind.
+ if (out->hasInstFlag(InstRWFlags::kMovOp)) {
+ if (!(opCount >= 2 && opTypeMask == Support::bitMask(OperandType::kReg) && hasSameRegType(reinterpret_cast<const BaseReg*>(operands), opCount)))
+ out->_instFlags &= ~InstRWFlags::kMovOp;
+ }
+
+ // Special cases require more logic.
+ if (instRmInfo.flags & (InstDB::RWInfoRm::kFlagMovssMovsd | InstDB::RWInfoRm::kFlagPextrw | InstDB::RWInfoRm::kFlagFeatureIfRMI)) {
+ if (instRmInfo.flags & InstDB::RWInfoRm::kFlagMovssMovsd) {
+ if (opCount == 2) {
+ if (operands[0].isReg() && operands[1].isReg()) {
+ // Doesn't zero extend the destination.
+ out->_operands[0]._extendByteMask = 0;
+ }
+ }
+ }
+ else if (instRmInfo.flags & InstDB::RWInfoRm::kFlagPextrw) {
+ if (opCount == 3 && Reg::isMm(operands[1])) {
+ out->_rmFeature = 0;
+ rmOpsMask = 0;
+ }
+ }
+ else if (instRmInfo.flags & InstDB::RWInfoRm::kFlagFeatureIfRMI) {
+ if (opCount != 3 || !operands[2].isImm()) {
+ out->_rmFeature = 0;
+ }
+ }
+ }
+
rmOpsMask &= instRmInfo.rmOpsMask;
if (rmOpsMask) {
Support::BitWordIterator<uint32_t> it(rmOpsMask);
@@ -916,6 +957,9 @@ Error InstInternal::queryRWInfo(Arch arch, const BaseInst& inst, const Operand_*
// used to move between GP, segment, control and debug registers. Moving between GP registers also allow to
// use memory operand.
+ // We will again set the flag if it's actually a move from GP to GP register, otherwise this flag cannot be set.
+ out->_instFlags &= ~InstRWFlags::kMovOp;
+
if (opCount == 2) {
if (operands[0].isReg() && operands[1].isReg()) {
const Reg& o0 = operands[0].as<Reg>();
@@ -926,6 +970,7 @@ Error InstInternal::queryRWInfo(Arch arch, const BaseInst& inst, const Operand_*
out->_operands[1].reset(R | RegM, operands[1].size());
rwZeroExtendGp(out->_operands[0], operands[0].as<Gp>(), nativeGpSize);
+ out->_instFlags |= InstRWFlags::kMovOp;
return kErrorOk;
}
@@ -1543,14 +1588,6 @@ Error InstInternal::queryFeatures(Arch arch, const BaseInst& inst, const Operand
uint32_t mustUseEvex = 0;
switch (instId) {
- // Special case: VPSLLDQ and VPSRLDQ instructions only allow `reg, reg. imm` combination in AVX|AVX2 mode,
- // then AVX-512 introduced `reg, reg/mem, imm` combination that uses EVEX prefix. This means that if the
- // second operand is memory then this is AVX-512_BW instruction and not AVX/AVX2 instruction.
- case Inst::kIdVpslldq:
- case Inst::kIdVpsrldq:
- mustUseEvex = opCount >= 2 && operands[1].isMem();
- break;
-
// Special case: VPBROADCAST[B|D|Q|W] only supports r32/r64 with EVEX prefix.
case Inst::kIdVpbroadcastb:
case Inst::kIdVpbroadcastd:
@@ -1559,6 +1596,29 @@ Error InstInternal::queryFeatures(Arch arch, const BaseInst& inst, const Operand
mustUseEvex = opCount >= 2 && x86::Reg::isGp(operands[1]);
break;
+ case Inst::kIdVcvtpd2dq:
+ case Inst::kIdVcvtpd2ps:
+ case Inst::kIdVcvttpd2dq:
+ mustUseEvex = opCount >= 2 && Reg::isYmm(operands[0]);
+ break;
+
+ // Special case: These instructions only allow `reg, reg. imm` combination in AVX|AVX2 mode, then
+ // AVX-512 introduced `reg, reg/mem, imm` combination that uses EVEX prefix. This means that if
+ // the second operand is memory then this is AVX-512_BW instruction and not AVX/AVX2 instruction.
+ case Inst::kIdVpslldq:
+ case Inst::kIdVpslld:
+ case Inst::kIdVpsllq:
+ case Inst::kIdVpsllw:
+ case Inst::kIdVpsrad:
+ case Inst::kIdVpsraq:
+ case Inst::kIdVpsraw:
+ case Inst::kIdVpsrld:
+ case Inst::kIdVpsrldq:
+ case Inst::kIdVpsrlq:
+ case Inst::kIdVpsrlw:
+ mustUseEvex = opCount >= 2 && operands[1].isMem();
+ break;
+
// Special case: VPERMPD - AVX2 vs AVX512-F case.
case Inst::kIdVpermpd:
mustUseEvex = opCount >= 3 && !operands[2].isImm();
@@ -1618,6 +1678,68 @@ UNIT(x86_inst_api_text) {
"Instructions do not match \"%s\" (#%u) != \"%s\" (#%u)", aName.data(), a, bName.data(), b);
}
}
+
+template<typename... Args>
+static Error queryRWInfoSimple(InstRWInfo* out, Arch arch, InstId instId, InstOptions options, Args&&... args) {
+ BaseInst inst(instId);
+ inst.addOptions(options);
+ Operand_ opArray[] = { std::forward<Args>(args)... };
+ return InstInternal::queryRWInfo(arch, inst, opArray, sizeof...(args), out);
+}
+
+UNIT(x86_inst_api_rm_feature) {
+ INFO("Verifying whether RM/feature is reported correctly for PEXTRW instruction");
+ {
+ InstRWInfo rwi;
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdPextrw, InstOptions::kNone, eax, mm1, imm(1));
+ EXPECT(rwi.rmFeature() == 0);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdPextrw, InstOptions::kNone, eax, xmm1, imm(1));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kSSE4_1);
+ }
+
+ INFO("Verifying whether RM/feature is reported correctly for AVX512 shift instructions");
+ {
+ InstRWInfo rwi;
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpslld, InstOptions::kNone, xmm1, xmm2, imm(8));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kAVX512_F);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpsllq, InstOptions::kNone, ymm1, ymm2, imm(8));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kAVX512_F);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpsrad, InstOptions::kNone, xmm1, xmm2, imm(8));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kAVX512_F);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpsrld, InstOptions::kNone, ymm1, ymm2, imm(8));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kAVX512_F);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpsrlq, InstOptions::kNone, xmm1, xmm2, imm(8));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kAVX512_F);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpslldq, InstOptions::kNone, xmm1, xmm2, imm(8));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kAVX512_BW);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpsllw, InstOptions::kNone, ymm1, ymm2, imm(8));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kAVX512_BW);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpsraw, InstOptions::kNone, xmm1, xmm2, imm(8));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kAVX512_BW);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpsrldq, InstOptions::kNone, ymm1, ymm2, imm(8));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kAVX512_BW);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpsrlw, InstOptions::kNone, xmm1, xmm2, imm(8));
+ EXPECT(rwi.rmFeature() == CpuFeatures::X86::kAVX512_BW);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpslld, InstOptions::kNone, xmm1, xmm2, xmm3);
+ EXPECT(rwi.rmFeature() == 0);
+
+ queryRWInfoSimple(&rwi, Arch::kX64, Inst::kIdVpsllw, InstOptions::kNone, xmm1, xmm2, xmm3);
+ EXPECT(rwi.rmFeature() == 0);
+ }
+}
#endif
ASMJIT_END_SUB_NAMESPACE
diff --git a/erts/emulator/asmjit/x86/x86instdb.cpp b/erts/emulator/asmjit/x86/x86instdb.cpp
index 3bf5d23ffd..ef65910d43 100644
--- a/erts/emulator/asmjit/x86/x86instdb.cpp
+++ b/erts/emulator/asmjit/x86/x86instdb.cpp
@@ -47,17 +47,9 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
#define E(PREFIX, OPCODE, ModO, LL, W, EvexW, N, TT) (O_ENCODE(Opcode::k##PREFIX, 0x##OPCODE, Opcode::kModO_##ModO, Opcode::kLL_##LL, Opcode::kW_##W, Opcode::kEvex_W_##EvexW, Opcode::kCDSHL_##N, Opcode::kCDTT_##TT) | Opcode::kMM_ForceEvex)
#define O_FPU(PREFIX, OPCODE, ModO) (Opcode::kFPU_##PREFIX | (0x##OPCODE & 0xFFu) | ((0x##OPCODE >> 8) << Opcode::kFPU_2B_Shift) | Opcode::kModO_##ModO)
-// Don't store `_nameDataIndex` if instruction names are disabled. Since some
-// APIs can use `_nameDataIndex` it's much safer if it's zero if it's not defined.
-#ifndef ASMJIT_NO_TEXT
- #define NAME_DATA_INDEX(Index) Index
-#else
- #define NAME_DATA_INDEX(Index) 0
-#endif
-
// Defines an X86 instruction.
-#define INST(id, encoding, opcode0, opcode1, mainOpcodeIndex, altOpcodeIndex, nameDataIndex, commomInfoIndex, additionalInfoIndex) { \
- uint32_t(NAME_DATA_INDEX(nameDataIndex)), \
+#define INST(id, encoding, opcode0, opcode1, mainOpcodeIndex, altOpcodeIndex, commomInfoIndex, additionalInfoIndex) { \
+ uint32_t(0), \
uint32_t(commomInfoIndex), \
uint32_t(additionalInfoIndex), \
uint8_t(InstDB::kEncoding##encoding), \
@@ -67,1674 +59,1674 @@ ASMJIT_BEGIN_SUB_NAMESPACE(x86)
}
const InstDB::InstInfo InstDB::_instInfoTable[] = {
- /*--------------------+--------------------+------------------+--------+------------------+--------+----+----+------+----+----+
- | Instruction | Instruction | Main Opcode | EVEX |Alternative Opcode| EVEX |Op0X|Op1X|Name-X|IdxA|IdxB|
- | Id & Name | Encoding | (pp+mmm|op/o|L|w|W|N|TT.)|--(pp+mmm|op/o|L|w|W|N|TT.)| (auto-generated) |
- +---------------------+--------------------+---------+----+-+-+-+-+----+---------+----+-+-+-+-+----+----+----+------+----+---*/
+ /*--------------------+--------------------+------------------+--------+------------------+--------+----+----+----+----+
+ | Instruction | Instruction | Main Opcode | EVEX |Alternative Opcode| EVEX |Op0X|Op1X|IdxA|IdxB|
+ | Id & Name | Encoding | (pp+mmm|op/o|L|w|W|N|TT.)|--(pp+mmm|op/o|L|w|W|N|TT.)| (auto-generated) |
+ +---------------------+--------------------+---------+----+-+-+-+-+----+---------+----+-+-+-+-+----+----+----+----+---*/
// ${InstInfo:Begin}
- INST(None , None , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), // #0
- INST(Aaa , X86Op_xAX , O(000000,37,_,_,_,_,_,_ ), 0 , 0 , 0 , 1 , 1 , 1 ), // #1
- INST(Aad , X86I_xAX , O(000000,D5,_,_,_,_,_,_ ), 0 , 0 , 0 , 5 , 2 , 1 ), // #2
- INST(Aam , X86I_xAX , O(000000,D4,_,_,_,_,_,_ ), 0 , 0 , 0 , 9 , 2 , 1 ), // #3
- INST(Aas , X86Op_xAX , O(000000,3F,_,_,_,_,_,_ ), 0 , 0 , 0 , 13 , 1 , 1 ), // #4
- INST(Adc , X86Arith , O(000000,10,2,_,x,_,_,_ ), 0 , 1 , 0 , 17 , 3 , 2 ), // #5
- INST(Adcx , X86Rm , O(660F38,F6,_,_,x,_,_,_ ), 0 , 2 , 0 , 21 , 4 , 3 ), // #6
- INST(Add , X86Arith , O(000000,00,0,_,x,_,_,_ ), 0 , 0 , 0 , 3146 , 3 , 1 ), // #7
- INST(Addpd , ExtRm , O(660F00,58,_,_,_,_,_,_ ), 0 , 3 , 0 , 5788 , 5 , 4 ), // #8
- INST(Addps , ExtRm , O(000F00,58,_,_,_,_,_,_ ), 0 , 4 , 0 , 5800 , 5 , 5 ), // #9
- INST(Addsd , ExtRm , O(F20F00,58,_,_,_,_,_,_ ), 0 , 5 , 0 , 6118 , 6 , 4 ), // #10
- INST(Addss , ExtRm , O(F30F00,58,_,_,_,_,_,_ ), 0 , 6 , 0 , 3283 , 7 , 5 ), // #11
- INST(Addsubpd , ExtRm , O(660F00,D0,_,_,_,_,_,_ ), 0 , 3 , 0 , 5410 , 5 , 6 ), // #12
- INST(Addsubps , ExtRm , O(F20F00,D0,_,_,_,_,_,_ ), 0 , 5 , 0 , 5422 , 5 , 6 ), // #13
- INST(Adox , X86Rm , O(F30F38,F6,_,_,x,_,_,_ ), 0 , 7 , 0 , 26 , 4 , 7 ), // #14
- INST(Aesdec , ExtRm , O(660F38,DE,_,_,_,_,_,_ ), 0 , 2 , 0 , 3352 , 5 , 8 ), // #15
- INST(Aesdeclast , ExtRm , O(660F38,DF,_,_,_,_,_,_ ), 0 , 2 , 0 , 3360 , 5 , 8 ), // #16
- INST(Aesenc , ExtRm , O(660F38,DC,_,_,_,_,_,_ ), 0 , 2 , 0 , 3372 , 5 , 8 ), // #17
- INST(Aesenclast , ExtRm , O(660F38,DD,_,_,_,_,_,_ ), 0 , 2 , 0 , 3380 , 5 , 8 ), // #18
- INST(Aesimc , ExtRm , O(660F38,DB,_,_,_,_,_,_ ), 0 , 2 , 0 , 3392 , 5 , 8 ), // #19
- INST(Aeskeygenassist , ExtRmi , O(660F3A,DF,_,_,_,_,_,_ ), 0 , 8 , 0 , 3400 , 8 , 8 ), // #20
- INST(And , X86Arith , O(000000,20,4,_,x,_,_,_ ), 0 , 9 , 0 , 2525 , 9 , 1 ), // #21
- INST(Andn , VexRvm_Wx , V(000F38,F2,_,0,x,_,_,_ ), 0 , 10 , 0 , 7789 , 10 , 9 ), // #22
- INST(Andnpd , ExtRm , O(660F00,55,_,_,_,_,_,_ ), 0 , 3 , 0 , 3433 , 5 , 4 ), // #23
- INST(Andnps , ExtRm , O(000F00,55,_,_,_,_,_,_ ), 0 , 4 , 0 , 3441 , 5 , 5 ), // #24
- INST(Andpd , ExtRm , O(660F00,54,_,_,_,_,_,_ ), 0 , 3 , 0 , 4745 , 11 , 4 ), // #25
- INST(Andps , ExtRm , O(000F00,54,_,_,_,_,_,_ ), 0 , 4 , 0 , 4755 , 11 , 5 ), // #26
- INST(Arpl , X86Mr_NoSize , O(000000,63,_,_,_,_,_,_ ), 0 , 0 , 0 , 31 , 12 , 10 ), // #27
- INST(Bextr , VexRmv_Wx , V(000F38,F7,_,0,x,_,_,_ ), 0 , 10 , 0 , 36 , 13 , 9 ), // #28
- INST(Blcfill , VexVm_Wx , V(XOP_M9,01,1,0,x,_,_,_ ), 0 , 11 , 0 , 42 , 14 , 11 ), // #29
- INST(Blci , VexVm_Wx , V(XOP_M9,02,6,0,x,_,_,_ ), 0 , 12 , 0 , 50 , 14 , 11 ), // #30
- INST(Blcic , VexVm_Wx , V(XOP_M9,01,5,0,x,_,_,_ ), 0 , 13 , 0 , 55 , 14 , 11 ), // #31
- INST(Blcmsk , VexVm_Wx , V(XOP_M9,02,1,0,x,_,_,_ ), 0 , 11 , 0 , 61 , 14 , 11 ), // #32
- INST(Blcs , VexVm_Wx , V(XOP_M9,01,3,0,x,_,_,_ ), 0 , 14 , 0 , 68 , 14 , 11 ), // #33
- INST(Blendpd , ExtRmi , O(660F3A,0D,_,_,_,_,_,_ ), 0 , 8 , 0 , 3483 , 8 , 12 ), // #34
- INST(Blendps , ExtRmi , O(660F3A,0C,_,_,_,_,_,_ ), 0 , 8 , 0 , 3492 , 8 , 12 ), // #35
- INST(Blendvpd , ExtRm_XMM0 , O(660F38,15,_,_,_,_,_,_ ), 0 , 2 , 0 , 3501 , 15 , 12 ), // #36
- INST(Blendvps , ExtRm_XMM0 , O(660F38,14,_,_,_,_,_,_ ), 0 , 2 , 0 , 3511 , 15 , 12 ), // #37
- INST(Blsfill , VexVm_Wx , V(XOP_M9,01,2,0,x,_,_,_ ), 0 , 15 , 0 , 73 , 14 , 11 ), // #38
- INST(Blsi , VexVm_Wx , V(000F38,F3,3,0,x,_,_,_ ), 0 , 16 , 0 , 81 , 14 , 9 ), // #39
- INST(Blsic , VexVm_Wx , V(XOP_M9,01,6,0,x,_,_,_ ), 0 , 12 , 0 , 86 , 14 , 11 ), // #40
- INST(Blsmsk , VexVm_Wx , V(000F38,F3,2,0,x,_,_,_ ), 0 , 17 , 0 , 92 , 14 , 9 ), // #41
- INST(Blsr , VexVm_Wx , V(000F38,F3,1,0,x,_,_,_ ), 0 , 18 , 0 , 99 , 14 , 9 ), // #42
- INST(Bndcl , X86Rm , O(F30F00,1A,_,_,_,_,_,_ ), 0 , 6 , 0 , 104 , 16 , 13 ), // #43
- INST(Bndcn , X86Rm , O(F20F00,1B,_,_,_,_,_,_ ), 0 , 5 , 0 , 110 , 16 , 13 ), // #44
- INST(Bndcu , X86Rm , O(F20F00,1A,_,_,_,_,_,_ ), 0 , 5 , 0 , 116 , 16 , 13 ), // #45
- INST(Bndldx , X86Rm , O(000F00,1A,_,_,_,_,_,_ ), 0 , 4 , 0 , 122 , 17 , 13 ), // #46
- INST(Bndmk , X86Rm , O(F30F00,1B,_,_,_,_,_,_ ), 0 , 6 , 0 , 129 , 18 , 13 ), // #47
- INST(Bndmov , X86Bndmov , O(660F00,1A,_,_,_,_,_,_ ), O(660F00,1B,_,_,_,_,_,_ ), 3 , 1 , 135 , 19 , 13 ), // #48
- INST(Bndstx , X86Mr , O(000F00,1B,_,_,_,_,_,_ ), 0 , 4 , 0 , 142 , 20 , 13 ), // #49
- INST(Bound , X86Rm , O(000000,62,_,_,_,_,_,_ ), 0 , 0 , 0 , 149 , 21 , 0 ), // #50
- INST(Bsf , X86Rm , O(000F00,BC,_,_,x,_,_,_ ), 0 , 4 , 0 , 155 , 22 , 1 ), // #51
- INST(Bsr , X86Rm , O(000F00,BD,_,_,x,_,_,_ ), 0 , 4 , 0 , 159 , 22 , 1 ), // #52
- INST(Bswap , X86Bswap , O(000F00,C8,_,_,x,_,_,_ ), 0 , 4 , 0 , 163 , 23 , 0 ), // #53
- INST(Bt , X86Bt , O(000F00,A3,_,_,x,_,_,_ ), O(000F00,BA,4,_,x,_,_,_ ), 4 , 2 , 169 , 24 , 14 ), // #54
- INST(Btc , X86Bt , O(000F00,BB,_,_,x,_,_,_ ), O(000F00,BA,7,_,x,_,_,_ ), 4 , 3 , 172 , 25 , 14 ), // #55
- INST(Btr , X86Bt , O(000F00,B3,_,_,x,_,_,_ ), O(000F00,BA,6,_,x,_,_,_ ), 4 , 4 , 176 , 25 , 14 ), // #56
- INST(Bts , X86Bt , O(000F00,AB,_,_,x,_,_,_ ), O(000F00,BA,5,_,x,_,_,_ ), 4 , 5 , 180 , 25 , 14 ), // #57
- INST(Bzhi , VexRmv_Wx , V(000F38,F5,_,0,x,_,_,_ ), 0 , 10 , 0 , 184 , 13 , 15 ), // #58
- INST(Call , X86Call , O(000000,FF,2,_,_,_,_,_ ), 0 , 1 , 0 , 3038 , 26 , 1 ), // #59
- INST(Cbw , X86Op_xAX , O(660000,98,_,_,_,_,_,_ ), 0 , 19 , 0 , 189 , 27 , 0 ), // #60
- INST(Cdq , X86Op_xDX_xAX , O(000000,99,_,_,_,_,_,_ ), 0 , 0 , 0 , 193 , 28 , 0 ), // #61
- INST(Cdqe , X86Op_xAX , O(000000,98,_,_,1,_,_,_ ), 0 , 20 , 0 , 197 , 29 , 0 ), // #62
- INST(Clac , X86Op , O(000F01,CA,_,_,_,_,_,_ ), 0 , 21 , 0 , 202 , 30 , 16 ), // #63
- INST(Clc , X86Op , O(000000,F8,_,_,_,_,_,_ ), 0 , 0 , 0 , 207 , 30 , 17 ), // #64
- INST(Cld , X86Op , O(000000,FC,_,_,_,_,_,_ ), 0 , 0 , 0 , 211 , 30 , 18 ), // #65
- INST(Cldemote , X86M_Only , O(000F00,1C,0,_,_,_,_,_ ), 0 , 4 , 0 , 215 , 31 , 19 ), // #66
- INST(Clflush , X86M_Only , O(000F00,AE,7,_,_,_,_,_ ), 0 , 22 , 0 , 224 , 31 , 20 ), // #67
- INST(Clflushopt , X86M_Only , O(660F00,AE,7,_,_,_,_,_ ), 0 , 23 , 0 , 232 , 31 , 21 ), // #68
- INST(Clgi , X86Op , O(000F01,DD,_,_,_,_,_,_ ), 0 , 21 , 0 , 243 , 30 , 22 ), // #69
- INST(Cli , X86Op , O(000000,FA,_,_,_,_,_,_ ), 0 , 0 , 0 , 248 , 30 , 23 ), // #70
- INST(Clrssbsy , X86M_Only , O(F30F00,AE,6,_,_,_,_,_ ), 0 , 24 , 0 , 252 , 32 , 24 ), // #71
- INST(Clts , X86Op , O(000F00,06,_,_,_,_,_,_ ), 0 , 4 , 0 , 261 , 30 , 0 ), // #72
- INST(Clui , X86Op , O(F30F01,EE,_,_,_,_,_,_ ), 0 , 25 , 0 , 266 , 33 , 25 ), // #73
- INST(Clwb , X86M_Only , O(660F00,AE,6,_,_,_,_,_ ), 0 , 26 , 0 , 271 , 31 , 26 ), // #74
- INST(Clzero , X86Op_MemZAX , O(000F01,FC,_,_,_,_,_,_ ), 0 , 21 , 0 , 276 , 34 , 27 ), // #75
- INST(Cmc , X86Op , O(000000,F5,_,_,_,_,_,_ ), 0 , 0 , 0 , 283 , 30 , 28 ), // #76
- INST(Cmova , X86Rm , O(000F00,47,_,_,x,_,_,_ ), 0 , 4 , 0 , 287 , 22 , 29 ), // #77
- INST(Cmovae , X86Rm , O(000F00,43,_,_,x,_,_,_ ), 0 , 4 , 0 , 293 , 22 , 30 ), // #78
- INST(Cmovb , X86Rm , O(000F00,42,_,_,x,_,_,_ ), 0 , 4 , 0 , 648 , 22 , 30 ), // #79
- INST(Cmovbe , X86Rm , O(000F00,46,_,_,x,_,_,_ ), 0 , 4 , 0 , 655 , 22 , 29 ), // #80
- INST(Cmovc , X86Rm , O(000F00,42,_,_,x,_,_,_ ), 0 , 4 , 0 , 300 , 22 , 30 ), // #81
- INST(Cmove , X86Rm , O(000F00,44,_,_,x,_,_,_ ), 0 , 4 , 0 , 663 , 22 , 31 ), // #82
- INST(Cmovg , X86Rm , O(000F00,4F,_,_,x,_,_,_ ), 0 , 4 , 0 , 306 , 22 , 32 ), // #83
- INST(Cmovge , X86Rm , O(000F00,4D,_,_,x,_,_,_ ), 0 , 4 , 0 , 312 , 22 , 33 ), // #84
- INST(Cmovl , X86Rm , O(000F00,4C,_,_,x,_,_,_ ), 0 , 4 , 0 , 319 , 22 , 33 ), // #85
- INST(Cmovle , X86Rm , O(000F00,4E,_,_,x,_,_,_ ), 0 , 4 , 0 , 325 , 22 , 32 ), // #86
- INST(Cmovna , X86Rm , O(000F00,46,_,_,x,_,_,_ ), 0 , 4 , 0 , 332 , 22 , 29 ), // #87
- INST(Cmovnae , X86Rm , O(000F00,42,_,_,x,_,_,_ ), 0 , 4 , 0 , 339 , 22 , 30 ), // #88
- INST(Cmovnb , X86Rm , O(000F00,43,_,_,x,_,_,_ ), 0 , 4 , 0 , 670 , 22 , 30 ), // #89
- INST(Cmovnbe , X86Rm , O(000F00,47,_,_,x,_,_,_ ), 0 , 4 , 0 , 678 , 22 , 29 ), // #90
- INST(Cmovnc , X86Rm , O(000F00,43,_,_,x,_,_,_ ), 0 , 4 , 0 , 347 , 22 , 30 ), // #91
- INST(Cmovne , X86Rm , O(000F00,45,_,_,x,_,_,_ ), 0 , 4 , 0 , 687 , 22 , 31 ), // #92
- INST(Cmovng , X86Rm , O(000F00,4E,_,_,x,_,_,_ ), 0 , 4 , 0 , 354 , 22 , 32 ), // #93
- INST(Cmovnge , X86Rm , O(000F00,4C,_,_,x,_,_,_ ), 0 , 4 , 0 , 361 , 22 , 33 ), // #94
- INST(Cmovnl , X86Rm , O(000F00,4D,_,_,x,_,_,_ ), 0 , 4 , 0 , 369 , 22 , 33 ), // #95
- INST(Cmovnle , X86Rm , O(000F00,4F,_,_,x,_,_,_ ), 0 , 4 , 0 , 376 , 22 , 32 ), // #96
- INST(Cmovno , X86Rm , O(000F00,41,_,_,x,_,_,_ ), 0 , 4 , 0 , 384 , 22 , 34 ), // #97
- INST(Cmovnp , X86Rm , O(000F00,4B,_,_,x,_,_,_ ), 0 , 4 , 0 , 391 , 22 , 35 ), // #98
- INST(Cmovns , X86Rm , O(000F00,49,_,_,x,_,_,_ ), 0 , 4 , 0 , 398 , 22 , 36 ), // #99
- INST(Cmovnz , X86Rm , O(000F00,45,_,_,x,_,_,_ ), 0 , 4 , 0 , 405 , 22 , 31 ), // #100
- INST(Cmovo , X86Rm , O(000F00,40,_,_,x,_,_,_ ), 0 , 4 , 0 , 412 , 22 , 34 ), // #101
- INST(Cmovp , X86Rm , O(000F00,4A,_,_,x,_,_,_ ), 0 , 4 , 0 , 418 , 22 , 35 ), // #102
- INST(Cmovpe , X86Rm , O(000F00,4A,_,_,x,_,_,_ ), 0 , 4 , 0 , 424 , 22 , 35 ), // #103
- INST(Cmovpo , X86Rm , O(000F00,4B,_,_,x,_,_,_ ), 0 , 4 , 0 , 431 , 22 , 35 ), // #104
- INST(Cmovs , X86Rm , O(000F00,48,_,_,x,_,_,_ ), 0 , 4 , 0 , 438 , 22 , 36 ), // #105
- INST(Cmovz , X86Rm , O(000F00,44,_,_,x,_,_,_ ), 0 , 4 , 0 , 444 , 22 , 31 ), // #106
- INST(Cmp , X86Arith , O(000000,38,7,_,x,_,_,_ ), 0 , 27 , 0 , 450 , 35 , 1 ), // #107
- INST(Cmppd , ExtRmi , O(660F00,C2,_,_,_,_,_,_ ), 0 , 3 , 0 , 3737 , 8 , 4 ), // #108
- INST(Cmpps , ExtRmi , O(000F00,C2,_,_,_,_,_,_ ), 0 , 4 , 0 , 3751 , 8 , 5 ), // #109
- INST(Cmps , X86StrMm , O(000000,A6,_,_,_,_,_,_ ), 0 , 0 , 0 , 454 , 36 , 37 ), // #110
- INST(Cmpsd , ExtRmi , O(F20F00,C2,_,_,_,_,_,_ ), 0 , 5 , 0 , 3758 , 37 , 4 ), // #111
- INST(Cmpss , ExtRmi , O(F30F00,C2,_,_,_,_,_,_ ), 0 , 6 , 0 , 3772 , 38 , 5 ), // #112
- INST(Cmpxchg , X86Cmpxchg , O(000F00,B0,_,_,x,_,_,_ ), 0 , 4 , 0 , 459 , 39 , 38 ), // #113
- INST(Cmpxchg16b , X86Cmpxchg8b_16b , O(000F00,C7,1,_,1,_,_,_ ), 0 , 28 , 0 , 467 , 40 , 39 ), // #114
- INST(Cmpxchg8b , X86Cmpxchg8b_16b , O(000F00,C7,1,_,_,_,_,_ ), 0 , 29 , 0 , 478 , 41 , 40 ), // #115
- INST(Comisd , ExtRm , O(660F00,2F,_,_,_,_,_,_ ), 0 , 3 , 0 , 11391, 6 , 41 ), // #116
- INST(Comiss , ExtRm , O(000F00,2F,_,_,_,_,_,_ ), 0 , 4 , 0 , 11409, 7 , 42 ), // #117
- INST(Cpuid , X86Op , O(000F00,A2,_,_,_,_,_,_ ), 0 , 4 , 0 , 488 , 42 , 43 ), // #118
- INST(Cqo , X86Op_xDX_xAX , O(000000,99,_,_,1,_,_,_ ), 0 , 20 , 0 , 494 , 43 , 0 ), // #119
- INST(Crc32 , X86Crc , O(F20F38,F0,_,_,x,_,_,_ ), 0 , 30 , 0 , 498 , 44 , 44 ), // #120
- INST(Cvtdq2pd , ExtRm , O(F30F00,E6,_,_,_,_,_,_ ), 0 , 6 , 0 , 3827 , 6 , 4 ), // #121
- INST(Cvtdq2ps , ExtRm , O(000F00,5B,_,_,_,_,_,_ ), 0 , 4 , 0 , 3847 , 5 , 4 ), // #122
- INST(Cvtpd2dq , ExtRm , O(F20F00,E6,_,_,_,_,_,_ ), 0 , 5 , 0 , 3886 , 5 , 4 ), // #123
- INST(Cvtpd2pi , ExtRm , O(660F00,2D,_,_,_,_,_,_ ), 0 , 3 , 0 , 504 , 45 , 4 ), // #124
- INST(Cvtpd2ps , ExtRm , O(660F00,5A,_,_,_,_,_,_ ), 0 , 3 , 0 , 3906 , 5 , 4 ), // #125
- INST(Cvtpi2pd , ExtRm , O(660F00,2A,_,_,_,_,_,_ ), 0 , 3 , 0 , 513 , 46 , 4 ), // #126
- INST(Cvtpi2ps , ExtRm , O(000F00,2A,_,_,_,_,_,_ ), 0 , 4 , 0 , 522 , 46 , 5 ), // #127
- INST(Cvtps2dq , ExtRm , O(660F00,5B,_,_,_,_,_,_ ), 0 , 3 , 0 , 4040 , 5 , 4 ), // #128
- INST(Cvtps2pd , ExtRm , O(000F00,5A,_,_,_,_,_,_ ), 0 , 4 , 0 , 4050 , 6 , 4 ), // #129
- INST(Cvtps2pi , ExtRm , O(000F00,2D,_,_,_,_,_,_ ), 0 , 4 , 0 , 531 , 47 , 5 ), // #130
- INST(Cvtsd2si , ExtRm_Wx_GpqOnly , O(F20F00,2D,_,_,x,_,_,_ ), 0 , 5 , 0 , 4153 , 48 , 4 ), // #131
- INST(Cvtsd2ss , ExtRm , O(F20F00,5A,_,_,_,_,_,_ ), 0 , 5 , 0 , 4163 , 6 , 4 ), // #132
- INST(Cvtsi2sd , ExtRm_Wx , O(F20F00,2A,_,_,x,_,_,_ ), 0 , 5 , 0 , 4225 , 49 , 4 ), // #133
- INST(Cvtsi2ss , ExtRm_Wx , O(F30F00,2A,_,_,x,_,_,_ ), 0 , 6 , 0 , 4245 , 49 , 5 ), // #134
- INST(Cvtss2sd , ExtRm , O(F30F00,5A,_,_,_,_,_,_ ), 0 , 6 , 0 , 4255 , 7 , 4 ), // #135
- INST(Cvtss2si , ExtRm_Wx_GpqOnly , O(F30F00,2D,_,_,x,_,_,_ ), 0 , 6 , 0 , 4275 , 50 , 5 ), // #136
- INST(Cvttpd2dq , ExtRm , O(660F00,E6,_,_,_,_,_,_ ), 0 , 3 , 0 , 4296 , 5 , 4 ), // #137
- INST(Cvttpd2pi , ExtRm , O(660F00,2C,_,_,_,_,_,_ ), 0 , 3 , 0 , 540 , 45 , 4 ), // #138
- INST(Cvttps2dq , ExtRm , O(F30F00,5B,_,_,_,_,_,_ ), 0 , 6 , 0 , 4409 , 5 , 4 ), // #139
- INST(Cvttps2pi , ExtRm , O(000F00,2C,_,_,_,_,_,_ ), 0 , 4 , 0 , 550 , 47 , 5 ), // #140
- INST(Cvttsd2si , ExtRm_Wx_GpqOnly , O(F20F00,2C,_,_,x,_,_,_ ), 0 , 5 , 0 , 4455 , 48 , 4 ), // #141
- INST(Cvttss2si , ExtRm_Wx_GpqOnly , O(F30F00,2C,_,_,x,_,_,_ ), 0 , 6 , 0 , 4501 , 50 , 5 ), // #142
- INST(Cwd , X86Op_xDX_xAX , O(660000,99,_,_,_,_,_,_ ), 0 , 19 , 0 , 560 , 51 , 0 ), // #143
- INST(Cwde , X86Op_xAX , O(000000,98,_,_,_,_,_,_ ), 0 , 0 , 0 , 564 , 52 , 0 ), // #144
- INST(Daa , X86Op , O(000000,27,_,_,_,_,_,_ ), 0 , 0 , 0 , 569 , 1 , 1 ), // #145
- INST(Das , X86Op , O(000000,2F,_,_,_,_,_,_ ), 0 , 0 , 0 , 573 , 1 , 1 ), // #146
- INST(Dec , X86IncDec , O(000000,FE,1,_,x,_,_,_ ), O(000000,48,_,_,x,_,_,_ ), 31 , 6 , 3355 , 53 , 45 ), // #147
- INST(Div , X86M_GPB_MulDiv , O(000000,F6,6,_,x,_,_,_ ), 0 , 32 , 0 , 810 , 54 , 1 ), // #148
- INST(Divpd , ExtRm , O(660F00,5E,_,_,_,_,_,_ ), 0 , 3 , 0 , 4652 , 5 , 4 ), // #149
- INST(Divps , ExtRm , O(000F00,5E,_,_,_,_,_,_ ), 0 , 4 , 0 , 4666 , 5 , 5 ), // #150
- INST(Divsd , ExtRm , O(F20F00,5E,_,_,_,_,_,_ ), 0 , 5 , 0 , 4673 , 6 , 4 ), // #151
- INST(Divss , ExtRm , O(F30F00,5E,_,_,_,_,_,_ ), 0 , 6 , 0 , 4687 , 7 , 5 ), // #152
- INST(Dppd , ExtRmi , O(660F3A,41,_,_,_,_,_,_ ), 0 , 8 , 0 , 4704 , 8 , 12 ), // #153
- INST(Dpps , ExtRmi , O(660F3A,40,_,_,_,_,_,_ ), 0 , 8 , 0 , 4710 , 8 , 12 ), // #154
- INST(Emms , X86Op , O(000F00,77,_,_,_,_,_,_ ), 0 , 4 , 0 , 778 , 55 , 46 ), // #155
- INST(Endbr32 , X86Op_Mod11RM , O(F30F00,1E,7,_,_,_,_,3 ), 0 , 33 , 0 , 577 , 30 , 47 ), // #156
- INST(Endbr64 , X86Op_Mod11RM , O(F30F00,1E,7,_,_,_,_,2 ), 0 , 34 , 0 , 585 , 30 , 47 ), // #157
- INST(Enqcmd , X86EnqcmdMovdir64b , O(F20F38,F8,_,_,_,_,_,_ ), 0 , 30 , 0 , 593 , 56 , 48 ), // #158
- INST(Enqcmds , X86EnqcmdMovdir64b , O(F30F38,F8,_,_,_,_,_,_ ), 0 , 7 , 0 , 600 , 56 , 48 ), // #159
- INST(Enter , X86Enter , O(000000,C8,_,_,_,_,_,_ ), 0 , 0 , 0 , 3046 , 57 , 0 ), // #160
- INST(Extractps , ExtExtract , O(660F3A,17,_,_,_,_,_,_ ), 0 , 8 , 0 , 4900 , 58 , 12 ), // #161
- INST(Extrq , ExtExtrq , O(660F00,79,_,_,_,_,_,_ ), O(660F00,78,0,_,_,_,_,_ ), 3 , 7 , 8625 , 59 , 49 ), // #162
- INST(F2xm1 , FpuOp , O_FPU(00,D9F0,_) , 0 , 35 , 0 , 608 , 30 , 0 ), // #163
- INST(Fabs , FpuOp , O_FPU(00,D9E1,_) , 0 , 35 , 0 , 614 , 30 , 0 ), // #164
- INST(Fadd , FpuArith , O_FPU(00,C0C0,0) , 0 , 36 , 0 , 2121 , 60 , 0 ), // #165
- INST(Faddp , FpuRDef , O_FPU(00,DEC0,_) , 0 , 37 , 0 , 619 , 61 , 0 ), // #166
- INST(Fbld , X86M_Only , O_FPU(00,00DF,4) , 0 , 38 , 0 , 625 , 62 , 0 ), // #167
- INST(Fbstp , X86M_Only , O_FPU(00,00DF,6) , 0 , 39 , 0 , 630 , 62 , 0 ), // #168
- INST(Fchs , FpuOp , O_FPU(00,D9E0,_) , 0 , 35 , 0 , 636 , 30 , 0 ), // #169
- INST(Fclex , FpuOp , O_FPU(9B,DBE2,_) , 0 , 40 , 0 , 641 , 30 , 0 ), // #170
- INST(Fcmovb , FpuR , O_FPU(00,DAC0,_) , 0 , 41 , 0 , 647 , 63 , 30 ), // #171
- INST(Fcmovbe , FpuR , O_FPU(00,DAD0,_) , 0 , 41 , 0 , 654 , 63 , 29 ), // #172
- INST(Fcmove , FpuR , O_FPU(00,DAC8,_) , 0 , 41 , 0 , 662 , 63 , 31 ), // #173
- INST(Fcmovnb , FpuR , O_FPU(00,DBC0,_) , 0 , 42 , 0 , 669 , 63 , 30 ), // #174
- INST(Fcmovnbe , FpuR , O_FPU(00,DBD0,_) , 0 , 42 , 0 , 677 , 63 , 29 ), // #175
- INST(Fcmovne , FpuR , O_FPU(00,DBC8,_) , 0 , 42 , 0 , 686 , 63 , 31 ), // #176
- INST(Fcmovnu , FpuR , O_FPU(00,DBD8,_) , 0 , 42 , 0 , 694 , 63 , 35 ), // #177
- INST(Fcmovu , FpuR , O_FPU(00,DAD8,_) , 0 , 41 , 0 , 702 , 63 , 35 ), // #178
- INST(Fcom , FpuCom , O_FPU(00,D0D0,2) , 0 , 43 , 0 , 709 , 64 , 0 ), // #179
- INST(Fcomi , FpuR , O_FPU(00,DBF0,_) , 0 , 42 , 0 , 714 , 63 , 50 ), // #180
- INST(Fcomip , FpuR , O_FPU(00,DFF0,_) , 0 , 44 , 0 , 720 , 63 , 50 ), // #181
- INST(Fcomp , FpuCom , O_FPU(00,D8D8,3) , 0 , 45 , 0 , 727 , 64 , 0 ), // #182
- INST(Fcompp , FpuOp , O_FPU(00,DED9,_) , 0 , 37 , 0 , 733 , 30 , 0 ), // #183
- INST(Fcos , FpuOp , O_FPU(00,D9FF,_) , 0 , 35 , 0 , 740 , 30 , 0 ), // #184
- INST(Fdecstp , FpuOp , O_FPU(00,D9F6,_) , 0 , 35 , 0 , 745 , 30 , 0 ), // #185
- INST(Fdiv , FpuArith , O_FPU(00,F0F8,6) , 0 , 46 , 0 , 753 , 60 , 0 ), // #186
- INST(Fdivp , FpuRDef , O_FPU(00,DEF8,_) , 0 , 37 , 0 , 758 , 61 , 0 ), // #187
- INST(Fdivr , FpuArith , O_FPU(00,F8F0,7) , 0 , 47 , 0 , 764 , 60 , 0 ), // #188
- INST(Fdivrp , FpuRDef , O_FPU(00,DEF0,_) , 0 , 37 , 0 , 770 , 61 , 0 ), // #189
- INST(Femms , X86Op , O(000F00,0E,_,_,_,_,_,_ ), 0 , 4 , 0 , 777 , 30 , 51 ), // #190
- INST(Ffree , FpuR , O_FPU(00,DDC0,_) , 0 , 48 , 0 , 783 , 63 , 0 ), // #191
- INST(Fiadd , FpuM , O_FPU(00,00DA,0) , 0 , 49 , 0 , 789 , 65 , 0 ), // #192
- INST(Ficom , FpuM , O_FPU(00,00DA,2) , 0 , 50 , 0 , 795 , 65 , 0 ), // #193
- INST(Ficomp , FpuM , O_FPU(00,00DA,3) , 0 , 51 , 0 , 801 , 65 , 0 ), // #194
- INST(Fidiv , FpuM , O_FPU(00,00DA,6) , 0 , 39 , 0 , 808 , 65 , 0 ), // #195
- INST(Fidivr , FpuM , O_FPU(00,00DA,7) , 0 , 52 , 0 , 814 , 65 , 0 ), // #196
- INST(Fild , FpuM , O_FPU(00,00DB,0) , O_FPU(00,00DF,5) , 49 , 8 , 821 , 66 , 0 ), // #197
- INST(Fimul , FpuM , O_FPU(00,00DA,1) , 0 , 53 , 0 , 826 , 65 , 0 ), // #198
- INST(Fincstp , FpuOp , O_FPU(00,D9F7,_) , 0 , 35 , 0 , 832 , 30 , 0 ), // #199
- INST(Finit , FpuOp , O_FPU(9B,DBE3,_) , 0 , 40 , 0 , 840 , 30 , 0 ), // #200
- INST(Fist , FpuM , O_FPU(00,00DB,2) , 0 , 50 , 0 , 846 , 65 , 0 ), // #201
- INST(Fistp , FpuM , O_FPU(00,00DB,3) , O_FPU(00,00DF,7) , 51 , 9 , 851 , 66 , 0 ), // #202
- INST(Fisttp , FpuM , O_FPU(00,00DB,1) , O_FPU(00,00DD,1) , 53 , 10 , 857 , 66 , 6 ), // #203
- INST(Fisub , FpuM , O_FPU(00,00DA,4) , 0 , 38 , 0 , 864 , 65 , 0 ), // #204
- INST(Fisubr , FpuM , O_FPU(00,00DA,5) , 0 , 54 , 0 , 870 , 65 , 0 ), // #205
- INST(Fld , FpuFldFst , O_FPU(00,00D9,0) , O_FPU(00,00DB,5) , 49 , 11 , 877 , 67 , 0 ), // #206
- INST(Fld1 , FpuOp , O_FPU(00,D9E8,_) , 0 , 35 , 0 , 881 , 30 , 0 ), // #207
- INST(Fldcw , X86M_Only , O_FPU(00,00D9,5) , 0 , 54 , 0 , 886 , 68 , 0 ), // #208
- INST(Fldenv , X86M_Only , O_FPU(00,00D9,4) , 0 , 38 , 0 , 892 , 69 , 0 ), // #209
- INST(Fldl2e , FpuOp , O_FPU(00,D9EA,_) , 0 , 35 , 0 , 899 , 30 , 0 ), // #210
- INST(Fldl2t , FpuOp , O_FPU(00,D9E9,_) , 0 , 35 , 0 , 906 , 30 , 0 ), // #211
- INST(Fldlg2 , FpuOp , O_FPU(00,D9EC,_) , 0 , 35 , 0 , 913 , 30 , 0 ), // #212
- INST(Fldln2 , FpuOp , O_FPU(00,D9ED,_) , 0 , 35 , 0 , 920 , 30 , 0 ), // #213
- INST(Fldpi , FpuOp , O_FPU(00,D9EB,_) , 0 , 35 , 0 , 927 , 30 , 0 ), // #214
- INST(Fldz , FpuOp , O_FPU(00,D9EE,_) , 0 , 35 , 0 , 933 , 30 , 0 ), // #215
- INST(Fmul , FpuArith , O_FPU(00,C8C8,1) , 0 , 55 , 0 , 2163 , 60 , 0 ), // #216
- INST(Fmulp , FpuRDef , O_FPU(00,DEC8,_) , 0 , 37 , 0 , 938 , 61 , 0 ), // #217
- INST(Fnclex , FpuOp , O_FPU(00,DBE2,_) , 0 , 42 , 0 , 944 , 30 , 0 ), // #218
- INST(Fninit , FpuOp , O_FPU(00,DBE3,_) , 0 , 42 , 0 , 951 , 30 , 0 ), // #219
- INST(Fnop , FpuOp , O_FPU(00,D9D0,_) , 0 , 35 , 0 , 958 , 30 , 0 ), // #220
- INST(Fnsave , X86M_Only , O_FPU(00,00DD,6) , 0 , 39 , 0 , 963 , 69 , 0 ), // #221
- INST(Fnstcw , X86M_Only , O_FPU(00,00D9,7) , 0 , 52 , 0 , 970 , 68 , 0 ), // #222
- INST(Fnstenv , X86M_Only , O_FPU(00,00D9,6) , 0 , 39 , 0 , 977 , 69 , 0 ), // #223
- INST(Fnstsw , FpuStsw , O_FPU(00,00DD,7) , O_FPU(00,DFE0,_) , 52 , 12 , 985 , 70 , 0 ), // #224
- INST(Fpatan , FpuOp , O_FPU(00,D9F3,_) , 0 , 35 , 0 , 992 , 30 , 0 ), // #225
- INST(Fprem , FpuOp , O_FPU(00,D9F8,_) , 0 , 35 , 0 , 999 , 30 , 0 ), // #226
- INST(Fprem1 , FpuOp , O_FPU(00,D9F5,_) , 0 , 35 , 0 , 1005 , 30 , 0 ), // #227
- INST(Fptan , FpuOp , O_FPU(00,D9F2,_) , 0 , 35 , 0 , 1012 , 30 , 0 ), // #228
- INST(Frndint , FpuOp , O_FPU(00,D9FC,_) , 0 , 35 , 0 , 1018 , 30 , 0 ), // #229
- INST(Frstor , X86M_Only , O_FPU(00,00DD,4) , 0 , 38 , 0 , 1026 , 69 , 0 ), // #230
- INST(Fsave , X86M_Only , O_FPU(9B,00DD,6) , 0 , 56 , 0 , 1033 , 69 , 0 ), // #231
- INST(Fscale , FpuOp , O_FPU(00,D9FD,_) , 0 , 35 , 0 , 1039 , 30 , 0 ), // #232
- INST(Fsin , FpuOp , O_FPU(00,D9FE,_) , 0 , 35 , 0 , 1046 , 30 , 0 ), // #233
- INST(Fsincos , FpuOp , O_FPU(00,D9FB,_) , 0 , 35 , 0 , 1051 , 30 , 0 ), // #234
- INST(Fsqrt , FpuOp , O_FPU(00,D9FA,_) , 0 , 35 , 0 , 1059 , 30 , 0 ), // #235
- INST(Fst , FpuFldFst , O_FPU(00,00D9,2) , 0 , 50 , 0 , 1065 , 71 , 0 ), // #236
- INST(Fstcw , X86M_Only , O_FPU(9B,00D9,7) , 0 , 57 , 0 , 1069 , 68 , 0 ), // #237
- INST(Fstenv , X86M_Only , O_FPU(9B,00D9,6) , 0 , 56 , 0 , 1075 , 69 , 0 ), // #238
- INST(Fstp , FpuFldFst , O_FPU(00,00D9,3) , O(000000,DB,7,_,_,_,_,_ ), 51 , 13 , 1082 , 67 , 0 ), // #239
- INST(Fstsw , FpuStsw , O_FPU(9B,00DD,7) , O_FPU(9B,DFE0,_) , 57 , 14 , 1087 , 70 , 0 ), // #240
- INST(Fsub , FpuArith , O_FPU(00,E0E8,4) , 0 , 58 , 0 , 2241 , 60 , 0 ), // #241
- INST(Fsubp , FpuRDef , O_FPU(00,DEE8,_) , 0 , 37 , 0 , 1093 , 61 , 0 ), // #242
- INST(Fsubr , FpuArith , O_FPU(00,E8E0,5) , 0 , 59 , 0 , 2247 , 60 , 0 ), // #243
- INST(Fsubrp , FpuRDef , O_FPU(00,DEE0,_) , 0 , 37 , 0 , 1099 , 61 , 0 ), // #244
- INST(Ftst , FpuOp , O_FPU(00,D9E4,_) , 0 , 35 , 0 , 1106 , 30 , 0 ), // #245
- INST(Fucom , FpuRDef , O_FPU(00,DDE0,_) , 0 , 48 , 0 , 1111 , 61 , 0 ), // #246
- INST(Fucomi , FpuR , O_FPU(00,DBE8,_) , 0 , 42 , 0 , 1117 , 63 , 50 ), // #247
- INST(Fucomip , FpuR , O_FPU(00,DFE8,_) , 0 , 44 , 0 , 1124 , 63 , 50 ), // #248
- INST(Fucomp , FpuRDef , O_FPU(00,DDE8,_) , 0 , 48 , 0 , 1132 , 61 , 0 ), // #249
- INST(Fucompp , FpuOp , O_FPU(00,DAE9,_) , 0 , 41 , 0 , 1139 , 30 , 0 ), // #250
- INST(Fwait , X86Op , O_FPU(00,009B,_) , 0 , 49 , 0 , 1147 , 30 , 0 ), // #251
- INST(Fxam , FpuOp , O_FPU(00,D9E5,_) , 0 , 35 , 0 , 1153 , 30 , 0 ), // #252
- INST(Fxch , FpuR , O_FPU(00,D9C8,_) , 0 , 35 , 0 , 1158 , 61 , 0 ), // #253
- INST(Fxrstor , X86M_Only , O(000F00,AE,1,_,_,_,_,_ ), 0 , 29 , 0 , 1163 , 69 , 52 ), // #254
- INST(Fxrstor64 , X86M_Only , O(000F00,AE,1,_,1,_,_,_ ), 0 , 28 , 0 , 1171 , 72 , 52 ), // #255
- INST(Fxsave , X86M_Only , O(000F00,AE,0,_,_,_,_,_ ), 0 , 4 , 0 , 1181 , 69 , 52 ), // #256
- INST(Fxsave64 , X86M_Only , O(000F00,AE,0,_,1,_,_,_ ), 0 , 60 , 0 , 1188 , 72 , 52 ), // #257
- INST(Fxtract , FpuOp , O_FPU(00,D9F4,_) , 0 , 35 , 0 , 1197 , 30 , 0 ), // #258
- INST(Fyl2x , FpuOp , O_FPU(00,D9F1,_) , 0 , 35 , 0 , 1205 , 30 , 0 ), // #259
- INST(Fyl2xp1 , FpuOp , O_FPU(00,D9F9,_) , 0 , 35 , 0 , 1211 , 30 , 0 ), // #260
- INST(Getsec , X86Op , O(000F00,37,_,_,_,_,_,_ ), 0 , 4 , 0 , 1219 , 30 , 53 ), // #261
- INST(Gf2p8affineinvqb , ExtRmi , O(660F3A,CF,_,_,_,_,_,_ ), 0 , 8 , 0 , 6789 , 8 , 54 ), // #262
- INST(Gf2p8affineqb , ExtRmi , O(660F3A,CE,_,_,_,_,_,_ ), 0 , 8 , 0 , 6807 , 8 , 54 ), // #263
- INST(Gf2p8mulb , ExtRm , O(660F38,CF,_,_,_,_,_,_ ), 0 , 2 , 0 , 6822 , 5 , 54 ), // #264
- INST(Haddpd , ExtRm , O(660F00,7C,_,_,_,_,_,_ ), 0 , 3 , 0 , 6833 , 5 , 6 ), // #265
- INST(Haddps , ExtRm , O(F20F00,7C,_,_,_,_,_,_ ), 0 , 5 , 0 , 6841 , 5 , 6 ), // #266
- INST(Hlt , X86Op , O(000000,F4,_,_,_,_,_,_ ), 0 , 0 , 0 , 1226 , 30 , 0 ), // #267
- INST(Hreset , X86Op_Mod11RM_I8 , O(F30F3A,F0,0,_,_,_,_,_ ), 0 , 61 , 0 , 1230 , 73 , 55 ), // #268
- INST(Hsubpd , ExtRm , O(660F00,7D,_,_,_,_,_,_ ), 0 , 3 , 0 , 6849 , 5 , 6 ), // #269
- INST(Hsubps , ExtRm , O(F20F00,7D,_,_,_,_,_,_ ), 0 , 5 , 0 , 6857 , 5 , 6 ), // #270
- INST(Idiv , X86M_GPB_MulDiv , O(000000,F6,7,_,x,_,_,_ ), 0 , 27 , 0 , 809 , 54 , 1 ), // #271
- INST(Imul , X86Imul , O(000000,F6,5,_,x,_,_,_ ), 0 , 62 , 0 , 827 , 74 , 1 ), // #272
- INST(In , X86In , O(000000,EC,_,_,_,_,_,_ ), O(000000,E4,_,_,_,_,_,_ ), 0 , 15 , 11572, 75 , 0 ), // #273
- INST(Inc , X86IncDec , O(000000,FE,0,_,x,_,_,_ ), O(000000,40,_,_,x,_,_,_ ), 0 , 16 , 1237 , 53 , 45 ), // #274
- INST(Incsspd , X86M , O(F30F00,AE,5,_,0,_,_,_ ), 0 , 63 , 0 , 1241 , 76 , 56 ), // #275
- INST(Incsspq , X86M , O(F30F00,AE,5,_,1,_,_,_ ), 0 , 64 , 0 , 1249 , 77 , 56 ), // #276
- INST(Ins , X86Ins , O(000000,6C,_,_,_,_,_,_ ), 0 , 0 , 0 , 1916 , 78 , 0 ), // #277
- INST(Insertps , ExtRmi , O(660F3A,21,_,_,_,_,_,_ ), 0 , 8 , 0 , 6993 , 38 , 12 ), // #278
- INST(Insertq , ExtInsertq , O(F20F00,79,_,_,_,_,_,_ ), O(F20F00,78,_,_,_,_,_,_ ), 5 , 17 , 1257 , 79 , 49 ), // #279
- INST(Int , X86Int , O(000000,CD,_,_,_,_,_,_ ), 0 , 0 , 0 , 1022 , 80 , 0 ), // #280
- INST(Int3 , X86Op , O(000000,CC,_,_,_,_,_,_ ), 0 , 0 , 0 , 1265 , 30 , 0 ), // #281
- INST(Into , X86Op , O(000000,CE,_,_,_,_,_,_ ), 0 , 0 , 0 , 1270 , 81 , 57 ), // #282
- INST(Invd , X86Op , O(000F00,08,_,_,_,_,_,_ ), 0 , 4 , 0 , 11501, 30 , 43 ), // #283
- INST(Invept , X86Rm_NoSize , O(660F38,80,_,_,_,_,_,_ ), 0 , 2 , 0 , 1275 , 82 , 58 ), // #284
- INST(Invlpg , X86M_Only , O(000F00,01,7,_,_,_,_,_ ), 0 , 22 , 0 , 1282 , 69 , 43 ), // #285
- INST(Invlpga , X86Op_xAddr , O(000F01,DF,_,_,_,_,_,_ ), 0 , 21 , 0 , 1289 , 83 , 22 ), // #286
- INST(Invpcid , X86Rm_NoSize , O(660F38,82,_,_,_,_,_,_ ), 0 , 2 , 0 , 1297 , 82 , 43 ), // #287
- INST(Invvpid , X86Rm_NoSize , O(660F38,81,_,_,_,_,_,_ ), 0 , 2 , 0 , 1305 , 82 , 58 ), // #288
- INST(Iret , X86Op , O(660000,CF,_,_,_,_,_,_ ), 0 , 19 , 0 , 3226 , 84 , 1 ), // #289
- INST(Iretd , X86Op , O(000000,CF,_,_,_,_,_,_ ), 0 , 0 , 0 , 1313 , 84 , 1 ), // #290
- INST(Iretq , X86Op , O(000000,CF,_,_,1,_,_,_ ), 0 , 20 , 0 , 1319 , 85 , 1 ), // #291
- INST(Ja , X86Jcc , O(000F00,87,_,_,_,_,_,_ ), O(000000,77,_,_,_,_,_,_ ), 4 , 18 , 1325 , 86 , 59 ), // #292
- INST(Jae , X86Jcc , O(000F00,83,_,_,_,_,_,_ ), O(000000,73,_,_,_,_,_,_ ), 4 , 19 , 1328 , 86 , 60 ), // #293
- INST(Jb , X86Jcc , O(000F00,82,_,_,_,_,_,_ ), O(000000,72,_,_,_,_,_,_ ), 4 , 20 , 1332 , 86 , 60 ), // #294
- INST(Jbe , X86Jcc , O(000F00,86,_,_,_,_,_,_ ), O(000000,76,_,_,_,_,_,_ ), 4 , 21 , 1335 , 86 , 59 ), // #295
- INST(Jc , X86Jcc , O(000F00,82,_,_,_,_,_,_ ), O(000000,72,_,_,_,_,_,_ ), 4 , 20 , 1339 , 86 , 60 ), // #296
- INST(Je , X86Jcc , O(000F00,84,_,_,_,_,_,_ ), O(000000,74,_,_,_,_,_,_ ), 4 , 22 , 1342 , 86 , 61 ), // #297
- INST(Jecxz , X86JecxzLoop , 0 , O(000000,E3,_,_,_,_,_,_ ), 0 , 23 , 1345 , 87 , 0 ), // #298
- INST(Jg , X86Jcc , O(000F00,8F,_,_,_,_,_,_ ), O(000000,7F,_,_,_,_,_,_ ), 4 , 24 , 1351 , 86 , 62 ), // #299
- INST(Jge , X86Jcc , O(000F00,8D,_,_,_,_,_,_ ), O(000000,7D,_,_,_,_,_,_ ), 4 , 25 , 1354 , 86 , 63 ), // #300
- INST(Jl , X86Jcc , O(000F00,8C,_,_,_,_,_,_ ), O(000000,7C,_,_,_,_,_,_ ), 4 , 26 , 1358 , 86 , 63 ), // #301
- INST(Jle , X86Jcc , O(000F00,8E,_,_,_,_,_,_ ), O(000000,7E,_,_,_,_,_,_ ), 4 , 27 , 1361 , 86 , 62 ), // #302
- INST(Jmp , X86Jmp , O(000000,FF,4,_,_,_,_,_ ), O(000000,EB,_,_,_,_,_,_ ), 9 , 28 , 1861 , 88 , 0 ), // #303
- INST(Jna , X86Jcc , O(000F00,86,_,_,_,_,_,_ ), O(000000,76,_,_,_,_,_,_ ), 4 , 21 , 1365 , 86 , 59 ), // #304
- INST(Jnae , X86Jcc , O(000F00,82,_,_,_,_,_,_ ), O(000000,72,_,_,_,_,_,_ ), 4 , 20 , 1369 , 86 , 60 ), // #305
- INST(Jnb , X86Jcc , O(000F00,83,_,_,_,_,_,_ ), O(000000,73,_,_,_,_,_,_ ), 4 , 19 , 1374 , 86 , 60 ), // #306
- INST(Jnbe , X86Jcc , O(000F00,87,_,_,_,_,_,_ ), O(000000,77,_,_,_,_,_,_ ), 4 , 18 , 1378 , 86 , 59 ), // #307
- INST(Jnc , X86Jcc , O(000F00,83,_,_,_,_,_,_ ), O(000000,73,_,_,_,_,_,_ ), 4 , 19 , 1383 , 86 , 60 ), // #308
- INST(Jne , X86Jcc , O(000F00,85,_,_,_,_,_,_ ), O(000000,75,_,_,_,_,_,_ ), 4 , 29 , 1387 , 86 , 61 ), // #309
- INST(Jng , X86Jcc , O(000F00,8E,_,_,_,_,_,_ ), O(000000,7E,_,_,_,_,_,_ ), 4 , 27 , 1391 , 86 , 62 ), // #310
- INST(Jnge , X86Jcc , O(000F00,8C,_,_,_,_,_,_ ), O(000000,7C,_,_,_,_,_,_ ), 4 , 26 , 1395 , 86 , 63 ), // #311
- INST(Jnl , X86Jcc , O(000F00,8D,_,_,_,_,_,_ ), O(000000,7D,_,_,_,_,_,_ ), 4 , 25 , 1400 , 86 , 63 ), // #312
- INST(Jnle , X86Jcc , O(000F00,8F,_,_,_,_,_,_ ), O(000000,7F,_,_,_,_,_,_ ), 4 , 24 , 1404 , 86 , 62 ), // #313
- INST(Jno , X86Jcc , O(000F00,81,_,_,_,_,_,_ ), O(000000,71,_,_,_,_,_,_ ), 4 , 30 , 1409 , 86 , 57 ), // #314
- INST(Jnp , X86Jcc , O(000F00,8B,_,_,_,_,_,_ ), O(000000,7B,_,_,_,_,_,_ ), 4 , 31 , 1413 , 86 , 64 ), // #315
- INST(Jns , X86Jcc , O(000F00,89,_,_,_,_,_,_ ), O(000000,79,_,_,_,_,_,_ ), 4 , 32 , 1417 , 86 , 65 ), // #316
- INST(Jnz , X86Jcc , O(000F00,85,_,_,_,_,_,_ ), O(000000,75,_,_,_,_,_,_ ), 4 , 29 , 1421 , 86 , 61 ), // #317
- INST(Jo , X86Jcc , O(000F00,80,_,_,_,_,_,_ ), O(000000,70,_,_,_,_,_,_ ), 4 , 33 , 1425 , 86 , 57 ), // #318
- INST(Jp , X86Jcc , O(000F00,8A,_,_,_,_,_,_ ), O(000000,7A,_,_,_,_,_,_ ), 4 , 34 , 1428 , 86 , 64 ), // #319
- INST(Jpe , X86Jcc , O(000F00,8A,_,_,_,_,_,_ ), O(000000,7A,_,_,_,_,_,_ ), 4 , 34 , 1431 , 86 , 64 ), // #320
- INST(Jpo , X86Jcc , O(000F00,8B,_,_,_,_,_,_ ), O(000000,7B,_,_,_,_,_,_ ), 4 , 31 , 1435 , 86 , 64 ), // #321
- INST(Js , X86Jcc , O(000F00,88,_,_,_,_,_,_ ), O(000000,78,_,_,_,_,_,_ ), 4 , 35 , 1439 , 86 , 65 ), // #322
- INST(Jz , X86Jcc , O(000F00,84,_,_,_,_,_,_ ), O(000000,74,_,_,_,_,_,_ ), 4 , 22 , 1442 , 86 , 61 ), // #323
- INST(Kaddb , VexRvm , V(660F00,4A,_,1,0,_,_,_ ), 0 , 65 , 0 , 1445 , 89 , 66 ), // #324
- INST(Kaddd , VexRvm , V(660F00,4A,_,1,1,_,_,_ ), 0 , 66 , 0 , 1451 , 89 , 67 ), // #325
- INST(Kaddq , VexRvm , V(000F00,4A,_,1,1,_,_,_ ), 0 , 67 , 0 , 1457 , 89 , 67 ), // #326
- INST(Kaddw , VexRvm , V(000F00,4A,_,1,0,_,_,_ ), 0 , 68 , 0 , 1463 , 89 , 66 ), // #327
- INST(Kandb , VexRvm , V(660F00,41,_,1,0,_,_,_ ), 0 , 65 , 0 , 1469 , 89 , 66 ), // #328
- INST(Kandd , VexRvm , V(660F00,41,_,1,1,_,_,_ ), 0 , 66 , 0 , 1475 , 89 , 67 ), // #329
- INST(Kandnb , VexRvm , V(660F00,42,_,1,0,_,_,_ ), 0 , 65 , 0 , 1481 , 89 , 66 ), // #330
- INST(Kandnd , VexRvm , V(660F00,42,_,1,1,_,_,_ ), 0 , 66 , 0 , 1488 , 89 , 67 ), // #331
- INST(Kandnq , VexRvm , V(000F00,42,_,1,1,_,_,_ ), 0 , 67 , 0 , 1495 , 89 , 67 ), // #332
- INST(Kandnw , VexRvm , V(000F00,42,_,1,0,_,_,_ ), 0 , 68 , 0 , 1502 , 89 , 68 ), // #333
- INST(Kandq , VexRvm , V(000F00,41,_,1,1,_,_,_ ), 0 , 67 , 0 , 1509 , 89 , 67 ), // #334
- INST(Kandw , VexRvm , V(000F00,41,_,1,0,_,_,_ ), 0 , 68 , 0 , 1515 , 89 , 68 ), // #335
- INST(Kmovb , VexKmov , V(660F00,90,_,0,0,_,_,_ ), V(660F00,92,_,0,0,_,_,_ ), 69 , 36 , 1521 , 90 , 66 ), // #336
- INST(Kmovd , VexKmov , V(660F00,90,_,0,1,_,_,_ ), V(F20F00,92,_,0,0,_,_,_ ), 70 , 37 , 9105 , 91 , 67 ), // #337
- INST(Kmovq , VexKmov , V(000F00,90,_,0,1,_,_,_ ), V(F20F00,92,_,0,1,_,_,_ ), 71 , 38 , 9116 , 92 , 67 ), // #338
- INST(Kmovw , VexKmov , V(000F00,90,_,0,0,_,_,_ ), V(000F00,92,_,0,0,_,_,_ ), 72 , 39 , 1527 , 93 , 68 ), // #339
- INST(Knotb , VexRm , V(660F00,44,_,0,0,_,_,_ ), 0 , 69 , 0 , 1533 , 94 , 66 ), // #340
- INST(Knotd , VexRm , V(660F00,44,_,0,1,_,_,_ ), 0 , 70 , 0 , 1539 , 94 , 67 ), // #341
- INST(Knotq , VexRm , V(000F00,44,_,0,1,_,_,_ ), 0 , 71 , 0 , 1545 , 94 , 67 ), // #342
- INST(Knotw , VexRm , V(000F00,44,_,0,0,_,_,_ ), 0 , 72 , 0 , 1551 , 94 , 68 ), // #343
- INST(Korb , VexRvm , V(660F00,45,_,1,0,_,_,_ ), 0 , 65 , 0 , 1557 , 89 , 66 ), // #344
- INST(Kord , VexRvm , V(660F00,45,_,1,1,_,_,_ ), 0 , 66 , 0 , 1562 , 89 , 67 ), // #345
- INST(Korq , VexRvm , V(000F00,45,_,1,1,_,_,_ ), 0 , 67 , 0 , 1567 , 89 , 67 ), // #346
- INST(Kortestb , VexRm , V(660F00,98,_,0,0,_,_,_ ), 0 , 69 , 0 , 1572 , 94 , 69 ), // #347
- INST(Kortestd , VexRm , V(660F00,98,_,0,1,_,_,_ ), 0 , 70 , 0 , 1581 , 94 , 70 ), // #348
- INST(Kortestq , VexRm , V(000F00,98,_,0,1,_,_,_ ), 0 , 71 , 0 , 1590 , 94 , 70 ), // #349
- INST(Kortestw , VexRm , V(000F00,98,_,0,0,_,_,_ ), 0 , 72 , 0 , 1599 , 94 , 71 ), // #350
- INST(Korw , VexRvm , V(000F00,45,_,1,0,_,_,_ ), 0 , 68 , 0 , 1608 , 89 , 68 ), // #351
- INST(Kshiftlb , VexRmi , V(660F3A,32,_,0,0,_,_,_ ), 0 , 73 , 0 , 1613 , 95 , 66 ), // #352
- INST(Kshiftld , VexRmi , V(660F3A,33,_,0,0,_,_,_ ), 0 , 73 , 0 , 1622 , 95 , 67 ), // #353
- INST(Kshiftlq , VexRmi , V(660F3A,33,_,0,1,_,_,_ ), 0 , 74 , 0 , 1631 , 95 , 67 ), // #354
- INST(Kshiftlw , VexRmi , V(660F3A,32,_,0,1,_,_,_ ), 0 , 74 , 0 , 1640 , 95 , 68 ), // #355
- INST(Kshiftrb , VexRmi , V(660F3A,30,_,0,0,_,_,_ ), 0 , 73 , 0 , 1649 , 95 , 66 ), // #356
- INST(Kshiftrd , VexRmi , V(660F3A,31,_,0,0,_,_,_ ), 0 , 73 , 0 , 1658 , 95 , 67 ), // #357
- INST(Kshiftrq , VexRmi , V(660F3A,31,_,0,1,_,_,_ ), 0 , 74 , 0 , 1667 , 95 , 67 ), // #358
- INST(Kshiftrw , VexRmi , V(660F3A,30,_,0,1,_,_,_ ), 0 , 74 , 0 , 1676 , 95 , 68 ), // #359
- INST(Ktestb , VexRm , V(660F00,99,_,0,0,_,_,_ ), 0 , 69 , 0 , 1685 , 94 , 69 ), // #360
- INST(Ktestd , VexRm , V(660F00,99,_,0,1,_,_,_ ), 0 , 70 , 0 , 1692 , 94 , 70 ), // #361
- INST(Ktestq , VexRm , V(000F00,99,_,0,1,_,_,_ ), 0 , 71 , 0 , 1699 , 94 , 70 ), // #362
- INST(Ktestw , VexRm , V(000F00,99,_,0,0,_,_,_ ), 0 , 72 , 0 , 1706 , 94 , 69 ), // #363
- INST(Kunpckbw , VexRvm , V(660F00,4B,_,1,0,_,_,_ ), 0 , 65 , 0 , 1713 , 89 , 68 ), // #364
- INST(Kunpckdq , VexRvm , V(000F00,4B,_,1,1,_,_,_ ), 0 , 67 , 0 , 1722 , 89 , 67 ), // #365
- INST(Kunpckwd , VexRvm , V(000F00,4B,_,1,0,_,_,_ ), 0 , 68 , 0 , 1731 , 89 , 67 ), // #366
- INST(Kxnorb , VexRvm , V(660F00,46,_,1,0,_,_,_ ), 0 , 65 , 0 , 1740 , 96 , 66 ), // #367
- INST(Kxnord , VexRvm , V(660F00,46,_,1,1,_,_,_ ), 0 , 66 , 0 , 1747 , 96 , 67 ), // #368
- INST(Kxnorq , VexRvm , V(000F00,46,_,1,1,_,_,_ ), 0 , 67 , 0 , 1754 , 96 , 67 ), // #369
- INST(Kxnorw , VexRvm , V(000F00,46,_,1,0,_,_,_ ), 0 , 68 , 0 , 1761 , 96 , 68 ), // #370
- INST(Kxorb , VexRvm , V(660F00,47,_,1,0,_,_,_ ), 0 , 65 , 0 , 1768 , 96 , 66 ), // #371
- INST(Kxord , VexRvm , V(660F00,47,_,1,1,_,_,_ ), 0 , 66 , 0 , 1774 , 96 , 67 ), // #372
- INST(Kxorq , VexRvm , V(000F00,47,_,1,1,_,_,_ ), 0 , 67 , 0 , 1780 , 96 , 67 ), // #373
- INST(Kxorw , VexRvm , V(000F00,47,_,1,0,_,_,_ ), 0 , 68 , 0 , 1786 , 96 , 68 ), // #374
- INST(Lahf , X86Op , O(000000,9F,_,_,_,_,_,_ ), 0 , 0 , 0 , 1792 , 97 , 72 ), // #375
- INST(Lar , X86Rm , O(000F00,02,_,_,_,_,_,_ ), 0 , 4 , 0 , 1797 , 98 , 10 ), // #376
- INST(Lcall , X86LcallLjmp , O(000000,FF,3,_,_,_,_,_ ), O(000000,9A,_,_,_,_,_,_ ), 75 , 40 , 1801 , 99 , 1 ), // #377
- INST(Lddqu , ExtRm , O(F20F00,F0,_,_,_,_,_,_ ), 0 , 5 , 0 , 7003 , 100, 6 ), // #378
- INST(Ldmxcsr , X86M_Only , O(000F00,AE,2,_,_,_,_,_ ), 0 , 76 , 0 , 7010 , 101, 5 ), // #379
- INST(Lds , X86Rm , O(000000,C5,_,_,_,_,_,_ ), 0 , 0 , 0 , 1807 , 102, 0 ), // #380
- INST(Ldtilecfg , AmxCfg , V(000F38,49,_,0,0,_,_,_ ), 0 , 10 , 0 , 1811 , 103, 73 ), // #381
- INST(Lea , X86Lea , O(000000,8D,_,_,x,_,_,_ ), 0 , 0 , 0 , 1821 , 104, 0 ), // #382
- INST(Leave , X86Op , O(000000,C9,_,_,_,_,_,_ ), 0 , 0 , 0 , 1825 , 30 , 0 ), // #383
- INST(Les , X86Rm , O(000000,C4,_,_,_,_,_,_ ), 0 , 0 , 0 , 1831 , 102, 0 ), // #384
- INST(Lfence , X86Fence , O(000F00,AE,5,_,_,_,_,_ ), 0 , 77 , 0 , 1835 , 30 , 4 ), // #385
- INST(Lfs , X86Rm , O(000F00,B4,_,_,_,_,_,_ ), 0 , 4 , 0 , 1842 , 105, 0 ), // #386
- INST(Lgdt , X86M_Only , O(000F00,01,2,_,_,_,_,_ ), 0 , 76 , 0 , 1846 , 69 , 0 ), // #387
- INST(Lgs , X86Rm , O(000F00,B5,_,_,_,_,_,_ ), 0 , 4 , 0 , 1851 , 105, 0 ), // #388
- INST(Lidt , X86M_Only , O(000F00,01,3,_,_,_,_,_ ), 0 , 78 , 0 , 1855 , 69 , 0 ), // #389
- INST(Ljmp , X86LcallLjmp , O(000000,FF,5,_,_,_,_,_ ), O(000000,EA,_,_,_,_,_,_ ), 62 , 41 , 1860 , 106, 0 ), // #390
- INST(Lldt , X86M_NoSize , O(000F00,00,2,_,_,_,_,_ ), 0 , 76 , 0 , 1865 , 107, 0 ), // #391
- INST(Llwpcb , VexR_Wx , V(XOP_M9,12,0,0,x,_,_,_ ), 0 , 79 , 0 , 1870 , 108, 74 ), // #392
- INST(Lmsw , X86M_NoSize , O(000F00,01,6,_,_,_,_,_ ), 0 , 80 , 0 , 1877 , 107, 0 ), // #393
- INST(Lods , X86StrRm , O(000000,AC,_,_,_,_,_,_ ), 0 , 0 , 0 , 1882 , 109, 75 ), // #394
- INST(Loop , X86JecxzLoop , 0 , O(000000,E2,_,_,_,_,_,_ ), 0 , 42 , 1887 , 110, 0 ), // #395
- INST(Loope , X86JecxzLoop , 0 , O(000000,E1,_,_,_,_,_,_ ), 0 , 43 , 1892 , 110, 61 ), // #396
- INST(Loopne , X86JecxzLoop , 0 , O(000000,E0,_,_,_,_,_,_ ), 0 , 44 , 1898 , 110, 61 ), // #397
- INST(Lsl , X86Rm , O(000F00,03,_,_,_,_,_,_ ), 0 , 4 , 0 , 1905 , 111, 10 ), // #398
- INST(Lss , X86Rm , O(000F00,B2,_,_,_,_,_,_ ), 0 , 4 , 0 , 7556 , 105, 0 ), // #399
- INST(Ltr , X86M_NoSize , O(000F00,00,3,_,_,_,_,_ ), 0 , 78 , 0 , 1909 , 107, 0 ), // #400
- INST(Lwpins , VexVmi4_Wx , V(XOP_MA,12,0,0,x,_,_,_ ), 0 , 81 , 0 , 1913 , 112, 74 ), // #401
- INST(Lwpval , VexVmi4_Wx , V(XOP_MA,12,1,0,x,_,_,_ ), 0 , 82 , 0 , 1920 , 112, 74 ), // #402
- INST(Lzcnt , X86Rm_Raw66H , O(F30F00,BD,_,_,x,_,_,_ ), 0 , 6 , 0 , 1927 , 22 , 76 ), // #403
- INST(Maskmovdqu , ExtRm_ZDI , O(660F00,F7,_,_,_,_,_,_ ), 0 , 3 , 0 , 7019 , 113, 4 ), // #404
- INST(Maskmovq , ExtRm_ZDI , O(000F00,F7,_,_,_,_,_,_ ), 0 , 4 , 0 , 9113 , 114, 77 ), // #405
- INST(Maxpd , ExtRm , O(660F00,5F,_,_,_,_,_,_ ), 0 , 3 , 0 , 7053 , 5 , 4 ), // #406
- INST(Maxps , ExtRm , O(000F00,5F,_,_,_,_,_,_ ), 0 , 4 , 0 , 7067 , 5 , 5 ), // #407
- INST(Maxsd , ExtRm , O(F20F00,5F,_,_,_,_,_,_ ), 0 , 5 , 0 , 9132 , 6 , 4 ), // #408
- INST(Maxss , ExtRm , O(F30F00,5F,_,_,_,_,_,_ ), 0 , 6 , 0 , 7088 , 7 , 5 ), // #409
- INST(Mcommit , X86Op , O(F30F01,FA,_,_,_,_,_,_ ), 0 , 25 , 0 , 1933 , 30 , 78 ), // #410
- INST(Mfence , X86Fence , O(000F00,AE,6,_,_,_,_,_ ), 0 , 80 , 0 , 1941 , 30 , 4 ), // #411
- INST(Minpd , ExtRm , O(660F00,5D,_,_,_,_,_,_ ), 0 , 3 , 0 , 7117 , 5 , 4 ), // #412
- INST(Minps , ExtRm , O(000F00,5D,_,_,_,_,_,_ ), 0 , 4 , 0 , 7131 , 5 , 5 ), // #413
- INST(Minsd , ExtRm , O(F20F00,5D,_,_,_,_,_,_ ), 0 , 5 , 0 , 9196 , 6 , 4 ), // #414
- INST(Minss , ExtRm , O(F30F00,5D,_,_,_,_,_,_ ), 0 , 6 , 0 , 7152 , 7 , 5 ), // #415
- INST(Monitor , X86Op , O(000F01,C8,_,_,_,_,_,_ ), 0 , 21 , 0 , 3232 , 115, 79 ), // #416
- INST(Monitorx , X86Op , O(000F01,FA,_,_,_,_,_,_ ), 0 , 21 , 0 , 1948 , 115, 80 ), // #417
- INST(Mov , X86Mov , 0 , 0 , 0 , 0 , 138 , 116, 0 ), // #418
- INST(Movabs , X86Movabs , 0 , 0 , 0 , 0 , 1957 , 117, 0 ), // #419
- INST(Movapd , ExtMov , O(660F00,28,_,_,_,_,_,_ ), O(660F00,29,_,_,_,_,_,_ ), 3 , 45 , 7183 , 118, 4 ), // #420
- INST(Movaps , ExtMov , O(000F00,28,_,_,_,_,_,_ ), O(000F00,29,_,_,_,_,_,_ ), 4 , 46 , 7191 , 118, 5 ), // #421
- INST(Movbe , ExtMovbe , O(000F38,F0,_,_,x,_,_,_ ), O(000F38,F1,_,_,x,_,_,_ ), 83 , 47 , 656 , 119, 81 ), // #422
- INST(Movd , ExtMovd , O(000F00,6E,_,_,_,_,_,_ ), O(000F00,7E,_,_,_,_,_,_ ), 4 , 48 , 9106 , 120, 82 ), // #423
- INST(Movddup , ExtMov , O(F20F00,12,_,_,_,_,_,_ ), 0 , 5 , 0 , 7205 , 6 , 6 ), // #424
- INST(Movdir64b , X86EnqcmdMovdir64b , O(660F38,F8,_,_,_,_,_,_ ), 0 , 2 , 0 , 1964 , 121, 83 ), // #425
- INST(Movdiri , X86MovntiMovdiri , O(000F38,F9,_,_,_,_,_,_ ), 0 , 83 , 0 , 1974 , 122, 84 ), // #426
- INST(Movdq2q , ExtMov , O(F20F00,D6,_,_,_,_,_,_ ), 0 , 5 , 0 , 1982 , 123, 4 ), // #427
- INST(Movdqa , ExtMov , O(660F00,6F,_,_,_,_,_,_ ), O(660F00,7F,_,_,_,_,_,_ ), 3 , 49 , 7214 , 118, 4 ), // #428
- INST(Movdqu , ExtMov , O(F30F00,6F,_,_,_,_,_,_ ), O(F30F00,7F,_,_,_,_,_,_ ), 6 , 50 , 7023 , 118, 4 ), // #429
- INST(Movhlps , ExtMov , O(000F00,12,_,_,_,_,_,_ ), 0 , 4 , 0 , 7289 , 124, 5 ), // #430
- INST(Movhpd , ExtMov , O(660F00,16,_,_,_,_,_,_ ), O(660F00,17,_,_,_,_,_,_ ), 3 , 51 , 7298 , 125, 4 ), // #431
- INST(Movhps , ExtMov , O(000F00,16,_,_,_,_,_,_ ), O(000F00,17,_,_,_,_,_,_ ), 4 , 52 , 7306 , 125, 5 ), // #432
- INST(Movlhps , ExtMov , O(000F00,16,_,_,_,_,_,_ ), 0 , 4 , 0 , 7314 , 124, 5 ), // #433
- INST(Movlpd , ExtMov , O(660F00,12,_,_,_,_,_,_ ), O(660F00,13,_,_,_,_,_,_ ), 3 , 53 , 7323 , 125, 4 ), // #434
- INST(Movlps , ExtMov , O(000F00,12,_,_,_,_,_,_ ), O(000F00,13,_,_,_,_,_,_ ), 4 , 54 , 7331 , 125, 5 ), // #435
- INST(Movmskpd , ExtMov , O(660F00,50,_,_,_,_,_,_ ), 0 , 3 , 0 , 7339 , 126, 4 ), // #436
- INST(Movmskps , ExtMov , O(000F00,50,_,_,_,_,_,_ ), 0 , 4 , 0 , 7349 , 126, 5 ), // #437
- INST(Movntdq , ExtMov , 0 , O(660F00,E7,_,_,_,_,_,_ ), 0 , 55 , 7359 , 127, 4 ), // #438
- INST(Movntdqa , ExtMov , O(660F38,2A,_,_,_,_,_,_ ), 0 , 2 , 0 , 7368 , 100, 12 ), // #439
- INST(Movnti , X86MovntiMovdiri , O(000F00,C3,_,_,x,_,_,_ ), 0 , 4 , 0 , 1990 , 122, 4 ), // #440
- INST(Movntpd , ExtMov , 0 , O(660F00,2B,_,_,_,_,_,_ ), 0 , 56 , 7378 , 127, 4 ), // #441
- INST(Movntps , ExtMov , 0 , O(000F00,2B,_,_,_,_,_,_ ), 0 , 57 , 7387 , 127, 5 ), // #442
- INST(Movntq , ExtMov , 0 , O(000F00,E7,_,_,_,_,_,_ ), 0 , 58 , 1997 , 128, 77 ), // #443
- INST(Movntsd , ExtMov , 0 , O(F20F00,2B,_,_,_,_,_,_ ), 0 , 59 , 2004 , 129, 49 ), // #444
- INST(Movntss , ExtMov , 0 , O(F30F00,2B,_,_,_,_,_,_ ), 0 , 60 , 2012 , 130, 49 ), // #445
- INST(Movq , ExtMovq , O(000F00,6E,_,_,x,_,_,_ ), O(000F00,7E,_,_,x,_,_,_ ), 4 , 48 , 9117 , 131, 82 ), // #446
- INST(Movq2dq , ExtRm , O(F30F00,D6,_,_,_,_,_,_ ), 0 , 6 , 0 , 2020 , 132, 4 ), // #447
- INST(Movs , X86StrMm , O(000000,A4,_,_,_,_,_,_ ), 0 , 0 , 0 , 439 , 133, 75 ), // #448
- INST(Movsd , ExtMov , O(F20F00,10,_,_,_,_,_,_ ), O(F20F00,11,_,_,_,_,_,_ ), 5 , 61 , 7402 , 134, 4 ), // #449
- INST(Movshdup , ExtRm , O(F30F00,16,_,_,_,_,_,_ ), 0 , 6 , 0 , 7416 , 5 , 6 ), // #450
- INST(Movsldup , ExtRm , O(F30F00,12,_,_,_,_,_,_ ), 0 , 6 , 0 , 7426 , 5 , 6 ), // #451
- INST(Movss , ExtMov , O(F30F00,10,_,_,_,_,_,_ ), O(F30F00,11,_,_,_,_,_,_ ), 6 , 62 , 7436 , 135, 5 ), // #452
- INST(Movsx , X86MovsxMovzx , O(000F00,BE,_,_,x,_,_,_ ), 0 , 4 , 0 , 2028 , 136, 0 ), // #453
- INST(Movsxd , X86Rm , O(000000,63,_,_,x,_,_,_ ), 0 , 0 , 0 , 2034 , 137, 0 ), // #454
- INST(Movupd , ExtMov , O(660F00,10,_,_,_,_,_,_ ), O(660F00,11,_,_,_,_,_,_ ), 3 , 63 , 7443 , 118, 4 ), // #455
- INST(Movups , ExtMov , O(000F00,10,_,_,_,_,_,_ ), O(000F00,11,_,_,_,_,_,_ ), 4 , 64 , 7451 , 118, 5 ), // #456
- INST(Movzx , X86MovsxMovzx , O(000F00,B6,_,_,x,_,_,_ ), 0 , 4 , 0 , 2041 , 136, 0 ), // #457
- INST(Mpsadbw , ExtRmi , O(660F3A,42,_,_,_,_,_,_ ), 0 , 8 , 0 , 7465 , 8 , 12 ), // #458
- INST(Mul , X86M_GPB_MulDiv , O(000000,F6,4,_,x,_,_,_ ), 0 , 9 , 0 , 828 , 54 , 1 ), // #459
- INST(Mulpd , ExtRm , O(660F00,59,_,_,_,_,_,_ ), 0 , 3 , 0 , 7519 , 5 , 4 ), // #460
- INST(Mulps , ExtRm , O(000F00,59,_,_,_,_,_,_ ), 0 , 4 , 0 , 7533 , 5 , 5 ), // #461
- INST(Mulsd , ExtRm , O(F20F00,59,_,_,_,_,_,_ ), 0 , 5 , 0 , 7540 , 6 , 4 ), // #462
- INST(Mulss , ExtRm , O(F30F00,59,_,_,_,_,_,_ ), 0 , 6 , 0 , 7554 , 7 , 5 ), // #463
- INST(Mulx , VexRvm_ZDX_Wx , V(F20F38,F6,_,0,x,_,_,_ ), 0 , 84 , 0 , 2047 , 138, 85 ), // #464
- INST(Mwait , X86Op , O(000F01,C9,_,_,_,_,_,_ ), 0 , 21 , 0 , 3241 , 139, 79 ), // #465
- INST(Mwaitx , X86Op , O(000F01,FB,_,_,_,_,_,_ ), 0 , 21 , 0 , 2052 , 140, 80 ), // #466
- INST(Neg , X86M_GPB , O(000000,F6,3,_,x,_,_,_ ), 0 , 75 , 0 , 2059 , 141, 1 ), // #467
- INST(Nop , X86M_Nop , O(000000,90,_,_,_,_,_,_ ), 0 , 0 , 0 , 959 , 142, 0 ), // #468
- INST(Not , X86M_GPB , O(000000,F6,2,_,x,_,_,_ ), 0 , 1 , 0 , 2063 , 141, 0 ), // #469
- INST(Or , X86Arith , O(000000,08,1,_,x,_,_,_ ), 0 , 31 , 0 , 3237 , 143, 1 ), // #470
- INST(Orpd , ExtRm , O(660F00,56,_,_,_,_,_,_ ), 0 , 3 , 0 , 11458, 11 , 4 ), // #471
- INST(Orps , ExtRm , O(000F00,56,_,_,_,_,_,_ ), 0 , 4 , 0 , 11465, 11 , 5 ), // #472
- INST(Out , X86Out , O(000000,EE,_,_,_,_,_,_ ), O(000000,E6,_,_,_,_,_,_ ), 0 , 65 , 2067 , 144, 0 ), // #473
- INST(Outs , X86Outs , O(000000,6E,_,_,_,_,_,_ ), 0 , 0 , 0 , 2071 , 145, 0 ), // #474
- INST(Pabsb , ExtRm_P , O(000F38,1C,_,_,_,_,_,_ ), 0 , 83 , 0 , 7636 , 146, 86 ), // #475
- INST(Pabsd , ExtRm_P , O(000F38,1E,_,_,_,_,_,_ ), 0 , 83 , 0 , 7643 , 146, 86 ), // #476
- INST(Pabsw , ExtRm_P , O(000F38,1D,_,_,_,_,_,_ ), 0 , 83 , 0 , 7657 , 146, 86 ), // #477
- INST(Packssdw , ExtRm_P , O(000F00,6B,_,_,_,_,_,_ ), 0 , 4 , 0 , 7664 , 146, 82 ), // #478
- INST(Packsswb , ExtRm_P , O(000F00,63,_,_,_,_,_,_ ), 0 , 4 , 0 , 7674 , 146, 82 ), // #479
- INST(Packusdw , ExtRm , O(660F38,2B,_,_,_,_,_,_ ), 0 , 2 , 0 , 7684 , 5 , 12 ), // #480
- INST(Packuswb , ExtRm_P , O(000F00,67,_,_,_,_,_,_ ), 0 , 4 , 0 , 7694 , 146, 82 ), // #481
- INST(Paddb , ExtRm_P , O(000F00,FC,_,_,_,_,_,_ ), 0 , 4 , 0 , 7704 , 146, 82 ), // #482
- INST(Paddd , ExtRm_P , O(000F00,FE,_,_,_,_,_,_ ), 0 , 4 , 0 , 7711 , 146, 82 ), // #483
- INST(Paddq , ExtRm_P , O(000F00,D4,_,_,_,_,_,_ ), 0 , 4 , 0 , 7718 , 146, 4 ), // #484
- INST(Paddsb , ExtRm_P , O(000F00,EC,_,_,_,_,_,_ ), 0 , 4 , 0 , 7725 , 146, 82 ), // #485
- INST(Paddsw , ExtRm_P , O(000F00,ED,_,_,_,_,_,_ ), 0 , 4 , 0 , 7733 , 146, 82 ), // #486
- INST(Paddusb , ExtRm_P , O(000F00,DC,_,_,_,_,_,_ ), 0 , 4 , 0 , 7741 , 146, 82 ), // #487
- INST(Paddusw , ExtRm_P , O(000F00,DD,_,_,_,_,_,_ ), 0 , 4 , 0 , 7750 , 146, 82 ), // #488
- INST(Paddw , ExtRm_P , O(000F00,FD,_,_,_,_,_,_ ), 0 , 4 , 0 , 7759 , 146, 82 ), // #489
- INST(Palignr , ExtRmi_P , O(000F3A,0F,_,_,_,_,_,_ ), 0 , 85 , 0 , 7766 , 147, 6 ), // #490
- INST(Pand , ExtRm_P , O(000F00,DB,_,_,_,_,_,_ ), 0 , 4 , 0 , 7775 , 148, 82 ), // #491
- INST(Pandn , ExtRm_P , O(000F00,DF,_,_,_,_,_,_ ), 0 , 4 , 0 , 7788 , 149, 82 ), // #492
- INST(Pause , X86Op , O(F30000,90,_,_,_,_,_,_ ), 0 , 86 , 0 , 3195 , 30 , 0 ), // #493
- INST(Pavgb , ExtRm_P , O(000F00,E0,_,_,_,_,_,_ ), 0 , 4 , 0 , 7818 , 146, 87 ), // #494
- INST(Pavgusb , Ext3dNow , O(000F0F,BF,_,_,_,_,_,_ ), 0 , 87 , 0 , 2076 , 150, 51 ), // #495
- INST(Pavgw , ExtRm_P , O(000F00,E3,_,_,_,_,_,_ ), 0 , 4 , 0 , 7825 , 146, 87 ), // #496
- INST(Pblendvb , ExtRm_XMM0 , O(660F38,10,_,_,_,_,_,_ ), 0 , 2 , 0 , 7881 , 15 , 12 ), // #497
- INST(Pblendw , ExtRmi , O(660F3A,0E,_,_,_,_,_,_ ), 0 , 8 , 0 , 7891 , 8 , 12 ), // #498
- INST(Pclmulqdq , ExtRmi , O(660F3A,44,_,_,_,_,_,_ ), 0 , 8 , 0 , 7984 , 8 , 88 ), // #499
- INST(Pcmpeqb , ExtRm_P , O(000F00,74,_,_,_,_,_,_ ), 0 , 4 , 0 , 8016 , 149, 82 ), // #500
- INST(Pcmpeqd , ExtRm_P , O(000F00,76,_,_,_,_,_,_ ), 0 , 4 , 0 , 8025 , 149, 82 ), // #501
- INST(Pcmpeqq , ExtRm , O(660F38,29,_,_,_,_,_,_ ), 0 , 2 , 0 , 8034 , 151, 12 ), // #502
- INST(Pcmpeqw , ExtRm_P , O(000F00,75,_,_,_,_,_,_ ), 0 , 4 , 0 , 8043 , 149, 82 ), // #503
- INST(Pcmpestri , ExtRmi , O(660F3A,61,_,_,_,_,_,_ ), 0 , 8 , 0 , 8052 , 152, 89 ), // #504
- INST(Pcmpestrm , ExtRmi , O(660F3A,60,_,_,_,_,_,_ ), 0 , 8 , 0 , 8063 , 153, 89 ), // #505
- INST(Pcmpgtb , ExtRm_P , O(000F00,64,_,_,_,_,_,_ ), 0 , 4 , 0 , 8074 , 149, 82 ), // #506
- INST(Pcmpgtd , ExtRm_P , O(000F00,66,_,_,_,_,_,_ ), 0 , 4 , 0 , 8083 , 149, 82 ), // #507
- INST(Pcmpgtq , ExtRm , O(660F38,37,_,_,_,_,_,_ ), 0 , 2 , 0 , 8092 , 151, 44 ), // #508
- INST(Pcmpgtw , ExtRm_P , O(000F00,65,_,_,_,_,_,_ ), 0 , 4 , 0 , 8101 , 149, 82 ), // #509
- INST(Pcmpistri , ExtRmi , O(660F3A,63,_,_,_,_,_,_ ), 0 , 8 , 0 , 8110 , 154, 89 ), // #510
- INST(Pcmpistrm , ExtRmi , O(660F3A,62,_,_,_,_,_,_ ), 0 , 8 , 0 , 8121 , 155, 89 ), // #511
- INST(Pconfig , X86Op , O(000F01,C5,_,_,_,_,_,_ ), 0 , 21 , 0 , 2084 , 30 , 90 ), // #512
- INST(Pdep , VexRvm_Wx , V(F20F38,F5,_,0,x,_,_,_ ), 0 , 84 , 0 , 2092 , 10 , 85 ), // #513
- INST(Pext , VexRvm_Wx , V(F30F38,F5,_,0,x,_,_,_ ), 0 , 88 , 0 , 2097 , 10 , 85 ), // #514
- INST(Pextrb , ExtExtract , O(000F3A,14,_,_,_,_,_,_ ), 0 , 85 , 0 , 8608 , 156, 12 ), // #515
- INST(Pextrd , ExtExtract , O(000F3A,16,_,_,_,_,_,_ ), 0 , 85 , 0 , 8616 , 58 , 12 ), // #516
- INST(Pextrq , ExtExtract , O(000F3A,16,_,_,1,_,_,_ ), 0 , 89 , 0 , 8624 , 157, 12 ), // #517
- INST(Pextrw , ExtPextrw , O(000F00,C5,_,_,_,_,_,_ ), O(000F3A,15,_,_,_,_,_,_ ), 4 , 66 , 8632 , 158, 91 ), // #518
- INST(Pf2id , Ext3dNow , O(000F0F,1D,_,_,_,_,_,_ ), 0 , 87 , 0 , 2102 , 150, 51 ), // #519
- INST(Pf2iw , Ext3dNow , O(000F0F,1C,_,_,_,_,_,_ ), 0 , 87 , 0 , 2108 , 150, 92 ), // #520
- INST(Pfacc , Ext3dNow , O(000F0F,AE,_,_,_,_,_,_ ), 0 , 87 , 0 , 2114 , 150, 51 ), // #521
- INST(Pfadd , Ext3dNow , O(000F0F,9E,_,_,_,_,_,_ ), 0 , 87 , 0 , 2120 , 150, 51 ), // #522
- INST(Pfcmpeq , Ext3dNow , O(000F0F,B0,_,_,_,_,_,_ ), 0 , 87 , 0 , 2126 , 150, 51 ), // #523
- INST(Pfcmpge , Ext3dNow , O(000F0F,90,_,_,_,_,_,_ ), 0 , 87 , 0 , 2134 , 150, 51 ), // #524
- INST(Pfcmpgt , Ext3dNow , O(000F0F,A0,_,_,_,_,_,_ ), 0 , 87 , 0 , 2142 , 150, 51 ), // #525
- INST(Pfmax , Ext3dNow , O(000F0F,A4,_,_,_,_,_,_ ), 0 , 87 , 0 , 2150 , 150, 51 ), // #526
- INST(Pfmin , Ext3dNow , O(000F0F,94,_,_,_,_,_,_ ), 0 , 87 , 0 , 2156 , 150, 51 ), // #527
- INST(Pfmul , Ext3dNow , O(000F0F,B4,_,_,_,_,_,_ ), 0 , 87 , 0 , 2162 , 150, 51 ), // #528
- INST(Pfnacc , Ext3dNow , O(000F0F,8A,_,_,_,_,_,_ ), 0 , 87 , 0 , 2168 , 150, 92 ), // #529
- INST(Pfpnacc , Ext3dNow , O(000F0F,8E,_,_,_,_,_,_ ), 0 , 87 , 0 , 2175 , 150, 92 ), // #530
- INST(Pfrcp , Ext3dNow , O(000F0F,96,_,_,_,_,_,_ ), 0 , 87 , 0 , 2183 , 150, 51 ), // #531
- INST(Pfrcpit1 , Ext3dNow , O(000F0F,A6,_,_,_,_,_,_ ), 0 , 87 , 0 , 2189 , 150, 51 ), // #532
- INST(Pfrcpit2 , Ext3dNow , O(000F0F,B6,_,_,_,_,_,_ ), 0 , 87 , 0 , 2198 , 150, 51 ), // #533
- INST(Pfrcpv , Ext3dNow , O(000F0F,86,_,_,_,_,_,_ ), 0 , 87 , 0 , 2207 , 150, 93 ), // #534
- INST(Pfrsqit1 , Ext3dNow , O(000F0F,A7,_,_,_,_,_,_ ), 0 , 87 , 0 , 2214 , 150, 51 ), // #535
- INST(Pfrsqrt , Ext3dNow , O(000F0F,97,_,_,_,_,_,_ ), 0 , 87 , 0 , 2223 , 150, 51 ), // #536
- INST(Pfrsqrtv , Ext3dNow , O(000F0F,87,_,_,_,_,_,_ ), 0 , 87 , 0 , 2231 , 150, 93 ), // #537
- INST(Pfsub , Ext3dNow , O(000F0F,9A,_,_,_,_,_,_ ), 0 , 87 , 0 , 2240 , 150, 51 ), // #538
- INST(Pfsubr , Ext3dNow , O(000F0F,AA,_,_,_,_,_,_ ), 0 , 87 , 0 , 2246 , 150, 51 ), // #539
- INST(Phaddd , ExtRm_P , O(000F38,02,_,_,_,_,_,_ ), 0 , 83 , 0 , 8711 , 146, 86 ), // #540
- INST(Phaddsw , ExtRm_P , O(000F38,03,_,_,_,_,_,_ ), 0 , 83 , 0 , 8728 , 146, 86 ), // #541
- INST(Phaddw , ExtRm_P , O(000F38,01,_,_,_,_,_,_ ), 0 , 83 , 0 , 8797 , 146, 86 ), // #542
- INST(Phminposuw , ExtRm , O(660F38,41,_,_,_,_,_,_ ), 0 , 2 , 0 , 8823 , 5 , 12 ), // #543
- INST(Phsubd , ExtRm_P , O(000F38,06,_,_,_,_,_,_ ), 0 , 83 , 0 , 8844 , 146, 86 ), // #544
- INST(Phsubsw , ExtRm_P , O(000F38,07,_,_,_,_,_,_ ), 0 , 83 , 0 , 8861 , 146, 86 ), // #545
- INST(Phsubw , ExtRm_P , O(000F38,05,_,_,_,_,_,_ ), 0 , 83 , 0 , 8870 , 146, 86 ), // #546
- INST(Pi2fd , Ext3dNow , O(000F0F,0D,_,_,_,_,_,_ ), 0 , 87 , 0 , 2253 , 150, 51 ), // #547
- INST(Pi2fw , Ext3dNow , O(000F0F,0C,_,_,_,_,_,_ ), 0 , 87 , 0 , 2259 , 150, 92 ), // #548
- INST(Pinsrb , ExtRmi , O(660F3A,20,_,_,_,_,_,_ ), 0 , 8 , 0 , 8887 , 159, 12 ), // #549
- INST(Pinsrd , ExtRmi , O(660F3A,22,_,_,_,_,_,_ ), 0 , 8 , 0 , 8895 , 160, 12 ), // #550
- INST(Pinsrq , ExtRmi , O(660F3A,22,_,_,1,_,_,_ ), 0 , 90 , 0 , 8903 , 161, 12 ), // #551
- INST(Pinsrw , ExtRmi_P , O(000F00,C4,_,_,_,_,_,_ ), 0 , 4 , 0 , 8911 , 162, 87 ), // #552
- INST(Pmaddubsw , ExtRm_P , O(000F38,04,_,_,_,_,_,_ ), 0 , 83 , 0 , 9081 , 146, 86 ), // #553
- INST(Pmaddwd , ExtRm_P , O(000F00,F5,_,_,_,_,_,_ ), 0 , 4 , 0 , 9092 , 146, 82 ), // #554
- INST(Pmaxsb , ExtRm , O(660F38,3C,_,_,_,_,_,_ ), 0 , 2 , 0 , 9123 , 11 , 12 ), // #555
- INST(Pmaxsd , ExtRm , O(660F38,3D,_,_,_,_,_,_ ), 0 , 2 , 0 , 9131 , 11 , 12 ), // #556
- INST(Pmaxsw , ExtRm_P , O(000F00,EE,_,_,_,_,_,_ ), 0 , 4 , 0 , 9147 , 148, 87 ), // #557
- INST(Pmaxub , ExtRm_P , O(000F00,DE,_,_,_,_,_,_ ), 0 , 4 , 0 , 9155 , 148, 87 ), // #558
- INST(Pmaxud , ExtRm , O(660F38,3F,_,_,_,_,_,_ ), 0 , 2 , 0 , 9163 , 11 , 12 ), // #559
- INST(Pmaxuw , ExtRm , O(660F38,3E,_,_,_,_,_,_ ), 0 , 2 , 0 , 9179 , 11 , 12 ), // #560
- INST(Pminsb , ExtRm , O(660F38,38,_,_,_,_,_,_ ), 0 , 2 , 0 , 9187 , 11 , 12 ), // #561
- INST(Pminsd , ExtRm , O(660F38,39,_,_,_,_,_,_ ), 0 , 2 , 0 , 9195 , 11 , 12 ), // #562
- INST(Pminsw , ExtRm_P , O(000F00,EA,_,_,_,_,_,_ ), 0 , 4 , 0 , 9211 , 148, 87 ), // #563
- INST(Pminub , ExtRm_P , O(000F00,DA,_,_,_,_,_,_ ), 0 , 4 , 0 , 9219 , 148, 87 ), // #564
- INST(Pminud , ExtRm , O(660F38,3B,_,_,_,_,_,_ ), 0 , 2 , 0 , 9227 , 11 , 12 ), // #565
- INST(Pminuw , ExtRm , O(660F38,3A,_,_,_,_,_,_ ), 0 , 2 , 0 , 9243 , 11 , 12 ), // #566
- INST(Pmovmskb , ExtRm_P , O(000F00,D7,_,_,_,_,_,_ ), 0 , 4 , 0 , 9321 , 163, 87 ), // #567
- INST(Pmovsxbd , ExtRm , O(660F38,21,_,_,_,_,_,_ ), 0 , 2 , 0 , 9418 , 7 , 12 ), // #568
- INST(Pmovsxbq , ExtRm , O(660F38,22,_,_,_,_,_,_ ), 0 , 2 , 0 , 9428 , 164, 12 ), // #569
- INST(Pmovsxbw , ExtRm , O(660F38,20,_,_,_,_,_,_ ), 0 , 2 , 0 , 9438 , 6 , 12 ), // #570
- INST(Pmovsxdq , ExtRm , O(660F38,25,_,_,_,_,_,_ ), 0 , 2 , 0 , 9448 , 6 , 12 ), // #571
- INST(Pmovsxwd , ExtRm , O(660F38,23,_,_,_,_,_,_ ), 0 , 2 , 0 , 9458 , 6 , 12 ), // #572
- INST(Pmovsxwq , ExtRm , O(660F38,24,_,_,_,_,_,_ ), 0 , 2 , 0 , 9468 , 7 , 12 ), // #573
- INST(Pmovzxbd , ExtRm , O(660F38,31,_,_,_,_,_,_ ), 0 , 2 , 0 , 9555 , 7 , 12 ), // #574
- INST(Pmovzxbq , ExtRm , O(660F38,32,_,_,_,_,_,_ ), 0 , 2 , 0 , 9565 , 164, 12 ), // #575
- INST(Pmovzxbw , ExtRm , O(660F38,30,_,_,_,_,_,_ ), 0 , 2 , 0 , 9575 , 6 , 12 ), // #576
- INST(Pmovzxdq , ExtRm , O(660F38,35,_,_,_,_,_,_ ), 0 , 2 , 0 , 9585 , 6 , 12 ), // #577
- INST(Pmovzxwd , ExtRm , O(660F38,33,_,_,_,_,_,_ ), 0 , 2 , 0 , 9595 , 6 , 12 ), // #578
- INST(Pmovzxwq , ExtRm , O(660F38,34,_,_,_,_,_,_ ), 0 , 2 , 0 , 9605 , 7 , 12 ), // #579
- INST(Pmuldq , ExtRm , O(660F38,28,_,_,_,_,_,_ ), 0 , 2 , 0 , 9615 , 5 , 12 ), // #580
- INST(Pmulhrsw , ExtRm_P , O(000F38,0B,_,_,_,_,_,_ ), 0 , 83 , 0 , 9623 , 146, 86 ), // #581
- INST(Pmulhrw , Ext3dNow , O(000F0F,B7,_,_,_,_,_,_ ), 0 , 87 , 0 , 2265 , 150, 51 ), // #582
- INST(Pmulhuw , ExtRm_P , O(000F00,E4,_,_,_,_,_,_ ), 0 , 4 , 0 , 9633 , 146, 87 ), // #583
- INST(Pmulhw , ExtRm_P , O(000F00,E5,_,_,_,_,_,_ ), 0 , 4 , 0 , 9642 , 146, 82 ), // #584
- INST(Pmulld , ExtRm , O(660F38,40,_,_,_,_,_,_ ), 0 , 2 , 0 , 9650 , 5 , 12 ), // #585
- INST(Pmullw , ExtRm_P , O(000F00,D5,_,_,_,_,_,_ ), 0 , 4 , 0 , 9666 , 146, 82 ), // #586
- INST(Pmuludq , ExtRm_P , O(000F00,F4,_,_,_,_,_,_ ), 0 , 4 , 0 , 9689 , 146, 4 ), // #587
- INST(Pop , X86Pop , O(000000,8F,0,_,_,_,_,_ ), O(000000,58,_,_,_,_,_,_ ), 0 , 67 , 2273 , 165, 0 ), // #588
- INST(Popa , X86Op , O(660000,61,_,_,_,_,_,_ ), 0 , 19 , 0 , 2277 , 81 , 0 ), // #589
- INST(Popad , X86Op , O(000000,61,_,_,_,_,_,_ ), 0 , 0 , 0 , 2282 , 81 , 0 ), // #590
- INST(Popcnt , X86Rm_Raw66H , O(F30F00,B8,_,_,x,_,_,_ ), 0 , 6 , 0 , 2288 , 22 , 94 ), // #591
- INST(Popf , X86Op , O(660000,9D,_,_,_,_,_,_ ), 0 , 19 , 0 , 2295 , 30 , 95 ), // #592
- INST(Popfd , X86Op , O(000000,9D,_,_,_,_,_,_ ), 0 , 0 , 0 , 2300 , 81 , 95 ), // #593
- INST(Popfq , X86Op , O(000000,9D,_,_,_,_,_,_ ), 0 , 0 , 0 , 2306 , 33 , 95 ), // #594
- INST(Por , ExtRm_P , O(000F00,EB,_,_,_,_,_,_ ), 0 , 4 , 0 , 9734 , 148, 82 ), // #595
- INST(Prefetch , X86M_Only , O(000F00,0D,0,_,_,_,_,_ ), 0 , 4 , 0 , 2312 , 31 , 51 ), // #596
- INST(Prefetchnta , X86M_Only , O(000F00,18,0,_,_,_,_,_ ), 0 , 4 , 0 , 2321 , 31 , 77 ), // #597
- INST(Prefetcht0 , X86M_Only , O(000F00,18,1,_,_,_,_,_ ), 0 , 29 , 0 , 2333 , 31 , 77 ), // #598
- INST(Prefetcht1 , X86M_Only , O(000F00,18,2,_,_,_,_,_ ), 0 , 76 , 0 , 2344 , 31 , 77 ), // #599
- INST(Prefetcht2 , X86M_Only , O(000F00,18,3,_,_,_,_,_ ), 0 , 78 , 0 , 2355 , 31 , 77 ), // #600
- INST(Prefetchw , X86M_Only , O(000F00,0D,1,_,_,_,_,_ ), 0 , 29 , 0 , 2366 , 31 , 96 ), // #601
- INST(Prefetchwt1 , X86M_Only , O(000F00,0D,2,_,_,_,_,_ ), 0 , 76 , 0 , 2376 , 31 , 97 ), // #602
- INST(Psadbw , ExtRm_P , O(000F00,F6,_,_,_,_,_,_ ), 0 , 4 , 0 , 4644 , 146, 87 ), // #603
- INST(Pshufb , ExtRm_P , O(000F38,00,_,_,_,_,_,_ ), 0 , 83 , 0 , 10060, 146, 86 ), // #604
- INST(Pshufd , ExtRmi , O(660F00,70,_,_,_,_,_,_ ), 0 , 3 , 0 , 10081, 8 , 4 ), // #605
- INST(Pshufhw , ExtRmi , O(F30F00,70,_,_,_,_,_,_ ), 0 , 6 , 0 , 10089, 8 , 4 ), // #606
- INST(Pshuflw , ExtRmi , O(F20F00,70,_,_,_,_,_,_ ), 0 , 5 , 0 , 10098, 8 , 4 ), // #607
- INST(Pshufw , ExtRmi_P , O(000F00,70,_,_,_,_,_,_ ), 0 , 4 , 0 , 2388 , 166, 77 ), // #608
- INST(Psignb , ExtRm_P , O(000F38,08,_,_,_,_,_,_ ), 0 , 83 , 0 , 10107, 146, 86 ), // #609
- INST(Psignd , ExtRm_P , O(000F38,0A,_,_,_,_,_,_ ), 0 , 83 , 0 , 10115, 146, 86 ), // #610
- INST(Psignw , ExtRm_P , O(000F38,09,_,_,_,_,_,_ ), 0 , 83 , 0 , 10123, 146, 86 ), // #611
- INST(Pslld , ExtRmRi_P , O(000F00,F2,_,_,_,_,_,_ ), O(000F00,72,6,_,_,_,_,_ ), 4 , 68 , 10131, 167, 82 ), // #612
- INST(Pslldq , ExtRmRi , 0 , O(660F00,73,7,_,_,_,_,_ ), 0 , 69 , 10138, 168, 4 ), // #613
- INST(Psllq , ExtRmRi_P , O(000F00,F3,_,_,_,_,_,_ ), O(000F00,73,6,_,_,_,_,_ ), 4 , 70 , 10146, 167, 82 ), // #614
- INST(Psllw , ExtRmRi_P , O(000F00,F1,_,_,_,_,_,_ ), O(000F00,71,6,_,_,_,_,_ ), 4 , 71 , 10177, 167, 82 ), // #615
- INST(Psmash , X86Op , O(F30F01,FF,_,_,_,_,_,_ ), 0 , 25 , 0 , 2395 , 33 , 98 ), // #616
- INST(Psrad , ExtRmRi_P , O(000F00,E2,_,_,_,_,_,_ ), O(000F00,72,4,_,_,_,_,_ ), 4 , 72 , 10184, 167, 82 ), // #617
- INST(Psraw , ExtRmRi_P , O(000F00,E1,_,_,_,_,_,_ ), O(000F00,71,4,_,_,_,_,_ ), 4 , 73 , 10222, 167, 82 ), // #618
- INST(Psrld , ExtRmRi_P , O(000F00,D2,_,_,_,_,_,_ ), O(000F00,72,2,_,_,_,_,_ ), 4 , 74 , 10229, 167, 82 ), // #619
- INST(Psrldq , ExtRmRi , 0 , O(660F00,73,3,_,_,_,_,_ ), 0 , 75 , 10236, 168, 4 ), // #620
- INST(Psrlq , ExtRmRi_P , O(000F00,D3,_,_,_,_,_,_ ), O(000F00,73,2,_,_,_,_,_ ), 4 , 76 , 10244, 167, 82 ), // #621
- INST(Psrlw , ExtRmRi_P , O(000F00,D1,_,_,_,_,_,_ ), O(000F00,71,2,_,_,_,_,_ ), 4 , 77 , 10275, 167, 82 ), // #622
- INST(Psubb , ExtRm_P , O(000F00,F8,_,_,_,_,_,_ ), 0 , 4 , 0 , 10282, 149, 82 ), // #623
- INST(Psubd , ExtRm_P , O(000F00,FA,_,_,_,_,_,_ ), 0 , 4 , 0 , 10289, 149, 82 ), // #624
- INST(Psubq , ExtRm_P , O(000F00,FB,_,_,_,_,_,_ ), 0 , 4 , 0 , 10296, 149, 4 ), // #625
- INST(Psubsb , ExtRm_P , O(000F00,E8,_,_,_,_,_,_ ), 0 , 4 , 0 , 10303, 149, 82 ), // #626
- INST(Psubsw , ExtRm_P , O(000F00,E9,_,_,_,_,_,_ ), 0 , 4 , 0 , 10311, 149, 82 ), // #627
- INST(Psubusb , ExtRm_P , O(000F00,D8,_,_,_,_,_,_ ), 0 , 4 , 0 , 10319, 149, 82 ), // #628
- INST(Psubusw , ExtRm_P , O(000F00,D9,_,_,_,_,_,_ ), 0 , 4 , 0 , 10328, 149, 82 ), // #629
- INST(Psubw , ExtRm_P , O(000F00,F9,_,_,_,_,_,_ ), 0 , 4 , 0 , 10337, 149, 82 ), // #630
- INST(Pswapd , Ext3dNow , O(000F0F,BB,_,_,_,_,_,_ ), 0 , 87 , 0 , 2402 , 150, 92 ), // #631
- INST(Ptest , ExtRm , O(660F38,17,_,_,_,_,_,_ ), 0 , 2 , 0 , 10366, 5 , 99 ), // #632
- INST(Ptwrite , X86M , O(F30F00,AE,4,_,_,_,_,_ ), 0 , 91 , 0 , 2409 , 169, 100), // #633
- INST(Punpckhbw , ExtRm_P , O(000F00,68,_,_,_,_,_,_ ), 0 , 4 , 0 , 10449, 146, 82 ), // #634
- INST(Punpckhdq , ExtRm_P , O(000F00,6A,_,_,_,_,_,_ ), 0 , 4 , 0 , 10460, 146, 82 ), // #635
- INST(Punpckhqdq , ExtRm , O(660F00,6D,_,_,_,_,_,_ ), 0 , 3 , 0 , 10471, 5 , 4 ), // #636
- INST(Punpckhwd , ExtRm_P , O(000F00,69,_,_,_,_,_,_ ), 0 , 4 , 0 , 10483, 146, 82 ), // #637
- INST(Punpcklbw , ExtRm_P , O(000F00,60,_,_,_,_,_,_ ), 0 , 4 , 0 , 10494, 170, 82 ), // #638
- INST(Punpckldq , ExtRm_P , O(000F00,62,_,_,_,_,_,_ ), 0 , 4 , 0 , 10505, 170, 82 ), // #639
- INST(Punpcklqdq , ExtRm , O(660F00,6C,_,_,_,_,_,_ ), 0 , 3 , 0 , 10516, 5 , 4 ), // #640
- INST(Punpcklwd , ExtRm_P , O(000F00,61,_,_,_,_,_,_ ), 0 , 4 , 0 , 10528, 170, 82 ), // #641
- INST(Push , X86Push , O(000000,FF,6,_,_,_,_,_ ), O(000000,50,_,_,_,_,_,_ ), 32 , 78 , 2417 , 171, 0 ), // #642
- INST(Pusha , X86Op , O(660000,60,_,_,_,_,_,_ ), 0 , 19 , 0 , 2422 , 81 , 0 ), // #643
- INST(Pushad , X86Op , O(000000,60,_,_,_,_,_,_ ), 0 , 0 , 0 , 2428 , 81 , 0 ), // #644
- INST(Pushf , X86Op , O(660000,9C,_,_,_,_,_,_ ), 0 , 19 , 0 , 2435 , 30 , 101), // #645
- INST(Pushfd , X86Op , O(000000,9C,_,_,_,_,_,_ ), 0 , 0 , 0 , 2441 , 81 , 101), // #646
- INST(Pushfq , X86Op , O(000000,9C,_,_,_,_,_,_ ), 0 , 0 , 0 , 2448 , 33 , 101), // #647
- INST(Pvalidate , X86Op , O(F20F01,FF,_,_,_,_,_,_ ), 0 , 92 , 0 , 2455 , 30 , 102), // #648
- INST(Pxor , ExtRm_P , O(000F00,EF,_,_,_,_,_,_ ), 0 , 4 , 0 , 10539, 149, 82 ), // #649
- INST(Rcl , X86Rot , O(000000,D0,2,_,x,_,_,_ ), 0 , 1 , 0 , 2465 , 172, 103), // #650
- INST(Rcpps , ExtRm , O(000F00,53,_,_,_,_,_,_ ), 0 , 4 , 0 , 10674, 5 , 5 ), // #651
- INST(Rcpss , ExtRm , O(F30F00,53,_,_,_,_,_,_ ), 0 , 6 , 0 , 10688, 7 , 5 ), // #652
- INST(Rcr , X86Rot , O(000000,D0,3,_,x,_,_,_ ), 0 , 75 , 0 , 2469 , 172, 103), // #653
- INST(Rdfsbase , X86M , O(F30F00,AE,0,_,x,_,_,_ ), 0 , 6 , 0 , 2473 , 173, 104), // #654
- INST(Rdgsbase , X86M , O(F30F00,AE,1,_,x,_,_,_ ), 0 , 93 , 0 , 2482 , 173, 104), // #655
- INST(Rdmsr , X86Op , O(000F00,32,_,_,_,_,_,_ ), 0 , 4 , 0 , 2491 , 174, 105), // #656
- INST(Rdpid , X86R_Native , O(F30F00,C7,7,_,_,_,_,_ ), 0 , 94 , 0 , 2497 , 175, 106), // #657
- INST(Rdpkru , X86Op , O(000F01,EE,_,_,_,_,_,_ ), 0 , 21 , 0 , 2503 , 174, 107), // #658
- INST(Rdpmc , X86Op , O(000F00,33,_,_,_,_,_,_ ), 0 , 4 , 0 , 2510 , 174, 0 ), // #659
- INST(Rdpru , X86Op , O(000F01,FD,_,_,_,_,_,_ ), 0 , 21 , 0 , 2516 , 174, 108), // #660
- INST(Rdrand , X86M , O(000F00,C7,6,_,x,_,_,_ ), 0 , 80 , 0 , 2522 , 23 , 109), // #661
- INST(Rdseed , X86M , O(000F00,C7,7,_,x,_,_,_ ), 0 , 22 , 0 , 2529 , 23 , 110), // #662
- INST(Rdsspd , X86M , O(F30F00,1E,1,_,_,_,_,_ ), 0 , 93 , 0 , 2536 , 76 , 56 ), // #663
- INST(Rdsspq , X86M , O(F30F00,1E,1,_,_,_,_,_ ), 0 , 93 , 0 , 2543 , 77 , 56 ), // #664
- INST(Rdtsc , X86Op , O(000F00,31,_,_,_,_,_,_ ), 0 , 4 , 0 , 2550 , 28 , 111), // #665
- INST(Rdtscp , X86Op , O(000F01,F9,_,_,_,_,_,_ ), 0 , 21 , 0 , 2556 , 174, 112), // #666
- INST(Ret , X86Ret , O(000000,C2,_,_,_,_,_,_ ), 0 , 0 , 0 , 3072 , 176, 0 ), // #667
- INST(Retf , X86Ret , O(000000,CA,_,_,x,_,_,_ ), 0 , 0 , 0 , 2563 , 177, 0 ), // #668
- INST(Rmpadjust , X86Op , O(F30F01,FE,_,_,_,_,_,_ ), 0 , 25 , 0 , 2568 , 33 , 98 ), // #669
- INST(Rmpupdate , X86Op , O(F20F01,FE,_,_,_,_,_,_ ), 0 , 92 , 0 , 2578 , 33 , 98 ), // #670
- INST(Rol , X86Rot , O(000000,D0,0,_,x,_,_,_ ), 0 , 0 , 0 , 2588 , 172, 113), // #671
- INST(Ror , X86Rot , O(000000,D0,1,_,x,_,_,_ ), 0 , 31 , 0 , 2592 , 172, 113), // #672
- INST(Rorx , VexRmi_Wx , V(F20F3A,F0,_,0,x,_,_,_ ), 0 , 95 , 0 , 2596 , 178, 85 ), // #673
- INST(Roundpd , ExtRmi , O(660F3A,09,_,_,_,_,_,_ ), 0 , 8 , 0 , 10827, 8 , 12 ), // #674
- INST(Roundps , ExtRmi , O(660F3A,08,_,_,_,_,_,_ ), 0 , 8 , 0 , 10836, 8 , 12 ), // #675
- INST(Roundsd , ExtRmi , O(660F3A,0B,_,_,_,_,_,_ ), 0 , 8 , 0 , 10845, 37 , 12 ), // #676
- INST(Roundss , ExtRmi , O(660F3A,0A,_,_,_,_,_,_ ), 0 , 8 , 0 , 10854, 38 , 12 ), // #677
- INST(Rsm , X86Op , O(000F00,AA,_,_,_,_,_,_ ), 0 , 4 , 0 , 2601 , 81 , 1 ), // #678
- INST(Rsqrtps , ExtRm , O(000F00,52,_,_,_,_,_,_ ), 0 , 4 , 0 , 10960, 5 , 5 ), // #679
- INST(Rsqrtss , ExtRm , O(F30F00,52,_,_,_,_,_,_ ), 0 , 6 , 0 , 10978, 7 , 5 ), // #680
- INST(Rstorssp , X86M_Only , O(F30F00,01,5,_,_,_,_,_ ), 0 , 63 , 0 , 2605 , 32 , 24 ), // #681
- INST(Sahf , X86Op , O(000000,9E,_,_,_,_,_,_ ), 0 , 0 , 0 , 2614 , 97 , 114), // #682
- INST(Sal , X86Rot , O(000000,D0,4,_,x,_,_,_ ), 0 , 9 , 0 , 2619 , 172, 1 ), // #683
- INST(Sar , X86Rot , O(000000,D0,7,_,x,_,_,_ ), 0 , 27 , 0 , 2623 , 172, 1 ), // #684
- INST(Sarx , VexRmv_Wx , V(F30F38,F7,_,0,x,_,_,_ ), 0 , 88 , 0 , 2627 , 13 , 85 ), // #685
- INST(Saveprevssp , X86Op , O(F30F01,EA,_,_,_,_,_,_ ), 0 , 25 , 0 , 2632 , 30 , 24 ), // #686
- INST(Sbb , X86Arith , O(000000,18,3,_,x,_,_,_ ), 0 , 75 , 0 , 2644 , 179, 2 ), // #687
- INST(Scas , X86StrRm , O(000000,AE,_,_,_,_,_,_ ), 0 , 0 , 0 , 2648 , 180, 37 ), // #688
- INST(Senduipi , X86M_NoSize , O(F30F00,C7,6,_,_,_,_,_ ), 0 , 24 , 0 , 2653 , 77 , 25 ), // #689
- INST(Serialize , X86Op , O(000F01,E8,_,_,_,_,_,_ ), 0 , 21 , 0 , 2662 , 30 , 115), // #690
- INST(Seta , X86Set , O(000F00,97,_,_,_,_,_,_ ), 0 , 4 , 0 , 2672 , 181, 59 ), // #691
- INST(Setae , X86Set , O(000F00,93,_,_,_,_,_,_ ), 0 , 4 , 0 , 2677 , 181, 60 ), // #692
- INST(Setb , X86Set , O(000F00,92,_,_,_,_,_,_ ), 0 , 4 , 0 , 2683 , 181, 60 ), // #693
- INST(Setbe , X86Set , O(000F00,96,_,_,_,_,_,_ ), 0 , 4 , 0 , 2688 , 181, 59 ), // #694
- INST(Setc , X86Set , O(000F00,92,_,_,_,_,_,_ ), 0 , 4 , 0 , 2694 , 181, 60 ), // #695
- INST(Sete , X86Set , O(000F00,94,_,_,_,_,_,_ ), 0 , 4 , 0 , 2699 , 181, 61 ), // #696
- INST(Setg , X86Set , O(000F00,9F,_,_,_,_,_,_ ), 0 , 4 , 0 , 2704 , 181, 62 ), // #697
- INST(Setge , X86Set , O(000F00,9D,_,_,_,_,_,_ ), 0 , 4 , 0 , 2709 , 181, 63 ), // #698
- INST(Setl , X86Set , O(000F00,9C,_,_,_,_,_,_ ), 0 , 4 , 0 , 2715 , 181, 63 ), // #699
- INST(Setle , X86Set , O(000F00,9E,_,_,_,_,_,_ ), 0 , 4 , 0 , 2720 , 181, 62 ), // #700
- INST(Setna , X86Set , O(000F00,96,_,_,_,_,_,_ ), 0 , 4 , 0 , 2726 , 181, 59 ), // #701
- INST(Setnae , X86Set , O(000F00,92,_,_,_,_,_,_ ), 0 , 4 , 0 , 2732 , 181, 60 ), // #702
- INST(Setnb , X86Set , O(000F00,93,_,_,_,_,_,_ ), 0 , 4 , 0 , 2739 , 181, 60 ), // #703
- INST(Setnbe , X86Set , O(000F00,97,_,_,_,_,_,_ ), 0 , 4 , 0 , 2745 , 181, 59 ), // #704
- INST(Setnc , X86Set , O(000F00,93,_,_,_,_,_,_ ), 0 , 4 , 0 , 2752 , 181, 60 ), // #705
- INST(Setne , X86Set , O(000F00,95,_,_,_,_,_,_ ), 0 , 4 , 0 , 2758 , 181, 61 ), // #706
- INST(Setng , X86Set , O(000F00,9E,_,_,_,_,_,_ ), 0 , 4 , 0 , 2764 , 181, 62 ), // #707
- INST(Setnge , X86Set , O(000F00,9C,_,_,_,_,_,_ ), 0 , 4 , 0 , 2770 , 181, 63 ), // #708
- INST(Setnl , X86Set , O(000F00,9D,_,_,_,_,_,_ ), 0 , 4 , 0 , 2777 , 181, 63 ), // #709
- INST(Setnle , X86Set , O(000F00,9F,_,_,_,_,_,_ ), 0 , 4 , 0 , 2783 , 181, 62 ), // #710
- INST(Setno , X86Set , O(000F00,91,_,_,_,_,_,_ ), 0 , 4 , 0 , 2790 , 181, 57 ), // #711
- INST(Setnp , X86Set , O(000F00,9B,_,_,_,_,_,_ ), 0 , 4 , 0 , 2796 , 181, 64 ), // #712
- INST(Setns , X86Set , O(000F00,99,_,_,_,_,_,_ ), 0 , 4 , 0 , 2802 , 181, 65 ), // #713
- INST(Setnz , X86Set , O(000F00,95,_,_,_,_,_,_ ), 0 , 4 , 0 , 2808 , 181, 61 ), // #714
- INST(Seto , X86Set , O(000F00,90,_,_,_,_,_,_ ), 0 , 4 , 0 , 2814 , 181, 57 ), // #715
- INST(Setp , X86Set , O(000F00,9A,_,_,_,_,_,_ ), 0 , 4 , 0 , 2819 , 181, 64 ), // #716
- INST(Setpe , X86Set , O(000F00,9A,_,_,_,_,_,_ ), 0 , 4 , 0 , 2824 , 181, 64 ), // #717
- INST(Setpo , X86Set , O(000F00,9B,_,_,_,_,_,_ ), 0 , 4 , 0 , 2830 , 181, 64 ), // #718
- INST(Sets , X86Set , O(000F00,98,_,_,_,_,_,_ ), 0 , 4 , 0 , 2836 , 181, 65 ), // #719
- INST(Setssbsy , X86Op , O(F30F01,E8,_,_,_,_,_,_ ), 0 , 25 , 0 , 2841 , 30 , 56 ), // #720
- INST(Setz , X86Set , O(000F00,94,_,_,_,_,_,_ ), 0 , 4 , 0 , 2850 , 181, 61 ), // #721
- INST(Sfence , X86Fence , O(000F00,AE,7,_,_,_,_,_ ), 0 , 22 , 0 , 2855 , 30 , 77 ), // #722
- INST(Sgdt , X86M_Only , O(000F00,01,0,_,_,_,_,_ ), 0 , 4 , 0 , 2862 , 69 , 0 ), // #723
- INST(Sha1msg1 , ExtRm , O(000F38,C9,_,_,_,_,_,_ ), 0 , 83 , 0 , 2867 , 5 , 116), // #724
- INST(Sha1msg2 , ExtRm , O(000F38,CA,_,_,_,_,_,_ ), 0 , 83 , 0 , 2876 , 5 , 116), // #725
- INST(Sha1nexte , ExtRm , O(000F38,C8,_,_,_,_,_,_ ), 0 , 83 , 0 , 2885 , 5 , 116), // #726
- INST(Sha1rnds4 , ExtRmi , O(000F3A,CC,_,_,_,_,_,_ ), 0 , 85 , 0 , 2895 , 8 , 116), // #727
- INST(Sha256msg1 , ExtRm , O(000F38,CC,_,_,_,_,_,_ ), 0 , 83 , 0 , 2905 , 5 , 116), // #728
- INST(Sha256msg2 , ExtRm , O(000F38,CD,_,_,_,_,_,_ ), 0 , 83 , 0 , 2916 , 5 , 116), // #729
- INST(Sha256rnds2 , ExtRm_XMM0 , O(000F38,CB,_,_,_,_,_,_ ), 0 , 83 , 0 , 2927 , 15 , 116), // #730
- INST(Shl , X86Rot , O(000000,D0,4,_,x,_,_,_ ), 0 , 9 , 0 , 2939 , 172, 1 ), // #731
- INST(Shld , X86ShldShrd , O(000F00,A4,_,_,x,_,_,_ ), 0 , 4 , 0 , 9938 , 182, 1 ), // #732
- INST(Shlx , VexRmv_Wx , V(660F38,F7,_,0,x,_,_,_ ), 0 , 96 , 0 , 2943 , 13 , 85 ), // #733
- INST(Shr , X86Rot , O(000000,D0,5,_,x,_,_,_ ), 0 , 62 , 0 , 2948 , 172, 1 ), // #734
- INST(Shrd , X86ShldShrd , O(000F00,AC,_,_,x,_,_,_ ), 0 , 4 , 0 , 2952 , 182, 1 ), // #735
- INST(Shrx , VexRmv_Wx , V(F20F38,F7,_,0,x,_,_,_ ), 0 , 84 , 0 , 2957 , 13 , 85 ), // #736
- INST(Shufpd , ExtRmi , O(660F00,C6,_,_,_,_,_,_ ), 0 , 3 , 0 , 11259, 8 , 4 ), // #737
- INST(Shufps , ExtRmi , O(000F00,C6,_,_,_,_,_,_ ), 0 , 4 , 0 , 11267, 8 , 5 ), // #738
- INST(Sidt , X86M_Only , O(000F00,01,1,_,_,_,_,_ ), 0 , 29 , 0 , 2962 , 69 , 0 ), // #739
- INST(Skinit , X86Op_xAX , O(000F01,DE,_,_,_,_,_,_ ), 0 , 21 , 0 , 2967 , 52 , 117), // #740
- INST(Sldt , X86M_NoMemSize , O(000F00,00,0,_,_,_,_,_ ), 0 , 4 , 0 , 2974 , 183, 0 ), // #741
- INST(Slwpcb , VexR_Wx , V(XOP_M9,12,1,0,x,_,_,_ ), 0 , 11 , 0 , 2979 , 108, 74 ), // #742
- INST(Smsw , X86M_NoMemSize , O(000F00,01,4,_,_,_,_,_ ), 0 , 97 , 0 , 2986 , 183, 0 ), // #743
- INST(Sqrtpd , ExtRm , O(660F00,51,_,_,_,_,_,_ ), 0 , 3 , 0 , 11275, 5 , 4 ), // #744
- INST(Sqrtps , ExtRm , O(000F00,51,_,_,_,_,_,_ ), 0 , 4 , 0 , 10961, 5 , 5 ), // #745
- INST(Sqrtsd , ExtRm , O(F20F00,51,_,_,_,_,_,_ ), 0 , 5 , 0 , 11299, 6 , 4 ), // #746
- INST(Sqrtss , ExtRm , O(F30F00,51,_,_,_,_,_,_ ), 0 , 6 , 0 , 10979, 7 , 5 ), // #747
- INST(Stac , X86Op , O(000F01,CB,_,_,_,_,_,_ ), 0 , 21 , 0 , 2991 , 30 , 16 ), // #748
- INST(Stc , X86Op , O(000000,F9,_,_,_,_,_,_ ), 0 , 0 , 0 , 2996 , 30 , 17 ), // #749
- INST(Std , X86Op , O(000000,FD,_,_,_,_,_,_ ), 0 , 0 , 0 , 7921 , 30 , 18 ), // #750
- INST(Stgi , X86Op , O(000F01,DC,_,_,_,_,_,_ ), 0 , 21 , 0 , 3000 , 30 , 117), // #751
- INST(Sti , X86Op , O(000000,FB,_,_,_,_,_,_ ), 0 , 0 , 0 , 3005 , 30 , 23 ), // #752
- INST(Stmxcsr , X86M_Only , O(000F00,AE,3,_,_,_,_,_ ), 0 , 78 , 0 , 11323, 101, 5 ), // #753
- INST(Stos , X86StrMr , O(000000,AA,_,_,_,_,_,_ ), 0 , 0 , 0 , 3009 , 184, 75 ), // #754
- INST(Str , X86M_NoMemSize , O(000F00,00,1,_,_,_,_,_ ), 0 , 29 , 0 , 3014 , 183, 0 ), // #755
- INST(Sttilecfg , AmxCfg , V(660F38,49,_,0,0,_,_,_ ), 0 , 96 , 0 , 3018 , 103, 73 ), // #756
- INST(Stui , X86Op , O(F30F01,EF,_,_,_,_,_,_ ), 0 , 25 , 0 , 3135 , 33 , 25 ), // #757
- INST(Sub , X86Arith , O(000000,28,5,_,x,_,_,_ ), 0 , 62 , 0 , 866 , 179, 1 ), // #758
- INST(Subpd , ExtRm , O(660F00,5C,_,_,_,_,_,_ ), 0 , 3 , 0 , 5413 , 5 , 4 ), // #759
- INST(Subps , ExtRm , O(000F00,5C,_,_,_,_,_,_ ), 0 , 4 , 0 , 5425 , 5 , 5 ), // #760
- INST(Subsd , ExtRm , O(F20F00,5C,_,_,_,_,_,_ ), 0 , 5 , 0 , 6392 , 6 , 4 ), // #761
- INST(Subss , ExtRm , O(F30F00,5C,_,_,_,_,_,_ ), 0 , 6 , 0 , 6402 , 7 , 5 ), // #762
- INST(Swapgs , X86Op , O(000F01,F8,_,_,_,_,_,_ ), 0 , 21 , 0 , 3028 , 33 , 0 ), // #763
- INST(Syscall , X86Op , O(000F00,05,_,_,_,_,_,_ ), 0 , 4 , 0 , 3035 , 33 , 0 ), // #764
- INST(Sysenter , X86Op , O(000F00,34,_,_,_,_,_,_ ), 0 , 4 , 0 , 3043 , 30 , 0 ), // #765
- INST(Sysexit , X86Op , O(000F00,35,_,_,_,_,_,_ ), 0 , 4 , 0 , 3052 , 30 , 0 ), // #766
- INST(Sysexitq , X86Op , O(000F00,35,_,_,1,_,_,_ ), 0 , 60 , 0 , 3060 , 30 , 0 ), // #767
- INST(Sysret , X86Op , O(000F00,07,_,_,_,_,_,_ ), 0 , 4 , 0 , 3069 , 33 , 0 ), // #768
- INST(Sysretq , X86Op , O(000F00,07,_,_,1,_,_,_ ), 0 , 60 , 0 , 3076 , 33 , 0 ), // #769
- INST(T1mskc , VexVm_Wx , V(XOP_M9,01,7,0,x,_,_,_ ), 0 , 98 , 0 , 3084 , 14 , 11 ), // #770
- INST(Tdpbf16ps , AmxRmv , V(F30F38,5C,_,0,0,_,_,_ ), 0 , 88 , 0 , 3091 , 185, 118), // #771
- INST(Tdpbssd , AmxRmv , V(F20F38,5E,_,0,0,_,_,_ ), 0 , 84 , 0 , 3101 , 185, 119), // #772
- INST(Tdpbsud , AmxRmv , V(F30F38,5E,_,0,0,_,_,_ ), 0 , 88 , 0 , 3109 , 185, 119), // #773
- INST(Tdpbusd , AmxRmv , V(660F38,5E,_,0,0,_,_,_ ), 0 , 96 , 0 , 3117 , 185, 119), // #774
- INST(Tdpbuud , AmxRmv , V(000F38,5E,_,0,0,_,_,_ ), 0 , 10 , 0 , 3125 , 185, 119), // #775
- INST(Test , X86Test , O(000000,84,_,_,x,_,_,_ ), O(000000,F6,_,_,x,_,_,_ ), 0 , 79 , 10367, 186, 1 ), // #776
- INST(Testui , X86Op , O(F30F01,ED,_,_,_,_,_,_ ), 0 , 25 , 0 , 3133 , 33 , 120), // #777
- INST(Tileloadd , AmxRm , V(F20F38,4B,_,0,0,_,_,_ ), 0 , 84 , 0 , 3140 , 187, 73 ), // #778
- INST(Tileloaddt1 , AmxRm , V(660F38,4B,_,0,0,_,_,_ ), 0 , 96 , 0 , 3150 , 187, 73 ), // #779
- INST(Tilerelease , VexOpMod , V(000F38,49,0,0,0,_,_,_ ), 0 , 10 , 0 , 3162 , 188, 73 ), // #780
- INST(Tilestored , AmxMr , V(F30F38,4B,_,0,0,_,_,_ ), 0 , 88 , 0 , 3174 , 189, 73 ), // #781
- INST(Tilezero , AmxR , V(F20F38,49,_,0,0,_,_,_ ), 0 , 84 , 0 , 3185 , 190, 73 ), // #782
- INST(Tpause , X86R32_EDX_EAX , O(660F00,AE,6,_,_,_,_,_ ), 0 , 26 , 0 , 3194 , 191, 121), // #783
- INST(Tzcnt , X86Rm_Raw66H , O(F30F00,BC,_,_,x,_,_,_ ), 0 , 6 , 0 , 3201 , 22 , 9 ), // #784
- INST(Tzmsk , VexVm_Wx , V(XOP_M9,01,4,0,x,_,_,_ ), 0 , 99 , 0 , 3207 , 14 , 11 ), // #785
- INST(Ucomisd , ExtRm , O(660F00,2E,_,_,_,_,_,_ ), 0 , 3 , 0 , 11390, 6 , 41 ), // #786
- INST(Ucomiss , ExtRm , O(000F00,2E,_,_,_,_,_,_ ), 0 , 4 , 0 , 11408, 7 , 42 ), // #787
- INST(Ud0 , X86Rm , O(000F00,FF,_,_,_,_,_,_ ), 0 , 4 , 0 , 3213 , 192, 0 ), // #788
- INST(Ud1 , X86Rm , O(000F00,B9,_,_,_,_,_,_ ), 0 , 4 , 0 , 3217 , 192, 0 ), // #789
- INST(Ud2 , X86Op , O(000F00,0B,_,_,_,_,_,_ ), 0 , 4 , 0 , 3221 , 30 , 0 ), // #790
- INST(Uiret , X86Op , O(F30F01,EC,_,_,_,_,_,_ ), 0 , 25 , 0 , 3225 , 33 , 25 ), // #791
- INST(Umonitor , X86R_FromM , O(F30F00,AE,6,_,_,_,_,_ ), 0 , 24 , 0 , 3231 , 193, 122), // #792
- INST(Umwait , X86R32_EDX_EAX , O(F20F00,AE,6,_,_,_,_,_ ), 0 , 100, 0 , 3240 , 191, 121), // #793
- INST(Unpckhpd , ExtRm , O(660F00,15,_,_,_,_,_,_ ), 0 , 3 , 0 , 11417, 5 , 4 ), // #794
- INST(Unpckhps , ExtRm , O(000F00,15,_,_,_,_,_,_ ), 0 , 4 , 0 , 11427, 5 , 5 ), // #795
- INST(Unpcklpd , ExtRm , O(660F00,14,_,_,_,_,_,_ ), 0 , 3 , 0 , 11437, 5 , 4 ), // #796
- INST(Unpcklps , ExtRm , O(000F00,14,_,_,_,_,_,_ ), 0 , 4 , 0 , 11447, 5 , 5 ), // #797
- INST(V4fmaddps , VexRm_T1_4X , E(F20F38,9A,_,2,_,0,4,T4X), 0 , 101, 0 , 3247 , 194, 123), // #798
- INST(V4fmaddss , VexRm_T1_4X , E(F20F38,9B,_,0,_,0,4,T4X), 0 , 102, 0 , 3257 , 195, 123), // #799
- INST(V4fnmaddps , VexRm_T1_4X , E(F20F38,AA,_,2,_,0,4,T4X), 0 , 101, 0 , 3267 , 194, 123), // #800
- INST(V4fnmaddss , VexRm_T1_4X , E(F20F38,AB,_,0,_,0,4,T4X), 0 , 102, 0 , 3278 , 195, 123), // #801
- INST(Vaddpd , VexRvm_Lx , V(660F00,58,_,x,I,1,4,FV ), 0 , 103, 0 , 3289 , 196, 124), // #802
- INST(Vaddph , VexRvm_Lx , E(00MAP5,58,_,_,_,0,4,FV ), 0 , 104, 0 , 3296 , 197, 125), // #803
- INST(Vaddps , VexRvm_Lx , V(000F00,58,_,x,I,0,4,FV ), 0 , 105, 0 , 3303 , 198, 124), // #804
- INST(Vaddsd , VexRvm , V(F20F00,58,_,I,I,1,3,T1S), 0 , 106, 0 , 3310 , 199, 126), // #805
- INST(Vaddsh , VexRvm , E(F3MAP5,58,_,_,_,0,1,T1S), 0 , 107, 0 , 3317 , 200, 127), // #806
- INST(Vaddss , VexRvm , V(F30F00,58,_,I,I,0,2,T1S), 0 , 108, 0 , 3324 , 201, 126), // #807
- INST(Vaddsubpd , VexRvm_Lx , V(660F00,D0,_,x,I,_,_,_ ), 0 , 69 , 0 , 3331 , 202, 128), // #808
- INST(Vaddsubps , VexRvm_Lx , V(F20F00,D0,_,x,I,_,_,_ ), 0 , 109, 0 , 3341 , 202, 128), // #809
- INST(Vaesdec , VexRvm_Lx , V(660F38,DE,_,x,I,_,4,FVM), 0 , 110, 0 , 3351 , 203, 129), // #810
- INST(Vaesdeclast , VexRvm_Lx , V(660F38,DF,_,x,I,_,4,FVM), 0 , 110, 0 , 3359 , 203, 129), // #811
- INST(Vaesenc , VexRvm_Lx , V(660F38,DC,_,x,I,_,4,FVM), 0 , 110, 0 , 3371 , 203, 129), // #812
- INST(Vaesenclast , VexRvm_Lx , V(660F38,DD,_,x,I,_,4,FVM), 0 , 110, 0 , 3379 , 203, 129), // #813
- INST(Vaesimc , VexRm , V(660F38,DB,_,0,I,_,_,_ ), 0 , 96 , 0 , 3391 , 204, 130), // #814
- INST(Vaeskeygenassist , VexRmi , V(660F3A,DF,_,0,I,_,_,_ ), 0 , 73 , 0 , 3399 , 205, 130), // #815
- INST(Valignd , VexRvmi_Lx , E(660F3A,03,_,x,_,0,4,FV ), 0 , 111, 0 , 3416 , 206, 131), // #816
- INST(Valignq , VexRvmi_Lx , E(660F3A,03,_,x,_,1,4,FV ), 0 , 112, 0 , 3424 , 207, 131), // #817
- INST(Vandnpd , VexRvm_Lx , V(660F00,55,_,x,I,1,4,FV ), 0 , 103, 0 , 3432 , 208, 132), // #818
- INST(Vandnps , VexRvm_Lx , V(000F00,55,_,x,I,0,4,FV ), 0 , 105, 0 , 3440 , 209, 132), // #819
- INST(Vandpd , VexRvm_Lx , V(660F00,54,_,x,I,1,4,FV ), 0 , 103, 0 , 3448 , 210, 132), // #820
- INST(Vandps , VexRvm_Lx , V(000F00,54,_,x,I,0,4,FV ), 0 , 105, 0 , 3455 , 211, 132), // #821
- INST(Vblendmpd , VexRvm_Lx , E(660F38,65,_,x,_,1,4,FV ), 0 , 113, 0 , 3462 , 212, 131), // #822
- INST(Vblendmps , VexRvm_Lx , E(660F38,65,_,x,_,0,4,FV ), 0 , 114, 0 , 3472 , 213, 131), // #823
- INST(Vblendpd , VexRvmi_Lx , V(660F3A,0D,_,x,I,_,_,_ ), 0 , 73 , 0 , 3482 , 214, 128), // #824
- INST(Vblendps , VexRvmi_Lx , V(660F3A,0C,_,x,I,_,_,_ ), 0 , 73 , 0 , 3491 , 214, 128), // #825
- INST(Vblendvpd , VexRvmr_Lx , V(660F3A,4B,_,x,0,_,_,_ ), 0 , 73 , 0 , 3500 , 215, 128), // #826
- INST(Vblendvps , VexRvmr_Lx , V(660F3A,4A,_,x,0,_,_,_ ), 0 , 73 , 0 , 3510 , 215, 128), // #827
- INST(Vbroadcastf128 , VexRm , V(660F38,1A,_,1,0,_,_,_ ), 0 , 115, 0 , 3520 , 216, 128), // #828
- INST(Vbroadcastf32x2 , VexRm_Lx , E(660F38,19,_,x,_,0,3,T2 ), 0 , 116, 0 , 3535 , 217, 133), // #829
- INST(Vbroadcastf32x4 , VexRm_Lx , E(660F38,1A,_,x,_,0,4,T4 ), 0 , 117, 0 , 3551 , 218, 68 ), // #830
- INST(Vbroadcastf32x8 , VexRm , E(660F38,1B,_,2,_,0,5,T8 ), 0 , 118, 0 , 3567 , 219, 66 ), // #831
- INST(Vbroadcastf64x2 , VexRm_Lx , E(660F38,1A,_,x,_,1,4,T2 ), 0 , 119, 0 , 3583 , 218, 133), // #832
- INST(Vbroadcastf64x4 , VexRm , E(660F38,1B,_,2,_,1,5,T4 ), 0 , 120, 0 , 3599 , 219, 68 ), // #833
- INST(Vbroadcasti128 , VexRm , V(660F38,5A,_,1,0,_,_,_ ), 0 , 115, 0 , 3615 , 216, 134), // #834
- INST(Vbroadcasti32x2 , VexRm_Lx , E(660F38,59,_,x,_,0,3,T2 ), 0 , 116, 0 , 3630 , 220, 133), // #835
- INST(Vbroadcasti32x4 , VexRm_Lx , E(660F38,5A,_,x,_,0,4,T4 ), 0 , 117, 0 , 3646 , 218, 131), // #836
- INST(Vbroadcasti32x8 , VexRm , E(660F38,5B,_,2,_,0,5,T8 ), 0 , 118, 0 , 3662 , 219, 66 ), // #837
- INST(Vbroadcasti64x2 , VexRm_Lx , E(660F38,5A,_,x,_,1,4,T2 ), 0 , 119, 0 , 3678 , 218, 133), // #838
- INST(Vbroadcasti64x4 , VexRm , E(660F38,5B,_,2,_,1,5,T4 ), 0 , 120, 0 , 3694 , 219, 68 ), // #839
- INST(Vbroadcastsd , VexRm_Lx , V(660F38,19,_,x,0,1,3,T1S), 0 , 121, 0 , 3710 , 221, 135), // #840
- INST(Vbroadcastss , VexRm_Lx , V(660F38,18,_,x,0,0,2,T1S), 0 , 122, 0 , 3723 , 222, 135), // #841
- INST(Vcmppd , VexRvmi_Lx_KEvex , V(660F00,C2,_,x,I,1,4,FV ), 0 , 103, 0 , 3736 , 223, 124), // #842
- INST(Vcmpph , VexRvmi_Lx_KEvex , E(000F3A,C2,_,_,_,0,4,FV ), 0 , 123, 0 , 3743 , 224, 125), // #843
- INST(Vcmpps , VexRvmi_Lx_KEvex , V(000F00,C2,_,x,I,0,4,FV ), 0 , 105, 0 , 3750 , 225, 124), // #844
- INST(Vcmpsd , VexRvmi_KEvex , V(F20F00,C2,_,I,I,1,3,T1S), 0 , 106, 0 , 3757 , 226, 126), // #845
- INST(Vcmpsh , VexRvmi_KEvex , E(F30F3A,C2,_,_,_,0,1,T1S), 0 , 124, 0 , 3764 , 227, 127), // #846
- INST(Vcmpss , VexRvmi_KEvex , V(F30F00,C2,_,I,I,0,2,T1S), 0 , 108, 0 , 3771 , 228, 126), // #847
- INST(Vcomisd , VexRm , V(660F00,2F,_,I,I,1,3,T1S), 0 , 125, 0 , 3778 , 229, 136), // #848
- INST(Vcomish , VexRm , E(00MAP5,2F,_,_,_,0,1,T1S), 0 , 126, 0 , 3786 , 230, 127), // #849
- INST(Vcomiss , VexRm , V(000F00,2F,_,I,I,0,2,T1S), 0 , 127, 0 , 3794 , 231, 136), // #850
- INST(Vcompresspd , VexMr_Lx , E(660F38,8A,_,x,_,1,3,T1S), 0 , 128, 0 , 3802 , 232, 131), // #851
- INST(Vcompressps , VexMr_Lx , E(660F38,8A,_,x,_,0,2,T1S), 0 , 129, 0 , 3814 , 232, 131), // #852
- INST(Vcvtdq2pd , VexRm_Lx , V(F30F00,E6,_,x,I,0,3,HV ), 0 , 130, 0 , 3826 , 233, 124), // #853
- INST(Vcvtdq2ph , VexRm_Lx , E(00MAP5,5B,_,_,_,0,4,FV ), 0 , 104, 0 , 3836 , 234, 125), // #854
- INST(Vcvtdq2ps , VexRm_Lx , V(000F00,5B,_,x,I,0,4,FV ), 0 , 105, 0 , 3846 , 235, 124), // #855
- INST(Vcvtne2ps2bf16 , VexRvm_Lx , E(F20F38,72,_,_,_,0,4,FV ), 0 , 131, 0 , 3856 , 213, 137), // #856
- INST(Vcvtneps2bf16 , VexRm_Lx_Narrow , E(F30F38,72,_,_,_,0,4,FV ), 0 , 132, 0 , 3871 , 236, 137), // #857
- INST(Vcvtpd2dq , VexRm_Lx_Narrow , V(F20F00,E6,_,x,I,1,4,FV ), 0 , 133, 0 , 3885 , 237, 124), // #858
- INST(Vcvtpd2ph , VexRm_Lx , E(66MAP5,5A,_,_,_,1,4,FV ), 0 , 134, 0 , 3895 , 238, 125), // #859
- INST(Vcvtpd2ps , VexRm_Lx_Narrow , V(660F00,5A,_,x,I,1,4,FV ), 0 , 103, 0 , 3905 , 237, 124), // #860
- INST(Vcvtpd2qq , VexRm_Lx , E(660F00,7B,_,x,_,1,4,FV ), 0 , 135, 0 , 3915 , 239, 133), // #861
- INST(Vcvtpd2udq , VexRm_Lx_Narrow , E(000F00,79,_,x,_,1,4,FV ), 0 , 136, 0 , 3925 , 240, 131), // #862
- INST(Vcvtpd2uqq , VexRm_Lx , E(660F00,79,_,x,_,1,4,FV ), 0 , 135, 0 , 3936 , 239, 133), // #863
- INST(Vcvtph2dq , VexRm_Lx , E(66MAP5,5B,_,_,_,0,3,HV ), 0 , 137, 0 , 3947 , 241, 125), // #864
- INST(Vcvtph2pd , VexRm_Lx , E(00MAP5,5A,_,_,_,0,2,QV ), 0 , 138, 0 , 3957 , 242, 125), // #865
- INST(Vcvtph2ps , VexRm_Lx , V(660F38,13,_,x,0,0,3,HVM), 0 , 139, 0 , 3967 , 243, 138), // #866
- INST(Vcvtph2psx , VexRm_Lx , E(66MAP6,13,_,_,_,0,3,HV ), 0 , 140, 0 , 3977 , 244, 125), // #867
- INST(Vcvtph2qq , VexRm_Lx , E(66MAP5,7B,_,_,_,0,2,QV ), 0 , 141, 0 , 3988 , 245, 125), // #868
- INST(Vcvtph2udq , VexRm_Lx , E(00MAP5,79,_,_,_,0,3,HV ), 0 , 142, 0 , 3998 , 241, 125), // #869
- INST(Vcvtph2uqq , VexRm_Lx , E(66MAP5,79,_,_,_,0,2,QV ), 0 , 141, 0 , 4009 , 245, 125), // #870
- INST(Vcvtph2uw , VexRm_Lx , E(00MAP5,7D,_,_,_,0,4,FV ), 0 , 104, 0 , 4020 , 246, 125), // #871
- INST(Vcvtph2w , VexRm_Lx , E(66MAP5,7D,_,_,_,0,4,FV ), 0 , 143, 0 , 4030 , 246, 125), // #872
- INST(Vcvtps2dq , VexRm_Lx , V(660F00,5B,_,x,I,0,4,FV ), 0 , 144, 0 , 4039 , 235, 124), // #873
- INST(Vcvtps2pd , VexRm_Lx , V(000F00,5A,_,x,I,0,3,HV ), 0 , 145, 0 , 4049 , 247, 124), // #874
- INST(Vcvtps2ph , VexMri_Lx , V(660F3A,1D,_,x,0,0,3,HVM), 0 , 146, 0 , 4059 , 248, 138), // #875
- INST(Vcvtps2phx , VexRm_Lx , E(66MAP5,1D,_,_,_,0,4,FV ), 0 , 143, 0 , 4069 , 234, 125), // #876
- INST(Vcvtps2qq , VexRm_Lx , E(660F00,7B,_,x,_,0,3,HV ), 0 , 147, 0 , 4080 , 249, 133), // #877
- INST(Vcvtps2udq , VexRm_Lx , E(000F00,79,_,x,_,0,4,FV ), 0 , 148, 0 , 4090 , 250, 131), // #878
- INST(Vcvtps2uqq , VexRm_Lx , E(660F00,79,_,x,_,0,3,HV ), 0 , 147, 0 , 4101 , 249, 133), // #879
- INST(Vcvtqq2pd , VexRm_Lx , E(F30F00,E6,_,x,_,1,4,FV ), 0 , 149, 0 , 4112 , 239, 133), // #880
- INST(Vcvtqq2ph , VexRm_Lx , E(00MAP5,5B,_,_,_,1,4,FV ), 0 , 150, 0 , 4122 , 238, 125), // #881
- INST(Vcvtqq2ps , VexRm_Lx_Narrow , E(000F00,5B,_,x,_,1,4,FV ), 0 , 136, 0 , 4132 , 240, 133), // #882
- INST(Vcvtsd2sh , VexRvm , E(F2MAP5,5A,_,_,_,1,3,T1S), 0 , 151, 0 , 4142 , 251, 127), // #883
- INST(Vcvtsd2si , VexRm_Wx , V(F20F00,2D,_,I,x,x,3,T1F), 0 , 152, 0 , 4152 , 252, 126), // #884
- INST(Vcvtsd2ss , VexRvm , V(F20F00,5A,_,I,I,1,3,T1S), 0 , 106, 0 , 4162 , 199, 126), // #885
- INST(Vcvtsd2usi , VexRm_Wx , E(F20F00,79,_,I,_,x,3,T1F), 0 , 153, 0 , 4172 , 253, 68 ), // #886
- INST(Vcvtsh2sd , VexRvm , E(F3MAP5,5A,_,_,_,0,1,T1S), 0 , 107, 0 , 4183 , 254, 127), // #887
- INST(Vcvtsh2si , VexRm_Wx , E(F3MAP5,2D,_,_,_,x,1,T1S), 0 , 107, 0 , 4193 , 255, 127), // #888
- INST(Vcvtsh2ss , VexRvm , E(00MAP6,13,_,_,_,0,1,T1S), 0 , 154, 0 , 4203 , 254, 127), // #889
- INST(Vcvtsh2usi , VexRm_Wx , E(F3MAP5,79,_,_,_,x,1,T1S), 0 , 107, 0 , 4213 , 255, 127), // #890
- INST(Vcvtsi2sd , VexRvm_Wx , V(F20F00,2A,_,I,x,x,2,T1W), 0 , 155, 0 , 4224 , 256, 126), // #891
- INST(Vcvtsi2sh , VexRvm_Wx , E(F3MAP5,2A,_,_,_,x,2,T1W), 0 , 156, 0 , 4234 , 257, 127), // #892
- INST(Vcvtsi2ss , VexRvm_Wx , V(F30F00,2A,_,I,x,x,2,T1W), 0 , 157, 0 , 4244 , 256, 126), // #893
- INST(Vcvtss2sd , VexRvm , V(F30F00,5A,_,I,I,0,2,T1S), 0 , 108, 0 , 4254 , 258, 126), // #894
- INST(Vcvtss2sh , VexRvm , E(00MAP5,1D,_,_,_,0,2,T1S), 0 , 158, 0 , 4264 , 259, 127), // #895
- INST(Vcvtss2si , VexRm_Wx , V(F30F00,2D,_,I,x,x,2,T1F), 0 , 108, 0 , 4274 , 260, 126), // #896
- INST(Vcvtss2usi , VexRm_Wx , E(F30F00,79,_,I,_,x,2,T1F), 0 , 159, 0 , 4284 , 261, 68 ), // #897
- INST(Vcvttpd2dq , VexRm_Lx_Narrow , V(660F00,E6,_,x,I,1,4,FV ), 0 , 103, 0 , 4295 , 262, 124), // #898
- INST(Vcvttpd2qq , VexRm_Lx , E(660F00,7A,_,x,_,1,4,FV ), 0 , 135, 0 , 4306 , 263, 131), // #899
- INST(Vcvttpd2udq , VexRm_Lx_Narrow , E(000F00,78,_,x,_,1,4,FV ), 0 , 136, 0 , 4317 , 264, 131), // #900
- INST(Vcvttpd2uqq , VexRm_Lx , E(660F00,78,_,x,_,1,4,FV ), 0 , 135, 0 , 4329 , 263, 133), // #901
- INST(Vcvttph2dq , VexRm_Lx , E(F3MAP5,5B,_,_,_,0,3,HV ), 0 , 160, 0 , 4341 , 244, 125), // #902
- INST(Vcvttph2qq , VexRm_Lx , E(66MAP5,7A,_,_,_,0,2,QV ), 0 , 141, 0 , 4352 , 242, 125), // #903
- INST(Vcvttph2udq , VexRm_Lx , E(00MAP5,78,_,_,_,0,3,HV ), 0 , 142, 0 , 4363 , 244, 125), // #904
- INST(Vcvttph2uqq , VexRm_Lx , E(66MAP5,78,_,_,_,0,2,QV ), 0 , 141, 0 , 4375 , 242, 125), // #905
- INST(Vcvttph2uw , VexRm_Lx , E(00MAP5,7C,_,_,_,0,4,FV ), 0 , 104, 0 , 4387 , 265, 125), // #906
- INST(Vcvttph2w , VexRm_Lx , E(66MAP5,7C,_,_,_,0,4,FV ), 0 , 143, 0 , 4398 , 265, 125), // #907
- INST(Vcvttps2dq , VexRm_Lx , V(F30F00,5B,_,x,I,0,4,FV ), 0 , 161, 0 , 4408 , 266, 124), // #908
- INST(Vcvttps2qq , VexRm_Lx , E(660F00,7A,_,x,_,0,3,HV ), 0 , 147, 0 , 4419 , 267, 133), // #909
- INST(Vcvttps2udq , VexRm_Lx , E(000F00,78,_,x,_,0,4,FV ), 0 , 148, 0 , 4430 , 268, 131), // #910
- INST(Vcvttps2uqq , VexRm_Lx , E(660F00,78,_,x,_,0,3,HV ), 0 , 147, 0 , 4442 , 267, 133), // #911
- INST(Vcvttsd2si , VexRm_Wx , V(F20F00,2C,_,I,x,x,3,T1F), 0 , 152, 0 , 4454 , 269, 126), // #912
- INST(Vcvttsd2usi , VexRm_Wx , E(F20F00,78,_,I,_,x,3,T1F), 0 , 153, 0 , 4465 , 270, 68 ), // #913
- INST(Vcvttsh2si , VexRm_Wx , E(F3MAP5,2C,_,_,_,x,1,T1S), 0 , 107, 0 , 4477 , 271, 127), // #914
- INST(Vcvttsh2usi , VexRm_Wx , E(F3MAP5,78,_,_,_,x,1,T1S), 0 , 107, 0 , 4488 , 271, 127), // #915
- INST(Vcvttss2si , VexRm_Wx , V(F30F00,2C,_,I,x,x,2,T1F), 0 , 108, 0 , 4500 , 272, 126), // #916
- INST(Vcvttss2usi , VexRm_Wx , E(F30F00,78,_,I,_,x,2,T1F), 0 , 159, 0 , 4511 , 273, 68 ), // #917
- INST(Vcvtudq2pd , VexRm_Lx , E(F30F00,7A,_,x,_,0,3,HV ), 0 , 162, 0 , 4523 , 274, 131), // #918
- INST(Vcvtudq2ph , VexRm_Lx , E(F2MAP5,7A,_,_,_,0,4,FV ), 0 , 163, 0 , 4534 , 234, 125), // #919
- INST(Vcvtudq2ps , VexRm_Lx , E(F20F00,7A,_,x,_,0,4,FV ), 0 , 164, 0 , 4545 , 250, 131), // #920
- INST(Vcvtuqq2pd , VexRm_Lx , E(F30F00,7A,_,x,_,1,4,FV ), 0 , 149, 0 , 4556 , 239, 133), // #921
- INST(Vcvtuqq2ph , VexRm_Lx , E(F2MAP5,7A,_,_,_,1,4,FV ), 0 , 165, 0 , 4567 , 238, 125), // #922
- INST(Vcvtuqq2ps , VexRm_Lx_Narrow , E(F20F00,7A,_,x,_,1,4,FV ), 0 , 166, 0 , 4578 , 240, 133), // #923
- INST(Vcvtusi2sd , VexRvm_Wx , E(F20F00,7B,_,I,_,x,2,T1W), 0 , 167, 0 , 4589 , 257, 68 ), // #924
- INST(Vcvtusi2sh , VexRvm_Wx , E(F3MAP5,7B,_,_,_,x,2,T1W), 0 , 156, 0 , 4600 , 257, 127), // #925
- INST(Vcvtusi2ss , VexRvm_Wx , E(F30F00,7B,_,I,_,x,2,T1W), 0 , 168, 0 , 4611 , 257, 68 ), // #926
- INST(Vcvtuw2ph , VexRm_Lx , E(F2MAP5,7D,_,_,_,0,4,FV ), 0 , 163, 0 , 4622 , 246, 125), // #927
- INST(Vcvtw2ph , VexRm_Lx , E(F3MAP5,7D,_,_,_,0,4,FV ), 0 , 169, 0 , 4632 , 246, 125), // #928
- INST(Vdbpsadbw , VexRvmi_Lx , E(660F3A,42,_,x,_,0,4,FVM), 0 , 111, 0 , 4641 , 275, 139), // #929
- INST(Vdivpd , VexRvm_Lx , V(660F00,5E,_,x,I,1,4,FV ), 0 , 103, 0 , 4651 , 196, 124), // #930
- INST(Vdivph , VexRvm_Lx , E(00MAP5,5E,_,_,_,0,4,FV ), 0 , 104, 0 , 4658 , 197, 125), // #931
- INST(Vdivps , VexRvm_Lx , V(000F00,5E,_,x,I,0,4,FV ), 0 , 105, 0 , 4665 , 198, 124), // #932
- INST(Vdivsd , VexRvm , V(F20F00,5E,_,I,I,1,3,T1S), 0 , 106, 0 , 4672 , 199, 126), // #933
- INST(Vdivsh , VexRvm , E(F3MAP5,5E,_,_,_,0,1,T1S), 0 , 107, 0 , 4679 , 200, 127), // #934
- INST(Vdivss , VexRvm , V(F30F00,5E,_,I,I,0,2,T1S), 0 , 108, 0 , 4686 , 201, 126), // #935
- INST(Vdpbf16ps , VexRvm_Lx , E(F30F38,52,_,_,_,0,4,FV ), 0 , 132, 0 , 4693 , 213, 137), // #936
- INST(Vdppd , VexRvmi_Lx , V(660F3A,41,_,x,I,_,_,_ ), 0 , 73 , 0 , 4703 , 276, 128), // #937
- INST(Vdpps , VexRvmi_Lx , V(660F3A,40,_,x,I,_,_,_ ), 0 , 73 , 0 , 4709 , 214, 128), // #938
- INST(Verr , X86M_NoSize , O(000F00,00,4,_,_,_,_,_ ), 0 , 97 , 0 , 4715 , 107, 10 ), // #939
- INST(Verw , X86M_NoSize , O(000F00,00,5,_,_,_,_,_ ), 0 , 77 , 0 , 4720 , 107, 10 ), // #940
- INST(Vexp2pd , VexRm , E(660F38,C8,_,2,_,1,4,FV ), 0 , 170, 0 , 4725 , 277, 140), // #941
- INST(Vexp2ps , VexRm , E(660F38,C8,_,2,_,0,4,FV ), 0 , 171, 0 , 4733 , 278, 140), // #942
- INST(Vexpandpd , VexRm_Lx , E(660F38,88,_,x,_,1,3,T1S), 0 , 128, 0 , 4741 , 279, 131), // #943
- INST(Vexpandps , VexRm_Lx , E(660F38,88,_,x,_,0,2,T1S), 0 , 129, 0 , 4751 , 279, 131), // #944
- INST(Vextractf128 , VexMri , V(660F3A,19,_,1,0,_,_,_ ), 0 , 172, 0 , 4761 , 280, 128), // #945
- INST(Vextractf32x4 , VexMri_Lx , E(660F3A,19,_,x,_,0,4,T4 ), 0 , 173, 0 , 4774 , 281, 131), // #946
- INST(Vextractf32x8 , VexMri , E(660F3A,1B,_,2,_,0,5,T8 ), 0 , 174, 0 , 4788 , 282, 66 ), // #947
- INST(Vextractf64x2 , VexMri_Lx , E(660F3A,19,_,x,_,1,4,T2 ), 0 , 175, 0 , 4802 , 281, 133), // #948
- INST(Vextractf64x4 , VexMri , E(660F3A,1B,_,2,_,1,5,T4 ), 0 , 176, 0 , 4816 , 282, 68 ), // #949
- INST(Vextracti128 , VexMri , V(660F3A,39,_,1,0,_,_,_ ), 0 , 172, 0 , 4830 , 280, 134), // #950
- INST(Vextracti32x4 , VexMri_Lx , E(660F3A,39,_,x,_,0,4,T4 ), 0 , 173, 0 , 4843 , 281, 131), // #951
- INST(Vextracti32x8 , VexMri , E(660F3A,3B,_,2,_,0,5,T8 ), 0 , 174, 0 , 4857 , 282, 66 ), // #952
- INST(Vextracti64x2 , VexMri_Lx , E(660F3A,39,_,x,_,1,4,T2 ), 0 , 175, 0 , 4871 , 281, 133), // #953
- INST(Vextracti64x4 , VexMri , E(660F3A,3B,_,2,_,1,5,T4 ), 0 , 176, 0 , 4885 , 282, 68 ), // #954
- INST(Vextractps , VexMri , V(660F3A,17,_,0,I,I,2,T1S), 0 , 177, 0 , 4899 , 283, 126), // #955
- INST(Vfcmaddcph , VexRvm_Lx , E(F2MAP6,56,_,_,_,0,4,FV ), 0 , 178, 0 , 4910 , 284, 125), // #956
- INST(Vfcmaddcsh , VexRvm , E(F2MAP6,57,_,_,_,0,2,T1S), 0 , 179, 0 , 4921 , 259, 125), // #957
- INST(Vfcmulcph , VexRvm_Lx , E(F2MAP6,D6,_,_,_,0,4,FV ), 0 , 178, 0 , 4932 , 284, 125), // #958
- INST(Vfcmulcsh , VexRvm , E(F2MAP6,D7,_,_,_,0,2,T1S), 0 , 179, 0 , 4942 , 259, 125), // #959
- INST(Vfixupimmpd , VexRvmi_Lx , E(660F3A,54,_,x,_,1,4,FV ), 0 , 112, 0 , 4952 , 285, 131), // #960
- INST(Vfixupimmps , VexRvmi_Lx , E(660F3A,54,_,x,_,0,4,FV ), 0 , 111, 0 , 4964 , 286, 131), // #961
- INST(Vfixupimmsd , VexRvmi , E(660F3A,55,_,I,_,1,3,T1S), 0 , 180, 0 , 4976 , 287, 68 ), // #962
- INST(Vfixupimmss , VexRvmi , E(660F3A,55,_,I,_,0,2,T1S), 0 , 181, 0 , 4988 , 288, 68 ), // #963
- INST(Vfmadd132pd , VexRvm_Lx , V(660F38,98,_,x,1,1,4,FV ), 0 , 182, 0 , 5000 , 196, 141), // #964
- INST(Vfmadd132ph , VexRvm_Lx , E(66MAP6,98,_,_,_,0,4,FV ), 0 , 183, 0 , 5012 , 197, 125), // #965
- INST(Vfmadd132ps , VexRvm_Lx , V(660F38,98,_,x,0,0,4,FV ), 0 , 110, 0 , 5024 , 198, 141), // #966
- INST(Vfmadd132sd , VexRvm , V(660F38,99,_,I,1,1,3,T1S), 0 , 184, 0 , 5036 , 199, 142), // #967
- INST(Vfmadd132sh , VexRvm , E(66MAP6,99,_,_,_,0,1,T1S), 0 , 185, 0 , 5048 , 200, 127), // #968
- INST(Vfmadd132ss , VexRvm , V(660F38,99,_,I,0,0,2,T1S), 0 , 122, 0 , 5060 , 201, 142), // #969
- INST(Vfmadd213pd , VexRvm_Lx , V(660F38,A8,_,x,1,1,4,FV ), 0 , 182, 0 , 5072 , 196, 141), // #970
- INST(Vfmadd213ph , VexRvm_Lx , E(66MAP6,A8,_,_,_,0,4,FV ), 0 , 183, 0 , 5084 , 197, 125), // #971
- INST(Vfmadd213ps , VexRvm_Lx , V(660F38,A8,_,x,0,0,4,FV ), 0 , 110, 0 , 5096 , 198, 141), // #972
- INST(Vfmadd213sd , VexRvm , V(660F38,A9,_,I,1,1,3,T1S), 0 , 184, 0 , 5108 , 199, 142), // #973
- INST(Vfmadd213sh , VexRvm , E(66MAP6,A9,_,_,_,0,1,T1S), 0 , 185, 0 , 5120 , 200, 127), // #974
- INST(Vfmadd213ss , VexRvm , V(660F38,A9,_,I,0,0,2,T1S), 0 , 122, 0 , 5132 , 201, 142), // #975
- INST(Vfmadd231pd , VexRvm_Lx , V(660F38,B8,_,x,1,1,4,FV ), 0 , 182, 0 , 5144 , 196, 141), // #976
- INST(Vfmadd231ph , VexRvm_Lx , E(66MAP6,B8,_,_,_,0,4,FV ), 0 , 183, 0 , 5156 , 197, 125), // #977
- INST(Vfmadd231ps , VexRvm_Lx , V(660F38,B8,_,x,0,0,4,FV ), 0 , 110, 0 , 5168 , 198, 141), // #978
- INST(Vfmadd231sd , VexRvm , V(660F38,B9,_,I,1,1,3,T1S), 0 , 184, 0 , 5180 , 199, 142), // #979
- INST(Vfmadd231sh , VexRvm , E(66MAP6,B9,_,_,_,0,1,T1S), 0 , 185, 0 , 5192 , 200, 127), // #980
- INST(Vfmadd231ss , VexRvm , V(660F38,B9,_,I,0,0,2,T1S), 0 , 122, 0 , 5204 , 201, 142), // #981
- INST(Vfmaddcph , VexRvm_Lx , E(F3MAP6,56,_,_,_,0,4,FV ), 0 , 186, 0 , 5216 , 284, 125), // #982
- INST(Vfmaddcsh , VexRvm , E(F3MAP6,57,_,_,_,0,2,T1S), 0 , 187, 0 , 5226 , 259, 125), // #983
- INST(Vfmaddpd , Fma4_Lx , V(660F3A,69,_,x,x,_,_,_ ), 0 , 73 , 0 , 5236 , 289, 143), // #984
- INST(Vfmaddps , Fma4_Lx , V(660F3A,68,_,x,x,_,_,_ ), 0 , 73 , 0 , 5245 , 289, 143), // #985
- INST(Vfmaddsd , Fma4 , V(660F3A,6B,_,0,x,_,_,_ ), 0 , 73 , 0 , 5254 , 290, 143), // #986
- INST(Vfmaddss , Fma4 , V(660F3A,6A,_,0,x,_,_,_ ), 0 , 73 , 0 , 5263 , 291, 143), // #987
- INST(Vfmaddsub132pd , VexRvm_Lx , V(660F38,96,_,x,1,1,4,FV ), 0 , 182, 0 , 5272 , 196, 141), // #988
- INST(Vfmaddsub132ph , VexRvm_Lx , E(66MAP6,96,_,_,_,0,4,FV ), 0 , 183, 0 , 5287 , 197, 125), // #989
- INST(Vfmaddsub132ps , VexRvm_Lx , V(660F38,96,_,x,0,0,4,FV ), 0 , 110, 0 , 5302 , 198, 141), // #990
- INST(Vfmaddsub213pd , VexRvm_Lx , V(660F38,A6,_,x,1,1,4,FV ), 0 , 182, 0 , 5317 , 196, 141), // #991
- INST(Vfmaddsub213ph , VexRvm_Lx , E(66MAP6,A6,_,_,_,0,4,FV ), 0 , 183, 0 , 5332 , 197, 125), // #992
- INST(Vfmaddsub213ps , VexRvm_Lx , V(660F38,A6,_,x,0,0,4,FV ), 0 , 110, 0 , 5347 , 198, 141), // #993
- INST(Vfmaddsub231pd , VexRvm_Lx , V(660F38,B6,_,x,1,1,4,FV ), 0 , 182, 0 , 5362 , 196, 141), // #994
- INST(Vfmaddsub231ph , VexRvm_Lx , E(66MAP6,B6,_,_,_,0,4,FV ), 0 , 183, 0 , 5377 , 197, 125), // #995
- INST(Vfmaddsub231ps , VexRvm_Lx , V(660F38,B6,_,x,0,0,4,FV ), 0 , 110, 0 , 5392 , 198, 141), // #996
- INST(Vfmaddsubpd , Fma4_Lx , V(660F3A,5D,_,x,x,_,_,_ ), 0 , 73 , 0 , 5407 , 289, 143), // #997
- INST(Vfmaddsubps , Fma4_Lx , V(660F3A,5C,_,x,x,_,_,_ ), 0 , 73 , 0 , 5419 , 289, 143), // #998
- INST(Vfmsub132pd , VexRvm_Lx , V(660F38,9A,_,x,1,1,4,FV ), 0 , 182, 0 , 5431 , 196, 141), // #999
- INST(Vfmsub132ph , VexRvm_Lx , E(66MAP6,9A,_,_,_,0,4,FV ), 0 , 183, 0 , 5443 , 197, 125), // #1000
- INST(Vfmsub132ps , VexRvm_Lx , V(660F38,9A,_,x,0,0,4,FV ), 0 , 110, 0 , 5455 , 198, 141), // #1001
- INST(Vfmsub132sd , VexRvm , V(660F38,9B,_,I,1,1,3,T1S), 0 , 184, 0 , 5467 , 199, 142), // #1002
- INST(Vfmsub132sh , VexRvm , E(66MAP6,9B,_,_,_,0,1,T1S), 0 , 185, 0 , 5479 , 200, 127), // #1003
- INST(Vfmsub132ss , VexRvm , V(660F38,9B,_,I,0,0,2,T1S), 0 , 122, 0 , 5491 , 201, 142), // #1004
- INST(Vfmsub213pd , VexRvm_Lx , V(660F38,AA,_,x,1,1,4,FV ), 0 , 182, 0 , 5503 , 196, 141), // #1005
- INST(Vfmsub213ph , VexRvm_Lx , E(66MAP6,AA,_,_,_,0,4,FV ), 0 , 183, 0 , 5515 , 197, 125), // #1006
- INST(Vfmsub213ps , VexRvm_Lx , V(660F38,AA,_,x,0,0,4,FV ), 0 , 110, 0 , 5527 , 198, 141), // #1007
- INST(Vfmsub213sd , VexRvm , V(660F38,AB,_,I,1,1,3,T1S), 0 , 184, 0 , 5539 , 199, 142), // #1008
- INST(Vfmsub213sh , VexRvm , E(66MAP6,AB,_,_,_,0,1,T1S), 0 , 185, 0 , 5551 , 200, 127), // #1009
- INST(Vfmsub213ss , VexRvm , V(660F38,AB,_,I,0,0,2,T1S), 0 , 122, 0 , 5563 , 201, 142), // #1010
- INST(Vfmsub231pd , VexRvm_Lx , V(660F38,BA,_,x,1,1,4,FV ), 0 , 182, 0 , 5575 , 196, 141), // #1011
- INST(Vfmsub231ph , VexRvm_Lx , E(66MAP6,BA,_,_,_,0,4,FV ), 0 , 183, 0 , 5587 , 197, 125), // #1012
- INST(Vfmsub231ps , VexRvm_Lx , V(660F38,BA,_,x,0,0,4,FV ), 0 , 110, 0 , 5599 , 198, 141), // #1013
- INST(Vfmsub231sd , VexRvm , V(660F38,BB,_,I,1,1,3,T1S), 0 , 184, 0 , 5611 , 199, 142), // #1014
- INST(Vfmsub231sh , VexRvm , E(66MAP6,BB,_,_,_,0,1,T1S), 0 , 185, 0 , 5623 , 200, 127), // #1015
- INST(Vfmsub231ss , VexRvm , V(660F38,BB,_,I,0,0,2,T1S), 0 , 122, 0 , 5635 , 201, 142), // #1016
- INST(Vfmsubadd132pd , VexRvm_Lx , V(660F38,97,_,x,1,1,4,FV ), 0 , 182, 0 , 5647 , 196, 141), // #1017
- INST(Vfmsubadd132ph , VexRvm_Lx , E(66MAP6,97,_,_,_,0,4,FV ), 0 , 183, 0 , 5662 , 197, 125), // #1018
- INST(Vfmsubadd132ps , VexRvm_Lx , V(660F38,97,_,x,0,0,4,FV ), 0 , 110, 0 , 5677 , 198, 141), // #1019
- INST(Vfmsubadd213pd , VexRvm_Lx , V(660F38,A7,_,x,1,1,4,FV ), 0 , 182, 0 , 5692 , 196, 141), // #1020
- INST(Vfmsubadd213ph , VexRvm_Lx , E(66MAP6,A7,_,_,_,0,4,FV ), 0 , 183, 0 , 5707 , 197, 125), // #1021
- INST(Vfmsubadd213ps , VexRvm_Lx , V(660F38,A7,_,x,0,0,4,FV ), 0 , 110, 0 , 5722 , 198, 141), // #1022
- INST(Vfmsubadd231pd , VexRvm_Lx , V(660F38,B7,_,x,1,1,4,FV ), 0 , 182, 0 , 5737 , 196, 141), // #1023
- INST(Vfmsubadd231ph , VexRvm_Lx , E(66MAP6,B7,_,_,_,0,4,FV ), 0 , 183, 0 , 5752 , 197, 125), // #1024
- INST(Vfmsubadd231ps , VexRvm_Lx , V(660F38,B7,_,x,0,0,4,FV ), 0 , 110, 0 , 5767 , 198, 141), // #1025
- INST(Vfmsubaddpd , Fma4_Lx , V(660F3A,5F,_,x,x,_,_,_ ), 0 , 73 , 0 , 5782 , 289, 143), // #1026
- INST(Vfmsubaddps , Fma4_Lx , V(660F3A,5E,_,x,x,_,_,_ ), 0 , 73 , 0 , 5794 , 289, 143), // #1027
- INST(Vfmsubpd , Fma4_Lx , V(660F3A,6D,_,x,x,_,_,_ ), 0 , 73 , 0 , 5806 , 289, 143), // #1028
- INST(Vfmsubps , Fma4_Lx , V(660F3A,6C,_,x,x,_,_,_ ), 0 , 73 , 0 , 5815 , 289, 143), // #1029
- INST(Vfmsubsd , Fma4 , V(660F3A,6F,_,0,x,_,_,_ ), 0 , 73 , 0 , 5824 , 290, 143), // #1030
- INST(Vfmsubss , Fma4 , V(660F3A,6E,_,0,x,_,_,_ ), 0 , 73 , 0 , 5833 , 291, 143), // #1031
- INST(Vfmulcph , VexRvm_Lx , E(F3MAP6,D6,_,_,_,0,4,FV ), 0 , 186, 0 , 5842 , 284, 125), // #1032
- INST(Vfmulcsh , VexRvm , E(F3MAP6,D7,_,_,_,0,2,T1S), 0 , 187, 0 , 5851 , 259, 125), // #1033
- INST(Vfnmadd132pd , VexRvm_Lx , V(660F38,9C,_,x,1,1,4,FV ), 0 , 182, 0 , 5860 , 196, 141), // #1034
- INST(Vfnmadd132ph , VexRvm_Lx , E(66MAP6,9C,_,_,_,0,4,FV ), 0 , 183, 0 , 5873 , 197, 125), // #1035
- INST(Vfnmadd132ps , VexRvm_Lx , V(660F38,9C,_,x,0,0,4,FV ), 0 , 110, 0 , 5886 , 198, 141), // #1036
- INST(Vfnmadd132sd , VexRvm , V(660F38,9D,_,I,1,1,3,T1S), 0 , 184, 0 , 5899 , 199, 142), // #1037
- INST(Vfnmadd132sh , VexRvm , E(66MAP6,9D,_,_,_,0,1,T1S), 0 , 185, 0 , 5912 , 200, 127), // #1038
- INST(Vfnmadd132ss , VexRvm , V(660F38,9D,_,I,0,0,2,T1S), 0 , 122, 0 , 5925 , 201, 142), // #1039
- INST(Vfnmadd213pd , VexRvm_Lx , V(660F38,AC,_,x,1,1,4,FV ), 0 , 182, 0 , 5938 , 196, 141), // #1040
- INST(Vfnmadd213ph , VexRvm_Lx , E(66MAP6,AC,_,_,_,0,4,FV ), 0 , 183, 0 , 5951 , 197, 125), // #1041
- INST(Vfnmadd213ps , VexRvm_Lx , V(660F38,AC,_,x,0,0,4,FV ), 0 , 110, 0 , 5964 , 198, 141), // #1042
- INST(Vfnmadd213sd , VexRvm , V(660F38,AD,_,I,1,1,3,T1S), 0 , 184, 0 , 5977 , 199, 142), // #1043
- INST(Vfnmadd213sh , VexRvm , E(66MAP6,AD,_,_,_,0,1,T1S), 0 , 185, 0 , 5990 , 200, 127), // #1044
- INST(Vfnmadd213ss , VexRvm , V(660F38,AD,_,I,0,0,2,T1S), 0 , 122, 0 , 6003 , 201, 142), // #1045
- INST(Vfnmadd231pd , VexRvm_Lx , V(660F38,BC,_,x,1,1,4,FV ), 0 , 182, 0 , 6016 , 196, 141), // #1046
- INST(Vfnmadd231ph , VexRvm_Lx , E(66MAP6,BC,_,_,_,0,4,FV ), 0 , 183, 0 , 6029 , 197, 125), // #1047
- INST(Vfnmadd231ps , VexRvm_Lx , V(660F38,BC,_,x,0,0,4,FV ), 0 , 110, 0 , 6042 , 198, 141), // #1048
- INST(Vfnmadd231sd , VexRvm , V(660F38,BD,_,I,1,1,3,T1S), 0 , 184, 0 , 6055 , 199, 142), // #1049
- INST(Vfnmadd231sh , VexRvm , E(66MAP6,BD,_,_,_,0,1,T1S), 0 , 185, 0 , 6068 , 200, 127), // #1050
- INST(Vfnmadd231ss , VexRvm , V(660F38,BD,_,I,0,0,2,T1S), 0 , 122, 0 , 6081 , 201, 142), // #1051
- INST(Vfnmaddpd , Fma4_Lx , V(660F3A,79,_,x,x,_,_,_ ), 0 , 73 , 0 , 6094 , 289, 143), // #1052
- INST(Vfnmaddps , Fma4_Lx , V(660F3A,78,_,x,x,_,_,_ ), 0 , 73 , 0 , 6104 , 289, 143), // #1053
- INST(Vfnmaddsd , Fma4 , V(660F3A,7B,_,0,x,_,_,_ ), 0 , 73 , 0 , 6114 , 290, 143), // #1054
- INST(Vfnmaddss , Fma4 , V(660F3A,7A,_,0,x,_,_,_ ), 0 , 73 , 0 , 6124 , 291, 143), // #1055
- INST(Vfnmsub132pd , VexRvm_Lx , V(660F38,9E,_,x,1,1,4,FV ), 0 , 182, 0 , 6134 , 196, 141), // #1056
- INST(Vfnmsub132ph , VexRvm_Lx , E(66MAP6,9E,_,_,_,0,4,FV ), 0 , 183, 0 , 6147 , 197, 125), // #1057
- INST(Vfnmsub132ps , VexRvm_Lx , V(660F38,9E,_,x,0,0,4,FV ), 0 , 110, 0 , 6160 , 198, 141), // #1058
- INST(Vfnmsub132sd , VexRvm , V(660F38,9F,_,I,1,1,3,T1S), 0 , 184, 0 , 6173 , 199, 142), // #1059
- INST(Vfnmsub132sh , VexRvm , E(66MAP6,9F,_,_,_,0,1,T1S), 0 , 185, 0 , 6186 , 200, 127), // #1060
- INST(Vfnmsub132ss , VexRvm , V(660F38,9F,_,I,0,0,2,T1S), 0 , 122, 0 , 6199 , 201, 142), // #1061
- INST(Vfnmsub213pd , VexRvm_Lx , V(660F38,AE,_,x,1,1,4,FV ), 0 , 182, 0 , 6212 , 196, 141), // #1062
- INST(Vfnmsub213ph , VexRvm_Lx , E(66MAP6,AE,_,_,_,0,4,FV ), 0 , 183, 0 , 6225 , 197, 125), // #1063
- INST(Vfnmsub213ps , VexRvm_Lx , V(660F38,AE,_,x,0,0,4,FV ), 0 , 110, 0 , 6238 , 198, 141), // #1064
- INST(Vfnmsub213sd , VexRvm , V(660F38,AF,_,I,1,1,3,T1S), 0 , 184, 0 , 6251 , 199, 142), // #1065
- INST(Vfnmsub213sh , VexRvm , E(66MAP6,AF,_,_,_,0,1,T1S), 0 , 185, 0 , 6264 , 200, 127), // #1066
- INST(Vfnmsub213ss , VexRvm , V(660F38,AF,_,I,0,0,2,T1S), 0 , 122, 0 , 6277 , 201, 142), // #1067
- INST(Vfnmsub231pd , VexRvm_Lx , V(660F38,BE,_,x,1,1,4,FV ), 0 , 182, 0 , 6290 , 196, 141), // #1068
- INST(Vfnmsub231ph , VexRvm_Lx , E(66MAP6,BE,_,_,_,0,4,FV ), 0 , 183, 0 , 6303 , 197, 125), // #1069
- INST(Vfnmsub231ps , VexRvm_Lx , V(660F38,BE,_,x,0,0,4,FV ), 0 , 110, 0 , 6316 , 198, 141), // #1070
- INST(Vfnmsub231sd , VexRvm , V(660F38,BF,_,I,1,1,3,T1S), 0 , 184, 0 , 6329 , 199, 142), // #1071
- INST(Vfnmsub231sh , VexRvm , E(66MAP6,BF,_,_,_,0,1,T1S), 0 , 185, 0 , 6342 , 200, 127), // #1072
- INST(Vfnmsub231ss , VexRvm , V(660F38,BF,_,I,0,0,2,T1S), 0 , 122, 0 , 6355 , 201, 142), // #1073
- INST(Vfnmsubpd , Fma4_Lx , V(660F3A,7D,_,x,x,_,_,_ ), 0 , 73 , 0 , 6368 , 289, 143), // #1074
- INST(Vfnmsubps , Fma4_Lx , V(660F3A,7C,_,x,x,_,_,_ ), 0 , 73 , 0 , 6378 , 289, 143), // #1075
- INST(Vfnmsubsd , Fma4 , V(660F3A,7F,_,0,x,_,_,_ ), 0 , 73 , 0 , 6388 , 290, 143), // #1076
- INST(Vfnmsubss , Fma4 , V(660F3A,7E,_,0,x,_,_,_ ), 0 , 73 , 0 , 6398 , 291, 143), // #1077
- INST(Vfpclasspd , VexRmi_Lx , E(660F3A,66,_,x,_,1,4,FV ), 0 , 112, 0 , 6408 , 292, 133), // #1078
- INST(Vfpclassph , VexRmi_Lx , E(000F3A,66,_,_,_,0,4,FV ), 0 , 123, 0 , 6419 , 293, 125), // #1079
- INST(Vfpclassps , VexRmi_Lx , E(660F3A,66,_,x,_,0,4,FV ), 0 , 111, 0 , 6430 , 294, 133), // #1080
- INST(Vfpclasssd , VexRmi , E(660F3A,67,_,I,_,1,3,T1S), 0 , 180, 0 , 6441 , 295, 66 ), // #1081
- INST(Vfpclasssh , VexRmi , E(000F3A,67,_,_,_,0,1,T1S), 0 , 188, 0 , 6452 , 296, 127), // #1082
- INST(Vfpclassss , VexRmi , E(660F3A,67,_,I,_,0,2,T1S), 0 , 181, 0 , 6463 , 297, 66 ), // #1083
- INST(Vfrczpd , VexRm_Lx , V(XOP_M9,81,_,x,0,_,_,_ ), 0 , 79 , 0 , 6474 , 298, 144), // #1084
- INST(Vfrczps , VexRm_Lx , V(XOP_M9,80,_,x,0,_,_,_ ), 0 , 79 , 0 , 6482 , 298, 144), // #1085
- INST(Vfrczsd , VexRm , V(XOP_M9,83,_,0,0,_,_,_ ), 0 , 79 , 0 , 6490 , 299, 144), // #1086
- INST(Vfrczss , VexRm , V(XOP_M9,82,_,0,0,_,_,_ ), 0 , 79 , 0 , 6498 , 300, 144), // #1087
- INST(Vgatherdpd , VexRmvRm_VM , V(660F38,92,_,x,1,_,_,_ ), E(660F38,92,_,x,_,1,3,T1S), 189, 80 , 6506 , 301, 145), // #1088
- INST(Vgatherdps , VexRmvRm_VM , V(660F38,92,_,x,0,_,_,_ ), E(660F38,92,_,x,_,0,2,T1S), 96 , 81 , 6517 , 302, 145), // #1089
- INST(Vgatherpf0dpd , VexM_VM , E(660F38,C6,1,2,_,1,3,T1S), 0 , 190, 0 , 6528 , 303, 146), // #1090
- INST(Vgatherpf0dps , VexM_VM , E(660F38,C6,1,2,_,0,2,T1S), 0 , 191, 0 , 6542 , 304, 146), // #1091
- INST(Vgatherpf0qpd , VexM_VM , E(660F38,C7,1,2,_,1,3,T1S), 0 , 190, 0 , 6556 , 305, 146), // #1092
- INST(Vgatherpf0qps , VexM_VM , E(660F38,C7,1,2,_,0,2,T1S), 0 , 191, 0 , 6570 , 305, 146), // #1093
- INST(Vgatherpf1dpd , VexM_VM , E(660F38,C6,2,2,_,1,3,T1S), 0 , 192, 0 , 6584 , 303, 146), // #1094
- INST(Vgatherpf1dps , VexM_VM , E(660F38,C6,2,2,_,0,2,T1S), 0 , 193, 0 , 6598 , 304, 146), // #1095
- INST(Vgatherpf1qpd , VexM_VM , E(660F38,C7,2,2,_,1,3,T1S), 0 , 192, 0 , 6612 , 305, 146), // #1096
- INST(Vgatherpf1qps , VexM_VM , E(660F38,C7,2,2,_,0,2,T1S), 0 , 193, 0 , 6626 , 305, 146), // #1097
- INST(Vgatherqpd , VexRmvRm_VM , V(660F38,93,_,x,1,_,_,_ ), E(660F38,93,_,x,_,1,3,T1S), 189, 82 , 6640 , 306, 145), // #1098
- INST(Vgatherqps , VexRmvRm_VM , V(660F38,93,_,x,0,_,_,_ ), E(660F38,93,_,x,_,0,2,T1S), 96 , 83 , 6651 , 307, 145), // #1099
- INST(Vgetexppd , VexRm_Lx , E(660F38,42,_,x,_,1,4,FV ), 0 , 113, 0 , 6662 , 263, 131), // #1100
- INST(Vgetexpph , VexRm_Lx , E(66MAP6,42,_,_,_,0,4,FV ), 0 , 183, 0 , 6672 , 265, 125), // #1101
- INST(Vgetexpps , VexRm_Lx , E(660F38,42,_,x,_,0,4,FV ), 0 , 114, 0 , 6682 , 268, 131), // #1102
- INST(Vgetexpsd , VexRvm , E(660F38,43,_,I,_,1,3,T1S), 0 , 128, 0 , 6692 , 308, 68 ), // #1103
- INST(Vgetexpsh , VexRvm , E(66MAP6,43,_,_,_,0,1,T1S), 0 , 185, 0 , 6702 , 254, 127), // #1104
- INST(Vgetexpss , VexRvm , E(660F38,43,_,I,_,0,2,T1S), 0 , 129, 0 , 6712 , 309, 68 ), // #1105
- INST(Vgetmantpd , VexRmi_Lx , E(660F3A,26,_,x,_,1,4,FV ), 0 , 112, 0 , 6722 , 310, 131), // #1106
- INST(Vgetmantph , VexRmi_Lx , E(000F3A,26,_,_,_,0,4,FV ), 0 , 123, 0 , 6733 , 311, 125), // #1107
- INST(Vgetmantps , VexRmi_Lx , E(660F3A,26,_,x,_,0,4,FV ), 0 , 111, 0 , 6744 , 312, 131), // #1108
- INST(Vgetmantsd , VexRvmi , E(660F3A,27,_,I,_,1,3,T1S), 0 , 180, 0 , 6755 , 287, 68 ), // #1109
- INST(Vgetmantsh , VexRvmi , E(000F3A,27,_,_,_,0,1,T1S), 0 , 188, 0 , 6766 , 313, 127), // #1110
- INST(Vgetmantss , VexRvmi , E(660F3A,27,_,I,_,0,2,T1S), 0 , 181, 0 , 6777 , 288, 68 ), // #1111
- INST(Vgf2p8affineinvqb, VexRvmi_Lx , V(660F3A,CF,_,x,1,1,4,FV ), 0 , 194, 0 , 6788 , 314, 147), // #1112
- INST(Vgf2p8affineqb , VexRvmi_Lx , V(660F3A,CE,_,x,1,1,4,FV ), 0 , 194, 0 , 6806 , 314, 147), // #1113
- INST(Vgf2p8mulb , VexRvm_Lx , V(660F38,CF,_,x,0,0,4,FV ), 0 , 110, 0 , 6821 , 315, 147), // #1114
- INST(Vhaddpd , VexRvm_Lx , V(660F00,7C,_,x,I,_,_,_ ), 0 , 69 , 0 , 6832 , 202, 128), // #1115
- INST(Vhaddps , VexRvm_Lx , V(F20F00,7C,_,x,I,_,_,_ ), 0 , 109, 0 , 6840 , 202, 128), // #1116
- INST(Vhsubpd , VexRvm_Lx , V(660F00,7D,_,x,I,_,_,_ ), 0 , 69 , 0 , 6848 , 202, 128), // #1117
- INST(Vhsubps , VexRvm_Lx , V(F20F00,7D,_,x,I,_,_,_ ), 0 , 109, 0 , 6856 , 202, 128), // #1118
- INST(Vinsertf128 , VexRvmi , V(660F3A,18,_,1,0,_,_,_ ), 0 , 172, 0 , 6864 , 316, 128), // #1119
- INST(Vinsertf32x4 , VexRvmi_Lx , E(660F3A,18,_,x,_,0,4,T4 ), 0 , 173, 0 , 6876 , 317, 131), // #1120
- INST(Vinsertf32x8 , VexRvmi , E(660F3A,1A,_,2,_,0,5,T8 ), 0 , 174, 0 , 6889 , 318, 66 ), // #1121
- INST(Vinsertf64x2 , VexRvmi_Lx , E(660F3A,18,_,x,_,1,4,T2 ), 0 , 175, 0 , 6902 , 317, 133), // #1122
- INST(Vinsertf64x4 , VexRvmi , E(660F3A,1A,_,2,_,1,5,T4 ), 0 , 176, 0 , 6915 , 318, 68 ), // #1123
- INST(Vinserti128 , VexRvmi , V(660F3A,38,_,1,0,_,_,_ ), 0 , 172, 0 , 6928 , 316, 134), // #1124
- INST(Vinserti32x4 , VexRvmi_Lx , E(660F3A,38,_,x,_,0,4,T4 ), 0 , 173, 0 , 6940 , 317, 131), // #1125
- INST(Vinserti32x8 , VexRvmi , E(660F3A,3A,_,2,_,0,5,T8 ), 0 , 174, 0 , 6953 , 318, 66 ), // #1126
- INST(Vinserti64x2 , VexRvmi_Lx , E(660F3A,38,_,x,_,1,4,T2 ), 0 , 175, 0 , 6966 , 317, 133), // #1127
- INST(Vinserti64x4 , VexRvmi , E(660F3A,3A,_,2,_,1,5,T4 ), 0 , 176, 0 , 6979 , 318, 68 ), // #1128
- INST(Vinsertps , VexRvmi , V(660F3A,21,_,0,I,0,2,T1S), 0 , 177, 0 , 6992 , 319, 126), // #1129
- INST(Vlddqu , VexRm_Lx , V(F20F00,F0,_,x,I,_,_,_ ), 0 , 109, 0 , 7002 , 320, 128), // #1130
- INST(Vldmxcsr , VexM , V(000F00,AE,2,0,I,_,_,_ ), 0 , 195, 0 , 7009 , 321, 128), // #1131
- INST(Vmaskmovdqu , VexRm_ZDI , V(660F00,F7,_,0,I,_,_,_ ), 0 , 69 , 0 , 7018 , 322, 128), // #1132
- INST(Vmaskmovpd , VexRvmMvr_Lx , V(660F38,2D,_,x,0,_,_,_ ), V(660F38,2F,_,x,0,_,_,_ ), 96 , 84 , 7030 , 323, 128), // #1133
- INST(Vmaskmovps , VexRvmMvr_Lx , V(660F38,2C,_,x,0,_,_,_ ), V(660F38,2E,_,x,0,_,_,_ ), 96 , 85 , 7041 , 323, 128), // #1134
- INST(Vmaxpd , VexRvm_Lx , V(660F00,5F,_,x,I,1,4,FV ), 0 , 103, 0 , 7052 , 324, 124), // #1135
- INST(Vmaxph , VexRvm_Lx , E(00MAP5,5F,_,_,_,0,4,FV ), 0 , 104, 0 , 7059 , 325, 125), // #1136
- INST(Vmaxps , VexRvm_Lx , V(000F00,5F,_,x,I,0,4,FV ), 0 , 105, 0 , 7066 , 326, 124), // #1137
- INST(Vmaxsd , VexRvm , V(F20F00,5F,_,I,I,1,3,T1S), 0 , 106, 0 , 7073 , 327, 124), // #1138
- INST(Vmaxsh , VexRvm , E(F3MAP5,5F,_,_,_,0,1,T1S), 0 , 107, 0 , 7080 , 254, 127), // #1139
- INST(Vmaxss , VexRvm , V(F30F00,5F,_,I,I,0,2,T1S), 0 , 108, 0 , 7087 , 258, 124), // #1140
- INST(Vmcall , X86Op , O(000F01,C1,_,_,_,_,_,_ ), 0 , 21 , 0 , 7094 , 30 , 58 ), // #1141
- INST(Vmclear , X86M_Only , O(660F00,C7,6,_,_,_,_,_ ), 0 , 26 , 0 , 7101 , 32 , 58 ), // #1142
- INST(Vmfunc , X86Op , O(000F01,D4,_,_,_,_,_,_ ), 0 , 21 , 0 , 7109 , 30 , 58 ), // #1143
- INST(Vminpd , VexRvm_Lx , V(660F00,5D,_,x,I,1,4,FV ), 0 , 103, 0 , 7116 , 324, 124), // #1144
- INST(Vminph , VexRvm_Lx , E(00MAP5,5D,_,_,_,0,4,FV ), 0 , 104, 0 , 7123 , 325, 125), // #1145
- INST(Vminps , VexRvm_Lx , V(000F00,5D,_,x,I,0,4,FV ), 0 , 105, 0 , 7130 , 326, 124), // #1146
- INST(Vminsd , VexRvm , V(F20F00,5D,_,I,I,1,3,T1S), 0 , 106, 0 , 7137 , 327, 124), // #1147
- INST(Vminsh , VexRvm , E(F3MAP5,5D,_,_,_,0,1,T1S), 0 , 107, 0 , 7144 , 254, 127), // #1148
- INST(Vminss , VexRvm , V(F30F00,5D,_,I,I,0,2,T1S), 0 , 108, 0 , 7151 , 258, 124), // #1149
- INST(Vmlaunch , X86Op , O(000F01,C2,_,_,_,_,_,_ ), 0 , 21 , 0 , 7158 , 30 , 58 ), // #1150
- INST(Vmload , X86Op_xAX , O(000F01,DA,_,_,_,_,_,_ ), 0 , 21 , 0 , 7167 , 328, 22 ), // #1151
- INST(Vmmcall , X86Op , O(000F01,D9,_,_,_,_,_,_ ), 0 , 21 , 0 , 7174 , 30 , 22 ), // #1152
- INST(Vmovapd , VexRmMr_Lx , V(660F00,28,_,x,I,1,4,FVM), V(660F00,29,_,x,I,1,4,FVM), 103, 86 , 7182 , 329, 124), // #1153
- INST(Vmovaps , VexRmMr_Lx , V(000F00,28,_,x,I,0,4,FVM), V(000F00,29,_,x,I,0,4,FVM), 105, 87 , 7190 , 329, 124), // #1154
- INST(Vmovd , VexMovdMovq , V(660F00,6E,_,0,0,0,2,T1S), V(660F00,7E,_,0,0,0,2,T1S), 196, 88 , 7198 , 330, 126), // #1155
- INST(Vmovddup , VexRm_Lx , V(F20F00,12,_,x,I,1,3,DUP), 0 , 197, 0 , 7204 , 331, 124), // #1156
- INST(Vmovdqa , VexRmMr_Lx , V(660F00,6F,_,x,I,_,_,_ ), V(660F00,7F,_,x,I,_,_,_ ), 69 , 89 , 7213 , 332, 128), // #1157
- INST(Vmovdqa32 , VexRmMr_Lx , E(660F00,6F,_,x,_,0,4,FVM), E(660F00,7F,_,x,_,0,4,FVM), 198, 90 , 7221 , 333, 131), // #1158
- INST(Vmovdqa64 , VexRmMr_Lx , E(660F00,6F,_,x,_,1,4,FVM), E(660F00,7F,_,x,_,1,4,FVM), 135, 91 , 7231 , 333, 131), // #1159
- INST(Vmovdqu , VexRmMr_Lx , V(F30F00,6F,_,x,I,_,_,_ ), V(F30F00,7F,_,x,I,_,_,_ ), 199, 92 , 7241 , 332, 128), // #1160
- INST(Vmovdqu16 , VexRmMr_Lx , E(F20F00,6F,_,x,_,1,4,FVM), E(F20F00,7F,_,x,_,1,4,FVM), 166, 93 , 7249 , 333, 139), // #1161
- INST(Vmovdqu32 , VexRmMr_Lx , E(F30F00,6F,_,x,_,0,4,FVM), E(F30F00,7F,_,x,_,0,4,FVM), 200, 94 , 7259 , 333, 131), // #1162
- INST(Vmovdqu64 , VexRmMr_Lx , E(F30F00,6F,_,x,_,1,4,FVM), E(F30F00,7F,_,x,_,1,4,FVM), 149, 95 , 7269 , 333, 131), // #1163
- INST(Vmovdqu8 , VexRmMr_Lx , E(F20F00,6F,_,x,_,0,4,FVM), E(F20F00,7F,_,x,_,0,4,FVM), 164, 96 , 7279 , 333, 139), // #1164
- INST(Vmovhlps , VexRvm , V(000F00,12,_,0,I,0,_,_ ), 0 , 72 , 0 , 7288 , 334, 126), // #1165
- INST(Vmovhpd , VexRvmMr , V(660F00,16,_,0,I,1,3,T1S), V(660F00,17,_,0,I,1,3,T1S), 125, 97 , 7297 , 335, 126), // #1166
- INST(Vmovhps , VexRvmMr , V(000F00,16,_,0,I,0,3,T2 ), V(000F00,17,_,0,I,0,3,T2 ), 201, 98 , 7305 , 335, 126), // #1167
- INST(Vmovlhps , VexRvm , V(000F00,16,_,0,I,0,_,_ ), 0 , 72 , 0 , 7313 , 334, 126), // #1168
- INST(Vmovlpd , VexRvmMr , V(660F00,12,_,0,I,1,3,T1S), V(660F00,13,_,0,I,1,3,T1S), 125, 99 , 7322 , 335, 126), // #1169
- INST(Vmovlps , VexRvmMr , V(000F00,12,_,0,I,0,3,T2 ), V(000F00,13,_,0,I,0,3,T2 ), 201, 100, 7330 , 335, 126), // #1170
- INST(Vmovmskpd , VexRm_Lx , V(660F00,50,_,x,I,_,_,_ ), 0 , 69 , 0 , 7338 , 336, 128), // #1171
- INST(Vmovmskps , VexRm_Lx , V(000F00,50,_,x,I,_,_,_ ), 0 , 72 , 0 , 7348 , 336, 128), // #1172
- INST(Vmovntdq , VexMr_Lx , V(660F00,E7,_,x,I,0,4,FVM), 0 , 144, 0 , 7358 , 337, 124), // #1173
- INST(Vmovntdqa , VexRm_Lx , V(660F38,2A,_,x,I,0,4,FVM), 0 , 110, 0 , 7367 , 338, 135), // #1174
- INST(Vmovntpd , VexMr_Lx , V(660F00,2B,_,x,I,1,4,FVM), 0 , 103, 0 , 7377 , 337, 124), // #1175
- INST(Vmovntps , VexMr_Lx , V(000F00,2B,_,x,I,0,4,FVM), 0 , 105, 0 , 7386 , 337, 124), // #1176
- INST(Vmovq , VexMovdMovq , V(660F00,6E,_,0,I,1,3,T1S), V(660F00,7E,_,0,I,1,3,T1S), 125, 101, 7395 , 339, 126), // #1177
- INST(Vmovsd , VexMovssMovsd , V(F20F00,10,_,I,I,1,3,T1S), V(F20F00,11,_,I,I,1,3,T1S), 106, 102, 7401 , 340, 126), // #1178
- INST(Vmovsh , VexMovssMovsd , E(F3MAP5,10,_,I,_,0,1,T1S), E(F3MAP5,11,_,I,_,0,1,T1S), 107, 103, 7408 , 341, 127), // #1179
- INST(Vmovshdup , VexRm_Lx , V(F30F00,16,_,x,I,0,4,FVM), 0 , 161, 0 , 7415 , 342, 124), // #1180
- INST(Vmovsldup , VexRm_Lx , V(F30F00,12,_,x,I,0,4,FVM), 0 , 161, 0 , 7425 , 342, 124), // #1181
- INST(Vmovss , VexMovssMovsd , V(F30F00,10,_,I,I,0,2,T1S), V(F30F00,11,_,I,I,0,2,T1S), 108, 104, 7435 , 343, 126), // #1182
- INST(Vmovupd , VexRmMr_Lx , V(660F00,10,_,x,I,1,4,FVM), V(660F00,11,_,x,I,1,4,FVM), 103, 105, 7442 , 329, 124), // #1183
- INST(Vmovups , VexRmMr_Lx , V(000F00,10,_,x,I,0,4,FVM), V(000F00,11,_,x,I,0,4,FVM), 105, 106, 7450 , 329, 124), // #1184
- INST(Vmovw , VexMovdMovq , E(66MAP5,6E,_,0,_,I,1,T1S), E(66MAP5,7E,_,0,_,I,1,T1S), 202, 107, 7458 , 344, 127), // #1185
- INST(Vmpsadbw , VexRvmi_Lx , V(660F3A,42,_,x,I,_,_,_ ), 0 , 73 , 0 , 7464 , 214, 148), // #1186
- INST(Vmptrld , X86M_Only , O(000F00,C7,6,_,_,_,_,_ ), 0 , 80 , 0 , 7473 , 32 , 58 ), // #1187
- INST(Vmptrst , X86M_Only , O(000F00,C7,7,_,_,_,_,_ ), 0 , 22 , 0 , 7481 , 32 , 58 ), // #1188
- INST(Vmread , X86Mr_NoSize , O(000F00,78,_,_,_,_,_,_ ), 0 , 4 , 0 , 7489 , 345, 58 ), // #1189
- INST(Vmresume , X86Op , O(000F01,C3,_,_,_,_,_,_ ), 0 , 21 , 0 , 7496 , 30 , 58 ), // #1190
- INST(Vmrun , X86Op_xAX , O(000F01,D8,_,_,_,_,_,_ ), 0 , 21 , 0 , 7505 , 328, 22 ), // #1191
- INST(Vmsave , X86Op_xAX , O(000F01,DB,_,_,_,_,_,_ ), 0 , 21 , 0 , 7511 , 328, 22 ), // #1192
- INST(Vmulpd , VexRvm_Lx , V(660F00,59,_,x,I,1,4,FV ), 0 , 103, 0 , 7518 , 196, 124), // #1193
- INST(Vmulph , VexRvm_Lx , E(00MAP5,59,_,_,_,0,4,FV ), 0 , 104, 0 , 7525 , 197, 125), // #1194
- INST(Vmulps , VexRvm_Lx , V(000F00,59,_,x,I,0,4,FV ), 0 , 105, 0 , 7532 , 198, 124), // #1195
- INST(Vmulsd , VexRvm , V(F20F00,59,_,I,I,1,3,T1S), 0 , 106, 0 , 7539 , 199, 126), // #1196
- INST(Vmulsh , VexRvm , E(F3MAP5,59,_,_,_,0,1,T1S), 0 , 107, 0 , 7546 , 200, 127), // #1197
- INST(Vmulss , VexRvm , V(F30F00,59,_,I,I,0,2,T1S), 0 , 108, 0 , 7553 , 201, 126), // #1198
- INST(Vmwrite , X86Rm_NoSize , O(000F00,79,_,_,_,_,_,_ ), 0 , 4 , 0 , 7560 , 346, 58 ), // #1199
- INST(Vmxon , X86M_Only , O(F30F00,C7,6,_,_,_,_,_ ), 0 , 24 , 0 , 7568 , 32 , 58 ), // #1200
- INST(Vorpd , VexRvm_Lx , V(660F00,56,_,x,I,1,4,FV ), 0 , 103, 0 , 7574 , 210, 132), // #1201
- INST(Vorps , VexRvm_Lx , V(000F00,56,_,x,I,0,4,FV ), 0 , 105, 0 , 7580 , 211, 132), // #1202
- INST(Vp2intersectd , VexRvm_Lx_2xK , E(F20F38,68,_,_,_,0,4,FV ), 0 , 131, 0 , 7586 , 347, 149), // #1203
- INST(Vp2intersectq , VexRvm_Lx_2xK , E(F20F38,68,_,_,_,1,4,FV ), 0 , 203, 0 , 7600 , 348, 149), // #1204
- INST(Vp4dpwssd , VexRm_T1_4X , E(F20F38,52,_,2,_,0,4,T4X), 0 , 101, 0 , 7614 , 194, 150), // #1205
- INST(Vp4dpwssds , VexRm_T1_4X , E(F20F38,53,_,2,_,0,4,T4X), 0 , 101, 0 , 7624 , 194, 150), // #1206
- INST(Vpabsb , VexRm_Lx , V(660F38,1C,_,x,I,_,4,FVM), 0 , 110, 0 , 7635 , 342, 151), // #1207
- INST(Vpabsd , VexRm_Lx , V(660F38,1E,_,x,I,0,4,FV ), 0 , 110, 0 , 7642 , 349, 135), // #1208
- INST(Vpabsq , VexRm_Lx , E(660F38,1F,_,x,_,1,4,FV ), 0 , 113, 0 , 7649 , 350, 131), // #1209
- INST(Vpabsw , VexRm_Lx , V(660F38,1D,_,x,I,_,4,FVM), 0 , 110, 0 , 7656 , 342, 151), // #1210
- INST(Vpackssdw , VexRvm_Lx , V(660F00,6B,_,x,I,0,4,FV ), 0 , 144, 0 , 7663 , 209, 151), // #1211
- INST(Vpacksswb , VexRvm_Lx , V(660F00,63,_,x,I,I,4,FVM), 0 , 144, 0 , 7673 , 315, 151), // #1212
- INST(Vpackusdw , VexRvm_Lx , V(660F38,2B,_,x,I,0,4,FV ), 0 , 110, 0 , 7683 , 209, 151), // #1213
- INST(Vpackuswb , VexRvm_Lx , V(660F00,67,_,x,I,I,4,FVM), 0 , 144, 0 , 7693 , 315, 151), // #1214
- INST(Vpaddb , VexRvm_Lx , V(660F00,FC,_,x,I,I,4,FVM), 0 , 144, 0 , 7703 , 315, 151), // #1215
- INST(Vpaddd , VexRvm_Lx , V(660F00,FE,_,x,I,0,4,FV ), 0 , 144, 0 , 7710 , 209, 135), // #1216
- INST(Vpaddq , VexRvm_Lx , V(660F00,D4,_,x,I,1,4,FV ), 0 , 103, 0 , 7717 , 208, 135), // #1217
- INST(Vpaddsb , VexRvm_Lx , V(660F00,EC,_,x,I,I,4,FVM), 0 , 144, 0 , 7724 , 315, 151), // #1218
- INST(Vpaddsw , VexRvm_Lx , V(660F00,ED,_,x,I,I,4,FVM), 0 , 144, 0 , 7732 , 315, 151), // #1219
- INST(Vpaddusb , VexRvm_Lx , V(660F00,DC,_,x,I,I,4,FVM), 0 , 144, 0 , 7740 , 315, 151), // #1220
- INST(Vpaddusw , VexRvm_Lx , V(660F00,DD,_,x,I,I,4,FVM), 0 , 144, 0 , 7749 , 315, 151), // #1221
- INST(Vpaddw , VexRvm_Lx , V(660F00,FD,_,x,I,I,4,FVM), 0 , 144, 0 , 7758 , 315, 151), // #1222
- INST(Vpalignr , VexRvmi_Lx , V(660F3A,0F,_,x,I,I,4,FVM), 0 , 204, 0 , 7765 , 314, 151), // #1223
- INST(Vpand , VexRvm_Lx , V(660F00,DB,_,x,I,_,_,_ ), 0 , 69 , 0 , 7774 , 351, 148), // #1224
- INST(Vpandd , VexRvm_Lx , E(660F00,DB,_,x,_,0,4,FV ), 0 , 198, 0 , 7780 , 352, 131), // #1225
- INST(Vpandn , VexRvm_Lx , V(660F00,DF,_,x,I,_,_,_ ), 0 , 69 , 0 , 7787 , 353, 148), // #1226
- INST(Vpandnd , VexRvm_Lx , E(660F00,DF,_,x,_,0,4,FV ), 0 , 198, 0 , 7794 , 354, 131), // #1227
- INST(Vpandnq , VexRvm_Lx , E(660F00,DF,_,x,_,1,4,FV ), 0 , 135, 0 , 7802 , 355, 131), // #1228
- INST(Vpandq , VexRvm_Lx , E(660F00,DB,_,x,_,1,4,FV ), 0 , 135, 0 , 7810 , 356, 131), // #1229
- INST(Vpavgb , VexRvm_Lx , V(660F00,E0,_,x,I,I,4,FVM), 0 , 144, 0 , 7817 , 315, 151), // #1230
- INST(Vpavgw , VexRvm_Lx , V(660F00,E3,_,x,I,I,4,FVM), 0 , 144, 0 , 7824 , 315, 151), // #1231
- INST(Vpblendd , VexRvmi_Lx , V(660F3A,02,_,x,0,_,_,_ ), 0 , 73 , 0 , 7831 , 214, 134), // #1232
- INST(Vpblendmb , VexRvm_Lx , E(660F38,66,_,x,_,0,4,FVM), 0 , 114, 0 , 7840 , 357, 139), // #1233
- INST(Vpblendmd , VexRvm_Lx , E(660F38,64,_,x,_,0,4,FV ), 0 , 114, 0 , 7850 , 213, 131), // #1234
- INST(Vpblendmq , VexRvm_Lx , E(660F38,64,_,x,_,1,4,FV ), 0 , 113, 0 , 7860 , 212, 131), // #1235
- INST(Vpblendmw , VexRvm_Lx , E(660F38,66,_,x,_,1,4,FVM), 0 , 113, 0 , 7870 , 357, 139), // #1236
- INST(Vpblendvb , VexRvmr_Lx , V(660F3A,4C,_,x,0,_,_,_ ), 0 , 73 , 0 , 7880 , 215, 148), // #1237
- INST(Vpblendw , VexRvmi_Lx , V(660F3A,0E,_,x,I,_,_,_ ), 0 , 73 , 0 , 7890 , 214, 148), // #1238
- INST(Vpbroadcastb , VexRm_Lx_Bcst , V(660F38,78,_,x,0,0,0,T1S), E(660F38,7A,_,x,0,0,0,T1S), 96 , 108, 7899 , 358, 152), // #1239
- INST(Vpbroadcastd , VexRm_Lx_Bcst , V(660F38,58,_,x,0,0,2,T1S), E(660F38,7C,_,x,0,0,0,T1S), 122, 109, 7912 , 359, 145), // #1240
- INST(Vpbroadcastmb2q , VexRm_Lx , E(F30F38,2A,_,x,_,1,_,_ ), 0 , 205, 0 , 7925 , 360, 153), // #1241
- INST(Vpbroadcastmw2d , VexRm_Lx , E(F30F38,3A,_,x,_,0,_,_ ), 0 , 206, 0 , 7941 , 360, 153), // #1242
- INST(Vpbroadcastq , VexRm_Lx_Bcst , V(660F38,59,_,x,0,1,3,T1S), E(660F38,7C,_,x,0,1,0,T1S), 121, 110, 7957 , 361, 145), // #1243
- INST(Vpbroadcastw , VexRm_Lx_Bcst , V(660F38,79,_,x,0,0,1,T1S), E(660F38,7B,_,x,0,0,0,T1S), 207, 111, 7970 , 362, 152), // #1244
- INST(Vpclmulqdq , VexRvmi_Lx , V(660F3A,44,_,x,I,_,4,FVM), 0 , 204, 0 , 7983 , 363, 154), // #1245
- INST(Vpcmov , VexRvrmRvmr_Lx , V(XOP_M8,A2,_,x,x,_,_,_ ), 0 , 208, 0 , 7994 , 289, 144), // #1246
- INST(Vpcmpb , VexRvmi_Lx , E(660F3A,3F,_,x,_,0,4,FVM), 0 , 111, 0 , 8001 , 364, 139), // #1247
- INST(Vpcmpd , VexRvmi_Lx , E(660F3A,1F,_,x,_,0,4,FV ), 0 , 111, 0 , 8008 , 365, 131), // #1248
- INST(Vpcmpeqb , VexRvm_Lx_KEvex , V(660F00,74,_,x,I,I,4,FV ), 0 , 144, 0 , 8015 , 366, 151), // #1249
- INST(Vpcmpeqd , VexRvm_Lx_KEvex , V(660F00,76,_,x,I,0,4,FVM), 0 , 144, 0 , 8024 , 367, 135), // #1250
- INST(Vpcmpeqq , VexRvm_Lx_KEvex , V(660F38,29,_,x,I,1,4,FVM), 0 , 209, 0 , 8033 , 368, 135), // #1251
- INST(Vpcmpeqw , VexRvm_Lx_KEvex , V(660F00,75,_,x,I,I,4,FV ), 0 , 144, 0 , 8042 , 366, 151), // #1252
- INST(Vpcmpestri , VexRmi , V(660F3A,61,_,0,I,_,_,_ ), 0 , 73 , 0 , 8051 , 369, 155), // #1253
- INST(Vpcmpestrm , VexRmi , V(660F3A,60,_,0,I,_,_,_ ), 0 , 73 , 0 , 8062 , 370, 155), // #1254
- INST(Vpcmpgtb , VexRvm_Lx_KEvex , V(660F00,64,_,x,I,I,4,FV ), 0 , 144, 0 , 8073 , 366, 151), // #1255
- INST(Vpcmpgtd , VexRvm_Lx_KEvex , V(660F00,66,_,x,I,0,4,FVM), 0 , 144, 0 , 8082 , 367, 135), // #1256
- INST(Vpcmpgtq , VexRvm_Lx_KEvex , V(660F38,37,_,x,I,1,4,FVM), 0 , 209, 0 , 8091 , 368, 135), // #1257
- INST(Vpcmpgtw , VexRvm_Lx_KEvex , V(660F00,65,_,x,I,I,4,FV ), 0 , 144, 0 , 8100 , 366, 151), // #1258
- INST(Vpcmpistri , VexRmi , V(660F3A,63,_,0,I,_,_,_ ), 0 , 73 , 0 , 8109 , 371, 155), // #1259
- INST(Vpcmpistrm , VexRmi , V(660F3A,62,_,0,I,_,_,_ ), 0 , 73 , 0 , 8120 , 372, 155), // #1260
- INST(Vpcmpq , VexRvmi_Lx , E(660F3A,1F,_,x,_,1,4,FV ), 0 , 112, 0 , 8131 , 373, 131), // #1261
- INST(Vpcmpub , VexRvmi_Lx , E(660F3A,3E,_,x,_,0,4,FVM), 0 , 111, 0 , 8138 , 364, 139), // #1262
- INST(Vpcmpud , VexRvmi_Lx , E(660F3A,1E,_,x,_,0,4,FV ), 0 , 111, 0 , 8146 , 365, 131), // #1263
- INST(Vpcmpuq , VexRvmi_Lx , E(660F3A,1E,_,x,_,1,4,FV ), 0 , 112, 0 , 8154 , 373, 131), // #1264
- INST(Vpcmpuw , VexRvmi_Lx , E(660F3A,3E,_,x,_,1,4,FVM), 0 , 112, 0 , 8162 , 373, 139), // #1265
- INST(Vpcmpw , VexRvmi_Lx , E(660F3A,3F,_,x,_,1,4,FVM), 0 , 112, 0 , 8170 , 373, 139), // #1266
- INST(Vpcomb , VexRvmi , V(XOP_M8,CC,_,0,0,_,_,_ ), 0 , 208, 0 , 8177 , 276, 144), // #1267
- INST(Vpcomd , VexRvmi , V(XOP_M8,CE,_,0,0,_,_,_ ), 0 , 208, 0 , 8184 , 276, 144), // #1268
- INST(Vpcompressb , VexMr_Lx , E(660F38,63,_,x,_,0,0,T1S), 0 , 210, 0 , 8191 , 232, 156), // #1269
- INST(Vpcompressd , VexMr_Lx , E(660F38,8B,_,x,_,0,2,T1S), 0 , 129, 0 , 8203 , 232, 131), // #1270
- INST(Vpcompressq , VexMr_Lx , E(660F38,8B,_,x,_,1,3,T1S), 0 , 128, 0 , 8215 , 232, 131), // #1271
- INST(Vpcompressw , VexMr_Lx , E(660F38,63,_,x,_,1,1,T1S), 0 , 211, 0 , 8227 , 232, 156), // #1272
- INST(Vpcomq , VexRvmi , V(XOP_M8,CF,_,0,0,_,_,_ ), 0 , 208, 0 , 8239 , 276, 144), // #1273
- INST(Vpcomub , VexRvmi , V(XOP_M8,EC,_,0,0,_,_,_ ), 0 , 208, 0 , 8246 , 276, 144), // #1274
- INST(Vpcomud , VexRvmi , V(XOP_M8,EE,_,0,0,_,_,_ ), 0 , 208, 0 , 8254 , 276, 144), // #1275
- INST(Vpcomuq , VexRvmi , V(XOP_M8,EF,_,0,0,_,_,_ ), 0 , 208, 0 , 8262 , 276, 144), // #1276
- INST(Vpcomuw , VexRvmi , V(XOP_M8,ED,_,0,0,_,_,_ ), 0 , 208, 0 , 8270 , 276, 144), // #1277
- INST(Vpcomw , VexRvmi , V(XOP_M8,CD,_,0,0,_,_,_ ), 0 , 208, 0 , 8278 , 276, 144), // #1278
- INST(Vpconflictd , VexRm_Lx , E(660F38,C4,_,x,_,0,4,FV ), 0 , 114, 0 , 8285 , 374, 153), // #1279
- INST(Vpconflictq , VexRm_Lx , E(660F38,C4,_,x,_,1,4,FV ), 0 , 113, 0 , 8297 , 374, 153), // #1280
- INST(Vpdpbusd , VexRvm_Lx , V(660F38,50,_,x,_,0,4,FV ), 0 , 110, 0 , 8309 , 375, 157), // #1281
- INST(Vpdpbusds , VexRvm_Lx , V(660F38,51,_,x,_,0,4,FV ), 0 , 110, 0 , 8318 , 375, 157), // #1282
- INST(Vpdpwssd , VexRvm_Lx , V(660F38,52,_,x,_,0,4,FV ), 0 , 110, 0 , 8328 , 375, 157), // #1283
- INST(Vpdpwssds , VexRvm_Lx , V(660F38,53,_,x,_,0,4,FV ), 0 , 110, 0 , 8337 , 375, 157), // #1284
- INST(Vperm2f128 , VexRvmi , V(660F3A,06,_,1,0,_,_,_ ), 0 , 172, 0 , 8347 , 376, 128), // #1285
- INST(Vperm2i128 , VexRvmi , V(660F3A,46,_,1,0,_,_,_ ), 0 , 172, 0 , 8358 , 376, 134), // #1286
- INST(Vpermb , VexRvm_Lx , E(660F38,8D,_,x,_,0,4,FVM), 0 , 114, 0 , 8369 , 357, 158), // #1287
- INST(Vpermd , VexRvm_Lx , V(660F38,36,_,x,0,0,4,FV ), 0 , 110, 0 , 8376 , 377, 145), // #1288
- INST(Vpermi2b , VexRvm_Lx , E(660F38,75,_,x,_,0,4,FVM), 0 , 114, 0 , 8383 , 357, 158), // #1289
- INST(Vpermi2d , VexRvm_Lx , E(660F38,76,_,x,_,0,4,FV ), 0 , 114, 0 , 8392 , 213, 131), // #1290
- INST(Vpermi2pd , VexRvm_Lx , E(660F38,77,_,x,_,1,4,FV ), 0 , 113, 0 , 8401 , 212, 131), // #1291
- INST(Vpermi2ps , VexRvm_Lx , E(660F38,77,_,x,_,0,4,FV ), 0 , 114, 0 , 8411 , 213, 131), // #1292
- INST(Vpermi2q , VexRvm_Lx , E(660F38,76,_,x,_,1,4,FV ), 0 , 113, 0 , 8421 , 212, 131), // #1293
- INST(Vpermi2w , VexRvm_Lx , E(660F38,75,_,x,_,1,4,FVM), 0 , 113, 0 , 8430 , 357, 139), // #1294
- INST(Vpermil2pd , VexRvrmiRvmri_Lx , V(660F3A,49,_,x,x,_,_,_ ), 0 , 73 , 0 , 8439 , 378, 144), // #1295
- INST(Vpermil2ps , VexRvrmiRvmri_Lx , V(660F3A,48,_,x,x,_,_,_ ), 0 , 73 , 0 , 8450 , 378, 144), // #1296
- INST(Vpermilpd , VexRvmRmi_Lx , V(660F38,0D,_,x,0,1,4,FV ), V(660F3A,05,_,x,0,1,4,FV ), 209, 112, 8461 , 379, 124), // #1297
- INST(Vpermilps , VexRvmRmi_Lx , V(660F38,0C,_,x,0,0,4,FV ), V(660F3A,04,_,x,0,0,4,FV ), 110, 113, 8471 , 380, 124), // #1298
- INST(Vpermpd , VexRvmRmi_Lx , E(660F38,16,_,x,1,1,4,FV ), V(660F3A,01,_,x,1,1,4,FV ), 212, 114, 8481 , 381, 145), // #1299
- INST(Vpermps , VexRvm_Lx , V(660F38,16,_,x,0,0,4,FV ), 0 , 110, 0 , 8489 , 377, 145), // #1300
- INST(Vpermq , VexRvmRmi_Lx , E(660F38,36,_,x,_,1,4,FV ), V(660F3A,00,_,x,1,1,4,FV ), 113, 115, 8497 , 381, 145), // #1301
- INST(Vpermt2b , VexRvm_Lx , E(660F38,7D,_,x,_,0,4,FVM), 0 , 114, 0 , 8504 , 357, 158), // #1302
- INST(Vpermt2d , VexRvm_Lx , E(660F38,7E,_,x,_,0,4,FV ), 0 , 114, 0 , 8513 , 213, 131), // #1303
- INST(Vpermt2pd , VexRvm_Lx , E(660F38,7F,_,x,_,1,4,FV ), 0 , 113, 0 , 8522 , 212, 131), // #1304
- INST(Vpermt2ps , VexRvm_Lx , E(660F38,7F,_,x,_,0,4,FV ), 0 , 114, 0 , 8532 , 213, 131), // #1305
- INST(Vpermt2q , VexRvm_Lx , E(660F38,7E,_,x,_,1,4,FV ), 0 , 113, 0 , 8542 , 212, 131), // #1306
- INST(Vpermt2w , VexRvm_Lx , E(660F38,7D,_,x,_,1,4,FVM), 0 , 113, 0 , 8551 , 357, 139), // #1307
- INST(Vpermw , VexRvm_Lx , E(660F38,8D,_,x,_,1,4,FVM), 0 , 113, 0 , 8560 , 357, 139), // #1308
- INST(Vpexpandb , VexRm_Lx , E(660F38,62,_,x,_,0,0,T1S), 0 , 210, 0 , 8567 , 279, 156), // #1309
- INST(Vpexpandd , VexRm_Lx , E(660F38,89,_,x,_,0,2,T1S), 0 , 129, 0 , 8577 , 279, 131), // #1310
- INST(Vpexpandq , VexRm_Lx , E(660F38,89,_,x,_,1,3,T1S), 0 , 128, 0 , 8587 , 279, 131), // #1311
- INST(Vpexpandw , VexRm_Lx , E(660F38,62,_,x,_,1,1,T1S), 0 , 211, 0 , 8597 , 279, 156), // #1312
- INST(Vpextrb , VexMri , V(660F3A,14,_,0,0,I,0,T1S), 0 , 73 , 0 , 8607 , 382, 159), // #1313
- INST(Vpextrd , VexMri , V(660F3A,16,_,0,0,0,2,T1S), 0 , 177, 0 , 8615 , 283, 160), // #1314
- INST(Vpextrq , VexMri , V(660F3A,16,_,0,1,1,3,T1S), 0 , 213, 0 , 8623 , 383, 160), // #1315
- INST(Vpextrw , VexMri_Vpextrw , V(660F3A,15,_,0,0,I,1,T1S), 0 , 214, 0 , 8631 , 384, 159), // #1316
- INST(Vpgatherdd , VexRmvRm_VM , V(660F38,90,_,x,0,_,_,_ ), E(660F38,90,_,x,_,0,2,T1S), 96 , 116, 8639 , 302, 145), // #1317
- INST(Vpgatherdq , VexRmvRm_VM , V(660F38,90,_,x,1,_,_,_ ), E(660F38,90,_,x,_,1,3,T1S), 189, 117, 8650 , 301, 145), // #1318
- INST(Vpgatherqd , VexRmvRm_VM , V(660F38,91,_,x,0,_,_,_ ), E(660F38,91,_,x,_,0,2,T1S), 96 , 118, 8661 , 307, 145), // #1319
- INST(Vpgatherqq , VexRmvRm_VM , V(660F38,91,_,x,1,_,_,_ ), E(660F38,91,_,x,_,1,3,T1S), 189, 119, 8672 , 306, 145), // #1320
- INST(Vphaddbd , VexRm , V(XOP_M9,C2,_,0,0,_,_,_ ), 0 , 79 , 0 , 8683 , 204, 144), // #1321
- INST(Vphaddbq , VexRm , V(XOP_M9,C3,_,0,0,_,_,_ ), 0 , 79 , 0 , 8692 , 204, 144), // #1322
- INST(Vphaddbw , VexRm , V(XOP_M9,C1,_,0,0,_,_,_ ), 0 , 79 , 0 , 8701 , 204, 144), // #1323
- INST(Vphaddd , VexRvm_Lx , V(660F38,02,_,x,I,_,_,_ ), 0 , 96 , 0 , 8710 , 202, 148), // #1324
- INST(Vphadddq , VexRm , V(XOP_M9,CB,_,0,0,_,_,_ ), 0 , 79 , 0 , 8718 , 204, 144), // #1325
- INST(Vphaddsw , VexRvm_Lx , V(660F38,03,_,x,I,_,_,_ ), 0 , 96 , 0 , 8727 , 202, 148), // #1326
- INST(Vphaddubd , VexRm , V(XOP_M9,D2,_,0,0,_,_,_ ), 0 , 79 , 0 , 8736 , 204, 144), // #1327
- INST(Vphaddubq , VexRm , V(XOP_M9,D3,_,0,0,_,_,_ ), 0 , 79 , 0 , 8746 , 204, 144), // #1328
- INST(Vphaddubw , VexRm , V(XOP_M9,D1,_,0,0,_,_,_ ), 0 , 79 , 0 , 8756 , 204, 144), // #1329
- INST(Vphaddudq , VexRm , V(XOP_M9,DB,_,0,0,_,_,_ ), 0 , 79 , 0 , 8766 , 204, 144), // #1330
- INST(Vphadduwd , VexRm , V(XOP_M9,D6,_,0,0,_,_,_ ), 0 , 79 , 0 , 8776 , 204, 144), // #1331
- INST(Vphadduwq , VexRm , V(XOP_M9,D7,_,0,0,_,_,_ ), 0 , 79 , 0 , 8786 , 204, 144), // #1332
- INST(Vphaddw , VexRvm_Lx , V(660F38,01,_,x,I,_,_,_ ), 0 , 96 , 0 , 8796 , 202, 148), // #1333
- INST(Vphaddwd , VexRm , V(XOP_M9,C6,_,0,0,_,_,_ ), 0 , 79 , 0 , 8804 , 204, 144), // #1334
- INST(Vphaddwq , VexRm , V(XOP_M9,C7,_,0,0,_,_,_ ), 0 , 79 , 0 , 8813 , 204, 144), // #1335
- INST(Vphminposuw , VexRm , V(660F38,41,_,0,I,_,_,_ ), 0 , 96 , 0 , 8822 , 204, 128), // #1336
- INST(Vphsubbw , VexRm , V(XOP_M9,E1,_,0,0,_,_,_ ), 0 , 79 , 0 , 8834 , 204, 144), // #1337
- INST(Vphsubd , VexRvm_Lx , V(660F38,06,_,x,I,_,_,_ ), 0 , 96 , 0 , 8843 , 202, 148), // #1338
- INST(Vphsubdq , VexRm , V(XOP_M9,E3,_,0,0,_,_,_ ), 0 , 79 , 0 , 8851 , 204, 144), // #1339
- INST(Vphsubsw , VexRvm_Lx , V(660F38,07,_,x,I,_,_,_ ), 0 , 96 , 0 , 8860 , 202, 148), // #1340
- INST(Vphsubw , VexRvm_Lx , V(660F38,05,_,x,I,_,_,_ ), 0 , 96 , 0 , 8869 , 202, 148), // #1341
- INST(Vphsubwd , VexRm , V(XOP_M9,E2,_,0,0,_,_,_ ), 0 , 79 , 0 , 8877 , 204, 144), // #1342
- INST(Vpinsrb , VexRvmi , V(660F3A,20,_,0,0,I,0,T1S), 0 , 73 , 0 , 8886 , 385, 159), // #1343
- INST(Vpinsrd , VexRvmi , V(660F3A,22,_,0,0,0,2,T1S), 0 , 177, 0 , 8894 , 386, 160), // #1344
- INST(Vpinsrq , VexRvmi , V(660F3A,22,_,0,1,1,3,T1S), 0 , 213, 0 , 8902 , 387, 160), // #1345
- INST(Vpinsrw , VexRvmi , V(660F00,C4,_,0,0,I,1,T1S), 0 , 215, 0 , 8910 , 388, 159), // #1346
- INST(Vplzcntd , VexRm_Lx , E(660F38,44,_,x,_,0,4,FV ), 0 , 114, 0 , 8918 , 374, 153), // #1347
- INST(Vplzcntq , VexRm_Lx , E(660F38,44,_,x,_,1,4,FV ), 0 , 113, 0 , 8927 , 350, 153), // #1348
- INST(Vpmacsdd , VexRvmr , V(XOP_M8,9E,_,0,0,_,_,_ ), 0 , 208, 0 , 8936 , 389, 144), // #1349
- INST(Vpmacsdqh , VexRvmr , V(XOP_M8,9F,_,0,0,_,_,_ ), 0 , 208, 0 , 8945 , 389, 144), // #1350
- INST(Vpmacsdql , VexRvmr , V(XOP_M8,97,_,0,0,_,_,_ ), 0 , 208, 0 , 8955 , 389, 144), // #1351
- INST(Vpmacssdd , VexRvmr , V(XOP_M8,8E,_,0,0,_,_,_ ), 0 , 208, 0 , 8965 , 389, 144), // #1352
- INST(Vpmacssdqh , VexRvmr , V(XOP_M8,8F,_,0,0,_,_,_ ), 0 , 208, 0 , 8975 , 389, 144), // #1353
- INST(Vpmacssdql , VexRvmr , V(XOP_M8,87,_,0,0,_,_,_ ), 0 , 208, 0 , 8986 , 389, 144), // #1354
- INST(Vpmacsswd , VexRvmr , V(XOP_M8,86,_,0,0,_,_,_ ), 0 , 208, 0 , 8997 , 389, 144), // #1355
- INST(Vpmacssww , VexRvmr , V(XOP_M8,85,_,0,0,_,_,_ ), 0 , 208, 0 , 9007 , 389, 144), // #1356
- INST(Vpmacswd , VexRvmr , V(XOP_M8,96,_,0,0,_,_,_ ), 0 , 208, 0 , 9017 , 389, 144), // #1357
- INST(Vpmacsww , VexRvmr , V(XOP_M8,95,_,0,0,_,_,_ ), 0 , 208, 0 , 9026 , 389, 144), // #1358
- INST(Vpmadcsswd , VexRvmr , V(XOP_M8,A6,_,0,0,_,_,_ ), 0 , 208, 0 , 9035 , 389, 144), // #1359
- INST(Vpmadcswd , VexRvmr , V(XOP_M8,B6,_,0,0,_,_,_ ), 0 , 208, 0 , 9046 , 389, 144), // #1360
- INST(Vpmadd52huq , VexRvm_Lx , E(660F38,B5,_,x,_,1,4,FV ), 0 , 113, 0 , 9056 , 212, 161), // #1361
- INST(Vpmadd52luq , VexRvm_Lx , E(660F38,B4,_,x,_,1,4,FV ), 0 , 113, 0 , 9068 , 212, 161), // #1362
- INST(Vpmaddubsw , VexRvm_Lx , V(660F38,04,_,x,I,I,4,FVM), 0 , 110, 0 , 9080 , 315, 151), // #1363
- INST(Vpmaddwd , VexRvm_Lx , V(660F00,F5,_,x,I,I,4,FVM), 0 , 144, 0 , 9091 , 315, 151), // #1364
- INST(Vpmaskmovd , VexRvmMvr_Lx , V(660F38,8C,_,x,0,_,_,_ ), V(660F38,8E,_,x,0,_,_,_ ), 96 , 120, 9100 , 323, 134), // #1365
- INST(Vpmaskmovq , VexRvmMvr_Lx , V(660F38,8C,_,x,1,_,_,_ ), V(660F38,8E,_,x,1,_,_,_ ), 189, 121, 9111 , 323, 134), // #1366
- INST(Vpmaxsb , VexRvm_Lx , V(660F38,3C,_,x,I,I,4,FVM), 0 , 110, 0 , 9122 , 390, 151), // #1367
- INST(Vpmaxsd , VexRvm_Lx , V(660F38,3D,_,x,I,0,4,FV ), 0 , 110, 0 , 9130 , 211, 135), // #1368
- INST(Vpmaxsq , VexRvm_Lx , E(660F38,3D,_,x,_,1,4,FV ), 0 , 113, 0 , 9138 , 212, 131), // #1369
- INST(Vpmaxsw , VexRvm_Lx , V(660F00,EE,_,x,I,I,4,FVM), 0 , 144, 0 , 9146 , 390, 151), // #1370
- INST(Vpmaxub , VexRvm_Lx , V(660F00,DE,_,x,I,I,4,FVM), 0 , 144, 0 , 9154 , 390, 151), // #1371
- INST(Vpmaxud , VexRvm_Lx , V(660F38,3F,_,x,I,0,4,FV ), 0 , 110, 0 , 9162 , 211, 135), // #1372
- INST(Vpmaxuq , VexRvm_Lx , E(660F38,3F,_,x,_,1,4,FV ), 0 , 113, 0 , 9170 , 212, 131), // #1373
- INST(Vpmaxuw , VexRvm_Lx , V(660F38,3E,_,x,I,I,4,FVM), 0 , 110, 0 , 9178 , 390, 151), // #1374
- INST(Vpminsb , VexRvm_Lx , V(660F38,38,_,x,I,I,4,FVM), 0 , 110, 0 , 9186 , 390, 151), // #1375
- INST(Vpminsd , VexRvm_Lx , V(660F38,39,_,x,I,0,4,FV ), 0 , 110, 0 , 9194 , 211, 135), // #1376
- INST(Vpminsq , VexRvm_Lx , E(660F38,39,_,x,_,1,4,FV ), 0 , 113, 0 , 9202 , 212, 131), // #1377
- INST(Vpminsw , VexRvm_Lx , V(660F00,EA,_,x,I,I,4,FVM), 0 , 144, 0 , 9210 , 390, 151), // #1378
- INST(Vpminub , VexRvm_Lx , V(660F00,DA,_,x,I,_,4,FVM), 0 , 144, 0 , 9218 , 390, 151), // #1379
- INST(Vpminud , VexRvm_Lx , V(660F38,3B,_,x,I,0,4,FV ), 0 , 110, 0 , 9226 , 211, 135), // #1380
- INST(Vpminuq , VexRvm_Lx , E(660F38,3B,_,x,_,1,4,FV ), 0 , 113, 0 , 9234 , 212, 131), // #1381
- INST(Vpminuw , VexRvm_Lx , V(660F38,3A,_,x,I,_,4,FVM), 0 , 110, 0 , 9242 , 390, 151), // #1382
- INST(Vpmovb2m , VexRm_Lx , E(F30F38,29,_,x,_,0,_,_ ), 0 , 206, 0 , 9250 , 391, 139), // #1383
- INST(Vpmovd2m , VexRm_Lx , E(F30F38,39,_,x,_,0,_,_ ), 0 , 206, 0 , 9259 , 391, 133), // #1384
- INST(Vpmovdb , VexMr_Lx , E(F30F38,31,_,x,_,0,2,QVM), 0 , 216, 0 , 9268 , 392, 131), // #1385
- INST(Vpmovdw , VexMr_Lx , E(F30F38,33,_,x,_,0,3,HVM), 0 , 217, 0 , 9276 , 393, 131), // #1386
- INST(Vpmovm2b , VexRm_Lx , E(F30F38,28,_,x,_,0,_,_ ), 0 , 206, 0 , 9284 , 360, 139), // #1387
- INST(Vpmovm2d , VexRm_Lx , E(F30F38,38,_,x,_,0,_,_ ), 0 , 206, 0 , 9293 , 360, 133), // #1388
- INST(Vpmovm2q , VexRm_Lx , E(F30F38,38,_,x,_,1,_,_ ), 0 , 205, 0 , 9302 , 360, 133), // #1389
- INST(Vpmovm2w , VexRm_Lx , E(F30F38,28,_,x,_,1,_,_ ), 0 , 205, 0 , 9311 , 360, 139), // #1390
- INST(Vpmovmskb , VexRm_Lx , V(660F00,D7,_,x,I,_,_,_ ), 0 , 69 , 0 , 9320 , 336, 148), // #1391
- INST(Vpmovq2m , VexRm_Lx , E(F30F38,39,_,x,_,1,_,_ ), 0 , 205, 0 , 9330 , 391, 133), // #1392
- INST(Vpmovqb , VexMr_Lx , E(F30F38,32,_,x,_,0,1,OVM), 0 , 218, 0 , 9339 , 394, 131), // #1393
- INST(Vpmovqd , VexMr_Lx , E(F30F38,35,_,x,_,0,3,HVM), 0 , 217, 0 , 9347 , 393, 131), // #1394
- INST(Vpmovqw , VexMr_Lx , E(F30F38,34,_,x,_,0,2,QVM), 0 , 216, 0 , 9355 , 392, 131), // #1395
- INST(Vpmovsdb , VexMr_Lx , E(F30F38,21,_,x,_,0,2,QVM), 0 , 216, 0 , 9363 , 392, 131), // #1396
- INST(Vpmovsdw , VexMr_Lx , E(F30F38,23,_,x,_,0,3,HVM), 0 , 217, 0 , 9372 , 393, 131), // #1397
- INST(Vpmovsqb , VexMr_Lx , E(F30F38,22,_,x,_,0,1,OVM), 0 , 218, 0 , 9381 , 394, 131), // #1398
- INST(Vpmovsqd , VexMr_Lx , E(F30F38,25,_,x,_,0,3,HVM), 0 , 217, 0 , 9390 , 393, 131), // #1399
- INST(Vpmovsqw , VexMr_Lx , E(F30F38,24,_,x,_,0,2,QVM), 0 , 216, 0 , 9399 , 392, 131), // #1400
- INST(Vpmovswb , VexMr_Lx , E(F30F38,20,_,x,_,0,3,HVM), 0 , 217, 0 , 9408 , 393, 139), // #1401
- INST(Vpmovsxbd , VexRm_Lx , V(660F38,21,_,x,I,I,2,QVM), 0 , 219, 0 , 9417 , 395, 135), // #1402
- INST(Vpmovsxbq , VexRm_Lx , V(660F38,22,_,x,I,I,1,OVM), 0 , 220, 0 , 9427 , 396, 135), // #1403
- INST(Vpmovsxbw , VexRm_Lx , V(660F38,20,_,x,I,I,3,HVM), 0 , 139, 0 , 9437 , 397, 151), // #1404
- INST(Vpmovsxdq , VexRm_Lx , V(660F38,25,_,x,I,0,3,HVM), 0 , 139, 0 , 9447 , 397, 135), // #1405
- INST(Vpmovsxwd , VexRm_Lx , V(660F38,23,_,x,I,I,3,HVM), 0 , 139, 0 , 9457 , 397, 135), // #1406
- INST(Vpmovsxwq , VexRm_Lx , V(660F38,24,_,x,I,I,2,QVM), 0 , 219, 0 , 9467 , 395, 135), // #1407
- INST(Vpmovusdb , VexMr_Lx , E(F30F38,11,_,x,_,0,2,QVM), 0 , 216, 0 , 9477 , 392, 131), // #1408
- INST(Vpmovusdw , VexMr_Lx , E(F30F38,13,_,x,_,0,3,HVM), 0 , 217, 0 , 9487 , 393, 131), // #1409
- INST(Vpmovusqb , VexMr_Lx , E(F30F38,12,_,x,_,0,1,OVM), 0 , 218, 0 , 9497 , 394, 131), // #1410
- INST(Vpmovusqd , VexMr_Lx , E(F30F38,15,_,x,_,0,3,HVM), 0 , 217, 0 , 9507 , 393, 131), // #1411
- INST(Vpmovusqw , VexMr_Lx , E(F30F38,14,_,x,_,0,2,QVM), 0 , 216, 0 , 9517 , 392, 131), // #1412
- INST(Vpmovuswb , VexMr_Lx , E(F30F38,10,_,x,_,0,3,HVM), 0 , 217, 0 , 9527 , 393, 139), // #1413
- INST(Vpmovw2m , VexRm_Lx , E(F30F38,29,_,x,_,1,_,_ ), 0 , 205, 0 , 9537 , 391, 139), // #1414
- INST(Vpmovwb , VexMr_Lx , E(F30F38,30,_,x,_,0,3,HVM), 0 , 217, 0 , 9546 , 393, 139), // #1415
- INST(Vpmovzxbd , VexRm_Lx , V(660F38,31,_,x,I,I,2,QVM), 0 , 219, 0 , 9554 , 395, 135), // #1416
- INST(Vpmovzxbq , VexRm_Lx , V(660F38,32,_,x,I,I,1,OVM), 0 , 220, 0 , 9564 , 396, 135), // #1417
- INST(Vpmovzxbw , VexRm_Lx , V(660F38,30,_,x,I,I,3,HVM), 0 , 139, 0 , 9574 , 397, 151), // #1418
- INST(Vpmovzxdq , VexRm_Lx , V(660F38,35,_,x,I,0,3,HVM), 0 , 139, 0 , 9584 , 397, 135), // #1419
- INST(Vpmovzxwd , VexRm_Lx , V(660F38,33,_,x,I,I,3,HVM), 0 , 139, 0 , 9594 , 397, 135), // #1420
- INST(Vpmovzxwq , VexRm_Lx , V(660F38,34,_,x,I,I,2,QVM), 0 , 219, 0 , 9604 , 395, 135), // #1421
- INST(Vpmuldq , VexRvm_Lx , V(660F38,28,_,x,I,1,4,FV ), 0 , 209, 0 , 9614 , 208, 135), // #1422
- INST(Vpmulhrsw , VexRvm_Lx , V(660F38,0B,_,x,I,I,4,FVM), 0 , 110, 0 , 9622 , 315, 151), // #1423
- INST(Vpmulhuw , VexRvm_Lx , V(660F00,E4,_,x,I,I,4,FVM), 0 , 144, 0 , 9632 , 315, 151), // #1424
- INST(Vpmulhw , VexRvm_Lx , V(660F00,E5,_,x,I,I,4,FVM), 0 , 144, 0 , 9641 , 315, 151), // #1425
- INST(Vpmulld , VexRvm_Lx , V(660F38,40,_,x,I,0,4,FV ), 0 , 110, 0 , 9649 , 209, 135), // #1426
- INST(Vpmullq , VexRvm_Lx , E(660F38,40,_,x,_,1,4,FV ), 0 , 113, 0 , 9657 , 212, 133), // #1427
- INST(Vpmullw , VexRvm_Lx , V(660F00,D5,_,x,I,I,4,FVM), 0 , 144, 0 , 9665 , 315, 151), // #1428
- INST(Vpmultishiftqb , VexRvm_Lx , E(660F38,83,_,x,_,1,4,FV ), 0 , 113, 0 , 9673 , 212, 158), // #1429
- INST(Vpmuludq , VexRvm_Lx , V(660F00,F4,_,x,I,1,4,FV ), 0 , 103, 0 , 9688 , 208, 135), // #1430
- INST(Vpopcntb , VexRm_Lx , E(660F38,54,_,x,_,0,4,FV ), 0 , 114, 0 , 9697 , 279, 162), // #1431
- INST(Vpopcntd , VexRm_Lx , E(660F38,55,_,x,_,0,4,FVM), 0 , 114, 0 , 9706 , 374, 163), // #1432
- INST(Vpopcntq , VexRm_Lx , E(660F38,55,_,x,_,1,4,FVM), 0 , 113, 0 , 9715 , 350, 163), // #1433
- INST(Vpopcntw , VexRm_Lx , E(660F38,54,_,x,_,1,4,FV ), 0 , 113, 0 , 9724 , 279, 162), // #1434
- INST(Vpor , VexRvm_Lx , V(660F00,EB,_,x,I,_,_,_ ), 0 , 69 , 0 , 9733 , 351, 148), // #1435
- INST(Vpord , VexRvm_Lx , E(660F00,EB,_,x,_,0,4,FV ), 0 , 198, 0 , 9738 , 352, 131), // #1436
- INST(Vporq , VexRvm_Lx , E(660F00,EB,_,x,_,1,4,FV ), 0 , 135, 0 , 9744 , 356, 131), // #1437
- INST(Vpperm , VexRvrmRvmr , V(XOP_M8,A3,_,0,x,_,_,_ ), 0 , 208, 0 , 9750 , 398, 144), // #1438
- INST(Vprold , VexVmi_Lx , E(660F00,72,1,x,_,0,4,FV ), 0 , 221, 0 , 9757 , 399, 131), // #1439
- INST(Vprolq , VexVmi_Lx , E(660F00,72,1,x,_,1,4,FV ), 0 , 222, 0 , 9764 , 400, 131), // #1440
- INST(Vprolvd , VexRvm_Lx , E(660F38,15,_,x,_,0,4,FV ), 0 , 114, 0 , 9771 , 213, 131), // #1441
- INST(Vprolvq , VexRvm_Lx , E(660F38,15,_,x,_,1,4,FV ), 0 , 113, 0 , 9779 , 212, 131), // #1442
- INST(Vprord , VexVmi_Lx , E(660F00,72,0,x,_,0,4,FV ), 0 , 198, 0 , 9787 , 399, 131), // #1443
- INST(Vprorq , VexVmi_Lx , E(660F00,72,0,x,_,1,4,FV ), 0 , 135, 0 , 9794 , 400, 131), // #1444
- INST(Vprorvd , VexRvm_Lx , E(660F38,14,_,x,_,0,4,FV ), 0 , 114, 0 , 9801 , 213, 131), // #1445
- INST(Vprorvq , VexRvm_Lx , E(660F38,14,_,x,_,1,4,FV ), 0 , 113, 0 , 9809 , 212, 131), // #1446
- INST(Vprotb , VexRvmRmvRmi , V(XOP_M9,90,_,0,x,_,_,_ ), V(XOP_M8,C0,_,0,x,_,_,_ ), 79 , 122, 9817 , 401, 144), // #1447
- INST(Vprotd , VexRvmRmvRmi , V(XOP_M9,92,_,0,x,_,_,_ ), V(XOP_M8,C2,_,0,x,_,_,_ ), 79 , 123, 9824 , 401, 144), // #1448
- INST(Vprotq , VexRvmRmvRmi , V(XOP_M9,93,_,0,x,_,_,_ ), V(XOP_M8,C3,_,0,x,_,_,_ ), 79 , 124, 9831 , 401, 144), // #1449
- INST(Vprotw , VexRvmRmvRmi , V(XOP_M9,91,_,0,x,_,_,_ ), V(XOP_M8,C1,_,0,x,_,_,_ ), 79 , 125, 9838 , 401, 144), // #1450
- INST(Vpsadbw , VexRvm_Lx , V(660F00,F6,_,x,I,I,4,FVM), 0 , 144, 0 , 9845 , 203, 151), // #1451
- INST(Vpscatterdd , VexMr_VM , E(660F38,A0,_,x,_,0,2,T1S), 0 , 129, 0 , 9853 , 402, 131), // #1452
- INST(Vpscatterdq , VexMr_VM , E(660F38,A0,_,x,_,1,3,T1S), 0 , 128, 0 , 9865 , 403, 131), // #1453
- INST(Vpscatterqd , VexMr_VM , E(660F38,A1,_,x,_,0,2,T1S), 0 , 129, 0 , 9877 , 404, 131), // #1454
- INST(Vpscatterqq , VexMr_VM , E(660F38,A1,_,x,_,1,3,T1S), 0 , 128, 0 , 9889 , 405, 131), // #1455
- INST(Vpshab , VexRvmRmv , V(XOP_M9,98,_,0,x,_,_,_ ), 0 , 79 , 0 , 9901 , 406, 144), // #1456
- INST(Vpshad , VexRvmRmv , V(XOP_M9,9A,_,0,x,_,_,_ ), 0 , 79 , 0 , 9908 , 406, 144), // #1457
- INST(Vpshaq , VexRvmRmv , V(XOP_M9,9B,_,0,x,_,_,_ ), 0 , 79 , 0 , 9915 , 406, 144), // #1458
- INST(Vpshaw , VexRvmRmv , V(XOP_M9,99,_,0,x,_,_,_ ), 0 , 79 , 0 , 9922 , 406, 144), // #1459
- INST(Vpshlb , VexRvmRmv , V(XOP_M9,94,_,0,x,_,_,_ ), 0 , 79 , 0 , 9929 , 406, 144), // #1460
- INST(Vpshld , VexRvmRmv , V(XOP_M9,96,_,0,x,_,_,_ ), 0 , 79 , 0 , 9936 , 406, 144), // #1461
- INST(Vpshldd , VexRvmi_Lx , E(660F3A,71,_,x,_,0,4,FV ), 0 , 111, 0 , 9943 , 206, 156), // #1462
- INST(Vpshldq , VexRvmi_Lx , E(660F3A,71,_,x,_,1,4,FV ), 0 , 112, 0 , 9951 , 207, 156), // #1463
- INST(Vpshldvd , VexRvm_Lx , E(660F38,71,_,x,_,0,4,FV ), 0 , 114, 0 , 9959 , 213, 156), // #1464
- INST(Vpshldvq , VexRvm_Lx , E(660F38,71,_,x,_,1,4,FV ), 0 , 113, 0 , 9968 , 212, 156), // #1465
- INST(Vpshldvw , VexRvm_Lx , E(660F38,70,_,x,_,1,4,FVM), 0 , 113, 0 , 9977 , 357, 156), // #1466
- INST(Vpshldw , VexRvmi_Lx , E(660F3A,70,_,x,_,1,4,FVM), 0 , 112, 0 , 9986 , 275, 156), // #1467
- INST(Vpshlq , VexRvmRmv , V(XOP_M9,97,_,0,x,_,_,_ ), 0 , 79 , 0 , 9994 , 406, 144), // #1468
- INST(Vpshlw , VexRvmRmv , V(XOP_M9,95,_,0,x,_,_,_ ), 0 , 79 , 0 , 10001, 406, 144), // #1469
- INST(Vpshrdd , VexRvmi_Lx , E(660F3A,73,_,x,_,0,4,FV ), 0 , 111, 0 , 10008, 206, 156), // #1470
- INST(Vpshrdq , VexRvmi_Lx , E(660F3A,73,_,x,_,1,4,FV ), 0 , 112, 0 , 10016, 207, 156), // #1471
- INST(Vpshrdvd , VexRvm_Lx , E(660F38,73,_,x,_,0,4,FV ), 0 , 114, 0 , 10024, 213, 156), // #1472
- INST(Vpshrdvq , VexRvm_Lx , E(660F38,73,_,x,_,1,4,FV ), 0 , 113, 0 , 10033, 212, 156), // #1473
- INST(Vpshrdvw , VexRvm_Lx , E(660F38,72,_,x,_,1,4,FVM), 0 , 113, 0 , 10042, 357, 156), // #1474
- INST(Vpshrdw , VexRvmi_Lx , E(660F3A,72,_,x,_,1,4,FVM), 0 , 112, 0 , 10051, 275, 156), // #1475
- INST(Vpshufb , VexRvm_Lx , V(660F38,00,_,x,I,I,4,FVM), 0 , 110, 0 , 10059, 315, 151), // #1476
- INST(Vpshufbitqmb , VexRvm_Lx , E(660F38,8F,_,x,0,0,4,FVM), 0 , 114, 0 , 10067, 407, 162), // #1477
- INST(Vpshufd , VexRmi_Lx , V(660F00,70,_,x,I,0,4,FV ), 0 , 144, 0 , 10080, 408, 135), // #1478
- INST(Vpshufhw , VexRmi_Lx , V(F30F00,70,_,x,I,I,4,FVM), 0 , 161, 0 , 10088, 409, 151), // #1479
- INST(Vpshuflw , VexRmi_Lx , V(F20F00,70,_,x,I,I,4,FVM), 0 , 223, 0 , 10097, 409, 151), // #1480
- INST(Vpsignb , VexRvm_Lx , V(660F38,08,_,x,I,_,_,_ ), 0 , 96 , 0 , 10106, 202, 148), // #1481
- INST(Vpsignd , VexRvm_Lx , V(660F38,0A,_,x,I,_,_,_ ), 0 , 96 , 0 , 10114, 202, 148), // #1482
- INST(Vpsignw , VexRvm_Lx , V(660F38,09,_,x,I,_,_,_ ), 0 , 96 , 0 , 10122, 202, 148), // #1483
- INST(Vpslld , VexRvmVmi_Lx_MEvex , V(660F00,F2,_,x,I,0,4,128), V(660F00,72,6,x,I,0,4,FV ), 224, 126, 10130, 410, 135), // #1484
- INST(Vpslldq , VexVmi_Lx_MEvex , V(660F00,73,7,x,I,I,4,FVM), 0 , 225, 0 , 10137, 411, 151), // #1485
- INST(Vpsllq , VexRvmVmi_Lx_MEvex , V(660F00,F3,_,x,I,1,4,128), V(660F00,73,6,x,I,1,4,FV ), 226, 127, 10145, 412, 135), // #1486
- INST(Vpsllvd , VexRvm_Lx , V(660F38,47,_,x,0,0,4,FV ), 0 , 110, 0 , 10152, 209, 145), // #1487
- INST(Vpsllvq , VexRvm_Lx , V(660F38,47,_,x,1,1,4,FV ), 0 , 182, 0 , 10160, 208, 145), // #1488
- INST(Vpsllvw , VexRvm_Lx , E(660F38,12,_,x,_,1,4,FVM), 0 , 113, 0 , 10168, 357, 139), // #1489
- INST(Vpsllw , VexRvmVmi_Lx_MEvex , V(660F00,F1,_,x,I,I,4,128), V(660F00,71,6,x,I,I,4,FVM), 224, 128, 10176, 413, 151), // #1490
- INST(Vpsrad , VexRvmVmi_Lx_MEvex , V(660F00,E2,_,x,I,0,4,128), V(660F00,72,4,x,I,0,4,FV ), 224, 129, 10183, 410, 135), // #1491
- INST(Vpsraq , VexRvmVmi_Lx_MEvex , E(660F00,E2,_,x,_,1,4,128), E(660F00,72,4,x,_,1,4,FV ), 227, 130, 10190, 414, 131), // #1492
- INST(Vpsravd , VexRvm_Lx , V(660F38,46,_,x,0,0,4,FV ), 0 , 110, 0 , 10197, 209, 145), // #1493
- INST(Vpsravq , VexRvm_Lx , E(660F38,46,_,x,_,1,4,FV ), 0 , 113, 0 , 10205, 212, 131), // #1494
- INST(Vpsravw , VexRvm_Lx , E(660F38,11,_,x,_,1,4,FVM), 0 , 113, 0 , 10213, 357, 139), // #1495
- INST(Vpsraw , VexRvmVmi_Lx_MEvex , V(660F00,E1,_,x,I,I,4,128), V(660F00,71,4,x,I,I,4,FVM), 224, 131, 10221, 413, 151), // #1496
- INST(Vpsrld , VexRvmVmi_Lx_MEvex , V(660F00,D2,_,x,I,0,4,128), V(660F00,72,2,x,I,0,4,FV ), 224, 132, 10228, 410, 135), // #1497
- INST(Vpsrldq , VexVmi_Lx_MEvex , V(660F00,73,3,x,I,I,4,FVM), 0 , 228, 0 , 10235, 411, 151), // #1498
- INST(Vpsrlq , VexRvmVmi_Lx_MEvex , V(660F00,D3,_,x,I,1,4,128), V(660F00,73,2,x,I,1,4,FV ), 226, 133, 10243, 412, 135), // #1499
- INST(Vpsrlvd , VexRvm_Lx , V(660F38,45,_,x,0,0,4,FV ), 0 , 110, 0 , 10250, 209, 145), // #1500
- INST(Vpsrlvq , VexRvm_Lx , V(660F38,45,_,x,1,1,4,FV ), 0 , 182, 0 , 10258, 208, 145), // #1501
- INST(Vpsrlvw , VexRvm_Lx , E(660F38,10,_,x,_,1,4,FVM), 0 , 113, 0 , 10266, 357, 139), // #1502
- INST(Vpsrlw , VexRvmVmi_Lx_MEvex , V(660F00,D1,_,x,I,I,4,128), V(660F00,71,2,x,I,I,4,FVM), 224, 134, 10274, 413, 151), // #1503
- INST(Vpsubb , VexRvm_Lx , V(660F00,F8,_,x,I,I,4,FVM), 0 , 144, 0 , 10281, 415, 151), // #1504
- INST(Vpsubd , VexRvm_Lx , V(660F00,FA,_,x,I,0,4,FV ), 0 , 144, 0 , 10288, 416, 135), // #1505
- INST(Vpsubq , VexRvm_Lx , V(660F00,FB,_,x,I,1,4,FV ), 0 , 103, 0 , 10295, 417, 135), // #1506
- INST(Vpsubsb , VexRvm_Lx , V(660F00,E8,_,x,I,I,4,FVM), 0 , 144, 0 , 10302, 415, 151), // #1507
- INST(Vpsubsw , VexRvm_Lx , V(660F00,E9,_,x,I,I,4,FVM), 0 , 144, 0 , 10310, 415, 151), // #1508
- INST(Vpsubusb , VexRvm_Lx , V(660F00,D8,_,x,I,I,4,FVM), 0 , 144, 0 , 10318, 415, 151), // #1509
- INST(Vpsubusw , VexRvm_Lx , V(660F00,D9,_,x,I,I,4,FVM), 0 , 144, 0 , 10327, 415, 151), // #1510
- INST(Vpsubw , VexRvm_Lx , V(660F00,F9,_,x,I,I,4,FVM), 0 , 144, 0 , 10336, 415, 151), // #1511
- INST(Vpternlogd , VexRvmi_Lx , E(660F3A,25,_,x,_,0,4,FV ), 0 , 111, 0 , 10343, 206, 131), // #1512
- INST(Vpternlogq , VexRvmi_Lx , E(660F3A,25,_,x,_,1,4,FV ), 0 , 112, 0 , 10354, 207, 131), // #1513
- INST(Vptest , VexRm_Lx , V(660F38,17,_,x,I,_,_,_ ), 0 , 96 , 0 , 10365, 298, 155), // #1514
- INST(Vptestmb , VexRvm_Lx , E(660F38,26,_,x,_,0,4,FVM), 0 , 114, 0 , 10372, 407, 139), // #1515
- INST(Vptestmd , VexRvm_Lx , E(660F38,27,_,x,_,0,4,FV ), 0 , 114, 0 , 10381, 418, 131), // #1516
- INST(Vptestmq , VexRvm_Lx , E(660F38,27,_,x,_,1,4,FV ), 0 , 113, 0 , 10390, 419, 131), // #1517
- INST(Vptestmw , VexRvm_Lx , E(660F38,26,_,x,_,1,4,FVM), 0 , 113, 0 , 10399, 407, 139), // #1518
- INST(Vptestnmb , VexRvm_Lx , E(F30F38,26,_,x,_,0,4,FVM), 0 , 132, 0 , 10408, 407, 139), // #1519
- INST(Vptestnmd , VexRvm_Lx , E(F30F38,27,_,x,_,0,4,FV ), 0 , 132, 0 , 10418, 418, 131), // #1520
- INST(Vptestnmq , VexRvm_Lx , E(F30F38,27,_,x,_,1,4,FV ), 0 , 229, 0 , 10428, 419, 131), // #1521
- INST(Vptestnmw , VexRvm_Lx , E(F30F38,26,_,x,_,1,4,FVM), 0 , 229, 0 , 10438, 407, 139), // #1522
- INST(Vpunpckhbw , VexRvm_Lx , V(660F00,68,_,x,I,I,4,FVM), 0 , 144, 0 , 10448, 315, 151), // #1523
- INST(Vpunpckhdq , VexRvm_Lx , V(660F00,6A,_,x,I,0,4,FV ), 0 , 144, 0 , 10459, 209, 135), // #1524
- INST(Vpunpckhqdq , VexRvm_Lx , V(660F00,6D,_,x,I,1,4,FV ), 0 , 103, 0 , 10470, 208, 135), // #1525
- INST(Vpunpckhwd , VexRvm_Lx , V(660F00,69,_,x,I,I,4,FVM), 0 , 144, 0 , 10482, 315, 151), // #1526
- INST(Vpunpcklbw , VexRvm_Lx , V(660F00,60,_,x,I,I,4,FVM), 0 , 144, 0 , 10493, 315, 151), // #1527
- INST(Vpunpckldq , VexRvm_Lx , V(660F00,62,_,x,I,0,4,FV ), 0 , 144, 0 , 10504, 209, 135), // #1528
- INST(Vpunpcklqdq , VexRvm_Lx , V(660F00,6C,_,x,I,1,4,FV ), 0 , 103, 0 , 10515, 208, 135), // #1529
- INST(Vpunpcklwd , VexRvm_Lx , V(660F00,61,_,x,I,I,4,FVM), 0 , 144, 0 , 10527, 315, 151), // #1530
- INST(Vpxor , VexRvm_Lx , V(660F00,EF,_,x,I,_,_,_ ), 0 , 69 , 0 , 10538, 353, 148), // #1531
- INST(Vpxord , VexRvm_Lx , E(660F00,EF,_,x,_,0,4,FV ), 0 , 198, 0 , 10544, 354, 131), // #1532
- INST(Vpxorq , VexRvm_Lx , E(660F00,EF,_,x,_,1,4,FV ), 0 , 135, 0 , 10551, 355, 131), // #1533
- INST(Vrangepd , VexRvmi_Lx , E(660F3A,50,_,x,_,1,4,FV ), 0 , 112, 0 , 10558, 285, 133), // #1534
- INST(Vrangeps , VexRvmi_Lx , E(660F3A,50,_,x,_,0,4,FV ), 0 , 111, 0 , 10567, 286, 133), // #1535
- INST(Vrangesd , VexRvmi , E(660F3A,51,_,I,_,1,3,T1S), 0 , 180, 0 , 10576, 287, 66 ), // #1536
- INST(Vrangess , VexRvmi , E(660F3A,51,_,I,_,0,2,T1S), 0 , 181, 0 , 10585, 288, 66 ), // #1537
- INST(Vrcp14pd , VexRm_Lx , E(660F38,4C,_,x,_,1,4,FV ), 0 , 113, 0 , 10594, 350, 131), // #1538
- INST(Vrcp14ps , VexRm_Lx , E(660F38,4C,_,x,_,0,4,FV ), 0 , 114, 0 , 10603, 374, 131), // #1539
- INST(Vrcp14sd , VexRvm , E(660F38,4D,_,I,_,1,3,T1S), 0 , 128, 0 , 10612, 420, 68 ), // #1540
- INST(Vrcp14ss , VexRvm , E(660F38,4D,_,I,_,0,2,T1S), 0 , 129, 0 , 10621, 421, 68 ), // #1541
- INST(Vrcp28pd , VexRm , E(660F38,CA,_,2,_,1,4,FV ), 0 , 170, 0 , 10630, 277, 140), // #1542
- INST(Vrcp28ps , VexRm , E(660F38,CA,_,2,_,0,4,FV ), 0 , 171, 0 , 10639, 278, 140), // #1543
- INST(Vrcp28sd , VexRvm , E(660F38,CB,_,I,_,1,3,T1S), 0 , 128, 0 , 10648, 308, 140), // #1544
- INST(Vrcp28ss , VexRvm , E(660F38,CB,_,I,_,0,2,T1S), 0 , 129, 0 , 10657, 309, 140), // #1545
- INST(Vrcpph , VexRm_Lx , E(66MAP6,4C,_,_,_,0,4,FV ), 0 , 183, 0 , 10666, 422, 127), // #1546
- INST(Vrcpps , VexRm_Lx , V(000F00,53,_,x,I,_,_,_ ), 0 , 72 , 0 , 10673, 298, 128), // #1547
- INST(Vrcpsh , VexRvm , E(66MAP6,4D,_,_,_,0,1,T1S), 0 , 185, 0 , 10680, 423, 127), // #1548
- INST(Vrcpss , VexRvm , V(F30F00,53,_,I,I,_,_,_ ), 0 , 199, 0 , 10687, 424, 128), // #1549
- INST(Vreducepd , VexRmi_Lx , E(660F3A,56,_,x,_,1,4,FV ), 0 , 112, 0 , 10694, 400, 133), // #1550
- INST(Vreduceph , VexRmi_Lx , E(000F3A,56,_,_,_,0,4,FV ), 0 , 123, 0 , 10704, 311, 125), // #1551
- INST(Vreduceps , VexRmi_Lx , E(660F3A,56,_,x,_,0,4,FV ), 0 , 111, 0 , 10714, 399, 133), // #1552
- INST(Vreducesd , VexRvmi , E(660F3A,57,_,I,_,1,3,T1S), 0 , 180, 0 , 10724, 425, 66 ), // #1553
- INST(Vreducesh , VexRvmi , E(000F3A,57,_,_,_,0,1,T1S), 0 , 188, 0 , 10734, 313, 127), // #1554
- INST(Vreducess , VexRvmi , E(660F3A,57,_,I,_,0,2,T1S), 0 , 181, 0 , 10744, 426, 66 ), // #1555
- INST(Vrndscalepd , VexRmi_Lx , E(660F3A,09,_,x,_,1,4,FV ), 0 , 112, 0 , 10754, 310, 131), // #1556
- INST(Vrndscaleph , VexRmi_Lx , E(000F3A,08,_,_,_,0,4,FV ), 0 , 123, 0 , 10766, 311, 125), // #1557
- INST(Vrndscaleps , VexRmi_Lx , E(660F3A,08,_,x,_,0,4,FV ), 0 , 111, 0 , 10778, 312, 131), // #1558
- INST(Vrndscalesd , VexRvmi , E(660F3A,0B,_,I,_,1,3,T1S), 0 , 180, 0 , 10790, 287, 68 ), // #1559
- INST(Vrndscalesh , VexRvmi , E(000F3A,0A,_,_,_,0,1,T1S), 0 , 188, 0 , 10802, 313, 127), // #1560
- INST(Vrndscaless , VexRvmi , E(660F3A,0A,_,I,_,0,2,T1S), 0 , 181, 0 , 10814, 288, 68 ), // #1561
- INST(Vroundpd , VexRmi_Lx , V(660F3A,09,_,x,I,_,_,_ ), 0 , 73 , 0 , 10826, 427, 128), // #1562
- INST(Vroundps , VexRmi_Lx , V(660F3A,08,_,x,I,_,_,_ ), 0 , 73 , 0 , 10835, 427, 128), // #1563
- INST(Vroundsd , VexRvmi , V(660F3A,0B,_,I,I,_,_,_ ), 0 , 73 , 0 , 10844, 428, 128), // #1564
- INST(Vroundss , VexRvmi , V(660F3A,0A,_,I,I,_,_,_ ), 0 , 73 , 0 , 10853, 429, 128), // #1565
- INST(Vrsqrt14pd , VexRm_Lx , E(660F38,4E,_,x,_,1,4,FV ), 0 , 113, 0 , 10862, 350, 131), // #1566
- INST(Vrsqrt14ps , VexRm_Lx , E(660F38,4E,_,x,_,0,4,FV ), 0 , 114, 0 , 10873, 374, 131), // #1567
- INST(Vrsqrt14sd , VexRvm , E(660F38,4F,_,I,_,1,3,T1S), 0 , 128, 0 , 10884, 420, 68 ), // #1568
- INST(Vrsqrt14ss , VexRvm , E(660F38,4F,_,I,_,0,2,T1S), 0 , 129, 0 , 10895, 421, 68 ), // #1569
- INST(Vrsqrt28pd , VexRm , E(660F38,CC,_,2,_,1,4,FV ), 0 , 170, 0 , 10906, 277, 140), // #1570
- INST(Vrsqrt28ps , VexRm , E(660F38,CC,_,2,_,0,4,FV ), 0 , 171, 0 , 10917, 278, 140), // #1571
- INST(Vrsqrt28sd , VexRvm , E(660F38,CD,_,I,_,1,3,T1S), 0 , 128, 0 , 10928, 308, 140), // #1572
- INST(Vrsqrt28ss , VexRvm , E(660F38,CD,_,I,_,0,2,T1S), 0 , 129, 0 , 10939, 309, 140), // #1573
- INST(Vrsqrtph , VexRm_Lx , E(66MAP6,4E,_,_,_,0,4,FV ), 0 , 183, 0 , 10950, 422, 125), // #1574
- INST(Vrsqrtps , VexRm_Lx , V(000F00,52,_,x,I,_,_,_ ), 0 , 72 , 0 , 10959, 298, 128), // #1575
- INST(Vrsqrtsh , VexRvm , E(66MAP6,4F,_,_,_,0,1,T1S), 0 , 185, 0 , 10968, 423, 127), // #1576
- INST(Vrsqrtss , VexRvm , V(F30F00,52,_,I,I,_,_,_ ), 0 , 199, 0 , 10977, 424, 128), // #1577
- INST(Vscalefpd , VexRvm_Lx , E(660F38,2C,_,x,_,1,4,FV ), 0 , 113, 0 , 10986, 430, 131), // #1578
- INST(Vscalefph , VexRvm_Lx , E(66MAP6,2C,_,_,_,0,4,FV ), 0 , 183, 0 , 10996, 197, 125), // #1579
- INST(Vscalefps , VexRvm_Lx , E(660F38,2C,_,x,_,0,4,FV ), 0 , 114, 0 , 11006, 284, 131), // #1580
- INST(Vscalefsd , VexRvm , E(660F38,2D,_,I,_,1,3,T1S), 0 , 128, 0 , 11016, 251, 68 ), // #1581
- INST(Vscalefsh , VexRvm , E(66MAP6,2D,_,_,_,0,1,T1S), 0 , 185, 0 , 11026, 200, 127), // #1582
- INST(Vscalefss , VexRvm , E(660F38,2D,_,I,_,0,2,T1S), 0 , 129, 0 , 11036, 259, 68 ), // #1583
- INST(Vscatterdpd , VexMr_VM , E(660F38,A2,_,x,_,1,3,T1S), 0 , 128, 0 , 11046, 403, 131), // #1584
- INST(Vscatterdps , VexMr_VM , E(660F38,A2,_,x,_,0,2,T1S), 0 , 129, 0 , 11058, 402, 131), // #1585
- INST(Vscatterpf0dpd , VexM_VM , E(660F38,C6,5,2,_,1,3,T1S), 0 , 230, 0 , 11070, 303, 146), // #1586
- INST(Vscatterpf0dps , VexM_VM , E(660F38,C6,5,2,_,0,2,T1S), 0 , 231, 0 , 11085, 304, 146), // #1587
- INST(Vscatterpf0qpd , VexM_VM , E(660F38,C7,5,2,_,1,3,T1S), 0 , 230, 0 , 11100, 305, 146), // #1588
- INST(Vscatterpf0qps , VexM_VM , E(660F38,C7,5,2,_,0,2,T1S), 0 , 231, 0 , 11115, 305, 146), // #1589
- INST(Vscatterpf1dpd , VexM_VM , E(660F38,C6,6,2,_,1,3,T1S), 0 , 232, 0 , 11130, 303, 146), // #1590
- INST(Vscatterpf1dps , VexM_VM , E(660F38,C6,6,2,_,0,2,T1S), 0 , 233, 0 , 11145, 304, 146), // #1591
- INST(Vscatterpf1qpd , VexM_VM , E(660F38,C7,6,2,_,1,3,T1S), 0 , 232, 0 , 11160, 305, 146), // #1592
- INST(Vscatterpf1qps , VexM_VM , E(660F38,C7,6,2,_,0,2,T1S), 0 , 233, 0 , 11175, 305, 146), // #1593
- INST(Vscatterqpd , VexMr_VM , E(660F38,A3,_,x,_,1,3,T1S), 0 , 128, 0 , 11190, 405, 131), // #1594
- INST(Vscatterqps , VexMr_VM , E(660F38,A3,_,x,_,0,2,T1S), 0 , 129, 0 , 11202, 404, 131), // #1595
- INST(Vshuff32x4 , VexRvmi_Lx , E(660F3A,23,_,x,_,0,4,FV ), 0 , 111, 0 , 11214, 431, 131), // #1596
- INST(Vshuff64x2 , VexRvmi_Lx , E(660F3A,23,_,x,_,1,4,FV ), 0 , 112, 0 , 11225, 432, 131), // #1597
- INST(Vshufi32x4 , VexRvmi_Lx , E(660F3A,43,_,x,_,0,4,FV ), 0 , 111, 0 , 11236, 431, 131), // #1598
- INST(Vshufi64x2 , VexRvmi_Lx , E(660F3A,43,_,x,_,1,4,FV ), 0 , 112, 0 , 11247, 432, 131), // #1599
- INST(Vshufpd , VexRvmi_Lx , V(660F00,C6,_,x,I,1,4,FV ), 0 , 103, 0 , 11258, 433, 124), // #1600
- INST(Vshufps , VexRvmi_Lx , V(000F00,C6,_,x,I,0,4,FV ), 0 , 105, 0 , 11266, 434, 124), // #1601
- INST(Vsqrtpd , VexRm_Lx , V(660F00,51,_,x,I,1,4,FV ), 0 , 103, 0 , 11274, 435, 124), // #1602
- INST(Vsqrtph , VexRm_Lx , E(00MAP5,51,_,_,_,0,4,FV ), 0 , 104, 0 , 11282, 246, 125), // #1603
- INST(Vsqrtps , VexRm_Lx , V(000F00,51,_,x,I,0,4,FV ), 0 , 105, 0 , 11290, 235, 124), // #1604
- INST(Vsqrtsd , VexRvm , V(F20F00,51,_,I,I,1,3,T1S), 0 , 106, 0 , 11298, 199, 126), // #1605
- INST(Vsqrtsh , VexRvm , E(F3MAP5,51,_,_,_,0,1,T1S), 0 , 107, 0 , 11306, 200, 127), // #1606
- INST(Vsqrtss , VexRvm , V(F30F00,51,_,I,I,0,2,T1S), 0 , 108, 0 , 11314, 201, 126), // #1607
- INST(Vstmxcsr , VexM , V(000F00,AE,3,0,I,_,_,_ ), 0 , 234, 0 , 11322, 321, 128), // #1608
- INST(Vsubpd , VexRvm_Lx , V(660F00,5C,_,x,I,1,4,FV ), 0 , 103, 0 , 11331, 196, 124), // #1609
- INST(Vsubph , VexRvm_Lx , E(00MAP5,5C,_,_,_,0,4,FV ), 0 , 104, 0 , 11338, 197, 125), // #1610
- INST(Vsubps , VexRvm_Lx , V(000F00,5C,_,x,I,0,4,FV ), 0 , 105, 0 , 11345, 198, 124), // #1611
- INST(Vsubsd , VexRvm , V(F20F00,5C,_,I,I,1,3,T1S), 0 , 106, 0 , 11352, 199, 126), // #1612
- INST(Vsubsh , VexRvm , E(F3MAP5,5C,_,_,_,0,1,T1S), 0 , 107, 0 , 11359, 200, 127), // #1613
- INST(Vsubss , VexRvm , V(F30F00,5C,_,I,I,0,2,T1S), 0 , 108, 0 , 11366, 201, 126), // #1614
- INST(Vtestpd , VexRm_Lx , V(660F38,0F,_,x,0,_,_,_ ), 0 , 96 , 0 , 11373, 298, 155), // #1615
- INST(Vtestps , VexRm_Lx , V(660F38,0E,_,x,0,_,_,_ ), 0 , 96 , 0 , 11381, 298, 155), // #1616
- INST(Vucomisd , VexRm , V(660F00,2E,_,I,I,1,3,T1S), 0 , 125, 0 , 11389, 229, 136), // #1617
- INST(Vucomish , VexRm , E(00MAP5,2E,_,_,_,0,1,T1S), 0 , 126, 0 , 11398, 230, 127), // #1618
- INST(Vucomiss , VexRm , V(000F00,2E,_,I,I,0,2,T1S), 0 , 127, 0 , 11407, 231, 136), // #1619
- INST(Vunpckhpd , VexRvm_Lx , V(660F00,15,_,x,I,1,4,FV ), 0 , 103, 0 , 11416, 208, 124), // #1620
- INST(Vunpckhps , VexRvm_Lx , V(000F00,15,_,x,I,0,4,FV ), 0 , 105, 0 , 11426, 209, 124), // #1621
- INST(Vunpcklpd , VexRvm_Lx , V(660F00,14,_,x,I,1,4,FV ), 0 , 103, 0 , 11436, 208, 124), // #1622
- INST(Vunpcklps , VexRvm_Lx , V(000F00,14,_,x,I,0,4,FV ), 0 , 105, 0 , 11446, 209, 124), // #1623
- INST(Vxorpd , VexRvm_Lx , V(660F00,57,_,x,I,1,4,FV ), 0 , 103, 0 , 11456, 417, 132), // #1624
- INST(Vxorps , VexRvm_Lx , V(000F00,57,_,x,I,0,4,FV ), 0 , 105, 0 , 11463, 416, 132), // #1625
- INST(Vzeroall , VexOp , V(000F00,77,_,1,I,_,_,_ ), 0 , 68 , 0 , 11470, 436, 128), // #1626
- INST(Vzeroupper , VexOp , V(000F00,77,_,0,I,_,_,_ ), 0 , 72 , 0 , 11479, 436, 128), // #1627
- INST(Wbinvd , X86Op , O(000F00,09,_,_,_,_,_,_ ), 0 , 4 , 0 , 11490, 30 , 0 ), // #1628
- INST(Wbnoinvd , X86Op , O(F30F00,09,_,_,_,_,_,_ ), 0 , 6 , 0 , 11497, 30 , 164), // #1629
- INST(Wrfsbase , X86M , O(F30F00,AE,2,_,x,_,_,_ ), 0 , 235, 0 , 11506, 173, 104), // #1630
- INST(Wrgsbase , X86M , O(F30F00,AE,3,_,x,_,_,_ ), 0 , 236, 0 , 11515, 173, 104), // #1631
- INST(Wrmsr , X86Op , O(000F00,30,_,_,_,_,_,_ ), 0 , 4 , 0 , 11524, 174, 105), // #1632
- INST(Wrssd , X86Mr , O(000F38,F6,_,_,_,_,_,_ ), 0 , 83 , 0 , 11530, 437, 56 ), // #1633
- INST(Wrssq , X86Mr , O(000F38,F6,_,_,1,_,_,_ ), 0 , 237, 0 , 11536, 438, 56 ), // #1634
- INST(Wrussd , X86Mr , O(660F38,F5,_,_,_,_,_,_ ), 0 , 2 , 0 , 11542, 437, 56 ), // #1635
- INST(Wrussq , X86Mr , O(660F38,F5,_,_,1,_,_,_ ), 0 , 238, 0 , 11549, 438, 56 ), // #1636
- INST(Xabort , X86Op_Mod11RM_I8 , O(000000,C6,7,_,_,_,_,_ ), 0 , 27 , 0 , 11556, 80 , 165), // #1637
- INST(Xadd , X86Xadd , O(000F00,C0,_,_,x,_,_,_ ), 0 , 4 , 0 , 11563, 439, 38 ), // #1638
- INST(Xbegin , X86JmpRel , O(000000,C7,7,_,_,_,_,_ ), 0 , 27 , 0 , 11568, 440, 165), // #1639
- INST(Xchg , X86Xchg , O(000000,86,_,_,x,_,_,_ ), 0 , 0 , 0 , 462 , 441, 0 ), // #1640
- INST(Xend , X86Op , O(000F01,D5,_,_,_,_,_,_ ), 0 , 21 , 0 , 11575, 30 , 165), // #1641
- INST(Xgetbv , X86Op , O(000F01,D0,_,_,_,_,_,_ ), 0 , 21 , 0 , 11580, 174, 166), // #1642
- INST(Xlatb , X86Op , O(000000,D7,_,_,_,_,_,_ ), 0 , 0 , 0 , 11587, 30 , 0 ), // #1643
- INST(Xor , X86Arith , O(000000,30,6,_,x,_,_,_ ), 0 , 32 , 0 , 10540, 179, 1 ), // #1644
- INST(Xorpd , ExtRm , O(660F00,57,_,_,_,_,_,_ ), 0 , 3 , 0 , 11457, 151, 4 ), // #1645
- INST(Xorps , ExtRm , O(000F00,57,_,_,_,_,_,_ ), 0 , 4 , 0 , 11464, 151, 5 ), // #1646
- INST(Xresldtrk , X86Op , O(F20F01,E9,_,_,_,_,_,_ ), 0 , 92 , 0 , 11593, 30 , 167), // #1647
- INST(Xrstor , X86M_Only_EDX_EAX , O(000F00,AE,5,_,_,_,_,_ ), 0 , 77 , 0 , 1164 , 442, 166), // #1648
- INST(Xrstor64 , X86M_Only_EDX_EAX , O(000F00,AE,5,_,1,_,_,_ ), 0 , 239, 0 , 1172 , 443, 166), // #1649
- INST(Xrstors , X86M_Only_EDX_EAX , O(000F00,C7,3,_,_,_,_,_ ), 0 , 78 , 0 , 11603, 442, 168), // #1650
- INST(Xrstors64 , X86M_Only_EDX_EAX , O(000F00,C7,3,_,1,_,_,_ ), 0 , 240, 0 , 11611, 443, 168), // #1651
- INST(Xsave , X86M_Only_EDX_EAX , O(000F00,AE,4,_,_,_,_,_ ), 0 , 97 , 0 , 1182 , 442, 166), // #1652
- INST(Xsave64 , X86M_Only_EDX_EAX , O(000F00,AE,4,_,1,_,_,_ ), 0 , 241, 0 , 1189 , 443, 166), // #1653
- INST(Xsavec , X86M_Only_EDX_EAX , O(000F00,C7,4,_,_,_,_,_ ), 0 , 97 , 0 , 11621, 442, 169), // #1654
- INST(Xsavec64 , X86M_Only_EDX_EAX , O(000F00,C7,4,_,1,_,_,_ ), 0 , 241, 0 , 11628, 443, 169), // #1655
- INST(Xsaveopt , X86M_Only_EDX_EAX , O(000F00,AE,6,_,_,_,_,_ ), 0 , 80 , 0 , 11637, 442, 170), // #1656
- INST(Xsaveopt64 , X86M_Only_EDX_EAX , O(000F00,AE,6,_,1,_,_,_ ), 0 , 242, 0 , 11646, 443, 170), // #1657
- INST(Xsaves , X86M_Only_EDX_EAX , O(000F00,C7,5,_,_,_,_,_ ), 0 , 77 , 0 , 11657, 442, 168), // #1658
- INST(Xsaves64 , X86M_Only_EDX_EAX , O(000F00,C7,5,_,1,_,_,_ ), 0 , 239, 0 , 11664, 443, 168), // #1659
- INST(Xsetbv , X86Op , O(000F01,D1,_,_,_,_,_,_ ), 0 , 21 , 0 , 11673, 174, 166), // #1660
- INST(Xsusldtrk , X86Op , O(F20F01,E8,_,_,_,_,_,_ ), 0 , 92 , 0 , 11680, 30 , 167), // #1661
- INST(Xtest , X86Op , O(000F01,D6,_,_,_,_,_,_ ), 0 , 21 , 0 , 11690, 30 , 171) // #1662
+ INST(None , None , 0 , 0 , 0 , 0 , 0 , 0 ), // #0
+ INST(Aaa , X86Op_xAX , O(000000,37,_,_,_,_,_,_ ), 0 , 0 , 0 , 1 , 1 ), // #1
+ INST(Aad , X86I_xAX , O(000000,D5,_,_,_,_,_,_ ), 0 , 0 , 0 , 2 , 1 ), // #2
+ INST(Aam , X86I_xAX , O(000000,D4,_,_,_,_,_,_ ), 0 , 0 , 0 , 2 , 1 ), // #3
+ INST(Aas , X86Op_xAX , O(000000,3F,_,_,_,_,_,_ ), 0 , 0 , 0 , 1 , 1 ), // #4
+ INST(Adc , X86Arith , O(000000,10,2,_,x,_,_,_ ), 0 , 1 , 0 , 3 , 2 ), // #5
+ INST(Adcx , X86Rm , O(660F38,F6,_,_,x,_,_,_ ), 0 , 2 , 0 , 4 , 3 ), // #6
+ INST(Add , X86Arith , O(000000,00,0,_,x,_,_,_ ), 0 , 0 , 0 , 3 , 1 ), // #7
+ INST(Addpd , ExtRm , O(660F00,58,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #8
+ INST(Addps , ExtRm , O(000F00,58,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #9
+ INST(Addsd , ExtRm , O(F20F00,58,_,_,_,_,_,_ ), 0 , 5 , 0 , 6 , 4 ), // #10
+ INST(Addss , ExtRm , O(F30F00,58,_,_,_,_,_,_ ), 0 , 6 , 0 , 7 , 5 ), // #11
+ INST(Addsubpd , ExtRm , O(660F00,D0,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 6 ), // #12
+ INST(Addsubps , ExtRm , O(F20F00,D0,_,_,_,_,_,_ ), 0 , 5 , 0 , 5 , 6 ), // #13
+ INST(Adox , X86Rm , O(F30F38,F6,_,_,x,_,_,_ ), 0 , 7 , 0 , 4 , 7 ), // #14
+ INST(Aesdec , ExtRm , O(660F38,DE,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 8 ), // #15
+ INST(Aesdeclast , ExtRm , O(660F38,DF,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 8 ), // #16
+ INST(Aesenc , ExtRm , O(660F38,DC,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 8 ), // #17
+ INST(Aesenclast , ExtRm , O(660F38,DD,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 8 ), // #18
+ INST(Aesimc , ExtRm , O(660F38,DB,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 8 ), // #19
+ INST(Aeskeygenassist , ExtRmi , O(660F3A,DF,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 8 ), // #20
+ INST(And , X86Arith , O(000000,20,4,_,x,_,_,_ ), 0 , 9 , 0 , 9 , 1 ), // #21
+ INST(Andn , VexRvm_Wx , V(000F38,F2,_,0,x,_,_,_ ), 0 , 10 , 0 , 10 , 9 ), // #22
+ INST(Andnpd , ExtRm , O(660F00,55,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #23
+ INST(Andnps , ExtRm , O(000F00,55,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #24
+ INST(Andpd , ExtRm , O(660F00,54,_,_,_,_,_,_ ), 0 , 3 , 0 , 11 , 4 ), // #25
+ INST(Andps , ExtRm , O(000F00,54,_,_,_,_,_,_ ), 0 , 4 , 0 , 11 , 5 ), // #26
+ INST(Arpl , X86Mr_NoSize , O(000000,63,_,_,_,_,_,_ ), 0 , 0 , 0 , 12 , 10 ), // #27
+ INST(Bextr , VexRmv_Wx , V(000F38,F7,_,0,x,_,_,_ ), 0 , 10 , 0 , 13 , 9 ), // #28
+ INST(Blcfill , VexVm_Wx , V(XOP_M9,01,1,0,x,_,_,_ ), 0 , 11 , 0 , 14 , 11 ), // #29
+ INST(Blci , VexVm_Wx , V(XOP_M9,02,6,0,x,_,_,_ ), 0 , 12 , 0 , 14 , 11 ), // #30
+ INST(Blcic , VexVm_Wx , V(XOP_M9,01,5,0,x,_,_,_ ), 0 , 13 , 0 , 14 , 11 ), // #31
+ INST(Blcmsk , VexVm_Wx , V(XOP_M9,02,1,0,x,_,_,_ ), 0 , 11 , 0 , 14 , 11 ), // #32
+ INST(Blcs , VexVm_Wx , V(XOP_M9,01,3,0,x,_,_,_ ), 0 , 14 , 0 , 14 , 11 ), // #33
+ INST(Blendpd , ExtRmi , O(660F3A,0D,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 12 ), // #34
+ INST(Blendps , ExtRmi , O(660F3A,0C,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 12 ), // #35
+ INST(Blendvpd , ExtRm_XMM0 , O(660F38,15,_,_,_,_,_,_ ), 0 , 2 , 0 , 15 , 12 ), // #36
+ INST(Blendvps , ExtRm_XMM0 , O(660F38,14,_,_,_,_,_,_ ), 0 , 2 , 0 , 15 , 12 ), // #37
+ INST(Blsfill , VexVm_Wx , V(XOP_M9,01,2,0,x,_,_,_ ), 0 , 15 , 0 , 14 , 11 ), // #38
+ INST(Blsi , VexVm_Wx , V(000F38,F3,3,0,x,_,_,_ ), 0 , 16 , 0 , 14 , 9 ), // #39
+ INST(Blsic , VexVm_Wx , V(XOP_M9,01,6,0,x,_,_,_ ), 0 , 12 , 0 , 14 , 11 ), // #40
+ INST(Blsmsk , VexVm_Wx , V(000F38,F3,2,0,x,_,_,_ ), 0 , 17 , 0 , 14 , 9 ), // #41
+ INST(Blsr , VexVm_Wx , V(000F38,F3,1,0,x,_,_,_ ), 0 , 18 , 0 , 14 , 9 ), // #42
+ INST(Bndcl , X86Rm , O(F30F00,1A,_,_,_,_,_,_ ), 0 , 6 , 0 , 16 , 13 ), // #43
+ INST(Bndcn , X86Rm , O(F20F00,1B,_,_,_,_,_,_ ), 0 , 5 , 0 , 16 , 13 ), // #44
+ INST(Bndcu , X86Rm , O(F20F00,1A,_,_,_,_,_,_ ), 0 , 5 , 0 , 16 , 13 ), // #45
+ INST(Bndldx , X86Rm , O(000F00,1A,_,_,_,_,_,_ ), 0 , 4 , 0 , 17 , 13 ), // #46
+ INST(Bndmk , X86Rm , O(F30F00,1B,_,_,_,_,_,_ ), 0 , 6 , 0 , 18 , 13 ), // #47
+ INST(Bndmov , X86Bndmov , O(660F00,1A,_,_,_,_,_,_ ), O(660F00,1B,_,_,_,_,_,_ ), 3 , 1 , 19 , 13 ), // #48
+ INST(Bndstx , X86Mr , O(000F00,1B,_,_,_,_,_,_ ), 0 , 4 , 0 , 20 , 13 ), // #49
+ INST(Bound , X86Rm , O(000000,62,_,_,_,_,_,_ ), 0 , 0 , 0 , 21 , 0 ), // #50
+ INST(Bsf , X86Rm , O(000F00,BC,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 1 ), // #51
+ INST(Bsr , X86Rm , O(000F00,BD,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 1 ), // #52
+ INST(Bswap , X86Bswap , O(000F00,C8,_,_,x,_,_,_ ), 0 , 4 , 0 , 23 , 0 ), // #53
+ INST(Bt , X86Bt , O(000F00,A3,_,_,x,_,_,_ ), O(000F00,BA,4,_,x,_,_,_ ), 4 , 2 , 24 , 14 ), // #54
+ INST(Btc , X86Bt , O(000F00,BB,_,_,x,_,_,_ ), O(000F00,BA,7,_,x,_,_,_ ), 4 , 3 , 25 , 14 ), // #55
+ INST(Btr , X86Bt , O(000F00,B3,_,_,x,_,_,_ ), O(000F00,BA,6,_,x,_,_,_ ), 4 , 4 , 25 , 14 ), // #56
+ INST(Bts , X86Bt , O(000F00,AB,_,_,x,_,_,_ ), O(000F00,BA,5,_,x,_,_,_ ), 4 , 5 , 25 , 14 ), // #57
+ INST(Bzhi , VexRmv_Wx , V(000F38,F5,_,0,x,_,_,_ ), 0 , 10 , 0 , 13 , 15 ), // #58
+ INST(Call , X86Call , O(000000,FF,2,_,_,_,_,_ ), 0 , 1 , 0 , 26 , 1 ), // #59
+ INST(Cbw , X86Op_xAX , O(660000,98,_,_,_,_,_,_ ), 0 , 19 , 0 , 27 , 0 ), // #60
+ INST(Cdq , X86Op_xDX_xAX , O(000000,99,_,_,_,_,_,_ ), 0 , 0 , 0 , 28 , 0 ), // #61
+ INST(Cdqe , X86Op_xAX , O(000000,98,_,_,1,_,_,_ ), 0 , 20 , 0 , 29 , 0 ), // #62
+ INST(Clac , X86Op , O(000F01,CA,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 16 ), // #63
+ INST(Clc , X86Op , O(000000,F8,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 17 ), // #64
+ INST(Cld , X86Op , O(000000,FC,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 18 ), // #65
+ INST(Cldemote , X86M_Only , O(000F00,1C,0,_,_,_,_,_ ), 0 , 4 , 0 , 31 , 19 ), // #66
+ INST(Clflush , X86M_Only , O(000F00,AE,7,_,_,_,_,_ ), 0 , 22 , 0 , 31 , 20 ), // #67
+ INST(Clflushopt , X86M_Only , O(660F00,AE,7,_,_,_,_,_ ), 0 , 23 , 0 , 31 , 21 ), // #68
+ INST(Clgi , X86Op , O(000F01,DD,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 22 ), // #69
+ INST(Cli , X86Op , O(000000,FA,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 23 ), // #70
+ INST(Clrssbsy , X86M_Only , O(F30F00,AE,6,_,_,_,_,_ ), 0 , 24 , 0 , 32 , 24 ), // #71
+ INST(Clts , X86Op , O(000F00,06,_,_,_,_,_,_ ), 0 , 4 , 0 , 30 , 0 ), // #72
+ INST(Clui , X86Op , O(F30F01,EE,_,_,_,_,_,_ ), 0 , 25 , 0 , 33 , 25 ), // #73
+ INST(Clwb , X86M_Only , O(660F00,AE,6,_,_,_,_,_ ), 0 , 26 , 0 , 31 , 26 ), // #74
+ INST(Clzero , X86Op_MemZAX , O(000F01,FC,_,_,_,_,_,_ ), 0 , 21 , 0 , 34 , 27 ), // #75
+ INST(Cmc , X86Op , O(000000,F5,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 28 ), // #76
+ INST(Cmova , X86Rm , O(000F00,47,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 29 ), // #77
+ INST(Cmovae , X86Rm , O(000F00,43,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 30 ), // #78
+ INST(Cmovb , X86Rm , O(000F00,42,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 30 ), // #79
+ INST(Cmovbe , X86Rm , O(000F00,46,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 29 ), // #80
+ INST(Cmovc , X86Rm , O(000F00,42,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 30 ), // #81
+ INST(Cmove , X86Rm , O(000F00,44,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 31 ), // #82
+ INST(Cmovg , X86Rm , O(000F00,4F,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 32 ), // #83
+ INST(Cmovge , X86Rm , O(000F00,4D,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 33 ), // #84
+ INST(Cmovl , X86Rm , O(000F00,4C,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 33 ), // #85
+ INST(Cmovle , X86Rm , O(000F00,4E,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 32 ), // #86
+ INST(Cmovna , X86Rm , O(000F00,46,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 29 ), // #87
+ INST(Cmovnae , X86Rm , O(000F00,42,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 30 ), // #88
+ INST(Cmovnb , X86Rm , O(000F00,43,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 30 ), // #89
+ INST(Cmovnbe , X86Rm , O(000F00,47,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 29 ), // #90
+ INST(Cmovnc , X86Rm , O(000F00,43,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 30 ), // #91
+ INST(Cmovne , X86Rm , O(000F00,45,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 31 ), // #92
+ INST(Cmovng , X86Rm , O(000F00,4E,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 32 ), // #93
+ INST(Cmovnge , X86Rm , O(000F00,4C,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 33 ), // #94
+ INST(Cmovnl , X86Rm , O(000F00,4D,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 33 ), // #95
+ INST(Cmovnle , X86Rm , O(000F00,4F,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 32 ), // #96
+ INST(Cmovno , X86Rm , O(000F00,41,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 34 ), // #97
+ INST(Cmovnp , X86Rm , O(000F00,4B,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 35 ), // #98
+ INST(Cmovns , X86Rm , O(000F00,49,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 36 ), // #99
+ INST(Cmovnz , X86Rm , O(000F00,45,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 31 ), // #100
+ INST(Cmovo , X86Rm , O(000F00,40,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 34 ), // #101
+ INST(Cmovp , X86Rm , O(000F00,4A,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 35 ), // #102
+ INST(Cmovpe , X86Rm , O(000F00,4A,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 35 ), // #103
+ INST(Cmovpo , X86Rm , O(000F00,4B,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 35 ), // #104
+ INST(Cmovs , X86Rm , O(000F00,48,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 36 ), // #105
+ INST(Cmovz , X86Rm , O(000F00,44,_,_,x,_,_,_ ), 0 , 4 , 0 , 22 , 31 ), // #106
+ INST(Cmp , X86Arith , O(000000,38,7,_,x,_,_,_ ), 0 , 27 , 0 , 35 , 1 ), // #107
+ INST(Cmppd , ExtRmi , O(660F00,C2,_,_,_,_,_,_ ), 0 , 3 , 0 , 8 , 4 ), // #108
+ INST(Cmpps , ExtRmi , O(000F00,C2,_,_,_,_,_,_ ), 0 , 4 , 0 , 8 , 5 ), // #109
+ INST(Cmps , X86StrMm , O(000000,A6,_,_,_,_,_,_ ), 0 , 0 , 0 , 36 , 37 ), // #110
+ INST(Cmpsd , ExtRmi , O(F20F00,C2,_,_,_,_,_,_ ), 0 , 5 , 0 , 37 , 4 ), // #111
+ INST(Cmpss , ExtRmi , O(F30F00,C2,_,_,_,_,_,_ ), 0 , 6 , 0 , 38 , 5 ), // #112
+ INST(Cmpxchg , X86Cmpxchg , O(000F00,B0,_,_,x,_,_,_ ), 0 , 4 , 0 , 39 , 38 ), // #113
+ INST(Cmpxchg16b , X86Cmpxchg8b_16b , O(000F00,C7,1,_,1,_,_,_ ), 0 , 28 , 0 , 40 , 39 ), // #114
+ INST(Cmpxchg8b , X86Cmpxchg8b_16b , O(000F00,C7,1,_,_,_,_,_ ), 0 , 29 , 0 , 41 , 40 ), // #115
+ INST(Comisd , ExtRm , O(660F00,2F,_,_,_,_,_,_ ), 0 , 3 , 0 , 6 , 41 ), // #116
+ INST(Comiss , ExtRm , O(000F00,2F,_,_,_,_,_,_ ), 0 , 4 , 0 , 7 , 42 ), // #117
+ INST(Cpuid , X86Op , O(000F00,A2,_,_,_,_,_,_ ), 0 , 4 , 0 , 42 , 43 ), // #118
+ INST(Cqo , X86Op_xDX_xAX , O(000000,99,_,_,1,_,_,_ ), 0 , 20 , 0 , 43 , 0 ), // #119
+ INST(Crc32 , X86Crc , O(F20F38,F0,_,_,x,_,_,_ ), 0 , 30 , 0 , 44 , 44 ), // #120
+ INST(Cvtdq2pd , ExtRm , O(F30F00,E6,_,_,_,_,_,_ ), 0 , 6 , 0 , 6 , 4 ), // #121
+ INST(Cvtdq2ps , ExtRm , O(000F00,5B,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 4 ), // #122
+ INST(Cvtpd2dq , ExtRm , O(F20F00,E6,_,_,_,_,_,_ ), 0 , 5 , 0 , 5 , 4 ), // #123
+ INST(Cvtpd2pi , ExtRm , O(660F00,2D,_,_,_,_,_,_ ), 0 , 3 , 0 , 45 , 4 ), // #124
+ INST(Cvtpd2ps , ExtRm , O(660F00,5A,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #125
+ INST(Cvtpi2pd , ExtRm , O(660F00,2A,_,_,_,_,_,_ ), 0 , 3 , 0 , 46 , 4 ), // #126
+ INST(Cvtpi2ps , ExtRm , O(000F00,2A,_,_,_,_,_,_ ), 0 , 4 , 0 , 46 , 5 ), // #127
+ INST(Cvtps2dq , ExtRm , O(660F00,5B,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #128
+ INST(Cvtps2pd , ExtRm , O(000F00,5A,_,_,_,_,_,_ ), 0 , 4 , 0 , 6 , 4 ), // #129
+ INST(Cvtps2pi , ExtRm , O(000F00,2D,_,_,_,_,_,_ ), 0 , 4 , 0 , 47 , 5 ), // #130
+ INST(Cvtsd2si , ExtRm_Wx_GpqOnly , O(F20F00,2D,_,_,x,_,_,_ ), 0 , 5 , 0 , 48 , 4 ), // #131
+ INST(Cvtsd2ss , ExtRm , O(F20F00,5A,_,_,_,_,_,_ ), 0 , 5 , 0 , 6 , 4 ), // #132
+ INST(Cvtsi2sd , ExtRm_Wx , O(F20F00,2A,_,_,x,_,_,_ ), 0 , 5 , 0 , 49 , 4 ), // #133
+ INST(Cvtsi2ss , ExtRm_Wx , O(F30F00,2A,_,_,x,_,_,_ ), 0 , 6 , 0 , 49 , 5 ), // #134
+ INST(Cvtss2sd , ExtRm , O(F30F00,5A,_,_,_,_,_,_ ), 0 , 6 , 0 , 7 , 4 ), // #135
+ INST(Cvtss2si , ExtRm_Wx_GpqOnly , O(F30F00,2D,_,_,x,_,_,_ ), 0 , 6 , 0 , 50 , 5 ), // #136
+ INST(Cvttpd2dq , ExtRm , O(660F00,E6,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #137
+ INST(Cvttpd2pi , ExtRm , O(660F00,2C,_,_,_,_,_,_ ), 0 , 3 , 0 , 45 , 4 ), // #138
+ INST(Cvttps2dq , ExtRm , O(F30F00,5B,_,_,_,_,_,_ ), 0 , 6 , 0 , 5 , 4 ), // #139
+ INST(Cvttps2pi , ExtRm , O(000F00,2C,_,_,_,_,_,_ ), 0 , 4 , 0 , 47 , 5 ), // #140
+ INST(Cvttsd2si , ExtRm_Wx_GpqOnly , O(F20F00,2C,_,_,x,_,_,_ ), 0 , 5 , 0 , 48 , 4 ), // #141
+ INST(Cvttss2si , ExtRm_Wx_GpqOnly , O(F30F00,2C,_,_,x,_,_,_ ), 0 , 6 , 0 , 50 , 5 ), // #142
+ INST(Cwd , X86Op_xDX_xAX , O(660000,99,_,_,_,_,_,_ ), 0 , 19 , 0 , 51 , 0 ), // #143
+ INST(Cwde , X86Op_xAX , O(000000,98,_,_,_,_,_,_ ), 0 , 0 , 0 , 52 , 0 ), // #144
+ INST(Daa , X86Op , O(000000,27,_,_,_,_,_,_ ), 0 , 0 , 0 , 1 , 1 ), // #145
+ INST(Das , X86Op , O(000000,2F,_,_,_,_,_,_ ), 0 , 0 , 0 , 1 , 1 ), // #146
+ INST(Dec , X86IncDec , O(000000,FE,1,_,x,_,_,_ ), O(000000,48,_,_,x,_,_,_ ), 31 , 6 , 53 , 45 ), // #147
+ INST(Div , X86M_GPB_MulDiv , O(000000,F6,6,_,x,_,_,_ ), 0 , 32 , 0 , 54 , 1 ), // #148
+ INST(Divpd , ExtRm , O(660F00,5E,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #149
+ INST(Divps , ExtRm , O(000F00,5E,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #150
+ INST(Divsd , ExtRm , O(F20F00,5E,_,_,_,_,_,_ ), 0 , 5 , 0 , 6 , 4 ), // #151
+ INST(Divss , ExtRm , O(F30F00,5E,_,_,_,_,_,_ ), 0 , 6 , 0 , 7 , 5 ), // #152
+ INST(Dppd , ExtRmi , O(660F3A,41,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 12 ), // #153
+ INST(Dpps , ExtRmi , O(660F3A,40,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 12 ), // #154
+ INST(Emms , X86Op , O(000F00,77,_,_,_,_,_,_ ), 0 , 4 , 0 , 55 , 46 ), // #155
+ INST(Endbr32 , X86Op_Mod11RM , O(F30F00,1E,7,_,_,_,_,3 ), 0 , 33 , 0 , 30 , 47 ), // #156
+ INST(Endbr64 , X86Op_Mod11RM , O(F30F00,1E,7,_,_,_,_,2 ), 0 , 34 , 0 , 30 , 47 ), // #157
+ INST(Enqcmd , X86EnqcmdMovdir64b , O(F20F38,F8,_,_,_,_,_,_ ), 0 , 30 , 0 , 56 , 48 ), // #158
+ INST(Enqcmds , X86EnqcmdMovdir64b , O(F30F38,F8,_,_,_,_,_,_ ), 0 , 7 , 0 , 56 , 48 ), // #159
+ INST(Enter , X86Enter , O(000000,C8,_,_,_,_,_,_ ), 0 , 0 , 0 , 57 , 0 ), // #160
+ INST(Extractps , ExtExtract , O(660F3A,17,_,_,_,_,_,_ ), 0 , 8 , 0 , 58 , 12 ), // #161
+ INST(Extrq , ExtExtrq , O(660F00,79,_,_,_,_,_,_ ), O(660F00,78,0,_,_,_,_,_ ), 3 , 7 , 59 , 49 ), // #162
+ INST(F2xm1 , FpuOp , O_FPU(00,D9F0,_) , 0 , 35 , 0 , 30 , 0 ), // #163
+ INST(Fabs , FpuOp , O_FPU(00,D9E1,_) , 0 , 35 , 0 , 30 , 0 ), // #164
+ INST(Fadd , FpuArith , O_FPU(00,C0C0,0) , 0 , 36 , 0 , 60 , 0 ), // #165
+ INST(Faddp , FpuRDef , O_FPU(00,DEC0,_) , 0 , 37 , 0 , 61 , 0 ), // #166
+ INST(Fbld , X86M_Only , O_FPU(00,00DF,4) , 0 , 38 , 0 , 62 , 0 ), // #167
+ INST(Fbstp , X86M_Only , O_FPU(00,00DF,6) , 0 , 39 , 0 , 62 , 0 ), // #168
+ INST(Fchs , FpuOp , O_FPU(00,D9E0,_) , 0 , 35 , 0 , 30 , 0 ), // #169
+ INST(Fclex , FpuOp , O_FPU(9B,DBE2,_) , 0 , 40 , 0 , 30 , 0 ), // #170
+ INST(Fcmovb , FpuR , O_FPU(00,DAC0,_) , 0 , 41 , 0 , 63 , 30 ), // #171
+ INST(Fcmovbe , FpuR , O_FPU(00,DAD0,_) , 0 , 41 , 0 , 63 , 29 ), // #172
+ INST(Fcmove , FpuR , O_FPU(00,DAC8,_) , 0 , 41 , 0 , 63 , 31 ), // #173
+ INST(Fcmovnb , FpuR , O_FPU(00,DBC0,_) , 0 , 42 , 0 , 63 , 30 ), // #174
+ INST(Fcmovnbe , FpuR , O_FPU(00,DBD0,_) , 0 , 42 , 0 , 63 , 29 ), // #175
+ INST(Fcmovne , FpuR , O_FPU(00,DBC8,_) , 0 , 42 , 0 , 63 , 31 ), // #176
+ INST(Fcmovnu , FpuR , O_FPU(00,DBD8,_) , 0 , 42 , 0 , 63 , 35 ), // #177
+ INST(Fcmovu , FpuR , O_FPU(00,DAD8,_) , 0 , 41 , 0 , 63 , 35 ), // #178
+ INST(Fcom , FpuCom , O_FPU(00,D0D0,2) , 0 , 43 , 0 , 64 , 0 ), // #179
+ INST(Fcomi , FpuR , O_FPU(00,DBF0,_) , 0 , 42 , 0 , 63 , 50 ), // #180
+ INST(Fcomip , FpuR , O_FPU(00,DFF0,_) , 0 , 44 , 0 , 63 , 50 ), // #181
+ INST(Fcomp , FpuCom , O_FPU(00,D8D8,3) , 0 , 45 , 0 , 64 , 0 ), // #182
+ INST(Fcompp , FpuOp , O_FPU(00,DED9,_) , 0 , 37 , 0 , 30 , 0 ), // #183
+ INST(Fcos , FpuOp , O_FPU(00,D9FF,_) , 0 , 35 , 0 , 30 , 0 ), // #184
+ INST(Fdecstp , FpuOp , O_FPU(00,D9F6,_) , 0 , 35 , 0 , 30 , 0 ), // #185
+ INST(Fdiv , FpuArith , O_FPU(00,F0F8,6) , 0 , 46 , 0 , 60 , 0 ), // #186
+ INST(Fdivp , FpuRDef , O_FPU(00,DEF8,_) , 0 , 37 , 0 , 61 , 0 ), // #187
+ INST(Fdivr , FpuArith , O_FPU(00,F8F0,7) , 0 , 47 , 0 , 60 , 0 ), // #188
+ INST(Fdivrp , FpuRDef , O_FPU(00,DEF0,_) , 0 , 37 , 0 , 61 , 0 ), // #189
+ INST(Femms , X86Op , O(000F00,0E,_,_,_,_,_,_ ), 0 , 4 , 0 , 30 , 51 ), // #190
+ INST(Ffree , FpuR , O_FPU(00,DDC0,_) , 0 , 48 , 0 , 63 , 0 ), // #191
+ INST(Fiadd , FpuM , O_FPU(00,00DA,0) , 0 , 49 , 0 , 65 , 0 ), // #192
+ INST(Ficom , FpuM , O_FPU(00,00DA,2) , 0 , 50 , 0 , 65 , 0 ), // #193
+ INST(Ficomp , FpuM , O_FPU(00,00DA,3) , 0 , 51 , 0 , 65 , 0 ), // #194
+ INST(Fidiv , FpuM , O_FPU(00,00DA,6) , 0 , 39 , 0 , 65 , 0 ), // #195
+ INST(Fidivr , FpuM , O_FPU(00,00DA,7) , 0 , 52 , 0 , 65 , 0 ), // #196
+ INST(Fild , FpuM , O_FPU(00,00DB,0) , O_FPU(00,00DF,5) , 49 , 8 , 66 , 0 ), // #197
+ INST(Fimul , FpuM , O_FPU(00,00DA,1) , 0 , 53 , 0 , 65 , 0 ), // #198
+ INST(Fincstp , FpuOp , O_FPU(00,D9F7,_) , 0 , 35 , 0 , 30 , 0 ), // #199
+ INST(Finit , FpuOp , O_FPU(9B,DBE3,_) , 0 , 40 , 0 , 30 , 0 ), // #200
+ INST(Fist , FpuM , O_FPU(00,00DB,2) , 0 , 50 , 0 , 65 , 0 ), // #201
+ INST(Fistp , FpuM , O_FPU(00,00DB,3) , O_FPU(00,00DF,7) , 51 , 9 , 66 , 0 ), // #202
+ INST(Fisttp , FpuM , O_FPU(00,00DB,1) , O_FPU(00,00DD,1) , 53 , 10 , 66 , 6 ), // #203
+ INST(Fisub , FpuM , O_FPU(00,00DA,4) , 0 , 38 , 0 , 65 , 0 ), // #204
+ INST(Fisubr , FpuM , O_FPU(00,00DA,5) , 0 , 54 , 0 , 65 , 0 ), // #205
+ INST(Fld , FpuFldFst , O_FPU(00,00D9,0) , O_FPU(00,00DB,5) , 49 , 11 , 67 , 0 ), // #206
+ INST(Fld1 , FpuOp , O_FPU(00,D9E8,_) , 0 , 35 , 0 , 30 , 0 ), // #207
+ INST(Fldcw , X86M_Only , O_FPU(00,00D9,5) , 0 , 54 , 0 , 68 , 0 ), // #208
+ INST(Fldenv , X86M_Only , O_FPU(00,00D9,4) , 0 , 38 , 0 , 69 , 0 ), // #209
+ INST(Fldl2e , FpuOp , O_FPU(00,D9EA,_) , 0 , 35 , 0 , 30 , 0 ), // #210
+ INST(Fldl2t , FpuOp , O_FPU(00,D9E9,_) , 0 , 35 , 0 , 30 , 0 ), // #211
+ INST(Fldlg2 , FpuOp , O_FPU(00,D9EC,_) , 0 , 35 , 0 , 30 , 0 ), // #212
+ INST(Fldln2 , FpuOp , O_FPU(00,D9ED,_) , 0 , 35 , 0 , 30 , 0 ), // #213
+ INST(Fldpi , FpuOp , O_FPU(00,D9EB,_) , 0 , 35 , 0 , 30 , 0 ), // #214
+ INST(Fldz , FpuOp , O_FPU(00,D9EE,_) , 0 , 35 , 0 , 30 , 0 ), // #215
+ INST(Fmul , FpuArith , O_FPU(00,C8C8,1) , 0 , 55 , 0 , 60 , 0 ), // #216
+ INST(Fmulp , FpuRDef , O_FPU(00,DEC8,_) , 0 , 37 , 0 , 61 , 0 ), // #217
+ INST(Fnclex , FpuOp , O_FPU(00,DBE2,_) , 0 , 42 , 0 , 30 , 0 ), // #218
+ INST(Fninit , FpuOp , O_FPU(00,DBE3,_) , 0 , 42 , 0 , 30 , 0 ), // #219
+ INST(Fnop , FpuOp , O_FPU(00,D9D0,_) , 0 , 35 , 0 , 30 , 0 ), // #220
+ INST(Fnsave , X86M_Only , O_FPU(00,00DD,6) , 0 , 39 , 0 , 69 , 0 ), // #221
+ INST(Fnstcw , X86M_Only , O_FPU(00,00D9,7) , 0 , 52 , 0 , 68 , 0 ), // #222
+ INST(Fnstenv , X86M_Only , O_FPU(00,00D9,6) , 0 , 39 , 0 , 69 , 0 ), // #223
+ INST(Fnstsw , FpuStsw , O_FPU(00,00DD,7) , O_FPU(00,DFE0,_) , 52 , 12 , 70 , 0 ), // #224
+ INST(Fpatan , FpuOp , O_FPU(00,D9F3,_) , 0 , 35 , 0 , 30 , 0 ), // #225
+ INST(Fprem , FpuOp , O_FPU(00,D9F8,_) , 0 , 35 , 0 , 30 , 0 ), // #226
+ INST(Fprem1 , FpuOp , O_FPU(00,D9F5,_) , 0 , 35 , 0 , 30 , 0 ), // #227
+ INST(Fptan , FpuOp , O_FPU(00,D9F2,_) , 0 , 35 , 0 , 30 , 0 ), // #228
+ INST(Frndint , FpuOp , O_FPU(00,D9FC,_) , 0 , 35 , 0 , 30 , 0 ), // #229
+ INST(Frstor , X86M_Only , O_FPU(00,00DD,4) , 0 , 38 , 0 , 69 , 0 ), // #230
+ INST(Fsave , X86M_Only , O_FPU(9B,00DD,6) , 0 , 56 , 0 , 69 , 0 ), // #231
+ INST(Fscale , FpuOp , O_FPU(00,D9FD,_) , 0 , 35 , 0 , 30 , 0 ), // #232
+ INST(Fsin , FpuOp , O_FPU(00,D9FE,_) , 0 , 35 , 0 , 30 , 0 ), // #233
+ INST(Fsincos , FpuOp , O_FPU(00,D9FB,_) , 0 , 35 , 0 , 30 , 0 ), // #234
+ INST(Fsqrt , FpuOp , O_FPU(00,D9FA,_) , 0 , 35 , 0 , 30 , 0 ), // #235
+ INST(Fst , FpuFldFst , O_FPU(00,00D9,2) , 0 , 50 , 0 , 71 , 0 ), // #236
+ INST(Fstcw , X86M_Only , O_FPU(9B,00D9,7) , 0 , 57 , 0 , 68 , 0 ), // #237
+ INST(Fstenv , X86M_Only , O_FPU(9B,00D9,6) , 0 , 56 , 0 , 69 , 0 ), // #238
+ INST(Fstp , FpuFldFst , O_FPU(00,00D9,3) , O(000000,DB,7,_,_,_,_,_ ), 51 , 13 , 67 , 0 ), // #239
+ INST(Fstsw , FpuStsw , O_FPU(9B,00DD,7) , O_FPU(9B,DFE0,_) , 57 , 14 , 70 , 0 ), // #240
+ INST(Fsub , FpuArith , O_FPU(00,E0E8,4) , 0 , 58 , 0 , 60 , 0 ), // #241
+ INST(Fsubp , FpuRDef , O_FPU(00,DEE8,_) , 0 , 37 , 0 , 61 , 0 ), // #242
+ INST(Fsubr , FpuArith , O_FPU(00,E8E0,5) , 0 , 59 , 0 , 60 , 0 ), // #243
+ INST(Fsubrp , FpuRDef , O_FPU(00,DEE0,_) , 0 , 37 , 0 , 61 , 0 ), // #244
+ INST(Ftst , FpuOp , O_FPU(00,D9E4,_) , 0 , 35 , 0 , 30 , 0 ), // #245
+ INST(Fucom , FpuRDef , O_FPU(00,DDE0,_) , 0 , 48 , 0 , 61 , 0 ), // #246
+ INST(Fucomi , FpuR , O_FPU(00,DBE8,_) , 0 , 42 , 0 , 63 , 50 ), // #247
+ INST(Fucomip , FpuR , O_FPU(00,DFE8,_) , 0 , 44 , 0 , 63 , 50 ), // #248
+ INST(Fucomp , FpuRDef , O_FPU(00,DDE8,_) , 0 , 48 , 0 , 61 , 0 ), // #249
+ INST(Fucompp , FpuOp , O_FPU(00,DAE9,_) , 0 , 41 , 0 , 30 , 0 ), // #250
+ INST(Fwait , X86Op , O_FPU(00,009B,_) , 0 , 49 , 0 , 30 , 0 ), // #251
+ INST(Fxam , FpuOp , O_FPU(00,D9E5,_) , 0 , 35 , 0 , 30 , 0 ), // #252
+ INST(Fxch , FpuR , O_FPU(00,D9C8,_) , 0 , 35 , 0 , 61 , 0 ), // #253
+ INST(Fxrstor , X86M_Only , O(000F00,AE,1,_,_,_,_,_ ), 0 , 29 , 0 , 69 , 52 ), // #254
+ INST(Fxrstor64 , X86M_Only , O(000F00,AE,1,_,1,_,_,_ ), 0 , 28 , 0 , 72 , 52 ), // #255
+ INST(Fxsave , X86M_Only , O(000F00,AE,0,_,_,_,_,_ ), 0 , 4 , 0 , 69 , 52 ), // #256
+ INST(Fxsave64 , X86M_Only , O(000F00,AE,0,_,1,_,_,_ ), 0 , 60 , 0 , 72 , 52 ), // #257
+ INST(Fxtract , FpuOp , O_FPU(00,D9F4,_) , 0 , 35 , 0 , 30 , 0 ), // #258
+ INST(Fyl2x , FpuOp , O_FPU(00,D9F1,_) , 0 , 35 , 0 , 30 , 0 ), // #259
+ INST(Fyl2xp1 , FpuOp , O_FPU(00,D9F9,_) , 0 , 35 , 0 , 30 , 0 ), // #260
+ INST(Getsec , X86Op , O(000F00,37,_,_,_,_,_,_ ), 0 , 4 , 0 , 30 , 53 ), // #261
+ INST(Gf2p8affineinvqb , ExtRmi , O(660F3A,CF,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 54 ), // #262
+ INST(Gf2p8affineqb , ExtRmi , O(660F3A,CE,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 54 ), // #263
+ INST(Gf2p8mulb , ExtRm , O(660F38,CF,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 54 ), // #264
+ INST(Haddpd , ExtRm , O(660F00,7C,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 6 ), // #265
+ INST(Haddps , ExtRm , O(F20F00,7C,_,_,_,_,_,_ ), 0 , 5 , 0 , 5 , 6 ), // #266
+ INST(Hlt , X86Op , O(000000,F4,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 0 ), // #267
+ INST(Hreset , X86Op_Mod11RM_I8 , O(F30F3A,F0,0,_,_,_,_,_ ), 0 , 61 , 0 , 73 , 55 ), // #268
+ INST(Hsubpd , ExtRm , O(660F00,7D,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 6 ), // #269
+ INST(Hsubps , ExtRm , O(F20F00,7D,_,_,_,_,_,_ ), 0 , 5 , 0 , 5 , 6 ), // #270
+ INST(Idiv , X86M_GPB_MulDiv , O(000000,F6,7,_,x,_,_,_ ), 0 , 27 , 0 , 54 , 1 ), // #271
+ INST(Imul , X86Imul , O(000000,F6,5,_,x,_,_,_ ), 0 , 62 , 0 , 74 , 1 ), // #272
+ INST(In , X86In , O(000000,EC,_,_,_,_,_,_ ), O(000000,E4,_,_,_,_,_,_ ), 0 , 15 , 75 , 0 ), // #273
+ INST(Inc , X86IncDec , O(000000,FE,0,_,x,_,_,_ ), O(000000,40,_,_,x,_,_,_ ), 0 , 16 , 53 , 45 ), // #274
+ INST(Incsspd , X86M , O(F30F00,AE,5,_,0,_,_,_ ), 0 , 63 , 0 , 76 , 56 ), // #275
+ INST(Incsspq , X86M , O(F30F00,AE,5,_,1,_,_,_ ), 0 , 64 , 0 , 77 , 56 ), // #276
+ INST(Ins , X86Ins , O(000000,6C,_,_,_,_,_,_ ), 0 , 0 , 0 , 78 , 0 ), // #277
+ INST(Insertps , ExtRmi , O(660F3A,21,_,_,_,_,_,_ ), 0 , 8 , 0 , 38 , 12 ), // #278
+ INST(Insertq , ExtInsertq , O(F20F00,79,_,_,_,_,_,_ ), O(F20F00,78,_,_,_,_,_,_ ), 5 , 17 , 79 , 49 ), // #279
+ INST(Int , X86Int , O(000000,CD,_,_,_,_,_,_ ), 0 , 0 , 0 , 80 , 0 ), // #280
+ INST(Int3 , X86Op , O(000000,CC,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 0 ), // #281
+ INST(Into , X86Op , O(000000,CE,_,_,_,_,_,_ ), 0 , 0 , 0 , 81 , 57 ), // #282
+ INST(Invd , X86Op , O(000F00,08,_,_,_,_,_,_ ), 0 , 4 , 0 , 30 , 43 ), // #283
+ INST(Invept , X86Rm_NoSize , O(660F38,80,_,_,_,_,_,_ ), 0 , 2 , 0 , 82 , 58 ), // #284
+ INST(Invlpg , X86M_Only , O(000F00,01,7,_,_,_,_,_ ), 0 , 22 , 0 , 69 , 43 ), // #285
+ INST(Invlpga , X86Op_xAddr , O(000F01,DF,_,_,_,_,_,_ ), 0 , 21 , 0 , 83 , 22 ), // #286
+ INST(Invpcid , X86Rm_NoSize , O(660F38,82,_,_,_,_,_,_ ), 0 , 2 , 0 , 82 , 43 ), // #287
+ INST(Invvpid , X86Rm_NoSize , O(660F38,81,_,_,_,_,_,_ ), 0 , 2 , 0 , 82 , 58 ), // #288
+ INST(Iret , X86Op , O(660000,CF,_,_,_,_,_,_ ), 0 , 19 , 0 , 84 , 1 ), // #289
+ INST(Iretd , X86Op , O(000000,CF,_,_,_,_,_,_ ), 0 , 0 , 0 , 84 , 1 ), // #290
+ INST(Iretq , X86Op , O(000000,CF,_,_,1,_,_,_ ), 0 , 20 , 0 , 85 , 1 ), // #291
+ INST(Ja , X86Jcc , O(000F00,87,_,_,_,_,_,_ ), O(000000,77,_,_,_,_,_,_ ), 4 , 18 , 86 , 59 ), // #292
+ INST(Jae , X86Jcc , O(000F00,83,_,_,_,_,_,_ ), O(000000,73,_,_,_,_,_,_ ), 4 , 19 , 86 , 60 ), // #293
+ INST(Jb , X86Jcc , O(000F00,82,_,_,_,_,_,_ ), O(000000,72,_,_,_,_,_,_ ), 4 , 20 , 86 , 60 ), // #294
+ INST(Jbe , X86Jcc , O(000F00,86,_,_,_,_,_,_ ), O(000000,76,_,_,_,_,_,_ ), 4 , 21 , 86 , 59 ), // #295
+ INST(Jc , X86Jcc , O(000F00,82,_,_,_,_,_,_ ), O(000000,72,_,_,_,_,_,_ ), 4 , 20 , 86 , 60 ), // #296
+ INST(Je , X86Jcc , O(000F00,84,_,_,_,_,_,_ ), O(000000,74,_,_,_,_,_,_ ), 4 , 22 , 86 , 61 ), // #297
+ INST(Jecxz , X86JecxzLoop , 0 , O(000000,E3,_,_,_,_,_,_ ), 0 , 23 , 87 , 0 ), // #298
+ INST(Jg , X86Jcc , O(000F00,8F,_,_,_,_,_,_ ), O(000000,7F,_,_,_,_,_,_ ), 4 , 24 , 86 , 62 ), // #299
+ INST(Jge , X86Jcc , O(000F00,8D,_,_,_,_,_,_ ), O(000000,7D,_,_,_,_,_,_ ), 4 , 25 , 86 , 63 ), // #300
+ INST(Jl , X86Jcc , O(000F00,8C,_,_,_,_,_,_ ), O(000000,7C,_,_,_,_,_,_ ), 4 , 26 , 86 , 63 ), // #301
+ INST(Jle , X86Jcc , O(000F00,8E,_,_,_,_,_,_ ), O(000000,7E,_,_,_,_,_,_ ), 4 , 27 , 86 , 62 ), // #302
+ INST(Jmp , X86Jmp , O(000000,FF,4,_,_,_,_,_ ), O(000000,EB,_,_,_,_,_,_ ), 9 , 28 , 88 , 0 ), // #303
+ INST(Jna , X86Jcc , O(000F00,86,_,_,_,_,_,_ ), O(000000,76,_,_,_,_,_,_ ), 4 , 21 , 86 , 59 ), // #304
+ INST(Jnae , X86Jcc , O(000F00,82,_,_,_,_,_,_ ), O(000000,72,_,_,_,_,_,_ ), 4 , 20 , 86 , 60 ), // #305
+ INST(Jnb , X86Jcc , O(000F00,83,_,_,_,_,_,_ ), O(000000,73,_,_,_,_,_,_ ), 4 , 19 , 86 , 60 ), // #306
+ INST(Jnbe , X86Jcc , O(000F00,87,_,_,_,_,_,_ ), O(000000,77,_,_,_,_,_,_ ), 4 , 18 , 86 , 59 ), // #307
+ INST(Jnc , X86Jcc , O(000F00,83,_,_,_,_,_,_ ), O(000000,73,_,_,_,_,_,_ ), 4 , 19 , 86 , 60 ), // #308
+ INST(Jne , X86Jcc , O(000F00,85,_,_,_,_,_,_ ), O(000000,75,_,_,_,_,_,_ ), 4 , 29 , 86 , 61 ), // #309
+ INST(Jng , X86Jcc , O(000F00,8E,_,_,_,_,_,_ ), O(000000,7E,_,_,_,_,_,_ ), 4 , 27 , 86 , 62 ), // #310
+ INST(Jnge , X86Jcc , O(000F00,8C,_,_,_,_,_,_ ), O(000000,7C,_,_,_,_,_,_ ), 4 , 26 , 86 , 63 ), // #311
+ INST(Jnl , X86Jcc , O(000F00,8D,_,_,_,_,_,_ ), O(000000,7D,_,_,_,_,_,_ ), 4 , 25 , 86 , 63 ), // #312
+ INST(Jnle , X86Jcc , O(000F00,8F,_,_,_,_,_,_ ), O(000000,7F,_,_,_,_,_,_ ), 4 , 24 , 86 , 62 ), // #313
+ INST(Jno , X86Jcc , O(000F00,81,_,_,_,_,_,_ ), O(000000,71,_,_,_,_,_,_ ), 4 , 30 , 86 , 57 ), // #314
+ INST(Jnp , X86Jcc , O(000F00,8B,_,_,_,_,_,_ ), O(000000,7B,_,_,_,_,_,_ ), 4 , 31 , 86 , 64 ), // #315
+ INST(Jns , X86Jcc , O(000F00,89,_,_,_,_,_,_ ), O(000000,79,_,_,_,_,_,_ ), 4 , 32 , 86 , 65 ), // #316
+ INST(Jnz , X86Jcc , O(000F00,85,_,_,_,_,_,_ ), O(000000,75,_,_,_,_,_,_ ), 4 , 29 , 86 , 61 ), // #317
+ INST(Jo , X86Jcc , O(000F00,80,_,_,_,_,_,_ ), O(000000,70,_,_,_,_,_,_ ), 4 , 33 , 86 , 57 ), // #318
+ INST(Jp , X86Jcc , O(000F00,8A,_,_,_,_,_,_ ), O(000000,7A,_,_,_,_,_,_ ), 4 , 34 , 86 , 64 ), // #319
+ INST(Jpe , X86Jcc , O(000F00,8A,_,_,_,_,_,_ ), O(000000,7A,_,_,_,_,_,_ ), 4 , 34 , 86 , 64 ), // #320
+ INST(Jpo , X86Jcc , O(000F00,8B,_,_,_,_,_,_ ), O(000000,7B,_,_,_,_,_,_ ), 4 , 31 , 86 , 64 ), // #321
+ INST(Js , X86Jcc , O(000F00,88,_,_,_,_,_,_ ), O(000000,78,_,_,_,_,_,_ ), 4 , 35 , 86 , 65 ), // #322
+ INST(Jz , X86Jcc , O(000F00,84,_,_,_,_,_,_ ), O(000000,74,_,_,_,_,_,_ ), 4 , 22 , 86 , 61 ), // #323
+ INST(Kaddb , VexRvm , V(660F00,4A,_,1,0,_,_,_ ), 0 , 65 , 0 , 89 , 66 ), // #324
+ INST(Kaddd , VexRvm , V(660F00,4A,_,1,1,_,_,_ ), 0 , 66 , 0 , 89 , 67 ), // #325
+ INST(Kaddq , VexRvm , V(000F00,4A,_,1,1,_,_,_ ), 0 , 67 , 0 , 89 , 67 ), // #326
+ INST(Kaddw , VexRvm , V(000F00,4A,_,1,0,_,_,_ ), 0 , 68 , 0 , 89 , 66 ), // #327
+ INST(Kandb , VexRvm , V(660F00,41,_,1,0,_,_,_ ), 0 , 65 , 0 , 89 , 66 ), // #328
+ INST(Kandd , VexRvm , V(660F00,41,_,1,1,_,_,_ ), 0 , 66 , 0 , 89 , 67 ), // #329
+ INST(Kandnb , VexRvm , V(660F00,42,_,1,0,_,_,_ ), 0 , 65 , 0 , 89 , 66 ), // #330
+ INST(Kandnd , VexRvm , V(660F00,42,_,1,1,_,_,_ ), 0 , 66 , 0 , 89 , 67 ), // #331
+ INST(Kandnq , VexRvm , V(000F00,42,_,1,1,_,_,_ ), 0 , 67 , 0 , 89 , 67 ), // #332
+ INST(Kandnw , VexRvm , V(000F00,42,_,1,0,_,_,_ ), 0 , 68 , 0 , 89 , 68 ), // #333
+ INST(Kandq , VexRvm , V(000F00,41,_,1,1,_,_,_ ), 0 , 67 , 0 , 89 , 67 ), // #334
+ INST(Kandw , VexRvm , V(000F00,41,_,1,0,_,_,_ ), 0 , 68 , 0 , 89 , 68 ), // #335
+ INST(Kmovb , VexKmov , V(660F00,90,_,0,0,_,_,_ ), V(660F00,92,_,0,0,_,_,_ ), 69 , 36 , 90 , 69 ), // #336
+ INST(Kmovd , VexKmov , V(660F00,90,_,0,1,_,_,_ ), V(F20F00,92,_,0,0,_,_,_ ), 70 , 37 , 91 , 70 ), // #337
+ INST(Kmovq , VexKmov , V(000F00,90,_,0,1,_,_,_ ), V(F20F00,92,_,0,1,_,_,_ ), 71 , 38 , 92 , 70 ), // #338
+ INST(Kmovw , VexKmov , V(000F00,90,_,0,0,_,_,_ ), V(000F00,92,_,0,0,_,_,_ ), 72 , 39 , 93 , 71 ), // #339
+ INST(Knotb , VexRm , V(660F00,44,_,0,0,_,_,_ ), 0 , 69 , 0 , 94 , 66 ), // #340
+ INST(Knotd , VexRm , V(660F00,44,_,0,1,_,_,_ ), 0 , 70 , 0 , 94 , 67 ), // #341
+ INST(Knotq , VexRm , V(000F00,44,_,0,1,_,_,_ ), 0 , 71 , 0 , 94 , 67 ), // #342
+ INST(Knotw , VexRm , V(000F00,44,_,0,0,_,_,_ ), 0 , 72 , 0 , 94 , 68 ), // #343
+ INST(Korb , VexRvm , V(660F00,45,_,1,0,_,_,_ ), 0 , 65 , 0 , 89 , 66 ), // #344
+ INST(Kord , VexRvm , V(660F00,45,_,1,1,_,_,_ ), 0 , 66 , 0 , 89 , 67 ), // #345
+ INST(Korq , VexRvm , V(000F00,45,_,1,1,_,_,_ ), 0 , 67 , 0 , 89 , 67 ), // #346
+ INST(Kortestb , VexRm , V(660F00,98,_,0,0,_,_,_ ), 0 , 69 , 0 , 94 , 72 ), // #347
+ INST(Kortestd , VexRm , V(660F00,98,_,0,1,_,_,_ ), 0 , 70 , 0 , 94 , 73 ), // #348
+ INST(Kortestq , VexRm , V(000F00,98,_,0,1,_,_,_ ), 0 , 71 , 0 , 94 , 73 ), // #349
+ INST(Kortestw , VexRm , V(000F00,98,_,0,0,_,_,_ ), 0 , 72 , 0 , 94 , 74 ), // #350
+ INST(Korw , VexRvm , V(000F00,45,_,1,0,_,_,_ ), 0 , 68 , 0 , 89 , 68 ), // #351
+ INST(Kshiftlb , VexRmi , V(660F3A,32,_,0,0,_,_,_ ), 0 , 73 , 0 , 95 , 66 ), // #352
+ INST(Kshiftld , VexRmi , V(660F3A,33,_,0,0,_,_,_ ), 0 , 73 , 0 , 95 , 67 ), // #353
+ INST(Kshiftlq , VexRmi , V(660F3A,33,_,0,1,_,_,_ ), 0 , 74 , 0 , 95 , 67 ), // #354
+ INST(Kshiftlw , VexRmi , V(660F3A,32,_,0,1,_,_,_ ), 0 , 74 , 0 , 95 , 68 ), // #355
+ INST(Kshiftrb , VexRmi , V(660F3A,30,_,0,0,_,_,_ ), 0 , 73 , 0 , 95 , 66 ), // #356
+ INST(Kshiftrd , VexRmi , V(660F3A,31,_,0,0,_,_,_ ), 0 , 73 , 0 , 95 , 67 ), // #357
+ INST(Kshiftrq , VexRmi , V(660F3A,31,_,0,1,_,_,_ ), 0 , 74 , 0 , 95 , 67 ), // #358
+ INST(Kshiftrw , VexRmi , V(660F3A,30,_,0,1,_,_,_ ), 0 , 74 , 0 , 95 , 68 ), // #359
+ INST(Ktestb , VexRm , V(660F00,99,_,0,0,_,_,_ ), 0 , 69 , 0 , 94 , 72 ), // #360
+ INST(Ktestd , VexRm , V(660F00,99,_,0,1,_,_,_ ), 0 , 70 , 0 , 94 , 73 ), // #361
+ INST(Ktestq , VexRm , V(000F00,99,_,0,1,_,_,_ ), 0 , 71 , 0 , 94 , 73 ), // #362
+ INST(Ktestw , VexRm , V(000F00,99,_,0,0,_,_,_ ), 0 , 72 , 0 , 94 , 72 ), // #363
+ INST(Kunpckbw , VexRvm , V(660F00,4B,_,1,0,_,_,_ ), 0 , 65 , 0 , 89 , 68 ), // #364
+ INST(Kunpckdq , VexRvm , V(000F00,4B,_,1,1,_,_,_ ), 0 , 67 , 0 , 89 , 67 ), // #365
+ INST(Kunpckwd , VexRvm , V(000F00,4B,_,1,0,_,_,_ ), 0 , 68 , 0 , 89 , 67 ), // #366
+ INST(Kxnorb , VexRvm , V(660F00,46,_,1,0,_,_,_ ), 0 , 65 , 0 , 96 , 66 ), // #367
+ INST(Kxnord , VexRvm , V(660F00,46,_,1,1,_,_,_ ), 0 , 66 , 0 , 96 , 67 ), // #368
+ INST(Kxnorq , VexRvm , V(000F00,46,_,1,1,_,_,_ ), 0 , 67 , 0 , 96 , 67 ), // #369
+ INST(Kxnorw , VexRvm , V(000F00,46,_,1,0,_,_,_ ), 0 , 68 , 0 , 96 , 68 ), // #370
+ INST(Kxorb , VexRvm , V(660F00,47,_,1,0,_,_,_ ), 0 , 65 , 0 , 96 , 66 ), // #371
+ INST(Kxord , VexRvm , V(660F00,47,_,1,1,_,_,_ ), 0 , 66 , 0 , 96 , 67 ), // #372
+ INST(Kxorq , VexRvm , V(000F00,47,_,1,1,_,_,_ ), 0 , 67 , 0 , 96 , 67 ), // #373
+ INST(Kxorw , VexRvm , V(000F00,47,_,1,0,_,_,_ ), 0 , 68 , 0 , 96 , 68 ), // #374
+ INST(Lahf , X86Op , O(000000,9F,_,_,_,_,_,_ ), 0 , 0 , 0 , 97 , 75 ), // #375
+ INST(Lar , X86Rm , O(000F00,02,_,_,_,_,_,_ ), 0 , 4 , 0 , 98 , 10 ), // #376
+ INST(Lcall , X86LcallLjmp , O(000000,FF,3,_,_,_,_,_ ), O(000000,9A,_,_,_,_,_,_ ), 75 , 40 , 99 , 1 ), // #377
+ INST(Lddqu , ExtRm , O(F20F00,F0,_,_,_,_,_,_ ), 0 , 5 , 0 , 100, 6 ), // #378
+ INST(Ldmxcsr , X86M_Only , O(000F00,AE,2,_,_,_,_,_ ), 0 , 76 , 0 , 101, 5 ), // #379
+ INST(Lds , X86Rm , O(000000,C5,_,_,_,_,_,_ ), 0 , 0 , 0 , 102, 0 ), // #380
+ INST(Ldtilecfg , AmxCfg , V(000F38,49,_,0,0,_,_,_ ), 0 , 10 , 0 , 103, 76 ), // #381
+ INST(Lea , X86Lea , O(000000,8D,_,_,x,_,_,_ ), 0 , 0 , 0 , 104, 0 ), // #382
+ INST(Leave , X86Op , O(000000,C9,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 0 ), // #383
+ INST(Les , X86Rm , O(000000,C4,_,_,_,_,_,_ ), 0 , 0 , 0 , 102, 0 ), // #384
+ INST(Lfence , X86Fence , O(000F00,AE,5,_,_,_,_,_ ), 0 , 77 , 0 , 30 , 4 ), // #385
+ INST(Lfs , X86Rm , O(000F00,B4,_,_,_,_,_,_ ), 0 , 4 , 0 , 105, 0 ), // #386
+ INST(Lgdt , X86M_Only , O(000F00,01,2,_,_,_,_,_ ), 0 , 76 , 0 , 69 , 0 ), // #387
+ INST(Lgs , X86Rm , O(000F00,B5,_,_,_,_,_,_ ), 0 , 4 , 0 , 105, 0 ), // #388
+ INST(Lidt , X86M_Only , O(000F00,01,3,_,_,_,_,_ ), 0 , 78 , 0 , 69 , 0 ), // #389
+ INST(Ljmp , X86LcallLjmp , O(000000,FF,5,_,_,_,_,_ ), O(000000,EA,_,_,_,_,_,_ ), 62 , 41 , 106, 0 ), // #390
+ INST(Lldt , X86M_NoSize , O(000F00,00,2,_,_,_,_,_ ), 0 , 76 , 0 , 107, 0 ), // #391
+ INST(Llwpcb , VexR_Wx , V(XOP_M9,12,0,0,x,_,_,_ ), 0 , 79 , 0 , 108, 77 ), // #392
+ INST(Lmsw , X86M_NoSize , O(000F00,01,6,_,_,_,_,_ ), 0 , 80 , 0 , 107, 0 ), // #393
+ INST(Lods , X86StrRm , O(000000,AC,_,_,_,_,_,_ ), 0 , 0 , 0 , 109, 78 ), // #394
+ INST(Loop , X86JecxzLoop , 0 , O(000000,E2,_,_,_,_,_,_ ), 0 , 42 , 110, 0 ), // #395
+ INST(Loope , X86JecxzLoop , 0 , O(000000,E1,_,_,_,_,_,_ ), 0 , 43 , 110, 61 ), // #396
+ INST(Loopne , X86JecxzLoop , 0 , O(000000,E0,_,_,_,_,_,_ ), 0 , 44 , 110, 61 ), // #397
+ INST(Lsl , X86Rm , O(000F00,03,_,_,_,_,_,_ ), 0 , 4 , 0 , 111, 10 ), // #398
+ INST(Lss , X86Rm , O(000F00,B2,_,_,_,_,_,_ ), 0 , 4 , 0 , 105, 0 ), // #399
+ INST(Ltr , X86M_NoSize , O(000F00,00,3,_,_,_,_,_ ), 0 , 78 , 0 , 107, 0 ), // #400
+ INST(Lwpins , VexVmi4_Wx , V(XOP_MA,12,0,0,x,_,_,_ ), 0 , 81 , 0 , 112, 77 ), // #401
+ INST(Lwpval , VexVmi4_Wx , V(XOP_MA,12,1,0,x,_,_,_ ), 0 , 82 , 0 , 112, 77 ), // #402
+ INST(Lzcnt , X86Rm_Raw66H , O(F30F00,BD,_,_,x,_,_,_ ), 0 , 6 , 0 , 22 , 79 ), // #403
+ INST(Maskmovdqu , ExtRm_ZDI , O(660F00,F7,_,_,_,_,_,_ ), 0 , 3 , 0 , 113, 4 ), // #404
+ INST(Maskmovq , ExtRm_ZDI , O(000F00,F7,_,_,_,_,_,_ ), 0 , 4 , 0 , 114, 80 ), // #405
+ INST(Maxpd , ExtRm , O(660F00,5F,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #406
+ INST(Maxps , ExtRm , O(000F00,5F,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #407
+ INST(Maxsd , ExtRm , O(F20F00,5F,_,_,_,_,_,_ ), 0 , 5 , 0 , 6 , 4 ), // #408
+ INST(Maxss , ExtRm , O(F30F00,5F,_,_,_,_,_,_ ), 0 , 6 , 0 , 7 , 5 ), // #409
+ INST(Mcommit , X86Op , O(F30F01,FA,_,_,_,_,_,_ ), 0 , 25 , 0 , 30 , 81 ), // #410
+ INST(Mfence , X86Fence , O(000F00,AE,6,_,_,_,_,_ ), 0 , 80 , 0 , 30 , 4 ), // #411
+ INST(Minpd , ExtRm , O(660F00,5D,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #412
+ INST(Minps , ExtRm , O(000F00,5D,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #413
+ INST(Minsd , ExtRm , O(F20F00,5D,_,_,_,_,_,_ ), 0 , 5 , 0 , 6 , 4 ), // #414
+ INST(Minss , ExtRm , O(F30F00,5D,_,_,_,_,_,_ ), 0 , 6 , 0 , 7 , 5 ), // #415
+ INST(Monitor , X86Op , O(000F01,C8,_,_,_,_,_,_ ), 0 , 21 , 0 , 115, 82 ), // #416
+ INST(Monitorx , X86Op , O(000F01,FA,_,_,_,_,_,_ ), 0 , 21 , 0 , 115, 83 ), // #417
+ INST(Mov , X86Mov , 0 , 0 , 0 , 0 , 116, 84 ), // #418
+ INST(Movabs , X86Movabs , 0 , 0 , 0 , 0 , 117, 0 ), // #419
+ INST(Movapd , ExtMov , O(660F00,28,_,_,_,_,_,_ ), O(660F00,29,_,_,_,_,_,_ ), 3 , 45 , 118, 85 ), // #420
+ INST(Movaps , ExtMov , O(000F00,28,_,_,_,_,_,_ ), O(000F00,29,_,_,_,_,_,_ ), 4 , 46 , 118, 86 ), // #421
+ INST(Movbe , ExtMovbe , O(000F38,F0,_,_,x,_,_,_ ), O(000F38,F1,_,_,x,_,_,_ ), 83 , 47 , 119, 87 ), // #422
+ INST(Movd , ExtMovd , O(000F00,6E,_,_,_,_,_,_ ), O(000F00,7E,_,_,_,_,_,_ ), 4 , 48 , 120, 88 ), // #423
+ INST(Movddup , ExtMov , O(F20F00,12,_,_,_,_,_,_ ), 0 , 5 , 0 , 6 , 6 ), // #424
+ INST(Movdir64b , X86EnqcmdMovdir64b , O(660F38,F8,_,_,_,_,_,_ ), 0 , 2 , 0 , 121, 89 ), // #425
+ INST(Movdiri , X86MovntiMovdiri , O(000F38,F9,_,_,_,_,_,_ ), 0 , 83 , 0 , 122, 90 ), // #426
+ INST(Movdq2q , ExtMov , O(F20F00,D6,_,_,_,_,_,_ ), 0 , 5 , 0 , 123, 4 ), // #427
+ INST(Movdqa , ExtMov , O(660F00,6F,_,_,_,_,_,_ ), O(660F00,7F,_,_,_,_,_,_ ), 3 , 49 , 118, 85 ), // #428
+ INST(Movdqu , ExtMov , O(F30F00,6F,_,_,_,_,_,_ ), O(F30F00,7F,_,_,_,_,_,_ ), 6 , 50 , 118, 85 ), // #429
+ INST(Movhlps , ExtMov , O(000F00,12,_,_,_,_,_,_ ), 0 , 4 , 0 , 124, 5 ), // #430
+ INST(Movhpd , ExtMov , O(660F00,16,_,_,_,_,_,_ ), O(660F00,17,_,_,_,_,_,_ ), 3 , 51 , 125, 4 ), // #431
+ INST(Movhps , ExtMov , O(000F00,16,_,_,_,_,_,_ ), O(000F00,17,_,_,_,_,_,_ ), 4 , 52 , 125, 5 ), // #432
+ INST(Movlhps , ExtMov , O(000F00,16,_,_,_,_,_,_ ), 0 , 4 , 0 , 124, 5 ), // #433
+ INST(Movlpd , ExtMov , O(660F00,12,_,_,_,_,_,_ ), O(660F00,13,_,_,_,_,_,_ ), 3 , 53 , 125, 4 ), // #434
+ INST(Movlps , ExtMov , O(000F00,12,_,_,_,_,_,_ ), O(000F00,13,_,_,_,_,_,_ ), 4 , 54 , 125, 5 ), // #435
+ INST(Movmskpd , ExtMov , O(660F00,50,_,_,_,_,_,_ ), 0 , 3 , 0 , 126, 4 ), // #436
+ INST(Movmskps , ExtMov , O(000F00,50,_,_,_,_,_,_ ), 0 , 4 , 0 , 126, 5 ), // #437
+ INST(Movntdq , ExtMov , 0 , O(660F00,E7,_,_,_,_,_,_ ), 0 , 55 , 127, 4 ), // #438
+ INST(Movntdqa , ExtMov , O(660F38,2A,_,_,_,_,_,_ ), 0 , 2 , 0 , 100, 12 ), // #439
+ INST(Movnti , X86MovntiMovdiri , O(000F00,C3,_,_,x,_,_,_ ), 0 , 4 , 0 , 122, 4 ), // #440
+ INST(Movntpd , ExtMov , 0 , O(660F00,2B,_,_,_,_,_,_ ), 0 , 56 , 127, 4 ), // #441
+ INST(Movntps , ExtMov , 0 , O(000F00,2B,_,_,_,_,_,_ ), 0 , 57 , 127, 5 ), // #442
+ INST(Movntq , ExtMov , 0 , O(000F00,E7,_,_,_,_,_,_ ), 0 , 58 , 128, 80 ), // #443
+ INST(Movntsd , ExtMov , 0 , O(F20F00,2B,_,_,_,_,_,_ ), 0 , 59 , 129, 49 ), // #444
+ INST(Movntss , ExtMov , 0 , O(F30F00,2B,_,_,_,_,_,_ ), 0 , 60 , 130, 49 ), // #445
+ INST(Movq , ExtMovq , O(000F00,6E,_,_,x,_,_,_ ), O(000F00,7E,_,_,x,_,_,_ ), 4 , 48 , 131, 91 ), // #446
+ INST(Movq2dq , ExtRm , O(F30F00,D6,_,_,_,_,_,_ ), 0 , 6 , 0 , 132, 4 ), // #447
+ INST(Movs , X86StrMm , O(000000,A4,_,_,_,_,_,_ ), 0 , 0 , 0 , 133, 78 ), // #448
+ INST(Movsd , ExtMov , O(F20F00,10,_,_,_,_,_,_ ), O(F20F00,11,_,_,_,_,_,_ ), 5 , 61 , 134, 85 ), // #449
+ INST(Movshdup , ExtRm , O(F30F00,16,_,_,_,_,_,_ ), 0 , 6 , 0 , 5 , 6 ), // #450
+ INST(Movsldup , ExtRm , O(F30F00,12,_,_,_,_,_,_ ), 0 , 6 , 0 , 5 , 6 ), // #451
+ INST(Movss , ExtMov , O(F30F00,10,_,_,_,_,_,_ ), O(F30F00,11,_,_,_,_,_,_ ), 6 , 62 , 135, 86 ), // #452
+ INST(Movsx , X86MovsxMovzx , O(000F00,BE,_,_,x,_,_,_ ), 0 , 4 , 0 , 136, 0 ), // #453
+ INST(Movsxd , X86Rm , O(000000,63,_,_,x,_,_,_ ), 0 , 0 , 0 , 137, 0 ), // #454
+ INST(Movupd , ExtMov , O(660F00,10,_,_,_,_,_,_ ), O(660F00,11,_,_,_,_,_,_ ), 3 , 63 , 118, 85 ), // #455
+ INST(Movups , ExtMov , O(000F00,10,_,_,_,_,_,_ ), O(000F00,11,_,_,_,_,_,_ ), 4 , 64 , 118, 86 ), // #456
+ INST(Movzx , X86MovsxMovzx , O(000F00,B6,_,_,x,_,_,_ ), 0 , 4 , 0 , 136, 0 ), // #457
+ INST(Mpsadbw , ExtRmi , O(660F3A,42,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 12 ), // #458
+ INST(Mul , X86M_GPB_MulDiv , O(000000,F6,4,_,x,_,_,_ ), 0 , 9 , 0 , 54 , 1 ), // #459
+ INST(Mulpd , ExtRm , O(660F00,59,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #460
+ INST(Mulps , ExtRm , O(000F00,59,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #461
+ INST(Mulsd , ExtRm , O(F20F00,59,_,_,_,_,_,_ ), 0 , 5 , 0 , 6 , 4 ), // #462
+ INST(Mulss , ExtRm , O(F30F00,59,_,_,_,_,_,_ ), 0 , 6 , 0 , 7 , 5 ), // #463
+ INST(Mulx , VexRvm_ZDX_Wx , V(F20F38,F6,_,0,x,_,_,_ ), 0 , 84 , 0 , 138, 92 ), // #464
+ INST(Mwait , X86Op , O(000F01,C9,_,_,_,_,_,_ ), 0 , 21 , 0 , 139, 82 ), // #465
+ INST(Mwaitx , X86Op , O(000F01,FB,_,_,_,_,_,_ ), 0 , 21 , 0 , 140, 83 ), // #466
+ INST(Neg , X86M_GPB , O(000000,F6,3,_,x,_,_,_ ), 0 , 75 , 0 , 141, 1 ), // #467
+ INST(Nop , X86M_Nop , O(000000,90,_,_,_,_,_,_ ), 0 , 0 , 0 , 142, 0 ), // #468
+ INST(Not , X86M_GPB , O(000000,F6,2,_,x,_,_,_ ), 0 , 1 , 0 , 141, 0 ), // #469
+ INST(Or , X86Arith , O(000000,08,1,_,x,_,_,_ ), 0 , 31 , 0 , 143, 1 ), // #470
+ INST(Orpd , ExtRm , O(660F00,56,_,_,_,_,_,_ ), 0 , 3 , 0 , 11 , 4 ), // #471
+ INST(Orps , ExtRm , O(000F00,56,_,_,_,_,_,_ ), 0 , 4 , 0 , 11 , 5 ), // #472
+ INST(Out , X86Out , O(000000,EE,_,_,_,_,_,_ ), O(000000,E6,_,_,_,_,_,_ ), 0 , 65 , 144, 0 ), // #473
+ INST(Outs , X86Outs , O(000000,6E,_,_,_,_,_,_ ), 0 , 0 , 0 , 145, 0 ), // #474
+ INST(Pabsb , ExtRm_P , O(000F38,1C,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #475
+ INST(Pabsd , ExtRm_P , O(000F38,1E,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #476
+ INST(Pabsw , ExtRm_P , O(000F38,1D,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #477
+ INST(Packssdw , ExtRm_P , O(000F00,6B,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #478
+ INST(Packsswb , ExtRm_P , O(000F00,63,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #479
+ INST(Packusdw , ExtRm , O(660F38,2B,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 12 ), // #480
+ INST(Packuswb , ExtRm_P , O(000F00,67,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #481
+ INST(Paddb , ExtRm_P , O(000F00,FC,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #482
+ INST(Paddd , ExtRm_P , O(000F00,FE,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #483
+ INST(Paddq , ExtRm_P , O(000F00,D4,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 4 ), // #484
+ INST(Paddsb , ExtRm_P , O(000F00,EC,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #485
+ INST(Paddsw , ExtRm_P , O(000F00,ED,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #486
+ INST(Paddusb , ExtRm_P , O(000F00,DC,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #487
+ INST(Paddusw , ExtRm_P , O(000F00,DD,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #488
+ INST(Paddw , ExtRm_P , O(000F00,FD,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #489
+ INST(Palignr , ExtRmi_P , O(000F3A,0F,_,_,_,_,_,_ ), 0 , 85 , 0 , 147, 6 ), // #490
+ INST(Pand , ExtRm_P , O(000F00,DB,_,_,_,_,_,_ ), 0 , 4 , 0 , 148, 88 ), // #491
+ INST(Pandn , ExtRm_P , O(000F00,DF,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #492
+ INST(Pause , X86Op , O(F30000,90,_,_,_,_,_,_ ), 0 , 86 , 0 , 30 , 0 ), // #493
+ INST(Pavgb , ExtRm_P , O(000F00,E0,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 94 ), // #494
+ INST(Pavgusb , Ext3dNow , O(000F0F,BF,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #495
+ INST(Pavgw , ExtRm_P , O(000F00,E3,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 94 ), // #496
+ INST(Pblendvb , ExtRm_XMM0 , O(660F38,10,_,_,_,_,_,_ ), 0 , 2 , 0 , 15 , 12 ), // #497
+ INST(Pblendw , ExtRmi , O(660F3A,0E,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 12 ), // #498
+ INST(Pclmulqdq , ExtRmi , O(660F3A,44,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 95 ), // #499
+ INST(Pcmpeqb , ExtRm_P , O(000F00,74,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #500
+ INST(Pcmpeqd , ExtRm_P , O(000F00,76,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #501
+ INST(Pcmpeqq , ExtRm , O(660F38,29,_,_,_,_,_,_ ), 0 , 2 , 0 , 151, 12 ), // #502
+ INST(Pcmpeqw , ExtRm_P , O(000F00,75,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #503
+ INST(Pcmpestri , ExtRmi , O(660F3A,61,_,_,_,_,_,_ ), 0 , 8 , 0 , 152, 96 ), // #504
+ INST(Pcmpestrm , ExtRmi , O(660F3A,60,_,_,_,_,_,_ ), 0 , 8 , 0 , 153, 96 ), // #505
+ INST(Pcmpgtb , ExtRm_P , O(000F00,64,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #506
+ INST(Pcmpgtd , ExtRm_P , O(000F00,66,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #507
+ INST(Pcmpgtq , ExtRm , O(660F38,37,_,_,_,_,_,_ ), 0 , 2 , 0 , 151, 44 ), // #508
+ INST(Pcmpgtw , ExtRm_P , O(000F00,65,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #509
+ INST(Pcmpistri , ExtRmi , O(660F3A,63,_,_,_,_,_,_ ), 0 , 8 , 0 , 154, 96 ), // #510
+ INST(Pcmpistrm , ExtRmi , O(660F3A,62,_,_,_,_,_,_ ), 0 , 8 , 0 , 155, 96 ), // #511
+ INST(Pconfig , X86Op , O(000F01,C5,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 97 ), // #512
+ INST(Pdep , VexRvm_Wx , V(F20F38,F5,_,0,x,_,_,_ ), 0 , 84 , 0 , 10 , 92 ), // #513
+ INST(Pext , VexRvm_Wx , V(F30F38,F5,_,0,x,_,_,_ ), 0 , 88 , 0 , 10 , 92 ), // #514
+ INST(Pextrb , ExtExtract , O(000F3A,14,_,_,_,_,_,_ ), 0 , 85 , 0 , 156, 12 ), // #515
+ INST(Pextrd , ExtExtract , O(000F3A,16,_,_,_,_,_,_ ), 0 , 85 , 0 , 58 , 12 ), // #516
+ INST(Pextrq , ExtExtract , O(000F3A,16,_,_,1,_,_,_ ), 0 , 89 , 0 , 157, 12 ), // #517
+ INST(Pextrw , ExtPextrw , O(000F00,C5,_,_,_,_,_,_ ), O(000F3A,15,_,_,_,_,_,_ ), 4 , 66 , 158, 98 ), // #518
+ INST(Pf2id , Ext3dNow , O(000F0F,1D,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #519
+ INST(Pf2iw , Ext3dNow , O(000F0F,1C,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 99 ), // #520
+ INST(Pfacc , Ext3dNow , O(000F0F,AE,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #521
+ INST(Pfadd , Ext3dNow , O(000F0F,9E,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #522
+ INST(Pfcmpeq , Ext3dNow , O(000F0F,B0,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #523
+ INST(Pfcmpge , Ext3dNow , O(000F0F,90,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #524
+ INST(Pfcmpgt , Ext3dNow , O(000F0F,A0,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #525
+ INST(Pfmax , Ext3dNow , O(000F0F,A4,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #526
+ INST(Pfmin , Ext3dNow , O(000F0F,94,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #527
+ INST(Pfmul , Ext3dNow , O(000F0F,B4,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #528
+ INST(Pfnacc , Ext3dNow , O(000F0F,8A,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 99 ), // #529
+ INST(Pfpnacc , Ext3dNow , O(000F0F,8E,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 99 ), // #530
+ INST(Pfrcp , Ext3dNow , O(000F0F,96,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #531
+ INST(Pfrcpit1 , Ext3dNow , O(000F0F,A6,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #532
+ INST(Pfrcpit2 , Ext3dNow , O(000F0F,B6,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #533
+ INST(Pfrcpv , Ext3dNow , O(000F0F,86,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 100), // #534
+ INST(Pfrsqit1 , Ext3dNow , O(000F0F,A7,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #535
+ INST(Pfrsqrt , Ext3dNow , O(000F0F,97,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #536
+ INST(Pfrsqrtv , Ext3dNow , O(000F0F,87,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 100), // #537
+ INST(Pfsub , Ext3dNow , O(000F0F,9A,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #538
+ INST(Pfsubr , Ext3dNow , O(000F0F,AA,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #539
+ INST(Phaddd , ExtRm_P , O(000F38,02,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #540
+ INST(Phaddsw , ExtRm_P , O(000F38,03,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #541
+ INST(Phaddw , ExtRm_P , O(000F38,01,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #542
+ INST(Phminposuw , ExtRm , O(660F38,41,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 12 ), // #543
+ INST(Phsubd , ExtRm_P , O(000F38,06,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #544
+ INST(Phsubsw , ExtRm_P , O(000F38,07,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #545
+ INST(Phsubw , ExtRm_P , O(000F38,05,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #546
+ INST(Pi2fd , Ext3dNow , O(000F0F,0D,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #547
+ INST(Pi2fw , Ext3dNow , O(000F0F,0C,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 99 ), // #548
+ INST(Pinsrb , ExtRmi , O(660F3A,20,_,_,_,_,_,_ ), 0 , 8 , 0 , 159, 12 ), // #549
+ INST(Pinsrd , ExtRmi , O(660F3A,22,_,_,_,_,_,_ ), 0 , 8 , 0 , 160, 12 ), // #550
+ INST(Pinsrq , ExtRmi , O(660F3A,22,_,_,1,_,_,_ ), 0 , 90 , 0 , 161, 12 ), // #551
+ INST(Pinsrw , ExtRmi_P , O(000F00,C4,_,_,_,_,_,_ ), 0 , 4 , 0 , 162, 94 ), // #552
+ INST(Pmaddubsw , ExtRm_P , O(000F38,04,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #553
+ INST(Pmaddwd , ExtRm_P , O(000F00,F5,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #554
+ INST(Pmaxsb , ExtRm , O(660F38,3C,_,_,_,_,_,_ ), 0 , 2 , 0 , 11 , 12 ), // #555
+ INST(Pmaxsd , ExtRm , O(660F38,3D,_,_,_,_,_,_ ), 0 , 2 , 0 , 11 , 12 ), // #556
+ INST(Pmaxsw , ExtRm_P , O(000F00,EE,_,_,_,_,_,_ ), 0 , 4 , 0 , 148, 94 ), // #557
+ INST(Pmaxub , ExtRm_P , O(000F00,DE,_,_,_,_,_,_ ), 0 , 4 , 0 , 148, 94 ), // #558
+ INST(Pmaxud , ExtRm , O(660F38,3F,_,_,_,_,_,_ ), 0 , 2 , 0 , 11 , 12 ), // #559
+ INST(Pmaxuw , ExtRm , O(660F38,3E,_,_,_,_,_,_ ), 0 , 2 , 0 , 11 , 12 ), // #560
+ INST(Pminsb , ExtRm , O(660F38,38,_,_,_,_,_,_ ), 0 , 2 , 0 , 11 , 12 ), // #561
+ INST(Pminsd , ExtRm , O(660F38,39,_,_,_,_,_,_ ), 0 , 2 , 0 , 11 , 12 ), // #562
+ INST(Pminsw , ExtRm_P , O(000F00,EA,_,_,_,_,_,_ ), 0 , 4 , 0 , 148, 94 ), // #563
+ INST(Pminub , ExtRm_P , O(000F00,DA,_,_,_,_,_,_ ), 0 , 4 , 0 , 148, 94 ), // #564
+ INST(Pminud , ExtRm , O(660F38,3B,_,_,_,_,_,_ ), 0 , 2 , 0 , 11 , 12 ), // #565
+ INST(Pminuw , ExtRm , O(660F38,3A,_,_,_,_,_,_ ), 0 , 2 , 0 , 11 , 12 ), // #566
+ INST(Pmovmskb , ExtRm_P , O(000F00,D7,_,_,_,_,_,_ ), 0 , 4 , 0 , 163, 94 ), // #567
+ INST(Pmovsxbd , ExtRm , O(660F38,21,_,_,_,_,_,_ ), 0 , 2 , 0 , 7 , 12 ), // #568
+ INST(Pmovsxbq , ExtRm , O(660F38,22,_,_,_,_,_,_ ), 0 , 2 , 0 , 164, 12 ), // #569
+ INST(Pmovsxbw , ExtRm , O(660F38,20,_,_,_,_,_,_ ), 0 , 2 , 0 , 6 , 12 ), // #570
+ INST(Pmovsxdq , ExtRm , O(660F38,25,_,_,_,_,_,_ ), 0 , 2 , 0 , 6 , 12 ), // #571
+ INST(Pmovsxwd , ExtRm , O(660F38,23,_,_,_,_,_,_ ), 0 , 2 , 0 , 6 , 12 ), // #572
+ INST(Pmovsxwq , ExtRm , O(660F38,24,_,_,_,_,_,_ ), 0 , 2 , 0 , 7 , 12 ), // #573
+ INST(Pmovzxbd , ExtRm , O(660F38,31,_,_,_,_,_,_ ), 0 , 2 , 0 , 7 , 12 ), // #574
+ INST(Pmovzxbq , ExtRm , O(660F38,32,_,_,_,_,_,_ ), 0 , 2 , 0 , 164, 12 ), // #575
+ INST(Pmovzxbw , ExtRm , O(660F38,30,_,_,_,_,_,_ ), 0 , 2 , 0 , 6 , 12 ), // #576
+ INST(Pmovzxdq , ExtRm , O(660F38,35,_,_,_,_,_,_ ), 0 , 2 , 0 , 6 , 12 ), // #577
+ INST(Pmovzxwd , ExtRm , O(660F38,33,_,_,_,_,_,_ ), 0 , 2 , 0 , 6 , 12 ), // #578
+ INST(Pmovzxwq , ExtRm , O(660F38,34,_,_,_,_,_,_ ), 0 , 2 , 0 , 7 , 12 ), // #579
+ INST(Pmuldq , ExtRm , O(660F38,28,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 12 ), // #580
+ INST(Pmulhrsw , ExtRm_P , O(000F38,0B,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #581
+ INST(Pmulhrw , Ext3dNow , O(000F0F,B7,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 51 ), // #582
+ INST(Pmulhuw , ExtRm_P , O(000F00,E4,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 94 ), // #583
+ INST(Pmulhw , ExtRm_P , O(000F00,E5,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #584
+ INST(Pmulld , ExtRm , O(660F38,40,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 12 ), // #585
+ INST(Pmullw , ExtRm_P , O(000F00,D5,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #586
+ INST(Pmuludq , ExtRm_P , O(000F00,F4,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 4 ), // #587
+ INST(Pop , X86Pop , O(000000,8F,0,_,_,_,_,_ ), O(000000,58,_,_,_,_,_,_ ), 0 , 67 , 165, 0 ), // #588
+ INST(Popa , X86Op , O(660000,61,_,_,_,_,_,_ ), 0 , 19 , 0 , 81 , 0 ), // #589
+ INST(Popad , X86Op , O(000000,61,_,_,_,_,_,_ ), 0 , 0 , 0 , 81 , 0 ), // #590
+ INST(Popcnt , X86Rm_Raw66H , O(F30F00,B8,_,_,x,_,_,_ ), 0 , 6 , 0 , 22 , 101), // #591
+ INST(Popf , X86Op , O(660000,9D,_,_,_,_,_,_ ), 0 , 19 , 0 , 30 , 102), // #592
+ INST(Popfd , X86Op , O(000000,9D,_,_,_,_,_,_ ), 0 , 0 , 0 , 81 , 102), // #593
+ INST(Popfq , X86Op , O(000000,9D,_,_,_,_,_,_ ), 0 , 0 , 0 , 33 , 102), // #594
+ INST(Por , ExtRm_P , O(000F00,EB,_,_,_,_,_,_ ), 0 , 4 , 0 , 148, 88 ), // #595
+ INST(Prefetch , X86M_Only , O(000F00,0D,0,_,_,_,_,_ ), 0 , 4 , 0 , 31 , 51 ), // #596
+ INST(Prefetchnta , X86M_Only , O(000F00,18,0,_,_,_,_,_ ), 0 , 4 , 0 , 31 , 80 ), // #597
+ INST(Prefetcht0 , X86M_Only , O(000F00,18,1,_,_,_,_,_ ), 0 , 29 , 0 , 31 , 80 ), // #598
+ INST(Prefetcht1 , X86M_Only , O(000F00,18,2,_,_,_,_,_ ), 0 , 76 , 0 , 31 , 80 ), // #599
+ INST(Prefetcht2 , X86M_Only , O(000F00,18,3,_,_,_,_,_ ), 0 , 78 , 0 , 31 , 80 ), // #600
+ INST(Prefetchw , X86M_Only , O(000F00,0D,1,_,_,_,_,_ ), 0 , 29 , 0 , 31 , 103), // #601
+ INST(Prefetchwt1 , X86M_Only , O(000F00,0D,2,_,_,_,_,_ ), 0 , 76 , 0 , 31 , 104), // #602
+ INST(Psadbw , ExtRm_P , O(000F00,F6,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 94 ), // #603
+ INST(Pshufb , ExtRm_P , O(000F38,00,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #604
+ INST(Pshufd , ExtRmi , O(660F00,70,_,_,_,_,_,_ ), 0 , 3 , 0 , 8 , 4 ), // #605
+ INST(Pshufhw , ExtRmi , O(F30F00,70,_,_,_,_,_,_ ), 0 , 6 , 0 , 8 , 4 ), // #606
+ INST(Pshuflw , ExtRmi , O(F20F00,70,_,_,_,_,_,_ ), 0 , 5 , 0 , 8 , 4 ), // #607
+ INST(Pshufw , ExtRmi_P , O(000F00,70,_,_,_,_,_,_ ), 0 , 4 , 0 , 166, 80 ), // #608
+ INST(Psignb , ExtRm_P , O(000F38,08,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #609
+ INST(Psignd , ExtRm_P , O(000F38,0A,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #610
+ INST(Psignw , ExtRm_P , O(000F38,09,_,_,_,_,_,_ ), 0 , 83 , 0 , 146, 93 ), // #611
+ INST(Pslld , ExtRmRi_P , O(000F00,F2,_,_,_,_,_,_ ), O(000F00,72,6,_,_,_,_,_ ), 4 , 68 , 167, 88 ), // #612
+ INST(Pslldq , ExtRmRi , 0 , O(660F00,73,7,_,_,_,_,_ ), 0 , 69 , 168, 4 ), // #613
+ INST(Psllq , ExtRmRi_P , O(000F00,F3,_,_,_,_,_,_ ), O(000F00,73,6,_,_,_,_,_ ), 4 , 70 , 167, 88 ), // #614
+ INST(Psllw , ExtRmRi_P , O(000F00,F1,_,_,_,_,_,_ ), O(000F00,71,6,_,_,_,_,_ ), 4 , 71 , 167, 88 ), // #615
+ INST(Psmash , X86Op , O(F30F01,FF,_,_,_,_,_,_ ), 0 , 25 , 0 , 33 , 105), // #616
+ INST(Psrad , ExtRmRi_P , O(000F00,E2,_,_,_,_,_,_ ), O(000F00,72,4,_,_,_,_,_ ), 4 , 72 , 167, 88 ), // #617
+ INST(Psraw , ExtRmRi_P , O(000F00,E1,_,_,_,_,_,_ ), O(000F00,71,4,_,_,_,_,_ ), 4 , 73 , 167, 88 ), // #618
+ INST(Psrld , ExtRmRi_P , O(000F00,D2,_,_,_,_,_,_ ), O(000F00,72,2,_,_,_,_,_ ), 4 , 74 , 167, 88 ), // #619
+ INST(Psrldq , ExtRmRi , 0 , O(660F00,73,3,_,_,_,_,_ ), 0 , 75 , 168, 4 ), // #620
+ INST(Psrlq , ExtRmRi_P , O(000F00,D3,_,_,_,_,_,_ ), O(000F00,73,2,_,_,_,_,_ ), 4 , 76 , 167, 88 ), // #621
+ INST(Psrlw , ExtRmRi_P , O(000F00,D1,_,_,_,_,_,_ ), O(000F00,71,2,_,_,_,_,_ ), 4 , 77 , 167, 88 ), // #622
+ INST(Psubb , ExtRm_P , O(000F00,F8,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #623
+ INST(Psubd , ExtRm_P , O(000F00,FA,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #624
+ INST(Psubq , ExtRm_P , O(000F00,FB,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 4 ), // #625
+ INST(Psubsb , ExtRm_P , O(000F00,E8,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #626
+ INST(Psubsw , ExtRm_P , O(000F00,E9,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #627
+ INST(Psubusb , ExtRm_P , O(000F00,D8,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #628
+ INST(Psubusw , ExtRm_P , O(000F00,D9,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #629
+ INST(Psubw , ExtRm_P , O(000F00,F9,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #630
+ INST(Pswapd , Ext3dNow , O(000F0F,BB,_,_,_,_,_,_ ), 0 , 87 , 0 , 150, 99 ), // #631
+ INST(Ptest , ExtRm , O(660F38,17,_,_,_,_,_,_ ), 0 , 2 , 0 , 5 , 106), // #632
+ INST(Ptwrite , X86M , O(F30F00,AE,4,_,_,_,_,_ ), 0 , 91 , 0 , 169, 107), // #633
+ INST(Punpckhbw , ExtRm_P , O(000F00,68,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #634
+ INST(Punpckhdq , ExtRm_P , O(000F00,6A,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #635
+ INST(Punpckhqdq , ExtRm , O(660F00,6D,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #636
+ INST(Punpckhwd , ExtRm_P , O(000F00,69,_,_,_,_,_,_ ), 0 , 4 , 0 , 146, 88 ), // #637
+ INST(Punpcklbw , ExtRm_P , O(000F00,60,_,_,_,_,_,_ ), 0 , 4 , 0 , 170, 88 ), // #638
+ INST(Punpckldq , ExtRm_P , O(000F00,62,_,_,_,_,_,_ ), 0 , 4 , 0 , 170, 88 ), // #639
+ INST(Punpcklqdq , ExtRm , O(660F00,6C,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #640
+ INST(Punpcklwd , ExtRm_P , O(000F00,61,_,_,_,_,_,_ ), 0 , 4 , 0 , 170, 88 ), // #641
+ INST(Push , X86Push , O(000000,FF,6,_,_,_,_,_ ), O(000000,50,_,_,_,_,_,_ ), 32 , 78 , 171, 0 ), // #642
+ INST(Pusha , X86Op , O(660000,60,_,_,_,_,_,_ ), 0 , 19 , 0 , 81 , 0 ), // #643
+ INST(Pushad , X86Op , O(000000,60,_,_,_,_,_,_ ), 0 , 0 , 0 , 81 , 0 ), // #644
+ INST(Pushf , X86Op , O(660000,9C,_,_,_,_,_,_ ), 0 , 19 , 0 , 30 , 108), // #645
+ INST(Pushfd , X86Op , O(000000,9C,_,_,_,_,_,_ ), 0 , 0 , 0 , 81 , 108), // #646
+ INST(Pushfq , X86Op , O(000000,9C,_,_,_,_,_,_ ), 0 , 0 , 0 , 33 , 108), // #647
+ INST(Pvalidate , X86Op , O(F20F01,FF,_,_,_,_,_,_ ), 0 , 92 , 0 , 30 , 109), // #648
+ INST(Pxor , ExtRm_P , O(000F00,EF,_,_,_,_,_,_ ), 0 , 4 , 0 , 149, 88 ), // #649
+ INST(Rcl , X86Rot , O(000000,D0,2,_,x,_,_,_ ), 0 , 1 , 0 , 172, 110), // #650
+ INST(Rcpps , ExtRm , O(000F00,53,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #651
+ INST(Rcpss , ExtRm , O(F30F00,53,_,_,_,_,_,_ ), 0 , 6 , 0 , 7 , 5 ), // #652
+ INST(Rcr , X86Rot , O(000000,D0,3,_,x,_,_,_ ), 0 , 75 , 0 , 172, 110), // #653
+ INST(Rdfsbase , X86M , O(F30F00,AE,0,_,x,_,_,_ ), 0 , 6 , 0 , 173, 111), // #654
+ INST(Rdgsbase , X86M , O(F30F00,AE,1,_,x,_,_,_ ), 0 , 93 , 0 , 173, 111), // #655
+ INST(Rdmsr , X86Op , O(000F00,32,_,_,_,_,_,_ ), 0 , 4 , 0 , 174, 112), // #656
+ INST(Rdpid , X86R_Native , O(F30F00,C7,7,_,_,_,_,_ ), 0 , 94 , 0 , 175, 113), // #657
+ INST(Rdpkru , X86Op , O(000F01,EE,_,_,_,_,_,_ ), 0 , 21 , 0 , 174, 114), // #658
+ INST(Rdpmc , X86Op , O(000F00,33,_,_,_,_,_,_ ), 0 , 4 , 0 , 174, 0 ), // #659
+ INST(Rdpru , X86Op , O(000F01,FD,_,_,_,_,_,_ ), 0 , 21 , 0 , 174, 115), // #660
+ INST(Rdrand , X86M , O(000F00,C7,6,_,x,_,_,_ ), 0 , 80 , 0 , 23 , 116), // #661
+ INST(Rdseed , X86M , O(000F00,C7,7,_,x,_,_,_ ), 0 , 22 , 0 , 23 , 117), // #662
+ INST(Rdsspd , X86M , O(F30F00,1E,1,_,_,_,_,_ ), 0 , 93 , 0 , 76 , 56 ), // #663
+ INST(Rdsspq , X86M , O(F30F00,1E,1,_,_,_,_,_ ), 0 , 93 , 0 , 77 , 56 ), // #664
+ INST(Rdtsc , X86Op , O(000F00,31,_,_,_,_,_,_ ), 0 , 4 , 0 , 28 , 118), // #665
+ INST(Rdtscp , X86Op , O(000F01,F9,_,_,_,_,_,_ ), 0 , 21 , 0 , 174, 119), // #666
+ INST(Ret , X86Ret , O(000000,C2,_,_,_,_,_,_ ), 0 , 0 , 0 , 176, 0 ), // #667
+ INST(Retf , X86Ret , O(000000,CA,_,_,x,_,_,_ ), 0 , 0 , 0 , 177, 0 ), // #668
+ INST(Rmpadjust , X86Op , O(F30F01,FE,_,_,_,_,_,_ ), 0 , 25 , 0 , 33 , 105), // #669
+ INST(Rmpupdate , X86Op , O(F20F01,FE,_,_,_,_,_,_ ), 0 , 92 , 0 , 33 , 105), // #670
+ INST(Rol , X86Rot , O(000000,D0,0,_,x,_,_,_ ), 0 , 0 , 0 , 172, 120), // #671
+ INST(Ror , X86Rot , O(000000,D0,1,_,x,_,_,_ ), 0 , 31 , 0 , 172, 120), // #672
+ INST(Rorx , VexRmi_Wx , V(F20F3A,F0,_,0,x,_,_,_ ), 0 , 95 , 0 , 178, 92 ), // #673
+ INST(Roundpd , ExtRmi , O(660F3A,09,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 12 ), // #674
+ INST(Roundps , ExtRmi , O(660F3A,08,_,_,_,_,_,_ ), 0 , 8 , 0 , 8 , 12 ), // #675
+ INST(Roundsd , ExtRmi , O(660F3A,0B,_,_,_,_,_,_ ), 0 , 8 , 0 , 37 , 12 ), // #676
+ INST(Roundss , ExtRmi , O(660F3A,0A,_,_,_,_,_,_ ), 0 , 8 , 0 , 38 , 12 ), // #677
+ INST(Rsm , X86Op , O(000F00,AA,_,_,_,_,_,_ ), 0 , 4 , 0 , 81 , 1 ), // #678
+ INST(Rsqrtps , ExtRm , O(000F00,52,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #679
+ INST(Rsqrtss , ExtRm , O(F30F00,52,_,_,_,_,_,_ ), 0 , 6 , 0 , 7 , 5 ), // #680
+ INST(Rstorssp , X86M_Only , O(F30F00,01,5,_,_,_,_,_ ), 0 , 63 , 0 , 32 , 24 ), // #681
+ INST(Sahf , X86Op , O(000000,9E,_,_,_,_,_,_ ), 0 , 0 , 0 , 97 , 121), // #682
+ INST(Sal , X86Rot , O(000000,D0,4,_,x,_,_,_ ), 0 , 9 , 0 , 172, 1 ), // #683
+ INST(Sar , X86Rot , O(000000,D0,7,_,x,_,_,_ ), 0 , 27 , 0 , 172, 1 ), // #684
+ INST(Sarx , VexRmv_Wx , V(F30F38,F7,_,0,x,_,_,_ ), 0 , 88 , 0 , 13 , 92 ), // #685
+ INST(Saveprevssp , X86Op , O(F30F01,EA,_,_,_,_,_,_ ), 0 , 25 , 0 , 30 , 24 ), // #686
+ INST(Sbb , X86Arith , O(000000,18,3,_,x,_,_,_ ), 0 , 75 , 0 , 179, 2 ), // #687
+ INST(Scas , X86StrRm , O(000000,AE,_,_,_,_,_,_ ), 0 , 0 , 0 , 180, 37 ), // #688
+ INST(Senduipi , X86M_NoSize , O(F30F00,C7,6,_,_,_,_,_ ), 0 , 24 , 0 , 77 , 25 ), // #689
+ INST(Serialize , X86Op , O(000F01,E8,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 122), // #690
+ INST(Seta , X86Set , O(000F00,97,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 59 ), // #691
+ INST(Setae , X86Set , O(000F00,93,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 60 ), // #692
+ INST(Setb , X86Set , O(000F00,92,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 60 ), // #693
+ INST(Setbe , X86Set , O(000F00,96,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 59 ), // #694
+ INST(Setc , X86Set , O(000F00,92,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 60 ), // #695
+ INST(Sete , X86Set , O(000F00,94,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 61 ), // #696
+ INST(Setg , X86Set , O(000F00,9F,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 62 ), // #697
+ INST(Setge , X86Set , O(000F00,9D,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 63 ), // #698
+ INST(Setl , X86Set , O(000F00,9C,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 63 ), // #699
+ INST(Setle , X86Set , O(000F00,9E,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 62 ), // #700
+ INST(Setna , X86Set , O(000F00,96,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 59 ), // #701
+ INST(Setnae , X86Set , O(000F00,92,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 60 ), // #702
+ INST(Setnb , X86Set , O(000F00,93,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 60 ), // #703
+ INST(Setnbe , X86Set , O(000F00,97,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 59 ), // #704
+ INST(Setnc , X86Set , O(000F00,93,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 60 ), // #705
+ INST(Setne , X86Set , O(000F00,95,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 61 ), // #706
+ INST(Setng , X86Set , O(000F00,9E,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 62 ), // #707
+ INST(Setnge , X86Set , O(000F00,9C,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 63 ), // #708
+ INST(Setnl , X86Set , O(000F00,9D,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 63 ), // #709
+ INST(Setnle , X86Set , O(000F00,9F,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 62 ), // #710
+ INST(Setno , X86Set , O(000F00,91,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 57 ), // #711
+ INST(Setnp , X86Set , O(000F00,9B,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 64 ), // #712
+ INST(Setns , X86Set , O(000F00,99,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 65 ), // #713
+ INST(Setnz , X86Set , O(000F00,95,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 61 ), // #714
+ INST(Seto , X86Set , O(000F00,90,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 57 ), // #715
+ INST(Setp , X86Set , O(000F00,9A,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 64 ), // #716
+ INST(Setpe , X86Set , O(000F00,9A,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 64 ), // #717
+ INST(Setpo , X86Set , O(000F00,9B,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 64 ), // #718
+ INST(Sets , X86Set , O(000F00,98,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 65 ), // #719
+ INST(Setssbsy , X86Op , O(F30F01,E8,_,_,_,_,_,_ ), 0 , 25 , 0 , 30 , 56 ), // #720
+ INST(Setz , X86Set , O(000F00,94,_,_,_,_,_,_ ), 0 , 4 , 0 , 181, 61 ), // #721
+ INST(Sfence , X86Fence , O(000F00,AE,7,_,_,_,_,_ ), 0 , 22 , 0 , 30 , 80 ), // #722
+ INST(Sgdt , X86M_Only , O(000F00,01,0,_,_,_,_,_ ), 0 , 4 , 0 , 69 , 0 ), // #723
+ INST(Sha1msg1 , ExtRm , O(000F38,C9,_,_,_,_,_,_ ), 0 , 83 , 0 , 5 , 123), // #724
+ INST(Sha1msg2 , ExtRm , O(000F38,CA,_,_,_,_,_,_ ), 0 , 83 , 0 , 5 , 123), // #725
+ INST(Sha1nexte , ExtRm , O(000F38,C8,_,_,_,_,_,_ ), 0 , 83 , 0 , 5 , 123), // #726
+ INST(Sha1rnds4 , ExtRmi , O(000F3A,CC,_,_,_,_,_,_ ), 0 , 85 , 0 , 8 , 123), // #727
+ INST(Sha256msg1 , ExtRm , O(000F38,CC,_,_,_,_,_,_ ), 0 , 83 , 0 , 5 , 123), // #728
+ INST(Sha256msg2 , ExtRm , O(000F38,CD,_,_,_,_,_,_ ), 0 , 83 , 0 , 5 , 123), // #729
+ INST(Sha256rnds2 , ExtRm_XMM0 , O(000F38,CB,_,_,_,_,_,_ ), 0 , 83 , 0 , 15 , 123), // #730
+ INST(Shl , X86Rot , O(000000,D0,4,_,x,_,_,_ ), 0 , 9 , 0 , 172, 1 ), // #731
+ INST(Shld , X86ShldShrd , O(000F00,A4,_,_,x,_,_,_ ), 0 , 4 , 0 , 182, 1 ), // #732
+ INST(Shlx , VexRmv_Wx , V(660F38,F7,_,0,x,_,_,_ ), 0 , 96 , 0 , 13 , 92 ), // #733
+ INST(Shr , X86Rot , O(000000,D0,5,_,x,_,_,_ ), 0 , 62 , 0 , 172, 1 ), // #734
+ INST(Shrd , X86ShldShrd , O(000F00,AC,_,_,x,_,_,_ ), 0 , 4 , 0 , 182, 1 ), // #735
+ INST(Shrx , VexRmv_Wx , V(F20F38,F7,_,0,x,_,_,_ ), 0 , 84 , 0 , 13 , 92 ), // #736
+ INST(Shufpd , ExtRmi , O(660F00,C6,_,_,_,_,_,_ ), 0 , 3 , 0 , 8 , 4 ), // #737
+ INST(Shufps , ExtRmi , O(000F00,C6,_,_,_,_,_,_ ), 0 , 4 , 0 , 8 , 5 ), // #738
+ INST(Sidt , X86M_Only , O(000F00,01,1,_,_,_,_,_ ), 0 , 29 , 0 , 69 , 0 ), // #739
+ INST(Skinit , X86Op_xAX , O(000F01,DE,_,_,_,_,_,_ ), 0 , 21 , 0 , 52 , 124), // #740
+ INST(Sldt , X86M_NoMemSize , O(000F00,00,0,_,_,_,_,_ ), 0 , 4 , 0 , 183, 0 ), // #741
+ INST(Slwpcb , VexR_Wx , V(XOP_M9,12,1,0,x,_,_,_ ), 0 , 11 , 0 , 108, 77 ), // #742
+ INST(Smsw , X86M_NoMemSize , O(000F00,01,4,_,_,_,_,_ ), 0 , 97 , 0 , 183, 0 ), // #743
+ INST(Sqrtpd , ExtRm , O(660F00,51,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #744
+ INST(Sqrtps , ExtRm , O(000F00,51,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #745
+ INST(Sqrtsd , ExtRm , O(F20F00,51,_,_,_,_,_,_ ), 0 , 5 , 0 , 6 , 4 ), // #746
+ INST(Sqrtss , ExtRm , O(F30F00,51,_,_,_,_,_,_ ), 0 , 6 , 0 , 7 , 5 ), // #747
+ INST(Stac , X86Op , O(000F01,CB,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 16 ), // #748
+ INST(Stc , X86Op , O(000000,F9,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 17 ), // #749
+ INST(Std , X86Op , O(000000,FD,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 18 ), // #750
+ INST(Stgi , X86Op , O(000F01,DC,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 124), // #751
+ INST(Sti , X86Op , O(000000,FB,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 23 ), // #752
+ INST(Stmxcsr , X86M_Only , O(000F00,AE,3,_,_,_,_,_ ), 0 , 78 , 0 , 101, 5 ), // #753
+ INST(Stos , X86StrMr , O(000000,AA,_,_,_,_,_,_ ), 0 , 0 , 0 , 184, 78 ), // #754
+ INST(Str , X86M_NoMemSize , O(000F00,00,1,_,_,_,_,_ ), 0 , 29 , 0 , 183, 0 ), // #755
+ INST(Sttilecfg , AmxCfg , V(660F38,49,_,0,0,_,_,_ ), 0 , 96 , 0 , 103, 76 ), // #756
+ INST(Stui , X86Op , O(F30F01,EF,_,_,_,_,_,_ ), 0 , 25 , 0 , 33 , 25 ), // #757
+ INST(Sub , X86Arith , O(000000,28,5,_,x,_,_,_ ), 0 , 62 , 0 , 179, 1 ), // #758
+ INST(Subpd , ExtRm , O(660F00,5C,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #759
+ INST(Subps , ExtRm , O(000F00,5C,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #760
+ INST(Subsd , ExtRm , O(F20F00,5C,_,_,_,_,_,_ ), 0 , 5 , 0 , 6 , 4 ), // #761
+ INST(Subss , ExtRm , O(F30F00,5C,_,_,_,_,_,_ ), 0 , 6 , 0 , 7 , 5 ), // #762
+ INST(Swapgs , X86Op , O(000F01,F8,_,_,_,_,_,_ ), 0 , 21 , 0 , 33 , 0 ), // #763
+ INST(Syscall , X86Op , O(000F00,05,_,_,_,_,_,_ ), 0 , 4 , 0 , 33 , 0 ), // #764
+ INST(Sysenter , X86Op , O(000F00,34,_,_,_,_,_,_ ), 0 , 4 , 0 , 30 , 0 ), // #765
+ INST(Sysexit , X86Op , O(000F00,35,_,_,_,_,_,_ ), 0 , 4 , 0 , 30 , 0 ), // #766
+ INST(Sysexitq , X86Op , O(000F00,35,_,_,1,_,_,_ ), 0 , 60 , 0 , 30 , 0 ), // #767
+ INST(Sysret , X86Op , O(000F00,07,_,_,_,_,_,_ ), 0 , 4 , 0 , 33 , 0 ), // #768
+ INST(Sysretq , X86Op , O(000F00,07,_,_,1,_,_,_ ), 0 , 60 , 0 , 33 , 0 ), // #769
+ INST(T1mskc , VexVm_Wx , V(XOP_M9,01,7,0,x,_,_,_ ), 0 , 98 , 0 , 14 , 11 ), // #770
+ INST(Tdpbf16ps , AmxRmv , V(F30F38,5C,_,0,0,_,_,_ ), 0 , 88 , 0 , 185, 125), // #771
+ INST(Tdpbssd , AmxRmv , V(F20F38,5E,_,0,0,_,_,_ ), 0 , 84 , 0 , 185, 126), // #772
+ INST(Tdpbsud , AmxRmv , V(F30F38,5E,_,0,0,_,_,_ ), 0 , 88 , 0 , 185, 126), // #773
+ INST(Tdpbusd , AmxRmv , V(660F38,5E,_,0,0,_,_,_ ), 0 , 96 , 0 , 185, 126), // #774
+ INST(Tdpbuud , AmxRmv , V(000F38,5E,_,0,0,_,_,_ ), 0 , 10 , 0 , 185, 126), // #775
+ INST(Test , X86Test , O(000000,84,_,_,x,_,_,_ ), O(000000,F6,_,_,x,_,_,_ ), 0 , 79 , 186, 1 ), // #776
+ INST(Testui , X86Op , O(F30F01,ED,_,_,_,_,_,_ ), 0 , 25 , 0 , 33 , 127), // #777
+ INST(Tileloadd , AmxRm , V(F20F38,4B,_,0,0,_,_,_ ), 0 , 84 , 0 , 187, 76 ), // #778
+ INST(Tileloaddt1 , AmxRm , V(660F38,4B,_,0,0,_,_,_ ), 0 , 96 , 0 , 187, 76 ), // #779
+ INST(Tilerelease , VexOpMod , V(000F38,49,0,0,0,_,_,_ ), 0 , 10 , 0 , 188, 76 ), // #780
+ INST(Tilestored , AmxMr , V(F30F38,4B,_,0,0,_,_,_ ), 0 , 88 , 0 , 189, 76 ), // #781
+ INST(Tilezero , AmxR , V(F20F38,49,_,0,0,_,_,_ ), 0 , 84 , 0 , 190, 76 ), // #782
+ INST(Tpause , X86R32_EDX_EAX , O(660F00,AE,6,_,_,_,_,_ ), 0 , 26 , 0 , 191, 128), // #783
+ INST(Tzcnt , X86Rm_Raw66H , O(F30F00,BC,_,_,x,_,_,_ ), 0 , 6 , 0 , 22 , 9 ), // #784
+ INST(Tzmsk , VexVm_Wx , V(XOP_M9,01,4,0,x,_,_,_ ), 0 , 99 , 0 , 14 , 11 ), // #785
+ INST(Ucomisd , ExtRm , O(660F00,2E,_,_,_,_,_,_ ), 0 , 3 , 0 , 6 , 41 ), // #786
+ INST(Ucomiss , ExtRm , O(000F00,2E,_,_,_,_,_,_ ), 0 , 4 , 0 , 7 , 42 ), // #787
+ INST(Ud0 , X86Rm , O(000F00,FF,_,_,_,_,_,_ ), 0 , 4 , 0 , 192, 0 ), // #788
+ INST(Ud1 , X86Rm , O(000F00,B9,_,_,_,_,_,_ ), 0 , 4 , 0 , 192, 0 ), // #789
+ INST(Ud2 , X86Op , O(000F00,0B,_,_,_,_,_,_ ), 0 , 4 , 0 , 30 , 0 ), // #790
+ INST(Uiret , X86Op , O(F30F01,EC,_,_,_,_,_,_ ), 0 , 25 , 0 , 33 , 25 ), // #791
+ INST(Umonitor , X86R_FromM , O(F30F00,AE,6,_,_,_,_,_ ), 0 , 24 , 0 , 193, 129), // #792
+ INST(Umwait , X86R32_EDX_EAX , O(F20F00,AE,6,_,_,_,_,_ ), 0 , 100, 0 , 191, 128), // #793
+ INST(Unpckhpd , ExtRm , O(660F00,15,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #794
+ INST(Unpckhps , ExtRm , O(000F00,15,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #795
+ INST(Unpcklpd , ExtRm , O(660F00,14,_,_,_,_,_,_ ), 0 , 3 , 0 , 5 , 4 ), // #796
+ INST(Unpcklps , ExtRm , O(000F00,14,_,_,_,_,_,_ ), 0 , 4 , 0 , 5 , 5 ), // #797
+ INST(V4fmaddps , VexRm_T1_4X , E(F20F38,9A,_,2,_,0,4,T4X), 0 , 101, 0 , 194, 130), // #798
+ INST(V4fmaddss , VexRm_T1_4X , E(F20F38,9B,_,0,_,0,4,T4X), 0 , 102, 0 , 195, 130), // #799
+ INST(V4fnmaddps , VexRm_T1_4X , E(F20F38,AA,_,2,_,0,4,T4X), 0 , 101, 0 , 194, 130), // #800
+ INST(V4fnmaddss , VexRm_T1_4X , E(F20F38,AB,_,0,_,0,4,T4X), 0 , 102, 0 , 195, 130), // #801
+ INST(Vaddpd , VexRvm_Lx , V(660F00,58,_,x,I,1,4,FV ), 0 , 103, 0 , 196, 131), // #802
+ INST(Vaddph , VexRvm_Lx , E(00MAP5,58,_,_,_,0,4,FV ), 0 , 104, 0 , 197, 132), // #803
+ INST(Vaddps , VexRvm_Lx , V(000F00,58,_,x,I,0,4,FV ), 0 , 105, 0 , 198, 131), // #804
+ INST(Vaddsd , VexRvm , V(F20F00,58,_,I,I,1,3,T1S), 0 , 106, 0 , 199, 133), // #805
+ INST(Vaddsh , VexRvm , E(F3MAP5,58,_,_,_,0,1,T1S), 0 , 107, 0 , 200, 134), // #806
+ INST(Vaddss , VexRvm , V(F30F00,58,_,I,I,0,2,T1S), 0 , 108, 0 , 201, 133), // #807
+ INST(Vaddsubpd , VexRvm_Lx , V(660F00,D0,_,x,I,_,_,_ ), 0 , 69 , 0 , 202, 135), // #808
+ INST(Vaddsubps , VexRvm_Lx , V(F20F00,D0,_,x,I,_,_,_ ), 0 , 109, 0 , 202, 135), // #809
+ INST(Vaesdec , VexRvm_Lx , V(660F38,DE,_,x,I,_,4,FVM), 0 , 110, 0 , 203, 136), // #810
+ INST(Vaesdeclast , VexRvm_Lx , V(660F38,DF,_,x,I,_,4,FVM), 0 , 110, 0 , 203, 136), // #811
+ INST(Vaesenc , VexRvm_Lx , V(660F38,DC,_,x,I,_,4,FVM), 0 , 110, 0 , 203, 136), // #812
+ INST(Vaesenclast , VexRvm_Lx , V(660F38,DD,_,x,I,_,4,FVM), 0 , 110, 0 , 203, 136), // #813
+ INST(Vaesimc , VexRm , V(660F38,DB,_,0,I,_,_,_ ), 0 , 96 , 0 , 204, 137), // #814
+ INST(Vaeskeygenassist , VexRmi , V(660F3A,DF,_,0,I,_,_,_ ), 0 , 73 , 0 , 205, 137), // #815
+ INST(Valignd , VexRvmi_Lx , E(660F3A,03,_,x,_,0,4,FV ), 0 , 111, 0 , 206, 138), // #816
+ INST(Valignq , VexRvmi_Lx , E(660F3A,03,_,x,_,1,4,FV ), 0 , 112, 0 , 207, 138), // #817
+ INST(Vandnpd , VexRvm_Lx , V(660F00,55,_,x,I,1,4,FV ), 0 , 103, 0 , 208, 139), // #818
+ INST(Vandnps , VexRvm_Lx , V(000F00,55,_,x,I,0,4,FV ), 0 , 105, 0 , 209, 139), // #819
+ INST(Vandpd , VexRvm_Lx , V(660F00,54,_,x,I,1,4,FV ), 0 , 103, 0 , 210, 139), // #820
+ INST(Vandps , VexRvm_Lx , V(000F00,54,_,x,I,0,4,FV ), 0 , 105, 0 , 211, 139), // #821
+ INST(Vblendmpd , VexRvm_Lx , E(660F38,65,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #822
+ INST(Vblendmps , VexRvm_Lx , E(660F38,65,_,x,_,0,4,FV ), 0 , 114, 0 , 213, 138), // #823
+ INST(Vblendpd , VexRvmi_Lx , V(660F3A,0D,_,x,I,_,_,_ ), 0 , 73 , 0 , 214, 135), // #824
+ INST(Vblendps , VexRvmi_Lx , V(660F3A,0C,_,x,I,_,_,_ ), 0 , 73 , 0 , 214, 135), // #825
+ INST(Vblendvpd , VexRvmr_Lx , V(660F3A,4B,_,x,0,_,_,_ ), 0 , 73 , 0 , 215, 135), // #826
+ INST(Vblendvps , VexRvmr_Lx , V(660F3A,4A,_,x,0,_,_,_ ), 0 , 73 , 0 , 215, 135), // #827
+ INST(Vbroadcastf128 , VexRm , V(660F38,1A,_,1,0,_,_,_ ), 0 , 115, 0 , 216, 135), // #828
+ INST(Vbroadcastf32x2 , VexRm_Lx , E(660F38,19,_,x,_,0,3,T2 ), 0 , 116, 0 , 217, 140), // #829
+ INST(Vbroadcastf32x4 , VexRm_Lx , E(660F38,1A,_,x,_,0,4,T4 ), 0 , 117, 0 , 218, 68 ), // #830
+ INST(Vbroadcastf32x8 , VexRm , E(660F38,1B,_,2,_,0,5,T8 ), 0 , 118, 0 , 219, 66 ), // #831
+ INST(Vbroadcastf64x2 , VexRm_Lx , E(660F38,1A,_,x,_,1,4,T2 ), 0 , 119, 0 , 218, 140), // #832
+ INST(Vbroadcastf64x4 , VexRm , E(660F38,1B,_,2,_,1,5,T4 ), 0 , 120, 0 , 219, 68 ), // #833
+ INST(Vbroadcasti128 , VexRm , V(660F38,5A,_,1,0,_,_,_ ), 0 , 115, 0 , 216, 141), // #834
+ INST(Vbroadcasti32x2 , VexRm_Lx , E(660F38,59,_,x,_,0,3,T2 ), 0 , 116, 0 , 220, 140), // #835
+ INST(Vbroadcasti32x4 , VexRm_Lx , E(660F38,5A,_,x,_,0,4,T4 ), 0 , 117, 0 , 218, 138), // #836
+ INST(Vbroadcasti32x8 , VexRm , E(660F38,5B,_,2,_,0,5,T8 ), 0 , 118, 0 , 219, 66 ), // #837
+ INST(Vbroadcasti64x2 , VexRm_Lx , E(660F38,5A,_,x,_,1,4,T2 ), 0 , 119, 0 , 218, 140), // #838
+ INST(Vbroadcasti64x4 , VexRm , E(660F38,5B,_,2,_,1,5,T4 ), 0 , 120, 0 , 219, 68 ), // #839
+ INST(Vbroadcastsd , VexRm_Lx , V(660F38,19,_,x,0,1,3,T1S), 0 , 121, 0 , 221, 142), // #840
+ INST(Vbroadcastss , VexRm_Lx , V(660F38,18,_,x,0,0,2,T1S), 0 , 122, 0 , 222, 142), // #841
+ INST(Vcmppd , VexRvmi_Lx_KEvex , V(660F00,C2,_,x,I,1,4,FV ), 0 , 103, 0 , 223, 131), // #842
+ INST(Vcmpph , VexRvmi_Lx_KEvex , E(000F3A,C2,_,_,_,0,4,FV ), 0 , 123, 0 , 224, 132), // #843
+ INST(Vcmpps , VexRvmi_Lx_KEvex , V(000F00,C2,_,x,I,0,4,FV ), 0 , 105, 0 , 225, 131), // #844
+ INST(Vcmpsd , VexRvmi_KEvex , V(F20F00,C2,_,I,I,1,3,T1S), 0 , 106, 0 , 226, 133), // #845
+ INST(Vcmpsh , VexRvmi_KEvex , E(F30F3A,C2,_,_,_,0,1,T1S), 0 , 124, 0 , 227, 134), // #846
+ INST(Vcmpss , VexRvmi_KEvex , V(F30F00,C2,_,I,I,0,2,T1S), 0 , 108, 0 , 228, 133), // #847
+ INST(Vcomisd , VexRm , V(660F00,2F,_,I,I,1,3,T1S), 0 , 125, 0 , 229, 143), // #848
+ INST(Vcomish , VexRm , E(00MAP5,2F,_,_,_,0,1,T1S), 0 , 126, 0 , 230, 134), // #849
+ INST(Vcomiss , VexRm , V(000F00,2F,_,I,I,0,2,T1S), 0 , 127, 0 , 231, 143), // #850
+ INST(Vcompresspd , VexMr_Lx , E(660F38,8A,_,x,_,1,3,T1S), 0 , 128, 0 , 232, 138), // #851
+ INST(Vcompressps , VexMr_Lx , E(660F38,8A,_,x,_,0,2,T1S), 0 , 129, 0 , 232, 138), // #852
+ INST(Vcvtdq2pd , VexRm_Lx , V(F30F00,E6,_,x,I,0,3,HV ), 0 , 130, 0 , 233, 131), // #853
+ INST(Vcvtdq2ph , VexRm_Lx , E(00MAP5,5B,_,_,_,0,4,FV ), 0 , 104, 0 , 234, 132), // #854
+ INST(Vcvtdq2ps , VexRm_Lx , V(000F00,5B,_,x,I,0,4,FV ), 0 , 105, 0 , 235, 131), // #855
+ INST(Vcvtne2ps2bf16 , VexRvm_Lx , E(F20F38,72,_,_,_,0,4,FV ), 0 , 131, 0 , 213, 144), // #856
+ INST(Vcvtneps2bf16 , VexRm_Lx_Narrow , E(F30F38,72,_,_,_,0,4,FV ), 0 , 132, 0 , 236, 144), // #857
+ INST(Vcvtpd2dq , VexRm_Lx_Narrow , V(F20F00,E6,_,x,I,1,4,FV ), 0 , 133, 0 , 237, 131), // #858
+ INST(Vcvtpd2ph , VexRm_Lx , E(66MAP5,5A,_,_,_,1,4,FV ), 0 , 134, 0 , 238, 132), // #859
+ INST(Vcvtpd2ps , VexRm_Lx_Narrow , V(660F00,5A,_,x,I,1,4,FV ), 0 , 103, 0 , 237, 131), // #860
+ INST(Vcvtpd2qq , VexRm_Lx , E(660F00,7B,_,x,_,1,4,FV ), 0 , 135, 0 , 239, 140), // #861
+ INST(Vcvtpd2udq , VexRm_Lx_Narrow , E(000F00,79,_,x,_,1,4,FV ), 0 , 136, 0 , 240, 138), // #862
+ INST(Vcvtpd2uqq , VexRm_Lx , E(660F00,79,_,x,_,1,4,FV ), 0 , 135, 0 , 239, 140), // #863
+ INST(Vcvtph2dq , VexRm_Lx , E(66MAP5,5B,_,_,_,0,3,HV ), 0 , 137, 0 , 241, 132), // #864
+ INST(Vcvtph2pd , VexRm_Lx , E(00MAP5,5A,_,_,_,0,2,QV ), 0 , 138, 0 , 242, 132), // #865
+ INST(Vcvtph2ps , VexRm_Lx , V(660F38,13,_,x,0,0,3,HVM), 0 , 139, 0 , 243, 145), // #866
+ INST(Vcvtph2psx , VexRm_Lx , E(66MAP6,13,_,_,_,0,3,HV ), 0 , 140, 0 , 244, 132), // #867
+ INST(Vcvtph2qq , VexRm_Lx , E(66MAP5,7B,_,_,_,0,2,QV ), 0 , 141, 0 , 245, 132), // #868
+ INST(Vcvtph2udq , VexRm_Lx , E(00MAP5,79,_,_,_,0,3,HV ), 0 , 142, 0 , 241, 132), // #869
+ INST(Vcvtph2uqq , VexRm_Lx , E(66MAP5,79,_,_,_,0,2,QV ), 0 , 141, 0 , 245, 132), // #870
+ INST(Vcvtph2uw , VexRm_Lx , E(00MAP5,7D,_,_,_,0,4,FV ), 0 , 104, 0 , 246, 132), // #871
+ INST(Vcvtph2w , VexRm_Lx , E(66MAP5,7D,_,_,_,0,4,FV ), 0 , 143, 0 , 246, 132), // #872
+ INST(Vcvtps2dq , VexRm_Lx , V(660F00,5B,_,x,I,0,4,FV ), 0 , 144, 0 , 235, 131), // #873
+ INST(Vcvtps2pd , VexRm_Lx , V(000F00,5A,_,x,I,0,3,HV ), 0 , 145, 0 , 247, 131), // #874
+ INST(Vcvtps2ph , VexMri_Lx , V(660F3A,1D,_,x,0,0,3,HVM), 0 , 146, 0 , 248, 145), // #875
+ INST(Vcvtps2phx , VexRm_Lx , E(66MAP5,1D,_,_,_,0,4,FV ), 0 , 143, 0 , 234, 132), // #876
+ INST(Vcvtps2qq , VexRm_Lx , E(660F00,7B,_,x,_,0,3,HV ), 0 , 147, 0 , 249, 140), // #877
+ INST(Vcvtps2udq , VexRm_Lx , E(000F00,79,_,x,_,0,4,FV ), 0 , 148, 0 , 250, 138), // #878
+ INST(Vcvtps2uqq , VexRm_Lx , E(660F00,79,_,x,_,0,3,HV ), 0 , 147, 0 , 249, 140), // #879
+ INST(Vcvtqq2pd , VexRm_Lx , E(F30F00,E6,_,x,_,1,4,FV ), 0 , 149, 0 , 239, 140), // #880
+ INST(Vcvtqq2ph , VexRm_Lx , E(00MAP5,5B,_,_,_,1,4,FV ), 0 , 150, 0 , 238, 132), // #881
+ INST(Vcvtqq2ps , VexRm_Lx_Narrow , E(000F00,5B,_,x,_,1,4,FV ), 0 , 136, 0 , 240, 140), // #882
+ INST(Vcvtsd2sh , VexRvm , E(F2MAP5,5A,_,_,_,1,3,T1S), 0 , 151, 0 , 251, 134), // #883
+ INST(Vcvtsd2si , VexRm_Wx , V(F20F00,2D,_,I,x,x,3,T1F), 0 , 152, 0 , 252, 133), // #884
+ INST(Vcvtsd2ss , VexRvm , V(F20F00,5A,_,I,I,1,3,T1S), 0 , 106, 0 , 199, 133), // #885
+ INST(Vcvtsd2usi , VexRm_Wx , E(F20F00,79,_,I,_,x,3,T1F), 0 , 153, 0 , 253, 68 ), // #886
+ INST(Vcvtsh2sd , VexRvm , E(F3MAP5,5A,_,_,_,0,1,T1S), 0 , 107, 0 , 254, 134), // #887
+ INST(Vcvtsh2si , VexRm_Wx , E(F3MAP5,2D,_,_,_,x,1,T1S), 0 , 107, 0 , 255, 134), // #888
+ INST(Vcvtsh2ss , VexRvm , E(00MAP6,13,_,_,_,0,1,T1S), 0 , 154, 0 , 254, 134), // #889
+ INST(Vcvtsh2usi , VexRm_Wx , E(F3MAP5,79,_,_,_,x,1,T1S), 0 , 107, 0 , 255, 134), // #890
+ INST(Vcvtsi2sd , VexRvm_Wx , V(F20F00,2A,_,I,x,x,2,T1W), 0 , 155, 0 , 256, 133), // #891
+ INST(Vcvtsi2sh , VexRvm_Wx , E(F3MAP5,2A,_,_,_,x,2,T1W), 0 , 156, 0 , 257, 134), // #892
+ INST(Vcvtsi2ss , VexRvm_Wx , V(F30F00,2A,_,I,x,x,2,T1W), 0 , 157, 0 , 256, 133), // #893
+ INST(Vcvtss2sd , VexRvm , V(F30F00,5A,_,I,I,0,2,T1S), 0 , 108, 0 , 258, 133), // #894
+ INST(Vcvtss2sh , VexRvm , E(00MAP5,1D,_,_,_,0,2,T1S), 0 , 158, 0 , 259, 134), // #895
+ INST(Vcvtss2si , VexRm_Wx , V(F30F00,2D,_,I,x,x,2,T1F), 0 , 108, 0 , 260, 133), // #896
+ INST(Vcvtss2usi , VexRm_Wx , E(F30F00,79,_,I,_,x,2,T1F), 0 , 159, 0 , 261, 68 ), // #897
+ INST(Vcvttpd2dq , VexRm_Lx_Narrow , V(660F00,E6,_,x,I,1,4,FV ), 0 , 103, 0 , 262, 131), // #898
+ INST(Vcvttpd2qq , VexRm_Lx , E(660F00,7A,_,x,_,1,4,FV ), 0 , 135, 0 , 263, 138), // #899
+ INST(Vcvttpd2udq , VexRm_Lx_Narrow , E(000F00,78,_,x,_,1,4,FV ), 0 , 136, 0 , 264, 138), // #900
+ INST(Vcvttpd2uqq , VexRm_Lx , E(660F00,78,_,x,_,1,4,FV ), 0 , 135, 0 , 263, 140), // #901
+ INST(Vcvttph2dq , VexRm_Lx , E(F3MAP5,5B,_,_,_,0,3,HV ), 0 , 160, 0 , 244, 132), // #902
+ INST(Vcvttph2qq , VexRm_Lx , E(66MAP5,7A,_,_,_,0,2,QV ), 0 , 141, 0 , 242, 132), // #903
+ INST(Vcvttph2udq , VexRm_Lx , E(00MAP5,78,_,_,_,0,3,HV ), 0 , 142, 0 , 244, 132), // #904
+ INST(Vcvttph2uqq , VexRm_Lx , E(66MAP5,78,_,_,_,0,2,QV ), 0 , 141, 0 , 242, 132), // #905
+ INST(Vcvttph2uw , VexRm_Lx , E(00MAP5,7C,_,_,_,0,4,FV ), 0 , 104, 0 , 265, 132), // #906
+ INST(Vcvttph2w , VexRm_Lx , E(66MAP5,7C,_,_,_,0,4,FV ), 0 , 143, 0 , 265, 132), // #907
+ INST(Vcvttps2dq , VexRm_Lx , V(F30F00,5B,_,x,I,0,4,FV ), 0 , 161, 0 , 266, 131), // #908
+ INST(Vcvttps2qq , VexRm_Lx , E(660F00,7A,_,x,_,0,3,HV ), 0 , 147, 0 , 267, 140), // #909
+ INST(Vcvttps2udq , VexRm_Lx , E(000F00,78,_,x,_,0,4,FV ), 0 , 148, 0 , 268, 138), // #910
+ INST(Vcvttps2uqq , VexRm_Lx , E(660F00,78,_,x,_,0,3,HV ), 0 , 147, 0 , 267, 140), // #911
+ INST(Vcvttsd2si , VexRm_Wx , V(F20F00,2C,_,I,x,x,3,T1F), 0 , 152, 0 , 269, 133), // #912
+ INST(Vcvttsd2usi , VexRm_Wx , E(F20F00,78,_,I,_,x,3,T1F), 0 , 153, 0 , 270, 68 ), // #913
+ INST(Vcvttsh2si , VexRm_Wx , E(F3MAP5,2C,_,_,_,x,1,T1S), 0 , 107, 0 , 271, 134), // #914
+ INST(Vcvttsh2usi , VexRm_Wx , E(F3MAP5,78,_,_,_,x,1,T1S), 0 , 107, 0 , 271, 134), // #915
+ INST(Vcvttss2si , VexRm_Wx , V(F30F00,2C,_,I,x,x,2,T1F), 0 , 108, 0 , 272, 133), // #916
+ INST(Vcvttss2usi , VexRm_Wx , E(F30F00,78,_,I,_,x,2,T1F), 0 , 159, 0 , 273, 68 ), // #917
+ INST(Vcvtudq2pd , VexRm_Lx , E(F30F00,7A,_,x,_,0,3,HV ), 0 , 162, 0 , 274, 138), // #918
+ INST(Vcvtudq2ph , VexRm_Lx , E(F2MAP5,7A,_,_,_,0,4,FV ), 0 , 163, 0 , 234, 132), // #919
+ INST(Vcvtudq2ps , VexRm_Lx , E(F20F00,7A,_,x,_,0,4,FV ), 0 , 164, 0 , 250, 138), // #920
+ INST(Vcvtuqq2pd , VexRm_Lx , E(F30F00,7A,_,x,_,1,4,FV ), 0 , 149, 0 , 239, 140), // #921
+ INST(Vcvtuqq2ph , VexRm_Lx , E(F2MAP5,7A,_,_,_,1,4,FV ), 0 , 165, 0 , 238, 132), // #922
+ INST(Vcvtuqq2ps , VexRm_Lx_Narrow , E(F20F00,7A,_,x,_,1,4,FV ), 0 , 166, 0 , 240, 140), // #923
+ INST(Vcvtusi2sd , VexRvm_Wx , E(F20F00,7B,_,I,_,x,2,T1W), 0 , 167, 0 , 257, 68 ), // #924
+ INST(Vcvtusi2sh , VexRvm_Wx , E(F3MAP5,7B,_,_,_,x,2,T1W), 0 , 156, 0 , 257, 134), // #925
+ INST(Vcvtusi2ss , VexRvm_Wx , E(F30F00,7B,_,I,_,x,2,T1W), 0 , 168, 0 , 257, 68 ), // #926
+ INST(Vcvtuw2ph , VexRm_Lx , E(F2MAP5,7D,_,_,_,0,4,FV ), 0 , 163, 0 , 246, 132), // #927
+ INST(Vcvtw2ph , VexRm_Lx , E(F3MAP5,7D,_,_,_,0,4,FV ), 0 , 169, 0 , 246, 132), // #928
+ INST(Vdbpsadbw , VexRvmi_Lx , E(660F3A,42,_,x,_,0,4,FVM), 0 , 111, 0 , 275, 146), // #929
+ INST(Vdivpd , VexRvm_Lx , V(660F00,5E,_,x,I,1,4,FV ), 0 , 103, 0 , 196, 131), // #930
+ INST(Vdivph , VexRvm_Lx , E(00MAP5,5E,_,_,_,0,4,FV ), 0 , 104, 0 , 197, 132), // #931
+ INST(Vdivps , VexRvm_Lx , V(000F00,5E,_,x,I,0,4,FV ), 0 , 105, 0 , 198, 131), // #932
+ INST(Vdivsd , VexRvm , V(F20F00,5E,_,I,I,1,3,T1S), 0 , 106, 0 , 199, 133), // #933
+ INST(Vdivsh , VexRvm , E(F3MAP5,5E,_,_,_,0,1,T1S), 0 , 107, 0 , 200, 134), // #934
+ INST(Vdivss , VexRvm , V(F30F00,5E,_,I,I,0,2,T1S), 0 , 108, 0 , 201, 133), // #935
+ INST(Vdpbf16ps , VexRvm_Lx , E(F30F38,52,_,_,_,0,4,FV ), 0 , 132, 0 , 213, 144), // #936
+ INST(Vdppd , VexRvmi_Lx , V(660F3A,41,_,x,I,_,_,_ ), 0 , 73 , 0 , 276, 135), // #937
+ INST(Vdpps , VexRvmi_Lx , V(660F3A,40,_,x,I,_,_,_ ), 0 , 73 , 0 , 214, 135), // #938
+ INST(Verr , X86M_NoSize , O(000F00,00,4,_,_,_,_,_ ), 0 , 97 , 0 , 107, 10 ), // #939
+ INST(Verw , X86M_NoSize , O(000F00,00,5,_,_,_,_,_ ), 0 , 77 , 0 , 107, 10 ), // #940
+ INST(Vexp2pd , VexRm , E(660F38,C8,_,2,_,1,4,FV ), 0 , 170, 0 , 277, 147), // #941
+ INST(Vexp2ps , VexRm , E(660F38,C8,_,2,_,0,4,FV ), 0 , 171, 0 , 278, 147), // #942
+ INST(Vexpandpd , VexRm_Lx , E(660F38,88,_,x,_,1,3,T1S), 0 , 128, 0 , 279, 138), // #943
+ INST(Vexpandps , VexRm_Lx , E(660F38,88,_,x,_,0,2,T1S), 0 , 129, 0 , 279, 138), // #944
+ INST(Vextractf128 , VexMri , V(660F3A,19,_,1,0,_,_,_ ), 0 , 172, 0 , 280, 135), // #945
+ INST(Vextractf32x4 , VexMri_Lx , E(660F3A,19,_,x,_,0,4,T4 ), 0 , 173, 0 , 281, 138), // #946
+ INST(Vextractf32x8 , VexMri , E(660F3A,1B,_,2,_,0,5,T8 ), 0 , 174, 0 , 282, 66 ), // #947
+ INST(Vextractf64x2 , VexMri_Lx , E(660F3A,19,_,x,_,1,4,T2 ), 0 , 175, 0 , 281, 140), // #948
+ INST(Vextractf64x4 , VexMri , E(660F3A,1B,_,2,_,1,5,T4 ), 0 , 176, 0 , 282, 68 ), // #949
+ INST(Vextracti128 , VexMri , V(660F3A,39,_,1,0,_,_,_ ), 0 , 172, 0 , 280, 141), // #950
+ INST(Vextracti32x4 , VexMri_Lx , E(660F3A,39,_,x,_,0,4,T4 ), 0 , 173, 0 , 281, 138), // #951
+ INST(Vextracti32x8 , VexMri , E(660F3A,3B,_,2,_,0,5,T8 ), 0 , 174, 0 , 282, 66 ), // #952
+ INST(Vextracti64x2 , VexMri_Lx , E(660F3A,39,_,x,_,1,4,T2 ), 0 , 175, 0 , 281, 140), // #953
+ INST(Vextracti64x4 , VexMri , E(660F3A,3B,_,2,_,1,5,T4 ), 0 , 176, 0 , 282, 68 ), // #954
+ INST(Vextractps , VexMri , V(660F3A,17,_,0,I,I,2,T1S), 0 , 177, 0 , 283, 133), // #955
+ INST(Vfcmaddcph , VexRvm_Lx , E(F2MAP6,56,_,_,_,0,4,FV ), 0 , 178, 0 , 284, 132), // #956
+ INST(Vfcmaddcsh , VexRvm , E(F2MAP6,57,_,_,_,0,2,T1S), 0 , 179, 0 , 259, 132), // #957
+ INST(Vfcmulcph , VexRvm_Lx , E(F2MAP6,D6,_,_,_,0,4,FV ), 0 , 178, 0 , 284, 132), // #958
+ INST(Vfcmulcsh , VexRvm , E(F2MAP6,D7,_,_,_,0,2,T1S), 0 , 179, 0 , 259, 132), // #959
+ INST(Vfixupimmpd , VexRvmi_Lx , E(660F3A,54,_,x,_,1,4,FV ), 0 , 112, 0 , 285, 138), // #960
+ INST(Vfixupimmps , VexRvmi_Lx , E(660F3A,54,_,x,_,0,4,FV ), 0 , 111, 0 , 286, 138), // #961
+ INST(Vfixupimmsd , VexRvmi , E(660F3A,55,_,I,_,1,3,T1S), 0 , 180, 0 , 287, 68 ), // #962
+ INST(Vfixupimmss , VexRvmi , E(660F3A,55,_,I,_,0,2,T1S), 0 , 181, 0 , 288, 68 ), // #963
+ INST(Vfmadd132pd , VexRvm_Lx , V(660F38,98,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #964
+ INST(Vfmadd132ph , VexRvm_Lx , E(66MAP6,98,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #965
+ INST(Vfmadd132ps , VexRvm_Lx , V(660F38,98,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #966
+ INST(Vfmadd132sd , VexRvm , V(660F38,99,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #967
+ INST(Vfmadd132sh , VexRvm , E(66MAP6,99,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #968
+ INST(Vfmadd132ss , VexRvm , V(660F38,99,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #969
+ INST(Vfmadd213pd , VexRvm_Lx , V(660F38,A8,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #970
+ INST(Vfmadd213ph , VexRvm_Lx , E(66MAP6,A8,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #971
+ INST(Vfmadd213ps , VexRvm_Lx , V(660F38,A8,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #972
+ INST(Vfmadd213sd , VexRvm , V(660F38,A9,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #973
+ INST(Vfmadd213sh , VexRvm , E(66MAP6,A9,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #974
+ INST(Vfmadd213ss , VexRvm , V(660F38,A9,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #975
+ INST(Vfmadd231pd , VexRvm_Lx , V(660F38,B8,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #976
+ INST(Vfmadd231ph , VexRvm_Lx , E(66MAP6,B8,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #977
+ INST(Vfmadd231ps , VexRvm_Lx , V(660F38,B8,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #978
+ INST(Vfmadd231sd , VexRvm , V(660F38,B9,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #979
+ INST(Vfmadd231sh , VexRvm , E(66MAP6,B9,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #980
+ INST(Vfmadd231ss , VexRvm , V(660F38,B9,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #981
+ INST(Vfmaddcph , VexRvm_Lx , E(F3MAP6,56,_,_,_,0,4,FV ), 0 , 186, 0 , 284, 132), // #982
+ INST(Vfmaddcsh , VexRvm , E(F3MAP6,57,_,_,_,0,2,T1S), 0 , 187, 0 , 259, 132), // #983
+ INST(Vfmaddpd , Fma4_Lx , V(660F3A,69,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #984
+ INST(Vfmaddps , Fma4_Lx , V(660F3A,68,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #985
+ INST(Vfmaddsd , Fma4 , V(660F3A,6B,_,0,x,_,_,_ ), 0 , 73 , 0 , 290, 150), // #986
+ INST(Vfmaddss , Fma4 , V(660F3A,6A,_,0,x,_,_,_ ), 0 , 73 , 0 , 291, 150), // #987
+ INST(Vfmaddsub132pd , VexRvm_Lx , V(660F38,96,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #988
+ INST(Vfmaddsub132ph , VexRvm_Lx , E(66MAP6,96,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #989
+ INST(Vfmaddsub132ps , VexRvm_Lx , V(660F38,96,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #990
+ INST(Vfmaddsub213pd , VexRvm_Lx , V(660F38,A6,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #991
+ INST(Vfmaddsub213ph , VexRvm_Lx , E(66MAP6,A6,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #992
+ INST(Vfmaddsub213ps , VexRvm_Lx , V(660F38,A6,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #993
+ INST(Vfmaddsub231pd , VexRvm_Lx , V(660F38,B6,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #994
+ INST(Vfmaddsub231ph , VexRvm_Lx , E(66MAP6,B6,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #995
+ INST(Vfmaddsub231ps , VexRvm_Lx , V(660F38,B6,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #996
+ INST(Vfmaddsubpd , Fma4_Lx , V(660F3A,5D,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #997
+ INST(Vfmaddsubps , Fma4_Lx , V(660F3A,5C,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #998
+ INST(Vfmsub132pd , VexRvm_Lx , V(660F38,9A,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #999
+ INST(Vfmsub132ph , VexRvm_Lx , E(66MAP6,9A,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1000
+ INST(Vfmsub132ps , VexRvm_Lx , V(660F38,9A,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1001
+ INST(Vfmsub132sd , VexRvm , V(660F38,9B,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #1002
+ INST(Vfmsub132sh , VexRvm , E(66MAP6,9B,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #1003
+ INST(Vfmsub132ss , VexRvm , V(660F38,9B,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #1004
+ INST(Vfmsub213pd , VexRvm_Lx , V(660F38,AA,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1005
+ INST(Vfmsub213ph , VexRvm_Lx , E(66MAP6,AA,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1006
+ INST(Vfmsub213ps , VexRvm_Lx , V(660F38,AA,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1007
+ INST(Vfmsub213sd , VexRvm , V(660F38,AB,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #1008
+ INST(Vfmsub213sh , VexRvm , E(66MAP6,AB,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #1009
+ INST(Vfmsub213ss , VexRvm , V(660F38,AB,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #1010
+ INST(Vfmsub231pd , VexRvm_Lx , V(660F38,BA,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1011
+ INST(Vfmsub231ph , VexRvm_Lx , E(66MAP6,BA,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1012
+ INST(Vfmsub231ps , VexRvm_Lx , V(660F38,BA,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1013
+ INST(Vfmsub231sd , VexRvm , V(660F38,BB,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #1014
+ INST(Vfmsub231sh , VexRvm , E(66MAP6,BB,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #1015
+ INST(Vfmsub231ss , VexRvm , V(660F38,BB,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #1016
+ INST(Vfmsubadd132pd , VexRvm_Lx , V(660F38,97,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1017
+ INST(Vfmsubadd132ph , VexRvm_Lx , E(66MAP6,97,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1018
+ INST(Vfmsubadd132ps , VexRvm_Lx , V(660F38,97,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1019
+ INST(Vfmsubadd213pd , VexRvm_Lx , V(660F38,A7,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1020
+ INST(Vfmsubadd213ph , VexRvm_Lx , E(66MAP6,A7,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1021
+ INST(Vfmsubadd213ps , VexRvm_Lx , V(660F38,A7,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1022
+ INST(Vfmsubadd231pd , VexRvm_Lx , V(660F38,B7,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1023
+ INST(Vfmsubadd231ph , VexRvm_Lx , E(66MAP6,B7,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1024
+ INST(Vfmsubadd231ps , VexRvm_Lx , V(660F38,B7,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1025
+ INST(Vfmsubaddpd , Fma4_Lx , V(660F3A,5F,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #1026
+ INST(Vfmsubaddps , Fma4_Lx , V(660F3A,5E,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #1027
+ INST(Vfmsubpd , Fma4_Lx , V(660F3A,6D,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #1028
+ INST(Vfmsubps , Fma4_Lx , V(660F3A,6C,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #1029
+ INST(Vfmsubsd , Fma4 , V(660F3A,6F,_,0,x,_,_,_ ), 0 , 73 , 0 , 290, 150), // #1030
+ INST(Vfmsubss , Fma4 , V(660F3A,6E,_,0,x,_,_,_ ), 0 , 73 , 0 , 291, 150), // #1031
+ INST(Vfmulcph , VexRvm_Lx , E(F3MAP6,D6,_,_,_,0,4,FV ), 0 , 186, 0 , 284, 132), // #1032
+ INST(Vfmulcsh , VexRvm , E(F3MAP6,D7,_,_,_,0,2,T1S), 0 , 187, 0 , 259, 132), // #1033
+ INST(Vfnmadd132pd , VexRvm_Lx , V(660F38,9C,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1034
+ INST(Vfnmadd132ph , VexRvm_Lx , E(66MAP6,9C,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1035
+ INST(Vfnmadd132ps , VexRvm_Lx , V(660F38,9C,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1036
+ INST(Vfnmadd132sd , VexRvm , V(660F38,9D,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #1037
+ INST(Vfnmadd132sh , VexRvm , E(66MAP6,9D,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #1038
+ INST(Vfnmadd132ss , VexRvm , V(660F38,9D,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #1039
+ INST(Vfnmadd213pd , VexRvm_Lx , V(660F38,AC,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1040
+ INST(Vfnmadd213ph , VexRvm_Lx , E(66MAP6,AC,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1041
+ INST(Vfnmadd213ps , VexRvm_Lx , V(660F38,AC,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1042
+ INST(Vfnmadd213sd , VexRvm , V(660F38,AD,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #1043
+ INST(Vfnmadd213sh , VexRvm , E(66MAP6,AD,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #1044
+ INST(Vfnmadd213ss , VexRvm , V(660F38,AD,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #1045
+ INST(Vfnmadd231pd , VexRvm_Lx , V(660F38,BC,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1046
+ INST(Vfnmadd231ph , VexRvm_Lx , E(66MAP6,BC,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1047
+ INST(Vfnmadd231ps , VexRvm_Lx , V(660F38,BC,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1048
+ INST(Vfnmadd231sd , VexRvm , V(660F38,BD,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #1049
+ INST(Vfnmadd231sh , VexRvm , E(66MAP6,BD,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #1050
+ INST(Vfnmadd231ss , VexRvm , V(660F38,BD,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #1051
+ INST(Vfnmaddpd , Fma4_Lx , V(660F3A,79,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #1052
+ INST(Vfnmaddps , Fma4_Lx , V(660F3A,78,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #1053
+ INST(Vfnmaddsd , Fma4 , V(660F3A,7B,_,0,x,_,_,_ ), 0 , 73 , 0 , 290, 150), // #1054
+ INST(Vfnmaddss , Fma4 , V(660F3A,7A,_,0,x,_,_,_ ), 0 , 73 , 0 , 291, 150), // #1055
+ INST(Vfnmsub132pd , VexRvm_Lx , V(660F38,9E,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1056
+ INST(Vfnmsub132ph , VexRvm_Lx , E(66MAP6,9E,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1057
+ INST(Vfnmsub132ps , VexRvm_Lx , V(660F38,9E,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1058
+ INST(Vfnmsub132sd , VexRvm , V(660F38,9F,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #1059
+ INST(Vfnmsub132sh , VexRvm , E(66MAP6,9F,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #1060
+ INST(Vfnmsub132ss , VexRvm , V(660F38,9F,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #1061
+ INST(Vfnmsub213pd , VexRvm_Lx , V(660F38,AE,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1062
+ INST(Vfnmsub213ph , VexRvm_Lx , E(66MAP6,AE,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1063
+ INST(Vfnmsub213ps , VexRvm_Lx , V(660F38,AE,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1064
+ INST(Vfnmsub213sd , VexRvm , V(660F38,AF,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #1065
+ INST(Vfnmsub213sh , VexRvm , E(66MAP6,AF,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #1066
+ INST(Vfnmsub213ss , VexRvm , V(660F38,AF,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #1067
+ INST(Vfnmsub231pd , VexRvm_Lx , V(660F38,BE,_,x,1,1,4,FV ), 0 , 182, 0 , 196, 148), // #1068
+ INST(Vfnmsub231ph , VexRvm_Lx , E(66MAP6,BE,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1069
+ INST(Vfnmsub231ps , VexRvm_Lx , V(660F38,BE,_,x,0,0,4,FV ), 0 , 110, 0 , 198, 148), // #1070
+ INST(Vfnmsub231sd , VexRvm , V(660F38,BF,_,I,1,1,3,T1S), 0 , 184, 0 , 199, 149), // #1071
+ INST(Vfnmsub231sh , VexRvm , E(66MAP6,BF,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #1072
+ INST(Vfnmsub231ss , VexRvm , V(660F38,BF,_,I,0,0,2,T1S), 0 , 122, 0 , 201, 149), // #1073
+ INST(Vfnmsubpd , Fma4_Lx , V(660F3A,7D,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #1074
+ INST(Vfnmsubps , Fma4_Lx , V(660F3A,7C,_,x,x,_,_,_ ), 0 , 73 , 0 , 289, 150), // #1075
+ INST(Vfnmsubsd , Fma4 , V(660F3A,7F,_,0,x,_,_,_ ), 0 , 73 , 0 , 290, 150), // #1076
+ INST(Vfnmsubss , Fma4 , V(660F3A,7E,_,0,x,_,_,_ ), 0 , 73 , 0 , 291, 150), // #1077
+ INST(Vfpclasspd , VexRmi_Lx , E(660F3A,66,_,x,_,1,4,FV ), 0 , 112, 0 , 292, 140), // #1078
+ INST(Vfpclassph , VexRmi_Lx , E(000F3A,66,_,_,_,0,4,FV ), 0 , 123, 0 , 293, 132), // #1079
+ INST(Vfpclassps , VexRmi_Lx , E(660F3A,66,_,x,_,0,4,FV ), 0 , 111, 0 , 294, 140), // #1080
+ INST(Vfpclasssd , VexRmi , E(660F3A,67,_,I,_,1,3,T1S), 0 , 180, 0 , 295, 66 ), // #1081
+ INST(Vfpclasssh , VexRmi , E(000F3A,67,_,_,_,0,1,T1S), 0 , 188, 0 , 296, 134), // #1082
+ INST(Vfpclassss , VexRmi , E(660F3A,67,_,I,_,0,2,T1S), 0 , 181, 0 , 297, 66 ), // #1083
+ INST(Vfrczpd , VexRm_Lx , V(XOP_M9,81,_,x,0,_,_,_ ), 0 , 79 , 0 , 298, 151), // #1084
+ INST(Vfrczps , VexRm_Lx , V(XOP_M9,80,_,x,0,_,_,_ ), 0 , 79 , 0 , 298, 151), // #1085
+ INST(Vfrczsd , VexRm , V(XOP_M9,83,_,0,0,_,_,_ ), 0 , 79 , 0 , 299, 151), // #1086
+ INST(Vfrczss , VexRm , V(XOP_M9,82,_,0,0,_,_,_ ), 0 , 79 , 0 , 300, 151), // #1087
+ INST(Vgatherdpd , VexRmvRm_VM , V(660F38,92,_,x,1,_,_,_ ), E(660F38,92,_,x,_,1,3,T1S), 189, 80 , 301, 152), // #1088
+ INST(Vgatherdps , VexRmvRm_VM , V(660F38,92,_,x,0,_,_,_ ), E(660F38,92,_,x,_,0,2,T1S), 96 , 81 , 302, 152), // #1089
+ INST(Vgatherpf0dpd , VexM_VM , E(660F38,C6,1,2,_,1,3,T1S), 0 , 190, 0 , 303, 153), // #1090
+ INST(Vgatherpf0dps , VexM_VM , E(660F38,C6,1,2,_,0,2,T1S), 0 , 191, 0 , 304, 153), // #1091
+ INST(Vgatherpf0qpd , VexM_VM , E(660F38,C7,1,2,_,1,3,T1S), 0 , 190, 0 , 305, 153), // #1092
+ INST(Vgatherpf0qps , VexM_VM , E(660F38,C7,1,2,_,0,2,T1S), 0 , 191, 0 , 305, 153), // #1093
+ INST(Vgatherpf1dpd , VexM_VM , E(660F38,C6,2,2,_,1,3,T1S), 0 , 192, 0 , 303, 153), // #1094
+ INST(Vgatherpf1dps , VexM_VM , E(660F38,C6,2,2,_,0,2,T1S), 0 , 193, 0 , 304, 153), // #1095
+ INST(Vgatherpf1qpd , VexM_VM , E(660F38,C7,2,2,_,1,3,T1S), 0 , 192, 0 , 305, 153), // #1096
+ INST(Vgatherpf1qps , VexM_VM , E(660F38,C7,2,2,_,0,2,T1S), 0 , 193, 0 , 305, 153), // #1097
+ INST(Vgatherqpd , VexRmvRm_VM , V(660F38,93,_,x,1,_,_,_ ), E(660F38,93,_,x,_,1,3,T1S), 189, 82 , 306, 152), // #1098
+ INST(Vgatherqps , VexRmvRm_VM , V(660F38,93,_,x,0,_,_,_ ), E(660F38,93,_,x,_,0,2,T1S), 96 , 83 , 307, 152), // #1099
+ INST(Vgetexppd , VexRm_Lx , E(660F38,42,_,x,_,1,4,FV ), 0 , 113, 0 , 263, 138), // #1100
+ INST(Vgetexpph , VexRm_Lx , E(66MAP6,42,_,_,_,0,4,FV ), 0 , 183, 0 , 265, 132), // #1101
+ INST(Vgetexpps , VexRm_Lx , E(660F38,42,_,x,_,0,4,FV ), 0 , 114, 0 , 268, 138), // #1102
+ INST(Vgetexpsd , VexRvm , E(660F38,43,_,I,_,1,3,T1S), 0 , 128, 0 , 308, 68 ), // #1103
+ INST(Vgetexpsh , VexRvm , E(66MAP6,43,_,_,_,0,1,T1S), 0 , 185, 0 , 254, 134), // #1104
+ INST(Vgetexpss , VexRvm , E(660F38,43,_,I,_,0,2,T1S), 0 , 129, 0 , 309, 68 ), // #1105
+ INST(Vgetmantpd , VexRmi_Lx , E(660F3A,26,_,x,_,1,4,FV ), 0 , 112, 0 , 310, 138), // #1106
+ INST(Vgetmantph , VexRmi_Lx , E(000F3A,26,_,_,_,0,4,FV ), 0 , 123, 0 , 311, 132), // #1107
+ INST(Vgetmantps , VexRmi_Lx , E(660F3A,26,_,x,_,0,4,FV ), 0 , 111, 0 , 312, 138), // #1108
+ INST(Vgetmantsd , VexRvmi , E(660F3A,27,_,I,_,1,3,T1S), 0 , 180, 0 , 287, 68 ), // #1109
+ INST(Vgetmantsh , VexRvmi , E(000F3A,27,_,_,_,0,1,T1S), 0 , 188, 0 , 313, 134), // #1110
+ INST(Vgetmantss , VexRvmi , E(660F3A,27,_,I,_,0,2,T1S), 0 , 181, 0 , 288, 68 ), // #1111
+ INST(Vgf2p8affineinvqb, VexRvmi_Lx , V(660F3A,CF,_,x,1,1,4,FV ), 0 , 194, 0 , 314, 154), // #1112
+ INST(Vgf2p8affineqb , VexRvmi_Lx , V(660F3A,CE,_,x,1,1,4,FV ), 0 , 194, 0 , 314, 154), // #1113
+ INST(Vgf2p8mulb , VexRvm_Lx , V(660F38,CF,_,x,0,0,4,FV ), 0 , 110, 0 , 315, 154), // #1114
+ INST(Vhaddpd , VexRvm_Lx , V(660F00,7C,_,x,I,_,_,_ ), 0 , 69 , 0 , 202, 135), // #1115
+ INST(Vhaddps , VexRvm_Lx , V(F20F00,7C,_,x,I,_,_,_ ), 0 , 109, 0 , 202, 135), // #1116
+ INST(Vhsubpd , VexRvm_Lx , V(660F00,7D,_,x,I,_,_,_ ), 0 , 69 , 0 , 202, 135), // #1117
+ INST(Vhsubps , VexRvm_Lx , V(F20F00,7D,_,x,I,_,_,_ ), 0 , 109, 0 , 202, 135), // #1118
+ INST(Vinsertf128 , VexRvmi , V(660F3A,18,_,1,0,_,_,_ ), 0 , 172, 0 , 316, 135), // #1119
+ INST(Vinsertf32x4 , VexRvmi_Lx , E(660F3A,18,_,x,_,0,4,T4 ), 0 , 173, 0 , 317, 138), // #1120
+ INST(Vinsertf32x8 , VexRvmi , E(660F3A,1A,_,2,_,0,5,T8 ), 0 , 174, 0 , 318, 66 ), // #1121
+ INST(Vinsertf64x2 , VexRvmi_Lx , E(660F3A,18,_,x,_,1,4,T2 ), 0 , 175, 0 , 317, 140), // #1122
+ INST(Vinsertf64x4 , VexRvmi , E(660F3A,1A,_,2,_,1,5,T4 ), 0 , 176, 0 , 318, 68 ), // #1123
+ INST(Vinserti128 , VexRvmi , V(660F3A,38,_,1,0,_,_,_ ), 0 , 172, 0 , 316, 141), // #1124
+ INST(Vinserti32x4 , VexRvmi_Lx , E(660F3A,38,_,x,_,0,4,T4 ), 0 , 173, 0 , 317, 138), // #1125
+ INST(Vinserti32x8 , VexRvmi , E(660F3A,3A,_,2,_,0,5,T8 ), 0 , 174, 0 , 318, 66 ), // #1126
+ INST(Vinserti64x2 , VexRvmi_Lx , E(660F3A,38,_,x,_,1,4,T2 ), 0 , 175, 0 , 317, 140), // #1127
+ INST(Vinserti64x4 , VexRvmi , E(660F3A,3A,_,2,_,1,5,T4 ), 0 , 176, 0 , 318, 68 ), // #1128
+ INST(Vinsertps , VexRvmi , V(660F3A,21,_,0,I,0,2,T1S), 0 , 177, 0 , 319, 133), // #1129
+ INST(Vlddqu , VexRm_Lx , V(F20F00,F0,_,x,I,_,_,_ ), 0 , 109, 0 , 320, 135), // #1130
+ INST(Vldmxcsr , VexM , V(000F00,AE,2,0,I,_,_,_ ), 0 , 195, 0 , 321, 135), // #1131
+ INST(Vmaskmovdqu , VexRm_ZDI , V(660F00,F7,_,0,I,_,_,_ ), 0 , 69 , 0 , 322, 135), // #1132
+ INST(Vmaskmovpd , VexRvmMvr_Lx , V(660F38,2D,_,x,0,_,_,_ ), V(660F38,2F,_,x,0,_,_,_ ), 96 , 84 , 323, 135), // #1133
+ INST(Vmaskmovps , VexRvmMvr_Lx , V(660F38,2C,_,x,0,_,_,_ ), V(660F38,2E,_,x,0,_,_,_ ), 96 , 85 , 323, 135), // #1134
+ INST(Vmaxpd , VexRvm_Lx , V(660F00,5F,_,x,I,1,4,FV ), 0 , 103, 0 , 324, 131), // #1135
+ INST(Vmaxph , VexRvm_Lx , E(00MAP5,5F,_,_,_,0,4,FV ), 0 , 104, 0 , 325, 132), // #1136
+ INST(Vmaxps , VexRvm_Lx , V(000F00,5F,_,x,I,0,4,FV ), 0 , 105, 0 , 326, 131), // #1137
+ INST(Vmaxsd , VexRvm , V(F20F00,5F,_,I,I,1,3,T1S), 0 , 106, 0 , 327, 131), // #1138
+ INST(Vmaxsh , VexRvm , E(F3MAP5,5F,_,_,_,0,1,T1S), 0 , 107, 0 , 254, 134), // #1139
+ INST(Vmaxss , VexRvm , V(F30F00,5F,_,I,I,0,2,T1S), 0 , 108, 0 , 258, 131), // #1140
+ INST(Vmcall , X86Op , O(000F01,C1,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 58 ), // #1141
+ INST(Vmclear , X86M_Only , O(660F00,C7,6,_,_,_,_,_ ), 0 , 26 , 0 , 32 , 58 ), // #1142
+ INST(Vmfunc , X86Op , O(000F01,D4,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 58 ), // #1143
+ INST(Vminpd , VexRvm_Lx , V(660F00,5D,_,x,I,1,4,FV ), 0 , 103, 0 , 324, 131), // #1144
+ INST(Vminph , VexRvm_Lx , E(00MAP5,5D,_,_,_,0,4,FV ), 0 , 104, 0 , 325, 132), // #1145
+ INST(Vminps , VexRvm_Lx , V(000F00,5D,_,x,I,0,4,FV ), 0 , 105, 0 , 326, 131), // #1146
+ INST(Vminsd , VexRvm , V(F20F00,5D,_,I,I,1,3,T1S), 0 , 106, 0 , 327, 131), // #1147
+ INST(Vminsh , VexRvm , E(F3MAP5,5D,_,_,_,0,1,T1S), 0 , 107, 0 , 254, 134), // #1148
+ INST(Vminss , VexRvm , V(F30F00,5D,_,I,I,0,2,T1S), 0 , 108, 0 , 258, 131), // #1149
+ INST(Vmlaunch , X86Op , O(000F01,C2,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 58 ), // #1150
+ INST(Vmload , X86Op_xAX , O(000F01,DA,_,_,_,_,_,_ ), 0 , 21 , 0 , 328, 22 ), // #1151
+ INST(Vmmcall , X86Op , O(000F01,D9,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 22 ), // #1152
+ INST(Vmovapd , VexRmMr_Lx , V(660F00,28,_,x,I,1,4,FVM), V(660F00,29,_,x,I,1,4,FVM), 103, 86 , 329, 155), // #1153
+ INST(Vmovaps , VexRmMr_Lx , V(000F00,28,_,x,I,0,4,FVM), V(000F00,29,_,x,I,0,4,FVM), 105, 87 , 329, 155), // #1154
+ INST(Vmovd , VexMovdMovq , V(660F00,6E,_,0,0,0,2,T1S), V(660F00,7E,_,0,0,0,2,T1S), 196, 88 , 330, 133), // #1155
+ INST(Vmovddup , VexRm_Lx , V(F20F00,12,_,x,I,1,3,DUP), 0 , 197, 0 , 331, 131), // #1156
+ INST(Vmovdqa , VexRmMr_Lx , V(660F00,6F,_,x,I,_,_,_ ), V(660F00,7F,_,x,I,_,_,_ ), 69 , 89 , 332, 156), // #1157
+ INST(Vmovdqa32 , VexRmMr_Lx , E(660F00,6F,_,x,_,0,4,FVM), E(660F00,7F,_,x,_,0,4,FVM), 198, 90 , 333, 157), // #1158
+ INST(Vmovdqa64 , VexRmMr_Lx , E(660F00,6F,_,x,_,1,4,FVM), E(660F00,7F,_,x,_,1,4,FVM), 135, 91 , 333, 157), // #1159
+ INST(Vmovdqu , VexRmMr_Lx , V(F30F00,6F,_,x,I,_,_,_ ), V(F30F00,7F,_,x,I,_,_,_ ), 199, 92 , 332, 156), // #1160
+ INST(Vmovdqu16 , VexRmMr_Lx , E(F20F00,6F,_,x,_,1,4,FVM), E(F20F00,7F,_,x,_,1,4,FVM), 166, 93 , 333, 158), // #1161
+ INST(Vmovdqu32 , VexRmMr_Lx , E(F30F00,6F,_,x,_,0,4,FVM), E(F30F00,7F,_,x,_,0,4,FVM), 200, 94 , 333, 157), // #1162
+ INST(Vmovdqu64 , VexRmMr_Lx , E(F30F00,6F,_,x,_,1,4,FVM), E(F30F00,7F,_,x,_,1,4,FVM), 149, 95 , 333, 157), // #1163
+ INST(Vmovdqu8 , VexRmMr_Lx , E(F20F00,6F,_,x,_,0,4,FVM), E(F20F00,7F,_,x,_,0,4,FVM), 164, 96 , 333, 158), // #1164
+ INST(Vmovhlps , VexRvm , V(000F00,12,_,0,I,0,_,_ ), 0 , 72 , 0 , 334, 133), // #1165
+ INST(Vmovhpd , VexRvmMr , V(660F00,16,_,0,I,1,3,T1S), V(660F00,17,_,0,I,1,3,T1S), 125, 97 , 335, 133), // #1166
+ INST(Vmovhps , VexRvmMr , V(000F00,16,_,0,I,0,3,T2 ), V(000F00,17,_,0,I,0,3,T2 ), 201, 98 , 335, 133), // #1167
+ INST(Vmovlhps , VexRvm , V(000F00,16,_,0,I,0,_,_ ), 0 , 72 , 0 , 334, 133), // #1168
+ INST(Vmovlpd , VexRvmMr , V(660F00,12,_,0,I,1,3,T1S), V(660F00,13,_,0,I,1,3,T1S), 125, 99 , 335, 133), // #1169
+ INST(Vmovlps , VexRvmMr , V(000F00,12,_,0,I,0,3,T2 ), V(000F00,13,_,0,I,0,3,T2 ), 201, 100, 335, 133), // #1170
+ INST(Vmovmskpd , VexRm_Lx , V(660F00,50,_,x,I,_,_,_ ), 0 , 69 , 0 , 336, 135), // #1171
+ INST(Vmovmskps , VexRm_Lx , V(000F00,50,_,x,I,_,_,_ ), 0 , 72 , 0 , 336, 135), // #1172
+ INST(Vmovntdq , VexMr_Lx , V(660F00,E7,_,x,I,0,4,FVM), 0 , 144, 0 , 337, 131), // #1173
+ INST(Vmovntdqa , VexRm_Lx , V(660F38,2A,_,x,I,0,4,FVM), 0 , 110, 0 , 338, 142), // #1174
+ INST(Vmovntpd , VexMr_Lx , V(660F00,2B,_,x,I,1,4,FVM), 0 , 103, 0 , 337, 131), // #1175
+ INST(Vmovntps , VexMr_Lx , V(000F00,2B,_,x,I,0,4,FVM), 0 , 105, 0 , 337, 131), // #1176
+ INST(Vmovq , VexMovdMovq , V(660F00,6E,_,0,I,1,3,T1S), V(660F00,7E,_,0,I,1,3,T1S), 125, 101, 339, 159), // #1177
+ INST(Vmovsd , VexMovssMovsd , V(F20F00,10,_,I,I,1,3,T1S), V(F20F00,11,_,I,I,1,3,T1S), 106, 102, 340, 159), // #1178
+ INST(Vmovsh , VexMovssMovsd , E(F3MAP5,10,_,I,_,0,1,T1S), E(F3MAP5,11,_,I,_,0,1,T1S), 107, 103, 341, 134), // #1179
+ INST(Vmovshdup , VexRm_Lx , V(F30F00,16,_,x,I,0,4,FVM), 0 , 161, 0 , 342, 131), // #1180
+ INST(Vmovsldup , VexRm_Lx , V(F30F00,12,_,x,I,0,4,FVM), 0 , 161, 0 , 342, 131), // #1181
+ INST(Vmovss , VexMovssMovsd , V(F30F00,10,_,I,I,0,2,T1S), V(F30F00,11,_,I,I,0,2,T1S), 108, 104, 343, 159), // #1182
+ INST(Vmovupd , VexRmMr_Lx , V(660F00,10,_,x,I,1,4,FVM), V(660F00,11,_,x,I,1,4,FVM), 103, 105, 329, 155), // #1183
+ INST(Vmovups , VexRmMr_Lx , V(000F00,10,_,x,I,0,4,FVM), V(000F00,11,_,x,I,0,4,FVM), 105, 106, 329, 155), // #1184
+ INST(Vmovw , VexMovdMovq , E(66MAP5,6E,_,0,_,I,1,T1S), E(66MAP5,7E,_,0,_,I,1,T1S), 202, 107, 344, 134), // #1185
+ INST(Vmpsadbw , VexRvmi_Lx , V(660F3A,42,_,x,I,_,_,_ ), 0 , 73 , 0 , 214, 160), // #1186
+ INST(Vmptrld , X86M_Only , O(000F00,C7,6,_,_,_,_,_ ), 0 , 80 , 0 , 32 , 58 ), // #1187
+ INST(Vmptrst , X86M_Only , O(000F00,C7,7,_,_,_,_,_ ), 0 , 22 , 0 , 32 , 58 ), // #1188
+ INST(Vmread , X86Mr_NoSize , O(000F00,78,_,_,_,_,_,_ ), 0 , 4 , 0 , 345, 58 ), // #1189
+ INST(Vmresume , X86Op , O(000F01,C3,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 58 ), // #1190
+ INST(Vmrun , X86Op_xAX , O(000F01,D8,_,_,_,_,_,_ ), 0 , 21 , 0 , 328, 22 ), // #1191
+ INST(Vmsave , X86Op_xAX , O(000F01,DB,_,_,_,_,_,_ ), 0 , 21 , 0 , 328, 22 ), // #1192
+ INST(Vmulpd , VexRvm_Lx , V(660F00,59,_,x,I,1,4,FV ), 0 , 103, 0 , 196, 131), // #1193
+ INST(Vmulph , VexRvm_Lx , E(00MAP5,59,_,_,_,0,4,FV ), 0 , 104, 0 , 197, 132), // #1194
+ INST(Vmulps , VexRvm_Lx , V(000F00,59,_,x,I,0,4,FV ), 0 , 105, 0 , 198, 131), // #1195
+ INST(Vmulsd , VexRvm , V(F20F00,59,_,I,I,1,3,T1S), 0 , 106, 0 , 199, 133), // #1196
+ INST(Vmulsh , VexRvm , E(F3MAP5,59,_,_,_,0,1,T1S), 0 , 107, 0 , 200, 134), // #1197
+ INST(Vmulss , VexRvm , V(F30F00,59,_,I,I,0,2,T1S), 0 , 108, 0 , 201, 133), // #1198
+ INST(Vmwrite , X86Rm_NoSize , O(000F00,79,_,_,_,_,_,_ ), 0 , 4 , 0 , 346, 58 ), // #1199
+ INST(Vmxon , X86M_Only , O(F30F00,C7,6,_,_,_,_,_ ), 0 , 24 , 0 , 32 , 58 ), // #1200
+ INST(Vorpd , VexRvm_Lx , V(660F00,56,_,x,I,1,4,FV ), 0 , 103, 0 , 210, 139), // #1201
+ INST(Vorps , VexRvm_Lx , V(000F00,56,_,x,I,0,4,FV ), 0 , 105, 0 , 211, 139), // #1202
+ INST(Vp2intersectd , VexRvm_Lx_2xK , E(F20F38,68,_,_,_,0,4,FV ), 0 , 131, 0 , 347, 161), // #1203
+ INST(Vp2intersectq , VexRvm_Lx_2xK , E(F20F38,68,_,_,_,1,4,FV ), 0 , 203, 0 , 348, 161), // #1204
+ INST(Vp4dpwssd , VexRm_T1_4X , E(F20F38,52,_,2,_,0,4,T4X), 0 , 101, 0 , 194, 162), // #1205
+ INST(Vp4dpwssds , VexRm_T1_4X , E(F20F38,53,_,2,_,0,4,T4X), 0 , 101, 0 , 194, 162), // #1206
+ INST(Vpabsb , VexRm_Lx , V(660F38,1C,_,x,I,_,4,FVM), 0 , 110, 0 , 342, 163), // #1207
+ INST(Vpabsd , VexRm_Lx , V(660F38,1E,_,x,I,0,4,FV ), 0 , 110, 0 , 349, 142), // #1208
+ INST(Vpabsq , VexRm_Lx , E(660F38,1F,_,x,_,1,4,FV ), 0 , 113, 0 , 350, 138), // #1209
+ INST(Vpabsw , VexRm_Lx , V(660F38,1D,_,x,I,_,4,FVM), 0 , 110, 0 , 342, 163), // #1210
+ INST(Vpackssdw , VexRvm_Lx , V(660F00,6B,_,x,I,0,4,FV ), 0 , 144, 0 , 209, 163), // #1211
+ INST(Vpacksswb , VexRvm_Lx , V(660F00,63,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1212
+ INST(Vpackusdw , VexRvm_Lx , V(660F38,2B,_,x,I,0,4,FV ), 0 , 110, 0 , 209, 163), // #1213
+ INST(Vpackuswb , VexRvm_Lx , V(660F00,67,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1214
+ INST(Vpaddb , VexRvm_Lx , V(660F00,FC,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1215
+ INST(Vpaddd , VexRvm_Lx , V(660F00,FE,_,x,I,0,4,FV ), 0 , 144, 0 , 209, 142), // #1216
+ INST(Vpaddq , VexRvm_Lx , V(660F00,D4,_,x,I,1,4,FV ), 0 , 103, 0 , 208, 142), // #1217
+ INST(Vpaddsb , VexRvm_Lx , V(660F00,EC,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1218
+ INST(Vpaddsw , VexRvm_Lx , V(660F00,ED,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1219
+ INST(Vpaddusb , VexRvm_Lx , V(660F00,DC,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1220
+ INST(Vpaddusw , VexRvm_Lx , V(660F00,DD,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1221
+ INST(Vpaddw , VexRvm_Lx , V(660F00,FD,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1222
+ INST(Vpalignr , VexRvmi_Lx , V(660F3A,0F,_,x,I,I,4,FVM), 0 , 204, 0 , 314, 163), // #1223
+ INST(Vpand , VexRvm_Lx , V(660F00,DB,_,x,I,_,_,_ ), 0 , 69 , 0 , 351, 160), // #1224
+ INST(Vpandd , VexRvm_Lx , E(660F00,DB,_,x,_,0,4,FV ), 0 , 198, 0 , 352, 138), // #1225
+ INST(Vpandn , VexRvm_Lx , V(660F00,DF,_,x,I,_,_,_ ), 0 , 69 , 0 , 353, 160), // #1226
+ INST(Vpandnd , VexRvm_Lx , E(660F00,DF,_,x,_,0,4,FV ), 0 , 198, 0 , 354, 138), // #1227
+ INST(Vpandnq , VexRvm_Lx , E(660F00,DF,_,x,_,1,4,FV ), 0 , 135, 0 , 355, 138), // #1228
+ INST(Vpandq , VexRvm_Lx , E(660F00,DB,_,x,_,1,4,FV ), 0 , 135, 0 , 356, 138), // #1229
+ INST(Vpavgb , VexRvm_Lx , V(660F00,E0,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1230
+ INST(Vpavgw , VexRvm_Lx , V(660F00,E3,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1231
+ INST(Vpblendd , VexRvmi_Lx , V(660F3A,02,_,x,0,_,_,_ ), 0 , 73 , 0 , 214, 141), // #1232
+ INST(Vpblendmb , VexRvm_Lx , E(660F38,66,_,x,_,0,4,FVM), 0 , 114, 0 , 357, 146), // #1233
+ INST(Vpblendmd , VexRvm_Lx , E(660F38,64,_,x,_,0,4,FV ), 0 , 114, 0 , 213, 138), // #1234
+ INST(Vpblendmq , VexRvm_Lx , E(660F38,64,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1235
+ INST(Vpblendmw , VexRvm_Lx , E(660F38,66,_,x,_,1,4,FVM), 0 , 113, 0 , 357, 146), // #1236
+ INST(Vpblendvb , VexRvmr_Lx , V(660F3A,4C,_,x,0,_,_,_ ), 0 , 73 , 0 , 215, 160), // #1237
+ INST(Vpblendw , VexRvmi_Lx , V(660F3A,0E,_,x,I,_,_,_ ), 0 , 73 , 0 , 214, 160), // #1238
+ INST(Vpbroadcastb , VexRm_Lx_Bcst , V(660F38,78,_,x,0,0,0,T1S), E(660F38,7A,_,x,0,0,0,T1S), 96 , 108, 358, 164), // #1239
+ INST(Vpbroadcastd , VexRm_Lx_Bcst , V(660F38,58,_,x,0,0,2,T1S), E(660F38,7C,_,x,0,0,0,T1S), 122, 109, 359, 152), // #1240
+ INST(Vpbroadcastmb2q , VexRm_Lx , E(F30F38,2A,_,x,_,1,_,_ ), 0 , 205, 0 , 360, 165), // #1241
+ INST(Vpbroadcastmw2d , VexRm_Lx , E(F30F38,3A,_,x,_,0,_,_ ), 0 , 206, 0 , 360, 165), // #1242
+ INST(Vpbroadcastq , VexRm_Lx_Bcst , V(660F38,59,_,x,0,1,3,T1S), E(660F38,7C,_,x,0,1,0,T1S), 121, 110, 361, 152), // #1243
+ INST(Vpbroadcastw , VexRm_Lx_Bcst , V(660F38,79,_,x,0,0,1,T1S), E(660F38,7B,_,x,0,0,0,T1S), 207, 111, 362, 164), // #1244
+ INST(Vpclmulqdq , VexRvmi_Lx , V(660F3A,44,_,x,I,_,4,FVM), 0 , 204, 0 , 363, 166), // #1245
+ INST(Vpcmov , VexRvrmRvmr_Lx , V(XOP_M8,A2,_,x,x,_,_,_ ), 0 , 208, 0 , 289, 151), // #1246
+ INST(Vpcmpb , VexRvmi_Lx , E(660F3A,3F,_,x,_,0,4,FVM), 0 , 111, 0 , 364, 146), // #1247
+ INST(Vpcmpd , VexRvmi_Lx , E(660F3A,1F,_,x,_,0,4,FV ), 0 , 111, 0 , 365, 138), // #1248
+ INST(Vpcmpeqb , VexRvm_Lx_KEvex , V(660F00,74,_,x,I,I,4,FV ), 0 , 144, 0 , 366, 163), // #1249
+ INST(Vpcmpeqd , VexRvm_Lx_KEvex , V(660F00,76,_,x,I,0,4,FVM), 0 , 144, 0 , 367, 142), // #1250
+ INST(Vpcmpeqq , VexRvm_Lx_KEvex , V(660F38,29,_,x,I,1,4,FVM), 0 , 209, 0 , 368, 142), // #1251
+ INST(Vpcmpeqw , VexRvm_Lx_KEvex , V(660F00,75,_,x,I,I,4,FV ), 0 , 144, 0 , 366, 163), // #1252
+ INST(Vpcmpestri , VexRmi , V(660F3A,61,_,0,I,_,_,_ ), 0 , 73 , 0 , 369, 167), // #1253
+ INST(Vpcmpestrm , VexRmi , V(660F3A,60,_,0,I,_,_,_ ), 0 , 73 , 0 , 370, 167), // #1254
+ INST(Vpcmpgtb , VexRvm_Lx_KEvex , V(660F00,64,_,x,I,I,4,FV ), 0 , 144, 0 , 366, 163), // #1255
+ INST(Vpcmpgtd , VexRvm_Lx_KEvex , V(660F00,66,_,x,I,0,4,FVM), 0 , 144, 0 , 367, 142), // #1256
+ INST(Vpcmpgtq , VexRvm_Lx_KEvex , V(660F38,37,_,x,I,1,4,FVM), 0 , 209, 0 , 368, 142), // #1257
+ INST(Vpcmpgtw , VexRvm_Lx_KEvex , V(660F00,65,_,x,I,I,4,FV ), 0 , 144, 0 , 366, 163), // #1258
+ INST(Vpcmpistri , VexRmi , V(660F3A,63,_,0,I,_,_,_ ), 0 , 73 , 0 , 371, 167), // #1259
+ INST(Vpcmpistrm , VexRmi , V(660F3A,62,_,0,I,_,_,_ ), 0 , 73 , 0 , 372, 167), // #1260
+ INST(Vpcmpq , VexRvmi_Lx , E(660F3A,1F,_,x,_,1,4,FV ), 0 , 112, 0 , 373, 138), // #1261
+ INST(Vpcmpub , VexRvmi_Lx , E(660F3A,3E,_,x,_,0,4,FVM), 0 , 111, 0 , 364, 146), // #1262
+ INST(Vpcmpud , VexRvmi_Lx , E(660F3A,1E,_,x,_,0,4,FV ), 0 , 111, 0 , 365, 138), // #1263
+ INST(Vpcmpuq , VexRvmi_Lx , E(660F3A,1E,_,x,_,1,4,FV ), 0 , 112, 0 , 373, 138), // #1264
+ INST(Vpcmpuw , VexRvmi_Lx , E(660F3A,3E,_,x,_,1,4,FVM), 0 , 112, 0 , 373, 146), // #1265
+ INST(Vpcmpw , VexRvmi_Lx , E(660F3A,3F,_,x,_,1,4,FVM), 0 , 112, 0 , 373, 146), // #1266
+ INST(Vpcomb , VexRvmi , V(XOP_M8,CC,_,0,0,_,_,_ ), 0 , 208, 0 , 276, 151), // #1267
+ INST(Vpcomd , VexRvmi , V(XOP_M8,CE,_,0,0,_,_,_ ), 0 , 208, 0 , 276, 151), // #1268
+ INST(Vpcompressb , VexMr_Lx , E(660F38,63,_,x,_,0,0,T1S), 0 , 210, 0 , 232, 168), // #1269
+ INST(Vpcompressd , VexMr_Lx , E(660F38,8B,_,x,_,0,2,T1S), 0 , 129, 0 , 232, 138), // #1270
+ INST(Vpcompressq , VexMr_Lx , E(660F38,8B,_,x,_,1,3,T1S), 0 , 128, 0 , 232, 138), // #1271
+ INST(Vpcompressw , VexMr_Lx , E(660F38,63,_,x,_,1,1,T1S), 0 , 211, 0 , 232, 168), // #1272
+ INST(Vpcomq , VexRvmi , V(XOP_M8,CF,_,0,0,_,_,_ ), 0 , 208, 0 , 276, 151), // #1273
+ INST(Vpcomub , VexRvmi , V(XOP_M8,EC,_,0,0,_,_,_ ), 0 , 208, 0 , 276, 151), // #1274
+ INST(Vpcomud , VexRvmi , V(XOP_M8,EE,_,0,0,_,_,_ ), 0 , 208, 0 , 276, 151), // #1275
+ INST(Vpcomuq , VexRvmi , V(XOP_M8,EF,_,0,0,_,_,_ ), 0 , 208, 0 , 276, 151), // #1276
+ INST(Vpcomuw , VexRvmi , V(XOP_M8,ED,_,0,0,_,_,_ ), 0 , 208, 0 , 276, 151), // #1277
+ INST(Vpcomw , VexRvmi , V(XOP_M8,CD,_,0,0,_,_,_ ), 0 , 208, 0 , 276, 151), // #1278
+ INST(Vpconflictd , VexRm_Lx , E(660F38,C4,_,x,_,0,4,FV ), 0 , 114, 0 , 374, 165), // #1279
+ INST(Vpconflictq , VexRm_Lx , E(660F38,C4,_,x,_,1,4,FV ), 0 , 113, 0 , 374, 165), // #1280
+ INST(Vpdpbusd , VexRvm_Lx , V(660F38,50,_,x,_,0,4,FV ), 0 , 110, 0 , 375, 169), // #1281
+ INST(Vpdpbusds , VexRvm_Lx , V(660F38,51,_,x,_,0,4,FV ), 0 , 110, 0 , 375, 169), // #1282
+ INST(Vpdpwssd , VexRvm_Lx , V(660F38,52,_,x,_,0,4,FV ), 0 , 110, 0 , 375, 169), // #1283
+ INST(Vpdpwssds , VexRvm_Lx , V(660F38,53,_,x,_,0,4,FV ), 0 , 110, 0 , 375, 169), // #1284
+ INST(Vperm2f128 , VexRvmi , V(660F3A,06,_,1,0,_,_,_ ), 0 , 172, 0 , 376, 135), // #1285
+ INST(Vperm2i128 , VexRvmi , V(660F3A,46,_,1,0,_,_,_ ), 0 , 172, 0 , 376, 141), // #1286
+ INST(Vpermb , VexRvm_Lx , E(660F38,8D,_,x,_,0,4,FVM), 0 , 114, 0 , 357, 170), // #1287
+ INST(Vpermd , VexRvm_Lx , V(660F38,36,_,x,0,0,4,FV ), 0 , 110, 0 , 377, 152), // #1288
+ INST(Vpermi2b , VexRvm_Lx , E(660F38,75,_,x,_,0,4,FVM), 0 , 114, 0 , 357, 170), // #1289
+ INST(Vpermi2d , VexRvm_Lx , E(660F38,76,_,x,_,0,4,FV ), 0 , 114, 0 , 213, 138), // #1290
+ INST(Vpermi2pd , VexRvm_Lx , E(660F38,77,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1291
+ INST(Vpermi2ps , VexRvm_Lx , E(660F38,77,_,x,_,0,4,FV ), 0 , 114, 0 , 213, 138), // #1292
+ INST(Vpermi2q , VexRvm_Lx , E(660F38,76,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1293
+ INST(Vpermi2w , VexRvm_Lx , E(660F38,75,_,x,_,1,4,FVM), 0 , 113, 0 , 357, 146), // #1294
+ INST(Vpermil2pd , VexRvrmiRvmri_Lx , V(660F3A,49,_,x,x,_,_,_ ), 0 , 73 , 0 , 378, 151), // #1295
+ INST(Vpermil2ps , VexRvrmiRvmri_Lx , V(660F3A,48,_,x,x,_,_,_ ), 0 , 73 , 0 , 378, 151), // #1296
+ INST(Vpermilpd , VexRvmRmi_Lx , V(660F38,0D,_,x,0,1,4,FV ), V(660F3A,05,_,x,0,1,4,FV ), 209, 112, 379, 131), // #1297
+ INST(Vpermilps , VexRvmRmi_Lx , V(660F38,0C,_,x,0,0,4,FV ), V(660F3A,04,_,x,0,0,4,FV ), 110, 113, 380, 131), // #1298
+ INST(Vpermpd , VexRvmRmi_Lx , E(660F38,16,_,x,1,1,4,FV ), V(660F3A,01,_,x,1,1,4,FV ), 212, 114, 381, 152), // #1299
+ INST(Vpermps , VexRvm_Lx , V(660F38,16,_,x,0,0,4,FV ), 0 , 110, 0 , 377, 152), // #1300
+ INST(Vpermq , VexRvmRmi_Lx , E(660F38,36,_,x,_,1,4,FV ), V(660F3A,00,_,x,1,1,4,FV ), 113, 115, 381, 152), // #1301
+ INST(Vpermt2b , VexRvm_Lx , E(660F38,7D,_,x,_,0,4,FVM), 0 , 114, 0 , 357, 170), // #1302
+ INST(Vpermt2d , VexRvm_Lx , E(660F38,7E,_,x,_,0,4,FV ), 0 , 114, 0 , 213, 138), // #1303
+ INST(Vpermt2pd , VexRvm_Lx , E(660F38,7F,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1304
+ INST(Vpermt2ps , VexRvm_Lx , E(660F38,7F,_,x,_,0,4,FV ), 0 , 114, 0 , 213, 138), // #1305
+ INST(Vpermt2q , VexRvm_Lx , E(660F38,7E,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1306
+ INST(Vpermt2w , VexRvm_Lx , E(660F38,7D,_,x,_,1,4,FVM), 0 , 113, 0 , 357, 146), // #1307
+ INST(Vpermw , VexRvm_Lx , E(660F38,8D,_,x,_,1,4,FVM), 0 , 113, 0 , 357, 146), // #1308
+ INST(Vpexpandb , VexRm_Lx , E(660F38,62,_,x,_,0,0,T1S), 0 , 210, 0 , 279, 168), // #1309
+ INST(Vpexpandd , VexRm_Lx , E(660F38,89,_,x,_,0,2,T1S), 0 , 129, 0 , 279, 138), // #1310
+ INST(Vpexpandq , VexRm_Lx , E(660F38,89,_,x,_,1,3,T1S), 0 , 128, 0 , 279, 138), // #1311
+ INST(Vpexpandw , VexRm_Lx , E(660F38,62,_,x,_,1,1,T1S), 0 , 211, 0 , 279, 168), // #1312
+ INST(Vpextrb , VexMri , V(660F3A,14,_,0,0,I,0,T1S), 0 , 73 , 0 , 382, 171), // #1313
+ INST(Vpextrd , VexMri , V(660F3A,16,_,0,0,0,2,T1S), 0 , 177, 0 , 283, 172), // #1314
+ INST(Vpextrq , VexMri , V(660F3A,16,_,0,1,1,3,T1S), 0 , 213, 0 , 383, 172), // #1315
+ INST(Vpextrw , VexMri_Vpextrw , V(660F3A,15,_,0,0,I,1,T1S), 0 , 214, 0 , 384, 171), // #1316
+ INST(Vpgatherdd , VexRmvRm_VM , V(660F38,90,_,x,0,_,_,_ ), E(660F38,90,_,x,_,0,2,T1S), 96 , 116, 302, 152), // #1317
+ INST(Vpgatherdq , VexRmvRm_VM , V(660F38,90,_,x,1,_,_,_ ), E(660F38,90,_,x,_,1,3,T1S), 189, 117, 301, 152), // #1318
+ INST(Vpgatherqd , VexRmvRm_VM , V(660F38,91,_,x,0,_,_,_ ), E(660F38,91,_,x,_,0,2,T1S), 96 , 118, 307, 152), // #1319
+ INST(Vpgatherqq , VexRmvRm_VM , V(660F38,91,_,x,1,_,_,_ ), E(660F38,91,_,x,_,1,3,T1S), 189, 119, 306, 152), // #1320
+ INST(Vphaddbd , VexRm , V(XOP_M9,C2,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1321
+ INST(Vphaddbq , VexRm , V(XOP_M9,C3,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1322
+ INST(Vphaddbw , VexRm , V(XOP_M9,C1,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1323
+ INST(Vphaddd , VexRvm_Lx , V(660F38,02,_,x,I,_,_,_ ), 0 , 96 , 0 , 202, 160), // #1324
+ INST(Vphadddq , VexRm , V(XOP_M9,CB,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1325
+ INST(Vphaddsw , VexRvm_Lx , V(660F38,03,_,x,I,_,_,_ ), 0 , 96 , 0 , 202, 160), // #1326
+ INST(Vphaddubd , VexRm , V(XOP_M9,D2,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1327
+ INST(Vphaddubq , VexRm , V(XOP_M9,D3,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1328
+ INST(Vphaddubw , VexRm , V(XOP_M9,D1,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1329
+ INST(Vphaddudq , VexRm , V(XOP_M9,DB,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1330
+ INST(Vphadduwd , VexRm , V(XOP_M9,D6,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1331
+ INST(Vphadduwq , VexRm , V(XOP_M9,D7,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1332
+ INST(Vphaddw , VexRvm_Lx , V(660F38,01,_,x,I,_,_,_ ), 0 , 96 , 0 , 202, 160), // #1333
+ INST(Vphaddwd , VexRm , V(XOP_M9,C6,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1334
+ INST(Vphaddwq , VexRm , V(XOP_M9,C7,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1335
+ INST(Vphminposuw , VexRm , V(660F38,41,_,0,I,_,_,_ ), 0 , 96 , 0 , 204, 135), // #1336
+ INST(Vphsubbw , VexRm , V(XOP_M9,E1,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1337
+ INST(Vphsubd , VexRvm_Lx , V(660F38,06,_,x,I,_,_,_ ), 0 , 96 , 0 , 202, 160), // #1338
+ INST(Vphsubdq , VexRm , V(XOP_M9,E3,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1339
+ INST(Vphsubsw , VexRvm_Lx , V(660F38,07,_,x,I,_,_,_ ), 0 , 96 , 0 , 202, 160), // #1340
+ INST(Vphsubw , VexRvm_Lx , V(660F38,05,_,x,I,_,_,_ ), 0 , 96 , 0 , 202, 160), // #1341
+ INST(Vphsubwd , VexRm , V(XOP_M9,E2,_,0,0,_,_,_ ), 0 , 79 , 0 , 204, 151), // #1342
+ INST(Vpinsrb , VexRvmi , V(660F3A,20,_,0,0,I,0,T1S), 0 , 73 , 0 , 385, 171), // #1343
+ INST(Vpinsrd , VexRvmi , V(660F3A,22,_,0,0,0,2,T1S), 0 , 177, 0 , 386, 172), // #1344
+ INST(Vpinsrq , VexRvmi , V(660F3A,22,_,0,1,1,3,T1S), 0 , 213, 0 , 387, 172), // #1345
+ INST(Vpinsrw , VexRvmi , V(660F00,C4,_,0,0,I,1,T1S), 0 , 215, 0 , 388, 171), // #1346
+ INST(Vplzcntd , VexRm_Lx , E(660F38,44,_,x,_,0,4,FV ), 0 , 114, 0 , 374, 165), // #1347
+ INST(Vplzcntq , VexRm_Lx , E(660F38,44,_,x,_,1,4,FV ), 0 , 113, 0 , 350, 165), // #1348
+ INST(Vpmacsdd , VexRvmr , V(XOP_M8,9E,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1349
+ INST(Vpmacsdqh , VexRvmr , V(XOP_M8,9F,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1350
+ INST(Vpmacsdql , VexRvmr , V(XOP_M8,97,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1351
+ INST(Vpmacssdd , VexRvmr , V(XOP_M8,8E,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1352
+ INST(Vpmacssdqh , VexRvmr , V(XOP_M8,8F,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1353
+ INST(Vpmacssdql , VexRvmr , V(XOP_M8,87,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1354
+ INST(Vpmacsswd , VexRvmr , V(XOP_M8,86,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1355
+ INST(Vpmacssww , VexRvmr , V(XOP_M8,85,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1356
+ INST(Vpmacswd , VexRvmr , V(XOP_M8,96,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1357
+ INST(Vpmacsww , VexRvmr , V(XOP_M8,95,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1358
+ INST(Vpmadcsswd , VexRvmr , V(XOP_M8,A6,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1359
+ INST(Vpmadcswd , VexRvmr , V(XOP_M8,B6,_,0,0,_,_,_ ), 0 , 208, 0 , 389, 151), // #1360
+ INST(Vpmadd52huq , VexRvm_Lx , E(660F38,B5,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 173), // #1361
+ INST(Vpmadd52luq , VexRvm_Lx , E(660F38,B4,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 173), // #1362
+ INST(Vpmaddubsw , VexRvm_Lx , V(660F38,04,_,x,I,I,4,FVM), 0 , 110, 0 , 315, 163), // #1363
+ INST(Vpmaddwd , VexRvm_Lx , V(660F00,F5,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1364
+ INST(Vpmaskmovd , VexRvmMvr_Lx , V(660F38,8C,_,x,0,_,_,_ ), V(660F38,8E,_,x,0,_,_,_ ), 96 , 120, 323, 141), // #1365
+ INST(Vpmaskmovq , VexRvmMvr_Lx , V(660F38,8C,_,x,1,_,_,_ ), V(660F38,8E,_,x,1,_,_,_ ), 189, 121, 323, 141), // #1366
+ INST(Vpmaxsb , VexRvm_Lx , V(660F38,3C,_,x,I,I,4,FVM), 0 , 110, 0 , 390, 163), // #1367
+ INST(Vpmaxsd , VexRvm_Lx , V(660F38,3D,_,x,I,0,4,FV ), 0 , 110, 0 , 211, 142), // #1368
+ INST(Vpmaxsq , VexRvm_Lx , E(660F38,3D,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1369
+ INST(Vpmaxsw , VexRvm_Lx , V(660F00,EE,_,x,I,I,4,FVM), 0 , 144, 0 , 390, 163), // #1370
+ INST(Vpmaxub , VexRvm_Lx , V(660F00,DE,_,x,I,I,4,FVM), 0 , 144, 0 , 390, 163), // #1371
+ INST(Vpmaxud , VexRvm_Lx , V(660F38,3F,_,x,I,0,4,FV ), 0 , 110, 0 , 211, 142), // #1372
+ INST(Vpmaxuq , VexRvm_Lx , E(660F38,3F,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1373
+ INST(Vpmaxuw , VexRvm_Lx , V(660F38,3E,_,x,I,I,4,FVM), 0 , 110, 0 , 390, 163), // #1374
+ INST(Vpminsb , VexRvm_Lx , V(660F38,38,_,x,I,I,4,FVM), 0 , 110, 0 , 390, 163), // #1375
+ INST(Vpminsd , VexRvm_Lx , V(660F38,39,_,x,I,0,4,FV ), 0 , 110, 0 , 211, 142), // #1376
+ INST(Vpminsq , VexRvm_Lx , E(660F38,39,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1377
+ INST(Vpminsw , VexRvm_Lx , V(660F00,EA,_,x,I,I,4,FVM), 0 , 144, 0 , 390, 163), // #1378
+ INST(Vpminub , VexRvm_Lx , V(660F00,DA,_,x,I,_,4,FVM), 0 , 144, 0 , 390, 163), // #1379
+ INST(Vpminud , VexRvm_Lx , V(660F38,3B,_,x,I,0,4,FV ), 0 , 110, 0 , 211, 142), // #1380
+ INST(Vpminuq , VexRvm_Lx , E(660F38,3B,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1381
+ INST(Vpminuw , VexRvm_Lx , V(660F38,3A,_,x,I,_,4,FVM), 0 , 110, 0 , 390, 163), // #1382
+ INST(Vpmovb2m , VexRm_Lx , E(F30F38,29,_,x,_,0,_,_ ), 0 , 206, 0 , 391, 146), // #1383
+ INST(Vpmovd2m , VexRm_Lx , E(F30F38,39,_,x,_,0,_,_ ), 0 , 206, 0 , 391, 140), // #1384
+ INST(Vpmovdb , VexMr_Lx , E(F30F38,31,_,x,_,0,2,QVM), 0 , 216, 0 , 392, 138), // #1385
+ INST(Vpmovdw , VexMr_Lx , E(F30F38,33,_,x,_,0,3,HVM), 0 , 217, 0 , 393, 138), // #1386
+ INST(Vpmovm2b , VexRm_Lx , E(F30F38,28,_,x,_,0,_,_ ), 0 , 206, 0 , 360, 146), // #1387
+ INST(Vpmovm2d , VexRm_Lx , E(F30F38,38,_,x,_,0,_,_ ), 0 , 206, 0 , 360, 140), // #1388
+ INST(Vpmovm2q , VexRm_Lx , E(F30F38,38,_,x,_,1,_,_ ), 0 , 205, 0 , 360, 140), // #1389
+ INST(Vpmovm2w , VexRm_Lx , E(F30F38,28,_,x,_,1,_,_ ), 0 , 205, 0 , 360, 146), // #1390
+ INST(Vpmovmskb , VexRm_Lx , V(660F00,D7,_,x,I,_,_,_ ), 0 , 69 , 0 , 336, 160), // #1391
+ INST(Vpmovq2m , VexRm_Lx , E(F30F38,39,_,x,_,1,_,_ ), 0 , 205, 0 , 391, 140), // #1392
+ INST(Vpmovqb , VexMr_Lx , E(F30F38,32,_,x,_,0,1,OVM), 0 , 218, 0 , 394, 138), // #1393
+ INST(Vpmovqd , VexMr_Lx , E(F30F38,35,_,x,_,0,3,HVM), 0 , 217, 0 , 393, 138), // #1394
+ INST(Vpmovqw , VexMr_Lx , E(F30F38,34,_,x,_,0,2,QVM), 0 , 216, 0 , 392, 138), // #1395
+ INST(Vpmovsdb , VexMr_Lx , E(F30F38,21,_,x,_,0,2,QVM), 0 , 216, 0 , 392, 138), // #1396
+ INST(Vpmovsdw , VexMr_Lx , E(F30F38,23,_,x,_,0,3,HVM), 0 , 217, 0 , 393, 138), // #1397
+ INST(Vpmovsqb , VexMr_Lx , E(F30F38,22,_,x,_,0,1,OVM), 0 , 218, 0 , 394, 138), // #1398
+ INST(Vpmovsqd , VexMr_Lx , E(F30F38,25,_,x,_,0,3,HVM), 0 , 217, 0 , 393, 138), // #1399
+ INST(Vpmovsqw , VexMr_Lx , E(F30F38,24,_,x,_,0,2,QVM), 0 , 216, 0 , 392, 138), // #1400
+ INST(Vpmovswb , VexMr_Lx , E(F30F38,20,_,x,_,0,3,HVM), 0 , 217, 0 , 393, 146), // #1401
+ INST(Vpmovsxbd , VexRm_Lx , V(660F38,21,_,x,I,I,2,QVM), 0 , 219, 0 , 395, 142), // #1402
+ INST(Vpmovsxbq , VexRm_Lx , V(660F38,22,_,x,I,I,1,OVM), 0 , 220, 0 , 396, 142), // #1403
+ INST(Vpmovsxbw , VexRm_Lx , V(660F38,20,_,x,I,I,3,HVM), 0 , 139, 0 , 397, 163), // #1404
+ INST(Vpmovsxdq , VexRm_Lx , V(660F38,25,_,x,I,0,3,HVM), 0 , 139, 0 , 397, 142), // #1405
+ INST(Vpmovsxwd , VexRm_Lx , V(660F38,23,_,x,I,I,3,HVM), 0 , 139, 0 , 397, 142), // #1406
+ INST(Vpmovsxwq , VexRm_Lx , V(660F38,24,_,x,I,I,2,QVM), 0 , 219, 0 , 395, 142), // #1407
+ INST(Vpmovusdb , VexMr_Lx , E(F30F38,11,_,x,_,0,2,QVM), 0 , 216, 0 , 392, 138), // #1408
+ INST(Vpmovusdw , VexMr_Lx , E(F30F38,13,_,x,_,0,3,HVM), 0 , 217, 0 , 393, 138), // #1409
+ INST(Vpmovusqb , VexMr_Lx , E(F30F38,12,_,x,_,0,1,OVM), 0 , 218, 0 , 394, 138), // #1410
+ INST(Vpmovusqd , VexMr_Lx , E(F30F38,15,_,x,_,0,3,HVM), 0 , 217, 0 , 393, 138), // #1411
+ INST(Vpmovusqw , VexMr_Lx , E(F30F38,14,_,x,_,0,2,QVM), 0 , 216, 0 , 392, 138), // #1412
+ INST(Vpmovuswb , VexMr_Lx , E(F30F38,10,_,x,_,0,3,HVM), 0 , 217, 0 , 393, 146), // #1413
+ INST(Vpmovw2m , VexRm_Lx , E(F30F38,29,_,x,_,1,_,_ ), 0 , 205, 0 , 391, 146), // #1414
+ INST(Vpmovwb , VexMr_Lx , E(F30F38,30,_,x,_,0,3,HVM), 0 , 217, 0 , 393, 146), // #1415
+ INST(Vpmovzxbd , VexRm_Lx , V(660F38,31,_,x,I,I,2,QVM), 0 , 219, 0 , 395, 142), // #1416
+ INST(Vpmovzxbq , VexRm_Lx , V(660F38,32,_,x,I,I,1,OVM), 0 , 220, 0 , 396, 142), // #1417
+ INST(Vpmovzxbw , VexRm_Lx , V(660F38,30,_,x,I,I,3,HVM), 0 , 139, 0 , 397, 163), // #1418
+ INST(Vpmovzxdq , VexRm_Lx , V(660F38,35,_,x,I,0,3,HVM), 0 , 139, 0 , 397, 142), // #1419
+ INST(Vpmovzxwd , VexRm_Lx , V(660F38,33,_,x,I,I,3,HVM), 0 , 139, 0 , 397, 142), // #1420
+ INST(Vpmovzxwq , VexRm_Lx , V(660F38,34,_,x,I,I,2,QVM), 0 , 219, 0 , 395, 142), // #1421
+ INST(Vpmuldq , VexRvm_Lx , V(660F38,28,_,x,I,1,4,FV ), 0 , 209, 0 , 208, 142), // #1422
+ INST(Vpmulhrsw , VexRvm_Lx , V(660F38,0B,_,x,I,I,4,FVM), 0 , 110, 0 , 315, 163), // #1423
+ INST(Vpmulhuw , VexRvm_Lx , V(660F00,E4,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1424
+ INST(Vpmulhw , VexRvm_Lx , V(660F00,E5,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1425
+ INST(Vpmulld , VexRvm_Lx , V(660F38,40,_,x,I,0,4,FV ), 0 , 110, 0 , 209, 142), // #1426
+ INST(Vpmullq , VexRvm_Lx , E(660F38,40,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 140), // #1427
+ INST(Vpmullw , VexRvm_Lx , V(660F00,D5,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1428
+ INST(Vpmultishiftqb , VexRvm_Lx , E(660F38,83,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 170), // #1429
+ INST(Vpmuludq , VexRvm_Lx , V(660F00,F4,_,x,I,1,4,FV ), 0 , 103, 0 , 208, 142), // #1430
+ INST(Vpopcntb , VexRm_Lx , E(660F38,54,_,x,_,0,4,FV ), 0 , 114, 0 , 279, 174), // #1431
+ INST(Vpopcntd , VexRm_Lx , E(660F38,55,_,x,_,0,4,FVM), 0 , 114, 0 , 374, 175), // #1432
+ INST(Vpopcntq , VexRm_Lx , E(660F38,55,_,x,_,1,4,FVM), 0 , 113, 0 , 350, 175), // #1433
+ INST(Vpopcntw , VexRm_Lx , E(660F38,54,_,x,_,1,4,FV ), 0 , 113, 0 , 279, 174), // #1434
+ INST(Vpor , VexRvm_Lx , V(660F00,EB,_,x,I,_,_,_ ), 0 , 69 , 0 , 351, 160), // #1435
+ INST(Vpord , VexRvm_Lx , E(660F00,EB,_,x,_,0,4,FV ), 0 , 198, 0 , 352, 138), // #1436
+ INST(Vporq , VexRvm_Lx , E(660F00,EB,_,x,_,1,4,FV ), 0 , 135, 0 , 356, 138), // #1437
+ INST(Vpperm , VexRvrmRvmr , V(XOP_M8,A3,_,0,x,_,_,_ ), 0 , 208, 0 , 398, 151), // #1438
+ INST(Vprold , VexVmi_Lx , E(660F00,72,1,x,_,0,4,FV ), 0 , 221, 0 , 399, 138), // #1439
+ INST(Vprolq , VexVmi_Lx , E(660F00,72,1,x,_,1,4,FV ), 0 , 222, 0 , 400, 138), // #1440
+ INST(Vprolvd , VexRvm_Lx , E(660F38,15,_,x,_,0,4,FV ), 0 , 114, 0 , 213, 138), // #1441
+ INST(Vprolvq , VexRvm_Lx , E(660F38,15,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1442
+ INST(Vprord , VexVmi_Lx , E(660F00,72,0,x,_,0,4,FV ), 0 , 198, 0 , 399, 138), // #1443
+ INST(Vprorq , VexVmi_Lx , E(660F00,72,0,x,_,1,4,FV ), 0 , 135, 0 , 400, 138), // #1444
+ INST(Vprorvd , VexRvm_Lx , E(660F38,14,_,x,_,0,4,FV ), 0 , 114, 0 , 213, 138), // #1445
+ INST(Vprorvq , VexRvm_Lx , E(660F38,14,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1446
+ INST(Vprotb , VexRvmRmvRmi , V(XOP_M9,90,_,0,x,_,_,_ ), V(XOP_M8,C0,_,0,x,_,_,_ ), 79 , 122, 401, 151), // #1447
+ INST(Vprotd , VexRvmRmvRmi , V(XOP_M9,92,_,0,x,_,_,_ ), V(XOP_M8,C2,_,0,x,_,_,_ ), 79 , 123, 401, 151), // #1448
+ INST(Vprotq , VexRvmRmvRmi , V(XOP_M9,93,_,0,x,_,_,_ ), V(XOP_M8,C3,_,0,x,_,_,_ ), 79 , 124, 401, 151), // #1449
+ INST(Vprotw , VexRvmRmvRmi , V(XOP_M9,91,_,0,x,_,_,_ ), V(XOP_M8,C1,_,0,x,_,_,_ ), 79 , 125, 401, 151), // #1450
+ INST(Vpsadbw , VexRvm_Lx , V(660F00,F6,_,x,I,I,4,FVM), 0 , 144, 0 , 203, 163), // #1451
+ INST(Vpscatterdd , VexMr_VM , E(660F38,A0,_,x,_,0,2,T1S), 0 , 129, 0 , 402, 138), // #1452
+ INST(Vpscatterdq , VexMr_VM , E(660F38,A0,_,x,_,1,3,T1S), 0 , 128, 0 , 403, 138), // #1453
+ INST(Vpscatterqd , VexMr_VM , E(660F38,A1,_,x,_,0,2,T1S), 0 , 129, 0 , 404, 138), // #1454
+ INST(Vpscatterqq , VexMr_VM , E(660F38,A1,_,x,_,1,3,T1S), 0 , 128, 0 , 405, 138), // #1455
+ INST(Vpshab , VexRvmRmv , V(XOP_M9,98,_,0,x,_,_,_ ), 0 , 79 , 0 , 406, 151), // #1456
+ INST(Vpshad , VexRvmRmv , V(XOP_M9,9A,_,0,x,_,_,_ ), 0 , 79 , 0 , 406, 151), // #1457
+ INST(Vpshaq , VexRvmRmv , V(XOP_M9,9B,_,0,x,_,_,_ ), 0 , 79 , 0 , 406, 151), // #1458
+ INST(Vpshaw , VexRvmRmv , V(XOP_M9,99,_,0,x,_,_,_ ), 0 , 79 , 0 , 406, 151), // #1459
+ INST(Vpshlb , VexRvmRmv , V(XOP_M9,94,_,0,x,_,_,_ ), 0 , 79 , 0 , 406, 151), // #1460
+ INST(Vpshld , VexRvmRmv , V(XOP_M9,96,_,0,x,_,_,_ ), 0 , 79 , 0 , 406, 151), // #1461
+ INST(Vpshldd , VexRvmi_Lx , E(660F3A,71,_,x,_,0,4,FV ), 0 , 111, 0 , 206, 168), // #1462
+ INST(Vpshldq , VexRvmi_Lx , E(660F3A,71,_,x,_,1,4,FV ), 0 , 112, 0 , 207, 168), // #1463
+ INST(Vpshldvd , VexRvm_Lx , E(660F38,71,_,x,_,0,4,FV ), 0 , 114, 0 , 213, 168), // #1464
+ INST(Vpshldvq , VexRvm_Lx , E(660F38,71,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 168), // #1465
+ INST(Vpshldvw , VexRvm_Lx , E(660F38,70,_,x,_,1,4,FVM), 0 , 113, 0 , 357, 168), // #1466
+ INST(Vpshldw , VexRvmi_Lx , E(660F3A,70,_,x,_,1,4,FVM), 0 , 112, 0 , 275, 168), // #1467
+ INST(Vpshlq , VexRvmRmv , V(XOP_M9,97,_,0,x,_,_,_ ), 0 , 79 , 0 , 406, 151), // #1468
+ INST(Vpshlw , VexRvmRmv , V(XOP_M9,95,_,0,x,_,_,_ ), 0 , 79 , 0 , 406, 151), // #1469
+ INST(Vpshrdd , VexRvmi_Lx , E(660F3A,73,_,x,_,0,4,FV ), 0 , 111, 0 , 206, 168), // #1470
+ INST(Vpshrdq , VexRvmi_Lx , E(660F3A,73,_,x,_,1,4,FV ), 0 , 112, 0 , 207, 168), // #1471
+ INST(Vpshrdvd , VexRvm_Lx , E(660F38,73,_,x,_,0,4,FV ), 0 , 114, 0 , 213, 168), // #1472
+ INST(Vpshrdvq , VexRvm_Lx , E(660F38,73,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 168), // #1473
+ INST(Vpshrdvw , VexRvm_Lx , E(660F38,72,_,x,_,1,4,FVM), 0 , 113, 0 , 357, 168), // #1474
+ INST(Vpshrdw , VexRvmi_Lx , E(660F3A,72,_,x,_,1,4,FVM), 0 , 112, 0 , 275, 168), // #1475
+ INST(Vpshufb , VexRvm_Lx , V(660F38,00,_,x,I,I,4,FVM), 0 , 110, 0 , 315, 163), // #1476
+ INST(Vpshufbitqmb , VexRvm_Lx , E(660F38,8F,_,x,0,0,4,FVM), 0 , 114, 0 , 407, 174), // #1477
+ INST(Vpshufd , VexRmi_Lx , V(660F00,70,_,x,I,0,4,FV ), 0 , 144, 0 , 408, 142), // #1478
+ INST(Vpshufhw , VexRmi_Lx , V(F30F00,70,_,x,I,I,4,FVM), 0 , 161, 0 , 409, 163), // #1479
+ INST(Vpshuflw , VexRmi_Lx , V(F20F00,70,_,x,I,I,4,FVM), 0 , 223, 0 , 409, 163), // #1480
+ INST(Vpsignb , VexRvm_Lx , V(660F38,08,_,x,I,_,_,_ ), 0 , 96 , 0 , 202, 160), // #1481
+ INST(Vpsignd , VexRvm_Lx , V(660F38,0A,_,x,I,_,_,_ ), 0 , 96 , 0 , 202, 160), // #1482
+ INST(Vpsignw , VexRvm_Lx , V(660F38,09,_,x,I,_,_,_ ), 0 , 96 , 0 , 202, 160), // #1483
+ INST(Vpslld , VexRvmVmi_Lx_MEvex , V(660F00,F2,_,x,I,0,4,128), V(660F00,72,6,x,I,0,4,FV ), 224, 126, 410, 142), // #1484
+ INST(Vpslldq , VexVmi_Lx_MEvex , V(660F00,73,7,x,I,I,4,FVM), 0 , 225, 0 , 411, 163), // #1485
+ INST(Vpsllq , VexRvmVmi_Lx_MEvex , V(660F00,F3,_,x,I,1,4,128), V(660F00,73,6,x,I,1,4,FV ), 226, 127, 412, 142), // #1486
+ INST(Vpsllvd , VexRvm_Lx , V(660F38,47,_,x,0,0,4,FV ), 0 , 110, 0 , 209, 152), // #1487
+ INST(Vpsllvq , VexRvm_Lx , V(660F38,47,_,x,1,1,4,FV ), 0 , 182, 0 , 208, 152), // #1488
+ INST(Vpsllvw , VexRvm_Lx , E(660F38,12,_,x,_,1,4,FVM), 0 , 113, 0 , 357, 146), // #1489
+ INST(Vpsllw , VexRvmVmi_Lx_MEvex , V(660F00,F1,_,x,I,I,4,128), V(660F00,71,6,x,I,I,4,FVM), 224, 128, 413, 163), // #1490
+ INST(Vpsrad , VexRvmVmi_Lx_MEvex , V(660F00,E2,_,x,I,0,4,128), V(660F00,72,4,x,I,0,4,FV ), 224, 129, 410, 142), // #1491
+ INST(Vpsraq , VexRvmVmi_Lx_MEvex , E(660F00,E2,_,x,_,1,4,128), E(660F00,72,4,x,_,1,4,FV ), 227, 130, 414, 138), // #1492
+ INST(Vpsravd , VexRvm_Lx , V(660F38,46,_,x,0,0,4,FV ), 0 , 110, 0 , 209, 152), // #1493
+ INST(Vpsravq , VexRvm_Lx , E(660F38,46,_,x,_,1,4,FV ), 0 , 113, 0 , 212, 138), // #1494
+ INST(Vpsravw , VexRvm_Lx , E(660F38,11,_,x,_,1,4,FVM), 0 , 113, 0 , 357, 146), // #1495
+ INST(Vpsraw , VexRvmVmi_Lx_MEvex , V(660F00,E1,_,x,I,I,4,128), V(660F00,71,4,x,I,I,4,FVM), 224, 131, 413, 163), // #1496
+ INST(Vpsrld , VexRvmVmi_Lx_MEvex , V(660F00,D2,_,x,I,0,4,128), V(660F00,72,2,x,I,0,4,FV ), 224, 132, 410, 142), // #1497
+ INST(Vpsrldq , VexVmi_Lx_MEvex , V(660F00,73,3,x,I,I,4,FVM), 0 , 228, 0 , 411, 163), // #1498
+ INST(Vpsrlq , VexRvmVmi_Lx_MEvex , V(660F00,D3,_,x,I,1,4,128), V(660F00,73,2,x,I,1,4,FV ), 226, 133, 412, 142), // #1499
+ INST(Vpsrlvd , VexRvm_Lx , V(660F38,45,_,x,0,0,4,FV ), 0 , 110, 0 , 209, 152), // #1500
+ INST(Vpsrlvq , VexRvm_Lx , V(660F38,45,_,x,1,1,4,FV ), 0 , 182, 0 , 208, 152), // #1501
+ INST(Vpsrlvw , VexRvm_Lx , E(660F38,10,_,x,_,1,4,FVM), 0 , 113, 0 , 357, 146), // #1502
+ INST(Vpsrlw , VexRvmVmi_Lx_MEvex , V(660F00,D1,_,x,I,I,4,128), V(660F00,71,2,x,I,I,4,FVM), 224, 134, 413, 163), // #1503
+ INST(Vpsubb , VexRvm_Lx , V(660F00,F8,_,x,I,I,4,FVM), 0 , 144, 0 , 415, 163), // #1504
+ INST(Vpsubd , VexRvm_Lx , V(660F00,FA,_,x,I,0,4,FV ), 0 , 144, 0 , 416, 142), // #1505
+ INST(Vpsubq , VexRvm_Lx , V(660F00,FB,_,x,I,1,4,FV ), 0 , 103, 0 , 417, 142), // #1506
+ INST(Vpsubsb , VexRvm_Lx , V(660F00,E8,_,x,I,I,4,FVM), 0 , 144, 0 , 415, 163), // #1507
+ INST(Vpsubsw , VexRvm_Lx , V(660F00,E9,_,x,I,I,4,FVM), 0 , 144, 0 , 415, 163), // #1508
+ INST(Vpsubusb , VexRvm_Lx , V(660F00,D8,_,x,I,I,4,FVM), 0 , 144, 0 , 415, 163), // #1509
+ INST(Vpsubusw , VexRvm_Lx , V(660F00,D9,_,x,I,I,4,FVM), 0 , 144, 0 , 415, 163), // #1510
+ INST(Vpsubw , VexRvm_Lx , V(660F00,F9,_,x,I,I,4,FVM), 0 , 144, 0 , 415, 163), // #1511
+ INST(Vpternlogd , VexRvmi_Lx , E(660F3A,25,_,x,_,0,4,FV ), 0 , 111, 0 , 206, 138), // #1512
+ INST(Vpternlogq , VexRvmi_Lx , E(660F3A,25,_,x,_,1,4,FV ), 0 , 112, 0 , 207, 138), // #1513
+ INST(Vptest , VexRm_Lx , V(660F38,17,_,x,I,_,_,_ ), 0 , 96 , 0 , 298, 167), // #1514
+ INST(Vptestmb , VexRvm_Lx , E(660F38,26,_,x,_,0,4,FVM), 0 , 114, 0 , 407, 146), // #1515
+ INST(Vptestmd , VexRvm_Lx , E(660F38,27,_,x,_,0,4,FV ), 0 , 114, 0 , 418, 138), // #1516
+ INST(Vptestmq , VexRvm_Lx , E(660F38,27,_,x,_,1,4,FV ), 0 , 113, 0 , 419, 138), // #1517
+ INST(Vptestmw , VexRvm_Lx , E(660F38,26,_,x,_,1,4,FVM), 0 , 113, 0 , 407, 146), // #1518
+ INST(Vptestnmb , VexRvm_Lx , E(F30F38,26,_,x,_,0,4,FVM), 0 , 132, 0 , 407, 146), // #1519
+ INST(Vptestnmd , VexRvm_Lx , E(F30F38,27,_,x,_,0,4,FV ), 0 , 132, 0 , 418, 138), // #1520
+ INST(Vptestnmq , VexRvm_Lx , E(F30F38,27,_,x,_,1,4,FV ), 0 , 229, 0 , 419, 138), // #1521
+ INST(Vptestnmw , VexRvm_Lx , E(F30F38,26,_,x,_,1,4,FVM), 0 , 229, 0 , 407, 146), // #1522
+ INST(Vpunpckhbw , VexRvm_Lx , V(660F00,68,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1523
+ INST(Vpunpckhdq , VexRvm_Lx , V(660F00,6A,_,x,I,0,4,FV ), 0 , 144, 0 , 209, 142), // #1524
+ INST(Vpunpckhqdq , VexRvm_Lx , V(660F00,6D,_,x,I,1,4,FV ), 0 , 103, 0 , 208, 142), // #1525
+ INST(Vpunpckhwd , VexRvm_Lx , V(660F00,69,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1526
+ INST(Vpunpcklbw , VexRvm_Lx , V(660F00,60,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1527
+ INST(Vpunpckldq , VexRvm_Lx , V(660F00,62,_,x,I,0,4,FV ), 0 , 144, 0 , 209, 142), // #1528
+ INST(Vpunpcklqdq , VexRvm_Lx , V(660F00,6C,_,x,I,1,4,FV ), 0 , 103, 0 , 208, 142), // #1529
+ INST(Vpunpcklwd , VexRvm_Lx , V(660F00,61,_,x,I,I,4,FVM), 0 , 144, 0 , 315, 163), // #1530
+ INST(Vpxor , VexRvm_Lx , V(660F00,EF,_,x,I,_,_,_ ), 0 , 69 , 0 , 353, 160), // #1531
+ INST(Vpxord , VexRvm_Lx , E(660F00,EF,_,x,_,0,4,FV ), 0 , 198, 0 , 354, 138), // #1532
+ INST(Vpxorq , VexRvm_Lx , E(660F00,EF,_,x,_,1,4,FV ), 0 , 135, 0 , 355, 138), // #1533
+ INST(Vrangepd , VexRvmi_Lx , E(660F3A,50,_,x,_,1,4,FV ), 0 , 112, 0 , 285, 140), // #1534
+ INST(Vrangeps , VexRvmi_Lx , E(660F3A,50,_,x,_,0,4,FV ), 0 , 111, 0 , 286, 140), // #1535
+ INST(Vrangesd , VexRvmi , E(660F3A,51,_,I,_,1,3,T1S), 0 , 180, 0 , 287, 66 ), // #1536
+ INST(Vrangess , VexRvmi , E(660F3A,51,_,I,_,0,2,T1S), 0 , 181, 0 , 288, 66 ), // #1537
+ INST(Vrcp14pd , VexRm_Lx , E(660F38,4C,_,x,_,1,4,FV ), 0 , 113, 0 , 350, 138), // #1538
+ INST(Vrcp14ps , VexRm_Lx , E(660F38,4C,_,x,_,0,4,FV ), 0 , 114, 0 , 374, 138), // #1539
+ INST(Vrcp14sd , VexRvm , E(660F38,4D,_,I,_,1,3,T1S), 0 , 128, 0 , 420, 68 ), // #1540
+ INST(Vrcp14ss , VexRvm , E(660F38,4D,_,I,_,0,2,T1S), 0 , 129, 0 , 421, 68 ), // #1541
+ INST(Vrcp28pd , VexRm , E(660F38,CA,_,2,_,1,4,FV ), 0 , 170, 0 , 277, 147), // #1542
+ INST(Vrcp28ps , VexRm , E(660F38,CA,_,2,_,0,4,FV ), 0 , 171, 0 , 278, 147), // #1543
+ INST(Vrcp28sd , VexRvm , E(660F38,CB,_,I,_,1,3,T1S), 0 , 128, 0 , 308, 147), // #1544
+ INST(Vrcp28ss , VexRvm , E(660F38,CB,_,I,_,0,2,T1S), 0 , 129, 0 , 309, 147), // #1545
+ INST(Vrcpph , VexRm_Lx , E(66MAP6,4C,_,_,_,0,4,FV ), 0 , 183, 0 , 422, 134), // #1546
+ INST(Vrcpps , VexRm_Lx , V(000F00,53,_,x,I,_,_,_ ), 0 , 72 , 0 , 298, 135), // #1547
+ INST(Vrcpsh , VexRvm , E(66MAP6,4D,_,_,_,0,1,T1S), 0 , 185, 0 , 423, 134), // #1548
+ INST(Vrcpss , VexRvm , V(F30F00,53,_,I,I,_,_,_ ), 0 , 199, 0 , 424, 135), // #1549
+ INST(Vreducepd , VexRmi_Lx , E(660F3A,56,_,x,_,1,4,FV ), 0 , 112, 0 , 400, 140), // #1550
+ INST(Vreduceph , VexRmi_Lx , E(000F3A,56,_,_,_,0,4,FV ), 0 , 123, 0 , 311, 132), // #1551
+ INST(Vreduceps , VexRmi_Lx , E(660F3A,56,_,x,_,0,4,FV ), 0 , 111, 0 , 399, 140), // #1552
+ INST(Vreducesd , VexRvmi , E(660F3A,57,_,I,_,1,3,T1S), 0 , 180, 0 , 425, 66 ), // #1553
+ INST(Vreducesh , VexRvmi , E(000F3A,57,_,_,_,0,1,T1S), 0 , 188, 0 , 313, 134), // #1554
+ INST(Vreducess , VexRvmi , E(660F3A,57,_,I,_,0,2,T1S), 0 , 181, 0 , 426, 66 ), // #1555
+ INST(Vrndscalepd , VexRmi_Lx , E(660F3A,09,_,x,_,1,4,FV ), 0 , 112, 0 , 310, 138), // #1556
+ INST(Vrndscaleph , VexRmi_Lx , E(000F3A,08,_,_,_,0,4,FV ), 0 , 123, 0 , 311, 132), // #1557
+ INST(Vrndscaleps , VexRmi_Lx , E(660F3A,08,_,x,_,0,4,FV ), 0 , 111, 0 , 312, 138), // #1558
+ INST(Vrndscalesd , VexRvmi , E(660F3A,0B,_,I,_,1,3,T1S), 0 , 180, 0 , 287, 68 ), // #1559
+ INST(Vrndscalesh , VexRvmi , E(000F3A,0A,_,_,_,0,1,T1S), 0 , 188, 0 , 313, 134), // #1560
+ INST(Vrndscaless , VexRvmi , E(660F3A,0A,_,I,_,0,2,T1S), 0 , 181, 0 , 288, 68 ), // #1561
+ INST(Vroundpd , VexRmi_Lx , V(660F3A,09,_,x,I,_,_,_ ), 0 , 73 , 0 , 427, 135), // #1562
+ INST(Vroundps , VexRmi_Lx , V(660F3A,08,_,x,I,_,_,_ ), 0 , 73 , 0 , 427, 135), // #1563
+ INST(Vroundsd , VexRvmi , V(660F3A,0B,_,I,I,_,_,_ ), 0 , 73 , 0 , 428, 135), // #1564
+ INST(Vroundss , VexRvmi , V(660F3A,0A,_,I,I,_,_,_ ), 0 , 73 , 0 , 429, 135), // #1565
+ INST(Vrsqrt14pd , VexRm_Lx , E(660F38,4E,_,x,_,1,4,FV ), 0 , 113, 0 , 350, 138), // #1566
+ INST(Vrsqrt14ps , VexRm_Lx , E(660F38,4E,_,x,_,0,4,FV ), 0 , 114, 0 , 374, 138), // #1567
+ INST(Vrsqrt14sd , VexRvm , E(660F38,4F,_,I,_,1,3,T1S), 0 , 128, 0 , 420, 68 ), // #1568
+ INST(Vrsqrt14ss , VexRvm , E(660F38,4F,_,I,_,0,2,T1S), 0 , 129, 0 , 421, 68 ), // #1569
+ INST(Vrsqrt28pd , VexRm , E(660F38,CC,_,2,_,1,4,FV ), 0 , 170, 0 , 277, 147), // #1570
+ INST(Vrsqrt28ps , VexRm , E(660F38,CC,_,2,_,0,4,FV ), 0 , 171, 0 , 278, 147), // #1571
+ INST(Vrsqrt28sd , VexRvm , E(660F38,CD,_,I,_,1,3,T1S), 0 , 128, 0 , 308, 147), // #1572
+ INST(Vrsqrt28ss , VexRvm , E(660F38,CD,_,I,_,0,2,T1S), 0 , 129, 0 , 309, 147), // #1573
+ INST(Vrsqrtph , VexRm_Lx , E(66MAP6,4E,_,_,_,0,4,FV ), 0 , 183, 0 , 422, 132), // #1574
+ INST(Vrsqrtps , VexRm_Lx , V(000F00,52,_,x,I,_,_,_ ), 0 , 72 , 0 , 298, 135), // #1575
+ INST(Vrsqrtsh , VexRvm , E(66MAP6,4F,_,_,_,0,1,T1S), 0 , 185, 0 , 423, 134), // #1576
+ INST(Vrsqrtss , VexRvm , V(F30F00,52,_,I,I,_,_,_ ), 0 , 199, 0 , 424, 135), // #1577
+ INST(Vscalefpd , VexRvm_Lx , E(660F38,2C,_,x,_,1,4,FV ), 0 , 113, 0 , 430, 138), // #1578
+ INST(Vscalefph , VexRvm_Lx , E(66MAP6,2C,_,_,_,0,4,FV ), 0 , 183, 0 , 197, 132), // #1579
+ INST(Vscalefps , VexRvm_Lx , E(660F38,2C,_,x,_,0,4,FV ), 0 , 114, 0 , 284, 138), // #1580
+ INST(Vscalefsd , VexRvm , E(660F38,2D,_,I,_,1,3,T1S), 0 , 128, 0 , 251, 68 ), // #1581
+ INST(Vscalefsh , VexRvm , E(66MAP6,2D,_,_,_,0,1,T1S), 0 , 185, 0 , 200, 134), // #1582
+ INST(Vscalefss , VexRvm , E(660F38,2D,_,I,_,0,2,T1S), 0 , 129, 0 , 259, 68 ), // #1583
+ INST(Vscatterdpd , VexMr_VM , E(660F38,A2,_,x,_,1,3,T1S), 0 , 128, 0 , 403, 138), // #1584
+ INST(Vscatterdps , VexMr_VM , E(660F38,A2,_,x,_,0,2,T1S), 0 , 129, 0 , 402, 138), // #1585
+ INST(Vscatterpf0dpd , VexM_VM , E(660F38,C6,5,2,_,1,3,T1S), 0 , 230, 0 , 303, 153), // #1586
+ INST(Vscatterpf0dps , VexM_VM , E(660F38,C6,5,2,_,0,2,T1S), 0 , 231, 0 , 304, 153), // #1587
+ INST(Vscatterpf0qpd , VexM_VM , E(660F38,C7,5,2,_,1,3,T1S), 0 , 230, 0 , 305, 153), // #1588
+ INST(Vscatterpf0qps , VexM_VM , E(660F38,C7,5,2,_,0,2,T1S), 0 , 231, 0 , 305, 153), // #1589
+ INST(Vscatterpf1dpd , VexM_VM , E(660F38,C6,6,2,_,1,3,T1S), 0 , 232, 0 , 303, 153), // #1590
+ INST(Vscatterpf1dps , VexM_VM , E(660F38,C6,6,2,_,0,2,T1S), 0 , 233, 0 , 304, 153), // #1591
+ INST(Vscatterpf1qpd , VexM_VM , E(660F38,C7,6,2,_,1,3,T1S), 0 , 232, 0 , 305, 153), // #1592
+ INST(Vscatterpf1qps , VexM_VM , E(660F38,C7,6,2,_,0,2,T1S), 0 , 233, 0 , 305, 153), // #1593
+ INST(Vscatterqpd , VexMr_VM , E(660F38,A3,_,x,_,1,3,T1S), 0 , 128, 0 , 405, 138), // #1594
+ INST(Vscatterqps , VexMr_VM , E(660F38,A3,_,x,_,0,2,T1S), 0 , 129, 0 , 404, 138), // #1595
+ INST(Vshuff32x4 , VexRvmi_Lx , E(660F3A,23,_,x,_,0,4,FV ), 0 , 111, 0 , 431, 138), // #1596
+ INST(Vshuff64x2 , VexRvmi_Lx , E(660F3A,23,_,x,_,1,4,FV ), 0 , 112, 0 , 432, 138), // #1597
+ INST(Vshufi32x4 , VexRvmi_Lx , E(660F3A,43,_,x,_,0,4,FV ), 0 , 111, 0 , 431, 138), // #1598
+ INST(Vshufi64x2 , VexRvmi_Lx , E(660F3A,43,_,x,_,1,4,FV ), 0 , 112, 0 , 432, 138), // #1599
+ INST(Vshufpd , VexRvmi_Lx , V(660F00,C6,_,x,I,1,4,FV ), 0 , 103, 0 , 433, 131), // #1600
+ INST(Vshufps , VexRvmi_Lx , V(000F00,C6,_,x,I,0,4,FV ), 0 , 105, 0 , 434, 131), // #1601
+ INST(Vsqrtpd , VexRm_Lx , V(660F00,51,_,x,I,1,4,FV ), 0 , 103, 0 , 435, 131), // #1602
+ INST(Vsqrtph , VexRm_Lx , E(00MAP5,51,_,_,_,0,4,FV ), 0 , 104, 0 , 246, 132), // #1603
+ INST(Vsqrtps , VexRm_Lx , V(000F00,51,_,x,I,0,4,FV ), 0 , 105, 0 , 235, 131), // #1604
+ INST(Vsqrtsd , VexRvm , V(F20F00,51,_,I,I,1,3,T1S), 0 , 106, 0 , 199, 133), // #1605
+ INST(Vsqrtsh , VexRvm , E(F3MAP5,51,_,_,_,0,1,T1S), 0 , 107, 0 , 200, 134), // #1606
+ INST(Vsqrtss , VexRvm , V(F30F00,51,_,I,I,0,2,T1S), 0 , 108, 0 , 201, 133), // #1607
+ INST(Vstmxcsr , VexM , V(000F00,AE,3,0,I,_,_,_ ), 0 , 234, 0 , 321, 135), // #1608
+ INST(Vsubpd , VexRvm_Lx , V(660F00,5C,_,x,I,1,4,FV ), 0 , 103, 0 , 196, 131), // #1609
+ INST(Vsubph , VexRvm_Lx , E(00MAP5,5C,_,_,_,0,4,FV ), 0 , 104, 0 , 197, 132), // #1610
+ INST(Vsubps , VexRvm_Lx , V(000F00,5C,_,x,I,0,4,FV ), 0 , 105, 0 , 198, 131), // #1611
+ INST(Vsubsd , VexRvm , V(F20F00,5C,_,I,I,1,3,T1S), 0 , 106, 0 , 199, 133), // #1612
+ INST(Vsubsh , VexRvm , E(F3MAP5,5C,_,_,_,0,1,T1S), 0 , 107, 0 , 200, 134), // #1613
+ INST(Vsubss , VexRvm , V(F30F00,5C,_,I,I,0,2,T1S), 0 , 108, 0 , 201, 133), // #1614
+ INST(Vtestpd , VexRm_Lx , V(660F38,0F,_,x,0,_,_,_ ), 0 , 96 , 0 , 298, 167), // #1615
+ INST(Vtestps , VexRm_Lx , V(660F38,0E,_,x,0,_,_,_ ), 0 , 96 , 0 , 298, 167), // #1616
+ INST(Vucomisd , VexRm , V(660F00,2E,_,I,I,1,3,T1S), 0 , 125, 0 , 229, 143), // #1617
+ INST(Vucomish , VexRm , E(00MAP5,2E,_,_,_,0,1,T1S), 0 , 126, 0 , 230, 134), // #1618
+ INST(Vucomiss , VexRm , V(000F00,2E,_,I,I,0,2,T1S), 0 , 127, 0 , 231, 143), // #1619
+ INST(Vunpckhpd , VexRvm_Lx , V(660F00,15,_,x,I,1,4,FV ), 0 , 103, 0 , 208, 131), // #1620
+ INST(Vunpckhps , VexRvm_Lx , V(000F00,15,_,x,I,0,4,FV ), 0 , 105, 0 , 209, 131), // #1621
+ INST(Vunpcklpd , VexRvm_Lx , V(660F00,14,_,x,I,1,4,FV ), 0 , 103, 0 , 208, 131), // #1622
+ INST(Vunpcklps , VexRvm_Lx , V(000F00,14,_,x,I,0,4,FV ), 0 , 105, 0 , 209, 131), // #1623
+ INST(Vxorpd , VexRvm_Lx , V(660F00,57,_,x,I,1,4,FV ), 0 , 103, 0 , 417, 139), // #1624
+ INST(Vxorps , VexRvm_Lx , V(000F00,57,_,x,I,0,4,FV ), 0 , 105, 0 , 416, 139), // #1625
+ INST(Vzeroall , VexOp , V(000F00,77,_,1,I,_,_,_ ), 0 , 68 , 0 , 436, 135), // #1626
+ INST(Vzeroupper , VexOp , V(000F00,77,_,0,I,_,_,_ ), 0 , 72 , 0 , 436, 135), // #1627
+ INST(Wbinvd , X86Op , O(000F00,09,_,_,_,_,_,_ ), 0 , 4 , 0 , 30 , 0 ), // #1628
+ INST(Wbnoinvd , X86Op , O(F30F00,09,_,_,_,_,_,_ ), 0 , 6 , 0 , 30 , 176), // #1629
+ INST(Wrfsbase , X86M , O(F30F00,AE,2,_,x,_,_,_ ), 0 , 235, 0 , 173, 111), // #1630
+ INST(Wrgsbase , X86M , O(F30F00,AE,3,_,x,_,_,_ ), 0 , 236, 0 , 173, 111), // #1631
+ INST(Wrmsr , X86Op , O(000F00,30,_,_,_,_,_,_ ), 0 , 4 , 0 , 174, 112), // #1632
+ INST(Wrssd , X86Mr , O(000F38,F6,_,_,_,_,_,_ ), 0 , 83 , 0 , 437, 56 ), // #1633
+ INST(Wrssq , X86Mr , O(000F38,F6,_,_,1,_,_,_ ), 0 , 237, 0 , 438, 56 ), // #1634
+ INST(Wrussd , X86Mr , O(660F38,F5,_,_,_,_,_,_ ), 0 , 2 , 0 , 437, 56 ), // #1635
+ INST(Wrussq , X86Mr , O(660F38,F5,_,_,1,_,_,_ ), 0 , 238, 0 , 438, 56 ), // #1636
+ INST(Xabort , X86Op_Mod11RM_I8 , O(000000,C6,7,_,_,_,_,_ ), 0 , 27 , 0 , 80 , 177), // #1637
+ INST(Xadd , X86Xadd , O(000F00,C0,_,_,x,_,_,_ ), 0 , 4 , 0 , 439, 38 ), // #1638
+ INST(Xbegin , X86JmpRel , O(000000,C7,7,_,_,_,_,_ ), 0 , 27 , 0 , 440, 177), // #1639
+ INST(Xchg , X86Xchg , O(000000,86,_,_,x,_,_,_ ), 0 , 0 , 0 , 441, 0 ), // #1640
+ INST(Xend , X86Op , O(000F01,D5,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 177), // #1641
+ INST(Xgetbv , X86Op , O(000F01,D0,_,_,_,_,_,_ ), 0 , 21 , 0 , 174, 178), // #1642
+ INST(Xlatb , X86Op , O(000000,D7,_,_,_,_,_,_ ), 0 , 0 , 0 , 30 , 0 ), // #1643
+ INST(Xor , X86Arith , O(000000,30,6,_,x,_,_,_ ), 0 , 32 , 0 , 179, 1 ), // #1644
+ INST(Xorpd , ExtRm , O(660F00,57,_,_,_,_,_,_ ), 0 , 3 , 0 , 151, 4 ), // #1645
+ INST(Xorps , ExtRm , O(000F00,57,_,_,_,_,_,_ ), 0 , 4 , 0 , 151, 5 ), // #1646
+ INST(Xresldtrk , X86Op , O(F20F01,E9,_,_,_,_,_,_ ), 0 , 92 , 0 , 30 , 179), // #1647
+ INST(Xrstor , X86M_Only_EDX_EAX , O(000F00,AE,5,_,_,_,_,_ ), 0 , 77 , 0 , 442, 178), // #1648
+ INST(Xrstor64 , X86M_Only_EDX_EAX , O(000F00,AE,5,_,1,_,_,_ ), 0 , 239, 0 , 443, 178), // #1649
+ INST(Xrstors , X86M_Only_EDX_EAX , O(000F00,C7,3,_,_,_,_,_ ), 0 , 78 , 0 , 442, 180), // #1650
+ INST(Xrstors64 , X86M_Only_EDX_EAX , O(000F00,C7,3,_,1,_,_,_ ), 0 , 240, 0 , 443, 180), // #1651
+ INST(Xsave , X86M_Only_EDX_EAX , O(000F00,AE,4,_,_,_,_,_ ), 0 , 97 , 0 , 442, 178), // #1652
+ INST(Xsave64 , X86M_Only_EDX_EAX , O(000F00,AE,4,_,1,_,_,_ ), 0 , 241, 0 , 443, 178), // #1653
+ INST(Xsavec , X86M_Only_EDX_EAX , O(000F00,C7,4,_,_,_,_,_ ), 0 , 97 , 0 , 442, 181), // #1654
+ INST(Xsavec64 , X86M_Only_EDX_EAX , O(000F00,C7,4,_,1,_,_,_ ), 0 , 241, 0 , 443, 181), // #1655
+ INST(Xsaveopt , X86M_Only_EDX_EAX , O(000F00,AE,6,_,_,_,_,_ ), 0 , 80 , 0 , 442, 182), // #1656
+ INST(Xsaveopt64 , X86M_Only_EDX_EAX , O(000F00,AE,6,_,1,_,_,_ ), 0 , 242, 0 , 443, 182), // #1657
+ INST(Xsaves , X86M_Only_EDX_EAX , O(000F00,C7,5,_,_,_,_,_ ), 0 , 77 , 0 , 442, 180), // #1658
+ INST(Xsaves64 , X86M_Only_EDX_EAX , O(000F00,C7,5,_,1,_,_,_ ), 0 , 239, 0 , 443, 180), // #1659
+ INST(Xsetbv , X86Op , O(000F01,D1,_,_,_,_,_,_ ), 0 , 21 , 0 , 174, 178), // #1660
+ INST(Xsusldtrk , X86Op , O(F20F01,E8,_,_,_,_,_,_ ), 0 , 92 , 0 , 30 , 179), // #1661
+ INST(Xtest , X86Op , O(000F01,D6,_,_,_,_,_,_ ), 0 , 21 , 0 , 30 , 183) // #1662
// ${InstInfo:End}
};
#undef NAME_DATA_INDEX
@@ -2373,12 +2365,12 @@ const InstDB::CommonInfo InstDB::_commonInfoTable[] = {
{ F(Evex)|F(Vec) , X(K)|X(Z) , 482, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #220 [ref=1x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(K)|X(Z) , 479, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #221 [ref=1x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(K)|X(Z) , 483, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #222 [ref=1x]
- { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(B64)|X(ImplicitZ)|X(K)|X(SAE), 197, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #223 [ref=1x]
- { F(Evex)|F(Vec) , X(B16)|X(ImplicitZ)|X(K)|X(SAE), 200, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #224 [ref=1x]
- { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(B32)|X(ImplicitZ)|X(K)|X(SAE), 197, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #225 [ref=1x]
- { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(ImplicitZ)|X(K)|X(SAE) , 484, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #226 [ref=1x]
- { F(Evex)|F(Vec) , X(ImplicitZ)|X(K)|X(SAE) , 485, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #227 [ref=1x]
- { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(ImplicitZ)|X(K)|X(SAE) , 486, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #228 [ref=1x]
+ { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(B64)|X(K)|X(SAE)|X(Z) , 197, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #223 [ref=1x]
+ { F(Evex)|F(Vec) , X(B16)|X(K)|X(SAE) , 200, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #224 [ref=1x]
+ { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(B32)|X(K)|X(SAE)|X(Z) , 197, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #225 [ref=1x]
+ { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(K)|X(SAE)|X(Z) , 484, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #226 [ref=1x]
+ { F(Evex)|F(Vec) , X(K)|X(SAE) , 485, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #227 [ref=1x]
+ { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(K)|X(SAE)|X(Z) , 486, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #228 [ref=1x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(SAE) , 106, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #229 [ref=2x]
{ F(Evex)|F(Vec) , X(SAE) , 263, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #230 [ref=2x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(SAE) , 212, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #231 [ref=2x]
@@ -2442,12 +2434,12 @@ const InstDB::CommonInfo InstDB::_commonInfoTable[] = {
{ F(Vec)|F(Vex) , 0 , 159, 4 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #289 [ref=13x]
{ F(Vec)|F(Vex) , 0 , 357, 2 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #290 [ref=4x]
{ F(Vec)|F(Vex) , 0 , 359, 2 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #291 [ref=4x]
- { F(Evex)|F(Vec) , X(B64)|X(ImplicitZ)|X(K) , 493, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #292 [ref=1x]
+ { F(Evex)|F(Vec) , X(B64)|X(K) , 493, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #292 [ref=1x]
{ F(Evex)|F(Vec) , X(B16)|X(K) , 493, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #293 [ref=1x]
- { F(Evex)|F(Vec) , X(B32)|X(ImplicitZ)|X(K) , 493, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #294 [ref=1x]
- { F(Evex)|F(Vec) , X(ImplicitZ)|X(K) , 494, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #295 [ref=1x]
+ { F(Evex)|F(Vec) , X(B32)|X(K) , 493, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #294 [ref=1x]
+ { F(Evex)|F(Vec) , X(K) , 494, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #295 [ref=1x]
{ F(Evex)|F(Vec) , X(K) , 495, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #296 [ref=1x]
- { F(Evex)|F(Vec) , X(ImplicitZ)|X(K) , 496, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #297 [ref=1x]
+ { F(Evex)|F(Vec) , X(K) , 496, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #297 [ref=1x]
{ F(Vec)|F(Vex) , 0 , 209, 2 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #298 [ref=7x]
{ F(Vec)|F(Vex) , 0 , 106, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #299 [ref=1x]
{ F(Vec)|F(Vex) , 0 , 212, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #300 [ref=1x]
@@ -2514,16 +2506,16 @@ const InstDB::CommonInfo InstDB::_commonInfoTable[] = {
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(K)|X(Z) , 248, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #361 [ref=1x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(K)|X(Z) , 506, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #362 [ref=1x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , 0 , 194, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #363 [ref=1x]
- { F(Evex)|F(Vec) , X(ImplicitZ)|X(K) , 200, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #364 [ref=2x]
- { F(Evex)|F(Vec) , X(B32)|X(ImplicitZ)|X(K) , 200, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #365 [ref=2x]
- { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(ImplicitZ)|X(K) , 251, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #366 [ref=4x]
- { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(B32)|X(ImplicitZ)|X(K) , 251, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #367 [ref=2x]
- { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(B64)|X(ImplicitZ)|X(K) , 251, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #368 [ref=2x]
+ { F(Evex)|F(Vec) , X(K) , 200, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #364 [ref=2x]
+ { F(Evex)|F(Vec) , X(B32)|X(K) , 200, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #365 [ref=2x]
+ { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(K) , 251, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #366 [ref=4x]
+ { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(B32)|X(K) , 251, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #367 [ref=2x]
+ { F(Evex)|F(EvexKReg)|F(Vec)|F(Vex) , X(B64)|X(K) , 251, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #368 [ref=2x]
{ F(Vec)|F(Vex) , 0 , 449, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #369 [ref=1x]
{ F(Vec)|F(Vex) , 0 , 450, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #370 [ref=1x]
{ F(Vec)|F(Vex) , 0 , 451, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #371 [ref=1x]
{ F(Vec)|F(Vex) , 0 , 452, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #372 [ref=1x]
- { F(Evex)|F(Vec) , X(B64)|X(ImplicitZ)|X(K) , 200, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #373 [ref=4x]
+ { F(Evex)|F(Vec) , X(B64)|X(K) , 200, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #373 [ref=4x]
{ F(Evex)|F(Vec) , X(B32)|X(K)|X(Z) , 209, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #374 [ref=6x]
{ F(Evex)|F(EvexCompat)|F(PreferEvex)|F(Vec)|F(Vex) , X(B32)|X(K)|X(Z) , 191, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #375 [ref=4x]
{ F(Vec)|F(Vex) , 0 , 195, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #376 [ref=2x]
@@ -2557,7 +2549,7 @@ const InstDB::CommonInfo InstDB::_commonInfoTable[] = {
{ F(Evex)|F(Vec)|F(Vsib) , X(K) , 379, 2 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #404 [ref=2x]
{ F(Evex)|F(Vec)|F(Vsib) , X(K) , 269, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #405 [ref=2x]
{ F(Vec)|F(Vex) , 0 , 381, 2 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #406 [ref=8x]
- { F(Evex)|F(Vec) , X(ImplicitZ)|X(K) , 272, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #407 [ref=5x]
+ { F(Evex)|F(Vec) , X(K) , 272, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #407 [ref=5x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(B32)|X(K)|X(Z) , 221, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #408 [ref=1x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(K)|X(Z) , 221, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #409 [ref=2x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(B32)|X(K)|X(Z) , 91 , 6 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #410 [ref=3x]
@@ -2568,8 +2560,8 @@ const InstDB::CommonInfo InstDB::_commonInfoTable[] = {
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(K)|X(Z) , 191, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #415 [ref=6x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(B32)|X(K)|X(Z) , 191, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #416 [ref=2x]
{ F(Evex)|F(EvexCompat)|F(Vec)|F(Vex) , X(B64)|X(K)|X(Z) , 191, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(WO)}, // #417 [ref=2x]
- { F(Evex)|F(Vec) , X(B32)|X(ImplicitZ)|X(K) , 272, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #418 [ref=2x]
- { F(Evex)|F(Vec) , X(B64)|X(ImplicitZ)|X(K) , 272, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #419 [ref=2x]
+ { F(Evex)|F(Vec) , X(B32)|X(K) , 272, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #418 [ref=2x]
+ { F(Evex)|F(Vec) , X(B64)|X(K) , 272, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #419 [ref=2x]
{ F(Evex)|F(Vec) , X(K)|X(Z) , 475, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #420 [ref=2x]
{ F(Evex)|F(Vec) , X(K)|X(Z) , 477, 1 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #421 [ref=2x]
{ F(Evex)|F(Vec) , X(B16)|X(K)|X(Z) , 209, 3 , CONTROL_FLOW(Regular), SAME_REG_HINT(None)}, // #422 [ref=2x]
@@ -2609,178 +2601,190 @@ const InstDB::CommonInfo InstDB::_commonInfoTable[] = {
// ------------------- Automatically generated, do not edit -------------------
#define EXT(VAL) uint32_t(CpuFeatures::X86::k##VAL)
const InstDB::AdditionalInfo InstDB::_additionalInfoTable[] = {
- { { 0 }, 0, 0 }, // #0 [ref=149x]
- { { 0 }, 1, 0 }, // #1 [ref=32x]
- { { 0 }, 2, 0 }, // #2 [ref=2x]
- { { EXT(ADX) }, 3, 0 }, // #3 [ref=1x]
- { { EXT(SSE2) }, 0, 0 }, // #4 [ref=65x]
- { { EXT(SSE) }, 0, 0 }, // #5 [ref=44x]
- { { EXT(SSE3) }, 0, 0 }, // #6 [ref=12x]
- { { EXT(ADX) }, 4, 0 }, // #7 [ref=1x]
- { { EXT(AESNI) }, 0, 0 }, // #8 [ref=6x]
- { { EXT(BMI) }, 1, 0 }, // #9 [ref=6x]
- { { 0 }, 5, 0 }, // #10 [ref=5x]
- { { EXT(TBM) }, 0, 0 }, // #11 [ref=9x]
- { { EXT(SSE4_1) }, 0, 0 }, // #12 [ref=47x]
- { { EXT(MPX) }, 0, 0 }, // #13 [ref=7x]
- { { 0 }, 6, 0 }, // #14 [ref=4x]
- { { EXT(BMI2) }, 1, 0 }, // #15 [ref=1x]
- { { EXT(SMAP) }, 7, 0 }, // #16 [ref=2x]
- { { 0 }, 8, 0 }, // #17 [ref=2x]
- { { 0 }, 9, 0 }, // #18 [ref=2x]
- { { EXT(CLDEMOTE) }, 0, 0 }, // #19 [ref=1x]
- { { EXT(CLFLUSH) }, 0, 0 }, // #20 [ref=1x]
- { { EXT(CLFLUSHOPT) }, 0, 0 }, // #21 [ref=1x]
- { { EXT(SVM) }, 0, 0 }, // #22 [ref=6x]
- { { 0 }, 10, 0 }, // #23 [ref=2x]
- { { EXT(CET_SS) }, 1, 0 }, // #24 [ref=3x]
- { { EXT(UINTR) }, 0, 0 }, // #25 [ref=4x]
- { { EXT(CLWB) }, 0, 0 }, // #26 [ref=1x]
- { { EXT(CLZERO) }, 0, 0 }, // #27 [ref=1x]
- { { 0 }, 3, 0 }, // #28 [ref=1x]
- { { EXT(CMOV) }, 11, 0 }, // #29 [ref=6x]
- { { EXT(CMOV) }, 12, 0 }, // #30 [ref=8x]
- { { EXT(CMOV) }, 13, 0 }, // #31 [ref=6x]
- { { EXT(CMOV) }, 14, 0 }, // #32 [ref=4x]
- { { EXT(CMOV) }, 15, 0 }, // #33 [ref=4x]
- { { EXT(CMOV) }, 16, 0 }, // #34 [ref=2x]
- { { EXT(CMOV) }, 17, 0 }, // #35 [ref=6x]
- { { EXT(CMOV) }, 18, 0 }, // #36 [ref=2x]
- { { 0 }, 19, 0 }, // #37 [ref=2x]
- { { EXT(I486) }, 1, 0 }, // #38 [ref=2x]
- { { EXT(CMPXCHG16B) }, 5, 0 }, // #39 [ref=1x]
- { { EXT(CMPXCHG8B) }, 5, 0 }, // #40 [ref=1x]
- { { EXT(SSE2) }, 1, 0 }, // #41 [ref=2x]
- { { EXT(SSE) }, 1, 0 }, // #42 [ref=2x]
- { { EXT(I486) }, 0, 0 }, // #43 [ref=4x]
- { { EXT(SSE4_2) }, 0, 0 }, // #44 [ref=2x]
- { { 0 }, 20, 0 }, // #45 [ref=2x]
- { { EXT(MMX) }, 0, 0 }, // #46 [ref=1x]
- { { EXT(CET_IBT) }, 0, 0 }, // #47 [ref=2x]
- { { EXT(ENQCMD) }, 0, 0 }, // #48 [ref=2x]
- { { EXT(SSE4A) }, 0, 0 }, // #49 [ref=4x]
- { { 0 }, 21, 0 }, // #50 [ref=4x]
- { { EXT(3DNOW) }, 0, 0 }, // #51 [ref=21x]
- { { EXT(FXSR) }, 0, 0 }, // #52 [ref=4x]
- { { EXT(SMX) }, 0, 0 }, // #53 [ref=1x]
- { { EXT(GFNI) }, 0, 0 }, // #54 [ref=3x]
- { { EXT(HRESET) }, 0, 0 }, // #55 [ref=1x]
- { { EXT(CET_SS) }, 0, 0 }, // #56 [ref=9x]
- { { 0 }, 16, 0 }, // #57 [ref=5x]
- { { EXT(VMX) }, 0, 0 }, // #58 [ref=12x]
- { { 0 }, 11, 0 }, // #59 [ref=8x]
- { { 0 }, 12, 0 }, // #60 [ref=12x]
- { { 0 }, 13, 0 }, // #61 [ref=10x]
- { { 0 }, 14, 0 }, // #62 [ref=8x]
- { { 0 }, 15, 0 }, // #63 [ref=8x]
- { { 0 }, 17, 0 }, // #64 [ref=8x]
- { { 0 }, 18, 0 }, // #65 [ref=4x]
- { { EXT(AVX512_DQ) }, 0, 0 }, // #66 [ref=23x]
- { { EXT(AVX512_BW) }, 0, 0 }, // #67 [ref=22x]
- { { EXT(AVX512_F) }, 0, 0 }, // #68 [ref=37x]
- { { EXT(AVX512_DQ) }, 1, 0 }, // #69 [ref=3x]
- { { EXT(AVX512_BW) }, 1, 0 }, // #70 [ref=4x]
- { { EXT(AVX512_F) }, 1, 0 }, // #71 [ref=1x]
- { { EXT(LAHFSAHF) }, 22, 0 }, // #72 [ref=1x]
- { { EXT(AMX_TILE) }, 0, 0 }, // #73 [ref=7x]
- { { EXT(LWP) }, 0, 0 }, // #74 [ref=4x]
- { { 0 }, 23, 0 }, // #75 [ref=3x]
- { { EXT(LZCNT) }, 1, 0 }, // #76 [ref=1x]
- { { EXT(MMX2) }, 0, 0 }, // #77 [ref=8x]
- { { EXT(MCOMMIT) }, 1, 0 }, // #78 [ref=1x]
- { { EXT(MONITOR) }, 0, 0 }, // #79 [ref=2x]
- { { EXT(MONITORX) }, 0, 0 }, // #80 [ref=2x]
- { { EXT(MOVBE) }, 0, 0 }, // #81 [ref=1x]
- { { EXT(MMX), EXT(SSE2) }, 0, 0 }, // #82 [ref=46x]
- { { EXT(MOVDIR64B) }, 0, 0 }, // #83 [ref=1x]
- { { EXT(MOVDIRI) }, 0, 0 }, // #84 [ref=1x]
- { { EXT(BMI2) }, 0, 0 }, // #85 [ref=7x]
- { { EXT(SSSE3) }, 0, 0 }, // #86 [ref=15x]
- { { EXT(MMX2), EXT(SSE2) }, 0, 0 }, // #87 [ref=10x]
- { { EXT(PCLMULQDQ) }, 0, 0 }, // #88 [ref=1x]
- { { EXT(SSE4_2) }, 1, 0 }, // #89 [ref=4x]
- { { EXT(PCONFIG) }, 0, 0 }, // #90 [ref=1x]
- { { EXT(MMX2), EXT(SSE2), EXT(SSE4_1) }, 0, 0 }, // #91 [ref=1x]
- { { EXT(3DNOW2) }, 0, 0 }, // #92 [ref=5x]
- { { EXT(GEODE) }, 0, 0 }, // #93 [ref=2x]
- { { EXT(POPCNT) }, 1, 0 }, // #94 [ref=1x]
- { { 0 }, 24, 0 }, // #95 [ref=3x]
- { { EXT(PREFETCHW) }, 1, 0 }, // #96 [ref=1x]
- { { EXT(PREFETCHWT1) }, 1, 0 }, // #97 [ref=1x]
- { { EXT(SNP) }, 20, 0 }, // #98 [ref=3x]
- { { EXT(SSE4_1) }, 1, 0 }, // #99 [ref=1x]
- { { EXT(PTWRITE) }, 0, 0 }, // #100 [ref=1x]
- { { 0 }, 25, 0 }, // #101 [ref=3x]
- { { EXT(SNP) }, 1, 0 }, // #102 [ref=1x]
- { { 0 }, 26, 0 }, // #103 [ref=2x]
- { { EXT(FSGSBASE) }, 0, 0 }, // #104 [ref=4x]
- { { EXT(MSR) }, 0, 0 }, // #105 [ref=2x]
- { { EXT(RDPID) }, 0, 0 }, // #106 [ref=1x]
- { { EXT(OSPKE) }, 0, 0 }, // #107 [ref=1x]
- { { EXT(RDPRU) }, 0, 0 }, // #108 [ref=1x]
- { { EXT(RDRAND) }, 1, 0 }, // #109 [ref=1x]
- { { EXT(RDSEED) }, 1, 0 }, // #110 [ref=1x]
- { { EXT(RDTSC) }, 0, 0 }, // #111 [ref=1x]
- { { EXT(RDTSCP) }, 0, 0 }, // #112 [ref=1x]
- { { 0 }, 27, 0 }, // #113 [ref=2x]
- { { EXT(LAHFSAHF) }, 28, 0 }, // #114 [ref=1x]
- { { EXT(SERIALIZE) }, 0, 0 }, // #115 [ref=1x]
- { { EXT(SHA) }, 0, 0 }, // #116 [ref=7x]
- { { EXT(SKINIT) }, 0, 0 }, // #117 [ref=2x]
- { { EXT(AMX_BF16) }, 0, 0 }, // #118 [ref=1x]
- { { EXT(AMX_INT8) }, 0, 0 }, // #119 [ref=4x]
- { { EXT(UINTR) }, 1, 0 }, // #120 [ref=1x]
- { { EXT(WAITPKG) }, 1, 0 }, // #121 [ref=2x]
- { { EXT(WAITPKG) }, 0, 0 }, // #122 [ref=1x]
- { { EXT(AVX512_4FMAPS) }, 0, 0 }, // #123 [ref=4x]
- { { EXT(AVX), EXT(AVX512_F), EXT(AVX512_VL) }, 0, 0 }, // #124 [ref=46x]
- { { EXT(AVX512_FP16), EXT(AVX512_VL) }, 0, 0 }, // #125 [ref=63x]
- { { EXT(AVX), EXT(AVX512_F) }, 0, 0 }, // #126 [ref=32x]
- { { EXT(AVX512_FP16) }, 0, 0 }, // #127 [ref=43x]
- { { EXT(AVX) }, 0, 0 }, // #128 [ref=37x]
- { { EXT(AESNI), EXT(AVX), EXT(AVX512_F), EXT(AVX512_VL), EXT(VAES) }, 0, 0 }, // #129 [ref=4x]
- { { EXT(AESNI), EXT(AVX) }, 0, 0 }, // #130 [ref=2x]
- { { EXT(AVX512_F), EXT(AVX512_VL) }, 0, 0 }, // #131 [ref=112x]
- { { EXT(AVX), EXT(AVX512_DQ), EXT(AVX512_VL) }, 0, 0 }, // #132 [ref=8x]
- { { EXT(AVX512_DQ), EXT(AVX512_VL) }, 0, 0 }, // #133 [ref=30x]
- { { EXT(AVX2) }, 0, 0 }, // #134 [ref=7x]
- { { EXT(AVX), EXT(AVX2), EXT(AVX512_F), EXT(AVX512_VL) }, 0, 0 }, // #135 [ref=39x]
- { { EXT(AVX), EXT(AVX512_F) }, 1, 0 }, // #136 [ref=4x]
- { { EXT(AVX512_BF16), EXT(AVX512_VL) }, 0, 0 }, // #137 [ref=3x]
- { { EXT(AVX512_F), EXT(AVX512_VL), EXT(F16C) }, 0, 0 }, // #138 [ref=2x]
- { { EXT(AVX512_BW), EXT(AVX512_VL) }, 0, 0 }, // #139 [ref=26x]
- { { EXT(AVX512_ERI) }, 0, 0 }, // #140 [ref=10x]
- { { EXT(AVX512_F), EXT(AVX512_VL), EXT(FMA) }, 0, 0 }, // #141 [ref=36x]
- { { EXT(AVX512_F), EXT(FMA) }, 0, 0 }, // #142 [ref=24x]
- { { EXT(FMA4) }, 0, 0 }, // #143 [ref=20x]
- { { EXT(XOP) }, 0, 0 }, // #144 [ref=55x]
- { { EXT(AVX2), EXT(AVX512_F), EXT(AVX512_VL) }, 0, 0 }, // #145 [ref=19x]
- { { EXT(AVX512_PFI) }, 0, 0 }, // #146 [ref=16x]
- { { EXT(AVX), EXT(AVX512_F), EXT(AVX512_VL), EXT(GFNI) }, 0, 0 }, // #147 [ref=3x]
- { { EXT(AVX), EXT(AVX2) }, 0, 0 }, // #148 [ref=17x]
- { { EXT(AVX512_VP2INTERSECT) }, 0, 0 }, // #149 [ref=2x]
- { { EXT(AVX512_4VNNIW) }, 0, 0 }, // #150 [ref=2x]
- { { EXT(AVX), EXT(AVX2), EXT(AVX512_BW), EXT(AVX512_VL) }, 0, 0 }, // #151 [ref=54x]
- { { EXT(AVX2), EXT(AVX512_BW), EXT(AVX512_VL) }, 0, 0 }, // #152 [ref=2x]
- { { EXT(AVX512_CDI), EXT(AVX512_VL) }, 0, 0 }, // #153 [ref=6x]
- { { EXT(AVX), EXT(AVX512_F), EXT(AVX512_VL), EXT(PCLMULQDQ), EXT(VPCLMULQDQ) }, 0, 0 }, // #154 [ref=1x]
- { { EXT(AVX) }, 1, 0 }, // #155 [ref=7x]
- { { EXT(AVX512_VBMI2), EXT(AVX512_VL) }, 0, 0 }, // #156 [ref=16x]
- { { EXT(AVX512_VL), EXT(AVX512_VNNI), EXT(AVX_VNNI) }, 0, 0 }, // #157 [ref=4x]
- { { EXT(AVX512_VBMI), EXT(AVX512_VL) }, 0, 0 }, // #158 [ref=4x]
- { { EXT(AVX), EXT(AVX512_BW) }, 0, 0 }, // #159 [ref=4x]
- { { EXT(AVX), EXT(AVX512_DQ) }, 0, 0 }, // #160 [ref=4x]
- { { EXT(AVX512_IFMA), EXT(AVX512_VL) }, 0, 0 }, // #161 [ref=2x]
- { { EXT(AVX512_BITALG), EXT(AVX512_VL) }, 0, 0 }, // #162 [ref=3x]
- { { EXT(AVX512_VL), EXT(AVX512_VPOPCNTDQ) }, 0, 0 }, // #163 [ref=2x]
- { { EXT(WBNOINVD) }, 0, 0 }, // #164 [ref=1x]
- { { EXT(RTM) }, 0, 0 }, // #165 [ref=3x]
- { { EXT(XSAVE) }, 0, 0 }, // #166 [ref=6x]
- { { EXT(TSXLDTRK) }, 0, 0 }, // #167 [ref=2x]
- { { EXT(XSAVES) }, 0, 0 }, // #168 [ref=4x]
- { { EXT(XSAVEC) }, 0, 0 }, // #169 [ref=2x]
- { { EXT(XSAVEOPT) }, 0, 0 }, // #170 [ref=2x]
- { { EXT(TSX) }, 1, 0 } // #171 [ref=1x]
+ { 0, 0, { 0 } }, // #0 [ref=148x]
+ { 0, 1, { 0 } }, // #1 [ref=32x]
+ { 0, 2, { 0 } }, // #2 [ref=2x]
+ { 0, 3, { EXT(ADX) } }, // #3 [ref=1x]
+ { 0, 0, { EXT(SSE2) } }, // #4 [ref=60x]
+ { 0, 0, { EXT(SSE) } }, // #5 [ref=41x]
+ { 0, 0, { EXT(SSE3) } }, // #6 [ref=12x]
+ { 0, 4, { EXT(ADX) } }, // #7 [ref=1x]
+ { 0, 0, { EXT(AESNI) } }, // #8 [ref=6x]
+ { 0, 1, { EXT(BMI) } }, // #9 [ref=6x]
+ { 0, 5, { 0 } }, // #10 [ref=5x]
+ { 0, 0, { EXT(TBM) } }, // #11 [ref=9x]
+ { 0, 0, { EXT(SSE4_1) } }, // #12 [ref=47x]
+ { 0, 0, { EXT(MPX) } }, // #13 [ref=7x]
+ { 0, 6, { 0 } }, // #14 [ref=4x]
+ { 0, 1, { EXT(BMI2) } }, // #15 [ref=1x]
+ { 0, 7, { EXT(SMAP) } }, // #16 [ref=2x]
+ { 0, 8, { 0 } }, // #17 [ref=2x]
+ { 0, 9, { 0 } }, // #18 [ref=2x]
+ { 0, 0, { EXT(CLDEMOTE) } }, // #19 [ref=1x]
+ { 0, 0, { EXT(CLFLUSH) } }, // #20 [ref=1x]
+ { 0, 0, { EXT(CLFLUSHOPT) } }, // #21 [ref=1x]
+ { 0, 0, { EXT(SVM) } }, // #22 [ref=6x]
+ { 0, 10, { 0 } }, // #23 [ref=2x]
+ { 0, 1, { EXT(CET_SS) } }, // #24 [ref=3x]
+ { 0, 0, { EXT(UINTR) } }, // #25 [ref=4x]
+ { 0, 0, { EXT(CLWB) } }, // #26 [ref=1x]
+ { 0, 0, { EXT(CLZERO) } }, // #27 [ref=1x]
+ { 0, 3, { 0 } }, // #28 [ref=1x]
+ { 0, 11, { EXT(CMOV) } }, // #29 [ref=6x]
+ { 0, 12, { EXT(CMOV) } }, // #30 [ref=8x]
+ { 0, 13, { EXT(CMOV) } }, // #31 [ref=6x]
+ { 0, 14, { EXT(CMOV) } }, // #32 [ref=4x]
+ { 0, 15, { EXT(CMOV) } }, // #33 [ref=4x]
+ { 0, 16, { EXT(CMOV) } }, // #34 [ref=2x]
+ { 0, 17, { EXT(CMOV) } }, // #35 [ref=6x]
+ { 0, 18, { EXT(CMOV) } }, // #36 [ref=2x]
+ { 0, 19, { 0 } }, // #37 [ref=2x]
+ { 0, 1, { EXT(I486) } }, // #38 [ref=2x]
+ { 0, 5, { EXT(CMPXCHG16B) } }, // #39 [ref=1x]
+ { 0, 5, { EXT(CMPXCHG8B) } }, // #40 [ref=1x]
+ { 0, 1, { EXT(SSE2) } }, // #41 [ref=2x]
+ { 0, 1, { EXT(SSE) } }, // #42 [ref=2x]
+ { 0, 0, { EXT(I486) } }, // #43 [ref=4x]
+ { 0, 0, { EXT(SSE4_2) } }, // #44 [ref=2x]
+ { 0, 20, { 0 } }, // #45 [ref=2x]
+ { 0, 0, { EXT(MMX) } }, // #46 [ref=1x]
+ { 0, 0, { EXT(CET_IBT) } }, // #47 [ref=2x]
+ { 0, 0, { EXT(ENQCMD) } }, // #48 [ref=2x]
+ { 0, 0, { EXT(SSE4A) } }, // #49 [ref=4x]
+ { 0, 21, { 0 } }, // #50 [ref=4x]
+ { 0, 0, { EXT(3DNOW) } }, // #51 [ref=21x]
+ { 0, 0, { EXT(FXSR) } }, // #52 [ref=4x]
+ { 0, 0, { EXT(SMX) } }, // #53 [ref=1x]
+ { 0, 0, { EXT(GFNI) } }, // #54 [ref=3x]
+ { 0, 0, { EXT(HRESET) } }, // #55 [ref=1x]
+ { 0, 0, { EXT(CET_SS) } }, // #56 [ref=9x]
+ { 0, 16, { 0 } }, // #57 [ref=5x]
+ { 0, 0, { EXT(VMX) } }, // #58 [ref=12x]
+ { 0, 11, { 0 } }, // #59 [ref=8x]
+ { 0, 12, { 0 } }, // #60 [ref=12x]
+ { 0, 13, { 0 } }, // #61 [ref=10x]
+ { 0, 14, { 0 } }, // #62 [ref=8x]
+ { 0, 15, { 0 } }, // #63 [ref=8x]
+ { 0, 17, { 0 } }, // #64 [ref=8x]
+ { 0, 18, { 0 } }, // #65 [ref=4x]
+ { 0, 0, { EXT(AVX512_DQ) } }, // #66 [ref=22x]
+ { 0, 0, { EXT(AVX512_BW) } }, // #67 [ref=20x]
+ { 0, 0, { EXT(AVX512_F) } }, // #68 [ref=36x]
+ { 1, 0, { EXT(AVX512_DQ) } }, // #69 [ref=1x]
+ { 1, 0, { EXT(AVX512_BW) } }, // #70 [ref=2x]
+ { 1, 0, { EXT(AVX512_F) } }, // #71 [ref=1x]
+ { 0, 1, { EXT(AVX512_DQ) } }, // #72 [ref=3x]
+ { 0, 1, { EXT(AVX512_BW) } }, // #73 [ref=4x]
+ { 0, 1, { EXT(AVX512_F) } }, // #74 [ref=1x]
+ { 0, 22, { EXT(LAHFSAHF) } }, // #75 [ref=1x]
+ { 0, 0, { EXT(AMX_TILE) } }, // #76 [ref=7x]
+ { 0, 0, { EXT(LWP) } }, // #77 [ref=4x]
+ { 0, 23, { 0 } }, // #78 [ref=3x]
+ { 0, 1, { EXT(LZCNT) } }, // #79 [ref=1x]
+ { 0, 0, { EXT(MMX2) } }, // #80 [ref=8x]
+ { 0, 1, { EXT(MCOMMIT) } }, // #81 [ref=1x]
+ { 0, 0, { EXT(MONITOR) } }, // #82 [ref=2x]
+ { 0, 0, { EXT(MONITORX) } }, // #83 [ref=2x]
+ { 1, 0, { 0 } }, // #84 [ref=1x]
+ { 1, 0, { EXT(SSE2) } }, // #85 [ref=5x]
+ { 1, 0, { EXT(SSE) } }, // #86 [ref=3x]
+ { 0, 0, { EXT(MOVBE) } }, // #87 [ref=1x]
+ { 0, 0, { EXT(MMX), EXT(SSE2) } }, // #88 [ref=45x]
+ { 0, 0, { EXT(MOVDIR64B) } }, // #89 [ref=1x]
+ { 0, 0, { EXT(MOVDIRI) } }, // #90 [ref=1x]
+ { 1, 0, { EXT(MMX), EXT(SSE2) } }, // #91 [ref=1x]
+ { 0, 0, { EXT(BMI2) } }, // #92 [ref=7x]
+ { 0, 0, { EXT(SSSE3) } }, // #93 [ref=15x]
+ { 0, 0, { EXT(MMX2), EXT(SSE2) } }, // #94 [ref=10x]
+ { 0, 0, { EXT(PCLMULQDQ) } }, // #95 [ref=1x]
+ { 0, 1, { EXT(SSE4_2) } }, // #96 [ref=4x]
+ { 0, 0, { EXT(PCONFIG) } }, // #97 [ref=1x]
+ { 0, 0, { EXT(MMX2), EXT(SSE2), EXT(SSE4_1) } }, // #98 [ref=1x]
+ { 0, 0, { EXT(3DNOW2) } }, // #99 [ref=5x]
+ { 0, 0, { EXT(GEODE) } }, // #100 [ref=2x]
+ { 0, 1, { EXT(POPCNT) } }, // #101 [ref=1x]
+ { 0, 24, { 0 } }, // #102 [ref=3x]
+ { 0, 1, { EXT(PREFETCHW) } }, // #103 [ref=1x]
+ { 0, 1, { EXT(PREFETCHWT1) } }, // #104 [ref=1x]
+ { 0, 20, { EXT(SNP) } }, // #105 [ref=3x]
+ { 0, 1, { EXT(SSE4_1) } }, // #106 [ref=1x]
+ { 0, 0, { EXT(PTWRITE) } }, // #107 [ref=1x]
+ { 0, 25, { 0 } }, // #108 [ref=3x]
+ { 0, 1, { EXT(SNP) } }, // #109 [ref=1x]
+ { 0, 26, { 0 } }, // #110 [ref=2x]
+ { 0, 0, { EXT(FSGSBASE) } }, // #111 [ref=4x]
+ { 0, 0, { EXT(MSR) } }, // #112 [ref=2x]
+ { 0, 0, { EXT(RDPID) } }, // #113 [ref=1x]
+ { 0, 0, { EXT(OSPKE) } }, // #114 [ref=1x]
+ { 0, 0, { EXT(RDPRU) } }, // #115 [ref=1x]
+ { 0, 1, { EXT(RDRAND) } }, // #116 [ref=1x]
+ { 0, 1, { EXT(RDSEED) } }, // #117 [ref=1x]
+ { 0, 0, { EXT(RDTSC) } }, // #118 [ref=1x]
+ { 0, 0, { EXT(RDTSCP) } }, // #119 [ref=1x]
+ { 0, 27, { 0 } }, // #120 [ref=2x]
+ { 0, 28, { EXT(LAHFSAHF) } }, // #121 [ref=1x]
+ { 0, 0, { EXT(SERIALIZE) } }, // #122 [ref=1x]
+ { 0, 0, { EXT(SHA) } }, // #123 [ref=7x]
+ { 0, 0, { EXT(SKINIT) } }, // #124 [ref=2x]
+ { 0, 0, { EXT(AMX_BF16) } }, // #125 [ref=1x]
+ { 0, 0, { EXT(AMX_INT8) } }, // #126 [ref=4x]
+ { 0, 1, { EXT(UINTR) } }, // #127 [ref=1x]
+ { 0, 1, { EXT(WAITPKG) } }, // #128 [ref=2x]
+ { 0, 0, { EXT(WAITPKG) } }, // #129 [ref=1x]
+ { 0, 0, { EXT(AVX512_4FMAPS) } }, // #130 [ref=4x]
+ { 0, 0, { EXT(AVX), EXT(AVX512_F), EXT(AVX512_VL) } }, // #131 [ref=42x]
+ { 0, 0, { EXT(AVX512_FP16), EXT(AVX512_VL) } }, // #132 [ref=63x]
+ { 0, 0, { EXT(AVX), EXT(AVX512_F) } }, // #133 [ref=29x]
+ { 0, 0, { EXT(AVX512_FP16) } }, // #134 [ref=43x]
+ { 0, 0, { EXT(AVX) } }, // #135 [ref=35x]
+ { 0, 0, { EXT(AESNI), EXT(AVX), EXT(AVX512_F), EXT(AVX512_VL), EXT(VAES) } }, // #136 [ref=4x]
+ { 0, 0, { EXT(AESNI), EXT(AVX) } }, // #137 [ref=2x]
+ { 0, 0, { EXT(AVX512_F), EXT(AVX512_VL) } }, // #138 [ref=108x]
+ { 0, 0, { EXT(AVX), EXT(AVX512_DQ), EXT(AVX512_VL) } }, // #139 [ref=8x]
+ { 0, 0, { EXT(AVX512_DQ), EXT(AVX512_VL) } }, // #140 [ref=30x]
+ { 0, 0, { EXT(AVX2) } }, // #141 [ref=7x]
+ { 0, 0, { EXT(AVX), EXT(AVX2), EXT(AVX512_F), EXT(AVX512_VL) } }, // #142 [ref=39x]
+ { 0, 1, { EXT(AVX), EXT(AVX512_F) } }, // #143 [ref=4x]
+ { 0, 0, { EXT(AVX512_BF16), EXT(AVX512_VL) } }, // #144 [ref=3x]
+ { 0, 0, { EXT(AVX512_F), EXT(AVX512_VL), EXT(F16C) } }, // #145 [ref=2x]
+ { 0, 0, { EXT(AVX512_BW), EXT(AVX512_VL) } }, // #146 [ref=24x]
+ { 0, 0, { EXT(AVX512_ERI) } }, // #147 [ref=10x]
+ { 0, 0, { EXT(AVX512_F), EXT(AVX512_VL), EXT(FMA) } }, // #148 [ref=36x]
+ { 0, 0, { EXT(AVX512_F), EXT(FMA) } }, // #149 [ref=24x]
+ { 0, 0, { EXT(FMA4) } }, // #150 [ref=20x]
+ { 0, 0, { EXT(XOP) } }, // #151 [ref=55x]
+ { 0, 0, { EXT(AVX2), EXT(AVX512_F), EXT(AVX512_VL) } }, // #152 [ref=19x]
+ { 0, 0, { EXT(AVX512_PFI) } }, // #153 [ref=16x]
+ { 0, 0, { EXT(AVX), EXT(AVX512_F), EXT(AVX512_VL), EXT(GFNI) } }, // #154 [ref=3x]
+ { 1, 0, { EXT(AVX), EXT(AVX512_F), EXT(AVX512_VL) } }, // #155 [ref=4x]
+ { 1, 0, { EXT(AVX) } }, // #156 [ref=2x]
+ { 1, 0, { EXT(AVX512_F), EXT(AVX512_VL) } }, // #157 [ref=4x]
+ { 1, 0, { EXT(AVX512_BW), EXT(AVX512_VL) } }, // #158 [ref=2x]
+ { 1, 0, { EXT(AVX), EXT(AVX512_F) } }, // #159 [ref=3x]
+ { 0, 0, { EXT(AVX), EXT(AVX2) } }, // #160 [ref=17x]
+ { 0, 0, { EXT(AVX512_VP2INTERSECT) } }, // #161 [ref=2x]
+ { 0, 0, { EXT(AVX512_4VNNIW) } }, // #162 [ref=2x]
+ { 0, 0, { EXT(AVX), EXT(AVX2), EXT(AVX512_BW), EXT(AVX512_VL) } }, // #163 [ref=54x]
+ { 0, 0, { EXT(AVX2), EXT(AVX512_BW), EXT(AVX512_VL) } }, // #164 [ref=2x]
+ { 0, 0, { EXT(AVX512_CDI), EXT(AVX512_VL) } }, // #165 [ref=6x]
+ { 0, 0, { EXT(AVX), EXT(AVX512_F), EXT(AVX512_VL), EXT(PCLMULQDQ), EXT(VPCLMULQDQ) } }, // #166 [ref=1x]
+ { 0, 1, { EXT(AVX) } }, // #167 [ref=7x]
+ { 0, 0, { EXT(AVX512_VBMI2), EXT(AVX512_VL) } }, // #168 [ref=16x]
+ { 0, 0, { EXT(AVX512_VL), EXT(AVX512_VNNI), EXT(AVX_VNNI) } }, // #169 [ref=4x]
+ { 0, 0, { EXT(AVX512_VBMI), EXT(AVX512_VL) } }, // #170 [ref=4x]
+ { 0, 0, { EXT(AVX), EXT(AVX512_BW) } }, // #171 [ref=4x]
+ { 0, 0, { EXT(AVX), EXT(AVX512_DQ) } }, // #172 [ref=4x]
+ { 0, 0, { EXT(AVX512_IFMA), EXT(AVX512_VL) } }, // #173 [ref=2x]
+ { 0, 0, { EXT(AVX512_BITALG), EXT(AVX512_VL) } }, // #174 [ref=3x]
+ { 0, 0, { EXT(AVX512_VL), EXT(AVX512_VPOPCNTDQ) } }, // #175 [ref=2x]
+ { 0, 0, { EXT(WBNOINVD) } }, // #176 [ref=1x]
+ { 0, 0, { EXT(RTM) } }, // #177 [ref=3x]
+ { 0, 0, { EXT(XSAVE) } }, // #178 [ref=6x]
+ { 0, 0, { EXT(TSXLDTRK) } }, // #179 [ref=2x]
+ { 0, 0, { EXT(XSAVES) } }, // #180 [ref=4x]
+ { 0, 0, { EXT(XSAVEC) } }, // #181 [ref=2x]
+ { 0, 0, { EXT(XSAVEOPT) } }, // #182 [ref=2x]
+ { 0, 1, { EXT(TSX) } } // #183 [ref=1x]
};
#undef EXT
@@ -2817,6 +2821,13 @@ const InstDB::RWFlagsInfoTable InstDB::_rwFlagsInfoTable[] = {
{ 0, FLAG(AF) | FLAG(CF) | FLAG(PF) | FLAG(SF) | FLAG(ZF) } // #28 [ref=1x]
};
#undef FLAG
+
+#define FLAG(VAL) uint32_t(InstRWFlags::k##VAL)
+const InstRWFlags InstDB::_instFlagsTable[] = {
+ InstRWFlags(FLAG(None)), // #0 [ref=1634x]
+ InstRWFlags(FLAG(MovOp)) // #1 [ref=29x]
+};
+#undef FLAG
// ----------------------------------------------------------------------------
// ${AdditionalInfoTable:End}
@@ -2826,161 +2837,1698 @@ const InstDB::RWFlagsInfoTable InstDB::_rwFlagsInfoTable[] = {
#ifndef ASMJIT_NO_TEXT
// ${NameData:Begin}
// ------------------- Automatically generated, do not edit -------------------
-const char InstDB::_nameData[] =
- "\0" "aaa\0" "aad\0" "aam\0" "aas\0" "adc\0" "adcx\0" "adox\0" "arpl\0" "bextr\0" "blcfill\0" "blci\0" "blcic\0"
- "blcmsk\0" "blcs\0" "blsfill\0" "blsi\0" "blsic\0" "blsmsk\0" "blsr\0" "bndcl\0" "bndcn\0" "bndcu\0" "bndldx\0"
- "bndmk\0" "bndmov\0" "bndstx\0" "bound\0" "bsf\0" "bsr\0" "bswap\0" "bt\0" "btc\0" "btr\0" "bts\0" "bzhi\0" "cbw\0"
- "cdq\0" "cdqe\0" "clac\0" "clc\0" "cld\0" "cldemote\0" "clflush\0" "clflushopt\0" "clgi\0" "cli\0" "clrssbsy\0"
- "clts\0" "clui\0" "clwb\0" "clzero\0" "cmc\0" "cmova\0" "cmovae\0" "cmovc\0" "cmovg\0" "cmovge\0" "cmovl\0"
- "cmovle\0" "cmovna\0" "cmovnae\0" "cmovnc\0" "cmovng\0" "cmovnge\0" "cmovnl\0" "cmovnle\0" "cmovno\0" "cmovnp\0"
- "cmovns\0" "cmovnz\0" "cmovo\0" "cmovp\0" "cmovpe\0" "cmovpo\0" "cmovs\0" "cmovz\0" "cmp\0" "cmps\0" "cmpxchg\0"
- "cmpxchg16b\0" "cmpxchg8b\0" "cpuid\0" "cqo\0" "crc32\0" "cvtpd2pi\0" "cvtpi2pd\0" "cvtpi2ps\0" "cvtps2pi\0"
- "cvttpd2pi\0" "cvttps2pi\0" "cwd\0" "cwde\0" "daa\0" "das\0" "endbr32\0" "endbr64\0" "enqcmd\0" "enqcmds\0" "f2xm1\0"
- "fabs\0" "faddp\0" "fbld\0" "fbstp\0" "fchs\0" "fclex\0" "fcmovb\0" "fcmovbe\0" "fcmove\0" "fcmovnb\0" "fcmovnbe\0"
- "fcmovne\0" "fcmovnu\0" "fcmovu\0" "fcom\0" "fcomi\0" "fcomip\0" "fcomp\0" "fcompp\0" "fcos\0" "fdecstp\0" "fdiv\0"
- "fdivp\0" "fdivr\0" "fdivrp\0" "femms\0" "ffree\0" "fiadd\0" "ficom\0" "ficomp\0" "fidiv\0" "fidivr\0" "fild\0"
- "fimul\0" "fincstp\0" "finit\0" "fist\0" "fistp\0" "fisttp\0" "fisub\0" "fisubr\0" "fld\0" "fld1\0" "fldcw\0"
- "fldenv\0" "fldl2e\0" "fldl2t\0" "fldlg2\0" "fldln2\0" "fldpi\0" "fldz\0" "fmulp\0" "fnclex\0" "fninit\0" "fnop\0"
- "fnsave\0" "fnstcw\0" "fnstenv\0" "fnstsw\0" "fpatan\0" "fprem\0" "fprem1\0" "fptan\0" "frndint\0" "frstor\0"
- "fsave\0" "fscale\0" "fsin\0" "fsincos\0" "fsqrt\0" "fst\0" "fstcw\0" "fstenv\0" "fstp\0" "fstsw\0" "fsubp\0"
- "fsubrp\0" "ftst\0" "fucom\0" "fucomi\0" "fucomip\0" "fucomp\0" "fucompp\0" "fwait\0" "fxam\0" "fxch\0" "fxrstor\0"
- "fxrstor64\0" "fxsave\0" "fxsave64\0" "fxtract\0" "fyl2x\0" "fyl2xp1\0" "getsec\0" "hlt\0" "hreset\0" "inc\0"
- "incsspd\0" "incsspq\0" "insertq\0" "int3\0" "into\0" "invept\0" "invlpg\0" "invlpga\0" "invpcid\0" "invvpid\0"
- "iretd\0" "iretq\0" "ja\0" "jae\0" "jb\0" "jbe\0" "jc\0" "je\0" "jecxz\0" "jg\0" "jge\0" "jl\0" "jle\0" "jna\0"
- "jnae\0" "jnb\0" "jnbe\0" "jnc\0" "jne\0" "jng\0" "jnge\0" "jnl\0" "jnle\0" "jno\0" "jnp\0" "jns\0" "jnz\0" "jo\0"
- "jp\0" "jpe\0" "jpo\0" "js\0" "jz\0" "kaddb\0" "kaddd\0" "kaddq\0" "kaddw\0" "kandb\0" "kandd\0" "kandnb\0"
- "kandnd\0" "kandnq\0" "kandnw\0" "kandq\0" "kandw\0" "kmovb\0" "kmovw\0" "knotb\0" "knotd\0" "knotq\0" "knotw\0"
- "korb\0" "kord\0" "korq\0" "kortestb\0" "kortestd\0" "kortestq\0" "kortestw\0" "korw\0" "kshiftlb\0" "kshiftld\0"
- "kshiftlq\0" "kshiftlw\0" "kshiftrb\0" "kshiftrd\0" "kshiftrq\0" "kshiftrw\0" "ktestb\0" "ktestd\0" "ktestq\0"
- "ktestw\0" "kunpckbw\0" "kunpckdq\0" "kunpckwd\0" "kxnorb\0" "kxnord\0" "kxnorq\0" "kxnorw\0" "kxorb\0" "kxord\0"
- "kxorq\0" "kxorw\0" "lahf\0" "lar\0" "lcall\0" "lds\0" "ldtilecfg\0" "lea\0" "leave\0" "les\0" "lfence\0" "lfs\0"
- "lgdt\0" "lgs\0" "lidt\0" "ljmp\0" "lldt\0" "llwpcb\0" "lmsw\0" "lods\0" "loop\0" "loope\0" "loopne\0" "lsl\0"
- "ltr\0" "lwpins\0" "lwpval\0" "lzcnt\0" "mcommit\0" "mfence\0" "monitorx\0" "movabs\0" "movdir64b\0" "movdiri\0"
- "movdq2q\0" "movnti\0" "movntq\0" "movntsd\0" "movntss\0" "movq2dq\0" "movsx\0" "movsxd\0" "movzx\0" "mulx\0"
- "mwaitx\0" "neg\0" "not\0" "out\0" "outs\0" "pavgusb\0" "pconfig\0" "pdep\0" "pext\0" "pf2id\0" "pf2iw\0" "pfacc\0"
- "pfadd\0" "pfcmpeq\0" "pfcmpge\0" "pfcmpgt\0" "pfmax\0" "pfmin\0" "pfmul\0" "pfnacc\0" "pfpnacc\0" "pfrcp\0"
- "pfrcpit1\0" "pfrcpit2\0" "pfrcpv\0" "pfrsqit1\0" "pfrsqrt\0" "pfrsqrtv\0" "pfsub\0" "pfsubr\0" "pi2fd\0" "pi2fw\0"
- "pmulhrw\0" "pop\0" "popa\0" "popad\0" "popcnt\0" "popf\0" "popfd\0" "popfq\0" "prefetch\0" "prefetchnta\0"
- "prefetcht0\0" "prefetcht1\0" "prefetcht2\0" "prefetchw\0" "prefetchwt1\0" "pshufw\0" "psmash\0" "pswapd\0"
- "ptwrite\0" "push\0" "pusha\0" "pushad\0" "pushf\0" "pushfd\0" "pushfq\0" "pvalidate\0" "rcl\0" "rcr\0" "rdfsbase\0"
- "rdgsbase\0" "rdmsr\0" "rdpid\0" "rdpkru\0" "rdpmc\0" "rdpru\0" "rdrand\0" "rdseed\0" "rdsspd\0" "rdsspq\0" "rdtsc\0"
- "rdtscp\0" "retf\0" "rmpadjust\0" "rmpupdate\0" "rol\0" "ror\0" "rorx\0" "rsm\0" "rstorssp\0" "sahf\0" "sal\0"
- "sar\0" "sarx\0" "saveprevssp\0" "sbb\0" "scas\0" "senduipi\0" "serialize\0" "seta\0" "setae\0" "setb\0" "setbe\0"
- "setc\0" "sete\0" "setg\0" "setge\0" "setl\0" "setle\0" "setna\0" "setnae\0" "setnb\0" "setnbe\0" "setnc\0" "setne\0"
- "setng\0" "setnge\0" "setnl\0" "setnle\0" "setno\0" "setnp\0" "setns\0" "setnz\0" "seto\0" "setp\0" "setpe\0"
- "setpo\0" "sets\0" "setssbsy\0" "setz\0" "sfence\0" "sgdt\0" "sha1msg1\0" "sha1msg2\0" "sha1nexte\0" "sha1rnds4\0"
- "sha256msg1\0" "sha256msg2\0" "sha256rnds2\0" "shl\0" "shlx\0" "shr\0" "shrd\0" "shrx\0" "sidt\0" "skinit\0" "sldt\0"
- "slwpcb\0" "smsw\0" "stac\0" "stc\0" "stgi\0" "sti\0" "stos\0" "str\0" "sttilecfg\0" "swapgs\0" "syscall\0"
- "sysenter\0" "sysexit\0" "sysexitq\0" "sysret\0" "sysretq\0" "t1mskc\0" "tdpbf16ps\0" "tdpbssd\0" "tdpbsud\0"
- "tdpbusd\0" "tdpbuud\0" "testui\0" "tileloadd\0" "tileloaddt1\0" "tilerelease\0" "tilestored\0" "tilezero\0"
- "tpause\0" "tzcnt\0" "tzmsk\0" "ud0\0" "ud1\0" "ud2\0" "uiret\0" "umonitor\0" "umwait\0" "v4fmaddps\0" "v4fmaddss\0"
- "v4fnmaddps\0" "v4fnmaddss\0" "vaddpd\0" "vaddph\0" "vaddps\0" "vaddsd\0" "vaddsh\0" "vaddss\0" "vaddsubpd\0"
- "vaddsubps\0" "vaesdec\0" "vaesdeclast\0" "vaesenc\0" "vaesenclast\0" "vaesimc\0" "vaeskeygenassist\0" "valignd\0"
- "valignq\0" "vandnpd\0" "vandnps\0" "vandpd\0" "vandps\0" "vblendmpd\0" "vblendmps\0" "vblendpd\0" "vblendps\0"
- "vblendvpd\0" "vblendvps\0" "vbroadcastf128\0" "vbroadcastf32x2\0" "vbroadcastf32x4\0" "vbroadcastf32x8\0"
- "vbroadcastf64x2\0" "vbroadcastf64x4\0" "vbroadcasti128\0" "vbroadcasti32x2\0" "vbroadcasti32x4\0"
- "vbroadcasti32x8\0" "vbroadcasti64x2\0" "vbroadcasti64x4\0" "vbroadcastsd\0" "vbroadcastss\0" "vcmppd\0" "vcmpph\0"
- "vcmpps\0" "vcmpsd\0" "vcmpsh\0" "vcmpss\0" "vcomisd\0" "vcomish\0" "vcomiss\0" "vcompresspd\0" "vcompressps\0"
- "vcvtdq2pd\0" "vcvtdq2ph\0" "vcvtdq2ps\0" "vcvtne2ps2bf16\0" "vcvtneps2bf16\0" "vcvtpd2dq\0" "vcvtpd2ph\0"
- "vcvtpd2ps\0" "vcvtpd2qq\0" "vcvtpd2udq\0" "vcvtpd2uqq\0" "vcvtph2dq\0" "vcvtph2pd\0" "vcvtph2ps\0" "vcvtph2psx\0"
- "vcvtph2qq\0" "vcvtph2udq\0" "vcvtph2uqq\0" "vcvtph2uw\0" "vcvtph2w\0" "vcvtps2dq\0" "vcvtps2pd\0" "vcvtps2ph\0"
- "vcvtps2phx\0" "vcvtps2qq\0" "vcvtps2udq\0" "vcvtps2uqq\0" "vcvtqq2pd\0" "vcvtqq2ph\0" "vcvtqq2ps\0" "vcvtsd2sh\0"
- "vcvtsd2si\0" "vcvtsd2ss\0" "vcvtsd2usi\0" "vcvtsh2sd\0" "vcvtsh2si\0" "vcvtsh2ss\0" "vcvtsh2usi\0" "vcvtsi2sd\0"
- "vcvtsi2sh\0" "vcvtsi2ss\0" "vcvtss2sd\0" "vcvtss2sh\0" "vcvtss2si\0" "vcvtss2usi\0" "vcvttpd2dq\0" "vcvttpd2qq\0"
- "vcvttpd2udq\0" "vcvttpd2uqq\0" "vcvttph2dq\0" "vcvttph2qq\0" "vcvttph2udq\0" "vcvttph2uqq\0" "vcvttph2uw\0"
- "vcvttph2w\0" "vcvttps2dq\0" "vcvttps2qq\0" "vcvttps2udq\0" "vcvttps2uqq\0" "vcvttsd2si\0" "vcvttsd2usi\0"
- "vcvttsh2si\0" "vcvttsh2usi\0" "vcvttss2si\0" "vcvttss2usi\0" "vcvtudq2pd\0" "vcvtudq2ph\0" "vcvtudq2ps\0"
- "vcvtuqq2pd\0" "vcvtuqq2ph\0" "vcvtuqq2ps\0" "vcvtusi2sd\0" "vcvtusi2sh\0" "vcvtusi2ss\0" "vcvtuw2ph\0" "vcvtw2ph\0"
- "vdbpsadbw\0" "vdivpd\0" "vdivph\0" "vdivps\0" "vdivsd\0" "vdivsh\0" "vdivss\0" "vdpbf16ps\0" "vdppd\0" "vdpps\0"
- "verr\0" "verw\0" "vexp2pd\0" "vexp2ps\0" "vexpandpd\0" "vexpandps\0" "vextractf128\0" "vextractf32x4\0"
- "vextractf32x8\0" "vextractf64x2\0" "vextractf64x4\0" "vextracti128\0" "vextracti32x4\0" "vextracti32x8\0"
- "vextracti64x2\0" "vextracti64x4\0" "vextractps\0" "vfcmaddcph\0" "vfcmaddcsh\0" "vfcmulcph\0" "vfcmulcsh\0"
- "vfixupimmpd\0" "vfixupimmps\0" "vfixupimmsd\0" "vfixupimmss\0" "vfmadd132pd\0" "vfmadd132ph\0" "vfmadd132ps\0"
- "vfmadd132sd\0" "vfmadd132sh\0" "vfmadd132ss\0" "vfmadd213pd\0" "vfmadd213ph\0" "vfmadd213ps\0" "vfmadd213sd\0"
- "vfmadd213sh\0" "vfmadd213ss\0" "vfmadd231pd\0" "vfmadd231ph\0" "vfmadd231ps\0" "vfmadd231sd\0" "vfmadd231sh\0"
- "vfmadd231ss\0" "vfmaddcph\0" "vfmaddcsh\0" "vfmaddpd\0" "vfmaddps\0" "vfmaddsd\0" "vfmaddss\0" "vfmaddsub132pd\0"
- "vfmaddsub132ph\0" "vfmaddsub132ps\0" "vfmaddsub213pd\0" "vfmaddsub213ph\0" "vfmaddsub213ps\0" "vfmaddsub231pd\0"
- "vfmaddsub231ph\0" "vfmaddsub231ps\0" "vfmaddsubpd\0" "vfmaddsubps\0" "vfmsub132pd\0" "vfmsub132ph\0" "vfmsub132ps\0"
- "vfmsub132sd\0" "vfmsub132sh\0" "vfmsub132ss\0" "vfmsub213pd\0" "vfmsub213ph\0" "vfmsub213ps\0" "vfmsub213sd\0"
- "vfmsub213sh\0" "vfmsub213ss\0" "vfmsub231pd\0" "vfmsub231ph\0" "vfmsub231ps\0" "vfmsub231sd\0" "vfmsub231sh\0"
- "vfmsub231ss\0" "vfmsubadd132pd\0" "vfmsubadd132ph\0" "vfmsubadd132ps\0" "vfmsubadd213pd\0" "vfmsubadd213ph\0"
- "vfmsubadd213ps\0" "vfmsubadd231pd\0" "vfmsubadd231ph\0" "vfmsubadd231ps\0" "vfmsubaddpd\0" "vfmsubaddps\0"
- "vfmsubpd\0" "vfmsubps\0" "vfmsubsd\0" "vfmsubss\0" "vfmulcph\0" "vfmulcsh\0" "vfnmadd132pd\0" "vfnmadd132ph\0"
- "vfnmadd132ps\0" "vfnmadd132sd\0" "vfnmadd132sh\0" "vfnmadd132ss\0" "vfnmadd213pd\0" "vfnmadd213ph\0"
- "vfnmadd213ps\0" "vfnmadd213sd\0" "vfnmadd213sh\0" "vfnmadd213ss\0" "vfnmadd231pd\0" "vfnmadd231ph\0"
- "vfnmadd231ps\0" "vfnmadd231sd\0" "vfnmadd231sh\0" "vfnmadd231ss\0" "vfnmaddpd\0" "vfnmaddps\0" "vfnmaddsd\0"
- "vfnmaddss\0" "vfnmsub132pd\0" "vfnmsub132ph\0" "vfnmsub132ps\0" "vfnmsub132sd\0" "vfnmsub132sh\0" "vfnmsub132ss\0"
- "vfnmsub213pd\0" "vfnmsub213ph\0" "vfnmsub213ps\0" "vfnmsub213sd\0" "vfnmsub213sh\0" "vfnmsub213ss\0"
- "vfnmsub231pd\0" "vfnmsub231ph\0" "vfnmsub231ps\0" "vfnmsub231sd\0" "vfnmsub231sh\0" "vfnmsub231ss\0" "vfnmsubpd\0"
- "vfnmsubps\0" "vfnmsubsd\0" "vfnmsubss\0" "vfpclasspd\0" "vfpclassph\0" "vfpclassps\0" "vfpclasssd\0" "vfpclasssh\0"
- "vfpclassss\0" "vfrczpd\0" "vfrczps\0" "vfrczsd\0" "vfrczss\0" "vgatherdpd\0" "vgatherdps\0" "vgatherpf0dpd\0"
- "vgatherpf0dps\0" "vgatherpf0qpd\0" "vgatherpf0qps\0" "vgatherpf1dpd\0" "vgatherpf1dps\0" "vgatherpf1qpd\0"
- "vgatherpf1qps\0" "vgatherqpd\0" "vgatherqps\0" "vgetexppd\0" "vgetexpph\0" "vgetexpps\0" "vgetexpsd\0" "vgetexpsh\0"
- "vgetexpss\0" "vgetmantpd\0" "vgetmantph\0" "vgetmantps\0" "vgetmantsd\0" "vgetmantsh\0" "vgetmantss\0"
- "vgf2p8affineinvqb\0" "vgf2p8affineqb\0" "vgf2p8mulb\0" "vhaddpd\0" "vhaddps\0" "vhsubpd\0" "vhsubps\0"
- "vinsertf128\0" "vinsertf32x4\0" "vinsertf32x8\0" "vinsertf64x2\0" "vinsertf64x4\0" "vinserti128\0" "vinserti32x4\0"
- "vinserti32x8\0" "vinserti64x2\0" "vinserti64x4\0" "vinsertps\0" "vlddqu\0" "vldmxcsr\0" "vmaskmovdqu\0"
- "vmaskmovpd\0" "vmaskmovps\0" "vmaxpd\0" "vmaxph\0" "vmaxps\0" "vmaxsd\0" "vmaxsh\0" "vmaxss\0" "vmcall\0"
- "vmclear\0" "vmfunc\0" "vminpd\0" "vminph\0" "vminps\0" "vminsd\0" "vminsh\0" "vminss\0" "vmlaunch\0" "vmload\0"
- "vmmcall\0" "vmovapd\0" "vmovaps\0" "vmovd\0" "vmovddup\0" "vmovdqa\0" "vmovdqa32\0" "vmovdqa64\0" "vmovdqu\0"
- "vmovdqu16\0" "vmovdqu32\0" "vmovdqu64\0" "vmovdqu8\0" "vmovhlps\0" "vmovhpd\0" "vmovhps\0" "vmovlhps\0" "vmovlpd\0"
- "vmovlps\0" "vmovmskpd\0" "vmovmskps\0" "vmovntdq\0" "vmovntdqa\0" "vmovntpd\0" "vmovntps\0" "vmovq\0" "vmovsd\0"
- "vmovsh\0" "vmovshdup\0" "vmovsldup\0" "vmovss\0" "vmovupd\0" "vmovups\0" "vmovw\0" "vmpsadbw\0" "vmptrld\0"
- "vmptrst\0" "vmread\0" "vmresume\0" "vmrun\0" "vmsave\0" "vmulpd\0" "vmulph\0" "vmulps\0" "vmulsd\0" "vmulsh\0"
- "vmulss\0" "vmwrite\0" "vmxon\0" "vorpd\0" "vorps\0" "vp2intersectd\0" "vp2intersectq\0" "vp4dpwssd\0" "vp4dpwssds\0"
- "vpabsb\0" "vpabsd\0" "vpabsq\0" "vpabsw\0" "vpackssdw\0" "vpacksswb\0" "vpackusdw\0" "vpackuswb\0" "vpaddb\0"
- "vpaddd\0" "vpaddq\0" "vpaddsb\0" "vpaddsw\0" "vpaddusb\0" "vpaddusw\0" "vpaddw\0" "vpalignr\0" "vpand\0" "vpandd\0"
- "vpandn\0" "vpandnd\0" "vpandnq\0" "vpandq\0" "vpavgb\0" "vpavgw\0" "vpblendd\0" "vpblendmb\0" "vpblendmd\0"
- "vpblendmq\0" "vpblendmw\0" "vpblendvb\0" "vpblendw\0" "vpbroadcastb\0" "vpbroadcastd\0" "vpbroadcastmb2q\0"
- "vpbroadcastmw2d\0" "vpbroadcastq\0" "vpbroadcastw\0" "vpclmulqdq\0" "vpcmov\0" "vpcmpb\0" "vpcmpd\0" "vpcmpeqb\0"
- "vpcmpeqd\0" "vpcmpeqq\0" "vpcmpeqw\0" "vpcmpestri\0" "vpcmpestrm\0" "vpcmpgtb\0" "vpcmpgtd\0" "vpcmpgtq\0"
- "vpcmpgtw\0" "vpcmpistri\0" "vpcmpistrm\0" "vpcmpq\0" "vpcmpub\0" "vpcmpud\0" "vpcmpuq\0" "vpcmpuw\0" "vpcmpw\0"
- "vpcomb\0" "vpcomd\0" "vpcompressb\0" "vpcompressd\0" "vpcompressq\0" "vpcompressw\0" "vpcomq\0" "vpcomub\0"
- "vpcomud\0" "vpcomuq\0" "vpcomuw\0" "vpcomw\0" "vpconflictd\0" "vpconflictq\0" "vpdpbusd\0" "vpdpbusds\0"
- "vpdpwssd\0" "vpdpwssds\0" "vperm2f128\0" "vperm2i128\0" "vpermb\0" "vpermd\0" "vpermi2b\0" "vpermi2d\0"
- "vpermi2pd\0" "vpermi2ps\0" "vpermi2q\0" "vpermi2w\0" "vpermil2pd\0" "vpermil2ps\0" "vpermilpd\0" "vpermilps\0"
- "vpermpd\0" "vpermps\0" "vpermq\0" "vpermt2b\0" "vpermt2d\0" "vpermt2pd\0" "vpermt2ps\0" "vpermt2q\0" "vpermt2w\0"
- "vpermw\0" "vpexpandb\0" "vpexpandd\0" "vpexpandq\0" "vpexpandw\0" "vpextrb\0" "vpextrd\0" "vpextrq\0" "vpextrw\0"
- "vpgatherdd\0" "vpgatherdq\0" "vpgatherqd\0" "vpgatherqq\0" "vphaddbd\0" "vphaddbq\0" "vphaddbw\0" "vphaddd\0"
- "vphadddq\0" "vphaddsw\0" "vphaddubd\0" "vphaddubq\0" "vphaddubw\0" "vphaddudq\0" "vphadduwd\0" "vphadduwq\0"
- "vphaddw\0" "vphaddwd\0" "vphaddwq\0" "vphminposuw\0" "vphsubbw\0" "vphsubd\0" "vphsubdq\0" "vphsubsw\0" "vphsubw\0"
- "vphsubwd\0" "vpinsrb\0" "vpinsrd\0" "vpinsrq\0" "vpinsrw\0" "vplzcntd\0" "vplzcntq\0" "vpmacsdd\0" "vpmacsdqh\0"
- "vpmacsdql\0" "vpmacssdd\0" "vpmacssdqh\0" "vpmacssdql\0" "vpmacsswd\0" "vpmacssww\0" "vpmacswd\0" "vpmacsww\0"
- "vpmadcsswd\0" "vpmadcswd\0" "vpmadd52huq\0" "vpmadd52luq\0" "vpmaddubsw\0" "vpmaddwd\0" "vpmaskmovd\0"
- "vpmaskmovq\0" "vpmaxsb\0" "vpmaxsd\0" "vpmaxsq\0" "vpmaxsw\0" "vpmaxub\0" "vpmaxud\0" "vpmaxuq\0" "vpmaxuw\0"
- "vpminsb\0" "vpminsd\0" "vpminsq\0" "vpminsw\0" "vpminub\0" "vpminud\0" "vpminuq\0" "vpminuw\0" "vpmovb2m\0"
- "vpmovd2m\0" "vpmovdb\0" "vpmovdw\0" "vpmovm2b\0" "vpmovm2d\0" "vpmovm2q\0" "vpmovm2w\0" "vpmovmskb\0" "vpmovq2m\0"
- "vpmovqb\0" "vpmovqd\0" "vpmovqw\0" "vpmovsdb\0" "vpmovsdw\0" "vpmovsqb\0" "vpmovsqd\0" "vpmovsqw\0" "vpmovswb\0"
- "vpmovsxbd\0" "vpmovsxbq\0" "vpmovsxbw\0" "vpmovsxdq\0" "vpmovsxwd\0" "vpmovsxwq\0" "vpmovusdb\0" "vpmovusdw\0"
- "vpmovusqb\0" "vpmovusqd\0" "vpmovusqw\0" "vpmovuswb\0" "vpmovw2m\0" "vpmovwb\0" "vpmovzxbd\0" "vpmovzxbq\0"
- "vpmovzxbw\0" "vpmovzxdq\0" "vpmovzxwd\0" "vpmovzxwq\0" "vpmuldq\0" "vpmulhrsw\0" "vpmulhuw\0" "vpmulhw\0"
- "vpmulld\0" "vpmullq\0" "vpmullw\0" "vpmultishiftqb\0" "vpmuludq\0" "vpopcntb\0" "vpopcntd\0" "vpopcntq\0"
- "vpopcntw\0" "vpor\0" "vpord\0" "vporq\0" "vpperm\0" "vprold\0" "vprolq\0" "vprolvd\0" "vprolvq\0" "vprord\0"
- "vprorq\0" "vprorvd\0" "vprorvq\0" "vprotb\0" "vprotd\0" "vprotq\0" "vprotw\0" "vpsadbw\0" "vpscatterdd\0"
- "vpscatterdq\0" "vpscatterqd\0" "vpscatterqq\0" "vpshab\0" "vpshad\0" "vpshaq\0" "vpshaw\0" "vpshlb\0" "vpshld\0"
- "vpshldd\0" "vpshldq\0" "vpshldvd\0" "vpshldvq\0" "vpshldvw\0" "vpshldw\0" "vpshlq\0" "vpshlw\0" "vpshrdd\0"
- "vpshrdq\0" "vpshrdvd\0" "vpshrdvq\0" "vpshrdvw\0" "vpshrdw\0" "vpshufb\0" "vpshufbitqmb\0" "vpshufd\0" "vpshufhw\0"
- "vpshuflw\0" "vpsignb\0" "vpsignd\0" "vpsignw\0" "vpslld\0" "vpslldq\0" "vpsllq\0" "vpsllvd\0" "vpsllvq\0"
- "vpsllvw\0" "vpsllw\0" "vpsrad\0" "vpsraq\0" "vpsravd\0" "vpsravq\0" "vpsravw\0" "vpsraw\0" "vpsrld\0" "vpsrldq\0"
- "vpsrlq\0" "vpsrlvd\0" "vpsrlvq\0" "vpsrlvw\0" "vpsrlw\0" "vpsubb\0" "vpsubd\0" "vpsubq\0" "vpsubsb\0" "vpsubsw\0"
- "vpsubusb\0" "vpsubusw\0" "vpsubw\0" "vpternlogd\0" "vpternlogq\0" "vptest\0" "vptestmb\0" "vptestmd\0" "vptestmq\0"
- "vptestmw\0" "vptestnmb\0" "vptestnmd\0" "vptestnmq\0" "vptestnmw\0" "vpunpckhbw\0" "vpunpckhdq\0" "vpunpckhqdq\0"
- "vpunpckhwd\0" "vpunpcklbw\0" "vpunpckldq\0" "vpunpcklqdq\0" "vpunpcklwd\0" "vpxor\0" "vpxord\0" "vpxorq\0"
- "vrangepd\0" "vrangeps\0" "vrangesd\0" "vrangess\0" "vrcp14pd\0" "vrcp14ps\0" "vrcp14sd\0" "vrcp14ss\0" "vrcp28pd\0"
- "vrcp28ps\0" "vrcp28sd\0" "vrcp28ss\0" "vrcpph\0" "vrcpps\0" "vrcpsh\0" "vrcpss\0" "vreducepd\0" "vreduceph\0"
- "vreduceps\0" "vreducesd\0" "vreducesh\0" "vreducess\0" "vrndscalepd\0" "vrndscaleph\0" "vrndscaleps\0"
- "vrndscalesd\0" "vrndscalesh\0" "vrndscaless\0" "vroundpd\0" "vroundps\0" "vroundsd\0" "vroundss\0" "vrsqrt14pd\0"
- "vrsqrt14ps\0" "vrsqrt14sd\0" "vrsqrt14ss\0" "vrsqrt28pd\0" "vrsqrt28ps\0" "vrsqrt28sd\0" "vrsqrt28ss\0" "vrsqrtph\0"
- "vrsqrtps\0" "vrsqrtsh\0" "vrsqrtss\0" "vscalefpd\0" "vscalefph\0" "vscalefps\0" "vscalefsd\0" "vscalefsh\0"
- "vscalefss\0" "vscatterdpd\0" "vscatterdps\0" "vscatterpf0dpd\0" "vscatterpf0dps\0" "vscatterpf0qpd\0"
- "vscatterpf0qps\0" "vscatterpf1dpd\0" "vscatterpf1dps\0" "vscatterpf1qpd\0" "vscatterpf1qps\0" "vscatterqpd\0"
- "vscatterqps\0" "vshuff32x4\0" "vshuff64x2\0" "vshufi32x4\0" "vshufi64x2\0" "vshufpd\0" "vshufps\0" "vsqrtpd\0"
- "vsqrtph\0" "vsqrtps\0" "vsqrtsd\0" "vsqrtsh\0" "vsqrtss\0" "vstmxcsr\0" "vsubpd\0" "vsubph\0" "vsubps\0" "vsubsd\0"
- "vsubsh\0" "vsubss\0" "vtestpd\0" "vtestps\0" "vucomisd\0" "vucomish\0" "vucomiss\0" "vunpckhpd\0" "vunpckhps\0"
- "vunpcklpd\0" "vunpcklps\0" "vxorpd\0" "vxorps\0" "vzeroall\0" "vzeroupper\0" "wbinvd\0" "wbnoinvd\0" "wrfsbase\0"
- "wrgsbase\0" "wrmsr\0" "wrssd\0" "wrssq\0" "wrussd\0" "wrussq\0" "xabort\0" "xadd\0" "xbegin\0" "xend\0" "xgetbv\0"
- "xlatb\0" "xresldtrk\0" "xrstors\0" "xrstors64\0" "xsavec\0" "xsavec64\0" "xsaveopt\0" "xsaveopt64\0" "xsaves\0"
- "xsaves64\0" "xsetbv\0" "xsusldtrk\0" "xtest";
+const uint32_t InstDB::_instNameIndexTable[] = {
+ 0x80000000, // Small ''.
+ 0x80000421, // Small 'aaa'.
+ 0x80001021, // Small 'aad'.
+ 0x80003421, // Small 'aam'.
+ 0x80004C21, // Small 'aas'.
+ 0x80000C81, // Small 'adc'.
+ 0x800C0C81, // Small 'adcx'.
+ 0x80001081, // Small 'add'.
+ 0x80481081, // Small 'addpd'.
+ 0x81381081, // Small 'addps'.
+ 0x80499081, // Small 'addsd'.
+ 0x81399081, // Small 'addss'.
+ 0x20876079, // Large 'addsub|pd'.
+ 0x20706079, // Large 'addsub|ps'.
+ 0x800C3C81, // Small 'adox'.
+ 0x86524CA1, // Small 'aesdec'.
+ 0x3028718D, // Large 'aesdecl|ast'.
+ 0x86E2CCA1, // Small 'aesenc'.
+ 0x30287195, // Large 'aesencl|ast'.
+ 0x86D4CCA1, // Small 'aesimc'.
+ 0x0000F012, // Large 'aeskeygenassist'.
+ 0x800011C1, // Small 'and'.
+ 0x800711C1, // Small 'andn'.
+ 0x890711C1, // Small 'andnpd'.
+ 0xA70711C1, // Small 'andnps'.
+ 0x804811C1, // Small 'andpd'.
+ 0x813811C1, // Small 'andps'.
+ 0x80064241, // Small 'arpl'.
+ 0x812A60A2, // Small 'bextr'.
+ 0x261B5630, // Large 'blcfi|ll'.
+ 0x80048D82, // Small 'blci'.
+ 0x80348D82, // Small 'blcic'.
+ 0x97368D82, // Small 'blcmsk'.
+ 0x80098D82, // Small 'blcs'.
+ 0x208753E4, // Large 'blend|pd'.
+ 0x207053E4, // Large 'blend|ps'.
+ 0x33EA53E4, // Large 'blend|vpd'.
+ 0x315053E4, // Large 'blend|vps'.
+ 0x261B5635, // Large 'blsfi|ll'.
+ 0x8004CD82, // Small 'blsi'.
+ 0x8034CD82, // Small 'blsic'.
+ 0x9736CD82, // Small 'blsmsk'.
+ 0x80094D82, // Small 'blsr'.
+ 0x80C191C2, // Small 'bndcl'.
+ 0x80E191C2, // Small 'bndcn'.
+ 0x815191C2, // Small 'bndcu'.
+ 0xB04611C2, // Small 'bndldx'.
+ 0x80B691C2, // Small 'bndmk'.
+ 0xACF691C2, // Small 'bndmov'.
+ 0xB14991C2, // Small 'bndstx'.
+ 0x804755E2, // Small 'bound'.
+ 0x80001A62, // Small 'bsf'.
+ 0x80004A62, // Small 'bsr'.
+ 0x8100DE62, // Small 'bswap'.
+ 0x80000282, // Small 'bt'.
+ 0x80000E82, // Small 'btc'.
+ 0x80004A82, // Small 'btr'.
+ 0x80004E82, // Small 'bts'.
+ 0x8004A342, // Small 'bzhi'.
+ 0x80063023, // Small 'call'.
+ 0x80005C43, // Small 'cbw'.
+ 0x80004483, // Small 'cdq'.
+ 0x8002C483, // Small 'cdqe'.
+ 0x80018583, // Small 'clac'.
+ 0x80000D83, // Small 'clc'.
+ 0x80001183, // Small 'cld'.
+ 0x20BF6503, // Large 'cldemo|te'.
+ 0x0000724D, // Large 'clflush'.
+ 0x1020924D, // Large 'clflushop|t'.
+ 0x80049D83, // Small 'clgi'.
+ 0x80002583, // Small 'cli'.
+ 0x10177509, // Large 'clrssbs|y'.
+ 0x8009D183, // Small 'clts'.
+ 0x8004D583, // Small 'clui'.
+ 0x80015D83, // Small 'clwb'.
+ 0x9F22E983, // Small 'clzero'.
+ 0x80000DA3, // Small 'cmc'.
+ 0x801B3DA3, // Small 'cmova'.
+ 0x8A1B3DA3, // Small 'cmovae'.
+ 0x802B3DA3, // Small 'cmovb'.
+ 0x8A2B3DA3, // Small 'cmovbe'.
+ 0x803B3DA3, // Small 'cmovc'.
+ 0x805B3DA3, // Small 'cmove'.
+ 0x807B3DA3, // Small 'cmovg'.
+ 0x8A7B3DA3, // Small 'cmovge'.
+ 0x80CB3DA3, // Small 'cmovl'.
+ 0x8ACB3DA3, // Small 'cmovle'.
+ 0x82EB3DA3, // Small 'cmovna'.
+ 0x20125516, // Large 'cmovn|ae'.
+ 0x84EB3DA3, // Small 'cmovnb'.
+ 0x100B6516, // Large 'cmovnb|e'.
+ 0x86EB3DA3, // Small 'cmovnc'.
+ 0x8AEB3DA3, // Small 'cmovne'.
+ 0x8EEB3DA3, // Small 'cmovng'.
+ 0x20185516, // Large 'cmovn|ge'.
+ 0x98EB3DA3, // Small 'cmovnl'.
+ 0x217C5516, // Large 'cmovn|le'.
+ 0x9EEB3DA3, // Small 'cmovno'.
+ 0xA0EB3DA3, // Small 'cmovnp'.
+ 0xA6EB3DA3, // Small 'cmovns'.
+ 0xB4EB3DA3, // Small 'cmovnz'.
+ 0x80FB3DA3, // Small 'cmovo'.
+ 0x810B3DA3, // Small 'cmovp'.
+ 0x8B0B3DA3, // Small 'cmovpe'.
+ 0x9F0B3DA3, // Small 'cmovpo'.
+ 0x813B3DA3, // Small 'cmovs'.
+ 0x81AB3DA3, // Small 'cmovz'.
+ 0x800041A3, // Small 'cmp'.
+ 0x804841A3, // Small 'cmppd'.
+ 0x813841A3, // Small 'cmpps'.
+ 0x8009C1A3, // Small 'cmps'.
+ 0x8049C1A3, // Small 'cmpsd'.
+ 0x8139C1A3, // Small 'cmpss'.
+ 0x00007256, // Large 'cmpxchg'.
+ 0x10109256, // Large 'cmpxchg16|b'.
+ 0x23837256, // Large 'cmpxchg|8b'.
+ 0x8934B5E3, // Small 'comisd'.
+ 0xA734B5E3, // Small 'comiss'.
+ 0x8044D603, // Small 'cpuid'.
+ 0x80003E23, // Small 'cqo'.
+ 0x81DF0E43, // Small 'crc32'.
+ 0x208763EE, // Large 'cvtdq2|pd'.
+ 0x207063EE, // Large 'cvtdq2|ps'.
+ 0x20C5627F, // Large 'cvtpd2|dq'.
+ 0x21E2627F, // Large 'cvtpd2|pi'.
+ 0x2070627F, // Large 'cvtpd2|ps'.
+ 0x34875510, // Large 'cvtpi|2pd'.
+ 0x306F5510, // Large 'cvtpi|2ps'.
+ 0x20C56293, // Large 'cvtps2|dq'.
+ 0x10267293, // Large 'cvtps2p|d'.
+ 0x10097293, // Large 'cvtps2p|i'.
+ 0x201D629C, // Large 'cvtsd2|si'.
+ 0x201C629C, // Large 'cvtsd2|ss'.
+ 0x210E63FE, // Large 'cvtsi2|sd'.
+ 0x201C63FE, // Large 'cvtsi2|ss'.
+ 0x210E62AC, // Large 'cvtss2|sd'.
+ 0x201D62AC, // Large 'cvtss2|si'.
+ 0x20C571A6, // Large 'cvttpd2|dq'.
+ 0x21E271A6, // Large 'cvttpd2|pi'.
+ 0x20C571BA, // Large 'cvttps2|dq'.
+ 0x21E271BA, // Large 'cvttps2|pi'.
+ 0x201D71C3, // Large 'cvttsd2|si'.
+ 0x201D71D5, // Large 'cvttss2|si'.
+ 0x800012E3, // Small 'cwd'.
+ 0x800292E3, // Small 'cwde'.
+ 0x80000424, // Small 'daa'.
+ 0x80004C24, // Small 'das'.
+ 0x80000CA4, // Small 'dec'.
+ 0x80005924, // Small 'div'.
+ 0x80485924, // Small 'divpd'.
+ 0x81385924, // Small 'divps'.
+ 0x8049D924, // Small 'divsd'.
+ 0x8139D924, // Small 'divss'.
+ 0x80024204, // Small 'dppd'.
+ 0x8009C204, // Small 'dpps'.
+ 0x8009B5A5, // Small 'emms'.
+ 0x202C563A, // Large 'endbr|32'.
+ 0x2030563A, // Large 'endbr|64'.
+ 0x88D1C5C5, // Small 'enqcmd'.
+ 0x207B563F, // Large 'enqcm|ds'.
+ 0x8122D1C5, // Small 'enter'.
+ 0x207070F0, // Large 'extract|ps'.
+ 0x81195305, // Small 'extrq'.
+ 0x81C6E3A6, // Small 'f2xm1'.
+ 0x80098826, // Small 'fabs'.
+ 0x80021026, // Small 'fadd'.
+ 0x81021026, // Small 'faddp'.
+ 0x80023046, // Small 'fbld'.
+ 0x810A4C46, // Small 'fbstp'.
+ 0x8009A066, // Small 'fchs'.
+ 0x8182B066, // Small 'fclex'.
+ 0x8567B466, // Small 'fcmovb'.
+ 0x26445515, // Large 'fcmov|be'.
+ 0x8B67B466, // Small 'fcmove'.
+ 0x00007515, // Large 'fcmovnb'.
+ 0x100B7515, // Large 'fcmovnb|e'.
+ 0x100B6515, // Large 'fcmovn|e'.
+ 0x107D6515, // Large 'fcmovn|u'.
+ 0xAB67B466, // Small 'fcmovu'.
+ 0x8006BC66, // Small 'fcom'.
+ 0x8096BC66, // Small 'fcomi'.
+ 0xA096BC66, // Small 'fcomip'.
+ 0x8106BC66, // Small 'fcomp'.
+ 0xA106BC66, // Small 'fcompp'.
+ 0x8009BC66, // Small 'fcos'.
+ 0x21A95646, // Large 'fdecs|tp'.
+ 0x800B2486, // Small 'fdiv'.
+ 0x810B2486, // Small 'fdivp'.
+ 0x812B2486, // Small 'fdivr'.
+ 0xA12B2486, // Small 'fdivrp'.
+ 0x8136B4A6, // Small 'femms'.
+ 0x8052C8C6, // Small 'ffree'.
+ 0x80420526, // Small 'fiadd'.
+ 0x80D78D26, // Small 'ficom'.
+ 0xA0D78D26, // Small 'ficomp'.
+ 0x81649126, // Small 'fidiv'.
+ 0xA5649126, // Small 'fidivr'.
+ 0x80023126, // Small 'fild'.
+ 0x80CAB526, // Small 'fimul'.
+ 0x21A9564B, // Large 'fincs|tp'.
+ 0x8144B926, // Small 'finit'.
+ 0x800A4D26, // Small 'fist'.
+ 0x810A4D26, // Small 'fistp'.
+ 0xA14A4D26, // Small 'fisttp'.
+ 0x802ACD26, // Small 'fisub'.
+ 0xA42ACD26, // Small 'fisubr'.
+ 0x80001186, // Small 'fld'.
+ 0x800E1186, // Small 'fld1'.
+ 0x81719186, // Small 'fldcw'.
+ 0xACE29186, // Small 'fldenv'.
+ 0x8BD61186, // Small 'fldl2e'.
+ 0xA9D61186, // Small 'fldl2t'.
+ 0xBA761186, // Small 'fldlg2'.
+ 0xBAE61186, // Small 'fldln2'.
+ 0x80981186, // Small 'fldpi'.
+ 0x800D1186, // Small 'fldz'.
+ 0x800655A6, // Small 'fmul'.
+ 0x810655A6, // Small 'fmulp'.
+ 0xB0560DC6, // Small 'fnclex'.
+ 0xA89725C6, // Small 'fninit'.
+ 0x80083DC6, // Small 'fnop'.
+ 0x8B60CDC6, // Small 'fnsave'.
+ 0xAE3A4DC6, // Small 'fnstcw'.
+ 0x200D5650, // Large 'fnste|nv'.
+ 0xAF3A4DC6, // Small 'fnstsw'.
+ 0x9C1A0606, // Small 'fpatan'.
+ 0x80D2CA06, // Small 'fprem'.
+ 0xB8D2CA06, // Small 'fprem1'.
+ 0x80E0D206, // Small 'fptan'.
+ 0x31054655, // Large 'frnd|int'.
+ 0xA4FA4E46, // Small 'frstor'.
+ 0x805B0666, // Small 'fsave'.
+ 0x8AC08E66, // Small 'fscale'.
+ 0x80072666, // Small 'fsin'.
+ 0x221D5659, // Large 'fsinc|os'.
+ 0x81494666, // Small 'fsqrt'.
+ 0x80005266, // Small 'fst'.
+ 0x8171D266, // Small 'fstcw'.
+ 0xACE2D266, // Small 'fstenv'.
+ 0x80085266, // Small 'fstp'.
+ 0x8179D266, // Small 'fstsw'.
+ 0x80015666, // Small 'fsub'.
+ 0x81015666, // Small 'fsubp'.
+ 0x81215666, // Small 'fsubr'.
+ 0xA1215666, // Small 'fsubrp'.
+ 0x800A4E86, // Small 'ftst'.
+ 0x80D78EA6, // Small 'fucom'.
+ 0x92D78EA6, // Small 'fucomi'.
+ 0x2543565E, // Large 'fucom|ip'.
+ 0xA0D78EA6, // Small 'fucomp'.
+ 0x2663565E, // Large 'fucom|pp'.
+ 0x814486E6, // Small 'fwait'.
+ 0x80068706, // Small 'fxam'.
+ 0x80040F06, // Small 'fxch'.
+ 0x00007385, // Large 'fxrstor'.
+ 0x20307385, // Large 'fxrstor|64'.
+ 0x8B60CF06, // Small 'fxsave'.
+ 0x2030651C, // Large 'fxsave|64'.
+ 0x50F22385, // Large 'fx|tract'.
+ 0x818EB326, // Small 'fyl2x'.
+ 0x26025665, // Large 'fyl2x|p1'.
+ 0x8659D0A7, // Small 'getsec'.
+ 0x1010F001, // Large 'gf2p8affineinvq|b'.
+ 0x200FB001, // Large 'gf2p8affine|qb'.
+ 0x42E25001, // Large 'gf2p8|mulb'.
+ 0x89021028, // Small 'haddpd'.
+ 0xA7021028, // Small 'haddps'.
+ 0x80005188, // Small 'hlt'.
+ 0xA8599648, // Small 'hreset'.
+ 0x89015668, // Small 'hsubpd'.
+ 0xA7015668, // Small 'hsubps'.
+ 0x800B2489, // Small 'idiv'.
+ 0x800655A9, // Small 'imul'.
+ 0x800001C9, // Small 'in'.
+ 0x80000DC9, // Small 'inc'.
+ 0x2087566A, // Large 'incss|pd'.
+ 0x266F566A, // Large 'incss|pq'.
+ 0x80004DC9, // Small 'ins'.
+ 0x20706149, // Large 'insert|ps'.
+ 0x100F6149, // Large 'insert|q'.
+ 0x800051C9, // Small 'int'.
+ 0x800F51C9, // Small 'int3'.
+ 0x8007D1C9, // Small 'into'.
+ 0x800259C9, // Small 'invd'.
+ 0xA902D9C9, // Small 'invept'.
+ 0x8F0659C9, // Small 'invlpg'.
+ 0x33164671, // Large 'invl|pga'.
+ 0x23A05675, // Large 'invpc|id'.
+ 0x23A0567A, // Large 'invvp|id'.
+ 0x800A1649, // Small 'iret'.
+ 0x804A1649, // Small 'iretd'.
+ 0x811A1649, // Small 'iretq'.
+ 0x8000002A, // Small 'ja'.
+ 0x8000142A, // Small 'jae'.
+ 0x8000004A, // Small 'jb'.
+ 0x8000144A, // Small 'jbe'.
+ 0x8000006A, // Small 'jc'.
+ 0x800000AA, // Small 'je'.
+ 0x81AC0CAA, // Small 'jecxz'.
+ 0x800000EA, // Small 'jg'.
+ 0x800014EA, // Small 'jge'.
+ 0x8000018A, // Small 'jl'.
+ 0x8000158A, // Small 'jle'.
+ 0x800041AA, // Small 'jmp'.
+ 0x800005CA, // Small 'jna'.
+ 0x800285CA, // Small 'jnae'.
+ 0x800009CA, // Small 'jnb'.
+ 0x800289CA, // Small 'jnbe'.
+ 0x80000DCA, // Small 'jnc'.
+ 0x800015CA, // Small 'jne'.
+ 0x80001DCA, // Small 'jng'.
+ 0x80029DCA, // Small 'jnge'.
+ 0x800031CA, // Small 'jnl'.
+ 0x8002B1CA, // Small 'jnle'.
+ 0x80003DCA, // Small 'jno'.
+ 0x800041CA, // Small 'jnp'.
+ 0x80004DCA, // Small 'jns'.
+ 0x800069CA, // Small 'jnz'.
+ 0x800001EA, // Small 'jo'.
+ 0x8000020A, // Small 'jp'.
+ 0x8000160A, // Small 'jpe'.
+ 0x80003E0A, // Small 'jpo'.
+ 0x8000026A, // Small 'js'.
+ 0x8000034A, // Small 'jz'.
+ 0x8022102B, // Small 'kaddb'.
+ 0x8042102B, // Small 'kaddd'.
+ 0x8112102B, // Small 'kaddq'.
+ 0x8172102B, // Small 'kaddw'.
+ 0x8022382B, // Small 'kandb'.
+ 0x8042382B, // Small 'kandd'.
+ 0x84E2382B, // Small 'kandnb'.
+ 0x88E2382B, // Small 'kandnd'.
+ 0xA2E2382B, // Small 'kandnq'.
+ 0xAEE2382B, // Small 'kandnw'.
+ 0x8112382B, // Small 'kandq'.
+ 0x8172382B, // Small 'kandw'.
+ 0x802B3DAB, // Small 'kmovb'.
+ 0x804B3DAB, // Small 'kmovd'.
+ 0x811B3DAB, // Small 'kmovq'.
+ 0x817B3DAB, // Small 'kmovw'.
+ 0x802A3DCB, // Small 'knotb'.
+ 0x804A3DCB, // Small 'knotd'.
+ 0x811A3DCB, // Small 'knotq'.
+ 0x817A3DCB, // Small 'knotw'.
+ 0x800149EB, // Small 'korb'.
+ 0x800249EB, // Small 'kord'.
+ 0x8008C9EB, // Small 'korq'.
+ 0x10107522, // Large 'kortest|b'.
+ 0x10267522, // Large 'kortest|d'.
+ 0x100F7522, // Large 'kortest|q'.
+ 0x105F7522, // Large 'kortest|w'.
+ 0x800BC9EB, // Small 'korw'.
+ 0x22E46529, // Large 'kshift|lb'.
+ 0x234B6529, // Large 'kshift|ld'.
+ 0x22406529, // Large 'kshift|lq'.
+ 0x234E6529, // Large 'kshift|lw'.
+ 0x252F6529, // Large 'kshift|rb'.
+ 0x10267529, // Large 'kshiftr|d'.
+ 0x100F7529, // Large 'kshiftr|q'.
+ 0x105F7529, // Large 'kshiftr|w'.
+ 0x8549968B, // Small 'ktestb'.
+ 0x8949968B, // Small 'ktestd'.
+ 0xA349968B, // Small 'ktestq'.
+ 0xAF49968B, // Small 'ktestw'.
+ 0x23446531, // Large 'kunpck|bw'.
+ 0x20C56531, // Large 'kunpck|dq'.
+ 0x23466531, // Large 'kunpck|wd'.
+ 0x8527BB0B, // Small 'kxnorb'.
+ 0x8927BB0B, // Small 'kxnord'.
+ 0xA327BB0B, // Small 'kxnorq'.
+ 0xAF27BB0B, // Small 'kxnorw'.
+ 0x80293F0B, // Small 'kxorb'.
+ 0x80493F0B, // Small 'kxord'.
+ 0x81193F0B, // Small 'kxorq'.
+ 0x81793F0B, // Small 'kxorw'.
+ 0x8003202C, // Small 'lahf'.
+ 0x8000482C, // Small 'lar'.
+ 0x80C6046C, // Small 'lcall'.
+ 0x8158908C, // Small 'lddqu'.
+ 0x1023657B, // Large 'ldmxcs|r'.
+ 0x80004C8C, // Small 'lds'.
+ 0x1001838C, // Large 'ldtilecf|g'.
+ 0x800004AC, // Small 'lea'.
+ 0x805B04AC, // Small 'leave'.
+ 0x80004CAC, // Small 'les'.
+ 0x8A3714CC, // Small 'lfence'.
+ 0x80004CCC, // Small 'lfs'.
+ 0x800A10EC, // Small 'lgdt'.
+ 0x80004CEC, // Small 'lgs'.
+ 0x800A112C, // Small 'lidt'.
+ 0x8008354C, // Small 'ljmp'.
+ 0x800A118C, // Small 'lldt'.
+ 0x84385D8C, // Small 'llwpcb'.
+ 0x800BCDAC, // Small 'lmsw'.
+ 0x800991EC, // Small 'lods'.
+ 0x80083DEC, // Small 'loop'.
+ 0x80583DEC, // Small 'loope'.
+ 0x8AE83DEC, // Small 'loopne'.
+ 0x8000326C, // Small 'lsl'.
+ 0x80004E6C, // Small 'lss'.
+ 0x80004A8C, // Small 'ltr'.
+ 0xA6E4C2EC, // Small 'lwpins'.
+ 0x981B42EC, // Small 'lwpval'.
+ 0x81470F4C, // Small 'lzcnt'.
+ 0x107D91F9, // Large 'maskmovdq|u'.
+ 0x100F71F9, // Large 'maskmov|q'.
+ 0x8048602D, // Small 'maxpd'.
+ 0x8138602D, // Small 'maxps'.
+ 0x8049E02D, // Small 'maxsd'.
+ 0x8139E02D, // Small 'maxss'.
+ 0x2157567F, // Large 'mcomm|it'.
+ 0x8A3714CD, // Small 'mfence'.
+ 0x8048392D, // Small 'minpd'.
+ 0x8138392D, // Small 'minps'.
+ 0x8049B92D, // Small 'minsd'.
+ 0x8139B92D, // Small 'minss'.
+ 0x00007537, // Large 'monitor'.
+ 0x102E7537, // Large 'monitor|x'.
+ 0x800059ED, // Small 'mov'.
+ 0xA620D9ED, // Small 'movabs'.
+ 0x8900D9ED, // Small 'movapd'.
+ 0xA700D9ED, // Small 'movaps'.
+ 0x805159ED, // Small 'movbe'.
+ 0x800259ED, // Small 'movd'.
+ 0x358741FD, // Large 'movd|dup'.
+ 0x10108394, // Large 'movdir64|b'.
+ 0x10096394, // Large 'movdir|i'.
+ 0x25BD51FD, // Large 'movdq|2q'.
+ 0x831259ED, // Small 'movdqa'.
+ 0xAB1259ED, // Small 'movdqu'.
+ 0x34ED458D, // Large 'movh|lps'.
+ 0x890459ED, // Small 'movhpd'.
+ 0xA70459ED, // Small 'movhps'.
+ 0x20705592, // Large 'movlh|ps'.
+ 0x890659ED, // Small 'movlpd'.
+ 0xA70659ED, // Small 'movlps'.
+ 0x20876443, // Large 'movmsk|pd'.
+ 0x20706443, // Large 'movmsk|ps'.
+ 0x20C5544A, // Large 'movnt|dq'.
+ 0x3436544A, // Large 'movnt|dqa'.
+ 0x934759ED, // Small 'movnti'.
+ 0x2087544A, // Large 'movnt|pd'.
+ 0x2070544A, // Large 'movnt|ps'.
+ 0xA34759ED, // Small 'movntq'.
+ 0x210E544A, // Large 'movnt|sd'.
+ 0x201C544A, // Large 'movnt|ss'.
+ 0x8008D9ED, // Small 'movq'.
+ 0x20C55684, // Large 'movq2|dq'.
+ 0x8009D9ED, // Small 'movs'.
+ 0x8049D9ED, // Small 'movsd'.
+ 0x21E16450, // Large 'movshd|up'.
+ 0x21E16457, // Large 'movsld|up'.
+ 0x8139D9ED, // Small 'movss'.
+ 0x8189D9ED, // Small 'movsx'.
+ 0x8989D9ED, // Small 'movsxd'.
+ 0x890AD9ED, // Small 'movupd'.
+ 0xA70AD9ED, // Small 'movups'.
+ 0x818D59ED, // Small 'movzx'.
+ 0x23445598, // Large 'mpsad|bw'.
+ 0x800032AD, // Small 'mul'.
+ 0x804832AD, // Small 'mulpd'.
+ 0x813832AD, // Small 'mulps'.
+ 0x8049B2AD, // Small 'mulsd'.
+ 0x8139B2AD, // Small 'mulss'.
+ 0x800C32AD, // Small 'mulx'.
+ 0x814486ED, // Small 'mwait'.
+ 0xB14486ED, // Small 'mwaitx'.
+ 0x80001CAE, // Small 'neg'.
+ 0x800041EE, // Small 'nop'.
+ 0x800051EE, // Small 'not'.
+ 0x8000024F, // Small 'or'.
+ 0x8002424F, // Small 'orpd'.
+ 0x8009C24F, // Small 'orps'.
+ 0x800052AF, // Small 'out'.
+ 0x8009D2AF, // Small 'outs'.
+ 0x80298830, // Small 'pabsb'.
+ 0x80498830, // Small 'pabsd'.
+ 0x81798830, // Small 'pabsw'.
+ 0x0000845E, // Large 'packssdw'.
+ 0x2465645E, // Large 'packss|wb'.
+ 0x24646468, // Large 'packus|dw'.
+ 0x00008468, // Large 'packuswb'.
+ 0x80221030, // Small 'paddb'.
+ 0x80421030, // Small 'paddd'.
+ 0x81121030, // Small 'paddq'.
+ 0x85321030, // Small 'paddsb'.
+ 0xAF321030, // Small 'paddsw'.
+ 0x250D55A5, // Large 'paddu|sb'.
+ 0x232D55A5, // Large 'paddu|sw'.
+ 0x81721030, // Small 'paddw'.
+ 0x102365AB, // Large 'palign|r'.
+ 0x80023830, // Small 'pand'.
+ 0x80E23830, // Small 'pandn'.
+ 0x8059D430, // Small 'pause'.
+ 0x8023D830, // Small 'pavgb'.
+ 0x250D5689, // Large 'pavgu|sb'.
+ 0x8173D830, // Small 'pavgw'.
+ 0x20216471, // Large 'pblend|vb'.
+ 0x105F6471, // Large 'pblend|w'.
+ 0x424052EF, // Large 'pclmu|lqdq'.
+ 0x200F52F5, // Large 'pcmpe|qb'.
+ 0x223552F5, // Large 'pcmpe|qd'.
+ 0x21AE52F5, // Large 'pcmpe|qq'.
+ 0x24BB52F5, // Large 'pcmpe|qw'.
+ 0x100982F5, // Large 'pcmpestr|i'.
+ 0x105C82F5, // Large 'pcmpestr|m'.
+ 0x35B14255, // Large 'pcmp|gtb'.
+ 0x35B44255, // Large 'pcmp|gtd'.
+ 0x35B74255, // Large 'pcmp|gtq'.
+ 0x35BA4255, // Large 'pcmp|gtw'.
+ 0x100982FE, // Large 'pcmpistr|i'.
+ 0x105C82FE, // Large 'pcmpistr|m'.
+ 0x25AE520D, // Large 'pconf|ig'.
+ 0x80081490, // Small 'pdep'.
+ 0x800A60B0, // Small 'pext'.
+ 0x852A60B0, // Small 'pextrb'.
+ 0x892A60B0, // Small 'pextrd'.
+ 0xA32A60B0, // Small 'pextrq'.
+ 0xAF2A60B0, // Small 'pextrw'.
+ 0x8044F4D0, // Small 'pf2id'.
+ 0x8174F4D0, // Small 'pf2iw'.
+ 0x803184D0, // Small 'pfacc'.
+ 0x804204D0, // Small 'pfadd'.
+ 0x100F668E, // Large 'pfcmpe|q'.
+ 0x2018568E, // Large 'pfcmp|ge'.
+ 0x25B1568E, // Large 'pfcmp|gt'.
+ 0x8180B4D0, // Small 'pfmax'.
+ 0x80E4B4D0, // Small 'pfmin'.
+ 0x80CAB4D0, // Small 'pfmul'.
+ 0x8630B8D0, // Small 'pfnacc'.
+ 0x24245694, // Large 'pfpna|cc'.
+ 0x8101C8D0, // Small 'pfrcp'.
+ 0x2165653E, // Large 'pfrcpi|t1'.
+ 0x2261653E, // Large 'pfrcpi|t2'.
+ 0xAD01C8D0, // Small 'pfrcpv'.
+ 0x21656544, // Large 'pfrsqi|t1'.
+ 0x214D5544, // Large 'pfrsq|rt'.
+ 0x354A5544, // Large 'pfrsq|rtv'.
+ 0x802ACCD0, // Small 'pfsub'.
+ 0xA42ACCD0, // Small 'pfsubr'.
+ 0x88420510, // Small 'phaddd'.
+ 0x232D5498, // Large 'phadd|sw'.
+ 0xAE420510, // Small 'phaddw'.
+ 0x105F9217, // Large 'phminposu|w'.
+ 0x882ACD10, // Small 'phsubd'.
+ 0x232D55C4, // Large 'phsub|sw'.
+ 0xAE2ACD10, // Small 'phsubw'.
+ 0x80437530, // Small 'pi2fd'.
+ 0x81737530, // Small 'pi2fw'.
+ 0x8529B930, // Small 'pinsrb'.
+ 0x8929B930, // Small 'pinsrd'.
+ 0xA329B930, // Small 'pinsrq'.
+ 0xAF29B930, // Small 'pinsrw'.
+ 0x432F5221, // Large 'pmadd|ubsw'.
+ 0x23465221, // Large 'pmadd|wd'.
+ 0x853C05B0, // Small 'pmaxsb'.
+ 0x893C05B0, // Small 'pmaxsd'.
+ 0xAF3C05B0, // Small 'pmaxsw'.
+ 0x855C05B0, // Small 'pmaxub'.
+ 0x895C05B0, // Small 'pmaxud'.
+ 0xAF5C05B0, // Small 'pmaxuw'.
+ 0x853725B0, // Small 'pminsb'.
+ 0x893725B0, // Small 'pminsd'.
+ 0xAF3725B0, // Small 'pminsw'.
+ 0x855725B0, // Small 'pminub'.
+ 0x895725B0, // Small 'pminud'.
+ 0xAF5725B0, // Small 'pminuw'.
+ 0x101074A5, // Large 'pmovmsk|b'.
+ 0x102674AD, // Large 'pmovsxb|d'.
+ 0x100F74AD, // Large 'pmovsxb|q'.
+ 0x105F74AD, // Large 'pmovsxb|w'.
+ 0x20C564AD, // Large 'pmovsx|dq'.
+ 0x234664AD, // Large 'pmovsx|wd'.
+ 0x249F64AD, // Large 'pmovsx|wq'.
+ 0x102674BE, // Large 'pmovzxb|d'.
+ 0x100F74BE, // Large 'pmovzxb|q'.
+ 0x105F74BE, // Large 'pmovzxb|w'.
+ 0x20C564BE, // Large 'pmovzx|dq'.
+ 0x234664BE, // Large 'pmovzx|wd'.
+ 0x249F64BE, // Large 'pmovzx|wq'.
+ 0xA24655B0, // Small 'pmuldq'.
+ 0x232D64C6, // Large 'pmulhr|sw'.
+ 0x105F64C6, // Large 'pmulhr|w'.
+ 0x23F454C6, // Large 'pmulh|uw'.
+ 0xAE8655B0, // Small 'pmulhw'.
+ 0x88C655B0, // Small 'pmulld'.
+ 0xAEC655B0, // Small 'pmullw'.
+ 0x328F40AF, // Large 'pmul|udq'.
+ 0x800041F0, // Small 'pop'.
+ 0x8000C1F0, // Small 'popa'.
+ 0x8040C1F0, // Small 'popad'.
+ 0xA8E1C1F0, // Small 'popcnt'.
+ 0x800341F0, // Small 'popf'.
+ 0x804341F0, // Small 'popfd'.
+ 0x811341F0, // Small 'popfq'.
+ 0x800049F0, // Small 'por'.
+ 0x0000815A, // Large 'prefetch'.
+ 0x1006A15A, // Large 'prefetchnt|a'.
+ 0x225F815A, // Large 'prefetch|t0'.
+ 0x2165815A, // Large 'prefetch|t1'.
+ 0x2261815A, // Large 'prefetch|t2'.
+ 0x105F815A, // Large 'prefetch|w'.
+ 0x3164815A, // Large 'prefetch|wt1'.
+ 0xAE220670, // Small 'psadbw'.
+ 0x846AA270, // Small 'pshufb'.
+ 0x886AA270, // Small 'pshufd'.
+ 0x25F15151, // Large 'pshuf|hw'.
+ 0x234E5151, // Large 'pshuf|lw'.
+ 0xAE6AA270, // Small 'pshufw'.
+ 0x84E3A670, // Small 'psignb'.
+ 0x88E3A670, // Small 'psignd'.
+ 0xAEE3A670, // Small 'psignw'.
+ 0x80463270, // Small 'pslld'.
+ 0xA2463270, // Small 'pslldq'.
+ 0x81163270, // Small 'psllq'.
+ 0x81763270, // Small 'psllw'.
+ 0x9130B670, // Small 'psmash'.
+ 0x8040CA70, // Small 'psrad'.
+ 0x8170CA70, // Small 'psraw'.
+ 0x80464A70, // Small 'psrld'.
+ 0xA2464A70, // Small 'psrldq'.
+ 0x81164A70, // Small 'psrlq'.
+ 0x81764A70, // Small 'psrlw'.
+ 0x80215670, // Small 'psubb'.
+ 0x80415670, // Small 'psubd'.
+ 0x81115670, // Small 'psubq'.
+ 0x85315670, // Small 'psubsb'.
+ 0xAF315670, // Small 'psubsw'.
+ 0x250D55F4, // Large 'psubu|sb'.
+ 0x232D55F4, // Large 'psubu|sw'.
+ 0x81715670, // Small 'psubw'.
+ 0x8900DE70, // Small 'pswapd'.
+ 0x81499690, // Small 'ptest'.
+ 0x20BF5699, // Large 'ptwri|te'.
+ 0x23447238, // Large 'punpckh|bw'.
+ 0x20C57238, // Large 'punpckh|dq'.
+ 0x20C58238, // Large 'punpckhq|dq'.
+ 0x23467238, // Large 'punpckh|wd'.
+ 0x33486238, // Large 'punpck|lbw'.
+ 0x334B6238, // Large 'punpck|ldq'.
+ 0x42406238, // Large 'punpck|lqdq'.
+ 0x334E6238, // Large 'punpck|lwd'.
+ 0x80044EB0, // Small 'push'.
+ 0x80144EB0, // Small 'pusha'.
+ 0x88144EB0, // Small 'pushad'.
+ 0x80644EB0, // Small 'pushf'.
+ 0x88644EB0, // Small 'pushfd'.
+ 0xA2644EB0, // Small 'pushfq'.
+ 0x20BF739C, // Large 'pvalida|te'.
+ 0x80093F10, // Small 'pxor'.
+ 0x80003072, // Small 'rcl'.
+ 0x81384072, // Small 'rcpps'.
+ 0x8139C072, // Small 'rcpss'.
+ 0x80004872, // Small 'rcr'.
+ 0x33B0554D, // Large 'rdfsb|ase'.
+ 0x33B05552, // Large 'rdgsb|ase'.
+ 0x8129B492, // Small 'rdmsr'.
+ 0x8044C092, // Small 'rdpid'.
+ 0xAB25C092, // Small 'rdpkru'.
+ 0x8036C092, // Small 'rdpmc'.
+ 0x81594092, // Small 'rdpru'.
+ 0x88E0C892, // Small 'rdrand'.
+ 0x8852CC92, // Small 'rdseed'.
+ 0x8909CC92, // Small 'rdsspd'.
+ 0xA309CC92, // Small 'rdsspq'.
+ 0x8039D092, // Small 'rdtsc'.
+ 0xA039D092, // Small 'rdtscp'.
+ 0x800050B2, // Small 'ret'.
+ 0x800350B2, // Small 'retf'.
+ 0x201F73A3, // Large 'rmpadju|st'.
+ 0x20BF73AA, // Large 'rmpupda|te'.
+ 0x800031F2, // Small 'rol'.
+ 0x800049F2, // Small 'ror'.
+ 0x800C49F2, // Small 'rorx'.
+ 0x20875606, // Large 'round|pd'.
+ 0x20705606, // Large 'round|ps'.
+ 0x00007606, // Large 'roundsd'.
+ 0x10146606, // Large 'rounds|s'.
+ 0x80003672, // Small 'rsm'.
+ 0x20705352, // Large 'rsqrt|ps'.
+ 0x201C5352, // Large 'rsqrt|ss'.
+ 0x35575387, // Large 'rstor|ssp'.
+ 0x80032033, // Small 'sahf'.
+ 0x80003033, // Small 'sal'.
+ 0x80004833, // Small 'sar'.
+ 0x800C4833, // Small 'sarx'.
+ 0x1004A167, // Large 'saveprevss|p'.
+ 0x80000853, // Small 'sbb'.
+ 0x80098473, // Small 'scas'.
+ 0x21E2655A, // Large 'sendui|pi'.
+ 0x237573B1, // Large 'seriali|ze'.
+ 0x8000D0B3, // Small 'seta'.
+ 0x8050D0B3, // Small 'setae'.
+ 0x800150B3, // Small 'setb'.
+ 0x805150B3, // Small 'setbe'.
+ 0x8001D0B3, // Small 'setc'.
+ 0x8002D0B3, // Small 'sete'.
+ 0x8003D0B3, // Small 'setg'.
+ 0x8053D0B3, // Small 'setge'.
+ 0x800650B3, // Small 'setl'.
+ 0x805650B3, // Small 'setle'.
+ 0x801750B3, // Small 'setna'.
+ 0x8A1750B3, // Small 'setnae'.
+ 0x802750B3, // Small 'setnb'.
+ 0x8A2750B3, // Small 'setnbe'.
+ 0x803750B3, // Small 'setnc'.
+ 0x805750B3, // Small 'setne'.
+ 0x807750B3, // Small 'setng'.
+ 0x8A7750B3, // Small 'setnge'.
+ 0x80C750B3, // Small 'setnl'.
+ 0x8AC750B3, // Small 'setnle'.
+ 0x80F750B3, // Small 'setno'.
+ 0x810750B3, // Small 'setnp'.
+ 0x813750B3, // Small 'setns'.
+ 0x81A750B3, // Small 'setnz'.
+ 0x8007D0B3, // Small 'seto'.
+ 0x800850B3, // Small 'setp'.
+ 0x805850B3, // Small 'setpe'.
+ 0x80F850B3, // Small 'setpo'.
+ 0x8009D0B3, // Small 'sets'.
+ 0x10177560, // Large 'setssbs|y'.
+ 0x800D50B3, // Small 'setz'.
+ 0x8A3714D3, // Small 'sfence'.
+ 0x800A10F3, // Small 'sgdt'.
+ 0x426343B8, // Large 'sha1|msg1'.
+ 0x426743B8, // Large 'sha1|msg2'.
+ 0x20BF73B8, // Large 'sha1nex|te'.
+ 0x102F83BF, // Large 'sha1rnds|4'.
+ 0x42636171, // Large 'sha256|msg1'.
+ 0x42676171, // Large 'sha256|msg2'.
+ 0x20719171, // Large 'sha256rnd|s2'.
+ 0x80003113, // Small 'shl'.
+ 0x80023113, // Small 'shld'.
+ 0x800C3113, // Small 'shlx'.
+ 0x80004913, // Small 'shr'.
+ 0x80024913, // Small 'shrd'.
+ 0x800C4913, // Small 'shrx'.
+ 0x89035513, // Small 'shufpd'.
+ 0xA7035513, // Small 'shufps'.
+ 0x800A1133, // Small 'sidt'.
+ 0xA8972573, // Small 'skinit'.
+ 0x800A1193, // Small 'sldt'.
+ 0x84385D93, // Small 'slwpcb'.
+ 0x800BCDB3, // Small 'smsw'.
+ 0x890A4A33, // Small 'sqrtpd'.
+ 0xA70A4A33, // Small 'sqrtps'.
+ 0x893A4A33, // Small 'sqrtsd'.
+ 0xA73A4A33, // Small 'sqrtss'.
+ 0x80018693, // Small 'stac'.
+ 0x80000E93, // Small 'stc'.
+ 0x80001293, // Small 'std'.
+ 0x80049E93, // Small 'stgi'.
+ 0x80002693, // Small 'sti'.
+ 0x1023660E, // Large 'stmxcs|r'.
+ 0x8009BE93, // Small 'stos'.
+ 0x80004A93, // Small 'str'.
+ 0x100183C7, // Large 'sttilecf|g'.
+ 0x8004D693, // Small 'stui'.
+ 0x80000AB3, // Small 'sub'.
+ 0x80480AB3, // Small 'subpd'.
+ 0x81380AB3, // Small 'subps'.
+ 0x80498AB3, // Small 'subsd'.
+ 0x81398AB3, // Small 'subss'.
+ 0xA67806F3, // Small 'swapgs'.
+ 0x361A469E, // Large 'sysc|all'.
+ 0x41064567, // Large 'syse|nter'.
+ 0x2157556B, // Large 'sysex|it'.
+ 0x3157556B, // Large 'sysex|itq'.
+ 0xA8594F33, // Small 'sysret'.
+ 0x215856A2, // Large 'sysre|tq'.
+ 0x86B9B794, // Small 't1mskc'.
+ 0x207073CF, // Large 'tdpbf16|ps'.
+ 0x332243CF, // Large 'tdpb|ssd'.
+ 0x328E43CF, // Large 'tdpb|sud'.
+ 0x210E56A7, // Large 'tdpbu|sd'.
+ 0x228F56A7, // Large 'tdpbu|ud'.
+ 0x800A4CB4, // Small 'test'.
+ 0x935A4CB4, // Small 'testui'.
+ 0x0000917A, // Large 'tileloadd'.
+ 0x2165917A, // Large 'tileloadd|t1'.
+ 0x210A9183, // Large 'tilerelea|se'.
+ 0x1026926B, // Large 'tilestore|d'.
+ 0x4375417A, // Large 'tile|zero'.
+ 0x8B3A8614, // Small 'tpause'.
+ 0x81470F54, // Small 'tzcnt'.
+ 0x80B9B754, // Small 'tzmsk'.
+ 0x210E5615, // Large 'ucomi|sd'.
+ 0x201C5615, // Large 'ucomi|ss'.
+ 0x80006C95, // Small 'ud0'.
+ 0x80007095, // Small 'ud1'.
+ 0x80007495, // Small 'ud2'.
+ 0x8142C935, // Small 'uiret'.
+ 0x7537107D, // Large 'u|monitor'.
+ 0xA890DDB5, // Small 'umwait'.
+ 0x20876239, // Large 'unpckh|pd'.
+ 0x20706239, // Large 'unpckh|ps'.
+ 0x34EA5239, // Large 'unpck|lpd'.
+ 0x34ED5239, // Large 'unpck|lps'.
+ 0x30D163D6, // Large 'v4fmad|dps'.
+ 0x327B63D6, // Large 'v4fmad|dss'.
+ 0x30D17274, // Large 'v4fnmad|dps'.
+ 0x327B7274, // Large 'v4fnmad|dss'.
+ 0x89021036, // Small 'vaddpd'.
+ 0x91021036, // Small 'vaddph'.
+ 0xA7021036, // Small 'vaddps'.
+ 0x89321036, // Small 'vaddsd'.
+ 0x91321036, // Small 'vaddsh'.
+ 0xA7321036, // Small 'vaddss'.
+ 0x208773DC, // Large 'vaddsub|pd'.
+ 0x207073DC, // Large 'vaddsub|ps'.
+ 0x0000718C, // Large 'vaesdec'.
+ 0x3028818C, // Large 'vaesdecl|ast'.
+ 0x00007194, // Large 'vaesenc'.
+ 0x30288194, // Large 'vaesencl|ast'.
+ 0x267F56AC, // Large 'vaesi|mc'.
+ 0x1020F011, // Large 'vaeskeygenassis|t'.
+ 0x217856B1, // Large 'valig|nd'.
+ 0x264056B1, // Large 'valig|nq'.
+ 0x208756B6, // Large 'vandn|pd'.
+ 0x207056B6, // Large 'vandn|ps'.
+ 0x89023836, // Small 'vandpd'.
+ 0xA7023836, // Small 'vandps'.
+ 0x208773E3, // Large 'vblendm|pd'.
+ 0x207073E3, // Large 'vblendm|ps'.
+ 0x208763E3, // Large 'vblend|pd'.
+ 0x207063E3, // Large 'vblend|ps'.
+ 0x33EA63E3, // Large 'vblend|vpd'.
+ 0x315063E3, // Large 'vblend|vps'.
+ 0x3062B021, // Large 'vbroadcastf|128'.
+ 0x1003E021, // Large 'vbroadcastf32x|2'.
+ 0x102FE021, // Large 'vbroadcastf32x|4'.
+ 0x1005E021, // Large 'vbroadcastf32x|8'.
+ 0x4030B021, // Large 'vbroadcastf|64x2'.
+ 0x4034B021, // Large 'vbroadcastf|64x4'.
+ 0x4065A021, // Large 'vbroadcast|i128'.
+ 0x5038A021, // Large 'vbroadcast|i32x2'.
+ 0x503DA021, // Large 'vbroadcast|i32x4'.
+ 0x5042A021, // Large 'vbroadcast|i32x8'.
+ 0x5047A021, // Large 'vbroadcast|i64x2'.
+ 0x504CA021, // Large 'vbroadcast|i64x4'.
+ 0x210EA021, // Large 'vbroadcast|sd'.
+ 0x201CA021, // Large 'vbroadcast|ss'.
+ 0x89083476, // Small 'vcmppd'.
+ 0x91083476, // Small 'vcmpph'.
+ 0xA7083476, // Small 'vcmpps'.
+ 0x89383476, // Small 'vcmpsd'.
+ 0x91383476, // Small 'vcmpsh'.
+ 0xA7383476, // Small 'vcmpss'.
+ 0x210E56BB, // Large 'vcomi|sd'.
+ 0x20B556BB, // Large 'vcomi|sh'.
+ 0x201C56BB, // Large 'vcomi|ss'.
+ 0x2087919C, // Large 'vcompress|pd'.
+ 0x2070919C, // Large 'vcompress|ps'.
+ 0x208773ED, // Large 'vcvtdq2|pd'.
+ 0x208273ED, // Large 'vcvtdq2|ph'.
+ 0x207073ED, // Large 'vcvtdq2|ps'.
+ 0x1030D069, // Large 'vcvtne2ps2bf1|6'.
+ 0x1030C0DC, // Large 'vcvtneps2bf1|6'.
+ 0x20C5727E, // Large 'vcvtpd2|dq'.
+ 0x2082727E, // Large 'vcvtpd2|ph'.
+ 0x2070727E, // Large 'vcvtpd2|ps'.
+ 0x21AE727E, // Large 'vcvtpd2|qq'.
+ 0x20C5827E, // Large 'vcvtpd2u|dq'.
+ 0x21AE827E, // Large 'vcvtpd2u|qq'.
+ 0x20C57286, // Large 'vcvtph2|dq'.
+ 0x10268286, // Large 'vcvtph2p|d'.
+ 0x00009286, // Large 'vcvtph2ps'.
+ 0x102E9286, // Large 'vcvtph2ps|x'.
+ 0x21AE7286, // Large 'vcvtph2|qq'.
+ 0x328F7286, // Large 'vcvtph2|udq'.
+ 0x31AD7286, // Large 'vcvtph2|uqq'.
+ 0x23F47286, // Large 'vcvtph2|uw'.
+ 0x105F7286, // Large 'vcvtph2|w'.
+ 0x20C57292, // Large 'vcvtps2|dq'.
+ 0x10268292, // Large 'vcvtps2p|d'.
+ 0x00009292, // Large 'vcvtps2ph'.
+ 0x102E9292, // Large 'vcvtps2ph|x'.
+ 0x21AE7292, // Large 'vcvtps2|qq'.
+ 0x328F7292, // Large 'vcvtps2|udq'.
+ 0x31AD7292, // Large 'vcvtps2|uqq'.
+ 0x208773F6, // Large 'vcvtqq2|pd'.
+ 0x208273F6, // Large 'vcvtqq2|ph'.
+ 0x207073F6, // Large 'vcvtqq2|ps'.
+ 0x20B5729B, // Large 'vcvtsd2|sh'.
+ 0x201D729B, // Large 'vcvtsd2|si'.
+ 0x201C729B, // Large 'vcvtsd2|ss'.
+ 0x201D829B, // Large 'vcvtsd2u|si'.
+ 0x210E72A3, // Large 'vcvtsh2|sd'.
+ 0x201D72A3, // Large 'vcvtsh2|si'.
+ 0x201C72A3, // Large 'vcvtsh2|ss'.
+ 0x201D82A3, // Large 'vcvtsh2u|si'.
+ 0x210E73FD, // Large 'vcvtsi2|sd'.
+ 0x20B573FD, // Large 'vcvtsi2|sh'.
+ 0x201C73FD, // Large 'vcvtsi2|ss'.
+ 0x210E72AB, // Large 'vcvtss2|sd'.
+ 0x20B572AB, // Large 'vcvtss2|sh'.
+ 0x201D72AB, // Large 'vcvtss2|si'.
+ 0x201D82AB, // Large 'vcvtss2u|si'.
+ 0x20C581A5, // Large 'vcvttpd2|dq'.
+ 0x21AE81A5, // Large 'vcvttpd2|qq'.
+ 0x20C591A5, // Large 'vcvttpd2u|dq'.
+ 0x21AE91A5, // Large 'vcvttpd2u|qq'.
+ 0x20C581B0, // Large 'vcvttph2|dq'.
+ 0x21AE81B0, // Large 'vcvttph2|qq'.
+ 0x20C591B0, // Large 'vcvttph2u|dq'.
+ 0x21AE91B0, // Large 'vcvttph2u|qq'.
+ 0x105F91B0, // Large 'vcvttph2u|w'.
+ 0x105F81B0, // Large 'vcvttph2|w'.
+ 0x20C581B9, // Large 'vcvttps2|dq'.
+ 0x21AE81B9, // Large 'vcvttps2|qq'.
+ 0x20C591B9, // Large 'vcvttps2u|dq'.
+ 0x21AE91B9, // Large 'vcvttps2u|qq'.
+ 0x201D81C2, // Large 'vcvttsd2|si'.
+ 0x201D91C2, // Large 'vcvttsd2u|si'.
+ 0x201D81CB, // Large 'vcvttsh2|si'.
+ 0x201D91CB, // Large 'vcvttsh2u|si'.
+ 0x201D81D4, // Large 'vcvttss2|si'.
+ 0x201D91D4, // Large 'vcvttss2u|si'.
+ 0x208782B3, // Large 'vcvtudq2|pd'.
+ 0x208282B3, // Large 'vcvtudq2|ph'.
+ 0x207082B3, // Large 'vcvtudq2|ps'.
+ 0x208782BB, // Large 'vcvtuqq2|pd'.
+ 0x208282BB, // Large 'vcvtuqq2|ph'.
+ 0x207082BB, // Large 'vcvtuqq2|ps'.
+ 0x210E82C3, // Large 'vcvtusi2|sd'.
+ 0x20B582C3, // Large 'vcvtusi2|sh'.
+ 0x201C82C3, // Large 'vcvtusi2|ss'.
+ 0x30816404, // Large 'vcvtuw|2ph'.
+ 0x30815570, // Large 'vcvtw|2ph'.
+ 0x2344740A, // Large 'vdbpsad|bw'.
+ 0x890B2496, // Small 'vdivpd'.
+ 0x910B2496, // Small 'vdivph'.
+ 0xA70B2496, // Small 'vdivps'.
+ 0x893B2496, // Small 'vdivsd'.
+ 0x913B2496, // Small 'vdivsh'.
+ 0xA73B2496, // Small 'vdivss'.
+ 0x20707411, // Large 'vdpbf16|ps'.
+ 0x80484096, // Small 'vdppd'.
+ 0x81384096, // Small 'vdpps'.
+ 0x800948B6, // Small 'verr'.
+ 0x800BC8B6, // Small 'verw'.
+ 0x34874418, // Large 'vexp|2pd'.
+ 0x306F4418, // Large 'vexp|2ps'.
+ 0x30CD6418, // Large 'vexpan|dpd'.
+ 0x30D16418, // Large 'vexpan|dps'.
+ 0x306290EF, // Large 'vextractf|128'.
+ 0x602A70E8, // Large 'vextrac|tf32x4'.
+ 0x404390EF, // Large 'vextractf|32x8'.
+ 0x403090EF, // Large 'vextractf|64x2'.
+ 0x403490EF, // Large 'vextractf|64x4'.
+ 0x406580EF, // Large 'vextract|i128'.
+ 0x503D80EF, // Large 'vextract|i32x4'.
+ 0x504280EF, // Large 'vextract|i32x8'.
+ 0x504780EF, // Large 'vextract|i64x2'.
+ 0x504C80EF, // Large 'vextract|i64x4'.
+ 0x207080EF, // Large 'vextract|ps'.
+ 0x208282CB, // Large 'vfcmaddc|ph'.
+ 0x20B582CB, // Large 'vfcmaddc|sh'.
+ 0x2082741E, // Large 'vfcmulc|ph'.
+ 0x20B5741E, // Large 'vfcmulc|sh'.
+ 0x208791DD, // Large 'vfixupimm|pd'.
+ 0x207091DD, // Large 'vfixupimm|ps'.
+ 0x210E91DD, // Large 'vfixupimm|sd'.
+ 0x201C91DD, // Large 'vfixupimm|ss'.
+ 0x208791E6, // Large 'vfmadd132|pd'.
+ 0x208291E6, // Large 'vfmadd132|ph'.
+ 0x207091E6, // Large 'vfmadd132|ps'.
+ 0x210E91E6, // Large 'vfmadd132|sd'.
+ 0x20B591E6, // Large 'vfmadd132|sh'.
+ 0x201C91E6, // Large 'vfmadd132|ss'.
+ 0x50846076, // Large 'vfmadd|213pd'.
+ 0x50896076, // Large 'vfmadd|213ph'.
+ 0x508E6076, // Large 'vfmadd|213ps'.
+ 0x511A6076, // Large 'vfmadd|213sd'.
+ 0x511F6076, // Large 'vfmadd|213sh'.
+ 0x51246076, // Large 'vfmadd|213ss'.
+ 0x50936076, // Large 'vfmadd|231pd'.
+ 0x50986076, // Large 'vfmadd|231ph'.
+ 0x509D6076, // Large 'vfmadd|231ps'.
+ 0x51296076, // Large 'vfmadd|231sd'.
+ 0x512E6076, // Large 'vfmadd|231sh'.
+ 0x51336076, // Large 'vfmadd|231ss'.
+ 0x34256076, // Large 'vfmadd|cph'.
+ 0x34286076, // Large 'vfmadd|csh'.
+ 0x20876076, // Large 'vfmadd|pd'.
+ 0x20706076, // Large 'vfmadd|ps'.
+ 0x10267076, // Large 'vfmadds|d'.
+ 0x10147076, // Large 'vfmadds|s'.
+ 0x1026D076, // Large 'vfmaddsub132p|d'.
+ 0x1083D076, // Large 'vfmaddsub132p|h'.
+ 0x1014D076, // Large 'vfmaddsub132p|s'.
+ 0x50849076, // Large 'vfmaddsub|213pd'.
+ 0x50899076, // Large 'vfmaddsub|213ph'.
+ 0x508E9076, // Large 'vfmaddsub|213ps'.
+ 0x50939076, // Large 'vfmaddsub|231pd'.
+ 0x50989076, // Large 'vfmaddsub|231ph'.
+ 0x509D9076, // Large 'vfmaddsub|231ps'.
+ 0x20879076, // Large 'vfmaddsub|pd'.
+ 0x20709076, // Large 'vfmaddsub|ps'.
+ 0x208791EF, // Large 'vfmsub132|pd'.
+ 0x208291EF, // Large 'vfmsub132|ph'.
+ 0x207091EF, // Large 'vfmsub132|ps'.
+ 0x210E91EF, // Large 'vfmsub132|sd'.
+ 0x20B591EF, // Large 'vfmsub132|sh'.
+ 0x201C91EF, // Large 'vfmsub132|ss'.
+ 0x508460A2, // Large 'vfmsub|213pd'.
+ 0x508960A2, // Large 'vfmsub|213ph'.
+ 0x508E60A2, // Large 'vfmsub|213ps'.
+ 0x511A60A2, // Large 'vfmsub|213sd'.
+ 0x511F60A2, // Large 'vfmsub|213sh'.
+ 0x512460A2, // Large 'vfmsub|213ss'.
+ 0x509360A2, // Large 'vfmsub|231pd'.
+ 0x509860A2, // Large 'vfmsub|231ph'.
+ 0x509D60A2, // Large 'vfmsub|231ps'.
+ 0x512960A2, // Large 'vfmsub|231sd'.
+ 0x512E60A2, // Large 'vfmsub|231sh'.
+ 0x513360A2, // Large 'vfmsub|231ss'.
+ 0x2087C0A2, // Large 'vfmsubadd132|pd'.
+ 0x2082C0A2, // Large 'vfmsubadd132|ph'.
+ 0x2070C0A2, // Large 'vfmsubadd132|ps'.
+ 0x508490A2, // Large 'vfmsubadd|213pd'.
+ 0x508990A2, // Large 'vfmsubadd|213ph'.
+ 0x508E90A2, // Large 'vfmsubadd|213ps'.
+ 0x509390A2, // Large 'vfmsubadd|231pd'.
+ 0x509890A2, // Large 'vfmsubadd|231ph'.
+ 0x509D90A2, // Large 'vfmsubadd|231ps'.
+ 0x208790A2, // Large 'vfmsubadd|pd'.
+ 0x207090A2, // Large 'vfmsubadd|ps'.
+ 0x208760A2, // Large 'vfmsub|pd'.
+ 0x207060A2, // Large 'vfmsub|ps'.
+ 0x210E60A2, // Large 'vfmsub|sd'.
+ 0x201C60A2, // Large 'vfmsub|ss'.
+ 0x34255575, // Large 'vfmul|cph'.
+ 0x34285575, // Large 'vfmul|csh'.
+ 0x2087A110, // Large 'vfnmadd132|pd'.
+ 0x2082A110, // Large 'vfnmadd132|ph'.
+ 0x2070A110, // Large 'vfnmadd132|ps'.
+ 0x210EA110, // Large 'vfnmadd132|sd'.
+ 0x20B5A110, // Large 'vfnmadd132|sh'.
+ 0x201CA110, // Large 'vfnmadd132|ss'.
+ 0x50847110, // Large 'vfnmadd|213pd'.
+ 0x50897110, // Large 'vfnmadd|213ph'.
+ 0x508E7110, // Large 'vfnmadd|213ps'.
+ 0x511A7110, // Large 'vfnmadd|213sd'.
+ 0x511F7110, // Large 'vfnmadd|213sh'.
+ 0x51247110, // Large 'vfnmadd|213ss'.
+ 0x50937110, // Large 'vfnmadd|231pd'.
+ 0x50987110, // Large 'vfnmadd|231ph'.
+ 0x509D7110, // Large 'vfnmadd|231ps'.
+ 0x51297110, // Large 'vfnmadd|231sd'.
+ 0x512E7110, // Large 'vfnmadd|231sh'.
+ 0x51337110, // Large 'vfnmadd|231ss'.
+ 0x20877110, // Large 'vfnmadd|pd'.
+ 0x20707110, // Large 'vfnmadd|ps'.
+ 0x210E7110, // Large 'vfnmadd|sd'.
+ 0x201C7110, // Large 'vfnmadd|ss'.
+ 0x2087A138, // Large 'vfnmsub132|pd'.
+ 0x2082A138, // Large 'vfnmsub132|ph'.
+ 0x2070A138, // Large 'vfnmsub132|ps'.
+ 0x210EA138, // Large 'vfnmsub132|sd'.
+ 0x20B5A138, // Large 'vfnmsub132|sh'.
+ 0x201CA138, // Large 'vfnmsub132|ss'.
+ 0x50847138, // Large 'vfnmsub|213pd'.
+ 0x50897138, // Large 'vfnmsub|213ph'.
+ 0x508E7138, // Large 'vfnmsub|213ps'.
+ 0x511A7138, // Large 'vfnmsub|213sd'.
+ 0x511F7138, // Large 'vfnmsub|213sh'.
+ 0x51247138, // Large 'vfnmsub|213ss'.
+ 0x50937138, // Large 'vfnmsub|231pd'.
+ 0x50987138, // Large 'vfnmsub|231ph'.
+ 0x509D7138, // Large 'vfnmsub|231ps'.
+ 0x51297138, // Large 'vfnmsub|231sd'.
+ 0x512E7138, // Large 'vfnmsub|231sh'.
+ 0x51337138, // Large 'vfnmsub|231ss'.
+ 0x20877138, // Large 'vfnmsub|pd'.
+ 0x20707138, // Large 'vfnmsub|ps'.
+ 0x210E7138, // Large 'vfnmsub|sd'.
+ 0x201C7138, // Large 'vfnmsub|ss'.
+ 0x208782D3, // Large 'vfpclass|pd'.
+ 0x208282D3, // Large 'vfpclass|ph'.
+ 0x207082D3, // Large 'vfpclass|ps'.
+ 0x210E82D3, // Large 'vfpclass|sd'.
+ 0x20B582D3, // Large 'vfpclass|sh'.
+ 0x201C82D3, // Large 'vfpclass|ss'.
+ 0x208756C0, // Large 'vfrcz|pd'.
+ 0x207056C0, // Large 'vfrcz|ps'.
+ 0x210E56C0, // Large 'vfrcz|sd'.
+ 0x201C56C0, // Large 'vfrcz|ss'.
+ 0x30CD70F8, // Large 'vgather|dpd'.
+ 0x30D170F8, // Large 'vgather|dps'.
+ 0x30CDA0F8, // Large 'vgatherpf0|dpd'.
+ 0x30D1A0F8, // Large 'vgatherpf0|dps'.
+ 0x30C6A0F8, // Large 'vgatherpf0|qpd'.
+ 0x30C9A0F8, // Large 'vgatherpf0|qps'.
+ 0x40CC90F8, // Large 'vgatherpf|1dpd'.
+ 0x40D090F8, // Large 'vgatherpf|1dps'.
+ 0x40D490F8, // Large 'vgatherpf|1qpd'.
+ 0x40D890F8, // Large 'vgatherpf|1qps'.
+ 0x30C670F8, // Large 'vgather|qpd'.
+ 0x30C970F8, // Large 'vgather|qps'.
+ 0x2087742B, // Large 'vgetexp|pd'.
+ 0x2082742B, // Large 'vgetexp|ph'.
+ 0x2070742B, // Large 'vgetexp|ps'.
+ 0x210E742B, // Large 'vgetexp|sd'.
+ 0x20B5742B, // Large 'vgetexp|sh'.
+ 0x201C742B, // Large 'vgetexp|ss'.
+ 0x31A972DB, // Large 'vgetman|tpd'.
+ 0x31B472DB, // Large 'vgetman|tph'.
+ 0x31BD72DB, // Large 'vgetman|tps'.
+ 0x310D72DB, // Large 'vgetman|tsd'.
+ 0x31CF72DB, // Large 'vgetman|tsh'.
+ 0x31D872DB, // Large 'vgetman|tss'.
+ 0x200FF000, // Large 'vgf2p8affineinv|qb'.
+ 0x200FC000, // Large 'vgf2p8affine|qb'.
+ 0x42E26000, // Large 'vgf2p8|mulb'.
+ 0x30CD46C5, // Large 'vhad|dpd'.
+ 0x30D146C5, // Large 'vhad|dps'.
+ 0x208756C9, // Large 'vhsub|pd'.
+ 0x207056C9, // Large 'vhsub|ps'.
+ 0x30628148, // Large 'vinsertf|128'.
+ 0x602A6142, // Large 'vinser|tf32x4'.
+ 0x40438148, // Large 'vinsertf|32x8'.
+ 0x40308148, // Large 'vinsertf|64x2'.
+ 0x40348148, // Large 'vinsertf|64x4'.
+ 0x40657148, // Large 'vinsert|i128'.
+ 0x503D7148, // Large 'vinsert|i32x4'.
+ 0x50427148, // Large 'vinsert|i32x8'.
+ 0x50477148, // Large 'vinsert|i64x2'.
+ 0x504C7148, // Large 'vinsert|i64x4'.
+ 0x20707148, // Large 'vinsert|ps'.
+ 0xAB121196, // Small 'vlddqu'.
+ 0x1023757A, // Large 'vldmxcs|r'.
+ 0x107DA1F8, // Large 'vmaskmovdq|u'.
+ 0x208781F8, // Large 'vmaskmov|pd'.
+ 0x207081F8, // Large 'vmaskmov|ps'.
+ 0x890C05B6, // Small 'vmaxpd'.
+ 0x910C05B6, // Small 'vmaxph'.
+ 0xA70C05B6, // Small 'vmaxps'.
+ 0x893C05B6, // Small 'vmaxsd'.
+ 0x913C05B6, // Small 'vmaxsh'.
+ 0xA73C05B6, // Small 'vmaxss'.
+ 0x98C08DB6, // Small 'vmcall'.
+ 0x23A256CE, // Large 'vmcle|ar'.
+ 0x86EA99B6, // Small 'vmfunc'.
+ 0x890725B6, // Small 'vminpd'.
+ 0x910725B6, // Small 'vminph'.
+ 0xA70725B6, // Small 'vminps'.
+ 0x893725B6, // Small 'vminsd'.
+ 0x913725B6, // Small 'vminsh'.
+ 0xA73725B6, // Small 'vminss'.
+ 0x21606581, // Large 'vmlaun|ch'.
+ 0x8817B1B6, // Small 'vmload'.
+ 0x361A46D3, // Large 'vmmc|all'.
+ 0x208756D7, // Large 'vmova|pd'.
+ 0x207056D7, // Large 'vmova|ps'.
+ 0x804B3DB6, // Small 'vmovd'.
+ 0x35875432, // Large 'vmovd|dup'.
+ 0x00007432, // Large 'vmovdqa'.
+ 0x202C7432, // Large 'vmovdqa|32'.
+ 0x20307432, // Large 'vmovdqa|64'.
+ 0x107D6432, // Large 'vmovdq|u'.
+ 0x34396432, // Large 'vmovdq|u16'.
+ 0x343C6432, // Large 'vmovdq|u32'.
+ 0x343F6432, // Large 'vmovdq|u64'.
+ 0x258A6432, // Large 'vmovdq|u8'.
+ 0x34ED558C, // Large 'vmovh|lps'.
+ 0x2087558C, // Large 'vmovh|pd'.
+ 0x2070558C, // Large 'vmovh|ps'.
+ 0x20706591, // Large 'vmovlh|ps'.
+ 0x20875591, // Large 'vmovl|pd'.
+ 0x20705591, // Large 'vmovl|ps'.
+ 0x20877442, // Large 'vmovmsk|pd'.
+ 0x20707442, // Large 'vmovmsk|ps'.
+ 0x20C56449, // Large 'vmovnt|dq'.
+ 0x34366449, // Large 'vmovnt|dqa'.
+ 0x20876449, // Large 'vmovnt|pd'.
+ 0x20706449, // Large 'vmovnt|ps'.
+ 0x811B3DB6, // Small 'vmovq'.
+ 0x893B3DB6, // Small 'vmovsd'.
+ 0x913B3DB6, // Small 'vmovsh'.
+ 0x21E1744F, // Large 'vmovshd|up'.
+ 0x21E17456, // Large 'vmovsld|up'.
+ 0xA73B3DB6, // Small 'vmovss'.
+ 0x33AD4432, // Large 'vmov|upd'.
+ 0x207056DC, // Large 'vmovu|ps'.
+ 0x817B3DB6, // Small 'vmovw'.
+ 0x23446597, // Large 'vmpsad|bw'.
+ 0x338B46E1, // Large 'vmpt|rld'.
+ 0x338746E1, // Large 'vmpt|rst'.
+ 0x8812C9B6, // Small 'vmread'.
+ 0x100B759D, // Large 'vmresum|e'.
+ 0x80EAC9B6, // Small 'vmrun'.
+ 0x8B60CDB6, // Small 'vmsave'.
+ 0x890655B6, // Small 'vmulpd'.
+ 0x910655B6, // Small 'vmulph'.
+ 0xA70655B6, // Small 'vmulps'.
+ 0x893655B6, // Small 'vmulsd'.
+ 0x913655B6, // Small 'vmulsh'.
+ 0xA73655B6, // Small 'vmulss'.
+ 0x20BF56E5, // Large 'vmwri|te'.
+ 0x80E7E1B6, // Small 'vmxon'.
+ 0x804849F6, // Small 'vorpd'.
+ 0x813849F6, // Small 'vorps'.
+ 0x1026C102, // Large 'vp2intersect|d'.
+ 0x100FC102, // Large 'vp2intersect|q'.
+ 0x102682E6, // Large 'vp4dpwss|d'.
+ 0x207B82E6, // Large 'vp4dpwss|ds'.
+ 0x85310616, // Small 'vpabsb'.
+ 0x89310616, // Small 'vpabsd'.
+ 0xA3310616, // Small 'vpabsq'.
+ 0xAF310616, // Small 'vpabsw'.
+ 0x105F845D, // Large 'vpackssd|w'.
+ 0x2465745D, // Large 'vpackss|wb'.
+ 0x34636467, // Large 'vpacku|sdw'.
+ 0x346D6467, // Large 'vpacku|swb'.
+ 0x84420616, // Small 'vpaddb'.
+ 0x88420616, // Small 'vpaddd'.
+ 0xA2420616, // Small 'vpaddq'.
+ 0x250D55A4, // Large 'vpadd|sb'.
+ 0x232D55A4, // Large 'vpadd|sw'.
+ 0x250D65A4, // Large 'vpaddu|sb'.
+ 0x232D65A4, // Large 'vpaddu|sw'.
+ 0xAE420616, // Small 'vpaddw'.
+ 0x102375AA, // Large 'vpalign|r'.
+ 0x80470616, // Small 'vpand'.
+ 0x88470616, // Small 'vpandd'.
+ 0x9C470616, // Small 'vpandn'.
+ 0x217856EA, // Large 'vpand|nd'.
+ 0x264056EA, // Large 'vpand|nq'.
+ 0xA2470616, // Small 'vpandq'.
+ 0x847B0616, // Small 'vpavgb'.
+ 0xAE7B0616, // Small 'vpavgw'.
+ 0x10267470, // Large 'vpblend|d'.
+ 0x205C7470, // Large 'vpblend|mb'.
+ 0x24777470, // Large 'vpblend|md'.
+ 0x100F8470, // Large 'vpblendm|q'.
+ 0x105F8470, // Large 'vpblendm|w'.
+ 0x20217470, // Large 'vpblend|vb'.
+ 0x105F7470, // Large 'vpblend|w'.
+ 0x1010B051, // Large 'vpbroadcast|b'.
+ 0x1026B051, // Large 'vpbroadcast|d'.
+ 0x100FE051, // Large 'vpbroadcastmb2|q'.
+ 0x305FC051, // Large 'vpbroadcastm|w2d'.
+ 0x100FB051, // Large 'vpbroadcast|q'.
+ 0x105FB051, // Large 'vpbroadcast|w'.
+ 0x424062EE, // Large 'vpclmu|lqdq'.
+ 0xACF68E16, // Small 'vpcmov'.
+ 0x85068E16, // Small 'vpcmpb'.
+ 0x89068E16, // Small 'vpcmpd'.
+ 0x200F62F4, // Large 'vpcmpe|qb'.
+ 0x223562F4, // Large 'vpcmpe|qd'.
+ 0x21AE62F4, // Large 'vpcmpe|qq'.
+ 0x24BB62F4, // Large 'vpcmpe|qw'.
+ 0x100992F4, // Large 'vpcmpestr|i'.
+ 0x105C92F4, // Large 'vpcmpestr|m'.
+ 0x35B152F4, // Large 'vpcmp|gtb'.
+ 0x35B452F4, // Large 'vpcmp|gtd'.
+ 0x35B752F4, // Large 'vpcmp|gtq'.
+ 0x35BA52F4, // Large 'vpcmp|gtw'.
+ 0x100992FD, // Large 'vpcmpistr|i'.
+ 0x105C92FD, // Large 'vpcmpistr|m'.
+ 0xA3068E16, // Small 'vpcmpq'.
+ 0x207D52F4, // Large 'vpcmp|ub'.
+ 0x228F52F4, // Large 'vpcmp|ud'.
+ 0x21AD52F4, // Large 'vpcmp|uq'.
+ 0x23F452F4, // Large 'vpcmp|uw'.
+ 0xAF068E16, // Small 'vpcmpw'.
+ 0x84D78E16, // Small 'vpcomb'.
+ 0x88D78E16, // Small 'vpcomd'.
+ 0x1010A202, // Large 'vpcompress|b'.
+ 0x1026A202, // Large 'vpcompress|d'.
+ 0x100FA202, // Large 'vpcompress|q'.
+ 0x105FA202, // Large 'vpcompress|w'.
+ 0xA2D78E16, // Small 'vpcomq'.
+ 0x207D5202, // Large 'vpcom|ub'.
+ 0x228F5202, // Large 'vpcom|ud'.
+ 0x21AD5202, // Large 'vpcom|uq'.
+ 0x23F45202, // Large 'vpcom|uw'.
+ 0xAED78E16, // Small 'vpcomw'.
+ 0x1026A20C, // Large 'vpconflict|d'.
+ 0x100FA20C, // Large 'vpconflict|q'.
+ 0x10267479, // Large 'vpdpbus|d'.
+ 0x207B7479, // Large 'vpdpbus|ds'.
+ 0x10267480, // Large 'vpdpwss|d'.
+ 0x207B7480, // Large 'vpdpwss|ds'.
+ 0x30627306, // Large 'vperm2f|128'.
+ 0x40656306, // Large 'vperm2|i128'.
+ 0x84D91616, // Small 'vpermb'.
+ 0x88D91616, // Small 'vpermd'.
+ 0x2072630D, // Large 'vpermi|2b'.
+ 0x2060630D, // Large 'vpermi|2d'.
+ 0x3487630D, // Large 'vpermi|2pd'.
+ 0x306F630D, // Large 'vpermi|2ps'.
+ 0x25BD630D, // Large 'vpermi|2q'.
+ 0x205E630D, // Large 'vpermi|2w'.
+ 0x2087830D, // Large 'vpermil2|pd'.
+ 0x2070830D, // Large 'vpermil2|ps'.
+ 0x2087730D, // Large 'vpermil|pd'.
+ 0x2070730D, // Large 'vpermil|ps'.
+ 0x20875306, // Large 'vperm|pd'.
+ 0x20705306, // Large 'vperm|ps'.
+ 0xA2D91616, // Small 'vpermq'.
+ 0x2072648A, // Large 'vpermt|2b'.
+ 0x2060648A, // Large 'vpermt|2d'.
+ 0x3487648A, // Large 'vpermt|2pd'.
+ 0x306F648A, // Large 'vpermt|2ps'.
+ 0x25BD648A, // Large 'vpermt|2q'.
+ 0x205E648A, // Large 'vpermt|2w'.
+ 0xAED91616, // Small 'vpermw'.
+ 0x240B7490, // Large 'vpexpan|db'.
+ 0x207A7490, // Large 'vpexpan|dd'.
+ 0x20C57490, // Large 'vpexpan|dq'.
+ 0x24647490, // Large 'vpexpan|dw'.
+ 0x352E4490, // Large 'vpex|trb'.
+ 0x254D56EF, // Large 'vpext|rd'.
+ 0x223456EF, // Large 'vpext|rq'.
+ 0x26F456EF, // Large 'vpext|rw'.
+ 0x207A8315, // Large 'vpgather|dd'.
+ 0x20C58315, // Large 'vpgather|dq'.
+ 0x22358315, // Large 'vpgather|qd'.
+ 0x21AE8315, // Large 'vpgather|qq'.
+ 0x25BF6497, // Large 'vphadd|bd'.
+ 0x25C16497, // Large 'vphadd|bq'.
+ 0x23446497, // Large 'vphadd|bw'.
+ 0x10266497, // Large 'vphadd|d'.
+ 0x20C56497, // Large 'vphadd|dq'.
+ 0x232D6497, // Large 'vphadd|sw'.
+ 0x10268497, // Large 'vphaddub|d'.
+ 0x100F8497, // Large 'vphaddub|q'.
+ 0x105F8497, // Large 'vphaddub|w'.
+ 0x20C57497, // Large 'vphaddu|dq'.
+ 0x23467497, // Large 'vphaddu|wd'.
+ 0x249F7497, // Large 'vphaddu|wq'.
+ 0x105F6497, // Large 'vphadd|w'.
+ 0x23466497, // Large 'vphadd|wd'.
+ 0x249F6497, // Large 'vphadd|wq'.
+ 0x105FA216, // Large 'vphminposu|w'.
+ 0x234465C3, // Large 'vphsub|bw'.
+ 0x102665C3, // Large 'vphsub|d'.
+ 0x20C565C3, // Large 'vphsub|dq'.
+ 0x232D65C3, // Large 'vphsub|sw'.
+ 0x105F65C3, // Large 'vphsub|w'.
+ 0x234665C3, // Large 'vphsub|wd'.
+ 0x252F56F6, // Large 'vpins|rb'.
+ 0x254D56F6, // Large 'vpins|rd'.
+ 0x223456F6, // Large 'vpins|rq'.
+ 0x26F456F6, // Large 'vpins|rw'.
+ 0x23CF65C9, // Large 'vplzcn|td'.
+ 0x215865C9, // Large 'vplzcn|tq'.
+ 0x207A631D, // Large 'vpmacs|dd'.
+ 0x34A1631D, // Large 'vpmacs|dqh'.
+ 0x334C631D, // Large 'vpmacs|dql'.
+ 0x1026831D, // Large 'vpmacssd|d'.
+ 0x1083931D, // Large 'vpmacssdq|h'.
+ 0x10B2931D, // Large 'vpmacssdq|l'.
+ 0x2346731D, // Large 'vpmacss|wd'.
+ 0x2345731D, // Large 'vpmacss|ww'.
+ 0x2346631D, // Large 'vpmacs|wd'.
+ 0x2345631D, // Large 'vpmacs|ww'.
+ 0x10269326, // Large 'vpmadcssw|d'.
+ 0x23467326, // Large 'vpmadcs|wd'.
+ 0x21AD9220, // Large 'vpmadd52h|uq'.
+ 0x32298220, // Large 'vpmadd52|luq'.
+ 0x432F6220, // Large 'vpmadd|ubsw'.
+ 0x23466220, // Large 'vpmadd|wd'.
+ 0x61FB4220, // Large 'vpma|skmovd'.
+ 0x200E8333, // Large 'vpmaskmo|vq'.
+ 0x250D56FB, // Large 'vpmax|sb'.
+ 0x210E56FB, // Large 'vpmax|sd'.
+ 0x235356FB, // Large 'vpmax|sq'.
+ 0x232D56FB, // Large 'vpmax|sw'.
+ 0x207D56FB, // Large 'vpmax|ub'.
+ 0x228F56FB, // Large 'vpmax|ud'.
+ 0x21AD56FB, // Large 'vpmax|uq'.
+ 0x23F456FB, // Large 'vpmax|uw'.
+ 0x250D5700, // Large 'vpmin|sb'.
+ 0x210E5700, // Large 'vpmin|sd'.
+ 0x23535700, // Large 'vpmin|sq'.
+ 0x232D5700, // Large 'vpmin|sw'.
+ 0x207D5700, // Large 'vpmin|ub'.
+ 0x228F5700, // Large 'vpmin|ud'.
+ 0x21AD5700, // Large 'vpmin|uq'.
+ 0x23F45700, // Large 'vpmin|uw'.
+ 0x35CF54A4, // Large 'vpmov|b2m'.
+ 0x35D254A4, // Large 'vpmov|d2m'.
+ 0x240B54A4, // Large 'vpmov|db'.
+ 0x246454A4, // Large 'vpmov|dw'.
+ 0x207264A4, // Large 'vpmovm|2b'.
+ 0x206064A4, // Large 'vpmovm|2d'.
+ 0x25BD64A4, // Large 'vpmovm|2q'.
+ 0x205E64A4, // Large 'vpmovm|2w'.
+ 0x101084A4, // Large 'vpmovmsk|b'.
+ 0x35D554A4, // Large 'vpmov|q2m'.
+ 0x200F54A4, // Large 'vpmov|qb'.
+ 0x223554A4, // Large 'vpmov|qd'.
+ 0x24BB54A4, // Large 'vpmov|qw'.
+ 0x240B64AC, // Large 'vpmovs|db'.
+ 0x246464AC, // Large 'vpmovs|dw'.
+ 0x200F64AC, // Large 'vpmovs|qb'.
+ 0x223564AC, // Large 'vpmovs|qd'.
+ 0x24BB64AC, // Large 'vpmovs|qw'.
+ 0x246564AC, // Large 'vpmovs|wb'.
+ 0x102684AC, // Large 'vpmovsxb|d'.
+ 0x100F84AC, // Large 'vpmovsxb|q'.
+ 0x105F84AC, // Large 'vpmovsxb|w'.
+ 0x20C574AC, // Large 'vpmovsx|dq'.
+ 0x234674AC, // Large 'vpmovsx|wd'.
+ 0x249F74AC, // Large 'vpmovsx|wq'.
+ 0x240B74B4, // Large 'vpmovus|db'.
+ 0x246474B4, // Large 'vpmovus|dw'.
+ 0x200F74B4, // Large 'vpmovus|qb'.
+ 0x223574B4, // Large 'vpmovus|qd'.
+ 0x24BB74B4, // Large 'vpmovus|qw'.
+ 0x246574B4, // Large 'vpmovus|wb'.
+ 0x35D854A4, // Large 'vpmov|w2m'.
+ 0x246554A4, // Large 'vpmov|wb'.
+ 0x102684BD, // Large 'vpmovzxb|d'.
+ 0x100F84BD, // Large 'vpmovzxb|q'.
+ 0x105F84BD, // Large 'vpmovzxb|w'.
+ 0x20C574BD, // Large 'vpmovzx|dq'.
+ 0x234674BD, // Large 'vpmovzx|wd'.
+ 0x249F74BD, // Large 'vpmovzx|wq'.
+ 0x20C550AE, // Large 'vpmul|dq'.
+ 0x232D74C5, // Large 'vpmulhr|sw'.
+ 0x23F464C5, // Large 'vpmulh|uw'.
+ 0x105F64C5, // Large 'vpmulh|w'.
+ 0x234B50AE, // Large 'vpmul|ld'.
+ 0x224050AE, // Large 'vpmul|lq'.
+ 0x234E50AE, // Large 'vpmul|lw'.
+ 0x200FC0AE, // Large 'vpmultishift|qb'.
+ 0x328F50AE, // Large 'vpmul|udq'.
+ 0x25B265DB, // Large 'vpopcn|tb'.
+ 0x23CF65DB, // Large 'vpopcn|td'.
+ 0x215865DB, // Large 'vpopcn|tq'.
+ 0x216365DB, // Large 'vpopcn|tw'.
+ 0x80093E16, // Small 'vpor'.
+ 0x80493E16, // Small 'vpord'.
+ 0x81193E16, // Small 'vporq'.
+ 0x9B22C216, // Small 'vpperm'.
+ 0x88C7CA16, // Small 'vprold'.
+ 0xA2C7CA16, // Small 'vprolq'.
+ 0x21FF5705, // Large 'vprol|vd'.
+ 0x200E5705, // Large 'vprol|vq'.
+ 0x8927CA16, // Small 'vprord'.
+ 0xA327CA16, // Small 'vprorq'.
+ 0x21FF570A, // Large 'vpror|vd'.
+ 0x200E570A, // Large 'vpror|vq'.
+ 0x8547CA16, // Small 'vprotb'.
+ 0x8947CA16, // Small 'vprotd'.
+ 0xA347CA16, // Small 'vprotq'.
+ 0xAF47CA16, // Small 'vprotw'.
+ 0x2344570F, // Large 'vpsad|bw'.
+ 0x207A922C, // Large 'vpscatter|dd'.
+ 0x20C5922C, // Large 'vpscatter|dq'.
+ 0x2235922C, // Large 'vpscatter|qd'.
+ 0x100FA22C, // Large 'vpscatterq|q'.
+ 0x84144E16, // Small 'vpshab'.
+ 0x88144E16, // Small 'vpshad'.
+ 0xA2144E16, // Small 'vpshaq'.
+ 0xAE144E16, // Small 'vpshaw'.
+ 0x84C44E16, // Small 'vpshlb'.
+ 0x88C44E16, // Small 'vpshld'.
+ 0x102665E1, // Large 'vpshld|d'.
+ 0x100F65E1, // Large 'vpshld|q'.
+ 0x341055E1, // Large 'vpshl|dvd'.
+ 0x35E655E1, // Large 'vpshl|dvq'.
+ 0x105F75E1, // Large 'vpshldv|w'.
+ 0x105F65E1, // Large 'vpshld|w'.
+ 0xA2C44E16, // Small 'vpshlq'.
+ 0xAEC44E16, // Small 'vpshlw'.
+ 0x102665E9, // Large 'vpshrd|d'.
+ 0x100F65E9, // Large 'vpshrd|q'.
+ 0x341055E9, // Large 'vpshr|dvd'.
+ 0x35E655E9, // Large 'vpshr|dvq'.
+ 0x35EE55E9, // Large 'vpshr|dvw'.
+ 0x105F65E9, // Large 'vpshrd|w'.
+ 0x00007150, // Large 'vpshufb'.
+ 0x205CA150, // Large 'vpshufbitq|mb'.
+ 0x10266150, // Large 'vpshuf|d'.
+ 0x25F16150, // Large 'vpshuf|hw'.
+ 0x234E6150, // Large 'vpshuf|lw'.
+ 0x251A5714, // Large 'vpsig|nb'.
+ 0x21785714, // Large 'vpsig|nd'.
+ 0x26225714, // Large 'vpsig|nw'.
+ 0x88C64E16, // Small 'vpslld'.
+ 0x334B4719, // Large 'vpsl|ldq'.
+ 0xA2C64E16, // Small 'vpsllq'.
+ 0x21FF571D, // Large 'vpsll|vd'.
+ 0x200E571D, // Large 'vpsll|vq'.
+ 0x25EF571D, // Large 'vpsll|vw'.
+ 0xAEC64E16, // Small 'vpsllw'.
+ 0x88194E16, // Small 'vpsrad'.
+ 0xA2194E16, // Small 'vpsraq'.
+ 0x21FF5722, // Large 'vpsra|vd'.
+ 0x200E5722, // Large 'vpsra|vq'.
+ 0x25EF5722, // Large 'vpsra|vw'.
+ 0xAE194E16, // Small 'vpsraw'.
+ 0x88C94E16, // Small 'vpsrld'.
+ 0x334B4722, // Large 'vpsr|ldq'.
+ 0xA2C94E16, // Small 'vpsrlq'.
+ 0x21FF5727, // Large 'vpsrl|vd'.
+ 0x200E5727, // Large 'vpsrl|vq'.
+ 0x25EF5727, // Large 'vpsrl|vw'.
+ 0xAEC94E16, // Small 'vpsrlw'.
+ 0x842ACE16, // Small 'vpsubb'.
+ 0x882ACE16, // Small 'vpsubd'.
+ 0xA22ACE16, // Small 'vpsubq'.
+ 0x250D55F3, // Large 'vpsub|sb'.
+ 0x232D55F3, // Large 'vpsub|sw'.
+ 0x250D65F3, // Large 'vpsubu|sb'.
+ 0x232D65F3, // Large 'vpsubu|sw'.
+ 0xAE2ACE16, // Small 'vpsubw'.
+ 0x1026933B, // Large 'vpternlog|d'.
+ 0x100F933B, // Large 'vpternlog|q'.
+ 0xA932D216, // Small 'vptest'.
+ 0x205C64CC, // Large 'vptest|mb'.
+ 0x247764CC, // Large 'vptest|md'.
+ 0x24D364CC, // Large 'vptest|mq'.
+ 0x25D764CC, // Large 'vptest|mw'.
+ 0x205C74CC, // Large 'vptestn|mb'.
+ 0x247774CC, // Large 'vptestn|md'.
+ 0x24D374CC, // Large 'vptestn|mq'.
+ 0x105F84CC, // Large 'vptestnm|w'.
+ 0x23448237, // Large 'vpunpckh|bw'.
+ 0x20C58237, // Large 'vpunpckh|dq'.
+ 0x20C59237, // Large 'vpunpckhq|dq'.
+ 0x23468237, // Large 'vpunpckh|wd'.
+ 0x33487237, // Large 'vpunpck|lbw'.
+ 0x334B7237, // Large 'vpunpck|ldq'.
+ 0x42407237, // Large 'vpunpck|lqdq'.
+ 0x334E7237, // Large 'vpunpck|lwd'.
+ 0x8127E216, // Small 'vpxor'.
+ 0x8927E216, // Small 'vpxord'.
+ 0xA327E216, // Small 'vpxorq'.
+ 0x208765F9, // Large 'vrange|pd'.
+ 0x207065F9, // Large 'vrange|ps'.
+ 0x210E65F9, // Large 'vrange|sd'.
+ 0x201C65F9, // Large 'vrange|ss'.
+ 0x208765FF, // Large 'vrcp14|pd'.
+ 0x207065FF, // Large 'vrcp14|ps'.
+ 0x210E65FF, // Large 'vrcp14|sd'.
+ 0x201C65FF, // Large 'vrcp14|ss'.
+ 0x435945FF, // Large 'vrcp|28pd'.
+ 0x435D45FF, // Large 'vrcp|28ps'.
+ 0x436145FF, // Large 'vrcp|28sd'.
+ 0x436545FF, // Large 'vrcp|28ss'.
+ 0x91080E56, // Small 'vrcpph'.
+ 0xA7080E56, // Small 'vrcpps'.
+ 0x91380E56, // Small 'vrcpsh'.
+ 0xA7380E56, // Small 'vrcpss'.
+ 0x208774D5, // Large 'vreduce|pd'.
+ 0x208274D5, // Large 'vreduce|ph'.
+ 0x207074D5, // Large 'vreduce|ps'.
+ 0x210E74D5, // Large 'vreduce|sd'.
+ 0x20B574D5, // Large 'vreduce|sh'.
+ 0x201C74D5, // Large 'vreduce|ss'.
+ 0x20879244, // Large 'vrndscale|pd'.
+ 0x20829244, // Large 'vrndscale|ph'.
+ 0x20709244, // Large 'vrndscale|ps'.
+ 0x210E9244, // Large 'vrndscale|sd'.
+ 0x20B59244, // Large 'vrndscale|sh'.
+ 0x201C9244, // Large 'vrndscale|ss'.
+ 0x30CD5605, // Large 'vroun|dpd'.
+ 0x30D15605, // Large 'vroun|dps'.
+ 0x360A5605, // Large 'vroun|dsd'.
+ 0x10147605, // Large 'vrounds|s'.
+ 0x20878351, // Large 'vrsqrt14|pd'.
+ 0x20708351, // Large 'vrsqrt14|ps'.
+ 0x210E8351, // Large 'vrsqrt14|sd'.
+ 0x201C8351, // Large 'vrsqrt14|ss'.
+ 0x43596351, // Large 'vrsqrt|28pd'.
+ 0x435D6351, // Large 'vrsqrt|28ps'.
+ 0x43616351, // Large 'vrsqrt|28sd'.
+ 0x43656351, // Large 'vrsqrt|28ss'.
+ 0x20826351, // Large 'vrsqrt|ph'.
+ 0x20706351, // Large 'vrsqrt|ps'.
+ 0x20B56351, // Large 'vrsqrt|sh'.
+ 0x201C6351, // Large 'vrsqrt|ss'.
+ 0x208774DC, // Large 'vscalef|pd'.
+ 0x208274DC, // Large 'vscalef|ph'.
+ 0x207074DC, // Large 'vscalef|ps'.
+ 0x210E74DC, // Large 'vscalef|sd'.
+ 0x20B574DC, // Large 'vscalef|sh'.
+ 0x201C74DC, // Large 'vscalef|ss'.
+ 0x30CD80BA, // Large 'vscatter|dpd'.
+ 0x30D180BA, // Large 'vscatter|dps'.
+ 0x2087C0BA, // Large 'vscatterpf0d|pd'.
+ 0x2070C0BA, // Large 'vscatterpf0d|ps'.
+ 0x30C6B0BA, // Large 'vscatterpf0|qpd'.
+ 0x30C9B0BA, // Large 'vscatterpf0|qps'.
+ 0x40CCA0BA, // Large 'vscatterpf|1dpd'.
+ 0x40D0A0BA, // Large 'vscatterpf|1dps'.
+ 0x40D4A0BA, // Large 'vscatterpf|1qpd'.
+ 0x40D8A0BA, // Large 'vscatterpf|1qps'.
+ 0x30C680BA, // Large 'vscatter|qpd'.
+ 0x30C980BA, // Large 'vscatter|qps'.
+ 0x502B5369, // Large 'vshuf|f32x4'.
+ 0x4030636E, // Large 'vshuff|64x2'.
+ 0x503D5369, // Large 'vshuf|i32x4'.
+ 0x50475369, // Large 'vshuf|i64x2'.
+ 0x20875369, // Large 'vshuf|pd'.
+ 0x20705369, // Large 'vshuf|ps'.
+ 0x31A9472C, // Large 'vsqr|tpd'.
+ 0x31B4472C, // Large 'vsqr|tph'.
+ 0x31BD472C, // Large 'vsqr|tps'.
+ 0x310D472C, // Large 'vsqr|tsd'.
+ 0x31CF472C, // Large 'vsqr|tsh'.
+ 0x31D8472C, // Large 'vsqr|tss'.
+ 0x1023760D, // Large 'vstmxcs|r'.
+ 0x89015676, // Small 'vsubpd'.
+ 0x91015676, // Small 'vsubph'.
+ 0xA7015676, // Small 'vsubps'.
+ 0x89315676, // Small 'vsubsd'.
+ 0x91315676, // Small 'vsubsh'.
+ 0xA7315676, // Small 'vsubss'.
+ 0x31A94730, // Large 'vtes|tpd'.
+ 0x31BD4730, // Large 'vtes|tps'.
+ 0x210E6614, // Large 'vucomi|sd'.
+ 0x20B56614, // Large 'vucomi|sh'.
+ 0x201C6614, // Large 'vucomi|ss'.
+ 0x208774E3, // Large 'vunpckh|pd'.
+ 0x207074E3, // Large 'vunpckh|ps'.
+ 0x34EA64E3, // Large 'vunpck|lpd'.
+ 0x34ED64E3, // Large 'vunpck|lps'.
+ 0x89093F16, // Small 'vxorpd'.
+ 0xA7093F16, // Small 'vxorps'.
+ 0x361A5374, // Large 'vzero|all'.
+ 0x33077374, // Large 'vzeroup|per'.
+ 0x89672457, // Small 'wbinvd'.
+ 0x21FF661D, // Large 'wbnoin|vd'.
+ 0x33B05623, // Large 'wrfsb|ase'.
+ 0x33B05628, // Large 'wrgsb|ase'.
+ 0x8129B657, // Small 'wrmsr'.
+ 0x8049CE57, // Small 'wrssd'.
+ 0x8119CE57, // Small 'wrssq'.
+ 0x8939D657, // Small 'wrussd'.
+ 0xA339D657, // Small 'wrussq'.
+ 0xA9278838, // Small 'xabort'.
+ 0x80021038, // Small 'xadd'.
+ 0x9C939458, // Small 'xbegin'.
+ 0x8003A078, // Small 'xchg'.
+ 0x800238B8, // Small 'xend'.
+ 0xAC2A14F8, // Small 'xgetbv'.
+ 0x802A0598, // Small 'xlatb'.
+ 0x800049F8, // Small 'xor'.
+ 0x804849F8, // Small 'xorpd'.
+ 0x813849F8, // Small 'xorps'.
+ 0x101584F0, // Large 'xresldtr|k'.
+ 0xA4FA4E58, // Small 'xrstor'.
+ 0x20306386, // Large 'xrstor|64'.
+ 0x10146386, // Large 'xrstor|s'.
+ 0x34F86386, // Large 'xrstor|s64'.
+ 0x805B0678, // Small 'xsave'.
+ 0x2030537B, // Large 'xsave|64'.
+ 0x865B0678, // Small 'xsavec'.
+ 0x362D537B, // Large 'xsave|c64'.
+ 0x0000837B, // Large 'xsaveopt'.
+ 0x2030837B, // Large 'xsaveopt|64'.
+ 0xA65B0678, // Small 'xsaves'.
+ 0x34F8537B, // Large 'xsave|s64'.
+ 0xAC2A1678, // Small 'xsetbv'.
+ 0x101584FB, // Large 'xsusldtr|k'.
+ 0x81499698 // Small 'xtest'.
+};
+
+const char InstDB::_instNameStringTable[] =
+ "vgf2p8affineinvqbvaeskeygenassistvbroadcastf32x464x264x4i32x2i32x4i32x8i64x2i64x"
+ "4vpbroadcastmb2w2d128i128vcvtne2ps2bf1vfmaddsub132ph213pd213ph213ps231pd231ph231"
+ "psvfmsubadd132vpmultishiftvscatterpf0dqpdqps1dpd1dps1qpd1qpsvcvtneps2bf1vextracv"
+ "extractfvgatherpf0vp2intersectsdvfnmadd132213sd213sh213ss231sd231sh231ssvfnmsub1"
+ "32vinservinsertfvpshufbitqprefetchntwt1saveprevsssha256rndtileloaddtilereleavaes"
+ "declvaesenclvcompressvcvttpd2uqqvcvttph2uvcvttps2uvcvttsd2uvcvttsh2uvcvttss2uvfi"
+ "xupimmvfmadd132vfmsub132vmaskmovdqvpcompressvpconflictvphminposuvpmadd52hluqvpsc"
+ "atterqdvpunpckhqlqdqvrndscaleclflushopcmpxchg16t0t2msg1msg2tilestorev4fnmaddssvc"
+ "vtpd2uvcvtph2psudqvcvtps2phvcvtsd2uvcvtsh2uvcvtss2uvcvtudq2vcvtuqq2vcvtusi2vfcma"
+ "ddcvfpclassvgetmanmulbvp4dpwssvpclmuvpcmpestrvpcmpistrvperm2fvpermil2vpgathervpm"
+ "acssdqvpmadcsswubswvpmaskmovpternlogbwwdlbwldqlwdvrsqrt1428pd28ps28sd28ssvshufvs"
+ "huffvzeroupxsaveopt8bfxrstorldtilecfmovdir64pvalidarmpadjurmpupdaserialisha1nexs"
+ "ha1rndssttilecftdpbf16v4fmadvaddsubvblendmvpdvcvtdq2uwvcvtqq2vcvtsi2vcvtuwvdbpsa"
+ "dvdpbf16vexpanvfcmulccphcshvgetexpvmovdqau16u32u64vmovmskvmovntvmovshdvmovsldvpa"
+ "ckssdwbvpackuswbvpblendmdvpdpbusvpdpwss2pdvpermtvpexpanvphaddubwqdqhvpmovmskvpmo"
+ "vsxbvpmovusqwvpmovzxbvpmulhrvptestnmqvreducevscalefvunpckhlpdlpsxresldtrs64xsusl"
+ "dtrcldemoclrssbscvtpifcmovnbfxsavekortestkshiftrbkunpckmonitorpfrcpipfrsqirtvrdf"
+ "sbrdgsbsspsenduisetssbssysesysexvcvtwvfmulvldmxcsvmlaundupu8vmovhvmovlhvmpsadvmr"
+ "esumvpadduvpaligngtbgtdgtqgtw2qbdbqvphsubvplzcnb2md2mq2mw2mvpopcnvpshldvqvpshrdv"
+ "whwvpsubuvrangevrcp14vroundsdvstmxcsvucomiallwbnoinwrfsbwrgsbc64blcfiblsfiendbre"
+ "nqcmbefdecsfincsfnstefrndfsincfucomppfyl2xincsspqinvlinvpcinvvpmcommmovq2pavgupf"
+ "cmpepfpnaptwrisyscsysretdpbuvaesivaligvandnvcomivfrczvhadvhsubvmclevmmcvmovavmov"
+ "uvmptvmwrivpandvpextrwvpinsvpmaxvpminvprolvprorvpsadvpsigvpslvpsllvpsravpsrlvsqr"
+ "vtes";
+
const InstDB::InstNameIndex InstDB::instNameIndex[26] = {
{ Inst::kIdAaa , Inst::kIdArpl + 1 },
@@ -3071,8 +4619,8 @@ const InstDB::InstSignature InstDB::_instSignatureTable[] = {
ROW(3, 1, 1, 2, 34 , 33 , 27 , 0 , 0 , 0 ), // {<dx>, <ax>, r16|m16|mem}
ROW(3, 1, 1, 2, 35 , 36 , 28 , 0 , 0 , 0 ), // {<edx>, <eax>, r32|m32|mem}
ROW(3, 0, 1, 2, 37 , 38 , 15 , 0 , 0 , 0 ), // {<rdx>, <rax>, r64|m64|mem}
- ROW(2, 1, 1, 0, 4 , 39 , 0 , 0 , 0 , 0 ), // {r16, r16|m16|mem|i8|i16}
- ROW(2, 1, 1, 0, 6 , 40 , 0 , 0 , 0 , 0 ), // {r32, r32|m32|mem|i8|i32}
+ ROW(2, 1, 1, 0, 4 , 39 , 0 , 0 , 0 , 0 ), // {r16, r16|m16|mem|i8|i16|u16}
+ ROW(2, 1, 1, 0, 6 , 40 , 0 , 0 , 0 , 0 ), // {r32, r32|m32|mem|i8|i32|u32}
ROW(2, 0, 1, 0, 8 , 41 , 0 , 0 , 0 , 0 ), // {r64, r64|m64|mem|i8|i32}
ROW(3, 1, 1, 0, 4 , 27 , 42 , 0 , 0 , 0 ), // {r16, r16|m16|mem, i8|i16|u16}
ROW(3, 1, 1, 0, 6 , 28 , 43 , 0 , 0 , 0 ), // {r32, r32|m32|mem, i8|i32|u32}
@@ -3589,8 +5137,8 @@ const InstDB::OpSignature InstDB::_opSignatureTable[] = {
ROW(F(RegGpd) | F(FlagImplicit), 0x01),
ROW(F(RegGpq) | F(FlagImplicit), 0x04),
ROW(F(RegGpq) | F(FlagImplicit), 0x01),
- ROW(F(RegGpw) | F(MemUnspecified) | F(Mem16) | F(ImmI8) | F(ImmI16), 0x00),
- ROW(F(RegGpd) | F(MemUnspecified) | F(Mem32) | F(ImmI8) | F(ImmI32), 0x00),
+ ROW(F(RegGpw) | F(MemUnspecified) | F(Mem16) | F(ImmI8) | F(ImmI16) | F(ImmU16), 0x00),
+ ROW(F(RegGpd) | F(MemUnspecified) | F(Mem32) | F(ImmI8) | F(ImmI32) | F(ImmU32), 0x00),
ROW(F(RegGpq) | F(MemUnspecified) | F(Mem64) | F(ImmI8) | F(ImmI32), 0x00),
ROW(F(ImmI8) | F(ImmI16) | F(ImmU16), 0x00),
ROW(F(ImmI8) | F(ImmI32) | F(ImmU32), 0x00),
@@ -3761,59 +5309,59 @@ const uint8_t InstDB::rwInfoIndexA[Inst::_kIdCount] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 0, 59, 0, 60, 0, 61, 0, 60, 0, 60, 0, 60,
0, 0, 0, 0, 0, 62, 63, 63, 63, 58, 60, 0, 0, 0, 9, 0, 0, 4, 4, 5, 6, 0, 0, 4,
4, 5, 6, 0, 0, 64, 65, 66, 66, 67, 47, 24, 36, 67, 52, 66, 66, 68, 69, 69, 70,
- 71, 71, 72, 72, 59, 59, 67, 59, 59, 71, 71, 73, 48, 52, 74, 48, 7, 7, 47, 75,
- 9, 66, 66, 75, 0, 35, 4, 4, 5, 6, 0, 76, 0, 0, 77, 0, 2, 4, 4, 78, 79, 9, 9,
- 9, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 0, 3, 80, 3, 0, 0, 0, 3, 3,
- 4, 3, 0, 0, 3, 3, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 27, 80, 80, 80, 80, 80,
- 80, 80, 80, 80, 80, 27, 80, 80, 80, 27, 27, 80, 80, 80, 3, 3, 3, 81, 3, 3, 3,
- 27, 27, 0, 0, 0, 0, 3, 3, 4, 4, 3, 3, 4, 4, 4, 4, 3, 3, 4, 4, 82, 83, 84, 24,
- 24, 24, 83, 83, 84, 24, 24, 24, 83, 4, 3, 80, 3, 3, 4, 3, 3, 0, 0, 0, 9, 0, 0,
- 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 3, 3, 3, 3, 85, 3, 3, 0, 3, 3,
- 3, 85, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 27, 86, 0, 3, 3, 4, 3, 87, 87, 4, 87, 0,
- 0, 0, 0, 0, 0, 0, 3, 88, 7, 89, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0,
- 0, 0, 0, 88, 88, 0, 0, 0, 0, 0, 0, 7, 89, 0, 0, 88, 88, 0, 0, 2, 91, 0, 0, 0,
+ 71, 71, 72, 72, 59, 59, 67, 59, 59, 71, 71, 73, 48, 52, 74, 75, 7, 7, 76, 77,
+ 9, 66, 66, 77, 0, 35, 4, 4, 5, 6, 0, 78, 0, 0, 79, 0, 2, 4, 4, 80, 81, 9, 9,
+ 9, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 0, 3, 82, 3, 0, 0, 0, 3, 3,
+ 4, 3, 0, 0, 3, 3, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 83, 27, 27, 82, 82, 82, 82, 82,
+ 82, 82, 82, 82, 82, 27, 82, 82, 82, 27, 27, 82, 82, 82, 3, 3, 3, 84, 3, 3, 3,
+ 27, 27, 0, 0, 0, 0, 3, 3, 4, 4, 3, 3, 4, 4, 4, 4, 3, 3, 4, 4, 85, 86, 87, 24,
+ 24, 24, 86, 86, 87, 24, 24, 24, 86, 4, 3, 82, 3, 3, 4, 3, 3, 0, 0, 0, 9, 0,
+ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 3, 3, 3, 3, 88, 3, 3, 0, 3, 3,
+ 3, 88, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 27, 89, 0, 3, 3, 4, 3, 90, 90, 4, 90, 0,
+ 0, 0, 0, 0, 0, 0, 3, 91, 7, 92, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 0, 0,
+ 0, 0, 0, 91, 91, 0, 0, 0, 0, 0, 0, 7, 92, 0, 0, 91, 91, 0, 0, 2, 94, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 4, 4, 4, 0, 4, 4, 0, 88, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 7, 7, 26, 89, 0, 0, 0, 0, 0, 0, 92, 0, 0, 0, 2, 4, 4, 5, 6, 0, 0, 0, 0, 0,
- 0, 0, 9, 0, 0, 0, 0, 0, 15, 0, 93, 93, 0, 94, 0, 0, 9, 9, 20, 21, 95, 95, 0, 0,
- 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 28, 97, 98, 97, 98, 96, 28, 97, 98, 97, 98,
- 99, 100, 0, 0, 0, 0, 0, 0, 20, 101, 21, 102, 102, 103, 75, 9, 0, 75, 104, 105,
- 104, 9, 104, 9, 106, 107, 103, 106, 107, 106, 107, 9, 9, 9, 103, 0, 75, 103,
- 9, 103, 9, 105, 104, 0, 28, 0, 28, 0, 108, 0, 108, 0, 0, 0, 0, 0, 33, 33, 104,
- 9, 104, 9, 106, 107, 106, 107, 9, 9, 9, 103, 9, 103, 28, 28, 108, 108, 33,
- 33, 103, 75, 9, 9, 105, 104, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 109, 109, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 4, 4, 4, 0, 4, 4, 0, 91, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 7, 7, 26, 92, 0, 0, 0, 0, 0, 0, 95, 0, 0, 0, 2, 4, 4, 5, 6, 0, 0, 0, 0, 0,
+ 0, 0, 9, 0, 0, 0, 0, 0, 15, 0, 96, 96, 0, 97, 0, 0, 9, 9, 20, 21, 98, 98, 0,
+ 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 28, 100, 101, 100, 101, 99, 28, 100, 101,
+ 100, 101, 102, 103, 0, 0, 0, 0, 0, 0, 20, 104, 21, 105, 105, 106, 77, 9, 0, 77,
+ 107, 108, 107, 9, 107, 9, 109, 110, 106, 109, 110, 109, 110, 9, 9, 9, 106,
+ 0, 77, 106, 9, 106, 9, 108, 107, 0, 28, 0, 28, 0, 111, 0, 111, 0, 0, 0, 0, 0,
+ 33, 33, 107, 9, 107, 9, 109, 110, 109, 110, 9, 9, 9, 106, 9, 106, 28, 28, 111,
+ 111, 33, 33, 106, 77, 9, 9, 108, 107, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 112, 112, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 27, 110, 60, 60, 0, 0, 0, 0,
- 0, 0, 0, 0, 60, 111, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 112, 47, 113, 112, 112, 112, 112, 112, 112, 112,
- 112, 0, 114, 114, 0, 71, 71, 115, 116, 67, 67, 67, 67, 117, 71, 118, 9, 9,
- 73, 112, 112, 49, 0, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 0, 0, 0, 0, 0,
- 0, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 120, 33, 121, 121, 28, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 102, 102, 102, 0, 0, 0, 0,
- 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 0, 0, 0, 0, 60, 60, 111, 60, 7, 7, 7, 0, 7, 0,
- 7, 7, 7, 7, 7, 7, 0, 7, 7, 81, 7, 0, 7, 0, 0, 7, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 27, 113, 60, 60,
+ 0, 0, 0, 0, 0, 0, 0, 0, 60, 114, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 115, 47, 116, 115, 115, 115, 115, 115,
+ 115, 115, 115, 0, 117, 117, 0, 71, 71, 118, 119, 67, 67, 67, 67, 120, 71, 121,
+ 9, 9, 73, 115, 115, 49, 0, 0, 0, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 122, 0, 0,
+ 0, 0, 0, 0, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 33, 124, 124, 28, 125, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 105, 105, 105, 0,
+ 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 0, 0, 0, 0, 60, 60, 114, 60, 7, 7, 7,
+ 0, 7, 0, 7, 7, 7, 7, 7, 7, 0, 7, 7, 84, 7, 0, 7, 0, 0, 7, 0, 0, 0, 0, 9, 9, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 123, 123, 124, 125, 121, 121, 121, 121, 82, 123, 126, 125, 124, 124,
- 125, 126, 125, 124, 125, 127, 128, 103, 103, 103, 127, 124, 125, 126, 125,
- 124, 125, 123, 125, 127, 128, 103, 103, 103, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 129, 67,
+ 0, 0, 0, 0, 0, 0, 0, 126, 126, 127, 128, 124, 124, 124, 124, 85, 126, 129, 128,
+ 127, 127, 128, 129, 128, 127, 128, 130, 131, 106, 106, 106, 130, 127, 128,
+ 129, 128, 127, 128, 126, 128, 130, 131, 106, 106, 106, 130, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67,
+ 132, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 9, 9, 0, 0, 109, 109, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 109, 109, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0,
- 0, 0, 67, 67, 0, 0, 0, 0, 0, 0, 0, 0, 67, 129, 0, 0, 0, 0, 0, 0, 9, 9, 9, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 119, 119, 20, 101, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 130, 131, 130, 131, 0, 132, 0, 133, 0, 0, 0, 2, 4, 4, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 112, 112, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 112, 112, 0, 0, 9, 9, 0, 0, 0,
+ 0, 0, 0, 0, 0, 67, 67, 0, 0, 0, 0, 0, 0, 0, 0, 67, 132, 0, 0, 0, 0, 0, 0, 9,
+ 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 122, 122, 20, 104, 21, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 133, 134, 133, 134, 0, 135, 0, 136, 0, 0, 0, 2, 4, 4, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
const uint8_t InstDB::rwInfoIndexB[Inst::_kIdCount] = {
@@ -3836,68 +5384,68 @@ const uint8_t InstDB::rwInfoIndexB[Inst::_kIdCount] = {
0, 24, 0, 53, 0, 54, 0, 0, 0, 0, 0, 10, 0, 10, 24, 55, 56, 55, 0, 0, 0, 0,
0, 0, 55, 57, 57, 0, 58, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 60, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 61, 0, 0, 0, 0, 62, 0, 63, 20, 64, 20, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 6,
- 5, 5, 0, 0, 0, 0, 66, 67, 0, 0, 0, 0, 68, 69, 0, 3, 3, 70, 22, 71, 72, 0, 0,
+ 0, 61, 0, 0, 61, 0, 0, 0, 0, 0, 5, 62, 0, 0, 0, 0, 63, 0, 64, 20, 65, 20, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0,
+ 6, 5, 5, 0, 0, 0, 0, 67, 68, 0, 0, 0, 0, 69, 70, 0, 3, 3, 71, 22, 72, 73, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 73, 39, 74, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, 10,
- 10, 10, 10, 10, 10, 10, 0, 0, 2, 2, 2, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 78, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 79, 79, 80, 79, 80, 80, 80, 79, 79, 81, 82, 0, 83, 0,
- 0, 0, 0, 0, 0, 84, 2, 2, 85, 86, 0, 0, 0, 11, 87, 0, 0, 4, 0, 0, 0, 88, 0, 89,
- 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,
+ 0, 0, 0, 74, 39, 75, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 10,
+ 10, 10, 10, 10, 10, 10, 0, 0, 2, 2, 2, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 79, 79, 80, 79, 80, 80, 80, 79, 79, 81, 82, 0, 83,
+ 0, 0, 0, 0, 0, 0, 84, 2, 2, 85, 86, 0, 0, 0, 11, 87, 0, 0, 4, 0, 0, 0, 88, 0,
+ 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,
89, 89, 89, 89, 89, 89, 89, 89, 89, 0, 89, 0, 32, 0, 0, 0, 5, 0, 0, 6, 0, 90,
- 4, 0, 90, 4, 5, 5, 32, 19, 91, 79, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 91, 93,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 94, 94, 94, 94, 0, 0, 0, 0, 0,
- 0, 95, 96, 0, 0, 0, 0, 0, 0, 0, 0, 56, 96, 0, 0, 0, 0, 97, 98, 97, 98, 3, 3,
- 3, 99, 100, 101, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 102, 102,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 103, 3, 104, 105, 106, 0, 0,
+ 4, 0, 90, 4, 5, 5, 32, 19, 91, 79, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 91,
+ 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 94, 94, 94, 94, 0, 0, 0, 0,
+ 0, 0, 95, 96, 0, 0, 0, 0, 0, 0, 0, 0, 56, 96, 0, 0, 0, 0, 97, 98, 97, 98, 3,
+ 3, 3, 99, 100, 101, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 102,
+ 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 103, 3, 104, 105, 106, 0, 0,
0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107,
0, 0, 0, 0, 0, 0, 0, 108, 0, 109, 0, 110, 0, 110, 0, 111, 112, 113, 114, 115,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 111, 112, 113, 0, 0, 3, 3, 3, 3, 99, 110, 101, 3, 116, 3, 55, 55, 0, 0,
- 0, 0, 117, 118, 119, 118, 119, 117, 118, 119, 118, 119, 22, 120, 121, 120, 121,
- 120, 120, 122, 123, 120, 120, 120, 124, 125, 126, 120, 120, 120, 124, 125,
- 126, 120, 120, 120, 124, 125, 126, 120, 121, 127, 127, 128, 129, 120, 120, 120,
- 120, 120, 120, 120, 120, 120, 127, 127, 120, 120, 120, 124, 130, 126, 120,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 111, 112, 113, 0, 0, 3, 3, 3, 3, 99, 110, 101, 3, 116, 3, 55, 55, 0,
+ 0, 0, 0, 117, 118, 119, 118, 119, 117, 118, 119, 118, 119, 22, 120, 121, 120,
+ 121, 120, 120, 122, 123, 120, 120, 120, 124, 125, 126, 120, 120, 120, 124, 125,
+ 126, 120, 120, 120, 124, 125, 126, 120, 121, 127, 127, 128, 129, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 127, 127, 120, 120, 120, 124, 130, 126, 120,
120, 120, 124, 130, 126, 120, 120, 120, 124, 130, 126, 120, 120, 120, 120, 120,
- 120, 120, 120, 120, 127, 127, 127, 127, 128, 129, 120, 121, 120, 120, 120, 124,
- 125, 126, 120, 120, 120, 124, 125, 126, 120, 120, 120, 124, 125, 126, 127,
- 127, 128, 129, 120, 120, 120, 124, 130, 126, 120, 120, 120, 124, 130, 126, 120,
- 120, 120, 131, 130, 132, 127, 127, 128, 129, 133, 133, 133, 77, 134, 135, 0,
- 0, 0, 0, 136, 137, 10, 10, 10, 10, 10, 10, 10, 10, 137, 138, 0, 0, 0, 139, 140,
- 141, 84, 84, 84, 139, 140, 141, 3, 3, 3, 3, 3, 3, 3, 142, 143, 144, 143, 144,
- 142, 143, 144, 143, 144, 101, 0, 53, 58, 145, 145, 3, 3, 3, 99, 100, 101,
+ 120, 120, 120, 120, 127, 127, 127, 127, 128, 129, 120, 121, 120, 120, 120,
+ 124, 125, 126, 120, 120, 120, 124, 125, 126, 120, 120, 120, 124, 125, 126, 127,
+ 127, 128, 129, 120, 120, 120, 124, 130, 126, 120, 120, 120, 124, 130, 126,
+ 120, 120, 120, 131, 130, 132, 127, 127, 128, 129, 133, 133, 133, 78, 134, 135,
+ 0, 0, 0, 0, 136, 137, 10, 10, 10, 10, 10, 10, 10, 10, 137, 138, 0, 0, 0, 139,
+ 140, 141, 84, 84, 84, 139, 140, 141, 3, 3, 3, 3, 3, 3, 3, 142, 143, 144, 143,
+ 144, 142, 143, 144, 143, 144, 101, 0, 53, 58, 145, 145, 3, 3, 3, 99, 100, 101,
0, 146, 0, 3, 3, 3, 99, 100, 101, 0, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 148, 149, 149, 150, 151, 151, 0, 0, 0, 0, 0, 0, 0, 152, 153, 0, 0, 154, 0,
0, 0, 3, 11, 146, 0, 0, 155, 147, 3, 3, 3, 99, 100, 101, 0, 11, 3, 3, 156, 156,
157, 157, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
- 3, 3, 3, 3, 3, 3, 3, 3, 102, 3, 0, 0, 0, 0, 0, 0, 3, 127, 103, 103, 3, 3, 3, 3,
- 66, 67, 3, 3, 3, 3, 68, 69, 103, 103, 103, 103, 103, 103, 116, 116, 0, 0, 0,
- 0, 116, 116, 116, 116, 116, 116, 0, 0, 120, 120, 120, 120, 158, 158, 3, 3, 3,
- 120, 3, 3, 120, 120, 127, 127, 159, 159, 159, 3, 159, 3, 120, 120, 120, 120,
- 120, 3, 0, 0, 0, 0, 70, 22, 71, 160, 137, 136, 138, 137, 0, 0, 0, 3, 0, 3, 0,
+ 3, 3, 3, 3, 3, 3, 3, 3, 102, 3, 0, 0, 0, 0, 0, 0, 3, 127, 103, 103, 3, 3, 3,
+ 3, 67, 68, 3, 3, 3, 3, 69, 70, 103, 103, 103, 103, 103, 103, 116, 116, 0, 0,
+ 0, 0, 116, 116, 116, 116, 116, 116, 0, 0, 120, 120, 120, 120, 158, 158, 3, 3,
+ 3, 120, 3, 3, 120, 120, 127, 127, 159, 159, 159, 3, 159, 3, 120, 120, 120, 120,
+ 120, 3, 0, 0, 0, 0, 71, 22, 72, 160, 137, 136, 138, 137, 0, 0, 0, 3, 0, 3, 0,
0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 3, 0, 3, 3, 0, 161, 101, 99, 100, 0, 0, 162, 162,
162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 120, 120, 3, 3, 145, 145,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 3, 3, 3, 163, 84, 84, 3, 3, 84,
- 84, 3, 3, 164, 164, 164, 164, 3, 0, 0, 0, 0, 164, 164, 164, 164, 164, 164, 3,
- 3, 120, 120, 120, 3, 164, 164, 3, 3, 120, 120, 120, 3, 3, 103, 84, 84, 84, 3,
- 3, 3, 165, 166, 165, 3, 3, 3, 165, 165, 165, 3, 3, 3, 165, 165, 166, 165, 3,
- 3, 3, 165, 3, 3, 3, 3, 3, 3, 3, 3, 120, 120, 0, 103, 103, 103, 103, 103, 103,
- 103, 103, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 139, 141, 0, 0, 139, 141, 0,
- 0, 139, 141, 0, 0, 140, 141, 84, 84, 84, 139, 140, 141, 84, 84, 84, 139, 140,
+ 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 3, 3, 3, 163, 84, 84, 3, 3,
+ 84, 84, 3, 3, 164, 164, 164, 164, 3, 0, 0, 0, 0, 164, 164, 164, 164, 164, 164,
+ 3, 3, 120, 120, 120, 3, 164, 164, 3, 3, 120, 120, 120, 3, 3, 103, 84, 84, 84,
+ 3, 3, 3, 165, 166, 165, 3, 3, 3, 167, 165, 168, 3, 3, 3, 167, 165, 166, 165,
+ 3, 3, 3, 167, 3, 3, 3, 3, 3, 3, 3, 3, 120, 120, 0, 103, 103, 103, 103, 103, 103,
+ 103, 103, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 139, 141, 0, 0, 139, 141,
+ 0, 0, 139, 141, 0, 0, 140, 141, 84, 84, 84, 139, 140, 141, 84, 84, 84, 139, 140,
141, 84, 84, 139, 141, 0, 0, 139, 141, 0, 0, 139, 141, 0, 0, 140, 141, 3, 3,
- 3, 99, 100, 101, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 3, 3, 3, 3, 3, 3,
- 0, 0, 0, 139, 140, 141, 92, 3, 3, 3, 99, 100, 101, 0, 0, 0, 0, 0, 3, 3, 3, 3,
- 3, 3, 0, 0, 0, 0, 56, 56, 167, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0,
- 168, 168, 168, 168, 169, 169, 169, 169, 169, 169, 169, 169, 167, 0, 0
+ 3, 99, 100, 101, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 3, 3, 3, 3, 3,
+ 3, 0, 0, 0, 139, 140, 141, 92, 3, 3, 3, 99, 100, 101, 0, 0, 0, 0, 0, 3, 3, 3,
+ 3, 3, 3, 0, 0, 0, 0, 56, 56, 169, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0,
+ 170, 170, 170, 170, 171, 171, 171, 171, 171, 171, 171, 171, 169, 0, 0
};
const InstDB::RWInfo InstDB::rwInfoA[] = {
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #0 [ref=1008x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #0 [ref=1007x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 1 , 0 , 0 , 0 , 0 , 0 } }, // #1 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 1 , { 2 , 3 , 0 , 0 , 0 , 0 } }, // #2 [ref=7x]
{ InstDB::RWInfo::kCategoryGeneric , 2 , { 2 , 3 , 0 , 0 , 0 , 0 } }, // #3 [ref=96x]
@@ -3944,8 +5492,8 @@ const InstDB::RWInfo InstDB::rwInfoA[] = {
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 22, 29, 0 , 0 , 0 , 0 } }, // #44 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 55, 0 , 0 , 0 , 0 , 0 } }, // #45 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 23, { 56, 40, 0 , 0 , 0 , 0 } }, // #46 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 24, { 44, 9 , 0 , 0 , 0 , 0 } }, // #47 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 25, { 35, 7 , 0 , 0 , 0 , 0 } }, // #48 [ref=3x]
+ { InstDB::RWInfo::kCategoryGeneric , 24, { 44, 9 , 0 , 0 , 0 , 0 } }, // #47 [ref=3x]
+ { InstDB::RWInfo::kCategoryGeneric , 25, { 35, 7 , 0 , 0 , 0 , 0 } }, // #48 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 26, { 48, 13, 0 , 0 , 0 , 0 } }, // #49 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 56, 40, 0 , 0 , 0 , 0 } }, // #50 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 44, 9 , 0 , 0 , 0 , 0 } }, // #51 [ref=1x]
@@ -3972,69 +5520,72 @@ const InstDB::RWInfo InstDB::rwInfoA[] = {
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 56, 5 , 0 , 0 , 0 , 0 } }, // #72 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 28, { 44, 9 , 0 , 0 , 0 , 0 } }, // #73 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 63, 20, 0 , 0 , 0 , 0 } }, // #74 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 14, { 11, 3 , 0 , 0 , 0 , 0 } }, // #75 [ref=6x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 17, 29, 0 , 0 , 0 , 0 } }, // #76 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 11, { 3 , 3 , 0 , 0 , 0 , 0 } }, // #77 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 52, 22, 0 , 0 , 0 , 0 } }, // #78 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 52, 66, 0 , 0 , 0 , 0 } }, // #79 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 4 , { 26, 7 , 0 , 0 , 0 , 0 } }, // #80 [ref=18x]
- { InstDB::RWInfo::kCategoryGeneric , 3 , { 69, 5 , 0 , 0 , 0 , 0 } }, // #81 [ref=2x]
- { InstDB::RWInfo::kCategoryVmov1_8 , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #82 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 5 , { 10, 9 , 0 , 0 , 0 , 0 } }, // #83 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 27, { 10, 13, 0 , 0 , 0 , 0 } }, // #84 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 4 , 0 , 0 , 0 , 0 , 0 } }, // #85 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 3 , { 5 , 5 , 0 , 0 , 0 , 0 } }, // #86 [ref=1x]
- { InstDB::RWInfo::kCategoryPunpcklxx , 34, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #87 [ref=3x]
- { InstDB::RWInfo::kCategoryGeneric , 10, { 2 , 71, 0 , 0 , 0 , 0 } }, // #88 [ref=8x]
- { InstDB::RWInfo::kCategoryGeneric , 5 , { 37, 9 , 0 , 0 , 0 , 0 } }, // #89 [ref=3x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 16, 50, 0 , 0 , 0 , 0 } }, // #90 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 22, 21, 0 , 0 , 0 , 0 } }, // #91 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 63, 22, 0 , 0 , 0 , 0 } }, // #92 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 8 , { 74, 3 , 0 , 0 , 0 , 0 } }, // #93 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 8 , { 11, 43, 0 , 0 , 0 , 0 } }, // #94 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 5 , { 53, 9 , 0 , 0 , 0 , 0 } }, // #95 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 13, { 80, 5 , 0 , 0 , 0 , 0 } }, // #96 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 13, { 11, 5 , 0 , 0 , 0 , 0 } }, // #97 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 39, { 74, 81, 0 , 0 , 0 , 0 } }, // #98 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 40, { 11, 7 , 0 , 0 , 0 , 0 } }, // #99 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 41, { 11, 9 , 0 , 0 , 0 , 0 } }, // #100 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 27, { 13, 13, 0 , 0 , 0 , 0 } }, // #101 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 11, { 11, 3 , 0 , 0 , 0 , 0 } }, // #102 [ref=7x]
- { InstDB::RWInfo::kCategoryVmov2_1 , 42, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #103 [ref=14x]
- { InstDB::RWInfo::kCategoryVmov1_2 , 14, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #104 [ref=7x]
- { InstDB::RWInfo::kCategoryGeneric , 14, { 10, 3 , 0 , 0 , 0 , 0 } }, // #105 [ref=3x]
- { InstDB::RWInfo::kCategoryGeneric , 42, { 11, 3 , 0 , 0 , 0 , 0 } }, // #106 [ref=5x]
- { InstDB::RWInfo::kCategoryGeneric , 43, { 11, 5 , 0 , 0 , 0 , 0 } }, // #107 [ref=5x]
- { InstDB::RWInfo::kCategoryGeneric , 27, { 11, 5 , 0 , 0 , 0 , 0 } }, // #108 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 47, { 74, 43, 0 , 0 , 0 , 0 } }, // #109 [ref=6x]
- { InstDB::RWInfo::kCategoryGeneric , 5 , { 44, 9 , 0 , 0 , 0 , 0 } }, // #110 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 18, { 2 , 3 , 0 , 0 , 0 , 0 } }, // #111 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 54, { 11, 3 , 0 , 0 , 0 , 0 } }, // #112 [ref=12x]
- { InstDB::RWInfo::kCategoryVmovddup , 34, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #113 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 12, { 35, 61, 0 , 0 , 0 , 0 } }, // #114 [ref=2x]
- { InstDB::RWInfo::kCategoryVmovmskpd , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #115 [ref=1x]
- { InstDB::RWInfo::kCategoryVmovmskps , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #116 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 55, { 35, 7 , 0 , 0 , 0 , 0 } }, // #117 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 21, { 48, 13, 0 , 0 , 0 , 0 } }, // #118 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 2 , { 3 , 3 , 0 , 0 , 0 , 0 } }, // #119 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 15, { 11, 40, 0 , 0 , 0 , 0 } }, // #120 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 11, 7 , 0 , 0 , 0 , 0 } }, // #121 [ref=6x]
- { InstDB::RWInfo::kCategoryGeneric , 27, { 11, 13, 0 , 0 , 0 , 0 } }, // #122 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 35, 3 , 0 , 0 , 0 , 0 } }, // #123 [ref=4x]
- { InstDB::RWInfo::kCategoryVmov1_4 , 58, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #124 [ref=6x]
- { InstDB::RWInfo::kCategoryVmov1_2 , 44, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #125 [ref=9x]
- { InstDB::RWInfo::kCategoryVmov1_8 , 59, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #126 [ref=3x]
- { InstDB::RWInfo::kCategoryVmov4_1 , 43, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #127 [ref=4x]
- { InstDB::RWInfo::kCategoryVmov8_1 , 60, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #128 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 18, { 11, 3 , 0 , 0 , 0 , 0 } }, // #129 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 17, { 44, 9 , 0 , 0 , 0 , 0 } }, // #130 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 32, { 35, 7 , 0 , 0 , 0 , 0 } }, // #131 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 11, { 2 , 2 , 0 , 0 , 0 , 0 } }, // #132 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 54, { 2 , 2 , 0 , 0 , 0 , 0 } } // #133 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 31, { 35, 7 , 0 , 0 , 0 , 0 } }, // #75 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 33, { 44, 9 , 0 , 0 , 0 , 0 } }, // #76 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 14, { 11, 3 , 0 , 0 , 0 , 0 } }, // #77 [ref=6x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 17, 29, 0 , 0 , 0 , 0 } }, // #78 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 11, { 3 , 3 , 0 , 0 , 0 , 0 } }, // #79 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 52, 22, 0 , 0 , 0 , 0 } }, // #80 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 52, 66, 0 , 0 , 0 , 0 } }, // #81 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 4 , { 26, 7 , 0 , 0 , 0 , 0 } }, // #82 [ref=18x]
+ { InstDB::RWInfo::kCategoryGeneric , 36, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #83 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 3 , { 69, 5 , 0 , 0 , 0 , 0 } }, // #84 [ref=2x]
+ { InstDB::RWInfo::kCategoryVmov1_8 , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #85 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 5 , { 10, 9 , 0 , 0 , 0 , 0 } }, // #86 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 27, { 10, 13, 0 , 0 , 0 , 0 } }, // #87 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 4 , 0 , 0 , 0 , 0 , 0 } }, // #88 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 3 , { 5 , 5 , 0 , 0 , 0 , 0 } }, // #89 [ref=1x]
+ { InstDB::RWInfo::kCategoryPunpcklxx , 38, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #90 [ref=3x]
+ { InstDB::RWInfo::kCategoryGeneric , 10, { 2 , 70, 0 , 0 , 0 , 0 } }, // #91 [ref=8x]
+ { InstDB::RWInfo::kCategoryGeneric , 5 , { 37, 9 , 0 , 0 , 0 , 0 } }, // #92 [ref=3x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 16, 50, 0 , 0 , 0 , 0 } }, // #93 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 22, 21, 0 , 0 , 0 , 0 } }, // #94 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 63, 22, 0 , 0 , 0 , 0 } }, // #95 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 8 , { 73, 3 , 0 , 0 , 0 , 0 } }, // #96 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 8 , { 11, 43, 0 , 0 , 0 , 0 } }, // #97 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 5 , { 53, 9 , 0 , 0 , 0 , 0 } }, // #98 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 13, { 79, 5 , 0 , 0 , 0 , 0 } }, // #99 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 13, { 11, 5 , 0 , 0 , 0 , 0 } }, // #100 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 43, { 73, 80, 0 , 0 , 0 , 0 } }, // #101 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 44, { 11, 7 , 0 , 0 , 0 , 0 } }, // #102 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 45, { 11, 9 , 0 , 0 , 0 , 0 } }, // #103 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 27, { 13, 13, 0 , 0 , 0 , 0 } }, // #104 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 11, { 11, 3 , 0 , 0 , 0 , 0 } }, // #105 [ref=7x]
+ { InstDB::RWInfo::kCategoryVmov2_1 , 46, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #106 [ref=14x]
+ { InstDB::RWInfo::kCategoryVmov1_2 , 14, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #107 [ref=7x]
+ { InstDB::RWInfo::kCategoryGeneric , 14, { 10, 3 , 0 , 0 , 0 , 0 } }, // #108 [ref=3x]
+ { InstDB::RWInfo::kCategoryGeneric , 46, { 11, 3 , 0 , 0 , 0 , 0 } }, // #109 [ref=5x]
+ { InstDB::RWInfo::kCategoryGeneric , 47, { 11, 5 , 0 , 0 , 0 , 0 } }, // #110 [ref=5x]
+ { InstDB::RWInfo::kCategoryGeneric , 27, { 11, 5 , 0 , 0 , 0 , 0 } }, // #111 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 51, { 73, 43, 0 , 0 , 0 , 0 } }, // #112 [ref=6x]
+ { InstDB::RWInfo::kCategoryGeneric , 5 , { 44, 9 , 0 , 0 , 0 , 0 } }, // #113 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 18, { 2 , 3 , 0 , 0 , 0 , 0 } }, // #114 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 58, { 11, 3 , 0 , 0 , 0 , 0 } }, // #115 [ref=12x]
+ { InstDB::RWInfo::kCategoryVmovddup , 38, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #116 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 12, { 35, 61, 0 , 0 , 0 , 0 } }, // #117 [ref=2x]
+ { InstDB::RWInfo::kCategoryVmovmskpd , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #118 [ref=1x]
+ { InstDB::RWInfo::kCategoryVmovmskps , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #119 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 59, { 35, 7 , 0 , 0 , 0 , 0 } }, // #120 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 21, { 48, 13, 0 , 0 , 0 , 0 } }, // #121 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 2 , { 3 , 3 , 0 , 0 , 0 , 0 } }, // #122 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 15, { 11, 40, 0 , 0 , 0 , 0 } }, // #123 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 11, 7 , 0 , 0 , 0 , 0 } }, // #124 [ref=6x]
+ { InstDB::RWInfo::kCategoryGeneric , 27, { 11, 13, 0 , 0 , 0 , 0 } }, // #125 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 35, 3 , 0 , 0 , 0 , 0 } }, // #126 [ref=4x]
+ { InstDB::RWInfo::kCategoryVmov1_4 , 62, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #127 [ref=6x]
+ { InstDB::RWInfo::kCategoryVmov1_2 , 48, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #128 [ref=9x]
+ { InstDB::RWInfo::kCategoryVmov1_8 , 63, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #129 [ref=3x]
+ { InstDB::RWInfo::kCategoryVmov4_1 , 47, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #130 [ref=4x]
+ { InstDB::RWInfo::kCategoryVmov8_1 , 64, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #131 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 18, { 11, 3 , 0 , 0 , 0 , 0 } }, // #132 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 17, { 44, 9 , 0 , 0 , 0 , 0 } }, // #133 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 35, { 35, 7 , 0 , 0 , 0 , 0 } }, // #134 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 11, { 2 , 2 , 0 , 0 , 0 , 0 } }, // #135 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 58, { 2 , 2 , 0 , 0 , 0 , 0 } } // #136 [ref=1x]
};
const InstDB::RWInfo InstDB::rwInfoB[] = {
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #0 [ref=775x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #0 [ref=773x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 1 , 0 , 0 , 0 , 0 , 0 } }, // #1 [ref=5x]
{ InstDB::RWInfo::kCategoryGeneric , 3 , { 10, 5 , 0 , 0 , 0 , 0 } }, // #2 [ref=7x]
{ InstDB::RWInfo::kCategoryGeneric , 6 , { 11, 3 , 3 , 0 , 0 , 0 } }, // #3 [ref=193x]
@@ -4095,24 +5646,24 @@ const InstDB::RWInfo InstDB::rwInfoB[] = {
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 5 , 5 , 59, 0 , 0 , 0 } }, // #58 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 7 , 7 , 59, 0 , 0 , 0 } }, // #59 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 19, 29, 60, 0 , 0 , 0 } }, // #60 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 6 , { 64, 42, 3 , 0 , 0 , 0 } }, // #61 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 6 , { 11, 11, 3 , 65, 0 , 0 } }, // #62 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 17, 29, 30, 0 , 0 , 0 } }, // #63 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 10, { 3 , 0 , 0 , 0 , 0 , 0 } }, // #64 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 2 , { 2 , 3 , 0 , 0 , 0 , 0 } }, // #65 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 3 , { 5 , 5 , 0 , 67, 17, 60 } }, // #66 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 3 , { 5 , 5 , 0 , 68, 17, 60 } }, // #67 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 3 , { 5 , 5 , 0 , 67, 0 , 0 } }, // #68 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 3 , { 5 , 5 , 0 , 68, 0 , 0 } }, // #69 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 31, { 56, 5 , 0 , 0 , 0 , 0 } }, // #70 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 32, { 35, 5 , 0 , 0 , 0 , 0 } }, // #71 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 33, { 48, 3 , 0 , 0 , 0 , 0 } }, // #72 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 15, { 4 , 40, 0 , 0 , 0 , 0 } }, // #73 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 4 , { 4 , 7 , 0 , 0 , 0 , 0 } }, // #74 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 27, { 2 , 13, 0 , 0 , 0 , 0 } }, // #75 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 10, { 70, 0 , 0 , 0 , 0 , 0 } }, // #76 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 4 , { 35, 7 , 0 , 0 , 0 , 0 } }, // #77 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 10, { 65, 0 , 0 , 0 , 0 , 0 } }, // #78 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 32, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #61 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 6 , { 64, 42, 3 , 0 , 0 , 0 } }, // #62 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 6 , { 11, 11, 3 , 65, 0 , 0 } }, // #63 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 17, 29, 30, 0 , 0 , 0 } }, // #64 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 10, { 3 , 0 , 0 , 0 , 0 , 0 } }, // #65 [ref=3x]
+ { InstDB::RWInfo::kCategoryGeneric , 2 , { 2 , 3 , 0 , 0 , 0 , 0 } }, // #66 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 3 , { 5 , 5 , 0 , 67, 17, 60 } }, // #67 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 3 , { 5 , 5 , 0 , 68, 17, 60 } }, // #68 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 3 , { 5 , 5 , 0 , 67, 0 , 0 } }, // #69 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 3 , { 5 , 5 , 0 , 68, 0 , 0 } }, // #70 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 34, { 56, 5 , 0 , 0 , 0 , 0 } }, // #71 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 35, { 35, 5 , 0 , 0 , 0 , 0 } }, // #72 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 37, { 48, 3 , 0 , 0 , 0 , 0 } }, // #73 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 15, { 4 , 40, 0 , 0 , 0 , 0 } }, // #74 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 4 , { 4 , 7 , 0 , 0 , 0 , 0 } }, // #75 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 27, { 2 , 13, 0 , 0 , 0 , 0 } }, // #76 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 10, { 11, 0 , 0 , 0 , 0 , 0 } }, // #77 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 4 , { 35, 7 , 0 , 0 , 0 , 0 } }, // #78 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 11, 0 , 0 , 0 , 0 , 0 } }, // #79 [ref=6x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 16, 50, 29, 0 , 0 , 0 } }, // #80 [ref=5x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 44, 0 , 0 , 0 , 0 , 0 } }, // #81 [ref=1x]
@@ -4121,64 +5672,64 @@ const InstDB::RWInfo InstDB::rwInfoB[] = {
{ InstDB::RWInfo::kCategoryGeneric , 2 , { 11, 3 , 0 , 0 , 0 , 0 } }, // #84 [ref=19x]
{ InstDB::RWInfo::kCategoryGeneric , 4 , { 36, 7 , 0 , 0 , 0 , 0 } }, // #85 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 5 , { 37, 9 , 0 , 0 , 0 , 0 } }, // #86 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 72, 0 , 0 , 0 , 0 , 0 } }, // #87 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 71, 0 , 0 , 0 , 0 , 0 } }, // #87 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 7 , 0 , 0 , 0 , 0 , 0 } }, // #88 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 31, { 73, 0 , 0 , 0 , 0 , 0 } }, // #89 [ref=30x]
- { InstDB::RWInfo::kCategoryGeneric , 11, { 2 , 3 , 71, 0 , 0 , 0 } }, // #90 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 35, { 11, 0 , 0 , 0 , 0 , 0 } }, // #91 [ref=3x]
+ { InstDB::RWInfo::kCategoryGeneric , 34, { 72, 0 , 0 , 0 , 0 , 0 } }, // #89 [ref=30x]
+ { InstDB::RWInfo::kCategoryGeneric , 11, { 2 , 3 , 70, 0 , 0 , 0 } }, // #90 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 39, { 11, 0 , 0 , 0 , 0 , 0 } }, // #91 [ref=3x]
{ InstDB::RWInfo::kCategoryGeneric , 28, { 44, 0 , 0 , 0 , 0 , 0 } }, // #92 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 16, { 74, 0 , 0 , 0 , 0 , 0 } }, // #93 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 75, 43, 43, 0 , 0 , 0 } }, // #94 [ref=5x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 74, 0 , 0 , 0 , 0 , 0 } }, // #95 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 16, { 73, 0 , 0 , 0 , 0 , 0 } }, // #93 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 74, 43, 43, 0 , 0 , 0 } }, // #94 [ref=5x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 73, 0 , 0 , 0 , 0 , 0 } }, // #95 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 9 , 60, 17, 0 , 0 , 0 } }, // #96 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 13, { 75, 76, 77, 77, 77, 5 } }, // #97 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 13, { 4 , 78, 79, 79, 79, 5 } }, // #98 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 36, { 10, 5 , 7 , 0 , 0 , 0 } }, // #99 [ref=8x]
- { InstDB::RWInfo::kCategoryGeneric , 37, { 10, 5 , 13, 0 , 0 , 0 } }, // #100 [ref=7x]
- { InstDB::RWInfo::kCategoryGeneric , 38, { 10, 5 , 9 , 0 , 0 , 0 } }, // #101 [ref=9x]
+ { InstDB::RWInfo::kCategoryGeneric , 13, { 74, 75, 76, 76, 76, 5 } }, // #97 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 13, { 4 , 77, 78, 78, 78, 5 } }, // #98 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 40, { 10, 5 , 7 , 0 , 0 , 0 } }, // #99 [ref=8x]
+ { InstDB::RWInfo::kCategoryGeneric , 41, { 10, 5 , 13, 0 , 0 , 0 } }, // #100 [ref=7x]
+ { InstDB::RWInfo::kCategoryGeneric , 42, { 10, 5 , 9 , 0 , 0 , 0 } }, // #101 [ref=9x]
{ InstDB::RWInfo::kCategoryGeneric , 6 , { 11, 3 , 3 , 3 , 0 , 0 } }, // #102 [ref=3x]
{ InstDB::RWInfo::kCategoryGeneric , 6 , { 35, 3 , 3 , 0 , 0 , 0 } }, // #103 [ref=18x]
- { InstDB::RWInfo::kCategoryGeneric , 36, { 11, 5 , 7 , 0 , 0 , 0 } }, // #104 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 37, { 35, 13, 13, 0 , 0 , 0 } }, // #105 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 38, { 11, 5 , 9 , 0 , 0 , 0 } }, // #106 [ref=1x]
- { InstDB::RWInfo::kCategoryVmov1_2 , 44, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #107 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 36, { 10, 5 , 5 , 0 , 0 , 0 } }, // #108 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 36, { 10, 82, 7 , 0 , 0 , 0 } }, // #109 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 37, { 10, 5 , 5 , 0 , 0 , 0 } }, // #110 [ref=3x]
- { InstDB::RWInfo::kCategoryGeneric , 45, { 10, 61, 3 , 0 , 0 , 0 } }, // #111 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 45, { 10, 3 , 3 , 0 , 0 , 0 } }, // #112 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 45, { 10, 82, 3 , 0 , 0 , 0 } }, // #113 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 38, { 10, 61, 9 , 0 , 0 , 0 } }, // #114 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 38, { 10, 5 , 5 , 0 , 0 , 0 } }, // #115 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 46, { 10, 5 , 5 , 0 , 0 , 0 } }, // #116 [ref=9x]
- { InstDB::RWInfo::kCategoryGeneric , 48, { 10, 81, 0 , 0 , 0 , 0 } }, // #117 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 48, { 10, 3 , 0 , 0 , 0 , 0 } }, // #118 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 49, { 80, 43, 0 , 0 , 0 , 0 } }, // #119 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 40, { 11, 5 , 7 , 0 , 0 , 0 } }, // #104 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 41, { 35, 13, 13, 0 , 0 , 0 } }, // #105 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 42, { 11, 5 , 9 , 0 , 0 , 0 } }, // #106 [ref=1x]
+ { InstDB::RWInfo::kCategoryVmov1_2 , 48, { 0 , 0 , 0 , 0 , 0 , 0 } }, // #107 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 40, { 10, 5 , 5 , 0 , 0 , 0 } }, // #108 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 40, { 10, 81, 7 , 0 , 0 , 0 } }, // #109 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 41, { 10, 5 , 5 , 0 , 0 , 0 } }, // #110 [ref=3x]
+ { InstDB::RWInfo::kCategoryGeneric , 49, { 10, 61, 3 , 0 , 0 , 0 } }, // #111 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 49, { 10, 3 , 3 , 0 , 0 , 0 } }, // #112 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 49, { 10, 81, 3 , 0 , 0 , 0 } }, // #113 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 42, { 10, 61, 9 , 0 , 0 , 0 } }, // #114 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 42, { 10, 5 , 5 , 0 , 0 , 0 } }, // #115 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 50, { 10, 5 , 5 , 0 , 0 , 0 } }, // #116 [ref=9x]
+ { InstDB::RWInfo::kCategoryGeneric , 52, { 10, 80, 0 , 0 , 0 , 0 } }, // #117 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 52, { 10, 3 , 0 , 0 , 0 , 0 } }, // #118 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 53, { 79, 43, 0 , 0 , 0 , 0 } }, // #119 [ref=4x]
{ InstDB::RWInfo::kCategoryGeneric , 6 , { 2 , 3 , 3 , 0 , 0 , 0 } }, // #120 [ref=82x]
- { InstDB::RWInfo::kCategoryGeneric , 38, { 4 , 5 , 5 , 0 , 0 , 0 } }, // #121 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 36, { 4 , 61, 7 , 0 , 0 , 0 } }, // #122 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 38, { 4 , 82, 9 , 0 , 0 , 0 } }, // #123 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 36, { 6 , 7 , 7 , 0 , 0 , 0 } }, // #124 [ref=11x]
- { InstDB::RWInfo::kCategoryGeneric , 37, { 4 , 5 , 5 , 0 , 0 , 0 } }, // #125 [ref=6x]
- { InstDB::RWInfo::kCategoryGeneric , 38, { 8 , 9 , 9 , 0 , 0 , 0 } }, // #126 [ref=11x]
- { InstDB::RWInfo::kCategoryGeneric , 50, { 11, 3 , 3 , 3 , 0 , 0 } }, // #127 [ref=15x]
- { InstDB::RWInfo::kCategoryGeneric , 51, { 35, 7 , 7 , 7 , 0 , 0 } }, // #128 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 52, { 44, 9 , 9 , 9 , 0 , 0 } }, // #129 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 37, { 4 , 5 , 13, 0 , 0 , 0 } }, // #130 [ref=6x]
- { InstDB::RWInfo::kCategoryGeneric , 36, { 26, 7 , 7 , 0 , 0 , 0 } }, // #131 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 38, { 53, 9 , 9 , 0 , 0 , 0 } }, // #132 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 42, { 4 , 5 , 5 , 0 , 0 , 0 } }, // #121 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 40, { 4 , 61, 7 , 0 , 0 , 0 } }, // #122 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 42, { 4 , 81, 9 , 0 , 0 , 0 } }, // #123 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 40, { 6 , 7 , 7 , 0 , 0 , 0 } }, // #124 [ref=11x]
+ { InstDB::RWInfo::kCategoryGeneric , 41, { 4 , 5 , 5 , 0 , 0 , 0 } }, // #125 [ref=6x]
+ { InstDB::RWInfo::kCategoryGeneric , 42, { 8 , 9 , 9 , 0 , 0 , 0 } }, // #126 [ref=11x]
+ { InstDB::RWInfo::kCategoryGeneric , 54, { 11, 3 , 3 , 3 , 0 , 0 } }, // #127 [ref=15x]
+ { InstDB::RWInfo::kCategoryGeneric , 55, { 35, 7 , 7 , 7 , 0 , 0 } }, // #128 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 56, { 44, 9 , 9 , 9 , 0 , 0 } }, // #129 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 41, { 4 , 5 , 13, 0 , 0 , 0 } }, // #130 [ref=6x]
+ { InstDB::RWInfo::kCategoryGeneric , 40, { 26, 7 , 7 , 0 , 0 , 0 } }, // #131 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 42, { 53, 9 , 9 , 0 , 0 , 0 } }, // #132 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 14, { 35, 3 , 0 , 0 , 0 , 0 } }, // #133 [ref=3x]
{ InstDB::RWInfo::kCategoryGeneric , 27, { 35, 13, 0 , 0 , 0 , 0 } }, // #134 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 5 , { 35, 9 , 0 , 0 , 0 , 0 } }, // #135 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 8 , { 2 , 3 , 2 , 0 , 0 , 0 } }, // #136 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 2 , 3 , 2 , 0 , 0 , 0 } }, // #137 [ref=4x]
{ InstDB::RWInfo::kCategoryGeneric , 18, { 4 , 3 , 4 , 0 , 0 , 0 } }, // #138 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 36, { 10, 61, 7 , 0 , 0 , 0 } }, // #139 [ref=11x]
- { InstDB::RWInfo::kCategoryGeneric , 37, { 10, 83, 13, 0 , 0 , 0 } }, // #140 [ref=7x]
- { InstDB::RWInfo::kCategoryGeneric , 38, { 10, 82, 9 , 0 , 0 , 0 } }, // #141 [ref=13x]
- { InstDB::RWInfo::kCategoryGeneric , 46, { 80, 81, 5 , 0 , 0 , 0 } }, // #142 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 46, { 11, 3 , 5 , 0 , 0 , 0 } }, // #143 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 53, { 74, 43, 81, 0 , 0 , 0 } }, // #144 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 40, { 10, 61, 7 , 0 , 0 , 0 } }, // #139 [ref=11x]
+ { InstDB::RWInfo::kCategoryGeneric , 41, { 10, 82, 13, 0 , 0 , 0 } }, // #140 [ref=7x]
+ { InstDB::RWInfo::kCategoryGeneric , 42, { 10, 81, 9 , 0 , 0 , 0 } }, // #141 [ref=13x]
+ { InstDB::RWInfo::kCategoryGeneric , 50, { 79, 80, 5 , 0 , 0 , 0 } }, // #142 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 50, { 11, 3 , 5 , 0 , 0 , 0 } }, // #143 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 57, { 73, 43, 80, 0 , 0 , 0 } }, // #144 [ref=4x]
{ InstDB::RWInfo::kCategoryVmaskmov , 0 , { 0 , 0 , 0 , 0 , 0 , 0 } }, // #145 [ref=4x]
{ InstDB::RWInfo::kCategoryGeneric , 12, { 35, 0 , 0 , 0 , 0 , 0 } }, // #146 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 22, 0 , 0 , 0 , 0 , 0 } }, // #147 [ref=2x]
@@ -4187,30 +5738,32 @@ const InstDB::RWInfo InstDB::rwInfoB[] = {
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 10, 7 , 7 , 0 , 0 , 0 } }, // #150 [ref=1x]
{ InstDB::RWInfo::kCategoryGeneric , 12, { 10, 61, 7 , 0 , 0 , 0 } }, // #151 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 0 , { 10, 61, 7 , 0 , 0 , 0 } }, // #152 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 10, 83, 13, 0 , 0 , 0 } }, // #153 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 10, 82, 9 , 0 , 0 , 0 } }, // #154 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 84, 0 , 0 , 0 , 0 , 0 } }, // #155 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 56, { 85, 86, 3 , 3 , 0 , 0 } }, // #156 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 13, { 74, 76, 77, 77, 77, 5 } }, // #157 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 53, { 80, 81, 81, 0 , 0 , 0 } }, // #158 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 10, 82, 13, 0 , 0 , 0 } }, // #153 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 10, 81, 9 , 0 , 0 , 0 } }, // #154 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 83, 0 , 0 , 0 , 0 , 0 } }, // #155 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 60, { 84, 85, 3 , 3 , 0 , 0 } }, // #156 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 13, { 73, 75, 76, 76, 76, 5 } }, // #157 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 57, { 79, 80, 80, 0 , 0 , 0 } }, // #158 [ref=2x]
{ InstDB::RWInfo::kCategoryGeneric , 22, { 11, 3 , 3 , 0 , 0 , 0 } }, // #159 [ref=4x]
{ InstDB::RWInfo::kCategoryGeneric , 7 , { 48, 5 , 0 , 0 , 0 , 0 } }, // #160 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 57, { 10, 5 , 40, 0 , 0 , 0 } }, // #161 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 46, { 10, 5 , 5 , 5 , 0 , 0 } }, // #162 [ref=12x]
- { InstDB::RWInfo::kCategoryGeneric , 61, { 10, 5 , 5 , 5 , 0 , 0 } }, // #163 [ref=1x]
- { InstDB::RWInfo::kCategoryGeneric , 62, { 10, 5 , 5 , 0 , 0 , 0 } }, // #164 [ref=12x]
- { InstDB::RWInfo::kCategoryGeneric , 22, { 11, 3 , 5 , 0 , 0 , 0 } }, // #165 [ref=9x]
- { InstDB::RWInfo::kCategoryGeneric , 63, { 11, 3 , 0 , 0 , 0 , 0 } }, // #166 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 0 , { 60, 17, 29, 0 , 0 , 0 } }, // #167 [ref=2x]
- { InstDB::RWInfo::kCategoryGeneric , 8 , { 3 , 60, 17, 0 , 0 , 0 } }, // #168 [ref=4x]
- { InstDB::RWInfo::kCategoryGeneric , 8 , { 11, 60, 17, 0 , 0 , 0 } } // #169 [ref=8x]
+ { InstDB::RWInfo::kCategoryGeneric , 61, { 10, 5 , 40, 0 , 0 , 0 } }, // #161 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 50, { 10, 5 , 5 , 5 , 0 , 0 } }, // #162 [ref=12x]
+ { InstDB::RWInfo::kCategoryGeneric , 65, { 10, 5 , 5 , 5 , 0 , 0 } }, // #163 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 66, { 10, 5 , 5 , 0 , 0 , 0 } }, // #164 [ref=12x]
+ { InstDB::RWInfo::kCategoryGeneric , 67, { 11, 3 , 5 , 0 , 0 , 0 } }, // #165 [ref=5x]
+ { InstDB::RWInfo::kCategoryGeneric , 68, { 11, 3 , 0 , 0 , 0 , 0 } }, // #166 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 69, { 11, 3 , 5 , 0 , 0 , 0 } }, // #167 [ref=3x]
+ { InstDB::RWInfo::kCategoryGeneric , 22, { 11, 3 , 5 , 0 , 0 , 0 } }, // #168 [ref=1x]
+ { InstDB::RWInfo::kCategoryGeneric , 0 , { 60, 17, 29, 0 , 0 , 0 } }, // #169 [ref=2x]
+ { InstDB::RWInfo::kCategoryGeneric , 8 , { 3 , 60, 17, 0 , 0 , 0 } }, // #170 [ref=4x]
+ { InstDB::RWInfo::kCategoryGeneric , 8 , { 11, 60, 17, 0 , 0 , 0 } } // #171 [ref=8x]
};
const InstDB::RWInfoOp InstDB::rwInfoOp[] = {
{ 0x0000000000000000u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kNone }, // #0 [ref=16519x]
{ 0x0000000000000003u, 0x0000000000000003u, 0x00, 0, { 0 }, OpRWFlags::kRW | OpRWFlags::kRegPhysId }, // #1 [ref=10x]
{ 0x0000000000000000u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRW | OpRWFlags::kZExt }, // #2 [ref=236x]
- { 0x0000000000000000u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #3 [ref=1077x]
+ { 0x0000000000000000u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #3 [ref=1078x]
{ 0x000000000000FFFFu, 0x000000000000FFFFu, 0xFF, 0, { 0 }, OpRWFlags::kRW | OpRWFlags::kZExt }, // #4 [ref=108x]
{ 0x000000000000FFFFu, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #5 [ref=348x]
{ 0x00000000000000FFu, 0x00000000000000FFu, 0xFF, 0, { 0 }, OpRWFlags::kRW }, // #6 [ref=18x]
@@ -4218,7 +5771,7 @@ const InstDB::RWInfoOp InstDB::rwInfoOp[] = {
{ 0x000000000000000Fu, 0x000000000000000Fu, 0xFF, 0, { 0 }, OpRWFlags::kRW }, // #8 [ref=18x]
{ 0x000000000000000Fu, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #9 [ref=135x]
{ 0x0000000000000000u, 0x000000000000FFFFu, 0xFF, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt }, // #10 [ref=184x]
- { 0x0000000000000000u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt }, // #11 [ref=455x]
+ { 0x0000000000000000u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt }, // #11 [ref=456x]
{ 0x0000000000000003u, 0x0000000000000003u, 0xFF, 0, { 0 }, OpRWFlags::kRW }, // #12 [ref=1x]
{ 0x0000000000000003u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #13 [ref=63x]
{ 0x000000000000FFFFu, 0x0000000000000000u, 0x00, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kRegPhysId }, // #14 [ref=4x]
@@ -4272,32 +5825,31 @@ const InstDB::RWInfoOp InstDB::rwInfoOp[] = {
{ 0x0000000000000000u, 0x000000000000FF00u, 0xFF, 0, { 0 }, OpRWFlags::kWrite }, // #62 [ref=1x]
{ 0x0000000000000000u, 0x0000000000000000u, 0x07, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt | OpRWFlags::kMemBaseRW | OpRWFlags::kMemBasePostModify | OpRWFlags::kMemPhysId }, // #63 [ref=2x]
{ 0x0000000000000000u, 0x0000000000000000u, 0x02, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kRegPhysId | OpRWFlags::kZExt }, // #64 [ref=1x]
- { 0x0000000000000000u, 0x0000000000000000u, 0x02, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kRegPhysId }, // #65 [ref=2x]
+ { 0x0000000000000000u, 0x0000000000000000u, 0x02, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kRegPhysId }, // #65 [ref=1x]
{ 0x0000000000000000u, 0x0000000000000000u, 0x06, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kMemPhysId }, // #66 [ref=1x]
{ 0x0000000000000000u, 0x000000000000000Fu, 0x01, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt | OpRWFlags::kRegPhysId }, // #67 [ref=5x]
{ 0x0000000000000000u, 0x000000000000FFFFu, 0x00, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt | OpRWFlags::kRegPhysId }, // #68 [ref=4x]
{ 0x0000000000000000u, 0x0000000000000007u, 0xFF, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt }, // #69 [ref=2x]
- { 0x0000000000000000u, 0x0000000000000000u, 0x04, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt | OpRWFlags::kRegPhysId }, // #70 [ref=1x]
- { 0x0000000000000001u, 0x0000000000000000u, 0x01, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kRegPhysId }, // #71 [ref=10x]
- { 0x0000000000000001u, 0x0000000000000000u, 0x00, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kRegPhysId }, // #72 [ref=1x]
- { 0x0000000000000000u, 0x0000000000000001u, 0xFF, 0, { 0 }, OpRWFlags::kWrite }, // #73 [ref=30x]
- { 0x0000000000000000u, 0xFFFFFFFFFFFFFFFFu, 0xFF, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt }, // #74 [ref=20x]
- { 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFF, 0, { 0 }, OpRWFlags::kRW | OpRWFlags::kZExt }, // #75 [ref=7x]
- { 0xFFFFFFFFFFFFFFFFu, 0x0000000000000000u, 0xFF, 4, { 0 }, OpRWFlags::kRead }, // #76 [ref=4x]
- { 0xFFFFFFFFFFFFFFFFu, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kConsecutive }, // #77 [ref=12x]
- { 0x000000000000FFFFu, 0x0000000000000000u, 0xFF, 4, { 0 }, OpRWFlags::kRead }, // #78 [ref=2x]
- { 0x000000000000FFFFu, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kConsecutive }, // #79 [ref=6x]
- { 0x0000000000000000u, 0x00000000FFFFFFFFu, 0xFF, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt }, // #80 [ref=10x]
- { 0x00000000FFFFFFFFu, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #81 [ref=16x]
- { 0x000000000000FFF0u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #82 [ref=18x]
- { 0x000000000000FFFCu, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #83 [ref=8x]
- { 0x0000000000000000u, 0x0000000000000000u, 0x00, 0, { 0 }, OpRWFlags::kRW | OpRWFlags::kZExt | OpRWFlags::kRegPhysId }, // #84 [ref=1x]
- { 0x0000000000000000u, 0x00000000000000FFu, 0xFF, 2, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt }, // #85 [ref=2x]
- { 0x0000000000000000u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt | OpRWFlags::kConsecutive } // #86 [ref=2x]
+ { 0x0000000000000001u, 0x0000000000000000u, 0x01, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kRegPhysId }, // #70 [ref=10x]
+ { 0x0000000000000001u, 0x0000000000000000u, 0x00, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kRegPhysId }, // #71 [ref=1x]
+ { 0x0000000000000000u, 0x0000000000000001u, 0xFF, 0, { 0 }, OpRWFlags::kWrite }, // #72 [ref=30x]
+ { 0x0000000000000000u, 0xFFFFFFFFFFFFFFFFu, 0xFF, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt }, // #73 [ref=20x]
+ { 0xFFFFFFFFFFFFFFFFu, 0xFFFFFFFFFFFFFFFFu, 0xFF, 0, { 0 }, OpRWFlags::kRW | OpRWFlags::kZExt }, // #74 [ref=7x]
+ { 0xFFFFFFFFFFFFFFFFu, 0x0000000000000000u, 0xFF, 4, { 0 }, OpRWFlags::kRead }, // #75 [ref=4x]
+ { 0xFFFFFFFFFFFFFFFFu, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kConsecutive }, // #76 [ref=12x]
+ { 0x000000000000FFFFu, 0x0000000000000000u, 0xFF, 4, { 0 }, OpRWFlags::kRead }, // #77 [ref=2x]
+ { 0x000000000000FFFFu, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead | OpRWFlags::kConsecutive }, // #78 [ref=6x]
+ { 0x0000000000000000u, 0x00000000FFFFFFFFu, 0xFF, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt }, // #79 [ref=10x]
+ { 0x00000000FFFFFFFFu, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #80 [ref=16x]
+ { 0x000000000000FFF0u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #81 [ref=18x]
+ { 0x000000000000FFFCu, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kRead }, // #82 [ref=8x]
+ { 0x0000000000000000u, 0x0000000000000000u, 0x00, 0, { 0 }, OpRWFlags::kRW | OpRWFlags::kZExt | OpRWFlags::kRegPhysId }, // #83 [ref=1x]
+ { 0x0000000000000000u, 0x00000000000000FFu, 0xFF, 2, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt }, // #84 [ref=2x]
+ { 0x0000000000000000u, 0x0000000000000000u, 0xFF, 0, { 0 }, OpRWFlags::kWrite | OpRWFlags::kZExt | OpRWFlags::kConsecutive } // #85 [ref=2x]
};
const InstDB::RWInfoRm InstDB::rwInfoRm[] = {
- { InstDB::RWInfoRm::kCategoryNone , 0x00, 0 , 0, 0 }, // #0 [ref=2000x]
+ { InstDB::RWInfoRm::kCategoryNone , 0x00, 0 , 0, 0 }, // #0 [ref=1997x]
{ InstDB::RWInfoRm::kCategoryConsistent, 0x03, 0 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #1 [ref=8x]
{ InstDB::RWInfoRm::kCategoryConsistent, 0x02, 0 , 0, 0 }, // #2 [ref=204x]
{ InstDB::RWInfoRm::kCategoryFixed , 0x02, 16, 0, 0 }, // #3 [ref=122x]
@@ -4319,48 +5871,54 @@ const InstDB::RWInfoRm InstDB::rwInfoRm[] = {
{ InstDB::RWInfoRm::kCategoryFixed , 0x00, 10, 0, 0 }, // #19 [ref=2x]
{ InstDB::RWInfoRm::kCategoryNone , 0x01, 0 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #20 [ref=5x]
{ InstDB::RWInfoRm::kCategoryFixed , 0x00, 2 , 0, 0 }, // #21 [ref=4x]
- { InstDB::RWInfoRm::kCategoryConsistent, 0x06, 0 , 0, 0 }, // #22 [ref=14x]
+ { InstDB::RWInfoRm::kCategoryConsistent, 0x06, 0 , 0, 0 }, // #22 [ref=6x]
{ InstDB::RWInfoRm::kCategoryFixed , 0x03, 1 , 0, 0 }, // #23 [ref=1x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x03, 4 , 0, 0 }, // #24 [ref=4x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x03, 8 , 0, 0 }, // #25 [ref=3x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x03, 4 , 0, 0 }, // #24 [ref=3x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x03, 8 , 0, 0 }, // #25 [ref=2x]
{ InstDB::RWInfoRm::kCategoryFixed , 0x03, 2 , 0, 0 }, // #26 [ref=2x]
{ InstDB::RWInfoRm::kCategoryFixed , 0x02, 2 , 0, 0 }, // #27 [ref=13x]
{ InstDB::RWInfoRm::kCategoryFixed , 0x00, 4 , 0, 0 }, // #28 [ref=6x]
{ InstDB::RWInfoRm::kCategoryNone , 0x03, 0 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #29 [ref=1x]
{ InstDB::RWInfoRm::kCategoryFixed , 0x03, 16, 0, 0 }, // #30 [ref=6x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x01, 1 , 0, 0 }, // #31 [ref=32x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x01, 8 , 0, 0 }, // #32 [ref=4x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x01, 2 , 0, uint32_t(CpuFeatures::X86::kSSE4_1) }, // #33 [ref=1x]
- { InstDB::RWInfoRm::kCategoryNone , 0x02, 0 , 0, 0 }, // #34 [ref=4x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x01, 2 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #35 [ref=3x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x04, 8 , 0, 0 }, // #36 [ref=35x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x04, 2 , 0, 0 }, // #37 [ref=30x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x04, 4 , 0, 0 }, // #38 [ref=42x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x00, 32, 0, 0 }, // #39 [ref=4x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x02, 8 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #40 [ref=1x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x02, 4 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #41 [ref=1x]
- { InstDB::RWInfoRm::kCategoryHalf , 0x02, 0 , 0, 0 }, // #42 [ref=19x]
- { InstDB::RWInfoRm::kCategoryQuarter , 0x02, 0 , 0, 0 }, // #43 [ref=9x]
- { InstDB::RWInfoRm::kCategoryHalf , 0x01, 0 , 0, 0 }, // #44 [ref=10x]
- { InstDB::RWInfoRm::kCategoryConsistent, 0x04, 0 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #45 [ref=6x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x04, 16, 0, 0 }, // #46 [ref=27x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x02, 64, 0, 0 }, // #47 [ref=6x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x01, 16, 0, 0 }, // #48 [ref=6x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x01, 32, 0, 0 }, // #49 [ref=4x]
- { InstDB::RWInfoRm::kCategoryConsistent, 0x0C, 0 , 0, 0 }, // #50 [ref=15x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x0C, 8 , 0, 0 }, // #51 [ref=4x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x0C, 4 , 0, 0 }, // #52 [ref=4x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x04, 32, 0, 0 }, // #53 [ref=6x]
- { InstDB::RWInfoRm::kCategoryConsistent, 0x03, 0 , 0, 0 }, // #54 [ref=13x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x03, 8 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #55 [ref=1x]
- { InstDB::RWInfoRm::kCategoryConsistent, 0x08, 0 , 0, 0 }, // #56 [ref=2x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x04, 1 , 0, 0 }, // #57 [ref=1x]
- { InstDB::RWInfoRm::kCategoryQuarter , 0x01, 0 , 0, 0 }, // #58 [ref=6x]
- { InstDB::RWInfoRm::kCategoryEighth , 0x01, 0 , 0, 0 }, // #59 [ref=3x]
- { InstDB::RWInfoRm::kCategoryEighth , 0x02, 0 , 0, 0 }, // #60 [ref=2x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x0C, 16, 0, 0 }, // #61 [ref=1x]
- { InstDB::RWInfoRm::kCategoryFixed , 0x06, 16, 0, 0 }, // #62 [ref=12x]
- { InstDB::RWInfoRm::kCategoryConsistent, 0x02, 0 , 0, uint32_t(CpuFeatures::X86::kAVX512_BW) } // #63 [ref=2x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x03, 8 , InstDB::RWInfoRm::kFlagMovssMovsd, 0 }, // #31 [ref=1x]
+ { InstDB::RWInfoRm::kCategoryNone , 0x00, 0 , InstDB::RWInfoRm::kFlagMovssMovsd, 0 }, // #32 [ref=2x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x03, 4 , InstDB::RWInfoRm::kFlagMovssMovsd, 0 }, // #33 [ref=1x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x01, 1 , 0, 0 }, // #34 [ref=32x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x01, 8 , 0, 0 }, // #35 [ref=4x]
+ { InstDB::RWInfoRm::kCategoryNone , 0x00, 0 , InstDB::RWInfoRm::kFlagPextrw, 0 }, // #36 [ref=1x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x01, 2 , InstDB::RWInfoRm::kFlagPextrw, uint32_t(CpuFeatures::X86::kSSE4_1) }, // #37 [ref=1x]
+ { InstDB::RWInfoRm::kCategoryNone , 0x02, 0 , 0, 0 }, // #38 [ref=4x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x01, 2 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #39 [ref=3x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x04, 8 , 0, 0 }, // #40 [ref=35x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x04, 2 , 0, 0 }, // #41 [ref=30x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x04, 4 , 0, 0 }, // #42 [ref=42x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x00, 32, 0, 0 }, // #43 [ref=4x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x02, 8 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #44 [ref=1x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x02, 4 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #45 [ref=1x]
+ { InstDB::RWInfoRm::kCategoryHalf , 0x02, 0 , 0, 0 }, // #46 [ref=19x]
+ { InstDB::RWInfoRm::kCategoryQuarter , 0x02, 0 , 0, 0 }, // #47 [ref=9x]
+ { InstDB::RWInfoRm::kCategoryHalf , 0x01, 0 , 0, 0 }, // #48 [ref=10x]
+ { InstDB::RWInfoRm::kCategoryConsistent, 0x04, 0 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #49 [ref=6x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x04, 16, 0, 0 }, // #50 [ref=27x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x02, 64, 0, 0 }, // #51 [ref=6x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x01, 16, 0, 0 }, // #52 [ref=6x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x01, 32, 0, 0 }, // #53 [ref=4x]
+ { InstDB::RWInfoRm::kCategoryConsistent, 0x0C, 0 , 0, 0 }, // #54 [ref=15x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x0C, 8 , 0, 0 }, // #55 [ref=4x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x0C, 4 , 0, 0 }, // #56 [ref=4x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x04, 32, 0, 0 }, // #57 [ref=6x]
+ { InstDB::RWInfoRm::kCategoryConsistent, 0x03, 0 , 0, 0 }, // #58 [ref=13x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x03, 8 , InstDB::RWInfoRm::kFlagAmbiguous, 0 }, // #59 [ref=1x]
+ { InstDB::RWInfoRm::kCategoryConsistent, 0x08, 0 , 0, 0 }, // #60 [ref=2x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x04, 1 , 0, 0 }, // #61 [ref=1x]
+ { InstDB::RWInfoRm::kCategoryQuarter , 0x01, 0 , 0, 0 }, // #62 [ref=6x]
+ { InstDB::RWInfoRm::kCategoryEighth , 0x01, 0 , 0, 0 }, // #63 [ref=3x]
+ { InstDB::RWInfoRm::kCategoryEighth , 0x02, 0 , 0, 0 }, // #64 [ref=2x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x0C, 16, 0, 0 }, // #65 [ref=1x]
+ { InstDB::RWInfoRm::kCategoryFixed , 0x06, 16, 0, 0 }, // #66 [ref=12x]
+ { InstDB::RWInfoRm::kCategoryConsistent, 0x06, 0 , InstDB::RWInfoRm::kFlagFeatureIfRMI, uint32_t(CpuFeatures::X86::kAVX512_F) }, // #67 [ref=5x]
+ { InstDB::RWInfoRm::kCategoryConsistent, 0x02, 0 , InstDB::RWInfoRm::kFlagFeatureIfRMI, uint32_t(CpuFeatures::X86::kAVX512_BW) }, // #68 [ref=2x]
+ { InstDB::RWInfoRm::kCategoryConsistent, 0x06, 0 , InstDB::RWInfoRm::kFlagFeatureIfRMI, uint32_t(CpuFeatures::X86::kAVX512_BW) } // #69 [ref=3x]
};
// ----------------------------------------------------------------------------
// ${InstRWInfoTable:End}
diff --git a/erts/emulator/asmjit/x86/x86instdb.h b/erts/emulator/asmjit/x86/x86instdb.h
index 99112891e0..c8a914f2b8 100644
--- a/erts/emulator/asmjit/x86/x86instdb.h
+++ b/erts/emulator/asmjit/x86/x86instdb.h
@@ -442,8 +442,8 @@ ASMJIT_VARAPI const CommonInfo _commonInfoTable[];
//! Instruction information.
struct InstInfo {
- //! Index to \ref _nameData.
- uint32_t _nameDataIndex : 14;
+ //! Reserved for future use.
+ uint32_t _reserved : 14;
//! Index to \ref _commonInfoTable.
uint32_t _commonInfoIndex : 10;
//! Index to \ref _additionalInfoTable.
@@ -461,7 +461,7 @@ struct InstInfo {
//! \name Accessors
//! \{
- //! Returns common information, see `CommonInfo`.
+ //! Returns common information, see \ref CommonInfo.
inline const CommonInfo& commonInfo() const noexcept { return _commonInfoTable[_commonInfoIndex]; }
//! Returns instruction flags, see \ref Flags.
diff --git a/erts/emulator/asmjit/x86/x86instdb_p.h b/erts/emulator/asmjit/x86/x86instdb_p.h
index 9a6f780ea1..4bfa0b9183 100644
--- a/erts/emulator/asmjit/x86/x86instdb_p.h
+++ b/erts/emulator/asmjit/x86/x86instdb_p.h
@@ -189,12 +189,12 @@ enum EncodingId : uint32_t {
//! Additional information table, provides CPU extensions required to execute an instruction and RW flags.
struct AdditionalInfo {
- //! Features vector.
- uint8_t _features[6];
+ //! Index to `_instFlagsTable`.
+ uint8_t _instFlagsIndex;
//! Index to `_rwFlagsTable`.
uint8_t _rwFlagsIndex;
- //! Reserved for future use.
- uint8_t _reserved;
+ //! Features vector.
+ uint8_t _features[6];
inline const uint8_t* featuresBegin() const noexcept { return _features; }
inline const uint8_t* featuresEnd() const noexcept { return _features + ASMJIT_ARRAY_SIZE(_features); }
@@ -259,7 +259,13 @@ struct RWInfoRm {
};
enum Flags : uint8_t {
- kFlagAmbiguous = 0x01
+ kFlagAmbiguous = 0x01,
+ //! Special semantics for PEXTRW - memory operand can only be used with SSE4.1 instruction and it's forbidden in MMX.
+ kFlagPextrw = 0x02,
+ //! Special semantics for MOVSS and MOVSD - doesn't zero extend the destination if the operation is a reg to reg move.
+ kFlagMovssMovsd = 0x04,
+ //! Special semantics for AVX shift instructions that do not provide reg/mem in AVX/AVX2 mode (AVX-512 is required).
+ kFlagFeatureIfRMI = 0x08
};
uint8_t category;
@@ -283,12 +289,14 @@ extern const RWInfo rwInfoB[];
extern const RWInfoOp rwInfoOp[];
extern const RWInfoRm rwInfoRm[];
extern const RWFlagsInfoTable _rwFlagsInfoTable[];
+extern const InstRWFlags _instFlagsTable[];
extern const uint32_t _mainOpcodeTable[];
extern const uint32_t _altOpcodeTable[];
#ifndef ASMJIT_NO_TEXT
-extern const char _nameData[];
+extern const uint32_t _instNameIndexTable[];
+extern const char _instNameStringTable[];
extern const InstNameIndex instNameIndex[26];
#endif // !ASMJIT_NO_TEXT
diff --git a/erts/emulator/asmjit/x86/x86operand.h b/erts/emulator/asmjit/x86/x86operand.h
index 037d4af4dd..8781be1a4f 100644
--- a/erts/emulator/asmjit/x86/x86operand.h
+++ b/erts/emulator/asmjit/x86/x86operand.h
@@ -174,24 +174,24 @@ public:
static inline bool isTmm(const Operand_& op) noexcept { return op.as<Reg>().isTmm(); }
static inline bool isRip(const Operand_& op) noexcept { return op.as<Reg>().isRip(); }
- static inline bool isGpb(const Operand_& op, uint32_t rId) noexcept { return isGpb(op) & (op.id() == rId); }
- static inline bool isGpbLo(const Operand_& op, uint32_t rId) noexcept { return isGpbLo(op) & (op.id() == rId); }
- static inline bool isGpbHi(const Operand_& op, uint32_t rId) noexcept { return isGpbHi(op) & (op.id() == rId); }
- static inline bool isGpw(const Operand_& op, uint32_t rId) noexcept { return isGpw(op) & (op.id() == rId); }
- static inline bool isGpd(const Operand_& op, uint32_t rId) noexcept { return isGpd(op) & (op.id() == rId); }
- static inline bool isGpq(const Operand_& op, uint32_t rId) noexcept { return isGpq(op) & (op.id() == rId); }
- static inline bool isXmm(const Operand_& op, uint32_t rId) noexcept { return isXmm(op) & (op.id() == rId); }
- static inline bool isYmm(const Operand_& op, uint32_t rId) noexcept { return isYmm(op) & (op.id() == rId); }
- static inline bool isZmm(const Operand_& op, uint32_t rId) noexcept { return isZmm(op) & (op.id() == rId); }
- static inline bool isMm(const Operand_& op, uint32_t rId) noexcept { return isMm(op) & (op.id() == rId); }
- static inline bool isKReg(const Operand_& op, uint32_t rId) noexcept { return isKReg(op) & (op.id() == rId); }
- static inline bool isSReg(const Operand_& op, uint32_t rId) noexcept { return isSReg(op) & (op.id() == rId); }
- static inline bool isCReg(const Operand_& op, uint32_t rId) noexcept { return isCReg(op) & (op.id() == rId); }
- static inline bool isDReg(const Operand_& op, uint32_t rId) noexcept { return isDReg(op) & (op.id() == rId); }
- static inline bool isSt(const Operand_& op, uint32_t rId) noexcept { return isSt(op) & (op.id() == rId); }
- static inline bool isBnd(const Operand_& op, uint32_t rId) noexcept { return isBnd(op) & (op.id() == rId); }
- static inline bool isTmm(const Operand_& op, uint32_t rId) noexcept { return isTmm(op) & (op.id() == rId); }
- static inline bool isRip(const Operand_& op, uint32_t rId) noexcept { return isRip(op) & (op.id() == rId); }
+ static inline bool isGpb(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isGpb(op)) & unsigned(op.id() == rId)); }
+ static inline bool isGpbLo(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isGpbLo(op)) & unsigned(op.id() == rId)); }
+ static inline bool isGpbHi(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isGpbHi(op)) & unsigned(op.id() == rId)); }
+ static inline bool isGpw(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isGpw(op)) & unsigned(op.id() == rId)); }
+ static inline bool isGpd(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isGpd(op)) & unsigned(op.id() == rId)); }
+ static inline bool isGpq(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isGpq(op)) & unsigned(op.id() == rId)); }
+ static inline bool isXmm(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isXmm(op)) & unsigned(op.id() == rId)); }
+ static inline bool isYmm(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isYmm(op)) & unsigned(op.id() == rId)); }
+ static inline bool isZmm(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isZmm(op)) & unsigned(op.id() == rId)); }
+ static inline bool isMm(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isMm(op)) & unsigned(op.id() == rId)); }
+ static inline bool isKReg(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isKReg(op)) & unsigned(op.id() == rId)); }
+ static inline bool isSReg(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isSReg(op)) & unsigned(op.id() == rId)); }
+ static inline bool isCReg(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isCReg(op)) & unsigned(op.id() == rId)); }
+ static inline bool isDReg(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isDReg(op)) & unsigned(op.id() == rId)); }
+ static inline bool isSt(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isSt(op)) & unsigned(op.id() == rId)); }
+ static inline bool isBnd(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isBnd(op)) & unsigned(op.id() == rId)); }
+ static inline bool isTmm(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isTmm(op)) & unsigned(op.id() == rId)); }
+ static inline bool isRip(const Operand_& op, uint32_t rId) noexcept { return bool(unsigned(isRip(op)) & unsigned(op.id() == rId)); }
};
//! General purpose register (X86).
@@ -790,15 +790,23 @@ public:
//! Clones the memory operand.
inline constexpr Mem clone() const noexcept { return Mem(*this); }
- //! Creates a new copy of this memory operand adjusted by `off`.
+ //! Creates a copy of this memory operand adjusted by `off`.
inline Mem cloneAdjusted(int64_t off) const noexcept {
Mem result(*this);
result.addOffset(off);
return result;
}
- inline constexpr Mem cloneBroadcasted(Broadcast b) const noexcept {
- return Mem((_signature & ~Signature{kSignatureMemBroadcastMask}) | Signature::fromValue<kSignatureMemBroadcastMask>(b), _baseId, _data[0], int32_t(_data[1]));
+ //! Creates a copy of this memory operand resized to `size`.
+ inline Mem cloneResized(uint32_t size) const noexcept {
+ Mem result(*this);
+ result.setSize(size);
+ return result;
+ }
+
+ //! Creates a copy of this memory operand with a broadcast `bcst`.
+ inline constexpr Mem cloneBroadcasted(Broadcast bcst) const noexcept {
+ return Mem((_signature & ~Signature{kSignatureMemBroadcastMask}) | Signature::fromValue<kSignatureMemBroadcastMask>(bcst), _baseId, _data[0], int32_t(_data[1]));
}
//! \}
diff --git a/erts/emulator/asmjit/x86/x86rapass.cpp b/erts/emulator/asmjit/x86/x86rapass.cpp
index 4f0325ad9f..4d8f3aa069 100644
--- a/erts/emulator/asmjit/x86/x86rapass.cpp
+++ b/erts/emulator/asmjit/x86/x86rapass.cpp
@@ -126,6 +126,12 @@ Error RACFGBuilder::onInst(InstNode* inst, InstControlFlow& cf, RAInstBuilder& i
bool hasGpbHiConstraint = false;
uint32_t singleRegOps = 0;
+ // Copy instruction RW flags to instruction builder except kMovOp, which is propagated manually later.
+ ib.addInstRWFlags(rwInfo.instFlags() & ~InstRWFlags::kMovOp);
+
+ // Mask of all operand types used by the instruction - can be used as an optimization later.
+ uint32_t opTypesMask = 0u;
+
if (opCount) {
// The mask is for all registers, but we are mostly interested in AVX-512 registers at the moment. The mask
// will be combined with all available registers of the Compiler at the end so we it never use more registers
@@ -167,6 +173,8 @@ Error RACFGBuilder::onInst(InstNode* inst, InstControlFlow& cf, RAInstBuilder& i
const Operand& op = opArray[i];
const OpRWInfo& opRwInfo = rwInfo.operand(i);
+ opTypesMask |= 1u << uint32_t(op.opType());
+
if (op.isReg()) {
// Register Operand
// ----------------
@@ -212,9 +220,11 @@ Error RACFGBuilder::onInst(InstNode* inst, InstControlFlow& cf, RAInstBuilder& i
}
}
- // Do not use RegMem flag if changing Reg to Mem requires additional CPU feature that may not be enabled.
+ // Do not use RegMem flag if changing Reg to Mem requires a CPU feature that is not available.
if (rwInfo.rmFeature() && Support::test(flags, RATiedFlags::kUseRM | RATiedFlags::kOutRM)) {
- flags &= ~(RATiedFlags::kUseRM | RATiedFlags::kOutRM);
+ if (!cc()->code()->cpuFeatures().has(rwInfo.rmFeature())) {
+ flags &= ~(RATiedFlags::kUseRM | RATiedFlags::kOutRM);
+ }
}
RegGroup group = workReg->group();
@@ -394,6 +404,24 @@ Error RACFGBuilder::onInst(InstNode* inst, InstControlFlow& cf, RAInstBuilder& i
}
}
+ // If this instruction has move semantics then check whether it could be eliminated if all virtual registers
+ // are allocated into the same register. Take into account the virtual size of the destination register as that's
+ // more important than a physical register size in this case.
+ if (rwInfo.hasInstFlag(InstRWFlags::kMovOp) && !inst->hasExtraReg() && Support::bitTest(opTypesMask, uint32_t(OperandType::kReg))) {
+ // AVX+ move instructions have 3 operand form - the first two operands must be the same to guarantee move semantics.
+ if (opCount == 2 || (opCount == 3 && opArray[0] == opArray[1])) {
+ uint32_t vIndex = Operand::virtIdToIndex(opArray[0].as<Reg>().id());
+ if (vIndex < Operand::kVirtIdCount) {
+ const VirtReg* vReg = _cc->virtRegByIndex(vIndex);
+ const OpRWInfo& opRwInfo = rwInfo.operand(0);
+
+ uint64_t remainingByteMask = vReg->workReg()->regByteMask() & ~opRwInfo.writeByteMask();
+ if (remainingByteMask == 0u || (remainingByteMask & opRwInfo.extendByteMask()) == 0)
+ ib.addInstRWFlags(InstRWFlags::kMovOp);
+ }
+ }
+ }
+
// Handle X86 constraints.
if (hasGpbHiConstraint) {
for (RATiedReg& tiedReg : ib) {
@@ -1251,6 +1279,10 @@ ASMJIT_FAVOR_SPEED Error X86RAPass::_rewrite(BaseNode* first, BaseNode* stop) no
// Rewrite virtual registers into physical registers.
if (raInst) {
+ // This data is allocated by Zone passed to `runOnFunction()`, which will be reset after the RA pass finishes.
+ // So reset this data to prevent having a dead pointer after the RA pass is complete.
+ node->resetPassData();
+
// If the instruction contains pass data (raInst) then it was a subject for register allocation and must be
// rewritten to use physical regs.
RATiedReg* tiedRegs = raInst->tiedRegs();
@@ -1274,16 +1306,25 @@ ASMJIT_FAVOR_SPEED Error X86RAPass::_rewrite(BaseNode* first, BaseNode* stop) no
}
}
+ // Transform VEX instruction to EVEX when necessary.
if (raInst->isTransformable()) {
if (maxRegId > 15) {
- // Transform VEX instruction to EVEX.
inst->setId(transformVexToEvex(inst->id()));
}
}
- // This data is allocated by Zone passed to `runOnFunction()`, which will be reset after the RA pass finishes.
- // So reset this data to prevent having a dead pointer after the RA pass is complete.
- node->resetPassData();
+ // Remove moves that do not do anything.
+ //
+ // Usually these moves are inserted during code generation and originally they used different registers. If RA
+ // allocated these into the same register such redundant mov would appear.
+ if (raInst->hasInstRWFlag(InstRWFlags::kMovOp) && !inst->hasExtraReg()) {
+ if (inst->opCount() == 2) {
+ if (inst->op(0) == inst->op(1)) {
+ cc()->removeNode(node);
+ goto Next;
+ }
+ }
+ }
if (ASMJIT_UNLIKELY(node->type() != NodeType::kInst)) {
// FuncRet terminates the flow, it must either be removed if the exit label is next to it (optimization) or
@@ -1327,6 +1368,7 @@ ASMJIT_FAVOR_SPEED Error X86RAPass::_rewrite(BaseNode* first, BaseNode* stop) no
}
}
+Next:
node = next;
}
diff --git a/erts/emulator/beam/atom.c b/erts/emulator/beam/atom.c
index ffeeb664ad..036816df2f 100644
--- a/erts/emulator/beam/atom.c
+++ b/erts/emulator/beam/atom.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -197,7 +197,6 @@ atom_alloc(Atom* tmpl)
static void
atom_free(Atom* obj)
{
- ASSERT(obj->slot.index == atom_val(am_ErtsSecretAtom));
}
static void latin1_to_utf8(byte* conv_buf, Uint buf_sz,
@@ -509,8 +508,6 @@ init_atom_table(void)
atom_tab(ix)->name = (byte*)erl_atom_names[i];
}
- /* Hide am_ErtsSecretAtom */
- hash_erase(&erts_atom_table.htable, atom_tab(atom_val(am_ErtsSecretAtom)));
}
void
diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names
index 5a5bcbceea..f0fa834d49 100644
--- a/erts/emulator/beam/atom.names
+++ b/erts/emulator/beam/atom.names
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2022. All Rights Reserved.
+# Copyright Ericsson AB 1996-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -59,9 +59,10 @@ atom nocatch
atom undefined_function
atom undefined_lambda
-# Secret internal atom that can never be found by string lookup
-# and should never leak out to be seen by the user.
-atom ErtsSecretAtom='3RT$'
+#
+# Other commonly used atoms.
+#
+atom nil no none
# All other atoms. Try to keep the order alphabetic.
#
@@ -160,7 +161,9 @@ atom busy_port
atom call
atom call_count
atom call_error_handler
+atom call_memory
atom call_time
+atom call_trace_return
atom caller
atom caller_line
atom capture
@@ -258,6 +261,7 @@ atom enable_trace
atom enabled
atom endian
atom env
+atom ensure_at_least ensure_exactly
atom eof
atom eol
atom Eq='=:='
@@ -326,6 +330,7 @@ atom get_all_trap
atom get_internal_state_blocked
atom get_seq_token
atom get_size
+atom get_tail
atom get_tcw
atom gather_gc_info_result
atom gather_io_bytes
@@ -356,6 +361,7 @@ atom ignore
atom in
atom in_exiting
atom inactive
+atom include_shared_binaries
atom incomplete
atom inconsistent
atom index
@@ -600,10 +606,10 @@ atom reset_seq_trace
atom restart
atom resume
atom return_from
-atom return_time_trace
atom return_to
atom return_to_trace
atom return_trace
+atom reuse
atom run_process
atom run_queue
atom run_queue_lengths
@@ -646,6 +652,7 @@ atom set_tcw_fake
atom short
atom shutdown
atom sighup
+atom signed
atom sigterm
atom sigusr1
atom sigusr2
@@ -660,6 +667,7 @@ atom sigtstp
atom sigquit
atom silent
atom size
+atom skip
atom spawn_executable
atom spawn_driver
atom spawn_init
diff --git a/erts/emulator/beam/beam_bif_load.c b/erts/emulator/beam/beam_bif_load.c
index 853ae05d3f..148a848cf5 100644
--- a/erts/emulator/beam/beam_bif_load.c
+++ b/erts/emulator/beam/beam_bif_load.c
@@ -57,7 +57,7 @@ static struct {
ErlFunEntry **funs;
ErlFunEntry *def_funs[10];
Uint fe_size;
- Uint fe_ix;
+ Uint fe_count;
struct erl_module_instance saved_old;
} purge_state;
@@ -88,7 +88,7 @@ init_purge_state(void)
purge_state.funs = &purge_state.def_funs[0];
purge_state.fe_size = sizeof(purge_state.def_funs) / sizeof(purge_state.def_funs[0]);
- purge_state.fe_ix = 0;
+ purge_state.fe_count = 0;
purge_state.saved_old.code_hdr = 0;
}
@@ -302,7 +302,8 @@ struct m {
Eterm exception;
};
-static struct /* Protected by code_write_permission */
+
+static struct /* Protected by code loading permission */
{
Process* stager;
ErtsCodeBarrier barrier;
@@ -320,7 +321,7 @@ static void commit_code_ix(void *null)
committer_state.stager = NULL;
#endif
- erts_release_code_write_permission();
+ erts_release_code_load_permission();
erts_proc_lock(p, ERTS_PROC_LOCK_STATUS);
if (!ERTS_PROC_IS_EXITING(p)) {
@@ -360,8 +361,9 @@ finish_loading_1(BIF_ALIST_1)
int is_blocking = 0;
int do_commit = 0;
- if (!erts_try_seize_code_write_permission(BIF_P)) {
- ERTS_BIF_YIELD1(BIF_TRAP_EXPORT(BIF_finish_loading_1), BIF_P, BIF_ARG_1);
+ if (!erts_try_seize_code_load_permission(BIF_P)) {
+ ERTS_BIF_YIELD1(BIF_TRAP_EXPORT(BIF_finish_loading_1),
+ BIF_P, BIF_ARG_1);
}
/*
@@ -376,11 +378,11 @@ finish_loading_1(BIF_ALIST_1)
n = erts_list_length(BIF_ARG_1);
if (n < 0) {
badarg:
- if (p) {
- erts_free(ERTS_ALC_T_LOADER_TMP, p);
- }
- erts_release_code_write_permission();
- BIF_ERROR(BIF_P, BADARG);
+ if (p) {
+ erts_free(ERTS_ALC_T_LOADER_TMP, p);
+ }
+ erts_release_code_load_permission();
+ BIF_ERROR(BIF_P, BADARG);
}
p = erts_alloc(ERTS_ALC_T_LOADER_TMP, n*sizeof(struct m));
@@ -412,13 +414,13 @@ finish_loading_1(BIF_ALIST_1)
*/
if (n > 1) {
- for (i = 0; i < n; i++) {
- if (erts_has_code_on_load(p[i].code) == am_true) {
- erts_free(ERTS_ALC_T_LOADER_TMP, p);
- erts_release_code_write_permission();
- BIF_ERROR(BIF_P, SYSTEM_LIMIT);
- }
- }
+ for (i = 0; i < n; i++) {
+ if (erts_has_code_on_load(p[i].code) == am_true) {
+ erts_free(ERTS_ALC_T_LOADER_TMP, p);
+ erts_release_code_load_permission();
+ BIF_ERROR(BIF_P, SYSTEM_LIMIT);
+ }
+ }
}
/*
@@ -546,7 +548,7 @@ staging_epilogue(Process* c_p, int commit, Eterm res, int is_blocking,
if (is_blocking) {
erts_thr_progress_unblock();
}
- erts_release_code_write_permission();
+ erts_release_code_load_permission();
return res;
}
else {
@@ -704,7 +706,7 @@ BIF_RETTYPE delete_module_1(BIF_ALIST_1)
BIF_ERROR(BIF_P, BADARG);
}
- if (!erts_try_seize_code_write_permission(BIF_P)) {
+ if (!erts_try_seize_code_load_permission(BIF_P)) {
ERTS_BIF_YIELD1(BIF_TRAP_EXPORT(BIF_delete_module_1), BIF_P, BIF_ARG_1);
}
@@ -841,7 +843,7 @@ BIF_RETTYPE finish_after_on_load_2(BIF_ALIST_2)
BIF_ERROR(BIF_P, BADARG);
}
- if (!erts_try_seize_code_write_permission(BIF_P)) {
+ if (!erts_try_seize_code_load_permission(BIF_P)) {
ERTS_BIF_YIELD2(BIF_TRAP_EXPORT(BIF_finish_after_on_load_2),
BIF_P, BIF_ARG_1, BIF_ARG_2);
}
@@ -852,7 +854,7 @@ BIF_RETTYPE finish_after_on_load_2(BIF_ALIST_2)
if (!modp || !modp->on_load || !(modp->on_load)->code_hdr
|| !((modp->on_load)->code_hdr)->on_load) {
- erts_release_code_write_permission();
+ erts_release_code_load_permission();
BIF_ERROR(BIF_P, BADARG);
}
@@ -917,10 +919,13 @@ BIF_RETTYPE finish_after_on_load_2(BIF_ALIST_2)
{
BeamCodeHeader *code_hdr_rw;
+ erts_unseal_module(&modp->curr);
+
code_hdr_rw = erts_writable_code_ptr(&modp->curr,
modp->curr.code_hdr);
-
code_hdr_rw->on_load = NULL;
+
+ erts_seal_module(&modp->curr);
}
mods[0].modp = modp;
@@ -949,7 +954,7 @@ BIF_RETTYPE finish_after_on_load_2(BIF_ALIST_2)
ep->trampoline.not_loaded.deferred = 0;
}
}
- erts_release_code_write_permission();
+ erts_release_code_load_permission();
BIF_RET(am_true);
}
@@ -1755,19 +1760,19 @@ void
erts_purge_state_add_fun(ErlFunEntry *fe)
{
ASSERT(is_value(purge_state.module));
- if (purge_state.fe_ix >= purge_state.fe_size) {
+ if (purge_state.fe_count >= purge_state.fe_size) {
ErlFunEntry **funs;
purge_state.fe_size += 100;
funs = erts_alloc(ERTS_ALC_T_PURGE_DATA,
sizeof(ErlFunEntry *)*purge_state.fe_size);
sys_memcpy((void *) funs,
(void *) purge_state.funs,
- purge_state.fe_ix*sizeof(ErlFunEntry *));
+ purge_state.fe_count*sizeof(ErlFunEntry *));
if (purge_state.funs != &purge_state.def_funs[0])
erts_free(ERTS_ALC_T_PURGE_DATA, purge_state.funs);
purge_state.funs = funs;
}
- purge_state.funs[purge_state.fe_ix++] = fe;
+ purge_state.funs[purge_state.fe_count++] = fe;
}
Export *
@@ -1850,7 +1855,7 @@ finalize_purge_operation(Process *c_p, int succeded)
purge_state.fe_size = sizeof(purge_state.def_funs);
purge_state.fe_size /= sizeof(purge_state.def_funs[0]);
}
- purge_state.fe_ix = 0;
+ purge_state.fe_count = 0;
}
@@ -1868,7 +1873,12 @@ resume_purger(void *unused)
static void
finalize_purge_abort(void *unused)
{
- erts_fun_purge_abort_finalize(purge_state.funs, purge_state.fe_ix);
+ /* We're not supposed to land here if we don't have any funs to abort
+ * purging for. */
+ ASSERT(purge_state.fe_count > 0);
+
+ erts_fun_purge_abort_finalize(purge_state.funs, purge_state.fe_count);
+ erts_release_code_stage_permission();
finalize_purge_operation(NULL, 0);
@@ -1878,240 +1888,258 @@ finalize_purge_abort(void *unused)
BIF_RETTYPE erts_internal_purge_module_2(BIF_ALIST_2)
{
- if (BIF_P != erts_code_purger)
- BIF_ERROR(BIF_P, EXC_NOTSUP);
+ if (BIF_P != erts_code_purger) {
+ BIF_ERROR(BIF_P, EXC_NOTSUP);
+ }
- if (is_not_atom(BIF_ARG_1))
- BIF_ERROR(BIF_P, BADARG);
+ if (is_not_atom(BIF_ARG_1)) {
+ goto raise_badarg;
+ }
switch (BIF_ARG_2) {
case am_prepare:
case am_prepare_on_load: {
- /*
- * Prepare for purge by marking all fun
- * entries referring to the code to purge
- * with "pending purge" markers.
- */
- ErtsCodeIndex code_ix;
- Module* modp;
- Eterm res;
-
- if (is_value(purge_state.module))
- BIF_ERROR(BIF_P, BADARG);
+ /* Prepare for purge by marking all fun entries referring to the code
+ * to purge with "pending purge" markers. */
+ ErtsCodeIndex code_ix;
+ Module* modp;
+ Eterm res;
+
+ if (is_value(purge_state.module)) {
+ goto raise_badarg;
+ }
- code_ix = erts_active_code_ix();
+ /* Fun purging requires that we don't stage new code while any purge
+ * markers are alive, lest we kill them by reloading a new module on
+ * top of an old instance of the same module. */
+ if (!erts_try_seize_code_stage_permission(BIF_P)) {
+ ERTS_BIF_YIELD2(BIF_TRAP_EXPORT(BIF_erts_internal_purge_module_2),
+ BIF_P, BIF_ARG_1, BIF_ARG_2);
+ }
- /*
- * Correct module?
- */
- modp = erts_get_module(BIF_ARG_1, code_ix);
- if (!modp)
- res = am_false;
- else {
- /*
- * Any code to purge?
- */
+ code_ix = erts_active_code_ix();
- if (BIF_ARG_2 == am_prepare_on_load) {
+ /* Correct module? */
+ modp = erts_get_module(BIF_ARG_1, code_ix);
+ if (!modp) {
+ res = am_false;
+ } else {
+ /* Any code to purge? */
+ if (BIF_ARG_2 == am_prepare_on_load) {
erts_rwlock_old_code(code_ix);
- } else {
- erts_rlock_old_code(code_ix);
- }
+ } else {
+ erts_rlock_old_code(code_ix);
+ }
- if (BIF_ARG_2 == am_prepare_on_load) {
- ASSERT(modp->on_load);
- ASSERT(modp->on_load->code_hdr);
- purge_state.saved_old = modp->old;
- modp->old = *modp->on_load;
- erts_free(ERTS_ALC_T_PREPARED_CODE, (void *) modp->on_load);
- modp->on_load = 0;
- }
+ if (BIF_ARG_2 == am_prepare_on_load) {
+ ASSERT(modp->on_load);
+ ASSERT(modp->on_load->code_hdr);
+ purge_state.saved_old = modp->old;
+ modp->old = *modp->on_load;
+ erts_free(ERTS_ALC_T_PREPARED_CODE, (void *) modp->on_load);
+ modp->on_load = 0;
+ }
- if (!modp->old.code_hdr)
- res = am_false;
- else {
- erts_mtx_lock(&purge_state.mtx);
- purge_state.module = BIF_ARG_1;
- erts_mtx_unlock(&purge_state.mtx);
- res = am_true;
- erts_fun_purge_prepare(&modp->old);
- }
+ if (!modp->old.code_hdr) {
+ res = am_false;
+ } else {
+ erts_mtx_lock(&purge_state.mtx);
+ purge_state.module = BIF_ARG_1;
+ erts_mtx_unlock(&purge_state.mtx);
+
+ /* Because fun calls always land in the latest instance, there
+ * is no need to set up purge markers if there's current code
+ * for this module. */
+ if (!modp->curr.code_hdr) {
+ /* Set up "pending purge" markers for the funs in this
+ * module. Processes trying to call these funs will be
+ * suspended _before_ calling them, which will then either
+ * crash or succeed when resumed after the purge finishes
+ * or is aborted.
+ *
+ * This guarantees that we won't get any more direct
+ * references into the code while checking for such
+ * funs. */
+ erts_fun_purge_prepare(&modp->old);
+ }
+
+ res = am_true;
+ }
if (BIF_ARG_2 == am_prepare_on_load) {
erts_rwunlock_old_code(code_ix);
- } else {
+ } else {
erts_runlock_old_code(code_ix);
- }
- }
-
- if (res != am_true)
- BIF_RET(res);
- else {
- /*
- * We'll be resumed when all schedulers are guaranteed
- * to see the "pending purge" markers that we've made on
- * all fun entries of the code that we are about to purge.
- * Processes trying to call these funs will be suspended
- * before calling the funs. That is we are guaranteed not
- * to get any more direct references into the code while
- * checking for such references...
- */
- erts_schedule_thr_prgr_later_op(resume_purger,
- NULL,
- &purger_lop_data);
- erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL);
- ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
- }
- }
-
- case am_abort: {
- /*
- * Soft purge that detected direct references into the code
- * we set out to purge. Abort the purge.
- */
+ }
+ }
- if (purge_state.module != BIF_ARG_1)
- BIF_ERROR(BIF_P, BADARG);
+ if (res == am_true) {
+ if (purge_state.fe_count == 0) {
+ /* No funs to purge, so we can safely release stage permission
+ * and allow code to be loaded while checking process code. */
+ erts_release_code_stage_permission();
+ }
- erts_fun_purge_abort_prepare(purge_state.funs, purge_state.fe_ix);
+ /* Resume ourselves when all schedulers are guaranteed to either
+ * call the newest instance of the module, or see the "pending
+ * purge" markers that we set on all fun entries related to the
+ * code we're about to purge. */
+ erts_schedule_thr_prgr_later_op(resume_purger,
+ NULL,
+ &purger_lop_data);
+ erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL);
+ ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
+ }
- /*
- * We need to restore the code addresses of the funs in
- * two stages in order to ensure that we do not get any
- * stale suspended processes due to the purge abort.
- * Restore address pointer (erts_fun_purge_abort_prepare);
- * wait for thread progress; clear pending purge address
- * pointer (erts_fun_purge_abort_finalize), and then
- * resume processes that got suspended
- * (finalize_purge_operation).
- */
- erts_schedule_thr_prgr_later_op(finalize_purge_abort,
- NULL,
- &purger_lop_data);
- erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL);
- ERTS_BIF_YIELD_RETURN(BIF_P, am_false);
+ erts_release_code_stage_permission();
+ BIF_RET(res);
}
- case am_complete: {
- ErtsCodeIndex code_ix;
- Module* modp;
- int is_blocking = 0;
- Eterm ret;
- ErtsLiteralArea *literals = NULL;
-
+ case am_abort: {
+ /* Soft purge that detected direct references into the code we set out
+ * to purge. Abort the purge. */
+ if (purge_state.module != BIF_ARG_1) {
+ goto raise_badarg;
+ }
- /*
- * We have no direct references into the code.
- * Complete to purge.
- */
+ if (purge_state.fe_count > 0) {
+ erts_fun_purge_abort_prepare(purge_state.funs,
+ purge_state.fe_count);
+
+ /* We need to restore the code addresses of the funs in two stages
+ * to ensure that we do not get any stale suspended processes due
+ * to the purge abort.
+ *
+ * Restore address pointer (erts_fun_purge_abort_prepare); wait for
+ * thread progress; clear pending purge address pointer
+ * (erts_fun_purge_abort_finalize), and then resume processes that
+ * got suspended (finalize_purge_operation). */
+ erts_schedule_thr_prgr_later_op(finalize_purge_abort,
+ NULL,
+ &purger_lop_data);
+ erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL);
+ ERTS_BIF_YIELD_RETURN(BIF_P, am_false);
+ }
- if (purge_state.module != BIF_ARG_1)
- BIF_ERROR(BIF_P, BADARG);
+ /* No funs to restore, just clean up and return. */
+ finalize_purge_operation(NULL, 0);
+ BIF_RET(am_false);
+ }
- if (!erts_try_seize_code_write_permission(BIF_P)) {
- ERTS_BIF_YIELD2(BIF_TRAP_EXPORT(BIF_erts_internal_purge_module_2),
- BIF_P, BIF_ARG_1, BIF_ARG_2);
- }
+ case am_complete: {
+ ErtsCodeIndex code_ix;
+ Module* modp;
+ int is_blocking = 0;
+ Eterm ret;
+ ErtsLiteralArea *literals = NULL;
+
+ /* We have no direct references into the code. Go ahead with the
+ * purge. */
+ if (purge_state.module != BIF_ARG_1) {
+ goto raise_badarg;
+ }
- code_ix = erts_active_code_ix();
+ if (!erts_try_seize_code_mod_permission(BIF_P)) {
+ ERTS_BIF_YIELD2(BIF_TRAP_EXPORT(BIF_erts_internal_purge_module_2),
+ BIF_P, BIF_ARG_1, BIF_ARG_2);
+ }
- /*
- * Correct module?
- */
+ code_ix = erts_active_code_ix();
- if ((modp = erts_get_module(BIF_ARG_1, code_ix)) == NULL) {
- ERTS_BIF_PREP_RET(ret, am_false);
- }
- else {
+ /* Correct module? */
+ if ((modp = erts_get_module(BIF_ARG_1, code_ix)) == NULL) {
+ ERTS_BIF_PREP_RET(ret, am_false);
+ } else {
+ erts_rwlock_old_code(code_ix);
- erts_rwlock_old_code(code_ix);
+ /* Any code to purge? */
+ if (!modp->old.code_hdr) {
+ ERTS_BIF_PREP_RET(ret, am_false);
+ } else {
+ literals = (modp->old.code_hdr)->literal_area;
- /*
- * Any code to purge?
- */
- if (!modp->old.code_hdr) {
- ERTS_BIF_PREP_RET(ret, am_false);
- }
- else {
- /*
- * Unload any NIF library
- */
+ /* Unload any NIF library. */
if (modp->old.nif) {
- erts_unload_nif(modp->old.nif);
- modp->old.nif = NULL;
+ erts_unload_nif(modp->old.nif);
+ modp->old.nif = NULL;
}
- /*
- * Remove the old code.
- */
- ASSERT(erts_total_code_size >= modp->old.code_length);
- erts_total_code_size -= modp->old.code_length;
- erts_fun_purge_complete(purge_state.funs, purge_state.fe_ix);
+ /* Remove the old code. */
+ ASSERT(erts_total_code_size >= modp->old.code_length);
+ erts_total_code_size -= modp->old.code_length;
+
+ if (purge_state.fe_count > 0) {
+ erts_fun_purge_complete(purge_state.funs,
+ purge_state.fe_count);
+ }
beam_catches_delmod(modp->old.catches,
modp->old.code_hdr,
modp->old.code_length,
code_ix);
- {
- BeamCodeHeader *code_hdr_rw;
-
- code_hdr_rw = erts_writable_code_ptr(&modp->old,
- modp->old.code_hdr);
-
- literals = code_hdr_rw->literal_area;
- code_hdr_rw->literal_area = NULL;
- }
-
- erts_remove_from_ranges(modp->old.code_hdr);
+ erts_remove_from_ranges(modp->old.code_hdr);
if (modp->old.code_hdr->are_nifs) {
erts_free(ERTS_ALC_T_PREPARED_CODE,
modp->old.code_hdr->are_nifs);
}
+
#ifndef BEAMASM
erts_free(ERTS_ALC_T_CODE, (void *) modp->old.code_hdr);
#else
-# ifdef ADDRESS_SANITIZER
+# ifdef ADDRESS_SANITIZER
__lsan_unregister_root_region(modp->old.code_hdr,
modp->old.code_length);
-# endif
- beamasm_purge_module(modp->old.native_module_exec,
- modp->old.native_module_rw);
+# endif
+ beamasm_purge_module(modp->old.executable_region,
+ modp->old.writable_region,
+ modp->old.code_length);
#endif
- modp->old.code_hdr = NULL;
- modp->old.code_length = 0;
- modp->old.catches = BEAM_CATCHES_NIL;
- ERTS_BIF_PREP_RET(ret, am_true);
- }
+ modp->old.code_hdr = NULL;
+ modp->old.code_length = 0;
+ modp->old.catches = BEAM_CATCHES_NIL;
+ ERTS_BIF_PREP_RET(ret, am_true);
+ }
- if (purge_state.saved_old.code_hdr) {
- modp->old = purge_state.saved_old;
- purge_state.saved_old.code_hdr = 0;
- }
- erts_rwunlock_old_code(code_ix);
- }
- if (is_blocking) {
- erts_thr_progress_unblock();
- erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
- }
+ if (purge_state.saved_old.code_hdr) {
+ modp->old = purge_state.saved_old;
+ purge_state.saved_old.code_hdr = 0;
+ }
- erts_release_code_write_permission();
+ erts_rwunlock_old_code(code_ix);
+ }
- finalize_purge_operation(BIF_P, ret == am_true);
+ if (is_blocking) {
+ erts_thr_progress_unblock();
+ erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
+ }
+
+ erts_release_code_mod_permission();
+
+ if (purge_state.fe_count > 0) {
+ erts_release_code_stage_permission();
+ }
- if (literals) {
+ finalize_purge_operation(BIF_P, ret == am_true);
+
+ if (literals) {
erts_queue_release_literals(BIF_P, literals);
- }
+ }
- return ret;
+ return ret;
}
default:
- BIF_ERROR(BIF_P, BADARG);
+ raise_badarg:
+ if (purge_state.fe_count > 0) {
+ ASSERT(is_value(purge_state.module));
+ erts_release_code_stage_permission();
+ }
+ BIF_ERROR(BIF_P, BADARG);
}
}
diff --git a/erts/emulator/beam/beam_bp.c b/erts/emulator/beam/beam_bp.c
index 7f105ce7c4..559a417128 100644
--- a/erts/emulator/beam/beam_bp.c
+++ b/erts/emulator/beam/beam_bp.c
@@ -58,8 +58,10 @@
#define ERTS_BPF_TIME_TRACE 0x20
#define ERTS_BPF_TIME_TRACE_ACTIVE 0x40
#define ERTS_BPF_GLOBAL_TRACE 0x80
+#define ERTS_BPF_MEM_TRACE 0x100
+#define ERTS_BPF_MEM_TRACE_ACTIVE 0x200
-#define ERTS_BPF_ALL 0xFF
+#define ERTS_BPF_ALL 0x3FF
erts_atomic32_t erts_active_bp_index;
erts_atomic32_t erts_staging_bp_index;
@@ -103,6 +105,12 @@ release_bp_sched_ix(Uint32 ix)
/*
** Helpers
*/
+const ErtsCodeInfo* erts_trace_call(Process* c_p,
+ const ErtsCodeInfo *ci,
+ BpDataAccumulator accum,
+ int psd_ix,
+ BpDataCallTrace* bdt);
+
static ErtsTracer do_call_trace(Process* c_p, ErtsCodeInfo *info, Eterm* reg,
int local, Binary* ms, ErtsTracer tracer);
static void set_break(BpFunctions* f, Binary *match_spec, Uint break_flags,
@@ -116,28 +124,30 @@ static void set_function_break(ErtsCodeInfo *ci,
static void clear_break(BpFunctions* f, Uint break_flags);
static int clear_function_break(const ErtsCodeInfo *ci, Uint break_flags);
-static BpDataTime* get_time_break(const ErtsCodeInfo *ci);
+static BpDataCallTrace* get_time_break(const ErtsCodeInfo *ci);
+static BpDataCallTrace* get_memory_break(const ErtsCodeInfo *ci);
static GenericBpData* check_break(const ErtsCodeInfo *ci, Uint break_flags);
static void bp_meta_unref(BpMetaTracer *bmt);
static void bp_count_unref(BpCount *bcp);
-static void bp_time_unref(BpDataTime *bdt);
-static void consolidate_bp_data(Module *modp, ErtsCodeInfo *ci, int local);
+static void bp_calltrace_unref(BpDataCallTrace *bdt);
+static void consolidate_bp_data(struct erl_module_instance *mi,
+ ErtsCodeInfo *ci, int local);
static void uninstall_breakpoint(ErtsCodeInfo *ci_rw,
const ErtsCodeInfo *ci_exec);
/* bp_hash */
-#define BP_TIME_ADD(pi0, pi1) \
- do { \
- (pi0)->count += (pi1)->count; \
- (pi0)->time += (pi1)->time; \
+#define BP_ACCUMULATE(pi0, pi1) \
+ do { \
+ (pi0)->count += (pi1)->count; \
+ (pi0)->accumulator += (pi1)->accumulator; \
} while(0)
-static void bp_hash_init(bp_time_hash_t *hash, Uint n);
-static void bp_hash_rehash(bp_time_hash_t *hash, Uint n);
-static ERTS_INLINE bp_data_time_item_t * bp_hash_get(bp_time_hash_t *hash, bp_data_time_item_t *sitem);
-static ERTS_INLINE bp_data_time_item_t * bp_hash_put(bp_time_hash_t *hash, bp_data_time_item_t *sitem);
-static void bp_hash_delete(bp_time_hash_t *hash);
+static void bp_hash_init(bp_trace_hash_t *hash, Uint n);
+static void bp_hash_rehash(bp_trace_hash_t *hash, Uint n);
+static ERTS_INLINE bp_data_trace_item_t * bp_hash_get(bp_trace_hash_t *hash, bp_data_trace_item_t *sitem);
+static ERTS_INLINE bp_data_trace_item_t * bp_hash_put(bp_trace_hash_t *hash, bp_data_trace_item_t *sitem);
+static void bp_hash_delete(bp_trace_hash_t *hash);
/* *************************************************************************
** External interfaces
@@ -188,37 +198,30 @@ erts_bp_match_functions(BpFunctions* f, ErtsCodeMFA *mfa, int specified)
}
}
- for (fi = 0; fi < num_functions; fi++) {
- const ErtsCodeInfo* ci_exec;
- ErtsCodeInfo* ci_rw;
- void *w_ptr;
-
- ci_exec = code_hdr->functions[fi];
- w_ptr = erts_writable_code_ptr(&module[current]->curr, ci_exec);
- ci_rw = (ErtsCodeInfo*)w_ptr;
+ for (fi = 0; fi < num_functions; fi++) {
+ const ErtsCodeInfo* ci = code_hdr->functions[fi];
#ifndef BEAMASM
- ASSERT(BeamIsOpCode(ci_rw->u.op, op_i_func_info_IaaI));
+ ASSERT(BeamIsOpCode(ci->u.op, op_i_func_info_IaaI));
#endif
switch (specified) {
case 3:
- if (ci_rw->mfa.arity != mfa->arity)
+ if (ci->mfa.arity != mfa->arity)
continue;
case 2:
- if (ci_rw->mfa.function != mfa->function)
+ if (ci->mfa.function != mfa->function)
continue;
case 1:
- if (ci_rw->mfa.module != mfa->module)
+ if (ci->mfa.module != mfa->module)
continue;
case 0:
break;
}
/* Store match */
- f->matching[i].ci_exec = ci_exec;
- f->matching[i].ci_rw = ci_rw;
+ f->matching[i].code_info = ci;
f->matching[i].mod = module[current];
i++;
- }
+ }
}
f->matched = i;
Free(module);
@@ -263,8 +266,7 @@ erts_bp_match_export(BpFunctions* f, ErtsCodeMFA *mfa, int specified)
ASSERT(BeamIsOpCode(ep->trampoline.common.op, op_i_generic_breakpoint));
}
- f->matching[ne].ci_exec = &ep->info;
- f->matching[ne].ci_rw = &ep->info;
+ f->matching[ne].code_info = &ep->info;
f->matching[ne].mod = erts_get_module(ep->info.mfa.module, code_ix);
ne++;
@@ -283,21 +285,74 @@ erts_bp_free_matched_functions(BpFunctions* f)
}
void
-erts_consolidate_bp_data(BpFunctions* f, int local)
+erts_consolidate_export_bp_data(BpFunctions* f)
{
BpFunction* fs = f->matching;
- Uint i;
- Uint n = f->matched;
+ Uint i, n;
+
+ ERTS_LC_ASSERT(erts_has_code_mod_permission());
- ERTS_LC_ASSERT(erts_has_code_write_permission());
+ n = f->matched;
for (i = 0; i < n; i++) {
- consolidate_bp_data(fs[i].mod, fs[i].ci_rw, local);
+ struct erl_module_instance *mi;
+ ErtsCodeInfo *ci_rw;
+
+ mi = fs[i].mod ? &fs[i].mod->curr : NULL;
+
+ /* Export entries are always writable, discard const. */
+ ci_rw = (ErtsCodeInfo*)fs[i].code_info;
+
+ ASSERT(mi == NULL ||
+ !ErtsInArea(ci_rw,
+ mi->executable_region,
+ mi->code_length));
+
+ consolidate_bp_data(mi, ci_rw, 0);
+ }
+}
+
+void
+erts_consolidate_local_bp_data(BpFunctions* f)
+{
+ struct erl_module_instance *prev_mi;
+ BpFunction* fs = f->matching;
+ Uint i, n;
+
+ ERTS_LC_ASSERT(erts_has_code_mod_permission());
+
+ n = f->matched;
+ prev_mi = NULL;
+
+ for (i = 0; i < n; i++) {
+ struct erl_module_instance *mi;
+ ErtsCodeInfo *ci_rw;
+
+ ASSERT(fs[i].mod);
+ mi = &fs[i].mod->curr;
+
+ if (prev_mi != mi) {
+ if (prev_mi != NULL) {
+ erts_seal_module(prev_mi);
+ }
+
+ erts_unseal_module(mi);
+ prev_mi = mi;
+ }
+
+ ci_rw = (ErtsCodeInfo*)erts_writable_code_ptr(mi, fs[i].code_info);
+
+ consolidate_bp_data(mi, ci_rw, 1);
+ }
+
+ if (prev_mi != NULL) {
+ erts_seal_module(prev_mi);
}
}
static void
-consolidate_bp_data(Module* modp, ErtsCodeInfo *ci_rw, int local)
+consolidate_bp_data(struct erl_module_instance *mi,
+ ErtsCodeInfo *ci_rw, int local)
{
GenericBp* g = ci_rw->gen_bp;
GenericBpData* src;
@@ -328,7 +383,10 @@ consolidate_bp_data(Module* modp, ErtsCodeInfo *ci_rw, int local)
bp_count_unref(dst->count);
}
if (flags & ERTS_BPF_TIME_TRACE) {
- bp_time_unref(dst->time);
+ bp_calltrace_unref(dst->time);
+ }
+ if (flags & ERTS_BPF_MEM_TRACE) {
+ bp_calltrace_unref(dst->memory);
}
/*
@@ -337,14 +395,14 @@ consolidate_bp_data(Module* modp, ErtsCodeInfo *ci_rw, int local)
flags = dst->flags = src->flags;
if (flags == 0) {
- if (modp) {
+ if (mi) {
if (local) {
- modp->curr.num_breakpoints--;
+ mi->num_breakpoints--;
} else {
- modp->curr.num_traced_exports--;
+ mi->num_traced_exports--;
}
- ASSERT(modp->curr.num_breakpoints >= 0);
- ASSERT(modp->curr.num_traced_exports >= 0);
+ ASSERT(mi->num_breakpoints >= 0);
+ ASSERT(mi->num_traced_exports >= 0);
#if !defined(BEAMASM) && defined(DEBUG)
{
BeamInstr instr = *(const BeamInstr*)erts_codeinfo_to_code(ci_rw);
@@ -382,6 +440,11 @@ consolidate_bp_data(Module* modp, ErtsCodeInfo *ci_rw, int local)
erts_refc_inc(&dst->time->refc, 1);
ASSERT(dst->time->hash);
}
+ if (flags & ERTS_BPF_MEM_TRACE) {
+ dst->memory = src->memory;
+ erts_refc_inc(&dst->memory->refc, 1);
+ ASSERT(dst->memory->hash);
+ }
}
void
@@ -397,37 +460,38 @@ erts_commit_staged_bp(void)
void
erts_install_breakpoints(BpFunctions* f)
{
- Uint i;
- Uint n = f->matched;
+ struct erl_module_instance *prev_mi;
+ Uint i, n;
+
+ n = f->matched;
+ prev_mi = NULL;
for (i = 0; i < n; i++) {
- const ErtsCodeInfo *ci_exec = f->matching[i].ci_exec;
- ErtsCodeInfo *ci_rw = f->matching[i].ci_rw;
- GenericBp *g = ci_rw->gen_bp;
- Module *modp = f->matching[i].mod;
-#ifdef BEAMASM
- if ((erts_asm_bp_get_flags(ci_exec) & ERTS_ASM_BP_FLAG_BP) == 0 && g) {
- /*
- * The breakpoint must be disabled in the active data
- * (it will enabled later by switching bp indices),
- * and enabled in the staging data.
- */
- ASSERT(g->data[erts_active_bp_ix()].flags == 0);
- ASSERT(g->data[erts_staging_bp_ix()].flags != 0);
+ struct erl_module_instance *mi;
+ const ErtsCodeInfo *ci_exec;
+ ErtsCodeInfo *ci_rw;
+ GenericBp *g;
+ Module *modp;
- erts_asm_bp_set_flag(ci_rw, ci_exec, ERTS_ASM_BP_FLAG_BP);
- modp->curr.num_breakpoints++;
+ modp = f->matching[i].mod;
+ mi = &modp->curr;
+
+ if (prev_mi != mi) {
+ if (prev_mi != NULL) {
+ erts_seal_module(prev_mi);
+ }
+
+ erts_unseal_module(mi);
+ prev_mi = mi;
}
-#else
- BeamInstr volatile *pc = (BeamInstr*)erts_codeinfo_to_code(ci_rw);
- BeamInstr instr = *pc;
- ASSERT(ci_exec == ci_rw);
- (void)ci_exec;
+ ci_exec = f->matching[i].code_info;
+ ci_rw = (ErtsCodeInfo*)erts_writable_code_ptr(mi, ci_exec);
- if (!BeamIsOpCode(instr, op_i_generic_breakpoint) && g) {
- BeamInstr br = BeamOpCodeAddr(op_i_generic_breakpoint);
+ g = ci_rw->gen_bp;
+#ifdef BEAMASM
+ if ((erts_asm_bp_get_flags(ci_exec) & ERTS_ASM_BP_FLAG_BP) == 0 && g) {
/*
* The breakpoint must be disabled in the active data
* (it will enabled later by switching bp indices),
@@ -436,33 +500,83 @@ erts_install_breakpoints(BpFunctions* f)
ASSERT(g->data[erts_active_bp_ix()].flags == 0);
ASSERT(g->data[erts_staging_bp_ix()].flags != 0);
- /*
- * The following write is not protected by any lock. We
- * assume that the hardware guarantees that a write of an
- * aligned word-size writes is atomic (i.e. that other
- * processes executing this code will not see a half
- * pointer).
- *
- * The contents of *pc is marked 'volatile' to ensure that
- * the compiler will do a single full-word write, and not
- * try any fancy optimizations to write a half word.
- */
- instr = BeamSetCodeAddr(instr, br);
- *pc = instr;
- modp->curr.num_breakpoints++;
- }
+ erts_asm_bp_set_flag(ci_rw, ci_exec, ERTS_ASM_BP_FLAG_BP);
+ mi->num_breakpoints++;
+ }
+#else
+ {
+ BeamInstr volatile *pc = (BeamInstr*)erts_codeinfo_to_code(ci_rw);
+ BeamInstr instr = *pc;
+
+ ASSERT(ci_exec == ci_rw);
+ (void)ci_exec;
+
+ if (!BeamIsOpCode(instr, op_i_generic_breakpoint) && g) {
+ BeamInstr br = BeamOpCodeAddr(op_i_generic_breakpoint);
+
+ /* The breakpoint must be disabled in the active data
+ * (it will enabled later by switching bp indices),
+ * and enabled in the staging data. */
+ ASSERT(g->data[erts_active_bp_ix()].flags == 0);
+ ASSERT(g->data[erts_staging_bp_ix()].flags != 0);
+
+ /* The following write is not protected by any lock. We
+ * assume that the hardware guarantees that a write of an
+ * aligned word-size writes is atomic (i.e. that other
+ * processes executing this code will not see a half
+ * pointer).
+ *
+ * The contents of *pc is marked 'volatile' to ensure that
+ * the compiler will do a single full-word write, and not
+ * try any fancy optimizations to write a half word.
+ */
+ instr = BeamSetCodeAddr(instr, br);
+ *pc = instr;
+
+ mi->num_breakpoints++;
+ }
+ }
#endif
}
+
+ if (prev_mi != NULL) {
+ erts_seal_module(prev_mi);
+ }
}
void
erts_uninstall_breakpoints(BpFunctions* f)
{
- Uint i;
- Uint n = f->matched;
+ struct erl_module_instance *prev_mi;
+ Uint i, n;
+
+ n = f->matched;
+ prev_mi = NULL;
for (i = 0; i < n; i++) {
- uninstall_breakpoint(f->matching[i].ci_rw, f->matching[i].ci_exec);
+ struct erl_module_instance *mi = &f->matching[i].mod->curr;
+ const ErtsCodeInfo *ci_exec;
+ ErtsCodeInfo *ci_rw;
+
+ mi = &f->matching[i].mod->curr;
+
+ if (prev_mi != mi) {
+ if (prev_mi != NULL) {
+ erts_seal_module(prev_mi);
+ }
+
+ erts_unseal_module(mi);
+ prev_mi = mi;
+ }
+
+ ci_exec = f->matching[i].code_info;
+ ci_rw = erts_writable_code_ptr(mi, ci_exec);
+
+ uninstall_breakpoint(ci_rw, ci_exec);
+ }
+
+ if (prev_mi != NULL) {
+ erts_seal_module(prev_mi);
}
}
@@ -544,6 +658,13 @@ erts_set_time_break(BpFunctions* f, enum erts_break_op count_op)
}
void
+erts_set_memory_break(BpFunctions* f, enum erts_break_op count_op)
+{
+ set_break(f, 0, ERTS_BPF_MEM_TRACE|ERTS_BPF_MEM_TRACE_ACTIVE,
+ count_op, erts_tracer_nil);
+}
+
+void
erts_clear_trace_break(BpFunctions* f)
{
clear_break(f, ERTS_BPF_LOCAL_TRACE);
@@ -587,6 +708,12 @@ erts_clear_time_break(BpFunctions* f)
{
clear_break(f, ERTS_BPF_TIME_TRACE|ERTS_BPF_TIME_TRACE_ACTIVE);
}
+
+void
+erts_clear_memory_break(BpFunctions* f)
+{
+ clear_break(f, ERTS_BPF_MEM_TRACE|ERTS_BPF_MEM_TRACE_ACTIVE);
+}
void
erts_clear_all_breaks(BpFunctions* f)
@@ -596,6 +723,7 @@ erts_clear_all_breaks(BpFunctions* f)
int
erts_clear_module_break(Module *modp) {
+ struct erl_module_instance *mi;
const BeamCodeHeader* code_hdr;
Uint n;
Uint i;
@@ -603,7 +731,9 @@ erts_clear_module_break(Module *modp) {
ERTS_LC_ASSERT(erts_thr_progress_is_blocking());
ASSERT(modp);
- code_hdr = modp->curr.code_hdr;
+ mi = &modp->curr;
+
+ code_hdr = mi->code_hdr;
if (!code_hdr) {
return 0;
}
@@ -617,21 +747,23 @@ erts_clear_module_break(Module *modp) {
erts_commit_staged_bp();
+ erts_unseal_module(mi);
+
for (i = 0; i < n; ++i) {
const ErtsCodeInfo *ci_exec;
ErtsCodeInfo *ci_rw;
- void *w_ptr;
ci_exec = code_hdr->functions[i];
- w_ptr = erts_writable_code_ptr(&modp->curr, ci_exec);
- ci_rw = (ErtsCodeInfo*)w_ptr;
+ ci_rw = (ErtsCodeInfo*)erts_writable_code_ptr(mi, ci_exec);
uninstall_breakpoint(ci_rw, ci_exec);
- consolidate_bp_data(modp, ci_rw, 1);
+ consolidate_bp_data(mi, ci_rw, 1);
ASSERT(ci_rw->gen_bp == NULL);
}
+ erts_seal_module(mi);
+
return n;
}
@@ -653,7 +785,7 @@ erts_clear_export_break(Module* modp, Export *ep)
clear_function_break(ci, ERTS_BPF_ALL);
erts_commit_staged_bp();
- consolidate_bp_data(modp, ci, 0);
+ consolidate_bp_data(&modp->curr, ci, 0);
ASSERT(ci->gen_bp == NULL);
}
@@ -689,12 +821,12 @@ static void fixup_cp_before_trace(Process *c_p,
erts_inspect_frame(cpp, &w);
if (BeamIsReturnTrace(w)) {
- cpp += CP_SIZE + 2;
- } else if (BeamIsReturnTimeTrace(w)) {
- cpp += CP_SIZE + 1;
+ cpp += CP_SIZE + BEAM_RETURN_TRACE_FRAME_SZ;
+ } else if (BeamIsReturnCallAccTrace(w)) {
+ cpp += CP_SIZE + BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ;
} else if (BeamIsReturnToTrace(w)) {
*return_to_trace = 1;
- cpp += CP_SIZE;
+ cpp += CP_SIZE + BEAM_RETURN_TO_TRACE_FRAME_SZ;
} else {
if (frame_layout == ERTS_FRAME_LAYOUT_FP_RA) {
ASSERT(is_CP(cpp[1]));
@@ -717,6 +849,12 @@ static void restore_cp_after_trace(Process *c_p, const Eterm cp_save[2]) {
c_p->stop[0] = cp_save[0];
}
+static ERTS_INLINE Uint get_allocated_words(Process *c_p, Sint allocated) {
+ if (c_p->abandoned_heap)
+ return allocated + c_p->htop - c_p->heap + c_p->mbuf_sz;
+ return allocated + c_p->htop - c_p->high_water + c_p->mbuf_sz;
+}
+
BeamInstr
erts_generic_breakpoint(Process* c_p, ErtsCodeInfo *info, Eterm* reg)
{
@@ -735,12 +873,15 @@ erts_generic_breakpoint(Process* c_p, ErtsCodeInfo *info, Eterm* reg)
ASSERT((bp_flags & ~ERTS_BPF_ALL) == 0);
if (bp_flags & (ERTS_BPF_LOCAL_TRACE|
ERTS_BPF_GLOBAL_TRACE|
- ERTS_BPF_TIME_TRACE_ACTIVE) &&
+ ERTS_BPF_TIME_TRACE_ACTIVE|
+ ERTS_BPF_MEM_TRACE_ACTIVE) &&
!IS_TRACED_FL(c_p, F_TRACE_CALLS)) {
bp_flags &= ~(ERTS_BPF_LOCAL_TRACE|
ERTS_BPF_GLOBAL_TRACE|
ERTS_BPF_TIME_TRACE|
- ERTS_BPF_TIME_TRACE_ACTIVE);
+ ERTS_BPF_TIME_TRACE_ACTIVE|
+ ERTS_BPF_MEM_TRACE|
+ ERTS_BPF_MEM_TRACE_ACTIVE);
if (bp_flags == 0) { /* Quick exit */
return g->orig_instr;
}
@@ -776,12 +917,28 @@ erts_generic_breakpoint(Process* c_p, ErtsCodeInfo *info, Eterm* reg)
erts_atomic_inc_nob(&bp->count->acount);
}
- if (bp_flags & ERTS_BPF_TIME_TRACE_ACTIVE) {
- const ErtsCodeInfo* prev_info;
+ if (bp_flags & (ERTS_BPF_TIME_TRACE_ACTIVE | ERTS_BPF_MEM_TRACE_ACTIVE)) {
+ const ErtsCodeInfo* prev_info = 0;
ErtsCodePtr w;
Eterm* E;
- prev_info = erts_trace_time_call(c_p, info, bp->time);
+ if (bp_flags & ERTS_BPF_TIME_TRACE_ACTIVE) {
+ BpDataAccumulator time = get_mtime(c_p);
+ prev_info = erts_trace_call(c_p, info, time, ERTS_PSD_CALL_TIME_BP, bp->time);
+ }
+
+ if (bp_flags & ERTS_BPF_MEM_TRACE_ACTIVE) {
+ BpDataAccumulator allocated;
+ /* if this is initial call, ignore 'allocated' */
+ if (c_p->u.initial.function == info->mfa.function && c_p->u.initial.module == info->mfa.module &&
+ c_p->u.initial.arity == info->mfa.arity)
+ allocated = 0;
+ else {
+ process_breakpoint_trace_t * pbt = ERTS_PROC_GET_CALL_MEMORY(c_p);
+ allocated = get_allocated_words(c_p, pbt ? pbt->allocated : 0);
+ }
+ prev_info = erts_trace_call(c_p, info, allocated, ERTS_PSD_CALL_MEMORY_BP, bp->memory);
+ }
E = c_p->stop;
@@ -789,8 +946,8 @@ erts_generic_breakpoint(Process* c_p, ErtsCodeInfo *info, Eterm* reg)
if (!(BeamIsReturnTrace(w) ||
BeamIsReturnToTrace(w) ||
- BeamIsReturnTimeTrace(w))) {
- int need = CP_SIZE + 1;
+ BeamIsReturnCallAccTrace(w))) {
+ int need = CP_SIZE + BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ;
ASSERT(c_p->htop <= E && E <= c_p->hend);
@@ -803,10 +960,11 @@ erts_generic_breakpoint(Process* c_p, ErtsCodeInfo *info, Eterm* reg)
E = c_p->stop;
ASSERT(c_p->htop <= E && E <= c_p->hend);
-
- E -= 2;
+ ERTS_CT_ASSERT(BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ == 2);
+ E -= 3;
+ E[2] = make_small(bp_flags);
E[1] = prev_info ? make_cp(erts_codeinfo_to_code(prev_info)) : NIL;
- E[0] = make_cp(beam_return_time_trace);
+ E[0] = make_cp(beam_call_trace_return);
if (erts_frame_layout == ERTS_FRAME_LAYOUT_FP_RA) {
E -= 1;
@@ -821,7 +979,7 @@ erts_generic_breakpoint(Process* c_p, ErtsCodeInfo *info, Eterm* reg)
if (bp_flags & ERTS_BPF_DEBUG) {
return BeamOpCodeAddr(op_i_debug_breakpoint);
} else {
- return g->orig_instr;
+ return g->orig_instr;
}
}
@@ -918,13 +1076,13 @@ do_call_trace(Process* c_p, ErtsCodeInfo* info, Eterm* reg,
}
const ErtsCodeInfo*
-erts_trace_time_call(Process* c_p, const ErtsCodeInfo *info, BpDataTime* bdt)
+erts_trace_call(Process* c_p, const ErtsCodeInfo *info, BpDataAccumulator accum,
+ int psd_ix, BpDataCallTrace* bdt)
{
- ErtsMonotonicTime time;
- process_breakpoint_time_t *pbt = NULL;
- bp_data_time_item_t sitem, *item = NULL;
- bp_time_hash_t *h = NULL;
- BpDataTime *pbdt = NULL;
+ process_breakpoint_trace_t *pbt = NULL;
+ bp_data_trace_item_t sitem, *item = NULL;
+ bp_trace_hash_t *h = NULL;
+ BpDataCallTrace *pbdt = NULL;
Uint32 six = acquire_bp_sched_ix(c_p);
const ErtsCodeInfo* prev_info;
@@ -932,32 +1090,33 @@ erts_trace_time_call(Process* c_p, const ErtsCodeInfo *info, BpDataTime* bdt)
ASSERT(erts_atomic32_read_acqb(&c_p->state) & (ERTS_PSFLG_RUNNING
| ERTS_PSFLG_DIRTY_RUNNING));
- /* get previous timestamp and breakpoint
+ /* get previous (timestamp or allocated memory size) and breakpoint
* from the process psd */
- pbt = ERTS_PROC_GET_CALL_TIME(c_p);
- time = get_mtime(c_p);
+ pbt = (process_breakpoint_trace_t *) erts_psd_get(c_p, psd_ix);
/* get pbt
- * timestamp = t0
+ * timestamp/allocated = ta0
* lookup bdt from code
- * set ts0 to pbt
+ * set ts0/alloc0 to pbt
* add call count here?
*/
if (pbt == 0) {
/* First call of process to instrumented function */
- pbt = Alloc(sizeof(process_breakpoint_time_t));
- (void) ERTS_PROC_SET_CALL_TIME(c_p, pbt);
+ pbt = Alloc(sizeof(process_breakpoint_trace_t));
+ erts_psd_set(c_p, psd_ix, pbt);
+ pbt->allocated = 0;
pbt->ci = NULL;
}
else if (pbt->ci) {
- /* add time to previous code */
- sitem.time = time - pbt->time;
+ /* add time/allocation to previous code */
+ sitem.accumulator = accum - pbt->accumulator;
sitem.pid = c_p->common.id;
sitem.count = 0;
/* previous breakpoint */
- pbdt = get_time_break(pbt->ci);
+ pbdt = (psd_ix == ERTS_PSD_CALL_TIME_BP) ?
+ get_time_break(pbt->ci) : get_memory_break(pbt->ci);
/* if null then the breakpoint was removed */
if (pbdt) {
@@ -970,7 +1129,7 @@ erts_trace_time_call(Process* c_p, const ErtsCodeInfo *info, BpDataTime* bdt)
if (!item) {
item = bp_hash_put(h, &sitem);
} else {
- BP_TIME_ADD(item, &sitem);
+ BP_ACCUMULATE(item, &sitem);
}
}
}
@@ -979,7 +1138,7 @@ erts_trace_time_call(Process* c_p, const ErtsCodeInfo *info, BpDataTime* bdt)
/* Add count to this code */
sitem.pid = c_p->common.id;
sitem.count = 1;
- sitem.time = 0;
+ sitem.accumulator = 0;
/* this breakpoint */
ASSERT(bdt);
@@ -992,79 +1151,96 @@ erts_trace_time_call(Process* c_p, const ErtsCodeInfo *info, BpDataTime* bdt)
if (!item) {
item = bp_hash_put(h, &sitem);
} else {
- BP_TIME_ADD(item, &sitem);
+ BP_ACCUMULATE(item, &sitem);
}
prev_info = pbt->ci;
pbt->ci = info;
- pbt->time = time;
+ pbt->accumulator = accum;
release_bp_sched_ix(six);
return prev_info;
}
-void
-erts_trace_time_return(Process *p, const ErtsCodeInfo *prev_info)
-{
- ErtsMonotonicTime time;
- process_breakpoint_time_t *pbt = NULL;
- bp_data_time_item_t sitem, *item = NULL;
- bp_time_hash_t *h = NULL;
- BpDataTime *pbdt = NULL;
- Uint32 six = acquire_bp_sched_ix(p);
-
- ASSERT(p);
- ASSERT(erts_atomic32_read_acqb(&p->state) & (ERTS_PSFLG_RUNNING
- | ERTS_PSFLG_DIRTY_RUNNING));
- /* get previous timestamp and breakpoint
- * from the process psd */
-
- pbt = ERTS_PROC_GET_CALL_TIME(p);
- time = get_mtime(p);
-
- /* get pbt
- * lookup bdt from code
- * timestamp = t1
- * get ts0 from pbt
- * get item from bdt->hash[bp_hash(p->id)]
- * ack diff (t1, t0) to item
- */
-
- if (pbt) {
-
- /* might have been removed due to
- * trace_pattern(false)
- */
- ASSERT(pbt->ci);
+static void
+call_trace_add(Process *p, BpDataCallTrace *pbdt, Uint32 six,
+ BpDataAccumulator accum, BpDataAccumulator prev_accum)
+{
+ bp_data_trace_item_t sitem, *item = NULL;
+ bp_trace_hash_t *h = NULL;
- sitem.time = time - pbt->time;
- sitem.pid = p->common.id;
- sitem.count = 0;
+ sitem.accumulator = accum - prev_accum;
+ sitem.pid = p->common.id;
+ sitem.count = 0;
- /* previous breakpoint */
- pbdt = get_time_break(pbt->ci);
+ /* beware, the trace_pattern might have been removed */
+ if (pbdt) {
- /* beware, the trace_pattern might have been removed */
- if (pbdt) {
+ h = &(pbdt->hash[six]);
- h = &(pbdt->hash[six]);
+ ASSERT(h);
+ ASSERT(h->item);
- ASSERT(h);
- ASSERT(h->item);
+ item = bp_hash_get(h, &sitem);
+ if (!item) {
+ item = bp_hash_put(h, &sitem);
+ } else {
+ BP_ACCUMULATE(item, &sitem);
+ }
+ }
+}
- item = bp_hash_get(h, &sitem);
- if (!item) {
- item = bp_hash_put(h, &sitem);
- } else {
- BP_TIME_ADD(item, &sitem);
- }
+void
+erts_call_trace_return(Process *p, const ErtsCodeInfo *prev_info,
+ Eterm bp_flags_term)
+{
+ process_breakpoint_trace_t *pbt = NULL;
+ BpDataCallTrace *pbdt;
+ Uint32 six = acquire_bp_sched_ix(p);
+ const Uint bp_flags = unsigned_val(bp_flags_term);
- }
+ ASSERT(p);
+ ASSERT(erts_atomic32_read_acqb(&p->state) & (ERTS_PSFLG_RUNNING
+ | ERTS_PSFLG_DIRTY_RUNNING));
- pbt->ci = prev_info;
- pbt->time = time;
+ /* get pbt
+ * lookup bdt from code
+ * timestamp/alloc = t1
+ * get ts0/alloc from pbt
+ * get item from bdt->hash[bp_hash(p->id)]
+ * add diff (t1, t0) to item
+ */
+ if (bp_flags & ERTS_BPF_TIME_TRACE_ACTIVE) {
+ /* get previous timestamp and breakpoint
+ * from the process psd */
+ pbt = ERTS_PROC_GET_CALL_TIME(p);
+ if (pbt) {
+ ErtsMonotonicTime time = get_mtime(p);
+
+ /* might have been removed due to
+ * trace_pattern(false)
+ */
+ ASSERT(pbt->ci);
+ /* previous breakpoint */
+ pbdt = get_time_break(pbt->ci);
+ call_trace_add(p, pbdt, six, time, pbt->accumulator);
+ pbt->ci = prev_info;
+ pbt->accumulator = time;
+ }
+ }
+ if (bp_flags & ERTS_BPF_MEM_TRACE_ACTIVE) {
+ pbt = (process_breakpoint_trace_t *) erts_psd_get(p, ERTS_PSD_CALL_MEMORY_BP);
+ if (pbt) {
+ Sint allocated = get_allocated_words(p, pbt->allocated);
+ /* previous breakpoint */
+ ASSERT(pbt->ci);
+ pbdt = get_memory_break(pbt->ci);
+ call_trace_add(p, pbdt, six, allocated, pbt->accumulator);
+ pbt->ci = prev_info;
+ pbt->accumulator = allocated;
+ }
}
release_bp_sched_ix(six);
@@ -1117,65 +1293,76 @@ erts_is_count_break(const ErtsCodeInfo *ci, Uint *count_ret)
return 0;
}
-int erts_is_time_break(Process *p, const ErtsCodeInfo *ci, Eterm *retval) {
+int erts_is_call_break(Process *p, int is_time, const ErtsCodeInfo *ci,
+ Eterm *retval) {
Uint i, ix;
- bp_time_hash_t hash;
- Uint size;
- Eterm *hp, t;
- bp_data_time_item_t *item = NULL;
- BpDataTime *bdt = get_time_break(ci);
-
- if (bdt) {
- if (retval) {
- /* collect all hashes to one hash */
- bp_hash_init(&hash, 64);
- /* foreach threadspecific hash */
- for (i = 0; i < bdt->n; i++) {
- bp_data_time_item_t *sitem;
-
- /* foreach hash bucket not NIL*/
- for(ix = 0; ix < bdt->hash[i].n; ix++) {
- item = &(bdt->hash[i].item[ix]);
- if (item->pid != NIL) {
- sitem = bp_hash_get(&hash, item);
- if (sitem) {
- BP_TIME_ADD(sitem, item);
- } else {
- bp_hash_put(&hash, item);
- }
- }
- }
- }
- /* *retval should be NIL or term from previous bif in export entry */
-
- if (hash.used > 0) {
- size = (5 + 2)*hash.used;
- hp = HAlloc(p, size);
-
- for(ix = 0; ix < hash.n; ix++) {
- item = &(hash.item[ix]);
- if (item->pid != NIL) {
- ErtsMonotonicTime sec, usec;
- usec = ERTS_MONOTONIC_TO_USEC(item->time);
- sec = usec / 1000000;
- usec = usec - sec*1000000;
- t = TUPLE4(hp, item->pid,
- make_small(item->count),
- make_small((Uint) sec),
- make_small((Uint) usec));
- hp += 5;
- *retval = CONS(hp, t, *retval); hp += 2;
- }
- }
- }
- bp_hash_delete(&hash);
- }
- return 1;
- }
+ bp_trace_hash_t hash;
+ bp_data_trace_item_t *item = NULL;
+ BpDataCallTrace *bdt = is_time ? get_time_break(ci) : get_memory_break(ci);
- return 0;
-}
+ if (!bdt)
+ return 0;
+ ASSERT(retval);
+ /* collect all hashes to one hash */
+ bp_hash_init(&hash, 64);
+ /* foreach threadspecific hash */
+ for (i = 0; i < bdt->n; i++) {
+ bp_data_trace_item_t *sitem;
+
+ /* foreach hash bucket not NIL*/
+ for(ix = 0; ix < bdt->hash[i].n; ix++) {
+ item = &(bdt->hash[i].item[ix]);
+ if (item->pid != NIL) {
+ sitem = bp_hash_get(&hash, item);
+ if (sitem) {
+ BP_ACCUMULATE(sitem, item);
+ } else {
+ bp_hash_put(&hash, item);
+ }
+ }
+ }
+ }
+ /* *retval should be NIL or term from previous bif in export entry */
+
+ if (hash.used > 0) {
+ Uint size;
+ Eterm *hp, *hp_end, t;
+
+ size = hash.used * (is_time ? (2+5) : (2+4+ERTS_MAX_SINT64_HEAP_SIZE));
+ hp = HAlloc(p, size);
+ hp_end = hp + size;
+
+ for(ix = 0; ix < hash.n; ix++) {
+ item = &(hash.item[ix]);
+ if (item->pid != NIL) {
+ if (is_time) {
+ BpDataAccumulator sec, usec;
+ usec = ERTS_MONOTONIC_TO_USEC(item->accumulator);
+ sec = usec / 1000000;
+ usec = usec - sec*1000000;
+ t = TUPLE4(hp, item->pid,
+ make_small(item->count),
+ make_small((Uint) sec),
+ make_small((Uint) usec));
+ hp += 5;
+ }
+ else {
+ Eterm words = erts_bld_sint64(&hp, NULL, item->accumulator);
+ t = TUPLE3(hp, item->pid,
+ make_small(item->count),
+ words);
+ hp += 4;
+ }
+ *retval = CONS(hp, t, *retval); hp += 2;
+ }
+ }
+ ASSERT(hp <= hp_end);
+ HRelease(p, hp_end, hp);
+ }
+ bp_hash_delete(&hash);
+ return 1;
+}
const ErtsCodeInfo *
erts_find_local_func(const ErtsCodeMFA *mfa) {
@@ -1203,14 +1390,14 @@ erts_find_local_func(const ErtsCodeMFA *mfa) {
return NULL;
}
-static void bp_hash_init(bp_time_hash_t *hash, Uint n) {
- Uint size = sizeof(bp_data_time_item_t)*n;
+static void bp_hash_init(bp_trace_hash_t *hash, Uint n) {
+ Uint size = sizeof(bp_data_trace_item_t)*n;
Uint i;
hash->n = n;
hash->used = 0;
- hash->item = (bp_data_time_item_t *)Alloc(size);
+ hash->item = (bp_data_trace_item_t *)Alloc(size);
sys_memzero(hash->item, size);
for(i = 0; i < n; ++i) {
@@ -1218,15 +1405,15 @@ static void bp_hash_init(bp_time_hash_t *hash, Uint n) {
}
}
-static void bp_hash_rehash(bp_time_hash_t *hash, Uint n) {
- bp_data_time_item_t *item = NULL;
- Uint size = sizeof(bp_data_time_item_t)*n;
+static void bp_hash_rehash(bp_trace_hash_t *hash, Uint n) {
+ bp_data_trace_item_t *item = NULL;
+ Uint size = sizeof(bp_data_trace_item_t)*n;
Uint ix;
Uint hval;
ASSERT(n > 0);
- item = (bp_data_time_item_t *)Alloc(size);
+ item = (bp_data_trace_item_t *)Alloc(size);
sys_memzero(item, size);
for( ix = 0; ix < n; ++ix) {
@@ -1246,7 +1433,7 @@ static void bp_hash_rehash(bp_time_hash_t *hash, Uint n) {
}
item[hval].pid = hash->item[ix].pid;
item[hval].count = hash->item[ix].count;
- item[hval].time = hash->item[ix].time;
+ item[hval].accumulator = hash->item[ix].accumulator;
}
}
@@ -1254,10 +1441,10 @@ static void bp_hash_rehash(bp_time_hash_t *hash, Uint n) {
hash->n = n;
hash->item = item;
}
-static ERTS_INLINE bp_data_time_item_t * bp_hash_get(bp_time_hash_t *hash, bp_data_time_item_t *sitem) {
+static ERTS_INLINE bp_data_trace_item_t * bp_hash_get(bp_trace_hash_t *hash, bp_data_trace_item_t *sitem) {
Eterm pid = sitem->pid;
Uint hval = (pid >> 4) % hash->n;
- bp_data_time_item_t *item = NULL;
+ bp_data_trace_item_t *item = NULL;
item = hash->item;
@@ -1269,10 +1456,10 @@ static ERTS_INLINE bp_data_time_item_t * bp_hash_get(bp_time_hash_t *hash, bp_da
return &(item[hval]);
}
-static ERTS_INLINE bp_data_time_item_t * bp_hash_put(bp_time_hash_t *hash, bp_data_time_item_t* sitem) {
+static ERTS_INLINE bp_data_trace_item_t * bp_hash_put(bp_trace_hash_t *hash, bp_data_trace_item_t* sitem) {
Uint hval;
float r = 0.0;
- bp_data_time_item_t *item;
+ bp_data_trace_item_t *item;
/* make sure that the hash is not saturated */
/* if saturated, rehash it */
@@ -1294,25 +1481,33 @@ static ERTS_INLINE bp_data_time_item_t * bp_hash_put(bp_time_hash_t *hash, bp_da
item = &(hash->item[hval]);
item->pid = sitem->pid;
- item->time = sitem->time;
+ item->accumulator = sitem->accumulator;
item->count = sitem->count;
hash->used++;
return item;
}
-static void bp_hash_delete(bp_time_hash_t *hash) {
+static void bp_hash_delete(bp_trace_hash_t *hash) {
hash->n = 0;
hash->used = 0;
Free(hash->item);
hash->item = NULL;
}
+static void bp_hash_reset(BpDataCallTrace* bdt) {
+ Uint i;
+ for (i = 0; i < bdt->n; i++) {
+ bp_hash_delete(&(bdt->hash[i]));
+ bp_hash_init(&(bdt->hash[i]), 32);
+ }
+}
+
void erts_schedule_time_break(Process *p, Uint schedule) {
- process_breakpoint_time_t *pbt = NULL;
- bp_data_time_item_t sitem, *item = NULL;
- bp_time_hash_t *h = NULL;
- BpDataTime *pbdt = NULL;
+ process_breakpoint_trace_t *pbt = NULL;
+ bp_data_trace_item_t sitem, *item = NULL;
+ bp_trace_hash_t *h = NULL;
+ BpDataCallTrace *pbdt = NULL;
Uint32 six = acquire_bp_sched_ix(p);
ASSERT(p);
@@ -1333,7 +1528,7 @@ void erts_schedule_time_break(Process *p, Uint schedule) {
if (pbt->ci) {
pbdt = get_time_break(pbt->ci);
if (pbdt) {
- sitem.time = get_mtime(p) - pbt->time;
+ sitem.accumulator = get_mtime(p) - pbt->accumulator;
sitem.pid = p->common.id;
sitem.count = 0;
@@ -1346,7 +1541,7 @@ void erts_schedule_time_break(Process *p, Uint schedule) {
if (!item) {
item = bp_hash_put(h, &sitem);
} else {
- BP_TIME_ADD(item, &sitem);
+ BP_ACCUMULATE(item, &sitem);
}
}
}
@@ -1356,7 +1551,7 @@ void erts_schedule_time_break(Process *p, Uint schedule) {
* timestamp it and remove the previous
* timestamp in the psd.
*/
- pbt->time = get_mtime(p);
+ pbt->accumulator = get_mtime(p);
break;
default :
ASSERT(0);
@@ -1377,15 +1572,36 @@ static void
set_break(BpFunctions* f, Binary *match_spec, Uint break_flags,
enum erts_break_op count_op, ErtsTracer tracer)
{
- Uint i;
- Uint n;
+ struct erl_module_instance *prev_mi = NULL;
+ BpFunction* fs = f->matching;
+ Uint i, n;
n = f->matched;
+ prev_mi = NULL;
+
for (i = 0; i < n; i++) {
- set_function_break(f->matching[i].ci_rw,
+ struct erl_module_instance *mi = &fs[i].mod->curr;
+ ErtsCodeInfo *ci_rw;
+
+ if (prev_mi != mi) {
+ if (prev_mi != NULL) {
+ erts_seal_module(prev_mi);
+ }
+
+ erts_unseal_module(mi);
+ prev_mi = mi;
+ }
+
+ ci_rw = (ErtsCodeInfo *)erts_writable_code_ptr(mi, fs[i].code_info);
+
+ set_function_break(ci_rw,
match_spec, break_flags,
count_op, tracer);
}
+
+ if (prev_mi != NULL) {
+ erts_seal_module(prev_mi);
+ }
}
static void
@@ -1397,7 +1613,7 @@ set_function_break(ErtsCodeInfo *ci, Binary *match_spec, Uint break_flags,
Uint common;
ErtsBpIndex ix = erts_staging_bp_ix();
- ERTS_LC_ASSERT(erts_has_code_write_permission());
+ ERTS_LC_ASSERT(erts_has_code_mod_permission());
g = ci->gen_bp;
if (g == 0) {
int i;
@@ -1450,17 +1666,20 @@ set_function_break(ErtsCodeInfo *ci, Binary *match_spec, Uint break_flags,
ASSERT((bp->flags & ~ERTS_BPF_ALL) == 0);
return;
} else if (common & ERTS_BPF_TIME_TRACE) {
- BpDataTime* bdt = bp->time;
- Uint i = 0;
-
if (count_op == ERTS_BREAK_PAUSE) {
bp->flags &= ~ERTS_BPF_TIME_TRACE_ACTIVE;
} else {
bp->flags |= ERTS_BPF_TIME_TRACE_ACTIVE;
- for (i = 0; i < bdt->n; i++) {
- bp_hash_delete(&(bdt->hash[i]));
- bp_hash_init(&(bdt->hash[i]), 32);
- }
+ bp_hash_reset(bp->time);
+ }
+ ASSERT((bp->flags & ~ERTS_BPF_ALL) == 0);
+ return;
+ } else if (common & ERTS_BPF_MEM_TRACE) {
+ if (count_op == ERTS_BREAK_PAUSE) {
+ bp->flags &= ~ERTS_BPF_MEM_TRACE_ACTIVE;
+ } else {
+ bp->flags |= ERTS_BPF_MEM_TRACE_ACTIVE;
+ bp_hash_reset(bp->memory);
}
ASSERT((bp->flags & ~ERTS_BPF_ALL) == 0);
return;
@@ -1491,19 +1710,23 @@ set_function_break(ErtsCodeInfo *ci, Binary *match_spec, Uint break_flags,
erts_refc_init(&bcp->refc, 1);
erts_atomic_init_nob(&bcp->acount, 0);
bp->count = bcp;
- } else if (break_flags & ERTS_BPF_TIME_TRACE) {
- BpDataTime* bdt;
+ } else if (break_flags & (ERTS_BPF_TIME_TRACE | ERTS_BPF_MEM_TRACE)) {
+ BpDataCallTrace* bdt;
Uint i;
- ASSERT((bp->flags & ERTS_BPF_TIME_TRACE) == 0);
- bdt = Alloc(sizeof(BpDataTime));
+ ASSERT((break_flags & bp->flags & ERTS_BPF_TIME_TRACE) == 0);
+ ASSERT((break_flags & bp->flags & ERTS_BPF_MEM_TRACE) == 0);
+ bdt = Alloc(sizeof(BpDataCallTrace));
erts_refc_init(&bdt->refc, 1);
bdt->n = erts_no_schedulers + 1;
- bdt->hash = Alloc(sizeof(bp_time_hash_t)*(bdt->n));
+ bdt->hash = Alloc(sizeof(bp_trace_hash_t)*(bdt->n));
for (i = 0; i < bdt->n; i++) {
bp_hash_init(&(bdt->hash[i]), 32);
}
- bp->time = bdt;
+ if (break_flags & ERTS_BPF_TIME_TRACE)
+ bp->time = bdt;
+ else
+ bp->memory = bdt;
}
bp->flags |= break_flags;
@@ -1518,7 +1741,7 @@ clear_break(BpFunctions* f, Uint break_flags)
n = f->matched;
for (i = 0; i < n; i++) {
- clear_function_break(f->matching[i].ci_exec, break_flags);
+ clear_function_break(f->matching[i].code_info, break_flags);
}
}
@@ -1530,7 +1753,7 @@ clear_function_break(const ErtsCodeInfo *ci, Uint break_flags)
Uint common;
ErtsBpIndex ix = erts_staging_bp_ix();
- ERTS_LC_ASSERT(erts_has_code_write_permission());
+ ERTS_LC_ASSERT(erts_has_code_mod_permission());
if ((g = ci->gen_bp) == NULL) {
return 1;
@@ -1553,7 +1776,11 @@ clear_function_break(const ErtsCodeInfo *ci, Uint break_flags)
}
if (common & ERTS_BPF_TIME_TRACE) {
ASSERT((bp->flags & ERTS_BPF_TIME_TRACE_ACTIVE) == 0);
- bp_time_unref(bp->time);
+ bp_calltrace_unref(bp->time);
+ }
+ if (common & ERTS_BPF_MEM_TRACE) {
+ ASSERT((bp->flags & ERTS_BPF_MEM_TRACE_ACTIVE) == 0);
+ bp_calltrace_unref(bp->memory);
}
ASSERT((bp->flags & ~ERTS_BPF_ALL) == 0);
@@ -1579,7 +1806,7 @@ bp_count_unref(BpCount* bcp)
}
static void
-bp_time_unref(BpDataTime* bdt)
+bp_calltrace_unref(BpDataCallTrace* bdt)
{
if (erts_refc_dectest(&bdt->refc, 0) <= 0) {
Uint i = 0;
@@ -1592,13 +1819,20 @@ bp_time_unref(BpDataTime* bdt)
}
}
-static BpDataTime*
+static BpDataCallTrace*
get_time_break(const ErtsCodeInfo *ci)
{
GenericBpData* bp = check_break(ci, ERTS_BPF_TIME_TRACE);
return bp ? bp->time : 0;
}
+static BpDataCallTrace*
+get_memory_break(const ErtsCodeInfo *ci)
+{
+ GenericBpData* bp = check_break(ci, ERTS_BPF_MEM_TRACE);
+ return bp ? bp->memory : 0;
+}
+
static GenericBpData*
check_break(const ErtsCodeInfo *ci, Uint break_flags)
{
diff --git a/erts/emulator/beam/beam_bp.h b/erts/emulator/beam/beam_bp.h
index 3688f08332..83eac585f4 100644
--- a/erts/emulator/beam/beam_bp.h
+++ b/erts/emulator/beam/beam_bp.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2000-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2000-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,28 +26,36 @@
#include "erl_vm.h"
#include "global.h"
+/* Use the same signed long long for both call_time and call_memory.
+* There is not much value in future-proofing until there is a need
+* to support anything other than a simple 8-byte number. When such
+* a use-case is identified, this type could be turned into a union.
+*/
+typedef ErtsMonotonicTime BpDataAccumulator;
+
typedef struct {
Eterm pid;
Sint count;
- ErtsMonotonicTime time;
-} bp_data_time_item_t;
+ BpDataAccumulator accumulator;
+} bp_data_trace_item_t;
typedef struct {
Uint n;
Uint used;
- bp_data_time_item_t *item;
-} bp_time_hash_t;
+ bp_data_trace_item_t *item;
+} bp_trace_hash_t;
-typedef struct bp_data_time { /* Call time */
+typedef struct bp_data_time { /* Call time, Memory trace */
Uint n;
- bp_time_hash_t *hash;
+ bp_trace_hash_t *hash;
erts_refc_t refc;
-} BpDataTime;
+} BpDataCallTrace;
typedef struct {
const ErtsCodeInfo *ci;
- ErtsMonotonicTime time;
-} process_breakpoint_time_t; /* used within psd */
+ BpDataAccumulator accumulator;
+ BpDataAccumulator allocated; /* adjustment for GC and messages on the heap */
+} process_breakpoint_trace_t; /* used within psd */
typedef struct {
erts_atomic_t acount;
@@ -65,7 +73,8 @@ typedef struct generic_bp_data {
Binary* meta_ms; /* Match spec for meta trace */
BpMetaTracer* meta_tracer; /* Meta tracer */
BpCount* count; /* For call count */
- BpDataTime* time; /* For time trace */
+ BpDataCallTrace* time; /* For time trace */
+ BpDataCallTrace* memory; /* For memory trace */
} GenericBpData;
#define ERTS_NUM_BP_IX 2
@@ -91,8 +100,7 @@ enum erts_break_op{
typedef Uint32 ErtsBpIndex;
typedef struct {
- const ErtsCodeInfo *ci_exec;
- ErtsCodeInfo *ci_rw;
+ const ErtsCodeInfo *code_info;
Module* mod;
} BpFunction;
@@ -119,7 +127,9 @@ void erts_bp_free_matched_functions(BpFunctions* f);
void erts_install_breakpoints(BpFunctions* f);
void erts_uninstall_breakpoints(BpFunctions* f);
-void erts_consolidate_bp_data(BpFunctions* f, int local);
+
+void erts_consolidate_local_bp_data(BpFunctions* f);
+void erts_consolidate_export_bp_data(BpFunctions* f);
void erts_set_trace_break(BpFunctions *f, Binary *match_spec);
void erts_clear_trace_break(BpFunctions *f);
@@ -150,15 +160,16 @@ int erts_is_mtrace_break(const ErtsCodeInfo *ci, Binary **match_spec_ret,
ErtsTracer *tracer_ret);
int erts_is_count_break(const ErtsCodeInfo *ci, Uint *count_ret);
-int erts_is_time_break(Process *p, const ErtsCodeInfo *ci, Eterm *call_time);
+int erts_is_call_break(Process *p, int is_time, const ErtsCodeInfo *ci, Eterm *call_time);
-const ErtsCodeInfo* erts_trace_time_call(Process* c_p,
- const ErtsCodeInfo *ci,
- BpDataTime* bdt);
-void erts_trace_time_return(Process* c_p, const ErtsCodeInfo *ci);
+void erts_call_trace_return(Process* c_p, const ErtsCodeInfo *ci, Eterm bp_flags_term);
void erts_schedule_time_break(Process *p, Uint out);
+ERTS_GLB_INLINE void erts_adjust_memory_break(Process *p, Sint adjustment);
+ERTS_GLB_INLINE void erts_adjust_message_break(Process *p, Eterm message);
void erts_set_time_break(BpFunctions *f, enum erts_break_op);
+void erts_set_memory_break(BpFunctions *f, enum erts_break_op);
void erts_clear_time_break(BpFunctions *f);
+void erts_clear_memory_break(BpFunctions *f);
const ErtsCodeInfo *erts_find_local_func(const ErtsCodeMFA *mfa);
@@ -176,6 +187,23 @@ ERTS_GLB_INLINE ErtsBpIndex erts_staging_bp_ix(void)
{
return erts_atomic32_read_nob(&erts_staging_bp_index);
}
+
+ERTS_GLB_INLINE
+void erts_adjust_memory_break(Process *p, Sint adjustment)
+{
+ process_breakpoint_trace_t * pbt = ERTS_PROC_GET_CALL_MEMORY(p);
+ if (pbt)
+ pbt->allocated += adjustment;
+}
+
+ERTS_GLB_INLINE
+void erts_adjust_message_break(Process *p, Eterm message)
+{
+ process_breakpoint_trace_t * pbt = ERTS_PROC_GET_CALL_MEMORY(p);
+ if (pbt)
+ pbt->allocated += size_object(message);
+}
+
#endif
#endif /* _BEAM_BP_H */
diff --git a/erts/emulator/beam/beam_common.c b/erts/emulator/beam/beam_common.c
index 2055f60e91..6eb97fc864 100644
--- a/erts/emulator/beam/beam_common.c
+++ b/erts/emulator/beam/beam_common.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -415,11 +415,11 @@ ErtsCodePtr erts_printable_return_address(Process* p, Eterm *E) {
erts_inspect_frame(scanner, &return_address);
if (BeamIsReturnTrace(return_address)) {
- scanner += CP_SIZE + 2;
- } else if (BeamIsReturnTimeTrace(return_address)) {
- scanner += CP_SIZE + 1;
+ scanner += CP_SIZE + BEAM_RETURN_TRACE_FRAME_SZ;
+ } else if (BeamIsReturnCallAccTrace(return_address)) {
+ scanner += CP_SIZE + BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ;
} else if (BeamIsReturnToTrace(return_address)) {
- scanner += CP_SIZE;
+ scanner += CP_SIZE + BEAM_RETURN_TO_TRACE_FRAME_SZ;
} else {
return return_address;
}
@@ -622,15 +622,14 @@ next_catch(Process* c_p, Eterm *reg) {
ASSERT_MFA(mfa);
erts_trace_exception(c_p, mfa, reg[3], reg[1], tracer);
}
-
- ptr += CP_SIZE + 2;
- } else if (BeamIsReturnTimeTrace(return_address)) {
- ptr += CP_SIZE + 1;
+ ptr += CP_SIZE + BEAM_RETURN_TRACE_FRAME_SZ;
+ } else if (BeamIsReturnCallAccTrace(return_address)) {
+ ptr += CP_SIZE + BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ;
} else if (BeamIsReturnToTrace(return_address)) {
have_return_to_trace = 1; /* Record next cp */
return_to_trace_address = NULL;
- ptr += CP_SIZE;
+ ptr += CP_SIZE + BEAM_RETURN_TO_TRACE_FRAME_SZ;
} else {
/* This is an ordinary call frame: if the previous frame was a
* return_to trace we should record this CP as a return_to
@@ -801,11 +800,11 @@ gather_stacktrace(Process* p, struct StackTrace* s, int depth)
erts_inspect_frame(ptr, &return_address);
if (BeamIsReturnTrace(return_address)) {
- ptr += CP_SIZE + 2;
- } else if (BeamIsReturnTimeTrace(return_address)) {
- ptr += CP_SIZE + 1;
+ ptr += CP_SIZE + BEAM_RETURN_TRACE_FRAME_SZ;
+ } else if (BeamIsReturnCallAccTrace(return_address)) {
+ ptr += CP_SIZE + BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ;
} else if (BeamIsReturnToTrace(return_address)) {
- ptr += CP_SIZE;
+ ptr += CP_SIZE + BEAM_RETURN_TO_TRACE_FRAME_SZ;
} else {
if (return_address != prev) {
ErtsCodePtr adjusted_address;
@@ -2076,6 +2075,8 @@ erts_gc_update_map_assoc(Process* p, Eterm* reg, Uint live,
Eterm new_key;
Eterm* kp;
Eterm map;
+ int changed_values = 0;
+ int changed_keys = 0;
num_updates = n / 2;
map = reg[live];
@@ -2127,35 +2128,46 @@ erts_gc_update_map_assoc(Process* p, Eterm* reg, Uint live,
* Build the skeleton for the map, ready to be filled in.
*
* +-----------------------------------+
- * | (Space for aritvyal for keys) | <-----------+
- * +-----------------------------------+ |
- * | (Space for key 1) | | <-- kp
- * +-----------------------------------+ |
- * . |
- * . |
- * . |
- * +-----------------------------------+ |
- * | (Space for last key) | |
- * +-----------------------------------+ |
- * | MAP_HEADER | |
- * +-----------------------------------+ |
- * | (Space for number of keys/values) | |
- * +-----------------------------------+ |
+ * | MAP_HEADER_FLATMAP |
+ * +-----------------------------------+
+ * | (Space for number of keys/values) |
+ * +-----------------------------------+
* | Boxed tuple pointer >----------------+
+ * +-----------------------------------+ |
+ * | (Space for value 1) | | <-- hp
+ * +-----------------------------------+ |
+ * . |
+ * . |
+ * . |
+ * +-----------------------------------+ |
+ * | (Space for last value) | |
+ * +-----------------------------------+ |
+ * +-----------------------------------+ |
+ * | (Space for aritvyal for keys) | <-----------+
+ * +-----------------------------------+
+ * | (Space for key 1) | <-- kp
+ * +-----------------------------------+
+ * .
+ * .
+ * .
* +-----------------------------------+
- * | (Space for value 1) | <-- hp
+ * | (Space for last key) |
* +-----------------------------------+
*/
+ hp = p->htop;
E = p->stop;
- kp = p->htop + 1; /* Point to first key */
- hp = kp + num_old + num_updates;
res = make_flatmap(hp);
mp = (flatmap_t *)hp;
hp += MAP_HEADER_FLATMAP_SZ;
mp->thing_word = MAP_HEADER_FLATMAP;
- mp->keys = make_tuple(kp-1);
+
+ kp = hp + num_old + num_updates; /* Point to key tuple. */
+
+ mp->keys = make_tuple(kp);
+
+ kp = kp + 1; /* Point to first key. */
old_vals = flatmap_get_values(old_mp);
old_keys = flatmap_get_keys(old_mp);
@@ -2172,21 +2184,25 @@ erts_gc_update_map_assoc(Process* p, Eterm* reg, Uint live,
Eterm key;
Sint c;
- ASSERT(kp < (Eterm *)mp);
key = *old_keys;
- if ((c = CMP_TERM(key, new_key)) < 0) {
+ if ((c = (key == new_key) ? 0 : erts_cmp_flatmap_keys(key, new_key)) < 0) {
/* Copy old key and value */
*kp++ = key;
*hp++ = *old_vals;
old_keys++, old_vals++, num_old--;
} else { /* Replace or insert new */
- GET_TERM(new_p[1], *hp++);
+ GET_TERM(new_p[1], *hp);
if (c > 0) { /* If new key */
*kp++ = new_key;
+ changed_keys = 1;
} else { /* If replacement */
+ if (*old_vals != *hp) {
+ changed_values = 1;
+ }
*kp++ = key;
old_keys++, old_vals++, num_old--;
}
+ hp++;
n--;
if (n == 0) {
break;
@@ -2218,6 +2234,28 @@ erts_gc_update_map_assoc(Process* p, Eterm* reg, Uint live,
GET_TERM(new_p[1], *hp++);
new_p += 2;
}
+ } else if (!changed_keys && !changed_values) {
+ /*
+ * All updates are now done, no new keys were introduced, and
+ * all new values were the same as old ones. We can just
+ * return the old map and skip committing the new allocation,
+ * effectively releasing it.
+ */
+ ASSERT(n == 0);
+ return map;
+ } else if (!changed_keys) {
+ /*
+ * All updates are now done, no new keys were introduced, but
+ * some values were changed. We can retain the old key tuple.
+ */
+ ASSERT(n == 0);
+ mp->size = old_mp->size;
+ mp->keys = old_mp->keys;
+ while (num_old-- > 0) {
+ *hp++ = *old_vals++;
+ }
+ p->htop = hp;
+ return res;
} else {
/*
* All updates are now done. We may still have old
@@ -2225,7 +2263,6 @@ erts_gc_update_map_assoc(Process* p, Eterm* reg, Uint live,
*/
ASSERT(n == 0);
while (num_old-- > 0) {
- ASSERT(kp < (Eterm *)mp);
*kp++ = *old_keys++;
*hp++ = *old_vals++;
}
@@ -2233,20 +2270,22 @@ erts_gc_update_map_assoc(Process* p, Eterm* reg, Uint live,
/*
* Calculate how many values that are unused at the end of the
- * key tuple and fill it out with a bignum header.
+ * value array and fill it out with a bignum header.
*/
- if ((n = (Eterm *)mp - kp) > 0) {
- *kp = make_pos_bignum_header(n-1);
+ if ((n = boxed_val(mp->keys) - hp) > 0) {
+ ASSERT(n <= num_updates);
+ *hp = make_pos_bignum_header(n-1);
}
/*
* Fill in the size of the map in both the key tuple and in the map.
*/
- n = kp - p->htop - 1; /* Actual number of keys/values */
- *p->htop = make_arityval(n);
- p->htop = hp;
+ n = hp - (Eterm *)mp - MAP_HEADER_FLATMAP_SZ; /* Actual number of keys/values */
+ ASSERT(n <= old_mp->size + num_updates);
mp->size = n;
+ *(boxed_val(mp->keys)) = make_arityval(n);
+ p->htop = kp;
/* The expensive case, need to build a hashmap */
if (n > MAP_SMALL_MAP_LIMIT) {
diff --git a/erts/emulator/beam/beam_common.h b/erts/emulator/beam/beam_common.h
index f3725d0d2e..0349d488ac 100644
--- a/erts/emulator/beam/beam_common.h
+++ b/erts/emulator/beam/beam_common.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -280,7 +280,8 @@ void check_monitor_long_schedule(Process *c_p, Uint64 start_time,
extern ErtsCodePtr beam_run_process;
extern ErtsCodePtr beam_normal_exit;
extern ErtsCodePtr beam_exit;
-extern ErtsCodePtr beam_save_calls;
+extern ErtsCodePtr beam_save_calls_export;
+extern ErtsCodePtr beam_save_calls_fun;
extern ErtsCodePtr beam_bif_export_trap;
extern ErtsCodePtr beam_export_trampoline;
extern ErtsCodePtr beam_continue_exit;
@@ -289,7 +290,7 @@ extern ErtsCodePtr beam_unloaded_fun;
extern ErtsCodePtr beam_return_to_trace; /* OpCode(i_return_to_trace) */
extern ErtsCodePtr beam_return_trace; /* OpCode(i_return_trace) */
extern ErtsCodePtr beam_exception_trace; /* OpCode(i_exception_trace) */
-extern ErtsCodePtr beam_return_time_trace; /* OpCode(i_return_time_trace) */
+extern ErtsCodePtr beam_call_trace_return; /* OpCode(i_call_trace_return) */
/** @brief Inspects an Erlang stack frame, returning the base of the data
* (first Y register).
diff --git a/erts/emulator/beam/beam_debug.c b/erts/emulator/beam/beam_debug.c
index 5b7e2a9414..3819de701f 100644
--- a/erts/emulator/beam/beam_debug.c
+++ b/erts/emulator/beam/beam_debug.c
@@ -165,10 +165,11 @@ erts_debug_breakpoint_2(BIF_ALIST_2)
mfa.arity = signed_val(tp[3]);
}
- if (!erts_try_seize_code_write_permission(BIF_P)) {
+ if (!erts_try_seize_code_mod_permission(BIF_P)) {
ERTS_BIF_YIELD2(BIF_TRAP_EXPORT(BIF_erts_debug_breakpoint_2),
BIF_P, BIF_ARG_1, BIF_ARG_2);
}
+
erts_proc_unlock(p, ERTS_PROC_LOCK_MAIN);
erts_thr_progress_block();
@@ -182,13 +183,17 @@ erts_debug_breakpoint_2(BIF_ALIST_2)
erts_commit_staged_bp();
erts_uninstall_breakpoints(&f);
}
- erts_consolidate_bp_data(&f, 1);
+ erts_consolidate_local_bp_data(&f);
res = make_small(f.matched);
erts_bp_free_matched_functions(&f);
+ erts_blocking_code_barrier();
+
erts_thr_progress_unblock();
erts_proc_lock(p, ERTS_PROC_LOCK_MAIN);
- erts_release_code_write_permission();
+
+ erts_release_code_mod_permission();
+
return res;
error:
@@ -682,7 +687,7 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
case 'F': /* Function definition */
{
ErlFunEntry* fe = (ErlFunEntry *) *ap;
- const ErtsCodeMFA *cmfa = erts_get_fun_mfa(fe);
+ const ErtsCodeMFA *cmfa = erts_get_fun_mfa(fe, erts_active_code_ix());
erts_print(to, to_arg, "fun(`%T`:`%T`/%bpu)", cmfa->module,
cmfa->function, cmfa->arity);
ap++;
diff --git a/erts/emulator/beam/beam_file.c b/erts/emulator/beam/beam_file.c
index 0c7bdfbec2..d210d0fc16 100644
--- a/erts/emulator/beam/beam_file.c
+++ b/erts/emulator/beam/beam_file.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -527,7 +527,7 @@ static int parse_line_chunk(BeamFile *beam, IFF_Chunk *chunk) {
hp = name_heap;
name = erts_atom_to_string(&hp, beam->module, suffix);
- lines->names[0] = beamfile_add_literal(beam, name);
+ lines->names[0] = beamfile_add_literal(beam, name, 1);
for (i = 1; i < name_count; i++) {
Uint num_chars, num_built, num_eaten;
@@ -563,7 +563,7 @@ static int parse_line_chunk(BeamFile *beam, IFF_Chunk *chunk) {
ASSERT(num_built == num_chars);
ASSERT(num_eaten == name_length);
- lines->names[i] = beamfile_add_literal(beam, name);
+ lines->names[i] = beamfile_add_literal(beam, name, 1);
if (name_heap != default_name_buf) {
erts_free(ERTS_ALC_T_LOADER_TMP, name_heap);
@@ -595,30 +595,52 @@ static void init_fallback_type_table(BeamFile *beam) {
types->fallback = 1;
types->entries[0].type_union = BEAM_TYPE_ANY;
- types->entries[0].min = 0;
- types->entries[0].max = -1;
+ types->entries[0].min = MAX_SMALL + 1;
+ types->entries[0].max = MIN_SMALL - 1;
}
-static int parse_type_chunk(BeamFile *beam, IFF_Chunk *chunk) {
+static int parse_type_chunk_otp_25(BeamFile *beam, BeamReader *p_reader) {
BeamFile_TypeTable *types;
- BeamReader reader;
- Sint32 version, count;
+ Sint32 count;
int i;
- beamreader_init(chunk->data, chunk->size, &reader);
+ types = &beam->types;
+ ASSERT(types->entries == NULL);
- LoadAssert(beamreader_read_i32(&reader, &version));
- if (version != BEAM_TYPES_VERSION) {
- /* Incompatible type format. */
- init_fallback_type_table(beam);
- return 1;
+ LoadAssert(beamreader_read_i32(p_reader, &count));
+ LoadAssert(CHECK_ITEM_COUNT(count, 0, sizeof(types->entries[0])));
+ LoadAssert(count >= 1);
+
+ types->entries = erts_alloc(ERTS_ALC_T_PREPARED_CODE,
+ count * sizeof(types->entries[0]));
+ types->count = count;
+ types->fallback = 0;
+
+ for (i = 0; i < count; i++) {
+ const byte *type_data;
+
+ LoadAssert(beamreader_read_bytes(p_reader, 18, &type_data));
+ LoadAssert(beam_types_decode_otp_25(type_data, 18, &types->entries[i]));
}
+ /* The first entry MUST be the "any type." */
+ LoadAssert(types->entries[0].type_union == BEAM_TYPE_ANY);
+ LoadAssert(types->entries[0].min > types->entries[0].max);
+
+ return 1;
+}
+
+static int parse_type_chunk_otp_26(BeamFile *beam, BeamReader *p_reader) {
+ BeamFile_TypeTable *types;
+
+ Sint32 count;
+ int i;
+
types = &beam->types;
ASSERT(types->entries == NULL);
- LoadAssert(beamreader_read_i32(&reader, &count));
+ LoadAssert(beamreader_read_i32(p_reader, &count));
LoadAssert(CHECK_ITEM_COUNT(count, 0, sizeof(types->entries[0])));
LoadAssert(count >= 1);
@@ -629,9 +651,13 @@ static int parse_type_chunk(BeamFile *beam, IFF_Chunk *chunk) {
for (i = 0; i < count; i++) {
const byte *type_data;
+ int extra;
- LoadAssert(beamreader_read_bytes(&reader, 18, &type_data));
- LoadAssert(beam_types_decode(type_data, 18, &types->entries[i]));
+ LoadAssert(beamreader_read_bytes(p_reader, 2, &type_data));
+ extra = beam_types_decode_type_otp_26(type_data, &types->entries[i]);
+ LoadAssert(extra >= 0);
+ LoadAssert(beamreader_read_bytes(p_reader, extra, &type_data));
+ beam_types_decode_extra_otp_26(type_data, &types->entries[i]);
}
/* The first entry MUST be the "any type." */
@@ -641,6 +667,25 @@ static int parse_type_chunk(BeamFile *beam, IFF_Chunk *chunk) {
return 1;
}
+static int parse_type_chunk(BeamFile *beam, IFF_Chunk *chunk) {
+ BeamReader reader;
+ Sint32 version;
+
+ beamreader_init(chunk->data, chunk->size, &reader);
+
+ LoadAssert(beamreader_read_i32(&reader, &version));
+ switch (version) {
+ case 1: /* OTP 25 */
+ return parse_type_chunk_otp_25(beam, &reader);
+ case BEAM_TYPES_VERSION: /* OTP 26 */
+ return parse_type_chunk_otp_26(beam, &reader);
+ default:
+ /* Incompatible type format. */
+ init_fallback_type_table(beam);
+ return 1;
+ }
+}
+
static ErlHeapFragment *new_literal_fragment(Uint size)
{
ErlHeapFragment *bp;
@@ -1134,7 +1179,7 @@ void beamfile_free(BeamFile *beam) {
}
}
-Sint beamfile_add_literal(BeamFile *beam, Eterm term) {
+Sint beamfile_add_literal(BeamFile *beam, Eterm term, int deduplicate) {
BeamFile_LiteralTable *literals;
BeamFile_LiteralEntry *entries;
@@ -1146,22 +1191,17 @@ Sint beamfile_add_literal(BeamFile *beam, Eterm term) {
literals = &beam->dynamic_literals;
entries = literals->entries;
- if (entries == NULL) {
- literals->allocated = 32;
-
- entries = erts_alloc(ERTS_ALC_T_PREPARED_CODE,
- literals->allocated * sizeof(*entries));
-
- literals->entries = entries;
- } else {
- /* Return a matching index if this literal already exists. We search
- * backwards since duplicates tend to be used close to one another,
- * and skip searching static literals as the chances of overlap are
- * pretty slim. */
- for (i = literals->count - 1; i >= 0; i--) {
- if (EQ(term, entries[i].value)) {
- /* Dynamic literal indexes are negative, starting at -1 */
- return ~i;
+ if (entries != NULL) {
+ if (deduplicate) {
+ /* Return a matching index if this literal already exists.
+ * We search backwards since duplicates tend to be used close to
+ * one another, and skip searching static literals as the chances
+ * of overlap are pretty slim. */
+ for (i = literals->count - 1; i >= 0; i--) {
+ if (EQ(term, entries[i].value)) {
+ /* Dynamic literal indexes are negative, starting at -1 */
+ return ~i;
+ }
}
}
@@ -1173,6 +1213,13 @@ Sint beamfile_add_literal(BeamFile *beam, Eterm term) {
literals->entries = entries;
}
+ } else {
+ literals->allocated = 32;
+
+ entries = erts_alloc(ERTS_ALC_T_PREPARED_CODE,
+ literals->allocated * sizeof(*entries));
+
+ literals->entries = entries;
}
term_size = size_object(term);
@@ -1271,7 +1318,7 @@ int iff_read_chunk(IFF_File *iff, Uint id, IFF_Chunk *chunk)
return read_beam_chunks(iff, 1, &id, chunk);
}
-void beamfile_init() {
+void beamfile_init(void) {
Eterm suffix;
Eterm *hp;
@@ -1419,7 +1466,8 @@ static int marshal_integer(BeamCodeReader *code_reader, TaggedNumber *value) {
value->tag = TAG_q;
value->size = 0;
- value->word_value = beamfile_add_literal(code_reader->file, term);
+ value->word_value = beamfile_add_literal(code_reader->file,
+ term, 1);
} else {
/* Result doesn't fit into a bignum. */
value->tag = TAG_o;
diff --git a/erts/emulator/beam/beam_file.h b/erts/emulator/beam/beam_file.h
index 7c73ae1e37..7168ffc793 100644
--- a/erts/emulator/beam/beam_file.h
+++ b/erts/emulator/beam/beam_file.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -253,10 +253,14 @@ beamfile_read(const byte *data, size_t size, BeamFile *beam);
* it. */
void beamfile_free(BeamFile *beam);
-/** @brief Copies a term into the dynamic literal table.
+/** @brief Copies a term into the dynamic literal table
+ *
+ * @param[in] deduplicate Whether to try to deduplicate the term before
+ * insertion. Set to zero if you require a new unique literal, for example if
+ * it needs to be modified in the late stages of loading.
*
* @return A literal index that can be used in beamfile_get_literal */
-Sint beamfile_add_literal(BeamFile *beam, Eterm term);
+Sint beamfile_add_literal(BeamFile *beam, Eterm term, int deduplicate);
/** @brief Gets a term from the literal table.
*
diff --git a/erts/emulator/beam/beam_load.c b/erts/emulator/beam/beam_load.c
index 85beba57ee..d051085023 100644
--- a/erts/emulator/beam/beam_load.c
+++ b/erts/emulator/beam/beam_load.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ Uint erts_total_code_size;
static int load_code(LoaderState *stp);
-#define PLEASE_RECOMPILE "please re-compile this module with an Erlang/OTP " ERLANG_OTP_RELEASE " compiler"
+#define PLEASE_RECOMPILE "please re-compile this module with an Erlang/OTP " ERLANG_OTP_RELEASE " compiler or update your Erlang/OTP version"
/**********************************************************************/
@@ -215,8 +215,8 @@ erts_finish_loading(Binary* magic, Process* c_p,
struct erl_module_instance* inst_p;
Module* mod_tab_p;
- ERTS_LC_ASSERT(erts_initialized == 0 || erts_has_code_write_permission() ||
- erts_thr_progress_is_blocking());
+ ERTS_LC_ASSERT(erts_initialized == 0 || erts_has_code_load_permission() ||
+ erts_thr_progress_is_blocking());
/* Make current code for the module old and insert the new code
* as current. This will fail if there already exists old code
@@ -571,6 +571,20 @@ static int load_code(LoaderState* stp)
BeamLoadError3(stp, "unsupported guard BIF: %T:%T/%d\n",
Mod, Name, arity);
}
+ case genop_bad_bs_match_1:
+ {
+ BeamOpArg sub_command = stp->genop->a[0];
+ beamopallocator_free_op(&stp->op_allocator,
+ stp->genop);
+ stp->genop = NULL;
+ if (sub_command.type == TAG_a) {
+ BeamLoadError1(stp, "bs_match: invalid sub instruction '%T' "
+ "(is this BEAM file generated by a future version of Erlang/OTP?)\n",
+ sub_command.val);
+ } else {
+ BeamLoadError0(stp, "bs_match: invalid operands (corrupt BEAM file?)\n");
+ }
+ }
default:
BeamLoadError0(stp, "no specific operation found");
}
diff --git a/erts/emulator/beam/beam_transform_helpers.c b/erts/emulator/beam/beam_transform_helpers.c
index 3a4ed5cd0b..01a0f6c289 100644
--- a/erts/emulator/beam/beam_transform_helpers.c
+++ b/erts/emulator/beam/beam_transform_helpers.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -150,7 +150,7 @@ oparg_compare(BeamOpArg* a, BeamOpArg* b)
static int
oparg_term_compare(SortBeamOpArg* a, SortBeamOpArg* b)
{
- Sint res = CMP_TERM(a->term, b->term);
+ Sint res = erts_cmp_flatmap_keys(a->term, b->term);
if (res < 0) {
return -1;
diff --git a/erts/emulator/beam/beam_types.c b/erts/emulator/beam/beam_types.c
index f0b8c8a6dc..876db9f47b 100644
--- a/erts/emulator/beam/beam_types.c
+++ b/erts/emulator/beam/beam_types.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2021-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2021-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,70 @@
static Sint64 get_sint64(const byte *data);
-int beam_types_decode(const byte *data, Uint size, BeamType *out) {
+int beam_types_decode_type_otp_26(const byte *data, BeamType *out) {
+ int flags, extra;
+
+ flags = (Uint16)data[0] << 8 | (Uint16)data[1];
+ if (flags == BEAM_TYPE_NONE) {
+ return -1;
+ }
+
+ extra = 0;
+
+ out->type_union = flags & BEAM_TYPE_ANY;
+ out->metadata_flags = flags & BEAM_TYPE_METADATA_MASK;
+
+ if (out->metadata_flags & BEAM_TYPE_HAS_LOWER_BOUND) {
+ if (!(out->type_union & (BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER))) {
+ return -1;
+ }
+
+ extra += 8;
+ }
+
+ if (out->metadata_flags & BEAM_TYPE_HAS_UPPER_BOUND) {
+ if (!(out->type_union & (BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER))) {
+ return -1;
+ }
+
+ extra += 8;
+ }
+
+ if (out->metadata_flags & BEAM_TYPE_HAS_UNIT) {
+ if (!(out->type_union & BEAM_TYPE_BITSTRING)) {
+ return -1;
+ }
+
+ extra += 1;
+ }
+
+ return extra;
+}
+
+void beam_types_decode_extra_otp_26(const byte *data, BeamType *out) {
+ out->min = MAX_SMALL + 1;
+ out->max = MIN_SMALL - 1;
+ out->size_unit = 1;
+
+ if (out->metadata_flags & BEAM_TYPE_HAS_LOWER_BOUND) {
+ ASSERT(out->type_union & (BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER));
+ out->min = get_sint64(data);
+ data += 8;
+ }
+
+ if (out->metadata_flags & BEAM_TYPE_HAS_UPPER_BOUND) {
+ ASSERT(out->type_union & (BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER));
+ out->max = get_sint64(data);
+ data += 8;
+ }
+
+ if (out->metadata_flags & BEAM_TYPE_HAS_UNIT) {
+ ASSERT(out->type_union & BEAM_TYPE_BITSTRING);
+ out->size_unit = data[0] + 1;
+ }
+}
+
+int beam_types_decode_otp_25(const byte *data, Uint size, BeamType *out) {
int types;
if (size != 18) {
@@ -49,6 +112,22 @@ int beam_types_decode(const byte *data, Uint size, BeamType *out) {
data += 8;
out->max = get_sint64(data);
+ if (out->min <= out->max) {
+ if (!(out->type_union & (BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER))) {
+ return -1;
+ }
+
+ out->metadata_flags = (BEAM_TYPE_HAS_LOWER_BOUND |
+ BEAM_TYPE_HAS_UPPER_BOUND);
+ } else {
+ out->min = MAX_SMALL + 1;
+ out->max = MIN_SMALL - 1;
+
+ out->metadata_flags = 0;
+ }
+
+ out->size_unit = 1;
+
return 1;
}
@@ -57,7 +136,7 @@ static Sint64 get_sint64(const byte *data) {
int i;
for (i = 0; i < 8; i++) {
- value = value << 8 | (Sint16)data[i];
+ value = value << 8 | (Sint64)data[i];
}
return value;
}
diff --git a/erts/emulator/beam/beam_types.h b/erts/emulator/beam/beam_types.h
index 000a644edb..fe215a1340 100644
--- a/erts/emulator/beam/beam_types.h
+++ b/erts/emulator/beam/beam_types.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2021-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2021-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@
#include "sys.h"
-#define BEAM_TYPES_VERSION 1
+#define BEAM_TYPES_VERSION 2
#define BEAM_TYPE_NONE (0)
@@ -57,45 +57,39 @@
#define BEAM_TYPE_ANY ((1 << 13) - 1)
-#define BEAM_TYPE_MASK_BOXED \
- (BEAM_TYPE_BITSTRING | \
- BEAM_TYPE_BS_MATCHSTATE | \
- BEAM_TYPE_FLOAT | \
- BEAM_TYPE_FUN | \
- BEAM_TYPE_INTEGER | \
- BEAM_TYPE_MAP | \
- BEAM_TYPE_PID | \
- BEAM_TYPE_PORT | \
- BEAM_TYPE_REFERENCE | \
- BEAM_TYPE_TUPLE)
-
-#define BEAM_TYPE_MASK_IMMEDIATE \
- (BEAM_TYPE_ATOM | \
- BEAM_TYPE_INTEGER | \
- BEAM_TYPE_NIL | \
- BEAM_TYPE_PID | \
- BEAM_TYPE_PORT)
-
-#define BEAM_TYPE_MASK_CELL \
- (BEAM_TYPE_CONS)
-
-#define BEAM_TYPE_MASK_ALWAYS_IMMEDIATE \
- (BEAM_TYPE_MASK_IMMEDIATE & ~(BEAM_TYPE_MASK_BOXED | BEAM_TYPE_MASK_CELL))
-#define BEAM_TYPE_MASK_ALWAYS_BOXED \
- (BEAM_TYPE_MASK_BOXED & ~(BEAM_TYPE_MASK_CELL | BEAM_TYPE_MASK_IMMEDIATE))
-#define BEAM_TYPE_MASK_ALWAYS_CELL \
- (BEAM_TYPE_MASK_CELL & ~(BEAM_TYPE_MASK_BOXED | BEAM_TYPE_MASK_IMMEDIATE))
+/* This is not a part of the type union proper, but is present in the format
+ * to signal the presence of metadata. */
+#define BEAM_TYPE_HAS_LOWER_BOUND (1 << 13)
+#define BEAM_TYPE_HAS_UPPER_BOUND (1 << 14)
+#define BEAM_TYPE_HAS_UNIT (1 << 15)
+
+#define BEAM_TYPE_METADATA_MASK (BEAM_TYPE_HAS_LOWER_BOUND | \
+ BEAM_TYPE_HAS_UPPER_BOUND | \
+ BEAM_TYPE_HAS_UNIT)
typedef struct {
/** @brief A set of the possible types (atom, tuple, etc) this term may
* be. When a single bit is set, the term will always be of that type. */
int type_union;
- /** @brief Minimum and maximum values. Only valid if min <= max. */
+ /** @brief A set of metadata presence flags, BEAM_TYPE_HAS_XYZ. */
+ int metadata_flags;
+
+ /** @brief Minimum numerical value. Only valid when the
+ * BEAM_TYPE_HAS_LOWER_BOUND metadata flag is present. */
Sint64 min;
+
+ /** @brief Maximum numerical value. Only valid when the
+ * BEAM_TYPE_HAS_UPPER_BOUND metadata flag is present. */
Sint64 max;
+
+ /** @brief Unit for bitstring size. Only valid when the BEAM_TYPE_HAS_UNIT
+ * metadata flag is present. */
+ byte size_unit;
} BeamType;
-int beam_types_decode(const byte *data, Uint size, BeamType *out);
+int beam_types_decode_type_otp_26(const byte *data, BeamType *out);
+void beam_types_decode_extra_otp_26(const byte *data, BeamType *out);
+int beam_types_decode_otp_25(const byte *data, Uint size, BeamType *out);
#endif
diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c
index 808b23090d..80eb260153 100644
--- a/erts/emulator/beam/bif.c
+++ b/erts/emulator/beam/bif.c
@@ -23,6 +23,10 @@
#endif
#include <stddef.h> /* offsetof() */
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#define WANT_NONBLOCKING
#include "sys.h"
#include "erl_vm.h"
#include "erl_sys_driver.h"
@@ -376,7 +380,6 @@ demonitor(Process *c_p, Eterm ref, Eterm *multip)
}
case ERTS_ML_STATE_ALIAS_ONCE:
case ERTS_ML_STATE_ALIAS_DEMONITOR:
- erts_pid_ref_delete(ref);
/* fall through... */
default:
erts_monitor_tree_delete(&ERTS_P_MONITORS(c_p), mon);
@@ -1896,8 +1899,8 @@ BIF_RETTYPE process_flag_2(BIF_ALIST_2)
BIF_RET(old_value);
}
else if (BIF_ARG_1 == am_max_heap_size) {
- Eterm *hp;
- Uint sz = 0, max_heap_size, max_heap_flags;
+ ErtsHeapFactory factory;
+ Uint max_heap_size, max_heap_flags;
if (!erts_max_heap_size(BIF_ARG_2, &max_heap_size, &max_heap_flags))
goto error;
@@ -1905,9 +1908,11 @@ BIF_RETTYPE process_flag_2(BIF_ALIST_2)
if ((max_heap_size < MIN_HEAP_SIZE(BIF_P) && max_heap_size != 0))
goto error;
- erts_max_heap_size_map(MAX_HEAP_SIZE_GET(BIF_P), MAX_HEAP_SIZE_FLAGS_GET(BIF_P), NULL, &sz);
- hp = HAlloc(BIF_P, sz);
- old_value = erts_max_heap_size_map(MAX_HEAP_SIZE_GET(BIF_P), MAX_HEAP_SIZE_FLAGS_GET(BIF_P), &hp, NULL);
+ erts_factory_proc_init(&factory, BIF_P);
+ old_value = erts_max_heap_size_map(&factory,
+ MAX_HEAP_SIZE_GET(BIF_P),
+ MAX_HEAP_SIZE_FLAGS_GET(BIF_P));
+ erts_factory_close(&factory);
MAX_HEAP_SIZE_SET(BIF_P, max_heap_size);
MAX_HEAP_SIZE_FLAGS_SET(BIF_P, max_heap_flags);
BIF_RET(old_value);
@@ -2637,6 +2642,7 @@ done:
and is only here to keep Robert happy (Even more, since it's OP as well) */
BIF_RETTYPE hd_1(BIF_ALIST_1)
{
+ /* NOTE: The JIT has its own implementation of this BIF. */
if (is_not_list(BIF_ARG_1)) {
BIF_ERROR(BIF_P, BADARG);
}
@@ -2649,6 +2655,7 @@ BIF_RETTYPE hd_1(BIF_ALIST_1)
BIF_RETTYPE tl_1(BIF_ALIST_1)
{
+ /* NOTE: The JIT has its own implementation of this BIF. */
if (is_not_list(BIF_ARG_1)) {
BIF_ERROR(BIF_P, BADARG);
}
@@ -2925,6 +2932,7 @@ BIF_RETTYPE element_2(BIF_ALIST_2)
BIF_RETTYPE tuple_size_1(BIF_ALIST_1)
{
+ /* NOTE: The JIT has its own implementation of this BIF. */
if (is_tuple(BIF_ARG_1)) {
return make_small(arityval(*tuple_val(BIF_ARG_1)));
}
@@ -3182,8 +3190,8 @@ BIF_RETTYPE list_to_atom_1(BIF_ALIST_1)
Eterm res;
byte *buf = (byte *) erts_alloc(ERTS_ALC_T_TMP, MAX_ATOM_SZ_LIMIT);
Sint written;
- int i = erts_unicode_list_to_buf(BIF_ARG_1, buf, MAX_ATOM_CHARACTERS,
- &written);
+ int i = erts_unicode_list_to_buf(BIF_ARG_1, buf, MAX_ATOM_SZ_LIMIT,
+ MAX_ATOM_CHARACTERS, &written);
if (i < 0) {
erts_free(ERTS_ALC_T_TMP, (void *) buf);
if (i == -2) {
@@ -3203,8 +3211,8 @@ BIF_RETTYPE list_to_existing_atom_1(BIF_ALIST_1)
{
byte *buf = (byte *) erts_alloc(ERTS_ALC_T_TMP, MAX_ATOM_SZ_LIMIT);
Sint written;
- int i = erts_unicode_list_to_buf(BIF_ARG_1, buf, MAX_ATOM_CHARACTERS,
- &written);
+ int i = erts_unicode_list_to_buf(BIF_ARG_1, buf, MAX_ATOM_SZ_LIMIT,
+ MAX_ATOM_CHARACTERS, &written);
if (i < 0) {
error:
erts_free(ERTS_ALC_T_TMP, (void *) buf);
@@ -4204,33 +4212,107 @@ BIF_RETTYPE erts_debug_display_1(BIF_ALIST_1)
BIF_RET(res);
}
-
-BIF_RETTYPE display_string_1(BIF_ALIST_1)
+BIF_RETTYPE display_string_2(BIF_ALIST_2)
{
Process* p = BIF_P;
- Eterm string = BIF_ARG_1;
- Sint len = erts_unicode_list_to_buf_len(string);
+ Eterm string = BIF_ARG_2;
+ Sint len;
Sint written;
byte *str;
int res;
+ byte *temp_alloc = NULL;
- if (len < 0) {
- BIF_ERROR(p, BADARG);
+#ifdef __WIN32__
+ HANDLE fd;
+ if (ERTS_IS_ATOM_STR("stdout", BIF_ARG_1)) {
+ fd = GetStdHandle(STD_OUTPUT_HANDLE);
+ } else if (ERTS_IS_ATOM_STR("stderr", BIF_ARG_1)) {
+ fd = GetStdHandle(STD_ERROR_HANDLE);
+ }
+#else
+ int fd;
+ if (ERTS_IS_ATOM_STR("stdout", BIF_ARG_1)) {
+ fd = fileno(stdout);
+ } else if (ERTS_IS_ATOM_STR("stderr", BIF_ARG_1)) {
+ fd = fileno(stderr);
+ }
+#if defined(HAVE_SYS_IOCTL_H) && defined(TIOCSTI)
+ else if (ERTS_IS_ATOM_STR("stdin", BIF_ARG_1)) {
+ const char stdin_fname[] = "/dev/tty";
+ fd = open(stdin_fname,0);
+ if (fd < 0) {
+ fprintf(stderr,"failed to open %s (%s)\r\n", stdin_fname,
+ strerror(errno));
+ goto error;
+ }
+ }
+#endif
+#endif
+ else {
+ BIF_ERROR(p, BADARG);
+ }
+ if (is_list(string) || is_nil(string)) {
+ len = erts_unicode_list_to_buf_len(string);
+ if (len < 0) BIF_ERROR(p, BADARG);
+ str = temp_alloc = (byte *) erts_alloc(ERTS_ALC_T_TMP, sizeof(char)*len);
+ res = erts_unicode_list_to_buf(string, str, len, len, &written);
+ if (res != 0 || written != len)
+ erts_exit(ERTS_ERROR_EXIT, "%s:%d: Internal error (%d)\n", __FILE__, __LINE__, res);
+ } else if (is_binary(string)) {
+ Uint bitoffs, bitsize;
+ ERTS_GET_BINARY_BYTES(string, str, bitoffs, bitsize);
+ if (bitsize % 8 != 0) BIF_ERROR(p, BADARG);
+ len = binary_size(string);
+ if (bitoffs != 0) {
+ str = erts_get_aligned_binary_bytes(string, &temp_alloc);
+ }
+ } else {
+ BIF_ERROR(p, BADARG);
}
- str = (byte *) erts_alloc(ERTS_ALC_T_TMP, sizeof(char)*(len + 1));
- res = erts_unicode_list_to_buf(string, str, len, &written);
- if (res != 0 || written != len)
- erts_exit(ERTS_ERROR_EXIT, "%s:%d: Internal error (%d)\n", __FILE__, __LINE__, res);
- str[len] = '\0';
- erts_fprintf(stderr, "%s", str);
- erts_free(ERTS_ALC_T_TMP, (void *) str);
- BIF_RET(am_true);
-}
-BIF_RETTYPE display_nl_0(BIF_ALIST_0)
-{
- erts_fprintf(stderr, "\n");
+#if defined(HAVE_SYS_IOCTL_H) && defined(TIOCSTI)
+ if (ERTS_IS_ATOM_STR("stdin", BIF_ARG_1)) {
+ for (int i = 0; i < len; i++) {
+ if (ioctl(fd, TIOCSTI, str+i) < 0) {
+ fprintf(stderr,"failed to write to %s (%s)\r\n", "/proc/self/fd/0",
+ strerror(errno));
+ close(fd);
+ goto error;
+ }
+ }
+ close(fd);
+ } else
+#endif
+ {
+#ifdef __WIN32__
+ if (!WriteFile(fd, str, len, &written, NULL)) {
+ goto error;
+ }
+#else
+ written = 0;
+ do {
+ res = write(fd, str+written, len-written);
+ if (res < 0 && errno != ERRNO_BLOCK && errno != EINTR)
+ goto error;
+ written += res;
+ } while (written < len);
+#endif
+ }
+ if (temp_alloc)
+ erts_free(ERTS_ALC_T_TMP, (void *) temp_alloc);
BIF_RET(am_true);
+
+error: {
+#ifdef __WIN32__
+ char *errnostr = last_error();
+#else
+ char *errnostr = erl_errno_id(errno);
+#endif
+ BIF_P->fvalue = am_atom_put(errnostr, strlen(errnostr));
+ if (temp_alloc)
+ erts_free(ERTS_ALC_T_TMP, (void *) temp_alloc);
+ BIF_ERROR(p, BADARG | EXF_HAS_EXT_INFO);
+ }
}
/**********************************************************************/
@@ -4298,8 +4380,8 @@ BIF_RETTYPE halt_2(BIF_ALIST_2)
static byte halt_msg[4*HALT_MSG_SIZE+1];
Sint written;
- if (erts_unicode_list_to_buf(BIF_ARG_1, halt_msg, HALT_MSG_SIZE,
- &written) == -1 ) {
+ if (erts_unicode_list_to_buf(BIF_ARG_1, halt_msg, 4 * HALT_MSG_SIZE,
+ HALT_MSG_SIZE, &written) == -1 ) {
goto error;
}
ASSERT(written >= 0 && written < sizeof(halt_msg));
@@ -4697,7 +4779,7 @@ BIF_RETTYPE list_to_ref_1(BIF_ALIST_1)
ErtsMagicBinary *mb;
Uint32 sid;
if (refn[0] >= MAX_REFERENCE) goto bad;
- if (n != ERTS_REF_NUMBERS) goto bad;
+ if (n != ERTS_REF_NUMBERS && n != ERTS_PID_REF_NUMBERS) goto bad;
sid = erts_get_ref_numbers_thr_id(refn);
if (sid > erts_no_schedulers) goto bad;
if (erts_is_ordinary_ref_numbers(refn)) {
@@ -4708,12 +4790,15 @@ BIF_RETTYPE list_to_ref_1(BIF_ALIST_1)
}
else {
/* Check if it is a pid reference... */
- Eterm pid = erts_pid_ref_lookup(refn);
+ Eterm pid = erts_pid_ref_lookup(refn, n);
if (is_internal_pid(pid)) {
hp = HAlloc(BIF_P, ERTS_PID_REF_THING_SIZE);
write_pid_ref_thing(hp, refn[0], refn[1], refn[2], pid);
res = make_internal_ref(hp);
}
+ else if (is_non_value(pid)) {
+ goto bad;
+ }
else {
/* Check if it is a magic reference... */
mb = erts_magic_ref_lookup_bin(refn);
@@ -5006,9 +5091,9 @@ BIF_RETTYPE system_flag_2(BIF_ALIST_2)
BIF_RET(make_small(oval));
} else if (BIF_ARG_1 == am_max_heap_size) {
-
- Eterm *hp, old_value;
- Uint sz = 0, max_heap_size, max_heap_flags;
+ ErtsHeapFactory factory;
+ Eterm old_value;
+ Uint max_heap_size, max_heap_flags;
if (!erts_max_heap_size(BIF_ARG_2, &max_heap_size, &max_heap_flags))
goto error;
@@ -5016,9 +5101,9 @@ BIF_RETTYPE system_flag_2(BIF_ALIST_2)
if (max_heap_size < H_MIN_SIZE && max_heap_size != 0)
goto error;
- erts_max_heap_size_map(H_MAX_SIZE, H_MAX_FLAGS, NULL, &sz);
- hp = HAlloc(BIF_P, sz);
- old_value = erts_max_heap_size_map(H_MAX_SIZE, H_MAX_FLAGS, &hp, NULL);
+ erts_factory_proc_init(&factory, BIF_P);
+ old_value = erts_max_heap_size_map(&factory, H_MAX_SIZE, H_MAX_FLAGS);
+ erts_factory_close(&factory);
erts_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
erts_thr_progress_block();
@@ -5440,7 +5525,7 @@ void erts_init_trap_export(Export *ep, Eterm m, Eterm f, Uint a,
}
#ifdef BEAMASM
- ep->dispatch.addresses[ERTS_SAVE_CALLS_CODE_IX] = beam_save_calls;
+ ep->dispatch.addresses[ERTS_SAVE_CALLS_CODE_IX] = beam_save_calls_export;
#endif
ep->bif_number = -1;
@@ -5830,8 +5915,6 @@ BIF_RETTYPE unalias_1(BIF_ALIST_1)
ASSERT(erts_monitor_is_origin(mon));
- erts_pid_ref_delete(BIF_ARG_1);
-
mon->flags &= ~ERTS_ML_STATE_ALIAS_MASK;
if (mon->type == ERTS_MON_TYPE_ALIAS) {
erts_monitor_tree_delete(&ERTS_P_MONITORS(BIF_P), mon);
diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab
index ebf815570c..e1ca5a961b 100644
--- a/erts/emulator/beam/bif.tab
+++ b/erts/emulator/beam/bif.tab
@@ -56,8 +56,7 @@ bif erlang:crc32_combine/3
bif erlang:date/0
bif erlang:delete_module/1
bif erlang:display/1
-bif erlang:display_string/1
-bif erlang:display_nl/0
+bif erlang:display_string/2
ubif erlang:element/2
bif erlang:erase/0
hbif erlang:erase/1
@@ -362,6 +361,7 @@ bif ets:first/1
bif ets:is_compiled_ms/1
bif ets:lookup/2
bif ets:lookup_element/3
+bif ets:lookup_element/4
bif ets:info/1
bif ets:info/2
bif ets:last/1
@@ -778,3 +778,9 @@ bif erlang:unalias/1
bif erlang:monitor/3
bif erlang:error/3
bif maps:from_keys/2
+
+#
+# New in 26
+#
+ubif erlang:min/2
+ubif erlang:max/2
diff --git a/erts/emulator/beam/break.c b/erts/emulator/beam/break.c
index 3b6de45587..9efc797481 100644
--- a/erts/emulator/beam/break.c
+++ b/erts/emulator/beam/break.c
@@ -401,12 +401,12 @@ print_process_info(fmtfn_t to, void *to_arg, Process *p, ErtsProcLocks orig_lock
erts_print(to, to_arg, "OldHeap unused: %bpu\n",
(OLD_HEAP(p) == NULL) ? 0 : (OLD_HEND(p) - OLD_HTOP(p)) );
erts_print(to, to_arg, "BinVHeap: %b64u\n", p->off_heap.overhead);
- erts_print(to, to_arg, "OldBinVHeap: %b64u\n", BIN_OLD_VHEAP(p));
+ erts_print(to, to_arg, "OldBinVHeap: %b64u\n", p->bin_old_vheap);
erts_print(to, to_arg, "BinVHeap unused: %b64u\n",
- BIN_VHEAP_SZ(p) - p->off_heap.overhead);
- if (BIN_OLD_VHEAP_SZ(p) >= BIN_OLD_VHEAP(p)) {
+ p->bin_vheap_sz - p->off_heap.overhead);
+ if (p->bin_old_vheap_sz >= p->bin_old_vheap) {
erts_print(to, to_arg, "OldBinVHeap unused: %b64u\n",
- BIN_OLD_VHEAP_SZ(p) - BIN_OLD_VHEAP(p));
+ p->bin_old_vheap_sz - p->bin_old_vheap);
} else {
erts_print(to, to_arg, "OldBinVHeap unused: overflow\n");
}
@@ -576,19 +576,29 @@ do_break(void)
" (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution\n";
int i;
#ifdef __WIN32__
+ char *clearscreen = "\033[J";
char *mode; /* enough for storing "window" */
/* check if we're in console mode and, if so,
halt immediately if break is called */
mode = erts_read_env("ERL_CONSOLE_MODE");
- if (mode && sys_strcmp(mode, "window") != 0)
+ if (mode && sys_strcmp(mode, "detached") == 0)
erts_exit(0, "");
erts_free_read_env(mode);
-#endif /* __WIN32__ */
+#else
+ char *clearscreen = "\E[J";
+#endif
ASSERT(erts_thr_progress_is_blocking());
- erts_printf("\n%s", helpstring);
+ /* If we are writing to something known to be a tty we clear the screen
+ after doing newline as the shell tab completion may have written
+ things there. */
+ if (!isatty(fileno(stdin)) || !isatty(fileno(stdout))) {
+ clearscreen = "";
+ }
+
+ erts_printf("\n%s%s", clearscreen, helpstring);
while (1) {
if ((i = sys_get_key(0)) <= 0)
@@ -781,7 +791,7 @@ crash_dump_limited_writer(void* vfdp, char* buf, size_t len)
}
/* We assume that crash dump was called from erts_exit_vv() */
- erts_exit_epilogue();
+ erts_exit_epilogue(0);
}
/* XXX THIS SHOULD BE IN SYSTEM !!!! */
diff --git a/erts/emulator/beam/code_ix.c b/erts/emulator/beam/code_ix.c
index 8f393f92cf..3888c72363 100644
--- a/erts/emulator/beam/code_ix.c
+++ b/erts/emulator/beam/code_ix.c
@@ -46,20 +46,24 @@ erts_atomic32_t outstanding_blocking_code_barriers;
erts_atomic32_t the_active_code_index;
erts_atomic32_t the_staging_code_index;
-static int code_writing_seized = 0;
-static Process* code_writing_process = NULL;
-struct code_write_queue_item {
- Process *p;
- void (*aux_func)(void *);
- void *aux_arg;
- struct code_write_queue_item* next;
+struct code_permission {
+ erts_mtx_t lock;
+
+ ErtsSchedulerData *scheduler;
+ Process *owner;
+
+ int seized;
+ struct code_permission_queue_item {
+ Process *p;
+ void (*aux_func)(void *);
+ void *aux_arg;
+
+ struct code_permission_queue_item *next;
+ } *queue;
};
-static struct code_write_queue_item* code_write_queue = NULL;
-static erts_mtx_t code_write_permission_mtx;
-#ifdef ERTS_ENABLE_LOCK_CHECK
-static erts_tsd_key_t has_code_write_permission;
-#endif
+static struct code_permission code_mod_permission = {0};
+static struct code_permission code_stage_permission = {0};
#ifdef DEBUG
static erts_tsd_key_t needs_code_barrier;
@@ -74,12 +78,14 @@ void erts_code_ix_init(void)
erts_atomic32_init_nob(&outstanding_blocking_code_barriers, 0);
erts_atomic32_init_nob(&the_active_code_index, 0);
erts_atomic32_init_nob(&the_staging_code_index, 0);
- erts_mtx_init(&code_write_permission_mtx, "code_write_permission", NIL,
+
+ erts_mtx_init(&code_mod_permission.lock,
+ "code_mod_permission", NIL,
ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
-#ifdef ERTS_ENABLE_LOCK_CHECK
- erts_tsd_key_create(&has_code_write_permission,
- "erts_has_code_write_permission");
-#endif
+ erts_mtx_init(&code_stage_permission.lock,
+ "code_stage_permission", NIL,
+ ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
+
#ifdef DEBUG
erts_tsd_key_create(&needs_code_barrier,
"erts_needs_code_barrier");
@@ -90,16 +96,17 @@ void erts_code_ix_init(void)
void erts_start_staging_code_ix(int num_new)
{
beam_catches_start_staging();
+ erts_fun_start_staging();
export_start_staging();
module_start_staging();
erts_start_staging_ranges(num_new);
CIX_TRACE("start");
}
-
void erts_end_staging_code_ix(void)
{
beam_catches_end_staging(1);
+ erts_fun_end_staging(1);
export_end_staging(1);
module_end_staging(1);
erts_end_staging_ranges(1);
@@ -123,122 +130,235 @@ void erts_commit_staging_code_ix(void)
void erts_abort_staging_code_ix(void)
{
beam_catches_end_staging(0);
+ erts_fun_end_staging(0);
export_end_staging(0);
module_end_staging(0);
erts_end_staging_ranges(0);
CIX_TRACE("abort");
}
-static int try_seize_cwp(Process* c_p,
- void (*aux_func)(void *),
- void *aux_arg);
-
#if defined(DEBUG) || defined(ADDRESS_SANITIZER)
# define CWP_DBG_FORCE_TRAP
#endif
-/*
- * Calller _must_ yield if we return 0
- */
-int erts_try_seize_code_write_permission(Process* c_p)
-{
- ASSERT(c_p != NULL);
-
-#ifdef CWP_DBG_FORCE_TRAP
- if (!(c_p->flags & F_DBG_FORCED_TRAP)) {
- c_p->flags |= F_DBG_FORCED_TRAP;
- return 0;
- } else {
- /* back from forced trap */
- c_p->flags &= ~F_DBG_FORCED_TRAP;
- }
+#ifdef ERTS_ENABLE_LOCK_CHECK
+static int has_code_permission(struct code_permission *lock);
#endif
- return try_seize_cwp(c_p, NULL, NULL);
-}
-
-int erts_try_seize_code_write_permission_aux(void (*aux_func)(void *),
- void *aux_arg)
-{
- ASSERT(aux_func != NULL);
- return try_seize_cwp(NULL, aux_func, aux_arg);
-}
-
-static int try_seize_cwp(Process* c_p,
- void (*aux_func)(void *),
- void *aux_arg)
+static int try_seize_code_permission(struct code_permission *perm,
+ Process* c_p,
+ void (*aux_func)(void *),
+ void *aux_arg)
{
int success;
- ASSERT(!erts_thr_progress_is_blocking()); /* to avoid deadlock */
- erts_mtx_lock(&code_write_permission_mtx);
- success = !code_writing_seized;
+ ASSERT(!erts_thr_progress_is_blocking()); /* To avoid deadlock */
+
+ erts_mtx_lock(&perm->lock);
+ success = !perm->seized;
+
if (success) {
- code_writing_seized = 1;
- code_writing_process = c_p;
-#ifdef ERTS_ENABLE_LOCK_CHECK
- erts_tsd_set(has_code_write_permission, (void *) 1);
-#endif
- }
- else { /* Already locked */
- struct code_write_queue_item* qitem;
+ if (c_p == NULL) {
+ ASSERT(aux_func);
+ perm->scheduler = erts_get_scheduler_data();
+ }
+
+ perm->owner = c_p;
+ perm->seized = 1;
+ } else { /* Already locked */
+ struct code_permission_queue_item* qitem;
+
qitem = erts_alloc(ERTS_ALC_T_CODE_IX_LOCK_Q, sizeof(*qitem));
if (c_p) {
- ASSERT(code_writing_process != c_p);
+ ERTS_LC_ASSERT(perm->owner != c_p);
+
qitem->p = c_p;
qitem->aux_func = NULL;
qitem->aux_arg = NULL;
erts_proc_inc_refc(c_p);
erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL);
- }
- else {
+ } else {
qitem->p = NULL;
qitem->aux_func = aux_func;
qitem->aux_arg = aux_arg;
}
- qitem->next = code_write_queue;
- code_write_queue = qitem;
+
+ qitem->next = perm->queue;
+ perm->queue = qitem;
}
- erts_mtx_unlock(&code_write_permission_mtx);
- return success;
+
+ erts_mtx_unlock(&perm->lock);
+
+ return success;
}
-void erts_release_code_write_permission(void)
-{
- erts_mtx_lock(&code_write_permission_mtx);
- ERTS_LC_ASSERT(erts_has_code_write_permission());
- while (code_write_queue != NULL) { /* unleash the entire herd */
- struct code_write_queue_item* qitem = code_write_queue;
+static void release_code_permission(struct code_permission *perm) {
+ ERTS_LC_ASSERT(has_code_permission(perm));
+
+ erts_mtx_lock(&perm->lock);
+
+ /* Unleash the entire herd */
+ while (perm->queue != NULL) {
+ struct code_permission_queue_item* qitem = perm->queue;
+
if (qitem->p) {
erts_proc_lock(qitem->p, ERTS_PROC_LOCK_STATUS);
+
if (!ERTS_PROC_IS_EXITING(qitem->p)) {
erts_resume(qitem->p, ERTS_PROC_LOCK_STATUS);
}
+
erts_proc_unlock(qitem->p, ERTS_PROC_LOCK_STATUS);
erts_proc_dec_refc(qitem->p);
- }
- else { /* aux work*/
+ } else { /* aux work */
ErtsSchedulerData *esdp = erts_get_scheduler_data();
ASSERT(esdp && esdp->type == ERTS_SCHED_NORMAL);
erts_schedule_misc_aux_work((int) esdp->no,
qitem->aux_func,
qitem->aux_arg);
}
- code_write_queue = qitem->next;
- erts_free(ERTS_ALC_T_CODE_IX_LOCK_Q, qitem);
+
+ perm->queue = qitem->next;
+ erts_free(ERTS_ALC_T_CODE_IX_LOCK_Q, qitem);
+ }
+
+ perm->scheduler = NULL;
+ perm->owner = NULL;
+ perm->seized = 0;
+
+ erts_mtx_unlock(&perm->lock);
+}
+
+int erts_try_seize_code_mod_permission_aux(void (*aux_func)(void *),
+ void *aux_arg)
+{
+ ASSERT(aux_func != NULL);
+ return try_seize_code_permission(&code_mod_permission, NULL,
+ aux_func, aux_arg);
+}
+
+int erts_try_seize_code_mod_permission(Process* c_p)
+{
+ ASSERT(c_p != NULL);
+
+#ifdef CWP_DBG_FORCE_TRAP
+ if (!(c_p->flags & F_DBG_FORCED_TRAP)) {
+ c_p->flags |= F_DBG_FORCED_TRAP;
+ return 0;
+ } else {
+ /* back from forced trap */
+ c_p->flags &= ~F_DBG_FORCED_TRAP;
+ }
+#endif
+
+ return try_seize_code_permission(&code_mod_permission, c_p, NULL, NULL);
+}
+
+void erts_release_code_mod_permission(void)
+{
+ release_code_permission(&code_mod_permission);
+}
+
+int erts_try_seize_code_stage_permission(Process* c_p)
+{
+ ASSERT(c_p != NULL);
+
+#ifdef CWP_DBG_FORCE_TRAP
+ if (!(c_p->flags & F_DBG_FORCED_TRAP)) {
+ c_p->flags |= F_DBG_FORCED_TRAP;
+ return 0;
+ } else {
+ /* back from forced trap */
+ c_p->flags &= ~F_DBG_FORCED_TRAP;
+ }
+#endif
+
+ return try_seize_code_permission(&code_stage_permission, c_p, NULL, NULL);
+}
+
+void erts_release_code_stage_permission(void) {
+ release_code_permission(&code_stage_permission);
+}
+
+int erts_try_seize_code_load_permission(Process* c_p) {
+ ASSERT(c_p != NULL);
+
+#ifdef CWP_DBG_FORCE_TRAP
+ if (!(c_p->flags & F_DBG_FORCED_TRAP)) {
+ c_p->flags |= F_DBG_FORCED_TRAP;
+ return 0;
+ } else {
+ /* back from forced trap */
+ c_p->flags &= ~F_DBG_FORCED_TRAP;
}
- code_writing_seized = 0;
- code_writing_process = NULL;
-#ifdef ERTS_ENABLE_LOCK_CHECK
- erts_tsd_set(has_code_write_permission, (void *) 0);
#endif
- erts_mtx_unlock(&code_write_permission_mtx);
+
+ if (try_seize_code_permission(&code_stage_permission, c_p, NULL, NULL)) {
+ if (try_seize_code_permission(&code_mod_permission, c_p, NULL, NULL)) {
+ return 1;
+ }
+
+ erts_release_code_stage_permission();
+ }
+
+ return 0;
+}
+
+void erts_release_code_load_permission(void) {
+ erts_release_code_mod_permission();
+ erts_release_code_stage_permission();
}
#ifdef ERTS_ENABLE_LOCK_CHECK
-int erts_has_code_write_permission(void)
+static int has_code_permission(struct code_permission *perm)
{
- return code_writing_seized && erts_tsd_get(has_code_write_permission);
+ const ErtsSchedulerData *esdp = erts_get_scheduler_data();
+
+ if (esdp && esdp->type == ERTS_SCHED_NORMAL) {
+ int res;
+
+ erts_mtx_lock(&perm->lock);
+
+ res = perm->seized;
+
+ if (esdp->current_process != NULL) {
+ /* If we're running a process, it has to match the owner of the
+ * permission. We don't care about which scheduler we are running
+ * on in order to support holding permissions when yielding (such
+ * as in code purging). */
+ res &= perm->owner == esdp->current_process;
+ } else {
+ /* If we're running an aux job, we crudely assume that this current
+ * job was started by the owner if there is one, and therefore has
+ * permission.
+ *
+ * If we don't have an owner, we assume that we have permission if
+ * we're running on the same scheduler that started the job.
+ *
+ * This is very blunt and only catches _some_ cases where we lack
+ * lack permission, but at least it's better than the old method of
+ * using thread-specific-data. */
+ res &= perm->owner || perm->scheduler == esdp;
+ }
+
+ erts_mtx_unlock(&perm->lock);
+
+ return res;
+ }
+
+ return 0;
+}
+
+int erts_has_code_load_permission() {
+ return erts_has_code_stage_permission() && erts_has_code_mod_permission();
+}
+
+int erts_has_code_stage_permission() {
+ return has_code_permission(&code_stage_permission);
+}
+
+int erts_has_code_mod_permission() {
+ return has_code_permission(&code_mod_permission);
}
#endif
@@ -278,29 +398,7 @@ static void issue_instruction_barrier(void *barrier_) {
ERTS_THR_INSTRUCTION_BARRIER;
if (erts_refc_dectest(&barrier->pending_schedulers, 0) == 0) {
-# ifdef ERTS_ENABLE_LOCK_CHECK
- ErtsSchedulerData *initial_esdp = (ErtsSchedulerData*)barrier->esdp;
-
- /* HACK: Dodges a broken lock-checking assertion, which requires that
- * the _thread_ that takes a code permission is also the one that
- * releases it.
- *
- * This has never held since processes can migrate between schedulers,
- * but we have to roll with the punches. Commit the code on the
- * scheduler that called `erts_schedule_code_barrier` in the hopes that
- * it's the right one.
- *
- * This has been fixed in `master`. */
- if (initial_esdp && initial_esdp != erts_get_scheduler_data()) {
- erts_schedule_misc_aux_work(initial_esdp->no,
- schedule_code_barrier_later_op,
- barrier);
- } else {
- schedule_code_barrier_later_op(barrier);
- }
-# else
schedule_code_barrier_later_op(barrier);
-# endif
}
}
#endif
@@ -320,7 +418,6 @@ void erts_schedule_code_barrier_cleanup(ErtsCodeBarrier *barrier,
erts_debug_unrequire_code_barrier();
#endif
- barrier->esdp = erts_get_scheduler_data();
barrier->later_function = later_function;
barrier->later_data = later_data;
barrier->size = size;
@@ -377,7 +474,7 @@ static void schedule_blocking_code_barriers(void *ignored) {
}
#endif
-void erts_blocking_code_barrier()
+void erts_blocking_code_barrier(void)
{
#ifdef DEBUG
erts_debug_unrequire_code_barrier();
@@ -390,7 +487,7 @@ void erts_blocking_code_barrier()
#endif
}
-void erts_code_ix_finalize_wait() {
+void erts_code_ix_finalize_wait(void) {
#ifdef CODE_IX_ISSUE_INSTRUCTION_BARRIERS
if (erts_atomic32_read_nob(&outstanding_blocking_code_barriers) != 0) {
ERTS_THR_INSTRUCTION_BARRIER;
diff --git a/erts/emulator/beam/code_ix.h b/erts/emulator/beam/code_ix.h
index f557ff2980..54ceefcc9b 100644
--- a/erts/emulator/beam/code_ix.h
+++ b/erts/emulator/beam/code_ix.h
@@ -121,7 +121,6 @@ typedef struct ErtsCodeInfo_ {
typedef struct {
erts_refc_t pending_schedulers;
ErtsThrPrgrLaterOp later_op;
- void *esdp;
UWord size;
void (*later_function)(void *);
@@ -158,37 +157,76 @@ ErtsCodeIndex erts_active_code_ix(void);
/* Return staging code ix.
* Only used by a process performing code loading/upgrading/deleting/purging.
- * Code write permission must be seized.
+ * Code staging permission must be seized.
*/
ERTS_GLB_INLINE
ErtsCodeIndex erts_staging_code_ix(void);
-/* Try seize exclusive code write permission. Needed for code staging.
+/** @brief Try to seize exclusive code loading permission. That is, both
+ * staging and modification permission.
+ *
* Main process lock (only) must be held.
* System thread progress must not be blocked.
- * Caller must not already hold the code write permission.
- * Caller is suspended and *must* yield if 0 is returned.
+ * Caller must not already have the code modification or staging permissions.
+ * Caller is suspended and *must* yield if 0 is returned. */
+int erts_try_seize_code_load_permission(struct process* c_p);
+
+/** @brief Release code loading permission. Resumes any suspended waiters. */
+void erts_release_code_load_permission(void);
+
+/** @brief Try to seize exclusive code staging permission. Needed for code
+ * loading and purging.
+ *
+ * This is kept separate from code modification permission to allow tracing and
+ * similar during long-running purge operations.
+ *
+ * * Main process lock (only) must be held.
+ * * System thread progress must not be blocked.
+ * * Caller is suspended and *must* yield if 0 is returned.
+ * * Caller must not already have the code modification or staging permissions.
+ *
+ * That is, it is _NOT_ possible to add code modification permission when you
+ * already have staging permission. The other way around is fine however.
+ */
+int erts_try_seize_code_stage_permission(struct process* c_p);
+
+/** @brief Release code stage permission. Resumes any suspended waiters. */
+void erts_release_code_stage_permission(void);
+
+/** @brief Try to seize exclusive code modification permission. Needed for
+ * tracing, breakpoints, and so on.
+ *
+ * This used to be called code_write_permission, but was renamed to break
+ * merges of code that uses the old locking paradigm.
+ *
+ * * Main process lock (only) must be held.
+ * * System thread progress must not be blocked.
+ * * Caller is suspended and *must* yield if 0 is returned.
+ * * Caller must not already have the code modification permission, but may
+ * have staging permission.
*/
-int erts_try_seize_code_write_permission(struct process* c_p);
+int erts_try_seize_code_mod_permission(struct process* c_p);
-/* Try seize exclusive code write permission for aux work.
+/** @brief As \c erts_try_seize_code_mod_permission but for aux work.
+ *
* System thread progress must not be blocked.
* On success return true.
* On failure return false and aux work func(arg) will be scheduled when
- * permission is released. .
+ * permission is released.
*/
-int erts_try_seize_code_write_permission_aux(void (*func)(void *),
- void *arg);
+int erts_try_seize_code_mod_permission_aux(void (*func)(void *),
+ void *arg);
-/* Release code write permission.
- * Will resume any suspended waiters.
- */
-void erts_release_code_write_permission(void);
+/** @brief Release code modification permission. Resumes any suspended
+ * waiters. */
+void erts_release_code_mod_permission(void);
/* Prepare the "staging area" to be a complete copy of the active code.
- * Code write permission must have been seized.
+ *
+ * Code staging permission must have been seized.
+ *
* Must be followed by calls to either "end" and "commit" or "abort" before
- * code write permission can be released.
+ * code staging permission can be released.
*/
void erts_start_staging_code_ix(int num_new);
@@ -234,7 +272,9 @@ void erts_blocking_code_barrier(void);
void erts_code_ix_finalize_wait(void);
#ifdef ERTS_ENABLE_LOCK_CHECK
-int erts_has_code_write_permission(void);
+int erts_has_code_load_permission(void);
+int erts_has_code_stage_permission(void);
+int erts_has_code_mod_permission(void);
#endif
/* module/function/arity can be NIL/NIL/-1 when the MFA is pointing to some
diff --git a/erts/emulator/beam/copy.c b/erts/emulator/beam/copy.c
index 79f90fbcb0..a22317f6c1 100644
--- a/erts/emulator/beam/copy.c
+++ b/erts/emulator/beam/copy.c
@@ -619,7 +619,6 @@ cleanup:
return sum;
}
-
/*
* Copy a structure to a heap.
*/
@@ -1932,19 +1931,52 @@ all_clean:
* pointers are offsetted to point correctly in the new location.
*
* Typically used to copy a term from an ets table.
+ */
+Eterm copy_shallow_obj_x(Eterm obj, Uint sz, Eterm **hpp, ErlOffHeap *off_heap
+#ifdef ERTS_COPY_REGISTER_LOCATION
+ ,
+ char *file, int line
+#endif
+) {
+ Eterm *source_ptr;
+ Eterm *target_ptr;
+
+ if (sz == 0) {
+ ASSERT(is_zero_sized(obj));
+ return obj;
+ }
+
+ ASSERT(is_boxed(obj) || is_list(obj));
+ ASSERT(!is_zero_sized(obj));
+
+ source_ptr = ptr_val(obj);
+#ifdef ERTS_COPY_REGISTER_LOCATION
+ target_ptr = copy_shallow_x(source_ptr, sz, hpp, off_heap, file, line);
+#else
+ target_ptr = copy_shallow_x(source_ptr, sz, hpp, off_heap);
+#endif
+
+ return is_boxed(obj) ? make_boxed(target_ptr) : make_list(target_ptr);
+}
+
+
+/*
+ * Copy a term that is guaranteed to be contained in a single
+ * heap block. The heap block is copied word by word, and any
+ * pointers are offsetted to point correctly in the new location.
*
- * NOTE: Assumes that term is a tuple (ptr is an untagged tuple ptr).
+ * Typically used to copy a term from an ets table.
*/
-Eterm
-copy_shallow_x(Eterm* ERTS_RESTRICT ptr, Uint sz, Eterm** hpp, ErlOffHeap* off_heap
+Eterm* copy_shallow_x(Eterm *ERTS_RESTRICT ptr, Uint sz, Eterm **hpp,
+ ErlOffHeap *off_heap
#ifdef ERTS_COPY_REGISTER_LOCATION
- , char *file, int line
+ ,
+ char *file, int line
#endif
- )
-{
- Eterm* tp = ptr;
- Eterm* hp = *hpp;
- const Eterm res = make_tuple(hp);
+) {
+ Eterm *tp = ptr;
+ Eterm *hp = *hpp;
+ Eterm* res = hp;
const Sint offs = (hp - tp) * sizeof(Eterm);
const Eterm empty_tuple_literal =
ERTS_GLOBAL_LIT_EMPTY_TUPLE;
diff --git a/erts/emulator/beam/dist.c b/erts/emulator/beam/dist.c
index 2a2c4fd31b..158f7116a2 100644
--- a/erts/emulator/beam/dist.c
+++ b/erts/emulator/beam/dist.c
@@ -1449,28 +1449,14 @@ erts_dsig_send_unlink(ErtsDSigSendContext *ctx, Eterm local, Eterm remote, Uint6
Eterm big_heap[ERTS_MAX_UINT64_HEAP_SIZE];
Eterm unlink_id;
Eterm ctl;
- if (ctx->dflags & DFLAG_UNLINK_ID) {
- if (IS_USMALL(0, id))
- unlink_id = make_small(id);
- else {
- Eterm *hp = &big_heap[0];
- unlink_id = erts_uint64_to_big(id, &hp);
- }
- ctl = TUPLE4(&ctx->ctl_heap[0], make_small(DOP_UNLINK_ID),
- unlink_id, local, remote);
- }
+ if (IS_USMALL(0, id))
+ unlink_id = make_small(id);
else {
- /*
- * A node that isn't capable of talking the new link protocol.
- *
- * Send an old unlink op, and send ourselves an unlink-ack. We may
- * end up in an inconsistent state as we could before the new link
- * protocol was introduced...
- */
- erts_proc_sig_send_dist_unlink_ack(ctx->dep, ctx->connection_id,
- remote, local, id);
- ctl = TUPLE3(&ctx->ctl_heap[0], make_small(DOP_UNLINK), local, remote);
+ Eterm *hp = &big_heap[0];
+ unlink_id = erts_uint64_to_big(id, &hp);
}
+ ctl = TUPLE4(&ctx->ctl_heap[0], make_small(DOP_UNLINK_ID),
+ unlink_id, local, remote);
return dsig_send_ctl(ctx, ctl);
}
@@ -1481,11 +1467,6 @@ erts_dsig_send_unlink_ack(ErtsDSigSendContext *ctx, Eterm local, Eterm remote, U
Eterm unlink_id;
Eterm ctl;
- if (!(ctx->dflags & DFLAG_UNLINK_ID)) {
- /* Receiving node does not understand it, so drop it... */
- return ERTS_DSIG_SEND_OK;
- }
-
if (IS_USMALL(0, id))
unlink_id = make_small(id);
else {
@@ -2269,6 +2250,13 @@ int erts_net_message(Port *prt,
break;
}
+ case DOP_UNLINK:
+ /*
+ * DOP_UNLINK should never be passed. The new link protocol is
+ * mandatory as of OTP 26.
+ */
+ goto invalid_message;
+
case DOP_UNLINK_ID: {
Eterm *element;
Uint64 id;
@@ -2282,14 +2270,6 @@ int erts_net_message(Port *prt,
if (id == 0)
goto invalid_message;
- if (0) {
- case DOP_UNLINK:
- if (tuple_arity != 3)
- goto invalid_message;
- element = &tuple[2];
- id = 0;
- }
-
from = *(element++);
to = *element;
if (is_not_external_pid(from))
@@ -4904,7 +4884,7 @@ BIF_RETTYPE setnode_2(BIF_ALIST_2)
erts_thr_progress_block();
success = (!ERTS_PROC_IS_EXITING(net_kernel)
- & !ERTS_PROC_GET_DIST_ENTRY(net_kernel));
+ && !ERTS_PROC_GET_DIST_ENTRY(net_kernel));
if (success) {
/*
* Ensure we don't use a nodename-creation pair with
@@ -5622,6 +5602,9 @@ int erts_auto_connect(DistEntry* dep, Process *proc, ErtsProcLocks proc_locks)
return 0;
}
+ if (proc == net_kernel)
+ nk_locks |= ERTS_PROC_LOCK_MAIN;
+
/*
* Send {auto_connect, Node, DHandle} to net_kernel
*/
@@ -5632,6 +5615,10 @@ int erts_auto_connect(DistEntry* dep, Process *proc, ErtsProcLocks proc_locks)
msg = TUPLE3(hp, am_auto_connect, dep->sysname, dhandle);
ERL_MESSAGE_TOKEN(mp) = am_undefined;
erts_queue_proc_message(proc, net_kernel, nk_locks, mp, msg);
+
+ if (proc == net_kernel)
+ nk_locks &= ~ERTS_PROC_LOCK_MAIN;
+
erts_proc_unlock(net_kernel, nk_locks);
}
@@ -5924,10 +5911,10 @@ BIF_RETTYPE erts_internal_dist_spawn_request_4(BIF_ALIST_4)
ok_result = ref;
else {
Eterm *hp = HAlloc(BIF_P, 3);
- Eterm bool = ((monitor_oflags & ERTS_ML_FLG_SPAWN_MONITOR)
+ Eterm spawns_monitor = ((monitor_oflags & ERTS_ML_FLG_SPAWN_MONITOR)
? am_true : am_false);
ASSERT(BIF_ARG_4 == am_spawn_opt);
- ok_result = TUPLE2(hp, ref, bool);
+ ok_result = TUPLE2(hp, ref, spawns_monitor);
}
code = erts_dsig_prepare(&ctx, dep,
@@ -6066,7 +6053,8 @@ send_error:
/* node(Object) -> Node */
BIF_RETTYPE node_1(BIF_ALIST_1)
-{
+{
+ /* NOTE: The JIT has its own implementation of this BIF. */
if (is_not_node_container(BIF_ARG_1))
BIF_ERROR(BIF_P, BADARG);
BIF_RET(node_container_node_name(BIF_ARG_1));
@@ -6294,7 +6282,8 @@ nodes(Process *c_p, Eterm node_types, Eterm options)
vs[map_size++] = eni->type;
}
- info_map = erts_map_from_sorted_ks_and_vs(&hfact, ks, vs, map_size);
+ info_map = erts_map_from_ks_and_vs(&hfact, ks, vs, map_size);
+ ASSERT(is_value(info_map));
hp = erts_produce_heap(&hfact, 3+2, xtra);
@@ -6880,7 +6869,7 @@ send_nodes_mon_msgs(Process *c_p, Eterm what, Eterm node,
map_size++;
}
- info = erts_map_from_sorted_ks_and_vs(&hfact, ks, vs, map_size);
+ info = erts_map_from_ks_and_vs(&hfact, ks, vs, map_size);
ASSERT(is_value(info));
}
else { /* Info list */
diff --git a/erts/emulator/beam/dist.h b/erts/emulator/beam/dist.h
index 7dabdd4961..475d87a09f 100644
--- a/erts/emulator/beam/dist.h
+++ b/erts/emulator/beam/dist.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,6 +63,7 @@
#define DFLAG_NAME_ME (((Uint64)0x2) << 32)
#define DFLAG_V4_NC (((Uint64)0x4) << 32)
#define DFLAG_ALIAS (((Uint64)0x8) << 32)
+#define DFLAG_LOCAL_EXT (((Uint64)0x10) << 32) /* internal */
/*
* In term_to_binary/2, we will use DFLAG_ATOM_CACHE to mean
@@ -84,8 +85,13 @@
| DFLAG_BIT_BINARIES \
| DFLAG_HANDSHAKE_23)
+/* New mandatory flags for distribution in OTP 26 */
+#define DFLAG_DIST_MANDATORY_26 (DFLAG_V4_NC \
+ | DFLAG_UNLINK_ID)
+
/* Mandatory flags for distribution. */
-#define DFLAG_DIST_MANDATORY DFLAG_DIST_MANDATORY_25
+#define DFLAG_DIST_MANDATORY (DFLAG_DIST_MANDATORY_25 \
+ | DFLAG_DIST_MANDATORY_26)
/*
* Additional optimistic flags when encoding toward pending connection.
@@ -95,8 +101,7 @@
#define DFLAG_DIST_HOPEFULLY (DFLAG_DIST_MONITOR \
| DFLAG_DIST_MONITOR_NAME \
| DFLAG_SPAWN \
- | DFLAG_ALIAS \
- | DFLAG_UNLINK_ID)
+ | DFLAG_ALIAS)
/* Our preferred set of flags. Used for connection setup handshake */
#define DFLAG_DIST_DEFAULT (DFLAG_DIST_MANDATORY | DFLAG_DIST_HOPEFULLY \
@@ -108,9 +113,7 @@
| DFLAG_EXIT_PAYLOAD \
| DFLAG_FRAGMENTS \
| DFLAG_SPAWN \
- | DFLAG_V4_NC \
| DFLAG_ALIAS \
- | DFLAG_UNLINK_ID \
| DFLAG_MANDATORY_25_DIGEST)
/* Flags addable by local distr implementations */
@@ -126,7 +129,8 @@
#define DFLAG_DIST_STRICT_ORDER DFLAG_DIST_HDR_ATOM_CACHE
/* All flags that should be enabled when term_to_binary/1 is used. */
-#define TERM_TO_BINARY_DFLAGS DFLAG_NEW_FLOATS
+#define TERM_TO_BINARY_DFLAGS (DFLAG_NEW_FLOATS \
+ | DFLAG_UTF8_ATOMS)
/* opcodes used in distribution messages */
enum dop {
@@ -279,10 +283,17 @@ typedef struct TTBEncodeContext_ {
SysIOVec* iov;
ErlDrvBinary** binv;
Eterm *termv;
- int iovec;
Uint fragment_size;
Sint frag_ix;
ErlIOVec *fragment_eiovs;
+ int iovec;
+ int continue_make_lext_hash;
+ int lext_vlen;
+ byte *lext_hash;
+ union {
+ ErtsBlockHashState block;
+ ErtsIovBlockHashState iov_block;
+ } lext_state;
#ifdef DEBUG
int debug_fragments;
int debug_vlen;
@@ -302,6 +313,8 @@ typedef struct TTBEncodeContext_ {
(Ctx)->iov = NULL; \
(Ctx)->binv = NULL; \
(Ctx)->fragment_size = ~((Uint) 0); \
+ (Ctx)->continue_make_lext_hash = 0; \
+ (Ctx)->lext_vlen = -1; \
if ((Flags) & DFLAG_PENDING_CONNECT) { \
(Ctx)->hopefull_flags = 0; \
(Ctx)->hopefull_flagsp = NULL; \
diff --git a/erts/emulator/beam/emu/beam_emu.c b/erts/emulator/beam/emu/beam_emu.c
index 258a61e480..0c91c1ec83 100644
--- a/erts/emulator/beam/emu/beam_emu.c
+++ b/erts/emulator/beam/emu/beam_emu.c
@@ -138,9 +138,9 @@ ErtsCodePtr beam_return_trace;
static BeamInstr beam_exception_trace_[1];
ErtsCodePtr beam_exception_trace;
-/* OpCode(i_return_time_trace) */
-static BeamInstr beam_return_time_trace_[1];
-ErtsCodePtr beam_return_time_trace;
+/* OpCode(i_call_trace_return) */
+static BeamInstr beam_call_trace_return_[1];
+ErtsCodePtr beam_call_trace_return;
/* The address field of every fun that has no loaded code will point to
* beam_unloaded_fun[]. The -1 in beam_unloaded_fun[0] will be interpreted
@@ -313,6 +313,8 @@ void process_main(ErtsSchedulerData *esdp)
#endif
#endif
+ Uint bitdata = 0;
+
Uint64 start_time = 0; /* Monitor long schedule */
ErtsCodePtr start_time_i = NULL;
@@ -694,8 +696,8 @@ init_emulator_finish(void)
beam_exception_trace_[0] = BeamOpCodeAddr(op_return_trace); /* UGLY */
beam_exception_trace = (ErtsCodePtr)&beam_exception_trace_[0];
- beam_return_time_trace_[0] = BeamOpCodeAddr(op_i_return_time_trace);
- beam_return_time_trace = (ErtsCodePtr)&beam_return_time_trace_[0];
+ beam_call_trace_return_[0] = BeamOpCodeAddr(op_i_call_trace_return);
+ beam_call_trace_return = (ErtsCodePtr)&beam_call_trace_return_[0];
install_bifs();
}
diff --git a/erts/emulator/beam/emu/bif_instrs.tab b/erts/emulator/beam/emu/bif_instrs.tab
index 9f90bf6600..d1ec68168a 100644
--- a/erts/emulator/beam/emu/bif_instrs.tab
+++ b/erts/emulator/beam/emu/bif_instrs.tab
@@ -628,12 +628,12 @@ nif_bif.epilogue() {
i_load_nif() {
//| -no_next
- if (erts_try_seize_code_write_permission(c_p)) {
+ if (erts_try_seize_code_mod_permission(c_p)) {
Eterm result;
PRE_BIF_SWAPOUT(c_p);
result = erts_load_nif(c_p, I, r(0), r(1));
- erts_release_code_write_permission();
+ erts_release_code_mod_permission();
ERTS_REQ_PROC_MAIN_LOCK(c_p);
SWAPIN;
diff --git a/erts/emulator/beam/emu/bs_instrs.tab b/erts/emulator/beam/emu/bs_instrs.tab
index 54ecc2648e..a42c2eb331 100644
--- a/erts/emulator/beam/emu/bs_instrs.tab
+++ b/erts/emulator/beam/emu/bs_instrs.tab
@@ -125,7 +125,7 @@ BS_GET_UNCHECKED_FIELD_SIZE(Bits, Unit, Fail, Dst) {
TEST_BIN_VHEAP(VNh, Nh, Live) {
Uint need = $Nh;
if ((E - HTOP < (need + S_RESERVED)) ||
- (MSO(c_p).overhead + $VNh >= BIN_VHEAP_SZ(c_p))) {
+ (MSO(c_p).overhead + $VNh >= c_p->bin_vheap_sz)) {
$GC_SWAPOUT();
PROCESS_MAIN_CHK_LOCKS(c_p);
FCALLS -= erts_garbage_collect_nobump(c_p, need, reg, $Live, FCALLS);
@@ -428,7 +428,6 @@ bs_init.verify(Fail) {
bs_init.execute(Live, Dst) {
erts_bin_offset = 0;
- erts_writable_bin = 0;
if (BsOp1 <= ERL_ONHEAP_BIN_LIMIT) {
ErlHeapBin* hb;
@@ -540,7 +539,6 @@ bs_init_bits.execute(Live, Dst) {
}
erts_bin_offset = 0;
- erts_writable_bin = 0;
/* num_bits = Number of bits to build
* num_bytes = Number of bytes to allocate in the binary
@@ -1114,7 +1112,6 @@ i_bs_create_bin(Fail, Alloc, Live, Dst, N) {
* alloc = Total number of words to allocate on heap
*/
erts_bin_offset = 0;
- erts_writable_bin = 0;
if (num_bytes <= ERL_ONHEAP_BIN_LIMIT) {
ErlHeapBin* hb;
@@ -1866,3 +1863,286 @@ i_bs_start_match3.execute(Live, Fail, Dst) {
}
%endif
+
+//
+// New instructions introduced in OTP 26 for matching of integers and
+// binaries of fixed sizes follow.
+//
+
+//
+// i_bs_ensure_bits Ctx Size Fail
+//
+
+i_bs_ensure_bits := i_bs_ensure_bits.fetch.execute;
+
+i_bs_ensure_bits.head() {
+ Eterm context;
+}
+
+i_bs_ensure_bits.fetch(Src) {
+ context = $Src;
+}
+
+i_bs_ensure_bits.execute(NumBits, Fail) {
+ ErlBinMatchBuffer* mb = ms_matchbuffer(context);
+ Uint size = $NumBits;
+ if (mb->size - mb->offset < size) {
+ $FAIL($Fail);
+ }
+}
+
+//
+// i_bs_ensure_bits_unit Ctx Size Unit Fail
+//
+
+i_bs_ensure_bits_unit := i_bs_ensure_bits_unit.fetch.execute;
+
+i_bs_ensure_bits_unit.head() {
+ Eterm context;
+}
+
+i_bs_ensure_bits_unit.fetch(Src) {
+ context = $Src;
+}
+
+i_bs_ensure_bits_unit.execute(NumBits, Unit, Fail) {
+ ErlBinMatchBuffer* mb = ms_matchbuffer(context);
+ Uint size = $NumBits;
+ Uint diff;
+
+ if ((diff = mb->size - mb->offset) < size) {
+ $FAIL($Fail);
+ }
+ if ((diff - size) % $Unit != 0) {
+ $FAIL($Fail);
+ }
+}
+
+//
+// i_bs_read_bits Ctx Size
+// i_bs_ensure_bits_read Ctx Size Fail
+//
+
+i_bs_read_bits := i_bs_read_bits.fetch.execute;
+i_bs_ensure_bits_read := i_bs_read_bits.fetch.ensure_bits.execute;
+
+i_bs_read_bits.head() {
+ ErlBinMatchBuffer* mb;
+ Uint size;
+}
+
+i_bs_read_bits.fetch(Src, NumBits) {
+ mb = ms_matchbuffer($Src);
+ size = $NumBits;
+}
+
+i_bs_read_bits.ensure_bits(Fail) {
+ if (mb->size - mb->offset < size) {
+ $FAIL($Fail);
+ }
+}
+
+i_bs_read_bits.execute() {
+ byte *byte_ptr;
+ Uint bit_offset = mb->offset % 8;
+ Uint num_bytes_to_read = (size + 7) / 8;
+ Uint num_partial = size % 8;
+
+ if ((num_partial == 0 && bit_offset != 0) ||
+ (num_partial != 0 && bit_offset > 8 - num_partial)) {
+ num_bytes_to_read++;
+ }
+
+ bitdata = 0;
+ byte_ptr = mb->base + (mb->offset >> 3);
+ mb->offset += size;
+ switch (num_bytes_to_read) {
+#ifdef ARCH_64
+ case 9:
+ case 8:
+ bitdata = bitdata << 8 | *byte_ptr++;
+ case 7:
+ bitdata = bitdata << 8 | *byte_ptr++;
+ case 6:
+ bitdata = bitdata << 8 | *byte_ptr++;
+ case 5:
+ bitdata = bitdata << 8 | *byte_ptr++;
+#else
+ case 5:
+#endif
+ case 4:
+ bitdata = bitdata << 8 | *byte_ptr++;
+ case 3:
+ bitdata = bitdata << 8 | *byte_ptr++;
+ case 2:
+ bitdata = bitdata << 8 | *byte_ptr++;
+ case 1:
+ bitdata = bitdata << 8 | *byte_ptr++;
+ }
+
+ if (num_bytes_to_read <= sizeof(Uint)) {
+ bitdata <<= 8 * (sizeof(Uint) - num_bytes_to_read) + bit_offset;
+ } else {
+ bitdata <<= bit_offset;
+ bitdata = bitdata | ((*byte_ptr << bit_offset) >> 8);
+ }
+}
+
+// i_bs_eq Fail Size Value
+i_bs_eq(Fail, NumBits, Value) {
+ Uint size = $NumBits;
+ Eterm result;
+
+ result = bitdata >> (8 * sizeof(Uint) - size);
+ bitdata <<= size;
+ if (result != $Value) {
+ $FAIL($Fail);
+ }
+}
+
+// i_bs_extract_integer Size Dst
+i_bs_extract_integer(NumBits, Dst) {
+ Uint size = $NumBits;
+ Eterm result;
+
+ result = bitdata >> (8 * sizeof(Uint) - size);
+ result = make_small(result);
+ bitdata <<= size;
+ $Dst = result;
+}
+
+// i_bs_read_integer_8 Ctx Dst
+i_bs_read_integer_8(Ctx, Dst) {
+ ErlBinMatchBuffer* mb = ms_matchbuffer($Ctx);
+ byte *byte_ptr;
+ Uint bit_offset = mb->offset % 8;
+ Eterm result;
+
+ byte_ptr = mb->base + (mb->offset >> 3);
+ mb->offset += 8;
+ result = byte_ptr[0];
+ if (bit_offset != 0) {
+ result = result << 8 | byte_ptr[1];
+ result = ((result << bit_offset) >> 8) & 0xff;
+ }
+ result = make_small(result);
+ $Dst = result;
+}
+
+//
+// i_bs_get_fixed_integer Ctx Size Flags Dst
+//
+
+i_bs_get_fixed_integer := i_bs_get_fixed_integer.fetch.execute;
+
+i_bs_get_fixed_integer.head() {
+ Eterm context;
+}
+
+i_bs_get_fixed_integer.fetch(Src) {
+ context = $Src;
+}
+
+i_bs_get_fixed_integer.execute(Size, Flags, Dst) {
+ ErlBinMatchBuffer* mb;
+ Uint size = $Size;
+ Eterm result;
+
+ mb = ms_matchbuffer(context);
+ LIGHT_SWAPOUT;
+ result = erts_bs_get_integer_2(c_p, size, $Flags, mb);
+ LIGHT_SWAPIN;
+ HEAP_SPACE_VERIFIED(0);
+ $Dst = result;
+}
+
+//
+// i_get_fixed_binary Ctx Size Dst
+//
+
+i_bs_get_fixed_binary := i_bs_get_fixed_binary.fetch.execute;
+
+i_bs_get_fixed_binary.head() {
+ Eterm context;
+}
+
+i_bs_get_fixed_binary.fetch(Src) {
+ context = $Src;
+}
+
+i_bs_get_fixed_binary.execute(Size, Dst) {
+ ErlBinMatchBuffer* mb;
+ Uint size = $Size;
+ Eterm* htop;
+ Eterm result;
+
+ ASSERT(header_is_bin_matchstate(*boxed_val(context)));
+
+ htop = HTOP;
+ mb = ms_matchbuffer(context);
+ result = erts_extract_sub_binary(&htop, mb->orig, mb->base,
+ mb->offset, size);
+ HTOP = htop;
+
+ mb->offset += size;
+
+ $Dst = result;
+}
+
+//
+// i_get_tail Ctx Dst
+//
+
+i_bs_get_tail := i_bs_get_tail.fetch.execute;
+
+i_bs_get_tail.head() {
+ Eterm context;
+}
+
+i_bs_get_tail.fetch(Src) {
+ context = $Src;
+}
+
+i_bs_get_tail.execute(Dst) {
+ ErlBinMatchBuffer* mb;
+ Eterm* htop;
+ Eterm result;
+
+ ASSERT(header_is_bin_matchstate(*boxed_val(context)));
+
+ htop = HTOP;
+ mb = ms_matchbuffer(context);
+ result = erts_extract_sub_binary(&htop, mb->orig, mb->base,
+ mb->offset, mb->size - mb->offset);
+ HTOP = htop;
+
+ $Dst = result;
+}
+
+//
+// i_bs_skip Ctx Size
+//
+
+i_bs_skip := i_bs_skip.fetch.execute;
+
+i_bs_skip.head() {
+ Eterm context;
+}
+
+i_bs_skip.fetch(Src) {
+ context = $Src;
+}
+
+i_bs_skip.execute(Size) {
+ ErlBinMatchBuffer* mb;
+ Uint size = $Size;
+
+ ASSERT(header_is_bin_matchstate(*boxed_val(context)));
+ mb = ms_matchbuffer(context);
+ mb->offset += size;
+}
+
+// i_bs_drop Size
+i_bs_drop(Size) {
+ bitdata <<= $Size;
+}
diff --git a/erts/emulator/beam/emu/emu_load.c b/erts/emulator/beam/emu/emu_load.c
index 3188325acc..8395a87076 100644
--- a/erts/emulator/beam/emu/emu_load.c
+++ b/erts/emulator/beam/emu/emu_load.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -93,6 +93,12 @@ int beam_load_prepare_emit(LoaderState *stp) {
init_label(&stp->labels[i]);
}
+ stp->lambda_literals = erts_alloc(ERTS_ALC_T_PREPARED_CODE,
+ stp->beam.lambdas.count * sizeof(SWord));
+ for (i = 0; i < stp->beam.lambdas.count; i++) {
+ stp->lambda_literals[i] = ERTS_SWORD_MAX;
+ }
+
stp->import_patches =
erts_alloc(ERTS_ALC_T_PREPARED_CODE,
stp->beam.imports.count * sizeof(BeamInstr));
@@ -184,6 +190,11 @@ int beam_load_prepared_dtor(Binary* magic)
stp->labels = NULL;
}
+ if (stp->lambda_literals != NULL) {
+ erts_free(ERTS_ALC_T_PREPARED_CODE, (void *)stp->lambda_literals);
+ stp->lambda_literals = NULL;
+ }
+
if (stp->import_patches != NULL) {
erts_free(ERTS_ALC_T_PREPARED_CODE, (void*)stp->import_patches);
stp->import_patches = NULL;
@@ -543,6 +554,7 @@ int beam_load_finish_emit(LoaderState *stp) {
void beam_load_finalize_code(LoaderState* stp, struct erl_module_instance* inst_p)
{
+ ErtsCodeIndex staging_ix;
unsigned int i;
int on_load = stp->on_load;
unsigned catches;
@@ -554,6 +566,13 @@ void beam_load_finalize_code(LoaderState* stp, struct erl_module_instance* inst_
inst_p->code_hdr = stp->code_hdr;
inst_p->code_length = size;
+ inst_p->writable_region = (void*)inst_p->code_hdr;
+ inst_p->executable_region = inst_p->writable_region;
+
+ staging_ix = erts_staging_code_ix();
+
+ ERTS_LC_ASSERT(erts_initialized == 0 || erts_has_code_load_permission() ||
+ erts_thr_progress_is_blocking());
/* Update ranges (used for finding a function from a PC value). */
erts_update_ranges(inst_p->code_hdr, size);
@@ -604,6 +623,7 @@ void beam_load_finalize_code(LoaderState* stp, struct erl_module_instance* inst_
*/
if (stp->beam.lambdas.count) {
BeamFile_LambdaTable *lambda_table;
+ ErtsLiteralArea *literal_area;
ErlFunEntry **fun_entries;
LambdaPatch* lp;
int i;
@@ -612,6 +632,8 @@ void beam_load_finalize_code(LoaderState* stp, struct erl_module_instance* inst_
fun_entries = erts_alloc(ERTS_ALC_T_LOADER_TMP,
sizeof(ErlFunEntry*) * lambda_table->count);
+ literal_area = (stp->code_hdr)->literal_area;
+
for (i = 0; i < lambda_table->count; i++) {
BeamFile_LambdaEntry *lambda;
ErlFunEntry *fun_entry;
@@ -626,14 +648,38 @@ void beam_load_finalize_code(LoaderState* stp, struct erl_module_instance* inst_
lambda->arity - lambda->num_free);
fun_entries[i] = fun_entry;
- if (erts_is_fun_loaded(fun_entry)) {
+ if (erts_is_fun_loaded(fun_entry, staging_ix)) {
/* We've reloaded a module over itself and inherited the old
* instance's fun entries, so we need to undo the reference
* bump in `erts_put_fun_entry2` to make fun purging work. */
erts_refc_dectest(&fun_entry->refc, 1);
}
+ /* Finalize the literal we've created for this lambda, if any,
+ * converting it from an external fun to a local one with the newly
+ * created fun entry. */
+ if (stp->lambda_literals[i] != ERTS_SWORD_MAX) {
+ ErlFunThing *funp;
+ Eterm literal;
+
+ literal = beamfile_get_literal(&stp->beam,
+ stp->lambda_literals[i]);
+ funp = (ErlFunThing *)fun_val(literal);
+ ASSERT(funp->creator == am_external);
+
+ funp->entry.fun = fun_entry;
+
+ funp->next = literal_area->off_heap;
+ literal_area->off_heap = (struct erl_off_heap_header *)funp;
+
+ ASSERT(erts_init_process_id != ERTS_INVALID_PID);
+ funp->creator = erts_init_process_id;
+
+ erts_refc_inc(&fun_entry->refc, 2);
+ }
+
erts_set_fun_code(fun_entry,
+ staging_ix,
stp->codev + stp->labels[lambda->label].value);
}
@@ -690,7 +736,7 @@ void beam_load_finalize_code(LoaderState* stp, struct erl_module_instance* inst_
*/
ep->trampoline.not_loaded.deferred = (BeamInstr) address;
} else {
- ep->dispatch.addresses[erts_staging_code_ix()] = address;
+ ep->dispatch.addresses[staging_ix] = address;
}
}
@@ -704,7 +750,7 @@ void beam_load_finalize_code(LoaderState* stp, struct erl_module_instance* inst_
entry->name,
entry->arity);
const BeamInstr *addr =
- ep->dispatch.addresses[erts_staging_code_ix()];
+ ep->dispatch.addresses[staging_ix];
if (!ErtsInArea(addr, stp->codev, stp->ci * sizeof(BeamInstr))) {
erts_exit(ERTS_ABORT_EXIT,
diff --git a/erts/emulator/beam/emu/generators.tab b/erts/emulator/beam/emu/generators.tab
index 65d5c60175..1f20a19ecc 100644
--- a/erts/emulator/beam/emu/generators.tab
+++ b/erts/emulator/beam/emu/generators.tab
@@ -2,7 +2,7 @@
//
// %CopyrightBegin%
//
-// Copyright Ericsson AB 2020-2021. All Rights Reserved.
+// Copyright Ericsson AB 2020-2023. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -590,7 +590,7 @@ gen.new_small_map_lit(Dst, Live, Size, Rest) {
*dst++ = Rest[i + 1];
}
- lit = beamfile_add_literal(&S->beam, keys);
+ lit = beamfile_add_literal(&S->beam, keys, 1);
erts_free(ERTS_ALC_T_LOADER_TMP, tmp);
op->a[0] = Dst;
@@ -1025,3 +1025,412 @@ gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) {
}
return op;
}
+
+gen.update_record(Size, Src, Dst, N, Updates) {
+ BeamOp *begin, *prev;
+ Sint count, i;
+
+ ASSERT(Size.type == TAG_u && Size.val < SCRATCH_X_REG);
+ ASSERT(N.type == TAG_u && !(N.val % 2) && (N.val / 2) <= Size.val);
+
+ $NewBeamOp(S, begin);
+ $BeamOpNameArity(begin, i_update_record, 5);
+
+ begin->a[0] = Size;
+ begin->a[1] = Src;
+ begin->a[2] = Dst;
+ begin->a[3] = Updates[0];
+ begin->a[4] = Updates[1];
+
+ count = N.val;
+ prev = begin;
+
+ for (i = 2; i < count; i += 2) {
+ BeamOp *next;
+
+ $NewBeamOp(S, next);
+ $BeamOpNameArity(next, i_update_record_continue, 2);
+
+ /* Encode the offset from the _end_ of the tuple so that we can act
+ * relative to HTOP. */
+ next->a[0].type = TAG_u;
+ next->a[0].val = (Size.val + 1) - Updates[i].val;
+
+ /* The first instruction overwrites the destination register after
+ * stashing its contents to SCRATCH_X_REG, so all updates must be
+ * rewritten accordingly. */
+ if (Updates[i + 1].type == Dst.type && Updates[i + 1].val == Dst.val) {
+ next->a[1].type = TAG_x;
+ next->a[1].val = SCRATCH_X_REG;
+ } else {
+ next->a[1] = Updates[i + 1];
+ }
+
+ next->next = NULL;
+ prev->next = next;
+
+ prev = next;
+ }
+
+ return begin;
+}
+
+gen.bs_match(Fail, Ctx, N, List) {
+ BeamOp* first_op = 0;
+ BeamOp** next_ptr = &first_op;
+ BeamOp* test_heap_op = 0;
+ BeamOp* read_op = 0;
+#ifdef ARCH_64
+ BeamOp* eq_op = 0;
+#endif
+ int src;
+
+ src = 0;
+ while (src < N.val) {
+ Uint unit;
+ Uint size;
+ Uint words_needed;
+ BeamOp* op;
+
+ /* Calculate the number of heap words needed for this
+ * instruction. */
+ words_needed = 0;
+ switch (List[src].val) {
+ case am_binary:
+ ASSERT(List[src+3].type == TAG_u);
+ ASSERT(List[src+4].type == TAG_u);
+ size = List[src+3].val * List[src+4].val;
+ words_needed = heap_bin_size((size + 7) / 8);
+ break;
+ case am_integer:
+ ASSERT(List[src+3].type == TAG_u);
+ ASSERT(List[src+4].type == TAG_u);
+ size = List[src+3].val * List[src+4].val;
+ if (size >= SMALL_BITS) {
+ words_needed = BIG_NEED_FOR_BITS(size);
+ }
+ break;
+ case am_get_tail:
+ words_needed = EXTRACT_SUB_BIN_HEAP_NEED;
+ break;
+ }
+
+ /* Emit a test_heap instrution if needed and there is
+ * no previous one. */
+ if ((List[src].val == am_Eq || words_needed) && test_heap_op == 0 &&
+ List[src+1].type == TAG_u) {
+ $NewBeamOp(S, test_heap_op);
+ $BeamOpNameArity(test_heap_op, test_heap, 2);
+
+ test_heap_op->a[0].type = TAG_u;
+ test_heap_op->a[0].val = 0; /* Number of heap words */
+ test_heap_op->a[1] = List[src+1]; /* Live */
+
+ *next_ptr = test_heap_op;
+ next_ptr = &test_heap_op->next;
+ }
+
+ if (words_needed) {
+ test_heap_op->a[0].val += words_needed;
+ }
+
+ /* Translate this sub-instruction to a BEAM instruction. */
+ op = 0;
+ switch (List[src].val) {
+ case am_ensure_at_least: {
+ Uint size = List[src+1].val;
+ unit = List[src+2].val;
+ if (size != 0 && unit == 1) {
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, i_bs_ensure_bits, 3);
+ op->a[0] = Ctx;
+ op->a[1].type = TAG_u;
+ op->a[1].val = size;
+ op->a[2] = Fail;
+ } else if (size != 0 && unit != 1) {
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, i_bs_ensure_bits_unit, 4);
+
+ op->a[0] = Ctx;
+ op->a[1].type = TAG_u;
+ op->a[1].val = size; /* Size */
+ op->a[2].type = TAG_u;
+ op->a[2].val = unit; /* Unit */
+ op->a[3] = Fail;
+ } else if (size == 0 && unit != 1) {
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, bs_test_unit, 3);
+
+ op->a[0] = Fail;
+ op->a[1] = Ctx;
+ op->a[2].type = TAG_u;
+ op->a[2].val = unit;
+ } else if (size == 0 && unit == 1) {
+ /* This test is redundant because it always
+ * succeeds. This should only happen for unoptimized
+ * code. Generate a dummy instruction to ensure that
+ * we don't trigger the sanity check at the end of
+ * this generator. */
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, delete_me, 0);
+ }
+ src += 3;
+ break;
+ }
+ case am_ensure_exactly: {
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, bs_test_tail2, 3);
+
+ op->a[0] = Fail;
+ op->a[1] = Ctx;
+ op->a[2]= List[src+1]; /* Size */
+
+ src += 2;
+ break;
+ }
+ case am_binary: {
+ ASSERT(List[src+3].type == TAG_u);
+ ASSERT(List[src+4].type == TAG_u);
+ size = List[src+3].val;
+ unit = List[src+4].val;
+
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, i_bs_get_fixed_binary, 3);
+
+ op->a[0] = Ctx;
+ op->a[1].type = TAG_u;
+ op->a[1].val = size * unit; /* Size */
+ op->a[2] = List[src+5]; /* Dst */
+
+ read_op = 0;
+ src += 6;
+ break;
+ }
+ case am_integer: {
+ Uint flags = 0;
+ BeamOpArg Flags;
+
+ /* Translate flags. */
+ Flags = List[src+2];
+ if (Flags.type == TAG_n) {
+ Flags.type = TAG_u;
+ Flags.val = 0;
+ } else if (Flags.type == TAG_q) {
+ Eterm term = beamfile_get_literal(&S->beam, Flags.val);
+ while (is_list(term)) {
+ Eterm* consp = list_val(term);
+ Eterm elem = CAR(consp);
+ switch (elem) {
+ case am_little:
+ flags |= BSF_LITTLE;
+ break;
+ case am_native:
+ flags |= BSF_NATIVE;
+ break;
+ case am_signed:
+ flags |= BSF_SIGNED;
+ break;
+ }
+ term = CDR(consp);
+ }
+ ASSERT(is_nil(term));
+ Flags.type = TAG_u;
+ Flags.val = flags;
+ $NativeEndian(Flags);
+ }
+
+ ASSERT(List[src+3].type == TAG_u);
+ ASSERT(List[src+4].type == TAG_u);
+ size = List[src+3].val * List[src+4].val;
+
+#define READ_OP_SIZE 1
+ if (size < SMALL_BITS && flags == 0) {
+ /* This is a suitable segment -- an unsigned big
+ * endian integer that fits in a small. */
+ if (read_op == 0 || read_op->a[READ_OP_SIZE].val + size > 8*sizeof(Uint)) {
+ /* There is either no previous i_bs_read_bits instruction or
+ * the size of this segment don't fit into it. */
+ $NewBeamOp(S, read_op);
+ $BeamOpNameArity(read_op, i_bs_read_bits, 2);
+
+ read_op->a[0] = Ctx;
+ read_op->a[1].type = TAG_u;
+ read_op->a[1].val = 0;
+
+ *next_ptr = read_op;
+ next_ptr = &read_op->next;
+ }
+
+ read_op->a[READ_OP_SIZE].val += size;
+
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, i_bs_extract_integer, 2);
+ op->a[0].type = TAG_u;
+ op->a[0].val = size;
+ op->a[1] = List[src+5]; /* Dst */
+ } else {
+ /* Little endian, signed, or might not fit in a small. */
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, i_bs_get_fixed_integer, 4);
+
+ op->a[0] = Ctx;
+ op->a[1].type = TAG_u;
+ op->a[1].val = size; /* Size */
+ op->a[2] = Flags; /* Flags */
+ op->a[3] = List[src+5]; /* Dst */
+
+ read_op = 0;
+ }
+
+ src += 6;
+ break;
+ }
+ case am_Eq: {
+ ASSERT(List[src+2].type == TAG_u);
+ ASSERT(List[src+3].type == TAG_u);
+ size = List[src+2].val;
+
+ if (read_op == 0 || read_op->a[READ_OP_SIZE].val + size > 8*sizeof(Uint)) {
+ /* There is either no previous i_bs_read_bits instruction or
+ * the size of this segment don't fit into it. */
+ $NewBeamOp(S, read_op);
+ $BeamOpNameArity(read_op, i_bs_read_bits, 2);
+
+ read_op->a[0] = Ctx;
+ read_op->a[1].type = TAG_u;
+ read_op->a[1].val = 0;
+
+ *next_ptr = read_op;
+ next_ptr = &read_op->next;
+ }
+
+ read_op->a[READ_OP_SIZE].val += size;
+
+#ifdef ARCH_64
+ if (eq_op &&
+ eq_op->next == 0 && /* Previous instruction? */
+ eq_op->a[1].val + size <= 8*sizeof(Uint)) {
+ /* Coalesce with the previous `=:=` instruction. */
+ eq_op->a[1].val += size;
+ eq_op->a[2].val = eq_op->a[2].val << size | List[src+3].val;
+ }
+#else
+ if (0) {
+ ;
+ }
+#endif
+ else {
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, i_bs_eq, 3);
+
+#ifdef ARCH_64
+ eq_op = op;
+#endif
+
+ op->a[0] = Fail;
+ op->a[1] = List[src+2]; /* Size */
+ op->a[2] = List[src+3]; /* Value */
+ }
+
+ src += 4;
+ break;
+ }
+ case am_get_tail:
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, i_bs_get_tail, 2);
+
+ op->a[0] = Ctx;
+ op->a[1] = List[src+3]; /* Dst */
+
+ read_op = 0;
+ src += 4;
+ break;
+ case am_skip:
+ ASSERT(List[src+1].type == TAG_u);
+ size = List[src+1].val;
+
+ $NewBeamOp(S, op);
+
+ if (read_op && read_op->a[READ_OP_SIZE].val + size <= 8*sizeof(Uint)) {
+ read_op->a[READ_OP_SIZE].val += size;
+ $BeamOpNameArity(op, i_bs_drop, 1);
+ op->a[0] = List[src+1]; /* Size */
+ } else {
+ $BeamOpNameArity(op, i_bs_skip, 2);
+ op->a[0] = Ctx;
+ op->a[1] = List[src+1]; /* Size */
+ read_op = 0;
+ }
+
+ src += 2;
+ break;
+ default:
+ /*
+ * This is an unknown sub command. It was probably produced by a later
+ * release of Erlang/OTP than the current one. Fail loading.
+ */
+
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, bad_bs_match, 1);
+ op->a[0] = List[src];
+ *next_ptr = op;
+ return first_op;
+ }
+
+ if (op) {
+ *next_ptr = op;
+ next_ptr = &op->next;
+ }
+ }
+
+ if (test_heap_op && test_heap_op->a[0].val == 0) {
+ /* This test_heap instruction was forced by the `=:=` sub
+ * instruction, but it turned out that no test_heap instruction was
+ * needed. */
+ $BeamOpNameArity(test_heap_op, delete_me, 0);
+ }
+
+ if (first_op == 0) {
+ erts_exit(ERTS_ERROR_EXIT, "loading bs_match in %T:%T/%d: no instructions loaded",
+ S->module, S->function, S->arity);
+ }
+
+ ASSERT(first_op);
+ return first_op;
+}
+
+gen_increment(stp, reg, val, dst) {
+ BeamOp* op;
+ $NewBeamOp($stp, op);
+ $BeamOpNameArity(op, i_increment, 3);
+ op->a[0] = $reg;
+ op->a[1].type = TAG_u;
+ op->a[1].val = $val;
+ op->a[2] = $dst;
+ return op;
+}
+
+gen.increment(Reg, Integer, Dst) {
+ $gen_increment(S, Reg, Integer.val, Dst);
+}
+
+gen.increment_from_minus(Reg, Integer, Dst) {
+ $gen_increment(S, Reg, -Integer.val, Dst);
+}
+
+gen.plus_from_minus(Fail, Live, Src, Integer, Dst) {
+ BeamOp* op;
+
+ ASSERT(Integer.type == TAG_i && IS_SSMALL(-(Sint)Integer.val));
+
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, gen_plus, 5);
+ op->a[0] = Fail;
+ op->a[1] = Live;
+ op->a[2] = Src;
+ op->a[3].type = TAG_i;
+ op->a[3].val = -(Sint)Integer.val;
+ op->a[4] = Dst;
+
+ return op;
+}
diff --git a/erts/emulator/beam/emu/instrs.tab b/erts/emulator/beam/emu/instrs.tab
index 2c794aa4b0..88e6da9bf1 100644
--- a/erts/emulator/beam/emu/instrs.tab
+++ b/erts/emulator/beam/emu/instrs.tab
@@ -762,6 +762,30 @@ self(Dst) {
$Dst = c_p->common.id;
}
+i_update_record(Size, Src, Dst, Offset, Element) {
+ Eterm *untagged_source = tuple_val($Src);
+ Uint size_on_heap = $Size + 1;
+
+ /* Copy the entire tuple up-front, mimicking the behavior of the old
+ * setelement/3 + set_tuple_element method. This overwrites every field
+ * we're setting, but that cost is generally pretty small. */
+ sys_memcpy(HTOP, untagged_source, size_on_heap * sizeof(Eterm));
+ HTOP[$Offset] = $Element;
+
+ /* We stash the contents of the destination register in SCRATCH_X_REG in
+ * case it's used in subsequent `i_update_record_continue` instructions.
+ * The updates have been rewritten accordingly. */
+ reg[SCRATCH_X_REG] = $Dst;
+ $Dst = make_tuple(HTOP);
+
+ HTOP += size_on_heap;
+}
+
+i_update_record_continue(OffsetFromEnd, Element) {
+ Sint offset = -(Sint)$OffsetFromEnd;
+ HTOP[offset] = $Element;
+}
+
set_tuple_element(Element, Tuple, Offset) {
Eterm* p;
diff --git a/erts/emulator/beam/emu/load.h b/erts/emulator/beam/emu/load.h
index f78fee217d..6de420a5dc 100644
--- a/erts/emulator/beam/emu/load.h
+++ b/erts/emulator/beam/emu/load.h
@@ -2,7 +2,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -142,6 +142,11 @@ struct LoaderState_ {
unsigned int current_li; /* Current line instruction */
unsigned int* func_line; /* Mapping from function to first line instr */
+ /* Translates lambda indexes to their literals, if any. Lambdas that lack
+ * a literal (for example if they have an environment) are represented by
+ * ERTS_SWORD_MAX. */
+ SWord *lambda_literals;
+
int otp_20_or_higher;
Uint last_func_start;
diff --git a/erts/emulator/beam/emu/ops.tab b/erts/emulator/beam/emu/ops.tab
index c9d87f071b..7c4447d145 100644
--- a/erts/emulator/beam/emu/ops.tab
+++ b/erts/emulator/beam/emu/ops.tab
@@ -69,7 +69,7 @@ nif_start
i_generic_breakpoint
i_debug_breakpoint
-i_return_time_trace
+i_call_trace_return
i_return_to_trace
i_yield
trace_jump W
@@ -1106,11 +1106,74 @@ is_function Fail=f c => jump Fail
func_info M F A => i_func_info u M F A
# ================================================================
-# New bit syntax matching (R11B).
+# New bit syntax matching for fixed sizes (from OTP 26).
# ================================================================
%warm
+bs_match Fail Ctx Size Rest=* => bs_match(Fail, Ctx, Size, Rest)
+
+# The bs_match generator breaks the bs_match instruction into
+# the instructions that follow.
+
+i_bs_ensure_bits xy I f
+i_bs_ensure_bits_unit xy I t f
+
+i_bs_read_bits xy t
+
+i_bs_eq f t W
+
+i_bs_extract_integer t d
+
+i_bs_read_bits Ctx=x u==8 | i_bs_extract_integer u==8 Dst=x =>
+ i_bs_read_integer_8 Ctx Dst
+
+i_bs_read_integer_8 x x
+
+i_bs_get_fixed_integer xy I t d
+
+i_bs_get_fixed_binary xy I d
+
+i_bs_get_tail xy d
+
+i_bs_skip xy I
+
+i_bs_drop I
+
+i_bs_ensure_bits Ctx1 Size1 Fail | i_bs_read_bits Ctx2 Size2 |
+ equal(Ctx1, Ctx2) | equal(Size1, Size2) =>
+ i_bs_ensure_bits_read Ctx1 Size1 Fail
+
+i_bs_ensure_bits_read xy t f
+
+# Optimize extraction of a single segment for some popular sizes.
+
+i_bs_ensure_bits Ctx1 u==8 Fail | i_bs_read_bits Ctx2 u==8 |
+ i_bs_extract_integer u==8 Dst=x | equal(Ctx1, Ctx2) =>
+ i_bs_get_integer_8 Ctx1 Fail Dst
+
+i_bs_ensure_bits Ctx1 u==16 Fail | i_bs_read_bits Ctx2 u==16 |
+ i_bs_extract_integer u==16 Dst=x | equal(Ctx1, Ctx2) =>
+ i_bs_get_integer_16 Ctx1 Fail Dst
+
+%if ARCH_64
+i_bs_ensure_bits Ctx1 u==32 Fail | i_bs_read_bits Ctx2 u==32 |
+ i_bs_extract_integer u==32 Dst=x | equal(Ctx1, Ctx2) =>
+ i_bs_get_integer_32 Ctx1 Fail Dst
+%endif
+
+#
+# The following instruction is specially handled in beam_load.c
+# to produce a user-friendly message if a bad bs_match instruction
+# is encountered.
+#
+bad_bs_match/1
+bad_bs_match A | never() => _
+
+# ================================================================
+# Bit syntax matching (from R11B).
+# ================================================================
+
# Matching integers
bs_match_string Fail Ms Bits Val => i_bs_match_string Ms Fail Bits Val
@@ -1719,3 +1782,15 @@ recv_marker_reserve S
recv_marker_bind S S
recv_marker_clear S
recv_marker_use S
+
+#
+# OTP 26
+#
+
+update_record Hint=a Size=u Src=s Dst=d N=u Updates=* =>
+ update_record(Size, Src, Dst, N, Updates)
+
+i_update_record Size=u Src=c Dst=xy Offset=u Element=s =>
+ move Src x | i_update_record Size x Dst Offset Element
+i_update_record t xy xy t s
+i_update_record_continue t s
diff --git a/erts/emulator/beam/emu/predicates.tab b/erts/emulator/beam/emu/predicates.tab
index 82f9501d39..927e4e504f 100644
--- a/erts/emulator/beam/emu/predicates.tab
+++ b/erts/emulator/beam/emu/predicates.tab
@@ -2,7 +2,7 @@
//
// %CopyrightBegin%
//
-// Copyright Ericsson AB 2020-2021. All Rights Reserved.
+// Copyright Ericsson AB 2020-2023. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -70,3 +70,14 @@ pred.literal_is_map(Lit) {
term = beamfile_get_literal(&S->beam, Lit.val);
return is_map(term);
}
+
+// Test whether the negation of the given number is small.
+pred.negation_is_small(Int) {
+ /*
+ * Check for the rare case of overflow in BeamInstr (UWord) -> Sint.
+ * Cast to the correct type before using IS_SSMALL (Sint).
+ */
+ return Int.type == TAG_i &&
+ !(Int.val & ~((((BeamInstr)1) << ((sizeof(Sint)*8)-1))-1)) &&
+ IS_SSMALL(-((Sint)Int.val));
+}
diff --git a/erts/emulator/beam/emu/trace_instrs.tab b/erts/emulator/beam/emu/trace_instrs.tab
index 5241af9ca9..9b0b377ed7 100644
--- a/erts/emulator/beam/emu/trace_instrs.tab
+++ b/erts/emulator/beam/emu/trace_instrs.tab
@@ -2,7 +2,7 @@
//
// %CopyrightBegin%
//
-// Copyright Ericsson AB 2017-2021. All Rights Reserved.
+// Copyright Ericsson AB 2017-2023. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ return_trace() {
erts_trace_return(c_p, mfa, r(0), ERTS_TRACER_FROM_ETERM(E+2)/* tracer */);
ERTS_REQ_PROC_MAIN_LOCK(c_p);
SWAPIN;
- E += 3;
+ E += 1 + BEAM_RETURN_TRACE_FRAME_SZ;
$RETURN();
Goto(*I);
//| -no_next
@@ -49,7 +49,7 @@ i_generic_breakpoint() {
//| -no_next
}
-i_return_time_trace() {
+i_call_trace_return() {
const ErtsCodeInfo *cinfo;
if (is_CP(E[1])) {
@@ -59,10 +59,10 @@ i_return_time_trace() {
}
SWAPOUT;
- erts_trace_time_return(c_p, cinfo);
+ erts_call_trace_return(c_p, cinfo, E[2]);
SWAPIN;
- E += 2;
+ E += 1 + BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ;
$RETURN();
Goto(*I);
//| -no_next
@@ -94,7 +94,7 @@ i_return_to_trace() {
ERTS_REQ_PROC_MAIN_LOCK(c_p);
SWAPIN;
}
- E += 1;
+ E += 1 + BEAM_RETURN_TO_TRACE_FRAME_SZ;
$RETURN();
Goto(*I);
//| -no_next
diff --git a/erts/emulator/beam/erl_alloc.c b/erts/emulator/beam/erl_alloc.c
index 3ab9c9d1b6..a344b585ae 100644
--- a/erts/emulator/beam/erl_alloc.c
+++ b/erts/emulator/beam/erl_alloc.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2002-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2002-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -185,7 +185,6 @@ struct au_init {
}
typedef struct {
- int erts_alloc_config;
#if HAVE_ERTS_MSEG
ErtsMsegInit_t mseg;
#endif
@@ -607,7 +606,6 @@ erts_alloc_init(int *argc, char **argv, ErtsAllocInitOpts *eaiop)
UWord extra_block_size = 0;
int i, ncpu;
erts_alc_hndl_args_init_t init = {
- 0,
#if HAVE_ERTS_MSEG
ERTS_MSEG_INIT_DEFAULT_INITIALIZER,
#endif
@@ -709,44 +707,6 @@ erts_alloc_init(int *argc, char **argv, ErtsAllocInitOpts *eaiop)
adjust_carrier_migration_support(&init.fix_alloc);
adjust_carrier_migration_support(&init.literal_alloc);
- if (init.erts_alloc_config) {
- /* Adjust flags that erts_alloc_config won't like */
-
- /* No thread specific instances */
- init.temp_alloc.thr_spec = 0;
- init.sl_alloc.thr_spec = 0;
- init.std_alloc.thr_spec = 0;
- init.ll_alloc.thr_spec = 0;
- init.eheap_alloc.thr_spec = 0;
- init.binary_alloc.thr_spec = 0;
- init.ets_alloc.thr_spec = 0;
- init.driver_alloc.thr_spec = 0;
- init.fix_alloc.thr_spec = 0;
- init.literal_alloc.thr_spec = 0;
-
- /* No carrier migration */
- init.temp_alloc.init.util.acul = 0;
- init.sl_alloc.init.util.acul = 0;
- init.std_alloc.init.util.acul = 0;
- init.ll_alloc.init.util.acul = 0;
- init.eheap_alloc.init.util.acul = 0;
- init.binary_alloc.init.util.acul = 0;
- init.ets_alloc.init.util.acul = 0;
- init.driver_alloc.init.util.acul = 0;
- init.fix_alloc.init.util.acul = 0;
- init.literal_alloc.init.util.acul = 0;
- init.temp_alloc.init.util.acful = 0;
- init.sl_alloc.init.util.acful = 0;
- init.std_alloc.init.util.acful = 0;
- init.ll_alloc.init.util.acful = 0;
- init.eheap_alloc.init.util.acful = 0;
- init.binary_alloc.init.util.acful = 0;
- init.ets_alloc.init.util.acful = 0;
- init.driver_alloc.init.util.acful = 0;
- init.fix_alloc.init.util.acful = 0;
- init.literal_alloc.init.util.acful = 0;
- }
-
/* Only temp_alloc can use thread specific interface */
if (init.temp_alloc.thr_spec)
init.temp_alloc.thr_spec = erts_no_schedulers + init.dirty_alloc_insts;
@@ -1736,9 +1696,6 @@ handle_args(int *argc, char **argv, erts_alc_hndl_args_init_t *init)
for (a = 0; a < aui_sz; a++)
aui[a]->enable = 1;
}
- else if (sys_strcmp("config", arg) == 0) {
- init->erts_alloc_config = 1;
- }
else if (sys_strcmp("r9c", arg) == 0
|| sys_strcmp("r10b", arg) == 0
|| sys_strcmp("r11b", arg) == 0) {
diff --git a/erts/emulator/beam/erl_arith.c b/erts/emulator/beam/erl_arith.c
index 813f79edaa..9815dd0a6c 100644
--- a/erts/emulator/beam/erl_arith.c
+++ b/erts/emulator/beam/erl_arith.c
@@ -1310,6 +1310,6 @@ Eterm erts_bnot(Process* p, Eterm arg)
}
/* Needed to remove compiler optimization */
-double erts_get_positive_zero_float() {
+double erts_get_positive_zero_float(void) {
return 0.0f;
}
diff --git a/erts/emulator/beam/erl_async.c b/erts/emulator/beam/erl_async.c
index 3e3dc3a29d..1dd6ef18f0 100644
--- a/erts/emulator/beam/erl_async.c
+++ b/erts/emulator/beam/erl_async.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2000-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2000-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -199,7 +199,7 @@ erts_init_async(void)
for (i = 0; i < erts_async_max_threads; i++) {
ErtsAsyncQ *aq = async_q(i);
- erts_snprintf(thr_opts.name, sizeof(thr_name), "async_%d", i+1);
+ erts_snprintf(thr_opts.name, sizeof(thr_name), "erts_async_%d", i+1);
erts_thr_create(&aq->thr_id, async_main, (void*) aq, &thr_opts);
}
diff --git a/erts/emulator/beam/erl_bif_guard.c b/erts/emulator/beam/erl_bif_guard.c
index 2242570cad..b4caaf6e38 100644
--- a/erts/emulator/beam/erl_bif_guard.c
+++ b/erts/emulator/beam/erl_bif_guard.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2006-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2006-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -338,6 +338,8 @@ BIF_RETTYPE bit_size_1(BIF_ALIST_1)
Uint low_bits;
Uint bytesize;
Uint high_bits;
+
+ /* NOTE: The JIT has its own implementation of this BIF. */
if (is_binary(BIF_ARG_1)) {
bytesize = binary_size(BIF_ARG_1);
high_bits = bytesize >> ((sizeof(Uint) * 8)-3);
@@ -367,6 +369,7 @@ BIF_RETTYPE bit_size_1(BIF_ALIST_1)
BIF_RETTYPE byte_size_1(BIF_ALIST_1)
{
+ /* NOTE: The JIT has its own implementation of this BIF. */
if (is_binary(BIF_ARG_1)) {
Uint bytesize = binary_size(BIF_ARG_1);
if (binary_bitsize(BIF_ARG_1) > 0) {
@@ -462,3 +465,15 @@ BIF_RETTYPE binary_part_2(BIF_ALIST_2)
badarg:
BIF_ERROR(BIF_P,BADARG);
}
+
+BIF_RETTYPE min_2(BIF_ALIST_2)
+{
+ /* NOTE: The JIT has its own implementation of this BIF. */
+ return CMP_GT(BIF_ARG_1, BIF_ARG_2) ? BIF_ARG_2 : BIF_ARG_1;
+}
+
+BIF_RETTYPE max_2(BIF_ALIST_2)
+{
+ /* NOTE: The JIT has its own implementation of this BIF. */
+ return CMP_LT(BIF_ARG_1, BIF_ARG_2) ? BIF_ARG_2 : BIF_ARG_1;
+}
diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c
index a187646857..4ba368ec7c 100644
--- a/erts/emulator/beam/erl_bif_info.c
+++ b/erts/emulator/beam/erl_bif_info.c
@@ -1871,21 +1871,16 @@ process_info_aux(Process *c_p,
case ERTS_PI_IX_MIN_BIN_VHEAP_SIZE: {
Uint hsz = 0;
- (void) erts_bld_uint(NULL, &hsz, MIN_VHEAP_SIZE(rp));
+ (void) erts_bld_uint(NULL, &hsz, rp->min_vheap_size);
hp = erts_produce_heap(hfact, hsz, reserve_size);
- res = erts_bld_uint(&hp, NULL, MIN_VHEAP_SIZE(rp));
+ res = erts_bld_uint(&hp, NULL, rp->min_vheap_size);
break;
}
case ERTS_PI_IX_MAX_HEAP_SIZE: {
- Uint hsz = 0;
- (void) erts_max_heap_size_map(MAX_HEAP_SIZE_GET(rp),
- MAX_HEAP_SIZE_FLAGS_GET(rp),
- NULL, &hsz);
- hp = erts_produce_heap(hfact, hsz, reserve_size);
- res = erts_max_heap_size_map(MAX_HEAP_SIZE_GET(rp),
- MAX_HEAP_SIZE_FLAGS_GET(rp),
- &hp, NULL);
+ res = erts_max_heap_size_map(hfact,
+ MAX_HEAP_SIZE_GET(rp),
+ MAX_HEAP_SIZE_FLAGS_GET(rp));
break;
}
@@ -1942,12 +1937,12 @@ process_info_aux(Process *c_p,
case ERTS_PI_IX_GARBAGE_COLLECTION: {
DECL_AM(minor_gcs);
- Eterm t;
- Uint map_sz = 0;
+ Eterm t, mhs_map;
- erts_max_heap_size_map(MAX_HEAP_SIZE_GET(rp), MAX_HEAP_SIZE_FLAGS_GET(rp), NULL, &map_sz);
+ mhs_map = erts_max_heap_size_map(hfact, MAX_HEAP_SIZE_GET(rp),
+ MAX_HEAP_SIZE_FLAGS_GET(rp));
- hp = erts_produce_heap(hfact, 3+2 + 3+2 + 3+2 + 3+2 + 3+2 + map_sz, reserve_size);
+ hp = erts_produce_heap(hfact, 5*(3+2), reserve_size);
t = TUPLE2(hp, AM_minor_gcs, make_small(GEN_GCS(rp))); hp += 3;
res = CONS(hp, t, NIL); hp += 2;
@@ -1956,12 +1951,10 @@ process_info_aux(Process *c_p,
t = TUPLE2(hp, am_min_heap_size, make_small(MIN_HEAP_SIZE(rp))); hp += 3;
res = CONS(hp, t, res); hp += 2;
- t = TUPLE2(hp, am_min_bin_vheap_size, make_small(MIN_VHEAP_SIZE(rp))); hp += 3;
+ t = TUPLE2(hp, am_min_bin_vheap_size, make_small(rp->min_vheap_size)); hp += 3;
res = CONS(hp, t, res); hp += 2;
- t = erts_max_heap_size_map(MAX_HEAP_SIZE_GET(rp), MAX_HEAP_SIZE_FLAGS_GET(rp), &hp, NULL);
-
- t = TUPLE2(hp, am_max_heap_size, t); hp += 3;
+ t = TUPLE2(hp, am_max_heap_size, mhs_map); hp += 3;
res = CONS(hp, t, res); hp += 2;
break;
}
@@ -2753,10 +2746,10 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
res = TUPLE2(hp, am_min_heap_size,make_small(H_MIN_SIZE));
BIF_RET(res);
} else if (BIF_ARG_1 == am_max_heap_size) {
- Uint sz = 0;
- erts_max_heap_size_map(H_MAX_SIZE, H_MAX_FLAGS, NULL, &sz);
- hp = HAlloc(BIF_P, sz);
- res = erts_max_heap_size_map(H_MAX_SIZE, H_MAX_FLAGS, &hp, NULL);
+ ErtsHeapFactory factory;
+ erts_factory_proc_init(&factory, BIF_P);
+ res = erts_max_heap_size_map(&factory, H_MAX_SIZE, H_MAX_FLAGS);
+ erts_factory_close(&factory);
BIF_RET(res);
} else if (BIF_ARG_1 == am_min_bin_vheap_size) {
hp = HAlloc(BIF_P, 3);
@@ -3668,7 +3661,7 @@ fun_info_2(BIF_ALIST_2)
if (is_local_fun(funp)) {
fe = funp->entry.fun;
- mfa = erts_get_fun_mfa(fe);
+ mfa = erts_get_fun_mfa(fe, erts_active_code_ix());
} else {
ASSERT(is_external_fun(funp) && funp->next == NULL);
mfa = &(funp->entry.exp)->info.mfa;
@@ -3761,7 +3754,7 @@ fun_info_mfa_1(BIF_ALIST_1)
hp = HAlloc(p, 4);
if (is_local_fun(funp)) {
- mfa = erts_get_fun_mfa(funp->entry.fun);
+ mfa = erts_get_fun_mfa(funp->entry.fun, erts_active_code_ix());
if (mfa == NULL) {
/* Unloaded funs must report their module even though we can't
@@ -4282,16 +4275,6 @@ BIF_RETTYPE erts_debug_get_internal_state_1(BIF_ALIST_1)
BIF_RET(am_ok);
}
#endif
- else if (ERTS_IS_ATOM_STR("pid_ref_table_size", BIF_ARG_1)) {
- Uint size = erts_pid_ref_table_size();
- if (IS_SSMALL(size))
- BIF_RET(make_small(size));
- else {
- Uint hsz = BIG_UWORD_HEAP_SIZE(size);
- Eterm *hp = HAlloc(BIF_P, hsz);
- BIF_RET(uword_to_big(size, hp));
- }
- }
else if (ERTS_IS_ATOM_STR("hashmap_collision_bonanza", BIF_ARG_1)) {
#ifdef DBG_HASHMAP_COLLISION_BONANZA
return am_true;
@@ -5076,20 +5059,20 @@ BIF_RETTYPE erts_debug_set_internal_state_2(BIF_ALIST_2)
BIF_RET(am_ok);
}
- else if (ERTS_IS_ATOM_STR("code_write_permission", BIF_ARG_1)) {
+ else if (ERTS_IS_ATOM_STR("code_mod_permission", BIF_ARG_1)) {
/*
* Warning: This is a unsafe way of seizing the "lock"
* as there is no automatic unlock if caller terminates.
*/
switch(BIF_ARG_2) {
case am_true:
- if (!erts_try_seize_code_write_permission(BIF_P)) {
+ if (!erts_try_seize_code_mod_permission(BIF_P)) {
ERTS_BIF_YIELD2(BIF_TRAP_EXPORT(BIF_erts_debug_set_internal_state_2),
BIF_P, BIF_ARG_1, BIF_ARG_2);
}
BIF_RET(am_true);
case am_false:
- erts_release_code_write_permission();
+ erts_release_code_mod_permission();
BIF_RET(am_true);
}
}
diff --git a/erts/emulator/beam/erl_bif_trace.c b/erts/emulator/beam/erl_bif_trace.c
index e3008dfcef..9f79607d97 100644
--- a/erts/emulator/beam/erl_bif_trace.c
+++ b/erts/emulator/beam/erl_bif_trace.c
@@ -47,7 +47,7 @@
const struct trace_pattern_flags erts_trace_pattern_flags_off = {0, 0, 0, 0, 0};
/*
- * The following variables are protected by code write permission.
+ * The following variables are protected by code modification permission.
*/
static int erts_default_trace_pattern_is_on;
static Binary *erts_default_match_spec;
@@ -55,7 +55,7 @@ static Binary *erts_default_meta_match_spec;
static struct trace_pattern_flags erts_default_trace_pattern_flags;
static ErtsTracer erts_default_meta_tracer;
-static struct { /* Protected by code write permission */
+static struct { /* Protected by code modification permission */
int current;
int install;
int local;
@@ -130,7 +130,7 @@ trace_pattern(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist)
ErtsTracer meta_tracer = erts_tracer_nil;
Uint freason = BADARG;
- if (!erts_try_seize_code_write_permission(p)) {
+ if (!erts_try_seize_code_mod_permission(p)) {
ERTS_BIF_YIELD3(BIF_TRAP_EXPORT(BIF_erts_internal_trace_pattern_3), p, MFA, Pattern, flaglist);
}
finish_bp.current = -1;
@@ -213,6 +213,13 @@ trace_pattern(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist)
flags.breakpoint = 1;
flags.call_time = 1;
break;
+ case am_call_memory:
+ if (is_global) {
+ goto error;
+ }
+ flags.breakpoint = 1;
+ flags.call_memory = 1;
+ break;
default:
goto error;
@@ -225,8 +232,8 @@ trace_pattern(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist)
p->fvalue = am_none;
- if (match_prog_set && !flags.local && !flags.meta && (flags.call_count || flags.call_time)) {
- /* A match prog is not allowed with just call_count or call_time*/
+ if (match_prog_set && !flags.local && !flags.meta && (flags.call_count || flags.call_time || flags.call_memory)) {
+ /* A match prog is not allowed with just call_count or call_time or call_memory */
p->fvalue = am_call_count;
goto error;
}
@@ -356,7 +363,7 @@ trace_pattern(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist)
ERTS_BIF_YIELD_RETURN(p, make_small(matches));
}
- erts_release_code_write_permission();
+ erts_release_code_mod_permission();
if (matches >= 0) {
return make_small(matches);
@@ -377,7 +384,7 @@ static void smp_bp_finisher(void* null)
#ifdef DEBUG
finish_bp.stager = NULL;
#endif
- erts_release_code_write_permission();
+ erts_release_code_mod_permission();
erts_proc_lock(p, ERTS_PROC_LOCK_STATUS);
if (!ERTS_PROC_IS_EXITING(p)) {
erts_resume(p, ERTS_PROC_LOCK_STATUS);
@@ -394,8 +401,8 @@ erts_get_default_trace_pattern(int *trace_pattern_is_on,
struct trace_pattern_flags *trace_pattern_flags,
ErtsTracer *meta_tracer)
{
- ERTS_LC_ASSERT(erts_has_code_write_permission() ||
- erts_thr_progress_is_blocking());
+ ERTS_LC_ASSERT(erts_has_code_mod_permission() ||
+ erts_thr_progress_is_blocking());
if (trace_pattern_is_on)
*trace_pattern_is_on = erts_default_trace_pattern_is_on;
if (match_spec)
@@ -410,8 +417,8 @@ erts_get_default_trace_pattern(int *trace_pattern_is_on,
int erts_is_default_trace_enabled(void)
{
- ERTS_LC_ASSERT(erts_has_code_write_permission() ||
- erts_thr_progress_is_blocking());
+ ERTS_LC_ASSERT(erts_has_code_mod_permission() ||
+ erts_thr_progress_is_blocking());
return erts_default_trace_pattern_is_on;
}
@@ -553,7 +560,7 @@ Eterm erts_internal_trace_3(BIF_ALIST_3)
BIF_ERROR(p, BADARG | EXF_HAS_EXT_INFO);
}
- if (!erts_try_seize_code_write_permission(BIF_P)) {
+ if (!erts_try_seize_code_mod_permission(BIF_P)) {
ERTS_TRACER_CLEAR(&tracer);
ERTS_BIF_YIELD3(BIF_TRAP_EXPORT(BIF_erts_internal_trace_3),
BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
@@ -771,7 +778,7 @@ Eterm erts_internal_trace_3(BIF_ALIST_3)
erts_thr_progress_unblock();
erts_proc_lock(p, ERTS_PROC_LOCK_MAIN);
}
- erts_release_code_write_permission();
+ erts_release_code_mod_permission();
ERTS_TRACER_CLEAR(&tracer);
BIF_RET(make_small(matches));
@@ -788,7 +795,7 @@ Eterm erts_internal_trace_3(BIF_ALIST_3)
erts_thr_progress_unblock();
erts_proc_lock(p, ERTS_PROC_LOCK_MAIN);
}
- erts_release_code_write_permission();
+ erts_release_code_mod_permission();
BIF_ERROR(p, BADARG);
}
@@ -804,7 +811,7 @@ Eterm trace_info_2(BIF_ALIST_2)
Eterm Key = BIF_ARG_2;
Eterm res;
- if (!erts_try_seize_code_write_permission(p)) {
+ if (!erts_try_seize_code_mod_permission(p)) {
ERTS_BIF_YIELD2(BIF_TRAP_EXPORT(BIF_trace_info_2), p, What, Key);
}
@@ -818,10 +825,10 @@ Eterm trace_info_2(BIF_ALIST_2)
res = trace_info_func(p, What, Key);
} else {
p->fvalue = am_badopt;
- erts_release_code_write_permission();
+ erts_release_code_mod_permission();
BIF_ERROR(p, BADARG | EXF_HAS_EXT_INFO);
}
- erts_release_code_write_permission();
+ erts_release_code_mod_permission();
if (is_value(res) && is_internal_ref(res))
BIF_TRAP1(erts_await_result, BIF_P, res);
@@ -1029,6 +1036,7 @@ trace_info_pid(Process* p, Eterm pid_spec, Eterm key)
#define FUNC_TRACE_META_TRACE (1<<3)
#define FUNC_TRACE_COUNT_TRACE (1<<4)
#define FUNC_TRACE_TIME_TRACE (1<<5)
+#define FUNC_TRACE_MEMORY_TRACE (1<<6)
/*
* Returns either FUNC_TRACE_NOEXIST, FUNC_TRACE_UNTRACED,
* FUNC_TRACE_GLOBAL_TRACE, or,
@@ -1049,7 +1057,8 @@ static int function_is_traced(Process *p,
Binary **ms_meta, /* out */
ErtsTracer *tracer_pid_meta, /* out */
Uint *count, /* out */
- Eterm *call_time) /* out */
+ Eterm *call_time, /* out */
+ Eterm *call_memory) /* out */
{
const ErtsCodeInfo *ci;
Export e;
@@ -1077,9 +1086,12 @@ static int function_is_traced(Process *p,
if (erts_is_mtrace_break(&ep->info, ms_meta, tracer_pid_meta)) {
r |= FUNC_TRACE_META_TRACE;
}
- if (erts_is_time_break(p, &ep->info, call_time)) {
+ if (erts_is_call_break(p, 1, &ep->info, call_time)) {
r |= FUNC_TRACE_TIME_TRACE;
}
+ if (erts_is_call_break(p, 0, &ep->info, call_memory)) {
+ r |= FUNC_TRACE_MEMORY_TRACE;
+ }
return r ? r : FUNC_TRACE_UNTRACED;
}
}
@@ -1093,8 +1105,10 @@ static int function_is_traced(Process *p,
? FUNC_TRACE_META_TRACE : 0)
| (erts_is_count_break(ci, count)
? FUNC_TRACE_COUNT_TRACE : 0)
- | (erts_is_time_break(p, ci, call_time)
- ? FUNC_TRACE_TIME_TRACE : 0);
+ | (erts_is_call_break(p, 1, ci, call_time)
+ ? FUNC_TRACE_TIME_TRACE : 0)
+ | (erts_is_call_break(p, 0, ci, call_memory)
+ ? FUNC_TRACE_MEMORY_TRACE : 0);
return r ? r : FUNC_TRACE_UNTRACED;
}
@@ -1114,6 +1128,7 @@ trace_info_func(Process* p, Eterm func_spec, Eterm key)
Eterm retval = am_false;
ErtsTracer meta = erts_tracer_nil;
Eterm call_time = NIL;
+ Eterm call_memory = NIL;
int r;
@@ -1133,7 +1148,7 @@ trace_info_func(Process* p, Eterm func_spec, Eterm key)
mfa[1] = tp[2];
mfa[2] = signed_val(tp[3]);
- if ( (key == am_call_time) || (key == am_all)) {
+ if ( (key == am_call_time) || (key == am_call_memory) || (key == am_all)) {
erts_proc_unlock(p, ERTS_PROC_LOCK_MAIN);
erts_thr_progress_block();
erts_proc_lock(p, ERTS_PROC_LOCK_MAIN);
@@ -1141,10 +1156,10 @@ trace_info_func(Process* p, Eterm func_spec, Eterm key)
erts_mtx_lock(&erts_dirty_bp_ix_mtx);
- r = function_is_traced(p, mfa, &ms, &ms_meta, &meta, &count, &call_time);
+ r = function_is_traced(p, mfa, &ms, &ms_meta, &meta, &count, &call_time, &call_memory);
erts_mtx_unlock(&erts_dirty_bp_ix_mtx);
- if ( (key == am_call_time) || (key == am_all)) {
+ if ( (key == am_call_time) || (key == am_call_memory) || (key == am_all)) {
erts_thr_progress_unblock();
}
@@ -1206,10 +1221,18 @@ trace_info_func(Process* p, Eterm func_spec, Eterm key)
retval = call_time;
}
break;
+ case am_call_memory:
+ if (r & FUNC_TRACE_MEMORY_TRACE) {
+ retval = call_memory;
+ }
+ break;
case am_all: {
- Eterm match_spec_meta = am_false, c = am_false, t, ct = am_false,
- m = am_false;
+ Eterm match_spec_meta = am_false;
+ Eterm call_count = am_false;
+ Eterm t, m;
+ /* ToDo: Rewrite this to loop and reuse the above cases */
+
if (ms) {
match_spec = MatchSetGetSource(ms);
match_spec = copy_object(match_spec, p);
@@ -1222,19 +1245,24 @@ trace_info_func(Process* p, Eterm func_spec, Eterm key)
match_spec_meta = NIL;
}
if (r & FUNC_TRACE_COUNT_TRACE) {
- c = erts_make_integer(count, p);
+ call_count = erts_make_integer(count, p);
}
- if (r & FUNC_TRACE_TIME_TRACE) {
- ct = call_time;
+ if (!(r & FUNC_TRACE_TIME_TRACE)) {
+ call_time = am_false;
+ }
+ if (!(r & FUNC_TRACE_MEMORY_TRACE)) {
+ call_memory = am_false;
}
m = erts_tracer_to_term(p, meta);
- hp = HAlloc(p, (3+2)*6);
+ hp = HAlloc(p, (3+2)*7);
retval = NIL;
- t = TUPLE2(hp, am_call_count, c); hp += 3;
+ t = TUPLE2(hp, am_call_count, call_count); hp += 3;
retval = CONS(hp, t, retval); hp += 2;
- t = TUPLE2(hp, am_call_time, ct); hp += 3;
+ t = TUPLE2(hp, am_call_time, call_time); hp += 3;
+ retval = CONS(hp, t, retval); hp += 2;
+ t = TUPLE2(hp, am_call_memory, call_memory); hp += 3;
retval = CONS(hp, t, retval); hp += 2;
t = TUPLE2(hp, am_meta_match_spec, match_spec_meta); hp += 3;
retval = CONS(hp, t, retval); hp += 2;
@@ -1432,19 +1460,21 @@ erts_set_trace_pattern(Process*p, ErtsCodeMFA *mfa, int specified,
ErtsTracer meta_tracer, int is_blocking)
{
const ErtsCodeIndex code_ix = erts_active_code_ix();
- int matches = 0;
- int i;
- int n;
+ Uint i, n, matches;
BpFunction* fp;
erts_bp_match_export(&finish_bp.e, mfa, specified);
+
fp = finish_bp.e.matching;
n = finish_bp.e.matched;
+ matches = 0;
for (i = 0; i < n; i++) {
- ErtsCodeInfo *ci_rw = fp[i].ci_rw;
+ ErtsCodeInfo *ci_rw;
Export* ep;
+ /* Export entries are always writable, discard const. */
+ ci_rw = (ErtsCodeInfo *)fp[i].code_info;
ep = ErtsContainerStruct(ci_rw, Export, info);
if (ep->bif_number != -1) {
@@ -1504,6 +1534,9 @@ erts_set_trace_pattern(Process*p, ErtsCodeMFA *mfa, int specified,
if (flags.call_time) {
erts_set_time_break(&finish_bp.f, on);
}
+ if (flags.call_memory) {
+ erts_set_memory_break(&finish_bp.f, on);
+ }
}
} else {
if (flags.local) {
@@ -1518,6 +1551,9 @@ erts_set_trace_pattern(Process*p, ErtsCodeMFA *mfa, int specified,
if (flags.call_time) {
erts_clear_time_break(&finish_bp.f);
}
+ if (flags.call_memory) {
+ erts_clear_memory_break(&finish_bp.f);
+ }
}
finish_bp.current = 0;
@@ -1583,7 +1619,7 @@ consolidate_event_tracing(ErtsTracingEvent te[])
int
erts_finish_breakpointing(void)
{
- ERTS_LC_ASSERT(erts_has_code_write_permission());
+ ERTS_LC_ASSERT(erts_has_code_mod_permission());
/*
* Memory and instruction barriers will be issued for all schedulers
@@ -1648,13 +1684,17 @@ erts_finish_breakpointing(void)
* deallocate the GenericBp structs for them.
*/
clean_export_entries(&finish_bp.e);
- erts_consolidate_bp_data(&finish_bp.e, 0);
- erts_consolidate_bp_data(&finish_bp.f, 1);
+ erts_consolidate_export_bp_data(&finish_bp.e);
+ erts_consolidate_local_bp_data(&finish_bp.f);
erts_bp_free_matched_functions(&finish_bp.e);
erts_bp_free_matched_functions(&finish_bp.f);
consolidate_event_tracing(erts_send_tracing);
consolidate_event_tracing(erts_receive_tracing);
- return 0;
+ return 1;
+ case 4:
+ /* All schedulers have run a code barrier (or will as soon as they
+ * awaken) after updating all breakpoints, it's safe to return now. */
+ return 0;
default:
ASSERT(0);
}
@@ -1670,7 +1710,9 @@ install_exp_breakpoints(BpFunctions* f)
Uint i;
for (i = 0; i < ne; i++) {
- Export* ep = ErtsContainerStruct(fp[i].ci_rw, Export, info);
+ /* Export entries are always writable, discard const. */
+ ErtsCodeInfo *ci_rw = (ErtsCodeInfo*)fp[i].code_info;
+ Export* ep = ErtsContainerStruct(ci_rw, Export, info);
erts_activate_export_trampoline(ep, code_ix);
}
}
@@ -1684,7 +1726,9 @@ uninstall_exp_breakpoints(BpFunctions* f)
Uint i;
for (i = 0; i < ne; i++) {
- Export* ep = ErtsContainerStruct(fp[i].ci_rw, Export, info);
+ /* Export entries are always writable, discard const. */
+ ErtsCodeInfo *ci_rw = (ErtsCodeInfo*)fp[i].code_info;
+ Export* ep = ErtsContainerStruct(ci_rw, Export, info);
if (erts_is_export_trampoline_active(ep, code_ix)) {
ASSERT(BeamIsOpCode(ep->trampoline.common.op, op_trace_jump_W));
@@ -1703,7 +1747,9 @@ clean_export_entries(BpFunctions* f)
Uint i;
for (i = 0; i < ne; i++) {
- Export* ep = ErtsContainerStruct(fp[i].ci_rw, Export, info);
+ /* Export entries are always writable, discard const. */
+ ErtsCodeInfo *ci_rw = (ErtsCodeInfo*)fp[i].code_info;
+ Export* ep = ErtsContainerStruct(ci_rw, Export, info);
if (erts_is_export_trampoline_active(ep, code_ix)) {
continue;
diff --git a/erts/emulator/beam/erl_bif_unique.c b/erts/emulator/beam/erl_bif_unique.c
index e1686c6f93..c5bc594332 100644
--- a/erts/emulator/beam/erl_bif_unique.c
+++ b/erts/emulator/beam/erl_bif_unique.c
@@ -62,7 +62,6 @@ static Uint32 max_thr_id;
#endif
static void init_magic_ref_tables(void);
-static void init_pid_ref_tables(void);
static Uint64 ref_init_value;
@@ -84,7 +83,6 @@ init_reference(void)
erts_atomic64_init_nob(&global_reference.count,
(erts_aint64_t) ref_init_value);
init_magic_ref_tables();
- init_pid_ref_tables();
}
static ERTS_INLINE void
@@ -146,8 +144,6 @@ Eterm erts_make_ref(Process *c_p)
return make_internal_ref(hp);
}
-static void pid_ref_save(Uint32 refn[ERTS_REF_NUMBERS], Eterm pid);
-
Eterm erts_make_pid_ref(Process *c_p)
{
Eterm* hp;
@@ -167,8 +163,6 @@ Eterm erts_make_pid_ref(Process *c_p)
write_pid_ref_thing(hp, ref[0], ref[1], ref[2], pid);
- pid_ref_save(ref, pid);
-
return make_internal_ref(hp);
}
@@ -439,290 +433,6 @@ void erts_ref_bin_free(ErtsMagicBinary *mb)
erts_bin_free((Binary *) mb);
}
-
-/*
- * Pid reference tables.
- *
- * These tables are intended to be temporary until huge
- * references (containing the pid) can be mandatory in
- * the external format.
- */
-
-typedef struct {
- HashBucket hash;
- Eterm pid;
- Uint64 value;
- Uint32 thr_id;
-} ErtsPidRefTableEntry;
-
-typedef struct {
- erts_rwmtx_t rwmtx;
- Hash hash;
- char name[32];
-} ErtsPidRefTable;
-
-typedef struct {
- union {
- ErtsPidRefTable table;
- char align__[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(ErtsPidRefTable))];
- } u;
-} ErtsAlignedPidRefTable;
-
-ErtsAlignedPidRefTable *pid_ref_table;
-
-Eterm
-erts_pid_ref_lookup__(Uint32 refn[ERTS_REF_NUMBERS])
-{
- ErtsPidRefTableEntry tmpl;
- ErtsPidRefTableEntry *tep;
- ErtsPidRefTable *tblp;
- Eterm pid;
-
- ASSERT(erts_is_pid_ref_numbers(refn));
-
- tmpl.value = erts_get_ref_numbers_value(refn);
- tmpl.thr_id = erts_get_ref_numbers_thr_id(refn);
- if (tmpl.thr_id > erts_no_schedulers)
- tblp = &pid_ref_table[0].u.table;
- else
- tblp = &pid_ref_table[tmpl.thr_id].u.table;
-
- erts_rwmtx_rlock(&tblp->rwmtx);
-
- tep = (ErtsPidRefTableEntry *) hash_get(&tblp->hash, &tmpl);
- pid = tep ? tep->pid : THE_NON_VALUE;
-
- erts_rwmtx_runlock(&tblp->rwmtx);
-
- return pid;
-}
-
-static void
-pid_ref_save(Uint32 refn[ERTS_REF_NUMBERS], Eterm pid)
-{
- ErtsPidRefTableEntry tmpl;
- ErtsPidRefTableEntry *tep;
- ErtsPidRefTable *tblp;
-
- ASSERT(erts_is_pid_ref_numbers(refn));
-
- tmpl.value = erts_get_ref_numbers_value(refn);
- tmpl.thr_id = erts_get_ref_numbers_thr_id(refn);
- tmpl.pid = pid;
-
- if (tmpl.thr_id > erts_no_schedulers)
- tblp = &pid_ref_table[0].u.table;
- else
- tblp = &pid_ref_table[tmpl.thr_id].u.table;
-
- erts_rwmtx_rlock(&tblp->rwmtx);
-
- tep = (ErtsPidRefTableEntry *) hash_get(&tblp->hash, &tmpl);
-
- erts_rwmtx_runlock(&tblp->rwmtx);
-
- if (!tep) {
- ErtsPidRefTableEntry *used_tep;
-
- ASSERT(tmpl.value == erts_get_ref_numbers_value(refn));
- ASSERT(tmpl.thr_id == erts_get_ref_numbers_thr_id(refn));
-
- if (tblp != &pid_ref_table[0].u.table) {
- tep = erts_alloc(ERTS_ALC_T_PREF_NSCHED_ENT,
- sizeof(ErtsNSchedPidRefTableEntry));
- }
- else {
- tep = erts_alloc(ERTS_ALC_T_PREF_ENT,
- sizeof(ErtsPidRefTableEntry));
- tep->thr_id = tmpl.thr_id;
- }
-
- tep->value = tmpl.value;
- tep->pid = pid;
-
- erts_rwmtx_rwlock(&tblp->rwmtx);
-
- used_tep = hash_put(&tblp->hash, tep);
-
- erts_rwmtx_rwunlock(&tblp->rwmtx);
-
- if (used_tep != tep) {
- if (tblp != &pid_ref_table[0].u.table)
- erts_free(ERTS_ALC_T_PREF_NSCHED_ENT, (void *) tep);
- else
- erts_free(ERTS_ALC_T_PREF_ENT, (void *) tep);
- }
- }
-}
-
-void
-erts_pid_ref_delete(Eterm ref)
-{
- ErtsPidRefTableEntry tmpl;
- ErtsPidRefTableEntry *tep;
- ErtsPidRefTable *tblp;
- Uint32 *refn;
-
- ASSERT(is_internal_pid_ref(ref));
-
- refn = internal_pid_ref_numbers(ref);
-
- ASSERT(erts_is_pid_ref_numbers(refn));
-
- tmpl.value = erts_get_ref_numbers_value(refn);
- tmpl.thr_id = erts_get_ref_numbers_thr_id(refn);
-
- if (tmpl.thr_id > erts_no_schedulers)
- tblp = &pid_ref_table[0].u.table;
- else
- tblp = &pid_ref_table[tmpl.thr_id].u.table;
-
- erts_rwmtx_rlock(&tblp->rwmtx);
-
- tep = (ErtsPidRefTableEntry *) hash_get(&tblp->hash, &tmpl);
-
- erts_rwmtx_runlock(&tblp->rwmtx);
-
- if (tep) {
-
- ASSERT(tmpl.value == erts_get_ref_numbers_value(refn));
- ASSERT(tmpl.thr_id == erts_get_ref_numbers_thr_id(refn));
-
- erts_rwmtx_rwlock(&tblp->rwmtx);
-
- tep = hash_remove(&tblp->hash, &tmpl);
-
- erts_rwmtx_rwunlock(&tblp->rwmtx);
-
- if (tep) {
- if (tblp != &pid_ref_table[0].u.table)
- erts_free(ERTS_ALC_T_PREF_NSCHED_ENT, (void *) tep);
- else
- erts_free(ERTS_ALC_T_PREF_ENT, (void *) tep);
- }
- }
-}
-
-static int nsched_preft_cmp(void *ve1, void *ve2)
-{
- ErtsNSchedPidRefTableEntry *e1 = ve1;
- ErtsNSchedPidRefTableEntry *e2 = ve2;
- return e1->value != e2->value;
-}
-
-static int non_nsched_preft_cmp(void *ve1, void *ve2)
-{
- ErtsPidRefTableEntry *e1 = ve1;
- ErtsPidRefTableEntry *e2 = ve2;
- return e1->value != e2->value || e1->thr_id != e2->thr_id;
-}
-
-static HashValue nsched_preft_hash(void *ve)
-{
- ErtsNSchedPidRefTableEntry *e = ve;
- return (HashValue) e->value;
-}
-
-static HashValue non_nsched_preft_hash(void *ve)
-{
- ErtsPidRefTableEntry *e = ve;
- HashValue h;
- h = (HashValue) e->thr_id;
- h *= 268440163;
- h += (HashValue) e->value;
- return h;
-}
-
-static void *preft_alloc(void *ve)
-{
- /*
- * We allocate the element before
- * hash_put() and pass it as
- * template which we get as
- * input...
- */
- return ve;
-}
-
-static void preft_free(void *ve)
-{
- /*
- * We free the element ourselves
- * after hash_remove()...
- */
-}
-
-static void *preft_meta_alloc(int i, size_t size)
-{
- return erts_alloc(ERTS_ALC_T_PREF_TAB_BKTS, size);
-}
-
-static void preft_meta_free(int i, void *ptr)
-{
- erts_free(ERTS_ALC_T_PREF_TAB_BKTS, ptr);
-}
-
-static void
-init_pid_ref_tables(void)
-{
- HashFunctions hash_funcs;
- int i;
- ErtsPidRefTable *tblp;
-
- pid_ref_table = erts_alloc_permanent_cache_aligned(ERTS_ALC_T_PREF_TAB,
- (sizeof(ErtsAlignedPidRefTable)
- * (erts_no_schedulers + 1)));
-
- hash_funcs.hash = non_nsched_preft_hash;
- hash_funcs.cmp = non_nsched_preft_cmp;
-
- hash_funcs.alloc = preft_alloc;
- hash_funcs.free = preft_free;
- hash_funcs.meta_alloc = preft_meta_alloc;
- hash_funcs.meta_free = preft_meta_free;
- hash_funcs.meta_print = erts_print;
-
- tblp = &pid_ref_table[0].u.table;
- erts_snprintf(&tblp->name[0], sizeof(tblp->name),
- "pid_ref_table_0");
- hash_init(0, &tblp->hash, &tblp->name[0], 1, hash_funcs);
- erts_rwmtx_init(&tblp->rwmtx, "pid_ref_table", NIL,
- ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
-
- hash_funcs.hash = nsched_preft_hash;
- hash_funcs.cmp = nsched_preft_cmp;
-
- for (i = 1; i <= erts_no_schedulers; i++) {
- ErtsPidRefTable *tblp = &pid_ref_table[i].u.table;
- erts_snprintf(&tblp->name[0], sizeof(tblp->name),
- "pid_ref_table_%d", i);
- hash_init(0, &tblp->hash, &tblp->name[0], 1, hash_funcs);
- erts_rwmtx_init(&tblp->rwmtx, "pid_ref_table", NIL,
- ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
- }
-}
-
-
-Uint
-erts_pid_ref_table_size(void)
-{
- int i;
- Uint sz = 0;
-
- for (i = 0; i <= erts_no_schedulers; i++) {
- HashInfo hi;
- ErtsPidRefTable *tblp = &pid_ref_table[i].u.table;
- erts_rwmtx_rlock(&tblp->rwmtx);
- hash_get_info(&hi, &tblp->hash);
- erts_rwmtx_runlock(&tblp->rwmtx);
- sz += (Uint) hi.objs;
- }
-
- return sz;
-}
-
-
-
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Unique Integer *
\* */
diff --git a/erts/emulator/beam/erl_bif_unique.h b/erts/emulator/beam/erl_bif_unique.h
index bcbcfaee0d..def05f0d53 100644
--- a/erts/emulator/beam/erl_bif_unique.h
+++ b/erts/emulator/beam/erl_bif_unique.h
@@ -40,9 +40,7 @@ void erts_make_magic_ref_in_array(Uint32 ref[ERTS_REF_NUMBERS]);
void erts_magic_ref_remove_bin(Uint32 refn[ERTS_REF_NUMBERS]);
void erts_magic_ref_save_bin__(Eterm ref);
ErtsMagicBinary *erts_magic_ref_lookup_bin__(Uint32 refn[ERTS_REF_NUMBERS]);
-void erts_pid_ref_delete(Eterm ref);
-Eterm erts_pid_ref_lookup__(Uint32 refn[ERTS_REF_NUMBERS]);
-Uint erts_pid_ref_table_size(void);
+
/* strict monotonic counter */
@@ -96,7 +94,7 @@ ERTS_GLB_INLINE Eterm erts_mk_magic_ref(Eterm **hpp, ErlOffHeap *ohp, Binary *mb
ERTS_GLB_INLINE Binary *erts_magic_ref2bin(Eterm mref);
ERTS_GLB_INLINE void erts_magic_ref_save_bin(Eterm ref);
ERTS_GLB_INLINE ErtsMagicBinary *erts_magic_ref_lookup_bin(Uint32 ref[ERTS_REF_NUMBERS]);
-ERTS_GLB_INLINE Eterm erts_pid_ref_lookup(Uint32 *refn);
+ERTS_GLB_INLINE Eterm erts_pid_ref_lookup(Uint32 *refn, int len);
ERTS_GLB_INLINE Eterm erts_get_pid_of_ref(Eterm ref);
#define ERTS_REF1_MAGIC_MARKER_BIT_NO__ \
@@ -256,11 +254,24 @@ erts_magic_ref_lookup_bin(Uint32 ref[ERTS_REF_NUMBERS])
* from the outside world...
*/
ERTS_GLB_INLINE Eterm
-erts_pid_ref_lookup(Uint32 *refn)
+erts_pid_ref_lookup(Uint32 *refn, int len)
{
+ Eterm pid;
+ if (len != ERTS_PID_REF_NUMBERS)
+ return am_undefined;
if (!erts_is_pid_ref_numbers(refn))
- return THE_NON_VALUE;
- return erts_pid_ref_lookup__(refn);
+ return am_undefined;
+
+ pid = (((Eterm) refn[3])
+#ifdef ARCH_64
+ | (((Eterm) refn[4]) << 32)
+#endif
+ );
+
+ if (!is_internal_pid(pid))
+ return THE_NON_VALUE; /* We got garbage; we have not created this... */
+
+ return pid;
}
ERTS_GLB_INLINE Eterm erts_get_pid_of_ref(Eterm ref)
diff --git a/erts/emulator/beam/erl_bits.h b/erts/emulator/beam/erl_bits.h
index 4596c65959..1d95536a68 100644
--- a/erts/emulator/beam/erl_bits.h
+++ b/erts/emulator/beam/erl_bits.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1999-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1999-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,23 +51,20 @@ typedef struct erl_bin_match_buffer {
struct erl_bits_state {
/*
- * Used for building binaries.
+ * Temporary buffer sometimes used by erts_new_bs_put_integer().
*/
byte *byte_buf_;
int byte_buf_len_;
+
/*
- * Used for building binaries using the new instruction set.
+ * Pointer to the beginning of the current binary.
*/
- byte* erts_current_bin_; /* Pointer to beginning of current binary. */
+ byte* erts_current_bin_;
+
/*
- * Offset in bits into the current binary (new instruction set) or
- * buffer (old instruction set).
+ * Offset in bits into the current binary.
*/
Uint erts_bin_offset_;
- /*
- * Whether the current binary is writable.
- */
- unsigned erts_writable_bin_;
};
typedef struct erl_bin_match_struct{
@@ -117,7 +114,6 @@ typedef struct erl_bin_match_struct{
#define erts_bin_offset (ErlBitsState.erts_bin_offset_)
#define erts_current_bin (ErlBitsState.erts_current_bin_)
-#define erts_writable_bin (ErlBitsState.erts_writable_bin_)
#define copy_binary_to_buffer(DstBuffer, DstBufOffset, SrcBuffer, SrcBufferOffset, NumBits) \
do { \
@@ -151,6 +147,11 @@ void erts_bits_destroy_state(ERL_BITS_PROTO_0);
#define WSIZE(n) ((n + sizeof(Eterm) - 1) / sizeof(Eterm))
/*
+ * Define the maximum number of bits in a unit for the binary syntax.
+ */
+#define ERL_UNIT_BITS 8
+
+/*
* Binary matching.
*/
@@ -192,7 +193,7 @@ Eterm erts_bs_init_writable(Process* p, Eterm sz);
* Common utilities.
*/
void erts_copy_bits(byte* src, size_t soffs, int sdir,
- byte* dst, size_t doffs,int ddir, size_t n);
+ byte* dst, size_t doffs,int ddir, size_t n);
int erts_cmp_bits(byte* a_ptr, size_t a_offs, byte* b_ptr, size_t b_offs, size_t size);
diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c
index 514fd2b74d..8f5e1a9543 100644
--- a/erts/emulator/beam/erl_db.c
+++ b/erts/emulator/beam/erl_db.c
@@ -507,13 +507,13 @@ save_sched_table(Process *c_p, DbTable *tb)
DbTable *first;
ASSERT(esdp);
- erts_atomic_inc_nob(&esdp->ets_tables.count);
+ erts_atomic_inc_nob(&esdp->u.ets_tables.count);
erts_refc_inc(&tb->common.refc, 1);
- first = esdp->ets_tables.clist;
+ first = esdp->u.ets_tables.clist;
if (!first) {
tb->common.all.next = tb->common.all.prev = tb;
- esdp->ets_tables.clist = tb;
+ esdp->u.ets_tables.clist = tb;
}
else {
tb->common.all.prev = first->common.all.prev;
@@ -531,14 +531,14 @@ remove_sched_table(ErtsSchedulerData *esdp, DbTable *tb)
ASSERT(erts_get_ref_numbers_thr_id(ERTS_MAGIC_BIN_REFN(tb->common.btid))
== (Uint32) esdp->no);
- ASSERT(erts_atomic_read_nob(&esdp->ets_tables.count) > 0);
- erts_atomic_dec_nob(&esdp->ets_tables.count);
+ ASSERT(erts_atomic_read_nob(&esdp->u.ets_tables.count) > 0);
+ erts_atomic_dec_nob(&esdp->u.ets_tables.count);
eaydp = ERTS_SCHED_AUX_YIELD_DATA(esdp, ets_all);
if (eaydp->ongoing) {
/* ets:all() op process list from last to first... */
if (eaydp->tab == tb) {
- if (eaydp->tab == esdp->ets_tables.clist)
+ if (eaydp->tab == esdp->u.ets_tables.clist)
eaydp->tab = NULL;
else
eaydp->tab = tb->common.all.prev;
@@ -547,23 +547,23 @@ remove_sched_table(ErtsSchedulerData *esdp, DbTable *tb)
if (tb->common.all.next == tb) {
ASSERT(tb->common.all.prev == tb);
- ASSERT(esdp->ets_tables.clist == tb);
- esdp->ets_tables.clist = NULL;
+ ASSERT(esdp->u.ets_tables.clist == tb);
+ esdp->u.ets_tables.clist = NULL;
}
else {
#ifdef DEBUG
- DbTable *tmp = esdp->ets_tables.clist;
+ DbTable *tmp = esdp->u.ets_tables.clist;
do {
if (tmp == tb) break;
tmp = tmp->common.all.next;
- } while (tmp != esdp->ets_tables.clist);
+ } while (tmp != esdp->u.ets_tables.clist);
ASSERT(tmp == tb);
#endif
tb->common.all.prev->common.all.next = tb->common.all.next;
tb->common.all.next->common.all.prev = tb->common.all.prev;
- if (esdp->ets_tables.clist == tb)
- esdp->ets_tables.clist = tb->common.all.next;
+ if (esdp->u.ets_tables.clist == tb)
+ esdp->u.ets_tables.clist = tb->common.all.next;
}
@@ -2765,8 +2765,44 @@ BIF_RETTYPE ets_lookup_element_3(BIF_ALIST_3)
}
}
-/*
- * BIF to erase a whole table and release all memory it holds
+/*
+** Get an element from a term
+** get_element_4(Tab, Key, Index, Default)
+** return the element or a list of elements if bag or Default if the element is not present
+*/
+BIF_RETTYPE ets_lookup_element_4(BIF_ALIST_4)
+{
+ DbTable* tb;
+ Sint index;
+ int cret;
+ Eterm ret;
+
+ CHECK_TABLES();
+
+ DB_BIF_GET_TABLE(tb, DB_READ, LCK_READ, BIF_ets_lookup_element_4);
+
+ if (is_not_small(BIF_ARG_3) || ((index = signed_val(BIF_ARG_3)) < 1)) {
+ db_unlock(tb, LCK_READ);
+ BIF_ERROR(BIF_P, BADARG);
+ }
+
+ cret = tb->common.meth->db_get_element(BIF_P, tb,
+ BIF_ARG_2, index, &ret);
+ db_unlock(tb, LCK_READ);
+ switch (cret) {
+ case DB_ERROR_NONE:
+ BIF_RET(ret);
+ case DB_ERROR_BADKEY:
+ BIF_RET(BIF_ARG_4);
+ case DB_ERROR_SYSRES:
+ BIF_ERROR(BIF_P, SYSTEM_LIMIT);
+ default:
+ BIF_ERROR(BIF_P, BADARG);
+ }
+}
+
+/*
+ * BIF to erase a whole table and release all memory it holds
*/
BIF_RETTYPE ets_delete_1(BIF_ALIST_1)
{
@@ -3311,7 +3347,7 @@ ets_all_reply(ErtsSchedulerData *esdp, ErtsEtsAllReq **reqpp,
hp = &hfragp->mem[hfragp->used_size];
list = *hp;
hfragp->used_size = hfragp->alloc_size;
- first = esdp->ets_tables.clist;
+ first = esdp->u.ets_tables.clist;
tb = *tablepp;
}
else {
@@ -3319,7 +3355,7 @@ ets_all_reply(ErtsSchedulerData *esdp, ErtsEtsAllReq **reqpp,
ASSERT(!*tablepp);
/* Max heap size needed... */
- sz = erts_atomic_read_nob(&esdp->ets_tables.count);
+ sz = erts_atomic_read_nob(&esdp->u.ets_tables.count);
sz *= ERTS_MAGIC_REF_THING_SIZE + 2;
sz += 3 + ERTS_REF_THING_SIZE;
hfragp = new_message_buffer(sz);
@@ -3327,7 +3363,7 @@ ets_all_reply(ErtsSchedulerData *esdp, ErtsEtsAllReq **reqpp,
hp = &hfragp->mem[0];
ohp = &hfragp->off_heap;
list = NIL;
- first = esdp->ets_tables.clist;
+ first = esdp->u.ets_tables.clist;
tb = first ? first->common.all.prev : NULL;
}
@@ -3413,7 +3449,7 @@ erts_handle_yielded_ets_all_request(ErtsAuxWorkData *awdp)
return 0; /* All work completed! */
if (yc < ERTS_ETS_ALL_TB_YCNT_START &&
- yc > erts_atomic_read_nob(&esdp->ets_tables.count))
+ yc > erts_atomic_read_nob(&esdp->u.ets_tables.count))
return 1; /* Yield! */
eaydp->ongoing = ongoing = eaydp->queue;
@@ -4615,8 +4651,8 @@ erts_ets_sched_spec_data_init(ErtsSchedulerData *esdp)
eaydp->hfrag = NULL;
eaydp->tab = NULL;
eaydp->queue = NULL;
- esdp->ets_tables.clist = NULL;
- erts_atomic_init_nob(&esdp->ets_tables.count, 0);
+ esdp->u.ets_tables.clist = NULL;
+ erts_atomic_init_nob(&esdp->u.ets_tables.count, 0);
}
@@ -5455,7 +5491,7 @@ erts_db_foreach_table(void (*func)(DbTable *, void *), void *arg, int alive_only
for (ix = 0; ix < erts_no_schedulers; ix++) {
ErtsSchedulerData *esdp = ERTS_SCHEDULER_IX(ix);
- DbTable *first = esdp->ets_tables.clist;
+ DbTable *first = esdp->u.ets_tables.clist;
if (first) {
DbTable *tb = first;
do {
@@ -5487,7 +5523,7 @@ erts_db_foreach_thr_prgr_offheap(void (*func)(ErlOffHeap *, void *),
/* retrieve max number of ets tables */
Uint
-erts_db_get_max_tabs()
+erts_db_get_max_tabs(void)
{
return db_max_tabs;
}
@@ -5499,7 +5535,7 @@ Uint erts_ets_table_count(void)
for (six = 0; six < erts_no_schedulers; six++) {
ErtsSchedulerData *esdp = &erts_aligned_scheduler_data[six].esd;
- tb_count += erts_atomic_read_nob(&esdp->ets_tables.count);
+ tb_count += erts_atomic_read_nob(&esdp->u.ets_tables.count);
}
return tb_count;
}
@@ -5566,7 +5602,7 @@ static void lcnt_update_db_locks_per_sched(void *enable) {
DbTable *head;
esdp = erts_get_scheduler_data();
- head = esdp->ets_tables.clist;
+ head = esdp->u.ets_tables.clist;
if(head) {
DbTable *iterator = head;
diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c
index 13aaf00c80..fa8a8c15ec 100644
--- a/erts/emulator/beam/erl_db_hash.c
+++ b/erts/emulator/beam/erl_db_hash.c
@@ -3877,6 +3877,7 @@ Ldone:
handle->flags = flags;
handle->new_size = b->dbterm.size;
handle->u.hash.lck_ctr = lck_ctr;
+ handle->old_tpl = NULL;
return 1;
}
diff --git a/erts/emulator/beam/erl_db_tree.c b/erts/emulator/beam/erl_db_tree.c
index 61125b94b2..31834d4131 100644
--- a/erts/emulator/beam/erl_db_tree.c
+++ b/erts/emulator/beam/erl_db_tree.c
@@ -3446,6 +3446,7 @@ int db_lookup_dbterm_tree_common(Process *p, DbTable *tbl, TreeDbTerm **root,
handle->flags = flags;
handle->bp = (void**) pp;
handle->new_size = (*pp)->dbterm.size;
+ handle->old_tpl = NULL;
return 1;
}
diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c
index a236a09791..af5aa09a5c 100644
--- a/erts/emulator/beam/erl_db_util.c
+++ b/erts/emulator/beam/erl_db_util.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1998-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1998-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -295,6 +295,7 @@ typedef enum {
matchTrace2,
matchTrace3,
matchCallerLine,
+ matchCurrentStacktrace
} MatchOps;
/*
@@ -588,16 +589,34 @@ static DMCGuardBif guard_tab[] =
DBIF_ALL
},
{
- am_is_binary,
- &is_binary_1,
- 1,
- DBIF_ALL
+ am_is_binary,
+ &is_binary_1,
+ 1,
+ DBIF_ALL
},
{
- am_is_function,
- &is_function_1,
- 1,
- DBIF_ALL
+ am_is_bitstring,
+ &is_bitstring_1,
+ 1,
+ DBIF_ALL
+ },
+ {
+ am_is_boolean,
+ &is_boolean_1,
+ 1,
+ DBIF_ALL
+ },
+ {
+ am_is_function,
+ &is_function_1,
+ 1,
+ DBIF_ALL
+ },
+ {
+ am_is_function,
+ &is_function_2,
+ 2,
+ DBIF_ALL
},
{
am_is_record,
@@ -630,6 +649,18 @@ static DMCGuardBif guard_tab[] =
DBIF_ALL
},
{
+ am_max,
+ &max_2,
+ 2,
+ DBIF_ALL
+ },
+ {
+ am_min,
+ &min_2,
+ 2,
+ DBIF_ALL
+ },
+ {
am_node,
&node_1,
1,
@@ -684,6 +715,12 @@ static DMCGuardBif guard_tab[] =
DBIF_ALL
},
{
+ am_tuple_size,
+ &tuple_size_1,
+ 1,
+ DBIF_ALL
+ },
+ {
am_binary_part,
&binary_part_2,
2,
@@ -708,10 +745,22 @@ static DMCGuardBif guard_tab[] =
DBIF_ALL
},
{
- am_float,
- &float_1,
- 1,
- DBIF_ALL
+ am_float,
+ &float_1,
+ 1,
+ DBIF_ALL
+ },
+ {
+ am_ceil,
+ &ceil_1,
+ 1,
+ DBIF_ALL
+ },
+ {
+ am_floor,
+ &floor_1,
+ 1,
+ DBIF_ALL
},
{
am_Plus,
@@ -2495,7 +2544,8 @@ restart:
top = HAllocX(build_proc, sz, HEAP_XTRA);
if (in_flags & ERTS_PAM_CONTIGUOUS_TUPLE) {
ASSERT(is_tuple(term));
- *esp++ = copy_shallow(tuple_val(term), sz, &top, &MSO(build_proc));
+ *esp++ = make_tuple(copy_shallow(tuple_val(term), sz, &top,
+ &MSO(build_proc)));
}
else {
*esp++ = copy_struct(term, sz, &top, &MSO(build_proc));
@@ -2792,6 +2842,64 @@ restart:
}
}
break;
+ case matchCurrentStacktrace: {
+ Uint sz;
+ Uint heap_size;
+ Eterm mfa;
+ Eterm res;
+ struct StackTrace *s;
+ int depth;
+ FunctionInfo* stk;
+ FunctionInfo* stkp;
+
+ ASSERT(c_p == self);
+
+ depth = unsigned_val(esp[-1]);
+ esp--;
+
+ sz = offsetof(struct StackTrace, trace) + sizeof(ErtsCodePtr) * depth;
+ s = (struct StackTrace *) erts_alloc(ERTS_ALC_T_TMP, sz);
+ s->depth = 0;
+ s->pc = NULL;
+
+ erts_save_stacktrace(c_p, s, depth);
+
+ depth = s->depth;
+ stk = stkp = (FunctionInfo *) erts_alloc(ERTS_ALC_T_TMP,
+ depth*sizeof(FunctionInfo));
+
+ heap_size = 0;
+ for (i = 0; i < depth; i++) {
+ erts_lookup_function_info(stkp, s->trace[i], 1);
+ if (stkp->mfa) {
+ heap_size += stkp->needed + 2;
+ stkp++;
+ }
+ }
+
+ res = NIL;
+
+ if (heap_size > 0) {
+ int count = stkp - stk;
+
+ ASSERT(count > 0 && count <= MAX_BACKTRACE_SIZE);
+
+ ehp = HAllocX(build_proc, heap_size, HEAP_XTRA);
+
+ for (i = count - 1; i >= 0; i--) {
+ ehp = erts_build_mfa_item(&stk[i], ehp, am_true, &mfa, NIL);
+ res = CONS(ehp, mfa, res);
+ ehp += 2;
+ }
+ }
+
+ *esp++ = res;
+
+ erts_free(ERTS_ALC_T_TMP, stk);
+ erts_free(ERTS_ALC_T_TMP, s);
+
+ break;
+ }
case matchSilent:
ASSERT(c_p == self);
--esp;
@@ -3125,9 +3233,27 @@ both_size_set:
handle->new_size = handle->new_size - oldval_sz + newval_sz;
- /* write new value in old dbterm, finalize will make a flat copy */
+ /*
+ * Write new value in old dbterm, finalize will make a flat copy.
+ */
+ if (!(handle->flags & DB_MUST_RESIZE)) {
+ const size_t nbytes = (arityval(handle->dbterm->tpl[0]) + 1) * sizeof(Eterm);
+ /*
+ * First time here. Save the original tuple array in order to make
+ * fast size calculations of untouched elements.
+ */
+ ASSERT(!handle->tb->common.compress);
+ ASSERT(!handle->old_tpl);
+ if (nbytes > sizeof(handle->old_tpl_dflt)) {
+ handle->old_tpl = erts_alloc(ERTS_ALC_T_TMP, nbytes);
+ } else {
+ handle->old_tpl = handle->old_tpl_dflt;
+ }
+ sys_memcpy(handle->old_tpl, handle->dbterm->tpl, nbytes);
+ handle->flags |= DB_MUST_RESIZE;
+ }
+ ASSERT(!!handle->old_tpl != !!handle->tb->common.compress);
handle->dbterm->tpl[position] = newval;
- handle->flags |= DB_MUST_RESIZE;
}
static ERTS_INLINE byte* db_realloc_term(DbTableCommon* tb, void* old,
@@ -3281,6 +3407,38 @@ static void* copy_to_comp(int keypos, Eterm obj, DbTerm* dest,
return top.cp;
}
+static ERTS_INLINE
+Eterm copy_ets_element(Eterm obj, int sz, Eterm **hpp, ErlOffHeap *off_heap)
+{
+#ifdef DEBUG
+ const Eterm* const hp_start = *hpp;
+#endif
+ Eterm copy;
+
+ if (sz == 0) {
+ ASSERT(is_immed(obj) || obj == ERTS_GLOBAL_LIT_EMPTY_TUPLE);
+ return obj;
+ }
+ ASSERT(is_not_immed(obj));
+
+ if (is_list(obj) && is_immed(CAR(list_val(obj)))) {
+ /* copy_struct() would put this last,
+ but we need the top term to be first in block */
+ Eterm* src = list_val(obj);
+ Eterm* dst = *hpp;
+
+ CAR(dst) = CAR(src);
+ *hpp += 2;
+ CDR(dst) = copy_struct(CDR(src), sz-2, hpp, off_heap);
+ copy = make_list(dst);
+ }
+ else {
+ copy = copy_struct(obj, sz, hpp, off_heap);
+ }
+ ASSERT(ptr_val(copy) == hp_start);
+ return copy;
+}
+
/*
** Copy the object into a possibly new DbTerm,
** offset is the offset of the DbTerm from the start
@@ -3293,8 +3451,24 @@ void* db_store_term(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj)
byte* basep;
DbTerm* newp;
Eterm* top;
- int size = size_object(obj);
+ Eterm* source_ptr;
+ Eterm* dest_ptr;
+ int arity, i, size;
ErlOffHeap tmp_offheap;
+ Uint elem_sizes_dflt[8];
+ Uint* elem_sizes = elem_sizes_dflt;
+
+ /* Calculate sizes of all elements and total size */
+ source_ptr = tuple_val(obj);
+ arity = arityval(*source_ptr);
+ if (arity > sizeof(elem_sizes_dflt) / sizeof(elem_sizes_dflt[0])) {
+ elem_sizes = erts_alloc(ERTS_ALC_T_TMP, arity * sizeof(*elem_sizes));
+ }
+ size = arity + 1;
+ for (i = 0; i < arity; i++) {
+ elem_sizes[i] = size_object(source_ptr[i+1]);
+ size += elem_sizes[i];
+ }
if (old != 0) {
basep = ((byte*) old) - offset;
@@ -3317,14 +3491,26 @@ void* db_store_term(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj)
(offset + sizeof(DbTerm) + sizeof(Eterm)*(size-1)));
newp = (DbTerm*) (basep + offset);
}
+
+ /*
+ * Do the actual copy. Lay out elements in order after the top tuple.
+ * This is relied upon by db_copy_element_from_ets.
+ */
newp->size = size;
top = newp->tpl;
- tmp_offheap.first = NULL;
- copy_struct(obj, size, &top, &tmp_offheap);
+ tmp_offheap.first = NULL;
+ *top++ = *source_ptr++; // copy the header
+ dest_ptr = top + arity;
+ for (i = 0; i < arity; ++i) {
+ *top++ = copy_ets_element(source_ptr[i], elem_sizes[i], &dest_ptr,
+ &tmp_offheap);
+ }
newp->first_oh = tmp_offheap.first;
#ifdef DEBUG_CLONE
newp->debug_clone = NULL;
#endif
+ if (elem_sizes != elem_sizes_dflt)
+ erts_free(ERTS_ALC_T_TMP, elem_sizes);
return basep;
}
@@ -3367,6 +3553,7 @@ void* db_store_term_comp(DbTableCommon *tb, /* May be NULL */
return basep;
}
+static Uint db_element_size(DbTerm *obj, Eterm* tpl, Uint pos);
void db_finalize_resize(DbUpdateHandle* handle, Uint offset)
{
@@ -3379,6 +3566,8 @@ void db_finalize_resize(DbUpdateHandle* handle, Uint offset)
byte* newp = erts_db_alloc(ERTS_ALC_T_DB_TERM, tbl, alloc_sz);
byte* oldp = *(handle->bp);
+ ASSERT(handle->flags & DB_MUST_RESIZE);
+
sys_memcpy(newp, oldp, offset); /* copy only hash/tree header */
*(handle->bp) = newp;
newDbTerm = (DbTerm*) (newp + offset);
@@ -3396,16 +3585,36 @@ void db_finalize_resize(DbUpdateHandle* handle, Uint offset)
}
else {
ErlOffHeap tmp_offheap;
- Eterm* tpl = handle->dbterm->tpl;
- Eterm* top = newDbTerm->tpl;
+ DbTerm* src = handle->dbterm;
+ const Uint arity = arityval(src->tpl[0]);
+ Eterm* top = &newDbTerm->tpl[arity+1];
+ int i;
+
+ ASSERT(handle->old_tpl);
tmp_offheap.first = NULL;
+ newDbTerm->tpl[0] = src->tpl[0];
+ for (i = 1; i <= arity; ++i) {
+ Uint sz;
+ if (is_immed(src->tpl[i])) {
+ newDbTerm->tpl[i] = src->tpl[i];
+ }
+ else {
+ if (src->tpl[i] != handle->old_tpl[i]) {
+ sz = size_object(src->tpl[i]);
+ }
+ else {
+ sz = db_element_size(src, handle->old_tpl, i);
+ }
+ newDbTerm->tpl[i] = copy_ets_element(src->tpl[i], sz, &top,
+ &tmp_offheap);
+ }
+ }
+ ASSERT((byte*)top == (newp + alloc_sz));
+ newDbTerm->first_oh = tmp_offheap.first;
- {
- copy_struct(make_tuple(tpl), handle->new_size, &top, &tmp_offheap);
- newDbTerm->first_oh = tmp_offheap.first;
- ASSERT((byte*)top == (newp + alloc_sz));
- }
+ if (handle->old_tpl != handle->old_tpl_dflt)
+ erts_free(ERTS_ALC_T_TMP, handle->old_tpl);
}
}
@@ -3446,36 +3655,78 @@ Eterm db_copy_from_comp(DbTableCommon* tb, DbTerm* bp, Eterm** hpp,
return make_tuple(hp);
}
-Eterm db_copy_element_from_ets(DbTableCommon* tb, Process* p,
- DbTerm* obj, Uint pos,
- Eterm** hpp, Uint extra)
-{
+Eterm db_copy_element_from_ets(DbTableCommon *tb, Process *p, DbTerm *obj,
+ Uint pos, Eterm **hpp, Uint extra) {
if (is_immed(obj->tpl[pos])) {
- *hpp = HAlloc(p, extra);
- return obj->tpl[pos];
- }
- if (tb->compress && pos != tb->keypos) {
- byte* ext = elem2ext(obj->tpl, pos);
- Sint sz = erts_decode_ext_size_ets(ext, db_alloced_size_comp(obj)) + extra;
- Eterm copy;
- ErtsHeapFactory factory;
-
- erts_factory_proc_prealloc_init(&factory, p, sz);
- copy = erts_decode_ext_ets(&factory, ext);
- *hpp = erts_produce_heap(&factory, extra, 0);
- erts_factory_close(&factory);
+ *hpp = HAlloc(p, extra);
+ return obj->tpl[pos];
+ }
+ if (tb->compress) {
+ if (pos == tb->keypos) {
+ Uint sz = size_object(obj->tpl[pos]);
+ *hpp = HAlloc(p, sz + extra);
+ return copy_struct(obj->tpl[pos], sz, hpp, &MSO(p));
+ }
+ else {
+ byte *ext = elem2ext(obj->tpl, pos);
+ Sint sz =
+ erts_decode_ext_size_ets(ext, db_alloced_size_comp(obj)) + extra;
+ Eterm copy;
+ ErtsHeapFactory factory;
+
+ erts_factory_proc_prealloc_init(&factory, p, sz);
+ copy = erts_decode_ext_ets(&factory, ext);
+ *hpp = erts_produce_heap(&factory, extra, 0);
+ erts_factory_close(&factory);
#ifdef DEBUG_CLONE
- ASSERT(EQ(copy, obj->debug_clone[pos]));
+ ASSERT(EQ(copy, obj->debug_clone[pos]));
#endif
- return copy;
- }
- else {
- Uint sz = size_object(obj->tpl[pos]);
- *hpp = HAlloc(p, sz + extra);
- return copy_struct(obj->tpl[pos], sz, hpp, &MSO(p));
+ return copy;
+ }
+ } else {
+ Uint sz = db_element_size(obj, obj->tpl, pos);
+ *hpp = HAlloc(p, sz + extra);
+ return copy_shallow_obj(obj->tpl[pos], sz, hpp, &MSO(p));
}
}
+/*
+ * Return the size of an element of an uncompressed ETS record.
+ * Relies on each element of the ETS record being laid out contiguously,
+ * and starting with the top term.
+ */
+static Uint db_element_size(DbTerm *obj, Eterm* tpl, Uint pos) {
+ Eterm *start_ptr;
+ Eterm *end_ptr;
+ Eterm elem;
+ Uint arity, i, sz;
+
+ elem = tpl[pos];
+ if (is_zero_sized(elem))
+ return 0;
+
+ ASSERT(is_boxed(elem) || is_list(elem));
+ start_ptr = ptr_val(elem);
+ ASSERT(!erts_is_literal(elem, start_ptr));
+
+ arity = arityval(tpl[0]);
+ for (i = pos + 1; i <= arity; ++i) {
+ elem = tpl[i];
+ if (!is_zero_sized(elem)) {
+ ASSERT(is_boxed(elem) || is_list(elem));
+ end_ptr = ptr_val(elem);
+ ASSERT(!erts_is_literal(elem, end_ptr));
+ goto done;
+ }
+ }
+ end_ptr = obj->tpl + obj->size;
+
+done:
+ sz = end_ptr - start_ptr;
+ ASSERT(sz == size_object(tpl[pos]));
+ return sz;
+
+}
/* Our own "cleanup_offheap"
* as refc-binaries may be unaligned in compressed terms
@@ -4958,6 +5209,57 @@ static DMCRet dmc_caller_line(DMCContext *context,
return retOk;
}
+static DMCRet dmc_current_stacktrace(DMCContext *context,
+ DMCHeap *heap,
+ DMC_STACK_TYPE(UWord) *text,
+ Eterm t,
+ int *constant)
+{
+ Eterm *p = tuple_val(t);
+ Uint a = arityval(*p);
+ DMCRet ret;
+ int depth;
+
+ if (!check_trace("current_stacktrace", context, constant,
+ (DCOMP_CALL_TRACE|DCOMP_ALLOW_TRACE_OPS), 0, &ret))
+ return ret;
+
+ switch (a) {
+ case 1:
+ *constant = 0;
+ do_emit_constant(context, text, make_small(erts_backtrace_depth));
+ DMC_PUSH(*text, matchCurrentStacktrace);
+ break;
+ case 2:
+ *constant = 0;
+
+ if (!is_small(p[2])) {
+ RETURN_ERROR("Special form 'current_stacktrace' called with non "
+ "small argument.", context, *constant);
+ }
+
+ depth = signed_val(p[2]);
+
+ if (depth < 0) {
+ RETURN_ERROR("Special form 'current_stacktrace' called with "
+ "negative integer argument.", context, *constant);
+ }
+
+ if (depth > erts_backtrace_depth) {
+ p[2] = make_small(erts_backtrace_depth);
+ }
+
+ do_emit_constant(context, text, p[2]);
+ DMC_PUSH(*text, matchCurrentStacktrace);
+ break;
+ default:
+ RETURN_TERM_ERROR("Special form 'current_stacktrace' called with wrong "
+ "number of arguments in %T.", t, context,
+ *constant);
+ }
+ return retOk;
+}
+
static DMCRet dmc_silent(DMCContext *context,
DMCHeap *heap,
DMC_STACK_TYPE(UWord) *text,
@@ -5046,6 +5348,8 @@ static DMCRet dmc_fun(DMCContext *context,
return dmc_caller(context, heap, text, t, constant);
case am_caller_line:
return dmc_caller_line(context, heap, text, t, constant);
+ case am_current_stacktrace:
+ return dmc_current_stacktrace(context, heap, text, t, constant);
case am_silent:
return dmc_silent(context, heap, text, t, constant);
case am_set_tcw:
@@ -6129,6 +6433,10 @@ void db_match_dis(Binary *bp)
++t;
erts_printf("CallerLine\n");
break;
+ case matchCurrentStacktrace:
+ ++t;
+ erts_printf("CurrentStacktrace\n");
+ break;
default:
erts_printf("??? (0x%bpx)\n", *t);
++t;
diff --git a/erts/emulator/beam/erl_db_util.h b/erts/emulator/beam/erl_db_util.h
index 8b8851cad8..e77ca4771c 100644
--- a/erts/emulator/beam/erl_db_util.h
+++ b/erts/emulator/beam/erl_db_util.h
@@ -101,6 +101,12 @@ typedef struct {
int current_level;
} catree;
} u;
+ Eterm* old_tpl;
+#ifdef DEBUG
+ Eterm old_tpl_dflt[2];
+#else
+ Eterm old_tpl_dflt[8];
+#endif
} DbUpdateHandle;
/* How safe are we from double-hits or missed objects
@@ -400,10 +406,10 @@ ERTS_GLB_INLINE Eterm db_copy_object_from_ets(DbTableCommon* tb, DbTerm* bp,
Eterm** hpp, ErlOffHeap* off_heap)
{
if (tb->compress) {
- return db_copy_from_comp(tb, bp, hpp, off_heap);
+ return db_copy_from_comp(tb, bp, hpp, off_heap);
}
else {
- return copy_shallow(bp->tpl, bp->size, hpp, off_heap);
+ return make_tuple(copy_shallow(bp->tpl, bp->size, hpp, off_heap));
}
}
diff --git a/erts/emulator/beam/erl_dirty_bif.tab b/erts/emulator/beam/erl_dirty_bif.tab
index 3f16f3e0f3..6e4dbfb30f 100644
--- a/erts/emulator/beam/erl_dirty_bif.tab
+++ b/erts/emulator/beam/erl_dirty_bif.tab
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2016-2020. All Rights Reserved.
+# Copyright Ericsson AB 2016-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@ dirty-io erts_debug:dirty_io/2
dirty-cpu erts_debug:lcnt_control/2
dirty-cpu erts_debug:lcnt_collect/0
dirty-cpu erts_debug:lcnt_clear/0
+dirty-cpu erlang:display_string/2
# --- TEST of Dirty BIF functionality ---
# Functions below will execute on dirty schedulers when emulator has
diff --git a/erts/emulator/beam/erl_drv_thread.c b/erts/emulator/beam/erl_drv_thread.c
index 949d89232a..c8e6b9906b 100644
--- a/erts/emulator/beam/erl_drv_thread.c
+++ b/erts/emulator/beam/erl_drv_thread.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2007-2020. All Rights Reserved.
+ * Copyright Ericsson AB 2007-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -609,6 +609,7 @@ erl_drv_thread_create(char *name,
struct ErlDrvTid_ *dtid;
ethr_thr_opts ethr_opts = ETHR_THR_OPTS_DEFAULT_INITER;
ethr_thr_opts *use_opts;
+ char name_buff[ETHR_THR_NAME_MAX + 1];
if (!opts && !name)
use_opts = NULL;
@@ -616,7 +617,8 @@ erl_drv_thread_create(char *name,
if(opts)
ethr_opts.suggested_stack_size = opts->suggested_stack_size;
- ethr_opts.name = name;
+ erts_snprintf(name_buff, sizeof(name_buff), "%s", name);
+ ethr_opts.name = name_buff;
use_opts = &ethr_opts;
}
diff --git a/erts/emulator/beam/erl_fun.c b/erts/emulator/beam/erl_fun.c
index 17b3e12dd8..ae6116ec19 100644
--- a/erts/emulator/beam/erl_fun.c
+++ b/erts/emulator/beam/erl_fun.c
@@ -29,6 +29,12 @@
#include "hash.h"
#include "beam_common.h"
+#ifdef DEBUG
+# define IF_DEBUG(x) x
+#else
+# define IF_DEBUG(x)
+#endif
+
/* Container structure for fun entries, allowing us to start `ErlFunEntry` with
* a field other than its `HashBucket`. */
typedef struct erl_fun_entry_container {
@@ -78,22 +84,33 @@ void
erts_fun_info(fmtfn_t to, void *to_arg)
{
int lock = !ERTS_IS_CRASH_DUMPING;
- if (lock)
- erts_fun_read_lock();
+
+ if (lock) {
+ erts_fun_read_lock();
+ }
+
hash_info(to, to_arg, &erts_fun_table);
- if (lock)
- erts_fun_read_unlock();
+
+ if (lock) {
+ erts_fun_read_unlock();
+ }
}
int erts_fun_table_sz(void)
{
int sz;
int lock = !ERTS_IS_CRASH_DUMPING;
- if (lock)
- erts_fun_read_lock();
+
+ if (lock) {
+ erts_fun_read_lock();
+ }
+
sz = hash_table_sz(&erts_fun_table);
- if (lock)
- erts_fun_read_unlock();
+
+ if (lock) {
+ erts_fun_read_unlock();
+ }
+
return sz;
}
@@ -130,8 +147,8 @@ erts_put_fun_entry2(Eterm mod, int old_uniq, int old_index,
return &fc->entry;
}
-const ErtsCodeMFA *erts_get_fun_mfa(const ErlFunEntry *fe) {
- ErtsCodePtr address = fe->dispatch.addresses[0];
+const ErtsCodeMFA *erts_get_fun_mfa(const ErlFunEntry *fe, ErtsCodeIndex ix) {
+ ErtsCodePtr address = fe->dispatch.addresses[ix];
if (address != beam_unloaded_fun) {
return erts_find_function_from_pc(address);
@@ -140,16 +157,14 @@ const ErtsCodeMFA *erts_get_fun_mfa(const ErlFunEntry *fe) {
return NULL;
}
-void erts_set_fun_code(ErlFunEntry *fe, ErtsCodePtr address) {
- int i;
-
- for (i = 0; i < ERTS_ADDRESSV_SIZE; i++) {
- fe->dispatch.addresses[i] = address;
- }
+void erts_set_fun_code(ErlFunEntry *fe, ErtsCodeIndex ix, ErtsCodePtr address) {
+ /* Fun entries MUST NOT be updated during a purge! */
+ ASSERT(fe->pend_purge_address == NULL);
+ fe->dispatch.addresses[ix] = address;
}
-int erts_is_fun_loaded(const ErlFunEntry* fe) {
- return fe->dispatch.addresses[0] != beam_unloaded_fun;
+int erts_is_fun_loaded(const ErlFunEntry* fe, ErtsCodeIndex ix) {
+ return fe->dispatch.addresses[ix] != beam_unloaded_fun;
}
static void
@@ -165,38 +180,49 @@ void
erts_erase_fun_entry(ErlFunEntry* fe)
{
erts_fun_write_lock();
- /*
- * We have to check refc again since someone might have looked up
- * the fun entry and incremented refc after last check.
- */
- if (erts_refc_dectest(&fe->refc, -1) <= 0)
- {
- if (erts_is_fun_loaded(fe)) {
+
+ /* We have to check refc again since someone might have looked up
+ * the fun entry and incremented refc after last check. */
+ if (erts_refc_dectest(&fe->refc, -1) <= 0) {
+ ErtsCodeIndex code_ix = erts_active_code_ix();
+
+ if (erts_is_fun_loaded(fe, code_ix)) {
erts_exit(ERTS_ERROR_EXIT,
"Internal error: "
"Invalid reference count found on #Fun<%T.%d.%d>: "
" About to erase fun still referred by code.\n",
fe->module, fe->old_index, fe->old_uniq);
}
+
erts_erase_fun_entry_unlocked(fe);
}
+
erts_fun_write_unlock();
}
+struct fun_prepare_purge_args {
+ struct erl_module_instance* modp;
+ ErtsCodeIndex code_ix;
+};
+
static void fun_purge_foreach(ErlFunEntryContainer *fc,
- struct erl_module_instance* modp)
+ struct fun_prepare_purge_args *args)
{
- const char *fun_addr, *mod_start;
+ struct erl_module_instance* modp = args->modp;
ErlFunEntry *fe = &fc->entry;
+ const char *mod_start;
+ ErtsCodePtr fun_addr;
- fun_addr = (const char*)fe->dispatch.addresses[0];
+ fun_addr = fe->dispatch.addresses[args->code_ix];
mod_start = (const char*)modp->code_hdr;
- if (ErtsInArea(fun_addr, mod_start, modp->code_length)) {
- fe->pend_purge_address = fe->dispatch.addresses[0];
+ if (ErtsInArea((const char*)fun_addr, mod_start, modp->code_length)) {
+ ASSERT(fe->pend_purge_address == NULL);
+
+ fe->pend_purge_address = fun_addr;
ERTS_THR_WRITE_MEMORY_BARRIER;
- erts_set_fun_code(fe, beam_unloaded_fun);
+ fe->dispatch.addresses[args->code_ix] = beam_unloaded_fun;
erts_purge_state_add_fun(fe);
}
@@ -205,46 +231,68 @@ static void fun_purge_foreach(ErlFunEntryContainer *fc,
void
erts_fun_purge_prepare(struct erl_module_instance* modp)
{
- erts_fun_read_lock();
- hash_foreach(&erts_fun_table, (HFOREACH_FUN)fun_purge_foreach, modp);
- erts_fun_read_unlock();
+ struct fun_prepare_purge_args args = {modp, erts_active_code_ix()};
+
+ ERTS_LC_ASSERT(erts_has_code_stage_permission());
+
+ erts_fun_write_lock();
+ hash_foreach(&erts_fun_table, (HFOREACH_FUN)fun_purge_foreach, &args);
+ erts_fun_write_unlock();
}
void
erts_fun_purge_abort_prepare(ErlFunEntry **funs, Uint no)
{
- Uint ix;
+ ErtsCodeIndex code_ix = erts_active_code_ix();
+ Uint fun_ix;
- for (ix = 0; ix < no; ix++) {
- ErlFunEntry *fe = funs[ix];
+ ERTS_LC_ASSERT(erts_has_code_stage_permission());
- if (fe->dispatch.addresses[0] == beam_unloaded_fun) {
- erts_set_fun_code(fe, fe->pend_purge_address);
- }
+ for (fun_ix = 0; fun_ix < no; fun_ix++) {
+ ErlFunEntry *fe = funs[fun_ix];
+
+ ASSERT(fe->dispatch.addresses[code_ix] == beam_unloaded_fun);
+ fe->dispatch.addresses[code_ix] = fe->pend_purge_address;
}
}
void
erts_fun_purge_abort_finalize(ErlFunEntry **funs, Uint no)
{
- Uint ix;
+ IF_DEBUG(ErtsCodeIndex code_ix = erts_active_code_ix();)
+ Uint fun_ix;
- for (ix = 0; ix < no; ix++) {
- funs[ix]->pend_purge_address = NULL;
+ ERTS_LC_ASSERT(erts_has_code_stage_permission());
+
+ for (fun_ix = 0; fun_ix < no; fun_ix++) {
+ ErlFunEntry *fe = funs[fun_ix];
+
+ /* The abort_prepare step should have set the active address to the
+ * actual one. */
+ ASSERT(fe->dispatch.addresses[code_ix] != beam_unloaded_fun);
+ fe->pend_purge_address = NULL;
}
}
void
erts_fun_purge_complete(ErlFunEntry **funs, Uint no)
{
+ IF_DEBUG(ErtsCodeIndex code_ix = erts_active_code_ix();)
Uint ix;
+ ERTS_LC_ASSERT(erts_has_code_stage_permission());
+
for (ix = 0; ix < no; ix++) {
- ErlFunEntry *fe = funs[ix];
- fe->pend_purge_address = NULL;
- if (erts_refc_dectest(&fe->refc, 0) == 0)
- erts_erase_fun_entry(fe);
+ ErlFunEntry *fe = funs[ix];
+
+ ASSERT(fe->dispatch.addresses[code_ix] == beam_unloaded_fun);
+ fe->pend_purge_address = NULL;
+
+ if (erts_refc_dectest(&fe->refc, 0) == 0) {
+ erts_erase_fun_entry(fe);
+ }
}
+
ERTS_THR_WRITE_MEMORY_BARRIER;
}
@@ -292,14 +340,11 @@ ErlFunThing *erts_new_local_fun_thing(Process *p, ErlFunEntry *fe,
#ifdef DEBUG
{
- /* FIXME: This assertion can fail because it may point to new code that
- * has not been committed yet. This is an actual bug but the fix is too
- * too involved and risky to release in a patch.
- *
- * As this problem has existed since the introduction of funs and is
- * very unlikely to cause actual issues in the wild, we've decided to
- * postpone the fix until OTP 26. See OTP-18016 for details. */
- const ErtsCodeMFA *mfa = erts_get_fun_mfa(fe);
+ /* Note that `mfa` may be NULL if the fun is currently being purged. We
+ * ignore this since it's not an error and we only need `mfa` to
+ * sanity-check the arity at this point. If the fun is called while in
+ * this state, the `error_handler` module will take care of it. */
+ const ErtsCodeMFA *mfa = erts_get_fun_mfa(fe, erts_active_code_ix());
ASSERT(!mfa || funp->arity == mfa->arity - num_free);
ASSERT(arity == fe->arity);
}
@@ -312,6 +357,7 @@ ErlFunThing *erts_new_local_fun_thing(Process *p, ErlFunEntry *fe,
struct dump_fun_foreach_args {
fmtfn_t to;
void *to_arg;
+ ErtsCodeIndex code_ix;
};
static void
@@ -323,21 +369,27 @@ dump_fun_foreach(ErlFunEntryContainer *fc, struct dump_fun_foreach_args *args)
erts_print(args->to, args->to_arg, "Module: %T\n", fe->module);
erts_print(args->to, args->to_arg, "Uniq: %d\n", fe->old_uniq);
erts_print(args->to, args->to_arg, "Index: %d\n",fe->old_index);
- erts_print(args->to, args->to_arg, "Address: %p\n", fe->dispatch.addresses[0]);
- erts_print(args->to, args->to_arg, "Refc: %ld\n", erts_refc_read(&fe->refc, 1));
+ erts_print(args->to, args->to_arg, "Address: %p\n",
+ fe->dispatch.addresses[args->code_ix]);
+ erts_print(args->to, args->to_arg, "Refc: %ld\n",
+ erts_refc_read(&fe->refc, 1));
}
void
erts_dump_fun_entries(fmtfn_t to, void *to_arg)
{
- struct dump_fun_foreach_args args = {to, to_arg};
+ struct dump_fun_foreach_args args = {to, to_arg, erts_active_code_ix()};
int lock = !ERTS_IS_CRASH_DUMPING;
- if (lock)
- erts_fun_read_lock();
+ if (lock) {
+ erts_fun_read_lock();
+ }
+
hash_foreach(&erts_fun_table, (HFOREACH_FUN)dump_fun_foreach, &args);
- if (lock)
- erts_fun_read_unlock();
+
+ if (lock) {
+ erts_fun_read_unlock();
+ }
}
static HashValue
@@ -365,7 +417,9 @@ fun_cmp(ErlFunEntryContainer* obj1, ErlFunEntryContainer* obj2)
static ErlFunEntryContainer*
fun_alloc(ErlFunEntryContainer* template)
{
- ErlFunEntryContainer* obj;
+ ErlFunEntryContainer *obj;
+ ErtsDispatchable *disp;
+ ErtsCodeIndex ix;
obj = (ErlFunEntryContainer *) erts_alloc(ERTS_ALC_T_FUN_ENTRY,
sizeof(ErlFunEntryContainer));
@@ -374,7 +428,14 @@ fun_alloc(ErlFunEntryContainer* template)
erts_refc_init(&obj->entry.refc, -1);
- erts_set_fun_code(&obj->entry, beam_unloaded_fun);
+ disp = &obj->entry.dispatch;
+ for (ix = 0; ix < ERTS_NUM_CODE_IX; ix++) {
+ disp->addresses[ix] = beam_unloaded_fun;
+ }
+
+#ifdef BEAMASM
+ disp->addresses[ERTS_SAVE_CALLS_CODE_IX] = beam_save_calls_fun;
+#endif
obj->entry.pend_purge_address = NULL;
@@ -386,3 +447,46 @@ fun_free(ErlFunEntryContainer* obj)
{
erts_free(ERTS_ALC_T_FUN_ENTRY, (void *) obj);
}
+
+struct fun_stage_args {
+ ErtsCodeIndex src_ix;
+ ErtsCodeIndex dst_ix;
+};
+
+static void fun_stage_foreach(ErlFunEntryContainer *fc,
+ struct fun_stage_args *args)
+{
+ ErtsDispatchable *disp = &fc->entry.dispatch;
+
+ /* Fun entries MUST NOT be updated during a purge! */
+ ASSERT(fc->entry.pend_purge_address == NULL);
+
+ disp->addresses[args->dst_ix] = disp->addresses[args->src_ix];
+}
+
+IF_DEBUG(static ErtsCodeIndex debug_fun_load_ix = 0;)
+
+void erts_fun_start_staging(void)
+{
+ ErtsCodeIndex dst_ix = erts_staging_code_ix();
+ ErtsCodeIndex src_ix = erts_active_code_ix();
+ struct fun_stage_args args = {src_ix, dst_ix};
+
+ ERTS_LC_ASSERT(erts_has_code_stage_permission());
+ ASSERT(dst_ix != src_ix);
+ ASSERT(debug_fun_load_ix == ~0);
+
+ erts_fun_write_lock();
+ hash_foreach(&erts_fun_table, (HFOREACH_FUN)fun_stage_foreach, &args);
+ erts_fun_write_unlock();
+
+ IF_DEBUG(debug_fun_load_ix = dst_ix);
+}
+
+void erts_fun_end_staging(int commit)
+{
+ ERTS_LC_ASSERT((erts_active_code_ix() == erts_active_code_ix()) ||
+ erts_has_code_stage_permission());
+ ASSERT(debug_fun_load_ix == erts_staging_code_ix());
+ IF_DEBUG(debug_fun_load_ix = ~0);
+}
diff --git a/erts/emulator/beam/erl_fun.h b/erts/emulator/beam/erl_fun.h
index 4276e9a999..a67afaceeb 100644
--- a/erts/emulator/beam/erl_fun.h
+++ b/erts/emulator/beam/erl_fun.h
@@ -74,14 +74,15 @@ typedef struct erl_fun_thing {
/* -- The following may be compound Erlang terms ---------------------- */
Eterm creator; /* Pid of creator process (contains node). */
- Eterm env[1]; /* Environment (free variables). */
+ Eterm env[]; /* Environment (free variables). */
} ErlFunThing;
#define is_local_fun(FunThing) ((FunThing)->creator != am_external)
#define is_external_fun(FunThing) ((FunThing)->creator == am_external)
-/* ERL_FUN_SIZE does _not_ include space for the environment */
-#define ERL_FUN_SIZE ((sizeof(ErlFunThing)/sizeof(Eterm))-1)
+/* ERL_FUN_SIZE does _not_ include space for the environment which is a
+ * C99-style flexible array */
+#define ERL_FUN_SIZE ((sizeof(ErlFunThing)/sizeof(Eterm)))
ErlFunThing *erts_new_export_fun_thing(Eterm **hpp, Export *exp, int arity);
ErlFunThing *erts_new_local_fun_thing(Process *p,
@@ -97,14 +98,14 @@ int erts_fun_table_sz(void);
ErlFunEntry* erts_put_fun_entry2(Eterm mod, int old_uniq, int old_index,
const byte* uniq, int index, int arity);
-const ErtsCodeMFA *erts_get_fun_mfa(const ErlFunEntry *fe);
+const ErtsCodeMFA *erts_get_fun_mfa(const ErlFunEntry *fe, ErtsCodeIndex ix);
-void erts_set_fun_code(ErlFunEntry *fe, ErtsCodePtr address);
+void erts_set_fun_code(ErlFunEntry *fe, ErtsCodeIndex ix, ErtsCodePtr address);
ERTS_GLB_INLINE
ErtsCodePtr erts_get_fun_code(ErlFunEntry *fe, ErtsCodeIndex ix);
-int erts_is_fun_loaded(const ErlFunEntry* fe);
+int erts_is_fun_loaded(const ErlFunEntry* fe, ErtsCodeIndex ix);
void erts_erase_fun_entry(ErlFunEntry* fe);
void erts_cleanup_funs(ErlFunThing* funp);
@@ -116,6 +117,10 @@ void erts_fun_purge_abort_finalize(ErlFunEntry **funs, Uint no);
void erts_fun_purge_complete(ErlFunEntry **funs, Uint no);
void erts_dump_fun_entries(fmtfn_t, void *);
+
+void erts_fun_start_staging(void);
+void erts_fun_end_staging(int commit);
+
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
ERTS_GLB_INLINE
diff --git a/erts/emulator/beam/erl_gc.c b/erts/emulator/beam/erl_gc.c
index 2bb41685bd..4b41d87ad5 100644
--- a/erts/emulator/beam/erl_gc.c
+++ b/erts/emulator/beam/erl_gc.c
@@ -41,6 +41,7 @@
#include "erl_nfunc_sched.h"
#include "erl_proc_sig_queue.h"
#include "beam_common.h"
+#include "beam_bp.h"
#define ERTS_INACT_WR_PB_LEAVE_MUCH_LIMIT 1
#define ERTS_INACT_WR_PB_LEAVE_MUCH_PERCENTAGE 20
@@ -158,6 +159,7 @@ static void offset_rootset(Process *p, Sint heap_offs, Sint stack_offs,
char* area, Uint area_sz, Eterm* objv, int nobj);
static void offset_off_heap(Process* p, Sint offs, char* area, Uint area_sz);
static void offset_mqueue(Process *p, Sint offs, char* area, Uint area_sz);
+static int has_reached_max_heap_size(Process *p, Uint total_heap_size);
static int reached_max_heap_size(Process *p, Uint total_heap_size,
Uint extra_heap_size, Uint extra_old_heap_size);
static void init_gc_info(ErtsGCInfo *gcip);
@@ -557,6 +559,7 @@ delay_garbage_collection(Process *p, ErlHeapFragment *live_hf_end, int need, int
}
}
p->abandoned_heap = orig_heap;
+ erts_adjust_memory_break(p, orig_htop - p->high_water);
}
#ifdef CHECK_FOR_HOLES
@@ -742,6 +745,12 @@ garbage_collect(Process* p, ErlHeapFragment *live_hf_end,
dtrace_proc_str(p, pidbuf);
}
#endif
+
+ if (p->abandoned_heap)
+ erts_adjust_memory_break(p, p->htop - p->heap + p->mbuf_sz);
+ else
+ erts_adjust_memory_break(p, p->htop - p->high_water + p->mbuf_sz);
+
/*
* Test which type of GC to do.
*/
@@ -1161,7 +1170,7 @@ erts_garbage_collect_literals(Process* p, Eterm* literals,
new_heap_size = HEAP_END(p) - HEAP_START(p);
old_heap_size = erts_next_heap_size(lit_size, 0);
total_heap_size = new_heap_size + old_heap_size;
- if (MAX_HEAP_SIZE_GET(p) < total_heap_size &&
+ if (has_reached_max_heap_size(p, total_heap_size) &&
reached_max_heap_size(p, total_heap_size,
new_heap_size, old_heap_size)) {
erts_set_self_exiting(p, am_killed);
@@ -1395,7 +1404,7 @@ minor_collection(Process* p, ErlHeapFragment *live_hf_end,
extra_heap_size = next_heap_size(p, stack_size + MAX(size_before,need), 0);
heap_size += extra_heap_size;
- if (heap_size > MAX_HEAP_SIZE_GET(p))
+ if (has_reached_max_heap_size(p, heap_size))
if (reached_max_heap_size(p, heap_size, extra_heap_size, extra_old_heap_size))
return -2;
}
@@ -1429,7 +1438,7 @@ minor_collection(Process* p, ErlHeapFragment *live_hf_end,
if (OLD_HEAP(p) &&
((mature_size <= OLD_HEND(p) - OLD_HTOP(p)) &&
- ((BIN_OLD_VHEAP_SZ(p) > BIN_OLD_VHEAP(p))) ) ) {
+ ((p->bin_old_vheap_sz > p->bin_old_vheap)) ) ) {
Eterm *prev_old_htop;
Uint stack_size, size_after, adjust_size, need_after, new_sz, new_mature;
@@ -1843,7 +1852,7 @@ major_collection(Process* p, ErlHeapFragment *live_hf_end,
/* Add size of new young heap */
heap_size += new_sz;
- if (MAX_HEAP_SIZE_GET(p) < heap_size)
+ if (has_reached_max_heap_size(p, heap_size))
if (reached_max_heap_size(p, heap_size, new_sz, 0))
return -2;
}
@@ -2947,7 +2956,7 @@ sweep_off_heap(Process *p, int fullsweep)
Uint oheap_sz = 0;
Uint64 bin_vheap = 0;
#ifdef DEBUG
- Uint64 orig_bin_old_vheap = BIN_OLD_VHEAP(p);
+ Uint64 orig_bin_old_vheap = p->bin_old_vheap;
int seen_mature = 0;
#endif
Uint shrink_ncandidates;
@@ -2983,7 +2992,7 @@ sweep_off_heap(Process *p, int fullsweep)
if (to_new_heap) {
bin_vheap += ptr->size / sizeof(Eterm);
} else {
- BIN_OLD_VHEAP(p) += ptr->size / sizeof(Eterm);
+ p->bin_old_vheap += ptr->size / sizeof(Eterm);
}
ASSERT(!(((ProcBin*)ptr)->flags & (PB_ACTIVE_WRITER|PB_IS_WRITABLE)));
break;
@@ -2997,7 +3006,7 @@ sweep_off_heap(Process *p, int fullsweep)
if (to_new_heap)
bin_vheap += size / sizeof(Eterm);
else
- BIN_OLD_VHEAP(p) += size / sizeof(Eterm); /* for binary gc (words)*/
+ p->bin_old_vheap += size / sizeof(Eterm); /* for binary gc (words)*/
/* fall through... */
}
default:
@@ -3063,7 +3072,7 @@ sweep_off_heap(Process *p, int fullsweep)
#ifdef DEBUG
if (fullsweep) {
ASSERT(ptr == NULL);
- ASSERT(BIN_OLD_VHEAP(p) == orig_bin_old_vheap);
+ ASSERT(p->bin_old_vheap == orig_bin_old_vheap);
}
else {
/* The rest of the list resides on the old heap and needs no
@@ -3111,7 +3120,7 @@ sweep_off_heap(Process *p, int fullsweep)
if (!on_old_heap) {
bin_vheap += pb->size / sizeof(Eterm);
} else {
- BIN_OLD_VHEAP(p) += pb->size / sizeof(Eterm);
+ p->bin_old_vheap += pb->size / sizeof(Eterm);
}
}
else {
@@ -3209,11 +3218,12 @@ sweep_off_heap(Process *p, int fullsweep)
}
if (fullsweep) {
- ASSERT(BIN_OLD_VHEAP(p) == orig_bin_old_vheap);
- BIN_OLD_VHEAP(p) = 0;
- BIN_OLD_VHEAP_SZ(p) = next_vheap_size(p, MSO(p).overhead, BIN_OLD_VHEAP_SZ(p));
+ ASSERT(p->bin_old_vheap == orig_bin_old_vheap);
+ p->bin_old_vheap = 0;
+ p->bin_old_vheap_sz = next_vheap_size(p, MSO(p).overhead,
+ p->bin_old_vheap_sz);
}
- BIN_VHEAP_SZ(p) = next_vheap_size(p, bin_vheap, BIN_VHEAP_SZ(p));
+ p->bin_vheap_sz = next_vheap_size(p, bin_vheap, p->bin_vheap_sz);
MSO(p).overhead = bin_vheap;
}
@@ -3641,9 +3651,9 @@ erts_process_gc_info(Process *p, Uint *sizep, Eterm **hpp,
OLD_HEAP(p) ? OLD_HTOP(p) - OLD_HEAP(p) : 0,
HEAP_TOP(p) - HEAP_START(p),
MSO(p).overhead,
- BIN_VHEAP_SZ(p),
- BIN_OLD_VHEAP(p),
- BIN_OLD_VHEAP_SZ(p)
+ p->bin_vheap_sz,
+ p->bin_old_vheap,
+ p->bin_old_vheap_sz
};
Eterm res = THE_NON_VALUE;
@@ -3685,6 +3695,16 @@ erts_process_gc_info(Process *p, Uint *sizep, Eterm **hpp,
return res;
}
+static int has_reached_max_heap_size(Process *p, Uint total_heap_size)
+{
+ Uint used = total_heap_size;
+
+ if (MAX_HEAP_SIZE_FLAGS_GET(p) & MAX_HEAP_SIZE_INCLUDE_OH_BINS) {
+ used += p->bin_old_vheap + p->off_heap.overhead;
+ }
+ return (used > MAX_HEAP_SIZE_GET(p));
+}
+
static int
reached_max_heap_size(Process *p, Uint total_heap_size,
Uint extra_heap_size, Uint extra_old_heap_size)
@@ -3745,28 +3765,21 @@ reached_max_heap_size(Process *p, Uint total_heap_size,
}
Eterm
-erts_max_heap_size_map(Sint max_heap_size, Uint max_heap_flags,
- Eterm **hpp, Uint *sz)
+erts_max_heap_size_map(ErtsHeapFactory *factory,
+ Sint max_heap_size, Uint max_heap_flags)
{
- if (!hpp) {
- *sz += ERTS_MAX_HEAP_SIZE_MAP_SZ;
- return THE_NON_VALUE;
- } else {
- Eterm *hp = *hpp;
- Eterm keys = TUPLE3(hp, am_error_logger, am_kill, am_size);
- flatmap_t *mp;
- hp += 4;
- mp = (flatmap_t*) hp;
- mp->thing_word = MAP_HEADER_FLATMAP;
- mp->size = 3;
- mp->keys = keys;
- hp += MAP_HEADER_FLATMAP_SZ;
- *hp++ = max_heap_flags & MAX_HEAP_SIZE_LOG ? am_true : am_false;
- *hp++ = max_heap_flags & MAX_HEAP_SIZE_KILL ? am_true : am_false;
- *hp++ = make_small(max_heap_size);
- *hpp = hp;
- return make_flatmap(mp);
- }
+ Eterm keys[] = {
+ am_error_logger, am_include_shared_binaries, am_kill, am_size
+ };
+ Eterm values[] = {
+ max_heap_flags & MAX_HEAP_SIZE_LOG ? am_true : am_false,
+ max_heap_flags & MAX_HEAP_SIZE_INCLUDE_OH_BINS ? am_true : am_false,
+ max_heap_flags & MAX_HEAP_SIZE_KILL ? am_true : am_false,
+ make_small(max_heap_size)
+ };
+ ERTS_CT_ASSERT(sizeof(keys) == sizeof(values));
+ return erts_map_from_ks_and_vs(factory, keys, values,
+ sizeof(keys) / sizeof(keys[0]));
}
int
@@ -3781,6 +3794,7 @@ erts_max_heap_size(Eterm arg, Uint *max_heap_size, Uint *max_heap_flags)
const Eterm *size = erts_maps_get(am_size, arg);
const Eterm *kill = erts_maps_get(am_kill, arg);
const Eterm *log = erts_maps_get(am_error_logger, arg);
+ const Eterm *incl_bins = erts_maps_get(am_include_shared_binaries, arg);
if (size && is_small(*size)) {
sz = signed_val(*size);
} else {
@@ -3803,6 +3817,14 @@ erts_max_heap_size(Eterm arg, Uint *max_heap_size, Uint *max_heap_flags)
else
return 0;
}
+ if (incl_bins) {
+ if (*incl_bins == am_true)
+ *max_heap_flags |= MAX_HEAP_SIZE_INCLUDE_OH_BINS;
+ else if (*incl_bins == am_false)
+ *max_heap_flags &= ~MAX_HEAP_SIZE_INCLUDE_OH_BINS;
+ else
+ return 0;
+ }
} else
return 0;
if (sz < 0)
@@ -3830,11 +3852,12 @@ void erts_validate_stack(Process *p, Eterm *frame_ptr, Eterm *stack_top) {
/* Skip MFA and tracer. */
ASSERT_MFA((ErtsCodeMFA*)cp_val(scanner[0]));
ASSERT(IS_TRACER_VALID(scanner[1]));
- scanner += 2;
- } else if (BeamIsReturnTimeTrace(p->i)) {
+ scanner += BEAM_RETURN_TRACE_FRAME_SZ;
+ } else if (BeamIsReturnCallAccTrace(p->i)) {
/* Skip prev_info. */
- scanner += 1;
+ scanner += BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ;
}
+ ERTS_CT_ASSERT(BEAM_RETURN_TO_TRACE_FRAME_SZ == 0);
while (next_fp) {
ASSERT(next_fp >= stack_top && next_fp <= stack_bottom);
@@ -3855,10 +3878,10 @@ void erts_validate_stack(Process *p, Eterm *frame_ptr, Eterm *stack_top) {
/* Skip MFA and tracer. */
ASSERT_MFA((ErtsCodeMFA*)cp_val(scanner[2]));
ASSERT(IS_TRACER_VALID(scanner[3]));
- scanner += 2;
- } else if (BeamIsReturnTimeTrace((ErtsCodePtr)scanner[1])) {
+ scanner += BEAM_RETURN_TRACE_FRAME_SZ;
+ } else if (BeamIsReturnCallAccTrace((ErtsCodePtr)scanner[1])) {
/* Skip prev_info. */
- scanner += 1;
+ scanner += BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ;
}
scanner += CP_SIZE;
diff --git a/erts/emulator/beam/erl_gc.h b/erts/emulator/beam/erl_gc.h
index 6b38379192..c1760562c9 100644
--- a/erts/emulator/beam/erl_gc.h
+++ b/erts/emulator/beam/erl_gc.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2007-2023. All Rights Reserved.
+ * Copyright Ericsson AB 2007-2022. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -160,7 +160,7 @@ typedef struct {
Uint64 garbage_cols;
} ErtsGCInfo;
-#define ERTS_MAX_HEAP_SIZE_MAP_SZ (2*3 + 1 + MAP_HEADER_FLATMAP_SZ)
+#define ERTS_MAX_HEAP_SIZE_MAP_SZ (2*4 + 1 + MAP_HEADER_FLATMAP_SZ)
#define ERTS_PROCESS_GC_INFO_MAX_TERMS (11) /* number of elements in process_gc_info*/
#define ERTS_PROCESS_GC_INFO_MAX_SIZE \
@@ -186,7 +186,7 @@ void erts_offset_off_heap(struct erl_off_heap*, Sint, Eterm*, Eterm*);
void erts_offset_heap_ptr(Eterm*, Uint, Sint, Eterm*, Eterm*);
void erts_offset_heap(Eterm*, Uint, Sint, Eterm*, Eterm*);
void erts_free_heap_frags(struct process* p);
-Eterm erts_max_heap_size_map(Sint, Uint, Eterm **, Uint *);
+Eterm erts_max_heap_size_map(ErtsHeapFactory *factory, Sint, Uint);
int erts_max_heap_size(Eterm, Uint *, Uint *);
void erts_deallocate_young_generation(Process *c_p);
void erts_copy_one_frag(Eterm** hpp, ErlOffHeap* off_heap,
diff --git a/erts/emulator/beam/erl_init.c b/erts/emulator/beam/erl_init.c
index 2f2e48d3c0..7df1e6a841 100644
--- a/erts/emulator/beam/erl_init.c
+++ b/erts/emulator/beam/erl_init.c
@@ -275,7 +275,7 @@ static ERTS_INLINE void
set_default_time_adj(int *time_correction_p, ErtsTimeWarpMode *time_warp_mode_p)
{
*time_correction_p = 1;
- *time_warp_mode_p = ERTS_NO_TIME_WARP_MODE;
+ *time_warp_mode_p = ERTS_MULTI_TIME_WARP_MODE;
if (!erts_check_time_adj_support(*time_correction_p,
*time_warp_mode_p)) {
*time_correction_p = 0;
@@ -388,6 +388,7 @@ erl_init(int ncpu,
erl_nif_init();
erts_msacc_init();
beamfile_init();
+ erts_late_init_external();
}
static Eterm
@@ -631,6 +632,7 @@ void erts_usage(void)
H_DEFAULT_MAX_SIZE);
erts_fprintf(stderr, "-hmaxk bool enable or disable kill at max heap size (default true)\n");
erts_fprintf(stderr, "-hmaxel bool enable or disable error_logger report at max heap size (default true)\n");
+ erts_fprintf(stderr, "-hmaxib bool enable or disable including off-heap binaries into max heap size (default false)\n");
erts_fprintf(stderr, "-hpds size set initial process dictionary size (default %d)\n",
erts_pd_initial_size);
erts_fprintf(stderr, "-hmqd val set default message queue data flag for processes;\n");
@@ -652,6 +654,7 @@ void erts_usage(void)
#ifdef BEAMASM
erts_fprintf(stderr, "-JDdump bool enable or disable dumping of generated assembly code for each module loaded\n");
erts_fprintf(stderr, "-JPperf true|false|dump|map|fp|no_fp enable or disable support for perf on Linux\n");
+ erts_fprintf(stderr, "-JMsingle bool enable the use of single-mapped RWX memory for JIT:ed code\n");
erts_fprintf(stderr, "\n");
#endif
@@ -1579,6 +1582,8 @@ erl_start(int argc, char **argv)
* h|max - max_heap_size
* h|maxk - max_heap_kill
* h|maxel - max_heap_error_logger
+ * h|maxib - map_heap_include_shared_binaries
+ *
*
*/
if (has_prefix("mbs", sub_param)) {
@@ -1641,6 +1646,17 @@ erl_start(int argc, char **argv)
erts_usage();
}
VERBOSE(DEBUG_SYSTEM, ("using max heap log %d\n", H_MAX_FLAGS));
+ } else if (has_prefix("maxib", sub_param)) {
+ arg = get_arg(sub_param+5, argv[i+1], &i);
+ if (sys_strcmp(arg,"true") == 0) {
+ H_MAX_FLAGS |= MAX_HEAP_SIZE_INCLUDE_OH_BINS;
+ } else if (sys_strcmp(arg,"false") == 0) {
+ H_MAX_FLAGS &= ~MAX_HEAP_SIZE_INCLUDE_OH_BINS;
+ } else {
+ erts_fprintf(stderr, "bad max heap include bins %s\n", arg);
+ erts_usage();
+ }
+ VERBOSE(DEBUG_SYSTEM, ("using max heap log %d\n", H_MAX_FLAGS));
} else if (has_prefix("max", sub_param)) {
Sint hMaxSize;
char *rest;
@@ -1753,6 +1769,23 @@ erl_start(int argc, char **argv)
#endif
}
break;
+ case 'M':
+ sub_param++;
+ if (has_prefix("single", sub_param)) {
+ arg = get_arg(sub_param+6, argv[i + 1], &i);
+ if (sys_strcmp(arg, "true") == 0) {
+ erts_jit_single_map = 1;
+ } else if (sys_strcmp(arg, "false") == 0) {
+ erts_jit_single_map = 0;
+ } else {
+ erts_fprintf(stderr, "bad +JMsingle support flag %s\n", arg);
+ erts_usage();
+ }
+ } else {
+ erts_fprintf(stderr, "bad +JM sub-option %s\n", arg);
+ erts_usage();
+ }
+ break;
default:
erts_fprintf(stderr, "invalid JIT option %s\n", argv[i]);
break;
@@ -2561,7 +2594,7 @@ __decl_noreturn void erts_thr_fatal_error(int err, const char *what)
static void
-system_cleanup(int flush_async)
+system_cleanup(int flush)
{
/*
* Make sure only one thread exits the runtime system.
@@ -2591,24 +2624,43 @@ system_cleanup(int flush_async)
* (in threaded non smp case).
*/
- if (!flush_async
- || !erts_initialized
- )
+ if (!flush || !erts_initialized)
return;
+ /*
+ * We only flush as a result of calling erts_halt() (which in turn
+ * is called from the erlang:halt() BIF when flushing is enabled);
+ * otherwise, flushing wont work properly. If erts_halt() has
+ * been called, 'erts_halt_code' won't equal INT_MIN...
+ */
+ ASSERT(erts_halt_code != INT_MIN);
+
+ /*
+ * Nif on-halt handlers may have been added after we initiated
+ * a halt. If so, make sure that these late added handlers are
+ * executed as well..
+ */
+ erts_nif_execute_on_halt();
+
#ifdef ERTS_ENABLE_LOCK_CHECK
erts_lc_check_exact(NULL, 0);
#endif
erts_exit_flush_async();
+
+ /*
+ * Wait for all NIF calls with delayed halt functionality
+ * enabled to complete before we continue...
+ */
+ erts_nif_wait_calls();
}
static int erts_exit_code;
static __decl_noreturn void __noreturn
-erts_exit_vv(int n, int flush_async, const char *fmt, va_list args1, va_list args2)
+erts_exit_vv(int n, int flush, const char *fmt, va_list args1, va_list args2)
{
- system_cleanup(flush_async);
+ system_cleanup(flush);
if (fmt != NULL && *fmt != '\0')
erl_error(fmt, args2); /* Print error message. */
@@ -2621,25 +2673,25 @@ erts_exit_vv(int n, int flush_async, const char *fmt, va_list args1, va_list arg
erl_crash_dump_v((char*) NULL, 0, fmt, args1);
}
- erts_exit_epilogue();
+ erts_exit_epilogue(flush);
}
-__decl_noreturn void __noreturn erts_exit_epilogue(void)
+__decl_noreturn void __noreturn erts_exit_epilogue(int flush)
{
int n = erts_exit_code;
sys_tty_reset(n);
if (n == ERTS_INTR_EXIT)
- exit(0);
+ (void) (flush ? exit(0) : _exit(0));
else if (n == ERTS_DUMP_EXIT)
ERTS_EXIT_AFTER_DUMP(1);
else if (n == ERTS_ERROR_EXIT || n == ERTS_ABORT_EXIT)
abort();
- exit(n);
+ (void) (flush ? exit(n) : _exit(n));
}
-/* Exit without flushing async threads */
+/* Exit without flushing */
__decl_noreturn void __noreturn erts_exit(int n, const char *fmt, ...)
{
va_list args1, args2;
@@ -2650,8 +2702,12 @@ __decl_noreturn void __noreturn erts_exit(int n, const char *fmt, ...)
va_end(args1);
}
-/* Exit after flushing async threads */
-__decl_noreturn void __noreturn erts_flush_async_exit(int n, char *fmt, ...)
+/*
+ * Exit after flushing. This is a continuation of erts_halt() and wont
+ * work properly if called by its own without proper initialization
+ * as made in erts_halt().
+ */
+__decl_noreturn void __noreturn erts_flush_exit(int n, char *fmt, ...)
{
va_list args1, args2;
va_start(args1, fmt);
diff --git a/erts/emulator/beam/erl_lock_check.c b/erts/emulator/beam/erl_lock_check.c
index c6f127e5a4..75cfd3408b 100644
--- a/erts/emulator/beam/erl_lock_check.c
+++ b/erts/emulator/beam/erl_lock_check.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2005-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2005-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -95,9 +95,11 @@ static erts_lc_lock_order_t erts_lock_order[] = {
{ "proc_msgq", "pid" },
{ "proc_btm", "pid" },
{ "dist_entry_links", "address" },
+ { "nif_load", NULL },
{ "update_persistent_term_permission", NULL },
{ "persistent_term_delete_permission", NULL },
- { "code_write_permission", NULL },
+ { "code_stage_permission", NULL },
+ { "code_mod_permission", NULL },
{ "purge_state", NULL },
{ "proc_status", "pid" },
{ "proc_trace", "pid" },
@@ -108,6 +110,7 @@ static erts_lc_lock_order_t erts_lock_order[] = {
{ "fun_tab", NULL },
{ "environ", NULL },
{ "release_literal_areas", NULL },
+ { "on_halt", NULL },
{ "drv_ev_state_grow", NULL, },
{ "drv_ev_state", "address" },
{ "safe_hash", "address" },
@@ -144,7 +147,6 @@ static erts_lc_lock_order_t erts_lock_order[] = {
{ "instr_x", NULL },
{ "instr", NULL },
{ "dyn_lock_check", NULL },
- { "nif_load", NULL },
{ "alcu_allocator", "index" },
{ "mseg", NULL },
{ "get_time", NULL },
diff --git a/erts/emulator/beam/erl_lock_count.h b/erts/emulator/beam/erl_lock_count.h
index 02ab64ec95..a42fde3546 100644
--- a/erts/emulator/beam/erl_lock_count.h
+++ b/erts/emulator/beam/erl_lock_count.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -261,12 +261,14 @@ int erts_lcnt_check_ref_installed(erts_lcnt_ref_t *ref);
/** @brief Convenience macro to re/enable counting on an already initialized
* reference. Don't forget to specify the lock type in \c flags! */
-#define erts_lcnt_install_new_lock_info(ref, name, id, flags) \
- if(!erts_lcnt_check_ref_installed(ref)) { \
- erts_lcnt_lock_info_carrier_t *__carrier; \
- __carrier = erts_lcnt_create_lock_info_carrier(1);\
- erts_lcnt_init_lock_info_idx(__carrier, 0, name, id, flags); \
- erts_lcnt_install(ref, __carrier);\
+#define erts_lcnt_install_new_lock_info(ref, name, id, flags) \
+ do { \
+ if(!erts_lcnt_check_ref_installed(ref)) { \
+ erts_lcnt_lock_info_carrier_t *__carrier; \
+ __carrier = erts_lcnt_create_lock_info_carrier(1); \
+ erts_lcnt_init_lock_info_idx(__carrier, 0, name, id, flags); \
+ erts_lcnt_install(ref, __carrier); \
+ } \
} while(0)
erts_lcnt_lock_info_carrier_t *erts_lcnt_create_lock_info_carrier(int count);
diff --git a/erts/emulator/beam/erl_map.c b/erts/emulator/beam/erl_map.c
index 8a336765f9..337567406b 100644
--- a/erts/emulator/beam/erl_map.c
+++ b/erts/emulator/beam/erl_map.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2014-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2014-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -151,6 +151,9 @@ void erts_init_map(void) {
BIF_RETTYPE map_size_1(BIF_ALIST_1) {
Sint size = erts_map_size(BIF_ARG_1);
+
+ /* NOTE: The JIT has its own implementation of this BIF. */
+
if (size < 0) {
BIF_P->fvalue = BIF_ARG_1;
BIF_ERROR(BIF_P, BADMAP);
@@ -267,6 +270,7 @@ BIF_RETTYPE maps_get_2(BIF_ALIST_2) {
}
BIF_RETTYPE map_get_2(BIF_ALIST_2) {
+ /* NOTE: The JIT has its own implementation of this BIF. */
BIF_RET(maps_get_2(BIF_CALL_ARGS));
}
@@ -433,7 +437,9 @@ static Eterm flatmap_from_validated_list(Process *p, Eterm list, Eterm fill_valu
idx = size;
- while(idx > 0 && (c = CMP_TERM(key,ks[idx-1])) < 0) { idx--; }
+ while(idx > 0 && (c = erts_cmp_flatmap_keys(key,ks[idx-1])) < 0) {
+ idx--;
+ }
if (c == 0) {
/* last compare was equal,
@@ -700,15 +706,10 @@ from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs,
sys_memcpy((void *) hp, (void *) vs, n * sizeof(Eterm));
- if (fmpp) {
- *fmpp = fmp;
- return THE_NON_VALUE;
- }
- return make_flatmap(fmp);
+ *fmpp = fmp;
+ return THE_NON_VALUE;
} else {
- if (fmpp) {
- *fmpp = NULL;
- }
+ *fmpp = NULL;
return erts_hashmap_from_ks_and_vs(factory, ks, vs, n);
}
}
@@ -730,21 +731,6 @@ Eterm erts_map_from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs, Ui
return res;
}
-Eterm erts_map_from_sorted_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs,
- Uint n)
-{
-#ifdef DEBUG
- Uint i;
- /* verify that key array contains unique and sorted keys... */
- for (i = 1; i < n; i++) {
- ASSERT(CMP_TERM(ks[i-1], ks[i]) < 0);
- }
-#endif
-
- return from_ks_and_vs(factory, ks, vs, n, NULL);
-}
-
-
Eterm erts_hashmap_from_ks_and_vs_extra(ErtsHeapFactory *factory,
Eterm *ks, Eterm *vs, Uint n,
Eterm key, Eterm value) {
@@ -801,14 +787,7 @@ static Eterm hashmap_from_unsorted_array(ErtsHeapFactory* factory,
Uint cx;
Eterm res;
- if (n == 0) {
- Eterm *hp;
- hp = erts_produce_heap(factory, 2, 0);
- hp[0] = MAP_HEADER_HAMT_HEAD_BITMAP(0);
- hp[1] = 0;
-
- return make_hashmap(hp);
- }
+ ASSERT(n > 0);
/* sort and compact array (remove non-unique entries) */
erts_qsort(hxns, n, sizeof(hxnode_t), hxnodecmp);
@@ -1225,6 +1204,7 @@ BIF_RETTYPE maps_is_key_2(BIF_ALIST_2) {
}
BIF_RETTYPE is_map_key_2(BIF_ALIST_2) {
+ /* NOTE: The JIT has its own implementation of this BIF. */
BIF_RET(maps_is_key_2(BIF_CALL_ARGS));
}
@@ -1316,34 +1296,31 @@ BIF_RETTYPE maps_merge_2(BIF_ALIST_2) {
BIF_ERROR(BIF_P, BADMAP);
}
-static Eterm flatmap_merge(Process *p, Eterm nodeA, Eterm nodeB) {
+static Eterm flatmap_merge(Process *p, Eterm map1, Eterm map2) {
Eterm *hp,*thp;
- Eterm tup;
Eterm *ks,*vs,*ks1,*vs1,*ks2,*vs2;
flatmap_t *mp1,*mp2,*mp_new;
Uint n,n1,n2,i1,i2,need,unused_size=0;
Sint c = 0;
- mp1 = (flatmap_t*)flatmap_val(nodeA);
- mp2 = (flatmap_t*)flatmap_val(nodeB);
+ mp1 = (flatmap_t*)flatmap_val(map1);
+ mp2 = (flatmap_t*)flatmap_val(map2);
n1 = flatmap_get_size(mp1);
n2 = flatmap_get_size(mp2);
- if (n1 == 0) return nodeB;
- if (n2 == 0) return nodeA;
+ if (n1 == 0) return map2;
+ if (n2 == 0) return map1;
need = MAP_HEADER_FLATMAP_SZ + 1 + 2 * (n1 + n2);
hp = HAlloc(p, need);
- thp = hp;
- tup = make_tuple(thp);
- ks = hp + 1; hp += 1 + n1 + n2;
mp_new = (flatmap_t*)hp; hp += MAP_HEADER_FLATMAP_SZ;
vs = hp; hp += n1 + n2;
+ thp = hp;
+ ks = hp + 1; hp += 1 + n1 + n2;
mp_new->thing_word = MAP_HEADER_FLATMAP;
- mp_new->size = 0;
- mp_new->keys = tup;
+ mp_new->keys = make_tuple(thp);
i1 = 0; i2 = 0;
ks1 = flatmap_get_keys(mp1);
@@ -1352,7 +1329,7 @@ static Eterm flatmap_merge(Process *p, Eterm nodeA, Eterm nodeB) {
vs2 = flatmap_get_values(mp2);
while(i1 < n1 && i2 < n2) {
- c = CMP_TERM(ks1[i1],ks2[i2]);
+ c = (ks1[i1] == ks2[i2]) ? 0 : erts_cmp_flatmap_keys(ks1[i1],ks2[i2]);
if (c == 0) {
/* use righthand side arguments map value,
* but advance both maps */
@@ -1383,20 +1360,42 @@ static Eterm flatmap_merge(Process *p, Eterm nodeA, Eterm nodeB) {
i2++;
}
- if (unused_size) {
- /* the key tuple is embedded in the heap, write a bignum to clear it.
- *
- * release values as normal since they are on the top of the heap
- * size = n1 + n1 - unused_size
- */
-
- *ks = make_pos_bignum_header(unused_size - 1);
- HRelease(p, vs + unused_size, vs);
- }
-
n = n1 + n2 - unused_size;
- *thp = make_arityval(n);
mp_new->size = n;
+ *thp = make_arityval(n);
+
+ if (unused_size ) {
+ Eterm* hp_release;
+
+ if (n == n2) {
+ /* Reuse entire map2 */
+ if (n == n1
+ && erts_is_literal(mp1->keys, boxed_val(mp1->keys))
+ && !erts_is_literal(mp2->keys, boxed_val(mp2->keys))) {
+ /*
+ * We want map2, but map1 has a nice literal key tuple.
+ * Solution: MUTATE HEAP to get both.
+ */
+ ASSERT(eq(mp1->keys, mp2->keys));
+ mp2->keys = mp1->keys;
+ }
+ HRelease(p, hp, (Eterm *)mp_new);
+ return map2;
+ }
+ else if (n == n1) {
+ /* Reuse key tuple of map1 */
+ mp_new->keys = mp1->keys;
+ /* Release key tuple and unused values */
+ hp_release = thp - unused_size;
+ }
+ else {
+ /* Unused values are embedded in the heap, write bignum to clear them */
+ *vs = make_pos_bignum_header(unused_size - 1);
+ /* Release unused keys */
+ hp_release = ks;
+ }
+ HRelease(p, hp, hp_release);
+ }
/* Reshape map to a hashmap if the map exceeds the limit */
@@ -2201,7 +2200,7 @@ Eterm erts_maps_put(Process *p, Eterm key, Eterm value, Eterm map) {
ASSERT(n >= 0);
/* copy map in order */
- while (n && ((c = CMP_TERM(*ks, key)) < 0)) {
+ while (n && ((c = erts_cmp_flatmap_keys(*ks, key)) < 0)) {
*shp++ = *ks++;
*hp++ = *vs++;
n--;
@@ -3200,7 +3199,7 @@ int erts_validate_and_sort_flatmap(flatmap_t* mp)
for (ix = 1; ix < sz; ix++) {
jx = ix;
- while( jx > 0 && (c = CMP_TERM(ks[jx],ks[jx-1])) <= 0 ) {
+ while( jx > 0 && (c = erts_cmp_flatmap_keys(ks[jx],ks[jx-1])) <= 0 ) {
/* identical key -> error */
if (c == 0) return 0;
@@ -3231,7 +3230,7 @@ void erts_usort_flatmap(flatmap_t* mp)
for (ix = 1; ix < sz; ix++) {
jx = ix;
- while( jx > 0 && (c = CMP_TERM(ks[jx],ks[jx-1])) <= 0 ) {
+ while( jx > 0 && (c = erts_cmp_flatmap_keys(ks[jx],ks[jx-1])) <= 0 ) {
/* identical key -> remove it */
if (c == 0) {
sys_memmove(ks+jx-1,ks+jx,(sz-ix)*sizeof(Eterm));
@@ -3656,6 +3655,60 @@ BIF_RETTYPE erts_internal_map_next_3(BIF_ALIST_3) {
BIF_ERROR(BIF_P, BADARG);
}
+ /* Handle an ordered iterator. */
+ if (type == iterator && (is_list(path) || is_nil(path))) {
+#ifdef DEBUG
+#define ORDERED_ITER_FACTOR 200
+#else
+#define ORDERED_ITER_FACTOR 32
+#endif
+ int orig_elems = MAX(1, ERTS_BIF_REDS_LEFT(BIF_P) / ORDERED_ITER_FACTOR);
+ int elems = orig_elems;
+ Uint needed = 4 * elems + 2;
+ Eterm *hp = HAlloc(BIF_P, needed);
+ Eterm *hp_end = hp + needed;
+ Eterm result = am_none;
+ Eterm *patch_ptr = &result;
+
+ while (is_list(path) && elems > 0) {
+ Eterm *lst = list_val(path);
+ Eterm key = CAR(lst);
+ Eterm res = make_tuple(hp);
+ const Eterm *value = erts_maps_get(key, map);
+ if (!value) {
+ ordered_badarg:
+ HRelease(BIF_P, hp_end, hp);
+ BIF_ERROR(BIF_P, BADARG);
+ }
+ hp[0] = make_arityval(3);
+ hp[1] = key;
+ hp[2] = *value;
+ *patch_ptr = res;
+ patch_ptr = &hp[3];
+ hp += 4;
+ path = CDR(lst);
+ elems--;
+ }
+
+ if (is_list(path)) {
+ Eterm next = CONS(hp, path, map);
+ hp += 2;
+ ASSERT(hp == hp_end);
+ *patch_ptr = next;
+ BUMP_ALL_REDS(BIF_P);
+ ASSERT(is_tuple(result));
+ BIF_RET(result);
+ } else if (is_nil(path)) {
+ HRelease(BIF_P, hp_end, hp);
+ *patch_ptr = am_none;
+ BUMP_REDS(BIF_P, ORDERED_ITER_FACTOR * (orig_elems - elems));
+ ASSERT(result == am_none || is_tuple(result));
+ BIF_RET(result);
+ } else {
+ goto ordered_badarg;
+ }
+ }
+
if (is_flatmap(map)) {
Uint n;
Eterm *ks,*vs, res, *hp;
diff --git a/erts/emulator/beam/erl_map.h b/erts/emulator/beam/erl_map.h
index a8f9271e99..05d09557e7 100644
--- a/erts/emulator/beam/erl_map.h
+++ b/erts/emulator/beam/erl_map.h
@@ -104,8 +104,6 @@ Eterm erts_hashmap_from_array(ErtsHeapFactory*, Eterm *leafs, Uint n, int rejec
erts_hashmap_from_ks_and_vs_extra((F), (KS), (VS), (N), THE_NON_VALUE, THE_NON_VALUE);
Eterm erts_map_from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs, Uint n);
-Eterm erts_map_from_sorted_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks0, Eterm *vs0,
- Uint n);
Eterm erts_hashmap_from_ks_and_vs_extra(ErtsHeapFactory *factory,
Eterm *ks, Eterm *vs, Uint n,
Eterm k, Eterm v);
diff --git a/erts/emulator/beam/erl_md5.c b/erts/emulator/beam/erl_md5.c
index f4b6301b5c..2a4e026d9c 100644
--- a/erts/emulator/beam/erl_md5.c
+++ b/erts/emulator/beam/erl_md5.c
@@ -129,10 +129,9 @@ void MD5Init(MD5_CTX* context)
* operation, processing another message block, and updating the
* context.
*/
-void MD5Update (context, input, inputLen)
- MD5_CTX *context; /* context */
- unsigned char *input; /* input block */
- unsigned int inputLen; /* length of input block */
+void MD5Update (MD5_CTX *context,
+ unsigned char *input, /* input block */
+ unsigned int inputLen) /* length of input block */
{
unsigned int i, index, partLen;
@@ -175,9 +174,8 @@ void MD5Update (context, input, inputLen)
* MD5 finalization. Ends an MD5 message-digest operation, writing
the message digest and zeroizing the context.
*/
-void MD5Final (digest, context)
- unsigned char digest[16]; /* message digest */
- MD5_CTX *context; /* context */
+void MD5Final (unsigned char digest[16], /* message digest */
+ MD5_CTX *context) /* context */
{
unsigned char bits[8];
unsigned int index, padLen;
@@ -213,9 +211,7 @@ void MD5Final (digest, context)
/*
* MD5 basic transformation. Transforms state based on block.
*/
-static void MD5Transform (state, block)
- Uint32 state[4];
- unsigned char block[64];
+static void MD5Transform (Uint32 state[4], unsigned char block[64])
{
Uint32 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
@@ -308,10 +304,7 @@ static void MD5Transform (state, block)
* Encodes input (Uint32) into output (unsigned char). Assumes len is
* a multiple of 4.
*/
-static void Encode (output, input, len)
- unsigned char *output;
- Uint32 *input;
- unsigned int len;
+static void Encode (unsigned char *output, Uint32 *input, unsigned int len)
{
unsigned int i, j;
@@ -327,10 +320,7 @@ static void Encode (output, input, len)
* Decodes input (unsigned char) into output (Uint32). Assumes len is
* a multiple of 4.
*/
-static void Decode (output, input, len)
- Uint32 *output;
- unsigned char *input;
- unsigned int len;
+static void Decode (Uint32 *output, unsigned char *input, unsigned int len)
{
unsigned int i, j;
diff --git a/erts/emulator/beam/erl_message.c b/erts/emulator/beam/erl_message.c
index 0026ecce99..2b5ec7d387 100644
--- a/erts/emulator/beam/erl_message.c
+++ b/erts/emulator/beam/erl_message.c
@@ -660,6 +660,7 @@ erts_try_alloc_message_on_heap(Process *pp,
mp = erts_alloc_message(0, NULL);
mp->data.attached = NULL;
*on_heap_p = !0;
+ erts_adjust_memory_break(pp, -sz);
}
else if (pp && erts_proc_trylock(pp, ERTS_PROC_LOCK_MAIN) == 0) {
locked_main = 1;
@@ -975,6 +976,7 @@ void erts_save_message_in_proc(Process *p, ErtsMessage *msgp)
hfp = msgp->data.heap_frag;
}
else {
+ erts_adjust_message_break(p, ERL_MESSAGE_TERM(msgp));
erts_free_message(msgp);
return; /* Nothing to save */
}
diff --git a/erts/emulator/beam/erl_monitor_link.c b/erts/emulator/beam/erl_monitor_link.c
index c25ca8d5e5..0c1eef68f0 100644
--- a/erts/emulator/beam/erl_monitor_link.c
+++ b/erts/emulator/beam/erl_monitor_link.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2018-2023. All Rights Reserved.
+ * Copyright Ericsson AB 2018-2022. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1100,16 +1100,6 @@ erts_monitor_destroy__(ErtsMonitorData *mdp)
|| ((mdp->origin.flags & ERTS_ML_FLGS_SAME)
== (mdp->u.target.flags & ERTS_ML_FLGS_SAME)));
- if (mdp->origin.flags & ERTS_ML_STATE_ALIAS_MASK) {
- ASSERT(mdp->origin.type == ERTS_MON_TYPE_ALIAS
- || mdp->origin.type == ERTS_MON_TYPE_PROC
- || mdp->origin.type == ERTS_MON_TYPE_PORT
- || mdp->origin.type == ERTS_MON_TYPE_TIME_OFFSET
- || mdp->origin.type == ERTS_MON_TYPE_DIST_PROC
- || mdp->origin.type == ERTS_MON_TYPE_DIST_PORT);
- erts_pid_ref_delete(mdp->ref);
- }
-
switch (mdp->origin.type) {
case ERTS_MON_TYPE_ALIAS:
ERTS_ML_ASSERT(!(mdp->origin.flags & ERTS_ML_FLG_TAG));
diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c
index 5a73a53f4e..a9661dc780 100644
--- a/erts/emulator/beam/erl_nif.c
+++ b/erts/emulator/beam/erl_nif.c
@@ -71,6 +71,17 @@
#include <limits.h>
#include <stddef.h> /* offsetof */
+#define ERTS_NIF_HALT_INFO_FLAG_BLOCK (1 << 0)
+#define ERTS_NIF_HALT_INFO_FLAG_HALTING (1 << 1)
+#define ERTS_NIF_HALT_INFO_FLAG_WAITING (1 << 2)
+
+typedef struct ErtsNifOnHaltData_ ErtsNifOnHaltData;
+struct ErtsNifOnHaltData_ {
+ ErtsNifOnHaltData *next;
+ ErtsNifOnHaltData *prev;
+ ErlNifOnHaltCallback *callback;
+};
+
/* Information about a loaded nif library.
* Each successful call to erlang:load_nif will allocate an instance of
* erl_module_nif. Two calls opening the same library will thus have the same
@@ -95,11 +106,26 @@ struct erl_module_nif {
+1 for each owned resource type with callbacks
+1 for each ongoing dirty NIF call
*/
+ int flags;
+ ErtsNifOnHaltData on_halt;
Module* mod; /* Can be NULL if purged and dynlib_refc > 0 */
+ /* Holds a copy of the `erl_module_instance` we're loading the module into,
+ * which may be freed (and moved into the staging table) if there's an
+ * on_load function that finishes before we're done loading the NIF. */
+ struct erl_module_instance mi_copy;
+
ErlNifFunc _funcs_copy_[1]; /* only used for old libs */
};
+#define ERTS_MOD_NIF_FLG_LOADING (1 << 0)
+#define ERTS_MOD_NIF_FLG_DELAY_HALT (1 << 1)
+#define ERTS_MOD_NIF_FLG_ON_HALT (1 << 2)
+
+static erts_atomic_t halt_tse;
+static erts_mtx_t on_halt_mtx;
+static ErtsNifOnHaltData *on_halt_requests;
+
typedef ERL_NIF_TERM (*NativeFunPtr)(ErlNifEnv*, int, const ERL_NIF_TERM[]);
#ifdef DEBUG
@@ -131,6 +157,8 @@ void dtrace_nifenv_str(ErlNifEnv *, char *);
#define MIN_HEAP_FRAG_SZ 200
static Eterm* alloc_heap_heavy(ErlNifEnv* env, size_t need, Eterm* hp);
+static void install_on_halt_callback(ErtsNifOnHaltData *ohdp);
+static void uninstall_on_halt_callback(ErtsNifOnHaltData *ohdp);
static ERTS_INLINE int
is_scheduler(void)
@@ -366,6 +394,25 @@ schedule(ErlNifEnv* env, NativeFunPtr direct_fp, NativeFunPtr indirect_fp,
return (ERL_NIF_TERM) THE_NON_VALUE;
}
+static ERTS_NOINLINE void
+eternal_sleep(void)
+{
+ while (!0)
+ erts_milli_sleep(1000*1000);
+}
+
+static ERTS_NOINLINE void
+handle_halting_unblocked_halt(erts_aint32_t info)
+{
+ if (info & ERTS_NIF_HALT_INFO_FLAG_WAITING) {
+ erts_tse_t *tse;
+ ERTS_THR_MEMORY_BARRIER;
+ tse = (erts_tse_t *) erts_atomic_read_nob(&halt_tse);
+ ASSERT(tse);
+ erts_tse_set(tse);
+ }
+ eternal_sleep();
+}
static ERL_NIF_TERM dirty_nif_finalizer(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM dirty_nif_exception(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
@@ -415,7 +462,34 @@ erts_call_dirty_nif(ErtsSchedulerData *esdp,
erts_proc_unlock(c_p, ERTS_PROC_LOCK_MAIN);
- result = (*dirty_nif)(&env, codemfa->arity, argv); /* Call dirty NIF */
+ if (!(env.mod_nif->flags & ERTS_MOD_NIF_FLG_DELAY_HALT)) {
+ result = (*dirty_nif)(&env, codemfa->arity, argv); /* Call dirty NIF */
+ }
+ else {
+ erts_atomic32_t *dirty_nif_halt_info = &esdp->u.dirty_nif_halt_info;
+ erts_aint32_t info;
+ info = erts_atomic32_cmpxchg_nob(dirty_nif_halt_info,
+ ERTS_NIF_HALT_INFO_FLAG_BLOCK,
+ 0);
+ if (info != 0) {
+ ASSERT(info == ERTS_NIF_HALT_INFO_FLAG_HALTING
+ || info == (ERTS_NIF_HALT_INFO_FLAG_HALTING
+ | ERTS_NIF_HALT_INFO_FLAG_WAITING));
+ eternal_sleep();
+ }
+ result = (*dirty_nif)(&env, codemfa->arity, argv); /* Call dirty NIF */
+ info = erts_atomic32_read_band_relb(dirty_nif_halt_info,
+ ~ERTS_NIF_HALT_INFO_FLAG_BLOCK);
+ if (info & ERTS_NIF_HALT_INFO_FLAG_HALTING) {
+ ASSERT(info == (ERTS_NIF_HALT_INFO_FLAG_BLOCK
+ | ERTS_NIF_HALT_INFO_FLAG_HALTING)
+ || info == (ERTS_NIF_HALT_INFO_FLAG_BLOCK
+ | ERTS_NIF_HALT_INFO_FLAG_HALTING
+ | ERTS_NIF_HALT_INFO_FLAG_WAITING));
+ handle_halting_unblocked_halt(info);
+ }
+ ASSERT(info == ERTS_NIF_HALT_INFO_FLAG_BLOCK);
+ }
erts_proc_lock(c_p, ERTS_PROC_LOCK_MAIN);
@@ -1485,36 +1559,104 @@ int enif_get_tuple(ErlNifEnv* env, Eterm tpl, int* arity, const Eterm** array)
return 1;
}
-int enif_get_string(ErlNifEnv *env, ERL_NIF_TERM list, char* buf, unsigned len,
- ErlNifCharEncoding encoding)
+int enif_get_string(ErlNifEnv *env, ERL_NIF_TERM list, char *buf, unsigned len,
+ ErlNifCharEncoding encoding)
{
- Eterm* listptr;
- int n = 0;
-
- ASSERT(encoding == ERL_NIF_LATIN1);
+ ASSERT(encoding == ERL_NIF_LATIN1 || encoding == ERL_NIF_UTF8);
if (len < 1) {
- return 0;
+ return 0;
}
- while (is_not_nil(list)) {
- if (is_not_list(list)) {
- buf[n] = '\0';
- return 0;
- }
- listptr = list_val(list);
-
- if (!is_byte(*listptr)) {
- buf[n] = '\0';
- return 0;
- }
- buf[n++] = unsigned_val(*listptr);
- if (n >= len) {
- buf[n-1] = '\0'; /* truncate */
- return -len;
- }
- list = CDR(listptr);
+ if (encoding == ERL_NIF_LATIN1) {
+ Eterm *listptr;
+ int n = 0;
+ while (is_not_nil(list)) {
+ if (is_not_list(list)) {
+ buf[n] = '\0';
+ return 0;
+ }
+ listptr = list_val(list);
+
+ if (!is_byte(*listptr)) {
+ buf[n] = '\0';
+ return 0;
+ }
+ buf[n++] = unsigned_val(*listptr);
+ if (n >= len) {
+ buf[n-1] = '\0'; /* truncate */
+ return -len;
+ }
+ list = CDR(listptr);
+ }
+ buf[n] = '\0';
+ return n + 1;
+ } else if (encoding == ERL_NIF_UTF8) {
+ int retval;
+ Sint written = 0;
+ if (is_nil(list)) {
+ buf[0] = '\0';
+ return 1;
+ }
+ if (is_not_list(list)) {
+ buf[0] = '\0';
+ return 0;
+ }
+ retval = erts_unicode_list_to_buf(list, (byte *)buf, (Sint)(len - 1), (Sint)len, &written);
+ if (retval == 0) {
+ if (len > 0 && written == 0) {
+ buf[written] = '\0';
+ return 1;
+ }
+ buf[written] = '\0'; /* success */
+ return (int)(written + 1);
+ }
+ if (retval == -2) {
+ buf[written] = '\0'; /* truncate */
+ return -(int)(written + 1);
+ }
+ buf[0] = '\0'; /* failure */
+ return 0;
+ }
+ return 0;
+}
+
+int enif_get_string_length(ErlNifEnv *env, ERL_NIF_TERM list, unsigned *len,
+ ErlNifCharEncoding encoding)
+{
+ ASSERT(encoding == ERL_NIF_LATIN1 || encoding == ERL_NIF_UTF8);
+ if (encoding == ERL_NIF_LATIN1) {
+ Eterm *listptr = NULL;
+ unsigned n = 0;
+ while (is_not_nil(list)) {
+ if (is_not_list(list)) {
+ return 0;
+ }
+ listptr = list_val(list);
+
+ if (!is_byte(*listptr)) {
+ return 0;
+ }
+ n++;
+ list = CDR(listptr);
+ }
+ *len = n;
+ return 1;
+ } else if (encoding == ERL_NIF_UTF8) {
+ Sint sz = 0;
+ if (is_nil(list)) {
+ *len = (unsigned)(sz);
+ return 1;
+ }
+ if (is_not_list(list)) {
+ return 0;
+ }
+ sz = erts_unicode_list_to_buf_len(list);
+ if (sz < 0) {
+ return 0;
+ }
+ *len = (unsigned)(sz);
+ return 1;
}
- buf[n] = '\0';
- return n + 1;
+ return 0;
}
Eterm enif_make_binary(ErlNifEnv* env, ErlNifBinary* bin)
@@ -1614,27 +1756,36 @@ int enif_has_pending_exception(ErlNifEnv* env, ERL_NIF_TERM* reason)
}
int enif_get_atom(ErlNifEnv* env, Eterm atom, char* buf, unsigned len,
- ErlNifCharEncoding encoding)
+ ErlNifCharEncoding encoding)
{
Atom* ap;
- ASSERT(encoding == ERL_NIF_LATIN1);
- if (is_not_atom(atom) || len==0) {
- return 0;
+ ASSERT(encoding == ERL_NIF_LATIN1 || encoding == ERL_NIF_UTF8);
+ if (is_not_atom(atom) || len == 0) {
+ return 0;
}
ap = atom_tab(atom_val(atom));
- if (ap->latin1_chars < 0 || ap->latin1_chars >= len) {
- return 0;
- }
- if (ap->latin1_chars == ap->len) {
- sys_memcpy(buf, ap->name, ap->len);
- }
- else {
- int dlen = erts_utf8_to_latin1((byte*)buf, ap->name, ap->len);
- ASSERT(dlen == ap->latin1_chars); (void)dlen;
+ if (encoding == ERL_NIF_LATIN1) {
+ if (ap->latin1_chars < 0 || ap->latin1_chars >= len) {
+ return 0;
+ }
+ if (ap->latin1_chars == ap->len) {
+ sys_memcpy(buf, ap->name, ap->len);
+ } else {
+ int dlen = erts_utf8_to_latin1((byte*)buf, ap->name, ap->len);
+ ASSERT(dlen == ap->latin1_chars); (void)dlen;
+ }
+ buf[ap->latin1_chars] = '\0';
+ return ap->latin1_chars + 1;
+ } else if (encoding == ERL_NIF_UTF8) {
+ if (ap->len >= len) {
+ return 0;
+ }
+ sys_memcpy(buf, ap->name, ap->len);
+ buf[ap->len] = '\0';
+ return ap->len + 1;
}
- buf[ap->latin1_chars] = '\0';
- return ap->latin1_chars + 1;
+ return 0;
}
int enif_get_int(ErlNifEnv* env, Eterm term, int* ip)
@@ -1730,17 +1881,24 @@ int enif_get_double(ErlNifEnv* env, ERL_NIF_TERM term, double* dp)
}
int enif_get_atom_length(ErlNifEnv* env, Eterm atom, unsigned* len,
- ErlNifCharEncoding enc)
+ ErlNifCharEncoding encoding)
{
- Atom* ap;
- ASSERT(enc == ERL_NIF_LATIN1);
- if (is_not_atom(atom)) return 0;
+ Atom *ap = NULL;
+ ASSERT(encoding == ERL_NIF_LATIN1 || encoding == ERL_NIF_UTF8);
+ if (is_not_atom(atom))
+ return 0;
ap = atom_tab(atom_val(atom));
- if (ap->latin1_chars < 0) {
- return 0;
+ if (encoding == ERL_NIF_LATIN1) {
+ if (ap->latin1_chars < 0) {
+ return 0;
+ }
+ *len = ap->latin1_chars;
+ return 1;
+ } else if (encoding == ERL_NIF_UTF8) {
+ *len = ap->len;
+ return 1;
}
- *len = ap->latin1_chars;
- return 1;
+ return 0;
}
int enif_get_list_cell(ErlNifEnv* env, Eterm term, Eterm* head, Eterm* tail)
@@ -1852,31 +2010,62 @@ ERL_NIF_TERM enif_make_double(ErlNifEnv* env, double d)
return make_float(hp);
}
-ERL_NIF_TERM enif_make_atom(ErlNifEnv* env, const char* name)
+ERL_NIF_TERM enif_make_atom(ErlNifEnv *env, const char *name)
{
return enif_make_atom_len(env, name, sys_strlen(name));
}
-ERL_NIF_TERM enif_make_atom_len(ErlNifEnv* env, const char* name, size_t len)
+ERL_NIF_TERM enif_make_atom_len(ErlNifEnv *env, const char *name, size_t len)
{
- if (len > MAX_ATOM_CHARACTERS)
+ ERL_NIF_TERM atom = THE_NON_VALUE;
+ if (!enif_make_new_atom_len(env, name, len, &atom, ERL_NIF_LATIN1)) {
return enif_make_badarg(env);
- return erts_atom_put((byte*)name, len, ERTS_ATOM_ENC_LATIN1, 1);
+ }
+ return atom;
}
-int enif_make_existing_atom(ErlNifEnv* env, const char* name, ERL_NIF_TERM* atom,
- ErlNifCharEncoding enc)
+int enif_make_new_atom(ErlNifEnv *env, const char *name, ERL_NIF_TERM *atom,
+ ErlNifCharEncoding encoding)
{
- return enif_make_existing_atom_len(env, name, sys_strlen(name), atom, enc);
+ return enif_make_new_atom_len(env, name, sys_strlen(name), atom, encoding);
}
-int enif_make_existing_atom_len(ErlNifEnv* env, const char* name, size_t len,
- ERL_NIF_TERM* atom, ErlNifCharEncoding encoding)
+int enif_make_new_atom_len(ErlNifEnv *env, const char *name, size_t len,
+ ERL_NIF_TERM *atom, ErlNifCharEncoding encoding)
{
- ASSERT(encoding == ERL_NIF_LATIN1);
- if (len > MAX_ATOM_CHARACTERS)
+ ERL_NIF_TERM atom_term = THE_NON_VALUE;
+ ASSERT(encoding == ERL_NIF_LATIN1 || encoding == ERL_NIF_UTF8);
+ if (encoding == ERL_NIF_LATIN1) {
+ atom_term = erts_atom_put((byte *)name, len, ERTS_ATOM_ENC_LATIN1, 0);
+ } else if (encoding == ERL_NIF_UTF8) {
+ atom_term = erts_atom_put((byte *)name, len, ERTS_ATOM_ENC_UTF8, 0);
+ }
+ if (atom_term == THE_NON_VALUE)
return 0;
- return erts_atom_get(name, len, atom, ERTS_ATOM_ENC_LATIN1);
+ *atom = atom_term;
+ return 1;
+}
+
+int enif_make_existing_atom(ErlNifEnv *env, const char *name, ERL_NIF_TERM *atom,
+ ErlNifCharEncoding encoding)
+{
+ return enif_make_existing_atom_len(env, name, sys_strlen(name), atom, encoding);
+}
+
+int enif_make_existing_atom_len(ErlNifEnv *env, const char *name, size_t len,
+ ERL_NIF_TERM *atom, ErlNifCharEncoding encoding)
+{
+ ASSERT(encoding == ERL_NIF_LATIN1 || encoding == ERL_NIF_UTF8);
+ if (encoding == ERL_NIF_LATIN1) {
+ if (len > MAX_ATOM_CHARACTERS)
+ return 0;
+ return erts_atom_get(name, len, atom, ERTS_ATOM_ENC_LATIN1);
+ } else if (encoding == ERL_NIF_UTF8) {
+ if (len > MAX_ATOM_SZ_LIMIT)
+ return 0;
+ return erts_atom_get(name, len, atom, ERTS_ATOM_ENC_UTF8);
+ }
+ return 0;
}
ERL_NIF_TERM enif_make_tuple(ErlNifEnv* env, unsigned cnt, ...)
@@ -1987,18 +2176,32 @@ ERL_NIF_TERM enif_make_list_from_array(ErlNifEnv* env, const ERL_NIF_TERM arr[],
return ret;
}
-ERL_NIF_TERM enif_make_string(ErlNifEnv* env, const char* string,
- ErlNifCharEncoding encoding)
+ERL_NIF_TERM enif_make_string(ErlNifEnv *env, const char *string,
+ ErlNifCharEncoding encoding)
{
return enif_make_string_len(env, string, sys_strlen(string), encoding);
}
-ERL_NIF_TERM enif_make_string_len(ErlNifEnv* env, const char* string,
- size_t len, ErlNifCharEncoding encoding)
-{
- Eterm* hp = alloc_heap(env,len*2);
- ASSERT(encoding == ERL_NIF_LATIN1);
- return erts_bld_string_n(&hp,NULL,string,len);
+ERL_NIF_TERM enif_make_string_len(ErlNifEnv *env, const char *string,
+ size_t len, ErlNifCharEncoding encoding)
+{
+ Eterm *hp = NULL;
+ ASSERT(encoding == ERL_NIF_LATIN1 || encoding == ERL_NIF_UTF8);
+ if (encoding == ERL_NIF_LATIN1) {
+ hp = alloc_heap(env, len * 2);
+ return erts_bld_string_n(&hp, NULL, string, len);
+ } else if (encoding == ERL_NIF_UTF8) {
+ const byte *err_pos;
+ Uint num_chars;
+ Uint num_built; /* characters */
+ Uint num_eaten; /* bytes */
+ if (erts_analyze_utf8((byte *)string, (Uint)len, &err_pos, &num_chars, NULL) != ERTS_UTF8_OK) {
+ return enif_make_badarg(env);
+ }
+ hp = alloc_heap(env, num_chars * 2);
+ return erts_make_list_from_utf8_buf(&hp, num_chars, (byte *)string, (Uint)len, &num_built, &num_eaten, NIL);
+ }
+ return enif_make_badarg(env);
}
ERL_NIF_TERM enif_make_ref(ErlNifEnv* env)
@@ -2229,7 +2432,7 @@ int enif_vsnprintf(char* buffer, size_t size, const char *format, va_list ap)
/*
* Sentinel node in circular list of all resource types.
- * List protected by code_write_permission.
+ * List protected by code modification permission.
*/
struct enif_resource_type_t resource_type_list;
@@ -2270,9 +2473,11 @@ static void close_dynlib(struct erl_module_nif* lib)
{
ASSERT(lib != NULL);
ASSERT(lib->mod == NULL);
- ASSERT(lib->handle != NULL);
ASSERT(erts_refc_read(&lib->dynlib_refc,0) == 0);
+ if (lib->flags & ERTS_MOD_NIF_FLG_ON_HALT)
+ uninstall_on_halt_callback(&lib->on_halt);
+
if (lib->entry.unload != NULL) {
struct enif_msg_environment_t msg_env;
pre_nif_noproc(&msg_env, lib, NULL);
@@ -2375,7 +2580,10 @@ ErlNifResourceType* open_resource_type(ErlNifEnv* env,
ErlNifResourceFlags op = flags;
Eterm module_am, name_am;
- ERTS_LC_ASSERT(erts_has_code_write_permission());
+ if (!env->mod_nif || !(env->mod_nif->flags & ERTS_MOD_NIF_FLG_LOADING))
+ goto done;
+
+ ERTS_LC_ASSERT(erts_has_code_mod_permission());
module_am = make_atom(env->mod_nif->mod->module);
name_am = enif_make_atom(env, name_str);
@@ -2431,6 +2639,7 @@ ErlNifResourceType* open_resource_type(ErlNifEnv* env,
ort->next = opened_rt_list;
opened_rt_list = ort;
}
+done:
if (tried != NULL) {
*tried = op;
}
@@ -2713,7 +2922,7 @@ void erts_nif_demonitored(ErtsResource* resource)
ASSERT(resource->type->fn.down);
erts_mtx_lock(&rmp->lock);
- free_me = ((rmon_refc_dec_read(rmp) == 0) & !!rmon_is_dying(rmp));
+ free_me = ((rmon_refc_dec_read(rmp) == 0) && !!rmon_is_dying(rmp));
erts_mtx_unlock(&rmp->lock);
if (free_me)
@@ -4167,7 +4376,7 @@ void erts_add_taint(Eterm mod_atom)
struct tainted_module_t *first, *t;
ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&erts_driver_list_lock)
- || erts_has_code_write_permission());
+ || erts_has_code_mod_permission());
first = (struct tainted_module_t*) erts_atomic_read_nob(&first_taint);
for (t=first ; t; t=t->next) {
@@ -4317,8 +4526,7 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2) {
typedef struct {
HashBucket bucket;
- const ErtsCodeInfo *code_info_exec;
- ErtsCodeInfo *code_info_rw;
+ const ErtsCodeInfo *code_info_ptr;
ErtsCodeInfo info;
struct
{
@@ -4349,12 +4557,12 @@ struct hash erts_nif_call_tab;
static HashValue nif_call_hash(ErtsNifBeamStub* obj)
{
- return ((HashValue)obj->code_info_exec / sizeof(BeamInstr));
+ return ((HashValue)obj->code_info_ptr / sizeof(BeamInstr));
}
static int nif_call_cmp(ErtsNifBeamStub* tmpl, ErtsNifBeamStub* obj)
{
- return tmpl->code_info_exec != obj->code_info_exec;
+ return tmpl->code_info_ptr != obj->code_info_ptr;
}
static ErtsNifBeamStub* nif_call_alloc(ErtsNifBeamStub* tmpl)
@@ -4519,8 +4727,11 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
lib->handle = handle;
erts_refc_init(&lib->refc, 2); /* Erlang code + NIF code */
erts_refc_init(&lib->dynlib_refc, 1);
+ lib->flags = 0;
+ lib->on_halt.callback = NULL;
ASSERT(opened_rt_list == NULL);
lib->mod = module_p;
+ lib->mi_copy = *this_mi;
lib->finish = erts_alloc(ERTS_ALC_T_NIF,
sizeof_ErtsNifFinish(entry->num_of_funcs));
@@ -4533,7 +4744,7 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
ErlNifFunc* f = &entry->funcs[i];
ErtsNifBeamStub* stub = &lib->finish->beam_stubv[i];
- stub->code_info_exec = NULL; /* end marker in case we fail */
+ stub->code_info_ptr = NULL; /* end marker in case we fail */
if (!erts_atom_get(f->name, sys_strlen(f->name), &f_atom, ERTS_ATOM_ENC_LATIN1)
|| (func_ix = get_func_ix(this_mi->code_hdr, f_atom, f->arity)) < 0) {
@@ -4575,8 +4786,7 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
ERTS_CT_ASSERT(sizeof(stub->code) <=
BEAM_NATIVE_MIN_FUNC_SZ * sizeof(Eterm));
- stub->code_info_exec = ci;
- stub->code_info_rw = erts_writable_code_ptr(this_mi, ci);
+ stub->code_info_ptr = ci;
stub->info = *ci;
if (hash_put(&erts_nif_call_tab, stub) != stub) {
@@ -4637,7 +4847,7 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
/* Call load or upgrade:
*/
env.mod_nif = lib;
-
+ lib->flags |= ERTS_MOD_NIF_FLG_LOADING;
lib->priv_data = NULL;
if (prev_mi->nif != NULL) { /**************** Upgrade ***************/
void* prev_old_data = prev_mi->nif->priv_data;
@@ -4671,12 +4881,17 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
ret = load_nif_error(c_p, "load", "Library load-call unsuccessful (%d).", veto);
}
}
+ lib->flags &= ~ERTS_MOD_NIF_FLG_LOADING;
if (ret == am_ok) {
/*
* Everything ok, make NIF code callable.
*/
- this_mi->nif = lib;
+ if (lib->flags & ERTS_MOD_NIF_FLG_ON_HALT)
+ install_on_halt_callback(&lib->on_halt);
+
+ this_mi->nif = lib;
+
prepare_opened_rt(lib);
/*
* The call table lock will make sure all NIFs and callbacks in module
@@ -4733,16 +4948,18 @@ static void patch_call_nif_early(ErlNifEntry* entry,
{
int i;
- ERTS_LC_ASSERT(erts_has_code_write_permission());
+ ERTS_LC_ASSERT(erts_has_code_mod_permission());
ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&erts_nif_call_tab_lock));
+ erts_unseal_module(this_mi);
+
for (i=0; i < entry->num_of_funcs; i++)
{
ErlNifFunc* f = &entry->funcs[i];
- int func_ix;
const ErtsCodeInfo *ci_exec;
ErtsCodeInfo *ci_rw;
Eterm f_atom;
+ int func_ix;
erts_atom_get(f->name, sys_strlen(f->name), &f_atom, ERTS_ATOM_ENC_LATIN1);
@@ -4779,6 +4996,8 @@ static void patch_call_nif_early(ErlNifEntry* entry,
erts_asm_bp_set_flag(ci_rw, ci_exec, ERTS_ASM_BP_FLAG_CALL_NIF_EARLY);
#endif
}
+
+ erts_seal_module(this_mi);
}
ErtsCodePtr erts_call_nif_early(Process* c_p, const ErtsCodeInfo* ci)
@@ -4786,7 +5005,7 @@ ErtsCodePtr erts_call_nif_early(Process* c_p, const ErtsCodeInfo* ci)
ErtsNifBeamStub* bs;
ErtsNifBeamStub tmpl;
- tmpl.code_info_exec = ci;
+ tmpl.code_info_ptr = ci;
erts_rwmtx_rlock(&erts_nif_call_tab_lock);
bs = (ErtsNifBeamStub*) hash_get(&erts_nif_call_tab, &tmpl);
@@ -4798,52 +5017,67 @@ ErtsCodePtr erts_call_nif_early(Process* c_p, const ErtsCodeInfo* ci)
static void load_nif_1st_finisher(void* vlib)
{
- struct erl_module_nif* lib = (struct erl_module_nif*) vlib;
+ struct erl_module_nif* lib = (struct erl_module_nif*)vlib;
ErtsNifFinish* fin;
int i;
+ /* Seize code modification permission to ensure that we're the only one
+ * unsealing modules. */
+ if (!erts_try_seize_code_mod_permission_aux(load_nif_1st_finisher,
+ vlib)) {
+ return;
+ }
+
erts_mtx_lock(&lib->load_mtx);
fin = lib->finish;
if (fin) {
+ erts_unseal_module(&lib->mi_copy);
+
for (i=0; i < lib->entry.num_of_funcs; i++) {
- const ErtsCodeInfo *ci_exec = fin->beam_stubv[i].code_info_exec;
- ErtsCodeInfo *ci_rw = fin->beam_stubv[i].code_info_rw;
+ const ErtsCodeInfo *ci_exec = fin->beam_stubv[i].code_info_ptr;
+ ErtsCodeInfo *ci_rw;
-#ifdef BEAMASM
- const char *code_exec = (char*)erts_codeinfo_to_code(ci_exec);
- char *code_rw = (char*)erts_codeinfo_to_code(ci_rw);
- const char *src = fin->beam_stubv[i].code.call_nif;
+ ci_rw = erts_writable_code_ptr(&lib->mi_copy, ci_exec);
- size_t cpy_sz = sizeof(fin->beam_stubv[0].code.call_nif) -
- BEAM_ASM_FUNC_PROLOGUE_SIZE;
+#ifdef BEAMASM
+ {
+ char *code_rw = (char*)erts_codeinfo_to_code(ci_rw);
+ const char *src = fin->beam_stubv[i].code.call_nif;
- sys_memcpy(&code_rw[BEAM_ASM_FUNC_PROLOGUE_SIZE],
- &src[BEAM_ASM_FUNC_PROLOGUE_SIZE],
- cpy_sz);
+ size_t cpy_sz = sizeof(fin->beam_stubv[0].code.call_nif) -
+ BEAM_ASM_FUNC_PROLOGUE_SIZE;
- beamasm_flush_icache(&code_exec[BEAM_ASM_FUNC_PROLOGUE_SIZE],
- cpy_sz);
+ sys_memcpy(&code_rw[BEAM_ASM_FUNC_PROLOGUE_SIZE],
+ &src[BEAM_ASM_FUNC_PROLOGUE_SIZE],
+ cpy_sz);
+ }
#else
- BeamInstr *code_ptr = (BeamInstr*)erts_codeinfo_to_code(ci_rw);
+ {
+ BeamInstr *code_ptr = (BeamInstr*)erts_codeinfo_to_code(ci_rw);
- (void)ci_exec;
+ (void)ci_exec;
- /* called function */
- code_ptr[1] = fin->beam_stubv[i].code.call_nif[1];
+ /* called function */
+ code_ptr[1] = fin->beam_stubv[i].code.call_nif[1];
- /* erl_module_nif */
- code_ptr[2] = fin->beam_stubv[i].code.call_nif[2];
+ /* erl_module_nif */
+ code_ptr[2] = fin->beam_stubv[i].code.call_nif[2];
- if (lib->entry.funcs[i].flags) {
- /* real NIF */
- code_ptr[3] = fin->beam_stubv[i].code.call_nif[3];
+ if (lib->entry.funcs[i].flags) {
+ /* real NIF */
+ code_ptr[3] = fin->beam_stubv[i].code.call_nif[3];
+ }
}
#endif
}
+
+ erts_seal_module(&lib->mi_copy);
}
erts_mtx_unlock(&lib->load_mtx);
+ erts_release_code_mod_permission();
+
if (fin) {
/* Schedule a code barrier to ensure that the `call_nif_WWW` sequence
* is visible before we start disabling the breakpoints. */
@@ -4860,54 +5094,61 @@ static void load_nif_2nd_finisher(void* vlib)
ErtsNifFinish* fin;
int i;
- /*
- * We seize code write permission only to avoid any trace breakpoints
- * to change while we patch the op_call_nif_WWW instruction.
- */
- if (!erts_try_seize_code_write_permission_aux(load_nif_2nd_finisher, vlib)) {
+ /* Seize code modification permission only to prevent any trace breakpoints
+ * from changing while we patch the op_call_nif_WWW instruction. */
+ if (!erts_try_seize_code_mod_permission_aux(load_nif_2nd_finisher,
+ vlib)) {
return;
}
erts_mtx_lock(&lib->load_mtx);
fin = lib->finish;
if (fin) {
+ erts_unseal_module(&lib->mi_copy);
+
for (i=0; i < lib->entry.num_of_funcs; i++) {
- const ErtsCodeInfo *ci_exec = fin->beam_stubv[i].code_info_exec;
- ErtsCodeInfo *ci_rw = fin->beam_stubv[i].code_info_rw;
+ const ErtsCodeInfo *ci_exec = fin->beam_stubv[i].code_info_ptr;
+ ErtsCodeInfo *ci_rw;
+
+ ci_rw = erts_writable_code_ptr(&lib->mi_copy, ci_exec);
#ifndef BEAMASM
- BeamInstr volatile *code_ptr;
+ {
+ BeamInstr volatile *code_ptr;
- ASSERT(ci_exec == ci_rw);
- (void)ci_exec;
+ ASSERT(ci_exec == ci_rw);
+ (void)ci_exec;
- code_ptr = (BeamInstr*)erts_codeinfo_to_code(ci_rw);
+ code_ptr = (BeamInstr*)erts_codeinfo_to_code(ci_rw);
- if (ci_rw->gen_bp) {
- /*
- * Function traced, patch the original instruction word
- */
- GenericBp* g = ci_rw->gen_bp;
- ASSERT(BeamIsOpCode(g->orig_instr, op_call_nif_early));
- g->orig_instr = BeamOpCodeAddr(op_call_nif_WWW);
+ if (ci_rw->gen_bp) {
+ /*
+ * Function traced, patch the original instruction word
+ */
+ GenericBp* g = ci_rw->gen_bp;
+ ASSERT(BeamIsOpCode(g->orig_instr, op_call_nif_early));
+ g->orig_instr = BeamOpCodeAddr(op_call_nif_WWW);
- if (BeamIsOpCode(code_ptr[0], op_i_generic_breakpoint)) {
- continue;
+ if (BeamIsOpCode(code_ptr[0], op_i_generic_breakpoint)) {
+ continue;
+ }
}
- }
- ASSERT(BeamIsOpCode(code_ptr[0], op_call_nif_early));
- code_ptr[0] = BeamOpCodeAddr(op_call_nif_WWW);
+ ASSERT(BeamIsOpCode(code_ptr[0], op_call_nif_early));
+ code_ptr[0] = BeamOpCodeAddr(op_call_nif_WWW);
+ }
#else
/* See beam_asm.h for details on how the nif load trampoline works */
erts_asm_bp_unset_flag(ci_rw, ci_exec,
ERTS_ASM_BP_FLAG_CALL_NIF_EARLY);
#endif
}
+
+ erts_seal_module(&lib->mi_copy);
}
erts_mtx_unlock(&lib->load_mtx);
- erts_release_code_write_permission();
+ erts_release_code_mod_permission();
if (fin) {
UWord bytes = sizeof_ErtsNifFinish(lib->entry.num_of_funcs);
@@ -4991,7 +5232,8 @@ erts_unload_nif(struct erl_module_nif* lib)
ASSERT(lib != NULL);
ASSERT(lib->mod != NULL);
- ERTS_LC_ASSERT(erts_has_code_write_permission());
+
+ ERTS_LC_ASSERT(erts_has_code_mod_permission());
erts_tracer_nif_clear();
@@ -5023,7 +5265,7 @@ erts_unload_nif(struct erl_module_nif* lib)
deref_nifmod(lib);
}
-void erl_nif_init()
+void erl_nif_init(void)
{
ERTS_CT_ASSERT((offsetof(ErtsResource,data) % 8)
== ERTS_MAGIC_BIN_BYTES_TO_ALIGN);
@@ -5038,6 +5280,20 @@ void erl_nif_init()
nif_call_table_init();
static_nifs_init();
+
+ erts_atomic_init_nob(&halt_tse, (erts_aint_t) NULL);
+ erts_mtx_init(&on_halt_mtx, "on_halt", NIL,
+ ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
+ on_halt_requests = NULL;
+}
+
+void
+erts_nif_sched_init(ErtsSchedulerData *esdp)
+{
+ if (esdp->type == ERTS_SCHED_DIRTY_CPU
+ || esdp->type == ERTS_SCHED_DIRTY_IO) {
+ erts_atomic32_init_nob(&esdp->u.dirty_nif_halt_info, 0);
+ }
}
int erts_nif_get_funcs(struct erl_module_nif* mod,
@@ -5125,6 +5381,191 @@ Eterm erts_nif_call_function(Process *p, Process *tracee,
return nif_result;
}
+/*
+ * Set options...
+ */
+
+int
+enif_set_option(ErlNifEnv *env, ErlNifOption opt, ...)
+{
+ if (!env)
+ return EINVAL;
+
+ switch (opt) {
+
+ case ERL_NIF_OPT_DELAY_HALT: {
+ struct erl_module_nif *m = env->mod_nif;
+
+ if (!m || !(m->flags & ERTS_MOD_NIF_FLG_LOADING)
+ || (m->flags & ERTS_MOD_NIF_FLG_DELAY_HALT)) {
+ return EINVAL;
+ }
+
+ m->flags |= ERTS_MOD_NIF_FLG_DELAY_HALT;
+
+ return 0;
+ }
+
+ case ERL_NIF_OPT_ON_HALT: {
+ struct erl_module_nif *m = env->mod_nif;
+ ErlNifOnHaltCallback *on_halt;
+ va_list argp;
+
+ if (!m || ((m->flags & (ERTS_MOD_NIF_FLG_LOADING
+ | ERTS_MOD_NIF_FLG_ON_HALT))
+ != ERTS_MOD_NIF_FLG_LOADING)) {
+ return EINVAL;
+ }
+
+ ASSERT(!m->on_halt.callback);
+
+ va_start(argp, opt);
+ on_halt = va_arg(argp, ErlNifOnHaltCallback *);
+ va_end(argp);
+ if (!on_halt)
+ return EINVAL;
+
+ m->on_halt.callback = on_halt;
+ m->flags |= ERTS_MOD_NIF_FLG_ON_HALT;
+
+ return 0;
+ }
+
+ default:
+ return EINVAL;
+
+ }
+}
+
+/*
+ * Halt functionality...
+ */
+
+void
+erts_nif_execute_on_halt(void)
+{
+ ErtsNifOnHaltData *ohdp;
+
+ erts_mtx_lock(&on_halt_mtx);
+ for (ohdp = on_halt_requests; ohdp; ohdp = ohdp->next) {
+ struct erl_module_nif *m;
+ m = ErtsContainerStruct(ohdp, struct erl_module_nif, on_halt);
+ ohdp->callback(m->priv_data);
+ }
+ on_halt_requests = NULL;
+ erts_mtx_unlock(&on_halt_mtx);
+}
+
+void
+erts_nif_notify_halt(void)
+{
+ int ix;
+
+ erts_nif_execute_on_halt();
+
+ for (ix = 0; ix < erts_no_dirty_cpu_schedulers; ix++) {
+ ErtsSchedulerData *esdp = ERTS_DIRTY_CPU_SCHEDULER_IX(ix);
+ ASSERT(esdp->type == ERTS_SCHED_DIRTY_CPU);
+ (void) erts_atomic32_read_bor_nob(&esdp->u.dirty_nif_halt_info,
+ ERTS_NIF_HALT_INFO_FLAG_HALTING);
+ }
+ for (ix = 0; ix < erts_no_dirty_io_schedulers; ix++) {
+ ErtsSchedulerData *esdp = ERTS_DIRTY_IO_SCHEDULER_IX(ix);
+ ASSERT(esdp->type == ERTS_SCHED_DIRTY_IO);
+ (void) erts_atomic32_read_bor_nob(&esdp->u.dirty_nif_halt_info,
+ ERTS_NIF_HALT_INFO_FLAG_HALTING);
+ }
+}
+
+static ERTS_INLINE void
+wait_dirty_call_blocking_halt(ErtsSchedulerData *esdp, erts_tse_t *tse)
+{
+ erts_atomic32_t *dirty_nif_halt_info = &esdp->u.dirty_nif_halt_info;
+ erts_aint32_t info = erts_atomic32_read_acqb(dirty_nif_halt_info);
+ ASSERT(info & ERTS_NIF_HALT_INFO_FLAG_HALTING);
+ if (!(info & ERTS_NIF_HALT_INFO_FLAG_BLOCK))
+ return;
+ info = erts_atomic32_read_bor_mb(dirty_nif_halt_info,
+ ERTS_NIF_HALT_INFO_FLAG_WAITING);
+ if (!(info & ERTS_NIF_HALT_INFO_FLAG_BLOCK))
+ return;
+ while (!0) {
+ erts_tse_reset(tse);
+ info = erts_atomic32_read_acqb(dirty_nif_halt_info);
+ if (!(info & ERTS_NIF_HALT_INFO_FLAG_BLOCK))
+ return;
+ while (!0) {
+ if (erts_tse_wait(tse) != EINTR)
+ break;
+ }
+ }
+}
+
+void
+erts_nif_wait_calls(void)
+{
+ erts_tse_t *tse;
+ int ix;
+
+ tse = erts_tse_fetch();
+ erts_atomic_set_nob(&halt_tse, (erts_aint_t) tse);
+
+ for (ix = 0; ix < erts_no_dirty_cpu_schedulers; ix++) {
+ ErtsSchedulerData *esdp = ERTS_DIRTY_CPU_SCHEDULER_IX(ix);
+ ASSERT(esdp->type == ERTS_SCHED_DIRTY_CPU);
+ wait_dirty_call_blocking_halt(esdp, tse);
+ }
+ for (ix = 0; ix < erts_no_dirty_io_schedulers; ix++) {
+ ErtsSchedulerData *esdp = ERTS_DIRTY_IO_SCHEDULER_IX(ix);
+ ASSERT(esdp->type == ERTS_SCHED_DIRTY_IO);
+ wait_dirty_call_blocking_halt(esdp, tse);
+ }
+}
+
+static void
+install_on_halt_callback(ErtsNifOnHaltData *ohdp)
+{
+ ASSERT(ohdp->callback);
+
+ erts_mtx_lock(&on_halt_mtx);
+ ohdp->next = on_halt_requests;
+ ohdp->prev = NULL;
+ if (on_halt_requests)
+ on_halt_requests->prev = ohdp;
+ on_halt_requests = ohdp;
+ erts_mtx_unlock(&on_halt_mtx);
+}
+
+static void
+uninstall_on_halt_callback(ErtsNifOnHaltData *ohdp)
+{
+ erts_mtx_lock(&on_halt_mtx);
+ ohdp->callback = NULL;
+ if (ohdp->prev) {
+ ASSERT(on_halt_requests != ohdp);
+ ohdp->prev->next = ohdp->next;
+ }
+ else if (on_halt_requests == ohdp) {
+ ASSERT(erts_halt_code == INT_MIN);
+ on_halt_requests = ohdp->next;
+ }
+ else {
+ /*
+ * Uninstall during halt; and our callback
+ * has already been called...
+ */
+ ASSERT(erts_halt_code != INT_MIN);
+ }
+ if (ohdp->next) {
+ ohdp->next->prev = ohdp->prev;
+ }
+ erts_mtx_unlock(&on_halt_mtx);
+}
+
+/*
+ * End of halt functionality...
+ */
+
#ifdef USE_VM_PROBES
void dtrace_nifenv_str(ErlNifEnv *env, char *process_buf)
{
diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h
index a9a81483f6..f4e8f5f76f 100644
--- a/erts/emulator/beam/erl_nif.h
+++ b/erts/emulator/beam/erl_nif.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2009-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2009-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,9 +57,11 @@
** 2.15: 22.0 ERL_NIF_SELECT_CANCEL, enif_select_(read|write)
** enif_term_type
** 2.16: 24.0 enif_init_resource_type, enif_dynamic_resource_call
+** 2.17: 26.0 enif_set_option, enif_get_string_length, enif_make_new_atom,
+** enif_make_new_atom_len, ERL_NIF_UTF8
*/
#define ERL_NIF_MAJOR_VERSION 2
-#define ERL_NIF_MINOR_VERSION 16
+#define ERL_NIF_MINOR_VERSION 17
/*
* WHEN CHANGING INTERFACE VERSION, also replace erts version below with
@@ -70,7 +72,7 @@
* If you're not on the OTP team, you should use a placeholder like
* erts-@MyName@ instead.
*/
-#define ERL_NIF_MIN_ERTS_VERSION "erts-12.0"
+#define ERL_NIF_MIN_ERTS_VERSION "erts-@OTP-17771:OTP-18334@"
/*
* The emulator will refuse to load a nif-lib with a major version
@@ -186,7 +188,8 @@ typedef enum
typedef enum
{
- ERL_NIF_LATIN1 = 1
+ ERL_NIF_LATIN1 = 1,
+ ERL_NIF_UTF8 = 2,
}ErlNifCharEncoding;
typedef struct
@@ -201,6 +204,8 @@ typedef struct
typedef ErlDrvMonitor ErlNifMonitor;
+typedef void ErlNifOnHaltCallback(void *priv_data);
+
typedef struct enif_resource_type_t ErlNifResourceType;
typedef void ErlNifResourceDtor(ErlNifEnv*, void*);
typedef void ErlNifResourceStop(ErlNifEnv*, void*, ErlNifEvent, int is_direct_call);
@@ -325,6 +330,11 @@ typedef enum {
#define ERL_NIF_THR_DIRTY_CPU_SCHEDULER 2
#define ERL_NIF_THR_DIRTY_IO_SCHEDULER 3
+typedef enum {
+ ERL_NIF_OPT_DELAY_HALT = 1,
+ ERL_NIF_OPT_ON_HALT = 2
+} ErlNifOption;
+
#if (defined(__WIN32__) || defined(_WIN32) || defined(_WIN32_))
# define ERL_NIF_API_FUNC_DECL(RET_TYPE, NAME, ARGS) RET_TYPE (*NAME) ARGS
typedef struct {
diff --git a/erts/emulator/beam/erl_nif_api_funcs.h b/erts/emulator/beam/erl_nif_api_funcs.h
index f9004eb64b..998bfebd8b 100644
--- a/erts/emulator/beam/erl_nif_api_funcs.h
+++ b/erts/emulator/beam/erl_nif_api_funcs.h
@@ -219,6 +219,11 @@ ERL_NIF_API_FUNC_DECL(ErlNifTermType,enif_term_type,(ErlNifEnv* env, ERL_NIF_TER
ERL_NIF_API_FUNC_DECL(ErlNifResourceType*,enif_init_resource_type,(ErlNifEnv*, const char* name_str, const ErlNifResourceTypeInit*, ErlNifResourceFlags flags, ErlNifResourceFlags* tried));
ERL_NIF_API_FUNC_DECL(int,enif_dynamic_resource_call,(ErlNifEnv*, ERL_NIF_TERM mod, ERL_NIF_TERM name, ERL_NIF_TERM rsrc, void* call_data));
+ERL_NIF_API_FUNC_DECL(int, enif_get_string_length, (ErlNifEnv *env, ERL_NIF_TERM list, unsigned *len, ErlNifCharEncoding encoding));
+ERL_NIF_API_FUNC_DECL(int, enif_make_new_atom, (ErlNifEnv *env, const char *name, ERL_NIF_TERM *atom, ErlNifCharEncoding encoding));
+ERL_NIF_API_FUNC_DECL(int, enif_make_new_atom_len, (ErlNifEnv *env, const char *name, size_t len, ERL_NIF_TERM *atom, ErlNifCharEncoding encoding));
+ERL_NIF_API_FUNC_DECL(int, enif_set_option, (ErlNifEnv *env, ErlNifOption opt, ...));
+
/*
** ADD NEW ENTRIES HERE (before this comment) !!!
*/
@@ -408,6 +413,10 @@ ERL_NIF_API_FUNC_DECL(int,enif_dynamic_resource_call,(ErlNifEnv*, ERL_NIF_TERM m
# define enif_term_type ERL_NIF_API_FUNC_MACRO(enif_term_type)
# define enif_init_resource_type ERL_NIF_API_FUNC_MACRO(enif_init_resource_type)
# define enif_dynamic_resource_call ERL_NIF_API_FUNC_MACRO(enif_dynamic_resource_call)
+# define enif_get_string_length ERL_NIF_API_FUNC_MACRO(enif_get_string_length)
+# define enif_make_new_atom ERL_NIF_API_FUNC_MACRO(enif_make_new_atom)
+# define enif_make_new_atom_len ERL_NIF_API_FUNC_MACRO(enif_make_new_atom_len)
+# define enif_set_option ERL_NIF_API_FUNC_MACRO(enif_set_option)
/*
** ADD NEW ENTRIES HERE (before this comment)
*/
diff --git a/erts/emulator/beam/erl_node_container_utils.h b/erts/emulator/beam/erl_node_container_utils.h
index a92f031971..a72c97cc4d 100644
--- a/erts/emulator/beam/erl_node_container_utils.h
+++ b/erts/emulator/beam/erl_node_container_utils.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2001-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2001-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -176,9 +176,9 @@ extern ErtsPTab erts_proc;
*/
#define ERTS_MAX_PROCESSES (ERTS_PTAB_MAX_SIZE-1)
-#define ERTS_MAX_INTERNAL_PID_DATA ((1 << _PID_DATA_SIZE) - 1)
-#define ERTS_MAX_INTERNAL_PID_NUMBER ((1 << _PID_NUM_SIZE) - 1)
-#define ERTS_MAX_INTERNAL_PID_SERIAL ((1 << _PID_SER_SIZE) - 1)
+#define ERTS_MAX_INTERNAL_PID_DATA ((UWORD_CONSTANT(1) << _PID_DATA_SIZE) - 1)
+#define ERTS_MAX_INTERNAL_PID_NUMBER ((UWORD_CONSTANT(1) << _PID_NUM_SIZE) - 1)
+#define ERTS_MAX_INTERNAL_PID_SERIAL ((UWORD_CONSTANT(1) << _PID_SER_SIZE) - 1)
#define ERTS_INTERNAL_PROC_BITS (_PID_SER_SIZE + _PID_NUM_SIZE)
@@ -244,9 +244,9 @@ extern ErtsPTab erts_port;
in the Erlang node. ERTS_MAX_PORTS is a hard upper limit.
*/
#define ERTS_MAX_PORTS (ERTS_PTAB_MAX_SIZE-1)
-#define ERTS_MAX_PORT_DATA ((1 << _PORT_DATA_SIZE) - 1)
-#define ERTS_MAX_INTERNAL_PORT_NUMBER ((1 << _PORT_NUM_SIZE) - 1)
-#define ERTS_MAX_V3_PORT_NUMBER ((1 << _PORT_NUM_SIZE) - 1)
+#define ERTS_MAX_PORT_DATA ((UWORD_CONSTANT(1) << _PORT_DATA_SIZE) - 1)
+#define ERTS_MAX_INTERNAL_PORT_NUMBER ((UWORD_CONSTANT(1) << _PORT_NUM_SIZE) - 1)
+#define ERTS_MAX_V3_PORT_NUMBER ((UWORD_CONSTANT(1) << 28) - 1)
#define ERTS_PORTS_BITS (_PORT_NUM_SIZE)
@@ -256,7 +256,9 @@ extern ErtsPTab erts_port;
* Refs *
\* */
-#define internal_ref_no_numbers(x) ERTS_REF_NUMBERS
+#define internal_ref_no_numbers(x) (is_internal_pid_ref((x)) \
+ ? ERTS_PID_REF_NUMBERS \
+ : ERTS_REF_NUMBERS)
#define internal_ref_numbers(x) (is_internal_magic_ref((x)) \
? internal_magic_ref_numbers((x)) \
: internal_non_magic_ref_numbers((x)))
diff --git a/erts/emulator/beam/erl_node_tables.c b/erts/emulator/beam/erl_node_tables.c
index c2d9b0967d..dac24d0310 100644
--- a/erts/emulator/beam/erl_node_tables.c
+++ b/erts/emulator/beam/erl_node_tables.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2001-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2001-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/erts/emulator/beam/erl_posix_str.c b/erts/emulator/beam/erl_posix_str.c
index 5b515d6e78..c57c269095 100644
--- a/erts/emulator/beam/erl_posix_str.c
+++ b/erts/emulator/beam/erl_posix_str.c
@@ -47,8 +47,7 @@
*/
char *
-erl_errno_id(error)
- int error; /* Posix error number (as from errno). */
+erl_errno_id(int error /* Posix error number (as from errno). */)
{
switch (error) {
#ifdef E2BIG
diff --git a/erts/emulator/beam/erl_printf_term.c b/erts/emulator/beam/erl_printf_term.c
index 24d6032012..18858a21bc 100644
--- a/erts/emulator/beam/erl_printf_term.c
+++ b/erts/emulator/beam/erl_printf_term.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2005-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2005-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -722,45 +722,46 @@ print_term(fmtfn_t fn, void* arg, Eterm obj, long *dcount) {
}
} else {
Uint n, mapval;
+ Eterm* assoc;
+ Eterm key, val;
+
mapval = MAP_HEADER_VAL(*head);
switch (MAP_HEADER_TYPE(*head)) {
case MAP_HEADER_TAG_HAMT_HEAD_ARRAY:
case MAP_HEADER_TAG_HAMT_HEAD_BITMAP:
- PRINT_STRING(res, fn, arg, "#<");
- PRINT_UWORD(res, fn, arg, 'x', 0, 1, mapval);
- PRINT_STRING(res, fn, arg, ">{");
- WSTACK_PUSH(s,PRT_CLOSE_TUPLE);
- n = hashmap_bitcount(mapval);
- ASSERT(n < 17);
- head += 2;
- if (n > 0) {
- n--;
- WSTACK_PUSH(s, head[n]);
- WSTACK_PUSH(s, PRT_TERM);
- while (n--) {
- WSTACK_PUSH(s, PRT_COMMA);
- WSTACK_PUSH(s, head[n]);
- WSTACK_PUSH(s, PRT_TERM);
- }
- }
- break;
+ PRINT_STRING(res, fn, arg, "#{");
+ WSTACK_PUSH(s, PRT_CLOSE_TUPLE);
+ head++;
+ /* fall through */
case MAP_HEADER_TAG_HAMT_NODE_BITMAP:
n = hashmap_bitcount(mapval);
- head++;
- PRINT_CHAR(res, fn, arg, '<');
- PRINT_UWORD(res, fn, arg, 'x', 0, 1, mapval);
- PRINT_STRING(res, fn, arg, ">{");
- WSTACK_PUSH(s,PRT_CLOSE_TUPLE);
- ASSERT(n < 17);
- if (n > 0) {
- n--;
- WSTACK_PUSH(s, head[n]);
- WSTACK_PUSH(s, PRT_TERM);
- while (n--) {
- WSTACK_PUSH(s, PRT_COMMA);
- WSTACK_PUSH(s, head[n]);
- WSTACK_PUSH(s, PRT_TERM);
+ ASSERT(0 < n && n < 17);
+ while (1) {
+ if (is_list(head[n])) {
+ assoc = list_val(head[n]);
+ key = CAR(assoc);
+ val = CDR(assoc);
+ WSTACK_PUSH5(s, val, PRT_TERM, PRT_ASSOC, key, PRT_TERM);
+ }
+ else if (is_tuple(head[n])) { /* collision node */
+ Eterm *tpl = tuple_val(head[n]);
+ Uint arity = arityval(tpl[0]);
+ ASSERT(arity >= 2);
+ while (1) {
+ assoc = list_val(tpl[arity]);
+ key = CAR(assoc);
+ val = CDR(assoc);
+ WSTACK_PUSH5(s, val, PRT_TERM, PRT_ASSOC, key, PRT_TERM);
+ if (--arity == 0)
+ break;
+ WSTACK_PUSH(s, PRT_COMMA);
+ }
+ } else {
+ WSTACK_PUSH2(s, head[n], PRT_TERM);
}
+ if (--n == 0)
+ break;
+ WSTACK_PUSH(s, PRT_COMMA);
}
break;
}
diff --git a/erts/emulator/beam/erl_proc_sig_queue.c b/erts/emulator/beam/erl_proc_sig_queue.c
index 79d661675e..ee1d345fb3 100644
--- a/erts/emulator/beam/erl_proc_sig_queue.c
+++ b/erts/emulator/beam/erl_proc_sig_queue.c
@@ -5311,8 +5311,6 @@ handle_alias_message(Process *c_p, ErtsMessage *sig, ErtsMessage ***next_nm_sig)
erts_monitor_tree_delete(&ERTS_P_MONITORS(c_p), mon);
- erts_pid_ref_delete(alias);
-
switch (mon->type) {
case ERTS_MON_TYPE_DIST_PORT:
case ERTS_MON_TYPE_ALIAS:
@@ -5637,7 +5635,6 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
case ERTS_ML_STATE_ALIAS_ONCE:
case ERTS_ML_STATE_ALIAS_DEMONITOR:
ASSERT(is_internal_pid_ref(mdp->ref));
- erts_pid_ref_delete(mdp->ref);
/* fall through... */
default:
if (type != ERTS_MON_TYPE_NODE)
@@ -5674,7 +5671,6 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
if ((mon->flags & ERTS_ML_STATE_ALIAS_MASK)
== ERTS_ML_STATE_ALIAS_ONCE) {
mon->flags &= ~ERTS_ML_STATE_ALIAS_MASK;
- erts_pid_ref_delete(key);
}
}
else {
diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index c2ca5a03f3..7a89df49e0 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -2508,7 +2508,7 @@ notify_reap_ports_relb(void)
}
erts_atomic32_t erts_halt_progress;
-int erts_halt_code;
+int erts_halt_code = INT_MIN;
static ERTS_INLINE erts_aint32_t
handle_reap_ports(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
@@ -2553,7 +2553,7 @@ handle_reap_ports(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
erts_port_release(prt);
}
if (erts_atomic32_dec_read_nob(&erts_halt_progress) == 0) {
- erts_flush_async_exit(erts_halt_code, "");
+ erts_flush_exit(erts_halt_code, "");
}
}
return aux_work & ~ERTS_SSI_AUX_WORK_REAP_PORTS;
@@ -6307,7 +6307,7 @@ erts_init_scheduling(int no_schedulers, int no_schedulers_online, int no_poll_th
erts_atomic32_init_relb(&erts_halt_progress, -1);
- erts_halt_code = 0;
+ erts_halt_code = INT_MIN;
}
@@ -7397,7 +7397,7 @@ static ERTS_INLINE int
have_dirty_work(void)
{
return !(ERTS_EMPTY_RUNQ(ERTS_DIRTY_CPU_RUNQ)
- | ERTS_EMPTY_RUNQ(ERTS_DIRTY_IO_RUNQ));
+ || ERTS_EMPTY_RUNQ(ERTS_DIRTY_IO_RUNQ));
}
#define ERTS_MSB_NONE_PRIO_BIT PORT_BIT
@@ -8665,6 +8665,7 @@ sched_thread_func(void *vesdp)
erts_ets_sched_spec_data_init(esdp);
erts_utils_sched_spec_data_init();
+ erts_nif_sched_init(esdp);
process_main(esdp);
@@ -8715,6 +8716,8 @@ sched_dirty_cpu_thread_func(void *vesdp)
erts_proc_lock_prepare_proc_lock_waiter();
+ erts_nif_sched_init(esdp);
+
erts_dirty_process_main(esdp);
/* No schedulers should *ever* terminate */
erts_exit(ERTS_ABORT_EXIT,
@@ -8763,6 +8766,8 @@ sched_dirty_io_thread_func(void *vesdp)
erts_proc_lock_prepare_proc_lock_waiter();
+ erts_nif_sched_init(esdp);
+
erts_dirty_process_main(esdp);
/* No schedulers should *ever* terminate */
erts_exit(ERTS_ABORT_EXIT,
@@ -8776,7 +8781,7 @@ erts_start_schedulers(void)
{
ethr_tid tid;
int res = 0;
- char name[32];
+ char name[ETHR_THR_NAME_MAX + 1];
ethr_thr_opts opts = ETHR_THR_OPTS_DEFAULT_INITER;
int ix;
@@ -8786,7 +8791,7 @@ erts_start_schedulers(void)
if (erts_runq_supervision_interval) {
opts.suggested_stack_size = 16;
- erts_snprintf(opts.name, sizeof(name), "runq_supervisor");
+ erts_snprintf(opts.name, sizeof(name), "erts_runq_sup");
erts_atomic_init_nob(&runq_supervisor_sleeping, 0);
if (0 != ethr_event_init(&runq_supervision_event))
erts_exit(ERTS_ABORT_EXIT, "Failed to create run-queue supervision event\n");
@@ -8807,7 +8812,7 @@ erts_start_schedulers(void)
for (ix = 0; ix < erts_no_schedulers; ix++) {
ErtsSchedulerData *esdp = ERTS_SCHEDULER_IX(ix);
ASSERT(ix == esdp->no - 1);
- erts_snprintf(opts.name, sizeof(name), "%lu_scheduler", ix + 1);
+ erts_snprintf(opts.name, sizeof(name), "erts_sched_%d", ix + 1);
res = ethr_thr_create(&esdp->tid, sched_thread_func, (void*)esdp, &opts);
if (res != 0) {
erts_exit(ERTS_ABORT_EXIT, "Failed to create scheduler thread %d, error = %d\n", ix, res);
@@ -8821,7 +8826,7 @@ erts_start_schedulers(void)
{
for (ix = 0; ix < erts_no_dirty_cpu_schedulers; ix++) {
ErtsSchedulerData *esdp = ERTS_DIRTY_CPU_SCHEDULER_IX(ix);
- erts_snprintf(opts.name, sizeof(name), "%d_dirty_cpu_scheduler", ix + 1);
+ erts_snprintf(opts.name, sizeof(name), "erts_dcpus_%d", ix + 1);
opts.suggested_stack_size = erts_dcpu_sched_thread_suggested_stack_size;
res = ethr_thr_create(&esdp->tid,sched_dirty_cpu_thread_func,(void*)esdp,&opts);
if (res != 0)
@@ -8829,7 +8834,7 @@ erts_start_schedulers(void)
}
for (ix = 0; ix < erts_no_dirty_io_schedulers; ix++) {
ErtsSchedulerData *esdp = ERTS_DIRTY_IO_SCHEDULER_IX(ix);
- erts_snprintf(opts.name, sizeof(name), "%d_dirty_io_scheduler", ix + 1);
+ erts_snprintf(opts.name, sizeof(name), "erts_dios_%d", ix + 1);
opts.suggested_stack_size = erts_dio_sched_thread_suggested_stack_size;
res = ethr_thr_create(&esdp->tid,sched_dirty_io_thread_func,(void*)esdp,&opts);
if (res != 0)
@@ -8840,7 +8845,7 @@ erts_start_schedulers(void)
ix = 0;
while (ix < erts_no_aux_work_threads) {
int id = ix == 0 ? 1 : ix + 1 - (int) erts_no_schedulers;
- erts_snprintf(opts.name, sizeof(name), "%d_aux", id);
+ erts_snprintf(opts.name, sizeof(name), "erts_aux_%d", id);
res = ethr_thr_create(&tid, aux_thread, (void *) (Sint) ix, &opts);
if (res != 0)
@@ -8867,7 +8872,7 @@ erts_start_schedulers(void)
bpt->blocked = 0;
bpt->id = ix;
- erts_snprintf(opts.name, sizeof(name), "%d_poller", ix);
+ erts_snprintf(opts.name, sizeof(name), "erts_poll_%d", ix);
res = ethr_thr_create(&tid, poll_thread, (void*) bpt, &opts);
if (res != 0)
@@ -13065,7 +13070,7 @@ delete_process(Process* p)
{
ErtsPSD *psd;
struct saved_calls *scb;
- process_breakpoint_time_t *pbt;
+ process_breakpoint_trace_t *pbt;
Uint32 block_rla_ref = (Uint32) (Uint) p->u.terminate;
VERBOSE(DEBUG_PROCESSES, ("Removing process: %T\n",p->common.id));
@@ -13088,6 +13093,9 @@ delete_process(Process* p)
pbt = ERTS_PROC_SET_CALL_TIME(p, NULL);
if (pbt)
erts_free(ERTS_ALC_T_BPD, (void *) pbt);
+ pbt = ERTS_PROC_SET_CALL_MEMORY(p, NULL);
+ if (pbt)
+ erts_free(ERTS_ALC_T_BPD, (void *) pbt);
erts_destroy_nfunc(p);
@@ -14858,6 +14866,7 @@ void erts_halt(int code)
ERTS_RUNQ_FLGS_SET(ERTS_DIRTY_CPU_RUNQ, ERTS_RUNQ_FLG_HALTING);
ERTS_RUNQ_FLGS_SET(ERTS_DIRTY_IO_RUNQ, ERTS_RUNQ_FLG_HALTING);
erts_halt_code = code;
+ erts_nif_notify_halt();
}
}
diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h
index ac70260d0d..a0fe781bb1 100644
--- a/erts/emulator/beam/erl_process.h
+++ b/erts/emulator/beam/erl_process.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -728,7 +728,10 @@ struct ErtsSchedulerData_ {
ErtsSchedWallTime sched_wall_time;
ErtsGCInfo gc_info;
ErtsPortTaskHandle nosuspend_port_task_handle;
- ErtsEtsTables ets_tables;
+ union {
+ ErtsEtsTables ets_tables;
+ erts_atomic32_t dirty_nif_halt_info;
+ } u;
#ifdef ERTS_DO_VERIFY_UNUSED_TEMP_ALLOC
erts_alloc_verify_func_t verify_unused_temp_alloc;
Allctr_t *verify_unused_temp_alloc_data;
@@ -868,9 +871,10 @@ erts_reset_max_len(ErtsRunQueue *rq, ErtsRunQueueInfo *rqi)
#define ERTS_PSD_ETS_OWNED_TABLES 6
#define ERTS_PSD_ETS_FIXED_TABLES 7
#define ERTS_PSD_DIST_ENTRY 8
-#define ERTS_PSD_PENDING_SUSPEND 9 /* keep last... */
+#define ERTS_PSD_PENDING_SUSPEND 9
+#define ERTS_PSD_CALL_MEMORY_BP 10
-#define ERTS_PSD_SIZE 10
+#define ERTS_PSD_SIZE 11
typedef struct {
void *data[ERTS_PSD_SIZE];
@@ -990,19 +994,15 @@ typedef struct ErtsProcSysTaskQs_ ErtsProcSysTaskQs;
# define MSO(p) (p)->off_heap
# define MIN_HEAP_SIZE(p) (p)->min_heap_size
-# define MIN_VHEAP_SIZE(p) (p)->min_vheap_size
-# define BIN_VHEAP_SZ(p) (p)->bin_vheap_sz
-# define BIN_OLD_VHEAP_SZ(p) (p)->bin_old_vheap_sz
-# define BIN_OLD_VHEAP(p) (p)->bin_old_vheap
-
-# define MAX_HEAP_SIZE_GET(p) ((p)->max_heap_size >> 2)
-# define MAX_HEAP_SIZE_SET(p, sz) ((p)->max_heap_size = ((sz) << 2) | \
+# define MAX_HEAP_SIZE_GET(p) ((p)->max_heap_size >> 3)
+# define MAX_HEAP_SIZE_SET(p, sz) ((p)->max_heap_size = ((sz) << 3) | \
MAX_HEAP_SIZE_FLAGS_GET(p))
-# define MAX_HEAP_SIZE_FLAGS_GET(p) ((p)->max_heap_size & 0x3)
+# define MAX_HEAP_SIZE_FLAGS_GET(p) ((p)->max_heap_size & 0x7)
# define MAX_HEAP_SIZE_FLAGS_SET(p, flags) ((p)->max_heap_size = flags | \
- ((p)->max_heap_size & ~0x3))
+ ((p)->max_heap_size & ~0x7))
# define MAX_HEAP_SIZE_KILL 1
# define MAX_HEAP_SIZE_LOG 2
+# define MAX_HEAP_SIZE_INCLUDE_OH_BINS 4
struct process {
ErtsPTabElementCommon common; /* *Need* to be first in struct */
@@ -2234,9 +2234,9 @@ erts_psd_set(Process *p, int ix, void *data)
((struct saved_calls *) erts_psd_set((P), ERTS_PSD_SAVED_CALLS_BUF, (void *) (SCB)))
#define ERTS_PROC_GET_CALL_TIME(P) \
- ((process_breakpoint_time_t *) erts_psd_get((P), ERTS_PSD_CALL_TIME_BP))
+ ((process_breakpoint_trace_t *) erts_psd_get((P), ERTS_PSD_CALL_TIME_BP))
#define ERTS_PROC_SET_CALL_TIME(P, PBT) \
- ((process_breakpoint_time_t *) erts_psd_set((P), ERTS_PSD_CALL_TIME_BP, (void *) (PBT)))
+ ((process_breakpoint_trace_t *) erts_psd_set((P), ERTS_PSD_CALL_TIME_BP, (void *) (PBT)))
#define ERTS_PROC_GET_DELAYED_GC_TASK_QS(P) \
((ErtsProcSysTaskQs *) erts_psd_get((P), ERTS_PSD_DELAYED_GC_TASK_QS))
@@ -2258,6 +2258,11 @@ erts_psd_set(Process *p, int ix, void *data)
#define ERTS_PROC_SET_PENDING_SUSPEND(P, PS) \
((void *) erts_psd_set((P), ERTS_PSD_PENDING_SUSPEND, (void *) (PS)))
+#define ERTS_PROC_GET_CALL_MEMORY(P) \
+ ((process_breakpoint_trace_t *) erts_psd_get((P), ERTS_PSD_CALL_MEMORY_BP))
+#define ERTS_PROC_SET_CALL_MEMORY(P, PBT) \
+ ((process_breakpoint_trace_t *) erts_psd_set((P), ERTS_PSD_CALL_MEMORY_BP, (void *) (PBT)))
+
ERTS_GLB_INLINE Eterm erts_proc_get_error_handler(Process *p);
ERTS_GLB_INLINE Eterm erts_proc_set_error_handler(Process *p, Eterm handler);
diff --git a/erts/emulator/beam/erl_ptab.c b/erts/emulator/beam/erl_ptab.c
index 8f28ed7f20..3740886091 100644
--- a/erts/emulator/beam/erl_ptab.c
+++ b/erts/emulator/beam/erl_ptab.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2012-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2012-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -391,7 +391,7 @@ erts_ptab_init_table(ErtsPTab *ptab,
tab_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic_t));
alloc_sz = tab_sz;
if (!legacy)
- alloc_sz += ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic32_t));
+ alloc_sz += ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic_t));
ptab->r.o.tab = erts_alloc_permanent_cache_aligned(atype, alloc_sz);
tab_end = ((char *) ptab->r.o.tab) + tab_sz;
tab_entry = ptab->r.o.tab;
@@ -429,11 +429,11 @@ erts_ptab_init_table(ErtsPTab *ptab,
}
else {
- tab_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic32_t));
- ptab->r.o.free_id_data = (erts_atomic32_t *) tab_end;
+ tab_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic_t));
+ ptab->r.o.free_id_data = (erts_atomic_t *) tab_end;
tab_cache_lines = tab_sz/ERTS_CACHE_LINE_SIZE;
- ix_per_cache_line = (ERTS_CACHE_LINE_SIZE/sizeof(erts_atomic32_t));
+ ix_per_cache_line = (ERTS_CACHE_LINE_SIZE/sizeof(erts_atomic_t));
ptab->r.o.dix_cl_mask = tab_cache_lines-1;
ptab->r.o.dix_cl_shift = erts_fit_in_bits_int32(ix_per_cache_line-1);
@@ -448,9 +448,9 @@ erts_ptab_init_table(ErtsPTab *ptab,
ix = 0;
for (cl = 0; cl < tab_cache_lines; cl++) {
for (cli = 0; cli < ix_per_cache_line; cli++) {
- erts_atomic32_init_nob(&ptab->r.o.free_id_data[ix],
- cli*tab_cache_lines+cl);
- ASSERT(erts_atomic32_read_nob(&ptab->r.o.free_id_data[ix]) != ptab->r.o.invalid_data);
+ erts_atomic_init_nob(&ptab->r.o.free_id_data[ix],
+ cli*tab_cache_lines+cl);
+ ASSERT(erts_atomic_read_nob(&ptab->r.o.free_id_data[ix]) != ptab->r.o.invalid_data);
ix++;
}
}
@@ -500,7 +500,8 @@ erts_ptab_new_element(ErtsPTab *ptab,
void *init_arg,
void (*init_ptab_el)(void *, Eterm))
{
- Uint32 pix, ix, data;
+ Uint32 pix, ix;
+ Uint data;
erts_aint32_t count;
erts_aint_t invalid = (erts_aint_t) ptab->r.o.invalid_element;
@@ -532,8 +533,8 @@ erts_ptab_new_element(ErtsPTab *ptab,
ix = (Uint32) erts_atomic32_inc_read_acqb(&ptab->vola.tile.aid_ix);
ix = ix_to_free_id_data_ix(ptab, ix);
- data = erts_atomic32_xchg_nob(&ptab->r.o.free_id_data[ix],
- (erts_aint32_t)ptab->r.o.invalid_data);
+ data = erts_atomic_xchg_nob(&ptab->r.o.free_id_data[ix],
+ (erts_aint_t) ptab->r.o.invalid_data);
}while ((Eterm)data == ptab->r.o.invalid_data);
init_ptab_el(init_arg, (Eterm) data);
@@ -673,7 +674,8 @@ erts_ptab_delete_element(ErtsPTab *ptab,
ErtsPTabElementCommon *ptab_el)
{
int maybe_save;
- Uint32 pix, ix, data;
+ Uint32 pix, ix;
+ Uint data;
pix = erts_ptab_id2pix(ptab, ptab_el->id);
@@ -690,14 +692,14 @@ erts_ptab_delete_element(ErtsPTab *ptab,
erts_atomic_set_relb(&ptab->r.o.tab[pix], ERTS_AINT_NULL);
if (ptab->r.o.free_id_data) {
- Uint32 prev_data;
+ Uint prev_data;
/* Next data for this slot... */
- data = (Uint32) erts_ptab_id2data(ptab, ptab_el->id);
+ data = erts_ptab_id2data(ptab, ptab_el->id);
data += ptab->r.o.max;
- data &= ~(~((Uint32) 0) << ERTS_PTAB_ID_DATA_SIZE);
+ data &= ~(~UWORD_CONSTANT(0) << ERTS_PTAB_ID_DATA_SIZE);
if (data == ptab->r.o.invalid_data) { /* make sure not invalid */
data += ptab->r.o.max;
- data &= ~(~((Uint32) 0) << ERTS_PTAB_ID_DATA_SIZE);
+ data &= ~(~UWORD_CONSTANT(0) << ERTS_PTAB_ID_DATA_SIZE);
}
ASSERT(data != ptab->r.o.invalid_data);
ASSERT(pix == erts_ptab_data2pix(ptab, data));
@@ -706,9 +708,9 @@ erts_ptab_delete_element(ErtsPTab *ptab,
ix = (Uint32) erts_atomic32_inc_read_relb(&ptab->vola.tile.fid_ix);
ix = ix_to_free_id_data_ix(ptab, ix);
- prev_data = erts_atomic32_cmpxchg_nob(&ptab->r.o.free_id_data[ix],
- data,
- ptab->r.o.invalid_data);
+ prev_data = erts_atomic_cmpxchg_nob(&ptab->r.o.free_id_data[ix],
+ data,
+ ptab->r.o.invalid_data);
}while ((Eterm)prev_data != ptab->r.o.invalid_data);
}
@@ -1326,14 +1328,15 @@ static void assert_ptab_consistency(ErtsPTab *ptab)
{
#ifdef DEBUG
if (ptab->r.o.free_id_data) {
- Uint32 ix, pix, data;
+ Uint32 ix, pix;
+ Uint data;
int free_pids = 0;
int null_slots = 0;
for (ix=0; ix < ptab->r.o.max; ix++) {
- if (erts_atomic32_read_nob(&ptab->r.o.free_id_data[ix]) != ptab->r.o.invalid_data) {
+ if (erts_atomic_read_nob(&ptab->r.o.free_id_data[ix]) != ptab->r.o.invalid_data) {
++free_pids;
- data = erts_atomic32_read_nob(&ptab->r.o.free_id_data[ix]);
+ data = erts_atomic_read_nob(&ptab->r.o.free_id_data[ix]);
pix = erts_ptab_data2pix(ptab, (Eterm) data);
ASSERT(erts_ptab_pix2intptr_nob(ptab, pix) == ERTS_AINT_NULL);
}
@@ -1363,7 +1366,8 @@ erts_ptab_test_next_id(ErtsPTab *ptab, int set, Uint next)
Uint32 id_ix, dix;
if (set) {
- Uint32 i, max_ix, num, stop_id_ix;
+ Uint32 i, max_ix, stop_id_ix;
+ Uint num;
max_ix = ptab->r.o.max - 1;
num = next;
id_ix = (Uint32) erts_atomic32_read_nob(&ptab->vola.tile.aid_ix);
@@ -1371,16 +1375,16 @@ erts_ptab_test_next_id(ErtsPTab *ptab, int set, Uint next)
for (i=0; i <= max_ix; ++i) {
Uint32 pix;
++num;
- num &= ~(~((Uint32) 0) << ERTS_PTAB_ID_DATA_SIZE);
+ num &= ~(~UWORD_CONSTANT(0) << ERTS_PTAB_ID_DATA_SIZE);
if (num == ptab->r.o.invalid_data) {
num += ptab->r.o.max;
- num &= ~(~((Uint32) 0) << ERTS_PTAB_ID_DATA_SIZE);
+ num &= ~(~UWORD_CONSTANT(0) << ERTS_PTAB_ID_DATA_SIZE);
}
pix = erts_ptab_data2pix(ptab, num);
if (ERTS_AINT_NULL == erts_ptab_pix2intptr_nob(ptab, pix)) {
++id_ix;
dix = ix_to_free_id_data_ix(ptab, id_ix);
- erts_atomic32_set_nob(&ptab->r.o.free_id_data[dix], num);
+ erts_atomic_set_nob(&ptab->r.o.free_id_data[dix], num);
ASSERT(pix == erts_ptab_data2pix(ptab, num));
}
}
@@ -1393,13 +1397,13 @@ erts_ptab_test_next_id(ErtsPTab *ptab, int set, Uint next)
if (id_ix == stop_id_ix)
break;
dix = ix_to_free_id_data_ix(ptab, id_ix);
- erts_atomic32_set_nob(&ptab->r.o.free_id_data[dix],
- ptab->r.o.invalid_data);
+ erts_atomic_set_nob(&ptab->r.o.free_id_data[dix],
+ ptab->r.o.invalid_data);
}
}
id_ix = (Uint32) erts_atomic32_read_nob(&ptab->vola.tile.aid_ix) + 1;
dix = ix_to_free_id_data_ix(ptab, id_ix);
- res = (Sint) erts_atomic32_read_nob(&ptab->r.o.free_id_data[dix]);
+ res = (Sint) erts_atomic_read_nob(&ptab->r.o.free_id_data[dix]);
}
else {
/* Deprecated legacy algorithm... */
diff --git a/erts/emulator/beam/erl_ptab.h b/erts/emulator/beam/erl_ptab.h
index c30a684002..a95b81162a 100644
--- a/erts/emulator/beam/erl_ptab.h
+++ b/erts/emulator/beam/erl_ptab.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2012-2018. All Rights Reserved.
+ * Copyright Ericsson AB 2012-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -101,16 +101,16 @@ typedef struct {
typedef struct {
erts_atomic_t *tab;
- erts_atomic32_t *free_id_data;
+ erts_atomic_t *free_id_data;
Uint32 max;
- Uint32 pix_mask;
- Uint32 pix_cl_mask;
+ Uint pix_mask;
+ Uint pix_cl_mask;
+ Uint pix_cli_mask;
Uint32 pix_cl_shift;
- Uint32 pix_cli_mask;
Uint32 pix_cli_shift;
- Uint32 dix_cl_mask;
+ Uint dix_cl_mask;
+ Uint dix_cli_mask;
Uint32 dix_cl_shift;
- Uint32 dix_cli_mask;
Uint32 dix_cli_shift;
ErtsPTabElementCommon *invalid_element;
Eterm invalid_data;
@@ -146,10 +146,10 @@ typedef struct {
} r;
} ErtsPTab;
-#define ERTS_PTAB_ID_DATA_SIZE 28
+#define ERTS_PTAB_ID_DATA_SIZE (ERTS_SIZEOF_TERM*8 - _TAG_IMMED1_SIZE)
#define ERTS_PTAB_ID_DATA_SHIFT (_TAG_IMMED1_SIZE)
/* ERTS_PTAB_MAX_SIZE must be a power of 2 */
-#define ERTS_PTAB_MAX_SIZE (SWORD_CONSTANT(1) << 27)
+#define ERTS_PTAB_MAX_SIZE (UWORD_CONSTANT(1) << 27)
#if (ERTS_PTAB_MAX_SIZE-1) > MAX_SMALL
# error "The maximum number of processes/ports must fit in a SMALL."
#endif
@@ -173,7 +173,8 @@ typedef struct {
#define ERTS_PTAB_INVALID_ID(TAG) \
((Eterm) \
- ((((1U << ERTS_PTAB_ID_DATA_SIZE) - 1) << ERTS_PTAB_ID_DATA_SHIFT) \
+ ((((UWORD_CONSTANT(1) << ERTS_PTAB_ID_DATA_SIZE) - 1) \
+ << ERTS_PTAB_ID_DATA_SHIFT) \
| (TAG)))
#define erts_ptab_is_valid_id(ID) \
@@ -202,8 +203,8 @@ ERTS_GLB_INLINE erts_interval_t *erts_ptab_interval(ErtsPTab *ptab);
ERTS_GLB_INLINE int erts_ptab_max(ErtsPTab *ptab);
ERTS_GLB_INLINE int erts_ptab_count(ErtsPTab *ptab);
ERTS_GLB_INLINE Uint erts_ptab_pixdata2data(ErtsPTab *ptab, Eterm pixdata);
-ERTS_GLB_INLINE Uint32 erts_ptab_pixdata2pix(ErtsPTab *ptab, Eterm pixdata);
-ERTS_GLB_INLINE Uint32 erts_ptab_data2pix(ErtsPTab *ptab, Eterm data);
+ERTS_GLB_INLINE Uint erts_ptab_pixdata2pix(ErtsPTab *ptab, Eterm pixdata);
+ERTS_GLB_INLINE Uint erts_ptab_data2pix(ErtsPTab *ptab, Eterm data);
ERTS_GLB_INLINE Uint erts_ptab_data2pixdata(ErtsPTab *ptab, Eterm data);
ERTS_GLB_INLINE Eterm erts_ptab_make_id(ErtsPTab *ptab, Eterm data, Eterm tag);
ERTS_GLB_INLINE int erts_ptab_id2pix(ErtsPTab *ptab, Eterm id);
@@ -264,21 +265,21 @@ erts_ptab_count(ErtsPTab *ptab)
ERTS_GLB_INLINE Uint erts_ptab_pixdata2data(ErtsPTab *ptab, Eterm pixdata)
{
- Uint32 data = ((Uint32) pixdata) & ~ptab->r.o.pix_mask;
+ Uint data = ((Uint) pixdata) & ~ptab->r.o.pix_mask;
data |= (pixdata >> ptab->r.o.pix_cl_shift) & ptab->r.o.pix_cl_mask;
data |= (pixdata & ptab->r.o.pix_cli_mask) << ptab->r.o.pix_cli_shift;
return data;
}
-ERTS_GLB_INLINE Uint32 erts_ptab_pixdata2pix(ErtsPTab *ptab, Eterm pixdata)
+ERTS_GLB_INLINE Uint erts_ptab_pixdata2pix(ErtsPTab *ptab, Eterm pixdata)
{
- return ((Uint32) pixdata) & ptab->r.o.pix_mask;
+ return ((Uint) pixdata) & ptab->r.o.pix_mask;
}
-ERTS_GLB_INLINE Uint32 erts_ptab_data2pix(ErtsPTab *ptab, Eterm data)
+ERTS_GLB_INLINE Uint erts_ptab_data2pix(ErtsPTab *ptab, Eterm data)
{
- Uint32 n, pix;
- n = (Uint32) data;
+ Uint n, pix;
+ n = (Uint) data;
pix = ((n & ptab->r.o.pix_cl_mask) << ptab->r.o.pix_cl_shift);
pix += ((n >> ptab->r.o.pix_cli_shift) & ptab->r.o.pix_cli_mask);
ASSERT(0 <= pix && pix < ptab->r.o.max);
@@ -293,43 +294,11 @@ ERTS_GLB_INLINE Uint erts_ptab_data2pixdata(ErtsPTab *ptab, Eterm data)
return pixdata;
}
-#if ERTS_SIZEOF_TERM == 8
-
-ERTS_GLB_INLINE Eterm
-erts_ptab_make_id(ErtsPTab *ptab, Eterm data, Eterm tag)
-{
- HUint huint;
- Uint32 low_data = (Uint32) data;
- low_data &= (1 << ERTS_PTAB_ID_DATA_SIZE) - 1;
- low_data <<= ERTS_PTAB_ID_DATA_SHIFT;
- huint.hval[ERTS_HUINT_HVAL_HIGH] = erts_ptab_data2pix(ptab, data);
- huint.hval[ERTS_HUINT_HVAL_LOW] = low_data | ((Uint32) tag);
- return (Eterm) huint.val;
-}
-
-ERTS_GLB_INLINE int
-erts_ptab_id2pix(ErtsPTab *ptab, Eterm id)
-{
- HUint huint;
- huint.val = id;
- return (int) huint.hval[ERTS_HUINT_HVAL_HIGH];
-}
-
-ERTS_GLB_INLINE Uint
-erts_ptab_id2data(ErtsPTab *ptab, Eterm id)
-{
- HUint huint;
- huint.val = id;
- return (Uint) (huint.hval[ERTS_HUINT_HVAL_LOW] >> ERTS_PTAB_ID_DATA_SHIFT);
-}
-
-#elif ERTS_SIZEOF_TERM == 4
-
ERTS_GLB_INLINE Eterm
erts_ptab_make_id(ErtsPTab *ptab, Eterm data, Eterm tag)
{
Eterm id;
- data &= ((1 << ERTS_PTAB_ID_DATA_SIZE) - 1);
+ data &= ((UWORD_CONSTANT(1) << ERTS_PTAB_ID_DATA_SIZE) - 1);
id = (Eterm) erts_ptab_data2pixdata(ptab, data);
return (id << ERTS_PTAB_ID_DATA_SHIFT) | tag;
}
@@ -350,10 +319,6 @@ erts_ptab_id2data(ErtsPTab *ptab, Eterm id)
return erts_ptab_pixdata2data(ptab, pixdata);
}
-#else
-#error "Unsupported size of term"
-#endif
-
ERTS_GLB_INLINE erts_aint_t erts_ptab_pix2intptr_nob(ErtsPTab *ptab, int ix)
{
ASSERT(0 <= ix && ix < ptab->r.o.max);
diff --git a/erts/emulator/beam/erl_term.h b/erts/emulator/beam/erl_term.h
index 73c155ef27..03211a6acd 100644
--- a/erts/emulator/beam/erl_term.h
+++ b/erts/emulator/beam/erl_term.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2000-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2000-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -179,6 +179,7 @@ struct erl_node_; /* Declared in erl_node_tables.h */
#endif
#define is_not_both_immed(x,y) (!is_both_immed((x),(y)))
+#define is_zero_sized(x) (is_immed(x) || (x) == ERTS_GLOBAL_LIT_EMPTY_TUPLE)
/* boxed object access methods */
@@ -585,9 +586,9 @@ _ET_DECLARE_CHECKED(Eterm*,tuple_val,Wterm)
*/
#define _PID_SER_SIZE (_PID_DATA_SIZE - _PID_NUM_SIZE)
-#define _PID_NUM_SIZE 15
+#define _PID_NUM_SIZE 28
-#define _PID_DATA_SIZE 28
+#define _PID_DATA_SIZE (ERTS_SIZEOF_TERM*8 - _TAG_IMMED1_SIZE)
#define _PID_DATA_SHIFT (_TAG_IMMED1_SIZE)
#define _GET_PID_DATA(X) _GETBITS((X),_PID_DATA_SHIFT,_PID_DATA_SIZE)
@@ -635,7 +636,7 @@ _ET_DECLARE_CHECKED(struct erl_node_*,internal_pid_node,Eterm)
*/
#define _PORT_NUM_SIZE _PORT_DATA_SIZE
-#define _PORT_DATA_SIZE 28
+#define _PORT_DATA_SIZE (ERTS_SIZEOF_TERM*8 - _TAG_IMMED1_SIZE)
#define _PORT_DATA_SHIFT (_TAG_IMMED1_SIZE)
#define _GET_PORT_DATA(X) _GETBITS((X),_PORT_DATA_SHIFT,_PORT_DATA_SIZE)
@@ -1267,15 +1268,12 @@ _ET_DECLARE_CHECKED(struct erl_node_*,external_ref_node,Eterm)
#define MAP_SZ(sz) (MAP_HEADER_FLATMAP_SZ + 2*sz + 1)
-#define MAP0_SZ MAP_SZ(0)
#define MAP1_SZ MAP_SZ(1)
#define MAP2_SZ MAP_SZ(2)
#define MAP3_SZ MAP_SZ(3)
#define MAP4_SZ MAP_SZ(4)
#define MAP5_SZ MAP_SZ(5)
-#define MAP0(hp) \
- (MAP_HEADER(hp, 0, TUPLE0(hp+MAP_HEADER_FLATMAP_SZ)), \
- make_flatmap(hp))
+
#define MAP1(hp, k1, v1) \
(MAP_HEADER(hp, 1, TUPLE1(hp+1+MAP_HEADER_FLATMAP_SZ, k1)), \
(hp)[MAP_HEADER_FLATMAP_SZ+0] = v1, \
diff --git a/erts/emulator/beam/erl_term_hashing.c b/erts/emulator/beam/erl_term_hashing.c
new file mode 100644
index 0000000000..848757c2f2
--- /dev/null
+++ b/erts/emulator/beam/erl_term_hashing.c
@@ -0,0 +1,2014 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022-2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "sys.h"
+#include "global.h"
+#include "erl_term_hashing.h"
+
+#include "big.h"
+#include "bif.h"
+#include "erl_map.h"
+#include "erl_binary.h"
+#include "erl_bits.h"
+
+#ifdef ERL_INTERNAL_HASH_CRC32C
+# if defined(__x86_64__)
+# include <immintrin.h>
+# elif defined(__aarch64__)
+# include <arm_acle.h>
+# endif
+#endif
+
+/* *\
+ * *
+\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/* make a hash index from an erlang term */
+
+/*
+** There are two hash functions.
+**
+** make_hash: A hash function that will give the same values for the same
+** terms regardless of the internal representation. Small integers are
+** hashed using the same algorithm as bignums and bignums are hashed
+** independent of the CPU endianess.
+** Make_hash also hashes pids, ports and references like 32 bit numbers
+** (but with different constants).
+** make_hash() is called from the bif erlang:phash/2
+**
+** The idea behind the hash algorithm is to produce values suitable for
+** linear dynamic hashing. We cannot choose the range at all while hashing
+** (it's not even supplied to the hashing functions). The good old algorithm
+** [H = H*C+X mod M, where H is the hash value, C is a "random" constant(or M),
+** M is the range, preferably a prime, and X is each byte value] is therefore
+** modified to:
+** H = H*C+X mod 2^32, where C is a large prime. This gives acceptable
+** "spreading" of the hashes, so that later modulo calculations also will give
+** acceptable "spreading" in the range.
+** We really need to hash on bytes, otherwise the
+** upper bytes of a word will be less significant than the lower ones. That's
+** not acceptable at all. For internal use one could maybe optimize by using
+** another hash function, that is less strict but faster. That is, however, not
+** implemented.
+**
+** Short semi-formal description of make_hash:
+**
+** In make_hash, the number N is treated like this:
+** Abs(N) is hashed bytewise with the least significant byte, B(0), first.
+** The number of bytes (J) to calculate hash on in N is
+** (the number of _32_ bit words needed to store the unsigned
+** value of abs(N)) * 4.
+** X = FUNNY_NUMBER2
+** If N < 0, Y = FUNNY_NUMBER4 else Y = FUNNY_NUMBER3.
+** The hash value is Y*h(J) mod 2^32 where h(J) is calculated like
+** h(0) = <initial hash>
+** h(i) = h(i-1)*X + B(i-1)
+** The above should hold regardless of internal representation.
+** Pids are hashed like small numbers but with different constants, as are
+** ports.
+** References are hashed like ports but only on the least significant byte.
+** Binaries are hashed on all bytes (not on the 15 first as in
+** make_broken_hash()).
+** Bytes in lists (possibly text strings) use a simpler multiplication inlined
+** in the handling of lists, that is an optimization.
+** Everything else is like in the old hash (make_broken_hash()).
+**
+** make_hash2() is faster than make_hash, in particular for bignums
+** and binaries, and produces better hash values.
+*/
+
+/* some prime numbers just above 2 ^ 28 */
+
+#define FUNNY_NUMBER1 268440163
+#define FUNNY_NUMBER2 268439161
+#define FUNNY_NUMBER3 268435459
+#define FUNNY_NUMBER4 268436141
+#define FUNNY_NUMBER5 268438633
+#define FUNNY_NUMBER6 268437017
+#define FUNNY_NUMBER7 268438039
+#define FUNNY_NUMBER8 268437511
+#define FUNNY_NUMBER9 268439627
+#define FUNNY_NUMBER10 268440479
+#define FUNNY_NUMBER11 268440577
+#define FUNNY_NUMBER12 268440581
+#define FUNNY_NUMBER13 268440593
+#define FUNNY_NUMBER14 268440611
+
+static Uint32
+hash_binary_bytes(Eterm bin, Uint sz, Uint32 hash)
+{
+ byte* ptr;
+ Uint bitoffs;
+ Uint bitsize;
+
+ ERTS_GET_BINARY_BYTES(bin, ptr, bitoffs, bitsize);
+ if (bitoffs == 0) {
+ while (sz--) {
+ hash = hash*FUNNY_NUMBER1 + *ptr++;
+ }
+ if (bitsize > 0) {
+ byte b = *ptr;
+
+ b >>= 8 - bitsize;
+ hash = (hash*FUNNY_NUMBER1 + b) * FUNNY_NUMBER12 + bitsize;
+ }
+ } else {
+ Uint previous = *ptr++;
+ Uint b;
+ Uint lshift = bitoffs;
+ Uint rshift = 8 - lshift;
+
+ while (sz--) {
+ b = (previous << lshift) & 0xFF;
+ previous = *ptr++;
+ b |= previous >> rshift;
+ hash = hash*FUNNY_NUMBER1 + b;
+ }
+ if (bitsize > 0) {
+ b = (previous << lshift) & 0xFF;
+ previous = *ptr++;
+ b |= previous >> rshift;
+
+ b >>= 8 - bitsize;
+ hash = (hash*FUNNY_NUMBER1 + b) * FUNNY_NUMBER12 + bitsize;
+ }
+ }
+ return hash;
+}
+
+Uint32 make_hash(Eterm term_arg)
+{
+ DECLARE_WSTACK(stack);
+ Eterm term = term_arg;
+ Eterm hash = 0;
+ unsigned op;
+
+#define MAKE_HASH_TUPLE_OP (FIRST_VACANT_TAG_DEF)
+#define MAKE_HASH_TERM_ARRAY_OP (FIRST_VACANT_TAG_DEF+1)
+#define MAKE_HASH_CDR_PRE_OP (FIRST_VACANT_TAG_DEF+2)
+#define MAKE_HASH_CDR_POST_OP (FIRST_VACANT_TAG_DEF+3)
+
+ /*
+ ** Convenience macro for calculating a bytewise hash on an unsigned 32 bit
+ ** integer.
+ ** If the endianess is known, we could be smarter here,
+ ** but that gives no significant speedup (on a sparc at least)
+ */
+#define UINT32_HASH_STEP(Expr, Prime1) \
+ do { \
+ Uint32 x = (Uint32) (Expr); \
+ hash = \
+ (((((hash)*(Prime1) + (x & 0xFF)) * (Prime1) + \
+ ((x >> 8) & 0xFF)) * (Prime1) + \
+ ((x >> 16) & 0xFF)) * (Prime1) + \
+ (x >> 24)); \
+ } while(0)
+
+#define UINT32_HASH_RET(Expr, Prime1, Prime2) \
+ UINT32_HASH_STEP(Expr, Prime1); \
+ hash = hash * (Prime2); \
+ break
+
+
+ /*
+ * Significant additions needed for real 64 bit port with larger fixnums.
+ */
+
+ /*
+ * Note, for the simple 64bit port, not utilizing the
+ * larger word size this function will work without modification.
+ */
+tail_recur:
+ op = tag_val_def(term);
+
+ for (;;) {
+ switch (op) {
+ case NIL_DEF:
+ hash = hash*FUNNY_NUMBER3 + 1;
+ break;
+ case ATOM_DEF:
+ hash = hash*FUNNY_NUMBER1 +
+ (atom_tab(atom_val(term))->slot.bucket.hvalue);
+ break;
+ case SMALL_DEF:
+ {
+ Sint y1 = signed_val(term);
+ Uint y2 = y1 < 0 ? -(Uint)y1 : y1;
+
+ UINT32_HASH_STEP(y2, FUNNY_NUMBER2);
+#if defined(ARCH_64)
+ if (y2 >> 32)
+ UINT32_HASH_STEP(y2 >> 32, FUNNY_NUMBER2);
+#endif
+ hash *= (y1 < 0 ? FUNNY_NUMBER4 : FUNNY_NUMBER3);
+ break;
+ }
+ case BINARY_DEF:
+ {
+ Uint sz = binary_size(term);
+
+ hash = hash_binary_bytes(term, sz, hash);
+ hash = hash*FUNNY_NUMBER4 + sz;
+ break;
+ }
+ case FUN_DEF:
+ {
+ ErlFunThing* funp = (ErlFunThing *) fun_val(term);
+
+ if (is_local_fun(funp)) {
+
+ ErlFunEntry* fe = funp->entry.fun;
+ Uint num_free = funp->num_free;
+
+ hash = hash * FUNNY_NUMBER10 + num_free;
+ hash = hash*FUNNY_NUMBER1 +
+ (atom_tab(atom_val(fe->module))->slot.bucket.hvalue);
+ hash = hash*FUNNY_NUMBER2 + fe->index;
+ hash = hash*FUNNY_NUMBER2 + fe->old_uniq;
+
+ if (num_free > 0) {
+ if (num_free > 1) {
+ WSTACK_PUSH3(stack, (UWord) &funp->env[1],
+ (num_free-1), MAKE_HASH_TERM_ARRAY_OP);
+ }
+
+ term = funp->env[0];
+ goto tail_recur;
+ }
+ } else {
+ const ErtsCodeMFA *mfa = &funp->entry.exp->info.mfa;
+
+ ASSERT(is_external_fun(funp) && funp->next == NULL);
+
+ hash = hash * FUNNY_NUMBER11 + mfa->arity;
+ hash = hash*FUNNY_NUMBER1 +
+ (atom_tab(atom_val(mfa->module))->slot.bucket.hvalue);
+ hash = hash*FUNNY_NUMBER1 +
+ (atom_tab(atom_val(mfa->function))->slot.bucket.hvalue);
+ }
+ break;
+ }
+ case PID_DEF:
+ /* only 15 bits... */
+ UINT32_HASH_RET(internal_pid_number(term),FUNNY_NUMBER5,FUNNY_NUMBER6);
+ case EXTERNAL_PID_DEF:
+ /* only 15 bits... */
+ UINT32_HASH_RET(external_pid_number(term),FUNNY_NUMBER5,FUNNY_NUMBER6);
+ case PORT_DEF:
+ case EXTERNAL_PORT_DEF: {
+ Uint64 number = port_number(term);
+ Uint32 low = (Uint32) (number & 0xffffffff);
+ Uint32 high = (Uint32) ((number >> 32) & 0xffffffff);
+ if (high)
+ UINT32_HASH_STEP(high, FUNNY_NUMBER11);
+ UINT32_HASH_RET(low,FUNNY_NUMBER9,FUNNY_NUMBER10);
+ }
+ case REF_DEF:
+ UINT32_HASH_RET(internal_ref_numbers(term)[0],FUNNY_NUMBER9,FUNNY_NUMBER10);
+ case EXTERNAL_REF_DEF:
+ UINT32_HASH_RET(external_ref_numbers(term)[0],FUNNY_NUMBER9,FUNNY_NUMBER10);
+ case FLOAT_DEF:
+ {
+ FloatDef ff;
+ GET_DOUBLE(term, ff);
+ if (ff.fd == 0.0f) {
+ /* ensure positive 0.0 */
+ ff.fd = erts_get_positive_zero_float();
+ }
+ hash = hash*FUNNY_NUMBER6 + (ff.fw[0] ^ ff.fw[1]);
+ break;
+ }
+ case MAKE_HASH_CDR_PRE_OP:
+ term = (Eterm) WSTACK_POP(stack);
+ if (is_not_list(term)) {
+ WSTACK_PUSH(stack, (UWord) MAKE_HASH_CDR_POST_OP);
+ goto tail_recur;
+ }
+ /* fall through */
+ case LIST_DEF:
+ {
+ Eterm* list = list_val(term);
+ while(is_byte(*list)) {
+ /* Optimization for strings.
+ ** Note that this hash is different from a 'small' hash,
+ ** as multiplications on a Sparc is so slow.
+ */
+ hash = hash*FUNNY_NUMBER2 + unsigned_val(*list);
+
+ if (is_not_list(CDR(list))) {
+ WSTACK_PUSH(stack, MAKE_HASH_CDR_POST_OP);
+ term = CDR(list);
+ goto tail_recur;
+ }
+ list = list_val(CDR(list));
+ }
+ WSTACK_PUSH2(stack, CDR(list), MAKE_HASH_CDR_PRE_OP);
+ term = CAR(list);
+ goto tail_recur;
+ }
+ case MAKE_HASH_CDR_POST_OP:
+ hash *= FUNNY_NUMBER8;
+ break;
+
+ case BIG_DEF:
+ /* Note that this is the exact same thing as the hashing of smalls.*/
+ {
+ Eterm* ptr = big_val(term);
+ Uint n = BIG_SIZE(ptr);
+ Uint k = n-1;
+ ErtsDigit d;
+ int is_neg = BIG_SIGN(ptr);
+ Uint i;
+ int j;
+
+ for (i = 0; i < k; i++) {
+ d = BIG_DIGIT(ptr, i);
+ for(j = 0; j < sizeof(ErtsDigit); ++j) {
+ hash = (hash*FUNNY_NUMBER2) + (d & 0xff);
+ d >>= 8;
+ }
+ }
+ d = BIG_DIGIT(ptr, k);
+ k = sizeof(ErtsDigit);
+#if defined(ARCH_64)
+ if (!(d >> 32))
+ k /= 2;
+#endif
+ for(j = 0; j < (int)k; ++j) {
+ hash = (hash*FUNNY_NUMBER2) + (d & 0xff);
+ d >>= 8;
+ }
+ hash *= is_neg ? FUNNY_NUMBER4 : FUNNY_NUMBER3;
+ break;
+ }
+ case MAP_DEF:
+ hash = hash*FUNNY_NUMBER13 + FUNNY_NUMBER14 + make_hash2(term);
+ break;
+ case TUPLE_DEF:
+ {
+ Eterm* ptr = tuple_val(term);
+ Uint arity = arityval(*ptr);
+
+ WSTACK_PUSH3(stack, (UWord) arity, (UWord)(ptr+1), (UWord) arity);
+ op = MAKE_HASH_TUPLE_OP;
+ }/*fall through*/
+ case MAKE_HASH_TUPLE_OP:
+ case MAKE_HASH_TERM_ARRAY_OP:
+ {
+ Uint i = (Uint) WSTACK_POP(stack);
+ Eterm* ptr = (Eterm*) WSTACK_POP(stack);
+ if (i != 0) {
+ term = *ptr;
+ WSTACK_PUSH3(stack, (UWord)(ptr+1), (UWord) i-1, (UWord) op);
+ goto tail_recur;
+ }
+ if (op == MAKE_HASH_TUPLE_OP) {
+ Uint32 arity = (Uint32) WSTACK_POP(stack);
+ hash = hash*FUNNY_NUMBER9 + arity;
+ }
+ break;
+ }
+
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_hash(0x%X,0x%X)\n", term, op);
+ return 0;
+ }
+ if (WSTACK_ISEMPTY(stack)) break;
+ op = WSTACK_POP(stack);
+ }
+ DESTROY_WSTACK(stack);
+ return hash;
+
+#undef MAKE_HASH_TUPLE_OP
+#undef MAKE_HASH_TERM_ARRAY_OP
+#undef MAKE_HASH_CDR_PRE_OP
+#undef MAKE_HASH_CDR_POST_OP
+#undef UINT32_HASH_STEP
+#undef UINT32_HASH_RET
+}
+
+/* Hash function suggested by Bob Jenkins. */
+#define MIX(a,b,c) \
+ do { \
+ a -= b; a -= c; a ^= (c>>13); \
+ b -= c; b -= a; b ^= (a<<8); \
+ c -= a; c -= b; c ^= (b>>13); \
+ a -= b; a -= c; a ^= (c>>12); \
+ b -= c; b -= a; b ^= (a<<16); \
+ c -= a; c -= b; c ^= (b>>5); \
+ a -= b; a -= c; a ^= (c>>3); \
+ b -= c; b -= a; b ^= (a<<10); \
+ c -= a; c -= b; c ^= (b>>15); \
+ } while(0)
+
+#define HCONST 0x9e3779b9UL /* the golden ratio; an arbitrary value */
+
+#define BLOCK_HASH_BYTES_PER_ITER 12
+
+/* The three functions below are separated into different functions even
+ though they are always used together to make trapping and handling
+ of unaligned binaries easier. Examples of how they are used can be
+ found in block_hash and make_hash2_helper.*/
+static ERTS_INLINE
+void block_hash_setup(Uint32 initval,
+ ErtsBlockHashHelperCtx* ctx /* out parameter */)
+{
+ ctx->a = ctx->b = HCONST;
+ ctx->c = initval; /* the previous hash value */
+}
+
+static ERTS_INLINE
+void block_hash_buffer(byte *buf,
+ Uint buf_length,
+ ErtsBlockHashHelperCtx* ctx /* out parameter */)
+{
+ Uint len = buf_length;
+ byte *k = buf;
+ ASSERT(buf_length % BLOCK_HASH_BYTES_PER_ITER == 0);
+ while (len >= BLOCK_HASH_BYTES_PER_ITER) {
+ ctx->a += (k[0] +((Uint32)k[1]<<8) +((Uint32)k[2]<<16) +((Uint32)k[3]<<24));
+ ctx->b += (k[4] +((Uint32)k[5]<<8) +((Uint32)k[6]<<16) +((Uint32)k[7]<<24));
+ ctx->c += (k[8] +((Uint32)k[9]<<8) +((Uint32)k[10]<<16)+((Uint32)k[11]<<24));
+ MIX(ctx->a,ctx->b,ctx->c);
+ k += BLOCK_HASH_BYTES_PER_ITER; len -= BLOCK_HASH_BYTES_PER_ITER;
+ }
+}
+
+static ERTS_INLINE
+Uint32 block_hash_final_bytes(byte *buf,
+ Uint buf_length,
+ Uint full_length,
+ ErtsBlockHashHelperCtx* ctx)
+{
+ Uint len = buf_length;
+ byte *k = buf;
+ ctx->c += full_length;
+ switch(len)
+ { /* all the case statements fall through */
+ case 11: ctx->c+=((Uint32)k[10]<<24);
+ case 10: ctx->c+=((Uint32)k[9]<<16);
+ case 9 : ctx->c+=((Uint32)k[8]<<8);
+ /* the first byte of c is reserved for the length */
+ case 8 : ctx->b+=((Uint32)k[7]<<24);
+ case 7 : ctx->b+=((Uint32)k[6]<<16);
+ case 6 : ctx->b+=((Uint32)k[5]<<8);
+ case 5 : ctx->b+=k[4];
+ case 4 : ctx->a+=((Uint32)k[3]<<24);
+ case 3 : ctx->a+=((Uint32)k[2]<<16);
+ case 2 : ctx->a+=((Uint32)k[1]<<8);
+ case 1 : ctx->a+=k[0];
+ /* case 0: nothing left to add */
+ }
+ MIX(ctx->a,ctx->b,ctx->c);
+ return ctx->c;
+}
+
+static
+Uint32
+block_hash(byte *block, Uint block_length, Uint32 initval)
+{
+ ErtsBlockHashHelperCtx ctx;
+ Uint no_bytes_not_in_loop =
+ (block_length % BLOCK_HASH_BYTES_PER_ITER);
+ Uint no_bytes_to_process_in_loop =
+ block_length - no_bytes_not_in_loop;
+ byte *final_bytes = block + no_bytes_to_process_in_loop;
+ block_hash_setup(initval, &ctx);
+ block_hash_buffer(block,
+ no_bytes_to_process_in_loop,
+ &ctx);
+ return block_hash_final_bytes(final_bytes,
+ no_bytes_not_in_loop,
+ block_length,
+ &ctx);
+}
+
+/*
+ * Note! erts_block_hash() and erts_iov_block_hash() *must* produce
+ * the same result if the I/O vector is flattened and contain the
+ * same bytes as the array.
+ */
+
+void
+erts_block_hash_init(ErtsBlockHashState *state, const byte *ptr,
+ Uint len, Uint32 initval)
+{
+ block_hash_setup(initval, &state->hctx);
+ state->ptr = ptr;
+ state->len = len;
+ state->tot_len = len;
+}
+
+int
+erts_block_hash(Uint32 *hashp, Uint *sizep, ErtsBlockHashState *state)
+{
+ byte *ptr = (byte *) state->ptr;
+ Uint len = state->len;
+ Sint flen;
+ Uint llen;
+
+ do {
+
+ if (*sizep < len) {
+ llen = *sizep;
+ llen -= llen % BLOCK_HASH_BYTES_PER_ITER;
+ if (len > llen + BLOCK_HASH_BYTES_PER_ITER) {
+ llen += BLOCK_HASH_BYTES_PER_ITER;
+ flen = -1;
+ break;
+ }
+ }
+
+ /* do it all... */
+ flen = len % BLOCK_HASH_BYTES_PER_ITER;
+ llen = len - flen;
+
+ } while (0);
+
+ block_hash_buffer(ptr, llen, &state->hctx);
+
+ ptr += llen;
+
+ if (flen < 0) {
+ state->ptr = ptr;
+ state->len -= llen;
+ *sizep = llen;
+ return 0; /* yield */
+ }
+
+ *hashp = block_hash_final_bytes(ptr, (Uint) flen,
+ state->tot_len, &state->hctx);
+
+ *sizep = llen + flen;
+
+ state->ptr = ptr + flen;
+ state->len = 0;
+
+ return !0; /* done */
+}
+
+/*
+ * Note! erts_block_hash() and erts_iov_block_hash() *must* produce
+ * the same result if the I/O vector is flattened and contain the
+ * same bytes as the array.
+ */
+
+void
+erts_iov_block_hash_init(ErtsIovBlockHashState *state, SysIOVec *iov,
+ Uint vlen, Uint32 initval)
+{
+ block_hash_setup(initval, &state->hctx);
+ state->iov = iov;
+ state->vlen = vlen;
+ state->tot_len = 0;
+ state->vix = 0;
+ state->ix = 0;
+}
+
+int
+erts_iov_block_hash(Uint32 *hashp, Uint *sizep, ErtsIovBlockHashState *state)
+{
+ byte buf[BLOCK_HASH_BYTES_PER_ITER];
+ ErtsBlockHashHelperCtx *hctx = &state->hctx;
+ SysIOVec *iov = state->iov;
+ Uint vlen = state->vlen;
+ int vix = state->vix;
+ int ix = state->ix;
+ Uint cix = 0;
+ byte *final_bytes;
+ Uint no_final_bytes;
+ Uint chunk_sz = (*sizep
+ - *sizep % BLOCK_HASH_BYTES_PER_ITER
+ + BLOCK_HASH_BYTES_PER_ITER);
+
+ do {
+ Uint bsz, csz;
+ int left;
+ byte *ptr;
+
+ ASSERT((cix % BLOCK_HASH_BYTES_PER_ITER) == 0);
+
+ /*
+ * We may have empty vectors...
+ */
+ while (ix == iov[vix].iov_len) {
+ vix++;
+ if (vix == vlen) {
+ final_bytes = NULL;
+ no_final_bytes = 0;
+ goto finalize;
+ }
+ ix = 0;
+ }
+
+ csz = chunk_sz - cix;
+ left = iov[vix].iov_len - ix;
+ ptr = iov[vix].iov_base;
+
+ if (left >= BLOCK_HASH_BYTES_PER_ITER) {
+ if (csz <= left)
+ bsz = csz;
+ else
+ bsz = left - (left % BLOCK_HASH_BYTES_PER_ITER);
+ block_hash_buffer(ptr + ix, bsz, hctx);
+ cix += bsz;
+ ix += bsz;
+ }
+ else {
+ int bix = 0;
+ bsz = left;
+ while (!0) {
+ sys_memcpy(&buf[bix], ptr + ix, bsz);
+ bix += bsz;
+ cix += bsz;
+ ix += bsz;
+ if (bix == BLOCK_HASH_BYTES_PER_ITER) {
+ block_hash_buffer(&buf[0],
+ (Uint) BLOCK_HASH_BYTES_PER_ITER,
+ hctx);
+ break;
+ }
+ ASSERT(ix == iov[vix].iov_len);
+ vix++;
+ if (vix == vlen) {
+ final_bytes = &buf[0];
+ no_final_bytes = (Uint) bsz;
+ goto finalize;
+ }
+ ix = 0;
+ ptr = iov[vix].iov_base;
+ bsz = iov[vix].iov_len;
+ if (bsz > BLOCK_HASH_BYTES_PER_ITER - bix)
+ bsz = BLOCK_HASH_BYTES_PER_ITER - bix;
+ }
+ }
+
+ } while (cix < chunk_sz);
+
+ ASSERT((cix % BLOCK_HASH_BYTES_PER_ITER) == 0);
+
+ /* yield */
+
+ *sizep = cix;
+
+ state->tot_len += cix;
+ state->vix = vix;
+ state->ix = ix;
+
+ return 0;
+
+finalize:
+
+ state->tot_len += cix;
+ *sizep = cix;
+
+ *hashp = block_hash_final_bytes(final_bytes, no_final_bytes,
+ state->tot_len, hctx);
+ return !0; /* done */
+}
+
+
+
+typedef enum {
+ tag_primary_list,
+ arityval_subtag,
+ hamt_subtag_head_flatmap,
+ map_subtag,
+ fun_subtag,
+ neg_big_subtag,
+ sub_binary_subtag_1,
+ sub_binary_subtag_2,
+ hash2_common_1,
+ hash2_common_2,
+ hash2_common_3,
+} ErtsMakeHash2TrapLocation;
+
+typedef struct {
+ int c;
+ Uint32 sh;
+ Eterm* ptr;
+} ErtsMakeHash2Context_TAG_PRIMARY_LIST;
+
+typedef struct {
+ int i;
+ int arity;
+ Eterm* elem;
+} ErtsMakeHash2Context_ARITYVAL_SUBTAG;
+
+typedef struct {
+ Eterm *ks;
+ Eterm *vs;
+ int i;
+ Uint size;
+} ErtsMakeHash2Context_HAMT_SUBTAG_HEAD_FLATMAP;
+
+typedef struct {
+ Eterm* ptr;
+ int i;
+} ErtsMakeHash2Context_MAP_SUBTAG;
+
+typedef struct {
+ Uint num_free;
+ Eterm* bptr;
+} ErtsMakeHash2Context_FUN_SUBTAG;
+
+typedef struct {
+ Eterm* ptr;
+ Uint i;
+ Uint n;
+ Uint32 con;
+} ErtsMakeHash2Context_NEG_BIG_SUBTAG;
+
+typedef struct {
+ byte* bptr;
+ Uint sz;
+ Uint bitsize;
+ Uint bitoffs;
+ Uint no_bytes_processed;
+ ErtsBlockHashHelperCtx block_hash_ctx;
+ /* The following fields are only used when bitoffs != 0 */
+ byte* buf;
+ int done;
+
+} ErtsMakeHash2Context_SUB_BINARY_SUBTAG;
+
+typedef struct {
+ int dummy__; /* Empty structs are not supported on all platforms */
+} ErtsMakeHash2Context_EMPTY;
+
+typedef struct {
+ ErtsMakeHash2TrapLocation trap_location;
+ /* specific to the trap location: */
+ union {
+ ErtsMakeHash2Context_TAG_PRIMARY_LIST tag_primary_list;
+ ErtsMakeHash2Context_ARITYVAL_SUBTAG arityval_subtag;
+ ErtsMakeHash2Context_HAMT_SUBTAG_HEAD_FLATMAP hamt_subtag_head_flatmap;
+ ErtsMakeHash2Context_MAP_SUBTAG map_subtag;
+ ErtsMakeHash2Context_FUN_SUBTAG fun_subtag;
+ ErtsMakeHash2Context_NEG_BIG_SUBTAG neg_big_subtag;
+ ErtsMakeHash2Context_SUB_BINARY_SUBTAG sub_binary_subtag_1;
+ ErtsMakeHash2Context_SUB_BINARY_SUBTAG sub_binary_subtag_2;
+ ErtsMakeHash2Context_EMPTY hash2_common_1;
+ ErtsMakeHash2Context_EMPTY hash2_common_2;
+ ErtsMakeHash2Context_EMPTY hash2_common_3;
+ } trap_location_state;
+ /* same for all trap locations: */
+ Eterm term;
+ Uint32 hash;
+ Uint32 hash_xor_pairs;
+ ErtsEStack stack;
+} ErtsMakeHash2Context;
+
+static int make_hash2_ctx_bin_dtor(Binary *context_bin) {
+ ErtsMakeHash2Context* context = ERTS_MAGIC_BIN_DATA(context_bin);
+ DESTROY_SAVED_ESTACK(&context->stack);
+ if (context->trap_location == sub_binary_subtag_2 &&
+ context->trap_location_state.sub_binary_subtag_2.buf != NULL) {
+ erts_free(ERTS_ALC_T_PHASH2_TRAP, context->trap_location_state.sub_binary_subtag_2.buf);
+ }
+ return 1;
+}
+
+/* hash2_save_trap_state is called seldom so we want to avoid inlining */
+static ERTS_NOINLINE
+Eterm hash2_save_trap_state(Eterm state_mref,
+ Uint32 hash_xor_pairs,
+ Uint32 hash,
+ Process* p,
+ Eterm term,
+ Eterm* ESTK_DEF_STACK(s),
+ ErtsEStack s,
+ ErtsMakeHash2TrapLocation trap_location,
+ void* trap_location_state_ptr,
+ size_t trap_location_state_size) {
+ Binary* state_bin;
+ ErtsMakeHash2Context* context;
+ if (state_mref == THE_NON_VALUE) {
+ Eterm* hp;
+ state_bin = erts_create_magic_binary(sizeof(ErtsMakeHash2Context),
+ make_hash2_ctx_bin_dtor);
+ hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE);
+ state_mref = erts_mk_magic_ref(&hp, &MSO(p), state_bin);
+ } else {
+ state_bin = erts_magic_ref2bin(state_mref);
+ }
+ context = ERTS_MAGIC_BIN_DATA(state_bin);
+ context->term = term;
+ context->hash = hash;
+ context->hash_xor_pairs = hash_xor_pairs;
+ ESTACK_SAVE(s, &context->stack);
+ context->trap_location = trap_location;
+ sys_memcpy(&context->trap_location_state,
+ trap_location_state_ptr,
+ trap_location_state_size);
+ erts_set_gc_state(p, 0);
+ BUMP_ALL_REDS(p);
+ return state_mref;
+}
+#undef NOINLINE_HASH2_SAVE_TRAP_STATE
+
+/* Writes back a magic reference to *state_mref_write_back when the
+ function traps */
+static ERTS_INLINE Uint32
+make_hash2_helper(Eterm term_param, const int can_trap, Eterm* state_mref_write_back, Process* p)
+{
+ static const Uint ITERATIONS_PER_RED = 64;
+ Uint32 hash;
+ Uint32 hash_xor_pairs;
+ Eterm term = term_param;
+ ERTS_UNDEF(hash_xor_pairs, 0);
+
+/* (HCONST * {2, ..., 22}) mod 2^32 */
+#define HCONST_2 0x3c6ef372UL
+#define HCONST_3 0xdaa66d2bUL
+#define HCONST_4 0x78dde6e4UL
+#define HCONST_5 0x1715609dUL
+#define HCONST_6 0xb54cda56UL
+#define HCONST_7 0x5384540fUL
+#define HCONST_8 0xf1bbcdc8UL
+#define HCONST_9 0x8ff34781UL
+#define HCONST_10 0x2e2ac13aUL
+#define HCONST_11 0xcc623af3UL
+#define HCONST_12 0x6a99b4acUL
+#define HCONST_13 0x08d12e65UL
+#define HCONST_14 0xa708a81eUL
+#define HCONST_15 0x454021d7UL
+#define HCONST_16 0xe3779b90UL
+#define HCONST_17 0x81af1549UL
+#define HCONST_18 0x1fe68f02UL
+#define HCONST_19 0xbe1e08bbUL
+#define HCONST_20 0x5c558274UL
+#define HCONST_21 0xfa8cfc2dUL
+#define HCONST_22 0x98c475e6UL
+
+#define HASH_MAP_TAIL (_make_header(1,_TAG_HEADER_REF))
+#define HASH_MAP_PAIR (_make_header(2,_TAG_HEADER_REF))
+#define HASH_CDR (_make_header(3,_TAG_HEADER_REF))
+
+#define UINT32_HASH_2(Expr1, Expr2, AConst) \
+ do { \
+ Uint32 a,b; \
+ a = AConst + (Uint32) (Expr1); \
+ b = AConst + (Uint32) (Expr2); \
+ MIX(a,b,hash); \
+ } while(0)
+
+#define UINT32_HASH(Expr, AConst) UINT32_HASH_2(Expr, 0, AConst)
+
+#define SINT32_HASH(Expr, AConst) \
+ do { \
+ Sint32 y = (Sint32) (Expr); \
+ if (y < 0) { \
+ UINT32_HASH(-y, AConst); \
+ /* Negative numbers are unnecessarily mixed twice. */ \
+ } \
+ UINT32_HASH(y, AConst); \
+ } while(0)
+
+#define IS_SSMALL28(x) (((Uint) (((x) >> (28-1)) + 1)) < 2)
+
+#define NOT_SSMALL28_HASH(SMALL) \
+ do { \
+ Uint64 t; \
+ Uint32 x, y; \
+ Uint32 con; \
+ if (SMALL < 0) { \
+ con = HCONST_10; \
+ t = (Uint64)(SMALL * (-1)); \
+ } else { \
+ con = HCONST_11; \
+ t = SMALL; \
+ } \
+ x = t & 0xffffffff; \
+ y = t >> 32; \
+ UINT32_HASH_2(x, y, con); \
+ } while(0)
+
+#ifdef ARCH_64
+# define POINTER_HASH(Ptr, AConst) UINT32_HASH_2((Uint32)(UWord)(Ptr), (((UWord)(Ptr)) >> 32), AConst)
+#else
+# define POINTER_HASH(Ptr, AConst) UINT32_HASH(Ptr, AConst)
+#endif
+
+#define TRAP_LOCATION_NO_RED(location_name) \
+ do { \
+ if(can_trap && iterations_until_trap <= 0) { \
+ *state_mref_write_back = \
+ hash2_save_trap_state(state_mref, \
+ hash_xor_pairs, \
+ hash, \
+ p, \
+ term, \
+ ESTK_DEF_STACK(s), \
+ s, \
+ location_name, \
+ &ctx, \
+ sizeof(ctx)); \
+ return 0; \
+ L_##location_name: \
+ ctx = context->trap_location_state. location_name; \
+ } \
+ } while(0)
+
+#define TRAP_LOCATION(location_name) \
+ do { \
+ if (can_trap) { \
+ iterations_until_trap--; \
+ TRAP_LOCATION_NO_RED(location_name); \
+ } \
+ } while(0)
+
+#define TRAP_LOCATION_NO_CTX(location_name) \
+ do { \
+ ErtsMakeHash2Context_EMPTY ctx; \
+ TRAP_LOCATION(location_name); \
+ } while(0)
+
+ /* Optimization. Simple cases before declaration of estack. */
+ if (primary_tag(term) == TAG_PRIMARY_IMMED1) {
+ switch (term & _TAG_IMMED1_MASK) {
+ case _TAG_IMMED1_IMMED2:
+ switch (term & _TAG_IMMED2_MASK) {
+ case _TAG_IMMED2_ATOM:
+ /* Fast, but the poor hash value should be mixed. */
+ return atom_tab(atom_val(term))->slot.bucket.hvalue;
+ }
+ break;
+ case _TAG_IMMED1_SMALL:
+ {
+ Sint small = signed_val(term);
+ if (SMALL_BITS > 28 && !IS_SSMALL28(small)) {
+ hash = 0;
+ NOT_SSMALL28_HASH(small);
+ return hash;
+ }
+ hash = 0;
+ SINT32_HASH(small, HCONST);
+ return hash;
+ }
+ }
+ };
+ {
+ Eterm tmp;
+ long max_iterations = 0;
+ long iterations_until_trap = 0;
+ Eterm state_mref = THE_NON_VALUE;
+ ErtsMakeHash2Context* context = NULL;
+ DECLARE_ESTACK(s);
+ ESTACK_CHANGE_ALLOCATOR(s, ERTS_ALC_T_SAVED_ESTACK);
+ if(can_trap){
+#ifdef DEBUG
+ (void)ITERATIONS_PER_RED;
+ iterations_until_trap = max_iterations =
+ (1103515245 * (ERTS_BIF_REDS_LEFT(p)) + 12345) % 227;
+#else
+ iterations_until_trap = max_iterations =
+ ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(p);
+#endif
+ }
+ if (can_trap && is_internal_magic_ref(term)) {
+ Binary* state_bin;
+ state_mref = term;
+ state_bin = erts_magic_ref2bin(state_mref);
+ if (ERTS_MAGIC_BIN_DESTRUCTOR(state_bin) == make_hash2_ctx_bin_dtor) {
+ /* Restore state after a trap */
+ context = ERTS_MAGIC_BIN_DATA(state_bin);
+ term = context->term;
+ hash = context->hash;
+ hash_xor_pairs = context->hash_xor_pairs;
+ ESTACK_RESTORE(s, &context->stack);
+ ASSERT(p->flags & F_DISABLE_GC);
+ erts_set_gc_state(p, 1);
+ switch (context->trap_location) {
+ case hash2_common_3: goto L_hash2_common_3;
+ case tag_primary_list: goto L_tag_primary_list;
+ case arityval_subtag: goto L_arityval_subtag;
+ case hamt_subtag_head_flatmap: goto L_hamt_subtag_head_flatmap;
+ case map_subtag: goto L_map_subtag;
+ case fun_subtag: goto L_fun_subtag;
+ case neg_big_subtag: goto L_neg_big_subtag;
+ case sub_binary_subtag_1: goto L_sub_binary_subtag_1;
+ case sub_binary_subtag_2: goto L_sub_binary_subtag_2;
+ case hash2_common_1: goto L_hash2_common_1;
+ case hash2_common_2: goto L_hash2_common_2;
+ }
+ }
+ }
+ hash = 0;
+ for (;;) {
+ switch (primary_tag(term)) {
+ case TAG_PRIMARY_LIST:
+ {
+ ErtsMakeHash2Context_TAG_PRIMARY_LIST ctx = {
+ .c = 0,
+ .sh = 0,
+ .ptr = list_val(term)};
+ while (is_byte(*ctx.ptr)) {
+ /* Optimization for strings. */
+ ctx.sh = (ctx.sh << 8) + unsigned_val(*ctx.ptr);
+ if (ctx.c == 3) {
+ UINT32_HASH(ctx.sh, HCONST_4);
+ ctx.c = ctx.sh = 0;
+ } else {
+ ctx.c++;
+ }
+ term = CDR(ctx.ptr);
+ if (is_not_list(term))
+ break;
+ ctx.ptr = list_val(term);
+ TRAP_LOCATION(tag_primary_list);
+ }
+ if (ctx.c > 0)
+ UINT32_HASH(ctx.sh, HCONST_4);
+ if (is_list(term)) {
+ tmp = CDR(ctx.ptr);
+ ESTACK_PUSH(s, tmp);
+ term = CAR(ctx.ptr);
+ }
+ }
+ break;
+ case TAG_PRIMARY_BOXED:
+ {
+ Eterm hdr = *boxed_val(term);
+ ASSERT(is_header(hdr));
+ switch (hdr & _TAG_HEADER_MASK) {
+ case ARITYVAL_SUBTAG:
+ {
+ ErtsMakeHash2Context_ARITYVAL_SUBTAG ctx = {
+ .i = 0,
+ .arity = header_arity(hdr),
+ .elem = tuple_val(term)};
+ UINT32_HASH(ctx.arity, HCONST_9);
+ if (ctx.arity == 0) /* Empty tuple */
+ goto hash2_common;
+ for (ctx.i = ctx.arity; ; ctx.i--) {
+ term = ctx.elem[ctx.i];
+ if (ctx.i == 1)
+ break;
+ ESTACK_PUSH(s, term);
+ TRAP_LOCATION(arityval_subtag);
+ }
+ }
+ break;
+ case MAP_SUBTAG:
+ {
+ Uint size;
+ ErtsMakeHash2Context_MAP_SUBTAG ctx = {
+ .ptr = boxed_val(term) + 1,
+ .i = 0};
+ switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
+ case HAMT_SUBTAG_HEAD_FLATMAP:
+ {
+ flatmap_t *mp = (flatmap_t *)flatmap_val(term);
+ ErtsMakeHash2Context_HAMT_SUBTAG_HEAD_FLATMAP ctx = {
+ .ks = flatmap_get_keys(mp),
+ .vs = flatmap_get_values(mp),
+ .i = 0,
+ .size = flatmap_get_size(mp)};
+ UINT32_HASH(ctx.size, HCONST_16);
+ if (ctx.size == 0)
+ goto hash2_common;
+
+ /* We want a portable hash function that is *independent* of
+ * the order in which keys and values are encountered.
+ * We therefore calculate context independent hashes for all
+ * key-value pairs and then xor them together.
+ */
+ ESTACK_PUSH(s, hash_xor_pairs);
+ ESTACK_PUSH(s, hash);
+ ESTACK_PUSH(s, HASH_MAP_TAIL);
+ hash = 0;
+ hash_xor_pairs = 0;
+ for (ctx.i = ctx.size - 1; ctx.i >= 0; ctx.i--) {
+ ESTACK_PUSH(s, HASH_MAP_PAIR);
+ ESTACK_PUSH(s, ctx.vs[ctx.i]);
+ ESTACK_PUSH(s, ctx.ks[ctx.i]);
+ TRAP_LOCATION(hamt_subtag_head_flatmap);
+ }
+ goto hash2_common;
+ }
+
+ case HAMT_SUBTAG_HEAD_ARRAY:
+ case HAMT_SUBTAG_HEAD_BITMAP:
+ size = *ctx.ptr++;
+ UINT32_HASH(size, HCONST_16);
+ if (size == 0)
+ goto hash2_common;
+ ESTACK_PUSH(s, hash_xor_pairs);
+ ESTACK_PUSH(s, hash);
+ ESTACK_PUSH(s, HASH_MAP_TAIL);
+ hash = 0;
+ hash_xor_pairs = 0;
+ }
+ switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
+ case HAMT_SUBTAG_HEAD_ARRAY:
+ ctx.i = 16;
+ break;
+ case HAMT_SUBTAG_HEAD_BITMAP:
+ case HAMT_SUBTAG_NODE_BITMAP:
+ ctx.i = hashmap_bitcount(MAP_HEADER_VAL(hdr));
+ break;
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "bad header");
+ }
+ while (ctx.i) {
+ if (is_list(*ctx.ptr)) {
+ Eterm* cons = list_val(*ctx.ptr);
+ ESTACK_PUSH(s, HASH_MAP_PAIR);
+ ESTACK_PUSH(s, CDR(cons));
+ ESTACK_PUSH(s, CAR(cons));
+ }
+ else {
+ ASSERT(is_boxed(*ctx.ptr));
+ if (is_tuple(*ctx.ptr)) { /* collision node */
+ Eterm *coll_ptr = tuple_val(*ctx.ptr);
+ Uint n = arityval(*coll_ptr);
+ ASSERT(n >= 2);
+ coll_ptr++;
+ for (; n; n--, coll_ptr++) {
+ Eterm* cons = list_val(*coll_ptr);
+ ESTACK_PUSH3(s, HASH_MAP_PAIR, CDR(cons), CAR(cons));
+ }
+ }
+ else
+ ESTACK_PUSH(s, *ctx.ptr);
+ }
+ ctx.i--; ctx.ptr++;
+ TRAP_LOCATION(map_subtag);
+ }
+ goto hash2_common;
+ }
+ break;
+
+ case FUN_SUBTAG:
+ {
+ ErlFunThing* funp = (ErlFunThing *) fun_val(term);
+
+ if (is_local_fun(funp)) {
+ ErlFunEntry* fe = funp->entry.fun;
+ ErtsMakeHash2Context_FUN_SUBTAG ctx = {
+ .num_free = funp->num_free,
+ .bptr = NULL};
+
+ UINT32_HASH_2
+ (ctx.num_free,
+ atom_tab(atom_val(fe->module))->slot.bucket.hvalue,
+ HCONST);
+ UINT32_HASH_2
+ (fe->index, fe->old_uniq, HCONST);
+ if (ctx.num_free == 0) {
+ goto hash2_common;
+ } else {
+ ctx.bptr = funp->env + ctx.num_free - 1;
+ while (ctx.num_free-- > 1) {
+ term = *ctx.bptr--;
+ ESTACK_PUSH(s, term);
+ TRAP_LOCATION(fun_subtag);
+ }
+ term = *ctx.bptr;
+ }
+ } else {
+ Export *ep = funp->entry.exp;
+
+ ASSERT(is_external_fun(funp) && funp->next == NULL);
+
+ UINT32_HASH_2
+ (ep->info.mfa.arity,
+ atom_tab(atom_val(ep->info.mfa.module))->slot.bucket.hvalue,
+ HCONST);
+ UINT32_HASH
+ (atom_tab(atom_val(ep->info.mfa.function))->slot.bucket.hvalue,
+ HCONST_14);
+
+ goto hash2_common;
+ }
+ }
+ break;
+ case REFC_BINARY_SUBTAG:
+ case HEAP_BINARY_SUBTAG:
+ case SUB_BINARY_SUBTAG:
+ {
+#define BYTE_BITS 8
+ ErtsMakeHash2Context_SUB_BINARY_SUBTAG ctx = {
+ .bptr = 0,
+ /* !!!!!!!!!!!!!!!!!!!! OBS !!!!!!!!!!!!!!!!!!!!
+ *
+ * The size is truncated to 32 bits on the line
+ * below so that the code is compatible with old
+ * versions of the code. This means that hash
+ * values for binaries with a size greater than
+ * 4GB do not take all bytes in consideration.
+ *
+ * !!!!!!!!!!!!!!!!!!!! OBS !!!!!!!!!!!!!!!!!!!!
+ */
+ .sz = (0xFFFFFFFF & binary_size(term)),
+ .bitsize = 0,
+ .bitoffs = 0,
+ .no_bytes_processed = 0
+ };
+ Uint32 con = HCONST_13 + hash;
+ Uint iters_for_bin = MAX(1, ctx.sz / BLOCK_HASH_BYTES_PER_ITER);
+ ERTS_GET_BINARY_BYTES(term, ctx.bptr, ctx.bitoffs, ctx.bitsize);
+ if (ctx.sz == 0 && ctx.bitsize == 0) {
+ hash = con;
+ } else if (ctx.bitoffs == 0 &&
+ (!can_trap ||
+ (iterations_until_trap - iters_for_bin) > 0)) {
+ /* No need to trap while hashing binary */
+ if (can_trap) iterations_until_trap -= iters_for_bin;
+ hash = block_hash(ctx.bptr, ctx.sz, con);
+ if (ctx.bitsize > 0) {
+ UINT32_HASH_2(ctx.bitsize,
+ (ctx.bptr[ctx.sz] >> (BYTE_BITS - ctx.bitsize)),
+ HCONST_15);
+ }
+ } else if (ctx.bitoffs == 0) {
+ /* Need to trap while hashing binary */
+ ErtsBlockHashHelperCtx* block_hash_ctx = &ctx.block_hash_ctx;
+ block_hash_setup(con, block_hash_ctx);
+ do {
+ Uint max_bytes_to_process =
+ iterations_until_trap <= 0 ? BLOCK_HASH_BYTES_PER_ITER :
+ iterations_until_trap * BLOCK_HASH_BYTES_PER_ITER;
+ Uint bytes_left = ctx.sz - ctx.no_bytes_processed;
+ Uint even_bytes_left =
+ bytes_left - (bytes_left % BLOCK_HASH_BYTES_PER_ITER);
+ Uint bytes_to_process =
+ MIN(max_bytes_to_process, even_bytes_left);
+ block_hash_buffer(&ctx.bptr[ctx.no_bytes_processed],
+ bytes_to_process,
+ block_hash_ctx);
+ ctx.no_bytes_processed += bytes_to_process;
+ iterations_until_trap -=
+ MAX(1, bytes_to_process / BLOCK_HASH_BYTES_PER_ITER);
+ TRAP_LOCATION_NO_RED(sub_binary_subtag_1);
+ block_hash_ctx = &ctx.block_hash_ctx; /* Restore after trap */
+ } while ((ctx.sz - ctx.no_bytes_processed) >=
+ BLOCK_HASH_BYTES_PER_ITER);
+ hash = block_hash_final_bytes(ctx.bptr +
+ ctx.no_bytes_processed,
+ ctx.sz - ctx.no_bytes_processed,
+ ctx.sz,
+ block_hash_ctx);
+ if (ctx.bitsize > 0) {
+ UINT32_HASH_2(ctx.bitsize,
+ (ctx.bptr[ctx.sz] >> (BYTE_BITS - ctx.bitsize)),
+ HCONST_15);
+ }
+ } else if (/* ctx.bitoffs != 0 && */
+ (!can_trap ||
+ (iterations_until_trap - iters_for_bin) > 0)) {
+ /* No need to trap while hashing binary */
+ Uint nr_of_bytes = ctx.sz + (ctx.bitsize != 0);
+ byte *buf = erts_alloc(ERTS_ALC_T_TMP, nr_of_bytes);
+ Uint nr_of_bits_to_copy = ctx.sz*BYTE_BITS+ctx.bitsize;
+ if (can_trap) iterations_until_trap -= iters_for_bin;
+ erts_copy_bits(ctx.bptr,
+ ctx.bitoffs, 1, buf, 0, 1, nr_of_bits_to_copy);
+ hash = block_hash(buf, ctx.sz, con);
+ if (ctx.bitsize > 0) {
+ UINT32_HASH_2(ctx.bitsize,
+ (buf[ctx.sz] >> (BYTE_BITS - ctx.bitsize)),
+ HCONST_15);
+ }
+ erts_free(ERTS_ALC_T_TMP, buf);
+ } else /* ctx.bitoffs != 0 && */ {
+#ifdef DEBUG
+#define BINARY_BUF_SIZE (BLOCK_HASH_BYTES_PER_ITER * 3)
+#else
+#define BINARY_BUF_SIZE (BLOCK_HASH_BYTES_PER_ITER * 256)
+#endif
+#define BINARY_BUF_SIZE_BITS (BINARY_BUF_SIZE*BYTE_BITS)
+ /* Need to trap while hashing binary */
+ ErtsBlockHashHelperCtx* block_hash_ctx = &ctx.block_hash_ctx;
+ Uint nr_of_bytes = ctx.sz + (ctx.bitsize != 0);
+ ERTS_CT_ASSERT(BINARY_BUF_SIZE % BLOCK_HASH_BYTES_PER_ITER == 0);
+ ctx.buf = erts_alloc(ERTS_ALC_T_PHASH2_TRAP,
+ MIN(nr_of_bytes, BINARY_BUF_SIZE));
+ block_hash_setup(con, block_hash_ctx);
+ do {
+ Uint bytes_left =
+ ctx.sz - ctx.no_bytes_processed;
+ Uint even_bytes_left =
+ bytes_left - (bytes_left % BLOCK_HASH_BYTES_PER_ITER);
+ Uint bytes_to_process =
+ MIN(BINARY_BUF_SIZE, even_bytes_left);
+ Uint nr_of_bits_left =
+ (ctx.sz*BYTE_BITS+ctx.bitsize) -
+ ctx.no_bytes_processed*BYTE_BITS;
+ Uint nr_of_bits_to_copy =
+ MIN(nr_of_bits_left, BINARY_BUF_SIZE_BITS);
+ ctx.done = nr_of_bits_left == nr_of_bits_to_copy;
+ erts_copy_bits(ctx.bptr + ctx.no_bytes_processed,
+ ctx.bitoffs, 1, ctx.buf, 0, 1,
+ nr_of_bits_to_copy);
+ block_hash_buffer(ctx.buf,
+ bytes_to_process,
+ block_hash_ctx);
+ ctx.no_bytes_processed += bytes_to_process;
+ iterations_until_trap -=
+ MAX(1, bytes_to_process / BLOCK_HASH_BYTES_PER_ITER);
+ TRAP_LOCATION_NO_RED(sub_binary_subtag_2);
+ block_hash_ctx = &ctx.block_hash_ctx; /* Restore after trap */
+ } while (!ctx.done);
+ nr_of_bytes = ctx.sz + (ctx.bitsize != 0);
+ hash = block_hash_final_bytes(ctx.buf +
+ (ctx.no_bytes_processed -
+ ((nr_of_bytes-1) / BINARY_BUF_SIZE) * BINARY_BUF_SIZE),
+ ctx.sz - ctx.no_bytes_processed,
+ ctx.sz,
+ block_hash_ctx);
+ if (ctx.bitsize > 0) {
+ Uint last_byte_index =
+ nr_of_bytes - (((nr_of_bytes-1) / BINARY_BUF_SIZE) * BINARY_BUF_SIZE) -1;
+ UINT32_HASH_2(ctx.bitsize,
+ (ctx.buf[last_byte_index] >> (BYTE_BITS - ctx.bitsize)),
+ HCONST_15);
+ }
+ erts_free(ERTS_ALC_T_PHASH2_TRAP, ctx.buf);
+ context->trap_location_state.sub_binary_subtag_2.buf = NULL;
+ }
+ goto hash2_common;
+#undef BYTE_BITS
+#undef BINARY_BUF_SIZE
+#undef BINARY_BUF_SIZE_BITS
+ }
+ break;
+ case POS_BIG_SUBTAG:
+ case NEG_BIG_SUBTAG:
+ {
+ Eterm* big_val_ptr = big_val(term);
+ ErtsMakeHash2Context_NEG_BIG_SUBTAG ctx = {
+ .ptr = big_val_ptr,
+ .i = 0,
+ .n = BIG_SIZE(big_val_ptr),
+ .con = BIG_SIGN(big_val_ptr) ? HCONST_10 : HCONST_11};
+#if D_EXP == 16
+ do {
+ Uint32 x, y;
+ x = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
+ x += (Uint32)(ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0) << 16;
+ y = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
+ y += (Uint32)(ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0) << 16;
+ UINT32_HASH_2(x, y, ctx.con);
+ TRAP_LOCATION(neg_big_subtag);
+ } while (ctx.i < ctx.n);
+#elif D_EXP == 32
+ do {
+ Uint32 x, y;
+ x = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
+ y = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
+ UINT32_HASH_2(x, y, ctx.con);
+ TRAP_LOCATION(neg_big_subtag);
+ } while (ctx.i < ctx.n);
+#elif D_EXP == 64
+ do {
+ Uint t;
+ Uint32 x, y;
+ ASSERT(ctx.i < ctx.n);
+ t = BIG_DIGIT(ctx.ptr, ctx.i++);
+ x = t & 0xffffffff;
+ y = t >> 32;
+ UINT32_HASH_2(x, y, ctx.con);
+ TRAP_LOCATION(neg_big_subtag);
+ } while (ctx.i < ctx.n);
+#else
+#error "unsupported D_EXP size"
+#endif
+ goto hash2_common;
+ }
+ break;
+ case REF_SUBTAG:
+ /* All parts of the ref should be hashed. */
+ UINT32_HASH(internal_ref_numbers(term)[0], HCONST_7);
+ goto hash2_common;
+ break;
+ case EXTERNAL_REF_SUBTAG:
+ /* All parts of the ref should be hashed. */
+ UINT32_HASH(external_ref_numbers(term)[0], HCONST_7);
+ goto hash2_common;
+ break;
+ case EXTERNAL_PID_SUBTAG:
+ /* Only 15 bits are hashed. */
+ UINT32_HASH(external_pid_number(term), HCONST_5);
+ goto hash2_common;
+ case EXTERNAL_PORT_SUBTAG: {
+ Uint64 number = external_port_number(term);
+ Uint32 low = (Uint32) (number & 0xffffffff);
+ Uint32 high = (Uint32) ((number >> 32) & 0xffffffff);
+ UINT32_HASH_2(low, high, HCONST_6);
+ goto hash2_common;
+ }
+ case FLOAT_SUBTAG:
+ {
+ FloatDef ff;
+ GET_DOUBLE(term, ff);
+ if (ff.fd == 0.0f) {
+ /* ensure positive 0.0 */
+ ff.fd = erts_get_positive_zero_float();
+ }
+#if defined(WORDS_BIGENDIAN) || defined(DOUBLE_MIDDLE_ENDIAN)
+ UINT32_HASH_2(ff.fw[0], ff.fw[1], HCONST_12);
+#else
+ UINT32_HASH_2(ff.fw[1], ff.fw[0], HCONST_12);
+#endif
+ goto hash2_common;
+ }
+ break;
+
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_hash2(0x%X)\n", term);
+ }
+ }
+ break;
+ case TAG_PRIMARY_IMMED1:
+ switch (term & _TAG_IMMED1_MASK) {
+ case _TAG_IMMED1_PID:
+ /* Only 15 bits are hashed. */
+ UINT32_HASH(internal_pid_number(term), HCONST_5);
+ goto hash2_common;
+ case _TAG_IMMED1_PORT: {
+ Uint64 number = internal_port_number(term);
+ Uint32 low = (Uint32) (number & 0xffffffff);
+ Uint32 high = (Uint32) ((number >> 32) & 0xffffffff);
+ UINT32_HASH_2(low, high, HCONST_6);
+ goto hash2_common;
+ }
+ case _TAG_IMMED1_IMMED2:
+ switch (term & _TAG_IMMED2_MASK) {
+ case _TAG_IMMED2_ATOM:
+ if (hash == 0)
+ /* Fast, but the poor hash value should be mixed. */
+ hash = atom_tab(atom_val(term))->slot.bucket.hvalue;
+ else
+ UINT32_HASH(atom_tab(atom_val(term))->slot.bucket.hvalue,
+ HCONST_3);
+ goto hash2_common;
+ case _TAG_IMMED2_NIL:
+ if (hash == 0)
+ hash = 3468870702UL;
+ else
+ UINT32_HASH(NIL_DEF, HCONST_2);
+ goto hash2_common;
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_hash2(0x%X)\n", term);
+ }
+ case _TAG_IMMED1_SMALL:
+ {
+ Sint small = signed_val(term);
+ if (SMALL_BITS > 28 && !IS_SSMALL28(small)) {
+ NOT_SSMALL28_HASH(small);
+ } else {
+ SINT32_HASH(small, HCONST);
+ }
+
+ goto hash2_common;
+ }
+ }
+ break;
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_hash2(0x%X)\n", term);
+ hash2_common:
+
+ /* Uint32 hash always has the hash value of the previous term,
+ * compounded or otherwise.
+ */
+
+ if (ESTACK_ISEMPTY(s)) {
+ DESTROY_ESTACK(s);
+ if (can_trap) {
+ BUMP_REDS(p, (max_iterations - iterations_until_trap) / ITERATIONS_PER_RED);
+ ASSERT(!(p->flags & F_DISABLE_GC));
+ }
+ return hash;
+ }
+
+ term = ESTACK_POP(s);
+
+ switch (term) {
+ case HASH_MAP_TAIL: {
+ hash = (Uint32) ESTACK_POP(s);
+ UINT32_HASH(hash_xor_pairs, HCONST_19);
+ hash_xor_pairs = (Uint32) ESTACK_POP(s);
+ TRAP_LOCATION_NO_CTX(hash2_common_1);
+ goto hash2_common;
+ }
+ case HASH_MAP_PAIR:
+ hash_xor_pairs ^= hash;
+ hash = 0;
+ TRAP_LOCATION_NO_CTX(hash2_common_2);
+ goto hash2_common;
+ default:
+ break;
+ }
+
+ }
+ TRAP_LOCATION_NO_CTX(hash2_common_3);
+ }
+ }
+#undef TRAP_LOCATION_NO_RED
+#undef TRAP_LOCATION
+#undef TRAP_LOCATION_NO_CTX
+}
+
+Uint32
+make_hash2(Eterm term)
+{
+ return make_hash2_helper(term, 0, NULL, NULL);
+}
+
+Uint32
+trapping_make_hash2(Eterm term, Eterm* state_mref_write_back, Process* p)
+{
+ return make_hash2_helper(term, 1, state_mref_write_back, p);
+}
+
+/* Term hash function for internal use.
+ *
+ * Limitation #1: Is not "portable" in any way between different VM instances.
+ *
+ * Limitation #2: The hash value is only valid as long as the term exists
+ * somewhere in the VM. Why? Because external pids, ports and refs are hashed
+ * by mixing the node *pointer* value. If a node disappears and later reappears
+ * with a new ErlNode struct, externals from that node will hash different than
+ * before.
+ *
+ * The property "EVERY BIT of the term that is significant for equality
+ * MUST BE USED AS INPUT FOR THE HASH" is nice but no longer crucial for the
+ * hashmap implementation that now uses collision nodes at the bottom of
+ * the HAMT when all hash bits are exhausted.
+ *
+ */
+
+/* Use a better mixing function if available. */
+#if defined(ERL_INTERNAL_HASH_CRC32C)
+# undef MIX
+# if defined(__x86_64__)
+# define MIX(a,b,c) \
+ do { \
+ Uint32 initial_hash = c; \
+ c = __builtin_ia32_crc32si(c, a); \
+ c = __builtin_ia32_crc32si(c + initial_hash, b); \
+ } while(0)
+# elif defined(__aarch64__)
+# define MIX(a,b,c) \
+ do { \
+ Uint32 initial_hash = c; \
+ c = __crc32cw(c, a); \
+ c = __crc32cw(c + initial_hash, b); \
+ } while(0)
+# else
+# error "No suitable CRC32 intrinsic available."
+# endif
+#endif
+
+#define CONST_HASH(AConst) \
+ do { /* Lightweight mixing of constant (type info) */ \
+ hash ^= AConst; \
+ hash = (hash << 17) ^ (hash >> (32-17)); \
+ } while (0)
+
+/*
+ * Start with salt, 32-bit prime number, to avoid getting same hash as phash2
+ * which can cause bad hashing in distributed ETS tables for example.
+ */
+#define INTERNAL_HASH_SALT 3432918353U
+
+Uint32
+make_internal_hash(Eterm term, Uint32 salt)
+{
+ Uint32 hash = salt ^ INTERNAL_HASH_SALT;
+
+ /* Optimization. Simple cases before declaration of estack. */
+ if (primary_tag(term) == TAG_PRIMARY_IMMED1) {
+ #if ERTS_SIZEOF_ETERM == 8
+ UINT32_HASH_2((Uint32)term, (Uint32)(term >> 32), HCONST);
+ #elif ERTS_SIZEOF_ETERM == 4
+ UINT32_HASH(term, HCONST);
+ #else
+ # error "No you don't"
+ #endif
+ return hash;
+ }
+ {
+ Eterm tmp;
+ DECLARE_ESTACK(s);
+
+ for (;;) {
+ switch (primary_tag(term)) {
+ case TAG_PRIMARY_LIST:
+ {
+ int c = 0;
+ Uint32 sh = 0;
+ Eterm* ptr = list_val(term);
+ while (is_byte(*ptr)) {
+ /* Optimization for strings. */
+ sh = (sh << 8) + unsigned_val(*ptr);
+ if (c == 3) {
+ UINT32_HASH(sh, HCONST_4);
+ c = sh = 0;
+ } else {
+ c++;
+ }
+ term = CDR(ptr);
+ if (is_not_list(term))
+ break;
+ ptr = list_val(term);
+ }
+ if (c > 0)
+ UINT32_HASH_2(sh, (Uint32)c, HCONST_22);
+
+ if (is_list(term)) {
+ tmp = CDR(ptr);
+ CONST_HASH(HCONST_17); /* Hash CAR in cons cell */
+ ESTACK_PUSH(s, tmp);
+ if (is_not_list(tmp)) {
+ ESTACK_PUSH(s, HASH_CDR);
+ }
+ term = CAR(ptr);
+ }
+ }
+ break;
+ case TAG_PRIMARY_BOXED:
+ {
+ Eterm hdr = *boxed_val(term);
+ ASSERT(is_header(hdr));
+ switch (hdr & _TAG_HEADER_MASK) {
+ case ARITYVAL_SUBTAG:
+ {
+ int i;
+ int arity = header_arity(hdr);
+ Eterm* elem = tuple_val(term);
+ UINT32_HASH(arity, HCONST_9);
+ if (arity == 0) /* Empty tuple */
+ goto pop_next;
+ for (i = arity; ; i--) {
+ term = elem[i];
+ if (i == 1)
+ break;
+ ESTACK_PUSH(s, term);
+ }
+ }
+ break;
+
+ case MAP_SUBTAG:
+ {
+ Eterm* ptr = boxed_val(term) + 1;
+ Uint size;
+ int i;
+
+ /*
+ * We rely on key-value iteration order being constant
+ * for identical maps (in this VM instance).
+ */
+ switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
+ case HAMT_SUBTAG_HEAD_FLATMAP:
+ {
+ flatmap_t *mp = (flatmap_t *)flatmap_val(term);
+ Eterm *ks = flatmap_get_keys(mp);
+ Eterm *vs = flatmap_get_values(mp);
+ size = flatmap_get_size(mp);
+ UINT32_HASH(size, HCONST_16);
+ if (size == 0)
+ goto pop_next;
+
+ for (i = size - 1; i >= 0; i--) {
+ ESTACK_PUSH(s, vs[i]);
+ ESTACK_PUSH(s, ks[i]);
+ }
+ goto pop_next;
+ }
+ case HAMT_SUBTAG_HEAD_ARRAY:
+ case HAMT_SUBTAG_HEAD_BITMAP:
+ size = *ptr++;
+ UINT32_HASH(size, HCONST_16);
+ if (size == 0)
+ goto pop_next;
+ }
+ switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
+ case HAMT_SUBTAG_HEAD_ARRAY:
+ i = 16;
+ break;
+ case HAMT_SUBTAG_HEAD_BITMAP:
+ case HAMT_SUBTAG_NODE_BITMAP:
+ i = hashmap_bitcount(MAP_HEADER_VAL(hdr));
+ break;
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "bad header");
+ }
+ while (i) {
+ if (is_list(*ptr)) {
+ Eterm* cons = list_val(*ptr);
+ ESTACK_PUSH(s, CDR(cons));
+ ESTACK_PUSH(s, CAR(cons));
+ }
+ else {
+ ASSERT(is_boxed(*ptr));
+ /* no special treatment of collision nodes needed,
+ hash them as the tuples they are */
+ ESTACK_PUSH(s, *ptr);
+ }
+ i--; ptr++;
+ }
+ goto pop_next;
+ }
+ break;
+ case FUN_SUBTAG:
+ {
+ ErlFunThing* funp = (ErlFunThing *) fun_val(term);
+
+ if (is_local_fun(funp)) {
+ ErlFunEntry* fe = funp->entry.fun;
+ Uint num_free = funp->num_free;
+ UINT32_HASH_2(num_free, fe->module, HCONST_20);
+ UINT32_HASH_2(fe->index, fe->old_uniq, HCONST_21);
+ if (num_free == 0) {
+ goto pop_next;
+ } else {
+ Eterm* bptr = funp->env + num_free - 1;
+ while (num_free-- > 1) {
+ term = *bptr--;
+ ESTACK_PUSH(s, term);
+ }
+ term = *bptr;
+ }
+ } else {
+ ASSERT(is_external_fun(funp) && funp->next == NULL);
+
+ /* Assumes Export entries never move */
+ POINTER_HASH(funp->entry.exp, HCONST_14);
+ goto pop_next;
+ }
+ }
+ break;
+ case REFC_BINARY_SUBTAG:
+ case HEAP_BINARY_SUBTAG:
+ case SUB_BINARY_SUBTAG:
+ {
+ byte* bptr;
+ Uint sz = binary_size(term);
+ Uint32 con = HCONST_13 + hash;
+ Uint bitoffs;
+ Uint bitsize;
+
+ ERTS_GET_BINARY_BYTES(term, bptr, bitoffs, bitsize);
+ if (sz == 0 && bitsize == 0) {
+ hash = con;
+ } else {
+ if (bitoffs == 0) {
+ hash = block_hash(bptr, sz, con);
+ if (bitsize > 0) {
+ UINT32_HASH_2(bitsize, (bptr[sz] >> (8 - bitsize)),
+ HCONST_15);
+ }
+ } else {
+ byte* buf = (byte *) erts_alloc(ERTS_ALC_T_TMP,
+ sz + (bitsize != 0));
+ erts_copy_bits(bptr, bitoffs, 1, buf, 0, 1, sz*8+bitsize);
+ hash = block_hash(buf, sz, con);
+ if (bitsize > 0) {
+ UINT32_HASH_2(bitsize, (buf[sz] >> (8 - bitsize)),
+ HCONST_15);
+ }
+ erts_free(ERTS_ALC_T_TMP, (void *) buf);
+ }
+ }
+ goto pop_next;
+ }
+ break;
+ case POS_BIG_SUBTAG:
+ case NEG_BIG_SUBTAG:
+ {
+ Eterm* ptr = big_val(term);
+ Uint i = 0;
+ Uint n = BIG_SIZE(ptr);
+ Uint32 con = BIG_SIGN(ptr) ? HCONST_10 : HCONST_11;
+#if D_EXP == 16
+ do {
+ Uint32 x, y;
+ x = i < n ? BIG_DIGIT(ptr, i++) : 0;
+ x += (Uint32)(i < n ? BIG_DIGIT(ptr, i++) : 0) << 16;
+ y = i < n ? BIG_DIGIT(ptr, i++) : 0;
+ y += (Uint32)(i < n ? BIG_DIGIT(ptr, i++) : 0) << 16;
+ UINT32_HASH_2(x, y, con);
+ } while (i < n);
+#elif D_EXP == 32
+ do {
+ Uint32 x, y;
+ x = i < n ? BIG_DIGIT(ptr, i++) : 0;
+ y = i < n ? BIG_DIGIT(ptr, i++) : 0;
+ UINT32_HASH_2(x, y, con);
+ } while (i < n);
+#elif D_EXP == 64
+ do {
+ Uint t;
+ Uint32 x, y;
+ ASSERT(i < n);
+ t = BIG_DIGIT(ptr, i++);
+ x = t & 0xffffffff;
+ y = t >> 32;
+ UINT32_HASH_2(x, y, con);
+ } while (i < n);
+#else
+#error "unsupported D_EXP size"
+#endif
+ goto pop_next;
+ }
+ break;
+ case REF_SUBTAG: {
+ Uint32 *numbers = internal_ref_numbers(term);
+ ASSERT(internal_ref_no_numbers(term) >= 3);
+ UINT32_HASH(numbers[0], HCONST_7);
+ UINT32_HASH_2(numbers[1], numbers[2], HCONST_8);
+ if (is_internal_pid_ref(term)) {
+#ifdef ARCH_64
+ ASSERT(internal_ref_no_numbers(term) == 5);
+ UINT32_HASH_2(numbers[3], numbers[4], HCONST_9);
+#else
+ ASSERT(internal_ref_no_numbers(term) == 4);
+ UINT32_HASH(numbers[3], HCONST_9);
+#endif
+ }
+ goto pop_next;
+ }
+ case EXTERNAL_REF_SUBTAG:
+ {
+ ExternalThing* thing = external_thing_ptr(term);
+ Uint n = external_thing_ref_no_numbers(thing);
+ Uint32 *numbers = external_thing_ref_numbers(thing);
+
+ /* Can contain 0 to 5 32-bit numbers... */
+
+ /* See limitation #2 */
+ switch (n) {
+ case 5: {
+ Uint32 num4 = numbers[4];
+ if (0) {
+ case 4:
+ num4 = 0;
+ /* Fall through... */
+ }
+ UINT32_HASH_2(numbers[3], num4, HCONST_9);
+ /* Fall through... */
+ }
+ case 3: {
+ Uint32 num2 = numbers[2];
+ if (0) {
+ case 2:
+ num2 = 0;
+ /* Fall through... */
+ }
+ UINT32_HASH_2(numbers[1], num2, HCONST_8);
+ /* Fall through... */
+ }
+ case 1:
+#ifdef ARCH_64
+ POINTER_HASH(thing->node, HCONST_7);
+ UINT32_HASH(numbers[0], HCONST_7);
+#else
+ UINT32_HASH_2(thing->node, numbers[0], HCONST_7);
+#endif
+ break;
+ case 0:
+ POINTER_HASH(thing->node, HCONST_7);
+ break;
+ default:
+ ASSERT(!"Invalid amount of external reference numbers");
+ break;
+ }
+ goto pop_next;
+ }
+ case EXTERNAL_PID_SUBTAG: {
+ ExternalThing* thing = external_thing_ptr(term);
+ /* See limitation #2 */
+ POINTER_HASH(thing->node, HCONST_5);
+ UINT32_HASH_2(thing->data.pid.num, thing->data.pid.ser, HCONST_5);
+ goto pop_next;
+ }
+ case EXTERNAL_PORT_SUBTAG: {
+ ExternalThing* thing = external_thing_ptr(term);
+ /* See limitation #2 */
+ POINTER_HASH(thing->node, HCONST_6);
+ UINT32_HASH_2(thing->data.ui32[0], thing->data.ui32[1], HCONST_6);
+ goto pop_next;
+ }
+ case FLOAT_SUBTAG:
+ {
+ FloatDef ff;
+ GET_DOUBLE(term, ff);
+ if (ff.fd == 0.0f) {
+ /* ensure positive 0.0 */
+ ff.fd = erts_get_positive_zero_float();
+ }
+ UINT32_HASH_2(ff.fw[0], ff.fw[1], HCONST_12);
+ goto pop_next;
+ }
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_internal_hash(0x%X, %lu)\n", term, salt);
+ }
+ }
+ break;
+ case TAG_PRIMARY_IMMED1:
+ #if ERTS_SIZEOF_ETERM == 8
+ UINT32_HASH_2((Uint32)term, (Uint32)(term >> 32), HCONST);
+ #else
+ UINT32_HASH(term, HCONST);
+ #endif
+ goto pop_next;
+
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_internal_hash(0x%X, %lu)\n", term, salt);
+
+ pop_next:
+ if (ESTACK_ISEMPTY(s)) {
+ DESTROY_ESTACK(s);
+
+ return hash;
+ }
+
+ term = ESTACK_POP(s);
+
+ switch (term) {
+ case HASH_CDR:
+ CONST_HASH(HCONST_18); /* Hash CDR i cons cell */
+ goto pop_next;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+}
+
+#ifdef DBG_HASHMAP_COLLISION_BONANZA
+Uint32 erts_dbg_hashmap_collision_bonanza(Uint32 hash, Eterm key)
+{
+/*{
+ static Uint32 hashvec[7] = {
+ 0x02345678,
+ 0x12345678,
+ 0xe2345678,
+ 0xf2345678,
+ 0x12abcdef,
+ 0x13abcdef,
+ 0xcafebabe
+ };
+ hash = hashvec[hash % (sizeof(hashvec) / sizeof(hashvec[0]))];
+ }*/
+ const Uint32 bad_hash = (hash & 0x12482481) * 1442968193;
+ const Uint32 bad_bits = hash % 67;
+ if (bad_bits < 32) {
+ /* Mix in a number of high good bits to get "randomly" close
+ to the collision nodes */
+ const Uint32 bad_mask = (1 << bad_bits) - 1;
+ return (hash & ~bad_mask) | (bad_hash & bad_mask);
+ }
+ return bad_hash;
+}
+#endif
+
+/* Term hash function for hashmaps */
+Uint32 make_map_hash(Eterm key) {
+ Uint32 hash;
+
+ hash = make_internal_hash(key, 0);
+
+#ifdef DBG_HASHMAP_COLLISION_BONANZA
+ hash = erts_dbg_hashmap_collision_bonanza(hash, key);
+#endif
+ return hash;
+}
+
+#undef CONST_HASH
+#undef HASH_MAP_TAIL
+#undef HASH_MAP_PAIR
+#undef HASH_CDR
+
+#undef UINT32_HASH_2
+#undef UINT32_HASH
+#undef SINT32_HASH
+
+#undef HCONST
+#undef MIX
diff --git a/erts/emulator/beam/erl_term_hashing.h b/erts/emulator/beam/erl_term_hashing.h
new file mode 100644
index 0000000000..8a898b7c52
--- /dev/null
+++ b/erts/emulator/beam/erl_term_hashing.h
@@ -0,0 +1,79 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022-2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+#ifndef ERL_TERM_HASHING_H__
+#define ERL_TERM_HASHING_H__
+
+#include "sys.h"
+#include "erl_drv_nif.h"
+
+#if (defined(__aarch64__) && defined(__ARM_FEATURE_CRC32)) || \
+ (defined(__x86_64__) && defined(__SSE4_2__))
+# define ERL_INTERNAL_HASH_CRC32C
+#endif
+
+typedef struct {
+ Uint32 a,b,c;
+} ErtsBlockHashHelperCtx;
+
+typedef struct {
+ ErtsBlockHashHelperCtx hctx;
+ const byte *ptr;
+ Uint len;
+ Uint tot_len;
+} ErtsBlockHashState;
+
+typedef struct {
+ ErtsBlockHashHelperCtx hctx;
+ SysIOVec* iov;
+ Uint vlen;
+ Uint tot_len;
+ int vix;
+ int ix;
+} ErtsIovBlockHashState;
+
+Uint32 make_hash2(Eterm);
+Uint32 trapping_make_hash2(Eterm, Eterm*, struct process*);
+Uint32 make_hash(Eterm);
+Uint32 make_internal_hash(Eterm, Uint32 salt);
+#ifdef DEBUG
+# define DBG_HASHMAP_COLLISION_BONANZA
+#endif
+#ifdef DBG_HASHMAP_COLLISION_BONANZA
+Uint32 erts_dbg_hashmap_collision_bonanza(Uint32 hash, Eterm key);
+#endif
+Uint32 make_map_hash(Eterm key);
+void erts_block_hash_init(ErtsBlockHashState *state,
+ const byte *ptr,
+ Uint len,
+ Uint32 initval);
+int erts_block_hash(Uint32 *hashp,
+ Uint *sizep,
+ ErtsBlockHashState *state);
+void erts_iov_block_hash_init(ErtsIovBlockHashState *state,
+ SysIOVec *iov,
+ Uint vlen,
+ Uint32 initval);
+int erts_iov_block_hash(Uint32 *hashp,
+ Uint *sizep,
+ ErtsIovBlockHashState *state);
+
+
+#endif
diff --git a/erts/emulator/beam/erl_threads.h b/erts/emulator/beam/erl_threads.h
index d3639e1135..ec43772bca 100644
--- a/erts/emulator/beam/erl_threads.h
+++ b/erts/emulator/beam/erl_threads.h
@@ -430,6 +430,7 @@ ERTS_GLB_INLINE void erts_thr_exit(void *res);
ERTS_GLB_INLINE void erts_thr_install_exit_handler(void (*exit_handler)(void));
ERTS_GLB_INLINE erts_tid_t erts_thr_self(void);
ERTS_GLB_INLINE int erts_thr_getname(erts_tid_t tid, char *buf, size_t len);
+ERTS_GLB_INLINE void erts_thr_setname(char *buf);
ERTS_GLB_INLINE int erts_equal_tids(erts_tid_t x, erts_tid_t y);
ERTS_GLB_INLINE void erts_mtx_init(erts_mtx_t *mtx,
const char *name,
@@ -1623,6 +1624,13 @@ erts_thr_getname(erts_tid_t tid, char *buf, size_t len)
return ethr_getname(tid, buf, len);
}
+ERTS_GLB_INLINE void
+erts_thr_setname(char *buf)
+{
+ if (strlen(buf) > ETHR_THR_NAME_MAX)
+ erts_thr_fatal_error(EINVAL, "too long thread name");
+ ethr_setname(buf);
+}
ERTS_GLB_INLINE int
erts_equal_tids(erts_tid_t x, erts_tid_t y)
diff --git a/erts/emulator/beam/erl_trace.c b/erts/emulator/beam/erl_trace.c
index 82d4cf728c..2dd6c99d4c 100644
--- a/erts/emulator/beam/erl_trace.c
+++ b/erts/emulator/beam/erl_trace.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1999-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1999-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -2473,7 +2473,7 @@ init_sys_msg_dispatcher(void)
{
erts_thr_opts_t thr_opts = ERTS_THR_OPTS_DEFAULT_INITER;
thr_opts.detached = 1;
- thr_opts.name = "sys_msg_dispatcher";
+ thr_opts.name = "erts_smsg_disp";
init_smq_element_alloc();
sys_message_queue = NULL;
sys_message_queue_end = NULL;
@@ -3079,7 +3079,7 @@ erts_tracer_update(ErtsTracer *tracer, const ErtsTracer new_tracer)
}
}
-static void init_tracer_nif()
+static void init_tracer_nif(void)
{
erts_rwmtx_opt_t rwmtx_opt = ERTS_RWMTX_OPT_DEFAULT_INITER;
rwmtx_opt.type = ERTS_RWMTX_TYPE_EXTREMELY_FREQUENT_READ;
@@ -3092,7 +3092,7 @@ static void init_tracer_nif()
}
-int erts_tracer_nif_clear()
+int erts_tracer_nif_clear(void)
{
erts_rwmtx_rlock(&tracer_mtx);
diff --git a/erts/emulator/beam/erl_trace.h b/erts/emulator/beam/erl_trace.h
index 1c0fed9658..e67011e46b 100644
--- a/erts/emulator/beam/erl_trace.h
+++ b/erts/emulator/beam/erl_trace.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2012-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2012-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -172,6 +172,7 @@ struct trace_pattern_flags {
unsigned int meta : 1; /* Metadata trace breakpoint */
unsigned int call_count : 1; /* Fast call count breakpoint */
unsigned int call_time : 1; /* Fast call time breakpoint */
+ unsigned int call_memory: 1; /* Fast memory tracing breakpoint */
};
extern const struct trace_pattern_flags erts_trace_pattern_flags_off;
extern int erts_call_time_breakpoint_tracing;
diff --git a/erts/emulator/beam/erl_utils.h b/erts/emulator/beam/erl_utils.h
index 1585bbc6ec..dce3665a8d 100644
--- a/erts/emulator/beam/erl_utils.h
+++ b/erts/emulator/beam/erl_utils.h
@@ -24,6 +24,7 @@
#include "sys.h"
#include "atom.h"
#include "erl_printf.h"
+#include "erl_term_hashing.h"
struct process;
@@ -63,23 +64,11 @@ erts_current_interval_acqb(erts_interval_t *icp)
*/
void erts_silence_warn_unused_result(long unused);
-
int erts_fit_in_bits_int64(Sint64);
int erts_fit_in_bits_int32(Sint32);
int erts_fit_in_bits_uint(Uint);
Sint erts_list_length(Eterm);
int erts_is_builtin(Eterm, Eterm, int);
-Uint32 make_hash2(Eterm);
-Uint32 trapping_make_hash2(Eterm, Eterm*, struct process*);
-#ifdef DEBUG
-# define DBG_HASHMAP_COLLISION_BONANZA
-#endif
-#ifdef DBG_HASHMAP_COLLISION_BONANZA
-Uint32 erts_dbg_hashmap_collision_bonanza(Uint32 hash, Eterm key);
-#endif
-Uint32 make_hash(Eterm);
-Uint32 make_internal_hash(Eterm, Uint32 salt);
-Uint32 make_map_hash(Eterm key);
void erts_save_emu_args(int argc, char **argv);
Eterm erts_get_emu_args(struct process *c_p);
@@ -123,6 +112,7 @@ int eq(Eterm, Eterm);
ERTS_GLB_INLINE Sint erts_cmp(Eterm, Eterm, int, int);
ERTS_GLB_INLINE int erts_cmp_atoms(Eterm a, Eterm b);
+ERTS_GLB_INLINE Sint erts_cmp_flatmap_keys(Eterm, Eterm);
Sint erts_cmp_compound(Eterm, Eterm, int, int);
@@ -242,6 +232,17 @@ ERTS_GLB_INLINE Sint erts_cmp(Eterm a, Eterm b, int exact, int eq_only) {
return erts_cmp_compound(a,b,exact,eq_only);
}
+/*
+ * Only to be used for the *internal* sort order of flatmap keys.
+ */
+ERTS_GLB_INLINE Sint erts_cmp_flatmap_keys(Eterm key_a, Eterm key_b) {
+ if (is_atom(key_a) && is_atom(key_b)) {
+ return key_a - key_b;
+ }
+ return erts_cmp(key_a, key_b, 1, 0);
+}
+
+
#endif /* ERTS_GLB_INLINE_INCL_FUNC_DEF */
#endif
diff --git a/erts/emulator/beam/erl_vm.h b/erts/emulator/beam/erl_vm.h
index 54df471cda..6d5b7c6e1b 100644
--- a/erts/emulator/beam/erl_vm.h
+++ b/erts/emulator/beam/erl_vm.h
@@ -298,8 +298,8 @@ extern void** beam_ops;
#ifndef BEAMASM
-#define BeamIsReturnTimeTrace(w) \
- BeamIsOpCode(*(const BeamInstr*)(w), op_i_return_time_trace)
+#define BeamIsReturnCallAccTrace(w) \
+ BeamIsOpCode(*(const BeamInstr*)(w), op_i_call_trace_return)
#define BeamIsReturnToTrace(w) \
BeamIsOpCode(*(const BeamInstr*)(w), op_i_return_to_trace)
#define BeamIsReturnTrace(w) \
@@ -307,8 +307,8 @@ extern void** beam_ops;
#else /* BEAMASM */
-#define BeamIsReturnTimeTrace(w) \
- ((w) == beam_return_time_trace)
+#define BeamIsReturnCallAccTrace(w) \
+ ((w) == beam_call_trace_return)
#define BeamIsReturnToTrace(w) \
((w) == beam_return_to_trace)
#define BeamIsReturnTrace(w) \
@@ -316,9 +316,14 @@ extern void** beam_ops;
#endif /* BEAMASM */
+/* Stack frame sizes (not including CP_SIZE) */
+#define BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ 2
+#define BEAM_RETURN_TO_TRACE_FRAME_SZ 0
+#define BEAM_RETURN_TRACE_FRAME_SZ 2
+
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
ERTS_GLB_INLINE
-int erts_cp_size()
+int erts_cp_size(void)
{
if (erts_frame_layout == ERTS_FRAME_LAYOUT_RA) {
return 1;
diff --git a/erts/emulator/beam/export.c b/erts/emulator/beam/export.c
index fdaf1a15a5..af5df9ece7 100644
--- a/erts/emulator/beam/export.c
+++ b/erts/emulator/beam/export.c
@@ -293,7 +293,7 @@ erts_export_put(Eterm mod, Eterm func, unsigned int arity)
res = ee->ep;
#ifdef BEAMASM
- res->dispatch.addresses[ERTS_SAVE_CALLS_CODE_IX] = beam_save_calls;
+ res->dispatch.addresses[ERTS_SAVE_CALLS_CODE_IX] = beam_save_calls_export;
#endif
return res;
@@ -337,7 +337,7 @@ erts_export_get_or_make_stub(Eterm mod, Eterm func, unsigned int arity)
#ifdef BEAMASM
ep->dispatch.addresses[ERTS_SAVE_CALLS_CODE_IX] =
- beam_save_calls;
+ beam_save_calls_export;
#endif
ASSERT(ep);
@@ -387,7 +387,7 @@ Export *export_get(Export *e)
return entry ? entry->ep : NULL;
}
-IF_DEBUG(static ErtsCodeIndex debug_start_load_ix = 0;)
+IF_DEBUG(static ErtsCodeIndex debug_export_load_ix = 0;)
void export_start_staging(void)
@@ -399,7 +399,7 @@ void export_start_staging(void)
int i;
ASSERT(dst_ix != src_ix);
- ASSERT(debug_start_load_ix == -1);
+ ASSERT(debug_export_load_ix == ~0);
export_staging_lock();
/*
@@ -426,12 +426,12 @@ void export_start_staging(void)
}
export_staging_unlock();
- IF_DEBUG(debug_start_load_ix = dst_ix);
+ IF_DEBUG(debug_export_load_ix = dst_ix);
}
void export_end_staging(int commit)
{
- ASSERT(debug_start_load_ix == erts_staging_code_ix());
- IF_DEBUG(debug_start_load_ix = -1);
+ ASSERT(debug_export_load_ix == erts_staging_code_ix());
+ IF_DEBUG(debug_export_load_ix = ~0);
}
diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c
index d0a2c61834..081ce23e49 100644
--- a/erts/emulator/beam/external.c
+++ b/erts/emulator/beam/external.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,6 +47,8 @@
#include "erl_proc_sig_queue.h"
#include "erl_trace.h"
#include "erl_global_literals.h"
+#include "erl_term_hashing.h"
+
#define PASS_THROUGH 'p'
@@ -95,8 +97,8 @@ static byte* enc_atom(ErtsAtomCacheMap *, Eterm, byte*, Uint64);
static byte* enc_pid(ErtsAtomCacheMap *, Eterm, byte*, Uint64);
struct B2TContext_t;
static const byte* dec_term(ErtsDistExternal*, ErtsHeapFactory*, const byte*, Eterm*, struct B2TContext_t*, int);
-static const byte* dec_atom(ErtsDistExternal *, const byte*, Eterm*);
-static const byte* dec_pid(ErtsDistExternal *, ErtsHeapFactory*, const byte*, Eterm*, byte tag);
+static const byte* dec_atom(ErtsDistExternal *, const byte*, Eterm*, int);
+static const byte* dec_pid(ErtsDistExternal *, ErtsHeapFactory*, const byte*, Eterm*, byte tag, int);
static Sint decoded_size(const byte *ep, const byte* endp, int internal_tags, struct B2TContext_t*);
static BIF_RETTYPE term_to_binary_trap_1(BIF_ALIST_1);
@@ -104,7 +106,6 @@ static Eterm erts_term_to_binary_int(Process* p, Sint bif_ix, Eterm Term, Eterm
Uint64 dflags, Binary *context_b, int iovec,
Uint fragment_size);
-static Uint encode_size_struct2(ErtsAtomCacheMap *, Eterm, Uint64);
static ErtsExtSzRes encode_size_struct_int(TTBSizeContext*, ErtsAtomCacheMap *acmp,
Eterm obj, Uint64 dflags, Sint *reds, Uint *res);
@@ -126,6 +127,72 @@ void erts_init_external(void) {
return;
}
+static Uint32 local_node_hash;
+
+void erts_late_init_external(void)
+{
+ char hname[256], pidstr[21];
+ size_t hname_len, pidstr_len;
+ ErtsMonotonicTime mtime, toffs;
+ ErtsBlockHashState hstate;
+ byte *lnid;
+ Uint lnid_ix, chunk_size;
+ int res;
+
+ res = sys_get_hostname(&hname[0], sizeof(hname));
+ if (res == 0) {
+ hname_len = strlen(hname);
+ }
+ else {
+ hname[0] = '\0';
+ hname_len = 0;
+ }
+
+ sys_get_pid(&pidstr[0], sizeof(pidstr));
+ pidstr[20] = '\0';
+
+ pidstr_len = strlen(pidstr);
+
+ toffs = erts_get_time_offset();
+ mtime = erts_get_monotonic_time(NULL);
+
+ lnid = (byte *) erts_alloc(ERTS_ALC_T_TMP, 8 + hname_len + pidstr_len);
+
+ lnid_ix = 0;
+
+ /* time offset... */
+ lnid[lnid_ix++] = (byte) toffs & 0xff;
+ lnid[lnid_ix++] = (byte) (toffs >> 8) & 0xff;
+ lnid[lnid_ix++] = (byte) (toffs >> 16) & 0xff;
+ lnid[lnid_ix++] = (byte) (toffs >> 24) & 0xff;
+ lnid[lnid_ix++] = (byte) (toffs >> 32) & 0xff;
+ lnid[lnid_ix++] = (byte) (toffs >> 40) & 0xff;
+ lnid[lnid_ix++] = (byte) (toffs >> 48) & 0xff;
+ lnid[lnid_ix++] = (byte) (toffs >> 56) & 0xff;
+
+ /* hostname... */
+
+ sys_memcpy(&lnid[lnid_ix], &hname[0], hname_len);
+
+ lnid_ix += hname_len;
+
+ /* pid... */
+ memcpy(&lnid[lnid_ix], &pidstr[0], pidstr_len);
+
+ /*
+ * Use least significant 32 bits of monotonic time as initial
+ * value to hash...
+ */
+ erts_block_hash_init(&hstate, &lnid[0], lnid_ix,
+ (Uint32) (mtime & 0xffffffff));
+ chunk_size = ERTS_UINT_MAX;
+ res = erts_block_hash(&local_node_hash, &chunk_size, &hstate);
+ ASSERT(res); (void) res;
+ ASSERT(chunk_size == lnid_ix);
+
+ erts_free(ERTS_ALC_T_TMP, lnid);
+}
+
#define ERTS_MAX_INTERNAL_ATOM_CACHE_ENTRIES 255
#define ERTS_DIST_HDR_ATOM_CACHE_FLAG_BYTE_IX(IIX) \
@@ -659,7 +726,7 @@ erts_encode_dist_ext_size(Eterm term,
return res;
}
-ErtsExtSzRes erts_encode_ext_size_2(Eterm term, unsigned dflags, Uint *szp)
+ErtsExtSzRes erts_encode_ext_size_2(Eterm term, Uint64 dflags, Uint *szp)
{
ErtsExtSzRes res;
*szp = 0;
@@ -675,8 +742,14 @@ ErtsExtSzRes erts_encode_ext_size(Eterm term, Uint *szp)
Uint erts_encode_ext_size_ets(Eterm term)
{
- return encode_size_struct2(NULL, term,
- TERM_TO_BINARY_DFLAGS|DFLAG_ETS_COMPRESSED);
+ ErtsExtSzRes res;
+ Uint sz = 0;
+
+ res = encode_size_struct_int(NULL, NULL, term,
+ TERM_TO_BINARY_DFLAGS|DFLAG_ETS_COMPRESSED,
+ NULL, &sz);
+ ASSERT(res == ERTS_EXT_SZ_OK); (void) res;
+ return sz;
}
@@ -1339,7 +1412,7 @@ static BIF_RETTYPE term_to_binary_trap_1(BIF_ALIST_1)
0, 0,bin, 0, ~((Uint) 0));
if (is_non_value(res)) {
if (erts_set_gc_state(BIF_P, 1)
- || MSO(BIF_P).overhead > BIN_VHEAP_SZ(BIF_P)) {
+ || MSO(BIF_P).overhead > BIF_P->bin_vheap_sz) {
ERTS_VBUMP_ALL_REDS(BIF_P);
}
if (Opts == am_undefined)
@@ -1354,7 +1427,7 @@ static BIF_RETTYPE term_to_binary_trap_1(BIF_ALIST_1)
BIF_TRAP1(&term_to_binary_trap_export,BIF_P,res);
} else {
if (erts_set_gc_state(BIF_P, 1)
- || MSO(BIF_P).overhead > BIN_VHEAP_SZ(BIF_P))
+ || MSO(BIF_P).overhead > BIF_P->bin_vheap_sz)
ERTS_BIF_YIELD_RETURN(BIF_P, res);
else
BIF_RET(res);
@@ -1400,12 +1473,12 @@ BIF_RETTYPE term_to_iovec_1(BIF_ALIST_1)
}
static ERTS_INLINE int
-parse_t2b_opts(Eterm opts, Uint *flagsp, int *levelp, int *iovecp, Uint *fsizep)
+parse_t2b_opts(Eterm opts, Uint64 *flagsp, int *levelp, int *iovecp, Uint *fsizep)
{
int level = 0;
int iovec = 0;
- Uint flags = TERM_TO_BINARY_DFLAGS;
- int deterministic = 0;
+ Uint64 flags = TERM_TO_BINARY_DFLAGS;
+ int deterministic = 0, local = 0;
Uint fsize = ~((Uint) 0); /* one fragment */
while (is_list(opts)) {
@@ -1418,17 +1491,20 @@ parse_t2b_opts(Eterm opts, Uint *flagsp, int *levelp, int *iovecp, Uint *fsizep)
iovec = !0;
} else if (arg == am_deterministic) {
deterministic = 1;
+ } else if (arg == am_local) {
+ local = !0;
} else if (is_tuple(arg) && *(tp = tuple_val(arg)) == make_arityval(2)) {
if (tp[1] == am_minor_version && is_small(tp[2])) {
switch (signed_val(tp[2])) {
case 0:
- flags = TERM_TO_BINARY_DFLAGS & ~DFLAG_NEW_FLOATS;
+ flags = (TERM_TO_BINARY_DFLAGS
+ & ~(DFLAG_NEW_FLOATS | DFLAG_UTF8_ATOMS));
break;
case 1: /* Current default... */
- flags = TERM_TO_BINARY_DFLAGS;
+ flags = TERM_TO_BINARY_DFLAGS & ~DFLAG_UTF8_ATOMS;
break;
case 2:
- flags = TERM_TO_BINARY_DFLAGS | DFLAG_UTF8_ATOMS;
+ flags = TERM_TO_BINARY_DFLAGS;
break;
default:
return 0; /* badarg */
@@ -1459,6 +1535,14 @@ parse_t2b_opts(Eterm opts, Uint *flagsp, int *levelp, int *iovecp, Uint *fsizep)
return 0; /* badarg */
}
+ if (deterministic && local) {
+ return 0; /* badarg */
+ }
+
+ if (local) {
+ flags |= DFLAG_LOCAL_EXT;
+ }
+
if (deterministic) {
flags |= DFLAG_DETERMINISTIC;
}
@@ -1476,7 +1560,7 @@ parse_t2b_opts(Eterm opts, Uint *flagsp, int *levelp, int *iovecp, Uint *fsizep)
BIF_RETTYPE term_to_binary_2(BIF_ALIST_2)
{
int level;
- Uint flags;
+ Uint64 flags;
Eterm res;
if (!parse_t2b_opts(BIF_ARG_2, &flags, &level, NULL, NULL)) {
@@ -1503,7 +1587,7 @@ BIF_RETTYPE term_to_binary_2(BIF_ALIST_2)
BIF_RETTYPE term_to_iovec_2(BIF_ALIST_2)
{
int level;
- Uint flags;
+ Uint64 flags;
Eterm res;
if (!parse_t2b_opts(BIF_ARG_2, &flags, &level, NULL, NULL)) {
@@ -1532,7 +1616,7 @@ erts_debug_term_to_binary(Process *p, Eterm term, Eterm opts)
{
Eterm ret;
int level, iovec;
- Uint flags;
+ Uint64 flags;
Uint fsize;
if (!parse_t2b_opts(opts, &flags, &level, &iovec, &fsize)) {
@@ -1579,9 +1663,12 @@ enum B2TState { /* order is somewhat significant */
typedef struct {
Sint heap_size;
- int terms;
- const byte* ep;
+ Uint32 terms;
int atom_extra_skip;
+ const byte* ep;
+ ErtsBlockHashState lext_state;
+ const byte *lext_hash;
+ Uint32 lext_term_end;
} B2TSizeContext;
typedef struct {
@@ -1590,6 +1677,7 @@ typedef struct {
Eterm* next;
ErtsHeapFactory factory;
int remaining_n;
+ int internal_nc;
char* remaining_bytes;
ErtsPStack map_array;
} B2TDecodeContext;
@@ -1819,7 +1907,6 @@ static BIF_RETTYPE binary_to_term_trap_1(BIF_ALIST_1)
return binary_to_term_int(BIF_P, THE_NON_VALUE, ERTS_MAGIC_BIN_DATA(context_bin));
}
-
#define B2T_BYTES_PER_REDUCTION 128
#define B2T_MEMCPY_FACTOR 8
@@ -1946,6 +2033,7 @@ static BIF_RETTYPE binary_to_term_int(Process* p, Eterm bin, B2TContext *ctx)
ctx->u.dc.ep = ctx->b2ts.extp;
ctx->u.dc.res = (Eterm) (UWord) NULL;
ctx->u.dc.next = &ctx->u.dc.res;
+ ctx->u.dc.internal_nc = 0;
erts_factory_proc_prealloc_init(&ctx->u.dc.factory, p, ctx->heap_size);
ctx->u.dc.map_array.pstart = NULL;
ctx->state = B2TDecode;
@@ -2117,34 +2205,11 @@ Eterm
external_size_2(BIF_ALIST_2)
{
Uint size = 0;
- Uint flags = TERM_TO_BINARY_DFLAGS;
-
- while (is_list(BIF_ARG_2)) {
- Eterm arg = CAR(list_val(BIF_ARG_2));
- Eterm* tp;
+ int level;
+ Uint64 flags;
- if (is_tuple(arg) && *(tp = tuple_val(arg)) == make_arityval(2)) {
- if (tp[1] == am_minor_version && is_small(tp[2])) {
- switch (signed_val(tp[2])) {
- case 0:
- flags &= ~DFLAG_NEW_FLOATS;
- break;
- case 1:
- break;
- default:
- goto error;
- }
- } else {
- goto error;
- }
- } else {
- error:
- BIF_ERROR(BIF_P, BADARG);
- }
- BIF_ARG_2 = CDR(list_val(BIF_ARG_2));
- }
- if (is_not_nil(BIF_ARG_2)) {
- goto error;
+ if (!parse_t2b_opts(BIF_ARG_2, &flags, &level, NULL, NULL)) {
+ BIF_ERROR(BIF_P, BADARG);
}
switch (erts_encode_ext_size_2(BIF_ARG_1, flags, &size)) {
@@ -2816,45 +2881,80 @@ enc_atom(ErtsAtomCacheMap *acmp, Eterm atom, byte *ep, Uint64 dflags)
}
/*
- * We use this atom as sysname in local pid/port/refs
- * for the ETS compressed format
+ * We use INTERNAL_LOCAL_SYSNAME to mark internal node for pid/port/refs
+ * in the ETS compressed format and local format.
*
*/
-#define INTERNAL_LOCAL_SYSNAME am_ErtsSecretAtom
+#define INTERNAL_LOCAL_SYSNAME NIL
-static byte*
-enc_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint64 dflags)
+static ERTS_INLINE byte *
+enc_internal_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint64 dflags)
{
- Uint on, os;
- Eterm sysname = ((is_internal_pid(pid) && (dflags & DFLAG_ETS_COMPRESSED))
- ? INTERNAL_LOCAL_SYSNAME : pid_node_name(pid));
- Uint32 creation = pid_creation(pid);
+ Uint32 number, serial, creation;
- *ep++ = NEW_PID_EXT;
+ ASSERT(is_internal_pid(pid));
- ep = enc_atom(acmp, sysname, ep, dflags);
+ *ep++ = NEW_PID_EXT;
- if (is_internal_pid(pid)) {
- on = internal_pid_number(pid);
- os = internal_pid_serial(pid);
+ number = internal_pid_number(pid);
+ serial = internal_pid_serial(pid);
+ if (dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT)) {
+ *ep++ = NIL_EXT; /* INTERNAL_LOCAL_NODE */
+ creation = 0;
}
else {
- on = external_pid_number(pid);
- os = external_pid_serial(pid);
+ Eterm sysname = internal_pid_node_name(pid);
+ creation = internal_pid_creation(pid);
+ ep = enc_atom(acmp, sysname, ep, dflags);
}
- put_int32(on, ep);
+ put_int32(number, ep);
+ ep += 4;
+ put_int32(serial, ep);
+ ep += 4;
+ put_int32(creation, ep);
+ ep += 4;
+ return ep;
+
+}
+
+static ERTS_INLINE byte *
+enc_external_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint64 dflags)
+{
+ Uint32 number, serial, creation;
+ Eterm sysname;
+
+ ASSERT(is_external_pid(pid));
+
+ *ep++ = NEW_PID_EXT;
+
+ ASSERT(is_external_pid(pid));
+ number = external_pid_number(pid);
+ serial = external_pid_serial(pid);
+ sysname = external_pid_node_name(pid);
+ creation = external_pid_creation(pid);
+ ep = enc_atom(acmp, sysname, ep, dflags);
+
+ put_int32(number, ep);
ep += 4;
- put_int32(os, ep);
+ put_int32(serial, ep);
ep += 4;
put_int32(creation, ep);
ep += 4;
return ep;
}
+static byte*
+enc_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint64 dflags)
+{
+ if (is_internal_pid(pid))
+ return enc_internal_pid(acmp, pid, ep, dflags);
+ return enc_external_pid(acmp, pid, ep, dflags);
+}
+
/* Expect an atom in plain text or cached */
static const byte*
-dec_atom(ErtsDistExternal *edep, const byte* ep, Eterm* objp)
+dec_atom(ErtsDistExternal *edep, const byte* ep, Eterm* objp, int internal_nc)
{
Uint len;
int n;
@@ -2919,7 +3019,12 @@ dec_atom(ErtsDistExternal *edep, const byte* ep, Eterm* objp)
}
*objp = make_atom(n);
break;
-
+ case NIL_EXT:
+ if (internal_nc) {
+ *objp = INTERNAL_LOCAL_SYSNAME;
+ break;
+ }
+ /* else: fail... */
default:
error:
*objp = NIL; /* Don't leave a hole in the heap */
@@ -2947,7 +3052,7 @@ static ERTS_INLINE ErlNode* dec_get_node(Eterm sysname, Uint32 creation, Eterm b
static const byte*
dec_pid(ErtsDistExternal *edep, ErtsHeapFactory* factory, const byte* ep,
- Eterm* objp, byte tag)
+ Eterm* objp, byte tag, int internal_nc)
{
Eterm sysname;
Uint data;
@@ -2958,7 +3063,7 @@ dec_pid(ErtsDistExternal *edep, ErtsHeapFactory* factory, const byte* ep,
*objp = NIL; /* In case we fail, don't leave a hole in the heap */
/* eat first atom */
- if ((ep = dec_atom(edep, ep, &sysname)) == NULL)
+ if ((ep = dec_atom(edep, ep, &sysname, internal_nc)) == NULL)
return NULL;
num = get_uint32(ep);
ep += 4;
@@ -3071,6 +3176,7 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
FloatDef f;
register Sint r = 0;
int use_iov = 0;
+ byte *lext_hash = NULL; /* initialize to avoid faulty warning... */
/* The following variables are only used during encoding of
* a map when the `deterministic` option is active. */
@@ -3088,10 +3194,39 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
obj = ctx->obj;
map_array = ctx->map_array;
next_map_element = ctx->next_map_element;
+ lext_hash = ctx->lext_hash;
if (is_non_value(obj)) {
goto outer_loop;
}
+ else {
+ goto L_jump_start;
+ }
}
+ if (ctx->continue_make_lext_hash) {
+ lext_hash = ctx->lext_hash;
+ ep = ctx->ep;
+ if (use_iov) {
+ goto continue_make_lext_hash_iov;
+ }
+ else {
+ goto continue_make_lext_hash_bin;
+ }
+ }
+ }
+
+ /* We only pass here once at the start of the encoding... */
+ if (dflags & DFLAG_LOCAL_EXT) {
+ *ep++ = LOCAL_EXT;
+ lext_hash = ep;
+ if (ctx)
+ ctx->lext_hash = ep;
+ ep += 4; /* 32-bit hash */
+ if (use_iov) {
+ ASSERT(ctx);
+ store_in_vec(ctx, ep, NULL, THE_NON_VALUE, NULL, 0);
+ /* current vlen is now where to start calculating the hash */
+ ctx->lext_vlen = ctx->vlen;
+ }
}
goto L_jump_start;
@@ -3210,7 +3345,6 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
long num_reductions = r;
n = next_map_element - map_array;
- ASSERT(n > MAP_SMALL_MAP_LIMIT);
if (ctx == NULL) {
/* No context means that the external representation of term
* being encoded will fit in a heap binary (64 bytes). This
@@ -3375,54 +3509,89 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
}
break;
- case PID_DEF:
case EXTERNAL_PID_DEF:
- ep = enc_pid(acmp, obj, ep, dflags);
+ ep = enc_external_pid(acmp, obj, ep, dflags);
+ break;
+
+ case PID_DEF:
+ ep = enc_internal_pid(acmp, obj, ep, dflags);
break;
- case REF_DEF:
case EXTERNAL_REF_DEF: {
+ Eterm sysname;
Uint32 *ref_num;
- Eterm sysname = (((dflags & DFLAG_ETS_COMPRESSED) && is_internal_ref(obj))
- ? INTERNAL_LOCAL_SYSNAME : ref_node_name(obj));
- Uint32 creation = ref_creation(obj);
+ Uint32 creation;
- erts_magic_ref_save_bin(obj);
+ *ep++ = NEWER_REFERENCE_EXT;
+
+ ref_num = external_ref_numbers(obj);
+ i = external_ref_no_numbers(obj);
+ put_int16(i, ep);
+ ep += 2;
+
+ sysname = external_ref_node_name(obj);
+ creation = external_ref_creation(obj);
+ goto ref_common;
+
+ case REF_DEF:
*ep++ = NEWER_REFERENCE_EXT;
- i = ref_no_numbers(obj);
- put_int16(i, ep);
- ep += 2;
- ep = enc_atom(acmp, sysname, ep, dflags);
+
+ erts_magic_ref_save_bin(obj);
+
+ ref_num = internal_ref_numbers(obj);
+ i = internal_ref_no_numbers(obj);
+ put_int16(i, ep);
+ ep += 2;
+
+ if (dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT)) {
+ *ep++ = NIL_EXT; /* INTERNAL_LOCAL_NODE */
+ creation = 0;
+ }
+ else {
+ sysname = internal_ref_node_name(obj);
+ creation = internal_ref_creation(obj);
+ ref_common:
+ ep = enc_atom(acmp, sysname, ep, dflags);
+ }
+
put_int32(creation, ep);
ep += 4;
- ref_num = ref_numbers(obj);
for (j = 0; j < i; j++) {
put_int32(ref_num[j], ep);
ep += 4;
}
break;
}
- case PORT_DEF:
case EXTERNAL_PORT_DEF: {
- Eterm sysname = (((dflags & DFLAG_ETS_COMPRESSED) && is_internal_port(obj))
- ? INTERNAL_LOCAL_SYSNAME : port_node_name(obj));
- Uint32 creation = port_creation(obj);
- byte *tagp = ep++;
- Uint64 num;
-
- ep = enc_atom(acmp, sysname, ep, dflags);
- num = port_number(obj);
- if (num > ERTS_MAX_V3_PORT_NUMBER) {
- *tagp = V4_PORT_EXT;
- put_int64(num, ep);
- ep += 8;
- }
- else {
- *tagp = NEW_PORT_EXT;
- put_int32(num, ep);
- ep += 4;
- }
+ Eterm sysname;
+ Uint64 number;
+ Uint32 creation;
+
+ *ep++ = V4_PORT_EXT;
+ number = external_port_number(obj);
+ sysname = external_port_node_name(obj);
+ creation = external_port_creation(obj);
+
+ goto port_common;
+
+ case PORT_DEF:
+
+ *ep++ = V4_PORT_EXT;
+ number = internal_port_number(obj);
+ if (dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT)) {
+ *ep++ = NIL_EXT; /* INTERNAL_LOCAL_NODE */
+ creation = 0;
+ }
+ else {
+ sysname = internal_port_node_name(obj);
+ creation = internal_port_creation(obj);
+ port_common:
+ ep = enc_atom(acmp, sysname, ep, dflags);
+ }
+
+ put_int64(number, ep);
+ ep += 8;
put_int32(creation, ep);
ep += 4;
break;
@@ -3481,9 +3650,20 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
Eterm *kptr = flatmap_get_keys(mp);
Eterm *vptr = flatmap_get_values(mp);
- WSTACK_PUSH4(s, (UWord)kptr, (UWord)vptr,
- ENC_MAP_PAIR, size);
- }
+ if (dflags & DFLAG_DETERMINISTIC) {
+ ASSERT(map_array == NULL);
+ next_map_element = map_array = alloc_map_array(size);
+ while (size--) {
+ *next_map_element++ = *kptr++;
+ *next_map_element++ = *vptr++;
+ }
+ WSTACK_PUSH2(s, ENC_START_SORTING_MAP, THE_NON_VALUE);
+ }
+ else {
+ WSTACK_PUSH4(s, (UWord)kptr, (UWord)vptr,
+ ENC_MAP_PAIR, size);
+ }
+ }
} else {
Eterm hdr;
Uint node_sz;
@@ -3741,6 +3921,68 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
if (use_iov)
store_in_vec(ctx, ep, NULL, THE_NON_VALUE, NULL, 0);
}
+ if (dflags & DFLAG_LOCAL_EXT) {
+ int done;
+ Uint32 hash;
+ Uint chunk_len;
+
+ if (use_iov) {
+ ASSERT(ctx);
+ erts_iov_block_hash_init(&ctx->lext_state.iov_block,
+ &ctx->iov[ctx->lext_vlen],
+ ctx->vlen - ctx->lext_vlen,
+ local_node_hash);
+
+ continue_make_lext_hash_iov:
+ /* Do 128 bytes per reduction... */
+ chunk_len = (Uint) r*128;
+
+ done = erts_iov_block_hash(&hash, &chunk_len,
+ &ctx->lext_state.iov_block);
+ }
+ else {
+ ErtsBlockHashState lext_state_buf, *lext_state;
+ byte *ep_start = lext_hash + 4 /* 32 bit hash value */;
+ Sint len = ep - ep_start;
+
+ ASSERT(len >= 0);
+
+ lext_state = ctx ? &ctx->lext_state.block : &lext_state_buf;
+
+ erts_block_hash_init(lext_state, ep_start, len, local_node_hash);
+
+ if (!ctx) {
+ /* Do it all at once... */
+ chunk_len = ERTS_UINT_MAX;
+ }
+ else {
+ continue_make_lext_hash_bin:
+ lext_state = &ctx->lext_state.block;
+ /* Do 128 bytes per reduction... */
+ chunk_len = (Uint) r*128;
+ }
+
+ done = erts_block_hash(&hash, &chunk_len, lext_state);
+ }
+
+ if (!ctx) {
+ ASSERT(done);
+ }
+ else {
+ if (!done) {
+ /* yield; more work calculating hash... */
+ ctx->ep = ep;
+ ctx->continue_make_lext_hash = !0;
+ *reds = 0;
+ return -1;
+ }
+ r -= chunk_len/128;
+ *reds = r;
+ }
+
+ put_int32(hash, lext_hash);
+ }
+
*res = ep;
return 0;
}
@@ -3924,7 +4166,7 @@ dec_term(ErtsDistExternal *edep,
{
#define PSTACK_TYPE struct dec_term_map
PSTACK_DECLARE(map_array, 10);
- int n;
+ int n, internal_nc = ets_decode;
ErtsAtomEncoding char_enc;
register Eterm* hp; /* Please don't take the address of hp */
Eterm* next;
@@ -3938,6 +4180,7 @@ dec_term(ErtsDistExternal *edep,
next = ctx->u.dc.next;
ep = ctx->u.dc.ep;
factory = &ctx->u.dc.factory;
+ internal_nc = ctx->u.dc.internal_nc;
if (ctx->state != B2TDecode) {
int n_limit = reds;
@@ -4029,6 +4272,8 @@ dec_term(ErtsDistExternal *edep,
objp = next;
next = (Eterm *) *objp;
+ continue_this_obj:
+
switch (*ep++) {
case INTEGER_EXT:
{
@@ -4272,7 +4517,7 @@ dec_term_atom_common:
case PID_EXT:
case NEW_PID_EXT:
factory->hp = hp;
- ep = dec_pid(edep, factory, ep, objp, ep[-1]);
+ ep = dec_pid(edep, factory, ep, objp, ep[-1], internal_nc);
hp = factory->hp;
if (ep == NULL) {
goto error;
@@ -4288,7 +4533,7 @@ dec_term_atom_common:
Uint32 cre;
byte tag = ep[-1];
- if ((ep = dec_atom(edep, ep, &sysname)) == NULL) {
+ if ((ep = dec_atom(edep, ep, &sysname, internal_nc)) == NULL) {
goto error;
}
if (tag == V4_PORT_EXT) {
@@ -4348,7 +4593,7 @@ dec_term_atom_common:
ref_words = 1;
- if ((ep = dec_atom(edep, ep, &sysname)) == NULL)
+ if ((ep = dec_atom(edep, ep, &sysname, internal_nc)) == NULL)
goto error;
if ((r0 = get_int32(ep)) >= MAX_REFERENCE )
goto error;
@@ -4365,7 +4610,7 @@ dec_term_atom_common:
ref_words = get_int16(ep);
ep += 2;
- if ((ep = dec_atom(edep, ep, &sysname)) == NULL)
+ if ((ep = dec_atom(edep, ep, &sysname, internal_nc)) == NULL)
goto error;
cre = get_int8(ep);
@@ -4383,7 +4628,7 @@ dec_term_atom_common:
ref_words = get_int16(ep);
ep += 2;
- if ((ep = dec_atom(edep, ep, &sysname)) == NULL)
+ if ((ep = dec_atom(edep, ep, &sysname, internal_nc)) == NULL)
goto error;
cre = get_int32(ep);
@@ -4416,7 +4661,7 @@ dec_term_atom_common:
}
if (ref_words != ERTS_REF_NUMBERS) {
int i;
- if (ref_words > ERTS_REF_NUMBERS)
+ if (ref_words > ERTS_MAX_REF_NUMBERS)
goto error; /* Not a ref that we created... */
for (i = ref_words; i < ERTS_REF_NUMBERS; i++)
ref_num[i] = 0;
@@ -4428,12 +4673,15 @@ dec_term_atom_common:
}
else {
/* Check if it is a pid reference... */
- Eterm pid = erts_pid_ref_lookup(ref_num);
+ Eterm pid = erts_pid_ref_lookup(ref_num, ref_words);
if (is_internal_pid(pid)) {
write_pid_ref_thing(hp, ref_num[0], ref_num[1],
ref_num[2], pid);
hp += ERTS_PID_REF_THING_SIZE;
}
+ else if (is_non_value(pid)) {
+ goto error; /* invalid reference... */
+ }
else {
/* Check if it is a magic reference... */
ErtsMagicBinary *mb = erts_magic_ref_lookup_bin(ref_num);
@@ -4618,10 +4866,10 @@ dec_term_atom_common:
Eterm temp;
Sint arity;
- if ((ep = dec_atom(edep, ep, &mod)) == NULL) {
+ if ((ep = dec_atom(edep, ep, &mod, 0)) == NULL) {
goto error;
}
- if ((ep = dec_atom(edep, ep, &name)) == NULL) {
+ if ((ep = dec_atom(edep, ep, &name, 0)) == NULL) {
goto error;
}
factory->hp = hp;
@@ -4736,7 +4984,7 @@ dec_term_atom_common:
*objp = make_fun(funp);
/* Module */
- if ((ep = dec_atom(edep, ep, &module)) == NULL) {
+ if ((ep = dec_atom(edep, ep, &module, 0)) == NULL) {
goto error;
}
factory->hp = hp;
@@ -4849,6 +5097,13 @@ dec_term_atom_common:
break;
}
+ case LOCAL_EXT:
+ internal_nc = !0;
+ if (ctx)
+ ctx->u.dc.internal_nc = !0;
+ ep += 4; /* 32-bit hash (verified in decoded_size()) */
+ goto continue_this_obj;
+
default:
goto error;
}
@@ -4949,23 +5204,88 @@ error_map_fixup:
return NULL;
}
-/* returns the number of bytes needed to encode an object
- to a sequence of bytes
- N.B. That this must agree with to_external2() above!!!
- (except for cached atoms) */
-static Uint encode_size_struct2(ErtsAtomCacheMap *acmp,
- Eterm obj,
- Uint64 dflags) {
- Uint size = 0;
- ErtsExtSzRes res = encode_size_struct_int(NULL, acmp, obj,
- dflags, NULL,
- &size);
- /*
- * encode_size_struct2() only allowed when
- * we know the result will always be OK!
- */
- ASSERT(res == ERTS_EXT_SZ_OK); (void) res;
- return (Uint) size;
+static Uint
+encode_atom_size(ErtsAtomCacheMap *acmp, Eterm atom, Uint64 dflags)
+{
+ ASSERT(is_atom(atom));
+ if (dflags & DFLAG_ETS_COMPRESSED) {
+ if (atom_val(atom) >= (1<<16)) {
+ return (Uint) 1 + 3;
+ }
+ else {
+ return (Uint) 1 + 2;
+ }
+ }
+ else {
+ Atom *a = atom_tab(atom_val(atom));
+ int alen;
+ Uint result;
+ if ((dflags & DFLAG_UTF8_ATOMS) || a->latin1_chars < 0) {
+ alen = a->len;
+ result = (Uint) 1 + 1 + alen;
+ if (alen > 255) {
+ result++; /* ATOM_UTF8_EXT (not small) */
+ }
+ }
+ else {
+ alen = a->latin1_chars;
+ result = (Uint) 1 + 1 + alen;
+ if (alen > 255 || !(dflags & DFLAG_SMALL_ATOM_TAGS))
+ result++; /* ATOM_EXT (not small) */
+ }
+ insert_acache_map(acmp, atom, dflags);
+ return result;
+ }
+}
+
+static Uint
+encode_internal_pid_size(ErtsAtomCacheMap *acmp, Eterm pid, Uint64 dflags)
+{
+ int nlen;
+ ASSERT(is_internal_pid(pid));
+ nlen = ((dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT))
+ ? 1
+ : encode_atom_size(acmp, internal_pid_node_name(pid), dflags));
+ return (Uint) 1 + nlen + 4 + 4 + 4;
+}
+
+static Uint
+encode_external_pid_size(ErtsAtomCacheMap *acmp, Eterm pid, Uint64 dflags)
+{
+ int nlen;
+ ASSERT(is_external_pid(pid));
+ nlen = encode_atom_size(acmp, external_pid_node_name(pid), dflags);
+ return (Uint) 1 + nlen + 4 + 4 + 4;
+}
+
+static Uint
+encode_pid_size(ErtsAtomCacheMap *acmp, Eterm pid, Uint64 dflags)
+{
+ if (is_internal_pid(pid))
+ return encode_internal_pid_size(acmp, pid, dflags);
+ ASSERT(is_external_pid(pid));
+ return encode_external_pid_size(acmp, pid, dflags);
+}
+
+static Uint
+encode_small_size(ErtsAtomCacheMap *acmp, Eterm pid, Uint64 dflags)
+{
+ Sint val = signed_val(pid);
+ Uint result;
+
+ if ((Uint)val < 256)
+ result = (Uint) 1 + 1; /* SMALL_INTEGER_EXT */
+ else if (sizeof(Sint) == 4 || IS_SSMALL32(val))
+ result = (Uint) 1 + 4; /* INTEGER_EXT */
+ else {
+ int i;
+ DeclareTmpHeapNoproc(tmp_big,2);
+ UseTmpHeapNoproc(2);
+ i = big_bytes(small_to_big(val, tmp_big));
+ result = (Uint) 1 + 1 + 1 + i; /* SMALL_BIG_EXT */
+ UnUseTmpHeapNoproc(2);
+ }
+ return result;
}
static ErtsExtSzRes
@@ -4978,14 +5298,24 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
Sint r = 0;
int vlen = -1;
- if (ctx) {
+ if (!ctx) {
+ if (dflags & DFLAG_LOCAL_EXT)
+ result += 5;
+ }
+ else {
WSTACK_CHANGE_ALLOCATOR(s, ERTS_ALC_T_SAVED_ESTACK);
r = *reds;
vlen = ctx->vlen;
- if (!ctx->wstack.wstart)
+ if (!ctx->wstack.wstart) {
ctx->last_result = result;
+ if (dflags & DFLAG_LOCAL_EXT) {
+ result += 5;
+ if (vlen >= 0)
+ vlen++;
+ }
+ }
else { /* restore saved stack */
WSTACK_RESTORE(s, &ctx->wstack);
result = ctx->result;
@@ -5014,49 +5344,10 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
result++;
break;
case ATOM_DEF:
- if (dflags & DFLAG_ETS_COMPRESSED) {
- if (atom_val(obj) >= (1<<16)) {
- result += 1 + 3;
- }
- else {
- result += 1 + 2;
- }
- }
- else {
- Atom *a = atom_tab(atom_val(obj));
- int alen;
- if ((dflags & DFLAG_UTF8_ATOMS) || a->latin1_chars < 0) {
- alen = a->len;
- result += 1 + 1 + alen;
- if (alen > 255) {
- result++; /* ATOM_UTF8_EXT (not small) */
- }
- }
- else {
- alen = a->latin1_chars;
- result += 1 + 1 + alen;
- if (alen > 255 || !(dflags & DFLAG_SMALL_ATOM_TAGS))
- result++; /* ATOM_EXT (not small) */
- }
- insert_acache_map(acmp, obj, dflags);
- }
+ result += encode_atom_size(acmp, obj, dflags);
break;
case SMALL_DEF:
- {
- Sint val = signed_val(obj);
-
- if ((Uint)val < 256)
- result += 1 + 1; /* SMALL_INTEGER_EXT */
- else if (sizeof(Sint) == 4 || IS_SSMALL32(val))
- result += 1 + 4; /* INTEGER_EXT */
- else {
- DeclareTmpHeapNoproc(tmp_big,2);
- UseTmpHeapNoproc(2);
- i = big_bytes(small_to_big(val, tmp_big));
- result += 1 + 1 + 1 + i; /* SMALL_BIG_EXT */
- UnUseTmpHeapNoproc(2);
- }
- }
+ result += encode_small_size(acmp, obj, dflags);
break;
case BIG_DEF:
i = big_bytes(obj);
@@ -5068,22 +5359,47 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
result += 1 + 4 + 1 + i; /* tag,size,sign,digits */
break;
case EXTERNAL_PID_DEF:
+ result += encode_external_pid_size(acmp, obj, dflags);
+ break;
case PID_DEF:
- result += (1 + encode_size_struct2(acmp, pid_node_name(obj), dflags) +
- 4 + 4 + 4);
+ result += encode_internal_pid_size(acmp, obj, dflags);
+ break;
+ case EXTERNAL_REF_DEF: {
+ int nlen = encode_atom_size(acmp,
+ external_ref_node_name(obj),
+ dflags);
+ i = external_ref_no_numbers(obj);
+ result += (1 + 2 + nlen + 4 + 4*i);
break;
- case EXTERNAL_REF_DEF:
- case REF_DEF:
- i = ref_no_numbers(obj);
- result += (1 + 2 + encode_size_struct2(acmp, ref_node_name(obj), dflags) +
- 4 + 4*i);
+ }
+ case REF_DEF: {
+ int nlen;
+ i = internal_ref_no_numbers(obj);
+ if (dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT)) {
+ nlen = 1;
+ }
+ else {
+ nlen = encode_atom_size(acmp,
+ internal_ref_node_name(obj),
+ dflags);
+ }
+ result += (1 + 2 + nlen + 4 + 4*i);
+ break;
+ }
+ case EXTERNAL_PORT_DEF: {
+ int nlen = encode_atom_size(acmp,
+ external_port_node_name(obj),
+ dflags);
+ result += (1 + nlen + 8 + 4);
break;
- case EXTERNAL_PORT_DEF:
+ }
case PORT_DEF: {
- Uint64 num = port_number(obj);
- result += (num > ERTS_MAX_V3_PORT_NUMBER) ? 8 : 4;
- result += (1 + encode_size_struct2(acmp, port_node_name(obj), dflags)
- /* num */ + 4);
+ int nlen = ((dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT))
+ ? 1
+ : encode_atom_size(acmp,
+ internal_port_node_name(obj),
+ dflags));
+ result += (1 + nlen + 8 + 4);
break;
}
case LIST_DEF: {
@@ -5270,8 +5586,8 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
if (is_local_fun(funp)) {
result += 20+1+1+4; /* New ID + Tag */
result += 4; /* Length field (number of free variables */
- result += encode_size_struct2(acmp, funp->creator, dflags);
- result += encode_size_struct2(acmp, funp->entry.fun->module, dflags);
+ result += encode_pid_size(acmp, funp->creator, dflags);
+ result += encode_atom_size(acmp, funp->entry.fun->module, dflags);
result += 2 * (1+4); /* Index, Uniq */
if (funp->num_free > 1) {
WSTACK_PUSH2(s, (UWord) (funp->env + 1),
@@ -5287,9 +5603,9 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
ASSERT(is_external_fun(funp) && funp->next == NULL);
result += 1;
- result += encode_size_struct2(acmp, ep->info.mfa.module, dflags);
- result += encode_size_struct2(acmp, ep->info.mfa.function, dflags);
- result += encode_size_struct2(acmp, make_small(ep->info.mfa.arity), dflags);
+ result += encode_atom_size(acmp, ep->info.mfa.module, dflags);
+ result += encode_atom_size(acmp, ep->info.mfa.function, dflags);
+ result += encode_small_size(acmp, make_small(ep->info.mfa.arity), dflags);
}
break;
}
@@ -5345,8 +5661,6 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
return ERTS_EXT_SZ_OK;
}
-
-
static Sint
decoded_size(const byte *ep, const byte* endp, int internal_tags, B2TContext* ctx)
{
@@ -5354,6 +5668,8 @@ decoded_size(const byte *ep, const byte* endp, int internal_tags, B2TContext* ct
int atom_extra_skip;
Uint n;
SWord reds;
+ const byte *lext_hash;
+ Uint32 lext_term_end;
/* Keep track of the current number of sub terms remaining to be decoded.
*
@@ -5366,24 +5682,6 @@ decoded_size(const byte *ep, const byte* endp, int internal_tags, B2TContext* ct
*/
Uint32 terms;
- if (ctx) {
- reds = ctx->reds;
- if (ctx->u.sc.ep) {
- heap_size = ctx->u.sc.heap_size;
- terms = ctx->u.sc.terms;
- ep = ctx->u.sc.ep;
- atom_extra_skip = ctx->u.sc.atom_extra_skip;
- goto init_done;
- }
- }
- else
- ERTS_UNDEF(reds, 0);
-
- heap_size = 0;
- terms = 1;
- atom_extra_skip = 0;
-init_done:
-
#define SKIP(sz) \
do { \
if ((sz) <= endp-ep) { \
@@ -5413,6 +5711,38 @@ init_done:
if (terms < before) goto error; \
} while (0)
+
+ if (ctx) {
+ reds = ctx->reds;
+ if (ctx->u.sc.ep) {
+ ep = ctx->u.sc.ep;
+ atom_extra_skip = ctx->u.sc.atom_extra_skip;
+ heap_size = ctx->u.sc.heap_size;
+ lext_hash = ctx->u.sc.lext_hash;
+ lext_term_end = ctx->u.sc.lext_term_end;
+ terms = ctx->u.sc.terms;
+ if (terms == lext_term_end) {
+ ASSERT(lext_hash);
+ goto continue_check_lext;
+ }
+ goto init_done;
+ }
+ }
+ else
+ ERTS_UNDEF(reds, 0);
+
+ heap_size = 0;
+ terms = 1;
+ atom_extra_skip = 0;
+ /*
+ * lext_hash != NULL and local_term_end != ~0 when decoding local external
+ * term format...
+ */
+ lext_hash = NULL;
+ lext_term_end = ~0;
+
+init_done:
+
ASSERT(terms > 0);
do {
int tag;
@@ -5541,6 +5871,18 @@ init_done:
terms++;
break;
case NIL_EXT:
+ if (atom_extra_skip) {
+ /*
+ * atom_extra_skip != 0 should only appear due to local encoding,
+ * or compressed ets encoding, of a node name in internal
+ * pids/ports/refs. If not currently inside a local encoding,
+ * this is an error...
+ */
+ if (!lext_hash && !internal_tags)
+ goto error;
+ SKIP(atom_extra_skip);
+ atom_extra_skip = 0;
+ }
break;
case LIST_EXT:
CHKSIZE(4);
@@ -5687,22 +6029,87 @@ init_done:
SKIP(2+sizeof(ProcBin));
heap_size += PROC_BIN_SIZE + ERL_SUB_BIN_SIZE;
break;
+ CHKSIZE(1);
+ case LOCAL_EXT:
+ /*
+ * Currently the hash is 4 bytes large...
+ */
+ CHKSIZE(4);
+ lext_hash = ep;
+ lext_term_end = terms - 1;
+ terms++;
+ ep += 4;
+ break;
default:
goto error;
}
terms--;
+ if (terms == lext_term_end) {
+ ErtsBlockHashState lext_state_buf, *lext_state;
+ const byte *ep_start = lext_hash + 4 /* 32 bit hash value */;
+ Sint len = ep - ep_start;
+ Uint chunk_len;
+ Uint32 hash;
+
+ ASSERT(lext_hash);
+
+ if (len <= 0) {
+ /* no terms */
+ goto error;
+ }
+
+ lext_state = ctx ? &ctx->u.sc.lext_state : &lext_state_buf;
+
+ erts_block_hash_init(lext_state, ep_start, len, local_node_hash);
+
+ if (!ctx) {
+ /* Do it all at once... */
+ chunk_len = ERTS_UINT_MAX;
+ }
+ else {
+ continue_check_lext:
+ lext_state = &ctx->u.sc.lext_state;
+ /* 'reds' has been scaled up to "bytes per reds"... */
+ chunk_len = (Uint) reds;
+ }
+
+ if (!erts_block_hash(&hash, &chunk_len, lext_state)) {
+ /* yield; more work calculating hash... */
+ ASSERT(ctx);
+ reds = 0;
+ }
+ else {
+ reds -= chunk_len;
+ if (hash != get_int32(lext_hash)) {
+ /*
+ * Hash presented in external format did not match the
+ * calculated hash...
+ */
+ goto error;
+ }
+ lext_hash = NULL;
+ lext_term_end = ~0;
+ }
+
+ }
+
+ ASSERT(terms != ~0);
+
if (ctx && --reds <= 0 && terms != 0) {
ctx->u.sc.heap_size = heap_size;
ctx->u.sc.terms = terms;
ctx->u.sc.ep = ep;
ctx->u.sc.atom_extra_skip = atom_extra_skip;
+ ctx->u.sc.lext_hash = lext_hash;
+ ctx->u.sc.lext_term_end = lext_term_end;
ctx->reds = 0;
return 0;
}
} while (terms != 0);
if (terms == 0) {
+
if (ctx) {
ctx->state = B2TDecodeInit;
ctx->reds = reds;
@@ -5943,118 +6350,6 @@ Sint transcode_dist_obuf(ErtsDistOutputBuf* ob,
return 0;
return reds;
}
-
- if ((~dflags & DFLAG_UNLINK_ID)
- && ep[0] == SMALL_TUPLE_EXT
- && ep[1] == 4
- && ep[2] == SMALL_INTEGER_EXT
- && (ep[3] == DOP_UNLINK_ID_ACK || ep[3] == DOP_UNLINK_ID)) {
-
- if (ep[3] == DOP_UNLINK_ID_ACK) {
- /* Drop DOP_UNLINK_ID_ACK signal... */
- int i;
- for (i = 1; i < ob->eiov->vsize; i++) {
- if (ob->eiov->binv[i])
- driver_free_binary(ob->eiov->binv[i]);
- }
- ob->eiov->vsize = 1;
- ob->eiov->size = 0;
- }
- else {
- Eterm ctl_msg, remote, local, *tp;
- ErtsTranscodeDecodeState tds;
- Uint64 id;
- byte *ptr;
- ASSERT(ep[3] == DOP_UNLINK_ID);
- /*
- * Rewrite the DOP_UNLINK_ID signal into a
- * DOP_UNLINK signal and send an unlink ack
- * to the local sender.
- */
-
- /*
- * decode control message so we get info
- * needed for unlink ack signal to send...
- */
- ASSERT(get_int32(hdr + 4) == 0); /* No payload */
- ctl_msg = transcode_decode_ctl_msg(&tds, iov, eiov->vsize);
-
- ASSERT(is_tuple_arity(ctl_msg, 4));
-
- tp = tuple_val(ctl_msg);
- ASSERT(tp[1] == make_small(DOP_UNLINK_ID));
-
- if (!term_to_Uint64(tp[2], &id))
- ERTS_INTERNAL_ERROR("Invalid encoding of DOP_UNLINK_ID signal");
-
- local = tp[3];
- remote = tp[4];
-
- ASSERT(is_internal_pid(local));
- ASSERT(is_external_pid(remote));
-
- /*
- * Rewrite buffer to an unlink signal by removing
- * second element and change first element to
- * DOP_UNLINK. That is, to: {DOP_UNLINK, local, remote}
- */
-
- ptr = &ep[4];
- switch (*ptr) {
- case SMALL_INTEGER_EXT:
- ptr += 1;
- break;
- case INTEGER_EXT:
- ptr += 4;
- break;
- case SMALL_BIG_EXT:
- ptr += 1;
- ASSERT(*ptr <= 8);
- ptr += *ptr + 1;
- break;
- default:
- ERTS_INTERNAL_ERROR("Invalid encoding of DOP_UNLINK_ID signal");
- break;
- }
-
- ASSERT((ptr - ep) <= 16);
- ASSERT((ptr - ep) <= iov[2].iov_len);
-
- *(ptr--) = DOP_UNLINK;
- *(ptr--) = SMALL_INTEGER_EXT;
- *(ptr--) = 3;
- *ptr = SMALL_TUPLE_EXT;
-
- iov[2].iov_base = ptr;
- iov[2].iov_len -= (ptr - ep);
-
-#ifdef DEBUG
- {
- ErtsTranscodeDecodeState dbg_tds;
- Eterm new_ctl_msg = transcode_decode_ctl_msg(&dbg_tds,
- iov,
- eiov->vsize);
- ASSERT(is_tuple_arity(new_ctl_msg, 3));
- tp = tuple_val(new_ctl_msg);
- ASSERT(tp[1] == make_small(DOP_UNLINK));
- ASSERT(tp[2] == local);
- ASSERT(eq(tp[3], remote));
- transcode_decode_state_destroy(&dbg_tds);
- }
-#endif
-
- /* Send unlink ack to local sender... */
- erts_proc_sig_send_dist_unlink_ack(dep, dep->connection_id,
- remote, local, id);
-
- transcode_decode_state_destroy(&tds);
-
- reds -= 5;
- }
- if (reds < 0)
- return 0;
- return reds;
- }
start_r = r = reds*ERTS_TRANSCODE_REDS_FACT;
diff --git a/erts/emulator/beam/external.h b/erts/emulator/beam/external.h
index 7a59d02102..bd97164d82 100644
--- a/erts/emulator/beam/external.h
+++ b/erts/emulator/beam/external.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2020. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,6 +57,7 @@
#define ATOM_UTF8_EXT 'v'
#define SMALL_ATOM_UTF8_EXT 'w'
#define V4_PORT_EXT 'x'
+#define LOCAL_EXT 'y'
#define DIST_HEADER 'D'
#define DIST_FRAG_HEADER 'E'
@@ -187,7 +188,7 @@ int erts_encode_dist_ext(Eterm, byte **, Uint64, ErtsAtomCacheMap *,
struct TTBEncodeContext_ *, Uint *,
Sint *);
ErtsExtSzRes erts_encode_ext_size(Eterm, Uint *szp);
-ErtsExtSzRes erts_encode_ext_size_2(Eterm, unsigned, Uint *szp);
+ErtsExtSzRes erts_encode_ext_size_2(Eterm, Uint64, Uint *szp);
Uint erts_encode_ext_size_ets(Eterm);
void erts_encode_ext(Eterm, byte **);
byte* erts_encode_ext_ets(Eterm, byte *, struct erl_off_heap_header** ext_off_heap);
diff --git a/erts/emulator/beam/generators.tab b/erts/emulator/beam/generators.tab
index eec78315a2..a737228c44 100644
--- a/erts/emulator/beam/generators.tab
+++ b/erts/emulator/beam/generators.tab
@@ -2,7 +2,7 @@
//
// %CopyrightBegin%
//
-// Copyright Ericsson AB 2020-2021. All Rights Reserved.
+// Copyright Ericsson AB 2020-2023. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -300,111 +300,133 @@ gen.skip_bits2(Fail, Ms, Size, Unit, Flags) {
return op;
}
-gen_increment(stp, reg, val, dst) {
- BeamOp* op;
- $NewBeamOp($stp, op);
- $BeamOpNameArity(op, i_increment, 3);
- op->a[0] = $reg;
- op->a[1].type = TAG_u;
- op->a[1].val = $val;
- op->a[2] = $dst;
- return op;
-}
-
-gen.increment(Reg, Integer, Dst) {
- $gen_increment(S, Reg, Integer.val, Dst);
-}
-
-gen.increment_from_minus(Reg, Integer, Dst) {
- $gen_increment(S, Reg, -Integer.val, Dst);
-}
-
-gen.plus_from_minus(Fail, Live, Src, Integer, Dst) {
- BeamOp* op;
-
- ASSERT(Integer.type == TAG_i && IS_SSMALL(-(Sint)Integer.val));
-
- $NewBeamOp(S, op);
- $BeamOpNameArity(op, gen_plus, 5);
- op->a[0] = Fail;
- op->a[1] = Live;
- op->a[2] = Src;
- op->a[3].type = TAG_i;
- op->a[3].val = -(Sint)Integer.val;
- op->a[4] = Dst;
+// Creates an instruction that moves a literal lambda to a register.
+MakeLiteralLambda(Op, Index, DstType, DstVal) {
+ BeamFile_LambdaEntry *entry = &S->beam.lambdas.entries[Idx.val];
+ SWord literal;
+
+ ASSERT(entry->num_free == 0);
+
+ /* If we haven't already done so, we need to create a placeholder for the
+ * lambda. */
+ if (S->lambda_literals[$Index] == ERTS_SWORD_MAX) {
+ Eterm tmp_hp[ERL_FUN_SIZE];
+ ErlFunThing *funp;
+
+ /* The placeholder is an external fun with the export field set to
+ * NULL, preventing it from colliding with external fun literals
+ * created by the user. We also disable deduplication to prevent it
+ * from colliding with other placeholder lambdas of the same arity. */
+ funp = (ErlFunThing*)tmp_hp;
+ funp->thing_word = HEADER_FUN;
+ funp->entry.exp = NULL;
+ funp->next = NULL;
+ funp->arity = entry->arity;
+ funp->num_free = 0;
+ funp->creator = am_external;
+
+ literal = beamfile_add_literal(&S->beam, make_fun(tmp_hp), 0);
+ S->lambda_literals[$Index] = literal;
+ } else {
+ literal = S->lambda_literals[$Index];
+ }
- return op;
+ $BeamOpNameArity($Op, move, 2);
+ ($Op)->a[0].type = TAG_q;
+ ($Op)->a[0].val = literal;
+ ($Op)->a[1].type = $DstType;
+ ($Op)->a[1].val = $DstVal;
}
-gen.make_fun2(idx) {
+gen.make_fun2(Idx) {
BeamOp* op;
BeamOp* th;
$NewBeamOp(S, op);
- if (idx.val >= S->beam.lambdas.count) {
+ if (Idx.val >= S->beam.lambdas.count) {
$BeamOpNameArity(op, i_lambda_error, 1);
op->a[0].type = TAG_o;
op->a[0].val = 0;
- return op;
+ return op;
} else {
- int i;
- BeamFile_LambdaEntry *entry = &S->beam.lambdas.entries[idx.val];
- unsigned num_free = entry->num_free;
- unsigned arity = entry->arity;
-
- $NewBeamOp(S, th);
-
- $BeamOpNameArity(th, test_heap, 2);
- th->a[0].type = TAG_u;
- th->a[0].val = ERL_FUN_SIZE + num_free;
- th->a[1].type = TAG_u;
- th->a[1].val = num_free;
- th->next = op;
-
- $BeamOpNameArity(op, i_make_fun3, 4);
- $BeamOpArity(op, 4 + num_free);
- op->a[0].type = TAG_u;
- op->a[0].val = idx.val;
- op->a[1].type = TAG_x;
- op->a[1].val = 0;
- op->a[2].type = TAG_u;
- op->a[2].val = arity - num_free;
- op->a[3].type = TAG_u;
- op->a[3].val = num_free;
- for (i = 0; i < num_free; i++) {
- op->a[i+4].type = TAG_x;
- op->a[i+4].val = i;
- }
- return th;
+ BeamFile_LambdaEntry *entry = &S->beam.lambdas.entries[Idx.val];
+ unsigned num_free = entry->num_free;
+ unsigned arity = entry->arity;
+ int i;
+
+ /* Literal lambdas are all owned by the init process as a workaround
+ * for all local funs needing a creator pid. Skip the optimization if
+ * this module is being loaded before said process has started
+ * (just preloaded modules, really). */
+ if (num_free == 0 && erts_init_process_id != ERTS_INVALID_PID) {
+ $MakeLiteralLambda(op, Idx.val, TAG_x, 0);
+ return op;
+ }
+
+ $NewBeamOp(S, th);
+
+ $BeamOpNameArity(th, test_heap, 2);
+ th->a[0].type = TAG_u;
+ th->a[0].val = ERL_FUN_SIZE + num_free;
+ th->a[1].type = TAG_u;
+ th->a[1].val = num_free;
+ th->next = op;
+
+ $BeamOpNameArity(op, i_make_fun3, 4);
+ $BeamOpArity(op, 4 + num_free);
+ op->a[0].type = TAG_u;
+ op->a[0].val = Idx.val;
+ op->a[1].type = TAG_x;
+ op->a[1].val = 0;
+ op->a[2].type = TAG_u;
+ op->a[2].val = arity - num_free;
+ op->a[3].type = TAG_u;
+ op->a[3].val = num_free;
+
+ for (i = 0; i < num_free; i++) {
+ op->a[i+4].type = TAG_x;
+ op->a[i+4].val = i;
+ }
+
+ return th;
}
}
-gen.make_fun3(idx, Dst, NumFree, Env) {
+gen.make_fun3(Idx, Dst, NumFree, Env) {
BeamOp* op;
$NewBeamOp(S, op);
- if (idx.val < S->beam.lambdas.count) {
- BeamFile_LambdaEntry *entry = &S->beam.lambdas.entries[idx.val];
- if (NumFree.val == entry->num_free) {
- int i;
-
- $BeamOpNameArity(op, i_make_fun3, 4);
- $BeamOpArity(op, 4 + NumFree.val);
- op->a[0].type = TAG_u;
- op->a[0].val = idx.val;
- op->a[1] = Dst;
- op->a[2].type = TAG_u;
- op->a[2].val = entry->arity - entry->num_free;
- op->a[3].type = TAG_u;
- op->a[3].val = entry->num_free;
- for (i = 0; i < NumFree.val; i++) {
- op->a[i+4] = Env[i];
- }
- return op;
- }
+ if (Idx.val < S->beam.lambdas.count) {
+ BeamFile_LambdaEntry *entry = &S->beam.lambdas.entries[Idx.val];
+
+ if (NumFree.val == entry->num_free) {
+ int i;
+
+ if (NumFree.val == 0 && erts_init_process_id != ERTS_INVALID_PID) {
+ $MakeLiteralLambda(op, Idx.val, Dst.type, Dst.val);
+ } else {
+ $BeamOpNameArity(op, i_make_fun3, 4);
+ $BeamOpArity(op, 4 + NumFree.val);
+
+ op->a[0].type = TAG_u;
+ op->a[0].val = Idx.val;
+ op->a[1] = Dst;
+ op->a[2].type = TAG_u;
+ op->a[2].val = entry->arity - entry->num_free;
+ op->a[3].type = TAG_u;
+ op->a[3].val = entry->num_free;
+
+ for (i = 0; i < NumFree.val; i++) {
+ op->a[i+4] = Env[i];
+ }
+ }
+
+ return op;
+ }
}
+
$BeamOpNameArity(op, i_lambda_error, 1);
op->a[0].type = TAG_o;
op->a[0].val = 0;
diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h
index 8c4dd90966..3170cebe95 100644
--- a/erts/emulator/beam/global.h
+++ b/erts/emulator/beam/global.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -122,11 +122,16 @@ extern void erts_add_taint(Eterm mod_atom);
extern Eterm erts_nif_taints(Process* p);
extern void erts_print_nif_taints(fmtfn_t to, void* to_arg);
-/* Loads the specified NIF. The caller must have code write permission. */
+/* Loads the specified NIF. The caller must have code modification
+ * permission. */
Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args);
void erts_unload_nif(struct erl_module_nif* nif);
extern void erl_nif_init(void);
+extern void erts_nif_sched_init(ErtsSchedulerData *esdp);
+extern void erts_nif_execute_on_halt(void);
+extern void erts_nif_notify_halt(void);
+extern void erts_nif_wait_calls(void);
extern int erts_nif_get_funcs(struct erl_module_nif*,
struct enif_func_t **funcs);
extern Module *erts_nif_get_module(struct erl_module_nif*);
@@ -1044,9 +1049,9 @@ double erts_get_positive_zero_float(void);
/* config.c */
-__decl_noreturn void __noreturn erts_exit_epilogue(void);
+__decl_noreturn void __noreturn erts_exit_epilogue(int flush);
__decl_noreturn void __noreturn erts_exit(int n, const char*, ...);
-__decl_noreturn void __noreturn erts_flush_async_exit(int n, char*, ...);
+__decl_noreturn void __noreturn erts_flush_exit(int n, char*, ...);
void erl_error(const char*, va_list);
/* This controls whether sharing-preserving copy is used by Erlang */
@@ -1142,10 +1147,10 @@ Uint size_shared(Eterm);
#ifdef ERTS_COPY_REGISTER_LOCATION
-#define copy_shared_perform(U, V, X, Y, Z) \
- copy_shared_perform_x((U), (V), (X), (Y), (Z), __FILE__, __LINE__)
Eterm copy_shared_perform_x(Eterm, Uint, erts_shcopy_t*, Eterm**, ErlOffHeap*,
char *file, int line);
+#define copy_shared_perform(U, V, X, Y, Z) \
+ copy_shared_perform_x((U), (V), (X), (Y), (Z), __FILE__, __LINE__)
Eterm copy_struct_x(Eterm, Uint, Eterm**, ErlOffHeap*, Uint*, erts_literal_area_t*,
char *file, int line);
@@ -1154,16 +1159,21 @@ Eterm copy_struct_x(Eterm, Uint, Eterm**, ErlOffHeap*, Uint*, erts_literal_area_
#define copy_struct_litopt(Obj,Sz,HPP,OH,LitArea) \
copy_struct_x(Obj,Sz,HPP,OH,NULL,LitArea,__FILE__,__LINE__)
+Eterm* copy_shallow_x(Eterm* ERTS_RESTRICT, Uint, Eterm**, ErlOffHeap*,
+ char *file, int line);
#define copy_shallow(R, SZ, HPP, OH) \
copy_shallow_x((R), (SZ), (HPP), (OH), __FILE__, __LINE__)
-Eterm copy_shallow_x(Eterm* ERTS_RESTRICT, Uint, Eterm**, ErlOffHeap*,
+
+Eterm copy_shallow_obj_x(Eterm, Uint, Eterm**, ErlOffHeap*,
char *file, int line);
+#define copy_shallow_obj(R, SZ, HPP, OH) \
+ copy_shallow_obj_x((R), (SZ), (HPP), (OH), __FILE__, __LINE__)
#else
+Eterm copy_shared_perform_x(Eterm, Uint, erts_shcopy_t*, Eterm**, ErlOffHeap*);
#define copy_shared_perform(U, V, X, Y, Z) \
copy_shared_perform_x((U), (V), (X), (Y), (Z))
-Eterm copy_shared_perform_x(Eterm, Uint, erts_shcopy_t*, Eterm**, ErlOffHeap*);
Eterm copy_struct_x(Eterm, Uint, Eterm**, ErlOffHeap*, Uint*, erts_literal_area_t*);
#define copy_struct(Obj,Sz,HPP,OH) \
@@ -1171,9 +1181,13 @@ Eterm copy_struct_x(Eterm, Uint, Eterm**, ErlOffHeap*, Uint*, erts_literal_area_
#define copy_struct_litopt(Obj,Sz,HPP,OH,LitArea) \
copy_struct_x(Obj,Sz,HPP,OH,NULL,LitArea)
+Eterm* copy_shallow_x(Eterm* ERTS_RESTRICT, Uint, Eterm**, ErlOffHeap*);
#define copy_shallow(R, SZ, HPP, OH) \
copy_shallow_x((R), (SZ), (HPP), (OH))
-Eterm copy_shallow_x(Eterm* ERTS_RESTRICT, Uint, Eterm**, ErlOffHeap*);
+
+Eterm copy_shallow_obj_x(Eterm, Uint, Eterm**, ErlOffHeap*);
+#define copy_shallow_obj(R, SZ, HPP, OH) \
+ copy_shallow_obj_x((R), (SZ), (HPP), (OH))
#endif
@@ -1452,6 +1466,7 @@ Eterm erts_debug_persistent_term_xtra_info(Process* c_p);
/* external.c */
void erts_init_external(void);
+void erts_late_init_external(void);
/* erl_map.c */
void erts_init_map(void);
@@ -1506,7 +1521,7 @@ int erts_utf8_to_latin1(byte* dest, const byte* source, int slen);
void bin_write(fmtfn_t, void*, byte*, size_t);
Sint intlist_to_buf(Eterm, char*, Sint); /* most callers pass plain char*'s */
-int erts_unicode_list_to_buf(Eterm list, byte *buf, Sint len, Sint* written);
+int erts_unicode_list_to_buf(Eterm list, byte *buf, Sint capacity, Sint len, Sint* written);
Sint erts_unicode_list_to_buf_len(Eterm list);
int Sint_to_buf(Sint num, int base, char **buf_p, size_t buf_size);
diff --git a/erts/emulator/beam/hash.c b/erts/emulator/beam/hash.c
index 434ba25098..5c8b43e6e2 100644
--- a/erts/emulator/beam/hash.c
+++ b/erts/emulator/beam/hash.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,6 +64,7 @@ void hash_get_info(HashInfo *hi, Hash *h)
}
}
ASSERT(objects == h->nobjs);
+ (void)objects;
hi->name = h->name;
hi->size = hash_get_slots(h);
diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c
index d0a42b71bb..a79bce3d77 100644
--- a/erts/emulator/beam/io.c
+++ b/erts/emulator/beam/io.c
@@ -3126,8 +3126,7 @@ void erts_lcnt_update_port_locks(int enable) {
* Parameters:
* bufsiz - The (maximum) size of the line buffer.
*/
-LineBuf *allocate_linebuf(bufsiz)
-int bufsiz;
+LineBuf *allocate_linebuf(int bufsiz)
{
int ovsiz = (bufsiz < LINEBUF_INITIAL) ? bufsiz : LINEBUF_INITIAL;
LineBuf *lb = (LineBuf *) erts_alloc(ERTS_ALC_T_LINEBUF,
@@ -3772,7 +3771,7 @@ terminate_port(Port *prt)
if ((state & ERTS_PORT_SFLG_HALT)
&& (erts_atomic32_dec_read_nob(&erts_halt_progress) == 0)) {
erts_port_release(prt); /* We will exit and never return */
- erts_flush_async_exit(erts_halt_code, "");
+ erts_flush_exit(erts_halt_code, "");
}
if (is_internal_port(send_closed_port_id))
deliver_result(NULL, send_closed_port_id, connected_id, am_closed);
diff --git a/erts/emulator/beam/jit/arm/beam_asm.hpp b/erts/emulator/beam/jit/arm/beam_asm.hpp
index a2d28c27b5..8ef609f4b2 100644
--- a/erts/emulator/beam/jit/arm/beam_asm.hpp
+++ b/erts/emulator/beam/jit/arm/beam_asm.hpp
@@ -60,18 +60,20 @@ extern "C"
using namespace asmjit;
-class BeamAssembler : public ErrorHandler {
-protected:
- /* Holds code and relocation information. */
- CodeHolder code;
-
- a64::Assembler a;
-
- FileLogger logger;
+struct BeamAssembler : public BeamAssemblerCommon {
+ BeamAssembler() : BeamAssemblerCommon(a) {
+ Error err = code.attach(&a);
+ ERTS_ASSERT(!err && "Failed to attach codeHolder");
+ }
- Section *rodata = nullptr;
+ BeamAssembler(const std::string &log) : BeamAssembler() {
+ if (erts_jit_asm_dump) {
+ setLogger(log + ".asm");
+ }
+ }
- /* * * * * * * * * */
+protected:
+ a64::Assembler a;
/* Points at x_reg_array inside an ErtsSchedulerRegisters struct, allowing
* the aux_regs field to be addressed with an 8-bit displacement. */
@@ -204,6 +206,7 @@ protected:
/* Number of highest element displacement for L/SDP and L/STR. */
static const size_t MAX_LDP_STP_DISPLACEMENT = 0x3F;
static const size_t MAX_LDR_STR_DISPLACEMENT = 0xFFF;
+ static const size_t MAX_LDUR_STUR_DISPLACEMENT = 0xFF;
/* Constants for "alternate flag state" operands, which are distinct from
* `arm::CondCode::xyz`. Mainly used in `CCMP` instructions. */
@@ -223,27 +226,6 @@ protected:
kNone = 0
};
-public:
- static bool hasCpuFeature(uint32_t featureId);
-
- BeamAssembler();
- BeamAssembler(const std::string &log);
-
- ~BeamAssembler();
-
- void *getBaseAddress();
- size_t getOffset();
-
-protected:
- void _codegen(JitAllocator *allocator,
- const void **executable_ptr,
- void **writable_ptr);
-
- void *getCode(Label label);
- byte *getCode(char *labelName);
-
- void handleError(Error err, const char *message, BaseEmitter *origin);
-
#ifdef JIT_HARD_DEBUG
constexpr arm::Mem getInitialSPRef() const {
int base = offsetof(ErtsSchedulerRegisters, initial_sp);
@@ -416,12 +398,28 @@ protected:
return arm::Mem(ARG1, CodeIndex, arm::lsl(3));
}
+ /* Prefer `eHeapAlloc` over `eStack | eHeap` when calling
+ * functions in the runtime system that allocate heap
+ * memory (`HAlloc`, heap factories, etc).
+ *
+ * Prefer `eHeapOnlyAlloc` over `eHeapAlloc` for functions
+ * that assume there's already a certain amount of free
+ * space on the heap, such as those using `HeapOnlyAlloc`
+ * or similar. It's slightly cheaper in release builds,
+ * and in debug builds it updates `eStack` to ensure that
+ * we can make heap size assertions. */
enum Update : int {
eStack = (1 << 0),
eHeap = (1 << 1),
eReductions = (1 << 2),
eCodeIndex = (1 << 3),
- eXRegs = (1 << 4)
+ eXRegs = (1 << 4),
+ eHeapAlloc = Update::eHeap | Update::eStack,
+#ifndef DEBUG
+ eHeapOnlyAlloc = Update::eHeap,
+#else
+ eHeapOnlyAlloc = Update::eHeapAlloc
+#endif
};
void emit_enter_erlang_frame() {
@@ -743,12 +741,27 @@ protected:
}
}
+ void subs(arm::Gp to, arm::Gp src, int64_t val) {
+ if (Support::isUInt12(val)) {
+ a.subs(to, src, imm(val));
+ } else if (Support::isUInt12(-val)) {
+ a.adds(to, src, imm(-val));
+ } else {
+ mov_imm(SUPER_TMP, val);
+ a.subs(to, src, SUPER_TMP);
+ }
+ }
+
void cmp(arm::Gp src, int64_t val) {
if (Support::isUInt12(val)) {
a.cmp(src, imm(val));
} else if (Support::isUInt12(-val)) {
a.cmn(src, imm(-val));
+ } else if (src.isGpW()) {
+ mov_imm(SUPER_TMP.w(), val);
+ a.cmp(src, SUPER_TMP.w());
} else {
+ ERTS_ASSERT(src.isGpX());
mov_imm(SUPER_TMP, val);
a.cmp(src, SUPER_TMP);
}
@@ -791,125 +804,19 @@ protected:
add(to, arm::GpX(mem.baseId()), offset);
}
}
-
-public:
- void embed_rodata(const char *labelName, const char *buff, size_t size);
- void embed_bss(const char *labelName, size_t size);
- void embed_zeros(size_t size);
-
- void setLogger(std::string log);
- void setLogger(FILE *log);
-
- void comment(const char *format) {
- if (logger.file()) {
- a.commentf("# %s", format);
- }
- }
-
- template<typename... Ts>
- void comment(const char *format, Ts... args) {
- if (logger.file()) {
- char buff[1024];
- erts_snprintf(buff, sizeof(buff), format, args...);
- a.commentf("# %s", buff);
- }
- }
-
- struct AsmRange {
- ErtsCodePtr start;
- ErtsCodePtr stop;
- const std::string name;
-
- struct LineData {
- ErtsCodePtr start;
- const std::string file;
- unsigned line;
- };
-
- const std::vector<LineData> lines;
- };
-
- void embed(void *data, uint32_t size) {
- a.embed((char *)data, size);
- }
};
#include "beam_asm_global.hpp"
-class BeamModuleAssembler : public BeamAssembler {
- typedef unsigned BeamLabel;
-
- /* Map of BEAM label number to asmjit Label. These should not be used
- * directly by most instructions because of displacement limits, use
- * `resolve_beam_label` instead. */
- typedef std::unordered_map<BeamLabel, const Label> LabelMap;
- LabelMap rawLabels;
+class BeamModuleAssembler : public BeamAssembler,
+ public BeamModuleAssemblerCommon {
+ BeamGlobalAssembler *ga;
/* Sequence number used to create unique named labels by
* resolve_label(). Only used when assembly output has been
* requested. */
long labelSeq = 0;
- struct patch {
- Label where;
- uint64_t val_offs;
- };
-
- struct patch_catch {
- struct patch patch;
- Label handler;
- };
- std::vector<struct patch_catch> catches;
-
- /* Map of import entry to patch labels and mfa */
- struct patch_import {
- std::vector<struct patch> patches;
- ErtsCodeMFA mfa;
- };
- typedef std::unordered_map<unsigned, struct patch_import> ImportMap;
- ImportMap imports;
-
- /* Map of fun entry to patch labels */
- struct patch_lambda {
- std::vector<struct patch> patches;
- Label trampoline;
- };
- typedef std::unordered_map<unsigned, struct patch_lambda> LambdaMap;
- LambdaMap lambdas;
-
- /* Map of literals to patch labels */
- struct patch_literal {
- std::vector<struct patch> patches;
- };
- typedef std::unordered_map<unsigned, struct patch_literal> LiteralMap;
- LiteralMap literals;
-
- /* All string patches */
- std::vector<struct patch> strings;
-
- /* All functions that have been seen so far */
- std::vector<BeamLabel> functions;
-
- /* The BEAM file we've been loaded from, if any. */
- const BeamFile *beam;
-
- BeamGlobalAssembler *ga;
-
- /* Used by emit to populate the labelToMFA map */
- Label current_label;
-
- /* The offset of our BeamCodeHeader, if any. */
- Label code_header;
-
- /* The module's on_load function, if any. */
- Label on_load;
-
- /* The end of the last function. Note that the dispatch table, constants,
- * and veneers may follow. */
- Label code_end;
-
- Eterm mod;
-
/* Save the last PC for an error. */
size_t last_error_offset = 0;
@@ -992,6 +899,115 @@ class BeamModuleAssembler : public BeamAssembler {
* fragments as if they were local. */
std::unordered_map<void (*)(), Label> _dispatchTable;
+ /* Skip unnecessary moves in load_source(), load_sources(), and
+ * mov_arg(). Don't use these variables directly. */
+ size_t last_destination_offset = 0;
+ arm::Gp last_destination_from1, last_destination_from2;
+ arm::Mem last_destination_to1, last_destination_to2;
+
+ /* Private helper. */
+ void preserve__cache(arm::Gp dst) {
+ last_destination_offset = a.offset();
+ invalidate_cache(dst);
+ }
+
+ /* Works as the STR instruction, but also updates the cache. */
+ void str_cache(arm::Gp src, arm::Mem dst) {
+ if (a.offset() == last_destination_offset &&
+ dst != last_destination_to1) {
+ /* Something is already cached in the first slot. Use the
+ * second slot. */
+ a.str(src, dst);
+ last_destination_offset = a.offset();
+ last_destination_to2 = dst;
+ last_destination_from2 = src;
+ } else {
+ /* Nothing cached yet, or the first slot has the same
+ * memory address as we will store into. Use the first
+ * slot and invalidate the second slot. */
+ a.str(src, dst);
+ last_destination_offset = a.offset();
+ last_destination_to1 = dst;
+ last_destination_from1 = src;
+ last_destination_to2 = arm::Mem();
+ }
+ }
+
+ /* Works as the STP instruction, but also updates the cache. */
+ void stp_cache(arm::Gp src1, arm::Gp src2, arm::Mem dst) {
+ safe_stp(src1, src2, dst);
+ last_destination_offset = a.offset();
+ last_destination_to1 = dst;
+ last_destination_from1 = src1;
+ last_destination_to2 =
+ arm::Mem(arm::GpX(dst.baseId()), dst.offset() + 8);
+ last_destination_from2 = src2;
+ }
+
+ void invalidate_cache(arm::Gp dst) {
+ if (dst == last_destination_from1) {
+ last_destination_to1 = arm::Mem();
+ last_destination_from1 = arm::Gp();
+ }
+ if (dst == last_destination_from2) {
+ last_destination_to2 = arm::Mem();
+ last_destination_from2 = arm::Gp();
+ }
+ }
+
+ /* Works like LDR, but looks in the cache first. */
+ void ldr_cached(arm::Gp dst, arm::Mem mem) {
+ if (a.offset() == last_destination_offset) {
+ arm::Gp cached_reg;
+ if (mem == last_destination_to1) {
+ cached_reg = last_destination_from1;
+ } else if (mem == last_destination_to2) {
+ cached_reg = last_destination_from2;
+ }
+
+ if (cached_reg.isValid()) {
+ /* This memory location is cached. */
+ if (cached_reg != dst) {
+ comment("simplified fetching of BEAM register");
+ a.mov(dst, cached_reg);
+ preserve__cache(dst);
+ } else {
+ comment("skipped fetching of BEAM register");
+ invalidate_cache(dst);
+ }
+ } else {
+ /* Not cached. Load and preserve the cache. */
+ a.ldr(dst, mem);
+ preserve__cache(dst);
+ }
+ } else {
+ /* The cache is invalid. */
+ a.ldr(dst, mem);
+ }
+ }
+
+ void mov_preserve_cache(arm::VecD dst, arm::VecD src) {
+ a.mov(dst, src);
+ }
+
+ void mov_preserve_cache(arm::Gp dst, arm::Gp src) {
+ if (a.offset() == last_destination_offset) {
+ a.mov(dst, src);
+ preserve__cache(dst);
+ } else {
+ a.mov(dst, src);
+ }
+ }
+
+ void mov_imm_preserve_cache(arm::Gp dst, UWord value) {
+ if (a.offset() == last_destination_offset) {
+ mov_imm(dst, value);
+ preserve__cache(dst);
+ } else {
+ mov_imm(dst, value);
+ }
+ }
+
public:
BeamModuleAssembler(BeamGlobalAssembler *ga,
Eterm mod,
@@ -1043,180 +1059,19 @@ public:
const ErtsCodeInfo *getOnLoad(void);
unsigned patchCatches(char *rw_base);
- void patchLambda(char *rw_base, unsigned index, BeamInstr I);
+ void patchLambda(char *rw_base, unsigned index, const ErlFunEntry *fe);
void patchLiteral(char *rw_base, unsigned index, Eterm lit);
- void patchImport(char *rw_base, unsigned index, BeamInstr I);
+ void patchImport(char *rw_base, unsigned index, const Export *import);
void patchStrings(char *rw_base, const byte *string);
protected:
- int getTypeUnion(const ArgSource &arg) const {
- auto typeIndex =
- arg.isRegister() ? arg.as<ArgRegister>().typeIndex() : 0;
-
- ASSERT(typeIndex < beam->types.count);
- return beam->types.entries[typeIndex].type_union;
- }
-
- auto getIntRange(const ArgSource &arg) const {
- if (arg.isSmall()) {
- Sint value = arg.as<ArgSmall>().getSigned();
- return std::make_pair(value, value);
- } else {
- auto typeIndex =
- arg.isRegister() ? arg.as<ArgRegister>().typeIndex() : 0;
-
- ASSERT(typeIndex < beam->types.count);
- const auto &entry = beam->types.entries[typeIndex];
- ASSERT(entry.type_union & BEAM_TYPE_INTEGER);
- return std::make_pair(entry.min, entry.max);
- }
- }
-
- bool always_small(const ArgSource &arg) const {
- if (arg.isSmall()) {
- return true;
- }
-
- int type_union = getTypeUnion(arg);
- if (type_union == BEAM_TYPE_INTEGER) {
- auto [min, max] = getIntRange(arg);
- return min <= max;
- } else {
- return false;
- }
- }
-
- bool always_immediate(const ArgSource &arg) const {
- if (arg.isImmed() || always_small(arg)) {
- return true;
- }
-
- int type_union = getTypeUnion(arg);
- return (type_union & BEAM_TYPE_MASK_ALWAYS_IMMEDIATE) == type_union;
- }
-
- bool always_same_types(const ArgSource &lhs, const ArgSource &rhs) const {
- int lhs_types = getTypeUnion(lhs);
- int rhs_types = getTypeUnion(rhs);
-
- /* We can only be certain that the types are the same when there's
- * one possible type. For example, if one is a number and the other
- * is an integer, they could differ if the former is a float. */
- if ((lhs_types & (lhs_types - 1)) == 0) {
- return lhs_types == rhs_types;
- }
-
- return false;
- }
-
- bool always_one_of(const ArgSource &arg, int types) const {
- if (arg.isImmed()) {
- if (arg.isSmall()) {
- return !!(types & BEAM_TYPE_INTEGER);
- } else if (arg.isAtom()) {
- return !!(types & BEAM_TYPE_ATOM);
- } else if (arg.isNil()) {
- return !!(types & BEAM_TYPE_NIL);
- }
-
- return false;
- } else {
- int type_union = getTypeUnion(arg);
- return type_union == (type_union & types);
- }
- }
-
- int masked_types(const ArgSource &arg, int mask) const {
- if (arg.isImmed()) {
- if (arg.isSmall()) {
- return mask & BEAM_TYPE_INTEGER;
- } else if (arg.isAtom()) {
- return mask & BEAM_TYPE_ATOM;
- } else if (arg.isNil()) {
- return mask & BEAM_TYPE_NIL;
- }
-
- return BEAM_TYPE_NONE;
- } else {
- return getTypeUnion(arg) & mask;
- }
- }
-
- bool exact_type(const ArgSource &arg, int type_id) const {
- return always_one_of(arg, type_id);
- }
-
- bool is_sum_small(const ArgSource &LHS, const ArgSource &RHS) {
- if (!(always_small(LHS) && always_small(RHS))) {
- return false;
- } else {
- Sint min, max;
- auto [min1, max1] = getIntRange(LHS);
- auto [min2, max2] = getIntRange(RHS);
- min = min1 + min2;
- max = max1 + max2;
- return IS_SSMALL(min) && IS_SSMALL(max);
- }
- }
-
- bool is_difference_small(const ArgSource &LHS, const ArgSource &RHS) {
- if (!(always_small(LHS) && always_small(RHS))) {
- return false;
- } else {
- Sint min, max;
- auto [min1, max1] = getIntRange(LHS);
- auto [min2, max2] = getIntRange(RHS);
- min = min1 - max2;
- max = max1 - min2;
- return IS_SSMALL(min) && IS_SSMALL(max);
- }
- }
-
- bool is_product_small(const ArgSource &LHS, const ArgSource &RHS) {
- if (!(always_small(LHS) && always_small(RHS))) {
- return false;
- } else {
- auto [min1, max1] = getIntRange(LHS);
- auto [min2, max2] = getIntRange(RHS);
- auto mag1 = std::max(std::abs(min1), std::abs(max1));
- auto mag2 = std::max(std::abs(min2), std::abs(max2));
-
- /*
- * mag1 * mag2 <= MAX_SMALL
- * mag1 <= MAX_SMALL / mag2 (when mag2 != 0)
- */
- ERTS_CT_ASSERT(MAX_SMALL < -MIN_SMALL);
- return mag2 == 0 || mag1 <= MAX_SMALL / mag2;
- }
- }
-
- bool is_bsl_small(const ArgSource &LHS, const ArgSource &RHS) {
- /*
- * In the code compiled by scripts/diffable, there never
- * seems to be any range information for the RHS. Therefore,
- * don't bother unless RHS is an immediate small.
- */
- if (!(always_small(LHS) && RHS.isSmall())) {
- return false;
- } else {
- auto [min1, max1] = getIntRange(LHS);
- auto rhs_val = RHS.as<ArgSmall>().getSigned();
-
- if (min1 < 0 || max1 == 0 || rhs_val < 0) {
- return false;
- }
-
- return rhs_val < Support::clz(max1) - _TAG_IMMED1_SIZE;
- }
- }
-
- /* Helpers */
void emit_gc_test(const ArgWord &Stack,
const ArgWord &Heap,
const ArgWord &Live);
void emit_gc_test_preserve(const ArgWord &Need,
const ArgWord &Live,
- arm::Gp term);
+ const ArgSource &Preserve,
+ arm::Gp preserve_reg);
arm::Mem emit_variable_apply(bool includeI);
arm::Mem emit_fixed_apply(const ArgWord &arity, bool includeI);
@@ -1225,17 +1080,12 @@ protected:
bool skip_fun_test = false,
bool skip_arity_test = false);
- arm::Gp emit_is_binary(const ArgLabel &Fail,
- const ArgSource &Src,
- Label next,
- Label subbin);
-
void emit_is_boxed(Label Fail, arm::Gp Src) {
BeamAssembler::emit_is_boxed(Fail, Src);
}
void emit_is_boxed(Label Fail, const ArgVal &Arg, arm::Gp Src) {
- if (always_one_of(Arg, BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::AlwaysBoxed>(Arg)) {
comment("skipped box test since argument is always boxed");
return;
}
@@ -1243,10 +1093,28 @@ protected:
BeamAssembler::emit_is_boxed(Fail, Src);
}
+ /* Copies `count` words from the address at `from`, to the address at `to`.
+ *
+ * Clobbers v30 and v31. */
+ void emit_copy_words_increment(arm::Gp from, arm::Gp to, size_t count);
+
void emit_get_list(const arm::Gp boxed_ptr,
const ArgRegister &Hd,
const ArgRegister &Tl);
+ void emit_add_sub_types(bool is_small_result,
+ const ArgSource &LHS,
+ const a64::Gp lhs_reg,
+ const ArgSource &RHS,
+ const a64::Gp rhs_reg,
+ const Label next);
+
+ void emit_are_both_small(const ArgSource &LHS,
+ const a64::Gp lhs_reg,
+ const ArgSource &RHS,
+ const a64::Gp rhs_reg,
+ const Label next);
+
void emit_div_rem(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS,
@@ -1261,6 +1129,7 @@ protected:
const ArgRegister &Dst);
void emit_error(int code);
+ void emit_error(int reason, const ArgSource &Src);
int emit_bs_get_field_size(const ArgSource &Size,
int unit,
@@ -1271,6 +1140,30 @@ protected:
void emit_bs_get_utf16(const ArgRegister &Ctx,
const ArgLabel &Fail,
const ArgWord &Flags);
+ void update_bin_state(arm::Gp bin_offset,
+ Sint bit_offset,
+ Sint size,
+ arm::Gp size_reg);
+ void set_zero(Sint effectiveSize);
+ void emit_construct_utf8(const ArgVal &Src,
+ Sint bit_offset,
+ bool is_byte_aligned);
+
+ void emit_read_bits(Uint bits,
+ const arm::Gp bin_offset,
+ const arm::Gp bin_base,
+ const arm::Gp bitdata);
+
+ void emit_extract_integer(const arm::Gp bitdata,
+ Uint flags,
+ Uint bits,
+ const ArgRegister &Dst);
+
+ void emit_extract_binary(const arm::Gp bitdata,
+ Uint bits,
+ const ArgRegister &Dst);
+
+ UWord bs_get_flags(const ArgVal &val);
void emit_raise_exception();
void emit_raise_exception(const ErtsCodeMFA *exp);
@@ -1288,11 +1181,22 @@ protected:
void emit_validate_unicode(Label next, Label fail, arm::Gp value);
- void emit_bif_is_eq_ne_exact_immed(const ArgSource &LHS,
- const ArgSource &RHS,
- const ArgRegister &Dst,
- Eterm fail_value,
- Eterm succ_value);
+ void ubif_comment(const ArgWord &Bif);
+
+ void emit_cmp_immed_to_bool(arm::CondCode cc,
+ const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst);
+
+ void emit_cond_to_bool(arm::CondCode cc, const ArgRegister &Dst);
+ void emit_bif_is_ge_lt(arm::CondCode cc,
+ const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst);
+ void emit_bif_min_max(arm::CondCode cc,
+ const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst);
void emit_proc_lc_unrequire(void);
void emit_proc_lc_require(void);
@@ -1302,7 +1206,8 @@ protected:
/* Returns a vector of the untagged and rebased `args`. The adjusted
* `comparand` is stored in ARG1. */
- const std::vector<ArgVal> emit_select_untag(const Span<ArgVal> &args,
+ const std::vector<ArgVal> emit_select_untag(const ArgSource &Src,
+ const Span<ArgVal> &args,
a64::Gp comparand,
Label fail,
UWord base,
@@ -1457,11 +1362,13 @@ protected:
} else if (arg.isRegister()) {
if (isRegisterBacked(arg)) {
auto index = arg.as<ArgXRegister>().get();
- return Variable(register_backed_xregs[index]);
+ arm::Gp reg = register_backed_xregs[index];
+ invalidate_cache(reg);
+ return Variable(reg);
}
auto ref = getArgRef(arg);
- a.ldr(tmp, ref);
+ ldr_cached(tmp, ref);
return Variable(tmp, ref);
} else {
if (arg.isImmed() || arg.isWord()) {
@@ -1469,7 +1376,7 @@ protected:
: arg.as<ArgWord>().get();
if (Support::isIntOrUInt32(val)) {
- mov_imm(tmp, val);
+ mov_imm_preserve_cache(tmp, val);
return Variable(tmp);
}
}
@@ -1483,8 +1390,7 @@ protected:
arm::Gp tmp1,
const ArgVal &Src2,
arm::Gp tmp2) {
- if (Src1.isRegister() && Src2.isRegister() && !isRegisterBacked(Src1) &&
- !isRegisterBacked(Src2)) {
+ if (!isRegisterBacked(Src1) && !isRegisterBacked(Src2)) {
switch (ArgVal::memory_relation(Src1, Src2)) {
case ArgVal::Relation::consecutive:
safe_ldp(tmp1, tmp2, Src1, Src2);
@@ -1520,19 +1426,24 @@ protected:
template<typename Reg>
void mov_var(const Variable<Reg> &to, Reg from) {
if (to.reg != from) {
- a.mov(to.reg, from);
+ mov_preserve_cache(to.reg, from);
}
}
template<typename Reg>
void mov_var(Reg to, const Variable<Reg> &from) {
if (to != from.reg) {
- a.mov(to, from.reg);
+ mov_preserve_cache(to, from.reg);
}
}
- template<typename Reg>
- void flush_var(const Variable<Reg> &to) {
+ void flush_var(const Variable<arm::Gp> &to) {
+ if (to.mem.hasBase()) {
+ str_cache(to.reg, to.mem);
+ }
+ }
+
+ void flush_var(const Variable<arm::VecD> &to) {
if (to.mem.hasBase()) {
a.str(to.reg, to.mem);
}
@@ -1546,10 +1457,10 @@ protected:
if (mem1.hasBaseReg() && mem2.hasBaseReg() &&
mem1.baseId() == mem2.baseId()) {
if (mem1.offset() + 8 == mem2.offset()) {
- safe_stp(to1.reg, to2.reg, mem1);
+ stp_cache(to1.reg, to2.reg, mem1);
return;
} else if (mem1.offset() == mem2.offset() + 8) {
- safe_stp(to2.reg, to1.reg, mem2);
+ stp_cache(to2.reg, to1.reg, mem2);
return;
}
}
@@ -1604,18 +1515,12 @@ protected:
if (arg.isImmed() || arg.isWord()) {
Sint val = arg.isImmed() ? arg.as<ArgImmed>().get()
: arg.as<ArgWord>().get();
-
- if (Support::isUInt12(val)) {
- a.cmp(gp, imm(val));
- return;
- } else if (Support::isUInt12(-val)) {
- a.cmn(gp, imm(-val));
- return;
- }
+ cmp(gp, val);
+ return;
}
- mov_arg(SUPER_TMP, arg);
- a.cmp(gp, SUPER_TMP);
+ auto tmp = load_source(arg, SUPER_TMP);
+ a.cmp(gp, tmp.reg);
}
void safe_stp(arm::Gp gp1,
@@ -1628,14 +1533,14 @@ protected:
}
void safe_stp(arm::Gp gp1, arm::Gp gp2, arm::Mem mem) {
+ size_t abs_offset = std::abs(mem.offset());
auto offset = mem.offset();
ASSERT(gp1.isGpX() && gp2.isGpX());
- if (std::abs(offset) <= sizeof(Eterm) * MAX_LDP_STP_DISPLACEMENT) {
+ if (abs_offset <= sizeof(Eterm) * MAX_LDP_STP_DISPLACEMENT) {
a.stp(gp1, gp2, mem);
- } else if (std::abs(offset) <
- sizeof(Eterm) * MAX_LDR_STR_DISPLACEMENT) {
+ } else if (abs_offset < sizeof(Eterm) * MAX_LDR_STR_DISPLACEMENT) {
/* Note that we used `<` instead of `<=`, as we're loading two
* elements rather than one. */
a.str(gp1, mem);
@@ -1647,12 +1552,13 @@ protected:
}
void safe_ldr(arm::Gp gp, arm::Mem mem) {
+ size_t abs_offset = std::abs(mem.offset());
auto offset = mem.offset();
ASSERT(mem.hasBaseReg() && !mem.hasIndex());
ASSERT(gp.isGpX());
- if (std::abs(offset) <= sizeof(Eterm) * MAX_LDR_STR_DISPLACEMENT) {
+ if (abs_offset <= sizeof(Eterm) * MAX_LDR_STR_DISPLACEMENT) {
a.ldr(gp, mem);
} else {
add(SUPER_TMP, arm::GpX(mem.baseId()), offset);
@@ -1660,6 +1566,21 @@ protected:
}
}
+ void safe_ldur(arm::Gp gp, arm::Mem mem) {
+ size_t abs_offset = std::abs(mem.offset());
+ auto offset = mem.offset();
+
+ ASSERT(mem.hasBaseReg() && !mem.hasIndex());
+ ASSERT(gp.isGpX());
+
+ if (abs_offset <= MAX_LDUR_STUR_DISPLACEMENT) {
+ a.ldur(gp, mem);
+ } else {
+ add(SUPER_TMP, arm::GpX(mem.baseId()), offset);
+ a.ldr(gp, arm::Mem(SUPER_TMP));
+ }
+ }
+
void safe_ldp(arm::Gp gp1,
arm::Gp gp2,
const ArgVal &Src1,
@@ -1671,15 +1592,15 @@ protected:
}
void safe_ldp(arm::Gp gp1, arm::Gp gp2, arm::Mem mem) {
+ size_t abs_offset = std::abs(mem.offset());
auto offset = mem.offset();
ASSERT(gp1.isGpX() && gp2.isGpX());
ASSERT(gp1 != gp2);
- if (std::abs(offset) <= sizeof(Eterm) * MAX_LDP_STP_DISPLACEMENT) {
+ if (abs_offset <= sizeof(Eterm) * MAX_LDP_STP_DISPLACEMENT) {
a.ldp(gp1, gp2, mem);
- } else if (std::abs(offset) <
- sizeof(Eterm) * MAX_LDR_STR_DISPLACEMENT) {
+ } else if (abs_offset < sizeof(Eterm) * MAX_LDR_STR_DISPLACEMENT) {
/* Note that we used `<` instead of `<=`, as we're loading two
* elements rather than one. */
a.ldr(gp1, mem);
@@ -1691,10 +1612,9 @@ protected:
}
};
-void beamasm_metadata_update(
- std::string module_name,
- ErtsCodePtr base_address,
- size_t code_size,
- const std::vector<BeamAssembler::AsmRange> &ranges);
+void beamasm_metadata_update(std::string module_name,
+ ErtsCodePtr base_address,
+ size_t code_size,
+ const std::vector<AsmRange> &ranges);
void beamasm_metadata_early_init();
void beamasm_metadata_late_init();
diff --git a/erts/emulator/beam/jit/arm/beam_asm_global.cpp b/erts/emulator/beam/jit/arm/beam_asm_global.cpp
index 5e4360a770..a4d20a9356 100644
--- a/erts/emulator/beam/jit/arm/beam_asm_global.cpp
+++ b/erts/emulator/beam/jit/arm/beam_asm_global.cpp
@@ -51,11 +51,13 @@ BeamGlobalAssembler::BeamGlobalAssembler(JitAllocator *allocator)
}
{
- /* We have no need of the module pointers as we use `getCode(...)` for
- * everything. */
- const void *_ignored_exec;
- void *_ignored_rw;
- _codegen(allocator, &_ignored_exec, &_ignored_rw);
+ const void *executable_region;
+ void *writable_region;
+
+ BeamAssembler::codegen(allocator, &executable_region, &writable_region);
+ VirtMem::flushInstructionCache((void *)executable_region,
+ code.codeSize());
+ VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadExecute);
}
std::vector<AsmRange> ranges;
@@ -208,8 +210,8 @@ void BeamGlobalAssembler::emit_export_trampoline() {
a.bind(error_handler);
{
emit_enter_runtime_frame();
- emit_enter_runtime<Update::eReductions | Update::eStack |
- Update::eHeap | Update::eXRegs>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc |
+ Update::eXRegs>();
lea(ARG2, arm::Mem(ARG1, offsetof(Export, info.mfa)));
a.mov(ARG1, c_p);
@@ -219,8 +221,8 @@ void BeamGlobalAssembler::emit_export_trampoline() {
/* If there is no error_handler, any number of X registers
* can be live. */
- emit_leave_runtime<Update::eReductions | Update::eStack |
- Update::eHeap | Update::eXRegs>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc |
+ Update::eXRegs>();
emit_leave_runtime_frame();
a.cbz(ARG1, labels[process_exit]);
@@ -296,7 +298,7 @@ void BeamGlobalAssembler::emit_raise_exception_shared() {
* traces because it's equal to the error address. */
a.str(ARG2, arm::Mem(E, -8).pre());
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs>();
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs>();
/* The error address must be a valid CP or NULL. */
a.tst(ARG2, imm(_CPMASK));
@@ -307,7 +309,7 @@ void BeamGlobalAssembler::emit_raise_exception_shared() {
load_x_reg_array(ARG3);
runtime_call<4>(handle_error);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs>();
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs>();
a.cbz(ARG1, labels[do_schedule]);
diff --git a/erts/emulator/beam/jit/arm/beam_asm_global.hpp.pl b/erts/emulator/beam/jit/arm/beam_asm_global.hpp.pl
index f42d16e853..b2478bd56d 100644
--- a/erts/emulator/beam/jit/arm/beam_asm_global.hpp.pl
+++ b/erts/emulator/beam/jit/arm/beam_asm_global.hpp.pl
@@ -2,7 +2,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2022. All Rights Reserved.
+# Copyright Ericsson AB 2022-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -41,6 +41,8 @@ my @beam_global_funcs = qw(
bs_bit_size_shared
bs_create_bin_error_shared
bs_get_tail_shared
+ bs_get_utf8_shared
+ bs_get_utf8_short_shared
bs_size_check_shared
call_bif_shared
call_light_bif_shared
@@ -49,18 +51,24 @@ my @beam_global_funcs = qw(
call_nif_early
call_nif_shared
check_float_error
+ construct_utf8_shared
debug_bp
dispatch_bif
dispatch_nif
dispatch_return
- dispatch_save_calls
+ dispatch_save_calls_export
+ dispatch_save_calls_fun
export_trampoline
fconv_shared
+ get_sint64_shared
handle_and_error
handle_call_fun_error
handle_element_error_shared
handle_hd_error
+ handle_map_get_badkey
+ handle_map_get_badmap
handle_map_size_error
+ handle_node_error
handle_not_error
handle_or_error
handle_tl_error
@@ -82,12 +90,13 @@ my @beam_global_funcs = qw(
i_length_guard_shared
i_length_body_shared
i_loop_rec_shared
- i_new_small_map_lit_shared
i_test_yield_shared
i_bxor_body_shared
int_div_rem_body_shared
int_div_rem_guard_shared
internal_hash_helper
+ is_in_range_shared
+ is_ge_lt_shared
minus_body_shared
new_map_shared
update_map_assoc_shared
@@ -97,6 +106,7 @@ my @beam_global_funcs = qw(
process_main
raise_exception
raise_exception_shared
+ store_unaligned
times_body_shared
times_guard_shared
unary_minus_body_shared
diff --git a/erts/emulator/beam/jit/arm/beam_asm_module.cpp b/erts/emulator/beam/jit/arm/beam_asm_module.cpp
index 3f2aa86a02..57f5746a8c 100644
--- a/erts/emulator/beam/jit/arm/beam_asm_module.cpp
+++ b/erts/emulator/beam/jit/arm/beam_asm_module.cpp
@@ -373,6 +373,8 @@ void BeamModuleAssembler::emit_label(const ArgLabel &Label) {
current_label = rawLabels[Label.get()];
bind_veneer_target(current_label);
+
+ last_destination_offset = ~0;
}
void BeamModuleAssembler::emit_aligned_label(const ArgLabel &Label,
@@ -473,80 +475,6 @@ void BeamModuleAssembler::emit_call_error_handler() {
emit_nyi("call_error_handler should never be called");
}
-unsigned BeamModuleAssembler::patchCatches(char *rw_base) {
- unsigned catch_no = BEAM_CATCHES_NIL;
-
- for (const auto &c : catches) {
- const auto &patch = c.patch;
- ErtsCodePtr handler;
-
- handler = (ErtsCodePtr)getCode(c.handler);
- catch_no = beam_catches_cons(handler, catch_no, nullptr);
-
- /* Patch the `mov` instruction with the catch tag */
- auto offset = code.labelOffsetFromBase(patch.where);
- auto where = (Eterm *)&rw_base[offset];
-
- ASSERT(LLONG_MAX == *where);
- Eterm catch_term = make_catch(catch_no);
-
- /* With the current tag scheme, more than 33 million
- * catches can exist at once. */
- ERTS_ASSERT(catch_term >> 31 == 0);
-
- *where = catch_term;
- }
-
- return catch_no;
-}
-
-void BeamModuleAssembler::patchImport(char *rw_base,
- unsigned index,
- BeamInstr I) {
- for (const auto &patch : imports[index].patches) {
- auto offset = code.labelOffsetFromBase(patch.where);
- auto where = (Eterm *)&rw_base[offset];
-
- ASSERT(LLONG_MAX == *where);
- *where = I + patch.val_offs;
- }
-}
-
-void BeamModuleAssembler::patchLambda(char *rw_base,
- unsigned index,
- BeamInstr I) {
- for (const auto &patch : lambdas[index].patches) {
- auto offset = code.labelOffsetFromBase(patch.where);
- auto where = (Eterm *)&rw_base[offset];
-
- ASSERT(LLONG_MAX == *where);
- *where = I + patch.val_offs;
- }
-}
-
-void BeamModuleAssembler::patchLiteral(char *rw_base,
- unsigned index,
- Eterm lit) {
- for (const auto &patch : literals[index].patches) {
- auto offset = code.labelOffsetFromBase(patch.where);
- auto where = (Eterm *)&rw_base[offset];
-
- ASSERT(LLONG_MAX == *where);
- *where = lit + patch.val_offs;
- }
-}
-
-void BeamModuleAssembler::patchStrings(char *rw_base,
- const byte *string_table) {
- for (const auto &patch : strings) {
- auto offset = code.labelOffsetFromBase(patch.where);
- auto where = (const byte **)&rw_base[offset];
-
- ASSERT(LLONG_MAX == (Eterm)*where);
- *where = string_table + patch.val_offs;
- }
-}
-
const Label &BeamModuleAssembler::resolve_beam_label(const ArgLabel &Lbl,
enum Displacement disp) {
ASSERT(Lbl.isLabel());
@@ -695,11 +623,9 @@ void BeamModuleAssembler::check_pending_stubs() {
}
void BeamModuleAssembler::flush_pending_stubs(size_t range) {
- size_t effective_offset;
+ ssize_t effective_offset = a.offset() + range;
Label next;
- effective_offset = a.offset() + range;
-
while (!_pending_veneers.empty()) {
const Veneer &veneer = _pending_veneers.top();
@@ -804,30 +730,37 @@ void BeamModuleAssembler::emit_constant(const Constant &constant) {
} else if (value.isLabel()) {
a.embedLabel(rawLabels.at(value.as<ArgLabel>().get()));
} else {
- a.embedUInt64(LLONG_MAX);
-
switch (value.getType()) {
case ArgVal::BytePtr:
- strings.push_back({anchor, value.as<ArgBytePtr>().get()});
+ strings.push_back({anchor, 0, value.as<ArgBytePtr>().get()});
+ a.embedUInt64(LLONG_MAX);
break;
case ArgVal::Catch: {
auto handler = rawLabels[value.as<ArgCatch>().get()];
- catches.push_back({{anchor, 0}, handler});
+ catches.push_back({{anchor, 0, 0}, handler});
+
+ /* Catches are limited to 32 bits, but since we don't want to load
+ * 32-bit argument values due to displacement limits, we'll store
+ * this as a 64-bit value with the upper bits cleared. */
+ a.embedUInt64(INT_MAX);
break;
}
case ArgVal::Export: {
auto index = value.as<ArgExport>().get();
- imports[index].patches.push_back({anchor, 0});
+ imports[index].patches.push_back({anchor, 0, 0});
+ a.embedUInt64(LLONG_MAX);
break;
}
case ArgVal::FunEntry: {
auto index = value.as<ArgLambda>().get();
- lambdas[index].patches.push_back({anchor, 0});
+ lambdas[index].patches.push_back({anchor, 0, 0});
+ a.embedUInt64(LLONG_MAX);
break;
}
case ArgVal::Literal: {
auto index = value.as<ArgLiteral>().get();
- literals[index].patches.push_back({anchor, 0});
+ literals[index].patches.push_back({anchor, 0, 0});
+ a.embedUInt64(LLONG_MAX);
break;
}
default:
diff --git a/erts/emulator/beam/jit/arm/generators.tab b/erts/emulator/beam/jit/arm/generators.tab
index b6ec48c678..2ae1582d4b 100644
--- a/erts/emulator/beam/jit/arm/generators.tab
+++ b/erts/emulator/beam/jit/arm/generators.tab
@@ -2,7 +2,7 @@
//
// %CopyrightBegin%
//
-// Copyright Ericsson AB 2020-2022. All Rights Reserved.
+// Copyright Ericsson AB 2020-2023. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,47 +19,6 @@
// %CopyrightEnd%
//
-// Generate the fastest instruction to fetch an integer from a binary.
-gen.get_integer2(Fail, Ms, Live, Size, Unit, Flags, Dst) {
- BeamOp* op;
- UWord bits;
-
- $NewBeamOp(S, op);
- $NativeEndian(Flags);
-
- if (Size.type == TAG_i) {
- if (!beam_load_safe_mul(Size.val, Unit.val, &bits)) {
- $BeamOpNameArity(op, jump, 1);
- op->a[0] = Fail;
-
- return op;
- } else if (bits <= 1023) {
- $BeamOpNameArity(op, i_bs_get_fixed_integer, 6);
- op->a[0] = Ms;
- op->a[1] = Fail;
- op->a[2] = Live;
- op->a[3].type = TAG_u;
- op->a[3].val = Flags.val;
- op->a[4].type = TAG_u;
- op->a[4].val = bits;
- op->a[5] = Dst;
-
- return op;
- }
- }
-
- $BeamOpNameArity(op, i_bs_get_integer, 6);
- op->a[0] = Ms;
- op->a[1] = Fail;
- op->a[2] = Live;
- op->a[3].type = TAG_u;
- op->a[3].val = (Unit.val << 3) | Flags.val;
- op->a[4] = Size;
- op->a[5] = Dst;
-
- return op;
-}
-
gen.select_tuple_arity(Src, Fail, Size, Rest) {
BeamOp* op;
BeamOpArg *tmp;
@@ -330,7 +289,7 @@ gen.new_small_map_lit(Dst, Live, Size, Rest) {
*dst++ = Rest[i + 1];
}
- lit = beamfile_add_literal(&S->beam, keys);
+ lit = beamfile_add_literal(&S->beam, keys, 1);
erts_free(ERTS_ALC_T_LOADER_TMP, tmp);
op->a[0] = Dst;
@@ -567,6 +526,28 @@ gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) {
Flags.val = flags;
$NativeEndian(Flags);
op->a[i+fixed_args+3] = Flags;
+
+ /*
+ * Replace short string segments with integer segments.
+ * Integer segments can be combined with adjacent integer
+ * segments for better performance.
+ */
+ if (op->a[i+fixed_args+0].val == am_string) {
+ Sint num_chars = op->a[i+fixed_args+5].val;
+ if (num_chars <= 4) {
+ Sint index = op->a[i+fixed_args+4].val;
+ const byte* s = S->beam.strings.data + index;
+ Uint num = 0;
+ op->a[i+fixed_args+0].val = am_integer;
+ op->a[i+fixed_args+2].val = 8;
+ op->a[i+fixed_args+5].val = num_chars;
+ while (num_chars-- > 0) {
+ num = num << 8 | *s++;
+ }
+ op->a[i+fixed_args+4].type = TAG_i;
+ op->a[i+fixed_args+4].val = num;
+ }
+ }
}
if (op->a[4].val == am_private_append && Alloc.val != 0) {
@@ -584,3 +565,77 @@ gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) {
return op;
}
+
+gen.bs_match(Fail, Ctx, N, List) {
+ BeamOp* op;
+ int fixed_args;
+ int i;
+
+ /*
+ * If a BEAM file produced by a later version of Erlang/OTP
+ * is accidentally loaded into an earlier version, ensure
+ * that the loading fails (as opposed to crashing the runtime)
+ * if there are any unknown sub commands.
+ */
+ i = 0;
+ while (i < N.val) {
+ BeamOpArg current = List[i++];
+
+ if (current.type != TAG_a) {
+ goto error;
+ }
+
+ switch (current.val) {
+ case am_ensure_exactly:
+ case am_skip:
+ i += 1;
+ break;
+ case am_ensure_at_least:
+ i += 2;
+ break;
+ case am_get_tail:
+ case am_Eq:
+ i += 3;
+ break;
+ case am_binary:
+ case am_integer:
+ i += 5;
+ break;
+ default: {
+ error:
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, bad_bs_match, 1);
+ op->a[0] = current;
+ return op;
+ }
+ }
+ }
+
+ /*
+ * Make sure that we don't attempt to pass any overflow tags to the JIT.
+ */
+
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, i_bs_match, 2);
+ fixed_args = op->arity;
+ $BeamOpArity(op, (N.val + fixed_args));
+
+ op->a[0] = Fail;
+ op->a[1] = Ctx;
+
+ for (i = 0; i < N.val; i++) {
+ BeamOpArg current;
+
+ current = List[i];
+ if (current.type == TAG_o) {
+ /* An overflow tag (in ensure_at_least or ensure_exactly)
+ * means that the match will always fail. */
+ $BeamOpNameArity(op, jump, 1);
+ op->a[0] = Fail;
+ return op;
+ }
+ op->a[i+fixed_args] = current;
+ }
+
+ return op;
+}
diff --git a/erts/emulator/beam/jit/arm/instr_arith.cpp b/erts/emulator/beam/jit/arm/instr_arith.cpp
index 59c90b712a..1fbec6dbb2 100644
--- a/erts/emulator/beam/jit/arm/instr_arith.cpp
+++ b/erts/emulator/beam/jit/arm/instr_arith.cpp
@@ -26,6 +26,70 @@ extern "C"
#include "big.h"
}
+void BeamModuleAssembler::emit_add_sub_types(bool is_small_result,
+ const ArgSource &LHS,
+ const a64::Gp lhs_reg,
+ const ArgSource &RHS,
+ const a64::Gp rhs_reg,
+ const Label next) {
+ if (exact_type<BeamTypeId::Integer>(LHS) &&
+ exact_type<BeamTypeId::Integer>(RHS) && is_small_result) {
+ comment("skipped overflow test because the result is always small");
+ emit_are_both_small(LHS, lhs_reg, RHS, rhs_reg, next);
+ } else {
+ if (always_small(RHS)) {
+ a.and_(TMP1, lhs_reg, imm(_TAG_IMMED1_MASK));
+ } else if (always_small(LHS)) {
+ a.and_(TMP1, rhs_reg, imm(_TAG_IMMED1_MASK));
+ } else {
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.and_(TMP1, lhs_reg, rhs_reg);
+ a.and_(TMP1, TMP1, imm(_TAG_IMMED1_MASK));
+ }
+
+ comment("test for not overflow and small operands");
+ a.ccmp(TMP1,
+ imm(_TAG_IMMED1_SMALL),
+ imm(NZCV::kNone),
+ imm(arm::CondCode::kVC));
+ a.b_eq(next);
+ }
+}
+
+void BeamModuleAssembler::emit_are_both_small(const ArgSource &LHS,
+ const a64::Gp lhs_reg,
+ const ArgSource &RHS,
+ const a64::Gp rhs_reg,
+ const Label next) {
+ if (always_small(RHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(LHS)) {
+ comment("simplified test for small operand since other types are "
+ "boxed");
+ emit_is_boxed(next, lhs_reg);
+ } else if (always_small(LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ RHS)) {
+ comment("simplified test for small operand since other types are "
+ "boxed");
+ emit_is_boxed(next, rhs_reg);
+ } else if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ RHS)) {
+ comment("simplified test for small operands since other types are "
+ "boxed");
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.and_(TMP1, lhs_reg, rhs_reg);
+ emit_is_boxed(next, TMP1);
+ } else {
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.and_(TMP1, lhs_reg, rhs_reg);
+ a.and_(TMP1, TMP1, imm(_TAG_IMMED1_MASK));
+ a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
+ a.b_eq(next);
+ }
+}
+
/*
* ARG2 = LHS
* ARG3 = RHS
@@ -73,8 +137,9 @@ void BeamModuleAssembler::emit_i_plus(const ArgLabel &Fail,
const ArgRegister &Dst) {
bool rhs_is_arm_literal =
RHS.isSmall() && Support::isUInt12(RHS.as<ArgSmall>().get());
+ bool is_small_result = is_sum_small_if_args_are_small(LHS, RHS);
- if (is_sum_small(LHS, RHS)) {
+ if (always_small(LHS) && always_small(RHS) && is_small_result) {
auto dst = init_destination(Dst, ARG1);
if (rhs_is_arm_literal) {
auto lhs = load_source(LHS, ARG2);
@@ -96,31 +161,14 @@ void BeamModuleAssembler::emit_i_plus(const ArgLabel &Fail,
auto [lhs, rhs] = load_sources(LHS, ARG2, RHS, ARG3);
if (rhs_is_arm_literal) {
- comment("add small constant with overflow check");
Uint cleared_tag = RHS.as<ArgSmall>().get() & ~_TAG_IMMED1_MASK;
a.adds(ARG1, lhs.reg, imm(cleared_tag));
} else {
- comment("addition with overflow check");
a.and_(TMP1, rhs.reg, imm(~_TAG_IMMED1_MASK));
a.adds(ARG1, lhs.reg, TMP1);
}
- if (always_small(RHS)) {
- a.and_(TMP1, lhs.reg, imm(_TAG_IMMED1_MASK));
- } else if (always_small(LHS)) {
- a.and_(TMP1, rhs.reg, imm(_TAG_IMMED1_MASK));
- } else {
- ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
- a.and_(TMP1, lhs.reg, rhs.reg);
- a.and_(TMP1, TMP1, imm(_TAG_IMMED1_MASK));
- }
-
- /* Test for not overflow AND small operands. */
- a.ccmp(TMP1,
- imm(_TAG_IMMED1_SMALL),
- imm(NZCV::kNone),
- imm(arm::CondCode::kVC));
- a.b_eq(next);
+ emit_add_sub_types(is_small_result, LHS, lhs.reg, RHS, rhs.reg, next);
mov_var(ARG2, lhs);
mov_var(ARG3, rhs);
@@ -186,11 +234,13 @@ void BeamModuleAssembler::emit_i_unary_minus(const ArgLabel &Fail,
const ArgSource &Src,
const ArgRegister &Dst) {
auto src = load_source(Src, ARG2);
+ auto zero = ArgImmed(make_small(0));
+ bool is_small_result = is_diff_small_if_args_are_small(zero, Src);
a.mov(TMP1, imm(_TAG_IMMED1_SMALL));
a.and_(TMP2, src.reg, imm(~_TAG_IMMED1_MASK));
- if (is_difference_small(ArgImmed(make_small(0)), Src)) {
+ if (always_small(Src) && is_small_result) {
auto dst = init_destination(Dst, ARG1);
comment("no overflow test because result is always small");
a.sub(dst.reg, TMP1, TMP2);
@@ -274,8 +324,9 @@ void BeamModuleAssembler::emit_i_minus(const ArgLabel &Fail,
const ArgRegister &Dst) {
bool rhs_is_arm_literal =
RHS.isSmall() && Support::isUInt12(RHS.as<ArgSmall>().get());
+ bool is_small_result = is_diff_small_if_args_are_small(LHS, RHS);
- if (is_difference_small(LHS, RHS)) {
+ if (always_small(LHS) && always_small(RHS) && is_small_result) {
auto dst = init_destination(Dst, ARG1);
if (rhs_is_arm_literal) {
auto lhs = load_source(LHS, ARG2);
@@ -297,31 +348,14 @@ void BeamModuleAssembler::emit_i_minus(const ArgLabel &Fail,
auto [lhs, rhs] = load_sources(LHS, ARG2, RHS, ARG3);
if (rhs_is_arm_literal) {
- comment("subtract small constant with overflow check");
Uint cleared_tag = RHS.as<ArgSmall>().get() & ~_TAG_IMMED1_MASK;
a.subs(ARG1, lhs.reg, imm(cleared_tag));
} else {
- comment("subtraction with overflow check");
a.and_(TMP1, rhs.reg, imm(~_TAG_IMMED1_MASK));
a.subs(ARG1, lhs.reg, TMP1);
}
- if (always_small(RHS)) {
- a.and_(TMP1, lhs.reg, imm(_TAG_IMMED1_MASK));
- } else if (always_small(LHS)) {
- a.and_(TMP1, rhs.reg, imm(_TAG_IMMED1_MASK));
- } else {
- ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
- a.and_(TMP1, lhs.reg, rhs.reg);
- a.and_(TMP1, TMP1, imm(_TAG_IMMED1_MASK));
- }
-
- /* Test for not overflow AND small operands. */
- a.ccmp(TMP1,
- imm(_TAG_IMMED1_SMALL),
- imm(NZCV::kNone),
- imm(arm::CondCode::kVC));
- a.b_eq(next);
+ emit_add_sub_types(is_small_result, LHS, lhs.reg, RHS, rhs.reg, next);
mov_var(ARG2, lhs);
mov_var(ARG3, rhs);
@@ -453,20 +487,32 @@ void BeamModuleAssembler::emit_i_times(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS,
const ArgRegister &Dst) {
- if (is_product_small(LHS, RHS)) {
+ bool is_small_result = is_product_small_if_args_are_small(LHS, RHS);
+
+ if (always_small(LHS) && always_small(RHS) && is_small_result) {
auto dst = init_destination(Dst, ARG1);
comment("multiplication without overflow check");
if (RHS.isSmall()) {
auto lhs = load_source(LHS, ARG2);
+ Sint factor = RHS.as<ArgSmall>().getSigned();
+
a.and_(TMP1, lhs.reg, imm(~_TAG_IMMED1_MASK));
- mov_imm(TMP2, RHS.as<ArgSmall>().getSigned());
+ if (Support::isPowerOf2(factor)) {
+ int trailing_bits = Support::ctz<Eterm>(factor);
+ comment("optimized multiplication by replacing with left "
+ "shift");
+ a.lsl(TMP1, TMP1, imm(trailing_bits));
+ } else {
+ mov_imm(TMP2, factor);
+ a.mul(TMP1, TMP1, TMP2);
+ }
} else {
auto [lhs, rhs] = load_sources(LHS, ARG2, RHS, ARG3);
a.and_(TMP1, lhs.reg, imm(~_TAG_IMMED1_MASK));
a.asr(TMP2, rhs.reg, imm(_TAG_IMMED1_SIZE));
+ a.mul(TMP1, TMP1, TMP2);
}
- a.mul(dst.reg, TMP1, TMP2);
- a.orr(dst.reg, dst.reg, imm(_TAG_IMMED1_SMALL));
+ a.orr(dst.reg, TMP1, imm(_TAG_IMMED1_SMALL));
flush_var(dst);
} else {
auto [lhs, rhs] = load_sources(LHS, ARG2, RHS, ARG3);
@@ -647,21 +693,39 @@ void BeamModuleAssembler::emit_div_rem(const ArgLabel &Fail,
auto remainder = init_destination(Remainder, ARG2);
comment("skipped test for smalls operands and overflow");
- a.asr(TMP1, lhs.reg, imm(_TAG_IMMED1_SIZE));
- mov_imm(TMP2, divisor);
- a.sdiv(quotient.reg, TMP1, TMP2);
- if (need_rem) {
- a.msub(remainder.reg, quotient.reg, TMP2, TMP1);
- }
- mov_imm(TMP3, _TAG_IMMED1_SMALL);
- arm::Shift tagShift = arm::lsl(_TAG_IMMED1_SIZE);
+ if (Support::isPowerOf2(divisor) &&
+ std::get<0>(getClampedRange(LHS)) >= 0) {
+ int trailing_bits = Support::ctz<Eterm>(divisor);
+ if (need_div) {
+ comment("optimized div by replacing with right shift");
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.lsr(quotient.reg, lhs.reg, imm(trailing_bits));
+ a.orr(quotient.reg, quotient.reg, imm(_TAG_IMMED1_SMALL));
+ }
+ if (need_rem) {
+ comment("optimized rem by replacing with masking");
+ auto mask = Support::lsbMask<Uint>(trailing_bits +
+ _TAG_IMMED1_SIZE);
+ a.and_(remainder.reg, lhs.reg, imm(mask));
+ }
+ } else {
+ a.asr(TMP1, lhs.reg, imm(_TAG_IMMED1_SIZE));
+ mov_imm(TMP2, divisor);
+ a.sdiv(quotient.reg, TMP1, TMP2);
+ if (need_rem) {
+ a.msub(remainder.reg, quotient.reg, TMP2, TMP1);
+ }
- if (need_div) {
- a.orr(quotient.reg, TMP3, quotient.reg, tagShift);
- }
- if (need_rem) {
- a.orr(remainder.reg, TMP3, remainder.reg, tagShift);
+ mov_imm(TMP3, _TAG_IMMED1_SMALL);
+ const arm::Shift tagShift = arm::lsl(_TAG_IMMED1_SIZE);
+ if (need_div) {
+ a.orr(quotient.reg, TMP3, quotient.reg, tagShift);
+ }
+ if (need_rem) {
+ a.orr(remainder.reg, TMP3, remainder.reg, tagShift);
+ }
}
+
if (need_div) {
flush_var(quotient);
}
@@ -825,24 +889,52 @@ void BeamModuleAssembler::emit_i_band(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS,
const ArgRegister &Dst) {
+ if (always_small(LHS) && RHS.isSmall()) {
+ a64::Utils::LogicalImm ignore;
+ if (a64::Utils::encodeLogicalImm(RHS.as<ArgSmall>().get(),
+ 64,
+ &ignore)) {
+ comment("skipped test for small operands since they are always "
+ "small");
+ auto lhs = load_source(LHS, ARG2);
+ auto dst = init_destination(Dst, ARG1);
+
+ /* TAG & TAG = TAG, so we don't need to tag it again. */
+ a.and_(dst.reg, lhs.reg, RHS.as<ArgSmall>().get());
+ flush_var(dst);
+ return;
+ }
+ }
+
auto [lhs, rhs] = load_sources(LHS, ARG2, RHS, ARG3);
auto dst = init_destination(Dst, ARG1);
- /* TAG & TAG = TAG, so we don't need to tag it again. */
- a.and_(ARG1, lhs.reg, rhs.reg);
-
if (always_small(LHS) && always_small(RHS)) {
comment("skipped test for small operands since they are always small");
+
+ /* TAG & TAG = TAG, so we don't need to tag it again. */
+ a.and_(dst.reg, lhs.reg, rhs.reg);
+ flush_var(dst);
} else {
Label next = a.newLabel();
- /* All other term types has at least one zero in the low 4
- * bits. Therefore, the result will be a small iff both operands
- * are small. */
+ /* TAG & TAG = TAG, so we don't need to tag it again. */
+ a.and_(ARG1, lhs.reg, rhs.reg);
+
ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
- a.and_(TMP1, ARG1, imm(_TAG_IMMED1_MASK));
- a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
- a.b_eq(next);
+ if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(RHS)) {
+ comment("simplified test for small operands since other types are "
+ "boxed");
+ emit_is_boxed(next, ARG1);
+ } else {
+ /* All other term types has at least one zero in the low 4
+ * bits. Therefore, the result will be a small iff both
+ * operands are small. */
+ a.and_(TMP1, ARG1, imm(_TAG_IMMED1_MASK));
+ a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
+ a.b_eq(next);
+ }
mov_var(ARG2, lhs);
mov_var(ARG3, rhs);
@@ -861,10 +953,9 @@ void BeamModuleAssembler::emit_i_band(const ArgLabel &Fail,
}
a.bind(next);
+ mov_var(dst, ARG1);
+ flush_var(dst);
}
-
- mov_var(dst, ARG1);
- flush_var(dst);
}
/*
@@ -886,53 +977,58 @@ void BeamModuleAssembler::emit_i_bor(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS,
const ArgRegister &Dst) {
+ if (always_small(LHS) && RHS.isSmall()) {
+ a64::Utils::LogicalImm ignore;
+ Uint64 rhs = RHS.as<ArgSmall>().get() & ~_TAG_IMMED1_SMALL;
+ if (a64::Utils::encodeLogicalImm(rhs, 64, &ignore)) {
+ comment("skipped test for small operands since they are always "
+ "small");
+ auto lhs = load_source(LHS, ARG2);
+ auto dst = init_destination(Dst, ARG1);
+
+ a.orr(dst.reg, lhs.reg, rhs);
+ flush_var(dst);
+ return;
+ }
+ }
+
auto [lhs, rhs] = load_sources(LHS, ARG2, RHS, ARG3);
auto dst = init_destination(Dst, ARG1);
- /* TAG | TAG = TAG, so we don't need to tag it again. */
- a.orr(ARG1, lhs.reg, rhs.reg);
-
if (always_small(LHS) && always_small(RHS)) {
comment("skipped test for small operands since they are always small");
+
+ /* TAG | TAG = TAG, so we don't need to tag it again. */
+ a.orr(dst.reg, lhs.reg, rhs.reg);
+ flush_var(dst);
} else {
- Label generic = a.newLabel(), next = a.newLabel();
- if (always_small(RHS)) {
- a.and_(TMP1, lhs.reg, imm(_TAG_IMMED1_MASK));
- } else if (always_small(LHS)) {
- a.and_(TMP1, rhs.reg, imm(_TAG_IMMED1_MASK));
- } else {
- ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
- a.and_(TMP1, lhs.reg, rhs.reg);
- a.and_(TMP1, TMP1, imm(_TAG_IMMED1_MASK));
- }
+ Label next = a.newLabel();
- a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
- a.b_eq(next);
+ /* TAG | TAG = TAG, so we don't need to tag it again. */
+ a.orr(ARG1, lhs.reg, rhs.reg);
- a.bind(generic);
- {
- mov_var(ARG2, lhs);
- mov_var(ARG3, rhs);
-
- if (Fail.get() != 0) {
- emit_enter_runtime(Live.get());
- a.mov(ARG1, c_p);
- runtime_call<3>(erts_bor);
- emit_leave_runtime(Live.get());
- emit_branch_if_not_value(ARG1,
- resolve_beam_label(Fail, dispUnknown));
- } else {
- emit_enter_runtime(Live.get());
- fragment_call(ga->get_i_bor_body_shared());
- emit_leave_runtime(Live.get());
- }
+ emit_are_both_small(LHS, lhs.reg, RHS, rhs.reg, next);
+
+ mov_var(ARG2, lhs);
+ mov_var(ARG3, rhs);
+
+ if (Fail.get() != 0) {
+ emit_enter_runtime(Live.get());
+ a.mov(ARG1, c_p);
+ runtime_call<3>(erts_bor);
+ emit_leave_runtime(Live.get());
+ emit_branch_if_not_value(ARG1,
+ resolve_beam_label(Fail, dispUnknown));
+ } else {
+ emit_enter_runtime(Live.get());
+ fragment_call(ga->get_i_bor_body_shared());
+ emit_leave_runtime(Live.get());
}
a.bind(next);
+ mov_var(dst, ARG1);
+ flush_var(dst);
}
-
- mov_var(dst, ARG1);
- flush_var(dst);
}
/*
@@ -955,9 +1051,9 @@ void BeamModuleAssembler::emit_i_bxor(const ArgLabel &Fail,
const ArgSource &RHS,
const ArgRegister &Dst) {
auto [lhs, rhs] = load_sources(LHS, ARG2, RHS, ARG3);
+ auto dst = init_destination(Dst, ARG1);
if (always_small(LHS) && always_small(RHS)) {
- auto dst = init_destination(Dst, ARG1);
comment("skipped test for small operands because they are always "
"small");
@@ -969,24 +1065,12 @@ void BeamModuleAssembler::emit_i_bxor(const ArgLabel &Fail,
}
Label next = a.newLabel();
- auto dst = init_destination(Dst, ARG1);
/* TAG ^ TAG = 0, so we'll need to tag it again. */
a.eor(ARG1, lhs.reg, rhs.reg);
a.orr(ARG1, ARG1, imm(_TAG_IMMED1_SMALL));
- if (always_small(RHS)) {
- a.and_(TMP1, lhs.reg, imm(_TAG_IMMED1_MASK));
- } else if (always_small(LHS)) {
- a.and_(TMP1, rhs.reg, imm(_TAG_IMMED1_MASK));
- } else {
- ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
- a.and_(TMP1, lhs.reg, rhs.reg);
- a.and_(TMP1, TMP1, imm(_TAG_IMMED1_MASK));
- }
-
- a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
- a.b_eq(next);
+ emit_are_both_small(LHS, lhs.reg, RHS, rhs.reg, next);
mov_var(ARG2, lhs);
mov_var(ARG3, rhs);
@@ -1085,7 +1169,7 @@ void BeamModuleAssembler::emit_i_bnot(const ArgLabel &Fail,
/* Invert everything except the tag so we don't have to tag it again. */
a.eor(ARG1, src.reg, imm(~_TAG_IMMED1_MASK));
- if (always_one_of(Src, BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ if (always_one_of<BeamTypeId::Number>(Src)) {
comment("simplified test for small operand since it is a number");
emit_is_boxed(next, Src, ARG1);
} else {
@@ -1142,8 +1226,7 @@ void BeamModuleAssembler::emit_i_bsr(const ArgLabel &Fail,
comment("skipped test for small left operand because it is "
"always small");
need_generic = false;
- } else if (always_one_of(LHS,
- BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ } else if (always_one_of<BeamTypeId::Number>(LHS)) {
comment("simplified test for small operand since it is a "
"number");
emit_is_not_boxed(generic, lhs.reg);
@@ -1222,18 +1305,26 @@ void BeamModuleAssembler::emit_i_bsl(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS,
const ArgRegister &Dst) {
- auto [lhs, rhs] = load_sources(LHS, ARG2, RHS, ARG3);
auto dst = init_destination(Dst, ARG1);
if (is_bsl_small(LHS, RHS)) {
comment("skipped tests because operands and result are always small");
- a.and_(TMP1, lhs.reg, imm(~_TAG_IMMED1_MASK));
- a.lsl(TMP1, TMP1, imm(RHS.as<ArgSmall>().getSigned()));
+ if (RHS.isSmall()) {
+ auto lhs = load_source(LHS, ARG2);
+ a.and_(TMP1, lhs.reg, imm(~_TAG_IMMED1_MASK));
+ a.lsl(TMP1, TMP1, imm(RHS.as<ArgSmall>().getSigned()));
+ } else {
+ auto [lhs, rhs] = load_sources(LHS, ARG2, RHS, ARG3);
+ a.and_(TMP1, lhs.reg, imm(~_TAG_IMMED1_MASK));
+ a.lsr(TMP2, rhs.reg, imm(_TAG_IMMED1_SIZE));
+ a.lsl(TMP1, TMP1, TMP2);
+ }
a.orr(dst.reg, TMP1, imm(_TAG_IMMED1_SMALL));
flush_var(dst);
return;
}
+ auto [lhs, rhs] = load_sources(LHS, ARG2, RHS, ARG3);
Label generic = a.newLabel(), next = a.newLabel();
bool inline_shift = true;
@@ -1277,8 +1368,7 @@ void BeamModuleAssembler::emit_i_bsl(const ArgLabel &Fail,
if (always_small(LHS)) {
comment("skipped test for small operand since it is always "
"small");
- } else if (always_one_of(LHS,
- BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ } else if (always_one_of<BeamTypeId::Number>(LHS)) {
comment("simplified test for small operand since it is a "
"number");
emit_is_not_boxed(generic, TMP1);
diff --git a/erts/emulator/beam/jit/arm/instr_bif.cpp b/erts/emulator/beam/jit/arm/instr_bif.cpp
index 43890fc9a5..d12ea50cef 100644
--- a/erts/emulator/beam/jit/arm/instr_bif.cpp
+++ b/erts/emulator/beam/jit/arm/instr_bif.cpp
@@ -30,6 +30,15 @@ extern "C"
#include "erl_msacc.h"
}
+void BeamModuleAssembler::ubif_comment(const ArgWord &Bif) {
+ if (logger.file()) {
+ ErtsCodeMFA *mfa = ubif2mfa((void *)Bif.get());
+ if (mfa) {
+ comment("UBIF: %T/%d", mfa->function, mfa->arity);
+ }
+ }
+}
+
/* ARG2 = argument vector, ARG4 (!) = bif function pointer
*
* Result is returned in ARG1 (will be THE_NON_VALUE if the BIF call failed). */
@@ -104,6 +113,7 @@ void BeamModuleAssembler::emit_i_bif1(const ArgSource &Src1,
a.str(src1.reg, getXRef(0));
+ ubif_comment(Bif);
emit_i_bif(Fail, Bif, Dst);
}
@@ -116,6 +126,7 @@ void BeamModuleAssembler::emit_i_bif2(const ArgSource &Src1,
a.stp(src1.reg, src2.reg, getXRef(0));
+ ubif_comment(Bif);
emit_i_bif(Fail, Bif, Dst);
}
@@ -131,6 +142,7 @@ void BeamModuleAssembler::emit_i_bif3(const ArgSource &Src1,
a.stp(src1.reg, src2.reg, getXRef(0));
a.str(src3.reg, getXRef(2));
+ ubif_comment(Bif);
emit_i_bif(Fail, Bif, Dst);
}
@@ -161,6 +173,7 @@ void BeamModuleAssembler::emit_nofail_bif1(const ArgSource &Src1,
a.str(src1.reg, getXRef(0));
+ ubif_comment(Bif);
mov_arg(ARG4, Bif);
fragment_call(ga->get_i_bif_guard_shared());
mov_arg(Dst, ARG1);
@@ -174,6 +187,7 @@ void BeamModuleAssembler::emit_nofail_bif2(const ArgSource &Src1,
a.stp(src1.reg, src2.reg, getXRef(0));
+ ubif_comment(Bif);
mov_arg(ARG4, Bif);
fragment_call(ga->get_i_bif_guard_shared());
mov_arg(Dst, ARG1);
@@ -564,6 +578,10 @@ void BeamModuleAssembler::emit_call_light_bif(const ArgWord &Bif,
mov_arg(ARG8, Bif);
a.adr(ARG3, entry);
+ if (logger.file()) {
+ BeamFile_ImportEntry *e = &beam->imports.entries[Exp.get()];
+ comment("BIF: %T:%T/%d", e->module, e->function, e->arity);
+ }
fragment_call(ga->get_call_light_bif_shared());
}
@@ -776,6 +794,10 @@ void BeamModuleAssembler::emit_call_bif_mfa(const ArgAtom &M,
a.adr(ARG3, current_label);
a.sub(ARG2, ARG3, imm(sizeof(ErtsCodeMFA)));
+ comment("HBIF: %T:%T/%d",
+ e->info.mfa.module,
+ e->info.mfa.function,
+ A.get());
a.mov(ARG4, imm(func));
a.b(resolve_fragment(ga->get_call_bif_shared(), disp128MB));
@@ -968,14 +990,14 @@ void BeamModuleAssembler::emit_i_load_nif() {
a.bind(entry);
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs>(2);
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs>(2);
a.mov(ARG1, c_p);
a.adr(ARG2, current_label);
load_x_reg_array(ARG3);
runtime_call<3>(beam_jit_load_nif);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs>(2);
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs>(2);
a.cmp(ARG1, imm(RET_NIF_yield));
a.b_eq(schedule);
diff --git a/erts/emulator/beam/jit/arm/instr_bs.cpp b/erts/emulator/beam/jit/arm/instr_bs.cpp
index 06873cd709..d7e8f70d83 100644
--- a/erts/emulator/beam/jit/arm/instr_bs.cpp
+++ b/erts/emulator/beam/jit/arm/instr_bs.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
*/
#include "beam_asm.hpp"
+#include <numeric>
extern "C"
{
@@ -29,8 +30,6 @@ extern "C"
/* Clobbers TMP1+TMP2
*
- * If max_size > 0, we jump to the fail label when Size > max_size
- *
* Returns -1 when the field check always fails, 1 if it may fail, and 0 if it
* never fails. */
int BeamModuleAssembler::emit_bs_get_field_size(const ArgSource &Size,
@@ -55,18 +54,40 @@ int BeamModuleAssembler::emit_bs_get_field_size(const ArgSource &Size,
return -1;
} else {
auto size_reg = load_source(Size, TMP2);
+ bool can_fail = true;
+
+ if (always_small(Size)) {
+ auto [min, max] = getClampedRange(Size);
+ can_fail =
+ !(0 <= min && (max >> (SMALL_BITS - ERL_UNIT_BITS)) == 0);
+ }
/* Negating the tag bits lets us guard against non-smalls, negative
* numbers, and overflow with a single `tst` instruction. */
ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
ASSERT(unit <= 1024);
- a.eor(out, size_reg.reg, imm(_TAG_IMMED1_SMALL));
- a.tst(out, imm(0xFFF0000000000000UL | _TAG_IMMED1_MASK));
+ if (!can_fail) {
+ comment("simplified segment size checks because "
+ "the types are known");
+ }
+
+ if (unit == 1 && !can_fail) {
+ a.lsr(out, size_reg.reg, imm(_TAG_IMMED1_SIZE));
+ } else {
+ a.eor(out, size_reg.reg, imm(_TAG_IMMED1_SMALL));
+ }
+
+ if (can_fail) {
+ a.tst(out, imm(0xFFF0000000000000UL | _TAG_IMMED1_MASK));
+ }
if (unit == 0) {
/* Silly but legal.*/
mov_imm(out, 0);
+ } else if (unit == 1 && !can_fail) {
+ /* The result is already in the out register. */
+ ;
} else if (Support::isPowerOf2(unit)) {
int trailing_bits = Support::ctz<Eterm>(unit);
@@ -88,9 +109,11 @@ int BeamModuleAssembler::emit_bs_get_field_size(const ArgSource &Size,
a.mul(out, out, TMP1);
}
- a.b_ne(fail);
+ if (can_fail) {
+ a.b_ne(fail);
+ }
- return 1;
+ return can_fail;
}
}
@@ -102,7 +125,7 @@ void BeamModuleAssembler::emit_i_bs_init_heap(const ArgWord &Size,
mov_arg(ARG5, Heap);
mov_arg(ARG6, Live);
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get());
a.mov(ARG1, c_p);
@@ -110,7 +133,7 @@ void BeamModuleAssembler::emit_i_bs_init_heap(const ArgWord &Size,
load_erl_bits_state(ARG3);
runtime_call<6>(beam_jit_bs_init);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get());
mov_arg(Dst, ARG1);
@@ -148,7 +171,7 @@ void BeamModuleAssembler::emit_i_bs_init_fail_heap(const ArgSource &Size,
mov_arg(ARG5, Heap);
mov_arg(ARG6, Live);
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get());
a.mov(ARG1, c_p);
@@ -156,7 +179,7 @@ void BeamModuleAssembler::emit_i_bs_init_fail_heap(const ArgSource &Size,
load_erl_bits_state(ARG3);
runtime_call<6>(beam_jit_bs_init);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get());
mov_arg(Dst, ARG1);
@@ -207,7 +230,7 @@ void BeamModuleAssembler::emit_i_bs_init_bits_heap(const ArgWord &NumBits,
mov_arg(ARG5, Alloc);
mov_arg(ARG6, Live);
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get());
a.mov(ARG1, c_p);
@@ -215,7 +238,7 @@ void BeamModuleAssembler::emit_i_bs_init_bits_heap(const ArgWord &NumBits,
load_erl_bits_state(ARG3);
runtime_call<6>(beam_jit_bs_init_bits);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get());
mov_arg(Dst, ARG1);
@@ -248,7 +271,7 @@ void BeamModuleAssembler::emit_i_bs_init_bits_fail_heap(
mov_arg(ARG5, Alloc);
mov_arg(ARG6, Live);
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get());
a.mov(ARG1, c_p);
@@ -256,7 +279,7 @@ void BeamModuleAssembler::emit_i_bs_init_bits_fail_heap(
load_erl_bits_state(ARG3);
runtime_call<6>(beam_jit_bs_init_bits);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get());
mov_arg(Dst, ARG1);
@@ -541,19 +564,18 @@ void BeamModuleAssembler::emit_i_bs_start_match3(const ArgRegister &Src,
a.bind(is_binary);
{
- /* Src is not guaranteed to be inside the live range, so we need to
- * stash it during GC. */
- emit_gc_test_preserve(ArgVal(ArgVal::Word, ERL_BIN_MATCHSTATE_SIZE(0)),
+ emit_gc_test_preserve(ArgWord(ERL_BIN_MATCHSTATE_SIZE(0)),
Live,
+ Src,
ARG2);
- emit_enter_runtime<Update::eStack | Update::eHeap>(Live.get());
+ emit_enter_runtime<Update::eHeapOnlyAlloc>(Live.get());
a.mov(ARG1, c_p);
/* ARG2 was set above */
runtime_call<2>(erts_bs_start_match_3);
- emit_leave_runtime<Update::eStack | Update::eHeap>(Live.get());
+ emit_leave_runtime<Update::eHeapOnlyAlloc>(Live.get());
a.add(ARG2, ARG1, imm(TAG_PRIMARY_BOXED));
}
@@ -585,9 +607,8 @@ void BeamModuleAssembler::emit_i_bs_match_string(const ArgRegister &Ctx,
a.and_(ARG4, TMP2, imm(7));
/* ARG3 = mb->base + (mb->offset >> 3) */
- a.lsr(TMP2, TMP2, imm(3));
a.ldur(TMP1, emit_boxed_val(ctx_reg.reg, base_offset));
- a.add(ARG3, TMP1, TMP2);
+ a.add(ARG3, TMP1, TMP2, arm::lsr(3));
}
emit_enter_runtime();
@@ -624,77 +645,89 @@ void BeamModuleAssembler::emit_i_bs_get_position(const ArgRegister &Ctx,
flush_var(dst_reg);
}
-void BeamModuleAssembler::emit_i_bs_get_fixed_integer(const ArgRegister &Ctx,
- const ArgLabel &Fail,
- const ArgWord &Live,
- const ArgWord &Flags,
- const ArgWord &Bits,
- const ArgRegister &Dst) {
- auto ctx = load_source(Ctx, TMP1);
- int flags, bits;
-
- flags = Flags.get();
- bits = Bits.get();
+void BeamModuleAssembler::emit_bs_get_integer2(const ArgLabel &Fail,
+ const ArgRegister &Ctx,
+ const ArgWord &Live,
+ const ArgSource &Sz,
+ const ArgWord &Unit,
+ const ArgWord &Flags,
+ const ArgRegister &Dst) {
+ Uint size;
+ Uint flags = Flags.get();
- if (bits >= SMALL_BITS) {
- emit_gc_test_preserve(ArgVal(ArgVal::Word, BIG_NEED_FOR_BITS(bits)),
- Live,
- ctx.reg);
+ if (flags & BSF_NATIVE) {
+ flags &= ~BSF_NATIVE;
+ flags |= BSF_LITTLE;
}
- lea(ARG4, emit_boxed_val(ctx.reg, offsetof(ErlBinMatchState, mb)));
-
- if (bits >= SMALL_BITS) {
- emit_enter_runtime<Update::eHeap>(Live.get());
+ if (Sz.isSmall() && Sz.as<ArgSmall>().getUnsigned() < 8 * sizeof(Uint) &&
+ (size = Sz.as<ArgSmall>().getUnsigned() * Unit.get()) <
+ 8 * sizeof(Uint)) {
+ /* Segment of a fixed size supported by bs_match. */
+ const ArgVal match[] = {ArgAtom(am_ensure_at_least),
+ ArgWord(size),
+ ArgWord(1),
+ ArgAtom(am_integer),
+ Live,
+ ArgWord(flags),
+ ArgWord(size),
+ ArgWord(1),
+ Dst};
+
+ const Span<ArgVal> args(match, sizeof(match) / sizeof(match[0]));
+ emit_i_bs_match(Fail, Ctx, args);
} else {
- emit_enter_runtime(Live.get());
- }
-
- a.mov(ARG1, c_p);
- a.mov(ARG2, bits);
- a.mov(ARG3, flags);
- /* ARG4 set above. */
- runtime_call<4>(erts_bs_get_integer_2);
-
- if (bits >= SMALL_BITS) {
- emit_leave_runtime<Update::eHeap>(Live.get());
- } else {
- emit_leave_runtime(Live.get());
- }
-
- emit_branch_if_not_value(ARG1, resolve_beam_label(Fail, dispUnknown));
- mov_arg(Dst, ARG1);
-}
-
-void BeamModuleAssembler::emit_i_bs_get_integer(const ArgRegister &Ctx,
- const ArgLabel &Fail,
- const ArgWord &Live,
- const ArgWord &FlagsAndUnit,
- const ArgSource &Sz,
- const ArgRegister &Dst) {
- Label fail;
- int unit;
-
- fail = resolve_beam_label(Fail, dispUnknown);
- unit = FlagsAndUnit.get() >> 3;
-
- if (emit_bs_get_field_size(Sz, unit, fail, ARG5) >= 0) {
- mov_arg(ARG3, Ctx);
- mov_arg(ARG4, FlagsAndUnit);
- mov_arg(ARG6, Live);
+ Label fail = resolve_beam_label(Fail, dispUnknown);
+ int unit = Unit.get();
+
+ if (emit_bs_get_field_size(Sz, unit, fail, ARG5) >= 0) {
+ /* This operation can be expensive if a bignum can be
+ * created because there can be a garbage collection. */
+ auto max = std::get<1>(getClampedRange(Sz));
+ bool potentially_expensive =
+ max >= SMALL_BITS || (max * Unit.get()) >= SMALL_BITS;
+
+ mov_arg(ARG3, Ctx);
+ mov_imm(ARG4, flags);
+ if (potentially_expensive) {
+ mov_arg(ARG6, Live);
+ } else {
+#ifdef DEBUG
+ /* Never actually used. */
+ mov_imm(ARG6, 1023);
+#endif
+ }
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
- Update::eReductions>(Live.get());
+ if (potentially_expensive) {
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
+ Update::eReductions>(Live.get());
+ } else {
+ comment("simplified entering runtime because result is always "
+ "small");
+ emit_enter_runtime(Live.get());
+ }
- a.mov(ARG1, c_p);
- load_x_reg_array(ARG2);
- runtime_call<6>(beam_jit_bs_get_integer);
+ a.mov(ARG1, c_p);
+ if (potentially_expensive) {
+ load_x_reg_array(ARG2);
+ } else {
+#ifdef DEBUG
+ /* Never actually used. */
+ mov_imm(ARG2, 0);
+#endif
+ }
+ runtime_call<6>(beam_jit_bs_get_integer);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
- Update::eReductions>(Live.get());
+ if (potentially_expensive) {
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
+ Update::eReductions>(Live.get());
+ } else {
+ emit_leave_runtime(Live.get());
+ }
- emit_branch_if_not_value(ARG1, fail);
- mov_arg(Dst, ARG1);
+ emit_branch_if_not_value(ARG1, fail);
+ mov_arg(Dst, ARG1);
+ }
}
}
@@ -738,11 +771,7 @@ void BeamModuleAssembler::emit_i_bs_get_binary_all2(const ArgRegister &Ctx,
mov_arg(ARG1, Ctx);
- /* Ctx is not guaranteed to be inside the live range, so we need to stash
- * it during GC. */
- emit_gc_test_preserve(ArgVal(ArgVal::Word, EXTRACT_SUB_BIN_HEAP_NEED),
- Live,
- ARG1);
+ emit_gc_test_preserve(ArgWord(EXTRACT_SUB_BIN_HEAP_NEED), Live, Ctx, ARG1);
/* Make field fetching slightly more compact by pre-loading the match
* buffer into the right argument slot for `erts_bs_get_binary_all_2`. */
@@ -770,13 +799,13 @@ void BeamModuleAssembler::emit_i_bs_get_binary_all2(const ArgRegister &Ctx,
}
}
- emit_enter_runtime<Update::eHeap>(Live.get());
+ emit_enter_runtime<Update::eHeapOnlyAlloc>(Live.get());
a.mov(ARG1, c_p);
/* ARG2 was set above. */
runtime_call<2>(erts_bs_get_binary_all_2);
- emit_leave_runtime<Update::eHeap>(Live.get());
+ emit_leave_runtime<Update::eHeapOnlyAlloc>(Live.get());
mov_arg(Dst, ARG1);
}
@@ -796,11 +825,11 @@ void BeamGlobalAssembler::emit_bs_get_tail_shared() {
a.sub(ARG5, TMP1, ARG4);
emit_enter_runtime_frame();
- emit_enter_runtime<Update::eHeap>();
+ emit_enter_runtime<Update::eHeapOnlyAlloc>();
runtime_call<5>(erts_extract_sub_binary);
- emit_leave_runtime<Update::eHeap>();
+ emit_leave_runtime<Update::eHeapOnlyAlloc>();
emit_leave_runtime_frame();
a.ret(a64::x30);
@@ -811,11 +840,7 @@ void BeamModuleAssembler::emit_bs_get_tail(const ArgRegister &Ctx,
const ArgWord &Live) {
mov_arg(ARG1, Ctx);
- /* Ctx is not guaranteed to be inside the live range, so we need to stash
- * it during GC. */
- emit_gc_test_preserve(ArgVal(ArgVal::Word, EXTRACT_SUB_BIN_HEAP_NEED),
- Live,
- ARG1);
+ emit_gc_test_preserve(ArgWord(EXTRACT_SUB_BIN_HEAP_NEED), Live, Ctx, ARG1);
fragment_call(ga->get_bs_get_tail_shared());
@@ -841,12 +866,34 @@ void BeamModuleAssembler::emit_bs_skip_bits(const ArgLabel &Fail,
}
void BeamModuleAssembler::emit_i_bs_skip_bits2(const ArgRegister &Ctx,
- const ArgRegister &Bits,
+ const ArgRegister &Size,
const ArgLabel &Fail,
const ArgWord &Unit) {
Label fail = resolve_beam_label(Fail, dispUnknown);
- if (emit_bs_get_field_size(Bits, Unit.get(), fail, ARG1) >= 0) {
+ bool can_fail = true;
+
+ if (always_small(Size)) {
+ auto [min, max] = getClampedRange(Size);
+ can_fail = !(0 <= min && (max >> (SMALL_BITS - ERL_UNIT_BITS)) == 0);
+ }
+
+ if (!can_fail && Unit.get() == 1) {
+ comment("simplified skipping because the types are known");
+
+ const int position_offset = offsetof(ErlBinMatchState, mb.offset);
+ const int size_offset = offsetof(ErlBinMatchState, mb.size);
+ auto [ctx, size] = load_sources(Ctx, TMP1, Size, TMP2);
+
+ a.ldur(TMP3, emit_boxed_val(ctx.reg, position_offset));
+ a.ldur(TMP4, emit_boxed_val(ctx.reg, size_offset));
+
+ a.add(TMP3, TMP3, size.reg, arm::lsr(_TAG_IMMED1_SIZE));
+ a.cmp(TMP3, TMP4);
+ a.b_hi(resolve_beam_label(Fail, disp1MB));
+
+ a.stur(TMP3, emit_boxed_val(ctx.reg, position_offset));
+ } else if (emit_bs_get_field_size(Size, Unit.get(), fail, ARG1) >= 0) {
emit_bs_skip_bits(Fail, Ctx);
}
}
@@ -875,22 +922,21 @@ void BeamModuleAssembler::emit_i_bs_get_binary2(const ArgRegister &Ctx,
mov_arg(ARG4, Ctx);
- /* Ctx is not guaranteed to be inside the live range, so we need to
- * stash it during GC. */
- emit_gc_test_preserve(ArgVal(ArgVal::Word, EXTRACT_SUB_BIN_HEAP_NEED),
+ emit_gc_test_preserve(ArgWord(EXTRACT_SUB_BIN_HEAP_NEED),
Live,
+ Ctx,
ARG4);
lea(ARG4, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb)));
- emit_enter_runtime<Update::eHeap>(Live.get());
+ emit_enter_runtime<Update::eHeapOnlyAlloc>(Live.get());
a.mov(ARG1, c_p);
a.ldr(ARG2, TMP_MEM1q);
mov_imm(ARG3, Flags.get());
runtime_call<4>(erts_bs_get_binary_2);
- emit_leave_runtime<Update::eHeap>(Live.get());
+ emit_leave_runtime<Update::eHeapOnlyAlloc>(Live.get());
emit_branch_if_not_value(ARG1, fail);
@@ -912,20 +958,18 @@ void BeamModuleAssembler::emit_i_bs_get_float2(const ArgRegister &Ctx,
mov_arg(ARG4, Ctx);
- /* Ctx is not guaranteed to be inside the live range, so we need to stash
- * it during GC. */
- emit_gc_test_preserve(ArgWord(FLOAT_SIZE_OBJECT), Live, ARG4);
+ emit_gc_test_preserve(ArgWord(FLOAT_SIZE_OBJECT), Live, Ctx, ARG4);
if (emit_bs_get_field_size(Sz, unit, fail, ARG2) >= 0) {
lea(ARG4, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb)));
- emit_enter_runtime<Update::eHeap>(Live.get());
+ emit_enter_runtime<Update::eHeapOnlyAlloc>(Live.get());
a.mov(ARG1, c_p);
mov_imm(ARG3, Flags.get());
runtime_call<4>(erts_bs_get_float_2);
- emit_leave_runtime<Update::eHeap>(Live.get());
+ emit_leave_runtime<Update::eHeapOnlyAlloc>(Live.get());
emit_branch_if_not_value(ARG1, fail);
@@ -983,18 +1027,283 @@ void BeamModuleAssembler::emit_i_bs_put_utf8(const ArgLabel &Fail,
}
}
+/*
+ * ARG1 = pointer to match state
+ * ARG2 = number of bits left in binary (< 32)
+ * ARG3 = position in binary in bits
+ * ARG4 = base pointer to binary data
+ *
+ * See the comment for emit_bs_get_utf8_shared() for details about the
+ * return value.
+ */
+void BeamGlobalAssembler::emit_bs_get_utf8_short_shared() {
+ const int position_offset = offsetof(ErlBinMatchBuffer, offset);
+
+ const arm::Gp match_state = ARG1;
+ const arm::Gp bitdata = ARG2;
+ const arm::Gp bin_position = ARG3;
+ const arm::Gp bin_base = ARG4;
+
+ Label two = a.newLabel();
+ Label three_or_more = a.newLabel();
+ Label four = a.newLabel();
+ Label read_done = a.newLabel();
+ Label ascii = a.newLabel();
+ Label error = a.newLabel();
+
+ /* Calculate the number of bytes remaining in the binary and error
+ * out if less than one. */
+ a.lsr(bitdata, bitdata, imm(3));
+ a.cbz(bitdata, error);
+
+ /* Calculate a byte mask so we can zero out trailing garbage. */
+ a.neg(TMP5, bitdata, arm::lsl(3));
+ mov_imm(TMP4, -1);
+ a.lsl(TMP4, TMP4, TMP5);
+
+ /* If the position in the binary is not byte-aligned, we'll need
+ * to read one more byte. */
+ a.ands(TMP1, bin_position, imm(7));
+ a.cinc(bitdata, bitdata, imm(arm::CondCode::kNE));
+
+ /* Set up pointer to the first byte to read. */
+ a.add(TMP2, bin_base, bin_position, arm::lsr(3));
+
+ a.cmp(bitdata, 2);
+ a.b_eq(two);
+ a.b_hi(three_or_more);
+
+ /* Read one byte (always byte-aligned). */
+ a.ldrb(bitdata.w(), arm::Mem(TMP2));
+ a.b(read_done);
+
+ /* Read two bytes. */
+ a.bind(two);
+ a.ldrh(bitdata.w(), arm::Mem(TMP2));
+ a.b(read_done);
+
+ a.bind(three_or_more);
+ a.cmp(bitdata, 3);
+ a.b_ne(four);
+
+ /* Read three bytes. */
+ a.ldrh(bitdata.w(), arm::Mem(TMP2));
+ a.ldrb(TMP3.w(), arm::Mem(TMP2, 2));
+ a.orr(bitdata, bitdata, TMP3, arm::lsl(16));
+ a.b(read_done);
+
+ /* Read four bytes (always unaligned). */
+ a.bind(four);
+ a.ldr(bitdata.w(), arm::Mem(TMP2));
+
+ /* Handle the bytes read. */
+ a.bind(read_done);
+ a.rev64(bitdata, bitdata);
+ a.lsl(bitdata, bitdata, TMP1);
+ a.and_(bitdata, bitdata, TMP4);
+ a.tbz(bitdata, imm(63), ascii);
+ a.b(labels[bs_get_utf8_shared]);
+
+ /* Handle plain old ASCII (code point < 128). */
+ a.bind(ascii);
+ a.add(bin_position, bin_position, imm(8));
+ a.str(bin_position, arm::Mem(match_state, position_offset));
+ a.mov(ARG1, imm(_TAG_IMMED1_SMALL));
+ a.orr(ARG1, ARG1, bitdata, arm::lsr(56 - _TAG_IMMED1_SIZE));
+ a.ret(a64::x30);
+
+ /* Signal error. */
+ a.bind(error);
+ mov_imm(ARG1, 0);
+ a.ret(a64::x30);
+}
+
+/*
+ * ARG1 = pointer to match state
+ * ARG2 = 4 bytes read from the binary in big-endian order
+ * ARG3 = position in binary in bits
+ *
+ * On successful return, the extracted code point is a term tagged
+ * small in ARG1 and the position in the match state has been updated. On
+ * failure, ARG1 contains an invalid term where the tags bits are zero.
+ */
+void BeamGlobalAssembler::emit_bs_get_utf8_shared() {
+ const int position_offset = offsetof(ErlBinMatchBuffer, offset);
+
+ const arm::Gp match_state = ARG1;
+ const arm::Gp bitdata = ARG2;
+ const arm::Gp bin_position = ARG3;
+
+ const arm::Gp byte_count = ARG4;
+
+ const arm::Gp shift = TMP4;
+ const arm::Gp control_mask = TMP5;
+ const arm::Gp error_mask = TMP6;
+
+ /* UTF-8 has the following layout, where 'x' are data bits:
+ *
+ * 1 byte: 0xxxxxxx (not handled by this path)
+ * 2 bytes: 110xxxxx, 10xxxxxx
+ * 3 bytes: 1110xxxx, 10xxxxxx 10xxxxxx
+ * 4 bytes: 11110xxx, 10xxxxxx 10xxxxxx 10xxxxxx
+ *
+ * Note that the number of leading bits is equal to the number of bytes,
+ * which makes it very easy to create masks for extraction and error
+ * checking. */
+
+ /* Calculate the number of bytes. */
+ a.cls(byte_count, bitdata);
+ a.add(byte_count, byte_count, imm(1));
+
+ /* Get rid of the prefix bits. */
+ a.lsl(bitdata, bitdata, byte_count);
+ a.lsr(bitdata, bitdata, byte_count);
+
+ /* Calculate the bit shift now before we start to corrupt the
+ * byte_count. */
+ mov_imm(shift, 64);
+ a.sub(shift, shift, byte_count, arm::lsl(3));
+
+ /* Shift down the value to the least significant part of the word. */
+ a.lsr(bitdata, bitdata, shift);
+
+ /* Matches the '10xxxxxx' components, leaving the header byte alone. */
+ mov_imm(error_mask, 0x00808080ull << 32);
+ a.lsr(error_mask, error_mask, shift);
+
+ /* Construct the control mask '0x00C0C0C0' (already shifted). */
+ a.orr(control_mask, error_mask, error_mask, arm::lsr(1));
+
+ /* Assert that the header bits of each '10xxxxxx' component are correct,
+ * signaling errors by trashing the byte count with an illegal
+ * value (0). */
+ a.and_(TMP3, bitdata, control_mask);
+ a.cmp(TMP3, error_mask);
+
+ a.ubfx(TMP1, bitdata, imm(8), imm(6));
+ a.ubfx(TMP2, bitdata, imm(16), imm(6));
+ a.ubfx(TMP3, bitdata, imm(24), imm(3));
+ a.ubfx(bitdata, bitdata, imm(0), imm(6));
+
+ a.orr(bitdata, bitdata, TMP1, arm::lsl(6));
+ a.orr(bitdata, bitdata, TMP2, arm::lsl(12));
+ a.orr(bitdata, bitdata, TMP3, arm::lsl(18));
+
+ /* Check for too large code point. */
+ mov_imm(TMP1, 0x10FFFF);
+ a.ccmp(bitdata, TMP1, imm(NZCV::kCF), arm::CondCode::kEQ);
+
+ /* Check for the illegal range 16#D800 - 16#DFFF. */
+ a.lsr(TMP1, bitdata, imm(11));
+ a.ccmp(TMP1, imm(0xD800 >> 11), imm(NZCV::kZF), arm::CondCode::kLS);
+ a.csel(byte_count, byte_count, ZERO, imm(arm::CondCode::kNE));
+
+ /* Test for overlong UTF-8 sequence. That can be done by testing
+ * that the bits marked y below are all zero.
+ *
+ * 1 byte: 0xxxxxxx (not handled by this path)
+ * 2 bytes: 110yyyyx, 10xxxxxx
+ * 3 bytes: 1110yyyy, 10yxxxxx 10xxxxxx
+ * 4 bytes: 11110yyy, 10yyxxxx 10xxxxxx 10xxxxxx
+ *
+ * 1 byte: xx'xxxxx
+ * 2 bytes: y'yyyxx'xxxxx
+ * 3 bytes: y'yyyyx'xxxxx'xxxxx
+ * 4 bytes: y'yyyyx'xxxxx'xxxxx'xxxxx
+ *
+ * The y bits can be isolated by shifting down by the number of bits
+ * shown in this table:
+ *
+ * 2: 7 (byte_count * 4 - 1)
+ * 3: 11 (byte_count * 4 - 1)
+ * 4: 16 (byte_count * 4)
+ */
+
+ /* Calculate number of bits to shift. */
+ a.lsl(TMP1, byte_count, imm(2));
+ a.cmp(byte_count, imm(4));
+ a.csetm(TMP2, imm(arm::CondCode::kNE));
+ a.add(TMP1, TMP1, TMP2);
+
+ /* Pre-fill the tag bits so that we can clear them on error. */
+ mov_imm(TMP2, _TAG_IMMED1_SMALL);
+
+ /* Now isolate the y bits and compare to zero. This check will
+ * be used in a CCMP further down. */
+ a.lsr(TMP1, bitdata, TMP1);
+ a.cmp(TMP1, 0);
+
+ /* Byte count must be 2, 3, or 4. */
+ a.sub(TMP1, byte_count, imm(2));
+ a.ccmp(TMP1, imm(2), imm(NZCV::kCF), imm(arm::CondCode::kNE));
+
+ /* If we have failed, we set byte_count to zero to ensure that the
+ * position update nops, and set the pre-tagged result to zero so
+ * that we can check for error in module code by testing the tag
+ * bits. */
+ a.csel(byte_count, byte_count, ZERO, imm(arm::CondCode::kLS));
+ a.csel(TMP2, TMP2, ZERO, imm(arm::CondCode::kLS));
+
+ a.add(bin_position, bin_position, byte_count, arm::lsl(3));
+ a.str(bin_position, arm::Mem(match_state, position_offset));
+ a.orr(ARG1, TMP2, bitdata, arm::lsl(_TAG_IMMED1_SIZE));
+
+ a.ret(a64::x30);
+}
+
void BeamModuleAssembler::emit_bs_get_utf8(const ArgRegister &Ctx,
const ArgLabel &Fail) {
- mov_arg(ARG1, Ctx);
- lea(ARG1, emit_boxed_val(ARG1, offsetof(ErlBinMatchState, mb)));
+ const int base_offset = offsetof(ErlBinMatchBuffer, base);
+ const int position_offset = offsetof(ErlBinMatchBuffer, offset);
- emit_enter_runtime();
+ const arm::Gp match_state = ARG1;
+ const arm::Gp bitdata = ARG2;
+ const arm::Gp bin_position = ARG3;
+ const arm::Gp bin_base = ARG4;
+ const arm::Gp bin_size = ARG5;
- runtime_call<1>(erts_bs_get_utf8);
+ auto ctx = load_source(Ctx, ARG6);
- emit_leave_runtime();
+ Label non_ascii = a.newLabel();
+ Label fallback = a.newLabel();
+ Label check = a.newLabel();
+ Label done = a.newLabel();
- emit_branch_if_not_value(ARG1, resolve_beam_label(Fail, dispUnknown));
+ lea(match_state, emit_boxed_val(ctx.reg, offsetof(ErlBinMatchState, mb)));
+ ERTS_CT_ASSERT_FIELD_PAIR(ErlBinMatchBuffer, offset, size);
+ a.ldp(bin_position, bin_size, arm::Mem(ARG1, position_offset));
+ a.ldr(bin_base, arm::Mem(ARG1, base_offset));
+ a.sub(bitdata, bin_size, bin_position);
+ a.cmp(bitdata, imm(32));
+ a.b_lo(fallback);
+
+ emit_read_bits(32, bin_base, bin_position, bitdata);
+ a.tbnz(bitdata, imm(63), non_ascii);
+
+ /* Handle plain old ASCII (code point < 128). */
+ a.add(bin_position, bin_position, imm(8));
+ a.str(bin_position, arm::Mem(ARG1, position_offset));
+ a.mov(ARG1, imm(_TAG_IMMED1_SMALL));
+ a.orr(ARG1, ARG1, bitdata, arm::lsr(56 - _TAG_IMMED1_SIZE));
+ a.b(done);
+
+ /* Handle code point >= 128. */
+ a.bind(non_ascii);
+ fragment_call(ga->get_bs_get_utf8_shared());
+ a.b(check);
+
+ /*
+ * Handle the case that there are not 4 bytes available in the binary.
+ */
+
+ a.bind(fallback);
+ fragment_call(ga->get_bs_get_utf8_short_shared());
+
+ a.bind(check);
+ ERTS_CT_ASSERT((_TAG_IMMED1_SMALL & 1) != 0);
+ a.tbz(ARG1, imm(0), resolve_beam_label(Fail, disp32K));
+
+ a.bind(done);
}
void BeamModuleAssembler::emit_i_bs_get_utf8(const ArgRegister &Ctx,
@@ -1291,14 +1600,14 @@ void BeamModuleAssembler::emit_i_bs_append(const ArgLabel &Fail,
mov_arg(ArgXRegister(Live.get()), Bin);
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get() + 1);
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<6>(erts_bs_append);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get() + 1);
if (Fail.get() != 0) {
@@ -1355,11 +1664,11 @@ void BeamModuleAssembler::emit_bs_init_writable() {
/* We have an implicit liveness of 0, so we don't need to stash X
* registers. */
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>(0);
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>(0);
runtime_call<2>(erts_bs_init_writable);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>(0);
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>(0);
a.mov(XREG0, ARG1);
}
@@ -1367,7 +1676,7 @@ void BeamModuleAssembler::emit_bs_init_writable() {
void BeamGlobalAssembler::emit_bs_create_bin_error_shared() {
a.mov(XREG0, a64::x30);
- emit_enter_runtime<Update::eStack | Update::eHeap>(0);
+ emit_enter_runtime<Update::eHeapAlloc>(0);
/* ARG3 is already set by the caller */
a.mov(ARG2, ARG4);
@@ -1375,7 +1684,7 @@ void BeamGlobalAssembler::emit_bs_create_bin_error_shared() {
a.mov(ARG1, c_p);
runtime_call<4>(beam_jit_bs_construct_fail_info);
- emit_leave_runtime<Update::eStack | Update::eHeap>(0);
+ emit_leave_runtime<Update::eHeapAlloc>(0);
a.mov(ARG4, ZERO);
a.mov(ARG2, XREG0);
@@ -1429,10 +1738,49 @@ void BeamGlobalAssembler::emit_bs_bit_size_shared() {
a.ret(a64::x30);
}
+/*
+ * ARG1 = tagged bignum term
+ */
+void BeamGlobalAssembler::emit_get_sint64_shared() {
+ Label success = a.newLabel();
+ Label fail = a.newLabel();
+
+ emit_is_boxed(fail, ARG1);
+ arm::Gp boxed_ptr = emit_ptr_val(TMP3, ARG1);
+ a.ldr(TMP1, emit_boxed_val(boxed_ptr));
+ a.ldr(TMP2, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ a.and_(TMP1, TMP1, imm(_TAG_HEADER_MASK));
+ a.cmp(TMP1, imm(POS_BIG_SUBTAG));
+ a.b_eq(success);
+
+ a.cmp(TMP1, imm(NEG_BIG_SUBTAG));
+ a.b_ne(fail);
+
+ a.neg(TMP2, TMP2);
+
+ a.bind(success);
+ {
+ a.mov(ARG1, TMP2);
+ /* Clear Z flag.
+ *
+ * TMP1 is known to be POS_BIG_SUBTAG or NEG_BIG_SUBTAG at this point.
+ */
+ ERTS_CT_ASSERT(POS_BIG_SUBTAG != 0 && NEG_BIG_SUBTAG != 0);
+ a.tst(TMP1, TMP1);
+ a.ret(a64::x30);
+ }
+
+ a.bind(fail);
+ {
+ a.tst(ZERO, ZERO);
+ a.ret(a64::x30);
+ }
+}
+
struct BscSegment {
BscSegment()
: type(am_false), unit(1), flags(0), src(ArgNil()), size(ArgNil()),
- error_info(0), effectiveSize(-1) {
+ error_info(0), effectiveSize(-1), action(action::DIRECT) {
}
Eterm type;
@@ -1443,19 +1791,443 @@ struct BscSegment {
Uint error_info;
Sint effectiveSize;
+
+ /* Here are sub actions for storing integer segments.
+ *
+ * We use the ACCUMULATE_FIRST and ACCUMULATE actions to shift the
+ * values of segments with known, small sizes (no more than 64 bits)
+ * into an accumulator register.
+ *
+ * When no more segments can be accumulated, the STORE action is
+ * used to store the value of the accumulator into the binary.
+ *
+ * The DIRECT action is used when it is not possible to use the
+ * accumulator (for unknown or too large sizes).
+ */
+ enum class action { DIRECT, ACCUMULATE_FIRST, ACCUMULATE, STORE } action;
};
+static std::vector<BscSegment> bs_combine_segments(
+ const std::vector<BscSegment> segments) {
+ std::vector<BscSegment> segs;
+
+ for (auto seg : segments) {
+ switch (seg.type) {
+ case am_integer: {
+ if (!(0 < seg.effectiveSize && seg.effectiveSize <= 64)) {
+ /* Unknown or too large size. Handle using the default
+ * DIRECT action. */
+ segs.push_back(seg);
+ continue;
+ }
+
+ if (seg.flags & BSF_LITTLE || segs.size() == 0 ||
+ segs.back().action == BscSegment::action::DIRECT) {
+ /* There are no previous compatible ACCUMULATE / STORE
+ * actions. Create the first ones. */
+ seg.action = BscSegment::action::ACCUMULATE_FIRST;
+ segs.push_back(seg);
+ seg.action = BscSegment::action::STORE;
+ segs.push_back(seg);
+ continue;
+ }
+
+ auto prev = segs.back();
+ if (prev.flags & BSF_LITTLE) {
+ /* Little-endian segments cannot be combined with other
+ * segments. Create new ACCUMULATE_FIRST / STORE actions. */
+ seg.action = BscSegment::action::ACCUMULATE_FIRST;
+ segs.push_back(seg);
+ seg.action = BscSegment::action::STORE;
+ segs.push_back(seg);
+ continue;
+ }
+
+ /* The current segment is compatible with the previous
+ * segment. Try combining them. */
+ if (prev.effectiveSize + seg.effectiveSize <= 64) {
+ /* The combined values of the segments fit in the
+ * accumulator. Insert an ACCUMULATE action for the
+ * current segment before the pre-existing STORE
+ * action. */
+ segs.pop_back();
+ prev.effectiveSize += seg.effectiveSize;
+ seg.action = BscSegment::action::ACCUMULATE;
+ segs.push_back(seg);
+ segs.push_back(prev);
+ } else {
+ /* The size exceeds 64 bits. Can't combine. */
+ seg.action = BscSegment::action::ACCUMULATE_FIRST;
+ segs.push_back(seg);
+ seg.action = BscSegment::action::STORE;
+ segs.push_back(seg);
+ }
+ break;
+ }
+ default:
+ segs.push_back(seg);
+ break;
+ }
+ }
+ return segs;
+}
+
+/*
+ * In:
+ * bin_offset = register to store the bit offset into the binary
+ * bit_offset = current bit offset into binary, or -1 if unknown
+ * size = size of segment to be constructed
+ * (ignored if size_reg is valid register)
+ * size_reg = if a valid register, it contains the size of
+ * the segment to be constructed
+ *
+ * Out:
+ * bin_offset register = if bit_offset is not byte aligned, the bit
+ * offset into the binary
+ * TMP1 = pointer to the current byte in the binary
+ *
+ * Preserves all other ARG* registers.
+ */
+void BeamModuleAssembler::update_bin_state(arm::Gp bin_offset,
+ Sint bit_offset,
+ Sint size,
+ arm::Gp size_reg) {
+ int cur_bin_offset = offsetof(ErtsSchedulerRegisters,
+ aux_regs.d.erl_bits_state.erts_current_bin_);
+ arm::Mem mem_bin_base = arm::Mem(scheduler_registers, cur_bin_offset);
+ arm::Mem mem_bin_offset =
+ arm::Mem(scheduler_registers, cur_bin_offset + sizeof(Eterm));
+
+ if (bit_offset % 8 != 0) {
+ /* The bit offset is unknown or not byte-aligned. */
+ ERTS_CT_ASSERT_FIELD_PAIR(struct erl_bits_state,
+ erts_current_bin_,
+ erts_bin_offset_);
+ a.ldp(TMP2, bin_offset, mem_bin_base);
+
+ if (size_reg.isValid()) {
+ a.add(TMP1, bin_offset, size_reg);
+ } else {
+ add(TMP1, bin_offset, size);
+ }
+ a.str(TMP1, mem_bin_offset);
+
+ a.add(TMP1, TMP2, bin_offset, arm::lsr(3));
+ } else {
+ comment("optimized updating of binary construction state");
+ ASSERT(size >= 0 || size_reg.isValid());
+ ASSERT(bit_offset % 8 == 0);
+ a.ldr(TMP1, mem_bin_base);
+ if (size_reg.isValid()) {
+ if (bit_offset == 0) {
+ a.str(size_reg, mem_bin_offset);
+ } else {
+ add(TMP2, size_reg, bit_offset);
+ a.str(TMP2, mem_bin_offset);
+ }
+ } else {
+ mov_imm(TMP2, bit_offset + size);
+ a.str(TMP2, mem_bin_offset);
+ }
+ if (bit_offset != 0) {
+ add(TMP1, TMP1, bit_offset >> 3);
+ }
+ }
+}
+
+/*
+ * The size of the segment is assumed to be in ARG3.
+ */
+void BeamModuleAssembler::set_zero(Sint effectiveSize) {
+ Label store_units = a.newLabel();
+ Label less_than_a_store_unit = a.newLabel();
+ Sint store_unit = 1;
+
+ update_bin_state(ARG2, -1, -1, ARG3);
+
+ if (effectiveSize >= 256) {
+ /* Store four 64-bit words machine words when the size is
+ * known and at least 256 bits. */
+ store_unit = 4;
+ a.movi(a64::d31, 0);
+ } else if (effectiveSize >= 128) {
+ /* Store two 64-bit words machine words when the size is
+ * known and at least 128 bits. */
+ store_unit = 2;
+ }
+
+ if (effectiveSize < Sint(store_unit * 8 * sizeof(Eterm))) {
+ /* The size is either not known or smaller than a word. */
+ a.cmp(ARG3, imm(store_unit * 8 * sizeof(Eterm)));
+ a.b_lt(less_than_a_store_unit);
+ }
+
+ a.bind(store_units);
+ if (store_unit == 4) {
+ a.stp(a64::q31, a64::q31, arm::Mem(TMP1).post(sizeof(Eterm[4])));
+ } else if (store_unit == 2) {
+ a.stp(ZERO, ZERO, arm::Mem(TMP1).post(sizeof(Eterm[2])));
+ } else {
+ a.str(ZERO, arm::Mem(TMP1).post(sizeof(Eterm)));
+ }
+ a.sub(ARG3, ARG3, imm(store_unit * 8 * sizeof(Eterm)));
+
+ a.cmp(ARG3, imm(store_unit * 8 * sizeof(Eterm)));
+ a.b_ge(store_units);
+
+ a.bind(less_than_a_store_unit);
+ if (effectiveSize < 0) {
+ /* Unknown size. */
+ Label byte_loop = a.newLabel();
+ Label done = a.newLabel();
+
+ ASSERT(store_unit = 1);
+
+ a.cbz(ARG3, done);
+
+ a.bind(byte_loop);
+ a.strb(ZERO.w(), arm::Mem(TMP1).post(1));
+ a.subs(ARG3, ARG3, imm(8));
+ a.b_gt(byte_loop);
+
+ a.bind(done);
+ } else if (effectiveSize % (store_unit * 8 * sizeof(Eterm)) != 0) {
+ /* The size is known, and we know that there are less than
+ * 256 bits to initialize. */
+ if (store_unit == 4 && (effectiveSize & 255) >= 128) {
+ a.stp(ZERO, ZERO, arm::Mem(TMP1).post(16));
+ }
+
+ if ((effectiveSize & 127) >= 64) {
+ a.str(ZERO, arm::Mem(TMP1).post(8));
+ }
+
+ if ((effectiveSize & 63) >= 32) {
+ a.str(ZERO.w(), arm::Mem(TMP1).post(4));
+ }
+
+ if ((effectiveSize & 31) >= 16) {
+ a.strh(ZERO.w(), arm::Mem(TMP1).post(2));
+ }
+
+ if ((effectiveSize & 15) >= 8) {
+ a.strb(ZERO.w(), arm::Mem(TMP1).post(1));
+ }
+
+ if ((effectiveSize & 7) > 0) {
+ a.strb(ZERO.w(), arm::Mem(TMP1));
+ }
+ }
+}
+
+/*
+ * In:
+ *
+ * ARG1 = valid unicode code point (=> 0x80) to encode
+ *
+ * Out:
+ *
+ * ARG1 = the code point encoded in UTF-8.
+ * ARG4 = number of bits of result (16, 24, or 32)
+ *
+ * Preserves other ARG* registers, clobbers TMP* registers
+ */
+void BeamGlobalAssembler::emit_construct_utf8_shared() {
+ Label more_than_two_bytes = a.newLabel();
+ Label four_bytes = a.newLabel();
+ const arm::Gp value = ARG1;
+ const arm::Gp num_bits = ARG4;
+
+ a.cmp(value, imm(0x800));
+ a.b_hs(more_than_two_bytes);
+
+ /* Encode Unicode code point in two bytes. */
+ a.ubfiz(TMP1, value, imm(8), imm(6));
+ mov_imm(TMP2, 0x80c0);
+ a.orr(TMP1, TMP1, value, arm::lsr(6));
+ mov_imm(num_bits, 16);
+ a.orr(value, TMP1, TMP2);
+ a.ret(a64::x30);
+
+ /* Test whether the value should be encoded in four bytes. */
+ a.bind(more_than_two_bytes);
+ a.lsr(TMP1, value, imm(16));
+ a.cbnz(TMP1, four_bytes);
+
+ /* Encode Unicode code point in three bytes. */
+ a.lsl(TMP1, value, imm(2));
+ a.ubfiz(TMP2, value, imm(16), imm(6));
+ a.and_(TMP1, TMP1, imm(0x3f00));
+ mov_imm(num_bits, 24);
+ a.orr(TMP1, TMP1, value, arm::lsr(12));
+ a.orr(TMP1, TMP1, TMP2);
+ mov_imm(TMP2, 0x8080e0);
+ a.orr(value, TMP1, TMP2);
+ a.ret(a64::x30);
+
+ /* Encode Unicode code point in four bytes. */
+ a.bind(four_bytes);
+ a.lsl(TMP1, value, imm(10));
+ a.lsr(TMP2, value, imm(4));
+ a.and_(TMP1, TMP1, imm(0x3f0000));
+ a.and_(TMP2, TMP2, imm(0x3f00));
+ a.bfxil(TMP1, value, imm(18), imm(14));
+ mov_imm(num_bits, 32);
+ a.bfi(TMP1, value, imm(24), imm(6));
+ a.orr(TMP1, TMP1, TMP2);
+ mov_imm(TMP2, 0x808080f0);
+ a.orr(value, TMP1, TMP2);
+ a.ret(a64::x30);
+}
+
+void BeamModuleAssembler::emit_construct_utf8(const ArgVal &Src,
+ Sint bit_offset,
+ bool is_byte_aligned) {
+ Label prepare_store = a.newLabel();
+ Label store = a.newLabel();
+ Label next = a.newLabel();
+
+ comment("construct utf8 segment");
+ auto src = load_source(Src, ARG1);
+
+ a.lsr(ARG1, src.reg, imm(_TAG_IMMED1_SIZE));
+ mov_imm(ARG4, 8);
+ a.cmp(ARG1, imm(0x80));
+ a.b_lo(prepare_store);
+
+ fragment_call(ga->get_construct_utf8_shared());
+
+ a.bind(prepare_store);
+ arm::Gp bin_offset = ARG3;
+ update_bin_state(bin_offset, bit_offset, -1, ARG4);
+
+ if (!is_byte_aligned) {
+ /* Not known to be byte-aligned. Must test alignment. */
+ a.ands(TMP2, bin_offset, imm(7));
+ a.b_eq(store);
+
+ /* We must combine the last partial byte with the UTF-8
+ * encoded code point. */
+ a.ldrb(TMP5.w(), arm::Mem(TMP1));
+
+ a.rev64(TMP4, ARG1);
+ a.lsr(TMP4, TMP4, TMP2);
+ a.rev64(TMP4, TMP4);
+
+ a.lsl(TMP5, TMP5, TMP2);
+ a.and_(TMP5, TMP5, imm(~0xff));
+ a.lsr(TMP5, TMP5, TMP2);
+
+ a.orr(ARG1, TMP4, TMP5);
+
+ a.add(ARG4, ARG4, imm(8));
+ }
+
+ a.bind(store);
+ if (bit_offset % (4 * 8) == 0) {
+ /* This segment is aligned on a 4-byte boundary. This implies
+ * that a 4-byte write will be inside the allocated binary. */
+ a.str(ARG1.w(), arm::Mem(TMP1));
+ } else {
+ Label do_store_1 = a.newLabel();
+ Label do_store_2 = a.newLabel();
+
+ /* Unsuitable or unknown alignment. We must be careful not
+ * to write beyound the allocated end of the binary. */
+ a.cmp(ARG4, imm(8));
+ a.b_ne(do_store_1);
+
+ a.strb(ARG1.w(), arm::Mem(TMP1));
+ a.b(next);
+
+ a.bind(do_store_1);
+ a.cmp(ARG4, imm(24));
+ a.b_hi(do_store_2);
+
+ a.strh(ARG1.w(), arm::Mem(TMP1));
+ a.cmp(ARG4, imm(16));
+ a.b_eq(next);
+
+ a.lsr(ARG1, ARG1, imm(16));
+ a.strb(ARG1.w(), arm::Mem(TMP1, 2));
+ a.b(next);
+
+ a.bind(do_store_2);
+ a.str(ARG1.w(), arm::Mem(TMP1));
+
+ if (!is_byte_aligned) {
+ a.cmp(ARG4, imm(32));
+ a.b_eq(next);
+
+ a.lsr(ARG1, ARG1, imm(32));
+ a.strb(ARG1.w(), arm::Mem(TMP1, 4));
+ }
+ }
+
+ a.bind(next);
+}
+
+/*
+ * In:
+ * TMP1 = pointer to current byte
+ * ARG3 = bit offset
+ * ARG4 = number of bits to write
+ * ARG8 = data to write
+ */
+void BeamGlobalAssembler::emit_store_unaligned() {
+ Label loop = a.newLabel();
+ Label done = a.newLabel();
+ const arm::Gp left_bit_offset = ARG3;
+ const arm::Gp right_bit_offset = TMP6;
+ const arm::Gp num_bits = ARG4;
+ const arm::Gp bitdata = ARG8;
+
+ a.ldrb(TMP5.w(), arm::Mem(TMP1));
+
+ a.and_(TMP4, bitdata, imm(0xff));
+ a.lsr(TMP4, TMP4, left_bit_offset);
+
+ a.lsl(TMP5, TMP5, left_bit_offset);
+ a.and_(TMP5, TMP5, imm(~0xff));
+ a.lsr(TMP5, TMP5, left_bit_offset);
+
+ a.orr(TMP5, TMP4, TMP5);
+
+ a.strb(TMP5.w(), arm::Mem(TMP1).post(1));
+
+ mov_imm(right_bit_offset, 8);
+ a.sub(right_bit_offset, right_bit_offset, left_bit_offset);
+
+ a.rev64(bitdata, bitdata);
+ a.lsl(bitdata, bitdata, right_bit_offset);
+
+ a.subs(num_bits, num_bits, right_bit_offset);
+ a.b_le(done);
+
+ a.bind(loop);
+ a.ror(bitdata, bitdata, imm(56));
+ a.strb(bitdata.w(), arm::Mem(TMP1).post(1));
+ a.subs(num_bits, num_bits, imm(8));
+ a.b_gt(loop);
+
+ a.bind(done);
+ a.ret(a64::x30);
+}
+
void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
const ArgWord &Alloc,
const ArgWord &Live0,
const ArgRegister &Dst,
const Span<ArgVal> &args) {
Uint num_bits = 0;
+ Uint estimated_num_bits = 0;
std::size_t n = args.size();
std::vector<BscSegment> segments;
- Label error;
+ Label error; /* Intentionally uninitialized */
ArgWord Live = Live0;
arm::Gp sizeReg;
+ Sint allocated_size = -1;
+ bool need_error_handler = false;
/*
* Collect information about each segment and calculate sizes of
@@ -1501,17 +2273,67 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
seg.error_info = beam_jit_set_bsc_segment_op(bsc_segment, bsc_op);
/*
+ * Test whether we can omit the code for the error handler.
+ */
+ switch (seg.type) {
+ case am_append:
+ if (!(exact_type<BeamTypeId::Bitstring>(seg.src) &&
+ std::gcd(seg.unit, getSizeUnit(seg.src)) == seg.unit)) {
+ need_error_handler = true;
+ }
+ break;
+ case am_binary:
+ if (!(seg.size.isAtom() && seg.size.as<ArgAtom>().get() == am_all &&
+ exact_type<BeamTypeId::Bitstring>(seg.src) &&
+ std::gcd(seg.unit, getSizeUnit(seg.src)) == seg.unit)) {
+ need_error_handler = true;
+ }
+ break;
+ case am_integer:
+ if (!exact_type<BeamTypeId::Integer>(seg.src)) {
+ need_error_handler = true;
+ }
+ break;
+ case am_private_append:
+ case am_string:
+ break;
+ default:
+ need_error_handler = true;
+ break;
+ }
+
+ /*
* Attempt to calculate the effective size of this segment.
- * Give up is variable or invalid.
+ * Give up if variable or invalid.
*/
if (seg.size.isSmall() && seg.unit != 0) {
Uint unsigned_size = seg.size.as<ArgSmall>().getUnsigned();
- if ((unsigned_size >> (sizeof(Eterm) - 1) * 8) == 0) {
+ if ((unsigned_size >> (sizeof(Eterm) - 1) * 8) != 0) {
+ /* Suppress creation of heap binary. */
+ estimated_num_bits += (ERL_ONHEAP_BIN_LIMIT + 1) * 8;
+ } else {
/* This multiplication cannot overflow. */
Uint seg_size = seg.unit * unsigned_size;
seg.effectiveSize = seg_size;
num_bits += seg_size;
+ estimated_num_bits += seg_size;
+ }
+ } else if (seg.unit > 0) {
+ auto max = std::min(std::get<1>(getClampedRange(seg.size)),
+ Sint((ERL_ONHEAP_BIN_LIMIT + 1) * 8));
+ estimated_num_bits += max * seg.unit;
+ } else {
+ switch (seg.type) {
+ case am_utf8:
+ case am_utf16:
+ case am_utf32:
+ estimated_num_bits += 32;
+ break;
+ default:
+ /* Suppress creation of heap binary. */
+ estimated_num_bits += (ERL_ONHEAP_BIN_LIMIT + 1) * 8;
+ break;
}
}
@@ -1520,14 +2342,15 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
/* At least one segment will need a dynamic size
* calculation. */
sizeReg = ARG8;
+ need_error_handler = true;
}
segments.insert(segments.end(), seg);
}
- if (Fail.get() != 0) {
+ if (need_error_handler && Fail.get() != 0) {
error = resolve_beam_label(Fail, dispUnknown);
- } else {
+ } else if (need_error_handler) {
Label past_error = a.newLabel();
a.b(past_error);
@@ -1550,6 +2373,8 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
}
a.bind(past_error);
+ } else {
+ comment("(cannot fail)");
}
/* We count the total number of bits in an unsigned integer. To
@@ -1575,13 +2400,49 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
if (seg.size.isAtom() && seg.size.as<ArgAtom>().get() == am_all &&
seg.type == am_binary) {
comment("size of an entire binary");
- mov_arg(ARG1, seg.src);
- a.mov(ARG3, ARG1);
- fragment_call(ga->get_bs_bit_size_shared());
- if (exact_type(seg.src, BEAM_TYPE_BITSTRING)) {
- comment("skipped check for success since the source "
- "is always a bit string");
+ if (exact_type<BeamTypeId::Bitstring>(seg.src)) {
+ auto src = load_source(seg.src, ARG1);
+ arm::Gp boxed_ptr = emit_ptr_val(ARG1, src.reg);
+ auto unit = getSizeUnit(seg.src);
+ bool is_bitstring = unit == 0 || std::gcd(unit, 8) != 8;
+
+ if (is_bitstring) {
+ comment("inlined size code because the value is always "
+ "a bitstring");
+ } else {
+ comment("inlined size code because the value is always "
+ "a binary");
+ }
+
+ a.ldur(TMP2, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+
+ if (is_bitstring) {
+ a.ldur(TMP1, emit_boxed_val(boxed_ptr));
+ }
+
+ a.add(sizeReg, sizeReg, TMP2, arm::lsl(3));
+
+ if (is_bitstring) {
+ Label not_sub_bin = a.newLabel();
+ const int bit_number = 3;
+ ERTS_CT_ASSERT(
+ (_TAG_HEADER_SUB_BIN & (1 << bit_number)) != 0 &&
+ (_TAG_HEADER_REFC_BIN & (1 << bit_number)) == 0 &&
+ (_TAG_HEADER_HEAP_BIN & (1 << bit_number)) == 0);
+
+ a.tbz(TMP1, imm(bit_number), not_sub_bin);
+
+ a.ldurb(TMP2.w(),
+ emit_boxed_val(boxed_ptr,
+ offsetof(ErlSubBin, bitsize)));
+ a.add(sizeReg, sizeReg, TMP2);
+
+ a.bind(not_sub_bin);
+ }
} else {
+ mov_arg(ARG1, seg.src);
+ a.mov(ARG3, ARG1);
+ fragment_call(ga->get_bs_bit_size_shared());
if (Fail.get() == 0) {
mov_imm(ARG4,
beam_jit_update_bsc_reason_info(seg.error_info,
@@ -1590,14 +2451,14 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
BSC_VALUE_ARG3));
}
a.b_mi(resolve_label(error, disp1MB));
+ a.add(sizeReg, sizeReg, ARG1);
}
- a.add(sizeReg, sizeReg, ARG1);
} else if (seg.unit != 0) {
bool can_fail = true;
comment("size binary/integer/float/string");
if (always_small(seg.size)) {
- auto [min, _] = getIntRange(seg.size);
+ auto min = std::get<0>(getClampedRange(seg.size));
if (min >= 0) {
can_fail = false;
}
@@ -1615,8 +2476,7 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
if (always_small(seg.size)) {
comment("skipped test for small size since it is always small");
- } else if (always_one_of(seg.size,
- BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ } else if (always_one_of<BeamTypeId::Number>(seg.size)) {
comment("simplified test for small size since it is a number");
emit_is_not_boxed(error, ARG3);
} else {
@@ -1627,10 +2487,10 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
if (can_fail) {
a.tbnz(ARG3, 63, resolve_label(error, disp32K));
}
- a.asr(TMP1, ARG3, imm(_TAG_IMMED1_SIZE));
if (seg.unit == 1) {
- a.add(sizeReg, sizeReg, TMP1);
+ a.add(sizeReg, sizeReg, ARG3, arm::asr(_TAG_IMMED1_SIZE));
} else {
+ a.asr(TMP1, ARG3, imm(_TAG_IMMED1_SIZE));
if (Fail.get() == 0) {
mov_imm(ARG4,
beam_jit_update_bsc_reason_info(
@@ -1639,7 +2499,7 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
BSC_INFO_SIZE,
BSC_VALUE_ARG3));
}
- a.tst(TMP1, imm(0xffful << 52));
+ a.tst(TMP1, imm(0xffful << (SMALL_BITS - ERL_UNIT_BITS)));
a.b_ne(resolve_label(error, disp1MB));
mov_imm(TMP2, seg.unit);
a.madd(sizeReg, TMP1, TMP2, sizeReg);
@@ -1649,24 +2509,60 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
case am_utf8: {
comment("size utf8");
Label next = a.newLabel();
- auto src_reg = load_source(seg.src, TMP1);
- a.lsr(TMP1, src_reg.reg, imm(_TAG_IMMED1_SIZE));
- mov_imm(TMP2, 1 * 8);
+ mov_arg(ARG3, seg.src);
+
+ if (Fail.get() == 0) {
+ mov_imm(ARG4,
+ beam_jit_update_bsc_reason_info(seg.error_info,
+ BSC_REASON_BADARG,
+ BSC_INFO_TYPE,
+ BSC_VALUE_ARG3));
+ }
+
+ if (always_small(seg.src)) {
+ comment("skipped test for small value since it is always "
+ "small");
+ } else if (always_one_of<BeamTypeId::Integer,
+ BeamTypeId::AlwaysBoxed>(seg.src)) {
+ comment("simplified test for small operand since other "
+ "types are boxed");
+ emit_is_not_boxed(resolve_label(error, dispUnknown), ARG3);
+ } else {
+ a.and_(TMP1, ARG3, imm(_TAG_IMMED1_MASK));
+ a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
+ a.b_ne(resolve_label(error, disp1MB));
+ }
+
+ a.asr(TMP1, ARG3, imm(_TAG_IMMED1_SIZE));
+ mov_imm(TMP2, 1);
a.cmp(TMP1, imm(0x7F));
a.b_ls(next);
- mov_imm(TMP2, 2 * 8);
+ mov_imm(TMP2, 2);
a.cmp(TMP1, imm(0x7FFUL));
a.b_ls(next);
+ /* Ensure that the value is not in the invalid range
+ * 0xD800 through 0xDFFF. */
+ a.lsr(TMP3, TMP1, imm(11));
+ a.cmp(TMP3, 0x1b);
+ a.b_eq(resolve_label(error, disp1MB));
+
a.cmp(TMP1, imm(0x10000UL));
- mov_imm(TMP2, 3 * 8);
- mov_imm(TMP3, 4 * 8);
- a.csel(TMP2, TMP2, TMP3, arm::CondCode::kLO);
+ a.cset(TMP2, arm::CondCode::kHS);
+ a.add(TMP2, TMP2, imm(3));
+
+ auto [min, max] = getClampedRange(seg.src);
+ if (0 <= min && max < 0x110000) {
+ comment("skipped range check for unicode code point");
+ } else {
+ a.cmp(TMP1, 0x110000);
+ a.b_hs(resolve_label(error, disp1MB));
+ }
a.bind(next);
- a.add(sizeReg, sizeReg, TMP2);
+ a.add(sizeReg, sizeReg, TMP2, arm::lsl(3));
break;
}
case am_utf16: {
@@ -1742,21 +2638,28 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get() + 1);
runtime_call<6>(erts_bs_append_checked);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(Live.get() + 1);
- if (Fail.get() == 0) {
- mov_arg(ARG3, ArgXRegister(Live.get()));
- mov_imm(ARG4,
- beam_jit_update_bsc_reason_info(seg.error_info,
- BSC_REASON_BADARG,
- BSC_INFO_FVALUE,
- BSC_VALUE_ARG3));
+ if (exact_type<BeamTypeId::Bitstring>(seg.src) &&
+ std::gcd(seg.unit, getSizeUnit(seg.src)) == seg.unit) {
+ /* There is no way the call can fail with a system_limit
+ * exception on a 64-bit architecture. */
+ comment("skipped test for success because units are compatible");
+ } else {
+ if (Fail.get() == 0) {
+ mov_arg(ARG3, ArgXRegister(Live.get()));
+ mov_imm(ARG4,
+ beam_jit_update_bsc_reason_info(seg.error_info,
+ BSC_REASON_BADARG,
+ BSC_INFO_FVALUE,
+ BSC_VALUE_ARG3));
+ }
+ emit_branch_if_not_value(ARG1, resolve_label(error, dispUnknown));
}
- emit_branch_if_not_value(ARG1, resolve_label(error, dispUnknown));
} else if (segments[0].type == am_private_append) {
BscSegment seg = segments[0];
comment("private append to binary");
@@ -1773,6 +2676,82 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
runtime_call<4>(erts_bs_private_append_checked);
emit_leave_runtime(Live.get());
/* There is no way the call can fail on a 64-bit architecture. */
+ } else if (estimated_num_bits % 8 == 0 &&
+ estimated_num_bits / 8 <= ERL_ONHEAP_BIN_LIMIT) {
+ static constexpr auto cur_bin_offset =
+ offsetof(ErtsSchedulerRegisters, aux_regs.d.erl_bits_state) +
+ offsetof(struct erl_bits_state, erts_current_bin_);
+ Uint need;
+
+ arm::Mem mem_bin_base = arm::Mem(scheduler_registers, cur_bin_offset);
+
+ if (sizeReg.isValid()) {
+ Label after_gc_check = a.newLabel();
+
+ comment("allocate heap binary of dynamic size (=< %ld bits)",
+ estimated_num_bits);
+
+ /* Calculate number of bytes to allocate. */
+ need = (heap_bin_size(0) + Alloc.get() + S_RESERVED);
+ a.lsr(sizeReg, sizeReg, imm(3));
+ a.add(TMP3, sizeReg, imm(7));
+ a.and_(TMP3, TMP3, imm(-8));
+ a.add(TMP1, TMP3, imm(need * sizeof(Eterm)));
+
+ /* Do a GC test. */
+ a.add(ARG3, HTOP, TMP1);
+ a.cmp(ARG3, E);
+ a.b_ls(after_gc_check);
+
+ a.stp(sizeReg, TMP3, TMP_MEM1q);
+
+ mov_imm(ARG4, Live.get());
+ fragment_call(ga->get_garbage_collect());
+
+ a.ldp(sizeReg, TMP3, TMP_MEM1q);
+
+ a.bind(after_gc_check);
+
+ mov_imm(TMP1, header_heap_bin(0));
+ a.lsr(TMP4, TMP3, imm(3));
+ a.add(TMP1, TMP1, TMP4, arm::lsl(_HEADER_ARITY_OFFS));
+
+ /* Create the heap binary. */
+ a.add(ARG1, HTOP, imm(TAG_PRIMARY_BOXED));
+ a.stp(TMP1, sizeReg, arm::Mem(HTOP).post(sizeof(Eterm[2])));
+
+ /* Initialize the erl_bin_state struct. */
+ a.stp(HTOP, ZERO, mem_bin_base);
+
+ /* Update HTOP. */
+ a.add(HTOP, HTOP, TMP3);
+ } else {
+ Uint num_bytes = num_bits / 8;
+
+ comment("allocate heap binary of static size");
+
+ allocated_size = (num_bytes + 7) & (-8);
+
+ /* Ensure that there is sufficient room on the heap. */
+ need = heap_bin_size(num_bytes) + Alloc.get();
+ emit_gc_test(ArgWord(0), ArgWord(need), Live);
+
+ mov_imm(TMP1, header_heap_bin(num_bytes));
+ mov_imm(TMP2, num_bytes);
+
+ /* Create the heap binary. */
+ a.add(ARG1, HTOP, imm(TAG_PRIMARY_BOXED));
+ a.stp(TMP1, TMP2, arm::Mem(HTOP).post(sizeof(Eterm[2])));
+
+ /* Initialize the erl_bin_state struct. */
+ ERTS_CT_ASSERT_FIELD_PAIR(struct erl_bits_state,
+ erts_current_bin_,
+ erts_bin_offset_);
+ a.stp(HTOP, ZERO, mem_bin_base);
+
+ /* Update HTOP. */
+ a.add(HTOP, HTOP, imm(allocated_size));
+ }
} else {
comment("allocate binary");
mov_arg(ARG5, Alloc);
@@ -1780,30 +2759,43 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
load_erl_bits_state(ARG3);
load_x_reg_array(ARG2);
a.mov(ARG1, c_p);
- emit_enter_runtime<Update::eReductions | Update::eStack |
- Update::eHeap | Update::eXRegs>(Live.get());
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc |
+ Update::eXRegs>(Live.get());
if (sizeReg.isValid()) {
comment("(size in bits)");
a.mov(ARG4, sizeReg);
runtime_call<6>(beam_jit_bs_init_bits);
- } else if (num_bits % 8 == 0) {
- comment("(size in bytes)");
- mov_imm(ARG4, num_bits / 8);
- runtime_call<6>(beam_jit_bs_init);
} else {
+ allocated_size = (num_bits + 7) / 8;
+ if (allocated_size <= ERL_ONHEAP_BIN_LIMIT) {
+ allocated_size = (allocated_size + 7) & (-8);
+ }
mov_imm(ARG4, num_bits);
runtime_call<6>(beam_jit_bs_init_bits);
}
- emit_leave_runtime<Update::eReductions | Update::eStack |
- Update::eHeap | Update::eXRegs>(Live.get());
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc |
+ Update::eXRegs>(Live.get());
}
a.str(ARG1, TMP_MEM1q);
+ segments = bs_combine_segments(segments);
+
+ /* Keep track of the bit offset from the being of the binary.
+ * Set to -1 if offset is not known (when a segment of unknown
+ * size has been seen). */
+ Sint bit_offset = 0;
+
+ /* Keep track of whether the current segment is byte-aligned. (A
+ * segment can be known to be byte-aligned even if the bit offset
+ * is unknown.) */
+ bool is_byte_aligned = true;
+
/* Build each segment of the binary. */
for (auto seg : segments) {
switch (seg.type) {
case am_append:
case am_private_append:
+ bit_offset = -1;
break;
case am_binary: {
Uint error_info;
@@ -1838,8 +2830,10 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
BSC_REASON_BADARG,
BSC_INFO_UNIT,
BSC_VALUE_FVALUE);
- if (seg.unit == 1) {
- comment("skipped test for success because unit =:= 1");
+ if (exact_type<BeamTypeId::Bitstring>(seg.src) &&
+ std::gcd(seg.unit, getSizeUnit(seg.src)) == seg.unit) {
+ comment("skipped test for success because units are "
+ "compatible");
can_fail = false;
}
} else {
@@ -1847,8 +2841,8 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
* the value is a non-negative small in the
* appropriate range. Multiply the size with the
* unit. */
- mov_arg(ARG3, seg.size);
- a.asr(ARG3, ARG3, imm(_TAG_IMMED1_SIZE));
+ auto r = load_source(seg.size, ARG3);
+ a.asr(ARG3, r.reg, imm(_TAG_IMMED1_SIZE));
if (seg.unit != 1) {
mov_imm(TMP1, seg.unit);
a.mul(ARG3, ARG3, TMP1);
@@ -1879,8 +2873,8 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
if (seg.effectiveSize >= 0) {
mov_imm(ARG3, seg.effectiveSize);
} else {
- mov_arg(ARG3, seg.size);
- a.asr(ARG3, ARG3, imm(_TAG_IMMED1_SIZE));
+ auto r = load_source(seg.size, ARG3);
+ a.asr(ARG3, r.reg, imm(_TAG_IMMED1_SIZE));
if (seg.unit != 1) {
mov_imm(TMP1, seg.unit);
a.mul(ARG3, ARG3, TMP1);
@@ -1904,38 +2898,281 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
emit_branch_if_value(ARG1, resolve_label(error, dispUnknown));
break;
case am_integer:
- comment("construct integer segment");
- if (seg.effectiveSize >= 0) {
- mov_imm(ARG3, seg.effectiveSize);
- } else {
- mov_arg(ARG3, seg.size);
- a.asr(ARG3, ARG3, imm(_TAG_IMMED1_SIZE));
- if (seg.unit != 1) {
- mov_imm(TMP1, seg.unit);
- a.mul(ARG3, ARG3, TMP1);
+ switch (seg.action) {
+ case BscSegment::action::ACCUMULATE_FIRST:
+ case BscSegment::action::ACCUMULATE: {
+ /* Shift an integer of known size (no more than 64 bits)
+ * into a word-size accumulator. */
+ Label value_is_small = a.newLabel();
+ Label done = a.newLabel();
+
+ comment("accumulate value for integer segment");
+ auto src = load_source(seg.src, ARG1);
+ if (seg.effectiveSize < 64 &&
+ seg.action == BscSegment::action::ACCUMULATE) {
+ a.lsl(ARG8, ARG8, imm(seg.effectiveSize));
+ }
+
+ if (!always_small(seg.src)) {
+ if (always_one_of<BeamTypeId::Integer,
+ BeamTypeId::AlwaysBoxed>(seg.src)) {
+ comment("simplified small test since all other types "
+ "are boxed");
+ emit_is_boxed(value_is_small, seg.src, src.reg);
+ } else {
+ a.and_(TMP1, src.reg, imm(_TAG_IMMED1_MASK));
+ a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
+ a.b_eq(value_is_small);
+ }
+
+ /* The value is boxed. If it is a bignum, extract the
+ * least significant 64 bits. */
+ mov_var(ARG1, src);
+ fragment_call(ga->get_get_sint64_shared());
+ if (seg.effectiveSize == 64) {
+ a.mov(ARG8, ARG1);
+ } else {
+ a.bfxil(ARG8,
+ ARG1,
+ arm::lsr(0),
+ imm(seg.effectiveSize));
+ }
+
+ if (exact_type<BeamTypeId::Integer>(seg.src)) {
+ a.b(done);
+ } else {
+ a.b_ne(done);
+
+ /* Not a bignum. Signal error. */
+ if (Fail.get() == 0) {
+ mov_imm(ARG4,
+ beam_jit_update_bsc_reason_info(
+ seg.error_info,
+ BSC_REASON_BADARG,
+ BSC_INFO_TYPE,
+ BSC_VALUE_ARG1));
+ }
+ a.b(resolve_label(error, disp128MB));
+ }
+ }
+
+ a.bind(value_is_small);
+ if (seg.effectiveSize == 64) {
+ a.asr(ARG8, src.reg, imm(_TAG_IMMED1_SIZE));
+ } else if (seg.effectiveSize + _TAG_IMMED1_SIZE > 64) {
+ a.asr(TMP1, src.reg, imm(_TAG_IMMED1_SIZE));
+ a.bfxil(ARG8, TMP1, arm::lsr(0), imm(seg.effectiveSize));
+ } else {
+ a.bfxil(ARG8,
+ src.reg,
+ arm::lsr(_TAG_IMMED1_SIZE),
+ imm(seg.effectiveSize));
}
+
+ a.bind(done);
+ break;
}
- mov_arg(ARG2, seg.src);
- mov_imm(ARG4, seg.flags);
- load_erl_bits_state(ARG1);
+ case BscSegment::action::STORE: {
+ /* The accumulator is now full or the next segment is
+ * not possible to accumulate, so it's time to store
+ * the accumulator to the current position in the
+ * binary. */
+ Label store = a.newLabel();
+ Label done = a.newLabel();
+
+ comment("construct integer segment from accumulator");
+
+ /* First we'll need to ensure that the value in the
+ * accumulator is in little endian format. */
+ ASSERT(seg.effectiveSize >= 0);
+ if (seg.effectiveSize % 8) {
+ Uint complete_bytes = 8 * (seg.effectiveSize / 8);
+ Uint num_partial = seg.effectiveSize % 8;
+ if (seg.flags & BSF_LITTLE) {
+ a.ubfx(TMP1,
+ ARG8,
+ imm(complete_bytes),
+ imm(num_partial));
+ a.bfc(ARG8,
+ arm::lsr(complete_bytes),
+ imm(64 - complete_bytes));
+ a.bfi(ARG8,
+ TMP1,
+ imm(complete_bytes + 8 - num_partial),
+ imm(num_partial));
+ } else {
+ a.lsl(ARG8, ARG8, imm(64 - seg.effectiveSize));
+ a.rev64(ARG8, ARG8);
+ }
+ } else if ((seg.flags & BSF_LITTLE) == 0) {
+ switch (seg.effectiveSize) {
+ case 8:
+ break;
+ case 16:
+ a.rev16(ARG8, ARG8);
+ break;
+ case 32:
+ a.rev32(ARG8, ARG8);
+ break;
+ case 64:
+ a.rev64(ARG8, ARG8);
+ break;
+ default:
+ a.rev64(ARG8, ARG8);
+ a.lsr(ARG8, ARG8, imm(64 - seg.effectiveSize));
+ }
+ }
- emit_enter_runtime(Live.get());
- runtime_call<4>(erts_new_bs_put_integer);
- emit_leave_runtime(Live.get());
+ arm::Gp bin_offset = ARG3;
+ arm::Gp bin_data = ARG8;
+
+ update_bin_state(bin_offset,
+ bit_offset,
+ seg.effectiveSize,
+ arm::Gp());
+
+ if (!is_byte_aligned) {
+ if (bit_offset < 0) {
+ /* Bit offset is unknown. Must test alignment. */
+ a.ands(bin_offset, bin_offset, imm(7));
+ a.b_eq(store);
+ } else if (bit_offset >= 0) {
+ /* Alignment is known to be unaligned. */
+ mov_imm(bin_offset, bit_offset & 7);
+ }
+
+ /* Bit offset is tested or known to be unaligned. */
+ mov_imm(ARG4, seg.effectiveSize);
+ fragment_call(ga->get_store_unaligned());
+
+ if (bit_offset < 0) {
+ /* The bit offset is unknown, which implies that
+ * there exists store code that we will need to
+ * branch past. */
+ a.b(done);
+ }
+ }
- if (exact_type(seg.src, BEAM_TYPE_INTEGER)) {
- comment("skipped test for success because construction can't "
- "fail");
- } else {
- if (Fail.get() == 0) {
- mov_arg(ARG3, seg.src);
- mov_imm(ARG4,
- beam_jit_update_bsc_reason_info(seg.error_info,
- BSC_REASON_BADARG,
- BSC_INFO_TYPE,
- BSC_VALUE_ARG3));
+ a.bind(store);
+
+ if (bit_offset < 0 || is_byte_aligned) {
+ /* Bit offset is tested or known to be
+ * byte-aligned. Emit inline code to store the
+ * value of the accumulator into the binary. */
+ int num_bytes = (seg.effectiveSize + 7) / 8;
+
+ /* If more than one instruction is required for
+ * doing the store, test whether it would be safe
+ * to do a single 32 or 64 bit store. */
+ switch (num_bytes) {
+ case 3:
+ if (bit_offset >= 0 &&
+ allocated_size * 8 - bit_offset >= 32) {
+ comment("simplified complicated store");
+ num_bytes = 4;
+ }
+ break;
+ case 5:
+ case 6:
+ case 7:
+ if (bit_offset >= 0 &&
+ allocated_size * 8 - bit_offset >= 64) {
+ comment("simplified complicated store");
+ num_bytes = 8;
+ }
+ break;
+ }
+
+ do {
+ switch (num_bytes) {
+ case 1:
+ a.strb(bin_data.w(), arm::Mem(TMP1));
+ break;
+ case 2:
+ a.strh(bin_data.w(), arm::Mem(TMP1));
+ break;
+ case 3:
+ a.strh(bin_data.w(), arm::Mem(TMP1));
+ a.lsr(bin_data, bin_data, imm(16));
+ a.strb(bin_data.w(), arm::Mem(TMP1, 2));
+ break;
+ case 4:
+ a.str(bin_data.w(), arm::Mem(TMP1));
+ break;
+ case 5:
+ case 6:
+ case 7:
+ a.str(bin_data.w(), arm::Mem(TMP1).post(4));
+ a.lsr(bin_data, bin_data, imm(32));
+ break;
+ case 8:
+ a.str(bin_data, arm::Mem(TMP1));
+ num_bytes = 0;
+ break;
+ }
+ num_bytes -= 4;
+ } while (num_bytes > 0);
+ }
+
+ a.bind(done);
+ break;
+ }
+ case BscSegment::action::DIRECT:
+ /* This segment either has a size exceeding the maximum
+ * accumulator size of 64 bits or has a variable size.
+ *
+ * First load the effective size (size * unit) into ARG3.
+ */
+ comment("construct integer segment");
+ if (seg.effectiveSize >= 0) {
+ mov_imm(ARG3, seg.effectiveSize);
+ } else {
+ auto size = load_source(seg.size, TMP1);
+ a.lsr(ARG3, size.reg, imm(_TAG_IMMED1_SIZE));
+ if (Support::isPowerOf2(seg.unit)) {
+ Uint trailing_bits = Support::ctz<Eterm>(seg.unit);
+ if (trailing_bits) {
+ a.lsl(ARG3, ARG3, imm(trailing_bits));
+ }
+ } else {
+ mov_imm(TMP1, seg.unit);
+ a.mul(ARG3, ARG3, TMP1);
+ }
+ }
+
+ if (is_byte_aligned && seg.src.isSmall() &&
+ seg.src.as<ArgSmall>().getSigned() == 0) {
+ /* Optimize the special case of setting a known
+ * byte-aligned segment to zero. */
+ comment("optimized setting segment to 0");
+ set_zero(seg.effectiveSize);
+ } else {
+ /* Call the helper function to fetch and store the
+ * integer into the binary. */
+ mov_arg(ARG2, seg.src);
+ mov_imm(ARG4, seg.flags);
+ load_erl_bits_state(ARG1);
+
+ emit_enter_runtime(Live.get());
+ runtime_call<4>(erts_new_bs_put_integer);
+ emit_leave_runtime(Live.get());
+
+ if (exact_type<BeamTypeId::Integer>(seg.src)) {
+ comment("skipped test for success because construction "
+ "can't fail");
+ } else {
+ if (Fail.get() == 0) {
+ mov_arg(ARG3, seg.src);
+ mov_imm(ARG4,
+ beam_jit_update_bsc_reason_info(
+ seg.error_info,
+ BSC_REASON_BADARG,
+ BSC_INFO_TYPE,
+ BSC_VALUE_ARG3));
+ }
+ a.cbz(ARG1, resolve_label(error, disp1MB));
+ }
}
- a.cbz(ARG1, resolve_label(error, disp1MB));
}
break;
case am_string: {
@@ -1953,27 +3190,12 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
emit_leave_runtime(Live.get());
break;
}
- case am_utf8:
- comment("construct utf8 segment");
- mov_arg(ARG2, seg.src);
- load_erl_bits_state(ARG1);
-
- emit_enter_runtime(Live.get());
- runtime_call<2>(erts_bs_put_utf8);
-
- emit_leave_runtime(Live.get());
- if (Fail.get() == 0) {
- mov_arg(ARG3, seg.src);
- mov_imm(ARG4,
- beam_jit_update_bsc_reason_info(seg.error_info,
- BSC_REASON_BADARG,
- BSC_INFO_TYPE,
- BSC_VALUE_ARG3));
- }
- a.cbz(ARG1, resolve_label(error, disp1MB));
+ case am_utf8: {
+ emit_construct_utf8(seg.src, bit_offset, is_byte_aligned);
break;
+ }
case am_utf16:
- comment("construct utf8 segment");
+ comment("construct utf16 segment");
mov_arg(ARG2, seg.src);
a.mov(ARG3, seg.flags);
load_erl_bits_state(ARG1);
@@ -2016,8 +3238,925 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
ASSERT(0);
break;
}
+
+ /* Try to keep track of the bit offset. */
+ if (bit_offset >= 0 && (seg.action == BscSegment::action::DIRECT ||
+ seg.action == BscSegment::action::STORE)) {
+ if (seg.effectiveSize >= 0) {
+ bit_offset += seg.effectiveSize;
+ } else {
+ bit_offset = -1;
+ }
+ }
+
+ /* Try to keep track whether the next segment is byte
+ * aligned. */
+ if (seg.type == am_append || seg.type == am_private_append) {
+ if (!exact_type<BeamTypeId::Bitstring>(seg.src) ||
+ std::gcd(getSizeUnit(seg.src), 8) != 8) {
+ is_byte_aligned = false;
+ }
+ } else if (bit_offset % 8 == 0) {
+ is_byte_aligned = true;
+ } else if (seg.effectiveSize >= 0) {
+ if (seg.effectiveSize % 8 != 0) {
+ is_byte_aligned = false;
+ }
+ } else if (std::gcd(seg.unit, 8) != 8) {
+ is_byte_aligned = false;
+ }
}
comment("done");
mov_arg(Dst, TMP_MEM1q);
}
+
+/*
+ * Here follows the bs_match instruction and friends.
+ */
+
+struct BsmSegment {
+ BsmSegment()
+ : action(action::TEST_HEAP), live(ArgNil()), size(0), unit(1),
+ flags(0), dst(ArgXRegister(0)){};
+
+ enum class action {
+ TEST_HEAP,
+ ENSURE_AT_LEAST,
+ ENSURE_EXACTLY,
+ READ,
+ EXTRACT_BINARY,
+ EXTRACT_INTEGER,
+ GET_INTEGER,
+ GET_BINARY,
+ SKIP,
+ DROP,
+ GET_TAIL,
+ EQ
+ } action;
+ ArgVal live;
+ Uint size;
+ Uint unit;
+ Uint flags;
+ ArgRegister dst;
+};
+
+void BeamModuleAssembler::emit_read_bits(Uint bits,
+ const arm::Gp bin_base,
+ const arm::Gp bin_offset,
+ const arm::Gp bitdata) {
+ Label handle_partial = a.newLabel();
+ Label rev64 = a.newLabel();
+ Label shift = a.newLabel();
+ Label read_done = a.newLabel();
+
+ bool need_rev64 = false;
+
+ const arm::Gp bin_byte_ptr = TMP2;
+ const arm::Gp bit_offset = TMP4;
+ const arm::Gp tmp = TMP5;
+
+ auto num_partial = bits % 8;
+
+ ASSERT(1 <= bits && bits <= 64);
+
+ a.add(bin_byte_ptr, bin_base, bin_offset, arm::lsr(3));
+
+ if (bits <= 8) {
+ a.ands(bit_offset, bin_offset, imm(7));
+
+ if (num_partial == 0) {
+ /* Byte-sized segment. If bit_offset is not byte-aligned,
+ * this segment always spans two bytes. */
+ a.b_ne(handle_partial);
+ } else if (num_partial > 1) {
+ /* The segment is smaller than one byte but more than one
+ * bit. Test whether it fits within the current byte. */
+ a.cmp(bit_offset, imm(8 - num_partial));
+ a.b_gt(handle_partial);
+ }
+
+ /* The segment fits in the current byte. */
+ a.ldrb(bitdata.w(), arm::Mem(bin_byte_ptr));
+ if (num_partial == 0) {
+ a.rev64(bitdata, bitdata);
+ a.b(read_done);
+ } else if (num_partial > 1) {
+ a.b(rev64);
+ }
+
+ /* The segment is unaligned and spans two bytes. */
+ a.bind(handle_partial);
+ if (num_partial != 1) {
+ a.ldrh(bitdata.w(), arm::Mem(bin_byte_ptr));
+ }
+ need_rev64 = true;
+ } else if (bits <= 16) {
+ a.ands(bit_offset, bin_offset, imm(7));
+
+ /* We always need to read at least two bytes. */
+ a.ldrh(bitdata.w(), arm::Mem(bin_byte_ptr));
+ a.rev64(bitdata, bitdata);
+ a.b_eq(read_done); /* Done if segment is byte-aligned. */
+
+ /* The segment is unaligned. If its size is 9, it always fits
+ * in two bytes and we fall through to the shift instruction. */
+ a.bind(handle_partial);
+ if (num_partial > 1) {
+ /* If segment size is less than 15 bits or less, it is
+ * possible that it fits into two bytes. */
+ a.cmp(bit_offset, imm(8 - num_partial));
+ a.b_le(shift);
+ }
+
+ if (num_partial != 1) {
+ /* The segment spans three bytes. Read an additional byte and
+ * shift into place (right below the already read two bytes a
+ * the top of the word). */
+ a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 2));
+ a.orr(bitdata, bitdata, tmp, arm::lsl(40));
+ }
+ } else if (bits <= 24) {
+ a.ands(bit_offset, bin_offset, imm(7));
+
+ if (num_partial == 0) {
+ /* Byte-sized segment. If bit_offset is not byte-aligned,
+ * this segment always spans four bytes. */
+ a.b_ne(handle_partial);
+ } else if (num_partial > 1) {
+ /* The segment is smaller than three bytes. Test whether
+ * it spans three or four bytes. */
+ a.cmp(bit_offset, imm(8 - num_partial));
+ a.b_gt(handle_partial);
+ }
+
+ /* This segment spans three bytes. */
+ a.ldrh(bitdata.w(), arm::Mem(bin_byte_ptr));
+ a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 2));
+ a.orr(bitdata, bitdata, tmp, arm::lsl(16));
+ if (num_partial == 0) {
+ a.rev64(bitdata, bitdata);
+ a.b(read_done);
+ } else if (num_partial > 1) {
+ a.b(rev64);
+ }
+
+ /* This segment spans four bytes. */
+ a.bind(handle_partial);
+ if (num_partial != 1) {
+ a.ldr(bitdata.w(), arm::Mem(bin_byte_ptr));
+ }
+ need_rev64 = true;
+ } else if (bits <= 32) {
+ a.ands(bit_offset, bin_offset, imm(7));
+
+ /* We always need to read at least four bytes. */
+ a.ldr(bitdata.w(), arm::Mem(bin_byte_ptr));
+ a.rev64(bitdata, bitdata);
+ a.b_eq(read_done);
+
+ a.bind(handle_partial);
+ if (num_partial > 0) {
+ a.cmp(bit_offset, imm(8 - num_partial));
+ a.b_le(shift);
+ }
+
+ if (num_partial != 1) {
+ /* The segment spans five bytes. Read an additional byte and
+ * shift into place. */
+ a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 4));
+ a.orr(bitdata, bitdata, tmp, arm::lsl(24));
+ }
+ } else if (bits <= 40) {
+ a.ands(bit_offset, bin_offset, imm(7));
+
+ /* We always need to read four bytes. */
+ a.ldr(bitdata.w(), arm::Mem(bin_byte_ptr));
+ a.rev64(bitdata, bitdata);
+
+ if (num_partial == 0) {
+ /* Byte-sized segment. If bit_offset is not byte-aligned,
+ * this segment always spans six bytes. */
+ a.b_ne(handle_partial);
+ } else if (num_partial > 1) {
+ /* The segment is smaller than five bytes. Test whether it
+ * spans five or six bytes. */
+ a.cmp(bit_offset, imm(8 - num_partial));
+ a.b_gt(handle_partial);
+ }
+
+ /* This segment spans five bytes. Read an additional byte. */
+ a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 4));
+ a.orr(bitdata, bitdata, tmp, arm::lsl(24));
+ if (num_partial == 0) {
+ a.b(read_done);
+ } else if (num_partial > 1) {
+ a.b(shift);
+ }
+
+ a.bind(handle_partial);
+ if (num_partial != 1) {
+ /* This segment spans six bytes. Read two additional bytes. */
+ a.ldrh(tmp.w(), arm::Mem(bin_byte_ptr, 4));
+ a.rev16(tmp.w(), tmp.w());
+ a.orr(bitdata, bitdata, tmp, arm::lsl(16));
+ }
+ } else if (bits <= 48) {
+ a.ands(bit_offset, bin_offset, imm(7));
+ a.ldr(bitdata.w(), arm::Mem(bin_byte_ptr));
+ a.ldrh(tmp.w(), arm::Mem(bin_byte_ptr, 4));
+ a.orr(bitdata, bitdata, tmp, arm::lsl(32));
+ a.rev64(bitdata, bitdata);
+ a.b_eq(read_done);
+
+ a.bind(handle_partial);
+ if (num_partial > 1) {
+ a.cmp(bit_offset, imm(8 - num_partial));
+ a.b_le(shift);
+ }
+
+ if (num_partial != 1) {
+ a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 6));
+ a.orr(bitdata, bitdata, tmp, arm::lsl(8));
+ }
+ } else if (bits <= 56) {
+ a.ands(bit_offset, bin_offset, imm(7));
+
+ if (num_partial == 0) {
+ /* Byte-sized segment. If bit_offset is not byte-aligned,
+ * this segment always spans 8 bytes. */
+ a.b_ne(handle_partial);
+ } else if (num_partial > 1) {
+ /* The segment is smaller than 8 bytes. Test whether it
+ * spans 7 or 8 bytes. */
+ a.cmp(bit_offset, imm(8 - num_partial));
+ a.b_gt(handle_partial);
+ }
+
+ /* This segment spans 7 bytes. */
+ a.ldr(bitdata, arm::Mem(bin_byte_ptr, -1));
+ a.lsr(bitdata, bitdata, imm(8));
+ a.b(rev64);
+
+ /* This segment spans 8 bytes. */
+ a.bind(handle_partial);
+ if (num_partial != 1) {
+ a.ldr(bitdata, arm::Mem(bin_byte_ptr));
+ }
+ need_rev64 = true;
+ } else if (bits <= 64) {
+ a.ands(bit_offset, bin_offset, imm(7));
+ a.ldr(bitdata, arm::Mem(bin_byte_ptr));
+ a.rev64(bitdata, bitdata);
+
+ if (num_partial == 0) {
+ /* Byte-sized segment. If it is aligned it spans 8 bytes
+ * and we are done. */
+ a.b_eq(read_done);
+ } else if (num_partial == 1) {
+ /* This segment is 57 bits wide. It always spans 8 bytes. */
+ a.b(shift);
+ } else {
+ /* The segment is smaller than 8 bytes. Test whether it
+ * spans 8 or 9 bytes. */
+ a.cmp(bit_offset, imm(8 - num_partial));
+ a.b_le(shift);
+ }
+
+ /* This segments spans 9 bytes. Read an additional byte. */
+ a.bind(handle_partial);
+ if (num_partial != 1) {
+ a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 8));
+ a.lsl(bitdata, bitdata, bit_offset);
+ a.lsl(tmp, tmp, bit_offset);
+ a.orr(bitdata, bitdata, tmp, arm::lsr(8));
+ a.b(read_done);
+ }
+ }
+
+ a.bind(rev64);
+ if (need_rev64) {
+ a.rev64(bitdata, bitdata);
+ }
+
+ /* Shift the read data into the most significant bits of the
+ * word. */
+ a.bind(shift);
+ a.lsl(bitdata, bitdata, bit_offset);
+
+ a.bind(read_done);
+}
+
+void BeamModuleAssembler::emit_extract_integer(const arm::Gp bitdata,
+ Uint flags,
+ Uint bits,
+ const ArgRegister &Dst) {
+ Label big = a.newLabel();
+ Label done = a.newLabel();
+ arm::Gp data_reg;
+ auto dst = init_destination(Dst, TMP1);
+ Uint num_partial = bits % 8;
+ Uint num_complete = 8 * (bits / 8);
+
+ if (bits <= 8) {
+ /* Endian does not matter for values that fit in a byte. */
+ flags &= ~BSF_LITTLE;
+ }
+
+ /* If this segment is little-endian, reverse endianness. */
+ if ((flags & BSF_LITTLE) != 0) {
+ comment("reverse endian for a little-endian segment");
+ }
+ data_reg = TMP2;
+ if ((flags & BSF_LITTLE) == 0) {
+ data_reg = bitdata;
+ } else if (bits == 16) {
+ a.rev16(TMP2, bitdata);
+ } else if (bits == 32) {
+ a.rev32(TMP2, bitdata);
+ } else if (num_partial == 0) {
+ a.rev64(TMP2, bitdata);
+ a.lsr(TMP2, TMP2, arm::lsr(64 - bits));
+ } else {
+ a.ubfiz(TMP3, bitdata, imm(num_complete), imm(num_partial));
+ a.ubfx(TMP2, bitdata, imm(num_partial), imm(num_complete));
+ a.rev64(TMP2, TMP2);
+ a.orr(TMP2, TMP3, TMP2, arm::lsr(64 - num_complete));
+ }
+
+ /* Sign-extend the number if the segment is signed. */
+ if ((flags & BSF_SIGNED) != 0) {
+ if (0 < bits && bits < 64) {
+ comment("sign extend extracted value");
+ a.lsl(TMP2, data_reg, imm(64 - bits));
+ a.asr(TMP2, TMP2, imm(64 - bits));
+ data_reg = TMP2;
+ }
+ }
+
+ /* Handle segments whose values might not fit in a small integer. */
+ if (bits >= SMALL_BITS) {
+ comment("test whether it fits in a small");
+ if (bits < 64 && (flags & BSF_SIGNED) == 0) {
+ a.and_(TMP2, data_reg, imm((1ull << bits) - 1));
+ data_reg = TMP2;
+ }
+ if ((flags & BSF_SIGNED) != 0) {
+ /* Signed segment. */
+ a.adds(TMP3, ZERO, data_reg, arm::lsr(SMALL_BITS - 1));
+ a.ccmp(TMP3,
+ imm(_TAG_IMMED1_MASK << 1 | 1),
+ imm(NZCV::kEqual),
+ imm(arm::CondCode::kNE));
+ a.b_ne(big);
+ } else {
+ /* Unsigned segment. */
+ a.lsr(TMP3, data_reg, imm(SMALL_BITS - 1));
+ a.cbnz(TMP3, big);
+ }
+ }
+
+ /* Tag and store the extracted small integer. */
+ comment("store extracted integer as a small");
+ mov_imm(dst.reg, _TAG_IMMED1_SMALL);
+ if ((flags & BSF_SIGNED) != 0) {
+ a.orr(dst.reg, dst.reg, data_reg, arm::lsl(_TAG_IMMED1_SIZE));
+ } else {
+ if (bits >= SMALL_BITS) {
+ a.bfi(dst.reg,
+ data_reg,
+ arm::lsl(_TAG_IMMED1_SIZE),
+ imm(SMALL_BITS));
+ } else if (bits != 0) {
+ a.bfi(dst.reg, data_reg, arm::lsl(_TAG_IMMED1_SIZE), imm(bits));
+ }
+ }
+
+ if (bits >= SMALL_BITS) {
+ a.b(done);
+ }
+
+ /* Handle a bignum (up to 64 bits). */
+ a.bind(big);
+ if (bits >= SMALL_BITS) {
+ comment("store extracted integer as a bignum");
+ a.add(dst.reg, HTOP, imm(TAG_PRIMARY_BOXED));
+ mov_imm(TMP3, make_pos_bignum_header(1));
+ if ((flags & BSF_SIGNED) == 0) {
+ /* Unsigned. */
+ a.stp(TMP3, data_reg, arm::Mem(HTOP).post(sizeof(Eterm[2])));
+ } else {
+ /* Signed. */
+ Label store = a.newLabel();
+ a.adds(TMP2, data_reg, ZERO);
+ a.b_pl(store);
+
+ mov_imm(TMP3, make_neg_bignum_header(1));
+ a.neg(TMP2, TMP2);
+
+ a.bind(store);
+ a.stp(TMP3, TMP2, arm::Mem(HTOP).post(sizeof(Eterm[2])));
+ }
+ }
+
+ a.bind(done);
+ flush_var(dst);
+}
+
+void BeamModuleAssembler::emit_extract_binary(const arm::Gp bitdata,
+ Uint bits,
+ const ArgRegister &Dst) {
+ auto dst = init_destination(Dst, TMP1);
+ Uint num_bytes = bits / 8;
+
+ a.add(dst.reg, HTOP, imm(TAG_PRIMARY_BOXED));
+ mov_imm(TMP2, header_heap_bin(num_bytes));
+ mov_imm(TMP3, num_bytes);
+ a.rev64(TMP4, bitdata);
+ a.stp(TMP2, TMP3, arm::Mem(HTOP).post(sizeof(Eterm[2])));
+ if (num_bytes != 0) {
+ a.str(TMP4, arm::Mem(HTOP).post(sizeof(Eterm[1])));
+ }
+ flush_var(dst);
+}
+
+static std::vector<BsmSegment> opt_bsm_segments(
+ const std::vector<BsmSegment> segments,
+ const ArgWord &Need,
+ const ArgWord &Live) {
+ std::vector<BsmSegment> segs;
+
+ Uint heap_need = Need.get();
+
+ /*
+ * First calculate the total number of heap words needed for
+ * bignums and binaries.
+ */
+ for (auto seg : segments) {
+ switch (seg.action) {
+ case BsmSegment::action::GET_INTEGER:
+ if (seg.size >= SMALL_BITS) {
+ heap_need += BIG_NEED_FOR_BITS(seg.size);
+ }
+ break;
+ case BsmSegment::action::GET_BINARY:
+ heap_need += heap_bin_size((seg.size + 7) / 8);
+ break;
+ case BsmSegment::action::GET_TAIL:
+ heap_need += EXTRACT_SUB_BIN_HEAP_NEED;
+ break;
+ default:
+ break;
+ }
+ }
+
+ int index = 0;
+ int read_action_pos = -1;
+
+ index = 0;
+ for (auto seg : segments) {
+ if (heap_need != 0 && seg.live.isWord()) {
+ BsmSegment s = seg;
+
+ read_action_pos = -1;
+ s.action = BsmSegment::action::TEST_HEAP;
+ s.size = heap_need;
+ segs.push_back(s);
+ index++;
+ heap_need = 0;
+ }
+
+ switch (seg.action) {
+ case BsmSegment::action::GET_INTEGER:
+ case BsmSegment::action::GET_BINARY:
+ if (seg.size > 64) {
+ read_action_pos = -1;
+ } else if (seg.action == BsmSegment::action::GET_BINARY &&
+ seg.size % 8 != 0) {
+ read_action_pos = -1;
+ } else {
+ if ((seg.flags & BSF_LITTLE) != 0 || read_action_pos < 0 ||
+ seg.size + segs.at(read_action_pos).size > 64) {
+ BsmSegment s;
+
+ /* Create a new READ action. */
+ read_action_pos = index;
+ s.action = BsmSegment::action::READ;
+ s.size = seg.size;
+ segs.push_back(s);
+ index++;
+ } else {
+ /* Reuse previous READ action. */
+ segs.at(read_action_pos).size += seg.size;
+ }
+ switch (seg.action) {
+ case BsmSegment::action::GET_INTEGER:
+ seg.action = BsmSegment::action::EXTRACT_INTEGER;
+ break;
+ case BsmSegment::action::GET_BINARY:
+ seg.action = BsmSegment::action::EXTRACT_BINARY;
+ break;
+ default:
+ break;
+ }
+ }
+ segs.push_back(seg);
+ break;
+ case BsmSegment::action::EQ: {
+ if (read_action_pos < 0 ||
+ seg.size + segs.at(read_action_pos).size > 64) {
+ BsmSegment s;
+
+ /* Create a new READ action. */
+ read_action_pos = index;
+ s.action = BsmSegment::action::READ;
+ s.size = seg.size;
+ segs.push_back(s);
+ index++;
+ } else {
+ /* Reuse previous READ action. */
+ segs.at(read_action_pos).size += seg.size;
+ }
+ auto &prev = segs.back();
+ if (prev.action == BsmSegment::action::EQ &&
+ prev.size + seg.size <= 64) {
+ /* Coalesce with the previous EQ instruction. */
+ prev.size += seg.size;
+ prev.unit = prev.unit << seg.size | seg.unit;
+ index--;
+ } else {
+ segs.push_back(seg);
+ }
+ break;
+ }
+ case BsmSegment::action::SKIP:
+ if (read_action_pos >= 0 &&
+ seg.size + segs.at(read_action_pos).size <= 64) {
+ segs.at(read_action_pos).size += seg.size;
+ seg.action = BsmSegment::action::DROP;
+ } else {
+ read_action_pos = -1;
+ }
+ segs.push_back(seg);
+ break;
+ default:
+ read_action_pos = -1;
+ segs.push_back(seg);
+ break;
+ }
+ index++;
+ }
+
+ /* Handle a trailing test_heap instruction (for the
+ * i_bs_match_test_heap instruction). */
+ if (heap_need) {
+ BsmSegment seg;
+
+ seg.action = BsmSegment::action::TEST_HEAP;
+ seg.size = heap_need;
+ seg.live = Live;
+ segs.push_back(seg);
+ }
+ return segs;
+}
+
+UWord BeamModuleAssembler::bs_get_flags(const ArgVal &val) {
+ if (val.isNil()) {
+ return 0;
+ } else if (val.isLiteral()) {
+ Eterm term = beamfile_get_literal(beam, val.as<ArgLiteral>().get());
+ UWord flags = 0;
+
+ while (is_list(term)) {
+ Eterm *consp = list_val(term);
+ Eterm elem = CAR(consp);
+ switch (elem) {
+ case am_little:
+ case am_native:
+ flags |= BSF_LITTLE;
+ break;
+ case am_signed:
+ flags |= BSF_SIGNED;
+ break;
+ }
+ term = CDR(consp);
+ }
+ ASSERT(is_nil(term));
+ return flags;
+ } else if (val.isWord()) {
+ /* Originates from bs_get_integer2 instruction. */
+ return val.as<ArgWord>().get();
+ } else {
+ ASSERT(0); /* Should not happen. */
+ return 0;
+ }
+}
+
+void BeamModuleAssembler::emit_i_bs_match(ArgLabel const &Fail,
+ ArgRegister const &Ctx,
+ Span<ArgVal> const &List) {
+ emit_i_bs_match_test_heap(Fail, Ctx, ArgWord(0), ArgWord(0), List);
+}
+
+void BeamModuleAssembler::emit_i_bs_match_test_heap(ArgLabel const &Fail,
+ ArgRegister const &Ctx,
+ ArgWord const &Need,
+ ArgWord const &Live,
+ Span<ArgVal> const &List) {
+ const int orig_offset = offsetof(ErlBinMatchState, mb.orig);
+ const int base_offset = offsetof(ErlBinMatchState, mb.base);
+ const int position_offset = offsetof(ErlBinMatchState, mb.offset);
+ const int size_offset = offsetof(ErlBinMatchState, mb.size);
+
+ std::vector<BsmSegment> segments;
+
+ auto current = List.begin();
+ auto end = List.begin() + List.size();
+
+ while (current < end) {
+ auto cmd = current++->as<ArgImmed>().get();
+ BsmSegment seg;
+
+ switch (cmd) {
+ case am_ensure_at_least: {
+ seg.action = BsmSegment::action::ENSURE_AT_LEAST;
+ seg.size = current[0].as<ArgWord>().get();
+ seg.unit = current[1].as<ArgWord>().get();
+ current += 2;
+ break;
+ }
+ case am_ensure_exactly: {
+ seg.action = BsmSegment::action::ENSURE_EXACTLY;
+ seg.size = current[0].as<ArgWord>().get();
+ current += 1;
+ break;
+ }
+ case am_binary:
+ case am_integer: {
+ auto size = current[2].as<ArgWord>().get();
+ auto unit = current[3].as<ArgWord>().get();
+
+ switch (cmd) {
+ case am_integer:
+ seg.action = BsmSegment::action::GET_INTEGER;
+ break;
+ case am_binary:
+ seg.action = BsmSegment::action::GET_BINARY;
+ break;
+ }
+
+ seg.live = current[0];
+ seg.size = size * unit;
+ seg.unit = unit;
+ seg.flags = bs_get_flags(current[1]);
+ seg.dst = current[4].as<ArgRegister>();
+ current += 5;
+ break;
+ }
+ case am_get_tail: {
+ seg.action = BsmSegment::action::GET_TAIL;
+ seg.live = current[0].as<ArgWord>();
+ seg.dst = current[2].as<ArgRegister>();
+ current += 3;
+ break;
+ }
+ case am_skip: {
+ seg.action = BsmSegment::action::SKIP;
+ seg.size = current[0].as<ArgWord>().get();
+ seg.flags = 0;
+ current += 1;
+ break;
+ }
+ case am_Eq: {
+ seg.action = BsmSegment::action::EQ;
+ seg.live = current[0];
+ seg.size = current[1].as<ArgWord>().get();
+ seg.unit = current[2].as<ArgWord>().get();
+ current += 3;
+ break;
+ }
+ default:
+ abort();
+ break;
+ }
+ segments.push_back(seg);
+ }
+
+ segments = opt_bsm_segments(segments, Need, Live);
+
+ const arm::Gp bin_base = ARG2;
+ const arm::Gp bin_position = ARG3;
+ const arm::Gp bin_size = ARG4;
+ const arm::Gp bitdata = ARG8;
+ bool position_is_valid = false;
+
+ for (auto seg : segments) {
+ switch (seg.action) {
+ case BsmSegment::action::ENSURE_AT_LEAST: {
+ comment("ensure_at_least %ld %ld", seg.size, seg.unit);
+ auto ctx_reg = load_source(Ctx, TMP1);
+ auto stride = seg.size;
+ auto unit = seg.unit;
+
+ a.ldur(bin_position, emit_boxed_val(ctx_reg.reg, position_offset));
+ a.ldur(bin_size, emit_boxed_val(ctx_reg.reg, size_offset));
+ a.sub(TMP5, bin_size, bin_position);
+ if (stride != 0) {
+ cmp(TMP5, stride);
+ a.b_lo(resolve_beam_label(Fail, disp1MB));
+ }
+
+ if (unit != 1) {
+ if (stride % unit != 0) {
+ sub(TMP5, TMP5, stride);
+ }
+
+ if ((unit & (unit - 1)) != 0) {
+ mov_imm(TMP4, unit);
+
+ a.udiv(TMP3, TMP5, TMP4);
+ a.msub(TMP5, TMP3, TMP4, TMP5);
+
+ a.cbnz(TMP5, resolve_beam_label(Fail, disp1MB));
+ } else {
+ a.tst(TMP5, imm(unit - 1));
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
+ }
+ }
+
+ position_is_valid = true;
+ break;
+ }
+ case BsmSegment::action::ENSURE_EXACTLY: {
+ comment("ensure_exactly %ld", seg.size);
+ auto ctx_reg = load_source(Ctx, TMP1);
+ auto size = seg.size;
+
+ a.ldur(bin_position, emit_boxed_val(ctx_reg.reg, position_offset));
+ a.ldur(TMP3, emit_boxed_val(ctx_reg.reg, size_offset));
+ if (size != 0) {
+ a.sub(TMP1, TMP3, bin_position);
+ cmp(TMP1, size);
+ } else {
+ a.subs(TMP1, TMP3, bin_position);
+ }
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
+ position_is_valid = true;
+ break;
+ }
+ case BsmSegment::action::EQ: {
+ comment("=:= %ld %ld", seg.size, seg.unit);
+ if (seg.size != 0 && seg.size != 64) {
+ a.ror(bitdata, bitdata, imm(64 - seg.size));
+ }
+ if (seg.size == 64) {
+ cmp(bitdata, seg.unit);
+ } else if (seg.size == 32) {
+ cmp(bitdata.w(), seg.unit);
+ } else if (seg.unit == 0) {
+ a.tst(bitdata, imm((1ull << seg.size) - 1));
+ } else {
+ a.and_(TMP1, bitdata, imm((1ull << seg.size) - 1));
+ cmp(TMP1, seg.unit);
+ }
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
+ break;
+ }
+ case BsmSegment::action::TEST_HEAP: {
+ comment("test_heap %ld", seg.size);
+ emit_gc_test(ArgWord(0), ArgWord(seg.size), seg.live);
+ position_is_valid = false;
+ break;
+ }
+ case BsmSegment::action::READ: {
+ comment("read %ld", seg.size);
+ if (seg.size == 0) {
+ comment("(nothing to do)");
+ } else {
+ auto ctx = load_source(Ctx, ARG1);
+
+ if (!position_is_valid) {
+ a.ldur(bin_position,
+ emit_boxed_val(ctx.reg, position_offset));
+ position_is_valid = true;
+ }
+ a.ldur(bin_base, emit_boxed_val(ctx.reg, base_offset));
+
+ emit_read_bits(seg.size, bin_base, bin_position, bitdata);
+
+ a.add(bin_position, bin_position, imm(seg.size));
+ a.stur(bin_position, emit_boxed_val(ctx.reg, position_offset));
+ }
+ break;
+ }
+ case BsmSegment::action::EXTRACT_BINARY: {
+ auto bits = seg.size;
+ auto Dst = seg.dst;
+
+ comment("extract binary %ld", bits);
+ emit_extract_binary(bitdata, bits, Dst);
+ if (bits != 0 && bits != 64) {
+ a.ror(bitdata, bitdata, imm(64 - bits));
+ }
+ break;
+ }
+ case BsmSegment::action::EXTRACT_INTEGER: {
+ auto bits = seg.size;
+ auto flags = seg.flags;
+ auto Dst = seg.dst;
+
+ comment("extract integer %ld", bits);
+ if (bits != 0 && bits != 64) {
+ a.ror(bitdata, bitdata, imm(64 - bits));
+ }
+ emit_extract_integer(bitdata, flags, bits, Dst);
+ break;
+ }
+ case BsmSegment::action::GET_INTEGER: {
+ Uint live = seg.live.as<ArgWord>().get();
+ Uint flags = seg.flags;
+ auto bits = seg.size;
+ auto Dst = seg.dst;
+
+ comment("get integer %ld", bits);
+ auto ctx = load_source(Ctx, TMP1);
+
+ a.mov(ARG1, c_p);
+ a.mov(ARG2, bits);
+ a.mov(ARG3, flags);
+ lea(ARG4, emit_boxed_val(ctx.reg, offsetof(ErlBinMatchState, mb)));
+
+ if (bits >= SMALL_BITS) {
+ emit_enter_runtime<Update::eHeapOnlyAlloc>(live);
+ } else {
+ emit_enter_runtime(live);
+ }
+
+ runtime_call<4>(erts_bs_get_integer_2);
+
+ if (bits >= SMALL_BITS) {
+ emit_leave_runtime<Update::eHeapOnlyAlloc>(live);
+ } else {
+ emit_leave_runtime(live);
+ }
+
+ mov_arg(Dst, ARG1);
+
+ position_is_valid = false;
+ break;
+ }
+ case BsmSegment::action::GET_BINARY: {
+ auto Live = seg.live;
+ comment("get binary %ld", seg.size);
+ auto ctx = load_source(Ctx, TMP1);
+
+ lea(ARG1, arm::Mem(c_p, offsetof(Process, htop)));
+ a.ldur(ARG2, emit_boxed_val(ctx.reg, orig_offset));
+ a.ldur(ARG3, emit_boxed_val(ctx.reg, base_offset));
+ a.ldur(ARG4, emit_boxed_val(ctx.reg, position_offset));
+ mov_imm(ARG5, seg.size);
+ a.add(TMP2, ARG4, ARG5);
+ a.stur(TMP2, emit_boxed_val(ctx.reg, position_offset));
+
+ emit_enter_runtime<Update::eHeapOnlyAlloc>(
+ Live.as<ArgWord>().get());
+
+ runtime_call<5>(erts_extract_sub_binary);
+
+ emit_leave_runtime<Update::eHeapOnlyAlloc>(
+ Live.as<ArgWord>().get());
+
+ mov_arg(seg.dst, ARG1);
+ position_is_valid = false;
+ break;
+ }
+ case BsmSegment::action::GET_TAIL: {
+ comment("get_tail");
+
+ mov_arg(ARG1, Ctx);
+ fragment_call(ga->get_bs_get_tail_shared());
+ mov_arg(seg.dst, ARG1);
+ position_is_valid = false;
+ break;
+ }
+ case BsmSegment::action::SKIP: {
+ comment("skip %ld", seg.size);
+ auto ctx = load_source(Ctx, TMP1);
+ if (!position_is_valid) {
+ a.ldur(bin_position, emit_boxed_val(ctx.reg, position_offset));
+ position_is_valid = true;
+ }
+ add(bin_position, bin_position, seg.size);
+ a.stur(bin_position, emit_boxed_val(ctx.reg, position_offset));
+ break;
+ }
+ case BsmSegment::action::DROP:
+ auto bits = seg.size;
+ comment("drop %ld", bits);
+ if (bits != 0 && bits != 64) {
+ a.ror(bitdata, bitdata, imm(64 - bits));
+ }
+ break;
+ }
+ }
+}
diff --git a/erts/emulator/beam/jit/arm/instr_call.cpp b/erts/emulator/beam/jit/arm/instr_call.cpp
index 58bbd45a42..8f755df54b 100644
--- a/erts/emulator/beam/jit/arm/instr_call.cpp
+++ b/erts/emulator/beam/jit/arm/instr_call.cpp
@@ -59,17 +59,36 @@ void BeamModuleAssembler::emit_i_call_last(const ArgLabel &CallTarget,
emit_i_call_only(CallTarget);
}
+void BeamModuleAssembler::emit_move_call_last(const ArgYRegister &Src,
+ const ArgRegister &Dst,
+ const ArgLabel &CallTarget,
+ const ArgWord &Deallocate) {
+ auto src_index = Src.get();
+ Sint deallocate = Deallocate.get() * sizeof(Eterm);
+
+ if (src_index == 0 && Support::isInt9(deallocate)) {
+ auto dst = init_destination(Dst, TMP1);
+ const arm::Mem src_ref = arm::Mem(E).post(deallocate);
+ a.ldr(dst.reg, src_ref);
+ flush_var(dst);
+ } else {
+ mov_arg(Dst, Src);
+ emit_deallocate(Deallocate);
+ }
+ emit_i_call_only(CallTarget);
+}
+
void BeamModuleAssembler::emit_i_call_only(const ArgLabel &CallTarget) {
emit_leave_erlang_frame();
a.b(resolve_beam_label(CallTarget, disp128MB));
}
-/* Handles save_calls. When the active code index is ERTS_SAVE_CALLS_CODE_IX,
- * all remote calls will land here.
+/* Handles save_calls for remote calls. When the active code index is
+ * ERTS_SAVE_CALLS_CODE_IX, all remote calls will land here.
*
* Export entry is in ARG1, return address is in LR (x30). Both of these must
* be preserved since this runs between caller and callee. */
-void BeamGlobalAssembler::emit_dispatch_save_calls() {
+void BeamGlobalAssembler::emit_dispatch_save_calls_export() {
a.str(ARG1, TMP_MEM1q);
emit_enter_runtime_frame();
@@ -112,6 +131,25 @@ void BeamModuleAssembler::emit_i_call_ext_last(const ArgExport &Exp,
emit_i_call_ext_only(Exp);
}
+void BeamModuleAssembler::emit_move_call_ext_last(const ArgYRegister &Src,
+ const ArgRegister &Dst,
+ const ArgExport &Exp,
+ const ArgWord &Deallocate) {
+ auto src_index = Src.get();
+ Sint deallocate = Deallocate.get() * sizeof(Eterm);
+
+ if (src_index == 0 && Support::isInt9(deallocate)) {
+ auto dst = init_destination(Dst, TMP1);
+ const arm::Mem src_ref = arm::Mem(E).post(deallocate);
+ a.ldr(dst.reg, src_ref);
+ flush_var(dst);
+ } else {
+ mov_arg(Dst, Src);
+ emit_deallocate(Deallocate);
+ }
+ emit_i_call_ext_only(Exp);
+}
+
static ErtsCodeMFA apply3_mfa = {am_erlang, am_apply, 3};
arm::Mem BeamModuleAssembler::emit_variable_apply(bool includeI) {
@@ -119,7 +157,7 @@ arm::Mem BeamModuleAssembler::emit_variable_apply(bool includeI) {
a.bind(entry);
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap |
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc |
Update::eXRegs>(3);
a.mov(ARG1, c_p);
@@ -137,7 +175,7 @@ arm::Mem BeamModuleAssembler::emit_variable_apply(bool includeI) {
runtime_call<4>(apply);
/* Any number of X registers can be live at this point. */
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap |
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc |
Update::eXRegs>();
a.cbnz(ARG1, dispatch);
@@ -172,7 +210,7 @@ arm::Mem BeamModuleAssembler::emit_fixed_apply(const ArgWord &Arity,
mov_arg(ARG3, Arity);
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap |
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc |
Update::eXRegs>(Arity.get() + 2);
a.mov(ARG1, c_p);
@@ -190,7 +228,7 @@ arm::Mem BeamModuleAssembler::emit_fixed_apply(const ArgWord &Arity,
/* We will need to reload all X registers in case there has been
* an error. */
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap |
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc |
Update::eXRegs>();
a.cbnz(ARG1, dispatch);
diff --git a/erts/emulator/beam/jit/arm/instr_common.cpp b/erts/emulator/beam/jit/arm/instr_common.cpp
index 39a64f263f..e4e50712cc 100644
--- a/erts/emulator/beam/jit/arm/instr_common.cpp
+++ b/erts/emulator/beam/jit/arm/instr_common.cpp
@@ -52,6 +52,7 @@
*/
#include <algorithm>
+#include <numeric>
#include "beam_asm.hpp"
extern "C"
@@ -61,6 +62,7 @@ extern "C"
#include "beam_catches.h"
#include "beam_common.h"
#include "code_ix.h"
+#include "erl_binary.h"
}
using namespace asmjit;
@@ -73,23 +75,58 @@ void BeamModuleAssembler::emit_error(int reason) {
emit_raise_exception();
}
+void BeamModuleAssembler::emit_error(int reason, const ArgSource &Src) {
+ auto src = load_source(Src, TMP2);
+
+ ERTS_CT_ASSERT_FIELD_PAIR(Process, freason, fvalue);
+ mov_imm(TMP1, reason);
+ a.stp(TMP1, src.reg, arm::Mem(c_p, offsetof(Process, freason)));
+ emit_raise_exception();
+}
+
void BeamModuleAssembler::emit_gc_test_preserve(const ArgWord &Need,
const ArgWord &Live,
- arm::Gp term) {
+ const ArgSource &Preserve,
+ arm::Gp preserve_reg) {
const int32_t bytes_needed = (Need.get() + S_RESERVED) * sizeof(Eterm);
Label after_gc_check = a.newLabel();
+#ifdef DEBUG
+ comment("(debug: fill dead X registers with garbage)");
+ const arm::Gp garbage_reg = preserve_reg == ARG4 ? ARG3 : ARG4;
+ mov_imm(garbage_reg, ERTS_HOLE_MARKER);
+ if (!(Preserve.isXRegister() &&
+ Preserve.as<ArgXRegister>().get() >= Live.get())) {
+ mov_arg(ArgXRegister(Live.get()), garbage_reg);
+ mov_arg(ArgXRegister(Live.get() + 1), garbage_reg);
+ } else {
+ mov_imm(garbage_reg, ERTS_HOLE_MARKER);
+ mov_arg(ArgXRegister(Live.get() + 1), garbage_reg);
+ mov_arg(ArgXRegister(Live.get() + 2), garbage_reg);
+ }
+#endif
+
add(ARG3, HTOP, bytes_needed);
a.cmp(ARG3, E);
a.b_ls(after_gc_check);
ASSERT(Live.get() < ERTS_X_REGS_ALLOCATED);
- mov_arg(ArgVal(ArgVal::XReg, Live.get()), term);
- mov_imm(ARG4, Live.get() + 1);
- fragment_call(ga->get_garbage_collect());
+ /* We don't need to stash the preserved term if it's currently live, making
+ * the code slightly shorter. */
+ if (!(Preserve.isXRegister() &&
+ Preserve.as<ArgXRegister>().get() >= Live.get())) {
+ mov_imm(ARG4, Live.get());
+ fragment_call(ga->get_garbage_collect());
+ mov_arg(preserve_reg, Preserve);
+ } else {
+ mov_arg(ArgXRegister(Live.get()), preserve_reg);
+
+ mov_imm(ARG4, Live.get() + 1);
+ fragment_call(ga->get_garbage_collect());
- mov_arg(term, ArgVal(ArgVal::XReg, Live.get()));
+ mov_arg(preserve_reg, ArgXRegister(Live.get()));
+ }
a.bind(after_gc_check);
}
@@ -100,6 +137,13 @@ void BeamModuleAssembler::emit_gc_test(const ArgWord &Ns,
int32_t bytes_needed = (Ns.get() + Nh.get() + S_RESERVED) * sizeof(Eterm);
Label after_gc_check = a.newLabel();
+#ifdef DEBUG
+ comment("(debug: fill dead X registers with garbage)");
+ mov_imm(ARG4, ERTS_HOLE_MARKER);
+ mov_arg(ArgXRegister(Live.get()), ARG4);
+ mov_arg(ArgXRegister(Live.get() + 1), ARG4);
+#endif
+
add(ARG3, HTOP, bytes_needed);
a.cmp(ARG3, E);
a.b_ls(after_gc_check);
@@ -190,7 +234,7 @@ void BeamModuleAssembler::emit_normal_exit() {
/* This is implicitly global; it does not normally appear in modules and
* doesn't require size optimization. */
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
emit_proc_lc_unrequire();
@@ -202,7 +246,7 @@ void BeamModuleAssembler::emit_normal_exit() {
runtime_call<2>(erts_do_exit_process);
emit_proc_lc_require();
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
a.b(resolve_fragment(ga->get_do_schedule(), disp128MB));
@@ -212,14 +256,14 @@ void BeamModuleAssembler::emit_continue_exit() {
/* This is implicitly global; it does not normally appear in modules and
* doesn't require size optimization. */
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>(0);
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>(0);
emit_proc_lc_unrequire();
a.mov(ARG1, c_p);
runtime_call<1>(erts_continue_exit_process);
emit_proc_lc_require();
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>(0);
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>(0);
a.b(resolve_fragment(ga->get_do_schedule(), disp128MB));
}
@@ -336,6 +380,22 @@ void BeamModuleAssembler::emit_i_get_tuple_element(const ArgSource &Src,
flush_var(dst);
}
+void BeamModuleAssembler::emit_get_tuple_element_swap(
+ const ArgSource &Src,
+ const ArgWord &Element,
+ const ArgRegister &Dst,
+ const ArgRegister &OtherDst) {
+#ifdef DEBUG
+ emit_tuple_assertion(Src, ARG1);
+#endif
+
+ mov_arg(Dst, OtherDst);
+
+ auto dst = init_destination(OtherDst, TMP1);
+ safe_ldr(dst.reg, arm::Mem(ARG1, Element.get()));
+ flush_var(dst);
+}
+
/* Fetch two consecutive tuple elements from the tuple pointed to by
* the boxed pointer in ARG1. */
void BeamModuleAssembler::emit_get_two_tuple_elements(const ArgSource &Src,
@@ -418,12 +478,58 @@ void BeamModuleAssembler::emit_i_move(const ArgSource &Src,
mov_arg(Dst, Src);
}
+void BeamModuleAssembler::emit_move_trim(const ArgSource &Src,
+ const ArgRegister &Dst,
+ const ArgWord &Words) {
+ Sint trim = Words.get() * sizeof(Eterm);
+ ASSERT(Words.get() <= 1023);
+
+ if (Src.isYRegister()) {
+ auto src_index = Src.as<ArgYRegister>().get();
+ if (src_index == 0 && Support::isInt9(trim)) {
+ const arm::Mem src_ref = arm::Mem(E).post(trim);
+ if (Dst.isXRegister()) {
+ auto dst = init_destination(Dst, TMP1);
+ a.ldr(dst.reg, src_ref);
+ flush_var(dst);
+ } else {
+ auto dst_index = Dst.as<ArgYRegister>().get() - Words.get();
+ auto dst = init_destination(ArgYRegister(dst_index), TMP1);
+ a.ldr(dst.reg, src_ref);
+ flush_var(dst);
+ }
+
+ return;
+ }
+ }
+
+ if (Dst.isYRegister()) {
+ auto dst_index = Dst.as<ArgYRegister>().get();
+ if (dst_index == Words.get() && Support::isInt9(trim)) {
+ auto src = load_source(Src, TMP1);
+ const arm::Mem dst_ref = arm::Mem(E, trim).pre();
+ a.str(src.reg, dst_ref);
+
+ return;
+ }
+ }
+
+ /* Fallback. */
+ mov_arg(Dst, Src);
+ if (Words.get() > 0) {
+ add(E, E, trim);
+ }
+}
+
void BeamModuleAssembler::emit_store_two_xregs(const ArgXRegister &Src1,
const ArgYRegister &Dst1,
const ArgXRegister &Src2,
const ArgYRegister &Dst2) {
auto [src1, src2] = load_sources(Src1, TMP1, Src2, TMP2);
- safe_stp(src1.reg, src2.reg, Dst1, Dst2);
+ auto dst1 = init_destination(Dst1, src1.reg);
+ auto dst2 = init_destination(Dst2, src2.reg);
+
+ flush_vars(dst1, dst2);
}
void BeamModuleAssembler::emit_load_two_xregs(const ArgYRegister &Src1,
@@ -471,22 +577,11 @@ void BeamModuleAssembler::emit_swap(const ArgRegister &R1,
} else if (isRegisterBacked(R2)) {
return emit_swap(R2, R1);
} else {
- switch (ArgVal::memory_relation(R1, R2)) {
- case ArgVal::Relation::consecutive:
- safe_ldp(TMP1, TMP2, R1, R2);
- safe_stp(TMP2, TMP1, R1, R2);
- break;
- case ArgVal::Relation::reverse_consecutive:
- safe_ldp(TMP1, TMP2, R2, R1);
- safe_stp(TMP2, TMP1, R2, R1);
- break;
- case ArgVal::Relation::none:
- a.ldr(TMP1, getArgRef(R1));
- a.ldr(TMP2, getArgRef(R2));
- a.str(TMP1, getArgRef(R2));
- a.str(TMP2, getArgRef(R1));
- break;
- }
+ /* Both BEAM registers are stored in memory. */
+ auto [r1, r2] = load_sources(R1, TMP1, R2, TMP2);
+ auto dst1 = init_destination(R2, r1.reg);
+ auto dst2 = init_destination(R1, r2.reg);
+ flush_vars(dst1, dst2);
}
}
@@ -629,6 +724,157 @@ void BeamModuleAssembler::emit_self(const ArgRegister &Dst) {
mov_arg(Dst, arm::Mem(c_p, offsetof(Process, common.id)));
}
+void BeamModuleAssembler::emit_copy_words_increment(arm::Gp from,
+ arm::Gp to,
+ size_t count) {
+ check_pending_stubs();
+
+ /* Copy the words inline if we can, otherwise use a loop with the largest
+ * vector size we're capable of. */
+ if (count <= 16) {
+ while (count >= 4) {
+ a.ldp(a64::q30, a64::q31, arm::Mem(from).post(sizeof(UWord[4])));
+ a.stp(a64::q30, a64::q31, arm::Mem(to).post(sizeof(UWord[4])));
+ count -= 4;
+ }
+ } else {
+ Label copy_next = a.newLabel();
+
+ ASSERT(Support::isUInt16(count / 4));
+ mov_imm(SUPER_TMP, count / 4);
+ a.bind(copy_next);
+ {
+ a.ldp(a64::q30, a64::q31, arm::Mem(from).post(sizeof(UWord[4])));
+ a.stp(a64::q30, a64::q31, arm::Mem(to).post(sizeof(UWord[4])));
+ a.subs(SUPER_TMP, SUPER_TMP, imm(1));
+ a.b_ne(copy_next);
+ }
+
+ count = count % 4;
+ }
+
+ if (count >= 2) {
+ a.ldr(a64::q30, arm::Mem(from).post(sizeof(UWord[2])));
+ a.str(a64::q30, arm::Mem(to).post(sizeof(UWord[2])));
+ count -= 2;
+ }
+
+ if (count == 1) {
+ a.ldr(SUPER_TMP, arm::Mem(from).post(sizeof(UWord)));
+ a.str(SUPER_TMP, arm::Mem(to).post(sizeof(UWord)));
+ count -= 1;
+ }
+
+ ASSERT(count == 0);
+ (void)count;
+}
+
+void BeamModuleAssembler::emit_update_record(const ArgAtom &Hint,
+ const ArgWord &TupleSize,
+ const ArgSource &Src,
+ const ArgRegister &Dst,
+ const ArgWord &UpdateCount,
+ const Span<ArgVal> &updates) {
+ const size_t size_on_heap = TupleSize.get() + 1;
+ Label next = a.newLabel();
+
+ ASSERT(UpdateCount.get() == updates.size());
+ ASSERT((UpdateCount.get() % 2) == 0);
+
+ ASSERT(size_on_heap > 2);
+
+ auto destination = init_destination(Dst, ARG1);
+ auto src = load_source(Src, ARG2);
+
+ arm::Gp untagged_src = ARG3;
+ emit_untag_ptr(untagged_src, src.reg);
+
+ /* Setting a field to the same value is pretty common, so we'll check for
+ * that since it's vastly cheaper than copying if we're right, and doesn't
+ * cost much if we're wrong. */
+ if (Hint.get() == am_reuse && updates.size() == 2) {
+ const auto next_index = updates[0].as<ArgWord>().get();
+ const auto &next_value = updates[1].as<ArgSource>();
+
+ safe_ldr(TMP1, arm::Mem(untagged_src, next_index * sizeof(Eterm)));
+ cmp_arg(TMP1, next_value);
+
+ if (destination.reg != src.reg) {
+ a.csel(destination.reg,
+ destination.reg,
+ src.reg,
+ imm(arm::CondCode::kNE));
+ }
+ a.b_eq(next);
+ }
+
+ size_t copy_index = 0;
+
+ for (size_t i = 0; i < updates.size(); i += 2) {
+ const auto next_index = updates[i].as<ArgWord>().get();
+ const auto &next_value = updates[i + 1].as<ArgSource>();
+ bool odd_copy;
+
+ ASSERT(next_index > 0 && next_index >= copy_index);
+
+ /* If we need to copy an odd number of elements, we'll do the last one
+ * ourselves to save us from having to increment `untagged_src`
+ * separately. */
+ odd_copy = (next_index - copy_index) & 1;
+ emit_copy_words_increment(untagged_src,
+ HTOP,
+ (next_index - copy_index) & ~1);
+
+ if ((i + 2) < updates.size()) {
+ const auto adjacent_index = updates[i + 2].as<ArgWord>().get();
+ const auto &adjacent_value = updates[i + 3].as<ArgSource>();
+
+ if (adjacent_index == next_index + 1) {
+ auto [first, second] =
+ load_sources(next_value, TMP1, adjacent_value, TMP2);
+
+ if (odd_copy) {
+ a.ldr(TMP3, arm::Mem(untagged_src).post(sizeof(Eterm[3])));
+ a.stp(TMP3,
+ first.reg,
+ arm::Mem(HTOP).post(sizeof(Eterm[2])));
+ a.str(second.reg, arm::Mem(HTOP).post(sizeof(Eterm)));
+ } else {
+ a.add(untagged_src, untagged_src, imm(sizeof(Eterm[2])));
+ a.stp(first.reg,
+ second.reg,
+ arm::Mem(HTOP).post(sizeof(Eterm[2])));
+ }
+
+ copy_index = next_index + 2;
+ i += 2;
+ continue;
+ }
+ }
+
+ auto value = load_source(next_value, TMP1);
+
+ if ((next_index - copy_index) & 1) {
+ a.ldr(TMP2, arm::Mem(untagged_src).post(sizeof(Eterm[2])));
+ a.stp(TMP2, value.reg, arm::Mem(HTOP).post(sizeof(Eterm[2])));
+ } else {
+ a.add(untagged_src, untagged_src, imm(sizeof(Eterm)));
+ a.str(value.reg, arm::Mem(HTOP).post(sizeof(Eterm)));
+ }
+
+ copy_index = next_index + 1;
+ }
+
+ emit_copy_words_increment(untagged_src, HTOP, size_on_heap - copy_index);
+
+ sub(destination.reg,
+ HTOP,
+ (size_on_heap * sizeof(Eterm)) - TAG_PRIMARY_BOXED);
+
+ a.bind(next);
+ flush_var(destination);
+}
+
void BeamModuleAssembler::emit_set_tuple_element(const ArgSource &Element,
const ArgRegister &Tuple,
const ArgWord &Offset) {
@@ -670,65 +916,76 @@ void BeamModuleAssembler::emit_is_boolean(const ArgLabel &Fail,
ERTS_CT_ASSERT(am_true == make_atom(1));
auto src = load_source(Src, TMP1);
- a.and_(TMP1, src.reg, imm(~(am_true & ~_TAG_IMMED1_MASK)));
+ a.and_(TMP1, src.reg, imm(~(am_true & ~_TAG_IMMED2_MASK)));
a.cmp(TMP1, imm(am_false));
a.b_ne(resolve_beam_label(Fail, disp1MB));
}
-arm::Gp BeamModuleAssembler::emit_is_binary(const ArgLabel &Fail,
- const ArgSource &Src,
- Label next,
- Label subbin) {
+void BeamModuleAssembler::emit_is_binary(const ArgLabel &Fail,
+ const ArgSource &Src) {
+ Label is_binary = a.newLabel(), next = a.newLabel();
+
auto src = load_source(Src, ARG1);
emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, src.reg);
arm::Gp boxed_ptr = emit_ptr_val(ARG1, src.reg);
a.ldur(TMP1, emit_boxed_val(boxed_ptr));
- a.and_(TMP1, TMP1, imm(_TAG_HEADER_MASK));
- a.cmp(TMP1, imm(_TAG_HEADER_SUB_BIN));
- a.b_eq(subbin);
-
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_BITSTRING) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Bitstring) {
+ const int bit_number = 3;
+ ERTS_CT_ASSERT((_TAG_HEADER_SUB_BIN & (1 << bit_number)) != 0 &&
+ (_TAG_HEADER_REFC_BIN & (1 << bit_number)) == 0 &&
+ (_TAG_HEADER_HEAP_BIN & (1 << bit_number)) == 0);
comment("simplified binary test since source is always a bitstring "
"when boxed");
+ a.tbz(TMP1, imm(bit_number), next);
} else {
+ a.and_(TMP1, TMP1, imm(_TAG_HEADER_MASK));
+ a.cmp(TMP1, imm(_TAG_HEADER_SUB_BIN));
+ a.b_ne(is_binary);
+ }
+
+ /* This is a sub binary. */
+ a.ldrb(TMP1.w(), emit_boxed_val(boxed_ptr, offsetof(ErlSubBin, bitsize)));
+ a.cbnz(TMP1, resolve_beam_label(Fail, disp1MB));
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) != BeamTypeId::Bitstring) {
+ a.b(next);
+ }
+
+ a.bind(is_binary);
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) != BeamTypeId::Bitstring) {
ERTS_CT_ASSERT(_TAG_HEADER_REFC_BIN + 4 == _TAG_HEADER_HEAP_BIN);
a.and_(TMP1, TMP1, imm(~4));
a.cmp(TMP1, imm(_TAG_HEADER_REFC_BIN));
a.b_ne(resolve_beam_label(Fail, disp1MB));
}
- a.b(next);
-
- return boxed_ptr;
-}
-
-void BeamModuleAssembler::emit_is_binary(const ArgLabel &Fail,
- const ArgSource &Src) {
- Label next = a.newLabel(), subbin = a.newLabel();
-
- arm::Gp boxed_ptr = emit_is_binary(Fail, Src, next, subbin);
-
- a.bind(subbin);
- {
- /* emit_is_binary() has already removed the literal tag (if
- * applicable) from the copy of Src. */
- a.ldrb(TMP1.w(),
- emit_boxed_val(boxed_ptr, offsetof(ErlSubBin, bitsize)));
- a.cbnz(TMP1, resolve_beam_label(Fail, disp1MB));
- }
-
a.bind(next);
}
void BeamModuleAssembler::emit_is_bitstring(const ArgLabel &Fail,
const ArgSource &Src) {
- Label next = a.newLabel();
+ auto src = load_source(Src, ARG1);
+
+ emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, src.reg);
- (void)emit_is_binary(Fail, Src, next, next);
+ arm::Gp boxed_ptr = emit_ptr_val(ARG1, src.reg);
+ a.ldur(TMP1, emit_boxed_val(boxed_ptr));
- a.bind(next);
+ /* The header mask with the binary sub tag bits removed (0b110011)
+ * is not possible to use as an immediate operand for 'and'. (See
+ * the note at the beginning of the file.) Therefore, use a
+ * simpler mask (0b110000) that will also clear the primary tag
+ * bits. That works because we KNOW that a boxed pointer always
+ * points to a header word and that the primary tag for a header
+ * is 0.
+ */
+ const auto mask = _HEADER_SUBTAG_MASK - _BINARY_XXX_MASK;
+ ERTS_CT_ASSERT(TAG_PRIMARY_HEADER == 0);
+ ERTS_CT_ASSERT(_TAG_HEADER_REFC_BIN == (_TAG_HEADER_REFC_BIN & mask));
+ a.and_(TMP1, TMP1, imm(mask));
+ a.cmp(TMP1, imm(_TAG_HEADER_REFC_BIN));
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
}
void BeamModuleAssembler::emit_is_float(const ArgLabel &Fail,
@@ -737,7 +994,7 @@ void BeamModuleAssembler::emit_is_float(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, src.reg);
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_FLOAT) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Float) {
comment("skipped header test since we know it's a float when boxed");
} else {
arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
@@ -754,7 +1011,7 @@ void BeamModuleAssembler::emit_is_function(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, src.reg);
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_FUN) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Fun) {
comment("skipped header test since we know it's a fun when boxed");
} else {
arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
@@ -801,7 +1058,7 @@ void BeamModuleAssembler::emit_is_function2(const ArgLabel &Fail,
arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_FUN) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Fun) {
comment("skipped header test since we know it's a fun when boxed");
} else {
a.ldur(TMP2, emit_boxed_val(boxed_ptr));
@@ -828,7 +1085,7 @@ void BeamModuleAssembler::emit_is_integer(const ArgLabel &Fail,
Label next = a.newLabel();
- if (always_one_of(Src, BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(Src)) {
comment("simplified small test since all other types are boxed");
emit_is_boxed(next, Src, src.reg);
} else {
@@ -839,19 +1096,24 @@ void BeamModuleAssembler::emit_is_integer(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, TMP2);
}
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_INTEGER) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Integer) {
comment("skipped header test since we know it's a bignum when "
"boxed");
} else {
arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
a.ldur(TMP1, emit_boxed_val(boxed_ptr));
- /* The following value (0b111011) is not possible to use as
- * an immediate operand for 'and'. See the note at the beginning
- * of the file.
+ /* The header mask with the sign bit removed (0b111011) is not
+ * possible to use as an immediate operand for 'and'. (See the
+ * note at the beginning of the file.) Therefore, use a
+ * simpler mask (0b111000) that will also clear the primary
+ * tag bits. That works because we KNOW that a boxed pointer
+ * always points to a header word and that the primary tag for
+ * a header is 0.
*/
- mov_imm(TMP2, _TAG_HEADER_MASK - _BIG_SIGN_BIT);
- a.and_(TMP1, TMP1, TMP2);
+ auto mask = _HEADER_SUBTAG_MASK - _BIG_SIGN_BIT;
+ ERTS_CT_ASSERT(TAG_PRIMARY_HEADER == 0);
+ a.and_(TMP1, TMP1, imm(mask));
a.cmp(TMP1, imm(_TAG_HEADER_POS_BIG));
a.b_ne(resolve_beam_label(Fail, disp1MB));
}
@@ -877,7 +1139,7 @@ void BeamModuleAssembler::emit_is_map(const ArgLabel &Fail,
/* As an optimization for the `error | #{}` case, skip checking the header
* word when we know that the only possible boxed type is a map. */
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_MAP) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Map) {
comment("skipped header test since we know it's a map when boxed");
} else {
arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
@@ -891,8 +1153,17 @@ void BeamModuleAssembler::emit_is_map(const ArgLabel &Fail,
void BeamModuleAssembler::emit_is_nil(const ArgLabel &Fail,
const ArgRegister &Src) {
auto src = load_source(Src, TMP1);
- a.cmp(src.reg, imm(NIL));
- a.b_ne(resolve_beam_label(Fail, disp1MB));
+
+ if (always_one_of<BeamTypeId::List>(Src)) {
+ const int bitNumber = 1;
+ ERTS_CT_ASSERT(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST ==
+ (1 << bitNumber));
+ comment("simplified is_nil test because its argument is always a list");
+ a.tbz(src.reg, imm(bitNumber), resolve_beam_label(Fail, disp32K));
+ } else {
+ a.cmp(src.reg, imm(NIL));
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
+ }
}
void BeamModuleAssembler::emit_is_number(const ArgLabel &Fail,
@@ -900,7 +1171,7 @@ void BeamModuleAssembler::emit_is_number(const ArgLabel &Fail,
auto src = load_source(Src, TMP1);
Label next = a.newLabel();
- if (always_one_of(Src, BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(Src)) {
comment("simplified small test since all other types are boxed");
emit_is_boxed(next, Src, src.reg);
} else {
@@ -911,19 +1182,23 @@ void BeamModuleAssembler::emit_is_number(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, src.reg);
}
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) ==
- (BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Number) {
comment("skipped header test since we know it's a number when boxed");
} else {
arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
a.ldur(TMP1, emit_boxed_val(boxed_ptr));
- /* The following value (0b111011) is not possible to use as
- * an immediate operand for 'and'. See the note at the beginning
- * of the file.
+ /* The header mask with the sign bit removed (0b111011) is not
+ * possible to use as an immediate operand for 'and'. (See the
+ * note at the beginning of the file.) Therefore, use a
+ * simpler mask (0b111000) that will also clear the primary
+ * tag bits. That works because we KNOW that a boxed pointer
+ * always points to a header word and that the primary tag for
+ * a header is 0.
*/
- mov_imm(TMP2, _TAG_HEADER_MASK - _BIG_SIGN_BIT);
- a.and_(TMP2, TMP1, TMP2);
+ auto mask = _HEADER_SUBTAG_MASK - _BIG_SIGN_BIT;
+ ERTS_CT_ASSERT(TAG_PRIMARY_HEADER == 0);
+ a.and_(TMP2, TMP1, imm(mask));
a.cmp(TMP2, imm(_TAG_HEADER_POS_BIG));
a.mov(TMP3, imm(HEADER_FLONUM));
@@ -939,7 +1214,7 @@ void BeamModuleAssembler::emit_is_pid(const ArgLabel &Fail,
auto src = load_source(Src, TMP1);
Label next = a.newLabel();
- if (always_one_of(Src, BEAM_TYPE_PID | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::Pid, BeamTypeId::AlwaysBoxed>(Src)) {
comment("simplified local pid test since all other types are boxed");
emit_is_boxed(next, Src, src.reg);
} else {
@@ -951,7 +1226,7 @@ void BeamModuleAssembler::emit_is_pid(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, TMP2);
}
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_PID) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Pid) {
comment("skipped header test since we know it's a pid when boxed");
} else {
arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
@@ -969,7 +1244,7 @@ void BeamModuleAssembler::emit_is_port(const ArgLabel &Fail,
auto src = load_source(Src, TMP1);
Label next = a.newLabel();
- if (always_one_of(Src, BEAM_TYPE_PORT | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::Port, BeamTypeId::AlwaysBoxed>(Src)) {
comment("simplified local port test since all other types are boxed");
emit_is_boxed(next, Src, src.reg);
} else {
@@ -981,7 +1256,7 @@ void BeamModuleAssembler::emit_is_port(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, TMP2);
}
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_PORT) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Port) {
comment("skipped header test since we know it's a port when boxed");
} else {
arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
@@ -1000,7 +1275,7 @@ void BeamModuleAssembler::emit_is_reference(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, src.reg);
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_REFERENCE) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Reference) {
comment("skipped header test since we know it's a ref when boxed");
} else {
arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
@@ -1095,7 +1370,7 @@ void BeamModuleAssembler::emit_i_is_tuple(const ArgLabel &Fail,
/* As an optimization for the `error | {ok, Value}` case, skip checking the
* header word when we know that the only possible boxed type is a tuple. */
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_TUPLE) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Tuple) {
comment("skipped header test since we know it's a tuple when boxed");
} else {
a.ldr(TMP1, arm::Mem(ARG1));
@@ -1139,6 +1414,43 @@ void BeamModuleAssembler::emit_is_eq_exact(const ArgLabel &Fail,
const ArgSource &Y) {
auto x = load_source(X, ARG1);
+ bool is_empty_binary = false;
+ if (exact_type<BeamTypeId::Bitstring>(X) && Y.isLiteral()) {
+ Eterm literal = beamfile_get_literal(beam, Y.as<ArgLiteral>().get());
+ is_empty_binary = is_binary(literal) && binary_size(literal) == 0;
+ }
+
+ if (is_empty_binary) {
+ auto unit = getSizeUnit(X);
+
+ comment("simplified equality test with empty binary");
+ if (unit != 0 && std::gcd(unit, 8) == 8) {
+ arm::Gp boxed_ptr = emit_ptr_val(ARG1, x.reg);
+ a.ldur(TMP1, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ a.cbnz(TMP1, resolve_beam_label(Fail, disp1MB));
+ } else {
+ Label next = a.newLabel();
+
+ emit_untag_ptr(ARG1, x.reg);
+ ERTS_CT_ASSERT_FIELD_PAIR(ErlHeapBin, thing_word, size);
+ a.ldp(TMP1, TMP2, arm::Mem(ARG1));
+ a.cbnz(TMP2, resolve_beam_label(Fail, disp1MB));
+
+ const int bit_number = 3;
+ ERTS_CT_ASSERT((_TAG_HEADER_SUB_BIN & (1 << bit_number)) != 0 &&
+ (_TAG_HEADER_REFC_BIN & (1 << bit_number)) == 0 &&
+ (_TAG_HEADER_HEAP_BIN & (1 << bit_number)) == 0);
+ a.tbz(TMP1, imm(bit_number), next);
+
+ a.ldrb(TMP1.w(), arm::Mem(ARG1, offsetof(ErlSubBin, bitsize)));
+ a.cbnz(TMP1, resolve_beam_label(Fail, disp1MB));
+
+ a.bind(next);
+ }
+
+ return;
+ }
+
/* If either argument is known to be an immediate, we can fail immediately
* if they're not equal. */
if (always_immediate(X) || always_immediate(Y)) {
@@ -1152,7 +1464,7 @@ void BeamModuleAssembler::emit_is_eq_exact(const ArgLabel &Fail,
return;
}
- /* Both operands are registers. */
+ /* Both operands are registers or literals. */
Label next = a.newLabel();
auto y = load_source(Y, ARG2);
@@ -1161,9 +1473,16 @@ void BeamModuleAssembler::emit_is_eq_exact(const ArgLabel &Fail,
if (always_same_types(X, Y)) {
comment("skipped tag test since they are always equal");
+ } else if (Y.isLiteral()) {
+ /* Fail immediately unless X is the same type of pointer as
+ * the literal Y.
+ */
+ Eterm literal = beamfile_get_literal(beam, Y.as<ArgLiteral>().get());
+ Uint tag_test = _TAG_PRIMARY_MASK - (literal & _TAG_PRIMARY_MASK);
+ int bitNumber = Support::ctz<Eterm>(tag_test);
+ a.tbnz(x.reg, imm(bitNumber), resolve_beam_label(Fail, disp32K));
} else {
- /* The terms could still be equal if both operands are pointers
- * having the same tag. */
+ /* Fail immediately if the pointer tags are not equal. */
emit_is_unequal_based_on_tags(x.reg, y.reg);
a.b_eq(resolve_beam_label(Fail, disp1MB));
}
@@ -1174,9 +1493,7 @@ void BeamModuleAssembler::emit_is_eq_exact(const ArgLabel &Fail,
mov_var(ARG2, y);
emit_enter_runtime();
-
runtime_call<2>(eq);
-
emit_leave_runtime();
a.cbz(ARG1, resolve_beam_label(Fail, disp1MB));
@@ -1189,6 +1506,26 @@ void BeamModuleAssembler::emit_is_ne_exact(const ArgLabel &Fail,
const ArgSource &Y) {
auto x = load_source(X, ARG1);
+ bool is_empty_binary = false;
+ if (exact_type<BeamTypeId::Bitstring>(X) && Y.isLiteral()) {
+ auto unit = getSizeUnit(X);
+ if (unit != 0 && std::gcd(unit, 8) == 8) {
+ Eterm literal =
+ beamfile_get_literal(beam, Y.as<ArgLiteral>().get());
+ is_empty_binary = is_binary(literal) && binary_size(literal) == 0;
+ }
+ }
+
+ if (is_empty_binary) {
+ arm::Gp boxed_ptr = emit_ptr_val(ARG1, x.reg);
+
+ comment("simplified non-equality test with empty binary");
+ a.ldur(TMP1, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ a.cbz(TMP1, resolve_beam_label(Fail, disp1MB));
+
+ return;
+ }
+
/* If either argument is known to be an immediate, we can fail immediately
* if they're equal. */
if (always_immediate(X) || always_immediate(Y)) {
@@ -1202,7 +1539,7 @@ void BeamModuleAssembler::emit_is_ne_exact(const ArgLabel &Fail,
return;
}
- /* Both operands are registers. */
+ /* Both operands are registers or literals. */
Label next = a.newLabel();
auto y = load_source(Y, ARG2);
@@ -1211,6 +1548,14 @@ void BeamModuleAssembler::emit_is_ne_exact(const ArgLabel &Fail,
if (always_same_types(X, Y)) {
comment("skipped tag test since they are always equal");
+ } else if (Y.isLiteral()) {
+ /* Succeed immediately if X is not the same type of pointer as
+ * the literal Y.
+ */
+ Eterm literal = beamfile_get_literal(beam, Y.as<ArgLiteral>().get());
+ Uint tag_test = _TAG_PRIMARY_MASK - (literal & _TAG_PRIMARY_MASK);
+ int bitNumber = Support::ctz<Eterm>(tag_test);
+ a.tbnz(x.reg, imm(bitNumber), next);
} else {
/* Test whether the terms are definitely unequal based on the tags
* alone. */
@@ -1355,27 +1700,64 @@ void BeamGlobalAssembler::emit_arith_compare_shared() {
void BeamModuleAssembler::emit_is_lt(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS) {
- mov_arg(ARG1, LHS);
- mov_arg(ARG2, RHS);
+ auto [lhs, rhs] = load_sources(LHS, ARG1, RHS, ARG2);
if (always_small(LHS) && always_small(RHS)) {
comment("skipped test for small operands since they are always small");
- a.cmp(ARG1, ARG2);
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_ge(resolve_beam_label(Fail, disp1MB));
+ } else if (always_small(LHS) && exact_type<BeamTypeId::Integer>(RHS) &&
+ hasLowerBound(RHS)) {
+ Label next = a.newLabel();
+ comment("simplified test because it always succeeds when RHS is a "
+ "bignum");
+ emit_is_not_boxed(next, rhs.reg);
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_ge(resolve_beam_label(Fail, disp1MB));
+ a.bind(next);
+ } else if (always_small(LHS) && exact_type<BeamTypeId::Integer>(RHS) &&
+ hasUpperBound(RHS)) {
+ comment("simplified test because it always fails when RHS is a bignum");
+ emit_is_not_boxed(resolve_beam_label(Fail, dispUnknown), rhs.reg);
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_ge(resolve_beam_label(Fail, disp1MB));
+ } else if (exact_type<BeamTypeId::Integer>(LHS) && hasLowerBound(LHS) &&
+ always_small(RHS)) {
+ comment("simplified test because it always fails when LHS is a bignum");
+ emit_is_not_boxed(resolve_beam_label(Fail, dispUnknown), lhs.reg);
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_ge(resolve_beam_label(Fail, disp1MB));
+ } else if (exact_type<BeamTypeId::Integer>(LHS) && hasUpperBound(LHS) &&
+ always_small(RHS)) {
+ Label next = a.newLabel();
+ comment("simplified test because it always succeeds when LHS is a "
+ "bignum");
+ emit_is_not_boxed(next, rhs.reg);
+ a.cmp(lhs.reg, rhs.reg);
a.b_ge(resolve_beam_label(Fail, disp1MB));
- } else if (always_one_of(LHS,
- BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED) &&
- always_one_of(RHS,
- BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ a.bind(next);
+ } else if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ RHS)) {
Label branch_compare = a.newLabel();
- a.cmp(ARG1, ARG2);
+ a.cmp(lhs.reg, rhs.reg);
/* The only possible kind of immediate is a small and all other values
* are boxed, so we can test for smalls by testing boxed. */
comment("simplified small test since all other types are boxed");
- ERTS_CT_ASSERT(_TAG_PRIMARY_MASK - TAG_PRIMARY_BOXED == (1 << 0));
- a.and_(TMP1, ARG1, ARG2);
- a.tbnz(TMP1, imm(0), branch_compare);
+ if (always_small(LHS)) {
+ emit_is_boxed(branch_compare, rhs.reg);
+ } else if (always_small(RHS)) {
+ emit_is_boxed(branch_compare, lhs.reg);
+ } else {
+ a.and_(TMP1, lhs.reg, rhs.reg);
+ emit_is_boxed(branch_compare, TMP1);
+ }
+
+ mov_var(ARG1, lhs);
+ mov_var(ARG2, rhs);
fragment_call(ga->get_arith_compare_shared());
/* The flags will either be from the initial comparison, or from the
@@ -1387,23 +1769,29 @@ void BeamModuleAssembler::emit_is_lt(const ArgLabel &Fail,
/* Relative comparisons are overwhelmingly likely to be used on smalls,
* so we'll specialize those and keep the rest in a shared fragment. */
- if (RHS.isSmall()) {
- a.and_(TMP1, ARG1, imm(_TAG_IMMED1_MASK));
- } else if (LHS.isSmall()) {
- a.and_(TMP1, ARG2, imm(_TAG_IMMED1_MASK));
+ if (always_small(RHS)) {
+ a.and_(TMP1, lhs.reg, imm(_TAG_IMMED1_MASK));
+ } else if (always_small(LHS)) {
+ a.and_(TMP1, rhs.reg, imm(_TAG_IMMED1_MASK));
} else {
+ /* Avoid the expensive generic comparison for equal terms. */
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_eq(next);
+
ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
- a.and_(TMP1, ARG1, ARG2);
+ a.and_(TMP1, lhs.reg, rhs.reg);
a.and_(TMP1, TMP1, imm(_TAG_IMMED1_MASK));
}
a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
a.b_ne(generic);
- a.cmp(ARG1, ARG2);
+ a.cmp(lhs.reg, rhs.reg);
a.b(next);
a.bind(generic);
+ mov_var(ARG1, lhs);
+ mov_var(ARG2, rhs);
fragment_call(ga->get_arith_compare_shared());
a.bind(next);
@@ -1414,28 +1802,78 @@ void BeamModuleAssembler::emit_is_lt(const ArgLabel &Fail,
void BeamModuleAssembler::emit_is_ge(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS) {
- mov_arg(ARG1, LHS);
- mov_arg(ARG2, RHS);
+ if (always_small(LHS) && RHS.isSmall() && RHS.isImmed()) {
+ auto lhs = load_source(LHS, ARG1);
+ comment("simplified compare because one operand is an immediate small");
+ cmp(lhs.reg, RHS.as<ArgImmed>().get());
+ a.b_lt(resolve_beam_label(Fail, disp1MB));
+ return;
+ } else if (LHS.isSmall() && LHS.isImmed() && always_small(RHS)) {
+ auto rhs = load_source(RHS, ARG1);
+ comment("simplified compare because one operand is an immediate small");
+ cmp(rhs.reg, LHS.as<ArgImmed>().get());
+ a.b_gt(resolve_beam_label(Fail, disp1MB));
+ return;
+ }
+
+ auto [lhs, rhs] = load_sources(LHS, ARG1, RHS, ARG2);
if (always_small(LHS) && always_small(RHS)) {
comment("skipped test for small operands since they are always small");
- a.cmp(ARG1, ARG2);
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_lt(resolve_beam_label(Fail, disp1MB));
+ } else if (always_small(LHS) && exact_type<BeamTypeId::Integer>(RHS) &&
+ hasLowerBound(RHS)) {
+ comment("simplified test because it always fails when RHS is a bignum");
+ emit_is_not_boxed(resolve_beam_label(Fail, dispUnknown), rhs.reg);
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_lt(resolve_beam_label(Fail, disp1MB));
+ } else if (always_small(LHS) && exact_type<BeamTypeId::Integer>(RHS) &&
+ hasUpperBound(RHS)) {
+ Label next = a.newLabel();
+ comment("simplified test because it always succeeds when RHS is a "
+ "bignum");
+ emit_is_not_boxed(next, rhs.reg);
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_lt(resolve_beam_label(Fail, disp1MB));
+ a.bind(next);
+ } else if (exact_type<BeamTypeId::Integer>(LHS) && hasUpperBound(LHS) &&
+ always_small(RHS)) {
+ comment("simplified test because it always fails when LHS is a bignum");
+ emit_is_not_boxed(resolve_beam_label(Fail, dispUnknown), lhs.reg);
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_lt(resolve_beam_label(Fail, disp1MB));
+ } else if (exact_type<BeamTypeId::Integer>(LHS) && hasLowerBound(LHS) &&
+ always_small(RHS)) {
+ Label next = a.newLabel();
+ comment("simplified test because it always succeeds when LHS is a "
+ "bignum");
+ emit_is_not_boxed(next, lhs.reg);
+ a.cmp(lhs.reg, rhs.reg);
a.b_lt(resolve_beam_label(Fail, disp1MB));
- } else if (always_one_of(LHS,
- BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED) &&
- always_one_of(RHS,
- BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ a.bind(next);
+ } else if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ RHS)) {
Label branch_compare = a.newLabel();
- a.cmp(ARG1, ARG2);
+ a.cmp(lhs.reg, rhs.reg);
/* The only possible kind of immediate is a small and all other values
* are boxed, so we can test for smalls by testing boxed. */
comment("simplified small test since all other types are boxed");
- ERTS_CT_ASSERT(_TAG_PRIMARY_MASK - TAG_PRIMARY_BOXED == (1 << 0));
- a.and_(TMP1, ARG1, ARG2);
- a.tbnz(TMP1, imm(0), branch_compare);
+ if (always_small(LHS)) {
+ emit_is_boxed(branch_compare, rhs.reg);
+ } else if (always_small(RHS)) {
+ emit_is_boxed(branch_compare, lhs.reg);
+ } else {
+ a.and_(TMP1, lhs.reg, rhs.reg);
+ emit_is_boxed(branch_compare, TMP1);
+ }
+ mov_var(ARG1, lhs);
+ mov_var(ARG2, rhs);
fragment_call(ga->get_arith_compare_shared());
/* The flags will either be from the initial comparison, or from the
@@ -1447,23 +1885,29 @@ void BeamModuleAssembler::emit_is_ge(const ArgLabel &Fail,
/* Relative comparisons are overwhelmingly likely to be used on smalls,
* so we'll specialize those and keep the rest in a shared fragment. */
- if (RHS.isSmall()) {
- a.and_(TMP1, ARG1, imm(_TAG_IMMED1_MASK));
- } else if (LHS.isSmall()) {
- a.and_(TMP1, ARG2, imm(_TAG_IMMED1_MASK));
+ if (always_small(RHS)) {
+ a.and_(TMP1, lhs.reg, imm(_TAG_IMMED1_MASK));
+ } else if (always_small(LHS)) {
+ a.and_(TMP1, rhs.reg, imm(_TAG_IMMED1_MASK));
} else {
+ /* Avoid the expensive generic comparison for equal terms. */
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_eq(next);
+
ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
- a.and_(TMP1, ARG1, ARG2);
+ a.and_(TMP1, lhs.reg, rhs.reg);
a.and_(TMP1, TMP1, imm(_TAG_IMMED1_MASK));
}
a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
a.b_ne(generic);
- a.cmp(ARG1, ARG2);
+ a.cmp(lhs.reg, rhs.reg);
a.b(next);
a.bind(generic);
+ mov_var(ARG1, lhs);
+ mov_var(ARG2, rhs);
fragment_call(ga->get_arith_compare_shared());
a.bind(next);
@@ -1471,14 +1915,364 @@ void BeamModuleAssembler::emit_is_ge(const ArgLabel &Fail,
}
}
+/*
+ * ARG1 = Src
+ * ARG2 = Min
+ * ARG3 = Max
+ *
+ * Result is returned in the flags.
+ */
+void BeamGlobalAssembler::emit_is_in_range_shared() {
+ Label immediate = a.newLabel(), generic_compare = a.newLabel(),
+ float_done = a.newLabel(), done = a.newLabel();
+
+ /* Is the source a float? */
+ emit_is_boxed(immediate, ARG1);
+
+ arm::Gp boxed_ptr = emit_ptr_val(TMP1, ARG1);
+ a.ldur(TMP2, emit_boxed_val(boxed_ptr));
+
+ mov_imm(TMP3, HEADER_FLONUM);
+ a.cmp(TMP2, TMP3);
+ a.b_ne(generic_compare);
+
+ a.ldur(a64::d0, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ a.asr(TMP1, ARG2, imm(_TAG_IMMED1_SIZE));
+ a.scvtf(a64::d1, TMP1);
+
+ a.fcmpe(a64::d0, a64::d1);
+ a.b_mi(float_done);
+
+ a.asr(TMP1, ARG3, imm(_TAG_IMMED1_SIZE));
+ a.scvtf(a64::d1, TMP1);
+ a.fcmpe(a64::d0, a64::d1);
+ a.b_gt(float_done);
+ a.tst(ZERO, ZERO);
+
+ a.bind(float_done);
+ a.ret(a64::x30);
+
+ a.bind(immediate);
+ {
+ /*
+ * Src is an immediate (such as ATOM) but not SMALL.
+ * That means that Src must be greater than the upper
+ * limit.
+ */
+ mov_imm(TMP1, 1);
+ a.cmp(TMP1, imm(0));
+ a.ret(a64::x30);
+ }
+
+ a.bind(generic_compare);
+ {
+ emit_enter_runtime_frame();
+ emit_enter_runtime();
+
+ a.stp(ARG1, ARG3, TMP_MEM1q);
+
+ comment("erts_cmp_compound(X, Y, 0, 0);");
+ mov_imm(ARG3, 0);
+ mov_imm(ARG4, 0);
+ runtime_call<4>(erts_cmp_compound);
+ a.tst(ARG1, ARG1);
+ a.b_mi(done);
+
+ a.ldp(ARG1, ARG2, TMP_MEM1q);
+
+ comment("erts_cmp_compound(X, Y, 0, 0);");
+ mov_imm(ARG3, 0);
+ mov_imm(ARG4, 0);
+ runtime_call<4>(erts_cmp_compound);
+ a.tst(ARG1, ARG1);
+
+ a.bind(done);
+ emit_leave_runtime();
+ emit_leave_runtime_frame();
+
+ a.ret(a64::x30);
+ }
+}
+
+/*
+ * 1121 occurrences in OTP at the time of writing.
+ */
+void BeamModuleAssembler::emit_is_in_range(ArgLabel const &Small,
+ ArgLabel const &Large,
+ ArgRegister const &Src,
+ ArgConstant const &Min,
+ ArgConstant const &Max) {
+ Label next = a.newLabel(), generic = a.newLabel();
+ bool need_generic = true;
+ auto src = load_source(Src, ARG1);
+
+ if (always_small(Src)) {
+ need_generic = false;
+ comment("skipped test for small operand since it always small");
+ } else if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ Src)) {
+ /* The only possible kind of immediate is a small and all
+ * other values are boxed, so we can test for smalls by
+ * testing boxed. */
+ comment("simplified small test since all other types are boxed");
+ ERTS_CT_ASSERT(_TAG_PRIMARY_MASK - TAG_PRIMARY_BOXED == (1 << 0));
+ if (Small == Large && never_one_of<BeamTypeId::Float>(Src)) {
+ /* Src is never a float and the failure labels are
+ * equal. Therefore, since a bignum will never be within
+ * the range, we can fail immediately if Src is not a
+ * small. */
+ need_generic = false;
+ a.tbz(src.reg, imm(0), resolve_beam_label(Small, disp32K));
+ } else {
+ /* Src can be a float or the failures labels are distinct.
+ * We need to call the generic routine if Src is not a small. */
+ a.tbz(src.reg, imm(0), generic);
+ }
+ } else if (Small == Large) {
+ /* We can save one instruction if we incorporate the test for
+ * small into the range check. */
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ comment("simplified small & range tests since failure labels are "
+ "equal");
+ sub(TMP1, src.reg, Min.as<ArgImmed>().get());
+
+ /* Since we have subtracted the (tagged) lower bound, the
+ * tag bits of the difference is 0 if and only if Src is
+ * a small. Testing for a tag of 0 can be done in two
+ * instructions. */
+ a.tst(TMP1, imm(_TAG_IMMED1_MASK));
+ a.b_ne(generic);
+
+ /* Now do the range check. */
+ cmp(TMP1, Max.as<ArgImmed>().get() - Min.as<ArgImmed>().get());
+ a.b_hi(resolve_beam_label(Small, disp1MB));
+
+ /* Bypass the test code. */
+ goto test_done;
+ } else {
+ /* We have no applicable type information and the failure
+ * labels are distinct. Emit the standard test for small
+ * and call the generic routine if Src is not a small. */
+ a.and_(TMP1, src.reg, imm(_TAG_IMMED1_MASK));
+ a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
+ a.b_ne(generic);
+ }
+
+ /* We have now established that the operand is small. */
+ if (Small == Large) {
+ comment("simplified range test since failure labels are equal");
+ sub(TMP1, src.reg, Min.as<ArgImmed>().get());
+ cmp(TMP1, Max.as<ArgImmed>().get() - Min.as<ArgImmed>().get());
+ a.b_hi(resolve_beam_label(Small, disp1MB));
+ } else {
+ cmp(src.reg, Min.as<ArgImmed>().get());
+ a.b_lt(resolve_beam_label(Small, disp1MB));
+ cmp(src.reg, Max.as<ArgImmed>().get());
+ a.b_gt(resolve_beam_label(Large, disp1MB));
+ }
+
+test_done:
+ if (need_generic) {
+ a.b(next);
+ }
+
+ a.bind(generic);
+ if (!need_generic) {
+ comment("skipped generic comparison because it is not needed");
+ } else {
+ mov_var(ARG1, src);
+ mov_arg(ARG2, Min);
+ mov_arg(ARG3, Max);
+ fragment_call(ga->get_is_in_range_shared());
+ if (Small == Large) {
+ a.b_ne(resolve_beam_label(Small, disp1MB));
+ } else {
+ a.b_lt(resolve_beam_label(Small, disp1MB));
+ a.b_gt(resolve_beam_label(Large, disp1MB));
+ }
+ }
+
+ a.bind(next);
+}
+
+/*
+ * ARG1 = Src
+ * ARG2 = A
+ * ARG3 = B
+ *
+ * Result is returned in the flags.
+ */
+void BeamGlobalAssembler::emit_is_ge_lt_shared() {
+ Label done = a.newLabel();
+
+ emit_enter_runtime_frame();
+ emit_enter_runtime();
+
+ a.stp(ARG1, ARG3, TMP_MEM1q);
+
+ comment("erts_cmp_compound(Src, A, 0, 0);");
+ mov_imm(ARG3, 0);
+ mov_imm(ARG4, 0);
+ runtime_call<4>(erts_cmp_compound);
+ a.tst(ARG1, ARG1);
+ a.b_mi(done);
+
+ comment("erts_cmp_compound(B, Src, 0, 0);");
+ a.ldp(ARG2, ARG1, TMP_MEM1q);
+ mov_imm(ARG3, 0);
+ mov_imm(ARG4, 0);
+ runtime_call<4>(erts_cmp_compound);
+ a.cmp(ARG1, imm(0));
+
+ /* Make sure that ARG1 is -1, 0, or 1. */
+ a.cset(ARG1, imm(arm::CondCode::kNE));
+ a.csinv(ARG1, ARG1, ZERO, imm(arm::CondCode::kGE));
+
+ /* Prepare return value and flags. */
+ a.adds(ARG1, ARG1, imm(1));
+
+ /* We now have:
+ * ARG1 == 0 if B < SRC
+ * ARG1 > 0 if B => SRC
+ * and flags set accordingly. */
+
+ a.bind(done);
+ emit_leave_runtime();
+ emit_leave_runtime_frame();
+
+ a.ret(a64::x30);
+}
+
+/*
+ * The instruction sequence:
+ *
+ * is_ge Fail1 Src A
+ * is_lt Fail1 B Src
+ *
+ * is common (1841 occurrences in OTP at the time of writing).
+ *
+ * is_ge + is_lt is 18 instructions, while is_ge_lt is
+ * 14 instructions.
+ */
+void BeamModuleAssembler::emit_is_ge_lt(ArgLabel const &Fail1,
+ ArgLabel const &Fail2,
+ ArgRegister const &Src,
+ ArgConstant const &A,
+ ArgConstant const &B) {
+ Label generic = a.newLabel(), next = a.newLabel();
+ auto src = load_source(Src, ARG1);
+
+ mov_arg(ARG2, A);
+ mov_arg(ARG3, B);
+
+ a.and_(TMP1, src.reg, imm(_TAG_IMMED1_MASK));
+ a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
+ a.b_ne(generic);
+
+ a.cmp(src.reg, ARG2);
+ a.b_lt(resolve_beam_label(Fail1, disp1MB));
+ a.cmp(ARG3, src.reg);
+ a.b_ge(resolve_beam_label(Fail2, disp1MB));
+ a.b(next);
+
+ a.bind(generic);
+ mov_var(ARG1, src);
+ fragment_call(ga->get_is_ge_lt_shared());
+ a.b_lt(resolve_beam_label(Fail1, disp1MB));
+ a.b_gt(resolve_beam_label(Fail2, disp1MB));
+
+ a.bind(next);
+}
+
+/*
+ * 1190 occurrences in OTP at the time of writing.
+ */
+void BeamModuleAssembler::emit_is_ge_ge(ArgLabel const &Fail1,
+ ArgLabel const &Fail2,
+ ArgRegister const &Src,
+ ArgConstant const &A,
+ ArgConstant const &B) {
+ if (!always_small(Src)) {
+ /* In practice, it is uncommon that Src is not a known small
+ * integer, so we will not bother optimizing that case. */
+ emit_is_ge(Fail1, Src, A);
+ emit_is_ge(Fail2, Src, B);
+ return;
+ }
+
+ auto src = load_source(Src, ARG1);
+ subs(TMP1, src.reg, A.as<ArgImmed>().get());
+ a.b_lt(resolve_beam_label(Fail1, disp1MB));
+ cmp(TMP1, B.as<ArgImmed>().get() - A.as<ArgImmed>().get());
+ a.b_lo(resolve_beam_label(Fail2, disp1MB));
+}
+
+/*
+ * 60 occurrences in OTP at the time of writing. Seems to be common in
+ * Elixir code.
+ *
+ * Currently not very frequent in OTP but very nice reduction in code
+ * size when it happens. We expect this combination of instructions
+ * to become more common in the future.
+ */
+void BeamModuleAssembler::emit_is_int_in_range(ArgLabel const &Fail,
+ ArgRegister const &Src,
+ ArgConstant const &Min,
+ ArgConstant const &Max) {
+ auto src = load_source(Src, ARG1);
+
+ sub(TMP1, src.reg, Min.as<ArgImmed>().get());
+
+ /* Since we have subtracted the (tagged) lower bound, the
+ * tag bits of the difference is 0 if and only if Src is
+ * a small. */
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.tst(TMP1, imm(_TAG_IMMED1_MASK));
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
+ cmp(TMP1, Max.as<ArgImmed>().get() - Min.as<ArgImmed>().get());
+ a.b_hi(resolve_beam_label(Fail, disp1MB));
+}
+
+/*
+ * 428 occurrences in OTP at the time of writing.
+ */
+void BeamModuleAssembler::emit_is_int_ge(ArgLabel const &Fail,
+ ArgRegister const &Src,
+ ArgConstant const &Min) {
+ auto src = load_source(Src, ARG1);
+ Label small = a.newLabel(), next = a.newLabel();
+
+ if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(Src)) {
+ comment("simplified small test since all other types are boxed");
+ emit_is_boxed(small, Src, src.reg);
+ } else {
+ a.and_(TMP2, src.reg, imm(_TAG_IMMED1_MASK));
+ a.cmp(TMP2, imm(_TAG_IMMED1_SMALL));
+ a.b_eq(small);
+
+ emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, TMP2);
+ }
+
+ arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
+ a.ldur(TMP1, emit_boxed_val(boxed_ptr));
+ a.and_(TMP1, TMP1, imm(_TAG_HEADER_MASK));
+ a.cmp(TMP1, imm(_TAG_HEADER_POS_BIG));
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
+ a.b(next);
+
+ a.bind(small);
+ cmp(src.reg, Min.as<ArgImmed>().get());
+ a.b_lt(resolve_beam_label(Fail, disp1MB));
+
+ a.bind(next);
+}
+
void BeamModuleAssembler::emit_badmatch(const ArgSource &Src) {
- mov_arg(arm::Mem(c_p, offsetof(Process, fvalue)), Src);
- emit_error(BADMATCH);
+ emit_error(BADMATCH, Src);
}
void BeamModuleAssembler::emit_case_end(const ArgSource &Src) {
- mov_arg(arm::Mem(c_p, offsetof(Process, fvalue)), Src);
- emit_error(EXC_CASE_CLAUSE);
+ emit_error(EXC_CASE_CLAUSE, Src);
}
void BeamModuleAssembler::emit_system_limit_body() {
@@ -1490,8 +2284,7 @@ void BeamModuleAssembler::emit_if_end() {
}
void BeamModuleAssembler::emit_badrecord(const ArgSource &Src) {
- mov_arg(arm::Mem(c_p, offsetof(Process, fvalue)), Src);
- emit_error(EXC_BADRECORD);
+ emit_error(EXC_BADRECORD, Src);
}
void BeamModuleAssembler::emit_catch(const ArgYRegister &Y,
@@ -1535,12 +2328,12 @@ void BeamGlobalAssembler::emit_catch_end_shared() {
/* This is an error, attach a stacktrace to the reason. */
ERTS_CT_ASSERT(ERTS_HIGHEST_CALLEE_SAVE_XREG >= 2);
- emit_enter_runtime<Update::eStack | Update::eHeap>(2);
+ emit_enter_runtime<Update::eHeapAlloc>(2);
a.mov(ARG1, c_p);
runtime_call<3>(add_stacktrace);
- emit_leave_runtime<Update::eStack | Update::eHeap>(2);
+ emit_leave_runtime<Update::eHeapAlloc>(2);
/* Fall through! */
}
@@ -1630,17 +2423,16 @@ void BeamModuleAssembler::emit_try_case(const ArgYRegister &CatchTag) {
}
void BeamModuleAssembler::emit_try_case_end(const ArgSource &Src) {
- mov_arg(arm::Mem(c_p, offsetof(Process, fvalue)), Src);
- emit_error(EXC_TRY_CLAUSE);
+ emit_error(EXC_TRY_CLAUSE, Src);
}
void BeamModuleAssembler::emit_raise(const ArgSource &Trace,
const ArgSource &Value) {
- mov_arg(TMP1, Value);
+ auto value = load_source(Value, TMP1);
mov_arg(ARG2, Trace);
/* This is an error, attach a stacktrace to the reason. */
- a.str(TMP1, arm::Mem(c_p, offsetof(Process, fvalue)));
+ a.str(value.reg, arm::Mem(c_p, offsetof(Process, fvalue)));
a.str(ARG2, arm::Mem(c_p, offsetof(Process, ftrace)));
emit_enter_runtime(0);
@@ -1656,12 +2448,12 @@ void BeamModuleAssembler::emit_raise(const ArgSource &Trace,
void BeamModuleAssembler::emit_build_stacktrace() {
a.mov(ARG2, XREG0);
- emit_enter_runtime<Update::eStack | Update::eHeap>(0);
+ emit_enter_runtime<Update::eHeapAlloc>(0);
a.mov(ARG1, c_p);
runtime_call<2>(build_stacktrace);
- emit_leave_runtime<Update::eStack | Update::eHeap>(0);
+ emit_leave_runtime<Update::eHeapAlloc>(0);
a.mov(XREG0, ARG1);
}
diff --git a/erts/emulator/beam/jit/arm/instr_float.cpp b/erts/emulator/beam/jit/arm/instr_float.cpp
index 25fe82df90..9cb910bff7 100644
--- a/erts/emulator/beam/jit/arm/instr_float.cpp
+++ b/erts/emulator/beam/jit/arm/instr_float.cpp
@@ -159,12 +159,12 @@ void BeamModuleAssembler::emit_fconv(const ArgSource &Src,
a.bind(not_small);
{
- if (masked_types(Src, BEAM_TYPE_FLOAT) == BEAM_TYPE_NONE) {
+ if (never_one_of<BeamTypeId::Float>(Src)) {
comment("skipped float path since source cannot be a float");
} else {
/* If the source is always a number, we can skip the box test when
* it's not a small. */
- if (always_one_of(Src, BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ if (always_one_of<BeamTypeId::Number>(Src)) {
comment("skipped box test since source is always a number");
} else {
emit_is_boxed(fallback, Src, src.reg);
diff --git a/erts/emulator/beam/jit/arm/instr_fun.cpp b/erts/emulator/beam/jit/arm/instr_fun.cpp
index c236281ace..f2e0792f26 100644
--- a/erts/emulator/beam/jit/arm/instr_fun.cpp
+++ b/erts/emulator/beam/jit/arm/instr_fun.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2021-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2021-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ void BeamGlobalAssembler::emit_unloaded_fun() {
a.str(ARG5, TMP_MEM1q);
emit_enter_runtime_frame();
- emit_enter_runtime<Update::eHeap | Update::eStack | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
a.mov(ARG1, c_p);
@@ -43,7 +43,7 @@ void BeamGlobalAssembler::emit_unloaded_fun() {
/* ARG3 and ARG4 have already been set. */
runtime_call<4>(beam_jit_handle_unloaded_fun);
- emit_leave_runtime<Update::eHeap | Update::eStack | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions | Update::eCodeIndex>();
emit_leave_runtime_frame();
@@ -92,14 +92,14 @@ void BeamGlobalAssembler::emit_handle_call_fun_error() {
{
a.stp(ARG4, ARG5, TMP_MEM1q);
- emit_enter_runtime<Update::eHeap | Update::eStack | Update::eXRegs>();
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
/* ARG3 is already set */
runtime_call<3>(beam_jit_build_argument_list);
- emit_leave_runtime<Update::eHeap | Update::eStack | Update::eXRegs>();
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs>();
a.ldr(XREG0, TMP_MEM1q);
a.mov(XREG1, ARG1);
@@ -135,6 +135,19 @@ void BeamGlobalAssembler::emit_handle_call_fun_error() {
}
}
+/* Handles save_calls for local funs, which is a side-effect of our calling
+ * convention. Fun entry is in ARG1.
+ *
+ * When the active code index is ERTS_SAVE_CALLS_CODE_IX, all local fun calls
+ * will land here. */
+void BeamGlobalAssembler::emit_dispatch_save_calls_fun() {
+ /* Keep going with the actual code index. */
+ a.mov(TMP1, imm(&the_active_code_index));
+ a.ldr(TMP1.w(), arm::Mem(TMP1));
+
+ branch(emit_setup_dispatchable_call(ARG1, TMP1));
+}
+
/* `call_fun` instructions land here to set up their environment before jumping
* to the actual implementation.
*
@@ -200,11 +213,11 @@ void BeamModuleAssembler::emit_i_make_fun3(const ArgLambda &Lambda,
mov_arg(ARG3, Arity);
mov_arg(ARG4, NumFree);
- emit_enter_runtime<Update::eHeap>();
+ emit_enter_runtime<Update::eHeapOnlyAlloc>();
runtime_call<4>(erts_new_local_fun_thing);
- emit_leave_runtime<Update::eHeap>();
+ emit_leave_runtime<Update::eHeapOnlyAlloc>();
if (num_free) {
comment("Move fun environment");
@@ -394,8 +407,8 @@ void BeamModuleAssembler::emit_i_call_fun2(const ArgVal &Tag,
mov_imm(ARG3, Arity.get());
auto target = emit_call_fun(
- always_one_of(Func, BEAM_TYPE_MASK_ALWAYS_BOXED),
- masked_types(Func, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_FUN,
+ always_one_of<BeamTypeId::AlwaysBoxed>(Func),
+ masked_types<BeamTypeId::MaybeBoxed>(Func) == BeamTypeId::Fun,
Tag.as<ArgAtom>().get() == am_safe);
erlang_call(target);
@@ -415,8 +428,8 @@ void BeamModuleAssembler::emit_i_call_fun2_last(const ArgVal &Tag,
mov_imm(ARG3, Arity.get());
auto target = emit_call_fun(
- always_one_of(Func, BEAM_TYPE_MASK_ALWAYS_BOXED),
- masked_types(Func, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_FUN,
+ always_one_of<BeamTypeId::AlwaysBoxed>(Func),
+ masked_types<BeamTypeId::MaybeBoxed>(Func) == BeamTypeId::Fun,
Tag.as<ArgAtom>().get() == am_safe);
emit_deallocate(Deallocate);
diff --git a/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp b/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp
index 1c7df4e6f6..c24ca4831f 100644
--- a/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp
+++ b/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
*/
#include <algorithm>
+#include <numeric>
#include "beam_asm.hpp"
extern "C"
@@ -56,6 +57,8 @@ void BeamGlobalAssembler::emit_raise_badarg(const ErtsCodeMFA *mfa) {
/* ================================================================
* '=:='/2
* '=/='/2
+ * '>='/2
+ * '<'/2
* ================================================================
*/
@@ -125,13 +128,20 @@ void BeamGlobalAssembler::emit_bif_is_ne_exact_shared() {
}
}
-void BeamModuleAssembler::emit_bif_is_eq_ne_exact_immed(const ArgSource &LHS,
- const ArgSource &RHS,
- const ArgRegister &Dst,
- Eterm fail_value,
- Eterm succ_value) {
+void BeamModuleAssembler::emit_cond_to_bool(arm::CondCode cc,
+ const ArgRegister &Dst) {
auto dst = init_destination(Dst, TMP2);
+ mov_imm(TMP3, am_true);
+ mov_imm(TMP4, am_false);
+ a.csel(dst.reg, TMP3, TMP4, cc);
+ flush_var(dst);
+}
+
+void BeamModuleAssembler::emit_cmp_immed_to_bool(arm::CondCode cc,
+ const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
if (RHS.isImmed()) {
auto lhs = load_source(LHS, TMP1);
cmp_arg(lhs.reg, RHS);
@@ -139,11 +149,7 @@ void BeamModuleAssembler::emit_bif_is_eq_ne_exact_immed(const ArgSource &LHS,
auto [lhs, rhs] = load_sources(LHS, TMP1, RHS, TMP2);
a.cmp(lhs.reg, rhs.reg);
}
-
- mov_imm(TMP3, succ_value);
- mov_imm(TMP4, fail_value);
- a.csel(dst.reg, TMP3, TMP4, arm::CondCode::kEQ);
- flush_var(dst);
+ emit_cond_to_bool(cc, Dst);
}
void BeamModuleAssembler::emit_bif_is_eq_exact(const ArgRegister &LHS,
@@ -153,7 +159,7 @@ void BeamModuleAssembler::emit_bif_is_eq_exact(const ArgRegister &LHS,
if (!LHS.isImmed() && !RHS.isImmed()) {
comment("simplified check since one argument is an immediate");
}
- emit_bif_is_eq_ne_exact_immed(LHS, RHS, Dst, am_false, am_true);
+ emit_cmp_immed_to_bool(arm::CondCode::kEQ, LHS, RHS, Dst);
} else {
auto [lhs, rhs] = load_sources(LHS, ARG1, RHS, ARG2);
auto dst = init_destination(Dst, ARG1);
@@ -173,7 +179,7 @@ void BeamModuleAssembler::emit_bif_is_ne_exact(const ArgRegister &LHS,
if (!LHS.isImmed() && !RHS.isImmed()) {
comment("simplified check since one argument is an immediate");
}
- emit_bif_is_eq_ne_exact_immed(LHS, RHS, Dst, am_true, am_false);
+ emit_cmp_immed_to_bool(arm::CondCode::kNE, LHS, RHS, Dst);
} else {
auto [lhs, rhs] = load_sources(LHS, ARG1, RHS, ARG2);
auto dst = init_destination(Dst, ARG1);
@@ -186,6 +192,93 @@ void BeamModuleAssembler::emit_bif_is_ne_exact(const ArgRegister &LHS,
}
}
+void BeamModuleAssembler::emit_bif_is_ge_lt(arm::CondCode cc,
+ const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ auto [lhs, rhs] = load_sources(LHS, ARG1, RHS, ARG2);
+
+ Label generic = a.newLabel(), next = a.newLabel();
+
+ if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(RHS)) {
+ /* The only possible kind of immediate is a small and all
+ * other values are boxed, so we can test for smalls by
+ * testing boxed. */
+ comment("simplified small test since all other types are boxed");
+ if (always_small(LHS)) {
+ emit_is_not_boxed(generic, rhs.reg);
+ } else if (always_small(RHS)) {
+ emit_is_not_boxed(generic, lhs.reg);
+ } else {
+ a.and_(TMP1, lhs.reg, rhs.reg);
+ emit_is_not_boxed(generic, TMP1);
+ }
+ } else {
+ /* Relative comparisons are overwhelmingly likely to be used
+ * on smalls, so we'll specialize those and keep the rest in a
+ * shared fragment. */
+ if (always_small(RHS)) {
+ a.and_(TMP1, lhs.reg, imm(_TAG_IMMED1_MASK));
+ } else if (always_small(LHS)) {
+ a.and_(TMP1, rhs.reg, imm(_TAG_IMMED1_MASK));
+ } else {
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.and_(TMP1, lhs.reg, rhs.reg);
+ a.and_(TMP1, TMP1, imm(_TAG_IMMED1_MASK));
+ }
+
+ a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
+ a.b_ne(generic);
+ }
+
+ a.cmp(lhs.reg, rhs.reg);
+ a.b(next);
+
+ a.bind(generic);
+ {
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_eq(next);
+
+ mov_var(ARG1, lhs);
+ mov_var(ARG2, rhs);
+ fragment_call(ga->get_arith_compare_shared());
+ }
+
+ a.bind(next);
+ emit_cond_to_bool(cc, Dst);
+}
+
+void BeamModuleAssembler::emit_bif_is_ge(const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ if (always_small(LHS) && RHS.isSmall() && RHS.isImmed()) {
+ auto lhs = load_source(LHS, ARG1);
+
+ comment("simplified compare because one operand is an immediate small");
+ cmp(lhs.reg, RHS.as<ArgImmed>().get());
+ emit_cond_to_bool(arm::CondCode::kGE, Dst);
+
+ return;
+ } else if (LHS.isSmall() && LHS.isImmed() && always_small(RHS)) {
+ auto rhs = load_source(RHS, ARG1);
+
+ comment("simplified compare because one operand is an immediate small");
+ cmp(rhs.reg, LHS.as<ArgImmed>().get());
+ emit_cond_to_bool(arm::CondCode::kLE, Dst);
+
+ return;
+ }
+
+ emit_bif_is_ge_lt(arm::CondCode::kGE, LHS, RHS, Dst);
+}
+
+void BeamModuleAssembler::emit_bif_is_lt(const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ emit_bif_is_ge_lt(arm::CondCode::kLT, LHS, RHS, Dst);
+}
+
/* ================================================================
* and/2
* ================================================================
@@ -209,10 +302,17 @@ void BeamModuleAssembler::emit_bif_and(const ArgLabel &Fail,
ERTS_CT_ASSERT(am_false == make_atom(0));
ERTS_CT_ASSERT(am_true == make_atom(1));
- a.and_(TMP3, src1.reg, imm(_TAG_IMMED2_MASK | ~diff_bit));
- a.and_(TMP4, src2.reg, imm(_TAG_IMMED2_MASK | ~diff_bit));
- a.cmp(TMP3, imm(_TAG_IMMED2_ATOM));
- a.ccmp(TMP3, TMP4, imm(NZCV::kNone), imm(arm::CondCode::kEQ));
+ if (exact_type<BeamTypeId::Atom>(Src1) &&
+ exact_type<BeamTypeId::Atom>(Src2)) {
+ comment("simplified type check because operands are atoms");
+ a.orr(TMP3, src1.reg, src2.reg);
+ a.tst(TMP3, imm(-1ull << (_TAG_IMMED2_SIZE + 1)));
+ } else {
+ a.and_(TMP3, src1.reg, imm(_TAG_IMMED2_MASK | ~diff_bit));
+ a.and_(TMP4, src2.reg, imm(_TAG_IMMED2_MASK | ~diff_bit));
+ a.cmp(TMP3, imm(_TAG_IMMED2_ATOM));
+ a.ccmp(TMP3, TMP4, imm(NZCV::kNone), imm(arm::CondCode::kEQ));
+ }
if (Fail.get()) {
a.b_ne(resolve_beam_label(Fail, disp1MB));
@@ -298,16 +398,54 @@ void BeamModuleAssembler::emit_bif_bit_size(const ArgLabel &Fail,
auto src = load_source(Src, ARG1);
auto dst = init_destination(Dst, ARG1);
- mov_var(ARG1, src);
+ if (exact_type<BeamTypeId::Bitstring>(Src)) {
+ Label not_sub_bin = a.newLabel();
+ arm::Gp boxed_ptr = emit_ptr_val(ARG1, src.reg);
+ auto unit = getSizeUnit(Src);
+ bool is_bitstring = unit == 0 || std::gcd(unit, 8) != 8;
+
+ if (is_bitstring) {
+ comment("inlined bit_size/1 because "
+ "its argument is a bitstring");
+ } else {
+ comment("inlined and simplified bit_size/1 because "
+ "its argument is a binary");
+ }
- if (Fail.get() == 0) {
- fragment_call(ga->get_bif_bit_size_body());
+ if (is_bitstring) {
+ a.ldur(TMP1, emit_boxed_val(boxed_ptr));
+ }
+
+ a.ldur(TMP2, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ a.lsl(TMP2, TMP2, imm(_TAG_IMMED1_SIZE + 3));
+
+ if (is_bitstring) {
+ const int bit_number = 3;
+ ERTS_CT_ASSERT((_TAG_HEADER_SUB_BIN & (1 << bit_number)) != 0 &&
+ (_TAG_HEADER_REFC_BIN & (1 << bit_number)) == 0 &&
+ (_TAG_HEADER_HEAP_BIN & (1 << bit_number)) == 0);
+ a.tbz(TMP1, imm(bit_number), not_sub_bin);
+
+ a.ldurb(TMP1.w(),
+ emit_boxed_val(boxed_ptr, offsetof(ErlSubBin, bitsize)));
+ a.add(TMP2, TMP2, TMP1, imm(_TAG_IMMED1_SIZE));
+ }
+
+ a.bind(not_sub_bin);
+ a.orr(dst.reg, TMP2, _TAG_IMMED1_SMALL);
} else {
- fragment_call(ga->get_bif_bit_size_guard());
- emit_branch_if_not_value(ARG1, resolve_beam_label(Fail, dispUnknown));
+ mov_var(ARG1, src);
+
+ if (Fail.get() == 0) {
+ fragment_call(ga->get_bif_bit_size_body());
+ } else {
+ fragment_call(ga->get_bif_bit_size_guard());
+ emit_branch_if_not_value(ARG1,
+ resolve_beam_label(Fail, dispUnknown));
+ }
+ mov_var(dst, ARG1);
}
- mov_var(dst, ARG1);
flush_var(dst);
}
@@ -380,16 +518,55 @@ void BeamModuleAssembler::emit_bif_byte_size(const ArgLabel &Fail,
auto src = load_source(Src, ARG1);
auto dst = init_destination(Dst, ARG1);
- mov_var(ARG1, src);
+ if (exact_type<BeamTypeId::Bitstring>(Src)) {
+ Label not_sub_bin = a.newLabel();
+ arm::Gp boxed_ptr = emit_ptr_val(ARG1, src.reg);
+ auto unit = getSizeUnit(Src);
+ bool is_bitstring = unit == 0 || std::gcd(unit, 8) != 8;
+
+ if (is_bitstring) {
+ comment("inlined byte_size/1 because "
+ "its argument is a bitstring");
+ } else {
+ comment("inlined and simplified byte_size/1 because "
+ "its argument is a binary");
+ }
- if (Fail.get() == 0) {
- fragment_call(ga->get_bif_byte_size_body());
+ if (is_bitstring) {
+ a.ldur(TMP1, emit_boxed_val(boxed_ptr));
+ }
+
+ a.ldur(TMP2, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+
+ if (is_bitstring) {
+ const int bit_number = 3;
+ ERTS_CT_ASSERT((_TAG_HEADER_SUB_BIN & (1 << bit_number)) != 0 &&
+ (_TAG_HEADER_REFC_BIN & (1 << bit_number)) == 0 &&
+ (_TAG_HEADER_HEAP_BIN & (1 << bit_number)) == 0);
+ a.tbz(TMP1, imm(bit_number), not_sub_bin);
+
+ a.ldurb(TMP1.w(),
+ emit_boxed_val(boxed_ptr, offsetof(ErlSubBin, bitsize)));
+ a.cmp(TMP1.w(), imm(0));
+ a.cinc(TMP2, TMP2, arm::CondCode::kNE);
+ }
+
+ a.bind(not_sub_bin);
+ mov_imm(dst.reg, _TAG_IMMED1_SMALL);
+ a.bfi(dst.reg, TMP2, imm(_TAG_IMMED1_SIZE), imm(SMALL_BITS));
} else {
- fragment_call(ga->get_bif_byte_size_guard());
- emit_branch_if_not_value(ARG1, resolve_beam_label(Fail, dispUnknown));
+ mov_var(ARG1, src);
+
+ if (Fail.get() == 0) {
+ fragment_call(ga->get_bif_byte_size_body());
+ } else {
+ fragment_call(ga->get_bif_byte_size_guard());
+ emit_branch_if_not_value(ARG1,
+ resolve_beam_label(Fail, dispUnknown));
+ }
+ mov_var(dst, ARG1);
}
- mov_var(dst, ARG1);
flush_var(dst);
}
@@ -470,16 +647,15 @@ void BeamModuleAssembler::emit_bif_element(const ArgLabel &Fail,
/*
* Try to optimize the use of a tuple as a lookup table.
*/
- if (exact_type(Pos, BEAM_TYPE_INTEGER) && Tuple.isLiteral()) {
+ if (exact_type<BeamTypeId::Integer>(Pos) && Tuple.isLiteral()) {
Eterm tuple_literal =
beamfile_get_literal(beam, Tuple.as<ArgLiteral>().get());
if (is_tuple(tuple_literal)) {
Label next = a.newLabel(), fail = a.newLabel();
Sint size = Sint(arityval(*tuple_val(tuple_literal)));
- auto [min, max] = getIntRange(Pos);
- bool is_bounded = min <= max;
- bool can_fail = !is_bounded || min < 1 || size < max;
+ auto [min, max] = getClampedRange(Pos);
+ bool can_fail = min < 1 || size < max;
auto [pos, tuple] = load_sources(Pos, ARG3, Tuple, ARG4);
auto dst = init_destination(Dst, ARG1);
@@ -499,14 +675,14 @@ void BeamModuleAssembler::emit_bif_element(const ArgLabel &Fail,
a.asr(TMP3, pos.reg, imm(_TAG_IMMED1_SIZE));
- if (is_bounded && min >= 1) {
+ if (min >= 1) {
comment("skipped check for position >= 1");
} else {
a.cmp(TMP3, imm(1));
a.b_mi(fail);
}
- if (is_bounded && size >= max) {
+ if (size >= max) {
comment("skipped check for position beyond tuple");
} else {
mov_imm(TMP2, size);
@@ -538,19 +714,51 @@ void BeamModuleAssembler::emit_bif_element(const ArgLabel &Fail,
}
}
- mov_arg(ARG1, Pos);
- mov_arg(ARG2, Tuple);
+ bool const_position;
- if (Fail.get() != 0) {
- fragment_call(ga->get_bif_element_guard_shared());
- emit_branch_if_not_value(ARG1, resolve_beam_label(Fail, dispUnknown));
+ const_position = Pos.isSmall() && Pos.as<ArgSmall>().getSigned() > 0 &&
+ Pos.as<ArgSmall>().getSigned() <= (Sint)MAX_ARITYVAL;
+
+ if (const_position && exact_type<BeamTypeId::Tuple>(Tuple)) {
+ comment("simplified element/2 because arguments are known types");
+ auto tuple = load_source(Tuple, ARG2);
+ auto dst = init_destination(Dst, ARG1);
+ Uint position = Pos.as<ArgSmall>().getUnsigned();
+ arm::Gp boxed_ptr = emit_ptr_val(TMP1, tuple.reg);
+
+ a.ldur(TMP2, emit_boxed_val(boxed_ptr));
+ ERTS_CT_ASSERT(make_arityval_zero() == 0);
+ cmp(TMP2, position << _HEADER_ARITY_OFFS);
+ if (Fail.get() != 0) {
+ a.b_lo(resolve_beam_label(Fail, disp1MB));
+ } else {
+ Label good = a.newLabel();
+ a.b_hs(good);
+ mov_arg(ARG1, Pos);
+ mov_var(ARG2, tuple);
+ fragment_call(ga->get_handle_element_error_shared());
+ a.bind(good);
+ }
+
+ safe_ldur(dst.reg, emit_boxed_val(boxed_ptr, position << 3));
+ flush_var(dst);
} else {
- fragment_call(ga->get_bif_element_body_shared());
- }
+ /* Too much code to inline. Call a helper fragment. */
+ mov_arg(ARG1, Pos);
+ mov_arg(ARG2, Tuple);
+
+ if (Fail.get() != 0) {
+ fragment_call(ga->get_bif_element_guard_shared());
+ emit_branch_if_not_value(ARG1,
+ resolve_beam_label(Fail, dispUnknown));
+ } else {
+ fragment_call(ga->get_bif_element_body_shared());
+ }
- auto dst = init_destination(Dst, ARG1);
- mov_var(dst, ARG1);
- flush_var(dst);
+ auto dst = init_destination(Dst, ARG1);
+ mov_var(dst, ARG1);
+ flush_var(dst);
+ }
}
/* ================================================================
@@ -585,6 +793,145 @@ void BeamModuleAssembler::emit_bif_hd(const ArgSource &Src,
}
/* ================================================================
+ * is_map_key/2
+ * ================================================================
+ */
+
+void BeamModuleAssembler::emit_bif_is_map_key(const ArgWord &Bif,
+ const ArgLabel &Fail,
+ const ArgSource &Key,
+ const ArgSource &Src,
+ const ArgRegister &Dst) {
+ if (!exact_type<BeamTypeId::Map>(Src)) {
+ emit_i_bif2(Key, Src, Fail, Bif, Dst);
+ return;
+ }
+
+ comment("inlined BIF is_map_key/2");
+
+ mov_arg(ARG1, Src);
+ mov_arg(ARG2, Key);
+
+ if (maybe_one_of<BeamTypeId::MaybeImmediate>(Key)) {
+ fragment_call(ga->get_i_get_map_element_shared());
+ emit_cond_to_bool(arm::CondCode::kEQ, Dst);
+ } else {
+ emit_enter_runtime();
+ runtime_call<2>(get_map_element);
+ emit_leave_runtime();
+
+ cmp(ARG1, THE_NON_VALUE);
+ emit_cond_to_bool(arm::CondCode::kNE, Dst);
+ }
+}
+
+/* ================================================================
+ * map_get/2
+ * ================================================================
+ */
+
+void BeamGlobalAssembler::emit_handle_map_get_badmap() {
+ static ErtsCodeMFA mfa = {am_erlang, am_map_get, 2};
+ mov_imm(TMP1, BADMAP);
+ a.str(TMP1, arm::Mem(c_p, offsetof(Process, freason)));
+ a.str(ARG1, arm::Mem(c_p, offsetof(Process, fvalue)));
+ a.mov(XREG0, ARG2);
+ a.mov(XREG1, ARG1);
+ mov_imm(ARG4, &mfa);
+ a.b(labels[raise_exception]);
+}
+
+void BeamGlobalAssembler::emit_handle_map_get_badkey() {
+ static ErtsCodeMFA mfa = {am_erlang, am_map_get, 2};
+ mov_imm(TMP1, BADKEY);
+ a.str(TMP1, arm::Mem(c_p, offsetof(Process, freason)));
+ a.str(ARG2, arm::Mem(c_p, offsetof(Process, fvalue)));
+ a.mov(XREG0, ARG2);
+ a.mov(XREG1, ARG1);
+ mov_imm(ARG4, &mfa);
+ a.b(labels[raise_exception]);
+}
+
+void BeamModuleAssembler::emit_bif_map_get(const ArgLabel &Fail,
+ const ArgSource &Key,
+ const ArgSource &Src,
+ const ArgRegister &Dst) {
+ Label good_key = a.newLabel();
+
+ mov_arg(ARG1, Src);
+ mov_arg(ARG2, Key);
+
+ if (exact_type<BeamTypeId::Map>(Src)) {
+ comment("skipped test for map for known map argument");
+ } else {
+ Label bad_map = a.newLabel();
+ Label good_map = a.newLabel();
+
+ if (Fail.get() == 0) {
+ emit_is_boxed(bad_map, Src, ARG1);
+ } else {
+ emit_is_boxed(resolve_beam_label(Fail, dispUnknown), Src, ARG1);
+ }
+
+ /* As an optimization for the `error | #{}` case, skip checking the
+ * header word when we know that the only possible boxed type
+ * is a map. */
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Map) {
+ comment("skipped header test since we know it's a map when boxed");
+ if (Fail.get() == 0) {
+ a.b(good_map);
+ }
+ } else {
+ arm::Gp boxed_ptr = emit_ptr_val(TMP1, ARG1);
+ a.ldur(TMP1, emit_boxed_val(boxed_ptr));
+ a.and_(TMP1, TMP1, imm(_TAG_HEADER_MASK));
+ a.cmp(TMP1, imm(_TAG_HEADER_MAP));
+ if (Fail.get() == 0) {
+ a.b_eq(good_map);
+ } else {
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
+ }
+ }
+
+ a.bind(bad_map);
+ if (Fail.get() == 0) {
+ fragment_call(ga->get_handle_map_get_badmap());
+ }
+
+ a.bind(good_map);
+ }
+
+ if (maybe_one_of<BeamTypeId::MaybeImmediate>(Key)) {
+ fragment_call(ga->get_i_get_map_element_shared());
+ if (Fail.get() == 0) {
+ a.b_eq(good_key);
+ } else {
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
+ }
+ } else {
+ emit_enter_runtime();
+ runtime_call<2>(get_map_element);
+ emit_leave_runtime();
+
+ if (Fail.get() == 0) {
+ emit_branch_if_value(ARG1, good_key);
+ } else {
+ emit_branch_if_not_value(ARG1,
+ resolve_beam_label(Fail, dispUnknown));
+ }
+ }
+
+ if (Fail.get() == 0) {
+ mov_arg(ARG1, Src);
+ mov_arg(ARG2, Key);
+ fragment_call(ga->get_handle_map_get_badkey());
+ }
+
+ a.bind(good_key);
+ mov_arg(Dst, ARG1);
+}
+
+/* ================================================================
* map_size/1
* ================================================================
*/
@@ -612,20 +959,26 @@ void BeamModuleAssembler::emit_bif_map_size(const ArgLabel &Fail,
}
arm::Gp boxed_ptr = emit_ptr_val(TMP3, src.reg);
- a.ldur(TMP4, emit_boxed_val(boxed_ptr));
- a.and_(TMP4, TMP4, imm(_TAG_HEADER_MASK));
- a.cmp(TMP4, imm(_TAG_HEADER_MAP));
- if (Fail.get() == 0) {
- a.b_eq(good_map);
- a.bind(error);
- {
- mov_var(XREG0, src);
- fragment_call(ga->get_handle_map_size_error());
- }
- } else {
- a.b_ne(resolve_beam_label(Fail, disp1MB));
+ if (exact_type<BeamTypeId::Map>(Src)) {
+ comment("skipped type check because the argument is always a map");
a.bind(error); /* Never referenced. */
+ } else {
+ a.ldur(TMP4, emit_boxed_val(boxed_ptr));
+ a.and_(TMP4, TMP4, imm(_TAG_HEADER_MASK));
+ a.cmp(TMP4, imm(_TAG_HEADER_MAP));
+
+ if (Fail.get() == 0) {
+ a.b_eq(good_map);
+ a.bind(error);
+ {
+ mov_var(XREG0, src);
+ fragment_call(ga->get_handle_map_size_error());
+ }
+ } else {
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
+ a.bind(error); /* Never referenced. */
+ }
}
a.bind(good_map);
@@ -639,6 +992,180 @@ void BeamModuleAssembler::emit_bif_map_size(const ArgLabel &Fail,
}
/* ================================================================
+ * min/2
+ * max/2
+ * ================================================================
+ */
+
+void BeamModuleAssembler::emit_bif_min_max(arm::CondCode cc,
+ const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ auto [lhs, rhs] = load_sources(LHS, ARG1, RHS, ARG2);
+ auto dst = init_destination(Dst, ARG1);
+ bool both_small = always_small(LHS) && always_small(RHS);
+ bool need_generic = !both_small;
+ Label generic = a.newLabel(), next = a.newLabel();
+
+ if (both_small) {
+ comment("skipped test for small operands since they are always small");
+ } else if (always_small(RHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ LHS)) {
+ comment("simplified test for small operand");
+ emit_is_not_boxed(generic, lhs.reg);
+ } else if (always_small(LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ RHS)) {
+ comment("simplified test for small operand");
+ emit_is_not_boxed(generic, rhs.reg);
+ } else if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ RHS)) {
+ comment("simplified test for small operands");
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.and_(TMP1, lhs.reg, rhs.reg);
+ emit_is_not_boxed(generic, TMP1);
+ } else {
+ if (RHS.isSmall()) {
+ a.and_(TMP1, lhs.reg, imm(_TAG_IMMED1_MASK));
+ } else if (LHS.isSmall()) {
+ a.and_(TMP1, rhs.reg, imm(_TAG_IMMED1_MASK));
+ } else {
+ /* Avoid the expensive generic comparison for equal terms. */
+ a.cmp(lhs.reg, rhs.reg);
+ a.b_eq(next);
+
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.and_(TMP1, lhs.reg, rhs.reg);
+ a.and_(TMP1, TMP1, imm(_TAG_IMMED1_MASK));
+ }
+
+ a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
+ a.b_ne(generic);
+ }
+
+ /* Both arguments are smalls. */
+ a.cmp(lhs.reg, rhs.reg);
+ if (need_generic) {
+ a.b(next);
+ }
+
+ a.bind(generic);
+ if (need_generic) {
+ mov_var(ARG1, lhs);
+ mov_var(ARG2, rhs);
+ fragment_call(ga->get_arith_compare_shared());
+ load_sources(LHS, ARG1, RHS, ARG2);
+ }
+
+ a.bind(next);
+ a.csel(dst.reg, rhs.reg, lhs.reg, cc);
+ flush_var(dst);
+}
+
+void BeamModuleAssembler::emit_bif_max(const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ emit_bif_min_max(arm::CondCode::kLT, LHS, RHS, Dst);
+}
+
+void BeamModuleAssembler::emit_bif_min(const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ emit_bif_min_max(arm::CondCode::kGT, LHS, RHS, Dst);
+}
+
+/* ================================================================
+ * node/1
+ * ================================================================
+ */
+
+void BeamGlobalAssembler::emit_handle_node_error() {
+ static ErtsCodeMFA mfa = {am_erlang, am_node, 1};
+ emit_raise_badarg(&mfa);
+}
+
+void BeamModuleAssembler::emit_bif_node(const ArgLabel &Fail,
+ const ArgRegister &Src,
+ const ArgRegister &Dst) {
+ bool always_identifier = always_one_of<BeamTypeId::Identifier>(Src);
+ Label test_internal = a.newLabel();
+ Label internal = a.newLabel();
+ Label next = a.newLabel();
+ auto src = load_source(Src, ARG2);
+ Label fail;
+
+ if (Fail.get() != 0) {
+ fail = resolve_beam_label(Fail, dispUnknown);
+ } else if (!always_identifier) {
+ fail = a.newLabel();
+ }
+
+ emit_is_boxed(test_internal, Src, src.reg);
+
+ arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
+
+ if (!always_one_of<BeamTypeId::Pid, BeamTypeId::Port>(Src)) {
+ a.ldur(TMP2, emit_boxed_val(boxed_ptr));
+ a.and_(TMP2, TMP2, imm(_TAG_HEADER_MASK));
+ }
+
+ if (maybe_one_of<BeamTypeId::Reference>(Src)) {
+ a.cmp(TMP2, imm(_TAG_HEADER_REF));
+ a.b_eq(internal);
+ }
+
+ if (!always_identifier) {
+ Label external = a.newLabel();
+ ERTS_CT_ASSERT((_TAG_HEADER_EXTERNAL_PORT - _TAG_HEADER_EXTERNAL_PID) >>
+ _TAG_PRIMARY_SIZE ==
+ 1);
+ ERTS_CT_ASSERT((_TAG_HEADER_EXTERNAL_REF - _TAG_HEADER_EXTERNAL_PORT) >>
+ _TAG_PRIMARY_SIZE ==
+ 1);
+ a.sub(TMP3, TMP2, imm(_TAG_HEADER_EXTERNAL_PID));
+ a.cmp(TMP3, imm(_TAG_HEADER_EXTERNAL_REF - _TAG_HEADER_EXTERNAL_PID));
+
+ if (Fail.get() != 0) {
+ a.b_hi(fail);
+ } else {
+ a.b_ls(external);
+
+ a.bind(fail);
+ {
+ mov_var(XREG0, src);
+ fragment_call(ga->get_handle_node_error());
+ }
+ }
+
+ a.bind(external);
+ }
+
+ a.ldur(TMP1, emit_boxed_val(boxed_ptr, offsetof(ExternalThing, node)));
+ a.b(next);
+
+ a.bind(test_internal);
+ if (!always_identifier) {
+ a.and_(TMP1, src.reg, imm(_TAG_IMMED1_MASK));
+ a.cmp(TMP1, imm(_TAG_IMMED1_PID));
+ a.ccmp(TMP1,
+ imm(_TAG_IMMED1_PORT),
+ imm(NZCV::kEqual),
+ imm(arm::CondCode::kNE));
+ a.b_ne(fail);
+ }
+
+ a.bind(internal);
+ a.ldr(TMP1, embed_constant(&erts_this_node, disp32K));
+ a.ldr(TMP1, arm::Mem(TMP1));
+
+ a.bind(next);
+ mov_arg(Dst, arm::Mem(TMP1, offsetof(ErlNode, sysname)));
+}
+
+/* ================================================================
* not/1
* ================================================================
*/
@@ -699,10 +1226,17 @@ void BeamModuleAssembler::emit_bif_or(const ArgLabel &Fail,
ERTS_CT_ASSERT(am_false == make_atom(0));
ERTS_CT_ASSERT(am_true == make_atom(1));
- a.and_(TMP3, src1.reg, imm(_TAG_IMMED2_MASK | ~diff_bit));
- a.and_(TMP4, src2.reg, imm(_TAG_IMMED2_MASK | ~diff_bit));
- a.cmp(TMP3, imm(_TAG_IMMED2_ATOM));
- a.ccmp(TMP3, TMP4, imm(NZCV::kNone), imm(arm::CondCode::kEQ));
+ if (exact_type<BeamTypeId::Atom>(Src1) &&
+ exact_type<BeamTypeId::Atom>(Src2)) {
+ comment("simplified type check because operands are atoms");
+ a.orr(TMP3, src1.reg, src2.reg);
+ a.tst(TMP3, imm(-1ull << (_TAG_IMMED2_SIZE + 1)));
+ } else {
+ a.and_(TMP3, src1.reg, imm(_TAG_IMMED2_MASK | ~diff_bit));
+ a.and_(TMP4, src2.reg, imm(_TAG_IMMED2_MASK | ~diff_bit));
+ a.cmp(TMP3, imm(_TAG_IMMED2_ATOM));
+ a.ccmp(TMP3, TMP4, imm(NZCV::kNone), imm(arm::CondCode::kEQ));
+ }
if (Fail.get()) {
a.b_ne(resolve_beam_label(Fail, disp1MB));
@@ -805,15 +1339,27 @@ void BeamModuleAssembler::emit_bif_tuple_size(const ArgLabel &Fail,
auto src = load_source(Src, ARG1);
auto dst = init_destination(Dst, ARG1);
- mov_var(ARG1, src);
-
- if (Fail.get() == 0) {
- fragment_call(ga->get_bif_tuple_size_body());
+ if (exact_type<BeamTypeId::Tuple>(Src)) {
+ comment("simplifed tuple_size/1 because the argument is always a "
+ "tuple");
+ arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
+ a.ldur(TMP1, emit_boxed_val(boxed_ptr));
+ ERTS_CT_ASSERT(_HEADER_ARITY_OFFS - _TAG_IMMED1_SIZE > 0);
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.lsr(TMP1, TMP1, _HEADER_ARITY_OFFS - _TAG_IMMED1_SIZE);
+ a.orr(dst.reg, TMP1, imm(_TAG_IMMED1_SMALL));
} else {
- fragment_call(ga->get_bif_tuple_size_guard());
- emit_branch_if_not_value(ARG1, resolve_beam_label(Fail, dispUnknown));
- }
+ mov_var(ARG1, src);
+
+ if (Fail.get() == 0) {
+ fragment_call(ga->get_bif_tuple_size_body());
+ } else {
+ fragment_call(ga->get_bif_tuple_size_guard());
+ emit_branch_if_not_value(ARG1,
+ resolve_beam_label(Fail, dispUnknown));
+ }
- mov_var(dst, ARG1);
+ mov_var(dst, ARG1);
+ }
flush_var(dst);
}
diff --git a/erts/emulator/beam/jit/arm/instr_map.cpp b/erts/emulator/beam/jit/arm/instr_map.cpp
index 651461112d..8d7ad6f45f 100644
--- a/erts/emulator/beam/jit/arm/instr_map.cpp
+++ b/erts/emulator/beam/jit/arm/instr_map.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ using namespace asmjit;
extern "C"
{
#include "erl_map.h"
+#include "erl_term_hashing.h"
#include "beam_common.h"
}
@@ -52,6 +53,11 @@ void BeamGlobalAssembler::emit_internal_hash_helper() {
a.add(lower, lower, constant);
a.add(upper, upper, constant);
+#if defined(ERL_INTERNAL_HASH_CRC32C)
+ a.crc32cw(lower, hash, lower);
+ a.add(hash, hash, lower);
+ a.crc32cw(hash, hash, upper);
+#else
using rounds =
std::initializer_list<std::tuple<a64::Gp, a64::Gp, a64::Gp, int>>;
for (const auto &round : rounds{{lower, upper, hash, 13},
@@ -74,6 +80,7 @@ void BeamGlobalAssembler::emit_internal_hash_helper() {
a.eor(r_a, r_a, r_c, arm::lsl(-shift));
}
}
+#endif
#ifdef DBG_HASHMAP_COLLISION_BONANZA
emit_enter_runtime_frame();
@@ -245,14 +252,14 @@ void BeamGlobalAssembler::emit_flatmap_get_element() {
void BeamGlobalAssembler::emit_new_map_shared() {
emit_enter_runtime_frame();
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<5>(erts_gc_new_map);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
emit_leave_runtime_frame();
@@ -272,22 +279,6 @@ void BeamModuleAssembler::emit_new_map(const ArgRegister &Dst,
mov_arg(Dst, ARG1);
}
-void BeamGlobalAssembler::emit_i_new_small_map_lit_shared() {
- emit_enter_runtime_frame();
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
- Update::eReductions>();
-
- a.mov(ARG1, c_p);
- load_x_reg_array(ARG2);
- runtime_call<5>(erts_gc_new_small_map_lit);
-
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
- Update::eReductions>();
- emit_leave_runtime_frame();
-
- a.ret(a64::x30);
-}
-
void BeamModuleAssembler::emit_i_new_small_map_lit(const ArgRegister &Dst,
const ArgWord &Live,
const ArgLiteral &Keys,
@@ -295,15 +286,50 @@ void BeamModuleAssembler::emit_i_new_small_map_lit(const ArgRegister &Dst,
const Span<ArgVal> &args) {
ASSERT(Size.get() == args.size());
- embed_vararg_rodata(args, ARG5);
+ emit_gc_test(ArgWord(0),
+ ArgWord(args.size() + MAP_HEADER_FLATMAP_SZ + 1),
+ Live);
- ASSERT(Keys.isLiteral());
- mov_arg(ARG3, Keys);
- mov_arg(ARG4, Live);
+ std::vector<ArgVal> data;
+ data.reserve(args.size() + MAP_HEADER_FLATMAP_SZ + 1);
+ data.push_back(ArgWord(MAP_HEADER_FLATMAP));
+ data.push_back(Size);
+ data.push_back(Keys);
- fragment_call(ga->get_i_new_small_map_lit_shared());
+ bool dst_is_src = false;
+ for (auto arg : args) {
+ data.push_back(arg);
+ dst_is_src |= (arg == Dst);
+ }
- mov_arg(Dst, ARG1);
+ if (dst_is_src) {
+ a.add(TMP1, HTOP, TAG_PRIMARY_BOXED);
+ } else {
+ auto ptr = init_destination(Dst, TMP1);
+ a.add(ptr.reg, HTOP, TAG_PRIMARY_BOXED);
+ flush_var(ptr);
+ }
+
+ size_t size = data.size();
+ unsigned i;
+ for (i = 0; i < size - 1; i += 2) {
+ if ((i % 128) == 0) {
+ check_pending_stubs();
+ }
+
+ auto [first, second] = load_sources(data[i], TMP2, data[i + 1], TMP3);
+ a.stp(first.reg, second.reg, arm::Mem(HTOP).post(sizeof(Eterm[2])));
+ }
+
+ if (i < size) {
+ mov_arg(arm::Mem(HTOP).post(sizeof(Eterm)), data[i]);
+ }
+
+ if (dst_is_src) {
+ auto ptr = init_destination(Dst, TMP1);
+ mov_var(ptr, TMP1);
+ flush_var(ptr);
+ }
}
/* ARG1 = map
@@ -369,7 +395,7 @@ void BeamModuleAssembler::emit_i_get_map_element(const ArgLabel &Fail,
mov_arg(ARG1, Src);
mov_arg(ARG2, Key);
- if (masked_types(Key, BEAM_TYPE_MASK_IMMEDIATE) != BEAM_TYPE_NONE) {
+ if (maybe_one_of<BeamTypeId::MaybeImmediate>(Key)) {
fragment_call(ga->get_i_get_map_element_shared());
a.b_ne(resolve_beam_label(Fail, disp1MB));
} else {
@@ -540,14 +566,14 @@ void BeamModuleAssembler::emit_i_get_map_element_hash(const ArgLabel &Fail,
/* ARG3 = live registers, ARG4 = update vector size, ARG5 = update vector. */
void BeamGlobalAssembler::emit_update_map_assoc_shared() {
emit_enter_runtime_frame();
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<5>(erts_gc_update_map_assoc);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
emit_leave_runtime_frame();
@@ -579,14 +605,14 @@ void BeamModuleAssembler::emit_update_map_assoc(const ArgSource &Src,
* Result is returned in RET, error is indicated by ZF. */
void BeamGlobalAssembler::emit_update_map_exact_guard_shared() {
emit_enter_runtime_frame();
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<5>(erts_gc_update_map_exact);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
emit_leave_runtime_frame();
@@ -600,14 +626,14 @@ void BeamGlobalAssembler::emit_update_map_exact_body_shared() {
Label error = a.newLabel();
emit_enter_runtime_frame();
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<5>(erts_gc_update_map_exact);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
emit_leave_runtime_frame();
diff --git a/erts/emulator/beam/jit/arm/instr_msg.cpp b/erts/emulator/beam/jit/arm/instr_msg.cpp
index e512261d96..dd99af59c2 100644
--- a/erts/emulator/beam/jit/arm/instr_msg.cpp
+++ b/erts/emulator/beam/jit/arm/instr_msg.cpp
@@ -75,12 +75,12 @@ void BeamModuleAssembler::emit_i_recv_set() {
#endif /* ERTS_SUPPORT_OLD_RECV_MARK_INSTRS */
void BeamModuleAssembler::emit_recv_marker_reserve(const ArgRegister &Dst) {
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapAlloc>();
a.mov(ARG1, c_p);
runtime_call<1>(erts_msgq_recv_marker_insert);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapAlloc>();
mov_arg(Dst, ARG1);
}
@@ -175,7 +175,7 @@ void BeamGlobalAssembler::emit_i_loop_rec_shared() {
a.cbnz(ARG1, check_is_distributed);
comment("Inner queue empty, fetch more from outer/middle queues");
- emit_enter_runtime<Update::eReductions | Update::eStack |
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc |
Update::eHeap>(0);
a.str(ZERO, message_ptr);
@@ -196,8 +196,7 @@ void BeamGlobalAssembler::emit_i_loop_rec_shared() {
* Also note that another process may have loaded new code and sent us
* a message to notify us about it, so we must update the active code
* index. */
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eCodeIndex>(
- 0);
+ emit_leave_runtime<Update::eHeapAlloc | Update::eCodeIndex>(0);
a.sub(FCALLS, FCALLS, ARG1);
diff --git a/erts/emulator/beam/jit/arm/instr_select.cpp b/erts/emulator/beam/jit/arm/instr_select.cpp
index 41c9b0a95c..b5a4bb290c 100644
--- a/erts/emulator/beam/jit/arm/instr_select.cpp
+++ b/erts/emulator/beam/jit/arm/instr_select.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -102,6 +102,7 @@ static std::pair<UWord, int> plan_untag(const Span<ArgVal> &args) {
}
const std::vector<ArgVal> BeamModuleAssembler::emit_select_untag(
+ const ArgSource &Src,
const Span<ArgVal> &args,
a64::Gp comparand,
Label fail,
@@ -112,16 +113,21 @@ const std::vector<ArgVal> BeamModuleAssembler::emit_select_untag(
/* Emit code to test that the source value has the correct type and
* untag it. */
comment("(comparing untagged+rebased values)");
- if (args.front().isSmall()) {
- a.and_(TMP1, comparand, imm(_TAG_IMMED1_MASK));
- a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
+ if ((args.front().isSmall() && always_small(Src)) ||
+ (args.front().isAtom() && exact_type<BeamTypeId::Atom>(Src))) {
+ comment("(skipped type test)");
} else {
- ASSERT(args.front().isAtom());
- a.and_(TMP1, comparand, imm(_TAG_IMMED2_MASK));
- a.cmp(TMP1, imm(_TAG_IMMED2_ATOM));
- }
+ if (args.front().isSmall()) {
+ a.and_(TMP1, comparand, imm(_TAG_IMMED1_MASK));
+ a.cmp(TMP1, imm(_TAG_IMMED1_SMALL));
+ } else {
+ ASSERT(args.front().isAtom());
+ a.and_(TMP1, comparand, imm(_TAG_IMMED2_MASK));
+ a.cmp(TMP1, imm(_TAG_IMMED2_ATOM));
+ }
- a.b_ne(resolve_label(fail, disp1MB));
+ a.b_ne(resolve_label(fail, disp1MB));
+ }
if (shift != 0) {
a.lsr(ARG1, comparand, imm(shift));
@@ -222,7 +228,7 @@ void BeamModuleAssembler::emit_i_select_tuple_arity(const ArgRegister &Src,
arm::Gp boxed_ptr = emit_ptr_val(TMP1, src.reg);
a.ldur(TMP1, emit_boxed_val(boxed_ptr, 0));
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_TUPLE) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Tuple) {
comment("simplified tuple test since the source is always a tuple "
"when boxed");
} else {
@@ -281,7 +287,8 @@ void BeamModuleAssembler::emit_i_select_val_lins(const ArgSource &Src,
emit_linear_search(src.reg, fail, args);
}
} else {
- auto untagged = emit_select_untag(args, src.reg, next, base, shift);
+ auto untagged =
+ emit_select_untag(Src, args, src.reg, next, base, shift);
if (!emit_optimized_three_way_select(ARG1, fail, untagged)) {
emit_linear_search(ARG1, fail, untagged);
@@ -322,7 +329,8 @@ void BeamModuleAssembler::emit_i_select_val_bins(const ArgSource &Src,
if (base == 0 && shift == 0) {
emit_binsearch_nodes(src.reg, 0, count - 1, fail, args);
} else {
- auto untagged = emit_select_untag(args, src.reg, fail, base, shift);
+ auto untagged =
+ emit_select_untag(Src, args, src.reg, fail, base, shift);
emit_binsearch_nodes(ARG1, 0, count - 1, fail, untagged);
}
@@ -407,17 +415,21 @@ void BeamModuleAssembler::emit_i_jump_on_val(const ArgSource &Src,
ASSERT(Size.get() == args.size());
- a.and_(TMP3, src.reg, imm(_TAG_IMMED1_MASK));
- a.cmp(TMP3, imm(_TAG_IMMED1_SMALL));
-
- if (Fail.isLabel()) {
- a.b_ne(resolve_beam_label(Fail, disp1MB));
+ if (always_small(Src)) {
+ comment("(skipped type test)");
} else {
- /* NIL means fallthrough to the next instruction. */
- ASSERT(Fail.isNil());
+ a.and_(TMP3, src.reg, imm(_TAG_IMMED1_MASK));
+ a.cmp(TMP3, imm(_TAG_IMMED1_SMALL));
- fail = a.newLabel();
- a.b_ne(fail);
+ if (Fail.isLabel()) {
+ a.b_ne(resolve_beam_label(Fail, disp1MB));
+ } else {
+ /* NIL means fallthrough to the next instruction. */
+ ASSERT(Fail.isNil());
+
+ fail = a.newLabel();
+ a.b_ne(fail);
+ }
}
a.asr(TMP1, src.reg, imm(_TAG_IMMED1_SIZE));
diff --git a/erts/emulator/beam/jit/arm/instr_trace.cpp b/erts/emulator/beam/jit/arm/instr_trace.cpp
index b8a64d2628..1db21b7446 100644
--- a/erts/emulator/beam/jit/arm/instr_trace.cpp
+++ b/erts/emulator/beam/jit/arm/instr_trace.cpp
@@ -37,7 +37,7 @@ void BeamGlobalAssembler::emit_generic_bp_global() {
lea(ARG2, arm::Mem(ARG1, offsetof(Export, info)));
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
a.mov(ARG1, c_p);
@@ -45,7 +45,7 @@ void BeamGlobalAssembler::emit_generic_bp_global() {
load_x_reg_array(ARG3);
runtime_call<3>(erts_generic_breakpoint);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
/* This is technically a tail call so we must leave the current frame
@@ -72,7 +72,7 @@ void BeamGlobalAssembler::emit_generic_bp_local() {
a.sub(ARG2, ARG2, imm(BEAM_ASM_FUNC_PROLOGUE_SIZE + sizeof(ErtsCodeInfo)));
emit_enter_runtime_frame();
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
a.mov(ARG1, c_p);
@@ -80,7 +80,7 @@ void BeamGlobalAssembler::emit_generic_bp_local() {
load_x_reg_array(ARG3);
runtime_call<3>(erts_generic_breakpoint);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
a.cmp(ARG1, imm(BeamOpCodeAddr(op_i_debug_breakpoint)));
@@ -101,7 +101,7 @@ void BeamGlobalAssembler::emit_debug_bp() {
a.ldr(ARG2, TMP_MEM1q);
a.sub(ARG2, ARG2, imm(BEAM_ASM_FUNC_PROLOGUE_SIZE + sizeof(ErtsCodeMFA)));
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
a.mov(ARG1, c_p);
@@ -109,7 +109,7 @@ void BeamGlobalAssembler::emit_debug_bp() {
a.mov(ARG4, imm(am_breakpoint));
runtime_call<4>(call_error_handler);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>();
/* We skip two runtime frames (ours and the one entered in the module
@@ -147,62 +147,63 @@ void BeamModuleAssembler::emit_return_trace() {
lea(ARG4, getYRef(1));
ERTS_CT_ASSERT(ERTS_HIGHEST_CALLEE_SAVE_XREG >= 1);
- emit_enter_runtime<Update::eStack | Update::eHeap>(1);
+ emit_enter_runtime<Update::eHeapAlloc>(1);
a.mov(ARG1, c_p);
runtime_call<4>(return_trace);
- emit_leave_runtime<Update::eStack | Update::eHeap>(1);
+ emit_leave_runtime<Update::eHeapAlloc>(1);
- emit_deallocate(ArgVal(ArgVal::Word, 2));
+ emit_deallocate(ArgVal(ArgVal::Word, BEAM_RETURN_TRACE_FRAME_SZ));
emit_return();
}
-void BeamModuleAssembler::emit_i_return_time_trace() {
+void BeamModuleAssembler::emit_i_call_trace_return() {
/* Pass prev_info if present (is a CP), otherwise null. */
a.ldr(ARG2, getYRef(0));
- mov_imm(ARG3, 0);
+ mov_imm(ARG4, 0);
a.tst(ARG2, imm(_CPMASK));
a.sub(ARG2, ARG2, imm(sizeof(ErtsCodeInfo)));
- a.csel(ARG2, ARG2, ARG3, arm::CondCode::kEQ);
+ a.csel(ARG2, ARG2, ARG4, arm::CondCode::kEQ);
+ a.ldr(ARG3, getYRef(1));
ERTS_CT_ASSERT(ERTS_HIGHEST_CALLEE_SAVE_XREG >= 1);
- emit_enter_runtime<Update::eStack | Update::eHeap>(1);
+ emit_enter_runtime<Update::eHeapAlloc>(1);
a.mov(ARG1, c_p);
- runtime_call<2>(erts_trace_time_return);
+ runtime_call<3>(erts_call_trace_return);
- emit_leave_runtime<Update::eStack | Update::eHeap>(1);
+ emit_leave_runtime<Update::eHeapAlloc>(1);
- emit_deallocate(ArgVal(ArgVal::Word, 1));
+ emit_deallocate(ArgVal(ArgVal::Word, BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ));
emit_return();
}
void BeamModuleAssembler::emit_i_return_to_trace() {
ERTS_CT_ASSERT(ERTS_HIGHEST_CALLEE_SAVE_XREG >= 1);
- emit_enter_runtime<Update::eStack | Update::eHeap>(1);
+ emit_enter_runtime<Update::eHeapAlloc>(1);
a.mov(ARG1, c_p);
runtime_call<1>(beam_jit_return_to_trace);
- emit_leave_runtime<Update::eStack | Update::eHeap>(1);
+ emit_leave_runtime<Update::eHeapAlloc>(1);
- emit_deallocate(ArgVal(ArgVal::Word, 0));
+ emit_deallocate(ArgVal(ArgVal::Word, BEAM_RETURN_TO_TRACE_FRAME_SZ));
emit_return();
}
void BeamModuleAssembler::emit_i_hibernate() {
Label error = a.newLabel();
- emit_enter_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_enter_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(3);
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<2>(erts_hibernate);
- emit_leave_runtime<Update::eStack | Update::eHeap | Update::eXRegs |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eXRegs |
Update::eReductions>(3);
a.cbz(ARG1, error);
diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab
index 9cd7a9d925..6d27a18c2d 100644
--- a/erts/emulator/beam/jit/arm/ops.tab
+++ b/erts/emulator/beam/jit/arm/ops.tab
@@ -79,7 +79,7 @@ nif_start
i_generic_breakpoint
i_debug_breakpoint
-i_return_time_trace
+i_call_trace_return
i_return_to_trace
trace_jump W
i_yield
@@ -271,15 +271,19 @@ i_get_tuple_element Tuple Pos Tuple2 | current_tuple Tuple3 |
current_tuple Tuple => _
+i_get_tuple_element Tuple Pos Dst | current_tuple d | swap R1 R2 =>
+ i_get_tuple_element Tuple Pos Dst | swap R1 R2
+
load_tuple_ptr s
# If positions are in consecutive memory, fetch and store two words at
# once.
+## FIXME: Fix this bug in maint, too.
i_get_tuple_element Tuple Pos1 Dst1 |
current_tuple Tuple2 |
get_tuple_element Tuple3 Pos2 Dst2 |
equal(Tuple, Tuple2) | equal(Tuple, Tuple3) |
- consecutive_words(Pos1, Pos2) =>
+ consecutive_words(Pos1, Pos2) | distinct(Dst1, Dst2) =>
get_two_tuple_elements Tuple Pos1 Dst1 Dst2 |
current_tuple Tuple Dst2
@@ -293,6 +297,14 @@ current_tuple Tuple Dst => current_tuple Tuple
i_get_tuple_element s P S
get_two_tuple_elements s P S S
+i_get_tuple_element Tuple Pos Dst | swap Reg1 Reg2 | equal(Dst, Reg1) =>
+ get_tuple_element_swap Tuple Pos Dst Reg2
+
+i_get_tuple_element Tuple Pos Dst | swap Reg2 Reg1 | equal(Dst, Reg1) =>
+ get_tuple_element_swap Tuple Pos Dst Reg2
+
+get_tuple_element_swap s P d d
+
#
# Exception raising instructions. Infrequently executed.
#
@@ -359,6 +371,10 @@ move S1=y D1=y | move S2=y D2=y |
consecutive_words(D2, D1) =>
move_two_yregs S2 D2 S1 D1
+move Src Dst | trim N u => move_trim Src Dst N
+
+move_trim s d t
+
move Src Dst => i_move Src Dst
i_move s d
@@ -455,6 +471,41 @@ is_eq_exact f s s
is_ne_exact f s s
+is_integer NotInt N0 | is_ge Small N1=xy Min=i | is_ge Large Max=i N2=xy |
+ equal(N0, N1) | equal(N1, N2) |
+ equal(NotInt, Small) | equal(Small, Large) =>
+ is_int_in_range NotInt N0 Min Max
+
+is_integer NotInt N0 | is_ge Large Max=i N2=xy | is_ge Small N1=xy Min=i |
+ equal(N0, N1) | equal(N1, N2) |
+ equal(NotInt, Small) | equal(Small, Large) =>
+ is_int_in_range NotInt N0 Min Max
+
+is_integer NotInt N0 | is_ge Fail N1=xy Min=i |
+ equal(N0, N1) | equal(NotInt, Fail) =>
+ is_int_ge NotInt N0 Min
+
+is_int_in_range f S c c
+is_int_ge f S c
+
+is_ge Small N1=xy Min=i | is_ge Large Max=i N2=xy | equal(N1, N2) =>
+ is_in_range Small Large N1 Min Max
+
+is_ge Large Max=i N2=xy | is_ge Small N1=xy Min=i | equal(N1, N2) =>
+ is_in_range Small Large N2 Min Max
+
+is_in_range f f S c c
+
+is_ge Small N1=xy A=i | is_lt Large B=i N2=xy | equal(N1, N2) =>
+ is_ge_lt Small Large N1 A B
+
+is_ge_lt f f S c c
+
+is_ge Fail1 N1=xy A=i | is_ge Fail2 N2=xy B=i | equal(N1, N2) =>
+ is_ge_ge Fail1 Fail2 N1 A B
+
+is_ge_ge f f S c c
+
is_lt f s s
is_ge f s s
@@ -694,6 +745,9 @@ bif_or j s s d
bif1 Fail Bif=u$bif:erlang:not/1 Src=d Dst=d => bif_not Fail Src Dst
bif_not j S d
+bif1 Fail Bif=u$bif:erlang:node/1 Src=d Dst=d => bif_node Fail Src Dst
+bif_node j S d
+
gc_bif1 Fail Live Bif=u$bif:erlang:bit_size/1 Src Dst=d =>
bif_bit_size Fail Src Dst
bif_bit_size j s d
@@ -706,6 +760,21 @@ bif1 Fail Bif=u$bif:erlang:tuple_size/1 Src=d Dst=d =>
bif_tuple_size Fail Src Dst
bif_tuple_size j S d
+bif2 Fail Bif=u$bif:erlang:map_get/2 Src1 Src2=xy Dst=d =>
+ bif_map_get Fail Src1 Src2 Dst
+bif_map_get j s s d
+
+bif2 Fail Bif=u$bif:erlang:is_map_key/2 Key Map=xy Dst=d =>
+ bif_is_map_key Bif Fail Key Map Dst
+bif_is_map_key b j s s d
+
+bif2 Fail Bif=u$bif:erlang:max/2 Src1 Src2 Dst =>
+ bif_max Src1 Src2 Dst
+bif2 Fail Bif=u$bif:erlang:min/2 Src1 Src2 Dst =>
+ bif_min Src1 Src2 Dst
+bif_max s s d
+bif_min s s d
+
bif1 Fail Bif S1 Dst | never_fails(Bif) => nofail_bif1 S1 Bif Dst
bif2 Fail Bif S1 S2 Dst | never_fails(Bif) => nofail_bif2 S1 S2 Bif Dst
@@ -714,6 +783,8 @@ bif2 Fail Bif S1 S2 Dst => i_bif2 S1 S2 Fail Bif Dst
nofail_bif2 S1=d S2 Bif Dst | is_eq_exact_bif(Bif) => bif_is_eq_exact S1 S2 Dst
nofail_bif2 S1=d S2 Bif Dst | is_ne_exact_bif(Bif) => bif_is_ne_exact S1 S2 Dst
+nofail_bif2 S1 S2 Bif Dst | is_ge_bif(Bif) => bif_is_ge S1 S2 Dst
+nofail_bif2 S1 S2 Bif Dst | is_lt_bif(Bif) => bif_is_lt S1 S2 Dst
i_get_hash c I d
i_get s d
@@ -731,11 +802,19 @@ i_bif3 s s s j b d
bif_is_eq_exact S s d
bif_is_ne_exact S s d
+bif_is_ge s s d
+bif_is_lt s s d
#
# Internal calls.
#
+i_move S=y==0 Dst | call_last Ar P D => move_call_last S Dst P D
+i_move S=y==0 Dst | call_ext_last Ar P=u$is_not_bif D => move_call_ext_last S Dst P D
+
+move_call_last y d f t
+move_call_ext_last y d e t
+
call Ar Func => i_call Func
call_last Ar Func D => i_call_last Func D
call_only Ar Func => i_call_only Func
@@ -842,22 +921,38 @@ i_flush_stubs
i_breakpoint_trampoline
# ================================================================
-# New bit syntax matching (R11B).
+# New bit syntax matching for fixed sizes (from OTP 26).
+# ================================================================
+
+bs_match Fail Ctx Size Rest=* => bs_match(Fail, Ctx, Size, Rest)
+
+i_bs_match Fail Ctx Rest=* | test_heap Need Live =>
+ i_bs_match_test_heap Fail Ctx Need Live Rest
+
+i_bs_match f S *
+i_bs_match_test_heap f S I t *
+
+#
+# The following instruction is specially handled in beam_load.c
+# to produce a user-friendly message if a bad bs_match instruction
+# is encountered.
+#
+bad_bs_match/1
+bad_bs_match A | never() => _
+
+# ================================================================
+# Bit syntax matching (from R11B).
# ================================================================
%warm
-# Matching integers
+# Matching integers.
bs_match_string Fail Ms Bits Val => i_bs_match_string Ms Fail Bits Val
i_bs_match_string S f W M
# Fetching integers from binaries.
-bs_get_integer2 Fail=f Ms=xy Live=u Sz=sq Unit=u Flags=u Dst=d =>
- get_integer2(Fail, Ms, Live, Sz, Unit, Flags, Dst)
-
-i_bs_get_integer S f t t s d
-i_bs_get_fixed_integer S f t t t d
+bs_get_integer2 f S t s t t d
# Fetching binaries from binaries.
bs_get_binary2 Fail=f Ms=xy Live=u Sz=sq Unit=u Flags=u Dst=d =>
@@ -1384,12 +1479,11 @@ recv_marker_use S
# Mark all intentionally unused macros, predicates, and generators.
#
-%unused pred.negation_is_small
-
-%unused gen_increment
-%unused gen.increment
-%unused gen.increment_from_minus
-%unused gen.plus_from_minus
-
# Landing pad for fun calls/apply where we set up arguments and check errors
i_lambda_trampoline F f W W
+
+#
+# OTP 26
+#
+
+update_record a I s d I *
diff --git a/erts/emulator/beam/jit/arm/predicates.tab b/erts/emulator/beam/jit/arm/predicates.tab
index fc55441c28..3a1fa0219b 100644
--- a/erts/emulator/beam/jit/arm/predicates.tab
+++ b/erts/emulator/beam/jit/arm/predicates.tab
@@ -2,7 +2,7 @@
//
// %CopyrightBegin%
//
-// Copyright Ericsson AB 2020-2021. All Rights Reserved.
+// Copyright Ericsson AB 2020-2023. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ pred.is_mfa_bif(M, F, A) {
pred.never_fails(Bif) {
static Eterm nofail_bifs[] =
{am_Neqeq,
- am_Le,
+ am_Lt,
am_Neq,
am_Eq,
am_Le,
@@ -84,6 +84,7 @@ pred.never_fails(Bif) {
pred.consecutive_words(A1, A2) {
return A1.type == A2.type && A1.val + 1 == A2.val;
}
+
pred.is_eq_exact_bif(Bif) {
Uint index = Bif.val;
@@ -105,3 +106,25 @@ pred.is_ne_exact_bif(Bif) {
}
return 0;
}
+
+pred.is_ge_bif(Bif) {
+ Uint index = Bif.val;
+
+ if (Bif.type == TAG_u && index < S->beam.imports.count) {
+ BeamFile_ImportEntry *entry = &S->beam.imports.entries[index];
+
+ return entry->module == am_erlang && entry->function == am_Ge && entry->arity == 2;
+ }
+ return 0;
+}
+
+pred.is_lt_bif(Bif) {
+ Uint index = Bif.val;
+
+ if (Bif.type == TAG_u && index < S->beam.imports.count) {
+ BeamFile_ImportEntry *entry = &S->beam.imports.entries[index];
+
+ return entry->module == am_erlang && entry->function == am_Lt && entry->arity == 2;
+ }
+ return 0;
+}
diff --git a/erts/emulator/beam/jit/asm_load.c b/erts/emulator/beam/jit/asm_load.c
index a3594a4800..023f084051 100644
--- a/erts/emulator/beam/jit/asm_load.c
+++ b/erts/emulator/beam/jit/asm_load.c
@@ -77,11 +77,15 @@ int beam_load_prepare_emit(LoaderState *stp) {
init_label(&stp->labels[i]);
}
+ stp->lambda_literals = erts_alloc(ERTS_ALC_T_PREPARED_CODE,
+ stp->beam.lambdas.count * sizeof(SWord));
+
for (i = 0; i < stp->beam.lambdas.count; i++) {
BeamFile_LambdaEntry *lambda = &stp->beam.lambdas.entries[i];
if (stp->labels[lambda->label].lambda_index == INVALID_LAMBDA_INDEX) {
stp->labels[lambda->label].lambda_index = i;
+ stp->lambda_literals[i] = ERTS_SWORD_MAX;
} else {
beam_load_report_error(__LINE__,
stp,
@@ -177,6 +181,11 @@ int beam_load_prepared_dtor(Binary *magic) {
stp->labels = NULL;
}
+ if (stp->lambda_literals) {
+ erts_free(ERTS_ALC_T_PREPARED_CODE, (void *)stp->lambda_literals);
+ stp->lambda_literals = NULL;
+ }
+
if (stp->bif_imports) {
erts_free(ERTS_ALC_T_PREPARED_CODE, stp->bif_imports);
stp->bif_imports = NULL;
@@ -197,13 +206,15 @@ int beam_load_prepared_dtor(Binary *magic) {
stp->ba = NULL;
}
- if (stp->native_module_exec) {
- ASSERT(stp->native_module_rw != NULL);
+ if (stp->executable_region) {
+ ASSERT(stp->writable_region != NULL);
- beamasm_purge_module(stp->native_module_exec, stp->native_module_rw);
+ beamasm_purge_module(stp->executable_region,
+ stp->writable_region,
+ stp->loaded_size);
- stp->native_module_exec = NULL;
- stp->native_module_rw = NULL;
+ stp->executable_region = NULL;
+ stp->writable_region = NULL;
}
return 1;
@@ -640,17 +651,17 @@ static const BeamCodeLineTab *finish_line_table(LoaderState *stp,
fname_base_ro = (Eterm *)&line_items_ro[num_instrs + 1];
locp_base_ro = &fname_base_ro[stp->beam.lines.name_count];
- line_tab_rw = get_writable_ptr(stp->native_module_exec,
- stp->native_module_rw,
+ line_tab_rw = get_writable_ptr(stp->executable_region,
+ stp->writable_region,
line_tab_ro);
- line_items_rw = get_writable_ptr(stp->native_module_exec,
- stp->native_module_rw,
+ line_items_rw = get_writable_ptr(stp->executable_region,
+ stp->writable_region,
line_items_ro);
- locp_base_rw = get_writable_ptr(stp->native_module_exec,
- stp->native_module_rw,
+ locp_base_rw = get_writable_ptr(stp->executable_region,
+ stp->writable_region,
locp_base_ro);
- fname_base_rw = get_writable_ptr(stp->native_module_exec,
- stp->native_module_rw,
+ fname_base_rw = get_writable_ptr(stp->executable_region,
+ stp->writable_region,
fname_base_ro);
line_tab_rw->loc_size = stp->beam.lines.location_size;
@@ -710,11 +721,12 @@ static const BeamCodeLineTab *finish_line_table(LoaderState *stp,
int beam_load_finish_emit(LoaderState *stp) {
const BeamCodeHeader *code_hdr_ro = NULL;
BeamCodeHeader *code_hdr_rw = NULL;
+ size_t module_size;
+ char *module_base;
Sint decoded_size;
- int i;
+ int ret;
- char *module_base;
- size_t module_size;
+ ret = 0;
if (stp->line_instr != 0) {
Uint line_size = offsetof(BeamCodeLineTab, func_tab);
@@ -755,18 +767,19 @@ int beam_load_finish_emit(LoaderState *stp) {
/* Move the code to its final location. */
beamasm_codegen(stp->ba,
- &stp->native_module_exec,
- &stp->native_module_rw,
+ &stp->executable_region,
+ &stp->writable_region,
stp->load_hdr,
&code_hdr_ro,
&code_hdr_rw);
- stp->code_hdr = code_hdr_ro;
-
+ stp->on_load = beamasm_get_on_load(stp->ba);
module_base = beamasm_get_base(stp->ba);
module_size = beamasm_get_offset(stp->ba);
- stp->on_load = beamasm_get_on_load(stp->ba);
+ /* Save the updated code pointer and code size. */
+ stp->code_hdr = code_hdr_ro;
+ stp->loaded_size = module_size;
/*
* Place the literals in their own allocated heap (for fast range check)
@@ -778,6 +791,7 @@ int beam_load_finish_emit(LoaderState *stp) {
ErtsLiteralArea *literal_area;
Uint tot_lit_size;
Uint lit_asize;
+ int i;
tot_lit_size = stp->beam.static_literals.heap_size +
stp->beam.dynamic_literals.heap_size;
@@ -795,12 +809,12 @@ int beam_load_finish_emit(LoaderState *stp) {
* more like string patching. */
for (i = 0; i < stp->beam.static_literals.count; i++) {
Eterm lit = beamfile_get_literal(&stp->beam, i);
- beamasm_patch_literal(stp->ba, stp->native_module_rw, i, lit);
+ beamasm_patch_literal(stp->ba, stp->writable_region, i, lit);
}
for (i = 0; i < stp->beam.dynamic_literals.count; i++) {
Eterm lit = beamfile_get_literal(&stp->beam, ~i);
- beamasm_patch_literal(stp->ba, stp->native_module_rw, ~i, lit);
+ beamasm_patch_literal(stp->ba, stp->writable_region, ~i, lit);
}
literal_area->off_heap = code_off_heap.first;
@@ -856,30 +870,45 @@ int beam_load_finish_emit(LoaderState *stp) {
/* Patch all instructions that refer to the string table. */
if (stp->beam.strings.size) {
const byte *string_table = beamasm_get_rodata(stp->ba, "str");
- beamasm_patch_strings(stp->ba, stp->native_module_rw, string_table);
+ beamasm_patch_strings(stp->ba, stp->writable_region, string_table);
}
- /* Save the updated code pointer and code size. */
-
- stp->loaded_size = module_size;
+ ret = 1;
+load_error:
- return 1;
+ /* Some platforms use per-thread global permissions where a thread can
+ * either write to or execute _ALL_ JITed pages, so we must seal the module
+ * before yielding or this thread won't be able to execute any other JITed
+ * code.
+ *
+ * Note that we have to do this regardless of whether we've succeeded or
+ * not, as the module is unsealed after code generation. */
+ beamasm_seal_module(stp->executable_region,
+ stp->writable_region,
+ stp->loaded_size);
-load_error:
- return 0;
+ return ret;
}
void beam_load_finalize_code(LoaderState *stp,
struct erl_module_instance *inst_p) {
- int staging_ix, code_size, i;
+ ErtsCodeIndex staging_ix;
+ int code_size, i;
+
+ ERTS_LC_ASSERT(erts_initialized == 0 || erts_has_code_load_permission() ||
+ erts_thr_progress_is_blocking());
code_size = beamasm_get_header(stp->ba, &stp->code_hdr);
erts_total_code_size += code_size;
- inst_p->native_module_exec = stp->native_module_exec;
- inst_p->native_module_rw = stp->native_module_rw;
+ inst_p->executable_region = stp->executable_region;
+ inst_p->writable_region = stp->writable_region;
inst_p->code_hdr = stp->code_hdr;
inst_p->code_length = code_size;
+ inst_p->unsealed = 0;
+
+ erts_unseal_module(inst_p);
+
#ifdef ADDRESS_SANITIZER
/*
* LeakSanitizer ignores directly mmap'ed memory by default. This causes
@@ -895,7 +924,7 @@ void beam_load_finalize_code(LoaderState *stp,
erts_update_ranges(inst_p->code_hdr, code_size);
/* Allocate catch indices and fix up all catch_yf instructions. */
- inst_p->catches = beamasm_patch_catches(stp->ba, stp->native_module_rw);
+ inst_p->catches = beamasm_patch_catches(stp->ba, stp->writable_region);
/* Exported functions */
staging_ix = erts_staging_code_ix();
@@ -922,17 +951,16 @@ void beam_load_finalize_code(LoaderState *stp,
* the module may remote-call itself*/
for (i = 0; i < stp->beam.imports.count; i++) {
BeamFile_ImportEntry *entry = &stp->beam.imports.entries[i];
- BeamInstr import;
+ Export *import;
- import = (BeamInstr)erts_export_put(entry->module,
- entry->function,
- entry->arity);
+ import = erts_export_put(entry->module, entry->function, entry->arity);
- beamasm_patch_import(stp->ba, stp->native_module_rw, i, import);
+ beamasm_patch_import(stp->ba, stp->writable_region, i, import);
}
/* Patch fun creation. */
if (stp->beam.lambdas.count) {
+ ErtsLiteralArea *literal_area = (stp->code_hdr)->literal_area;
BeamFile_LambdaTable *lambda_table = &stp->beam.lambdas;
for (i = 0; i < lambda_table->count; i++) {
@@ -948,31 +976,53 @@ void beam_load_finalize_code(LoaderState *stp,
lambda->index,
lambda->arity - lambda->num_free);
- if (erts_is_fun_loaded(fun_entry)) {
+ if (erts_is_fun_loaded(fun_entry, staging_ix)) {
/* We've reloaded a module over itself and inherited the old
* instance's fun entries, so we need to undo the reference
* bump in `erts_put_fun_entry2` to make fun purging work. */
erts_refc_dectest(&fun_entry->refc, 1);
}
- erts_set_fun_code(fun_entry, beamasm_get_lambda(stp->ba, i));
+ erts_set_fun_code(fun_entry,
+ staging_ix,
+ beamasm_get_lambda(stp->ba, i));
+
+ /* Finalize the literal we've created for this lambda, if any,
+ * converting it from an external fun to a local one with the newly
+ * created fun entry. */
+ if (stp->lambda_literals[i] != ERTS_SWORD_MAX) {
+ ErlFunThing *funp;
+ Eterm literal;
+
+ literal = beamfile_get_literal(&stp->beam,
+ stp->lambda_literals[i]);
+ funp = (ErlFunThing *)fun_val(literal);
+ ASSERT(funp->creator == am_external);
+
+ funp->entry.fun = fun_entry;
+
+ funp->next = literal_area->off_heap;
+ literal_area->off_heap = (struct erl_off_heap_header *)funp;
+
+ ASSERT(erts_init_process_id != ERTS_INVALID_PID);
+ funp->creator = erts_init_process_id;
+
+ erts_refc_inc(&fun_entry->refc, 2);
+ }
- beamasm_patch_lambda(stp->ba,
- stp->native_module_rw,
- i,
- (BeamInstr)fun_entry);
+ beamasm_patch_lambda(stp->ba, stp->writable_region, i, fun_entry);
}
}
/* Register debug / profiling info with external tools. */
beamasm_register_metadata(stp->ba, stp->code_hdr);
- beamasm_flush_icache(inst_p->code_hdr, inst_p->code_length);
+ erts_seal_module(inst_p);
/* Prevent literals and code from being freed. */
(stp->load_hdr)->literal_area = NULL;
stp->load_hdr->are_nifs = NULL;
- stp->native_module_exec = NULL;
- stp->native_module_rw = NULL;
+ stp->executable_region = NULL;
+ stp->writable_region = NULL;
stp->code_hdr = NULL;
}
diff --git a/erts/emulator/beam/jit/beam_asm.h b/erts/emulator/beam/jit/beam_asm.h
index 1a4dff270a..a0d82f82e1 100644
--- a/erts/emulator/beam/jit/beam_asm.h
+++ b/erts/emulator/beam/jit/beam_asm.h
@@ -23,6 +23,7 @@
# include "sys.h"
# include "bif.h"
+# include "erl_fun.h"
# include "erl_process.h"
# include "beam_code.h"
# include "beam_file.h"
@@ -45,6 +46,7 @@ enum beamasm_perf_flags {
};
extern enum beamasm_perf_flags erts_jit_perf_support;
# endif
+extern int erts_jit_single_map;
void beamasm_init(void);
void *beamasm_new_assembler(Eterm mod,
@@ -52,14 +54,15 @@ void *beamasm_new_assembler(Eterm mod,
int num_functions,
BeamFile *beam);
void beamasm_codegen(void *ba,
- const void **native_module_exec,
- void **native_module_rw,
+ const void **executable_region,
+ void **writable_region,
const BeamCodeHeader *in_hdr,
const BeamCodeHeader **out_exec_hdr,
BeamCodeHeader **out_rw_hdr);
void beamasm_register_metadata(void *ba, const BeamCodeHeader *hdr);
-void beamasm_purge_module(const void *native_module_exec,
- void *native_module_rw);
+void beamasm_purge_module(const void *executable_region,
+ void *writable_region,
+ size_t size);
void beamasm_delete_assembler(void *ba);
int beamasm_emit(void *ba, unsigned specific_op, BeamOp *op);
ErtsCodePtr beamasm_get_code(void *ba, int label);
@@ -72,9 +75,15 @@ void beamasm_embed_rodata(void *ba,
void beamasm_embed_bss(void *ba, char *labelName, size_t size);
unsigned int beamasm_patch_catches(void *ba, char *rw_base);
-void beamasm_patch_import(void *ba, char *rw_base, int index, BeamInstr import);
+void beamasm_patch_import(void *ba,
+ char *rw_base,
+ int index,
+ const Export *import);
void beamasm_patch_literal(void *ba, char *rw_base, int index, Eterm lit);
-void beamasm_patch_lambda(void *ba, char *rw_base, int index, BeamInstr fe);
+void beamasm_patch_lambda(void *ba,
+ char *rw_base,
+ int index,
+ const ErlFunEntry *fe);
void beamasm_patch_strings(void *ba, char *rw_base, const byte *strtab);
void beamasm_emit_call_nif(const ErtsCodeInfo *info,
@@ -92,6 +101,12 @@ char *beamasm_get_base(void *instance);
/* Return current instruction offset, for line information. */
size_t beamasm_get_offset(void *ba);
+void beamasm_unseal_module(const void *executable_region,
+ void *writable_region,
+ size_t size);
+void beamasm_seal_module(const void *executable_region,
+ void *writable_region,
+ size_t size);
void beamasm_flush_icache(const void *address, size_t size);
/* Number of bytes emitted at first label in order to support trace and nif
@@ -159,10 +174,10 @@ static inline void erts_asm_bp_set_flag(ErtsCodeInfo *ci_rw,
const ErtsCodeInfo *ci_exec,
enum erts_asm_bp_flag flag) {
ASSERT(flag != ERTS_ASM_BP_FLAG_NONE);
+ (void)ci_exec;
if (ci_rw->u.metadata.breakpoint_flag == ERTS_ASM_BP_FLAG_NONE) {
# if defined(__aarch64__)
- const Uint32 *exec_code = (Uint32 *)erts_codeinfo_to_code(ci_exec);
Uint32 volatile *rw_code = (Uint32 *)erts_codeinfo_to_code(ci_rw);
/* B .next, .enabled: BL breakpoint_handler, .next: */
@@ -170,8 +185,6 @@ static inline void erts_asm_bp_set_flag(ErtsCodeInfo *ci_rw,
/* Reroute the initial jump instruction to `.enabled`. */
rw_code[1] = 0x14000001;
-
- beamasm_flush_icache(&exec_code[1], sizeof(Uint32));
# else /* x86_64 */
byte volatile *rw_code = (byte *)erts_codeinfo_to_code(ci_rw);
@@ -181,8 +194,6 @@ static inline void erts_asm_bp_set_flag(ErtsCodeInfo *ci_rw,
/* Reroute the initial jump instruction to `.enabled`. */
rw_code[1] = 1;
-
- (void)ci_exec;
# endif
}
@@ -193,6 +204,7 @@ static inline void erts_asm_bp_unset_flag(ErtsCodeInfo *ci_rw,
const ErtsCodeInfo *ci_exec,
enum erts_asm_bp_flag flag) {
ASSERT(flag != ERTS_ASM_BP_FLAG_NONE);
+ (void)ci_exec;
ci_rw->u.metadata.breakpoint_flag &= ~flag;
@@ -201,7 +213,6 @@ static inline void erts_asm_bp_unset_flag(ErtsCodeInfo *ci_rw,
* past the prologue. */
# if defined(__aarch64__)
- const Uint32 *exec_code = (Uint32 *)erts_codeinfo_to_code(ci_exec);
Uint32 volatile *rw_code = (Uint32 *)erts_codeinfo_to_code(ci_rw);
/* B .enabled, .enabled: BL breakpoint_handler, .next: */
@@ -210,8 +221,6 @@ static inline void erts_asm_bp_unset_flag(ErtsCodeInfo *ci_rw,
/* Reroute the initial jump instruction back to `.next`. */
ERTS_CT_ASSERT(BEAM_ASM_FUNC_PROLOGUE_SIZE == sizeof(Uint32[3]));
rw_code[1] = 0x14000002;
-
- beamasm_flush_icache(&exec_code[1], sizeof(Uint32));
# else /* x86_64 */
byte volatile *rw_code = (byte *)erts_codeinfo_to_code(ci_rw);
@@ -221,8 +230,6 @@ static inline void erts_asm_bp_unset_flag(ErtsCodeInfo *ci_rw,
/* Reroute the initial jump instruction back to `.next`. */
rw_code[1] = BEAM_ASM_FUNC_PROLOGUE_SIZE - 2;
-
- (void)ci_exec;
# endif
}
}
diff --git a/erts/emulator/beam/jit/beam_jit_args.hpp b/erts/emulator/beam/jit/beam_jit_args.hpp
index 8e1e0359ee..4dba1b3f4f 100644
--- a/erts/emulator/beam/jit/beam_jit_args.hpp
+++ b/erts/emulator/beam/jit/beam_jit_args.hpp
@@ -246,6 +246,11 @@ struct ArgRegister : public ArgSource {
constexpr int typeIndex() const {
return (int)(val >> 10);
}
+
+ template<typename T>
+ constexpr T copy(int n) const {
+ return T(n | (val & ~REG_MASK));
+ }
};
struct ArgXRegister : public ArgRegister {
diff --git a/erts/emulator/beam/jit/beam_jit_common.cpp b/erts/emulator/beam/jit/beam_jit_common.cpp
index 04fdf209d4..3200f75407 100644
--- a/erts/emulator/beam/jit/beam_jit_common.cpp
+++ b/erts/emulator/beam/jit/beam_jit_common.cpp
@@ -42,7 +42,8 @@ static std::string getAtom(Eterm atom) {
return std::string((char *)ap->name, ap->len);
}
-BeamAssembler::BeamAssembler() : code() {
+BeamAssemblerCommon::BeamAssemblerCommon(BaseAssembler &assembler_)
+ : assembler(assembler_), code() {
/* Setup with default code info */
Error err = code.init(Environment::host());
ERTS_ASSERT(!err && "Failed to init codeHolder");
@@ -54,42 +55,35 @@ BeamAssembler::BeamAssembler() : code() {
8);
ERTS_ASSERT(!err && "Failed to create .rodata section");
- err = code.attach(&a);
-
- ERTS_ASSERT(!err && "Failed to attach codeHolder");
#ifdef DEBUG
- a.addDiagnosticOptions(DiagnosticOptions::kValidateAssembler);
+ assembler.addDiagnosticOptions(DiagnosticOptions::kValidateAssembler);
#endif
- a.addEncodingOptions(EncodingOptions::kOptimizeForSize |
- EncodingOptions::kOptimizedAlign);
+ assembler.addEncodingOptions(EncodingOptions::kOptimizeForSize |
+ EncodingOptions::kOptimizedAlign);
code.setErrorHandler(this);
}
-BeamAssembler::BeamAssembler(const std::string &log) : BeamAssembler() {
- if (erts_jit_asm_dump) {
- setLogger(log + ".asm");
- }
-}
-
-BeamAssembler::~BeamAssembler() {
+BeamAssemblerCommon::~BeamAssemblerCommon() {
if (logger.file()) {
fclose(logger.file());
}
}
-void *BeamAssembler::getBaseAddress() {
+void *BeamAssemblerCommon::getBaseAddress() {
ASSERT(code.hasBaseAddress());
return (void *)code.baseAddress();
}
-size_t BeamAssembler::getOffset() {
- return a.offset();
+size_t BeamAssemblerCommon::getOffset() {
+ return assembler.offset();
}
-void BeamAssembler::_codegen(JitAllocator *allocator,
- const void **executable_ptr,
- void **writable_ptr) {
- Error err = code.flatten();
+void BeamAssemblerCommon::codegen(JitAllocator *allocator,
+ const void **executable_ptr,
+ void **writable_ptr) {
+ Error err;
+
+ err = code.flatten();
ERTS_ASSERT(!err && "Could not flatten code");
err = code.resolveUnresolvedLinks();
ERTS_ASSERT(!err && "Could not resolve all links");
@@ -121,6 +115,8 @@ void BeamAssembler::_codegen(JitAllocator *allocator,
ERTS_ASSERT("Failed to allocate module code");
}
+ VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadWrite);
+
code.relocateToBase((uint64_t)*executable_ptr);
code.copyFlattenedData(*writable_ptr,
code.codeSize(),
@@ -133,59 +129,63 @@ void BeamAssembler::_codegen(JitAllocator *allocator,
#endif
}
-void *BeamAssembler::getCode(Label label) {
+void *BeamAssemblerCommon::getCode(Label label) {
ASSERT(label.isValid());
return (char *)getBaseAddress() + code.labelOffsetFromBase(label);
}
-byte *BeamAssembler::getCode(char *labelName) {
+byte *BeamAssemblerCommon::getCode(char *labelName) {
return (byte *)getCode(code.labelByName(labelName, strlen(labelName)));
}
-void BeamAssembler::handleError(Error err,
- const char *message,
- BaseEmitter *origin) {
+void BeamAssemblerCommon::handleError(Error err,
+ const char *message,
+ BaseEmitter *origin) {
comment(message);
- fflush(logger.file());
+
+ if (logger.file() != NULL) {
+ fflush(logger.file());
+ }
+
ASSERT(0 && "Failed to encode instruction");
}
-void BeamAssembler::embed_rodata(const char *labelName,
- const char *buff,
- size_t size) {
- Label label = a.newNamedLabel(labelName);
+void BeamAssemblerCommon::embed_rodata(const char *labelName,
+ const char *buff,
+ size_t size) {
+ Label label = assembler.newNamedLabel(labelName);
- a.section(rodata);
- a.bind(label);
- a.embed(buff, size);
- a.section(code.textSection());
+ assembler.section(rodata);
+ assembler.bind(label);
+ assembler.embed(buff, size);
+ assembler.section(code.textSection());
}
-void BeamAssembler::embed_bss(const char *labelName, size_t size) {
- Label label = a.newNamedLabel(labelName);
+void BeamAssemblerCommon::embed_bss(const char *labelName, size_t size) {
+ Label label = assembler.newNamedLabel(labelName);
/* Reuse rodata section for now */
- a.section(rodata);
- a.bind(label);
+ assembler.section(rodata);
+ assembler.bind(label);
embed_zeros(size);
- a.section(code.textSection());
+ assembler.section(code.textSection());
}
-void BeamAssembler::embed_zeros(size_t size) {
+void BeamAssemblerCommon::embed_zeros(size_t size) {
static constexpr size_t buf_size = 16384;
static const char zeros[buf_size] = {};
while (size >= buf_size) {
- a.embed(zeros, buf_size);
+ assembler.embed(zeros, buf_size);
size -= buf_size;
}
if (size > 0) {
- a.embed(zeros, size);
+ assembler.embed(zeros, size);
}
}
-void BeamAssembler::setLogger(std::string log) {
+void BeamAssemblerCommon::setLogger(const std::string &log) {
FILE *f = fopen(log.data(), "w+");
/* FIXME: Don't crash when loading multiple modules with the same name.
@@ -198,7 +198,7 @@ void BeamAssembler::setLogger(std::string log) {
setLogger(f);
}
-void BeamAssembler::setLogger(FILE *log) {
+void BeamAssemblerCommon::setLogger(FILE *log) {
logger.setFile(log);
logger.setIndentation(FormatIndentationGroup::kCode, 4);
code.setLogger(&logger);
@@ -213,7 +213,7 @@ void BeamModuleAssembler::codegen(JitAllocator *allocator,
const BeamCodeHeader *code_hdr_exec;
BeamCodeHeader *code_hdr_rw;
- codegen(allocator, executable_ptr, writable_ptr);
+ BeamAssembler::codegen(allocator, executable_ptr, writable_ptr);
{
auto offset = code.labelOffsetFromBase(code_header);
@@ -236,6 +236,11 @@ void BeamModuleAssembler::codegen(JitAllocator *allocator,
char *module_end = (char *)code.baseAddress() + a.offset();
code_hdr_rw->functions[functions.size()] = (ErtsCodeInfo *)module_end;
+ /* Note that we don't make the module executable yet since we're going to
+ * patch literals et cetera and it's pointless to ping-pong the page
+ * permissions. The user will call `beamasm_seal_module` to do so later
+ * on. */
+
*out_exec_hdr = code_hdr_exec;
*out_rw_hdr = code_hdr_rw;
}
@@ -243,7 +248,8 @@ void BeamModuleAssembler::codegen(JitAllocator *allocator,
void BeamModuleAssembler::codegen(JitAllocator *allocator,
const void **executable_ptr,
void **writable_ptr) {
- _codegen(allocator, executable_ptr, writable_ptr);
+ BeamAssembler::codegen(allocator, executable_ptr, writable_ptr);
+ VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadExecute);
}
void BeamModuleAssembler::codegen(char *buff, size_t len) {
@@ -260,7 +266,8 @@ BeamModuleAssembler::BeamModuleAssembler(BeamGlobalAssembler *_ga,
Eterm _mod,
int num_labels,
const BeamFile *file)
- : BeamAssembler(getAtom(_mod)), beam(file), ga(_ga), mod(_mod) {
+ : BeamAssembler(getAtom(_mod)), BeamModuleAssemblerCommon(file, _mod),
+ ga(_ga) {
rawLabels.reserve(num_labels + 1);
if (logger.file() && beam) {
@@ -362,10 +369,11 @@ void BeamModuleAssembler::register_metadata(const BeamCodeHeader *header) {
ranges.reserve(functions.size() + 2);
ASSERT((ErtsCodePtr)getBaseAddress() == (ErtsCodePtr)header);
+ ASSERT(functions.size() == header->num_functions);
/* Push info about the header */
ranges.push_back({.start = (ErtsCodePtr)getBaseAddress(),
- .stop = getCode(functions[0]),
+ .stop = (ErtsCodePtr)&header->functions[functions.size()],
.name = module_name + "::codeHeader"});
for (unsigned i = 0; i < functions.size(); i++) {
@@ -437,6 +445,7 @@ void BeamModuleAssembler::register_metadata(const BeamCodeHeader *header) {
res = erts_unicode_list_to_buf(fname,
(byte *)name_buffer,
+ sizeof(name_buffer),
sizeof(name_buffer) / 4,
&n);
@@ -482,6 +491,80 @@ const ErtsCodeInfo *BeamModuleAssembler::getOnLoad() {
}
}
+unsigned BeamModuleAssembler::patchCatches(char *rw_base) {
+ unsigned catch_no = BEAM_CATCHES_NIL;
+
+ for (const auto &c : catches) {
+ const auto &patch = c.patch;
+ ErtsCodePtr handler;
+
+ handler = (ErtsCodePtr)getCode(c.handler);
+ catch_no = beam_catches_cons(handler, catch_no, nullptr);
+
+ /* Patch the `mov` instruction with the catch tag */
+ auto offset = code.labelOffsetFromBase(patch.where);
+ auto where = (int32_t *)&rw_base[offset + patch.ptr_offs];
+
+ ASSERT(INT_MAX == *where);
+ Eterm catch_term = make_catch(catch_no);
+
+ /* With the current tag scheme, more than 33 million
+ * catches can exist at once. */
+ ERTS_ASSERT(catch_term >> 31 == 0);
+
+ *where = catch_term;
+ }
+
+ return catch_no;
+}
+
+void BeamModuleAssembler::patchImport(char *rw_base,
+ unsigned index,
+ const Export *import) {
+ for (const auto &patch : imports[index].patches) {
+ auto offset = code.labelOffsetFromBase(patch.where);
+ auto where = (Eterm *)&rw_base[offset + patch.ptr_offs];
+
+ ASSERT(LLONG_MAX == *where);
+ *where = reinterpret_cast<Eterm>(import) + patch.val_offs;
+ }
+}
+
+void BeamModuleAssembler::patchLambda(char *rw_base,
+ unsigned index,
+ const ErlFunEntry *fe) {
+ for (const auto &patch : lambdas[index].patches) {
+ auto offset = code.labelOffsetFromBase(patch.where);
+ auto where = (Eterm *)&rw_base[offset + patch.ptr_offs];
+
+ ASSERT(LLONG_MAX == *where);
+ *where = reinterpret_cast<Eterm>(fe) + patch.val_offs;
+ }
+}
+
+void BeamModuleAssembler::patchLiteral(char *rw_base,
+ unsigned index,
+ Eterm lit) {
+ for (const auto &patch : literals[index].patches) {
+ auto offset = code.labelOffsetFromBase(patch.where);
+ auto where = (Eterm *)&rw_base[offset + patch.ptr_offs];
+
+ ASSERT(LLONG_MAX == *where);
+ *where = lit + patch.val_offs;
+ }
+}
+
+void BeamModuleAssembler::patchStrings(char *rw_base,
+ const byte *string_table) {
+ for (const auto &patch : strings) {
+ auto offset = code.labelOffsetFromBase(patch.where);
+ auto where = (const byte **)&rw_base[offset + patch.ptr_offs];
+
+ ASSERT(LLONG_MAX == (Eterm)*where);
+ *where = string_table + patch.val_offs;
+ }
+}
+
/* ** */
#if defined(DEBUG) && defined(JIT_HARD_DEBUG)
@@ -592,13 +675,13 @@ Eterm beam_jit_call_nif(Process *c_p,
enum beam_jit_nif_load_ret beam_jit_load_nif(Process *c_p,
ErtsCodePtr I,
Eterm *reg) {
- if (erts_try_seize_code_write_permission(c_p)) {
+ if (erts_try_seize_code_mod_permission(c_p)) {
Eterm result;
PROCESS_MAIN_CHK_LOCKS((c_p));
ERTS_UNREQ_PROC_MAIN_LOCK((c_p));
result = erts_load_nif(c_p, I, reg[0], reg[1]);
- erts_release_code_write_permission();
+ erts_release_code_mod_permission();
ERTS_REQ_PROC_MAIN_LOCK(c_p);
if (ERTS_LIKELY(is_value(result))) {
@@ -687,7 +770,7 @@ static void test_bin_vheap(Process *c_p,
int need = Nh;
if (c_p->stop - c_p->htop < (need + S_RESERVED) ||
- MSO(c_p).overhead + VNh >= BIN_VHEAP_SZ(c_p)) {
+ MSO(c_p).overhead + VNh >= c_p->bin_vheap_sz) {
c_p->fcalls -=
erts_garbage_collect_nobump(c_p, need, reg, Live, c_p->fcalls);
}
@@ -733,7 +816,6 @@ Eterm beam_jit_bs_init(Process *c_p,
Uint alloc,
unsigned Live) {
erts_bin_offset = 0;
- erts_writable_bin = 0;
if (num_bytes <= ERL_ONHEAP_BIN_LIMIT) {
ErlHeapBin *hb;
Uint bin_need;
@@ -800,7 +882,6 @@ Eterm beam_jit_bs_init_bits(Process *c_p,
}
erts_bin_offset = 0;
- erts_writable_bin = 0;
/* num_bits = Number of bits to build
* num_bytes = Number of bytes to allocate in the binary
@@ -1218,11 +1299,11 @@ void beam_jit_return_to_trace(Process *c_p) {
erts_inspect_frame(cpp, &return_to_address);
if (BeamIsReturnTrace(return_to_address)) {
- cpp += CP_SIZE + 2;
- } else if (BeamIsReturnTimeTrace(return_to_address)) {
- cpp += CP_SIZE + 1;
+ cpp += CP_SIZE + BEAM_RETURN_TRACE_FRAME_SZ;
+ } else if (BeamIsReturnCallAccTrace(return_to_address)) {
+ cpp += CP_SIZE + BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ;
} else if (BeamIsReturnToTrace(return_to_address)) {
- cpp += CP_SIZE;
+ cpp += CP_SIZE + BEAM_RETURN_TO_TRACE_FRAME_SZ;
} else {
break;
}
diff --git a/erts/emulator/beam/jit/beam_jit_common.hpp b/erts/emulator/beam/jit/beam_jit_common.hpp
index 41cf957daa..c7b9f0ade0 100644
--- a/erts/emulator/beam/jit/beam_jit_common.hpp
+++ b/erts/emulator/beam/jit/beam_jit_common.hpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2021-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2021-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,10 @@
#ifndef __BEAM_JIT_COMMON_HPP__
#define __BEAM_JIT_COMMON_HPP__
+#ifndef ASMJIT_ASMJIT_H_INCLUDED
+# include <asmjit/asmjit.hpp>
+#endif
+
#include <string>
#include <vector>
#include <unordered_map>
@@ -36,10 +40,390 @@ extern "C"
#include "bif.h"
#include "erl_vm.h"
#include "global.h"
+#include "big.h"
#include "beam_file.h"
+#include "beam_types.h"
}
+/* On Windows, the min and max macros may be defined. */
+#undef min
+#undef max
+
#include "beam_jit_args.hpp"
+#include "beam_jit_types.hpp"
+
+using namespace asmjit;
+
+struct AsmRange {
+ ErtsCodePtr start;
+ ErtsCodePtr stop;
+ const std::string name;
+
+ struct LineData {
+ ErtsCodePtr start;
+ const std::string file;
+ unsigned line;
+ };
+
+ const std::vector<LineData> lines;
+};
+
+/* This is a partial class for `BeamAssembler`, containing various fields and
+ * helper functions that are common to all architectures so that we won't have
+ * to redefine them all the time. */
+class BeamAssemblerCommon : public ErrorHandler {
+ BaseAssembler &assembler;
+
+protected:
+ CodeHolder code;
+ FileLogger logger;
+ Section *rodata;
+
+ static bool hasCpuFeature(uint32_t featureId);
+
+ BeamAssemblerCommon(BaseAssembler &assembler);
+ ~BeamAssemblerCommon();
+
+ void codegen(JitAllocator *allocator,
+ const void **executable_ptr,
+ void **writable_ptr);
+
+ void comment(const char *format) {
+ if (logger.file()) {
+ assembler.commentf("# %s", format);
+ }
+ }
+
+ template<typename... Ts>
+ void comment(const char *format, Ts... args) {
+ if (logger.file()) {
+ char buff[1024];
+ erts_snprintf(buff, sizeof(buff), format, args...);
+ assembler.commentf("# %s", buff);
+ }
+ }
+
+ void *getCode(Label label);
+ byte *getCode(char *labelName);
+
+ void embed(void *data, uint32_t size) {
+ assembler.embed((char *)data, size);
+ }
+
+ void handleError(Error err, const char *message, BaseEmitter *origin);
+
+ void setLogger(const std::string &log);
+ void setLogger(FILE *log);
+
+public:
+ void embed_rodata(const char *labelName, const char *buff, size_t size);
+ void embed_bss(const char *labelName, size_t size);
+ void embed_zeros(size_t size);
+
+ void *getBaseAddress();
+ size_t getOffset();
+};
+
+struct BeamModuleAssemblerCommon {
+ typedef unsigned BeamLabel;
+
+ /* The BEAM file we've been loaded from, if any. */
+ const BeamFile *beam;
+ Eterm mod;
+
+ /* Map of label number to asmjit Label */
+ typedef std::unordered_map<BeamLabel, const Label> LabelMap;
+ LabelMap rawLabels;
+
+ struct patch {
+ Label where;
+ size_t ptr_offs;
+ size_t val_offs;
+ };
+
+ struct patch_catch {
+ struct patch patch;
+ Label handler;
+ };
+ std::vector<struct patch_catch> catches;
+
+ /* Map of import entry to patch labels and mfa */
+ struct patch_import {
+ std::vector<struct patch> patches;
+ ErtsCodeMFA mfa;
+ };
+ typedef std::unordered_map<unsigned, struct patch_import> ImportMap;
+ ImportMap imports;
+
+ /* Map of fun entry to trampoline labels and patches */
+ struct patch_lambda {
+ std::vector<struct patch> patches;
+ Label trampoline;
+ };
+ typedef std::unordered_map<unsigned, struct patch_lambda> LambdaMap;
+ LambdaMap lambdas;
+
+ /* Map of literals to patch labels */
+ struct patch_literal {
+ std::vector<struct patch> patches;
+ };
+ typedef std::unordered_map<unsigned, struct patch_literal> LiteralMap;
+ LiteralMap literals;
+
+ /* All string patches */
+ std::vector<struct patch> strings;
+
+ /* All functions that have been seen so far */
+ std::vector<BeamLabel> functions;
+
+ /* Used by emit to populate the labelToMFA map */
+ Label current_label;
+
+ /* The offset of our BeamCodeHeader, if any. */
+ Label code_header;
+
+ /* The module's on_load function, if any. */
+ Label on_load;
+
+ /* The end of the last function. Note that the dispatch table, constants,
+ * and veneers may follow. */
+ Label code_end;
+
+ BeamModuleAssemblerCommon(const BeamFile *beam_, Eterm mod_)
+ : beam(beam_), mod(mod_) {
+ }
+
+ /* Helpers */
+ const auto &getTypeEntry(const ArgSource &arg) const {
+ auto typeIndex =
+ arg.isRegister() ? arg.as<ArgRegister>().typeIndex() : 0;
+ ASSERT(typeIndex < beam->types.count);
+ return static_cast<BeamArgType &>(beam->types.entries[typeIndex]);
+ }
+
+ auto getTypeUnion(const ArgSource &arg) const {
+ if (arg.isRegister()) {
+ return getTypeEntry(arg).type();
+ }
+
+ Eterm constant =
+ arg.isImmed()
+ ? arg.as<ArgImmed>().get()
+ : beamfile_get_literal(beam,
+ arg.as<ArgLiteral>().get());
+
+ switch (tag_val_def(constant)) {
+ case ATOM_DEF:
+ return BeamTypeId::Atom;
+ case BINARY_DEF:
+ return BeamTypeId::Bitstring;
+ case FLOAT_DEF:
+ return BeamTypeId::Float;
+ case FUN_DEF:
+ return BeamTypeId::Fun;
+ case BIG_DEF:
+ case SMALL_DEF:
+ return BeamTypeId::Integer;
+ case LIST_DEF:
+ return BeamTypeId::Cons;
+ case MAP_DEF:
+ return BeamTypeId::Map;
+ case NIL_DEF:
+ return BeamTypeId::Nil;
+ case EXTERNAL_PID_DEF:
+ case PID_DEF:
+ return BeamTypeId::Pid;
+ case EXTERNAL_PORT_DEF:
+ case PORT_DEF:
+ return BeamTypeId::Port;
+ case EXTERNAL_REF_DEF:
+ case REF_DEF:
+ return BeamTypeId::Reference;
+ case TUPLE_DEF:
+ return BeamTypeId::Tuple;
+ default:
+ ERTS_ASSERT(!"tag_val_def error");
+ }
+ }
+
+ auto getClampedRange(const ArgSource &arg) const {
+ if (arg.isSmall()) {
+ Sint value = arg.as<ArgSmall>().getSigned();
+ return std::make_pair(value, value);
+ } else {
+ const auto &entry = getTypeEntry(arg);
+
+ auto min = entry.hasLowerBound() ? entry.min() : MIN_SMALL;
+ auto max = entry.hasUpperBound() ? entry.max() : MAX_SMALL;
+
+ return std::make_pair(min, max);
+ }
+ }
+
+ bool always_small(const ArgSource &arg) const {
+ if (arg.isSmall()) {
+ return true;
+ } else if (!exact_type<BeamTypeId::Integer>(arg)) {
+ return false;
+ }
+
+ const auto &entry = getTypeEntry(arg);
+
+ if (entry.hasLowerBound() && entry.hasUpperBound()) {
+ return IS_SSMALL(entry.min()) && IS_SSMALL(entry.max());
+ }
+
+ return false;
+ }
+
+ bool always_immediate(const ArgSource &arg) const {
+ return always_one_of<BeamTypeId::AlwaysImmediate>(arg) ||
+ always_small(arg);
+ }
+
+ bool always_same_types(const ArgSource &lhs, const ArgSource &rhs) const {
+ using integral = std::underlying_type_t<BeamTypeId>;
+ auto lhs_types = static_cast<integral>(getTypeUnion(lhs));
+ auto rhs_types = static_cast<integral>(getTypeUnion(rhs));
+
+ /* We can only be certain that the types are the same when there's
+ * one possible type. For example, if one is a number and the other
+ * is an integer, they could differ if the former is a float. */
+ if ((lhs_types & (lhs_types - 1)) == 0) {
+ return lhs_types == rhs_types;
+ }
+
+ return false;
+ }
+
+ template<BeamTypeId... Types>
+ bool never_one_of(const ArgSource &arg) const {
+ using integral = std::underlying_type_t<BeamTypeId>;
+ auto types = static_cast<integral>(BeamTypeIdUnion<Types...>::value());
+ auto type_union = static_cast<integral>(getTypeUnion(arg));
+ return static_cast<BeamTypeId>(type_union & types) == BeamTypeId::None;
+ }
+
+ template<BeamTypeId... Types>
+ bool maybe_one_of(const ArgSource &arg) const {
+ return !never_one_of<Types...>(arg);
+ }
+
+ template<BeamTypeId... Types>
+ bool always_one_of(const ArgSource &arg) const {
+ /* Providing a single type to this function is not an error per se, but
+ * `exact_type` provides a bit more error checking for that use-case,
+ * so we want to encourage its use. */
+ static_assert(!BeamTypeIdUnion<Types...>::is_single_typed(),
+ "always_one_of expects a union of several primitive "
+ "types, use exact_type instead");
+
+ using integral = std::underlying_type_t<BeamTypeId>;
+ auto types = static_cast<integral>(BeamTypeIdUnion<Types...>::value());
+ auto type_union = static_cast<integral>(getTypeUnion(arg));
+ return type_union == (type_union & types);
+ }
+
+ template<BeamTypeId Type>
+ bool exact_type(const ArgSource &arg) const {
+ /* Rejects `exact_type<BeamTypeId::List>(...)` and similar, as it's
+ * almost always an error to exactly match a union of several types.
+ *
+ * On the off chance that you _do_ need to match a union exactly, use
+ * `masked_types<T>(arg) == T` instead. */
+ static_assert(BeamTypeIdUnion<Type>::is_single_typed(),
+ "exact_type expects exactly one primitive type, use "
+ "always_one_of instead");
+
+ using integral = std::underlying_type_t<BeamTypeId>;
+ auto type_union = static_cast<integral>(getTypeUnion(arg));
+ return type_union == (type_union & static_cast<integral>(Type));
+ }
+
+ template<BeamTypeId Mask>
+ BeamTypeId masked_types(const ArgSource &arg) const {
+ static_assert((Mask != BeamTypeId::AlwaysBoxed &&
+ Mask != BeamTypeId::AlwaysImmediate),
+ "using masked_types with AlwaysBoxed or AlwaysImmediate "
+ "is almost always an error, use exact_type, "
+ "maybe_one_of, or never_one_of instead");
+ static_assert((Mask == BeamTypeId::MaybeBoxed ||
+ Mask == BeamTypeId::MaybeImmediate ||
+ Mask == BeamTypeId::AlwaysBoxed ||
+ Mask == BeamTypeId::AlwaysImmediate),
+ "masked_types expects a mask type like MaybeBoxed or "
+ "MaybeImmediate, use exact_type, maybe_one_of, or"
+ "never_one_of instead");
+
+ using integral = std::underlying_type_t<BeamTypeId>;
+ auto mask = static_cast<integral>(Mask);
+ auto type_union = static_cast<integral>(getTypeUnion(arg));
+ return static_cast<BeamTypeId>(type_union & mask);
+ }
+
+ bool is_sum_small_if_args_are_small(const ArgSource &LHS,
+ const ArgSource &RHS) const {
+ Sint min, max;
+ auto [min1, max1] = getClampedRange(LHS);
+ auto [min2, max2] = getClampedRange(RHS);
+ min = min1 + min2;
+ max = max1 + max2;
+ return IS_SSMALL(min) && IS_SSMALL(max);
+ }
+
+ bool is_diff_small_if_args_are_small(const ArgSource &LHS,
+ const ArgSource &RHS) const {
+ Sint min, max;
+ auto [min1, max1] = getClampedRange(LHS);
+ auto [min2, max2] = getClampedRange(RHS);
+ min = min1 - max2;
+ max = max1 - min2;
+ return IS_SSMALL(min) && IS_SSMALL(max);
+ }
+
+ bool is_product_small_if_args_are_small(const ArgSource &LHS,
+ const ArgSource &RHS) const {
+ auto [min1, max1] = getClampedRange(LHS);
+ auto [min2, max2] = getClampedRange(RHS);
+ auto mag1 = std::max(std::abs(min1), std::abs(max1));
+ auto mag2 = std::max(std::abs(min2), std::abs(max2));
+
+ /*
+ * mag1 * mag2 <= MAX_SMALL
+ * mag1 <= MAX_SMALL / mag2 (when mag2 != 0)
+ */
+ ERTS_CT_ASSERT(MAX_SMALL < -MIN_SMALL);
+ return mag2 == 0 || mag1 <= MAX_SMALL / mag2;
+ }
+
+ bool is_bsl_small(const ArgSource &LHS, const ArgSource &RHS) const {
+ if (!(always_small(LHS) && always_small(RHS))) {
+ return false;
+ } else {
+ auto [min1, max1] = getClampedRange(LHS);
+ auto [min2, max2] = getClampedRange(RHS);
+
+ if (min1 < 0 || max1 == 0 || min2 < 0) {
+ return false;
+ }
+
+ return max2 < asmjit::Support::clz(max1) - _TAG_IMMED1_SIZE;
+ }
+ }
+
+ int getSizeUnit(const ArgSource &arg) const {
+ auto entry = getTypeEntry(arg);
+ return entry.hasUnit() ? entry.unit() : 1;
+ }
+
+ bool hasLowerBound(const ArgSource &arg) const {
+ return getTypeEntry(arg).hasLowerBound();
+ }
+
+ bool hasUpperBound(const ArgSource &arg) const {
+ return getTypeEntry(arg).hasUpperBound();
+ }
+};
/* This is a view into a contiguous container (like an array or `std::vector`),
* letting us reuse the existing argument array in `beamasm_emit` while keeping
diff --git a/erts/emulator/beam/jit/beam_jit_main.cpp b/erts/emulator/beam/jit/beam_jit_main.cpp
index ef0ad6943a..0cd732039f 100644
--- a/erts/emulator/beam/jit/beam_jit_main.cpp
+++ b/erts/emulator/beam/jit/beam_jit_main.cpp
@@ -43,6 +43,8 @@ ErtsFrameLayout ERTS_WRITE_UNLIKELY(erts_frame_layout);
#ifdef HAVE_LINUX_PERF_SUPPORT
enum beamasm_perf_flags erts_jit_perf_support;
#endif
+/* Force use of single-mapped RWX memory for JIT code */
+int erts_jit_single_map = 0;
/*
* Special Beam instructions.
@@ -54,7 +56,8 @@ ErtsCodePtr beam_exit;
ErtsCodePtr beam_export_trampoline;
ErtsCodePtr beam_bif_export_trap;
ErtsCodePtr beam_continue_exit;
-ErtsCodePtr beam_save_calls;
+ErtsCodePtr beam_save_calls_export;
+ErtsCodePtr beam_save_calls_fun;
ErtsCodePtr beam_unloaded_fun;
/* NOTE These should be the only variables containing trace instructions.
@@ -65,7 +68,7 @@ ErtsCodePtr beam_unloaded_fun;
ErtsCodePtr beam_return_to_trace; /* OpCode(i_return_to_trace) */
ErtsCodePtr beam_return_trace; /* OpCode(i_return_trace) */
ErtsCodePtr beam_exception_trace; /* UGLY also OpCode(i_return_trace) */
-ErtsCodePtr beam_return_time_trace; /* OpCode(i_return_time_trace) */
+ErtsCodePtr beam_call_trace_return; /* OpCode(i_call_trace_return) */
static JitAllocator *jit_allocator;
@@ -86,7 +89,7 @@ static void install_bifs(void) {
int i;
ASSERT(beam_export_trampoline != NULL);
- ASSERT(beam_save_calls != NULL);
+ ASSERT(beam_save_calls_export != NULL);
for (i = 0; i < BIF_SIZE; i++) {
BifEntry *entry;
@@ -117,42 +120,56 @@ static void install_bifs(void) {
}
}
-static JitAllocator *create_allocator(JitAllocator::CreateParams *params) {
+static auto create_allocator(const JitAllocator::CreateParams &params) {
void *test_ro, *test_rw;
+ bool single_mapped;
Error err;
- auto *allocator = new JitAllocator(params);
+ auto *allocator = new JitAllocator(&params);
err = allocator->alloc(&test_ro, &test_rw, 1);
+
+ if (err == ErrorCode::kErrorOk) {
+ /* We can get dual-mapped memory when asking for single-mapped memory
+ * if the latter is not possible: return whether that happened. */
+ single_mapped = (test_ro == test_rw);
+ }
+
allocator->release(test_ro);
if (err == ErrorCode::kErrorOk) {
- return allocator;
+ return std::make_pair(allocator, single_mapped);
}
delete allocator;
- return nullptr;
+ return std::make_pair((JitAllocator *)nullptr, false);
}
static JitAllocator *pick_allocator() {
- JitAllocator::CreateParams single_params;
- single_params.reset();
+ JitAllocator::CreateParams params;
+ JitAllocator *allocator;
+ bool single_mapped;
+
+#if defined(VALGRIND)
+ erts_jit_single_map = 1;
+#elif defined(__APPLE__) && defined(__aarch64__)
+ /* Allocating dual-mapped executable memory on this platform is horribly
+ * slow, and provides little security benefits over the MAP_JIT per-thread
+ * permission scheme. Force single-mapped memory.
+ *
+ * 64-bit x86 still uses dual-mapped memory as it lacks support for per-
+ * thread permissions and thus gets unprotected RWX pages with MAP_JIT. */
+ erts_jit_single_map = 1;
+#endif
#if defined(HAVE_LINUX_PERF_SUPPORT)
/* `perf` has a hard time showing symbols for dual-mapped memory, so we'll
* use single-mapped memory when enabled. */
if (erts_jit_perf_support & BEAMASM_PERF_ENABLED) {
- if (auto *alloc = create_allocator(&single_params)) {
- return alloc;
- }
-
- ERTS_INTERNAL_ERROR("jit: Failed to allocate executable+writable "
- "memory. Either allow this or disable the "
- "'+JPperf' option.");
+ erts_jit_single_map = 1;
}
#endif
-#if !defined(VALGRIND)
/* Default to dual-mapped memory with separate executable and writable
* regions of the same code. This is required for platforms that enforce
* W^X, and we prefer it when available to catch errors sooner.
@@ -162,28 +179,34 @@ static JitAllocator *pick_allocator() {
* file descriptor per block on most platforms. The block sizes do grow
* over time, but we don't want to waste half a dozen fds just to get to
* the shell on platforms that are very fd-constrained. */
- JitAllocator::CreateParams dual_params;
+ params.reset();
+ params.blockSize = 32 << 20;
- dual_params.reset();
- dual_params.options = JitAllocatorOptions::kUseDualMapping,
- dual_params.blockSize = 4 << 20;
+ allocator = nullptr;
+ single_mapped = false;
- if (auto *alloc = create_allocator(&dual_params)) {
- return alloc;
- } else if (auto *alloc = create_allocator(&single_params)) {
- return alloc;
+ if (!erts_jit_single_map) {
+ params.options = JitAllocatorOptions::kUseDualMapping;
+ std::tie(allocator, single_mapped) = create_allocator(params);
}
- ERTS_INTERNAL_ERROR("jit: Cannot allocate executable memory. Use the "
- "interpreter instead.");
-#elif defined(VALGRIND)
- if (auto *alloc = create_allocator(&single_params)) {
- return alloc;
+ if (allocator == nullptr) {
+ params.options &= ~JitAllocatorOptions::kUseDualMapping;
+ std::tie(allocator, single_mapped) = create_allocator(params);
}
- ERTS_INTERNAL_ERROR("jit: the valgrind emulator requires the ability to "
- "allocate executable+writable memory.");
-#endif
+ if (erts_jit_single_map && !single_mapped) {
+ ERTS_INTERNAL_ERROR("jit: Failed to allocate executable+writable "
+ "memory. Either allow this or disable both the "
+ "'+JPperf' and '+JMsingle' options.");
+ }
+
+ if (allocator == nullptr) {
+ ERTS_INTERNAL_ERROR("jit: Cannot allocate executable memory. Use the "
+ "interpreter instead.");
+ }
+
+ return allocator;
}
void beamasm_init() {
@@ -208,10 +231,10 @@ void beamasm_init() {
0,
op_i_return_to_trace,
&beam_return_to_trace},
- {am_return_time_trace,
+ {am_call_trace_return,
0,
- op_i_return_time_trace,
- &beam_return_time_trace}};
+ op_i_call_trace_return,
+ &beam_call_trace_return}};
Eterm mod_name;
ERTS_DECL_AM(erts_beamasm);
@@ -325,7 +348,8 @@ void beamasm_init() {
/* These instructions rely on register contents, and can only be reached
* from a `call_ext_*`-instruction or trapping from the emulator, hence the
* lack of wrapper functions. */
- beam_save_calls = (ErtsCodePtr)bga->get_dispatch_save_calls();
+ beam_save_calls_export = (ErtsCodePtr)bga->get_dispatch_save_calls_export();
+ beam_save_calls_fun = (ErtsCodePtr)bga->get_dispatch_save_calls_fun();
beam_export_trampoline = (ErtsCodePtr)bga->get_export_trampoline();
/* Used when trappping to Erlang code from the emulator, setting up
@@ -340,7 +364,7 @@ void beamasm_init() {
beamasm_metadata_late_init();
}
-bool BeamAssembler::hasCpuFeature(uint32_t featureId) {
+bool BeamAssemblerCommon::hasCpuFeature(uint32_t featureId) {
return cpuinfo.hasFeature(featureId);
}
@@ -365,6 +389,26 @@ extern "C"
#endif
}
+ void beamasm_unseal_module(const void *executable_region,
+ void *writable_region,
+ size_t size) {
+ (void)executable_region;
+ (void)writable_region;
+ (void)size;
+
+ VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadWrite);
+ }
+
+ void beamasm_seal_module(const void *executable_region,
+ void *writable_region,
+ size_t size) {
+ (void)executable_region;
+ (void)writable_region;
+ (void)size;
+
+ VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadExecute);
+ }
+
void beamasm_flush_icache(const void *address, size_t size) {
#ifdef DEBUG
erts_debug_require_code_barrier();
@@ -389,7 +433,7 @@ extern "C"
ETHR_COMPILER_BARRIER;
- for (size_t i = start & ~ERTS_CACHE_LINE_MASK; i < end;
+ for (UWord i = start & ~ERTS_CACHE_LINE_MASK; i < end;
i += ERTS_CACHE_LINE_SIZE) {
__asm__ __volatile__("dc cvau, %0\n"
"ic ivau, %0\n" ::"r"(i)
@@ -473,9 +517,11 @@ extern "C"
delete ba;
}
- void beamasm_purge_module(const void *native_module_exec,
- void *native_module_rw) {
- jit_allocator->release(const_cast<void *>(native_module_exec));
+ void beamasm_purge_module(const void *executable_region,
+ void *writable_region,
+ size_t size) {
+ (void)size;
+ jit_allocator->release(const_cast<void *>(executable_region));
}
ErtsCodePtr beamasm_get_code(void *instance, int label) {
@@ -511,16 +557,16 @@ extern "C"
}
void beamasm_codegen(void *instance,
- const void **native_module_exec,
- void **native_module_rw,
+ const void **executable_region,
+ void **writable_region,
const BeamCodeHeader *in_hdr,
const BeamCodeHeader **out_exec_hdr,
BeamCodeHeader **out_rw_hdr) {
BeamModuleAssembler *ba = static_cast<BeamModuleAssembler *>(instance);
ba->codegen(jit_allocator,
- native_module_exec,
- native_module_rw,
+ executable_region,
+ writable_region,
in_hdr,
out_exec_hdr,
out_rw_hdr);
@@ -562,7 +608,7 @@ extern "C"
void beamasm_patch_import(void *instance,
char *rw_base,
int index,
- BeamInstr import) {
+ const Export *import) {
BeamModuleAssembler *ba = static_cast<BeamModuleAssembler *>(instance);
ba->patchImport(rw_base, index, import);
}
@@ -578,7 +624,7 @@ extern "C"
void beamasm_patch_lambda(void *instance,
char *rw_base,
int index,
- BeamInstr fe) {
+ const ErlFunEntry *fe) {
BeamModuleAssembler *ba = static_cast<BeamModuleAssembler *>(instance);
ba->patchLambda(rw_base, index, fe);
}
diff --git a/erts/emulator/beam/jit/beam_jit_metadata.cpp b/erts/emulator/beam/jit/beam_jit_metadata.cpp
index 68b59babd4..10e82fdf37 100644
--- a/erts/emulator/beam/jit/beam_jit_metadata.cpp
+++ b/erts/emulator/beam/jit/beam_jit_metadata.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -155,11 +155,10 @@ static void beamasm_init_late_gdb() {
erts_mtx_unlock(&__jit_debug_descriptor.mutex);
}
-static void beamasm_update_gdb_info(
- std::string module_name,
- ErtsCodePtr base_address,
- size_t code_size,
- const std::vector<BeamAssembler::AsmRange> &ranges) {
+static void beamasm_update_gdb_info(std::string module_name,
+ ErtsCodePtr base_address,
+ size_t code_size,
+ const std::vector<AsmRange> &ranges) {
Sint symfile_size = sizeof(struct debug_info) + module_name.size() + 1;
for (const auto &range : ranges) {
@@ -336,7 +335,7 @@ public:
return true;
}
- void update(const std::vector<BeamAssembler::AsmRange> &ranges) {
+ void update(const std::vector<AsmRange> &ranges) {
struct JitCodeLoadRecord {
RecordHeader header;
Uint32 pid;
@@ -353,7 +352,7 @@ public:
record.pid = getpid();
record.tid = erts_thr_self();
- for (const BeamAssembler::AsmRange &range : ranges) {
+ for (const AsmRange &range : ranges) {
/* Line entries must be written first, if present. */
if (!range.lines.empty()) {
struct JitCodeDebugEntry {
@@ -449,7 +448,7 @@ public:
return true;
}
- void update(const std::vector<BeamAssembler::AsmRange> &ranges) {
+ void update(const std::vector<AsmRange> &ranges) {
for (const auto &range : ranges) {
char *start = (char *)range.start, *stop = (char *)range.stop;
ptrdiff_t size = stop - start;
@@ -491,7 +490,7 @@ public:
ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
}
- void update(const std::vector<BeamAssembler::AsmRange> &ranges) {
+ void update(const std::vector<AsmRange> &ranges) {
if (modes) {
erts_mtx_lock(&mutex);
# ifdef HAVE_LINUX_PERF_DUMP_SUPPORT
@@ -526,11 +525,10 @@ void beamasm_metadata_late_init() {
#endif
}
-void beamasm_metadata_update(
- std::string module_name,
- ErtsCodePtr base_address,
- size_t code_size,
- const std::vector<BeamAssembler::AsmRange> &ranges) {
+void beamasm_metadata_update(std::string module_name,
+ ErtsCodePtr base_address,
+ size_t code_size,
+ const std::vector<AsmRange> &ranges) {
#ifdef HAVE_LINUX_PERF_SUPPORT
perf.update(ranges);
#endif
diff --git a/erts/emulator/beam/jit/beam_jit_types.hpp b/erts/emulator/beam/jit/beam_jit_types.hpp
new file mode 100644
index 0000000000..74edc66496
--- /dev/null
+++ b/erts/emulator/beam/jit/beam_jit_types.hpp
@@ -0,0 +1,150 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+#ifndef __BEAM_JIT_TYPES_HPP__
+#define __BEAM_JIT_TYPES_HPP__
+
+/* Type-safe wrapper around the definitions in beam_types.h. We've redefined it
+ * as an `enum class` to force the usage of our helpers, which lets us check
+ * for common usage errors at compile time. */
+enum class BeamTypeId : int {
+ None = BEAM_TYPE_NONE,
+
+ Atom = BEAM_TYPE_ATOM,
+ Bitstring = BEAM_TYPE_BITSTRING,
+ BsMatchState = BEAM_TYPE_BS_MATCHSTATE,
+ Cons = BEAM_TYPE_CONS,
+ Float = BEAM_TYPE_FLOAT,
+ Fun = BEAM_TYPE_FUN,
+ Integer = BEAM_TYPE_INTEGER,
+ Map = BEAM_TYPE_MAP,
+ Nil = BEAM_TYPE_NIL,
+ Pid = BEAM_TYPE_PID,
+ Port = BEAM_TYPE_PORT,
+ Reference = BEAM_TYPE_REFERENCE,
+ Tuple = BEAM_TYPE_TUPLE,
+
+ Any = BEAM_TYPE_ANY,
+
+ Identifier = Pid | Port | Reference,
+ List = Cons | Nil,
+ Number = Float | Integer,
+
+ /** @brief Types that can be boxed, including those that may also be
+ * immediates (e.g. pids, integers). */
+ MaybeBoxed = Bitstring | BsMatchState | Float | Fun | Integer | Map | Pid |
+ Port | Reference | Tuple,
+ /** @brief Types that can be immediates, including those that may also be
+ * boxed (e.g. pids, integers). */
+ MaybeImmediate = Atom | Integer | Nil | Pid | Port,
+
+ /** @brief Types that are _always_ boxed. */
+ AlwaysBoxed = MaybeBoxed & ~(Cons | MaybeImmediate),
+ /** @brief Types that are _always_ immediates. */
+ AlwaysImmediate = MaybeImmediate & ~(Cons | MaybeBoxed),
+};
+
+template<BeamTypeId... T>
+struct BeamTypeIdUnion;
+
+template<>
+struct BeamTypeIdUnion<> {
+ static constexpr BeamTypeId value() {
+ return BeamTypeId::None;
+ }
+};
+
+template<BeamTypeId T, BeamTypeId... Rest>
+struct BeamTypeIdUnion<T, Rest...> : BeamTypeIdUnion<Rest...> {
+ using integral = std::underlying_type_t<BeamTypeId>;
+ using super = BeamTypeIdUnion<Rest...>;
+
+ /* Overlapping type specifications are redundant at best and a subtle error
+ * at worst. We've had several bugs where `Integer | MaybeBoxed` was used
+ * instead of `Integer | AlwaysBoxed` or similar, and erroneously drew the
+ * conclusion that the value is always an integer when not boxed, when it
+ * could also be a pid or port. */
+ static constexpr bool no_overlap =
+ (static_cast<integral>(super::value()) &
+ static_cast<integral>(T)) == BEAM_TYPE_NONE;
+ static constexpr bool no_boxed_overlap =
+ no_overlap || (super::value() != BeamTypeId::MaybeBoxed &&
+ T != BeamTypeId::MaybeBoxed);
+ static constexpr bool no_immed_overlap =
+ no_overlap || (super::value() != BeamTypeId::MaybeImmediate &&
+ T != BeamTypeId::MaybeImmediate);
+
+ static_assert(no_boxed_overlap,
+ "types must not overlap, did you mean to use "
+ "BeamTypeId::AlwaysBoxed here?");
+ static_assert(no_immed_overlap,
+ "types must not overlap, did you mean to use "
+ "BeamTypeId::AlwaysImmediate here?");
+ static_assert(no_overlap || no_boxed_overlap || no_immed_overlap,
+ "types must not overlap");
+
+ static constexpr bool is_single_typed() {
+ constexpr auto V = static_cast<integral>(value());
+ return (static_cast<integral>(V) & (static_cast<integral>(V) - 1)) ==
+ BEAM_TYPE_NONE;
+ }
+
+ static constexpr BeamTypeId value() {
+ return static_cast<BeamTypeId>(static_cast<integral>(super::value()) |
+ static_cast<integral>(T));
+ }
+};
+
+struct BeamArgType : public BeamType {
+ BeamTypeId type() const {
+ return static_cast<BeamTypeId>(BeamType::type_union);
+ }
+
+ bool hasLowerBound() const {
+ return metadata_flags & BEAM_TYPE_HAS_LOWER_BOUND;
+ }
+
+ bool hasUpperBound() const {
+ return metadata_flags & BEAM_TYPE_HAS_UPPER_BOUND;
+ }
+
+ bool hasUnit() const {
+ return metadata_flags & BEAM_TYPE_HAS_UNIT;
+ }
+
+ auto max() const {
+ ASSERT(hasUpperBound());
+ return BeamType::max;
+ }
+
+ auto min() const {
+ ASSERT(hasLowerBound());
+ return BeamType::min;
+ }
+
+ auto unit() const {
+ ASSERT(hasUnit());
+ return BeamType::size_unit;
+ }
+};
+
+static_assert(std::is_standard_layout<BeamArgType>::value);
+
+#endif
diff --git a/erts/emulator/beam/jit/load.h b/erts/emulator/beam/jit/load.h
index 819553ca54..3f259cfe7e 100644
--- a/erts/emulator/beam/jit/load.h
+++ b/erts/emulator/beam/jit/load.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,10 +82,15 @@ struct LoaderState_ {
unsigned int current_li; /* Current line instruction */
unsigned int *func_line; /* Mapping from function to first line instr */
+ /* Translates lambda indexes to their literals, if any. Lambdas that lack
+ * a literal (for example if they have an environment) are represented by
+ * ERTS_SWORD_MAX. */
+ SWord *lambda_literals;
+
void *ba; /* Assembler used to create x86 assembly */
- const void *native_module_exec; /* Native module after codegen */
- void *native_module_rw; /* Native module after codegen, writable mapping */
+ const void *executable_region; /* Native module after codegen */
+ void *writable_region; /* Native module after codegen, writable mapping */
int function_number;
int last_label;
diff --git a/erts/emulator/beam/jit/x86/beam_asm.hpp b/erts/emulator/beam/jit/x86/beam_asm.hpp
index 66891730ef..c7f085ee62 100644
--- a/erts/emulator/beam/jit/x86/beam_asm.hpp
+++ b/erts/emulator/beam/jit/x86/beam_asm.hpp
@@ -52,18 +52,20 @@ extern "C"
using namespace asmjit;
-class BeamAssembler : public ErrorHandler {
-protected:
- /* Holds code and relocation information. */
- CodeHolder code;
-
- /* TODO: Want to change this to x86::Builder in order to be able to patch
- * the correct I into the code after code generation */
- x86::Assembler a;
+struct BeamAssembler : public BeamAssemblerCommon {
+ BeamAssembler() : BeamAssemblerCommon(a) {
+ Error err = code.attach(&a);
+ ERTS_ASSERT(!err && "Failed to attach codeHolder");
+ }
- FileLogger logger;
+ BeamAssembler(const std::string &log) : BeamAssembler() {
+ if (erts_jit_asm_dump) {
+ setLogger(log + ".asm");
+ }
+ }
- Section *rodata = nullptr;
+protected:
+ x86::Assembler a;
/* * * * * * * * * */
@@ -170,27 +172,6 @@ protected:
enum Distance { dShort, dLong };
-public:
- static bool hasCpuFeature(uint32_t featureId);
-
- BeamAssembler();
- BeamAssembler(const std::string &log);
-
- ~BeamAssembler();
-
- void *getBaseAddress();
- size_t getOffset();
-
-protected:
- void _codegen(JitAllocator *allocator,
- const void **executable_ptr,
- void **writable_ptr);
-
- void *getCode(Label label);
- byte *getCode(char *labelName);
-
- void handleError(Error err, const char *message, BaseEmitter *origin);
-
constexpr x86::Mem getRuntimeStackRef() const {
int base = offsetof(ErtsSchedulerRegisters, aux_regs.d.runtime_stack);
@@ -577,11 +558,27 @@ protected:
#endif
}
+ /* Prefer `eHeapAlloc` over `eStack | eHeap` when calling
+ * functions in the runtime system that allocate heap
+ * memory (`HAlloc`, heap factories, etc).
+ *
+ * Prefer `eHeapOnlyAlloc` over `eHeapAlloc` for functions
+ * that assume there's already a certain amount of free
+ * space on the heap, such as those using `HeapOnlyAlloc`
+ * or similar. It's slightly cheaper in release builds,
+ * and in debug builds it updates `eStack` to ensure that
+ * we can make heap size assertions. */
enum Update : int {
eStack = (1 << 0),
eHeap = (1 << 1),
eReductions = (1 << 2),
- eCodeIndex = (1 << 3)
+ eCodeIndex = (1 << 3),
+ eHeapAlloc = Update::eHeap | Update::eStack,
+#ifndef DEBUG
+ eHeapOnlyAlloc = Update::eHeap,
+#else
+ eHeapOnlyAlloc = Update::eHeapAlloc
+#endif
};
void emit_enter_frame() {
@@ -624,9 +621,9 @@ protected:
if (ERTS_LIKELY(erts_frame_layout == ERTS_FRAME_LAYOUT_RA)) {
if ((Spec & (Update::eHeap | Update::eStack)) ==
(Update::eHeap | Update::eStack)) {
- /* To update both heap and stack we use sse instructions like
- * gcc -O3 does. Basically it is this function run through
- * gcc -O3:
+ /* To update both heap and stack we use SSE/AVX
+ * instructions like gcc -O3 does. Basically it is
+ * this function run through gcc -O3:
*
* struct a { long a; long b; long c; };
* void test(long a, long b, long c, struct a *s) {
@@ -636,11 +633,18 @@ protected:
* } */
ERTS_CT_ASSERT((offsetof(Process, stop) -
offsetof(Process, htop)) == sizeof(Eterm *));
- a.movq(x86::xmm0, HTOP);
- a.movq(x86::xmm1, E);
- a.punpcklqdq(x86::xmm0, x86::xmm1);
- a.movups(x86::xmmword_ptr(c_p, offsetof(Process, htop)),
- x86::xmm0);
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.vmovq(x86::xmm1, HTOP);
+ a.vpinsrq(x86::xmm0, x86::xmm1, E, 1);
+ a.vmovdqu(x86::xmmword_ptr(c_p, offsetof(Process, htop)),
+ x86::xmm0);
+ } else {
+ a.movq(x86::xmm0, HTOP);
+ a.movq(x86::xmm1, E);
+ a.punpcklqdq(x86::xmm0, x86::xmm1);
+ a.movups(x86::xmmword_ptr(c_p, offsetof(Process, htop)),
+ x86::xmm0);
+ }
} else if (Spec & Update::eHeap) {
a.mov(x86::qword_ptr(c_p, offsetof(Process, htop)), HTOP);
} else if (Spec & Update::eStack) {
@@ -659,11 +663,18 @@ protected:
if (Spec & Update::eStack) {
ERTS_CT_ASSERT((offsetof(Process, frame_pointer) -
offsetof(Process, stop)) == sizeof(Eterm *));
- a.movq(x86::xmm0, E);
- a.movq(x86::xmm1, frame_pointer);
- a.punpcklqdq(x86::xmm0, x86::xmm1);
- a.movups(x86::xmmword_ptr(c_p, offsetof(Process, stop)),
- x86::xmm0);
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.vmovq(x86::xmm1, E);
+ a.vpinsrq(x86::xmm0, x86::xmm1, frame_pointer, 1);
+ a.vmovdqu(x86::xmmword_ptr(c_p, offsetof(Process, stop)),
+ x86::xmm0);
+ } else {
+ a.movq(x86::xmm0, E);
+ a.movq(x86::xmm1, frame_pointer);
+ a.punpcklqdq(x86::xmm0, x86::xmm1);
+ a.movups(x86::xmmword_ptr(c_p, offsetof(Process, stop)),
+ x86::xmm0);
+ }
} else {
/* We can skip updating the frame pointer whenever the process
* doesn't have to inspect the stack. We still need to update
@@ -693,6 +704,14 @@ protected:
a.sub(x86::rsp, imm(15));
a.and_(x86::rsp, imm(-16));
#endif
+ /* If the emulator has not been compiled with AVX support (which stops
+ * it from using legacy SSE instructions), we'll need to clear the upper
+ * bits of all AVX registers to avoid AVX/SSE transition penalties. */
+#if !defined(__AVX__)
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.vzeroupper();
+ }
+#endif
}
template<int Spec = 0>
@@ -747,7 +766,7 @@ protected:
#endif
}
- void emit_is_boxed(Label Fail, x86::Gp Src, Distance dist = dLong) {
+ void emit_test_boxed(x86::Gp Src) {
/* Use the shortest possible instruction depending on the source
* register. */
if (Src == x86::rax || Src == x86::rdi || Src == x86::rsi ||
@@ -756,6 +775,10 @@ protected:
} else {
a.test(Src.r32(), imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_BOXED));
}
+ }
+
+ void emit_is_boxed(Label Fail, x86::Gp Src, Distance dist = dLong) {
+ emit_test_boxed(Src);
if (dist == dShort) {
a.short_().jne(Fail);
} else {
@@ -763,6 +786,15 @@ protected:
}
}
+ void emit_is_not_boxed(Label Fail, x86::Gp Src, Distance dist = dLong) {
+ emit_test_boxed(Src);
+ if (dist == dShort) {
+ a.short_().je(Fail);
+ } else {
+ a.je(Fail);
+ }
+ }
+
x86::Gp emit_ptr_val(x86::Gp Dst, x86::Gp Src) {
#if !defined(TAG_LITERAL_PTR)
return Src;
@@ -864,114 +896,233 @@ protected:
mov_imm(to, 0);
}
-public:
- void embed_rodata(const char *labelName, const char *buff, size_t size);
- void embed_bss(const char *labelName, size_t size);
- void embed_zeros(size_t size);
-
- void setLogger(std::string log);
- void setLogger(FILE *log);
+ template<typename Dst, typename Src>
+ void vmovups(Dst dst, Src src) {
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.vmovups(dst, src);
+ } else {
+ a.movups(dst, src);
+ }
+ }
- void comment(const char *format) {
- if (logger.file()) {
- a.commentf("# %s", format);
+ template<typename Dst, typename Src>
+ void vmovsd(Dst dst, Src src) {
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.vmovsd(dst, src);
+ } else {
+ a.movsd(dst, src);
}
}
- template<typename... Ts>
- void comment(const char *format, Ts... args) {
- if (logger.file()) {
- char buff[1024];
- erts_snprintf(buff, sizeof(buff), format, args...);
- a.commentf("# %s", buff);
+ template<typename Dst, typename Src>
+ void vucomisd(Dst dst, Src src) {
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.vucomisd(dst, src);
+ } else {
+ a.ucomisd(dst, src);
}
}
- struct AsmRange {
- ErtsCodePtr start;
- ErtsCodePtr stop;
- const std::string name;
+ /* Copies `count` words from `from` to `to`.
+ *
+ * Clobbers `spill` and the first vector register (xmm0, ymm0 etc). */
+ void emit_copy_words(x86::Mem from,
+ x86::Mem to,
+ Sint32 count,
+ x86::Gp spill) {
+ ASSERT(!from.hasIndex() && !to.hasIndex());
+ ASSERT(count >= 0 && count < (ERTS_SINT32_MAX / (Sint32)sizeof(UWord)));
+ ASSERT(from.offset() < ERTS_SINT32_MAX - count * (Sint32)sizeof(UWord));
+ ASSERT(to.offset() < ERTS_SINT32_MAX - count * (Sint32)sizeof(UWord));
+
+ /* We're going to mix sizes pretty wildly below, so it's easiest to
+ * turn off size validation. */
+ from.setSize(0);
+ to.setSize(0);
+
+ using vectors = std::initializer_list<std::tuple<x86::Vec,
+ Sint32,
+ x86::Inst::Id,
+ CpuFeatures::X86::Id>>;
+ for (const auto &spec : vectors{{x86::zmm0,
+ 8,
+ x86::Inst::kIdVmovups,
+ CpuFeatures::X86::kAVX512_VL},
+ {x86::zmm0,
+ 8,
+ x86::Inst::kIdVmovups,
+ CpuFeatures::X86::kAVX512_F},
+ {x86::ymm0,
+ 4,
+ x86::Inst::kIdVmovups,
+ CpuFeatures::X86::kAVX},
+ {x86::xmm0,
+ 2,
+ x86::Inst::kIdVmovups,
+ CpuFeatures::X86::kAVX},
+ {x86::xmm0,
+ 2,
+ x86::Inst::kIdMovups,
+ CpuFeatures::X86::kSSE}}) {
+ const auto &[vector_reg, vector_size, vector_inst, feature] = spec;
+
+ if (!hasCpuFeature(feature)) {
+ continue;
+ }
- struct LineData {
- ErtsCodePtr start;
- const std::string file;
- unsigned line;
- };
+ /* Copy the words inline if we can, otherwise use a loop with the
+ * largest vector size we're capable of. */
+ if (count <= vector_size * 4) {
+ while (count >= vector_size) {
+ a.emit(vector_inst, vector_reg, from);
+ a.emit(vector_inst, to, vector_reg);
+
+ from.addOffset(sizeof(UWord) * vector_size);
+ to.addOffset(sizeof(UWord) * vector_size);
+ count -= vector_size;
+ }
+ } else {
+ Sint32 loop_iterations, loop_size;
+ Label copy_next = a.newLabel();
- const std::vector<LineData> lines;
- };
-};
+ loop_iterations = count / vector_size;
+ loop_size = loop_iterations * vector_size * sizeof(UWord);
-#include "beam_asm_global.hpp"
+ from.addOffset(loop_size);
+ to.addOffset(loop_size);
+ from.setIndex(spill);
+ to.setIndex(spill);
-class BeamModuleAssembler : public BeamAssembler {
- typedef unsigned BeamLabel;
+ mov_imm(spill, -loop_size);
+ a.bind(copy_next);
+ {
+ a.emit(vector_inst, vector_reg, from);
+ a.emit(vector_inst, to, vector_reg);
- /* Map of label number to asmjit Label */
- typedef std::unordered_map<BeamLabel, const Label> LabelMap;
- LabelMap rawLabels;
+ a.add(spill, imm(vector_size * sizeof(UWord)));
+ a.short_().jne(copy_next);
+ }
- struct patch {
- Label where;
- int64_t ptr_offs;
- int64_t val_offs;
- };
+ from.resetIndex();
+ to.resetIndex();
- struct patch_catch {
- struct patch patch;
- Label handler;
- };
- std::vector<struct patch_catch> catches;
+ count %= vector_size;
+ }
+ }
- /* Map of import entry to patch labels and mfa */
- struct patch_import {
- std::vector<struct patch> patches;
- ErtsCodeMFA mfa;
- };
- typedef std::unordered_map<unsigned, struct patch_import> ImportMap;
- ImportMap imports;
+ if (count == 1) {
+ a.mov(spill, from);
+ a.mov(to, spill);
- /* Map of fun entry to trampoline labels and patches */
- struct patch_lambda {
- std::vector<struct patch> patches;
- Label trampoline;
- };
- typedef std::unordered_map<unsigned, struct patch_lambda> LambdaMap;
- LambdaMap lambdas;
+ count -= 1;
+ }
- /* Map of literals to patch labels */
- struct patch_literal {
- std::vector<struct patch> patches;
- };
- typedef std::unordered_map<unsigned, struct patch_literal> LiteralMap;
- LiteralMap literals;
+ ASSERT(count == 0);
+ (void)count;
+ }
+};
+
+#include "beam_asm_global.hpp"
+
+class BeamModuleAssembler : public BeamAssembler,
+ public BeamModuleAssemblerCommon {
+ BeamGlobalAssembler *ga;
- /* All string patches */
- std::vector<struct patch> strings;
+ /* Save the last PC for an error. */
+ size_t last_error_offset = 0;
- /* All functions that have been seen so far */
- std::vector<BeamLabel> functions;
+ /* Skip unnecessary moves in mov_arg() and cmp_arg(). */
+ size_t last_movarg_offset = 0;
+ x86::Gp last_movarg_from1, last_movarg_from2;
+ x86::Mem last_movarg_to1, last_movarg_to2;
- /* The BEAM file we've been loaded from, if any. */
- const BeamFile *beam;
+ /* Private helper. */
+ void preserve__cache(x86::Gp dst) {
+ last_movarg_offset = a.offset();
+ invalidate_cache(dst);
+ }
- BeamGlobalAssembler *ga;
+ bool is_cache_valid() {
+ return a.offset() == last_movarg_offset;
+ }
- Label code_header;
+ void preserve_cache(x86::Gp dst, bool cache_valid) {
+ if (cache_valid) {
+ preserve__cache(dst);
+ }
+ }
- /* Used by emit to populate the labelToMFA map */
- Label current_label;
+ /* Store CPU register into memory and update the cache. */
+ void store_cache(x86::Gp src, x86::Mem dst) {
+ if (is_cache_valid() && dst != last_movarg_to1) {
+ /* Something is already cached in the first slot. Use the
+ * second slot. */
+ a.mov(dst, src);
- /* The module's on_load function, if any. */
- Label on_load;
+ last_movarg_offset = a.offset();
+ last_movarg_to2 = dst;
+ last_movarg_from2 = src;
+ } else {
+ /* Nothing cached yet, or the first slot has the same
+ * memory address as we will store into. Use the first
+ * slot and invalidate the second slot. */
+ a.mov(dst, src);
- /* The end of the last function. */
- Label code_end;
+ last_movarg_offset = a.offset();
+ last_movarg_to1 = dst;
+ last_movarg_from1 = src;
- Eterm mod;
+ last_movarg_to2 = x86::Mem();
+ }
+ }
- /* Save the last PC for an error. */
- size_t last_error_offset = 0;
+ void invalidate_cache(x86::Gp dst) {
+ if (dst == last_movarg_from1) {
+ last_movarg_to1 = x86::Mem();
+ last_movarg_from1 = x86::Gp();
+ }
+ if (dst == last_movarg_from2) {
+ last_movarg_to2 = x86::Mem();
+ last_movarg_from2 = x86::Gp();
+ }
+ }
+
+ x86::Gp cached_reg(x86::Mem mem) {
+ if (is_cache_valid()) {
+ if (mem == last_movarg_to1) {
+ return last_movarg_from1;
+ }
+ if (mem == last_movarg_to2) {
+ return last_movarg_from2;
+ }
+ }
+ return x86::Gp();
+ }
+
+ void load_cached(x86::Gp dst, x86::Mem mem) {
+ if (a.offset() == last_movarg_offset) {
+ x86::Gp reg = cached_reg(mem);
+
+ if (reg.isValid()) {
+ /* This memory location is cached. */
+ if (reg != dst) {
+ comment("simplified fetching of BEAM register");
+ a.mov(dst, reg);
+ preserve__cache(dst);
+ } else {
+ comment("skipped fetching of BEAM register");
+ invalidate_cache(dst);
+ }
+ } else {
+ /* Not cached. Load and preserve the cache. */
+ a.mov(dst, mem);
+ preserve__cache(dst);
+ }
+ } else {
+ /* The cache is invalid. */
+ a.mov(dst, mem);
+ }
+ }
/* Maps code pointers to thunks that jump to them, letting us treat global
* fragments as if they were local. */
@@ -1027,180 +1178,19 @@ public:
const ErtsCodeInfo *getOnLoad(void);
unsigned patchCatches(char *rw_base);
- void patchLambda(char *rw_base, unsigned index, BeamInstr I);
+ void patchLambda(char *rw_base, unsigned index, const ErlFunEntry *fe);
void patchLiteral(char *rw_base, unsigned index, Eterm lit);
- void patchImport(char *rw_base, unsigned index, BeamInstr I);
+ void patchImport(char *rw_base, unsigned index, const Export *import);
void patchStrings(char *rw_base, const byte *string);
protected:
- int getTypeUnion(const ArgSource &arg) const {
- auto typeIndex =
- arg.isRegister() ? arg.as<ArgRegister>().typeIndex() : 0;
-
- ASSERT(typeIndex < beam->types.count);
- return beam->types.entries[typeIndex].type_union;
- }
-
- auto getIntRange(const ArgSource &arg) const {
- if (arg.isSmall()) {
- Sint value = arg.as<ArgSmall>().getSigned();
- return std::make_pair(value, value);
- } else {
- auto typeIndex =
- arg.isRegister() ? arg.as<ArgRegister>().typeIndex() : 0;
-
- ASSERT(typeIndex < beam->types.count);
- const auto &entry = beam->types.entries[typeIndex];
- ASSERT(entry.type_union & BEAM_TYPE_INTEGER);
- return std::make_pair(entry.min, entry.max);
- }
- }
-
- bool always_small(const ArgSource &arg) const {
- if (arg.isSmall()) {
- return true;
- }
-
- int type_union = getTypeUnion(arg);
- if (type_union == BEAM_TYPE_INTEGER) {
- auto [min, max] = getIntRange(arg);
- return min <= max;
- } else {
- return false;
- }
- }
-
- bool always_immediate(const ArgSource &arg) const {
- if (arg.isImmed() || always_small(arg)) {
- return true;
- }
-
- int type_union = getTypeUnion(arg);
- return (type_union & BEAM_TYPE_MASK_ALWAYS_IMMEDIATE) == type_union;
- }
-
- bool always_same_types(const ArgSource &lhs, const ArgSource &rhs) const {
- int lhs_types = getTypeUnion(lhs);
- int rhs_types = getTypeUnion(rhs);
-
- /* We can only be certain that the types are the same when there's
- * one possible type. For example, if one is a number and the other
- * is an integer, they could differ if the former is a float. */
- if ((lhs_types & (lhs_types - 1)) == 0) {
- return lhs_types == rhs_types;
- }
-
- return false;
- }
-
- bool always_one_of(const ArgSource &arg, int types) const {
- if (arg.isImmed()) {
- if (arg.isSmall()) {
- return !!(types & BEAM_TYPE_INTEGER);
- } else if (arg.isAtom()) {
- return !!(types & BEAM_TYPE_ATOM);
- } else if (arg.isNil()) {
- return !!(types & BEAM_TYPE_NIL);
- }
-
- return false;
- } else {
- int type_union = getTypeUnion(arg);
- return type_union == (type_union & types);
- }
- }
-
- int masked_types(const ArgSource &arg, int mask) const {
- if (arg.isImmed()) {
- if (arg.isSmall()) {
- return mask & BEAM_TYPE_INTEGER;
- } else if (arg.isAtom()) {
- return mask & BEAM_TYPE_ATOM;
- } else if (arg.isNil()) {
- return mask & BEAM_TYPE_NIL;
- }
-
- return BEAM_TYPE_NONE;
- } else {
- return getTypeUnion(arg) & mask;
- }
- }
-
- bool exact_type(const ArgSource &arg, int type_id) const {
- return always_one_of(arg, type_id);
- }
-
- bool is_sum_small(const ArgSource &LHS, const ArgSource &RHS) {
- if (!(always_small(LHS) && always_small(RHS))) {
- return false;
- } else {
- Sint min, max;
- auto [min1, max1] = getIntRange(LHS);
- auto [min2, max2] = getIntRange(RHS);
- min = min1 + min2;
- max = max1 + max2;
- return IS_SSMALL(min) && IS_SSMALL(max);
- }
- }
-
- bool is_difference_small(const ArgSource &LHS, const ArgSource &RHS) {
- if (!(always_small(LHS) && always_small(RHS))) {
- return false;
- } else {
- Sint min, max;
- auto [min1, max1] = getIntRange(LHS);
- auto [min2, max2] = getIntRange(RHS);
- min = min1 - max2;
- max = max1 - min2;
- return IS_SSMALL(min) && IS_SSMALL(max);
- }
- }
-
- bool is_product_small(const ArgSource &LHS, const ArgSource &RHS) {
- if (!(always_small(LHS) && always_small(RHS))) {
- return false;
- } else {
- auto [min1, max1] = getIntRange(LHS);
- auto [min2, max2] = getIntRange(RHS);
- auto mag1 = std::max(std::abs(min1), std::abs(max1));
- auto mag2 = std::max(std::abs(min2), std::abs(max2));
-
- /*
- * mag1 * mag2 <= MAX_SMALL
- * mag1 <= MAX_SMALL / mag2 (when mag2 != 0)
- */
- ERTS_CT_ASSERT(MAX_SMALL < -MIN_SMALL);
- return mag2 == 0 || mag1 <= MAX_SMALL / mag2;
- }
- }
-
- bool is_bsl_small(const ArgSource &LHS, const ArgSource &RHS) {
- /*
- * In the code compiled by scripts/diffable, there never
- * seems to be any range information for the RHS. Therefore,
- * don't bother unless RHS is an immediate small.
- */
- if (!(always_small(LHS) && RHS.isSmall())) {
- return false;
- } else {
- auto [min1, max1] = getIntRange(LHS);
- auto rhs_val = RHS.as<ArgSmall>().getSigned();
-
- if (min1 < 0 || max1 == 0 || rhs_val < 0) {
- return false;
- }
-
- return rhs_val < Support::clz(max1) - _TAG_IMMED1_SIZE;
- }
- }
-
- /* Helpers */
void emit_gc_test(const ArgWord &Stack,
const ArgWord &Heap,
const ArgWord &Live);
void emit_gc_test_preserve(const ArgWord &Need,
const ArgWord &Live,
- x86::Gp term);
+ const ArgSource &Preserve,
+ x86::Gp preserve_reg);
x86::Mem emit_variable_apply(bool includeI);
x86::Mem emit_fixed_apply(const ArgWord &arity, bool includeI);
@@ -1209,11 +1199,6 @@ protected:
bool skip_fun_test = false,
bool skip_arity_test = false);
- x86::Gp emit_is_binary(const ArgLabel &Fail,
- const ArgSource &Src,
- Label next,
- Label subbin);
-
void emit_is_boxed(Label Fail, x86::Gp Src, Distance dist = dLong) {
BeamAssembler::emit_is_boxed(Fail, Src, dist);
}
@@ -1222,7 +1207,7 @@ protected:
const ArgVal &Arg,
x86::Gp Src,
Distance dist = dLong) {
- if (always_one_of(Arg, BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::AlwaysBoxed>(Arg)) {
comment("skipped box test since argument is always boxed");
return;
}
@@ -1246,10 +1231,12 @@ protected:
void emit_error(int code);
- x86::Mem emit_bs_get_integer_prologue(Label next,
- Label fail,
- int flags,
- int size);
+ void emit_bs_get_integer(const ArgRegister &Ctx,
+ const ArgLabel &Fail,
+ const ArgWord &Live,
+ const ArgWord Flags,
+ int bits,
+ const ArgRegister &Dst);
int emit_bs_get_field_size(const ArgSource &Size,
int unit,
@@ -1261,6 +1248,40 @@ protected:
void emit_bs_get_utf16(const ArgRegister &Ctx,
const ArgLabel &Fail,
const ArgWord &Flags);
+ void update_bin_state(x86::Gp bin_offset,
+ x86::Gp current_byte,
+ Sint bit_offset,
+ Sint size,
+ x86::Gp size_reg);
+ bool need_mask(const ArgVal Val, Sint size);
+ void set_zero(Sint effectiveSize);
+ bool bs_maybe_enter_runtime(bool entered);
+ void bs_maybe_leave_runtime(bool entered);
+ void emit_construct_utf8_shared();
+ void emit_construct_utf8(const ArgVal &Src,
+ Sint bit_offset,
+ bool is_byte_aligned);
+
+ void emit_read_bits(Uint bits,
+ const x86::Gp bin_base,
+ const x86::Gp bin_offset,
+ const x86::Gp bitdata);
+ void emit_extract_integer(const x86::Gp bitdata,
+ const x86::Gp tmp,
+ Uint flags,
+ Uint bits,
+ const ArgRegister &Dst);
+ void emit_extract_binary(const x86::Gp bitdata,
+ Uint bits,
+ const ArgRegister &Dst);
+ void emit_read_integer(const x86::Gp bin_base,
+ const x86::Gp bin_position,
+ const x86::Gp tmp,
+ Uint flags,
+ Uint bits,
+ const ArgRegister &Dst);
+
+ UWord bs_get_flags(const ArgVal &val);
void emit_raise_exception();
void emit_raise_exception(const ErtsCodeMFA *exp);
@@ -1274,7 +1295,8 @@ protected:
const ArgVal &Fail,
const Span<ArgVal> &args);
- void emit_float_instr(uint32_t instId,
+ void emit_float_instr(uint32_t instIdSSE,
+ uint32_t instIdAVX,
const ArgFRegister &LHS,
const ArgFRegister &RHS,
const ArgFRegister &Dst);
@@ -1294,6 +1316,16 @@ protected:
Eterm fail_value,
Eterm succ_value);
+ void emit_cond_to_bool(uint32_t instId, const ArgRegister &Dst);
+ void emit_bif_is_ge_lt(uint32_t instId,
+ const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst);
+ void emit_bif_min_max(uint32_t instId,
+ const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst);
+
void emit_proc_lc_unrequire(void);
void emit_proc_lc_require(void);
@@ -1338,7 +1370,7 @@ protected:
void make_move_patch(x86::Gp to,
std::vector<struct patch> &patches,
- int64_t offset = 0) {
+ size_t offset = 0) {
const int MOV_IMM64_PAYLOAD_OFFSET = 2;
Label lbl = a.newLabel();
@@ -1374,13 +1406,33 @@ protected:
}
void cmp_arg(x86::Mem mem, const ArgVal &val, const x86::Gp &spill) {
- /* Note that the cast to Sint is necessary to handle negative numbers
- * such as NIL. */
- if (val.isImmed() && Support::isInt32((Sint)val.as<ArgImmed>().get())) {
- a.cmp(mem, imm(val.as<ArgImmed>().get()));
+ x86::Gp reg = cached_reg(mem);
+
+ if (reg.isValid()) {
+ /* Note that the cast to Sint is necessary to handle
+ * negative numbers such as NIL. */
+ if (val.isImmed() &&
+ Support::isInt32((Sint)val.as<ArgImmed>().get())) {
+ comment("simplified compare of BEAM register");
+ a.cmp(reg, imm(val.as<ArgImmed>().get()));
+ } else if (reg != spill) {
+ comment("simplified compare of BEAM register");
+ mov_arg(spill, val);
+ a.cmp(reg, spill);
+ } else {
+ mov_arg(spill, val);
+ a.cmp(mem, spill);
+ }
} else {
- mov_arg(spill, val);
- a.cmp(mem, spill);
+ /* Note that the cast to Sint is necessary to handle
+ * negative numbers such as NIL. */
+ if (val.isImmed() &&
+ Support::isInt32((Sint)val.as<ArgImmed>().get())) {
+ a.cmp(mem, imm(val.as<ArgImmed>().get()));
+ } else {
+ mov_arg(spill, val);
+ a.cmp(mem, spill);
+ }
}
}
@@ -1393,8 +1445,31 @@ protected:
}
}
+ void cmp(x86::Gp gp, int64_t val, const x86::Gp &spill) {
+ if (Support::isInt32(val)) {
+ a.cmp(gp, imm(val));
+ } else if (gp.isGpd()) {
+ mov_imm(spill, val);
+ a.cmp(gp, spill.r32());
+ } else {
+ mov_imm(spill, val);
+ a.cmp(gp, spill);
+ }
+ }
+
+ void sub(x86::Gp gp, int64_t val, const x86::Gp &spill) {
+ if (Support::isInt32(val)) {
+ a.sub(gp, imm(val));
+ } else {
+ mov_imm(spill, val);
+ a.sub(gp, spill);
+ }
+ }
+
/* Note: May clear flags. */
void mov_arg(x86::Gp to, const ArgVal &from, const x86::Gp &spill) {
+ bool valid_cache = is_cache_valid();
+
if (from.isBytePtr()) {
make_move_patch(to, strings, from.as<ArgBytePtr>().get());
} else if (from.isExport()) {
@@ -1406,13 +1481,15 @@ protected:
} else if (from.isLiteral()) {
make_move_patch(to, literals[from.as<ArgLiteral>().get()].patches);
} else if (from.isRegister()) {
- a.mov(to, getArgRef(from.as<ArgRegister>()));
+ auto mem = getArgRef(from.as<ArgRegister>());
+ load_cached(to, mem);
} else if (from.isWord()) {
mov_imm(to, from.as<ArgWord>().get());
} else {
ASSERT(!"mov_arg with incompatible type");
}
+ preserve_cache(to, valid_cache);
#ifdef DEBUG
/* Explicitly clear flags to catch bugs quicker, it may be very rare
* for a certain instruction to load values that would otherwise cause
@@ -1431,6 +1508,15 @@ protected:
a.mov(spill, imm(val));
a.mov(to, spill);
}
+ } else if (from.isWord()) {
+ auto val = from.as<ArgWord>().get();
+
+ if (Support::isInt32((Sint)val)) {
+ a.mov(to, imm(val));
+ } else {
+ a.mov(spill, imm(val));
+ a.mov(to, spill);
+ }
} else {
mov_arg(spill, from);
a.mov(to, spill);
@@ -1440,7 +1526,8 @@ protected:
void mov_arg(const ArgVal &to, x86::Gp from, const x86::Gp &spill) {
(void)spill;
- a.mov(getArgRef(to), from);
+ auto mem = getArgRef(to);
+ store_cache(from, mem);
}
void mov_arg(const ArgVal &to, x86::Mem from, const x86::Gp &spill) {
@@ -1467,10 +1554,9 @@ protected:
}
};
-void beamasm_metadata_update(
- std::string module_name,
- ErtsCodePtr base_address,
- size_t code_size,
- const std::vector<BeamAssembler::AsmRange> &ranges);
+void beamasm_metadata_update(std::string module_name,
+ ErtsCodePtr base_address,
+ size_t code_size,
+ const std::vector<AsmRange> &ranges);
void beamasm_metadata_early_init();
void beamasm_metadata_late_init();
diff --git a/erts/emulator/beam/jit/x86/beam_asm_global.cpp b/erts/emulator/beam/jit/x86/beam_asm_global.cpp
index ec38e05323..7fdfddf276 100644
--- a/erts/emulator/beam/jit/x86/beam_asm_global.cpp
+++ b/erts/emulator/beam/jit/x86/beam_asm_global.cpp
@@ -51,11 +51,13 @@ BeamGlobalAssembler::BeamGlobalAssembler(JitAllocator *allocator)
}
{
- /* We have no need of the module pointers as we use `getCode(...)` for
- * everything. */
- const void *_ignored_exec;
- void *_ignored_rw;
- _codegen(allocator, &_ignored_exec, &_ignored_rw);
+ const void *executable_region;
+ void *writable_region;
+
+ BeamAssembler::codegen(allocator, &executable_region, &writable_region);
+ VirtMem::flushInstructionCache((void *)executable_region,
+ code.codeSize());
+ VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadExecute);
}
#ifndef WIN32
@@ -209,8 +211,7 @@ void BeamGlobalAssembler::emit_export_trampoline() {
a.bind(error_handler);
{
emit_enter_frame();
- emit_enter_runtime<Update::eReductions | Update::eStack |
- Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
a.lea(ARG2, x86::qword_ptr(RET, offsetof(Export, info.mfa)));
@@ -218,8 +219,7 @@ void BeamGlobalAssembler::emit_export_trampoline() {
mov_imm(ARG4, am_undefined_function);
runtime_call<4>(call_error_handler);
- emit_leave_runtime<Update::eReductions | Update::eStack |
- Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
a.test(RET, RET);
a.je(labels[process_exit]);
@@ -323,7 +323,7 @@ void BeamGlobalAssembler::emit_raise_exception() {
void BeamGlobalAssembler::emit_raise_exception_shared() {
Label crash = a.newLabel();
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapAlloc>();
/* The error address must be a valid CP or NULL. */
a.test(ARG2d, imm(_CPMASK));
@@ -334,7 +334,7 @@ void BeamGlobalAssembler::emit_raise_exception_shared() {
load_x_reg_array(ARG3);
runtime_call<4>(handle_error);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapAlloc>();
a.test(RET, RET);
a.je(labels[do_schedule]);
diff --git a/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl b/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl
index 898a1f6065..af16da1eee 100755
--- a/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl
+++ b/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl
@@ -31,8 +31,9 @@ my @beam_global_funcs = qw(
bs_add_shared
bs_create_bin_error_shared
bs_size_check_shared
- bs_fixed_integer_shared
bs_get_tail_shared
+ bs_get_utf8_shared
+ bs_get_utf8_short_shared
call_bif_shared
call_light_bif_shared
call_nif_early
@@ -40,19 +41,26 @@ my @beam_global_funcs = qw(
call_nif_yield_helper
catch_end_shared
check_float_error
+ construct_utf8_shared
dispatch_bif
dispatch_nif
dispatch_return
- dispatch_save_calls
+ dispatch_save_calls_export
+ dispatch_save_calls_fun
export_trampoline
garbage_collect
generic_bp_global
generic_bp_local
+ get_sint64_shared
debug_bp
fconv_shared
handle_call_fun_error
handle_element_error
handle_hd_error
+ handle_map_get_badkey
+ handle_map_get_badmap
+ handle_map_size_error
+ handle_node_error
i_band_body_shared
i_band_guard_shared
i_bif_body_shared
@@ -75,11 +83,11 @@ my @beam_global_funcs = qw(
i_length_guard_shared
i_length_body_shared
i_loop_rec_shared
- i_new_small_map_lit_shared
i_test_yield_shared
- increment_body_shared
int_div_rem_body_shared
int_div_rem_guard_shared
+ is_in_range_shared
+ is_ge_lt_shared
internal_hash_helper
minus_body_shared
minus_guard_shared
@@ -90,6 +98,7 @@ my @beam_global_funcs = qw(
process_main
raise_exception
raise_exception_shared
+ store_unaligned
times_body_shared
times_guard_shared
unary_minus_body_shared
diff --git a/erts/emulator/beam/jit/x86/beam_asm_module.cpp b/erts/emulator/beam/jit/x86/beam_asm_module.cpp
index be01db5ac3..bc8a11e15e 100644
--- a/erts/emulator/beam/jit/x86/beam_asm_module.cpp
+++ b/erts/emulator/beam/jit/x86/beam_asm_module.cpp
@@ -332,6 +332,8 @@ void BeamModuleAssembler::emit_label(const ArgLabel &Label) {
current_label = rawLabels[Label.get()];
a.bind(current_label);
+
+ last_movarg_offset = ~0;
}
void BeamModuleAssembler::emit_aligned_label(const ArgLabel &Label,
@@ -401,79 +403,6 @@ void BeamModuleAssembler::emit_call_error_handler() {
emit_nyi("call_error_handler should never be called");
}
-unsigned BeamModuleAssembler::patchCatches(char *rw_base) {
- unsigned catch_no = BEAM_CATCHES_NIL;
-
- for (const auto &c : catches) {
- const auto &patch = c.patch;
- ErtsCodePtr handler;
-
- handler = (ErtsCodePtr)getCode(c.handler);
- catch_no = beam_catches_cons(handler, catch_no, nullptr);
-
- /* Patch the `mov` instruction with the catch tag */
- auto offset = code.labelOffsetFromBase(patch.where);
- auto where = (unsigned *)&rw_base[offset + patch.ptr_offs];
-
- ASSERT(0x7fffffff == *where);
- Eterm catch_term = make_catch(catch_no);
-
- /* With the current tag scheme, more than 33 million
- * catches can exist at once. */
- ERTS_ASSERT(catch_term >> 31 == 0);
- *where = (unsigned)catch_term;
- }
-
- return catch_no;
-}
-
-void BeamModuleAssembler::patchImport(char *rw_base,
- unsigned index,
- BeamInstr I) {
- for (const auto &patch : imports[index].patches) {
- auto offset = code.labelOffsetFromBase(patch.where);
- auto where = (Eterm *)&rw_base[offset + patch.ptr_offs];
-
- ASSERT(LLONG_MAX == *where);
- *where = I + patch.val_offs;
- }
-}
-
-void BeamModuleAssembler::patchLambda(char *rw_base,
- unsigned index,
- BeamInstr I) {
- for (const auto &patch : lambdas[index].patches) {
- auto offset = code.labelOffsetFromBase(patch.where);
- auto where = (Eterm *)&rw_base[offset + patch.ptr_offs];
-
- ASSERT(LLONG_MAX == *where);
- *where = I + patch.val_offs;
- }
-}
-
-void BeamModuleAssembler::patchLiteral(char *rw_base,
- unsigned index,
- Eterm lit) {
- for (const auto &patch : literals[index].patches) {
- auto offset = code.labelOffsetFromBase(patch.where);
- auto where = (Eterm *)&rw_base[offset + patch.ptr_offs];
-
- ASSERT(LLONG_MAX == *where);
- *where = lit + patch.val_offs;
- }
-}
-
-void BeamModuleAssembler::patchStrings(char *rw_base,
- const byte *string_table) {
- for (const auto &patch : strings) {
- auto offset = code.labelOffsetFromBase(patch.where);
- auto where = (const byte **)&rw_base[offset + 2];
-
- ASSERT(LLONG_MAX == (Eterm)*where);
- *where = string_table + patch.val_offs;
- }
-}
-
const Label &BeamModuleAssembler::resolve_fragment(void (*fragment)()) {
auto it = _dispatchTable.find(fragment);
diff --git a/erts/emulator/beam/jit/x86/generators.tab b/erts/emulator/beam/jit/x86/generators.tab
index 4b0b2ad043..007344d17d 100644
--- a/erts/emulator/beam/jit/x86/generators.tab
+++ b/erts/emulator/beam/jit/x86/generators.tab
@@ -2,7 +2,7 @@
//
// %CopyrightBegin%
//
-// Copyright Ericsson AB 2020-2022. All Rights Reserved.
+// Copyright Ericsson AB 2020-2023. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,60 +19,6 @@
// %CopyrightEnd%
//
-// Generate the fastest instruction to fetch an integer from a binary.
-gen.get_integer2(Fail, Ms, Live, Size, Unit, Flags, Dst) {
- BeamOp* op;
- UWord bits;
-
- $NewBeamOp(S, op);
- $NativeEndian(Flags);
- if (Size.type == TAG_i) {
- if (!beam_load_safe_mul(Size.val, Unit.val, &bits)) {
- $BeamOpNameArity(op, jump, 1);
- op->a[0] = Fail;
- } else if (bits == 8) {
- $BeamOpNameArity(op, i_bs_get_integer_8, 4);
- op->a[0] = Ms;
- op->a[1] = Flags;
- op->a[2] = Fail;
- op->a[3] = Dst;
- } else if (bits == 16) {
- $BeamOpNameArity(op, i_bs_get_integer_16, 4);
- op->a[0] = Ms;
- op->a[1] = Flags;
- op->a[2] = Fail;
- op->a[3] = Dst;
- } else if (bits == 32) {
- $BeamOpNameArity(op, i_bs_get_integer_32, 4);
- op->a[0] = Ms;
- op->a[1] = Flags;
- op->a[2] = Fail;
- op->a[3] = Dst;
- } else if (bits == 64) {
- $BeamOpNameArity(op, i_bs_get_integer_64, 5);
- op->a[0] = Ms;
- op->a[1] = Flags;
- op->a[2] = Fail;
- op->a[3] = Live;
- op->a[4] = Dst;
- } else {
- goto generic;
- }
- } else {
- generic:
- $BeamOpNameArity(op, i_bs_get_integer, 6);
- op->a[0] = Ms;
- op->a[1] = Fail;
- op->a[2] = Live;
- op->a[3].type = TAG_u;
- op->a[3].val = (Unit.val << 3) | Flags.val;
- op->a[4] = Size;
- op->a[5] = Dst;
- return op;
- }
- return op;
-}
-
gen.select_tuple_arity(Src, Fail, Size, Rest) {
BeamOp* op;
BeamOpArg *tmp;
@@ -413,7 +359,7 @@ gen.new_small_map_lit(Dst, Live, Size, Rest) {
*dst++ = Rest[i + 1];
}
- lit = beamfile_add_literal(&S->beam, keys);
+ lit = beamfile_add_literal(&S->beam, keys, 1);
erts_free(ERTS_ALC_T_LOADER_TMP, tmp);
op->a[0] = Dst;
@@ -487,27 +433,6 @@ gen.combine_conses(Len, Dst, Hd) {
return cons;
}
-gen.is_eq_exact_literal(Fail, R, C) {
- BeamOp* op;
- Eterm literal;
- Uint tag_test;
-
- ASSERT(C.type == TAG_q);
- literal = beamfile_get_literal(&S->beam, C.val);
- ASSERT(is_boxed(literal) || is_list(literal));
- tag_test = _TAG_PRIMARY_MASK - (literal & _TAG_PRIMARY_MASK);
-
- $NewBeamOp(S, op);
- $BeamOpNameArity(op, i_is_eq_exact_literal, 4);
- op->a[0] = Fail;
- op->a[1] = R;
- op->a[2] = C;
- op->a[3].type = TAG_u;
- op->a[3].val = tag_test;
-
- return op;
-}
-
gen.allocate_heap_zero(Ns, Nh, Live) {
BeamOp* alloc;
BeamOp* init;
@@ -635,6 +560,28 @@ gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) {
Flags.val = flags;
$NativeEndian(Flags);
op->a[i+fixed_args+3] = Flags;
+
+ /*
+ * Replace short string segments with integer segments.
+ * Integer segments can be combined with adjacent integer
+ * segments for better performance.
+ */
+ if (op->a[i+fixed_args+0].val == am_string) {
+ Sint num_chars = op->a[i+fixed_args+5].val;
+ if (num_chars <= 4) {
+ Sint index = op->a[i+fixed_args+4].val;
+ const byte* s = S->beam.strings.data + index;
+ Uint num = 0;
+ op->a[i+fixed_args+0].val = am_integer;
+ op->a[i+fixed_args+2].val = 8;
+ op->a[i+fixed_args+5].val = num_chars;
+ while (num_chars-- > 0) {
+ num = num << 8 | *s++;
+ }
+ op->a[i+fixed_args+4].type = TAG_i;
+ op->a[i+fixed_args+4].val = num;
+ }
+ }
}
if (op->a[4].val == am_private_append && Alloc.val != 0) {
@@ -652,3 +599,77 @@ gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) {
return op;
}
+
+gen.bs_match(Fail, Ctx, N, List) {
+ BeamOp* op;
+ int fixed_args;
+ int i;
+
+ /*
+ * If a BEAM file produced by a later version of Erlang/OTP
+ * is accidentally loaded into an earlier version, ensure
+ * that the loading fails (as opposed to crashing the runtime)
+ * if there are any unknown sub commands.
+ */
+ i = 0;
+ while (i < N.val) {
+ BeamOpArg current = List[i++];
+
+ if (current.type != TAG_a) {
+ goto error;
+ }
+
+ switch (current.val) {
+ case am_ensure_exactly:
+ case am_skip:
+ i += 1;
+ break;
+ case am_ensure_at_least:
+ i += 2;
+ break;
+ case am_get_tail:
+ case am_Eq:
+ i += 3;
+ break;
+ case am_binary:
+ case am_integer:
+ i += 5;
+ break;
+ default: {
+ error:
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, bad_bs_match, 1);
+ op->a[0] = current;
+ return op;
+ }
+ }
+ }
+
+ /*
+ * Make sure that we don't attempt to pass any overflow tags to the JIT.
+ */
+
+ $NewBeamOp(S, op);
+ $BeamOpNameArity(op, i_bs_match, 2);
+ fixed_args = op->arity;
+ $BeamOpArity(op, (N.val + fixed_args));
+
+ op->a[0] = Fail;
+ op->a[1] = Ctx;
+
+ for (i = 0; i < N.val; i++) {
+ BeamOpArg current;
+
+ current = List[i];
+ if (current.type == TAG_o) {
+ /* An overflow tag (in ensure_at_least or ensure_exactly)
+ * means that the match will always fail. */
+ $BeamOpNameArity(op, jump, 1);
+ op->a[0] = Fail;
+ return op;
+ }
+ op->a[i+fixed_args] = current;
+ }
+
+ return op;
+}
diff --git a/erts/emulator/beam/jit/x86/instr_arith.cpp b/erts/emulator/beam/jit/x86/instr_arith.cpp
index 31d6ff2631..888f3109f1 100644
--- a/erts/emulator/beam/jit/x86/instr_arith.cpp
+++ b/erts/emulator/beam/jit/x86/instr_arith.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,9 @@ extern "C"
#include "erl_bif_table.h"
}
+/*
+ * Clobbers ARG1.
+ */
void BeamModuleAssembler::emit_is_small(Label fail,
const ArgSource &Arg,
x86::Gp Reg) {
@@ -38,9 +41,9 @@ void BeamModuleAssembler::emit_is_small(Label fail,
if (always_small(Arg)) {
comment("skipped test for small operand since it is always small");
- } else if (always_one_of(Arg, BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ } else if (always_one_of<BeamTypeId::Number>(Arg)) {
comment("simplified test for small operand since it is a number");
- a.test(Reg.r32(), imm(TAG_PRIMARY_LIST));
+ a.test(Reg.r8(), imm(TAG_PRIMARY_LIST));
a.short_().je(fail);
} else {
comment("is the operand small?");
@@ -51,6 +54,9 @@ void BeamModuleAssembler::emit_is_small(Label fail,
}
}
+/*
+ * Clobbers RET, ARG1.
+ */
void BeamModuleAssembler::emit_are_both_small(Label fail,
const ArgSource &LHS,
x86::Gp A,
@@ -59,13 +65,13 @@ void BeamModuleAssembler::emit_are_both_small(Label fail,
ASSERT(ARG1 != A && ARG1 != B);
if (always_small(LHS) && always_small(RHS)) {
comment("skipped test for small operands since they are always small");
- } else if (always_one_of(LHS, BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER) &&
- always_one_of(RHS, BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ } else if (always_one_of<BeamTypeId::Number>(LHS) &&
+ always_one_of<BeamTypeId::Number>(RHS)) {
comment("simplified test for small operands since both are numbers");
if (always_small(RHS)) {
- a.test(A.r32(), imm(TAG_PRIMARY_LIST));
+ a.test(A.r8(), imm(TAG_PRIMARY_LIST));
} else if (always_small(LHS)) {
- a.test(B.r32(), imm(TAG_PRIMARY_LIST));
+ a.test(B.r8(), imm(TAG_PRIMARY_LIST));
} else if (A != RET && B != RET) {
a.mov(RETd, A.r32());
a.and_(RETd, B.r32());
@@ -73,9 +79,29 @@ void BeamModuleAssembler::emit_are_both_small(Label fail,
} else {
a.mov(ARG1d, A.r32());
a.and_(ARG1d, B.r32());
- a.test(ARG1d, imm(TAG_PRIMARY_LIST));
+ a.test(ARG1.r8(), imm(TAG_PRIMARY_LIST));
}
a.short_().je(fail);
+ } else if (always_small(LHS)) {
+ if (A == RET || B == RET) {
+ emit_is_small(fail, RHS, B);
+ } else {
+ comment("is the operand small?");
+ a.mov(RETd, B.r32());
+ a.and_(RETb, imm(_TAG_IMMED1_MASK));
+ a.cmp(RETb, imm(_TAG_IMMED1_SMALL));
+ a.short_().jne(fail);
+ }
+ } else if (always_small(RHS)) {
+ if (A == RET || B == RET) {
+ emit_is_small(fail, LHS, A);
+ } else {
+ comment("is the operand small?");
+ a.mov(RETd, A.r32());
+ a.and_(RETb, imm(_TAG_IMMED1_MASK));
+ a.cmp(RETb, imm(_TAG_IMMED1_SMALL));
+ a.short_().jne(fail);
+ }
} else {
comment("are both operands small?");
if (A != RET && B != RET) {
@@ -93,83 +119,6 @@ void BeamModuleAssembler::emit_are_both_small(Label fail,
}
}
-void BeamGlobalAssembler::emit_increment_body_shared() {
- Label error = a.newLabel();
-
- emit_enter_frame();
- emit_enter_runtime();
-
- a.mov(ARG1, c_p);
- a.or_(ARG3, imm(_TAG_IMMED1_SMALL));
- runtime_call<3>(erts_mixed_plus);
-
- emit_leave_runtime();
- emit_leave_frame();
-
- emit_test_the_non_value(RET);
- a.short_().je(error);
- a.ret();
-
- a.bind(error);
- {
- mov_imm(ARG4, 0);
- a.jmp(labels[raise_exception]);
- }
-}
-
-void BeamModuleAssembler::emit_i_increment(const ArgRegister &Src,
- const ArgWord &Val,
- const ArgRegister &Dst) {
- ArgVal tagged_val = ArgVal(ArgVal::Immediate, make_small(Val.get()));
-
- if (is_sum_small(Src, tagged_val)) {
- Uint shifted_val = Val.get() << _TAG_IMMED1_SIZE;
-
- comment("skipped operand and overflow checks");
- mov_arg(RET, Src);
- if (Support::isInt32(shifted_val)) {
- a.add(RET, imm(shifted_val));
- } else {
- mov_imm(ARG1, shifted_val);
- a.add(RET, ARG1);
- }
- mov_arg(Dst, RET);
-
- return;
- }
-
- Label mixed = a.newLabel(), next = a.newLabel();
-
- /* Place the values in ARG2 and ARG3 to prepare for the mixed call. Note
- * that ARG3 is untagged at this point */
- mov_arg(ARG2, Src);
- mov_imm(ARG3, Val.get() << _TAG_IMMED1_SIZE);
-
- if (always_one_of(Src, BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
- comment("simplified test for small operand since it is a number");
- a.mov(RET, ARG2);
- a.test(RETb, imm(TAG_PRIMARY_LIST));
- a.short_().je(mixed);
- a.add(RET, ARG3);
- a.short_().jno(next);
- } else {
- a.mov(RETd, ARG2d);
- a.and_(RETb, imm(_TAG_IMMED1_MASK));
- a.cmp(RETb, imm(_TAG_IMMED1_SMALL));
- a.short_().jne(mixed);
- a.mov(RET, ARG2);
- a.add(RET, ARG3);
- a.short_().jno(next);
- }
-
- a.bind(mixed);
- safe_fragment_call(ga->get_increment_body_shared());
-
- /* all went well, store result in dst */
- a.bind(next);
- mov_arg(Dst, RET);
-}
-
void BeamGlobalAssembler::emit_plus_body_shared() {
static const ErtsCodeMFA bif_mfa = {am_erlang, am_Plus, 2};
@@ -225,14 +174,35 @@ void BeamModuleAssembler::emit_i_plus(const ArgSource &LHS,
const ArgSource &RHS,
const ArgLabel &Fail,
const ArgRegister &Dst) {
- if (is_sum_small(LHS, RHS)) {
+ bool small_result = is_sum_small_if_args_are_small(LHS, RHS);
+
+ if (always_small(LHS) && always_small(RHS) && small_result) {
+ /* Since we don't need the order on this path (no exceptions), we'll
+ * simplify the code below by shuffling constants to the right-hand
+ * side. */
+ const ArgSource A = LHS.isSmall() ? RHS : LHS,
+ B = LHS.isSmall() ? LHS : RHS;
+
comment("add without overflow check");
- mov_arg(RET, LHS);
- mov_arg(ARG2, RHS);
- a.and_(RET, imm(~_TAG_IMMED1_MASK));
- a.add(RET, ARG2);
- mov_arg(Dst, RET);
+ mov_arg(RET, A);
+
+ if (B.isSmall()) {
+ /* Must be signed for the template magic in isInt32 to work for
+ * negative numbers. */
+ Sint untagged = B.as<ArgSmall>().getSigned() << _TAG_IMMED1_SIZE;
+
+ if (Support::isInt32(untagged)) {
+ a.add(RET, imm(untagged));
+ } else {
+ mov_imm(ARG2, B.as<ArgSmall>().get() & ~_TAG_IMMED1_MASK);
+ a.add(RET, ARG2);
+ }
+ } else {
+ mov_arg(ARG2, B);
+ a.lea(RET, x86::qword_ptr(RET, ARG2, 0, -_TAG_IMMED1_SMALL));
+ }
+ mov_arg(Dst, RET);
return;
}
@@ -242,12 +212,15 @@ void BeamModuleAssembler::emit_i_plus(const ArgSource &LHS,
mov_arg(ARG3, RHS); /* Used by erts_mixed_plus in this slot */
emit_are_both_small(mixed, LHS, ARG2, RHS, ARG3);
- comment("add with overflow check");
a.mov(RET, ARG2);
- a.mov(ARG4, ARG3);
- a.and_(ARG4, imm(~_TAG_IMMED1_MASK));
- a.add(RET, ARG4);
- a.short_().jno(next);
+ a.and_(RET, imm(~_TAG_IMMED1_MASK));
+ a.add(RET, ARG3);
+ if (small_result) {
+ comment("skipped overflow test because the result is always small");
+ a.short_().jmp(next);
+ } else {
+ a.short_().jno(next);
+ }
/* Call mixed addition. */
a.bind(mixed);
@@ -319,14 +292,30 @@ void BeamModuleAssembler::emit_i_minus(const ArgSource &LHS,
const ArgSource &RHS,
const ArgLabel &Fail,
const ArgRegister &Dst) {
- if (is_difference_small(LHS, RHS)) {
+ bool small_result = is_diff_small_if_args_are_small(LHS, RHS);
+
+ if (always_small(LHS) && always_small(RHS) && small_result) {
comment("subtract without overflow check");
mov_arg(RET, LHS);
- mov_arg(ARG2, RHS);
- a.and_(ARG2, imm(~_TAG_IMMED1_MASK));
- a.sub(RET, ARG2);
- mov_arg(Dst, RET);
+ if (RHS.isSmall()) {
+ /* Must be signed for the template magic in isInt32 to work for
+ * negative numbers. */
+ Sint untagged = RHS.as<ArgSmall>().getSigned() << _TAG_IMMED1_SIZE;
+
+ if (Support::isInt32(untagged)) {
+ a.sub(RET, imm(untagged));
+ } else {
+ mov_imm(ARG2, RHS.as<ArgSmall>().get() & ~_TAG_IMMED1_MASK);
+ a.sub(RET, ARG2);
+ }
+ } else {
+ mov_arg(ARG2, RHS);
+ a.and_(ARG2, imm(~_TAG_IMMED1_MASK));
+ a.sub(RET, ARG2);
+ }
+
+ mov_arg(Dst, RET);
return;
}
@@ -337,12 +326,19 @@ void BeamModuleAssembler::emit_i_minus(const ArgSource &LHS,
emit_are_both_small(mixed, LHS, ARG2, RHS, ARG3);
- comment("sub with overflow check");
- a.mov(RET, ARG2);
- a.mov(ARG4, ARG3);
- a.and_(ARG4, imm(~_TAG_IMMED1_MASK));
- a.sub(RET, ARG4);
- a.short_().jno(next);
+ if (small_result) {
+ comment("skipped overflow test because the result is always small");
+ a.mov(RET, ARG2);
+ a.and_(ARG3, imm(~_TAG_IMMED1_MASK));
+ a.sub(RET, ARG3);
+ a.short_().jmp(next);
+ } else {
+ a.mov(RET, ARG2);
+ a.mov(ARG4, ARG3);
+ a.and_(ARG4, imm(~_TAG_IMMED1_MASK));
+ a.sub(RET, ARG4);
+ a.short_().jno(next);
+ }
a.bind(mixed);
if (Fail.get() != 0) {
@@ -408,8 +404,9 @@ void BeamModuleAssembler::emit_i_unary_minus(const ArgSource &Src,
const ArgLabel &Fail,
const ArgRegister &Dst) {
ArgVal zero = ArgVal(ArgVal::Immediate, make_small(0));
+ bool small_result = is_diff_small_if_args_are_small(zero, Src);
- if (is_difference_small(zero, Src)) {
+ if (always_small(Src) && small_result) {
comment("negation without overflow test");
mov_arg(ARG2, Src);
a.mov(RETd, imm(_TAG_IMMED1_SMALL));
@@ -428,13 +425,18 @@ void BeamModuleAssembler::emit_i_unary_minus(const ArgSource &Src,
a.cmp(RETb, imm(_TAG_IMMED1_SMALL));
a.short_().jne(mixed);
- comment("negation with overflow test");
/* RETb is now equal to _TAG_IMMED1_SMALL. */
a.movzx(RET, RETb); /* Set RET to make_small(0). */
a.mov(ARG3, ARG2);
a.and_(ARG3, imm(~_TAG_IMMED1_MASK));
a.sub(RET, ARG3);
- a.short_().jno(next);
+
+ if (small_result) {
+ comment("skipped overflow test because the result is always small");
+ a.short_().jmp(next);
+ } else {
+ a.short_().jno(next);
+ }
a.bind(mixed);
if (Fail.get() != 0) {
@@ -657,11 +659,10 @@ void BeamModuleAssembler::emit_div_rem(const ArgLabel &Fail,
if (always_small(LHS)) {
comment("skipped test for small dividend since it is always small");
need_generic = false;
- } else if (always_one_of(LHS, BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ } else if (always_one_of<BeamTypeId::Number>(LHS)) {
comment("simplified test for small dividend since it is an "
"integer");
- a.mov(ARG2d, x86::eax);
- a.test(ARG2d, imm(TAG_PRIMARY_LIST));
+ a.test(x86::al, imm(TAG_PRIMARY_LIST));
a.short_().je(generic_div);
} else {
comment("testing for a small dividend");
@@ -673,25 +674,45 @@ void BeamModuleAssembler::emit_div_rem(const ArgLabel &Fail,
/* Sign-extend and divide. The result is implicitly placed in
* RAX and the remainder in RDX (ARG3). */
- comment("divide with inlined code");
- a.sar(x86::rax, imm(_TAG_IMMED1_SIZE));
- a.cqo();
- a.idiv(ARG6);
+ if (Support::isPowerOf2(divisor) &&
+ std::get<0>(getClampedRange(LHS)) >= 0) {
+ int trailing_bits = Support::ctz<Eterm>(divisor);
+
+ if (need_rem) {
+ Uint mask = Support::lsbMask<Uint>(trailing_bits +
+ _TAG_IMMED1_SIZE);
+ mask = (1ULL << (trailing_bits + _TAG_IMMED1_SIZE)) - 1;
+ comment("optimized rem by replacing with masking");
+ mov_imm(x86::rdx, mask);
+ a.and_(x86::rdx, x86::rax);
+ }
+ if (need_div) {
+ comment("optimized div by replacing with right shift");
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.shr(x86::rax, imm(trailing_bits));
+ a.or_(x86::rax, imm(_TAG_IMMED1_SMALL));
+ }
+ } else {
+ comment("divide with inlined code");
+ a.sar(x86::rax, imm(_TAG_IMMED1_SIZE));
+ a.cqo();
+ a.idiv(ARG6);
- if (need_div) {
- a.sal(x86::rax, imm(_TAG_IMMED1_SIZE));
- }
+ if (need_div) {
+ a.sal(x86::rax, imm(_TAG_IMMED1_SIZE));
+ }
- if (need_rem) {
- a.sal(x86::rdx, imm(_TAG_IMMED1_SIZE));
- }
+ if (need_rem) {
+ a.sal(x86::rdx, imm(_TAG_IMMED1_SIZE));
+ }
- if (need_div) {
- a.or_(x86::rax, imm(_TAG_IMMED1_SMALL));
- }
+ if (need_div) {
+ a.or_(x86::rax, imm(_TAG_IMMED1_SMALL));
+ }
- if (need_rem) {
- a.or_(x86::rdx, imm(_TAG_IMMED1_SMALL));
+ if (need_rem) {
+ a.or_(x86::rdx, imm(_TAG_IMMED1_SMALL));
+ }
}
if (need_generic) {
@@ -862,15 +883,35 @@ void BeamModuleAssembler::emit_i_times(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS,
const ArgRegister &Dst) {
- if (is_product_small(LHS, RHS)) {
+ bool small_result = is_product_small_if_args_are_small(LHS, RHS);
+
+ if (always_small(LHS) && always_small(RHS) && small_result) {
comment("multiplication without overflow check");
- mov_arg(RET, LHS);
- mov_arg(ARG2, RHS);
- a.and_(RET, imm(~_TAG_IMMED1_MASK));
- a.sar(ARG2, imm(_TAG_IMMED1_SIZE));
- a.imul(RET, ARG2);
+ if (RHS.isSmall()) {
+ Sint factor = RHS.as<ArgSmall>().getSigned();
+
+ mov_arg(RET, LHS);
+ a.and_(RET, imm(~_TAG_IMMED1_MASK));
+ if (Support::isPowerOf2(factor)) {
+ int trailing_bits = Support::ctz<Eterm>(factor);
+ comment("optimized multiplication by replacing with left "
+ "shift");
+ a.shl(RET, imm(trailing_bits));
+ } else {
+ mov_imm(ARG2, factor);
+ a.imul(RET, ARG2);
+ }
+ } else {
+ mov_arg(RET, LHS);
+ mov_arg(ARG2, RHS);
+ a.and_(RET, imm(~_TAG_IMMED1_MASK));
+ a.sar(ARG2, imm(_TAG_IMMED1_SIZE));
+ a.imul(RET, ARG2);
+ }
+
a.or_(RET, imm(_TAG_IMMED1_SMALL));
mov_arg(Dst, RET);
+
return;
}
@@ -882,18 +923,10 @@ void BeamModuleAssembler::emit_i_times(const ArgLabel &Fail,
if (RHS.isSmall()) {
Sint val = RHS.as<ArgSmall>().getSigned();
emit_is_small(mixed, LHS, ARG2);
- comment("mul with overflow check, imm RHS");
a.mov(RET, ARG2);
a.mov(ARG4, imm(val));
- } else if (LHS.isSmall()) {
- Sint val = LHS.as<ArgSmall>().getSigned();
- emit_is_small(mixed, RHS, ARG3);
- comment("mul with overflow check, imm LHS");
- a.mov(RET, ARG3);
- a.mov(ARG4, imm(val));
} else {
emit_are_both_small(mixed, LHS, ARG2, RHS, ARG3);
- comment("mul with overflow check");
a.mov(RET, ARG2);
a.mov(ARG4, ARG3);
a.sar(ARG4, imm(_TAG_IMMED1_SIZE));
@@ -901,7 +934,11 @@ void BeamModuleAssembler::emit_i_times(const ArgLabel &Fail,
a.and_(RET, imm(~_TAG_IMMED1_MASK));
a.imul(RET, ARG4);
- a.short_().jo(mixed);
+ if (small_result) {
+ comment("skipped overflow check because the result is always small");
+ } else {
+ a.short_().jo(mixed);
+ }
a.or_(RET, imm(_TAG_IMMED1_SMALL));
a.short_().jmp(next);
@@ -993,23 +1030,28 @@ void BeamModuleAssembler::emit_i_band(const ArgSource &LHS,
const ArgSource &RHS,
const ArgLabel &Fail,
const ArgRegister &Dst) {
- mov_arg(ARG2, LHS);
- mov_arg(RET, RHS);
-
if (always_small(LHS) && always_small(RHS)) {
comment("skipped test for small operands since they are always small");
- a.and_(RET, ARG2);
+ mov_arg(RET, LHS);
+ if (RHS.isSmall() && Support::isInt32(RHS.as<ArgSmall>().get())) {
+ a.and_(RETd, imm(RHS.as<ArgSmall>().get()));
+ } else if (RHS.isSmall() &&
+ Support::isInt32((Sint)RHS.as<ArgSmall>().get())) {
+ a.and_(RET, imm(RHS.as<ArgSmall>().get()));
+ } else {
+ mov_arg(ARG2, RHS);
+ a.and_(RET, ARG2);
+ }
mov_arg(Dst, RET);
return;
}
+ mov_arg(ARG2, LHS);
+ mov_arg(RET, RHS);
+
Label generic = a.newLabel(), next = a.newLabel();
- if (always_small(RHS)) {
- emit_is_small(generic, LHS, ARG2);
- } else {
- emit_are_both_small(generic, LHS, ARG2, RHS, RET);
- }
+ emit_are_both_small(generic, LHS, ARG2, RHS, RET);
/* TAG & TAG = TAG, so we don't need to tag it again. */
a.and_(RET, ARG2);
@@ -1048,23 +1090,25 @@ void BeamModuleAssembler::emit_i_bor(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS,
const ArgRegister &Dst) {
- mov_arg(ARG2, LHS);
- mov_arg(RET, RHS);
-
if (always_small(LHS) && always_small(RHS)) {
comment("skipped test for small operands since they are always small");
- a.or_(RET, ARG2);
+ mov_arg(RET, LHS);
+ if (RHS.isImmed() && Support::isInt32((Sint)RHS.as<ArgSmall>().get())) {
+ a.or_(RET, imm(RHS.as<ArgSmall>().get()));
+ } else {
+ mov_arg(ARG2, RHS);
+ a.or_(RET, ARG2);
+ }
mov_arg(Dst, RET);
return;
}
+ mov_arg(ARG2, LHS);
+ mov_arg(RET, RHS);
+
Label generic = a.newLabel(), next = a.newLabel();
- if (always_small(RHS)) {
- emit_is_small(generic, LHS, ARG2);
- } else {
- emit_are_both_small(generic, LHS, ARG2, RHS, RET);
- }
+ emit_are_both_small(generic, LHS, ARG2, RHS, RET);
/* TAG | TAG = TAG, so we don't need to tag it again. */
a.or_(RET, ARG2);
@@ -1103,25 +1147,27 @@ void BeamModuleAssembler::emit_i_bxor(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS,
const ArgRegister &Dst) {
- mov_arg(ARG2, LHS);
- mov_arg(RET, RHS);
-
if (always_small(LHS) && always_small(RHS)) {
comment("skipped test for small operands since they are always small");
- /* TAG ^ TAG = 0, so we need to tag it again. */
- a.xor_(RET, ARG2);
- a.or_(RET, imm(_TAG_IMMED1_SMALL));
+ mov_arg(RET, LHS);
+ if (RHS.isImmed() && Support::isInt32((Sint)RHS.as<ArgSmall>().get())) {
+ a.xor_(RET, imm(RHS.as<ArgSmall>().get() & ~_TAG_IMMED1_SMALL));
+ } else {
+ /* TAG ^ TAG = 0, so we need to tag it again. */
+ mov_arg(ARG2, RHS);
+ a.xor_(RET, ARG2);
+ a.or_(RET, imm(_TAG_IMMED1_SMALL));
+ }
mov_arg(Dst, RET);
return;
}
+ mov_arg(ARG2, LHS);
+ mov_arg(RET, RHS);
+
Label generic = a.newLabel(), next = a.newLabel();
- if (always_small(RHS)) {
- emit_is_small(generic, LHS, ARG2);
- } else {
- emit_are_both_small(generic, LHS, ARG2, RHS, RET);
- }
+ emit_are_both_small(generic, LHS, ARG2, RHS, RET);
/* TAG ^ TAG = 0, so we need to tag it again. */
a.xor_(RET, ARG2);
@@ -1216,7 +1262,7 @@ void BeamModuleAssembler::emit_i_bnot(const ArgLabel &Fail,
/* Fall through to the generic path if the result is not a small, where the
* above operation will be reverted. */
- if (always_one_of(Src, BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ if (always_one_of<BeamTypeId::Number>(Src)) {
comment("simplified test for small operand since it is a number");
a.test(RETb, imm(TAG_PRIMARY_LIST));
a.short_().jne(next);
@@ -1341,7 +1387,13 @@ void BeamModuleAssembler::emit_i_bsl(const ArgSource &LHS,
mov_arg(RET, LHS);
ERTS_CT_ASSERT(_TAG_IMMED1_MASK == _TAG_IMMED1_SMALL);
a.xor_(RET, imm(_TAG_IMMED1_MASK));
- a.sal(RET, imm(RHS.as<ArgSmall>().getSigned()));
+ if (RHS.isSmall()) {
+ a.shl(RET, imm(RHS.as<ArgSmall>().getSigned()));
+ } else {
+ mov_arg(x86::rcx, RHS);
+ a.shr(x86::rcx, imm(_TAG_IMMED1_SIZE));
+ a.shl(RET, x86::cl);
+ }
a.or_(RET, imm(_TAG_IMMED1_SMALL));
mov_arg(Dst, RET);
return;
diff --git a/erts/emulator/beam/jit/x86/instr_bif.cpp b/erts/emulator/beam/jit/x86/instr_bif.cpp
index 4c2d4f007e..46a514fd34 100644
--- a/erts/emulator/beam/jit/x86/instr_bif.cpp
+++ b/erts/emulator/beam/jit/x86/instr_bif.cpp
@@ -99,7 +99,7 @@ void BeamGlobalAssembler::emit_i_bif_body_shared() {
}
void BeamModuleAssembler::emit_setup_guard_bif(const std::vector<ArgVal> &args,
- const ArgWord &bif) {
+ const ArgWord &Bif) {
bool is_contiguous_mem = false;
ASSERT(args.size() > 0 && args.size() <= 3);
@@ -125,7 +125,13 @@ void BeamModuleAssembler::emit_setup_guard_bif(const std::vector<ArgVal> &args,
}
}
- mov_arg(ARG4, bif);
+ if (logger.file()) {
+ ErtsCodeMFA *mfa = ubif2mfa((void *)Bif.get());
+ if (mfa) {
+ comment("UBIF: %T/%d", mfa->function, mfa->arity);
+ }
+ }
+ mov_arg(ARG4, Bif);
}
void BeamModuleAssembler::emit_i_bif1(const ArgSource &Src1,
@@ -626,6 +632,10 @@ void BeamModuleAssembler::emit_call_light_bif(const ArgWord &Bif,
a.mov(RET, imm(Bif.get()));
a.lea(ARG3, x86::qword_ptr(entry));
+ if (logger.file()) {
+ BeamFile_ImportEntry *e = &beam->imports.entries[Exp.get()];
+ comment("BIF: %T:%T/%d", e->module, e->function, e->arity);
+ }
fragment_call(ga->get_call_light_bif_shared());
}
@@ -842,6 +852,10 @@ void BeamModuleAssembler::emit_call_bif_mfa(const ArgAtom &M,
e = erts_active_export_entry(M.get(), F.get(), A.get());
ASSERT(e != NULL && e->bif_number != -1);
+ comment("HBIF: %T:%T/%d",
+ e->info.mfa.module,
+ e->info.mfa.function,
+ A.get());
func = (BeamInstr)bif_table[e->bif_number].f;
emit_call_bif(ArgWord(func));
}
@@ -1007,14 +1021,14 @@ void BeamGlobalAssembler::emit_i_load_nif_shared() {
a.mov(TMP_MEM1q, ARG2);
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapAlloc>();
a.mov(ARG1, c_p);
/* ARG2 has already been set by caller */
load_x_reg_array(ARG3);
runtime_call<3>(beam_jit_load_nif);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapAlloc>();
a.cmp(RET, RET_NIF_yield);
a.short_().je(yield);
@@ -1114,14 +1128,14 @@ void BeamModuleAssembler::emit_i_load_nif() {
align_erlang_cp();
a.bind(entry);
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapAlloc>();
a.mov(ARG1, c_p);
a.lea(ARG2, x86::qword_ptr(current_label));
load_x_reg_array(ARG3);
runtime_call<3>(beam_jit_load_nif);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapAlloc>();
a.cmp(RET, imm(RET_NIF_yield));
a.je(schedule);
diff --git a/erts/emulator/beam/jit/x86/instr_bs.cpp b/erts/emulator/beam/jit/x86/instr_bs.cpp
index ab6abff6cc..36e95df57c 100644
--- a/erts/emulator/beam/jit/x86/instr_bs.cpp
+++ b/erts/emulator/beam/jit/x86/instr_bs.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
*/
#include "beam_asm.hpp"
+#include <numeric>
extern "C"
{
@@ -57,12 +58,22 @@ int BeamModuleAssembler::emit_bs_get_field_size(const ArgSource &Size,
a.jmp(fail);
return -1;
} else {
+ bool can_fail = true;
+
mov_arg(RET, Size);
- a.mov(ARG3d, RETd);
- a.and_(ARG3d, imm(_TAG_IMMED1_MASK));
- a.cmp(ARG3d, imm(_TAG_IMMED1_SMALL));
- a.jne(fail);
+ if (always_small(Size)) {
+ auto [min, max] = getClampedRange(Size);
+ can_fail =
+ !(0 <= min && (max >> (SMALL_BITS - ERL_UNIT_BITS)) == 0);
+ comment("simplified segment size checks because "
+ "the types are known");
+ } else {
+ a.mov(ARG3d, RETd);
+ a.and_(ARG3d, imm(_TAG_IMMED1_MASK));
+ a.cmp(ARG3d, imm(_TAG_IMMED1_SMALL));
+ a.jne(fail);
+ }
if (max_size) {
ASSERT(Support::isInt32((Sint)make_small(max_size)));
@@ -70,19 +81,35 @@ int BeamModuleAssembler::emit_bs_get_field_size(const ArgSource &Size,
a.ja(fail);
}
- if (unit == 1) {
+ if (unit == 0) {
+ mov_imm(RET, 0);
+ } else if (unit == 1) {
a.sar(RET, imm(_TAG_IMMED1_SIZE));
- a.js(fail);
+ if (can_fail) {
+ a.js(fail);
+ }
+ } else if (!can_fail && Support::isPowerOf2(unit)) {
+ int trailing_bits = Support::ctz<Eterm>(unit);
+ a.and_(RET, imm(~_TAG_IMMED1_MASK));
+ if (trailing_bits < _TAG_IMMED1_SIZE) {
+ a.sar(RET, imm(_TAG_IMMED1_SIZE - trailing_bits));
+ } else if (trailing_bits > _TAG_IMMED1_SIZE) {
+ a.shl(RET, imm(trailing_bits - _TAG_IMMED1_SIZE));
+ }
} else {
/* Untag the size but don't shift it just yet, we want to fail on
* overflow if the final result doesn't fit into a small. */
a.and_(RET, imm(~_TAG_IMMED1_MASK));
- a.js(fail);
+ if (can_fail) {
+ a.js(fail);
+ }
/* Size = (Size) * (Unit) */
mov_imm(ARG3, unit);
a.mul(ARG3); /* CLOBBERS ARG3! */
- a.jo(fail);
+ if (can_fail) {
+ a.jo(fail);
+ }
a.sar(RET, imm(_TAG_IMMED1_SIZE));
}
@@ -103,7 +130,7 @@ void BeamModuleAssembler::emit_i_bs_init_heap(const ArgWord &Size,
mov_arg(ARG5, Heap);
mov_arg(ARG6, Live);
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
/* Must be last since mov_arg() may clobber ARG1 */
a.mov(ARG1, c_p);
@@ -111,7 +138,7 @@ void BeamModuleAssembler::emit_i_bs_init_heap(const ArgWord &Size,
load_erl_bits_state(ARG3);
runtime_call<6>(beam_jit_bs_init);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
mov_arg(Dst, RET);
}
@@ -143,16 +170,14 @@ void BeamModuleAssembler::emit_i_bs_init_fail_heap(const ArgSource &Size,
mov_arg(ARG5, Heap);
mov_arg(ARG6, Live);
- emit_enter_runtime<Update::eReductions | Update::eStack |
- Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
load_erl_bits_state(ARG3);
runtime_call<6>(beam_jit_bs_init);
- emit_leave_runtime<Update::eReductions | Update::eStack |
- Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
mov_arg(Dst, RET);
}
@@ -204,7 +229,7 @@ void BeamModuleAssembler::emit_i_bs_init_bits_heap(const ArgWord &NumBits,
mov_arg(ARG5, Alloc);
mov_arg(ARG6, Live);
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
/* Must be last since mov_arg() may clobber ARG1 */
a.mov(ARG1, c_p);
@@ -212,7 +237,7 @@ void BeamModuleAssembler::emit_i_bs_init_bits_heap(const ArgWord &NumBits,
load_erl_bits_state(ARG3);
runtime_call<6>(beam_jit_bs_init_bits);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
mov_arg(Dst, RET);
}
@@ -245,8 +270,7 @@ void BeamModuleAssembler::emit_i_bs_init_bits_fail_heap(
mov_arg(ARG5, Alloc);
mov_arg(ARG6, Live);
- emit_enter_runtime<Update::eReductions | Update::eStack |
- Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
/* Must be last since mov_arg() may clobber ARG1 */
a.mov(ARG1, c_p);
@@ -254,8 +278,7 @@ void BeamModuleAssembler::emit_i_bs_init_bits_fail_heap(
load_erl_bits_state(ARG3);
runtime_call<6>(beam_jit_bs_init_bits);
- emit_leave_runtime<Update::eReductions | Update::eStack |
- Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
mov_arg(Dst, RET);
}
@@ -576,17 +599,18 @@ void BeamModuleAssembler::emit_i_bs_start_match3(const ArgRegister &Src,
a.bind(is_binary);
{
- /* Src is not guaranteed to be inside the live range, so we need to
- * stash it during GC. */
- emit_gc_test_preserve(ArgWord(ERL_BIN_MATCHSTATE_SIZE(0)), Live, ARG2);
+ emit_gc_test_preserve(ArgWord(ERL_BIN_MATCHSTATE_SIZE(0)),
+ Live,
+ Src,
+ ARG2);
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapOnlyAlloc>();
a.mov(ARG1, c_p);
/* ARG2 was set above */
runtime_call<2>(erts_bs_start_match_3);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapOnlyAlloc>();
a.lea(ARG2, x86::qword_ptr(RET, TAG_PRIMARY_BOXED));
}
@@ -650,278 +674,92 @@ void BeamModuleAssembler::emit_i_bs_get_position(const ArgRegister &Ctx,
mov_arg(Dst, ARG1);
}
-/* ARG3 = flags | (size << 3),
- * ARG4 = tagged match context */
-void BeamGlobalAssembler::emit_bs_fixed_integer_shared() {
- emit_enter_runtime<Update::eStack | Update::eHeap>();
-
- a.mov(ARG1, c_p);
- /* Unpack size ... */
- a.mov(ARG2, ARG3);
- a.shr(ARG2, imm(3));
- /* ... flags. */
- a.and_(ARG3, imm(BSF_ALIGNED | BSF_LITTLE | BSF_SIGNED));
- a.lea(ARG4, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb)));
- runtime_call<4>(erts_bs_get_integer_2);
-
- emit_leave_runtime<Update::eStack | Update::eHeap>();
-
- a.ret();
-}
-
-x86::Mem BeamModuleAssembler::emit_bs_get_integer_prologue(Label next,
- Label fail,
- int flags,
- int size) {
- Label aligned = a.newLabel();
-
- a.mov(ARG2, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb.offset)));
- a.lea(ARG3, x86::qword_ptr(ARG2, size));
- a.cmp(ARG3, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb.size)));
- a.ja(fail);
-
- a.test(ARG2.r8(), imm(CHAR_BIT - 1));
- a.short_().je(aligned);
-
- /* Actually unaligned reads are quite rare, so we handle everything in a
- * shared fragment. */
- mov_imm(ARG3, flags | (size << 3));
- safe_fragment_call(ga->get_bs_fixed_integer_shared());
-
- /* The above call can't fail since we work on small numbers and
- * bounds-tested above. */
-#ifdef JIT_HARD_DEBUG
- a.jmp(next);
-#else
- a.short_().jmp(next);
-#endif
-
- a.bind(aligned);
- {
- /* Read base address and convert offset to bytes. */
- a.mov(ARG1, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb.base)));
- a.shr(ARG2, imm(3));
-
- /* We cannot fail from here on; bump the match context's position. */
- a.mov(emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb.offset)),
- ARG3);
-
- return x86::Mem(ARG1, ARG2, 0, 0, size / 8);
- }
-}
-
-void BeamModuleAssembler::emit_i_bs_get_integer_8(const ArgRegister &Ctx,
- const ArgWord &Flags,
- const ArgLabel &Fail,
- const ArgRegister &Dst) {
- int flags = Flags.get();
- Label next = a.newLabel();
- x86::Mem address;
-
- mov_arg(ARG4, Ctx);
-
- address = emit_bs_get_integer_prologue(next,
- resolve_beam_label(Fail),
- flags,
- 8);
-
- if (flags & BSF_SIGNED) {
- a.movsx(RET, address);
- } else {
- a.movzx(RET, address);
- }
-
- a.shl(RET, imm(_TAG_IMMED1_SIZE));
- a.or_(RET, imm(_TAG_IMMED1_SMALL));
-
- a.bind(next);
- mov_arg(Dst, RET);
-}
-
-void BeamModuleAssembler::emit_i_bs_get_integer_16(const ArgRegister &Ctx,
- const ArgWord &Flags,
- const ArgLabel &Fail,
- const ArgRegister &Dst) {
- int flags = Flags.get();
- Label next = a.newLabel();
- x86::Mem address;
-
- mov_arg(ARG4, Ctx);
-
- address = emit_bs_get_integer_prologue(next,
- resolve_beam_label(Fail),
- flags,
- 16);
-
- if (flags & BSF_LITTLE) {
- if (flags & BSF_SIGNED) {
- a.movsx(RET, address);
- } else {
- a.movzx(RET, address);
- }
- } else {
- if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
- a.movbe(x86::ax, address);
- } else {
- a.mov(x86::ax, address);
- a.xchg(x86::al, x86::ah);
- }
+void BeamModuleAssembler::emit_bs_get_integer2(const ArgLabel &Fail,
+ const ArgRegister &Ctx,
+ const ArgWord &Live,
+ const ArgSource &Sz,
+ const ArgWord &Unit,
+ const ArgWord &Flags,
+ const ArgRegister &Dst) {
+ Uint size;
+ Uint flags = Flags.get();
- if (flags & BSF_SIGNED) {
- a.movsx(RET, x86::ax);
- } else {
- a.movzx(RET, x86::ax);
- }
+ if (flags & BSF_NATIVE) {
+ flags &= ~BSF_NATIVE;
+ flags |= BSF_LITTLE;
}
- a.shl(RET, imm(_TAG_IMMED1_SIZE));
- a.or_(RET, imm(_TAG_IMMED1_SMALL));
-
- a.bind(next);
- mov_arg(Dst, RET);
-}
-
-void BeamModuleAssembler::emit_i_bs_get_integer_32(const ArgRegister &Ctx,
- const ArgWord &Flags,
- const ArgLabel &Fail,
- const ArgRegister &Dst) {
- int flags = Flags.get();
- Label next = a.newLabel();
- x86::Mem address;
-
- mov_arg(ARG4, Ctx);
-
- address = emit_bs_get_integer_prologue(next,
- resolve_beam_label(Fail),
- flags,
- 32);
-
- if (flags & BSF_LITTLE) {
- if (flags & BSF_SIGNED) {
- a.movsxd(RET, address);
- } else {
- /* Implicitly zero-extends to 64 bits */
- a.mov(RETd, address);
- }
+ if (Sz.isSmall() && Sz.as<ArgSmall>().getUnsigned() < 8 * sizeof(Uint) &&
+ (size = Sz.as<ArgSmall>().getUnsigned() * Unit.get()) <
+ 8 * sizeof(Uint)) {
+ /* Segment of a fixed size supported by bs_match. */
+ const ArgVal match[] = {ArgAtom(am_ensure_at_least),
+ ArgWord(size),
+ ArgWord(1),
+ ArgAtom(am_integer),
+ Live,
+ ArgWord(flags),
+ ArgWord(size),
+ ArgWord(1),
+ Dst};
+
+ const Span<ArgVal> args(match, sizeof(match) / sizeof(match[0]));
+ emit_i_bs_match(Fail, Ctx, args);
} else {
- if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
- a.movbe(RETd, address);
- } else {
- a.mov(RETd, address);
- a.bswap(RETd);
- }
-
- if (flags & BSF_SIGNED) {
- a.movsxd(RET, RETd);
- }
- }
-
- a.shl(RET, imm(_TAG_IMMED1_SIZE));
- a.or_(RET, imm(_TAG_IMMED1_SMALL));
-
- a.bind(next);
- mov_arg(Dst, RET);
-}
+ Label fail = resolve_beam_label(Fail);
+ int unit = Unit.get();
+
+ /* Clobbers RET + ARG3, returns a negative result if we always
+ * fail and further work is redundant. */
+ if (emit_bs_get_field_size(Sz, unit, fail, ARG5) >= 0) {
+ /* This operation can be expensive if a bignum can be
+ * created because there can be a garbage collection. */
+ auto max = std::get<1>(getClampedRange(Sz));
+ bool potentially_expensive =
+ max >= SMALL_BITS || (max * Unit.get()) >= SMALL_BITS;
+
+ mov_arg(ARG3, Ctx);
+ mov_imm(ARG4, flags);
+ if (potentially_expensive) {
+ mov_arg(ARG6, Live);
+ } else {
+#ifdef DEBUG
+ /* Never actually used. */
+ mov_imm(ARG6, 1023);
+#endif
+ }
-void BeamModuleAssembler::emit_i_bs_get_integer_64(const ArgRegister &Ctx,
- const ArgWord &Flags,
- const ArgLabel &Fail,
- const ArgWord &Live,
- const ArgRegister &Dst) {
- int flags = Flags.get();
- Label next = a.newLabel();
- x86::Mem address;
+ if (potentially_expensive) {
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
+ } else {
+ comment("simplified entering runtime because result is always "
+ "small");
+ emit_enter_runtime();
+ }
- mov_arg(ARG4, Ctx);
+ a.mov(ARG1, c_p);
+ if (potentially_expensive) {
+ load_x_reg_array(ARG2);
+ } else {
+#ifdef DEBUG
+ /* Never actually used. */
+ mov_imm(ARG2, 0);
+#endif
+ }
+ runtime_call<6>(beam_jit_bs_get_integer);
- /* Ctx is not guaranteed to be inside the live range, so we need to stash
- * it during GC. */
- emit_gc_test_preserve(ArgWord(BIG_UINT_HEAP_SIZE), Live, ARG4);
+ if (potentially_expensive) {
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
+ } else {
+ emit_leave_runtime();
+ }
- address = emit_bs_get_integer_prologue(next,
- resolve_beam_label(Fail),
- flags,
- 64);
+ emit_test_the_non_value(RET);
+ a.je(fail);
- if (flags & BSF_LITTLE) {
- a.mov(RET, address);
- } else {
- if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
- a.movbe(RET, address);
- } else {
- a.mov(RET, address);
- a.bswap(RET);
+ mov_arg(Dst, RET);
}
}
-
- a.mov(ARG1, RET);
- a.mov(ARG2, RET);
-
- /* Speculatively make a small out of the result even though it might not
- * be one, and jump to the next instruction if it is. */
- a.shl(RET, imm(_TAG_IMMED1_SIZE));
- a.or_(RET, imm(_TAG_IMMED1_SMALL));
-
- if (flags & BSF_SIGNED) {
- a.sar(ARG2, imm(SMALL_BITS - 1));
- a.add(ARG2, imm(1));
- a.cmp(ARG2, imm(1));
- a.jbe(next);
- } else {
- a.shr(ARG2, imm(SMALL_BITS - 1));
- a.jz(next);
- }
-
- emit_enter_runtime();
-
- a.mov(ARG2, HTOP);
- if (flags & BSF_SIGNED) {
- runtime_call<2>(small_to_big);
- } else {
- runtime_call<2>(uword_to_big);
- }
- a.add(HTOP, imm(sizeof(Eterm) * BIG_UINT_HEAP_SIZE));
-
- emit_leave_runtime();
-
- a.bind(next);
- mov_arg(Dst, RET);
-}
-
-void BeamModuleAssembler::emit_i_bs_get_integer(const ArgRegister &Ctx,
- const ArgLabel &Fail,
- const ArgWord &Live,
- const ArgWord &FlagsAndUnit,
- const ArgSource &Sz,
- const ArgRegister &Dst) {
- Label fail;
- int unit;
-
- fail = resolve_beam_label(Fail);
- unit = FlagsAndUnit.get() >> 3;
-
- /* Clobbers RET + ARG3, returns a negative result if we always fail and
- * further work is redundant. */
- if (emit_bs_get_field_size(Sz, unit, fail, ARG5) >= 0) {
- mov_arg(ARG3, Ctx);
- mov_arg(ARG4, FlagsAndUnit);
- mov_arg(ARG6, Live);
-
- emit_enter_runtime<Update::eReductions | Update::eStack |
- Update::eHeap>();
-
- a.mov(ARG1, c_p);
- load_x_reg_array(ARG2);
- runtime_call<6>(beam_jit_bs_get_integer);
-
- emit_leave_runtime<Update::eReductions | Update::eStack |
- Update::eHeap>();
-
- emit_test_the_non_value(RET);
- a.je(fail);
-
- mov_arg(Dst, RET);
- }
}
void BeamModuleAssembler::emit_bs_test_tail2(const ArgLabel &Fail,
@@ -962,9 +800,7 @@ void BeamModuleAssembler::emit_i_bs_get_binary_all2(const ArgRegister &Ctx,
mov_arg(ARG1, Ctx);
- /* Ctx is not guaranteed to be inside the live range, so we need to stash
- * it during GC. */
- emit_gc_test_preserve(ArgWord(EXTRACT_SUB_BIN_HEAP_NEED), Live, ARG1);
+ emit_gc_test_preserve(ArgWord(EXTRACT_SUB_BIN_HEAP_NEED), Live, Ctx, ARG1);
a.mov(RET, emit_boxed_val(ARG1, offsetof(ErlBinMatchState, mb.size)));
a.sub(RET, emit_boxed_val(ARG1, offsetof(ErlBinMatchState, mb.offset)));
@@ -981,19 +817,19 @@ void BeamModuleAssembler::emit_i_bs_get_binary_all2(const ArgRegister &Ctx,
a.jne(resolve_beam_label(Fail));
- emit_enter_runtime<Update::eHeap>();
+ emit_enter_runtime<Update::eHeapOnlyAlloc>();
a.lea(ARG2, emit_boxed_val(ARG1, offsetof(ErlBinMatchState, mb)));
a.mov(ARG1, c_p);
runtime_call<2>(erts_bs_get_binary_all_2);
- emit_leave_runtime<Update::eHeap>();
+ emit_leave_runtime<Update::eHeapOnlyAlloc>();
mov_arg(Dst, RET);
}
void BeamGlobalAssembler::emit_bs_get_tail_shared() {
- emit_enter_runtime<Update::eHeap>();
+ emit_enter_runtime<Update::eHeapOnlyAlloc>();
a.mov(ARG2, emit_boxed_val(ARG1, offsetof(ErlBinMatchState, mb.orig)));
a.mov(ARG3, emit_boxed_val(ARG1, offsetof(ErlBinMatchState, mb.base)));
@@ -1006,7 +842,7 @@ void BeamGlobalAssembler::emit_bs_get_tail_shared() {
a.lea(ARG1, x86::qword_ptr(c_p, offsetof(Process, htop)));
runtime_call<5>(erts_extract_sub_binary);
- emit_leave_runtime<Update::eHeap>();
+ emit_leave_runtime<Update::eHeapOnlyAlloc>();
a.ret();
}
@@ -1016,9 +852,7 @@ void BeamModuleAssembler::emit_bs_get_tail(const ArgRegister &Ctx,
const ArgWord &Live) {
mov_arg(ARG1, Ctx);
- /* Ctx is not guaranteed to be inside the live range, so we need to stash
- * it during GC. */
- emit_gc_test_preserve(ArgWord(EXTRACT_SUB_BIN_HEAP_NEED), Live, ARG1);
+ emit_gc_test_preserve(ArgWord(EXTRACT_SUB_BIN_HEAP_NEED), Live, Ctx, ARG1);
safe_fragment_call(ga->get_bs_get_tail_shared());
@@ -1044,7 +878,6 @@ void BeamModuleAssembler::emit_i_bs_skip_bits2(const ArgRegister &Ctx,
Label fail;
fail = resolve_beam_label(Fail);
-
if (emit_bs_get_field_size(Bits, Unit.get(), fail, RET) >= 0) {
emit_bs_skip_bits(Fail, Ctx);
}
@@ -1076,11 +909,12 @@ void BeamModuleAssembler::emit_i_bs_get_binary2(const ArgRegister &Ctx,
mov_arg(ARG4, Ctx);
- /* Ctx is not guaranteed to be inside the live range, so we need to
- * stash it during GC. */
- emit_gc_test_preserve(ArgWord(EXTRACT_SUB_BIN_HEAP_NEED), Live, ARG4);
+ emit_gc_test_preserve(ArgWord(EXTRACT_SUB_BIN_HEAP_NEED),
+ Live,
+ Ctx,
+ ARG4);
- emit_enter_runtime<Update::eHeap>();
+ emit_enter_runtime<Update::eHeapOnlyAlloc>();
a.mov(ARG1, c_p);
a.mov(ARG2, TMP_MEM1q);
@@ -1088,7 +922,7 @@ void BeamModuleAssembler::emit_i_bs_get_binary2(const ArgRegister &Ctx,
a.lea(ARG4, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb)));
runtime_call<4>(erts_bs_get_binary_2);
- emit_leave_runtime<Update::eHeap>();
+ emit_leave_runtime<Update::eHeapOnlyAlloc>();
emit_test_the_non_value(RET);
a.je(fail);
@@ -1111,19 +945,17 @@ void BeamModuleAssembler::emit_i_bs_get_float2(const ArgRegister &Ctx,
mov_arg(ARG4, Ctx);
- /* Ctx is not guaranteed to be inside the live range, so we need to stash
- * it during GC. */
- emit_gc_test_preserve(ArgWord(FLOAT_SIZE_OBJECT), Live, ARG4);
+ emit_gc_test_preserve(ArgWord(FLOAT_SIZE_OBJECT), Live, Ctx, ARG4);
if (emit_bs_get_field_size(Sz, unit, fail, ARG2, 64) >= 0) {
- emit_enter_runtime<Update::eHeap>();
+ emit_enter_runtime<Update::eHeapOnlyAlloc>();
a.mov(ARG1, c_p);
mov_imm(ARG3, Flags.get());
a.lea(ARG4, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb)));
runtime_call<4>(erts_bs_get_float_2);
- emit_leave_runtime<Update::eHeap>();
+ emit_leave_runtime<Update::eHeapOnlyAlloc>();
emit_test_the_non_value(RET);
a.je(fail);
@@ -1181,19 +1013,385 @@ void BeamModuleAssembler::emit_i_bs_put_utf8(const ArgLabel &Fail,
}
}
+/*
+ * ARG1 = pointer to match state
+ * ARG2 = position in binary in bits
+ * ARG3 = base pointer to binary data
+ * RET = number of bits left in binary
+ *
+ * This fragment is called if the binary is unaligned and/or the number
+ * of remaining bits is less than 32.
+ *
+ * See the comment for emit_bs_get_utf8_shared() for details about the
+ * return value.
+ */
+void BeamGlobalAssembler::emit_bs_get_utf8_short_shared() {
+ const int position_offset = offsetof(ErlBinMatchBuffer, offset);
+
+ const x86::Gp ctx = ARG1;
+ const x86::Gp bin_position = ARG2;
+ const x86::Gp bin_base = ARG3;
+
+ Label at_least_one = a.newLabel();
+ Label two = a.newLabel();
+ Label three_or_more = a.newLabel();
+ Label four = a.newLabel();
+ Label five = a.newLabel();
+ Label read_done = a.newLabel();
+ Label no_masking = a.newLabel();
+ Label ascii = a.newLabel();
+
+ /* Calculate the number of bytes remaining in the binary and error
+ * out if less than one. */
+ a.shr(RET, imm(3));
+ a.test(RET, RET);
+ a.short_().jne(at_least_one);
+
+ /* ZF is is already set. */
+ a.ret();
+
+ a.bind(at_least_one);
+
+ /* Save number of bytes remaining in binary. */
+ a.mov(ARG5, RET);
+
+ /* If the position in the binary is not byte-aligned, we'll need
+ * to read one more byte. */
+ a.test(bin_position, imm(7));
+ a.setne(ARG4.r8());
+ a.movzx(ARG4d, ARG4.r8());
+ a.add(RET, ARG4);
+
+ /* Save original position in bits and set up byte offset for
+ * reading. */
+ a.push(bin_position);
+ a.shr(bin_position, imm(3));
+
+ a.cmp(RET, imm(2));
+ a.short_().je(two);
+ a.short_().ja(three_or_more);
+
+ /* Read one byte (always byte-aligned). */
+ a.mov(RETb, x86::byte_ptr(bin_base, bin_position));
+ a.movzx(RETd, RETb);
+ a.short_().jmp(read_done);
+
+ /* Read two bytes. */
+ a.bind(two);
+ a.mov(RET.r16(), x86::word_ptr(bin_base, bin_position));
+ a.movzx(RETd, RET.r16());
+ a.short_().jmp(read_done);
+
+ a.bind(three_or_more);
+ a.cmp(RET, imm(4));
+ a.short_().je(four);
+ a.short_().ja(five);
+
+ /* Read three bytes. */
+ a.mov(RET.r8(), x86::byte_ptr(bin_base, bin_position, 0, 2));
+ a.movzx(RETd, RETb);
+ a.shl(RETd, imm(16));
+ a.mov(RET.r16(), x86::word_ptr(bin_base, bin_position));
+ a.short_().jmp(read_done);
+
+ /* Read four bytes (always unaligned). */
+ a.bind(four);
+ a.mov(RETd, x86::dword_ptr(bin_base, bin_position));
+ a.short_().jmp(read_done);
+
+ /* Read five bytes (always unaligned). */
+ a.bind(five);
+ a.mov(RETd, x86::dword_ptr(bin_base, bin_position));
+ a.mov(ARG4.r8(), x86::byte_ptr(bin_base, bin_position, 0, 4));
+ a.movzx(ARG4d, ARG4.r8());
+ a.shl(ARG4, imm(32));
+ a.or_(RET, ARG4);
+
+ /* Handle the bytes read. */
+ a.bind(read_done);
+ a.pop(bin_position);
+ a.bswap(RET);
+
+ if (x86::rcx == ctx) {
+ a.push(x86::rcx);
+ }
+ a.mov(x86::ecx, bin_position.r32());
+ a.and_(x86::cl, imm(7));
+ a.shl(RET, x86::cl);
+
+ /* Check whether we will need to clear out trailing
+ * garbage not part of the binary. */
+ a.mov(x86::cl, 64);
+ a.cmp(ARG5, imm(3));
+ a.short_().ja(no_masking);
+
+ /* Calculate a byte mask and zero out trailing garbage. */
+ a.shl(ARG5d, imm(3));
+ a.sub(x86::cl, ARG5.r8());
+ mov_imm(ARG5, -1);
+ a.shl(ARG5, x86::cl);
+ a.and_(RET, ARG5);
+
+ a.bind(no_masking);
+ if (x86::rcx == ctx) {
+ a.pop(x86::rcx);
+ }
+
+ /* `test rax, rax` is a shorter instruction but can cause a warning
+ * in valgrind if there are any uninitialized bits in rax. */
+ a.bt(RET, imm(63));
+ a.short_().jnc(ascii);
+
+ /* The bs_get_utf8_shared fragment expects the contents in RETd. */
+ a.shr(RET, imm(32));
+ a.jmp(labels[bs_get_utf8_shared]);
+
+ /* Handle plain old ASCII (code point < 128). */
+ a.bind(ascii);
+ a.add(x86::qword_ptr(ctx, position_offset), imm(8));
+ a.shr(RET, imm(56 - _TAG_IMMED1_SIZE));
+ a.or_(RET, imm(_TAG_IMMED1_SMALL)); /* Always clears ZF. */
+ a.ret();
+}
+
+/*
+ * ARG1 = pointer to match state
+ * ARG2 = position in binary in bits
+ * RETd = 4 bytes read from the binary in big-endian order
+ *
+ * On successful return, the extracted code point is in RET, the
+ * position in the match state has been updated, and the ZF is clear.
+ * On failure, the ZF is set.
+ */
+void BeamGlobalAssembler::emit_bs_get_utf8_shared() {
+ Label error = a.newLabel();
+
+ x86::Gp shift_q = ARG4, shift_d = ARG4d, shift_b = ARG4.r8();
+ x86::Gp original_value_d = RETd;
+
+ x86::Gp byte_count_q = ARG2, byte_count_d = ARG2d;
+ x86::Gp extracted_value_d = ARG3d, extracted_value_b = ARG3.r8();
+ x86::Gp control_mask_d = ARG5d;
+ x86::Gp error_mask_d = ARG6d;
+
+ ASSERT(extracted_value_d != shift_d);
+ ASSERT(control_mask_d != shift_d);
+ ASSERT(error_mask_d != shift_d);
+ ASSERT(byte_count_d != shift_d);
+
+ /* UTF-8 has the following layout, where 'x' are data bits:
+ *
+ * 1 byte: 0xxxxxxx (not handled by this path)
+ * 2 bytes: 110xxxxx, 10xxxxxx
+ * 3 bytes: 1110xxxx, 10xxxxxx 10xxxxxx
+ * 4 bytes: 11110xxx, 10xxxxxx 10xxxxxx 10xxxxxx
+ *
+ * Note that the number of leading bits is equal to the number of bytes,
+ * which makes it very easy to create masks for extraction and error
+ * checking. */
+
+ /* The PEXT instruction has poor latency on some processors, so we try to
+ * hide that by extracting early on. Should this be a problem, it's not
+ * much slower to hand-roll it with shifts or BEXTR.
+ *
+ * The mask covers data bits from all variants. This includes the 23rd bit
+ * to support the 2-byte case, which is set on all well-formed 4-byte
+ * codepoints, so it must be cleared before range testing .*/
+ a.mov(extracted_value_d, imm(0x1F3F3F3F));
+ a.pext(extracted_value_d, original_value_d, extracted_value_d);
+
+ /* Preserve current match buffer and bit offset. */
+ a.push(ARG1);
+ a.push(ARG2);
+
+ /* Byte count = leading bit count. */
+ a.mov(byte_count_d, original_value_d);
+ a.not_(byte_count_d);
+ a.lzcnt(byte_count_d, byte_count_d);
+
+ /* Mask shift = (4 - byte count) * 8 */
+ a.mov(shift_d, imm(4));
+ a.sub(shift_d, byte_count_d);
+ a.lea(shift_d, x86::qword_ptr(0, shift_q, 3));
+
+ /* Shift the original value and masks into place. */
+ a.shrx(original_value_d, original_value_d, shift_d);
+
+ /* Matches the '10xxxxxx' components, leaving the header byte alone. */
+ a.mov(control_mask_d, imm(0x00C0C0C0));
+ a.shrx(control_mask_d, control_mask_d, shift_d);
+ a.mov(error_mask_d, imm(0x00808080));
+ a.shrx(error_mask_d, error_mask_d, shift_d);
+
+ /* Extracted value shift = (4 - byte count) * 6, as the leading '10' on
+ * every byte has been removed through PEXT.
+ *
+ * We calculate the shift here to avoid depending on byte_count_d later on
+ * when it may have changed. */
+ a.mov(shift_d, imm(4));
+ a.sub(shift_d, byte_count_d);
+ a.add(shift_d, shift_d);
+ a.lea(shift_d, x86::qword_ptr(shift_q, shift_q, 1));
+
+ /* Assert that the header bits of each '10xxxxxx' component is correct,
+ * signalling errors by trashing the byte count with a guaranteed-illegal
+ * value. */
+ a.and_(original_value_d, control_mask_d);
+ a.cmp(original_value_d, error_mask_d);
+ a.cmovne(byte_count_d, error_mask_d);
+
+ /* Shift the extracted value into place. */
+ a.shrx(RETd, extracted_value_d, shift_d);
+
+ /* The extraction mask is a bit too wide, see above for details. */
+ a.and_(RETd, imm(~(1 << 22)));
+
+ /* Check for too large code point. */
+ a.cmp(RETd, imm(0x10FFFF));
+ a.cmova(byte_count_d, error_mask_d);
+
+ /* Check for the illegal range 16#D800 - 16#DFFF. */
+ a.mov(shift_d, RETd);
+ a.and_(shift_d, imm(-0x800));
+ a.cmp(shift_d, imm(0xD800));
+ a.cmove(byte_count_d, error_mask_d);
+
+ /* Test for overlong UTF-8 sequence. That can be done by testing
+ * that the bits marked y below are all zero.
+ *
+ * 1 byte: 0xxxxxxx (not handled by this path)
+ * 2 bytes: 110yyyyx, 10xxxxxx
+ * 3 bytes: 1110yyyy, 10yxxxxx 10xxxxxx
+ * 4 bytes: 11110yyy, 10yyxxxx 10xxxxxx 10xxxxxx
+ *
+ * 1 byte: xx'xxxxx
+ * 2 bytes: y'yyyxx'xxxxx
+ * 3 bytes: y'yyyyx'xxxxx'xxxxx
+ * 4 bytes: y'yyyyx'xxxxx'xxxxx'xxxxx
+ *
+ * The y bits can be isolated by shifting down by the number of bits
+ * shown in this table:
+ *
+ * 2: 7 (byte_count * 4 - 1)
+ * 3: 11 (byte_count * 4 - 1)
+ * 4: 16 (byte_count * 4)
+ */
+
+ /* Calculate number of bits to shift. */
+ a.lea(shift_d, x86::qword_ptr(0, byte_count_q, 2));
+ a.cmp(byte_count_d, imm(4));
+ a.setne(extracted_value_b);
+ a.sub(shift_b, extracted_value_b);
+ a.movzx(shift_q, shift_b);
+
+ /* Now isolate the y bits and compare to zero. */
+ a.shrx(extracted_value_d, RETd, shift_d);
+ a.test(extracted_value_d, extracted_value_d);
+ a.cmove(byte_count_d, error_mask_d);
+
+ /* Restore current bit offset and match buffer. */
+ ASSERT(ARG1 != byte_count_q && ARG3 != byte_count_q);
+ a.pop(ARG3);
+ a.pop(ARG1);
+
+ /* Advance our current position. */
+ a.lea(ARG3, x86::qword_ptr(ARG3, byte_count_q, 3));
+
+ /* Byte count must be 2, 3, or 4. */
+ a.sub(byte_count_d, imm(2));
+ a.cmp(byte_count_d, imm(2));
+ a.ja(error);
+
+ a.mov(x86::qword_ptr(ARG1, offsetof(ErlBinMatchBuffer, offset)), ARG3);
+
+ a.shl(RETd, imm(_TAG_IMMED1_SIZE));
+ a.or_(RETd, imm(_TAG_IMMED1_SMALL)); /* Always clears ZF. */
+
+ a.ret();
+
+ a.bind(error);
+ {
+ /* Signal error by setting ZF. */
+ a.xor_(RET, RET);
+ a.ret();
+ }
+}
+
void BeamModuleAssembler::emit_bs_get_utf8(const ArgRegister &Ctx,
const ArgLabel &Fail) {
- mov_arg(ARG1, Ctx);
+ const int base_offset = offsetof(ErlBinMatchBuffer, base);
+ const int position_offset = offsetof(ErlBinMatchBuffer, offset);
+ const int size_offset = offsetof(ErlBinMatchBuffer, size);
+
+ const x86::Gp ctx = ARG1;
+ const x86::Gp bin_position = ARG2;
+ const x86::Gp bin_base = ARG3;
+
+ Label multi_byte = a.newLabel(), fallback = a.newLabel(),
+ check = a.newLabel(), done = a.newLabel();
+
+ mov_arg(ctx, Ctx);
+ a.lea(ctx, emit_boxed_val(ARG1, offsetof(ErlBinMatchState, mb)));
+
+ a.mov(bin_position, x86::qword_ptr(ctx, position_offset));
+ a.mov(RET, x86::qword_ptr(ctx, size_offset));
+ a.mov(bin_base, x86::qword_ptr(ctx, base_offset));
+ a.sub(RET, bin_position);
+ a.cmp(RET, imm(32));
+ a.short_().jb(fallback);
+
+ a.test(bin_position, imm(7));
+ a.short_().jnz(fallback);
+
+ /* We're byte-aligned and can read at least 32 bits. */
+ a.mov(RET, bin_position);
+ a.shr(RET, 3);
+
+ /* The most significant bits come first, so we'll read the the next four
+ * bytes as big-endian so we won't have to reorder them later. */
+ if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
+ a.movbe(RETd, x86::dword_ptr(bin_base, RET));
+ } else {
+ a.mov(RETd, x86::dword_ptr(bin_base, RET));
+ a.bswap(RETd);
+ }
+ a.test(RETd, RETd);
+ a.short_().js(multi_byte);
- emit_enter_runtime();
+ /* Handle plain old ASCII (code point < 128). */
+ a.add(x86::qword_ptr(ctx, position_offset), imm(8));
+ a.shr(RETd, imm(24 - _TAG_IMMED1_SIZE));
+ a.or_(RETd, imm(_TAG_IMMED1_SMALL));
+ a.short_().jmp(done);
- a.lea(ARG1, emit_boxed_val(ARG1, offsetof(ErlBinMatchState, mb)));
- runtime_call<1>(erts_bs_get_utf8);
+ a.bind(multi_byte);
- emit_leave_runtime();
+ if (hasCpuFeature(CpuFeatures::X86::kBMI2)) {
+ /* This CPU supports the PEXT and SHRX instructions. */
+ safe_fragment_call(ga->get_bs_get_utf8_shared());
+ a.short_().jmp(check);
+ }
- emit_test_the_non_value(RET);
+ /* Take care of unaligned binaries and binaries with less than 32
+ * bits left. */
+ a.bind(fallback);
+ if (hasCpuFeature(CpuFeatures::X86::kBMI2)) {
+ /* This CPU supports the PEXT and SHRX instructions. */
+ safe_fragment_call(ga->get_bs_get_utf8_short_shared());
+ } else {
+ emit_enter_runtime();
+
+ runtime_call<1>(erts_bs_get_utf8);
+
+ emit_leave_runtime();
+
+ emit_test_the_non_value(RET);
+ }
+
+ a.bind(check);
a.je(resolve_beam_label(Fail));
+
+ a.bind(done);
}
void BeamModuleAssembler::emit_i_bs_get_utf8(const ArgRegister &Ctx,
@@ -1286,8 +1484,8 @@ void BeamModuleAssembler::emit_validate_unicode(Label next,
Label fail,
x86::Gp value) {
a.mov(ARG3d, value.r32());
- a.and_(ARG3d, imm(_TAG_IMMED1_MASK));
- a.cmp(ARG3d, imm(_TAG_IMMED1_SMALL));
+ a.and_(ARG3d.r8(), imm(_TAG_IMMED1_MASK));
+ a.cmp(ARG3d.r8(), imm(_TAG_IMMED1_SMALL));
a.jne(fail);
a.cmp(value, imm(make_small(0xD800UL)));
@@ -1485,13 +1683,13 @@ void BeamModuleAssembler::emit_i_bs_append(const ArgLabel &Fail,
mov_arg(ArgXRegister(Live.get()), Bin);
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<6>(erts_bs_append);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
emit_test_the_non_value(RET);
@@ -1544,18 +1742,18 @@ void BeamModuleAssembler::emit_i_bs_private_append(const ArgLabel &Fail,
}
void BeamModuleAssembler::emit_bs_init_writable() {
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
a.mov(ARG2, getXRef(0));
runtime_call<2>(erts_bs_init_writable);
a.mov(getXRef(0), RET);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
}
void BeamGlobalAssembler::emit_bs_create_bin_error_shared() {
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapAlloc>();
/* ARG3 is already set by the caller */
a.mov(ARG2, ARG4);
@@ -1563,7 +1761,7 @@ void BeamGlobalAssembler::emit_bs_create_bin_error_shared() {
a.mov(ARG1, c_p);
runtime_call<4>(beam_jit_bs_construct_fail_info);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapAlloc>();
/* We must align the return address to make it a proper tagged CP, in case
* we were called with `safe_fragment_call`. This is safe because we will
@@ -1587,10 +1785,52 @@ void BeamGlobalAssembler::emit_bs_create_bin_error_shared() {
a.jmp(labels[raise_exception_shared]);
}
+/*
+ * ARG1 = tagged bignum term
+ *
+ * On return, Z is set if ARG1 is not a bignum. Otherwise, Z is clear and
+ * ARG1 is the 64 least significant bits of the bignum.
+ */
+void BeamGlobalAssembler::emit_get_sint64_shared() {
+ Label success = a.newLabel();
+ Label fail = a.newLabel();
+
+ emit_is_boxed(fail, ARG1);
+ x86::Gp boxed_ptr = emit_ptr_val(ARG4, ARG1);
+ a.mov(ARG2, emit_boxed_val(boxed_ptr));
+ a.mov(ARG3, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ a.and_(ARG2, imm(_TAG_HEADER_MASK));
+ a.cmp(ARG2, imm(POS_BIG_SUBTAG));
+ a.je(success);
+
+ a.cmp(ARG2, imm(NEG_BIG_SUBTAG));
+ a.jne(fail);
+
+ a.neg(ARG3);
+
+ a.bind(success);
+ {
+ a.mov(ARG1, ARG3);
+ /* Clear Z flag.
+ *
+ * ARG2 is known to be POS_BIG_SUBTAG or NEG_BIG_SUBTAG at this point.
+ */
+ ERTS_CT_ASSERT(POS_BIG_SUBTAG != 0 && NEG_BIG_SUBTAG != 0);
+ a.test(ARG2, ARG2);
+ a.ret();
+ }
+
+ a.bind(fail);
+ {
+ a.xor_(ARG2, ARG2); /* Set Z flag */
+ a.ret();
+ }
+}
+
struct BscSegment {
BscSegment()
: type(am_false), unit(1), flags(0), src(ArgNil()), size(ArgNil()),
- error_info(0), effectiveSize(-1) {
+ error_info(0), effectiveSize(-1), action(action::DIRECT) {
}
Eterm type;
@@ -1601,8 +1841,482 @@ struct BscSegment {
Uint error_info;
Sint effectiveSize;
+
+ /* Here are sub actions for storing integer segments.
+ *
+ * We use the ACCUMULATE_FIRST and ACCUMULATE actions to shift the
+ * values of segments with known, small sizes (no more than 64 bits)
+ * into an accumulator register.
+ *
+ * When no more segments can be accumulated, the STORE action is
+ * used to store the value of the accumulator into the binary.
+ *
+ * The DIRECT action is used when it is not possible to use the
+ * accumulator (for unknown or too large sizes).
+ */
+ enum class action { DIRECT, ACCUMULATE_FIRST, ACCUMULATE, STORE } action;
};
+static std::vector<BscSegment> bs_combine_segments(
+ const std::vector<BscSegment> segments) {
+ std::vector<BscSegment> segs;
+
+ for (auto seg : segments) {
+ switch (seg.type) {
+ case am_integer: {
+ if (!(0 < seg.effectiveSize && seg.effectiveSize <= 64)) {
+ /* Unknown or too large size. Handle using the default
+ * DIRECT action. */
+ segs.push_back(seg);
+ continue;
+ }
+
+ if (seg.flags & BSF_LITTLE || segs.size() == 0 ||
+ segs.back().action == BscSegment::action::DIRECT) {
+ /* There are no previous compatible ACCUMULATE / STORE
+ * actions. Create the first ones. */
+ seg.action = BscSegment::action::ACCUMULATE_FIRST;
+ segs.push_back(seg);
+ seg.action = BscSegment::action::STORE;
+ segs.push_back(seg);
+ continue;
+ }
+
+ auto prev = segs.back();
+ if (prev.flags & BSF_LITTLE) {
+ /* Little-endian segments cannot be combined with other
+ * segments. Create new ACCUMULATE_FIRST / STORE actions. */
+ seg.action = BscSegment::action::ACCUMULATE_FIRST;
+ segs.push_back(seg);
+ seg.action = BscSegment::action::STORE;
+ segs.push_back(seg);
+ continue;
+ }
+
+ /* The current segment is compatible with the previous
+ * segment. Try combining them. */
+ if (prev.effectiveSize + seg.effectiveSize <= 64) {
+ /* The combined values of the segments fits in the
+ * accumulator. Insert an ACCUMULATE action for the
+ * current segment before the pre-existing STORE
+ * action. */
+ segs.pop_back();
+ prev.effectiveSize += seg.effectiveSize;
+ seg.action = BscSegment::action::ACCUMULATE;
+ segs.push_back(seg);
+ segs.push_back(prev);
+ } else {
+ /* The size exceeds 64 bits. Can't combine. */
+ seg.action = BscSegment::action::ACCUMULATE_FIRST;
+ segs.push_back(seg);
+ seg.action = BscSegment::action::STORE;
+ segs.push_back(seg);
+ }
+ break;
+ }
+ default:
+ segs.push_back(seg);
+ break;
+ }
+ }
+ return segs;
+}
+
+/*
+ * In:
+ * bin_offset = if valid, register to store the lower 32 bits
+ * of the bit offset into the binary
+ * bin_ptr = register to store pointer to current byte in
+ * bit_offset = current bit offset into binary, or -1 if unknown
+ * size = size of segment to be constructed
+ * (ignored if size_reg is valid register)
+ * size_reg = if a valid register, it contains the size of
+ * the segment to be constructed
+ *
+ * Out:
+ * bin_offset register = the lower 32 bits of the bit offset
+ * into the binary
+ * bin_ptr register = pointer to current byte
+ *
+ * Preserves all other registers except RET.
+ */
+void BeamModuleAssembler::update_bin_state(x86::Gp bin_offset,
+ x86::Gp current_byte,
+ Sint bit_offset,
+ Sint size,
+ x86::Gp size_reg) {
+ const int x_reg_offset = offsetof(ErtsSchedulerRegisters, x_reg_array.d);
+ const int cur_bin_base =
+ offsetof(ErtsSchedulerRegisters, aux_regs.d.erl_bits_state) +
+ offsetof(struct erl_bits_state, erts_current_bin_);
+ const int cur_bin_offset =
+ offsetof(ErtsSchedulerRegisters, aux_regs.d.erl_bits_state) +
+ offsetof(struct erl_bits_state, erts_bin_offset_);
+
+ x86::Mem mem_bin_base =
+ x86::Mem(registers, cur_bin_base - x_reg_offset, sizeof(UWord));
+ x86::Mem mem_bin_offset =
+ x86::Mem(registers, cur_bin_offset - x_reg_offset, sizeof(UWord));
+
+ if (bit_offset % 8 != 0 || !Support::isInt32(bit_offset + size)) {
+ /* The bit offset is unknown or not byte-aligned. Alternatively,
+ * the sum of bit_offset and size does not fit in an immediate. */
+ a.mov(current_byte, mem_bin_offset);
+ a.mov(RET, mem_bin_base);
+
+ if (bin_offset.isValid()) {
+ a.mov(bin_offset.r32(), current_byte.r32());
+ }
+ if (size_reg.isValid()) {
+ a.add(mem_bin_offset, size_reg);
+ } else {
+ a.add(mem_bin_offset, imm(size));
+ }
+ a.shr(current_byte, imm(3));
+ a.add(current_byte, RET);
+ } else {
+ ASSERT(size >= 0 || size_reg.isValid());
+ ASSERT(bit_offset % 8 == 0);
+
+ comment("optimized updating of binary construction state");
+ a.mov(current_byte, mem_bin_base);
+ if (bit_offset) {
+ a.add(current_byte, imm(bit_offset >> 3));
+ }
+ if (size_reg.isValid()) {
+ a.add(mem_bin_offset, size_reg);
+ } else {
+ a.mov(mem_bin_offset, imm(bit_offset + size));
+ }
+ }
+}
+
+bool BeamModuleAssembler::need_mask(const ArgVal Val, Sint size) {
+ if (size == 64) {
+ return false;
+ } else {
+ auto [min, max] = getClampedRange(Val);
+ return !(0 <= min && max >> size == 0);
+ }
+}
+
+/*
+ * The size of the segment is assumed to be in ARG3.
+ */
+void BeamModuleAssembler::set_zero(Sint effectiveSize) {
+ update_bin_state(ARG2, ARG1, -1, -1, ARG3);
+
+ mov_imm(RET, 0);
+
+ if (effectiveSize < 0 || effectiveSize > 128) {
+ /* Size is unknown or greater than 128. Modern CPUs have an
+ * enhanced "rep stosb" instruction that in most circumstances
+ * is the fastest way to clear blocks of more than 128
+ * bytes. */
+ Label done = a.newLabel();
+
+ if (effectiveSize < 0) {
+ a.test(ARG3, ARG3);
+ a.short_().jz(done);
+ }
+
+ if (ARG1 != x86::rdi) {
+ a.mov(x86::rdi, ARG1);
+ }
+ a.mov(x86::rcx, ARG3);
+ a.add(x86::rcx, imm(7));
+ a.shr(x86::rcx, imm(3));
+ a.rep().stosb();
+
+ a.bind(done);
+ } else {
+ /* The size is known and it is at most 128 bits. */
+ Uint offset = 0;
+
+ ASSERT(0 <= effectiveSize && effectiveSize <= 128);
+
+ if (effectiveSize == 128) {
+ a.mov(x86::Mem(ARG1, offset, 8), RET);
+ offset += 8;
+ }
+
+ if (effectiveSize >= 64) {
+ a.mov(x86::Mem(ARG1, offset, 8), RET);
+ offset += 8;
+ }
+
+ if ((effectiveSize & 63) >= 32) {
+ a.mov(x86::Mem(ARG1, offset, 4), RETd);
+ offset += 4;
+ }
+
+ if ((effectiveSize & 31) >= 16) {
+ a.mov(x86::Mem(ARG1, offset, 2), RET.r16());
+ offset += 2;
+ }
+
+ if ((effectiveSize & 15) >= 8) {
+ a.mov(x86::Mem(ARG1, offset, 1), RET.r8());
+ offset += 1;
+ }
+
+ if ((effectiveSize & 7) > 0) {
+ a.mov(x86::Mem(ARG1, offset, 1), RET.r8());
+ }
+ }
+}
+
+/*
+ * In:
+ *
+ * ARG3 = valid unicode code point (=> 0x80) to encode
+ *
+ * Out:
+ *
+ * ARG3d = the code point encoded in UTF-8.
+ * ARG2 = number of bits of result (16, 24, or 32)
+ *
+ * Clobbers RET and the other ARG* registers.
+ */
+void BeamGlobalAssembler::emit_construct_utf8_shared() {
+ Label more_than_two_bytes = a.newLabel();
+ Label four_bytes = a.newLabel();
+ const x86::Gp tmp1 = ARG1;
+ const x86::Gp tmp2 = ARG2;
+ const x86::Gp value = ARG3;
+ const x86::Gp num_bits = ARG2;
+
+ a.mov(RETd, value.r32());
+ a.and_(RETd, imm(0x3f));
+
+ a.cmp(value.r32(), imm(0x800));
+ a.jae(more_than_two_bytes);
+
+ a.shl(RETd, imm(8));
+
+ a.shr(value, imm(6));
+
+ a.or_(value.r32(), RETd);
+ a.or_(value.r32(), imm(0x80c0));
+
+ mov_imm(num_bits, 16);
+ a.ret();
+
+ /* Test whether the value should be encoded in four bytes. */
+ a.bind(more_than_two_bytes);
+ a.cmp(value.r32(), imm(0x10000));
+ a.jae(four_bytes);
+
+ /* Encode Unicode code point in three bytes. */
+ a.shl(RETd, imm(16));
+
+ a.lea(tmp1.r32(), x86::Mem(0ULL, ARG3, 2, 0));
+ a.and_(tmp1.r32(), imm(0x3f00));
+
+ a.shr(value.r32(), imm(12));
+ a.or_(value.r32(), tmp1.r32());
+ a.or_(value.r32(), RETd);
+ a.or_(value.r32(), imm(0x8080e0));
+
+ mov_imm(num_bits, 24);
+ a.ret();
+
+ /* Encode Unicode code point in four bytes. */
+ a.bind(four_bytes);
+ a.shl(RETd, imm(24));
+
+ a.mov(tmp1.r32(), value.r32());
+ a.shl(tmp1.r32(), imm(10));
+ a.and_(tmp1.r32(), imm(0x3f0000));
+
+ a.mov(tmp2.r32(), value.r32());
+ a.shr(tmp2.r32(), imm(4));
+ a.and_(tmp2.r32(), imm(0x3f00));
+
+ a.shr(value.r32(), imm(18));
+
+ a.or_(value.r32(), RETd);
+ a.or_(value.r32(), tmp1.r32());
+ a.or_(value.r32(), tmp2.r32());
+ a.or_(value.r32(), imm(0xffffffff808080f0));
+
+ mov_imm(num_bits, 32);
+ a.ret();
+}
+
+void BeamModuleAssembler::emit_construct_utf8(const ArgVal &Src,
+ Sint bit_offset,
+ bool is_byte_aligned) {
+ Label prepare_store = a.newLabel();
+ Label store = a.newLabel();
+ Label next = a.newLabel();
+
+#ifdef WIN32
+ const x86::Gp bin_ptr = ARG4;
+ const x86::Gp bin_offset = is_byte_aligned ? x86::Gp() : ARG1;
+#else
+ const x86::Gp bin_ptr = ARG1;
+ const x86::Gp bin_offset = is_byte_aligned ? x86::Gp() : ARG4;
+#endif
+ ASSERT(!bin_offset.isValid() || bin_offset == x86::rcx);
+
+ /* The following two registers must be the same as
+ * emit_construct_utf8_shared() expects. */
+ const x86::Gp code_point = ARG3;
+ const x86::Gp size_reg = ARG2;
+
+ comment("construct utf8 segment");
+
+ mov_arg(code_point, Src);
+ a.shr(code_point.r32(), imm(_TAG_IMMED1_SIZE));
+ mov_imm(size_reg, 8);
+ a.cmp(code_point, imm(0x80));
+ a.jb(prepare_store);
+
+ safe_fragment_call(ga->get_construct_utf8_shared());
+
+ a.bind(prepare_store);
+
+ update_bin_state(bin_offset, bin_ptr, bit_offset, -1, size_reg);
+
+ if (!is_byte_aligned) {
+ /* Bit offset is unknown and is not known to be
+ * byte aligned. Must test alignment. */
+ a.and_(bin_offset.r32(), imm(7));
+ a.je(store);
+
+ /* We must combine the last partial byte with the UTF-8
+ * encoded code point. */
+
+ a.movzx(RETd, x86::byte_ptr(bin_ptr));
+
+ a.bswap(code_point);
+ a.shr(code_point, bin_offset.r8());
+ a.bswap(code_point);
+
+ a.shl(RETd, bin_offset.r8());
+ a.and_(RETd, imm(~0xff));
+ a.shr(RETd, bin_offset.r8());
+
+ a.or_(code_point, RET);
+
+ a.add(size_reg.r32(), imm(8));
+ }
+
+ a.bind(store);
+ if (bit_offset % (4 * 8) == 0) {
+ /* This segment is aligned on a 4-byte boundary. This implies
+ * that a 4-byte write will be inside the allocated binary. */
+ a.mov(x86::dword_ptr(bin_ptr), code_point.r32());
+ } else {
+ Label do_store_1 = a.newLabel();
+ Label do_store_2 = a.newLabel();
+
+ /* Unsuitable or unknown alignment. We must be careful not
+ * to write beyound the allocated end of the binary. */
+ a.cmp(size_reg.r8(), imm(8));
+ a.short_().jne(do_store_1);
+
+ a.mov(x86::byte_ptr(bin_ptr), code_point.r8());
+ a.short_().jmp(next);
+
+ a.bind(do_store_1);
+ a.cmp(size_reg.r8(), imm(24));
+ a.ja(do_store_2);
+
+ a.mov(x86::word_ptr(bin_ptr), code_point.r16());
+ a.cmp(size_reg.r8(), imm(16));
+ a.short_().je(next);
+
+ a.shr(code_point.r32(), imm(16));
+ a.mov(x86::byte_ptr(bin_ptr, 2), code_point.r8());
+ a.short_().jmp(next);
+
+ a.bind(do_store_2);
+ a.mov(x86::dword_ptr(bin_ptr), code_point.r32());
+
+ if (!is_byte_aligned) {
+ a.cmp(size_reg.r8(), imm(32));
+ a.je(next);
+
+ a.shr(code_point, imm(32));
+ a.mov(x86::byte_ptr(bin_ptr, 4), code_point.r8());
+ }
+ }
+
+ a.bind(next);
+}
+/*
+ * In:
+ * ARG1 = pointer to current byte
+ * ARG3 = bit offset
+ * ARG4 = number of bits to write
+ * ARG5 = data to write
+ */
+void BeamGlobalAssembler::emit_store_unaligned() {
+ Label loop = a.newLabel();
+ Label done = a.newLabel();
+ const x86::Gp bin_ptr = ARG1;
+ const x86::Gp left_bit_offset = ARG3;
+ const x86::Gp right_bit_offset = ARG2;
+ const x86::Gp num_bits = ARG4;
+ const x86::Gp bitdata = ARG5;
+
+ a.movzx(RETd, x86::byte_ptr(bin_ptr));
+
+ a.xchg(left_bit_offset, x86::rcx);
+
+ a.mov(right_bit_offset, bitdata);
+ a.and_(right_bit_offset, imm(0xff));
+ a.shr(right_bit_offset, x86::cl);
+
+ a.shl(RETd, x86::cl);
+ a.and_(RETd, imm(~0xff));
+ a.shr(RETd, x86::cl);
+
+ a.xchg(left_bit_offset, x86::rcx);
+
+ a.or_(RETd, ARG2d);
+ a.mov(byte_ptr(ARG1), RETb);
+ a.add(ARG1, imm(1));
+
+ mov_imm(right_bit_offset, 8);
+ a.sub(right_bit_offset, left_bit_offset);
+
+ a.xchg(right_bit_offset, x86::rcx);
+ a.bswap(bitdata);
+ a.shl(bitdata, x86::cl);
+ a.xchg(right_bit_offset, x86::rcx);
+
+ a.sub(ARG4, right_bit_offset);
+ a.jle(done);
+
+ a.bind(loop);
+ a.rol(bitdata, imm(8));
+ a.mov(byte_ptr(ARG1), bitdata.r8());
+ a.add(ARG1, imm(1));
+ a.sub(num_bits, imm(8));
+ a.jg(loop);
+
+ a.bind(done);
+ a.ret();
+}
+
+bool BeamModuleAssembler::bs_maybe_enter_runtime(bool entered) {
+ if (!entered) {
+ comment("enter runtime");
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
+ }
+ return true;
+}
+
+void BeamModuleAssembler::bs_maybe_leave_runtime(bool entered) {
+ if (entered) {
+ comment("leave runtime");
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
+ }
+}
+
void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
const ArgWord &Alloc,
const ArgWord &Live0,
@@ -1611,10 +2325,12 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
Uint num_bits = 0;
std::size_t n = args.size();
std::vector<BscSegment> segments;
- Label error = a.newLabel();
- Label past_error = a.newLabel();
+ Label error; /* Intentionally uninitialized */
ArgWord Live = Live0;
x86::Gp sizeReg;
+ Sint allocated_size = -1;
+ bool need_error_handler = false;
+ bool runtime_entered = false;
/*
* Collect information about each segment and calculate sizes of
@@ -1660,12 +2376,45 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
seg.error_info = beam_jit_set_bsc_segment_op(bsc_segment, bsc_op);
/*
+ * Test whether we can omit the code for the error handler.
+ */
+ switch (seg.type) {
+ case am_append:
+ if (!(exact_type<BeamTypeId::Bitstring>(seg.src) &&
+ std::gcd(seg.unit, getSizeUnit(seg.src)) == seg.unit)) {
+ need_error_handler = true;
+ }
+ break;
+ case am_binary:
+ if (!(seg.size.isAtom() && seg.size.as<ArgAtom>().get() == am_all &&
+ exact_type<BeamTypeId::Bitstring>(seg.src) &&
+ std::gcd(seg.unit, getSizeUnit(seg.src)) == seg.unit)) {
+ need_error_handler = true;
+ }
+ break;
+ case am_integer:
+ if (!exact_type<BeamTypeId::Integer>(seg.src)) {
+ need_error_handler = true;
+ }
+ break;
+ case am_private_append:
+ case am_string:
+ break;
+ default:
+ need_error_handler = true;
+ break;
+ }
+
+ /*
* As soon as we have entered runtime mode, Y registers can no
* longer be accessed in the usual way. Therefore, if the source
- * and/or size are in Y register, copy them to X registers.
+ * and/or size are in Y registers, copy them to X registers. Be
+ * careful to preserve any associated type information.
*/
if (seg.src.isYRegister()) {
- ArgVal reg = ArgXRegister(Live.get());
+ auto reg =
+ seg.src.as<ArgYRegister>().copy<ArgXRegister>(Live.get());
+ ASSERT(reg.typeIndex() == seg.src.as<ArgYRegister>().typeIndex());
mov_arg(reg, seg.src);
Live = Live + 1;
@@ -1673,7 +2422,9 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
}
if (seg.size.isYRegister()) {
- ArgVal reg = ArgXRegister(Live.get());
+ auto reg =
+ seg.size.as<ArgYRegister>().copy<ArgXRegister>(Live.get());
+ ASSERT(reg.typeIndex() == seg.size.as<ArgYRegister>().typeIndex());
mov_arg(reg, seg.size);
Live = Live + 1;
@@ -1694,16 +2445,64 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
if (seg.effectiveSize < 0 && seg.type != am_append &&
seg.type != am_private_append) {
sizeReg = FCALLS;
+ need_error_handler = true;
}
segments.insert(segments.end(), seg);
}
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ /*
+ * Test whether a heap binary of fixed size will result from the
+ * construction. If so, allocate and construct the binary now
+ * before entering the runtime mode.
+ */
+ if (!sizeReg.isValid() && num_bits % 8 == 0 &&
+ num_bits / 8 <= ERL_ONHEAP_BIN_LIMIT && segments[0].type != am_append &&
+ segments[0].type != am_private_append) {
+ const int x_reg_offset =
+ offsetof(ErtsSchedulerRegisters, x_reg_array.d);
+ const int cur_bin_base =
+ offsetof(ErtsSchedulerRegisters, aux_regs.d.erl_bits_state) +
+ offsetof(struct erl_bits_state, erts_current_bin_);
+ const int cur_bin_offset =
+ offsetof(ErtsSchedulerRegisters, aux_regs.d.erl_bits_state) +
+ offsetof(struct erl_bits_state, erts_bin_offset_);
+ x86::Mem mem_bin_base =
+ x86::qword_ptr(registers, cur_bin_base - x_reg_offset);
+ x86::Mem mem_bin_offset =
+ x86::qword_ptr(registers, cur_bin_offset - x_reg_offset);
+ Uint num_bytes = num_bits / 8;
+
+ comment("allocate heap binary");
+ allocated_size = (num_bytes + 7) & (-8);
+
+ /* Ensure that there is enough room on the heap. */
+ Uint need = heap_bin_size(num_bytes) + Alloc.get();
+ emit_gc_test(ArgWord(0), ArgWord(need), Live);
+
+ /* Create the heap binary. */
+ a.lea(RET, x86::qword_ptr(HTOP, TAG_PRIMARY_BOXED));
+ a.mov(TMP_MEM1q, RET);
+ a.mov(x86::qword_ptr(HTOP, 0), imm(header_heap_bin(num_bytes)));
+ a.mov(x86::qword_ptr(HTOP, sizeof(Eterm)), imm(num_bytes));
+
+ /* Initialize the erl_bin_state struct. */
+ a.add(HTOP, imm(sizeof(Eterm[2])));
+ a.mov(mem_bin_base, HTOP);
+ a.mov(mem_bin_offset, imm(0));
+
+ /* Update HTOP. */
+ a.add(HTOP, imm(allocated_size));
+ }
+
+ if (!need_error_handler) {
+ comment("(cannot fail)");
+ } else {
+ Label past_error = a.newLabel();
+
+ runtime_entered = bs_maybe_enter_runtime(false);
+ a.short_().jmp(past_error);
- a.short_().jmp(past_error);
- a.bind(error);
- {
/*
* ARG1 = optional bad size value; valid if BSC_VALUE_ARG1 is set in
* ARG4
@@ -1713,17 +2512,18 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
*
* ARG4 = packed error information
*/
+ error = a.newLabel();
+ a.bind(error);
+ bs_maybe_leave_runtime(runtime_entered);
comment("handle error");
- emit_leave_runtime<Update::eReductions | Update::eStack |
- Update::eHeap>();
if (Fail.get() != 0) {
a.jmp(resolve_beam_label(Fail));
} else {
safe_fragment_call(ga->get_bs_create_bin_error_shared());
}
- }
- a.bind(past_error);
+ a.bind(past_error);
+ }
/* We count the total number of bits in an unsigned integer. To
* avoid having to check for overflow when adding to the counter,
@@ -1748,12 +2548,50 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
if (seg.size.isAtom() && seg.size.as<ArgAtom>().get() == am_all &&
seg.type == am_binary) {
comment("size of an entire binary");
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
mov_arg(ARG1, seg.src);
- runtime_call<1>(beam_jit_bs_bit_size);
- if (exact_type(seg.src, BEAM_TYPE_BITSTRING)) {
- comment("skipped check for success since the source "
- "is always a bit string");
+
+ if (exact_type<BeamTypeId::Bitstring>(seg.src)) {
+ auto unit = getSizeUnit(seg.src);
+ bool is_bitstring = unit == 0 || std::gcd(unit, 8) != 8;
+ x86::Gp boxed_ptr = emit_ptr_val(ARG1, ARG1);
+
+ if (is_bitstring) {
+ comment("inlined size code because the value is always "
+ "a bitstring");
+ } else {
+ comment("inlined size code because the value is always "
+ "a binary");
+ }
+
+ a.mov(ARG2, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+
+ if (is_bitstring) {
+ a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
+ }
+
+ a.lea(sizeReg, x86::Mem(sizeReg, ARG2, 3, 0, 1));
+
+ if (is_bitstring) {
+ Label not_sub_bin = a.newLabel();
+ const auto diff_mask =
+ _TAG_HEADER_SUB_BIN - _TAG_HEADER_REFC_BIN;
+ ERTS_CT_ASSERT((_TAG_HEADER_SUB_BIN & diff_mask) != 0 &&
+ (_TAG_HEADER_REFC_BIN & diff_mask) == 0 &&
+ (_TAG_HEADER_HEAP_BIN & diff_mask) == 0);
+ a.test(RETb, imm(diff_mask));
+ a.short_().jz(not_sub_bin);
+
+ a.movzx(RETd,
+ emit_boxed_val(boxed_ptr,
+ offsetof(ErlSubBin, bitsize),
+ 1));
+ a.add(sizeReg, RET);
+
+ a.bind(not_sub_bin);
+ }
} else {
+ runtime_call<1>(beam_jit_bs_bit_size);
if (Fail.get() == 0) {
mov_arg(ARG1, seg.src);
mov_imm(ARG4,
@@ -1764,17 +2602,15 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
}
a.test(RET, RET);
a.js(error);
+ a.add(sizeReg, RET);
}
- a.add(sizeReg, RET);
} else if (seg.unit != 0) {
bool can_fail = true;
comment("size binary/integer/float/string");
- if (always_small(seg.size)) {
- auto min = std::get<0>(getIntRange(seg.size));
- if (min >= 0) {
- can_fail = false;
- }
+ if (std::get<0>(getClampedRange(seg.size)) >= 0) {
+ /* Can't fail if size is always positive. */
+ can_fail = false;
}
if (can_fail && Fail.get() == 0) {
@@ -1789,10 +2625,9 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
if (always_small(seg.size)) {
comment("skipped test for small size since it is always small");
- } else if (always_one_of(seg.size,
- BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ } else if (always_one_of<BeamTypeId::Number>(seg.size)) {
comment("simplified test for small size since it is a number");
- a.test(ARG1d, imm(TAG_PRIMARY_LIST));
+ a.test(ARG1.r8(), imm(TAG_PRIMARY_LIST));
a.je(error);
} else {
a.mov(RETd, ARG1d);
@@ -1827,23 +2662,59 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
comment("size utf8");
mov_arg(ARG1, seg.src);
+ if (Fail.get() == 0) {
+ mov_imm(ARG4,
+ beam_jit_update_bsc_reason_info(seg.error_info,
+ BSC_REASON_BADARG,
+ BSC_INFO_TYPE,
+ BSC_VALUE_ARG1));
+ }
+
+ if (always_small(seg.src)) {
+ comment("skipped test for small value since it is always "
+ "small");
+ } else if (always_one_of<BeamTypeId::Integer,
+ BeamTypeId::AlwaysBoxed>(seg.src)) {
+ comment("simplified test for small operand since other "
+ "types are boxed");
+ emit_is_not_boxed(error, ARG1);
+ } else {
+ a.mov(RETd, ARG1d);
+ a.and_(RETb, imm(_TAG_IMMED1_MASK));
+ a.cmp(RETb, imm(_TAG_IMMED1_SMALL));
+ a.jne(error);
+ }
+
mov_imm(RET, 0);
- a.mov(RETb, imm(1 * 8));
+ a.mov(RETb, imm(1));
a.cmp(ARG1, imm(make_small(0x80UL)));
- a.short_().jl(next);
+ a.short_().jb(next);
- a.mov(RETb, imm(2 * 8));
+ a.mov(RETb, imm(2));
a.cmp(ARG1, imm(make_small(0x800UL)));
- a.short_().jl(next);
+ a.short_().jb(next);
- a.mov(RETb, imm(3 * 8));
- a.cmp(ARG1, imm(make_small(0x10000UL)));
- a.short_().jl(next);
+ /* Ensure that the value is not in the invalid range
+ * 0xD800 through 0xDFFF. */
+ a.mov(ARG2, ARG1);
+ a.sar(ARG2, imm(11 + _TAG_IMMED1_SIZE));
+ a.cmp(ARG2, imm(0x1b));
+ a.je(error);
- a.mov(RETb, imm(4 * 8));
+ a.cmp(ARG1, imm(make_small(0x10000UL)));
+ a.setae(RETb);
+ a.add(RETb, imm(3));
+
+ auto [min, max] = getClampedRange(seg.src);
+ if (0 <= min && max < 0x110000) {
+ comment("skipped range check for unicode code point");
+ } else {
+ a.cmp(ARG1, imm(make_small(0x110000)));
+ a.jae(error);
+ }
a.bind(next);
- a.add(sizeReg, RET);
+ a.lea(sizeReg, x86::Mem(sizeReg, RET, 3, 0, 1));
break;
}
case am_utf16: {
@@ -1891,9 +2762,12 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
}
}
+ segments = bs_combine_segments(segments);
+
/* Allocate the binary. */
if (segments[0].type == am_append) {
BscSegment seg = segments[0];
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
comment("append to binary");
mov_arg(ARG3, Live);
if (sizeReg.isValid()) {
@@ -1907,18 +2781,28 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<6>(erts_bs_append_checked);
- if (Fail.get() == 0) {
- mov_arg(ARG1, ArgXRegister(Live.get()));
- mov_imm(ARG4,
- beam_jit_update_bsc_reason_info(seg.error_info,
- BSC_REASON_BADARG,
- BSC_INFO_FVALUE,
- BSC_VALUE_ARG1));
+
+ if (exact_type<BeamTypeId::Bitstring>(seg.src) &&
+ std::gcd(seg.unit, getSizeUnit(seg.src)) == seg.unit) {
+ /* There is no way the call can fail with a system_limit
+ * exception on a 64-bit architecture. */
+ comment("skipped test for success because units are compatible");
+ } else {
+ if (Fail.get() == 0) {
+ mov_arg(ARG1, ArgXRegister(Live.get()));
+ mov_imm(ARG4,
+ beam_jit_update_bsc_reason_info(seg.error_info,
+ BSC_REASON_BADARG,
+ BSC_INFO_FVALUE,
+ BSC_VALUE_ARG1));
+ }
+ emit_test_the_non_value(RET);
+ a.je(error);
}
- emit_test_the_non_value(RET);
- a.je(error);
+ a.mov(TMP_MEM1q, RET);
} else if (segments[0].type == am_private_append) {
BscSegment seg = segments[0];
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
comment("private append to binary");
ASSERT(Alloc.get() == 0);
mov_arg(ARG2, seg.src);
@@ -1931,38 +2815,53 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
a.mov(ARG1, c_p);
runtime_call<4>(erts_bs_private_append_checked);
/* There is no way the call can fail on a 64-bit architecture. */
+ a.mov(TMP_MEM1q, RET);
+ } else if (allocated_size >= 0) {
+ /* The binary has already been allocated. */
} else {
comment("allocate binary");
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
mov_arg(ARG5, Alloc);
mov_arg(ARG6, Live);
load_erl_bits_state(ARG3);
load_x_reg_array(ARG2);
a.mov(ARG1, c_p);
if (sizeReg.isValid()) {
- comment("(size in bits)");
a.mov(ARG4, sizeReg);
runtime_call<6>(beam_jit_bs_init_bits);
- } else if (num_bits % 8 == 0) {
- comment("(size in bytes)");
- mov_imm(ARG4, num_bits / 8);
- runtime_call<6>(beam_jit_bs_init);
} else {
+ allocated_size = (num_bits + 7) / 8;
+ if (allocated_size <= ERL_ONHEAP_BIN_LIMIT) {
+ allocated_size = (allocated_size + 7) & (-8);
+ }
mov_imm(ARG4, num_bits);
runtime_call<6>(beam_jit_bs_init_bits);
}
+ a.mov(TMP_MEM1q, RET);
}
- a.mov(TMP_MEM1q, RET);
+
+ /* Keep track of the bit offset from the being of the binary.
+ * Set to -1 if offset is not known (when a segment of unknown
+ * size has been seen). */
+ Sint bit_offset = 0;
+
+ /* Keep track of whether the current segment is byte-aligned. (A
+ * segment can be known to be byte-aligned even if the bit offset
+ * is unknown.) */
+ bool is_byte_aligned = true;
/* Build each segment of the binary. */
for (auto seg : segments) {
switch (seg.type) {
case am_append:
case am_private_append:
+ bit_offset = -1;
break;
case am_binary: {
Uint error_info;
bool can_fail = true;
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
comment("construct a binary segment");
if (seg.effectiveSize >= 0) {
/* The segment has a literal size. */
@@ -1986,8 +2885,10 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
BSC_REASON_BADARG,
BSC_INFO_UNIT,
BSC_VALUE_FVALUE);
- if (seg.unit == 1) {
- comment("skipped test for success because unit =:= 1");
+ if (exact_type<BeamTypeId::Bitstring>(seg.src) &&
+ std::gcd(seg.unit, getSizeUnit(seg.src)) == seg.unit) {
+ comment("skipped test for success because units are "
+ "compatible");
can_fail = false;
}
} else {
@@ -2021,6 +2922,7 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
break;
}
case am_float:
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
comment("construct float segment");
if (seg.effectiveSize >= 0) {
mov_imm(ARG3, seg.effectiveSize);
@@ -2049,42 +2951,292 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
a.jne(error);
break;
case am_integer:
- comment("construct integer segment");
- if (seg.effectiveSize >= 0) {
- mov_imm(ARG3, seg.effectiveSize);
- } else {
- mov_arg(ARG3, seg.size);
- a.sar(ARG3, imm(_TAG_IMMED1_SIZE));
- if (seg.unit != 1) {
- mov_imm(RET, seg.unit);
- a.mul(ARG3); /* CLOBBERS RDX = ARG3! */
- a.mov(ARG3, RET);
+ switch (seg.action) {
+ case BscSegment::action::ACCUMULATE_FIRST:
+ case BscSegment::action::ACCUMULATE: {
+ /* Shift an integer of known size (no more than 64 bits)
+ * into a word-size accumulator. */
+ Label accumulate = a.newLabel();
+ Label value_is_small = a.newLabel();
+ x86::Gp tmp = ARG4;
+ x86::Gp bin_data = ARG5;
+
+ comment("accumulate value for integer segment");
+ if (seg.action == BscSegment::action::ACCUMULATE_FIRST) {
+ mov_imm(bin_data, 0);
+ } else if (seg.effectiveSize < 64) {
+ a.shl(bin_data, imm(seg.effectiveSize));
+ }
+ mov_arg(ARG1, seg.src);
+
+ if (!always_small(seg.src)) {
+ if (always_one_of<BeamTypeId::Integer,
+ BeamTypeId::AlwaysBoxed>(seg.src)) {
+ comment("simplified small test since all other types "
+ "are boxed");
+ emit_is_boxed(value_is_small, seg.src, ARG1);
+ } else {
+ a.mov(ARG2d, ARG1d);
+ a.and_(ARG2d, imm(_TAG_IMMED1_MASK));
+ a.cmp(ARG2d, imm(_TAG_IMMED1_SMALL));
+ a.short_().je(value_is_small);
+ }
+
+ /* The value is boxed. If it is a bignum, extract the
+ * least significant 64 bits. */
+ safe_fragment_call(ga->get_get_sint64_shared());
+ if (exact_type<BeamTypeId::Integer>(seg.src)) {
+ a.short_().jmp(accumulate);
+ } else {
+ a.short_().jne(accumulate);
+
+ /* Not a bignum. Signal error. */
+ if (Fail.get() == 0) {
+ mov_imm(ARG4,
+ beam_jit_update_bsc_reason_info(
+ seg.error_info,
+ BSC_REASON_BADARG,
+ BSC_INFO_TYPE,
+ BSC_VALUE_ARG1));
+ }
+ a.jmp(error);
+ }
}
+
+ a.bind(value_is_small);
+ a.sar(ARG1, imm(_TAG_IMMED1_SIZE));
+
+ /* Mask (if needed) and accumulate. */
+ a.bind(accumulate);
+ if (seg.effectiveSize == 64) {
+ a.mov(bin_data, ARG1);
+ } else if (!need_mask(seg.src, seg.effectiveSize)) {
+ comment("skipped masking because the value always fits");
+ a.or_(bin_data, ARG1);
+ } else if (seg.effectiveSize == 32) {
+ a.mov(ARG1d, ARG1d);
+ a.or_(bin_data, ARG1);
+ } else if (seg.effectiveSize < 32) {
+ a.and_(ARG1, (1ULL << seg.effectiveSize) - 1);
+ a.or_(bin_data, ARG1);
+ } else {
+ mov_imm(tmp, (1ULL << seg.effectiveSize) - 1);
+ a.and_(ARG1, tmp);
+ a.or_(bin_data, ARG1);
+ }
+ break;
}
- mov_arg(ARG2, seg.src);
- mov_imm(ARG4, seg.flags);
- load_erl_bits_state(ARG1);
- runtime_call<4>(erts_new_bs_put_integer);
- if (exact_type(seg.src, BEAM_TYPE_INTEGER)) {
- comment("skipped test for success because construction can't "
- "fail");
- } else {
- if (Fail.get() == 0) {
- mov_arg(ARG1, seg.src);
- mov_imm(ARG4,
- beam_jit_update_bsc_reason_info(seg.error_info,
- BSC_REASON_BADARG,
- BSC_INFO_TYPE,
- BSC_VALUE_ARG1));
+ case BscSegment::action::STORE: {
+ /* The accumulator is now full or the next segment is
+ * not possible to accumulate, so it's time to store
+ * the accumulator to the current position in the
+ * binary. */
+ Label store = a.newLabel();
+ Label done = a.newLabel();
+ x86::Gp bin_ptr = ARG1;
+ x86::Gp bin_offset = ARG3;
+ x86::Gp tmp = ARG4;
+ x86::Gp bin_data = ARG5;
+
+ comment("construct integer segment from accumulator");
+
+ /* First we'll need to ensure that the value in the
+ * accumulator is in little endian format. */
+ if (seg.effectiveSize % 8 != 0) {
+ Uint complete_bytes = 8 * (seg.effectiveSize / 8);
+ Uint num_partial = seg.effectiveSize % 8;
+ if ((seg.flags & BSF_LITTLE) == 0) {
+ a.shl(bin_data, imm(64 - seg.effectiveSize));
+ a.bswap(bin_data);
+ } else {
+ Sint mask = (1ll << complete_bytes) - 1;
+ a.mov(RET, bin_data);
+ a.shr(RET, imm(complete_bytes));
+ a.and_(RETd, imm((1ull << num_partial) - 1));
+ a.shl(RET, imm(complete_bytes + 8 - num_partial));
+ if (Support::isInt32(mask)) {
+ a.and_(bin_data, imm(mask));
+ } else {
+ mov_imm(tmp, mask);
+ a.and_(bin_data, tmp);
+ }
+ a.or_(bin_data, RET);
+ }
+ } else if ((seg.flags & BSF_LITTLE) == 0) {
+ switch (seg.effectiveSize) {
+ case 8:
+ break;
+ case 32:
+ a.bswap(bin_data.r32());
+ break;
+ case 64:
+ a.bswap(bin_data);
+ break;
+ default:
+ a.bswap(bin_data);
+ a.shr(bin_data, imm(64 - seg.effectiveSize));
+ break;
+ }
}
- a.test(RETd, RETd);
- a.je(error);
+
+ update_bin_state(bin_offset,
+ bin_ptr,
+ bit_offset,
+ seg.effectiveSize,
+ x86::Gp());
+
+ if (!is_byte_aligned) {
+ if (bit_offset < 0) {
+ /* Bit offset is unknown. Must test alignment. */
+ a.and_(bin_offset, imm(7));
+ a.short_().je(store);
+ } else if (bit_offset >= 0) {
+ /* Alignment is known to be unaligned. */
+ mov_imm(bin_offset, bit_offset & 7);
+ }
+
+ /* Bit offset is tested or known to be unaligned. */
+ mov_imm(ARG4, seg.effectiveSize);
+ safe_fragment_call(ga->get_store_unaligned());
+
+ if (bit_offset < 0) {
+ /* The bit offset is unknown, which implies
+ * that there exists store code that we will
+ * need to branch past. */
+ a.short_().jmp(done);
+ }
+ }
+
+ a.bind(store);
+
+ if (bit_offset <= 0 || is_byte_aligned) {
+ /* Bit offset is tested or known to be
+ * byte-aligned. Emit inline code to store the
+ * value of the accumulator into the binary. */
+ int num_bytes = (seg.effectiveSize + 7) / 8;
+
+ /* If more than one instruction is required for
+ * doing the store, test whether it would be safe
+ * to do a single 32 or 64 bit store. */
+ switch (num_bytes) {
+ case 3:
+ if (bit_offset >= 0 &&
+ allocated_size * 8 - bit_offset >= 32) {
+ comment("simplified complicated store");
+ num_bytes = 4;
+ }
+ break;
+ case 5:
+ case 6:
+ case 7:
+ if (bit_offset >= 0 &&
+ allocated_size * 8 - bit_offset >= 64) {
+ comment("simplified complicated store");
+ num_bytes = 8;
+ }
+ break;
+ }
+
+ do {
+ switch (num_bytes) {
+ case 1:
+ a.mov(x86::Mem(bin_ptr, 0, 1), bin_data.r8());
+ break;
+ case 2:
+ a.mov(x86::Mem(bin_ptr, 0, 2), bin_data.r16());
+ break;
+ case 3:
+ a.mov(x86::Mem(bin_ptr, 0, 2), bin_data.r16());
+ a.shr(bin_data, imm(16));
+ a.mov(x86::Mem(bin_ptr, 2, 1), bin_data.r8());
+ break;
+ case 4:
+ a.mov(x86::Mem(bin_ptr, 0, 4), bin_data.r32());
+ break;
+ case 5:
+ case 6:
+ case 7:
+ a.mov(x86::Mem(bin_ptr, 0, 4), bin_data.r32());
+ a.add(bin_ptr, imm(4));
+ a.shr(bin_data, imm(32));
+ break;
+ case 8:
+ a.mov(x86::Mem(bin_ptr, 0, 8), bin_data);
+ num_bytes = 0;
+ break;
+ default:
+ ASSERT(0);
+ }
+ num_bytes -= 4;
+ } while (num_bytes > 0);
+ }
+
+ a.bind(done);
+ break;
+ }
+ case BscSegment::action::DIRECT:
+ /* This segment either has a size exceeding the maximum
+ * accumulator size of 64 bits or has a variable size.
+ *
+ * First load the effective size (size * unit) into ARG3.
+ */
+ comment("construct integer segment");
+ if (seg.effectiveSize >= 0) {
+ mov_imm(ARG3, seg.effectiveSize);
+ } else {
+ mov_arg(ARG3, seg.size);
+ a.sar(ARG3, imm(_TAG_IMMED1_SIZE));
+ if (Support::isPowerOf2(seg.unit)) {
+ Uint trailing_bits = Support::ctz<Eterm>(seg.unit);
+ if (trailing_bits) {
+ a.shl(ARG3, imm(trailing_bits));
+ }
+ } else {
+ mov_imm(RET, seg.unit);
+ a.mul(ARG3); /* CLOBBERS RDX = ARG3! */
+ a.mov(ARG3, RET);
+ }
+ }
+
+ if (is_byte_aligned && seg.src.isSmall() &&
+ seg.src.as<ArgSmall>().getSigned() == 0) {
+ /* Optimize the special case of setting a known
+ * byte-aligned segment to zero. */
+ comment("optimized setting segment to 0");
+ set_zero(seg.effectiveSize);
+ } else {
+ /* Call the helper function to fetch and store the
+ * integer into the binary. */
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
+ mov_arg(ARG2, seg.src);
+ mov_imm(ARG4, seg.flags);
+ load_erl_bits_state(ARG1);
+ runtime_call<4>(erts_new_bs_put_integer);
+ if (exact_type<BeamTypeId::Integer>(seg.src)) {
+ comment("skipped test for success because construction "
+ "can't fail");
+ } else {
+ if (Fail.get() == 0) {
+ mov_arg(ARG1, seg.src);
+ mov_imm(ARG4,
+ beam_jit_update_bsc_reason_info(
+ seg.error_info,
+ BSC_REASON_BADARG,
+ BSC_INFO_TYPE,
+ BSC_VALUE_ARG1));
+ }
+ a.test(RETd, RETd);
+ a.je(error);
+ }
+ }
+ break;
}
break;
case am_string: {
ArgBytePtr string_ptr(
ArgVal(ArgVal::BytePtr, seg.src.as<ArgWord>().get()));
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
comment("insert string");
ASSERT(seg.effectiveSize >= 0);
mov_imm(ARG3, seg.effectiveSize / 8);
@@ -2092,22 +3244,13 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
load_erl_bits_state(ARG1);
runtime_call<3>(erts_new_bs_put_string);
} break;
- case am_utf8:
- mov_arg(ARG2, seg.src);
- load_erl_bits_state(ARG1);
- runtime_call<2>(erts_bs_put_utf8);
- if (Fail.get() == 0) {
- mov_arg(ARG1, seg.src);
- mov_imm(ARG4,
- beam_jit_update_bsc_reason_info(seg.error_info,
- BSC_REASON_BADARG,
- BSC_INFO_TYPE,
- BSC_VALUE_ARG1));
- }
- a.test(RETd, RETd);
- a.je(error);
+ case am_utf8: {
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
+ emit_construct_utf8(seg.src, bit_offset, is_byte_aligned);
break;
+ }
case am_utf16:
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
mov_arg(ARG2, seg.src);
a.mov(ARG3, seg.flags);
load_erl_bits_state(ARG1);
@@ -2124,6 +3267,7 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
a.je(error);
break;
case am_utf32:
+ runtime_entered = bs_maybe_enter_runtime(runtime_entered);
mov_arg(ARG2, seg.src);
mov_imm(ARG3, 4 * 8);
a.mov(ARG4, seg.flags);
@@ -2144,10 +3288,1098 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail,
ASSERT(0);
break;
}
+
+ /* Try to keep track of the bit offset. */
+ if (bit_offset >= 0 && (seg.action == BscSegment::action::DIRECT ||
+ seg.action == BscSegment::action::STORE)) {
+ if (seg.effectiveSize >= 0) {
+ bit_offset += seg.effectiveSize;
+ } else {
+ bit_offset = -1;
+ }
+ }
+
+ /* Try to keep track whether the next segment is byte
+ * aligned. */
+ if (seg.type == am_append || seg.type == am_private_append) {
+ if (!exact_type<BeamTypeId::Bitstring>(seg.src) ||
+ std::gcd(getSizeUnit(seg.src), 8) != 8) {
+ is_byte_aligned = false;
+ }
+ } else if (bit_offset % 8 == 0) {
+ is_byte_aligned = true;
+ } else if (seg.effectiveSize >= 0) {
+ if (seg.effectiveSize % 8 != 0) {
+ is_byte_aligned = false;
+ }
+ } else if (std::gcd(seg.unit, 8) != 8) {
+ is_byte_aligned = false;
+ }
}
+ bs_maybe_leave_runtime(runtime_entered);
comment("done");
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
a.mov(RET, TMP_MEM1q);
mov_arg(Dst, RET);
}
+
+/*
+ * Here follows the bs_match instruction and friends.
+ */
+
+struct BsmSegment {
+ BsmSegment()
+ : action(action::TEST_HEAP), live(ArgNil()), size(0), unit(1),
+ flags(0), dst(ArgXRegister(0)){};
+
+ enum class action {
+ TEST_HEAP,
+ ENSURE_AT_LEAST,
+ ENSURE_EXACTLY,
+ READ,
+ EXTRACT_BINARY,
+ EXTRACT_INTEGER,
+ READ_INTEGER,
+ GET_INTEGER,
+ GET_BINARY,
+ SKIP,
+ DROP,
+ GET_TAIL,
+ EQ
+ } action;
+ ArgVal live;
+ Uint size;
+ Uint unit;
+ Uint flags;
+ ArgRegister dst;
+};
+
+void BeamModuleAssembler::emit_read_bits(Uint bits,
+ const x86::Gp bin_base,
+ const x86::Gp bin_offset,
+ const x86::Gp bitdata) {
+ Label read_done = a.newLabel();
+ auto num_partial = bits % 8;
+ auto num_bytes_to_read = (bits + 7) / 8;
+
+ ASSERT(bin_offset == x86::rcx);
+
+ a.mov(RET, bin_offset);
+ a.shr(RET, imm(3));
+ if (num_bytes_to_read != 1) {
+ a.add(bin_base, RET);
+ }
+ a.and_(bin_offset.r32(), imm(7));
+
+ /*
+ * Special-case handling of reading 8 or 9 bytes.
+ */
+ if (num_bytes_to_read == 8) {
+ if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
+ a.movbe(bitdata, x86::qword_ptr(bin_base, num_bytes_to_read - 8));
+ } else {
+ a.mov(bitdata, x86::qword_ptr(bin_base, num_bytes_to_read - 8));
+ a.bswap(bitdata);
+ }
+
+ a.shl(bitdata, bin_offset.r8());
+
+ a.test(x86::cl, imm(7));
+ if (num_partial == 0) {
+ /* Byte-sized segment. If bit_offset is not byte-aligned, this
+ * segment always needs an additional byte. */
+ a.jz(read_done);
+ } else if (num_partial > 1) {
+ /* Non-byte-sized segment. Test whether we will need an
+ * additional byte. */
+ a.cmp(bin_offset.r32(), imm(8 - num_partial));
+ a.jle(read_done);
+ }
+
+ if (num_partial != 1) {
+ /* Read an extra byte. */
+ a.movzx(RETd, x86::byte_ptr(bin_base, 8));
+ a.shl(RETd, bin_offset.r8());
+ a.shr(RETd, imm(8));
+ a.or_(bitdata, RET);
+ }
+
+ a.bind(read_done);
+
+ return;
+ }
+
+ /*
+ * Handle reading of up to 7 bytes.
+ */
+ Label handle_partial = a.newLabel();
+ Label swap = a.newLabel();
+ Label shift = a.newLabel();
+
+ if (num_partial == 0) {
+ /* Byte-sized segment. If bit_offset is not byte-aligned, this
+ * segment always needs an additional byte. */
+ a.jnz(handle_partial);
+ } else if (num_partial > 1) {
+ /* Non-byte-sized segment. Test whether we will need an
+ * additional byte. */
+ a.cmp(bin_offset.r32(), imm(8 - num_partial));
+ a.jg(handle_partial);
+ }
+
+ /* We don't need an extra byte. */
+ if (num_bytes_to_read == 1) {
+ a.movzx(bitdata.r32(), x86::byte_ptr(bin_base, RET));
+ if (num_partial == 0) {
+ a.bswap(bitdata);
+ a.short_().jmp(read_done);
+ } else if (num_partial > 1) {
+ a.short_().jmp(swap);
+ }
+ } else if (num_bytes_to_read <= 4) {
+ if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
+ a.movbe(bitdata.r32(),
+ x86::dword_ptr(bin_base, num_bytes_to_read - 4));
+ } else {
+ a.mov(bitdata.r32(),
+ x86::dword_ptr(bin_base, num_bytes_to_read - 4));
+ a.bswap(bitdata.r32());
+ }
+ a.add(bin_offset.r32(), imm(64 - 8 * num_bytes_to_read));
+ a.short_().jmp(shift);
+ } else {
+ if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
+ a.movbe(bitdata, x86::qword_ptr(bin_base, num_bytes_to_read - 8));
+ } else {
+ a.mov(bitdata, x86::qword_ptr(bin_base, num_bytes_to_read - 8));
+ a.bswap(bitdata);
+ }
+ ASSERT(num_bytes_to_read < 8);
+ a.add(bin_offset.r32(), imm(64 - 8 * num_bytes_to_read));
+ a.short_().jmp(shift);
+ }
+
+ /* We'll need an extra byte and we will need to shift. */
+ a.bind(handle_partial);
+ if (num_partial != 1) {
+ if (num_bytes_to_read == 1) {
+ a.mov(bitdata.r16(), x86::word_ptr(bin_base, RET));
+ } else {
+ ASSERT(num_bytes_to_read < 8);
+ a.mov(bitdata, x86::qword_ptr(bin_base, num_bytes_to_read - 7));
+ a.shr(bitdata, imm(64 - 8 * (num_bytes_to_read + 1)));
+ }
+ }
+
+ a.bind(swap);
+ a.bswap(bitdata);
+
+ /* Shift the read data into the most significant bits of the
+ * word. */
+ a.bind(shift);
+ a.shl(bitdata, bin_offset.r8());
+
+ a.bind(read_done);
+}
+
+/*
+ * Read an integer and store as a term. This function only handles
+ * integers of certain common sizes. This is a special optimization
+ * when only one integer is to be extracted from a binary.
+ *
+ * Input: bin_base, bin_offset
+ *
+ * Clobbers: bin_base, bin_offset, tmp, RET
+ */
+void BeamModuleAssembler::emit_read_integer(const x86::Gp bin_base,
+ const x86::Gp bin_offset,
+ const x86::Gp tmp,
+ Uint flags,
+ Uint bits,
+ const ArgRegister &Dst) {
+ Label handle_unaligned = a.newLabel();
+ Label store = a.newLabel();
+ x86::Mem address;
+
+ a.mov(tmp, bin_offset);
+ a.shr(tmp, imm(3));
+ a.and_(bin_offset.r32(), imm(7));
+
+ switch (bits) {
+ case 8:
+ address = x86::Mem(bin_base, tmp, 0, 0, 1);
+ if ((flags & BSF_SIGNED) == 0) {
+ a.movzx(RETd, address);
+ } else {
+ a.movsx(RET, address);
+ }
+
+ a.short_().jz(store);
+
+ a.bind(handle_unaligned);
+ address = x86::Mem(bin_base, tmp, 0, 0, 2);
+ if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
+ a.movbe(RET.r16(), address);
+ } else {
+ a.mov(RET.r16(), address);
+ a.xchg(x86::al, x86::ah);
+ }
+ ASSERT(bin_offset == x86::rcx);
+ a.shl(RETd, bin_offset.r8());
+ a.mov(x86::al, x86::ah);
+ if ((flags & BSF_SIGNED) == 0) {
+ a.movzx(RETd, RETb);
+ } else {
+ a.movsx(RET, RETb);
+ }
+ break;
+ case 16:
+ address = x86::Mem(bin_base, tmp, 0, 0, 2);
+ if ((flags & BSF_LITTLE) != 0) {
+ if ((flags & BSF_SIGNED) == 0) {
+ a.movzx(RETd, address);
+ } else {
+ a.movsx(RET, address);
+ }
+ } else {
+ /* Big-endian segment. */
+ if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
+ a.movbe(RET.r16(), address);
+ } else {
+ a.mov(RET.r16(), address);
+ a.xchg(x86::al, x86::ah);
+ }
+
+ if ((flags & BSF_SIGNED) != 0) {
+ a.movsx(RET, RET.r16());
+ } else {
+ a.movzx(RET, RET.r16());
+ }
+ }
+
+ a.short_().jz(store);
+
+ a.bind(handle_unaligned);
+ a.add(bin_base, tmp);
+ address = x86::Mem(bin_base, -1, 4);
+ if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
+ a.movbe(RETd, address);
+ } else {
+ a.mov(RETd, address);
+ a.bswap(RETd);
+ }
+ ASSERT(bin_offset == x86::rcx);
+ a.shl(RETd, bin_offset.r8());
+ a.shr(RETd, imm(8));
+
+ if ((flags & BSF_LITTLE) != 0) {
+ a.xchg(x86::al, x86::ah);
+ }
+
+ if ((flags & BSF_SIGNED) == 0) {
+ a.movzx(RETd, RET.r16());
+ } else {
+ a.movsx(RET, RET.r16());
+ }
+ break;
+ case 32:
+ address = x86::Mem(bin_base, tmp, 0, 0, 4);
+ if ((flags & BSF_LITTLE) != 0) {
+ /* Little-endian segment. */
+ if ((flags & BSF_SIGNED) == 0) {
+ a.mov(RETd, address);
+ } else {
+ a.movsxd(RET, address);
+ }
+ } else {
+ /* Big-endian segment. */
+ if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
+ a.movbe(RETd, address);
+ } else {
+ a.mov(RETd, address);
+ a.bswap(RETd);
+ }
+
+ if ((flags & BSF_SIGNED) != 0) {
+ a.movsxd(RET, RETd);
+ }
+ }
+
+ a.short_().jz(store);
+
+ a.bind(handle_unaligned);
+ a.add(bin_base, tmp);
+ address = x86::Mem(bin_base, -3, 8);
+ if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) {
+ a.movbe(RET, address);
+ } else {
+ a.mov(RET, address);
+ a.bswap(RET);
+ }
+ ASSERT(bin_offset == x86::rcx);
+ a.shl(RET, bin_offset.r8());
+ a.shr(RET, imm(8));
+
+ if ((flags & BSF_LITTLE) != 0) {
+ a.bswap(RETd);
+ }
+
+ if ((flags & BSF_SIGNED) == 0) {
+ a.mov(RETd, RETd);
+ } else {
+ a.movsxd(RET, RETd);
+ }
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+
+ a.bind(store);
+ a.shl(RET, imm(_TAG_IMMED1_SIZE));
+ a.or_(RET, imm(_TAG_IMMED1_SMALL));
+ mov_arg(Dst, RET);
+}
+
+void BeamModuleAssembler::emit_extract_integer(const x86::Gp bitdata,
+ const x86::Gp tmp,
+ Uint flags,
+ Uint bits,
+ const ArgRegister &Dst) {
+ if (bits == 0) {
+ /* Necessary for correctness when matching a zero-size
+ * signed segment.
+ */
+ mov_arg(Dst, make_small(0));
+ return;
+ }
+
+ Label big = a.newLabel();
+ Label done = a.newLabel();
+ Uint num_partial = bits % 8;
+ Uint num_complete = 8 * (bits / 8);
+
+ if (bits <= 8) {
+ /* Endian does not matter for values that fit in a byte. */
+ flags &= ~BSF_LITTLE;
+ }
+
+ if ((flags & BSF_LITTLE) == 0) {
+ /* Big-endian segment. */
+ a.mov(RET, bitdata);
+ } else if ((flags & BSF_LITTLE) != 0) {
+ /* Reverse endianness for this little-endian segment. */
+ if (num_partial == 0) {
+ a.mov(RET, bitdata);
+ a.bswap(RET);
+ if (bits < 64) {
+ a.shl(RET, imm(64 - num_complete));
+ }
+ } else {
+ Uint shifted_mask = ((1 << num_partial) - 1) << (8 - num_partial);
+ a.mov(tmp, bitdata);
+ a.shr(tmp, imm(64 - num_complete));
+ a.bswap(tmp);
+ a.shr(tmp, imm(num_partial));
+
+ a.mov(RET, bitdata);
+ a.rol(RET, imm(num_complete + 8));
+ a.and_(RETd, imm(shifted_mask));
+ a.ror(RET, imm(8));
+ a.or_(RET, tmp);
+ }
+ }
+
+ /* Now the extracted data is in RET. */
+ if (bits >= SMALL_BITS) {
+ /* Handle segments whose values might not fit in a small
+ * integer. */
+ Label small = a.newLabel();
+ comment("test whether this integer is a small");
+ if (bits < 64) {
+ if ((flags & BSF_SIGNED) == 0) {
+ /* Unsigned segment. */
+ a.shr(RET, imm(64 - bits));
+ } else {
+ /* Signed segment. */
+ a.sar(RET, imm(64 - bits));
+ }
+ }
+ a.mov(tmp, RET);
+ a.shr(tmp, imm(SMALL_BITS - 1));
+ if ((flags & BSF_SIGNED) == 0) {
+ /* Unsigned segment. */
+ a.jnz(big);
+ } else {
+ /* Signed segment. */
+ a.jz(small);
+ a.cmp(tmp.r32(), imm(_TAG_IMMED1_MASK << 1 | 1));
+ a.jnz(big);
+ }
+
+ comment("store extracted integer as a small");
+ a.bind(small);
+ a.shl(RET, imm(_TAG_IMMED1_SIZE));
+ a.or_(RET, imm(_TAG_IMMED1_SMALL));
+ a.short_().jmp(done);
+ } else {
+ /* This segment always fits in a small. */
+ comment("store extracted integer as a small");
+ if ((flags & BSF_SIGNED) == 0) {
+ /* Unsigned segment. */
+ a.shr(RET, imm(64 - bits - _TAG_IMMED1_SIZE));
+ } else {
+ /* Signed segment. */
+ a.sar(RET, imm(64 - bits - _TAG_IMMED1_SIZE));
+ }
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == (1 << _TAG_IMMED1_SIZE) - 1);
+ a.or_(RET, imm(_TAG_IMMED1_SMALL));
+ }
+
+ a.bind(big);
+ if (bits >= SMALL_BITS) {
+ comment("store extracted integer as a bignum");
+ if ((flags & BSF_SIGNED) == 0) {
+ /* Unsigned segment. */
+ a.mov(x86::qword_ptr(HTOP), make_pos_bignum_header(1));
+ a.mov(x86::qword_ptr(HTOP, sizeof(Eterm)), RET);
+ } else {
+ Label negative = a.newLabel();
+ Label sign_done = a.newLabel();
+
+ /* Signed segment. */
+ a.test(RET, RET);
+ a.short_().jl(negative);
+
+ a.mov(x86::qword_ptr(HTOP), make_pos_bignum_header(1));
+ a.short_().jmp(sign_done);
+
+ a.bind(negative);
+ a.mov(x86::qword_ptr(HTOP), make_neg_bignum_header(1));
+ a.neg(RET);
+
+ a.bind(sign_done);
+ a.mov(x86::qword_ptr(HTOP, sizeof(Eterm)), RET);
+ }
+ a.lea(RET, x86::qword_ptr(HTOP, TAG_PRIMARY_BOXED));
+ a.add(HTOP, imm(sizeof(Eterm[2])));
+ }
+
+ a.bind(done);
+ mov_arg(Dst, RET);
+}
+
+/*
+ * Clobbers: RET
+ */
+void BeamModuleAssembler::emit_extract_binary(const x86::Gp bitdata,
+ Uint bits,
+ const ArgRegister &Dst) {
+ Uint num_bytes = bits / 8;
+
+ a.lea(RET, x86::qword_ptr(HTOP, TAG_PRIMARY_BOXED));
+ mov_arg(Dst, RET);
+ a.mov(x86::qword_ptr(HTOP), header_heap_bin(num_bytes));
+ a.mov(x86::qword_ptr(HTOP, sizeof(Eterm)), imm(num_bytes));
+ a.mov(RET, bitdata);
+ a.bswap(RET);
+ if (num_bytes == 0) {
+ a.add(HTOP, imm(sizeof(Eterm[2])));
+ } else {
+ a.mov(x86::qword_ptr(HTOP, 2 * sizeof(Eterm)), RET);
+ a.add(HTOP, imm(sizeof(Eterm[3])));
+ }
+}
+
+static std::vector<BsmSegment> opt_bsm_segments(
+ const std::vector<BsmSegment> segments,
+ const ArgWord &Need,
+ const ArgWord &Live) {
+ std::vector<BsmSegment> segs;
+
+ Uint heap_need = Need.get();
+
+ /*
+ * First calculate the total number of heap words needed for
+ * bignums and binaries.
+ */
+ for (auto seg : segments) {
+ switch (seg.action) {
+ case BsmSegment::action::GET_INTEGER:
+ if (seg.size >= SMALL_BITS) {
+ heap_need += BIG_NEED_FOR_BITS(seg.size);
+ }
+ break;
+ case BsmSegment::action::GET_BINARY:
+ heap_need += heap_bin_size((seg.size + 7) / 8);
+ break;
+ case BsmSegment::action::GET_TAIL:
+ heap_need += EXTRACT_SUB_BIN_HEAP_NEED;
+ break;
+ default:
+ break;
+ }
+ }
+
+ int read_action_pos = -1;
+ int seg_index = 0;
+ int count = segments.size();
+
+ for (int i = 0; i < count; i++) {
+ auto seg = segments[i];
+ if (heap_need != 0 && seg.live.isWord()) {
+ BsmSegment s = seg;
+
+ read_action_pos = -1;
+ s.action = BsmSegment::action::TEST_HEAP;
+ s.size = heap_need;
+ segs.push_back(s);
+ heap_need = 0;
+ seg_index++;
+ }
+
+ switch (seg.action) {
+ case BsmSegment::action::GET_INTEGER:
+ case BsmSegment::action::GET_BINARY: {
+ bool is_common_size;
+ switch (seg.size) {
+ case 8:
+ case 16:
+ case 32:
+ is_common_size = true;
+ break;
+ default:
+ is_common_size = false;
+ break;
+ }
+
+ if (seg.size > 64) {
+ read_action_pos = -1;
+ } else if (seg.action == BsmSegment::action::GET_BINARY &&
+ seg.size % 8 != 0) {
+ read_action_pos = -1;
+ } else if ((seg.flags & BSF_LITTLE) != 0 && is_common_size) {
+ seg.action = BsmSegment::action::READ_INTEGER;
+ read_action_pos = -1;
+ } else if (read_action_pos < 0 &&
+ seg.action == BsmSegment::action::GET_INTEGER &&
+ is_common_size && i + 1 == count) {
+ seg.action = BsmSegment::action::READ_INTEGER;
+ read_action_pos = -1;
+ } else {
+ if ((seg.flags & BSF_LITTLE) != 0 || read_action_pos < 0 ||
+ seg.size + segs.at(read_action_pos).size > 64) {
+ BsmSegment s;
+
+ /* Create a new READ action. */
+ read_action_pos = seg_index;
+ s.action = BsmSegment::action::READ;
+ s.size = seg.size;
+ segs.push_back(s);
+ seg_index++;
+ } else {
+ /* Reuse previous READ action. */
+ segs.at(read_action_pos).size += seg.size;
+ }
+ switch (seg.action) {
+ case BsmSegment::action::GET_INTEGER:
+ seg.action = BsmSegment::action::EXTRACT_INTEGER;
+ break;
+ case BsmSegment::action::GET_BINARY:
+ seg.action = BsmSegment::action::EXTRACT_BINARY;
+ break;
+ default:
+ break;
+ }
+ }
+ segs.push_back(seg);
+ break;
+ }
+ case BsmSegment::action::EQ: {
+ if (read_action_pos < 0 ||
+ seg.size + segs.at(read_action_pos).size > 64) {
+ BsmSegment s;
+
+ /* Create a new READ action. */
+ read_action_pos = seg_index;
+ s.action = BsmSegment::action::READ;
+ s.size = seg.size;
+ segs.push_back(s);
+ seg_index++;
+ } else {
+ /* Reuse previous READ action. */
+ segs.at(read_action_pos).size += seg.size;
+ }
+ auto &prev = segs.back();
+ if (prev.action == BsmSegment::action::EQ &&
+ prev.size + seg.size <= 64) {
+ /* Coalesce with the previous EQ instruction. */
+ prev.size += seg.size;
+ prev.unit = prev.unit << seg.size | seg.unit;
+ seg_index--;
+ } else {
+ segs.push_back(seg);
+ }
+ break;
+ }
+ case BsmSegment::action::SKIP:
+ if (read_action_pos >= 0 &&
+ seg.size + segs.at(read_action_pos).size <= 64) {
+ segs.at(read_action_pos).size += seg.size;
+ seg.action = BsmSegment::action::DROP;
+ } else {
+ read_action_pos = -1;
+ }
+ segs.push_back(seg);
+ break;
+ default:
+ read_action_pos = -1;
+ segs.push_back(seg);
+ break;
+ }
+ seg_index++;
+ }
+
+ /* Handle a trailing test_heap instruction (for the
+ * i_bs_match_test_heap instruction). */
+ if (heap_need) {
+ BsmSegment seg;
+
+ seg.action = BsmSegment::action::TEST_HEAP;
+ seg.size = heap_need;
+ seg.live = Live;
+ segs.push_back(seg);
+ }
+ return segs;
+}
+
+UWord BeamModuleAssembler::bs_get_flags(const ArgVal &val) {
+ if (val.isNil()) {
+ return 0;
+ } else if (val.isLiteral()) {
+ Eterm term = beamfile_get_literal(beam, val.as<ArgLiteral>().get());
+ UWord flags = 0;
+
+ while (is_list(term)) {
+ Eterm *consp = list_val(term);
+ Eterm elem = CAR(consp);
+ switch (elem) {
+ case am_little:
+ case am_native:
+ flags |= BSF_LITTLE;
+ break;
+ case am_signed:
+ flags |= BSF_SIGNED;
+ break;
+ }
+ term = CDR(consp);
+ }
+ ASSERT(is_nil(term));
+ return flags;
+ } else if (val.isWord()) {
+ /* Originates from bs_get_integer2 instruction. */
+ return val.as<ArgWord>().get();
+ } else {
+ ASSERT(0); /* Should not happen. */
+ return 0;
+ }
+}
+
+void BeamModuleAssembler::emit_i_bs_match(ArgLabel const &Fail,
+ ArgRegister const &Ctx,
+ Span<ArgVal> const &List) {
+ emit_i_bs_match_test_heap(Fail, Ctx, ArgWord(0), ArgWord(0), List);
+}
+
+void BeamModuleAssembler::emit_i_bs_match_test_heap(ArgLabel const &Fail,
+ ArgRegister const &Ctx,
+ ArgWord const &Need,
+ ArgWord const &Live,
+ Span<ArgVal> const &List) {
+ const int orig_offset = offsetof(ErlBinMatchState, mb.orig);
+ const int base_offset = offsetof(ErlBinMatchState, mb.base);
+ const int position_offset = offsetof(ErlBinMatchState, mb.offset);
+ const int size_offset = offsetof(ErlBinMatchState, mb.size);
+
+ std::vector<BsmSegment> segments;
+
+ auto current = List.begin();
+ auto end = List.begin() + List.size();
+
+ while (current < end) {
+ auto cmd = current++->as<ArgImmed>().get();
+ BsmSegment seg;
+
+ switch (cmd) {
+ case am_ensure_at_least: {
+ seg.action = BsmSegment::action::ENSURE_AT_LEAST;
+ seg.size = current[0].as<ArgWord>().get();
+ seg.unit = current[1].as<ArgWord>().get();
+ current += 2;
+ break;
+ }
+ case am_ensure_exactly: {
+ seg.action = BsmSegment::action::ENSURE_EXACTLY;
+ seg.size = current[0].as<ArgWord>().get();
+ current += 1;
+ break;
+ }
+ case am_binary:
+ case am_integer: {
+ auto size = current[2].as<ArgWord>().get();
+ auto unit = current[3].as<ArgWord>().get();
+
+ switch (cmd) {
+ case am_integer:
+ seg.action = BsmSegment::action::GET_INTEGER;
+ break;
+ case am_binary:
+ seg.action = BsmSegment::action::GET_BINARY;
+ break;
+ }
+
+ seg.live = current[0];
+ seg.size = size * unit;
+ seg.unit = unit;
+ seg.flags = bs_get_flags(current[1]);
+ seg.dst = current[4].as<ArgRegister>();
+ current += 5;
+ break;
+ }
+ case am_get_tail: {
+ seg.action = BsmSegment::action::GET_TAIL;
+ seg.live = current[0].as<ArgWord>();
+ seg.dst = current[2].as<ArgRegister>();
+ current += 3;
+ break;
+ }
+ case am_skip: {
+ seg.action = BsmSegment::action::SKIP;
+ seg.size = current[0].as<ArgWord>().get();
+ seg.flags = 0;
+ current += 1;
+ break;
+ }
+ case am_Eq: {
+ seg.action = BsmSegment::action::EQ;
+ seg.live = current[0];
+ seg.size = current[1].as<ArgWord>().get();
+ seg.unit = current[2].as<ArgWord>().get();
+ current += 3;
+ break;
+ }
+ default:
+ abort();
+ break;
+ }
+ segments.push_back(seg);
+ }
+
+ segments = opt_bsm_segments(segments, Need, Live);
+
+ /* Constraints:
+ *
+ * bin_position must be RCX because only CL can be used for
+ * a variable shift without using the SHLX instruction from BMI2.
+ */
+#ifdef WIN32
+ const x86::Gp bin_position = ARG1;
+ const x86::Gp bitdata = ARG2;
+ const x86::Gp bin_base = ARG3;
+ const x86::Gp ctx = ARG4;
+#else
+ const x86::Gp bin_position = ARG4;
+ const x86::Gp bitdata = ARG3;
+ const x86::Gp bin_base = ARG1;
+ const x86::Gp ctx = ARG2;
+#endif
+ ASSERT(bin_position == x86::rcx);
+ const x86::Gp tmp = ARG5;
+
+ bool is_ctx_valid = false;
+ bool is_position_valid = false;
+ bool next_instr_clobbers = false;
+ int count = segments.size();
+
+ for (int i = 0; i < count; i++) {
+ auto seg = segments[i];
+
+ /* Find out whether the next sub instruction clobbers
+ * registers or is the last. */
+ next_instr_clobbers =
+ i == count - 1 ||
+ (i < count - 1 &&
+ segments[i + 1].action == BsmSegment::action::TEST_HEAP);
+
+ switch (seg.action) {
+ case BsmSegment::action::ENSURE_AT_LEAST: {
+ auto size = seg.size;
+ auto unit = seg.unit;
+ comment("ensure_at_least %ld %ld", size, seg.unit);
+ mov_arg(ctx, Ctx);
+ if (unit == 1) {
+ a.mov(bin_position, emit_boxed_val(ctx, position_offset));
+ a.lea(RET, qword_ptr(bin_position, size));
+ a.cmp(RET, emit_boxed_val(ctx, size_offset));
+ a.ja(resolve_beam_label(Fail));
+ } else if (size == 0 && next_instr_clobbers) {
+ a.mov(RET, emit_boxed_val(ctx, size_offset));
+ a.sub(RET, emit_boxed_val(ctx, position_offset));
+ is_ctx_valid = is_position_valid = false;
+ } else {
+ a.mov(RET, emit_boxed_val(ctx, size_offset));
+ a.mov(bin_position, emit_boxed_val(ctx, position_offset));
+ a.sub(RET, bin_position);
+ cmp(RET, size, tmp);
+ a.jl(resolve_beam_label(Fail));
+ }
+
+ is_ctx_valid = is_position_valid = true;
+
+ if (unit != 1) {
+ if (size % unit != 0) {
+ sub(RET, size, tmp);
+ }
+
+ if ((unit & (unit - 1))) {
+ /* Clobbers ARG3 */
+ a.cqo();
+ mov_imm(tmp, unit);
+ a.div(tmp);
+ a.test(x86::rdx, x86::rdx);
+ is_ctx_valid = is_position_valid = false;
+ } else {
+ a.test(RETb, imm(unit - 1));
+ }
+ a.jnz(resolve_beam_label(Fail));
+ }
+ break;
+ }
+ case BsmSegment::action::ENSURE_EXACTLY: {
+ auto size = seg.size;
+ comment("ensure_exactly %ld", size);
+
+ mov_arg(ctx, Ctx);
+ a.mov(RET, emit_boxed_val(ctx, size_offset));
+ if (next_instr_clobbers) {
+ a.sub(RET, emit_boxed_val(ctx, position_offset));
+ is_ctx_valid = is_position_valid = false;
+ } else {
+ a.mov(bin_position, emit_boxed_val(ctx, position_offset));
+ a.sub(RET, bin_position);
+ is_ctx_valid = is_position_valid = true;
+ }
+ if (size != 0) {
+ cmp(RET, size, tmp);
+ }
+ a.jne(resolve_beam_label(Fail));
+ break;
+ }
+ case BsmSegment::action::EQ: {
+ comment("=:= %ld %ld", seg.size, seg.unit);
+ auto bits = seg.size;
+ x86::Gp extract_reg;
+
+ if (next_instr_clobbers) {
+ extract_reg = bitdata;
+ } else {
+ extract_reg = RET;
+ a.mov(extract_reg, bitdata);
+ }
+ if (bits != 0 && bits != 64) {
+ a.shr(extract_reg, imm(64 - bits));
+ }
+
+ if (seg.size <= 32) {
+ cmp(extract_reg.r32(), seg.unit, tmp);
+ } else {
+ cmp(extract_reg, seg.unit, tmp);
+ }
+
+ a.jne(resolve_beam_label(Fail));
+
+ if (!next_instr_clobbers && bits != 0 && bits != 64) {
+ a.shl(bitdata, imm(bits));
+ }
+
+ /* bin_position is clobbered. */
+ is_position_valid = false;
+ break;
+ }
+ case BsmSegment::action::TEST_HEAP: {
+ comment("test_heap %ld", seg.size);
+ emit_gc_test(ArgWord(0), ArgWord(seg.size), seg.live);
+ is_ctx_valid = is_position_valid = false;
+ break;
+ }
+ case BsmSegment::action::READ: {
+ comment("read %ld", seg.size);
+ if (seg.size == 0) {
+ comment("(nothing to do)");
+ } else {
+ if (!is_ctx_valid) {
+ mov_arg(ctx, Ctx);
+ is_ctx_valid = true;
+ }
+ if (!is_position_valid) {
+ a.mov(bin_position, emit_boxed_val(ctx, position_offset));
+ is_position_valid = true;
+ }
+ a.mov(bin_base, emit_boxed_val(ctx, base_offset));
+ a.add(emit_boxed_val(ctx, position_offset), imm(seg.size));
+
+ emit_read_bits(seg.size, bin_base, bin_position, bitdata);
+ }
+
+ is_position_valid = false;
+ break;
+ }
+ case BsmSegment::action::EXTRACT_BINARY: {
+ auto bits = seg.size;
+ auto Dst = seg.dst;
+
+ comment("extract binary %ld", bits);
+ emit_extract_binary(bitdata, bits, Dst);
+ if (!next_instr_clobbers && bits != 0 && bits != 64) {
+ a.shl(bitdata, imm(bits));
+ }
+ break;
+ }
+ case BsmSegment::action::EXTRACT_INTEGER: {
+ auto bits = seg.size;
+ auto flags = seg.flags;
+ auto Dst = seg.dst;
+
+ comment("extract integer %ld", bits);
+ if (next_instr_clobbers && flags == 0 && bits < SMALL_BITS) {
+ a.shr(bitdata, imm(64 - bits - _TAG_IMMED1_SIZE));
+ a.or_(bitdata, imm(_TAG_IMMED1_SMALL));
+ mov_arg(Dst, bitdata);
+ } else {
+ emit_extract_integer(bitdata, tmp, flags, bits, Dst);
+ if (!next_instr_clobbers && bits != 0 && bits != 64) {
+ a.shl(bitdata, imm(bits));
+ }
+ }
+
+ /* bin_position is clobbered. */
+ is_position_valid = false;
+ break;
+ }
+ case BsmSegment::action::READ_INTEGER: {
+ auto bits = seg.size;
+ auto flags = seg.flags;
+ auto Dst = seg.dst;
+
+ comment("read integer %ld", bits);
+ if (!is_ctx_valid) {
+ mov_arg(ctx, Ctx);
+ is_ctx_valid = true;
+ }
+ if (!is_position_valid) {
+ a.mov(bin_position, emit_boxed_val(ctx, position_offset));
+ is_position_valid = true;
+ }
+
+ a.mov(bin_base, emit_boxed_val(ctx, base_offset));
+ a.add(emit_boxed_val(ctx, position_offset), imm(seg.size));
+ emit_read_integer(bin_base, bin_position, tmp, flags, bits, Dst);
+
+ is_position_valid = false;
+ break;
+ }
+ case BsmSegment::action::GET_INTEGER: {
+ Uint flags = seg.flags;
+ auto bits = seg.size;
+ auto Dst = seg.dst;
+
+ comment("get integer %ld", bits);
+ if (!is_ctx_valid) {
+ mov_arg(ctx, Ctx);
+ }
+
+ a.lea(ARG4, emit_boxed_val(ctx, offsetof(ErlBinMatchState, mb)));
+
+ if (bits >= SMALL_BITS) {
+ emit_enter_runtime<Update::eReductions |
+ Update::eHeapOnlyAlloc>();
+ } else {
+ emit_enter_runtime();
+ }
+
+ a.mov(ARG1, c_p);
+ a.mov(ARG2, bits);
+ a.mov(ARG3, flags);
+ /* ARG4 set above */
+ runtime_call<4>(erts_bs_get_integer_2);
+
+ if (bits >= SMALL_BITS) {
+ emit_leave_runtime<Update::eReductions |
+ Update::eHeapOnlyAlloc>();
+ } else {
+ emit_leave_runtime();
+ }
+
+ mov_arg(Dst, RET);
+
+ is_ctx_valid = is_position_valid = false;
+ break;
+ }
+ case BsmSegment::action::GET_BINARY: {
+ comment("get binary %ld", seg.size);
+ if (is_ctx_valid) {
+ a.mov(RET, ctx);
+ } else {
+ mov_arg(RET, Ctx);
+ }
+ emit_enter_runtime<Update::eHeapOnlyAlloc>();
+ a.lea(ARG1, x86::qword_ptr(c_p, offsetof(Process, htop)));
+ a.mov(ARG2, emit_boxed_val(RET, orig_offset));
+ a.mov(ARG3, emit_boxed_val(RET, base_offset));
+ a.mov(ARG4, emit_boxed_val(RET, position_offset));
+ mov_imm(ARG5, seg.size);
+ a.add(emit_boxed_val(RET, position_offset), ARG5);
+
+ runtime_call<5>(erts_extract_sub_binary);
+
+ emit_leave_runtime<Update::eHeapOnlyAlloc>();
+ mov_arg(seg.dst, RET);
+
+ is_ctx_valid = is_position_valid = false;
+ break;
+ }
+ case BsmSegment::action::GET_TAIL: {
+ comment("get_tail");
+ if (is_ctx_valid) {
+ a.mov(ARG1, ctx);
+ } else {
+ mov_arg(ARG1, Ctx);
+ }
+ safe_fragment_call(ga->get_bs_get_tail_shared());
+ mov_arg(seg.dst, RET);
+ is_ctx_valid = is_position_valid = false;
+ break;
+ }
+ case BsmSegment::action::SKIP: {
+ comment("skip %ld", seg.size);
+ if (!is_ctx_valid) {
+ mov_arg(ctx, Ctx);
+ is_ctx_valid = true;
+ }
+ /* The compiler limits the size of any segment in a bs_match
+ * instruction to 24 bits. */
+ ASSERT((seg.size >> 24) == 0);
+ a.add(emit_boxed_val(ctx, position_offset), imm(seg.size));
+ is_position_valid = false;
+ break;
+ }
+ case BsmSegment::action::DROP:
+ auto bits = seg.size;
+ comment("drop %ld", bits);
+ if (bits != 0 && bits != 64) {
+ a.shl(bitdata, imm(bits));
+ }
+ break;
+ }
+ }
+}
diff --git a/erts/emulator/beam/jit/x86/instr_call.cpp b/erts/emulator/beam/jit/x86/instr_call.cpp
index 8cf676737f..367e10e294 100644
--- a/erts/emulator/beam/jit/x86/instr_call.cpp
+++ b/erts/emulator/beam/jit/x86/instr_call.cpp
@@ -79,11 +79,11 @@ void BeamModuleAssembler::emit_i_call_only(const ArgLabel &CallDest) {
a.jmp(resolve_beam_label(CallDest));
}
-/* Handles save_calls. Export entry is in RET.
+/* Handles save_calls for export entries. Export entry is in RET.
*
* When the active code index is ERTS_SAVE_CALLS_CODE_IX, all remote calls will
* land here. */
-void BeamGlobalAssembler::emit_dispatch_save_calls() {
+void BeamGlobalAssembler::emit_dispatch_save_calls_export() {
a.mov(TMP_MEM1q, RET);
emit_enter_runtime();
@@ -136,7 +136,7 @@ x86::Mem BeamModuleAssembler::emit_variable_apply(bool includeI) {
align_erlang_cp();
a.bind(entry);
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
@@ -151,7 +151,7 @@ x86::Mem BeamModuleAssembler::emit_variable_apply(bool includeI) {
runtime_call<4>(apply);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
a.test(RET, RET);
a.short_().jne(dispatch);
@@ -187,7 +187,7 @@ x86::Mem BeamModuleAssembler::emit_fixed_apply(const ArgWord &Arity,
mov_arg(ARG3, Arity);
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
@@ -202,7 +202,7 @@ x86::Mem BeamModuleAssembler::emit_fixed_apply(const ArgWord &Arity,
runtime_call<5>(fixed_apply);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
a.test(RET, RET);
a.short_().jne(dispatch);
diff --git a/erts/emulator/beam/jit/x86/instr_common.cpp b/erts/emulator/beam/jit/x86/instr_common.cpp
index 6a64db52b2..99e67c40b2 100644
--- a/erts/emulator/beam/jit/x86/instr_common.cpp
+++ b/erts/emulator/beam/jit/x86/instr_common.cpp
@@ -81,6 +81,7 @@
*/
#include <algorithm>
+#include <numeric>
#include "beam_asm.hpp"
extern "C"
@@ -90,6 +91,7 @@ extern "C"
#include "beam_catches.h"
#include "beam_common.h"
#include "code_ix.h"
+#include "erl_binary.h"
}
using namespace asmjit;
@@ -103,20 +105,44 @@ void BeamModuleAssembler::emit_error(int reason) {
void BeamModuleAssembler::emit_gc_test_preserve(const ArgWord &Need,
const ArgWord &Live,
- x86::Gp term) {
+ const ArgSource &Preserve,
+ x86::Gp preserve_reg) {
const int32_t bytes_needed = (Need.get() + S_RESERVED) * sizeof(Eterm);
Label after_gc_check = a.newLabel();
- ASSERT(term != ARG3);
+ ASSERT(preserve_reg != ARG3);
+
+#ifdef DEBUG
+ comment("(debug: fill dead X registers with garbage)");
+ const x86::Gp garbage_reg = ARG3;
+ mov_imm(garbage_reg, ERTS_HOLE_MARKER);
+ if (!(Preserve.isXRegister() &&
+ Preserve.as<ArgXRegister>().get() >= Live.get())) {
+ mov_arg(ArgXRegister(Live.get()), garbage_reg);
+ mov_arg(ArgXRegister(Live.get() + 1), garbage_reg);
+ } else {
+ mov_arg(ArgXRegister(Live.get() + 1), garbage_reg);
+ mov_arg(ArgXRegister(Live.get() + 2), garbage_reg);
+ }
+#endif
a.lea(ARG3, x86::qword_ptr(HTOP, bytes_needed));
a.cmp(ARG3, E);
a.short_().jbe(after_gc_check);
- a.mov(getXRef(Live.get()), term);
- mov_imm(ARG4, Live.get() + 1);
- fragment_call(ga->get_garbage_collect());
- a.mov(term, getXRef(Live.get()));
+ /* We don't need to stash the preserved term if it's currently live, making
+ * the code slightly shorter. */
+ if (!(Preserve.isXRegister() &&
+ Preserve.as<ArgXRegister>().get() >= Live.get())) {
+ mov_imm(ARG4, Live.get());
+ fragment_call(ga->get_garbage_collect());
+ mov_arg(preserve_reg, Preserve);
+ } else {
+ a.mov(getXRef(Live.get()), preserve_reg);
+ mov_imm(ARG4, Live.get() + 1);
+ fragment_call(ga->get_garbage_collect());
+ a.mov(preserve_reg, getXRef(Live.get()));
+ }
a.bind(after_gc_check);
}
@@ -128,6 +154,13 @@ void BeamModuleAssembler::emit_gc_test(const ArgWord &Ns,
(Ns.get() + Nh.get() + S_RESERVED) * sizeof(Eterm);
Label after_gc_check = a.newLabel();
+#ifdef DEBUG
+ comment("(debug: fill dead X registers with garbage)");
+ mov_imm(ARG4, ERTS_HOLE_MARKER);
+ mov_arg(ArgXRegister(Live.get()), ARG4);
+ mov_arg(ArgXRegister(Live.get() + 1), ARG4);
+#endif
+
a.lea(ARG3, x86::qword_ptr(HTOP, bytes_needed));
a.cmp(ARG3, E);
a.short_().jbe(after_gc_check);
@@ -233,7 +266,7 @@ void BeamModuleAssembler::emit_normal_exit() {
/* This is implicitly global; it does not normally appear in modules and
* doesn't require size optimization. */
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
emit_proc_lc_unrequire();
a.mov(x86::qword_ptr(c_p, offsetof(Process, freason)), imm(EXC_NORMAL));
@@ -243,7 +276,7 @@ void BeamModuleAssembler::emit_normal_exit() {
runtime_call<2>(erts_do_exit_process);
emit_proc_lc_require();
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
a.jmp(resolve_fragment(ga->get_do_schedule()));
}
@@ -252,14 +285,14 @@ void BeamModuleAssembler::emit_continue_exit() {
/* This is implicitly global; it does not normally appear in modules and
* doesn't require size optimization. */
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
emit_proc_lc_unrequire();
a.mov(ARG1, c_p);
runtime_call<1>(erts_continue_exit_process);
emit_proc_lc_require();
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
a.jmp(resolve_fragment(ga->get_do_schedule()));
}
@@ -274,8 +307,8 @@ void BeamModuleAssembler::emit_get_list(const x86::Gp src,
comment("(moving head and tail together)");
x86::Mem dst_ptr = getArgRef(Hd, 16);
x86::Mem src_ptr = getCARRef(boxed_ptr, 16);
- a.movups(x86::xmm0, src_ptr);
- a.movups(dst_ptr, x86::xmm0);
+ vmovups(x86::xmm0, src_ptr);
+ vmovups(dst_ptr, x86::xmm0);
break;
}
case ArgVal::Relation::reverse_consecutive: {
@@ -439,6 +472,21 @@ void BeamModuleAssembler::emit_i_get_tuple_element(const ArgSource &Src,
mov_arg(Dst, ARG1);
}
+void BeamModuleAssembler::emit_get_tuple_element_swap(
+ const ArgSource &Src,
+ const ArgWord &Element,
+ const ArgRegister &Dst,
+ const ArgRegister &OtherDst) {
+#ifdef DEBUG
+ emit_tuple_assertion(Src, ARG2);
+#endif
+
+ mov_arg(ARG1, OtherDst);
+ a.mov(ARG3, emit_boxed_val(ARG2, Element.get()));
+ mov_arg(Dst, ARG1);
+ mov_arg(OtherDst, ARG3);
+}
+
/* Fetch two consecutive tuple elements from the tuple pointed to by
* the boxed pointer in ARG2. */
void BeamModuleAssembler::emit_get_two_tuple_elements(const ArgSource &Src,
@@ -455,8 +503,8 @@ void BeamModuleAssembler::emit_get_two_tuple_elements(const ArgSource &Src,
switch (ArgVal::memory_relation(Dst1, Dst2)) {
case ArgVal::Relation::consecutive: {
x86::Mem dst_ptr = getArgRef(Dst1, 16);
- a.movups(x86::xmm0, element_ptr);
- a.movups(dst_ptr, x86::xmm0);
+ vmovups(x86::xmm0, element_ptr);
+ vmovups(dst_ptr, x86::xmm0);
break;
}
case ArgVal::Relation::reverse_consecutive: {
@@ -590,8 +638,8 @@ void BeamModuleAssembler::emit_move_two_words(const ArgSource &Src1,
switch (ArgVal::memory_relation(Dst1, Dst2)) {
case ArgVal::Relation::consecutive: {
x86::Mem dst_ptr = getArgRef(Dst1, 16);
- a.movups(x86::xmm0, src_ptr);
- a.movups(dst_ptr, x86::xmm0);
+ vmovups(x86::xmm0, src_ptr);
+ vmovups(dst_ptr, x86::xmm0);
break;
}
case ArgVal::Relation::reverse_consecutive: {
@@ -659,8 +707,8 @@ void BeamModuleAssembler::emit_put_cons(const ArgSource &Hd,
x86::Mem src_ptr = getArgRef(Hd, 16);
x86::Mem dst_ptr = x86::xmmword_ptr(HTOP, 0);
comment("(put head and tail together)");
- a.movups(x86::xmm0, src_ptr);
- a.movups(dst_ptr, x86::xmm0);
+ vmovups(x86::xmm0, src_ptr);
+ vmovups(dst_ptr, x86::xmm0);
break;
}
case ArgVal::Relation::reverse_consecutive: {
@@ -721,8 +769,8 @@ void BeamModuleAssembler::emit_put_tuple2(const ArgRegister &Dst,
comment("(moving two elements at once)");
dst_ptr.setSize(16);
- a.movups(x86::xmm0, src_ptr);
- a.movups(dst_ptr, x86::xmm0);
+ vmovups(x86::xmm0, src_ptr);
+ vmovups(dst_ptr, x86::xmm0);
i++;
break;
}
@@ -760,6 +808,70 @@ void BeamModuleAssembler::emit_self(const ArgRegister &Dst) {
mov_arg(Dst, ARG1);
}
+void BeamModuleAssembler::emit_update_record(const ArgAtom &Hint,
+ const ArgWord &TupleSize,
+ const ArgSource &Src,
+ const ArgRegister &Dst,
+ const ArgWord &UpdateCount,
+ const Span<ArgVal> &updates) {
+ size_t copy_index = 0, size_on_heap = TupleSize.get() + 1;
+ Label next = a.newLabel();
+
+ x86::Gp ptr_val;
+
+ ASSERT(UpdateCount.get() == updates.size());
+ ASSERT((UpdateCount.get() % 2) == 0);
+
+ ASSERT(size_on_heap > 2);
+
+ mov_arg(RET, Src);
+
+ /* Setting a field to the same value is pretty common, so we'll check for
+ * that since it's vastly cheaper than copying if we're right, and doesn't
+ * cost much if we're wrong. */
+ if (Hint.get() == am_reuse && updates.size() == 2) {
+ const auto next_index = updates[0].as<ArgWord>().get();
+ const auto &next_value = updates[1].as<ArgSource>();
+
+ a.mov(ARG1, RET);
+ ptr_val = emit_ptr_val(ARG1, ARG1);
+ cmp_arg(emit_boxed_val(ptr_val, next_index * sizeof(Eterm)),
+ next_value,
+ ARG2);
+ a.je(next);
+ }
+
+ ptr_val = emit_ptr_val(RET, RET);
+
+ for (size_t i = 0; i < updates.size(); i += 2) {
+ const auto next_index = updates[i].as<ArgWord>().get();
+ const auto &next_value = updates[i + 1].as<ArgSource>();
+
+ ASSERT(next_index > 0 && next_index >= copy_index);
+
+ emit_copy_words(emit_boxed_val(ptr_val, copy_index * sizeof(Eterm)),
+ x86::qword_ptr(HTOP, copy_index * sizeof(Eterm)),
+ next_index - copy_index,
+ ARG1);
+
+ mov_arg(x86::qword_ptr(HTOP, next_index * sizeof(Eterm)),
+ next_value,
+ ARG1);
+ copy_index = next_index + 1;
+ }
+
+ emit_copy_words(emit_boxed_val(ptr_val, copy_index * sizeof(Eterm)),
+ x86::qword_ptr(HTOP, copy_index * sizeof(Eterm)),
+ size_on_heap - copy_index,
+ ARG1);
+
+ a.lea(RET, x86::qword_ptr(HTOP, TAG_PRIMARY_BOXED));
+ a.add(HTOP, imm(size_on_heap * sizeof(Eterm)));
+
+ a.bind(next);
+ mov_arg(Dst, RET);
+}
+
void BeamModuleAssembler::emit_set_tuple_element(const ArgSource &Element,
const ArgRegister &Tuple,
const ArgWord &Offset) {
@@ -785,7 +897,7 @@ void BeamModuleAssembler::emit_is_atom(const ArgLabel &Fail,
const ArgSource &Src) {
mov_arg(RET, Src);
- if (always_one_of(Src, BEAM_TYPE_ATOM | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::Atom, BeamTypeId::AlwaysBoxed>(Src)) {
comment("simplified atom test since all other types are boxed");
a.test(RETb, imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_BOXED));
a.je(resolve_beam_label(Fail));
@@ -806,55 +918,49 @@ void BeamModuleAssembler::emit_is_boolean(const ArgLabel &Fail,
mov_arg(ARG1, Src);
- a.and_(ARG1, imm(~(am_true & ~_TAG_IMMED1_MASK)));
+ a.and_(ARG1, imm(~(am_true & ~_TAG_IMMED2_MASK)));
a.cmp(ARG1, imm(am_false));
a.jne(resolve_beam_label(Fail));
}
-x86::Gp BeamModuleAssembler::emit_is_binary(const ArgLabel &Fail,
- const ArgSource &Src,
- Label next,
- Label subbin) {
+void BeamModuleAssembler::emit_is_binary(const ArgLabel &Fail,
+ const ArgSource &Src) {
+ Label is_binary = a.newLabel(), next = a.newLabel();
+
mov_arg(ARG1, Src);
emit_is_boxed(resolve_beam_label(Fail), Src, ARG1);
x86::Gp boxed_ptr = emit_ptr_val(ARG1, ARG1);
- a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
- a.and_(RETb, imm(_TAG_HEADER_MASK));
- a.cmp(RETb, imm(_TAG_HEADER_SUB_BIN));
- a.short_().je(subbin);
-
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_BITSTRING) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Bitstring) {
+ const auto diff_mask = _TAG_HEADER_SUB_BIN - _TAG_HEADER_REFC_BIN;
+ ERTS_CT_ASSERT((_TAG_HEADER_SUB_BIN & diff_mask) != 0 &&
+ (_TAG_HEADER_REFC_BIN & diff_mask) == 0 &&
+ (_TAG_HEADER_HEAP_BIN & diff_mask) == 0);
comment("simplified binary test since source is always a bitstring "
"when boxed");
+ a.test(emit_boxed_val(boxed_ptr, 0, 1), diff_mask);
+ a.short_().je(next);
} else {
- ERTS_CT_ASSERT(_TAG_HEADER_REFC_BIN + 4 == _TAG_HEADER_HEAP_BIN);
- a.and_(RETb, imm(~4));
- a.cmp(RETb, imm(_TAG_HEADER_REFC_BIN));
- a.jne(resolve_beam_label(Fail));
+ a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
+ a.and_(RETb, imm(_TAG_HEADER_MASK));
+ a.cmp(RETb, imm(_TAG_HEADER_SUB_BIN));
+ a.short_().jne(is_binary);
}
- a.short_().jmp(next);
-
- return boxed_ptr;
-}
-
-void BeamModuleAssembler::emit_is_binary(const ArgLabel &Fail,
- const ArgSource &Src) {
- Label next = a.newLabel(), subbin = a.newLabel();
- x86::Gp boxed_ptr;
-
- boxed_ptr = emit_is_binary(Fail, Src, next, subbin);
+ /* This is a sub binary. */
+ a.cmp(emit_boxed_val(boxed_ptr, offsetof(ErlSubBin, bitsize), sizeof(byte)),
+ imm(0));
+ a.jne(resolve_beam_label(Fail));
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) != BeamTypeId::Bitstring) {
+ a.short_().jmp(next);
+ }
- a.bind(subbin);
- {
- /* emit_is_binary has already removed the literal tag from Src, if
- * applicable. */
- a.cmp(emit_boxed_val(boxed_ptr,
- offsetof(ErlSubBin, bitsize),
- sizeof(byte)),
- imm(0));
+ a.bind(is_binary);
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) != BeamTypeId::Bitstring) {
+ ERTS_CT_ASSERT(_TAG_HEADER_REFC_BIN + 4 == _TAG_HEADER_HEAP_BIN);
+ a.and_(RETb, imm(~4));
+ a.cmp(RETb, imm(_TAG_HEADER_REFC_BIN));
a.jne(resolve_beam_label(Fail));
}
@@ -863,11 +969,19 @@ void BeamModuleAssembler::emit_is_binary(const ArgLabel &Fail,
void BeamModuleAssembler::emit_is_bitstring(const ArgLabel &Fail,
const ArgSource &Src) {
- Label next = a.newLabel();
+ mov_arg(ARG1, Src);
- emit_is_binary(Fail, Src, next, next);
+ emit_is_boxed(resolve_beam_label(Fail), Src, ARG1);
- a.bind(next);
+ x86::Gp boxed_ptr = emit_ptr_val(ARG1, ARG1);
+ a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
+
+ const auto mask = _HEADER_SUBTAG_MASK - _BINARY_XXX_MASK;
+ ERTS_CT_ASSERT(TAG_PRIMARY_HEADER == 0);
+ ERTS_CT_ASSERT(_TAG_HEADER_REFC_BIN == (_TAG_HEADER_REFC_BIN & mask));
+ a.and_(RETb, imm(mask));
+ a.cmp(RETb, imm(_TAG_HEADER_REFC_BIN));
+ a.jne(resolve_beam_label(Fail));
}
void BeamModuleAssembler::emit_is_float(const ArgLabel &Fail,
@@ -876,7 +990,7 @@ void BeamModuleAssembler::emit_is_float(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail), Src, ARG1);
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_FLOAT) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Float) {
comment("skipped header test since we know it's a float when boxed");
} else {
x86::Gp boxed_ptr = emit_ptr_val(ARG1, ARG1);
@@ -891,7 +1005,7 @@ void BeamModuleAssembler::emit_is_function(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail), Src, RET);
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_FUN) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Fun) {
comment("skipped header test since we know it's a fun when boxed");
} else {
x86::Gp boxed_ptr = emit_ptr_val(RET, RET);
@@ -934,7 +1048,7 @@ void BeamModuleAssembler::emit_is_function2(const ArgLabel &Fail,
x86::Gp boxed_ptr = emit_ptr_val(ARG1, ARG1);
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_FUN) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Fun) {
comment("skipped header test since we know it's a fun when boxed");
} else {
a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
@@ -963,7 +1077,7 @@ void BeamModuleAssembler::emit_is_integer(const ArgLabel &Fail,
mov_arg(ARG1, Src);
- if (always_one_of(Src, BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(Src)) {
comment("simplified small test since all other types are boxed");
emit_is_boxed(next, Src, ARG1);
} else {
@@ -975,7 +1089,7 @@ void BeamModuleAssembler::emit_is_integer(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail), Src, RET);
}
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_INTEGER) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Integer) {
comment("skipped header test since we know it's a bignum when "
"boxed");
} else {
@@ -1011,7 +1125,7 @@ void BeamModuleAssembler::emit_is_map(const ArgLabel &Fail,
/* As an optimization for the `error | #{}` case, skip checking the header
* word when we know that the only possible boxed type is a map. */
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_MAP) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Map) {
comment("skipped header test since we know it's a map when boxed");
} else {
x86::Gp boxed_ptr = emit_ptr_val(RET, RET);
@@ -1024,7 +1138,7 @@ void BeamModuleAssembler::emit_is_map(const ArgLabel &Fail,
void BeamModuleAssembler::emit_is_nil(const ArgLabel &Fail,
const ArgRegister &Src) {
- a.cmp(getArgRef(Src), imm(NIL));
+ a.cmp(getArgRef(Src, 1), imm(NIL));
a.jne(resolve_beam_label(Fail));
}
@@ -1035,7 +1149,7 @@ void BeamModuleAssembler::emit_is_number(const ArgLabel &Fail,
mov_arg(ARG1, Src);
- if (always_one_of(Src, BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(Src)) {
comment("simplified small test test since all other types are boxed");
emit_is_boxed(next, Src, ARG1);
} else {
@@ -1048,8 +1162,7 @@ void BeamModuleAssembler::emit_is_number(const ArgLabel &Fail,
emit_is_boxed(fail, Src, RET);
}
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) ==
- (BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Number) {
comment("skipped header test since we know it's a number when boxed");
} else {
x86::Gp boxed_ptr = emit_ptr_val(ARG1, ARG1);
@@ -1073,7 +1186,7 @@ void BeamModuleAssembler::emit_is_pid(const ArgLabel &Fail,
mov_arg(ARG1, Src);
- if (always_one_of(Src, BEAM_TYPE_PID | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::Pid, BeamTypeId::AlwaysBoxed>(Src)) {
comment("simplified local pid test since all other types are boxed");
emit_is_boxed(next, Src, ARG1);
} else {
@@ -1086,7 +1199,7 @@ void BeamModuleAssembler::emit_is_pid(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail), Src, RET);
}
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_PID) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Pid) {
comment("skipped header test since we know it's a pid when boxed");
} else {
x86::Gp boxed_ptr = emit_ptr_val(ARG1, ARG1);
@@ -1105,7 +1218,7 @@ void BeamModuleAssembler::emit_is_port(const ArgLabel &Fail,
mov_arg(ARG1, Src);
- if (always_one_of(Src, BEAM_TYPE_PORT | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ if (always_one_of<BeamTypeId::Port, BeamTypeId::AlwaysBoxed>(Src)) {
comment("simplified local port test since all other types are boxed");
emit_is_boxed(next, Src, ARG1);
} else {
@@ -1118,7 +1231,7 @@ void BeamModuleAssembler::emit_is_port(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail), Src, RET);
}
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_PORT) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Port) {
comment("skipped header test since we know it's a port when boxed");
} else {
x86::Gp boxed_ptr = emit_ptr_val(ARG1, ARG1);
@@ -1137,7 +1250,7 @@ void BeamModuleAssembler::emit_is_reference(const ArgLabel &Fail,
emit_is_boxed(resolve_beam_label(Fail), Src, RET);
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_REFERENCE) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Reference) {
comment("skipped header test since we know it's a ref when boxed");
} else {
Label next = a.newLabel();
@@ -1201,7 +1314,7 @@ void BeamModuleAssembler::emit_i_is_tagged_tuple_ff(const ArgLabel &NotTuple,
void BeamModuleAssembler::emit_i_is_tuple(const ArgLabel &Fail,
const ArgSource &Src) {
mov_arg(ARG2, Src);
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_TUPLE) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Tuple) {
/* Fast path for the `error | {ok, Value}` case. */
comment("simplified tuple test since the source is always a tuple "
"when boxed");
@@ -1249,6 +1362,28 @@ void BeamModuleAssembler::emit_i_test_arity(const ArgLabel &Fail,
void BeamModuleAssembler::emit_is_eq_exact(const ArgLabel &Fail,
const ArgSource &X,
const ArgSource &Y) {
+ bool is_empty_binary = false;
+ if (exact_type<BeamTypeId::Bitstring>(X) && Y.isLiteral()) {
+ auto unit = getSizeUnit(X);
+ if (unit != 0 && std::gcd(unit, 8) == 8) {
+ Eterm literal =
+ beamfile_get_literal(beam, Y.as<ArgLiteral>().get());
+ is_empty_binary = is_binary(literal) && binary_size(literal) == 0;
+ }
+ }
+
+ if (is_empty_binary) {
+ mov_arg(RET, X);
+
+ x86::Gp boxed_ptr = emit_ptr_val(RET, RET);
+
+ comment("simplified equality test with empty binary");
+ a.cmp(emit_boxed_val(boxed_ptr, sizeof(Eterm)), 0);
+ a.jne(resolve_beam_label(Fail));
+
+ return;
+ }
+
/* If one argument is known to be an immediate, we can fail
* immediately if they're not equal. */
if (X.isRegister() && always_immediate(Y)) {
@@ -1273,14 +1408,24 @@ void BeamModuleAssembler::emit_is_eq_exact(const ArgLabel &Fail,
#endif
if (always_same_types(X, Y)) {
- comment("skipped test of tags since they are always equal");
+ comment("skipped tag test since they are always equal");
+ } else if (Y.isLiteral()) {
+ /* Fail immediately unless X is the same type of pointer as
+ * the literal Y.
+ */
+ Eterm literal = beamfile_get_literal(beam, Y.as<ArgLiteral>().get());
+ Uint tag_test = _TAG_PRIMARY_MASK - (literal & _TAG_PRIMARY_MASK);
+ a.test(ARG1.r8(), imm(tag_test));
+ a.jne(resolve_beam_label(Fail));
} else {
- /* The terms could still be equal if both operands are pointers
- * having the same tag. */
+ /* Fail immediately if the pointer tags are not equal. */
emit_is_unequal_based_on_tags(ARG1, ARG2);
a.je(resolve_beam_label(Fail));
}
+ /* Both operands are pointers having the same tag. Must do a
+ * deeper comparison. */
+
emit_enter_runtime();
runtime_call<2>(eq);
@@ -1293,31 +1438,31 @@ void BeamModuleAssembler::emit_is_eq_exact(const ArgLabel &Fail,
a.bind(next);
}
-void BeamModuleAssembler::emit_i_is_eq_exact_literal(const ArgLabel &Fail,
- const ArgSource &Src,
- const ArgConstant &Literal,
- const ArgWord &tag_test) {
- mov_arg(ARG2, Literal); /* May clobber ARG1 */
- mov_arg(ARG1, Src);
-
- /* Fail immediately unless Src is the same type of pointer as the literal.
- */
- a.test(ARG1.r8(), imm(tag_test.get()));
- a.jne(resolve_beam_label(Fail));
+void BeamModuleAssembler::emit_is_ne_exact(const ArgLabel &Fail,
+ const ArgSource &X,
+ const ArgSource &Y) {
+ bool is_empty_binary = false;
+ if (exact_type<BeamTypeId::Bitstring>(X) && Y.isLiteral()) {
+ auto unit = getSizeUnit(X);
+ if (unit != 0 && std::gcd(unit, 8) == 8) {
+ Eterm literal =
+ beamfile_get_literal(beam, Y.as<ArgLiteral>().get());
+ is_empty_binary = is_binary(literal) && binary_size(literal) == 0;
+ }
+ }
- emit_enter_runtime();
+ if (is_empty_binary) {
+ mov_arg(RET, X);
- runtime_call<2>(eq);
+ x86::Gp boxed_ptr = emit_ptr_val(RET, RET);
- emit_leave_runtime();
+ comment("simplified non-equality test with empty binary");
+ a.cmp(emit_boxed_val(boxed_ptr, sizeof(Eterm)), 0);
+ a.je(resolve_beam_label(Fail));
- a.test(RETd, RETd);
- a.jz(resolve_beam_label(Fail));
-}
+ return;
+ }
-void BeamModuleAssembler::emit_is_ne_exact(const ArgLabel &Fail,
- const ArgSource &X,
- const ArgSource &Y) {
/* If one argument is known to be an immediate, we can fail
* immediately if they're equal. */
if (X.isRegister() && always_immediate(Y)) {
@@ -1339,6 +1484,18 @@ void BeamModuleAssembler::emit_is_ne_exact(const ArgLabel &Fail,
if (always_same_types(X, Y)) {
comment("skipped tag test since they are always equal");
+ } else if (Y.isLiteral()) {
+ /* Succeed immediately if X is not the same type of pointer as
+ * the literal Y.
+ */
+ Eterm literal = beamfile_get_literal(beam, Y.as<ArgLiteral>().get());
+ Uint tag_test = _TAG_PRIMARY_MASK - (literal & _TAG_PRIMARY_MASK);
+ a.test(ARG1.r8(), imm(tag_test));
+#ifdef JIT_HARD_DEBUG
+ a.jne(next);
+#else
+ a.short_().jne(next);
+#endif
} else {
/* Test whether the terms are definitely unequal based on the tags
* alone. */
@@ -1363,32 +1520,6 @@ void BeamModuleAssembler::emit_is_ne_exact(const ArgLabel &Fail,
a.bind(next);
}
-void BeamModuleAssembler::emit_i_is_ne_exact_literal(
- const ArgLabel &Fail,
- const ArgSource &Src,
- const ArgConstant &Literal) {
- Label next = a.newLabel();
-
- mov_arg(ARG2, Literal); /* May clobber ARG1 */
- mov_arg(ARG1, Src);
-
- a.mov(RETd, ARG1d);
- a.and_(RETb, imm(_TAG_IMMED1_MASK));
- a.cmp(RETb, imm(TAG_PRIMARY_IMMED1));
- a.short_().je(next);
-
- emit_enter_runtime();
-
- runtime_call<2>(eq);
-
- emit_leave_runtime();
-
- a.test(RETd, RETd);
- a.jnz(resolve_beam_label(Fail));
-
- a.bind(next);
-}
-
void BeamGlobalAssembler::emit_arith_eq_shared() {
Label generic_compare = a.newLabel();
@@ -1410,13 +1541,13 @@ void BeamGlobalAssembler::emit_arith_eq_shared() {
a.short_().jne(generic_compare);
boxed_ptr = emit_ptr_val(ARG1, ARG1);
- a.movsd(x86::xmm0, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ vmovsd(x86::xmm0, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
boxed_ptr = emit_ptr_val(ARG2, ARG2);
- a.movsd(x86::xmm1, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ vmovsd(x86::xmm1, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
/* All float terms are finite so our caller only needs to check ZF. We don't
* need to check for errors (PF). */
- a.comisd(x86::xmm0, x86::xmm1);
+ vucomisd(x86::xmm0, x86::xmm1);
a.ret();
@@ -1515,15 +1646,18 @@ void BeamGlobalAssembler::emit_arith_compare_shared() {
a.jne(generic_compare);
boxed_ptr = emit_ptr_val(ARG1, ARG1);
- a.movsd(x86::xmm0, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ vmovsd(x86::xmm0, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
boxed_ptr = emit_ptr_val(ARG2, ARG2);
- a.movsd(x86::xmm1, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
- a.comisd(x86::xmm0, x86::xmm1);
+ vmovsd(x86::xmm1, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ vucomisd(x86::xmm0, x86::xmm1);
- /* `comisd` doesn't set the flags the same way `test` and friends do, so
- * they need to be converted for jl/jge to work. */
- a.setae(x86::al);
- a.dec(x86::al);
+ /* `vucomisd` doesn't set the flags the same way `test` and
+ * friends do, so they need to be converted for jl/jge/jg to work.
+ * NOTE: jg is needed for min/2 to work.
+ */
+ a.seta(x86::al);
+ a.setb(x86::ah);
+ a.sub(x86::al, x86::ah);
emit_leave_frame();
a.ret();
@@ -1574,34 +1708,65 @@ void BeamGlobalAssembler::emit_arith_compare_shared() {
void BeamModuleAssembler::emit_is_lt(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS) {
- Label generic = a.newLabel(), next = a.newLabel();
+ Label generic = a.newLabel(), do_jge = a.newLabel(), next = a.newLabel();
bool both_small = always_small(LHS) && always_small(RHS);
+ bool need_generic = !both_small;
mov_arg(ARG2, RHS); /* May clobber ARG1 */
mov_arg(ARG1, LHS);
if (both_small) {
comment("skipped test for small operands since they are always small");
- } else if (always_one_of(LHS,
- BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED) &&
- always_one_of(RHS,
- BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ } else if (always_small(LHS) && exact_type<BeamTypeId::Integer>(RHS) &&
+ hasLowerBound(RHS)) {
+ comment("simplified test because it always succeeds when RHS is a "
+ "bignum");
+ need_generic = false;
+ emit_is_not_boxed(next, ARG2, dShort);
+ } else if (always_small(LHS) && exact_type<BeamTypeId::Integer>(RHS) &&
+ hasUpperBound(RHS)) {
+ comment("simplified test because it always fails when RHS is a bignum");
+ need_generic = false;
+ emit_is_not_boxed(resolve_beam_label(Fail), ARG2);
+ } else if (exact_type<BeamTypeId::Integer>(LHS) && hasLowerBound(LHS) &&
+ always_small(RHS)) {
+ comment("simplified test because it always fails when LHS is a bignum");
+ need_generic = false;
+ emit_is_not_boxed(resolve_beam_label(Fail), ARG1);
+ } else if (exact_type<BeamTypeId::Integer>(LHS) && hasUpperBound(LHS) &&
+ always_small(RHS)) {
+ comment("simplified test because it always succeeds when LHS is a "
+ "bignum");
+ emit_is_not_boxed(next, ARG1, dShort);
+ } else if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ RHS)) {
/* The only possible kind of immediate is a small and all other
* values are boxed, so we can test for smalls by testing boxed. */
comment("simplified small test since all other types are boxed");
- a.mov(RETd, ARG1d);
- a.and_(RETd, ARG2d);
- a.test(RETb, imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_BOXED));
- a.short_().je(generic);
+ if (always_small(LHS)) {
+ emit_is_not_boxed(generic, ARG2, dShort);
+ } else if (always_small(RHS)) {
+ emit_is_not_boxed(generic, ARG1, dShort);
+ } else {
+ a.mov(RETd, ARG1d);
+ a.and_(RETd, ARG2d);
+ emit_is_not_boxed(generic, RET, dShort);
+ }
} else {
/* Relative comparisons are overwhelmingly likely to be used on
* smalls, so we'll specialize those and keep the rest in a shared
* fragment. */
- if (RHS.isSmall()) {
+ if (always_small(RHS)) {
a.mov(RETd, ARG1d);
- } else if (LHS.isSmall()) {
+ } else if (always_small(LHS)) {
a.mov(RETd, ARG2d);
} else {
+ /* Avoid the expensive generic comparison for equal terms. */
+ a.cmp(ARG1, ARG2);
+ a.short_().je(do_jge);
+
a.mov(RETd, ARG1d);
a.and_(RETd, ARG2d);
}
@@ -1613,52 +1778,101 @@ void BeamModuleAssembler::emit_is_lt(const ArgLabel &Fail,
/* Both arguments are smalls. */
a.cmp(ARG1, ARG2);
- if (!both_small) {
- a.short_().jmp(next);
+ if (need_generic) {
+ a.short_().jmp(do_jge);
}
a.bind(generic);
{
- if (!both_small) {
+ if (need_generic) {
safe_fragment_call(ga->get_arith_compare_shared());
}
}
- a.bind(next);
+ a.bind(do_jge);
a.jge(resolve_beam_label(Fail));
+
+ a.bind(next);
}
void BeamModuleAssembler::emit_is_ge(const ArgLabel &Fail,
const ArgSource &LHS,
const ArgSource &RHS) {
- Label generic = a.newLabel(), next = a.newLabel();
bool both_small = always_small(LHS) && always_small(RHS);
+ if (both_small && LHS.isRegister() && RHS.isImmed() &&
+ Support::isInt32(RHS.as<ArgImmed>().get())) {
+ comment("simplified compare because one operand is an immediate small");
+ a.cmp(getArgRef(LHS.as<ArgRegister>()), imm(RHS.as<ArgImmed>().get()));
+ a.jl(resolve_beam_label(Fail));
+ return;
+ } else if (both_small && RHS.isRegister() && LHS.isImmed() &&
+ Support::isInt32(LHS.as<ArgImmed>().get())) {
+ comment("simplified compare because one operand is an immediate small");
+ a.cmp(getArgRef(RHS.as<ArgRegister>()), imm(LHS.as<ArgImmed>().get()));
+ a.jg(resolve_beam_label(Fail));
+ return;
+ }
+
+ Label generic = a.newLabel(), do_jl = a.newLabel(), next = a.newLabel();
+ bool need_generic = !both_small;
+
mov_arg(ARG2, RHS); /* May clobber ARG1 */
mov_arg(ARG1, LHS);
if (both_small) {
comment("skipped test for small operands since they are always small");
- } else if (always_one_of(LHS,
- BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED) &&
- always_one_of(RHS,
- BEAM_TYPE_INTEGER | BEAM_TYPE_MASK_ALWAYS_BOXED)) {
+ } else if (always_small(LHS) && exact_type<BeamTypeId::Integer>(RHS) &&
+ hasLowerBound(RHS)) {
+ comment("simplified test because it always fails when RHS is a bignum");
+ need_generic = false;
+ emit_is_not_boxed(resolve_beam_label(Fail), ARG2);
+ } else if (always_small(LHS) && exact_type<BeamTypeId::Integer>(RHS) &&
+ hasUpperBound(RHS)) {
+ comment("simplified test because it always succeeds when RHS is a "
+ "bignum");
+ need_generic = false;
+ emit_is_not_boxed(next, ARG2, dShort);
+ } else if (exact_type<BeamTypeId::Integer>(LHS) && hasUpperBound(LHS) &&
+ always_small(RHS)) {
+ comment("simplified test because it always fails when LHS is a bignum");
+ need_generic = false;
+ emit_is_not_boxed(resolve_beam_label(Fail), ARG1);
+ } else if (exact_type<BeamTypeId::Integer>(LHS) && hasLowerBound(LHS) &&
+ always_small(RHS)) {
+ comment("simplified test because it always succeeds when LHS is a "
+ "bignum");
+ need_generic = false;
+ emit_is_not_boxed(next, ARG1, dShort);
+ } else if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ RHS)) {
/* The only possible kind of immediate is a small and all other
* values are boxed, so we can test for smalls by testing boxed. */
comment("simplified small test since all other types are boxed");
- a.mov(RETd, ARG1d);
- a.and_(RETd, ARG2d);
- a.test(RETb, imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_BOXED));
- a.short_().je(generic);
+ if (always_small(LHS)) {
+ emit_is_not_boxed(generic, ARG2, dShort);
+ } else if (always_small(RHS)) {
+ emit_is_not_boxed(generic, ARG1, dShort);
+ } else {
+ a.mov(RETd, ARG1d);
+ a.and_(RETd, ARG2d);
+ emit_is_not_boxed(generic, RET, dShort);
+ }
} else {
/* Relative comparisons are overwhelmingly likely to be used on
* smalls, so we'll specialize those and keep the rest in a shared
* fragment. */
- if (RHS.isSmall()) {
+ if (always_small(RHS)) {
a.mov(RETd, ARG1d);
- } else if (LHS.isSmall()) {
+ } else if (always_small(LHS)) {
a.mov(RETd, ARG2d);
} else {
+ /* Avoid the expensive generic comparison for equal terms. */
+ a.cmp(ARG1, ARG2);
+ a.short_().je(do_jl);
+
a.mov(RETd, ARG1d);
a.and_(RETd, ARG2d);
}
@@ -1670,72 +1884,387 @@ void BeamModuleAssembler::emit_is_ge(const ArgLabel &Fail,
/* Both arguments are smalls. */
a.cmp(ARG1, ARG2);
- if (!both_small) {
- a.short_().jmp(next);
+ if (need_generic) {
+ a.short_().jmp(do_jl);
}
a.bind(generic);
{
- if (!both_small) {
+ if (need_generic) {
safe_fragment_call(ga->get_arith_compare_shared());
}
}
- a.bind(next);
+ a.bind(do_jl);
a.jl(resolve_beam_label(Fail));
+
+ a.bind(next);
}
-void BeamModuleAssembler::emit_bif_is_eq_ne_exact(const ArgSource &LHS,
- const ArgSource &RHS,
- const ArgRegister &Dst,
- Eterm fail_value,
- Eterm succ_value) {
- /* `mov_imm` may clobber the flags if either value is zero. */
- ASSERT(fail_value && succ_value);
+/*
+ * ARG1 = Src
+ * ARG2 = Min
+ * ARG3 = Max
+ *
+ * Result is returned in the flags.
+ */
+void BeamGlobalAssembler::emit_is_in_range_shared() {
+ Label immediate = a.newLabel();
+ Label generic_compare = a.newLabel();
+ Label done = a.newLabel();
- mov_imm(RET, succ_value);
- cmp_arg(getArgRef(LHS), RHS);
+ /* Is the source a float? */
+ emit_is_boxed(immediate, ARG1);
- if (always_immediate(LHS) || always_immediate(RHS)) {
- if (!LHS.isImmed() && !RHS.isImmed()) {
- comment("simplified check since one argument is an immediate");
- }
- mov_imm(ARG1, fail_value);
- a.cmovne(RET, ARG1);
+ x86::Gp boxed_ptr = emit_ptr_val(ARG4, ARG1);
+ a.cmp(emit_boxed_val(boxed_ptr), imm(HEADER_FLONUM));
+ a.short_().jne(generic_compare);
+
+ /* Compare the float to the limits. */
+ vmovsd(x86::xmm0, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ a.sar(ARG2, imm(_TAG_IMMED1_SIZE));
+ a.sar(ARG3, imm(_TAG_IMMED1_SIZE));
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.vcvtsi2sd(x86::xmm1, x86::xmm1, ARG2);
+ a.vcvtsi2sd(x86::xmm2, x86::xmm2, ARG3);
} else {
- Label next = a.newLabel();
+ a.cvtsi2sd(x86::xmm1, ARG2);
+ a.cvtsi2sd(x86::xmm2, ARG3);
+ }
- a.je(next);
+ mov_imm(RET, -1);
+ mov_imm(x86::rcx, 0);
+ vucomisd(x86::xmm0, x86::xmm2);
+ a.seta(x86::cl);
+ vucomisd(x86::xmm1, x86::xmm0);
- mov_arg(ARG1, LHS);
- mov_arg(ARG2, RHS);
+ a.cmovbe(RET, x86::rcx);
+ a.cmp(RET, imm(0));
+ a.ret();
+
+ a.bind(immediate);
+ {
+ /*
+ * Src is an immediate (such as ATOM) but not SMALL.
+ * That means that Src must be greater than the upper
+ * limit.
+ */
+ mov_imm(RET, 1);
+ a.cmp(RET, imm(0));
+ a.ret();
+ }
+
+ a.bind(generic_compare);
+ {
emit_enter_runtime();
- runtime_call<2>(eq);
- emit_leave_runtime();
+ a.mov(TMP_MEM1q, ARG1);
+ a.mov(TMP_MEM2q, ARG3);
+
+ comment("erts_cmp_compound(X, Y, 0, 0);");
+ mov_imm(ARG3, 0);
+ mov_imm(ARG4, 0);
+ runtime_call<4>(erts_cmp_compound);
a.test(RET, RET);
+ a.js(done);
- mov_imm(RET, succ_value);
- mov_imm(ARG1, fail_value);
- a.cmove(RET, ARG1);
+ a.mov(ARG1, TMP_MEM1q);
+ a.mov(ARG2, TMP_MEM2q);
- a.bind(next);
+ comment("erts_cmp_compound(X, Y, 0, 0);");
+ mov_imm(ARG3, 0);
+ mov_imm(ARG4, 0);
+ runtime_call<4>(erts_cmp_compound);
+ a.test(RET, RET);
+
+ a.bind(done);
+ emit_leave_runtime();
+
+ a.ret();
}
+}
- mov_arg(Dst, RET);
+void BeamModuleAssembler::emit_is_in_range(ArgLabel const &Small,
+ ArgLabel const &Large,
+ ArgRegister const &Src,
+ ArgConstant const &Min,
+ ArgConstant const &Max) {
+ Label next = a.newLabel(), generic = a.newLabel();
+ bool need_generic = true;
+
+ mov_arg(ARG1, Src);
+
+ if (always_small(Src)) {
+ need_generic = false;
+ comment("skipped test for small operand since it always small");
+ } else if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ Src)) {
+ /* The only possible kind of immediate is a small and all
+ * other values are boxed, so we can test for smalls by
+ * testing boxed. */
+ comment("simplified small test since all other types are boxed");
+ ERTS_CT_ASSERT(_TAG_PRIMARY_MASK - TAG_PRIMARY_BOXED == (1 << 0));
+ if (Small == Large && never_one_of<BeamTypeId::Float>(Src)) {
+ /* Src is never a float and the failure labels are
+ * equal. Therefore, since a bignum will never be within
+ * the range, we can fail immediately if Src is not a
+ * small. */
+ need_generic = false;
+ a.test(ARG1.r8(), imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_BOXED));
+ a.je(resolve_beam_label(Small));
+ } else {
+ /* Src can be a float or the failures labels are distinct.
+ * We need to call the generic routine if Src is not a small. */
+ a.test(ARG1.r8(), imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_BOXED));
+ a.short_().je(generic);
+ }
+ } else if (Small == Large) {
+ /* We can save one instruction if we incorporate the test for
+ * small into the range check. */
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ comment("simplified small & range tests since failure labels are "
+ "equal");
+ a.mov(RET, ARG1);
+ sub(RET, Min.as<ArgImmed>().get(), ARG4);
+
+ /* Since we have subtracted the (tagged) lower bound, the
+ * tag bits of the difference is 0 if and only if Src is
+ * a small. Testing for a tag of 0 can be done in two
+ * instructions. */
+ a.test(RETb, imm(_TAG_IMMED1_MASK));
+ a.jne(generic);
+
+ /* Now do the range check. */
+ cmp(RET, Max.as<ArgImmed>().get() - Min.as<ArgImmed>().get(), ARG4);
+ a.ja(resolve_beam_label(Small));
+
+ /* Bypass the test code. */
+ goto test_done;
+ } else {
+ /* We have no applicable type information and the failure
+ * labels are distinct. Emit the standard test for small
+ * and call the generic routine if Src is not a small. */
+ a.mov(RETd, ARG1d);
+ a.and_(RETb, imm(_TAG_IMMED1_MASK));
+ a.cmp(RETb, imm(_TAG_IMMED1_SMALL));
+ a.short_().jne(generic);
+ }
+
+ /* We have now established that the operand is small. */
+ if (Small == Large) {
+ comment("simplified range test since failure labels are equal");
+ sub(ARG1, Min.as<ArgImmed>().get(), RET);
+ cmp(ARG1, Max.as<ArgImmed>().get() - Min.as<ArgImmed>().get(), RET);
+ a.ja(resolve_beam_label(Small));
+ } else {
+ cmp(ARG1, Min.as<ArgImmed>().get(), RET);
+ a.jl(resolve_beam_label(Small));
+ cmp(ARG1, Max.as<ArgImmed>().get(), RET);
+ a.jg(resolve_beam_label(Large));
+ }
+
+test_done:
+ if (need_generic) {
+ a.short_().jmp(next);
+ }
+
+ a.bind(generic);
+ if (!need_generic) {
+ comment("skipped generic comparison because it is not needed");
+ } else {
+ mov_arg(ARG2, Min);
+ mov_arg(ARG3, Max);
+ safe_fragment_call(ga->get_is_in_range_shared());
+ if (Small == Large) {
+ a.jne(resolve_beam_label(Small));
+ } else {
+ a.jl(resolve_beam_label(Small));
+ a.jg(resolve_beam_label(Large));
+ }
+ }
+
+ a.bind(next);
}
-void BeamModuleAssembler::emit_bif_is_eq_exact(const ArgRegister &LHS,
- const ArgSource &RHS,
- const ArgRegister &Dst) {
- emit_bif_is_eq_ne_exact(LHS, RHS, Dst, am_false, am_true);
+/*
+ * ARG1 = Src
+ * ARG2 = A
+ * ARG3 = B
+ *
+ * Result is returned in the flags.
+ */
+void BeamGlobalAssembler::emit_is_ge_lt_shared() {
+ Label done = a.newLabel();
+
+ emit_enter_runtime();
+
+ a.mov(TMP_MEM1q, ARG1);
+ a.mov(TMP_MEM2q, ARG3);
+
+ comment("erts_cmp_compound(Src, A, 0, 0);");
+ mov_imm(ARG3, 0);
+ mov_imm(ARG4, 0);
+ runtime_call<4>(erts_cmp_compound);
+ a.test(RET, RET);
+ a.short_().js(done);
+
+ comment("erts_cmp_compound(B, Src, 0, 0);");
+ a.mov(ARG1, TMP_MEM2q);
+ a.mov(ARG2, TMP_MEM1q);
+ mov_imm(ARG3, 0);
+ mov_imm(ARG4, 0);
+ runtime_call<4>(erts_cmp_compound);
+
+ /* The following instructions implements the signum function. */
+ mov_imm(ARG1, -1);
+ mov_imm(ARG4, 1);
+ a.test(RET, RET);
+ a.cmovs(RET, ARG1);
+ a.cmovg(RET, ARG4);
+
+ /* RET is now -1, 0, or 1. */
+ a.add(RET, imm(1));
+
+ /* We now have:
+ * RET == 0 if B < SRC
+ * RET > 0 if B => SRC
+ * and flags set accordingly. */
+
+ a.bind(done);
+ emit_leave_runtime();
+
+ a.ret();
}
-void BeamModuleAssembler::emit_bif_is_ne_exact(const ArgRegister &LHS,
- const ArgSource &RHS,
- const ArgRegister &Dst) {
- emit_bif_is_eq_ne_exact(LHS, RHS, Dst, am_true, am_false);
+/*
+ * is_ge + is_lt is 20 instructions.
+ *
+ * is_ge_lt is 15 instructions.
+ */
+void BeamModuleAssembler::emit_is_ge_lt(ArgLabel const &Fail1,
+ ArgLabel const &Fail2,
+ ArgRegister const &Src,
+ ArgConstant const &A,
+ ArgConstant const &B) {
+ Label generic = a.newLabel(), next = a.newLabel();
+
+ mov_arg(ARG2, A);
+ mov_arg(ARG3, B);
+ mov_arg(ARG1, Src);
+
+ a.mov(RETd, ARG1d);
+ a.and_(RETb, imm(_TAG_IMMED1_MASK));
+ a.cmp(RETb, imm(_TAG_IMMED1_SMALL));
+ a.short_().jne(generic);
+
+ a.cmp(ARG1, ARG2);
+ a.jl(resolve_beam_label(Fail1));
+ a.cmp(ARG3, ARG1);
+ a.jge(resolve_beam_label(Fail2));
+ a.short_().jmp(next);
+
+ a.bind(generic);
+ safe_fragment_call(ga->get_is_ge_lt_shared());
+ a.jl(resolve_beam_label(Fail1));
+ a.jg(resolve_beam_label(Fail2));
+
+ a.bind(next);
+}
+
+/*
+ * The optimized instruction sequence is not always shorter,
+ * but it ensures that Src is only read from memory once.
+ */
+void BeamModuleAssembler::emit_is_ge_ge(ArgLabel const &Fail1,
+ ArgLabel const &Fail2,
+ ArgRegister const &Src,
+ ArgConstant const &A,
+ ArgConstant const &B) {
+ if (!always_small(Src)) {
+ /* In practice, it is uncommon that Src is not a known small
+ * integer, so we will not bother optimizing that case. */
+ emit_is_ge(Fail1, Src, A);
+ emit_is_ge(Fail2, Src, B);
+ return;
+ }
+
+ mov_arg(RET, Src);
+ sub(RET, A.as<ArgImmed>().get(), ARG1);
+ a.jl(resolve_beam_label(Fail1));
+ cmp(RET, B.as<ArgImmed>().get() - A.as<ArgImmed>().get(), ARG1);
+ a.jb(resolve_beam_label(Fail2));
+}
+
+/*
+ * Combine is_integer with range check.
+ *
+ * is_integer + is_ge + is_ge is 31 instructions.
+ *
+ * is_int_in_range is 6 instructions.
+ */
+void BeamModuleAssembler::emit_is_int_in_range(ArgLabel const &Fail,
+ ArgRegister const &Src,
+ ArgConstant const &Min,
+ ArgConstant const &Max) {
+ mov_arg(RET, Src);
+
+ sub(RET, Min.as<ArgImmed>().get(), ARG1);
+
+ /* Since we have subtracted the (tagged) lower bound, the
+ * tag bits of the difference is 0 if and only if Src is
+ * a small. */
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.test(RETb, imm(_TAG_IMMED1_MASK));
+ a.jne(resolve_beam_label(Fail));
+ cmp(RET, Max.as<ArgImmed>().get() - Min.as<ArgImmed>().get(), ARG1);
+ a.ja(resolve_beam_label(Fail));
+}
+
+/*
+ * is_integer + is_ge is 21 instructions.
+ *
+ * is_int_ge is 14 instructions.
+ */
+void BeamModuleAssembler::emit_is_int_ge(ArgLabel const &Fail,
+ ArgRegister const &Src,
+ ArgConstant const &Min) {
+ Label small = a.newLabel();
+ Label fail = a.newLabel();
+ Label next = a.newLabel();
+ /* On Unix, using rcx instead of ARG1 makes the `test` instruction
+ * in the boxed test one byte shorter. */
+ const x86::Gp src_reg = x86::rcx;
+
+ mov_arg(src_reg, Src);
+
+ if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(Src)) {
+ comment("simplified small test since all other types are boxed");
+ emit_is_boxed(small, Src, src_reg);
+ } else {
+ a.mov(RETd, src_reg.r32());
+ a.and_(RETb, imm(_TAG_IMMED1_MASK));
+ a.cmp(RETb, imm(_TAG_IMMED1_SMALL));
+ a.short_().je(small);
+
+ emit_is_boxed(resolve_beam_label(Fail), Src, src_reg);
+ }
+
+ /* Src is boxed. Jump to failure unless Src is a positive bignum. */
+ x86::Gp boxed_ptr = emit_ptr_val(src_reg, src_reg);
+ a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
+ a.and_(RETb, imm(_TAG_HEADER_MASK));
+ a.cmp(RETb, imm(_TAG_HEADER_POS_BIG));
+ a.short_().je(next);
+
+ a.bind(fail);
+ a.jmp(resolve_beam_label(Fail));
+
+ a.bind(small);
+ cmp(src_reg, Min.as<ArgImmed>().get(), RET);
+ a.short_().jl(fail);
+
+ a.bind(next);
}
void BeamModuleAssembler::emit_badmatch(const ArgSource &Src) {
@@ -1778,8 +2307,7 @@ void BeamModuleAssembler::emit_catch(const ArgYRegister &CatchTag,
* with the tagged catch
*/
a.bind(patch_addr);
- a.mov(RETd, imm(0x7fffffff));
-
+ a.mov(RETd, imm(INT_MAX));
mov_arg(CatchTag, RET);
/* Offset = 1 for `mov` payload */
@@ -1819,14 +2347,14 @@ void BeamGlobalAssembler::emit_catch_end_shared() {
a.jne(not_error);
/* This is an error, attach a stacktrace to the reason. */
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapAlloc>();
a.mov(ARG1, c_p);
/* ARG2 set above. */
a.mov(ARG3, getXRef(2));
runtime_call<3>(add_stacktrace);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapAlloc>();
/* not_error assumes stacktrace/reason is in ARG2 */
a.mov(ARG2, RET);
@@ -1926,13 +2454,13 @@ void BeamModuleAssembler::emit_raise(const ArgSource &Trace,
}
void BeamModuleAssembler::emit_build_stacktrace() {
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapAlloc>();
a.mov(ARG1, c_p);
a.mov(ARG2, getXRef(0));
runtime_call<2>(build_stacktrace);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapAlloc>();
a.mov(getXRef(0), RET);
}
diff --git a/erts/emulator/beam/jit/x86/instr_float.cpp b/erts/emulator/beam/jit/x86/instr_float.cpp
index d3e47d49dc..97ea29f940 100644
--- a/erts/emulator/beam/jit/x86/instr_float.cpp
+++ b/erts/emulator/beam/jit/x86/instr_float.cpp
@@ -35,10 +35,14 @@ void BeamGlobalAssembler::emit_check_float_error() {
Label error = a.newLabel(), floatMax = a.newLabel(),
floatSignMask = a.newLabel();
- a.movsd(x86::xmm2, x86::xmm0);
- a.movsd(x86::xmm1, x86::qword_ptr(floatMax));
- a.andpd(x86::xmm2, x86::xmmword_ptr(floatSignMask));
- a.ucomisd(x86::xmm1, x86::xmm2);
+ vmovsd(x86::xmm1, x86::qword_ptr(floatMax));
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.vandpd(x86::xmm2, x86::xmm0, x86::xmmword_ptr(floatSignMask));
+ } else {
+ a.movq(x86::xmm2, x86::xmm0);
+ a.andpd(x86::xmm2, x86::xmmword_ptr(floatSignMask));
+ }
+ vucomisd(x86::xmm1, x86::xmm2);
a.short_().jb(error);
a.ret();
@@ -57,16 +61,21 @@ void BeamGlobalAssembler::emit_check_float_error() {
a.embedDouble(std::numeric_limits<double>::max());
}
-void BeamModuleAssembler::emit_float_instr(uint32_t instId,
+void BeamModuleAssembler::emit_float_instr(uint32_t instIdSSE,
+ uint32_t instIdAVX,
const ArgFRegister &LHS,
const ArgFRegister &RHS,
const ArgFRegister &Dst) {
- a.movsd(x86::xmm0, getArgRef(LHS));
- a.movsd(x86::xmm1, getArgRef(RHS));
+ vmovsd(x86::xmm0, getArgRef(LHS));
+ vmovsd(x86::xmm1, getArgRef(RHS));
- a.emit(instId, x86::xmm0, x86::xmm1);
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.emit(instIdAVX, x86::xmm0, x86::xmm0, x86::xmm1);
+ } else {
+ a.emit(instIdSSE, x86::xmm0, x86::xmm1);
+ }
safe_fragment_call(ga->get_check_float_error());
- a.movsd(getArgRef(Dst), x86::xmm0);
+ vmovsd(getArgRef(Dst), x86::xmm0);
}
void BeamModuleAssembler::emit_fload(const ArgSource &Src,
@@ -76,17 +85,17 @@ void BeamModuleAssembler::emit_fload(const ArgSource &Src,
x86::Gp boxed_ptr = emit_ptr_val(ARG1, ARG1);
- a.movsd(x86::xmm0, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
- a.movsd(getArgRef(Dst), x86::xmm0);
+ vmovsd(x86::xmm0, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ vmovsd(getArgRef(Dst), x86::xmm0);
}
void BeamModuleAssembler::emit_fstore(const ArgFRegister &Src,
const ArgRegister &Dst) {
- a.movsd(x86::xmm0, getArgRef(Src));
+ vmovsd(x86::xmm0, getArgRef(Src));
/* {thing_word,double} */
a.mov(x86::qword_ptr(HTOP), imm(HEADER_FLONUM));
- a.movsd(x86::qword_ptr(HTOP, sizeof(Eterm)), x86::xmm0);
+ vmovsd(x86::qword_ptr(HTOP, sizeof(Eterm)), x86::xmm0);
a.lea(ARG1, x86::qword_ptr(HTOP, make_float(0)));
mov_arg(Dst, ARG1);
@@ -122,7 +131,7 @@ void BeamGlobalAssembler::emit_fconv_shared() {
a.test(RETd, RETd);
a.short_().js(error);
- a.movsd(x86::xmm0, TMP_MEM1q);
+ vmovsd(x86::xmm0, TMP_MEM1q);
a.ret();
a.bind(error);
@@ -140,8 +149,12 @@ void BeamModuleAssembler::emit_fconv(const ArgSource &Src,
comment("simplified fconv since source is always small");
mov_arg(ARG2, Src);
a.sar(ARG2, imm(_TAG_IMMED1_SIZE));
- a.cvtsi2sd(x86::xmm0, ARG2);
- a.movsd(getArgRef(Dst), x86::xmm0);
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.vcvtsi2sd(x86::xmm0, x86::xmm0, ARG2);
+ } else {
+ a.cvtsi2sd(x86::xmm0, ARG2);
+ }
+ vmovsd(getArgRef(Dst), x86::xmm0);
return;
}
@@ -153,17 +166,21 @@ void BeamModuleAssembler::emit_fconv(const ArgSource &Src,
emit_is_small(not_small, Src, ARG2);
a.sar(ARG2, imm(_TAG_IMMED1_SIZE));
- a.cvtsi2sd(x86::xmm0, ARG2);
+ if (hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ a.vcvtsi2sd(x86::xmm0, x86::xmm0, ARG2);
+ } else {
+ a.cvtsi2sd(x86::xmm0, ARG2);
+ }
a.short_().jmp(next);
a.bind(not_small);
{
- if (masked_types(Src, BEAM_TYPE_FLOAT) == BEAM_TYPE_NONE) {
+ if (never_one_of<BeamTypeId::Float>(Src)) {
comment("skipped float path since source cannot be a float");
} else {
/* If the source is always a number, we can skip the box test when
* it's not a small. */
- if (always_one_of(Src, BEAM_TYPE_FLOAT | BEAM_TYPE_INTEGER)) {
+ if (always_one_of<BeamTypeId::Number>(Src)) {
comment("skipped box test since source is always a number");
} else {
emit_is_boxed(fallback, Src, ARG2, dShort);
@@ -172,7 +189,7 @@ void BeamModuleAssembler::emit_fconv(const ArgSource &Src,
/* Speculatively load the float value, this is safe since all boxed
* terms are at least two words long. */
auto boxed_ptr = emit_ptr_val(ARG1, ARG2);
- a.movsd(x86::xmm0, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ vmovsd(x86::xmm0, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
a.cmp(emit_boxed_val(boxed_ptr), imm(HEADER_FLONUM));
a.short_().je(next);
@@ -184,31 +201,31 @@ void BeamModuleAssembler::emit_fconv(const ArgSource &Src,
}
a.bind(next);
- a.movsd(getArgRef(Dst), x86::xmm0);
+ vmovsd(getArgRef(Dst), x86::xmm0);
}
void BeamModuleAssembler::emit_i_fadd(const ArgFRegister &LHS,
const ArgFRegister &RHS,
const ArgFRegister &Dst) {
- emit_float_instr(x86::Inst::kIdAddpd, LHS, RHS, Dst);
+ emit_float_instr(x86::Inst::kIdAddpd, x86::Inst::kIdVaddpd, LHS, RHS, Dst);
}
void BeamModuleAssembler::emit_i_fsub(const ArgFRegister &LHS,
const ArgFRegister &RHS,
const ArgFRegister &Dst) {
- emit_float_instr(x86::Inst::kIdSubpd, LHS, RHS, Dst);
+ emit_float_instr(x86::Inst::kIdSubpd, x86::Inst::kIdVsubpd, LHS, RHS, Dst);
}
void BeamModuleAssembler::emit_i_fmul(const ArgFRegister &LHS,
const ArgFRegister &RHS,
const ArgFRegister &Dst) {
- emit_float_instr(x86::Inst::kIdMulpd, LHS, RHS, Dst);
+ emit_float_instr(x86::Inst::kIdMulpd, x86::Inst::kIdVmulpd, LHS, RHS, Dst);
}
void BeamModuleAssembler::emit_i_fdiv(const ArgFRegister &LHS,
const ArgFRegister &RHS,
const ArgFRegister &Dst) {
- emit_float_instr(x86::Inst::kIdDivpd, LHS, RHS, Dst);
+ emit_float_instr(x86::Inst::kIdDivpd, x86::Inst::kIdVdivpd, LHS, RHS, Dst);
}
void BeamModuleAssembler::emit_i_fnegate(const ArgFRegister &Src,
diff --git a/erts/emulator/beam/jit/x86/instr_fun.cpp b/erts/emulator/beam/jit/x86/instr_fun.cpp
index 1ae19aaaba..903ecef49b 100644
--- a/erts/emulator/beam/jit/x86/instr_fun.cpp
+++ b/erts/emulator/beam/jit/x86/instr_fun.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2021-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2021-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,14 +32,14 @@ void BeamGlobalAssembler::emit_unloaded_fun() {
a.mov(TMP_MEM1q, ARG5);
- emit_enter_runtime<Update::eHeap | Update::eStack | Update::eReductions>();
+ emit_enter_runtime<Update::eHeapAlloc | Update::eReductions>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
/* ARG3 and ARG4 have already been set. */
runtime_call<4>(beam_jit_handle_unloaded_fun);
- emit_leave_runtime<Update::eHeap | Update::eStack | Update::eReductions |
+ emit_leave_runtime<Update::eHeapAlloc | Update::eReductions |
Update::eCodeIndex>();
a.test(RET, RET);
@@ -93,14 +93,14 @@ void BeamGlobalAssembler::emit_handle_call_fun_error() {
a.mov(TMP_MEM1q, ARG4);
a.mov(TMP_MEM2q, ARG5);
- emit_enter_runtime<Update::eHeap | Update::eStack>();
+ emit_enter_runtime<Update::eHeapAlloc>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
/* ARG3 is already set. */
runtime_call<3>(beam_jit_build_argument_list);
- emit_leave_runtime<Update::eHeap | Update::eStack>();
+ emit_leave_runtime<Update::eHeapAlloc>();
a.mov(ARG1, TMP_MEM1q);
a.mov(getXRef(0), ARG1);
@@ -143,6 +143,19 @@ void BeamGlobalAssembler::emit_handle_call_fun_error() {
}
}
+/* Handles save_calls for local funs, which is a side-effect of our calling
+ * convention. Fun entry is in RET.
+ *
+ * When the active code index is ERTS_SAVE_CALLS_CODE_IX, all local fun calls
+ * will land here. */
+void BeamGlobalAssembler::emit_dispatch_save_calls_fun() {
+ /* Keep going with the actual code index. */
+ a.mov(ARG1, imm(&the_active_code_index));
+ a.mov(ARG1d, x86::dword_ptr(ARG1));
+
+ a.jmp(emit_setup_dispatchable_call(RET, ARG1));
+}
+
/* `call_fun` instructions land here to set up their environment before jumping
* to the actual implementation.
*
@@ -156,26 +169,17 @@ void BeamModuleAssembler::emit_i_lambda_trampoline(const ArgLambda &Lambda,
const ArgWord &NumFree) {
const ssize_t effective_arity = Arity.get() - NumFree.get();
const ssize_t num_free = NumFree.get();
- ssize_t i;
const auto &lambda = lambdas[Lambda.get()];
a.bind(lambda.trampoline);
emit_ptr_val(ARG4, ARG4);
- for (i = 0; i < num_free - 1; i += 2) {
- size_t offset = offsetof(ErlFunThing, env) + i * sizeof(Eterm);
-
- a.movups(x86::xmm0, emit_boxed_val(ARG4, offset, sizeof(Eterm[2])));
- a.movups(getXRef(i + effective_arity, sizeof(Eterm[2])), x86::xmm0);
- }
-
- if (i < num_free) {
- size_t offset = offsetof(ErlFunThing, env) + i * sizeof(Eterm);
-
- a.mov(RET, emit_boxed_val(ARG4, offset));
- a.mov(getXRef(i + effective_arity), RET);
- }
+ ASSERT(num_free > 0);
+ emit_copy_words(emit_boxed_val(ARG4, offsetof(ErlFunThing, env)),
+ getXRef(effective_arity),
+ num_free,
+ RET);
a.jmp(resolve_beam_label(Lbl));
}
@@ -193,12 +197,12 @@ void BeamModuleAssembler::emit_i_make_fun3(const ArgLambda &Lambda,
mov_arg(ARG3, Arity);
mov_arg(ARG4, NumFree);
- emit_enter_runtime<Update::eHeap>();
+ emit_enter_runtime<Update::eHeapOnlyAlloc>();
a.mov(ARG1, c_p);
runtime_call<4>(erts_new_local_fun_thing);
- emit_leave_runtime<Update::eHeap>();
+ emit_leave_runtime<Update::eHeapOnlyAlloc>();
comment("Move fun environment");
for (unsigned i = 0; i < num_free; i++) {
@@ -242,7 +246,7 @@ void BeamGlobalAssembler::emit_apply_fun_shared() {
a.cmp(ARG1d, imm(NIL));
a.short_().je(finished);
- a.test(ARG1d, imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST));
+ a.test(ARG1.r8(), imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST));
a.short_().jne(malformed_list);
emit_ptr_val(ARG1, ARG1);
@@ -376,8 +380,8 @@ void BeamModuleAssembler::emit_i_call_fun2(const ArgVal &Tag,
mov_imm(ARG3, Arity.get());
auto target = emit_call_fun(
- always_one_of(Func, BEAM_TYPE_MASK_ALWAYS_BOXED),
- masked_types(Func, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_FUN,
+ always_one_of<BeamTypeId::AlwaysBoxed>(Func),
+ masked_types<BeamTypeId::MaybeBoxed>(Func) == BeamTypeId::Fun,
Tag.as<ArgImmed>().get() == am_safe);
erlang_call(target, ARG6);
@@ -397,8 +401,8 @@ void BeamModuleAssembler::emit_i_call_fun2_last(const ArgVal &Tag,
mov_imm(ARG3, Arity.get());
auto target = emit_call_fun(
- always_one_of(Func, BEAM_TYPE_MASK_ALWAYS_BOXED),
- masked_types(Func, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_FUN,
+ always_one_of<BeamTypeId::AlwaysBoxed>(Func),
+ masked_types<BeamTypeId::MaybeBoxed>(Func) == BeamTypeId::Fun,
Tag.as<ArgImmed>().get() == am_safe);
emit_deallocate(Deallocate);
diff --git a/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp b/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp
index 9ef5486568..de243a45e6 100644
--- a/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp
+++ b/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp
@@ -19,6 +19,7 @@
*/
#include <algorithm>
+#include <numeric>
#include "beam_asm.hpp"
extern "C"
@@ -28,56 +29,285 @@ extern "C"
#include "beam_catches.h"
#include "beam_common.h"
#include "code_ix.h"
+#include "erl_map.h"
}
using namespace asmjit;
-/*
- * We considered specializing tuple_size/1, but ultimately didn't
- * consider it worth doing.
- *
- * At the time of writing, there were 294 uses of tuple_size/1
- * in the OTP source code. (11 of them were in dialyzer.)
- *
- * The code size for the specialization was 34 bytes,
- * while the code size for the bif1 instruction was 24 bytes.
+/* ================================================================
+ * '=:='/2
+ * '=/='/2
+ * '>='/2
+ * '<'/2
+ * ================================================================
*/
-void BeamGlobalAssembler::emit_handle_hd_error() {
- static ErtsCodeMFA mfa = {am_erlang, am_hd, 1};
+void BeamModuleAssembler::emit_bif_is_eq_ne_exact(const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst,
+ Eterm fail_value,
+ Eterm succ_value) {
+ /* `mov_imm` may clobber the flags if either value is zero. */
+ ASSERT(fail_value && succ_value);
- a.mov(getXRef(0), RET);
- a.mov(x86::qword_ptr(c_p, offsetof(Process, freason)), imm(BADARG));
- a.mov(ARG4, imm(&mfa));
- a.jmp(labels[raise_exception]);
+ cmp_arg(getArgRef(LHS), RHS);
+ mov_imm(RET, succ_value);
+
+ if (always_immediate(LHS) || always_immediate(RHS)) {
+ if (!LHS.isImmed() && !RHS.isImmed()) {
+ comment("simplified check since one argument is an immediate");
+ }
+ mov_imm(ARG1, fail_value);
+ a.cmovne(RET, ARG1);
+ } else {
+ Label next = a.newLabel();
+
+ a.je(next);
+
+ mov_arg(ARG1, LHS);
+ mov_arg(ARG2, RHS);
+
+ emit_enter_runtime();
+ runtime_call<2>(eq);
+ emit_leave_runtime();
+
+ a.test(RET, RET);
+
+ mov_imm(RET, succ_value);
+ mov_imm(ARG1, fail_value);
+ a.cmove(RET, ARG1);
+
+ a.bind(next);
+ }
+
+ mov_arg(Dst, RET);
}
-/*
- * At the time of implementation, there were 3285 uses of hd/1 in
- * the OTP source code. Most of them were in code generated by
- * yecc.
- *
- * The code size for this specialization of hd/1 is 21 bytes,
- * while the code size for the bif1 instruction is 24 bytes.
- */
-void BeamModuleAssembler::emit_bif_hd(const ArgSource &Src,
- const ArgRegister &Hd) {
- Label good_cons = a.newLabel();
+void BeamModuleAssembler::emit_bif_is_eq_exact(const ArgRegister &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ emit_bif_is_eq_ne_exact(LHS, RHS, Dst, am_false, am_true);
+}
- mov_arg(RET, Src);
- a.test(RETb, imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST));
+void BeamModuleAssembler::emit_bif_is_ne_exact(const ArgRegister &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ emit_bif_is_eq_ne_exact(LHS, RHS, Dst, am_true, am_false);
+}
- a.short_().je(good_cons);
- safe_fragment_call(ga->get_handle_hd_error());
+void BeamModuleAssembler::emit_cond_to_bool(uint32_t instId,
+ const ArgRegister &Dst) {
+ mov_imm(RET, am_true);
+ mov_imm(ARG1, am_false);
+ a.emit(instId, RET, ARG1);
+ mov_arg(Dst, RET);
+}
- a.bind(good_cons);
+void BeamModuleAssembler::emit_bif_is_ge_lt(uint32_t instId,
+ const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ Label generic = a.newLabel(), make_boolean = a.newLabel();
+
+ mov_arg(ARG2, RHS); /* May clobber ARG1 */
+ mov_arg(ARG1, LHS);
+
+ if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(RHS)) {
+ /* The only possible kind of immediate is a small and all other
+ * values are boxed, so we can test for smalls by testing boxed. */
+ comment("simplified small test since all other types are boxed");
+ if (always_small(LHS)) {
+ emit_is_not_boxed(generic, ARG2, dShort);
+ } else if (always_small(RHS)) {
+ emit_is_not_boxed(generic, ARG1, dShort);
+ } else {
+ a.mov(RETd, ARG1d);
+ a.and_(RETd, ARG2d);
+ emit_is_not_boxed(generic, RET, dShort);
+ }
+ } else {
+ /* Relative comparisons are overwhelmingly likely to be used on
+ * smalls, so we'll specialize those and keep the rest in a shared
+ * fragment. */
+ if (always_small(RHS)) {
+ a.mov(RETd, ARG1d);
+ } else if (always_small(LHS)) {
+ a.mov(RETd, ARG2d);
+ } else {
+ a.mov(RETd, ARG1d);
+ a.and_(RETd, ARG2d);
+ }
+
+ a.and_(RETb, imm(_TAG_IMMED1_MASK));
+ a.cmp(RETb, imm(_TAG_IMMED1_SMALL));
+ a.short_().jne(generic);
+ }
+
+ /* Both arguments are smalls. */
+ a.cmp(ARG1, ARG2);
+ a.short_().jmp(make_boolean);
+
+ a.bind(generic);
{
- x86::Gp boxed_ptr = emit_ptr_val(RET, RET);
- a.mov(ARG2, getCARRef(boxed_ptr));
- mov_arg(Hd, ARG2);
+ a.cmp(ARG1, ARG2);
+ a.short_().je(make_boolean);
+ safe_fragment_call(ga->get_arith_compare_shared());
}
+
+ a.bind(make_boolean);
+ emit_cond_to_bool(instId, Dst);
}
+void BeamModuleAssembler::emit_bif_is_ge(const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ bool both_small = always_small(LHS) && always_small(RHS);
+
+ if (both_small && LHS.isRegister() && RHS.isImmed() &&
+ Support::isInt32(RHS.as<ArgImmed>().get())) {
+ comment("simplified compare because one operand is an immediate small");
+ a.cmp(getArgRef(LHS.as<ArgRegister>()), imm(RHS.as<ArgImmed>().get()));
+ emit_cond_to_bool(x86::Inst::kIdCmovl, Dst);
+
+ return;
+ } else if (both_small && RHS.isRegister() && LHS.isImmed() &&
+ Support::isInt32(LHS.as<ArgImmed>().get())) {
+ comment("simplified compare because one operand is an immediate small");
+ a.cmp(getArgRef(RHS.as<ArgRegister>()), imm(LHS.as<ArgImmed>().get()));
+ emit_cond_to_bool(x86::Inst::kIdCmovg, Dst);
+
+ return;
+ }
+
+ emit_bif_is_ge_lt(x86::Inst::kIdCmovl, LHS, RHS, Dst);
+}
+
+void BeamModuleAssembler::emit_bif_is_lt(const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ emit_bif_is_ge_lt(x86::Inst::kIdCmovge, LHS, RHS, Dst);
+}
+
+/* ================================================================
+ * bit_size/1
+ * ================================================================
+ */
+
+void BeamModuleAssembler::emit_bif_bit_size(const ArgWord &Bif,
+ const ArgLabel &Fail,
+ const ArgSource &Src,
+ const ArgRegister &Dst) {
+ if (!exact_type<BeamTypeId::Bitstring>(Src)) {
+ /* Unknown type. Use the standard BIF instruction. */
+ emit_i_bif1(Src, Fail, Bif, Dst);
+ return;
+ }
+
+ mov_arg(ARG2, Src);
+
+ auto unit = getSizeUnit(Src);
+ bool is_bitstring = unit == 0 || std::gcd(unit, 8) != 8;
+ x86::Gp boxed_ptr = emit_ptr_val(ARG2, ARG2);
+
+ if (is_bitstring) {
+ comment("inlined bit_size/1 because "
+ "its argument is a bitstring");
+ } else {
+ comment("inlined and simplified bit_size/1 because "
+ "its argument is a binary");
+ }
+
+ if (is_bitstring) {
+ a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
+ }
+
+ a.mov(ARG1, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ a.shl(ARG1, imm(3 + _TAG_IMMED1_SIZE));
+
+ if (is_bitstring) {
+ Label not_sub_bin = a.newLabel();
+ const auto diff_mask = _TAG_HEADER_SUB_BIN - _TAG_HEADER_REFC_BIN;
+ ERTS_CT_ASSERT((_TAG_HEADER_SUB_BIN & diff_mask) != 0 &&
+ (_TAG_HEADER_REFC_BIN & diff_mask) == 0 &&
+ (_TAG_HEADER_HEAP_BIN & diff_mask) == 0);
+ a.test(RETb, imm(diff_mask));
+ a.short_().jz(not_sub_bin);
+
+ a.mov(RETb, emit_boxed_val(boxed_ptr, offsetof(ErlSubBin, bitsize), 1));
+ a.shl(RETb, imm(_TAG_IMMED1_SIZE));
+ a.add(ARG1.r8(), RETb);
+
+ a.bind(not_sub_bin);
+ }
+
+ a.or_(ARG1, imm(_TAG_IMMED1_SMALL));
+ mov_arg(Dst, ARG1);
+}
+
+/* ================================================================
+ * byte_size/1
+ * ================================================================
+ */
+
+void BeamModuleAssembler::emit_bif_byte_size(const ArgWord &Bif,
+ const ArgLabel &Fail,
+ const ArgSource &Src,
+ const ArgRegister &Dst) {
+ if (!exact_type<BeamTypeId::Bitstring>(Src)) {
+ /* Unknown type. Use the standard BIF instruction. */
+ emit_i_bif1(Src, Fail, Bif, Dst);
+ return;
+ }
+
+ mov_arg(ARG2, Src);
+
+ auto unit = getSizeUnit(Src);
+ bool is_bitstring = unit == 0 || std::gcd(unit, 8) != 8;
+ x86::Gp boxed_ptr = emit_ptr_val(ARG2, ARG2);
+
+ if (is_bitstring) {
+ comment("inlined byte_size/1 because "
+ "its argument is a bitstring");
+ } else {
+ comment("inlined and simplified byte_size/1 because "
+ "its argument is a binary");
+ }
+
+ if (is_bitstring) {
+ a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
+ }
+
+ a.mov(ARG1, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+
+ if (is_bitstring) {
+ Label not_sub_bin = a.newLabel();
+ const auto diff_mask = _TAG_HEADER_SUB_BIN - _TAG_HEADER_REFC_BIN;
+ ERTS_CT_ASSERT((_TAG_HEADER_SUB_BIN & diff_mask) != 0 &&
+ (_TAG_HEADER_REFC_BIN & diff_mask) == 0 &&
+ (_TAG_HEADER_HEAP_BIN & diff_mask) == 0);
+ a.test(RETb, imm(diff_mask));
+ a.short_().jz(not_sub_bin);
+
+ a.mov(RETb, emit_boxed_val(boxed_ptr, offsetof(ErlSubBin, bitsize), 1));
+ a.test(RETb, RETb);
+ a.setne(RETb);
+ a.movzx(RETd, RETb);
+ a.add(ARG1, RET);
+
+ a.bind(not_sub_bin);
+ }
+
+ a.shl(ARG1, imm(_TAG_IMMED1_SIZE));
+ a.or_(ARG1, imm(_TAG_IMMED1_SMALL));
+ mov_arg(Dst, ARG1);
+}
+
+/* ================================================================
+ * element/2
+ * ================================================================
+ */
+
void BeamGlobalAssembler::emit_handle_element_error() {
static ErtsCodeMFA mfa = {am_erlang, am_element, 2};
@@ -161,15 +391,14 @@ void BeamModuleAssembler::emit_bif_element(const ArgLabel &Fail,
/*
* Try to optimize the use of a tuple as a lookup table.
*/
- if (exact_type(Pos, BEAM_TYPE_INTEGER) && Tuple.isLiteral()) {
+ if (exact_type<BeamTypeId::Integer>(Pos) && Tuple.isLiteral()) {
Eterm tuple = beamfile_get_literal(beam, Tuple.as<ArgLiteral>().get());
if (is_tuple(tuple)) {
Label error = a.newLabel(), next = a.newLabel();
Sint size = Sint(arityval(*tuple_val(tuple)));
- auto [min, max] = getIntRange(Pos);
- bool is_bounded = min <= max;
- bool can_fail = !is_bounded || min < 1 || size < max;
+ auto [min, max] = getClampedRange(Pos);
+ bool can_fail = min < 1 || size < max;
comment("skipped tuple test since source is always a literal "
"tuple");
@@ -189,13 +418,13 @@ void BeamModuleAssembler::emit_bif_element(const ArgLabel &Fail,
a.mov(RET, ARG1);
a.sar(RET, imm(_TAG_IMMED1_SIZE));
- if (is_bounded && min >= 1) {
+ if (min >= 1) {
comment("skipped check for position =:= 0 since it is always "
">= 1");
} else {
a.short_().jz(error);
}
- if (is_bounded && min >= 0 && size >= max) {
+ if (min >= 0 && size >= max) {
comment("skipped check for negative position and position "
"beyond tuple");
} else {
@@ -236,7 +465,7 @@ void BeamModuleAssembler::emit_bif_element(const ArgLabel &Fail,
x86::Gp boxed_ptr = emit_ptr_val(ARG3, ARG2);
- if (exact_type(Tuple, BEAM_TYPE_TUPLE)) {
+ if (exact_type<BeamTypeId::Tuple>(Tuple)) {
comment("skipped tuple test since source is always a tuple");
ERTS_CT_ASSERT(Support::isInt32(make_arityval(MAX_ARITYVAL)));
a.cmp(emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)),
@@ -317,3 +546,451 @@ void BeamModuleAssembler::emit_bif_element(const ArgLabel &Fail,
mov_arg(Dst, RET);
}
+
+/* ================================================================
+ * hd/1
+ * ================================================================
+ */
+
+void BeamGlobalAssembler::emit_handle_hd_error() {
+ static ErtsCodeMFA mfa = {am_erlang, am_hd, 1};
+
+ a.mov(getXRef(0), RET);
+ a.mov(x86::qword_ptr(c_p, offsetof(Process, freason)), imm(BADARG));
+ a.mov(ARG4, imm(&mfa));
+ a.jmp(labels[raise_exception]);
+}
+
+/*
+ * At the time of implementation, there were 3285 uses of hd/1 in
+ * the OTP source code. Most of them were in code generated by
+ * yecc.
+ *
+ * The code size for this specialization of hd/1 is 21 bytes,
+ * while the code size for the bif1 instruction is 24 bytes.
+ */
+
+void BeamModuleAssembler::emit_bif_hd(const ArgSource &Src,
+ const ArgRegister &Hd) {
+ Label good_cons = a.newLabel();
+
+ mov_arg(RET, Src);
+ a.test(RETb, imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST));
+
+ a.short_().je(good_cons);
+ safe_fragment_call(ga->get_handle_hd_error());
+
+ a.bind(good_cons);
+ {
+ x86::Gp boxed_ptr = emit_ptr_val(RET, RET);
+ a.mov(ARG2, getCARRef(boxed_ptr));
+ mov_arg(Hd, ARG2);
+ }
+}
+
+/* ================================================================
+ * is_map_key/2
+ * ================================================================
+ */
+
+void BeamModuleAssembler::emit_bif_is_map_key(const ArgWord &Bif,
+ const ArgLabel &Fail,
+ const ArgSource &Key,
+ const ArgSource &Src,
+ const ArgRegister &Dst) {
+ if (!exact_type<BeamTypeId::Map>(Src)) {
+ emit_i_bif2(Key, Src, Fail, Bif, Dst);
+ return;
+ }
+
+ comment("inlined BIF is_map_key/2");
+
+ mov_arg(ARG1, Src);
+ mov_arg(ARG2, Key);
+
+ if (maybe_one_of<BeamTypeId::MaybeImmediate>(Key) &&
+ hasCpuFeature(CpuFeatures::X86::kBMI2)) {
+ safe_fragment_call(ga->get_i_get_map_element_shared());
+ emit_cond_to_bool(x86::Inst::kIdCmovne, Dst);
+ } else {
+ emit_enter_runtime();
+ runtime_call<2>(get_map_element);
+ emit_leave_runtime();
+
+ emit_test_the_non_value(RET);
+ emit_cond_to_bool(x86::Inst::kIdCmove, Dst);
+ }
+}
+
+/* ================================================================
+ * map_get/2
+ * ================================================================
+ */
+
+void BeamGlobalAssembler::emit_handle_map_get_badmap() {
+ static ErtsCodeMFA mfa = {am_erlang, am_map_get, 2};
+ a.mov(getXRef(0), ARG2);
+ a.mov(getXRef(1), ARG1);
+ a.mov(x86::qword_ptr(c_p, offsetof(Process, freason)), imm(BADMAP));
+ a.mov(x86::qword_ptr(c_p, offsetof(Process, fvalue)), ARG1);
+ a.mov(ARG4, imm(&mfa));
+ a.jmp(labels[raise_exception]);
+}
+
+void BeamGlobalAssembler::emit_handle_map_get_badkey() {
+ static ErtsCodeMFA mfa = {am_erlang, am_map_get, 2};
+ a.mov(getXRef(0), ARG2);
+ a.mov(getXRef(1), ARG1);
+ a.mov(x86::qword_ptr(c_p, offsetof(Process, freason)), imm(BADKEY));
+ a.mov(x86::qword_ptr(c_p, offsetof(Process, fvalue)), ARG2);
+ a.mov(ARG4, imm(&mfa));
+ a.jmp(labels[raise_exception]);
+}
+
+void BeamModuleAssembler::emit_bif_map_get(const ArgLabel &Fail,
+ const ArgSource &Key,
+ const ArgSource &Src,
+ const ArgRegister &Dst) {
+ Label good_key = a.newLabel();
+
+ mov_arg(ARG1, Src);
+ mov_arg(ARG2, Key);
+
+ if (exact_type<BeamTypeId::Map>(Src)) {
+ comment("skipped test for map for known map argument");
+ } else {
+ Label bad_map = a.newLabel();
+ Label good_map = a.newLabel();
+
+ if (Fail.get() == 0) {
+ emit_is_boxed(bad_map, Src, ARG1);
+ } else {
+ emit_is_boxed(resolve_beam_label(Fail), Src, ARG1);
+ }
+
+ /* As an optimization for the `error | #{}` case, skip checking the
+ * header word when we know that the only possible boxed type
+ * is a map. */
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Map) {
+ comment("skipped header test since we know it's a map when boxed");
+ if (Fail.get() == 0) {
+ a.short_().jmp(good_map);
+ }
+ } else {
+ x86::Gp boxed_ptr = emit_ptr_val(RET, ARG1);
+ a.mov(RET, emit_boxed_val(boxed_ptr));
+ a.and_(RETb, imm(_TAG_HEADER_MASK));
+ a.cmp(RETb, imm(_TAG_HEADER_MAP));
+ if (Fail.get() == 0) {
+ a.short_().je(good_map);
+ } else {
+ a.jne(resolve_beam_label(Fail));
+ }
+ }
+
+ a.bind(bad_map);
+ if (Fail.get() == 0) {
+ fragment_call(ga->get_handle_map_get_badmap());
+ }
+
+ a.bind(good_map);
+ }
+
+ if (maybe_one_of<BeamTypeId::MaybeImmediate>(Key) &&
+ hasCpuFeature(CpuFeatures::X86::kBMI2)) {
+ safe_fragment_call(ga->get_i_get_map_element_shared());
+ if (Fail.get() == 0) {
+ a.je(good_key);
+ } else {
+ a.jne(resolve_beam_label(Fail));
+ }
+ } else {
+ emit_enter_runtime();
+ runtime_call<2>(get_map_element);
+ emit_leave_runtime();
+
+ emit_test_the_non_value(RET);
+ if (Fail.get() == 0) {
+ a.short_().jne(good_key);
+ } else {
+ a.je(resolve_beam_label(Fail));
+ }
+ }
+
+ if (Fail.get() == 0) {
+ mov_arg(ARG1, Src);
+ mov_arg(ARG2, Key);
+ fragment_call(ga->get_handle_map_get_badkey());
+ }
+
+ a.bind(good_key);
+ mov_arg(Dst, RET);
+}
+
+/* ================================================================
+ * map_size/1
+ * ================================================================
+ */
+
+void BeamGlobalAssembler::emit_handle_map_size_error() {
+ static ErtsCodeMFA mfa = {am_erlang, am_map_size, 1};
+
+ a.mov(getXRef(0), RET);
+ a.mov(x86::qword_ptr(c_p, offsetof(Process, fvalue)), RET);
+ a.mov(x86::qword_ptr(c_p, offsetof(Process, freason)), imm(BADMAP));
+ a.mov(ARG4, imm(&mfa));
+ a.jmp(labels[raise_exception]);
+}
+
+void BeamModuleAssembler::emit_bif_map_size(const ArgLabel &Fail,
+ const ArgSource &Src,
+ const ArgRegister &Dst) {
+ Label error = a.newLabel(), good_map = a.newLabel();
+
+ mov_arg(RET, Src);
+
+ if (Fail.get() == 0) {
+ emit_is_boxed(error, Src, RET);
+ } else {
+ emit_is_boxed(resolve_beam_label(Fail), Src, RET);
+ }
+
+ x86::Gp boxed_ptr = emit_ptr_val(x86::rdx, RET);
+
+ if (exact_type<BeamTypeId::Map>(Src)) {
+ comment("skipped type check because the argument is always a map");
+ a.bind(error); /* Never referenced. */
+ } else {
+ a.mov(x86::ecx, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
+ a.and_(x86::cl, imm(_TAG_HEADER_MASK));
+ a.cmp(x86::cl, imm(_TAG_HEADER_MAP));
+ if (Fail.get() == 0) {
+ a.short_().je(good_map);
+
+ a.bind(error);
+ safe_fragment_call(ga->get_handle_map_size_error());
+ } else {
+ a.jne(resolve_beam_label(Fail));
+ a.bind(error); /* Never referenced. */
+ }
+ }
+
+ a.bind(good_map);
+ {
+ ERTS_CT_ASSERT(offsetof(flatmap_t, size) == sizeof(Eterm));
+ a.mov(RET, emit_boxed_val(boxed_ptr, sizeof(Eterm)));
+ a.shl(RET, imm(4));
+ a.or_(RETb, imm(_TAG_IMMED1_SMALL));
+ mov_arg(Dst, RET);
+ }
+}
+
+/* ================================================================
+ * min/2
+ * max/2
+ * ================================================================
+ */
+
+void BeamModuleAssembler::emit_bif_min_max(uint32_t instId,
+ const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ Label generic = a.newLabel(), do_cmov = a.newLabel();
+ bool both_small = always_small(LHS) && always_small(RHS);
+ bool need_generic = !both_small;
+
+ mov_arg(ARG2, RHS); /* May clobber ARG1 */
+ mov_arg(ARG1, LHS);
+
+ if (both_small) {
+ comment("skipped test for small operands since they are always small");
+ } else if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ LHS) &&
+ always_small(RHS)) {
+ emit_is_not_boxed(generic, ARG1, dShort);
+ } else if (always_small(LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ RHS)) {
+ emit_is_not_boxed(generic, ARG2, dShort);
+ } else if (always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ LHS) &&
+ always_one_of<BeamTypeId::Integer, BeamTypeId::AlwaysBoxed>(
+ RHS)) {
+ comment("simplified small test since all other types are boxed");
+ a.mov(RETd, ARG1d);
+ a.and_(RETd, ARG2d);
+ emit_is_not_boxed(generic, RETb, dShort);
+ } else {
+ /* Relative comparisons are overwhelmingly likely to be used on
+ * smalls, so we'll specialize those and keep the rest in a shared
+ * fragment. */
+ if (RHS.isSmall()) {
+ a.mov(RETd, ARG1d);
+ } else if (LHS.isSmall()) {
+ a.mov(RETd, ARG2d);
+ } else {
+ a.mov(RETd, ARG1d);
+ a.and_(RETd, ARG2d);
+ }
+
+ a.and_(RETb, imm(_TAG_IMMED1_MASK));
+ a.cmp(RETb, imm(_TAG_IMMED1_SMALL));
+ a.short_().jne(generic);
+ }
+
+ /* Both arguments are smalls. */
+ a.cmp(ARG1, ARG2);
+ if (need_generic) {
+ a.short_().jmp(do_cmov);
+ }
+
+ a.bind(generic);
+ if (need_generic) {
+ a.cmp(ARG1, ARG2);
+ a.short_().je(do_cmov);
+ a.push(ARG1);
+ a.push(ARG2);
+ safe_fragment_call(ga->get_arith_compare_shared());
+ a.pop(ARG2);
+ a.pop(ARG1);
+ }
+
+ a.bind(do_cmov);
+ a.emit(instId, ARG1, ARG2);
+ mov_arg(Dst, ARG1);
+}
+
+void BeamModuleAssembler::emit_bif_max(const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ emit_bif_min_max(x86::Inst::kIdCmovl, LHS, RHS, Dst);
+}
+
+void BeamModuleAssembler::emit_bif_min(const ArgSource &LHS,
+ const ArgSource &RHS,
+ const ArgRegister &Dst) {
+ emit_bif_min_max(x86::Inst::kIdCmovg, LHS, RHS, Dst);
+}
+
+/* ================================================================
+ * node/1
+ * ================================================================
+ */
+
+void BeamGlobalAssembler::emit_handle_node_error() {
+ static ErtsCodeMFA mfa = {am_erlang, am_node, 1};
+ a.mov(getXRef(0), ARG1);
+ a.mov(x86::qword_ptr(c_p, offsetof(Process, freason)), imm(BADARG));
+ a.mov(ARG4, imm(&mfa));
+
+ a.jmp(labels[raise_exception]);
+}
+
+void BeamModuleAssembler::emit_bif_node(const ArgLabel &Fail,
+ const ArgRegister &Src,
+ const ArgRegister &Dst) {
+ bool always_identifier = always_one_of<BeamTypeId::Identifier>(Src);
+ Label test_internal = a.newLabel();
+ Label internal = a.newLabel();
+ Label next = a.newLabel();
+ Label fail;
+
+ if (Fail.get() == 0 && !always_identifier) {
+ fail = a.newLabel();
+ }
+
+ mov_arg(ARG1, Src);
+ emit_is_boxed(test_internal, Src, ARG1);
+
+ x86::Gp boxed_ptr = emit_ptr_val(ARG2, ARG1);
+
+ if (!always_one_of<BeamTypeId::Pid, BeamTypeId::Port>(Src)) {
+ a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
+ a.and_(RETd, imm(_TAG_HEADER_MASK));
+ }
+
+ if (maybe_one_of<BeamTypeId::Reference>(Src)) {
+ a.cmp(RETb, imm(_TAG_HEADER_REF));
+ a.short_().je(internal);
+ }
+
+ if (!always_identifier) {
+ Label external = a.newLabel();
+
+ ERTS_CT_ASSERT((_TAG_HEADER_EXTERNAL_PORT - _TAG_HEADER_EXTERNAL_PID) >>
+ _TAG_PRIMARY_SIZE ==
+ 1);
+ ERTS_CT_ASSERT((_TAG_HEADER_EXTERNAL_REF - _TAG_HEADER_EXTERNAL_PORT) >>
+ _TAG_PRIMARY_SIZE ==
+ 1);
+ a.sub(RETb, imm(_TAG_HEADER_EXTERNAL_PID));
+ a.cmp(RETb, imm(_TAG_HEADER_EXTERNAL_REF - _TAG_HEADER_EXTERNAL_PID));
+ if (Fail.get() == 0) {
+ a.short_().jbe(external);
+
+ a.bind(fail);
+ fragment_call(ga->get_handle_node_error());
+ } else {
+ a.ja(resolve_beam_label(Fail));
+ }
+
+ a.bind(external);
+ }
+
+ a.mov(ARG1, emit_boxed_val(boxed_ptr, offsetof(ExternalThing, node)));
+ a.short_().jmp(next);
+
+ a.bind(test_internal);
+ if (!always_identifier) {
+ /* Since pids and ports differ by a single bit, we can
+ * simplify the check by clearing said bit and comparing
+ * against the lesser one. */
+ ERTS_CT_ASSERT(_TAG_IMMED1_PORT - _TAG_IMMED1_PID == 0x4);
+ a.mov(RETd, ARG1d);
+ a.and_(RETb,
+ imm(~(_TAG_IMMED1_PORT - _TAG_IMMED1_PID) & _TAG_IMMED1_MASK));
+ a.cmp(RETb, imm(_TAG_IMMED1_PID));
+ if (Fail.get() == 0) {
+ a.short_().jne(fail);
+ } else {
+ a.jne(resolve_beam_label(Fail));
+ }
+ }
+
+ a.bind(internal);
+ a.mov(ARG1, imm(&erts_this_node));
+ a.mov(ARG1, x86::qword_ptr(ARG1));
+
+ a.bind(next);
+ a.mov(ARG1, x86::qword_ptr(ARG1, offsetof(ErlNode, sysname)));
+ mov_arg(Dst, ARG1);
+}
+
+/* ================================================================
+ * tuple_size/1
+ * ================================================================
+ */
+
+void BeamModuleAssembler::emit_bif_tuple_size(const ArgWord &Bif,
+ const ArgLabel &Fail,
+ const ArgRegister &Src,
+ const ArgRegister &Dst) {
+ if (exact_type<BeamTypeId::Tuple>(Src)) {
+ comment("inlined tuple_size/1 because the argument is always a tuple");
+ mov_arg(RET, Src);
+
+ /* Instructions operating on dwords are shorter. */
+ ERTS_CT_ASSERT(Support::isInt32(make_arityval(MAX_ARITYVAL)));
+ x86::Gp boxed_ptr = emit_ptr_val(RET, RET);
+ a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
+
+ ERTS_CT_ASSERT(_HEADER_ARITY_OFFS - _TAG_IMMED1_SIZE > 0);
+ ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == _TAG_IMMED1_MASK);
+ a.shr(RETd, imm(_HEADER_ARITY_OFFS - _TAG_IMMED1_SIZE));
+ a.or_(RETb, imm(_TAG_IMMED1_SMALL));
+ mov_arg(Dst, RET);
+ } else {
+ /* Unknown type. Use the standard BIF instruction. */
+ emit_i_bif1(Src, Fail, Bif, Dst);
+ }
+}
diff --git a/erts/emulator/beam/jit/x86/instr_map.cpp b/erts/emulator/beam/jit/x86/instr_map.cpp
index 4ead792fab..5f89077ba6 100644
--- a/erts/emulator/beam/jit/x86/instr_map.cpp
+++ b/erts/emulator/beam/jit/x86/instr_map.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ using namespace asmjit;
extern "C"
{
#include "erl_map.h"
+#include "erl_term_hashing.h"
#include "beam_common.h"
}
@@ -50,6 +51,12 @@ void BeamGlobalAssembler::emit_internal_hash_helper() {
a.add(lower, imm(HCONST));
a.add(upper, imm(HCONST));
+#if defined(ERL_INTERNAL_HASH_CRC32C)
+ a.mov(ARG6d, hash);
+ a.crc32(hash, lower);
+ a.add(hash, ARG6d);
+ a.crc32(hash, upper);
+#else
using rounds =
std::initializer_list<std::tuple<x86::Gp, x86::Gp, x86::Gp, int>>;
for (const auto &round : rounds{{lower, upper, hash, 13},
@@ -78,6 +85,7 @@ void BeamGlobalAssembler::emit_internal_hash_helper() {
a.xor_(r_a, ARG6d);
}
+#endif
#ifdef DBG_HASHMAP_COLLISION_BONANZA
a.mov(TMP_MEM1q, ARG1);
@@ -157,7 +165,7 @@ void BeamGlobalAssembler::emit_hashmap_get_element() {
emit_ptr_val(node, node);
/* Have we found our leaf? */
- a.test(node.r32(), imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST));
+ a.test(node.r8(), imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST));
a.short_().je(leaf_node);
/* Nope, we have to search another node. */
@@ -238,13 +246,13 @@ void BeamGlobalAssembler::emit_flatmap_get_element() {
void BeamGlobalAssembler::emit_new_map_shared() {
emit_enter_frame();
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<5>(erts_gc_new_map);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
emit_leave_frame();
a.ret();
@@ -266,37 +274,80 @@ void BeamModuleAssembler::emit_new_map(const ArgRegister &Dst,
mov_arg(Dst, RET);
}
-void BeamGlobalAssembler::emit_i_new_small_map_lit_shared() {
- emit_enter_frame();
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
-
- a.mov(ARG1, c_p);
- load_x_reg_array(ARG2);
- runtime_call<5>(erts_gc_new_small_map_lit);
-
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
- emit_leave_frame();
-
- a.ret();
-}
-
void BeamModuleAssembler::emit_i_new_small_map_lit(const ArgRegister &Dst,
const ArgWord &Live,
const ArgLiteral &Keys,
const ArgWord &Size,
const Span<ArgVal> &args) {
- Label data = embed_vararg_rodata(args, CP_SIZE);
-
ASSERT(Size.get() == args.size());
- ASSERT(Keys.isLiteral());
- mov_arg(ARG3, Keys);
- mov_imm(ARG4, Live.get());
- a.lea(ARG5, x86::qword_ptr(data));
+ emit_gc_test(ArgWord(0),
+ ArgWord(args.size() + MAP_HEADER_FLATMAP_SZ + 1),
+ Live);
- fragment_call(ga->get_i_new_small_map_lit_shared());
+ std::vector<ArgVal> data;
+ data.reserve(args.size() + MAP_HEADER_FLATMAP_SZ + 1);
+ data.push_back(ArgWord(MAP_HEADER_FLATMAP));
+ data.push_back(Size);
+ data.push_back(Keys);
- mov_arg(Dst, RET);
+ for (auto arg : args) {
+ data.push_back(arg);
+ }
+
+ size_t size = data.size();
+ unsigned i;
+
+ mov_arg(x86::qword_ptr(HTOP), data[0]);
+
+ /* Starting from 1 instead of 0 gives more opportunities for
+ * applying the MMX optimizations. */
+ for (i = 1; i < size - 1; i += 2) {
+ x86::Mem dst_ptr0 = x86::qword_ptr(HTOP, i * sizeof(Eterm));
+ x86::Mem dst_ptr1 = x86::qword_ptr(HTOP, (i + 1) * sizeof(Eterm));
+ auto first = data[i];
+ auto second = data[i + 1];
+
+ switch (ArgVal::memory_relation(first, second)) {
+ case ArgVal::consecutive: {
+ x86::Mem src_ptr = getArgRef(first, 16);
+
+ comment("(initializing two elements at once)");
+ dst_ptr0.setSize(16);
+ vmovups(x86::xmm0, src_ptr);
+ vmovups(dst_ptr0, x86::xmm0);
+ break;
+ }
+ case ArgVal::reverse_consecutive: {
+ if (!hasCpuFeature(CpuFeatures::X86::kAVX)) {
+ mov_arg(dst_ptr0, first);
+ mov_arg(dst_ptr1, second);
+ } else {
+ x86::Mem src_ptr = getArgRef(second, 16);
+
+ comment("(initializing with two swapped elements at once)");
+ dst_ptr0.setSize(16);
+ a.vpermilpd(x86::xmm0, src_ptr, 1); /* Load and swap */
+ a.vmovups(dst_ptr0, x86::xmm0);
+ }
+ break;
+ }
+ case ArgVal::none:
+ mov_arg(dst_ptr0, first);
+ mov_arg(dst_ptr1, second);
+ break;
+ }
+ }
+
+ if (i < size) {
+ x86::Mem dst_ptr = x86::qword_ptr(HTOP, i * sizeof(Eterm));
+ mov_arg(dst_ptr, data[i]);
+ }
+
+ a.lea(ARG1, x86::byte_ptr(HTOP, TAG_PRIMARY_BOXED));
+ a.add(HTOP, imm(size * sizeof(Eterm)));
+
+ mov_arg(Dst, ARG1);
}
/* ARG1 = map, ARG2 = key
@@ -357,7 +408,7 @@ void BeamModuleAssembler::emit_i_get_map_element(const ArgLabel &Fail,
mov_arg(ARG1, Src);
mov_arg(ARG2, Key);
- if (masked_types(Key, BEAM_TYPE_MASK_IMMEDIATE) != BEAM_TYPE_NONE &&
+ if (maybe_one_of<BeamTypeId::MaybeImmediate>(Key) &&
hasCpuFeature(CpuFeatures::X86::kBMI2)) {
safe_fragment_call(ga->get_i_get_map_element_shared());
a.jne(resolve_beam_label(Fail));
@@ -523,13 +574,13 @@ void BeamModuleAssembler::emit_i_get_map_element_hash(const ArgLabel &Fail,
/* ARG3 = live registers, ARG4 = update vector size, ARG5 = update vector. */
void BeamGlobalAssembler::emit_update_map_assoc_shared() {
emit_enter_frame();
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<5>(erts_gc_update_map_assoc);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
emit_leave_frame();
a.ret();
@@ -559,13 +610,13 @@ void BeamModuleAssembler::emit_update_map_assoc(const ArgSource &Src,
* Result is returned in RET, error is indicated by ZF. */
void BeamGlobalAssembler::emit_update_map_exact_guard_shared() {
emit_enter_frame();
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<5>(erts_gc_update_map_exact);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
emit_leave_frame();
emit_test_the_non_value(RET);
@@ -579,13 +630,13 @@ void BeamGlobalAssembler::emit_update_map_exact_body_shared() {
Label error = a.newLabel();
emit_enter_frame();
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<5>(erts_gc_update_map_exact);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
emit_leave_frame();
emit_test_the_non_value(RET);
diff --git a/erts/emulator/beam/jit/x86/instr_msg.cpp b/erts/emulator/beam/jit/x86/instr_msg.cpp
index 025e834d59..d015d3b71b 100644
--- a/erts/emulator/beam/jit/x86/instr_msg.cpp
+++ b/erts/emulator/beam/jit/x86/instr_msg.cpp
@@ -75,12 +75,12 @@ void BeamModuleAssembler::emit_i_recv_set() {
#endif /* ERTS_SUPPORT_OLD_RECV_MARK_INSTRS */
void BeamModuleAssembler::emit_recv_marker_reserve(const ArgRegister &Dst) {
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapAlloc>();
a.mov(ARG1, c_p);
runtime_call<1>(erts_msgq_recv_marker_insert);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapAlloc>();
mov_arg(Dst, RET);
}
@@ -183,8 +183,7 @@ void BeamGlobalAssembler::emit_i_loop_rec_shared() {
comment("Inner queue empty, fetch more from outer/middle queues");
- emit_enter_runtime<Update::eReductions | Update::eStack |
- Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(message_ptr, imm(0));
a.mov(ARG1, c_p);
@@ -204,8 +203,7 @@ void BeamGlobalAssembler::emit_i_loop_rec_shared() {
* Also note that another process may have loaded new code and sent us
* a message to notify us about it, so we must update the active code
* index. */
- emit_leave_runtime<Update::eStack | Update::eHeap |
- Update::eCodeIndex>();
+ emit_leave_runtime<Update::eHeapAlloc | Update::eCodeIndex>();
a.sub(FCALLS, RET);
diff --git a/erts/emulator/beam/jit/x86/instr_select.cpp b/erts/emulator/beam/jit/x86/instr_select.cpp
index 39199d2027..529e1f4f01 100644
--- a/erts/emulator/beam/jit/x86/instr_select.cpp
+++ b/erts/emulator/beam/jit/x86/instr_select.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ void BeamModuleAssembler::emit_i_select_tuple_arity(const ArgRegister &Src,
ERTS_CT_ASSERT(Support::isInt32(make_arityval(MAX_ARITYVAL)));
a.mov(ARG2d, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
- if (masked_types(Src, BEAM_TYPE_MASK_BOXED) == BEAM_TYPE_TUPLE) {
+ if (masked_types<BeamTypeId::MaybeBoxed>(Src) == BeamTypeId::Tuple) {
comment("simplified tuple test since the source is always a tuple "
"when boxed");
} else {
diff --git a/erts/emulator/beam/jit/x86/instr_trace.cpp b/erts/emulator/beam/jit/x86/instr_trace.cpp
index f3a825775d..f6d7937f4e 100644
--- a/erts/emulator/beam/jit/x86/instr_trace.cpp
+++ b/erts/emulator/beam/jit/x86/instr_trace.cpp
@@ -32,14 +32,14 @@ extern "C"
* RET = export entry */
void BeamGlobalAssembler::emit_generic_bp_global() {
emit_enter_frame();
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
a.lea(ARG2, x86::qword_ptr(RET, offsetof(Export, info)));
load_x_reg_array(ARG3);
runtime_call<3>(erts_generic_breakpoint);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
/* This is technically a tail call so we must leave the current frame
* before jumping. Note that we might not leave the frame we entered
@@ -93,14 +93,14 @@ void BeamGlobalAssembler::emit_generic_bp_local() {
#endif
emit_enter_frame();
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
/* ARG2 is already set above */
load_x_reg_array(ARG3);
runtime_call<3>(erts_generic_breakpoint);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
/* This doesn't necessarily leave the frame entered above: see the
* corresponding comment in `generic_bp_global` */
@@ -109,10 +109,10 @@ void BeamGlobalAssembler::emit_generic_bp_local() {
a.cmp(RET, imm(BeamOpCodeAddr(op_i_debug_breakpoint)));
a.je(labels[debug_bp]);
+#ifdef NATIVE_ERLANG_STACK
/* Note that we don't restore our return addresses in the `debug_bp` case
* above, since it tail calls the error handler and thus never returns to
* module code or `call_nif_early`. */
-#ifdef NATIVE_ERLANG_STACK
a.push(TMP_MEM1q);
a.push(TMP_MEM2q);
#endif
@@ -127,10 +127,16 @@ void BeamGlobalAssembler::emit_generic_bp_local() {
void BeamGlobalAssembler::emit_debug_bp() {
Label error = a.newLabel();
+#ifndef NATIVE_ERLANG_STACK
+ /* We're never going to return to module code, so we have to discard the
+ * return addresses added by the breakpoint trampoline. */
+ a.add(x86::rsp, imm(sizeof(ErtsCodePtr[2])));
+#endif
+
emit_assert_erlang_stack();
emit_enter_frame();
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
/* Read and adjust the return address we saved in generic_bp_local. */
a.mov(ARG2, TMP_MEM1q);
@@ -142,7 +148,7 @@ void BeamGlobalAssembler::emit_debug_bp() {
a.mov(ARG4, imm(am_breakpoint));
runtime_call<4>(call_error_handler);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
emit_leave_frame();
a.test(RET, RET);
@@ -171,61 +177,65 @@ void BeamModuleAssembler::emit_return_trace() {
a.mov(ARG3, getXRef(0));
a.lea(ARG4, getYRef(1));
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapAlloc>();
a.mov(ARG1, c_p);
runtime_call<4>(return_trace);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapAlloc>();
- emit_deallocate(ArgWord(2));
+ emit_deallocate(ArgWord(BEAM_RETURN_TRACE_FRAME_SZ));
emit_return();
}
-void BeamModuleAssembler::emit_i_return_time_trace() {
+void BeamModuleAssembler::emit_i_call_trace_return() {
/* Pass prev_info if present (is a CP), otherwise null. */
a.mov(ARG2, getYRef(0));
- mov_imm(ARG3, 0);
+ mov_imm(ARG4, 0);
a.test(ARG2, imm(_CPMASK));
a.lea(ARG2, x86::qword_ptr(ARG2, -(Sint)sizeof(ErtsCodeInfo)));
- a.cmovnz(ARG2, ARG3);
+ a.cmovnz(ARG2, ARG4);
+ a.mov(ARG3, getYRef(1));
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eHeapAlloc>();
a.mov(ARG1, c_p);
- runtime_call<2>(erts_trace_time_return);
+ runtime_call<3>(erts_call_trace_return);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eHeapAlloc>();
- emit_deallocate(ArgWord(1));
+ emit_deallocate(ArgWord(BEAM_RETURN_CALL_ACC_TRACE_FRAME_SZ));
emit_return();
}
void BeamModuleAssembler::emit_i_return_to_trace() {
- emit_enter_runtime<Update::eStack | Update::eHeap>();
+ /* Remove our stack frame so that `beam_jit_return_to_trace` can inspect
+ * the next one.
+ *
+ * (This doesn't do anything if the native stack is used.) */
+ emit_deallocate(ArgWord(BEAM_RETURN_TO_TRACE_FRAME_SZ));
+
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
runtime_call<1>(beam_jit_return_to_trace);
- emit_leave_runtime<Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
- /* Remove the zero-sized stack frame. (Will actually do nothing if
- * the native stack is used.) */
- emit_deallocate(ArgWord(0));
emit_return();
}
void BeamModuleAssembler::emit_i_hibernate() {
Label error = a.newLabel();
- emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_enter_runtime<Update::eReductions | Update::eHeapAlloc>();
a.mov(ARG1, c_p);
load_x_reg_array(ARG2);
runtime_call<2>(erts_hibernate);
- emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
+ emit_leave_runtime<Update::eReductions | Update::eHeapAlloc>();
a.test(RET, RET);
a.je(error);
diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab
index 99cd96ac8b..ef57021d83 100644
--- a/erts/emulator/beam/jit/x86/ops.tab
+++ b/erts/emulator/beam/jit/x86/ops.tab
@@ -81,7 +81,7 @@ nif_start
i_generic_breakpoint
i_debug_breakpoint
-i_return_time_trace
+i_call_trace_return
i_return_to_trace
trace_jump W
i_yield
@@ -314,6 +314,14 @@ current_tuple Tuple Dst => current_tuple Tuple
i_get_tuple_element s P S
get_two_tuple_elements s P S S
+i_get_tuple_element Tuple Pos Dst | swap Reg1 Reg2 | equal(Dst, Reg1) =>
+ get_tuple_element_swap Tuple Pos Dst Reg2
+
+i_get_tuple_element Tuple Pos Dst | swap Reg2 Reg1 | equal(Dst, Reg1) =>
+ get_tuple_element_swap Tuple Pos Dst Reg2
+
+get_tuple_element_swap s P d d
+
#
# Exception raising instructions. Infrequently executed.
#
@@ -427,21 +435,48 @@ is_eq_exact Lbl LHS RHS | equal(LHS, RHS) => _
is_eq_exact Lbl C=c R=xy => is_eq_exact Lbl R C
is_eq_exact Lbl R=xy n => is_nil Lbl R
-is_eq_exact Lbl R=xy C=q => is_eq_exact_literal(Lbl, R, C)
is_ne_exact Lbl LHS RHS | equal(LHS, RHS) => jump Lbl
is_ne_exact Lbl C=c R=xy => is_ne_exact Lbl R C
-is_ne_exact Lbl R=xy C=q => i_is_ne_exact_literal Lbl R C
+is_eq_exact f s s
-i_is_eq_exact_literal/4
-i_is_eq_exact_literal f s c I
+is_ne_exact f s s
-i_is_ne_exact_literal f s c
+is_integer NotInt N0 | is_ge Small N1=xy Min=i | is_ge Large Max=i N2=xy |
+ equal(N0, N1) | equal(N1, N2) |
+ equal(NotInt, Small) | equal(Small, Large) =>
+ is_int_in_range NotInt N0 Min Max
-is_eq_exact f s s
+is_integer NotInt N0 | is_ge Large Max=i N2=xy | is_ge Small N1=xy Min=i |
+ equal(N0, N1) | equal(N1, N2) |
+ equal(NotInt, Small) | equal(Small, Large) =>
+ is_int_in_range NotInt N0 Min Max
-is_ne_exact f s s
+is_integer NotInt N0 | is_ge Fail N1=xy Min=i |
+ equal(N0, N1) | equal(NotInt, Fail) =>
+ is_int_ge NotInt N0 Min
+
+is_int_in_range f S c c
+is_int_ge f S c
+
+is_ge Small N1=xy Min=i | is_ge Large Max=i N2=xy | equal(N1, N2) =>
+ is_in_range Small Large N1 Min Max
+
+is_ge Large Max=i N2=xy | is_ge Small N1=xy Min=i | equal(N1, N2) =>
+ is_in_range Small Large N2 Min Max
+
+is_in_range f f S c c
+
+is_ge Small N1=xy A=i | is_lt Large B=i N2=xy | equal(N1, N2) =>
+ is_ge_lt Small Large N1 A B
+
+is_ge_lt f f S c c
+
+is_ge Fail1 N1=xy A=i | is_ge Fail2 N2=xy B=i | equal(N1, N2) =>
+ is_ge_ge Fail1 Fail2 N1 A B
+
+is_ge_ge f f S c c
is_lt f s s
is_ge f s s
@@ -675,6 +710,36 @@ bif1 Fail Bif=u$bif:erlang:get/1 Src=s Dst=d => get(Src, Dst)
bif2 Fail u$bif:erlang:element/2 S1=ixy S2 Dst => bif_element Fail S1 S2 Dst
bif_element j s s d
+bif1 Fail Bif=u$bif:erlang:node/1 Src=d Dst=d => bif_node Fail Src Dst
+bif_node j S d
+
+gc_bif1 Fail Live Bif=u$bif:erlang:bit_size/1 Src Dst=d =>
+ bif_bit_size Bif Fail Src Dst
+bif_bit_size b j s d
+
+gc_bif1 Fail Live Bif=u$bif:erlang:byte_size/1 Src Dst=d =>
+ bif_byte_size Bif Fail Src Dst
+bif_byte_size b j s d
+
+bif1 Fail Bif=u$bif:erlang:tuple_size/1 Src=d Dst=d =>
+ bif_tuple_size Bif Fail Src Dst
+bif_tuple_size b j S d
+
+bif2 Fail Bif=u$bif:erlang:map_get/2 Src1 Src2=xy Dst=d =>
+ bif_map_get Fail Src1 Src2 Dst
+bif_map_get j s s d
+
+bif2 Fail Bif=u$bif:erlang:is_map_key/2 Key Map=xy Dst=d =>
+ bif_is_map_key Bif Fail Key Map Dst
+bif_is_map_key b j s s d
+
+bif2 Fail Bif=u$bif:erlang:max/2 Src1 Src2 Dst =>
+ bif_max Src1 Src2 Dst
+bif2 Fail Bif=u$bif:erlang:min/2 Src1 Src2 Dst =>
+ bif_min Src1 Src2 Dst
+bif_max s s d
+bif_min s s d
+
bif1 Fail Bif S1 Dst | never_fails(Bif) => nofail_bif1 S1 Bif Dst
bif2 Fail Bif S1 S2 Dst | never_fails(Bif) => nofail_bif2 S1 S2 Bif Dst
@@ -683,6 +748,8 @@ bif2 Fail Bif S1 S2 Dst => i_bif2 S1 S2 Fail Bif Dst
nofail_bif2 S1=d S2 Bif Dst | is_eq_exact_bif(Bif) => bif_is_eq_exact S1 S2 Dst
nofail_bif2 S1=d S2 Bif Dst | is_ne_exact_bif(Bif) => bif_is_ne_exact S1 S2 Dst
+nofail_bif2 S1 S2 Bif Dst | is_ge_bif(Bif) => bif_is_ge S1 S2 Dst
+nofail_bif2 S1 S2 Bif Dst | is_lt_bif(Bif) => bif_is_lt S1 S2 Dst
i_get_hash c I d
i_get s d
@@ -700,6 +767,8 @@ i_bif3 s s s j b d
bif_is_eq_exact S s d
bif_is_ne_exact S s d
+bif_is_ge s s d
+bif_is_lt s s d
#
# Internal calls.
@@ -810,26 +879,38 @@ i_lambda_trampoline F f W W
i_breakpoint_trampoline
# ================================================================
-# New bit syntax matching (R11B).
+# New bit syntax matching for fixed sizes (from OTP 26).
+# ================================================================
+
+bs_match Fail Ctx Size Rest=* => bs_match(Fail, Ctx, Size, Rest)
+
+i_bs_match Fail Ctx Rest=* | test_heap Need Live =>
+ i_bs_match_test_heap Fail Ctx Need Live Rest
+
+i_bs_match f S *
+i_bs_match_test_heap f S I t *
+
+#
+# The following instruction is specially handled in beam_load.c
+# to produce a user-friendly message if a bad bs_match instruction
+# is encountered.
+#
+bad_bs_match/1
+bad_bs_match A | never() => _
+
+# ================================================================
+# Bit syntax matching (from R11B).
# ================================================================
%warm
-# Matching integers
+# Matching integers.
bs_match_string Fail Ms Bits Val => i_bs_match_string Ms Fail Bits Val
i_bs_match_string S f W M
# Fetching integers from binaries.
-bs_get_integer2 Fail=f Ms=xy Live=u Sz=sq Unit=u Flags=u Dst=d =>
- get_integer2(Fail, Ms, Live, Sz, Unit, Flags, Dst)
-
-i_bs_get_integer S f t t s d
-
-i_bs_get_integer_8 S t f d
-i_bs_get_integer_16 S t f d
-i_bs_get_integer_32 S t f d
-i_bs_get_integer_64 S t f t d
+bs_get_integer2 f S t s t t d
# Fetching binaries from binaries.
bs_get_binary2 Fail=f Ms=xy Live=u Sz=sq Unit=u Flags=u Dst=d =>
@@ -1186,28 +1267,9 @@ gc_bif2 Fail Live u$bif:erlang:sminus/2 S1 S2 Dst =>
gen_minus Fail Live S1 S2 Dst
#
-# Optimize addition and subtraction of small literals using
-# the i_increment/3 instruction (in bodies, not in guards).
-#
-
-gen_plus p Live Int=i Reg=d Dst =>
- increment(Reg, Int, Dst)
-gen_plus p Live Reg=d Int=i Dst =>
- increment(Reg, Int, Dst)
-
-gen_minus p Live Reg=d Int=i Dst | negation_is_small(Int) =>
- increment_from_minus(Reg, Int, Dst)
-
-#
# Arithmetic instructions.
#
-# It is OK to swap arguments for '+' in a guard. It is also
-# OK to turn minus into plus in a guard.
-gen_plus Fail=f Live S1=c S2 Dst => i_plus S2 S1 Fail Dst
-gen_minus Fail=f Live S1 S2=i Dst | negation_is_small(S2) =>
- plus_from_minus(Fail, Live, S1, S2, Dst)
-
gen_plus Fail Live S1 S2 Dst => i_plus S1 S2 Fail Dst
gen_minus Fail Live S1 S2 Dst => i_minus S1 S2 Fail Dst
@@ -1278,8 +1340,6 @@ gc_bif2 Fail Live u$bif:erlang:bxor/2 S1 S2 Dst =>
gc_bif1 Fail Live u$bif:erlang:bnot/1 Src Dst =>
i_bnot Fail Src Dst
-i_increment S W d
-
i_plus s s j d
i_minus s s j d
@@ -1324,6 +1384,13 @@ i_length_setup j t s
i_length j t d
#
+# Specialized guard BIFs.
+#
+
+gc_bif1 Fail Live Bif=u$bif:erlang:map_size/1 Src Dst=d => bif_map_size Fail Src Dst
+bif_map_size j s d
+
+#
# Guard BIFs.
#
gc_bif1 Fail Live Bif Src Dst => i_bif1 Src Fail Bif Dst
@@ -1380,3 +1447,9 @@ recv_marker_reserve S
recv_marker_bind S S
recv_marker_clear S
recv_marker_use S
+
+#
+# OTP 26
+#
+
+update_record a I s d I *
diff --git a/erts/emulator/beam/jit/x86/predicates.tab b/erts/emulator/beam/jit/x86/predicates.tab
index cd1230cd1a..c274aba598 100644
--- a/erts/emulator/beam/jit/x86/predicates.tab
+++ b/erts/emulator/beam/jit/x86/predicates.tab
@@ -2,7 +2,7 @@
//
// %CopyrightBegin%
//
-// Copyright Ericsson AB 2020-2021. All Rights Reserved.
+// Copyright Ericsson AB 2020-2023. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ pred.is_mfa_bif(M, F, A) {
pred.never_fails(Bif) {
static Eterm nofail_bifs[] =
{am_Neqeq,
- am_Le,
+ am_Lt,
am_Neq,
am_Eq,
am_Le,
@@ -107,3 +107,25 @@ pred.consecutive_words(S1, D1, S2, D2) {
return S1.type == S2.type && S1.val + 1 == S2.val &&
D1.type == D2.type && D1.val + 1 == D2.val;
}
+
+pred.is_ge_bif(Bif) {
+ Uint index = Bif.val;
+
+ if (Bif.type == TAG_u && index < S->beam.imports.count) {
+ BeamFile_ImportEntry *entry = &S->beam.imports.entries[index];
+
+ return entry->module == am_erlang && entry->function == am_Ge && entry->arity == 2;
+ }
+ return 0;
+}
+
+pred.is_lt_bif(Bif) {
+ Uint index = Bif.val;
+
+ if (Bif.type == TAG_u && index < S->beam.imports.count) {
+ BeamFile_ImportEntry *entry = &S->beam.imports.entries[index];
+
+ return entry->module == am_erlang && entry->function == am_Lt && entry->arity == 2;
+ }
+ return 0;
+}
diff --git a/erts/emulator/beam/module.c b/erts/emulator/beam/module.c
index ef16a6cf54..8776a1bf52 100644
--- a/erts/emulator/beam/module.c
+++ b/erts/emulator/beam/module.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,10 @@
#include "module.h"
#include "beam_catches.h"
+#ifdef BEAMASM
+# include "beam_asm.h"
+#endif
+
#ifdef DEBUG
# define IF_DEBUG(x) x
#else
@@ -74,6 +78,7 @@ void erts_module_instance_init(struct erl_module_instance* modi)
modi->nif = NULL;
modi->num_breakpoints = 0;
modi->num_traced_exports = 0;
+ modi->unsealed = 0;
}
static Module* module_alloc(Module* tmpl)
@@ -162,16 +167,18 @@ static Module* put_module(Eterm mod, IndexTable* mod_tab)
Module*
erts_put_module(Eterm mod)
{
- ERTS_LC_ASSERT(erts_initialized == 0
- || erts_has_code_write_permission());
+ ERTS_LC_ASSERT(erts_initialized == 0 || erts_has_code_load_permission());
return put_module(mod, &module_tables[erts_staging_code_ix()]);
}
-int erts_is_code_ptr_writable(struct erl_module_instance* modi,
- const void *ptr) {
+void *erts_writable_code_ptr(struct erl_module_instance *modi,
+ const void *ptr)
+{
const char *code_start, *code_end, *ptr_raw;
+ ASSERT(modi->unsealed);
+
code_start = (char*)modi->code_hdr;
code_end = code_start + modi->code_length;
ptr_raw = (const char*)ptr;
@@ -179,59 +186,62 @@ int erts_is_code_ptr_writable(struct erl_module_instance* modi,
(void)code_end;
(void)ptr_raw;
-#ifdef BEAMASM
- {
- const char *exec_mod_start, *rw_mod_start, *rw_mod_end;
+ ASSERT(ptr_raw >= code_start && ptr_raw < code_end);
- exec_mod_start = (const char*)modi->native_module_exec;
- rw_mod_start = (const char*)modi->native_module_rw;
+ {
+ const char *exec_mod_start;
+ char *rw_mod_start;
- rw_mod_end = rw_mod_start + modi->code_length +
- (exec_mod_start - code_start);
+ exec_mod_start = (const char*)modi->executable_region;
+ rw_mod_start = (char*)modi->writable_region;
- if (ptr_raw >= rw_mod_start && ptr_raw <= rw_mod_end) {
- return 1;
- }
+ ASSERT(code_start >= exec_mod_start);
- ASSERT(ptr_raw >= code_start && ptr_raw < code_end);
- return 0;
+ return (void*)(rw_mod_start + (ptr_raw - exec_mod_start));
}
-#else
- ASSERT(ptr_raw >= code_start && ptr_raw < code_end);
- return 1;
-#endif
}
-void *erts_writable_code_ptr(struct erl_module_instance* modi,
- const void *ptr) {
- const char *code_start, *code_end, *ptr_raw;
+#ifdef DEBUG
+/* Protected by code mod permission. */
+static struct erl_module_instance *unsealed_module = NULL;
+#endif
- ERTS_LC_ASSERT(erts_has_code_write_permission());
+void erts_unseal_module(struct erl_module_instance *modi) {
+ ERTS_LC_ASSERT(erts_initialized == 0 ||
+ erts_thr_progress_is_blocking() ||
+ erts_has_code_mod_permission());
+ ASSERT(unsealed_module == NULL && !modi->unsealed);
- code_start = (char*)modi->code_hdr;
- code_end = code_start + modi->code_length;
- ptr_raw = (const char*)ptr;
+#ifdef BEAMASM
+ beamasm_unseal_module(modi->executable_region,
+ modi->writable_region,
+ modi->code_length);
+#endif
- (void)code_end;
- (void)ptr_raw;
+#ifdef DEBUG
+ unsealed_module = modi;
+#endif
+ modi->unsealed = 1;
+}
- ASSERT(ptr_raw >= code_start && ptr_raw < code_end);
+void erts_seal_module(struct erl_module_instance *modi)
+{
+ ERTS_LC_ASSERT(erts_initialized == 0 ||
+ erts_thr_progress_is_blocking() ||
+ erts_has_code_mod_permission());
+ ASSERT(unsealed_module == modi && modi->unsealed == 1);
#ifdef BEAMASM
- {
- const char *exec_mod_start;
- char *rw_mod_start;
-
- exec_mod_start = (const char*)modi->native_module_exec;
- rw_mod_start = (char*)modi->native_module_rw;
-
- ASSERT(code_start >= exec_mod_start);
+ beamasm_flush_icache(modi->executable_region, modi->code_length);
+ beamasm_seal_module(modi->executable_region,
+ modi->writable_region,
+ modi->code_length);
+#endif
- return (void*)(rw_mod_start + (ptr_raw - exec_mod_start));
- }
-#else
- return (void*)ptr;
+#ifdef DEBUG
+ unsealed_module = NULL;
#endif
+ modi->unsealed = 0;
}
Module *module_code(int i, ErtsCodeIndex code_ix)
diff --git a/erts/emulator/beam/module.h b/erts/emulator/beam/module.h
index 2b087e644d..6bbadbd505 100644
--- a/erts/emulator/beam/module.h
+++ b/erts/emulator/beam/module.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,10 +32,11 @@ struct erl_module_instance {
int num_breakpoints;
int num_traced_exports;
-#if defined(BEAMASM)
- const void *native_module_exec;
- void *native_module_rw;
-#endif
+ const void *executable_region;
+ void *writable_region;
+
+ /* Protected by code modification permission. */
+ int unsealed;
};
typedef struct erl_module {
@@ -52,17 +53,24 @@ void erts_module_instance_init(struct erl_module_instance* modi);
Module* erts_get_module(Eterm mod, ErtsCodeIndex code_ix);
Module* erts_put_module(Eterm mod);
-/* Converts the given code pointer into a writable one.
+/** @brief Converts the given code pointer into a writable one. The module must
+ * have been made writable through \ref erts_unseal_module.
*
- * `ptr` must point within the given module. */
+ * @param[in] ptr Pointer to convert. Must point within the given module. */
void *erts_writable_code_ptr(struct erl_module_instance* modi,
const void *ptr);
-/* Debug function for asserting whether `ptr` is writable or not.
+/** @brief Opens a module for modification.
*
- * `ptr` must point within the given module. */
-int erts_is_code_ptr_writable(struct erl_module_instance* modi,
- const void *ptr);
+ * This may only be used for one module at a time. Remember to call
+ * \ref erts_seal_module before returning to Erlang code or unsealing another
+ * module. */
+void erts_unseal_module(struct erl_module_instance *modi);
+
+/** @brief Seals a previously unsealed module, changing page permissions,
+ * flushing code cache, et cetera as needed. The caller is responsible for
+ * setting up a code barrier. */
+void erts_seal_module(struct erl_module_instance *modi);
void init_module_table(void);
void module_start_staging(void);
diff --git a/erts/emulator/beam/packet_parser.c b/erts/emulator/beam/packet_parser.c
index 8ef009ca8d..a349c3ff84 100644
--- a/erts/emulator/beam/packet_parser.c
+++ b/erts/emulator/beam/packet_parser.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -230,7 +230,7 @@ struct tpkt_head {
unsigned char packet_length[2]; /* size incl header, big-endian (?) */
};
-void packet_parser_init()
+void packet_parser_init(void)
{
static int done = 0;
if (!done) {
@@ -293,7 +293,7 @@ int packet_get_length(enum PacketParseType htype,
case TCP_PB_LINE_LF: {
/* TCP_PB_LINE_LF: [Data ... Delimiter] */
const char* ptr2;
- if ((ptr2 = memchr(ptr, delimiter, n)) == NULL) {
+ if ((ptr2 = sys_memchr(ptr, delimiter, n)) == NULL) {
if (n > max_plen && max_plen != 0) { /* packet full */
DEBUGF((" => packet full (no NL)=%d\r\n", n));
goto error;
@@ -407,7 +407,7 @@ int packet_get_length(enum PacketParseType htype,
}
while (1) {
- const char* ptr2 = memchr(ptr1, '\n', len);
+ const char* ptr2 = sys_memchr(ptr1, '\n', len);
if (ptr2 == NULL) {
if (max_plen != 0) {
@@ -518,8 +518,9 @@ static void
http_parse_absoluteURI(PacketHttpURI* uri, const char* uri_ptr, int uri_len)
{
const char* p;
+ const char* v;
- if ((p = memchr(uri_ptr, '/', uri_len)) == NULL) {
+ if ((p = sys_memchr(uri_ptr, '/', uri_len)) == NULL) {
/* host [":" port] */
uri->s2_ptr = "/";
uri->s2_len = 1;
@@ -533,15 +534,39 @@ http_parse_absoluteURI(PacketHttpURI* uri, const char* uri_ptr, int uri_len)
uri->s1_ptr = uri_ptr;
uri->port = 0; /* undefined */
- /* host[:port] */
- if ((p = memchr(uri_ptr, ':', uri_len)) == NULL) {
+ if ((p = sys_memchr(uri_ptr, ':', uri_len)) == NULL) {
uri->s1_len = uri_len;
}
+ /* ipv6
+ * eg [::1]:4000
+ * eg [FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80
+ */
+ else if (sys_memchr(uri_ptr, '[', uri_len) == uri_ptr &&
+ (v = sys_memchr(uri_ptr, ']', uri_len)) != NULL) {
+ int n = (v - uri_ptr) + 1;
+ int port = 0;
+ uri->s1_len = n;
+ n = uri_len - (n+1);
+ // parse port if the next char is `:`
+ if (sys_memrchr(uri_ptr, ':', uri_len) == v + 1) {
+ // Skip over `]:`
+ v = v + 2;
+ while(n && isdigit((int) *v)) {
+ port = port*10 + (*v - '0');
+ n--;
+ v++;
+ }
+ if (n==0 && port!=0)
+ uri->port = port;
+ }
+ }
+ /* host[:port] */
else {
int n = (p - uri_ptr);
int port = 0;
uri->s1_len = n;
n = uri_len - (n+1);
+ // Skip over port delimiter `:`
p++;
while(n && isdigit((int) *p)) {
port = port*10 + (*p - '0');
@@ -607,7 +632,7 @@ static void http_parse_uri(PacketHttpURI* uri, const char* uri_ptr, int uri_len)
}
else {
char* ptr;
- if ((ptr = memchr(uri_ptr, ':', uri_len)) == NULL) {
+ if ((ptr = sys_memchr(uri_ptr, ':', uri_len)) == NULL) {
uri->type = URI_STRING;
uri->s1_ptr = uri_ptr;
uri->s1_len = uri_len;
diff --git a/erts/emulator/beam/predicates.tab b/erts/emulator/beam/predicates.tab
index 7ebd5bcb2b..4bccce577f 100644
--- a/erts/emulator/beam/predicates.tab
+++ b/erts/emulator/beam/predicates.tab
@@ -184,18 +184,6 @@ pred.binary_too_big(Size) {
(Size.type == TAG_u && ((Size.val >> (8*sizeof(Uint)-3)) != 0));
}
-
-// Test whether the negation of the given number is small.
-pred.negation_is_small(Int) {
- /*
- * Check for the rare case of overflow in BeamInstr (UWord) -> Sint.
- * Cast to the correct type before using IS_SSMALL (Sint).
- */
- return Int.type == TAG_i &&
- !(Int.val & ~((((BeamInstr)1) << ((sizeof(Sint)*8)-1))-1)) &&
- IS_SSMALL(-((Sint)Int.val));
-}
-
// Mark this label. Always succeeds.
pred.smp_mark_target_label(L) {
ASSERT(L.type == TAG_f);
diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h
index 20b0571e43..c96fd618d2 100644
--- a/erts/emulator/beam/sys.h
+++ b/erts/emulator/beam/sys.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -608,7 +608,7 @@ extern erts_tsd_key_t erts_is_crash_dumping_key;
static unsigned long zero_value = 0, one_value = 1;
# define SET_BLOCKING(fd) { if (ioctlsocket((fd), FIONBIO, &zero_value) != 0) fprintf(stderr, "Error setting socket to non-blocking: %d\n", WSAGetLastError()); }
# define SET_NONBLOCKING(fd) ioctlsocket((fd), FIONBIO, &one_value)
-
+# define ERRNO_BLOCK EAGAIN /* We use the posix way for windows */
# else
# ifdef NB_FIONBIO /* Old BSD */
# include <sys/ioctl.h>
@@ -847,6 +847,7 @@ int sys_double_to_chars(double, char*, size_t);
int sys_double_to_chars_ext(double, char*, size_t, size_t);
int sys_double_to_chars_fast(double, char*, int, int, int);
void sys_get_pid(char *, size_t);
+int sys_get_hostname(char *buf, size_t size);
/* erl_drv_get/putenv have been implicitly 8-bit for so long that we can't
* change them without breaking things on Windows. Their return values are
@@ -1079,6 +1080,8 @@ ERTS_GLB_INLINE void *sys_memmove(void *dest, const void *src, size_t n);
ERTS_GLB_INLINE int sys_memcmp(const void *s1, const void *s2, size_t n);
ERTS_GLB_INLINE void *sys_memset(void *s, int c, size_t n);
ERTS_GLB_INLINE void *sys_memzero(void *s, size_t n);
+ERTS_GLB_INLINE void *sys_memchr(const void *s, int c, size_t n);
+ERTS_GLB_INLINE void *sys_memrchr(const void *s, int c, size_t n);
ERTS_GLB_INLINE int sys_strcmp(const char *s1, const char *s2);
ERTS_GLB_INLINE int sys_strncmp(const char *s1, const char *s2, size_t n);
ERTS_GLB_INLINE char *sys_strcpy(char *dest, const char *src);
@@ -1112,6 +1115,26 @@ ERTS_GLB_INLINE void *sys_memzero(void *s, size_t n)
ASSERT(s != NULL);
return memset(s,'\0',n);
}
+ERTS_GLB_INLINE void *sys_memchr(const void *s, int c, size_t n)
+{
+ ASSERT(s != NULL);
+ return (void*)memchr(s, c, n);
+}
+ERTS_GLB_INLINE void *sys_memrchr(const void *s, int c, size_t n)
+{
+ ASSERT(s != NULL);
+#ifdef HAVE_MEMRCHR
+ return (void*)memrchr(s, c, n);
+#else
+ {
+ const unsigned char* ptr = (const unsigned char*)s + n;
+ while (ptr != s)
+ if (*(--ptr) == (unsigned char)c)
+ return (void*)ptr;
+ return NULL;
+ }
+#endif
+}
ERTS_GLB_INLINE int sys_strcmp(const char *s1, const char *s2)
{
ASSERT(s1 != NULL && s2 != NULL);
diff --git a/erts/emulator/beam/utils.c b/erts/emulator/beam/utils.c
index 1baeb44835..8e2b13136b 100644
--- a/erts/emulator/beam/utils.c
+++ b/erts/emulator/beam/utils.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -108,9 +108,11 @@ void *ycf_debug_get_stack_start(void) {
#endif
#if defined(DEBUG)
+# define IF_DEBUG(X) X
# define DBG_RANDOM_REDS(REDS, SEED) \
((REDS) * 0.01 * erts_sched_local_random_float(SEED))
#else
+# define IF_DEBUG(X)
# define DBG_RANDOM_REDS(REDS, SEED) (REDS)
#endif
@@ -764,1731 +766,6 @@ erts_bld_atom_2uint_3tup_list(Uint **hpp, Uint *szp, Sint length,
return res;
}
-/* *\
- * *
-\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
-
-/* make a hash index from an erlang term */
-
-/*
-** There are two hash functions.
-**
-** make_hash: A hash function that will give the same values for the same
-** terms regardless of the internal representation. Small integers are
-** hashed using the same algorithm as bignums and bignums are hashed
-** independent of the CPU endianess.
-** Make_hash also hashes pids, ports and references like 32 bit numbers
-** (but with different constants).
-** make_hash() is called from the bif erlang:phash/2
-**
-** The idea behind the hash algorithm is to produce values suitable for
-** linear dynamic hashing. We cannot choose the range at all while hashing
-** (it's not even supplied to the hashing functions). The good old algorithm
-** [H = H*C+X mod M, where H is the hash value, C is a "random" constant(or M),
-** M is the range, preferably a prime, and X is each byte value] is therefore
-** modified to:
-** H = H*C+X mod 2^32, where C is a large prime. This gives acceptable
-** "spreading" of the hashes, so that later modulo calculations also will give
-** acceptable "spreading" in the range.
-** We really need to hash on bytes, otherwise the
-** upper bytes of a word will be less significant than the lower ones. That's
-** not acceptable at all. For internal use one could maybe optimize by using
-** another hash function, that is less strict but faster. That is, however, not
-** implemented.
-**
-** Short semi-formal description of make_hash:
-**
-** In make_hash, the number N is treated like this:
-** Abs(N) is hashed bytewise with the least significant byte, B(0), first.
-** The number of bytes (J) to calculate hash on in N is
-** (the number of _32_ bit words needed to store the unsigned
-** value of abs(N)) * 4.
-** X = FUNNY_NUMBER2
-** If N < 0, Y = FUNNY_NUMBER4 else Y = FUNNY_NUMBER3.
-** The hash value is Y*h(J) mod 2^32 where h(J) is calculated like
-** h(0) = <initial hash>
-** h(i) = h(i-1)*X + B(i-1)
-** The above should hold regardless of internal representation.
-** Pids are hashed like small numbers but with different constants, as are
-** ports.
-** References are hashed like ports but only on the least significant byte.
-** Binaries are hashed on all bytes (not on the 15 first as in
-** make_broken_hash()).
-** Bytes in lists (possibly text strings) use a simpler multiplication inlined
-** in the handling of lists, that is an optimization.
-** Everything else is like in the old hash (make_broken_hash()).
-**
-** make_hash2() is faster than make_hash, in particular for bignums
-** and binaries, and produces better hash values.
-*/
-
-/* some prime numbers just above 2 ^ 28 */
-
-#define FUNNY_NUMBER1 268440163
-#define FUNNY_NUMBER2 268439161
-#define FUNNY_NUMBER3 268435459
-#define FUNNY_NUMBER4 268436141
-#define FUNNY_NUMBER5 268438633
-#define FUNNY_NUMBER6 268437017
-#define FUNNY_NUMBER7 268438039
-#define FUNNY_NUMBER8 268437511
-#define FUNNY_NUMBER9 268439627
-#define FUNNY_NUMBER10 268440479
-#define FUNNY_NUMBER11 268440577
-#define FUNNY_NUMBER12 268440581
-#define FUNNY_NUMBER13 268440593
-#define FUNNY_NUMBER14 268440611
-
-static Uint32
-hash_binary_bytes(Eterm bin, Uint sz, Uint32 hash)
-{
- byte* ptr;
- Uint bitoffs;
- Uint bitsize;
-
- ERTS_GET_BINARY_BYTES(bin, ptr, bitoffs, bitsize);
- if (bitoffs == 0) {
- while (sz--) {
- hash = hash*FUNNY_NUMBER1 + *ptr++;
- }
- if (bitsize > 0) {
- byte b = *ptr;
-
- b >>= 8 - bitsize;
- hash = (hash*FUNNY_NUMBER1 + b) * FUNNY_NUMBER12 + bitsize;
- }
- } else {
- Uint previous = *ptr++;
- Uint b;
- Uint lshift = bitoffs;
- Uint rshift = 8 - lshift;
-
- while (sz--) {
- b = (previous << lshift) & 0xFF;
- previous = *ptr++;
- b |= previous >> rshift;
- hash = hash*FUNNY_NUMBER1 + b;
- }
- if (bitsize > 0) {
- b = (previous << lshift) & 0xFF;
- previous = *ptr++;
- b |= previous >> rshift;
-
- b >>= 8 - bitsize;
- hash = (hash*FUNNY_NUMBER1 + b) * FUNNY_NUMBER12 + bitsize;
- }
- }
- return hash;
-}
-
-Uint32 make_hash(Eterm term_arg)
-{
- DECLARE_WSTACK(stack);
- Eterm term = term_arg;
- Eterm hash = 0;
- unsigned op;
-
-#define MAKE_HASH_TUPLE_OP (FIRST_VACANT_TAG_DEF)
-#define MAKE_HASH_TERM_ARRAY_OP (FIRST_VACANT_TAG_DEF+1)
-#define MAKE_HASH_CDR_PRE_OP (FIRST_VACANT_TAG_DEF+2)
-#define MAKE_HASH_CDR_POST_OP (FIRST_VACANT_TAG_DEF+3)
-
- /*
- ** Convenience macro for calculating a bytewise hash on an unsigned 32 bit
- ** integer.
- ** If the endianess is known, we could be smarter here,
- ** but that gives no significant speedup (on a sparc at least)
- */
-#define UINT32_HASH_STEP(Expr, Prime1) \
- do { \
- Uint32 x = (Uint32) (Expr); \
- hash = \
- (((((hash)*(Prime1) + (x & 0xFF)) * (Prime1) + \
- ((x >> 8) & 0xFF)) * (Prime1) + \
- ((x >> 16) & 0xFF)) * (Prime1) + \
- (x >> 24)); \
- } while(0)
-
-#define UINT32_HASH_RET(Expr, Prime1, Prime2) \
- UINT32_HASH_STEP(Expr, Prime1); \
- hash = hash * (Prime2); \
- break
-
-
- /*
- * Significant additions needed for real 64 bit port with larger fixnums.
- */
-
- /*
- * Note, for the simple 64bit port, not utilizing the
- * larger word size this function will work without modification.
- */
-tail_recur:
- op = tag_val_def(term);
-
- for (;;) {
- switch (op) {
- case NIL_DEF:
- hash = hash*FUNNY_NUMBER3 + 1;
- break;
- case ATOM_DEF:
- hash = hash*FUNNY_NUMBER1 +
- (atom_tab(atom_val(term))->slot.bucket.hvalue);
- break;
- case SMALL_DEF:
- {
- Sint y1 = signed_val(term);
- Uint y2 = y1 < 0 ? -(Uint)y1 : y1;
-
- UINT32_HASH_STEP(y2, FUNNY_NUMBER2);
-#if defined(ARCH_64)
- if (y2 >> 32)
- UINT32_HASH_STEP(y2 >> 32, FUNNY_NUMBER2);
-#endif
- hash *= (y1 < 0 ? FUNNY_NUMBER4 : FUNNY_NUMBER3);
- break;
- }
- case BINARY_DEF:
- {
- Uint sz = binary_size(term);
-
- hash = hash_binary_bytes(term, sz, hash);
- hash = hash*FUNNY_NUMBER4 + sz;
- break;
- }
- case FUN_DEF:
- {
- ErlFunThing* funp = (ErlFunThing *) fun_val(term);
-
- if (is_local_fun(funp)) {
-
- ErlFunEntry* fe = funp->entry.fun;
- Uint num_free = funp->num_free;
-
- hash = hash * FUNNY_NUMBER10 + num_free;
- hash = hash*FUNNY_NUMBER1 +
- (atom_tab(atom_val(fe->module))->slot.bucket.hvalue);
- hash = hash*FUNNY_NUMBER2 + fe->index;
- hash = hash*FUNNY_NUMBER2 + fe->old_uniq;
-
- if (num_free > 0) {
- if (num_free > 1) {
- WSTACK_PUSH3(stack, (UWord) &funp->env[1],
- (num_free-1), MAKE_HASH_TERM_ARRAY_OP);
- }
-
- term = funp->env[0];
- goto tail_recur;
- }
- } else {
- const ErtsCodeMFA *mfa = &funp->entry.exp->info.mfa;
-
- ASSERT(is_external_fun(funp) && funp->next == NULL);
-
- hash = hash * FUNNY_NUMBER11 + mfa->arity;
- hash = hash*FUNNY_NUMBER1 +
- (atom_tab(atom_val(mfa->module))->slot.bucket.hvalue);
- hash = hash*FUNNY_NUMBER1 +
- (atom_tab(atom_val(mfa->function))->slot.bucket.hvalue);
- }
- break;
- }
- case PID_DEF:
- /* only 15 bits... */
- UINT32_HASH_RET(internal_pid_number(term),FUNNY_NUMBER5,FUNNY_NUMBER6);
- case EXTERNAL_PID_DEF:
- /* only 15 bits... */
- UINT32_HASH_RET(external_pid_number(term),FUNNY_NUMBER5,FUNNY_NUMBER6);
- case PORT_DEF:
- case EXTERNAL_PORT_DEF: {
- Uint64 number = port_number(term);
- Uint32 low = (Uint32) (number & 0xffffffff);
- Uint32 high = (Uint32) ((number >> 32) & 0xffffffff);
- if (high)
- UINT32_HASH_STEP(high, FUNNY_NUMBER11);
- UINT32_HASH_RET(low,FUNNY_NUMBER9,FUNNY_NUMBER10);
- }
- case REF_DEF:
- UINT32_HASH_RET(internal_ref_numbers(term)[0],FUNNY_NUMBER9,FUNNY_NUMBER10);
- case EXTERNAL_REF_DEF:
- UINT32_HASH_RET(external_ref_numbers(term)[0],FUNNY_NUMBER9,FUNNY_NUMBER10);
- case FLOAT_DEF:
- {
- FloatDef ff;
- GET_DOUBLE(term, ff);
- if (ff.fd == 0.0f) {
- /* ensure positive 0.0 */
- ff.fd = erts_get_positive_zero_float();
- }
- hash = hash*FUNNY_NUMBER6 + (ff.fw[0] ^ ff.fw[1]);
- break;
- }
- case MAKE_HASH_CDR_PRE_OP:
- term = (Eterm) WSTACK_POP(stack);
- if (is_not_list(term)) {
- WSTACK_PUSH(stack, (UWord) MAKE_HASH_CDR_POST_OP);
- goto tail_recur;
- }
- /* fall through */
- case LIST_DEF:
- {
- Eterm* list = list_val(term);
- while(is_byte(*list)) {
- /* Optimization for strings.
- ** Note that this hash is different from a 'small' hash,
- ** as multiplications on a Sparc is so slow.
- */
- hash = hash*FUNNY_NUMBER2 + unsigned_val(*list);
-
- if (is_not_list(CDR(list))) {
- WSTACK_PUSH(stack, MAKE_HASH_CDR_POST_OP);
- term = CDR(list);
- goto tail_recur;
- }
- list = list_val(CDR(list));
- }
- WSTACK_PUSH2(stack, CDR(list), MAKE_HASH_CDR_PRE_OP);
- term = CAR(list);
- goto tail_recur;
- }
- case MAKE_HASH_CDR_POST_OP:
- hash *= FUNNY_NUMBER8;
- break;
-
- case BIG_DEF:
- /* Note that this is the exact same thing as the hashing of smalls.*/
- {
- Eterm* ptr = big_val(term);
- Uint n = BIG_SIZE(ptr);
- Uint k = n-1;
- ErtsDigit d;
- int is_neg = BIG_SIGN(ptr);
- Uint i;
- int j;
-
- for (i = 0; i < k; i++) {
- d = BIG_DIGIT(ptr, i);
- for(j = 0; j < sizeof(ErtsDigit); ++j) {
- hash = (hash*FUNNY_NUMBER2) + (d & 0xff);
- d >>= 8;
- }
- }
- d = BIG_DIGIT(ptr, k);
- k = sizeof(ErtsDigit);
-#if defined(ARCH_64)
- if (!(d >> 32))
- k /= 2;
-#endif
- for(j = 0; j < (int)k; ++j) {
- hash = (hash*FUNNY_NUMBER2) + (d & 0xff);
- d >>= 8;
- }
- hash *= is_neg ? FUNNY_NUMBER4 : FUNNY_NUMBER3;
- break;
- }
- case MAP_DEF:
- hash = hash*FUNNY_NUMBER13 + FUNNY_NUMBER14 + make_hash2(term);
- break;
- case TUPLE_DEF:
- {
- Eterm* ptr = tuple_val(term);
- Uint arity = arityval(*ptr);
-
- WSTACK_PUSH3(stack, (UWord) arity, (UWord)(ptr+1), (UWord) arity);
- op = MAKE_HASH_TUPLE_OP;
- }/*fall through*/
- case MAKE_HASH_TUPLE_OP:
- case MAKE_HASH_TERM_ARRAY_OP:
- {
- Uint i = (Uint) WSTACK_POP(stack);
- Eterm* ptr = (Eterm*) WSTACK_POP(stack);
- if (i != 0) {
- term = *ptr;
- WSTACK_PUSH3(stack, (UWord)(ptr+1), (UWord) i-1, (UWord) op);
- goto tail_recur;
- }
- if (op == MAKE_HASH_TUPLE_OP) {
- Uint32 arity = (Uint32) WSTACK_POP(stack);
- hash = hash*FUNNY_NUMBER9 + arity;
- }
- break;
- }
-
- default:
- erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_hash(0x%X,0x%X)\n", term, op);
- return 0;
- }
- if (WSTACK_ISEMPTY(stack)) break;
- op = WSTACK_POP(stack);
- }
- DESTROY_WSTACK(stack);
- return hash;
-
-#undef MAKE_HASH_TUPLE_OP
-#undef MAKE_HASH_TERM_ARRAY_OP
-#undef MAKE_HASH_CDR_PRE_OP
-#undef MAKE_HASH_CDR_POST_OP
-#undef UINT32_HASH_STEP
-#undef UINT32_HASH_RET
-}
-
-
-
-/* Hash function suggested by Bob Jenkins. */
-
-#define MIX(a,b,c) \
-do { \
- a -= b; a -= c; a ^= (c>>13); \
- b -= c; b -= a; b ^= (a<<8); \
- c -= a; c -= b; c ^= (b>>13); \
- a -= b; a -= c; a ^= (c>>12); \
- b -= c; b -= a; b ^= (a<<16); \
- c -= a; c -= b; c ^= (b>>5); \
- a -= b; a -= c; a ^= (c>>3); \
- b -= c; b -= a; b ^= (a<<10); \
- c -= a; c -= b; c ^= (b>>15); \
-} while(0)
-
-#define HCONST 0x9e3779b9UL /* the golden ratio; an arbitrary value */
-
-typedef struct {
- Uint32 a,b,c;
-} ErtsBlockHashHelperCtx;
-
-#define BLOCK_HASH_BYTES_PER_ITER 12
-
-/* The three functions below are separated into different functions even
- though they are always used together to make trapping and handling
- of unaligned binaries easier. Examples of how they are used can be
- found in block_hash and make_hash2_helper.*/
-static ERTS_INLINE
-void block_hash_setup(Uint32 initval,
- ErtsBlockHashHelperCtx* ctx /* out parameter */)
-{
- ctx->a = ctx->b = HCONST;
- ctx->c = initval; /* the previous hash value */
-}
-
-static ERTS_INLINE
-void block_hash_buffer(byte *buf,
- Uint buf_length,
- ErtsBlockHashHelperCtx* ctx /* out parameter */)
-{
- Uint len = buf_length;
- byte *k = buf;
- ASSERT(buf_length % BLOCK_HASH_BYTES_PER_ITER == 0);
- while (len >= BLOCK_HASH_BYTES_PER_ITER) {
- ctx->a += (k[0] +((Uint32)k[1]<<8) +((Uint32)k[2]<<16) +((Uint32)k[3]<<24));
- ctx->b += (k[4] +((Uint32)k[5]<<8) +((Uint32)k[6]<<16) +((Uint32)k[7]<<24));
- ctx->c += (k[8] +((Uint32)k[9]<<8) +((Uint32)k[10]<<16)+((Uint32)k[11]<<24));
- MIX(ctx->a,ctx->b,ctx->c);
- k += BLOCK_HASH_BYTES_PER_ITER; len -= BLOCK_HASH_BYTES_PER_ITER;
- }
-}
-
-static ERTS_INLINE
-Uint32 block_hash_final_bytes(byte *buf,
- Uint buf_length,
- Uint full_length,
- ErtsBlockHashHelperCtx* ctx)
-{
- Uint len = buf_length;
- byte *k = buf;
- ctx->c += full_length;
- switch(len)
- { /* all the case statements fall through */
- case 11: ctx->c+=((Uint32)k[10]<<24);
- case 10: ctx->c+=((Uint32)k[9]<<16);
- case 9 : ctx->c+=((Uint32)k[8]<<8);
- /* the first byte of c is reserved for the length */
- case 8 : ctx->b+=((Uint32)k[7]<<24);
- case 7 : ctx->b+=((Uint32)k[6]<<16);
- case 6 : ctx->b+=((Uint32)k[5]<<8);
- case 5 : ctx->b+=k[4];
- case 4 : ctx->a+=((Uint32)k[3]<<24);
- case 3 : ctx->a+=((Uint32)k[2]<<16);
- case 2 : ctx->a+=((Uint32)k[1]<<8);
- case 1 : ctx->a+=k[0];
- /* case 0: nothing left to add */
- }
- MIX(ctx->a,ctx->b,ctx->c);
- return ctx->c;
-}
-
-static
-Uint32
-block_hash(byte *block, Uint block_length, Uint32 initval)
-{
- ErtsBlockHashHelperCtx ctx;
- Uint no_bytes_not_in_loop =
- (block_length % BLOCK_HASH_BYTES_PER_ITER);
- Uint no_bytes_to_process_in_loop =
- block_length - no_bytes_not_in_loop;
- byte *final_bytes = block + no_bytes_to_process_in_loop;
- block_hash_setup(initval, &ctx);
- block_hash_buffer(block,
- no_bytes_to_process_in_loop,
- &ctx);
- return block_hash_final_bytes(final_bytes,
- no_bytes_not_in_loop,
- block_length,
- &ctx);
-}
-
-typedef enum {
- tag_primary_list,
- arityval_subtag,
- hamt_subtag_head_flatmap,
- map_subtag,
- fun_subtag,
- neg_big_subtag,
- sub_binary_subtag_1,
- sub_binary_subtag_2,
- hash2_common_1,
- hash2_common_2,
- hash2_common_3,
-} ErtsMakeHash2TrapLocation;
-
-typedef struct {
- int c;
- Uint32 sh;
- Eterm* ptr;
-} ErtsMakeHash2Context_TAG_PRIMARY_LIST;
-
-typedef struct {
- int i;
- int arity;
- Eterm* elem;
-} ErtsMakeHash2Context_ARITYVAL_SUBTAG;
-
-typedef struct {
- Eterm *ks;
- Eterm *vs;
- int i;
- Uint size;
-} ErtsMakeHash2Context_HAMT_SUBTAG_HEAD_FLATMAP;
-
-typedef struct {
- Eterm* ptr;
- int i;
-} ErtsMakeHash2Context_MAP_SUBTAG;
-
-typedef struct {
- Uint num_free;
- Eterm* bptr;
-} ErtsMakeHash2Context_FUN_SUBTAG;
-
-typedef struct {
- Eterm* ptr;
- Uint i;
- Uint n;
- Uint32 con;
-} ErtsMakeHash2Context_NEG_BIG_SUBTAG;
-
-typedef struct {
- byte* bptr;
- Uint sz;
- Uint bitsize;
- Uint bitoffs;
- Uint no_bytes_processed;
- ErtsBlockHashHelperCtx block_hash_ctx;
- /* The following fields are only used when bitoffs != 0 */
- byte* buf;
- int done;
-
-} ErtsMakeHash2Context_SUB_BINARY_SUBTAG;
-
-typedef struct {
- int dummy__; /* Empty structs are not supported on all platforms */
-} ErtsMakeHash2Context_EMPTY;
-
-typedef struct {
- ErtsMakeHash2TrapLocation trap_location;
- /* specific to the trap location: */
- union {
- ErtsMakeHash2Context_TAG_PRIMARY_LIST tag_primary_list;
- ErtsMakeHash2Context_ARITYVAL_SUBTAG arityval_subtag;
- ErtsMakeHash2Context_HAMT_SUBTAG_HEAD_FLATMAP hamt_subtag_head_flatmap;
- ErtsMakeHash2Context_MAP_SUBTAG map_subtag;
- ErtsMakeHash2Context_FUN_SUBTAG fun_subtag;
- ErtsMakeHash2Context_NEG_BIG_SUBTAG neg_big_subtag;
- ErtsMakeHash2Context_SUB_BINARY_SUBTAG sub_binary_subtag_1;
- ErtsMakeHash2Context_SUB_BINARY_SUBTAG sub_binary_subtag_2;
- ErtsMakeHash2Context_EMPTY hash2_common_1;
- ErtsMakeHash2Context_EMPTY hash2_common_2;
- ErtsMakeHash2Context_EMPTY hash2_common_3;
- } trap_location_state;
- /* same for all trap locations: */
- Eterm term;
- Uint32 hash;
- Uint32 hash_xor_pairs;
- ErtsEStack stack;
-} ErtsMakeHash2Context;
-
-static int make_hash2_ctx_bin_dtor(Binary *context_bin) {
- ErtsMakeHash2Context* context = ERTS_MAGIC_BIN_DATA(context_bin);
- DESTROY_SAVED_ESTACK(&context->stack);
- if (context->trap_location == sub_binary_subtag_2 &&
- context->trap_location_state.sub_binary_subtag_2.buf != NULL) {
- erts_free(ERTS_ALC_T_PHASH2_TRAP, context->trap_location_state.sub_binary_subtag_2.buf);
- }
- return 1;
-}
-
-/* hash2_save_trap_state is called seldom so we want to avoid inlining */
-static ERTS_NOINLINE
-Eterm hash2_save_trap_state(Eterm state_mref,
- Uint32 hash_xor_pairs,
- Uint32 hash,
- Process* p,
- Eterm term,
- Eterm* ESTK_DEF_STACK(s),
- ErtsEStack s,
- ErtsMakeHash2TrapLocation trap_location,
- void* trap_location_state_ptr,
- size_t trap_location_state_size) {
- Binary* state_bin;
- ErtsMakeHash2Context* context;
- if (state_mref == THE_NON_VALUE) {
- Eterm* hp;
- state_bin = erts_create_magic_binary(sizeof(ErtsMakeHash2Context),
- make_hash2_ctx_bin_dtor);
- hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE);
- state_mref = erts_mk_magic_ref(&hp, &MSO(p), state_bin);
- } else {
- state_bin = erts_magic_ref2bin(state_mref);
- }
- context = ERTS_MAGIC_BIN_DATA(state_bin);
- context->term = term;
- context->hash = hash;
- context->hash_xor_pairs = hash_xor_pairs;
- ESTACK_SAVE(s, &context->stack);
- context->trap_location = trap_location;
- sys_memcpy(&context->trap_location_state,
- trap_location_state_ptr,
- trap_location_state_size);
- erts_set_gc_state(p, 0);
- BUMP_ALL_REDS(p);
- return state_mref;
-}
-#undef NOINLINE_HASH2_SAVE_TRAP_STATE
-
-/* Writes back a magic reference to *state_mref_write_back when the
- function traps */
-static ERTS_INLINE Uint32
-make_hash2_helper(Eterm term_param, const int can_trap, Eterm* state_mref_write_back, Process* p)
-{
- static const Uint ITERATIONS_PER_RED = 64;
- Uint32 hash;
- Uint32 hash_xor_pairs;
- Eterm term = term_param;
- ERTS_UNDEF(hash_xor_pairs, 0);
-
-/* (HCONST * {2, ..., 22}) mod 2^32 */
-#define HCONST_2 0x3c6ef372UL
-#define HCONST_3 0xdaa66d2bUL
-#define HCONST_4 0x78dde6e4UL
-#define HCONST_5 0x1715609dUL
-#define HCONST_6 0xb54cda56UL
-#define HCONST_7 0x5384540fUL
-#define HCONST_8 0xf1bbcdc8UL
-#define HCONST_9 0x8ff34781UL
-#define HCONST_10 0x2e2ac13aUL
-#define HCONST_11 0xcc623af3UL
-#define HCONST_12 0x6a99b4acUL
-#define HCONST_13 0x08d12e65UL
-#define HCONST_14 0xa708a81eUL
-#define HCONST_15 0x454021d7UL
-#define HCONST_16 0xe3779b90UL
-#define HCONST_17 0x81af1549UL
-#define HCONST_18 0x1fe68f02UL
-#define HCONST_19 0xbe1e08bbUL
-#define HCONST_20 0x5c558274UL
-#define HCONST_21 0xfa8cfc2dUL
-#define HCONST_22 0x98c475e6UL
-
-#define HASH_MAP_TAIL (_make_header(1,_TAG_HEADER_REF))
-#define HASH_MAP_PAIR (_make_header(2,_TAG_HEADER_REF))
-#define HASH_CDR (_make_header(3,_TAG_HEADER_REF))
-
-#define UINT32_HASH_2(Expr1, Expr2, AConst) \
- do { \
- Uint32 a,b; \
- a = AConst + (Uint32) (Expr1); \
- b = AConst + (Uint32) (Expr2); \
- MIX(a,b,hash); \
- } while(0)
-
-#define UINT32_HASH(Expr, AConst) UINT32_HASH_2(Expr, 0, AConst)
-
-#define SINT32_HASH(Expr, AConst) \
- do { \
- Sint32 y = (Sint32) (Expr); \
- if (y < 0) { \
- UINT32_HASH(-y, AConst); \
- /* Negative numbers are unnecessarily mixed twice. */ \
- } \
- UINT32_HASH(y, AConst); \
- } while(0)
-
-#define IS_SSMALL28(x) (((Uint) (((x) >> (28-1)) + 1)) < 2)
-
-#define NOT_SSMALL28_HASH(SMALL) \
- do { \
- Uint64 t; \
- Uint32 x, y; \
- Uint32 con; \
- if (SMALL < 0) { \
- con = HCONST_10; \
- t = (Uint64)(SMALL * (-1)); \
- } else { \
- con = HCONST_11; \
- t = SMALL; \
- } \
- x = t & 0xffffffff; \
- y = t >> 32; \
- UINT32_HASH_2(x, y, con); \
- } while(0)
-
-#ifdef ARCH_64
-# define POINTER_HASH(Ptr, AConst) UINT32_HASH_2((Uint32)(UWord)(Ptr), (((UWord)(Ptr)) >> 32), AConst)
-#else
-# define POINTER_HASH(Ptr, AConst) UINT32_HASH(Ptr, AConst)
-#endif
-
-#define TRAP_LOCATION_NO_RED(location_name) \
- do { \
- if(can_trap && iterations_until_trap <= 0) { \
- *state_mref_write_back = \
- hash2_save_trap_state(state_mref, \
- hash_xor_pairs, \
- hash, \
- p, \
- term, \
- ESTK_DEF_STACK(s), \
- s, \
- location_name, \
- &ctx, \
- sizeof(ctx)); \
- return 0; \
- L_##location_name: \
- ctx = context->trap_location_state. location_name; \
- } \
- } while(0)
-
-#define TRAP_LOCATION(location_name) \
- do { \
- if (can_trap) { \
- iterations_until_trap--; \
- TRAP_LOCATION_NO_RED(location_name); \
- } \
- } while(0)
-
-#define TRAP_LOCATION_NO_CTX(location_name) \
- do { \
- ErtsMakeHash2Context_EMPTY ctx; \
- TRAP_LOCATION(location_name); \
- } while(0)
-
- /* Optimization. Simple cases before declaration of estack. */
- if (primary_tag(term) == TAG_PRIMARY_IMMED1) {
- switch (term & _TAG_IMMED1_MASK) {
- case _TAG_IMMED1_IMMED2:
- switch (term & _TAG_IMMED2_MASK) {
- case _TAG_IMMED2_ATOM:
- /* Fast, but the poor hash value should be mixed. */
- return atom_tab(atom_val(term))->slot.bucket.hvalue;
- }
- break;
- case _TAG_IMMED1_SMALL:
- {
- Sint small = signed_val(term);
- if (SMALL_BITS > 28 && !IS_SSMALL28(small)) {
- hash = 0;
- NOT_SSMALL28_HASH(small);
- return hash;
- }
- hash = 0;
- SINT32_HASH(small, HCONST);
- return hash;
- }
- }
- };
- {
- Eterm tmp;
- long max_iterations = 0;
- long iterations_until_trap = 0;
- Eterm state_mref = THE_NON_VALUE;
- ErtsMakeHash2Context* context = NULL;
- DECLARE_ESTACK(s);
- ESTACK_CHANGE_ALLOCATOR(s, ERTS_ALC_T_SAVED_ESTACK);
- if(can_trap){
-#ifdef DEBUG
- (void)ITERATIONS_PER_RED;
- iterations_until_trap = max_iterations =
- (1103515245 * (ERTS_BIF_REDS_LEFT(p)) + 12345) % 227;
-#else
- iterations_until_trap = max_iterations =
- ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(p);
-#endif
- }
- if (can_trap && is_internal_magic_ref(term)) {
- Binary* state_bin;
- state_mref = term;
- state_bin = erts_magic_ref2bin(state_mref);
- if (ERTS_MAGIC_BIN_DESTRUCTOR(state_bin) == make_hash2_ctx_bin_dtor) {
- /* Restore state after a trap */
- context = ERTS_MAGIC_BIN_DATA(state_bin);
- term = context->term;
- hash = context->hash;
- hash_xor_pairs = context->hash_xor_pairs;
- ESTACK_RESTORE(s, &context->stack);
- ASSERT(p->flags & F_DISABLE_GC);
- erts_set_gc_state(p, 1);
- switch (context->trap_location) {
- case hash2_common_3: goto L_hash2_common_3;
- case tag_primary_list: goto L_tag_primary_list;
- case arityval_subtag: goto L_arityval_subtag;
- case hamt_subtag_head_flatmap: goto L_hamt_subtag_head_flatmap;
- case map_subtag: goto L_map_subtag;
- case fun_subtag: goto L_fun_subtag;
- case neg_big_subtag: goto L_neg_big_subtag;
- case sub_binary_subtag_1: goto L_sub_binary_subtag_1;
- case sub_binary_subtag_2: goto L_sub_binary_subtag_2;
- case hash2_common_1: goto L_hash2_common_1;
- case hash2_common_2: goto L_hash2_common_2;
- }
- }
- }
- hash = 0;
- for (;;) {
- switch (primary_tag(term)) {
- case TAG_PRIMARY_LIST:
- {
- ErtsMakeHash2Context_TAG_PRIMARY_LIST ctx = {
- .c = 0,
- .sh = 0,
- .ptr = list_val(term)};
- while (is_byte(*ctx.ptr)) {
- /* Optimization for strings. */
- ctx.sh = (ctx.sh << 8) + unsigned_val(*ctx.ptr);
- if (ctx.c == 3) {
- UINT32_HASH(ctx.sh, HCONST_4);
- ctx.c = ctx.sh = 0;
- } else {
- ctx.c++;
- }
- term = CDR(ctx.ptr);
- if (is_not_list(term))
- break;
- ctx.ptr = list_val(term);
- TRAP_LOCATION(tag_primary_list);
- }
- if (ctx.c > 0)
- UINT32_HASH(ctx.sh, HCONST_4);
- if (is_list(term)) {
- tmp = CDR(ctx.ptr);
- ESTACK_PUSH(s, tmp);
- term = CAR(ctx.ptr);
- }
- }
- break;
- case TAG_PRIMARY_BOXED:
- {
- Eterm hdr = *boxed_val(term);
- ASSERT(is_header(hdr));
- switch (hdr & _TAG_HEADER_MASK) {
- case ARITYVAL_SUBTAG:
- {
- ErtsMakeHash2Context_ARITYVAL_SUBTAG ctx = {
- .i = 0,
- .arity = header_arity(hdr),
- .elem = tuple_val(term)};
- UINT32_HASH(ctx.arity, HCONST_9);
- if (ctx.arity == 0) /* Empty tuple */
- goto hash2_common;
- for (ctx.i = ctx.arity; ; ctx.i--) {
- term = ctx.elem[ctx.i];
- if (ctx.i == 1)
- break;
- ESTACK_PUSH(s, term);
- TRAP_LOCATION(arityval_subtag);
- }
- }
- break;
- case MAP_SUBTAG:
- {
- Uint size;
- ErtsMakeHash2Context_MAP_SUBTAG ctx = {
- .ptr = boxed_val(term) + 1,
- .i = 0};
- switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
- case HAMT_SUBTAG_HEAD_FLATMAP:
- {
- flatmap_t *mp = (flatmap_t *)flatmap_val(term);
- ErtsMakeHash2Context_HAMT_SUBTAG_HEAD_FLATMAP ctx = {
- .ks = flatmap_get_keys(mp),
- .vs = flatmap_get_values(mp),
- .i = 0,
- .size = flatmap_get_size(mp)};
- UINT32_HASH(ctx.size, HCONST_16);
- if (ctx.size == 0)
- goto hash2_common;
-
- /* We want a portable hash function that is *independent* of
- * the order in which keys and values are encountered.
- * We therefore calculate context independent hashes for all .
- * key-value pairs and then xor them together.
- */
- ESTACK_PUSH3(s, hash_xor_pairs, hash, HASH_MAP_TAIL);
- hash = 0;
- hash_xor_pairs = 0;
- for (ctx.i = ctx.size - 1; ctx.i >= 0; ctx.i--) {
- ESTACK_PUSH3(s, HASH_MAP_PAIR,
- ctx.vs[ctx.i], ctx.ks[ctx.i]);
- TRAP_LOCATION(hamt_subtag_head_flatmap);
- }
- goto hash2_common;
- }
-
- case HAMT_SUBTAG_HEAD_ARRAY:
- case HAMT_SUBTAG_HEAD_BITMAP:
- size = *ctx.ptr++;
- UINT32_HASH(size, HCONST_16);
- if (size == 0)
- goto hash2_common;
- ESTACK_PUSH3(s, hash_xor_pairs, hash, HASH_MAP_TAIL);
- hash = 0;
- hash_xor_pairs = 0;
- }
- switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
- case HAMT_SUBTAG_HEAD_ARRAY:
- ctx.i = 16;
- break;
- case HAMT_SUBTAG_HEAD_BITMAP:
- case HAMT_SUBTAG_NODE_BITMAP:
- ctx.i = hashmap_bitcount(MAP_HEADER_VAL(hdr));
- break;
- default:
- erts_exit(ERTS_ERROR_EXIT, "bad header");
- }
- while (ctx.i) {
- if (is_list(*ctx.ptr)) {
- Eterm* cons = list_val(*ctx.ptr);
- ESTACK_PUSH3(s, HASH_MAP_PAIR, CDR(cons), CAR(cons));
- }
- else {
- ASSERT(is_boxed(*ctx.ptr));
- if (is_tuple(*ctx.ptr)) { /* collision node */
- Eterm *coll_ptr = tuple_val(*ctx.ptr);
- Uint n = arityval(*coll_ptr);
- ASSERT(n >= 2);
- coll_ptr++;
- for (; n; n--, coll_ptr++) {
- Eterm* cons = list_val(*coll_ptr);
- ESTACK_PUSH3(s, HASH_MAP_PAIR, CDR(cons), CAR(cons));
- }
- }
- else
- ESTACK_PUSH(s, *ctx.ptr);
- }
- ctx.i--; ctx.ptr++;
- TRAP_LOCATION(map_subtag);
- }
- goto hash2_common;
- }
- break;
-
- case FUN_SUBTAG:
- {
- ErlFunThing* funp = (ErlFunThing *) fun_val(term);
-
- if (is_local_fun(funp)) {
- ErlFunEntry* fe = funp->entry.fun;
- ErtsMakeHash2Context_FUN_SUBTAG ctx = {
- .num_free = funp->num_free,
- .bptr = NULL};
-
- UINT32_HASH_2
- (ctx.num_free,
- atom_tab(atom_val(fe->module))->slot.bucket.hvalue,
- HCONST);
- UINT32_HASH_2
- (fe->index, fe->old_uniq, HCONST);
- if (ctx.num_free == 0) {
- goto hash2_common;
- } else {
- ctx.bptr = funp->env + ctx.num_free - 1;
- while (ctx.num_free-- > 1) {
- term = *ctx.bptr--;
- ESTACK_PUSH(s, term);
- TRAP_LOCATION(fun_subtag);
- }
- term = *ctx.bptr;
- }
- } else {
- Export *ep = funp->entry.exp;
-
- ASSERT(is_external_fun(funp) && funp->next == NULL);
-
- UINT32_HASH_2
- (ep->info.mfa.arity,
- atom_tab(atom_val(ep->info.mfa.module))->slot.bucket.hvalue,
- HCONST);
- UINT32_HASH
- (atom_tab(atom_val(ep->info.mfa.function))->slot.bucket.hvalue,
- HCONST_14);
-
- goto hash2_common;
- }
- }
- break;
- case REFC_BINARY_SUBTAG:
- case HEAP_BINARY_SUBTAG:
- case SUB_BINARY_SUBTAG:
- {
-#define BYTE_BITS 8
- ErtsMakeHash2Context_SUB_BINARY_SUBTAG ctx = {
- .bptr = 0,
- /* !!!!!!!!!!!!!!!!!!!! OBS !!!!!!!!!!!!!!!!!!!!
- *
- * The size is truncated to 32 bits on the line
- * below so that the code is compatible with old
- * versions of the code. This means that hash
- * values for binaries with a size greater than
- * 4GB do not take all bytes in consideration.
- *
- * !!!!!!!!!!!!!!!!!!!! OBS !!!!!!!!!!!!!!!!!!!!
- */
- .sz = (0xFFFFFFFF & binary_size(term)),
- .bitsize = 0,
- .bitoffs = 0,
- .no_bytes_processed = 0
- };
- Uint32 con = HCONST_13 + hash;
- Uint iters_for_bin = MAX(1, ctx.sz / BLOCK_HASH_BYTES_PER_ITER);
- ERTS_GET_BINARY_BYTES(term, ctx.bptr, ctx.bitoffs, ctx.bitsize);
- if (ctx.sz == 0 && ctx.bitsize == 0) {
- hash = con;
- } else if (ctx.bitoffs == 0 &&
- (!can_trap ||
- (iterations_until_trap - iters_for_bin) > 0)) {
- /* No need to trap while hashing binary */
- if (can_trap) iterations_until_trap -= iters_for_bin;
- hash = block_hash(ctx.bptr, ctx.sz, con);
- if (ctx.bitsize > 0) {
- UINT32_HASH_2(ctx.bitsize,
- (ctx.bptr[ctx.sz] >> (BYTE_BITS - ctx.bitsize)),
- HCONST_15);
- }
- } else if (ctx.bitoffs == 0) {
- /* Need to trap while hashing binary */
- ErtsBlockHashHelperCtx* block_hash_ctx = &ctx.block_hash_ctx;
- block_hash_setup(con, block_hash_ctx);
- do {
- Uint max_bytes_to_process =
- iterations_until_trap <= 0 ? BLOCK_HASH_BYTES_PER_ITER :
- iterations_until_trap * BLOCK_HASH_BYTES_PER_ITER;
- Uint bytes_left = ctx.sz - ctx.no_bytes_processed;
- Uint even_bytes_left =
- bytes_left - (bytes_left % BLOCK_HASH_BYTES_PER_ITER);
- Uint bytes_to_process =
- MIN(max_bytes_to_process, even_bytes_left);
- block_hash_buffer(&ctx.bptr[ctx.no_bytes_processed],
- bytes_to_process,
- block_hash_ctx);
- ctx.no_bytes_processed += bytes_to_process;
- iterations_until_trap -=
- MAX(1, bytes_to_process / BLOCK_HASH_BYTES_PER_ITER);
- TRAP_LOCATION_NO_RED(sub_binary_subtag_1);
- block_hash_ctx = &ctx.block_hash_ctx; /* Restore after trap */
- } while ((ctx.sz - ctx.no_bytes_processed) >=
- BLOCK_HASH_BYTES_PER_ITER);
- hash = block_hash_final_bytes(ctx.bptr +
- ctx.no_bytes_processed,
- ctx.sz - ctx.no_bytes_processed,
- ctx.sz,
- block_hash_ctx);
- if (ctx.bitsize > 0) {
- UINT32_HASH_2(ctx.bitsize,
- (ctx.bptr[ctx.sz] >> (BYTE_BITS - ctx.bitsize)),
- HCONST_15);
- }
- } else if (/* ctx.bitoffs != 0 && */
- (!can_trap ||
- (iterations_until_trap - iters_for_bin) > 0)) {
- /* No need to trap while hashing binary */
- Uint nr_of_bytes = ctx.sz + (ctx.bitsize != 0);
- byte *buf = erts_alloc(ERTS_ALC_T_TMP, nr_of_bytes);
- Uint nr_of_bits_to_copy = ctx.sz*BYTE_BITS+ctx.bitsize;
- if (can_trap) iterations_until_trap -= iters_for_bin;
- erts_copy_bits(ctx.bptr,
- ctx.bitoffs, 1, buf, 0, 1, nr_of_bits_to_copy);
- hash = block_hash(buf, ctx.sz, con);
- if (ctx.bitsize > 0) {
- UINT32_HASH_2(ctx.bitsize,
- (buf[ctx.sz] >> (BYTE_BITS - ctx.bitsize)),
- HCONST_15);
- }
- erts_free(ERTS_ALC_T_TMP, buf);
- } else /* ctx.bitoffs != 0 && */ {
-#ifdef DEBUG
-#define BINARY_BUF_SIZE (BLOCK_HASH_BYTES_PER_ITER * 3)
-#else
-#define BINARY_BUF_SIZE (BLOCK_HASH_BYTES_PER_ITER * 256)
-#endif
-#define BINARY_BUF_SIZE_BITS (BINARY_BUF_SIZE*BYTE_BITS)
- /* Need to trap while hashing binary */
- ErtsBlockHashHelperCtx* block_hash_ctx = &ctx.block_hash_ctx;
- Uint nr_of_bytes = ctx.sz + (ctx.bitsize != 0);
- ERTS_CT_ASSERT(BINARY_BUF_SIZE % BLOCK_HASH_BYTES_PER_ITER == 0);
- ctx.buf = erts_alloc(ERTS_ALC_T_PHASH2_TRAP,
- MIN(nr_of_bytes, BINARY_BUF_SIZE));
- block_hash_setup(con, block_hash_ctx);
- do {
- Uint bytes_left =
- ctx.sz - ctx.no_bytes_processed;
- Uint even_bytes_left =
- bytes_left - (bytes_left % BLOCK_HASH_BYTES_PER_ITER);
- Uint bytes_to_process =
- MIN(BINARY_BUF_SIZE, even_bytes_left);
- Uint nr_of_bits_left =
- (ctx.sz*BYTE_BITS+ctx.bitsize) -
- ctx.no_bytes_processed*BYTE_BITS;
- Uint nr_of_bits_to_copy =
- MIN(nr_of_bits_left, BINARY_BUF_SIZE_BITS);
- ctx.done = nr_of_bits_left == nr_of_bits_to_copy;
- erts_copy_bits(ctx.bptr + ctx.no_bytes_processed,
- ctx.bitoffs, 1, ctx.buf, 0, 1,
- nr_of_bits_to_copy);
- block_hash_buffer(ctx.buf,
- bytes_to_process,
- block_hash_ctx);
- ctx.no_bytes_processed += bytes_to_process;
- iterations_until_trap -=
- MAX(1, bytes_to_process / BLOCK_HASH_BYTES_PER_ITER);
- TRAP_LOCATION_NO_RED(sub_binary_subtag_2);
- block_hash_ctx = &ctx.block_hash_ctx; /* Restore after trap */
- } while (!ctx.done);
- nr_of_bytes = ctx.sz + (ctx.bitsize != 0);
- hash = block_hash_final_bytes(ctx.buf +
- (ctx.no_bytes_processed -
- ((nr_of_bytes-1) / BINARY_BUF_SIZE) * BINARY_BUF_SIZE),
- ctx.sz - ctx.no_bytes_processed,
- ctx.sz,
- block_hash_ctx);
- if (ctx.bitsize > 0) {
- Uint last_byte_index =
- nr_of_bytes - (((nr_of_bytes-1) / BINARY_BUF_SIZE) * BINARY_BUF_SIZE) -1;
- UINT32_HASH_2(ctx.bitsize,
- (ctx.buf[last_byte_index] >> (BYTE_BITS - ctx.bitsize)),
- HCONST_15);
- }
- erts_free(ERTS_ALC_T_PHASH2_TRAP, ctx.buf);
- context->trap_location_state.sub_binary_subtag_2.buf = NULL;
- }
- goto hash2_common;
-#undef BYTE_BITS
-#undef BINARY_BUF_SIZE
-#undef BINARY_BUF_SIZE_BITS
- }
- break;
- case POS_BIG_SUBTAG:
- case NEG_BIG_SUBTAG:
- {
- Eterm* big_val_ptr = big_val(term);
- ErtsMakeHash2Context_NEG_BIG_SUBTAG ctx = {
- .ptr = big_val_ptr,
- .i = 0,
- .n = BIG_SIZE(big_val_ptr),
- .con = BIG_SIGN(big_val_ptr) ? HCONST_10 : HCONST_11};
-#if D_EXP == 16
- do {
- Uint32 x, y;
- x = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
- x += (Uint32)(ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0) << 16;
- y = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
- y += (Uint32)(ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0) << 16;
- UINT32_HASH_2(x, y, ctx.con);
- TRAP_LOCATION(neg_big_subtag);
- } while (ctx.i < ctx.n);
-#elif D_EXP == 32
- do {
- Uint32 x, y;
- x = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
- y = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
- UINT32_HASH_2(x, y, ctx.con);
- TRAP_LOCATION(neg_big_subtag);
- } while (ctx.i < ctx.n);
-#elif D_EXP == 64
- do {
- Uint t;
- Uint32 x, y;
- ASSERT(ctx.i < ctx.n);
- t = BIG_DIGIT(ctx.ptr, ctx.i++);
- x = t & 0xffffffff;
- y = t >> 32;
- UINT32_HASH_2(x, y, ctx.con);
- TRAP_LOCATION(neg_big_subtag);
- } while (ctx.i < ctx.n);
-#else
-#error "unsupported D_EXP size"
-#endif
- goto hash2_common;
- }
- break;
- case REF_SUBTAG:
- /* All parts of the ref should be hashed. */
- UINT32_HASH(internal_ref_numbers(term)[0], HCONST_7);
- goto hash2_common;
- break;
- case EXTERNAL_REF_SUBTAG:
- /* All parts of the ref should be hashed. */
- UINT32_HASH(external_ref_numbers(term)[0], HCONST_7);
- goto hash2_common;
- break;
- case EXTERNAL_PID_SUBTAG:
- /* Only 15 bits are hashed. */
- UINT32_HASH(external_pid_number(term), HCONST_5);
- goto hash2_common;
- case EXTERNAL_PORT_SUBTAG: {
- Uint64 number = external_port_number(term);
- Uint32 low = (Uint32) (number & 0xffffffff);
- Uint32 high = (Uint32) ((number >> 32) & 0xffffffff);
- UINT32_HASH_2(low, high, HCONST_6);
- goto hash2_common;
- }
- case FLOAT_SUBTAG:
- {
- FloatDef ff;
- GET_DOUBLE(term, ff);
- if (ff.fd == 0.0f) {
- /* ensure positive 0.0 */
- ff.fd = erts_get_positive_zero_float();
- }
-#if defined(WORDS_BIGENDIAN) || defined(DOUBLE_MIDDLE_ENDIAN)
- UINT32_HASH_2(ff.fw[0], ff.fw[1], HCONST_12);
-#else
- UINT32_HASH_2(ff.fw[1], ff.fw[0], HCONST_12);
-#endif
- goto hash2_common;
- }
- break;
-
- default:
- erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_hash2(0x%X)\n", term);
- }
- }
- break;
- case TAG_PRIMARY_IMMED1:
- switch (term & _TAG_IMMED1_MASK) {
- case _TAG_IMMED1_PID:
- /* Only 15 bits are hashed. */
- UINT32_HASH(internal_pid_number(term), HCONST_5);
- goto hash2_common;
- case _TAG_IMMED1_PORT: {
- Uint64 number = internal_port_number(term);
- Uint32 low = (Uint32) (number & 0xffffffff);
- Uint32 high = (Uint32) ((number >> 32) & 0xffffffff);
- UINT32_HASH_2(low, high, HCONST_6);
- goto hash2_common;
- }
- case _TAG_IMMED1_IMMED2:
- switch (term & _TAG_IMMED2_MASK) {
- case _TAG_IMMED2_ATOM:
- if (hash == 0)
- /* Fast, but the poor hash value should be mixed. */
- hash = atom_tab(atom_val(term))->slot.bucket.hvalue;
- else
- UINT32_HASH(atom_tab(atom_val(term))->slot.bucket.hvalue,
- HCONST_3);
- goto hash2_common;
- case _TAG_IMMED2_NIL:
- if (hash == 0)
- hash = 3468870702UL;
- else
- UINT32_HASH(NIL_DEF, HCONST_2);
- goto hash2_common;
- default:
- erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_hash2(0x%X)\n", term);
- }
- case _TAG_IMMED1_SMALL:
- {
- Sint small = signed_val(term);
- if (SMALL_BITS > 28 && !IS_SSMALL28(small)) {
- NOT_SSMALL28_HASH(small);
- } else {
- SINT32_HASH(small, HCONST);
- }
-
- goto hash2_common;
- }
- }
- break;
- default:
- erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_hash2(0x%X)\n", term);
- hash2_common:
-
- /* Uint32 hash always has the hash value of the previous term,
- * compounded or otherwise.
- */
-
- if (ESTACK_ISEMPTY(s)) {
- DESTROY_ESTACK(s);
- if (can_trap) {
- BUMP_REDS(p, (max_iterations - iterations_until_trap) / ITERATIONS_PER_RED);
- ASSERT(!(p->flags & F_DISABLE_GC));
- }
- return hash;
- }
-
- term = ESTACK_POP(s);
-
- switch (term) {
- case HASH_MAP_TAIL: {
- hash = (Uint32) ESTACK_POP(s);
- UINT32_HASH(hash_xor_pairs, HCONST_19);
- hash_xor_pairs = (Uint32) ESTACK_POP(s);
- TRAP_LOCATION_NO_CTX(hash2_common_1);
- goto hash2_common;
- }
- case HASH_MAP_PAIR:
- hash_xor_pairs ^= hash;
- hash = 0;
- TRAP_LOCATION_NO_CTX(hash2_common_2);
- goto hash2_common;
- default:
- break;
- }
-
- }
- TRAP_LOCATION_NO_CTX(hash2_common_3);
- }
- }
-#undef TRAP_LOCATION_NO_RED
-#undef TRAP_LOCATION
-#undef TRAP_LOCATION_NO_CTX
-}
-
-Uint32
-make_hash2(Eterm term)
-{
- return make_hash2_helper(term, 0, NULL, NULL);
-}
-
-Uint32
-trapping_make_hash2(Eterm term, Eterm* state_mref_write_back, Process* p)
-{
- return make_hash2_helper(term, 1, state_mref_write_back, p);
-}
-
-#ifdef DBG_HASHMAP_COLLISION_BONANZA
-Uint32 erts_dbg_hashmap_collision_bonanza(Uint32 hash, Eterm key)
-{
-/*{
- static Uint32 hashvec[7] = {
- 0x02345678,
- 0x12345678,
- 0xe2345678,
- 0xf2345678,
- 0x12abcdef,
- 0x13abcdef,
- 0xcafebabe
- };
- hash = hashvec[hash % (sizeof(hashvec) / sizeof(hashvec[0]))];
- }*/
- const Uint32 bad_hash = (hash & 0x12482481) * 1442968193;
- const Uint32 bad_bits = hash % 67;
- if (bad_bits < 32) {
- /* Mix in a number of high good bits to get "randomly" close
- to the collision nodes */
- const Uint32 bad_mask = (1 << bad_bits) - 1;
- return (hash & ~bad_mask) | (bad_hash & bad_mask);
- }
- return bad_hash;
-}
-#endif
-
-/* Term hash function for maps, with a separate depth parameter */
-Uint32 make_map_hash(Eterm key) {
- Uint32 hash = 0;
-
- hash = make_internal_hash(key, hash);
-
-#ifdef DBG_HASHMAP_COLLISION_BONANZA
- hash = erts_dbg_hashmap_collision_bonanza(hash, key);
-#endif
- return hash;
-}
-
-/* Term hash function for internal use.
- *
- * Limitation #1: Is not "portable" in any way between different VM instances.
- *
- * Limitation #2: The hash value is only valid as long as the term exists
- * somewhere in the VM. Why? Because external pids, ports and refs are hashed
- * by mixing the node *pointer* value. If a node disappears and later reappears
- * with a new ErlNode struct, externals from that node will hash different than
- * before.
- *
- * The property "EVERY BIT of the term that is significant for equality
- * MUST BE USED AS INPUT FOR THE HASH" is nice but no longer crucial for the
- * hashmap implementation that now uses collision nodes at the bottom of
- * the HAMT when all hash bits are exhausted.
- *
- */
-
-#define CONST_HASH(AConst) \
-do { /* Lightweight mixing of constant (type info) */ \
- hash ^= AConst; \
- hash = (hash << 17) ^ (hash >> (32-17)); \
-} while (0)
-
-/*
- * Start with salt, 32-bit prime number, to avoid getting same hash as phash2
- * which can cause bad hashing in distributed ETS tables for example.
- */
-#define INTERNAL_HASH_SALT 3432918353U
-
-Uint32
-make_internal_hash(Eterm term, Uint32 salt)
-{
- Uint32 hash = salt ^ INTERNAL_HASH_SALT;
-
- /* Optimization. Simple cases before declaration of estack. */
- if (primary_tag(term) == TAG_PRIMARY_IMMED1) {
- #if ERTS_SIZEOF_ETERM == 8
- UINT32_HASH_2((Uint32)term, (Uint32)(term >> 32), HCONST);
- #elif ERTS_SIZEOF_ETERM == 4
- UINT32_HASH(term, HCONST);
- #else
- # error "No you don't"
- #endif
- return hash;
- }
- {
- Eterm tmp;
- DECLARE_ESTACK(s);
-
- for (;;) {
- switch (primary_tag(term)) {
- case TAG_PRIMARY_LIST:
- {
- int c = 0;
- Uint32 sh = 0;
- Eterm* ptr = list_val(term);
- while (is_byte(*ptr)) {
- /* Optimization for strings. */
- sh = (sh << 8) + unsigned_val(*ptr);
- if (c == 3) {
- UINT32_HASH(sh, HCONST_4);
- c = sh = 0;
- } else {
- c++;
- }
- term = CDR(ptr);
- if (is_not_list(term))
- break;
- ptr = list_val(term);
- }
- if (c > 0)
- UINT32_HASH_2(sh, (Uint32)c, HCONST_22);
-
- if (is_list(term)) {
- tmp = CDR(ptr);
- CONST_HASH(HCONST_17); /* Hash CAR in cons cell */
- ESTACK_PUSH(s, tmp);
- if (is_not_list(tmp)) {
- ESTACK_PUSH(s, HASH_CDR);
- }
- term = CAR(ptr);
- }
- }
- break;
- case TAG_PRIMARY_BOXED:
- {
- Eterm hdr = *boxed_val(term);
- ASSERT(is_header(hdr));
- switch (hdr & _TAG_HEADER_MASK) {
- case ARITYVAL_SUBTAG:
- {
- int i;
- int arity = header_arity(hdr);
- Eterm* elem = tuple_val(term);
- UINT32_HASH(arity, HCONST_9);
- if (arity == 0) /* Empty tuple */
- goto pop_next;
- for (i = arity; ; i--) {
- term = elem[i];
- if (i == 1)
- break;
- ESTACK_PUSH(s, term);
- }
- }
- break;
-
- case MAP_SUBTAG:
- {
- Eterm* ptr = boxed_val(term) + 1;
- Uint size;
- int i;
-
- /*
- * We rely on key-value iteration order being constant
- * for identical maps (in this VM instance).
- */
- switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
- case HAMT_SUBTAG_HEAD_FLATMAP:
- {
- flatmap_t *mp = (flatmap_t *)flatmap_val(term);
- Eterm *ks = flatmap_get_keys(mp);
- Eterm *vs = flatmap_get_values(mp);
- size = flatmap_get_size(mp);
- UINT32_HASH(size, HCONST_16);
- if (size == 0)
- goto pop_next;
-
- for (i = size - 1; i >= 0; i--) {
- ESTACK_PUSH(s, vs[i]);
- ESTACK_PUSH(s, ks[i]);
- }
- goto pop_next;
- }
- case HAMT_SUBTAG_HEAD_ARRAY:
- case HAMT_SUBTAG_HEAD_BITMAP:
- size = *ptr++;
- UINT32_HASH(size, HCONST_16);
- if (size == 0)
- goto pop_next;
- }
- switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
- case HAMT_SUBTAG_HEAD_ARRAY:
- i = 16;
- break;
- case HAMT_SUBTAG_HEAD_BITMAP:
- case HAMT_SUBTAG_NODE_BITMAP:
- i = hashmap_bitcount(MAP_HEADER_VAL(hdr));
- break;
- default:
- erts_exit(ERTS_ERROR_EXIT, "bad header");
- }
- while (i) {
- if (is_list(*ptr)) {
- Eterm* cons = list_val(*ptr);
- ESTACK_PUSH(s, CDR(cons));
- ESTACK_PUSH(s, CAR(cons));
- }
- else {
- ASSERT(is_boxed(*ptr));
- /* no special treatment of collision nodes needed,
- hash them as the tuples they are */
- ESTACK_PUSH(s, *ptr);
- }
- i--; ptr++;
- }
- goto pop_next;
- }
- break;
- case FUN_SUBTAG:
- {
- ErlFunThing* funp = (ErlFunThing *) fun_val(term);
-
- if (is_local_fun(funp)) {
- ErlFunEntry* fe = funp->entry.fun;
- Uint num_free = funp->num_free;
- UINT32_HASH_2(num_free, fe->module, HCONST_20);
- UINT32_HASH_2(fe->index, fe->old_uniq, HCONST_21);
- if (num_free == 0) {
- goto pop_next;
- } else {
- Eterm* bptr = funp->env + num_free - 1;
- while (num_free-- > 1) {
- term = *bptr--;
- ESTACK_PUSH(s, term);
- }
- term = *bptr;
- }
- } else {
- ASSERT(is_external_fun(funp) && funp->next == NULL);
-
- /* Assumes Export entries never move */
- POINTER_HASH(funp->entry.exp, HCONST_14);
- goto pop_next;
- }
- }
- break;
- case REFC_BINARY_SUBTAG:
- case HEAP_BINARY_SUBTAG:
- case SUB_BINARY_SUBTAG:
- {
- byte* bptr;
- Uint sz = binary_size(term);
- Uint32 con = HCONST_13 + hash;
- Uint bitoffs;
- Uint bitsize;
-
- ERTS_GET_BINARY_BYTES(term, bptr, bitoffs, bitsize);
- if (sz == 0 && bitsize == 0) {
- hash = con;
- } else {
- if (bitoffs == 0) {
- hash = block_hash(bptr, sz, con);
- if (bitsize > 0) {
- UINT32_HASH_2(bitsize, (bptr[sz] >> (8 - bitsize)),
- HCONST_15);
- }
- } else {
- byte* buf = (byte *) erts_alloc(ERTS_ALC_T_TMP,
- sz + (bitsize != 0));
- erts_copy_bits(bptr, bitoffs, 1, buf, 0, 1, sz*8+bitsize);
- hash = block_hash(buf, sz, con);
- if (bitsize > 0) {
- UINT32_HASH_2(bitsize, (buf[sz] >> (8 - bitsize)),
- HCONST_15);
- }
- erts_free(ERTS_ALC_T_TMP, (void *) buf);
- }
- }
- goto pop_next;
- }
- break;
- case POS_BIG_SUBTAG:
- case NEG_BIG_SUBTAG:
- {
- Eterm* ptr = big_val(term);
- Uint i = 0;
- Uint n = BIG_SIZE(ptr);
- Uint32 con = BIG_SIGN(ptr) ? HCONST_10 : HCONST_11;
-#if D_EXP == 16
- do {
- Uint32 x, y;
- x = i < n ? BIG_DIGIT(ptr, i++) : 0;
- x += (Uint32)(i < n ? BIG_DIGIT(ptr, i++) : 0) << 16;
- y = i < n ? BIG_DIGIT(ptr, i++) : 0;
- y += (Uint32)(i < n ? BIG_DIGIT(ptr, i++) : 0) << 16;
- UINT32_HASH_2(x, y, con);
- } while (i < n);
-#elif D_EXP == 32
- do {
- Uint32 x, y;
- x = i < n ? BIG_DIGIT(ptr, i++) : 0;
- y = i < n ? BIG_DIGIT(ptr, i++) : 0;
- UINT32_HASH_2(x, y, con);
- } while (i < n);
-#elif D_EXP == 64
- do {
- Uint t;
- Uint32 x, y;
- ASSERT(i < n);
- t = BIG_DIGIT(ptr, i++);
- x = t & 0xffffffff;
- y = t >> 32;
- UINT32_HASH_2(x, y, con);
- } while (i < n);
-#else
-#error "unsupported D_EXP size"
-#endif
- goto pop_next;
- }
- break;
- case REF_SUBTAG:
- UINT32_HASH(internal_ref_numbers(term)[0], HCONST_7);
- ASSERT(internal_ref_no_numbers(term) == 3);
- UINT32_HASH_2(internal_ref_numbers(term)[1],
- internal_ref_numbers(term)[2], HCONST_8);
- goto pop_next;
-
- case EXTERNAL_REF_SUBTAG:
- {
- ExternalThing* thing = external_thing_ptr(term);
-
- ASSERT(external_thing_ref_no_numbers(thing) == 3);
- /* See limitation #2 */
- #ifdef ARCH_64
- POINTER_HASH(thing->node, HCONST_7);
- UINT32_HASH(external_thing_ref_numbers(thing)[0], HCONST_7);
- #else
- UINT32_HASH_2(thing->node,
- external_thing_ref_numbers(thing)[0], HCONST_7);
- #endif
- UINT32_HASH_2(external_thing_ref_numbers(thing)[1],
- external_thing_ref_numbers(thing)[2], HCONST_8);
- goto pop_next;
- }
- case EXTERNAL_PID_SUBTAG: {
- ExternalThing* thing = external_thing_ptr(term);
- /* See limitation #2 */
- POINTER_HASH(thing->node, HCONST_5);
- UINT32_HASH_2(thing->data.pid.num, thing->data.pid.ser, HCONST_5);
- goto pop_next;
- }
- case EXTERNAL_PORT_SUBTAG: {
- ExternalThing* thing = external_thing_ptr(term);
- /* See limitation #2 */
- POINTER_HASH(thing->node, HCONST_6);
- UINT32_HASH_2(thing->data.ui32[0], thing->data.ui32[1], HCONST_6);
- goto pop_next;
- }
- case FLOAT_SUBTAG:
- {
- FloatDef ff;
- GET_DOUBLE(term, ff);
- if (ff.fd == 0.0f) {
- /* ensure positive 0.0 */
- ff.fd = erts_get_positive_zero_float();
- }
- UINT32_HASH_2(ff.fw[0], ff.fw[1], HCONST_12);
- goto pop_next;
- }
- default:
- erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_internal_hash(0x%X, %lu)\n", term, salt);
- }
- }
- break;
- case TAG_PRIMARY_IMMED1:
- #if ERTS_SIZEOF_ETERM == 8
- UINT32_HASH_2((Uint32)term, (Uint32)(term >> 32), HCONST);
- #else
- UINT32_HASH(term, HCONST);
- #endif
- goto pop_next;
-
- default:
- erts_exit(ERTS_ERROR_EXIT, "Invalid tag in make_internal_hash(0x%X, %lu)\n", term, salt);
-
- pop_next:
- if (ESTACK_ISEMPTY(s)) {
- DESTROY_ESTACK(s);
-
- return hash;
- }
-
- term = ESTACK_POP(s);
-
- switch (term) {
- case HASH_CDR:
- CONST_HASH(HCONST_18); /* Hash CDR i cons cell */
- goto pop_next;
- default:
- break;
- }
- }
- }
- }
-
-#undef CONST_HASH
-#undef HASH_MAP_TAIL
-#undef HASH_MAP_PAIR
-#undef HASH_CDR
-
-#undef UINT32_HASH_2
-#undef UINT32_HASH
-#undef SINT32_HASH
-}
-
-#undef HCONST
-#undef MIX
-
/* error_logger !
{log, Level, format, [args], #{ gl, pid, time, error_logger => #{tag, emulator => true} }}
*/
@@ -3225,6 +1502,110 @@ not_equal:
return 0;
}
+static
+Sint compare_flatmap_atom_keys(const Eterm* a_keys,
+ const Eterm* b_keys,
+ int n_atoms)
+{
+ Eterm min_key = THE_NON_VALUE;
+ Eterm a, b;
+ int ai, bi;
+ Sint res;
+
+ ASSERT(n_atoms > 0);
+ ASSERT(is_atom(a_keys[0]) && is_atom(b_keys[0]));
+ ASSERT(is_atom(a_keys[n_atoms-1]) || is_atom(b_keys[n_atoms-1]));
+
+ ai = n_atoms;
+ while (*a_keys == *b_keys) {
+ ASSERT(is_atom(*a_keys));
+ a_keys++;
+ b_keys++;
+ if (--ai == 0)
+ return 0;
+ }
+
+ /*
+ * Found atom key diff. Find the smallest unique atom.
+ * The atoms are sorted by atom index (not term order).
+ *
+ * Continue iterate atom key arrays by always advancing the one lagging
+ * behind atom index-wise. Identical atoms are skipped. An atom can only be
+ * candidate as minimal if we have passed that atom index in the other array
+ * (which means the atom did not exist in the other array).
+ *
+ * There can be different number of atom keys in the arrays (n_atoms is the
+ * larger count). We stop when either reaching the end or finding a non-atom.
+ * ERTS_UINT_MAX is used as an end marker while advancing the other one.
+ */
+ bi = ai;
+ a = *a_keys;
+ b = *b_keys;
+ IF_DEBUG(res = 0);
+ if (!is_atom(a)) {
+ ASSERT(is_atom(b));
+ return +1;
+ }
+ else if (!is_atom(b)) {
+ return -1;
+ }
+ do {
+ ASSERT(is_atom(a) || a == ERTS_UINT_MAX);
+ ASSERT(is_atom(b) || b == ERTS_UINT_MAX);
+ ASSERT(is_atom(a) || is_atom(b));
+
+ if (a < b) {
+ ASSERT(ai && is_atom(a));
+ if (is_non_value(min_key) || erts_cmp_atoms(a, min_key) < 0) {
+ min_key = a;
+ res = -1;
+ }
+ if (--ai) {
+ a = *(++a_keys);
+ if (is_not_atom(a))
+ a = ERTS_UINT_MAX;
+ }
+ else
+ a = ERTS_UINT_MAX;
+ }
+ else if (a > b) {
+ ASSERT(bi && is_atom(b));
+ if (is_non_value(min_key) || erts_cmp_atoms(b, min_key) < 0) {
+ min_key = b;
+ res = +1;
+ }
+ if (--bi) {
+ b = *(++b_keys);
+ if (is_not_atom(b))
+ b = ERTS_UINT_MAX;
+ }
+ else
+ b = ERTS_UINT_MAX;
+ }
+ else {
+ ASSERT(ai && bi && is_atom(a) && is_atom(b));
+ if (--ai) {
+ a = *(++a_keys);
+ if (is_not_atom(a))
+ a = ERTS_UINT_MAX;
+ }
+ else
+ a = ERTS_UINT_MAX;
+ if (--bi) {
+ b = *(++b_keys);
+ if (is_not_atom(b))
+ b = ERTS_UINT_MAX;
+ }
+ else
+ b = ERTS_UINT_MAX;
+ }
+ } while (~(a&b));
+
+ ASSERT(a == ERTS_UINT_MAX && b == ERTS_UINT_MAX);
+ ASSERT(is_atom(min_key));
+ ASSERT(res != 0);
+ return res;
+}
/*
@@ -3245,16 +1626,19 @@ Sint erts_cmp_compound(Eterm a, Eterm b, int exact, int eq_only);
*/
Sint erts_cmp_compound(Eterm a, Eterm b, int exact, int eq_only)
{
-#define PSTACK_TYPE struct erts_cmp_hashmap_state
- struct erts_cmp_hashmap_state {
+#define PSTACK_TYPE struct cmp_map_state
+ struct cmp_map_state {
Sint wstack_rollback;
- int was_exact;
- Eterm *ap;
- Eterm *bp;
+ int was_exact; /* hashmap only */
+ Eterm *atom_keys; /* flatmap only */
+ Eterm *ap, *bp; /* hashmap: kv-cons, flatmap: values */
Eterm min_key;
Sint cmp_res; /* result so far -1,0,+1 */
+#ifdef DEBUG
+ int is_hashmap;
+#endif
};
- PSTACK_DECLARE(hmap_stack, 1);
+ PSTACK_DECLARE(map_stack, 1);
WSTACK_DECLARE(stack);
WSTACK_DECLARE(b_stack); /* only used by hashmaps */
Eterm* aa;
@@ -3281,10 +1665,12 @@ Sint erts_cmp_compound(Eterm a, Eterm b, int exact, int eq_only)
#define HASHMAP_PHASE2_ARE_KEYS_EQUAL 5
#define HASHMAP_PHASE2_IS_MIN_KEY_A 6
#define HASHMAP_PHASE2_IS_MIN_KEY_B 7
+#define FLATMAP_ATOM_KEYS 8
+#define FLATMAP_ATOM_VALUES 9
+#define FLATMAP_ATOM_CMP_VALUES 10
-
-#define OP_WORD(OP) (((OP) << _TAG_PRIMARY_SIZE) | TAG_PRIMARY_HEADER)
-#define TERM_ARRAY_OP_WORD(SZ) OP_WORD(((SZ) << OP_BITS) | TERM_ARRAY_OP)
+#define OP_WORD(OP) (((OP) << _TAG_PRIMARY_SIZE) | TAG_PRIMARY_HEADER)
+#define OP_ARG_WORD(OP, SZ) OP_WORD(((SZ) << OP_BITS) | OP)
#define GET_OP(WORD) (ASSERT(is_header(WORD)), ((WORD) >> _TAG_PRIMARY_SIZE) & OP_MASK)
#define GET_OP_ARG(WORD) (ASSERT(is_header(WORD)), ((WORD) >> (OP_BITS + _TAG_PRIMARY_SIZE)))
@@ -3458,52 +1844,121 @@ tailrecur_ne:
goto term_array;
case (_TAG_HEADER_MAP >> _TAG_PRIMARY_SIZE) :
{
- struct erts_cmp_hashmap_state* sp;
+ struct cmp_map_state* sp;
if (is_flatmap_header(ahdr)) {
+ flatmap_t* afm = (flatmap_t*)flatmap_val(a);
+ flatmap_t* bfm;
if (!is_flatmap(b)) {
if (is_hashmap(b)) {
- aa = (Eterm *)flatmap_val(a);
- i = flatmap_get_size((flatmap_t*)aa) - hashmap_size(b);
- ASSERT(i != 0);
- RETURN_NEQ(i);
+ ASSERT(flatmap_get_size(afm) < hashmap_size(b));
+ RETURN_NEQ(-1);
}
a_tag = MAP_DEF;
goto mixed_types;
}
- aa = (Eterm *)flatmap_val(a);
- bb = (Eterm *)flatmap_val(b);
-
- i = flatmap_get_size((flatmap_t*)aa);
- if (i != flatmap_get_size((flatmap_t*)bb)) {
- RETURN_NEQ((int)(i - flatmap_get_size((flatmap_t*)bb)));
+ bfm = (flatmap_t*)flatmap_val(b);
+ i = flatmap_get_size(afm);
+ if (i != flatmap_get_size(bfm)) {
+ RETURN_NEQ((int)(i - flatmap_get_size(bfm)));
}
if (i == 0) {
goto pop_next;
}
- aa += 2;
- bb += 2;
if (exact) {
+ /*
+ * We only care about equality so we can compare
+ * the maps as two term arrays where the first
+ * element pair are the key tuples.
+ */
+ aa = &afm->keys;
+ bb = &bfm->keys;
i += 1; /* increment for tuple-keys */
goto term_array;
}
else {
- /* Value array */
- WSTACK_PUSH3(stack,(UWord)(bb+1),(UWord)(aa+1),TERM_ARRAY_OP_WORD(i));
- /* Switch back from 'exact' key compare */
- WSTACK_PUSH(stack,OP_WORD(SWITCH_EXACT_OFF_OP));
- /* Now do 'exact' compare of key tuples */
- a = *aa;
- b = *bb;
- exact = 1;
- goto bodyrecur;
+ Eterm* a_keys = flatmap_get_keys(afm);
+ Eterm* b_keys = flatmap_get_keys(bfm);
+ Eterm* a_vals = flatmap_get_values(afm);
+ Eterm* b_vals = flatmap_get_values(bfm);
+ int n_numbers; /* sorted before atoms */
+ int n_atoms;
+ int n_rest; /* sorted after atoms */
+ int n = 0;
+
+ /*
+ * All keys are sorted in term order except atoms
+ * which are sorted by atom index. The compare
+ * algorithm is optimized to only have to treat
+ * atoms specially and use the term order for other
+ * keys.
+ * The key arrays are divided into three possible
+ * partitions containing:
+ * #1. all numbers (< atoms)
+ * #2. atoms
+ * #3. only the rest (> atoms)
+ *
+ * The tree partions are compared separately in two
+ * phases, first only keys and then values.
+ */
+ while (n < i && !(is_atom(a_keys[n]) &&
+ is_atom(b_keys[n]))) {
+ ++n;
+ }
+ n_numbers = n;
+ while (n < i && (is_atom(a_keys[n]) ||
+ is_atom(b_keys[n]))) {
+ ++n;
+ }
+ n_atoms = n - n_numbers;
+ n_rest = i - n;
+
+ ASSERT(n_numbers + n_atoms + n_rest == i);
+ ASSERT(n_atoms || !n_rest);
+
+ if (n_rest) {
+ WSTACK_PUSH3(stack,
+ (UWord)&b_vals[n_numbers+n_atoms],
+ (UWord)&a_vals[n_numbers+n_atoms],
+ OP_ARG_WORD(TERM_ARRAY_OP,n_rest));
+ }
+ if (n_atoms) {
+ WSTACK_PUSH4(stack,
+ (UWord)&b_vals[n_numbers],
+ (UWord)&a_vals[n_numbers],
+ (UWord)&a_keys[n_numbers],
+ OP_ARG_WORD(FLATMAP_ATOM_VALUES,n_atoms));
+ }
+ if (n_numbers) {
+ WSTACK_PUSH3(stack, (UWord)b_vals, (UWord)a_vals,
+ OP_ARG_WORD(TERM_ARRAY_OP,n_numbers));
+ }
+ if (!exact) {
+ WSTACK_PUSH(stack, OP_WORD(SWITCH_EXACT_OFF_OP));
+ exact = 1;
+ }
+ if (n_rest) {
+ WSTACK_PUSH3(stack,
+ (UWord)&b_keys[n_numbers+n_atoms],
+ (UWord)&a_keys[n_numbers+n_atoms],
+ OP_ARG_WORD(TERM_ARRAY_OP,n_rest));
+ }
+ if (n_atoms) {
+ WSTACK_PUSH3(stack,
+ (UWord)&b_keys[n_numbers],
+ (UWord)&a_keys[n_numbers],
+ OP_ARG_WORD(FLATMAP_ATOM_KEYS,n_atoms));
+ }
+ if (n_numbers) {
+ WSTACK_PUSH3(stack, (UWord)b_keys, (UWord)a_keys,
+ OP_ARG_WORD(TERM_ARRAY_OP,n_numbers));
+ }
}
+ goto pop_next;
}
if (!is_hashmap(b)) {
if (is_flatmap(b)) {
- bb = (Eterm *)flatmap_val(b);
- i = hashmap_size(a) - flatmap_get_size((flatmap_t*)bb);
- ASSERT(i != 0);
- RETURN_NEQ(i);
+ ASSERT(hashmap_size(a) > flatmap_get_size(flatmap_val(b)));
+ RETURN_NEQ(1);
}
a_tag = MAP_DEF;
goto mixed_types;
@@ -3535,7 +1990,8 @@ tailrecur_ne:
one lagging behind key-wise.
*/
- sp = PSTACK_PUSH(hmap_stack);
+ sp = PSTACK_PUSH(map_stack);
+ IF_DEBUG(sp->is_hashmap = 1);
hashmap_iterator_init(&stack, a, 0);
hashmap_iterator_init(&b_stack, b, 0);
sp->ap = hashmap_iterator_next(&stack);
@@ -3911,7 +2367,8 @@ term_array: /* arrays in 'aa' and 'bb', length in 'i' */
goto not_equal;
}
} else {
- WSTACK_PUSH3(stack, (UWord)bb, (UWord)aa, TERM_ARRAY_OP_WORD(i));
+ WSTACK_PUSH3(stack, (UWord)bb, (UWord)aa,
+ OP_ARG_WORD(TERM_ARRAY_OP,i));
goto tailrecur_ne;
}
}
@@ -3923,7 +2380,7 @@ term_array: /* arrays in 'aa' and 'bb', length in 'i' */
pop_next:
if (!WSTACK_ISEMPTY(stack)) {
UWord something = WSTACK_POP(stack);
- struct erts_cmp_hashmap_state* sp;
+ struct cmp_map_state* sp;
if (primary_tag((Eterm) something) == TAG_PRIMARY_HEADER) { /* an operation */
switch (GET_OP(something)) {
case TERM_ARRAY_OP:
@@ -3939,7 +2396,8 @@ pop_next:
goto pop_next;
case HASHMAP_PHASE1_ARE_KEYS_EQUAL: {
- sp = PSTACK_TOP(hmap_stack);
+ sp = PSTACK_TOP(map_stack);
+ ASSERT(sp->is_hashmap);
if (j) {
/* Key diff found, enter phase 2 */
int hash_cmp = hashmap_key_hash_cmp(sp->ap, sp->bp);
@@ -3982,7 +2440,8 @@ pop_next:
goto bodyrecur;
}
case HASHMAP_PHASE1_IS_MIN_KEY:
- sp = PSTACK_TOP(hmap_stack);
+ sp = PSTACK_TOP(map_stack);
+ ASSERT(sp->is_hashmap);
if (j < 0) {
a = CDR(sp->ap);
b = CDR(sp->bp);
@@ -3994,7 +2453,8 @@ pop_next:
goto case_HASHMAP_PHASE1_LOOP;
case HASHMAP_PHASE1_CMP_VALUES:
- sp = PSTACK_TOP(hmap_stack);
+ sp = PSTACK_TOP(map_stack);
+ ASSERT(sp->is_hashmap);
if (j) {
sp->cmp_res = j;
sp->min_key = CAR(sp->ap);
@@ -4007,7 +2467,7 @@ pop_next:
ASSERT(!sp->bp); /* as we assume indentical map sizes */
j = sp->cmp_res;
exact = sp->was_exact;
- (void) PSTACK_POP(hmap_stack);
+ (void) PSTACK_POP(map_stack);
ON_CMP_GOTO(j);
}
a = CAR(sp->ap);
@@ -4029,7 +2489,8 @@ pop_next:
goto case_HASHMAP_PHASE2_NEXT_STEP;
case HASHMAP_PHASE2_ARE_KEYS_EQUAL:
- sp = PSTACK_TOP(hmap_stack);
+ sp = PSTACK_TOP(map_stack);
+ ASSERT(sp->is_hashmap);
if (j == 0) {
/* keys are equal, skip them */
sp->ap = hashmap_iterator_next(&stack);
@@ -4066,11 +2527,12 @@ pop_next:
/* End of both maps */
j = sp->cmp_res;
exact = sp->was_exact;
- (void) PSTACK_POP(hmap_stack);
+ (void) PSTACK_POP(map_stack);
ON_CMP_GOTO(j);
case HASHMAP_PHASE2_IS_MIN_KEY_A:
- sp = PSTACK_TOP(hmap_stack);
+ sp = PSTACK_TOP(map_stack);
+ ASSERT(sp->is_hashmap);
if (j < 0) {
sp->min_key = CAR(sp->ap);
sp->cmp_res = -1;
@@ -4079,7 +2541,8 @@ pop_next:
goto case_HASHMAP_PHASE2_LOOP;
case HASHMAP_PHASE2_IS_MIN_KEY_B:
- sp = PSTACK_TOP(hmap_stack);
+ sp = PSTACK_TOP(map_stack);
+ ASSERT(sp->is_hashmap);
if (j < 0) {
sp->min_key = CAR(sp->bp);
sp->cmp_res = 1;
@@ -4087,6 +2550,58 @@ pop_next:
sp->bp = hashmap_iterator_next(&b_stack);
goto case_HASHMAP_PHASE2_LOOP;
+ case FLATMAP_ATOM_KEYS:
+ i = GET_OP_ARG(something);
+ aa = (Eterm*) WSTACK_POP(stack);
+ bb = (Eterm*) WSTACK_POP(stack);
+ ON_CMP_GOTO(compare_flatmap_atom_keys(aa, bb, i));
+
+ case FLATMAP_ATOM_VALUES: {
+ /*
+ * Compare values of equal atom keys.
+ * Find the smallest atom key where the values differ
+ */
+ sp = PSTACK_PUSH(map_stack);
+ IF_DEBUG(sp->is_hashmap = 0);
+ sp->atom_keys = (Eterm*) WSTACK_POP(stack);
+ sp->ap = (Eterm*) WSTACK_POP(stack);
+ sp->bp = (Eterm*) WSTACK_POP(stack);
+ sp->min_key = THE_NON_VALUE;
+ sp->cmp_res = 0;
+
+ case_FLATMAP_ATOM_VALUES_LOOP:
+ i = GET_OP_ARG(something);
+
+ while (i--) {
+ ASSERT(is_atom(*sp->atom_keys));
+
+ if (is_non_value(sp->min_key)
+ || erts_cmp_atoms(*sp->atom_keys, sp->min_key) < 0) {
+ a = *sp->ap++;
+ b = *sp->bp++;
+ WSTACK_PUSH(stack, OP_ARG_WORD(FLATMAP_ATOM_CMP_VALUES,i));
+ sp->wstack_rollback = WSTACK_COUNT(stack);
+ goto bodyrecur;
+ }
+ sp->atom_keys++;
+ sp->ap++;
+ sp->bp++;
+ }
+ j = sp->cmp_res;
+ (void) PSTACK_POP(map_stack);
+ ON_CMP_GOTO(j);
+
+ case FLATMAP_ATOM_CMP_VALUES:
+ sp = PSTACK_TOP(map_stack);
+ ASSERT(!sp->is_hashmap);
+ if (j) {
+ sp->min_key = *sp->atom_keys;
+ sp->cmp_res = j;
+ }
+ sp->atom_keys++;
+ goto case_FLATMAP_ATOM_VALUES_LOOP;
+ }
+
default:
ASSERT(!"Invalid cmp op");
} /* switch */
@@ -4096,18 +2611,18 @@ pop_next:
goto tailrecur;
}
- ASSERT(PSTACK_IS_EMPTY(hmap_stack));
- PSTACK_DESTROY(hmap_stack);
+ ASSERT(PSTACK_IS_EMPTY(map_stack));
+ PSTACK_DESTROY(map_stack);
WSTACK_DESTROY(stack);
WSTACK_DESTROY(b_stack);
return 0;
not_equal:
- if (!PSTACK_IS_EMPTY(hmap_stack) && !eq_only) {
- WSTACK_ROLLBACK(stack, PSTACK_TOP(hmap_stack)->wstack_rollback);
+ if (!PSTACK_IS_EMPTY(map_stack) && !eq_only) {
+ WSTACK_ROLLBACK(stack, PSTACK_TOP(map_stack)->wstack_rollback);
goto pop_next;
}
- PSTACK_DESTROY(hmap_stack);
+ PSTACK_DESTROY(map_stack);
WSTACK_DESTROY(stack);
WSTACK_DESTROY(b_stack);
return j;
@@ -4217,14 +2732,16 @@ intlist_to_buf(Eterm list, char *buf, Sint len)
}
/** @brief Fill buf with the UTF8 contents of the unicode list
- * @param len Max number of characters to write.
- * @param written NULL or bytes written.
+ * (no terminating NULL character written)
+ * @param capacity Max number of bytes to write.
+ * @param len Max number of characters to write.
+ * @param written NULL or bytes written.
* @return 0 ok,
* -1 type error,
- * -2 list too long, only \c len characters written
+ * -2 list too long, not all characters written
*/
int
-erts_unicode_list_to_buf(Eterm list, byte *buf, Sint len, Sint* written)
+erts_unicode_list_to_buf(Eterm list, byte *buf, Sint capacity, Sint len, Sint* written)
{
Eterm* listptr;
Sint sz = 0;
@@ -4253,9 +2770,19 @@ erts_unicode_list_to_buf(Eterm list, byte *buf, Sint len, Sint* written)
}
val = (Uint) signed_val(CAR(listptr));
if (val < 0x80) {
+ capacity -= 1;
+ if (capacity < 0) {
+ res = -2;
+ break;
+ }
buf[sz] = val;
sz++;
} else if (val < 0x800) {
+ capacity -= 2;
+ if (capacity < 0) {
+ res = -2;
+ break;
+ }
buf[sz+0] = 0xC0 | (val >> 6);
buf[sz+1] = 0x80 | (val & 0x3F);
sz += 2;
@@ -4264,11 +2791,21 @@ erts_unicode_list_to_buf(Eterm list, byte *buf, Sint len, Sint* written)
res = -1;
break;
}
+ capacity -= 3;
+ if (capacity < 0) {
+ res = -2;
+ break;
+ }
buf[sz+0] = 0xE0 | (val >> 12);
buf[sz+1] = 0x80 | ((val >> 6) & 0x3F);
buf[sz+2] = 0x80 | (val & 0x3F);
sz += 3;
} else if (val < 0x110000) {
+ capacity -= 4;
+ if (capacity < 0) {
+ res = -2;
+ break;
+ }
buf[sz+0] = 0xF0 | (val >> 18);
buf[sz+1] = 0x80 | ((val >> 12) & 0x3F);
buf[sz+2] = 0x80 | ((val >> 6) & 0x3F);
@@ -5231,7 +3768,7 @@ erts_ptr_id(void *ptr)
return ptr;
}
-const void *erts_get_stacklimit() {
+const void *erts_get_stacklimit(void) {
return ethr_get_stacklimit();
}
diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c
index ec5f9a655c..329c48172c 100644
--- a/erts/emulator/drivers/common/inet_drv.c
+++ b/erts/emulator/drivers/common/inet_drv.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1997-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1997-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -120,12 +120,14 @@
#endif
/* Descriptor debug */
+/* #define INET_DRV_DEBUG 1 */
#ifdef INET_DRV_DEBUG
#define DDBG_DEFAULT TRUE
#else
#define DDBG_DEFAULT FALSE
#endif
-#define DDBG(__D__, __ARG__) if ( (__D__)->debug ) erts_printf __ARG__
+#define DDBG(__D__, __ARG__) \
+ do if ( (__D__)->debug ) { erts_printf __ARG__ ; } while (0)
#define B2S(__B__) ((__B__) ? "true" : "false")
#define SH2S(__H__) (((__H__) == TCP_SHUT_WR) ? "write" : \
@@ -575,7 +577,6 @@ static int (*p_sctp_connectx)
#endif
#include "sys.h"
-/* #define INET_DRV_DEBUG 1 */
#ifdef INET_DRV_DEBUG
#define DEBUG 1
#undef DEBUGF
@@ -862,6 +863,9 @@ static size_t my_strnlen(const char *s, size_t maxlen)
#define UDP_OPT_ADD_MEMBERSHIP 14 /* add an IP group membership */
#define UDP_OPT_DROP_MEMBERSHIP 15 /* drop an IP group membership */
#define INET_OPT_IPV6_V6ONLY 16 /* IPv6 only socket, no mapped v4 addrs */
+#define INET_OPT_REUSEPORT 17 /* enable/disable local port reuse */
+#define INET_OPT_REUSEPORT_LB 18 /* enable/disable local port reuse */
+#define INET_OPT_EXCLUSIVEADDRUSE 19 /* windows specific exclusive addr */
/* LOPT is local options */
#define INET_LOPT_BUFFER 20 /* min buffer size hint */
#define INET_LOPT_HEADER 21 /* list header size */
@@ -1114,6 +1118,11 @@ typedef union {
(1 + 2 + 4) : localaddrlen(data))
#endif
+typedef struct {
+ ErlDrvSizeT tag_len;
+ char *tag_buf;
+} CallerRef;
+
typedef int (MultiTimerFunction)(ErlDrvData drv_data, ErlDrvTermData caller);
typedef struct _multi_timer_data {
@@ -1184,10 +1193,13 @@ typedef struct {
(affect how to interpret hsz) */
int exitf; /* exit port on close or not */
int deliver; /* Delivery mode, TERM or PORT */
+#ifdef __WIN32__
+ /* placed here in order to not consume more memory in 64-bit case... */
+ int bsd_compat; /* State for Windows compatibility with BSD */
+#endif
- ErlDrvTermData caller; /* recipient of sync reply */
- ErlDrvTermData busy_caller; /* recipient of sync reply when caller busy.
- * Only valid while INET_F_BUSY. */
+ ErlDrvTermData caller; /* recipient of sync reply */
+ CallerRef caller_ref;
inet_async_op* oph; /* queue head or NULL */
inet_async_op* opt; /* queue tail or NULL */
@@ -1536,6 +1548,9 @@ static ErlDrvTermData am_linger;
static ErlDrvTermData am_recbuf;
static ErlDrvTermData am_sndbuf;
static ErlDrvTermData am_reuseaddr;
+static ErlDrvTermData am_reuseport;
+static ErlDrvTermData am_reuseport_lb;
+static ErlDrvTermData am_exclusiveaddruse;
static ErlDrvTermData am_dontroute;
static ErlDrvTermData am_priority;
static ErlDrvTermData am_recvtos;
@@ -1721,6 +1736,100 @@ static void *realloc_wrapper(void *current, ErlDrvSizeT size){
((vec)[(i)+1] = (size)), \
((i)+LOAD_LIST_CNT))
+#define LOAD_EXT_CNT 3
+#define LOAD_EXT(vec, i, buf, len) \
+ (((vec)[(i)] = ERL_DRV_EXT2TERM), \
+ ((vec)[(i)+1] = (ErlDrvTermData)(buf)), \
+ ((vec)[(i)+2] = (len)), \
+ ((i)+LOAD_EXT_CNT))
+
+
+static void iov_memmove(char *buf, SysIOVec **iov_pp, ErlDrvSizeT size) {
+ int i;
+ for (i = 0; size != 0; i++) {
+ if ((*iov_pp)[i].iov_len < size) {
+ if ((*iov_pp)[i].iov_len == 0) continue;
+ sys_memcpy(buf, (*iov_pp)[i].iov_base, (*iov_pp)[i].iov_len);
+ buf += (*iov_pp)[i].iov_len;
+ size -= (*iov_pp)[i].iov_len;
+ (*iov_pp)[i].iov_base =
+ (char*)((*iov_pp)[i].iov_base) + (*iov_pp)[i].iov_len;
+ (*iov_pp)[i].iov_len = 0;
+ }
+ else {
+ sys_memcpy(buf, (*iov_pp)[i].iov_base, size);
+ (*iov_pp)[i].iov_base = (char*)((*iov_pp)[i].iov_base) + size;
+ (*iov_pp)[i].iov_len -= size;
+ break;
+ }
+ }
+}
+
+/* Returns TRUE on success, FALSE on failure */
+static int init_caller_iov(ErlDrvTermData *caller_p,
+ CallerRef *cref_p,
+ ErlDrvPort port,
+ SysIOVec **iov_pp,
+ ErlDrvSizeT *size_p) {
+ ErlDrvTermData caller;
+ char buf[2];
+ ErlDrvUInt n;
+ /**/
+ caller = driver_caller(port);
+ if (is_not_internal_pid(caller)) {
+ return TRUE;
+ }
+ *caller_p = caller;
+ if (*size_p < 2) return FALSE;
+ iov_memmove(buf, iov_pp, 2);
+ *size_p -= 2;
+ n = get_int16(buf);
+ if (*size_p < n) return FALSE;
+ cref_p->tag_buf = ALLOC(n);
+ iov_memmove(cref_p->tag_buf, iov_pp, n);
+ *size_p -= n;
+ cref_p->tag_len = n;
+ return TRUE;
+}
+
+/* Returns TRUE on success, FALSE on failure */
+static int init_caller(ErlDrvTermData *caller_p,
+ CallerRef *cref_p,
+ ErlDrvPort port,
+ char **buf_p,
+ ErlDrvSizeT *size_p) {
+ ErlDrvTermData caller;
+ ErlDrvUInt n;
+ /**/
+ caller = driver_caller(port);
+ if (is_not_internal_pid(caller)) {
+ return TRUE;
+ }
+ *caller_p = caller;
+ if (*size_p < 2) return FALSE;
+ n = get_int16(*buf_p);
+ *buf_p += 2;
+ *size_p -= 2;
+ if (*size_p < n) return FALSE;
+ cref_p->tag_buf = ALLOC(n);
+ sys_memcpy(cref_p->tag_buf, *buf_p, n);
+ *buf_p += n;
+ *size_p -= n;
+ cref_p->tag_len = n;
+ return TRUE;
+}
+
+static CallerRef no_caller_ref(void) {
+ CallerRef ret = {0, NULL};
+ return ret;
+}
+
+static void end_caller_ref(CallerRef *cref_p) {
+ if (cref_p->tag_buf != NULL) FREE(cref_p->tag_buf);
+ *cref_p = no_caller_ref();
+}
+
+
#ifdef HAVE_SCTP
/* "IS_SCTP": tells the difference between a UDP and an SCTP socket: */
@@ -2456,73 +2565,101 @@ static int async_error(inet_descriptor* desc, int err)
return async_error_am(desc, error_atom(err));
}
+
+/* Maybe append the caller tag then send to caller */
+static int
+inet_reply_finish(inet_descriptor *desc, ErlDrvTermData *spec, int i) {
+ int ret;
+ ErlDrvSizeT tuple_size;
+ CallerRef *cref_p;
+ /**/
+ ret = 0;
+ cref_p = &desc->caller_ref;
+ if (i == 0) goto done;
+ if (cref_p->tag_buf != NULL) {
+ tuple_size = spec[i - 1];
+ i = LOAD_EXT(spec, i - LOAD_TUPLE_CNT,
+ cref_p->tag_buf, cref_p->tag_len);
+ i = LOAD_TUPLE(spec, i, tuple_size + 1);
+ }
+ ret = erl_drv_send_term(desc->dport, desc->caller, spec, i);
+ done:
+ desc->caller = am_undefined;
+ end_caller_ref(cref_p);
+ return ret;
+}
+
/* send:
-** {inet_reply, S, ok}
+** {inet_reply, S, ok[, CallerTag]}
*/
-
static int inet_reply_ok(inet_descriptor* desc)
{
- ErlDrvTermData spec[2*LOAD_ATOM_CNT + LOAD_PORT_CNT + LOAD_TUPLE_CNT];
- ErlDrvTermData caller = desc->caller;
+ ErlDrvTermData
+ spec[2*LOAD_ATOM_CNT + LOAD_PORT_CNT + LOAD_TUPLE_CNT +
+ LOAD_EXT_CNT];
int i = 0;
-
- desc->caller = 0;
- if (is_not_internal_pid(caller))
- return 0;
+
+ if (is_not_internal_pid(desc->caller)) goto done;
i = LOAD_ATOM(spec, i, am_inet_reply);
i = LOAD_PORT(spec, i, desc->dport);
i = LOAD_ATOM(spec, i, am_ok);
i = LOAD_TUPLE(spec, i, 3);
- ASSERT(i == sizeof(spec)/sizeof(*spec));
-
- return erl_drv_send_term(desc->dport, caller, spec, i);
+ ASSERT(i == sizeof(spec)/sizeof(*spec) - LOAD_EXT_CNT);
+ done:
+ return inet_reply_finish(desc, spec, i);
}
#ifdef HAVE_SCTP
static int inet_reply_ok_port(inet_descriptor* desc, ErlDrvTermData dport)
{
- ErlDrvTermData spec[2*LOAD_ATOM_CNT + 2*LOAD_PORT_CNT + 2*LOAD_TUPLE_CNT];
- ErlDrvTermData caller = desc->caller;
+ ErlDrvTermData
+ spec[2*LOAD_ATOM_CNT + 2*LOAD_PORT_CNT + 2*LOAD_TUPLE_CNT +
+ LOAD_EXT_CNT];
int i = 0;
+ if (is_not_internal_pid(desc->caller)) goto done;
+
i = LOAD_ATOM(spec, i, am_inet_reply);
i = LOAD_PORT(spec, i, desc->dport);
i = LOAD_ATOM(spec, i, am_ok);
i = LOAD_PORT(spec, i, dport);
i = LOAD_TUPLE(spec, i, 2);
i = LOAD_TUPLE(spec, i, 3);
- ASSERT(i == sizeof(spec)/sizeof(*spec));
-
- desc->caller = 0;
- return erl_drv_send_term(desc->dport, caller, spec, i);
+ ASSERT(i == sizeof(spec)/sizeof(*spec) - LOAD_EXT_CNT);
+ done:
+ return inet_reply_finish(desc, spec, i);
}
#endif
/* send:
-** {inet_reply, S, {error, Reason}}
+** {inet_reply, S, {error, Reason}[, CallerTag]}
*/
static int inet_reply_error_am(inet_descriptor* desc, ErlDrvTermData reason)
{
- ErlDrvTermData spec[3*LOAD_ATOM_CNT + LOAD_PORT_CNT + 2*LOAD_TUPLE_CNT];
- ErlDrvTermData caller = desc->caller;
+ ErlDrvTermData
+ spec[3*LOAD_ATOM_CNT + LOAD_PORT_CNT + 2*LOAD_TUPLE_CNT +
+ LOAD_EXT_CNT];
int i = 0;
-
+
+ if (is_not_internal_pid(desc->caller)) goto done;
+
i = LOAD_ATOM(spec, i, am_inet_reply);
i = LOAD_PORT(spec, i, desc->dport);
i = LOAD_ATOM(spec, i, am_error);
i = LOAD_ATOM(spec, i, reason);
i = LOAD_TUPLE(spec, i, 2);
i = LOAD_TUPLE(spec, i, 3);
- ASSERT(i == sizeof(spec)/sizeof(*spec));
- desc->caller = 0;
-
- DEBUGF(("inet_reply_error_am %ld %ld\r\n", caller, reason));
- return erl_drv_send_term(desc->dport, caller, spec, i);
+ ASSERT(i == sizeof(spec)/sizeof(*spec) - LOAD_EXT_CNT);
+
+ DEBUGF(("inet_reply_error_am %ld %ld\r\n",
+ desc->caller, reason));
+ done:
+ return inet_reply_finish(desc, spec, i);
}
/* send:
-** {inet_reply, S, {error, Reason}}
+** {inet_reply, S, {error, Reason}[, CallerTag]}
*/
static int inet_reply_error(inet_descriptor* desc, int err)
{
@@ -2596,7 +2733,7 @@ static int http_response_inetdrv(void *arg, int major, int minor,
tcp_descriptor* desc = (tcp_descriptor*) arg;
int i = 0;
ErlDrvTermData spec[27];
- ErlDrvTermData caller = ERL_DRV_NIL;
+ ErlDrvTermData caller = am_undefined;
if (desc->inet.active == INET_PASSIVE) {
/* {inet_async,S,Ref,{ok,{http_response,Version,Status,Phrase}}} */
@@ -2689,7 +2826,7 @@ http_request_inetdrv(void* arg, const http_atom_t* meth, const char* meth_ptr,
tcp_descriptor* desc = (tcp_descriptor*) arg;
int i = 0;
ErlDrvTermData spec[43];
- ErlDrvTermData caller = ERL_DRV_NIL;
+ ErlDrvTermData caller = am_undefined;
if (desc->inet.active == INET_PASSIVE) {
/* {inet_async, S, Ref, {ok,{http_request,Meth,Uri,Version}}} */
@@ -2742,7 +2879,7 @@ http_header_inetdrv(void* arg, const http_atom_t* name,
tcp_descriptor* desc = (tcp_descriptor*) arg;
int i = 0;
ErlDrvTermData spec[27];
- ErlDrvTermData caller = ERL_DRV_NIL;
+ ErlDrvTermData caller = am_undefined;
if (desc->inet.active == INET_PASSIVE) {
/* {inet_async,S,Ref,{ok,{http_header,Bit,Name,Oname,Value}} */
@@ -2871,7 +3008,7 @@ int ssl_tls_inetdrv(void* arg, unsigned type, unsigned major, unsigned minor,
tcp_descriptor* desc = (tcp_descriptor*) arg;
int i = 0;
ErlDrvTermData spec[30];
- ErlDrvTermData caller = ERL_DRV_NIL;
+ ErlDrvTermData caller = am_undefined;
ErlDrvBinary* bin;
int ret;
@@ -2966,7 +3103,7 @@ static int inet_async_data(inet_descriptor* desc, const char* buf, int len)
i = LOAD_TUPLE(spec, i, 2);
i = LOAD_TUPLE(spec, i, 4);
ASSERT(i == 15);
- desc->caller = 0;
+ /* desc->caller = am_undefined; XXX */
return erl_drv_send_term(desc->dport, caller, spec, i);
}
else {
@@ -2980,7 +3117,7 @@ static int inet_async_data(inet_descriptor* desc, const char* buf, int len)
i = LOAD_TUPLE(spec, i, 2);
i = LOAD_TUPLE(spec, i, 4);
ASSERT(i <= 20);
- desc->caller = 0;
+ /* desc->caller = am_undefined; XXX */
code = erl_drv_send_term(desc->dport, caller, spec, i);
return code;
}
@@ -3638,7 +3775,7 @@ inet_async_binary_data
{
unsigned int hsz = desc->hsz + phsz;
ErlDrvTermData spec [PACKET_ERL_DRV_TERM_DATA_LEN];
- ErlDrvTermData caller = desc->caller;
+ ErlDrvTermData caller;
int aid;
int req;
int i = 0;
@@ -3739,8 +3876,9 @@ inet_async_binary_data
/* Close up the outer {inet_async, S, Ref, {ok|error, ...}} tuple: */
i = LOAD_TUPLE(spec, i, 4);
- ASSERT(i <= PACKET_ERL_DRV_TERM_DATA_LEN);
- desc->caller = 0;
+ ASSERT(i <= PACKET_ERL_DRV_TERM_DATA_LEN);
+ desc->caller = am_undefined;
+ end_caller_ref(&desc->caller_ref);
return erl_drv_send_term(desc->dport, caller, spec, i);
}
@@ -4171,6 +4309,9 @@ static void inet_init_sctp(void) {
INIT_ATOM(recbuf);
INIT_ATOM(sndbuf);
INIT_ATOM(reuseaddr);
+ INIT_ATOM(reuseport);
+ INIT_ATOM(reuseport_lb);
+ INIT_ATOM(exclusiveaddruse);
INIT_ATOM(dontroute);
INIT_ATOM(priority);
INIT_ATOM(recvtos);
@@ -4280,7 +4421,7 @@ static void inet_init_sctp(void) {
}
#endif /* HAVE_SCTP */
-static int inet_init()
+static int inet_init(void)
{
if (!sock_init())
goto error;
@@ -6584,8 +6725,6 @@ int inet_setopt(int fd,
return res;
}
-
-
/* set socket options:
** return -1 on error
** 0 if ok
@@ -6634,6 +6773,10 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
while(len >= 5) {
int recv_cmsgflags;
+#ifdef __WIN32__
+ int bsd_compat_set = 0;
+ int bsd_compat_unset = 0;
+#endif
opt = *ptr++;
ival = get_int32(ptr);
@@ -6913,41 +7056,82 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
desc->delimiter = (char)ival;
continue;
+ case INET_OPT_EXCLUSIVEADDRUSE:
+ DDBG(desc,
+ ("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
+ "inet_set_opts(exclusiveaddruse) -> %s\r\n",
+ __LINE__, desc->s, driver_caller(desc->port), B2S(ival)) );
+#ifdef __WIN32__
+ type = SO_EXCLUSIVEADDRUSE;
+#else
+ continue;
+#endif
+ break;
+
+ case INET_OPT_REUSEPORT_LB:
+ DDBG(desc,
+ ("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
+ "inet_set_opts(reuseport_lb) -> %s\r\n",
+ __LINE__, desc->s, driver_caller(desc->port), B2S(ival)) );
+#if defined(__WIN32__)
+ continue;
+#elif defined(__linux__) && defined(SO_REUSEPORT)
+ /* SO_REUSEPORT is load balancing on linux */
+ type = SO_REUSEPORT;
+ break;
+#elif defined(SO_REUSEPORT_LB)
+ type = SO_REUSEPORT_LB;
+ break;
+#else
+ continue;
+#endif
+
+ case INET_OPT_REUSEPORT:
+ DDBG(desc,
+ ("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
+ "inet_set_opts(reuseport) -> %s\r\n",
+ __LINE__, desc->s, driver_caller(desc->port), B2S(ival)) );
+#if defined(__WIN32__)
+ /* fall through to INET_OPT_REUSEADDR */
+#elif defined(SO_REUSEPORT)
+ type = SO_REUSEPORT;
+ break;
+#else
+ continue;
+#endif
+
case INET_OPT_REUSEADDR:
+ DDBG(desc,
+ ("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
+ "inet_set_opts(reuseaddr) -> %s\r\n",
+ __LINE__, desc->s, driver_caller(desc->port), B2S(ival)) );
#ifdef __WIN32__
- /* The behaviour changed in Windows Server 2003.
- * Now it works as the combo of `SO_REUSEADDR` and
- * `SO_REUSEPORT` does on *BSD.
- */
- if (desc->stype != SOCK_DGRAM) {
+ {
+ int old_ra, new_ra, compat, ra_bits, opt_bit;
/*
- * We refuse usage of SO_REUSEADDR on non-UDP sockets since it
- * mostly (perhaps only) opens up for socket collisions and
- * probably hasn't got any useful use-cases. There are useful
- * use-cases for multicast sockets, though. For more
- * information see:
- * https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
- *
- * Prior to OTP 25 we also refused to use SO_REUSEADDR on any
- * sockets on Windows. See
- * 2a6ac6f3f027fcab6d607599e82714e930d9fde2
- *
- * We certainly do not want to use it for the Erlang
- * distribution TCP sockets since we can end up reusing our
- * own active sockets as demonstrated in the issue:
- * https://github.com/erlang/otp/issues/6461
- *
- * We probably want to expose the Windows specific
- * SO_EXCLUSIVEADDRUSE in the API as well...
+ * We only set SO_REUSEADDR on Windows if both 'reuseaddr' and
+ * 'reuseport' has been passed as options, since SO_REUSEADDR
+ * on Windows behaves like SO_REUSEADDR|SO_REUSEPORT does on BSD.
*/
- continue;
+ ra_bits = (1<<INET_OPT_REUSEADDR) | (1<<INET_OPT_REUSEPORT);
+ opt_bit = (1<<opt);
+ compat = desc->bsd_compat;
+ old_ra = (compat & ra_bits) == ra_bits;
+ if (ival) {
+ bsd_compat_set = opt_bit;
+ compat |= opt_bit;
+ }
+ else {
+ bsd_compat_unset = opt_bit;
+ compat &= ~opt_bit;
+ }
+ new_ra = (compat & ra_bits) == ra_bits;
+ desc->bsd_compat = compat;
+ if (old_ra == new_ra)
+ continue;
}
#endif
type = SO_REUSEADDR;
- DDBG(desc,
- ("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
- "inet_set_opts(reuseaddr) -> %s\r\n",
- __LINE__, desc->s, driver_caller(desc->port), B2S(ival)) );
break;
case INET_OPT_KEEPALIVE: type = SO_KEEPALIVE;
@@ -6978,7 +7162,8 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
__LINE__, desc->s, driver_caller(desc->port), B2S(ival)) );
break;
- case INET_OPT_SNDBUF: type = SO_SNDBUF; DDBG(desc,
+ case INET_OPT_SNDBUF: type = SO_SNDBUF;
+ DDBG(desc,
("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
"inet_set_opts(sndbuf) -> %d\r\n",
__LINE__, desc->s, driver_caller(desc->port), ival) );
@@ -7493,9 +7678,18 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
"inet_set_opts -> set opt result: %d\r\n",
__LINE__, desc->s, driver_caller(desc->port), res) );
- if (res == 0) desc->recv_cmsgflags = recv_cmsgflags;
- if (propagate && res != 0) {
- return -1;
+ if (res == 0) {
+ desc->recv_cmsgflags = recv_cmsgflags;
+ }
+ else {
+#ifdef __WIN32__
+ if (bsd_compat_set)
+ desc->bsd_compat &= ~bsd_compat_set;
+ if (bsd_compat_unset)
+ desc->bsd_compat |= bsd_compat_unset;
+#endif
+ if (propagate)
+ return -1;
}
}
@@ -7639,6 +7833,10 @@ static int sctp_set_opts(inet_descriptor* desc, char* ptr, int len)
int recv_cmsgflags;
/* Get the Erlang-encoded option type -- always 1 byte: */
int eopt;
+#ifdef __WIN32__
+ int bsd_compat_set = 0;
+ int bsd_compat_unset = 0;
+#endif
eopt = *curr;
curr++;
@@ -7882,13 +8080,99 @@ static int sctp_set_opts(inet_descriptor* desc, char* ptr, int len)
break;
}
+
+ case INET_OPT_EXCLUSIVEADDRUSE:
+ DDBG(desc,
+ ("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
+ "sctp_set_opts -> EXCLUSIVEADDRUSE\r\n",
+ __LINE__, desc->s, driver_caller(desc->port)) );
+#ifdef __WIN32__
+ arg.ival= get_int32 (curr); curr += 4;
+ proto = SOL_SOCKET;
+ type = SO_EXCLUSIVEADDRUSE;
+ arg_ptr = (char*) (&arg.ival);
+ arg_sz = sizeof ( arg.ival);
+ break;
+#else
+ continue;
+#endif
+
+ case INET_OPT_REUSEPORT_LB:
+ {
+ DDBG(desc,
+ ("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
+ "sctp_set_opts -> REUSEPORT_LB\r\n",
+ __LINE__, desc->s, driver_caller(desc->port)) );
+#if defined(__WIN32__)
+ continue;
+#elif defined(SO_REUSEPORT_LB) || (defined(__linux__) && defined(SO_REUSEPORT))
+ arg.ival= get_int32 (curr); curr += 4;
+ proto = SOL_SOCKET;
+#if defined(__linux__)
+ /* SO_REUSEPORT is load balancing on linux */
+ type = SO_REUSEPORT;
+#else
+ type = SO_REUSEPORT_LB;
+#endif
+ arg_ptr = (char*) (&arg.ival);
+ arg_sz = sizeof ( arg.ival);
+ break;
+#else
+ continue;
+#endif
+ }
+
+ case INET_OPT_REUSEPORT:
+ {
+ DDBG(desc,
+ ("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
+ "sctp_set_opts -> REUSEPORT\r\n",
+ __LINE__, desc->s, driver_caller(desc->port)) );
+#if defined(__WIN32__)
+ /* fall through to INET_OPT_REUSEADDR */
+#elif defined(SO_REUSEPORT)
+ arg.ival= get_int32 (curr); curr += 4;
+ proto = SOL_SOCKET;
+ type = SO_REUSEPORT;
+ arg_ptr = (char*) (&arg.ival);
+ arg_sz = sizeof ( arg.ival);
+ break;
+#else
+ continue;
+#endif
+ }
case INET_OPT_REUSEADDR:
{
DDBG(desc,
("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
"sctp_set_opts -> REUSEADDR\r\n",
__LINE__, desc->s, driver_caller(desc->port)) );
-
+#ifdef __WIN32__
+ {
+ int old_ra, new_ra, compat, ra_bits, opt_bit;
+ /*
+ * We only set SO_REUSEADDR on Windows if both 'reuseaddr' and
+ * 'reuseport' has been passed as options, since SO_REUSEADDR
+ * on Windows behaves like SO_REUSEADDR|SO_REUSEPORT does on BSD.
+ */
+ ra_bits = (1<<INET_OPT_REUSEADDR) | (1<<INET_OPT_REUSEPORT);
+ opt_bit = (1<<opt);
+ compat = desc->bsd_compat;
+ old_ra = (compat & ra_bits) == ra_bits;
+ if (ival) {
+ bsd_compat_set = opt_bit;
+ compat |= opt_bit;
+ }
+ else {
+ bsd_compat_unset = opt_bit;
+ compat &= ~opt_bit;
+ }
+ new_ra = (compat & ra_bits) == ra_bits;
+ desc->bsd_compat = compat;
+ if (old_ra == new_ra)
+ continue;
+ }
+#endif
arg.ival= get_int32 (curr); curr += 4;
proto = SOL_SOCKET;
type = SO_REUSEADDR;
@@ -8421,7 +8705,17 @@ static int sctp_set_opts(inet_descriptor* desc, char* ptr, int len)
/* The return values of "sock_setopt" can only be 0 or -1: */
ASSERT(res == 0 || res == -1);
- if (res == 0) desc->recv_cmsgflags = recv_cmsgflags;
+ if (res == 0) {
+ desc->recv_cmsgflags = recv_cmsgflags;
+ }
+#ifdef __WIN32__
+ else {
+ if (bsd_compat_set)
+ desc->bsd_compat &= ~bsd_compat_set;
+ if (bsd_compat_unset)
+ desc->bsd_compat |= bsd_compat_unset;
+ }
+#endif
if (res == -1)
{ /* Got an error, DO NOT continue with other options. However, on
Solaris 10, we DO allow SO_SNDBUF and SO_RCVBUF to fail, assu-
@@ -8770,9 +9064,55 @@ static ErlDrvSSizeT inet_fill_opts(inet_descriptor* desc,
TRUNCATE_TO(0,ptr);
continue;
#endif
- case INET_OPT_REUSEADDR:
- type = SO_REUSEADDR;
+ case INET_OPT_REUSEADDR: {
+#if defined(__WIN32__)
+ int res = !!(desc->bsd_compat & (1<<INET_OPT_REUSEADDR));
+ *ptr++ = opt;
+ put_int32(res, ptr);
+ continue;
+#else
+ type = SO_REUSEADDR;
+#endif
+ break;
+ }
+ case INET_OPT_REUSEPORT_LB: {
+#if defined(__linux__) && defined(SO_REUSEPORT)
+ /* SO_REUSEPORT is load balancing on linux */
+ type = SO_REUSEPORT;
+ break;
+#elif defined(SO_REUSEPORT_LB)
+ type = SO_REUSEPORT_LB;
+ break;
+#else
+ *ptr++ = opt;
+ put_int32(0, ptr);
+ continue;
+#endif
+ }
+ case INET_OPT_REUSEPORT: {
+#if defined(__WIN32__)
+ int res = !!(desc->bsd_compat & (1<<INET_OPT_REUSEPORT));
+ *ptr++ = opt;
+ put_int32(res, ptr);
+ continue;
+#elif defined(SO_REUSEPORT)
+ type = SO_REUSEPORT;
break;
+#else
+ *ptr++ = opt;
+ put_int32(0, ptr);
+ continue;
+#endif
+ }
+ case INET_OPT_EXCLUSIVEADDRUSE:
+#ifdef __WIN32__
+ type = SO_EXCLUSIVEADDRUSE;
+ break;
+#else
+ *ptr++ = opt;
+ put_int32(0, ptr);
+ continue;
+#endif
case INET_OPT_KEEPALIVE:
type = SO_KEEPALIVE;
break;
@@ -9351,7 +9691,6 @@ static ErlDrvSSizeT sctp_fill_opts(inet_descriptor* desc,
/* The following options just return an integer value: */
case INET_OPT_RCVBUF :
case INET_OPT_SNDBUF :
- case INET_OPT_REUSEADDR:
case INET_OPT_DONTROUTE:
case INET_OPT_PRIORITY :
case INET_OPT_TOS :
@@ -9361,6 +9700,10 @@ static ErlDrvSSizeT sctp_fill_opts(inet_descriptor* desc,
case SCTP_OPT_AUTOCLOSE:
case SCTP_OPT_MAXSEG :
/* The following options return true or false: */
+ case INET_OPT_REUSEADDR:
+ case INET_OPT_REUSEPORT:
+ case INET_OPT_REUSEPORT_LB:
+ case INET_OPT_EXCLUSIVEADDRUSE:
case SCTP_OPT_NODELAY :
case SCTP_OPT_DISABLE_FRAGMENTS:
case SCTP_OPT_I_WANT_MAPPED_V4_ADDR:
@@ -9391,13 +9734,66 @@ static ErlDrvSSizeT sctp_fill_opts(inet_descriptor* desc,
tag = am_sndbuf;
break;
}
+ case INET_OPT_EXCLUSIVEADDRUSE:
+ {
+#if defined(__WIN32__)
+ proto = SOL_SOCKET;
+ type = SO_EXCLUSIVEADDRUSE;
+ is_int = 0;
+ tag = am_exclusiveaddruse;
+ break;
+#else
+ continue;
+#endif
+ }
+ case INET_OPT_REUSEPORT_LB:
+ {
+#if defined(SO_REUSEPORT_LB) || (defined(__linux__) && defined(SO_REUSEPORT))
+ proto = SOL_SOCKET;
+#if defined(__linux__)
+ /* SO_REUSEPORT is load balancing on linux */
+ type = SO_REUSEPORT;
+#else
+ type = SO_REUSEPORT_LB;
+#endif
+ is_int = 0;
+ tag = am_reuseport_lb;
+ break;
+#else
+ continue;
+#endif
+ }
+ case INET_OPT_REUSEPORT:
+ {
+#if defined(__WIN32__)
+ res = !!(desc->bsd_compat & (1<<INET_OPT_REUSEPORT));
+ is_int = 0;
+ tag = am_reuseaddr;
+ goto form_result;
+#elif defined(SO_REUSEPORT)
+ proto = SOL_SOCKET;
+ type = SO_REUSEPORT;
+ is_int = 0;
+ tag = am_reuseport;
+ break;
+#else
+ continue;
+#endif
+ }
case INET_OPT_REUSEADDR:
{
+#if defined(__WIN32__)
+ res = !!(desc->bsd_compat & (1<<INET_OPT_REUSEADDR));
+ is_int = 0;
+ tag = am_reuseaddr;
+ goto form_result;
+#else
proto = SOL_SOCKET;
type = SO_REUSEADDR;
is_int = 0;
tag = am_reuseaddr;
break;
+#endif
}
case INET_OPT_DONTROUTE:
{
@@ -9556,6 +9952,9 @@ static ErlDrvSSizeT sctp_fill_opts(inet_descriptor* desc,
}
if (sock_getopt (desc->s, proto, type, &res, &sz) < 0) continue;
/* Form the result: */
+#ifdef __WIN32__
+form_result:
+#endif
PLACE_FOR(spec, i, LOAD_ATOM_CNT +
(is_int ? LOAD_INT_CNT : LOAD_BOOL_CNT) +
LOAD_TUPLE_CNT);
@@ -10144,6 +10543,9 @@ static ErlDrvData inet_start(ErlDrvPort port, int size, int protocol)
desc->opt = NULL;
desc->op_ref = 0;
+ desc->caller = am_undefined;
+ desc->caller_ref = no_caller_ref();
+
desc->peer_ptr = NULL;
desc->name_ptr = NULL;
@@ -11331,7 +11733,7 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
if (desc->inet.state == INET_STATE_ACCEPTING) {
unsigned long time_left = 0;
int oid = 0;
- ErlDrvTermData ocaller = ERL_DRV_NIL;
+ ErlDrvTermData ocaller = am_undefined;
int oreq = 0;
unsigned otimeout = 0;
ErlDrvTermData caller = driver_caller(desc->inet.port);
@@ -11395,17 +11797,17 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
return ctl_error(sock_errno(), rbuf, rsize);
}
} else {
- ErlDrvTermData caller = driver_caller(desc->inet.port);
+ ErlDrvTermData owner = driver_caller(desc->inet.port);
tcp_descriptor* accept_desc;
int err;
- if ((accept_desc = tcp_inet_copy(desc,s,caller,&err)) == NULL) {
+ if ((accept_desc = tcp_inet_copy(desc,s,owner,&err)) == NULL) {
sock_close(s);
return ctl_error(err, rbuf, rsize);
}
/* FIXME: may MUST lock access_port
* 1 - Port is accessible via the erlang:ports()
- * 2 - Port is accessible via callers process_info(links)
+ * 2 - Port is accessible via owners process_info(links)
*/
accept_desc->inet.remote = remote;
SET_NONBLOCKING(accept_desc->inet.s);
@@ -11477,7 +11879,7 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
async_error_am(INETP(desc), am_timeout);
else {
if (timeout != INET_INFINITY)
- add_multi_timer(desc, INETP(desc)->port, 0,
+ add_multi_timer(desc, INETP(desc)->port, am_undefined,
timeout, &tcp_inet_recv_timeout);
if (!INET_IGNORED(INETP(desc)))
sock_select(INETP(desc),(FD_READ|FD_CLOSE),1);
@@ -11596,8 +11998,6 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
desc->sendfile.ioq_skip = driver_sizeq(desc->inet.port);
desc->sendfile.bytes_sent = 0;
-
- desc->inet.caller = driver_caller(desc->inet.port);
desc->tcp_add_flags |= TCP_ADDF_SENDFILE;
/* See if we can finish sending without selecting & rescheduling. */
@@ -11628,7 +12028,6 @@ static int tcp_inet_send_timeout(ErlDrvData e, ErlDrvTermData dummy)
tcp_descriptor* desc = (tcp_descriptor*)e;
ASSERT(IS_BUSY(INETP(desc)));
ASSERT(desc->busy_on_send);
- desc->inet.caller = desc->inet.busy_caller;
desc->inet.state &= ~INET_F_BUSY;
desc->busy_on_send = 0;
set_busy_port(desc->inet.port, 0);
@@ -11717,42 +12116,54 @@ static int tcp_inet_multi_timeout(ErlDrvData e, ErlDrvTermData caller)
static void tcp_inet_command(ErlDrvData e, char *buf, ErlDrvSizeT len)
{
- tcp_descriptor* desc = (tcp_descriptor*)e;
- ErlDrvTermData caller = driver_caller(desc->inet.port);
+ tcp_descriptor *desc = (tcp_descriptor*)e;
+ inet_descriptor *inetp = INETP(desc);
- desc->inet.caller = caller;
+ if (! init_caller(&inetp->caller, &inetp->caller_ref,
+ inetp->port, &buf, &len)) {
+ driver_failure_posix(inetp->port, EINVAL);
+ return;
+ }
- DDBG(INETP(desc),
+ DDBG(inetp,
("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
"tcp_inet_command -> entry\r\n",
- __LINE__, desc->inet.s, caller) );
+ __LINE__, inetp->s, inetp->caller) );
- if (!IS_CONNECTED(INETP(desc)))
- inet_reply_error(INETP(desc), ENOTCONN);
+ if (!IS_CONNECTED(inetp))
+ inet_reply_error(inetp, ENOTCONN);
else if (tcp_send(desc, buf, len) == 0)
- inet_reply_ok(INETP(desc));
+ inet_reply_ok(inetp);
- DDBG(INETP(desc),
+ DDBG(inetp,
("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
"tcp_inet_command -> done\r\n",
- __LINE__, desc->inet.s, caller) );
+ __LINE__, inetp->s, inetp->caller) );
}
-static void tcp_inet_commandv(ErlDrvData e, ErlIOVec* ev)
+static void tcp_inet_commandv(ErlDrvData e, ErlIOVec *ev)
{
- tcp_descriptor* desc = (tcp_descriptor*)e;
- ErlDrvTermData caller = driver_caller(desc->inet.port);
+ tcp_descriptor *desc = (tcp_descriptor*)e;
+ inet_descriptor *inetp = INETP(desc);
+#ifdef INET_DRV_DEBUG
+ ErlDrvTermData caller;
+#endif
- desc->inet.caller = caller;
+ if (! init_caller_iov(&inetp->caller, &inetp->caller_ref,
+ inetp->port, &ev->iov, &ev->size)) {
+ driver_failure_posix(inetp->port, EINVAL);
+ return;
+ }
#ifdef INET_DRV_DEBUG
+ caller = inetp->caller;
/* This function is just called to much to be part of this *
* by default. Atleast as long as there are no 'debug levels'. */
- DDBG(INETP(desc),
+ DDBG(inetp,
("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
"tcp_inet_commandv -> entry\r\n",
- __LINE__, desc->inet.s, caller) );
+ __LINE__, inetp->s, caller) );
#endif
if (!IS_CONNECTED(INETP(desc))) {
@@ -11762,22 +12173,22 @@ static void tcp_inet_commandv(ErlDrvData e, ErlIOVec* ev)
/* Don't clear flag. Leave it enabled for the next receive
* operation.
*/
- inet_reply_error(INETP(desc), ECONNRESET);
+ inet_reply_error(inetp, ECONNRESET);
} else
- inet_reply_error_am(INETP(desc), am_closed);
+ inet_reply_error_am(inetp, am_closed);
}
else
- inet_reply_error(INETP(desc), ENOTCONN);
+ inet_reply_error(inetp, ENOTCONN);
}
else if (desc->tcp_add_flags & TCP_ADDF_PENDING_SHUTDOWN)
tcp_shutdown_error(desc, EPIPE);
else if (tcp_sendv(desc, ev) == 0)
- inet_reply_ok(INETP(desc));
+ inet_reply_ok(inetp);
#ifdef INET_DRV_DEBUG
DDBG(INETP(desc),
("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] tcp_inet_commandv -> done\r\n",
- __LINE__, desc->inet.s, caller) );
+ __LINE__, inetp->s, caller) );
#endif
}
@@ -11867,7 +12278,6 @@ static int tcp_recv_closed(tcp_descriptor* desc)
port, desc->inet.s, __LINE__));
if (IS_BUSY(INETP(desc))) {
/* A send is blocked */
- desc->inet.caller = desc->inet.busy_caller;
tcp_clear_output(desc);
if (desc->busy_on_send) {
cancel_multi_timer(desc, INETP(desc)->port, &tcp_inet_send_timeout);
@@ -11929,7 +12339,6 @@ static int tcp_recv_error(tcp_descriptor* desc, int err)
if (err != ERRNO_BLOCK) {
if (IS_BUSY(INETP(desc))) {
/* A send is blocked */
- desc->inet.caller = desc->inet.busy_caller;
tcp_clear_output(desc);
if (desc->busy_on_send) {
cancel_multi_timer(desc, INETP(desc)->port, &tcp_inet_send_timeout);
@@ -12605,7 +13014,6 @@ static int tcp_send_or_shutdown_error(tcp_descriptor* desc, int err)
* If the port is busy, we must do some clean-up before proceeding.
*/
if (IS_BUSY(INETP(desc))) {
- desc->inet.caller = desc->inet.busy_caller;
if (desc->busy_on_send) {
cancel_multi_timer(desc, INETP(desc)->port, &tcp_inet_send_timeout);
desc->busy_on_send = 0;
@@ -12631,7 +13039,7 @@ static int tcp_send_or_shutdown_error(tcp_descriptor* desc, int err)
err_atom = am_closed;
}
tcp_closed_message(desc);
- if (!(desc->tcp_add_flags & TCP_ADDF_SENDFILE))
+ if (! (desc->tcp_add_flags & TCP_ADDF_SENDFILE))
inet_reply_error_am(INETP(desc), err_atom);
if (desc->inet.exitf)
@@ -12641,21 +13049,19 @@ static int tcp_send_or_shutdown_error(tcp_descriptor* desc, int err)
} else {
tcp_close_check(desc);
- if (desc->inet.caller) {
- if (!(desc->tcp_add_flags & TCP_ADDF_SENDFILE)) {
- if (show_econnreset)
- inet_reply_error(INETP(desc), err);
- else
- inet_reply_error_am(INETP(desc), am_closed);
- }
- }
- else {
+ if (is_not_internal_pid(desc->inet.caller)) {
/* No blocking send op to reply to right now.
* If next op is a send, make sure it returns {error,closed}
* rather than {error,enotconn}.
*/
desc->tcp_add_flags |= TCP_ADDF_DELAYED_CLOSE_SEND;
- }
+ }
+ else if (! (desc->tcp_add_flags & TCP_ADDF_SENDFILE)) {
+ if (show_econnreset)
+ inet_reply_error(INETP(desc), err);
+ else
+ inet_reply_error_am(INETP(desc), am_closed);
+ }
tcp_desc_close(desc);
/*
@@ -12770,12 +13176,12 @@ static int tcp_sendv(tcp_descriptor* desc, ErlIOVec* ev)
DEBUGF(("tcp_sendv(%p): s=%d, sender forced busy\r\n",
desc->inet.port, desc->inet.s));
desc->inet.state |= INET_F_BUSY; /* mark for low-watermark */
- desc->inet.busy_caller = desc->inet.caller;
set_busy_port(desc->inet.port, 1);
if (desc->send_timeout != INET_INFINITY) {
desc->busy_on_send = 1;
add_multi_timer(desc, INETP(desc)->port,
- 0 /* arg */, desc->send_timeout /* timeout */,
+ am_undefined, /* caller */
+ desc->send_timeout /* timeout */,
&tcp_inet_send_timeout);
}
return 1;
@@ -12798,7 +13204,7 @@ static int tcp_sendv(tcp_descriptor* desc, ErlIOVec* ev)
n = 0;
} else if (desc->tcp_add_flags & TCP_ADDF_DELAY_SEND) {
driver_enqv(ix, ev, 0);
- add_multi_timer(desc, INETP(desc)->port, 0,
+ add_multi_timer(desc, INETP(desc)->port, am_undefined,
0, &tcp_inet_delay_send);
return 0;
} else if (IS_SOCKET_ERROR(sock_sendv(desc->inet.s, ev->iov,
@@ -12882,12 +13288,12 @@ static int tcp_send(tcp_descriptor* desc, char* ptr, ErlDrvSizeT len)
DEBUGF(("tcp_send(%p): s=%d, sender forced busy\r\n",
desc->inet.port, desc->inet.s));
desc->inet.state |= INET_F_BUSY; /* mark for low-watermark */
- desc->inet.busy_caller = desc->inet.caller;
set_busy_port(desc->inet.port, 1);
if (desc->send_timeout != INET_INFINITY) {
desc->busy_on_send = 1;
add_multi_timer(desc, INETP(desc)->port,
- 0 /* arg */, desc->send_timeout /* timeout */,
+ am_undefined /* caller */,
+ desc->send_timeout /* timeout */,
&tcp_inet_send_timeout);
}
return 1;
@@ -12994,7 +13400,6 @@ static int tcp_sendfile_completed(tcp_descriptor* desc) {
if (driver_sizeq(desc->inet.port) <= desc->low) {
if (IS_BUSY(INETP(desc))) {
- desc->inet.caller = desc->inet.busy_caller;
desc->inet.state &= ~INET_F_BUSY;
set_busy_port(desc->inet.port, 0);
@@ -13339,7 +13744,6 @@ static int tcp_inet_output(tcp_descriptor* desc, HANDLE event)
}
if (driver_deq(ix, n) <= desc->low) {
if (IS_BUSY(INETP(desc))) {
- desc->inet.caller = desc->inet.busy_caller;
desc->inet.state &= ~INET_F_BUSY;
set_busy_port(desc->inet.port, 0);
/* if we have a timer then cancel and send ok to client */
@@ -13415,7 +13819,8 @@ static int should_use_so_bsdcompat(void)
*/
static ErlDrvData packet_inet_start(ErlDrvPort port, char* args, int protocol);
-static udp_descriptor* sctp_inet_copy(udp_descriptor* desc, SOCKET s, int* err)
+static udp_descriptor* sctp_inet_copy(udp_descriptor* desc, SOCKET s,
+ ErlDrvTermData owner, int* err)
{
ErlDrvSizeT q_low, q_high;
ErlDrvPort port = desc->inet.port;
@@ -13444,8 +13849,8 @@ static udp_descriptor* sctp_inet_copy(udp_descriptor* desc, SOCKET s, int* err)
copy_desc->inet.hsz = desc->inet.hsz;
copy_desc->inet.bufsz = desc->inet.bufsz;
- /* The new port will be linked and connected to the caller */
- port = driver_create_port(port, desc->inet.caller, "sctp_inet",
+ /* The new port will be linked and connected to the owner */
+ port = driver_create_port(port, owner, "sctp_inet",
(ErlDrvData) copy_desc);
if ((long)port == -1) {
*err = ENFILE;
@@ -13470,7 +13875,7 @@ static udp_descriptor* sctp_inet_copy(udp_descriptor* desc, SOCKET s, int* err)
#ifdef HAVE_UDP
-static int packet_inet_init()
+static int packet_inet_init(void)
{
sys_memzero((char *)&disassoc_sa, sizeof(disassoc_sa));
#ifdef AF_UNSPEC
@@ -13908,11 +14313,14 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
udp_descriptor* new_udesc;
int err;
SOCKET new_socket;
+ ErlDrvTermData caller;
+
+ caller = driver_caller(desc->port);
DDBG(desc,
("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] "
"packet_inet_ctl -> PEELOFF\r\n",
- __LINE__, desc->s, driver_caller(desc->port)) );
+ __LINE__, desc->s, caller) );
if (!IS_SCTP(desc))
return ctl_xerror(EXBADPORT, rbuf, rsize);
@@ -13930,12 +14338,14 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
return ctl_error(sock_errno(), rbuf, rsize);
}
- desc->caller = driver_caller(desc->port);
- if ((new_udesc = sctp_inet_copy(udesc, new_socket, &err)) == NULL) {
+ if ((new_udesc =
+ sctp_inet_copy(udesc, new_socket, caller, &err)) == NULL) {
sock_close(new_socket);
- desc->caller = 0;
return ctl_error(err, rbuf, rsize);
}
+ else {
+ desc->caller = caller;
+ }
new_udesc->inet.state = INET_STATE_CONNECTED;
new_udesc->inet.stype = SOCK_STREAM;
SET_NONBLOCKING(new_udesc->inet.s);
@@ -14020,14 +14430,19 @@ static void packet_inet_command(ErlDrvData e, char* buf, ErlDrvSizeT len)
{
udp_descriptor * udesc= (udp_descriptor*) e;
inet_descriptor* desc = INETP(udesc);
- char* ptr = buf;
+ char* ptr;
char* qtr;
char* xerror;
ErlDrvSizeT sz;
int code;
inet_address other;
- desc->caller = driver_caller(desc->port);
+ if (! init_caller(&desc->caller, &desc->caller_ref,
+ desc->port, &buf, &len)) {
+ driver_failure_posix(desc->port, EINVAL);
+ return;
+ }
+ ptr = buf;
if (!IS_OPEN(desc)) {
inet_reply_error(desc, EINVAL);
@@ -14645,8 +15060,7 @@ static MultiTimerData *add_multi_timer(tcp_descriptor *desc, ErlDrvPort port,
-----------------------------------------------------------------------------*/
static int
-save_subscriber(subs, subs_pid)
-subs_list *subs; ErlDrvTermData subs_pid;
+save_subscriber(subs_list *subs, ErlDrvTermData subs_pid)
{
subs_list *tmp;
@@ -14668,8 +15082,7 @@ subs_list *subs; ErlDrvTermData subs_pid;
}
static void
-free_subscribers(subs)
-subs_list *subs;
+free_subscribers(subs_list *subs)
{
subs_list *this;
subs_list *next;
diff --git a/erts/emulator/drivers/unix/ttsl_drv.c b/erts/emulator/drivers/unix/ttsl_drv.c
deleted file mode 100644
index 08ab714153..0000000000
--- a/erts/emulator/drivers/unix/ttsl_drv.c
+++ /dev/null
@@ -1,1606 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
- *
- * 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.
- *
- * %CopyrightEnd%
- */
-/*
- * Tty driver that reads one character at the time and provides a
- * smart line for output.
- */
-
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-
-#include "erl_driver.h"
-
-static int ttysl_init(void);
-static ErlDrvData ttysl_start(ErlDrvPort, char*);
-
-#ifdef HAVE_TERMCAP /* else make an empty driver that cannot be opened */
-
-#ifndef WANT_NONBLOCKING
-#define WANT_NONBLOCKING
-#endif
-
-#include "sys.h"
-#include <ctype.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <signal.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <locale.h>
-#include <unistd.h>
-#include <termios.h>
-#ifdef HAVE_WCWIDTH
-#include <wchar.h>
-#endif
-#ifdef HAVE_FCNTL_H
-#include <fcntl.h>
-#endif
-#ifdef HAVE_SYS_IOCTL_H
-#include <sys/ioctl.h>
-#endif
-#if !defined(HAVE_SETLOCALE) || !defined(HAVE_NL_LANGINFO) || !defined(HAVE_LANGINFO_H)
-#define PRIMITIVE_UTF8_CHECK 1
-#else
-#include <langinfo.h>
-#endif
-
-#if defined IOV_MAX
-#define MAXIOV IOV_MAX
-#elif defined UIO_MAXIOV
-#define MAXIOV UIO_MAXIOV
-#else
-#define MAXIOV 16
-#endif
-
-#define TRUE 1
-#define FALSE 0
-
-/* Termcap functions. */
-int tgetent(char* bp, char *name);
-int tgetnum(char* cap);
-int tgetflag(char* cap);
-char *tgetstr(char* cap, char** buf);
-char *tgoto(char* cm, int col, int line);
-int tputs(char* cp, int affcnt, int (*outc)(int c));
-
-/* Terminal capabilities in which we are interested. */
-static char *capbuf;
-static char *up, *down, *left, *right;
-static int cols, xn;
-static volatile int cols_needs_update = FALSE;
-
-/* The various opcodes. */
-#define OP_PUTC 0
-#define OP_MOVE 1
-#define OP_INSC 2
-#define OP_DELC 3
-#define OP_BEEP 4
-#define OP_PUTC_SYNC 5
-/* Control op */
-#define CTRL_OP_GET_WINSIZE 100
-#define CTRL_OP_GET_UNICODE_STATE 101
-#define CTRL_OP_SET_UNICODE_STATE 102
-
-/* We use 1024 as the buf size as that was the default buf size of FILE streams
- on all platforms that I checked. */
-#define TTY_BUFFSIZE 1024
-
-static int lbuf_size = BUFSIZ;
-static Uint32 *lbuf; /* The current line buffer */
-static int llen; /* The current line length */
-static int lpos; /* The current "cursor position" in the line buffer */
- /* NOTE: not the same as column position a char may not take a"
- * column to display or it might take many columns
- */
-/*
- * Tags used in line buffer to show that these bytes represent special characters,
- * Max unicode is 0x0010ffff, so we have lots of place for meta tags...
- */
-#define CONTROL_TAG 0x10000000U /* Control character, value in first position */
-#define ESCAPED_TAG 0x01000000U /* Escaped character, value in first position */
-#define TAG_MASK 0xFF000000U
-
-#define MAXSIZE (1 << 16)
-
-#define COL(_l) ((_l) % cols)
-#define LINE(_l) ((_l) / cols)
-
-#define NL '\n'
-
-/* Main interface functions. */
-static void ttysl_stop(ErlDrvData);
-static void ttysl_from_erlang(ErlDrvData, char*, ErlDrvSizeT);
-static void ttysl_to_tty(ErlDrvData, ErlDrvEvent);
-static void ttysl_flush_tty(ErlDrvData);
-static void ttysl_from_tty(ErlDrvData, ErlDrvEvent);
-static void ttysl_stop_select(ErlDrvEvent, void*);
-static Sint16 get_sint16(char*);
-
-static ErlDrvPort ttysl_port;
-static int ttysl_fd;
-static int ttysl_terminate = 0;
-static int ttysl_send_ok = 0;
-static ErlDrvBinary *putcbuf;
-static int putcpos;
-static int putclen;
-
-/* Functions that work on the line buffer. */
-static int start_lbuf(void);
-static int stop_lbuf(void);
-static int put_chars(byte*,int);
-static int move_rel(int);
-static int ins_chars(byte *,int);
-static int del_chars(int);
-static int step_over_chars(int);
-static int insert_buf(byte*,int);
-static int write_buf(Uint32 *,int,int);
-static int outc(int c);
-static int move_cursor(int,int);
-static int cp_pos_to_col(int cp_pos);
-
-
-/* Termcap functions. */
-static int start_termcap(void);
-static int stop_termcap(void);
-static int move_left(int);
-static int move_right(int);
-static int move_up(int);
-static int move_down(int);
-static void update_cols(void);
-
-/* Terminal setting functions. */
-static int tty_init(int,int,int,int);
-static int tty_set(int);
-static int tty_reset(int);
-static ErlDrvSSizeT ttysl_control(ErlDrvData, unsigned int,
- char *, ErlDrvSizeT, char **, ErlDrvSizeT);
-#ifdef ERTS_NOT_USED
-static RETSIGTYPE suspend(int);
-#endif
-static RETSIGTYPE cont(int);
-static RETSIGTYPE winch(int);
-
-/*#define LOG_DEBUG*/
-
-#ifdef LOG_DEBUG
-FILE *debuglog = NULL;
-
-#define DEBUGLOG(X) \
-do { \
- if (debuglog != NULL) { \
- my_debug_printf X; \
- } \
-} while (0)
-
-static void my_debug_printf(char *fmt, ...)
-{
- char buffer[1024];
- va_list args;
-
- va_start(args, fmt);
- erts_vsnprintf(buffer,1024,fmt,args);
- va_end(args);
- erts_fprintf(debuglog,"%s\n",buffer);
- /*erts_printf("Debuglog = %s\n",buffer);*/
-}
-
-#else
-
-#define DEBUGLOG(X)
-
-#endif
-
-static int utf8_mode = 0;
-static byte utf8buf[4]; /* for incomplete input */
-static int utf8buf_size; /* size of incomplete input */
-
-# define IF_IMPL(x) x
-#else
-# define IF_IMPL(x) NULL
-#endif /* HAVE_TERMCAP */
-
-/* Define the driver table entry. */
-struct erl_drv_entry ttsl_driver_entry = {
- ttysl_init,
- ttysl_start,
- IF_IMPL(ttysl_stop),
- IF_IMPL(ttysl_from_erlang),
- IF_IMPL(ttysl_from_tty),
- IF_IMPL(ttysl_to_tty),
- "tty_sl", /* driver_name */
- NULL, /* finish */
- NULL, /* handle */
- IF_IMPL(ttysl_control),
- NULL, /* timeout */
- NULL, /* outputv */
- NULL, /* ready_async */
- IF_IMPL(ttysl_flush_tty),
- NULL, /* call */
- NULL, /* event */
- ERL_DRV_EXTENDED_MARKER,
- ERL_DRV_EXTENDED_MAJOR_VERSION,
- ERL_DRV_EXTENDED_MINOR_VERSION,
- 0, /* ERL_DRV_FLAGs */
- NULL, /* handle2 */
- NULL, /* process_exit */
- IF_IMPL(ttysl_stop_select)
-};
-
-
-static int ttysl_init(void)
-{
-#ifdef HAVE_TERMCAP
- ttysl_port = (ErlDrvPort)-1;
- ttysl_fd = -1;
- lbuf = NULL; /* For line buffer handling */
- capbuf = NULL; /* For termcap handling */
-#endif
-#ifdef LOG_DEBUG
- {
- char *dl;
- if ((dl = getenv("TTYSL_DEBUG_LOG")) != NULL && *dl) {
- debuglog = fopen(dl,"w+");
- if (debuglog != NULL)
- setbuf(debuglog,NULL);
- }
- DEBUGLOG(("ttysl_init: Debuglog = %s(0x%ld)\n",dl,(long) debuglog));
- }
-#endif
- return 0;
-}
-
-static ErlDrvData ttysl_start(ErlDrvPort port, char* buf)
-{
-#ifndef HAVE_TERMCAP
- return ERL_DRV_ERROR_GENERAL;
-#else
- char *s, *t, *l;
- int canon, echo, sig; /* Terminal characteristics */
- int flag;
- extern int using_oldshell; /* set this to let the rest of erts know */
-
- DEBUGLOG(("ttysl_start: driver input \"%s\", ttysl_port = %d (-1 expected)", buf, ttysl_port));
- utf8buf_size = 0;
- if (ttysl_port != (ErlDrvPort)-1) {
- DEBUGLOG(("ttysl_start: failure with ttysl_port = %d, not initialized properly?\n", ttysl_port));
- return ERL_DRV_ERROR_GENERAL;
- }
-
- DEBUGLOG(("ttysl_start: isatty(0) = %d (1 expected), isatty(1) = %d (1 expected)", isatty(0), isatty(1)));
- if (!isatty(0) || !isatty(1)) {
- DEBUGLOG(("ttysl_start: failure in isatty, isatty(0) = %d, isatty(1) = %d", isatty(0), isatty(1)));
- return ERL_DRV_ERROR_GENERAL;
- }
-
- /* Set the terminal modes to default leave as is. */
- canon = echo = sig = 0;
-
- /* Parse the input parameters. */
- for (s = strchr(buf, ' '); s; s = t) {
- s++;
- /* Find end of this argument (start of next) and insert NUL. */
- if ((t = strchr(s, ' '))) {
- *t = '\0';
- }
- if ((flag = ((*s == '+') ? 1 : ((*s == '-') ? -1 : 0)))) {
- if (s[1] == 'c') canon = flag;
- if (s[1] == 'e') echo = flag;
- if (s[1] == 's') sig = flag;
- }
- else if ((ttysl_fd = open(s, O_RDWR, 0)) < 0) {
- DEBUGLOG(("ttysl_start: failed to open ttysl_fd, open(%s, O_RDWR, 0)) = %d\n", s, ttysl_fd));
- return ERL_DRV_ERROR_GENERAL;
- }
- }
-
- if (ttysl_fd < 0)
- ttysl_fd = 0;
-
- if (tty_init(ttysl_fd, canon, echo, sig) < 0 ||
- tty_set(ttysl_fd) < 0) {
- DEBUGLOG(("ttysl_start: failed init tty or set tty\n"));
- ttysl_port = (ErlDrvPort)-1;
- tty_reset(ttysl_fd);
- return ERL_DRV_ERROR_GENERAL;
- }
-
- /* Set up smart line and termcap stuff. */
- if (!start_lbuf() || !start_termcap()) {
- DEBUGLOG(("ttysl_start: failed to start_lbuf or start_termcap\n"));
- stop_lbuf(); /* Must free this */
- tty_reset(ttysl_fd);
- return ERL_DRV_ERROR_GENERAL;
- }
-
- SET_NONBLOCKING(ttysl_fd);
-
-#ifdef PRIMITIVE_UTF8_CHECK
- setlocale(LC_CTYPE, ""); /* Set international environment,
- ignore result */
- if (((l = getenv("LC_ALL")) && *l) ||
- ((l = getenv("LC_CTYPE")) && *l) ||
- ((l = getenv("LANG")) && *l)) {
- if (strstr(l, "UTF-8"))
- utf8_mode = 1;
- }
-
-#else
- l = setlocale(LC_CTYPE, ""); /* Set international environment */
- if (l != NULL) {
- utf8_mode = (strcmp(nl_langinfo(CODESET), "UTF-8") == 0);
- DEBUGLOG(("ttysl_start: setlocale: %s",l));
- }
-#endif
- DEBUGLOG(("ttysl_start: utf8_mode is %s",(utf8_mode) ? "on" : "off"));
- sys_signal(SIGCONT, cont);
- sys_signal(SIGWINCH, winch);
-
- driver_select(port, (ErlDrvEvent)(UWord)ttysl_fd, ERL_DRV_READ|ERL_DRV_USE, 1);
- ttysl_port = port;
-
- /* we need to know this when we enter the break handler */
- using_oldshell = 0;
-
- DEBUGLOG(("ttysl_start: successful start\n"));
- return (ErlDrvData)ttysl_port; /* Nothing important to return */
-#endif /* HAVE_TERMCAP */
-}
-
-#ifdef HAVE_TERMCAP
-
-#define DEF_HEIGHT 24
-#define DEF_WIDTH 80
-static void ttysl_get_window_size(Uint32 *width, Uint32 *height)
-{
-#ifdef TIOCGWINSZ
- struct winsize ws;
- if (ioctl(ttysl_fd,TIOCGWINSZ,&ws) == 0) {
- *width = (Uint32) ws.ws_col;
- *height = (Uint32) ws.ws_row;
- if (*width <= 0)
- *width = DEF_WIDTH;
- if (*height <= 0)
- *height = DEF_HEIGHT;
- return;
- }
-#endif
- *width = DEF_WIDTH;
- *height = DEF_HEIGHT;
-}
-
-static ErlDrvSSizeT ttysl_control(ErlDrvData drv_data,
- unsigned int command,
- char *buf, ErlDrvSizeT len,
- char **rbuf, ErlDrvSizeT rlen)
-{
- char resbuff[2*sizeof(Uint32)];
- ErlDrvSizeT res_size;
-
- command -= ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER;
- switch (command) {
- case CTRL_OP_GET_WINSIZE:
- {
- Uint32 w,h;
- ttysl_get_window_size(&w,&h);
- memcpy(resbuff,&w,sizeof(Uint32));
- memcpy(resbuff+sizeof(Uint32),&h,sizeof(Uint32));
- res_size = 2*sizeof(Uint32);
- }
- break;
- case CTRL_OP_GET_UNICODE_STATE:
- *resbuff = (utf8_mode) ? 1 : 0;
- res_size = 1;
- break;
- case CTRL_OP_SET_UNICODE_STATE:
- if (len > 0) {
- int m = (int) *buf;
- *resbuff = (utf8_mode) ? 1 : 0;
- res_size = 1;
- utf8_mode = (m) ? 1 : 0;
- } else {
- return 0;
- }
- break;
- default:
- return -1;
- }
- if (rlen < res_size) {
- *rbuf = driver_alloc(res_size);
- }
- memcpy(*rbuf,resbuff,res_size);
- return res_size;
-}
-
-
-static void ttysl_stop(ErlDrvData ttysl_data)
-{
- DEBUGLOG(("ttysl_stop: ttysl_port = %d\n",ttysl_port));
- if (ttysl_port != (ErlDrvPort)-1) {
- stop_lbuf();
- stop_termcap();
- tty_reset(ttysl_fd);
- driver_select(ttysl_port, (ErlDrvEvent)(UWord)ttysl_fd,
- ERL_DRV_WRITE|ERL_DRV_READ|ERL_DRV_USE, 0);
- sys_signal(SIGCONT, SIG_DFL);
- sys_signal(SIGWINCH, SIG_DFL);
- }
- ttysl_port = (ErlDrvPort)-1;
- ttysl_fd = -1;
- ttysl_terminate = 0;
- /* return TRUE; */
-}
-
-static int put_utf8(int ch, byte *target, int sz, int *pos)
-{
- Uint x = (Uint) ch;
- if (x < 0x80) {
- if (*pos >= sz) {
- return -1;
- }
- target[(*pos)++] = (byte) x;
- }
- else if (x < 0x800) {
- if (((*pos) + 1) >= sz) {
- return -1;
- }
- target[(*pos)++] = (((byte) (x >> 6)) |
- ((byte) 0xC0));
- target[(*pos)++] = (((byte) (x & 0x3F)) |
- ((byte) 0x80));
- } else if (x < 0x10000) {
- if ((x >= 0xD800 && x <= 0xDFFF) ||
- (x == 0xFFFE) ||
- (x == 0xFFFF)) { /* Invalid unicode range */
- return -1;
- }
- if (((*pos) + 2) >= sz) {
- return -1;
- }
-
- target[(*pos)++] = (((byte) (x >> 12)) |
- ((byte) 0xE0));
- target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) |
- ((byte) 0x80));
- target[(*pos)++] = (((byte) (x & 0x3F)) |
- ((byte) 0x80));
- } else if (x < 0x110000) { /* Standard imposed max */
- if (((*pos) + 3) >= sz) {
- return -1;
- }
- target[(*pos)++] = (((byte) (x >> 18)) |
- ((byte) 0xF0));
- target[(*pos)++] = ((((byte) (x >> 12)) & 0x3F) |
- ((byte) 0x80));
- target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) |
- ((byte) 0x80));
- target[(*pos)++] = (((byte) (x & 0x3F)) |
- ((byte) 0x80));
- } else {
- return -1;
- }
- return 0;
-}
-
-
-static int pick_utf8(byte *s, int sz, int *pos)
-{
- int size = sz - (*pos);
- byte *source;
- Uint unipoint;
-
- if (size > 0) {
- source = s + (*pos);
- if (((*source) & ((byte) 0x80)) == 0) {
- unipoint = (int) *source;
- ++(*pos);
- return (int) unipoint;
- } else if (((*source) & ((byte) 0xE0)) == 0xC0) {
- if (size < 2) {
- return -2;
- }
- if (((source[1] & ((byte) 0xC0)) != 0x80) ||
- ((*source) < 0xC2) /* overlong */) {
- return -1;
- }
- (*pos) += 2;
- unipoint =
- (((Uint) ((*source) & ((byte) 0x1F))) << 6) |
- ((Uint) (source[1] & ((byte) 0x3F)));
- return (int) unipoint;
- } else if (((*source) & ((byte) 0xF0)) == 0xE0) {
- if (size < 3) {
- return -2;
- }
- if (((source[1] & ((byte) 0xC0)) != 0x80) ||
- ((source[2] & ((byte) 0xC0)) != 0x80) ||
- (((*source) == 0xE0) && (source[1] < 0xA0)) /* overlong */ ) {
- return -1;
- }
- if ((((*source) & ((byte) 0xF)) == 0xD) &&
- ((source[1] & 0x20) != 0)) {
- return -1;
- }
- if (((*source) == 0xEF) && (source[1] == 0xBF) &&
- ((source[2] == 0xBE) || (source[2] == 0xBF))) {
- return -1;
- }
- (*pos) += 3;
- unipoint =
- (((Uint) ((*source) & ((byte) 0xF))) << 12) |
- (((Uint) (source[1] & ((byte) 0x3F))) << 6) |
- ((Uint) (source[2] & ((byte) 0x3F)));
- return (int) unipoint;
- } else if (((*source) & ((byte) 0xF8)) == 0xF0) {
- if (size < 4) {
- return -2 ;
- }
- if (((source[1] & ((byte) 0xC0)) != 0x80) ||
- ((source[2] & ((byte) 0xC0)) != 0x80) ||
- ((source[3] & ((byte) 0xC0)) != 0x80) ||
- (((*source) == 0xF0) && (source[1] < 0x90)) /* overlong */) {
- return -1;
- }
- if ((((*source) & ((byte)0x7)) > 0x4U) ||
- ((((*source) & ((byte)0x7)) == 0x4U) &&
- ((source[1] & ((byte)0x3F)) > 0xFU))) {
- return -1;
- }
- (*pos) += 4;
- unipoint =
- (((Uint) ((*source) & ((byte) 0x7))) << 18) |
- (((Uint) (source[1] & ((byte) 0x3F))) << 12) |
- (((Uint) (source[2] & ((byte) 0x3F))) << 6) |
- ((Uint) (source[3] & ((byte) 0x3F)));
- return (int) unipoint;
- } else {
- return -1;
- }
- } else {
- return -1;
- }
-}
-
-static int octal_or_hex_positions(Uint c)
-{
- int x = 0;
- Uint ch = c;
- if (!ch) {
- return 1;
- }
- while(ch) {
- ++x;
- ch >>= 3;
- }
- if (x <= 3) {
- return 3;
- }
- /* \x{H ...} format when larger than \777 */
- x = 0;
- ch = c;
- while(ch) {
- ++x;
- ch >>= 4;
- }
- return x+3;
-}
-
-static void octal_or_hex_format(Uint ch, byte *buf, int *pos)
-{
- static byte hex_chars[] = { '0','1','2','3','4','5','6','7','8','9',
- 'A','B','C','D','E','F'};
- int num = octal_or_hex_positions(ch);
- if (num != 3) {
- ASSERT(num > 3);
- buf[(*pos)++] = 'x';
- buf[(*pos)++] = '{';
- num -= 3;
- while(num--) {
- buf[(*pos)++] = hex_chars[((ch >> (4*num)) & 0xFU)];
- }
- buf[(*pos)++] = '}';
- } else {
- while(num--) {
- buf[(*pos)++] = ((byte) ((ch >> (3*num)) & 0x7U) + '0');
- }
- }
-}
-
-/*
- * Check that there is enough room in all buffers to copy all pad chars
- * and stiff we need If not, realloc lbuf.
- */
-static int check_buf_size(byte *s, int n)
-{
- int pos = 0;
- int ch;
- int size = 10;
-
- DEBUGLOG(("check_buf_size: n = %d",n));
- while(pos < n) {
- /* Indata is always UTF-8 */
- if ((ch = pick_utf8(s,n,&pos)) < 0) {
- /* XXX temporary allow invalid chars */
- ch = (int) s[pos];
- DEBUGLOG(("check_buf_size: Invalid UTF8:%d",ch));
- ++pos;
- }
- if (utf8_mode) { /* That is, terminal is UTF8 compliant */
- if (ch >= 128 || isprint(ch)) {
-#ifdef HAVE_WCWIDTH
- int width;
-#endif
- DEBUGLOG(("check_buf_size: Printable(UTF-8:%d):%d",pos,ch));
- size++;
-#ifdef HAVE_WCWIDTH
- if ((width = wcwidth(ch)) > 1) {
- size += width - 1;
- }
-#endif
- } else if (ch == '\t') {
- size += 8;
- } else {
- DEBUGLOG(("check_buf_size: Magic(UTF-8:%d):%d",pos,ch));
- size += 2;
- }
- } else {
- if (ch <= 255 && isprint(ch)) {
- DEBUGLOG(("check_buf_size: Printable:%d",ch));
- size++;
- } else if (ch == '\t')
- size += 8;
- else if (ch >= 128) {
- DEBUGLOG(("check_buf_size: Non printable:%d",ch));
- size += (octal_or_hex_positions(ch) + 1);
- }
- else {
- DEBUGLOG(("check_buf_size: Magic:%d",ch));
- size += 2;
- }
- }
- }
-
- if (size + lpos >= lbuf_size) {
-
- lbuf_size = size + lpos + BUFSIZ;
- if ((lbuf = driver_realloc(lbuf, lbuf_size * sizeof(Uint32))) == NULL) {
- DEBUGLOG(("check_buf_size: alloc failure of %d bytes", lbuf_size * sizeof(Uint32)));
- driver_failure(ttysl_port, -1);
- return(0);
- }
- }
- DEBUGLOG(("check_buf_size: success\n"));
- return(1);
-}
-
-
-static void ttysl_from_erlang(ErlDrvData ttysl_data, char* buf, ErlDrvSizeT count)
-{
- ErlDrvSizeT sz;
-
- sz = driver_sizeq(ttysl_port);
-
- putclen = count > TTY_BUFFSIZE ? TTY_BUFFSIZE : count;
- putcbuf = driver_alloc_binary(putclen);
- putcpos = 0;
-
- if (lpos > MAXSIZE)
- put_chars((byte*)"\n", 1);
-
- DEBUGLOG(("ttysl_from_erlang: OP = %d", buf[0]));
-
- switch (buf[0]) {
- case OP_PUTC_SYNC:
- /* Using sync means that we have to send an ok to the
- controlling process for each command call. We delay
- sending ok if the driver queue exceeds a certain size.
- We do not set ourselves as a busy port, as this
- could be very bad for user_drv, if it gets blocked on
- the port_command. */
- /* fall through */
- case OP_PUTC:
- DEBUGLOG(("ttysl_from_erlang: OP: Putc(%lu)",(unsigned long) count-1));
- if (check_buf_size((byte*)buf+1, count-1) == 0)
- return;
- put_chars((byte*)buf+1, count-1);
- break;
- case OP_MOVE:
- move_rel(get_sint16(buf+1));
- break;
- case OP_INSC:
- if (check_buf_size((byte*)buf+1, count-1) == 0)
- return;
- ins_chars((byte*)buf+1, count-1);
- break;
- case OP_DELC:
- del_chars(get_sint16(buf+1));
- break;
- case OP_BEEP:
- outc('\007');
- break;
- default:
- /* Unknown op, just ignore. */
- break;
- }
-
- driver_enq_bin(ttysl_port,putcbuf,0,putcpos);
- driver_free_binary(putcbuf);
-
- if (sz == 0) {
- for (;;) {
- int written, qlen;
- SysIOVec *iov;
-
- iov = driver_peekq(ttysl_port,&qlen);
- if (iov)
- written = writev(ttysl_fd, iov, qlen > MAXIOV ? MAXIOV : qlen);
- else
- written = 0;
- if (written < 0) {
- if (errno == ERRNO_BLOCK || errno == EINTR) {
- driver_select(ttysl_port,(ErlDrvEvent)(long)ttysl_fd,
- ERL_DRV_USE|ERL_DRV_WRITE,1);
- break;
- } else {
- DEBUGLOG(("ttysl_from_erlang: driver failure in writev(%d,..) = %d (errno = %d)\n", ttysl_fd, written, errno));
- driver_failure_posix(ttysl_port, errno);
- return;
- }
- } else {
- if (driver_deq(ttysl_port, written) == 0)
- break;
- }
- }
- }
-
- if (buf[0] == OP_PUTC_SYNC) {
- if (driver_sizeq(ttysl_port) > TTY_BUFFSIZE && !ttysl_terminate) {
- /* We delay sending the ack until the buffer has been consumed */
- ttysl_send_ok = 1;
- } else {
- ErlDrvTermData spec[] = {
- ERL_DRV_PORT, driver_mk_port(ttysl_port),
- ERL_DRV_ATOM, driver_mk_atom("ok"),
- ERL_DRV_TUPLE, 2
- };
- ASSERT(ttysl_send_ok == 0);
- erl_drv_output_term(driver_mk_port(ttysl_port), spec,
- sizeof(spec) / sizeof(spec[0]));
- }
- }
-
- return; /* TRUE; */
-}
-
-static void ttysl_to_tty(ErlDrvData ttysl_data, ErlDrvEvent fd) {
- for (;;) {
- int written, qlen;
- SysIOVec *iov;
- ErlDrvSizeT sz;
-
- iov = driver_peekq(ttysl_port,&qlen);
-
- DEBUGLOG(("ttysl_to_tty: qlen = %d", qlen));
-
- if (iov)
- written = writev(ttysl_fd, iov, qlen > MAXIOV ? MAXIOV : qlen);
- else
- written = 0;
- if (written < 0) {
- if (errno == EINTR) {
- continue;
- } else if (errno != ERRNO_BLOCK){
- DEBUGLOG(("ttysl_to_tty: driver failure in writev(%d,..) = %d (errno = %d)\n", ttysl_fd, written, errno));
- driver_failure_posix(ttysl_port, errno);
- }
- break;
- } else {
- sz = driver_deq(ttysl_port, written);
- if (sz < TTY_BUFFSIZE && ttysl_send_ok) {
- ErlDrvTermData spec[] = {
- ERL_DRV_PORT, driver_mk_port(ttysl_port),
- ERL_DRV_ATOM, driver_mk_atom("ok"),
- ERL_DRV_TUPLE, 2
- };
- ttysl_send_ok = 0;
- erl_drv_output_term(driver_mk_port(ttysl_port), spec,
- sizeof(spec) / sizeof(spec[0]));
- }
- if (sz == 0) {
- driver_select(ttysl_port,(ErlDrvEvent)(long)ttysl_fd,
- ERL_DRV_WRITE,0);
- if (ttysl_terminate) {
- /* flush has been called, which means we should terminate
- when queue is empty. This will not send any exit
- message */
- DEBUGLOG(("ttysl_to_tty: ttysl_terminate normal\n"));
- driver_failure_atom(ttysl_port, "normal");
- }
- break;
- }
- }
- }
-
- return;
-}
-
-static void ttysl_flush_tty(ErlDrvData ttysl_data) {
- DEBUGLOG(("ttysl_flush_tty: .."));
- ttysl_terminate = 1;
- return;
-}
-
-static void ttysl_from_tty(ErlDrvData ttysl_data, ErlDrvEvent fd)
-{
- byte b[1024];
- ssize_t i;
- int ch = 0, pos = 0;
- int left = 1024;
- byte *p = b;
- byte t[1024];
- int tpos;
-
- if (utf8buf_size > 0) {
- memcpy(b,utf8buf,utf8buf_size);
- left -= utf8buf_size;
- p += utf8buf_size;
- utf8buf_size = 0;
- }
-
- DEBUGLOG(("ttysl_from_tty: remainder = %d", left));
-
- if ((i = read((int)(SWord)fd, (char *) p, left)) >= 0) {
- if (p != b) {
- i += (p - b);
- }
- if (utf8_mode) { /* Hopefully an UTF8 terminal */
- while(pos < i && (ch = pick_utf8(b,i,&pos)) >= 0)
- ;
- if (ch == -2 && i - pos <= 4) {
- /* bytes left to care for */
- utf8buf_size = i -pos;
- memcpy(utf8buf,b+pos,utf8buf_size);
- } else if (ch == -1) {
- DEBUGLOG(("ttysl_from_tty: Giving up on UTF8 mode, invalid character"));
- utf8_mode = 0;
- goto latin_terminal;
- }
- driver_output(ttysl_port, (char *) b, pos);
- } else {
- latin_terminal:
- tpos = 0;
- while (pos < i) {
- while (tpos < 1020 && pos < i) { /* Max 4 bytes for UTF8 */
- put_utf8((int) b[pos++], t, 1024, &tpos);
- }
- driver_output(ttysl_port, (char *) t, tpos);
- tpos = 0;
- }
- }
- } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
- DEBUGLOG(("ttysl_from_tty: driver failure in read(%d,..) = %d (errno = %d)\n", (int)(SWord)fd, i, errno));
- driver_failure(ttysl_port, -1);
- }
-}
-
-static void ttysl_stop_select(ErlDrvEvent e, void* _)
-{
- int fd = (int)(long)e;
- if (fd != 0) {
- close(fd);
- }
-}
-
-/* Procedures for putting and getting integers to/from strings. */
-static Sint16 get_sint16(char *s)
-{
- return ((*s << 8) | ((byte*)s)[1]);
-}
-
-static int start_lbuf(void)
-{
- if (!lbuf && !(lbuf = ( Uint32*) driver_alloc(lbuf_size * sizeof(Uint32))))
- return FALSE;
- llen = 0;
- lpos = 0;
- return TRUE;
-}
-
-static int stop_lbuf(void)
-{
- if (lbuf) {
- driver_free(lbuf);
- lbuf = NULL;
- }
- return TRUE;
-}
-
-/* Put l bytes (in UTF8) from s into the buffer and output them. */
-static int put_chars(byte *s, int l)
-{
- int n;
-
- n = insert_buf(s, l);
- if (lpos > llen)
- llen = lpos;
- if (n > 0)
- write_buf(lbuf + lpos - n, n, 0);
- return TRUE;
-}
-
-/*
- * Move the current position forwards or backwards within the current
- * line. We know about padding.
- */
-static int move_rel(int n)
-{
- int npos; /* The new position */
-
- /* Step forwards or backwards over the buffer. */
- npos = step_over_chars(n);
-
- /* Calculate move, updates pointers and move the cursor. */
- move_cursor(lpos, npos);
- lpos = npos;
- return TRUE;
-}
-
-/* Insert characters into the buffer at the current position. */
-static int ins_chars(byte *s, int l)
-{
- int n, tl;
- Uint32 *tbuf = NULL; /* Suppress warning about use-before-set */
-
- /* Move tail of buffer to make space. */
- if ((tl = llen - lpos) > 0) {
- if ((tbuf = driver_alloc(tl * sizeof(Uint32))) == NULL)
- return FALSE;
- memcpy(tbuf, lbuf + lpos, tl * sizeof(Uint32));
- }
- n = insert_buf(s, l);
- if (tl > 0) {
- memcpy(lbuf + lpos, tbuf, tl * sizeof(Uint32));
- driver_free(tbuf);
- }
- llen += n;
- write_buf(lbuf + (lpos - n), llen - (lpos - n), 0);
- move_cursor(llen, lpos);
- return TRUE;
-}
-
-/*
- * Delete characters in the buffer. Can delete characters before (n < 0)
- * and after (n > 0) the current position. Cursor left at beginning of
- * deleted block.
- */
-static int del_chars(int n)
-{
- int i, l, r;
- int pos;
- int gcs; /* deleted grapheme characters */
-
- update_cols();
-
- /* Step forward or backwards over n logical characters. */
- pos = step_over_chars(n);
- DEBUGLOG(("del_chars: %d from %d %d %d\n", n, lpos, pos, llen));
- if (pos > lpos) {
- l = pos - lpos; /* Buffer characters to delete */
- r = llen - lpos - l; /* Characters after deleted */
- gcs = cp_pos_to_col(pos) - cp_pos_to_col(lpos);
- /* Fix up buffer and buffer pointers. */
- if (r > 0)
- memmove(lbuf + lpos, lbuf + pos, r * sizeof(Uint32));
- llen -= l;
- /* Write out characters after, blank the tail and jump back to lpos. */
- write_buf(lbuf + lpos, r, 0);
- for (i = gcs ; i > 0; --i)
- outc(' ');
- if (xn && COL(cp_pos_to_col(llen)+gcs) == 0)
- {
- outc(' ');
- move_left(1);
- }
- move_cursor(llen + gcs, lpos);
- }
- else if (pos < lpos) {
- l = lpos - pos; /* Buffer characters */
- r = llen - lpos; /* Characters after deleted */
- gcs = -move_cursor(lpos, lpos-l); /* Move back */
- /* Fix up buffer and buffer pointers. */
- if (r > 0)
- memmove(lbuf + pos, lbuf + lpos, r * sizeof(Uint32));
- lpos -= l;
- llen -= l;
- /* Write out characters after, blank the tail and jump back to lpos. */
- write_buf(lbuf + lpos, r, 0);
- for (i = gcs ; i > 0; --i)
- outc(' ');
- if (xn && COL(cp_pos_to_col(llen)+gcs) == 0)
- {
- outc(' ');
- move_left(1);
- }
- move_cursor(llen + gcs, lpos);
- }
- return TRUE;
-}
-
-/* Step over n logical characters, check for overflow. */
-static int step_over_chars(int n)
-{
- Uint32 *c, *beg, *end;
-
- beg = lbuf;
- end = lbuf + llen;
- c = lbuf + lpos;
- for ( ; n > 0 && c < end; --n) {
- c++;
- while (c < end && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0))
- c++;
- }
- for ( ; n < 0 && c > beg; n++) {
- --c;
- while (c > beg && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0))
- --c;
- }
- return c - lbuf;
-}
-
-/*
- * Insert n characters into the buffer at lpos.
- * Know about pad characters and treat \n specially.
- */
-
-static int insert_buf(byte *s, int n)
-{
- int pos = 0;
- int buffpos = lpos;
- int ch;
-
- while (pos < n) {
- if ((ch = pick_utf8(s,n,&pos)) < 0) {
- /* XXX temporary allow invalid chars */
- ch = (int) s[pos];
- DEBUGLOG(("insert_buf: Invalid UTF8:%d",ch));
- ++pos;
- }
- if ((utf8_mode && (ch >= 128 || isprint(ch))) || (ch <= 255 && isprint(ch))) {
- DEBUGLOG(("insert_buf: Printable(UTF-8):%d",ch));
- lbuf[lpos++] = (Uint32) ch;
- } else if (ch >= 128) { /* not utf8 mode */
- int nc = octal_or_hex_positions(ch);
- lbuf[lpos++] = ((Uint32) ch) | ESCAPED_TAG;
- while (nc--) {
- lbuf[lpos++] = ESCAPED_TAG;
- }
- } else if (ch == '\t') {
- do {
- lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch));
- ch = 0;
- } while (lpos % 8);
- } else if (ch == '\e') {
- DEBUGLOG(("insert_buf: ANSI Escape: \\e"));
- lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch));
- } else if (ch == '\n' || ch == '\r') {
- write_buf(lbuf + buffpos, lpos - buffpos, 1);
- outc('\r');
- if (ch == '\n')
- outc('\n');
- if (llen > lpos) {
- memmove(lbuf, lbuf + lpos, llen - lpos);
- }
- llen -= lpos;
- lpos = buffpos = 0;
- } else {
- DEBUGLOG(("insert_buf: Magic(UTF-8):%d",ch));
- lbuf[lpos++] = ch | CONTROL_TAG;
- lbuf[lpos++] = CONTROL_TAG;
- }
- }
- return lpos - buffpos; /* characters "written" into
- current buffer (may be less due to newline) */
-}
-
-
-
-/*
- * Write n characters in line buffer starting at s. Be smart about
- * non-printables. Know about pad characters and that \n can never
- * occur normally.
- */
-
-static int write_buf(Uint32 *s, int n, int next_char_is_crnl)
-{
- byte ubuf[4];
- int ubytes = 0, i;
- byte lastput = ' ';
-
- update_cols();
-
- DEBUGLOG(("write_buf(%d, %d)",n,next_char_is_crnl));
-
- while (n > 0) {
- if (!(*s & TAG_MASK) ) {
- if (utf8_mode) {
- ubytes = 0;
- if (put_utf8((int) *s, ubuf, 4, &ubytes) == 0) {
- for (i = 0; i < ubytes; ++i) {
- outc(ubuf[i]);
- }
- lastput = 0; /* Means the last written character was multibyte UTF8 */
- }
- } else {
- outc((byte) *s);
- lastput = (byte) *s;
- }
- --n;
- ++s;
- } else if (*s == (CONTROL_TAG | ((Uint32) '\t'))) {
- outc(lastput = ' ');
- --n; s++;
- while (n > 0 && *s == CONTROL_TAG) {
- outc(lastput = ' ');
- --n; s++;
- }
- } else if (*s == (CONTROL_TAG | ((Uint32) '\e'))) {
- outc(lastput = '\e');
- --n;
- ++s;
- } else if (*s & CONTROL_TAG) {
- outc('^');
- outc(lastput = ((byte) ((*s == 0177) ? '?' : *s | 0x40)));
- n -= 2;
- s += 2;
- } else if (*s & ESCAPED_TAG) {
- Uint32 ch = *s & ~(TAG_MASK);
- byte *octbuff;
- byte octtmp[256];
- int octbytes;
- DEBUGLOG(("write_buf: Escaped: %d", ch));
- octbytes = octal_or_hex_positions(ch);
- if (octbytes > 256) {
- octbuff = driver_alloc(octbytes);
- } else {
- octbuff = octtmp;
- }
- octbytes = 0;
- octal_or_hex_format(ch, octbuff, &octbytes);
- DEBUGLOG(("write_buf: octbytes: %d", octbytes));
- outc('\\');
- for (i = 0; i < octbytes; ++i) {
- outc(lastput = octbuff[i]);
- DEBUGLOG(("write_buf: outc: %d", (int) lastput));
- }
- n -= octbytes+1;
- s += octbytes+1;
- if (octbuff != octtmp) {
- driver_free(octbuff);
- }
- } else {
- DEBUGLOG(("write_buf: Very unexpected character %d",(int) *s));
- ++n;
- --s;
- }
- }
- /* Check landed in first column of new line and have 'xn' bug.
- * https://www.gnu.org/software/termutils/manual/termcap-1.3/html_node/termcap_27.html
- *
- * The 'xn' bugs (from what I understand) is that the terminal cursor does
- * not wrap to the next line when the current line is full. For example:
- *
- * If the terminal column size is 20 and we output 20 'a' the cursor will be
- * on row 1, column 21. While we actually want it at row 2 column 0. So to
- * achieve this the code below emits " \b", which will move the cursor to the
- * correct place.
- *
- * We should not apply this 'xn' workaround if we know that the next character
- * to be emitted is a cr|nl as that will wrap by itself.
- */
- n = s - lbuf;
- if (!next_char_is_crnl && xn && n != 0 && COL(cp_pos_to_col(n)) == 0) {
- if (n >= llen) {
- outc(' ');
- } else if (lastput == 0) { /* A multibyte UTF8 character */
- for (i = 0; i < ubytes; ++i) {
- outc(ubuf[i]);
- }
- } else {
- outc(lastput);
- }
- move_left(1);
- }
- return TRUE;
-}
-
-
-/* The basic procedure for outputting one character. */
-static int outc(int c)
-{
- putcbuf->orig_bytes[putcpos++] = c;
- if (putcpos == putclen) {
- driver_enq_bin(ttysl_port,putcbuf,0,putclen);
- driver_free_binary(putcbuf);
- putcpos = 0;
- putclen = TTY_BUFFSIZE;
- putcbuf = driver_alloc_binary(BUFSIZ);
- }
- return 1;
-}
-
-static int move_cursor(int from_pos, int to_pos)
-{
- int from_col, to_col;
- int dc, dl;
- update_cols();
-
- from_col = cp_pos_to_col(from_pos);
- to_col = cp_pos_to_col(to_pos);
-
- dc = COL(to_col) - COL(from_col);
- dl = LINE(to_col) - LINE(from_col);
- DEBUGLOG(("move_cursor: from %d %d to %d %d => %d %d\n",
- from_pos, from_col, to_pos, to_col, dl, dc));
- if (dl > 0)
- move_down(dl);
- else if (dl < 0)
- move_up(-dl);
- if (dc > 0)
- move_right(dc);
- else if (dc < 0)
- move_left(-dc);
- return to_col-from_col;
-}
-
-/*
- * Returns the length of an ANSI escape code in a buffer, this function only consider
- * color escape sequences like `\e[33m` or `\e[21;33m`. If a sequence has no valid
- * terminator, the length is equal the number of characters between `\e` and the first
- * invalid character, inclusive.
- */
-
-static int ansi_escape_width(Uint32 *s, int max_length)
-{
- int i;
-
- if (*s != (CONTROL_TAG | ((Uint32) '\e'))) {
- return 0;
- } else if (max_length <= 1) {
- return 1;
- } else if (s[1] != '[') {
- return 2;
- }
-
- for (i = 2; i < max_length && (s[i] == ';' || (s[i] >= '0' && s[i] <= '9')); i++);
-
- return i + 1;
-}
-
-static int cp_pos_to_col(int cp_pos)
-{
- /*
- * If we don't have any character width information. Assume that
- * code points are one column wide
- */
- int w = 1;
- int col = 0;
- int i = 0;
- int j;
-
- if (cp_pos > llen) {
- col += cp_pos - llen;
- cp_pos = llen;
- }
-
- while (i < cp_pos) {
- j = ansi_escape_width(lbuf + i, llen - i);
-
- if (j > 0) {
- i += j;
- } else {
-#ifdef HAVE_WCWIDTH
- w = wcwidth(lbuf[i]);
-#endif
- if (w > 0) {
- col += w;
- }
- i++;
- }
- }
-
- return col;
-}
-
-static int start_termcap(void)
-{
- int eres;
- size_t envsz = 1024;
- char *env = NULL;
- char *c;
- int tres;
-
- DEBUGLOG(("start_termcap: .."));
-
- capbuf = driver_alloc(1024);
- if (!capbuf)
- goto termcap_false;
- eres = erl_drv_getenv("TERM", capbuf, &envsz);
- if (eres == 0)
- env = capbuf;
- else if (eres < 0) {
- DEBUGLOG(("start_termcap: failure in erl_drv_getenv(\"TERM\", ..) = %d\n", eres));
- goto termcap_false;
- } else /* if (eres > 1) */ {
- char *envbuf = driver_alloc(envsz);
- if (!envbuf)
- goto termcap_false;
- while (1) {
- char *newenvbuf;
- eres = erl_drv_getenv("TERM", envbuf, &envsz);
- if (eres == 0)
- break;
- newenvbuf = driver_realloc(envbuf, envsz);
- if (eres < 0 || !newenvbuf) {
- DEBUGLOG(("start_termcap: failure in erl_drv_getenv(\"TERM\", ..) = %d or realloc buf == %p\n", eres, newenvbuf));
- env = newenvbuf ? newenvbuf : envbuf;
- goto termcap_false;
- }
- envbuf = newenvbuf;
- }
- env = envbuf;
- }
- if ((tres = tgetent((char*)lbuf, env)) <= 0) {
- DEBUGLOG(("start_termcap: failure in tgetent(..) = %d\n", tres));
- goto termcap_false;
- }
- if (env != capbuf) {
- env = NULL;
- driver_free(env);
- }
- c = capbuf;
- cols = tgetnum("co");
- if (cols <= 0)
- cols = DEF_WIDTH;
- xn = tgetflag("xn");
- up = tgetstr("up", &c);
- if (!(down = tgetstr("do", &c)))
- down = "\n";
- if (!(left = tgetflag("bs") ? "\b" : tgetstr("bc", &c)))
- left = "\b"; /* Can't happen - but does on Solaris 2 */
- right = tgetstr("nd", &c);
- if (up && down && left && right) {
- DEBUGLOG(("start_termcap: successful start\n"));
- return TRUE;
- }
- DEBUGLOG(("start_termcap: failed start\n"));
- termcap_false:
- if (env && env != capbuf)
- driver_free(env);
- if (capbuf)
- driver_free(capbuf);
- capbuf = NULL;
- return FALSE;
-}
-
-static int stop_termcap(void)
-{
- if (capbuf) driver_free(capbuf);
- capbuf = NULL;
- return TRUE;
-}
-
-static int move_left(int n)
-{
- while (n-- > 0)
- tputs(left, 1, outc);
- return TRUE;
-}
-
-static int move_right(int n)
-{
- while (n-- > 0)
- tputs(right, 1, outc);
- return TRUE;
-}
-
-static int move_up(int n)
-{
- while (n-- > 0)
- tputs(up, 1, outc);
- return TRUE;
-}
-
-static int move_down(int n)
-{
- while (n-- > 0)
- tputs(down, 1, outc);
- return TRUE;
-}
-
-
-/*
- * Updates cols if terminal has resized (SIGWINCH). Should be called
- * at the start of any function that uses the COL or LINE macros. If
- * the terminal is resized after calling this function but before use
- * of the macros, then we may write to the wrong screen location.
- *
- * We cannot call this from the SIGWINCH handler because it uses
- * ioctl() which is not a safe function as listed in the signal(7)
- * man page.
- */
-static void update_cols(void)
-{
- Uint32 width, height;
-
- if (cols_needs_update) {
- cols_needs_update = FALSE;
- ttysl_get_window_size(&width, &height);
- cols = width;
- }
-}
-
-
-/*
- * Put a terminal device into non-canonical mode with ECHO off.
- * Before doing so we first save the terminal's current mode,
- * assuming the caller will call the tty_reset() function
- * (also in this file) when it's done with raw mode.
- */
-
-static struct termios tty_smode, tty_rmode;
-
-static int tty_init(int fd, int canon, int echo, int sig) {
- int tres;
- DEBUGLOG(("tty_init: fd = %d, canon = %d, echo = %d, sig = %d", fd, canon, echo, sig));
- if ((tres = tcgetattr(fd, &tty_rmode)) < 0) {
- DEBUGLOG(("tty_init: failure in tcgetattr(%d,..) = %d\n", fd, tres));
- return -1;
- }
- tty_smode = tty_rmode;
-
- /* Default characteristics for all usage including termcap output. */
- tty_smode.c_iflag &= ~ISTRIP;
-
- /* Turn canonical (line mode) on off. */
- if (canon > 0) {
- tty_smode.c_iflag |= ICRNL;
- tty_smode.c_lflag |= ICANON;
- tty_smode.c_oflag |= OPOST;
- tty_smode.c_cc[VEOF] = tty_rmode.c_cc[VEOF];
-#ifdef VDSUSP
- tty_smode.c_cc[VDSUSP] = tty_rmode.c_cc[VDSUSP];
-#endif
- }
- if (canon < 0) {
- tty_smode.c_iflag &= ~ICRNL;
- tty_smode.c_lflag &= ~ICANON;
- tty_smode.c_oflag &= ~OPOST;
- /* Must get these really right or funny effects can occur. */
- tty_smode.c_cc[VMIN] = 1;
- tty_smode.c_cc[VTIME] = 0;
-#ifdef VDSUSP
- tty_smode.c_cc[VDSUSP] = 0;
-#endif
- }
-
- /* Turn echo on or off. */
- if (echo > 0)
- tty_smode.c_lflag |= ECHO;
- if (echo < 0)
- tty_smode.c_lflag &= ~ECHO;
-
- /* Set extra characteristics for "RAW" mode, no signals. */
- if (sig > 0) {
- /* Ignore IMAXBEL as not POSIX. */
-#ifndef QNX
- tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON|IXANY);
-#else
- tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON);
-#endif
- tty_smode.c_lflag |= (ISIG|IEXTEN);
- }
- if (sig < 0) {
- /* Ignore IMAXBEL as not POSIX. */
-#ifndef QNX
- tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON|IXANY);
-#else
- tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON);
-#endif
- tty_smode.c_lflag &= ~(ISIG|IEXTEN);
- }
- DEBUGLOG(("tty_init: successful init\n"));
- return 0;
-}
-
-/*
- * Set/restore a terminal's mode to whatever it was on the most
- * recent call to the tty_init() function above.
- */
-
-static int tty_set(int fd)
-{
- int tres;
- DEBUGF(("tty_set: Setting tty...\n"));
-
- if ((tres = tcsetattr(fd, TCSANOW, &tty_smode)) < 0) {
- DEBUGLOG(("tty_set: failure in tcgetattr(%d,..) = %d\n", fd, tres));
- return(-1);
- }
- return(0);
-}
-
-static int tty_reset(int fd) /* of terminal device */
-{
- int tres;
- DEBUGF(("tty_reset: Resetting tty...\n"));
-
- if ((tres = tcsetattr(fd, TCSANOW, &tty_rmode)) < 0) {
- DEBUGLOG(("tty_reset: failure in tcsetattr(%d,..) = %d\n", fd, tres));
- return(-1);
- }
- return(0);
-}
-
-/*
- * Signal handler to cope with signals so that we can reset the tty
- * to the original settings
- */
-
-#ifdef ERTS_NOT_USED
-/* XXX: A mistake that it isn't used, or should it be removed? */
-
-static RETSIGTYPE suspend(int sig)
-{
- if (tty_reset(ttysl_fd) < 0) {
- DEBUGLOG(("signal: failure in suspend(%d), can't reset tty %d\n", sig, ttysl_fd));
- fprintf(stderr,"Can't reset tty \n");
- exit(1);
- }
-
- sys_signal(sig, SIG_DFL); /* Set signal handler to default */
- sys_sigrelease(sig); /* Allow 'sig' to come through */
- kill(getpid(), sig); /* Send ourselves the signal */
- sys_sigblock(sig); /* Reset to old mask */
- sys_signal(sig, suspend); /* Reset signal handler */
-
- if (tty_set(ttysl_fd) < 0) {
- DEBUGLOG(("signal: failure in suspend(%d), can't set tty %d\n", sig, ttysl_fd));
- fprintf(stderr,"Can't set tty raw \n");
- exit(1);
- }
-}
-
-#endif
-
-static RETSIGTYPE cont(int sig)
-{
- if (tty_set(ttysl_fd) < 0) {
- DEBUGLOG(("signal: failure in cont(%d), can't set tty raw %d\n", sig, ttysl_fd));
- fprintf(stderr,"Can't set tty raw\n");
- exit(1);
- }
-}
-
-static RETSIGTYPE winch(int sig)
-{
- cols_needs_update = TRUE;
-}
-#endif /* HAVE_TERMCAP */
diff --git a/erts/emulator/drivers/win32/ttsl_drv.c b/erts/emulator/drivers/win32/ttsl_drv.c
deleted file mode 100644
index 8917e48919..0000000000
--- a/erts/emulator/drivers/win32/ttsl_drv.c
+++ /dev/null
@@ -1,786 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2021. All Rights Reserved.
- *
- * 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.
- *
- * %CopyrightEnd%
- */
-/*
- * Tty driver that reads one character at the time and provides a
- * smart line for output.
- */
-
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-#include "sys.h"
-#include <ctype.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <signal.h>
-
-#include "erl_driver.h"
-#include "win_con.h"
-
-#define TRUE 1
-#define FALSE 0
-
-static int cols; /* Number of columns available. */
-static int rows; /* Number of rows available. */
-
-/* The various opcodes. */
-#define OP_PUTC 0
-#define OP_MOVE 1
-#define OP_INSC 2
-#define OP_DELC 3
-#define OP_BEEP 4
-#define OP_PUTC_SYNC 5
-
-/* Control op */
-#define CTRL_OP_GET_WINSIZE 100
-#define CTRL_OP_GET_UNICODE_STATE 101
-#define CTRL_OP_SET_UNICODE_STATE 102
-
-static int lbuf_size = BUFSIZ;
-Uint32 *lbuf; /* The current line buffer */
-int llen; /* The current line length */
-int lpos; /* The current "cursor position" in the line buffer */
-
-/*
- * Tags used in line buffer to show that these bytes represent special characters,
- * Max unicode is 0x0010ffff, so we have lots of place for meta tags...
- */
-#define CONTROL_TAG 0x10000000U /* Control character, value in first position */
-#define ESCAPED_TAG 0x01000000U /* Escaped character, value in first position */
-#define TAG_MASK 0xFF000000U
-
-#define MAXSIZE (1 << 16)
-
-#define ISPRINT(c) (isprint(c) || (128+32 <= (c) && (c) < 256))
-
-#define DEBUGLOG(X) /* nothing */
-
-/*
- * XXX These are used by win_con.c (for command history).
- * Should be cleaned up.
- */
-
-
-#define NL '\n'
-
-/* Main interface functions. */
-static int ttysl_init(void);
-static ErlDrvData ttysl_start(ErlDrvPort, char*);
-static void ttysl_stop(ErlDrvData);
-static ErlDrvSSizeT ttysl_control(ErlDrvData, unsigned int,
- char *, ErlDrvSizeT, char **, ErlDrvSizeT);
-static void ttysl_from_erlang(ErlDrvData, char*, ErlDrvSizeT);
-static void ttysl_from_tty(ErlDrvData, ErlDrvEvent);
-static Sint16 get_sint16(char *s);
-
-static ErlDrvPort ttysl_port;
-
-extern ErlDrvEvent console_input_event;
-extern HANDLE console_thread;
-
-static HANDLE ttysl_in = INVALID_HANDLE_VALUE; /* Handle for console input. */
-static HANDLE ttysl_out = INVALID_HANDLE_VALUE; /* Handle for console output */
-
-/* Functions that work on the line buffer. */
-static int start_lbuf();
-static int stop_lbuf();
-static int put_chars();
-static int move_rel();
-static int ins_chars();
-static int del_chars();
-static int step_over_chars(int n);
-static int insert_buf();
-static int write_buf();
-static void move_cursor(int, int);
-
-/* Define the driver table entry. */
-struct erl_drv_entry ttsl_driver_entry = {
- ttysl_init,
- ttysl_start,
- ttysl_stop,
- ttysl_from_erlang,
- ttysl_from_tty,
- NULL,
- "tty_sl",
- NULL,
- NULL,
- ttysl_control,
- NULL, /* timeout */
- NULL, /* outputv */
- NULL, /* ready_async */
- NULL, /* flush */
- NULL, /* call */
- NULL, /* event */
- ERL_DRV_EXTENDED_MARKER,
- ERL_DRV_EXTENDED_MAJOR_VERSION,
- ERL_DRV_EXTENDED_MINOR_VERSION,
- 0,
- NULL,
- NULL,
- NULL,
-};
-
-static int utf8_mode = 0;
-
-static int ttysl_init()
-{
- lbuf = NULL; /* For line buffer handling */
- ttysl_port = (ErlDrvPort)-1;
- return 0;
-}
-
-static ErlDrvData ttysl_start(ErlDrvPort port, char* buf)
-{
- if ((SWord)ttysl_port != -1 || console_thread == NULL) {
- return ERL_DRV_ERROR_GENERAL;
- }
- start_lbuf();
- utf8_mode = 1;
- driver_select(port, console_input_event, ERL_DRV_READ, 1);
- ttysl_port = port;
- return (ErlDrvData)ttysl_port;/* Nothing important to return */
-}
-
-#define DEF_HEIGHT 24
-#define DEF_WIDTH 80
-
-static void ttysl_get_window_size(Uint32 *width, Uint32 *height)
-{
- *width = ConGetColumns();
- *height = ConGetRows();
-}
-
-
-static ErlDrvSSizeT ttysl_control(ErlDrvData drv_data,
- unsigned int command,
- char *buf, ErlDrvSizeT len,
- char **rbuf, ErlDrvSizeT rlen)
-{
- char resbuff[2*sizeof(Uint32)];
- ErlDrvSizeT res_size;
-
- command -= ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER;
- switch (command) {
- case CTRL_OP_GET_WINSIZE:
- {
- Uint32 w,h;
- ttysl_get_window_size(&w,&h);
- memcpy(resbuff,&w,sizeof(Uint32));
- memcpy(resbuff+sizeof(Uint32),&h,sizeof(Uint32));
- res_size = 2*sizeof(Uint32);
- }
- break;
- case CTRL_OP_GET_UNICODE_STATE:
- *resbuff = (utf8_mode) ? 1 : 0;
- res_size = 1;
- break;
- case CTRL_OP_SET_UNICODE_STATE:
- if (len != 0) {
- int m = (int) *buf;
- *resbuff = (utf8_mode) ? 1 : 0;
- res_size = 1;
- utf8_mode = (m) ? 1 : 0;
- } else {
- return 0;
- }
- break;
- default:
- return -1;
- }
- if (rlen < res_size) {
- *rbuf = driver_alloc(res_size);
- }
- memcpy(*rbuf,resbuff,res_size);
- return res_size;
-}
-
-
-static void ttysl_stop(ErlDrvData ttysl_data)
-{
- if ((SWord)ttysl_port != -1) {
- driver_select(ttysl_port, console_input_event, ERL_DRV_READ, 0);
- }
-
- ttysl_in = ttysl_out = INVALID_HANDLE_VALUE;
- stop_lbuf();
- ttysl_port = (ErlDrvPort)-1;
-}
-
-static int put_utf8(int ch, byte *target, int sz, int *pos)
-{
- Uint x = (Uint) ch;
- if (x < 0x80) {
- if (*pos >= sz) {
- return -1;
- }
- target[(*pos)++] = (byte) x;
- }
- else if (x < 0x800) {
- if (((*pos) + 1) >= sz) {
- return -1;
- }
- target[(*pos)++] = (((byte) (x >> 6)) |
- ((byte) 0xC0));
- target[(*pos)++] = (((byte) (x & 0x3F)) |
- ((byte) 0x80));
- } else if (x < 0x10000) {
- if ((x >= 0xD800 && x <= 0xDFFF) ||
- (x == 0xFFFE) ||
- (x == 0xFFFF)) { /* Invalid unicode range */
- return -1;
- }
- if (((*pos) + 2) >= sz) {
- return -1;
- }
-
- target[(*pos)++] = (((byte) (x >> 12)) |
- ((byte) 0xE0));
- target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) |
- ((byte) 0x80));
- target[(*pos)++] = (((byte) (x & 0x3F)) |
- ((byte) 0x80));
- } else if (x < 0x110000) { /* Standard imposed max */
- if (((*pos) + 3) >= sz) {
- return -1;
- }
- target[(*pos)++] = (((byte) (x >> 18)) |
- ((byte) 0xF0));
- target[(*pos)++] = ((((byte) (x >> 12)) & 0x3F) |
- ((byte) 0x80));
- target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) |
- ((byte) 0x80));
- target[(*pos)++] = (((byte) (x & 0x3F)) |
- ((byte) 0x80));
- } else {
- return -1;
- }
- return 0;
-}
-
-
-static int pick_utf8(byte *s, int sz, int *pos)
-{
- int size = sz - (*pos);
- byte *source;
- Uint unipoint;
-
- if (size > 0) {
- source = s + (*pos);
- if (((*source) & ((byte) 0x80)) == 0) {
- unipoint = (int) *source;
- ++(*pos);
- return (int) unipoint;
- } else if (((*source) & ((byte) 0xE0)) == 0xC0) {
- if (size < 2) {
- return -2;
- }
- if (((source[1] & ((byte) 0xC0)) != 0x80) ||
- ((*source) < 0xC2) /* overlong */) {
- return -1;
- }
- (*pos) += 2;
- unipoint =
- (((Uint) ((*source) & ((byte) 0x1F))) << 6) |
- ((Uint) (source[1] & ((byte) 0x3F)));
- return (int) unipoint;
- } else if (((*source) & ((byte) 0xF0)) == 0xE0) {
- if (size < 3) {
- return -2;
- }
- if (((source[1] & ((byte) 0xC0)) != 0x80) ||
- ((source[2] & ((byte) 0xC0)) != 0x80) ||
- (((*source) == 0xE0) && (source[1] < 0xA0)) /* overlong */ ) {
- return -1;
- }
- if ((((*source) & ((byte) 0xF)) == 0xD) &&
- ((source[1] & 0x20) != 0)) {
- return -1;
- }
- if (((*source) == 0xEF) && (source[1] == 0xBF) &&
- ((source[2] == 0xBE) || (source[2] == 0xBF))) {
- return -1;
- }
- (*pos) += 3;
- unipoint =
- (((Uint) ((*source) & ((byte) 0xF))) << 12) |
- (((Uint) (source[1] & ((byte) 0x3F))) << 6) |
- ((Uint) (source[2] & ((byte) 0x3F)));
- return (int) unipoint;
- } else if (((*source) & ((byte) 0xF8)) == 0xF0) {
- if (size < 4) {
- return -2 ;
- }
- if (((source[1] & ((byte) 0xC0)) != 0x80) ||
- ((source[2] & ((byte) 0xC0)) != 0x80) ||
- ((source[3] & ((byte) 0xC0)) != 0x80) ||
- (((*source) == 0xF0) && (source[1] < 0x90)) /* overlong */) {
- return -1;
- }
- if ((((*source) & ((byte)0x7)) > 0x4U) ||
- ((((*source) & ((byte)0x7)) == 0x4U) &&
- ((source[1] & ((byte)0x3F)) > 0xFU))) {
- return -1;
- }
- (*pos) += 4;
- unipoint =
- (((Uint) ((*source) & ((byte) 0x7))) << 18) |
- (((Uint) (source[1] & ((byte) 0x3F))) << 12) |
- (((Uint) (source[2] & ((byte) 0x3F))) << 6) |
- ((Uint) (source[3] & ((byte) 0x3F)));
- return (int) unipoint;
- } else {
- return -1;
- }
- } else {
- return -1;
- }
-}
-
-static int octal_or_hex_positions(Uint c)
-{
- int x = 0;
- Uint ch = c;
- if (!ch) {
- return 1;
- }
- while(ch) {
- ++x;
- ch >>= 3;
- }
- if (x <= 3) {
- return 3;
- }
- /* \x{H ...} format when larger than \777 */
- x = 0;
- ch = c;
- while(ch) {
- ++x;
- ch >>= 4;
- }
- return x+3;
-}
-
-static void octal_or_hex_format(Uint ch, byte *buf, int *pos)
-{
- static byte hex_chars[] = { '0','1','2','3','4','5','6','7','8','9',
- 'A','B','C','D','E','F'};
- int num = octal_or_hex_positions(ch);
- if (num != 3) {
- buf[(*pos)++] = 'x';
- buf[(*pos)++] = '{';
- num -= 3;
- while(num--) {
- buf[(*pos)++] = hex_chars[((ch >> (4*num)) & 0xFU)];
- }
- buf[(*pos)++] = '}';
- } else {
- while(num--) {
- buf[(*pos)++] = ((byte) ((ch >> (3*num)) & 0x7U) + '0');
- }
- }
-}
-
-/*
- * Check that there is enough room in all buffers to copy all pad chars
- * and stiff we need If not, realloc lbuf.
- */
-static int check_buf_size(byte *s, int n)
-{
- int pos = 0;
- int ch;
- int size = 10;
-
- while(pos < n) {
- /* Indata is always UTF-8 */
- if ((ch = pick_utf8(s,n,&pos)) < 0) {
- /* XXX temporary allow invalid chars */
- ch = (int) s[pos];
- DEBUGLOG(("Invalid UTF8:%d",ch));
- ++pos;
- }
- if (utf8_mode) { /* That is, terminal is UTF8 compliant */
- if (ch >= 128 || isprint(ch)) {
- DEBUGLOG(("Printable(UTF-8:%d):%d",pos,ch));
- size++; /* Buffer contains wide characters... */
- } else if (ch == '\t') {
- size += 8;
- } else {
- DEBUGLOG(("Magic(UTF-8:%d):%d",pos,ch));
- size += 2;
- }
- } else {
- if (ch <= 255 && isprint(ch)) {
- DEBUGLOG(("Printable:%d",ch));
- size++;
- } else if (ch == '\t')
- size += 8;
- else if (ch >= 128) {
- DEBUGLOG(("Non printable:%d",ch));
- size += (octal_or_hex_positions(ch) + 1);
- }
- else {
- DEBUGLOG(("Magic:%d",ch));
- size += 2;
- }
- }
- }
-
- if (size + lpos >= lbuf_size) {
-
- lbuf_size = size + lpos + BUFSIZ;
- if ((lbuf = driver_realloc(lbuf, lbuf_size * sizeof(Uint32))) == NULL) {
- driver_failure(ttysl_port, -1);
- return(0);
- }
- }
- return(1);
-}
-
-
-static void ttysl_from_erlang(ErlDrvData ttysl_data, char* buf, ErlDrvSizeT count)
-{
- if (lpos > MAXSIZE)
- put_chars((byte*)"\n", 1);
-
- switch (buf[0]) {
- case OP_PUTC:
- case OP_PUTC_SYNC:
- DEBUGLOG(("OP: Putc(%I64u)",(unsigned long long)count-1));
- if (check_buf_size((byte*)buf+1, count-1) == 0)
- return;
- put_chars((byte*)buf+1, count-1);
- break;
- case OP_MOVE:
- move_rel(get_sint16(buf+1));
- break;
- case OP_INSC:
- if (check_buf_size((byte*)buf+1, count-1) == 0)
- return;
- ins_chars((byte*)buf+1, count-1);
- break;
- case OP_DELC:
- del_chars(get_sint16(buf+1));
- break;
- case OP_BEEP:
- ConBeep();
- break;
- default:
- /* Unknown op, just ignore. */
- break;
- }
-
- if (buf[0] == OP_PUTC_SYNC) {
- /* On windows we do a blocking write to the tty so we just
- send the ack immediately. If at some point in the future
- someone has a problem with tty output being blocking
- this has to be changed. */
- ErlDrvTermData spec[] = {
- ERL_DRV_PORT, driver_mk_port(ttysl_port),
- ERL_DRV_ATOM, driver_mk_atom("ok"),
- ERL_DRV_TUPLE, 2
- };
- erl_drv_output_term(driver_mk_port(ttysl_port), spec,
- sizeof(spec) / sizeof(spec[0]));
- }
- return;
-}
-
-extern int read_inbuf(char *data, int n);
-
-static void ttysl_from_tty(ErlDrvData ttysl_data, ErlDrvEvent fd)
-{
- Uint32 inbuf[64];
- byte t[1024];
- int i,pos,tpos;
-
- i = ConReadInput(inbuf,1);
-
- pos = 0;
- tpos = 0;
-
- while (pos < i) {
- while (tpos < 1020 && pos < i) { /* Max 4 bytes for UTF8 */
- put_utf8((int) inbuf[pos++], t, 1024, &tpos);
- }
- driver_output(ttysl_port, (char *) t, tpos);
- tpos = 0;
- }
-}
-
-/*
- * Gets signed 16 bit integer from binary buffer.
- */
-static Sint16
-get_sint16(char *s)
-{
- return ((*s << 8) | ((byte*)s)[1]);
-}
-
-
-static int start_lbuf(void)
-{
- if (!lbuf && !(lbuf = ( Uint32*) driver_alloc(lbuf_size * sizeof(Uint32))))
- return FALSE;
- llen = 0;
- lpos = 0;
- return TRUE;
-}
-
-static int stop_lbuf(void)
-{
- if (lbuf) {
- driver_free(lbuf);
- lbuf = NULL;
- }
- llen = 0; /* To avoid access error in win_con:AddToCmdHistory during exit*/
- return TRUE;
-}
-
-/* Put l bytes (in UTF8) from s into the buffer and output them. */
-static int put_chars(byte *s, int l)
-{
- int n;
-
- n = insert_buf(s, l);
- if (n > 0)
- write_buf(lbuf + lpos - n, n);
- if (lpos > llen)
- llen = lpos;
- return TRUE;
-}
-
-/*
- * Move the current position forwards or backwards within the current
- * line. We know about padding.
- */
-static int move_rel(int n)
-{
- int npos; /* The new position */
-
- /* Step forwards or backwards over the buffer. */
- npos = step_over_chars(n);
-
- /* Calculate move, updates pointers and move the cursor. */
- move_cursor(lpos, npos);
- lpos = npos;
- return TRUE;
-}
-
-/* Insert characters into the buffer at the current position. */
-static int ins_chars(byte *s, int l)
-{
- int n, tl;
- Uint32 *tbuf = NULL; /* Suppress warning about use-before-set */
-
- /* Move tail of buffer to make space. */
- if ((tl = llen - lpos) > 0) {
- if ((tbuf = driver_alloc(tl * sizeof(Uint32))) == NULL)
- return FALSE;
- memcpy(tbuf, lbuf + lpos, tl * sizeof(Uint32));
- }
- n = insert_buf(s, l);
- if (tl > 0) {
- memcpy(lbuf + lpos, tbuf, tl * sizeof(Uint32));
- driver_free(tbuf);
- }
- llen += n;
- write_buf(lbuf + (lpos - n), llen - (lpos - n));
- move_cursor(llen, lpos);
- return TRUE;
-}
-
-/*
- * Delete characters in the buffer. Can delete characters before (n < 0)
- * and after (n > 0) the current position. Cursor left at beginning of
- * deleted block.
- */
-static int del_chars(int n)
-{
- int i, l, r;
- int pos;
-
- /*update_cols();*/
-
- /* Step forward or backwards over n logical characters. */
- pos = step_over_chars(n);
-
- if (pos > lpos) {
- l = pos - lpos; /* Buffer characters to delete */
- r = llen - lpos - l; /* Characters after deleted */
- /* Fix up buffer and buffer pointers. */
- if (r > 0)
- memcpy(lbuf + lpos, lbuf + pos, r * sizeof(Uint32));
- llen -= l;
- /* Write out characters after, blank the tail and jump back to lpos. */
- write_buf(lbuf + lpos, r);
- for (i = l ; i > 0; --i)
- ConPutChar(' ');
- move_cursor(llen + l, lpos);
- }
- else if (pos < lpos) {
- l = lpos - pos; /* Buffer characters */
- r = llen - lpos; /* Characters after deleted */
- move_cursor(lpos, lpos-l); /* Move back */
- /* Fix up buffer and buffer pointers. */
- if (r > 0)
- memcpy(lbuf + pos, lbuf + lpos, r * sizeof(Uint32));
- lpos -= l;
- llen -= l;
- /* Write out characters after, blank the tail and jump back to lpos. */
- write_buf(lbuf + lpos, r);
- for (i = l ; i > 0; --i)
- ConPutChar(' ');
- move_cursor(llen + l, lpos);
- }
- return TRUE;
-}
-
-
-/* Step over n logical characters, check for overflow. */
-static int step_over_chars(int n)
-{
- Uint32 *c, *beg, *end;
-
- beg = lbuf;
- end = lbuf + llen;
- c = lbuf + lpos;
- for ( ; n > 0 && c < end; --n) {
- c++;
- while (c < end && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0))
- c++;
- }
- for ( ; n < 0 && c > beg; n++) {
- --c;
- while (c > beg && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0))
- --c;
- }
- return c - lbuf;
-}
-
-static int insert_buf(byte *s, int n)
-{
- int pos = 0;
- int buffpos = lpos;
- int ch;
-
- while (pos < n) {
- if ((ch = pick_utf8(s,n,&pos)) < 0) {
- /* XXX temporary allow invalid chars */
- ch = (int) s[pos];
- DEBUGLOG(("insert_buf: Invalid UTF8:%d",ch));
- ++pos;
- }
- if ((utf8_mode && (ch >= 128 || isprint(ch))) || (ch <= 255 && isprint(ch))) {
- DEBUGLOG(("insert_buf: Printable(UTF-8):%d",ch));
- lbuf[lpos++] = (Uint32) ch;
- } else if (ch >= 128) { /* not utf8 mode */
- int nc = octal_or_hex_positions(ch);
- lbuf[lpos++] = ((Uint32) ch) | ESCAPED_TAG;
- while (nc--) {
- lbuf[lpos++] = ESCAPED_TAG;
- }
- } else if (ch == '\t') {
- do {
- lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch));
- ch = 0;
- } while (lpos % 8);
- } else if (ch == '\n' || ch == '\r') {
- write_buf(lbuf + buffpos, lpos - buffpos);
- ConPutChar('\r');
- if (ch == '\n')
- ConPutChar('\n');
- if (llen > lpos) {
- memcpy(lbuf, lbuf + lpos, llen - lpos);
- }
- llen -= lpos;
- lpos = buffpos = 0;
- } else {
- DEBUGLOG(("insert_buf: Magic(UTF-8):%d",ch));
- lbuf[lpos++] = ch | CONTROL_TAG;
- lbuf[lpos++] = CONTROL_TAG;
- }
- }
- return lpos - buffpos; /* characters "written" into
- current buffer (may be less due to newline) */
-}
-static int write_buf(Uint32 *s, int n)
-{
- int i;
-
- /*update_cols();*/
-
- while (n > 0) {
- if (!(*s & TAG_MASK) ) {
- ConPutChar(*s);
- --n;
- ++s;
- }
- else if (*s == (CONTROL_TAG | ((Uint32) '\t'))) {
- ConPutChar(' ');
- --n; s++;
- while (n > 0 && *s == CONTROL_TAG) {
- ConPutChar(' ');
- --n; s++;
- }
- } else if (*s & CONTROL_TAG) {
- ConPutChar('^');
- ConPutChar((*s == 0177) ? '?' : *s | 0x40);
- n -= 2;
- s += 2;
- } else if (*s & ESCAPED_TAG) {
- Uint32 ch = *s & ~(TAG_MASK);
- byte *octbuff;
- byte octtmp[256];
- int octbytes;
- DEBUGLOG(("Escaped: %d", ch));
- octbytes = octal_or_hex_positions(ch);
- if (octbytes > 256) {
- octbuff = driver_alloc(octbytes);
- } else {
- octbuff = octtmp;
- }
- octbytes = 0;
- octal_or_hex_format(ch, octbuff, &octbytes);
- DEBUGLOG(("octbytes: %d", octbytes));
- ConPutChar('\\');
- for (i = 0; i < octbytes; ++i) {
- ConPutChar(octbuff[i]);
- }
- n -= octbytes+1;
- s += octbytes+1;
- if (octbuff != octtmp) {
- driver_free(octbuff);
- }
- } else {
- DEBUGLOG(("Very unexpected character %d",(int) *s));
- ++n;
- --s;
- }
- }
- return TRUE;
-}
-
-
-static void
-move_cursor(int from, int to)
-{
- ConSetCursor(from,to);
-}
diff --git a/erts/emulator/drivers/win32/win_con.c b/erts/emulator/drivers/win32/win_con.c
deleted file mode 100644
index 2e3c12dc58..0000000000
--- a/erts/emulator/drivers/win32/win_con.c
+++ /dev/null
@@ -1,2355 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2021. All Rights Reserved.
- *
- * 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.
- *
- * %CopyrightEnd%
- */
-
-#define UNICODE 1
-#define _UNICODE 1
-#include <tchar.h>
-#include <stdio.h>
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-#include "sys.h"
-#include <windowsx.h>
-#include "resource.h"
-#include "erl_version.h"
-#include <commdlg.h>
-#include <commctrl.h>
-#include "erl_driver.h"
-#include "win_con.h"
-
-#define ALLOC(X) malloc(X)
-#define REALLOC(X,Y) realloc(X,Y)
-#define FREE(X) free(X)
-
-#if SIZEOF_VOID_P == 8
-#define WIN64 1
-#ifndef GCL_HBRBACKGROUND
-#define GCL_HBRBACKGROUND GCLP_HBRBACKGROUND
-#endif
-#define DIALOG_PROC_RET INT_PTR
-#define CF_HOOK_RET INT_PTR
-#define CC_HOOK_RET INT_PTR
-#define OFN_HOOK_RET INT_PTR
-#else
-#define DIALOG_PROC_RET BOOL
-#define CF_HOOK_RET UINT
-#define CC_HOOK_RET UINT
-#define OFN_HOOK_RET UINT
-#endif
-
-
-#ifndef STATE_SYSTEM_INVISIBLE
-/* Mingw problem with oleacc.h and WIN32_LEAN_AND_MEAN */
-#define STATE_SYSTEM_INVISIBLE 0x00008000
-#endif
-
-#define WM_CONTEXT (0x0401)
-#define WM_CONBEEP (0x0402)
-#define WM_SAVE_PREFS (0x0403)
-
-#define USER_KEY TEXT("Software\\Ericsson\\Erlang\\") TEXT(ERLANG_VERSION)
-
-#define FRAME_HEIGHT ((2*GetSystemMetrics(SM_CYEDGE))+(2*GetSystemMetrics(SM_CYFRAME))+GetSystemMetrics(SM_CYCAPTION))
-#define FRAME_WIDTH (2*GetSystemMetrics(SM_CXFRAME)+(2*GetSystemMetrics(SM_CXFRAME))+GetSystemMetrics(SM_CXVSCROLL))
-
-#define LINE_LENGTH canvasColumns
-#define COL(_l) ((_l) % LINE_LENGTH)
-#define LINE(_l) ((_l) / LINE_LENGTH)
-
-#ifdef UNICODE
-/*
- * We use a character in the invalid unicode range
- */
-#define SET_CURSOR (0xD8FF)
-#else
-/*
- * XXX There is no escape to send a character 0x80. Fortunately,
- * the ttsl driver currently replaces 0x80 with an octal sequence.
- */
-#define SET_CURSOR (0x80)
-#endif
-
-#define SCAN_CODE_BREAK 0x46 /* scan code for Ctrl-Break */
-
-
-typedef struct ScreenLine_s {
- struct ScreenLine_s* next;
- struct ScreenLine_s* prev;
- int width;
-#ifdef HARDDEBUG
- int allocated;
-#endif
- int newline; /* Ends with hard newline: 1, wrapped at end: 0 */
- TCHAR *text;
-} ScreenLine_t;
-
-extern Uint32 *lbuf; /* The current line buffer */
-extern int llen; /* The current line length */
-extern int lpos;
-
-HANDLE console_input_event;
-HANDLE console_thread = NULL;
-
-#define DEF_CANVAS_COLUMNS 80
-#define DEF_CANVAS_ROWS 26
-
-#define BUFSIZE 4096
-#define MAXBUFSIZE 32768
-typedef struct {
- TCHAR *data;
- int size;
- int wrPos;
- int rdPos;
-} buffer_t;
-
-static buffer_t inbuf;
-static buffer_t outbuf;
-
-static CHOOSEFONT cf;
-
-static TCHAR szFrameClass[] = TEXT("FrameClass");
-static TCHAR szClientClass[] = TEXT("ClientClass");
-static HWND hFrameWnd;
-static HWND hClientWnd;
-static HWND hTBWnd;
-static HWND hComboWnd;
-static HANDLE console_input;
-static HANDLE console_output;
-static int cxChar,cyChar, cxCharMax;
-static int cxClient,cyClient;
-static int cyToolBar;
-static int iVscrollPos,iHscrollPos;
-static int iVscrollMax,iHscrollMax;
-static int nBufLines;
-static int cur_x;
-static int cur_y;
-static int canvasColumns = DEF_CANVAS_COLUMNS;
-static int canvasRows = DEF_CANVAS_ROWS;
-static ScreenLine_t *buffer_top,*buffer_bottom;
-static ScreenLine_t* cur_line;
-static POINT editBeg,editEnd;
-static BOOL fSelecting = FALSE;
-static BOOL fTextSelected = FALSE;
-static HKEY key;
-static BOOL has_key = FALSE;
-static LOGFONT logfont;
-static DWORD fgColor;
-static DWORD bkgColor;
-static FILE *logfile = NULL;
-static RECT winPos;
-static BOOL toolbarVisible;
-static BOOL destroyed = FALSE;
-
-static int lines_to_save = 10000; /* Maximum number of screen lines to save. */
-
-#define TITLE_BUF_SZ 256
-
-struct title_buf {
- TCHAR *name;
- TCHAR buf[TITLE_BUF_SZ];
-};
-
-static TCHAR *erlang_window_title = TEXT("Erlang");
-
-static unsigned __stdcall ConThreadInit(LPVOID param);
-static LRESULT CALLBACK ClientWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
-static LRESULT CALLBACK FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
-static DIALOG_PROC_RET CALLBACK AboutDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam);
-static ScreenLine_t *ConNewLine(void);
-static void DeleteTopLine(void);
-static void ensure_line_below(void);
-static ScreenLine_t *GetLineFromY(int y);
-static void LoadUserPreferences(void);
-static void SaveUserPreferences(void);
-static void set_scroll_info(HWND hwnd);
-static void ConCarriageFeed(int);
-static void ConScrollScreen(void);
-static BOOL ConChooseFont(HWND hwnd);
-static void ConFontInitialize(HWND hwnd);
-static void ConSetFont(HWND hwnd);
-static void ConChooseColor(HWND hwnd);
-static void DrawSelection(HWND hwnd, POINT pt1, POINT pt2);
-static void InvertSelectionArea(HWND hwnd);
-static void OnEditCopy(HWND hwnd);
-static void OnEditPaste(HWND hwnd);
-static void OnEditSelAll(HWND hwnd);
-static void GetFileName(HWND hwnd, TCHAR *pFile);
-static void OpenLogFile(HWND hwnd);
-static void CloseLogFile(HWND hwnd);
-static void LogFileWrite(TCHAR *buf, int n);
-static int write_inbuf(TCHAR *data, int n);
-static void init_buffers(void);
-static void AddToCmdHistory(void);
-static int write_outbuf(TCHAR *data, int num_chars);
-static void ConDrawText(HWND hwnd);
-static BOOL (WINAPI *ctrl_handler)(DWORD);
-static HWND InitToolBar(HWND hwndParent);
-static void window_title(struct title_buf *);
-static void free_window_title(struct title_buf *);
-static void Client_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags);
-
-#ifdef HARDDEBUG
-/* For really hard GUI startup debugging, place DEBUGBOX() macros in code
- and get modal message boxes with the line number. */
-static void debug_box(int line) {
- TCHAR buff[1024];
- swprintf(buff,1024,TEXT("DBG:%d"),line);
- MessageBox(NULL,buff,TEXT("DBG"),MB_OK|MB_APPLMODAL);
-}
-
-#define DEBUGBOX() debug_box(__LINE__)
-#endif
-
-#define CON_VPRINTF_BUF_INC_SIZE 1024
-
-static erts_dsprintf_buf_t *
-grow_con_vprintf_buf(erts_dsprintf_buf_t *dsbufp, size_t need)
-{
- char *buf;
- size_t size;
-
- ASSERT(dsbufp);
-
- if (!dsbufp->str) {
- size = (((need + CON_VPRINTF_BUF_INC_SIZE - 1)
- / CON_VPRINTF_BUF_INC_SIZE)
- * CON_VPRINTF_BUF_INC_SIZE);
- buf = (char *) ALLOC(size * sizeof(char));
- }
- else {
- size_t free_size = dsbufp->size - dsbufp->str_len;
-
- if (need <= free_size)
- return dsbufp;
-
- size = need - free_size + CON_VPRINTF_BUF_INC_SIZE;
- size = (((size + CON_VPRINTF_BUF_INC_SIZE - 1)
- / CON_VPRINTF_BUF_INC_SIZE)
- * CON_VPRINTF_BUF_INC_SIZE);
- size += dsbufp->size;
- buf = (char *) REALLOC((void *) dsbufp->str,
- size * sizeof(char));
- }
- if (!buf)
- return NULL;
- if (buf != dsbufp->str)
- dsbufp->str = buf;
- dsbufp->size = size;
- return dsbufp;
-}
-
-static int con_vprintf(char *format, va_list arg_list)
-{
- int res,i;
- erts_dsprintf_buf_t dsbuf = ERTS_DSPRINTF_BUF_INITER(grow_con_vprintf_buf);
- res = erts_vdsprintf(&dsbuf, format, arg_list);
- if (res >= 0) {
- TCHAR *tmp = ALLOC(dsbuf.str_len*sizeof(TCHAR));
- for (i=0;i<dsbuf.str_len;++i) {
- tmp[i] = dsbuf.str[i];
- }
- write_outbuf(tmp, dsbuf.str_len);
- FREE(tmp);
- }
- if (dsbuf.str)
- FREE((void *) dsbuf.str);
- return res;
-}
-
-void
-ConInit(void)
-{
- unsigned tid;
-
- console_input = CreateSemaphore(NULL, 0, 1, NULL);
- console_output = CreateSemaphore(NULL, 0, 1, NULL);
- console_input_event = CreateManualEvent(FALSE);
- console_thread = (HANDLE *) _beginthreadex(NULL, 0,
- ConThreadInit,
- 0, 0, &tid);
-
- /* Make all erts_*printf on stdout and stderr use con_vprintf */
- erts_printf_stdout_func = con_vprintf;
- erts_printf_stderr_func = con_vprintf;
-}
-
-/*
- ConNormalExit() is called from erts_exit() when the emulator
- is stopping. If the exit has not been initiated by this
- console thread (WM_DESTROY or ID_BREAK), the function must
- invoke the console thread to save the user preferences.
-*/
-void
-ConNormalExit(void)
-{
- if (!destroyed)
- SendMessage(hFrameWnd, WM_SAVE_PREFS, 0L, 0L);
-}
-
-void
-ConWaitForExit(void)
-{
- ConPrintf("\n\nAbnormal termination\n");
- WaitForSingleObject(console_thread, INFINITE);
-}
-
-void ConSetCtrlHandler(BOOL (WINAPI *handler)(DWORD))
-{
- ctrl_handler = handler;
-}
-
-int ConPutChar(Uint32 c)
-{
- TCHAR sbuf[1];
-#ifdef HARDDEBUG
- fprintf(stderr,"ConPutChar: %d\n",(int) c);
- fflush(stderr);
-#endif
- sbuf[0] = c;
- write_outbuf(sbuf, 1);
- return 1;
-}
-
-static int GetXFromLine(HDC hdc, int hscroll, int xpos,ScreenLine_t *pLine)
-{
- SIZE size;
- int hscrollPix = hscroll * cxChar;
-
- if (pLine == NULL) {
- return 0;
- }
-
- if (pLine->width < xpos) {
- return (canvasColumns-hscroll)*cxChar;
- }
- /* Not needed (?): SelectObject(hdc,CreateFontIndirect(&logfont)); */
- if (GetTextExtentPoint32(hdc,pLine->text,xpos,&size)) {
-#ifdef HARDDEBUG
- fprintf(stderr,"size.cx:%d\n",(int)size.cx);
- fflush(stderr);
-#endif
- if (hscrollPix >= size.cx) {
- return 0;
- }
- return ((int) size.cx) - hscrollPix;
- } else {
- return (xpos-hscroll)*cxChar;
- }
-}
-
-static int GetXFromCurrentY(HDC hdc, int hscroll, int xpos) {
- return GetXFromLine(hdc, hscroll, xpos, GetLineFromY(cur_y));
-}
-
-void ConSetCursor(int from, int to)
-{ TCHAR cmd[9];
- int *p;
- //DebugBreak();
- cmd[0] = SET_CURSOR;
- /*
- * XXX Expect trouble on CPUs which don't allow misaligned read and writes.
- */
- p = (int *)&cmd[1];
- *p++ = from;
- *p = to;
- write_outbuf(cmd, 1 + (2*sizeof(int)/sizeof(TCHAR)));
-}
-
-void ConPrintf(char *format, ...)
-{
- va_list va;
-
- va_start(va, format);
- (void) con_vprintf(format, va);
- va_end(va);
-}
-
-void ConBeep(void)
-{
- SendMessage(hClientWnd, WM_CONBEEP, 0L, 0L);
-}
-
-int ConReadInput(Uint32 *data, int num_chars)
-{
- TCHAR *buf;
- int nread;
- WaitForSingleObject(console_input,INFINITE);
- nread = num_chars = min(num_chars,inbuf.wrPos-inbuf.rdPos);
- buf = &inbuf.data[inbuf.rdPos];
- inbuf.rdPos += nread;
- while (nread--)
- *data++ = *buf++;
- if (inbuf.rdPos >= inbuf.wrPos) {
- inbuf.rdPos = 0;
- inbuf.wrPos = 0;
- ResetEvent(console_input_event);
- }
- ReleaseSemaphore(console_input,1,NULL);
- return num_chars;
-}
-
-int ConGetKey(void)
-{
- Uint32 c;
- WaitForSingleObject(console_input,INFINITE);
- ResetEvent(console_input_event);
- inbuf.rdPos = inbuf.wrPos = 0;
- ReleaseSemaphore(console_input,1,NULL);
- WaitForSingleObject(console_input_event,INFINITE);
- ConReadInput(&c, 1);
- return (int) c;
-}
-
-int ConGetColumns(void)
-{
- return (int) canvasColumns; /* 32bit atomic on windows */
-}
-
-int ConGetRows(void) {
- return (int) canvasRows;
-}
-
-
-static HINSTANCE hInstance;
-extern HMODULE beam_module;
-
-static unsigned __stdcall
-ConThreadInit(LPVOID param)
-{
- MSG msg;
- WNDCLASSEX wndclass;
- int iCmdShow;
- STARTUPINFO StartupInfo;
- HACCEL hAccel;
- int x, y, w, h;
- struct title_buf title;
-
- /*DebugBreak();*/
-#ifdef HARDDEBUG
- if(AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) {
- freopen("CONOUT$", "w", stdout);
- freopen("CONOUT$", "w", stderr);
- }
-#endif
-
- hInstance = GetModuleHandle(NULL);
- StartupInfo.dwFlags = 0;
- GetStartupInfo(&StartupInfo);
- iCmdShow = StartupInfo.dwFlags & STARTF_USESHOWWINDOW ?
- StartupInfo.wShowWindow : SW_SHOWDEFAULT;
-
- LoadUserPreferences();
-
- /* frame window class */
- wndclass.cbSize = sizeof (wndclass);
- wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNCLIENT;
- wndclass.lpfnWndProc = FrameWndProc;
- wndclass.cbClsExtra = 0;
- wndclass.cbWndExtra = 0;
- wndclass.hInstance = hInstance;
- wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(1));
- wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
- wndclass.hbrBackground = NULL;
- wndclass.lpszMenuName = NULL;
- wndclass.lpszClassName = szFrameClass;
- wndclass.hIconSm = LoadIcon (hInstance, MAKEINTRESOURCE(1));
- RegisterClassExW (&wndclass);
-
- /* client window class */
- wndclass.cbSize = sizeof (wndclass);
- wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
- wndclass.lpfnWndProc = ClientWndProc;
- wndclass.cbClsExtra = 0;
- wndclass.cbWndExtra = 0;
- wndclass.hInstance = hInstance;
- wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(1));
- wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
- wndclass.hbrBackground = CreateSolidBrush(bkgColor);
- wndclass.lpszMenuName = NULL;
- wndclass.lpszClassName = szClientClass;
- wndclass.hIconSm = LoadIcon (hInstance, MAKEINTRESOURCE(1));
- RegisterClassExW (&wndclass);
-
- InitCommonControls();
- init_buffers();
-
- nBufLines = 0;
- buffer_top = cur_line = ConNewLine();
- cur_line->next = buffer_bottom = ConNewLine();
- buffer_bottom->prev = cur_line;
-
- /* Create Frame Window */
- window_title(&title);
- hFrameWnd = CreateWindowEx(0, szFrameClass, title.name,
- WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,
- CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
- NULL,LoadMenu(beam_module,MAKEINTRESOURCE(1)),
- hInstance,NULL);
- free_window_title(&title);
-
- /* XXX OTP-5522:
- The window position is not saved correctly and if the window
- is closed when minimized, it's not possible to start werl again
- with the window open. Temporary fix so far is to ignore saved values
- and always start with initial settings. */
- /* Original: if (winPos.left == -1) { */
- /* Temporary: if (1) { */
- if (1) {
-
- /* initial window position */
- x = 0;
- y = 0;
- w = cxChar*LINE_LENGTH+FRAME_WIDTH+GetSystemMetrics(SM_CXVSCROLL);
- h = cyChar*30+FRAME_HEIGHT;
- } else {
- /* saved window position */
- x = winPos.left;
- y = winPos.top;
- w = winPos.right - x;
- h = winPos.bottom - y;
- }
- SetWindowPos(hFrameWnd, NULL, x, y, w, h, SWP_NOZORDER);
-
- ShowWindow(hFrameWnd, iCmdShow);
- UpdateWindow(hFrameWnd);
-
- hAccel = LoadAccelerators(beam_module,MAKEINTRESOURCE(1));
-
- ReleaseSemaphore(console_input, 1, NULL);
- ReleaseSemaphore(console_output, 1, NULL);
-
-
- /* Main message loop */
- while (GetMessage (&msg, NULL, 0, 0))
- {
- if (!TranslateAccelerator(hFrameWnd,hAccel,&msg))
- {
- TranslateMessage (&msg);
- DispatchMessage (&msg);
- }
- }
- /*
- PostQuitMessage() results in WM_QUIT which makes GetMessage()
- return 0 (which stops the main loop). Before we return from
- the console thread, the ctrl_handler is called to do erts_exit.
- */
- (*ctrl_handler)(CTRL_CLOSE_EVENT);
- return msg.wParam;
-}
-
-static LRESULT CALLBACK
-FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
-{
- RECT r;
- int cy,i,bufsize;
- TCHAR c;
- unsigned long l;
- TCHAR buf[128];
- struct title_buf title;
-
- switch (iMsg) {
- case WM_CREATE:
- /* client window creation */
- window_title(&title);
- hClientWnd = CreateWindowEx(0, szClientClass, title.name,
- WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL,
- CW_USEDEFAULT, CW_USEDEFAULT,
- CW_USEDEFAULT, CW_USEDEFAULT,
- hwnd, (HMENU)0, hInstance, NULL);
- free_window_title(&title);
- hTBWnd = InitToolBar(hwnd);
- UpdateWindow (hClientWnd);
- return 0;
- case WM_SIZE :
- if (IsWindowVisible(hTBWnd)) {
- SendMessage(hTBWnd,TB_AUTOSIZE,0,0L);
- GetWindowRect(hTBWnd,&r);
- cy = r.bottom-r.top;
- } else cy = 0;
- MoveWindow(hClientWnd,0,cy,LOWORD(lParam),HIWORD(lParam)-cy,TRUE);
- return 0;
- case WM_ERASEBKGND:
- return 1;
- case WM_SETFOCUS :
- CreateCaret(hClientWnd, NULL, cxChar, cyChar);
- SetCaretPos(GetXFromCurrentY(GetDC(hClientWnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar);
- ShowCaret(hClientWnd);
- return 0;
- case WM_KILLFOCUS:
- HideCaret(hClientWnd);
- DestroyCaret();
- return 0;
- case WM_INITMENUPOPUP :
- if (lParam == 0) /* File popup menu */
- {
- EnableMenuItem((HMENU)wParam, IDMENU_STARTLOG,
- logfile ? MF_GRAYED : MF_ENABLED);
- EnableMenuItem((HMENU)wParam, IDMENU_STOPLOG,
- logfile ? MF_ENABLED : MF_GRAYED);
- return 0;
- }
- else if (lParam == 1) /* Edit popup menu */
- {
- EnableMenuItem((HMENU)wParam, IDMENU_COPY,
- fTextSelected ? MF_ENABLED : MF_GRAYED);
- EnableMenuItem((HMENU)wParam, IDMENU_PASTE,
- IsClipboardFormatAvailable(CF_TEXT) ? MF_ENABLED : MF_GRAYED);
- return 0;
- }
- else if (lParam == 3) /* View popup menu */
- {
- CheckMenuItem((HMENU)wParam,IDMENU_TOOLBAR,
- IsWindowVisible(hTBWnd) ? MF_CHECKED : MF_UNCHECKED);
- return 0;
- }
- break;
- case WM_NOTIFY:
- switch (((LPNMHDR) lParam)->code) {
- case TTN_NEEDTEXT:
- {
- LPTOOLTIPTEXT lpttt;
- lpttt = (LPTOOLTIPTEXT) lParam;
- lpttt->hinst = hInstance;
- /* check for combobox handle */
- if (lpttt->uFlags&TTF_IDISHWND) {
- if ((lpttt->hdr.idFrom == (UINT_PTR) hComboWnd)) {
- lstrcpy(lpttt->lpszText,TEXT("Command History"));
- break;
- }
- }
- /* check for toolbar buttons */
- switch (lpttt->hdr.idFrom) {
- case IDMENU_COPY:
- lstrcpy(lpttt->lpszText,TEXT("Copy (Ctrl+C)"));
- break;
- case IDMENU_PASTE:
- lstrcpy(lpttt->lpszText,TEXT("Paste (Ctrl+V)"));
- break;
- case IDMENU_FONT:
- lstrcpy(lpttt->lpszText,TEXT("Fonts"));
- break;
- case IDMENU_ABOUT:
- lstrcpy(lpttt->lpszText,TEXT("Help"));
- break;
- }
- }
- }
- break;
- case WM_COMMAND:
- switch(LOWORD(wParam))
- {
- case IDMENU_STARTLOG:
- OpenLogFile(hwnd);
- return 0;
- case IDMENU_STOPLOG:
- CloseLogFile(hwnd);
- return 0;
- case IDMENU_EXIT:
- SendMessage(hwnd, WM_CLOSE, 0, 0L);
- return 0;
- case IDMENU_COPY:
- if (fTextSelected)
- OnEditCopy(hClientWnd);
- return 0;
- case IDMENU_PASTE:
- OnEditPaste(hClientWnd);
- return 0;
- case IDMENU_SELALL:
- OnEditSelAll(hClientWnd);
- return 0;
- case IDMENU_FONT:
- if (ConChooseFont(hClientWnd)) {
- ConSetFont(hClientWnd);
- }
- SaveUserPreferences();
- return 0;
- case IDMENU_SELECTBKG:
- ConChooseColor(hClientWnd);
- SaveUserPreferences();
- return 0;
- case IDMENU_TOOLBAR:
- if (toolbarVisible) {
- ShowWindow(hTBWnd,SW_HIDE);
- toolbarVisible = FALSE;
- } else {
- ShowWindow(hTBWnd,SW_SHOW);
- toolbarVisible = TRUE;
- }
- GetClientRect(hwnd,&r);
- PostMessage(hwnd,WM_SIZE,0,MAKELPARAM(r.right,r.bottom));
- return 0;
- case IDMENU_ABOUT:
- DialogBox(beam_module,TEXT("AboutBox"),hwnd,AboutDlgProc);
- return 0;
- case ID_COMBOBOX:
- switch (HIWORD(wParam)) {
- case CBN_SELENDOK:
- i = SendMessage(hComboWnd,CB_GETCURSEL,0,0);
- if (i != CB_ERR) {
- buf[0] = 0x01; /* CTRL+A */
- buf[1] = 0x0B; /* CTRL+K */
- bufsize = SendMessage(hComboWnd,CB_GETLBTEXT,i,(LPARAM)&buf[2]);
- if (bufsize != CB_ERR)
- write_inbuf(buf,bufsize+2);
- SetFocus(hwnd);
- }
- break;
- case CBN_SELENDCANCEL:
- break;
- }
- break;
- case ID_BREAK: /* CTRL+BRK */
- /* pass on break char if the ctrl_handler is disabled */
- if ((*ctrl_handler)(CTRL_C_EVENT) == FALSE) {
- c = 0x03;
- write_inbuf(&c,1);
- }
- return 0;
- }
- break;
- case WM_KEYDOWN :
- switch (wParam) {
- case VK_UP: c = 'P'-'@'; break;
- case VK_DOWN : c = 'N'-'@'; break;
- case VK_RIGHT : c = 'F'-'@'; break;
- case VK_LEFT : c = 'B'-'@'; break;
- case VK_DELETE : c = 'D' -'@'; break;
- case VK_HOME : c = 'A'-'@'; break;
- case VK_END : c = 'E'-'@'; break;
- case VK_RETURN : AddToCmdHistory(); return 0;
- case VK_PRIOR : /* PageUp */
- PostMessage(hClientWnd, WM_VSCROLL, SB_PAGEUP, 0);
- return 0;
- case VK_NEXT : /* PageDown */
- PostMessage(hClientWnd, WM_VSCROLL, SB_PAGEDOWN, 0);
- return 0;
- default: return 0;
- }
- write_inbuf(&c, 1);
- return 0;
- case WM_MOUSEWHEEL:
- {
- int delta = GET_WHEEL_DELTA_WPARAM(wParam);
- if (delta < 0) {
- PostMessage(hClientWnd, WM_VSCROLL, MAKELONG(SB_THUMBTRACK,
- (iVscrollPos + 5)),0);
- } else {
- WORD pos = ((iVscrollPos - 5) < 0) ? 0 : (iVscrollPos - 5);
- PostMessage(hClientWnd, WM_VSCROLL, MAKELONG(SB_THUMBTRACK,pos),0);
- }
- return 0;
- }
- case WM_CHAR:
- c = (TCHAR)wParam;
- write_inbuf(&c,1);
- return 0;
- case WM_CLOSE :
- break;
- case WM_DESTROY :
- SaveUserPreferences();
- destroyed = TRUE;
- PostQuitMessage(0);
- return 0;
- case WM_SAVE_PREFS :
- SaveUserPreferences();
- return 0;
- }
- return DefWindowProc(hwnd, iMsg, wParam, lParam);
-}
-
-static BOOL
-Client_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
-{
- ConFontInitialize(hwnd);
- cur_x = cur_y = 0;
- iVscrollPos = 0;
- iHscrollPos = 0;
- return TRUE;
-}
-
-static void
-Client_OnPaint(HWND hwnd)
-{
- ScreenLine_t *pLine;
- int x,y,i,iTop,iBot;
- PAINTSTRUCT ps;
- RECT rcInvalid;
- HDC hdc;
-
- hdc = BeginPaint(hwnd, &ps);
- rcInvalid = ps.rcPaint;
- hdc = ps.hdc;
- iTop = max(0, iVscrollPos + rcInvalid.top/cyChar);
- iBot = min(nBufLines, iVscrollPos + rcInvalid.bottom/cyChar+1);
- pLine = GetLineFromY(iTop);
- for (i = iTop; i < iBot && pLine != NULL; i++) {
- y = cyChar*(i-iVscrollPos);
- x = -cxChar*iHscrollPos;
- TextOut(hdc, x, y, &pLine->text[0], pLine->width);
- pLine = pLine->next;
- }
- if (fTextSelected || fSelecting) {
- InvertSelectionArea(hwnd);
- }
- SetCaretPos(GetXFromCurrentY(hdc,iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar);
- EndPaint(hwnd, &ps);
-}
-#ifdef HARDDEBUG
-static void dump_linebufs(void) {
- char *buff;
- ScreenLine_t *s = buffer_top;
- fprintf(stderr,"LinebufDump------------------------\n");
- while(s) {
- if (s == buffer_top) fprintf(stderr,"BT-> ");
- if (s == buffer_bottom) fprintf(stderr,"BB-> ");
- if (s == cur_line) fprintf(stderr,"CL-> ");
-
- buff = (char *) ALLOC(s->width+1);
- memcpy(buff,s->text,s->width);
- buff[s->width] = '\0';
- fprintf(stderr,"{\"%s\",%d,%d}\n",buff,s->newline,s->allocated);
- FREE(buff);
- s = s->next;
- }
- fprintf(stderr,"LinebufDumpEnd---------------------\n");
- fflush(stderr);
-}
-#endif
-
-static void reorganize_linebufs(HWND hwnd) {
- ScreenLine_t *otop = buffer_top;
- ScreenLine_t *obot = buffer_bottom;
- ScreenLine_t *next;
- int i,cpos;
-
- cpos = 0;
- i = nBufLines - cur_y;
- while (i > 1) {
- cpos += obot->width;
- obot = obot->prev;
- i--;
- }
- cpos += (obot->width - cur_x);
-#ifdef HARDDEBUG
- fprintf(stderr,"nBufLines = %d, cur_x = %d, cur_y = %d, cpos = %d\n",
- nBufLines,cur_x,cur_y,cpos);
- fflush(stderr);
-#endif
-
-
- nBufLines = 0;
- buffer_top = cur_line = ConNewLine();
- cur_line->next = buffer_bottom = ConNewLine();
- buffer_bottom->prev = cur_line;
-
- cur_x = cur_y = 0;
- iVscrollPos = 0;
- iHscrollPos = 0;
-
- while(otop) {
- for(i=0;i<otop->width;++i) {
- cur_line->text[cur_x] = otop->text[i];
- cur_x++;
- if (cur_x > cur_line->width)
- cur_line->width = cur_x;
- if (GetXFromCurrentY(GetDC(hwnd),0,cur_x) + cxChar >
- (LINE_LENGTH * cxChar)) {
- ConCarriageFeed(0);
- }
- }
- if (otop->newline) {
- ConCarriageFeed(1);
- /*ConScrollScreen();*/
- }
- next = otop->next;
- FREE(otop->text);
- FREE(otop);
- otop = next;
- }
- while (cpos) {
- cur_x--;
- if (cur_x < 0) {
- cur_y--;
- cur_line = cur_line->prev;
- cur_x = cur_line->width-1;
- }
- cpos--;
- }
- SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar);
-#ifdef HARDDEBUG
- fprintf(stderr,"canvasColumns = %d,nBufLines = %d, cur_x = %d, cur_y = %d\n",
- canvasColumns,nBufLines,cur_x,cur_y);
- fflush(stderr);
-#endif
-}
-
-
-static void
-Client_OnSize(HWND hwnd, UINT state, int cx, int cy)
-{
- RECT r;
- SCROLLBARINFO sbi;
- int w,h,columns;
- int scrollheight;
- cxClient = cx;
- cyClient = cy;
- set_scroll_info(hwnd);
- GetClientRect(hwnd,&r);
- w = r.right - r.left;
- h = r.bottom - r.top;
- sbi.cbSize = sizeof(SCROLLBARINFO);
- if (!GetScrollBarInfo(hwnd, OBJID_HSCROLL,&sbi) ||
- (sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE)) {
- scrollheight = 0;
- } else {
- scrollheight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top;
- }
- canvasRows = (h - scrollheight) / cyChar;
- if (canvasRows < DEF_CANVAS_ROWS) {
- canvasRows = DEF_CANVAS_ROWS;
- }
- columns = (w - GetSystemMetrics(SM_CXVSCROLL)) /cxChar;
- if (columns < DEF_CANVAS_COLUMNS)
- columns = DEF_CANVAS_COLUMNS;
- if (columns != canvasColumns) {
- canvasColumns = columns;
- /*dump_linebufs();*/
- reorganize_linebufs(hwnd);
- fSelecting = fTextSelected = FALSE;
- InvalidateRect(hwnd, NULL, TRUE);
-#ifdef HARDDEBUG
- fprintf(stderr,"Paint: cols = %d, rows = %d\n",canvasColumns,canvasRows);
- fflush(stderr);
-#endif
- }
-
- SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar);
-}
-
-static void calc_charpoint_from_point(HDC dc, int x, int y, int y_offset, POINT *pt)
-{
- int r;
- int hscrollPix = iHscrollPos * cxChar;
-
- pt->y = y/cyChar + iVscrollPos + y_offset;
-
- if (x > (LINE_LENGTH-iHscrollPos) * cxChar) {
- x = (LINE_LENGTH-iHscrollPos) * cxChar;
- }
- if (pt->y - y_offset > 0 && GetLineFromY(pt->y - y_offset) == NULL) {
- pt->y = nBufLines - 1 + y_offset;
- pt->x = GetLineFromY(pt->y - y_offset)->width;
- } else {
- for (pt->x = 1;
- (r = GetXFromLine(dc, 0, pt->x, GetLineFromY(pt->y - y_offset))) != 0 &&
- (r - hscrollPix) < x;
- ++(pt->x))
- ;
- if ((r - hscrollPix) > x)
- --(pt->x);
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"pt->x = %d, iHscrollPos = %d\n",(int) pt->x, iHscrollPos);
- fflush(stderr);
-#endif
- if (pt->x <= 0) {
- pt->x = x/cxChar + iHscrollPos;
- }
- }
-}
-
-
-static void
-Client_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
-{
- int r;
- SetFocus(GetParent(hwnd)); /* In case combobox steals the focus */
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"OnLButtonDown fSelecting = %d, fTextSelected = %d:\n",
- fSelecting,fTextSelected);
- fflush(stderr);
-#endif
- if (fTextSelected) {
- InvertSelectionArea(hwnd);
- }
- fTextSelected = FALSE;
-
- calc_charpoint_from_point(GetDC(hwnd), x, y, 0, &editBeg);
-
- editEnd.x = editBeg.x;
- editEnd.y = editBeg.y + 1;
- fSelecting = TRUE;
- SetCapture(hwnd);
-}
-
-static void
-Client_OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
-{
- if (fTextSelected) {
- fSelecting = TRUE;
- Client_OnMouseMove(hwnd,x,y,keyFlags);
- fSelecting = FALSE;
- }
-}
-
-static void
-Client_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags)
-{
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"OnLButtonUp fSelecting = %d, fTextSelected = %d:\n",
- fSelecting,fTextSelected);
- fprintf(stderr,"(Beg.x = %d, Beg.y = %d, "
- "End.x = %d, End.y = %d)\n",editBeg.x,editBeg.y,
- editEnd.x,editEnd.y);
-#endif
- if (fSelecting &&
- !(editBeg.x == editEnd.x && editBeg.y == (editEnd.y - 1))) {
- fTextSelected = TRUE;
- }
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"OnLButtonUp fTextSelected = %d:\n",
- fTextSelected);
- fflush(stderr);
-#endif
- fSelecting = FALSE;
- ReleaseCapture();
-}
-
-#define EMPTY_RECT(R) \
-(((R).bottom - (R).top == 0) || ((R).right - (R).left == 0))
-#define ABS(X) (((X)< 0) ? -1 * (X) : X)
-#define DIFF(A,B) ABS(((int)(A)) - ((int)(B)))
-
-static int diff_sel_area(RECT old[3], RECT new[3], RECT result[6])
-{
- int absposold = old[0].left + old[0].top * canvasColumns;
- int absposnew = new[0].left + new[0].top * canvasColumns;
- int absendold = absposold, absendnew = absposnew;
- int i, x, ret = 0;
- int abspos[2],absend[2];
- for(i = 0; i < 3; ++i) {
- if (!EMPTY_RECT(old[i])) {
- absendold += (old[i].right - old[i].left) *
- (old[i].bottom - old[i].top);
- }
- if (!EMPTY_RECT(new[i])) {
- absendnew += (new[i].right - new[i].left) *
- (new[i].bottom - new[i].top);
- }
- }
- abspos[0] = min(absposold, absposnew);
- absend[0] = DIFF(absposold, absposnew) + abspos[0];
- abspos[1] = min(absendold, absendnew);
- absend[1] = DIFF(absendold, absendnew) + abspos[1];
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"abspos[0] = %d, absend[0] = %d, abspos[1] = %d, absend[1] = %d\n",abspos[0],absend[0],abspos[1],absend[1]);
- fflush(stderr);
-#endif
- i = 0;
- for (x = 0; x < 2; ++x) {
- if (abspos[x] != absend[x]) {
- int consumed = 0;
- result[i].left = abspos[x] % canvasColumns;
- result[i].top = abspos[x] / canvasColumns;
- result[i].bottom = result[i].top + 1;
- if ((absend[x] - abspos[x]) + result[i].left < canvasColumns) {
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"Nowrap, %d < canvasColumns\n",
- (absend[x] - abspos[x]) + result[i].left);
- fflush(stderr);
-#endif
- result[i].right = (absend[x] - abspos[x]) + result[i].left;
- consumed += result[i].right - result[i].left;
- } else {
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"Wrap, %d >= canvasColumns\n",
- (absend[x] - abspos[x]) + result[i].left);
- fflush(stderr);
-#endif
- result[i].right = canvasColumns;
- consumed += result[i].right - result[i].left;
- if (absend[x] - abspos[x] - consumed >= canvasColumns) {
- ++i;
- result[i].top = result[i-1].bottom;
- result[i].left = 0;
- result[i].right = canvasColumns;
- result[i].bottom = (absend[x] - abspos[x] - consumed) / canvasColumns + result[i].top;
- consumed += (result[i].bottom - result[i].top) * canvasColumns;
- }
- if (absend[x] - abspos[x] - consumed > 0) {
- ++i;
- result[i].top = result[i-1].bottom;
- result[i].bottom = result[i].top + 1;
- result[i].left = 0;
- result[i].right = absend[x] - abspos[x] - consumed;
- }
- }
- ++i;
- }
- }
-#ifdef HARD_SEL_DEBUG
- if (i > 2) {
- int x;
- fprintf(stderr,"i = %d\n",i);
- fflush(stderr);
- for (x = 0; x < i; ++x) {
- fprintf(stderr, "result[%d]: top = %d, left = %d, "
- "bottom = %d. right = %d\n",
- x, result[x].top, result[x].left,
- result[x].bottom, result[x].right);
- }
- }
-#endif
- return i;
-}
-
-
-
-static void calc_sel_area(RECT rects[3], POINT beg, POINT end)
-{
- /* These are not really rects and points, these are character
- based positions, need to be multiplied by cxChar and cyChar to
- make up canvas coordinates */
- memset(rects,0,3*sizeof(RECT));
- rects[0].left = beg.x;
- rects[0].top = beg.y;
- rects[0].bottom = beg.y+1;
- if (end.y - beg.y == 1) { /* Only one row */
- rects[0].right = end.x;
- goto out;
- }
- rects[0].right = canvasColumns;
- if (end.y - beg.y > 2) {
- rects[1].left = 0;
- rects[1].top = rects[0].bottom;
- rects[1].right = canvasColumns;
- rects[1].bottom = end.y - 1;
- }
- rects[2].left = 0;
- rects[2].top = end.y - 1;
- rects[2].bottom = end.y;
- rects[2].right = end.x;
-
- out:
-#ifdef HARD_SEL_DEBUG
- {
- int i;
- fprintf(stderr,"beg.x = %d, beg.y = %d, end.x = %d, end.y = %d\n",
- beg.x,beg.y,end.x,end.y);
- for (i = 0; i < 3; ++i) {
- fprintf(stderr,"[%d] left = %d, top = %d, "
- "right = %d, bottom = %d\n",
- i, rects[i].left, rects[i].top,
- rects[i].right, rects[i].bottom);
- }
- fflush(stderr);
- }
-#endif
- return;
-}
-
-static void calc_sel_area_turned(RECT rects[3], POINT eBeg, POINT eEnd) {
- POINT from,to;
- if (eBeg.y >= eEnd.y ||
- (eBeg.y == eEnd.y - 1 && eBeg.x > eEnd.x)) {
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"Reverting (Beg.x = %d, Beg.y = %d, "
- "End.x = %d, End.y = %d)\n",eBeg.x,eBeg.y,
- eEnd.x,eEnd.y);
- fflush(stderr);
-#endif
- from.x = eEnd.x;
- from.y = eEnd.y - 1;
- to.x = eBeg.x;
- to.y = eBeg.y + 1;
- calc_sel_area(rects,from,to);
- } else {
- calc_sel_area(rects,eBeg,eEnd);
- }
-}
-
-
-static void InvertSelectionArea(HWND hwnd)
-{
- RECT rects[3];
- POINT from,to;
- int i;
- calc_sel_area_turned(rects,editBeg,editEnd);
- for (i = 0; i < 3; ++i) {
- if (!EMPTY_RECT(rects[i])) {
- from.x = rects[i].left;
- to.x = rects[i].right;
- from.y = rects[i].top;
- to.y = rects[i].bottom;
- DrawSelection(hwnd,from,to);
- }
- }
-}
-
-static void
-Client_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
-{
- if (fSelecting) {
- RECT rold[3], rnew[3], rupdate[6];
- int num_updates,i,r;
- POINT from,to;
- calc_sel_area_turned(rold,editBeg,editEnd);
-
- calc_charpoint_from_point(GetDC(hwnd), x, y, 1, &editEnd);
-
- calc_sel_area_turned(rnew,editBeg,editEnd);
- num_updates = diff_sel_area(rold,rnew,rupdate);
- for (i = 0; i < num_updates;++i) {
- from.x = rupdate[i].left;
- to.x = rupdate[i].right;
- from.y = rupdate[i].top;
- to.y = rupdate[i].bottom;
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"from: x=%d,y=%d, to: x=%d, y=%d\n",
- from.x, from.y,to.x,to.y);
- fflush(stderr);
-#endif
- DrawSelection(hwnd,from,to);
- }
- }
-}
-
-static void
-Client_OnVScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos)
-{
- int iVscroll;
-
- switch(code) {
- case SB_LINEDOWN:
- iVscroll = 1;
- break;
- case SB_LINEUP:
- iVscroll = -1;
- break;
- case SB_PAGEDOWN:
- iVscroll = max(1, cyClient/cyChar);
- break;
- case SB_PAGEUP:
- iVscroll = min(-1, -cyClient/cyChar);
- break;
- case SB_THUMBTRACK:
- iVscroll = pos - iVscrollPos;
- break;
- default:
- iVscroll = 0;
- }
- iVscroll = max(-iVscrollPos, min(iVscroll, iVscrollMax-iVscrollPos));
- if (iVscroll != 0) {
- iVscrollPos += iVscroll;
- ScrollWindowEx(hwnd, 0, -cyChar*iVscroll, NULL, NULL,
- NULL, NULL, SW_ERASE | SW_INVALIDATE);
- SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
- iVscroll = GetScrollPos(hwnd, SB_VERT);
- UpdateWindow(hwnd);
- }
-}
-
-static void
-Client_OnHScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos)
-{
- int iHscroll, curCharWidth = cxClient/cxChar;
-
- switch(code) {
- case SB_LINEDOWN:
- iHscroll = 1;
- break;
- case SB_LINEUP:
- iHscroll = -1;
- break;
- case SB_PAGEDOWN:
- iHscroll = max(1,curCharWidth-1);
- break;
- case SB_PAGEUP:
- iHscroll = min(-1,-(curCharWidth-1));
- break;
- case SB_THUMBTRACK:
- iHscroll = pos - iHscrollPos;
- break;
- default:
- iHscroll = 0;
- }
- iHscroll = max(-iHscrollPos, min(iHscroll, iHscrollMax-iHscrollPos-(curCharWidth-1)));
- if (iHscroll != 0) {
- iHscrollPos += iHscroll;
- ScrollWindow(hwnd, -cxChar*iHscroll, 0, NULL, NULL);
- SetScrollPos(hwnd, SB_HORZ, iHscrollPos, TRUE);
- UpdateWindow(hwnd);
- }
-}
-
-static LRESULT CALLBACK
-ClientWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
-{
- switch (iMsg) {
- HANDLE_MSG(hwnd, WM_CREATE, Client_OnCreate);
- HANDLE_MSG(hwnd, WM_SIZE, Client_OnSize);
- HANDLE_MSG(hwnd, WM_PAINT, Client_OnPaint);
- HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Client_OnLButtonDown);
- HANDLE_MSG(hwnd, WM_RBUTTONDOWN, Client_OnRButtonDown);
- HANDLE_MSG(hwnd, WM_LBUTTONUP, Client_OnLButtonUp);
- HANDLE_MSG(hwnd, WM_MOUSEMOVE, Client_OnMouseMove);
- HANDLE_MSG(hwnd, WM_VSCROLL, Client_OnVScroll);
- HANDLE_MSG(hwnd, WM_HSCROLL, Client_OnHScroll);
- case WM_CONBEEP:
- if (0) Beep(440, 400);
- return 0;
- case WM_CONTEXT:
- ConDrawText(hwnd);
- return 0;
- case WM_CLOSE:
- break;
- case WM_DESTROY:
- PostQuitMessage(0);
- return 0;
- }
- return DefWindowProc (hwnd, iMsg, wParam, lParam);
-}
-
-static void
-LoadUserPreferences(void)
-{
- DWORD size;
- DWORD res;
- DWORD type;
- HFONT hfont;
- /* default prefs */
- hfont = CreateFont(0,0, 0,0, 0, FALSE,FALSE,FALSE,
- ANSI_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS,
- CLEARTYPE_QUALITY, FIXED_PITCH, TEXT("Consolas"));
- if(hfont) {
- GetObject(hfont, sizeof(LOGFONT), (PSTR)&logfont);
- DeleteObject(hfont);
- } else {
- GetObject(GetStockObject(SYSTEM_FIXED_FONT),sizeof(LOGFONT),(PSTR)&logfont);
- }
- fgColor = GetSysColor(COLOR_WINDOWTEXT);
- bkgColor = GetSysColor(COLOR_WINDOW);
- winPos.left = -1;
- toolbarVisible = FALSE;
-
- if (RegCreateKeyEx(HKEY_CURRENT_USER, USER_KEY, 0, 0,
- REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
- &key, &res) != ERROR_SUCCESS)
- return;
- has_key = TRUE;
- if (res == REG_CREATED_NEW_KEY)
- return;
- size = sizeof(logfont);
- res = RegQueryValueEx(key,TEXT("Font"),NULL,&type,(LPBYTE)&logfont,&size);
- size = sizeof(fgColor);
- res = RegQueryValueEx(key,TEXT("FgColor"),NULL,&type,(LPBYTE)&fgColor,&size);
- size = sizeof(bkgColor);
- res = RegQueryValueEx(key,TEXT("BkColor"),NULL,&type,(LPBYTE)&bkgColor,&size);
- size = sizeof(winPos);
- res = RegQueryValueEx(key,TEXT("Pos"),NULL,&type,(LPBYTE)&winPos,&size);
- size = sizeof(toolbarVisible);
- res = RegQueryValueEx(key,TEXT("Toolbar"),NULL,&type,(LPBYTE)&toolbarVisible,&size);
-}
-
-static void
-SaveUserPreferences(void)
-{
- WINDOWPLACEMENT wndPlace;
-
- if (has_key == TRUE) {
- RegSetValueEx(key,TEXT("Font"),0,REG_BINARY,(CONST BYTE *)&logfont,sizeof(LOGFONT));
- RegSetValueEx(key,TEXT("FgColor"),0,REG_DWORD,(CONST BYTE *)&fgColor,sizeof(fgColor));
- RegSetValueEx(key,TEXT("BkColor"),0,REG_DWORD,(CONST BYTE *)&bkgColor,sizeof(bkgColor));
- RegSetValueEx(key,TEXT("Toolbar"),0,REG_DWORD,(CONST BYTE *)&toolbarVisible,sizeof(toolbarVisible));
-
- wndPlace.length = sizeof(WINDOWPLACEMENT);
- GetWindowPlacement(hFrameWnd,&wndPlace);
- /* If wndPlace.showCmd == SW_MINIMIZE, then the window is minimized.
- We don't care, wndPlace.rcNormalPosition always holds the last known position. */
- winPos = wndPlace.rcNormalPosition;
- RegSetValueEx(key,TEXT("Pos"),0,REG_BINARY,(CONST BYTE *)&winPos,sizeof(winPos));
- }
-}
-
-
-static void
-set_scroll_info(HWND hwnd)
-{
- SCROLLINFO info;
- int hScrollBy;
- /*
- * Set vertical scrolling range and scroll box position.
- */
-
- iVscrollMax = nBufLines-1;
- iVscrollPos = min(iVscrollPos, iVscrollMax);
- info.cbSize = sizeof(info);
- info.fMask = SIF_PAGE|SIF_RANGE|SIF_POS;
- info.nMin = 0;
- info.nPos = iVscrollPos;
- info.nPage = min(cyClient/cyChar, iVscrollMax);
- info.nMax = iVscrollMax;
- SetScrollInfo(hwnd, SB_VERT, &info, TRUE);
-
- /*
- * Set horizontal scrolling range and scroll box position.
- */
-
- iHscrollMax = LINE_LENGTH-1;
- hScrollBy = max(0, (iHscrollPos - (iHscrollMax-cxClient/cxChar))*cxChar);
- iHscrollPos = min(iHscrollPos, iHscrollMax);
- info.nPos = iHscrollPos;
- info.nPage = cxClient/cxChar;
- info.nMax = iHscrollMax;
- SetScrollInfo(hwnd, SB_HORZ, &info, TRUE);
- /*ScrollWindow(hwnd, hScrollBy, 0, NULL, NULL);*/
-}
-
-
-static void
-ensure_line_below(void)
-{
- if (cur_line->next == NULL) {
- if (nBufLines >= lines_to_save) {
- ScreenLine_t* pLine = buffer_top->next;
- FREE(buffer_top->text);
- FREE(buffer_top);
- buffer_top = pLine;
- buffer_top->prev = NULL;
- nBufLines--;
- }
- cur_line->next = ConNewLine();
- cur_line->next->prev = cur_line;
- buffer_bottom = cur_line->next;
- set_scroll_info(hClientWnd);
- }
-}
-
-static ScreenLine_t*
-ConNewLine(void)
-{
- ScreenLine_t *pLine;
-
- pLine = (ScreenLine_t *)ALLOC(sizeof(ScreenLine_t));
- if (!pLine)
- return NULL;
- pLine->text = (TCHAR *) ALLOC(canvasColumns * sizeof(TCHAR));
-#ifdef HARDDEBUG
- pLine->allocated = canvasColumns;
-#endif
- pLine->width = 0;
- pLine->prev = pLine->next = NULL;
- pLine->newline = 0;
- nBufLines++;
- return pLine;
-}
-
-static ScreenLine_t*
-GetLineFromY(int y)
-{
- ScreenLine_t *pLine = buffer_top;
- int i;
-
- for (i = 0; i < nBufLines && pLine != NULL; i++) {
- if (i == y)
- return pLine;
- pLine = pLine->next;
- }
- return NULL;
-}
-
-void ConCarriageFeed(int hard_newline)
-{
- cur_x = 0;
- ensure_line_below();
- cur_line->newline = hard_newline;
- cur_line = cur_line->next;
- if (cur_y < nBufLines-1) {
- cur_y++;
- } else if (iVscrollPos > 0) {
- iVscrollPos--;
- }
-}
-
-/*
- * Scroll screen if cursor is not visible.
- */
-static void
-ConScrollScreen(void)
-{
- if (cur_y >= iVscrollPos + cyClient/cyChar) {
- int iVscroll;
-
- iVscroll = cur_y - iVscrollPos - cyClient/cyChar + 1;
- iVscrollPos += iVscroll;
- ScrollWindowEx(hClientWnd, 0, -cyChar*iVscroll, NULL, NULL,
- NULL, NULL, SW_ERASE | SW_INVALIDATE);
- SetScrollPos(hClientWnd, SB_VERT, iVscrollPos, TRUE);
- UpdateWindow(hClientWnd);
- }
-}
-
-static void
-DrawSelection(HWND hwnd, POINT pt1, POINT pt2)
-{
- HDC hdc;
- int width,height;
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"pt1.x = %d, pt1.y = %d, pt2.x = %d, pt2.y = %d\n",
- (int) pt1.x, (int) pt1.y, (int) pt2.x, (int) pt2.y);
-#endif
- pt1.x = GetXFromLine(GetDC(hwnd),iHscrollPos,pt1.x,GetLineFromY(pt1.y));
- pt2.x = GetXFromLine(GetDC(hwnd),iHscrollPos,pt2.x,GetLineFromY(pt2.y-1));
- pt1.y -= iVscrollPos;
- pt2.y -= iVscrollPos;
- pt1.y *= cyChar;
- pt2.y *= cyChar;
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"pt1.x = %d, pt1.y = %d, pt2.x = %d, pt2.y = %d\n",
- (int) pt1.x, (int) pt1.y, (int) pt2.x, (int) pt2.y);
- fflush(stderr);
-#endif
- width = pt2.x-pt1.x;
- height = pt2.y - pt1.y;
- hdc = GetDC(hwnd);
- PatBlt(hdc,pt1.x,pt1.y,width,height,DSTINVERT);
- ReleaseDC(hwnd,hdc);
-}
-
-static void
-OnEditCopy(HWND hwnd)
-{
- HGLOBAL hMem;
- TCHAR *pMem;
- ScreenLine_t *pLine;
- RECT rects[3];
- POINT from,to;
- int i,j,sum,len;
- if (editBeg.y >= editEnd.y ||
- (editBeg.y == editEnd.y - 1 && editBeg.x > editEnd.x)) {
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"CopyReverting (Beg.x = %d, Beg.y = %d, "
- "End.x = %d, End.y = %d)\n",editBeg.x,editBeg.y,
- editEnd.x,editEnd.y);
- fflush(stderr);
-#endif
- from.x = editEnd.x;
- from.y = editEnd.y - 1;
- to.x = editBeg.x;
- to.y = editBeg.y + 1;
- calc_sel_area(rects,from,to);
- } else {
- calc_sel_area(rects,editBeg,editEnd);
- }
- sum = 1;
- for (i = 0; i < 3; ++i) {
- if (!EMPTY_RECT(rects[i])) {
- pLine = GetLineFromY(rects[i].top);
- for (j = rects[i].top; j < rects[i].bottom ;++j) {
- if (pLine == NULL) {
- sum += 2;
- break;
- }
- if (pLine->width > rects[i].left) {
- sum += (pLine->width < rects[i].right) ?
- pLine->width - rects[i].left :
- rects[i].right - rects[i].left;
- }
- if(pLine->newline && rects[i].right >= pLine->width) {
- sum += 2;
- }
- pLine = pLine->next;
- }
- }
- }
-#ifdef HARD_SEL_DEBUG
- fprintf(stderr,"sum = %d\n",sum);
- fflush(stderr);
-#endif
- hMem = GlobalAlloc(GHND, sum * sizeof(TCHAR));
- pMem = GlobalLock(hMem);
- for (i = 0; i < 3; ++i) {
- if (!EMPTY_RECT(rects[i])) {
- pLine = GetLineFromY(rects[i].top);
- for (j = rects[i].top; j < rects[i].bottom; ++j) {
- if (pLine == NULL) {
- memcpy(pMem,TEXT("\r\n"),2 * sizeof(TCHAR));
- pMem += 2;
- break;
- }
- if (pLine->width > rects[i].left) {
- len = (pLine->width < rects[i].right) ?
- pLine->width - rects[i].left :
- rects[i].right - rects[i].left;
- memcpy(pMem,pLine->text + rects[i].left,len * sizeof(TCHAR));
- pMem +=len;
- }
- if(pLine->newline && rects[i].right >= pLine->width) {
- memcpy(pMem,TEXT("\r\n"),2 * sizeof(TCHAR));
- pMem += 2;
- }
- pLine = pLine->next;
- }
- }
- }
- *pMem = TEXT('\0');
- /* Flash de selection area to give user feedback about copying */
- InvertSelectionArea(hwnd);
- Sleep(100);
- InvertSelectionArea(hwnd);
-
- OpenClipboard(hwnd);
- EmptyClipboard();
- GlobalUnlock(hMem);
- SetClipboardData(CF_UNICODETEXT,hMem);
- CloseClipboard();
-}
-
-/* XXX:PaN Tchar or char? */
-static void
-OnEditPaste(HWND hwnd)
-{
- HANDLE hClipMem;
- TCHAR *pClipMem,*pMem,*pMem2;
- if (!OpenClipboard(hwnd))
- return;
- if ((hClipMem = GetClipboardData(CF_UNICODETEXT)) != NULL) {
- pClipMem = GlobalLock(hClipMem);
- pMem = (TCHAR *)ALLOC(GlobalSize(hClipMem) * sizeof(TCHAR));
- pMem2 = pMem;
- while ((*pMem2 = *pClipMem) != TEXT('\0')) {
- if (*pClipMem == TEXT('\r'))
- *pMem2 = TEXT('\n');
- ++pMem2;
- ++pClipMem;
- }
- GlobalUnlock(hClipMem);
- write_inbuf(pMem, _tcsclen(pMem));
- }
- CloseClipboard();
-}
-
-static void
-OnEditSelAll(HWND hwnd)
-{
- editBeg.x = 0;
- editBeg.y = 0;
- editEnd.x = LINE_LENGTH-1;
- editEnd.y = cur_y;
- fTextSelected = TRUE;
- InvalidateRect(hwnd, NULL, TRUE);
-}
-
-CF_HOOK_RET APIENTRY CFHookProc(HWND hDlg,UINT iMsg,WPARAM wParam,LPARAM lParam)
-{
- /* Hook procedure for font dialog box */
- HWND hOwner;
- RECT rc,rcOwner,rcDlg;
- switch (iMsg) {
- case WM_INITDIALOG:
- /* center dialogbox within its owner window */
- if ((hOwner = GetParent(hDlg)) == NULL)
- hOwner = GetDesktopWindow();
- GetWindowRect(hOwner, &rcOwner);
- GetWindowRect(hDlg, &rcDlg);
- CopyRect(&rc, &rcOwner);
- OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
- OffsetRect(&rc, -rc.left, -rc.top);
- OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);
- SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2),
- rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE);
- return (CF_HOOK_RET) 1;
- default:
- break;
- }
- return (CF_HOOK_RET) 0; /* Let the default procedure process the message */
-}
-
-static BOOL
-ConChooseFont(HWND hwnd)
-{
- HDC hdc;
- hdc = GetDC(hwnd);
- cf.lStructSize = sizeof(CHOOSEFONT);
- cf.hwndOwner = hwnd;
- cf.hDC = NULL;
- cf.lpLogFont = &logfont;
- cf.iPointSize = 0;
- cf.Flags = CF_INITTOLOGFONTSTRUCT|CF_SCREENFONTS|CF_FIXEDPITCHONLY|CF_EFFECTS|CF_ENABLEHOOK;
- cf.rgbColors = GetTextColor(hdc);
- cf.lCustData = 0L;
- cf.lpfnHook = CFHookProc;
- cf.lpTemplateName = NULL;
- cf.hInstance = NULL;
- cf.lpszStyle = NULL;
- cf.nFontType = 0;
- cf.nSizeMin = 0;
- cf.nSizeMax = 0;
- ReleaseDC(hwnd,hdc);
- return ChooseFont(&cf);
-}
-
-static void
-ConFontInitialize(HWND hwnd)
-{
- HDC hdc;
- TEXTMETRIC tm;
- HFONT hFont;
-
- hFont = CreateFontIndirect(&logfont);
- hdc = GetDC(hwnd);
- SelectObject(hdc, hFont);
- SetTextColor(hdc,fgColor);
- SetBkColor(hdc,bkgColor);
- GetTextMetrics(hdc, &tm);
- cxChar = tm.tmAveCharWidth;
- cxCharMax = tm.tmMaxCharWidth;
- cyChar = tm.tmHeight + tm.tmExternalLeading;
- ReleaseDC(hwnd, hdc);
-}
-
-static void
-ConSetFont(HWND hwnd)
-{
- HDC hdc;
- TEXTMETRIC tm;
- HFONT hFontNew;
-
- hFontNew = CreateFontIndirect(&logfont);
- SendMessage(hComboWnd,WM_SETFONT,(WPARAM)hFontNew,
- MAKELPARAM(1,0));
- hdc = GetDC(hwnd);
- DeleteObject(SelectObject(hdc, hFontNew));
- GetTextMetrics(hdc, &tm);
- cxChar = tm.tmAveCharWidth;
- cxCharMax = tm.tmMaxCharWidth;
- cyChar = tm.tmHeight + tm.tmExternalLeading;
- fgColor = cf.rgbColors;
- SetTextColor(hdc,fgColor);
- ReleaseDC(hwnd, hdc);
- set_scroll_info(hwnd);
- HideCaret(hwnd);
- if (DestroyCaret()) {
- CreateCaret(hwnd, NULL, cxChar, cyChar);
- SetCaretPos(GetXFromCurrentY(hdc,iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar);
- }
- ShowCaret(hwnd);
- InvalidateRect(hwnd, NULL, TRUE);
-}
-
-CC_HOOK_RET APIENTRY
-CCHookProc(HWND hDlg,UINT iMsg,WPARAM wParam,LPARAM lParam)
-{
- /* Hook procedure for choose color dialog box */
- HWND hOwner;
- RECT rc,rcOwner,rcDlg;
- switch (iMsg) {
- case WM_INITDIALOG:
- /* center dialogbox within its owner window */
- if ((hOwner = GetParent(hDlg)) == NULL)
- hOwner = GetDesktopWindow();
- GetWindowRect(hOwner, &rcOwner);
- GetWindowRect(hDlg, &rcDlg);
- CopyRect(&rc, &rcOwner);
- OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
- OffsetRect(&rc, -rc.left, -rc.top);
- OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);
- SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2),
- rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE);
- return (CC_HOOK_RET) 1;
- default:
- break;
- }
- return (CC_HOOK_RET) 0; /* Let the default procedure process the message */
-}
-
-void ConChooseColor(HWND hwnd)
-{
- CHOOSECOLOR cc;
- static COLORREF acrCustClr[16];
- HBRUSH hbrush;
- HDC hdc;
-
- /* Initialize CHOOSECOLOR */
- ZeroMemory(&cc, sizeof(CHOOSECOLOR));
- cc.lStructSize = sizeof(CHOOSECOLOR);
- cc.hwndOwner = hwnd;
- cc.lpCustColors = (LPDWORD) acrCustClr;
- cc.rgbResult = bkgColor;
- cc.lpfnHook = CCHookProc;
- cc.Flags = CC_FULLOPEN|CC_RGBINIT|CC_SOLIDCOLOR|CC_ENABLEHOOK;
-
- if (ChooseColor(&cc)==TRUE) {
- bkgColor = cc.rgbResult;
- hdc = GetDC(hwnd);
- SetBkColor(hdc,bkgColor);
- ReleaseDC(hwnd,hdc);
- hbrush = CreateSolidBrush(bkgColor);
- DeleteObject((HBRUSH)SetClassLongPtr(hClientWnd,GCL_HBRBACKGROUND,(LONG_PTR)hbrush));
- InvalidateRect(hwnd,NULL,TRUE);
- }
-}
-
-OFN_HOOK_RET APIENTRY OFNHookProc(HWND hwndDlg,UINT iMsg,
- WPARAM wParam,LPARAM lParam)
-{
- /* Hook procedure for open file dialog box */
- HWND hOwner,hDlg;
- RECT rc,rcOwner,rcDlg;
- hDlg = GetParent(hwndDlg);
- switch (iMsg) {
- case WM_INITDIALOG:
- /* center dialogbox within its owner window */
- if ((hOwner = GetParent(hDlg)) == NULL)
- hOwner = GetDesktopWindow();
- GetWindowRect(hOwner, &rcOwner);
- GetWindowRect(hDlg, &rcDlg);
- CopyRect(&rc, &rcOwner);
- OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
- OffsetRect(&rc, -rc.left, -rc.top);
- OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);
- SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2),
- rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE);
- return (OFN_HOOK_RET) 1;
- default:
- break;
- }
- return (OFN_HOOK_RET) 0; /* the let default procedure process the message */
-}
-
-static void
-GetFileName(HWND hwnd, TCHAR *pFile)
-{
- /* Open the File Open dialog box and */
- /* retrieve the file name */
- OPENFILENAME ofn;
- TCHAR szFilterSpec [128] = TEXT("logfiles (*.log)\0*.log\0All files (*.*)\0*.*\0\0");
- #define MAXFILENAME 256
- TCHAR szFileName[MAXFILENAME];
- TCHAR szFileTitle[MAXFILENAME];
-
- /* these need to be filled in */
- _tcscpy(szFileName, TEXT("erlshell.log"));
- _tcscpy(szFileTitle, TEXT("")); /* must be NULL */
-
- ofn.lStructSize = sizeof(OPENFILENAME);
- ofn.hwndOwner = NULL;
- ofn.lpstrFilter = szFilterSpec;
- ofn.lpstrCustomFilter = NULL;
- ofn.nMaxCustFilter = 0;
- ofn.nFilterIndex = 0;
- ofn.lpstrFile = szFileName;
- ofn.nMaxFile = MAXFILENAME;
- ofn.lpstrInitialDir = NULL;
- ofn.lpstrFileTitle = szFileTitle;
- ofn.nMaxFileTitle = MAXFILENAME;
- ofn.lpstrTitle = TEXT("Open logfile");
- ofn.lpstrDefExt = TEXT("log");
- ofn.Flags = OFN_CREATEPROMPT|OFN_HIDEREADONLY|OFN_EXPLORER|OFN_ENABLEHOOK|OFN_NOCHANGEDIR; /* OFN_NOCHANGEDIR only works in Vista :( */
- ofn.lpfnHook = OFNHookProc;
-
- if (!GetOpenFileName ((LPOPENFILENAME)&ofn)){
- *pFile = TEXT('\0');
- } else {
- _tcscpy(pFile, ofn.lpstrFile);
- }
-}
-
-void OpenLogFile(HWND hwnd)
-{
- /* open a file for logging */
- TCHAR filename[_MAX_PATH];
-
- GetFileName(hwnd, filename);
- if (filename[0] == '\0')
- return;
- if (NULL == (logfile = _tfopen(filename,TEXT("w,ccs=UNICODE"))))
- return;
-}
-
-void CloseLogFile(HWND hwnd)
-{
- /* close log file */
- fclose(logfile);
- logfile = NULL;
-}
-
-void LogFileWrite(TCHAR *buf, int num_chars)
-{
- /* write to logfile */
- int from,to;
- while (num_chars-- > 0) {
- switch (*buf) {
- case SET_CURSOR:
- buf++;
- from = *((int *)buf);
- buf += sizeof(int)/sizeof(TCHAR);
- to = *((int *)buf);
- buf += (sizeof(int)/sizeof(TCHAR))-1;
- num_chars -= 2 * (sizeof(int)/sizeof(TCHAR));
- // Won't seek in Unicode file, sorry...
- // fseek(logfile,to-from *sizeof(TCHAR),SEEK_CUR);
- break;
- default:
- _fputtc(*buf,logfile);
- break;
- }
- buf++;
- }
-}
-
-static void
-init_buffers(void)
-{
- inbuf.data = (TCHAR *) ALLOC(BUFSIZE * sizeof(TCHAR));
- outbuf.data = (TCHAR *) ALLOC(BUFSIZE * sizeof(TCHAR));
- inbuf.size = BUFSIZE;
- inbuf.rdPos = inbuf.wrPos = 0;
- outbuf.size = BUFSIZE;
- outbuf.rdPos = outbuf.wrPos = 0;
-}
-
-static int
-check_realloc(buffer_t *buf, int num_chars)
-{
- if (buf->wrPos + num_chars >= buf->size) {
- if (buf->size > MAXBUFSIZE)
- return 0;
- buf->size += num_chars + BUFSIZE;
- if (!(buf->data = (TCHAR *)REALLOC(buf->data, buf->size * sizeof(TCHAR)))) {
- buf->size = buf->rdPos = buf->wrPos = 0;
- return 0;
- }
- }
- return 1;
-}
-
-static int
-write_inbuf(TCHAR *data, int num_chars)
-{
- TCHAR *buf;
- int nwrite;
- WaitForSingleObject(console_input,INFINITE);
- if (!check_realloc(&inbuf,num_chars)) {
- ReleaseSemaphore(console_input,1,NULL);
- return -1;
- }
- buf = &inbuf.data[inbuf.wrPos];
- inbuf.wrPos += num_chars;
- nwrite = num_chars;
- while (nwrite--)
- *buf++ = *data++;
- SetEvent(console_input_event);
- ReleaseSemaphore(console_input,1,NULL);
- return num_chars;
-}
-
-static int
-write_outbuf(TCHAR *data, int num_chars)
-{
- TCHAR *buf;
- int nwrite;
-
- WaitForSingleObject(console_output,INFINITE);
- if (!check_realloc(&outbuf, num_chars)) {
- ReleaseSemaphore(console_output,1,NULL);
- return -1;
- }
- if (outbuf.rdPos == outbuf.wrPos)
- PostMessage(hClientWnd, WM_CONTEXT, 0L, 0L);
- buf = &outbuf.data[outbuf.wrPos];
- outbuf.wrPos += num_chars;
- nwrite = num_chars;
- while (nwrite--)
- *buf++ = *data++;
- ReleaseSemaphore(console_output,1,NULL);
- return num_chars;
-}
-
-DIALOG_PROC_RET CALLBACK AboutDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
-{
- HWND hOwner;
- RECT rc,rcOwner,rcDlg;
-
- switch (iMsg) {
- case WM_INITDIALOG:
- /* center dialogbox within its owner window */
- if ((hOwner = GetParent(hDlg)) == NULL)
- hOwner = GetDesktopWindow();
- GetWindowRect(hOwner, &rcOwner);
- GetWindowRect(hDlg, &rcDlg);
- CopyRect(&rc, &rcOwner);
- OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
- OffsetRect(&rc, -rc.left, -rc.top);
- OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);
- SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2),
- rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE);
- SetDlgItemText(hDlg, ID_OTP_VERSIONSTRING,
- TEXT("OTP version ") TEXT(ERLANG_OTP_VERSION));
- SetDlgItemText(hDlg, ID_ERTS_VERSIONSTRING,
- TEXT("Erlang emulator version ") TEXT(ERLANG_VERSION));
- return (DIALOG_PROC_RET) TRUE;
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- case IDCANCEL:
- EndDialog(hDlg,0);
- return (DIALOG_PROC_RET) TRUE;
- }
- break;
- }
- return (DIALOG_PROC_RET) FALSE;
-}
-
-static void
-ConDrawText(HWND hwnd)
-{
- int num_chars;
- int nchars;
- TCHAR *buf;
- int from, to;
- int dl;
- int dc;
- RECT rc;
-
- WaitForSingleObject(console_output, INFINITE);
- nchars = 0;
- num_chars = outbuf.wrPos - outbuf.rdPos;
- buf = &outbuf.data[outbuf.rdPos];
- if (logfile != NULL)
- LogFileWrite(buf, num_chars);
-
-
-#ifdef HARDDEBUG
- {
- TCHAR *bu = (TCHAR *) ALLOC((num_chars+1) * sizeof(TCHAR));
- memcpy(bu,buf,num_chars * sizeof(TCHAR));
- bu[num_chars]='\0';
- fprintf(stderr,"ConDrawText\"%S\"\n",bu);
- FREE(bu);
- fflush(stderr);
- }
-#endif
- /*
- * Don't draw any text in the window; just update the line buffers
- * and invalidate the appropriate part of the window. The window
- * will be updated on the next WM_PAINT message.
- */
-
- while (num_chars-- > 0) {
- switch (*buf) {
- case '\r':
- break;
- case '\n':
- if (nchars > 0) {
- rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars);
- rc.right = rc.left + cxCharMax*nchars;
- rc.top = cyChar * (cur_y-iVscrollPos);
- rc.bottom = rc.top + cyChar;
- InvalidateRect(hwnd, &rc, TRUE);
- nchars = 0;
- }
- ConCarriageFeed(1);
- ConScrollScreen();
- break;
- case SET_CURSOR:
- if (nchars > 0) {
- rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars);
- rc.right = rc.left + cxCharMax*nchars;
- rc.top = cyChar * (cur_y-iVscrollPos);
- rc.bottom = rc.top + cyChar;
- InvalidateRect(hwnd, &rc, TRUE);
- nchars = 0;
- }
- buf++;
- from = *((int *)buf);
- buf += sizeof(int)/sizeof(TCHAR);
- to = *((int *)buf);
- buf += (sizeof(int)/sizeof(TCHAR))-1;
- num_chars -= 2 * (sizeof(int)/sizeof(TCHAR));
- while (to > from) {
- cur_x++;
- if (GetXFromCurrentY(GetDC(hwnd),0,cur_x)+cxChar >
- (LINE_LENGTH * cxChar)) {
- cur_x = 0;
- cur_y++;
- ensure_line_below();
- cur_line = cur_line->next;
- }
- from++;
- }
- while (to < from) {
- cur_x--;
- if (cur_x < 0) {
- cur_y--;
- cur_line = cur_line->prev;
- cur_x = cur_line->width-1;
- }
- from--;
- }
-
- break;
- default:
- nchars++;
- cur_line->text[cur_x] = *buf;
- cur_x++;
- if (cur_x > cur_line->width)
- cur_line->width = cur_x;
- if (GetXFromCurrentY(GetDC(hwnd),0,cur_x)+cxChar >
- (LINE_LENGTH * cxChar)) {
- if (nchars > 0) {
- rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars);
- rc.right = rc.left + cxCharMax*nchars;
- rc.top = cyChar * (cur_y-iVscrollPos);
- rc.bottom = rc.top + cyChar;
- InvalidateRect(hwnd, &rc, TRUE);
- }
- ConCarriageFeed(0);
- nchars = 0;
- }
- }
- buf++;
- }
- if (nchars > 0) {
- rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars);
- rc.right = rc.left + cxCharMax*nchars;
- rc.top = cyChar * (cur_y-iVscrollPos);
- rc.bottom = rc.top + cyChar;
- InvalidateRect(hwnd, &rc, TRUE);
- }
- ConScrollScreen();
- SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar);
- outbuf.wrPos = outbuf.rdPos = 0;
- ReleaseSemaphore(console_output, 1, NULL);
-}
-
-static void
-AddToCmdHistory(void)
-{
- int i;
- int size;
- Uint32 *buf;
- wchar_t cmdBuf[128];
-
- if (llen != 0) {
- for (i = 0, size = 0; i < llen-1; i++) {
- /*
- * Find end of prompt.
- */
- if ((lbuf[i] == '>') && lbuf[i+1] == ' ') {
- buf = &lbuf[i+2];
- size = llen-i-2;
- break;
- }
- }
- if (size > 0 && size < 128) {
- for (i = 0;i < size; ++i) {
- cmdBuf[i] = (wchar_t) buf[i];
- }
- cmdBuf[size] = 0;
- SendMessage(hComboWnd,CB_INSERTSTRING,0,(LPARAM)cmdBuf);
- }
- }
-}
-
-/*static TBBUTTON tbb[] =
-{
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- 0, IDMENU_COPY, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0,
- 1, IDMENU_PASTE, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0,
- 2, IDMENU_FONT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0,
- 3, IDMENU_ABOUT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0,
- 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0,
- };*/
-static TBBUTTON tbb[] =
-{
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP},
- {0, IDMENU_COPY, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE},
- {1, IDMENU_PASTE, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE},
- {2, IDMENU_FONT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE},
- {3, IDMENU_ABOUT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE},
- {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}
-};
-
-static TBADDBITMAP tbbitmap =
-{
- HINST_COMMCTRL, IDB_STD_SMALL_COLOR,
-};
-
-
-static HWND
-InitToolBar(HWND hwndParent)
-{
- int x,y,cx;
- HWND hwndTB,hwndTT;
- RECT r;
- TOOLINFO ti;
- HFONT hFontNew;
- DWORD backgroundColor = GetSysColor(COLOR_BTNFACE);
- COLORMAP colorMap;
- colorMap.from = RGB(192, 192, 192);
- colorMap.to = backgroundColor;
- /* Create toolbar window with tooltips */
- hwndTB = CreateWindowEx(0,TOOLBARCLASSNAME,(TCHAR *)NULL,
- WS_CHILD|CCS_TOP|WS_CLIPSIBLINGS|TBSTYLE_TOOLTIPS,
- 0,0,0,0,hwndParent,
- (HMENU)2,hInstance,NULL);
- SendMessage(hwndTB,TB_BUTTONSTRUCTSIZE,
- (WPARAM) sizeof(TBBUTTON),0);
- tbbitmap.hInst = NULL;
- tbbitmap.nID = (UINT_PTR) CreateMappedBitmap(beam_module, 1,0, &colorMap, 1);
- SendMessage(hwndTB, TB_ADDBITMAP, (WPARAM) 4,
- (LPARAM) &tbbitmap);
-
- SendMessage(hwndTB,TB_ADDBUTTONS, (WPARAM) 30,
- (LPARAM) tbb);
- if (toolbarVisible)
- ShowWindow(hwndTB, SW_SHOW);
-
- /* Create combobox window */
- SendMessage(hwndTB,TB_GETITEMRECT,0,(LPARAM)&r);
- x = r.left; y = r.top;
- SendMessage(hwndTB,TB_GETITEMRECT,23,(LPARAM)&r);
- cx = r.right - x + 1;
- hComboWnd = CreateWindow(TEXT("combobox"),NULL,WS_VSCROLL|WS_CHILD|WS_VISIBLE|CBS_DROPDOWNLIST,
- x,y,cx,100,hwndParent,(HMENU)ID_COMBOBOX, hInstance,NULL);
- SetParent(hComboWnd,hwndTB);
- hFontNew = CreateFontIndirect(&logfont);
- SendMessage(hComboWnd,WM_SETFONT,(WPARAM)hFontNew,
- MAKELPARAM(1,0));
-
- /* Add tooltip for combo box */
- ZeroMemory(&ti,sizeof(TOOLINFO));
- ti.cbSize = sizeof(TOOLINFO);
- ti.uFlags = TTF_IDISHWND|TTF_CENTERTIP|TTF_SUBCLASS;
- ti.hwnd = hwndTB;;
- ti.uId = (UINT_PTR)hComboWnd;
- ti.lpszText = LPSTR_TEXTCALLBACK;
- hwndTT = (HWND)SendMessage(hwndTB,TB_GETTOOLTIPS,0,0);
- SendMessage(hwndTT,TTM_ADDTOOL,0,(LPARAM)&ti);
-
- return hwndTB;
-}
-
-static void
-window_title(struct title_buf *tbuf)
-{
- int res, i;
- size_t bufsz = TITLE_BUF_SZ;
- unsigned char charbuff[TITLE_BUF_SZ];
-
- res = erl_drv_getenv("ERL_WINDOW_TITLE", charbuff, &bufsz);
- if (res < 0)
- tbuf->name = erlang_window_title;
- else if (res == 0) {
- for (i = 0; i < bufsz; ++i) {
- tbuf->buf[i] = charbuff[i];
- }
- tbuf->buf[bufsz - 1] = 0;
- tbuf->name = &tbuf->buf[0];
- } else {
- char *buf = ALLOC(bufsz);
- if (!buf)
- tbuf->name = erlang_window_title;
- else {
- while (1) {
- char *newbuf;
- res = erl_drv_getenv("ERL_WINDOW_TITLE", buf, &bufsz);
- if (res <= 0) {
- if (res == 0) {
- TCHAR *wbuf = ALLOC(bufsz *sizeof(TCHAR));
- for (i = 0; i < bufsz ; ++i) {
- wbuf[i] = buf[i];
- }
- wbuf[bufsz - 1] = 0;
- FREE(buf);
- tbuf->name = wbuf;
- } else {
- tbuf->name = erlang_window_title;
- FREE(buf);
- }
- break;
- }
- newbuf = REALLOC(buf, bufsz);
- if (newbuf)
- buf = newbuf;
- else {
- tbuf->name = erlang_window_title;
- FREE(buf);
- break;
- }
- }
- }
- }
-}
-
-static void
-free_window_title(struct title_buf *tbuf)
-{
- if (tbuf->name != erlang_window_title && tbuf->name != &tbuf->buf[0])
- FREE(tbuf->name);
-}
diff --git a/erts/emulator/drivers/win32/win_con.h b/erts/emulator/drivers/win32/win_con.h
deleted file mode 100644
index 7a642cd7ed..0000000000
--- a/erts/emulator/drivers/win32/win_con.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2007-2016. All Rights Reserved.
- *
- * 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.
- *
- * %CopyrightEnd%
- */
-
-/*
- * External API for the windows console (aka werl window)
- * used by ttsl_drv.c
- */
-#ifndef _WIN_CON_H_VISITED
-#define _WIN_CON_H_VISITED 1
-void ConNormalExit(void);
-void ConWaitForExit(void);
-void ConSetCtrlHandler(BOOL (WINAPI *handler)(DWORD));
-int ConPutChar(Uint32 c);
-void ConSetCursor(int from, int to);
-void ConPrintf(char *format, ...);
-void ConVprintf(char *format, va_list va);
-void ConBeep(void);
-int ConReadInput(Uint32 *data, int nbytes);
-int ConGetKey(void);
-int ConGetColumns(void);
-int ConGetRows(void);
-void ConInit(void);
-#endif /* _WIN_CON_H_VISITED */
diff --git a/erts/emulator/internal_doc/BeamAsm.md b/erts/emulator/internal_doc/BeamAsm.md
index 351b908296..4df4634d2d 100644
--- a/erts/emulator/internal_doc/BeamAsm.md
+++ b/erts/emulator/internal_doc/BeamAsm.md
@@ -218,14 +218,16 @@ executable page and once with a writable page. Since they're backed by the
same memory, writes to the writable page appear magically in the executable
one.
-The `erts_writable_code_ptr` function can be used to get writable pointers,
-given a module instance:
+The `erts_writable_code_ptr` function can be used to get writable pointers
+given a module instance, provided that it has been unsealed first:
- for (i = 0; i < n; ++i) {
+ for (i = 0; i < n; i++) {
const ErtsCodeInfo* ci_exec;
ErtsCodeInfo* ci_rw;
void *w_ptr;
+ erts_unseal_module(&modp->curr);
+
ci_exec = code_hdr->functions[i];
w_ptr = erts_writable_code_ptr(&modp->curr, ci_exec);
ci_rw = (ErtsCodeInfo*)w_ptr;
@@ -233,11 +235,15 @@ given a module instance:
uninstall_breakpoint(ci_rw, ci_exec);
consolidate_bp_data(modp, ci_rw, 1);
ASSERT(ci_rw->gen_bp == NULL);
+
+ erts_seal_module(&modp->curr);
}
Without the module instance there's no reliable way to figure out the writable
address of a code page, and we rely on _address space layout randomization_
-(ASLR) to make it difficult to guess.
+(ASLR) to make it difficult to guess. On some platforms, security is further
+enhanced by protecting the writable area from writes until the module has been
+unsealed by `erts_unseal_module`.
### Export tracing
@@ -302,7 +308,12 @@ that perf provides functionality similar to that of [eprof](https://erlang.org/d
You can run perf on BeamAsm like this:
- perf record erl +JPperf true
+ # Start Erlang under perf
+ perf record -- erl +JPperf true
+ # Record a running instance started with `+JPperf true` for 10s
+ perf record --pid $BEAM_PID -- sleep 10
+ # Record a running instance started with `+JPperf true` until interrupted
+ perf record --pid $BEAM_PID
and then look at the results using `perf report` as you normally would with
perf.
@@ -374,8 +385,9 @@ not need the symbols in the executable.
Using the same data we can also produce a graph where the scheduler profile data
has been merged by using `sed`:
- ## Strip [0-9]+_ from all scheduler names
- sed -e 's/^[0-9]\+_//' out.folded > out.folded_sched
+ ## Strip [0-9]+_ and/or _[0-9]+ from all scheduler names
+ ## scheduler names changed in OTP26, hence two expressions
+ sed -e 's/^[0-9]\+_//' -e 's/^erts_\([^_]\+\)_[0-9]\+/erts_\1/' out.folded > out.folded_sched
## Create the svg
flamegraph.pl out.folded_sched > out_sched.svg
@@ -432,7 +444,7 @@ we have found useful:
on another host. In early version of perf this command does not work,
instead you can use [this bash script](https://github.com/torvalds/linux/blob/master/tools/perf/perf-archive.sh).
* `perf report` gives "failed to process sample" and/or "failed to process type: 68"
- This probably means that you are running a bugge version of perf. We have
+ This probably means that you are running a bugged version of perf. We have
seen this when running Ubuntu 18.04 with kernel version 4. If you update
to Ubuntu 20.04 or use Ubuntu 18.04 with kernel version 5 the problem
should go away.
diff --git a/erts/emulator/internal_doc/CodeLoading.md b/erts/emulator/internal_doc/CodeLoading.md
index fa5bba0643..83d3d8048b 100644
--- a/erts/emulator/internal_doc/CodeLoading.md
+++ b/erts/emulator/internal_doc/CodeLoading.md
@@ -41,8 +41,8 @@ only be done by one loader process at a time. A second loader process
trying to enter finishing phase will be suspended until the first
loader is done. This will only block the process, the scheduler is
free to schedule other work while the second loader is waiting. (See
-`erts_try_seize_code_write_permission` and
-`erts_release_code_write_permission`).
+`erts_try_seize_code_load_permission` and
+`erts_release_code_load_permission`).
The ability to prepare several modules in parallel is not currently
used as almost all code loading is serialized by the code\_server
@@ -101,7 +101,7 @@ result of a half loaded module.
The finishing phase is carried out in the following sequence by the
BIF `erlang:finish_loading`:
-1. Seize exclusive code write permission (suspend process if needed
+1. Seize exclusive code load permission (suspend process if needed
until we get it).
2. Make a full copy of all the active access structures. This copy is
@@ -119,7 +119,7 @@ BIF `erlang:finish_loading`:
6. After thread progress, commit the staging area by assigning
`the_staging_code_index` to `the_active_code_index`.
-7. Release the code write permission allowing other processes to stage
+7. Release the code load permission allowing other processes to stage
new code.
8. Resume the loader process allowing it to return from
diff --git a/erts/emulator/internal_doc/Tracing.md b/erts/emulator/internal_doc/Tracing.md
index f0182daad8..28c973c264 100644
--- a/erts/emulator/internal_doc/Tracing.md
+++ b/erts/emulator/internal_doc/Tracing.md
@@ -127,7 +127,8 @@ aligned write operation on all hardware architectures we use.
This is a simplified sequence describing what `trace_pattern` goes
through when adding a new breakpoint.
-1. Seize exclusive code write permission (suspend process until we get it).
+1. Seize exclusive code modification permission (suspend process until we get
+ it).
2. Allocate breakpoint structure `GenericBp` including both generations.
Set the active part as disabled with a zeroed flagfield. Save the original
@@ -147,21 +148,20 @@ through when adding a new breakpoint.
7. Wait for thread progress.
-8. Commit the breadpoint by switching `erts_active_bp_index`.
+8. Commit the breakpoint by switching `erts_active_bp_index`.
9. Wait for thread progress.
10. Prepare for next call to `trace_pattern` by updating the new staging part
(the old active) of the breakpoint to be identic to the the new active part.
-11. Release code write permission and return from `trace_pattern`.
+11. Release code modification permission and return from `trace_pattern`.
-The code write permission "lock" seized in step 1 is the same as used
-by code loading. This will ensure that only one process at a time can
-stage new trace settings but it will also prevent concurrent code
-loading and make sure we see a consistent view of the beam code during
-the entire sequence.
+The code modification permission "lock" seized in step 1 is also taken by code
+loading. This ensures that only one process at a time can stage new trace
+settings, and also prevents concurrent codeloading and make sure we see a
+consistent view of the beam code during the entire sequence.
Between step 6 and 8, runninng processes might execute the written
`op_i_generic_breakpoint` instruction. They will get the breakpoint
@@ -186,7 +186,8 @@ original beam instruction.
Here is a more complete sequence that contains both adding, updating
and removing breakpoints.
-1. Seize exclusive code write permission (suspend process until we get it).
+1. Seize exclusive code modification permission (suspend process until we get
+ it).
2. Allocate new breakpoint structures with a disabled active part and
the original beam instruction. Write a pointer to the breakpoint in
@@ -202,7 +203,7 @@ and removing breakpoints.
6. Wait for thread progress.
-7. Commit all staged breadpoints by switching `erts_active_bp_index`.
+7. Commit all staged breakpoints by switching `erts_active_bp_index`.
8. Wait for thread progress.
@@ -216,7 +217,7 @@ and removing breakpoints.
12. Deallocate disabled breakpoint structures.
-13. Release code write permission and return from `trace_pattern`.
+13. Release code modification permission and return from `trace_pattern`.
### All that Waiting for Thread Progress
diff --git a/erts/emulator/nifs/common/prim_socket_int.h b/erts/emulator/nifs/common/prim_socket_int.h
new file mode 100644
index 0000000000..9f753bf80b
--- /dev/null
+++ b/erts/emulator/nifs/common/prim_socket_int.h
@@ -0,0 +1,853 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022-2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ *
+ * ----------------------------------------------------------------------
+ * Purpose : "Global" Types and stuff for socket.
+ * ----------------------------------------------------------------------
+ *
+ */
+
+#ifndef PRIM_SOCKET_INT_H__
+#define PRIM_SOCKET_INT_H__
+
+#include <erl_nif.h>
+#include <sys.h>
+
+#include "socket_int.h"
+#include "socket_dbg.h"
+
+
+/* ********************************************************************* *
+ * SOCKET and HANDLE *
+ * ********************************************************************* *
+ */
+
+#if defined(__WIN32__)
+
+#define INVALID_EVENT NULL
+#define SOCKET_FORMAT_STR "%lld"
+
+#else
+
+#define INVALID_HANDLE (-1)
+typedef int HANDLE;
+#define INVALID_SOCKET (-1)
+typedef int SOCKET; /* A subset of HANDLE */
+#define INVALID_EVENT INVALID_HANDLE
+#define SOCKET_FORMAT_STR "%d"
+
+#endif
+
+
+/* ********************************************************************* *
+ * Socket state defs and macros *
+ * ********************************************************************* *
+ */
+
+#define ESOCK_STATE_BOUND 0x0001 /* readState */
+#define ESOCK_STATE_LISTENING 0x0002 /* readState */
+#define ESOCK_STATE_ACCEPTING 0x0004 /* readState */
+#define ESOCK_STATE_CONNECTING 0x0010 /* writeState */
+#define ESOCK_STATE_CONNECTED 0x0020 /* writeState */
+
+/* This is set in either readState or writeState
+ * so it has to be read from both.
+ * Means that the socket has been used in select,
+ * so select_stop is required. */
+#define ESOCK_STATE_SELECTED 0x0100 /* readState or writeState */
+
+/* These are set in both readState and writeState
+ * so they can be read from either. */
+#define ESOCK_STATE_CLOSING 0x0200 /* readState and writeState */
+
+#define ESOCK_STATE_CLOSED 0x0400 /* readState and writeState */
+//
+#define ESOCK_STATE_DTOR 0x8000
+
+#define IS_BOUND(st) \
+ (((st) & ESOCK_STATE_BOUND) != 0)
+
+#define IS_CLOSED(st) \
+ (((st) & ESOCK_STATE_CLOSED) != 0)
+
+#define IS_CLOSING(st) \
+ (((st) & ESOCK_STATE_CLOSING) != 0)
+
+#define IS_ACCEPTING(st) \
+ (((st) & ESOCK_STATE_ACCEPTING) != 0)
+
+#define IS_OPEN(st) \
+ (((st) & (ESOCK_STATE_CLOSED | ESOCK_STATE_CLOSING)) == 0)
+
+#define IS_SELECTED(d) \
+ ((((d)->readState | (d)->writeState) & ESOCK_STATE_SELECTED) != 0)
+
+
+#define ESOCK_DESC_PATTERN_CREATED 0x03030303
+#define ESOCK_DESC_PATTERN_DTOR 0xC0C0C0C0
+
+
+/* ==========================================================================
+ * The ESOCK_IS_ERROR macro below is used for portability reasons.
+ * While POSIX specifies that errors from socket-related system calls
+ * should be indicated with a -1 return value, some users have experienced
+ * non-Windows OS kernels that return negative values other than -1.
+ * While one can argue that such kernels are technically broken, comparing
+ * against values less than 0 covers their out-of-spec return values without
+ * imposing incorrect semantics on systems that manage to correctly return -1
+ * for errors, thus increasing Erlang's portability.
+ */
+#ifdef __WIN32__
+#define ESOCK_IS_ERROR(val) ((val) == INVALID_SOCKET)
+#else
+#define ESOCK_IS_ERROR(val) ((val) < 0)
+#endif
+
+
+/* ********************************************************************* *
+ * Misc *
+ * ********************************************************************* *
+ */
+
+#define ESOCK_GET_RESOURCE(ENV, REF, RES) \
+ enif_get_resource((ENV), (REF), esocks, (RES))
+
+#define ESOCK_MON2TERM(E, M) \
+ esock_make_monitor_term((E), (M))
+
+
+/* ********************************************************************* *
+ * Counter type and related "things" *
+ * ********************************************************************* *
+ */
+
+#if ESOCK_COUNTER_SIZE == 16
+
+typedef Uint16 ESockCounter;
+#define ESOCK_COUNTER_MAX ((ESockCounter) 0xFFFF)
+#define MKCNT(ENV, CNT) MKUI((ENV), (CNT))
+#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKCNT((CNT)))
+#define ESOCK_COUNTER_FORMAT_STR "%u"
+
+#elif ESOCK_COUNTER_SIZE == 24
+
+typedef Uint32 ESockCounter;
+#define ESOCK_COUNTER_MAX ((ESockCounter) 0xFFFFFF)
+#define MKCNT(ENV, CNT) MKUI((ENV), (CNT))
+#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKCNT((ENV), (CNT)))
+#define ESOCK_COUNTER_FORMAT_STR "%lu"
+
+#elif ESOCK_COUNTER_SIZE == 32
+
+typedef Uint32 ESockCounter;
+#define ESOCK_COUNTER_MAX (~((ESockCounter) 0))
+#define MKCNT(ENV, CNT) MKUI((ENV), (CNT))
+#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKCNT((ENV), (CNT)))
+#define ESOCK_COUNTER_FORMAT_STR "%lu"
+
+#elif ESOCK_COUNTER_SIZE == 48
+
+typedef Uint64 ESockCounter;
+#define ESOCK_COUNTER_MAX ((ESockCounter) 0xFFFFFFFFFFFF)
+#define MKCNT(ENV, CNT) MKUI64((ENV), (CNT))
+#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKCNT((ENV), (CNT)))
+#define ESOCK_COUNTER_FORMAT_STR "%llu"
+
+#elif ESOCK_COUNTER_SIZE == 64
+
+typedef Uint64 ESockCounter;
+#define ESOCK_COUNTER_MAX (~((ESockCounter) 0))
+#define MKCNT(ENV, CNT) MKUI64((ENV), (CNT))
+#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKCNT((ENV), (CNT)))
+#define ESOCK_COUNTER_FORMAT_STR "%llu"
+
+#else
+
+#error "Invalid counter size"
+
+#endif
+
+#define ESOCK_CNT_INC( __E__, __D__, SF, ACNT, CNT, INC) \
+ do { \
+ if (esock_cnt_inc((CNT), (INC))) { \
+ esock_send_wrap_msg((__E__), (__D__), (SF), (ACNT)); \
+ } \
+ } while (0)
+
+
+
+/* ********************************************************************* *
+ * (Socket) Debug macros *
+ * ********************************************************************* *
+ */
+
+#define SSDBG( __D__ , proto ) ESOCK_DBG_PRINTF( (__D__)->dbg , proto )
+#define SSDBG2( __DBG__ , proto ) ESOCK_DBG_PRINTF( (__DBG__) , proto )
+
+
+/* ********************************************************************* *
+ * Sendfile stuff *
+ * ********************************************************************* *
+ */
+
+#if defined(HAVE_SENDFILE)
+
+typedef struct {
+ ESockCounter cnt; // Calls to OS sendfile()
+ ESockCounter byteCnt; // Bytes sent with sendfile
+ ESockCounter fails; // Failed sendfile operations
+ ESockCounter max; // Largest sendfile operation
+ ESockCounter maxCnt; // Counter for ="=
+ ESockCounter pkg; // Sendfile chunks
+ ESockCounter pkgMax; // Largest sendfile chunk
+ ESockCounter tries; // Started sendfile operations
+ ESockCounter waits; // Select's during sendfile
+} ESockSendfileCounters;
+
+#endif
+
+
+/* ********************************************************************* *
+ * Monitor wrapper type *
+ * ********************************************************************* *
+ */
+
+typedef struct {
+ ErlNifMonitor mon;
+ BOOLEAN_T isActive;
+} ESockMonitor;
+
+
+
+
+/* ********************************************************************* *
+ * The 'request' data structure *
+ * Each I/O request (write, read, accept, connect, ...) that cannot be *
+ * completed directly, is scheduled for later or asynchronous. *
+ * These requests are "pushed" into a request "queue". *
+ * In the case of the classic (unix) implementation, this is an actual *
+ * queue which also takes care of the order of the requests. *
+ * In case of the I/O Completion Port (windows), it is only used as an *
+ * database (we "push" into the list but we never "pop" from the list, *
+ * we search and delete). *
+ * ********************************************************************* *
+ */
+
+typedef struct {
+ ErlNifPid pid; // PID of the requesting process
+ ESockMonitor mon; // Monitor to the requesting process
+
+ /* We need an environment for the copy of the ref we store here.
+ * We will also use this environment for any messages we send
+ * (with the ref in it). Such as the select message (used in the
+ * select call) or the abort message.
+ */
+ ErlNifEnv* env;
+ ERL_NIF_TERM ref; // The (unique) reference (ID) of the request
+
+ /* The socket for which this request is made.
+ * A pointer to an *optional* data structure. This is intended for
+ * the 'overlapped' structure used by I/O Completion Port.
+ * A request scheduled by the I/O Completion Port can be 'cancelled'
+ * using the socket and the 'overlapped' data (provided when the
+ * operation was scheduled).
+ */
+ SOCKET sock;
+ void* dataP;
+
+} ESockRequestor;
+
+typedef struct esock_request_queue_element {
+ struct esock_request_queue_element* nextP;
+ ESockRequestor data;
+} ESockRequestQueueElement;
+
+typedef struct {
+ ESockRequestQueueElement* first;
+ ESockRequestQueueElement* last;
+} ESockRequestQueue;
+
+
+
+/* ********************************************************************* *
+ * Holding the socket level 'otp' option 'meta' term *
+ * ********************************************************************* *
+ */
+
+typedef struct{
+ ErlNifEnv* env;
+ ERL_NIF_TERM ref;
+} ESockMeta;
+
+
+
+/* ********************************************************************* *
+ * Control Message spec type *
+ * ********************************************************************* *
+ */
+
+typedef struct {
+ int type; // Message type
+
+ // Function to encode into erlang term
+ BOOLEAN_T (* encode)(ErlNifEnv* env,
+ unsigned char* data,
+ size_t dataLen,
+ ERL_NIF_TERM* eResult);
+
+ // Function to decode from erlang term
+ BOOLEAN_T (* decode)(ErlNifEnv* env,
+ ERL_NIF_TERM eValue,
+ struct cmsghdr* cmsgP,
+ size_t rem,
+ size_t* usedP);
+
+ ERL_NIF_TERM *nameP; // Pointer to option name atom
+} ESockCmsgSpec;
+
+
+/*----------------------------------------------------------------------------
+ * Interface types and constants.
+ *
+ * The set of elements should be the same as for the type
+ * msg_flag() in socket.erl.
+ */
+typedef struct {
+ int flag;
+ ERL_NIF_TERM* name;
+} ESockFlag;
+
+extern const ESockFlag esock_msg_flags[];
+extern const int esock_msg_flags_length;
+extern const ESockFlag esock_ioctl_flags[];
+extern const int esock_ioctl_flags_length;
+
+
+/* ********************************************************************* *
+ * The socket nif global info *
+ * ********************************************************************* *
+ */
+
+typedef struct {
+ /* These are for debugging, testing and the like */
+ // ERL_NIF_TERM version;
+ // ERL_NIF_TERM buildDate;
+
+ /* XXX Should be locked but too awkward and small gain */
+ BOOLEAN_T dbg;
+ BOOLEAN_T useReg;
+ BOOLEAN_T eei;
+
+ /* Registry stuff */
+ ErlNifPid regPid; /* Constant - not locked */
+
+ /* IOV_MAX. Constant - not locked */
+ int iov_max;
+
+ /* XXX
+ * Should be locked but too awkward for no gain since it is not used yet
+ */
+ BOOLEAN_T iow; // Where do we send this? Subscription?
+
+ ErlNifMutex* protocolsMtx;
+
+ ErlNifMutex* cntMtx; /* Locks the below */
+ /* Its extreme overkill to have these counters be 64-bit,
+ * but since the other counters are, it's much simpler to
+ * let these be 64-bit also.
+ */
+ ESockCounter numSockets;
+ ESockCounter numTypeStreams;
+ ESockCounter numTypeDGrams;
+ ESockCounter numTypeSeqPkgs;
+ ESockCounter numDomainInet;
+ ESockCounter numDomainInet6;
+ ESockCounter numDomainLocal;
+ ESockCounter numProtoIP;
+ ESockCounter numProtoTCP;
+ ESockCounter numProtoUDP;
+ ESockCounter numProtoSCTP;
+ //
+ BOOLEAN_T sockDbg;
+} ESockData;
+
+
+
+/* ********************************************************************* *
+ * The socket descriptor *
+ * ********************************************************************* *
+ */
+
+typedef struct {
+ /*
+ * +++ This is a way to, possibly, detect memory overrides "and stuff" +++
+ *
+ * We have two patterns. One is set when the descriptor is created
+ * (allocated) and one is set when the descriptor is dtor'ed.
+ */
+ Uint32 pattern;
+
+ /* +++ Stuff "about" the socket +++ */
+
+ /* "Constant" - set when socket is created and never changed */
+ int domain;
+ int type;
+ int protocol;
+
+ /* The state is partly for debugging, decisions are made often
+ * based on other variables. The state is divided in
+ * a readState half and a writeState half that can be
+ * OR:ed together to create the complete state.
+ * The halves are locked by their corresponding lock.
+ */
+
+ /* +++ Write stuff +++ */
+ ErlNifMutex* writeMtx;
+ /**/
+ unsigned int writeState; // For debugging
+#ifndef __WIN32__
+ /*
+ * On *none* Windows:
+ * This is intended for the *current* writer.
+ * The queue is intended for *waiting* writers.
+ *
+ * *On* Windows:
+ * We let the I/O Completion Ports handle the queue'ing
+ * so we do not need to keep track which request is active
+ * and which are waiting.
+ * We only use the *queue* as a database.
+ */
+ ESockRequestor currentWriter;
+ ESockRequestor* currentWriterP; // NULL or &currentWriter
+#endif
+ ESockRequestQueue writersQ;
+ ESockCounter writePkgCnt;
+ ESockCounter writePkgMax;
+ ESockCounter writePkgMaxCnt;
+ ESockCounter writeByteCnt;
+ ESockCounter writeTries;
+ ESockCounter writeWaits;
+ ESockCounter writeFails;
+#ifdef HAVE_SENDFILE
+ HANDLE sendfileHandle;
+ ESockSendfileCounters* sendfileCountersP;
+#endif
+
+ /* +++ Connector +++ */
+ ESockRequestor connector;
+ ESockRequestor* connectorP; // NULL or &connector
+ /* +++ Config stuff +++ */
+ size_t wCtrlSz; // Write control buffer size
+ ESockMeta meta; // Level 'otp' option 'meta' term
+
+ /* +++ Read stuff +++ */
+ ErlNifMutex* readMtx;
+ /**/
+ unsigned int readState; // For debugging
+#ifndef __WIN32__
+ /*
+ * On *none* Windows:
+ * This is intended for the *current* reader.
+ * The queue is intended for *waiting* readers.
+ *
+ * *On* Windows:
+ * We let the I/O Completion Ports handle the queue'ing
+ * so we do not need to keep track which request is active
+ * and which are waiting.
+ * We only use the *queue* as a database.
+ */
+ ESockRequestor currentReader;
+ ESockRequestor* currentReaderP; // NULL or &currentReader
+#endif
+ ESockRequestQueue readersQ;
+ ErlNifBinary rbuffer; // DO WE NEED THIS
+ Uint32 readCapacity; // DO WE NEED THIS
+ ESockCounter readPkgCnt;
+ ESockCounter readPkgMax;
+ ESockCounter readPkgMaxCnt;
+ ESockCounter readByteCnt;
+ ESockCounter readTries;
+ ESockCounter readWaits;
+ ESockCounter readFails;
+
+ /* +++ Accept stuff +++ */
+#ifndef __WIN32__
+ /*
+ * On *none* Windows:
+ * This is intended for the *current* acceptor.
+ * The queue is intended for *waiting* acceptors.
+ *
+ * *On* Windows:
+ * We let the I/O Completion Ports handle the queue'ing
+ * so we do not need to keep track which request is active
+ * and which are waiting.
+ * We only use the *queue* as a database.
+ */
+ ESockRequestor currentAcceptor;
+ ESockRequestor* currentAcceptorP; // NULL or &currentAcceptor
+#endif
+ ESockRequestQueue acceptorsQ;
+ ESockCounter accSuccess;
+ ESockCounter accTries;
+ ESockCounter accWaits;
+ ESockCounter accFails;
+ /* +++ Config stuff +++ */
+ size_t rBufSz; // Read buffer size (when data length = 0)
+ /* rNum and rNumCnt are used (together with rBufSz) when calling the recv
+ * function with the Length argument set to 0 (zero).
+ * If rNum is 0 (zero), then rNumCnt is not used and only *one* read will
+ * be done. Also, when get'ing the value of the option (rcvbuf) with
+ * getopt, the value will be reported as an integer. If the rNum has a
+ * value greater then 0 (zero), then it will instead be reported as
+ * {N, BufSz}.
+ * On Windows, rNum and rNumCnt is *not* used!
+ */
+#ifndef __WIN32__
+ unsigned int rNum; // recv: Number of reads using rBufSz
+ unsigned int rNumCnt; // recv: Current number of reads (so far)
+#endif
+ size_t rCtrlSz; // Read control buffer size
+
+ /* Locked by readMtx and writeMtx combined for writing,
+ * which means only one of them is required for reading
+ */
+ /* +++ Close stuff +++ */
+ ErlNifPid closerPid;
+ ESockMonitor closerMon;
+ ErlNifEnv* closeEnv;
+ ERL_NIF_TERM closeRef;
+ /* +++ Inform On (counter) Wrap +++ */
+ BOOLEAN_T iow;
+ /* +++ Controller (owner) process +++ */
+ ErlNifPid ctrlPid;
+ ESockMonitor ctrlMon;
+ /* +++ The actual socket +++ */
+ SOCKET sock;
+ SOCKET origFD; // A 'socket' created from this FD
+ BOOLEAN_T closeOnClose; // Have we dup'ed or not
+ /* +++ The dbg flag for SSDBG +++ */
+ BOOLEAN_T dbg;
+ BOOLEAN_T useReg;
+
+ /* Lock order: readMtx, writeMtx, cntMtx
+ */
+
+#if defined(ESOCK_DESCRIPTOR_FILLER)
+ char filler[1024];
+#endif
+
+} ESockDescriptor;
+
+
+
+/* ======================================================================== *
+ * What to do about this? *
+ * ======================================================================== *
+ */
+
+extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */
+
+
+/* ======================================================================== *
+ * Functions *
+ * ======================================================================== *
+ */
+
+extern ESockDescriptor* esock_alloc_descriptor(SOCKET sock);
+extern void esock_dealloc_descriptor(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+extern BOOLEAN_T esock_open_is_debug(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ BOOLEAN_T def);
+extern BOOLEAN_T esock_open_use_registry(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ BOOLEAN_T def);
+extern BOOLEAN_T esock_open_which_protocol(SOCKET sock, int* proto);
+
+extern BOOLEAN_T esock_getopt_int(SOCKET sock,
+ int level,
+ int opt,
+ int* valP);
+
+
+/* ** Socket Registry functions *** */
+extern void esock_send_reg_add_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef);
+extern void esock_send_reg_del_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef);
+
+
+/* *** Message sending functions *** */
+extern void esock_send_simple_abort_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* pid,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason);
+extern void esock_send_abort_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ESockRequestor* reqP,
+ ERL_NIF_TERM reason);
+extern void esock_send_close_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* pid);
+extern void esock_send_wrap_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM cnt);
+
+
+/* ** Monitor functions *** */
+extern int esock_monitor(const char* slogan,
+ ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pid,
+ ESockMonitor* mon);
+extern int esock_demonitor(const char* slogan,
+ ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESockMonitor* monP);
+extern void esock_monitor_init(ESockMonitor* mon);
+extern ERL_NIF_TERM esock_make_monitor_term(ErlNifEnv* env,
+ const ESockMonitor* monP);
+extern BOOLEAN_T esock_monitor_eq(const ESockMonitor* monP,
+ const ErlNifMonitor* mon);
+
+
+/* *** Counter functions *** */
+extern BOOLEAN_T esock_cnt_inc(ESockCounter* cnt, ESockCounter inc);
+extern void esock_cnt_dec(ESockCounter* cnt, ESockCounter dec);
+extern void esock_inc_socket(int domain, int type, int protocol);
+extern void esock_dec_socket(int domain, int type, int protocol);
+
+
+/* *** Select functions *** */
+extern int esock_select_read(ErlNifEnv* env,
+ ErlNifEvent event,
+ void* obj,
+ const ErlNifPid* pidP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM selectRef);
+extern int esock_select_write(ErlNifEnv* env,
+ ErlNifEvent event,
+ void* obj,
+ const ErlNifPid* pidP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM selectRef);
+extern int esock_select_stop(ErlNifEnv* env,
+ ErlNifEvent event,
+ void* obj);
+extern int esock_select_cancel(ErlNifEnv* env,
+ ErlNifEvent event,
+ enum ErlNifSelectFlags mode,
+ void* obj);
+extern ERL_NIF_TERM esock_cancel_write_select(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef);
+extern ERL_NIF_TERM esock_cancel_read_select(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef);
+extern ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef,
+ int smode,
+ int rmode);
+
+
+/* *** Request queue functions *** */
+extern void esock_free_request_queue(ESockRequestQueue* q);
+extern BOOLEAN_T esock_requestor_pop(ESockRequestQueue* q,
+ ESockRequestor* reqP);
+
+extern void esock_requestor_init(ESockRequestor* reqP);
+extern void esock_requestor_release(const char* slogan,
+ ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESockRequestor* reqP);
+
+
+/* *** esock_activate_next_acceptor ***
+ * *** esock_activate_next_writer ***
+ * *** esock_activate_next_reader ***
+ *
+ * All the activate-next functions for acceptor, writer and reader
+ * have exactly the same API, so we apply some macro magic to simplify.
+ * They simply operates on dufferent data structures.
+ *
+ */
+
+#define ACTIVATE_NEXT_FUNCS_DEFS \
+ ACTIVATE_NEXT_FUNC_DEF(acceptor) \
+ ACTIVATE_NEXT_FUNC_DEF(writer) \
+ ACTIVATE_NEXT_FUNC_DEF(reader)
+
+#define ACTIVATE_NEXT_FUNC_DEF(F) \
+ extern BOOLEAN_T esock_activate_next_##F(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM sockRef);
+ACTIVATE_NEXT_FUNCS_DEFS
+#undef ACTIVATE_NEXT_FUNC_DEF
+
+/* esock_acceptor_search4pid | esock_writer_search4pid | esock_reader_search4pid
+ * esock_acceptor_push | esock_writer_push | esock_reader_push
+ * esock_acceptor_pop | esock_writer_pop | esock_reader_pop
+ * esock_acceptor_unqueue | esock_writer_unqueue | esock_reader_unqueue
+ *
+ * All the queue operator functions (search4pid, push, pop
+ * and unqueue) for acceptor, writer and reader has exactly
+ * the same API, so we apply some macro magic to simplify.
+ */
+
+#define ESOCK_OPERATOR_FUNCS_DEFS \
+ ESOCK_OPERATOR_FUNCS_DEF(acceptor) \
+ ESOCK_OPERATOR_FUNCS_DEF(writer) \
+ ESOCK_OPERATOR_FUNCS_DEF(reader)
+
+#define ESOCK_OPERATOR_FUNCS_DEF(O) \
+ extern BOOLEAN_T esock_##O##_search4pid(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ErlNifPid* pid); \
+ extern void esock_##O##_push(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ErlNifPid pid, \
+ ERL_NIF_TERM ref, \
+ void* dataP); \
+ extern BOOLEAN_T esock_##O##_pop(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ESockRequestor* reqP); \
+ extern BOOLEAN_T esock_##O##_unqueue(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM* refP, \
+ const ErlNifPid* pidP);
+ESOCK_OPERATOR_FUNCS_DEFS
+#undef ESOCK_OPERATOR_FUNCS_DEF
+
+
+/* *** Environment wrapper functions ***
+ * These hould really be inline, but for now...
+ */
+extern void esock_clear_env(const char* slogan, ErlNifEnv* env);
+extern void esock_free_env(const char* slogan, ErlNifEnv* env);
+extern ErlNifEnv* esock_alloc_env(const char* slogan);
+
+
+/* *** Control Message utility functions ***
+ */
+#ifndef __WIN32__
+extern void* esock_init_cmsghdr(struct cmsghdr* cmsgP,
+ size_t rem,
+ size_t size,
+ size_t* usedP);
+#if defined(IP_TTL) || \
+ defined(IPV6_HOPLIMIT) || \
+ defined(IPV6_TCLASS) || defined(IPV6_RECVTCLASS)
+extern BOOLEAN_T esock_cmsg_decode_int(ErlNifEnv* env,
+ ERL_NIF_TERM eValue,
+ struct cmsghdr* cmsgP,
+ size_t rem,
+ size_t* usedP);
+#endif
+extern BOOLEAN_T esock_cmsg_decode_bool(ErlNifEnv* env,
+ ERL_NIF_TERM eValue,
+ struct cmsghdr* cmsgP,
+ size_t rem,
+ size_t* usedP);
+extern ESockCmsgSpec* esock_lookup_cmsg_table(int level, size_t *num);
+extern ESockCmsgSpec* esock_lookup_cmsg_spec(ESockCmsgSpec* table,
+ size_t num,
+ ERL_NIF_TERM eType);
+
+extern BOOLEAN_T esock_encode_cmsg(ErlNifEnv* env,
+ int level,
+ int type,
+ unsigned char* dataP,
+ size_t dataLen,
+ ERL_NIF_TERM* eType,
+ ERL_NIF_TERM* eData);
+extern void esock_encode_msg_flags(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int msgFlags,
+ ERL_NIF_TERM* flags);
+#endif
+
+
+extern void esock_stop_handle_current(ErlNifEnv* env,
+ const char* role,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ESockRequestor* reqP);
+extern void esock_inform_waiting_procs(ErlNifEnv* env,
+ const char* role,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ESockRequestQueue* q,
+ ERL_NIF_TERM reason);
+
+
+/* *** Control Message 'stuff' ***
+ */
+extern void* esock_init_cmsghdr(struct cmsghdr* cmsgP,
+ size_t rem, // Remaining space
+ size_t size, // Size of data
+ size_t* usedP);
+extern ESockCmsgSpec* esock_lookup_cmsg_table(int level, size_t *num);
+extern ESockCmsgSpec* esock_lookup_cmsg_spec(ESockCmsgSpec* table,
+ size_t num,
+ ERL_NIF_TERM eType);
+
+/* *** Sendfile 'stuff' ***
+ */
+#ifdef HAVE_SENDFILE
+
+extern ESockSendfileCounters initESockSendfileCounters;
+
+#endif
+
+/* *** message functions ****
+ */
+extern void esock_send_wrap_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM cnt);
+extern BOOLEAN_T esock_send_msg(ErlNifEnv* env,
+ ErlNifPid* pid,
+ ERL_NIF_TERM msg,
+ ErlNifEnv* msgEnv);
+extern ERL_NIF_TERM esock_mk_socket_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM tag,
+ ERL_NIF_TERM info);
+extern ERL_NIF_TERM esock_mk_socket(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef);
+#ifdef HAVE_SENDFILE
+extern void esock_send_sendfile_deferred_close_msg(ErlNifEnv* env,
+ ESockDescriptor* descP);
+#endif
+
+
+/* *** 'close' functions ***
+ */
+extern int esock_close_socket(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ BOOLEAN_T unlock);
+
+#endif // PRIM_SOCKET_INT_H__
diff --git a/erts/emulator/nifs/common/prim_socket_nif.c b/erts/emulator/nifs/common/prim_socket_nif.c
index 4ac0fc0966..9d6755387d 100644
--- a/erts/emulator/nifs/common/prim_socket_nif.c
+++ b/erts/emulator/nifs/common/prim_socket_nif.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2018-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2018-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,12 +24,13 @@
* The first function is called 'nif_<something>', e.g. nif_open.
* This does the initial validation and argument processing and then
* calls the function that does the actual work. This is called
- * 'esock_<something>', e.g. esock_open (actually esock_open2 or
- * esock_open4).
+ * '<io-backend>_<something>', e.g. essio_open (actually
+ * essio_open_with_fd or essio_open_plain).
* ----------------------------------------------------------------------
*
*
- * This is just a code snippet in case there is need of extra debugging
+ * This is just a code snippet example in case there is need of
+ * extra debugging:
*
* esock_dbg_printf("DEMONP", "[%d] %s: %T\r\n",
* descP->sock, slogan,
@@ -124,6 +125,10 @@ ERL_NIF_INIT(prim_socket, esock_funcs, on_load, NULL, NULL, NULL)
* *
* vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */
+#define ESOCK_CMSG_SPACE(l) WSA_CMSG_SPACE((l))
+#define ESOCK_CMSG_LEN(l) WSA_CMSG_LEN((l))
+#define ESOCK_CMSG_DATA(p) WSA_CMSG_DATA((p))
+
#define STRNCASECMP strncasecmp
#define INCL_WINSOCK_API_TYPEDEFS 1
@@ -166,6 +171,10 @@ ERL_NIF_INIT(prim_socket, esock_funcs, on_load, NULL, NULL, NULL)
* *
* vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */
+#define ESOCK_CMSG_SPACE(l) CMSG_SPACE((l))
+#define ESOCK_CMSG_LEN(l) CMSG_LEN((l))
+#define ESOCK_CMSG_DATA(p) CMSG_DATA((p))
+
#include <sys/time.h>
#ifdef NETDB_H_NEEDS_IN_H
@@ -363,12 +372,12 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL;
#endif /* #if defined(HAVE_SCTP_H) */
-
#ifndef WANT_NONBLOCKING
#define WANT_NONBLOCKING
#endif
#include "sys.h"
+
/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *
* *
* End of non-__WIN32__ section a.k.a UNIX section *
@@ -384,6 +393,10 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL;
#include "socket_tarray.h"
#include "socket_int.h"
#include "socket_util.h"
+#include "prim_socket_int.h"
+#include "socket_io.h"
+#include "socket_asyncio.h"
+#include "socket_syncio.h"
#include "prim_file_nif_dyncall.h"
#if defined(ERTS_INLINE)
@@ -411,41 +424,6 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL;
#define ESOCK_NIF_IOW_DEFAULT FALSE
-#ifdef __WIN32__
-
-//#define INVALID_HANDLE from Windows header file
-//typedef void *HANDLE from Windows header file
-//#define INVALID_SOCKET from Windows header file
-//typedef void *SOCKET from Windows header file
-#define INVALID_EVENT NULL
-
-#else
-
-#define INVALID_HANDLE (-1)
-typedef int HANDLE;
-#define INVALID_SOCKET (-1)
-typedef int SOCKET; /* A subset of HANDLE */
-#define INVALID_EVENT INVALID_HANDLE
-
-#endif
-
-
-/* ==============================================================================
- * The ESOCK_IS_ERROR macro below is used for portability reasons.
- * While POSIX specifies that errors from socket-related system calls
- * should be indicated with a -1 return value, some users have experienced
- * non-Windows OS kernels that return negative values other than -1.
- * While one can argue that such kernels are technically broken, comparing
- * against values less than 0 covers their out-of-spec return values without
- * imposing incorrect semantics on systems that manage to correctly return -1
- * for errors, thus increasing Erlang's portability.
- */
-#ifdef __WIN32__
-#define ESOCK_IS_ERROR(val) ((val) == INVALID_SOCKET)
-#else
-#define ESOCK_IS_ERROR(val) ((val) < 0)
-#endif
-
/* *** Misc macros and defines *** */
@@ -473,73 +451,11 @@ typedef int SOCKET; /* A subset of HANDLE */
-/* *** Socket state defs *** */
-
-#define ESOCK_STATE_BOUND 0x0001 /* readState */
-#define ESOCK_STATE_LISTENING 0x0002 /* readState */
-#define ESOCK_STATE_ACCEPTING 0x0004 /* readState */
-#define ESOCK_STATE_CONNECTING 0x0010 /* writeState */
-#define ESOCK_STATE_CONNECTED 0x0020 /* writeState */
-
-/* This is set in either readState or writeState
- * so it has to be read from both.
- * Means that the socket has been used in select,
- * so select_stop is required. */
-#define ESOCK_STATE_SELECTED 0x0100 /* readState or writeState */
-
-/* These are set in both readState and writeState
- * so they can be read from either. */
-#define ESOCK_STATE_CLOSING 0x0200 /* readState and writeState */
-
-#define ESOCK_STATE_CLOSED 0x0400 /* readState and writeState */
-//
-#define ESOCK_STATE_DTOR 0x8000
-
-#define IS_CLOSED(st) \
- (((st) & ESOCK_STATE_CLOSED) != 0)
-
-#define IS_CLOSING(st) \
- (((st) & ESOCK_STATE_CLOSING) != 0)
-
-#define IS_OPEN(st) \
- (((st) & (ESOCK_STATE_CLOSED | ESOCK_STATE_CLOSING)) == 0)
-
-#define IS_SELECTED(d) \
- ((((d)->readState | (d)->writeState) & ESOCK_STATE_SELECTED) != 0)
-
-
-#define ESOCK_GET_RESOURCE(ENV, REF, RES) \
- enif_get_resource((ENV), (REF), esocks, (RES))
-
-#define ESOCK_MON2TERM(E, M) \
- esock_make_monitor_term((E), (M))
-
#define ESOCK_RECV_BUFFER_COUNT_DEFAULT 0
#define ESOCK_RECV_BUFFER_SIZE_DEFAULT 8192
#define ESOCK_RECV_CTRL_BUFFER_SIZE_DEFAULT 1024
#define ESOCK_SEND_CTRL_BUFFER_SIZE_DEFAULT 1024
-#define ESOCK_DESC_PATTERN_CREATED 0x03030303
-#define ESOCK_DESC_PATTERN_DTOR 0xC0C0C0C0
-
-/*
-typedef union {
- struct {
- // 0 = not open, 1 = open
- unsigned int open:1;
- // 0 = not conn, 1 = connecting, 2 = connected
- unsigned int connect:2;
- // unsigned int connecting:1;
- // unsigned int connected:1;
- // 0 = not listen, 1 = listening, 2 = accepting
- unsigned int listen:2;
- // unsigned int listening:1;
- // unsigned int accepting:1;
- / * Room for more... * /
- } flags;
- unsigned int field; // Make it easy to reset all flags...
-} SocketState;
-*/
/*----------------------------------------------------------------------------
* Interface constants.
@@ -548,11 +464,7 @@ typedef union {
* msg_flag() in socket.erl.
*/
-static const struct msg_flag {
- int flag;
- ERL_NIF_TERM *name;
-} msg_flags[] = {
-
+const ESockFlag esock_msg_flags[] = {
{
#ifdef MSG_CMSG_CLOEXEC
MSG_CMSG_CLOEXEC,
@@ -641,13 +553,9 @@ static const struct msg_flag {
#endif
&esock_atom_trunc}
};
+const int esock_msg_flags_length = NUM(esock_msg_flags);
-
-static const struct ioctl_flag {
- int flag;
- ERL_NIF_TERM *name;
-} ioctl_flags[] = {
-
+const ESockFlag esock_ioctl_flags[] = {
{
#ifdef IFF_UP
IFF_UP,
@@ -917,6 +825,7 @@ static const struct ioctl_flag {
#endif
&esock_atom_nogroup}
};
+const int esock_ioctl_flags_length = NUM(esock_ioctl_flags);
@@ -955,16 +864,6 @@ static const struct ioctl_flag {
/* Global socket debug */
#define SGDBG( proto ) ESOCK_DBG_PRINTF( data.dbg , proto )
-/* Socket specific debug */
-#define SSDBG( __D__ , proto ) ESOCK_DBG_PRINTF( (__D__)->dbg , proto )
-#define SSDBG2( __DBG__ , proto ) ESOCK_DBG_PRINTF( (__DBG__) , proto )
-
-#define ESOCK_CNT_INC( __E__, __D__, SF, ACNT, CNT, INC) \
- do { \
- if (cnt_inc((CNT), (INC))) { \
- esock_send_wrap_msg((__E__), (__D__), (SF), (ACNT)); \
- } \
- } while (0)
/* =================================================================== *
@@ -982,29 +881,29 @@ static const struct ioctl_flag {
/* *** Windows macros *** */
-#define sock_accept(s, addr, len) \
- make_noninheritable_handle(accept((s), (addr), (len)))
-#define sock_bind(s, addr, len) bind((s), (addr), (len))
+/* #define sock_accept(s, addr, len) \
+ make_noninheritable_handle(accept((s), (addr), (len))) */
+// #define sock_bind(s, addr, len) bind((s), (addr), (len))
#define sock_close(s) closesocket((s))
-#define sock_close_event(e) WSACloseEvent(e)
-#define sock_connect(s, addr, len) connect((s), (addr), (len))
-#define sock_create_event(s) WSACreateEvent()
+// #define sock_close_event(e) WSACloseEvent(e)
+// #define sock_connect(s, addr, len) connect((s), (addr), (len))
+// #define sock_create_event(s) WSACreateEvent()
#define sock_errno() WSAGetLastError()
#define sock_getopt(s,l,o,v,ln) getsockopt((s),(l),(o),(v),(ln))
-#define sock_htons(x) htons((x))
-#define sock_htonl(x) htonl((x))
+// #define sock_htons(x) htons((x))
+// #define sock_htonl(x) htonl((x))
#define sock_listen(s, b) listen((s), (b))
#define sock_name(s, addr, len) getsockname((s), (addr), (len))
-#define sock_ntohs(x) ntohs((x))
-#define sock_open(domain, type, proto) \
- make_noninheritable_handle(socket((domain), (type), (proto)))
+// #define sock_ntohs(x) ntohs((x))
+/* #define sock_open(domain, type, proto) \
+ make_noninheritable_handle(socket((domain), (type), (proto))) */
#define sock_peer(s, addr, len) getpeername((s), (addr), (len))
-#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag))
+// #define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag))
#define sock_recvfrom(s,buf,blen,flag,addr,alen) \
recvfrom((s),(buf),(blen),(flag),(addr),(alen))
-#define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag))
-#define sock_sendto(s,buf,blen,flag,addr,alen) \
- sendto((s),(buf),(blen),(flag),(addr),(alen))
+/* #define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) */
+/* #define sock_sendto(s,buf,blen,flag,addr,alen) \
+ sendto((s),(buf),(blen),(flag),(addr),(alen)) */
#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln))
#define sock_shutdown(s, how) shutdown((s), (how))
@@ -1028,34 +927,34 @@ static unsigned long one_value = 1;
* vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */
-#ifdef HAS_ACCEPT4
+// #ifdef HAS_ACCEPT4
// We have to figure out what the flags are...
-#define sock_accept(s, addr, len) accept4((s), (addr), (len), (SOCK_CLOEXEC))
-#else
-#define sock_accept(s, addr, len) accept((s), (addr), (len))
-#endif
-#define sock_bind(s, addr, len) bind((s), (addr), (len))
+// #define sock_accept(s, addr, len) accept4((s), (addr), (len), (SOCK_CLOEXEC))
+// #else
+// #define sock_accept(s, addr, len) accept((s), (addr), (len))
+// #endif
+// #define sock_bind(s, addr, len) bind((s), (addr), (len))
#define sock_close(s) close((s))
-#define sock_close_event(e) /* do nothing */
-#define sock_connect(s, addr, len) connect((s), (addr), (len))
-#define sock_create_event(s) (s) /* return file descriptor */
+// #define sock_close_event(e) /* do nothing */
+// #define sock_connect(s, addr, len) connect((s), (addr), (len))
+// #define sock_create_event(s) (s) /* return file descriptor */
#define sock_errno() errno
#define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l))
-#define sock_htons(x) htons((x))
-#define sock_htonl(x) htonl((x))
+// #define sock_htons(x) htons((x))
+// #define sock_htonl(x) htonl((x))
#define sock_listen(s, b) listen((s), (b))
#define sock_name(s, addr, len) getsockname((s), (addr), (len))
-#define sock_ntohs(x) ntohs((x))
-#define sock_open(domain, type, proto) socket((domain), (type), (proto))
+// #define sock_ntohs(x) ntohs((x))
+// #define sock_open(domain, type, proto) socket((domain), (type), (proto))
#define sock_peer(s, addr, len) getpeername((s), (addr), (len))
-#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag))
-#define sock_recvfrom(s,buf,blen,flag,addr,alen) \
- recvfrom((s),(buf),(blen),(flag),(addr),(alen))
-#define sock_recvmsg(s,msghdr,flag) recvmsg((s),(msghdr),(flag))
-#define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag))
-#define sock_sendmsg(s,msghdr,flag) sendmsg((s),(msghdr),(flag))
-#define sock_sendto(s,buf,blen,flag,addr,alen) \
- sendto((s),(buf),(blen),(flag),(addr),(alen))
+// #define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag))
+/* #define sock_recvfrom(s,buf,blen,flag,addr,alen) \ */
+/* recvfrom((s),(buf),(blen),(flag),(addr),(alen)) */
+// #define sock_recvmsg(s,msghdr,flag) recvmsg((s),(msghdr),(flag))
+// #define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag))
+// #define sock_sendmsg(s,msghdr,flag) sendmsg((s),(msghdr),(flag))
+/* #define sock_sendto(s,buf,blen,flag,addr,alen) \ */
+/* sendto((s),(buf),(blen),(flag),(addr),(alen)) */
#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln))
#define sock_shutdown(s, how) shutdown((s), (how))
@@ -1068,284 +967,20 @@ static unsigned long one_value = 1;
#endif /* #ifdef __WIN32__ #else */
-/* We can use the IPv4 def for this since the beginning
- * is the same for INET and INET6 */
-#define which_address_port(sap) \
- ((((sap)->in4.sin_family == AF_INET) || \
- ((sap)->in4.sin_family == AF_INET6)) ? \
- ((sap)->in4.sin_port) : -1)
-
-
-typedef struct {
- ErlNifMonitor mon;
- BOOLEAN_T isActive;
-} ESockMonitor;
-
-typedef struct {
- ErlNifPid pid; // PID of the requesting process
- ESockMonitor mon; // Monitor to the requesting process
-
- /* We need an environment for the copy of the ref we store here.
- * We will also use this environment for any messages we send
- * (with the ref in it). Such as the select message (used in the
- * select call) or the abort message.
- */
- ErlNifEnv* env;
- ERL_NIF_TERM ref; // The (unique) reference (ID) of the request
-} ESockRequestor;
-
-typedef struct{
- // Holding the socket level 'otp' option 'meta' term
- ErlNifEnv* env;
- ERL_NIF_TERM ref;
-} ESockMeta;
-
-typedef struct esock_request_queue_element {
- struct esock_request_queue_element* nextP;
- ESockRequestor data;
-} ESockRequestQueueElement;
-
-typedef struct {
- ESockRequestQueueElement* first;
- ESockRequestQueueElement* last;
-} ESockRequestQueue;
-
-
-/*** The point of this is primarily testing ***/
-/*
-#if defined(ESOCK_COUNTER_SIZE)
-#undef ESOCK_COUNTER_SIZE
-// #define ESOCK_COUNTER_SIZE 16
-// #define ESOCK_COUNTER_SIZE 24
-// #define ESOCK_COUNTER_SIZE 32
-// #define ESOCK_COUNTER_SIZE 48
-// #define ESOCK_COUNTER_SIZE 64
-
-#endif
-*/
-
-#if ESOCK_COUNTER_SIZE == 16
-
-typedef Uint16 ESockCounter;
-#define ESOCK_COUNTER_MAX ((ESockCounter) 0xFFFF)
-#define MKCNT(ENV, CNT) MKUI((ENV), (CNT))
-#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKCNT((CNT)))
-#define ESOCK_COUNTER_FORMAT_STR "%u"
-
-#elif ESOCK_COUNTER_SIZE == 24
-
-typedef Uint32 ESockCounter;
-#define ESOCK_COUNTER_MAX ((ESockCounter) 0xFFFFFF)
-#define MKCNT(ENV, CNT) MKUI((ENV), (CNT))
-#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKCNT((ENV), (CNT)))
-#define ESOCK_COUNTER_FORMAT_STR "%lu"
-
-#elif ESOCK_COUNTER_SIZE == 32
-
-typedef Uint32 ESockCounter;
-#define ESOCK_COUNTER_MAX (~((ESockCounter) 0))
-#define MKCNT(ENV, CNT) MKUI((ENV), (CNT))
-#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKCNT((ENV), (CNT)))
-#define ESOCK_COUNTER_FORMAT_STR "%lu"
-
-#elif ESOCK_COUNTER_SIZE == 48
-
-typedef Uint64 ESockCounter;
-#define ESOCK_COUNTER_MAX ((ESockCounter) 0xFFFFFFFFFFFF)
-#define MKCNT(ENV, CNT) MKUI64((ENV), (CNT))
-#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKCNT((ENV), (CNT)))
-#define ESOCK_COUNTER_FORMAT_STR "%llu"
-
-#elif ESOCK_COUNTER_SIZE == 64
-
-typedef Uint64 ESockCounter;
-#define ESOCK_COUNTER_MAX (~((ESockCounter) 0))
-#define MKCNT(ENV, CNT) MKUI64((ENV), (CNT))
-#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKCNT((ENV), (CNT)))
-#define ESOCK_COUNTER_FORMAT_STR "%llu"
-
-#else
-
-#error "Invalid counter size"
-
-#endif
-
-// static const ESockCounter esock_counter_max = ESOCK_COUNTER_MAX;
-
#ifdef HAVE_SENDFILE
-typedef struct {
- ESockCounter cnt; // Calls to OS sendfile()
- ESockCounter byteCnt; // Bytes sent with sendfile
- ESockCounter fails; // Failed sendfile operations
- ESockCounter max; // Largest sendfile operation
- ESockCounter maxCnt; // Counter for ="=
- ESockCounter pkg; // Sendfile chunks
- ESockCounter pkgMax; // Largest sendfile chunk
- ESockCounter tries; // Started sendfile operations
- ESockCounter waits; // Select's during sendfile
-} ESockSendfileCounters;
-static ESockSendfileCounters initESockSendfileCounters =
+ESockSendfileCounters initESockSendfileCounters =
{0, 0, 0, 0, 0, 0, 0, 0, 0};
#endif
-typedef struct {
- /*
- * +++ This is a way to, possibly, detect memory overrides "and stuff" +++
- *
- * We have two patterns. One is set when the descriptor is created
- * (allocated) and one is set when the descriptor is dtor'ed.
- */
- Uint32 pattern;
-
- /* +++ Stuff "about" the socket +++ */
-
- /* "Constant" - set when socket is created and never changed */
- int domain;
- int type;
- int protocol;
-
- /* The state is partly for debugging, decisions are made often
- * based on other variables. The state is divided in
- * a readState half and a writeState half that can be
- * OR:ed together to create the complete state.
- * The halves are locked by their corresponding lock.
- */
-
- /* +++ Write stuff +++ */
- ErlNifMutex* writeMtx;
- /**/
- unsigned int writeState; // For debugging
- ESockRequestor currentWriter;
- ESockRequestor* currentWriterP; // NULL or &currentWriter
- ESockRequestQueue writersQ;
- ESockCounter writePkgCnt;
- ESockCounter writePkgMax;
- ESockCounter writePkgMaxCnt;
- ESockCounter writeByteCnt;
- ESockCounter writeTries;
- ESockCounter writeWaits;
- ESockCounter writeFails;
-#ifdef HAVE_SENDFILE
- HANDLE sendfileHandle;
- ESockSendfileCounters* sendfileCountersP;
-#endif
- /* +++ Connector +++ */
- ESockRequestor connector;
- ESockRequestor* connectorP; // NULL or &connector
- /* +++ Config stuff +++ */
- size_t wCtrlSz; // Write control buffer size
- ESockMeta meta; // Level 'otp' option 'meta' term
-
- /* +++ Read stuff +++ */
- ErlNifMutex* readMtx;
- /**/
- unsigned int readState; // For debugging
- ESockRequestor currentReader;
- ESockRequestor* currentReaderP; // NULL or &currentReader
- ESockRequestQueue readersQ;
- ErlNifBinary rbuffer; // DO WE NEED THIS
- Uint32 readCapacity; // DO WE NEED THIS
- ESockCounter readPkgCnt;
- ESockCounter readPkgMax;
- ESockCounter readPkgMaxCnt;
- ESockCounter readByteCnt;
- ESockCounter readTries;
- ESockCounter readWaits;
- ESockCounter readFails;
- /* +++ Accept stuff +++ */
- ESockRequestor currentAcceptor;
- ESockRequestor* currentAcceptorP; // NULL or &currentAcceptor
- ESockRequestQueue acceptorsQ;
- ESockCounter accSuccess;
- ESockCounter accTries;
- ESockCounter accWaits;
- ESockCounter accFails;
- /* +++ Config stuff +++ */
- size_t rBufSz; // Read buffer size (when data length = 0)
- /* rNum and rNumCnt are used (together with rBufSz) when calling the recv
- * function with the Length argument set to 0 (zero).
- * If rNum is 0 (zero), then rNumCnt is not used and only *one* read will
- * be done. Also, when get'ing the value of the option (rcvbuf) with
- * getopt, the value will be reported as an integer. If the rNum has a
- * value greater then 0 (zero), then it will instead be reported as {N, BufSz}.
- */
- unsigned int rNum; // recv: Number of reads using rBufSz
- unsigned int rNumCnt; // recv: Current number of reads (so far)
- size_t rCtrlSz; // Read control buffer size
-
- /* Locked by readMtx and writeMtx combined for writing,
- * which means only one of them is required for reading
- */
- /* +++ Close stuff +++ */
- ErlNifPid closerPid;
- ESockMonitor closerMon;
- ErlNifEnv* closeEnv;
- ERL_NIF_TERM closeRef;
- /* +++ Inform On (counter) Wrap +++ */
- BOOLEAN_T iow;
- /* +++ Controller (owner) process +++ */
- ErlNifPid ctrlPid;
- ESockMonitor ctrlMon;
- /* +++ The actual socket +++ */
- SOCKET sock;
- ErlNifEvent event;
- SOCKET origFD; // A 'socket' created from this FD
- BOOLEAN_T closeOnClose; // Have we dup'ed or not
- /* +++ The dbg flag for SSDBG +++ */
- BOOLEAN_T dbg;
- BOOLEAN_T useReg;
-
- /* Lock order: readMtx, writeMtx, cntMtx
- */
-} ESockDescriptor;
-
-
-/* Global stuff.
- */
-typedef struct {
- /* These are for debugging, testing and the like */
- // ERL_NIF_TERM version;
- // ERL_NIF_TERM buildDate;
-
- /* XXX Should be locked but too awkward and small gain */
- BOOLEAN_T dbg;
- BOOLEAN_T useReg;
-
- /* Registry stuff */
- ErlNifPid regPid; /* Constant - not locked */
-
- /* IOV_MAX. Constant - not locked */
- int iov_max;
-
- /* XXX
- * Should be locked but too awkward for no gain since it is not used yet
- */
- BOOLEAN_T iow; // Where do we send this? Subscription?
-
- ErlNifMutex* protocolsMtx;
-
- ErlNifMutex* cntMtx; /* Locks the below */
- /* Its extreme overkill to have these counters be 64-bit,
- * but since the other counters are, it's much simpler to
- * let these be 64-bit also.
- */
- ESockCounter numSockets;
- ESockCounter numTypeStreams;
- ESockCounter numTypeDGrams;
- ESockCounter numTypeSeqPkgs;
- ESockCounter numDomainInet;
- ESockCounter numDomainInet6;
- ESockCounter numDomainLocal;
- ESockCounter numProtoIP;
- ESockCounter numProtoTCP;
- ESockCounter numProtoUDP;
- ESockCounter numProtoSCTP;
- //
- BOOLEAN_T sockDbg;
-} ESockData;
+/* We can use the IPv4 def for this since the beginning
+ * is the same for INET and INET6 */
+#define which_address_port(sap) \
+ ((((sap)->in4.sin_family == AF_INET) || \
+ ((sap)->in4.sin_family == AF_INET6)) ? \
+ ((sap)->in4.sin_port) : -1)
/* ----------------------------------------------------------------------
@@ -1354,7 +989,6 @@ typedef struct {
*/
-extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */
/* All the nif "callback" functions for the socket API has
@@ -1430,247 +1064,133 @@ ESOCK_NIF_FUNCS
#undef ESOCK_NIF_FUNC_DEF
-#ifndef __WIN32__
-/* ---------------------------------------------------------------------- *
- * *
- * *
- * Start of non-__WIN32__ section a.k.a UNIX section *
- * *
- * *
- * vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */
+/* =======================================================================
+ * Socket specific backend 'synchronicity' functions.
+ * This type is used to create 'sync' function table.
+ * This table is initiated when the nif is loaded.
+ * Initially, its content will be hardcoded to:
+ * * Windows: async (esaio)
+ * * Other (unix): sync (essio)
+ * When we introduce async I/O for unix (io_uring or something similar)
+ * we may make it possible to choose (set a flag when the VM is started;
+ * --esock-io=<async|sync>).
+ */
-/* And here comes the functions that does the actual work (for the most part) */
+typedef struct {
+ ESockIOInit init;
+ ESockIOFinish finish;
+
+ ESockIOInfo info;
+ ESockIOCommand cmd;
+ ESockIOSupports0 supports_0;
+ ESockIOSupports1 supports_1;
+
+ ESockIOOpenWithFd open_with_fd;
+ ESockIOOpenPlain open_plain;
+ ESockIOBind bind;
+
+ ESockIOConnect connect;
+ ESockIOListen listen;
+ ESockIOAccept accept;
+
+ ESockIOSend send;
+ ESockIOSendTo sendto;
+ ESockIOSendMsg sendmsg;
+ ESockIOSendFileStart sendfile_start;
+ ESockIOSendFileContinue sendfile_cont;
+ ESockIOSendFileDeferredClose sendfile_dc;
+
+ ESockIORecv recv;
+ ESockIORecvFrom recvfrom;
+ ESockIORecvMsg recvmsg;
+
+ ESockIOClose close;
+ ESockIOFinClose fin_close;
+ ESockIOShutdown shutdown;
+
+ ESockIOSockName sockname;
+ ESockIOPeerName peername;
+
+ /* The various cancel operations */
+ ESockIOCancelConnect cancel_connect;
+ ESockIOCancelAccept cancel_accept;
+ ESockIOCancelSend cancel_send;
+ ESockIOCancelRecv cancel_recv;
+
+ /* Socket option callback functions */
+ ESockIOSetopt setopt;
+ ESockIOSetoptNative setopt_native;
+ ESockIOSetoptOtp setopt_otp;
+ ESockIOGetopt getopt;
+ ESockIOGetoptNative getopt_native;
+ ESockIOGetoptOtp getopt_otp;
+
+ /* Socket ioctl callback functions */
+ ESockIOIoctl_2 ioctl_2;
+ ESockIOIoctl_3 ioctl_3;
+ ESockIOIoctl_4 ioctl_4;
+
+ /* (socket) NIF resource callback functions */
+ ESockIODTor dtor;
+ ESockIOStop stop;
+ ESockIODown down;
+
+} ESockIoBackend;
-static ERL_NIF_TERM esock_command(ErlNifEnv* env,
- ERL_NIF_TERM command,
- ERL_NIF_TERM cdata);
-static ERL_NIF_TERM esock_command_debug(ErlNifEnv* env,
- ERL_NIF_TERM cdata);
-static ERL_NIF_TERM esock_command_socket_debug(ErlNifEnv* env,
- ERL_NIF_TERM cdata);
-static ERL_NIF_TERM esock_command_use_socket_registry(ErlNifEnv* env,
- ERL_NIF_TERM cdata);
-static ERL_NIF_TERM esock_global_info(ErlNifEnv* env);
-static ERL_NIF_TERM esock_socket_info(ErlNifEnv* env,
- ESockDescriptor* descP);
-static ERL_NIF_TERM esock_socket_info_domain(ErlNifEnv* env,
- ESockDescriptor* descP);
-static ERL_NIF_TERM esock_socket_info_type(ErlNifEnv* env,
- ESockDescriptor* descP);
-static ERL_NIF_TERM esock_socket_info_counters(ErlNifEnv* env,
- ESockDescriptor* descP);
-static ERL_NIF_TERM esock_socket_info_ctype(ErlNifEnv* env,
- ESockDescriptor* descP);
-static ERL_NIF_TERM esock_socket_info_state(ErlNifEnv* env,
- unsigned int state);
-#define ESOCK_SOCKET_INFO_REQ_FUNCS \
- ESOCK_SOCKET_INFO_REQ_FUNC_DEF(readers); \
- ESOCK_SOCKET_INFO_REQ_FUNC_DEF(writers); \
- ESOCK_SOCKET_INFO_REQ_FUNC_DEF(acceptors);
+/* ------------------------------------------------------------------------
+ * Socket option(s) and table(s)
+ */
-#define ESOCK_SOCKET_INFO_REQ_FUNC_DEF(F) \
- static ERL_NIF_TERM esock_socket_info_##F(ErlNifEnv* env, \
- ESockDescriptor* descP);
-ESOCK_SOCKET_INFO_REQ_FUNCS
-#undef ESOCK_SOCKET_INFO_REQ_FUNC_DEF
+struct ESockOpt
+{
+ int opt; // Option number
-static ERL_NIF_TERM socket_info_reqs(ErlNifEnv* env,
- ESockDescriptor* descP,
- ESockRequestor* currentRequestorP,
- ESockRequestQueue* q);
+ // Function to set option
+ ERL_NIF_TERM (*setopt)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal);
-static ERL_NIF_TERM esock_supports_0(ErlNifEnv* env);
-static ERL_NIF_TERM esock_supports_1(ErlNifEnv* env, ERL_NIF_TERM key);
+ // Function to get option
+ ERL_NIF_TERM (*getopt)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt);
-static ERL_NIF_TERM esock_supports_msg_flags(ErlNifEnv* env);
-static ERL_NIF_TERM esock_supports_protocols(ErlNifEnv* env);
-static ERL_NIF_TERM esock_supports_ioctl_requests(ErlNifEnv* env);
-static ERL_NIF_TERM esock_supports_ioctl_flags(ErlNifEnv* env);
-static ERL_NIF_TERM esock_supports_options(ErlNifEnv* env);
+ ERL_NIF_TERM *nameP; // Pointer to option name atom
+};
-static ERL_NIF_TERM esock_open2(ErlNifEnv* env,
- int fd,
- ERL_NIF_TERM eextra);
-static BOOLEAN_T esock_open2_todup(ErlNifEnv* env,
- ERL_NIF_TERM eextra);
-static BOOLEAN_T esock_open2_get_domain(ErlNifEnv* env,
- ERL_NIF_TERM eopts,
- int* domain);
-static BOOLEAN_T esock_open2_get_type(ErlNifEnv* env,
- ERL_NIF_TERM eopt,
- int* type);
-static ERL_NIF_TERM esock_open4(ErlNifEnv* env,
- int domain,
- int type,
- int protocol,
- ERL_NIF_TERM eopts);
-static BOOLEAN_T esock_open_is_debug(ErlNifEnv* env,
- ERL_NIF_TERM eextra,
- BOOLEAN_T dflt);
-static BOOLEAN_T esock_open_use_registry(ErlNifEnv* env,
- ERL_NIF_TERM eextra,
- BOOLEAN_T dflt);
-static BOOLEAN_T esock_open_which_domain(SOCKET sock, int* domain);
-static BOOLEAN_T esock_open_which_type(SOCKET sock, int* type);
-static BOOLEAN_T esock_open_which_protocol(SOCKET sock, int* proto);
-
-static ERL_NIF_TERM esock_bind(ErlNifEnv* env,
- ESockDescriptor* descP,
- ESockAddress* sockAddrP,
- SOCKLEN_T addrLen);
-static ERL_NIF_TERM esock_connect(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM connRef,
- ESockAddress* addrP,
- SOCKLEN_T addrLen);
-static ERL_NIF_TERM esock_listen(ErlNifEnv* env,
- ESockDescriptor* descP,
- int backlog);
-static ERL_NIF_TERM esock_accept(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM ref);
-static ERL_NIF_TERM esock_accept_listening_error(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM accRef,
- ErlNifPid caller,
- int save_errno);
-static ERL_NIF_TERM esock_accept_listening_accept(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- SOCKET accSock,
- ErlNifPid caller);
-static ERL_NIF_TERM esock_accept_accepting_current(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM ref);
-static ERL_NIF_TERM
-esock_accept_accepting_current_accept(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- SOCKET accSock);
-static ERL_NIF_TERM esock_accept_accepting_current_error(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM opRef,
- int save_errno);
-static ERL_NIF_TERM esock_accept_accepting_other(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM ref,
- ErlNifPid caller);
-static ERL_NIF_TERM esock_accept_busy_retry(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM accRef,
- ErlNifPid* pidP);
-static BOOLEAN_T esock_accept_accepted(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- SOCKET accSock,
- ErlNifPid pid,
- ERL_NIF_TERM* result);
-static ERL_NIF_TERM esock_send(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- ErlNifBinary* dataP,
- int flags);
-static ERL_NIF_TERM esock_sendto(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- ErlNifBinary* dataP,
- int flags,
- ESockAddress* toAddrP,
- SOCKLEN_T toAddrLen);
-static ERL_NIF_TERM esock_sendmsg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- ERL_NIF_TERM eMsg,
- int flags,
- ERL_NIF_TERM eIOV);
-#ifdef HAVE_SENDFILE
-static ERL_NIF_TERM
-esock_sendfile_start(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- off_t offset,
- size_t count,
- ERL_NIF_TERM fRef);
-static ERL_NIF_TERM
-esock_sendfile_cont(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- off_t offset,
- size_t count);
-static ERL_NIF_TERM
-esock_sendfile_deferred_close(ErlNifEnv *env,
- ESockDescriptor *descP);
-static int
-esock_sendfile(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- off_t offset,
- size_t *count,
- int *errP);
-static ERL_NIF_TERM
-esock_sendfile_error(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM reason);
-static ERL_NIF_TERM
-esock_sendfile_errno(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- int err);
-static ERL_NIF_TERM
-esock_sendfile_ok(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- size_t count);
-static ERL_NIF_TERM
-esock_sendfile_select(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- size_t count);
-#endif // #ifdef HAVE_SENDFILE
+/* Option levels table*/
-static ERL_NIF_TERM esock_recv(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sendRef,
- ERL_NIF_TERM recvRef,
- ssize_t len,
- int flags);
-static ERL_NIF_TERM esock_recvfrom(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef,
- ssize_t bufSz,
- int flags);
-static ERL_NIF_TERM esock_recvmsg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef,
- ssize_t bufLen,
- ssize_t ctrlLen,
- int flags);
-static ERL_NIF_TERM esock_close(ErlNifEnv* env,
- ESockDescriptor* descP);
-static BOOLEAN_T esock_do_stop(ErlNifEnv* env,
- ESockDescriptor* descP);
-static ERL_NIF_TERM esock_shutdown(ErlNifEnv* env,
- ESockDescriptor* descP,
- int how);
+struct ESockOptLevel
+{
+ int level; // Level number
+
+ size_t num; // Number of options
+ struct ESockOpt *opts; // Options table
+ ERL_NIF_TERM *nameP; // Pointer to level name atom
+};
+
+
+
+/* First chunk of forwards...some of these are used in the options tables... */
+
+/* Set native options */
+static ERL_NIF_TERM esock_setopt_native(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM valueSpec);
/* Set OTP level options */
static ERL_NIF_TERM esock_setopt_otp(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -1685,28 +1205,186 @@ static ERL_NIF_TERM esock_setopt_otp(ErlNifEnv* env,
* *** esock_setopt_otp_meta ***
* *** esock_setopt_otp_use_registry ***
*/
-#define ESOCK_SETOPT_OTP_FUNCS \
- ESOCK_SETOPT_OTP_FUNC_DEF(debug); \
- ESOCK_SETOPT_OTP_FUNC_DEF(iow); \
- ESOCK_SETOPT_OTP_FUNC_DEF(ctrl_proc); \
- ESOCK_SETOPT_OTP_FUNC_DEF(rcvbuf); \
- ESOCK_SETOPT_OTP_FUNC_DEF(rcvctrlbuf); \
- ESOCK_SETOPT_OTP_FUNC_DEF(sndctrlbuf); \
- ESOCK_SETOPT_OTP_FUNC_DEF(meta); \
+#define ESOCK_SETOPT_OTP_FUNCS \
+ ESOCK_SETOPT_OTP_FUNC_DEF(debug); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(iow); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(ctrl_proc); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(rcvbuf); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(rcvctrlbuf); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(sndctrlbuf); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(meta); \
ESOCK_SETOPT_OTP_FUNC_DEF(use_registry);
-#define ESOCK_SETOPT_OTP_FUNC_DEF(F) \
- static ERL_NIF_TERM esock_setopt_otp_##F(ErlNifEnv* env, \
- ESockDescriptor* descP, \
+#define ESOCK_SETOPT_OTP_FUNC_DEF(F) \
+ static ERL_NIF_TERM esock_setopt_otp_##F(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
ERL_NIF_TERM eVal)
ESOCK_SETOPT_OTP_FUNCS
#undef ESOCK_SETOPT_OTP_FUNC_DEF
-/* Set native options */
-static ERL_NIF_TERM esock_setopt_native(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- int opt,
- ERL_NIF_TERM eVal);
+static ERL_NIF_TERM esock_getopt_otp(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int eOpt);
+/* *** esock_getopt_otp_debug ***
+ * *** esock_getopt_otp_iow ***
+ * *** esock_getopt_otp_ctrl_proc ***
+ * *** esock_getopt_otp_rcvbuf ***
+ * *** esock_getopt_otp_rcvctrlbuf ***
+ * *** esock_getopt_otp_sndctrlbuf ***
+ * *** esock_getopt_otp_fd ***
+ * *** esock_getopt_otp_meta ***
+ * *** esock_getopt_otp_use_registry ***
+ * *** esock_getopt_otp_domain ***
+ * *** //esock_getopt_otp_type ***
+ * *** //esock_getopt_otp_protocol ***
+ * *** //esock_getopt_otp_dtp ***
+ */
+#define ESOCK_GETOPT_OTP_FUNCS \
+ ESOCK_GETOPT_OTP_FUNC_DEF(debug); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(iow); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(ctrl_proc); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(rcvbuf); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(rcvctrlbuf); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(sndctrlbuf); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(fd); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(meta); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(use_registry); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(domain);
+#if 0
+ESOCK_GETOPT_OTP_FUNC_DEF(type); \
+ESOCK_GETOPT_OTP_FUNC_DEF(protocol); \
+ESOCK_GETOPT_OTP_FUNC_DEF(dtp);
+#endif
+#define ESOCK_GETOPT_OTP_FUNC_DEF(F) \
+ static ERL_NIF_TERM esock_getopt_otp_##F(ErlNifEnv* env, \
+ ESockDescriptor* descP)
+ESOCK_GETOPT_OTP_FUNCS
+#undef ESOCK_GETOPT_OTP_FUNC_DEF
+
+static ERL_NIF_TERM esock_setopt_level_opt(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt,
+ void* optVal,
+ socklen_t optLen);
+static ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt);
+static ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt);
+static ERL_NIF_TERM esock_getopt_size_opt(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt,
+ SOCKOPTLEN_T valueSz);
+static ERL_NIF_TERM esock_getopt_bin_opt(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt,
+ ErlNifBinary* binP);
+
+static int socket_setopt(int sock,
+ int level,
+ int opt,
+ const void* optVal,
+ const socklen_t optLen);
+
+static int cmpESockOpt(const void *vpa, const void *vpb);
+static int cmpESockOptLevel(const void *vpa, const void *vpb);
+static struct ESockOpt *lookupOpt(int level, int opt);
+
+
+static ERL_NIF_TERM esock_supports_0(ErlNifEnv* env);
+static ERL_NIF_TERM esock_supports_1(ErlNifEnv* env, ERL_NIF_TERM key);
+
+static ERL_NIF_TERM esock_supports_msg_flags(ErlNifEnv* env);
+static ERL_NIF_TERM esock_supports_protocols(ErlNifEnv* env);
+static ERL_NIF_TERM esock_supports_ioctl_requests(ErlNifEnv* env);
+static ERL_NIF_TERM esock_supports_ioctl_flags(ErlNifEnv* env);
+static ERL_NIF_TERM esock_supports_options(ErlNifEnv* env);
+
+#ifndef __WIN32__
+/* ---------------------------------------------------------------------- *
+ * *
+ * *
+ * Start of non-__WIN32__ section a.k.a UNIX section *
+ * *
+ * *
+ * vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */
+
+/* *** esock_activate_next_acceptor ***
+ * *** esock_activate_next_writer ***
+ * *** esock_activate_next_reader ***
+ *
+ * All the activate-next functions for acceptor, writer and reader
+ * have exactly the same API, so we apply some macro magic to simplify.
+ * They simply operates on dufferent data structures.
+ *
+ */
+
+#define ACTIVATE_NEXT_FUNCS_DEFS \
+ ACTIVATE_NEXT_FUNC_DEF(acceptor) \
+ ACTIVATE_NEXT_FUNC_DEF(writer) \
+ ACTIVATE_NEXT_FUNC_DEF(reader)
+
+#define ACTIVATE_NEXT_FUNC_DEF(F) \
+ extern BOOLEAN_T esock_activate_next_##F(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM sockRef);
+ACTIVATE_NEXT_FUNCS_DEFS
+#undef ACTIVATE_NEXT_FUNC_DEF
+
+/* esock_acceptor_search4pid | esock_writer_search4pid | esock_reader_search4pid
+ * esock_acceptor_push | esock_writer_push | esock_reader_push
+ * esock_acceptor_pop | esock_writer_pop | esock_reader_pop
+ * esock_acceptor_unqueue | esock_writer_unqueue | esock_reader_unqueue
+ *
+ * All the queue operator functions (search4pid, push, pop
+ * and unqueue) for acceptor, writer and reader has exactly
+ * the same API, so we apply some macro magic to simplify.
+ */
+
+#define ESOCK_OPERATOR_FUNCS_DEFS \
+ ESOCK_OPERATOR_FUNCS_DEF(acceptor) \
+ ESOCK_OPERATOR_FUNCS_DEF(writer) \
+ ESOCK_OPERATOR_FUNCS_DEF(reader)
+
+#define ESOCK_OPERATOR_FUNCS_DEF(O) \
+ extern BOOLEAN_T esock_##O##_search4pid(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ErlNifPid* pid); \
+ extern void esock_##O##_push(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ErlNifPid pid, \
+ ERL_NIF_TERM ref, \
+ void* dataP); \
+ extern BOOLEAN_T esock_##O##_pop(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ESockRequestor* reqP); \
+ extern BOOLEAN_T esock_##O##_unqueue(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM* refP, \
+ const ErlNifPid* pidP);
+ESOCK_OPERATOR_FUNCS_DEFS
+#undef ESOCK_OPERATOR_FUNCS_DEF
+
+static ERL_NIF_TERM mk_select_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM selectRef);
+
+
+/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *
+ * *
+ * *
+ * End of non-__WIN32__ section a.k.a UNIX section *
+ * *
+ * *
+ * ---------------------------------------------------------------------- */
+#endif // #ifndef __WIN32__
+
+
static ERL_NIF_TERM esock_setopt(ErlNifEnv* env,
ESockDescriptor* descP,
int level,
@@ -1872,50 +1550,6 @@ static ERL_NIF_TERM esock_setopt_sctp_rtoinfo(ErlNifEnv* env,
#endif // defined(HAVE_SCTP)
-static ERL_NIF_TERM esock_getopt_otp(ErlNifEnv* env,
- ESockDescriptor* descP,
- int eOpt);
-/* *** esock_getopt_otp_debug ***
- * *** esock_getopt_otp_iow ***
- * *** esock_getopt_otp_ctrl_proc ***
- * *** esock_getopt_otp_rcvbuf ***
- * *** esock_getopt_otp_rcvctrlbuf ***
- * *** esock_getopt_otp_sndctrlbuf ***
- * *** esock_getopt_otp_fd ***
- * *** esock_getopt_otp_meta ***
- * *** esock_getopt_otp_use_registry ***
- * *** esock_getopt_otp_domain ***
- * *** //esock_getopt_otp_type ***
- * *** //esock_getopt_otp_protocol ***
- * *** //esock_getopt_otp_dtp ***
- */
-#define ESOCK_GETOPT_OTP_FUNCS \
- ESOCK_GETOPT_OTP_FUNC_DEF(debug); \
- ESOCK_GETOPT_OTP_FUNC_DEF(iow); \
- ESOCK_GETOPT_OTP_FUNC_DEF(ctrl_proc); \
- ESOCK_GETOPT_OTP_FUNC_DEF(rcvbuf); \
- ESOCK_GETOPT_OTP_FUNC_DEF(rcvctrlbuf); \
- ESOCK_GETOPT_OTP_FUNC_DEF(sndctrlbuf); \
- ESOCK_GETOPT_OTP_FUNC_DEF(fd); \
- ESOCK_GETOPT_OTP_FUNC_DEF(meta); \
- ESOCK_GETOPT_OTP_FUNC_DEF(use_registry); \
- ESOCK_GETOPT_OTP_FUNC_DEF(domain);
-#if 0
- ESOCK_GETOPT_OTP_FUNC_DEF(type); \
- ESOCK_GETOPT_OTP_FUNC_DEF(protocol); \
- ESOCK_GETOPT_OTP_FUNC_DEF(dtp);
-#endif
-#define ESOCK_GETOPT_OTP_FUNC_DEF(F) \
- static ERL_NIF_TERM esock_getopt_otp_##F(ErlNifEnv* env, \
- ESockDescriptor* descP)
-ESOCK_GETOPT_OTP_FUNCS
-#undef ESOCK_GETOPT_OTP_FUNC_DEF
-
-static ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- int opt,
- ERL_NIF_TERM valueSpec);
static ERL_NIF_TERM esock_getopt(ErlNifEnv* env,
ESockDescriptor* descP,
int level,
@@ -1952,9 +1586,9 @@ ERL_NIF_TERM esock_getopt_sock_protocol(ErlNifEnv* env,
#if defined(IP_MTU_DISCOVER)
static ERL_NIF_TERM esock_getopt_ip_mtu_discover(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- int opt);
+ ESockDescriptor* descP,
+ int level,
+ int opt);
#endif
#if defined(IP_MULTICAST_IF)
static ERL_NIF_TERM esock_getopt_multicast_if(ErlNifEnv* env,
@@ -2020,300 +1654,6 @@ static ERL_NIF_TERM esock_getopt_sctp_rtoinfo(ErlNifEnv* env,
#endif // defined(HAVE_SCTP)
-static ERL_NIF_TERM esock_sockname(ErlNifEnv* env,
- ESockDescriptor* descP);
-static ERL_NIF_TERM esock_peername(ErlNifEnv* env,
- ESockDescriptor* descP);
-
-static ERL_NIF_TERM esock_ioctl1(ErlNifEnv* env,
- ESockDescriptor* descP,
- unsigned long req);
-static ERL_NIF_TERM esock_ioctl2(ErlNifEnv* env,
- ESockDescriptor* descP,
- unsigned long req,
- ERL_NIF_TERM arg);
-static ERL_NIF_TERM esock_ioctl3(ErlNifEnv* env,
- ESockDescriptor* descP,
- unsigned long req,
- ERL_NIF_TERM ename,
- ERL_NIF_TERM eval);
-static ERL_NIF_TERM esock_ioctl_gifconf(ErlNifEnv* env,
- ESockDescriptor* descP);
-#if defined(SIOCGIFNAME)
-static ERL_NIF_TERM esock_ioctl_gifname(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eidx);
-#endif
-
-/* esock_ioctl_gifindex */
-#if defined(SIOCGIFINDEX)
-#define IOCTL_GIFINDEX_FUNC_DEF IOCTL_GET_FUNC_DEF(gifindex)
-#else
-#define IOCTL_GIFINDEX_FUNC_DEF
-#endif
-
-/* esock_ioctl_gifflags */
-#if defined(SIOCGIFFLAGS)
-#define IOCTL_GIFFLAGS_FUNC_DEF IOCTL_GET_FUNC_DEF(gifflags)
-#else
-#define IOCTL_GIFFLAGS_FUNC_DEF
-#endif
-
-/* esock_ioctl_gifaddr */
-#if defined(SIOCGIFADDR)
-#define IOCTL_GIFADDR_FUNC_DEF IOCTL_GET_FUNC_DEF(gifaddr)
-#else
-#define IOCTL_GIFADDR_FUNC_DEF
-#endif
-
-/* esock_ioctl_gifdstaddr */
-#if defined(SIOCGIFDSTADDR)
-#define IOCTL_GIFDSTADDR_FUNC_DEF IOCTL_GET_FUNC_DEF(gifdstaddr)
-#else
-#define IOCTL_GIFDSTADDR_FUNC_DEF
-#endif
-
-/* esock_ioctl_gifbrdaddr */
-#if defined(SIOCGIFBRDADDR)
-#define IOCTL_GIFBRDADDR_FUNC_DEF IOCTL_GET_FUNC_DEF(gifbrdaddr)
-#else
-#define IOCTL_GIFBRDADDR_FUNC_DEF
-#endif
-
-/* esock_ioctl_gifnetmask */
-#if defined(SIOCGIFNETMASK)
-#define IOCTL_GIFNETMASK_FUNC_DEF IOCTL_GET_FUNC_DEF(gifnetmask)
-#else
-#define IOCTL_GIFNETMASK_FUNC_DEF
-#endif
-
-/* esock_ioctl_gifmtu */
-#if defined(SIOCGIFMTU)
-#define IOCTL_GIFMTU_FUNC_DEF IOCTL_GET_FUNC_DEF(gifmtu)
-#else
-#define IOCTL_GIFMTU_FUNC_DEF
-#endif
-
-/* esock_ioctl_gifhwaddr */
-#if defined(SIOCGIFHWADDR) && defined(ESOCK_USE_HWADDR)
-#define IOCTL_GIFHWADDR_FUNC_DEF IOCTL_GET_FUNC_DEF(gifhwaddr)
-#else
-#define IOCTL_GIFHWADDR_FUNC_DEF
-#endif
-
-/* esock_ioctl_gifmap */
-#if defined(SIOCGIFMAP) && defined(ESOCK_USE_IFMAP)
-#define IOCTL_GIFMAP_FUNC_DEF IOCTL_GET_FUNC_DEF(gifmap)
-#else
-#define IOCTL_GIFMAP_FUNC_DEF
-#endif
-
-/* esock_ioctl_giftxqlen */
-#if defined(SIOCGIFTXQLEN)
-#define IOCTL_GIFTXQLEN_FUNC_DEF IOCTL_GET_FUNC_DEF(giftxqlen)
-#else
-#define IOCTL_GIFTXQLEN_FUNC_DEF
-#endif
-
-#define IOCTL_GET_FUNCS_DEF \
- IOCTL_GIFINDEX_FUNC_DEF; \
- IOCTL_GIFFLAGS_FUNC_DEF; \
- IOCTL_GIFADDR_FUNC_DEF; \
- IOCTL_GIFDSTADDR_FUNC_DEF; \
- IOCTL_GIFBRDADDR_FUNC_DEF; \
- IOCTL_GIFNETMASK_FUNC_DEF; \
- IOCTL_GIFMTU_FUNC_DEF; \
- IOCTL_GIFHWADDR_FUNC_DEF; \
- IOCTL_GIFMAP_FUNC_DEF; \
- IOCTL_GIFTXQLEN_FUNC_DEF;
-#define IOCTL_GET_FUNC_DEF(F) \
- static ERL_NIF_TERM esock_ioctl_##F(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ERL_NIF_TERM ename)
-IOCTL_GET_FUNCS_DEF
-#undef IOCTL_GET_FUNC_DEF
-
-/* esock_ioctl_sifflags */
-#if defined(SIOCSIFFLAGS)
-#define IOCTL_SIFFLAGS_FUNC_DEF IOCTL_SET_FUNC_DEF(sifflags)
-#else
-#define IOCTL_SIFFLAGS_FUNC_DEF
-#endif
-
-/* esock_ioctl_sifaddr */
-#if defined(SIOCSIFADDR)
-#define IOCTL_SIFADDR_FUNC_DEF IOCTL_SET_FUNC_DEF(sifaddr)
-#else
-#define IOCTL_SIFADDR_FUNC_DEF
-#endif
-
-/* esock_ioctl_sifdstaddr */
-#if defined(SIOCSIFDSTADDR)
-#define IOCTL_SIFDSTADDR_FUNC_DEF IOCTL_SET_FUNC_DEF(sifdstaddr)
-#else
-#define IOCTL_SIFDSTADDR_FUNC_DEF
-#endif
-
-/* esock_ioctl_sifbrdaddr */
-#if defined(SIOCSIFBRDADDR)
-#define IOCTL_SIFBRDADDR_FUNC_DEF IOCTL_SET_FUNC_DEF(sifbrdaddr)
-#else
-#define IOCTL_SIFBRDADDR_FUNC_DEF
-#endif
-
-/* esock_ioctl_sifnetmask */
-#if defined(SIOCSIFNETMASK)
-#define IOCTL_SIFNETMASK_FUNC_DEF IOCTL_SET_FUNC_DEF(sifnetmask)
-#else
-#define IOCTL_SIFNETMASK_FUNC_DEF
-#endif
-
-/* esock_ioctl_sifmtu */
-#if defined(SIOCSIFMTU)
-#define IOCTL_SIFMTU_FUNC_DEF IOCTL_SET_FUNC_DEF(sifmtu)
-#else
-#define IOCTL_SIFMTU_FUNC_DEF
-#endif
-
-/* esock_ioctl_siftxqlen */
-#if defined(SIOCSIFTXQLEN)
-#define IOCTL_SIFTXQLEN_FUNC_DEF IOCTL_SET_FUNC_DEF(siftxqlen)
-#else
-#define IOCTL_SIFTXQLEN_FUNC_DEF
-#endif
-
-#define IOCTL_SET_FUNCS_DEF \
- IOCTL_SIFFLAGS_FUNC_DEF; \
- IOCTL_SIFADDR_FUNC_DEF; \
- IOCTL_SIFDSTADDR_FUNC_DEF; \
- IOCTL_SIFBRDADDR_FUNC_DEF; \
- IOCTL_SIFNETMASK_FUNC_DEF; \
- IOCTL_SIFMTU_FUNC_DEF; \
- IOCTL_SIFTXQLEN_FUNC_DEF;
-#define IOCTL_SET_FUNC_DEF(F) \
- static ERL_NIF_TERM esock_ioctl_##F(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ERL_NIF_TERM ename, \
- ERL_NIF_TERM evalue)
-IOCTL_SET_FUNCS_DEF
-#undef IOCTL_SET_FUNC_DEF
-
-
-static ERL_NIF_TERM encode_ioctl_ifconf(ErlNifEnv* env,
- ESockDescriptor* descP,
- struct ifconf* ifcP);
-static ERL_NIF_TERM encode_ioctl_ifconf_ifreq(ErlNifEnv* env,
- ESockDescriptor* descP,
- struct ifreq* ifrP);
-static ERL_NIF_TERM encode_ioctl_ifreq_name(ErlNifEnv* env,
- char* name);
-static ERL_NIF_TERM encode_ioctl_ifreq_sockaddr(ErlNifEnv* env,
- struct sockaddr* sa);
-static ERL_NIF_TERM make_ifreq(ErlNifEnv* env,
- ERL_NIF_TERM name,
- ERL_NIF_TERM key2,
- ERL_NIF_TERM val2);
-#if defined(SIOCGIFMAP) && defined(ESOCK_USE_IFMAP)
-static ERL_NIF_TERM encode_ioctl_ifrmap(ErlNifEnv* env,
- ESockDescriptor* descP,
- struct ifmap* mapP);
-#endif
-#if defined(SIOCGIFHWADDR) && defined(ESOCK_USE_HWADDR)
-static ERL_NIF_TERM encode_ioctl_hwaddr(ErlNifEnv* env,
- ESockDescriptor* descP,
- struct sockaddr* addrP);
-#endif
-static ERL_NIF_TERM encode_ioctl_ifraddr(ErlNifEnv* env,
- ESockDescriptor* descP,
- struct sockaddr* addrP);
-static ERL_NIF_TERM encode_ioctl_flags(ErlNifEnv* env,
- ESockDescriptor* descP,
- short flags);
-#if defined(SIOCSIFFLAGS)
-static BOOLEAN_T decode_ioctl_flags(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eflags,
- short* flags);
-#endif
-static BOOLEAN_T decode_ioctl_sockaddr(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eaddr,
- ESockAddress* addr);
-#if defined(SIOCSIFMTU)
-static BOOLEAN_T decode_ioctl_mtu(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM emtu,
- int* mtu);
-#endif
-#if defined(SIOCSIFTXQLEN)
-static BOOLEAN_T decode_ioctl_txqlen(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM etxqlen,
- int* txqlen);
-#endif
-#if defined(SIOCSIFTXQLEN)
-static BOOLEAN_T decode_ioctl_ivalue(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eivalue,
- int* ivalue);
-#endif
-static ERL_NIF_TERM encode_ioctl_ivalue(ErlNifEnv* env,
- ESockDescriptor* descP,
- int ivalue);
-
-static ERL_NIF_TERM esock_cancel(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM op,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM opRef);
-static ERL_NIF_TERM esock_cancel_connect(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef);
-static ERL_NIF_TERM esock_cancel_accept(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM opRef);
-static ERL_NIF_TERM esock_cancel_accept_current(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM esock_cancel_accept_waiting(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef,
- const ErlNifPid* selfP);
-static ERL_NIF_TERM esock_cancel_send(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM opRef);
-static ERL_NIF_TERM esock_cancel_send_current(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM esock_cancel_send_waiting(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef,
- const ErlNifPid* selfP);
-static ERL_NIF_TERM esock_cancel_recv(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM opRef);
-static ERL_NIF_TERM esock_cancel_recv_current(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM esock_cancel_recv_waiting(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef,
- const ErlNifPid* selfP);
-static ERL_NIF_TERM esock_cancel_read_select(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef);
-static ERL_NIF_TERM esock_cancel_write_select(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef);
-static ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef,
- int smode,
- int rmode);
-
#if defined(USE_SETOPT_STR_OPT)
static ERL_NIF_TERM esock_setopt_str_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -2332,7 +1672,7 @@ static ERL_NIF_TERM esock_setopt_int_opt(ErlNifEnv* env,
int level,
int opt,
ERL_NIF_TERM eVal);
-#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) \
+#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) \
&& defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -2340,13 +1680,6 @@ static ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv* env,
int opt,
ERL_NIF_TERM eVal);
#endif
-static ERL_NIF_TERM esock_setopt_level_opt(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- int opt,
- void* optVal,
- socklen_t optLen);
-
#if defined(USE_GETOPT_STR_OPT)
static ERL_NIF_TERM esock_getopt_str_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -2355,29 +1688,7 @@ static ERL_NIF_TERM esock_getopt_str_opt(ErlNifEnv* env,
int max,
BOOLEAN_T stripNUL);
#endif
-static ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- int opt);
-static ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- int opt);
-static BOOLEAN_T esock_getopt_int(SOCKET sock,
- int level,
- int opt,
- int* valP);
-static ERL_NIF_TERM esock_getopt_size_opt(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- int opt,
- SOCKOPTLEN_T valueSz);
-static ERL_NIF_TERM esock_getopt_bin_opt(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- int opt,
- ErlNifBinary* binP);
-#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) \
+#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) \
&& defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -2387,36 +1698,948 @@ static ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv* env,
-/* ------------------------------------------------------------------------
- * Socket option tables and handling
+
+
+
+
+static ERL_NIF_TERM esock_shutdown(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int how);
+static ERL_NIF_TERM esock_sockname(ErlNifEnv* env,
+ ESockDescriptor* descP);
+static ERL_NIF_TERM esock_peername(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+static ERL_NIF_TERM esock_command(ErlNifEnv* env,
+ ERL_NIF_TERM command,
+ ERL_NIF_TERM cdata);
+static ERL_NIF_TERM esock_command_debug(ErlNifEnv* env,
+ ERL_NIF_TERM cdata);
+static ERL_NIF_TERM esock_command_socket_debug(ErlNifEnv* env,
+ ERL_NIF_TERM cdata);
+static ERL_NIF_TERM esock_command_use_socket_registry(ErlNifEnv* env,
+ ERL_NIF_TERM cdata);
+
+#define ESOCK_SOCKET_INFO_REQ_FUNCS \
+ ESOCK_SOCKET_INFO_REQ_FUNC_DEF(readers); \
+ ESOCK_SOCKET_INFO_REQ_FUNC_DEF(writers); \
+ ESOCK_SOCKET_INFO_REQ_FUNC_DEF(acceptors);
+
+#define ESOCK_SOCKET_INFO_REQ_FUNC_DEF(F) \
+ static ERL_NIF_TERM esock_socket_info_##F(ErlNifEnv* env, \
+ ESockDescriptor* descP);
+ESOCK_SOCKET_INFO_REQ_FUNCS
+#undef ESOCK_SOCKET_INFO_REQ_FUNC_DEF
+
+static ERL_NIF_TERM esock_cancel(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM op,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+/*
+static ERL_NIF_TERM esock_cancel_recv(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+*/
+static ERL_NIF_TERM esock_listen(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int backlog);
+
+static ERL_NIF_TERM socket_info_reqs(ErlNifEnv* env,
+ ESockDescriptor* descP,
+#ifndef __WIN32__
+ ESockRequestor* currentRequestorP,
+#endif
+ ESockRequestQueue* q);
+
+static ERL_NIF_TERM esock_global_info(ErlNifEnv* env);
+static ERL_NIF_TERM esock_socket_info(ErlNifEnv* env,
+ ESockDescriptor* descP);
+static ERL_NIF_TERM esock_socket_info_domain(ErlNifEnv* env,
+ ESockDescriptor* descP);
+static ERL_NIF_TERM esock_socket_info_type(ErlNifEnv* env,
+ ESockDescriptor* descP);
+static ERL_NIF_TERM esock_socket_info_ctype(ErlNifEnv* env,
+ ESockDescriptor* descP);
+static ERL_NIF_TERM esock_socket_info_state(ErlNifEnv* env,
+ unsigned int state);
+static ERL_NIF_TERM esock_socket_info_counters(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+static ERL_NIF_TERM mk_close_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM closeRef);
+static ERL_NIF_TERM mk_reg_msg(ErlNifEnv* env,
+ ERL_NIF_TERM tag,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM mk_reg_add_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM mk_reg_del_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM mk_simple_abort_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason);
+static ERL_NIF_TERM mk_abort_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef,
+ ERL_NIF_TERM reason);
+static ERL_NIF_TERM mk_wrap_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM cnt);
+static BOOLEAN_T qsearch4pid(ErlNifEnv* env,
+ ESockRequestQueue* q,
+ ErlNifPid* pid);
+static unsigned int qlength(ESockRequestQueue* q);
+static void qpush(ESockRequestQueue* q,
+ ESockRequestQueueElement* e);
+static ESockRequestQueueElement* qpop(ESockRequestQueue* q);
+static BOOLEAN_T qunqueue(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const char* slogan,
+ ESockRequestQueue* q,
+ ERL_NIF_TERM* refP,
+ const ErlNifPid* pidP);
+static ESockRequestQueueElement* qget(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const char* slogan,
+ ESockRequestQueue* q,
+ ERL_NIF_TERM* refP,
+ const ErlNifPid* pidP);
+
+static char* extract_debug_filename(ErlNifEnv* env,
+ ERL_NIF_TERM map);
+
+
+/* --------------------------------------------------------------------- */
+
+#if defined(IP_TOS)
+static BOOLEAN_T decode_ip_tos(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ int* val);
+#endif
+#if defined(IP_MTU_DISCOVER)
+static BOOLEAN_T decode_ip_pmtudisc(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ int* val);
+#endif
+#if defined(IP_MTU_DISCOVER)
+static void encode_ip_pmtudisc(ErlNifEnv* env,
+ int val,
+ ERL_NIF_TERM* eVal);
+#endif
+#if defined(IPV6_MTU_DISCOVER)
+static BOOLEAN_T decode_ipv6_pmtudisc(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ int* val);
+#endif
+#if defined(IPV6_MTU_DISCOVER)
+static void encode_ipv6_pmtudisc(ErlNifEnv* env,
+ int val,
+ ERL_NIF_TERM* eVal);
+#endif
+
+static ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val);
+
+#if defined(IPV6_MULTICAST_HOPS) || defined(IPV6_UNICAST_HOPS)
+static
+BOOLEAN_T decode_hops(ErlNifEnv *env, ERL_NIF_TERM eVal, int *val);
+#endif
+
+#if defined(SCTP_ASSOCINFO) || defined(SCTP_RTOINOFO)
+static BOOLEAN_T decode_sctp_assoc_t(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ sctp_assoc_t* val);
+static ERL_NIF_TERM encode_sctp_assoc_t(ErlNifEnv* env,
+ sctp_assoc_t val);
+#endif // #if defined(SCTP_ASSOCINFO) || defined(SCTP_RTOINOFO)
+
+
+static BOOLEAN_T ehow2how(ERL_NIF_TERM ehow, int* how);
+
+
+/*
+#if defined(HAS_AF_LOCAL) || defined(SO_BINDTODEVICE)
+static size_t my_strnlen(const char *s, size_t maxlen);
+#endif
+*/
+
+static void esock_dtor(ErlNifEnv* env, void* obj);
+static void esock_stop(ErlNifEnv* env,
+ void* obj,
+ ErlNifEvent fd,
+ int is_direct_call);
+static void esock_down(ErlNifEnv* env,
+ void* obj,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP);
+
+static void esock_on_halt(void* priv_data);
+
+static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info);
+
+
+
+#if HAVE_IN6
+# if ! defined(HAVE_IN6ADDR_ANY) || ! HAVE_IN6ADDR_ANY
+# if HAVE_DECL_IN6ADDR_ANY_INIT
+static const struct in6_addr in6addr_any = { { IN6ADDR_ANY_INIT } };
+# else
+static const struct in6_addr in6addr_any =
+ { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } };
+# endif /* HAVE_IN6ADDR_ANY_INIT */
+# endif /* ! HAVE_DECL_IN6ADDR_ANY */
+
+# if ! defined(HAVE_IN6ADDR_LOOPBACK) || ! HAVE_IN6ADDR_LOOPBACK
+# if HAVE_DECL_IN6ADDR_LOOPBACK_INIT
+static const struct in6_addr in6addr_loopback =
+ { { IN6ADDR_LOOPBACK_INIT } };
+# else
+static const struct in6_addr in6addr_loopback =
+ { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } };
+# endif /* HAVE_IN6ADDR_LOOPBACk_INIT */
+# endif /* ! HAVE_DECL_IN6ADDR_LOOPBACK */
+#endif /* HAVE_IN6 */
+
+
+
+/* *** Global atoms ***
+ * Note that when an (global) atom is added here, it must also be added
+ * in the socket_int.h file!
*/
+#define GLOBAL_ATOMS \
+ GLOBAL_ATOM_DECL(abort); \
+ GLOBAL_ATOM_DECL(accept); \
+ GLOBAL_ATOM_DECL(acceptconn); \
+ GLOBAL_ATOM_DECL(acceptfilter); \
+ GLOBAL_ATOM_DECL(acc_success); \
+ GLOBAL_ATOM_DECL(acc_fails); \
+ GLOBAL_ATOM_DECL(acc_tries); \
+ GLOBAL_ATOM_DECL(acc_waits); \
+ GLOBAL_ATOM_DECL(adaption_layer); \
+ GLOBAL_ATOM_DECL(addr); \
+ GLOBAL_ATOM_DECL(addrform); \
+ GLOBAL_ATOM_DECL(add_membership); \
+ GLOBAL_ATOM_DECL(add_socket); \
+ GLOBAL_ATOM_DECL(add_source_membership); \
+ GLOBAL_ATOM_DECL(alen); \
+ GLOBAL_ATOM_DECL(allmulti); \
+ GLOBAL_ATOM_DECL(already); \
+ GLOBAL_ATOM_DECL(any); \
+ GLOBAL_ATOM_DECL(appletlk); \
+ GLOBAL_ATOM_DECL(arcnet); \
+ GLOBAL_ATOM_DECL(associnfo); \
+ GLOBAL_ATOM_DECL(atm); \
+ GLOBAL_ATOM_DECL(authhdr); \
+ GLOBAL_ATOM_DECL(auth_active_key); \
+ GLOBAL_ATOM_DECL(auth_asconf); \
+ GLOBAL_ATOM_DECL(auth_chunk); \
+ GLOBAL_ATOM_DECL(auth_delete_key); \
+ GLOBAL_ATOM_DECL(auth_key); \
+ GLOBAL_ATOM_DECL(auth_level); \
+ GLOBAL_ATOM_DECL(autoclose); \
+ GLOBAL_ATOM_DECL(automedia); \
+ GLOBAL_ATOM_DECL(ax25); \
+ GLOBAL_ATOM_DECL(bad_data); \
+ GLOBAL_ATOM_DECL(base_addr); \
+ GLOBAL_ATOM_DECL(bindtodevice); \
+ GLOBAL_ATOM_DECL(block_source); \
+ GLOBAL_ATOM_DECL(broadcast); \
+ GLOBAL_ATOM_DECL(busy_poll); \
+ GLOBAL_ATOM_DECL(cancel); \
+ GLOBAL_ATOM_DECL(cantconfig); \
+ GLOBAL_ATOM_DECL(chaos); \
+ GLOBAL_ATOM_DECL(checksum); \
+ GLOBAL_ATOM_DECL(close); \
+ GLOBAL_ATOM_DECL(closed); \
+ GLOBAL_ATOM_DECL(cmsg_cloexec); \
+ GLOBAL_ATOM_DECL(command); \
+ GLOBAL_ATOM_DECL(completion); \
+ GLOBAL_ATOM_DECL(completion_status); \
+ GLOBAL_ATOM_DECL(confirm); \
+ GLOBAL_ATOM_DECL(congestion); \
+ GLOBAL_ATOM_DECL(connect); \
+ GLOBAL_ATOM_DECL(connected); \
+ GLOBAL_ATOM_DECL(connecting); \
+ GLOBAL_ATOM_DECL(context); \
+ GLOBAL_ATOM_DECL(cork); \
+ GLOBAL_ATOM_DECL(counters); \
+ GLOBAL_ATOM_DECL(credentials); \
+ GLOBAL_ATOM_DECL(ctrl); \
+ GLOBAL_ATOM_DECL(ctrunc); \
+ GLOBAL_ATOM_DECL(data); \
+ GLOBAL_ATOM_DECL(data_size); \
+ GLOBAL_ATOM_DECL(debug); \
+ GLOBAL_ATOM_DECL(default); \
+ GLOBAL_ATOM_DECL(default_send_params); \
+ GLOBAL_ATOM_DECL(delayed_ack_time); \
+ GLOBAL_ATOM_DECL(dgram); \
+ GLOBAL_ATOM_DECL(disable_fragments); \
+ GLOBAL_ATOM_DECL(dlci); \
+ GLOBAL_ATOM_DECL(dma); \
+ GLOBAL_ATOM_DECL(domain); \
+ GLOBAL_ATOM_DECL(dontfrag); \
+ GLOBAL_ATOM_DECL(dontroute); \
+ GLOBAL_ATOM_DECL(dormant); \
+ GLOBAL_ATOM_DECL(drop_membership); \
+ GLOBAL_ATOM_DECL(drop_source_membership); \
+ GLOBAL_ATOM_DECL(dstopts); \
+ GLOBAL_ATOM_DECL(dup); \
+ GLOBAL_ATOM_DECL(dying); \
+ GLOBAL_ATOM_DECL(dynamic); \
+ GLOBAL_ATOM_DECL(echo); \
+ GLOBAL_ATOM_DECL(eether); \
+ GLOBAL_ATOM_DECL(efile); \
+ GLOBAL_ATOM_DECL(egp); \
+ GLOBAL_ATOM_DECL(enotsup); \
+ GLOBAL_ATOM_DECL(eor); \
+ GLOBAL_ATOM_DECL(error); \
+ GLOBAL_ATOM_DECL(errqueue); \
+ GLOBAL_ATOM_DECL(esp_network_level); \
+ GLOBAL_ATOM_DECL(esp_trans_level); \
+ GLOBAL_ATOM_DECL(ether); \
+ GLOBAL_ATOM_DECL(eui64); \
+ GLOBAL_ATOM_DECL(events); \
+ GLOBAL_ATOM_DECL(explicit_eor); \
+ GLOBAL_ATOM_DECL(faith); \
+ GLOBAL_ATOM_DECL(false); \
+ GLOBAL_ATOM_DECL(family); \
+ GLOBAL_ATOM_DECL(fastroute); \
+ GLOBAL_ATOM_DECL(flags); \
+ GLOBAL_ATOM_DECL(flowinfo); \
+ GLOBAL_ATOM_DECL(fragment_interleave); \
+ GLOBAL_ATOM_DECL(freebind); \
+ GLOBAL_ATOM_DECL(frelay); \
+ GLOBAL_ATOM_DECL(get_overlapped_result); \
+ GLOBAL_ATOM_DECL(get_peer_addr_info); \
+ GLOBAL_ATOM_DECL(hatype); \
+ GLOBAL_ATOM_DECL(hdrincl); \
+ GLOBAL_ATOM_DECL(hmac_ident); \
+ GLOBAL_ATOM_DECL(hoplimit); \
+ GLOBAL_ATOM_DECL(hopopts); \
+ GLOBAL_ATOM_DECL(host); \
+ GLOBAL_ATOM_DECL(icmp); \
+ GLOBAL_ATOM_DECL(icmp6); \
+ GLOBAL_ATOM_DECL(ieee802); \
+ GLOBAL_ATOM_DECL(ieee1394); \
+ GLOBAL_ATOM_DECL(ifindex); \
+ GLOBAL_ATOM_DECL(igmp); \
+ GLOBAL_ATOM_DECL(implink); \
+ GLOBAL_ATOM_DECL(index); \
+ GLOBAL_ATOM_DECL(inet); \
+ GLOBAL_ATOM_DECL(inet6); \
+ GLOBAL_ATOM_DECL(infiniband); \
+ GLOBAL_ATOM_DECL(info); \
+ GLOBAL_ATOM_DECL(initmsg); \
+ GLOBAL_ATOM_DECL(invalid); \
+ GLOBAL_ATOM_DECL(integer_range); \
+ GLOBAL_ATOM_DECL(iov); \
+ GLOBAL_ATOM_DECL(ip); \
+ GLOBAL_ATOM_DECL(ipcomp_level); \
+ GLOBAL_ATOM_DECL(ipip); \
+ GLOBAL_ATOM_DECL(ipv6); \
+ GLOBAL_ATOM_DECL(irq); \
+ GLOBAL_ATOM_DECL(i_want_mapped_v4_addr); \
+ GLOBAL_ATOM_DECL(join_group); \
+ GLOBAL_ATOM_DECL(keepalive); \
+ GLOBAL_ATOM_DECL(keepcnt); \
+ GLOBAL_ATOM_DECL(keepidle); \
+ GLOBAL_ATOM_DECL(keepintvl); \
+ GLOBAL_ATOM_DECL(kernel); \
+ GLOBAL_ATOM_DECL(knowsepoch); \
+ GLOBAL_ATOM_DECL(leave_group); \
+ GLOBAL_ATOM_DECL(level); \
+ GLOBAL_ATOM_DECL(linger); \
+ GLOBAL_ATOM_DECL(link); \
+ GLOBAL_ATOM_DECL(link0); \
+ GLOBAL_ATOM_DECL(link1); \
+ GLOBAL_ATOM_DECL(link2); \
+ GLOBAL_ATOM_DECL(local); \
+ GLOBAL_ATOM_DECL(localtlk); \
+ GLOBAL_ATOM_DECL(local_auth_chunks); \
+ GLOBAL_ATOM_DECL(loopback); \
+ GLOBAL_ATOM_DECL(lowdelay); \
+ GLOBAL_ATOM_DECL(lower_up); \
+ GLOBAL_ATOM_DECL(mark); \
+ GLOBAL_ATOM_DECL(master); \
+ GLOBAL_ATOM_DECL(maxburst); \
+ GLOBAL_ATOM_DECL(maxseg); \
+ GLOBAL_ATOM_DECL(md5sig); \
+ GLOBAL_ATOM_DECL(mem_end); \
+ GLOBAL_ATOM_DECL(mem_start); \
+ GLOBAL_ATOM_DECL(metricom); \
+ GLOBAL_ATOM_DECL(mincost); \
+ GLOBAL_ATOM_DECL(minttl); \
+ GLOBAL_ATOM_DECL(monitor); \
+ GLOBAL_ATOM_DECL(more); \
+ GLOBAL_ATOM_DECL(msfilter); \
+ GLOBAL_ATOM_DECL(mtu); \
+ GLOBAL_ATOM_DECL(mtu_discover); \
+ GLOBAL_ATOM_DECL(multicast); \
+ GLOBAL_ATOM_DECL(multicast_all); \
+ GLOBAL_ATOM_DECL(multicast_hops); \
+ GLOBAL_ATOM_DECL(multicast_if); \
+ GLOBAL_ATOM_DECL(multicast_loop); \
+ GLOBAL_ATOM_DECL(multicast_ttl); \
+ GLOBAL_ATOM_DECL(name); \
+ GLOBAL_ATOM_DECL(netns); \
+ GLOBAL_ATOM_DECL(netrom); \
+ GLOBAL_ATOM_DECL(nlen); \
+ GLOBAL_ATOM_DECL(noarp); \
+ GLOBAL_ATOM_DECL(nodelay); \
+ GLOBAL_ATOM_DECL(nodefrag); \
+ GLOBAL_ATOM_DECL(nogroup); \
+ GLOBAL_ATOM_DECL(none); \
+ GLOBAL_ATOM_DECL(noopt); \
+ GLOBAL_ATOM_DECL(nopush); \
+ GLOBAL_ATOM_DECL(nosignal); \
+ GLOBAL_ATOM_DECL(notrailers); \
+ GLOBAL_ATOM_DECL(not_bound); \
+ GLOBAL_ATOM_DECL(not_found); \
+ GLOBAL_ATOM_DECL(num_general_errors); \
+ GLOBAL_ATOM_DECL(not_owner); \
+ GLOBAL_ATOM_DECL(num_threads); \
+ GLOBAL_ATOM_DECL(num_unexpected_accepts); \
+ GLOBAL_ATOM_DECL(num_unexpected_connects); \
+ GLOBAL_ATOM_DECL(num_unexpected_reads); \
+ GLOBAL_ATOM_DECL(num_unexpected_writes); \
+ GLOBAL_ATOM_DECL(num_unknown_cmds); \
+ GLOBAL_ATOM_DECL(oactive); \
+ GLOBAL_ATOM_DECL(ok); \
+ GLOBAL_ATOM_DECL(oob); \
+ GLOBAL_ATOM_DECL(oobinline); \
+ GLOBAL_ATOM_DECL(options); \
+ GLOBAL_ATOM_DECL(origdstaddr); \
+ GLOBAL_ATOM_DECL(otherhost); \
+ GLOBAL_ATOM_DECL(outgoing); \
+ GLOBAL_ATOM_DECL(packet); \
+ GLOBAL_ATOM_DECL(partial_delivery_point); \
+ GLOBAL_ATOM_DECL(passcred); \
+ GLOBAL_ATOM_DECL(path); \
+ GLOBAL_ATOM_DECL(peek); \
+ GLOBAL_ATOM_DECL(peek_off); \
+ GLOBAL_ATOM_DECL(peer_addr_params); \
+ GLOBAL_ATOM_DECL(peer_auth_chunks); \
+ GLOBAL_ATOM_DECL(peercred); \
+ GLOBAL_ATOM_DECL(pktinfo); \
+ GLOBAL_ATOM_DECL(pktoptions); \
+ GLOBAL_ATOM_DECL(pkttype); \
+ GLOBAL_ATOM_DECL(pointopoint); \
+ GLOBAL_ATOM_DECL(port); \
+ GLOBAL_ATOM_DECL(portrange); \
+ GLOBAL_ATOM_DECL(portsel); \
+ GLOBAL_ATOM_DECL(ppromisc); \
+ GLOBAL_ATOM_DECL(primary_addr); \
+ GLOBAL_ATOM_DECL(prim_file); \
+ GLOBAL_ATOM_DECL(priority); \
+ GLOBAL_ATOM_DECL(promisc); \
+ GLOBAL_ATOM_DECL(pronet); \
+ GLOBAL_ATOM_DECL(protocol); \
+ GLOBAL_ATOM_DECL(pup); \
+ GLOBAL_ATOM_DECL(raw); \
+ GLOBAL_ATOM_DECL(rcvbuf); \
+ GLOBAL_ATOM_DECL(rcvbufforce); \
+ GLOBAL_ATOM_DECL(rcvlowat); \
+ GLOBAL_ATOM_DECL(rcvtimeo); \
+ GLOBAL_ATOM_DECL(rdm); \
+ GLOBAL_ATOM_DECL(read_byte); \
+ GLOBAL_ATOM_DECL(read_fails); \
+ GLOBAL_ATOM_DECL(read_pkg); \
+ GLOBAL_ATOM_DECL(read_tries); \
+ GLOBAL_ATOM_DECL(read_waits); \
+ GLOBAL_ATOM_DECL(recv); \
+ GLOBAL_ATOM_DECL(recvdstaddr); \
+ GLOBAL_ATOM_DECL(recverr); \
+ GLOBAL_ATOM_DECL(recvfrom); \
+ GLOBAL_ATOM_DECL(recvhoplimit); \
+ GLOBAL_ATOM_DECL(recvif); \
+ GLOBAL_ATOM_DECL(recvmsg); \
+ GLOBAL_ATOM_DECL(recvopts); \
+ GLOBAL_ATOM_DECL(recvorigdstaddr); \
+ GLOBAL_ATOM_DECL(recvpktinfo); \
+ GLOBAL_ATOM_DECL(recvtclass); \
+ GLOBAL_ATOM_DECL(recvtos); \
+ GLOBAL_ATOM_DECL(recvttl); \
+ GLOBAL_ATOM_DECL(reliability); \
+ GLOBAL_ATOM_DECL(renaming); \
+ GLOBAL_ATOM_DECL(reset_streams); \
+ GLOBAL_ATOM_DECL(retopts); \
+ GLOBAL_ATOM_DECL(reuseaddr); \
+ GLOBAL_ATOM_DECL(reuseport); \
+ GLOBAL_ATOM_DECL(rights); \
+ GLOBAL_ATOM_DECL(router_alert); \
+ GLOBAL_ATOM_DECL(rthdr); \
+ GLOBAL_ATOM_DECL(rtoinfo); \
+ GLOBAL_ATOM_DECL(running); \
+ GLOBAL_ATOM_DECL(rxq_ovfl); \
+ GLOBAL_ATOM_DECL(scope_id); \
+ GLOBAL_ATOM_DECL(sctp); \
+ GLOBAL_ATOM_DECL(sec); \
+ GLOBAL_ATOM_DECL(select); \
+ GLOBAL_ATOM_DECL(select_failed); \
+ GLOBAL_ATOM_DECL(select_sent); \
+ GLOBAL_ATOM_DECL(send); \
+ GLOBAL_ATOM_DECL(sendfile); \
+ GLOBAL_ATOM_DECL(sendfile_byte); \
+ GLOBAL_ATOM_DECL(sendfile_deferred_close); \
+ GLOBAL_ATOM_DECL(sendfile_fails); \
+ GLOBAL_ATOM_DECL(sendfile_max); \
+ GLOBAL_ATOM_DECL(sendfile_pkg); \
+ GLOBAL_ATOM_DECL(sendfile_pkg_max); \
+ GLOBAL_ATOM_DECL(sendfile_tries); \
+ GLOBAL_ATOM_DECL(sendfile_waits); \
+ GLOBAL_ATOM_DECL(sendmsg); \
+ GLOBAL_ATOM_DECL(sendsrcaddr); \
+ GLOBAL_ATOM_DECL(sendto); \
+ GLOBAL_ATOM_DECL(seqpacket); \
+ GLOBAL_ATOM_DECL(setfib); \
+ GLOBAL_ATOM_DECL(set_peer_primary_addr); \
+ GLOBAL_ATOM_DECL(simplex); \
+ GLOBAL_ATOM_DECL(slave); \
+ GLOBAL_ATOM_DECL(slen); \
+ GLOBAL_ATOM_DECL(sndbuf); \
+ GLOBAL_ATOM_DECL(sndbufforce); \
+ GLOBAL_ATOM_DECL(sndlowat); \
+ GLOBAL_ATOM_DECL(sndtimeo); \
+ GLOBAL_ATOM_DECL(sockaddr); \
+ GLOBAL_ATOM_DECL(socket); \
+ GLOBAL_ATOM_DECL(spec_dst); \
+ GLOBAL_ATOM_DECL(staticarp); \
+ GLOBAL_ATOM_DECL(state); \
+ GLOBAL_ATOM_DECL(status); \
+ GLOBAL_ATOM_DECL(stream); \
+ GLOBAL_ATOM_DECL(syncnt); \
+ GLOBAL_ATOM_DECL(tclass); \
+ GLOBAL_ATOM_DECL(tcp); \
+ GLOBAL_ATOM_DECL(throughput); \
+ GLOBAL_ATOM_DECL(timestamp); \
+ GLOBAL_ATOM_DECL(tos); \
+ GLOBAL_ATOM_DECL(transparent); \
+ GLOBAL_ATOM_DECL(timeout); \
+ GLOBAL_ATOM_DECL(true); \
+ GLOBAL_ATOM_DECL(trunc); \
+ GLOBAL_ATOM_DECL(ttl); \
+ GLOBAL_ATOM_DECL(tunnel); \
+ GLOBAL_ATOM_DECL(tunnel6); \
+ GLOBAL_ATOM_DECL(txqlen); \
+ GLOBAL_ATOM_DECL(type); \
+ GLOBAL_ATOM_DECL(udp); \
+ GLOBAL_ATOM_DECL(unblock_source); \
+ GLOBAL_ATOM_DECL(undefined); \
+ GLOBAL_ATOM_DECL(unicast_hops); \
+ GLOBAL_ATOM_DECL(unknown); \
+ GLOBAL_ATOM_DECL(unspec); \
+ GLOBAL_ATOM_DECL(up); \
+ GLOBAL_ATOM_DECL(update_accept_context); \
+ GLOBAL_ATOM_DECL(update_connect_context); \
+ GLOBAL_ATOM_DECL(usec); \
+ GLOBAL_ATOM_DECL(user); \
+ GLOBAL_ATOM_DECL(user_timeout); \
+ GLOBAL_ATOM_DECL(use_ext_recvinfo); \
+ GLOBAL_ATOM_DECL(use_min_mtu); \
+ GLOBAL_ATOM_DECL(use_registry); \
+ GLOBAL_ATOM_DECL(value); \
+ GLOBAL_ATOM_DECL(void); \
+ GLOBAL_ATOM_DECL(v6only); \
+ GLOBAL_ATOM_DECL(write_byte); \
+ GLOBAL_ATOM_DECL(write_fails); \
+ GLOBAL_ATOM_DECL(write_pkg); \
+ GLOBAL_ATOM_DECL(write_tries); \
+ GLOBAL_ATOM_DECL(write_waits); \
+ GLOBAL_ATOM_DECL(zero)
-struct ESockOpt
-{
- int opt; // Option number
- // Function to set option
- ERL_NIF_TERM (*setopt)
- (ErlNifEnv *env, ESockDescriptor *descP,
- int level, int opt, ERL_NIF_TERM eVal);
- // Function to get option
- ERL_NIF_TERM (*getopt)
- (ErlNifEnv *env, ESockDescriptor *descP,
- int level, int opt);
+/* *** Global error reason atoms *** */
+#define GLOBAL_ERROR_REASON_ATOMS \
+ GLOBAL_ATOM_DECL(create_accept_socket); \
+ GLOBAL_ATOM_DECL(eagain); \
+ GLOBAL_ATOM_DECL(einval); \
+ GLOBAL_ATOM_DECL(select_read); \
+ GLOBAL_ATOM_DECL(select_write)
- ERL_NIF_TERM *nameP; // Pointer to option name atom
+
+#define GLOBAL_ATOM_DECL(A) ERL_NIF_TERM esock_atom_##A
+GLOBAL_ATOMS;
+GLOBAL_ERROR_REASON_ATOMS;
+#undef GLOBAL_ATOM_DECL
+ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket')
+
+/* *** Local atoms *** */
+#define LOCAL_ATOMS \
+ LOCAL_ATOM_DECL(accepting); \
+ LOCAL_ATOM_DECL(adaptation_layer); \
+ LOCAL_ATOM_DECL(add); \
+ LOCAL_ATOM_DECL(addr_unreach); \
+ LOCAL_ATOM_DECL(address); \
+ LOCAL_ATOM_DECL(adm_prohibited); \
+ LOCAL_ATOM_DECL(association); \
+ LOCAL_ATOM_DECL(assoc_id); \
+ LOCAL_ATOM_DECL(authentication); \
+ LOCAL_ATOM_DECL(boolean); \
+ LOCAL_ATOM_DECL(bound); \
+ LOCAL_ATOM_DECL(bufsz); \
+ LOCAL_ATOM_DECL(close); \
+ LOCAL_ATOM_DECL(closing); \
+ LOCAL_ATOM_DECL(code); \
+ LOCAL_ATOM_DECL(cookie_life); \
+ LOCAL_ATOM_DECL(counter_wrap); \
+ LOCAL_ATOM_DECL(ctype); \
+ LOCAL_ATOM_DECL(data_io); \
+ LOCAL_ATOM_DECL(debug_filename); \
+ LOCAL_ATOM_DECL(del); \
+ LOCAL_ATOM_DECL(dest_unreach); \
+ LOCAL_ATOM_DECL(do); \
+ LOCAL_ATOM_DECL(dont); \
+ LOCAL_ATOM_DECL(dtor); \
+ LOCAL_ATOM_DECL(eei); \
+ LOCAL_ATOM_DECL(exclude); \
+ LOCAL_ATOM_DECL(false); \
+ LOCAL_ATOM_DECL(frag_needed); \
+ LOCAL_ATOM_DECL(gifaddr); \
+ LOCAL_ATOM_DECL(gifbrdaddr); \
+ LOCAL_ATOM_DECL(gifconf); \
+ LOCAL_ATOM_DECL(gifdstaddr); \
+ LOCAL_ATOM_DECL(gifflags); \
+ LOCAL_ATOM_DECL(gifhwaddr); \
+ LOCAL_ATOM_DECL(gifindex); \
+ LOCAL_ATOM_DECL(gifmap); \
+ LOCAL_ATOM_DECL(gifmtu); \
+ LOCAL_ATOM_DECL(gifname); \
+ LOCAL_ATOM_DECL(gifnetmask); \
+ LOCAL_ATOM_DECL(giftxqlen); \
+ LOCAL_ATOM_DECL(host_unknown); \
+ LOCAL_ATOM_DECL(host_unreach); \
+ LOCAL_ATOM_DECL(how); \
+ LOCAL_ATOM_DECL(in4_sockaddr); \
+ LOCAL_ATOM_DECL(in6_sockaddr); \
+ LOCAL_ATOM_DECL(include); \
+ LOCAL_ATOM_DECL(initial); \
+ LOCAL_ATOM_DECL(interface); \
+ LOCAL_ATOM_DECL(integer); \
+ LOCAL_ATOM_DECL(ioctl_flags); \
+ LOCAL_ATOM_DECL(ioctl_requests); \
+ LOCAL_ATOM_DECL(iov_max); \
+ LOCAL_ATOM_DECL(iow); \
+ LOCAL_ATOM_DECL(io_backend); \
+ LOCAL_ATOM_DECL(io_num_threads); \
+ LOCAL_ATOM_DECL(listening); \
+ LOCAL_ATOM_DECL(local_rwnd); \
+ LOCAL_ATOM_DECL(map); \
+ LOCAL_ATOM_DECL(max); \
+ LOCAL_ATOM_DECL(max_attempts); \
+ LOCAL_ATOM_DECL(max_init_timeo); \
+ LOCAL_ATOM_DECL(max_instreams); \
+ LOCAL_ATOM_DECL(asocmaxrxt); \
+ LOCAL_ATOM_DECL(min); \
+ LOCAL_ATOM_DECL(missing); \
+ LOCAL_ATOM_DECL(mode); \
+ LOCAL_ATOM_DECL(msg); \
+ LOCAL_ATOM_DECL(msg_flags); \
+ LOCAL_ATOM_DECL(mtu); \
+ LOCAL_ATOM_DECL(multiaddr); \
+ LOCAL_ATOM_DECL(net_unknown); \
+ LOCAL_ATOM_DECL(net_unreach); \
+ LOCAL_ATOM_DECL(nogroup); \
+ LOCAL_ATOM_DECL(none); \
+ LOCAL_ATOM_DECL(noroute); \
+ LOCAL_ATOM_DECL(not_neighbour); \
+ LOCAL_ATOM_DECL(null); \
+ LOCAL_ATOM_DECL(num_acceptors); \
+ LOCAL_ATOM_DECL(num_cnt_bits); \
+ LOCAL_ATOM_DECL(num_dinet); \
+ LOCAL_ATOM_DECL(num_dinet6); \
+ LOCAL_ATOM_DECL(num_dlocal); \
+ LOCAL_ATOM_DECL(num_outstreams); \
+ LOCAL_ATOM_DECL(number_peer_destinations); \
+ LOCAL_ATOM_DECL(num_pip); \
+ LOCAL_ATOM_DECL(num_psctp); \
+ LOCAL_ATOM_DECL(num_ptcp); \
+ LOCAL_ATOM_DECL(num_pudp); \
+ LOCAL_ATOM_DECL(num_readers); \
+ LOCAL_ATOM_DECL(num_sockets); \
+ LOCAL_ATOM_DECL(num_tdgrams); \
+ LOCAL_ATOM_DECL(num_tseqpkgs); \
+ LOCAL_ATOM_DECL(num_tstreams); \
+ LOCAL_ATOM_DECL(num_writers); \
+ LOCAL_ATOM_DECL(offender); \
+ LOCAL_ATOM_DECL(onoff); \
+ LOCAL_ATOM_DECL(options); \
+ LOCAL_ATOM_DECL(origin); \
+ LOCAL_ATOM_DECL(otp); \
+ LOCAL_ATOM_DECL(otp_socket_option);\
+ LOCAL_ATOM_DECL(owner); \
+ LOCAL_ATOM_DECL(partial_delivery); \
+ LOCAL_ATOM_DECL(peer_error); \
+ LOCAL_ATOM_DECL(peer_rwnd); \
+ LOCAL_ATOM_DECL(pkt_toobig); \
+ LOCAL_ATOM_DECL(policy_fail); \
+ LOCAL_ATOM_DECL(port); \
+ LOCAL_ATOM_DECL(port_unreach); \
+ LOCAL_ATOM_DECL(probe); \
+ LOCAL_ATOM_DECL(protocols); \
+ LOCAL_ATOM_DECL(rcvctrlbuf); \
+ LOCAL_ATOM_DECL(read); \
+ LOCAL_ATOM_DECL(read_pkg_max); \
+ LOCAL_ATOM_DECL(read_waits); \
+ LOCAL_ATOM_DECL(read_write); \
+ LOCAL_ATOM_DECL(registry); \
+ LOCAL_ATOM_DECL(reject_route); \
+ LOCAL_ATOM_DECL(remote); \
+ LOCAL_ATOM_DECL(rstates); \
+ LOCAL_ATOM_DECL(selected); \
+ LOCAL_ATOM_DECL(sender_dry); \
+ LOCAL_ATOM_DECL(send_failure); \
+ LOCAL_ATOM_DECL(shutdown); \
+ LOCAL_ATOM_DECL(sifaddr); \
+ LOCAL_ATOM_DECL(sifbrdaddr); \
+ LOCAL_ATOM_DECL(sifdstaddr); \
+ LOCAL_ATOM_DECL(sifflags); \
+ LOCAL_ATOM_DECL(sifmtu); \
+ LOCAL_ATOM_DECL(sifnetmask); \
+ LOCAL_ATOM_DECL(siftxqlen); \
+ LOCAL_ATOM_DECL(slist); \
+ LOCAL_ATOM_DECL(sndctrlbuf); \
+ LOCAL_ATOM_DECL(socket_debug); \
+ LOCAL_ATOM_DECL(socket_level); \
+ LOCAL_ATOM_DECL(socket_option); \
+ LOCAL_ATOM_DECL(sourceaddr); \
+ LOCAL_ATOM_DECL(time_exceeded); \
+ LOCAL_ATOM_DECL(true); \
+ LOCAL_ATOM_DECL(txstatus); \
+ LOCAL_ATOM_DECL(txtime); \
+ LOCAL_ATOM_DECL(want); \
+ LOCAL_ATOM_DECL(write); \
+ LOCAL_ATOM_DECL(write_pkg_max); \
+ LOCAL_ATOM_DECL(wstates); \
+ LOCAL_ATOM_DECL(zerocopy)
+
+/* Local error reason atoms
+ * Keep this (commented) for future use...
+ */
+/*
+#define LOCAL_ERROR_REASON_ATOMS \
+ LOCAL_ATOM_DECL(select_read); \
+ LOCAL_ATOM_DECL(select_write)
+*/
+#define LOCAL_ATOM_DECL(LA) static ERL_NIF_TERM atom_##LA
+LOCAL_ATOMS;
+// LOCAL_ERROR_REASON_ATOMS;
+#undef LOCAL_ATOM_DECL
+
+/* *** Sockets *** */
+static ErlNifResourceType* esocks;
+static ErlNifResourceTypeInit esockInit = {
+ esock_dtor,
+ esock_stop,
+ (ErlNifResourceDown*) esock_down
};
-// qsort and bsearch helper
-static int cmpESockOpt(const void *vpa, const void *vpb) {
- struct ESockOpt *a, *b;
- a = (struct ESockOpt *) vpa;
- b = (struct ESockOpt *) vpb;
- return a->opt < b->opt ? -1 : (a->opt > b->opt ? 1 : 0);
-}
+// Initiated when the nif is loaded
+static ESockData data;
+
+/* Jump table for the I/O backend (async or sync) */
+static ESockIoBackend io_backend = {0};
+
+
+/* This, the test for NULL), is temporary until we have a win stub */
+#define ESOCK_IO_INIT(NUMT) \
+ ((io_backend.init != NULL) ? \
+ io_backend.init((NUMT), &data) : ESOCK_IO_ERR_UNSUPPORTED)
+#define ESOCK_IO_FIN() \
+ ((io_backend.finish != NULL) ? \
+ io_backend.finish() : ESOCK_IO_ERR_UNSUPPORTED)
+
+#define ESOCK_IO_INFO(ENV) \
+ ((io_backend.info != NULL) ? \
+ io_backend.info((ENV)) : MKEMA((ENV)))
+#define ESOCK_IO_CMD(ENV, CMD, CDATA) \
+ ((io_backend.cmd != NULL) ? \
+ io_backend.cmd((ENV), (CMD), (CDATA)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SUPPORTS_0(ENV) \
+ ((io_backend.supports_0 != NULL) ? \
+ io_backend.supports_0((ENV)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SUPPORTS_1(ENV, KEY) \
+ ((io_backend.supports_1 != NULL) ? \
+ io_backend.supports_1((ENV), (KEY)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+
+#define ESOCK_IO_OPEN_WITH_FD(ENV, FD, EOPTS) \
+ ((io_backend.open_with_fd != NULL) ? \
+ io_backend.open_with_fd((ENV), (FD), (EOPTS), &data) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_OPEN_PLAIN(ENV, D, T, P, EOPTS) \
+ ((io_backend.open_plain != NULL) ? \
+ io_backend.open_plain((ENV), (D), \
+ (T), (P), (EOPTS), &data) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_BIND(ENV, D, SAP, AL) \
+ ((io_backend.bind != NULL) ? \
+ io_backend.bind((ENV), (D), (SAP), (AL)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_CONNECT(ENV, D, SR, CR, AP, AL) \
+ ((io_backend.connect != NULL) ? \
+ io_backend.connect((ENV), (D), \
+ (SR), (CR), (AP), (AL)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_LISTEN(ENV, D, BL) \
+ ((io_backend.listen != NULL) ? \
+ io_backend.listen((ENV), (D), (BL)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_ACCEPT(ENV, D, SR, AR) \
+ ((io_backend.accept != NULL) ? \
+ io_backend.accept((ENV), (D), (SR), (AR)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SEND(ENV, D, SR, RF, L, F) \
+ ((io_backend.send != NULL) ? \
+ io_backend.send((ENV), (D), \
+ (SR), (RF), (L), (F)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SENDTO(ENV, D, \
+ SOCKR, SENDR, \
+ DP, F, TAP, TAL) \
+ ((io_backend.sendto != NULL) ? \
+ io_backend.sendto((ENV), (D), \
+ (SOCKR), (SENDR), \
+ (DP), (F), (TAP), (TAL)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SENDMSG(ENV, D, \
+ SOCKR, SENDR, EM, F, EIOV) \
+ ((io_backend.sendmsg != NULL) ? \
+ io_backend.sendmsg((ENV), (D), \
+ (SOCKR), (SENDR), \
+ (EM), (F), (EIOV), &data) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SENDFILE_START(ENV, D, \
+ SOR, SNR, \
+ O, CN, FR) \
+ ((io_backend.sendfile_start != NULL) ? \
+ io_backend.sendfile_start((ENV), (D), \
+ (SOR), (SNR), \
+ (O), (CN), (FR)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SENDFILE_CONT(ENV, D, \
+ SOR, SNR, \
+ O, CP) \
+ ((io_backend.sendfile_cont != NULL) ? \
+ io_backend.sendfile_cont((ENV), (D), \
+ (SOR), (SNR), \
+ (O), (CP)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SENDFILE_DC(ENV, D) \
+ ((io_backend.sendfile_dc != NULL) ? \
+ io_backend.sendfile_dc((ENV), (D)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_RECV(ENV, D, \
+ SR, RR, L, F) \
+ ((io_backend.recv != NULL) ? \
+ io_backend.recv((ENV), (D), \
+ (SR), (RR), (L), (F)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_RECVFROM(ENV, D, \
+ SR, RR, L, F) \
+ ((io_backend.recvfrom != NULL) ? \
+ io_backend.recvfrom((ENV), (D), \
+ (SR), (RR), (L), (F)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_RECVMSG(ENV, D, \
+ SR, RR, BL, CL, F) \
+ ((io_backend.recvmsg != NULL) ? \
+ io_backend.recvmsg((ENV), (D), \
+ (SR), (RR), \
+ (BL), (CL), (F)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_CLOSE(ENV, D) \
+ ((io_backend.close != NULL) ? \
+ io_backend.close((ENV), (D)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_FIN_CLOSE(ENV, D) \
+ ((io_backend.fin_close != NULL) ? \
+ io_backend.fin_close((ENV), (D)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SHUTDOWN(ENV, D, H) \
+ ((io_backend.shutdown != NULL) ? \
+ io_backend.shutdown((ENV), (D), (H)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SOCKNAME(ENV, D) \
+ ((io_backend.sockname != NULL) ? \
+ io_backend.sockname((ENV), (D)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_PEERNAME(ENV, D) \
+ ((io_backend.peername != NULL) ? \
+ io_backend.peername((ENV), (D)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_CANCEL_CONNECT(ENV, D, OR) \
+ ((io_backend.cancel_connect != NULL) ? \
+ io_backend.cancel_connect((ENV), (D), (OR)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_CANCEL_ACCEPT(ENV, D, SR, OR) \
+ ((io_backend.cancel_accept != NULL) ? \
+ io_backend.cancel_accept((ENV), (D), (SR), (OR)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_CANCEL_SEND(ENV, D, SR, OR) \
+ ((io_backend.cancel_send != NULL) ? \
+ io_backend.cancel_send((ENV), (D), (SR), (OR)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_CANCEL_RECV(ENV, D, SR, OR) \
+ ((io_backend.cancel_recv != NULL) ? \
+ io_backend.cancel_recv((ENV), (D), (SR), (OR)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SETOPT(ENV, D, L, O, EV) \
+ ((io_backend.setopt != NULL) ? \
+ io_backend.setopt((ENV), (D), (L), (O), (EV)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SETOPT_NATIVE(ENV, D, L, O, EV) \
+ ((io_backend.setopt_native != NULL) ? \
+ io_backend.setopt_native((ENV), (D), (L), (O), (EV)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_SETOPT_OTP(ENV, D, L, O) \
+ ((io_backend.setopt_otp != NULL) ? \
+ io_backend.setopt_otp((ENV), (D), (L), (O)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_GETOPT(ENV, D, L, O) \
+ ((io_backend.getopt != NULL) ? \
+ io_backend.getopt((ENV), (D), (L), (O)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_GETOPT_NATIVE(ENV, D, L, O, VS) \
+ ((io_backend.getopt_native != NULL) ? \
+ io_backend.getopt_native((ENV), (D), (L), (O), (VS)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_GETOPT_OTP(ENV, D, EO) \
+ ((io_backend.getopt_otp != NULL) ? \
+ io_backend.getopt_otp((ENV), (D), (EO)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_IOCTL_2(ENV, D, R) \
+ ((io_backend.ioctl_2 != NULL) ? \
+ io_backend.ioctl_2((ENV), (D), (R)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_IOCTL_3(ENV, D, R, A) \
+ ((io_backend.ioctl_3 != NULL) ? \
+ io_backend.ioctl_3((ENV), (D), (R), (A)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+#define ESOCK_IO_IOCTL_4(ENV, D, R, A1, A2) \
+ ((io_backend.ioctl_4 != NULL) ? \
+ io_backend.ioctl_4((ENV), (D), (R), (A1), (A2)) : \
+ enif_raise_exception((ENV), MKA((ENV), "notsup")))
+
+#define ESOCK_IO_DTOR(ENV, D) \
+ ((io_backend.dtor != NULL) ? \
+ io_backend.dtor((ENV), (D)) : ((void) (D)))
+#define ESOCK_IO_STOP(ENV, D) \
+ ((io_backend.stop != NULL) ? \
+ io_backend.stop((ENV), (D)) : ((void) (D)))
+#define ESOCK_IO_DOWN(ENV, D, PP, MP) \
+ ((io_backend.down != NULL) ? \
+ io_backend.down((ENV), (D), (PP), (MP)) : ((void) (D)))
+
+/* ------------------------------------------------------------------------
+ * Socket option tables and handling
+ */
+
/* SO_* options -------------------------------------------------------- */
static struct ESockOpt optLevelSocket[] =
@@ -3111,11 +3334,11 @@ static struct ESockOpt optLevelIPV6[] =
{
#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
#if defined(IPV6_RECVPKTINFO)
- IPV6_RECVPKTINFO,
+ IPV6_RECVPKTINFO,
#else
- IPV6_PKTINFO,
+ IPV6_PKTINFO,
#endif
- esock_setopt_bool_opt, esock_getopt_bool_opt,
+ esock_setopt_bool_opt, esock_getopt_bool_opt,
#else
0, NULL, NULL,
#endif
@@ -3177,7 +3400,7 @@ static struct ESockOpt optLevelIPV6[] =
#endif
&esock_atom_v6only}
- };
+ };
#endif // #ifdef HAVE_IPV6
@@ -3366,60 +3589,53 @@ static struct ESockOpt optLevelUDP[] =
};
-/* Option levels table ------------------------------------------------- */
-struct ESockOptLevel
-{
- int level; // Level number
-
- size_t num; // Number of options
-
- struct ESockOpt *opts; // Options table
-};
+/* Option levels utility macro */
-// qsort and bsearch helper
-static int cmpESockOptLevel(const void *vpa, const void *vpb) {
- struct ESockOptLevel *a, *b;
- a = (struct ESockOptLevel*) vpa;
- b = (struct ESockOptLevel*) vpb;
- return a->level < b->level ? -1 : (a->level > b->level ? 1 : 0);
-}
+#define OPT_LEVEL(Level, Opts, Name) {(Level), NUM(Opts), (Opts), (Name)}
-#define OPT_LEVEL(Level, Opts) {(Level), NUM(Opts), (Opts)}
/* Table --------------------------------------------------------------- */
static struct ESockOptLevel optLevels[] =
{
- OPT_LEVEL(SOL_SOCKET, optLevelSocket),
+ OPT_LEVEL(SOL_SOCKET, optLevelSocket, &esock_atom_socket),
+#ifndef __WIN32__
#ifdef SOL_IP
- OPT_LEVEL(SOL_IP, optLevelIP),
+ OPT_LEVEL(SOL_IP, optLevelIP, &esock_atom_ip),
+#else
+ OPT_LEVEL(IPPROTO_IP, optLevelIP, &esock_atom_ip),
+#endif
#else
- OPT_LEVEL(IPPROTO_IP, optLevelIP),
+ OPT_LEVEL(IPPROTO_IP, optLevelIP, &esock_atom_ip),
#endif
#ifdef HAVE_IPV6
+#ifndef __WIN32__
#ifdef SOL_IPV6
- OPT_LEVEL(SOL_IPV6, optLevelIPV6),
+ OPT_LEVEL(SOL_IPV6, optLevelIPV6, &esock_atom_ipv6),
+#else
+ OPT_LEVEL(IPPROTO_IPV6, optLevelIPV6, &esock_atom_ipv6),
+#endif
#else
- OPT_LEVEL(IPPROTO_IPV6, optLevelIPV6),
+ OPT_LEVEL(IPPROTO_IPV6, optLevelIPV6, &esock_atom_ipv6),
#endif
#endif // #ifdef HAVE_IPV6
#ifdef HAVE_SCTP
- OPT_LEVEL(IPPROTO_SCTP, optLevelSCTP),
+ OPT_LEVEL(IPPROTO_SCTP, optLevelSCTP, &esock_atom_sctp),
#endif // #ifdef HAVE_SCTP
- OPT_LEVEL(IPPROTO_UDP, optLevelUDP),
- OPT_LEVEL(IPPROTO_TCP, optLevelTCP)
+ OPT_LEVEL(IPPROTO_UDP, optLevelUDP, &esock_atom_udp),
+ OPT_LEVEL(IPPROTO_TCP, optLevelTCP, &esock_atom_tcp)
};
#undef OPT_LEVEL
/* Tables init (sorting) ----------------------------------------------- */
-#define ESOCK_SORT_TABLE(Array, Cmp) \
+#define ESOCK_SORT_TABLE(Array, Cmp) \
qsort((Array), NUM(Array), sizeof(*(Array)), (Cmp))
static void initOpts(void) {
@@ -3436,1085 +3652,89 @@ static void initOpts(void) {
ESOCK_SORT_TABLE(optLevels, cmpESockOptLevel);
}
+
+/* ------------------------------------------------------------------------
+ * Socket option tables and handling
+ */
+
+// qsort and bsearch helper(s)
+static
+int cmpESockOpt(const void *vpa, const void *vpb)
+{
+ struct ESockOpt *a, *b;
+ a = (struct ESockOpt *) vpa;
+ b = (struct ESockOpt *) vpb;
+ return a->opt < b->opt ? -1 : (a->opt > b->opt ? 1 : 0);
+}
+
+static
+int cmpESockOptLevel(const void *vpa, const void *vpb)
+{
+ struct ESockOptLevel *a, *b;
+ a = (struct ESockOptLevel*) vpa;
+ b = (struct ESockOptLevel*) vpb;
+ return a->level < b->level ? -1 : (a->level > b->level ? 1 : 0);
+}
+
/* Option lookup in tables --------------------------------------------- */
-static struct ESockOpt *lookupOpt(int level, int opt) {
+static
+struct ESockOpt *lookupOpt(int level, int opt)
+{
struct ESockOptLevel levelKey, *levelP;
struct ESockOpt optKey;
sys_memzero((char *) &levelKey, sizeof(levelKey));
levelKey.level = level;
- levelP =
- bsearch(&levelKey, optLevels, NUM(optLevels), sizeof(*optLevels),
- cmpESockOptLevel);
+ levelP = bsearch(&levelKey, optLevels, NUM(optLevels), sizeof(*optLevels),
+ cmpESockOptLevel);
if (levelP == NULL)
return NULL;
sys_memzero((char *) &optKey, sizeof(optKey));
optKey.opt = opt;
- return
- bsearch(&optKey, levelP->opts, levelP->num, sizeof(*levelP->opts),
- cmpESockOpt);
+ return bsearch(&optKey, levelP->opts, levelP->num, sizeof(*levelP->opts),
+ cmpESockOpt);
}
-/* --------------------------------------------------------------------- */
-
-
-
-static BOOLEAN_T send_check_writer(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM ref,
- ERL_NIF_TERM* checkResult);
-static ERL_NIF_TERM send_check_result(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t send_result,
- ssize_t dataSize,
- BOOLEAN_T dataInTail,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef);
-static ERL_NIF_TERM send_check_ok(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t written,
- ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM send_check_fail(ErlNifEnv* env,
- ESockDescriptor* descP,
- int saveErrno,
- ERL_NIF_TERM sockRef);
-static void send_error_waiting_writers(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM reason);
-static ERL_NIF_TERM send_check_retry(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t written,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef);
-static BOOLEAN_T recv_check_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM ref,
- ERL_NIF_TERM* checkResult);
-static void recv_init_current_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM ref);
-static void recv_update_current_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef);
-static void recv_error_current_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM reason);
-static ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ssize_t toRead,
- int saveErrno,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef);
-static ERL_NIF_TERM recv_check_full(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ssize_t toRead,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef);
-static ERL_NIF_TERM recv_check_full_maybe_done(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef);
-static ERL_NIF_TERM recv_check_full_done(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM recv_check_fail(ErlNifEnv* env,
- ESockDescriptor* descP,
- int saveErrno,
- ErlNifBinary* buf1P,
- ErlNifBinary* buf2P,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef);
-static ERL_NIF_TERM recv_check_fail_econnreset(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef);
-static ERL_NIF_TERM recv_check_partial(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ssize_t toRead,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef);
-static ERL_NIF_TERM recv_check_partial_done(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM recv_check_partial_part(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef);
-static ERL_NIF_TERM recv_check_retry(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef);
-static ERL_NIF_TERM recv_check_fail_gen(ErlNifEnv* env,
- ESockDescriptor* descP,
- int saveErrno,
- ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- int saveErrno,
- ErlNifBinary* bufP,
- ESockAddress* fromAddrP,
- SOCKLEN_T fromAddrLen,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef);
-static ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- int saveErrno,
- struct msghdr* msgHdrP,
- ErlNifBinary* dataBufP,
- ErlNifBinary* ctrlBufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef);
-static ERL_NIF_TERM recvmsg_check_msg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- struct msghdr* msgHdrP,
- ErlNifBinary* dataBufP,
- ErlNifBinary* ctrlBufP,
- ERL_NIF_TERM sockRef);
-
-static ERL_NIF_TERM esock_finalize_close(ErlNifEnv* env,
- ESockDescriptor* descP);
-static int esock_close_socket(ErlNifEnv* env,
- ESockDescriptor* descP,
- BOOLEAN_T unlock);
-
-static void encode_msg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- struct msghdr* msgHdrP,
- ErlNifBinary* dataBufP,
- ErlNifBinary* ctrlBufP,
- ERL_NIF_TERM* eSockAddr);
-static void encode_cmsgs(ErlNifEnv* env,
- ESockDescriptor* descP,
- ErlNifBinary* cmsgBinP,
- struct msghdr* msgHdrP,
- ERL_NIF_TERM* eCMsg);
-static BOOLEAN_T encode_cmsg(ErlNifEnv* env,
- int level,
- int type,
- unsigned char* dataP,
- size_t dataLen,
- ERL_NIF_TERM* eType,
- ERL_NIF_TERM* eData);
-static BOOLEAN_T decode_cmsghdrs(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eCMsg,
- char* cmsgHdrBufP,
- size_t cmsgHdrBufLen,
- size_t* cmsgHdrBufUsed);
-static BOOLEAN_T decode_cmsghdr(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eCMsg,
- char* bufP,
- size_t rem,
- size_t* used);
-static BOOLEAN_T decode_cmsghdr_value(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- ERL_NIF_TERM eType,
- ERL_NIF_TERM eValue,
- char* dataP,
- size_t dataLen,
- size_t* dataUsedP);
-static BOOLEAN_T decode_cmsghdr_data(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- ERL_NIF_TERM eType,
- ERL_NIF_TERM eData,
- char* dataP,
- size_t dataLen,
- size_t* dataUsedP);
-static void *init_cmsghdr(struct cmsghdr* cmsgP,
- size_t rem,
- size_t size,
- size_t* usedP);
-static void encode_msg_flags(ErlNifEnv* env,
- ESockDescriptor* descP,
- int msgFlags,
- ERL_NIF_TERM* flags);
-#if defined(IP_TOS)
-static BOOLEAN_T decode_ip_tos(ErlNifEnv* env,
- ERL_NIF_TERM eVal,
- int* val);
-#endif
-#if defined(IP_MTU_DISCOVER)
-static BOOLEAN_T decode_ip_pmtudisc(ErlNifEnv* env,
- ERL_NIF_TERM eVal,
- int* val);
-#endif
-#if defined(IP_MTU_DISCOVER)
-static void encode_ip_pmtudisc(ErlNifEnv* env,
- int val,
- ERL_NIF_TERM* eVal);
-#endif
-#if defined(IPV6_MTU_DISCOVER)
-static BOOLEAN_T decode_ipv6_pmtudisc(ErlNifEnv* env,
- ERL_NIF_TERM eVal,
- int* val);
-#endif
-#if defined(IPV6_MTU_DISCOVER)
-static void encode_ipv6_pmtudisc(ErlNifEnv* env,
- int val,
- ERL_NIF_TERM* eVal);
-#endif
-#if defined(IPV6_MULTICAST_HOPS) || defined(IPV6_UNICAST_HOPS)
-static
-BOOLEAN_T decode_hops(ErlNifEnv *env, ERL_NIF_TERM eVal, int *val);
-#endif
-
-/*
-static BOOLEAN_T decode_bool(ErlNifEnv* env,
- ERL_NIF_TERM eVal,
- BOOLEAN_T* val);
-*/
-// static void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal);
-static ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val);
-
-#if defined(SCTP_ASSOCINFO) || defined(SCTP_RTOINOFO)
-static BOOLEAN_T decode_sctp_assoc_t(ErlNifEnv* env,
- ERL_NIF_TERM eVal,
- sctp_assoc_t* val);
-static ERL_NIF_TERM encode_sctp_assoc_t(ErlNifEnv* env,
- sctp_assoc_t val);
-#endif // #if defined(SCTP_ASSOCINFO) || defined(SCTP_RTOINOFO)
-
-static void esock_stop_handle_current(ErlNifEnv* env,
- const char* role,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ESockRequestor* reqP);
-static void inform_waiting_procs(ErlNifEnv* env,
- const char* role,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ESockRequestQueue* q,
- ERL_NIF_TERM reason);
-
-static int socket_setopt(int sock,
- int level,
- int opt,
- const void* optVal,
- const socklen_t optLen);
-
-static BOOLEAN_T verify_is_connected(ESockDescriptor* descP, int* err);
-
-static ESockDescriptor* alloc_descriptor(SOCKET sock, ErlNifEvent event);
-
-
-static BOOLEAN_T ehow2how(ERL_NIF_TERM ehow, int* how);
-#ifdef HAVE_SETNS
-static BOOLEAN_T esock_open4_get_netns(ErlNifEnv* env,
- ERL_NIF_TERM opts,
- char** netns);
-static BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err);
-static BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err);
-#endif
-
-static BOOLEAN_T cnt_inc(ESockCounter* cnt, ESockCounter inc);
-static void cnt_dec(ESockCounter* cnt, ESockCounter dec);
-
-static void inc_socket(int domain, int type, int protocol);
-static void dec_socket(int domain, int type, int protocol);
-
-
-
-/* *** activate_next_acceptor ***
- * *** activate_next_writer ***
- * *** activate_next_reader ***
- *
- * All the activate-next functions for acceptor, writer and reader
- * have exactly the same API, so we apply some macro magic to simplify.
- * They simply operates on dufferent data structures.
- *
- */
-
-#define ACTIVATE_NEXT_FUNCS_DEFS \
- ACTIVATE_NEXT_FUNC_DEF(acceptor) \
- ACTIVATE_NEXT_FUNC_DEF(writer) \
- ACTIVATE_NEXT_FUNC_DEF(reader)
-
-#define ACTIVATE_NEXT_FUNC_DEF(F) \
- static BOOLEAN_T activate_next_##F(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ERL_NIF_TERM sockRef);
-ACTIVATE_NEXT_FUNCS_DEFS
-#undef ACTIVATE_NEXT_FUNC_DEF
-
-/* *** acceptor_search4pid | writer_search4pid | reader_search4pid ***
- * *** acceptor_push | writer_push | reader_push ***
- * *** acceptor_pop | writer_pop | reader_pop ***
- * *** acceptor_unqueue | writer_unqueue | reader_unqueue ***
- *
- * All the queue operator functions (search4pid, push, pop
- * and unqueue) for acceptor, writer and reader has exactly
- * the same API, so we apply some macro magic to simplify.
- */
-
-#define ESOCK_OPERATOR_FUNCS_DEFS \
- ESOCK_OPERATOR_FUNCS_DEF(acceptor) \
- ESOCK_OPERATOR_FUNCS_DEF(writer) \
- ESOCK_OPERATOR_FUNCS_DEF(reader)
-
-#define ESOCK_OPERATOR_FUNCS_DEF(O) \
- static BOOLEAN_T O##_search4pid(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ErlNifPid* pid); \
- static void O##_push(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ErlNifPid pid, \
- ERL_NIF_TERM ref); \
- static BOOLEAN_T O##_pop(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ESockRequestor* reqP); \
- static BOOLEAN_T O##_unqueue(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ERL_NIF_TERM* refP, \
- const ErlNifPid* pidP);
-ESOCK_OPERATOR_FUNCS_DEFS
-#undef ESOCK_OPERATOR_FUNCS_DEF
-static BOOLEAN_T requestor_pop(ESockRequestQueue* q,
- ESockRequestor* reqP);
-static void requestor_init(ESockRequestor* reqP);
-static void requestor_release(const char* slogan,
- ErlNifEnv* env,
- ESockDescriptor* descP,
- ESockRequestor* reqP);
-
-static BOOLEAN_T qsearch4pid(ErlNifEnv* env,
- ESockRequestQueue* q,
- ErlNifPid* pid);
-static void qpush(ESockRequestQueue* q,
- ESockRequestQueueElement* e);
-static ESockRequestQueueElement* qpop(ESockRequestQueue* q);
-static BOOLEAN_T qunqueue(ErlNifEnv* env,
- ESockDescriptor* descP,
- const char* slogan,
- ESockRequestQueue* q,
- ERL_NIF_TERM* refP,
- const ErlNifPid* pidP);
-
-static int esock_monitor(const char* slogan,
- ErlNifEnv* env,
- ESockDescriptor* descP,
- const ErlNifPid* pid,
- ESockMonitor* mon);
-static int esock_demonitor(const char* slogan,
- ErlNifEnv* env,
- ESockDescriptor* descP,
- ESockMonitor* monP);
-static void esock_monitor_init(ESockMonitor* mon);
-static ERL_NIF_TERM esock_make_monitor_term(ErlNifEnv* env,
- const ESockMonitor* monP);
-static BOOLEAN_T esock_monitor_eq(const ESockMonitor* monP,
- const ErlNifMonitor* mon);
-
-
-
-static void esock_down_ctrl(ErlNifEnv* env,
- ESockDescriptor* descP,
- const ErlNifPid* pidP);
-static void esock_down_acceptor(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- const ErlNifPid* pidP,
- const ErlNifMonitor* monP);
-static void esock_down_writer(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- const ErlNifPid* pidP,
- const ErlNifMonitor* monP);
-static void esock_down_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- const ErlNifPid* pidP,
- const ErlNifMonitor* monP);
-
-static void esock_send_reg_add_msg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef);
-static void esock_send_reg_del_msg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef);
-
-static void esock_send_wrap_msg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM cnt);
-static void esock_send_close_msg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ErlNifPid* pid);
-#ifdef HAVE_SENDFILE
-static void
-esock_send_sendfile_deferred_close_msg(ErlNifEnv* env,
- ESockDescriptor* descP);
-#endif
-static void esock_send_abort_msg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ESockRequestor* reqP,
- ERL_NIF_TERM reason);
-static BOOLEAN_T esock_send_msg(ErlNifEnv* env,
- ErlNifPid* pid,
- ERL_NIF_TERM msg,
- ErlNifEnv* msgEnv);
-
-static ERL_NIF_TERM mk_reg_add_msg(ErlNifEnv* env,
- ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM mk_reg_del_msg(ErlNifEnv* env,
- ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM mk_reg_msg(ErlNifEnv* env,
- ERL_NIF_TERM tag,
- ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM mk_abort_msg(ErlNifEnv* env,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM opRef,
- ERL_NIF_TERM reason);
-static ERL_NIF_TERM mk_wrap_msg(ErlNifEnv* env,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM cnt);
-static ERL_NIF_TERM mk_close_msg(ErlNifEnv* env,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM closeRef);
-static ERL_NIF_TERM mk_select_msg(ErlNifEnv* env,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM selectRef);
-static ERL_NIF_TERM mk_socket_msg(ErlNifEnv* env,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM tag,
- ERL_NIF_TERM info);
-static ERL_NIF_TERM mk_socket(ErlNifEnv* env,
- ERL_NIF_TERM sockRef);
-
-static int esock_select_read(ErlNifEnv* env,
- ErlNifEvent event,
- void* obj,
- const ErlNifPid* pidP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM selectRef);
-static int esock_select_write(ErlNifEnv* env,
- ErlNifEvent event,
- void* obj,
- const ErlNifPid* pidP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM selectRef);
-static int esock_select_stop(ErlNifEnv* env,
- ErlNifEvent event,
- void* obj);
-static int esock_select_cancel(ErlNifEnv* env,
- ErlNifEvent event,
- enum ErlNifSelectFlags mode,
- void* obj);
-
-static char* extract_debug_filename(ErlNifEnv* env,
- ERL_NIF_TERM map);
-
-
-/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *
- * *
- * *
- * End of non-__WIN32__ section a.k.a UNIX section *
- * *
- * *
- * ---------------------------------------------------------------------- */
-#endif // #ifndef __WIN32__
-
-
-/*
-#if defined(HAS_AF_LOCAL) || defined(SO_BINDTODEVICE)
-static size_t my_strnlen(const char *s, size_t maxlen);
-#endif
-*/
-
-static void esock_dtor(ErlNifEnv* env, void* obj);
-static void esock_stop(ErlNifEnv* env,
- void* obj,
- ErlNifEvent fd,
- int is_direct_call);
-static void esock_down(ErlNifEnv* env,
- void* obj,
- const ErlNifPid* pidP,
- const ErlNifMonitor* monP);
-
-static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info);
-
-
-
-#if HAVE_IN6
-# if ! defined(HAVE_IN6ADDR_ANY) || ! HAVE_IN6ADDR_ANY
-# if HAVE_DECL_IN6ADDR_ANY_INIT
-static const struct in6_addr in6addr_any = { { IN6ADDR_ANY_INIT } };
-# else
-static const struct in6_addr in6addr_any =
- { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } };
-# endif /* HAVE_IN6ADDR_ANY_INIT */
-# endif /* ! HAVE_DECL_IN6ADDR_ANY */
-
-# if ! defined(HAVE_IN6ADDR_LOOPBACK) || ! HAVE_IN6ADDR_LOOPBACK
-# if HAVE_DECL_IN6ADDR_LOOPBACK_INIT
-static const struct in6_addr in6addr_loopback =
- { { IN6ADDR_LOOPBACK_INIT } };
-# else
-static const struct in6_addr in6addr_loopback =
- { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } };
-# endif /* HAVE_IN6ADDR_LOOPBACk_INIT */
-# endif /* ! HAVE_DECL_IN6ADDR_LOOPBACK */
-#endif /* HAVE_IN6 */
-
-
-
-/* *** Global atoms ***
- * Note that when an (global) atom is added here, it must also be added
- * in the socket_int.h file!
+/* These three (inline) functions are primarily intended for debugging,
+ * that is, to make it easy to add debug printouts.
*/
-#define GLOBAL_ATOMS \
- GLOBAL_ATOM_DECL(abort); \
- GLOBAL_ATOM_DECL(accept); \
- GLOBAL_ATOM_DECL(acceptconn); \
- GLOBAL_ATOM_DECL(acceptfilter); \
- GLOBAL_ATOM_DECL(adaption_layer); \
- GLOBAL_ATOM_DECL(addr); \
- GLOBAL_ATOM_DECL(addrform); \
- GLOBAL_ATOM_DECL(add_membership); \
- GLOBAL_ATOM_DECL(add_source_membership); \
- GLOBAL_ATOM_DECL(alen); \
- GLOBAL_ATOM_DECL(allmulti); \
- GLOBAL_ATOM_DECL(any); \
- GLOBAL_ATOM_DECL(appletlk); \
- GLOBAL_ATOM_DECL(arcnet); \
- GLOBAL_ATOM_DECL(associnfo); \
- GLOBAL_ATOM_DECL(atm); \
- GLOBAL_ATOM_DECL(authhdr); \
- GLOBAL_ATOM_DECL(auth_active_key); \
- GLOBAL_ATOM_DECL(auth_asconf); \
- GLOBAL_ATOM_DECL(auth_chunk); \
- GLOBAL_ATOM_DECL(auth_delete_key); \
- GLOBAL_ATOM_DECL(auth_key); \
- GLOBAL_ATOM_DECL(auth_level); \
- GLOBAL_ATOM_DECL(autoclose); \
- GLOBAL_ATOM_DECL(automedia); \
- GLOBAL_ATOM_DECL(ax25); \
- GLOBAL_ATOM_DECL(bad_data); \
- GLOBAL_ATOM_DECL(bindtodevice); \
- GLOBAL_ATOM_DECL(block_source); \
- GLOBAL_ATOM_DECL(broadcast); \
- GLOBAL_ATOM_DECL(busy_poll); \
- GLOBAL_ATOM_DECL(cantconfig); \
- GLOBAL_ATOM_DECL(chaos); \
- GLOBAL_ATOM_DECL(checksum); \
- GLOBAL_ATOM_DECL(close); \
- GLOBAL_ATOM_DECL(cmsg_cloexec); \
- GLOBAL_ATOM_DECL(command); \
- GLOBAL_ATOM_DECL(confirm); \
- GLOBAL_ATOM_DECL(congestion); \
- GLOBAL_ATOM_DECL(connect); \
- GLOBAL_ATOM_DECL(connected); \
- GLOBAL_ATOM_DECL(connecting); \
- GLOBAL_ATOM_DECL(context); \
- GLOBAL_ATOM_DECL(cork); \
- GLOBAL_ATOM_DECL(credentials); \
- GLOBAL_ATOM_DECL(ctrl); \
- GLOBAL_ATOM_DECL(ctrunc); \
- GLOBAL_ATOM_DECL(data); \
- GLOBAL_ATOM_DECL(debug); \
- GLOBAL_ATOM_DECL(default); \
- GLOBAL_ATOM_DECL(default_send_params); \
- GLOBAL_ATOM_DECL(delayed_ack_time); \
- GLOBAL_ATOM_DECL(dgram); \
- GLOBAL_ATOM_DECL(disable_fragments); \
- GLOBAL_ATOM_DECL(dlci); \
- GLOBAL_ATOM_DECL(domain); \
- GLOBAL_ATOM_DECL(dontfrag); \
- GLOBAL_ATOM_DECL(dontroute); \
- GLOBAL_ATOM_DECL(dormant); \
- GLOBAL_ATOM_DECL(drop_membership); \
- GLOBAL_ATOM_DECL(drop_source_membership); \
- GLOBAL_ATOM_DECL(dstopts); \
- GLOBAL_ATOM_DECL(dying); \
- GLOBAL_ATOM_DECL(dynamic); \
- GLOBAL_ATOM_DECL(echo); \
- GLOBAL_ATOM_DECL(eether); \
- GLOBAL_ATOM_DECL(egp); \
- GLOBAL_ATOM_DECL(enotsup); \
- GLOBAL_ATOM_DECL(eor); \
- GLOBAL_ATOM_DECL(error); \
- GLOBAL_ATOM_DECL(errqueue); \
- GLOBAL_ATOM_DECL(esp_network_level); \
- GLOBAL_ATOM_DECL(esp_trans_level); \
- GLOBAL_ATOM_DECL(ether); \
- GLOBAL_ATOM_DECL(eui64); \
- GLOBAL_ATOM_DECL(events); \
- GLOBAL_ATOM_DECL(explicit_eor); \
- GLOBAL_ATOM_DECL(faith); \
- GLOBAL_ATOM_DECL(false); \
- GLOBAL_ATOM_DECL(family); \
- GLOBAL_ATOM_DECL(fastroute); \
- GLOBAL_ATOM_DECL(flags); \
- GLOBAL_ATOM_DECL(flowinfo); \
- GLOBAL_ATOM_DECL(fragment_interleave); \
- GLOBAL_ATOM_DECL(freebind); \
- GLOBAL_ATOM_DECL(frelay); \
- GLOBAL_ATOM_DECL(get_peer_addr_info); \
- GLOBAL_ATOM_DECL(hatype); \
- GLOBAL_ATOM_DECL(hdrincl); \
- GLOBAL_ATOM_DECL(hmac_ident); \
- GLOBAL_ATOM_DECL(hoplimit); \
- GLOBAL_ATOM_DECL(hopopts); \
- GLOBAL_ATOM_DECL(host); \
- GLOBAL_ATOM_DECL(icmp); \
- GLOBAL_ATOM_DECL(icmp6); \
- GLOBAL_ATOM_DECL(ieee802); \
- GLOBAL_ATOM_DECL(ieee1394); \
- GLOBAL_ATOM_DECL(ifindex); \
- GLOBAL_ATOM_DECL(igmp); \
- GLOBAL_ATOM_DECL(implink); \
- GLOBAL_ATOM_DECL(index); \
- GLOBAL_ATOM_DECL(inet); \
- GLOBAL_ATOM_DECL(inet6); \
- GLOBAL_ATOM_DECL(infiniband); \
- GLOBAL_ATOM_DECL(info); \
- GLOBAL_ATOM_DECL(initmsg); \
- GLOBAL_ATOM_DECL(invalid); \
- GLOBAL_ATOM_DECL(integer_range); \
- GLOBAL_ATOM_DECL(iov); \
- GLOBAL_ATOM_DECL(ip); \
- GLOBAL_ATOM_DECL(ipcomp_level); \
- GLOBAL_ATOM_DECL(ipip); \
- GLOBAL_ATOM_DECL(ipv6); \
- GLOBAL_ATOM_DECL(i_want_mapped_v4_addr); \
- GLOBAL_ATOM_DECL(join_group); \
- GLOBAL_ATOM_DECL(keepalive); \
- GLOBAL_ATOM_DECL(keepcnt); \
- GLOBAL_ATOM_DECL(keepidle); \
- GLOBAL_ATOM_DECL(keepintvl); \
- GLOBAL_ATOM_DECL(kernel); \
- GLOBAL_ATOM_DECL(knowsepoch); \
- GLOBAL_ATOM_DECL(leave_group); \
- GLOBAL_ATOM_DECL(level); \
- GLOBAL_ATOM_DECL(linger); \
- GLOBAL_ATOM_DECL(link); \
- GLOBAL_ATOM_DECL(link0); \
- GLOBAL_ATOM_DECL(link1); \
- GLOBAL_ATOM_DECL(link2); \
- GLOBAL_ATOM_DECL(local); \
- GLOBAL_ATOM_DECL(localtlk); \
- GLOBAL_ATOM_DECL(local_auth_chunks); \
- GLOBAL_ATOM_DECL(loopback); \
- GLOBAL_ATOM_DECL(lowdelay); \
- GLOBAL_ATOM_DECL(lower_up); \
- GLOBAL_ATOM_DECL(mark); \
- GLOBAL_ATOM_DECL(master); \
- GLOBAL_ATOM_DECL(maxburst); \
- GLOBAL_ATOM_DECL(maxseg); \
- GLOBAL_ATOM_DECL(md5sig); \
- GLOBAL_ATOM_DECL(metricom); \
- GLOBAL_ATOM_DECL(mincost); \
- GLOBAL_ATOM_DECL(minttl); \
- GLOBAL_ATOM_DECL(monitor); \
- GLOBAL_ATOM_DECL(more); \
- GLOBAL_ATOM_DECL(msfilter); \
- GLOBAL_ATOM_DECL(mtu); \
- GLOBAL_ATOM_DECL(mtu_discover); \
- GLOBAL_ATOM_DECL(multicast); \
- GLOBAL_ATOM_DECL(multicast_all); \
- GLOBAL_ATOM_DECL(multicast_hops); \
- GLOBAL_ATOM_DECL(multicast_if); \
- GLOBAL_ATOM_DECL(multicast_loop); \
- GLOBAL_ATOM_DECL(multicast_ttl); \
- GLOBAL_ATOM_DECL(name); \
- GLOBAL_ATOM_DECL(netrom); \
- GLOBAL_ATOM_DECL(nlen); \
- GLOBAL_ATOM_DECL(noarp); \
- GLOBAL_ATOM_DECL(nodelay); \
- GLOBAL_ATOM_DECL(nodefrag); \
- GLOBAL_ATOM_DECL(nogroup); \
- GLOBAL_ATOM_DECL(none); \
- GLOBAL_ATOM_DECL(noopt); \
- GLOBAL_ATOM_DECL(nopush); \
- GLOBAL_ATOM_DECL(nosignal); \
- GLOBAL_ATOM_DECL(notrailers); \
- GLOBAL_ATOM_DECL(not_found); \
- GLOBAL_ATOM_DECL(not_owner); \
- GLOBAL_ATOM_DECL(oactive); \
- GLOBAL_ATOM_DECL(ok); \
- GLOBAL_ATOM_DECL(oob); \
- GLOBAL_ATOM_DECL(oobinline); \
- GLOBAL_ATOM_DECL(options); \
- GLOBAL_ATOM_DECL(origdstaddr); \
- GLOBAL_ATOM_DECL(otherhost); \
- GLOBAL_ATOM_DECL(outgoing); \
- GLOBAL_ATOM_DECL(packet); \
- GLOBAL_ATOM_DECL(partial_delivery_point); \
- GLOBAL_ATOM_DECL(passcred); \
- GLOBAL_ATOM_DECL(path); \
- GLOBAL_ATOM_DECL(peek); \
- GLOBAL_ATOM_DECL(peek_off); \
- GLOBAL_ATOM_DECL(peer_addr_params); \
- GLOBAL_ATOM_DECL(peer_auth_chunks); \
- GLOBAL_ATOM_DECL(peercred); \
- GLOBAL_ATOM_DECL(pktinfo); \
- GLOBAL_ATOM_DECL(pktoptions); \
- GLOBAL_ATOM_DECL(pkttype); \
- GLOBAL_ATOM_DECL(pointopoint); \
- GLOBAL_ATOM_DECL(port); \
- GLOBAL_ATOM_DECL(portrange); \
- GLOBAL_ATOM_DECL(portsel); \
- GLOBAL_ATOM_DECL(ppromisc); \
- GLOBAL_ATOM_DECL(primary_addr); \
- GLOBAL_ATOM_DECL(priority); \
- GLOBAL_ATOM_DECL(promisc); \
- GLOBAL_ATOM_DECL(pronet); \
- GLOBAL_ATOM_DECL(protocol); \
- GLOBAL_ATOM_DECL(pup); \
- GLOBAL_ATOM_DECL(raw); \
- GLOBAL_ATOM_DECL(rcvbuf); \
- GLOBAL_ATOM_DECL(rcvbufforce); \
- GLOBAL_ATOM_DECL(rcvlowat); \
- GLOBAL_ATOM_DECL(rcvtimeo); \
- GLOBAL_ATOM_DECL(rdm); \
- GLOBAL_ATOM_DECL(recv); \
- GLOBAL_ATOM_DECL(recvdstaddr); \
- GLOBAL_ATOM_DECL(recverr); \
- GLOBAL_ATOM_DECL(recvfrom); \
- GLOBAL_ATOM_DECL(recvhoplimit); \
- GLOBAL_ATOM_DECL(recvif); \
- GLOBAL_ATOM_DECL(recvmsg); \
- GLOBAL_ATOM_DECL(recvopts); \
- GLOBAL_ATOM_DECL(recvorigdstaddr); \
- GLOBAL_ATOM_DECL(recvpktinfo); \
- GLOBAL_ATOM_DECL(recvtclass); \
- GLOBAL_ATOM_DECL(recvtos); \
- GLOBAL_ATOM_DECL(recvttl); \
- GLOBAL_ATOM_DECL(reliability); \
- GLOBAL_ATOM_DECL(renaming); \
- GLOBAL_ATOM_DECL(reset_streams); \
- GLOBAL_ATOM_DECL(retopts); \
- GLOBAL_ATOM_DECL(reuseaddr); \
- GLOBAL_ATOM_DECL(reuseport); \
- GLOBAL_ATOM_DECL(rights); \
- GLOBAL_ATOM_DECL(router_alert); \
- GLOBAL_ATOM_DECL(rthdr); \
- GLOBAL_ATOM_DECL(rtoinfo); \
- GLOBAL_ATOM_DECL(running); \
- GLOBAL_ATOM_DECL(rxq_ovfl); \
- GLOBAL_ATOM_DECL(scope_id); \
- GLOBAL_ATOM_DECL(sctp); \
- GLOBAL_ATOM_DECL(sec); \
- GLOBAL_ATOM_DECL(select_failed); \
- GLOBAL_ATOM_DECL(select_sent); \
- GLOBAL_ATOM_DECL(send); \
- GLOBAL_ATOM_DECL(sendmsg); \
- GLOBAL_ATOM_DECL(sendsrcaddr); \
- GLOBAL_ATOM_DECL(sendto); \
- GLOBAL_ATOM_DECL(seqpacket); \
- GLOBAL_ATOM_DECL(setfib); \
- GLOBAL_ATOM_DECL(set_peer_primary_addr); \
- GLOBAL_ATOM_DECL(simplex); \
- GLOBAL_ATOM_DECL(slave); \
- GLOBAL_ATOM_DECL(slen); \
- GLOBAL_ATOM_DECL(sndbuf); \
- GLOBAL_ATOM_DECL(sndbufforce); \
- GLOBAL_ATOM_DECL(sndlowat); \
- GLOBAL_ATOM_DECL(sndtimeo); \
- GLOBAL_ATOM_DECL(socket); \
- GLOBAL_ATOM_DECL(spec_dst); \
- GLOBAL_ATOM_DECL(staticarp); \
- GLOBAL_ATOM_DECL(status); \
- GLOBAL_ATOM_DECL(stream); \
- GLOBAL_ATOM_DECL(syncnt); \
- GLOBAL_ATOM_DECL(tclass); \
- GLOBAL_ATOM_DECL(tcp); \
- GLOBAL_ATOM_DECL(throughput); \
- GLOBAL_ATOM_DECL(timestamp); \
- GLOBAL_ATOM_DECL(tos); \
- GLOBAL_ATOM_DECL(transparent); \
- GLOBAL_ATOM_DECL(true); \
- GLOBAL_ATOM_DECL(trunc); \
- GLOBAL_ATOM_DECL(ttl); \
- GLOBAL_ATOM_DECL(tunnel); \
- GLOBAL_ATOM_DECL(tunnel6); \
- GLOBAL_ATOM_DECL(type); \
- GLOBAL_ATOM_DECL(udp); \
- GLOBAL_ATOM_DECL(unblock_source); \
- GLOBAL_ATOM_DECL(undefined); \
- GLOBAL_ATOM_DECL(unicast_hops); \
- GLOBAL_ATOM_DECL(unspec); \
- GLOBAL_ATOM_DECL(up); \
- GLOBAL_ATOM_DECL(usec); \
- GLOBAL_ATOM_DECL(user); \
- GLOBAL_ATOM_DECL(user_timeout); \
- GLOBAL_ATOM_DECL(use_ext_recvinfo); \
- GLOBAL_ATOM_DECL(use_min_mtu); \
- GLOBAL_ATOM_DECL(void); \
- GLOBAL_ATOM_DECL(v6only)
-
-
-/* *** Global error reason atoms *** */
-#define GLOBAL_ERROR_REASON_ATOMS \
- GLOBAL_ATOM_DECL(eagain); \
- GLOBAL_ATOM_DECL(einval)
-
-
-#define GLOBAL_ATOM_DECL(A) ERL_NIF_TERM esock_atom_##A
-GLOBAL_ATOMS;
-GLOBAL_ERROR_REASON_ATOMS;
-#undef GLOBAL_ATOM_DECL
-ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket')
-/* *** Local atoms *** */
-#define LOCAL_ATOMS \
- LOCAL_ATOM_DECL(accepting); \
- LOCAL_ATOM_DECL(acc_success); \
- LOCAL_ATOM_DECL(acc_fails); \
- LOCAL_ATOM_DECL(acc_tries); \
- LOCAL_ATOM_DECL(acc_waits); \
- LOCAL_ATOM_DECL(adaptation_layer); \
- LOCAL_ATOM_DECL(add); \
- LOCAL_ATOM_DECL(addr_unreach); \
- LOCAL_ATOM_DECL(address); \
- LOCAL_ATOM_DECL(adm_prohibited); \
- LOCAL_ATOM_DECL(already); \
- LOCAL_ATOM_DECL(association); \
- LOCAL_ATOM_DECL(assoc_id); \
- LOCAL_ATOM_DECL(authentication); \
- LOCAL_ATOM_DECL(base_addr); \
- LOCAL_ATOM_DECL(boolean); \
- LOCAL_ATOM_DECL(bound); \
- LOCAL_ATOM_DECL(bufsz); \
- LOCAL_ATOM_DECL(close); \
- LOCAL_ATOM_DECL(closed); \
- LOCAL_ATOM_DECL(closing); \
- LOCAL_ATOM_DECL(code); \
- LOCAL_ATOM_DECL(cookie_life); \
- LOCAL_ATOM_DECL(counter_wrap); \
- LOCAL_ATOM_DECL(counters); \
- LOCAL_ATOM_DECL(ctype); \
- LOCAL_ATOM_DECL(data_io); \
- LOCAL_ATOM_DECL(data_size); \
- LOCAL_ATOM_DECL(debug_filename); \
- LOCAL_ATOM_DECL(del); \
- LOCAL_ATOM_DECL(dest_unreach); \
- LOCAL_ATOM_DECL(dma); \
- LOCAL_ATOM_DECL(do); \
- LOCAL_ATOM_DECL(dont); \
- LOCAL_ATOM_DECL(dtor); \
- LOCAL_ATOM_DECL(dup); \
- LOCAL_ATOM_DECL(efile); \
- LOCAL_ATOM_DECL(exclude); \
- LOCAL_ATOM_DECL(false); \
- LOCAL_ATOM_DECL(frag_needed); \
- LOCAL_ATOM_DECL(gifaddr); \
- LOCAL_ATOM_DECL(gifbrdaddr); \
- LOCAL_ATOM_DECL(gifconf); \
- LOCAL_ATOM_DECL(gifdstaddr); \
- LOCAL_ATOM_DECL(gifflags); \
- LOCAL_ATOM_DECL(gifhwaddr); \
- LOCAL_ATOM_DECL(gifindex); \
- LOCAL_ATOM_DECL(gifmap); \
- LOCAL_ATOM_DECL(gifmtu); \
- LOCAL_ATOM_DECL(gifname); \
- LOCAL_ATOM_DECL(gifnetmask); \
- LOCAL_ATOM_DECL(giftxqlen); \
- LOCAL_ATOM_DECL(host_unknown); \
- LOCAL_ATOM_DECL(host_unreach); \
- LOCAL_ATOM_DECL(how); \
- LOCAL_ATOM_DECL(in4_sockaddr); \
- LOCAL_ATOM_DECL(in6_sockaddr); \
- LOCAL_ATOM_DECL(include); \
- LOCAL_ATOM_DECL(initial); \
- LOCAL_ATOM_DECL(interface); \
- LOCAL_ATOM_DECL(integer); \
- LOCAL_ATOM_DECL(ioctl_flags); \
- LOCAL_ATOM_DECL(ioctl_requests); \
- LOCAL_ATOM_DECL(iov_max); \
- LOCAL_ATOM_DECL(iow); \
- LOCAL_ATOM_DECL(irq); \
- LOCAL_ATOM_DECL(listening); \
- LOCAL_ATOM_DECL(local_rwnd); \
- LOCAL_ATOM_DECL(map); \
- LOCAL_ATOM_DECL(max); \
- LOCAL_ATOM_DECL(max_attempts); \
- LOCAL_ATOM_DECL(max_init_timeo); \
- LOCAL_ATOM_DECL(max_instreams); \
- LOCAL_ATOM_DECL(asocmaxrxt); \
- LOCAL_ATOM_DECL(mem_end); \
- LOCAL_ATOM_DECL(mem_start); \
- LOCAL_ATOM_DECL(min); \
- LOCAL_ATOM_DECL(missing); \
- LOCAL_ATOM_DECL(mode); \
- LOCAL_ATOM_DECL(msg); \
- LOCAL_ATOM_DECL(msg_flags); \
- LOCAL_ATOM_DECL(mtu); \
- LOCAL_ATOM_DECL(multiaddr); \
- LOCAL_ATOM_DECL(net_unknown); \
- LOCAL_ATOM_DECL(net_unreach); \
- LOCAL_ATOM_DECL(netns); \
- LOCAL_ATOM_DECL(nogroup); \
- LOCAL_ATOM_DECL(none); \
- LOCAL_ATOM_DECL(noroute); \
- LOCAL_ATOM_DECL(not_neighbour); \
- LOCAL_ATOM_DECL(null); \
- LOCAL_ATOM_DECL(num_acceptors); \
- LOCAL_ATOM_DECL(num_cnt_bits); \
- LOCAL_ATOM_DECL(num_dinet); \
- LOCAL_ATOM_DECL(num_dinet6); \
- LOCAL_ATOM_DECL(num_dlocal); \
- LOCAL_ATOM_DECL(num_outstreams); \
- LOCAL_ATOM_DECL(number_peer_destinations); \
- LOCAL_ATOM_DECL(num_pip); \
- LOCAL_ATOM_DECL(num_psctp); \
- LOCAL_ATOM_DECL(num_ptcp); \
- LOCAL_ATOM_DECL(num_pudp); \
- LOCAL_ATOM_DECL(num_readers); \
- LOCAL_ATOM_DECL(num_sockets); \
- LOCAL_ATOM_DECL(num_tdgrams); \
- LOCAL_ATOM_DECL(num_tseqpkgs); \
- LOCAL_ATOM_DECL(num_tstreams); \
- LOCAL_ATOM_DECL(num_writers); \
- LOCAL_ATOM_DECL(offender); \
- LOCAL_ATOM_DECL(onoff); \
- LOCAL_ATOM_DECL(options); \
- LOCAL_ATOM_DECL(origin); \
- LOCAL_ATOM_DECL(otp); \
- LOCAL_ATOM_DECL(otp_socket_option);\
- LOCAL_ATOM_DECL(owner); \
- LOCAL_ATOM_DECL(partial_delivery); \
- LOCAL_ATOM_DECL(peer_error); \
- LOCAL_ATOM_DECL(peer_rwnd); \
- LOCAL_ATOM_DECL(pkt_toobig); \
- LOCAL_ATOM_DECL(policy_fail); \
- LOCAL_ATOM_DECL(port); \
- LOCAL_ATOM_DECL(port_unreach); \
- LOCAL_ATOM_DECL(prim_file); \
- LOCAL_ATOM_DECL(probe); \
- LOCAL_ATOM_DECL(protocols); \
- LOCAL_ATOM_DECL(rcvctrlbuf); \
- LOCAL_ATOM_DECL(read); \
- LOCAL_ATOM_DECL(read_byte); \
- LOCAL_ATOM_DECL(read_fails); \
- LOCAL_ATOM_DECL(read_pkg); \
- LOCAL_ATOM_DECL(read_pkg_max); \
- LOCAL_ATOM_DECL(read_tries); \
- LOCAL_ATOM_DECL(read_waits); \
- LOCAL_ATOM_DECL(read_write); \
- LOCAL_ATOM_DECL(registry); \
- LOCAL_ATOM_DECL(reject_route); \
- LOCAL_ATOM_DECL(remote); \
- LOCAL_ATOM_DECL(rstates); \
- LOCAL_ATOM_DECL(select); \
- LOCAL_ATOM_DECL(selected); \
- LOCAL_ATOM_DECL(sender_dry); \
- LOCAL_ATOM_DECL(send_failure); \
- LOCAL_ATOM_DECL(sendfile); \
- LOCAL_ATOM_DECL(sendfile_byte); \
- LOCAL_ATOM_DECL(sendfile_deferred_close); \
- LOCAL_ATOM_DECL(sendfile_fails); \
- LOCAL_ATOM_DECL(sendfile_max); \
- LOCAL_ATOM_DECL(sendfile_pkg); \
- LOCAL_ATOM_DECL(sendfile_pkg_max); \
- LOCAL_ATOM_DECL(sendfile_tries); \
- LOCAL_ATOM_DECL(sendfile_waits); \
- LOCAL_ATOM_DECL(shutdown); \
- LOCAL_ATOM_DECL(sifaddr); \
- LOCAL_ATOM_DECL(sifbrdaddr); \
- LOCAL_ATOM_DECL(sifdstaddr); \
- LOCAL_ATOM_DECL(sifflags); \
- LOCAL_ATOM_DECL(sifmtu); \
- LOCAL_ATOM_DECL(sifnetmask); \
- LOCAL_ATOM_DECL(siftxqlen); \
- LOCAL_ATOM_DECL(slist); \
- LOCAL_ATOM_DECL(sndctrlbuf); \
- LOCAL_ATOM_DECL(sockaddr); \
- LOCAL_ATOM_DECL(socket_debug); \
- LOCAL_ATOM_DECL(socket_level); \
- LOCAL_ATOM_DECL(socket_option); \
- LOCAL_ATOM_DECL(sourceaddr); \
- LOCAL_ATOM_DECL(state); \
- LOCAL_ATOM_DECL(time_exceeded); \
- LOCAL_ATOM_DECL(timeout); \
- LOCAL_ATOM_DECL(true); \
- LOCAL_ATOM_DECL(txqlen); \
- LOCAL_ATOM_DECL(txstatus); \
- LOCAL_ATOM_DECL(txtime); \
- LOCAL_ATOM_DECL(use_registry); \
- LOCAL_ATOM_DECL(value); \
- LOCAL_ATOM_DECL(want); \
- LOCAL_ATOM_DECL(write); \
- LOCAL_ATOM_DECL(write_byte); \
- LOCAL_ATOM_DECL(write_fails); \
- LOCAL_ATOM_DECL(write_pkg); \
- LOCAL_ATOM_DECL(write_pkg_max); \
- LOCAL_ATOM_DECL(write_tries); \
- LOCAL_ATOM_DECL(write_waits); \
- LOCAL_ATOM_DECL(wstates); \
- LOCAL_ATOM_DECL(zero); \
- LOCAL_ATOM_DECL(zerocopy)
-
-/* Local error reason atoms */
-#define LOCAL_ERROR_REASON_ATOMS \
- LOCAL_ATOM_DECL(select_read); \
- LOCAL_ATOM_DECL(select_write)
-
-#define LOCAL_ATOM_DECL(LA) static ERL_NIF_TERM atom_##LA
-LOCAL_ATOMS;
-LOCAL_ERROR_REASON_ATOMS;
-#undef LOCAL_ATOM_DECL
+// static ESOCK_INLINE void esock_clear_env(const char* slogan, ErlNifEnv* env)
+extern void esock_clear_env(const char* slogan, ErlNifEnv* env)
+{
+ // ESOCK_DBG_PRINTF( TRUE, ("SOCKET", "env clear - %s: 0x%lX\r\n", slogan, env) );
+ SGDBG( ("SOCKET", "env clear - %s: 0x%lX\r\n", slogan, env) );
-/* *** Sockets *** */
-static ErlNifResourceType* esocks;
-static ErlNifResourceTypeInit esockInit = {
- esock_dtor,
- esock_stop,
- (ErlNifResourceDown*) esock_down
-};
-
-// Initiated when the nif is loaded
-static ESockData data;
+ if (env != NULL) enif_clear_env(env);
+}
-/* These two (inline) functions are primarily intended for debugging,
- * that is, to make it easy to add debug printouts.
- */
-static ESOCK_INLINE void esock_free_env(const char* slogan, ErlNifEnv* env)
+// static ESOCK_INLINE void esock_free_env(const char* slogan, ErlNifEnv* env)
+extern void esock_free_env(const char* slogan, ErlNifEnv* env)
{
+ // ESOCK_DBG_PRINTF( TRUE, ("SOCKET", "env free - %s: 0x%lX\r\n", slogan, env) );
+
SGDBG( ("SOCKET", "env free - %s: 0x%lX\r\n", slogan, env) );
- // esock_dbg_printf("SOCK ENV", "free - %s: 0x%lX\r\n", slogan, env);
if (env != NULL) enif_free_env(env);
}
-static ESOCK_INLINE ErlNifEnv* esock_alloc_env(const char* slogan)
+// static ESOCK_INLINE ErlNifEnv* esock_alloc_env(const char* slogan)
+extern ErlNifEnv* esock_alloc_env(const char* slogan)
{
- ErlNifEnv* env;
+ ErlNifEnv* env = enif_alloc_env();
- ESOCK_ASSERT( (env = enif_alloc_env()) != NULL);
+ // ESOCK_DBG_PRINTF( TRUE, ("SOCKET", "env alloc - %s: 0x%lX\r\n", slogan, env) );
SGDBG( ("SOCKET", "env alloc - %s: 0x%lX\r\n", slogan, env) );
- // esock_dbg_printf("SOCK ENV", "alloc - %s: 0x%lX\r\n", slogan, env);
+
+ ESOCK_ASSERT( env != NULL );
return env;
}
@@ -4578,9 +3798,6 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ERL_NIF_TERM info;
ESockDescriptor* descP;
@@ -4612,7 +3829,6 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env,
MUNLOCK(descP->readMtx);
return info;
-#endif
}
@@ -4623,7 +3839,6 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env,
* actually a counter, the num_cnt_bits. This is the "size" of each counter,
* in number of bits: 16 | 24 | 32 | 48 | 64.
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
{
@@ -4631,7 +3846,7 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
numBits, numSockets, numTypeDGrams, numTypeStreams,
numTypeSeqPkgs, numDomLocal, numDomInet, numDomInet6,
numProtoIP, numProtoTCP, numProtoUDP, numProtoSCTP,
- sockDbg, iovMax, dbg, useReg, iow;
+ sockDbg, iovMax, dbg, useReg, iow, eei;
MLOCK(data.cntMtx);
numBits = MKUI(env, ESOCK_COUNTER_SIZE);
@@ -4647,6 +3862,7 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
numProtoUDP = MKUI(env, data.numProtoUDP);
numProtoSCTP = MKUI(env, data.numProtoSCTP);
sockDbg = BOOL2ATOM(data.sockDbg);
+ eei = BOOL2ATOM(data.eei);
MUNLOCK(data.cntMtx);
iovMax = MKI(env, data.iov_max);
@@ -4678,16 +3894,33 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
ERL_NIF_TERM
keys[] = {esock_atom_debug,
atom_socket_debug,
- atom_use_registry,
+ atom_eei,
+ esock_atom_use_registry,
atom_iow,
- atom_counters,
- atom_iov_max},
+ esock_atom_counters,
+ atom_iov_max,
+ atom_io_backend},
vals[] = {dbg,
sockDbg,
+ eei,
useReg,
iow,
gcnt,
- iovMax},
+ iovMax,
+ ESOCK_IO_INFO(env)
+ /* This mess is just a temporary hack
+ * and shall be replaced by a callback
+ * function (eventually).
+ * That function should return a 'term' (a map).
+ */
+ /*
+#ifdef __WIN32__
+ MKA(env, "win_esaio")
+#else
+ MKA(env, "unix_essio")
+#endif
+ */
+ },
info;
unsigned int
numKeys = NUM(keys),
@@ -4700,7 +3933,6 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
}
}
}
-#endif // #ifndef __WIN32__
/*
@@ -4716,7 +3948,6 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
* writers: The number of current and waiting writers
* acceptors: The number of current and waiting acceptors
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_socket_info(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -4742,7 +3973,7 @@ ERL_NIF_TERM esock_socket_info(ErlNifEnv* env,
atom_rstates,
atom_wstates,
atom_ctype,
- atom_counters,
+ esock_atom_counters,
atom_num_readers,
atom_num_writers,
atom_num_acceptors};
@@ -4773,13 +4004,11 @@ ERL_NIF_TERM esock_socket_info(ErlNifEnv* env,
return info;
}
}
-#endif // #ifndef __WIN32__
/*
* Encode the socket domain
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_socket_info_domain(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -4790,13 +4019,11 @@ ERL_NIF_TERM esock_socket_info_domain(ErlNifEnv* env,
esock_encode_domain(env, domain, &edomain);
return edomain;
}
-#endif // #ifndef __WIN32__
/*
* Encode the socket type
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_socket_info_type(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -4808,16 +4035,14 @@ ERL_NIF_TERM esock_socket_info_type(ErlNifEnv* env,
return etype;
}
-#endif // #ifndef __WIN32__
/*
* Encode the socket "create type"
- * That is "show" how this socket was created:
+ * That is; "show" how this socket was created:
*
* normal | fromfd | {fromfd, integer()}
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_socket_info_ctype(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -4830,7 +4055,7 @@ ERL_NIF_TERM esock_socket_info_ctype(ErlNifEnv* env,
"\r\n", descP->sock,
descP->origFD, B2S(descP->closeOnClose)) );
- if (descP->origFD > 0) {
+ if (descP->origFD != INVALID_SOCKET) {
/* Created from other FD */
if (descP->closeOnClose) {
/* We *have* dup'ed: {fromfd, integer()} */
@@ -4850,16 +4075,12 @@ ERL_NIF_TERM esock_socket_info_ctype(ErlNifEnv* env,
return ctype;
}
-#endif // #ifndef __WIN32__
/*
* Encode the socket "state"
- * That is "show" how this socket was created:
- *
- * normal | fromfd | {fromfd, integer()}
+ * This is a list of atoms, one for each valid 'state' value.
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_socket_info_state(ErlNifEnv* env,
unsigned int state)
@@ -4929,12 +4150,12 @@ ERL_NIF_TERM esock_socket_info_state(ErlNifEnv* env,
SSDBG( descP, ("SOCKET", "esock_socket_info_state {%d} -> closed"
"\r\n", descP->sock) );
*/
- TARRAY_ADD(estate, atom_closed);
+ TARRAY_ADD(estate, esock_atom_closed);
}
if ((state & ESOCK_STATE_DTOR) != 0) {
/*
- SSDBG( descP, ("SOCKET", "esock_socket_info_state {%d} -> dror"
+ SSDBG( descP, ("SOCKET", "esock_socket_info_state {%d} -> dtor"
"\r\n", descP->sock) );
*/
TARRAY_ADD(estate, atom_dtor);
@@ -4944,33 +4165,31 @@ ERL_NIF_TERM esock_socket_info_state(ErlNifEnv* env,
return estateList;
}
-#endif // #ifndef __WIN32__
/*
* Collect all counters for a socket.
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_socket_info_counters(ErlNifEnv* env,
ESockDescriptor* descP)
{
- ERL_NIF_TERM keys[] = {atom_read_byte,
- atom_read_fails,
- atom_read_pkg,
+ ERL_NIF_TERM keys[] = {esock_atom_read_byte,
+ esock_atom_read_fails,
+ esock_atom_read_pkg,
atom_read_pkg_max,
- atom_read_tries,
+ esock_atom_read_tries,
atom_read_waits,
- atom_write_byte,
- atom_write_fails,
- atom_write_pkg,
+ esock_atom_write_byte,
+ esock_atom_write_fails,
+ esock_atom_write_pkg,
atom_write_pkg_max,
- atom_write_tries,
- atom_write_waits,
- atom_acc_success,
- atom_acc_fails,
- atom_acc_tries,
- atom_acc_waits};
+ esock_atom_write_tries,
+ esock_atom_write_waits,
+ esock_atom_acc_success,
+ esock_atom_acc_fails,
+ esock_atom_acc_tries,
+ esock_atom_acc_waits};
unsigned int numKeys = NUM(keys);
ERL_NIF_TERM vals[] = {MKCNT(env, descP->readByteCnt),
MKCNT(env, descP->readFails),
@@ -5004,14 +4223,14 @@ ERL_NIF_TERM esock_socket_info_counters(ErlNifEnv* env,
ESockSendfileCounters *cP = descP->sendfileCountersP;
ERL_NIF_TERM m,
sfKeys[] =
- {atom_sendfile,
- atom_sendfile_byte,
- atom_sendfile_fails,
- atom_sendfile_max,
- atom_sendfile_pkg,
- atom_sendfile_pkg_max,
- atom_sendfile_tries,
- atom_sendfile_waits},
+ {esock_atom_sendfile,
+ esock_atom_sendfile_byte,
+ esock_atom_sendfile_fails,
+ esock_atom_sendfile_max,
+ esock_atom_sendfile_pkg,
+ esock_atom_sendfile_pkg_max,
+ esock_atom_sendfile_tries,
+ esock_atom_sendfile_waits},
sfVals[] =
{MKUI(env, cP->cnt),
MKUI(env, cP->byteCnt),
@@ -5039,7 +4258,6 @@ ERL_NIF_TERM esock_socket_info_counters(ErlNifEnv* env,
return cnts;
}
-#endif // #ifndef __WIN32__
@@ -5067,9 +4285,6 @@ ERL_NIF_TERM nif_command(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ERL_NIF_TERM command, cdata, result;
ESOCK_ASSERT( argc == 1 );
@@ -5089,10 +4304,10 @@ ERL_NIF_TERM nif_command(ErlNifEnv* env,
SGDBG( ("SOCKET", "nif_command -> "
"\r\n command: %T"
- "\r\n cdata: %T"
+ "\r\n cdata: %T"
"\r\n", command, cdata) );
- result = esock_command(env, command, cdata);
+ result = ESOCK_IO_CMD(env, command, cdata);
SGDBG( ("SOCKET", "nif_command -> done with result: "
"\r\n %T"
@@ -5100,14 +4315,11 @@ ERL_NIF_TERM nif_command(ErlNifEnv* env,
return result;
-#endif // #ifdef __WIN32__ #else
}
-#ifndef __WIN32__
static
-ERL_NIF_TERM
-esock_command(ErlNifEnv* env, ERL_NIF_TERM command, ERL_NIF_TERM cdata)
+ERL_NIF_TERM esock_command(ErlNifEnv* env, ERL_NIF_TERM command, ERL_NIF_TERM cdata)
{
int cmp;
@@ -5120,7 +4332,7 @@ esock_command(ErlNifEnv* env, ERL_NIF_TERM command, ERL_NIF_TERM cdata)
if (COMPARE(command, esock_atom_debug) == 0)
return esock_command_debug(env, cdata);
} else { // 0 < cmp
- if (COMPARE(command, atom_use_registry) == 0)
+ if (COMPARE(command, esock_atom_use_registry) == 0)
return esock_command_use_socket_registry(env, cdata);
}
@@ -5129,10 +4341,8 @@ esock_command(ErlNifEnv* env, ERL_NIF_TERM command, ERL_NIF_TERM cdata)
return esock_raise_invalid(env, MKT2(env, esock_atom_command, command));
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_command_debug(ErlNifEnv* env, ERL_NIF_TERM cdata)
{
@@ -5142,15 +4352,12 @@ ERL_NIF_TERM esock_command_debug(ErlNifEnv* env, ERL_NIF_TERM cdata)
if (esock_decode_bool(cdata, &data.dbg))
result = esock_atom_ok;
else
- result =
- esock_raise_invalid(env, MKT2(env, esock_atom_data, cdata));
+ result = esock_raise_invalid(env, MKT2(env, esock_atom_data, cdata));
return result;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_command_socket_debug(ErlNifEnv* env, ERL_NIF_TERM cdata)
{
@@ -5158,8 +4365,7 @@ ERL_NIF_TERM esock_command_socket_debug(ErlNifEnv* env, ERL_NIF_TERM cdata)
/* The data *should* be a boolean() */
if (! esock_decode_bool(cdata, &dbg))
- return
- esock_raise_invalid(env, MKT2(env, esock_atom_data, cdata));
+ return esock_raise_invalid(env, MKT2(env, esock_atom_data, cdata));
MLOCK(data.cntMtx);
data.sockDbg = dbg;
@@ -5167,10 +4373,8 @@ ERL_NIF_TERM esock_command_socket_debug(ErlNifEnv* env, ERL_NIF_TERM cdata)
return esock_atom_ok;;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_command_use_socket_registry(ErlNifEnv* env,
ERL_NIF_TERM cdata)
@@ -5179,17 +4383,14 @@ ERL_NIF_TERM esock_command_use_socket_registry(ErlNifEnv* env,
/* The data *should* be a boolean() */
if (! esock_decode_bool(cdata, &useReg))
- return
- esock_raise_invalid(env, MKT2(env, esock_atom_data, cdata));
+ return esock_raise_invalid(env, MKT2(env, esock_atom_data, cdata));
MLOCK(data.cntMtx);
data.useReg = useReg;
MUNLOCK(data.cntMtx);
- return esock_atom_ok;;
+ return esock_atom_ok;
}
-#endif
-
@@ -5199,14 +4400,14 @@ ERL_NIF_TERM esock_command_use_socket_registry(ErlNifEnv* env,
*
* Calculate how many readers | writers | acceptors we have for this socket.
* Current requestor + any waiting requestors (of the type).
- *
+ * Note that "Current requestor" is *not* used on Windows.
*/
#ifndef __WIN32__
#define ESOCK_INFO_REQ_FUNCS \
ESOCK_INFO_REQ_FUNC_DECL(readers, currentReaderP, readersQ) \
- ESOCK_INFO_REQ_FUNC_DECL(writers, currentWriterP, writersQ) \
+ ESOCK_INFO_REQ_FUNC_DECL(writers, currentWriterP, writersQ) \
ESOCK_INFO_REQ_FUNC_DECL(acceptors, currentAcceptorP, acceptorsQ)
#define ESOCK_INFO_REQ_FUNC_DECL(F, CRP, Q) \
@@ -5216,32 +4417,49 @@ ERL_NIF_TERM esock_command_use_socket_registry(ErlNifEnv* env,
{ \
return socket_info_reqs(env, descP, descP->CRP, &descP->Q); \
}
+#else
+
+#define ESOCK_INFO_REQ_FUNCS \
+ ESOCK_INFO_REQ_FUNC_DECL(readers, readersQ) \
+ ESOCK_INFO_REQ_FUNC_DECL(writers, writersQ) \
+ ESOCK_INFO_REQ_FUNC_DECL(acceptors, acceptorsQ)
+
+#define ESOCK_INFO_REQ_FUNC_DECL(F, Q) \
+ static \
+ ERL_NIF_TERM esock_socket_info_##F(ErlNifEnv* env, \
+ ESockDescriptor* descP) \
+ { \
+ return socket_info_reqs(env, descP, &descP->Q); \
+ }
+
+#endif
+
ESOCK_INFO_REQ_FUNCS
#undef ESOCK_INFO_REQ_FUNC_DECL
static
ERL_NIF_TERM socket_info_reqs(ErlNifEnv* env,
ESockDescriptor* descP,
+#ifndef __WIN32__
ESockRequestor* currentRequestorP,
+#endif
ESockRequestQueue* q)
{
- ESockRequestQueueElement* tmp;
- ERL_NIF_TERM info;
- unsigned int cnt = 0;
+ ERL_NIF_TERM info;
+ unsigned int cnt;
+#ifdef __WIN32__
+ cnt = 0;
+#else
if (currentRequestorP != NULL) {
// We have an active requestor!
- cnt++;
-
- // And add all the waiting requestors
- tmp = q->first;
- while (tmp != NULL) {
- cnt++;
- tmp = tmp->nextP;
- }
+ cnt = 1;
+ } else {
+ cnt = 0;
}
+#endif
- info = MKUI(env, cnt);
+ info = MKUI(env, cnt + qlength(q));
SSDBG( descP, ("SOCKET", "socket_info_reqs -> done with"
"\r\n info: %T"
@@ -5250,8 +4468,6 @@ ERL_NIF_TERM socket_info_reqs(ErlNifEnv* env,
return info;
}
-#endif // #ifndef __WIN32__
-
/* ----------------------------------------------------------------------
* nif_supports
@@ -5281,29 +4497,28 @@ ERL_NIF_TERM nif_supports(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
SGDBG( ("SOCKET", "nif_supports -> entry with %d args\r\n", argc) );
/* Extract arguments and perform preliminary validation */
if (argc == 0)
- return esock_supports_0(env);
+ return ESOCK_IO_SUPPORTS_0(env);
- ESOCK_ASSERT( argc == 1 );
+ if (argc == 1)
+ return ESOCK_IO_SUPPORTS_1(env, argv[0]);
+
+ return esock_make_error(env, esock_atom_einval);
- return esock_supports_1(env, argv[0]);
-#endif
}
+
/* esock_supports - what features do we support?
*
* This gives information about what features actually
* work on the current platform.
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_supports_0(ErlNifEnv* env)
{
@@ -5339,21 +4554,21 @@ ERL_NIF_TERM esock_supports_0(ErlNifEnv* env)
#else
is_supported = esock_atom_false;
#endif
- TARRAY_ADD(opts, MKT2(env, atom_netns, is_supported));
+ TARRAY_ADD(opts, MKT2(env, esock_atom_netns, is_supported));
#if defined(HAVE_SENDFILE)
is_supported = esock_atom_true;
#else
is_supported = esock_atom_false;
#endif
- TARRAY_ADD(opts, MKT2(env, atom_sendfile, is_supported));
+ TARRAY_ADD(opts, MKT2(env, esock_atom_sendfile, is_supported));
TARRAY_TOLIST(opts, env, &opts_list);
return opts_list;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
+
+
static
ERL_NIF_TERM esock_supports_1(ErlNifEnv* env, ERL_NIF_TERM key)
{
@@ -5379,37 +4594,52 @@ ERL_NIF_TERM esock_supports_1(ErlNifEnv* env, ERL_NIF_TERM key)
return result;
}
-#endif // #ifndef __WIN32__
-
-#ifndef __WIN32__
static ERL_NIF_TERM esock_supports_msg_flags(ErlNifEnv* env) {
size_t n;
ERL_NIF_TERM result;
result = MKEL(env);
- for (n = 0; n < NUM(msg_flags); n++) {
+ for (n = 0; n < esock_msg_flags_length; n++) {
result =
MKC(env,
MKT2(env,
- *(msg_flags[n].name),
- MKI(env, msg_flags[n].flag)),
+ *(esock_msg_flags[n].name),
+ MKI(env, esock_msg_flags[n].flag)),
result);
}
return result;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_supports_protocols(ErlNifEnv* env)
{
ERL_NIF_TERM protocols;
+#ifndef __WIN32__
+#if defined(SOL_IP)
+ int protoIP = SOL_IP;
+#else
+ int protoIP = IPPROTO_IP;
+#endif
+#else
+ int protoIP = IPPROTO_IP;
+#endif
+#if defined(HAVE_IPV6)
+#ifndef __WIN32__
+#if defined(SOL_IPV6)
+ int protoIPV6 = SOL_IPV6;
+#else
+ int protoIPV6 = IPPROTO_IPV6;
+#endif
+#else
+ int protoIPV6 = IPPROTO_IPV6;
+#endif
+#endif
protocols = MKEL(env);
@@ -5449,29 +4679,13 @@ ERL_NIF_TERM esock_supports_protocols(ErlNifEnv* env)
protocols =
MKC(env,
- MKT2(env,
- MKL1(env, esock_atom_ip),
- MKI(env,
-#ifdef SOL_IP
- SOL_IP
-#else
- IPPROTO_IP
-#endif
- )),
+ MKT2(env, MKL1(env, esock_atom_ip), MKI(env, protoIP)),
protocols);
#ifdef HAVE_IPV6
protocols =
MKC(env,
- MKT2(env,
- MKL1(env, esock_atom_ipv6),
- MKI(env,
-#ifdef SOL_IPV6
- SOL_IPV6
-#else
- IPPROTO_IPV6
-#endif
- )),
+ MKT2(env, MKL1(env, esock_atom_ipv6), MKI(env, protoIPV6)),
protocols);
#endif
@@ -5494,11 +4708,9 @@ ERL_NIF_TERM esock_supports_protocols(ErlNifEnv* env)
return protocols;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_supports_ioctl_requests(ErlNifEnv* env)
{
@@ -5582,35 +4794,30 @@ ERL_NIF_TERM esock_supports_ioctl_requests(ErlNifEnv* env)
return requests;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
-static ERL_NIF_TERM esock_supports_ioctl_flags(ErlNifEnv* env)
+static
+ERL_NIF_TERM esock_supports_ioctl_flags(ErlNifEnv* env)
{
size_t n;
ERL_NIF_TERM result;
result = MKEL(env);
- for (n = 0; n < NUM(ioctl_flags); n++) {
+ for (n = 0; n < esock_ioctl_flags_length; n++) {
result =
MKC(env,
MKT2(env,
- *(ioctl_flags[n].name),
- MKI(env, ioctl_flags[n].flag)),
+ *(esock_ioctl_flags[n].name),
+ MKI(env, esock_ioctl_flags[n].flag)),
result);
}
return result;
}
-#endif // #ifndef __WIN32__
-
-
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_supports_options(ErlNifEnv* env)
@@ -5650,7 +4857,6 @@ ERL_NIF_TERM esock_supports_options(ErlNifEnv* env)
return levels;
}
-#endif // #ifndef __WIN32__
@@ -5699,9 +4905,6 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ERL_NIF_TERM result;
SGDBG( ("SOCKET", "nif_open -> "
@@ -5729,36 +4932,38 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env,
"\r\n", fd, eopts) );
MLOCK(data.cntMtx);
- result = esock_open2(env, fd, eopts);
+ result = ESOCK_IO_OPEN_WITH_FD(env, fd, eopts);
MUNLOCK(data.cntMtx);
} else {
- ERL_NIF_TERM edomain, etype, eopts;
+ ERL_NIF_TERM edomain, etype, eproto, eopts;
int domain, type, proto;
ESOCK_ASSERT( argc == 4 );
/* Extract arguments and perform preliminary validation */
- if (! GET_INT(env, argv[2], &proto)) {
- if (IS_INTEGER(env, argv[2]))
- return esock_make_error_integer_range(env, argv[2]);
- else
- return enif_make_badarg(env);
- }
- if (! IS_MAP(env, argv[3])) {
- return enif_make_badarg(env);
- }
edomain = argv[0];
- etype = argv[1];
- eopts = argv[3];
+ etype = argv[1];
+ eproto = argv[2];
+ eopts = argv[3];
SGDBG( ("SOCKET", "nif_open -> "
"\r\n edomain: %T"
"\r\n etype: %T"
- "\r\n proto: %T"
+ "\r\n eproto: %T"
"\r\n eopts: %T"
- "\r\n", argv[0], argv[1], argv[2], eopts) );
+ "\r\n", edomain, etype, eproto, eopts) );
+
+ if (! GET_INT(env, eproto, &proto)) {
+ if (IS_INTEGER(env, eproto))
+ return esock_make_error_integer_range(env, eproto);
+ else
+ return enif_make_badarg(env);
+ }
+ if (! IS_MAP(env, argv[3])) {
+ return enif_make_badarg(env);
+ }
if (esock_decode_domain(env, edomain, &domain) == 0) {
SGDBG( ("SOCKET",
@@ -5773,7 +4978,7 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env,
}
MLOCK(data.cntMtx);
- result = esock_open4(env, domain, type, proto, eopts);
+ result = ESOCK_IO_OPEN_PLAIN(env, domain, type, proto, eopts);
MUNLOCK(data.cntMtx);
}
@@ -5783,403 +4988,26 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env,
return result;
-#endif // #ifdef __WIN32__ #else
-}
-
-
-/* esock_open - create an endpoint (from an existing fd) for communication
- *
- * Assumes the input has been validated.
- *
- * Normally we want debugging on (individual) sockets to be controlled
- * by the sockets own debug flag. But since we don't even have a socket
- * yet, we must use the global debug flag.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_open2(ErlNifEnv* env,
- int fd,
- ERL_NIF_TERM eopts)
-{
- BOOLEAN_T dbg = esock_open_is_debug(env, eopts, data.sockDbg);
- BOOLEAN_T useReg = esock_open_use_registry(env, eopts, data.useReg);
- ESockDescriptor* descP;
- ERL_NIF_TERM sockRef;
- int domain, type, protocol;
- int save_errno = 0;
- BOOLEAN_T closeOnClose;
- SOCKET sock;
- ErlNifEvent event;
- ErlNifPid self;
-
- /* Keep track of the creator
- * This should not be a problem, but just in case
- * the *open* function is used with the wrong kind
- * of environment...
- */
- ESOCK_ASSERT( enif_self(env, &self) != NULL );
-
- SSDBG2( dbg,
- ("SOCKET", "esock_open2 -> entry with"
- "\r\n fd: %d"
- "\r\n eopts: %T"
- "\r\n", fd, eopts) );
-
- /*
- * Before we do anything else, we try to retrieve domain, type and protocol
- * This information is either present in the eopts map or if not we need
- * to "get" it from the system (getsockopt).
- * Note that its not possible to get all of these on all platforms,
- * and in those cases the user *must* provide us with them (eopts).
- *
- * We try the system first (since its more reliable) and if that fails
- * we check the eopts map. If neither one works, we *give up*!
- */
-
- if (! esock_open_which_domain(fd, &domain)) {
- SSDBG2( dbg,
- ("SOCKET",
- "esock_open2 -> failed get domain from system\r\n") );
-
- if (! esock_open2_get_domain(env, eopts, &domain)) {
- return esock_make_invalid(env, esock_atom_domain);
- }
- }
-
- if (! esock_open_which_type(fd, &type)) {
- SSDBG2( dbg,
- ("SOCKET", "esock_open2 -> failed get type from system\r\n") );
-
- if (! esock_open2_get_type(env, eopts, &type))
- return esock_make_invalid(env, esock_atom_type);
- }
-
- if (! esock_open_which_protocol(fd, &protocol)) {
- SSDBG2( dbg,
- ("SOCKET",
- "esock_open2 -> failed get protocol from system\r\n") );
-
- if (! esock_extract_int_from_map(env, eopts,
- esock_atom_protocol, &protocol)) {
- SSDBG2( dbg,
- ("SOCKET",
- "esock_open2 -> trying protocol 0\r\n") );
- protocol = 0;
- }
- }
-
-
- SSDBG2( dbg,
- ("SOCKET", "esock_open2 -> "
- "\r\n domain: %d"
- "\r\n type: %d"
- "\r\n protocol: %d"
- "\r\n", domain, type, protocol) );
-
-
- if (esock_open2_todup(env, eopts)) {
- /* We shall dup the socket */
- if (ESOCK_IS_ERROR(sock = dup(fd))) {
- save_errno = sock_errno();
-
- SSDBG2( dbg,
- ("SOCKET",
- "esock_open2 -> dup failed: %d\r\n",
- save_errno) );
-
- return esock_make_error_errno(env, save_errno);
- }
- closeOnClose = TRUE;
- } else {
- sock = fd;
- closeOnClose = FALSE;
- }
-
- event = sock;
-
- SET_NONBLOCKING(sock);
-
- /* Create and initiate the socket "descriptor" */
- descP = alloc_descriptor(sock, event);
- descP->ctrlPid = self;
- descP->domain = domain;
- descP->type = type;
- descP->protocol = protocol;
- descP->closeOnClose = closeOnClose;
- descP->origFD = fd;
-
- /* Check if we are already connected, if so change state */
- {
- ESockAddress remote;
- SOCKLEN_T addrLen = sizeof(remote);
- sys_memzero((char *) &remote, addrLen);
- if (sock_peer(descP->sock,
- (struct sockaddr*) &remote,
- &addrLen) == 0) {
- SSDBG2( dbg, ("SOCKET", "esock_open2 -> connected\r\n") );
- descP->writeState |= ESOCK_STATE_CONNECTED;
- } else {
- SSDBG2( dbg, ("SOCKET", "esock_open2 -> not connected\r\n") );
- }
- }
-
- /* And create the 'socket' resource */
- sockRef = enif_make_resource(env, descP);
- enif_release_resource(descP);
-
- ESOCK_ASSERT( MONP("esock_open2 -> ctrl",
- env, descP,
- &descP->ctrlPid,
- &descP->ctrlMon) == 0 );
-
- descP->dbg = dbg;
- descP->useReg = useReg;
- inc_socket(domain, type, protocol);
-
- /* And finally (maybe) update the registry.
- * Shall we keep track of the fact that this socket is created elsewhere?
- */
- if (descP->useReg) esock_send_reg_add_msg(env, descP, sockRef);
-
- SSDBG2( dbg,
- ("SOCKET", "esock_open2 -> done: %T\r\n", sockRef) );
-
- return esock_make_ok2(env, sockRef);
-}
-#endif // #ifndef __WIN32__
-
-
-/* The eextra contains a boolean 'dup' key. Defaults to TRUE.
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T esock_open2_todup(ErlNifEnv* env, ERL_NIF_TERM eextra)
-{
- return esock_get_bool_from_map(env, eextra, atom_dup, TRUE);
-}
-#endif // #ifndef __WIN32__
-
-/* The eextra contains an integer 'domain' key.
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T esock_open2_get_domain(ErlNifEnv* env,
- ERL_NIF_TERM eopts, int* domain)
-{
- ERL_NIF_TERM edomain;
-
- SGDBG( ("SOCKET", "esock_open2_get_domain -> entry with"
- "\r\n eopts: %T"
- "\r\n", eopts) );
-
- if (!GET_MAP_VAL(env, eopts,
- esock_atom_domain, &edomain))
- return FALSE;
-
- if (esock_decode_domain(env, edomain, domain) == 0)
- return FALSE;
-
- return TRUE;
-}
-#endif // #ifndef __WIN32__
-
-/* The eextra contains an integer 'type' key.
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T esock_open2_get_type(ErlNifEnv* env,
- ERL_NIF_TERM eopts, int* type)
-{
- ERL_NIF_TERM etype;
-
- SGDBG( ("SOCKET", "esock_open2_get_type -> entry with"
- "\r\n eopts: %T"
- "\r\n", eopts) );
-
- if (! GET_MAP_VAL(env, eopts, esock_atom_type, &etype))
- return FALSE;
-
- if (! esock_decode_type(env, etype, type))
- return FALSE;
-
- return TRUE;
-}
-#endif // #ifndef __WIN32__
-
-
-/* esock_open4 - create an endpoint for communication
- *
- * Assumes the input has been validated.
- *
- * Normally we want debugging on (individual) sockets to be controlled
- * by the sockets own debug flag. But since we don't even have a socket
- * yet, we must use the global debug flag.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_open4(ErlNifEnv* env,
- int domain,
- int type,
- int protocol,
- ERL_NIF_TERM eopts)
-{
- BOOLEAN_T dbg = esock_open_is_debug(env, eopts, data.sockDbg);
- BOOLEAN_T useReg = esock_open_use_registry(env, eopts, data.useReg);
- ESockDescriptor* descP;
- ERL_NIF_TERM sockRef;
- int proto = protocol, save_errno;
- SOCKET sock;
- ErlNifEvent event;
- char* netns;
-#ifdef HAVE_SETNS
- int current_ns = 0;
-#endif
- ErlNifPid self;
-
- /* Keep track of the creator
- * This should not be a problem, but just in case
- * the *open* function is used with the wrong kind
- * of environment...
- */
- ESOCK_ASSERT( enif_self(env, &self) != NULL );
-
- SSDBG2( dbg,
- ("SOCKET", "esock_open4 -> entry with"
- "\r\n domain: %d"
- "\r\n type: %d"
- "\r\n protocol: %d"
- "\r\n eopts: %T"
- "\r\n", domain, type, protocol, eopts) );
-
-
-#ifdef HAVE_SETNS
- if (esock_open4_get_netns(env, eopts, &netns)) {
- SGDBG( ("SOCKET", "nif_open -> namespace: %s\r\n", netns) );
- }
-#else
- netns = NULL;
-#endif
-
-
-#ifdef HAVE_SETNS
- if ((netns != NULL) &&
- (! change_network_namespace(netns, &current_ns, &save_errno))) {
- FREE(netns);
- return esock_make_error_errno(env, save_errno);
- }
-#endif
-
- if (ESOCK_IS_ERROR(sock = sock_open(domain, type, proto))) {
- if (netns != NULL) FREE(netns);
- return esock_make_error_errno(env, sock_errno());
- }
-
- SSDBG2( dbg, ("SOCKET", "esock_open -> open success: %d\r\n", sock) );
-
-
- /* NOTE that if the protocol = 0 (default) and the domain is not
- * local (AF_LOCAL) we need to explicitly get the protocol here!
- */
-
- if (proto == 0)
- (void) esock_open_which_protocol(sock, &proto);
-
-#ifdef HAVE_SETNS
- if (netns != NULL) {
- FREE(netns);
- if (! restore_network_namespace(current_ns, sock, &save_errno))
- return esock_make_error_errno(env, save_errno);
- }
-#endif
-
-
- if ((event = sock_create_event(sock)) == INVALID_EVENT) {
- save_errno = sock_errno();
- (void) sock_close(sock);
- return esock_make_error_errno(env, save_errno);
- }
-
- SSDBG2( dbg, ("SOCKET", "esock_open4 -> event success: %d\r\n", event) );
-
- SET_NONBLOCKING(sock);
-
-
- /* Create and initiate the socket "descriptor" */
- descP = alloc_descriptor(sock, event);
- descP->ctrlPid = self;
- descP->domain = domain;
- descP->type = type;
- descP->protocol = proto;
-
- sockRef = enif_make_resource(env, descP);
- enif_release_resource(descP);
-
- ESOCK_ASSERT( MONP("esock_open -> ctrl",
- env, descP,
- &descP->ctrlPid,
- &descP->ctrlMon) == 0 );
-
- descP->dbg = dbg;
- descP->useReg = useReg;
- inc_socket(domain, type, protocol);
-
- /* And finally (maybe) update the registry */
- if (descP->useReg) esock_send_reg_add_msg(env, descP, sockRef);
-
- return esock_make_ok2(env, sockRef);
-}
-#endif // #ifndef __WIN32__
-
-/* The eextra map "may" contain a boolean 'debug' key.
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T esock_open_is_debug(ErlNifEnv* env, ERL_NIF_TERM eextra,
- BOOLEAN_T dflt)
-{
- return esock_get_bool_from_map(env, eextra, esock_atom_debug, dflt);
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
-static
-BOOLEAN_T esock_open_use_registry(ErlNifEnv* env, ERL_NIF_TERM eextra,
- BOOLEAN_T dflt)
+extern
+BOOLEAN_T esock_open_is_debug(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ BOOLEAN_T def)
{
- return esock_get_bool_from_map(env, eextra, atom_use_registry, dflt);
+ return esock_get_bool_from_map(env, eopts, esock_atom_debug, def);
}
-#endif
-
-#ifndef __WIN32__
-static
-BOOLEAN_T esock_open_which_domain(SOCKET sock, int* domain)
-{
-#if defined(SO_DOMAIN)
- if (esock_getopt_int(sock, SOL_SOCKET, SO_DOMAIN, domain))
- return TRUE;
-#endif
- return FALSE;
-}
-#endif // #ifndef __WIN32__
-
-
-#ifndef __WIN32__
-static
-BOOLEAN_T esock_open_which_type(SOCKET sock, int* type)
+extern
+BOOLEAN_T esock_open_use_registry(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ BOOLEAN_T def)
{
-#if defined(SO_TYPE)
- if (esock_getopt_int(sock, SOL_SOCKET, SO_TYPE, type))
- return TRUE;
-#endif
- return FALSE;
+ return esock_get_bool_from_map(env, eopts, esock_atom_use_registry, def);
}
-#endif // #ifndef __WIN32__
-
-#ifndef __WIN32__
-static
+extern
BOOLEAN_T esock_open_which_protocol(SOCKET sock, int* proto)
{
#if defined(SO_PROTOCOL)
@@ -6188,89 +5016,7 @@ BOOLEAN_T esock_open_which_protocol(SOCKET sock, int* proto)
#endif
return FALSE;
}
-#endif // #ifndef __WIN32__
-
-
-
-#ifdef HAVE_SETNS
-
-
-/* We should really have another API, so that we can return errno... */
-
-/* *** change network namespace ***
- * Retrieve the current namespace and set the new.
- * Return result and previous namespace if successful.
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err)
-{
- int save_errno;
- int current_ns = 0;
- int new_ns = 0;
-
- SGDBG( ("SOCKET", "change_network_namespace -> entry with"
- "\r\n new ns: %s"
- "\r\n", netns) );
-
- current_ns = open("/proc/self/ns/net", O_RDONLY);
- if (ESOCK_IS_ERROR(current_ns)) {
- *err = sock_errno();
- return FALSE;
- }
- new_ns = open(netns, O_RDONLY);
- if (ESOCK_IS_ERROR(new_ns)) {
- save_errno = sock_errno();
- (void) close(current_ns);
- *err = save_errno;
- return FALSE;
- }
- if (setns(new_ns, CLONE_NEWNET) != 0) {
- save_errno = sock_errno();
- (void) close(new_ns);
- (void) close(current_ns);
- *err = save_errno;
- return FALSE;
- } else {
- (void) close(new_ns);
- *cns = current_ns;
- return TRUE;
- }
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** restore network namespace ***
- * Restore the previous namespace (see above).
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err)
-{
- SGDBG( ("SOCKET", "restore_network_namespace -> entry with"
- "\r\n ns: %d"
- "\r\n", ns) );
-
- if (setns(ns, CLONE_NEWNET) != 0) {
- /* XXX Failed to restore network namespace.
- * What to do? Tidy up and return an error...
- * Note that the thread now might still be in the namespace.
- * Can this even happen? Should the emulator be aborted?
- */
- int save_errno = sock_errno();
- (void) close(sock);
- (void) close(ns);
- *err = save_errno;
- return FALSE;
- } else {
- (void) close(ns);
- return TRUE;
- }
-}
-#endif // #ifndef __WIN32__
-
-#endif // ifdef HAVE_SETNS
@@ -6289,9 +5035,6 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM eSockAddr, ret;
ESockAddress sockAddr;
@@ -6309,7 +5052,7 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env,
eSockAddr = argv[1];
if (! esock_decode_sockaddr(env, eSockAddr, &sockAddr, &addrLen))
- return esock_make_invalid(env, atom_sockaddr);
+ return esock_make_invalid(env, esock_atom_sockaddr);
MLOCK(descP->readMtx);
@@ -6320,7 +5063,7 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env,
argv[0], descP->sock, descP->readState,
eSockAddr) );
- ret = esock_bind(env, descP, &sockAddr, addrLen);
+ ret = ESOCK_IO_BIND(env, descP, &sockAddr, addrLen);
SSDBG( descP, ("SOCKET", "nif_bind(%T) -> done with"
"\r\n ret: %T"
@@ -6329,32 +5072,9 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env,
MUNLOCK(descP->readMtx);
return ret;
-#endif // #ifdef __WIN32__ #else
}
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_bind(ErlNifEnv* env,
- ESockDescriptor* descP,
- ESockAddress* sockAddrP,
- SOCKLEN_T addrLen)
-{
- if (! IS_OPEN(descP->readState))
- return esock_make_error(env, atom_closed);
-
- if (sock_bind(descP->sock, &sockAddrP->sa, addrLen) < 0) {
- return esock_make_error_errno(env, sock_errno());
- }
-
- descP->readState |= ESOCK_STATE_BOUND;
-
- return esock_atom_ok;
-}
-#endif // #ifndef __WIN32__
-
-
-
/* ----------------------------------------------------------------------
* nif_connect
*
@@ -6374,9 +5094,6 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM res, sockRef, connRef;
ESockAddress addr, *addrP;
@@ -6400,14 +5117,14 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env,
return enif_make_badarg(env);
if (! esock_decode_sockaddr(env, eSockAddr, &addr, &addrLen))
- return esock_make_invalid(env, atom_sockaddr);
+ return esock_make_invalid(env, esock_atom_sockaddr);
addrP = &addr;
MLOCK(descP->writeMtx);
SSDBG( descP,
("SOCKET", "nif_connect(%T), {%d0x%X} ->"
- "\r\n ConnRef: %T"
+ "\r\n ConnRef: %T"
"\r\n SockAddr: %T"
"\r\n",
sockRef, descP->sock, descP->writeState,
@@ -6417,7 +5134,7 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env,
ESOCK_ASSERT( argc == 1 );
connRef = esock_atom_undefined;
- addrP = NULL;
+ addrP = NULL;
addrLen = 0;
MLOCK(descP->writeMtx);
@@ -6429,7 +5146,7 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env,
) );
}
- res = esock_connect(env, descP, sockRef, connRef, addrP, addrLen);
+ res = ESOCK_IO_CONNECT(env, descP, sockRef, connRef, addrP, addrLen);
SSDBG( descP, ("SOCKET", "nif_connect(%T) -> done with"
"\r\n res: %T"
@@ -6439,189 +5156,7 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env,
return res;
-#endif // #ifdef __WIN32__ #else
-}
-
-
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_connect(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM connRef,
- ESockAddress* addrP,
- SOCKLEN_T addrLen)
-{
- int save_errno;
- ErlNifPid self;
-
- ESOCK_ASSERT( enif_self(env, &self) != NULL );
-
- /*
- * Verify that we are in the proper state
- */
-
- if (! IS_OPEN(descP->writeState))
- return esock_make_error(env, atom_closed);
-
- /* Connect and Write uses the same select flag
- * so they can not be simultaneous
- */
- if (descP->currentWriterP != NULL)
- return esock_make_error_invalid(env, atom_state);
-
- if (descP->connectorP != NULL) {
- /* Connect in progress */
-
- if (COMPARE_PIDS(&self, &descP->connector.pid) != 0) {
- /* Other process has connect in progress */
- if (addrP != NULL) {
- return esock_make_error(env, atom_already);
- } else {
- /* This is a bad call sequence
- * - connect without an address is only allowed
- * for the connecting process
- */
- return esock_raise_invalid(env, atom_state);
- }
- }
-
- /* Finalize after received select message */
-
- requestor_release("esock_connect finalize -> connected",
- env, descP, &descP->connector);
- descP->connectorP = NULL;
- descP->writeState &= ~ESOCK_STATE_CONNECTING;
-
- if (! verify_is_connected(descP, &save_errno)) {
- return esock_make_error_errno(env, save_errno);
- }
-
- descP->writeState |= ESOCK_STATE_CONNECTED;
-
- return esock_atom_ok;
- }
-
- /* No connect in progress */
-
- if (addrP == NULL)
- /* This is a bad call sequence
- * - connect without an address is only allowed when
- * a connect is in progress, after getting the select message
- */
- return esock_raise_invalid(env, atom_state);
-
- /* Initial connect call, with address */
-
- if (sock_connect(descP->sock, (struct sockaddr*) addrP, addrLen) == 0) {
- /* Success already! */
- SSDBG( descP, ("SOCKET", "esock_connect {%d} -> connected\r\n",
- descP->sock) );
-
- descP->writeState |= ESOCK_STATE_CONNECTED;
-
- return esock_atom_ok;
- }
-
- /* Connect returned error */
- save_errno = sock_errno();
-
- switch (save_errno) {
-
- case ERRNO_BLOCK: /* Winsock2 */
- case EINPROGRESS: /* Unix & OSE!! */
- SSDBG( descP,
- ("SOCKET", "esock_connect {%d} -> would block => select\r\n",
- descP->sock) );
- {
- int sres;
-
- if ((sres =
- esock_select_write(env, descP->sock, descP, NULL,
- sockRef, connRef)) < 0)
- return
- enif_raise_exception(env,
- MKT2(env, atom_select_write,
- MKI(env, sres)));
- /* Initiate connector */
- descP->connector.pid = self;
- ESOCK_ASSERT( MONP("esock_connect -> conn",
- env, descP,
- &self, &descP->connector.mon) == 0 );
- descP->connector.env = esock_alloc_env("connector");
- descP->connector.ref = CP_TERM(descP->connector.env, connRef);
- descP->connectorP = &descP->connector;
- descP->writeState |=
- (ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED);
-
- return atom_select;
- }
- break;
-
- default:
- SSDBG( descP,
- ("SOCKET", "esock_connect {%d} -> error: %d\r\n",
- descP->sock, save_errno) );
-
- return esock_make_error_errno(env, save_errno);
-
- } // switch(save_errno)
-}
-#endif // #ifndef __WIN32__
-
-
-
-/* *** verify_is_connected ***
- * Check if a connection has been established.
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T verify_is_connected(ESockDescriptor* descP, int* err)
-{
- /*
- * *** This is strange ***
- *
- * This *should* work on Windows NT too, but doesn't.
- * An bug in Winsock 2.0 for Windows NT?
- *
- * See "Unix Netwok Programming", "The Sockets Networking API",
- * W.R.Stevens, Volume 1, third edition, 16.4 Nonblocking 'connect',
- * before Interrupted 'connect' (p 412) for a discussion about
- * Unix portability and non blocking connect.
- */
-
- int error = 0;
-
-#ifdef SO_ERROR
- if (! esock_getopt_int(descP->sock, SOL_SOCKET, SO_ERROR, &error)) {
- // Solaris does it this way according to W.R.Stevens
- error = sock_errno();
- }
-#elif 1
- char buf[0];
- if (ESOCK_IS_ERROR(read(descP->sock, buf, sizeof(buf)))) {
- error = sock_errno();
- }
-#else
- /* This variant probably returns wrong error value
- * ENOTCONN instead of the actual connect error
- */
- ESockAddress remote;
- SOCKLEN_T addrLen = sizeof(remote);
- sys_memzero((char *) &remote, addrLen);
- if (sock_peer(descP->sock,
- (struct sockaddr*) &remote, &addrLen)) < 0) {
- error = sock_errno();
- }
-#endif
-
- if (error != 0) {
- *err = error;
- return FALSE;
- }
- return TRUE;
}
-#endif // #ifndef __WIN32__
@@ -6641,9 +5176,6 @@ ERL_NIF_TERM nif_listen(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
int backlog;
ERL_NIF_TERM ret;
@@ -6673,7 +5205,7 @@ ERL_NIF_TERM nif_listen(ErlNifEnv* env,
argv[0], descP->sock, descP->readState,
backlog) );
- ret = esock_listen(env, descP, backlog);
+ ret = ESOCK_IO_LISTEN(env, descP, backlog);
SSDBG( descP, ("SOCKET", "nif_listen(%T) -> done with"
"\r\n ret: %T"
@@ -6682,12 +5214,12 @@ ERL_NIF_TERM nif_listen(ErlNifEnv* env,
MUNLOCK(descP->readMtx);
return ret;
-#endif // #ifdef __WIN32__ #else
}
-#ifndef __WIN32__
+/* ========================================================================
+ */
static
ERL_NIF_TERM esock_listen(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -6698,13 +5230,26 @@ ERL_NIF_TERM esock_listen(ErlNifEnv* env,
* Verify that we are in the proper state
*/
+ SSDBG( descP,
+ ("SOCKET", "esock_listen(%d) -> verify open\r\n", descP->sock) );
if (! IS_OPEN(descP->readState))
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
+
+#if defined(__WIN32__)
+ SSDBG( descP,
+ ("SOCKET", "esock_listen(%d) -> verify bound\r\n", descP->sock) );
+ if (! IS_BOUND(descP->writeState))
+ return esock_make_error(env, esock_atom_not_bound);
+#endif
/*
* And attempt to make socket listening
*/
+ SSDBG( descP, ("SOCKET", "esock_listen(%d) -> try listen with"
+ "\r\n backlog: %d"
+ "\r\n", descP->sock, backlog) );
+
if ((sock_listen(descP->sock, backlog)) < 0)
return esock_make_error_errno(env, sock_errno());
@@ -6713,7 +5258,6 @@ ERL_NIF_TERM esock_listen(ErlNifEnv* env,
return esock_atom_ok;
}
-#endif // #ifndef __WIN32__
@@ -6733,10 +5277,6 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
-
ESockDescriptor* descP;
ERL_NIF_TERM sockRef, ref, res;
@@ -6754,6 +5294,7 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env,
MLOCK(descP->readMtx);
+#ifndef __WIN32__
SSDBG( descP,
("SOCKET", "nif_accept%T), {%d,0x%X} ->"
"\r\n ReqRef: %T"
@@ -6770,8 +5311,18 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env,
ESOCK_MON2TERM(env, &descP->currentAcceptor.mon),
descP->currentAcceptor.env,
descP->currentAcceptor.ref) );
+#else
+ SSDBG( descP,
+ ("SOCKET", "nif_accept%T), {%d,0x%X} ->"
+ "\r\n ReqRef: %T"
+ "\r\n First Acceptor addr: %p"
+ "\r\n",
+ sockRef, descP->sock, descP->readState,
+ ref,
+ descP->acceptorsQ.first) );
+#endif
- res = esock_accept(env, descP, sockRef, ref);
+ res = ESOCK_IO_ACCEPT(env, descP, sockRef, ref);
SSDBG( descP, ("SOCKET", "nif_accept(%T) -> done with"
"\r\n res: %T"
@@ -6781,492 +5332,7 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env,
return res;
-#endif // #ifdef __WIN32__ #else
-}
-
-
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_accept(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM accRef)
-{
- ErlNifPid caller;
-
- ESOCK_ASSERT( enif_self(env, &caller) != NULL );
-
- if (! IS_OPEN(descP->readState))
- return esock_make_error(env, atom_closed);
-
- /* Accept and Read uses the same select flag
- * so they can not be simultaneous
- */
- if (descP->currentReaderP != NULL)
- return esock_make_error_invalid(env, atom_state);
-
- if (descP->currentAcceptorP == NULL) {
- SOCKET accSock;
-
- /* We have no active acceptor (and therefore no acceptors in queue)
- */
-
- SSDBG( descP, ("SOCKET", "esock_accept {%d} -> try accept\r\n",
- descP->sock) );
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_acc_tries, &descP->accTries, 1);
-
- accSock = sock_accept(descP->sock, NULL, NULL);
-
- if (ESOCK_IS_ERROR(accSock)) {
- int save_errno;
-
- save_errno = sock_errno();
-
- return esock_accept_listening_error(env, descP, sockRef,
- accRef, caller, save_errno);
- } else {
- /* We got an incoming connection */
- return esock_accept_listening_accept(env, descP, sockRef,
- accSock, caller);
- }
- } else {
-
- /* We have an active acceptor and possibly acceptors waiting in queue.
- * If the pid of the calling process is not the pid of the
- * "current process", push the requester onto the (acceptor) queue.
- */
-
- SSDBG( descP, ("SOCKET", "esock_accept_accepting -> check: "
- "is caller current acceptor:"
- "\r\n Caller: %T"
- "\r\n Current: %T"
- "\r\n Current Mon: %T"
- "\r\n",
- caller,
- descP->currentAcceptor.pid,
- ESOCK_MON2TERM(env, &descP->currentAcceptor.mon)) );
-
- if (COMPARE_PIDS(&descP->currentAcceptor.pid, &caller) == 0) {
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_accepting {%d} -> current acceptor"
- "\r\n", descP->sock) );
-
- return esock_accept_accepting_current(env, descP, sockRef, accRef);
-
- } else {
-
- /* Not the "current acceptor", so (maybe) push onto queue */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_accepting {%d} -> *not* current acceptor\r\n",
- descP->sock) );
-
- return esock_accept_accepting_other(env, descP, accRef, caller);
- }
- }
-}
-#endif // #ifndef __WIN32__
-
-/* *** esock_accept_listening_error ***
- *
- * The accept call resultet in an error - handle it.
- * There are only two cases:
- * 1) BLOCK => Attempt a "retry"
- * 2) Other => Return the value (converted to an atom)
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_accept_listening_error(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM accRef,
- ErlNifPid caller,
- int save_errno)
-{
- ERL_NIF_TERM res;
-
- if (save_errno == ERRNO_BLOCK ||
- save_errno == EAGAIN) {
-
- /* *** Try again later *** */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_listening_error {%d} -> would block - retry\r\n",
- descP->sock) );
-
- descP->currentAcceptor.pid = caller;
- ESOCK_ASSERT( MONP("esock_accept_listening -> current acceptor",
- env, descP,
- &descP->currentAcceptor.pid,
- &descP->currentAcceptor.mon) == 0 );
- ESOCK_ASSERT( descP->currentAcceptor.env == NULL );
- descP->currentAcceptor.env = esock_alloc_env("current acceptor");
- descP->currentAcceptor.ref =
- CP_TERM(descP->currentAcceptor.env, accRef);
- descP->currentAcceptorP = &descP->currentAcceptor;
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_listening_error {%d} -> retry for: "
- "\r\n Current Pid: %T"
- "\r\n Current Mon: %T"
- "\r\n",
- descP->sock,
- descP->currentAcceptor.pid,
- ESOCK_MON2TERM(env, &descP->currentAcceptor.mon)) );
-
- res = esock_accept_busy_retry(env, descP, sockRef, accRef, NULL);
- } else {
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_listening {%d} -> errno: %d\r\n",
- descP->sock, save_errno) );
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_acc_fails, &descP->accFails, 1);
-
- res = esock_make_error_errno(env, save_errno);
- }
-
- return res;
}
-#endif // #ifndef __WIN32__
-
-
-/* *** esock_accept_listening_accept ***
- *
- * The accept call was successful (accepted) - handle the new connection.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_accept_listening_accept(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- SOCKET accSock,
- ErlNifPid caller)
-{
- ERL_NIF_TERM res;
-
- esock_accept_accepted(env, descP, sockRef, accSock, caller, &res);
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** esock_accept_accepting_current ***
- * Handles when the current acceptor makes another attempt.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_accept_accepting_current(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM accRef)
-{
- SOCKET accSock;
- int save_errno;
- ERL_NIF_TERM res;
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_accepting_current {%d} -> try accept\r\n",
- descP->sock) );
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_acc_tries, &descP->accTries, 1);
-
- accSock = sock_accept(descP->sock, NULL, NULL);
-
- if (ESOCK_IS_ERROR(accSock)) {
-
- save_errno = sock_errno();
-
- res = esock_accept_accepting_current_error(env, descP, sockRef,
- accRef, save_errno);
- } else {
-
- res = esock_accept_accepting_current_accept(env, descP, sockRef,
- accSock);
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** esock_accept_accepting_current_accept ***
- *
- * Handles when the current acceptor succeeded in its accept call -
- * handle the new connection.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_accept_accepting_current_accept(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- SOCKET accSock)
-{
- ERL_NIF_TERM res;
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_accepting_current_accept {%d}"
- "\r\n", descP->sock) );
-
- if (esock_accept_accepted(env, descP, sockRef, accSock,
- descP->currentAcceptor.pid, &res)) {
-
- ESOCK_ASSERT( DEMONP("esock_accept_accepting_current_accept -> "
- "current acceptor",
- env, descP, &descP->currentAcceptor.mon) == 0);
-
- MON_INIT(&descP->currentAcceptor.mon);
-
- if (!activate_next_acceptor(env, descP, sockRef)) {
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_accepting_current_accept {%d} ->"
- " no more acceptors"
- "\r\n", descP->sock) );
-
- descP->readState &= ~ESOCK_STATE_ACCEPTING;
-
- descP->currentAcceptorP = NULL;
- }
-
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** esock_accept_accepting_current_error ***
- * The accept call of current acceptor resultet in an error - handle it.
- * There are only two cases:
- * 1) BLOCK => Attempt a "retry"
- * 2) Other => Return the value (converted to an atom)
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_accept_accepting_current_error(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM opRef,
- int save_errno)
-{
- ERL_NIF_TERM res, reason;
-
- if (save_errno == ERRNO_BLOCK ||
- save_errno == EAGAIN) {
-
- /*
- * Just try again, no real error, just a ghost trigger from poll,
- */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_accepting_current_error {%d} -> "
- "would block: try again\r\n", descP->sock) );
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_acc_waits, &descP->accWaits, 1);
-
- res = esock_accept_busy_retry(env, descP, sockRef, opRef,
- &descP->currentAcceptor.pid);
-
- } else {
- ESockRequestor req;
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_accepting_current_error {%d} -> "
- "error: %d\r\n", descP->sock, save_errno) );
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_acc_fails, &descP->accFails, 1);
-
- requestor_release("esock_accept_accepting_current_error",
- env, descP, &descP->currentAcceptor);
-
- reason = MKA(env, erl_errno_id(save_errno));
- res = esock_make_error(env, reason);
-
- req.env = NULL;
- while (acceptor_pop(env, descP, &req)) {
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_accepting_current_error {%d} -> abort %T\r\n",
- descP->sock, req.pid) );
-
- esock_send_abort_msg(env, descP, sockRef, &req, reason);
-
- (void) DEMONP("esock_accept_accepting_current_error -> "
- "pop'ed writer",
- env, descP, &req.mon);
- }
- descP->currentAcceptorP = NULL;
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** esock_accept_accepting_other ***
- * Handles when the another acceptor makes an attempt, which
- * results (maybe) in the request being pushed onto the
- * acceptor queue.
- */
-#ifndef __WIN32__
-ERL_NIF_TERM
-esock_accept_accepting_other(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM ref,
- ErlNifPid caller)
-{
- if (! acceptor_search4pid(env, descP, &caller)) {
- acceptor_push(env, descP, caller, ref);
- return atom_select;
- } else {
- /* Acceptor already in queue */
- return esock_raise_invalid(env, atom_state);
- }
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** esock_accept_busy_retry ***
- *
- * Perform a retry select. If successful, set nextState.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_accept_busy_retry(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM accRef,
- ErlNifPid* pidP)
-{
- int sres;
- ERL_NIF_TERM res;
-
- if ((sres = esock_select_read(env, descP->sock, descP, pidP,
- sockRef, accRef)) < 0) {
-
- ESOCK_ASSERT( DEMONP("esock_accept_busy_retry - select failed",
- env, descP, &descP->currentAcceptor.mon) == 0);
-
- MON_INIT(&descP->currentAcceptor.mon);
-
- /* It is very unlikely that a next acceptor will be able
- * to do anything successful, but we will clean the queue
- */
-
- if (!activate_next_acceptor(env, descP, sockRef)) {
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_busy_retry {%d} -> no more acceptors\r\n",
- descP->sock) );
-
- descP->readState &= ~ESOCK_STATE_ACCEPTING;
-
- descP->currentAcceptorP = NULL;
- }
-
- res =
- enif_raise_exception(env,
- MKT2(env, atom_select_read,
- MKI(env, sres)));
- } else {
- descP->readState |=
- (ESOCK_STATE_ACCEPTING | ESOCK_STATE_SELECTED);
- res = atom_select;
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** esock_accept_accepted ***
- *
- * Generic function handling a successful accept.
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T esock_accept_accepted(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- SOCKET accSock,
- ErlNifPid pid,
- ERL_NIF_TERM* result)
-{
- ESockDescriptor* accDescP;
- ErlNifEvent accEvent;
- ERL_NIF_TERM accRef;
- int save_errno;
-
- /*
- * We got one
- */
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_acc_success, &descP->accSuccess, 1);
-
- if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) {
- save_errno = sock_errno();
- (void) sock_close(accSock);
- *result = esock_make_error_errno(env, save_errno);
- return FALSE;
- }
-
- accDescP = alloc_descriptor(accSock, accEvent);
- accDescP->domain = descP->domain;
- accDescP->type = descP->type;
- accDescP->protocol = descP->protocol;
-
- MLOCK(descP->writeMtx);
-
- accDescP->rBufSz = descP->rBufSz; // Inherit buffer size
- accDescP->rNum = descP->rNum; // Inherit buffer uses
- accDescP->rNumCnt = 0;
- accDescP->rCtrlSz = descP->rCtrlSz; // Inherit buffer size
- accDescP->wCtrlSz = descP->wCtrlSz; // Inherit buffer size
- accDescP->iow = descP->iow; // Inherit iow
- accDescP->dbg = descP->dbg; // Inherit debug flag
- accDescP->useReg = descP->useReg; // Inherit useReg flag
- inc_socket(accDescP->domain, accDescP->type, accDescP->protocol);
-
- accRef = enif_make_resource(env, accDescP);
- enif_release_resource(accDescP);
-
- accDescP->ctrlPid = pid;
- /* pid has actually been compared equal to self()
- * in this code path just a little while ago
- */
- ESOCK_ASSERT( MONP("esock_accept_accepted -> ctrl",
- env, accDescP,
- &accDescP->ctrlPid,
- &accDescP->ctrlMon) == 0 );
-
- SET_NONBLOCKING(accDescP->sock);
-
- descP->writeState |= ESOCK_STATE_CONNECTED;
-
- MUNLOCK(descP->writeMtx);
-
- /* And finally (maybe) update the registry */
- if (descP->useReg) esock_send_reg_add_msg(env, descP, accRef);
-
- *result = esock_make_ok2(env, accRef);
-
- return TRUE;
-}
-#endif // #ifndef __WIN32__
@@ -7288,9 +5354,6 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM sockRef, sendRef;
ErlNifBinary sndData;
@@ -7342,7 +5405,7 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env,
* is done!
*/
- res = esock_send(env, descP, sockRef, sendRef, &sndData, flags);
+ res = ESOCK_IO_SEND(env, descP, sockRef, sendRef, &sndData, flags);
SSDBG( descP, ("SOCKET", "nif_send(%T) -> done with"
"\r\n res: %T"
@@ -7353,65 +5416,10 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env,
SGDBG( ("SOCKET", "nif_send -> done with result: "
"\r\n %T"
"\r\n", res) );
- return res;
-
-#endif // #ifdef __WIN32__ #else
-}
-
-
-
-/* *** esock_send ***
- *
- * Do the actual send.
- * Do some initial writer checks, do the actual send and then
- * analyze the result. If we are done, another writer may be
- * scheduled (if there is one in the writer queue).
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_send(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- ErlNifBinary* sndDataP,
- int flags)
-{
- ssize_t send_result;
- ERL_NIF_TERM writerCheck;
-
- if (! IS_OPEN(descP->writeState))
- return esock_make_error(env, atom_closed);
-
- /* Connect and Write uses the same select flag
- * so they can not be simultaneous
- */
- if (descP->connectorP != NULL)
- return esock_make_error_invalid(env, atom_state);
-
- send_result = (ssize_t) sndDataP->size;
- if ((size_t) send_result != sndDataP->size)
- return esock_make_error_invalid(env, atom_data_size);
-
- /* Ensure that we either have no current writer or we are it,
- * or enqueue this process if there is a current writer */
- if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
- SSDBG( descP, ("SOCKET", "esock_send {%d} -> writer check failed: "
- "\r\n %T\r\n", descP->sock, writerCheck) );
- return writerCheck;
- }
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_write_tries, &descP->writeTries, 1);
- send_result =
- sock_send(descP->sock, sndDataP->data, sndDataP->size, flags);
-
- return send_check_result(env, descP,
- send_result, sndDataP->size, FALSE,
- sockRef, sendRef);
+ return res;
}
-#endif // #ifndef __WIN32__
-
/* ----------------------------------------------------------------------
@@ -7433,9 +5441,6 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM sockRef, sendRef;
ErlNifBinary sndData;
@@ -7480,7 +5485,7 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env,
"nif_sendto(%T), {%d} -> sockaddr decode failed \r\n",
sockRef, descP->sock) );
- return esock_make_invalid(env, atom_sockaddr);
+ return esock_make_invalid(env, esock_atom_sockaddr);
}
MLOCK(descP->writeMtx);
@@ -7495,8 +5500,8 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env,
sockRef, descP->sock, descP->readState,
sendRef, sndData.size, eSockAddr, flags) );
- res = esock_sendto(env, descP, sockRef, sendRef, &sndData, flags,
- &remoteAddr, remoteAddrLen);
+ res = ESOCK_IO_SENDTO(env, descP, sockRef, sendRef, &sndData, flags,
+ &remoteAddr, remoteAddrLen);
SSDBG( descP, ("SOCKET", "nif_sendto(%T) -> done with"
"\r\n res: %T"
@@ -7506,66 +5511,9 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env,
return res;
-#endif // if defined(__WIN32__)
}
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_sendto(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- ErlNifBinary* dataP,
- int flags,
- ESockAddress* toAddrP,
- SOCKLEN_T toAddrLen)
-{
- ssize_t sendto_result;
- ERL_NIF_TERM writerCheck;
-
- if (! IS_OPEN(descP->writeState))
- return esock_make_error(env, atom_closed);
-
- /* Connect and Write uses the same select flag
- * so they can not be simultaneous
- */
- if (descP->connectorP != NULL)
- return esock_make_error_invalid(env, atom_state);
-
- sendto_result = (ssize_t) dataP->size;
- if ((size_t) sendto_result != dataP->size)
- return esock_make_error_invalid(env, atom_data_size);
-
- /* Ensure that we either have no current writer or we are it,
- * or enqueue this process if there is a current writer */
- if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
- SSDBG( descP, ("SOCKET", "esock_sendto {%d} -> writer check failed: "
- "\r\n %T\r\n", descP->sock, writerCheck) );
- return writerCheck;
- }
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_write_tries, &descP->writeTries, 1);
-
- if (toAddrP != NULL) {
- sendto_result =
- sock_sendto(descP->sock,
- dataP->data, dataP->size, flags,
- &toAddrP->sa, toAddrLen);
- } else {
- sendto_result =
- sock_sendto(descP->sock,
- dataP->data, dataP->size, flags,
- NULL, 0);
- }
-
- return send_check_result(env, descP, sendto_result, dataP->size, FALSE,
- sockRef, sendRef);
-}
-#endif // #ifndef __WIN32__
-
-
-
/* ----------------------------------------------------------------------
* nif_sendmsg
*
@@ -7577,6 +5525,7 @@ ERL_NIF_TERM esock_sendto(ErlNifEnv* env,
* Msg - Message - map() with data and (maybe) control and dest
* Flags - Send flags as an integer().
* SendRef - A unique id reference() for this (send) request.
+ * IOV - List of binaries
*/
static
@@ -7584,9 +5533,6 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ERL_NIF_TERM res, sockRef, sendRef, eMsg, eIOV;
ESockDescriptor* descP;
int flags;
@@ -7629,7 +5575,7 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env,
sockRef, descP->sock, descP->writeState,
sendRef, flags) );
- res = esock_sendmsg(env, descP, sockRef, sendRef, eMsg, flags, eIOV);
+ res = ESOCK_IO_SENDMSG(env, descP, sockRef, sendRef, eMsg, flags, eIOV);
MUNLOCK(descP->writeMtx);
@@ -7639,231 +5585,9 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env,
return res;
-#endif // #ifdef __WIN32__ #else
}
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_sendmsg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- ERL_NIF_TERM eMsg,
- int flags,
- ERL_NIF_TERM eIOV)
-{
- ERL_NIF_TERM res, eAddr, eCtrl;
- ESockAddress addr;
- struct msghdr msgHdr;
- ErlNifIOVec *iovec = NULL;
- char* ctrlBuf;
- size_t ctrlBufLen, ctrlBufUsed;
- ssize_t dataSize, sendmsg_result;
- ERL_NIF_TERM writerCheck, tail;
-
- if (! IS_OPEN(descP->writeState))
- return esock_make_error(env, atom_closed);
-
- /* Connect and Write uses the same select flag
- * so they can not be simultaneous
- */
- if (descP->connectorP != NULL)
- return esock_make_error_invalid(env, atom_state);
-
- /* Ensure that we either have no current writer or we are it,
- * or enqueue this process if there is a current writer */
- if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
- SSDBG( descP,
- ("SOCKET", "esock_sendmsg {%d} -> writer check failed: "
- "\r\n %T\r\n", descP->sock, writerCheck) );
- return writerCheck;
- }
-
- /* Initiate the .name and .namelen fields depending on if
- * we have an address or not
- */
- if (! GET_MAP_VAL(env, eMsg, esock_atom_addr, &eAddr)) {
-
- SSDBG( descP, ("SOCKET",
- "esock_sendmsg {%d} -> no address\r\n", descP->sock) );
-
- msgHdr.msg_name = NULL;
- msgHdr.msg_namelen = 0;
- } else {
- msgHdr.msg_name = (void*) &addr;
- msgHdr.msg_namelen = sizeof(addr);
- sys_memzero((char *) msgHdr.msg_name, msgHdr.msg_namelen);
-
- SSDBG( descP, ("SOCKET", "esock_sendmsg {%d} ->"
- "\r\n address: %T"
- "\r\n", descP->sock, eAddr) );
-
- if (! esock_decode_sockaddr(env, eAddr,
- msgHdr.msg_name,
- &msgHdr.msg_namelen)) {
- SSDBG( descP, ("SOCKET",
- "esock_sendmsg {%d} -> invalid address\r\n",
- descP->sock) );
- return esock_make_invalid(env, esock_atom_addr);
- }
- }
-
- /* Extract the *mandatory* 'iov', which must be an erlang:iovec(),
- * from which we take at most IOV_MAX binaries
- */
- if ((! enif_inspect_iovec(NULL, data.iov_max, eIOV, &tail, &iovec))) {
- SSDBG( descP, ("SOCKET",
- "esock_sendmsg {%d} -> not an iov\r\n",
- descP->sock) );
-
- return esock_make_invalid(env, esock_atom_iov);
- }
-
- SSDBG( descP, ("SOCKET", "esock_sendmsg {%d} ->"
- "\r\n iovcnt: %lu"
- "\r\n tail: %s"
- "\r\n", descP->sock,
- (unsigned long) iovec->iovcnt,
- B2S(! enif_is_empty_list(env, tail))) );
-
- /* We now have an allocated iovec */
-
- eCtrl = esock_atom_undefined;
- ctrlBufLen = 0;
- ctrlBuf = NULL;
-
- if (iovec->iovcnt > data.iov_max) {
- if (descP->type == SOCK_STREAM) {
- iovec->iovcnt = data.iov_max;
- } else {
- /* We can not send the whole packet in one sendmsg() call */
- SSDBG( descP, ("SOCKET",
- "esock_sendmsg {%d} -> iovcnt > iov_max\r\n",
- descP->sock) );
- res = esock_make_invalid(env, esock_atom_iov);
- goto done_free_iovec;
- }
- }
-
- dataSize = 0;
- {
- ERL_NIF_TERM h, t;
- ErlNifBinary bin;
- size_t i;
-
- /* Find out if there is remaining data in the tail.
- * Skip empty binaries otherwise break.
- * If 'tail' after loop exit is the empty list
- * there was no more data. Otherwise there is more
- * data or the 'iov' is invalid.
- */
- for (;;) {
- if (enif_get_list_cell(env, tail, &h, &t) &&
- enif_inspect_binary(env, h, &bin) &&
- (bin.size == 0)) {
- tail = t;
- continue;
- } else
- break;
- }
-
- if ((! enif_is_empty_list(env, tail)) &&
- (descP->type != SOCK_STREAM)) {
- /* We can not send the whole packet in one sendmsg() call */
- SSDBG( descP, ("SOCKET",
- "esock_sendmsg {%d} -> invalid tail\r\n",
- descP->sock) );
- res = esock_make_invalid(env, esock_atom_iov);
- goto done_free_iovec;
- }
-
- /* Calculate the data size */
-
- for (i = 0; i < iovec->iovcnt; i++) {
- size_t len = iovec->iov[i].iov_len;
- dataSize += len;
- if (dataSize < len) {
- /* Overflow */
- SSDBG( descP, ("SOCKET", "esock_sendmsg {%d} -> Overflow"
- "\r\n i: %lu"
- "\r\n len: %lu"
- "\r\n dataSize: %ld"
- "\r\n", descP->sock, (unsigned long) i,
- (unsigned long) len, (long) dataSize) );
- res = esock_make_invalid(env, esock_atom_iov);
- goto done_free_iovec;
- }
- }
- }
- SSDBG( descP,
- ("SOCKET",
- "esock_sendmsg {%d} ->"
- "\r\n iov length: %lu"
- "\r\n data size: %u"
- "\r\n",
- descP->sock,
- (unsigned long) iovec->iovcnt, (long) dataSize) );
-
- msgHdr.msg_iovlen = iovec->iovcnt;
- msgHdr.msg_iov = iovec->iov;
-
- /* Extract the *optional* 'ctrl' */
- if (GET_MAP_VAL(env, eMsg, esock_atom_ctrl, &eCtrl)) {
- ctrlBufLen = descP->wCtrlSz;
- ctrlBuf = (char*) MALLOC(ctrlBufLen);
- ESOCK_ASSERT( ctrlBuf != NULL );
- }
- SSDBG( descP, ("SOCKET", "esock_sendmsg {%d} -> optional ctrl: "
- "\r\n ctrlBuf: %p"
- "\r\n ctrlBufLen: %lu"
- "\r\n eCtrl: %T"
- "\r\n", descP->sock,
- ctrlBuf, (unsigned long) ctrlBufLen, eCtrl) );
-
- /* Decode the ctrl and initiate that part of the msghdr.
- */
- if (ctrlBuf != NULL) {
- if (! decode_cmsghdrs(env, descP,
- eCtrl,
- ctrlBuf, ctrlBufLen, &ctrlBufUsed)) {
- SSDBG( descP, ("SOCKET",
- "esock_sendmsg {%d} -> invalid ctrl\r\n",
- descP->sock) );
- res = esock_make_invalid(env, esock_atom_ctrl);
- goto done_free_iovec;
- }
- } else {
- ctrlBufUsed = 0;
- }
- msgHdr.msg_control = ctrlBuf;
- msgHdr.msg_controllen = ctrlBufUsed;
-
- /* The msg_flags field is not used when sending,
- * but zero it just in case */
- msgHdr.msg_flags = 0;
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_write_tries, &descP->writeTries, 1);
-
- /* And now, try to send the message */
- sendmsg_result = sock_sendmsg(descP->sock, &msgHdr, flags);
-
- res = send_check_result(env, descP, sendmsg_result, dataSize,
- (! enif_is_empty_list(env, tail)),
- sockRef, sendRef);
-
- done_free_iovec:
- enif_free_iovec(iovec);
- if (ctrlBuf != NULL) FREE(ctrlBuf);
-
- SSDBG( descP,
- ("SOCKET", "esock_sendmsg {%d} ->"
- "\r\n %T\r\n", descP->sock, res) );
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
#ifdef FOOBAR
@@ -7957,7 +5681,7 @@ nif_sendfile(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#if defined(__WIN32__) || !defined(HAVE_SENDFILE)
+#if !defined(HAVE_SENDFILE)
return enif_raise_exception(env, MKA(env, "notsup"));
#else
ESockDescriptor *descP;
@@ -7985,7 +5709,7 @@ nif_sendfile(ErlNifEnv* env,
sockRef,
descP->sock, descP->sendfileHandle, descP->writeState) );
- res = esock_sendfile_deferred_close(env, descP);
+ res = ESOCK_IO_SENDFILE_DC(env, descP);
} else {
ERL_NIF_TERM sendRef;
@@ -8034,9 +5758,9 @@ nif_sendfile(ErlNifEnv* env,
sockRef, descP->sock, descP->readState,
sendRef, (long) offset, (long) count) );
- res =
- esock_sendfile_cont(env, descP, sockRef, sendRef,
- offset, count);
+ res = ESOCK_IO_SENDFILE_CONT(env, descP,
+ sockRef, sendRef,
+ offset, count);
} else {
ERL_NIF_TERM fRef;
@@ -8052,15 +5776,15 @@ nif_sendfile(ErlNifEnv* env,
("SOCKET", "nif_sendfile(%T), {%d,0x%X} ->"
"\r\n sendRef: %T"
"\r\n offset: %ld"
- "\r\n count: %ld"
+ "\r\n count: %ld"
"\r\n fRef: %T"
"\r\n",
sockRef, descP->sock, descP->readState,
sendRef, (long) offset, (long) count, fRef) );
- res =
- esock_sendfile_start(env, descP, sockRef, sendRef,
- offset, count, fRef);
+ res = ESOCK_IO_SENDFILE_START(env, descP,
+ sockRef, sendRef,
+ offset, count, fRef);
}
}
@@ -8072,565 +5796,9 @@ nif_sendfile(ErlNifEnv* env,
return res;
-#endif // #if defined(__WIN32__) || !defined(HAVE_SENDFILE)
-}
-
-#ifndef __WIN32__
-#ifdef HAVE_SENDFILE
-
-/* Start a sendfile() operation
- */
-static ERL_NIF_TERM
-esock_sendfile_start(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- off_t offset,
- size_t count,
- ERL_NIF_TERM fRef) {
- ERL_NIF_TERM writerCheck;
- ssize_t res;
- int err;
-
- SSDBG( descP, ("SOCKET",
- "esock_sendfile_start(%T) {%d} -> sendRef: %T\r\n"
- " fRef: %T\r\n"
- " offset: %lu\r\n"
- " count: %lu\r\n",
- sockRef, descP->sock, sendRef,
- fRef, (unsigned long) offset, (unsigned long) count) );
-
- if (! IS_OPEN(descP->writeState)) {
- return esock_make_error(env, atom_closed);
- }
-
- /* Connect and Write uses the same select flag
- * so they can not be simultaneous
- */
- if (descP->connectorP != NULL) {
- return esock_make_error_invalid(env, atom_state);
- }
-
- /* Ensure that we either have no current writer or we are it,
- * or enqueue this process if there is a current writer
- */
- if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
- SSDBG( descP, ("SOCKET",
- "esock_sendfile_start {%d} -> writer check failed: "
- "\r\n %T\r\n", descP->sock, writerCheck) );
-
- /* Returns 'select' if current process got enqueued,
- * or exception invalid state if current process already
- * was enqueued
- */
- return writerCheck;
- }
-
- if (descP->sendfileHandle != INVALID_HANDLE)
- return esock_make_error_invalid(env, atom_state);
-
- /* Get a dup:ed file handle from prim_file_nif
- * through a NIF dyncall
- */
- {
- struct prim_file_nif_dyncall_dup dc_dup;
-
- dc_dup.op = prim_file_nif_dyncall_dup;
- dc_dup.result = EINVAL; // should not be needed
-
- /* Request the handle */
- if (enif_dynamic_resource_call(env,
- atom_prim_file, atom_efile, fRef,
- &dc_dup)
- != 0) {
- return
- esock_sendfile_error(env, descP, sockRef,
- MKT2(env, esock_atom_invalid,
- atom_efile));
- }
- if (dc_dup.result != 0) {
- return
- esock_sendfile_errno(env, descP, sockRef, dc_dup.result);
- }
- descP->sendfileHandle = dc_dup.handle;
- }
-
- SSDBG( descP, ("SOCKET",
- "esock_sendfile_start(%T) {%d} -> sendRef: %T\r\n"
- " sendfileHandle: %d\r\n",
- sockRef, descP->sock, sendRef,
- descP->sendfileHandle) );
-
- if (descP->sendfileCountersP == NULL) {
- descP->sendfileCountersP = MALLOC(sizeof(ESockSendfileCounters));
- *descP->sendfileCountersP = initESockSendfileCounters;
- }
-
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_sendfile_tries, &descP->sendfileCountersP->tries, 1);
- descP->sendfileCountersP->maxCnt = 0;
-
- res = esock_sendfile(env, descP, sockRef, offset, &count, &err);
-
- if (res < 0) { // Terminal error
-
- (void) close(descP->sendfileHandle);
- descP->sendfileHandle = INVALID_HANDLE;
-
- return esock_sendfile_errno(env, descP, sockRef, err);
-
- } else if (res > 0) { // Retry by select
-
- if (descP->currentWriterP == NULL) {
- int mon_res;
-
- /* Register writer as current */
- ESOCK_ASSERT( enif_self(env, &descP->currentWriter.pid) != NULL );
- mon_res =
- MONP("sendfile -> current writer",
- env, descP,
- &descP->currentWriter.pid,
- &descP->currentWriter.mon);
- ESOCK_ASSERT( mon_res >= 0 );
-
- if (mon_res > 0) {
- /* Caller died already, can happen for dirty NIFs */
-
- (void) close(descP->sendfileHandle);
- descP->sendfileHandle = INVALID_HANDLE;
-
- return
- esock_sendfile_error(env, descP, sockRef,
- MKT2(env,
- esock_atom_invalid,
- esock_atom_not_owner));
- }
- ESOCK_ASSERT( descP->currentWriter.env == NULL );
- descP->currentWriter.env = esock_alloc_env("current-writer");
- descP->currentWriter.ref =
- CP_TERM(descP->currentWriter.env, sendRef);
- descP->currentWriterP = &descP->currentWriter;
- }
- // else current writer is already registered by requestor_pop()
-
- return esock_sendfile_select(env, descP, sockRef, sendRef, count);
-
- } else { // res == 0: Done
- return esock_sendfile_ok(env, descP, sockRef, count);
- }
-}
-
-/* Continue an ongoing sendfile operation
- */
-static ERL_NIF_TERM
-esock_sendfile_cont(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- off_t offset,
- size_t count) {
- ErlNifPid caller;
- ssize_t res;
- int err;
-
- SSDBG( descP, ("SOCKET",
- "esock_sendfile_cont(%T) {%d} -> sendRef: %T\r\n",
- sockRef, descP->sock, sendRef) );
-
- if (! IS_OPEN(descP->writeState))
- return esock_make_error(env, atom_closed);
-
- /* Connect and Write uses the same select flag
- * so they can not be simultaneous
- */
- if (descP->connectorP != NULL)
- return esock_make_error_invalid(env, atom_state);
-
- /* Verify that this process has a sendfile operation in progress */
- ESOCK_ASSERT( enif_self(env, &caller) != NULL );
- if ((descP->currentWriterP == NULL) ||
- (descP->sendfileHandle == INVALID_HANDLE) ||
- (COMPARE_PIDS(&descP->currentWriter.pid, &caller) != 0)) {
- //
- return esock_raise_invalid(env, atom_state);
- }
-
- res = esock_sendfile(env, descP, sockRef, offset, &count, &err);
-
- if (res < 0) { // Terminal error
-
- (void) close(descP->sendfileHandle);
- descP->sendfileHandle = INVALID_HANDLE;
-
- return esock_sendfile_errno(env, descP, sockRef, err);
-
- } else if (res > 0) { // Retry by select
-
- /* Overwrite current writer registration */
- enif_clear_env(descP->currentWriter.env);
- descP->currentWriter.ref =
- CP_TERM(descP->currentWriter.env, sendRef);
-
- return esock_sendfile_select(env, descP, sockRef, sendRef, count);
-
- } else { // res == 0: Done
- return esock_sendfile_ok(env, descP, sockRef, count);
- }
+#endif // !defined(HAVE_SENDFILE)
}
-/* Deferred close of the dup:ed file descriptor
- */
-static ERL_NIF_TERM
-esock_sendfile_deferred_close(ErlNifEnv *env,
- ESockDescriptor *descP) {
- if (descP->sendfileHandle == INVALID_HANDLE)
- return esock_make_error_invalid(env, atom_state);
-
- (void) close(descP->sendfileHandle);
- descP->sendfileHandle = INVALID_HANDLE;
-
- return esock_atom_ok;
-}
-
-/* Platform independent sendfile() function
- *
- * Return < 0 for terminal error
- * 0 for done
- * > 0 for retry with select
- */
-static int
-esock_sendfile(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- off_t offset,
- size_t *countP,
- int *errP) {
-
- size_t pkgSize = 0; // Total sent in this call
-
- SSDBG( descP, ("SOCKET",
- "esock_sendfile(%T) {%d,%d}\r\n",
- sockRef, descP->sock, descP->sendfileHandle) );
-
- for (;;) {
- size_t chunk_size = (size_t) 0x20000000UL; // 0.5 GB
- size_t bytes_sent;
- ssize_t res;
- int error;
-
- /* *countP == 0 means send the whole file - use chunk size */
- if ((*countP > 0) && (*countP < chunk_size))
- chunk_size = *countP;
-
- {
- /* Platform dependent code:
- * update and check offset, set and check bytes_sent, and
- * set res to >= 0 and error to 0, or
- * set res to < 0 and error to sock_errno()
- */
-#if defined (__linux__)
-
- off_t prev_offset;
-
- prev_offset = offset;
- res =
- sendfile(descP->sock, descP->sendfileHandle,
- &offset, chunk_size);
- error = (res < 0) ? sock_errno() : 0;
-
- ESOCK_ASSERT( offset >= prev_offset );
- ESOCK_ASSERT( (off_t) chunk_size >= (offset - prev_offset) );
- bytes_sent = (size_t) (offset - prev_offset);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_sendfile(%T) {%d,%d}"
- "\r\n res: %d"
- "\r\n bytes_sent: %lu"
- "\r\n error: %d"
- "\r\n",
- sockRef, descP->sock, descP->sendfileHandle,
- res, (unsigned long) bytes_sent, error) );
-
-#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__DARWIN__)
-
- off_t sbytes;
-
-#if defined(__DARWIN__)
- sbytes = (off_t) chunk_size;
- res = (ssize_t)
- sendfile(descP->sendfileHandle, descP->sock, offset,
- &sbytes, NULL, 0);
-#else
- sbytes = 0;
- res = (ssize_t)
- sendfile(descP->sendfileHandle, descP->sock, offset,
- chunk_size, NULL, &sbytes, 0);
-#endif
- error = (res < 0) ? sock_errno() : 0;
-
- /* For an error return, we do not dare trust that sbytes is set
- * unless the error is ERRNO_BLOCK or EINTR
- * - the man page is to vague
- */
- if ((res < 0) && (error != ERRNO_BLOCK) && (error != EINTR)) {
- sbytes = 0;
- } else {
- ESOCK_ASSERT( sbytes >= 0 );
- ESOCK_ASSERT( (off_t) chunk_size >= sbytes );
- ESOCK_ASSERT( offset + sbytes >= offset );
- offset += sbytes;
- }
- bytes_sent = (size_t) sbytes;
-
- SSDBG( descP,
- ("SOCKET",
- "esock_sendfile(%T) {%d,%d}"
- "\r\n res: %d"
- "\r\n bytes_sent: %lu"
- "\r\n error: %d"
- "\r\n",
- sockRef, descP->sock, descP->sendfileHandle,
- res, (unsigned long) bytes_sent, error) );
-
-#elif defined(__sun) && defined(__SVR4) && defined(HAVE_SENDFILEV)
-
- sendfilevec_t sfvec[1];
-
- sfvec[0].sfv_fd = descP->sendfileHandle;
- sfvec[0].sfv_flag = 0;
- sfvec[0].sfv_off = offset;
- sfvec[0].sfv_len = chunk_size;
-
- res = sendfilev(descP->sock, sfvec, NUM(sfvec), &bytes_sent);
- error = (res < 0) ? sock_errno() : 0;
-
- SSDBG( descP,
- ("SOCKET",
- "esock_sendfile(%T) {%d,%d}"
- "\r\n res: %d"
- "\r\n bytes_sent: %lu"
- "\r\n error: %d"
- "\r\n",
- sockRef, descP->sock, descP->sendfileHandle,
- res, (unsigned long) bytes_sent, error) );
-
- if ((res < 0) && (error == EINVAL)) {
- /* On e.b SunOS 5.10 using sfv_len > file size
- * lands here - we regard this as a successful send.
- * All other causes for EINVAL are avoided,
- * except for .sfv_fd not seekable, which would
- * give bytes_sent == 0 that we would interpret
- * as end of file, which is kind of true.
- */
- res = 0;
- }
- ESOCK_ASSERT( chunk_size >= bytes_sent );
- ESOCK_ASSERT( offset + bytes_sent >= offset );
- offset += bytes_sent;
-
-#else
-#error "Unsupported sendfile syscall; update configure test."
-#endif
-
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_sendfile, &descP->sendfileCountersP->cnt, 1);
-
- if (bytes_sent != 0) {
-
- pkgSize += bytes_sent;
-
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_sendfile_pkg,
- &descP->sendfileCountersP->pkg,
- 1);
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_sendfile_byte,
- &descP->sendfileCountersP->byteCnt,
- bytes_sent);
-
- if (pkgSize > descP->sendfileCountersP->pkgMax)
- descP->sendfileCountersP->pkgMax = pkgSize;
- if ((descP->sendfileCountersP->maxCnt += bytes_sent)
- > descP->sendfileCountersP->max)
- descP->sendfileCountersP->max =
- descP->sendfileCountersP->maxCnt;
- }
-
- /* *countP == 0 means send whole file */
- if (*countP > 0) {
-
- *countP -= bytes_sent;
-
- if (*countP == 0) { // All sent
- *countP = pkgSize;
- return 0;
- }
- }
-
- if (res < 0) {
- if (error == ERRNO_BLOCK) {
- *countP = pkgSize;
- return 1;
- }
- if (error == EINTR)
- continue;
- *errP = error;
- return -1;
- }
-
- if (bytes_sent == 0) { // End of input file
- *countP = pkgSize;
- return 0;
- }
- }
- } // for (;;)
-}
-
-static ERL_NIF_TERM
-esock_sendfile_errno(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- int err) {
- ERL_NIF_TERM reason;
-
- reason = MKA(env, erl_errno_id(err));
- return esock_sendfile_error(env, descP, sockRef, reason);
-}
-
-static ERL_NIF_TERM
-esock_sendfile_error(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM reason) {
-
- if (descP->sendfileCountersP == NULL) {
- descP->sendfileCountersP = MALLOC(sizeof(ESockSendfileCounters));
- *descP->sendfileCountersP = initESockSendfileCounters;
- }
-
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_sendfile_fails,
- &descP->sendfileCountersP->fails, 1);
-
- SSDBG( descP, ("SOCKET",
- "esock_sendfile_error(%T) {%d} -> error: %T\r\n",
- sockRef, descP->sock, reason) );
-
- /* XXX Should we have special treatment for EINVAL,
- * such as to only fail current operation and activate
- * the next from the queue?
- */
-
- if (descP->currentWriterP != NULL) {
-
- (void) DEMONP("esock_sendfile_error",
- env, descP, &descP->currentWriter.mon);
-
- /* Fail all queued writers */
- requestor_release("esock_sendfile_error",
- env, descP, &descP->currentWriter);
- send_error_waiting_writers(env, descP, sockRef, reason);
- descP->currentWriterP = NULL;
-
- }
-
- return esock_make_error(env, reason);
-}
-
-static ERL_NIF_TERM
-esock_sendfile_select(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef,
- size_t count) {
- int sres;
-
- /* Select write for this process */
- sres =
- esock_select_write(env, descP->sock, descP, NULL, sockRef, sendRef);
- if (sres < 0) {
- ERL_NIF_TERM reason;
-
- /* Internal select error */
- (void) DEMONP("esock_sendfile_select - failed",
- env, descP, &descP->currentWriter.mon);
-
- /* Fail all queued writers */
- reason = MKT2(env, atom_select_write, MKI(env, sres));
- requestor_release("esock_sendfile_select_fail",
- env, descP, &descP->currentWriter);
- send_error_waiting_writers(env, descP, sockRef, reason);
- descP->currentWriterP = NULL;
-
- (void) close(descP->sendfileHandle);
- descP->sendfileHandle = INVALID_HANDLE;
-
- return enif_raise_exception(env, reason);
-
- } else {
- ErlNifUInt64 bytes_sent;
-
- SSDBG( descP,
- ("SOCKET", "esock_sendfile_select(%T) {%d} -> "
- "sendRef (%T)\r\n"
- "count: %lu\r\n",
- sockRef, descP->sock, sendRef, (unsigned long) count) );
-
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_sendfile_waits,
- &descP->sendfileCountersP->waits,
- 1);
- descP->writeState |= ESOCK_STATE_SELECTED;
- bytes_sent = (ErlNifUInt64) count;
-
- return MKT2(env, atom_select, MKUI64(env, bytes_sent));
- }
-}
-
-static ERL_NIF_TERM
-esock_sendfile_ok(ErlNifEnv *env,
- ESockDescriptor *descP,
- ERL_NIF_TERM sockRef,
- size_t count) {
- ErlNifUInt64 bytes_sent64u;
-
- SSDBG( descP,
- ("SOCKET", "esock_sendfile_ok(%T) {%d} -> "
- "everything written (%lu) - done\r\n",
- sockRef, descP->sock, (unsigned long) count) );
-
- if (descP->currentWriterP != NULL) {
-
- (void) DEMONP("esock_sendfile_ok -> current writer",
- env, descP, &descP->currentWriter.mon);
-
- /*
- * Ok, this write is done maybe activate the next (if any)
- */
- if (! activate_next_writer(env, descP, sockRef)) {
-
- SSDBG( descP,
- ("SOCKET",
- "esock_sendfile_ok(%T) {%d} -> no more writers\r\n",
- sockRef, descP->sock) );
-
- descP->currentWriterP = NULL;
- }
- }
-
- descP->writePkgMaxCnt = 0;
- bytes_sent64u = (ErlNifUInt64) count;
-
- (void) close(descP->sendfileHandle);
- descP->sendfileHandle = INVALID_HANDLE;
-
- return esock_make_ok2(env, MKUI64(env, bytes_sent64u));
-}
-
-#endif // #ifdef HAVE_SENDFILE
-#endif // #ifndef __WIN32__
-
/* ----------------------------------------------------------------------
@@ -8655,9 +5823,6 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM sockRef, recvRef;
ErlNifUInt64 elen;
@@ -8676,7 +5841,7 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env,
}
if ((! enif_is_ref(env, recvRef)) &&
- (COMPARE(recvRef, atom_zero) != 0)) {
+ (COMPARE(recvRef, esock_atom_zero) != 0)) {
return enif_make_badarg(env);
}
if ((! (a1ok = GET_UINT64(env, argv[1], &elen))) ||
@@ -8713,7 +5878,7 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env,
* is done!
*/
- res = esock_recv(env, descP, sockRef, recvRef, len, flags);
+ res = ESOCK_IO_RECV(env, descP, sockRef, recvRef, len, flags);
SSDBG( descP, ("SOCKET", "nif_recv(%T) -> done"
"\r\n", sockRef) );
@@ -8722,83 +5887,9 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env,
return res;
-#endif // #ifdef __WIN32__ #else
}
-/* The (read) buffer handling should be optimized!
- * But for now we make it easy for ourselves by
- * allocating a binary (of the specified or default
- * size) and then throwing it away...
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_recv(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef,
- ssize_t len,
- int flags)
-{
- ssize_t read;
- ErlNifBinary buf;
- ERL_NIF_TERM readerCheck;
- int save_errno;
- size_t bufSz = (len != 0 ? len : descP->rBufSz);
-
- SSDBG( descP, ("SOCKET", "esock_recv {%d} -> entry with"
- "\r\n count,size: (%ld:%u:%lu)"
- "\r\n", descP->sock,
- (long) len, descP->rNumCnt, (unsigned long) bufSz) );
-
- if (! IS_OPEN(descP->readState))
- return esock_make_error(env, atom_closed);
-
- /* Accept and Read uses the same select flag
- * so they can not be simultaneous
- */
- if (descP->currentAcceptorP != NULL)
- return esock_make_error_invalid(env, atom_state);
-
- /* Ensure that we either have no current reader or that we are it,
- * or enqueue this process if there is a current reader */
- if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
- SSDBG( descP,
- ("SOCKET", "esock_recv {%d} -> reader check failed: "
- "\r\n %T\r\n", descP->sock, readerCheck) );
- return readerCheck;
- }
-
- /* Allocate a buffer:
- * Either as much as we want to read or (if zero (0)) use the "default"
- * size (what has been configured).
- */
- ESOCK_ASSERT( ALLOC_BIN(bufSz, &buf) );
-
- // If it fails (read = -1), we need errno...
- SSDBG( descP, ("SOCKET", "esock_recv {%d} -> try read (%lu)\r\n",
- descP->sock, (unsigned long) buf.size) );
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_tries, &descP->readTries, 1);
-
- read = sock_recv(descP->sock, buf.data, buf.size, flags);
- if (ESOCK_IS_ERROR(read)) {
- save_errno = sock_errno();
- } else {
- save_errno = 0; // The value does not actually matter in this case
- }
-
- SSDBG( descP, ("SOCKET",
- "esock_recv {%d} -> read: %ld (%d)\r\n",
- descP->sock, (long) read, save_errno) );
-
- return recv_check_result(env, descP, read, len, save_errno,
- &buf, sockRef, recvRef);
-}
-#endif // #ifndef __WIN32__
-
-
-
/* ----------------------------------------------------------------------
* nif_recvfrom
*
@@ -8829,9 +5920,6 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM sockRef, recvRef;
ErlNifUInt64 elen;
@@ -8854,7 +5942,7 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
/* Extract arguments and perform preliminary validation */
if ((! enif_is_ref(env, recvRef)) &&
- (COMPARE(recvRef, atom_zero) != 0)) {
+ (COMPARE(recvRef, esock_atom_zero) != 0)) {
return enif_make_badarg(env);
}
@@ -8866,8 +5954,7 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
if (! a1ok)
return esock_make_error_integer_range(env, argv[1]);
- return
- esock_make_error_integer_range(env, argv[2]);
+ return esock_make_error_integer_range(env, argv[2]);
}
len = (ssize_t) elen;
if (elen != (ErlNifUInt64) len)
@@ -8899,7 +5986,7 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
* </KOLLA>
*/
- res = esock_recvfrom(env, descP, sockRef, recvRef, len, flags);
+ res = ESOCK_IO_RECVFROM(env, descP, sockRef, recvRef, len, flags);
SSDBG( descP, ("SOCKET", "nif_recvfrom(%T) -> done"
"\r\n", sockRef) );
@@ -8907,77 +5994,7 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
MUNLOCK(descP->readMtx);
return res;
-#endif // #ifdef __WIN32__ #else
-}
-
-
-/* The (read) buffer handling *must* be optimized!
- * But for now we make it easy for ourselves by
- * allocating a binary (of the specified or default
- * size) and then throwing it away...
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_recvfrom(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef,
- ssize_t len,
- int flags)
-{
- ESockAddress fromAddr;
- SOCKLEN_T addrLen;
- ssize_t read;
- int save_errno;
- ErlNifBinary buf;
- ERL_NIF_TERM readerCheck;
- size_t bufSz = (len != 0 ? len : descP->rBufSz);
-
- SSDBG( descP, ("SOCKET", "esock_recvfrom {%d} -> entry with"
- "\r\n bufSz: %d"
- "\r\n", descP->sock, bufSz) );
-
- if (! IS_OPEN(descP->readState))
- return esock_make_error(env, atom_closed);
-
- /* Accept and Read uses the same select flag
- * so they can not be simultaneous
- */
- if (descP->currentAcceptorP != NULL)
- return esock_make_error_invalid(env, atom_state);
-
- /* Ensure that we either have no current reader or that we are it,
- * or enqueue this process if there is a current reader */
- if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
- SSDBG( descP,
- ("SOCKET", "esock_recv {%d} -> reader check failed: "
- "\r\n %T\r\n", descP->sock, readerCheck) );
- return readerCheck;
- }
-
- /* Allocate a buffer:
- * Either as much as we want to read or (if zero (0)) use the "default"
- * size (what has been configured).
- */
- ESOCK_ASSERT( ALLOC_BIN(bufSz, &buf) );
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_tries, &descP->readTries, 1);
-
- addrLen = sizeof(fromAddr);
- sys_memzero((char*) &fromAddr, addrLen);
-
- read = sock_recvfrom(descP->sock, buf.data, buf.size, flags,
- &fromAddr.sa, &addrLen);
- if (ESOCK_IS_ERROR(read))
- save_errno = sock_errno();
- else
- save_errno = 0; // The value does not actually matter in this case
-
- return recvfrom_check_result(env, descP, read, save_errno,
- &buf, &fromAddr, addrLen,
- sockRef, recvRef);
}
-#endif // #ifndef __WIN32__
@@ -9015,9 +6032,6 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM sockRef, recvRef;
ErlNifUInt64 eBufSz, eCtrlSz;
@@ -9040,7 +6054,7 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env,
/* Extract arguments and perform preliminary validation */
if ((! enif_is_ref(env, recvRef)) &&
- (COMPARE(recvRef, atom_zero) != 0)) {
+ (COMPARE(recvRef, esock_atom_zero) != 0)) {
return enif_make_badarg(env);
}
@@ -9097,7 +6111,7 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env,
* </KOLLA>
*/
- res = esock_recvmsg(env, descP, sockRef, recvRef, bufSz, ctrlSz, flags);
+ res = ESOCK_IO_RECVMSG(env, descP, sockRef, recvRef, bufSz, ctrlSz, flags);
SSDBG( descP, ("SOCKET", "nif_recvmsg(%T) -> done"
"\r\n", sockRef) );
@@ -9105,108 +6119,7 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env,
MUNLOCK(descP->readMtx);
return res;
-#endif // #ifdef __WIN32__ #else
-}
-
-
-/* The (read) buffer handling *must* be optimized!
- * But for now we make it easy for ourselves by
- * allocating a binary (of the specified or default
- * size) and then throwing it away...
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_recvmsg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef,
- ssize_t bufLen,
- ssize_t ctrlLen,
- int flags)
-{
- SOCKLEN_T addrLen;
- ssize_t read;
- int save_errno;
- size_t bufSz = (bufLen != 0 ? bufLen : descP->rBufSz);
- size_t ctrlSz = (ctrlLen != 0 ? ctrlLen : descP->rCtrlSz);
- struct msghdr msgHdr;
- SysIOVec iov[1]; // Shall we always use 1?
- ErlNifBinary data[1]; // Shall we always use 1?
- ErlNifBinary ctrl;
- ERL_NIF_TERM readerCheck;
- ESockAddress addr;
-
- SSDBG( descP, ("SOCKET", "esock_recvmsg {%d} -> entry with"
- "\r\n bufSz: %lu (%ld)"
- "\r\n ctrlSz: %ld (%ld)"
- "\r\n", descP->sock,
- (unsigned long) bufSz, (long) bufLen,
- (unsigned long) ctrlSz, (long) ctrlLen) );
-
- if (! IS_OPEN(descP->readState))
- return esock_make_error(env, atom_closed);
-
- /* Accept and Read uses the same select flag
- * so they can not be simultaneous
- */
- if (descP->currentAcceptorP != NULL)
- return esock_make_error_invalid(env, atom_state);
-
- /* Ensure that we either have no current reader or that we are it,
- * or enqueue this process if there is a current reader */
- if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
- SSDBG( descP,
- ("SOCKET", "esock_recv {%d} -> reader check failed: "
- "\r\n %T\r\n", descP->sock, readerCheck) );
- return readerCheck;
- }
-
- /*
- for (i = 0; i < sizeof(buf); i++) {
- ESOCK_ASSERT( ALLOC_BIN(bifSz, &buf[i]) );
- iov[i].iov_base = buf[i].data;
- iov[i].iov_len = buf[i].size;
- }
- */
-
- /* Allocate the (msg) data buffer:
- */
- ESOCK_ASSERT( ALLOC_BIN(bufSz, &data[0]) );
-
- /* Allocate the ctrl (buffer):
- */
- ESOCK_ASSERT( ALLOC_BIN(ctrlSz, &ctrl) );
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_tries, &descP->readTries, 1);
-
- addrLen = sizeof(addr);
- sys_memzero((char*) &addr, addrLen);
- sys_memzero((char*) &msgHdr, sizeof(msgHdr));
-
- iov[0].iov_base = data[0].data;
- iov[0].iov_len = data[0].size;
-
- msgHdr.msg_name = &addr;
- msgHdr.msg_namelen = addrLen;
- msgHdr.msg_iov = iov;
- msgHdr.msg_iovlen = 1; // Should use a constant or calculate...
- msgHdr.msg_control = ctrl.data;
- msgHdr.msg_controllen = ctrl.size;
-
- read = sock_recvmsg(descP->sock, &msgHdr, flags);
- if (ESOCK_IS_ERROR(read))
- save_errno = sock_errno();
- else
- save_errno = 0; // The value does not actually matter in this case
-
- return recvmsg_check_result(env, descP, read, save_errno,
- &msgHdr,
- data, // Needed for iov encode
- &ctrl, // Needed for ctrl header encode
- sockRef, recvRef);
}
-#endif // #ifndef __WIN32__
-
/* ----------------------------------------------------------------------
@@ -9224,9 +6137,6 @@ ERL_NIF_TERM nif_close(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM res;
@@ -9253,7 +6163,7 @@ ERL_NIF_TERM nif_close(ErlNifEnv* env,
descP->readState, descP->writeState,
esock_self(env)) );
- res = esock_close(env, descP);
+ res = ESOCK_IO_CLOSE(env, descP);
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
@@ -9263,230 +6173,7 @@ ERL_NIF_TERM nif_close(ErlNifEnv* env,
"\r\n", argv[0], res) );
return res;
-#endif // #ifdef __WIN32__ #else
-}
-
-
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_close(ErlNifEnv* env,
- ESockDescriptor* descP)
-{
- if (! IS_OPEN(descP->readState)) {
- /* A bit of cheeting; maybe not closed yet - do we need a queue? */
- return esock_make_error(env, atom_closed);
- }
-
- /* Store the PID of the caller,
- * since we need to inform it when we
- * (that is, the stop callback function)
- * completes.
- */
- ESOCK_ASSERT( enif_self(env, &descP->closerPid) != NULL );
-
- /* If the caller is not the owner; monitor the caller,
- * since we should complete this operation even if the caller dies
- * (for whatever reason).
- */
- if (COMPARE_PIDS(&descP->closerPid, &descP->ctrlPid) != 0) {
-
- ESOCK_ASSERT( MONP("esock_close_check -> closer",
- env, descP,
- &descP->closerPid,
- &descP->closerMon) == 0 );
- }
-
- /* Prepare for closing the socket */
- descP->readState |= ESOCK_STATE_CLOSING;
- descP->writeState |= ESOCK_STATE_CLOSING;
- if (esock_do_stop(env, descP)) {
- // esock_stop() has been scheduled - wait for it
- SSDBG( descP,
- ("SOCKET", "esock_close {%d} -> stop was scheduled\r\n",
- descP->sock) );
-
- // Create closeRef for the close msg that esock_stop() will send
- descP->closeEnv = esock_alloc_env("esock_close_do - close-env");
- descP->closeRef = MKREF(descP->closeEnv);
-
- return esock_make_ok2(env, CP_TERM(env, descP->closeRef));
- } else {
- // The socket may be closed - tell caller to finalize
- SSDBG( descP,
- ("SOCKET",
- "esock_close {%d} -> stop was called\r\n",
- descP->sock) );
-
- return esock_atom_ok;
- }
}
-#endif // #ifndef __WIN32__
-
-
-#ifndef __WIN32__
-/* Prepare for close - return whether stop is scheduled
- */
-static
-BOOLEAN_T esock_do_stop(ErlNifEnv* env,
- ESockDescriptor* descP) {
- BOOLEAN_T ret;
- int sres;
- ERL_NIF_TERM sockRef;
-
- sockRef = enif_make_resource(env, descP);
-
- if (IS_SELECTED(descP)) {
- ESOCK_ASSERT( (sres = esock_select_stop(env, descP->sock, descP))
- >= 0 );
- if ((sres & ERL_NIF_SELECT_STOP_CALLED) != 0) {
- /* The socket is no longer known by the select machinery
- * - it may be closed
- */
- ret = FALSE;
- } else {
- ESOCK_ASSERT( (sres & ERL_NIF_SELECT_STOP_SCHEDULED) != 0 );
- /* esock_stop() is scheduled
- * - socket may be removed by esock_stop() or later
- */
- ret = TRUE;
- }
- } else {
- sres = 0;
- /* The socket has never been used in the select machinery
- * - it may be closed
- */
- ret = FALSE;
- }
-
- /* +++++++ Current and waiting Writers +++++++ */
-
- if (descP->currentWriterP != NULL) {
-
- /* We have a current Writer; was it deselected?
- */
-
- if (sres & ERL_NIF_SELECT_WRITE_CANCELLED) {
-
- /* The current Writer will not get a select message
- * - send it an abort message
- */
-
- esock_stop_handle_current(env,
- "writer",
- descP, sockRef, &descP->currentWriter);
- }
-
- /* Inform the waiting Writers (in the same way) */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_do_stop {%d} -> handle waiting writer(s)\r\n",
- descP->sock) );
-
- inform_waiting_procs(env, "writer",
- descP, sockRef, &descP->writersQ, atom_closed);
-
- descP->currentWriterP = NULL;
- }
-
- /* +++++++ Connector +++++++
- * Note that there should not be Writers and a Connector
- * at the same time so the check for if the
- * current Writer/Connecter was deselected is only correct
- * under that assumption
- */
-
- if (descP->connectorP != NULL) {
-
- /* We have a Connector; was it deselected?
- */
-
- if (sres & ERL_NIF_SELECT_WRITE_CANCELLED) {
-
- /* The Connector will not get a select message
- * - send it an abort message
- */
-
- esock_stop_handle_current(env,
- "connector",
- descP, sockRef, &descP->connector);
- }
-
- descP->connectorP = NULL;
- }
-
- /* +++++++ Current and waiting Readers +++++++ */
-
- if (descP->currentReaderP != NULL) {
-
- /* We have a current Reader; was it deselected?
- */
-
- if (sres & ERL_NIF_SELECT_READ_CANCELLED) {
-
- /* The current Reader will not get a select message
- * - send it an abort message
- */
-
- esock_stop_handle_current(env,
- "reader",
- descP, sockRef, &descP->currentReader);
- }
-
- /* Inform the Readers (in the same way) */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_do_stop {%d} -> handle waiting reader(s)\r\n",
- descP->sock) );
-
- inform_waiting_procs(env, "writer",
- descP, sockRef, &descP->readersQ, atom_closed);
-
- descP->currentReaderP = NULL;
- }
-
- /* +++++++ Current and waiting Acceptors +++++++
- *
- * Note that there should not be Readers and Acceptors
- * at the same time so the check for if the
- * current Reader/Acceptor was deselected is only correct
- * under that assumption
- */
-
- if (descP->currentAcceptorP != NULL) {
-
- /* We have a current Acceptor; was it deselected?
- */
-
- if (sres & ERL_NIF_SELECT_READ_CANCELLED) {
-
- /* The current Acceptor will not get a select message
- * - send it an abort message
- */
-
- esock_stop_handle_current(env,
- "acceptor",
- descP, sockRef, &descP->currentAcceptor);
- }
-
- /* Inform the waiting Acceptor (in the same way) */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_do_stop {%d} -> handle waiting acceptors(s)\r\n",
- descP->sock) );
-
- inform_waiting_procs(env, "acceptor",
- descP, sockRef, &descP->acceptorsQ, atom_closed);
-
- descP->currentAcceptorP = NULL;
- }
-
- return ret;
-}
-#endif // #ifndef __WIN32__
-
/* ----------------------------------------------------------------------
@@ -9506,9 +6193,6 @@ ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env,
{
ESockDescriptor* descP;
ERL_NIF_TERM result;
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
/* Extract arguments and perform preliminary validation */
@@ -9522,10 +6206,13 @@ ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env,
MLOCK(descP->writeMtx);
SSDBG( descP,
- ("SOCKET", "nif_finalize_close(%T), {%d,0x%X}\r\n",
- argv[0], descP->sock, descP->readState) );
+ ("SOCKET", "nif_finalize_close(%T, %d) -> "
+ "\r\n ReadState: 0x%X"
+ "\r\n WriteState: 0x%X"
+ "\r\n",
+ argv[0], descP->sock, descP->readState, descP->writeState) );
- result = esock_finalize_close(env, descP);
+ result = ESOCK_IO_FIN_CLOSE(env, descP);
SSDBG( descP, ("SOCKET", "nif_finalize_close(%T) -> done with"
"\r\n result: %T"
@@ -9535,104 +6222,14 @@ ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env,
MUNLOCK(descP->readMtx);
return result;
-#endif // #ifdef __WIN32__ #else
}
-/* *** esock_finalize_close ***
- * Perform the final step in the socket close.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_finalize_close(ErlNifEnv* env,
- ESockDescriptor* descP)
+extern
+int esock_close_socket(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ BOOLEAN_T unlock)
{
- int err;
- ErlNifPid self;
-#ifdef HAVE_SENDFILE
- HANDLE sendfileHandle;
-#endif
-
- ESOCK_ASSERT( enif_self(env, &self) != NULL );
-
- if (IS_CLOSED(descP->readState))
- return esock_make_error(env, atom_closed);
-
- if (! IS_CLOSING(descP->readState)) {
- // esock_close() has not been called
- return esock_raise_invalid(env, atom_state);
- }
-
- if (IS_SELECTED(descP) && (descP->closeEnv != NULL)) {
- // esock_stop() is scheduled but has not been called
- return esock_raise_invalid(env, atom_state);
- }
-
- if (COMPARE_PIDS(&descP->closerPid, &self) != 0) {
- // This process is not the closer
- return esock_raise_invalid(env, atom_state);
- }
-
- // Close the socket
-
- /* Stop monitoring the closer.
- * Demonitoring may fail since this is a dirty NIF
- * - the caller may have died already.
- */
- enif_set_pid_undefined(&descP->closerPid);
- if (descP->closerMon.isActive) {
- (void) DEMONP("esock_finalize_close -> closer",
- env, descP, &descP->closerMon);
- }
-
- /* Stop monitoring the owner */
- enif_set_pid_undefined(&descP->ctrlPid);
- (void) DEMONP("esock_finalize_close -> ctrl",
- env, descP, &descP->ctrlMon);
- /* Not impossible to still get a esock_down() call from a
- * just triggered owner monitor down
- */
-
-#ifdef HAVE_SENDFILE
- sendfileHandle = descP->sendfileHandle;
- descP->sendfileHandle = INVALID_HANDLE;
-#endif
-
- /* This nif is executed in a dirty scheduler just so that
- * it can "hang" (with minimum effect on the VM) while the
- * kernel writes our buffers. IF we have set the linger option
- * for this ({true, integer() > 0}). For this to work we must
- * be blocking...
- */
- SET_BLOCKING(descP->sock);
- err = esock_close_socket(env, descP, TRUE);
-
-#ifdef HAVE_SENDFILE
- if (sendfileHandle != INVALID_HANDLE) {
- (void) close(descP->sendfileHandle);
- }
-#endif
-
- if (err != 0) {
- if (err == ERRNO_BLOCK) {
- /* Not all data in the buffers where sent,
- * make sure the caller gets this.
- */
- return esock_make_error(env, atom_timeout);
- } else {
- return esock_make_error_errno(env, err);
- }
- }
-
- return esock_atom_ok;
-}
-#endif // #ifndef __WIN32__
-
-
-#ifndef __WIN32__
-static int esock_close_socket(ErlNifEnv* env,
- ESockDescriptor* descP,
- BOOLEAN_T unlock) {
int err = 0;
SOCKET sock = descP->sock;
ERL_NIF_TERM sockRef;
@@ -9650,21 +6247,22 @@ static int esock_close_socket(ErlNifEnv* env,
* finalize_close
*/
descP->sock = INVALID_SOCKET;
- descP->event = INVALID_EVENT;
descP->readState |= ESOCK_STATE_CLOSED;
descP->writeState |= ESOCK_STATE_CLOSED;
- dec_socket(descP->domain, descP->type, descP->protocol);
+ esock_dec_socket(descP->domain, descP->type, descP->protocol);
/* +++++++ Clear the meta option +++++++ */
enif_clear_env(descP->meta.env);
descP->meta.ref = esock_atom_undefined;
- sock_close_event(descP->event);
if (descP->closeOnClose) {
if (unlock) {
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
}
+ SSDBG( descP,
+ ("SOCKET", "esock_close_socket(%d) -> "
+ "try socket close\r\n", sock) );
if (sock_close(sock) != 0)
err = sock_errno();
if (unlock) {
@@ -9675,8 +6273,8 @@ static int esock_close_socket(ErlNifEnv* env,
if (err != 0) {
SSDBG( descP,
- ("SOCKET", "esock_close_socket {%d} -> %d\r\n",
- sock, err) );
+ ("SOCKET", "esock_close_socket(%d) -> %s (%d)\r\n",
+ sock, erl_errno_id(err), err) );
}
/* (maybe) Update the registry */
@@ -9687,7 +6285,7 @@ static int esock_close_socket(ErlNifEnv* env,
return err;
}
-#endif // #ifndef __WIN32__
+
/* ----------------------------------------------------------------------
@@ -9706,9 +6304,6 @@ ERL_NIF_TERM nif_shutdown(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM ehow, res;
int how;
@@ -9734,7 +6329,7 @@ ERL_NIF_TERM nif_shutdown(ErlNifEnv* env,
argv[0], descP->sock, descP->readState | descP->writeState,
how) );
- res = esock_shutdown(env, descP, how);
+ res = ESOCK_IO_SHUTDOWN(env, descP, how);
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
@@ -9744,26 +6339,25 @@ ERL_NIF_TERM nif_shutdown(ErlNifEnv* env,
"\r\n", argv[0], res) );
return res;
-#endif // #ifdef __WIN32__ #else
}
-#ifndef __WIN32__
+/* ========================================================================
+ */
static
ERL_NIF_TERM esock_shutdown(ErlNifEnv* env,
ESockDescriptor* descP,
int how)
{
if (! IS_OPEN(descP->readState))
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
if (sock_shutdown(descP->sock, how) == 0)
return esock_atom_ok;
else
return esock_make_error_errno(env, sock_errno());
}
-#endif // #ifndef __WIN32__
@@ -9790,64 +6384,73 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP = NULL;
+ ERL_NIF_TERM esock, elevel, eopt, eval, enval;
int level, opt, nativeValue;
- ERL_NIF_TERM eVal;
ESOCK_ASSERT( argc == 5 );
- SGDBG( ("SOCKET", "nif_setopt -> entry with argc: %d\r\n", argc) );
+ esock = argv[0];
+ elevel = argv[1];
+ eopt = argv[2];
+ eval = argv[3];
+ enval = argv[4];
+
+ SGDBG( ("SOCKET",
+ "nif_setopt -> entry with argc: %d"
+ "\r\n esock: %T"
+ "\r\n elevel: %T"
+ "\r\n eopt: %T"
+ "\r\n eval: %T"
+ "\r\n enval: %T"
+ "\r\n", argc, esock, elevel, eopt, eval, enval) );
/* Extract arguments and perform preliminary validation */
- if ((! ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) ||
- (! GET_INT(env, argv[4], &nativeValue))) {
- //
+ if ((! ESOCK_GET_RESOURCE(env, esock, (void**) &descP)) ||
+ (! GET_INT(env, enval, &nativeValue))) {
SGDBG( ("SOCKET", "nif_setopt -> failed initial arg check\r\n") );
return enif_make_badarg(env);
}
- if (! GET_INT(env, argv[2], &opt)) {
+
+ if (! GET_INT(env, eopt, &opt)) {
SSDBG( descP,
("SOCKET", "nif_setopt -> failed initial arg check\r\n") );
- if (! IS_INTEGER(env, argv[2]))
+ if (! IS_INTEGER(env, eopt))
return enif_make_badarg(env);
else
- return esock_make_error_integer_range(env, argv[2]);
- }
- eVal = argv[3];
-
- if (esock_decode_level(env, argv[1], &level)) {
- if (nativeValue == 0)
- return esock_setopt(env, descP, level, opt, eVal);
- else
- return esock_setopt_native(env, descP, level, opt, eVal);
+ return esock_make_error_integer_range(env, eopt);
}
- if (COMPARE(argv[1], atom_otp) == 0) {
+ if (COMPARE(elevel, atom_otp) == 0) {
if (nativeValue == 0) {
- return esock_setopt_otp(env, descP, opt, eVal);
+ return ESOCK_IO_SETOPT_OTP(env, descP, opt, eval);
} else {
SSDBG( descP, ("SOCKET", "nif_setopt -> failed arg check\r\n") );
return enif_make_badarg(env);
}
}
+ if (esock_decode_level(env, elevel, &level)) {
+ if (nativeValue == 0)
+ return ESOCK_IO_SETOPT(env, descP, level, opt, eval);
+ else
+ return ESOCK_IO_SETOPT_NATIVE(env, descP, level, opt, eval);
+ }
+
SGDBG( ("SOCKET", "nif_setopt -> failed arg check\r\n") );
- if (IS_INTEGER(env, argv[1]))
- return esock_make_error_integer_range(env, argv[1]);
+ if (IS_INTEGER(env, elevel))
+ return esock_make_error_integer_range(env, elevel);
else
return enif_make_badarg(env);
-#endif // #ifdef __WIN32__ #else
+
}
/* esock_setopt_otp - Handle OTP (level) options
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_otp(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -9931,13 +6534,12 @@ ERL_NIF_TERM esock_setopt_otp(ErlNifEnv* env,
return result;
}
-#endif // #ifndef __WIN32__
/* esock_setopt_otp_debug - Handle the OTP (level) debug options
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_otp_debug(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -9947,11 +6549,11 @@ ERL_NIF_TERM esock_setopt_otp_debug(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_setopt_otp_debug {%d} -> closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
if (! esock_decode_bool(eVal, &descP->dbg))
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
SSDBG( descP,
("SOCKET", "esock_setopt_otp_debug {%d} -> ok"
@@ -9960,12 +6562,12 @@ ERL_NIF_TERM esock_setopt_otp_debug(ErlNifEnv* env,
return esock_atom_ok;
}
-#endif // #ifndef __WIN32__
+
/* esock_setopt_otp_iow - Handle the OTP (level) iow options
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_otp_iow(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -9975,11 +6577,11 @@ ERL_NIF_TERM esock_setopt_otp_iow(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_setopt_otp_iow {%d} -> closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
if (! esock_decode_bool(eVal, &descP->iow))
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
SSDBG( descP,
("SOCKET", "esock_setopt_otp_iow {%d} -> ok"
@@ -9988,13 +6590,13 @@ ERL_NIF_TERM esock_setopt_otp_iow(ErlNifEnv* env,
return esock_atom_ok;
}
-#endif // #ifndef __WIN32__
+
/* esock_setopt_otp_ctrl_proc - Handle the OTP (level)
* controlling_process options
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_otp_ctrl_proc(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -10012,7 +6614,7 @@ ERL_NIF_TERM esock_setopt_otp_ctrl_proc(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_setopt_otp_ctrl_proc {%d} -> closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
/* Ensure that caller is (current) controlling process */
@@ -10027,7 +6629,7 @@ ERL_NIF_TERM esock_setopt_otp_ctrl_proc(ErlNifEnv* env,
/* Ensure that the new controller is a local process */
if (!GET_LPID(env, eVal, &newCtrlPid)) {
esock_warning_msg("Failed get pid of new controlling process\r\n");
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
if ((xres = DEMONP("esock_setopt_otp_ctrl_proc -> (old) ctrl",
@@ -10063,7 +6665,12 @@ ERL_NIF_TERM esock_setopt_otp_ctrl_proc(ErlNifEnv* env,
enif_set_pid_undefined(&descP->ctrlPid);
- esock_down_ctrl(env, descP, &newCtrlPid);
+ /* Shall we use an function pointer argument instead? */
+#ifndef __WIN32__
+ essio_down_ctrl(env, descP, &newCtrlPid);
+#else
+ esaio_down_ctrl(env, descP, &newCtrlPid);
+#endif
descP->readState |= ESOCK_STATE_CLOSING;
descP->writeState |= ESOCK_STATE_CLOSING;
@@ -10076,7 +6683,7 @@ ERL_NIF_TERM esock_setopt_otp_ctrl_proc(ErlNifEnv* env,
return esock_atom_ok;
}
-#endif // #ifndef __WIN32__
+
/* esock_setopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option
@@ -10086,8 +6693,9 @@ ERL_NIF_TERM esock_setopt_otp_ctrl_proc(ErlNifEnv* env,
* {N :: pos_integer(), Sz :: default | pos_integer()}
*
* Where N is the max number of reads.
+ * Note that on Windows the tuple variant is not allowed!
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_otp_rcvbuf(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -10095,7 +6703,9 @@ ERL_NIF_TERM esock_setopt_otp_rcvbuf(ErlNifEnv* env,
{
const ERL_NIF_TERM* t; // The array of the elements of the tuple
int tsz; // The size of the tuple - should be 2
+#ifndef __WIN32__
unsigned int n;
+#endif
size_t bufSz;
ssize_t z;
@@ -10108,9 +6718,25 @@ ERL_NIF_TERM esock_setopt_otp_rcvbuf(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_setopt_otp_rcvbuf {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
+ }
+
+
+#ifdef __WIN32__
+
+ if (!esock_decode_bufsz(env,
+ eVal,
+ ESOCK_RECV_BUFFER_SIZE_DEFAULT,
+ &bufSz)) {
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_setopt_otp_rcvbuf(%d) -> done invalid\r\n",
+ descP->sock) );
+ return esock_make_invalid(env, esock_atom_value);
}
+#else
+
if (esock_decode_bufsz(env,
eVal,
ESOCK_RECV_BUFFER_SIZE_DEFAULT,
@@ -10122,21 +6748,25 @@ ERL_NIF_TERM esock_setopt_otp_rcvbuf(ErlNifEnv* env,
(! GET_UINT(env, t[0], &n)) ||
(n == 0) ||
(! esock_decode_bufsz(env, t[1],
- ESOCK_RECV_BUFFER_SIZE_DEFAULT,
+ ESOCK_RECV_BUFFER_SIZE_DEFAULT,
&bufSz))) {
SSDBG( descP,
("SOCKET",
"esock_setopt_otp_rcvbuf {%d} -> done invalid\r\n",
- descP->sock) );
- return esock_make_invalid(env, atom_value);
+ descP->sock) );
+ return esock_make_invalid(env, esock_atom_value);
}
}
+#endif
+
// We do not want a buffer size that does not fit in ssize_t
z = bufSz;
if (bufSz != (size_t) z)
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
+#ifndef __WIN32__
descP->rNum = n;
+#endif
descP->rBufSz = bufSz;
SSDBG( descP,
@@ -10145,12 +6775,12 @@ ERL_NIF_TERM esock_setopt_otp_rcvbuf(ErlNifEnv* env,
return esock_atom_ok;
}
-#endif // #ifndef __WIN32__
+
/* esock_setopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_otp_rcvctrlbuf(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -10167,7 +6797,7 @@ ERL_NIF_TERM esock_setopt_otp_rcvctrlbuf(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_setopt_otp_rcvctrlbuf {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
if (! esock_decode_bufsz(env,
@@ -10178,7 +6808,7 @@ ERL_NIF_TERM esock_setopt_otp_rcvctrlbuf(ErlNifEnv* env,
("SOCKET",
"esock_setopt_otp_rcvctrlbuf {%d} -> done invalid\r\n",
descP->sock) );
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
descP->rCtrlSz = val;
@@ -10189,12 +6819,12 @@ ERL_NIF_TERM esock_setopt_otp_rcvctrlbuf(ErlNifEnv* env,
return esock_atom_ok;
}
-#endif // #ifndef __WIN32__
+
/* esock_setopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_otp_sndctrlbuf(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -10211,7 +6841,7 @@ ERL_NIF_TERM esock_setopt_otp_sndctrlbuf(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_setopt_otp_sndctrlbuf {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
if (! esock_decode_bufsz(env,
@@ -10222,7 +6852,7 @@ ERL_NIF_TERM esock_setopt_otp_sndctrlbuf(ErlNifEnv* env,
("SOCKET",
"esock_setopt_otp_sndctrlbuf {%d} -> done invalid\r\n",
descP->sock) );
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
descP->wCtrlSz = val;
@@ -10233,12 +6863,12 @@ ERL_NIF_TERM esock_setopt_otp_sndctrlbuf(ErlNifEnv* env,
return esock_atom_ok;
}
-#endif // #ifndef __WIN32__
+
/* esock_setopt_otp_meta - Handle the OTP (level) meta options
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_otp_meta(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -10257,7 +6887,7 @@ ERL_NIF_TERM esock_setopt_otp_meta(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_setopt_otp_meta {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
if (COMPARE_PIDS(&descP->ctrlPid, &caller) != 0) {
@@ -10276,12 +6906,12 @@ ERL_NIF_TERM esock_setopt_otp_meta(ErlNifEnv* env,
return esock_atom_ok;
}
-#endif // #ifndef __WIN32__
+
/* esock_setopt_otp_use_registry - Handle the OTP (level) use_registry option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_otp_use_registry(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -10293,15 +6923,15 @@ ERL_NIF_TERM esock_setopt_otp_use_registry(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_setopt_otp_use_registry {%d} -> closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
if (! esock_decode_bool(eVal, &useReg))
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
/* We only allow turning this on! */
if (! useReg)
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
if (!descP->useReg) {
ERL_NIF_TERM sockRef = enif_make_resource(env, descP);
@@ -10317,13 +6947,13 @@ ERL_NIF_TERM esock_setopt_otp_use_registry(ErlNifEnv* env,
return esock_atom_ok;
}
-#endif // #ifndef __WIN32__
+
/* The option has *not* been encoded. Instead it has been provided
* in "native mode" (value is a binary, an integer or a boolean).
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_native(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -10352,7 +6982,7 @@ ERL_NIF_TERM esock_setopt_native(ErlNifEnv* env,
descP->sock) );
MUNLOCK(descP->writeMtx);
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
if (GET_BIN(env, eVal, &binary)) {
@@ -10366,7 +6996,7 @@ ERL_NIF_TERM esock_setopt_native(ErlNifEnv* env,
result = esock_setopt_level_opt(env, descP, level, opt,
&integer, sizeof(integer));
} else {
- result = esock_make_error_invalid(env, atom_value);
+ result = esock_make_error_invalid(env, esock_atom_value);
}
SSDBG( descP,
@@ -10377,13 +7007,13 @@ ERL_NIF_TERM esock_setopt_native(ErlNifEnv* env,
MUNLOCK(descP->writeMtx);
return result;
}
-#endif // #ifndef __WIN32__
+
/* esock_setopt - A "proper" level (option) has been specified,
* and we have an value of known encoding
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -10409,7 +7039,7 @@ ERL_NIF_TERM esock_setopt(ErlNifEnv* env,
descP->sock) );
MUNLOCK(descP->writeMtx);
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
optP = lookupOpt(level, opt);
@@ -10445,10 +7075,8 @@ ERL_NIF_TERM esock_setopt(ErlNifEnv* env,
MUNLOCK(descP->writeMtx);
return result;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
#if defined(SO_BINDTODEVICE)
static
ERL_NIF_TERM esock_setopt_so_bindtodevice(ErlNifEnv* env,
@@ -10460,10 +7088,8 @@ ERL_NIF_TERM esock_setopt_so_bindtodevice(ErlNifEnv* env,
return esock_setopt_str_opt(env, descP, level, opt, IFNAMSIZ, eVal);
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
#if defined(SO_LINGER)
static
ERL_NIF_TERM esock_setopt_linger(ErlNifEnv* env,
@@ -10487,13 +7113,13 @@ ERL_NIF_TERM esock_setopt_linger(ErlNifEnv* env,
return esock_setopt_level_opt(env, descP, level, opt,
&val, sizeof(val));
} else
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
if ((! esock_decode_bool(eOnOff, &onOff)) ||
- (! GET_INT(env, eLinger, &val.l_linger)) ||
+ (! GET_INT(env, eLinger, (int*) &val.l_linger)) ||
(val.l_linger < 0)) {
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
val.l_onoff = onOff ? 1 : 0;
@@ -10501,11 +7127,9 @@ ERL_NIF_TERM esock_setopt_linger(ErlNifEnv* env,
&val, sizeof(val));
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE)
/* esock_setopt_msfilter - Level IP MSFILTER option
@@ -10574,7 +7198,7 @@ ERL_NIF_TERM esock_setopt_msfilter(ErlNifEnv* env,
free_invalid:
FREE(msfP);
invalid:
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
}
@@ -10600,14 +7224,12 @@ BOOLEAN_T decode_msfilter_mode(ErlNifEnv* env,
}
#endif // #if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE)
-#endif // #ifndef __WIN32__
/* esock_setopt_ip_mtu_discover - Level IP MTU_DISCOVER option
*
* The value is an atom of the type ip_pmtudisc().
*/
-#ifndef __WIN32__
#if defined(IP_MTU_DISCOVER)
static
ERL_NIF_TERM esock_setopt_ip_mtu_discover(ErlNifEnv* env,
@@ -10619,13 +7241,12 @@ ERL_NIF_TERM esock_setopt_ip_mtu_discover(ErlNifEnv* env,
int val;
if (! decode_ip_pmtudisc(env, eVal, &val))
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
else
return esock_setopt_level_opt(env, descP, level, opt,
&val, sizeof(val));
}
#endif // #if defined(IP_MTU_DISCOVER)
-#endif // #ifndef __WIN32__
@@ -10633,7 +7254,6 @@ ERL_NIF_TERM esock_setopt_ip_mtu_discover(ErlNifEnv* env,
*
* The value is either the atom 'any' or a 4-tuple.
*/
-#ifndef __WIN32__
#if defined(IP_MULTICAST_IF)
static
ERL_NIF_TERM esock_setopt_multicast_if(ErlNifEnv* env,
@@ -10646,7 +7266,7 @@ ERL_NIF_TERM esock_setopt_multicast_if(ErlNifEnv* env,
struct in_addr ifAddr;
if (! esock_decode_in_addr(env, eVal, &ifAddr)) {
- result = esock_make_invalid(env, atom_value);
+ result = esock_make_invalid(env, esock_atom_value);
} else {
result =
esock_setopt_level_opt(env, descP, level, opt,
@@ -10656,11 +7276,11 @@ ERL_NIF_TERM esock_setopt_multicast_if(ErlNifEnv* env,
return result;
}
#endif
-#endif // #ifndef __WIN32__
+
/* esock_setopt_tos - Level IP TOS option
*/
-#ifndef __WIN32__
+
#if defined(IP_TOS)
static
ERL_NIF_TERM esock_setopt_tos(ErlNifEnv* env,
@@ -10677,14 +7297,12 @@ ERL_NIF_TERM esock_setopt_tos(ErlNifEnv* env,
esock_setopt_level_opt(env, descP, level, opt,
&val, sizeof(val));
} else {
- result = esock_make_invalid(env, atom_value);
+ result = esock_make_invalid(env, esock_atom_value);
}
return result;
}
#endif
-#endif // #ifndef __WIN32__
-
@@ -10693,7 +7311,7 @@ ERL_NIF_TERM esock_setopt_tos(ErlNifEnv* env,
* The attribute 'interface' is either the atom 'any' or a 4-tuple
* (IPv4 address).
*/
-#ifndef __WIN32__
+
#if defined(IP_ADD_MEMBERSHIP) || defined(IP_DROP_MEMBERSHIP)
static
ERL_NIF_TERM esock_setopt_in_update_membership(ErlNifEnv* env,
@@ -10741,10 +7359,9 @@ ERL_NIF_TERM esock_setopt_in_update_membership(ErlNifEnv* env,
&mreq, sizeof(mreq));
invalid:
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
#endif
-#endif // #ifndef __WIN32__
/* The value is a map with three attributes: multiaddr, interface and
@@ -10754,7 +7371,7 @@ ERL_NIF_TERM esock_setopt_in_update_membership(ErlNifEnv* env,
* The attribute 'sourceaddr' is always a 4-tuple (IPv4 address).
* (IPv4 address).
*/
-#ifndef __WIN32__
+
#if defined(IP_ADD_SOURCE_MEMBERSHIP) || \
defined(IP_DROP_SOURCE_MEMBERSHIP) || \
defined(IP_BLOCK_SOURCE) || \
@@ -10786,18 +7403,14 @@ ERL_NIF_TERM esock_setopt_in_update_source(ErlNifEnv* env,
return esock_setopt_level_opt(env, descP, level, opt,
&mreq, sizeof(mreq));
invalid:
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
#endif
-#endif // #ifndef __WIN32__
#if defined(HAVE_IPV6)
-
-
-#ifndef __WIN32__
#if defined(IPV6_ADDRFORM)
static
ERL_NIF_TERM esock_setopt_addrform(ErlNifEnv* env,
@@ -10814,7 +7427,7 @@ ERL_NIF_TERM esock_setopt_addrform(ErlNifEnv* env,
"\r\n", eVal) );
if (esock_decode_domain(env, eVal, &domain) == 0)
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
SSDBG( descP, ("SOCKET",
"esock_setopt_addrform -> try set opt to %d\r\n",
@@ -10824,7 +7437,6 @@ ERL_NIF_TERM esock_setopt_addrform(ErlNifEnv* env,
&domain, sizeof(domain));
}
#endif
-#endif // #ifndef __WIN32__
@@ -10832,7 +7444,7 @@ ERL_NIF_TERM esock_setopt_addrform(ErlNifEnv* env,
*
* The value is an atom of the type ipv6_pmtudisc().
*/
-#ifndef __WIN32__
+
#if defined(IPV6_MTU_DISCOVER)
static
ERL_NIF_TERM esock_setopt_ipv6_mtu_discover(ErlNifEnv* env,
@@ -10844,16 +7456,15 @@ ERL_NIF_TERM esock_setopt_ipv6_mtu_discover(ErlNifEnv* env,
int val;
if (! decode_ipv6_pmtudisc(env, eVal, &val))
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
return esock_setopt_level_opt(env, descP, level, opt,
&val, sizeof(val));
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
+
#if defined(IPV6_MULTICAST_HOPS)
static
ERL_NIF_TERM esock_setopt_hops(ErlNifEnv* env,
@@ -10865,16 +7476,15 @@ ERL_NIF_TERM esock_setopt_hops(ErlNifEnv* env,
int hops;
if (! decode_hops(env, eVal, &hops))
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
return esock_setopt_level_opt(env, descP, level, opt,
&hops, sizeof(hops));
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
+
#if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP)
static
ERL_NIF_TERM esock_setopt_in6_update_membership(ErlNifEnv* env,
@@ -10920,10 +7530,9 @@ ERL_NIF_TERM esock_setopt_in6_update_membership(ErlNifEnv* env,
&mreq, sizeof(mreq));
invalid:
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
#endif
-#endif // #ifndef __WIN32__
#endif // defined(HAVE_IPV6)
@@ -10933,7 +7542,6 @@ ERL_NIF_TERM esock_setopt_in6_update_membership(ErlNifEnv* env,
/* esock_setopt_tcp_congestion - Level TCP CONGESTION option
*/
-#ifndef __WIN32__
#if defined(TCP_CONGESTION)
static
ERL_NIF_TERM esock_setopt_tcp_congestion(ErlNifEnv* env,
@@ -10947,17 +7555,13 @@ ERL_NIF_TERM esock_setopt_tcp_congestion(ErlNifEnv* env,
return esock_setopt_str_opt(env, descP, level, opt, max, eVal);
}
#endif
-#endif // #ifndef __WIN32__
-
#if defined(HAVE_SCTP)
-
-
/* esock_setopt_sctp_associnfo - Level SCTP ASSOCINFO option
*/
-#ifndef __WIN32__
+
#if defined(SCTP_ASSOCINFO)
static
ERL_NIF_TERM esock_setopt_sctp_associnfo(ErlNifEnv* env,
@@ -11035,15 +7639,15 @@ ERL_NIF_TERM esock_setopt_sctp_associnfo(ErlNifEnv* env,
&assocParams, sizeof(assocParams));
invalid:
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
#endif
-#endif // #ifndef __WIN32__
+
/* esock_setopt_sctp_events - Level SCTP EVENTS option
*/
-#ifndef __WIN32__
+
#if defined(SCTP_EVENTS)
static
ERL_NIF_TERM esock_setopt_sctp_events(ErlNifEnv* env,
@@ -11119,7 +7723,7 @@ ERL_NIF_TERM esock_setopt_sctp_events(ErlNifEnv* env,
"esock_setopt_sctp_events {%d} -> invalid\r\n",
descP->sock) );
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
/* Return the value to make use of automatic type casting.
@@ -11141,12 +7745,12 @@ static int esock_setopt_sctp_event(ErlNifEnv *env,
return 0;
}
#endif
-#endif // #ifndef __WIN32__
+
/* esock_setopt_sctp_initmsg - Level SCTP INITMSG option
*/
-#ifndef __WIN32__
+
#if defined(SCTP_INITMSG)
static
ERL_NIF_TERM esock_setopt_sctp_initmsg(ErlNifEnv* env,
@@ -11206,15 +7810,15 @@ ERL_NIF_TERM esock_setopt_sctp_initmsg(ErlNifEnv* env,
&initMsg, sizeof(initMsg));
invalid:
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
#endif
-#endif // #ifndef __WIN32__
+
/* esock_setopt_sctp_rtoinfo - Level SCTP RTOINFO option
*/
-#ifndef __WIN32__
+
#if defined(SCTP_RTOINFO)
static
ERL_NIF_TERM esock_setopt_sctp_rtoinfo(ErlNifEnv* env,
@@ -11265,12 +7869,9 @@ ERL_NIF_TERM esock_setopt_sctp_rtoinfo(ErlNifEnv* env,
&rtoInfo, sizeof(rtoInfo));
invalid:
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
}
#endif
-#endif // #ifndef __WIN32__
-
-
#endif // defined(HAVE_SCTP)
@@ -11279,7 +7880,7 @@ ERL_NIF_TERM esock_setopt_sctp_rtoinfo(ErlNifEnv* env,
/* esock_setopt_bool_opt - set an option that has an (integer) bool value
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_bool_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -11291,18 +7892,18 @@ ERL_NIF_TERM esock_setopt_bool_opt(ErlNifEnv* env,
int ival;
if (! esock_decode_bool(eVal, &val))
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
ival = (val) ? 1 : 0;
return esock_setopt_level_opt(env, descP, level, opt,
&ival, sizeof(ival));
}
-#endif // #ifndef __WIN32__
+
/* esock_setopt_int_opt - set an option that has an integer value
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_setopt_int_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -11318,16 +7919,16 @@ ERL_NIF_TERM esock_setopt_int_opt(ErlNifEnv* env,
esock_setopt_level_opt(env, descP, level, opt,
&val, sizeof(val));
} else {
- result = esock_make_invalid(env, atom_value);
+ result = esock_make_invalid(env, esock_atom_value);
}
return result;
}
-#endif // #ifndef __WIN32__
+
/* esock_setopt_str_opt - set an option that has an string value
*/
-#ifndef __WIN32__
+
#if defined(USE_SETOPT_STR_OPT)
static
ERL_NIF_TERM esock_setopt_str_opt(ErlNifEnv* env,
@@ -11350,7 +7951,7 @@ ERL_NIF_TERM esock_setopt_str_opt(ErlNifEnv* env,
esock_setopt_level_opt(env, descP, level, opt,
val, optLen);
} else {
- result = esock_make_invalid(env, atom_value);
+ result = esock_make_invalid(env, esock_atom_value);
}
FREE(val);
@@ -11358,12 +7959,12 @@ ERL_NIF_TERM esock_setopt_str_opt(ErlNifEnv* env,
return result;
}
#endif
-#endif // #ifndef __WIN32__
+
/* esock_setopt_timeval_opt - set an option that has an (timeval) bool value
*/
-#ifndef __WIN32__
+
#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) \
&& defined(ESOCK_USE_RCVSNDTIMEO)
static
@@ -11382,7 +7983,7 @@ ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv* env,
"\r\n", eVal) );
if (! esock_decode_timeval(env, eVal, &timeVal))
- return esock_make_invalid(env, atom_value);
+ return esock_make_invalid(env, esock_atom_value);
SSDBG( descP,
("SOCKET", "esock_setopt_timeval_opt -> set timeval option\r\n") );
@@ -11400,10 +8001,8 @@ ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv* env,
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
static ERL_NIF_TERM esock_setopt_level_opt(ErlNifEnv* env,
ESockDescriptor* descP,
int level,
@@ -11416,7 +8015,7 @@ static ERL_NIF_TERM esock_setopt_level_opt(ErlNifEnv* env,
else
return esock_atom_ok;
}
-#endif // #ifndef __WIN32__
+
/* +++ socket_setopt +++
@@ -11442,7 +8041,6 @@ static ERL_NIF_TERM esock_setopt_level_opt(ErlNifEnv* env,
* user feeling socket options are independent.
* </PaN>
*/
-#ifndef __WIN32__
static
int socket_setopt(int sock, int level, int opt,
const void* optVal, const socklen_t optLen)
@@ -11503,7 +8101,6 @@ int socket_setopt(int sock, int level, int opt,
return res;
}
-#endif // #ifndef __WIN32__
@@ -11527,58 +8124,67 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
+ ERL_NIF_TERM esock, elevel, eopt, evspec;
int level, opt;
ESOCK_ASSERT( (argc == 3) || (argc == 4) );
- SGDBG( ("SOCKET", "nif_getopt -> entry with argc: %d\r\n", argc) );
+ esock = argv[0];
+ elevel = argv[1];
+ eopt = argv[2];
+ evspec = ((argc == 4) ? argv[3] : esock_atom_undefined);
- if (! ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) {
- SGDBG( ("SOCKET", "nif_getopt -> failed initial args check\r\n") );
+ SGDBG( ("SOCKET",
+ "nif_getopt -> entry with argc: %d"
+ "\r\n esock: %T"
+ "\r\n elevel: %T"
+ "\r\n eopt: %T"
+ "\r\n evspec: %T"
+ "\r\n", argc, esock, elevel, eopt, evspec) );
+
+ if (! ESOCK_GET_RESOURCE(env, esock, (void**) &descP)) {
+ SGDBG( ("SOCKET",
+ "nif_getopt -> failed initial args check - sock\r\n") );
return enif_make_badarg(env);
}
- if (! GET_INT(env, argv[2], &opt)) {
+ if (! GET_INT(env, eopt, &opt)) {
SSDBG( descP,
- ("SOCKET", "nif_getopt -> failed initial args check\r\n") );
- if (! IS_INTEGER(env, argv[2]))
+ ("SOCKET",
+ "nif_getopt -> failed initial args check - opt\r\n") );
+ if (! IS_INTEGER(env, eopt))
return enif_make_badarg(env);
else
- return esock_make_error_integer_range(env, argv[2]);
+ return esock_make_error_integer_range(env, eopt);
+ }
+
+ if ((COMPARE(elevel, atom_otp) == 0) &&
+ (argc == 3)) {
+ return ESOCK_IO_GETOPT_OTP(env, descP, opt) ;
}
- if (esock_decode_level(env, argv[1], &level)) {
+ if (esock_decode_level(env, elevel, &level)) {
if (argc == 4) {
- ERL_NIF_TERM valueSpec = argv[3];
- return esock_getopt_native(env, descP, level, opt, valueSpec);
+ return ESOCK_IO_GETOPT_NATIVE(env, descP, level, opt, evspec);
} else {
- return esock_getopt(env, descP, level, opt);
+ return ESOCK_IO_GETOPT(env, descP, level, opt);
}
}
- if ((COMPARE(argv[1], atom_otp) == 0) &&
- (argc == 3)) {
- return esock_getopt_otp(env, descP, opt) ;
- }
-
SGDBG( ("SOCKET", "nif_getopt -> failed args check\r\n") );
- if (IS_INTEGER(env, argv[1]))
- return esock_make_error_integer_range(env, argv[1]);
+ if (IS_INTEGER(env, elevel))
+ return esock_make_error_integer_range(env, elevel);
else
return enif_make_badarg(env);
-#endif // #ifdef __WIN32__ #else
}
/* esock_getopt_otp - Handle OTP (level) options
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -11692,11 +8298,11 @@ ERL_NIF_TERM esock_getopt_otp(ErlNifEnv* env,
return result;
}
-#endif // #ifndef __WIN32__
+
/* esock_getopt_otp_debug - Handle the OTP (level) debug option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_debug(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11707,18 +8313,18 @@ ERL_NIF_TERM esock_getopt_otp_debug(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_getopt_otp_debug {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
eVal = esock_encode_bool(descP->dbg);
return esock_make_ok2(env, eVal);
}
-#endif // #ifndef __WIN32__
+
/* esock_getopt_otp_iow - Handle the OTP (level) iow option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_iow(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11729,7 +8335,7 @@ ERL_NIF_TERM esock_getopt_otp_iow(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_getopt_otp_iow {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
eVal = esock_encode_bool(descP->iow);
@@ -11741,12 +8347,12 @@ ERL_NIF_TERM esock_getopt_otp_iow(ErlNifEnv* env,
return esock_make_ok2(env, eVal);
}
-#endif // #ifndef __WIN32__
+
/* esock_getopt_otp_ctrl_proc - Handle the OTP (level) controlling_process option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_ctrl_proc(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11758,7 +8364,7 @@ ERL_NIF_TERM esock_getopt_otp_ctrl_proc(ErlNifEnv* env,
("SOCKET",
"esock_getopt_otp_ctrl_proc {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
eVal = MKPID(env, &descP->ctrlPid);
@@ -11770,12 +8376,11 @@ ERL_NIF_TERM esock_getopt_otp_ctrl_proc(ErlNifEnv* env,
return esock_make_ok2(env, eVal);
}
-#endif // #ifndef __WIN32__
/* esock_getopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_rcvbuf(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11786,9 +8391,12 @@ ERL_NIF_TERM esock_getopt_otp_rcvbuf(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_getopt_otp_rcvbuf {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
+#ifdef __WIN32__
+ eVal = MKUL(env, (unsigned long) descP->rBufSz);
+#else
if (descP->rNum == 0) {
eVal = MKUL(env, (unsigned long) descP->rBufSz);
} else {
@@ -11796,6 +8404,7 @@ ERL_NIF_TERM esock_getopt_otp_rcvbuf(ErlNifEnv* env,
MKI(env, descP->rNum),
MKUL(env, (unsigned long) descP->rBufSz));
}
+#endif
SSDBG( descP,
("SOCKET", "esock_getopt_otp_rcvbuf {%d} ->"
@@ -11804,12 +8413,12 @@ ERL_NIF_TERM esock_getopt_otp_rcvbuf(ErlNifEnv* env,
return esock_make_ok2(env, eVal);
}
-#endif // #ifndef __WIN32__
+
/* esock_getopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_rcvctrlbuf(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11821,7 +8430,7 @@ ERL_NIF_TERM esock_getopt_otp_rcvctrlbuf(ErlNifEnv* env,
("SOCKET",
"esock_getopt_otp_rcvctrlbuf {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
eVal = MKUL(env, (unsigned long) descP->rCtrlSz);
@@ -11833,12 +8442,12 @@ ERL_NIF_TERM esock_getopt_otp_rcvctrlbuf(ErlNifEnv* env,
return esock_make_ok2(env, eVal);
}
-#endif // #ifndef __WIN32__
+
/* esock_getopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_sndctrlbuf(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11850,7 +8459,7 @@ ERL_NIF_TERM esock_getopt_otp_sndctrlbuf(ErlNifEnv* env,
("SOCKET",
"esock_getopt_otp_sndctrlbuf {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
eVal = MKUL(env, (unsigned long) descP->wCtrlSz);
@@ -11862,12 +8471,12 @@ ERL_NIF_TERM esock_getopt_otp_sndctrlbuf(ErlNifEnv* env,
return esock_make_ok2(env, eVal);
}
-#endif // #ifndef __WIN32__
+
/* esock_getopt_otp_fd - Handle the OTP (level) fd option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_fd(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11878,7 +8487,7 @@ ERL_NIF_TERM esock_getopt_otp_fd(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_getopt_otp_debug {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
eVal = MKI(env, descP->sock);
@@ -11890,12 +8499,12 @@ ERL_NIF_TERM esock_getopt_otp_fd(ErlNifEnv* env,
return esock_make_ok2(env, eVal);
}
-#endif // #ifndef __WIN32__
+
/* esock_getopt_otp_meta - Handle the OTP (level) meta option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_meta(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11906,7 +8515,7 @@ ERL_NIF_TERM esock_getopt_otp_meta(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_getopt_otp_meta {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
eVal = CP_TERM(env, descP->meta.ref);
@@ -11918,12 +8527,12 @@ ERL_NIF_TERM esock_getopt_otp_meta(ErlNifEnv* env,
return esock_make_ok2(env, eVal);
}
-#endif // #ifndef __WIN32__
+
/* esock_getopt_otp_use_registry - Handle the OTP (level) use_registry option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_use_registry(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11932,13 +8541,13 @@ ERL_NIF_TERM esock_getopt_otp_use_registry(ErlNifEnv* env,
return esock_make_ok2(env, eVal);
}
-#endif
+
/*
* esock_getopt_otp_domain - Handle the OTP (level) domain option
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_domain(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11949,7 +8558,7 @@ ERL_NIF_TERM esock_getopt_otp_domain(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_getopt_otp_domain {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
esock_encode_domain(env, descP->domain, &domain);
@@ -11962,7 +8571,6 @@ ERL_NIF_TERM esock_getopt_otp_domain(ErlNifEnv* env,
return result;
}
-#endif // #ifndef __WIN32__
@@ -11971,7 +8579,7 @@ ERL_NIF_TERM esock_getopt_otp_domain(ErlNifEnv* env,
/*
* esock_getopt_otp_type - Handle the OTP (level) type options.
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_type(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -11982,7 +8590,7 @@ ERL_NIF_TERM esock_getopt_otp_type(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_getopt_otp_type {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
esock_encode_type(env, descP->type, &type);
@@ -11995,13 +8603,13 @@ ERL_NIF_TERM esock_getopt_otp_type(ErlNifEnv* env,
return result;
}
-#endif // #ifndef __WIN32__
+
/*
* esock_getopt_otp_protocol - Handle the OTP (level) protocol options.
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_protocol(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -12012,7 +8620,7 @@ ERL_NIF_TERM esock_getopt_otp_protocol(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_getopt_otp_protocol {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
protocol = MKI(env, descP->protocol);
@@ -12025,13 +8633,13 @@ ERL_NIF_TERM esock_getopt_otp_protocol(ErlNifEnv* env,
return result;
}
-#endif // #ifndef __WIN32__
+
/*
* esock_getopt_otp_dtp - Handle the OTP (level) type options.
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_otp_dtp(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -12042,7 +8650,7 @@ ERL_NIF_TERM esock_getopt_otp_dtp(ErlNifEnv* env,
SSDBG( descP,
("SOCKET", "esock_getopt_otp_dtp {%d} -> done closed\r\n",
descP->sock) );
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
esock_encode_domain(env, descP->domain, &domain);
@@ -12058,7 +8666,6 @@ ERL_NIF_TERM esock_getopt_otp_dtp(ErlNifEnv* env,
return result;
}
-#endif // #ifndef __WIN32__
#endif // #if 0
@@ -12066,7 +8673,7 @@ ERL_NIF_TERM esock_getopt_otp_dtp(ErlNifEnv* env,
/* How to decode the value is specified with valueSpec
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -12094,7 +8701,7 @@ ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env,
("SOCKET", "esock_getopt_native {%d} -> done closed\r\n",
descP->sock) );
MUNLOCK(descP->readMtx);
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
/* <KOLLA>
@@ -12113,7 +8720,7 @@ ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env,
result =
esock_getopt_size_opt(env, descP, level, opt, valueSz);
} else {
- result = esock_make_invalid(env, atom_value);
+ result = esock_make_invalid(env, esock_atom_value);
}
} else if (COMPARE(valueSpec, atom_integer) == 0) {
SSDBG( descP,
@@ -12132,7 +8739,7 @@ ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env,
"\r\n", descP->sock, (unsigned long) bin.size) );
result = esock_getopt_bin_opt(env, descP, level, opt, &bin);
} else {
- result = esock_make_invalid(env, atom_value);
+ result = esock_make_invalid(env, esock_atom_value);
}
SSDBG( descP,
@@ -12143,35 +8750,34 @@ ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env,
MUNLOCK(descP->readMtx);
return result;
}
-#endif // #ifndef __WIN32__
+
/* esock_getopt - An option that we know how to decode
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_getopt(ErlNifEnv* env,
ESockDescriptor* descP,
int level,
int opt)
{
- ERL_NIF_TERM result;
+ ERL_NIF_TERM result;
const struct ESockOpt *optP;
MLOCK(descP->readMtx);
SSDBG( descP,
("SOCKET", "esock_getopt {%d} -> entry with"
- "\r\n level: %d"
- "\r\n opt: %d"
+ "\r\n level: %d"
+ "\r\n opt: %d"
"\r\n", descP->sock, level, opt) );
if (! IS_OPEN(descP->readState)) {
SSDBG( descP,
- ("SOCKET", "esock_getopt {%d} -> done closed\r\n",
+ ("SOCKET", "esock_getopt {%d} -> done when closed\r\n",
descP->sock) );
MUNLOCK(descP->readMtx);
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
}
optP = lookupOpt(level, opt);
@@ -12205,10 +8811,8 @@ ERL_NIF_TERM esock_getopt(ErlNifEnv* env,
MUNLOCK(descP->readMtx);
return result;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
#if defined(SO_BINDTODEVICE)
static
ERL_NIF_TERM esock_getopt_so_bindtodevice(ErlNifEnv* env,
@@ -12219,10 +8823,8 @@ ERL_NIF_TERM esock_getopt_so_bindtodevice(ErlNifEnv* env,
return esock_getopt_str_opt(env, descP, level, opt, IFNAMSIZ+1, FALSE);
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
#if defined(SO_DOMAIN)
static
ERL_NIF_TERM esock_getopt_sock_domain(ErlNifEnv* env,
@@ -12244,10 +8846,8 @@ ERL_NIF_TERM esock_getopt_sock_domain(ErlNifEnv* env,
return result;
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
#if defined(SO_LINGER)
static
ERL_NIF_TERM esock_getopt_linger(ErlNifEnv* env,
@@ -12262,7 +8862,11 @@ ERL_NIF_TERM esock_getopt_linger(ErlNifEnv* env,
sys_memzero((void *) &val, sizeof(val));
+#ifdef __WIN32__
+ res = sock_getopt(descP->sock, level, opt, (char*) &val, &valSz);
+#else
res = sock_getopt(descP->sock, level, opt, &val, &valSz);
+#endif
if (res != 0) {
result = esock_make_error_errno(env, sock_errno());
@@ -12284,11 +8888,9 @@ ERL_NIF_TERM esock_getopt_linger(ErlNifEnv* env,
return result;
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
#if defined(SO_TYPE)
static
ERL_NIF_TERM esock_getopt_sock_type(ErlNifEnv* env,
@@ -12310,10 +8912,8 @@ ERL_NIF_TERM esock_getopt_sock_type(ErlNifEnv* env,
return result;
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
#if defined(SO_PROTOCOL)
static
ERL_NIF_TERM esock_getopt_sock_protocol(ErlNifEnv* env,
@@ -12346,10 +8946,8 @@ ERL_NIF_TERM esock_getopt_sock_protocol(ErlNifEnv* env,
return result;
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
/* esock_getopt_ip_mtu_discover - Level IP MTU_DISCOVER option
*/
#if defined(IP_MTU_DISCOVER)
@@ -12374,12 +8972,11 @@ ERL_NIF_TERM esock_getopt_ip_mtu_discover(ErlNifEnv* env,
}
#endif
-#endif // #ifndef __WIN32__
/* esock_getopt_multicast_if - Level IP MULTICAST_IF option
*/
-#ifndef __WIN32__
+
#if defined(IP_MULTICAST_IF)
static
ERL_NIF_TERM esock_getopt_multicast_if(ErlNifEnv* env,
@@ -12395,7 +8992,11 @@ ERL_NIF_TERM esock_getopt_multicast_if(ErlNifEnv* env,
sys_memzero((void *) &ifAddr, ifAddrSz);
+#ifdef __WIN32__
+ res = sock_getopt(descP->sock, level, opt, (char*) &ifAddr, &ifAddrSz);
+#else
res = sock_getopt(descP->sock, level, opt, &ifAddr, &ifAddrSz);
+#endif
if (res != 0) {
result = esock_make_error_errno(env, sock_errno());
@@ -12408,12 +9009,12 @@ ERL_NIF_TERM esock_getopt_multicast_if(ErlNifEnv* env,
}
#endif
-#endif // #ifndef __WIN32__
+
/* esock_getopt_tos - Level IP TOS option
*/
-#ifndef __WIN32__
+
#if defined(IP_TOS)
static
ERL_NIF_TERM esock_getopt_tos(ErlNifEnv* env,
@@ -12433,15 +9034,14 @@ ERL_NIF_TERM esock_getopt_tos(ErlNifEnv* env,
return result;
}
#endif
-#endif // #ifndef __WIN32__
-#if defined(HAVE_IPV6)
+#if defined(HAVE_IPV6)
/* esock_getopt_ipv6_mtu_discover - Level IPv6 MTU_DISCOVER option
*/
-#ifndef __WIN32__
+
#if defined(IPV6_MTU_DISCOVER)
static
ERL_NIF_TERM esock_getopt_ipv6_mtu_discover(ErlNifEnv* env,
@@ -12464,16 +9064,13 @@ ERL_NIF_TERM esock_getopt_ipv6_mtu_discover(ErlNifEnv* env,
}
#endif
-#endif // #ifndef __WIN32__
-
#endif // defined(HAVE_IPV6)
-
/* esock_getopt_tcp_congestion - Level TCP CONGESTION option
*/
-#ifndef __WIN32__
+
#if defined(TCP_CONGESTION)
static
ERL_NIF_TERM esock_getopt_tcp_congestion(ErlNifEnv* env,
@@ -12486,14 +9083,11 @@ ERL_NIF_TERM esock_getopt_tcp_congestion(ErlNifEnv* env,
return esock_getopt_str_opt(env, descP, level, opt, max, TRUE);
}
#endif
-#endif // #ifndef __WIN32__
#if defined(HAVE_SCTP)
-
-
/* esock_getopt_sctp_associnfo - Level SCTP ASSOCINFO option
*
* <KOLLA>
@@ -12657,7 +9251,6 @@ ERL_NIF_TERM esock_getopt_sctp_rtoinfo(ErlNifEnv* env,
/* esock_getopt_bool_opt - get an (integer) bool option
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -12676,12 +9269,10 @@ ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv* env,
}
return result;
}
-#endif // #ifndef __WIN32__
/* esock_getopt_int_opt - get an integer option
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -12695,14 +9286,12 @@ ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv* env,
return esock_make_ok2(env, MKI(env, val));
}
-#endif // #ifndef __WIN32__
/* esock_getopt_int - get an integer option
*/
-#ifndef __WIN32__
-static
+extern
BOOLEAN_T esock_getopt_int(SOCKET sock,
int level,
int opt,
@@ -12711,17 +9300,19 @@ BOOLEAN_T esock_getopt_int(SOCKET sock,
int val = 0;
SOCKOPTLEN_T valSz = sizeof(val);
+#ifdef __WIN32__
+ if (sock_getopt(sock, level, opt, (char*) &val, &valSz) != 0)
+#else
if (sock_getopt(sock, level, opt, &val, &valSz) != 0)
+#endif
return FALSE;
*valP = val;
return TRUE;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_getopt_size_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -12768,10 +9359,9 @@ ERL_NIF_TERM esock_getopt_size_opt(ErlNifEnv* env,
return result;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
+
static
ERL_NIF_TERM esock_getopt_bin_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -12786,7 +9376,7 @@ ERL_NIF_TERM esock_getopt_bin_opt(ErlNifEnv* env,
vsz = (SOCKOPTLEN_T) binP->size;
if (SZT(vsz) != binP->size) {
- result = esock_make_error_invalid(env, atom_data_size);
+ result = esock_make_error_invalid(env, esock_atom_data_size);
} else {
ESOCK_ASSERT( ALLOC_BIN(vsz, &val) );
sys_memcpy(val.data, binP->data, vsz);
@@ -12814,7 +9404,7 @@ ERL_NIF_TERM esock_getopt_bin_opt(ErlNifEnv* env,
return result;
}
-#endif // #ifndef __WIN32__
+
/* esock_getopt_timeval_opt - get an timeval option
@@ -12866,11 +9456,11 @@ ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv* env,
* Some platforms (seen on ppc Linux 2.6.29-3.ydl61.3)
* may return 0 as the cmsg_len if the cmsg is to be ignored.
*/
-#define LEN_CMSG_DATA(__CMSG__) \
- ((__CMSG__)->cmsg_len < sizeof (struct cmsghdr) ? 0 : \
- (__CMSG__)->cmsg_len - ((char*)CMSG_DATA(__CMSG__) - (char*)(__CMSG__)))
-#define NEXT_CMSG_HDR(__CMSG__) \
- ((struct cmsghdr*)(((char*)(__CMSG__)) + CMSG_SPACE(LEN_CMSG_DATA(__CMSG__))))
+#define ESOCK_LEN_CMSG_DATA(__CMSG__) \
+ ((__CMSG__)->cmsg_len < sizeof (struct cmsghdr) ? 0 : \
+ (__CMSG__)->cmsg_len - ((char*)ESOCK_CMSG_DATA(__CMSG__) - (char*)(__CMSG__)))
+#define ESOCK_NEXT_CMSG_HDR(__CMSG__) \
+ ((struct cmsghdr*)(((char*)(__CMSG__)) + ESOCK_CMSG_SPACE(ESOCK_LEN_CMSG_DATA(__CMSG__))))
static
ERL_NIF_TERM esock_getopt_pktoptions(ErlNifEnv* env,
@@ -12902,8 +9492,8 @@ ERL_NIF_TERM esock_getopt_pktoptions(ErlNifEnv* env,
for (endOfBuf = (struct cmsghdr*)(cmsgs.data + cmsgs.size),
currentP = (struct cmsghdr*)(cmsgs.data);
(currentP != NULL) && (currentP < endOfBuf);
- currentP = NEXT_CMSG_HDR(currentP)) {
- unsigned char* dataP = UCHARP(CMSG_DATA(currentP));
+ currentP = ESOCK_NEXT_CMSG_HDR(currentP)) {
+ unsigned char* dataP = UCHARP(ESOCK_CMSG_DATA(currentP));
size_t dataPos = dataP - cmsgs.data;
size_t dataLen = (UCHARP(currentP) + currentP->cmsg_len) - dataP;
@@ -12939,7 +9529,7 @@ ERL_NIF_TERM esock_getopt_pktoptions(ErlNifEnv* env,
ERL_NIF_TERM keys[] = {esock_atom_level,
esock_atom_type,
esock_atom_data,
- atom_value};
+ esock_atom_value};
ERL_NIF_TERM vals[NUM(keys)];
size_t numKeys = NUM(keys);
BOOLEAN_T haveValue;
@@ -12947,10 +9537,10 @@ ERL_NIF_TERM esock_getopt_pktoptions(ErlNifEnv* env,
vals[0] = esock_encode_level(env, currentP->cmsg_level);
vals[2] = MKSBIN(env, ctrlBuf, dataPos, dataLen);
- haveValue = encode_cmsg(env,
- currentP->cmsg_level,
- currentP->cmsg_type,
- dataP, dataLen, &vals[1], &vals[3]);
+ haveValue = esock_encode_cmsg(env,
+ currentP->cmsg_level,
+ currentP->cmsg_type,
+ dataP, dataLen, &vals[1], &vals[3]);
SSDBG( descP,
("SOCKET", "esock_getopt_pktoptions {%d} -> "
@@ -13066,9 +9656,6 @@ ERL_NIF_TERM nif_sockname(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM res;
@@ -13088,7 +9675,7 @@ ERL_NIF_TERM nif_sockname(ErlNifEnv* env,
("SOCKET", "nif_sockname(%T) {%d}"
"\r\n", argv[0], descP->sock) );
- res = esock_sockname(env, descP);
+ res = ESOCK_IO_SOCKNAME(env, descP);
SSDBG( descP,
("SOCKET", "nif_sockname(%T) {%d} -> done with res = %T\r\n",
@@ -13097,22 +9684,27 @@ ERL_NIF_TERM nif_sockname(ErlNifEnv* env,
MUNLOCK(descP->readMtx);
return res;
-#endif // #ifdef __WIN32__ #else
}
-#ifndef __WIN32__
+/* ========================================================================
+ */
+
static
ERL_NIF_TERM esock_sockname(ErlNifEnv* env,
ESockDescriptor* descP)
{
ESockAddress sa;
ESockAddress* saP = &sa;
+#ifdef __WIN32__
+ int sz = sizeof(ESockAddress);
+#else
SOCKLEN_T sz = sizeof(ESockAddress);
+#endif
if (! IS_OPEN(descP->readState))
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
SSDBG( descP,
("SOCKET", "esock_sockname {%d} -> open - try get sockname\r\n",
@@ -13129,7 +9721,7 @@ ERL_NIF_TERM esock_sockname(ErlNifEnv* env,
"got sockname - try decode\r\n",
descP->sock) );
- esock_encode_sockaddr(env, saP, sz, &esa);
+ esock_encode_sockaddr(env, saP, (SOCKLEN_T) sz, &esa);
SSDBG( descP,
("SOCKET", "esock_sockname {%d} -> decoded: "
@@ -13139,8 +9731,6 @@ ERL_NIF_TERM esock_sockname(ErlNifEnv* env,
return esock_make_ok2(env, esa);
}
}
-#endif // #ifndef __WIN32__
-
@@ -13159,9 +9749,6 @@ ERL_NIF_TERM nif_peername(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM res;
@@ -13181,7 +9768,7 @@ ERL_NIF_TERM nif_peername(ErlNifEnv* env,
("SOCKET", "nif_peername(%T) {%d}"
"\r\n", argv[0], descP->sock) );
- res = esock_peername(env, descP);
+ res = ESOCK_IO_PEERNAME(env, descP);
SSDBG( descP,
("SOCKET", "nif_peername(%T) {%d} -> done with res = %T\r\n",
@@ -13190,22 +9777,27 @@ ERL_NIF_TERM nif_peername(ErlNifEnv* env,
MUNLOCK(descP->readMtx);
return res;
-#endif // #ifdef __WIN32__ #else
}
-#ifndef __WIN32__
+/* ========================================================================
+ */
+
static
ERL_NIF_TERM esock_peername(ErlNifEnv* env,
ESockDescriptor* descP)
{
ESockAddress sa;
ESockAddress* saP = &sa;
+#ifdef __WIN32__
+ int sz = sizeof(ESockAddress);
+#else
SOCKLEN_T sz = sizeof(ESockAddress);
+#endif
if (! IS_OPEN(descP->readState))
- return esock_make_error(env, atom_closed);
+ return esock_make_error_closed(env);
SSDBG( descP,
("SOCKET", "esock_peername {%d} -> open - try get peername\r\n",
@@ -13222,7 +9814,7 @@ ERL_NIF_TERM esock_peername(ErlNifEnv* env,
"got peername - try decode\r\n",
descP->sock) );
- esock_encode_sockaddr(env, saP, sz, &esa);
+ esock_encode_sockaddr(env, saP, (SOCKLEN_T) sz, &esa);
SSDBG( descP,
("SOCKET", "esock_peername {%d} -> decoded: "
@@ -13232,7 +9824,6 @@ ERL_NIF_TERM esock_peername(ErlNifEnv* env,
return esock_make_ok2(env, esa);
}
}
-#endif // #ifndef __WIN32__
@@ -13260,9 +9851,6 @@ ERL_NIF_TERM nif_ioctl(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM res;
unsigned long req;
@@ -13285,49 +9873,54 @@ ERL_NIF_TERM nif_ioctl(ErlNifEnv* env,
("SOCKET", "nif_ioctl(%T) {%d} -> ioctl request %d"
"\r\n", argv[0], descP->sock, req) );
+ /* Is this really enough? Why not the write mutex also? */
MLOCK(descP->readMtx);
if (! IS_OPEN(descP->readState)) {
- res = esock_make_error(env, atom_closed);
+ res = esock_make_error_closed(env);
} else {
- if (argc == 2) {
-
- /* Only one request with this number of arguments: gifconf
- * Socket and request (=gifconf)
- */
-
- /* Two arguments: Socket and get request */
- res = esock_ioctl1(env, descP, req);
-
- } else if (argc == 3) {
-
- /* (Currently) All *other* get requests has 3 arguments
- * Socket, request and name/index
- */
-
- ERL_NIF_TERM earg = argv[2];
-
- /* Two arguments: request and arg */
- res = esock_ioctl2(env, descP, req, earg);
-
- } else if (argc == 4) {
-
- /* (Currently) Set requests has 4 arguments
- * Socket, request, name and value
- */
-
- ERL_NIF_TERM earg1 = argv[2]; // (currently) Name
- ERL_NIF_TERM earg2 = argv[3]; // Value
-
- /* Three arguments: request, arg1 (name) and arg2 (value) */
- res = esock_ioctl3(env, descP, req, earg1, earg2);
-
- } else {
-
- res = esock_make_error(env, esock_atom_einval);
-
- }
+ switch (argc) {
+ case 2:
+ /* Only one request with this number of arguments: gifconf
+ * Socket and request (=gifconf)
+ */
+
+ /* Two arguments: socket and request */
+ res = ESOCK_IO_IOCTL_2(env, descP, req);
+ break;
+
+ case 3:
+ /* (Currently) All *other* get requests has 3 arguments
+ * Socket, request and name/index
+ */
+ {
+ ERL_NIF_TERM earg = argv[2];
+
+ /* Three arguments: socket, request and arg */
+ res = ESOCK_IO_IOCTL_3(env, descP, req, earg);
+ }
+ break;
+
+ case 4:
+ /* (Currently) Set requests has 4 arguments
+ * Socket, request, name and value
+ */
+ {
+ ERL_NIF_TERM earg1 = argv[2]; // (currently) Name
+ ERL_NIF_TERM earg2 = argv[3]; // Value
+
+ res = ESOCK_IO_IOCTL_4(env, descP, req, earg1, earg2);
+ }
+ break;
+
+ default:
+ /* This is just to protect against programming errors,
+ * since we have an assert above!
+ */
+ res = esock_make_error(env, esock_atom_einval);
+ break;
+ }
}
MUNLOCK(descP->readMtx);
@@ -13337,1132 +9930,8 @@ ERL_NIF_TERM nif_ioctl(ErlNifEnv* env,
argv[0], descP->sock, res) );
return res;
-#endif // #ifdef __WIN32__ #else
-}
-
-
-
-#ifndef __WIN32__
-
-static
-ERL_NIF_TERM esock_ioctl1(ErlNifEnv* env,
- ESockDescriptor* descP,
- unsigned long req)
-{
- switch (req) {
-
-#if defined(SIOCGIFCONF)
- case SIOCGIFCONF:
- return esock_ioctl_gifconf(env, descP);
- break;
-#endif
-
- default:
- return esock_make_error(env, esock_atom_enotsup);
- break;
- }
-
-}
-
-
-/* The type and value of 'arg' depend on the request,
- * which we have not yet "analyzed".
- *
- * Request arg arg type
- * ------- ------- --------
- * gifname ifindex integer
- * gifindex name string
- * gifflags name string
- * gifaddr name string
- * gifdstaddr name string
- * gifbdraddr name string
- * gifnetmask name string
- * gifmtu name string
- * gifhwaddr name string
- * gifmap name string
- * giftxqlen name string
- */
-static
-ERL_NIF_TERM esock_ioctl2(ErlNifEnv* env,
- ESockDescriptor* descP,
- unsigned long req,
- ERL_NIF_TERM arg)
-{
- /* This for *get* requests */
-
- switch (req) {
-
-#if defined(SIOCGIFNAME)
- case SIOCGIFNAME:
- return esock_ioctl_gifname(env, descP, arg);
- break;
-#endif
-
-#if defined(SIOCGIFINDEX)
- case SIOCGIFINDEX:
- return esock_ioctl_gifindex(env, descP, arg);
- break;
-#endif
-
-#if defined(SIOCGIFFLAGS)
- case SIOCGIFFLAGS:
- return esock_ioctl_gifflags(env, descP, arg);
- break;
-#endif
-
-#if defined(SIOCGIFADDR)
- case SIOCGIFADDR:
- return esock_ioctl_gifaddr(env, descP, arg);
- break;
-#endif
-
-#if defined(SIOCGIFDSTADDR)
- case SIOCGIFDSTADDR:
- return esock_ioctl_gifdstaddr(env, descP, arg);
- break;
-#endif
-
-#if defined(SIOCGIFBRDADDR)
- case SIOCGIFBRDADDR:
- return esock_ioctl_gifbrdaddr(env, descP, arg);
- break;
-#endif
-
-#if defined(SIOCGIFNETMASK)
- case SIOCGIFNETMASK:
- return esock_ioctl_gifnetmask(env, descP, arg);
- break;
-#endif
-
-#if defined(SIOCGIFMTU)
- case SIOCGIFMTU:
- return esock_ioctl_gifmtu(env, descP, arg);
- break;
-#endif
-
-#if defined(SIOCGIFHWADDR) && defined(ESOCK_USE_HWADDR)
- case SIOCGIFHWADDR:
- return esock_ioctl_gifhwaddr(env, descP, arg);
- break;
-#endif
-
-#if defined(SIOCGIFMAP) && defined(ESOCK_USE_IFMAP)
- case SIOCGIFMAP:
- return esock_ioctl_gifmap(env, descP, arg);
- break;
-#endif
-
-#if defined(SIOCGIFTXQLEN)
- case SIOCGIFTXQLEN:
- return esock_ioctl_giftxqlen(env, descP, arg);
- break;
-#endif
-
- default:
- return esock_make_error(env, esock_atom_enotsup);
- break;
- }
-
-}
-
-
-/* The type and value of arg(s) depend on the request,
- * which we have not yet "analyzed".
- *
- * Request arg1 arg1 type arg2 arg2 type
- * ------- ------- --------- ------ ---------
- * sifflags name string Flags #{IntFlag := boolean()}
- * IntFlag is the native flag
- * sifaddr name string Addr sockaddr()
- * sifdstaddr name string DstAddr sockaddr()
- * sifbrdaddr name string BrdAddr sockaddr()
- * sifnetmask name string NetMask sockaddr()
- * gifmtu name string MTU integer()
- * sifhwaddr name string HwAddr sockaddr()
- * giftxqlen name string Len integer()
- */
-static
-ERL_NIF_TERM esock_ioctl3(ErlNifEnv* env,
- ESockDescriptor* descP,
- unsigned long req,
- ERL_NIF_TERM ename,
- ERL_NIF_TERM eval)
-{
-
- switch (req) {
-
-#if defined(SIOCSIFFLAGS)
- case SIOCSIFFLAGS:
- return esock_ioctl_sifflags(env, descP, ename, eval);
- break;
-#endif
-
-#if defined(SIOCSIFADDR)
- case SIOCSIFADDR:
- return esock_ioctl_sifaddr(env, descP, ename, eval);
- break;
-#endif
-
-#if defined(SIOCSIFDSTADDR)
- case SIOCSIFDSTADDR:
- return esock_ioctl_sifdstaddr(env, descP, ename, eval);
- break;
-#endif
-
-#if defined(SIOCSIFBRDADDR)
- case SIOCSIFBRDADDR:
- return esock_ioctl_sifbrdaddr(env, descP, ename, eval);
- break;
-#endif
-
-#if defined(SIOCSIFNETMASK)
- case SIOCSIFNETMASK:
- return esock_ioctl_sifnetmask(env, descP, ename, eval);
- break;
-#endif
-
-#if defined(SIOCSIFMTU)
- case SIOCSIFMTU:
- return esock_ioctl_sifmtu(env, descP, ename, eval);
- break;
-#endif
-
-#if defined(SIOCSIFTXQLEN)
- case SIOCSIFTXQLEN:
- return esock_ioctl_siftxqlen(env, descP, ename, eval);
- break;
-#endif
-
- default:
- return esock_make_error(env, esock_atom_enotsup);
- break;
- }
-
-}
-
-
-/* ===========================================================================
- * The implemented (ioctl) get requests falls into three grops:
- *
- * 1) gifconf - Takes no argument other then the request
- * 2) gifname - Takes the interface index (integer) as an argument
- * 3) other - All other (get) requests takes the interface name (string)
- * as the argument.
- *
- * The functions defined using the macros below are all in the third (3)
- * group.
- *
- */
-
-/* *** esock_ioctl_gifindex *** */
-#if defined(SIOCGIFINDEX)
-#if defined(ESOCK_USE_IFINDEX)
-#define IOCTL_GIFINDEX_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifindex, SIOCGIFINDEX, ivalue, ifreq.ifr_ifindex)
-#elif defined(ESOCK_USE_INDEX)
-#define IOCTL_GIFINDEX_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifindex, SIOCGIFINDEX, ivalue, ifreq.ifr_index)
-#else
-#define IOCTL_GIFINDEX_FUNC_DECL
-#endif
-#else
-#define IOCTL_GIFINDEX_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_gifflags *** */
-#if defined(SIOCGIFFLAGS)
-#define IOCTL_GIFFLAGS_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifflags, SIOCGIFFLAGS, flags, ifreq.ifr_flags)
-#else
-#define IOCTL_GIFFLAGS_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_gifaddr *** */
-#if defined(SIOCGIFADDR)
-#define IOCTL_GIFADDR_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifaddr, SIOCGIFADDR, ifraddr, &ifreq.ifr_addr)
-#else
-#define IOCTL_GIFADDR_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_gifdstaddr *** */
-#if defined(SIOCGIFDSTADDR)
-#define IOCTL_GIFDSTADDR_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifdstaddr, SIOCGIFDSTADDR, ifraddr, &ifreq.ifr_dstaddr)
-#else
-#define IOCTL_GIFDSTADDR_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_gifbrdaddr *** */
-#if defined(SIOCGIFBRDADDR)
-#define IOCTL_GIFBRDADDR_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifbrdaddr, SIOCGIFBRDADDR, ifraddr, &ifreq.ifr_broadaddr)
-#else
-#define IOCTL_GIFBRDADDR_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_gifnetmask *** */
-#if defined(SIOCGIFNETMASK)
-#ifdef __linux__
-#define IOCTL_GIFNETMASK_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifnetmask, SIOCGIFNETMASK, ifraddr, &ifreq.ifr_netmask)
-#else
-#define IOCTL_GIFNETMASK_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifnetmask, SIOCGIFNETMASK, ifraddr, &ifreq.ifr_addr)
-#endif
-#else
-#define IOCTL_GIFNETMASK_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_gifmtu *** */
-#if defined(SIOCGIFMTU)
-#define IOCTL_GIFMTU_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifmtu, SIOCGIFMTU, ivalue, ifreq.ifr_mtu)
-#else
-#define IOCTL_GIFMTU_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_gifhwaddr *** */
-#if defined(SIOCGIFHWADDR) && defined(ESOCK_USE_HWADDR)
-#define IOCTL_GIFHWADDR_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifhwaddr, SIOCGIFHWADDR, hwaddr, &ifreq.ifr_hwaddr)
-#else
-#define IOCTL_GIFHWADDR_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_gifmap *** */
-#if defined(SIOCGIFMAP) && defined(ESOCK_USE_IFMAP)
-#define IOCTL_GIFMAP_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(gifmap, SIOCGIFMAP, ifrmap, &ifreq.ifr_map)
-#else
-#define IOCTL_GIFMAP_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_giftxqlen *** */
-#if defined(SIOCGIFTXQLEN)
-#define IOCTL_GIFTXQLEN_FUNC_DECL \
- IOCTL_GET_REQUEST_DECL(giftxqlen, SIOCGIFTXQLEN, ivalue, ifreq.ifr_qlen)
-#else
-#define IOCTL_GIFTXQLEN_FUNC_DECL
-#endif
-
-#define IOCTL_GET_FUNCS \
- IOCTL_GIFINDEX_FUNC_DECL \
- IOCTL_GIFFLAGS_FUNC_DECL \
- IOCTL_GIFADDR_FUNC_DECL \
- IOCTL_GIFDSTADDR_FUNC_DECL \
- IOCTL_GIFBRDADDR_FUNC_DECL \
- IOCTL_GIFNETMASK_FUNC_DECL \
- IOCTL_GIFMTU_FUNC_DECL \
- IOCTL_GIFHWADDR_FUNC_DECL \
- IOCTL_GIFMAP_FUNC_DECL \
- IOCTL_GIFTXQLEN_FUNC_DECL
-
-#define IOCTL_GET_REQUEST_DECL(OR, R, EF, UV) \
- static \
- ERL_NIF_TERM esock_ioctl_##OR(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ERL_NIF_TERM ename) \
- { \
- ERL_NIF_TERM result; \
- struct ifreq ifreq; \
- char* ifn = NULL; \
- int nlen; \
- \
- SSDBG( descP, ("SOCKET", "esock_ioctl_" #OR " {%d} -> entry with" \
- "\r\n (e)Name: %T" \
- "\r\n", descP->sock, ename) ); \
- \
- if (!esock_decode_string(env, ename, &ifn)) \
- return enif_make_badarg(env); \
- \
- nlen = esock_strnlen(ifn, IFNAMSIZ); \
- \
- sys_memset(ifreq.ifr_name, '\0', IFNAMSIZ); \
- sys_memcpy(ifreq.ifr_name, ifn, \
- (nlen >= IFNAMSIZ) ? IFNAMSIZ-1 : nlen); \
- \
- SSDBG( descP, \
- ("SOCKET", \
- "esock_ioctl_" #OR " {%d} -> try ioctl\r\n", \
- descP->sock) ); \
- \
- if (ioctl(descP->sock, R, (char *) &ifreq) < 0) { \
- int saveErrno = sock_errno(); \
- ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno)); \
- \
- SSDBG( descP, \
- ("SOCKET", "esock_ioctl_" #OR " {%d} -> failure: " \
- "\r\n reason: %T (%d)" \
- "\r\n", descP->sock, reason, saveErrno) ); \
- \
- result = esock_make_error(env, reason); \
- \
- } else { \
- SSDBG( descP, \
- ("SOCKET", "esock_ioctl_" #OR " {%d} -> encode value\r\n", \
- descP->sock) ); \
- result = encode_ioctl_##EF(env, descP, UV); \
- } \
- \
- FREE(ifn); \
- \
- return result; \
- \
- }
-IOCTL_GET_FUNCS
-#undef IOCTL_GET_FUNCS
-
-
-/* ===========================================================================
- * The "rest" of the implemented (ioctl) get requests
- *
- * These (get) requests could not be 'generated' by the macros above.
- */
-
-static
-ERL_NIF_TERM esock_ioctl_gifconf(ErlNifEnv* env,
- ESockDescriptor* descP)
-{
- struct ifconf ifc;
- int ifc_len = 0;
- int buflen = 100 * sizeof(struct ifreq);
- char *buf = MALLOC(buflen);
- ERL_NIF_TERM result;
-
- SSDBG( descP, ("SOCKET", "esock_ioctl_gifconf {%d} -> entry\r\n", descP->sock) );
-
- for (;;) {
- ifc.ifc_len = buflen;
- ifc.ifc_buf = buf;
- if (ioctl(descP->sock, SIOCGIFCONF, (char *) &ifc) < 0) {
- int saveErrno = sock_errno();
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_gifconf {%d} -> failure: "
- "\r\n errno: %d (%s)"
- "\r\n", descP->sock, saveErrno, erl_errno_id(saveErrno)) );
-
- if (saveErrno != EINVAL || ifc_len) {
- ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
- FREE(buf);
- return esock_make_error(env, reason);
- }
- } else {
- if (ifc.ifc_len == ifc_len) break; /* buf large enough */
- ifc_len = ifc.ifc_len;
- }
- buflen += 10 * sizeof(struct ifreq);
- buf = (char *) REALLOC(buf, buflen);
- }
-
- result = encode_ioctl_ifconf(env, descP, &ifc);
-
- FREE(ifc.ifc_buf);
-
- return result;
-}
-
-
-#if defined(SIOCGIFNAME)
-static
-ERL_NIF_TERM esock_ioctl_gifname(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eidx)
-{
- ERL_NIF_TERM result;
- struct ifreq ifreq;
- int index;
-
- SSDBG( descP, ("SOCKET", "esock_ioctl_gifname {%d} -> entry with"
- "\r\n (e)Index: %T"
- "\r\n", descP->sock, eidx) );
-
- if (!GET_INT(env, eidx, &index))
- return enif_make_badarg(env);
-
- ifreq.ifr_ifindex = index;
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_gifname {%d} -> try ioctl\r\n", descP->sock) );
-
- if (ioctl(descP->sock, SIOCGIFNAME, (char *) &ifreq) < 0) {
- int saveErrno = sock_errno();
- ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_gifname {%d} -> failure: "
- "\r\n reason: %T (%d)"
- "\r\n", descP->sock, reason, saveErrno) );
-
- result = esock_make_error(env, reason);
-
- } else {
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_gifname {%d} -> encode name\r\n",
- descP->sock) );
-
- result = esock_make_ok2(env, encode_ioctl_ifreq_name(env, ifreq.ifr_name));
- }
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_gifname {%d} -> done with"
- "\r\n result: %T"
- "\r\n",
- descP->sock, result) );
-
- return result;
-
-}
-#endif
-
-
-
-
-/* ===========================================================================
- * The implemented (ioctl) set requests:
- *
- */
-
-/* *** esock_ioctl_sifaddr *** */
-#if defined(SIOCSIFADDR)
-#define IOCTL_SIFADDR_FUNC_DECL \
- IOCTL_SET_REQUEST_DECL(sifaddr, SIOCSIFADDR, sockaddr, \
- ((ESockAddress*) &ifreq.ifr_addr))
-#else
-#define IOCTL_SIFADDR_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_sifdstaddr *** */
-#if defined(SIOCSIFDSTADDR)
-#define IOCTL_SIFDSTADDR_FUNC_DECL \
- IOCTL_SET_REQUEST_DECL(sifdstaddr, SIOCSIFDSTADDR, sockaddr, \
- ((ESockAddress*) &ifreq.ifr_dstaddr))
-#else
-#define IOCTL_SIFDSTADDR_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_sifbrdaddr *** */
-#if defined(SIOCSIFBRDADDR)
-#define IOCTL_SIFBRDADDR_FUNC_DECL \
- IOCTL_SET_REQUEST_DECL(sifbrdaddr, SIOCSIFBRDADDR, sockaddr, \
- ((ESockAddress*) &ifreq.ifr_broadaddr))
-#else
-#define IOCTL_SIFBRDADDR_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_sifnetmask *** */
-#if defined(SIOCSIFNETMASK)
-#ifdef __linux__
-#define IOCTL_SIFNETMASK_FUNC_DECL \
- IOCTL_SET_REQUEST_DECL(sifnetmask, SIOCSIFNETMASK, sockaddr, \
- ((ESockAddress*) &ifreq.ifr_netmask))
-#else
-#define IOCTL_SIFNETMASK_FUNC_DECL \
- IOCTL_SET_REQUEST_DECL(sifnetmask, SIOCSIFNETMASK, sockaddr, \
- ((ESockAddress*) &ifreq.ifr_addr))
-#endif
-#else
-#define IOCTL_SIFNETMASK_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_sifmtu ***
- * On some platforms, MTU is an unsigned int
- */
-#if defined(SIOCSIFMTU)
-#define IOCTL_SIFMTU_FUNC_DECL \
- IOCTL_SET_REQUEST_DECL(sifmtu, SIOCSIFMTU, mtu, (int*) &ifreq.ifr_mtu)
-#else
-#define IOCTL_SIFMTU_FUNC_DECL
-#endif
-
-/* *** esock_ioctl_siftxqlen *** */
-#if defined(SIOCSIFTXQLEN)
-#define IOCTL_SIFTXQLEN_FUNC_DECL \
- IOCTL_SET_REQUEST_DECL(siftxqlen, SIOCSIFTXQLEN, txqlen, &ifreq.ifr_qlen)
-#else
-#define IOCTL_SIFTXQLEN_FUNC_DECL
-#endif
-
-#define IOCTL_SET_FUNCS \
- IOCTL_SIFADDR_FUNC_DECL \
- IOCTL_SIFDSTADDR_FUNC_DECL \
- IOCTL_SIFBRDADDR_FUNC_DECL \
- IOCTL_SIFNETMASK_FUNC_DECL \
- IOCTL_SIFMTU_FUNC_DECL \
- IOCTL_SIFTXQLEN_FUNC_DECL
-
-#define IOCTL_SET_REQUEST_DECL(OR, R, DF, UVP) \
- static \
- ERL_NIF_TERM esock_ioctl_##OR(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ERL_NIF_TERM ename, \
- ERL_NIF_TERM evalue) \
- { \
- ERL_NIF_TERM result; \
- struct ifreq ifreq; \
- char* ifn = NULL; \
- int nlen; \
- \
- SSDBG( descP, ("SOCKET", "esock_ioctl_" #OR " {%d} -> entry with" \
- "\r\n (e)Name: %T" \
- "\r\n (e)Value: %T" \
- "\r\n", descP->sock, ename, evalue) ); \
- \
- if (!esock_decode_string(env, ename, &ifn)) { \
- \
- SSDBG( descP, \
- ("SOCKET", "esock_ioctl_" #OR " {%d} -> failed decode name" \
- "\r\n", descP->sock) ); \
- \
- return enif_make_badarg(env); \
- } \
- \
- if (! decode_ioctl_##DF(env, descP, evalue, UVP)) { \
- \
- SSDBG( descP, \
- ("SOCKET", "esock_ioctl_" #OR " {%d} -> failed decode addr" \
- "\r\n", descP->sock) ); \
- \
- return esock_make_invalid(env, atom_##DF); \
- } \
- \
- nlen = esock_strnlen(ifn, IFNAMSIZ); \
- \
- sys_memset(ifreq.ifr_name, '\0', IFNAMSIZ); \
- sys_memcpy(ifreq.ifr_name, ifn, \
- (nlen >= IFNAMSIZ) ? IFNAMSIZ-1 : nlen); \
- \
- SSDBG( descP, \
- ("SOCKET", "esock_ioctl_" #OR " {%d} -> try ioctl\r\n", \
- descP->sock) ); \
- \
- if (ioctl(descP->sock, R, (char *) &ifreq) < 0) { \
- int saveErrno = sock_errno(); \
- ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno)); \
- \
- SSDBG( descP, \
- ("SOCKET", "esock_ioctl_" #OR " {%d} -> failure: " \
- "\r\n reason: %T (%d)" \
- "\r\n", descP->sock, reason, saveErrno) ); \
- \
- result = esock_make_error(env, reason); \
- \
- } else { \
- SSDBG( descP, \
- ("SOCKET", "esock_ioctl_" #OR " {%d} -> " \
- "addr successfully set\r\n", \
- descP->sock) ); \
- result = esock_atom_ok; \
- } \
- \
- FREE(ifn); \
- \
- return result; \
- \
- }
-
-IOCTL_SET_FUNCS
-#undef IOCTL_SET_FUNCS
-
-
-/* ===========================================================================
- * The "rest" of the implemented (ioctl) set requests
- *
- * These (set) requests could not be 'generated' by the macros above.
- */
-
-#if defined(SIOCSIFFLAGS)
-static
-ERL_NIF_TERM esock_ioctl_sifflags(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM ename,
- ERL_NIF_TERM eflags)
-{
- ERL_NIF_TERM result;
- struct ifreq ifreq;
- char* ifn = NULL;
- int nlen;
-
- SSDBG( descP, ("SOCKET", "esock_ioctl_sifflags {%d} -> entry with"
- "\r\n (e)Name: %T"
- "\r\n (e)Flags: %T"
- "\r\n", descP->sock, ename, eflags) );
-
- if (!esock_decode_string(env, ename, &ifn)) {
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_sifflags {%d} -> failed decode name"
- "\r\n", descP->sock) );
-
- return enif_make_badarg(env);
- }
-
- // Make sure the length of the string is valid!
- nlen = esock_strnlen(ifn, IFNAMSIZ);
-
- sys_memset(ifreq.ifr_name, '\0', IFNAMSIZ); // Just in case
- sys_memcpy(ifreq.ifr_name, ifn,
- (nlen >= IFNAMSIZ) ? IFNAMSIZ-1 : nlen);
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_sifflags {%d} -> try (get) ioctl\r\n",
- descP->sock) );
-
- if (ioctl(descP->sock, SIOCGIFFLAGS, (char *) &ifreq) < 0) {
- int saveErrno = sock_errno();
- ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_sifflags {%d} -> "
- "failure: failed reading *current* flags"
- "\r\n reason: %T (%d)"
- "\r\n", descP->sock, reason, saveErrno) );
-
- result = esock_make_error(env, reason);
-
- } else {
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_sifflags {%d} -> (local) update flags\r\n",
- descP->sock) );
-
- if (decode_ioctl_flags(env, descP, eflags, &ifreq.ifr_flags)) {
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_sifflags {%d} -> try (set) ioctl\r\n",
- descP->sock) );
-
- if (ioctl(descP->sock, SIOCSIFFLAGS, (char *) &ifreq) < 0) {
- int saveErrno = sock_errno();
- ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_sifflags {%d} -> failure: "
- "\r\n reason: %T (%d)"
- "\r\n", descP->sock, reason, saveErrno) );
-
- result = esock_make_error(env, reason);
-
- } else {
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_sifflags {%d} -> "
- "updated flags successfully set\r\n",
- descP->sock) );
- result = esock_atom_ok;
- }
-
- /* We know that if esock_decode_string is successful,
- * we have "some" form of string, and therefor memory
- * has been allocated (and need to be freed)... */
- FREE(ifn);
-
- } else {
- result = enif_make_badarg(env);
- }
- }
-
- SSDBG( descP,
- ("SOCKET", "esock_ioctl_sifflags {%d} -> done with result: "
- "\r\n %T"
- "\r\n",
- descP->sock, result) );
-
- return result;
-
-}
-#endif
-
-
-
-/* ===========================================================================
- * ioctl utility functions
- *
- */
-
-static
-ERL_NIF_TERM encode_ioctl_ifconf(ErlNifEnv* env,
- ESockDescriptor* descP,
- struct ifconf* ifcP)
-{
- ERL_NIF_TERM result;
- unsigned int len = ((ifcP == NULL) ? 0 :
- (ifcP->ifc_len / sizeof(struct ifreq)));
-
- SSDBG( descP,
- ("SOCKET", "encode_ioctl_ifconf -> entry (when len = %d)\r\n", len) );
-
- if (len > 0) {
- ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM));
- unsigned int i = 0;
- struct ifreq* p = ifcP->ifc_req;
-
- for (i = 0 ; i < len ; i++) {
- SSDBG( descP,
- ("SOCKET", "encode_ioctl_ifconf -> encode ifreq entry %d\r\n", i) );
- array[i] = encode_ioctl_ifconf_ifreq(env, descP, &p[i]);
- }
-
- SSDBG( descP,
- ("SOCKET", "encode_ioctl_ifconf -> all entries encoded\r\n", i) );
-
- result = esock_make_ok2(env, MKLA(env, array, len));
- FREE(array);
-
- } else {
-
- result = esock_make_ok2(env, MKEL(env));
-
- }
-
- return result;
-}
-
-
-#if defined(SIOCGIFMAP) && defined(ESOCK_USE_IFMAP)
-static
-ERL_NIF_TERM encode_ioctl_ifrmap(ErlNifEnv* env,
- ESockDescriptor* descP,
- struct ifmap* mapP)
-{
- ERL_NIF_TERM mapKeys[] = {atom_mem_start,
- atom_mem_end,
- atom_base_addr,
- atom_irq,
- atom_dma,
- atom_port};
- ERL_NIF_TERM mapVals[] = {MKUL(env, mapP->mem_start),
- MKUL(env, mapP->mem_end),
- MKUI(env, mapP->base_addr),
- MKUI(env, mapP->irq),
- MKUI(env, mapP->dma),
- MKUI(env, mapP->port)};
- unsigned int numMapKeys = NUM(mapKeys);
- unsigned int numMapVals = NUM(mapVals);
- ERL_NIF_TERM emap;
-
- ESOCK_ASSERT( numMapVals == numMapKeys );
- ESOCK_ASSERT( MKMA(env, mapKeys, mapVals, numMapKeys, &emap) );
-
- SSDBG( descP, ("SOCKET", "encode_ioctl_ifrmap -> done with"
- "\r\n Map: %T"
- "\r\n", emap) );
-
- return esock_make_ok2(env, emap);;
}
-#endif
-
-#if defined(SIOCGIFHWADDR) && defined(ESOCK_USE_HWADDR)
-static
-ERL_NIF_TERM encode_ioctl_hwaddr(ErlNifEnv* env,
- ESockDescriptor* descP,
- struct sockaddr* addrP)
-{
- ERL_NIF_TERM eaddr;
- SOCKLEN_T sz = sizeof(struct sockaddr);
-
- esock_encode_hwsockaddr(env, addrP, sz, &eaddr);
-
- SSDBG( descP, ("SOCKET", "encode_ioctl_ifraddr -> done with"
- "\r\n Sock Addr: %T"
- "\r\n", eaddr) );
-
- return esock_make_ok2(env, eaddr);;
-}
-#endif
-
-
-static
-ERL_NIF_TERM encode_ioctl_ifraddr(ErlNifEnv* env,
- ESockDescriptor* descP,
- struct sockaddr* addrP)
-{
- ERL_NIF_TERM eaddr;
-
- esock_encode_sockaddr(env, (ESockAddress*) addrP, -1, &eaddr);
-
- SSDBG( descP, ("SOCKET", "encode_ioctl_ifraddr -> done with"
- "\r\n Sock Addr: %T"
- "\r\n", eaddr) );
-
- return esock_make_ok2(env, eaddr);;
-}
-
-
-static
-ERL_NIF_TERM encode_ioctl_flags(ErlNifEnv* env,
- ESockDescriptor* descP,
- short flags)
-{
- int i, flag, num = NUM(ioctl_flags);
- ERL_NIF_TERM eflags, eflag;
- SocketTArray ta = TARRAY_CREATE(20); // Just to be on the safe side
-
- if (flags == 0) {
- eflags = MKEL(env);
- } else {
- for (i = 0; (i < num) && (flags != 0); i++) {
- flag = ioctl_flags[i].flag;
- if ((flag != 0) && ((flags & flag) == flag)) {
- eflag = *(ioctl_flags[i].name);
- flags &= ~flag;
-
- SSDBG( descP, ("SOCKET", "encode_ioctl_flags {%d} -> "
- "\r\n i: %d"
- "\r\n found flag: %T (%d)"
- "\r\n remaining flags: %d"
- "\r\n", descP->sock, i, eflag, flag, flags) );
-
- TARRAY_ADD(ta, eflag);
- }
- }
- if (flags != 0) {
-
- SSDBG( descP, ("SOCKET", "encode_ioctl_flags {%d} -> unknown flag(s): %d"
- "\r\n", descP->sock, flags) );
-
- TARRAY_ADD(ta, MKI(env, flags));
- }
-
- TARRAY_TOLIST(ta, env, &eflags);
- }
-
-
- SSDBG( descP, ("SOCKET", "encode_ioctl_flags -> done with"
- "\r\n Flags: %T (%d)"
- "\r\n", eflags, flags) );
-
- return esock_make_ok2(env, eflags);;
-}
-
-
-static
-BOOLEAN_T decode_ioctl_sockaddr(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eaddr,
- ESockAddress* addr)
-{
- SOCKLEN_T addrLen;
- BOOLEAN_T result;
-
- result = esock_decode_sockaddr(env, eaddr, (ESockAddress*) addr, &addrLen);
-
- VOID(addrLen);
-
- SSDBG( descP,
- ("SOCKET", "esock_decode_ioctl_sockaddr {%d} -> decode result: %s"
- "\r\n", descP->sock, B2S(result)) );
-
- return result;
-}
-
-
-static
-BOOLEAN_T decode_ioctl_mtu(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM emtu,
- int* mtu)
-{
- BOOLEAN_T result;
-
- if (! GET_INT(env, emtu, mtu)) {
- result = FALSE;
- } else {
- result = TRUE;
- }
-
- SSDBG( descP,
- ("SOCKET", "esock_decode_ioctl_mtu {%d} -> decode result: %s"
- "\r\n", descP->sock, B2S(result)) );
-
- return result;
-}
-
-
-#if defined(SIOCSIFTXQLEN)
-static
-BOOLEAN_T decode_ioctl_txqlen(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM etxqlen,
- int* txqlen)
-{
- return decode_ioctl_ivalue(env, descP, etxqlen, txqlen);
-}
-#endif
-
-/* All uses of the function should be added. For instance:
- * #if defined(SIOCGIFTXQLEN) || defined(FOOBAR) || defined(YXA)
- */
-#if defined(SIOCGIFTXQLEN)
-static
-BOOLEAN_T decode_ioctl_ivalue(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eivalue,
- int* ivalue)
-{
- BOOLEAN_T result;
-
- if (! GET_INT(env, eivalue, ivalue)) {
- result = FALSE;
- } else {
- result = TRUE;
- }
-
- SSDBG( descP,
- ("SOCKET", "esock_decode_ioctl_ivalue {%d} -> decode result: %s"
- "\r\n", descP->sock, B2S(result)) );
-
- return result;
-}
-#endif
-
-
-static
-BOOLEAN_T decode_ioctl_flags(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eflags,
- short* flags)
-{
- ERL_NIF_TERM key, value;
- ErlNifMapIterator iter;
- int tmpFlags = (int) *flags; // Current value
- int flag;
-
- SSDBG( descP,
- ("SOCKET", "decode_ioctl_flags {%d} -> entry with"
- "\r\n flags: %d"
- "\r\n",
- descP->sock, tmpFlags) );
-
- enif_map_iterator_create(env, eflags, &iter, ERL_NIF_MAP_ITERATOR_FIRST);
-
- while (enif_map_iterator_get_pair(env, &iter, &key, &value)) {
-
- /* Convert key (eflag) to int */
- if (! GET_INT(env, key, &flag)) {
- enif_map_iterator_destroy(env, &iter);
- return FALSE;
- }
-
- // Update flag
- if (COMPARE(value, esock_atom_true) == 0) {
- SSDBG( descP,
- ("SOCKET", "decode_ioctl_flags {%d} -> set %d\r\n",
- descP->sock, flag) );
- tmpFlags |= flag;
- } else {
- SSDBG( descP,
- ("SOCKET", "decode_ioctl_flags {%d} -> reset %d\r\n",
- descP->sock, flag) );
- tmpFlags &= ~flag;
- }
-
- enif_map_iterator_next(env, &iter);
- }
-
- enif_map_iterator_destroy(env, &iter);
-
- SSDBG( descP,
- ("SOCKET", "decode_ioctl_flags {%d} -> done with"
- "\r\n (new) flags: %d"
- "\r\n",
- descP->sock, tmpFlags) );
-
- *flags = (short) tmpFlags;
-
- return TRUE;
-}
-
-
-static
-ERL_NIF_TERM encode_ioctl_ivalue(ErlNifEnv* env,
- ESockDescriptor* descP,
- int ivalue)
-{
- ERL_NIF_TERM eivalue = MKI(env, ivalue);
-
- SSDBG( descP, ("SOCKET", "encode_ioctl_ivalue -> done with"
- "\r\n iValue: %T (%d)"
- "\r\n", eivalue, ivalue) );
-
- return esock_make_ok2(env, eivalue);;
-}
-
-static
-ERL_NIF_TERM encode_ioctl_ifconf_ifreq(ErlNifEnv* env,
- ESockDescriptor* descP,
- struct ifreq* ifrP)
-{
- ERL_NIF_TERM ename, eaddr;
-
- ESOCK_ASSERT( ifrP != NULL );
-
- SSDBG( descP, ("SOCKET", "encode_ioctl_ifconf_ifreq -> encode name\r\n") );
- ename = encode_ioctl_ifreq_name(env, ifrP->ifr_name);
-
- SSDBG( descP, ("SOCKET", "encode_ioctl_ifconf_ifreq -> encode sockaddr\r\n") );
- eaddr = encode_ioctl_ifreq_sockaddr(env, &ifrP->ifr_addr);
-
- SSDBG( descP, ("SOCKET", "encode_ioctl_ifconf_ifreq -> make ifreq map with"
- "\r\n Name: %T"
- "\r\n Sock Addr: %T"
- "\r\n", ename, eaddr) );
- return make_ifreq(env, ename, esock_atom_addr, eaddr);
-}
-
-static
-ERL_NIF_TERM encode_ioctl_ifreq_name(ErlNifEnv* env,
- char* name)
-{
- return ((name == NULL) ? esock_atom_undefined : MKS(env, name));
-}
-
-static
-ERL_NIF_TERM encode_ioctl_ifreq_sockaddr(ErlNifEnv* env, struct sockaddr* sa)
-{
- ERL_NIF_TERM esa;
-
- if (sa != NULL) {
-
- esock_encode_sockaddr(env, (ESockAddress*) sa, -1, &esa);
-
- } else {
-
- esa = esock_atom_undefined;
-
- }
-
- return esa;
-}
-
-
-/* The ifreq structure *always* contain a name
- * and *one* other element. The second element
- * depend on the ioctl request.
- */
-static
-ERL_NIF_TERM make_ifreq(ErlNifEnv* env,
- ERL_NIF_TERM name,
- ERL_NIF_TERM key2,
- ERL_NIF_TERM val2)
-{
- ERL_NIF_TERM keys[2];
- ERL_NIF_TERM vals[2];
- ERL_NIF_TERM res;
-
- keys[0] = esock_atom_name;
- vals[0] = name;
-
- keys[1] = key2;
- vals[1] = val2;
-
- ESOCK_ASSERT( MKMA(env, keys, vals, NUM(keys), &res) );
-
- return res;
-}
-
-#endif // #ifndef __WIN32__
@@ -14474,7 +9943,8 @@ ERL_NIF_TERM make_ifreq(ErlNifEnv* env,
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
- * Operation (atom) - What kind of operation (accept, send, ...) is to be cancelled
+ * Operation (atom) - What kind of operation (accept, send, ...)
+ * is to be cancelled
* Ref (ref) - Unique id for the operation
*/
static
@@ -14482,9 +9952,6 @@ ERL_NIF_TERM nif_cancel(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#ifdef __WIN32__
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
ESockDescriptor* descP;
ERL_NIF_TERM op, sockRef, opRef;
@@ -14507,11 +9974,9 @@ ERL_NIF_TERM nif_cancel(ErlNifEnv* env,
return esock_cancel(env, descP, op, sockRef, opRef);
-#endif // #ifdef __WIN32__ #else
}
-#ifndef __WIN32__
static
ERL_NIF_TERM esock_cancel(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -14519,7 +9984,8 @@ ERL_NIF_TERM esock_cancel(ErlNifEnv* env,
ERL_NIF_TERM sockRef,
ERL_NIF_TERM opRef)
{
- int cmp;
+ ERL_NIF_TERM result;
+ int cmp;
/* <KOLLA>
*
@@ -14531,43 +9997,78 @@ ERL_NIF_TERM esock_cancel(ErlNifEnv* env,
*/
/* Hand crafted binary search */
- if ((cmp = COMPARE(op, esock_atom_recvmsg)) == 0)
- return esock_cancel_recv(env, descP, sockRef, opRef);
+ if ((cmp = COMPARE(op, esock_atom_recvmsg)) == 0) {
+ MLOCK(descP->readMtx);
+ result = ESOCK_IO_CANCEL_RECV(env, descP, sockRef, opRef);
+ MUNLOCK(descP->readMtx);
+ return result;
+ }
if (cmp < 0) {
- if ((cmp = COMPARE(op, esock_atom_recv)) == 0)
- return esock_cancel_recv(env, descP, sockRef, opRef);
+ if ((cmp = COMPARE(op, esock_atom_recv)) == 0) {
+ MLOCK(descP->readMtx);
+ result = ESOCK_IO_CANCEL_RECV(env, descP, sockRef, opRef);
+ MUNLOCK(descP->readMtx);
+ return result;
+ }
if (cmp < 0) {
- if (COMPARE(op, esock_atom_connect) == 0)
- return esock_cancel_connect(env, descP, opRef);
- if (COMPARE(op, esock_atom_accept) == 0)
- return esock_cancel_accept(env, descP, sockRef, opRef);
+ if (COMPARE(op, esock_atom_connect) == 0) {
+ MLOCK(descP->writeMtx);
+ result = ESOCK_IO_CANCEL_CONNECT(env, descP, opRef);
+ MUNLOCK(descP->writeMtx);
+ return result;
+ }
+ if (COMPARE(op, esock_atom_accept) == 0) {
+ MLOCK(descP->readMtx);
+ result = ESOCK_IO_CANCEL_ACCEPT(env, descP, sockRef, opRef);
+ MUNLOCK(descP->readMtx);
+ return result;
+ }
} else {
- if (COMPARE(op, esock_atom_recvfrom) == 0)
- return esock_cancel_recv(env, descP, sockRef, opRef);
+ if (COMPARE(op, esock_atom_recvfrom) == 0) {
+ MLOCK(descP->readMtx);
+ result = ESOCK_IO_CANCEL_RECV(env, descP, sockRef, opRef);
+ MUNLOCK(descP->readMtx);
+ return result;
+ }
}
} else {
- if ((cmp = COMPARE(op, esock_atom_sendmsg)) == 0)
- return esock_cancel_send(env, descP, sockRef, opRef);
+ if ((cmp = COMPARE(op, esock_atom_sendmsg)) == 0) {
+ MLOCK(descP->writeMtx);
+ result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef);
+ MUNLOCK(descP->writeMtx);
+ return result;
+ }
if (cmp < 0) {
- if (COMPARE(op, esock_atom_send) == 0)
- return esock_cancel_send(env, descP, sockRef, opRef);
- if (COMPARE(op, atom_sendfile) == 0)
- return esock_cancel_send(env, descP, sockRef, opRef);
+ if (COMPARE(op, esock_atom_send) == 0) {
+ MLOCK(descP->writeMtx);
+ result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef);
+ MUNLOCK(descP->writeMtx);
+ return result;
+ }
+ if (COMPARE(op, esock_atom_sendfile) == 0) {
+ MLOCK(descP->writeMtx);
+ result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef);
+ MUNLOCK(descP->writeMtx);
+ return result;
+ }
} else {
- if (COMPARE(op, esock_atom_sendto) == 0)
- return esock_cancel_send(env, descP, sockRef, opRef);
+ if (COMPARE(op, esock_atom_sendto) == 0) {
+ MLOCK(descP->writeMtx);
+ result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef);
+ MUNLOCK(descP->writeMtx);
+ return result;
+ }
}
}
{
- ERL_NIF_TERM result;
const char *reason;
MLOCK(descP->readMtx);
MLOCK(descP->writeMtx);
if (! IS_OPEN(descP->readState)) {
- result = esock_make_error(env, atom_closed);
+ result = esock_make_error_closed(env);
reason = "closed";
} else {
result = enif_make_badarg(env);
@@ -14586,430 +10087,10 @@ ERL_NIF_TERM esock_cancel(ErlNifEnv* env,
return result;
}
}
-#endif // #ifndef __WIN32__
-
-/* *** esock_cancel_connect ***
- *
- *
- */
#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_cancel_connect(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef)
-{
- ERL_NIF_TERM res;
- ErlNifPid self;
-
- ESOCK_ASSERT( enif_self(env, &self) != NULL );
-
- MLOCK(descP->writeMtx);
-
- if (! IS_OPEN(descP->writeState)) {
-
- res = esock_make_error(env, atom_closed);
-
- } else if ((descP->connectorP == NULL) ||
- (COMPARE_PIDS(&self, &descP->connector.pid) != 0) ||
- (COMPARE(opRef, descP->connector.ref) != 0)) {
-
- res = esock_atom_not_found;
-
- } else {
-
- res = esock_cancel_write_select(env, descP, opRef);
- requestor_release("esock_cancel_connect",
- env, descP, &descP->connector);
- descP->connectorP = NULL;
- descP->writeState &= ~ESOCK_STATE_CONNECTING;
- }
-
- SSDBG( descP,
- ("SOCKET",
- "esock_cancel_connect {%d,0x%X} ->"
- "\r\n opRef: %T"
- "\r\n res: %T"
- "\r\n",
- descP->sock, descP->writeState,
- opRef, res) );
-
- MUNLOCK(descP->writeMtx);
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** esock_cancel_accept ***
- *
- * We have two different cases:
- * *) Its the current acceptor
- * Cancel the select!
- * We need to activate one of the waiting acceptors.
- * *) Its one of the acceptors ("waiting") in the queue
- * Simply remove the acceptor from the queue.
- *
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_cancel_accept(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM opRef)
-{
- ERL_NIF_TERM res;
-
- MLOCK(descP->readMtx);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_cancel_accept(%T), {%d,0x%X} ->"
- "\r\n opRef: %T"
- "\r\n %s"
- "\r\n",
- sockRef, descP->sock, descP->readState,
- opRef,
- ((descP->currentAcceptorP == NULL)
- ? "without acceptor" : "with acceptor")) );
-
- if (! IS_OPEN(descP->readState)) {
-
- res = esock_make_error(env, atom_closed);
-
- } else if (descP->currentAcceptorP == NULL) {
-
- res = esock_atom_not_found;
-
- } else {
- ErlNifPid self;
-
- ESOCK_ASSERT( enif_self(env, &self) != NULL );
-
- if (COMPARE_PIDS(&self, &descP->currentAcceptor.pid) == 0) {
- if (COMPARE(opRef, descP->currentAcceptor.ref) == 0)
- res = esock_cancel_accept_current(env, descP, sockRef);
- else
- res = esock_atom_not_found;
- } else {
- res = esock_cancel_accept_waiting(env, descP, opRef, &self);
- }
- }
-
- SSDBG( descP,
- ("SOCKET", "esock_cancel_accept(%T) -> done with result:"
- "\r\n %T"
- "\r\n", sockRef, res) );
-
- MUNLOCK(descP->readMtx);
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* The current acceptor process has an ongoing select we first must
- * cancel. Then we must re-activate the "first" (the first
- * in the acceptor queue).
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_cancel_accept_current(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef)
-{
- ERL_NIF_TERM res;
-
- ESOCK_ASSERT( DEMONP("esock_cancel_accept_current -> current acceptor",
- env, descP, &descP->currentAcceptor.mon) == 0);
- MON_INIT(&descP->currentAcceptor.mon);
- res = esock_cancel_read_select(env, descP, descP->currentAcceptor.ref);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_cancel_accept_current(%T) {%d} -> cancel res: %T"
- "\r\n", sockRef, descP->sock, res) );
-
- if (!activate_next_acceptor(env, descP, sockRef)) {
-
- SSDBG( descP,
- ("SOCKET",
- "esock_cancel_accept_current(%T) {%d} -> "
- "no more acceptors\r\n",
- sockRef, descP->sock) );
-
- descP->readState &= ~ESOCK_STATE_ACCEPTING;
-
- descP->currentAcceptorP = NULL;
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* These processes have not performed a select, so we can simply
- * remove them from the acceptor queue.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_cancel_accept_waiting(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef,
- const ErlNifPid* selfP)
-{
- /* unqueue request from (acceptor) queue */
-
- if (acceptor_unqueue(env, descP, &opRef, selfP)) {
- return esock_atom_ok;
- } else {
- return esock_atom_not_found;
- }
-}
-#endif // #ifndef __WIN32__
-
-
-
-/* *** esock_cancel_send ***
- *
- * Cancel a send operation.
- * Its either the current writer or one of the waiting writers.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_cancel_send(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM opRef)
-{
- ERL_NIF_TERM res;
-
- MLOCK(descP->writeMtx);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_cancel_send(%T), {%d,0x%X} -> entry with"
- "\r\n opRef: %T"
- "\r\n %s"
- "\r\n",
- sockRef, descP->sock, descP->writeState,
- opRef,
- ((descP->currentWriterP == NULL)
- ? "without writer" : "with writer")) );
-
- if (! IS_OPEN(descP->writeState)) {
-
- res = esock_make_error(env, atom_closed);
-
- } else if (descP->currentWriterP == NULL) {
-
- res = esock_atom_not_found;
-
- } else {
- ErlNifPid self;
-
- ESOCK_ASSERT( enif_self(env, &self) != NULL );
-
- if (COMPARE_PIDS(&self, &descP->currentWriter.pid) == 0) {
- if (COMPARE(opRef, descP->currentWriter.ref) == 0)
- res = esock_cancel_send_current(env, descP, sockRef);
- else
- res = esock_atom_not_found;
- } else {
- res = esock_cancel_send_waiting(env, descP, opRef, &self);
- }
- }
-
- SSDBG( descP,
- ("SOCKET", "esock_cancel_send(%T) {%d} -> done with result:"
- "\r\n %T"
- "\r\n", sockRef, descP->sock, res) );
-
- MUNLOCK(descP->writeMtx);
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-
-/* The current writer process has an ongoing select we first must
- * cancel. Then we must re-activate the "first" (the first
- * in the writer queue).
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_cancel_send_current(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef)
-{
- ERL_NIF_TERM res;
-
- ESOCK_ASSERT( DEMONP("esock_cancel_send_current -> current writer",
- env, descP, &descP->currentWriter.mon) == 0);
- res = esock_cancel_write_select(env, descP, descP->currentWriter.ref);
-
- SSDBG( descP,
- ("SOCKET", "esock_cancel_send_current(%T) {%d} -> cancel res: %T"
- "\r\n", sockRef, descP->sock, res) );
-
- if (!activate_next_writer(env, descP, sockRef)) {
- SSDBG( descP,
- ("SOCKET",
- "esock_cancel_send_current(%T) {%d} -> no more writers"
- "\r\n", sockRef, descP->sock) );
-
- descP->currentWriterP = NULL;
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* These processes have not performed a select, so we can simply
- * remove them from the writer queue.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_cancel_send_waiting(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef,
- const ErlNifPid* selfP)
-{
- /* unqueue request from (writer) queue */
-
- if (writer_unqueue(env, descP, &opRef, selfP)) {
- return esock_atom_ok;
- } else {
- return esock_atom_not_found;
- }
-}
-#endif // #ifndef __WIN32__
-
-
-
-/* *** esock_cancel_recv ***
- *
- * Cancel a read operation.
- * Its either the current reader or one of the waiting readers.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_cancel_recv(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM opRef)
-{
- ERL_NIF_TERM res;
-
- MLOCK(descP->readMtx);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_cancel_recv(%T), {%d,0x%X} -> entry with"
- "\r\n opRef: %T"
- "\r\n %s"
- "\r\n",
- sockRef, descP->sock, descP->readState,
- opRef,
- ((descP->currentReaderP == NULL)
- ? "without reader" : "with reader")) );
-
- if (! IS_OPEN(descP->readState)) {
-
- res = esock_make_error(env, atom_closed);
-
- } else if (descP->currentReaderP == NULL) {
-
- res = esock_atom_not_found;
-
- } else {
- ErlNifPid self;
-
- ESOCK_ASSERT( enif_self(env, &self) != NULL );
-
- if (COMPARE_PIDS(&self, &descP->currentReader.pid) == 0) {
- if (COMPARE(opRef, descP->currentReader.ref) == 0)
- res = esock_cancel_recv_current(env, descP, sockRef);
- else
- res = esock_atom_not_found;
- } else {
- res = esock_cancel_recv_waiting(env, descP, opRef, &self);
- }
- }
-
- SSDBG( descP,
- ("SOCKET", "esock_cancel_recv(%T) {%d} -> done with result:"
- "\r\n %T"
- "\r\n", sockRef, descP->sock, res) );
-
- MUNLOCK(descP->readMtx);
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* The current reader process has an ongoing select we first must
- * cancel. Then we must re-activate the "first" (the first
- * in the reader queue).
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_cancel_recv_current(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef)
-{
- ERL_NIF_TERM res;
-
- ESOCK_ASSERT( DEMONP("esock_cancel_recv_current -> current reader",
- env, descP, &descP->currentReader.mon) == 0);
- res = esock_cancel_read_select(env, descP, descP->currentReader.ref);
-
- SSDBG( descP,
- ("SOCKET", "esock_cancel_recv_current(%T) {%d} -> cancel res: %T"
- "\r\n", sockRef, descP->sock, res) );
-
- if (! activate_next_reader(env, descP, sockRef)) {
- SSDBG( descP,
- ("SOCKET",
- "esock_cancel_recv_current(%T) {%d} -> no more readers"
- "\r\n", sockRef, descP->sock) );
-
- descP->currentReaderP = NULL;
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* These processes have not performed a select, so we can simply
- * remove them from the reader queue.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM esock_cancel_recv_waiting(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef,
- const ErlNifPid* selfP)
-{
- /* unqueue request from (reader) queue */
-
- if (reader_unqueue(env, descP, &opRef, selfP)) {
- return esock_atom_ok;
- } else {
- return esock_atom_not_found;
- }
-}
-#endif // #ifndef __WIN32__
-
-
-
-#ifndef __WIN32__
-static
+extern
ERL_NIF_TERM esock_cancel_read_select(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM opRef)
@@ -15022,10 +10103,10 @@ ERL_NIF_TERM esock_cancel_read_select(ErlNifEnv* env,
#ifndef __WIN32__
-static
+extern
ERL_NIF_TERM esock_cancel_write_select(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM opRef)
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef)
{
return esock_cancel_mode_select(env, descP, opRef,
ERL_NIF_SELECT_WRITE,
@@ -15035,7 +10116,7 @@ ERL_NIF_TERM esock_cancel_write_select(ErlNifEnv* env,
#ifndef __WIN32__
-static
+extern
ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM opRef,
@@ -15053,7 +10134,7 @@ ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv* env,
return esock_atom_ok;
} else {
/* Has already sent the message */
- return esock_atom_select_sent;
+ return esock_make_error(env, esock_atom_select_sent);
}
} else {
/* Stopped? */
@@ -15062,7 +10143,7 @@ ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv* env,
"esock_cancel_mode_select {%d} -> failed: %d (0x%lX)"
"\r\n", descP->sock, selectRes, selectRes) );
- return esock_atom_not_found;
+ return esock_make_error(env, esock_atom_not_found);
}
}
#endif // #ifndef __WIN32__
@@ -15074,1654 +10155,95 @@ ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv* env,
* ----------------------------------------------------------------------
*/
-/* *** send_check_writer ***
- *
- * Checks if we have a current writer and if that is us.
- * If not (current writer), then we must be made to wait
- * for our turn. This is done by pushing us unto the writer queue.
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T send_check_writer(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM ref,
- ERL_NIF_TERM* checkResult)
-{
- if (descP->currentWriterP != NULL) {
- ErlNifPid caller;
-
- ESOCK_ASSERT( enif_self(env, &caller) != NULL );
-
- if (COMPARE_PIDS(&descP->currentWriter.pid, &caller) != 0) {
- /* Not the "current writer", so (maybe) push onto queue */
-
- SSDBG( descP,
- ("SOCKET",
- "send_check_writer {%d} -> not (current) writer"
- "\r\n ref: %T"
- "\r\n", descP->sock, ref) );
-
- if (! writer_search4pid(env, descP, &caller)) {
- writer_push(env, descP, caller, ref);
- *checkResult = atom_select;
- } else {
- /* Writer already in queue */
- *checkResult = esock_raise_invalid(env, atom_state);
- }
-
- SSDBG( descP,
- ("SOCKET",
- "send_check_writer {%d} -> queue (push) result: %T\r\n"
- "\r\n ref: %T"
- "\r\n", descP->sock, *checkResult, ref) );
-
- return FALSE;
- }
- }
-
- // Does not actually matter in this case, but ...
- *checkResult = esock_atom_ok;
-
- return TRUE;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** send_check_result ***
- *
- * Check the result of a socket send (send, sendto and sendmsg) call.
- * If a "complete" send has been made, the next (waiting) writer will be
- * scheduled (if there is one).
- * If we did not manage to send the entire package, make another select,
- * so that we can be informed when we can make another try (to send the rest),
- * and return with the amount we actually managed to send (its up to the caller
- * (that is the erlang code) to figure out hust much is left to send).
- * If the write fail, we give up and return with the appropriate error code.
- *
- * What about the remaining writers!!
- *
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM send_check_result(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t send_result,
- ssize_t dataSize,
- BOOLEAN_T dataInTail,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef)
+extern
+BOOLEAN_T esock_encode_cmsg(ErlNifEnv* env,
+ int level,
+ int type,
+ unsigned char* dataP,
+ size_t dataLen,
+ ERL_NIF_TERM* eType,
+ ERL_NIF_TERM* eData)
{
- ERL_NIF_TERM res;
- BOOLEAN_T send_error;
- int err;
-
- send_error = ESOCK_IS_ERROR(send_result);
- err = send_error ? sock_errno() : 0;
-
- SSDBG( descP,
- ("SOCKET", "send_check_result(%T) {%d} -> entry with"
- "\r\n send_result: %ld"
- "\r\n dataSize: %ld"
- "\r\n err: %d"
- "\r\n sendRef: %T"
- "\r\n", sockRef, descP->sock,
- (long) send_result, (long) dataSize, err, sendRef) );
-
- if (send_error) {
- /* Some kind of send failure - check what kind */
- if ((err != EAGAIN) && (err != EINTR)) {
- res = send_check_fail(env, descP, err, sockRef);
- } else {
- /* Ok, try again later */
-
- SSDBG( descP,
- ("SOCKET",
- "send_check_result(%T) {%d} -> try again"
- "\r\n", sockRef, descP->sock) );
-
- res = send_check_retry(env, descP, -1, sockRef, sendRef);
- }
- } else {
- ssize_t written = send_result;
- ESOCK_ASSERT( dataSize >= written );
-
- if (written < dataSize) {
- /* Not the entire package */
- SSDBG( descP,
- ("SOCKET",
- "send_check_result(%T) {%d} -> "
- "not entire package written (%d of %d)"
- "\r\n", sockRef, descP->sock,
- written, dataSize) );
-
- res = send_check_retry(env, descP, written, sockRef, sendRef);
- } else if (dataInTail) {
- /* Not the entire package */
- SSDBG( descP,
- ("SOCKET",
- "send_check_result(%T) {%d} -> "
- "not entire package written (%d but data in tail)"
- "\r\n", sockRef, descP->sock,
- written) );
-
- res =
- send_check_retry(env, descP, written, sockRef,
- esock_atom_iov);
- } else {
- res = send_check_ok(env, descP, written, sockRef);
- }
- }
-
- SSDBG( descP,
- ("SOCKET",
- "send_check_result(%T) {%d} -> done:"
- "\r\n res: %T"
- "\r\n", sockRef, descP->sock,
- res) );
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** send_check_ok ***
- *
- * Processing done upon successful send.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM send_check_ok(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t written,
- ERL_NIF_TERM sockRef)
-{
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_write_pkg, &descP->writePkgCnt, 1);
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_write_byte, &descP->writeByteCnt, written);
- descP->writePkgMaxCnt += written;
- if (descP->writePkgMaxCnt > descP->writePkgMax)
- descP->writePkgMax = descP->writePkgMaxCnt;
- descP->writePkgMaxCnt = 0;
-
- SSDBG( descP,
- ("SOCKET", "send_check_ok(%T) {%d} -> "
- "everything written (%ld) - done\r\n",
- sockRef, descP->sock, written) );
-
- if (descP->currentWriterP != NULL) {
- ESOCK_ASSERT( DEMONP("send_check_ok -> current writer",
- env, descP, &descP->currentWriter.mon) == 0);
- }
- /*
- * Ok, this write is done maybe activate the next (if any)
- */
- if (!activate_next_writer(env, descP, sockRef)) {
-
- SSDBG( descP,
- ("SOCKET", "send_check_ok(%T) {%d} -> no more writers\r\n",
- sockRef, descP->sock) );
-
- descP->currentWriterP = NULL;
- }
-
- return esock_atom_ok;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** send_check_failure ***
- *
- * Processing done upon failed send.
- * An actual failure - we (and everyone waiting) give up.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM send_check_fail(ErlNifEnv* env,
- ESockDescriptor* descP,
- int saveErrno,
- ERL_NIF_TERM sockRef)
-{
- ERL_NIF_TERM reason;
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_write_fails, &descP->writeFails, 1);
-
- SSDBG( descP, ("SOCKET", "send_check_fail(%T) {%d} -> error: %d\r\n",
- sockRef, descP->sock, saveErrno) );
-
- reason = MKA(env, erl_errno_id(saveErrno));
+ const ESockCmsgSpec *cmsgTable;
+ size_t num;
- if (saveErrno != EINVAL) {
+ if ((cmsgTable = esock_lookup_cmsg_table(level, &num)) != NULL) {
+ size_t n;
- /*
- * We assume that anything other then einval (invalid input)
- * is basically fatal (=> all waiting sends are aborted)
+ /* Linear search for type number in level table
*/
+ for (n = 0; n < num; n++) {
+ if (cmsgTable[n].type == type) {
+ /* Found the type number in the level table;
+ * return the symbolic type (atom)
+ * and try to encode the data
+ */
- if (descP->currentWriterP != NULL) {
-
- requestor_release("send_check_fail",
- env, descP, &descP->currentWriter);
-
- send_error_waiting_writers(env, descP, sockRef, reason);
-
- descP->currentWriterP = NULL;
- }
- }
- return esock_make_error(env, reason);
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** send_error_waiting_writers ***
- *
- * Process all waiting writers when a fatal error has occurred.
- * All waiting writers will be "aborted", that is a
- * nif_abort message will be sent (with ref and reason).
- */
-#ifndef __WIN32__
-static
-void send_error_waiting_writers(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM reason)
-{
- ESockRequestor req;
-
- req.env = NULL; /* read by writer_pop before free */
- while (writer_pop(env, descP, &req)) {
- SSDBG( descP,
- ("SOCKET",
- "send_error_waiting_writers(%T) {%d} -> abort"
- "\r\n pid: %T"
- "\r\n reason: %T"
- "\r\n",
- sockRef, descP->sock, &req.pid, reason) );
-
- esock_send_abort_msg(env, descP, sockRef, &req, reason);
-
- (void) DEMONP("send_error_waiting_writers -> pop'ed writer",
- env, descP, &req.mon);
- }
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** send_check_retry ***
- *
- * Processing done upon incomplete or blocked send.
- *
- * We failed to write the *entire* packet (anything less
- * then size of the packet, which is 0 <= written < sizeof
- * packet, so schedule the rest for later.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM send_check_retry(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t written,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM sendRef)
-{
- int sres;
- ERL_NIF_TERM res;
-
- SSDBG( descP,
- ("SOCKET",
- "send_check_retry(%T) {%d} -> %ld"
- "\r\n", sockRef, descP->sock, (long) written) );
-
- if (written >= 0) {
- descP->writePkgMaxCnt += written;
-
- if (descP->type != SOCK_STREAM) {
- /* Partial write for packet oriented socket
- * - done with packet
- */
- if (descP->writePkgMaxCnt > descP->writePkgMax)
- descP->writePkgMax = descP->writePkgMaxCnt;
- descP->writePkgMaxCnt = 0;
-
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_write_pkg, &descP->writePkgCnt, 1);
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_write_byte, &descP->writeByteCnt, written);
-
- if (descP->currentWriterP != NULL) {
- ESOCK_ASSERT( DEMONP("send_check_retry -> current writer",
- env, descP,
- &descP->currentWriter.mon) == 0);
- }
- /*
- * Ok, this write is done maybe activate the next (if any)
- */
- if (!activate_next_writer(env, descP, sockRef)) {
-
- SSDBG( descP,
- ("SOCKET",
- "send_check_retry(%T) {%d} -> no more writers\r\n",
- sockRef, descP->sock) );
-
- descP->currentWriterP = NULL;
- }
-
- return esock_make_ok2(env, MKI64(env, written));
- } /* else partial write for stream socket */
- } /* else send would have blocked */
-
- /* Register this process as current writer */
-
- if (descP->currentWriterP == NULL) {
- /* Register writer as current */
-
- ESOCK_ASSERT( enif_self(env, &descP->currentWriter.pid) != NULL );
- ESOCK_ASSERT( MONP("send_check_retry -> current writer",
- env, descP,
- &descP->currentWriter.pid,
- &descP->currentWriter.mon) == 0 );
- ESOCK_ASSERT( descP->currentWriter.env == NULL );
-
- descP->currentWriter.env = esock_alloc_env("current-writer");
- descP->currentWriter.ref =
- CP_TERM(descP->currentWriter.env, sendRef);
- descP->currentWriterP = &descP->currentWriter;
- } else {
- /* Overwrite current writer registration */
- enif_clear_env(descP->currentWriter.env);
- descP->currentWriter.ref = CP_TERM(descP->currentWriter.env, sendRef);
- }
-
- if (COMPARE(sendRef, esock_atom_iov) == 0) {
- ESOCK_ASSERT( written >= 0 );
- /* IOV iteration - do not select */
- return MKT2(env, esock_atom_iov, MKI64(env, written));
- }
-
- /* Select write for this process */
-
- sres = esock_select_write(env, descP->sock, descP, NULL, sockRef, sendRef);
-
- if (sres < 0) {
- ERL_NIF_TERM reason;
-
- /* Internal select error */
- ESOCK_ASSERT( DEMONP("send_check_retry - select error",
- env, descP, &descP->currentWriter.mon) == 0);
-
- /* Fail all queued writers */
- reason = MKT2(env, atom_select_write, MKI(env, sres));
- requestor_release("send_check_retry - select error",
- env, descP, &descP->currentWriter);
- send_error_waiting_writers(env, descP, sockRef, reason);
- descP->currentWriterP = NULL;
-
- res =
- enif_raise_exception(env,
- MKT2(env, atom_select_write,
- MKI(env, sres)));
-
- } else {
- ESOCK_CNT_INC(env, descP, sockRef, atom_write_waits,
- &descP->writeWaits, 1);
-
- descP->writeState |= ESOCK_STATE_SELECTED;
-
- if (written >= 0) {
- /* Partial write success */
- res = MKT2(env, atom_select, MKI64(env, written));
- } else {
- /* No write - try again */
- res = atom_select;
- }
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_check_reader ***
- *
- * Checks if we have a current reader and if that is us. If not,
- * then we must be made to wait for our turn. This is done by pushing
- * us unto the reader queue.
- * Note that we do *not* actually initiate the currentReader structure
- * here, since we do not actually know yet if we need to! We do that in
- * the [recv|recvfrom|recvmsg]_check_result function.
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T recv_check_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM ref,
- ERL_NIF_TERM* checkResult)
-{
- if (descP->currentReaderP != NULL) {
- ErlNifPid caller;
-
- ESOCK_ASSERT( enif_self(env, &caller) != NULL );
-
- if (COMPARE_PIDS(&descP->currentReader.pid, &caller) != 0) {
- /* Not the "current reader", so (maybe) push onto queue */
+ *eType = *cmsgTable[n].nameP;
- SSDBG( descP,
- ("SOCKET",
- "recv_check_reader {%d} -> not (current) reader"
- "\r\n ref: %T"
- "\r\n", descP->sock, ref) );
-
- if (! reader_search4pid(env, descP, &caller)) {
- if (COMPARE(ref, atom_zero) == 0)
- goto done_ok;
- reader_push(env, descP, caller, ref);
- *checkResult = atom_select;
- } else {
- /* Reader already in queue */
- *checkResult = esock_raise_invalid(env, atom_state);
+ if (cmsgTable[n].encode != NULL)
+ return cmsgTable[n].encode(env, dataP, dataLen, eData);
+ else
+ return FALSE;
}
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_reader {%d} -> queue (push) result: %T\r\n",
- descP->sock, *checkResult) );
-
- return FALSE;
- }
- }
-
- done_ok:
- // Does not actually matter in this case, but ...
- *checkResult = esock_atom_ok;
- return TRUE;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_init_current_reader ***
- *
- * Initiate (maybe) the currentReader structure of the descriptor.
- * Including monitoring the calling process.
- */
-#ifndef __WIN32__
-static
-void recv_init_current_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM recvRef)
-{
- if (descP->currentReaderP == NULL) {
-
- ESOCK_ASSERT( enif_self(env, &descP->currentReader.pid) != NULL );
-
- ESOCK_ASSERT( MONP("recv_init_current_reader -> current reader",
- env, descP,
- &descP->currentReader.pid,
- &descP->currentReader.mon) == 0);
- ESOCK_ASSERT(!descP->currentReader.env);
-
- descP->currentReader.env = esock_alloc_env("current-reader");
- descP->currentReader.ref =
- CP_TERM(descP->currentReader.env, recvRef);
- descP->currentReaderP = &descP->currentReader;
- } else {
-
- /*
- * This is a retry:
- * We have done, for instance, recv(Sock, X), but only received Y < X.
- * We then call recv again with size = X-Y. So, we then get a new ref.
- *
- * Make use of the existing environment
- */
-
- enif_clear_env(descP->currentReader.env);
- descP->currentReader.ref = CP_TERM(descP->currentReader.env, recvRef);
- }
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_update_current_reader ***
- *
- * Demonitors the current reader process and pop's the reader queue.
- * If there is a waiting (reader) process, then it will be assigned
- * as the new current reader and a new (read) select will be done.
- */
-#ifndef __WIN32__
-static void
-recv_update_current_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef)
-{
- if (descP->currentReaderP != NULL) {
-
- ESOCK_ASSERT( DEMONP("recv_update_current_reader",
- env, descP, &descP->currentReader.mon) == 0);
-
- if (! activate_next_reader(env, descP, sockRef)) {
-
- SSDBG( descP,
- ("SOCKET",
- "recv_update_current_reader(%T) {%d} -> no more readers\r\n",
- sockRef, descP->sock) );
-
- descP->currentReaderP = NULL;
- }
- }
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_error_current_reader ***
- *
- * Process the current reader and any waiting readers
- * when a read (fatal) error has occurred.
- * All waiting readers will be "aborted", that is a
- * nif_abort message will be sent (with ref and reason).
- */
-#ifndef __WIN32__
-static
-void recv_error_current_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM reason)
-{
- if (descP->currentReaderP != NULL) {
- ESockRequestor req;
-
- requestor_release("recv_error_current_reader",
- env, descP, &descP->currentReader);
-
- req.env = NULL; /* read by reader_pop before free */
- while (reader_pop(env, descP, &req)) {
-
- SSDBG( descP,
- ("SOCKET", "recv_error_current_reader(%T) {%d} -> abort"
- "\r\n pid: %T"
- "\r\n reason %T"
- "\r\n", sockRef, descP->sock,
- req.pid, reason) );
-
- esock_send_abort_msg(env, descP, sockRef, &req, reason);
-
- ESOCK_ASSERT( DEMONP("recv_error_current_reader -> pop'ed reader",
- env, descP, &req.mon) == 0);
- }
-
- descP->currentReaderP = NULL;
- }
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_check_result ***
- *
- * Process the result of a call to recv.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ssize_t toRead,
- int saveErrno,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef)
-{
- ERL_NIF_TERM res;
-
- SSDBG( descP,
- ("SOCKET", "recv_check_result(%T) {%d} -> entry with"
- "\r\n read: %ld"
- "\r\n toRead: %ld"
- "\r\n saveErrno: %d"
- "\r\n recvRef: %T"
- "\r\n", sockRef, descP->sock,
- (long) read, (long) toRead, saveErrno, recvRef) );
-
-
- /* <KOLLA>
- *
- * We need to handle read = 0 for other type(s) (DGRAM) when
- * its actually valid to read 0 bytes.
- *
- * </KOLLA>
- */
-
- if ((read == 0) && (descP->type == SOCK_STREAM)) {
- ERL_NIF_TERM reason = atom_closed;
- res = esock_make_error(env, reason);
-
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_read_fails, &descP->readFails, 1);
-
- /*
- * When a stream socket peer has performed an orderly shutdown,
- * the return value will be 0 (the traditional "end-of-file" return).
- *
- * *We* do never actually try to read 0 bytes!
- *
- * We must also notify any waiting readers!
- */
-
- recv_error_current_reader(env, descP, sockRef, reason);
-
- FREE_BIN(bufP);
-
- } else {
-
- /* There is a special case: If the provided 'to read' value is
- * zero (0) (only for type =/= stream).
- * That means that we read as much as we can, using the default
- * read buffer size.
- */
-
- if (bufP->size == read) {
-
- /* +++ We filled the buffer +++ */
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_result(%T) {%d} -> [%lu] filled the buffer\r\n",
- sockRef, descP->sock, (unsigned long) bufP->size) );
-
- res = recv_check_full(env, descP, read, toRead, bufP,
- sockRef, recvRef);
-
- } else if (read < 0) {
-
- /* +++ Error handling +++ */
-
- res = recv_check_fail(env, descP, saveErrno, bufP, NULL,
- sockRef, recvRef);
-
- } else {
-
- /* +++ We did not fill the buffer +++ */
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_result(%T) {%d} -> [%lu] "
- "did not fill the buffer (%ld)\r\n",
- sockRef, descP->sock, (unsigned long) bufP->size,
- (long) read) );
-
- res = recv_check_partial(env, descP, read, toRead, bufP,
- sockRef, recvRef);
}
}
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_check_full ***
- *
- * This function is called if we filled the allocated buffer.
- * But are we done yet?
- *
- * toRead = 0 means: Give me everything you have => maybe
- * toRead > 0 means: Yes
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_full(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ssize_t toRead,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef)
-{
- ERL_NIF_TERM res;
-
- if ((toRead == 0) &&
- (descP->type == SOCK_STREAM)) {
-
- /* +++ Give us everything you have got => *
- * (maybe) needs to continue +++ */
-
- /* Send up each chunk of data for each of the read
- * and let the erlang code assemble it: {more, Bin}
- * (when complete it should return {ok, Bin}).
- * We need to read at least one more time to be sure if its
- * done...
- *
- * Also, we need to check if the rNumCnt has reached its max (rNum),
- * in which case we will assume the read to be done!
- */
-
- SSDBG( descP,
- ("SOCKET", "recv_check_full(%T) {%d} -> shall we continue reading?"
- "\r\n read: %ld"
- "\r\n rNum: %u"
- "\r\n rNumCnt: %u"
- "\r\n", sockRef, descP->sock,
- (unsigned long) read, descP->rNum, descP->rNumCnt) );
-
- res = recv_check_full_maybe_done(env, descP, read, bufP,
- sockRef, recvRef);
-
- } else {
-
- /* +++ We got exactly as much as we requested => We are done +++ */
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_full(%T) {%d} -> [%ld] "
- "we got exactly what we could fit\r\n",
- sockRef, descP->sock, (long) toRead) );
-
- res = recv_check_full_done(env, descP, read, bufP, sockRef);
-
- }
-
- return res;
-
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_check_full_maybe_done ***
- *
- * Send up each chunk of data for each of the read
- * and let the erlang code assemble it: {more, Bin}
- * (when complete it should return {ok, Bin}).
- * We need to read at least one more time to be sure if its
- * done...
- *
- * Also, we need to check if the rNumCnt has reached its max (rNum),
- * in which case we will assume the read to be done!
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_full_maybe_done(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef)
-{
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_read_byte, &descP->readByteCnt, read);
- descP->readPkgMaxCnt += read;
-
- descP->rNumCnt++;
- if (descP->rNumCnt >= descP->rNum) {
-
- descP->rNumCnt = 0;
-
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_read_pkg, &descP->readPkgCnt, 1);
- if (descP->readPkgMaxCnt > descP->readPkgMax)
- descP->readPkgMax = descP->readPkgMaxCnt;
- descP->readPkgMaxCnt = 0;
-
- recv_update_current_reader(env, descP, sockRef);
-
- /* This transfers "ownership" of the *allocated* binary to an
- * erlang term (no need for an explicit free).
- */
-
- return esock_make_ok2(env, MKBIN(env, bufP));
-
- }
-
- /* Yes, we *do* need to continue reading */
-
- recv_init_current_reader(env, descP, recvRef);
-
- /* This transfers "ownership" of the *allocated* binary to an
- * erlang term (no need for an explicit free).
- */
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_full_maybe_done(%T) {%d} -> [%lu] "
- "we are done for now - read more\r\n",
- sockRef, descP->sock, (unsigned long)bufP->size) );
-
- return MKT2(env, esock_atom_more, MKBIN(env, bufP));
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_check_full_done ***
- *
- * A successful recv and we filled the buffer.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_full_done(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef)
-{
- ERL_NIF_TERM data;
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_pkg, &descP->readPkgCnt, 1);
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
- &descP->readByteCnt, read);
-
- descP->readPkgMaxCnt += read;
- if (descP->readPkgMaxCnt > descP->readPkgMax)
- descP->readPkgMax = descP->readPkgMaxCnt;
- descP->readPkgMaxCnt = 0;
-
- recv_update_current_reader(env, descP, sockRef);
-
- /* This transfers "ownership" of the *allocated* binary to an
- * erlang term (no need for an explicit free).
- */
- data = MKBIN(env, bufP);
-
- return esock_make_ok2(env, data);
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_check_fail ***
- *
- * Handle recv failure.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_fail(ErlNifEnv* env,
- ESockDescriptor* descP,
- int saveErrno,
- ErlNifBinary* buf1P,
- ErlNifBinary* buf2P,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef)
-{
- ERL_NIF_TERM res;
-
- FREE_BIN(buf1P);
- if (buf2P != NULL) FREE_BIN(buf2P);
-
- if (saveErrno == ECONNRESET) {
-
- /* +++ Oops - closed +++ */
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_fail(%T) {%d} -> econnreset: closed"
- "\r\n recvRef: %T"
- "\r\n", sockRef, descP->sock, recvRef) );
-
- // This is a bit overkill (to count here), but just in case...
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_fails,
- &descP->readFails, 1);
-
- res = recv_check_fail_econnreset(env, descP, sockRef, recvRef);
-
- } else if ((saveErrno == ERRNO_BLOCK) ||
- (saveErrno == EAGAIN)) {
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_fail(%T) {%d} -> eagain"
- "\r\n recvRef: %T"
- "\r\n", sockRef, descP->sock, recvRef) );
-
- if (COMPARE(recvRef, atom_zero) == 0)
- res = esock_atom_ok;
- else
- res = recv_check_retry(env, descP, sockRef, recvRef);
-
- } else {
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_fail(%T) {%d} -> errno: %d\r\n"
- "\r\n recvRef: %T"
- "\r\n", sockRef, descP->sock, saveErrno, recvRef) );
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_fails,
- &descP->readFails, 1);
-
- res = recv_check_fail_gen(env, descP, saveErrno, sockRef);
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_check_fail_econnreset ***
- *
- * We detected that the socket was closed while reading.
- * Inform current and waiting readers.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_fail_econnreset(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef)
-{
- ERL_NIF_TERM reason = MKA(env, erl_errno_id(ECONNRESET));
- ERL_NIF_TERM res = esock_make_error(env, reason);
-
- /* <KOLLA>
- *
- * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING
- * PROCESS, WE NEED TO INFORM IT!!!
- *
- * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!!
- * HANDLED BY THE STOP (CALLBACK) FUNCTION?
- *
- * SINCE THIS IS A REMOTE CLOSE, WE DON'T NEED TO WAIT
- * FOR OUTPUT TO BE WRITTEN (NO ONE WILL READ), JUST
- * ABORT THE SOCKET REGARDLESS OF LINGER???
- *
- * </KOLLA>
+ /* No level table, or unknown type number in the table;
+ * just return the type number as an erlang integer
*/
- recv_error_current_reader(env, descP, sockRef, reason);
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_check_retry ***
- *
- * The recv call would have blocked, so retry.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_retry(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef)
-{
- int sres;
- ERL_NIF_TERM res;
-
- descP->rNumCnt = 0;
- recv_init_current_reader(env, descP, recvRef);
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_retry(%T) {%d} -> SELECT for more"
- "\r\n recvRef: %T"
- "\r\n", sockRef, descP->sock, recvRef) );
-
- if ((sres = esock_select_read(env, descP->sock, descP, NULL,
- sockRef, recvRef)) < 0) {
- /* Unlikely that any next reader will have better luck,
- * but why not give them a shot - the queue will be cleared
- */
- recv_update_current_reader(env, descP, sockRef);
-
- res =
- enif_raise_exception(env,
- MKT2(env, atom_select_read,
- MKI(env, sres)));
- } else {
- descP->readState |= ESOCK_STATE_SELECTED;
- res = atom_select;
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_check_fail_gen ***
- *
- * The recv call had a "general" failure.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_fail_gen(ErlNifEnv* env,
- ESockDescriptor* descP,
- int saveErrno,
- ERL_NIF_TERM sockRef)
-{
- ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
-
- recv_error_current_reader(env, descP, sockRef, reason);
+ *eType = MKI(env, type);
- return esock_make_error(env, reason);
+ return FALSE;
}
-#endif // #ifndef __WIN32__
-/* *** recv_check_partial ***
+/* +++ esock_encode_msg_flags +++
*
- * Handle a successful recv which only partly filled the specified buffer.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_partial(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ssize_t toRead,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef)
-{
- ERL_NIF_TERM res;
-
- if ((toRead == 0) ||
- (descP->type != SOCK_STREAM) ||
- (COMPARE(recvRef, atom_zero) == 0)) {
-
- /* +++ We got it all, but since we +++
- * +++ did not fill the buffer, we +++
- * +++ must split it into a sub-binary. +++
- */
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_partial(%T) {%d} -> [%ld] split buffer"
- "\r\n recvRef: %T"
- "\r\n", sockRef, descP->sock, (long) toRead,
- recvRef) );
-
- res = recv_check_partial_done(env, descP, read, bufP, sockRef);
-
- } else {
- /* A stream socket with specified read size
- * and not a polling read, we got a partial read
- * - return a select result to initiate a retry
- */
-
- SSDBG( descP,
- ("SOCKET",
- "recv_check_partial(%T) {%d} -> [%ld]"
- " only part of message - expect more"
- "\r\n recvRef: %T"
- "\r\n", sockRef, descP->sock, (long) toRead,
- recvRef) );
-
- res =
- recv_check_partial_part(env, descP, read,
- bufP, sockRef, recvRef);
- }
-
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recv_check_partial_done ***
+ * Encode a list of msg_flag().
*
- * A successful but only partial recv, which fulfilled the required read.
*/
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_partial_done(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef)
-{
- ERL_NIF_TERM data;
-
- descP->rNumCnt = 0;
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_pkg, &descP->readPkgCnt, 1);
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
- &descP->readByteCnt, read);
-
- descP->readPkgMaxCnt += read;
- if (descP->readPkgMaxCnt > descP->readPkgMax)
- descP->readPkgMax = descP->readPkgMaxCnt;
- descP->readPkgMaxCnt = 0;
-
- recv_update_current_reader(env, descP, sockRef);
-
- /* This transfers "ownership" of the *allocated* binary to an
- * erlang term (no need for an explicit free).
- */
- data = MKBIN(env, bufP);
- data = MKSBIN(env, data, 0, read);
-
- SSDBG( descP,
- ("SOCKET", "recv_check_partial_done(%T) {%d} -> [%ld] done\r\n",
- sockRef, descP->sock, (long) read) );
-
- return esock_make_ok2(env, data);
-}
-#endif // #ifndef __WIN32__
-
-/* *** recv_check_partial_part ***
- *
- * A successful but only partial recv, which only partly fulfilled
- * the required read.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recv_check_partial_part(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- ErlNifBinary* bufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef)
-{
- ERL_NIF_TERM res;
- int sres;
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
- &descP->readByteCnt, read);
-
- recv_init_current_reader(env, descP, recvRef);
-
- /* SELECT for more data */
-
- sres = esock_select_read(env, descP->sock, descP, NULL,
- sockRef, recvRef);
- if (sres < 0) {
- /* Unlikely that any next reader will have better luck,
- * but why not give them a shot - the queue will be cleared
- */
- recv_update_current_reader(env, descP, sockRef);
-
- res =
- enif_raise_exception(env,
- MKT2(env, atom_select_read,
- MKI(env, sres)));
- } else {
- ERL_NIF_TERM data;
-
- descP->readState |= ESOCK_STATE_SELECTED;
- data = MKBIN(env, bufP);
- data = MKSBIN(env, data, 0, read);
- res = MKT2(env, atom_select, data);
- }
-
- /* This transfers "ownership" of the *allocated* binary to an
- * erlang term (no need for an explicit free).
- */
- return res;
-}
-#endif // #ifndef __WIN32__
-
-
-
-
-/* The recvfrom function delivers one (1) message. If our buffer
- * is too small, the message will be truncated. So, regardless
- * if we filled the buffer or not, we have got what we are going
- * to get regarding this message.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- int saveErrno,
- ErlNifBinary* bufP,
- ESockAddress* fromAddrP,
- SOCKLEN_T fromAddrLen,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef)
+extern
+void esock_encode_msg_flags(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int msgFlags,
+ ERL_NIF_TERM* flags)
{
- ERL_NIF_TERM data, res;
-
SSDBG( descP,
- ("SOCKET", "recvfrom_check_result(%T) {%d} -> entry with"
- "\r\n read: %ld"
- "\r\n saveErrno: %d"
- "\r\n recvRef: %T"
- "\r\n", sockRef, descP->sock,
- (long) read, saveErrno, recvRef) );
-
- /* <KOLLA>
- *
- * We need to handle read = 0 for non_stream socket type(s) when
- * its actually valid to read 0 bytes.
- *
- * </KOLLA>
- */
-
- if ((read == 0) && (descP->type == SOCK_STREAM)) {
-
- /*
- * When a stream socket peer has performed an orderly shutdown,
- * the return value will be 0 (the traditional "end-of-file" return).
- *
- * *We* do never actually try to read 0 bytes!
- */
-
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_read_fails, &descP->readFails, 1);
-
- FREE_BIN(bufP);
-
- return esock_make_error(env, atom_closed);
- }
-
- if (read < 0) {
-
- /* +++ Error handling +++ */
-
- res = recv_check_fail(env, descP, saveErrno, bufP, NULL,
- sockRef, recvRef);
+ ("SOCKET", "encode_msg_flags {%d} -> entry with"
+ "\r\n msgFlags: %d (0x%lX)"
+ "\r\n", descP->sock, msgFlags, msgFlags) );
+ if (msgFlags == 0) {
+ *flags = MKEL(env);
} else {
+ size_t n;
+ SocketTArray ta = TARRAY_CREATE(10); // Just to be on the safe side
- /* +++ We successfully got a message - time to encode the address +++ */
-
- ERL_NIF_TERM eSockAddr;
-
- esock_encode_sockaddr(env,
- fromAddrP, fromAddrLen,
- &eSockAddr);
-
- if (read == bufP->size) {
-
- data = MKBIN(env, bufP);
-
- } else {
-
- /* +++ We got a chunk of data but +++
- * +++ since we did not fill the +++
- * +++ buffer, we must split it +++
- * +++ into a sub-binary. +++
- */
-
- data = MKBIN(env, bufP);
- data = MKSBIN(env, data, 0, read);
+ for (n = 0; n < esock_msg_flags_length; n++) {
+ int f = esock_msg_flags[n].flag;
+ if ((f != 0) && ((msgFlags & f) == f)) {
+ msgFlags &= ~f;
+ TARRAY_ADD(ta, *(esock_msg_flags[n].name));
+ }
+ if (msgFlags == 0) goto done;
}
+ /* Append remaining flags as an integer */
+ if (msgFlags != 0)
+ TARRAY_ADD(ta, MKI(env, msgFlags));
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_pkg,
- &descP->readPkgCnt, 1);
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
- &descP->readByteCnt, read);
-
- recv_update_current_reader(env, descP, sockRef);
-
- res = esock_make_ok2(env, MKT2(env, eSockAddr, data));
-
- }
-
- return res;
-
-}
-#endif // #ifndef __WIN32__
-
-
-
-/* *** recvmsg_check_result ***
- *
- * The recvmsg function delivers one (1) message. If our buffer
- * is to small, the message will be truncated. So, regardless
- * if we filled the buffer or not, we have got what we are going
- * to get regarding this message.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- int saveErrno,
- struct msghdr* msgHdrP,
- ErlNifBinary* dataBufP,
- ErlNifBinary* ctrlBufP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM recvRef)
-{
- ERL_NIF_TERM res;
-
- SSDBG( descP,
- ("SOCKET", "recvmsg_check_result(%T) {%d} -> entry with"
- "\r\n read: %ld"
- "\r\n saveErrno: %d"
- "\r\n recvRef: %T"
- "\r\n", sockRef, descP->sock,
- (long) read, saveErrno, recvRef) );
-
-
- /* <KOLLA>
- *
- * We need to handle read = 0 for non_stream socket type(s) when
- * its actually valid to read 0 bytes.
- *
- * </KOLLA>
- */
-
- if ((read == 0) && (descP->type == SOCK_STREAM)) {
-
- /*
- * When a stream socket peer has performed an orderly shutdown,
- * the return value will be 0 (the traditional "end-of-file" return).
- *
- * *We* do never actually try to read 0 bytes!
- */
-
- ESOCK_CNT_INC(env, descP, sockRef,
- atom_read_fails, &descP->readFails, 1);
-
- FREE_BIN(dataBufP); FREE_BIN(ctrlBufP);
-
- return esock_make_error(env, atom_closed);
- }
-
-
- if (read < 0) {
-
- /* +++ Error handling +++ */
-
- res = recv_check_fail(env, descP, saveErrno, dataBufP, ctrlBufP,
- sockRef, recvRef);
-
- } else {
-
- /* +++ We successfully got a message - time to encode it +++ */
-
- res = recvmsg_check_msg(env, descP, read, msgHdrP,
- dataBufP, ctrlBufP, sockRef);
-
- }
-
- return res;
-
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** recvmsg_check_msg ***
- *
- * We successfully read one message. Time to process.
- */
-#ifndef __WIN32__
-static
-ERL_NIF_TERM recvmsg_check_msg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- struct msghdr* msgHdrP,
- ErlNifBinary* dataBufP,
- ErlNifBinary* ctrlBufP,
- ERL_NIF_TERM sockRef)
-{
- ERL_NIF_TERM eMsg;
-
- /*
- * <KOLLA>
- *
- * The return value of recvmsg is the *total* number of bytes
- * that where successfully read. This data has been put into
- * the *IO vector*.
- *
- * </KOLLA>
- */
-
- encode_msg(env, descP,
- read, msgHdrP, dataBufP, ctrlBufP,
- &eMsg);
-
- SSDBG( descP,
- ("SOCKET", "recvmsg_check_result(%T) {%d} -> ok\r\n",
- sockRef, descP->sock) );
-
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_pkg, &descP->readPkgCnt, 1);
- ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
- &descP->readByteCnt, read);
-
- recv_update_current_reader(env, descP, sockRef);
-
- return esock_make_ok2(env, eMsg);
-}
-#endif // #ifndef __WIN32__
-
-
-/* +++ encode_msg +++
- *
- * Encode a msg() (recvmsg). In erlang its represented as
- * a map, which has a specific set of attributes:
- *
- * addr (source address) - sockaddr()
- * iov - [binary()]
- * ctrl - [cmsg()]
- * flags - msg_flags()
- */
-#ifndef __WIN32__
-static
-void encode_msg(ErlNifEnv* env,
- ESockDescriptor* descP,
- ssize_t read,
- struct msghdr* msgHdrP,
- ErlNifBinary* dataBufP,
- ErlNifBinary* ctrlBufP,
- ERL_NIF_TERM* eSockAddr)
-{
- ERL_NIF_TERM addr, iov, ctrl, flags;
-
- SSDBG( descP,
- ("SOCKET", "encode_msg {%d} -> entry with"
- "\r\n read: %ld"
- "\r\n", descP->sock, (long) read) );
-
- /* The address is not used if we are connected (unless, maybe,
- * family is 'local'), so check (length = 0) before we try to encodel
- */
- if (msgHdrP->msg_namelen != 0) {
- esock_encode_sockaddr(env,
- (ESockAddress*) msgHdrP->msg_name,
- msgHdrP->msg_namelen,
- &addr);
- } else {
- addr = esock_atom_undefined;
- }
-
- SSDBG( descP,
- ("SOCKET", "encode_msg {%d} -> encode iov"
- "\r\n msg_iovlen: %lu"
- "\r\n",
- descP->sock,
- (unsigned long) msgHdrP->msg_iovlen) );
-
- esock_encode_iov(env, read,
- msgHdrP->msg_iov, msgHdrP->msg_iovlen, dataBufP,
- &iov);
-
- SSDBG( descP,
- ("SOCKET",
- "encode_msg {%d} -> try encode cmsgs\r\n",
- descP->sock) );
-
- encode_cmsgs(env, descP, ctrlBufP, msgHdrP, &ctrl);
-
- SSDBG( descP,
- ("SOCKET",
- "encode_msg {%d} -> try encode flags\r\n",
- descP->sock) );
-
- encode_msg_flags(env, descP, msgHdrP->msg_flags, &flags);
-
- SSDBG( descP,
- ("SOCKET", "encode_msg {%d} -> components encoded:"
- "\r\n addr: %T"
- "\r\n ctrl: %T"
- "\r\n flags: %T"
- "\r\n", descP->sock, addr, ctrl, flags) );
-
- {
- ERL_NIF_TERM keys[] = {esock_atom_iov,
- esock_atom_ctrl,
- esock_atom_flags,
- esock_atom_addr};
- ERL_NIF_TERM vals[] = {iov, ctrl, flags, addr};
- size_t numKeys = NUM(keys);
-
- ESOCK_ASSERT( numKeys == NUM(vals) );
-
- SSDBG( descP,
- ("SOCKET",
- "encode_msg {%d} -> create map\r\n",
- descP->sock) );
-
- if (msgHdrP->msg_namelen == 0)
- numKeys--; // No addr
- ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eSockAddr) );
-
- SSDBG( descP,
- ("SOCKET",
- "encode_msg {%d}-> map encoded\r\n",
- descP->sock) );
- }
-
- SSDBG( descP,
- ("SOCKET", "encode_msg {%d} -> done\r\n", descP->sock) );
-}
-#endif // #ifndef __WIN32__
-
-
-
-/* +++ encode_cmsgs +++
- *
- * Encode a list of cmsg(). There can be 0 or more cmsghdr "blocks".
- *
- * Our "problem" is that we have no idea how many control messages
- * we have.
- *
- * The cmsgHdrP arguments points to the start of the control data buffer,
- * an actual binary. Its the only way to create sub-binaries. So, what we
- * need to continue processing this is to turn that into an binary erlang
- * term (which can then in turn be turned into sub-binaries).
- *
- * We need the cmsgBufP (even though cmsgHdrP points to it) to be able
- * to create sub-binaries (one for each cmsg hdr).
- *
- * The TArray (term array) is created with the size of 128, which should
- * be enough. But if its not, then it will be automatically realloc'ed during
- * add. Once we are done adding hdr's to it, we convert the tarray to a list.
- */
-#ifndef __WIN32__
-static
-void encode_cmsgs(ErlNifEnv* env,
- ESockDescriptor* descP,
- ErlNifBinary* cmsgBinP,
- struct msghdr* msgHdrP,
- ERL_NIF_TERM* eCMsg)
-{
- ERL_NIF_TERM ctrlBuf = MKBIN(env, cmsgBinP); // The *entire* binary
- SocketTArray cmsghdrs = TARRAY_CREATE(128);
- struct cmsghdr* firstP = CMSG_FIRSTHDR(msgHdrP);
- struct cmsghdr* currentP;
-
- SSDBG( descP, ("SOCKET", "encode_cmsgs {%d} -> entry when"
- "\r\n msg ctrl len: %d"
- "\r\n (ctrl) firstP: 0x%lX"
- "\r\n", descP->sock,
- msgHdrP->msg_controllen, firstP) );
-
- for (currentP = firstP;
- /*
- * In *old* versions of darwin, the CMSG_FIRSTHDR does not
- * check the msg_controllen, so we do it here.
- * We should really test this stuff during configure,
- * but for now, this will have to do.
- */
-#if defined(__DARWIN__)
- (msgHdrP->msg_controllen >= sizeof(struct cmsghdr)) &&
- (currentP != NULL);
-#else
- (currentP != NULL);
-#endif
- currentP = CMSG_NXTHDR(msgHdrP, currentP)) {
-
+ done:
SSDBG( descP,
- ("SOCKET", "encode_cmsgs {%d} -> process cmsg header when"
- "\r\n TArray Size: %d"
- "\r\n", descP->sock, TARRAY_SZ(cmsghdrs)) );
-
- /* MUST check this since on Linux the returned "cmsg" may actually
- * go too far!
- */
- if (((CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP)) >
- msgHdrP->msg_controllen) {
-
- /* Ouch, fatal error - give up
- * We assume we cannot trust any data if this is wrong.
- */
-
- SSDBG( descP,
- ("SOCKET", "encode_cmsgs {%d} -> check failed when: "
- "\r\n currentP: 0x%lX"
- "\r\n (current) cmsg_len: %d"
- "\r\n firstP: 0x%lX"
- "\r\n => %d"
- "\r\n msg ctrl len: %d"
- "\r\n", descP->sock,
- CHARP(currentP), currentP->cmsg_len, CHARP(firstP),
- (CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP),
- msgHdrP->msg_controllen) );
-
- TARRAY_ADD(cmsghdrs, esock_atom_bad_data);
- break;
-
- } else {
- unsigned char* dataP = UCHARP(CMSG_DATA(currentP));
- size_t dataPos = dataP - cmsgBinP->data;
- size_t dataLen =
- (UCHARP(currentP) + currentP->cmsg_len) - dataP;
- ERL_NIF_TERM
- cmsgHdr,
- keys[] =
- {esock_atom_level,
- esock_atom_type,
- esock_atom_data,
- atom_value},
- vals[NUM(keys)];
- size_t numKeys = NUM(keys);
- BOOLEAN_T have_value;
-
- SSDBG( descP,
- ("SOCKET", "encode_cmsgs {%d} -> cmsg header data: "
- "\r\n dataPos: %d"
- "\r\n dataLen: %d"
- "\r\n", descP->sock, dataPos, dataLen) );
-
- vals[0] = esock_encode_level(env, currentP->cmsg_level);
- vals[2] = MKSBIN(env, ctrlBuf, dataPos, dataLen);
- have_value =
- encode_cmsg(env,
- currentP->cmsg_level,
- currentP->cmsg_type,
- dataP, dataLen, &vals[1], &vals[3]);
+ ("SOCKET", "encode_msg_flags {%d} -> flags processed when"
+ "\r\n TArray size: %d"
+ "\r\n", descP->sock, TARRAY_SZ(ta)) );
- SSDBG( descP,
- ("SOCKET", "encode_cmsgs {%d} -> "
- "\r\n %T: %T"
- "\r\n %T: %T"
- "\r\n %T: %T"
- "\r\n", descP->sock,
- keys[0], vals[0], keys[1], vals[1], keys[2], vals[2]) );
- if (have_value)
- SSDBG( descP,
- ("SOCKET", "encode_cmsgs {%d} -> "
- "\r\n %T: %T"
- "\r\n", descP->sock, keys[3], vals[3]) );
-
- /* Guard against cut-and-paste errors */
- ESOCK_ASSERT( numKeys == NUM(vals) );
- ESOCK_ASSERT( MKMA(env, keys, vals,
- numKeys - (have_value ? 0 : 1), &cmsgHdr) );
-
- /* And finally add it to the list... */
- TARRAY_ADD(cmsghdrs, cmsgHdr);
- }
+ TARRAY_TOLIST(ta, env, flags);
}
-
- SSDBG( descP,
- ("SOCKET", "encode_cmsgs {%d} -> cmsg headers processed when"
- "\r\n TArray Size: %d"
- "\r\n", descP->sock, TARRAY_SZ(cmsghdrs)) );
-
- /* The tarray is populated - convert it to a list */
- TARRAY_TOLIST(cmsghdrs, env, eCMsg);
}
-#endif // #ifndef __WIN32__
-
-#ifndef __WIN32__
#ifdef SCM_TIMESTAMP
static
BOOLEAN_T esock_cmsg_encode_timeval(ErlNifEnv *env,
@@ -16748,17 +10270,15 @@ static BOOLEAN_T esock_cmsg_decode_timeval(ErlNifEnv *env,
if (! esock_decode_timeval(env, eValue, &time))
return FALSE;
- if ((timeP = init_cmsghdr(cmsgP, rem, sizeof(*timeP), usedP)) == NULL)
+ if ((timeP = esock_init_cmsghdr(cmsgP, rem, sizeof(*timeP), usedP)) == NULL)
return FALSE;
*timeP = time;
return TRUE;
}
#endif
-#endif
-#ifndef __WIN32__
#if defined(IP_TOS) || defined(IP_RECVTOS)
static
BOOLEAN_T esock_cmsg_encode_ip_tos(ErlNifEnv *env,
@@ -16788,16 +10308,15 @@ static BOOLEAN_T esock_cmsg_decode_ip_tos(ErlNifEnv *env,
if (! decode_ip_tos(env, eValue, &tos))
return FALSE;
- if ((tosP = init_cmsghdr(cmsgP, rem, sizeof(*tosP), usedP)) == NULL)
+ if ((tosP = esock_init_cmsghdr(cmsgP, rem, sizeof(*tosP), usedP)) == NULL)
return FALSE;
*tosP = tos;
return TRUE;
}
#endif // #ifdef IP_TOS
-#endif // #ifdef __WIN32__
-#ifndef __WIN32__
+
#if defined(IP_TTL) || \
defined(IPV6_HOPLIMIT) || \
defined(IPV6_TCLASS) || defined(IPV6_RECVTCLASS)
@@ -16816,50 +10335,50 @@ BOOLEAN_T esock_cmsg_encode_int(ErlNifEnv *env,
return TRUE;
}
-static BOOLEAN_T esock_cmsg_decode_int(ErlNifEnv *env,
- ERL_NIF_TERM eValue,
- struct cmsghdr *cmsgP,
- size_t rem,
- size_t *usedP)
+extern
+BOOLEAN_T esock_cmsg_decode_int(ErlNifEnv* env,
+ ERL_NIF_TERM eValue,
+ struct cmsghdr* cmsgP,
+ size_t rem,
+ size_t* usedP)
{
int value, *valueP;
if (! GET_INT(env, eValue, &value))
return FALSE;
- if ((valueP = init_cmsghdr(cmsgP, rem, sizeof(*valueP), usedP)) == NULL)
+ valueP = esock_init_cmsghdr(cmsgP, rem, sizeof(*valueP), usedP);
+ if (valueP == NULL)
return FALSE;
*valueP = value;
return TRUE;
}
#endif
-#endif
-#ifndef __WIN32__
-static BOOLEAN_T esock_cmsg_decode_bool(ErlNifEnv *env,
- ERL_NIF_TERM eValue,
- struct cmsghdr *cmsgP,
- size_t rem,
- size_t *usedP)
+extern
+BOOLEAN_T esock_cmsg_decode_bool(ErlNifEnv* env,
+ ERL_NIF_TERM eValue,
+ struct cmsghdr* cmsgP,
+ size_t rem,
+ size_t* usedP)
{
BOOLEAN_T v;
- int *valueP;
+ int* valueP;
if (! esock_decode_bool(eValue, &v))
return FALSE;
- if ((valueP = init_cmsghdr(cmsgP, rem, sizeof(*valueP), usedP)) == NULL)
+ if ((valueP = esock_init_cmsghdr(cmsgP, rem,
+ sizeof(*valueP), usedP)) == NULL)
return FALSE;
*valueP = v? 1 : 0;
return TRUE;
}
-#endif
-#ifndef __WIN32__
#ifdef IP_RECVTTL
static
BOOLEAN_T esock_cmsg_encode_uchar(ErlNifEnv *env,
@@ -16876,15 +10395,15 @@ BOOLEAN_T esock_cmsg_encode_uchar(ErlNifEnv *env,
return TRUE;
}
#endif
-#endif
-#ifndef __WIN32__
+
#ifdef IP_PKTINFO
static
BOOLEAN_T esock_cmsg_encode_in_pktinfo(ErlNifEnv *env,
unsigned char *data,
size_t dataLen,
- ERL_NIF_TERM *eResult) {
+ ERL_NIF_TERM *eResult)
+{
struct in_pktinfo* pktInfoP = (struct in_pktinfo*) data;
ERL_NIF_TERM ifIndex;
ERL_NIF_TERM specDst, addr;
@@ -16893,14 +10412,23 @@ BOOLEAN_T esock_cmsg_encode_in_pktinfo(ErlNifEnv *env,
return FALSE;
ifIndex = MKUI(env, pktInfoP->ipi_ifindex);
+#ifndef __WIN32__
+ /* On Windows, the field ipi_spec_dst does not exist */
esock_encode_in_addr(env, &pktInfoP->ipi_spec_dst, &specDst);
+#endif
esock_encode_in_addr(env, &pktInfoP->ipi_addr, &addr);
{
ERL_NIF_TERM keys[] = {esock_atom_ifindex,
esock_atom_spec_dst,
esock_atom_addr};
- ERL_NIF_TERM vals[] = {ifIndex, specDst, addr};
+ ERL_NIF_TERM vals[] = {ifIndex,
+#ifndef __WIN32__
+ specDst,
+#else
+ esock_atom_undefined,
+#endif
+ addr};
unsigned int numKeys = NUM(keys);
unsigned int numVals = NUM(vals);
@@ -16910,7 +10438,7 @@ BOOLEAN_T esock_cmsg_encode_in_pktinfo(ErlNifEnv *env,
return TRUE;
}
#endif
-#endif
+
#ifndef __WIN32__
#ifdef IP_ORIGDSTADDR
@@ -16933,6 +10461,7 @@ BOOLEAN_T esock_cmsg_encode_sockaddr(ErlNifEnv *env,
#endif
#endif
+
#ifndef __WIN32__
#ifdef HAVE_LINUX_ERRQUEUE_H
#if defined(IP_RECVERR) || defined(IPV6_RECVERR)
@@ -17196,7 +10725,6 @@ BOOLEAN_T esock_cmsg_encode_recverr(ErlNifEnv *env,
#endif // #ifdef HAVE_LINUX_ERRQUEUE_H
#endif // #ifndef __WIN32__
-#ifndef __WIN32__
#ifdef IPV6_PKTINFO
static
BOOLEAN_T esock_cmsg_encode_in6_pktinfo(ErlNifEnv *env,
@@ -17222,37 +10750,24 @@ BOOLEAN_T esock_cmsg_encode_in6_pktinfo(ErlNifEnv *env,
return TRUE;
}
#endif
-#endif
-
-#ifndef __WIN32__
-
-struct ESockCmsgSpec {
- int type; // Message type
-
- // Function to encode into erlang term
- BOOLEAN_T (* encode)
- (ErlNifEnv *env, unsigned char *data, size_t dataLen,
- ERL_NIF_TERM *eResult);
-
- // Function to decode from erlang term
- BOOLEAN_T (* decode)
- (ErlNifEnv *env, ERL_NIF_TERM eValue,
- struct cmsghdr *cmsgP, size_t rem, size_t *usedP);
-
- ERL_NIF_TERM *nameP; // Pointer to option name atom
-};
static int cmpESockCmsgSpec(const void *vpa, const void *vpb) {
- struct ESockCmsgSpec *a, *b;
- a = (struct ESockCmsgSpec *) vpa;
- b = (struct ESockCmsgSpec *) vpb;
+ ESockCmsgSpec *a, *b;
+ a = (ESockCmsgSpec *) vpa;
+ b = (ESockCmsgSpec *) vpb;
return COMPARE(*(a->nameP), *(b->nameP));
}
-static struct ESockCmsgSpec
- cmsgLevelSocket[] =
+
+#if defined(SCM_CREDENTIALS) || defined(SCM_RIGHTS) || defined(SCM_TIMESTAMP)
+#define HAVE_ESOCK_CMSG_SOCKET
+#endif
+
+
+#if defined(HAVE_ESOCK_CMSG_SOCKET)
+static ESockCmsgSpec cmsgLevelSocket[] =
{
#if defined(SCM_CREDENTIALS)
{SCM_CREDENTIALS, NULL, NULL,
@@ -17272,9 +10787,10 @@ static struct ESockCmsgSpec
&esock_cmsg_encode_timeval, esock_cmsg_decode_timeval,
&esock_atom_timestamp},
#endif
- },
+ };
+#endif
- cmsgLevelIP[] =
+static ESockCmsgSpec cmsgLevelIP[] =
{
#if defined(IP_TOS)
{IP_TOS, esock_cmsg_encode_ip_tos, esock_cmsg_decode_ip_tos,
@@ -17319,7 +10835,7 @@ static struct ESockCmsgSpec
};
#ifdef HAVE_IPV6
-static struct ESockCmsgSpec cmsgLevelIPv6[] =
+static ESockCmsgSpec cmsgLevelIPv6[] =
{
#if defined(IPV6_PKTINFO)
{IPV6_PKTINFO, esock_cmsg_encode_in6_pktinfo, NULL,
@@ -17354,35 +10870,52 @@ static struct ESockCmsgSpec cmsgLevelIPv6[] =
};
#endif // #ifdef HAVE_IPV6
-static void initCmsgTables(void) {
+static void initCmsgTables(void)
+{
+#if defined(HAVE_ESOCK_CMSG_SOCKET)
ESOCK_SORT_TABLE(cmsgLevelSocket, cmpESockCmsgSpec);
- ESOCK_SORT_TABLE(cmsgLevelIP, cmpESockCmsgSpec);
+#endif
+
+ ESOCK_SORT_TABLE(cmsgLevelIP, cmpESockCmsgSpec);
+
#ifdef HAVE_IPV6
- ESOCK_SORT_TABLE(cmsgLevelIPv6, cmpESockCmsgSpec);
+ ESOCK_SORT_TABLE(cmsgLevelIPv6, cmpESockCmsgSpec);
#endif
}
-static struct ESockCmsgSpec *lookupCmsgTable(int level, size_t *num) {
+extern
+ESockCmsgSpec* esock_lookup_cmsg_table(int level, size_t *num)
+{
switch (level) {
+#if defined(HAVE_ESOCK_CMSG_SOCKET)
case SOL_SOCKET:
*num = NUM(cmsgLevelSocket);
return cmsgLevelSocket;
+#endif
+#ifndef __WIN32__
#ifdef SOL_IP
case SOL_IP:
#else
case IPPROTO_IP:
#endif
+#else
+ case IPPROTO_IP:
+#endif
*num = NUM(cmsgLevelIP);
return cmsgLevelIP;
#ifdef HAVE_IPV6
+#ifndef __WIN32__
#ifdef SOL_IPV6
case SOL_IPV6:
#else
case IPPROTO_IPV6:
#endif
+#else
+ case IPPROTO_IPV6:
+#endif
*num = NUM(cmsgLevelIPv6);
return cmsgLevelIPv6;
#endif
@@ -17392,477 +10925,44 @@ static struct ESockCmsgSpec *lookupCmsgTable(int level, size_t *num) {
}
}
-static struct ESockCmsgSpec *lookupCmsgSpec(struct ESockCmsgSpec *table,
- size_t num,
- ERL_NIF_TERM eType) {
- struct ESockCmsgSpec key;
+extern
+ESockCmsgSpec* esock_lookup_cmsg_spec(ESockCmsgSpec* table,
+ size_t num,
+ ERL_NIF_TERM eType)
+{
+ ESockCmsgSpec key;
sys_memzero(CHARP(&key), sizeof(key));
key.nameP = &eType;
return bsearch(&key, table, num, sizeof(*table), cmpESockCmsgSpec);
}
-#endif // #ifdef __WIN32__
-
-
-
-#ifndef __WIN32__
-static
-BOOLEAN_T encode_cmsg(ErlNifEnv* env,
- int level,
- int type,
- unsigned char* dataP,
- size_t dataLen,
- ERL_NIF_TERM* eType,
- ERL_NIF_TERM* eData) {
- const struct ESockCmsgSpec *cmsgTable;
- size_t num;
-
- if ((cmsgTable = lookupCmsgTable(level, &num)) != NULL) {
- size_t n;
-
- /* Linear search for type number in level table
- */
- for (n = 0; n < num; n++) {
- if (cmsgTable[n].type == type) {
- /* Found the type number in the level table;
- * return the symbolic type (atom)
- * and try to encode the data
- */
-
- *eType = *cmsgTable[n].nameP;
-
- if (cmsgTable[n].encode != NULL)
- return cmsgTable[n].encode(env, dataP, dataLen, eData);
- else
- return FALSE;
- }
- }
- }
- /* No level table, or unknown type number in the table;
- * just return the type number as an erlang integer
- */
-
-
- *eType = MKI(env, type);
- return FALSE;
-}
-#endif
-
-
-#ifndef __WIN32__
-static
-BOOLEAN_T decode_cmsghdr_value(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- ERL_NIF_TERM eType,
- ERL_NIF_TERM eValue,
- char* bufP,
- size_t rem,
- size_t* usedP)
-{
- int type;
- struct cmsghdr *cmsgP = (struct cmsghdr *) bufP;
- struct ESockCmsgSpec *cmsgTable;
- struct ESockCmsgSpec *cmsgSpecP = NULL;
- size_t num = 0;
-
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdr_value {%d} -> entry \r\n"
- " eType: %T\r\n"
- " eValue: %T\r\n",
- descP->sock, eType, eValue) );
-
- // We have decode functions only for symbolic (atom) types
- if (! IS_ATOM(env, eType)) {
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdr_value {%d} -> FALSE:\r\n"
- " eType not an atom\r\n",
- descP->sock) );
- return FALSE;
- }
-
- /* Try to look up the symbolic type
- */
- if (((cmsgTable = lookupCmsgTable(level, &num)) == NULL) ||
- ((cmsgSpecP = lookupCmsgSpec(cmsgTable, num, eType)) == NULL) ||
- (cmsgSpecP->decode == NULL)) {
- /* We found no table for this level,
- * we found no symbolic type in the level table,
- * or no decode function for this type
- */
-
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdr_value {%d} -> FALSE:\r\n"
- " cmsgTable: %p\r\n"
- " cmsgSpecP: %p\r\n",
- descP->sock, cmsgTable, cmsgSpecP) );
- return FALSE;
- }
-
- if (! cmsgSpecP->decode(env, eValue, cmsgP, rem, usedP)) {
- // Decode function failed
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdr_value {%d} -> FALSE:\r\n"
- " decode function failed\r\n",
- descP->sock) );
- return FALSE;
- }
-
- // Successful decode
-
- type = cmsgSpecP->type;
-
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdr_value {%d} -> TRUE:\r\n"
- " level: %d\r\n"
- " type: %d\r\n",
- " *usedP: %lu\r\n",
- descP->sock, level, type, (unsigned long) *usedP) );
-
- cmsgP->cmsg_level = level;
- cmsgP->cmsg_type = type;
- return TRUE;
-}
-#endif
-
-#ifndef __WIN32__
-static
-BOOLEAN_T decode_cmsghdr_data(ErlNifEnv* env,
- ESockDescriptor* descP,
- int level,
- ERL_NIF_TERM eType,
- ERL_NIF_TERM eData,
- char* bufP,
- size_t rem,
- size_t* usedP)
-{
- int type;
- ErlNifBinary bin;
- struct cmsghdr *cmsgP = (struct cmsghdr *) bufP;
- struct ESockCmsgSpec *cmsgSpecP = NULL;
-
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdr_data {%d} -> entry \r\n"
- " eType: %T\r\n"
- " eData: %T\r\n",
- descP->sock, eType, eData) );
-
- // Decode Type
- if (! GET_INT(env, eType, &type)) {
- struct ESockCmsgSpec *cmsgTable = NULL;
- size_t num = 0;
-
- /* Try to look up the symbolic (atom) type
- */
- if ((! IS_ATOM(env, eType)) ||
- ((cmsgTable = lookupCmsgTable(level, &num)) == NULL) ||
- ((cmsgSpecP = lookupCmsgSpec(cmsgTable, num, eType)) == NULL)) {
- /* Type was not an atom,
- * we found no table for this level,
- * or we found no symbolic type in the level table
- */
-
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdr_data {%d} -> FALSE:\r\n"
- " cmsgTable: %p\r\n"
- " cmsgSpecP: %p\r\n",
- descP->sock, cmsgTable, cmsgSpecP) );
- return FALSE;
- }
-
- type = cmsgSpecP->type;
- }
-
- // Decode Data
- if (GET_BIN(env, eData, &bin)) {
- void *p;
-
- p = init_cmsghdr(cmsgP, rem, bin.size, usedP);
- if (p == NULL) {
- /* No room for the data
- */
-
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdr_data {%d} -> FALSE:\r\n"
- " rem: %lu\r\n"
- " bin.size: %lu\r\n",
- descP->sock,
- (unsigned long) rem,
- (unsigned long) bin.size) );
- return FALSE;
- }
-
- // Copy the binary data
- sys_memcpy(p, bin.data, bin.size);
-
- } else if ((! esock_cmsg_decode_int(env, eData, cmsgP, rem, usedP)) &&
- (! esock_cmsg_decode_bool(env, eData, cmsgP, rem, usedP))) {
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdr_data {%d} -> FALSE\r\n",
- descP->sock) );
- return FALSE;
- }
- // Successful decode
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdr_data {%d} -> TRUE:\r\n"
- " level: %d\r\n"
- " type: %d\r\n"
- " *usedP: %lu\r\n",
- descP->sock, level, type, (unsigned long) *usedP) );
-
- cmsgP->cmsg_level = level;
- cmsgP->cmsg_type = type;
- return TRUE;
-}
-#endif
/* Clear the CMSG space and init the ->cmsg_len member,
* return the position for the data, and the total used space
*/
-#ifndef __WIN32__
-static void *init_cmsghdr(struct cmsghdr *cmsgP,
- size_t rem, // Remaining space
- size_t size, // Size of data
- size_t *usedP)
+extern
+void* esock_init_cmsghdr(struct cmsghdr* cmsgP,
+ size_t rem, // Remaining space
+ size_t size, // Size of data
+ size_t* usedP)
{
- size_t space = CMSG_SPACE(size);
+ size_t space = ESOCK_CMSG_SPACE(size);
+ void* dataP;
if (rem < space)
return NULL; // Not enough space
sys_memzero(cmsgP, space);
- cmsgP->cmsg_len = CMSG_LEN(size);
+ cmsgP->cmsg_len = ESOCK_CMSG_LEN(size);
*usedP = space;
- return CMSG_DATA(cmsgP);
-}
-#endif
-
-
-/* +++ decode_cmsghdrs +++
- *
- * Decode a list of cmsg(). There can be 0 or more "blocks".
- *
- * Each element can either be a (erlang) map that needs to be decoded,
- * or a (erlang) binary that just needs to be appended to the control
- * buffer.
- *
- * Our "problem" is that we have no idea much memory we actually need.
- *
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T decode_cmsghdrs(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eCMsg,
- char* cmsgHdrBufP,
- size_t cmsgHdrBufLen,
- size_t* cmsgHdrBufUsed)
-{
- ERL_NIF_TERM elem, tail, list;
- char* bufP;
- size_t rem, used, totUsed = 0;
- unsigned int len;
- int i;
-
- SSDBG( descP, ("SOCKET", "decode_cmsghdrs {%d} -> entry with"
- "\r\n eCMsg: %T"
- "\r\n cmsgHdrBufP: 0x%lX"
- "\r\n cmsgHdrBufLen: %d"
- "\r\n", descP->sock,
- eCMsg, cmsgHdrBufP, cmsgHdrBufLen) );
-
- if (! GET_LIST_LEN(env, eCMsg, &len))
- return FALSE;
-
- SSDBG( descP,
- ("SOCKET",
- "decode_cmsghdrs {%d} -> list length: %d\r\n",
- descP->sock, len) );
-
- for (i = 0, list = eCMsg, rem = cmsgHdrBufLen, bufP = cmsgHdrBufP;
- i < len; i++) {
-
- SSDBG( descP, ("SOCKET", "decode_cmsghdrs {%d} -> process elem %d:"
- "\r\n (buffer) rem: %u"
- "\r\n (buffer) totUsed: %u"
- "\r\n", descP->sock, i, rem, totUsed) );
-
- /* Extract the (current) head of the (cmsg hdr) list */
- if (! GET_LIST_ELEM(env, list, &elem, &tail))
- return FALSE;
-
- used = 0; // Just in case...
- if (! decode_cmsghdr(env, descP, elem, bufP, rem, &used))
- return FALSE;
-
- bufP = CHARP( ULONG(bufP) + used );
- rem = SZT( rem - used );
- list = tail;
- totUsed += used;
-
- }
-
- *cmsgHdrBufUsed = totUsed;
-
- SSDBG( descP, ("SOCKET", "decode_cmsghdrs {%d} -> done"
- "\r\n all %u ctrl headers processed"
- "\r\n totUsed = %lu\r\n",
- descP->sock, len, (unsigned long) totUsed) );
-
- return TRUE;
-}
-#endif // #ifndef __WIN32__
-
-
-/* +++ decode_cmsghdr +++
- *
- * Decode one cmsg(). Put the "result" into the buffer and advance the
- * pointer (of the buffer) afterwards. Also update 'rem' accordingly.
- * But before the actual decode, make sure that there is enough room in
- * the buffer for the cmsg header (sizeof(*hdr) < rem).
- *
- * The eCMsg should be a map with three fields:
- *
- * level :: socket | protocol() | integer()
- * type :: atom() | integer()
- * What values are valid depend on the level
- * data :: binary() | integer() | boolean()
- * The type of the data depends on
- * or level and type, but can be a binary,
- * which means that the data is already coded.
- * value :: term() Which is a term matching the decode function
- */
-#ifndef __WIN32__
-static
-BOOLEAN_T decode_cmsghdr(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM eCMsg,
- char* bufP,
- size_t rem,
- size_t* used)
-{
- ERL_NIF_TERM eLevel, eType, eData, eValue;
- int level;
-
- SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> entry with"
- "\r\n eCMsg: %T"
- "\r\n", descP->sock, eCMsg) );
-
- // Get 'level' field
- if (! GET_MAP_VAL(env, eCMsg, esock_atom_level, &eLevel))
- return FALSE;
- SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> eLevel: %T"
- "\r\n", descP->sock, eLevel) );
-
- // Get 'type' field
- if (! GET_MAP_VAL(env, eCMsg, esock_atom_type, &eType))
- return FALSE;
- SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> eType: %T"
- "\r\n", descP->sock, eType) );
-
- // Decode Level
- if (! esock_decode_level(env, eLevel, &level))
- return FALSE;
- SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d}-> level: %d\r\n",
- descP->sock, level) );
-
- // Get 'data' field
- if (! GET_MAP_VAL(env, eCMsg, esock_atom_data, &eData)) {
-
- // Get 'value' field
- if (! GET_MAP_VAL(env, eCMsg, atom_value, &eValue))
- return FALSE;
- SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> eValue: %T"
- "\r\n", descP->sock, eValue) );
-
- // Decode Value
- if (! decode_cmsghdr_value(env, descP, level, eType, eValue,
- bufP, rem, used))
- return FALSE;
-
- } else {
+ dataP = ESOCK_CMSG_DATA(cmsgP);
- // Verify no 'value' field
- if (GET_MAP_VAL(env, eCMsg, atom_value, &eValue))
- return FALSE;
-
- SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> eData: %T"
- "\r\n", descP->sock, eData) );
-
- // Decode Data
- if (! decode_cmsghdr_data(env, descP, level, eType, eData,
- bufP, rem, used))
- return FALSE;
- }
-
- SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d}-> used: %lu\r\n",
- descP->sock, (unsigned long) *used) );
-
- return TRUE;
-}
-#endif // #ifndef __WIN32__
-
-
-
-/* +++ encode_msg_flags +++
- *
- * Encode a list of msg_flag().
- *
- */
-#ifndef __WIN32__
-static
-void encode_msg_flags(ErlNifEnv* env,
- ESockDescriptor* descP,
- int msgFlags,
- ERL_NIF_TERM* flags)
-{
- SSDBG( descP,
- ("SOCKET", "encode_msg_flags {%d} -> entry with"
- "\r\n msgFlags: %d (0x%lX)"
- "\r\n", descP->sock, msgFlags, msgFlags) );
-
- if (msgFlags == 0) {
- *flags = MKEL(env);
- } else {
- size_t n;
- SocketTArray ta = TARRAY_CREATE(10); // Just to be on the safe side
-
- for (n = 0; n < NUM(msg_flags); n++) {
- int f = msg_flags[n].flag;
- if ((f != 0) && ((msgFlags & f) == f)) {
- msgFlags &= ~f;
- TARRAY_ADD(ta, *(msg_flags[n].name));
- }
- if (msgFlags == 0) goto done;
- }
- /* Append remaining flags as an integer */
- if (msgFlags != 0)
- TARRAY_ADD(ta, MKI(env, msgFlags));
-
- done:
- SSDBG( descP,
- ("SOCKET", "encode_msg_flags {%d} -> flags processed when"
- "\r\n TArray size: %d"
- "\r\n", descP->sock, TARRAY_SZ(ta)) );
-
- TARRAY_TOLIST(ta, env, flags);
- }
+ return dataP;
}
-#endif // #ifndef __WIN32__
/* +++ decode the ip socket option TOS +++
@@ -17874,8 +10974,10 @@ void encode_msg_flags(ErlNifEnv* env,
*
* lowdelay | throughput | reliability | mincost
*
+ *
+ * For Windows, the Microsoft recommendation is: *Do not use*
*/
-#ifndef __WIN32__
+
#if defined(IP_TOS)
static
BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
@@ -17884,6 +10986,14 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
if (IS_ATOM(env, eVal)) {
+#ifdef __WIN32__
+
+ /* See above */
+ *val = -1;
+ result = FALSE;
+
+#else
+
if (COMPARE(eVal, esock_atom_lowdelay) == 0) {
*val = IPTOS_LOWDELAY;
result = TRUE;
@@ -17898,11 +11008,14 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
*val = IPTOS_MINCOST;
result = TRUE;
#endif
+
} else {
*val = -1;
result = FALSE;
}
+#endif // ifdef __WIN32__
+
} else if (IS_NUM(env, eVal)) {
if (GET_INT(env, eVal, val)) {
@@ -17920,7 +11033,7 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
return result;
}
#endif
-#endif // #ifndef __WIN32__
+
/* +++ decode the ip socket option MTU_DISCOVER +++
@@ -17932,20 +11045,23 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
*
* want | dont | do | probe
*
+ * Note that on Windows, the 'want' value seems to not exist!
*/
-#ifndef __WIN32__
+
#if defined(IP_MTU_DISCOVER)
static
BOOLEAN_T decode_ip_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
{
if (IS_ATOM(env, eVal)) {
- if (COMPARE(eVal, atom_want) == 0) {
- *val = IP_PMTUDISC_WANT;
- } else if (COMPARE(eVal, atom_dont) == 0) {
+ if (COMPARE(eVal, atom_dont) == 0) {
*val = IP_PMTUDISC_DONT;
} else if (COMPARE(eVal, atom_do) == 0) {
*val = IP_PMTUDISC_DO;
+#if defined(IP_PMTUDISC_WANT)
+ } else if (COMPARE(eVal, atom_want) == 0) {
+ *val = IP_PMTUDISC_WANT;
+#endif
#if defined(IP_PMTUDISC_PROBE)
} else if (COMPARE(eVal, atom_probe) == 0) {
*val = IP_PMTUDISC_PROBE;
@@ -17961,10 +11077,8 @@ BOOLEAN_T decode_ip_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
return TRUE;
}
#endif
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
#if defined(IPV6_MULTICAST_HOPS) || defined(IPV6_UNICAST_HOPS)
static
BOOLEAN_T decode_hops(ErlNifEnv *env, ERL_NIF_TERM eVal, int *val) {
@@ -17984,7 +11098,6 @@ BOOLEAN_T decode_hops(ErlNifEnv *env, ERL_NIF_TERM eVal, int *val) {
return TRUE;
}
#endif
-#endif // #ifndef __WIN32__
/* +++ decode the ipv6 socket option MTU_DISCOVER +++
@@ -17996,20 +11109,33 @@ BOOLEAN_T decode_hops(ErlNifEnv *env, ERL_NIF_TERM eVal, int *val) {
*
* want | dont | do | probe
*
+ * Use same as IP on Windows!!
*/
-#ifndef __WIN32__
+
#if defined(IPV6_MTU_DISCOVER)
static
BOOLEAN_T decode_ipv6_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
{
if (IS_ATOM(env, eVal)) {
- if (COMPARE(eVal, atom_want) == 0) {
- *val = IPV6_PMTUDISC_WANT;
- } else if (COMPARE(eVal, atom_dont) == 0) {
+#ifdef __WIN32__
+ /* On Windows, the IP-flags are used */
+ if (COMPARE(eVal, atom_dont) == 0) {
+ *val = IP_PMTUDISC_DONT;
+ } else if (COMPARE(eVal, atom_do) == 0) {
+ *val = IP_PMTUDISC_DO;
+ } else if (COMPARE(eVal, atom_probe) == 0) {
+ *val = IP_PMTUDISC_PROBE;
+ } else {
+ return FALSE;
+ }
+#else
+ if (COMPARE(eVal, atom_dont) == 0) {
*val = IPV6_PMTUDISC_DONT;
} else if (COMPARE(eVal, atom_do) == 0) {
*val = IPV6_PMTUDISC_DO;
+ } else if (COMPARE(eVal, atom_want) == 0) {
+ *val = IPV6_PMTUDISC_WANT;
#if defined(IPV6_PMTUDISC_PROBE)
} else if (COMPARE(eVal, atom_probe) == 0) {
*val = IPV6_PMTUDISC_PROBE;
@@ -18017,6 +11143,7 @@ BOOLEAN_T decode_ipv6_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
} else {
return FALSE;
}
+#endif
} else if (! GET_INT(env, eVal, val)) {
return FALSE;
@@ -18025,7 +11152,6 @@ BOOLEAN_T decode_ipv6_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
return TRUE;
}
#endif
-#endif // #ifndef __WIN32__
/* +++ encode the ip socket option MTU_DISCOVER +++
@@ -18038,15 +11164,17 @@ BOOLEAN_T decode_ipv6_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
* want | dont | do | probe
*
*/
-#ifndef __WIN32__
+
#if defined(IP_MTU_DISCOVER)
static
void encode_ip_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal)
{
switch (val) {
+#if defined(IP_PMTUDISC_WANT)
case IP_PMTUDISC_WANT:
*eVal = atom_want;
break;
+#endif
case IP_PMTUDISC_DONT:
*eVal = atom_dont;
@@ -18070,7 +11198,6 @@ void encode_ip_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal)
return;
}
#endif
-#endif // #ifndef __WIN32__
/* +++ encode the ipv6 socket option MTU_DISCOVER +++
@@ -18082,30 +11209,47 @@ void encode_ip_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal)
*
* want | dont | do | probe
*
+ * Windows uses the IP-flags.
*/
-#ifndef __WIN32__
+
#if defined(IPV6_MTU_DISCOVER)
static
void encode_ipv6_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal)
{
switch (val) {
+#if defined(IPV6_PMTUDISC_WANT)
case IPV6_PMTUDISC_WANT:
*eVal = atom_want;
break;
+#endif
+#if defined(__WIN32__)
+ case IP_PMTUDISC_DONT:
+#else
case IPV6_PMTUDISC_DONT:
+#endif
*eVal = atom_dont;
break;
+#if defined(__WIN32__)
+ case IP_PMTUDISC_DO:
+#else
case IPV6_PMTUDISC_DO:
+#endif
*eVal = atom_do;
break;
+#if defined(__WIN32__)
+ case IP_PMTUDISC_PROBE:
+ *eVal = atom_probe;
+ break;
+#else
#if defined(IPV6_PMTUDISC_PROBE)
case IPV6_PMTUDISC_PROBE:
*eVal = atom_probe;
break;
#endif
+#endif
default:
*eVal = MKI(env, val);
@@ -18115,7 +11259,7 @@ void encode_ipv6_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal)
return;
}
#endif
-#endif // #ifndef __WIN32__
+
/* +++ encode the ip socket option tos +++
@@ -18124,24 +11268,30 @@ void encode_ipv6_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal)
* lowdelay | throughput | reliability | mincost | integer()
*
*/
-#ifndef __WIN32__
+
static
ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val)
{
ERL_NIF_TERM result;
switch (IPTOS_TOS(val)) {
+#if defined(IPTOS_LOWDELAY)
case IPTOS_LOWDELAY:
result = esock_atom_lowdelay;
break;
+#endif
+#if defined(IPTOS_THROUGHPUT)
case IPTOS_THROUGHPUT:
result = esock_atom_throughput;
break;
+#endif
+#if defined(IPTOS_RELIABILITY)
case IPTOS_RELIABILITY:
result = esock_atom_reliability;
break;
+#endif
#if defined(IPTOS_MINCOST)
case IPTOS_MINCOST:
@@ -18156,10 +11306,9 @@ ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val)
return result;
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
+
#if defined(SCTP_ASSOCINFO) || defined(SCTP_RTOINOFO)
static
@@ -18203,20 +11352,19 @@ ERL_NIF_TERM encode_sctp_assoc_t(ErlNifEnv* env, sctp_assoc_t val)
}
#endif // #if defined(SCTP_ASSOCINFO) || defined(SCTP_RTOINOFO)
-#endif // #ifdef __WIN32__
-/* *** alloc_descriptor ***
+
+/* *** esock_alloc_descriptor ***
*
* Allocate and perform basic initialization of a socket descriptor.
*
*/
-#ifndef __WIN32__
-static
-ESockDescriptor* alloc_descriptor(SOCKET sock, ErlNifEvent event)
+extern
+ESockDescriptor* esock_alloc_descriptor(SOCKET sock)
{
ESockDescriptor* descP;
- char buf[64]; /* Buffer used for building the mutex name */
+ char buf[64]; /* Buffer used for building the mutex name(s) */
ESOCK_ASSERT( (descP =
enif_alloc_resource(esocks, sizeof(ESockDescriptor)))
@@ -18224,14 +11372,17 @@ ESockDescriptor* alloc_descriptor(SOCKET sock, ErlNifEvent event)
descP->pattern = ESOCK_DESC_PATTERN_CREATED;
- requestor_init(&descP->connector);
+ esock_requestor_init(&descP->connector);
descP->connectorP = NULL;
- sprintf(buf, "esock.w[%d]", sock);
+ sprintf(buf, "esock.w[" SOCKET_FORMAT_STR "]", sock);
descP->writeMtx = MCREATE(buf);
descP->writeState = 0;
- requestor_init(&descP->currentWriter);
+#ifndef __WIN32__
+ /* Not used on Windows - see header for more info */
+ esock_requestor_init(&descP->currentWriter);
descP->currentWriterP = NULL; // currentWriter not used
+#endif
descP->writersQ.first = NULL;
descP->writersQ.last = NULL;
@@ -18248,11 +11399,14 @@ ESockDescriptor* alloc_descriptor(SOCKET sock, ErlNifEvent event)
descP->sendfileCountersP = NULL;
#endif
- sprintf(buf, "esock.r[%d]", sock);
+ sprintf(buf, "esock.r[" SOCKET_FORMAT_STR "]", sock);
descP->readMtx = MCREATE(buf);
descP->readState = 0;
- requestor_init(&descP->currentReader);
+#ifndef __WIN32__
+ /* Not used on Windows - see header for more info */
+ esock_requestor_init(&descP->currentReader);
descP->currentReaderP = NULL; // currentReader not used
+#endif
descP->readersQ.first = NULL;
descP->readersQ.last = NULL;
@@ -18264,9 +11418,12 @@ ESockDescriptor* alloc_descriptor(SOCKET sock, ErlNifEvent event)
descP->readWaits = 0;
descP->readFails = 0;
- sprintf(buf, "esock.acc[%d]", sock);
- requestor_init(&descP->currentAcceptor);
+ sprintf(buf, "esock.acc[" SOCKET_FORMAT_STR "]", sock);
+#ifndef __WIN32__
+ /* Not used on Windows - see header for more info */
+ esock_requestor_init(&descP->currentAcceptor);
descP->currentAcceptorP = NULL; // currentAcceptor not used
+#endif
descP->acceptorsQ.first = NULL;
descP->acceptorsQ.last = NULL;
descP->accSuccess = 0;
@@ -18274,130 +11431,162 @@ ESockDescriptor* alloc_descriptor(SOCKET sock, ErlNifEvent event)
descP->accTries = 0;
descP->accWaits = 0;
- sprintf(buf, "esock.close[%d]", sock);
+ sprintf(buf, "esock.close[" SOCKET_FORMAT_STR "]", sock);
descP->closeEnv = NULL;
descP->closeRef = esock_atom_undefined;
enif_set_pid_undefined(&descP->closerPid);
MON_INIT(&descP->closerMon);
- sprintf(buf, "esock.cfg[%d]", sock);
+ sprintf(buf, "esock.cfg[" SOCKET_FORMAT_STR "]", sock);
descP->rBufSz = ESOCK_RECV_BUFFER_SIZE_DEFAULT;
+#ifndef __WIN32__
descP->rNum = ESOCK_RECV_BUFFER_COUNT_DEFAULT;
descP->rNumCnt = 0;
+#endif
descP->rCtrlSz = ESOCK_RECV_CTRL_BUFFER_SIZE_DEFAULT;
descP->wCtrlSz = ESOCK_SEND_CTRL_BUFFER_SIZE_DEFAULT;
descP->iow = FALSE;
- descP->dbg = ESOCK_DEBUG_DEFAULT; // Overwritten by caller
- descP->useReg = ESOCK_USE_SOCKET_REGISTRY; // Overwritten by caller
- descP->meta.env = esock_alloc_env("alloc_descriptor - "
+ descP->dbg = ESOCK_DEBUG_DEFAULT; // Overwritten by caller
+ descP->useReg = ESOCK_USE_SOCKET_REGISTRY;// Overwritten by caller
+ descP->meta.env = esock_alloc_env("esock_alloc_descriptor - "
"meta-env");
descP->meta.ref = esock_atom_undefined;
descP->sock = sock;
- descP->event = event;
descP->origFD = INVALID_SOCKET;
descP->closeOnClose = TRUE;
enif_set_pid_undefined(&descP->ctrlPid);
MON_INIT(&descP->ctrlMon);
+#if defined(ESOCK_DESCRIPTOR_FILLER)
+ sys_memzero(descP->filler, sizeof(descP->filler));
+#endif
+
return descP;
}
-#endif // #ifndef __WIN32__
+
+
+/* This function is *only* called during 'open' after an
+ * descriptor has been allocated but before it has been used.
+ */
+extern
+void esock_dealloc_descriptor(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ if (descP->writeMtx != NULL) {
+ MDESTROY(descP->writeMtx);
+ descP->writeMtx = NULL;
+ }
+
+ if (descP->readMtx != NULL) {
+ MDESTROY(descP->readMtx);
+ descP->readMtx = NULL;
+ }
+
+ if (descP->closeEnv != NULL) {
+ esock_free_env("dealloc descriptor", descP->closeEnv);
+ descP->closeEnv = NULL;
+ }
+
+ if (descP->meta.env != NULL) {
+ esock_free_env("dealloc descriptor", descP->meta.env);
+ descP->meta.env = NULL;
+ }
+
+}
+
+
/* Decrement counters for when a socket is closed
*/
-#ifndef __WIN32__
-static
-void dec_socket(int domain, int type, int protocol)
+extern
+void esock_dec_socket(int domain, int type, int protocol)
{
MLOCK(data.cntMtx);
- cnt_dec(&data.numSockets, 1);
+ esock_cnt_dec(&data.numSockets, 1);
/* *** Domain counter *** */
if (domain == AF_INET)
- cnt_dec(&data.numDomainInet, 1);
+ esock_cnt_dec(&data.numDomainInet, 1);
#if defined(HAVE_IN6) && defined(AF_INET6)
else if (domain == AF_INET6)
- cnt_dec(&data.numDomainInet6, 1);
+ esock_cnt_dec(&data.numDomainInet6, 1);
#endif
-#ifdef HAS_AF_LOCAL
+#if defined(HAS_AF_LOCAL)
else if (domain == AF_LOCAL)
- cnt_dec(&data.numDomainInet6, 1);
+ esock_cnt_dec(&data.numDomainInet6, 1);
#endif
/* *** Type counter *** */
if (type == SOCK_STREAM)
- cnt_dec(&data.numTypeStreams, 1);
+ esock_cnt_dec(&data.numTypeStreams, 1);
else if (type == SOCK_DGRAM)
- cnt_dec(&data.numTypeDGrams, 1);
-#ifdef SOCK_SEQPACKET
+ esock_cnt_dec(&data.numTypeDGrams, 1);
+#if defined(SOCK_SEQPACKET)
else if (type == SOCK_SEQPACKET)
- cnt_dec(&data.numTypeSeqPkgs, 1);
+ esock_cnt_dec(&data.numTypeSeqPkgs, 1);
#endif
/* *** Protocol counter *** */
if (protocol == IPPROTO_IP)
- cnt_dec(&data.numProtoIP, 1);
+ esock_cnt_dec(&data.numProtoIP, 1);
else if (protocol == IPPROTO_TCP)
- cnt_dec(&data.numProtoTCP, 1);
+ esock_cnt_dec(&data.numProtoTCP, 1);
else if (protocol == IPPROTO_UDP)
- cnt_dec(&data.numProtoUDP, 1);
+ esock_cnt_dec(&data.numProtoUDP, 1);
#if defined(HAVE_SCTP)
else if (protocol == IPPROTO_SCTP)
- cnt_dec(&data.numProtoSCTP, 1);
+ esock_cnt_dec(&data.numProtoSCTP, 1);
#endif
MUNLOCK(data.cntMtx);
}
-#endif // #ifndef __WIN32__
/* Increment counters for when a socket is opened
*/
-#ifndef __WIN32__
-static
-void inc_socket(int domain, int type, int protocol)
+extern
+void esock_inc_socket(int domain, int type, int protocol)
{
- cnt_inc(&data.numSockets, 1);
+ esock_cnt_inc(&data.numSockets, 1);
/* *** Domain counter *** */
if (domain == AF_INET)
- cnt_inc(&data.numDomainInet, 1);
+ esock_cnt_inc(&data.numDomainInet, 1);
#if defined(HAVE_IN6) && defined(AF_INET6)
else if (domain == AF_INET6)
- cnt_inc(&data.numDomainInet6, 1);
+ esock_cnt_inc(&data.numDomainInet6, 1);
#endif
-#ifdef HAS_AF_LOCAL
+#if defined(HAS_AF_LOCAL)
else if (domain == AF_LOCAL)
- cnt_inc(&data.numDomainInet6, 1);
+ esock_cnt_inc(&data.numDomainInet6, 1);
#endif
/* *** Type counter *** */
if (type == SOCK_STREAM)
- cnt_inc(&data.numTypeStreams, 1);
+ esock_cnt_inc(&data.numTypeStreams, 1);
else if (type == SOCK_DGRAM)
- cnt_inc(&data.numTypeDGrams, 1);
-#ifdef SOCK_SEQPACKET
+ esock_cnt_inc(&data.numTypeDGrams, 1);
+#if defined(SOCK_SEQPACKET)
else if (type == SOCK_SEQPACKET)
- cnt_inc(&data.numTypeSeqPkgs, 1);
+ esock_cnt_inc(&data.numTypeSeqPkgs, 1);
#endif
/* *** Protocol counter *** */
if (protocol == IPPROTO_IP)
- cnt_inc(&data.numProtoIP, 1);
+ esock_cnt_inc(&data.numProtoIP, 1);
else if (protocol == IPPROTO_TCP)
- cnt_inc(&data.numProtoTCP, 1);
+ esock_cnt_inc(&data.numProtoTCP, 1);
else if (protocol == IPPROTO_UDP)
- cnt_inc(&data.numProtoUDP, 1);
+ esock_cnt_inc(&data.numProtoUDP, 1);
#if defined(HAVE_SCTP)
else if (protocol == IPPROTO_SCTP)
- cnt_inc(&data.numProtoSCTP, 1);
+ esock_cnt_inc(&data.numProtoSCTP, 1);
#endif
}
-#endif // #ifndef __WIN32__
@@ -18406,44 +11595,10 @@ void inc_socket(int domain, int type, int protocol)
* ----------------------------------------------------------------------
*/
-#ifndef __WIN32__
-#ifdef HAVE_SETNS
-/* esock_open4_get_netns - extract the netns field from the opts map
- */
-static
-BOOLEAN_T esock_open4_get_netns(ErlNifEnv* env, ERL_NIF_TERM opts, char** netns)
-{
- ERL_NIF_TERM val;
- ErlNifBinary bin;
- char* buf;
-
- /* The currently only supported extra option is: netns */
- if (!GET_MAP_VAL(env, opts, atom_netns, &val)) {
- *netns = NULL; // Just in case...
- return FALSE;
- }
-
- /* The value should be a binary file name */
- if (! enif_inspect_binary(env, val, &bin)) {
- *netns = NULL; // Just in case...
- return FALSE;
- }
-
- ESOCK_ASSERT( (buf = MALLOC(bin.size+1)) != NULL );
-
- sys_memcpy(buf, bin.data, bin.size);
- buf[bin.size] = '\0';
- *netns = buf;
- return TRUE;
-}
-#endif
-#endif // #ifndef __WIN32__
-
/* ehow2how - convert internal (erlang) "shutdown how" to
* (proper) "shutdown how"
*/
-#ifndef __WIN32__
static
BOOLEAN_T ehow2how(ERL_NIF_TERM ehow, int* how)
{
@@ -18451,22 +11606,33 @@ BOOLEAN_T ehow2how(ERL_NIF_TERM ehow, int* how)
cmp = COMPARE(ehow, atom_read_write);
if (cmp == 0)
+#ifdef __WIN32__
+ *how = SD_BOTH;
+#else
*how = SHUT_RDWR;
+#endif
else if (cmp < 0) {
if (COMPARE(ehow, atom_read) == 0)
+#ifdef __WIN32__
+ *how = SD_RECEIVE;
+#else
*how = SHUT_RD;
+#endif
else
return FALSE;
} else {
if (COMPARE(ehow, atom_write) == 0)
+#ifdef __WIN32__
+ *how = SD_SEND;
+#else
*how = SHUT_WR;
+#endif
else
return FALSE;
}
return TRUE;
}
-#endif // #ifndef __WIN32__
@@ -18501,8 +11667,7 @@ size_t my_strnlen(const char *s, size_t maxlen)
* terminate otherwise, so there is no need to test if
* the sending fails.
*/
-#ifndef __WIN32__
-static
+extern
void esock_send_reg_add_msg(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef)
@@ -18518,7 +11683,6 @@ void esock_send_reg_add_msg(ErlNifEnv* env,
sockRef, descP->sock, MKPID(env, &data.regPid)) );
}
}
-#endif // #ifndef __WIN32__
@@ -18527,8 +11691,7 @@ void esock_send_reg_add_msg(ErlNifEnv* env,
* terminate otherwise, so there is no need to test if
* the sending fails.
*/
-#ifndef __WIN32__
-static
+extern
void esock_send_reg_del_msg(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef)
@@ -18544,9 +11707,6 @@ void esock_send_reg_del_msg(ErlNifEnv* env,
sockRef, descP->sock, MKPID(env, &data.regPid)) );
}
}
-#endif // #ifndef __WIN32__
-
-
/* ===========================================================================
@@ -18563,8 +11723,7 @@ void esock_send_reg_del_msg(ErlNifEnv* env,
*
* This message will only be sent if the iow (Inform On Wrap) is TRUE.
*/
-#ifndef __WIN32__
-static
+extern
void esock_send_wrap_msg(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
@@ -18582,7 +11741,6 @@ void esock_send_wrap_msg(ErlNifEnv* env,
sockRef, descP->sock, MKPID(env, &descP->ctrlPid), cnt) );
}
}
-#endif // #ifndef __WIN32__
/* Send an close message to the specified process:
@@ -18594,8 +11752,7 @@ void esock_send_wrap_msg(ErlNifEnv* env,
* erlang API (close-) function for the socket to be "closed"
* (actually that the 'stop' callback function has been called).
*/
-#ifndef __WIN32__
-static
+extern
void esock_send_close_msg(ErlNifEnv* env,
ESockDescriptor* descP,
ErlNifPid* pid)
@@ -18615,17 +11772,20 @@ void esock_send_close_msg(ErlNifEnv* env,
sockRef, descP->sock, MKPID(env, pid), descP->closeRef) );
}
}
+
+
+
#ifdef HAVE_SENDFILE
-static void
-esock_send_sendfile_deferred_close_msg(ErlNifEnv* env,
- ESockDescriptor* descP)
+extern
+void esock_send_sendfile_deferred_close_msg(ErlNifEnv* env,
+ ESockDescriptor* descP)
{
ERL_NIF_TERM sockRef, msg;
ErlNifPid *pid;
pid = &data.regPid;
sockRef = enif_make_resource(env, descP);
- msg = mk_reg_msg(env, atom_sendfile_deferred_close, sockRef);
+ msg = mk_reg_msg(env, esock_atom_sendfile_deferred_close, sockRef);
/* If this send should fail we have leaked a file descriptor
* (intolerable), and if we try to close it here, on a regular
@@ -18636,7 +11796,6 @@ esock_send_sendfile_deferred_close_msg(ErlNifEnv* env,
ESOCK_ASSERT( esock_send_msg(env, pid, msg, NULL) );
}
#endif // #ifdef HAVE_SENDFILE
-#endif // #ifndef __WIN32__
/* Send an abort message to the specified process:
@@ -18647,8 +11806,7 @@ esock_send_sendfile_deferred_close_msg(ErlNifEnv* env,
* This message is for processes that is waiting in the
* erlang API functions for a select message.
*/
-#ifndef __WIN32__
-static
+extern
void esock_send_abort_msg(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
@@ -18661,10 +11819,11 @@ void esock_send_abort_msg(ErlNifEnv* env,
{
ERL_NIF_TERM msg;
- msg =
- mk_abort_msg(reqP->env,
- /* sockRef not in env so copy */
- CP_TERM(reqP->env, sockRef), reqP->ref, reason);
+ msg = mk_abort_msg(reqP->env,
+ /* sockRef not in env so copy */
+ CP_TERM(reqP->env, sockRef),
+ reqP->ref,
+ CP_TERM(reqP->env, reason));
if (! esock_send_msg(env, &reqP->pid, msg, reqP->env)) {
SSDBG( descP,
@@ -18676,13 +11835,38 @@ void esock_send_abort_msg(ErlNifEnv* env,
}
reqP->env = NULL;
}
-#endif // #ifndef __WIN32__
+
+
+/* Send an *simple* abort message to the specified process:
+ * A message in the form:
+ *
+ * {'$socket', Socket, abort, Info}
+ *
+ */
+extern
+void esock_send_simple_abort_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* pid,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason)
+{
+ ERL_NIF_TERM msg = mk_simple_abort_msg(env, sockRef, reason);
+
+ if (! esock_send_msg(env, pid, msg, NULL)) {
+
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_send_simple_abort_msg(%T) {%d} failed ->"
+ "\r\n pid: %T"
+ "\r\n",
+ sockRef, descP->sock, MKPID(env, pid)) );
+ }
+}
/* Send a message to the specified process.
*/
-#ifndef __WIN32__
-static
+extern
BOOLEAN_T esock_send_msg(ErlNifEnv* env,
ErlNifPid* pid,
ERL_NIF_TERM msg,
@@ -18693,7 +11877,6 @@ BOOLEAN_T esock_send_msg(ErlNifEnv* env,
return !!res;
}
-#endif // #ifndef __WIN32__
@@ -18704,14 +11887,12 @@ BOOLEAN_T esock_send_msg(ErlNifEnv* env,
* {'$socket', add, Socket}
*
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM mk_reg_add_msg(ErlNifEnv* env,
ERL_NIF_TERM sockRef)
{
return mk_reg_msg(env, atom_add, sockRef);
}
-#endif // #ifndef __WIN32__
/* *** mk_reg_del_msg ***
@@ -18721,14 +11902,12 @@ ERL_NIF_TERM mk_reg_add_msg(ErlNifEnv* env,
* {'$socket', del, Socket}
*
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM mk_reg_del_msg(ErlNifEnv* env,
ERL_NIF_TERM sockRef)
{
return mk_reg_msg(env, atom_del, sockRef);
}
-#endif // #ifndef __WIN32__
/* *** mk_reg_msg ***
@@ -18739,17 +11918,31 @@ ERL_NIF_TERM mk_reg_del_msg(ErlNifEnv* env,
* {'$socket', Tag, Socket}
*
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM mk_reg_msg(ErlNifEnv* env,
ERL_NIF_TERM tag,
ERL_NIF_TERM sockRef)
{
- ERL_NIF_TERM socket = mk_socket(env, sockRef);
+ ERL_NIF_TERM socket = esock_mk_socket(env, sockRef);
return MKT3(env, esock_atom_socket_tag, tag, socket);
}
-#endif // #ifndef __WIN32__
+
+
+/* *** mk_simple_abort_msg ***
+ *
+ * Create the simple abort message, which has the following form:
+ *
+ * {'$socket', Socket, abort, Info}
+ *
+ */
+static
+ERL_NIF_TERM mk_simple_abort_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason)
+{
+ return esock_mk_socket_msg(env, sockRef, esock_atom_abort, reason);
+}
/* *** mk_abort_msg ***
@@ -18761,7 +11954,6 @@ ERL_NIF_TERM mk_reg_msg(ErlNifEnv* env,
* This message is for processes that are waiting in the
* erlang API functions for a select (or this) message.
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM mk_abort_msg(ErlNifEnv* env,
ERL_NIF_TERM sockRef,
@@ -18770,9 +11962,8 @@ ERL_NIF_TERM mk_abort_msg(ErlNifEnv* env,
{
ERL_NIF_TERM info = MKT2(env, opRef, reason);
- return mk_socket_msg(env, sockRef, esock_atom_abort, info);
+ return esock_mk_socket_msg(env, sockRef, esock_atom_abort, info);
}
-#endif // #ifndef __WIN32__
/* *** mk_wrap_msg ***
@@ -18782,15 +11973,13 @@ ERL_NIF_TERM mk_abort_msg(ErlNifEnv* env,
* {'$socket', Socket, counter_wrap, Counter}
*
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM mk_wrap_msg(ErlNifEnv* env,
ERL_NIF_TERM sockRef,
ERL_NIF_TERM cnt)
{
- return mk_socket_msg(env, sockRef, atom_counter_wrap, cnt);
+ return esock_mk_socket_msg(env, sockRef, atom_counter_wrap, cnt);
}
-#endif // #ifndef __WIN32__
/* *** mk_close_msg ***
@@ -18800,15 +11989,14 @@ ERL_NIF_TERM mk_wrap_msg(ErlNifEnv* env,
* {'$socket', Socket, close, closeRef}
*
*/
-#ifndef __WIN32__
static
ERL_NIF_TERM mk_close_msg(ErlNifEnv* env,
ERL_NIF_TERM sockRef,
ERL_NIF_TERM closeRef)
{
- return mk_socket_msg(env, sockRef, esock_atom_close, closeRef);
+ return esock_mk_socket_msg(env, sockRef, esock_atom_close, closeRef);
}
-#endif // #ifndef __WIN32__
+
/* *** mk_select_msg ***
@@ -18824,12 +12012,12 @@ ERL_NIF_TERM mk_select_msg(ErlNifEnv* env,
ERL_NIF_TERM sockRef,
ERL_NIF_TERM selectRef)
{
- return mk_socket_msg(env, sockRef, atom_select, selectRef);
+ return esock_mk_socket_msg(env, sockRef, esock_atom_select, selectRef);
}
#endif // #ifndef __WIN32__
-/* *** mk_socket_msg ***
+/* *** esock_mk_socket_msg ***
*
* Construct the socket message:
*
@@ -18840,18 +12028,16 @@ ERL_NIF_TERM mk_select_msg(ErlNifEnv* env,
* Info :: term()
*
*/
-#ifndef __WIN32__
-static
-ERL_NIF_TERM mk_socket_msg(ErlNifEnv* env,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM tag,
- ERL_NIF_TERM info)
+extern
+ERL_NIF_TERM esock_mk_socket_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM tag,
+ ERL_NIF_TERM info)
{
- ERL_NIF_TERM socket = mk_socket(env, sockRef);
+ ERL_NIF_TERM socket = esock_mk_socket(env, sockRef);
return MKT4(env, esock_atom_socket_tag, socket, tag, info);
}
-#endif // #ifndef __WIN32__
/* *** mk_socket ***
@@ -18860,14 +12046,12 @@ ERL_NIF_TERM mk_socket_msg(ErlNifEnv* env,
*
* socket:socket() :: {'$socket', SockRef :: reference()}
*/
-#ifndef __WIN32__
-static
-ERL_NIF_TERM mk_socket(ErlNifEnv* env,
- ERL_NIF_TERM sockRef)
+extern
+ERL_NIF_TERM esock_mk_socket(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef)
{
return MKT2(env, esock_atom_socket_tag, sockRef);
}
-#endif // #ifndef __WIN32__
/* ----------------------------------------------------------------------
@@ -18890,7 +12074,7 @@ ERL_NIF_TERM mk_socket(ErlNifEnv* env,
* We choose the second alternative.
*/
#ifndef __WIN32__
-static
+extern
int esock_select_read(ErlNifEnv* env,
ErlNifEvent event, // The file descriptor
void* obj, // The socket descriptor object
@@ -18914,7 +12098,7 @@ int esock_select_read(ErlNifEnv* env,
* so no need to do that here, but the selectRef needs to be copied.
*/
#ifndef __WIN32__
-static
+extern
int esock_select_write(ErlNifEnv* env,
ErlNifEvent event, // The file descriptor
void* obj, // The socket descriptor
@@ -18938,8 +12122,7 @@ int esock_select_write(ErlNifEnv* env,
* So readMtx and writeMtx are supposed to be locked
* when this function is called.
*/
-#ifndef __WIN32__
-static
+extern
int esock_select_stop(ErlNifEnv* env,
ErlNifEvent event,
void* obj)
@@ -18947,10 +12130,8 @@ int esock_select_stop(ErlNifEnv* env,
return enif_select(env, event, (ERL_NIF_SELECT_STOP), obj, NULL,
esock_atom_undefined);
}
-#endif // #ifndef __WIN32__
-#ifndef __WIN32__
-static
+extern
int esock_select_cancel(ErlNifEnv* env,
ErlNifEvent event,
enum ErlNifSelectFlags mode,
@@ -18959,7 +12140,7 @@ int esock_select_cancel(ErlNifEnv* env,
return enif_select(env, event, (ERL_NIF_SELECT_CANCEL | mode), obj, NULL,
esock_atom_undefined);
}
-#endif // #ifndef __WIN32__
+
/* ----------------------------------------------------------------------
@@ -18967,9 +12148,9 @@ int esock_select_cancel(ErlNifEnv* env,
* ----------------------------------------------------------------------
*/
-/* *** activate_next_acceptor ***
- * *** activate_next_writer ***
- * *** activate_next_reader ***
+/* *** esock_activate_next_acceptor ***
+ * *** esock_activate_next_writer ***
+ * *** esock_activate_next_reader ***
*
* This functions pops the requestors queue and then selects until it
* manages to successfully activate a requestor or the queue is empty.
@@ -18983,11 +12164,11 @@ int esock_select_cancel(ErlNifEnv* env,
ACTIVATE_NEXT_FUNC_DECL(writer, write, currentWriter, writersQ) \
ACTIVATE_NEXT_FUNC_DECL(reader, read, currentReader, readersQ)
-#define ACTIVATE_NEXT_FUNC_DECL(F, S, R, Q) \
- static \
- BOOLEAN_T activate_next_##F(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ERL_NIF_TERM sockRef) \
+#define ACTIVATE_NEXT_FUNC_DECL(F, S, R, Q) \
+ extern \
+ BOOLEAN_T esock_activate_next_##F(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM sockRef) \
{ \
BOOLEAN_T popped, activated; \
int sres; \
@@ -18998,13 +12179,13 @@ int esock_select_cancel(ErlNifEnv* env,
popped = FALSE; \
do { \
\
- if (requestor_pop(q, reqP)) { \
+ if (esock_requestor_pop(q, reqP)) { \
\
/* There was another one */ \
\
SSDBG( descP, \
("SOCKET", \
- "activate_next_" #F "(%T) {%d} ->" \
+ "esock_activate_next_" #F "(%T) {%d} ->" \
" new (active) requestor: " \
"\r\n pid: %T" \
"\r\n ref: %T" \
@@ -19042,7 +12223,7 @@ int esock_select_cancel(ErlNifEnv* env,
\
SSDBG( descP, \
("SOCKET", \
- "activate_next_" #F "(%T) {%d} ->" \
+ "esock_activate_next_" #F "(%T) {%d} ->" \
" no more requestors\r\n", \
sockRef, descP->sock) ); \
\
@@ -19053,7 +12234,7 @@ int esock_select_cancel(ErlNifEnv* env,
} while (!popped); \
\
SSDBG( descP, \
- ("SOCKET", "activate_next_" #F "(%T) {%d} -> " \
+ ("SOCKET", "esock_activate_next_" #F "(%T) {%d} -> " \
"done with %s\r\n", \
sockRef, descP->sock, B2S(activated)) ); \
\
@@ -19074,11 +12255,22 @@ ACTIVATE_NEXT_FUNCS
* we make use of set of declaration macros.
*/
-#ifndef __WIN32__
-/* *** acceptor_search4pid ***
- * *** writer_search4pid ***
- * *** reader_search4pid ***
+extern
+void esock_free_request_queue(ESockRequestQueue* q)
+{
+ while (q->first) {
+ ESockRequestQueueElement* free_me = q->first;
+ q->first = free_me->nextP;
+ esock_free_env("dtor", free_me->data.env);
+ FREE(free_me);
+ }
+}
+
+
+/* *** esock_acceptor_search4pid ***
+ * *** esock_writer_search4pid ***
+ * *** esock_reader_search4pid ***
*
* Search for a pid in the requestor (acceptor, writer, or reader) queue.
*
@@ -19089,43 +12281,41 @@ ACTIVATE_NEXT_FUNCS
REQ_SEARCH4PID_FUNC_DECL(writer, writersQ) \
REQ_SEARCH4PID_FUNC_DECL(reader, readersQ)
-#define REQ_SEARCH4PID_FUNC_DECL(F, Q) \
- static \
- BOOLEAN_T F##_search4pid(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ErlNifPid* pid) \
- { \
- return qsearch4pid(env, &descP->Q, pid); \
+#define REQ_SEARCH4PID_FUNC_DECL(F, Q) \
+ extern \
+ BOOLEAN_T esock_##F##_search4pid(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ErlNifPid* pid) \
+ { \
+ return qsearch4pid(env, &descP->Q, pid); \
}
REQ_SEARCH4PID_FUNCS
#undef REQ_SEARCH4PID_FUNC_DECL
-#endif // #ifndef __WIN32__
-/* *** acceptor_push ***
- * *** writer_push ***
- * *** reader_push ***
+/* *** esock_acceptor_push ***
+ * *** esock_writer_push ***
+ * *** esock_reader_push ***
*
* Push a requestor (acceptor, writer, or reader) onto its queue.
* This happens when we already have a current request (of its type).
*
*/
-#ifndef __WIN32__
-
#define REQ_PUSH_FUNCS \
REQ_PUSH_FUNC_DECL(acceptor, acceptorsQ) \
REQ_PUSH_FUNC_DECL(writer, writersQ) \
REQ_PUSH_FUNC_DECL(reader, readersQ)
#define REQ_PUSH_FUNC_DECL(F, Q) \
- static \
- void F##_push(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ErlNifPid pid, /* self() */ \
- ERL_NIF_TERM ref) \
+ extern \
+ void esock_##F##_push(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ErlNifPid pid, /* self() */ \
+ ERL_NIF_TERM ref, \
+ void* dataP) \
{ \
ESockRequestQueueElement *e; \
ESockRequestor *reqP; \
@@ -19133,24 +12323,22 @@ REQ_SEARCH4PID_FUNCS
ESOCK_ASSERT( (e = MALLOC(sizeof(ESockRequestQueueElement))) \
!= NULL ); \
reqP = &e->data; \
- reqP->pid = pid; \
- ESOCK_ASSERT( MONP(#F "_push -> " #F " request", \
+ reqP->dataP = dataP; \
+ reqP->pid = pid; \
+ ESOCK_ASSERT( MONP("esock_" #F "_push -> " #F " request", \
env, descP, &pid, &reqP->mon) == 0 ); \
- reqP->env = esock_alloc_env(#F "_push"); \
- reqP->ref = CP_TERM(reqP->env, ref); \
+ reqP->env = esock_alloc_env("esock_" #F "_push"); \
+ reqP->ref = CP_TERM(reqP->env, ref); \
\
qpush(&descP->Q, e); \
}
REQ_PUSH_FUNCS
#undef REQ_PUSH_FUNC_DECL
-#endif // #ifndef __WIN32__
-
-
-/* *** acceptor_pop ***
- * *** writer_pop ***
- * *** reader_pop ***
+/* *** esock_acceptor_pop ***
+ * *** esock_writer_pop ***
+ * *** esock_reader_pop ***
*
* Pop a requestor (acceptor, writer, or reader) from its queue.
*
@@ -19163,13 +12351,13 @@ REQ_PUSH_FUNCS
REQ_POP_FUNC_DECL(writer, writersQ) \
REQ_POP_FUNC_DECL(reader, readersQ)
-#define REQ_POP_FUNC_DECL(F, Q) \
- static \
- BOOLEAN_T F##_pop(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ESockRequestor* reqP) \
- { \
- return requestor_pop(&descP->Q, reqP); \
+#define REQ_POP_FUNC_DECL(F, Q) \
+ extern \
+ BOOLEAN_T esock_##F##_pop(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ESockRequestor* reqP) \
+ { \
+ return esock_requestor_pop(&descP->Q, reqP); \
}
REQ_POP_FUNCS
#undef REQ_POP_FUNC_DECL
@@ -19178,15 +12366,57 @@ REQ_POP_FUNCS
-/* *** acceptor_unqueue ***
- * *** writer_unqueue ***
- * *** reader_unqueue ***
+/* *** esock_acceptor_get ***
+ * *** esock_writer_get ***
+ * *** esock_reader_get ***
*
* Remove a requestor (acceptor, writer, or reader) from its queue.
*
*/
-#ifndef __WIN32__
+#ifdef __WIN32__
+
+#define REQ_GET_FUNCS \
+ REQ_GET_FUNC_DECL(acceptor, acceptorsQ) \
+ REQ_GET_FUNC_DECL(writer, writersQ) \
+ REQ_GET_FUNC_DECL(reader, readersQ)
+
+#define REQ_GET_FUNC_DECL(F, Q) \
+ extern \
+ BOOLEAN_T esock_##F##_get(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM* refP, \
+ const ErlNifPid* pidP, \
+ ESockRequestor* reqP) \
+ { \
+ ESockRequestQueueElement* elemP; \
+ \
+ elemP = qget(env, descP, "esock_" #F "_get ", \
+ &descP->Q, refP, pidP); \
+ if (elemP != NULL) { \
+ reqP->pid = elemP->data.pid; \
+ reqP->mon = elemP->data.mon; \
+ reqP->env = elemP->data.env; \
+ reqP->ref = elemP->data.ref; \
+ reqP->dataP = elemP->data.dataP; \
+ return TRUE; \
+ } \
+ return FALSE; \
+ }
+REQ_GET_FUNCS
+#undef REQ_GET_FUNC_DECL
+
+#endif // #ifndef __WIN32__
+
+
+
+/* *** esock_acceptor_unqueue ***
+ * *** esock_writer_unqueue ***
+ * *** esock_reader_unqueue ***
+ *
+ * Remove a requestor (acceptor, writer, or reader) from its queue.
+ *
+ */
#define REQ_UNQUEUE_FUNCS \
REQ_UNQUEUE_FUNC_DECL(acceptor, acceptorsQ) \
@@ -19194,11 +12424,11 @@ REQ_POP_FUNCS
REQ_UNQUEUE_FUNC_DECL(reader, readersQ)
#define REQ_UNQUEUE_FUNC_DECL(F, Q) \
- static \
- BOOLEAN_T F##_unqueue(ErlNifEnv* env, \
- ESockDescriptor* descP, \
- ERL_NIF_TERM* refP, \
- const ErlNifPid* pidP) \
+ extern \
+ BOOLEAN_T esock_##F##_unqueue(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM* refP, \
+ const ErlNifPid* pidP) \
{ \
return qunqueue(env, descP, "qunqueue -> waiting " #F, \
&descP->Q, refP, pidP); \
@@ -19206,63 +12436,62 @@ REQ_POP_FUNCS
REQ_UNQUEUE_FUNCS
#undef REQ_UNQUEUE_FUNC_DECL
-#endif // #ifndef __WIN32__
-
-
/* *** requestor pop ***
*
* Pop an requestor from its queue.
*/
-#ifndef __WIN32__
-
-static
-BOOLEAN_T requestor_pop(ESockRequestQueue* q,
- ESockRequestor* reqP)
+extern
+BOOLEAN_T esock_requestor_pop(ESockRequestQueue* q,
+ ESockRequestor* reqP)
{
ESockRequestQueueElement* e = qpop(q);
esock_free_env("requestor_pop", reqP->env);
if (e != NULL) {
- reqP->pid = e->data.pid;
- reqP->mon = e->data.mon;
- reqP->env = e->data.env;
- reqP->ref = e->data.ref;
+ reqP->pid = e->data.pid;
+ reqP->mon = e->data.mon;
+ reqP->env = e->data.env;
+ reqP->ref = e->data.ref;
+ reqP->dataP = e->data.dataP;
FREE(e);
return TRUE;
} else {
/* Queue was empty */
- requestor_init(reqP);
+ esock_requestor_init(reqP);
return FALSE;
}
}
-static void requestor_init(ESockRequestor* reqP) {
+extern
+void esock_requestor_init(ESockRequestor* reqP)
+{
enif_set_pid_undefined(&reqP->pid);
MON_INIT(&reqP->mon);
- reqP->env = NULL;
- reqP->ref = esock_atom_undefined;
+ reqP->env = NULL;
+ reqP->ref = esock_atom_undefined;
+ reqP->dataP = NULL;
}
-static void requestor_release(const char* slogan,
- ErlNifEnv* env,
- ESockDescriptor* descP,
- ESockRequestor* reqP) {
-
+extern
+void esock_requestor_release(const char* slogan,
+ ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESockRequestor* reqP)
+{
+ reqP->dataP = NULL;
enif_set_pid_undefined(&reqP->pid);
(void) DEMONP(slogan, env, descP, &reqP->mon);
+ esock_clear_env(slogan, reqP->env);
esock_free_env(slogan, reqP->env);
reqP->env = NULL;
reqP->ref = esock_atom_undefined;
}
-#endif // #ifndef __WIN32__
-
-#ifndef __WIN32__
static
BOOLEAN_T qsearch4pid(ErlNifEnv* env,
@@ -19282,6 +12511,22 @@ BOOLEAN_T qsearch4pid(ErlNifEnv* env,
}
static
+unsigned int qlength(ESockRequestQueue* q)
+{
+ ESockRequestQueueElement* tmp;
+ unsigned int cnt = 0;
+
+ tmp = q->first;
+ while (tmp != NULL) {
+ cnt++;
+ tmp = tmp->nextP;
+ }
+
+ return cnt;
+}
+
+
+static
void qpush(ESockRequestQueue* q,
ESockRequestQueueElement* e)
{
@@ -19296,6 +12541,7 @@ void qpush(ESockRequestQueue* q,
}
}
+
static
ESockRequestQueueElement* qpop(ESockRequestQueue* q)
{
@@ -19314,11 +12560,8 @@ ESockRequestQueueElement* qpop(ESockRequestQueue* q)
return e;
}
-#endif // #ifndef __WIN32__
-
-#ifndef __WIN32__
static
BOOLEAN_T qunqueue(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -19327,19 +12570,40 @@ BOOLEAN_T qunqueue(ErlNifEnv* env,
ERL_NIF_TERM* refP,
const ErlNifPid* pidP)
{
+ ESockRequestQueueElement* e = qget(env, descP, slogan, q, refP, pidP);
+
+ if (e != NULL) {
+ (void) DEMONP(slogan, env, descP, &e->data.mon);
+ esock_clear_env(slogan, e->data.env);
+ esock_free_env(slogan, e->data.env);
+ FREE(e);
+
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+
+static
+ESockRequestQueueElement* qget(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const char* slogan,
+ ESockRequestQueue* q,
+ ERL_NIF_TERM* refP,
+ const ErlNifPid* pidP)
+{
ESockRequestQueueElement* e = q->first;
ESockRequestQueueElement* p = NULL;
- /* Check if it was one of the waiting acceptor processes */
+ /* Check if it was one of the waiting requestor processes */
while (e != NULL) {
if (COMPARE_PIDS(&e->data.pid, pidP) == 0) {
if ((refP != NULL) && (COMPARE(e->data.ref, *refP) != 0))
- return FALSE;
+ return NULL;
/* We have a match */
- (void) DEMONP(slogan, env, descP, &e->data.mon);
-
if (p != NULL) {
/* Not the first, but could be the last */
if (q->last == e) {
@@ -19359,10 +12623,7 @@ BOOLEAN_T qunqueue(ErlNifEnv* env,
}
}
- esock_free_env("qunqueue", e->data.env);
- FREE(e);
-
- return TRUE;
+ return e;
}
/* Try next */
@@ -19370,9 +12631,8 @@ BOOLEAN_T qunqueue(ErlNifEnv* env,
e = e->nextP;
}
- return FALSE;
+ return NULL;
}
-#endif // #ifndef __WIN32__
@@ -19381,10 +12641,8 @@ BOOLEAN_T qunqueue(ErlNifEnv* env,
* ----------------------------------------------------------------------
*/
-#ifndef __WIN32__
-
-static
-BOOLEAN_T cnt_inc(ESockCounter* cnt, ESockCounter inc)
+extern
+BOOLEAN_T esock_cnt_inc(ESockCounter* cnt, ESockCounter inc)
{
BOOLEAN_T wrap;
ESockCounter max = ESOCK_COUNTER_MAX;
@@ -19401,8 +12659,8 @@ BOOLEAN_T cnt_inc(ESockCounter* cnt, ESockCounter inc)
return (wrap);
}
-static
-void cnt_dec(ESockCounter* cnt, ESockCounter dec)
+extern
+void esock_cnt_dec(ESockCounter* cnt, ESockCounter dec)
{
ESockCounter current = *cnt;
@@ -19414,9 +12672,6 @@ void cnt_dec(ESockCounter* cnt, ESockCounter dec)
return;
}
-#endif // #ifndef __WIN32__
-
-
/* ----------------------------------------------------------------------
@@ -19424,9 +12679,7 @@ void cnt_dec(ESockCounter* cnt, ESockCounter dec)
* ----------------------------------------------------------------------
*/
-#ifndef __WIN32__
-
-static
+extern
int esock_monitor(const char* slogan,
ErlNifEnv* env,
ESockDescriptor* descP,
@@ -19461,7 +12714,7 @@ int esock_monitor(const char* slogan,
return res;
}
-static
+extern
int esock_demonitor(const char* slogan,
ErlNifEnv* env,
ESockDescriptor* descP,
@@ -19490,13 +12743,13 @@ int esock_demonitor(const char* slogan,
return res;
}
-static
+extern
void esock_monitor_init(ESockMonitor* monP)
{
monP->isActive = FALSE;
}
-static
+extern
ERL_NIF_TERM esock_make_monitor_term(ErlNifEnv* env, const ESockMonitor* monP)
{
if (monP->isActive)
@@ -19505,16 +12758,15 @@ ERL_NIF_TERM esock_make_monitor_term(ErlNifEnv* env, const ESockMonitor* monP)
return esock_atom_undefined;
}
-static BOOLEAN_T esock_monitor_eq(const ESockMonitor* monP,
- const ErlNifMonitor* mon) {
+extern
+BOOLEAN_T esock_monitor_eq(const ESockMonitor* monP,
+ const ErlNifMonitor* mon) {
if (monP->isActive)
return enif_compare_monitors(&monP->mon, mon) == 0;
else
return FALSE;
}
-#endif // #ifndef __WIN32__
-
/* ----------------------------------------------------------------------
@@ -19523,18 +12775,6 @@ static BOOLEAN_T esock_monitor_eq(const ESockMonitor* monP,
*/
-#ifndef __WIN32__
-static void free_request_queue(ESockRequestQueue* q)
-{
- while (q->first) {
- ESockRequestQueueElement* free_me = q->first;
- q->first = free_me->nextP;
- esock_free_env("dtor", free_me->data.env);
- FREE(free_me);
- }
-}
-#endif // #ifndef __WIN32__
-
/* =========================================================================
* esock_dtor - Callback function for resource destructor
*
@@ -19542,79 +12782,30 @@ static void free_request_queue(ESockRequestQueue* q)
static
void esock_dtor(ErlNifEnv* env, void* obj)
{
-#ifndef __WIN32__
ESockDescriptor* descP = (ESockDescriptor*) obj;
MLOCK(descP->readMtx);
MLOCK(descP->writeMtx);
- SGDBG( ("SOCKET", "dtor {%d,0x%X}\r\n",
+ SGDBG( ("SOCKET", "esock_dtor {%d,0x%X}\r\n",
descP->sock, descP->readState | descP->writeState) );
- if (IS_SELECTED(descP)) {
- /* We have used the socket in the select machinery,
- * so we must have closed it properly to get here
- */
- ESOCK_ASSERT( IS_CLOSED(descP->readState) );
- ESOCK_ASSERT( IS_CLOSED(descP->writeState) );
- ESOCK_ASSERT( descP->sock == INVALID_SOCKET );
- } else {
- /* The socket is only opened, should be safe to close nonblocking */
- (void) sock_close(descP->sock);
- descP->sock = INVALID_SOCKET;
- }
-
- SGDBG( ("SOCKET", "dtor -> set state and pattern\r\n") );
- descP->readState |= (ESOCK_STATE_DTOR | ESOCK_STATE_CLOSED);
- descP->writeState |= (ESOCK_STATE_DTOR | ESOCK_STATE_CLOSED);
- descP->pattern = (ESOCK_DESC_PATTERN_DTOR | ESOCK_STATE_CLOSED);
-
- esock_free_env("dtor reader", descP->currentReader.env);
- descP->currentReader.env = NULL;
-
- esock_free_env("dtor writer", descP->currentWriter.env);
- descP->currentWriter.env = NULL;
-
- esock_free_env("dtor acceptor", descP->currentAcceptor.env);
- descP->currentAcceptor.env = NULL;
-
- SGDBG( ("SOCKET", "dtor -> try free readers request queue\r\n") );
- free_request_queue(&descP->readersQ);
-
- SGDBG( ("SOCKET", "dtor -> try free writers request queue\r\n") );
- free_request_queue(&descP->writersQ);
-
- SGDBG( ("SOCKET", "dtor -> try free acceptors request queue\r\n") );
- free_request_queue(&descP->acceptorsQ);
-
-#ifdef HAVE_SENDFILE
- ESOCK_ASSERT( descP->sendfileHandle == INVALID_HANDLE );
- if (descP->sendfileCountersP != NULL) {
- FREE(descP->sendfileCountersP);
- descP->sendfileCountersP = NULL;
- }
-#endif
-
- esock_free_env("dtor close env", descP->closeEnv);
- descP->closeEnv = NULL;
-
- esock_free_env("dtor meta env", descP->meta.env);
- descP->meta.env = NULL;
+ ESOCK_IO_DTOR(env, descP);
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
- SGDBG( ("SOCKET", "dtor -> try destroy read mutex\r\n") );
+ SGDBG( ("SOCKET", "esock_dtor -> try destroy read mutex\r\n") );
MDESTROY(descP->readMtx); descP->readMtx = NULL;
- SGDBG( ("SOCKET", "dtor -> try destroy write mutex\r\n") );
+ SGDBG( ("SOCKET", "esock_dtor -> try destroy write mutex\r\n") );
MDESTROY(descP->writeMtx); descP->writeMtx = NULL;
- SGDBG( ("SOCKET", "dtor -> done\r\n") );
-#endif // #ifndef __WIN32__
+ SGDBG( ("SOCKET", "esock_dtor -> done\r\n") );
}
+
/* =========================================================================
* esock_stop - Callback function for resource stop
*
@@ -19633,7 +12824,6 @@ void esock_dtor(ErlNifEnv* env, void* obj)
static
void esock_stop(ErlNifEnv* env, void* obj, ErlNifEvent fd, int is_direct_call)
{
-#ifndef __WIN32__
ESockDescriptor* descP = (ESockDescriptor*) obj;
if (is_direct_call) {
@@ -19686,97 +12876,26 @@ void esock_stop(ErlNifEnv* env, void* obj, ErlNifEvent fd, int is_direct_call)
(unsigned long) descP->accWaits,
(unsigned long) descP->accFails) );
-#ifdef HAVE_SENDFILE
- if (descP->sendfileCountersP != NULL) {
- ESockSendfileCounters *cP = descP->sendfileCountersP;
-
- SSDBG( descP, ("SOCKET", "esock_stop {%d/%d} ->"
- "\r\nsendfileCounters:"
- "\r\n cnt: %lu"
- "\r\n byteCnt: %lu"
- "\r\n fails: %lu"
- "\r\n max: %lu"
- "\r\n pkg: %lu"
- "\r\n pkgMax %lu"
- "\r\n tries: %lu"
- "\r\n waits: %lu"
- "\r\n",
- descP->sock, fd,
- (unsigned long) cP->cnt,
- (unsigned long) cP->byteCnt,
- (unsigned long) cP->fails,
- (unsigned long) cP->max,
- (unsigned long) cP->pkg,
- (unsigned long) cP->pkgMax,
- (unsigned long) cP->tries,
- (unsigned long) cP->waits) );
- }
-#endif
+ ESOCK_IO_STOP(env, descP);
- /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- *
- * Inform waiting Closer, or close socket
- *
- * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- */
-
- if (! enif_is_pid_undefined(&descP->closerPid)) {
- /* We have a waiting closer process after nif_close()
- * - send message to trigger nif_finalize_close()
- */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_stop {%d/%d} -> send close msg to %T\r\n",
- descP->sock, fd, MKPID(env, &descP->closerPid)) );
-
- esock_send_close_msg(env, descP, &descP->closerPid);
- /* Message send frees closeEnv */
- descP->closeEnv = NULL;
- descP->closeRef = esock_atom_undefined;
- } else {
- int err;
-
- /* We do not have a closer process
- * - have to do an unclean (non blocking) close */
-
-#ifdef HAVE_SENDFILE
- if (descP->sendfileHandle != INVALID_HANDLE)
- esock_send_sendfile_deferred_close_msg(env, descP);
-#endif
-
- err = esock_close_socket(env, descP, FALSE);
-
- if (err != 0)
- esock_warning_msg("Failed closing socket without "
- "closer process: "
- "\r\n Controlling Process: %T"
- "\r\n Descriptor: %d"
- "\r\n Errno: %d (%T)"
- "\r\n",
- descP->ctrlPid, descP->sock,
- err, MKA(env, erl_errno_id(err)));
- }
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
SSDBG( descP,
("SOCKET",
"esock_stop {%d/%d} -> done\r\n",
descP->sock, fd) );
- MUNLOCK(descP->writeMtx);
- MUNLOCK(descP->readMtx);
-#endif // #ifndef __WIN32__
}
-/* *** esock_stop_handle_current ***
+/* *** esock_stop_handle_currentalloc_desc ***
*
* Handle current requestor (reader, writer or acceptor) during
* socket stop.
*/
-#ifndef __WIN32__
-static
+extern
void esock_stop_handle_current(ErlNifEnv* env,
const char* role,
ESockDescriptor* descP,
@@ -19790,12 +12909,11 @@ void esock_stop_handle_current(ErlNifEnv* env,
" send abort message to current %s %T %T\r\n",
descP->sock, role, reqP->pid, reqP->ref) );
- esock_send_abort_msg(env, descP, sockRef, reqP, atom_closed);
+ esock_send_abort_msg(env, descP, sockRef, reqP, esock_atom_closed);
enif_set_pid_undefined(&reqP->pid);
reqP->ref = esock_atom_undefined;
}
-#endif // #ifndef __WIN32__
@@ -19803,14 +12921,13 @@ void esock_stop_handle_current(ErlNifEnv* env,
* nif_abort message with the specified reason to each member,
* and empty the queue.
*/
-#ifndef __WIN32__
-static
-void inform_waiting_procs(ErlNifEnv* env,
- const char* role,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ESockRequestQueue* q,
- ERL_NIF_TERM reason)
+extern
+void esock_inform_waiting_procs(ErlNifEnv* env,
+ const char* role,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ESockRequestQueue* q,
+ ERL_NIF_TERM reason)
{
ESockRequestQueueElement* currentP = q->first;
ESockRequestQueueElement* nextP;
@@ -19850,7 +12967,6 @@ void inform_waiting_procs(ErlNifEnv* env,
q->first = NULL;
q->last = NULL;
}
-#endif // #ifndef __WIN32__
/* =========================================================================
@@ -19863,7 +12979,6 @@ void esock_down(ErlNifEnv* env,
const ErlNifPid* pidP,
const ErlNifMonitor* monP)
{
-#ifndef __WIN32__
ESockDescriptor* descP = (ESockDescriptor*) obj;
MLOCK(descP->readMtx);
@@ -19877,361 +12992,37 @@ void esock_down(ErlNifEnv* env,
B2S(IS_CLOSED(descP->readState)),
B2S(IS_CLOSING(descP->readState))) );
- if (COMPARE_PIDS(&descP->closerPid, pidP) == 0) {
-
- /* The closer process went down
- * - it will not call nif_finalize_close
- */
-
- enif_set_pid_undefined(&descP->closerPid);
-
- if (MON_EQ(&descP->closerMon, monP)) {
- MON_INIT(&descP->closerMon);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down {%d} -> closer process exit\r\n",
- descP->sock) );
-
- } else {
- // The owner is the closer so we used its monitor
-
- ESOCK_ASSERT( MON_EQ(&descP->ctrlMon, monP) );
- MON_INIT(&descP->ctrlMon);
- enif_set_pid_undefined(&descP->ctrlPid);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down {%d} -> closer controlling process exit\r\n",
- descP->sock) );
- }
-
- /* Since the closer went down there was one,
- * hence esock_close() must have run or scheduled esock_stop(),
- * or the socket has never been selected upon
- */
-
- if (descP->closeEnv == NULL) {
- int err;
-
- /* Since there is no closeEnv,
- * esock_close() did not schedule esock_stop()
- * and is about to call esock_finalize_close() but died,
- * or esock_stop() has run, sent close_msg to the closer
- * and cleared ->closeEnv but the closer died
- * - we have to do an unclean (non blocking) socket close here
- */
-
-#ifdef HAVE_SENDFILE
- if (descP->sendfileHandle != INVALID_HANDLE)
- esock_send_sendfile_deferred_close_msg(env, descP);
-#endif
-
- err = esock_close_socket(env, descP, FALSE);
- if (err != 0)
- esock_warning_msg("Failed closing socket for terminating "
- "closer process: "
- "\r\n Closer Process: %T"
- "\r\n Descriptor: %d"
- "\r\n Errno: %d (%T)"
- "\r\n",
- MKPID(env, pidP), descP->sock,
- err, MKA(env, erl_errno_id(err)));
- } else {
- /* Since there is a closeEnv esock_stop() has not run yet
- * - when it finds that there is no closer process
- * it will close the socket and ignore the close_msg
- */
- esock_free_env("esock_down - close-env", descP->closeEnv);
- descP->closeEnv = NULL;
- descP->closeRef = esock_atom_undefined;
- }
-
- } else if (MON_EQ(&descP->ctrlMon, monP)) {
- MON_INIT(&descP->ctrlMon);
- /* The owner went down */
- enif_set_pid_undefined(&descP->ctrlPid);
-
- if (IS_OPEN(descP->readState)) {
- SSDBG( descP,
- ("SOCKET",
- "esock_down {%d} -> controller process exit"
- "\r\n initiate close\r\n",
- descP->sock) );
-
- esock_down_ctrl(env, descP, pidP);
-
- descP->readState |= ESOCK_STATE_CLOSING;
- descP->writeState |= ESOCK_STATE_CLOSING;
- } else {
- SSDBG( descP,
- ("SOCKET",
- "esock_down {%d} -> controller process exit"
- "\r\n already closed or closing\r\n",
- descP->sock) );
- }
-
- } else if (descP->connectorP != NULL &&
- MON_EQ(&descP->connector.mon, monP)) {
- MON_INIT(&descP->connector.mon);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down {%d} -> connector process exit\r\n",
- descP->sock) );
-
- /* connectorP is only set during connection.
- * Forget all about the ongoing connection.
- * We might end up connected, but the process that initiated
- * the connection has died and will never know
- */
-
- requestor_release("esock_down->connector",
- env, descP, &descP->connector);
- descP->connectorP = NULL;
- descP->writeState &= ~ESOCK_STATE_CONNECTING;
-
- } else {
- ERL_NIF_TERM sockRef;
-
- /* check all operation queue(s): acceptor, writer and reader.
- *
- * Is it really any point in doing this if the socket is closed?
- *
- */
-
- sockRef = enif_make_resource(env, descP);
-
- if (IS_CLOSED(descP->readState)) {
- SSDBG( descP,
- ("SOCKET",
- "esock_down(%T) {%d} -> stray down: %T\r\n",
- sockRef, descP->sock, pidP) );
- } else {
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down(%T) {%d} -> other process term\r\n",
- sockRef, descP->sock) );
-
- if (descP->currentReaderP != NULL)
- esock_down_reader(env, descP, sockRef, pidP, monP);
- if (descP->currentAcceptorP != NULL)
- esock_down_acceptor(env, descP, sockRef, pidP, monP);
- if (descP->currentWriterP != NULL)
- esock_down_writer(env, descP, sockRef, pidP, monP);
- }
- }
+ ESOCK_IO_DOWN(env, descP, pidP, monP);
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
SSDBG( descP, ("SOCKET", "esock_down -> done\r\n") );
-#endif // #ifndef __WIN32__
-}
-
-
-
-/* *** esock_down_ctrl ***
- *
- * Stop after a downed controller
- *
- */
-#ifndef __WIN32__
-static
-void esock_down_ctrl(ErlNifEnv* env,
- ESockDescriptor* descP,
- const ErlNifPid* pidP)
-{
- SSDBG( descP,
- ("SOCKET", "esock_down_ctrl {%d} ->"
- "\r\n Pid: %T"
- "\r\n", descP->sock, MKPID(env, pidP)) );
-
- if (esock_do_stop(env, descP)) {
- /* esock_stop() is scheduled
- * - it has to close the socket
- */
- SSDBG( descP,
- ("SOCKET", "esock_down_ctrl {%d} -> stop was scheduled\r\n",
- descP->sock) );
- } else {
- int err;
-
- /* Socket is not in the select machinery
- * so esock_stop() will not be called
- * - we have to do an unclean (non blocking) socket close here
- */
-
-#ifdef HAVE_SENDFILE
- if (descP->sendfileHandle != INVALID_HANDLE)
- esock_send_sendfile_deferred_close_msg(env, descP);
-#endif
-
- err = esock_close_socket(env, descP, FALSE);
- if (err != 0)
- esock_warning_msg("Failed closing socket for terminating "
- "owner process: "
- "\r\n Owner Process: %T"
- "\r\n Descriptor: %d"
- "\r\n Errno: %d (%T)"
- "\r\n",
- MKPID(env, pidP), descP->sock,
- err, MKA(env, erl_errno_id(err)));
- }
}
-#endif // #ifndef __WIN32__
-
-
-
-/* *** esock_down_acceptor ***
- *
- * Check and then handle a downed acceptor process.
- *
- */
-#ifndef __WIN32__
-static
-void esock_down_acceptor(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- const ErlNifPid* pidP,
- const ErlNifMonitor* monP)
-{
- if (MON_EQ(&descP->currentAcceptor.mon, monP)) {
- MON_INIT(&descP->currentAcceptor.mon);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down_acceptor(%T) {%d} -> "
- "current acceptor - try activate next\r\n",
- sockRef, descP->sock) );
-
- if (!activate_next_acceptor(env, descP, sockRef)) {
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down_acceptor(%T) {%d} -> no more writers\r\n",
- sockRef, descP->sock) );
- descP->readState &= ~ESOCK_STATE_ACCEPTING;
- descP->currentAcceptorP = NULL;
- }
- } else {
-
- /* Maybe unqueue one of the waiting acceptors */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down_acceptor(%T) {%d} -> "
- "not current acceptor - maybe a waiting acceptor\r\n",
- sockRef, descP->sock) );
-
- acceptor_unqueue(env, descP, NULL, pidP);
- }
-}
-#endif // #ifndef __WIN32__
-
-
-/* *** esock_down_writer ***
- *
- * Check and then handle a downed writer process.
- *
+/*
+ * The idea with this function is that it should call esock_io_finish
+ * and release anything allocated by the I/O backend (just to be a
+ * nice citizen).
+ * On Unix this currently a void operation, but on Windows it will be
+ * more substantial...
+ * So, this is currently just a placeholder.
*/
-#ifndef __WIN32__
static
-void esock_down_writer(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- const ErlNifPid* pidP,
- const ErlNifMonitor* monP)
+void esock_on_halt(void* priv_data)
{
- if (MON_EQ(&descP->currentWriter.mon, monP)) {
- MON_INIT(&descP->currentWriter.mon);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down_writer(%T) {%d} -> "
- "current writer - try activate next\r\n",
- sockRef, descP->sock) );
-
- if (!activate_next_writer(env, descP, sockRef)) {
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down_writer(%T) {%d} -> no active writer\r\n",
- sockRef, descP->sock) );
-
- descP->currentWriterP = NULL;
- }
-
- } else {
-
- /* Maybe unqueue one of the waiting writer(s) */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down_writer(%T) {%d} -> "
- "not current writer - maybe a waiting writer\r\n",
- sockRef, descP->sock) );
-
- writer_unqueue(env, descP, NULL, pidP);
- }
-}
-#endif // #ifndef __WIN32__
-
-
-
-
-/* *** esock_down_reader ***
- *
- * Check and then handle a downed reader process.
- *
- */
+ // We do not *currently* use this (priv_data), so ignore
#ifndef __WIN32__
-static
-void esock_down_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- const ErlNifPid* pidP,
- const ErlNifMonitor* monP)
-{
- if (MON_EQ(&descP->currentReader.mon, monP)) {
- MON_INIT(&descP->currentReader.mon);
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down_reader(%T) {%d} -> "
- "current reader - try activate next\r\n",
- sockRef, descP->sock) );
-
- if (! activate_next_reader(env, descP, sockRef)) {
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down_reader(%T) {%d} -> no more readers\r\n",
- sockRef, descP->sock) );
-
- descP->currentReaderP = NULL;
- }
+ VOID(priv_data);
+#else
+ VOIDP(priv_data);
+#endif
- } else {
-
- /* Maybe unqueue one of the waiting reader(s) */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_down_reader(%T) {%d} -> "
- "not current reader - maybe a waiting reader\r\n",
- sockRef, descP->sock) );
-
- reader_unqueue(env, descP, NULL, pidP);
- }
+ ESOCK_IO_FIN();
}
-#endif // #ifndef __WIN32__
-
/* ----------------------------------------------------------------------
@@ -20288,7 +13079,6 @@ ErlNifFunc esock_funcs[] =
};
-#ifndef __WIN32__
static
char* extract_debug_filename(ErlNifEnv* env,
ERL_NIF_TERM map)
@@ -20310,7 +13100,6 @@ char* extract_debug_filename(ErlNifEnv* env,
filename[bin.size] = '\0';
return filename;
}
-#endif // #ifndef __WIN32__
@@ -20321,10 +13110,13 @@ char* extract_debug_filename(ErlNifEnv* env,
static
int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
{
+ ErlNifSysInfo sysInfo;
+ unsigned int ioNumThreads, ioNumThreadsDef;
+
/* +++ Local atoms and error reason atoms +++ */
#define LOCAL_ATOM_DECL(A) atom_##A = MKA(env, #A)
LOCAL_ATOMS;
- LOCAL_ERROR_REASON_ATOMS;
+ // LOCAL_ERROR_REASON_ATOMS;
#undef LOCAL_ATOM_DECL
/* Global atom(s) and error reason atom(s) */
@@ -20335,8 +13127,6 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
esock_atom_socket_tag = MKA(env, "$socket");
-#ifndef __WIN32__
-
if (! esock_extract_pid_from_map(env, load_info,
atom_registry,
&data.regPid)) {
@@ -20344,16 +13134,26 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
return 1; // Failure - no registry pid
}
+ /* --esock-disable-registry */
data.useReg =
esock_get_bool_from_map(env, load_info,
- atom_use_registry,
+ esock_atom_use_registry,
ESOCK_USE_SOCKET_REGISTRY);
+ /* --esock-enable-iow */
data.iow =
esock_get_bool_from_map(env, load_info,
atom_iow,
ESOCK_NIF_IOW_DEFAULT);
+ /* --enable-extended-error-info */
+#if defined(ESOCK_USE_EXTENDED_ERROR_INFO)
+ data.eei = TRUE;
+#else
+ data.eei = FALSE;
+#endif
+
+ /* --esock-debug-file=<filename> */
{
char *debug_filename;
@@ -20392,9 +13192,37 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
data.numProtoUDP = 0;
data.numProtoSCTP = 0;
+
initOpts();
initCmsgTables();
+
+ // #define ESOCK_DISPLAY_OPTION_TABLES 1
+#if defined(ESOCK_DISPLAY_OPTION_TABLES)
+ {
+ /* Display option table(s) after init */
+ ESOCK_EPRINTF("\r\n[ESOCK] Option tables after init:\r\n");
+
+ for (int levelIdx = 0; levelIdx < NUM(optLevels); levelIdx++) {
+ int numOpts = optLevels[levelIdx].num;
+ int level = optLevels[levelIdx].level;
+ ERL_NIF_TERM lname = *optLevels[levelIdx].nameP;
+ struct ESockOpt* opts = optLevels[levelIdx].opts;
+
+ ESOCK_EPRINTF("[ESOCK] [%d] Option table for level %T (%d) (%d options):\r\n",
+ levelIdx, lname, level, numOpts);
+
+ for (int optIdx = 0; optIdx < numOpts; optIdx++) {
+ ESOCK_EPRINTF("[ESOCK] %T[%d]: %T -> %d\r\n",
+ lname, optIdx, lname, opts[optIdx].opt);
+
+ }
+ ESOCK_EPRINTF("\r\n");
+ }
+ }
+#endif
+
+
data.iov_max =
#if defined(NO_SYSCONF) || (! defined(_SC_IOV_MAX))
# ifdef IOV_MAX
@@ -20408,16 +13236,158 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
;
ESOCK_ASSERT( data.iov_max > 0 );
-#endif // #ifndef __WIN32__
+
+ /* This is (currently) intended for Windows use */
+ enif_system_info(&sysInfo, sizeof(ErlNifSysInfo));
+
+ /* We should have a config options for this:
+ * --esock-num-io-threads=16
+ *
+ * ESOCK_IO_NUM_THREADS
+ */
+ ioNumThreadsDef =
+ (unsigned int) (sysInfo.scheduler_threads > 0) ?
+ 2*sysInfo.scheduler_threads : 2;
+
+ ioNumThreads = esock_get_uint_from_map(env, load_info,
+ atom_io_num_threads,
+ ioNumThreadsDef);
+
+#ifdef __WIN32__
+
+ io_backend.init = esaio_init;
+ io_backend.finish = esaio_finish;
+
+ io_backend.info = esaio_info;
+ io_backend.cmd = esock_command;
+ io_backend.supports_0 = esock_supports_0;
+ io_backend.supports_1 = esock_supports_1;
+
+ io_backend.open_with_fd = NULL;
+ io_backend.open_plain = esaio_open_plain;
+ io_backend.bind = esaio_bind;
+ io_backend.connect = esaio_connect;
+ io_backend.listen = esock_listen;
+ io_backend.accept = esaio_accept;
+ io_backend.send = esaio_send;
+ io_backend.sendto = esaio_sendto;
+ io_backend.sendmsg = esaio_sendmsg;
+ io_backend.sendfile_start = NULL;
+ io_backend.sendfile_cont = NULL;
+ io_backend.sendfile_dc = NULL;
+ io_backend.recv = esaio_recv;
+ io_backend.recvfrom = esaio_recvfrom;
+ io_backend.recvmsg = esaio_recvmsg;
+ io_backend.close = esaio_close;
+ io_backend.fin_close = esaio_fin_close;
+ io_backend.shutdown = esock_shutdown;
+ io_backend.sockname = esock_sockname;
+ io_backend.peername = esock_peername;
+ io_backend.cancel_connect = esaio_cancel_connect;
+ io_backend.cancel_accept = esaio_cancel_accept;
+ io_backend.cancel_send = esaio_cancel_send;
+ io_backend.cancel_recv = esaio_cancel_recv;
+
+ io_backend.setopt = esock_setopt;
+ io_backend.setopt_native = esock_setopt_native;
+ io_backend.setopt_otp = esock_setopt_otp;
+ io_backend.getopt = esock_getopt;
+ io_backend.getopt_native = esock_getopt_native;
+ io_backend.getopt_otp = esock_getopt_otp;
+
+ io_backend.ioctl_2 = NULL;
+ io_backend.ioctl_3 = NULL;
+ io_backend.ioctl_4 = NULL;
+
+ io_backend.dtor = esaio_dtor;
+ io_backend.stop = NULL; // esaio_stop;
+ io_backend.down = esaio_down;
+
+#else
+
+ io_backend.init = essio_init;
+ io_backend.finish = essio_finish;
+
+ io_backend.info = essio_info;
+ io_backend.cmd = esock_command;
+ io_backend.supports_0 = esock_supports_0;
+ io_backend.supports_1 = esock_supports_1;
+
+ io_backend.open_with_fd = essio_open_with_fd;
+ io_backend.open_plain = essio_open_plain;
+ io_backend.bind = essio_bind;
+ io_backend.connect = essio_connect;
+ io_backend.listen = esock_listen;
+ io_backend.accept = essio_accept;
+ io_backend.send = essio_send;
+ io_backend.sendto = essio_sendto;
+ io_backend.sendmsg = essio_sendmsg;
+ io_backend.sendfile_start = essio_sendfile_start;
+ io_backend.sendfile_cont = essio_sendfile_cont;
+ io_backend.sendfile_dc = essio_sendfile_deferred_close;
+ io_backend.recv = essio_recv;
+ io_backend.recvfrom = essio_recvfrom;
+ io_backend.recvmsg = essio_recvmsg;
+ io_backend.close = essio_close;
+ io_backend.fin_close = essio_fin_close;
+ io_backend.shutdown = esock_shutdown;
+ io_backend.sockname = esock_sockname;
+ io_backend.peername = esock_peername;
+ io_backend.cancel_connect = essio_cancel_connect;
+ io_backend.cancel_accept = essio_cancel_accept;
+ io_backend.cancel_send = essio_cancel_send;
+ io_backend.cancel_recv = essio_cancel_recv;
+
+ io_backend.setopt = esock_setopt;
+ io_backend.setopt_native = esock_setopt_native;
+ io_backend.setopt_otp = esock_setopt_otp;
+ io_backend.getopt = esock_getopt;
+ io_backend.getopt_native = esock_getopt_native;
+ io_backend.getopt_otp = esock_getopt_otp;
+
+ io_backend.ioctl_2 = essio_ioctl2;
+ io_backend.ioctl_3 = essio_ioctl3;
+ io_backend.ioctl_4 = essio_ioctl4;
+
+ io_backend.dtor = essio_dtor;
+ io_backend.stop = essio_stop;
+ io_backend.down = essio_down;
+
+#endif
+
+ if (ESOCK_IO_INIT(ioNumThreads) != ESOCK_IO_OK) {
+ esock_error_msg("Failed initiating I/O backend");
+ return 1; // Failure
+ }
esocks = enif_open_resource_type_x(env,
"sockets",
&esockInit,
ERL_NIF_RT_CREATE,
NULL);
- return esocks != NULL ?
- 0: // Success
- 1; // Failure
+
+ if (esocks != NULL) {
+ int ores;
+
+ // *Try* install on-halt (callback) function
+ if ((ores = enif_set_option(env,
+ ERL_NIF_OPT_ON_HALT,
+ esock_on_halt)) != 0) {
+ esock_error_msg("Failed installing 'on-halt' "
+ "callback function (%d)\r\n", ores);
+ return 1; // Failure
+ }
+
+ // *Try* enable 'delay on halt' (none-fatal)
+ if ((ores = enif_set_option(env, ERL_NIF_OPT_DELAY_HALT)) != 0) {
+ esock_error_msg("Failed enable 'on-halt' delay (%d)\r\n", ores);
+ }
+
+ return 0; // Success
+ } else {
+ esock_error_msg("Failed open esock resource type\r\n");
+ return 1; // Failure
+ }
}
/*
diff --git a/erts/emulator/nifs/common/prim_tty_nif.c b/erts/emulator/nifs/common/prim_tty_nif.c
new file mode 100644
index 0000000000..7328ec894e
--- /dev/null
+++ b/erts/emulator/nifs/common/prim_tty_nif.c
@@ -0,0 +1,1036 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson 2015-2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Purpose: NIF library for interacting with the tty
+ *
+ */
+
+#define STATIC_ERLANG_NIF 1
+
+#ifndef WANT_NONBLOCKING
+#define WANT_NONBLOCKING
+#endif
+
+#include "config.h"
+#include "sys.h"
+#include "erl_nif.h"
+#include "erl_driver.h"
+
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <wchar.h>
+#include <stdio.h>
+#include <signal.h>
+#include <locale.h>
+#ifdef HAVE_TERMCAP
+ #include <termios.h>
+ #include <curses.h>
+ #include <term.h>
+#endif
+#ifndef __WIN32__
+ #include <unistd.h>
+ #include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_UIO_H
+ #include <sys/uio.h>
+#endif
+
+#if defined IOV_MAX
+#define MAXIOV IOV_MAX
+#elif defined UIO_MAXIOV
+#define MAXIOV UIO_MAXIOV
+#else
+#define MAXIOV 16
+#endif
+
+#if !defined(HAVE_SETLOCALE) || !defined(HAVE_NL_LANGINFO) || !defined(HAVE_LANGINFO_H)
+#define PRIMITIVE_UTF8_CHECK 1
+#else
+#include <langinfo.h>
+#endif
+
+#ifdef VALGRIND
+# include <valgrind/memcheck.h>
+#endif
+
+#define DEF_HEIGHT 24
+#define DEF_WIDTH 80
+
+typedef struct {
+#ifdef __WIN32__
+ HANDLE ofd;
+ HANDLE ifd;
+ DWORD dwOriginalOutMode;
+ DWORD dwOriginalInMode;
+ DWORD dwOutMode;
+ DWORD dwInMode;
+#else
+ int ofd; /* stdout */
+ int ifd; /* stdin */
+#endif
+ ErlNifPid self;
+ ErlNifPid reader;
+ int tty; /* if the tty is initialized */
+#ifdef THREADED_READER
+ ErlNifTid reader_tid;
+#endif
+#ifndef __WIN32__
+ int signal[2]; /* Pipe used for signal (winch + cont) notifications */
+#endif
+#ifdef HAVE_TERMCAP
+ struct termios tty_smode;
+ struct termios tty_rmode;
+#endif
+} TTYResource;
+
+static ErlNifResourceType *tty_rt;
+
+/* The NIFs: */
+static ERL_NIF_TERM isatty_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_create_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_set_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM setlocale_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_write_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_read_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM isprint_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM wcwidth_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM wcswidth_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM sizeof_wchar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_window_size_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_tgetent_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_tgetnum_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_tgetflag_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_tgetstr_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_tgoto_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM tty_read_signal_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+static ErlNifFunc nif_funcs[] = {
+ {"isatty", 1, isatty_nif},
+ {"tty_create", 0, tty_create_nif},
+ {"tty_init", 3, tty_init_nif},
+ {"tty_set", 1, tty_set_nif},
+ {"tty_read_signal", 2, tty_read_signal_nif},
+ {"setlocale", 1, setlocale_nif},
+ {"tty_select", 3, tty_select_nif},
+ {"tty_window_size", 1, tty_window_size_nif},
+ {"write_nif", 2, tty_write_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"read_nif", 2, tty_read_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"isprint", 1, isprint_nif},
+ {"wcwidth", 1, wcwidth_nif},
+ {"wcswidth", 1, wcswidth_nif},
+ {"sizeof_wchar", 0, sizeof_wchar_nif},
+ {"tgetent_nif", 1, tty_tgetent_nif},
+ {"tgetnum_nif", 1, tty_tgetnum_nif},
+ {"tgetflag_nif", 1, tty_tgetflag_nif},
+ {"tgetstr_nif", 1, tty_tgetstr_nif},
+ {"tgoto_nif", 2, tty_tgoto_nif},
+ {"tgoto_nif", 3, tty_tgoto_nif}
+};
+
+/* NIF interface declarations */
+static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info);
+static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info);
+static void unload(ErlNifEnv* env, void* priv_data);
+
+ERL_NIF_INIT(prim_tty, nif_funcs, load, NULL, upgrade, unload)
+
+#define ATOMS \
+ ATOM_DECL(canon); \
+ ATOM_DECL(echo); \
+ ATOM_DECL(ebadf); \
+ ATOM_DECL(undefined); \
+ ATOM_DECL(error); \
+ ATOM_DECL(true); \
+ ATOM_DECL(ok); \
+ ATOM_DECL(input); \
+ ATOM_DECL(false); \
+ ATOM_DECL(stdin); \
+ ATOM_DECL(stdout); \
+ ATOM_DECL(stderr); \
+ ATOM_DECL(sig);
+
+
+#define ATOM_DECL(A) static ERL_NIF_TERM atom_##A
+ATOMS
+#undef ATOM_DECL
+
+static ERL_NIF_TERM make_error(ErlNifEnv *env, ERL_NIF_TERM reason) {
+ return enif_make_tuple2(env, atom_error, reason);
+}
+
+static ERL_NIF_TERM make_enotsup(ErlNifEnv *env) {
+ return make_error(env, enif_make_atom(env, "enotsup"));
+}
+
+static ERL_NIF_TERM make_errno(ErlNifEnv *env) {
+#ifdef __WIN32__
+ return enif_make_atom(env, last_error());
+#else
+ return enif_make_atom(env, erl_errno_id(errno));
+#endif
+}
+
+static ERL_NIF_TERM make_errno_error(ErlNifEnv *env, const char *function) {
+ return make_error(
+ env, enif_make_tuple2(
+ env, enif_make_atom(env, function), make_errno(env)));
+}
+
+static int tty_get_fd(ErlNifEnv *env, ERL_NIF_TERM atom, int *fd) {
+ if (enif_is_identical(atom, atom_stdout)) {
+ *fd = fileno(stdout);
+ } else if (enif_is_identical(atom, atom_stdin)) {
+ *fd = fileno(stdin);
+ } else if (enif_is_identical(atom, atom_stderr)) {
+ *fd = fileno(stderr);
+ } else {
+ return 0;
+ }
+ return 1;
+}
+
+static ERL_NIF_TERM isatty_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+ int fd;
+ if (tty_get_fd(env, argv[0], &fd)) {
+ if (isatty(fd)) {
+ return atom_true;
+ } else if (errno == EINVAL || errno == ENOTTY) {
+ return atom_false;
+ } else {
+ return atom_ebadf;
+ }
+ }
+ return enif_make_badarg(env);
+}
+
+static ERL_NIF_TERM isprint_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+ int i;
+ if (enif_get_int(env, argv[0], &i)) {
+ ASSERT(i > 0 && i < 256);
+ return isprint((char)i) ? atom_true : atom_false;
+ }
+ return enif_make_badarg(env);
+}
+
+static ERL_NIF_TERM wcwidth_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+ int i;
+ if (enif_get_int(env, argv[0], &i)) {
+#ifndef __WIN32__
+ int width;
+ ASSERT(i > 0 && i < (1l << 21));
+ width = wcwidth((wchar_t)i);
+ if (width == -1) {
+ return make_error(env, enif_make_atom(env, "not_printable"));
+ }
+ return enif_make_int(env, width);
+#else
+ return make_enotsup(env);
+#endif
+ }
+ return enif_make_badarg(env);
+}
+
+static ERL_NIF_TERM wcswidth_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+ ErlNifBinary bin;
+ if (enif_inspect_iolist_as_binary(env, argv[0], &bin)) {
+ wchar_t *chars = (wchar_t*)bin.data;
+ int width;
+#ifdef DEBUG
+ for (int i = 0; i < bin.size / sizeof(wchar_t); i++) {
+ ASSERT(chars[i] >= 0 && chars[i] < (1l << 21));
+ }
+#endif
+#ifndef __WIN32__
+ width = wcswidth(chars, bin.size / sizeof(wchar_t));
+#else
+ width = bin.size / sizeof(wchar_t);
+#endif
+ if (width == -1) {
+ return make_error(env, enif_make_atom(env, "not_printable"));
+ }
+ return enif_make_tuple2(env, atom_ok, enif_make_int(env, width));
+ }
+ return enif_make_badarg(env);
+}
+
+static ERL_NIF_TERM sizeof_wchar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+ return enif_make_int(env, sizeof(wchar_t));
+}
+
+static ERL_NIF_TERM tty_write_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+ ERL_NIF_TERM head = argv[1], tail;
+ ErlNifIOQueue *q = NULL;
+ ErlNifIOVec vec, *iovec = &vec;
+ SysIOVec *iov;
+ int iovcnt;
+ TTYResource *tty;
+ ssize_t res = 0;
+ size_t size;
+ if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty))
+ return enif_make_badarg(env);
+
+ while (!enif_is_identical(head, enif_make_list(env, 0))) {
+ if (!enif_inspect_iovec(env, MAXIOV, head, &tail, &iovec))
+ return enif_make_badarg(env);
+
+ head = tail;
+
+ iov = iovec->iov;
+ size = iovec->size;
+ iovcnt = iovec->iovcnt;
+
+ do {
+#ifndef __WIN32__
+ do {
+ res = writev(tty->ofd, iov, iovcnt);
+ } while(res < 0 && (errno == EINTR || errno == EAGAIN));
+#else
+ for (int i = 0; i < iovec->iovcnt; i++) {
+ ssize_t written;
+ BOOL r = WriteFile(tty->ofd, iovec->iov[i].iov_base,
+ iovec->iov[i].iov_len, &written, NULL);
+ if (!r) {
+ res = -1;
+ break;
+ }
+ res += written;
+ }
+#endif
+ if (res < 0) {
+ if (q) enif_ioq_destroy(q);
+ return make_error(env, make_errno(env));
+ }
+ if (res != size) {
+ if (!q) {
+ q = enif_ioq_create(ERL_NIF_IOQ_NORMAL);
+ enif_ioq_enqv(q, iovec, 0);
+ }
+ }
+
+ if (q) {
+ enif_ioq_deq(q, res, &size);
+ if (size == 0) {
+ enif_ioq_destroy(q);
+ q = NULL;
+ } else {
+ iov = enif_ioq_peek(q, &iovcnt);
+ }
+ }
+ } while(q);
+
+ };
+ return atom_ok;
+}
+
+static ERL_NIF_TERM tty_read_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+ TTYResource *tty;
+ ErlNifBinary bin;
+ ERL_NIF_TERM res_term;
+ ssize_t res = 0;
+
+ if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty))
+ return enif_make_badarg(env);
+
+#ifdef __WIN32__
+ if (tty->dwInMode) {
+ ssize_t inputs_read, num_characters = 0;
+ wchar_t *characters = NULL;
+ INPUT_RECORD inputs[128];
+ if (!ReadConsoleInputW(tty->ifd, inputs, sizeof(inputs)/sizeof(*inputs),
+ &inputs_read)) {
+ return make_errno_error(env, "ReadConsoleInput");
+ }
+ for (int i = 0; i < inputs_read; i++) {
+ if (inputs[i].EventType == KEY_EVENT) {
+ if (inputs[i].Event.KeyEvent.bKeyDown &&
+ inputs[i].Event.KeyEvent.uChar.UnicodeChar < 256 &&
+ inputs[i].Event.KeyEvent.uChar.UnicodeChar != 0) {
+ num_characters++;
+ }
+ if (!inputs[i].Event.KeyEvent.bKeyDown &&
+ inputs[i].Event.KeyEvent.uChar.UnicodeChar > 255 &&
+ inputs[i].Event.KeyEvent.uChar.UnicodeChar != 0) {
+ num_characters++;
+ }
+ }
+ }
+ enif_alloc_binary(num_characters * sizeof(wchar_t), &bin);
+ characters = (wchar_t*)bin.data;
+ for (int i = 0; i < inputs_read; i++) {
+ switch (inputs[i].EventType)
+ {
+ case KEY_EVENT:
+ if (inputs[i].Event.KeyEvent.bKeyDown &&
+ inputs[i].Event.KeyEvent.uChar.UnicodeChar < 256 &&
+ inputs[i].Event.KeyEvent.uChar.UnicodeChar != 0) {
+ characters[res++] = inputs[i].Event.KeyEvent.uChar.UnicodeChar;
+ }
+ if (!inputs[i].Event.KeyEvent.bKeyDown &&
+ inputs[i].Event.KeyEvent.uChar.UnicodeChar > 255 &&
+ inputs[i].Event.KeyEvent.uChar.UnicodeChar != 0) {
+ characters[res++] = inputs[i].Event.KeyEvent.uChar.UnicodeChar;
+ }
+ break;
+ case WINDOW_BUFFER_SIZE_EVENT:
+ enif_send(env, &tty->self, NULL,
+ enif_make_tuple2(env, enif_make_atom(env, "resize"),
+ enif_make_tuple2(env,
+ enif_make_int(env, inputs[i].Event.WindowBufferSizeEvent.dwSize.Y),
+ enif_make_int(env, inputs[i].Event.WindowBufferSizeEvent.dwSize.X))));
+ break;
+ case MENU_EVENT:
+ case FOCUS_EVENT:
+ /* Should be ignored according to
+ https://docs.microsoft.com/en-us/windows/console/input-record-str */
+ break;
+ default:
+ fprintf(stderr,"Unknown event: %d\r\n", inputs[i].EventType);
+ break;
+ }
+ }
+ res *= sizeof(wchar_t);
+ } else {
+ DWORD bytesTransferred;
+ enif_alloc_binary(1024, &bin);
+ if (ReadFile(tty->ifd, bin.data, bin.size,
+ &bytesTransferred, NULL)) {
+ res = bytesTransferred;
+ if (res == 0) {
+ enif_release_binary(&bin);
+ return make_error(env, enif_make_atom(env, "closed"));
+ }
+ } else {
+ DWORD error = GetLastError();
+ enif_release_binary(&bin);
+ if (error == ERROR_BROKEN_PIPE)
+ return make_error(env, enif_make_atom(env, "closed"));
+ return make_errno_error(env, "ReadFile");
+ }
+ }
+#else
+ enif_alloc_binary(1024, &bin);
+ res = read(tty->ifd, bin.data, bin.size);
+ if (res < 0) {
+ if (errno != EAGAIN && errno != EINTR) {
+ enif_release_binary(&bin);
+ return make_errno_error(env, "read");
+ }
+ res = 0;
+ } else if (res == 0) {
+ enif_release_binary(&bin);
+ return make_error(env, enif_make_atom(env, "closed"));
+ }
+#endif
+ enif_select(env, tty->ifd, ERL_NIF_SELECT_READ, tty, NULL, argv[1]);
+ if (res == bin.size) {
+ res_term = enif_make_binary(env, &bin);
+ } else if (res < bin.size / 2) {
+ unsigned char *buff = enif_make_new_binary(env, res, &res_term);
+ if (res > 0) {
+ memcpy(buff, bin.data, res);
+ }
+ enif_release_binary(&bin);
+ } else {
+ enif_realloc_binary(&bin, res);
+ res_term = enif_make_binary(env, &bin);
+ }
+
+ return enif_make_tuple2(env, atom_ok, res_term);
+}
+
+static ERL_NIF_TERM setlocale_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+#ifdef __WIN32__
+ TTYResource *tty;
+
+ if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty))
+ return enif_make_badarg(env);
+
+ if (tty->dwOutMode)
+ {
+ if (!SetConsoleOutputCP(CP_UTF8)) {
+ return make_errno_error(env, "SetConsoleOutputCP");
+ }
+ }
+ return atom_true;
+#elif defined(PRIMITIVE_UTF8_CHECK)
+ setlocale(LC_CTYPE, ""); /* Set international environment,
+ ignore result */
+ return enif_make_atom(env, "primitive");
+#else
+ char *l = setlocale(LC_CTYPE, ""); /* Set international environment */
+ if (l != NULL) {
+ if (strcmp(nl_langinfo(CODESET), "UTF-8") == 0)
+ return atom_true;
+ }
+ return atom_false;
+#endif
+}
+
+static ERL_NIF_TERM tty_tgetent_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+#ifdef HAVE_TERMCAP
+ ErlNifBinary TERM;
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &TERM))
+ return enif_make_badarg(env);
+ if (tgetent((char *)NULL /* ignored */, (char *)TERM.data) <= 0) {
+ return make_errno_error(env, "tgetent");
+ }
+ return atom_ok;
+#else
+ return make_enotsup(env);
+#endif
+}
+
+static ERL_NIF_TERM tty_tgetnum_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+#ifdef HAVE_TERMCAP
+ ErlNifBinary TERM;
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &TERM))
+ return enif_make_badarg(env);
+ return enif_make_int(env, tgetnum((char*)TERM.data));
+#else
+ return make_enotsup(env);
+#endif
+}
+
+static ERL_NIF_TERM tty_tgetflag_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+#ifdef HAVE_TERMCAP
+ ErlNifBinary TERM;
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &TERM))
+ return enif_make_badarg(env);
+ if (tgetflag((char*)TERM.data))
+ return atom_true;
+ return atom_false;
+#else
+ return make_enotsup(env);
+#endif
+}
+
+static ERL_NIF_TERM tty_tgetstr_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+#ifdef HAVE_TERMCAP
+ ErlNifBinary TERM, ret;
+ /* tgetstr seems to use a lot of stack buffer space,
+ so buff needs to be relatively "small" */
+ char *str = NULL;
+ char buff[BUFSIZ] = {0};
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &TERM))
+ return enif_make_badarg(env);
+ str = tgetstr((char*)TERM.data, (char**)&buff);
+ if (!str) return atom_false;
+ enif_alloc_binary(strlen(str), &ret);
+ memcpy(ret.data, str, strlen(str));
+ return enif_make_tuple2(
+ env, atom_ok, enif_make_binary(env, &ret));
+#else
+ return make_enotsup(env);
+#endif
+}
+
+#ifdef HAVE_TERMCAP
+static int tputs_buffer_index;
+static unsigned char tputs_buffer[1024];
+
+#if defined(__sun) && defined(__SVR4) /* Solaris */
+static int tty_puts_putc(char c) {
+#else
+static int tty_puts_putc(int c) {
+#endif
+ tputs_buffer[tputs_buffer_index++] = (unsigned char)c;
+ return 0;
+}
+#endif
+
+static ERL_NIF_TERM tty_tgoto_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+#ifdef HAVE_TERMCAP
+ ErlNifBinary TERM;
+ char *ent;
+ int value1, value2 = 0;
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &TERM) ||
+ !enif_get_int(env, argv[1], &value1))
+ return enif_make_badarg(env);
+ if (argc == 2) {
+ ent = tgoto((char*)TERM.data, 0, value1);
+ } else {
+ ASSERT(argc == 3);
+ ent = tgoto((char*)TERM.data, value1, value2);
+ }
+ if (!ent) return make_errno_error(env, "tgoto");
+
+ tputs_buffer_index = 0;
+ if (tputs(ent, 1, tty_puts_putc)) {
+ return make_errno_error(env, "tputs");
+ } else {
+ ERL_NIF_TERM ret;
+ unsigned char *buff = enif_make_new_binary(env, tputs_buffer_index, &ret);
+ memcpy(buff, tputs_buffer, tputs_buffer_index);
+ return enif_make_tuple2(env, atom_ok, ret);
+ }
+#else
+ return make_enotsup(env);
+#endif
+}
+
+static ERL_NIF_TERM tty_create_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+
+ TTYResource *tty = enif_alloc_resource(tty_rt, sizeof(TTYResource));
+ ERL_NIF_TERM tty_term;
+ memset(tty, 0, sizeof(*tty));
+#ifndef __WIN32__
+ tty->ifd = 0;
+ tty->ofd = 1;
+#else
+ tty->ifd = GetStdHandle(STD_INPUT_HANDLE);
+ if (tty->ifd == INVALID_HANDLE_VALUE || tty->ifd == NULL) {
+ tty->ifd = CreateFile("nul", GENERIC_READ, 0,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ }
+ tty->ofd = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (tty->ofd == INVALID_HANDLE_VALUE || tty->ofd == NULL) {
+ tty->ofd = CreateFile("nul", GENERIC_WRITE, 0,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ }
+ if (GetConsoleMode(tty->ofd, &tty->dwOriginalOutMode))
+ {
+ tty->dwOutMode = ENABLE_VIRTUAL_TERMINAL_PROCESSING | tty->dwOriginalOutMode;
+ if (!SetConsoleMode(tty->ofd, tty->dwOutMode)) {
+ /* Failed to set any VT mode, can't do anything here. */
+ return make_errno_error(env, "SetConsoleMode");
+ }
+ }
+ if (GetConsoleMode(tty->ifd, &tty->dwOriginalInMode))
+ {
+ tty->dwInMode = ENABLE_VIRTUAL_TERMINAL_INPUT | tty->dwOriginalInMode;
+ if (!SetConsoleMode(tty->ifd, tty->dwInMode)) {
+ /* Failed to set any VT mode, can't do anything here. */
+ return make_errno_error(env, "SetConsoleMode");
+ }
+ }
+#endif
+
+ tty_term = enif_make_resource(env, tty);
+ enif_release_resource(tty);
+
+ enif_set_pid_undefined(&tty->self);
+ enif_set_pid_undefined(&tty->reader);
+
+ return enif_make_tuple2(env, atom_ok, tty_term);
+}
+
+static ERL_NIF_TERM tty_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+
+#if defined(HAVE_TERMCAP) || defined(__WIN32__)
+ ERL_NIF_TERM canon, echo, sig;
+ TTYResource *tty;
+ int fd;
+
+ if (argc != 3 ||
+ !tty_get_fd(env, argv[1], &fd) ||
+ !enif_is_map(env, argv[2])) {
+ return enif_make_badarg(env);
+ }
+
+ if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty))
+ return enif_make_badarg(env);
+
+ if (!enif_get_map_value(env, argv[2], enif_make_atom(env,"canon"), &canon))
+ canon = enif_make_atom(env, "undefined");
+ if (!enif_get_map_value(env, argv[2], enif_make_atom(env,"echo"), &echo))
+ echo = enif_make_atom(env, "undefined");
+ if (!enif_get_map_value(env, argv[2], enif_make_atom(env,"sig"), &sig))
+ sig = enif_make_atom(env, "undefined");
+
+#ifndef __WIN32__
+ if (tcgetattr(fd, &tty->tty_rmode) < 0) {
+ return make_errno_error(env, "tcgetattr");
+ }
+
+ tty->tty_smode = tty->tty_rmode;
+
+ /* Default characteristics for all usage including termcap output. */
+ tty->tty_smode.c_iflag &= ~ISTRIP;
+
+ /* erts_fprintf(stderr,"canon %T\r\n", canon); */
+ /* Turn canonical (line mode) on off. */
+ if (enif_is_identical(canon, atom_true)) {
+ tty->tty_smode.c_iflag |= ICRNL;
+ tty->tty_smode.c_lflag |= ICANON;
+ tty->tty_smode.c_oflag |= OPOST;
+ tty->tty_smode.c_cc[VEOF] = tty->tty_rmode.c_cc[VEOF];
+#ifdef VDSUSP
+ tty->tty_smode.c_cc[VDSUSP] = tty->tty_rmode.c_cc[VDSUSP];
+#endif
+ }
+ if (enif_is_identical(canon, atom_false)) {
+ tty->tty_smode.c_iflag &= ~ICRNL;
+ tty->tty_smode.c_lflag &= ~ICANON;
+ tty->tty_smode.c_oflag &= ~OPOST;
+
+ tty->tty_smode.c_cc[VMIN] = 1;
+ tty->tty_smode.c_cc[VTIME] = 0;
+#ifdef VDSUSP
+ tty->tty_smode.c_cc[VDSUSP] = 0;
+#endif
+ }
+
+ /* Turn echo on or off. */
+ /* erts_fprintf(stderr,"echo %T\r\n", echo); */
+ if (enif_is_identical(echo, atom_true))
+ tty->tty_smode.c_lflag |= ECHO;
+ if (enif_is_identical(echo, atom_false))
+ tty->tty_smode.c_lflag &= ~ECHO;
+
+ /* erts_fprintf(stderr,"sig %T\r\n", sig); */
+ /* Set extra characteristics for "RAW" mode, no signals. */
+ if (enif_is_identical(sig, atom_true)) {
+ /* Ignore IMAXBEL as not POSIX. */
+#ifndef QNX
+ tty->tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON|IXANY);
+#else
+ tty->tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON);
+#endif
+ tty->tty_smode.c_lflag |= (ISIG|IEXTEN);
+ }
+ if (enif_is_identical(sig, atom_false)) {
+ /* Ignore IMAXBEL as not POSIX. */
+#ifndef QNX
+ tty->tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON|IXANY);
+#else
+ tty->tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON);
+#endif
+ tty->tty_smode.c_lflag &= ~(ISIG|IEXTEN);
+ }
+
+#else
+ /* fprintf(stderr, "origOutMode: %x origInMode: %x\r\n", */
+ /* tty->dwOriginalOutMode, tty->dwOriginalInMode); */
+
+ /* If we cannot disable NEWLINE_AUTO_RETURN we continue anyway as things work */
+ if (SetConsoleMode(tty->ofd, tty->dwOutMode | DISABLE_NEWLINE_AUTO_RETURN)) {
+ tty->dwOutMode |= DISABLE_NEWLINE_AUTO_RETURN;
+ }
+
+ tty->dwInMode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT);
+ if (!SetConsoleMode(tty->ifd, tty->dwInMode))
+ {
+ /* Failed to set disable echo or line input mode */
+ return make_errno_error(env, "SetConsoleMode");
+ }
+
+#endif /* __WIN32__ */
+
+ tty->tty = 1;
+
+ return atom_ok;
+#else
+ return make_enotsup(env);
+#endif
+}
+
+static ERL_NIF_TERM tty_set_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+#if defined(HAVE_TERMCAP) || defined(__WIN32__)
+ TTYResource *tty;
+ if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty))
+ return enif_make_badarg(env);
+#ifdef HAVE_TERMCAP
+ if (tty->tty && tcsetattr(tty->ifd, TCSANOW, &tty->tty_smode) < 0) {
+ return make_errno_error(env, "tcsetattr");
+ }
+#endif
+ enif_self(env, &tty->self);
+ enif_monitor_process(env, tty, &tty->self, NULL);
+ return atom_ok;
+#else
+ return make_enotsup(env);
+#endif
+}
+
+static ERL_NIF_TERM tty_window_size_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+ TTYResource *tty;
+ int width = -1, height = -1;
+ if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty))
+ return enif_make_badarg(env);
+ {
+#ifdef TIOCGWINSZ
+ struct winsize ws;
+ if (ioctl(tty->ifd,TIOCGWINSZ,&ws) == 0) {
+ if (ws.ws_col > 0)
+ width = ws.ws_col;
+ if (ws.ws_row > 0)
+ height = ws.ws_row;
+ } else if (ioctl(tty->ofd,TIOCGWINSZ,&ws) == 0) {
+ if (ws.ws_col > 0)
+ width = ws.ws_col;
+ if (ws.ws_row > 0)
+ height = ws.ws_row;
+ }
+#elif defined(__WIN32__)
+ CONSOLE_SCREEN_BUFFER_INFOEX buffer_info;
+ buffer_info.cbSize = sizeof(buffer_info);
+ if (GetConsoleScreenBufferInfoEx(tty->ofd, &buffer_info)) {
+ height = buffer_info.dwSize.Y;
+ width = buffer_info.dwSize.X;
+ } else {
+ return make_errno_error(env,"GetConsoleScreenBufferInfoEx");
+ }
+#endif
+ }
+ if (width == -1 && height == -1) {
+ return make_enotsup(env);
+ }
+ return enif_make_tuple2(
+ env, atom_ok,
+ enif_make_tuple2(
+ env,
+ enif_make_int(env, width),
+ enif_make_int(env, height)
+ ));
+}
+
+#ifndef __WIN32__
+
+static int tty_signal_fd = -1;
+
+static RETSIGTYPE tty_cont(int sig)
+{
+ if (tty_signal_fd != 1) {
+ while (write(tty_signal_fd, "c", 1) < 0 && errno == EINTR) { };
+ }
+}
+
+
+static RETSIGTYPE tty_winch(int sig)
+{
+ if (tty_signal_fd != 1) {
+ while (write(tty_signal_fd, "w", 1) < 0 && errno == EINTR) { };
+ }
+}
+
+#endif
+
+static ERL_NIF_TERM tty_read_signal_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+ TTYResource *tty;
+ char buff[1];
+ ssize_t ret;
+ ERL_NIF_TERM res;
+ if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty))
+ return enif_make_badarg(env);
+#ifndef __WIN32__
+ do {
+ ret = read(tty->signal[0], buff, 1);
+ } while (ret < 0 && errno == EAGAIN);
+
+ if (ret < 0) {
+ return make_errno_error(env, "read");
+ } else if (ret == 0) {
+ return make_error(env, enif_make_atom(env,"empty"));
+ }
+
+ enif_select(env, tty->signal[0], ERL_NIF_SELECT_READ, tty, NULL, argv[1]);
+
+ if (buff[0] == 'w') {
+ res = enif_make_atom(env, "winch");
+ } else if (buff[0] == 'c') {
+ res = enif_make_atom(env, "cont");
+ } else {
+ res = enif_make_string_len(env, buff, 1, ERL_NIF_LATIN1);
+ }
+ return enif_make_tuple2(env, atom_ok, res);
+#else
+ return make_enotsup(env);
+#endif
+}
+
+#ifdef THREADED_READED
+struct tty_reader_init {
+ ErlNifEnv *env;
+ ERL_NIF_TERM tty;
+};
+
+#define TTY_READER_BUF_SIZE 1024
+
+static void *tty_reader_thread(void *args) {
+ struct tty_reader_init *tty_reader_init = (struct tty_reader_init*)args;
+ TTYResource *tty;
+ ErlNifBinary binary;
+ ErlNifEnv *env = NULL;
+ ERL_NIF_TERM data[10];
+ int cnt = 0;
+
+ enif_alloc_binary(TTY_READER_BUF_SIZE, &binary);
+
+ enif_get_resource(tty_reader_init->env, tty_reader_init->tty, tty_rt, (void **)&tty);
+
+ SET_BLOCKING(tty->ifd);
+
+ while(true) {
+ ssize_t i = read(tty->ifd, binary.data, TTY_READER_BUF_SIZE);
+ /* fprintf(stderr,"Read: %ld bytes from %d\r\n", i, tty->ifd); */
+ if (i < 0) {
+ int saved_errno = errno;
+ if (env) {
+ ERL_NIF_TERM msg = enif_make_list_from_array(env, data, cnt);
+ enif_send(env, &tty->self, NULL, enif_make_tuple2(env, atom_input, msg));
+ cnt = 0;
+ env = NULL;
+ }
+ if (saved_errno != EAGAIN) {
+ env = enif_alloc_env();
+ errno = saved_errno;
+ enif_send(env, &tty->self, NULL, make_errno_error(env, "read"));
+ break;
+ }
+ } else {
+ if (!env) {
+ env = enif_alloc_env();
+ }
+ enif_realloc_binary(&binary, i);
+ data[cnt++] = enif_make_binary(env, &binary);
+ if (cnt == 10 || i != TTY_READER_BUF_SIZE) {
+ ERL_NIF_TERM msg = enif_make_list_from_array(env, data, cnt);
+ enif_send(env, &tty->self, NULL, enif_make_tuple2(env, atom_input, msg));
+ cnt = 0;
+ env = NULL;
+ }
+ enif_alloc_binary(TTY_READER_BUF_SIZE, &binary);
+ }
+ }
+
+ enif_free_env(tty_reader_init->env);
+ enif_free(tty_reader_init);
+ return (void*)0;
+}
+
+#endif
+
+static ERL_NIF_TERM tty_select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
+ TTYResource *tty;
+#ifdef THREADED_READER
+ struct tty_reader_init *tty_reader_init;
+#endif
+#ifndef __WIN32__
+ extern int using_oldshell; /* set this to let the rest of erts know */
+#endif
+ if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty))
+ return enif_make_badarg(env);
+
+#ifndef __WIN32__
+ if (pipe(tty->signal) == -1) {
+ return make_errno_error(env, "pipe");
+ }
+ SET_NONBLOCKING(tty->signal[0]);
+ enif_select(env, tty->signal[0], ERL_NIF_SELECT_READ, tty, NULL, argv[1]);
+ tty_signal_fd = tty->signal[1];
+
+ sys_signal(SIGCONT, tty_cont);
+ sys_signal(SIGWINCH, tty_winch);
+
+ using_oldshell = 0;
+
+#endif
+
+ enif_select(env, tty->ifd, ERL_NIF_SELECT_READ, tty, NULL, argv[2]);
+
+ enif_self(env, &tty->reader);
+ enif_monitor_process(env, tty, &tty->reader, NULL);
+
+#ifdef THREADED_READER
+
+ tty_reader_init = enif_alloc(sizeof(struct tty_reader_init));
+ tty_reader_init->env = enif_alloc_env();
+ tty_reader_init->tty = enif_make_copy(tty_reader_init->env, argv[0]);
+
+ if (enif_thread_create(
+ "stdin_reader",
+ &tty->reader_tid,
+ tty_reader_thread, tty_reader_init, NULL)) {
+ enif_free(tty_reader_init);
+ return make_errno_error(env, "enif_thread_create");
+ }
+#endif
+ return atom_ok;
+}
+
+static void tty_monitor_down(ErlNifEnv* caller_env, void* obj, ErlNifPid* pid, ErlNifMonitor* mon) {
+ TTYResource *tty = obj;
+#ifdef HAVE_TERMCAP
+ if (enif_compare_pids(pid, &tty->self) == 0) {
+ tcsetattr(tty->ifd, TCSANOW, &tty->tty_rmode);
+ }
+#endif
+ if (enif_compare_pids(pid, &tty->reader) == 0) {
+ enif_select(caller_env, tty->ifd, ERL_NIF_SELECT_STOP, tty, NULL, atom_undefined);
+#ifndef __WIN32__
+ enif_select(caller_env, tty->signal[0], ERL_NIF_SELECT_STOP, tty, NULL, atom_undefined);
+ close(tty->signal[1]);
+ sys_signal(SIGCONT, SIG_DFL);
+ sys_signal(SIGWINCH, SIG_DFL);
+#endif
+ }
+}
+
+static void tty_select_stop(ErlNifEnv* caller_env, void* obj, ErlNifEvent event, int is_direct_call) {
+/* Only used to close the signal pipe on unix */
+#ifndef __WIN32__
+ if (event != 0)
+ close(event);
+#endif
+}
+
+static void load_resources(ErlNifEnv* env, ErlNifResourceFlags rt_flags) {
+ ErlNifResourceTypeInit rt = {
+ NULL /* dtor */,
+ tty_select_stop,
+ tty_monitor_down};
+
+#define ATOM_DECL(A) atom_##A = enif_make_atom(env, #A)
+ATOMS
+#undef ATOM_DECL
+
+ tty_rt = enif_open_resource_type_x(env, "tty", &rt, rt_flags, NULL);
+}
+
+static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ *priv_data = NULL;
+ load_resources(env, ERL_NIF_RT_CREATE);
+ return 0;
+}
+
+static void unload(ErlNifEnv* env, void* priv_data)
+{
+
+}
+
+static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data,
+ ERL_NIF_TERM load_info)
+{
+ if (*old_priv_data != NULL) {
+ return -1; /* Don't know how to do that */
+ }
+ if (*priv_data != NULL) {
+ return -1; /* Don't know how to do that */
+ }
+ *priv_data = NULL;
+ load_resources(env, ERL_NIF_RT_TAKEOVER);
+ return 0;
+}
diff --git a/erts/emulator/nifs/common/socket_asyncio.h b/erts/emulator/nifs/common/socket_asyncio.h
new file mode 100644
index 0000000000..059ee2bf08
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_asyncio.h
@@ -0,0 +1,172 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2023-2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ *
+ * ----------------------------------------------------------------------
+ * Purpose : Asynchronous I/O functions.
+ * ----------------------------------------------------------------------
+ *
+ * essio = ESock Asynchronous I/O
+ *
+ */
+
+#ifndef SOCKET_ASYNCIO_H__
+#define SOCKET_ASYNCIO_H__
+
+#include "socket_io.h"
+
+extern int esaio_init(unsigned int numThreads,
+ const ESockData* dataP);
+extern void esaio_finish(void);
+extern ERL_NIF_TERM esaio_info(ErlNifEnv* env);
+
+/*
+extern ERL_NIF_TERM esaio_open_with_fd(ErlNifEnv* env,
+ int fd,
+ ERL_NIF_TERM eopts,
+ const ESockData* dataP);
+ */
+extern ERL_NIF_TERM esaio_open_plain(ErlNifEnv* env,
+ int domain,
+ int type,
+ int protocol,
+ ERL_NIF_TERM eopts,
+ const ESockData* dataP);
+extern ERL_NIF_TERM esaio_bind(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESockAddress* sockAddrP,
+ SOCKLEN_T addrLen);
+extern ERL_NIF_TERM esaio_connect(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM connRef,
+ ESockAddress* addrP,
+ SOCKLEN_T addrLen);
+/*
+extern ERL_NIF_TERM esaio_listen(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int backlog);
+*/
+extern ERL_NIF_TERM esaio_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef);
+extern ERL_NIF_TERM esaio_send(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* sndDataP,
+ int flags);
+extern ERL_NIF_TERM esaio_sendto(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* dataP,
+ int flags,
+ ESockAddress* toAddrP,
+ SOCKLEN_T toAddrLen);
+extern ERL_NIF_TERM esaio_sendmsg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ERL_NIF_TERM eMsg,
+ int flags,
+ ERL_NIF_TERM eIOV,
+ const ESockData* dataP);
+/*
+extern
+ERL_NIF_TERM esaio_sendfile_start(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ off_t offset,
+ size_t count,
+ ERL_NIF_TERM fRef);
+extern
+ERL_NIF_TERM esaio_sendfile_cont(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ off_t offset,
+ size_t count);
+extern
+ERL_NIF_TERM esaio_sendfile_deferred_close(ErlNifEnv* env,
+ ESockDescriptor* descP);
+*/
+
+extern ERL_NIF_TERM esaio_recv(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t len,
+ int flags);
+extern ERL_NIF_TERM esaio_recvfrom(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t len,
+ int flags);
+extern ERL_NIF_TERM esaio_recvmsg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t bufLen,
+ ssize_t ctrlLen,
+ int flags);
+extern ERL_NIF_TERM esaio_close(ErlNifEnv* env,
+ ESockDescriptor* descP);
+extern ERL_NIF_TERM esaio_fin_close(ErlNifEnv* env,
+ ESockDescriptor* descP);
+extern ERL_NIF_TERM esaio_shutdown(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int how);
+extern ERL_NIF_TERM esaio_sockname(ErlNifEnv* env,
+ ESockDescriptor* descP);
+extern ERL_NIF_TERM esaio_peername(ErlNifEnv* env,
+ ESockDescriptor* descP);
+extern ERL_NIF_TERM esaio_cancel_connect(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef);
+extern ERL_NIF_TERM esaio_cancel_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+extern ERL_NIF_TERM esaio_cancel_send(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+extern ERL_NIF_TERM esaio_cancel_recv(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+
+extern void esaio_dtor(ErlNifEnv* env,
+ ESockDescriptor* descP);
+extern void esaio_stop(ErlNifEnv* env,
+ ESockDescriptor* descP);
+extern void esaio_down(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP);
+
+/* Temporary (I hope) workaround */
+extern void esaio_down_ctrl(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pidP);
+
+#endif // SOCKET_ASYNCIO_H__
diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h
index 95b2c6c22f..9c3381f975 100644
--- a/erts/emulator/nifs/common/socket_int.h
+++ b/erts/emulator/nifs/common/socket_int.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2018-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2018-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,7 +33,9 @@
#ifdef __WIN32__
/* All this just to replace sys/socket.h, netinet/in.h and sys/un.h??? */
+#if !defined(INCL_WINSOCK_API_TYPEDEFS)
#define INCL_WINSOCK_API_TYPEDEFS 1
+#endif
#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H
#include <winsock2.h>
#endif
@@ -156,6 +158,13 @@ typedef int BOOLEAN_T;
#define B2S(__B__) ((__B__) ? "true" : "false")
+#define TYPE2STR(T) (((T) == SOCK_STREAM) ? "stream" : \
+ (((T) == SOCK_DGRAM) ? "dgram" : \
+ (((T) == SOCK_RAW) ? "raw" : \
+ (((T) == SOCK_RDM) ? "rdm" : \
+ (((T) == SOCK_SEQPACKET) ? "seqpacket" : \
+ "undefined")))))
+
#define NUM(Array) (sizeof(Array) / sizeof(*(Array)))
@@ -191,6 +200,10 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(accept); \
GLOBAL_ATOM_DEF(acceptconn); \
GLOBAL_ATOM_DEF(acceptfilter); \
+ GLOBAL_ATOM_DEF(acc_success); \
+ GLOBAL_ATOM_DEF(acc_fails); \
+ GLOBAL_ATOM_DEF(acc_tries); \
+ GLOBAL_ATOM_DEF(acc_waits); \
GLOBAL_ATOM_DEF(adaption_layer); \
GLOBAL_ATOM_DEF(addr); \
GLOBAL_ATOM_DEF(addrform); \
@@ -198,6 +211,7 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(add_source_membership); \
GLOBAL_ATOM_DEF(alen); \
GLOBAL_ATOM_DEF(allmulti); \
+ GLOBAL_ATOM_DEF(already); \
GLOBAL_ATOM_DEF(any); \
GLOBAL_ATOM_DEF(appletlk); \
GLOBAL_ATOM_DEF(arcnet); \
@@ -214,16 +228,21 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(autoclose); \
GLOBAL_ATOM_DEF(automedia); \
GLOBAL_ATOM_DEF(bad_data); \
+ GLOBAL_ATOM_DEF(base_addr); \
GLOBAL_ATOM_DEF(bindtodevice); \
GLOBAL_ATOM_DEF(block_source); \
GLOBAL_ATOM_DEF(broadcast); \
GLOBAL_ATOM_DEF(busy_poll); \
+ GLOBAL_ATOM_DEF(cancel); \
GLOBAL_ATOM_DEF(cantconfig); \
GLOBAL_ATOM_DEF(chaos); \
GLOBAL_ATOM_DEF(checksum); \
GLOBAL_ATOM_DEF(close); \
+ GLOBAL_ATOM_DEF(closed); \
GLOBAL_ATOM_DEF(cmsg_cloexec); \
GLOBAL_ATOM_DEF(command); \
+ GLOBAL_ATOM_DEF(completion); \
+ GLOBAL_ATOM_DEF(completion_status); \
GLOBAL_ATOM_DEF(confirm); \
GLOBAL_ATOM_DEF(congestion); \
GLOBAL_ATOM_DEF(connect); \
@@ -231,10 +250,12 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(connecting); \
GLOBAL_ATOM_DEF(context); \
GLOBAL_ATOM_DEF(cork); \
+ GLOBAL_ATOM_DEF(counters); \
GLOBAL_ATOM_DEF(credentials); \
GLOBAL_ATOM_DEF(ctrl); \
GLOBAL_ATOM_DEF(ctrunc); \
GLOBAL_ATOM_DEF(data); \
+ GLOBAL_ATOM_DEF(data_size); \
GLOBAL_ATOM_DEF(debug); \
GLOBAL_ATOM_DEF(default); \
GLOBAL_ATOM_DEF(default_send_params); \
@@ -242,6 +263,7 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(dgram); \
GLOBAL_ATOM_DEF(disable_fragments); \
GLOBAL_ATOM_DEF(dlci); \
+ GLOBAL_ATOM_DEF(dma); \
GLOBAL_ATOM_DEF(domain); \
GLOBAL_ATOM_DEF(dontfrag); \
GLOBAL_ATOM_DEF(dontroute); \
@@ -249,10 +271,12 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(drop_membership); \
GLOBAL_ATOM_DEF(drop_source_membership); \
GLOBAL_ATOM_DEF(dstopts); \
+ GLOBAL_ATOM_DEF(dup); \
GLOBAL_ATOM_DEF(dying); \
GLOBAL_ATOM_DEF(dynamic); \
GLOBAL_ATOM_DEF(echo); \
GLOBAL_ATOM_DEF(eether); \
+ GLOBAL_ATOM_DEF(efile); \
GLOBAL_ATOM_DEF(egp); \
GLOBAL_ATOM_DEF(enotsup); \
GLOBAL_ATOM_DEF(eor); \
@@ -273,6 +297,7 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(fragment_interleave); \
GLOBAL_ATOM_DEF(freebind); \
GLOBAL_ATOM_DEF(frelay); \
+ GLOBAL_ATOM_DEF(get_overlapped_result); \
GLOBAL_ATOM_DEF(get_peer_addr_info); \
GLOBAL_ATOM_DEF(hatype); \
GLOBAL_ATOM_DEF(hdrincl); \
@@ -300,6 +325,7 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(ipcomp_level); \
GLOBAL_ATOM_DEF(ipip); \
GLOBAL_ATOM_DEF(ipv6); \
+ GLOBAL_ATOM_DEF(irq); \
GLOBAL_ATOM_DEF(i_want_mapped_v4_addr); \
GLOBAL_ATOM_DEF(join_group); \
GLOBAL_ATOM_DEF(keepalive); \
@@ -312,9 +338,9 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(level); \
GLOBAL_ATOM_DEF(linger); \
GLOBAL_ATOM_DEF(link); \
- GLOBAL_ATOM_DEF(link0); \
- GLOBAL_ATOM_DEF(link1); \
- GLOBAL_ATOM_DEF(link2); \
+ GLOBAL_ATOM_DEF(link0); \
+ GLOBAL_ATOM_DEF(link1); \
+ GLOBAL_ATOM_DEF(link2); \
GLOBAL_ATOM_DEF(local); \
GLOBAL_ATOM_DEF(localtlk); \
GLOBAL_ATOM_DEF(local_auth_chunks); \
@@ -326,6 +352,8 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(maxburst); \
GLOBAL_ATOM_DEF(maxseg); \
GLOBAL_ATOM_DEF(md5sig); \
+ GLOBAL_ATOM_DEF(mem_end); \
+ GLOBAL_ATOM_DEF(mem_start); \
GLOBAL_ATOM_DEF(metricom); \
GLOBAL_ATOM_DEF(mincost); \
GLOBAL_ATOM_DEF(minttl); \
@@ -341,6 +369,7 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(multicast_loop); \
GLOBAL_ATOM_DEF(multicast_ttl); \
GLOBAL_ATOM_DEF(name); \
+ GLOBAL_ATOM_DEF(netns); \
GLOBAL_ATOM_DEF(netrom); \
GLOBAL_ATOM_DEF(nlen); \
GLOBAL_ATOM_DEF(noarp); \
@@ -352,8 +381,16 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(nopush); \
GLOBAL_ATOM_DEF(nosignal); \
GLOBAL_ATOM_DEF(notrailers); \
+ GLOBAL_ATOM_DEF(not_bound); \
GLOBAL_ATOM_DEF(not_found); \
+ GLOBAL_ATOM_DEF(num_general_errors); \
GLOBAL_ATOM_DEF(not_owner); \
+ GLOBAL_ATOM_DEF(num_threads); \
+ GLOBAL_ATOM_DEF(num_unexpected_accepts); \
+ GLOBAL_ATOM_DEF(num_unexpected_connects); \
+ GLOBAL_ATOM_DEF(num_unexpected_reads); \
+ GLOBAL_ATOM_DEF(num_unexpected_writes); \
+ GLOBAL_ATOM_DEF(num_unknown_cmds); \
GLOBAL_ATOM_DEF(oactive); \
GLOBAL_ATOM_DEF(ok); \
GLOBAL_ATOM_DEF(oob); \
@@ -381,6 +418,7 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(portsel); \
GLOBAL_ATOM_DEF(primary_addr); \
GLOBAL_ATOM_DEF(priority); \
+ GLOBAL_ATOM_DEF(prim_file); \
GLOBAL_ATOM_DEF(promisc); \
GLOBAL_ATOM_DEF(pronet); \
GLOBAL_ATOM_DEF(protocol); \
@@ -391,6 +429,11 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(rcvlowat); \
GLOBAL_ATOM_DEF(rcvtimeo); \
GLOBAL_ATOM_DEF(rdm); \
+ GLOBAL_ATOM_DEF(read_byte); \
+ GLOBAL_ATOM_DEF(read_fails); \
+ GLOBAL_ATOM_DEF(read_pkg); \
+ GLOBAL_ATOM_DEF(read_tries); \
+ GLOBAL_ATOM_DEF(read_waits); \
GLOBAL_ATOM_DEF(recv); \
GLOBAL_ATOM_DEF(recvdstaddr); \
GLOBAL_ATOM_DEF(recverr); \
@@ -419,9 +462,19 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(scope_id); \
GLOBAL_ATOM_DEF(sctp); \
GLOBAL_ATOM_DEF(sec); \
+ GLOBAL_ATOM_DEF(select); \
GLOBAL_ATOM_DEF(select_failed); \
GLOBAL_ATOM_DEF(select_sent); \
GLOBAL_ATOM_DEF(send); \
+ GLOBAL_ATOM_DEF(sendfile); \
+ GLOBAL_ATOM_DEF(sendfile_byte); \
+ GLOBAL_ATOM_DEF(sendfile_deferred_close); \
+ GLOBAL_ATOM_DEF(sendfile_fails); \
+ GLOBAL_ATOM_DEF(sendfile_max); \
+ GLOBAL_ATOM_DEF(sendfile_pkg); \
+ GLOBAL_ATOM_DEF(sendfile_pkg_max); \
+ GLOBAL_ATOM_DEF(sendfile_tries); \
+ GLOBAL_ATOM_DEF(sendfile_waits); \
GLOBAL_ATOM_DEF(sendmsg); \
GLOBAL_ATOM_DEF(sendsrcaddr); \
GLOBAL_ATOM_DEF(sendto); \
@@ -435,9 +488,11 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(sndbufforce); \
GLOBAL_ATOM_DEF(sndlowat); \
GLOBAL_ATOM_DEF(sndtimeo); \
+ GLOBAL_ATOM_DEF(sockaddr); \
GLOBAL_ATOM_DEF(socket); \
GLOBAL_ATOM_DEF(socket_tag); \
GLOBAL_ATOM_DEF(spec_dst); \
+ GLOBAL_ATOM_DEF(state); \
GLOBAL_ATOM_DEF(status); \
GLOBAL_ATOM_DEF(staticarp); \
GLOBAL_ATOM_DEF(stream); \
@@ -448,16 +503,19 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(timestamp); \
GLOBAL_ATOM_DEF(tos); \
GLOBAL_ATOM_DEF(transparent); \
+ GLOBAL_ATOM_DEF(timeout); \
GLOBAL_ATOM_DEF(true); \
GLOBAL_ATOM_DEF(trunc); \
GLOBAL_ATOM_DEF(ttl); \
GLOBAL_ATOM_DEF(tunnel); \
GLOBAL_ATOM_DEF(tunnel6); \
+ GLOBAL_ATOM_DEF(txqlen); \
GLOBAL_ATOM_DEF(type); \
GLOBAL_ATOM_DEF(udp); \
GLOBAL_ATOM_DEF(unblock_source); \
GLOBAL_ATOM_DEF(undefined); \
GLOBAL_ATOM_DEF(unicast_hops); \
+ GLOBAL_ATOM_DEF(unknown); \
GLOBAL_ATOM_DEF(unspec); \
GLOBAL_ATOM_DEF(up); \
GLOBAL_ATOM_DEF(usec); \
@@ -465,22 +523,36 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(user_timeout); \
GLOBAL_ATOM_DEF(use_ext_recvinfo); \
GLOBAL_ATOM_DEF(use_min_mtu); \
+ GLOBAL_ATOM_DEF(use_registry); \
+ GLOBAL_ATOM_DEF(value); \
GLOBAL_ATOM_DEF(void); \
- GLOBAL_ATOM_DEF(v6only);
+ GLOBAL_ATOM_DEF(v6only); \
+ GLOBAL_ATOM_DEF(write_byte); \
+ GLOBAL_ATOM_DEF(write_fails); \
+ GLOBAL_ATOM_DEF(write_pkg); \
+ GLOBAL_ATOM_DEF(write_tries); \
+ GLOBAL_ATOM_DEF(write_waits); \
+ GLOBAL_ATOM_DEF(zero)
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* Error reason atoms
*/
-#define GLOBAL_ERROR_REASON_ATOM_DEFS \
- GLOBAL_ATOM_DEF(eagain); \
- GLOBAL_ATOM_DEF(einval);
+#define GLOBAL_ERROR_REASON_ATOM_DEFS \
+ GLOBAL_ATOM_DEF(add_socket); \
+ GLOBAL_ATOM_DEF(create_accept_socket); \
+ GLOBAL_ATOM_DEF(eagain); \
+ GLOBAL_ATOM_DEF(einval); \
+ GLOBAL_ATOM_DEF(select_read); \
+ GLOBAL_ATOM_DEF(select_write); \
+ GLOBAL_ATOM_DEF(update_accept_context); \
+ GLOBAL_ATOM_DEF(update_connect_context)
#define GLOBAL_ATOM_DEF(A) extern ERL_NIF_TERM esock_atom_##A
-GLOBAL_ATOM_DEFS
-GLOBAL_ERROR_REASON_ATOM_DEFS
+GLOBAL_ATOM_DEFS;
+GLOBAL_ERROR_REASON_ATOM_DEFS;
#undef GLOBAL_ATOM_DEF
@@ -500,6 +572,7 @@ GLOBAL_ERROR_REASON_ATOM_DEFS
#define MKL1(E,T) enif_make_list1((E), (T))
#define MKEL(E) enif_make_list((E), 0)
#define MKC(E,H,T) enif_make_list_cell((E), (H), (T))
+#define MKEMA(E) enif_make_new_map((E))
#define MKMA(E,KA,VA,L,M) enif_make_map_from_arrays((E), (KA), (VA), (L), (M))
#define MKPID(E, P) enif_make_pid((E), (P))
#define MKREF(E) enif_make_ref((E))
@@ -531,6 +604,7 @@ GLOBAL_ERROR_REASON_ATOM_DEFS
#define COMPARE(A, B) enif_compare((A), (B))
#define COMPARE_PIDS(P1, P2) enif_compare_pids((P1), (P2))
+#define IS_ZERO(R) (COMPARE((R), esock_atom_zero) == 0)
#define IS_ATOM(E, TE) enif_is_atom((E), (TE))
#define IS_BIN(E, TE) enif_is_binary((E), (TE))
@@ -540,6 +614,9 @@ GLOBAL_ERROR_REASON_ATOM_DEFS
#define IS_TUPLE(E, TE) enif_is_tuple((E), (TE))
#define IS_INTEGER(E, TE) esock_is_integer((E), (TE))
+#define IS_PID_UNDEF(P) enif_is_pid_undefined((P))
+#define SET_PID_UNDEF(P) enif_set_pid_undefined((P))
+
#define GET_ATOM_LEN(E, TE, LP) \
enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1)
#define GET_ATOM(E, TE, BP, MAX) \
@@ -563,7 +640,22 @@ GLOBAL_ERROR_REASON_ATOM_DEFS
#define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP))
#define FREE_BIN(BP) enif_release_binary((BP))
+#define FREE_IOVEC(IV) enif_free_iovec((IV))
+
/* Copy term T into environment E */
-#define CP_TERM(E, T) enif_make_copy((E), (T))
+#define CP_TERM(E, T) enif_make_copy((E), (T))
+
+#define CLEAR_ENV(E) esock_clear_env(__FUNCTION__, (E))
+#define FREE_ENV(E) esock_free_env(__FUNCTION__, (E))
+
+#define TCREATE(NAME, TID, FUNC, ARGS, OPTS) \
+ enif_thread_create((NAME), (TID), (FUNC), (ARGS), (OPTS))
+#define TEXIT(EVAL) enif_thread_exit((EVAL))
+#define TJOIN(TID, EV) enif_thread_join((TID), (EV))
+#define TOCREATE(NAME) enif_thread_opts_create((NAME))
+#define TODESTROY(OPTS) enif_thread_opts_destroy((OPTS))
+
+#define ESOCK_PRINTF(...) enif_fprintf(stdout, __VA_ARGS__)
+#define ESOCK_EPRINTF(...) enif_fprintf(stderr, __VA_ARGS__)
#endif // SOCKET_INT_H__
diff --git a/erts/emulator/nifs/common/socket_io.h b/erts/emulator/nifs/common/socket_io.h
new file mode 100644
index 0000000000..100ca372be
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_io.h
@@ -0,0 +1,232 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022-2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ *
+ * ----------------------------------------------------------------------
+ * Purpose : Types and stuff for the socket I/O backend
+ * ----------------------------------------------------------------------
+ *
+ */
+
+#ifndef SOCKET_IO_H__
+#define SOCKET_IO_H__
+
+#include <erl_nif.h>
+#include "prim_socket_int.h"
+
+
+#define ESOCK_IO_OK 0
+#define ESOCK_IO_ERR_UNSUPPORTED -1
+
+typedef int (*ESockIOInit)(unsigned int numThreads,
+ const ESockData* dataP);
+
+typedef void (*ESockIOFinish)(void);
+
+
+typedef ERL_NIF_TERM (*ESockIOInfo)(ErlNifEnv* env);
+typedef ERL_NIF_TERM (*ESockIOCommand)(ErlNifEnv* env,
+ ERL_NIF_TERM command,
+ ERL_NIF_TERM cdata);
+typedef ERL_NIF_TERM (*ESockIOSupports0)(ErlNifEnv* env);
+typedef ERL_NIF_TERM (*ESockIOSupports1)(ErlNifEnv* env,
+ ERL_NIF_TERM key);
+
+
+typedef ERL_NIF_TERM (*ESockIOOpenWithFd)(ErlNifEnv* env,
+ int fd,
+ ERL_NIF_TERM eopts,
+ const ESockData* dataP);
+
+typedef ERL_NIF_TERM (*ESockIOOpenPlain)(ErlNifEnv* env,
+ int domain,
+ int type,
+ int protocol,
+ ERL_NIF_TERM eopts,
+ const ESockData* dataP);
+
+typedef ERL_NIF_TERM (*ESockIOBind)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESockAddress* sockAddrP,
+ SOCKLEN_T addrLen);
+
+typedef ERL_NIF_TERM (*ESockIOConnect)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM connRef,
+ ESockAddress* addrP,
+ SOCKLEN_T addrLen);
+
+typedef ERL_NIF_TERM (*ESockIOListen)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int backlog);
+
+typedef ERL_NIF_TERM (*ESockIOAccept)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef);
+
+typedef ERL_NIF_TERM (*ESockIOSend)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* sndDataP,
+ int flags);
+
+typedef ERL_NIF_TERM (*ESockIOSendTo)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* dataP,
+ int flags,
+ ESockAddress* toAddrP,
+ SOCKLEN_T toAddrLen);
+
+typedef ERL_NIF_TERM (*ESockIOSendMsg)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ERL_NIF_TERM eMsg,
+ int flags,
+ ERL_NIF_TERM eIOV,
+ const ESockData* dataP);
+
+typedef ERL_NIF_TERM (*ESockIOSendFileStart)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ off_t offset,
+ size_t count,
+ ERL_NIF_TERM fRef);
+typedef ERL_NIF_TERM (*ESockIOSendFileContinue)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ off_t offset,
+ size_t count);
+typedef ERL_NIF_TERM (*ESockIOSendFileDeferredClose)(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+typedef ERL_NIF_TERM (*ESockIORecv)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t len,
+ int flags);
+
+typedef ERL_NIF_TERM (*ESockIORecvFrom)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t len,
+ int flags);
+
+typedef ERL_NIF_TERM (*ESockIORecvMsg)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t bufLen,
+ ssize_t ctrlLen,
+ int flags);
+
+typedef ERL_NIF_TERM (*ESockIOClose)(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+typedef ERL_NIF_TERM (*ESockIOFinClose)(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+typedef ERL_NIF_TERM (*ESockIOShutdown)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int how);
+
+typedef ERL_NIF_TERM (*ESockIOSockName)(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+typedef ERL_NIF_TERM (*ESockIOPeerName)(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+typedef ERL_NIF_TERM (*ESockIOCancelConnect)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef);
+
+typedef ERL_NIF_TERM (*ESockIOCancelAccept)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+
+typedef ERL_NIF_TERM (*ESockIOCancelSend)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+
+typedef ERL_NIF_TERM (*ESockIOCancelRecv)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+
+typedef ERL_NIF_TERM (*ESockIOSetopt)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal);
+typedef ERL_NIF_TERM (*ESockIOSetoptNative)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal);
+typedef ERL_NIF_TERM (*ESockIOSetoptOtp)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+
+typedef ERL_NIF_TERM (*ESockIOGetopt)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt);
+typedef ERL_NIF_TERM (*ESockIOGetoptNative)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM valueSpec);
+typedef ERL_NIF_TERM (*ESockIOGetoptOtp)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int eOpt);
+
+typedef ERL_NIF_TERM (*ESockIOIoctl_2)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ unsigned long req);
+typedef ERL_NIF_TERM (*ESockIOIoctl_3)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ unsigned long req,
+ ERL_NIF_TERM arg);
+typedef ERL_NIF_TERM (*ESockIOIoctl_4)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ unsigned long req,
+ ERL_NIF_TERM arg1,
+ ERL_NIF_TERM arg2);
+
+typedef void (*ESockIODTor)(ErlNifEnv* env,
+ ESockDescriptor* descP);
+typedef void (*ESockIOStop)(ErlNifEnv* env,
+ ESockDescriptor* descP);
+typedef void (*ESockIODown)(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP);
+
+#endif // SOCKET_IO_H__
diff --git a/erts/emulator/nifs/common/socket_syncio.h b/erts/emulator/nifs/common/socket_syncio.h
new file mode 100644
index 0000000000..2d548b8645
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_syncio.h
@@ -0,0 +1,174 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022-2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ *
+ * ----------------------------------------------------------------------
+ * Purpose : Synchronous I/O functions.
+ * ----------------------------------------------------------------------
+ *
+ * essio = ESock Synchronous I/O
+ *
+ */
+
+#ifndef SOCKET_SYNCIO_H__
+#define SOCKET_SYNCIO_H__
+
+#include "socket_io.h"
+
+extern int essio_init(unsigned int numThreads,
+ const ESockData* dataP);
+extern void essio_finish(void);
+extern ERL_NIF_TERM essio_info(ErlNifEnv* env);
+
+extern ERL_NIF_TERM essio_open_with_fd(ErlNifEnv* env,
+ int fd,
+ ERL_NIF_TERM eopts,
+ const ESockData* dataP);
+extern ERL_NIF_TERM essio_open_plain(ErlNifEnv* env,
+ int domain,
+ int type,
+ int protocol,
+ ERL_NIF_TERM eopts,
+ const ESockData* dataP);
+extern ERL_NIF_TERM essio_bind(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESockAddress* sockAddrP,
+ SOCKLEN_T addrLen);
+extern ERL_NIF_TERM essio_connect(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM connRef,
+ ESockAddress* addrP,
+ SOCKLEN_T addrLen);
+/*
+extern ERL_NIF_TERM essio_listen(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int backlog);
+*/
+extern ERL_NIF_TERM essio_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef);
+extern ERL_NIF_TERM essio_send(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* sndDataP,
+ int flags);
+extern ERL_NIF_TERM essio_sendto(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* dataP,
+ int flags,
+ ESockAddress* toAddrP,
+ SOCKLEN_T toAddrLen);
+extern ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ERL_NIF_TERM eMsg,
+ int flags,
+ ERL_NIF_TERM eIOV,
+ const ESockData* dataP);
+extern
+ERL_NIF_TERM essio_sendfile_start(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ off_t offset,
+ size_t count,
+ ERL_NIF_TERM fRef);
+extern
+ERL_NIF_TERM essio_sendfile_cont(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ off_t offset,
+ size_t count);
+extern
+ERL_NIF_TERM essio_sendfile_deferred_close(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+extern ERL_NIF_TERM essio_recv(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t len,
+ int flags);
+extern ERL_NIF_TERM essio_recvfrom(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t len,
+ int flags);
+extern ERL_NIF_TERM essio_recvmsg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t bufLen,
+ ssize_t ctrlLen,
+ int flags);
+extern ERL_NIF_TERM essio_close(ErlNifEnv* env,
+ ESockDescriptor* descP);
+extern ERL_NIF_TERM essio_fin_close(ErlNifEnv* env,
+ ESockDescriptor* descP);
+extern ERL_NIF_TERM essio_cancel_connect(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef);
+extern ERL_NIF_TERM essio_cancel_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+extern ERL_NIF_TERM essio_cancel_send(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+extern ERL_NIF_TERM essio_cancel_recv(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef);
+
+extern ERL_NIF_TERM essio_ioctl2(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ unsigned long req);
+extern ERL_NIF_TERM essio_ioctl3(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ unsigned long req,
+ ERL_NIF_TERM arg);
+extern ERL_NIF_TERM essio_ioctl4(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ unsigned long req,
+ ERL_NIF_TERM ename,
+ ERL_NIF_TERM eval);
+
+extern void essio_dtor(ErlNifEnv* env,
+ ESockDescriptor* descP);
+extern void essio_stop(ErlNifEnv* env,
+ ESockDescriptor* descP);
+extern void essio_down(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP);
+
+/* Temporary (I hope) workaround */
+extern void essio_down_ctrl(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pidP);
+
+#endif // SOCKET_SYNCIO_H__
diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c
index 3747723ede..a1c3e7eddc 100644
--- a/erts/emulator/nifs/common/socket_util.c
+++ b/erts/emulator/nifs/common/socket_util.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2018-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2018-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -147,11 +147,38 @@ static SOCKLEN_T sa_local_length(int l, struct sockaddr_un* sa);
#endif
+/* *** esock_get_uint_from_map ***
+ *
+ * Simple utility function used to extract a unsigned int value from a map.
+ * If it fails to extract the value (for whatever reason) the default
+ * value is used.
+ */
+
+extern
+unsigned int esock_get_uint_from_map(ErlNifEnv* env,
+ ERL_NIF_TERM map,
+ ERL_NIF_TERM key,
+ unsigned int def)
+{
+ ERL_NIF_TERM eval;
+ unsigned int val;
+
+ if (!GET_MAP_VAL(env, map, key, &eval)) {
+ return def;
+ } else {
+ if (GET_UINT(env, eval, &val))
+ return val;
+ else
+ return def;
+ }
+}
+
+
/* *** esock_get_bool_from_map ***
*
* Simple utility function used to extract a boolean value from a map.
* If it fails to extract the value (for whatever reason) the default
- * value is returned.
+ * value is used.
*/
extern
@@ -160,14 +187,14 @@ BOOLEAN_T esock_get_bool_from_map(ErlNifEnv* env,
ERL_NIF_TERM key,
BOOLEAN_T def)
{
- ERL_NIF_TERM val;
+ ERL_NIF_TERM eval;
- if (!GET_MAP_VAL(env, map, key, &val)) {
+ if (!GET_MAP_VAL(env, map, key, &eval)) {
return def;
} else {
- if (COMPARE(val, esock_atom_true) == 0)
+ if (COMPARE(eval, esock_atom_true) == 0)
return TRUE;
- else if (COMPARE(val, esock_atom_false) == 0)
+ else if (COMPARE(eval, esock_atom_false) == 0)
return FALSE;
else
return def;
@@ -2259,15 +2286,15 @@ ERL_NIF_TERM esock_encode_bool(BOOLEAN_T val)
/* *** esock_decode_level ***
*
- * Decode option or cmsg level - 'socket' or protocol number.
+ * Decode option or cmsg level - 'socket' or level number.
*
*/
extern
-BOOLEAN_T esock_decode_level(ErlNifEnv* env, ERL_NIF_TERM eVal, int *val)
+BOOLEAN_T esock_decode_level(ErlNifEnv* env, ERL_NIF_TERM elevel, int *level)
{
- if (COMPARE(esock_atom_socket, eVal) == 0)
- *val = SOL_SOCKET;
- else if (! GET_INT(env, eVal, val))
+ if (COMPARE(esock_atom_socket, elevel) == 0)
+ *level = SOL_SOCKET;
+ else if (! GET_INT(env, elevel, level))
return FALSE;
return TRUE;
@@ -2302,6 +2329,113 @@ ERL_NIF_TERM esock_make_ok2(ErlNifEnv* env, ERL_NIF_TERM any)
}
+/* Takes an 'errno' value and converts it to a term.
+ *
+ * If the errno can be translated using erl_errno_id,
+ * then we use that value otherwise we use the errno
+ * integer value converted to a term.
+ * Unless there is a specific error code that can be
+ * handled specially.
+ */
+extern
+ERL_NIF_TERM esock_errno_to_term(ErlNifEnv* env, int err)
+{
+ switch (err) {
+#if defined(NO_ERROR)
+ case NO_ERROR:
+ return MKA(env, "no_error");
+ break;
+#endif
+
+#if defined(WSA_IO_PENDING)
+ case WSA_IO_PENDING:
+ return MKA(env, "io_pending");
+ break;
+#endif
+
+#if defined(WSA_IO_INCOMPLETE)
+ case WSA_IO_INCOMPLETE:
+ return MKA(env, "io_incomplete");
+ break;
+#endif
+
+#if defined(WSA_OPERATION_ABORTED)
+ case WSA_OPERATION_ABORTED:
+ return MKA(env, "operation_aborted");
+ break;
+#endif
+
+#if defined(WSA_INVALID_PARAMETER)
+ case WSA_INVALID_PARAMETER:
+ return MKA(env, "invalid_parameter");
+ break;
+#endif
+
+#if defined(ERROR_INVALID_NETNAME)
+ case ERROR_INVALID_NETNAME:
+ return MKA(env, "invalid_netname");
+ break;
+#endif
+
+#if defined(ERROR_MORE_DATA)
+ case ERROR_MORE_DATA:
+ return MKA(env, "more_data");
+ break;
+#endif
+
+ default:
+ {
+ char* str = erl_errno_id(err);
+ if ( strcmp(str, "unknown") == 0 )
+ return MKI(env, err);
+ else
+ return MKA(env, str);
+ }
+ break;
+ }
+
+ /* This is just in case of programming error.
+ * We should not get this far!
+ */
+ return MKI(env, err);
+}
+
+
+
+/* *** esock_make_extra_error_info_term ***
+ * This is used primarily for debugging.
+ * Is supposed to be called via the 'MKEEI' macro.
+ */
+extern
+ERL_NIF_TERM esock_make_extra_error_info_term(ErlNifEnv* env,
+ const char* file,
+ const char* function,
+ const int line,
+ ERL_NIF_TERM rawinfo,
+ ERL_NIF_TERM info)
+{
+ ERL_NIF_TERM keys[] = {MKA(env, "file"),
+ MKA(env, "function"),
+ MKA(env, "line"),
+ MKA(env, "raw_info"),
+ MKA(env, "info")};
+ ERL_NIF_TERM vals[] = {MKS(env, file),
+ MKS(env, function),
+ MKI(env, line),
+ rawinfo,
+ info};
+ unsigned int numKeys = NUM(keys);
+ unsigned int numVals = NUM(vals);
+ ERL_NIF_TERM map;
+
+ ESOCK_ASSERT( numKeys == numVals );
+ ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &map) );
+
+ return map;
+}
+
+
+
/* Create an error two (2) tuple in the form:
*
* {error, Reason}
@@ -2317,6 +2451,18 @@ ERL_NIF_TERM esock_make_error(ErlNifEnv* env, ERL_NIF_TERM reason)
+/* Create an error two (2) tuple in the form:
+ *
+ * {error, closed}
+ */
+extern
+ERL_NIF_TERM esock_make_error_closed(ErlNifEnv* env)
+{
+ return esock_make_error(env, esock_atom_closed);
+}
+
+
+
/* Create an error two (2) tuple in the form: {error, Reason}.
*
* {error, Reason}
@@ -2348,6 +2494,23 @@ ERL_NIF_TERM esock_make_error_errno(ErlNifEnv* env, int err)
/* Create an error two (2) tuple in the form:
*
+ * {error, {Tag, Reason}}
+ *
+ * Both 'Tag' and 'Reason' are already in the form of an
+ * ERL_NIF_TERM so all we have to do is create "the" tuple.
+ */
+extern
+ERL_NIF_TERM esock_make_error_t2r(ErlNifEnv* env,
+ ERL_NIF_TERM tag,
+ ERL_NIF_TERM reason)
+{
+ return MKT2(env, esock_atom_error, MKT2(env, tag, reason));
+}
+
+
+
+/* Create an error two (2) tuple in the form:
+ *
* {error, {invalid, What}}}
*/
extern
@@ -2457,45 +2620,53 @@ ERL_NIF_TERM esock_self(ErlNifEnv* env)
-/* *** esock_warning_msg ***
- *
- * Temporary function for issuing warning messages.
+/*
+ * We should really include self in the printout,
+ * so we can se which process are executing the code.
+ * But then I must change the API....something for later.
*
+ * esock_info_msg
+ * esock_warning_msg
+ * esock_error_msg
*/
-extern
-void esock_warning_msg( const char* format, ... )
-{
- va_list args;
- char f[512 + sizeof(format)]; // This has to suffice...
- char stamp[64]; // Just in case...
- int res;
-
- /*
- * We should really include self in the printout,
- * so we can se which process are executing the code.
- * But then I must change the API....something for later.
- */
-
- // 2018-06-29 12:13:21.232089
- // 29-Jun-2018::13:47:25.097097
-
- if (esock_timestamp_str(stamp, sizeof(stamp))) {
- res = enif_snprintf(f, sizeof(f),
- "=WARNING MSG==== %s ===\r\n%s",
- stamp, format);
- } else {
- res = enif_snprintf(f, sizeof(f), "=WARNING MSG==== %s", format);
- }
- if (res > 0) {
- va_start (args, format);
- enif_vfprintf (stdout, f, args);
- va_end (args);
- fflush(stdout);
- }
-
- return;
-}
+#define MSG_FUNCS \
+ MSG_FUNC_DECL(info, INFO) \
+ MSG_FUNC_DECL(warning, WARNING) \
+ MSG_FUNC_DECL(error, ERROR)
+
+#define MSG_FUNC_DECL(FN, MC) \
+ extern \
+ void esock_##FN##_msg( const char* format, ... ) \
+ { \
+ va_list args; \
+ char f[512 + sizeof(format)]; \
+ char stamp[64]; \
+ int res; \
+ \
+ if (esock_timestamp_str(stamp, sizeof(stamp))) { \
+ res = enif_snprintf(f, sizeof(f), \
+ "=" #MC " MSG==== %s ===\r\n%s", \
+ stamp, format); \
+ } else { \
+ res = enif_snprintf(f, \
+ sizeof(f), \
+ "=" #MC " MSG==== %s", format); \
+ } \
+ \
+ if (res > 0) { \
+ va_start (args, format); \
+ enif_vfprintf (stdout, f, args); \
+ va_end (args); \
+ fflush(stdout); \
+ } \
+ \
+ return; \
+ } \
+
+MSG_FUNCS
+#undef MSG_FUNC_DECL
+#undef MSG_FUNCS
/* *** esock_timestamp ***
@@ -2507,7 +2678,7 @@ void esock_warning_msg( const char* format, ... )
*/
extern
-ErlNifTime esock_timestamp()
+ErlNifTime esock_timestamp(void)
{
ErlNifTime monTime = enif_monotonic_time(ERL_NIF_USEC);
ErlNifTime offTime = enif_time_offset(ERL_NIF_USEC);
diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h
index 2533ee145a..c16803bbdc 100644
--- a/erts/emulator/nifs/common/socket_util.h
+++ b/erts/emulator/nifs/common/socket_util.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2018-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2018-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,36 @@
#define ESOCK_ABORT(E) esock_abort(E, __func__, __FILE__, __LINE__)
#define ESOCK_ASSERT(e) ((void) ((e) ? 1 : (ESOCK_ABORT(#e), 0)))
+#define MKEEI(E, RI, I) \
+ esock_make_extra_error_info_term((E), \
+ __FILE__, \
+ __FUNCTION__, \
+ __LINE__, \
+ (RI), (I))
+
+#if defined(ESOCK_USE_EXTENDED_ERROR_INFO)
+#define ENO2T(E, ENO) MKEEI((E), \
+ MKI((E), (ENO)), \
+ esock_errno_to_term((E), (ENO)))
+#else
+#define ENO2T(E, ENO) esock_errno_to_term((E), (ENO))
+#endif
+
+
+extern
+ERL_NIF_TERM esock_make_extra_error_info_term(ErlNifEnv* env,
+ const char* file,
+ const char* function,
+ const int line,
+ ERL_NIF_TERM rawinfo,
+ ERL_NIF_TERM info);
+
+extern
+unsigned int esock_get_uint_from_map(ErlNifEnv* env,
+ ERL_NIF_TERM map,
+ ERL_NIF_TERM key,
+ unsigned int def);
+
extern
BOOLEAN_T esock_get_bool_from_map(ErlNifEnv* env,
ERL_NIF_TERM map,
@@ -241,12 +271,19 @@ ERL_NIF_TERM esock_self(ErlNifEnv* env);
extern
ERL_NIF_TERM esock_make_ok2(ErlNifEnv* env, ERL_NIF_TERM any);
extern
+ERL_NIF_TERM esock_errno_to_term(ErlNifEnv* env, int err);
+extern
ERL_NIF_TERM esock_make_error(ErlNifEnv* env, ERL_NIF_TERM reason);
extern
+ERL_NIF_TERM esock_make_error_closed(ErlNifEnv* env);
+extern
ERL_NIF_TERM esock_make_error_str(ErlNifEnv* env, char* reason);
extern
ERL_NIF_TERM esock_make_error_errno(ErlNifEnv* env, int err);
extern
+ERL_NIF_TERM esock_make_error_t2r(ErlNifEnv* env,
+ ERL_NIF_TERM tag, ERL_NIF_TERM reason);
+extern
ERL_NIF_TERM esock_make_error_invalid(ErlNifEnv* env, ERL_NIF_TERM what);
extern
ERL_NIF_TERM esock_make_error_integer_range(ErlNifEnv* env, ERL_NIF_TERM i);
@@ -267,8 +304,18 @@ BOOLEAN_T esock_timestamp_str(char *buf, unsigned int len);
extern
BOOLEAN_T esock_format_timestamp(ErlNifTime timestamp, char *buf, unsigned int len);
-extern
-void esock_warning_msg(const char* format, ... );
+#define MSG_FUNCS \
+ MSG_FUNC_DEF(info) \
+ MSG_FUNC_DEF(warning) \
+ MSG_FUNC_DEF(error)
+
+#define MSG_FUNC_DEF(FN) \
+ extern \
+ void esock_##FN##_msg(const char* format, ... );
+
+MSG_FUNCS
+#undef MSG_FUNC_DEF
+#undef MSG_FUNCS
extern
BOOLEAN_T esock_is_integer(ErlNifEnv *env, ERL_NIF_TERM term);
diff --git a/erts/emulator/nifs/unix/unix_socket_syncio.c b/erts/emulator/nifs/unix/unix_socket_syncio.c
new file mode 100644
index 0000000000..644400aeb5
--- /dev/null
+++ b/erts/emulator/nifs/unix/unix_socket_syncio.c
@@ -0,0 +1,7390 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022-2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ *
+ * ----------------------------------------------------------------------
+ * Purpose : UNIX version of synchronous I/O backend.
+ * ----------------------------------------------------------------------
+ *
+ * essio = ESock Synchronous I/O
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef ESOCK_ENABLE
+
+#ifdef HAVE_SENDFILE
+#if defined(__linux__) || (defined(__sun) && defined(__SVR4))
+ #include <sys/sendfile.h>
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+ /* Need to define __BSD_VISIBLE in order to expose prototype
+ * of sendfile in sys/socket.h
+ */
+ #define __BSD_VISIBLE 1
+#endif
+#endif
+
+#ifndef WANT_NONBLOCKING
+#define WANT_NONBLOCKING
+#endif
+#include "sys.h"
+
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#include <net/if.h>
+
+#include "prim_socket_int.h"
+#include "socket_util.h"
+#include "socket_io.h"
+#include "socket_syncio.h"
+#include "socket_tarray.h"
+#include "prim_file_nif_dyncall.h"
+
+
+/* ======================================================================== *
+ * Socket wrappers *
+ * ======================================================================== *
+ */
+
+#ifdef HAS_ACCEPT4
+// We have to figure out what the flags are...
+#define sock_accept(s, addr, len) \
+ accept4((s), (addr), (len), (SOCK_CLOEXEC))
+#else
+#define sock_accept(s, addr, len) accept((s), (addr), (len))
+#endif
+#define sock_bind(s, addr, len) bind((s), (addr), (len))
+#define sock_close(s) close((s))
+// #define sock_close_event(e) /* do nothing */
+#define sock_connect(s, addr, len) connect((s), (addr), (len))
+#define sock_errno() errno
+// #define sock_listen(s, b) listen((s), (b))
+// #define sock_name(s, addr, len) getsockname((s), (addr), (len))
+#define sock_open(domain, type, proto) socket((domain), (type), (proto))
+#define sock_peer(s, addr, len) getpeername((s), (addr), (len))
+#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag))
+#define sock_recvfrom(s,buf,blen,flag,addr,alen) \
+ recvfrom((s),(buf),(blen),(flag),(addr),(alen))
+#define sock_recvmsg(s,msghdr,flag) recvmsg((s),(msghdr),(flag))
+#define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag))
+#define sock_sendmsg(s,msghdr,flag) sendmsg((s),(msghdr),(flag))
+#define sock_sendto(s,buf,blen,flag,addr,alen) \
+ sendto((s),(buf),(blen),(flag),(addr),(alen))
+#define sock_shutdown(s, how) shutdown((s), (how))
+
+
+/* =================================================================== *
+ * *
+ * Various esaio macros *
+ * *
+ * =================================================================== */
+
+/* Global socket debug */
+#define SGDBG( proto ) ESOCK_DBG_PRINTF( ctrl.dbg , proto )
+
+
+/* =================================================================== *
+ * *
+ * Local types *
+ * *
+ * =================================================================== */
+
+typedef struct {
+ /* Misc stuff */
+ BOOLEAN_T dbg;
+ BOOLEAN_T sockDbg;
+} ESSIOControl;
+
+
+
+/* ======================================================================== *
+ * Function Forwards *
+ * ======================================================================== *
+ */
+static BOOLEAN_T open_todup(ErlNifEnv* env,
+ ERL_NIF_TERM eopts);
+static BOOLEAN_T open_which_domain(SOCKET sock, int* domain);
+static BOOLEAN_T open_which_type(SOCKET sock, int* type);
+static BOOLEAN_T open_get_domain(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ int* domain);
+static BOOLEAN_T open_get_type(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ int* type);
+static BOOLEAN_T open_get_protocol(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ int* protocol);
+
+#ifdef HAVE_SETNS
+static BOOLEAN_T open_get_netns(ErlNifEnv* env,
+ ERL_NIF_TERM opts,
+ char** netns);
+static BOOLEAN_T change_network_namespace(BOOLEAN_T dbg,
+ char* netns, int* cns, int* err);
+static BOOLEAN_T restore_network_namespace(BOOLEAN_T dbg,
+ int ns, SOCKET sock, int* err);
+#endif
+
+static BOOLEAN_T verify_is_connected(ESockDescriptor* descP, int* err);
+
+static ERL_NIF_TERM essio_cancel_accept_current(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM essio_cancel_accept_waiting(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef,
+ const ErlNifPid* selfP);
+static ERL_NIF_TERM essio_cancel_send_current(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM essio_cancel_send_waiting(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef,
+ const ErlNifPid* selfP);
+static ERL_NIF_TERM essio_cancel_recv_current(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM essio_cancel_recv_waiting(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef,
+ const ErlNifPid* selfP);
+
+static ERL_NIF_TERM essio_accept_listening_error(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef,
+ ErlNifPid caller,
+ int save_errno);
+static ERL_NIF_TERM essio_accept_listening_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ SOCKET accSock,
+ ErlNifPid caller);
+static ERL_NIF_TERM essio_accept_accepting_current(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM ref);
+static
+ERL_NIF_TERM essio_accept_accepting_current_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ SOCKET accSock);
+static
+ERL_NIF_TERM essio_accept_accepting_current_error(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef,
+ int save_errno);
+static ERL_NIF_TERM essio_accept_accepting_other(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ErlNifPid caller);
+static ERL_NIF_TERM essio_accept_busy_retry(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef,
+ ErlNifPid* pidP);
+static BOOLEAN_T essio_accept_accepted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ SOCKET accSock,
+ ErlNifPid pid,
+ ERL_NIF_TERM* result);
+
+static BOOLEAN_T send_check_writer(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ERL_NIF_TERM* checkResult);
+static ERL_NIF_TERM send_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t send_result,
+ ssize_t dataSize,
+ BOOLEAN_T dataInTail,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef);
+static ERL_NIF_TERM send_check_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t written,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM send_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef);
+static void send_error_waiting_writers(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason);
+static ERL_NIF_TERM send_check_retry(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t written,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef);
+
+static BOOLEAN_T decode_cmsghdrs(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eCMsg,
+ char* cmsgHdrBufP,
+ size_t cmsgHdrBufLen,
+ size_t* cmsgHdrBufUsed);
+static BOOLEAN_T decode_cmsghdr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eCMsg,
+ char* bufP,
+ size_t rem,
+ size_t* used);
+static BOOLEAN_T decode_cmsghdr_value(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ ERL_NIF_TERM eType,
+ ERL_NIF_TERM eValue,
+ char* dataP,
+ size_t dataLen,
+ size_t* dataUsedP);
+static BOOLEAN_T decode_cmsghdr_data(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ ERL_NIF_TERM eType,
+ ERL_NIF_TERM eData,
+ char* dataP,
+ size_t dataLen,
+ size_t* dataUsedP);
+
+static void encode_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ struct msghdr* msgHdrP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM* eMsg);
+static void encode_cmsgs(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifBinary* cmsgBinP,
+ struct msghdr* msgHdrP,
+ ERL_NIF_TERM* eCMsg);
+
+#if defined(HAVE_SENDFILE)
+static int essio_sendfile(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ off_t offset,
+ size_t* countP,
+ int* errP);
+static ERL_NIF_TERM essio_sendfile_errno(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ int err);
+static ERL_NIF_TERM essio_sendfile_error(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason);
+static ERL_NIF_TERM essio_sendfile_select(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ size_t count);
+static ERL_NIF_TERM essio_sendfile_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ size_t count);
+#endif
+
+static ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ssize_t toRead,
+ int saveErrno,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ int saveErrno,
+ ErlNifBinary* bufP,
+ ESockAddress* fromAddrP,
+ SOCKLEN_T fromAddrLen,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static BOOLEAN_T recv_check_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ERL_NIF_TERM* checkResult);
+static ERL_NIF_TERM recv_check_full(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ssize_t toRead,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recv_check_full_maybe_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recv_check_full_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM recv_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int saveErrno,
+ ErlNifBinary* buf1P,
+ ErlNifBinary* buf2P,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recv_check_fail_gen(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM recv_check_fail_econnreset(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recv_check_retry(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recv_check_partial(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ssize_t toRead,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recv_check_partial_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM recv_check_partial_part(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static void recv_init_current_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM recvRef);
+static void recv_update_current_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef);
+static void recv_error_current_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason);
+
+static ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ int saveErrno,
+ struct msghdr* msgHdrP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recvmsg_check_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ struct msghdr* msgHdrP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM sockRef);
+
+
+static ERL_NIF_TERM essio_ioctl_gifconf(ErlNifEnv* env,
+ ESockDescriptor* descP);
+#if defined(SIOCGIFNAME)
+static ERL_NIF_TERM essio_ioctl_gifname(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eidx);
+#endif
+
+/* esock_ioctl_gifindex */
+#if defined(SIOCGIFINDEX)
+#define IOCTL_GIFINDEX_FUNC_DEF IOCTL_GET_FUNC_DEF(gifindex)
+#else
+#define IOCTL_GIFINDEX_FUNC_DEF
+#endif
+
+/* esock_ioctl_gifflags */
+#if defined(SIOCGIFFLAGS)
+#define IOCTL_GIFFLAGS_FUNC_DEF IOCTL_GET_FUNC_DEF(gifflags)
+#else
+#define IOCTL_GIFFLAGS_FUNC_DEF
+#endif
+
+/* esock_ioctl_gifaddr */
+#if defined(SIOCGIFADDR)
+#define IOCTL_GIFADDR_FUNC_DEF IOCTL_GET_FUNC_DEF(gifaddr)
+#else
+#define IOCTL_GIFADDR_FUNC_DEF
+#endif
+
+/* esock_ioctl_gifdstaddr */
+#if defined(SIOCGIFDSTADDR)
+#define IOCTL_GIFDSTADDR_FUNC_DEF IOCTL_GET_FUNC_DEF(gifdstaddr)
+#else
+#define IOCTL_GIFDSTADDR_FUNC_DEF
+#endif
+
+/* esock_ioctl_gifbrdaddr */
+#if defined(SIOCGIFBRDADDR)
+#define IOCTL_GIFBRDADDR_FUNC_DEF IOCTL_GET_FUNC_DEF(gifbrdaddr)
+#else
+#define IOCTL_GIFBRDADDR_FUNC_DEF
+#endif
+
+/* esock_ioctl_gifnetmask */
+#if defined(SIOCGIFNETMASK)
+#define IOCTL_GIFNETMASK_FUNC_DEF IOCTL_GET_FUNC_DEF(gifnetmask)
+#else
+#define IOCTL_GIFNETMASK_FUNC_DEF
+#endif
+
+/* esock_ioctl_gifmtu */
+#if defined(SIOCGIFMTU)
+#define IOCTL_GIFMTU_FUNC_DEF IOCTL_GET_FUNC_DEF(gifmtu)
+#else
+#define IOCTL_GIFMTU_FUNC_DEF
+#endif
+
+/* esock_ioctl_gifhwaddr */
+#if defined(SIOCGIFHWADDR) && defined(ESOCK_USE_HWADDR)
+#define IOCTL_GIFHWADDR_FUNC_DEF IOCTL_GET_FUNC_DEF(gifhwaddr)
+#else
+#define IOCTL_GIFHWADDR_FUNC_DEF
+#endif
+
+/* esock_ioctl_gifmap */
+#if defined(SIOCGIFMAP) && defined(ESOCK_USE_IFMAP)
+#define IOCTL_GIFMAP_FUNC_DEF IOCTL_GET_FUNC_DEF(gifmap)
+#else
+#define IOCTL_GIFMAP_FUNC_DEF
+#endif
+
+/* esock_ioctl_giftxqlen */
+#if defined(SIOCGIFTXQLEN)
+#define IOCTL_GIFTXQLEN_FUNC_DEF IOCTL_GET_FUNC_DEF(giftxqlen)
+#else
+#define IOCTL_GIFTXQLEN_FUNC_DEF
+#endif
+
+#define IOCTL_GET_FUNCS_DEF \
+ IOCTL_GIFINDEX_FUNC_DEF; \
+ IOCTL_GIFFLAGS_FUNC_DEF; \
+ IOCTL_GIFADDR_FUNC_DEF; \
+ IOCTL_GIFDSTADDR_FUNC_DEF; \
+ IOCTL_GIFBRDADDR_FUNC_DEF; \
+ IOCTL_GIFNETMASK_FUNC_DEF; \
+ IOCTL_GIFMTU_FUNC_DEF; \
+ IOCTL_GIFHWADDR_FUNC_DEF; \
+ IOCTL_GIFMAP_FUNC_DEF; \
+ IOCTL_GIFTXQLEN_FUNC_DEF;
+#define IOCTL_GET_FUNC_DEF(F) \
+ static ERL_NIF_TERM essio_ioctl_##F(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM ename)
+IOCTL_GET_FUNCS_DEF
+#undef IOCTL_GET_FUNC_DEF
+
+/* esock_ioctl_sifflags */
+#if defined(SIOCSIFFLAGS)
+#define IOCTL_SIFFLAGS_FUNC_DEF IOCTL_SET_FUNC_DEF(sifflags)
+#else
+#define IOCTL_SIFFLAGS_FUNC_DEF
+#endif
+
+/* esock_ioctl_sifaddr */
+#if defined(SIOCSIFADDR)
+#define IOCTL_SIFADDR_FUNC_DEF IOCTL_SET_FUNC_DEF(sifaddr)
+#else
+#define IOCTL_SIFADDR_FUNC_DEF
+#endif
+
+/* esock_ioctl_sifdstaddr */
+#if defined(SIOCSIFDSTADDR)
+#define IOCTL_SIFDSTADDR_FUNC_DEF IOCTL_SET_FUNC_DEF(sifdstaddr)
+#else
+#define IOCTL_SIFDSTADDR_FUNC_DEF
+#endif
+
+/* esock_ioctl_sifbrdaddr */
+#if defined(SIOCSIFBRDADDR)
+#define IOCTL_SIFBRDADDR_FUNC_DEF IOCTL_SET_FUNC_DEF(sifbrdaddr)
+#else
+#define IOCTL_SIFBRDADDR_FUNC_DEF
+#endif
+
+/* esock_ioctl_sifnetmask */
+#if defined(SIOCSIFNETMASK)
+#define IOCTL_SIFNETMASK_FUNC_DEF IOCTL_SET_FUNC_DEF(sifnetmask)
+#else
+#define IOCTL_SIFNETMASK_FUNC_DEF
+#endif
+
+/* esock_ioctl_sifmtu */
+#if defined(SIOCSIFMTU)
+#define IOCTL_SIFMTU_FUNC_DEF IOCTL_SET_FUNC_DEF(sifmtu)
+#else
+#define IOCTL_SIFMTU_FUNC_DEF
+#endif
+
+/* esock_ioctl_siftxqlen */
+#if defined(SIOCSIFTXQLEN)
+#define IOCTL_SIFTXQLEN_FUNC_DEF IOCTL_SET_FUNC_DEF(siftxqlen)
+#else
+#define IOCTL_SIFTXQLEN_FUNC_DEF
+#endif
+
+#define IOCTL_SET_FUNCS_DEF \
+ IOCTL_SIFFLAGS_FUNC_DEF; \
+ IOCTL_SIFADDR_FUNC_DEF; \
+ IOCTL_SIFDSTADDR_FUNC_DEF; \
+ IOCTL_SIFBRDADDR_FUNC_DEF; \
+ IOCTL_SIFNETMASK_FUNC_DEF; \
+ IOCTL_SIFMTU_FUNC_DEF; \
+ IOCTL_SIFTXQLEN_FUNC_DEF;
+#define IOCTL_SET_FUNC_DEF(F) \
+ static ERL_NIF_TERM essio_ioctl_##F(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM ename, \
+ ERL_NIF_TERM evalue)
+IOCTL_SET_FUNCS_DEF
+#undef IOCTL_SET_FUNC_DEF
+
+
+static ERL_NIF_TERM encode_ioctl_ifconf(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ struct ifconf* ifcP);
+static ERL_NIF_TERM encode_ioctl_ifconf_ifreq(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ struct ifreq* ifrP);
+static ERL_NIF_TERM encode_ioctl_ifreq_name(ErlNifEnv* env,
+ char* name);
+static ERL_NIF_TERM encode_ioctl_ifreq_sockaddr(ErlNifEnv* env,
+ struct sockaddr* sa);
+static ERL_NIF_TERM make_ifreq(ErlNifEnv* env,
+ ERL_NIF_TERM name,
+ ERL_NIF_TERM key2,
+ ERL_NIF_TERM val2);
+#if defined(SIOCGIFMAP) && defined(ESOCK_USE_IFMAP)
+static ERL_NIF_TERM encode_ioctl_ifrmap(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ struct ifmap* mapP);
+#endif
+#if defined(SIOCGIFHWADDR) && defined(ESOCK_USE_HWADDR)
+static ERL_NIF_TERM encode_ioctl_hwaddr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ struct sockaddr* addrP);
+#endif
+static ERL_NIF_TERM encode_ioctl_ifraddr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ struct sockaddr* addrP);
+static ERL_NIF_TERM encode_ioctl_flags(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ short flags);
+#if defined(SIOCSIFFLAGS)
+static BOOLEAN_T decode_ioctl_flags(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eflags,
+ short* flags);
+#endif
+static BOOLEAN_T decode_ioctl_sockaddr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eaddr,
+ ESockAddress* addr);
+#if defined(SIOCSIFMTU)
+static BOOLEAN_T decode_ioctl_mtu(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM emtu,
+ int* mtu);
+#endif
+#if defined(SIOCSIFTXQLEN)
+static BOOLEAN_T decode_ioctl_txqlen(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM etxqlen,
+ int* txqlen);
+#endif
+#if defined(SIOCSIFTXQLEN)
+static BOOLEAN_T decode_ioctl_ivalue(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eivalue,
+ int* ivalue);
+#endif
+static ERL_NIF_TERM encode_ioctl_ivalue(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int ivalue);
+
+
+/*
+static void essio_down_ctrl(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pidP);
+*/
+static void essio_down_acceptor(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP);
+static void essio_down_writer(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP);
+static void essio_down_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP);
+
+static BOOLEAN_T do_stop(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+
+/* =================================================================== *
+ * *
+ * Local (global) variables *
+ * *
+ * =================================================================== */
+
+static ESSIOControl ctrl = {0};
+
+
+
+/* ======================================================================== *
+ * ESSIO Functions *
+ * ======================================================================== *
+ */
+
+/*
+ * For "standard" (unix) synchronous I/O, in our case
+ * this is just a dummy function.
+ */
+extern
+int essio_init(unsigned int numThreads,
+ const ESockData* dataP)
+{
+ VOID(numThreads);
+
+ ctrl.dbg = dataP->dbg;
+ ctrl.sockDbg = dataP->sockDbg;
+
+ return ESOCK_IO_OK;
+}
+
+
+/*
+ * For "standard" (unix) synchronous I/O, this is just a dummy function.
+ * Also, will we ever call this?
+ */
+extern
+void essio_finish(void)
+{
+ return;
+}
+
+
+
+/* *******************************************************************
+ * essio_info - Return info "about" this I/O backend.
+ */
+
+extern
+ERL_NIF_TERM essio_info(ErlNifEnv* env)
+{
+ ERL_NIF_TERM info;
+ ERL_NIF_TERM keys[] = {esock_atom_name};
+ ERL_NIF_TERM vals[] = {MKA(env, "unix_essio")};
+ unsigned int numKeys = NUM(keys);
+ unsigned int numVals = NUM(vals);
+
+ ESOCK_ASSERT( numKeys == numVals );
+ ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &info) );
+
+ return info;
+}
+
+
+
+/* ========================================================================
+ * essio_open - create an endpoint (from an existing fd) for communication
+ *
+ * Assumes the input has been validated.
+ *
+ * Normally we want debugging on (individual) sockets to be controlled
+ * by the sockets own debug flag. But since we don't even have a socket
+ * yet, we must use the global debug flag.
+ */
+extern
+ERL_NIF_TERM essio_open_with_fd(ErlNifEnv* env,
+ int fd,
+ ERL_NIF_TERM eopts,
+ const ESockData* dataP)
+{
+ BOOLEAN_T dbg = esock_open_is_debug(env, eopts, dataP->sockDbg);
+ BOOLEAN_T useReg = esock_open_use_registry(env, eopts, dataP->useReg);
+ ESockDescriptor* descP;
+ ERL_NIF_TERM sockRef;
+ int domain, type, protocol;
+ int save_errno = 0;
+ BOOLEAN_T closeOnClose;
+ SOCKET sock;
+ ErlNifPid self;
+
+ /* Keep track of the creator
+ * This should not be a problem, but just in case
+ * the *open* function is used with the wrong kind
+ * of environment...
+ */
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ SSDBG2( dbg,
+ ("UNIX-ESSIO", "essio_open2 -> entry with"
+ "\r\n fd: %d"
+ "\r\n eopts: %T"
+ "\r\n", fd, eopts) );
+
+ /*
+ * Before we do anything else, we try to retrieve domain, type and protocol
+ * This information is either present in the eopts map or if not we need
+ * to "get" it from the system (getsockopt).
+ * Note that its not possible to get all of these on all platforms,
+ * and in those cases the user *must* provide us with them (eopts).
+ *
+ * We try the system first (since its more reliable) and if that fails
+ * we check the eopts map. If neither one works, we *give up*!
+ */
+
+ if (! open_which_domain(fd, &domain)) {
+ SSDBG2( dbg,
+ ("UNIX-ESSIO",
+ "essio_open2 -> failed get domain from system\r\n") );
+
+ if (! open_get_domain(env, eopts, &domain)) {
+ return esock_make_invalid(env, esock_atom_domain);
+ }
+ }
+
+ if (! open_which_type(fd, &type)) {
+ SSDBG2( dbg,
+ ("UNIX-ESSIO",
+ "essio_open2 -> failed get type from system\r\n") );
+
+ if (! open_get_type(env, eopts, &type))
+ return esock_make_invalid(env, esock_atom_type);
+ }
+
+ if (! esock_open_which_protocol(fd, &protocol)) {
+ SSDBG2( dbg,
+ ("UNIX-ESSIO",
+ "essio_open2 -> failed get protocol from system\r\n") );
+
+ if (! open_get_protocol(env, eopts, &protocol)) {
+ SSDBG2( dbg,
+ ("UNIX-ESSIO",
+ "essio_open2 -> "
+ "failed get protocol => try protocol 0\r\n") );
+ protocol = 0;
+ }
+ }
+
+
+ SSDBG2( dbg,
+ ("UNIX-ESSIO",
+ "essio_open2 -> "
+ "\r\n domain: %d"
+ "\r\n type: %d"
+ "\r\n protocol: %d"
+ "\r\n", domain, type, protocol) );
+
+
+ if (open_todup(env, eopts)) {
+ /* We shall dup the socket */
+ if (ESOCK_IS_ERROR(sock = dup(fd))) {
+ save_errno = sock_errno();
+
+ SSDBG2( dbg,
+ ("UNIX-ESSIO",
+ "essio_open2 -> dup failed: %d\r\n",
+ save_errno) );
+
+ return esock_make_error_errno(env, save_errno);
+ }
+ closeOnClose = TRUE;
+ } else {
+ sock = fd;
+ closeOnClose = FALSE;
+ }
+
+
+ SET_NONBLOCKING(sock);
+
+ /* Create and initiate the socket "descriptor" */
+ descP = esock_alloc_descriptor(sock);
+ descP->ctrlPid = self;
+ descP->domain = domain;
+ descP->type = type;
+ descP->protocol = protocol;
+ descP->closeOnClose = closeOnClose;
+ descP->origFD = fd;
+
+ /* Check if we are already connected, if so change state */
+ {
+ ESockAddress remote;
+ SOCKLEN_T addrLen = sizeof(remote);
+ sys_memzero((char *) &remote, addrLen);
+ if (sock_peer(descP->sock,
+ (struct sockaddr*) &remote,
+ &addrLen) == 0) {
+ SSDBG2( dbg, ("UNIX-ESSIO", "essio_open2 -> connected\r\n") );
+ descP->writeState |= ESOCK_STATE_CONNECTED;
+ } else {
+ SSDBG2( dbg, ("UNIX-ESSIO", "essio_open2 -> not connected\r\n") );
+ }
+ }
+
+ /* And create the 'socket' resource */
+ sockRef = enif_make_resource(env, descP);
+ enif_release_resource(descP);
+
+ ESOCK_ASSERT( MONP("essio_open2 -> ctrl",
+ env, descP,
+ &descP->ctrlPid,
+ &descP->ctrlMon) == 0 );
+
+ descP->dbg = dbg;
+ descP->useReg = useReg;
+ esock_inc_socket(domain, type, protocol);
+
+ /* And finally (maybe) update the registry.
+ * Shall we keep track of the fact that this socket is created elsewhere?
+ */
+ if (descP->useReg) esock_send_reg_add_msg(env, descP, sockRef);
+
+ SSDBG2( dbg,
+ ("UNIX-ESSIO", "essio_open2 -> done: %T\r\n", sockRef) );
+
+ return esock_make_ok2(env, sockRef);
+}
+
+
+static
+BOOLEAN_T open_which_domain(SOCKET sock, int* domain)
+{
+#if defined(SO_DOMAIN)
+ if (esock_getopt_int(sock, SOL_SOCKET, SO_DOMAIN, domain))
+ return TRUE;
+#endif
+ return FALSE;
+}
+
+/* The eopts contains an integer 'domain' key.
+ */
+static
+BOOLEAN_T open_get_domain(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ int* domain)
+{
+ ERL_NIF_TERM edomain;
+
+ if (!GET_MAP_VAL(env, eopts,
+ esock_atom_domain, &edomain))
+ return FALSE;
+
+ if (esock_decode_domain(env, edomain, domain) == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static
+BOOLEAN_T open_which_type(SOCKET sock, int* type)
+{
+#if defined(SO_TYPE)
+ if (esock_getopt_int(sock, SOL_SOCKET, SO_TYPE, type))
+ return TRUE;
+#endif
+ return FALSE;
+}
+
+/* The eopts contains an integer 'type' key.
+ */
+static
+BOOLEAN_T open_get_type(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ int* type)
+{
+ ERL_NIF_TERM etype;
+
+ if (! GET_MAP_VAL(env, eopts, esock_atom_type, &etype))
+ return FALSE;
+
+ if (! esock_decode_type(env, etype, type))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* The eopts contains an integer 'type' key.
+ */
+static
+BOOLEAN_T open_get_protocol(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ int* protocol)
+{
+ return esock_extract_int_from_map(env, eopts,
+ esock_atom_protocol, protocol);
+}
+
+
+/* The eopts contains a boolean 'dup' key. Defaults to TRUE.
+ */
+static
+BOOLEAN_T open_todup(ErlNifEnv* env, ERL_NIF_TERM eopts)
+{
+ return esock_get_bool_from_map(env, eopts, esock_atom_dup, TRUE);
+}
+
+
+/* ========================================================================
+ */
+extern
+ERL_NIF_TERM essio_open_plain(ErlNifEnv* env,
+ int domain,
+ int type,
+ int protocol,
+ ERL_NIF_TERM eopts,
+ const ESockData* dataP)
+{
+ BOOLEAN_T dbg = esock_open_is_debug(env, eopts, dataP->sockDbg);
+ BOOLEAN_T useReg = esock_open_use_registry(env, eopts, dataP->useReg);
+ ESockDescriptor* descP;
+ ERL_NIF_TERM sockRef;
+ int proto = protocol;
+ SOCKET sock;
+ char* netns;
+#ifdef HAVE_SETNS
+ int save_errno;
+ int current_ns = 0;
+#endif
+ ErlNifPid self;
+
+ /* Keep track of the creator
+ * This should not be a problem, but just in case
+ * the *open* function is used with the wrong kind
+ * of environment...
+ */
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ SSDBG2( dbg,
+ ("UNIX-ESSIO", "essio_open4 -> entry with"
+ "\r\n domain: %d"
+ "\r\n type: %d"
+ "\r\n protocol: %d"
+ "\r\n eopts: %T"
+ "\r\n", domain, type, protocol, eopts) );
+
+
+#ifdef HAVE_SETNS
+ if (open_get_netns(env, eopts, &netns)) {
+ SSDBG2( dbg,
+ ("UNIX-ESSIO", "essio_open4 -> namespace: %s\r\n", netns) );
+ }
+#else
+ netns = NULL;
+#endif
+
+
+#ifdef HAVE_SETNS
+ if ((netns != NULL) &&
+ (! change_network_namespace(dbg,
+ netns, &current_ns, &save_errno))) {
+ FREE(netns);
+ return esock_make_error_errno(env, save_errno);
+ }
+#endif
+
+ if (ESOCK_IS_ERROR(sock = sock_open(domain, type, proto))) {
+ if (netns != NULL) FREE(netns);
+ return esock_make_error_errno(env, sock_errno());
+ }
+
+ SSDBG2( dbg, ("UNIX-ESSIO", "essio_open4 -> open success: %d\r\n", sock) );
+
+
+ /* NOTE that if the protocol = 0 (default) and the domain is not
+ * local (AF_LOCAL) we need to explicitly get the protocol here!
+ */
+
+ if (proto == 0)
+ (void) esock_open_which_protocol(sock, &proto);
+
+#ifdef HAVE_SETNS
+ if (netns != NULL) {
+ FREE(netns);
+ if (! restore_network_namespace(dbg,
+ current_ns, sock, &save_errno))
+ return esock_make_error_errno(env, save_errno);
+ }
+#endif
+
+ SET_NONBLOCKING(sock);
+
+
+ /* Create and initiate the socket "descriptor" */
+ descP = esock_alloc_descriptor(sock);
+ descP->ctrlPid = self;
+ descP->domain = domain;
+ descP->type = type;
+ descP->protocol = proto;
+
+ sockRef = enif_make_resource(env, descP);
+ enif_release_resource(descP);
+
+ ESOCK_ASSERT( MONP("esock_open -> ctrl",
+ env, descP,
+ &descP->ctrlPid,
+ &descP->ctrlMon) == 0 );
+
+ descP->dbg = dbg;
+ descP->useReg = useReg;
+ esock_inc_socket(domain, type, proto);
+
+ /* And finally (maybe) update the registry */
+ if (descP->useReg) esock_send_reg_add_msg(env, descP, sockRef);
+
+ return esock_make_ok2(env, sockRef);
+}
+
+
+#ifdef HAVE_SETNS
+/* open_get_netns - extract the netns field from the opts map
+ */
+static
+BOOLEAN_T open_get_netns(ErlNifEnv* env, ERL_NIF_TERM opts, char** netns)
+{
+ ERL_NIF_TERM val;
+ ErlNifBinary bin;
+ char* buf;
+
+ /* The currently only supported extra option is: netns */
+ if (!GET_MAP_VAL(env, opts, esock_atom_netns, &val)) {
+ *netns = NULL; // Just in case...
+ return FALSE;
+ }
+
+ /* The value should be a binary file name */
+ if (! enif_inspect_binary(env, val, &bin)) {
+ *netns = NULL; // Just in case...
+ return FALSE;
+ }
+
+ ESOCK_ASSERT( (buf = MALLOC(bin.size+1)) != NULL );
+
+ sys_memcpy(buf, bin.data, bin.size);
+ buf[bin.size] = '\0';
+ *netns = buf;
+
+ return TRUE;
+}
+
+
+/* We should really have another API, so that we can return errno... */
+
+/* *** change network namespace ***
+ * Retrieve the current namespace and set the new.
+ * Return result and previous namespace if successful.
+ */
+static
+BOOLEAN_T change_network_namespace(BOOLEAN_T dbg,
+ char* netns, int* cns, int* err)
+{
+ int save_errno;
+ int current_ns = 0;
+ int new_ns = 0;
+
+ SSDBG2( dbg,
+ ("UNIX-ESSIO", "change_network_namespace -> entry with"
+ "\r\n new ns: %s"
+ "\r\n", netns) );
+
+ current_ns = open("/proc/self/ns/net", O_RDONLY);
+ if (ESOCK_IS_ERROR(current_ns)) {
+ *err = sock_errno();
+ return FALSE;
+ }
+ new_ns = open(netns, O_RDONLY);
+ if (ESOCK_IS_ERROR(new_ns)) {
+ save_errno = sock_errno();
+ (void) close(current_ns);
+ *err = save_errno;
+ return FALSE;
+ }
+ if (setns(new_ns, CLONE_NEWNET) != 0) {
+ save_errno = sock_errno();
+ (void) close(new_ns);
+ (void) close(current_ns);
+ *err = save_errno;
+ return FALSE;
+ } else {
+ (void) close(new_ns);
+ *cns = current_ns;
+ return TRUE;
+ }
+}
+
+
+/* *** restore network namespace ***
+ * Restore the previous namespace (see above).
+ */
+static
+BOOLEAN_T restore_network_namespace(BOOLEAN_T dbg,
+ int ns, SOCKET sock, int* err)
+{
+ SSDBG2( dbg,
+ ("UNIX-ESSIO", "restore_network_namespace -> entry with"
+ "\r\n ns: %d"
+ "\r\n", ns) );
+
+ if (setns(ns, CLONE_NEWNET) != 0) {
+ /* XXX Failed to restore network namespace.
+ * What to do? Tidy up and return an error...
+ * Note that the thread now might still be in the namespace.
+ * Can this even happen? Should the emulator be aborted?
+ */
+ int save_errno = sock_errno();
+ (void) close(sock);
+ (void) close(ns);
+ *err = save_errno;
+ return FALSE;
+ } else {
+ (void) close(ns);
+ return TRUE;
+ }
+}
+
+#endif
+
+
+
+/* ========================================================================
+ */
+extern
+ERL_NIF_TERM essio_bind(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESockAddress* sockAddrP,
+ SOCKLEN_T addrLen)
+{
+ if (! IS_OPEN(descP->readState))
+ return esock_make_error_closed(env);
+
+ if (sock_bind(descP->sock, &sockAddrP->sa, addrLen) < 0) {
+ return esock_make_error_errno(env, sock_errno());
+ }
+
+ descP->readState |= ESOCK_STATE_BOUND;
+
+ return esock_atom_ok;
+}
+
+
+/* ========================================================================
+ */
+extern
+ERL_NIF_TERM essio_connect(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM connRef,
+ ESockAddress* addrP,
+ SOCKLEN_T addrLen)
+{
+ int save_errno;
+ ErlNifPid self;
+
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ /*
+ * Verify that we are in the proper state
+ */
+
+ if (! IS_OPEN(descP->writeState))
+ return esock_make_error_closed(env);
+
+ /* Connect and Write uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->currentWriterP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ if (descP->connectorP != NULL) {
+ /* Connect in progress */
+
+ if (COMPARE_PIDS(&self, &descP->connector.pid) != 0) {
+ /* Other process has connect in progress */
+ if (addrP != NULL) {
+ return esock_make_error(env, esock_atom_already);
+ } else {
+ /* This is a bad call sequence
+ * - connect without an address is only allowed
+ * for the connecting process
+ */
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+ }
+
+ /* Finalize after received select message */
+
+ esock_requestor_release("essio_connect finalize -> connected",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+ descP->writeState &= ~ESOCK_STATE_CONNECTING;
+
+ if (! verify_is_connected(descP, &save_errno)) {
+ return esock_make_error_errno(env, save_errno);
+ }
+
+ descP->writeState |= ESOCK_STATE_CONNECTED;
+
+ return esock_atom_ok;
+ }
+
+ /* No connect in progress */
+
+ if (addrP == NULL)
+ /* This is a bad call sequence
+ * - connect without an address is only allowed when
+ * a connect is in progress, after getting the select message
+ */
+ return esock_raise_invalid(env, esock_atom_state);
+
+ /* Initial connect call, with address */
+
+ if (sock_connect(descP->sock, (struct sockaddr*) addrP, addrLen) == 0) {
+ /* Success already! */
+ SSDBG( descP, ("UNIX-ESSIO", "essio_connect {%d} -> connected\r\n",
+ descP->sock) );
+
+ descP->writeState |= ESOCK_STATE_CONNECTED;
+
+ return esock_atom_ok;
+ }
+
+ /* Connect returned error */
+ save_errno = sock_errno();
+
+ switch (save_errno) {
+
+ case EINPROGRESS: /* Unix & OSE!! */
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_connect {%d} -> would block => select\r\n",
+ descP->sock) );
+ {
+ int sres;
+
+ if ((sres =
+ esock_select_write(env, descP->sock, descP, NULL,
+ sockRef, connRef)) < 0)
+ return
+ enif_raise_exception(env,
+ MKT2(env, esock_atom_select_write,
+ MKI(env, sres)));
+ /* Initiate connector */
+ descP->connector.pid = self;
+ ESOCK_ASSERT( MONP("essio_connect -> conn",
+ env, descP,
+ &self, &descP->connector.mon) == 0 );
+ descP->connector.env = esock_alloc_env("connector");
+ descP->connector.ref = CP_TERM(descP->connector.env, connRef);
+ descP->connectorP = &descP->connector;
+ descP->writeState |=
+ (ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED);
+
+ return esock_atom_select;
+ }
+ break;
+
+ default:
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_connect {%d} -> error: %d\r\n",
+ descP->sock, save_errno) );
+
+ return esock_make_error_errno(env, save_errno);
+
+ } // switch(save_errno)
+}
+
+
+/* *** verify_is_connected ***
+ * Check if a connection has been established.
+ */
+static
+BOOLEAN_T verify_is_connected(ESockDescriptor* descP, int* err)
+{
+ /*
+ * *** This is strange ***
+ *
+ * This *should* work on Windows NT too, but doesn't.
+ * An bug in Winsock 2.0 for Windows NT?
+ *
+ * See "Unix Netwok Programming", "The Sockets Networking API",
+ * W.R.Stevens, Volume 1, third edition, 16.4 Nonblocking 'connect',
+ * before Interrupted 'connect' (p 412) for a discussion about
+ * Unix portability and non blocking connect.
+ */
+
+ int error = 0;
+
+#ifdef SO_ERROR
+ if (! esock_getopt_int(descP->sock, SOL_SOCKET, SO_ERROR, &error)) {
+ // Solaris does it this way according to W.R.Stevens
+ error = sock_errno();
+ }
+#elif 1
+ char buf[0];
+ if (ESOCK_IS_ERROR(read(descP->sock, buf, sizeof(buf)))) {
+ error = sock_errno();
+ }
+#else
+ /* This variant probably returns wrong error value
+ * ENOTCONN instead of the actual connect error
+ */
+ ESockAddress remote;
+ SOCKLEN_T addrLen = sizeof(remote);
+ sys_memzero((char *) &remote, addrLen);
+ if (sock_peer(descP->sock,
+ (struct sockaddr*) &remote, &addrLen)) < 0) {
+ error = sock_errno();
+ }
+#endif
+
+ if (error != 0) {
+ *err = error;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+
+/* *** essio_listen *** */
+
+
+/* ========================================================================
+ */
+extern
+ERL_NIF_TERM essio_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef)
+{
+ ErlNifPid caller;
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (! IS_OPEN(descP->readState))
+ return esock_make_error_closed(env);
+
+ /* Accept and Read uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->currentReaderP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ if (descP->currentAcceptorP == NULL) {
+ SOCKET accSock;
+
+ /* We have no active acceptor (and therefore no acceptors in queue)
+ */
+
+ SSDBG( descP, ("UNIX-ESSIO", "essio_accept {%d} -> try accept\r\n",
+ descP->sock) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_tries, &descP->accTries, 1);
+
+ accSock = sock_accept(descP->sock, NULL, NULL);
+
+ if (ESOCK_IS_ERROR(accSock)) {
+ int save_errno;
+
+ save_errno = sock_errno();
+
+ return essio_accept_listening_error(env, descP, sockRef,
+ accRef, caller, save_errno);
+ } else {
+ /* We got an incoming connection */
+ return essio_accept_listening_accept(env, descP, sockRef,
+ accSock, caller);
+ }
+ } else {
+
+ /* We have an active acceptor and possibly acceptors waiting in queue.
+ * If the pid of the calling process is not the pid of the
+ * "current process", push the requester onto the (acceptor) queue.
+ */
+
+ SSDBG( descP, ("UNIX-ESSIO", "essio_accept_accepting -> check: "
+ "is caller current acceptor:"
+ "\r\n Caller: %T"
+ "\r\n Current: %T"
+ "\r\n Current Mon: %T"
+ "\r\n",
+ caller,
+ descP->currentAcceptor.pid,
+ ESOCK_MON2TERM(env, &descP->currentAcceptor.mon)) );
+
+ if (COMPARE_PIDS(&descP->currentAcceptor.pid, &caller) == 0) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_accepting {%d} -> current acceptor"
+ "\r\n", descP->sock) );
+
+ return essio_accept_accepting_current(env, descP, sockRef, accRef);
+
+ } else {
+
+ /* Not the "current acceptor", so (maybe) push onto queue */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_accepting {%d} -> *not* current acceptor\r\n",
+ descP->sock) );
+
+ return essio_accept_accepting_other(env, descP, accRef, caller);
+ }
+ }
+}
+
+
+/* *** essio_accept_listening_error ***
+ *
+ * The accept call resultet in an error - handle it.
+ * There are only two cases:
+ * 1) BLOCK => Attempt a "retry"
+ * 2) Other => Return the value (converted to an atom)
+ */
+static
+ERL_NIF_TERM essio_accept_listening_error(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef,
+ ErlNifPid caller,
+ int save_errno)
+{
+ ERL_NIF_TERM res;
+
+ if (save_errno == ERRNO_BLOCK ||
+ save_errno == EAGAIN) {
+
+ /* *** Try again later *** */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_listening_error {%d} -> would block - retry\r\n",
+ descP->sock) );
+
+ descP->currentAcceptor.pid = caller;
+ ESOCK_ASSERT( MONP("essio_accept_listening -> current acceptor",
+ env, descP,
+ &descP->currentAcceptor.pid,
+ &descP->currentAcceptor.mon) == 0 );
+ ESOCK_ASSERT( descP->currentAcceptor.env == NULL );
+ descP->currentAcceptor.env = esock_alloc_env("current acceptor");
+ descP->currentAcceptor.ref =
+ CP_TERM(descP->currentAcceptor.env, accRef);
+ descP->currentAcceptorP = &descP->currentAcceptor;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_listening_error {%d} -> retry for: "
+ "\r\n Current Pid: %T"
+ "\r\n Current Mon: %T"
+ "\r\n",
+ descP->sock,
+ descP->currentAcceptor.pid,
+ ESOCK_MON2TERM(env, &descP->currentAcceptor.mon)) );
+
+ res = essio_accept_busy_retry(env, descP, sockRef, accRef, NULL);
+
+ } else {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_listening {%d} -> errno: %d\r\n",
+ descP->sock, save_errno) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_fails, &descP->accFails, 1);
+
+ res = esock_make_error_errno(env, save_errno);
+ }
+
+ return res;
+}
+
+
+/* *** essio_accept_listening_accept ***
+ *
+ * The accept call was successful (accepted) - handle the new connection.
+ */
+static
+ERL_NIF_TERM essio_accept_listening_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ SOCKET accSock,
+ ErlNifPid caller)
+{
+ ERL_NIF_TERM res;
+
+ essio_accept_accepted(env, descP, sockRef, accSock, caller, &res);
+
+ return res;
+}
+
+
+/* *** essio_accept_accepting_current ***
+ * Handles when the current acceptor makes another attempt.
+ */
+static
+ERL_NIF_TERM essio_accept_accepting_current(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef)
+{
+ SOCKET accSock;
+ int save_errno;
+ ERL_NIF_TERM res;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_accepting_current {%d} -> try accept\r\n",
+ descP->sock) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_tries, &descP->accTries, 1);
+
+ accSock = sock_accept(descP->sock, NULL, NULL);
+
+ if (ESOCK_IS_ERROR(accSock)) {
+
+ save_errno = sock_errno();
+
+ res = essio_accept_accepting_current_error(env, descP, sockRef,
+ accRef, save_errno);
+ } else {
+
+ res = essio_accept_accepting_current_accept(env, descP, sockRef,
+ accSock);
+ }
+
+ return res;
+}
+
+
+/* *** essio_accept_accepting_current_accept ***
+ *
+ * Handles when the current acceptor succeeded in its accept call -
+ * handle the new connection.
+ */
+static
+ERL_NIF_TERM essio_accept_accepting_current_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ SOCKET accSock)
+{
+ ERL_NIF_TERM res;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_accepting_current_accept {%d}"
+ "\r\n", descP->sock) );
+
+ if (essio_accept_accepted(env, descP, sockRef, accSock,
+ descP->currentAcceptor.pid, &res)) {
+
+ ESOCK_ASSERT( DEMONP("essio_accept_accepting_current_accept -> "
+ "current acceptor",
+ env, descP, &descP->currentAcceptor.mon) == 0);
+
+ MON_INIT(&descP->currentAcceptor.mon);
+
+ if (!esock_activate_next_acceptor(env, descP, sockRef)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_accepting_current_accept {%d} ->"
+ " no more acceptors"
+ "\r\n", descP->sock) );
+
+ descP->readState &= ~ESOCK_STATE_ACCEPTING;
+
+ descP->currentAcceptorP = NULL;
+ }
+
+ }
+
+ return res;
+}
+
+
+/* *** essio_accept_accepting_current_error ***
+ * The accept call of current acceptor resultet in an error - handle it.
+ * There are only two cases:
+ * 1) BLOCK => Attempt a "retry"
+ * 2) Other => Return the value (converted to an atom)
+ */
+static
+ERL_NIF_TERM essio_accept_accepting_current_error(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef,
+ int save_errno)
+{
+ ERL_NIF_TERM res, reason;
+
+ if (save_errno == ERRNO_BLOCK ||
+ save_errno == EAGAIN) {
+
+ /*
+ * Just try again, no real error, just a ghost trigger from poll,
+ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_accepting_current_error {%d} -> "
+ "would block: try again\r\n", descP->sock) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_waits, &descP->accWaits, 1);
+
+ res = essio_accept_busy_retry(env, descP, sockRef, opRef,
+ &descP->currentAcceptor.pid);
+
+ } else {
+ ESockRequestor req;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_accepting_current_error {%d} -> "
+ "error: %d\r\n", descP->sock, save_errno) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_fails, &descP->accFails, 1);
+
+ esock_requestor_release("essio_accept_accepting_current_error",
+ env, descP, &descP->currentAcceptor);
+
+ reason = MKA(env, erl_errno_id(save_errno));
+ res = esock_make_error(env, reason);
+
+ req.env = NULL;
+ while (esock_acceptor_pop(env, descP, &req)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_accepting_current_error {%d} -> abort %T\r\n",
+ descP->sock, req.pid) );
+
+ esock_send_abort_msg(env, descP, sockRef, &req, reason);
+
+ (void) DEMONP("essio_accept_accepting_current_error -> "
+ "pop'ed writer",
+ env, descP, &req.mon);
+ }
+ descP->currentAcceptorP = NULL;
+ }
+
+ return res;
+}
+
+
+/* *** essio_accept_accepting_other ***
+ * Handles when the another acceptor makes an attempt, which
+ * results (maybe) in the request being pushed onto the
+ * acceptor queue.
+ */
+static
+ERL_NIF_TERM essio_accept_accepting_other(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ErlNifPid caller)
+{
+ if (! esock_acceptor_search4pid(env, descP, &caller)) {
+ esock_acceptor_push(env, descP, caller, ref, NULL);
+ return esock_atom_select;
+ } else {
+ /* Acceptor already in queue */
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+}
+
+
+/* *** essio_accept_busy_retry ***
+ *
+ * Perform a retry select. If successful, set nextState.
+ */
+static
+ERL_NIF_TERM essio_accept_busy_retry(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef,
+ ErlNifPid* pidP)
+{
+ int sres;
+ ERL_NIF_TERM res;
+
+ if ((sres = esock_select_read(env, descP->sock, descP, pidP,
+ sockRef, accRef)) < 0) {
+
+ ESOCK_ASSERT( DEMONP("essio_accept_busy_retry - select failed",
+ env, descP, &descP->currentAcceptor.mon) == 0);
+
+ MON_INIT(&descP->currentAcceptor.mon);
+
+ /* It is very unlikely that a next acceptor will be able
+ * to do anything successful, but we will clean the queue
+ */
+
+ if (!esock_activate_next_acceptor(env, descP, sockRef)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_accept_busy_retry {%d} -> no more acceptors\r\n",
+ descP->sock) );
+
+ descP->readState &= ~ESOCK_STATE_ACCEPTING;
+
+ descP->currentAcceptorP = NULL;
+ }
+
+ res =
+ enif_raise_exception(env,
+ MKT2(env, esock_atom_select_read,
+ MKI(env, sres)));
+ } else {
+ descP->readState |=
+ (ESOCK_STATE_ACCEPTING | ESOCK_STATE_SELECTED);
+ res = esock_atom_select;
+ }
+
+ return res;
+}
+
+
+/* *** essio_accept_accepted ***
+ *
+ * Generic function handling a successful accept.
+ */
+static
+BOOLEAN_T essio_accept_accepted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ SOCKET accSock,
+ ErlNifPid pid,
+ ERL_NIF_TERM* result)
+{
+ ESockDescriptor* accDescP;
+ ERL_NIF_TERM accRef;
+
+ /*
+ * We got one
+ */
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_success, &descP->accSuccess, 1);
+
+ accDescP = esock_alloc_descriptor(accSock);
+ accDescP->domain = descP->domain;
+ accDescP->type = descP->type;
+ accDescP->protocol = descP->protocol;
+
+ MLOCK(descP->writeMtx);
+
+ accDescP->rBufSz = descP->rBufSz; // Inherit buffer size
+ accDescP->rNum = descP->rNum; // Inherit buffer uses
+ accDescP->rNumCnt = 0;
+ accDescP->rCtrlSz = descP->rCtrlSz; // Inherit buffer size
+ accDescP->wCtrlSz = descP->wCtrlSz; // Inherit buffer size
+ accDescP->iow = descP->iow; // Inherit iow
+ accDescP->dbg = descP->dbg; // Inherit debug flag
+ accDescP->useReg = descP->useReg; // Inherit useReg flag
+ esock_inc_socket(accDescP->domain, accDescP->type, accDescP->protocol);
+
+ accRef = enif_make_resource(env, accDescP);
+ enif_release_resource(accDescP);
+
+ accDescP->ctrlPid = pid;
+ /* pid has actually been compared equal to self()
+ * in this code path just a little while ago
+ */
+ ESOCK_ASSERT( MONP("essio_accept_accepted -> ctrl",
+ env, accDescP,
+ &accDescP->ctrlPid,
+ &accDescP->ctrlMon) == 0 );
+
+ SET_NONBLOCKING(accDescP->sock);
+
+ accDescP->writeState |= ESOCK_STATE_CONNECTED;
+
+ MUNLOCK(descP->writeMtx);
+
+ /* And finally (maybe) update the registry */
+ if (descP->useReg) esock_send_reg_add_msg(env, descP, accRef);
+
+ *result = esock_make_ok2(env, accRef);
+
+ return TRUE;
+}
+
+
+
+/* ========================================================================
+ * Do the actual send.
+ * Do some initial writer checks, do the actual send and then
+ * analyze the result. If we are done, another writer may be
+ * scheduled (if there is one in the writer queue).
+ */
+extern
+ERL_NIF_TERM essio_send(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* sndDataP,
+ int flags)
+{
+ ssize_t send_result;
+ ERL_NIF_TERM writerCheck;
+
+ if (! IS_OPEN(descP->writeState))
+ return esock_make_error_closed(env);
+
+ /* Connect and Write uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->connectorP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ send_result = (ssize_t) sndDataP->size;
+ if ((size_t) send_result != sndDataP->size)
+ return esock_make_error_invalid(env, esock_atom_data_size);
+
+ /* Ensure that we either have no current writer or we are it,
+ * or enqueue this process if there is a current writer */
+ if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
+ SSDBG( descP, ("UNIX-ESSIO", "esock_send {%d} -> writer check failed: "
+ "\r\n %T\r\n", descP->sock, writerCheck) );
+ return writerCheck;
+ }
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_tries, &descP->writeTries, 1);
+
+ send_result = sock_send(descP->sock, sndDataP->data, sndDataP->size, flags);
+
+ return send_check_result(env, descP,
+ send_result, sndDataP->size, FALSE,
+ sockRef, sendRef);
+
+}
+
+
+/* ========================================================================
+ */
+extern
+ERL_NIF_TERM essio_sendto(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* dataP,
+ int flags,
+ ESockAddress* toAddrP,
+ SOCKLEN_T toAddrLen)
+{
+ ssize_t result;
+ ERL_NIF_TERM writerCheck;
+
+ if (! IS_OPEN(descP->writeState))
+ return esock_make_error_closed(env);
+
+ /* Connect and Write uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->connectorP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ result = (ssize_t) dataP->size;
+ if ((size_t) result != dataP->size)
+ return esock_make_error_invalid(env, esock_atom_data_size);
+
+ /* Ensure that we either have no current writer or we are it,
+ * or enqueue this process if there is a current writer */
+ if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendto {%d} -> writer check failed: "
+ "\r\n %T\r\n", descP->sock, writerCheck) );
+ return writerCheck;
+ }
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_tries, &descP->writeTries, 1);
+
+ if (toAddrP != NULL) {
+ result = sock_sendto(descP->sock,
+ dataP->data, dataP->size, flags,
+ &toAddrP->sa, toAddrLen);
+ } else {
+ result = sock_sendto(descP->sock,
+ dataP->data, dataP->size, flags,
+ NULL, 0);
+ }
+
+ return send_check_result(env, descP, result, dataP->size, FALSE,
+ sockRef, sendRef);
+}
+
+
+/* ========================================================================
+ */
+extern
+ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ERL_NIF_TERM eMsg,
+ int flags,
+ ERL_NIF_TERM eIOV,
+ const ESockData* dataP)
+{
+ ERL_NIF_TERM res, eAddr, eCtrl;
+ ESockAddress addr;
+ struct msghdr msgHdr;
+ ErlNifIOVec *iovec = NULL;
+ char* ctrlBuf;
+ size_t ctrlBufLen, ctrlBufUsed;
+ ssize_t dataSize, sendmsg_result;
+ ERL_NIF_TERM writerCheck, tail;
+
+ if (! IS_OPEN(descP->writeState))
+ return esock_make_error_closed(env);
+
+ /* Connect and Write uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->connectorP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Ensure that we either have no current writer or we are it,
+ * or enqueue this process if there is a current writer */
+ if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_sendmsg {%d} -> writer check failed: "
+ "\r\n %T\r\n", descP->sock, writerCheck) );
+ return writerCheck;
+ }
+
+ /* Initiate the .name and .namelen fields depending on if
+ * we have an address or not
+ */
+ if (! GET_MAP_VAL(env, eMsg, esock_atom_addr, &eAddr)) {
+
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendmsg {%d} -> no address\r\n", descP->sock) );
+
+ msgHdr.msg_name = NULL;
+ msgHdr.msg_namelen = 0;
+ } else {
+ msgHdr.msg_name = (void*) &addr;
+ msgHdr.msg_namelen = sizeof(addr);
+ sys_memzero((char *) msgHdr.msg_name, msgHdr.msg_namelen);
+
+ SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg {%d} ->"
+ "\r\n address: %T"
+ "\r\n", descP->sock, eAddr) );
+
+ if (! esock_decode_sockaddr(env, eAddr,
+ msgHdr.msg_name,
+ &msgHdr.msg_namelen)) {
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendmsg {%d} -> invalid address\r\n",
+ descP->sock) );
+ return esock_make_invalid(env, esock_atom_addr);
+ }
+ }
+
+ /* Extract the *mandatory* 'iov', which must be an erlang:iovec(),
+ * from which we take at most IOV_MAX binaries
+ */
+ if ((! enif_inspect_iovec(NULL, dataP->iov_max, eIOV, &tail, &iovec))) {
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendmsg {%d} -> not an iov\r\n",
+ descP->sock) );
+
+ return esock_make_invalid(env, esock_atom_iov);
+ }
+
+ SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg {%d} ->"
+ "\r\n iovcnt: %lu"
+ "\r\n tail: %s"
+ "\r\n", descP->sock,
+ (unsigned long) iovec->iovcnt,
+ B2S(! enif_is_empty_list(env, tail))) );
+
+ /* We now have an allocated iovec */
+
+ eCtrl = esock_atom_undefined;
+ ctrlBufLen = 0;
+ ctrlBuf = NULL;
+
+ if (iovec->iovcnt > dataP->iov_max) {
+ if (descP->type == SOCK_STREAM) {
+ iovec->iovcnt = dataP->iov_max;
+ } else {
+ /* We can not send the whole packet in one sendmsg() call */
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendmsg {%d} -> iovcnt > iov_max\r\n",
+ descP->sock) );
+ res = esock_make_invalid(env, esock_atom_iov);
+ goto done_free_iovec;
+ }
+ }
+
+ dataSize = 0;
+ {
+ ERL_NIF_TERM h, t;
+ ErlNifBinary bin;
+ size_t i;
+
+ /* Find out if there is remaining data in the tail.
+ * Skip empty binaries otherwise break.
+ * If 'tail' after loop exit is the empty list
+ * there was no more data. Otherwise there is more
+ * data or the 'iov' is invalid.
+ */
+ for (;;) {
+ if (enif_get_list_cell(env, tail, &h, &t) &&
+ enif_inspect_binary(env, h, &bin) &&
+ (bin.size == 0)) {
+ tail = t;
+ continue;
+ } else
+ break;
+ }
+
+ if ((! enif_is_empty_list(env, tail)) &&
+ (descP->type != SOCK_STREAM)) {
+ /* We can not send the whole packet in one sendmsg() call */
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendmsg {%d} -> invalid tail\r\n",
+ descP->sock) );
+ res = esock_make_invalid(env, esock_atom_iov);
+ goto done_free_iovec;
+ }
+
+ /* Calculate the data size */
+
+ for (i = 0; i < iovec->iovcnt; i++) {
+ size_t len = iovec->iov[i].iov_len;
+ dataSize += len;
+ if (dataSize < len) {
+ /* Overflow */
+ SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg {%d} -> Overflow"
+ "\r\n i: %lu"
+ "\r\n len: %lu"
+ "\r\n dataSize: %ld"
+ "\r\n", descP->sock, (unsigned long) i,
+ (unsigned long) len, (long) dataSize) );
+ res = esock_make_invalid(env, esock_atom_iov);
+ goto done_free_iovec;
+ }
+ }
+ }
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_sendmsg {%d} -> iovec size verified"
+ "\r\n iov length: %lu"
+ "\r\n data size: %u"
+ "\r\n",
+ descP->sock,
+ (unsigned long) iovec->iovcnt, (long) dataSize) );
+
+ msgHdr.msg_iovlen = iovec->iovcnt;
+ msgHdr.msg_iov = iovec->iov;
+
+ /* Extract the *optional* 'ctrl' */
+ if (GET_MAP_VAL(env, eMsg, esock_atom_ctrl, &eCtrl)) {
+ ctrlBufLen = descP->wCtrlSz;
+ ctrlBuf = (char*) MALLOC(ctrlBufLen);
+ ESOCK_ASSERT( ctrlBuf != NULL );
+ }
+ SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg {%d} -> optional ctrl: "
+ "\r\n ctrlBuf: %p"
+ "\r\n ctrlBufLen: %lu"
+ "\r\n eCtrl: %T"
+ "\r\n", descP->sock,
+ ctrlBuf, (unsigned long) ctrlBufLen, eCtrl) );
+
+ /* Decode the ctrl and initiate that part of the msghdr.
+ */
+ if (ctrlBuf != NULL) {
+ if (! decode_cmsghdrs(env, descP,
+ eCtrl,
+ ctrlBuf, ctrlBufLen, &ctrlBufUsed)) {
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendmsg {%d} -> invalid ctrl\r\n",
+ descP->sock) );
+ res = esock_make_invalid(env, esock_atom_ctrl);
+ goto done_free_iovec;
+ }
+ } else {
+ ctrlBufUsed = 0;
+ }
+ msgHdr.msg_control = ctrlBuf;
+ msgHdr.msg_controllen = ctrlBufUsed;
+
+ /* The msg_flags field is not used when sending,
+ * but zero it just in case */
+ msgHdr.msg_flags = 0;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_tries, &descP->writeTries, 1);
+
+ /* And now, try to send the message */
+ sendmsg_result = sock_sendmsg(descP->sock, &msgHdr, flags);
+
+ res = send_check_result(env, descP, sendmsg_result, dataSize,
+ (! enif_is_empty_list(env, tail)),
+ sockRef, sendRef);
+
+ done_free_iovec:
+ FREE_IOVEC( iovec );
+ if (ctrlBuf != NULL) FREE(ctrlBuf);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_sendmsg {%d} -> done"
+ "\r\n %T"
+ "\r\n", descP->sock, res) );
+
+ return res;
+
+}
+
+
+/* ========================================================================
+ * Start a sendfile() operation
+ */
+extern
+ERL_NIF_TERM essio_sendfile_start(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ off_t offset,
+ size_t count,
+ ERL_NIF_TERM fRef)
+{
+#if defined(HAVE_SENDFILE)
+ ERL_NIF_TERM writerCheck;
+ ssize_t res;
+ int err;
+
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendfile_start {%d} -> entry with"
+ "\r\n sockRef: %T"
+ "\r\n sendRef: %T"
+ "\r\n fRef: %T"
+ "\r\n offset: %lu"
+ "\r\n count: %lu"
+ "\r\n",
+ descP->sock, sockRef, sendRef,
+ fRef, (unsigned long) offset, (unsigned long) count) );
+
+ if (! IS_OPEN(descP->writeState)) {
+ return esock_make_error_closed(env);
+ }
+
+ /* Connect and Write uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->connectorP != NULL) {
+ return esock_make_error_invalid(env, esock_atom_state);
+ }
+
+ /* Ensure that we either have no current writer or we are it,
+ * or enqueue this process if there is a current writer
+ */
+ if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendfile_start {%d} -> writer check failed: "
+ "\r\n %T\r\n", descP->sock, writerCheck) );
+
+ /* Returns 'select' if current process got enqueued,
+ * or exception invalid state if current process already
+ * was enqueued
+ */
+ return writerCheck;
+ }
+
+ if (descP->sendfileHandle != INVALID_HANDLE)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Get a dup:ed file handle from prim_file_nif
+ * through a NIF dyncall
+ */
+ {
+ struct prim_file_nif_dyncall_dup dc_dup;
+
+ dc_dup.op = prim_file_nif_dyncall_dup;
+ dc_dup.result = EINVAL; // should not be needed
+
+ /* Request the handle */
+ if (enif_dynamic_resource_call(env,
+ esock_atom_prim_file,
+ esock_atom_efile,
+ fRef,
+ &dc_dup)
+ != 0) {
+ return
+ essio_sendfile_error(env, descP, sockRef,
+ MKT2(env,
+ esock_atom_invalid,
+ esock_atom_efile));
+ }
+ if (dc_dup.result != 0) {
+ return
+ essio_sendfile_errno(env, descP, sockRef, dc_dup.result);
+ }
+ descP->sendfileHandle = dc_dup.handle;
+ }
+
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendfile_start(%T) {%d} -> sendRef: %T"
+ "\r\n sendfileHandle: %d"
+ "\r\n",
+ sockRef, descP->sock, sendRef,
+ descP->sendfileHandle) );
+
+ if (descP->sendfileCountersP == NULL) {
+ descP->sendfileCountersP = MALLOC(sizeof(ESockSendfileCounters));
+ *descP->sendfileCountersP = initESockSendfileCounters;
+ }
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_sendfile_tries,
+ &descP->sendfileCountersP->tries, 1);
+ descP->sendfileCountersP->maxCnt = 0;
+
+ res = essio_sendfile(env, descP, sockRef, offset, &count, &err);
+
+ if (res < 0) { // Terminal error
+
+ (void) close(descP->sendfileHandle);
+ descP->sendfileHandle = INVALID_HANDLE;
+
+ return essio_sendfile_errno(env, descP, sockRef, err);
+
+ } else if (res > 0) { // Retry by select
+
+ if (descP->currentWriterP == NULL) {
+ int mon_res;
+
+ /* Register writer as current */
+ ESOCK_ASSERT( enif_self(env, &descP->currentWriter.pid) != NULL );
+ mon_res =
+ MONP("sendfile-start -> current writer",
+ env, descP,
+ &descP->currentWriter.pid,
+ &descP->currentWriter.mon);
+ ESOCK_ASSERT( mon_res >= 0 );
+
+ if (mon_res > 0) {
+ /* Caller died already, can happen for dirty NIFs */
+
+ (void) close(descP->sendfileHandle);
+ descP->sendfileHandle = INVALID_HANDLE;
+
+ return essio_sendfile_error(env, descP, sockRef,
+ MKT2(env,
+ esock_atom_invalid,
+ esock_atom_not_owner));
+ }
+ ESOCK_ASSERT( descP->currentWriter.env == NULL );
+ descP->currentWriter.env = esock_alloc_env("current-writer");
+ descP->currentWriter.ref =
+ CP_TERM(descP->currentWriter.env, sendRef);
+ descP->currentWriterP = &descP->currentWriter;
+ }
+ // else current writer is already registered by esock_requestor_pop()
+
+ return essio_sendfile_select(env, descP, sockRef, sendRef, count);
+
+ } else { // res == 0: Done
+ return essio_sendfile_ok(env, descP, sockRef, count);
+ }
+#else
+ VOID(env);
+ VOID(descP);
+ VOID(sockRef);
+ VOID(sendRef);
+ VOID(offset);
+ VOID(count);
+ VOID(fRef);
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#endif
+}
+
+
+/* ========================================================================
+ * Continue an ongoing sendfile operation
+ */
+
+extern
+ERL_NIF_TERM essio_sendfile_cont(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ off_t offset,
+ size_t count)
+{
+#if defined(HAVE_SENDFILE)
+ ErlNifPid caller;
+ ssize_t res;
+ int err;
+
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendfile_cont {%d} -> entry"
+ "\r\n sockRef: %T"
+ "\r\n sendRef: %T"
+ "\r\n", descP->sock, sockRef, sendRef) );
+
+ if (! IS_OPEN(descP->writeState))
+ return esock_make_error_closed(env);
+
+ /* Connect and Write uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->connectorP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Verify that this process has a sendfile operation in progress */
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+ if ((descP->currentWriterP == NULL) ||
+ (descP->sendfileHandle == INVALID_HANDLE) ||
+ (COMPARE_PIDS(&descP->currentWriter.pid, &caller) != 0)) {
+ //
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ res = essio_sendfile(env, descP, sockRef, offset, &count, &err);
+
+ if (res < 0) { // Terminal error
+
+ (void) close(descP->sendfileHandle);
+ descP->sendfileHandle = INVALID_HANDLE;
+
+ return essio_sendfile_errno(env, descP, sockRef, err);
+
+ } else if (res > 0) { // Retry by select
+
+ /* Overwrite current writer registration */
+ enif_clear_env(descP->currentWriter.env);
+ descP->currentWriter.ref =
+ CP_TERM(descP->currentWriter.env, sendRef);
+
+ return essio_sendfile_select(env, descP, sockRef, sendRef, count);
+
+ } else { // res == 0: Done
+ return essio_sendfile_ok(env, descP, sockRef, count);
+ }
+#else
+ VOID(env);
+ VOID(descP);
+ VOID(sockRef);
+ VOID(sendRef);
+ VOID(offset);
+ VOID(count);
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#endif
+}
+
+
+/* ========================================================================
+ * Deferred close of the dup:ed file descriptor
+ */
+
+extern
+ERL_NIF_TERM essio_sendfile_deferred_close(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+#if defined(HAVE_SENDFILE)
+ if (descP->sendfileHandle == INVALID_HANDLE)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ (void) close(descP->sendfileHandle);
+ descP->sendfileHandle = INVALID_HANDLE;
+
+ return esock_atom_ok;
+#else
+ VOID(env);
+ VOID(descP);
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#endif
+}
+
+
+
+/* ========================================================================
+ * The (read) buffer handling should be optimized!
+ * But for now we make it easy for ourselves by
+ * allocating a binary (of the specified or default
+ * size) and then throwing it away...
+ */
+extern
+ERL_NIF_TERM essio_recv(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t len,
+ int flags)
+{
+ ssize_t read;
+ ErlNifBinary buf;
+ ERL_NIF_TERM readerCheck;
+ int save_errno;
+ size_t bufSz = (len != 0 ? len : descP->rBufSz);
+
+ SSDBG( descP, ("UNIX-ESSIO", "essio_recv {%d} -> entry with"
+ "\r\n count,size: (%ld:%u:%lu)"
+ "\r\n", descP->sock,
+ (long) len, descP->rNumCnt, (unsigned long) bufSz) );
+
+ if (! IS_OPEN(descP->readState))
+ return esock_make_error_closed(env);
+
+ /* Accept and Read uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->currentAcceptorP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Ensure that we either have no current reader or that we are it,
+ * or enqueue this process if there is a current reader */
+ if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_recv {%d} -> reader check failed: "
+ "\r\n %T"
+ "\r\n", descP->sock, readerCheck) );
+ return readerCheck;
+ }
+
+ /* Allocate a buffer:
+ * Either as much as we want to read or (if zero (0)) use the "default"
+ * size (what has been configured).
+ */
+ ESOCK_ASSERT( ALLOC_BIN(bufSz, &buf) );
+
+ // If it fails (read = -1), we need errno...
+ SSDBG( descP, ("UNIX-ESSIO", "essio_recv {%d} -> try read (%lu)\r\n",
+ descP->sock, (unsigned long) buf.size) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_tries, &descP->readTries, 1);
+
+ read = sock_recv(descP->sock, buf.data, buf.size, flags);
+ if (ESOCK_IS_ERROR(read)) {
+ save_errno = sock_errno();
+ } else {
+ save_errno = 0; // The value does not actually matter in this case
+ }
+
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_recv {%d} -> read: %ld (%d)\r\n",
+ descP->sock, (long) read, save_errno) );
+
+ return recv_check_result(env, descP, read, len, save_errno,
+ &buf, sockRef, recvRef);
+}
+
+
+/* *** recv_check_result ***
+ *
+ * Process the result of a call to recv.
+ */
+static
+ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ssize_t toRead,
+ int saveErrno,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM res;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "recv_check_result(%T) {%d} -> entry with"
+ "\r\n read: %ld"
+ "\r\n toRead: %ld"
+ "\r\n saveErrno: %d"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock,
+ (long) read, (long) toRead, saveErrno, recvRef) );
+
+
+ /* <KOLLA>
+ *
+ * We need to handle read = 0 for other type(s) (DGRAM) when
+ * its actually valid to read 0 bytes.
+ *
+ * </KOLLA>
+ */
+
+ if ((read == 0) && (descP->type == SOCK_STREAM)) {
+ ERL_NIF_TERM reason = esock_atom_closed;
+ res = esock_make_error(env, reason);
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_fails, &descP->readFails, 1);
+
+ /*
+ * When a stream socket peer has performed an orderly shutdown,
+ * the return value will be 0 (the traditional "end-of-file" return).
+ *
+ * *We* do never actually try to read 0 bytes!
+ *
+ * We must also notify any waiting readers!
+ */
+
+ recv_error_current_reader(env, descP, sockRef, reason);
+
+ FREE_BIN(bufP);
+
+ } else {
+
+ /* There is a special case: If the provided 'to read' value is
+ * zero (0) (only for type =/= stream).
+ * That means that we read as much as we can, using the default
+ * read buffer size.
+ */
+
+ if (bufP->size == read) {
+
+ /* +++ We filled the buffer +++ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_result(%T) {%d} -> [%lu] filled the buffer\r\n",
+ sockRef, descP->sock, (unsigned long) bufP->size) );
+
+ res = recv_check_full(env, descP, read, toRead, bufP,
+ sockRef, recvRef);
+
+ } else if (read < 0) {
+
+ /* +++ Error handling +++ */
+
+ res = recv_check_fail(env, descP, saveErrno, bufP, NULL,
+ sockRef, recvRef);
+
+ } else {
+
+ /* +++ We did not fill the buffer +++ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_result(%T) {%d} -> [%lu] "
+ "did not fill the buffer (%ld)\r\n",
+ sockRef, descP->sock, (unsigned long) bufP->size,
+ (long) read) );
+
+ res = recv_check_partial(env, descP, read, toRead, bufP,
+ sockRef, recvRef);
+ }
+ }
+
+ return res;
+}
+
+
+
+/* ========================================================================
+ * The (read) buffer handling *must* be optimized!
+ * But for now we make it easy for ourselves by
+ * allocating a binary (of the specified or default
+ * size) and then throwing it away...
+ */
+extern
+ERL_NIF_TERM essio_recvfrom(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t len,
+ int flags)
+{
+ ESockAddress fromAddr;
+ SOCKLEN_T addrLen;
+ ssize_t read;
+ int save_errno;
+ ErlNifBinary buf;
+ ERL_NIF_TERM readerCheck;
+ size_t bufSz = (len != 0 ? len : descP->rBufSz);
+
+ SSDBG( descP, ("UNIX-ESSIO", "essio_recvfrom {%d} -> entry with"
+ "\r\n bufSz: %d"
+ "\r\n", descP->sock, bufSz) );
+
+ if (! IS_OPEN(descP->readState))
+ return esock_make_error_closed(env);
+
+ /* Accept and Read uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->currentAcceptorP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Ensure that we either have no current reader or that we are it,
+ * or enqueue this process if there is a current reader */
+ if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_recv {%d} -> reader check failed: "
+ "\r\n %T\r\n", descP->sock, readerCheck) );
+ return readerCheck;
+ }
+
+ /* Allocate a buffer:
+ * Either as much as we want to read or (if zero (0)) use the "default"
+ * size (what has been configured).
+ */
+ ESOCK_ASSERT( ALLOC_BIN(bufSz, &buf) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_tries, &descP->readTries, 1);
+
+ addrLen = sizeof(fromAddr);
+ sys_memzero((char*) &fromAddr, addrLen);
+
+ read = sock_recvfrom(descP->sock, buf.data, buf.size, flags,
+ &fromAddr.sa, &addrLen);
+ if (ESOCK_IS_ERROR(read))
+ save_errno = sock_errno();
+ else
+ save_errno = 0; // The value does not actually matter in this case
+
+ return recvfrom_check_result(env, descP, read, save_errno,
+ &buf, &fromAddr, addrLen,
+ sockRef, recvRef);
+}
+
+
+/* The recvfrom function delivers one (1) message. If our buffer
+ * is too small, the message will be truncated. So, regardless
+ * if we filled the buffer or not, we have got what we are going
+ * to get regarding this message.
+ */
+
+static
+ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ int saveErrno,
+ ErlNifBinary* bufP,
+ ESockAddress* fromAddrP,
+ SOCKLEN_T fromAddrLen,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM data, res;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "recvfrom_check_result(%T) {%d} -> entry with"
+ "\r\n read: %ld"
+ "\r\n saveErrno: %d"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock,
+ (long) read, saveErrno, recvRef) );
+
+ /* <KOLLA>
+ *
+ * We need to handle read = 0 for non_stream socket type(s) when
+ * its actually valid to read 0 bytes.
+ *
+ * </KOLLA>
+ */
+
+ if ((read == 0) && (descP->type == SOCK_STREAM)) {
+
+ /*
+ * When a stream socket peer has performed an orderly shutdown,
+ * the return value will be 0 (the traditional "end-of-file" return).
+ *
+ * *We* do never actually try to read 0 bytes!
+ */
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_fails, &descP->readFails, 1);
+
+ FREE_BIN(bufP);
+
+ return esock_make_error_closed(env);
+ }
+
+ if (read < 0) {
+
+ /* +++ Error handling +++ */
+
+ res = recv_check_fail(env, descP, saveErrno, bufP, NULL,
+ sockRef, recvRef);
+
+ } else {
+
+ /* +++ We successfully got a message - time to encode the address +++ */
+
+ ERL_NIF_TERM eSockAddr;
+
+ esock_encode_sockaddr(env,
+ fromAddrP, fromAddrLen,
+ &eSockAddr);
+
+ if (read == bufP->size) {
+
+ data = MKBIN(env, bufP);
+
+ } else {
+
+ /* +++ We got a chunk of data but +++
+ * +++ since we did not fill the +++
+ * +++ buffer, we must split it +++
+ * +++ into a sub-binary. +++
+ */
+
+ data = MKBIN(env, bufP);
+ data = MKSBIN(env, data, 0, read);
+ }
+
+ ESOCK_CNT_INC(env, descP, sockRef, esock_atom_read_pkg,
+ &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef, esock_atom_read_byte,
+ &descP->readByteCnt, read);
+
+ recv_update_current_reader(env, descP, sockRef);
+
+ res = esock_make_ok2(env, MKT2(env, eSockAddr, data));
+
+ }
+
+ return res;
+
+}
+
+
+
+/* ========================================================================
+ * The (read) buffer handling *must* be optimized!
+ * But for now we make it easy for ourselves by
+ * allocating a binary (of the specified or default
+ * size) and then throwing it away...
+ */
+extern
+ERL_NIF_TERM essio_recvmsg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t bufLen,
+ ssize_t ctrlLen,
+ int flags)
+{
+ SOCKLEN_T addrLen;
+ ssize_t read;
+ int save_errno;
+ size_t bufSz = (bufLen != 0 ? bufLen : descP->rBufSz);
+ size_t ctrlSz = (ctrlLen != 0 ? ctrlLen : descP->rCtrlSz);
+ struct msghdr msgHdr;
+ SysIOVec iov[1]; // Shall we always use 1?
+ ErlNifBinary data[1]; // Shall we always use 1?
+ ErlNifBinary ctrl;
+ ERL_NIF_TERM readerCheck;
+ ESockAddress addr;
+
+ SSDBG( descP, ("UNIX-ESSIO", "essio_recvmsg {%d} -> entry with"
+ "\r\n bufSz: %lu (%ld)"
+ "\r\n ctrlSz: %ld (%ld)"
+ "\r\n", descP->sock,
+ (unsigned long) bufSz, (long) bufLen,
+ (unsigned long) ctrlSz, (long) ctrlLen) );
+
+ if (! IS_OPEN(descP->readState))
+ return esock_make_error_closed(env);
+
+ /* Accept and Read uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->currentAcceptorP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Ensure that we either have no current reader or that we are it,
+ * or enqueue this process if there is a current reader */
+ if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_recvmsg {%d} -> reader check failed: "
+ "\r\n %T\r\n", descP->sock, readerCheck) );
+ return readerCheck;
+ }
+
+ /* Allocate the (msg) data buffer:
+ */
+ ESOCK_ASSERT( ALLOC_BIN(bufSz, &data[0]) );
+
+ /* Allocate the ctrl (buffer):
+ */
+ ESOCK_ASSERT( ALLOC_BIN(ctrlSz, &ctrl) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_tries, &descP->readTries, 1);
+
+ addrLen = sizeof(addr);
+ sys_memzero((char*) &addr, addrLen);
+ sys_memzero((char*) &msgHdr, sizeof(msgHdr));
+
+ iov[0].iov_base = data[0].data;
+ iov[0].iov_len = data[0].size;
+
+ msgHdr.msg_name = &addr;
+ msgHdr.msg_namelen = addrLen;
+ msgHdr.msg_iov = iov;
+ msgHdr.msg_iovlen = 1; // Should use a constant or calculate...
+ msgHdr.msg_control = ctrl.data;
+ msgHdr.msg_controllen = ctrl.size;
+
+ read = sock_recvmsg(descP->sock, &msgHdr, flags);
+ if (ESOCK_IS_ERROR(read))
+ save_errno = sock_errno();
+ else
+ save_errno = 0; // The value does not actually matter in this case
+
+ return recvmsg_check_result(env, descP, read, save_errno,
+ &msgHdr,
+ data, // Needed for iov encode
+ &ctrl, // Needed for ctrl header encode
+ sockRef, recvRef);
+}
+
+
+/* *** recvmsg_check_result ***
+ *
+ * The recvmsg function delivers one (1) message. If our buffer
+ * is to small, the message will be truncated. So, regardless
+ * if we filled the buffer or not, we have got what we are going
+ * to get regarding this message.
+ */
+static
+ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ int saveErrno,
+ struct msghdr* msgHdrP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM res;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "recvmsg_check_result(%T) {%d} -> entry with"
+ "\r\n read: %ld"
+ "\r\n saveErrno: %d"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock,
+ (long) read, saveErrno, recvRef) );
+
+
+ /* <KOLLA>
+ *
+ * We need to handle read = 0 for non_stream socket type(s) when
+ * its actually valid to read 0 bytes.
+ *
+ * </KOLLA>
+ */
+
+ if ((read == 0) && (descP->type == SOCK_STREAM)) {
+
+ /*
+ * When a stream socket peer has performed an orderly shutdown,
+ * the return value will be 0 (the traditional "end-of-file" return).
+ *
+ * *We* do never actually try to read 0 bytes!
+ */
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_fails, &descP->readFails, 1);
+
+ FREE_BIN(dataBufP); FREE_BIN(ctrlBufP);
+
+ return esock_make_error_closed(env);
+ }
+
+
+ if (read < 0) {
+
+ /* +++ Error handling +++ */
+
+ res = recv_check_fail(env, descP, saveErrno, dataBufP, ctrlBufP,
+ sockRef, recvRef);
+
+ } else {
+
+ /* +++ We successfully got a message - time to encode it +++ */
+
+ res = recvmsg_check_msg(env, descP, read, msgHdrP,
+ dataBufP, ctrlBufP, sockRef);
+
+ }
+
+ return res;
+
+}
+
+
+/* *** recvmsg_check_msg ***
+ *
+ * We successfully read one message. Time to process.
+ */
+static
+ERL_NIF_TERM recvmsg_check_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ struct msghdr* msgHdrP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM eMsg;
+
+ /*
+ * <KOLLA>
+ *
+ * The return value of recvmsg is the *total* number of bytes
+ * that where successfully read. This data has been put into
+ * the *IO vector*.
+ *
+ * </KOLLA>
+ */
+
+ encode_msg(env, descP,
+ read, msgHdrP, dataBufP, ctrlBufP,
+ &eMsg);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "recvmsg_check_result(%T) {%d} -> ok\r\n",
+ sockRef, descP->sock) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef, esock_atom_read_byte,
+ &descP->readByteCnt, read);
+
+ recv_update_current_reader(env, descP, sockRef);
+
+ return esock_make_ok2(env, eMsg);
+}
+
+
+
+/* ========================================================================
+ */
+extern
+ERL_NIF_TERM essio_close(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ if (! IS_OPEN(descP->readState)) {
+ /* A bit of cheeting; maybe not closed yet - do we need a queue? */
+ return esock_make_error_closed(env);
+ }
+
+ /* Store the PID of the caller,
+ * since we need to inform it when we
+ * (that is, the stop callback function)
+ * completes.
+ */
+ ESOCK_ASSERT( enif_self(env, &descP->closerPid) != NULL );
+
+ /* If the caller is not the owner; monitor the caller,
+ * since we should complete this operation even if the caller dies
+ * (for whatever reason).
+ */
+ if (COMPARE_PIDS(&descP->closerPid, &descP->ctrlPid) != 0) {
+
+ ESOCK_ASSERT( MONP("essio_close-check -> closer",
+ env, descP,
+ &descP->closerPid,
+ &descP->closerMon) == 0 );
+ }
+
+ /* Prepare for closing the socket */
+ descP->readState |= ESOCK_STATE_CLOSING;
+ descP->writeState |= ESOCK_STATE_CLOSING;
+ if (do_stop(env, descP)) {
+ // stop() has been scheduled - wait for it
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_close {%d} -> stop was scheduled\r\n",
+ descP->sock) );
+
+ // Create closeRef for the close msg that esock_stop() will send
+ descP->closeEnv = esock_alloc_env("esock_close_do - close-env");
+ descP->closeRef = MKREF(descP->closeEnv);
+
+ return esock_make_ok2(env, CP_TERM(env, descP->closeRef));
+ } else {
+ // The socket may be closed - tell caller to finalize
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_close {%d} -> stop was called\r\n",
+ descP->sock) );
+
+ return esock_atom_ok;
+ }
+}
+
+
+
+/* Prepare for close - return whether stop is scheduled or not
+ */
+static
+BOOLEAN_T do_stop(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ BOOLEAN_T ret;
+ int sres;
+ ERL_NIF_TERM sockRef;
+
+ sockRef = enif_make_resource(env, descP);
+
+ if (IS_SELECTED(descP)) {
+ ESOCK_ASSERT( (sres = esock_select_stop(env,
+ (ErlNifEvent) descP->sock,
+ descP))
+ >= 0 );
+ if ((sres & ERL_NIF_SELECT_STOP_CALLED) != 0) {
+ /* The socket is no longer known by the select machinery
+ * - it may be closed
+ */
+ ret = FALSE;
+ } else {
+ ESOCK_ASSERT( (sres & ERL_NIF_SELECT_STOP_SCHEDULED) != 0 );
+ /* esock_stop() is scheduled
+ * - socket may be removed by esock_stop() or later
+ */
+ ret = TRUE;
+ }
+ } else {
+ sres = 0;
+ /* The socket has never been used in the select machinery
+ * - it may be closed
+ */
+ ret = FALSE;
+ }
+
+ /* +++++++ Current and waiting Writers +++++++ */
+
+ if (descP->currentWriterP != NULL) {
+
+ /* We have a current Writer; was it deselected?
+ */
+
+ if (sres & ERL_NIF_SELECT_WRITE_CANCELLED) {
+
+ /* The current Writer will not get a select message
+ * - send it an abort message
+ */
+
+ esock_stop_handle_current(env,
+ "writer",
+ descP, sockRef, &descP->currentWriter);
+ }
+
+ /* Inform the waiting Writers (in the same way) */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "do_stop {%d} -> handle waiting writer(s)\r\n",
+ descP->sock) );
+
+ esock_inform_waiting_procs(env, "writer",
+ descP, sockRef, &descP->writersQ,
+ esock_atom_closed);
+
+ descP->currentWriterP = NULL;
+ }
+
+ /* +++++++ Connector +++++++
+ * Note that there should not be Writers and a Connector
+ * at the same time so the check for if the
+ * current Writer/Connecter was deselected is only correct
+ * under that assumption
+ */
+
+ if (descP->connectorP != NULL) {
+
+ /* We have a Connector; was it deselected?
+ */
+
+ if (sres & ERL_NIF_SELECT_WRITE_CANCELLED) {
+
+ /* The Connector will not get a select message
+ * - send it an abort message
+ */
+
+ esock_stop_handle_current(env,
+ "connector",
+ descP, sockRef, &descP->connector);
+ }
+
+ descP->connectorP = NULL;
+ }
+
+ /* +++++++ Current and waiting Readers +++++++ */
+
+ if (descP->currentReaderP != NULL) {
+
+ /* We have a current Reader; was it deselected?
+ */
+
+ if (sres & ERL_NIF_SELECT_READ_CANCELLED) {
+
+ /* The current Reader will not get a select message
+ * - send it an abort message
+ */
+
+ esock_stop_handle_current(env,
+ "reader",
+ descP, sockRef, &descP->currentReader);
+ }
+
+ /* Inform the Readers (in the same way) */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "do_stop {%d} -> handle waiting reader(s)\r\n",
+ descP->sock) );
+
+ esock_inform_waiting_procs(env, "writer",
+ descP, sockRef, &descP->readersQ,
+ esock_atom_closed);
+
+ descP->currentReaderP = NULL;
+ }
+
+ /* +++++++ Current and waiting Acceptors +++++++
+ *
+ * Note that there should not be Readers and Acceptors
+ * at the same time so the check for if the
+ * current Reader/Acceptor was deselected is only correct
+ * under that assumption
+ */
+
+ if (descP->currentAcceptorP != NULL) {
+
+ /* We have a current Acceptor; was it deselected?
+ */
+
+ if (sres & ERL_NIF_SELECT_READ_CANCELLED) {
+
+ /* The current Acceptor will not get a select message
+ * - send it an abort message
+ */
+
+ esock_stop_handle_current(env,
+ "acceptor",
+ descP, sockRef, &descP->currentAcceptor);
+ }
+
+ /* Inform the waiting Acceptor (in the same way) */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "do_stop {%d} -> handle waiting acceptors(s)\r\n",
+ descP->sock) );
+
+ esock_inform_waiting_procs(env, "acceptor",
+ descP, sockRef, &descP->acceptorsQ,
+ esock_atom_closed);
+
+ descP->currentAcceptorP = NULL;
+ }
+
+ return ret;
+}
+
+
+
+/* ========================================================================
+ * Perform the final step in the socket close.
+ */
+extern
+ERL_NIF_TERM essio_fin_close(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ int err;
+ ErlNifPid self;
+#ifdef HAVE_SENDFILE
+ HANDLE sendfileHandle;
+#endif
+
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ if (IS_CLOSED(descP->readState))
+ return esock_make_error_closed(env);
+
+ if (! IS_CLOSING(descP->readState)) {
+ // esock_close() has not been called
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ if (IS_SELECTED(descP) && (descP->closeEnv != NULL)) {
+ // esock_stop() is scheduled but has not been called
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ if (COMPARE_PIDS(&descP->closerPid, &self) != 0) {
+ // This process is not the closer
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ // Close the socket
+
+ /* Stop monitoring the closer.
+ * Demonitoring may fail since this is a dirty NIF
+ * - the caller may have died already.
+ */
+ enif_set_pid_undefined(&descP->closerPid);
+ if (descP->closerMon.isActive) {
+ (void) DEMONP("essio_fin_close -> closer",
+ env, descP, &descP->closerMon);
+ }
+
+ /* Stop monitoring the owner */
+ enif_set_pid_undefined(&descP->ctrlPid);
+ (void) DEMONP("essio_fin_close -> ctrl",
+ env, descP, &descP->ctrlMon);
+ /* Not impossible to still get a esock_down() call from a
+ * just triggered owner monitor down
+ */
+
+#ifdef HAVE_SENDFILE
+ sendfileHandle = descP->sendfileHandle;
+ descP->sendfileHandle = INVALID_HANDLE;
+#endif
+
+ /* This nif-function is executed in a dirty scheduler just so
+ * that it can "hang" (with minimum effect on the VM) while the
+ * kernel writes our buffers. IF we have set the linger option
+ * for this ({true, integer() > 0}). For this to work we must
+ * be blocking...
+ */
+ SET_BLOCKING(descP->sock);
+ err = esock_close_socket(env, descP, TRUE);
+
+#ifdef HAVE_SENDFILE
+ if (sendfileHandle != INVALID_HANDLE) {
+ (void) close(descP->sendfileHandle);
+ }
+#endif
+
+ if (err != 0) {
+ if (err == ERRNO_BLOCK) {
+ /* Not all data in the buffers where sent,
+ * make sure the caller gets this.
+ */
+ return esock_make_error(env, esock_atom_timeout);
+ } else {
+ return esock_make_error_errno(env, err);
+ }
+ }
+
+ return esock_atom_ok;
+}
+
+
+/* ========================================================================
+ * *** essio_shutdown should go here - if we need one ***
+ */
+
+
+/* ========================================================================
+ * *** essio_sockname should go here - if we need one ***
+ */
+
+
+/* ========================================================================
+ * *** essio_peername should go here - if we need one ***
+ */
+
+
+/* ========================================================================
+ * Cancel a connect request.
+ */
+
+extern
+ERL_NIF_TERM essio_cancel_connect(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+ ErlNifPid self;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_cancel_connect {%d} -> entry with"
+ "\r\n writeState: 0x%X"
+ "\r\n opRef: %T"
+ "\r\n",
+ descP->sock, descP->writeState, opRef) );
+
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ if (! IS_OPEN(descP->writeState)) {
+
+ res = esock_make_error_closed(env);
+
+ } else if ((descP->connectorP == NULL) ||
+ (COMPARE_PIDS(&self, &descP->connector.pid) != 0) ||
+ (COMPARE(opRef, descP->connector.ref) != 0)) {
+
+ res = esock_make_error(env, esock_atom_not_found);
+
+ } else {
+
+ res = esock_cancel_write_select(env, descP, opRef);
+ esock_requestor_release("esock_cancel_connect",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+ descP->writeState &= ~ESOCK_STATE_CONNECTING;
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_cancel_connect {%d} -> done when"
+ "\r\n res: %T"
+ "\r\n",
+ descP->sock, descP->writeState,
+ opRef, res) );
+
+ return res;
+}
+
+
+
+/* ========================================================================
+ * Cancel accept request
+ *
+ * We have two different cases:
+ * *) Its the current acceptor
+ * Cancel the select!
+ * We need to activate one of the waiting acceptors.
+ * *) Its one of the acceptors ("waiting") in the queue
+ * Simply remove the acceptor from the queue.
+ *
+ */
+extern
+ERL_NIF_TERM essio_cancel_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_cancel_accept(%T), {%d,0x%X} ->"
+ "\r\n opRef: %T"
+ "\r\n %s"
+ "\r\n",
+ sockRef, descP->sock, descP->readState,
+ opRef,
+ ((descP->currentAcceptorP == NULL)
+ ? "without acceptor" : "with acceptor")) );
+
+ if (! IS_OPEN(descP->readState)) {
+
+ res = esock_make_error_closed(env);
+
+ } else if (descP->currentAcceptorP == NULL) {
+
+ res = esock_atom_not_found;
+
+ } else {
+ ErlNifPid self;
+
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ if (COMPARE_PIDS(&self, &descP->currentAcceptor.pid) == 0) {
+ if (COMPARE(opRef, descP->currentAcceptor.ref) == 0)
+ res = essio_cancel_accept_current(env, descP, sockRef);
+ else
+ res = esock_atom_not_found;
+ } else {
+ res = essio_cancel_accept_waiting(env, descP, opRef, &self);
+ }
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_cancel_accept(%T) -> done with result:"
+ "\r\n %T"
+ "\r\n", sockRef, res) );
+
+ return res;
+}
+
+
+/* The current acceptor process has an ongoing select we first must
+ * cancel. Then we must re-activate the "first" (the first
+ * in the acceptor queue).
+ */
+static
+ERL_NIF_TERM essio_cancel_accept_current(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM res;
+
+ ESOCK_ASSERT( DEMONP("essio_cancel_accept_current -> current acceptor",
+ env, descP, &descP->currentAcceptor.mon) == 0);
+ MON_INIT(&descP->currentAcceptor.mon);
+ res = esock_cancel_read_select(env, descP, descP->currentAcceptor.ref);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_cancel_accept_current(%T) {%d} -> cancel res: %T"
+ "\r\n", sockRef, descP->sock, res) );
+
+ if (!esock_activate_next_acceptor(env, descP, sockRef)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_cancel_accept_current(%T) {%d} -> "
+ "no more acceptors\r\n",
+ sockRef, descP->sock) );
+
+ descP->readState &= ~ESOCK_STATE_ACCEPTING;
+
+ descP->currentAcceptorP = NULL;
+ }
+
+ return res;
+}
+
+
+/* These processes have not performed a select, so we can simply
+ * remove them from the acceptor queue.
+ */
+static
+ERL_NIF_TERM essio_cancel_accept_waiting(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef,
+ const ErlNifPid* selfP)
+{
+ /* unqueue request from (acceptor) queue */
+
+ if (esock_acceptor_unqueue(env, descP, &opRef, selfP)) {
+ return esock_atom_ok;
+ } else {
+ return esock_atom_not_found;
+ }
+}
+
+
+
+/* ========================================================================
+ * Cancel send request
+ *
+ * Cancel a send operation.
+ * Its either the current writer or one of the waiting writers.
+ */
+
+extern
+ERL_NIF_TERM essio_cancel_send(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_cancel_send(%T), {%d,0x%X} -> entry with"
+ "\r\n opRef: %T"
+ "\r\n %s"
+ "\r\n",
+ sockRef, descP->sock, descP->writeState,
+ opRef,
+ ((descP->currentWriterP == NULL)
+ ? "without writer" : "with writer")) );
+
+ if (! IS_OPEN(descP->writeState)) {
+
+ res = esock_make_error_closed(env);
+
+ } else if (descP->currentWriterP == NULL) {
+
+ res = esock_atom_not_found;
+
+ } else {
+ ErlNifPid self;
+
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ if (COMPARE_PIDS(&self, &descP->currentWriter.pid) == 0) {
+ if (COMPARE(opRef, descP->currentWriter.ref) == 0)
+ res = essio_cancel_send_current(env, descP, sockRef);
+ else
+ res = esock_atom_not_found;
+ } else {
+ res = essio_cancel_send_waiting(env, descP, opRef, &self);
+ }
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_cancel_send(%T) {%d} -> done with result:"
+ "\r\n %T"
+ "\r\n", sockRef, descP->sock, res) );
+
+ return res;
+}
+
+
+
+/* The current writer process has an ongoing select we first must
+ * cancel. Then we must re-activate the "first" (the first
+ * in the writer queue).
+ */
+static
+ERL_NIF_TERM essio_cancel_send_current(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM res;
+
+ ESOCK_ASSERT( DEMONP("essio_cancel_send_current -> current writer",
+ env, descP, &descP->currentWriter.mon) == 0);
+ res = esock_cancel_write_select(env, descP, descP->currentWriter.ref);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_cancel_send_current(%T) {%d} -> cancel res: %T"
+ "\r\n", sockRef, descP->sock, res) );
+
+ if (!esock_activate_next_writer(env, descP, sockRef)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_cancel_send_current(%T) {%d} -> no more writers"
+ "\r\n", sockRef, descP->sock) );
+
+ descP->currentWriterP = NULL;
+ }
+
+ return res;
+}
+
+
+
+/* These processes have not performed a select, so we can simply
+ * remove them from the writer queue.
+ */
+static
+ERL_NIF_TERM essio_cancel_send_waiting(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef,
+ const ErlNifPid* selfP)
+{
+ /* unqueue request from (writer) queue */
+
+ if (esock_writer_unqueue(env, descP, &opRef, selfP)) {
+ return esock_atom_ok;
+ } else {
+ return esock_atom_not_found;
+ }
+}
+
+
+
+/* ========================================================================
+ * Cancel receive request
+ *
+ * Cancel a read operation.
+ * Its either the current reader or one of the waiting readers.
+ */
+extern
+ERL_NIF_TERM essio_cancel_recv(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_cancel_recv(%T), {%d,0x%X} -> entry with"
+ "\r\n opRef: %T"
+ "\r\n %s"
+ "\r\n",
+ sockRef, descP->sock, descP->readState,
+ opRef,
+ ((descP->currentReaderP == NULL)
+ ? "without reader" : "with reader")) );
+
+ if (! IS_OPEN(descP->readState)) {
+
+ res = esock_make_error_closed(env);
+
+ } else if (descP->currentReaderP == NULL) {
+
+ res = esock_atom_not_found;
+
+ } else {
+ ErlNifPid self;
+
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ if (COMPARE_PIDS(&self, &descP->currentReader.pid) == 0) {
+ if (COMPARE(opRef, descP->currentReader.ref) == 0)
+ res = essio_cancel_recv_current(env, descP, sockRef);
+ else
+ res = esock_atom_not_found;
+ } else {
+ res = essio_cancel_recv_waiting(env, descP, opRef, &self);
+ }
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_cancel_recv(%T) {%d} -> done with result:"
+ "\r\n %T"
+ "\r\n", sockRef, descP->sock, res) );
+
+
+ return res;
+
+}
+
+
+/* The current reader process has an ongoing select we first must
+ * cancel. Then we must re-activate the "first" (the first
+ * in the reader queue).
+ */
+static
+ERL_NIF_TERM essio_cancel_recv_current(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM res;
+
+ ESOCK_ASSERT( DEMONP("essio_cancel_recv_current -> current reader",
+ env, descP, &descP->currentReader.mon) == 0);
+ res = esock_cancel_read_select(env, descP, descP->currentReader.ref);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_cancel_recv_current(%T) {%d} -> cancel res: %T"
+ "\r\n", sockRef, descP->sock, res) );
+
+ if (!esock_activate_next_reader(env, descP, sockRef)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_cancel_recv_current(%T) {%d} -> no more readers"
+ "\r\n", sockRef, descP->sock) );
+
+ descP->currentReaderP = NULL;
+ }
+
+ return res;
+}
+
+
+/* These processes have not performed a select, so we can simply
+ * remove them from the reader queue.
+ */
+static
+ERL_NIF_TERM essio_cancel_recv_waiting(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef,
+ const ErlNifPid* selfP)
+{
+ /* unqueue request from (reader) queue */
+
+ if (esock_reader_unqueue(env, descP, &opRef, selfP)) {
+ return esock_atom_ok;
+ } else {
+ return esock_atom_not_found;
+ }
+}
+
+
+
+/* ========================================================================
+ * IOCTL with two args (socket and request "key")
+ *
+ */
+extern
+ERL_NIF_TERM essio_ioctl2(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ unsigned long req)
+{
+ switch (req) {
+
+#if defined(SIOCGIFCONF)
+ case SIOCGIFCONF:
+ return essio_ioctl_gifconf(env, descP);
+ break;
+#endif
+
+ default:
+ return esock_make_error(env, esock_atom_enotsup);
+ break;
+ }
+
+}
+
+
+
+/* ========================================================================
+ * IOCTL with three args (socket, request "key" and one argument)
+ *
+ * The type and value of 'arg' depend on the request,
+ * which we have not yet "analyzed".
+ *
+ * Request arg arg type
+ * ------- ------- --------
+ * gifname ifindex integer
+ * gifindex name string
+ * gifflags name string
+ * gifaddr name string
+ * gifdstaddr name string
+ * gifbdraddr name string
+ * gifnetmask name string
+ * gifmtu name string
+ * gifhwaddr name string
+ * gifmap name string
+ * giftxqlen name string
+ */
+extern
+ERL_NIF_TERM essio_ioctl3(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ unsigned long req,
+ ERL_NIF_TERM arg)
+{
+ /* This for *get* requests */
+
+ switch (req) {
+
+#if defined(SIOCGIFNAME)
+ case SIOCGIFNAME:
+ return essio_ioctl_gifname(env, descP, arg);
+ break;
+#endif
+
+#if defined(SIOCGIFINDEX)
+ case SIOCGIFINDEX:
+ return essio_ioctl_gifindex(env, descP, arg);
+ break;
+#endif
+
+#if defined(SIOCGIFFLAGS)
+ case SIOCGIFFLAGS:
+ return essio_ioctl_gifflags(env, descP, arg);
+ break;
+#endif
+
+#if defined(SIOCGIFADDR)
+ case SIOCGIFADDR:
+ return essio_ioctl_gifaddr(env, descP, arg);
+ break;
+#endif
+
+#if defined(SIOCGIFDSTADDR)
+ case SIOCGIFDSTADDR:
+ return essio_ioctl_gifdstaddr(env, descP, arg);
+ break;
+#endif
+
+#if defined(SIOCGIFBRDADDR)
+ case SIOCGIFBRDADDR:
+ return essio_ioctl_gifbrdaddr(env, descP, arg);
+ break;
+#endif
+
+#if defined(SIOCGIFNETMASK)
+ case SIOCGIFNETMASK:
+ return essio_ioctl_gifnetmask(env, descP, arg);
+ break;
+#endif
+
+#if defined(SIOCGIFMTU)
+ case SIOCGIFMTU:
+ return essio_ioctl_gifmtu(env, descP, arg);
+ break;
+#endif
+
+#if defined(SIOCGIFHWADDR) && defined(ESOCK_USE_HWADDR)
+ case SIOCGIFHWADDR:
+ return essio_ioctl_gifhwaddr(env, descP, arg);
+ break;
+#endif
+
+#if defined(SIOCGIFMAP) && defined(ESOCK_USE_IFMAP)
+ case SIOCGIFMAP:
+ return essio_ioctl_gifmap(env, descP, arg);
+ break;
+#endif
+
+#if defined(SIOCGIFTXQLEN)
+ case SIOCGIFTXQLEN:
+ return essio_ioctl_giftxqlen(env, descP, arg);
+ break;
+#endif
+
+ default:
+ return esock_make_error(env, esock_atom_enotsup);
+ break;
+ }
+
+}
+
+
+
+/* ========================================================================
+ * IOCTL with four args (socket, request "key" and two arguments)
+ *
+ * The type and value of arg(s) depend on the request,
+ * which we have not yet "analyzed".
+ *
+ * Request arg1 arg1 type arg2 arg2 type
+ * ------- ------- --------- ------ ---------
+ * sifflags name string Flags #{IntFlag := boolean()}
+ * IntFlag is the native flag
+ * sifaddr name string Addr sockaddr()
+ * sifdstaddr name string DstAddr sockaddr()
+ * sifbrdaddr name string BrdAddr sockaddr()
+ * sifnetmask name string NetMask sockaddr()
+ * gifmtu name string MTU integer()
+ * sifhwaddr name string HwAddr sockaddr()
+ * giftxqlen name string Len integer()
+ */
+extern
+ERL_NIF_TERM essio_ioctl4(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ unsigned long req,
+ ERL_NIF_TERM ename,
+ ERL_NIF_TERM eval)
+{
+
+ switch (req) {
+
+#if defined(SIOCSIFFLAGS)
+ case SIOCSIFFLAGS:
+ return essio_ioctl_sifflags(env, descP, ename, eval);
+ break;
+#endif
+
+#if defined(SIOCSIFADDR)
+ case SIOCSIFADDR:
+ return essio_ioctl_sifaddr(env, descP, ename, eval);
+ break;
+#endif
+
+#if defined(SIOCSIFDSTADDR)
+ case SIOCSIFDSTADDR:
+ return essio_ioctl_sifdstaddr(env, descP, ename, eval);
+ break;
+#endif
+
+#if defined(SIOCSIFBRDADDR)
+ case SIOCSIFBRDADDR:
+ return essio_ioctl_sifbrdaddr(env, descP, ename, eval);
+ break;
+#endif
+
+#if defined(SIOCSIFNETMASK)
+ case SIOCSIFNETMASK:
+ return essio_ioctl_sifnetmask(env, descP, ename, eval);
+ break;
+#endif
+
+#if defined(SIOCSIFMTU)
+ case SIOCSIFMTU:
+ return essio_ioctl_sifmtu(env, descP, ename, eval);
+ break;
+#endif
+
+#if defined(SIOCSIFTXQLEN)
+ case SIOCSIFTXQLEN:
+ return essio_ioctl_siftxqlen(env, descP, ename, eval);
+ break;
+#endif
+
+ default:
+ return esock_make_error(env, esock_atom_enotsup);
+ break;
+ }
+
+}
+
+
+
+/* ===========================================================================
+ * The implemented (ioctl) get requests falls into three grops:
+ *
+ * 1) gifconf - Takes no argument other then the request
+ * 2) gifname - Takes the interface index (integer) as an argument
+ * 3) other - All other (get) requests takes the interface name (string)
+ * as the argument.
+ *
+ * The functions defined using the macros below are all in the third (3)
+ * group.
+ *
+ */
+
+/* *** essio_ioctl_gifindex *** */
+#if defined(SIOCGIFINDEX)
+#if defined(ESOCK_USE_IFINDEX)
+#define IOCTL_GIFINDEX_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifindex, SIOCGIFINDEX, ivalue, ifreq.ifr_ifindex)
+#elif defined(ESOCK_USE_INDEX)
+#define IOCTL_GIFINDEX_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifindex, SIOCGIFINDEX, ivalue, ifreq.ifr_index)
+#else
+#define IOCTL_GIFINDEX_FUNC_DECL
+#endif
+#else
+#define IOCTL_GIFINDEX_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_gifflags *** */
+#if defined(SIOCGIFFLAGS)
+#define IOCTL_GIFFLAGS_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifflags, SIOCGIFFLAGS, flags, ifreq.ifr_flags)
+#else
+#define IOCTL_GIFFLAGS_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_gifaddr *** */
+#if defined(SIOCGIFADDR)
+#define IOCTL_GIFADDR_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifaddr, SIOCGIFADDR, ifraddr, &ifreq.ifr_addr)
+#else
+#define IOCTL_GIFADDR_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_gifdstaddr *** */
+#if defined(SIOCGIFDSTADDR)
+#define IOCTL_GIFDSTADDR_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifdstaddr, SIOCGIFDSTADDR, ifraddr, &ifreq.ifr_dstaddr)
+#else
+#define IOCTL_GIFDSTADDR_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_gifbrdaddr *** */
+#if defined(SIOCGIFBRDADDR)
+#define IOCTL_GIFBRDADDR_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifbrdaddr, SIOCGIFBRDADDR, ifraddr, &ifreq.ifr_broadaddr)
+#else
+#define IOCTL_GIFBRDADDR_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_gifnetmask *** */
+#if defined(SIOCGIFNETMASK)
+#ifdef __linux__
+#define IOCTL_GIFNETMASK_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifnetmask, SIOCGIFNETMASK, ifraddr, &ifreq.ifr_netmask)
+#else
+#define IOCTL_GIFNETMASK_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifnetmask, SIOCGIFNETMASK, ifraddr, &ifreq.ifr_addr)
+#endif
+#else
+#define IOCTL_GIFNETMASK_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_gifmtu *** */
+#if defined(SIOCGIFMTU)
+#define IOCTL_GIFMTU_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifmtu, SIOCGIFMTU, ivalue, ifreq.ifr_mtu)
+#else
+#define IOCTL_GIFMTU_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_gifhwaddr *** */
+#if defined(SIOCGIFHWADDR) && defined(ESOCK_USE_HWADDR)
+#define IOCTL_GIFHWADDR_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifhwaddr, SIOCGIFHWADDR, hwaddr, &ifreq.ifr_hwaddr)
+#else
+#define IOCTL_GIFHWADDR_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_gifmap *** */
+#if defined(SIOCGIFMAP) && defined(ESOCK_USE_IFMAP)
+#define IOCTL_GIFMAP_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(gifmap, SIOCGIFMAP, ifrmap, &ifreq.ifr_map)
+#else
+#define IOCTL_GIFMAP_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_giftxqlen *** */
+#if defined(SIOCGIFTXQLEN)
+#define IOCTL_GIFTXQLEN_FUNC_DECL \
+ IOCTL_GET_REQUEST_DECL(giftxqlen, SIOCGIFTXQLEN, ivalue, ifreq.ifr_qlen)
+#else
+#define IOCTL_GIFTXQLEN_FUNC_DECL
+#endif
+
+#define IOCTL_GET_FUNCS \
+ IOCTL_GIFINDEX_FUNC_DECL \
+ IOCTL_GIFFLAGS_FUNC_DECL \
+ IOCTL_GIFADDR_FUNC_DECL \
+ IOCTL_GIFDSTADDR_FUNC_DECL \
+ IOCTL_GIFBRDADDR_FUNC_DECL \
+ IOCTL_GIFNETMASK_FUNC_DECL \
+ IOCTL_GIFMTU_FUNC_DECL \
+ IOCTL_GIFHWADDR_FUNC_DECL \
+ IOCTL_GIFMAP_FUNC_DECL \
+ IOCTL_GIFTXQLEN_FUNC_DECL
+
+#define IOCTL_GET_REQUEST_DECL(OR, R, EF, UV) \
+ static \
+ ERL_NIF_TERM essio_ioctl_##OR(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM ename) \
+ { \
+ ERL_NIF_TERM result; \
+ struct ifreq ifreq; \
+ char* ifn = NULL; \
+ int nlen; \
+ \
+ SSDBG( descP, ("UNIX-ESSIO", "essio_ioctl_" #OR " {%d} -> entry with" \
+ "\r\n (e)Name: %T" \
+ "\r\n", descP->sock, ename) ); \
+ \
+ if (!esock_decode_string(env, ename, &ifn)) \
+ return enif_make_badarg(env); \
+ \
+ nlen = esock_strnlen(ifn, IFNAMSIZ); \
+ \
+ sys_memset(ifreq.ifr_name, '\0', IFNAMSIZ); \
+ sys_memcpy(ifreq.ifr_name, ifn, \
+ (nlen >= IFNAMSIZ) ? IFNAMSIZ-1 : nlen); \
+ \
+ SSDBG( descP, \
+ ("UNIX-ESSIO", \
+ "essio_ioctl_" #OR " {%d} -> try ioctl\r\n", \
+ descP->sock) ); \
+ \
+ if (ioctl(descP->sock, R, (char *) &ifreq) < 0) { \
+ int saveErrno = sock_errno(); \
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno)); \
+ \
+ SSDBG( descP, \
+ ("UNIX-ESSIO", "essio_ioctl_" #OR " {%d} -> failure: " \
+ "\r\n reason: %T (%d)" \
+ "\r\n", descP->sock, reason, saveErrno) ); \
+ \
+ result = esock_make_error(env, reason); \
+ \
+ } else { \
+ SSDBG( descP, \
+ ("UNIX-ESSIO", "essio_ioctl_" #OR " {%d} -> encode value\r\n", \
+ descP->sock) ); \
+ result = encode_ioctl_##EF(env, descP, UV); \
+ } \
+ \
+ FREE(ifn); \
+ \
+ return result; \
+ \
+ }
+IOCTL_GET_FUNCS
+#undef IOCTL_GET_FUNCS
+
+
+/* ===========================================================================
+ * The "rest" of the implemented (ioctl) get requests
+ *
+ * These (get) requests could not be 'generated' by the macros above.
+ */
+
+static
+ERL_NIF_TERM essio_ioctl_gifconf(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ struct ifconf ifc;
+ int ifc_len = 0;
+ int buflen = 100 * sizeof(struct ifreq);
+ char *buf = MALLOC(buflen);
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_gifconf {%d} -> entry\r\n", descP->sock) );
+
+ for (;;) {
+ ifc.ifc_len = buflen;
+ ifc.ifc_buf = buf;
+ if (ioctl(descP->sock, SIOCGIFCONF, (char *) &ifc) < 0) {
+ int saveErrno = sock_errno();
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_gifconf {%d} -> failure: "
+ "\r\n errno: %d (%s)"
+ "\r\n", descP->sock, saveErrno, erl_errno_id(saveErrno)) );
+
+ if (saveErrno != EINVAL || ifc_len) {
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
+ FREE(buf);
+ return esock_make_error(env, reason);
+ }
+ } else {
+ if (ifc.ifc_len == ifc_len) break; /* buf large enough */
+ ifc_len = ifc.ifc_len;
+ }
+ buflen += 10 * sizeof(struct ifreq);
+ buf = (char *) REALLOC(buf, buflen);
+ }
+
+ result = encode_ioctl_ifconf(env, descP, &ifc);
+
+ FREE(ifc.ifc_buf);
+
+ return result;
+}
+
+
+#if defined(SIOCGIFNAME)
+static
+ERL_NIF_TERM essio_ioctl_gifname(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eidx)
+{
+ ERL_NIF_TERM result;
+ struct ifreq ifreq;
+ int index;
+
+ SSDBG( descP, ("UNIX-ESSIO", "essio_ioctl_gifname {%d} -> entry with"
+ "\r\n (e)Index: %T"
+ "\r\n", descP->sock, eidx) );
+
+ if (!GET_INT(env, eidx, &index))
+ return enif_make_badarg(env);
+
+ ifreq.ifr_ifindex = index;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_ioctl_gifname {%d} -> try ioctl\r\n", descP->sock) );
+
+ if (ioctl(descP->sock, SIOCGIFNAME, (char *) &ifreq) < 0) {
+ int saveErrno = sock_errno();
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_gifname {%d} -> failure: "
+ "\r\n reason: %T (%d)"
+ "\r\n", descP->sock, reason, saveErrno) );
+
+ result = esock_make_error(env, reason);
+
+ } else {
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_gifname {%d} -> encode name\r\n",
+ descP->sock) );
+
+ result = esock_make_ok2(env, encode_ioctl_ifreq_name(env, ifreq.ifr_name));
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_gifname {%d} -> done with"
+ "\r\n result: %T"
+ "\r\n",
+ descP->sock, result) );
+
+ return result;
+
+}
+#endif
+
+
+
+
+/* ===========================================================================
+ * The implemented (ioctl) set requests:
+ *
+ */
+
+/* *** essio_ioctl_sifaddr *** */
+#if defined(SIOCSIFADDR)
+#define IOCTL_SIFADDR_FUNC_DECL \
+ IOCTL_SET_REQUEST_DECL(sifaddr, SIOCSIFADDR, sockaddr, \
+ ((ESockAddress*) &ifreq.ifr_addr))
+#else
+#define IOCTL_SIFADDR_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_sifdstaddr *** */
+#if defined(SIOCSIFDSTADDR)
+#define IOCTL_SIFDSTADDR_FUNC_DECL \
+ IOCTL_SET_REQUEST_DECL(sifdstaddr, SIOCSIFDSTADDR, sockaddr, \
+ ((ESockAddress*) &ifreq.ifr_dstaddr))
+#else
+#define IOCTL_SIFDSTADDR_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_sifbrdaddr *** */
+#if defined(SIOCSIFBRDADDR)
+#define IOCTL_SIFBRDADDR_FUNC_DECL \
+ IOCTL_SET_REQUEST_DECL(sifbrdaddr, SIOCSIFBRDADDR, sockaddr, \
+ ((ESockAddress*) &ifreq.ifr_broadaddr))
+#else
+#define IOCTL_SIFBRDADDR_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_sifnetmask *** */
+#if defined(SIOCSIFNETMASK)
+#ifdef __linux__
+#define IOCTL_SIFNETMASK_FUNC_DECL \
+ IOCTL_SET_REQUEST_DECL(sifnetmask, SIOCSIFNETMASK, sockaddr, \
+ ((ESockAddress*) &ifreq.ifr_netmask))
+#else
+#define IOCTL_SIFNETMASK_FUNC_DECL \
+ IOCTL_SET_REQUEST_DECL(sifnetmask, SIOCSIFNETMASK, sockaddr, \
+ ((ESockAddress*) &ifreq.ifr_addr))
+#endif
+#else
+#define IOCTL_SIFNETMASK_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_sifmtu ***
+ * On some platforms, MTU is an unsigned int
+ */
+#if defined(SIOCSIFMTU)
+#define IOCTL_SIFMTU_FUNC_DECL \
+ IOCTL_SET_REQUEST_DECL(sifmtu, SIOCSIFMTU, mtu, (int*) &ifreq.ifr_mtu)
+#else
+#define IOCTL_SIFMTU_FUNC_DECL
+#endif
+
+/* *** essio_ioctl_siftxqlen *** */
+#if defined(SIOCSIFTXQLEN)
+#define IOCTL_SIFTXQLEN_FUNC_DECL \
+ IOCTL_SET_REQUEST_DECL(siftxqlen, SIOCSIFTXQLEN, txqlen, &ifreq.ifr_qlen)
+#else
+#define IOCTL_SIFTXQLEN_FUNC_DECL
+#endif
+
+#define IOCTL_SET_FUNCS \
+ IOCTL_SIFADDR_FUNC_DECL \
+ IOCTL_SIFDSTADDR_FUNC_DECL \
+ IOCTL_SIFBRDADDR_FUNC_DECL \
+ IOCTL_SIFNETMASK_FUNC_DECL \
+ IOCTL_SIFMTU_FUNC_DECL \
+ IOCTL_SIFTXQLEN_FUNC_DECL
+
+#define IOCTL_SET_REQUEST_DECL(OR, R, DF, UVP) \
+ static \
+ ERL_NIF_TERM essio_ioctl_##OR(ErlNifEnv* env, \
+ ESockDescriptor* descP, \
+ ERL_NIF_TERM ename, \
+ ERL_NIF_TERM evalue) \
+ { \
+ ERL_NIF_TERM result; \
+ struct ifreq ifreq; \
+ char* ifn = NULL; \
+ int nlen; \
+ \
+ SSDBG( descP, ("UNIX-ESSIO", "essio_ioctl_" #OR " {%d} -> entry with" \
+ "\r\n (e)Name: %T" \
+ "\r\n (e)Value: %T" \
+ "\r\n", descP->sock, ename, evalue) ); \
+ \
+ if (!esock_decode_string(env, ename, &ifn)) { \
+ \
+ SSDBG( descP, \
+ ("UNIX-ESSIO", "essio_ioctl_" #OR " {%d} -> failed decode name" \
+ "\r\n", descP->sock) ); \
+ \
+ return enif_make_badarg(env); \
+ } \
+ \
+ if (! decode_ioctl_##DF(env, descP, evalue, UVP)) { \
+ \
+ SSDBG( descP, \
+ ("UNIX-ESSIO", "essio_ioctl_" #OR " {%d} -> failed decode addr" \
+ "\r\n", descP->sock) ); \
+ \
+ return esock_make_invalid(env, esock_atom_##DF); \
+ } \
+ \
+ nlen = esock_strnlen(ifn, IFNAMSIZ); \
+ \
+ sys_memset(ifreq.ifr_name, '\0', IFNAMSIZ); \
+ sys_memcpy(ifreq.ifr_name, ifn, \
+ (nlen >= IFNAMSIZ) ? IFNAMSIZ-1 : nlen); \
+ \
+ SSDBG( descP, \
+ ("UNIX-ESSIO", "essio_ioctl_" #OR " {%d} -> try ioctl\r\n", \
+ descP->sock) ); \
+ \
+ if (ioctl(descP->sock, R, (char *) &ifreq) < 0) { \
+ int saveErrno = sock_errno(); \
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno)); \
+ \
+ SSDBG( descP, \
+ ("UNIX-ESSIO", "essio_ioctl_" #OR " {%d} -> failure: " \
+ "\r\n reason: %T (%d)" \
+ "\r\n", descP->sock, reason, saveErrno) ); \
+ \
+ result = esock_make_error(env, reason); \
+ \
+ } else { \
+ SSDBG( descP, \
+ ("UNIX-ESSIO", "essio_ioctl_" #OR " {%d} -> " \
+ "addr successfully set\r\n", \
+ descP->sock) ); \
+ result = esock_atom_ok; \
+ } \
+ \
+ FREE(ifn); \
+ \
+ return result; \
+ \
+ }
+
+IOCTL_SET_FUNCS
+#undef IOCTL_SET_FUNCS
+
+
+/* ===========================================================================
+ * The "rest" of the implemented (ioctl) set requests
+ *
+ * These (set) requests could not be 'generated' by the macros above.
+ */
+
+#if defined(SIOCSIFFLAGS)
+static
+ERL_NIF_TERM essio_ioctl_sifflags(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM ename,
+ ERL_NIF_TERM eflags)
+{
+ ERL_NIF_TERM result;
+ struct ifreq ifreq;
+ char* ifn = NULL;
+ int nlen;
+
+ SSDBG( descP, ("UNIX-ESSIO", "essio_ioctl_sifflags {%d} -> entry with"
+ "\r\n (e)Name: %T"
+ "\r\n (e)Flags: %T"
+ "\r\n", descP->sock, ename, eflags) );
+
+ if (!esock_decode_string(env, ename, &ifn)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_sifflags {%d} -> failed decode name"
+ "\r\n", descP->sock) );
+
+ return enif_make_badarg(env);
+ }
+
+ // Make sure the length of the string is valid!
+ nlen = esock_strnlen(ifn, IFNAMSIZ);
+
+ sys_memset(ifreq.ifr_name, '\0', IFNAMSIZ); // Just in case
+ sys_memcpy(ifreq.ifr_name, ifn,
+ (nlen >= IFNAMSIZ) ? IFNAMSIZ-1 : nlen);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_sifflags {%d} -> try (get) ioctl\r\n",
+ descP->sock) );
+
+ if (ioctl(descP->sock, SIOCGIFFLAGS, (char *) &ifreq) < 0) {
+ int saveErrno = sock_errno();
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_sifflags {%d} -> "
+ "failure: failed reading *current* flags"
+ "\r\n reason: %T (%d)"
+ "\r\n", descP->sock, reason, saveErrno) );
+
+ result = esock_make_error(env, reason);
+
+ } else {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_ioctl_sifflags {%d} -> (local) update flags\r\n",
+ descP->sock) );
+
+ if (decode_ioctl_flags(env, descP, eflags, &ifreq.ifr_flags)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_sifflags {%d} -> try (set) ioctl\r\n",
+ descP->sock) );
+
+ if (ioctl(descP->sock, SIOCSIFFLAGS, (char *) &ifreq) < 0) {
+ int saveErrno = sock_errno();
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_sifflags {%d} -> failure: "
+ "\r\n reason: %T (%d)"
+ "\r\n", descP->sock, reason, saveErrno) );
+
+ result = esock_make_error(env, reason);
+
+ } else {
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_sifflags {%d} -> "
+ "updated flags successfully set\r\n",
+ descP->sock) );
+ result = esock_atom_ok;
+ }
+
+ /* We know that if esock_decode_string is successful,
+ * we have "some" form of string, and therefor memory
+ * has been allocated (and need to be freed)... */
+ FREE(ifn);
+
+ } else {
+ result = enif_make_badarg(env);
+ }
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_ioctl_sifflags {%d} -> done with result: "
+ "\r\n %T"
+ "\r\n",
+ descP->sock, result) );
+
+ return result;
+
+}
+#endif
+
+
+
+/* ===========================================================================
+ * ioctl utility functions
+ *
+ */
+
+static
+ERL_NIF_TERM encode_ioctl_ifconf(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ struct ifconf* ifcP)
+{
+ ERL_NIF_TERM result;
+ unsigned int len = ((ifcP == NULL) ? 0 :
+ (ifcP->ifc_len / sizeof(struct ifreq)));
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "encode_ioctl_ifconf -> entry (when len = %d)\r\n", len) );
+
+ if (len > 0) {
+ ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM));
+ unsigned int i = 0;
+ struct ifreq* p = ifcP->ifc_req;
+
+ for (i = 0 ; i < len ; i++) {
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "encode_ioctl_ifconf -> encode ifreq entry %d\r\n", i) );
+ array[i] = encode_ioctl_ifconf_ifreq(env, descP, &p[i]);
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_ioctl_ifconf -> all entries encoded\r\n", i) );
+
+ result = esock_make_ok2(env, MKLA(env, array, len));
+ FREE(array);
+
+ } else {
+
+ result = esock_make_ok2(env, MKEL(env));
+
+ }
+
+ return result;
+}
+
+
+#if defined(SIOCGIFMAP) && defined(ESOCK_USE_IFMAP)
+static
+ERL_NIF_TERM encode_ioctl_ifrmap(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ struct ifmap* mapP)
+{
+ ERL_NIF_TERM mapKeys[] = {esock_atom_mem_start,
+ esock_atom_mem_end,
+ esock_atom_base_addr,
+ esock_atom_irq,
+ esock_atom_dma,
+ esock_atom_port};
+ ERL_NIF_TERM mapVals[] = {MKUL(env, mapP->mem_start),
+ MKUL(env, mapP->mem_end),
+ MKUI(env, mapP->base_addr),
+ MKUI(env, mapP->irq),
+ MKUI(env, mapP->dma),
+ MKUI(env, mapP->port)};
+ unsigned int numMapKeys = NUM(mapKeys);
+ unsigned int numMapVals = NUM(mapVals);
+ ERL_NIF_TERM emap;
+
+ ESOCK_ASSERT( numMapVals == numMapKeys );
+ ESOCK_ASSERT( MKMA(env, mapKeys, mapVals, numMapKeys, &emap) );
+
+ SSDBG( descP, ("UNIX-ESSIO", "encode_ioctl_ifrmap -> done with"
+ "\r\n Map: %T"
+ "\r\n", emap) );
+
+ return esock_make_ok2(env, emap);;
+}
+#endif
+
+
+#if defined(SIOCGIFHWADDR) && defined(ESOCK_USE_HWADDR)
+static
+ERL_NIF_TERM encode_ioctl_hwaddr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ struct sockaddr* addrP)
+{
+ ERL_NIF_TERM eaddr;
+ SOCKLEN_T sz = sizeof(struct sockaddr);
+
+ esock_encode_hwsockaddr(env, addrP, sz, &eaddr);
+
+ SSDBG( descP, ("UNIX-ESSIO", "encode_ioctl_ifraddr -> done with"
+ "\r\n Sock Addr: %T"
+ "\r\n", eaddr) );
+
+ return esock_make_ok2(env, eaddr);;
+}
+#endif
+
+
+static
+ERL_NIF_TERM encode_ioctl_ifraddr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ struct sockaddr* addrP)
+{
+ ERL_NIF_TERM eaddr;
+
+ esock_encode_sockaddr(env, (ESockAddress*) addrP, -1, &eaddr);
+
+ SSDBG( descP, ("UNIX-ESSIO", "encode_ioctl_ifraddr -> done with"
+ "\r\n Sock Addr: %T"
+ "\r\n", eaddr) );
+
+ return esock_make_ok2(env, eaddr);;
+}
+
+
+static
+ERL_NIF_TERM encode_ioctl_flags(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ short flags)
+{
+ int i, flag, num = esock_ioctl_flags_length; // NUM(ioctl_flags);
+ ERL_NIF_TERM eflags, eflag;
+ SocketTArray ta = TARRAY_CREATE(20); // Just to be on the safe side
+
+ if (flags == 0) {
+ eflags = MKEL(env);
+ } else {
+ for (i = 0; (i < num) && (flags != 0); i++) {
+ flag = esock_ioctl_flags[i].flag;
+ if ((flag != 0) && ((flags & flag) == flag)) {
+ eflag = *(esock_ioctl_flags[i].name);
+ flags &= ~flag;
+
+ SSDBG( descP, ("UNIX-ESSIO", "encode_ioctl_flags {%d} -> "
+ "\r\n i: %d"
+ "\r\n found flag: %T (%d)"
+ "\r\n remaining flags: %d"
+ "\r\n", descP->sock, i, eflag, flag, flags) );
+
+ TARRAY_ADD(ta, eflag);
+ }
+ }
+ if (flags != 0) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_ioctl_flags {%d} -> unknown flag(s): %d"
+ "\r\n", descP->sock, flags) );
+
+ TARRAY_ADD(ta, MKI(env, flags));
+ }
+
+ TARRAY_TOLIST(ta, env, &eflags);
+ }
+
+
+ SSDBG( descP, ("UNIX-ESSIO", "encode_ioctl_flags -> done with"
+ "\r\n Flags: %T (%d)"
+ "\r\n", eflags, flags) );
+
+ return esock_make_ok2(env, eflags);
+}
+
+
+static
+BOOLEAN_T decode_ioctl_sockaddr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eaddr,
+ ESockAddress* addr)
+{
+ SOCKLEN_T addrLen;
+ BOOLEAN_T result;
+
+ result = esock_decode_sockaddr(env, eaddr, (ESockAddress*) addr, &addrLen);
+
+ VOID(addrLen);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "decode_ioctl_sockaddr {%d} -> decode result: %s"
+ "\r\n", descP->sock, B2S(result)) );
+
+ return result;
+}
+
+
+static
+BOOLEAN_T decode_ioctl_mtu(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM emtu,
+ int* mtu)
+{
+ BOOLEAN_T result;
+
+ if (! GET_INT(env, emtu, mtu)) {
+ result = FALSE;
+ } else {
+ result = TRUE;
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "decode_ioctl_mtu {%d} -> decode result: %s"
+ "\r\n", descP->sock, B2S(result)) );
+
+ return result;
+}
+
+
+#if defined(SIOCSIFTXQLEN)
+static
+BOOLEAN_T decode_ioctl_txqlen(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM etxqlen,
+ int* txqlen)
+{
+ return decode_ioctl_ivalue(env, descP, etxqlen, txqlen);
+}
+#endif
+
+/* All uses of the function should be added. For instance:
+ * #if defined(SIOCGIFTXQLEN) || defined(FOOBAR) || defined(YXA)
+ */
+#if defined(SIOCGIFTXQLEN)
+static
+BOOLEAN_T decode_ioctl_ivalue(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eivalue,
+ int* ivalue)
+{
+ BOOLEAN_T result;
+
+ if (! GET_INT(env, eivalue, ivalue)) {
+ result = FALSE;
+ } else {
+ result = TRUE;
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "decode_ioctl_ivalue {%d} -> decode result: %s"
+ "\r\n", descP->sock, B2S(result)) );
+
+ return result;
+}
+#endif
+
+
+static
+BOOLEAN_T decode_ioctl_flags(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eflags,
+ short* flags)
+{
+ ERL_NIF_TERM key, value;
+ ErlNifMapIterator iter;
+ int tmpFlags = (int) *flags; // Current value
+ int flag;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "decode_ioctl_flags {%d} -> entry with"
+ "\r\n flags: %d"
+ "\r\n",
+ descP->sock, tmpFlags) );
+
+ enif_map_iterator_create(env, eflags, &iter, ERL_NIF_MAP_ITERATOR_FIRST);
+
+ while (enif_map_iterator_get_pair(env, &iter, &key, &value)) {
+
+ /* Convert key (eflag) to int */
+ if (! GET_INT(env, key, &flag)) {
+ enif_map_iterator_destroy(env, &iter);
+ return FALSE;
+ }
+
+ // Update flag
+ if (COMPARE(value, esock_atom_true) == 0) {
+ SSDBG( descP,
+ ("UNIX-ESSIO", "decode_ioctl_flags {%d} -> set %d\r\n",
+ descP->sock, flag) );
+ tmpFlags |= flag;
+ } else {
+ SSDBG( descP,
+ ("UNIX-ESSIO", "decode_ioctl_flags {%d} -> reset %d\r\n",
+ descP->sock, flag) );
+ tmpFlags &= ~flag;
+ }
+
+ enif_map_iterator_next(env, &iter);
+ }
+
+ enif_map_iterator_destroy(env, &iter);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "decode_ioctl_flags {%d} -> done with"
+ "\r\n (new) flags: %d"
+ "\r\n",
+ descP->sock, tmpFlags) );
+
+ *flags = (short) tmpFlags;
+
+ return TRUE;
+}
+
+
+static
+ERL_NIF_TERM encode_ioctl_ivalue(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int ivalue)
+{
+ ERL_NIF_TERM eivalue = MKI(env, ivalue);
+
+ SSDBG( descP, ("UNIX-ESSIO", "encode_ioctl_ivalue -> done with"
+ "\r\n iValue: %T (%d)"
+ "\r\n", eivalue, ivalue) );
+
+ return esock_make_ok2(env, eivalue);;
+}
+
+static
+ERL_NIF_TERM encode_ioctl_ifconf_ifreq(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ struct ifreq* ifrP)
+{
+ ERL_NIF_TERM ename, eaddr;
+
+ ESOCK_ASSERT( ifrP != NULL );
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_ioctl_ifconf_ifreq -> encode name\r\n") );
+ ename = encode_ioctl_ifreq_name(env, ifrP->ifr_name);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_ioctl_ifconf_ifreq -> encode sockaddr\r\n") );
+ eaddr = encode_ioctl_ifreq_sockaddr(env, &ifrP->ifr_addr);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_ioctl_ifconf_ifreq -> make ifreq map with"
+ "\r\n Name: %T"
+ "\r\n Sock Addr: %T"
+ "\r\n", ename, eaddr) );
+ return make_ifreq(env, ename, esock_atom_addr, eaddr);
+}
+
+static
+ERL_NIF_TERM encode_ioctl_ifreq_name(ErlNifEnv* env,
+ char* name)
+{
+ return ((name == NULL) ? esock_atom_undefined : MKS(env, name));
+}
+
+static
+ERL_NIF_TERM encode_ioctl_ifreq_sockaddr(ErlNifEnv* env, struct sockaddr* sa)
+{
+ ERL_NIF_TERM esa;
+
+ if (sa != NULL) {
+
+ esock_encode_sockaddr(env, (ESockAddress*) sa, -1, &esa);
+
+ } else {
+
+ esa = esock_atom_undefined;
+
+ }
+
+ return esa;
+}
+
+
+/* The ifreq structure *always* contain a name
+ * and *one* other element. The second element
+ * depend on the ioctl request.
+ */
+static
+ERL_NIF_TERM make_ifreq(ErlNifEnv* env,
+ ERL_NIF_TERM name,
+ ERL_NIF_TERM key2,
+ ERL_NIF_TERM val2)
+{
+ ERL_NIF_TERM keys[2];
+ ERL_NIF_TERM vals[2];
+ ERL_NIF_TERM res;
+
+ keys[0] = esock_atom_name;
+ vals[0] = name;
+
+ keys[1] = key2;
+ vals[1] = val2;
+
+ ESOCK_ASSERT( MKMA(env, keys, vals, NUM(keys), &res) );
+
+ return res;
+}
+
+
+
+
+/* ----------------------------------------------------------------------
+ * U t i l i t y F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+/* *** send_check_writer ***
+ *
+ * Checks if we have a current writer and if that is us.
+ * If not (current writer), then we must be made to wait
+ * for our turn. This is done by pushing us unto the writer queue.
+ */
+static
+BOOLEAN_T send_check_writer(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ERL_NIF_TERM* checkResult)
+{
+ if (descP->currentWriterP != NULL) {
+ ErlNifPid caller;
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (COMPARE_PIDS(&descP->currentWriter.pid, &caller) != 0) {
+ /* Not the "current writer", so (maybe) push onto queue */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "send_check_writer {%d} -> not (current) writer"
+ "\r\n ref: %T"
+ "\r\n", descP->sock, ref) );
+
+ if (! esock_writer_search4pid(env, descP, &caller)) {
+ esock_writer_push(env, descP, caller, ref, NULL);
+ *checkResult = esock_atom_select;
+ } else {
+ /* Writer already in queue */
+ *checkResult = esock_raise_invalid(env, esock_atom_state);
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "send_check_writer {%d} -> queue (push) result: %T\r\n"
+ "\r\n ref: %T"
+ "\r\n", descP->sock, *checkResult, ref) );
+
+ return FALSE;
+ }
+ }
+
+ // Does not actually matter in this case, but ...
+ *checkResult = esock_atom_ok;
+
+ return TRUE;
+}
+
+
+/* *** send_check_result ***
+ *
+ * Check the result of a socket send (send, sendto and sendmsg) call.
+ * If a "complete" send has been made, the next (waiting) writer will be
+ * scheduled (if there is one).
+ * If we did not manage to send the entire package, make another select,
+ * so that we can be informed when we can make another try (to send the rest),
+ * and return with the amount we actually managed to send (its up to the caller
+ * (that is the erlang code) to figure out hust much is left to send).
+ * If the write fail, we give up and return with the appropriate error code.
+ *
+ * What about the remaining writers!!
+ *
+ */
+static
+ERL_NIF_TERM send_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t send_result,
+ ssize_t dataSize,
+ BOOLEAN_T dataInTail,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef)
+{
+ ERL_NIF_TERM res;
+ BOOLEAN_T send_error;
+ int err;
+
+ send_error = ESOCK_IS_ERROR(send_result);
+ err = send_error ? sock_errno() : 0;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "send_check_result(%T) {%d} -> entry with"
+ "\r\n send_result: %ld"
+ "\r\n dataSize: %ld"
+ "\r\n err: %d"
+ "\r\n sendRef: %T"
+ "\r\n", sockRef, descP->sock,
+ (long) send_result, (long) dataSize, err, sendRef) );
+
+ if (send_error) {
+ /* Some kind of send failure - check what kind */
+ if ((err != EAGAIN) && (err != EINTR)) {
+ res = send_check_fail(env, descP, err, sockRef);
+ } else {
+ /* Ok, try again later */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "send_check_result(%T) {%d} -> try again"
+ "\r\n", sockRef, descP->sock) );
+
+ res = send_check_retry(env, descP, -1, sockRef, sendRef);
+ }
+ } else {
+ ssize_t written = send_result;
+ ESOCK_ASSERT( dataSize >= written );
+
+ if (written < dataSize) {
+ /* Not the entire package */
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "send_check_result(%T) {%d} -> "
+ "not entire package written (%d of %d)"
+ "\r\n", sockRef, descP->sock,
+ written, dataSize) );
+
+ res = send_check_retry(env, descP, written, sockRef, sendRef);
+ } else if (dataInTail) {
+ /* We sent all we could, but not everything (data in tail) */
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "send_check_result(%T) {%d} -> "
+ "not entire package written (%d but data in tail)"
+ "\r\n", sockRef, descP->sock,
+ written) );
+
+ res =
+ send_check_retry(env, descP, written, sockRef,
+ esock_atom_iov);
+ } else {
+ res = send_check_ok(env, descP, written, sockRef);
+ }
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "send_check_result(%T) {%d} -> done:"
+ "\r\n res: %T"
+ "\r\n", sockRef, descP->sock,
+ res) );
+
+ return res;
+}
+
+
+/* *** send_check_ok ***
+ *
+ * Processing done upon successful send.
+ */
+static
+ERL_NIF_TERM send_check_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t written,
+ ERL_NIF_TERM sockRef)
+{
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_pkg, &descP->writePkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_byte, &descP->writeByteCnt, written);
+ descP->writePkgMaxCnt += written;
+ if (descP->writePkgMaxCnt > descP->writePkgMax)
+ descP->writePkgMax = descP->writePkgMaxCnt;
+ descP->writePkgMaxCnt = 0;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "send_check_ok(%T) {%d} -> "
+ "everything written (%ld) - done\r\n",
+ sockRef, descP->sock, written) );
+
+ if (descP->currentWriterP != NULL) {
+ ESOCK_ASSERT( DEMONP("send_check_ok -> current writer",
+ env, descP, &descP->currentWriter.mon) == 0);
+ }
+ /*
+ * Ok, this write is done maybe activate the next (if any)
+ */
+ if (!esock_activate_next_writer(env, descP, sockRef)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "send_check_ok(%T) {%d} -> no more writers\r\n",
+ sockRef, descP->sock) );
+
+ descP->currentWriterP = NULL;
+ }
+
+ return esock_atom_ok;
+}
+
+
+/* *** send_check_fail ***
+ *
+ * Processing done upon failed send.
+ * An actual failure - we (and everyone waiting) give up.
+ */
+static
+ERL_NIF_TERM send_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM reason;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_fails, &descP->writeFails, 1);
+
+ reason = MKA(env, erl_errno_id(saveErrno));
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "send_check_fail(%T) {%d} -> error: %d (%T)\r\n",
+ sockRef, descP->sock, saveErrno, reason) );
+
+ if (saveErrno != EINVAL) {
+
+ /*
+ * We assume that anything other then einval (invalid input)
+ * is basically fatal (=> all waiting sends are aborted)
+ */
+
+ if (descP->currentWriterP != NULL) {
+
+ esock_requestor_release("send_check_fail",
+ env, descP, &descP->currentWriter);
+
+ send_error_waiting_writers(env, descP, sockRef, reason);
+
+ descP->currentWriterP = NULL;
+ }
+ }
+
+ return esock_make_error(env, reason);
+}
+
+
+/* *** send_error_waiting_writers ***
+ *
+ * Process all waiting writers when a fatal error has occurred.
+ * All waiting writers will be "aborted", that is a
+ * nif_abort message will be sent (with ref and reason).
+ */
+static
+void send_error_waiting_writers(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason)
+{
+ ESockRequestor req;
+
+ req.env = NULL; /* read by writer_pop before free */
+ while (esock_writer_pop(env, descP, &req)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "send_error_waiting_writers(%T) {%d} -> abort"
+ "\r\n pid: %T"
+ "\r\n reason: %T"
+ "\r\n",
+ sockRef, descP->sock, &req.pid, reason) );
+
+ esock_send_abort_msg(env, descP, sockRef, &req, reason);
+
+ (void) DEMONP("send_error_waiting_writers -> pop'ed writer",
+ env, descP, &req.mon);
+ }
+}
+
+
+/* *** send_check_retry ***
+ *
+ * Processing done upon incomplete or blocked send.
+ *
+ * We failed to write the *entire* packet (anything less
+ * then size of the packet, which is 0 <= written < sizeof
+ * packet, so schedule the rest for later.
+ */
+static
+ERL_NIF_TERM send_check_retry(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t written,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef)
+{
+ int sres;
+ ERL_NIF_TERM res;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "send_check_retry(%T) {%d} -> %ld"
+ "\r\n", sockRef, descP->sock, (long) written) );
+
+ if (written >= 0) {
+ descP->writePkgMaxCnt += written;
+
+ if (descP->type != SOCK_STREAM) {
+ /* Partial write for packet oriented socket
+ * - done with packet
+ */
+ if (descP->writePkgMaxCnt > descP->writePkgMax)
+ descP->writePkgMax = descP->writePkgMaxCnt;
+ descP->writePkgMaxCnt = 0;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_pkg, &descP->writePkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_byte, &descP->writeByteCnt, written);
+
+ if (descP->currentWriterP != NULL) {
+ ESOCK_ASSERT( DEMONP("send_check_retry -> current writer",
+ env, descP,
+ &descP->currentWriter.mon) == 0);
+ }
+ /*
+ * Ok, this write is done maybe activate the next (if any)
+ */
+ if (!esock_activate_next_writer(env, descP, sockRef)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "send_check_retry(%T) {%d} -> no more writers\r\n",
+ sockRef, descP->sock) );
+
+ descP->currentWriterP = NULL;
+ }
+
+ return esock_make_ok2(env, MKI64(env, written));
+ } /* else partial write for stream socket */
+ } /* else send would have blocked */
+
+ /* Register this process as current writer */
+
+ if (descP->currentWriterP == NULL) {
+ /* Register writer as current */
+
+ ESOCK_ASSERT( enif_self(env, &descP->currentWriter.pid) != NULL );
+ ESOCK_ASSERT( MONP("send_check_retry -> current writer",
+ env, descP,
+ &descP->currentWriter.pid,
+ &descP->currentWriter.mon) == 0 );
+ ESOCK_ASSERT( descP->currentWriter.env == NULL );
+
+ descP->currentWriter.env = esock_alloc_env("current-writer");
+ descP->currentWriter.ref =
+ CP_TERM(descP->currentWriter.env, sendRef);
+ descP->currentWriterP = &descP->currentWriter;
+ } else {
+ /* Overwrite current writer registration */
+ enif_clear_env(descP->currentWriter.env);
+ descP->currentWriter.ref = CP_TERM(descP->currentWriter.env, sendRef);
+ }
+
+ if (COMPARE(sendRef, esock_atom_iov) == 0) {
+ ESOCK_ASSERT( written >= 0 );
+ /* IOV iteration - do not select */
+ return MKT2(env, esock_atom_iov, MKI64(env, written));
+ }
+
+ /* Select write for this process */
+
+ sres = esock_select_write(env, descP->sock, descP, NULL, sockRef, sendRef);
+
+ if (sres < 0) {
+ ERL_NIF_TERM reason;
+
+ /* Internal select error */
+ ESOCK_ASSERT( DEMONP("send_check_retry - select error",
+ env, descP, &descP->currentWriter.mon) == 0);
+
+ /* Fail all queued writers */
+ reason = MKT2(env, esock_atom_select_write, MKI(env, sres));
+ esock_requestor_release("send_check_retry - select error",
+ env, descP, &descP->currentWriter);
+ send_error_waiting_writers(env, descP, sockRef, reason);
+ descP->currentWriterP = NULL;
+
+ res =
+ enif_raise_exception(env,
+ MKT2(env, esock_atom_select_write,
+ MKI(env, sres)));
+
+ } else {
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_waits, &descP->writeWaits, 1);
+
+ descP->writeState |= ESOCK_STATE_SELECTED;
+
+ if (written >= 0) {
+ /* Partial write success */
+ res = MKT2(env, esock_atom_select, MKI64(env, written));
+ } else {
+ /* No write - try again */
+ res = esock_atom_select;
+ }
+ }
+
+ return res;
+}
+
+
+/* *** Control message utility functions *** */
+
+/* +++ decode_cmsghdrs +++
+ *
+ * Decode a list of cmsg(). There can be 0 or more "blocks".
+ *
+ * Each element can either be a (erlang) map that needs to be decoded,
+ * or a (erlang) binary that just needs to be appended to the control
+ * buffer.
+ *
+ * Our "problem" is that we have no idea how much memory we actually need.
+ *
+ */
+
+static
+BOOLEAN_T decode_cmsghdrs(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eCMsg,
+ char* cmsgHdrBufP,
+ size_t cmsgHdrBufLen,
+ size_t* cmsgHdrBufUsed)
+{
+ ERL_NIF_TERM elem, tail, list;
+ char* bufP;
+ size_t rem, used, totUsed = 0;
+ unsigned int len;
+ int i;
+
+ SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdrs {%d} -> entry with"
+ "\r\n eCMsg: %T"
+ "\r\n cmsgHdrBufP: 0x%lX"
+ "\r\n cmsgHdrBufLen: %d"
+ "\r\n", descP->sock,
+ eCMsg, cmsgHdrBufP, cmsgHdrBufLen) );
+
+ if (! GET_LIST_LEN(env, eCMsg, &len))
+ return FALSE;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdrs {%d} -> list length: %d\r\n",
+ descP->sock, len) );
+
+ for (i = 0, list = eCMsg, rem = cmsgHdrBufLen, bufP = cmsgHdrBufP;
+ i < len; i++) {
+
+ SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdrs {%d} -> process elem %d:"
+ "\r\n (buffer) rem: %u"
+ "\r\n (buffer) totUsed: %u"
+ "\r\n", descP->sock, i, rem, totUsed) );
+
+ /* Extract the (current) head of the (cmsg hdr) list */
+ if (! GET_LIST_ELEM(env, list, &elem, &tail))
+ return FALSE;
+
+ used = 0; // Just in case...
+ if (! decode_cmsghdr(env, descP, elem, bufP, rem, &used))
+ return FALSE;
+
+ bufP = CHARP( ULONG(bufP) + used );
+ rem = SZT( rem - used );
+ list = tail;
+ totUsed += used;
+
+ }
+
+ *cmsgHdrBufUsed = totUsed;
+
+ SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdrs {%d} -> done"
+ "\r\n all %u ctrl headers processed"
+ "\r\n totUsed = %lu\r\n",
+ descP->sock, len, (unsigned long) totUsed) );
+
+ return TRUE;
+}
+
+
+/* +++ decode_cmsghdr +++
+ *
+ * Decode one cmsg(). Put the "result" into the buffer and advance the
+ * pointer (of the buffer) afterwards. Also update 'rem' accordingly.
+ * But before the actual decode, make sure that there is enough room in
+ * the buffer for the cmsg header (sizeof(*hdr) < rem).
+ *
+ * The eCMsg should be a map with three fields:
+ *
+ * level :: socket | protocol() | integer()
+ * type :: atom() | integer()
+ * What values are valid depend on the level
+ * data :: binary() | integer() | boolean()
+ * The type of the data depends on
+ * or level and type, but can be a binary,
+ * which means that the data is already coded.
+ * value :: term() Which is a term matching the decode function
+ */
+
+static
+BOOLEAN_T decode_cmsghdr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eCMsg,
+ char* bufP,
+ size_t rem,
+ size_t* used)
+{
+ ERL_NIF_TERM eLevel, eType, eData, eValue;
+ int level;
+
+ SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d} -> entry with"
+ "\r\n eCMsg: %T"
+ "\r\n", descP->sock, eCMsg) );
+
+ // Get 'level' field
+ if (! GET_MAP_VAL(env, eCMsg, esock_atom_level, &eLevel))
+ return FALSE;
+ SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d} -> eLevel: %T"
+ "\r\n", descP->sock, eLevel) );
+
+ // Get 'type' field
+ if (! GET_MAP_VAL(env, eCMsg, esock_atom_type, &eType))
+ return FALSE;
+ SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d} -> eType: %T"
+ "\r\n", descP->sock, eType) );
+
+ // Decode Level
+ if (! esock_decode_level(env, eLevel, &level))
+ return FALSE;
+ SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d}-> level: %d\r\n",
+ descP->sock, level) );
+
+ // Get 'data' field
+ if (! GET_MAP_VAL(env, eCMsg, esock_atom_data, &eData)) {
+
+ // Get 'value' field
+ if (! GET_MAP_VAL(env, eCMsg, esock_atom_value, &eValue))
+ return FALSE;
+ SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d} -> eValue: %T"
+ "\r\n", descP->sock, eValue) );
+
+ // Decode Value
+ if (! decode_cmsghdr_value(env, descP, level, eType, eValue,
+ bufP, rem, used))
+ return FALSE;
+
+ } else {
+
+ // Verify no 'value' field
+ if (GET_MAP_VAL(env, eCMsg, esock_atom_value, &eValue))
+ return FALSE;
+
+ SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d} -> eData: %T"
+ "\r\n", descP->sock, eData) );
+
+ // Decode Data
+ if (! decode_cmsghdr_data(env, descP, level, eType, eData,
+ bufP, rem, used))
+ return FALSE;
+ }
+
+ SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d}-> used: %lu\r\n",
+ descP->sock, (unsigned long) *used) );
+
+ return TRUE;
+}
+
+
+static
+BOOLEAN_T decode_cmsghdr_value(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ ERL_NIF_TERM eType,
+ ERL_NIF_TERM eValue,
+ char* bufP,
+ size_t rem,
+ size_t* usedP)
+{
+ int type;
+ struct cmsghdr* cmsgP = (struct cmsghdr *) bufP;
+ ESockCmsgSpec* cmsgTable;
+ ESockCmsgSpec* cmsgSpecP = NULL;
+ size_t num = 0;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdr_value {%d} -> entry \r\n"
+ " eType: %T\r\n"
+ " eValue: %T\r\n",
+ descP->sock, eType, eValue) );
+
+ // We have decode functions only for symbolic (atom) types
+ if (! IS_ATOM(env, eType)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdr_value {%d} -> FALSE:\r\n"
+ " eType not an atom\r\n",
+ descP->sock) );
+ return FALSE;
+ }
+
+ /* Try to look up the symbolic type
+ */
+ if (((cmsgTable = esock_lookup_cmsg_table(level, &num)) == NULL) ||
+ ((cmsgSpecP = esock_lookup_cmsg_spec(cmsgTable, num, eType)) == NULL) ||
+ (cmsgSpecP->decode == NULL)) {
+ /* We found no table for this level,
+ * we found no symbolic type in the level table,
+ * or no decode function for this type
+ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdr_value {%d} -> FALSE:\r\n"
+ " cmsgTable: %p\r\n"
+ " cmsgSpecP: %p\r\n",
+ descP->sock, cmsgTable, cmsgSpecP) );
+ return FALSE;
+ }
+
+ if (! cmsgSpecP->decode(env, eValue, cmsgP, rem, usedP)) {
+ // Decode function failed
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdr_value {%d} -> FALSE:\r\n"
+ " decode function failed\r\n",
+ descP->sock) );
+ return FALSE;
+ }
+
+ // Successful decode
+
+ type = cmsgSpecP->type;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdr_value {%d} -> TRUE:\r\n"
+ " level: %d\r\n"
+ " type: %d\r\n",
+ " *usedP: %lu\r\n",
+ descP->sock, level, type, (unsigned long) *usedP) );
+
+ cmsgP->cmsg_level = level;
+ cmsgP->cmsg_type = type;
+ return TRUE;
+}
+
+
+static
+BOOLEAN_T decode_cmsghdr_data(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ ERL_NIF_TERM eType,
+ ERL_NIF_TERM eData,
+ char* bufP,
+ size_t rem,
+ size_t* usedP)
+{
+ int type;
+ ErlNifBinary bin;
+ struct cmsghdr* cmsgP = (struct cmsghdr *) bufP;
+ ESockCmsgSpec* cmsgSpecP = NULL;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdr_data {%d} -> entry \r\n"
+ " eType: %T\r\n"
+ " eData: %T\r\n",
+ descP->sock, eType, eData) );
+
+ // Decode Type
+ if (! GET_INT(env, eType, &type)) {
+ ESockCmsgSpec* cmsgTable = NULL;
+ size_t num = 0;
+
+ /* Try to look up the symbolic (atom) type
+ */
+ if ((! IS_ATOM(env, eType)) ||
+ ((cmsgTable = esock_lookup_cmsg_table(level, &num)) == NULL) ||
+ ((cmsgSpecP = esock_lookup_cmsg_spec(cmsgTable, num, eType)) == NULL)) {
+ /* Type was not an atom,
+ * we found no table for this level,
+ * or we found no symbolic type in the level table
+ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdr_data {%d} -> FALSE:\r\n"
+ " cmsgTable: %p\r\n"
+ " cmsgSpecP: %p\r\n",
+ descP->sock, cmsgTable, cmsgSpecP) );
+ return FALSE;
+ }
+
+ type = cmsgSpecP->type;
+ }
+
+ // Decode Data
+ if (GET_BIN(env, eData, &bin)) {
+ void *p;
+
+ p = esock_init_cmsghdr(cmsgP, rem, bin.size, usedP);
+ if (p == NULL) {
+ /* No room for the data
+ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdr_data {%d} -> FALSE:\r\n"
+ " rem: %lu\r\n"
+ " bin.size: %lu\r\n",
+ descP->sock,
+ (unsigned long) rem,
+ (unsigned long) bin.size) );
+ return FALSE;
+ }
+
+ // Copy the binary data
+ sys_memcpy(p, bin.data, bin.size);
+
+ } else if ((! esock_cmsg_decode_int(env, eData, cmsgP, rem, usedP)) &&
+ (! esock_cmsg_decode_bool(env, eData, cmsgP, rem, usedP))) {
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdr_data {%d} -> FALSE\r\n",
+ descP->sock) );
+ return FALSE;
+ }
+
+ // Successful decode
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "decode_cmsghdr_data {%d} -> TRUE:\r\n"
+ " level: %d\r\n"
+ " type: %d\r\n"
+ " *usedP: %lu\r\n",
+ descP->sock, level, type, (unsigned long) *usedP) );
+
+ cmsgP->cmsg_level = level;
+ cmsgP->cmsg_type = type;
+ return TRUE;
+}
+
+
+/* +++ encode_msg +++
+ *
+ * Encode a msg() (recvmsg). In erlang its represented as
+ * a map, which has a specific set of attributes:
+ *
+ * addr (source address) - sockaddr()
+ * iov - [binary()]
+ * ctrl - [cmsg()]
+ * flags - msg_flags()
+ */
+
+static
+void encode_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ struct msghdr* msgHdrP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM* eMsg)
+{
+ ERL_NIF_TERM addr, iov, ctrl, flags;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_msg {%d} -> entry with"
+ "\r\n read: %ld"
+ "\r\n", descP->sock, (long) read) );
+
+ /* The address is not used if we are connected (unless, maybe,
+ * family is 'local'), so check (length = 0) before we try to encodel
+ */
+ if (msgHdrP->msg_namelen != 0) {
+ esock_encode_sockaddr(env,
+ (ESockAddress*) msgHdrP->msg_name,
+ msgHdrP->msg_namelen,
+ &addr);
+ } else {
+ addr = esock_atom_undefined;
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_msg {%d} -> encode iov"
+ "\r\n msg_iovlen: %lu"
+ "\r\n",
+ descP->sock,
+ (unsigned long) msgHdrP->msg_iovlen) );
+
+ esock_encode_iov(env, read,
+ msgHdrP->msg_iov, msgHdrP->msg_iovlen, dataBufP,
+ &iov);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "encode_msg {%d} -> try encode cmsgs\r\n",
+ descP->sock) );
+
+ encode_cmsgs(env, descP, ctrlBufP, msgHdrP, &ctrl);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "encode_msg {%d} -> try encode flags\r\n",
+ descP->sock) );
+
+ esock_encode_msg_flags(env, descP, msgHdrP->msg_flags, &flags);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_msg {%d} -> components encoded:"
+ "\r\n addr: %T"
+ "\r\n ctrl: %T"
+ "\r\n flags: %T"
+ "\r\n", descP->sock, addr, ctrl, flags) );
+
+ {
+ ERL_NIF_TERM keys[] = {esock_atom_iov,
+ esock_atom_ctrl,
+ esock_atom_flags,
+ esock_atom_addr};
+ ERL_NIF_TERM vals[] = {iov, ctrl, flags, addr};
+ size_t numKeys = NUM(keys);
+
+ ESOCK_ASSERT( numKeys == NUM(vals) );
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "encode_msg {%d} -> create map\r\n",
+ descP->sock) );
+
+ if (msgHdrP->msg_namelen == 0)
+ numKeys--; // No addr
+ ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eMsg) );
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "encode_msg {%d}-> map encoded\r\n",
+ descP->sock) );
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_msg {%d} -> done\r\n", descP->sock) );
+}
+
+
+
+/* +++ encode_cmsgs +++
+ *
+ * Encode a list of cmsg(). There can be 0 or more cmsghdr "blocks".
+ *
+ * Our "problem" is that we have no idea how many control messages
+ * we have.
+ *
+ * The cmsgHdrP arguments points to the start of the control data buffer,
+ * an actual binary. Its the only way to create sub-binaries. So, what we
+ * need to continue processing this is to turn that into an binary erlang
+ * term (which can then in turn be turned into sub-binaries).
+ *
+ * We need the cmsgBufP (even though cmsgHdrP points to it) to be able
+ * to create sub-binaries (one for each cmsg hdr).
+ *
+ * The TArray (term array) is created with the size of 128, which should
+ * be enough. But if its not, then it will be automatically realloc'ed during
+ * add. Once we are done adding hdr's to it, we convert the tarray to a list.
+ */
+
+static
+void encode_cmsgs(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifBinary* cmsgBinP,
+ struct msghdr* msgHdrP,
+ ERL_NIF_TERM* eCMsg)
+{
+ ERL_NIF_TERM ctrlBuf = MKBIN(env, cmsgBinP); // The *entire* binary
+ SocketTArray cmsghdrs = TARRAY_CREATE(128);
+ struct cmsghdr* firstP = CMSG_FIRSTHDR(msgHdrP);
+ struct cmsghdr* currentP;
+
+ SSDBG( descP, ("UNIX-ESSIO", "encode_cmsgs {%d} -> entry when"
+ "\r\n msg ctrl len: %d"
+ "\r\n (ctrl) firstP: 0x%lX"
+ "\r\n", descP->sock,
+ msgHdrP->msg_controllen, firstP) );
+
+ for (currentP = firstP;
+ /*
+ * In *old* versions of darwin, the CMSG_FIRSTHDR does not
+ * check the msg_controllen, so we do it here.
+ * We should really test this stuff during configure,
+ * but for now, this will have to do.
+ */
+#if defined(__DARWIN__)
+ (msgHdrP->msg_controllen >= sizeof(struct cmsghdr)) &&
+ (currentP != NULL);
+#else
+ (currentP != NULL);
+#endif
+ currentP = CMSG_NXTHDR(msgHdrP, currentP)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_cmsgs {%d} -> process cmsg header when"
+ "\r\n TArray Size: %d"
+ "\r\n", descP->sock, TARRAY_SZ(cmsghdrs)) );
+
+ /* MUST check this since on Linux the returned "cmsg" may actually
+ * go too far!
+ */
+ if (((CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP)) >
+ msgHdrP->msg_controllen) {
+
+ /* Ouch, fatal error - give up
+ * We assume we cannot trust any data if this is wrong.
+ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_cmsgs {%d} -> check failed when: "
+ "\r\n currentP: 0x%lX"
+ "\r\n (current) cmsg_len: %d"
+ "\r\n firstP: 0x%lX"
+ "\r\n => %d"
+ "\r\n msg ctrl len: %d"
+ "\r\n", descP->sock,
+ CHARP(currentP), currentP->cmsg_len, CHARP(firstP),
+ (CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP),
+ msgHdrP->msg_controllen) );
+
+ TARRAY_ADD(cmsghdrs, esock_atom_bad_data);
+ break;
+
+ } else {
+ unsigned char* dataP = UCHARP(CMSG_DATA(currentP));
+ size_t dataPos = dataP - cmsgBinP->data;
+ size_t dataLen =
+ (UCHARP(currentP) + currentP->cmsg_len) - dataP;
+ ERL_NIF_TERM
+ cmsgHdr,
+ keys[] =
+ {esock_atom_level,
+ esock_atom_type,
+ esock_atom_data,
+ esock_atom_value},
+ vals[NUM(keys)];
+ size_t numKeys = NUM(keys);
+ BOOLEAN_T have_value;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_cmsgs {%d} -> cmsg header data: "
+ "\r\n dataPos: %d"
+ "\r\n dataLen: %d"
+ "\r\n", descP->sock, dataPos, dataLen) );
+
+ vals[0] = esock_encode_level(env, currentP->cmsg_level);
+ vals[2] = MKSBIN(env, ctrlBuf, dataPos, dataLen);
+ have_value = esock_encode_cmsg(env,
+ currentP->cmsg_level,
+ currentP->cmsg_type,
+ dataP, dataLen, &vals[1], &vals[3]);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_cmsgs {%d} -> "
+ "\r\n %T: %T"
+ "\r\n %T: %T"
+ "\r\n %T: %T"
+ "\r\n", descP->sock,
+ keys[0], vals[0], keys[1], vals[1], keys[2], vals[2]) );
+ if (have_value)
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_cmsgs {%d} -> "
+ "\r\n %T: %T"
+ "\r\n", descP->sock, keys[3], vals[3]) );
+
+ /* Guard against cut-and-paste errors */
+ ESOCK_ASSERT( numKeys == NUM(vals) );
+ ESOCK_ASSERT( MKMA(env, keys, vals,
+ numKeys - (have_value ? 0 : 1), &cmsgHdr) );
+
+ /* And finally add it to the list... */
+ TARRAY_ADD(cmsghdrs, cmsgHdr);
+ }
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "encode_cmsgs {%d} -> cmsg headers processed when"
+ "\r\n TArray Size: %d"
+ "\r\n", descP->sock, TARRAY_SZ(cmsghdrs)) );
+
+ /* The tarray is populated - convert it to a list */
+ TARRAY_TOLIST(cmsghdrs, env, eCMsg);
+}
+
+
+
+/* *** Sendfile utility functions *** */
+
+/* Platform independent sendfile() function
+ *
+ * Return < 0 for terminal error
+ * 0 for done
+ * > 0 for retry with select
+ */
+#if defined(HAVE_SENDFILE)
+static
+int essio_sendfile(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ off_t offset,
+ size_t* countP,
+ int* errP)
+{
+ size_t pkgSize = 0; // Total sent in this call
+
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendfile {%d,%d} -> entry"
+ "\r\n sockRef: %T"
+ "\r\n",
+ descP->sock, descP->sendfileHandle, sockRef) );
+
+ for (;;) {
+ size_t chunk_size = (size_t) 0x20000000UL; // 0.5 GB
+ size_t bytes_sent;
+ ssize_t res;
+ int error;
+
+ /* *countP == 0 means send the whole file - use chunk size */
+ if ((*countP > 0) && (*countP < chunk_size))
+ chunk_size = *countP;
+
+ {
+ /* Platform dependent code:
+ * update and check offset, set and check bytes_sent, and
+ * set res to >= 0 and error to 0, or
+ * set res to < 0 and error to sock_errno()
+ */
+#if defined (__linux__)
+
+ off_t prev_offset;
+
+ prev_offset = offset;
+ res =
+ sendfile(descP->sock, descP->sendfileHandle,
+ &offset, chunk_size);
+ error = (res < 0) ? sock_errno() : 0;
+
+ ESOCK_ASSERT( offset >= prev_offset );
+ ESOCK_ASSERT( (off_t) chunk_size >= (offset - prev_offset) );
+ bytes_sent = (size_t) (offset - prev_offset);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_sendfile(%T) {%d,%d}"
+ "\r\n res: %d"
+ "\r\n bytes_sent: %lu"
+ "\r\n error: %d"
+ "\r\n",
+ sockRef, descP->sock, descP->sendfileHandle,
+ res, (unsigned long) bytes_sent, error) );
+
+#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__DARWIN__)
+
+ off_t sbytes;
+
+#if defined(__DARWIN__)
+ sbytes = (off_t) chunk_size;
+ res = (ssize_t)
+ sendfile(descP->sendfileHandle, descP->sock, offset,
+ &sbytes, NULL, 0);
+#else
+ sbytes = 0;
+ res = (ssize_t)
+ sendfile(descP->sendfileHandle, descP->sock, offset,
+ chunk_size, NULL, &sbytes, 0);
+#endif
+ error = (res < 0) ? sock_errno() : 0;
+
+ /* For an error return, we do not dare trust that sbytes is set
+ * unless the error is ERRNO_BLOCK or EINTR
+ * - the man page is to vague
+ */
+ if ((res < 0) && (error != ERRNO_BLOCK) && (error != EINTR)) {
+ sbytes = 0;
+ } else {
+ ESOCK_ASSERT( sbytes >= 0 );
+ ESOCK_ASSERT( (off_t) chunk_size >= sbytes );
+ ESOCK_ASSERT( offset + sbytes >= offset );
+ offset += sbytes;
+ }
+ bytes_sent = (size_t) sbytes;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_sendfile(%T) {%d,%d}"
+ "\r\n res: %d"
+ "\r\n bytes_sent: %lu"
+ "\r\n error: %d"
+ "\r\n",
+ sockRef, descP->sock, descP->sendfileHandle,
+ res, (unsigned long) bytes_sent, error) );
+
+#elif defined(__sun) && defined(__SVR4) && defined(HAVE_SENDFILEV)
+
+ sendfilevec_t sfvec[1];
+
+ sfvec[0].sfv_fd = descP->sendfileHandle;
+ sfvec[0].sfv_flag = 0;
+ sfvec[0].sfv_off = offset;
+ sfvec[0].sfv_len = chunk_size;
+
+ res = sendfilev(descP->sock, sfvec, NUM(sfvec), &bytes_sent);
+ error = (res < 0) ? sock_errno() : 0;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_sendfile(%T) {%d,%d}"
+ "\r\n res: %d"
+ "\r\n bytes_sent: %lu"
+ "\r\n error: %d"
+ "\r\n",
+ sockRef, descP->sock, descP->sendfileHandle,
+ res, (unsigned long) bytes_sent, error) );
+
+ if ((res < 0) && (error == EINVAL)) {
+ /* On e.b SunOS 5.10 using sfv_len > file size
+ * lands here - we regard this as a successful send.
+ * All other causes for EINVAL are avoided,
+ * except for .sfv_fd not seekable, which would
+ * give bytes_sent == 0 that we would interpret
+ * as end of file, which is kind of true.
+ */
+ res = 0;
+ }
+ ESOCK_ASSERT( chunk_size >= bytes_sent );
+ ESOCK_ASSERT( offset + bytes_sent >= offset );
+ offset += bytes_sent;
+
+#else
+#error "Unsupported sendfile syscall; update configure test."
+#endif
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_sendfile,
+ &descP->sendfileCountersP->cnt, 1);
+
+ if (bytes_sent != 0) {
+
+ pkgSize += bytes_sent;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_sendfile_pkg,
+ &descP->sendfileCountersP->pkg,
+ 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_sendfile_byte,
+ &descP->sendfileCountersP->byteCnt,
+ bytes_sent);
+
+ if (pkgSize > descP->sendfileCountersP->pkgMax)
+ descP->sendfileCountersP->pkgMax = pkgSize;
+ if ((descP->sendfileCountersP->maxCnt += bytes_sent)
+ > descP->sendfileCountersP->max)
+ descP->sendfileCountersP->max =
+ descP->sendfileCountersP->maxCnt;
+ }
+
+ /* *countP == 0 means send whole file */
+ if (*countP > 0) {
+
+ *countP -= bytes_sent;
+
+ if (*countP == 0) { // All sent
+ *countP = pkgSize;
+ return 0;
+ }
+ }
+
+ if (res < 0) {
+ if (error == ERRNO_BLOCK) {
+ *countP = pkgSize;
+ return 1;
+ }
+ if (error == EINTR)
+ continue;
+ *errP = error;
+ return -1;
+ }
+
+ if (bytes_sent == 0) { // End of input file
+ *countP = pkgSize;
+ return 0;
+ }
+ }
+ } // for (;;)
+}
+
+
+static
+ERL_NIF_TERM essio_sendfile_errno(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ int err)
+{
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(err));
+
+ return essio_sendfile_error(env, descP, sockRef, reason);
+}
+
+
+static
+ERL_NIF_TERM essio_sendfile_error(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason)
+{
+ SSDBG( descP, ("UNIX-ESSIO",
+ "essio_sendfile_error {%d} -> entry"
+ "\r\n sockRef: %T"
+ "\r\n reason: %T"
+ "\r\n", descP->sock, sockRef, reason) );
+
+ if (descP->sendfileCountersP == NULL) {
+ descP->sendfileCountersP = MALLOC(sizeof(ESockSendfileCounters));
+ *descP->sendfileCountersP = initESockSendfileCounters;
+ }
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_sendfile_fails,
+ &descP->sendfileCountersP->fails, 1);
+
+ /* XXX Should we have special treatment for EINVAL,
+ * such as to only fail current operation and activate
+ * the next from the queue?
+ */
+
+ if (descP->currentWriterP != NULL) {
+
+ (void) DEMONP("essio_sendfile_error",
+ env, descP, &descP->currentWriter.mon);
+
+ /* Fail all queued writers */
+ esock_requestor_release("essio_sendfile_error",
+ env, descP, &descP->currentWriter);
+ send_error_waiting_writers(env, descP, sockRef, reason);
+ descP->currentWriterP = NULL;
+
+ }
+
+ return esock_make_error(env, reason);
+}
+
+static
+ERL_NIF_TERM essio_sendfile_select(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ size_t count)
+{
+ int sres;
+
+ /* Select write for this process */
+ sres = esock_select_write(env, descP->sock, descP, NULL, sockRef, sendRef);
+ if (sres < 0) {
+ ERL_NIF_TERM reason;
+
+ /* Internal select error */
+ (void) DEMONP("essio_sendfile_select - failed",
+ env, descP, &descP->currentWriter.mon);
+
+ /* Fail all queued writers */
+ reason = MKT2(env, esock_atom_select_write, MKI(env, sres));
+ esock_requestor_release("essio_sendfile_select - failed",
+ env, descP, &descP->currentWriter);
+ send_error_waiting_writers(env, descP, sockRef, reason);
+ descP->currentWriterP = NULL;
+
+ (void) close(descP->sendfileHandle);
+ descP->sendfileHandle = INVALID_HANDLE;
+
+ return enif_raise_exception(env, reason);
+
+ } else {
+ ErlNifUInt64 bytes_sent;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_sendfile_select {%d} -> selected"
+ "\r\n sockRef: %T"
+ "\r\n sendRef: %T"
+ "\r\n count: %lu"
+ "\r\n", descP->sock, sockRef, sendRef, (unsigned long) count) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_sendfile_waits,
+ &descP->sendfileCountersP->waits,
+ 1);
+
+ descP->writeState |= ESOCK_STATE_SELECTED;
+ bytes_sent = (ErlNifUInt64) count;
+
+ return MKT2(env, esock_atom_select, MKUI64(env, bytes_sent));
+ }
+}
+
+
+static
+ERL_NIF_TERM essio_sendfile_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ size_t count)
+{
+ ErlNifUInt64 bytes_sent64u;
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_sendfile_ok {%d} -> entry when done"
+ "\r\n sockRef: %T"
+ "\r\n written: %lu"
+ "\r\n", descP->sock, sockRef, (unsigned long) count) );
+
+ if (descP->currentWriterP != NULL) {
+
+ (void) DEMONP("essio_sendfile_ok -> current writer",
+ env, descP, &descP->currentWriter.mon);
+
+ /*
+ * Ok, this write is done maybe activate the next (if any)
+ */
+ if (! esock_activate_next_writer(env, descP, sockRef)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_sendfile_ok {%d} -> no more writers"
+ "\r\n sockRef: %T"
+ "\r\n",
+ descP->sock, sockRef) );
+
+ descP->currentWriterP = NULL;
+ }
+ }
+
+ descP->writePkgMaxCnt = 0;
+ bytes_sent64u = (ErlNifUInt64) count;
+
+ (void) close(descP->sendfileHandle);
+ descP->sendfileHandle = INVALID_HANDLE;
+
+ return esock_make_ok2(env, MKUI64(env, bytes_sent64u));
+}
+
+#endif // #ifdef HAVE_SENDFILE
+
+
+/* ====================================================================
+ *
+ * NIF (I/O backend) Resource callback functions: dtor, stop and down
+ *
+ * ====================================================================
+ */
+
+extern
+void essio_dtor(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ SGDBG( ("UNIX-ESSIO", "dtor -> entry\r\n") );
+
+ if (IS_SELECTED(descP)) {
+ /* We have used the socket in the select machinery,
+ * so we must have closed it properly to get here
+ */
+ ESOCK_ASSERT( IS_CLOSED(descP->readState) );
+ ESOCK_ASSERT( IS_CLOSED(descP->writeState) );
+ ESOCK_ASSERT( descP->sock == INVALID_SOCKET );
+ } else {
+ /* The socket is only opened, should be safe to close nonblocking */
+ (void) sock_close(descP->sock);
+ descP->sock = INVALID_SOCKET;
+ }
+
+ SGDBG( ("UNIX-ESSIO", "dtor -> set state and pattern\r\n") );
+ descP->readState |= (ESOCK_STATE_DTOR | ESOCK_STATE_CLOSED);
+ descP->writeState |= (ESOCK_STATE_DTOR | ESOCK_STATE_CLOSED);
+ descP->pattern = (ESOCK_DESC_PATTERN_DTOR | ESOCK_STATE_CLOSED);
+
+ esock_free_env("dtor reader", descP->currentReader.env);
+ descP->currentReader.env = NULL;
+
+ esock_free_env("dtor writer", descP->currentWriter.env);
+ descP->currentWriter.env = NULL;
+
+ esock_free_env("dtor acceptor", descP->currentAcceptor.env);
+ descP->currentAcceptor.env = NULL;
+
+ SGDBG( ("UNIX-ESSIO", "dtor -> try free readers request queue\r\n") );
+ esock_free_request_queue(&descP->readersQ);
+
+ SGDBG( ("UNIX-ESSIO", "dtor -> try free writers request queue\r\n") );
+ esock_free_request_queue(&descP->writersQ);
+
+ SGDBG( ("UNIX-ESSIO", "dtor -> try free acceptors request queue\r\n") );
+ esock_free_request_queue(&descP->acceptorsQ);
+
+#ifdef HAVE_SENDFILE
+ ESOCK_ASSERT( descP->sendfileHandle == INVALID_HANDLE );
+ if (descP->sendfileCountersP != NULL) {
+ FREE(descP->sendfileCountersP);
+ descP->sendfileCountersP = NULL;
+ }
+#endif
+
+ esock_free_env("dtor close env", descP->closeEnv);
+ descP->closeEnv = NULL;
+
+ esock_free_env("dtor meta env", descP->meta.env);
+ descP->meta.env = NULL;
+
+ SGDBG( ("UNIX-ESSIO", "dtor -> done\r\n") );
+}
+
+
+extern
+void essio_stop(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+#ifdef HAVE_SENDFILE
+ if (descP->sendfileCountersP != NULL) {
+ ESockSendfileCounters* cntP = descP->sendfileCountersP;
+
+ SSDBG( descP, ("UNIX-ESSIO", "esock_stop(%d) -> sendfileCounters:"
+ "\r\n cnt: %lu"
+ "\r\n byteCnt: %lu"
+ "\r\n fails: %lu"
+ "\r\n max: %lu"
+ "\r\n pkg: %lu"
+ "\r\n pkgMax %lu"
+ "\r\n tries: %lu"
+ "\r\n waits: %lu"
+ "\r\n",
+ descP->sock,
+ (unsigned long) cntP->cnt,
+ (unsigned long) cntP->byteCnt,
+ (unsigned long) cntP->fails,
+ (unsigned long) cntP->max,
+ (unsigned long) cntP->pkg,
+ (unsigned long) cntP->pkgMax,
+ (unsigned long) cntP->tries,
+ (unsigned long) cntP->waits) );
+ }
+#endif
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ *
+ * Inform waiting Closer, or close socket
+ *
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+ if (! enif_is_pid_undefined(&descP->closerPid)) {
+ /* We have a waiting closer process after nif_close()
+ * - send message to trigger nif_finalize_close()
+ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "esock_stop(%d) -> send close msg to %T\r\n",
+ descP->sock, MKPID(env, &descP->closerPid)) );
+
+ esock_send_close_msg(env, descP, &descP->closerPid);
+ /* Message send frees closeEnv */
+ descP->closeEnv = NULL;
+ descP->closeRef = esock_atom_undefined;
+
+ } else {
+ int err;
+
+ /* We do not have a closer process
+ * - have to do an unclean (non blocking) close */
+
+#ifdef HAVE_SENDFILE
+ if (descP->sendfileHandle != INVALID_HANDLE)
+ esock_send_sendfile_deferred_close_msg(env, descP);
+#endif
+
+ err = esock_close_socket(env, descP, FALSE);
+
+ if (err != 0)
+ esock_warning_msg("[UNIX-ESSIO] Failed closing socket without "
+ "closer process: "
+ "\r\n Controlling Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Errno: %d (%T)"
+ "\r\n",
+ descP->ctrlPid, descP->sock,
+ err, MKA(env, erl_errno_id(err)));
+ }
+
+}
+
+
+/* A 'down' has occured.
+ * Check the possible processes we monitor in turn:
+ * closer, controlling process (owner), connector, reader, acceptor and writer.
+ *
+ */
+extern
+void essio_down(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP)
+{
+ if (COMPARE_PIDS(&descP->closerPid, pidP) == 0) {
+
+ /* The closer process went down
+ * - it will not call nif_finalize_close
+ */
+
+ enif_set_pid_undefined(&descP->closerPid);
+
+ if (MON_EQ(&descP->closerMon, monP)) {
+ MON_INIT(&descP->closerMon);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down {%d} -> closer process exit\r\n",
+ descP->sock) );
+
+ } else {
+ // The owner is the closer so we used its monitor
+
+ ESOCK_ASSERT( MON_EQ(&descP->ctrlMon, monP) );
+ MON_INIT(&descP->ctrlMon);
+ enif_set_pid_undefined(&descP->ctrlPid);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down {%d} -> closer controlling process exit\r\n",
+ descP->sock) );
+ }
+
+ /* Since the closer went down there was one,
+ * hence esock_close() must have run or scheduled esock_stop(),
+ * or the socket has never been "selected" upon.
+ */
+
+ if (descP->closeEnv == NULL) {
+ int err;
+
+ /* Since there is no closeEnv,
+ * esock_close() did not schedule esock_stop()
+ * and is about to call esock_finalize_close() but died,
+ * or esock_stop() has run, sent close_msg to the closer
+ * and cleared ->closeEnv but the closer died
+ * - we have to do an unclean (non blocking) socket close here
+ */
+
+#ifdef HAVE_SENDFILE
+ if (descP->sendfileHandle != INVALID_HANDLE)
+ esock_send_sendfile_deferred_close_msg(env, descP);
+#endif
+
+ err = esock_close_socket(env, descP, FALSE);
+ if (err != 0)
+ esock_warning_msg("[UNIX-ESSIO] "
+ "Failed closing socket for terminating "
+ "closer process: "
+ "\r\n Closer Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Errno: %d (%T)"
+ "\r\n",
+ MKPID(env, pidP), descP->sock,
+ err, MKA(env, erl_errno_id(err)));
+ } else {
+ /* Since there is a closeEnv esock_stop() has not run yet
+ * - when it finds that there is no closer process
+ * it will close the socket and ignore the close_msg
+ */
+ esock_clear_env("essio_down - close-env", descP->closeEnv);
+ esock_free_env("essio_down - close-env", descP->closeEnv);
+ descP->closeEnv = NULL;
+ descP->closeRef = esock_atom_undefined;
+ }
+
+ } else if (MON_EQ(&descP->ctrlMon, monP)) {
+ MON_INIT(&descP->ctrlMon);
+ /* The owner went down */
+ enif_set_pid_undefined(&descP->ctrlPid);
+
+ if (IS_OPEN(descP->readState)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down {%d} -> controller process exit"
+ "\r\n initiate close\r\n",
+ descP->sock) );
+
+ essio_down_ctrl(env, descP, pidP);
+
+ descP->readState |= ESOCK_STATE_CLOSING;
+ descP->writeState |= ESOCK_STATE_CLOSING;
+
+ } else {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down {%d} -> controller process exit"
+ "\r\n already closed or closing\r\n",
+ descP->sock) );
+
+ }
+
+ } else if (descP->connectorP != NULL &&
+ MON_EQ(&descP->connector.mon, monP)) {
+ MON_INIT(&descP->connector.mon);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down {%d} -> connector process exit\r\n",
+ descP->sock) );
+
+ /* connectorP is only set during connection.
+ * Forget all about the ongoing connection.
+ * We might end up connected, but the process that initiated
+ * the connection has died and will never know
+ */
+
+ esock_requestor_release("esock_down->connector",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+ descP->writeState &= ~ESOCK_STATE_CONNECTING;
+
+ } else {
+ ERL_NIF_TERM sockRef = enif_make_resource(env, descP);
+
+ /* check all operation queue(s): acceptor, writer and reader.
+ *
+ * Is it really any point in doing this if the socket is closed?
+ *
+ */
+
+ if (IS_CLOSED(descP->readState)) {
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down(%T) {%d} -> stray down: %T\r\n",
+ sockRef, descP->sock, pidP) );
+ } else {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down(%T) {%d} -> other process term\r\n",
+ sockRef, descP->sock) );
+
+ if (descP->currentReaderP != NULL)
+ essio_down_reader(env, descP, sockRef, pidP, monP);
+ if (descP->currentAcceptorP != NULL)
+ essio_down_acceptor(env, descP, sockRef, pidP, monP);
+ if (descP->currentWriterP != NULL)
+ essio_down_writer(env, descP, sockRef, pidP, monP);
+ }
+ }
+
+}
+
+
+/* ==================================================================== */
+
+/* *** Recv/recvfrom/recvmsg utility functions *** */
+
+/* *** recv_check_reader ***
+ *
+ * Checks if we have a current reader and if that is us. If not,
+ * then we must be made to wait for our turn. This is done by pushing
+ * us unto the reader queue.
+ * Note that we do *not* actually initiate the currentReader structure
+ * here, since we do not actually know yet if we need to! We do that in
+ * the [recv|recvfrom|recvmsg]_check_result function.
+ */
+
+static
+BOOLEAN_T recv_check_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ERL_NIF_TERM* checkResult)
+{
+ if (descP->currentReaderP != NULL) {
+ ErlNifPid caller;
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (COMPARE_PIDS(&descP->currentReader.pid, &caller) != 0) {
+ /* Not the "current reader", so (maybe) push onto queue */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_reader {%d} -> not (current) reader"
+ "\r\n ref: %T"
+ "\r\n", descP->sock, ref) );
+
+ if (! esock_reader_search4pid(env, descP, &caller)) {
+ if (COMPARE(ref, esock_atom_zero) == 0)
+ goto done_ok;
+ esock_reader_push(env, descP, caller, ref, NULL);
+ *checkResult = esock_atom_select;
+ } else {
+ /* Reader already in queue */
+ *checkResult = esock_raise_invalid(env, esock_atom_state);
+ }
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_reader {%d} -> queue (push) result: %T\r\n",
+ descP->sock, *checkResult) );
+
+ return FALSE;
+ }
+ }
+
+ done_ok:
+ // Does not actually matter in this case, but ...
+ *checkResult = esock_atom_ok;
+ return TRUE;
+}
+
+
+/* *** recv_check_full ***
+ *
+ * This function is called if we filled the allocated buffer.
+ * But are we done yet?
+ *
+ * toRead = 0 means: Give me everything you have => maybe
+ * toRead > 0 means: Yes
+ */
+
+static
+ERL_NIF_TERM recv_check_full(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ssize_t toRead,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM res;
+
+ if ((toRead == 0) &&
+ (descP->type == SOCK_STREAM)) {
+
+ /* +++ Give us everything you have got => *
+ * (maybe) needs to continue +++ */
+
+ /* Send up each chunk of data for each of the read
+ * and let the erlang code assemble it: {more, Bin}
+ * (when complete it should return {ok, Bin}).
+ * We need to read at least one more time to be sure if its
+ * done...
+ *
+ * Also, we need to check if the rNumCnt has reached its max (rNum),
+ * in which case we will assume the read to be done!
+ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_full(%T) {%d} -> shall we continue reading?"
+ "\r\n read: %ld"
+ "\r\n rNum: %u"
+ "\r\n rNumCnt: %u"
+ "\r\n", sockRef, descP->sock,
+ (unsigned long) read, descP->rNum, descP->rNumCnt) );
+
+ res = recv_check_full_maybe_done(env, descP, read, bufP,
+ sockRef, recvRef);
+
+ } else {
+
+ /* +++ We got exactly as much as we requested => We are done +++ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_full(%T) {%d} -> [%ld] "
+ "we got exactly what we could fit\r\n",
+ sockRef, descP->sock, (long) toRead) );
+
+ res = recv_check_full_done(env, descP, read, bufP, sockRef);
+
+ }
+
+ return res;
+
+}
+
+
+/* *** recv_check_full_maybe_done ***
+ *
+ * Send up each chunk of data for each of the read
+ * and let the erlang code assemble it: {more, Bin}
+ * (when complete it should return {ok, Bin}).
+ * We need to read at least one more time to be sure if its
+ * done...
+ *
+ * Also, we need to check if the rNumCnt has reached its max (rNum),
+ * in which case we will assume the read to be done!
+ */
+
+static
+ERL_NIF_TERM recv_check_full_maybe_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+ descP->readPkgMaxCnt += read;
+
+ descP->rNumCnt++;
+ if (descP->rNumCnt >= descP->rNum) {
+
+ descP->rNumCnt = 0;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ if (descP->readPkgMaxCnt > descP->readPkgMax)
+ descP->readPkgMax = descP->readPkgMaxCnt;
+ descP->readPkgMaxCnt = 0;
+
+ recv_update_current_reader(env, descP, sockRef);
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+
+ return esock_make_ok2(env, MKBIN(env, bufP));
+
+ }
+
+ /* Yes, we *do* need to continue reading */
+
+ recv_init_current_reader(env, descP, recvRef);
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_full_maybe_done(%T) {%d} -> [%lu] "
+ "we are done for now - read more\r\n",
+ sockRef, descP->sock, (unsigned long)bufP->size) );
+
+ return MKT2(env, esock_atom_more, MKBIN(env, bufP));
+}
+
+
+
+/* *** recv_check_full_done ***
+ *
+ * A successful recv and we filled the buffer.
+ */
+
+static
+ERL_NIF_TERM recv_check_full_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM data;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ descP->readPkgMaxCnt += read;
+ if (descP->readPkgMaxCnt > descP->readPkgMax)
+ descP->readPkgMax = descP->readPkgMaxCnt;
+ descP->readPkgMaxCnt = 0;
+
+ recv_update_current_reader(env, descP, sockRef);
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(env, bufP);
+
+ return esock_make_ok2(env, data);
+}
+
+
+
+/* *** recv_check_fail ***
+ *
+ * Handle recv failure.
+ */
+
+static
+ERL_NIF_TERM recv_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int saveErrno,
+ ErlNifBinary* buf1P,
+ ErlNifBinary* buf2P,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM res;
+
+ FREE_BIN(buf1P);
+ if (buf2P != NULL) FREE_BIN(buf2P);
+
+ if (saveErrno == ECONNRESET) {
+
+ /* +++ Oops - closed +++ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_fail(%T) {%d} -> econnreset: closed"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, recvRef) );
+
+ // This is a bit overkill (to count here), but just in case...
+ ESOCK_CNT_INC(env, descP, sockRef, esock_atom_read_fails,
+ &descP->readFails, 1);
+
+ res = recv_check_fail_econnreset(env, descP, sockRef, recvRef);
+
+ } else if ((saveErrno == ERRNO_BLOCK) ||
+ (saveErrno == EAGAIN)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_fail(%T) {%d} -> eagain"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, recvRef) );
+
+ if (COMPARE(recvRef, esock_atom_zero) == 0)
+ res = esock_atom_ok;
+ else
+ res = recv_check_retry(env, descP, sockRef, recvRef);
+
+ } else {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_fail(%T) {%d} -> errno: %d\r\n"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, saveErrno, recvRef) );
+
+ ESOCK_CNT_INC(env, descP, sockRef, esock_atom_read_fails,
+ &descP->readFails, 1);
+
+ res = recv_check_fail_gen(env, descP, saveErrno, sockRef);
+ }
+
+ return res;
+}
+
+
+/* *** recv_check_fail_gen ***
+ *
+ * The recv call had a "general" failure.
+ */
+
+static
+ERL_NIF_TERM recv_check_fail_gen(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
+
+ recv_error_current_reader(env, descP, sockRef, reason);
+
+ return esock_make_error(env, reason);
+}
+
+
+/* *** recv_check_fail_econnreset ***
+ *
+ * We detected that the socket was closed while reading.
+ * Inform current and waiting readers.
+ */
+
+static
+ERL_NIF_TERM recv_check_fail_econnreset(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(ECONNRESET));
+ ERL_NIF_TERM res = esock_make_error(env, reason);
+
+ /* <KOLLA>
+ *
+ * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING
+ * PROCESS, WE NEED TO INFORM IT!!!
+ *
+ * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!!
+ * HANDLED BY THE STOP (CALLBACK) FUNCTION?
+ *
+ * SINCE THIS IS A REMOTE CLOSE, WE DON'T NEED TO WAIT
+ * FOR OUTPUT TO BE WRITTEN (NO ONE WILL READ), JUST
+ * ABORT THE SOCKET REGARDLESS OF LINGER???
+ *
+ * </KOLLA>
+ */
+
+ recv_error_current_reader(env, descP, sockRef, reason);
+
+ return res;
+}
+
+
+/* *** recv_check_retry ***
+ *
+ * The recv call would have blocked, so retry.
+ */
+
+static
+ERL_NIF_TERM recv_check_retry(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ int sres;
+ ERL_NIF_TERM res;
+
+ descP->rNumCnt = 0;
+ recv_init_current_reader(env, descP, recvRef);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_retry(%T) {%d} -> SELECT for more"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, recvRef) );
+
+ if ((sres = esock_select_read(env, descP->sock, descP, NULL,
+ sockRef, recvRef)) < 0) {
+ /* Unlikely that any next reader will have better luck,
+ * but why not give them a shot - the queue will be cleared
+ */
+ recv_update_current_reader(env, descP, sockRef);
+
+ res = enif_raise_exception(env,
+ MKT2(env, esock_atom_select_read,
+ MKI(env, sres)));
+ } else {
+ descP->readState |= ESOCK_STATE_SELECTED;
+ res = esock_atom_select;
+ }
+
+ return res;
+}
+
+
+
+/* *** recv_check_partial ***
+ *
+ * Handle a successful recv which only partly filled the specified buffer.
+ */
+
+static
+ERL_NIF_TERM recv_check_partial(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ssize_t toRead,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM res;
+
+ if ((toRead == 0) ||
+ (descP->type != SOCK_STREAM) ||
+ (COMPARE(recvRef, esock_atom_zero) == 0)) {
+
+ /* +++ We got it all, but since we +++
+ * +++ did not fill the buffer, we +++
+ * +++ must split it into a sub-binary. +++
+ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_partial(%T) {%d} -> [%ld] split buffer"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, (long) toRead,
+ recvRef) );
+
+ res = recv_check_partial_done(env, descP, read, bufP, sockRef);
+
+ } else {
+ /* A stream socket with specified read size
+ * and not a polling read, we got a partial read
+ * - return a select result to initiate a retry
+ */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_check_partial(%T) {%d} -> [%ld]"
+ " only part of message - expect more"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, (long) toRead,
+ recvRef) );
+
+ res = recv_check_partial_part(env, descP, read,
+ bufP, sockRef, recvRef);
+ }
+
+ return res;
+}
+
+
+/* *** recv_check_partial_done ***
+ *
+ * A successful but only partial recv, which fulfilled the required read.
+ */
+
+static
+ERL_NIF_TERM recv_check_partial_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM data;
+
+ descP->rNumCnt = 0;
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ descP->readPkgMaxCnt += read;
+ if (descP->readPkgMaxCnt > descP->readPkgMax)
+ descP->readPkgMax = descP->readPkgMaxCnt;
+ descP->readPkgMaxCnt = 0;
+
+ recv_update_current_reader(env, descP, sockRef);
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(env, bufP);
+ data = MKSBIN(env, data, 0, read);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "recv_check_partial_done(%T) {%d} -> [%ld] done\r\n",
+ sockRef, descP->sock, (long) read) );
+
+ return esock_make_ok2(env, data);
+}
+
+
+/* *** recv_check_partial_part ***
+ *
+ * A successful but only partial recv, which only partly fulfilled
+ * the required read.
+ */
+
+static
+ERL_NIF_TERM recv_check_partial_part(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM res;
+ int sres;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ recv_init_current_reader(env, descP, recvRef);
+
+ /* SELECT for more data */
+
+ sres = esock_select_read(env, descP->sock, descP, NULL,
+ sockRef, recvRef);
+ if (sres < 0) {
+ /* Unlikely that any next reader will have better luck,
+ * but why not give them a shot - the queue will be cleared
+ */
+ recv_update_current_reader(env, descP, sockRef);
+
+ res = enif_raise_exception(env,
+ MKT2(env, esock_atom_select_read,
+ MKI(env, sres)));
+ } else {
+ ERL_NIF_TERM data;
+
+ descP->readState |= ESOCK_STATE_SELECTED;
+ data = MKBIN(env, bufP);
+ data = MKSBIN(env, data, 0, read);
+ res = MKT2(env, esock_atom_select, data);
+ }
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ return res;
+}
+
+
+/* *** recv_init_current_reader ***
+ *
+ * Initiate (maybe) the currentReader structure of the descriptor.
+ * Including monitoring the calling process.
+ */
+static
+void recv_init_current_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM recvRef)
+{
+ if (descP->currentReaderP == NULL) {
+
+ ESOCK_ASSERT( enif_self(env, &descP->currentReader.pid) != NULL );
+
+ ESOCK_ASSERT( MONP("recv_init_current_reader -> current reader",
+ env, descP,
+ &descP->currentReader.pid,
+ &descP->currentReader.mon) == 0);
+ ESOCK_ASSERT(!descP->currentReader.env);
+
+ descP->currentReader.env = esock_alloc_env("current-reader");
+ descP->currentReader.ref =
+ CP_TERM(descP->currentReader.env, recvRef);
+ descP->currentReaderP = &descP->currentReader;
+ } else {
+
+ /*
+ * This is a retry:
+ * We have done, for instance, recv(Sock, X), but only received Y < X.
+ * We then call recv again with size = X-Y. So, we then get a new ref.
+ *
+ * Make use of the existing environment
+ */
+
+ enif_clear_env(descP->currentReader.env);
+ descP->currentReader.ref = CP_TERM(descP->currentReader.env, recvRef);
+ }
+}
+
+
+/* *** recv_update_current_reader ***
+ *
+ * Demonitors the current reader process and pop's the reader queue.
+ * If there is a waiting (reader) process, then it will be assigned
+ * as the new current reader and a new (read) select will be done.
+ */
+
+static
+void recv_update_current_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef)
+{
+ if (descP->currentReaderP != NULL) {
+
+ ESOCK_ASSERT( DEMONP("recv_update_current_reader",
+ env, descP, &descP->currentReader.mon) == 0);
+
+ if (! esock_activate_next_reader(env, descP, sockRef)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "recv_update_current_reader(%T) {%d} -> no more readers\r\n",
+ sockRef, descP->sock) );
+
+ descP->currentReaderP = NULL;
+ }
+ }
+}
+
+
+/* *** recv_error_current_reader ***
+ *
+ * Process the current reader and any waiting readers
+ * when a read (fatal) error has occurred.
+ * All waiting readers will be "aborted", that is a
+ * nif_abort message will be sent (with ref and reason).
+ */
+
+static
+void recv_error_current_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason)
+{
+ if (descP->currentReaderP != NULL) {
+ ESockRequestor req;
+
+ esock_requestor_release("recv_error_current_reader",
+ env, descP, &descP->currentReader);
+
+ req.env = NULL; /* read by reader_pop before free */
+ while (esock_reader_pop(env, descP, &req)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO", "recv_error_current_reader(%T) {%d} -> abort"
+ "\r\n pid: %T"
+ "\r\n reason %T"
+ "\r\n", sockRef, descP->sock,
+ req.pid, reason) );
+
+ esock_send_abort_msg(env, descP, sockRef, &req, reason);
+
+ ESOCK_ASSERT( DEMONP("recv_error_current_reader -> pop'ed reader",
+ env, descP, &req.mon) == 0);
+ }
+
+ descP->currentReaderP = NULL;
+ }
+}
+
+
+/* *** essio_down_ctrl ***
+ *
+ * Stop after a downed controller (controlling process = owner process)
+ *
+ * This is 'extern' because its currently called from prim_socket_nif
+ * (esock_setopt_otp_ctrl_proc).
+ */
+extern
+void essio_down_ctrl(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pidP)
+{
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_down_ctrl {%d} ->"
+ "\r\n Pid: %T"
+ "\r\n", descP->sock, MKPID(env, pidP)) );
+
+ if (do_stop(env, descP)) {
+ /* esock_stop() is scheduled
+ * - it has to close the socket
+ */
+ SSDBG( descP,
+ ("UNIX-ESSIO", "essio_down_ctrl {%d} -> stop was scheduled\r\n",
+ descP->sock) );
+ } else {
+ int err;
+
+ /* Socket is not in the select machinery
+ * so esock_stop() will not be called
+ * - we have to do an unclean (non blocking) socket close here
+ */
+
+#ifdef HAVE_SENDFILE
+ if (descP->sendfileHandle != INVALID_HANDLE)
+ esock_send_sendfile_deferred_close_msg(env, descP);
+#endif
+
+ err = esock_close_socket(env, descP, FALSE);
+ if (err != 0)
+ esock_warning_msg("[UNIX-ESSIO] "
+ "Failed closing socket for terminating "
+ "owner process: "
+ "\r\n Owner Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Errno: %d (%T)"
+ "\r\n",
+ MKPID(env, pidP), descP->sock,
+ err, MKA(env, erl_errno_id(err)));
+ }
+}
+
+
+
+/* *** essio_down_acceptor ***
+ *
+ * Check and then handle a downed acceptor process.
+ *
+ */
+static
+void essio_down_acceptor(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP)
+{
+ if (MON_EQ(&descP->currentAcceptor.mon, monP)) {
+ MON_INIT(&descP->currentAcceptor.mon);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down_acceptor(%T) {%d} -> "
+ "current acceptor - try activate next\r\n",
+ sockRef, descP->sock) );
+
+ if (!esock_activate_next_acceptor(env, descP, sockRef)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down_acceptor(%T) {%d} -> no more writers\r\n",
+ sockRef, descP->sock) );
+
+ descP->readState &= ~ESOCK_STATE_ACCEPTING;
+
+ descP->currentAcceptorP = NULL;
+ }
+
+ } else {
+
+ /* Maybe unqueue one of the waiting acceptors */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down_acceptor(%T) {%d} -> "
+ "not current acceptor - maybe a waiting acceptor\r\n",
+ sockRef, descP->sock) );
+
+ esock_acceptor_unqueue(env, descP, NULL, pidP);
+ }
+}
+
+
+/* *** essio_down_writer ***
+ *
+ * Check and then handle a downed writer process.
+ *
+ */
+
+static
+void essio_down_writer(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP)
+{
+ if (MON_EQ(&descP->currentWriter.mon, monP)) {
+ MON_INIT(&descP->currentWriter.mon);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down_writer(%T) {%d} -> "
+ "current writer - try activate next\r\n",
+ sockRef, descP->sock) );
+
+ if (!esock_activate_next_writer(env, descP, sockRef)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down_writer(%T) {%d} -> no active writer\r\n",
+ sockRef, descP->sock) );
+
+ descP->currentWriterP = NULL;
+ }
+
+ } else {
+
+ /* Maybe unqueue one of the waiting writer(s) */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down_writer(%T) {%d} -> "
+ "not current writer - maybe a waiting writer\r\n",
+ sockRef, descP->sock) );
+
+ esock_writer_unqueue(env, descP, NULL, pidP);
+ }
+}
+
+
+/* *** essio_down_reader ***
+ *
+ * Check and then handle a downed reader process.
+ *
+ */
+
+static
+void essio_down_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP)
+{
+ if (MON_EQ(&descP->currentReader.mon, monP)) {
+ MON_INIT(&descP->currentReader.mon);
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down_reader(%T) {%d} -> "
+ "current reader - try activate next\r\n",
+ sockRef, descP->sock) );
+
+ if (! esock_activate_next_reader(env, descP, sockRef)) {
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down_reader(%T) {%d} -> no more readers\r\n",
+ sockRef, descP->sock) );
+
+ descP->currentReaderP = NULL;
+ }
+
+ } else {
+
+ /* Maybe unqueue one of the waiting reader(s) */
+
+ SSDBG( descP,
+ ("UNIX-ESSIO",
+ "essio_down_reader(%T) {%d} -> "
+ "not current reader - maybe a waiting reader\r\n",
+ sockRef, descP->sock) );
+
+ esock_reader_unqueue(env, descP, NULL, pidP);
+ }
+}
+
+
+#endif
diff --git a/erts/emulator/nifs/win32/win_socket_asyncio.c b/erts/emulator/nifs/win32/win_socket_asyncio.c
new file mode 100644
index 0000000000..b7d1f424fd
--- /dev/null
+++ b/erts/emulator/nifs/win32/win_socket_asyncio.c
@@ -0,0 +1,10250 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2023-2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ *
+ * ----------------------------------------------------------------------
+ * Purpose : Windows version of the asyncronous I/O backend.
+ * ----------------------------------------------------------------------
+ *
+ * Misc:
+ * This is based of the Windows concept: I/O Completion Port.
+ * This feature works on "all" kinds of I/O, even file I/O. But,
+ * our implementation only deals with socket I/O.
+ * The "asynchronous" access functions (that "can" block) that we
+ * use are as following:
+ *
+ * * WSASend, WSASendTo, WSASendMsg, WSARecv, WSARecvFrom, WSARecvMsg
+ * * AccepxEx
+ * (this is actually a function pointer, obtained at run time by
+ * making a call to the WSAIoctl function with the
+ * SIO_GET_EXTENSION_FUNCTION_POINTER opcode specified.
+ * The input buffer passed to the WSAIoctl function must contain
+ * WSAID_ACCEPTEX).
+ * To get the local and remote addresses, the GetAcceptExSockaddrs
+ * must be called. This function is *also* a function pointer
+ * obtained at run time by making a call to the WSAIoctl function.
+ * * ConnectEx:
+ * (this is actually a function pointer, obtained at run time by
+ * The function pointer for the ConnectEx function must be
+ * making a call to the WSAIoctl function with the
+ * SIO_GET_EXTENSION_FUNCTION_POINTER opcode specified.
+ * The input buffer passed to the WSAIoctl function must contain
+ * WSAID_CONNECTEX).
+ * * WSASendMsg & WSARecvMsg are actually *also* function pointers!!
+ *
+ * But since we want them to "behave" the same way, we need to add
+ * some wrapper code to simulate the "completion behaviour".
+ *
+ * These functions (in erlang) should return simething *like* this:
+ *
+ * ok | completion | {error, Reason}
+ *
+ * And if the return value was 'completion', the caller shall expect
+ * the following message, when the "operation" has "completed" (success
+ * or failure):
+ *
+ * {'$socket', Socket, completion, {Ref, CompletionStatus}}
+ *
+ * Where 'Socket' is the socket on which the call was made (for example,
+ * 'socket:send(Socket, ...)), and CompletionStatus is the result of
+ * actual operation: ok | {error, Reason}
+ *
+ * Examples:
+ * * https://www.winsocketdotnetworkprogramming.com/winsock2programming/winsock2advancediomethod5i.html
+ * * https://www.codeproject.com/Articles/13382/A-simple-application-using-I-O-Completion-Ports-an
+ * * https://www.winsocketdotnetworkprogramming.com/winsock2programming/winsock2advancedscalableapp6b.html
+ *
+ * More useful links:
+ * * https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-acceptex
+ * * https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaioctl
+ * * https://learn.microsoft.com/en-us/windows/win32/winsock/socket-options-and-ioctls-2
+ *
+ * Note:
+ * -
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef ESOCK_ENABLE
+
+// #include <Ws2def.h>
+// #include <winsock2.h>
+// #include <windows.h>
+#include <ws2tcpip.h>
+#include <mswsock.h>
+#include <stdio.h>
+
+#include <sys.h>
+
+#include "socket_int.h"
+#include "socket_io.h"
+#include "socket_asyncio.h"
+#include "socket_util.h"
+#include "socket_tarray.h"
+#include "socket_dbg.h"
+
+
+/* =================================================================== *
+ * *
+ * Local Constants *
+ * *
+ * =================================================================== */
+
+#define ESAIO_OK ESOCK_IO_OK
+#define ESAIO_ERR_WINSOCK_INIT 0x0001
+#define ESAIO_ERR_IOCPORT_CREATE 0x0002
+#define ESAIO_ERR_FSOCK_CREATE 0x0003
+#define ESAIO_ERR_IOCTL_ACCEPT_GET 0x0004
+#define ESAIO_ERR_IOCTL_CONNECT_GET 0x0005
+#define ESAIO_ERR_IOCTL_SENDMSG_GET 0x0006
+#define ESAIO_ERR_IOCTL_RECVMSG_GET 0x0007
+#define ESAIO_ERR_THREAD_OPTS_CREATE 0x0011
+#define ESAIO_ERR_THREAD_CREATE 0x0012
+
+#define ERRNO_BLOCK WSAEWOULDBLOCK
+
+
+/* ======================================================================== *
+ * Socket wrappers *
+ * ======================================================================== *
+ */
+
+#define sock_accept_O(s, as, b, al, rb, o) \
+ ctrl.accept((s), (as), (b), 0, (al), (al), (rb), (o))
+#define sock_bind(s, addr, len) bind((s), (addr), (len))
+#define sock_close(s) closesocket((s))
+#define sock_connect(s, a, al) connect((s), (a), (al))
+#define sock_connect_O(s, a, al, sent, o) \
+ ctrl.connect((s), (struct sockaddr*) (a), (al), NULL, 0, (sent), (o))
+#define sock_errno() WSAGetLastError()
+// #define sock_listen(s, b) listen((s), (b))
+// #define sock_name(s, addr, len) getsockname((s), (addr), (len))
+#define sock_open(domain, type, proto) socket((domain), (type), (proto))
+#define sock_open_O(domain, type, proto) \
+ WSASocket((domain), (type), (proto), NULL, 0, WSA_FLAG_OVERLAPPED)
+#define sock_recv_O(s,buf,flag,ol) \
+ WSARecv((s), (buf), 1, NULL, (flag), (ol), NULL)
+#define sock_recvfrom_O(s,buf,flag,fa,fal,ol) \
+ WSARecvFrom((s), (buf), 1, NULL, (flag), (fa), (fal), (ol), NULL)
+#define sock_recvmsg_O(s,msg,o) \
+ ctrl.recvmsg((s), (msg), NULL, (o), NULL)
+#define sock_send_O(s,buf,flag,o) \
+ WSASend((s), (buf), 1, NULL, (flag), (o), NULL)
+/* #define sock_sendmsg_O(s,buf,flag,ol) \ */
+/* WSASendMsg((s), (buf), (flag), NULL, (ol), NULL) */
+#define sock_sendmsg_O(s,buf,flag,ol) \
+ ctrl.sendmsg((s), (buf), (flag), NULL, (ol), NULL)
+#define sock_sendto_O(s,buf,flag,ta,tal,o) \
+ WSASendTo((s), (buf), 1, NULL, (flag), (ta), (tal), (o), NULL)
+#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln))
+
+
+#define ESAIO_UPDATE_ACCEPT_CONTEXT(AS, LS) \
+ sock_setopt( (AS), SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, \
+ (char*) &(LS), sizeof( (LS) ))
+#define ESAIO_UPDATE_CONNECT_CONTEXT(S) \
+ sock_setopt((S), SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0)
+
+
+#define ESOCK_CMSG_FIRSTHDR(M) WSA_CMSG_FIRSTHDR((M))
+#define ESOCK_CMSG_NXTHDR(M,C) WSA_CMSG_NXTHDR((M), (C))
+#define ESOCK_CMSG_DATA(C) WSA_CMSG_DATA((C))
+
+
+/* =================================================================== *
+ * *
+ * Local types *
+ * *
+ * =================================================================== */
+
+typedef struct {
+ Uint16 id; /* Thread id: mainly used for debugging,
+ * and name creation */
+
+ /* Thread state(s) */
+#define ESAIO_THREAD_STATE_UNDEF 0xFFFF
+#define ESAIO_THREAD_STATE_INITIATING 0x0000
+#define ESAIO_THREAD_STATE_OPERATIONAL 0x0001
+#define ESAIO_THREAD_STATE_TERMINATING 0x0002
+#define ESAIO_THREAD_STATE_TERMINATED 0x0003
+
+ Uint16 state; /* State of the thread:
+ * undefined, initiating, operational, terminating */
+
+ /* Thread error 'state(s)'.
+ * If the thread is "not running", this value tells why.
+ */
+#define ESAIO_THREAD_ERROR_UNDEF 0xFFFF
+#define ESAIO_THREAD_ERROR_OK 0x0000
+#define ESAIO_THREAD_ERROR_TOCREATE 0x0001
+#define ESAIO_THREAD_ERROR_TCREATE 0x0002
+#define ESAIO_THREAD_ERROR_GET 0x0003
+#define ESAIO_THREAD_ERROR_CMD 0x0004
+
+ Uint32 error; /* In case the thread exits,
+ * this is where the (error) reason is stored.
+ * Not sure i f we would ever be able to
+ * read this (if things are bad enough that the
+ * threads terminate)...
+ */
+
+ /* Do we need this?
+ * "The environment of the calling thread (process bound or
+ * callback environment) or NULL if calling from a custom
+ * thread not spawned by ERTS."
+ */
+ ErlNifEnv* env; /* Used when sending messages */
+
+#define ESAIO_THREAD_CNT_MAX 0xFFFFFFFF
+
+ Uint32 cnt; /* Run-counter: mainly used for debugging */
+
+ unsigned int latest; /* Latest request (tag) */
+} ESAIOThreadData;
+
+typedef struct {
+ ErlNifThreadOpts* optsP;
+ ErlNifTid tid;
+ ESAIOThreadData data;
+} ESAIOThread;
+
+typedef struct {
+ WSADATA wsaData;
+ HANDLE cport;
+
+ SOCKET dummy; // Used for extracting AcceptEx and ConnectEx
+
+ LPFN_ACCEPTEX accept;
+ LPFN_CONNECTEX connect;
+ LPFN_WSASENDMSG sendmsg;
+ LPFN_WSARECVMSG recvmsg;
+
+ /* Thread pool stuff.
+ * The size of the pool is configurable. */
+ DWORD numThreads;
+ ESAIOThread* threads;
+
+ /* Misc stuff */
+ BOOLEAN_T dbg;
+ BOOLEAN_T sockDbg;
+
+ /* Counter stuff */
+ ErlNifMutex* cntMtx;
+ ESockCounter unexpectedConnects;
+ ESockCounter unexpectedAccepts;
+ ESockCounter unexpectedWrites;
+ ESockCounter unexpectedReads;
+ ESockCounter genErrs;
+ ESockCounter unknownCmds;
+
+} ESAIOControl;
+
+
+typedef struct __ESAIOOpDataAccept {
+ /* AcceptEx; ; lookup with WSAID_ACCEPTEX */
+
+ /* The socket, sock, is created empty and then provided as an
+ * argumented to AcceptEx (together with the listen socket
+ * and the other arguments).
+ * When AcceptEx has completed successfully, the socket, s, is
+ * usable.
+ * But in order for the functions sockname and peername to work,
+ * the SO_UPDATE_ACCEPT_CONTEXT option must be set on the
+ * accepted socket, sock. */
+ SOCKET lsock; /* The listen socket */
+ SOCKET asock; /* The "accepted" socket.
+ * This is created "in advance"
+ * and then sent to AcceptEx as an argument.
+ */
+ char* buf; /* Size depends on domain.
+ * This is used for 'initial data',
+ * 'local address' and 'remote address'.
+ * We use neither of these, but the
+ * AcceptEx function requires this argument!
+ */
+ ERL_NIF_TERM lSockRef; /* The listen socket */
+ ERL_NIF_TERM accRef; /* The (unique) reference (ID) of the accept */
+} ESAIOOpDataAccept;
+
+typedef struct __ESAIOOpDataConnect {
+ /* ConnectEx; ; lookup with WSAID_CONNECTEX */
+
+ /* When ConnectEx has completed successfully,
+ * the socket is usable.
+ * *But*, in order for the functions sockname and peername to work,
+ * the SO_UPDATE_CONNECT_CONTEXT option must be set on the socket.
+ */
+ ERL_NIF_TERM sockRef; /* The socket */
+ ERL_NIF_TERM connRef; /* The (unique) reference (ID)
+ * of the connect request */
+} ESAIOOpDataConnect;
+
+typedef struct __ESAIOOpDataSend {
+ /* WSASend */
+ WSABUF wbuf; /* During ongoing sending, this buffer cannot
+ * be de-allocated. */
+ ERL_NIF_TERM sockRef; /* The socket */
+ ERL_NIF_TERM sendRef; /* The (unique) reference (ID)
+ * of the send request */
+} ESAIOOpDataSend;
+
+typedef struct __ESAIOOpDataSendTo {
+ /* WSASendTo */
+ WSABUF wbuf; /* During ongoing sending, this buffer cannot
+ * be de-allocated. */
+
+ /* Do we actually need these (remote address)?
+ * Debugging/logging?
+ */
+ ESockAddress remoteAddr;
+ SOCKLEN_T remoteAddrLen;
+
+ ERL_NIF_TERM sockRef; /* The socket */
+ ERL_NIF_TERM sendRef; /* The (unique) reference (ID)
+ * of the send request */
+} ESAIOOpDataSendTo;
+
+typedef struct __ESAIOOpDataSendMsg {
+ /* WSASendMsg; lookup with WSAID_WSASENDMSG */
+ WSAMSG msg;
+ ErlNifIOVec* iovec;
+ char* ctrlBuf;
+ ESockAddress addr;
+ ERL_NIF_TERM sockRef; /* The socket */
+ ERL_NIF_TERM sendRef; /* The (unique) reference (ID)
+ * of the send request */
+} ESAIOOpDataSendMsg;
+
+typedef struct __ESAIOOpDataRecv {
+ /* WSARecv */
+ DWORD toRead; /* Can be 0 (= zero)
+ * "just to indicate: give me what you got"
+ */
+ ErlNifBinary buf;
+ ERL_NIF_TERM sockRef; /* The socket */
+ ERL_NIF_TERM recvRef; /* The (unique) reference (ID)
+ * of the recv request */
+} ESAIOOpDataRecv;
+
+typedef struct __ESAIOOpDataRecvFrom {
+ /* WSARecvFrom */
+ DWORD toRead; /* Can be 0 (= zero)
+ * "just to indicate: give me what you got"
+ */
+ ErlNifBinary buf;
+
+ ESockAddress fromAddr;
+ INT addrLen; // SOCKLEN_T
+
+ ERL_NIF_TERM sockRef; /* The socket */
+ ERL_NIF_TERM recvRef; /* The (unique) reference (ID)
+ * of the recv request */
+} ESAIOOpDataRecvFrom;
+
+typedef struct __ESAIOOpDataRecvMsg {
+ /* WSARecvMsg; lookup with WSAID_WSARECVMSG */
+
+ /* If we used I/O - vectors of different size(s),
+ * we would (maybe) need to malloc them, and simply
+ * have a pointer here.
+ */
+
+ WSAMSG msg;
+ WSABUF wbufs[1];
+ ErlNifBinary data[1];
+ ErlNifBinary ctrl;
+ ESockAddress addr;
+
+ ERL_NIF_TERM sockRef; /* The socket */
+ ERL_NIF_TERM recvRef; /* The (unique) reference (ID)
+ * of the recv request */
+} ESAIOOpDataRecvMsg;
+
+/* An 'operation', recv/recvfrom/recvmsg and send/sendto/sendmsg,
+ * accept or connect, is 'encoded' into this structure, which is
+ * "passed around".
+ */
+typedef struct __ESAIOOperation {
+ /* Has to be first and is *only* used by I/O Completion Port framework */
+ WSAOVERLAPPED ol;
+
+ /* *** Commands (=tags) *** */
+#define ESAIO_OP_NONE 0x0000 // None
+ /* "system" commands */
+#define ESAIO_OP_TERMINATE 0x0001 // Terminate
+#define ESAIO_OP_DEBUG 0x0002 // Change debug level for thread(s)
+ /* Commands for establishing connections; connect and accept */
+#define ESAIO_OP_CONNECT 0x0011 // ConnectEx (function pointer)
+#define ESAIO_OP_ACCEPT 0x0012 // AcceptEx (function pointer)
+ /* Commands for sending */
+#define ESAIO_OP_SEND 0x0021 // WSASend
+#define ESAIO_OP_SENDTO 0x0022 // WSASendTo
+#define ESAIO_OP_SENDMSG 0x0023 // WSASendMsg
+ /* Commands for receiving */
+#define ESAIO_OP_RECV 0x0031 // WSARecv
+#define ESAIO_OP_RECVFROM 0x0032 // WSARecvFrom
+#define ESAIO_OP_RECVMSG 0x0033 // WSARecvMsg
+
+ unsigned int tag; /* The 'tag' of the operation */
+
+ ErlNifPid caller; /* Almost every request (not connect)
+ * operations require a caller */
+ ErlNifEnv* env; /* Almost every request
+ * needs an environment */
+
+ /* Generic "data" field.
+ * This is different for each 'operation'!
+ * Also, not all opererations have this!
+ */
+
+ union {
+ /* +++ accept +++ */
+ ESAIOOpDataAccept accept;
+
+ /* +++ connect +++ */
+ ESAIOOpDataConnect connect;
+
+ /* +++ send +++ */
+ ESAIOOpDataSend send;
+
+ /* +++ sendto +++ */
+ ESAIOOpDataSendTo sendto;
+
+ /* +++ sendmsg +++ */
+ ESAIOOpDataSendMsg sendmsg;
+
+ /* +++ recv +++ */
+ ESAIOOpDataRecv recv;
+
+ /* +++ recvfrom +++ */
+ ESAIOOpDataRecvFrom recvfrom;
+
+ /* +++ recvmsg +++ */
+ ESAIOOpDataRecvMsg recvmsg;
+
+ } data;
+
+} ESAIOOperation;
+
+
+
+/* =================================================================== *
+ * *
+ * Function Forwards *
+ * *
+ * =================================================================== */
+
+static ERL_NIF_TERM esaio_connect_stream(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM connRef,
+ ESockAddress* addrP,
+ SOCKLEN_T addrLen);
+static ERL_NIF_TERM connect_stream_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ BOOL cres);
+static ERL_NIF_TERM esaio_connect_dgram(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM connRef,
+ ESockAddress* addrP,
+ SOCKLEN_T addrLen);
+
+static ERL_NIF_TERM accept_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ BOOL ares,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef,
+ SOCKET accSock,
+ ErlNifPid caller);
+static ERL_NIF_TERM accept_check_pending(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef);
+static ERL_NIF_TERM accept_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ int saveErrno,
+ SOCKET accSock,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM esaio_accept_accepted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid pid,
+ ERL_NIF_TERM sockRef,
+ SOCKET accSock);
+
+static ERL_NIF_TERM send_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ int send_result,
+ ssize_t dataSize,
+ BOOLEAN_T dataInTail,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ BOOLEAN_T* cleanup);
+static ERL_NIF_TERM send_check_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ DWORD written,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM send_check_pending(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef);
+static ERL_NIF_TERM send_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef);
+
+static BOOLEAN_T init_sendmsg_sockaddr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eMsg,
+ WSAMSG* msgP,
+ ESockAddress* addrP);
+static BOOLEAN_T verify_sendmsg_iovec_size(const ESockData* dataP,
+ ESockDescriptor* descP,
+ ErlNifIOVec* iovec);
+static BOOLEAN_T verify_sendmsg_iovec_tail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM* tail);
+static BOOLEAN_T check_sendmsg_iovec_overflow(ESockDescriptor* descP,
+ ErlNifIOVec* iovec,
+ ssize_t* dataSize);
+static BOOLEAN_T decode_cmsghdrs(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eCMsg,
+ char* cmsgHdrBufP,
+ size_t cmsgHdrBufLen,
+ size_t* cmsgHdrBufUsed);
+static BOOLEAN_T decode_cmsghdr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eCMsg,
+ char* bufP,
+ size_t rem,
+ size_t* used);
+static BOOLEAN_T decode_cmsghdr_value(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ ERL_NIF_TERM eType,
+ ERL_NIF_TERM eValue,
+ char* dataP,
+ size_t dataLen,
+ size_t* dataUsedP);
+static BOOLEAN_T decode_cmsghdr_data(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ ERL_NIF_TERM eType,
+ ERL_NIF_TERM eData,
+ char* dataP,
+ size_t dataLen,
+ size_t* dataUsedP);
+static void encode_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ WSAMSG* msgP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM* eMsg);
+static void encode_cmsgs(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifBinary* cmsgBinP,
+ WSAMSG* msgP,
+ ERL_NIF_TERM* eCMsg);
+static ERL_NIF_TERM recv_check_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+
+static ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ int recv_result,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recv_check_pending(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recv_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM recv_check_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ int recv_result,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recvfrom_check_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recvfrom_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef);
+static ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ int recv_result,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recvmsg_check_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recvmsg_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef);
+
+static void* esaio_completion_main(void* threadDataP);
+static BOOLEAN_T esaio_completion_terminate(ESAIOThreadData* dataP,
+ OVERLAPPED* ovl);
+static BOOLEAN_T esaio_completion_unknown(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ DWORD numBytes,
+ int error);
+static void esaio_completion_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const char* opStr,
+ int error,
+ BOOLEAN_T inform);
+
+static BOOLEAN_T esaio_completion_connect(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataConnect* opDataP,
+ int error);
+static void esaio_completion_connect_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOpDataConnect* opDataP);
+static void esaio_completion_connect_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOpDataConnect* opDataP);
+static void esaio_completion_connect_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOpDataConnect* opDataP,
+ int error);
+static void esaio_completion_connect_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOpDataConnect* opDataPP);
+static void esaio_completion_connect_not_active(ESockDescriptor* descP);
+static void esaio_completion_connect_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform);
+
+static BOOLEAN_T esaio_completion_accept(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataAccept* opDataP,
+ int error);
+static void esaio_completion_accept_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataAccept* opDataP);
+static void esaio_completion_accept_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataAccept* opDataP);
+static void esaio_completion_accept_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataAccept* opDataP,
+ int error);
+static void esaio_completion_accept_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataAccept* opDataP,
+ ESockRequestor* reqP);
+static void esaio_completion_accept_not_active(ESockDescriptor* descP);
+static void esaio_completion_accept_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform);
+
+static BOOLEAN_T esaio_completion_send(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP,
+ int error);
+static void esaio_completion_send_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP);
+static void esaio_completion_send_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP);
+static void esaio_completion_send_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP,
+ int error);
+static void esaio_completion_send_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* sender,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ DWORD toWrite,
+ ESockRequestor* reqP);
+static ERL_NIF_TERM esaio_completion_send_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ DWORD written);
+static ERL_NIF_TERM esaio_completion_send_partial(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ DWORD written);
+static void esaio_completion_send_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform);
+static void esaio_completion_send_not_active(ESockDescriptor* descP);
+static BOOLEAN_T esaio_completion_sendto(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP,
+ int error);
+static void esaio_completion_sendto_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP);
+static void esaio_completion_sendto_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP);
+static void esaio_completion_sendto_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP,
+ int error);
+static void esaio_completion_sendto_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform);
+static BOOLEAN_T esaio_completion_sendmsg(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP,
+ int error);
+static void esaio_completion_sendmsg_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP);
+static void esaio_completion_sendmsg_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP);
+static void esaio_completion_sendmsg_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP,
+ int error);
+static void esaio_completion_sendmsg_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform);
+static BOOLEAN_T esaio_completion_recv(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP,
+ int error);
+static void esaio_completion_recv_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP);
+static void esaio_completion_recv_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP);
+static void esaio_completion_recv_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP,
+ int error);
+static void esaio_completion_recv_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP,
+ ESockRequestor* reqP);
+static ERL_NIF_TERM esaio_completion_recv_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecv* opDataP,
+ DWORD flags);
+static ERL_NIF_TERM esaio_completion_recv_partial(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecv* opDataP,
+ ESockRequestor* reqP,
+ DWORD read,
+ DWORD flags);
+static ERL_NIF_TERM esaio_completion_recv_partial_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecv* opDataP,
+ ssize_t read,
+ DWORD flags);
+static ERL_NIF_TERM esaio_completion_recv_partial_part(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecv* opDataP,
+ ssize_t read,
+ DWORD flags);
+static void esaio_completion_recv_not_active(ESockDescriptor* descP);
+static void esaio_completion_recv_closed(ESockDescriptor* descP,
+ int error);
+static void esaio_completion_recv_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform);
+static BOOLEAN_T esaio_completion_recvfrom(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ int error);
+static void esaio_completion_recvfrom_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP);
+static void esaio_completion_recvfrom_more_data(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ int error);
+static void esaio_completion_recvfrom_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP);
+static void esaio_completion_recvfrom_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ int error);
+static void esaio_completion_recvfrom_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ ESockRequestor* reqP);
+static ERL_NIF_TERM esaio_completion_recvfrom_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecvFrom* opDataP,
+ DWORD flags);
+static ERL_NIF_TERM esaio_completion_recvfrom_partial(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecvFrom* opDataP,
+ ESockRequestor* reqP,
+ DWORD read,
+ DWORD flags);
+static void esaio_completion_recvfrom_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform);
+static BOOLEAN_T esaio_completion_recvmsg(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP,
+ int error);
+static void esaio_completion_recvmsg_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP);
+static void esaio_completion_recvmsg_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP);
+static void esaio_completion_recvmsg_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP,
+ int error);
+static void esaio_completion_recvmsg_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP,
+ ESockRequestor* reqP);
+static ERL_NIF_TERM esaio_completion_recvmsg_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecvMsg* opDataP,
+ DWORD flags);
+static ERL_NIF_TERM esaio_completion_recvmsg_partial(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecvMsg* opDataP,
+ ESockRequestor* reqP,
+ DWORD read,
+ DWORD flags);
+static void esaio_completion_recvmsg_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform);
+
+static ERL_NIF_TERM esaio_completion_get_ovl_result_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error);
+
+static BOOL get_send_ovl_result(SOCKET sock,
+ OVERLAPPED* ovl,
+ DWORD* written);
+static BOOL get_recv_ovl_result(SOCKET sock,
+ OVERLAPPED* ovl,
+ DWORD* read,
+ DWORD* flags);
+static BOOL get_recvmsg_ovl_result(SOCKET sock,
+ OVERLAPPED* ovl,
+ DWORD* read);
+static BOOL get_ovl_result(SOCKET sock,
+ OVERLAPPED* ovl,
+ DWORD* transfer,
+ DWORD* flags);
+
+static void esaio_completion_inc(ESAIOThreadData* dataP);
+
+static int esaio_add_socket(ESockDescriptor* descP);
+
+static void esaio_send_completion_msg(ErlNifEnv* sendEnv,
+ ESockDescriptor* descP,
+ ErlNifPid* pid,
+ ErlNifEnv* msgEnv,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM connRef);
+static ERL_NIF_TERM mk_completion_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM completionRef);
+
+static void esaio_down_acceptor(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP);
+static void esaio_down_writer(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP);
+static void esaio_down_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP);
+
+static BOOLEAN_T do_stop(ErlNifEnv* env,
+ ESockDescriptor* descP);
+
+
+/* =================================================================== *
+ * *
+ * Local (global) variables *
+ * *
+ * =================================================================== */
+
+static ESAIOControl ctrl = {0};
+
+
+
+/* =================================================================== *
+ * *
+ * Various esaio macros *
+ * *
+ * =================================================================== */
+
+/* Global socket debug */
+#define SGDBG( proto ) ESOCK_DBG_PRINTF( ctrl.dbg , proto )
+
+/* These are just wrapper macros for the I/O Completion Port */
+#define ESAIO_IOCP_CREATE(NT) \
+ CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, (u_long) 0, (NT))
+#define ESAIO_IOCP_ADD(SOCK, DP) \
+ CreateIoCompletionPort((SOCK), ctrl.cport, (ULONG*) (DP), 0)
+
+/* These are just wrapper macros for the I/O Completion Port queue */
+#define ESAIO_IOCQ_POP(NB, DP, OLP) \
+ GetQueuedCompletionStatus(ctrl.cport, (DP), (DP), (OLP), INFINITE)
+#define ESAIO_IOCQ_PUSH(OP) \
+ PostQueuedCompletionStatus(ctrl.cport, 0, 0, (OVERLAPPED*) (OP))
+#define ESAIO_IOCQ_CANCEL(H, OP) \
+ CancelIoEx((H), (OVERLAPPED*) (OP))
+
+
+/* =================================================================== *
+ * *
+ * I/O Backend exports *
+ * *
+ * =================================================================== */
+
+
+/* *******************************************************************
+ * This function is called during (esock) nif loading
+ * The only argument that we actually *need* is the 'numThreads'.
+ * 'dataP' is just for convenience (dbg and stuff).
+ */
+extern
+int esaio_init(unsigned int numThreads,
+ const ESockData* dataP)
+{
+ int ires, save_errno;
+ unsigned int i;
+ DWORD dummy;
+ GUID guidAcceptEx = WSAID_ACCEPTEX;
+ GUID guidConnectEx = WSAID_CONNECTEX;
+ GUID guidSendMsg = WSAID_WSASENDMSG;
+ GUID guidRecvMsg = WSAID_WSARECVMSG;
+
+ /* Enabling this results in a core dump when calling socket:accept
+ * multiple times (the second call fails in env alloc).!
+ */
+ // ctrl.dbg = TRUE;
+ ctrl.dbg = dataP->dbg;
+ ctrl.sockDbg = dataP->sockDbg;
+
+ SGDBG( ("WIN-ESAIO", "esaio_init -> entry\r\n") );
+
+ ctrl.cntMtx = MCREATE("win-esaio.cnt");
+ ctrl.unexpectedConnects = 0;
+ ctrl.unexpectedAccepts = 0;
+ ctrl.unexpectedWrites = 0;
+ ctrl.unexpectedReads = 0;
+ ctrl.genErrs = 0;
+ ctrl.unknownCmds = 0;
+
+ /* We should actually check the value of 'numThreads'
+ * Since if its zero (the default), we should instead
+ * assign: 2 * 'number of schedulers'
+ * Or shall we trust the 'prim_socket' preloaded to
+ * select the proper value?
+ */
+ ctrl.numThreads = (DWORD) numThreads;
+
+ // Initialize Winsock
+ SGDBG( ("WIN-ESAIO", "esaio_init -> try initialize winsock\r\n") );
+ ires = WSAStartup(MAKEWORD(2, 2), &ctrl.wsaData);
+ if (ires != NO_ERROR) {
+ save_errno = sock_errno();
+
+ esock_error_msg("Failed initialize winsock: %d"
+ "\r\n %s (%d)"
+ "\r\n",
+ ires, erl_errno_id(save_errno), save_errno);
+
+ return ESAIO_ERR_WINSOCK_INIT;
+ }
+
+ // Create a handle for the completion port
+ SGDBG( ("WIN-ESAIO", "esaio_init -> try create I/O completion port\r\n") );
+ ctrl.cport = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
+ NULL, (u_long) 0, ctrl.numThreads);
+ if (ctrl.cport == NULL) {
+ save_errno = sock_errno();
+
+ esock_error_msg("Failed create I/O Completion Port:"
+ "\r\n %s (%d)"
+ "\r\n", erl_errno_id(save_errno), save_errno);
+
+ WSACleanup();
+
+ return ESAIO_ERR_IOCPORT_CREATE;
+ }
+
+
+ /* Create the "dummy" socket and then
+ * extract the AcceptEx and ConnectEx functions.
+ */
+ SGDBG( ("WIN-ESAIO", "esaio_init -> try create 'dummy' socket\r\n") );
+ ctrl.dummy = sock_open(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (ctrl.dummy == INVALID_SOCKET) {
+ save_errno = sock_errno();
+
+ esock_error_msg("Failed create 'dummy' socket: "
+ "\r\n %s (%d)"
+ "\r\n",
+ erl_errno_id(save_errno), save_errno);
+
+ WSACleanup();
+
+ return ESAIO_ERR_FSOCK_CREATE;
+ }
+
+
+ /* Load the AcceptEx function into memory using WSAIoctl.
+ * The WSAIoctl function is an extension of the ioctlsocket()
+ * function that can use overlapped I/O.
+ * The function's 3rd through 6th parameters are input and output
+ * buffers where we pass the pointer to our AcceptEx function.
+ * This is used so that we can call the AcceptEx function directly,
+ * rather than refer to the Mswsock.lib library.
+ */
+ SGDBG( ("WIN-ESAIO", "esaio_init -> try extract 'accept' function\r\n") );
+ ires = WSAIoctl(ctrl.dummy, SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &guidAcceptEx, sizeof (guidAcceptEx),
+ &ctrl.accept, sizeof (ctrl.accept),
+ &dummy, NULL, NULL);
+ if (ires == SOCKET_ERROR) {
+ save_errno = sock_errno();
+
+ esock_error_msg("Failed extracting 'accept' function: %d"
+ "\r\n %s (%d)"
+ "\r\n",
+ ires, erl_errno_id(save_errno), save_errno);
+
+ (void) sock_close(ctrl.dummy);
+ ctrl.dummy = INVALID_SOCKET;
+ ctrl.accept = NULL;
+
+ WSACleanup();
+
+ return ESAIO_ERR_IOCTL_ACCEPT_GET;
+ }
+
+
+ /* Basically the same as for AcceptEx above */
+ SGDBG( ("WIN-ESAIO", "esaio_init -> try extract 'connect' function\r\n") );
+ ires = WSAIoctl(ctrl.dummy, SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &guidConnectEx, sizeof (guidConnectEx),
+ &ctrl.connect, sizeof (ctrl.connect),
+ &dummy, NULL, NULL);
+ if (ires == SOCKET_ERROR) {
+ save_errno = sock_errno();
+
+ esock_error_msg("Failed extracting 'connect' function: %d"
+ "\r\n %s (%d)"
+ "\r\n",
+ ires, erl_errno_id(save_errno), save_errno);
+
+ (void) sock_close(ctrl.dummy);
+ ctrl.dummy = INVALID_SOCKET;
+ ctrl.accept = NULL;
+
+ WSACleanup();
+ return ESAIO_ERR_IOCTL_CONNECT_GET;
+ }
+
+
+ /* Basically the same as for AcceptEx above */
+ SGDBG( ("WIN-ESAIO", "esaio_init -> try extract 'sendmsg' function\r\n") );
+ ires = WSAIoctl(ctrl.dummy, SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &guidSendMsg, sizeof (guidSendMsg),
+ &ctrl.sendmsg, sizeof (ctrl.sendmsg),
+ &dummy, NULL, NULL);
+ if (ires == SOCKET_ERROR) {
+ save_errno = sock_errno();
+
+ esock_error_msg("Failed extracting 'sendmsg' function: %d"
+ "\r\n %s (%d)"
+ "\r\n",
+ ires, erl_errno_id(save_errno), save_errno);
+
+ (void) sock_close(ctrl.dummy);
+ ctrl.dummy = INVALID_SOCKET;
+ ctrl.accept = NULL;
+ ctrl.connect = NULL;
+
+ WSACleanup();
+ return ESAIO_ERR_IOCTL_SENDMSG_GET;
+ }
+
+
+ /* Basically the same as for AcceptEx above */
+ SGDBG( ("WIN-ESAIO", "esaio_init -> try extract 'recvmsg' function\r\n") );
+ ires = WSAIoctl(ctrl.dummy, SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &guidRecvMsg, sizeof (guidRecvMsg),
+ &ctrl.recvmsg, sizeof (ctrl.recvmsg),
+ &dummy, NULL, NULL);
+ if (ires == SOCKET_ERROR) {
+ save_errno = sock_errno();
+
+ esock_error_msg("Failed extracting 'recvmsg' function: %d"
+ "\r\n %s (%d)"
+ "\r\n",
+ ires, erl_errno_id(save_errno), save_errno);
+
+ (void) sock_close(ctrl.dummy);
+ ctrl.dummy = INVALID_SOCKET;
+ ctrl.accept = NULL;
+ ctrl.connect = NULL;
+ ctrl.sendmsg = NULL;
+
+ WSACleanup();
+ return ESAIO_ERR_IOCTL_RECVMSG_GET;
+ }
+
+
+ /*
+ * Create the completion port thread pool.
+ */
+ SGDBG( ("WIN-ESAIO", "esaio_init -> try alloc thread pool memory\r\n") );
+ ctrl.threads = MALLOC(numThreads * sizeof(ESAIOThread));
+ ESOCK_ASSERT( ctrl.threads != NULL );
+
+ SGDBG( ("WIN-ESAIO", "esaio_init -> basic init of thread data\r\n") );
+ for (i = 0; i < numThreads; i++) {
+ ctrl.threads[i].data.id = i;
+ ctrl.threads[i].data.state = ESAIO_THREAD_STATE_UNDEF;
+ ctrl.threads[i].data.error = ESAIO_THREAD_ERROR_UNDEF;
+ ctrl.threads[i].data.env = NULL;
+ ctrl.threads[i].data.cnt = 0;
+ }
+
+ SGDBG( ("WIN-ESAIO", "esaio_init -> try create thread(s)\r\n") );
+ for (i = 0; i < numThreads; i++) {
+ char buf[64]; /* Buffer used for building the names */
+ int j;
+
+ /* We set these here to avoid raise with the thread.
+ * *If* we fail in the creation, then we set an error */
+
+ ctrl.threads[i].data.state = ESAIO_THREAD_STATE_INITIATING;
+ ctrl.threads[i].data.error = ESAIO_THREAD_ERROR_OK;
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_init -> try create %d thread opts\r\n", i) );
+ sprintf(buf, "esaio-opts[%d]", i);
+ ctrl.threads[i].optsP = TOCREATE(buf);
+ if (ctrl.threads[i].optsP == NULL) {
+ esock_error_msg("Failed create thread opts %d\r\n");
+
+ ctrl.threads[i].data.error = ESAIO_THREAD_ERROR_TOCREATE;
+
+ for (j = 0; j < i; j++) {
+ SGDBG( ("WIN-ESAIO",
+ "esaio_init -> destroy thread opts %d\r\n", j) );
+ TODESTROY(ctrl.threads[j].optsP);
+ }
+ return ESAIO_ERR_THREAD_OPTS_CREATE;
+ }
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_init -> try create thread %d\r\n", i) );
+ sprintf(buf, "esaio[%d]", i);
+ if (0 != TCREATE(buf,
+ &ctrl.threads[i].tid,
+ esaio_completion_main,
+ (void*) &ctrl.threads[i].data,
+ ctrl.threads[i].optsP)) {
+
+ ctrl.threads[i].data.error = ESAIO_THREAD_ERROR_TCREATE;
+
+ for (j = 0; j <= i; j++) {
+ SGDBG( ("WIN-ESAIO",
+ "esaio_init -> destroy thread opts %d\r\n", j) );
+ TODESTROY(ctrl.threads[j].optsP);
+ }
+ return ESAIO_ERR_THREAD_CREATE;
+ }
+
+ }
+
+
+ SGDBG( ("WIN-ESAIO", "esaio_init -> done\r\n") );
+
+ return ESAIO_OK;
+}
+
+
+
+/* *******************************************************************
+ * Finish, terminate, the ESock Async I/O backend.
+ * This means principally to terminate (threads of) the thread pool.
+ * Issue a "message" via PostQueuedCompletionStatus
+ * instructing all (completion) threads to terminate.
+ */
+extern
+void esaio_finish()
+{
+ int t, lastThread;
+
+ SGDBG( ("WIN-ESAIO", "esaio_finish -> entry\r\n") );
+
+ if (ctrl.dummy != INVALID_SOCKET) {
+ SGDBG( ("WIN-ESAIO", "esaio_finish -> close 'dummy' socket\r\n") );
+ (void) sock_close(ctrl.dummy);
+ ctrl.dummy = INVALID_SOCKET;
+ }
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_finish -> try terminate %d worker threads\r\n",
+ ctrl.numThreads) );
+ for (t = 0, lastThread = -1, lastThread; t < ctrl.numThreads; t++) {
+ ESAIOOperation* opP;
+ BOOL qres;
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_finish -> "
+ "[%d] try allocate (terminate-) operation\r\n", t) );
+
+ /* Where is this FREE'ed??
+ * By the thread after it has been received?
+ */
+
+ opP = MALLOC( sizeof(ESAIOOperation) );
+
+ /* We should actually check that the alloc was successful
+ * and if not ...
+ * Note that this function is only called when we are terminating
+ * the VM. So, is there actuall any pointy in "doing" something?
+ * Or should we solve this another way? Instead of allocating
+ * a memory block; Send in a constant, ESAIO_OP_TERMINATE,
+ * *instead of* the overlapped pointer!
+ */
+
+ /* If this dows not work, there is not much we can do!
+ * And since this is *only* done when we terminate the VM...
+ */
+
+ if (opP != NULL) {
+ sys_memzero((char *) opP, sizeof(ESAIOOperation));
+
+ opP->tag = ESAIO_OP_TERMINATE;
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_finish -> "
+ "try post (terminate-) package %d\r\n", t) );
+
+ qres = PostQueuedCompletionStatus(ctrl.cport,
+ 0, 0, (OVERLAPPED*) opP);
+
+ if (!qres) {
+ int save_errno = sock_errno();
+ esock_error_msg("Failed posting 'terminate' command: "
+ "\r\n %s (%d)"
+ "\r\n", erl_errno_id(save_errno), save_errno);
+ break;
+ } else {
+ lastThread = t;
+ }
+
+ } else {
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_finish -> "
+ "failed allocate (terminate-) operation %d\r\n", t) );
+
+ }
+ }
+
+ if (lastThread >= 0) {
+ SGDBG( ("WIN-ESAIO",
+ "esaio_finish -> await (worker) thread(s) termination\r\n") );
+ for (t = 0; t < (lastThread+1); t++) {
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_finish -> try join with thread %d\r\n", t) );
+
+ (void) TJOIN(ctrl.threads[t].tid, NULL);
+
+ SGDBG( ("WIN-ESAIO", "esaio_finish -> joined with %d\r\n", t) );
+
+ }
+ }
+
+ /* This is overkill,
+ * since this function, esaio_finish, is called when the VM is halt'ing...
+ * ...but just to be a nice citizen...
+ */
+ SGDBG( ("WIN-ESAIO", "esaio_finish -> free the thread pool data\r\n") );
+ FREE( ctrl.threads );
+
+ SGDBG( ("WIN-ESAIO", "esaio_finish -> invalidate functions\r\n") );
+ ctrl.accept = NULL;
+ ctrl.connect = NULL;
+
+ SGDBG( ("WIN-ESAIO", "esaio_finish -> done\r\n") );
+
+ return;
+}
+
+
+
+/* *******************************************************************
+ * esaio_info - Return info "about" this I/O backend.
+ */
+
+extern
+ERL_NIF_TERM esaio_info(ErlNifEnv* env)
+{
+ ERL_NIF_TERM info, numThreads,
+ numUnexpAccs, numUnexpConns, numUnexpWs, numUnexpRs,
+ numGenErrs,
+ numUnknownCmds;
+
+ numThreads = MKUI(env, ctrl.numThreads);
+
+ MLOCK(ctrl.cntMtx);
+
+ numUnexpConns = MKUI(env, ctrl.unexpectedConnects);
+ numUnexpAccs = MKUI(env, ctrl.unexpectedAccepts);
+ numUnexpWs = MKUI(env, ctrl.unexpectedWrites);
+ numUnexpRs = MKUI(env, ctrl.unexpectedReads);
+ numGenErrs = MKUI(env, ctrl.genErrs);
+ numUnknownCmds = MKUI(env, ctrl.unknownCmds);
+
+ MUNLOCK(ctrl.cntMtx);
+
+ {
+ ERL_NIF_TERM cntKeys[] = {esock_atom_num_unexpected_connects,
+ esock_atom_num_unexpected_accepts,
+ esock_atom_num_unexpected_writes,
+ esock_atom_num_unexpected_reads,
+ esock_atom_num_general_errors,
+ esock_atom_num_unknown_cmds};
+ ERL_NIF_TERM cntVals[] = {numUnexpConns, numUnexpAccs,
+ numUnexpWs, numUnexpRs,
+ numGenErrs,
+ numUnknownCmds};
+ unsigned int numCntKeys = NUM(cntKeys);
+ unsigned int numCntVals = NUM(cntVals);
+ ERL_NIF_TERM counters;
+
+ ESOCK_ASSERT( numCntKeys == numCntVals );
+ ESOCK_ASSERT( MKMA(env, cntKeys, cntVals, numCntKeys, &counters) );
+
+ {
+ ERL_NIF_TERM keys[] = {esock_atom_name,
+ esock_atom_num_threads,
+ esock_atom_counters};
+ ERL_NIF_TERM vals[] = {MKA(env, "win_esaio"),
+ numThreads,
+ counters};
+ unsigned int numKeys = NUM(keys);
+ unsigned int numVals = NUM(vals);
+
+ ESOCK_ASSERT( numKeys == numVals );
+ ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &info) );
+ }
+ }
+
+ return info;
+}
+
+
+
+/* *******************************************************************
+ * esaio_open_plain - create an endpoint (from an existing fd) for
+ * communication.
+ *
+ * Create an *overlapped* socket, then add it to the I/O
+ * completion port.
+ */
+
+extern
+ERL_NIF_TERM esaio_open_plain(ErlNifEnv* env,
+ int domain,
+ int type,
+ int protocol,
+ ERL_NIF_TERM eopts,
+ const ESockData* dataP)
+{
+ /* We do not actually need the dataP since we already have the dbg... */
+ BOOLEAN_T dbg = esock_open_is_debug(env, eopts, dataP->sockDbg);
+ BOOLEAN_T useReg = esock_open_use_registry(env, eopts, dataP->useReg);
+ ESockDescriptor* descP;
+ ERL_NIF_TERM sockRef;
+ int proto = protocol;
+ DWORD dwFlags = WSA_FLAG_OVERLAPPED;
+ SOCKET sock = INVALID_SOCKET;
+ ErlNifPid self;
+ int res, save_errno;
+
+ /* Keep track of the creator
+ * This should not be a problem, but just in case
+ * the *open* function is used with the wrong kind
+ * of environment...
+ */
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ SSDBG2( dbg,
+ ("WIN-ESAIO", "esaio_open_plain -> entry with"
+ "\r\n domain: %d"
+ "\r\n type: %d"
+ "\r\n protocol: %d"
+ "\r\n eopts: %T"
+ "\r\n", domain, type, protocol, eopts) );
+
+ sock = sock_open_O(domain, type, proto);
+
+ if (sock == INVALID_SOCKET) {
+ save_errno = sock_errno();
+ return esock_make_error_errno(env, save_errno);
+ }
+
+ SSDBG2( dbg, ("WIN-ESAIO",
+ "esaio_open_plain -> open success: %d\r\n", sock) );
+
+ /* NOTE that if the protocol = 0 (default) and the domain is not
+ * local (AF_LOCAL) we need to explicitly get the protocol here!
+ */
+
+ if (proto == 0)
+ (void) esock_open_which_protocol(sock, &proto);
+
+ /* Create and initiate the socket "descriptor" */
+ descP = esock_alloc_descriptor(sock);
+ descP->ctrlPid = self;
+ descP->domain = domain;
+ descP->type = type;
+ descP->protocol = proto;
+
+ SSDBG2( dbg, ("WIN-ESAIO",
+ "esaio_open_plain -> add to completion port\r\n") );
+
+ if (ESAIO_OK != (save_errno = esaio_add_socket(descP))) {
+ // See esock_dtor for what needs done!
+ ERL_NIF_TERM tag = esock_atom_add_socket;
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(save_errno));
+
+ SSDBG2( dbg, ("WIN-ESAIO",
+ "esaio_open_plain -> "
+ "failed adding socket to completion port: "
+ "%T (%d)\r\n", reason, save_errno) );
+
+ esock_dealloc_descriptor(env, descP);
+ sock_close(sock);
+
+ /* This should really be:
+ * {error, {invalid, {add_to_completion_port, Reason}}}
+ */
+
+ return esock_make_error_t2r(env, tag, reason);
+ }
+
+ SSDBG2( dbg, ("WIN-ESAIO",
+ "esaio_open_plain -> create socket ref\r\n") );
+
+ sockRef = enif_make_resource(env, descP);
+ enif_release_resource(descP);
+
+ SSDBG2( dbg, ("WIN-ESAIO",
+ "esaio_open_plain -> monitor owner %T\r\n", descP->ctrlPid) );
+
+ ESOCK_ASSERT( MONP("esaio_open -> ctrl",
+ env, descP,
+ &descP->ctrlPid,
+ &descP->ctrlMon) == 0 );
+
+ descP->dbg = dbg;
+ descP->useReg = useReg;
+ esock_inc_socket(domain, type, proto);
+
+ SSDBG2( dbg, ("WIN-ESAIO",
+ "esaio_open_plain -> maybe update registry\r\n") );
+
+ /* And finally (maybe) update the registry */
+ if (descP->useReg) esock_send_reg_add_msg(env, descP, sockRef);
+
+ SSDBG2( dbg, ("WIN-ESAIO",
+ "esaio_open_plain -> done\r\n") );
+
+ return esock_make_ok2(env, sockRef);
+}
+
+
+
+/* *******************************************************************
+ * esaio_bind - Bind a name to a socket.
+ *
+ */
+extern
+ERL_NIF_TERM esaio_bind(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESockAddress* sockAddrP,
+ SOCKLEN_T addrLen)
+{
+ if (! IS_OPEN(descP->readState))
+ return esock_make_error_closed(env);
+
+ if (sock_bind(descP->sock, &sockAddrP->sa, addrLen) < 0) {
+ return esock_make_error_errno(env, sock_errno());
+ }
+
+ descP->writeState |= ESOCK_STATE_BOUND;
+
+ return esock_atom_ok;
+}
+
+
+
+/* *******************************************************************
+ * esaio_connect - Connect the socket to a specified address
+ *
+ * The function we use here, ConnectEx, is intended for STREAM/TCP.
+ * But (traditional) connect can be used with DGRAM/UDP.
+ * So, does ConnectEx work with DGRAM/UDP sockets? I think not.
+ * So we may need to test what kind of socket we have, and for
+ * DGRAM/UDP use the "old" connect:
+ *
+ * if (type == DGRAM) && (protocol == UDP)
+ * connect(...);
+ * else
+ * ConnectEx(...);
+ *
+ * Quote from the Microsoft documentation:
+ * "The ConnectEx function can only be used with connection-oriented sockets.
+ * The socket passed in the s parameter must be created with a socket type
+ * of SOCK_STREAM, SOCK_RDM, or SOCK_SEQPACKET."
+ *
+ * Quote from the Microsoft documentation:
+ * When the ConnectEx function successfully completes, the "connected socket"
+ * can be passed to only the following functions:
+ *
+ * * ReadFile (not provided by our API)
+ * * WriteFile (not provided by our API)
+ * * send or WSASend (send not used by us)
+ * * recv or WSARecv (recv not used by us)
+ * * TransmitFile (not provided by our API)
+ * * closesocket
+ *
+ * Socket used *must* be *bound* before calling ConnectEx!
+ */
+
+extern
+ERL_NIF_TERM esaio_connect(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM connRef,
+ ESockAddress* addrP,
+ SOCKLEN_T addrLen)
+{
+ /*
+ * Verify that we are in the proper state
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_connect(%T, %d) -> verify open\r\n",
+ sockRef, descP->sock) );
+
+ if (! IS_OPEN(descP->writeState))
+ return esock_make_error(env, esock_atom_closed);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_connect(%T, %d) -> verify type: %s\r\n",
+ sockRef, descP->sock, TYPE2STR(descP->type)) );
+
+ switch (descP->type) {
+ case SOCK_STREAM:
+ return esaio_connect_stream(env,
+ descP, sockRef, connRef,
+ addrP, addrLen);
+ break;
+
+ case SOCK_DGRAM:
+ return esaio_connect_dgram(env,
+ descP, sockRef, connRef,
+ addrP, addrLen);
+ break;
+
+ default:
+ return enif_make_badarg(env);
+ }
+}
+
+
+
+/* *******************************************************************
+ * esaio_connect_stream - Connect the (stream) socket
+ */
+static
+ERL_NIF_TERM esaio_connect_stream(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM connRef,
+ ESockAddress* addrP,
+ SOCKLEN_T addrLen)
+{
+ int save_errno;
+ BOOL cres;
+ ESAIOOperation* opP;
+ ERL_NIF_TERM eres;
+ ErlNifPid self;
+
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+
+ /* ConnectEx *requires* the socket to be bound */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_connect_stream(%T) -> verify bound\r\n", sockRef) );
+ if (! IS_BOUND(descP->writeState))
+ return esock_make_error(env, esock_atom_not_bound);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_connect_stream(%T) -> check if ongoing\r\n",
+ sockRef) );
+ if (descP->connectorP != NULL) {
+
+ /* Connect already in progress, check if its us */
+
+ if (COMPARE_PIDS(&self, &descP->connector.pid) != 0) {
+ /* *Other* process has connect in progress */
+ if (addrP != NULL) {
+ eres = esock_make_error(env, esock_atom_already);
+ } else {
+ /* This is a bad call sequence
+ * - connect without an address is only allowed
+ * for the connecting process
+ */
+ eres = esock_raise_invalid(env, esock_atom_state);
+ }
+ } else {
+
+ /* TRHIS IS NOT HOW IT WORKS ON WINDOWS!
+ * This function should never be called *again*
+ * The completion message contains the full and final answer.
+ * No need to call again!
+ */
+
+ eres = esock_raise_invalid(env, esock_atom_state);
+ }
+
+ } else if (addrP == NULL) {
+
+ /* This is a bad call sequence
+ * - connect without an address is not valid on Windows.
+ */
+ eres = esock_raise_invalid(env, esock_atom_state);
+
+ } else {
+
+ DWORD sentDummy = 0;
+
+ /* No connect in progress */
+
+ /* Initial connect call, with address */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_connect_stream(%T) -> allocate (connect) operation\r\n",
+ sockRef) );
+
+ opP = MALLOC( sizeof(ESAIOOperation) );
+ ESOCK_ASSERT( opP != NULL);
+ sys_memzero((char*) opP, sizeof(ESAIOOperation));
+
+ opP->tag = ESAIO_OP_CONNECT;
+
+ /* Its a bit annoying that we have to alloc an env and then
+ * copy the ref *before* we know that we actually need it.
+ * How much does this cost?
+ */
+
+ /* Initiate connector */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_connect_stream(%T) -> initiate connector\r\n",
+ sockRef) );
+
+ descP->connector.pid = self;
+ ESOCK_ASSERT( MONP("esaio_connect_stream -> conn",
+ env, descP,
+ &self, &descP->connector.mon) == 0 );
+ descP->connector.dataP = (void*) opP;
+ descP->connector.env = esock_alloc_env("connector");
+ descP->connector.ref = CP_TERM(descP->connector.env, connRef);
+ descP->connectorP = &descP->connector;
+ descP->writeState |=
+ (ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED);
+
+ opP->env = esock_alloc_env("esaio-connect-stream");
+ opP->caller = self;
+ opP->data.connect.sockRef = CP_TERM(opP->env, sockRef);
+ opP->data.connect.connRef = CP_TERM(opP->env, connRef);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_connect_stream {%d} -> try connect\r\n",
+ descP->sock) );
+
+ /*
+ * BOOL LpfnConnectex(
+ * [in] SOCKET s,
+ * [in] const sockaddr *name,
+ * [in] int namelen,
+ * [in, optional] PVOID lpSendBuffer,
+ * [in] DWORD dwSendDataLength,
+ * [out] LPDWORD lpdwBytesSent,
+ * [in] LPOVERLAPPED lpOverlapped
+ * )
+ */
+
+ cres = sock_connect_O(descP->sock,
+ addrP, addrLen,
+ &sentDummy, (OVERLAPPED*) opP);
+
+ /*
+ * We need to keep using the requestor "queues"!
+ * That is the "only" way to handle the monitoring of
+ * the requestor.
+ * That is if the request (for instance a connect) is
+ * is scheduled, WSA_IO_PENDING, then we need to store
+ * the info about the requestor somewhere we can access it,
+ * in case the requestor for example dies (and we need to
+ * clean up).
+ */
+
+ eres = connect_stream_check_result(env, descP, opP, cres);
+
+ }
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_connect {%d} -> done with"
+ "\r\n eres: %T"
+ "\r\n",
+ descP->sock, eres) );
+
+ return eres;
+}
+
+
+static
+ERL_NIF_TERM connect_stream_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ BOOL cres)
+{
+ ERL_NIF_TERM eres, tag, reason;
+ int save_errno;
+
+ if (cres) {
+
+ /* Success already! */
+
+ int err;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "connect_stream_check_result(%d) -> connected\r\n",
+ descP->sock) );
+
+ /* Clean up the connector stuff, no need for that anymore */
+ esock_requestor_release("connect_stream_check_result -> success",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+
+ /* We need to make sure peername and sockname works! */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "connect_stream_check_result {%d} -> "
+ "update connect context\r\n",
+ descP->sock) );
+
+ err = ESAIO_UPDATE_CONNECT_CONTEXT( descP->sock );
+
+ if (err == 0) {
+
+ descP->writeState &=
+ ~(ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED);
+ descP->writeState |= ESOCK_STATE_CONNECTED;
+
+ eres = esock_atom_ok;
+
+ } else {
+
+ save_errno = sock_errno();
+ tag = esock_atom_update_connect_context;
+ reason = ENO2T(env, save_errno);
+
+ SSDBG( descP, ("WIN-ESAIO",
+ "connect_stream_check_result(%d) -> "
+ "connect context update failed: %T\r\n",
+ descP->sock, reason) );
+
+ sock_close(descP->sock);
+ descP->writeState = ESOCK_STATE_CLOSED;
+
+ WSACleanup();
+
+ eres = esock_make_error_t2r(env, tag, reason);
+ }
+
+ } else {
+
+ /* Connect returned error, check which */
+
+ save_errno = sock_errno();
+
+ if (save_errno == WSA_IO_PENDING) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "connect_stream_check_result(%d) -> connect scheduled\r\n",
+ descP->sock) );
+
+ eres = esock_atom_completion;
+
+ } else {
+ ERL_NIF_TERM ereason = ENO2T(env, save_errno);
+
+ SSDBG( descP, ("WIN-ESAIO",
+ "connect_stream_check_result(%d) -> "
+ "connect attempt failed: %T\r\n",
+ descP->sock, ereason) );
+
+ /* Clean up the connector stuff, no need for that anymore */
+ esock_requestor_release("connect_stream_check_result -> failure",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+
+ /* Will an event be generetade in this case?
+ * Assume not => We need to clean up here!
+ */
+ esock_clear_env("connect_stream_check_result", opP->env);
+ esock_free_env("connect_stream_check_result", opP->env);
+ FREE( opP );
+
+ sock_close(descP->sock);
+ descP->writeState = ESOCK_STATE_CLOSED;
+ WSACleanup();
+
+ eres = esock_make_error(env, ereason);
+ }
+ }
+
+ return eres;
+}
+
+
+
+/* *** esaio_connect_dgram ***
+ * Handle the "fake" connect of a DGRAM socket ("bind" to
+ * a remote address, so user can use send/recv instead of
+ * sendto/recvfrom). Should use sock_connect(...)
+ * This is corrently just a placeholder!
+ */
+static
+ERL_NIF_TERM esaio_connect_dgram(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM connRef,
+ ESockAddress* addrP,
+ SOCKLEN_T addrLen)
+{
+ return enif_make_badarg(env);
+}
+
+
+
+/* *** esaio_listen *** */
+
+
+/* ========================================================================
+ */
+extern
+ERL_NIF_TERM esaio_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef)
+{
+ ErlNifPid caller;
+ ERL_NIF_TERM eres;
+ SOCKET accSock;
+ ESAIOOperation* opP;
+ BOOLEAN_T ares;
+ unsigned int addrSz, bufSz;
+ DWORD recvBytes;
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ /* Ensure that this caller does not already have a
+ * (accept) request waiting */
+ if (esock_acceptor_search4pid(env, descP, &caller)) {
+ /* Acceptor already in queue */
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ /* Accept and Read uses the same flag so they can not be simultaneous.
+ */
+ SSDBG( descP, ("WIN-ESAIO", "esaio_accept {%d} -> verify not reading\r\n",
+ descP->sock) );
+ if (descP->readersQ.first != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Should we verify domain, type and protocol? */
+
+ /* Allocate 'operation' */
+ SSDBG( descP, ("WIN-ESAIO",
+ "esaio_accept {%d} -> allocate 'operation'\r\n",
+ descP->sock) );
+ opP = MALLOC( sizeof(ESAIOOperation) );
+ ESOCK_ASSERT( opP != NULL);
+ sys_memzero((char*) opP, sizeof(ESAIOOperation));
+
+ opP->tag = ESAIO_OP_ACCEPT;
+
+ /* Its a bit annoying that we have to alloc an env and then
+ * copy the ref *before* we know that we actually need it.
+ * How much does this cost?
+ */
+ SSDBG( descP, ("WIN-ESAIO",
+ "esaio_accept {%d} -> initiate 'operation'\r\n",
+ descP->sock) );
+ opP->env = esock_alloc_env("esaio_accept - operation");
+ opP->data.accept.lSockRef = CP_TERM(opP->env, sockRef);
+ opP->data.accept.accRef = CP_TERM(opP->env, accRef);
+ opP->data.accept.lsock = descP->sock;
+ opP->caller = caller;
+
+ /* Create the accepting socket
+ * domain - should be AF_INET | AF_INET6 (sould we make sure?)
+ * type - should be SOCK_STREAM | SOCK_SEQPACKET (should we make sure?)
+ * protocol - should be IPPROTO_TCP | IPPROTO_SCTP (should we make sure?)
+ * See check above!
+ */
+ SSDBG( descP, ("WIN-ESAIO",
+ "esaio_accept {%d} -> try create 'accepting' socket\r\n",
+ descP->sock) );
+ accSock = sock_open(descP->domain, descP->type, descP->protocol);
+ if (accSock == INVALID_SOCKET) {
+ int save_errno = sock_errno();
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(save_errno));
+ ERL_NIF_TERM tag = esock_atom_create_accept_socket;
+
+ esock_clear_env("esaio_accept - invalid accept socket", opP->env);
+ esock_free_env("esaio_accept - invalid accept socket", opP->env);
+ FREE( opP );
+ WSACleanup();
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_accept {%d} -> failed create 'accepting' socket:"
+ "\r\n %T (%d)"
+ "\r\n",
+ descP->sock, reason, save_errno) );
+
+ return esock_make_error_t2r(env, tag, reason);
+ }
+
+ opP->data.accept.asock = accSock;
+
+ /* According to the (Microsoft) documentation, the buffer size of the
+ * local and remote address must be 16 bytes *more* than the size of
+ * the sockaddr structure for the transport protocol in use.
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_accept {%d} -> "
+ "try calculate address and address buffer size(s)\r\n",
+ descP->sock) );
+ switch (descP->domain) {
+ case AF_INET:
+ addrSz = sizeof(struct sockaddr_in) + 16;
+ break;
+ case AF_INET6:
+ addrSz = sizeof(struct sockaddr_in6) + 16;
+ break;
+ default:
+ return esock_make_error_invalid(env, esock_atom_domain);
+ break;
+ }
+ bufSz = 2 * addrSz;
+
+ opP->data.accept.buf = MALLOC( bufSz );
+ ESOCK_ASSERT( opP->data.accept.buf != NULL);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_accept(%T, %d) -> try accept\r\n",
+ sockRef, descP->sock) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_tries, &descP->accTries, 1);
+
+ ares = sock_accept_O(descP->sock, accSock,
+ opP->data.accept.buf,
+ addrSz,
+ &recvBytes,
+ (OVERLAPPED*) opP);
+
+ eres = accept_check_result(env, descP, opP, ares,
+ sockRef, accRef, accSock, caller);
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_accept(%T, %d) -> done when"
+ "\r\n eres: %T"
+ "\r\n", sockRef, descP->sock, eres) );
+
+ return eres;
+}
+
+
+
+static
+ERL_NIF_TERM accept_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ BOOL ares,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef,
+ SOCKET accSock,
+ ErlNifPid caller)
+{
+ ERL_NIF_TERM eres;
+
+ if (ares) {
+
+ /* Success already!
+ * So, no need to store the data (in the "queue").
+ * And then allocate and initiate the new descriptor.
+ */
+
+ eres = esaio_accept_accepted(env, descP, caller, sockRef, accSock);
+
+ } else {
+
+ /* Accept returned error, check which */
+
+ int save_errno = sock_errno();
+
+ /* As pointed out above, there are basically two kinds of errors:
+ * 1) Pending:
+ * An overlapped operation was successfully initiated.
+ * Completion will be "indicated" at a later time.
+ * 2) An actual error
+ */
+
+ if (save_errno == WSA_IO_PENDING) {
+
+ /* We need to store the data in the queue! */
+
+ eres = accept_check_pending(env, descP, opP, caller,
+ sockRef, accRef);
+
+ } else {
+
+ eres = accept_check_fail(env, descP, opP, save_errno,
+ accSock, sockRef);
+
+ }
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "accept_check_result(%T, %d) -> done with"
+ "\r\n result: %T"
+ "\r\n", sockRef, descP->sock, eres) );
+
+ return eres;
+}
+
+
+
+static
+ERL_NIF_TERM accept_check_pending(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM accRef)
+{
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "accept_check_pending(%T, %d) -> entry with"
+ "\r\n accRef: %T"
+ "\r\n", sockRef, descP->sock, accRef) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_waits, &descP->accWaits, 1);
+
+ if (descP->acceptorsQ.first == NULL)
+ descP->readState |= ESOCK_STATE_ACCEPTING;
+
+ /* Will be picked up by the (worker) threads when the event comes */
+ esock_acceptor_push(env, descP, caller, accRef, opP);
+
+ return esock_atom_completion;
+
+}
+
+
+static
+ERL_NIF_TERM accept_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ int saveErrno,
+ SOCKET accSock,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM reason;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "accept_check_fail(%T, %d) -> entry with"
+ "\r\n errno: %d"
+ "\r\n (acc) socket: %d"
+ "\r\n", sockRef, descP->sock, saveErrno, accSock) );
+
+ reason = MKA(env, erl_errno_id(saveErrno));
+
+ /* Will an event be generetade in this case?
+ * Assume not => We need to clean up here!
+ */
+ esock_clear_env("esaio_accept", opP->env);
+ esock_free_env("esaio_accept", opP->env);
+ FREE( opP->data.accept.buf );
+ FREE( opP );
+
+ sock_close(accSock);
+ WSACleanup();
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_fails, &descP->accFails, 1);
+
+ return esock_make_error(env, reason);
+}
+
+
+
+/* *** esaio_accept_accepted ***
+ *
+ * Generic function handling a successful accept.
+ */
+static
+ERL_NIF_TERM esaio_accept_accepted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid pid,
+ ERL_NIF_TERM sockRef,
+ SOCKET accSock)
+{
+ ESockDescriptor* accDescP;
+ ERL_NIF_TERM accRef;
+ int save_errno;
+
+ /*
+ * We got one
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_accept_accepted(%T, %d) -> entry with"
+ "\r\n (accept) socket: %T"
+ "\r\n", sockRef, descP->sock, accSock) );
+
+ // Allocate the descriptor
+ accDescP = esock_alloc_descriptor(accSock);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_accept_accepted(%T, %d) -> add to completion port\r\n",
+ sockRef, descP->sock) );
+
+ if (ESAIO_OK != (save_errno = esaio_add_socket(accDescP))) {
+ // See esock_dtor for what needs done!
+ ERL_NIF_TERM tag = esock_atom_add_socket;
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(save_errno));
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_fails, &descP->accFails, 1);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_accept_accepted(%T, %d) -> "
+ "failed adding (accepted) socket to completion port: "
+ "%T (%d)\r\n", sockRef, descP->sock, reason, save_errno) );
+
+ esock_dealloc_descriptor(env, accDescP);
+ sock_close(accSock);
+
+ /* This should really be:
+ * {error, {invalid, {add_to_completion_port, Reason}}}
+ */
+
+ return esock_make_error_t2r(env, tag, reason);
+ }
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_acc_success, &descP->accSuccess, 1);
+
+ accDescP->domain = descP->domain;
+ accDescP->type = descP->type;
+ accDescP->protocol = descP->protocol;
+
+ MLOCK(descP->writeMtx);
+
+ accDescP->rBufSz = descP->rBufSz; // Inherit buffer size
+ accDescP->rCtrlSz = descP->rCtrlSz; // Inherit buffer size
+ accDescP->wCtrlSz = descP->wCtrlSz; // Inherit buffer size
+ accDescP->iow = descP->iow; // Inherit iow
+ accDescP->dbg = descP->dbg; // Inherit debug flag
+ accDescP->useReg = descP->useReg; // Inherit useReg flag
+ esock_inc_socket(accDescP->domain, accDescP->type, accDescP->protocol);
+
+ accRef = enif_make_resource(env, accDescP);
+ enif_release_resource(accDescP);
+
+ accDescP->ctrlPid = pid;
+ /* pid has actually been compared equal to self()
+ * in this code path just a little while ago
+ */
+ ESOCK_ASSERT( MONP("esaio_accept_accepted -> ctrl",
+ env, accDescP,
+ &accDescP->ctrlPid,
+ &accDescP->ctrlMon) == 0 );
+
+ accDescP->writeState |= ESOCK_STATE_CONNECTED;
+
+ MUNLOCK(descP->writeMtx);
+
+ /* And finally (maybe) update the registry */
+ if (descP->useReg) esock_send_reg_add_msg(env, descP, accRef);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_accept_accepted(%T, %d) -> done\r\n",
+ sockRef, descP->sock) );
+
+ return esock_make_ok2(env, accRef);
+
+}
+
+
+
+/* ========================================================================
+ * Do the actual send.
+ * Do some initial writer checks, do the actual send and then
+ * analyze the result.
+ *
+ * The following flags are "valid":
+ *
+ * MSG_DONTROUTE, MSG_PARTIAL, and MSG_OOB
+ *
+ */
+extern
+ERL_NIF_TERM esaio_send(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* sndDataP,
+ int flags)
+{
+ ErlNifPid caller;
+ ERL_NIF_TERM eres;
+ BOOLEAN_T cleanup = FALSE;
+ int wres;
+ DWORD toWrite;
+ char* buf;
+ DWORD f = (DWORD) flags;
+ ESAIOOperation* opP;
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (! IS_OPEN(descP->writeState)) {
+ ESOCK_EPRINTF("esaio_send(%T, %d) -> NOT OPEN\r\n",
+ sockRef, descP->sock);
+ return esock_make_error_closed(env);
+ }
+
+ /* Connect and Write can not be simultaneous? */
+ if (descP->connectorP != NULL) {
+ ESOCK_EPRINTF("esaio_send(%T, %d) -> CONNECTING\r\n",
+ sockRef, descP->sock);
+ return esock_make_error_invalid(env, esock_atom_state);
+ }
+
+ /* Ensure that this caller does not *already* have a
+ * (send) request waiting */
+ if (esock_writer_search4pid(env, descP, &caller)) {
+ /* Sender already in queue */
+ ESOCK_EPRINTF("esaio_send(%T, %d) -> ALREADY SENDING\r\n",
+ sockRef, descP->sock);
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ /* This is a size check,
+ * to ensure we do not try to send something *to* large */
+ toWrite = (DWORD) sndDataP->size;
+ if ((size_t) toWrite != sndDataP->size)
+ return esock_make_error_invalid(env, esock_atom_data_size);
+
+ /* Once the send function has been called, this memory
+ * is "owned" by the system. That is, we cannot free it
+ * (or do anything with it) until the *operation* has completed,
+ * so the free is done by the thread(s).
+ */
+ buf = MALLOC( toWrite );
+ ESOCK_ASSERT( buf != NULL );
+ sys_memcpy(buf, sndDataP->data, toWrite);
+
+ opP = MALLOC( sizeof(ESAIOOperation) );
+ ESOCK_ASSERT( opP != NULL);
+ sys_memzero((char*) opP, sizeof(ESAIOOperation));
+
+ opP->tag = ESAIO_OP_SEND;
+ /* Its a bit annoying that we have to alloc an env and then
+ * copy the ref *before* we know that we actually need it.
+ * But after the call is to late, so...
+ */
+ opP->env = esock_alloc_env("esaio-send - operation");
+ opP->data.send.sendRef = CP_TERM(opP->env, sendRef);
+ opP->data.send.sockRef = CP_TERM(opP->env, sockRef);
+ opP->data.send.wbuf.buf = buf;
+ opP->data.send.wbuf.len = toWrite;
+ opP->caller = caller;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_tries, &descP->writeTries, 1);
+
+ wres = sock_send_O(descP->sock, &opP->data.send.wbuf, f, (OVERLAPPED*) opP);
+
+ eres = send_check_result(env, descP, opP, caller,
+ wres, toWrite, FALSE,
+ sockRef, sendRef, &cleanup);
+
+ if (cleanup) {
+
+ /* "Manually" allocated buffer */
+ FREE( opP->data.send.wbuf.buf );
+
+ esock_clear_env("esaio_send - cleanup", opP->env);
+ esock_free_env("esaio_send - cleanup", opP->env);
+
+ FREE( opP );
+
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_send {%d} -> done (%s)"
+ "\r\n %T"
+ "\r\n", descP->sock, B2S(cleanup), eres) );
+
+ return eres;
+}
+
+
+
+/* ========================================================================
+ * Do the actual send.
+ * Do some initial writer checks, do the actual send and then
+ * analyze the result.
+ *
+ * The following flags are "valid":
+ *
+ * MSG_DONTROUTE, MSG_PARTIAL, and MSG_OOB
+ *
+ * "Explicit binding is discouraged for client applications."
+ */
+
+extern
+ERL_NIF_TERM esaio_sendto(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* sndDataP,
+ int flags,
+ ESockAddress* toAddrP,
+ SOCKLEN_T toAddrLen)
+{
+ ErlNifPid caller;
+ ERL_NIF_TERM eres;
+ BOOLEAN_T cleanup = FALSE;
+ int wres;
+ DWORD toWrite;
+ char* buf;
+ DWORD f = (DWORD) flags;
+ ESAIOOperation* opP;
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (! IS_OPEN(descP->writeState))
+ return esock_make_error_closed(env);
+
+ /* Connect and Write can not be simultaneous? */
+ if (descP->connectorP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Empty address to allowed */
+ if (toAddrP == NULL)
+ return esock_make_invalid(env, esock_atom_sockaddr);
+
+ /* Ensure that this caller does not *already* have a
+ * (send) request waiting */
+ if (esock_writer_search4pid(env, descP, &caller)) {
+ /* Sender already in queue */
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ /* This is a size check,
+ * to ensure we do not try to send something *to* large */
+ toWrite = (DWORD) sndDataP->size;
+ if ((size_t) toWrite != sndDataP->size)
+ return esock_make_error_invalid(env, esock_atom_data_size);
+
+ /* Once the send function has been called, this memory
+ * (buf) "belongs" to the "system" (so no need to free it).
+ */
+ buf = MALLOC( toWrite );
+ ESOCK_ASSERT( buf != NULL );
+ sys_memcpy(buf, sndDataP->data, toWrite);
+
+ opP = MALLOC( sizeof(ESAIOOperation) );
+ ESOCK_ASSERT( opP != NULL);
+ sys_memzero((char*) opP, sizeof(ESAIOOperation));
+
+ opP->tag = ESAIO_OP_SENDTO;
+ /* Its a bit annoying that we have to alloc an env and then
+ * copy the ref *before* we know that we actually need it.
+ * But after the call is to late, so...
+ */
+ opP->env = esock_alloc_env("esaio-sendto - operation");
+ opP->data.sendto.sendRef = CP_TERM(opP->env, sendRef);
+ opP->data.sendto.sockRef = CP_TERM(opP->env, sockRef);
+ opP->data.sendto.wbuf.buf = buf;
+ opP->data.sendto.wbuf.len = toWrite;
+ opP->data.sendto.remoteAddr = *toAddrP; // Do we need this?
+ opP->data.sendto.remoteAddrLen = toAddrLen;// Do we need this?
+ opP->caller = caller;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_tries, &descP->writeTries, 1);
+
+ wres = sock_sendto_O(descP->sock, &opP->data.sendto.wbuf, f,
+ (struct sockaddr*) toAddrP, toAddrLen,
+ (OVERLAPPED*) opP);
+
+ eres = send_check_result(env, descP, opP, caller,
+ wres, toWrite, FALSE,
+ sockRef, sendRef, &cleanup);
+
+ if (cleanup) {
+
+ /* "Manually" allocated buffer */
+ FREE( opP->data.sendto.wbuf.buf );
+
+ esock_clear_env("esaio_sendto - cleanup", opP->env);
+ esock_free_env("esaio_sendto - cleanup", opP->env);
+
+ FREE( opP );
+
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_sendto {%d} -> done (%s)"
+ "\r\n %T"
+ "\r\n", descP->sock, B2S(cleanup), eres) );
+
+ return eres;
+}
+
+
+
+/* ========================================================================
+ * Do the actual sendmsg.
+ * Do some initial writer checks, do the actual send and then
+ * analyze the result.
+ *
+ * The following flags are "valid":
+ *
+ * MSG_DONTROUTE, MSG_PARTIAL, and MSG_OOB
+ *
+ * "Explicit binding is discouraged for client applications."
+ *
+ * Also, according to Microsoft documentation:
+ *
+ * "can only be used with datagrams and raw sockets."
+ *
+ * So, should we check, or let the user crash and burn?
+ *
+ * Note that this operation *only* works for socket
+ * of types SOCK_DGRAM and SOCK_RAW! Should we check
+ * and throw 'enotsup' otherwise? Would make testing
+ * easier...
+ */
+
+extern
+ERL_NIF_TERM esaio_sendmsg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ERL_NIF_TERM eMsg,
+ int flags,
+ ERL_NIF_TERM eIOV,
+ const ESockData* dataP)
+{
+ ErlNifPid caller;
+ ERL_NIF_TERM eres;
+ BOOLEAN_T cleanup = FALSE;
+ int wres;
+ ERL_NIF_TERM tail;
+ ERL_NIF_TERM eAddr, eCtrl;
+ ssize_t dataSize;
+ size_t ctrlBufLen, ctrlBufUsed;
+ WSABUF* wbufs = NULL;
+ ESAIOOperation* opP = NULL;
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_sendmsg(%T, %d) -> entry with"
+ "\r\n", sockRef, descP->sock) );
+
+ /* This *only* works on socket type(s) DGRAM or RAW.
+ * Other socket types results in einval, which is not very
+ * helpful. So, in order to, atleast, help with testing,
+ * we do this...
+ */
+ if (! ((descP->type == SOCK_DGRAM) || (descP->type == SOCK_RAW))) {
+ return enif_raise_exception(env, MKA(env, "notsup"));
+ }
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (! IS_OPEN(descP->writeState))
+ return esock_make_error_closed(env);
+
+ /* Connect and Write can not be simultaneous? */
+ if (descP->connectorP != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Ensure that this caller does not *already* have a
+ * (send) request waiting */
+ if (esock_writer_search4pid(env, descP, &caller)) {
+ /* Sender already in queue */
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ opP = MALLOC( sizeof(ESAIOOperation) );
+ ESOCK_ASSERT( opP != NULL);
+ sys_memzero((char*) opP, sizeof(ESAIOOperation));
+
+ opP->tag = ESAIO_OP_SENDMSG;
+
+ if (! init_sendmsg_sockaddr(env, descP, eMsg,
+ &opP->data.sendmsg.msg,
+ &opP->data.sendmsg.addr)) {
+
+ FREE( opP );
+
+ return esock_make_invalid(env, esock_atom_addr);
+ }
+
+ /* Its a bit annoying that we have to alloc an env and then
+ * copy the ref *before* we know that we actually need it.
+ * How much does this cost?
+ */
+ opP->env = esock_alloc_env("esaio_sendmsg - operation");
+
+ /* Extract the *mandatory* 'iov', which must be an erlang:iovec(),
+ * from which we take at most IOV_MAX binaries.
+ * The env *cannot* be NULL because we don't actually know if
+ * the send succeeds *now*. It could be sceduled!
+ */
+ if ((! enif_inspect_iovec(opP->env,
+ dataP->iov_max, eIOV, &tail,
+ &opP->data.sendmsg.iovec))) {
+
+ SSDBG( descP, ("WIN-ESAIO",
+ "essaio_sendmsg {%d} -> not an iov\r\n",
+ descP->sock) );
+
+ esock_free_env("esaio-sendmsg - iovec failure", opP->env);
+ FREE( opP );
+
+ return esock_make_error_invalid(env, esock_atom_iov);
+ }
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_sendmsg {%d} ->"
+ "\r\n iovcnt: %lu"
+ "\r\n tail: %s"
+ "\r\n", descP->sock,
+ (unsigned long) opP->data.sendmsg.iovec->iovcnt,
+ B2S(! enif_is_empty_list(opP->env, tail))) );
+
+
+ /* We now have an allocated iovec - verify vector size */
+
+ if (! verify_sendmsg_iovec_size(dataP, descP, opP->data.sendmsg.iovec)) {
+
+ /* We can not send the whole packet in one sendmsg() call */
+ SSDBG( descP, ("WIN-ESAIO",
+ "esaio_sendmsg {%d} -> iovcnt > iov_max\r\n",
+ descP->sock) );
+
+ // No need - belongs to op env: FREE_IOVEC( opP->data.sendmsg.iovec );
+ esock_free_env("esaio-sendmsg - iovec failure", opP->env);
+ FREE( opP );
+
+ return esock_make_error_invalid(env, esock_atom_iov);
+ }
+
+
+ /* Verify that we can send the entire message.
+ * On DGRAM the tail must be "empty" (= everything must fit in one message).
+ */
+ if (! verify_sendmsg_iovec_tail(opP->env, descP, &tail)) {
+
+ // No need - belongs to op env: FREE_IOVEC( opP->data.sendmsg.iovec );
+ esock_free_env("esaio-sendmsg - iovec tail failure", opP->env);
+ FREE( opP );
+
+ return esock_make_error_invalid(env, esock_atom_iov);
+
+ }
+
+ if (! check_sendmsg_iovec_overflow(descP,
+ opP->data.sendmsg.iovec, &dataSize)) {
+
+ // No need - belongs to op env: FREE_IOVEC( opP->data.sendmsg.iovec );
+ esock_free_env("esaio-sendmsg - iovec size failure", opP->env);
+ FREE( opP );
+
+ return esock_make_error_invalid(env, esock_atom_iov);
+
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_sendmsg {%d} -> iovec size verified"
+ "\r\n iov length: %lu"
+ "\r\n data size: %u"
+ "\r\n",
+ descP->sock,
+ (unsigned long) opP->data.sendmsg.iovec->iovcnt,
+ (long) dataSize) );
+
+ wbufs = MALLOC(opP->data.sendmsg.iovec->iovcnt * sizeof(WSABUF));
+ ESOCK_ASSERT( wbufs != NULL );
+ for (int i = 0; i < opP->data.sendmsg.iovec->iovcnt; i++) {
+ wbufs[i].len = opP->data.sendmsg.iovec->iov[i].iov_len;
+ wbufs[i].buf = opP->data.sendmsg.iovec->iov[i].iov_base;
+ }
+
+ opP->data.sendmsg.msg.lpBuffers = wbufs;
+ opP->data.sendmsg.msg.dwBufferCount = opP->data.sendmsg.iovec->iovcnt;
+
+ /* And now for the control headers - some default first */
+ eCtrl = esock_atom_undefined;
+ ctrlBufLen = 0;
+ opP->data.sendmsg.ctrlBuf = NULL;
+
+ /* Extract the *optional* 'ctrl' out of the eMsg map */
+ if (GET_MAP_VAL(env, eMsg, esock_atom_ctrl, &eCtrl)) {
+ ctrlBufLen = descP->wCtrlSz;
+ opP->data.sendmsg.ctrlBuf = (char*) MALLOC(ctrlBufLen);
+ ESOCK_ASSERT( opP->data.sendmsg.ctrlBuf != NULL );
+ }
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_sendmsg {%d} -> optional ctrl: "
+ "\r\n ctrlBuf: %p"
+ "\r\n ctrlBufLen: %lu"
+ "\r\n eCtrl: %T"
+ "\r\n", descP->sock,
+ opP->data.sendmsg.ctrlBuf,
+ (unsigned long) ctrlBufLen, eCtrl) );
+
+ if (opP->data.sendmsg.ctrlBuf != NULL) {
+ if (! decode_cmsghdrs(env, descP,
+ eCtrl,
+ opP->data.sendmsg.ctrlBuf, ctrlBufLen,
+ &ctrlBufUsed)) {
+
+ FREE( opP->data.sendmsg.ctrlBuf );
+ FREE( opP->data.sendmsg.msg.lpBuffers );
+ // No need - belongs to op env: FREE_IOVEC( opP->data.sendmsg.iovec );
+ esock_free_env("esaio-sendmsg - iovec size failure", opP->env);
+ FREE( opP );
+
+ return esock_make_invalid(env, esock_atom_ctrl);
+ }
+ } else {
+ ctrlBufUsed = 0;
+ }
+ opP->data.sendmsg.msg.Control.len = ctrlBufUsed;
+ opP->data.sendmsg.msg.Control.buf = opP->data.sendmsg.ctrlBuf;
+
+ /* We do not yet handle the flags (see function header above),
+ * so zero it just in case. */
+ opP->data.sendmsg.msg.dwFlags = 0;
+
+ opP->tag = ESAIO_OP_SENDMSG;
+ opP->caller = caller;
+ opP->data.sendmsg.sockRef = CP_TERM(opP->env, sockRef);
+ opP->data.sendmsg.sendRef = CP_TERM(opP->env, sendRef);
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_tries, &descP->writeTries, 1);
+
+ wres = sock_sendmsg_O(descP->sock, &opP->data.sendmsg.msg, flags,
+ (OVERLAPPED*) opP);
+
+ eres = send_check_result(env, descP, opP, caller,
+ wres, dataSize,
+ (! enif_is_empty_list(opP->env, tail)),
+ sockRef, sendRef, &cleanup);
+
+ if (cleanup) {
+
+ /* "Manually" allocated buffers */
+ FREE( opP->data.sendmsg.msg.lpBuffers );
+ if (opP->data.sendmsg.ctrlBuf != NULL)
+ FREE( opP->data.sendmsg.ctrlBuf );
+
+ /* The i/o vector belongs to the op env,
+ * so it goes when the env goes.
+ */
+ esock_clear_env("esaio_sendto - cleanup", opP->env);
+ esock_free_env("esaio_sendto - cleanup", opP->env);
+
+ FREE( opP );
+
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_sendmsg {%d} -> done (%s)"
+ "\r\n %T"
+ "\r\n", descP->sock, B2S(cleanup), eres) );
+
+ return eres;
+}
+
+
+static
+BOOLEAN_T init_sendmsg_sockaddr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eMsg,
+ WSAMSG* msgP,
+ ESockAddress* addrP)
+{
+ ERL_NIF_TERM eAddr;
+
+ if (! GET_MAP_VAL(env, eMsg, esock_atom_addr, &eAddr)) {
+
+ SSDBG( descP, ("WIN-ESAIO",
+ "init_sendmsg_sockaddr {%d} -> no address\r\n",
+ descP->sock) );
+
+ msgP->name = NULL;
+ msgP->namelen = 0;
+
+ } else {
+
+ SSDBG( descP, ("WIN-ESAIO", "init_sendmsg_sockaddr {%d} ->"
+ "\r\n address: %T"
+ "\r\n", descP->sock, eAddr) );
+
+ msgP->name = (void*) addrP;
+ msgP->namelen = sizeof(ESockAddress);
+ sys_memzero((char *) msgP->name, msgP->namelen);
+
+ if (! esock_decode_sockaddr(env, eAddr,
+ (ESockAddress*) msgP->name,
+ (SOCKLEN_T*) &msgP->namelen)) {
+
+ SSDBG( descP, ("WIN-ESAIO",
+ "init_sendmsg_sockaddr {%d} -> invalid address\r\n",
+ descP->sock) );
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+
+}
+
+
+
+static
+BOOLEAN_T verify_sendmsg_iovec_size(const ESockData* dataP,
+ ESockDescriptor* descP,
+ ErlNifIOVec* iovec)
+{
+ if (iovec->iovcnt > dataP->iov_max) {
+ if (descP->type == SOCK_STREAM) {
+ iovec->iovcnt = dataP->iov_max;
+ } else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+
+
+static
+BOOLEAN_T verify_sendmsg_iovec_tail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM* tail)
+{
+ ERL_NIF_TERM h, t, tmp = *tail;
+ ErlNifBinary bin;
+
+ /* Find out if there is remaining data in the tail.
+ * Skip empty binaries otherwise break.
+ * If 'tail' after loop exit is the empty list
+ * there was no more data. Otherwise there is more
+ * data or the 'iov' is invalid.
+ */
+
+ for (;;) {
+ if (enif_get_list_cell(env, tmp, &h, &t) &&
+ enif_inspect_binary(env, h, &bin) &&
+ (bin.size == 0)) {
+ tmp = t;
+ continue;
+ } else
+ break;
+ }
+
+ *tail = tmp;
+
+ if ((! enif_is_empty_list(env, tmp)) &&
+ (descP->type != SOCK_STREAM)) {
+
+ /* We can not send the whole packet in one sendmsg() call */
+ SSDBG( descP, ("WIN-ESAIO",
+ "essio_sendmsg {%d} -> invalid tail\r\n",
+ descP->sock) );
+
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+
+
+static
+BOOLEAN_T check_sendmsg_iovec_overflow(ESockDescriptor* descP,
+ ErlNifIOVec* iovec,
+ ssize_t* dataSize)
+{
+ ssize_t dsz = 0;
+ size_t i;
+
+ for (i = 0; i < iovec->iovcnt; i++) {
+ size_t len = iovec->iov[i].iov_len;
+ dsz += len;
+ if (dsz < len) {
+
+ /* Overflow */
+
+ SSDBG( descP, ("WIN-ESAIO",
+ "verify_sendmsg_iovec_size {%d} -> Overflow"
+ "\r\n i: %lu"
+ "\r\n len: %lu"
+ "\r\n dataSize: %ld"
+ "\r\n", descP->sock, (unsigned long) i,
+ (unsigned long) len, (long) dsz) );
+
+ *dataSize = dsz;
+
+ return FALSE;
+
+ }
+ }
+
+ *dataSize = dsz;
+
+ return TRUE;
+
+}
+
+
+
+/* *** Control message utility functions *** */
+
+/* +++ decode_cmsghdrs +++
+ *
+ * Decode a list of cmsg(). There can be 0 or more "blocks".
+ *
+ * Each element can either be a (erlang) map that needs to be decoded,
+ * or a (erlang) binary that just needs to be appended to the control
+ * buffer.
+ *
+ * Our "problem" is that we have no idea how much memory we actually need.
+ *
+ */
+
+static
+BOOLEAN_T decode_cmsghdrs(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eCMsg,
+ char* cmsgHdrBufP,
+ size_t cmsgHdrBufLen,
+ size_t* cmsgHdrBufUsed)
+{
+ ERL_NIF_TERM elem, tail, list;
+ char* bufP;
+ size_t rem, used, totUsed = 0;
+ unsigned int len;
+ int i;
+
+ SSDBG( descP, ("WIN-ESAIO", "decode_cmsghdrs {%d} -> entry with"
+ "\r\n eCMsg: %T"
+ "\r\n cmsgHdrBufP: 0x%lX"
+ "\r\n cmsgHdrBufLen: %d"
+ "\r\n", descP->sock,
+ eCMsg, cmsgHdrBufP, cmsgHdrBufLen) );
+
+ if (! GET_LIST_LEN(env, eCMsg, &len))
+ return FALSE;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdrs {%d} -> list length: %d\r\n",
+ descP->sock, len) );
+
+ for (i = 0, list = eCMsg, rem = cmsgHdrBufLen, bufP = cmsgHdrBufP;
+ i < len; i++) {
+
+ SSDBG( descP, ("WIN-ESAIO", "decode_cmsghdrs {%d} -> process elem %d:"
+ "\r\n (buffer) rem: %u"
+ "\r\n (buffer) totUsed: %u"
+ "\r\n", descP->sock, i, rem, totUsed) );
+
+ /* Extract the (current) head of the (cmsg hdr) list */
+ if (! GET_LIST_ELEM(env, list, &elem, &tail))
+ return FALSE;
+
+ used = 0; // Just in case...
+ if (! decode_cmsghdr(env, descP, elem, bufP, rem, &used))
+ return FALSE;
+
+#ifdef __WIN32__
+ bufP = CHARP( bufP + used );
+#else
+ bufP = CHARP( ULONG(bufP) + used );
+#endif
+ rem = SZT( rem - used );
+ list = tail;
+ totUsed += used;
+
+ }
+
+ *cmsgHdrBufUsed = totUsed;
+
+ SSDBG( descP, ("WIN-ESAIO", "decode_cmsghdrs {%d} -> done"
+ "\r\n all %u ctrl headers processed"
+ "\r\n totUsed = %lu\r\n",
+ descP->sock, len, (unsigned long) totUsed) );
+
+ return TRUE;
+}
+
+
+
+/* +++ decode_cmsghdr +++
+ *
+ * Decode one cmsg(). Put the "result" into the buffer and advance the
+ * pointer (of the buffer) afterwards. Also update 'rem' accordingly.
+ * But before the actual decode, make sure that there is enough room in
+ * the buffer for the cmsg header (sizeof(*hdr) < rem).
+ *
+ * The eCMsg should be a map with three fields:
+ *
+ * level :: socket | protocol() | integer()
+ * type :: atom() | integer()
+ * What values are valid depend on the level
+ * data :: binary() | integer() | boolean()
+ * The type of the data depends on
+ * or level and type, but can be a binary,
+ * which means that the data is already coded.
+ * value :: term() Which is a term matching the decode function
+ */
+
+static
+BOOLEAN_T decode_cmsghdr(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM eCMsg,
+ char* bufP,
+ size_t rem,
+ size_t* used)
+{
+ ERL_NIF_TERM eLevel, eType, eData, eValue;
+ int level;
+
+ SSDBG( descP, ("WIN-ESAIO", "decode_cmsghdr {%d} -> entry with"
+ "\r\n eCMsg: %T"
+ "\r\n", descP->sock, eCMsg) );
+
+ // Get 'level' field
+ if (! GET_MAP_VAL(env, eCMsg, esock_atom_level, &eLevel))
+ return FALSE;
+ SSDBG( descP, ("WIN-ESAIO", "decode_cmsghdr {%d} -> eLevel: %T"
+ "\r\n", descP->sock, eLevel) );
+
+ // Get 'type' field
+ if (! GET_MAP_VAL(env, eCMsg, esock_atom_type, &eType))
+ return FALSE;
+ SSDBG( descP, ("WIN-ESAIO", "decode_cmsghdr {%d} -> eType: %T"
+ "\r\n", descP->sock, eType) );
+
+ // Decode Level
+ if (! esock_decode_level(env, eLevel, &level))
+ return FALSE;
+ SSDBG( descP, ("WIN-ESAIO", "decode_cmsghdr {%d}-> level: %d\r\n",
+ descP->sock, level) );
+
+ // Get 'data' field
+ if (! GET_MAP_VAL(env, eCMsg, esock_atom_data, &eData)) {
+
+ // Get 'value' field
+ if (! GET_MAP_VAL(env, eCMsg, esock_atom_value, &eValue))
+ return FALSE;
+ SSDBG( descP, ("WIN-ESAIO", "decode_cmsghdr {%d} -> eValue: %T"
+ "\r\n", descP->sock, eValue) );
+
+ // Decode Value
+ if (! decode_cmsghdr_value(env, descP, level, eType, eValue,
+ bufP, rem, used))
+ return FALSE;
+
+ } else {
+
+ // Verify no 'value' field
+ if (GET_MAP_VAL(env, eCMsg, esock_atom_value, &eValue))
+ return FALSE;
+
+ SSDBG( descP, ("WIN-ESAIO", "decode_cmsghdr {%d} -> eData: %T"
+ "\r\n", descP->sock, eData) );
+
+ // Decode Data
+ if (! decode_cmsghdr_data(env, descP, level, eType, eData,
+ bufP, rem, used))
+ return FALSE;
+ }
+
+ SSDBG( descP, ("WIN-ESAIO", "decode_cmsghdr {%d}-> used: %lu\r\n",
+ descP->sock, (unsigned long) *used) );
+
+ return TRUE;
+}
+
+
+static
+BOOLEAN_T decode_cmsghdr_value(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ ERL_NIF_TERM eType,
+ ERL_NIF_TERM eValue,
+ char* bufP,
+ size_t rem,
+ size_t* usedP)
+{
+ int type;
+ struct cmsghdr* cmsgP = (struct cmsghdr *) bufP;
+ ESockCmsgSpec* cmsgTable;
+ ESockCmsgSpec* cmsgSpecP = NULL;
+ size_t num = 0;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdr_value {%d} -> entry \r\n"
+ " eType: %T\r\n"
+ " eValue: %T\r\n",
+ descP->sock, eType, eValue) );
+
+ // We have decode functions only for symbolic (atom) types
+ if (! IS_ATOM(env, eType)) {
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdr_value {%d} -> FALSE:\r\n"
+ " eType not an atom\r\n",
+ descP->sock) );
+ return FALSE;
+ }
+
+ /* Try to look up the symbolic type
+ */
+ if (((cmsgTable = esock_lookup_cmsg_table(level, &num)) == NULL) ||
+ ((cmsgSpecP = esock_lookup_cmsg_spec(cmsgTable, num, eType)) == NULL) ||
+ (cmsgSpecP->decode == NULL)) {
+
+ /* We found no table for this level,
+ * we found no symbolic type in the level table,
+ * or no decode function for this type
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdr_value {%d} -> FALSE:\r\n"
+ " cmsgTable: %p\r\n"
+ " cmsgSpecP: %p\r\n",
+ descP->sock, cmsgTable, cmsgSpecP) );
+
+ return FALSE;
+ }
+
+ if (! cmsgSpecP->decode(env, eValue, cmsgP, rem, usedP)) {
+ // Decode function failed
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdr_value {%d} -> FALSE:\r\n"
+ " decode function failed\r\n",
+ descP->sock) );
+ return FALSE;
+ }
+
+ // Successful decode
+
+ type = cmsgSpecP->type;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdr_value {%d} -> TRUE:\r\n"
+ " level: %d\r\n"
+ " type: %d\r\n",
+ " *usedP: %lu\r\n",
+ descP->sock, level, type, (unsigned long) *usedP) );
+
+ cmsgP->cmsg_level = level;
+ cmsgP->cmsg_type = type;
+ return TRUE;
+}
+
+
+static
+BOOLEAN_T decode_cmsghdr_data(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int level,
+ ERL_NIF_TERM eType,
+ ERL_NIF_TERM eData,
+ char* bufP,
+ size_t rem,
+ size_t* usedP)
+{
+ int type;
+ ErlNifBinary bin;
+ struct cmsghdr* cmsgP = (struct cmsghdr *) bufP;
+ ESockCmsgSpec* cmsgSpecP = NULL;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdr_data {%d} -> entry \r\n"
+ " eType: %T\r\n"
+ " eData: %T\r\n",
+ descP->sock, eType, eData) );
+
+ // Decode Type
+ if (! GET_INT(env, eType, &type)) {
+ ESockCmsgSpec* cmsgTable = NULL;
+ size_t num = 0;
+
+ /* Try to look up the symbolic (atom) type
+ */
+ if ((! IS_ATOM(env, eType)) ||
+ ((cmsgTable = esock_lookup_cmsg_table(level, &num)) == NULL) ||
+ ((cmsgSpecP = esock_lookup_cmsg_spec(cmsgTable, num, eType)) == NULL)) {
+ /* Type was not an atom,
+ * we found no table for this level,
+ * or we found no symbolic type in the level table
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdr_data {%d} -> FALSE:\r\n"
+ " cmsgTable: %p\r\n"
+ " cmsgSpecP: %p\r\n",
+ descP->sock, cmsgTable, cmsgSpecP) );
+ return FALSE;
+ }
+
+ type = cmsgSpecP->type;
+ }
+
+ // Decode Data
+ if (GET_BIN(env, eData, &bin)) {
+ void *p;
+
+ p = esock_init_cmsghdr(cmsgP, rem, bin.size, usedP);
+ if (p == NULL) {
+ /* No room for the data
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdr_data {%d} -> FALSE:\r\n"
+ " rem: %lu\r\n"
+ " bin.size: %lu\r\n",
+ descP->sock,
+ (unsigned long) rem,
+ (unsigned long) bin.size) );
+ return FALSE;
+ }
+
+ // Copy the binary data
+ sys_memcpy(p, bin.data, bin.size);
+
+ } else if ((! esock_cmsg_decode_int(env, eData, cmsgP, rem, usedP)) &&
+ (! esock_cmsg_decode_bool(env, eData, cmsgP, rem, usedP))) {
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdr_data {%d} -> FALSE\r\n",
+ descP->sock) );
+ return FALSE;
+ }
+
+ // Successful decode
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "decode_cmsghdr_data {%d} -> TRUE:\r\n"
+ " level: %d\r\n"
+ " type: %d\r\n"
+ " *usedP: %lu\r\n",
+ descP->sock, level, type, (unsigned long) *usedP) );
+
+ cmsgP->cmsg_level = level;
+ cmsgP->cmsg_type = type;
+ return TRUE;
+}
+
+
+
+
+/* +++ encode_msg +++
+ *
+ * Encode a msg() (recvmsg). In erlang its represented as
+ * a map, which has a specific set of attributes:
+ *
+ * addr (source address) - sockaddr()
+ * iov - [binary()]
+ * ctrl - [cmsg()]
+ * flags - msg_flags()
+ */
+
+static
+void encode_msg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ssize_t read,
+ WSAMSG* msgP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM* eMsg)
+{
+ ERL_NIF_TERM addr, iov, ctrl, flags;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "encode_msg {%d} -> entry with"
+ "\r\n read: %ld"
+ "\r\n", descP->sock, (long) read) );
+
+ /* The address is not used if we are connected (unless, maybe,
+ * family is 'local'), so check (length = 0) before we try to encodel
+ */
+ if (msgP->namelen != 0) {
+ esock_encode_sockaddr(env,
+ (ESockAddress*) msgP->name,
+ msgP->namelen,
+ &addr);
+ } else {
+ addr = esock_atom_undefined;
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "encode_msg {%d} -> encode iov"
+ "\r\n num vectors: %lu"
+ "\r\n", descP->sock, (unsigned long) msgP->dwBufferCount) );
+
+ esock_encode_iov(env, read,
+ (SysIOVec*) msgP->lpBuffers, msgP->dwBufferCount, dataBufP,
+ &iov);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "encode_msg {%d} -> try encode cmsgs\r\n",
+ descP->sock) );
+
+ encode_cmsgs(env, descP, ctrlBufP, msgP, &ctrl);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "encode_msg {%d} -> try encode flags\r\n",
+ descP->sock) );
+
+ esock_encode_msg_flags(env, descP, msgP->dwFlags, &flags);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "encode_msg {%d} -> components encoded:"
+ "\r\n addr: %T"
+ "\r\n ctrl: %T"
+ "\r\n flags: %T"
+ "\r\n", descP->sock, addr, ctrl, flags) );
+
+ {
+ ERL_NIF_TERM keys[] = {esock_atom_iov,
+ esock_atom_ctrl,
+ esock_atom_flags,
+ esock_atom_addr};
+ ERL_NIF_TERM vals[] = {iov, ctrl, flags, addr};
+ size_t numKeys = NUM(keys);
+
+ ESOCK_ASSERT( numKeys == NUM(vals) );
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "encode_msg {%d} -> create map\r\n",
+ descP->sock) );
+
+ if (msgP->namelen == 0)
+ numKeys--; // No addr
+ ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eMsg) );
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "encode_msg {%d}-> map encoded\r\n",
+ descP->sock) );
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "encode_msg {%d} -> done\r\n", descP->sock) );
+}
+
+
+
+/* +++ encode_cmsgs +++
+ *
+ * Encode a list of cmsg(). There can be 0 or more cmsghdr "blocks".
+ *
+ * Our "problem" is that we have no idea how many control messages
+ * we have.
+ *
+ * The cmsgHdrP arguments points to the start of the control data buffer,
+ * an actual binary. Its the only way to create sub-binaries. So, what we
+ * need to continue processing this is to turn that into an binary erlang
+ * term (which can then in turn be turned into sub-binaries).
+ *
+ * We need the cmsgBufP (even though cmsgHdrP points to it) to be able
+ * to create sub-binaries (one for each cmsg hdr).
+ *
+ * The TArray (term array) is created with the size of 128, which should
+ * be enough. But if its not, then it will be automatically realloc'ed during
+ * add. Once we are done adding hdr's to it, we convert the tarray to a list.
+ */
+
+static
+void encode_cmsgs(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifBinary* cmsgBinP,
+ WSAMSG* msgP,
+ ERL_NIF_TERM* eCMsg)
+{
+ ERL_NIF_TERM ctrlBuf = MKBIN(env, cmsgBinP); // The *entire* binary
+ SocketTArray cmsghdrs = TARRAY_CREATE(128);
+ WSACMSGHDR* firstP = ESOCK_CMSG_FIRSTHDR(msgP);
+ WSACMSGHDR* currentP;
+
+ SSDBG( descP, ("WIN-ESAIO", "encode_cmsgs {%d} -> entry when"
+ "\r\n msg ctrl len: %d"
+ "\r\n (ctrl) firstP: 0x%lX"
+ "\r\n", descP->sock, msgP->Control.len, firstP) );
+
+ for (currentP = firstP;
+ (currentP != NULL);
+ /* nifs\win32\win_socket_asyncio.c(3167):
+ * warning C4116: unnamed type definition in parentheses
+ */
+ currentP = ESOCK_CMSG_NXTHDR(msgP, currentP)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "encode_cmsgs {%d} -> process cmsg header when"
+ "\r\n TArray Size: %d"
+ "\r\n", descP->sock, TARRAY_SZ(cmsghdrs)) );
+
+ /* MUST check this since on Linux the returned "cmsg" may actually
+ * go too far!
+ */
+ if (((CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP)) >
+ msgP->Control.len) {
+
+ /* Ouch, fatal error - give up
+ * We assume we cannot trust any data if this is wrong.
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "encode_cmsgs {%d} -> check failed when: "
+ "\r\n currentP: 0x%lX"
+ "\r\n (current) cmsg_len: %d"
+ "\r\n firstP: 0x%lX"
+ "\r\n => %d"
+ "\r\n msg ctrl len: %d"
+ "\r\n", descP->sock,
+ CHARP(currentP), currentP->cmsg_len, CHARP(firstP),
+ (CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP),
+ msgP->Control.len) );
+
+ TARRAY_ADD(cmsghdrs, esock_atom_bad_data);
+ break;
+
+ } else {
+ unsigned char* dataP = UCHARP(ESOCK_CMSG_DATA(currentP));
+ size_t dataPos = dataP - cmsgBinP->data;
+ size_t dataLen =
+ (UCHARP(currentP) + currentP->cmsg_len) - dataP;
+ ERL_NIF_TERM
+ cmsgHdr,
+ keys[] =
+ {esock_atom_level,
+ esock_atom_type,
+ esock_atom_data,
+ esock_atom_value},
+ vals[NUM(keys)];
+ size_t numKeys = NUM(keys);
+ BOOLEAN_T have_value;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "encode_cmsgs {%d} -> cmsg header data: "
+ "\r\n dataPos: %d"
+ "\r\n dataLen: %d"
+ "\r\n", descP->sock, dataPos, dataLen) );
+
+ vals[0] = esock_encode_level(env, currentP->cmsg_level);
+ vals[2] = MKSBIN(env, ctrlBuf, dataPos, dataLen);
+ have_value = esock_encode_cmsg(env,
+ currentP->cmsg_level,
+ currentP->cmsg_type,
+ dataP, dataLen, &vals[1], &vals[3]);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "encode_cmsgs {%d} -> "
+ "\r\n %T: %T"
+ "\r\n %T: %T"
+ "\r\n %T: %T"
+ "\r\n", descP->sock,
+ keys[0], vals[0], keys[1], vals[1], keys[2], vals[2]) );
+ if (have_value)
+ SSDBG( descP,
+ ("WIN-ESAIO", "encode_cmsgs {%d} -> "
+ "\r\n %T: %T"
+ "\r\n", descP->sock, keys[3], vals[3]) );
+
+ /* Guard against cut-and-paste errors */
+ ESOCK_ASSERT( numKeys == NUM(vals) );
+ ESOCK_ASSERT( MKMA(env, keys, vals,
+ numKeys - (have_value ? 0 : 1), &cmsgHdr) );
+
+ /* And finally add it to the list... */
+ TARRAY_ADD(cmsghdrs, cmsgHdr);
+ }
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "encode_cmsgs {%d} -> cmsg headers processed when"
+ "\r\n TArray Size: %d"
+ "\r\n", descP->sock, TARRAY_SZ(cmsghdrs)) );
+
+ /* The tarray is populated - convert it to a list */
+ TARRAY_TOLIST(cmsghdrs, env, eCMsg);
+}
+
+
+
+/* ====== Receive functions ====== */
+
+
+/* ========================================================================
+ * The (read) buffer handling should be optimized!
+ * But for now we make it easy for ourselves by
+ * allocating a binary (of the specified or default
+ * size) and then throwing it away...
+ *
+ * The following flags are supported (with overlapped):
+ * MSG_OOB - Processes OOB data.
+ * MSG_PARTIAL - *message-oriented* sockets only.
+ * Both intput to *and* output from recv.
+ * MSG_PUSH_IMMEDIATE - *stream-oriented* sockets only.
+ * Hint rather than an actual guarantee.
+ * MSG_WAITALL - *stream-oriented* sockets only.
+ * The request will complete only when one of
+ * the following conditions apply:
+ * - buffer is completely full.
+ * - The connection has been closed.
+ * - The request has been canceled or an error occurred.
+ */
+extern
+ERL_NIF_TERM esaio_recv(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t len,
+ int flags)
+{
+ ErlNifPid caller;
+ ESAIOOperation* opP;
+ int rres;
+ WSABUF wbuf;
+ DWORD f = flags;
+ size_t bufSz = (len != 0 ? len : descP->rBufSz);
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_recv {%d} -> entry with"
+ "\r\n length: %ld"
+ "\r\n (buffer) size: %lu"
+ "\r\n", descP->sock,
+ (long) len, (unsigned long) bufSz) );
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (! IS_OPEN(descP->readState))
+ return esock_make_error_closed(env);
+
+ /* Accept and Read can not be simultaneous? */
+ if (descP->acceptorsQ.first != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Ensure that this caller does not *already* have a
+ * (recv) request waiting */
+ if (esock_reader_search4pid(env, descP, &caller)) {
+ /* Reader already in queue */
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ /* Allocate the operation */
+ opP = MALLOC( sizeof(ESAIOOperation) );
+ ESOCK_ASSERT( opP != NULL);
+ sys_memzero((char*) opP, sizeof(ESAIOOperation));
+
+ opP->tag = ESAIO_OP_RECV;
+ /* Its a bit annoying that we have to alloc an env and then
+ * copy the ref *before* we know that we actually need it.
+ * How much does this cost?
+ */
+ opP->env = esock_alloc_env("esaio-recv - operation");
+ opP->data.recv.recvRef = CP_TERM(opP->env, recvRef);
+ opP->data.recv.sockRef = CP_TERM(opP->env, sockRef);
+ opP->caller = caller;
+
+ /* Allocate a buffer:
+ * Either as much as we want to read or (if zero (0)) use the "default"
+ * size (what has been configured).
+ */
+ ESOCK_ASSERT( ALLOC_BIN(bufSz, &opP->data.recv.buf) );
+
+ opP->data.recv.toRead = len;
+ wbuf.buf = opP->data.recv.buf.data;
+ wbuf.len = opP->data.recv.buf.size;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_tries, &descP->readTries, 1);
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_recv {%d} -> try read (%lu)\r\n",
+ descP->sock, (unsigned long) bufSz) );
+
+ rres = sock_recv_O(descP->sock, &wbuf, &f, (OVERLAPPED*) opP);
+
+ return recv_check_result(env, descP, opP, caller, rres,
+ sockRef, recvRef);
+}
+
+
+/* *** recv_check_result ***
+ *
+ * Analyze the result of a receive attempt.
+ * The receive may have been completed directly or scheduled (overlapped).
+ */
+static
+ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ int recv_result,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM eres;
+
+ if (recv_result == 0) {
+
+ /* +++ Success +++ */
+
+ eres = recv_check_ok(env, descP, opP, caller, sockRef, recvRef);
+
+ } else {
+ int err;
+
+ /* +++ Failure or pending +++ */
+
+ err = sock_errno();
+
+ /* As pointed out above, there are basically two kinds of errors:
+ * 1) Pending:
+ * An overlapped operation was successfully initiated.
+ * Completion will be indicated at a later time.
+ * 2) An actual error
+ */
+
+ if (err == WSA_IO_PENDING) {
+
+ if (! IS_ZERO(recvRef)) {
+
+ eres = recv_check_pending(env, descP, opP, caller,
+ sockRef, recvRef);
+
+ } else {
+
+ /* We are not allowed to wait! => cancel */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recv_check_result(%T, %d) -> "
+ "pending - but we are not allowed to wait => cancel"
+ "\r\n", sockRef, descP->sock) );
+
+ if (! CancelIoEx((HANDLE) descP->sock, (OVERLAPPED*) opP)) {
+ int save_errno = sock_errno();
+ ERL_NIF_TERM tag = esock_atom_cancel;
+ ERL_NIF_TERM reason = ENO2T(env, save_errno);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recv_check_result(%T, %d) -> "
+ "failed cancel pending operation"
+ "\r\n %T"
+ "\r\n", sockRef, descP->sock, reason) );
+
+ eres = esock_make_error(env, MKT2(env, tag, reason));
+
+ } else {
+
+ eres = esock_atom_ok;
+
+ }
+
+ }
+
+ } else {
+
+ eres = recv_check_fail(env, descP, opP, err, sockRef);
+
+ }
+
+ }
+
+ return eres;
+}
+
+
+
+/* *** recv_check_ok ***
+ *
+ * A successful recv. We *know* that in this case the buffer is filled!
+ */
+
+static
+ERL_NIF_TERM recv_check_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM data, result;
+ DWORD read = 0, flags = 0;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recv_check_ok -> try get overlapped result\r\n") );
+
+ if (get_recv_ovl_result(descP->sock, (OVERLAPPED*) opP, &read, &flags)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recv_check_ok -> overlapped success result: "
+ "\r\n read: %d"
+ "\r\n flags: 0x%X"
+ "\r\n", read, flags) );
+
+ (void) flags; // We should really do something with this...
+
+ /* <KOLLA>
+ *
+ * We need to handle read = 0 for other type(s) (DGRAM) when
+ * its actually valid to read 0 bytes.
+ *
+ * </KOLLA>
+ */
+
+ if ((read == 0) && (descP->type == SOCK_STREAM)) {
+
+ /*
+ * When a stream socket peer has performed an orderly
+ * shutdown, the return value will be 0 (the traditional
+ * "end-of-file" return).
+ *
+ * *We* do never actually try to read 0 bytes!
+ */
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_fails, &descP->readFails, 1);
+
+ result = esock_make_error(env, esock_atom_closed);
+
+ } else {
+
+ if (read == opP->data.recv.buf.size) {
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(env, &opP->data.recv.buf);
+
+ } else {
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(env, &opP->data.recv.buf);
+ data = MKSBIN(env, data, 0, read);
+
+ }
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ /* (maybe) Update max */
+ if (read > descP->readPkgMax)
+ descP->readPkgMax = read;
+
+
+ result = esock_make_ok2(env, data);
+
+ }
+
+ } else {
+
+ int save_errno = sock_errno();
+
+ switch (save_errno) {
+ case WSA_IO_INCOMPLETE:
+ /*
+ * WSA_IO_INCOMPLETE
+ *
+ * Even though it (the I/O Completion Port framework) told
+ * us it was done, it was not. So we need to postpone and let
+ * the (worker) threads deal with it anyway...effing framework...
+ */
+
+ if (! IS_ZERO(recvRef)) {
+
+ result = recv_check_pending(env, descP, opP, caller,
+ sockRef, recvRef);
+ } else {
+
+ /* But we are not allowed to wait! => cancel */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recv_check_ok(%T, %d) -> "
+ "incomplete - but we are not allowed to wait => cancel"
+ "\r\n", sockRef, descP->sock) );
+
+ if (! CancelIoEx((HANDLE) descP->sock, (OVERLAPPED*) opP)) {
+ int save_errno = sock_errno();
+ ERL_NIF_TERM tag = esock_atom_cancel;
+ ERL_NIF_TERM reason = ENO2T(env, save_errno);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recv_check_ok(%T, %d) -> "
+ "failed cancel incomplete operation"
+ "\r\n %T"
+ "\r\n", sockRef, descP->sock, reason) );
+
+ result = esock_make_error(env, MKT2(env, tag, reason));
+
+ } else {
+
+ result = esock_atom_ok; // Will trigger {error, timeout}
+
+ }
+ }
+ break;
+
+ default:
+ {
+ ERL_NIF_TERM eerrno = ENO2T(env, save_errno);
+ ERL_NIF_TERM reason = MKT2(env,
+ esock_atom_get_overlapped_result,
+ eerrno);
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_fails, &descP->readFails, 1);
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.genErrs, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+ result = esock_make_error(env, reason);
+ }
+ break;
+ }
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "recv_check_ok(%T) {%d} -> done with"
+ "\r\n result: %T"
+ "\r\n",
+ sockRef, descP->sock, result) );
+
+ return result;
+}
+
+
+
+/* *** recv_check_pending ***
+ *
+ * The recv operation was scheduled, that is, its now in the hands
+ * of the I/O Completion Port framework.
+ */
+static
+ERL_NIF_TERM recv_check_pending(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recv_check_pending(%T, %d) -> entry with"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, recvRef) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_waits, &descP->readWaits, 1);
+
+ descP->readState |= ESOCK_STATE_SELECTED;
+
+ esock_reader_push(env, descP, caller, recvRef, opP);
+
+ return esock_atom_completion;
+
+}
+
+
+
+/* *** recv_check_fail ***
+ *
+ * Processing done upon failed 'recv'.
+ * An actual failure.
+ */
+static
+ERL_NIF_TERM recv_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef)
+{
+ SSDBG( descP,
+ ("WIN-ESAIO", "recv_check_fail(%T) {%d} -> entry with"
+ "\r\n errno: %d"
+ "\r\n",
+ sockRef, descP->sock, saveErrno) );
+
+ FREE_BIN( &opP->data.recv.buf );
+
+ return recv_check_failure(env, descP, opP, saveErrno, sockRef);
+}
+
+
+
+/* *** recv_check_failure ***
+ *
+ * Processing done upon failed recv.
+ * An actual failure.
+ */
+static
+ERL_NIF_TERM recv_check_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "recv_check_failure(%T) {%d} -> error: %d (%T)\r\n",
+ sockRef, descP->sock, saveErrno, reason) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_fails, &descP->readFails, 1);
+
+ esock_clear_env("recv_check_failure", opP->env);
+ esock_free_env("recv_check_failure", opP->env);
+ FREE( opP );
+
+ return esock_make_error(env, reason);
+}
+
+
+
+/* ========================================================================
+ * esaio_recvfrom - Read a "packet" from a socket
+ *
+ * The (read) buffer handling *must* be optimized!
+ * But for now we make it easy for ourselves by
+ * allocating a binary (of the specified or default
+ * size) and then throw'ing it away...
+ */
+extern
+ERL_NIF_TERM esaio_recvfrom(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t len,
+ int flags)
+{
+ ErlNifPid caller;
+ ESAIOOperation* opP;
+ int rres;
+ WSABUF wbuf;
+ DWORD f = flags;
+ size_t bufSz = (len != 0 ? len : descP->rBufSz);
+
+ SSDBG( descP, ("WIN-ESAIO", "essio_recvfrom {%d} -> entry with"
+ "\r\n bufSz: %d"
+ "\r\n", descP->sock, bufSz) );
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (! IS_OPEN(descP->readState))
+ return esock_make_error_closed(env);
+
+ /* Accept and Read can not be simultaneous? */
+ if (descP->acceptorsQ.first != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Ensure that this caller does not *already* have a
+ * (recv) request waiting */
+ if (esock_reader_search4pid(env, descP, &caller)) {
+ /* Reader already in queue */
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ /* Allocate the operation */
+ opP = MALLOC( sizeof(ESAIOOperation) );
+ ESOCK_ASSERT( opP != NULL);
+ sys_memzero((char*) opP, sizeof(ESAIOOperation));
+
+ opP->tag = ESAIO_OP_RECVFROM;
+ /* Its a bit annoying that we have to alloc an env and then
+ * copy the ref *before* we know that we actually need it.
+ * How much does this cost?
+ */
+ opP->env = esock_alloc_env("esaio-recvfrom - operation");
+ opP->data.recvfrom.recvRef = CP_TERM(opP->env, recvRef);
+ opP->data.recvfrom.sockRef = CP_TERM(opP->env, sockRef);
+ opP->caller = caller;
+
+ /* Allocate a buffer:
+ * Either as much as we want to read or (if zero (0)) use the "default"
+ * size (what has been configured).
+ */
+ ESOCK_ASSERT( ALLOC_BIN(bufSz, &opP->data.recv.buf) );
+
+ opP->data.recvfrom.toRead = len;
+ wbuf.buf = opP->data.recvfrom.buf.data;
+ wbuf.len = opP->data.recvfrom.buf.size;
+
+ opP->data.recvfrom.addrLen = sizeof(ESockAddress);
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_tries, &descP->readTries, 1);
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_recvfrom {%d} -> try read (%lu)\r\n",
+ descP->sock, (unsigned long) bufSz) );
+
+ rres = sock_recvfrom_O(descP->sock, &wbuf, &f,
+ (struct sockaddr*) &opP->data.recvfrom.fromAddr,
+ &opP->data.recvfrom.addrLen, (OVERLAPPED*) opP);
+
+ return recvfrom_check_result(env, descP, opP, caller, rres,
+ sockRef, recvRef);
+}
+
+
+
+/* *** recvfrom_check_result ***
+ *
+ * Analyze the result of a receive attempt.
+ * The receive may have been completed directly or scheduled (overlapped).
+ */
+static
+ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ int recv_result,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM eres;
+
+ if (recv_result == 0) {
+
+ /* +++ Success +++ */
+
+ eres = recvfrom_check_ok(env, descP, opP, caller, sockRef, recvRef);
+
+ } else {
+ int err;
+
+ /* +++ Failure +++ */
+
+ err = sock_errno();
+
+ /* As pointed out above, there are basically two kinds of errors:
+ * 1) Pending:
+ * An overlapped operation was successfully initiated.
+ * Completion will be indicated at a later time.
+ * 2) An actual error
+ */
+
+ if (err == WSA_IO_PENDING) {
+
+ if (! IS_ZERO(recvRef)) {
+
+ eres = recv_check_pending(env, descP, opP, caller,
+ sockRef, recvRef);
+ } else {
+
+ /* We are not allowed to wait! => cancel */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvfrom_check_result(%T, %d) -> "
+ "pending - but we are not allowed to wait => cancel"
+ "\r\n", sockRef, descP->sock) );
+
+ if (! CancelIoEx((HANDLE) descP->sock, (OVERLAPPED*) opP)) {
+ int save_errno = sock_errno();
+ ERL_NIF_TERM tag = esock_atom_cancel;
+ ERL_NIF_TERM reason = ENO2T(env, save_errno);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvfrom_check_result(%T, %d) -> "
+ "failed cancel pending operation"
+ "\r\n %T"
+ "\r\n", sockRef, descP->sock, reason) );
+
+ eres = esock_make_error(env, MKT2(env, tag, reason));
+
+ } else {
+
+ eres = esock_atom_ok; // Will trigger {error, timeout}
+
+ }
+
+ }
+
+ } else {
+
+ eres = recvfrom_check_fail(env, descP, opP, err, sockRef);
+
+ }
+
+ }
+
+ return eres;
+}
+
+
+
+/* *** recvfrom_check_ok ***
+ *
+ * A successful recvfrom. We *know* that in this case the buffer is filled!
+ */
+
+static
+ERL_NIF_TERM recvfrom_check_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM data, result;
+ DWORD read = 0, flags = 0;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvfrom_check_ok -> try get overlapped result\r\n") );
+
+ if (get_recv_ovl_result(descP->sock, (OVERLAPPED*) opP, &read, &flags)) {
+
+ ERL_NIF_TERM eSockAddr;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvfrom_check_ok -> overlapped result: "
+ "\r\n read: %d"
+ "\r\n flags: 0x%X"
+ "\r\n", read, flags) );
+
+ (void) flags; // We should really do something with this...
+
+ esock_encode_sockaddr(env,
+ &opP->data.recvfrom.fromAddr,
+ opP->data.recvfrom.addrLen,
+ &eSockAddr);
+
+ if (read == opP->data.recvfrom.buf.size) {
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(env, &opP->data.recvfrom.buf);
+ } else {
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(env, &opP->data.recvfrom.buf);
+ data = MKSBIN(env, data, 0, read);
+ }
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ /* (maybe) Update max */
+ if (read > descP->readPkgMax)
+ descP->readPkgMax = read;
+
+ /*
+ * This is: {ok, {Source, Data}}
+ * But it should really be: {ok, {Source, Flags, Data}}
+ */
+ result = esock_make_ok2(env, MKT2(env, eSockAddr, data));
+
+ } else {
+
+ int save_errno = sock_errno();
+
+ switch (save_errno) {
+ case WSA_IO_INCOMPLETE:
+ /*
+ * WSA_IO_INCOMPLETE
+ *
+ * Even though it (the I/O Completion Port framework) told
+ * us it was done, it was not. So we need to postpone and let
+ * the (worker) threads deal with it anyway...effing framework...
+ */
+
+ if (! IS_ZERO(recvRef)) {
+
+ result = recv_check_pending(env, descP, opP, caller,
+ sockRef, recvRef);
+
+ } else {
+
+ /* But we are not allowed to wait! => cancel */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvfrom_check_ok(%T, %d) -> "
+ "incomplete - but we are not allowed to wait => cancel"
+ "\r\n", sockRef, descP->sock) );
+
+ if (! CancelIoEx((HANDLE) descP->sock, (OVERLAPPED*) opP)) {
+ int save_errno = sock_errno();
+ ERL_NIF_TERM tag = esock_atom_cancel;
+ ERL_NIF_TERM reason = ENO2T(env, save_errno);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvfrom_check_ok(%T, %d) -> "
+ "failed cancel incomplete operation"
+ "\r\n %T"
+ "\r\n", sockRef, descP->sock, reason) );
+
+ result = esock_make_error(env, MKT2(env, tag, reason));
+
+ } else {
+
+ result = esock_atom_ok; // Will trigger {error, timeout}
+
+ }
+ }
+ break;
+
+ default:
+ {
+ ERL_NIF_TERM eerrno = ENO2T(env, save_errno);
+ ERL_NIF_TERM reason = MKT2(env,
+ esock_atom_get_overlapped_result,
+ eerrno);
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_fails, &descP->readFails, 1);
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.genErrs, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+ result = esock_make_error(env, reason);
+ }
+ break;
+ }
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "recvfrom_check_ok(%T) {%d} -> done with"
+ "\r\n result: %T"
+ "\r\n",
+ sockRef, descP->sock, result) );
+
+ return result;
+}
+
+
+
+/* *** recvfrom_check_fail ***
+ *
+ * Processing done upon failed 'recvfrom'.
+ * An actual failure.
+ */
+static
+ERL_NIF_TERM recvfrom_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef)
+{
+ SSDBG( descP,
+ ("WIN-ESAIO", "recfrom_check_fail(%T) {%d} -> entry with"
+ "\r\n errno: %d"
+ "\r\n",
+ sockRef, descP->sock, saveErrno) );
+
+ FREE_BIN( &opP->data.recvfrom.buf );
+
+ return recv_check_failure(env, descP, opP, saveErrno, sockRef);
+}
+
+
+
+
+/* ========================================================================
+ * esaio_recvmsg - Read a "message" from a socket
+ * The (read) buffer handling *must* be optimized!
+ * But for now we make it easy for ourselves by
+ * allocating a binary (of the specified or default
+ * size) and then throwing it away...
+ *
+ * Note that this operation *only* works for socket
+ * of types SOCK_DGRAM and SOCK_RAW! Should we check
+ * and throw 'enotsup' otherwise? Would make testing
+ * easier...
+ */
+extern
+ERL_NIF_TERM esaio_recvmsg(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ssize_t bufLen,
+ ssize_t ctrlLen,
+ int flags)
+{
+ ErlNifPid caller;
+ ESAIOOperation* opP;
+ SOCKLEN_T addrLen;
+ size_t bufSz = (bufLen != 0 ? bufLen : descP->rBufSz);
+ size_t ctrlSz = (ctrlLen != 0 ? ctrlLen : descP->rCtrlSz);
+ int rres;
+ ERL_NIF_TERM eres;
+
+ (void) flags;
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_recvmsg(%T) {%d} -> entry with"
+ "\r\n bufSz: %lu (%ld)"
+ "\r\n ctrlSz: %ld (%ld)"
+ "\r\n", sockRef, descP->sock,
+ (unsigned long) bufSz, (long) bufLen,
+ (unsigned long) ctrlSz, (long) ctrlLen) );
+
+ /* This *only* works on socket type(s) DGRAM or RAW.
+ * Other socket types results in einval, which is not very
+ * helpful. So, in order to, atleast, help with testing,
+ * we do this...
+ */
+ if (! ((descP->type == SOCK_DGRAM) || (descP->type == SOCK_RAW))) {
+ return enif_raise_exception(env, MKA(env, "notsup"));
+ }
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (! IS_OPEN(descP->readState))
+ return esock_make_error_closed(env);
+
+ /* Accept and Read can not be simultaneous? */
+ if (descP->acceptorsQ.first != NULL)
+ return esock_make_error_invalid(env, esock_atom_state);
+
+ /* Ensure that this caller does not *already* have a
+ * (recv) request waiting */
+ if (esock_reader_search4pid(env, descP, &caller)) {
+ /* Reader already in queue */
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ /* Allocate the operation */
+ opP = MALLOC( sizeof(ESAIOOperation) );
+ ESOCK_ASSERT( opP != NULL);
+ sys_memzero((char*) opP, sizeof(ESAIOOperation));
+
+ opP->tag = ESAIO_OP_RECVMSG;
+ /* Its a bit annoying that we have to alloc an env and then
+ * copy the ref *before* we know that we actually need it.
+ * How much does this cost?
+ */
+ opP->env = esock_alloc_env("esaio-recvmsg - operation");
+ opP->data.recvmsg.recvRef = CP_TERM(opP->env, recvRef);
+ opP->data.recvmsg.sockRef = CP_TERM(opP->env, sockRef);
+ opP->caller = caller;
+
+ /* Allocate the (msg) data buffer:
+ */
+ ESOCK_ASSERT( ALLOC_BIN(bufSz, &opP->data.recvmsg.data[0]) );
+
+ /* Allocate the ctrl (buffer):
+ */
+ ESOCK_ASSERT( ALLOC_BIN(ctrlSz, &opP->data.recvmsg.ctrl) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_tries, &descP->readTries, 1);
+
+ addrLen = sizeof(opP->data.recvmsg.addr);
+ sys_memzero((char*) &opP->data.recvmsg.addr, addrLen);
+ sys_memzero((char*) &opP->data.recvmsg.msg, sizeof(opP->data.recvmsg.msg));
+
+ opP->data.recvmsg.wbufs[0].buf = opP->data.recvmsg.data[0].data;
+ opP->data.recvmsg.wbufs[0].len = opP->data.recvmsg.data[0].size;
+
+ opP->data.recvmsg.msg.name = (SOCKADDR*) &opP->data.recvmsg.addr;
+ opP->data.recvmsg.msg.namelen = addrLen;
+ opP->data.recvmsg.msg.lpBuffers = opP->data.recvmsg.wbufs;
+ opP->data.recvmsg.msg.dwBufferCount = 1; // Should be calculated...
+ opP->data.recvmsg.msg.Control.buf = opP->data.recvmsg.ctrl.data;
+ opP->data.recvmsg.msg.Control.len = opP->data.recvmsg.ctrl.size;
+ opP->data.recvmsg.msg.dwFlags = 0; // TMP
+
+ rres = sock_recvmsg_O(descP->sock,
+ &opP->data.recvmsg.msg,
+ (OVERLAPPED*) opP);
+
+ eres = recvmsg_check_result(env, descP, opP, caller, rres,
+ sockRef, recvRef);
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_recvmsg(%T) {%d} -> done\r\n",
+ sockRef, descP->sock) );
+
+ return eres;
+}
+
+
+static
+BOOLEAN_T recv_check_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* caller,
+ ERL_NIF_TERM ref,
+ ERL_NIF_TERM* checkResult)
+{
+ BOOLEAN_T result;
+
+ /* Check if already reader */
+ if (! esock_reader_search4pid(env, descP, caller)) {
+ /* No; check if we can wait for a result */
+ if (COMPARE(ref, esock_atom_zero) == 0)
+ return FALSE;
+ } else {
+ *checkResult = esock_raise_invalid(env, esock_atom_state);
+ }
+
+ return TRUE;
+}
+
+
+/* *** recvmsg_check_result ***
+ *
+ * The recvmsg function (maybe) delivers one (1) message. If our buffer
+ * is to small, the message will be truncated. So, regardless of
+ * if we filled the buffer or not, we have got what we are going
+ * to get regarding this message.
+ */
+static
+ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ int recv_result,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM eres;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "recvmsg_check_result(%T) {%d} -> entry with"
+ "\r\n recv_result: %d"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, recv_result, recvRef) );
+
+ if (recv_result == 0) {
+
+ /* +++ Success +++ */
+
+ eres = recvmsg_check_ok(env, descP, opP, caller, sockRef, recvRef);
+
+ } else {
+ int err;
+
+ /* +++ Failure +++ */
+
+ err = sock_errno();
+
+ /* As pointed out above, there are basically two kinds of errors:
+ * 1) Pending:
+ * An overlapped operation was successfully initiated.
+ * Completion will be indicated at a later time.
+ * 2) An actual error
+ */
+
+ if (err == WSA_IO_PENDING) {
+
+ if (! IS_ZERO(recvRef)) {
+
+ eres = recv_check_pending(env, descP, opP, caller,
+ sockRef, recvRef);
+
+ } else {
+
+ /* We are not allowed to wait! => cancel */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvmsg_check_result(%T, %d) -> "
+ "pending - but we are not allowed to wait => cancel"
+ "\r\n", sockRef, descP->sock) );
+
+ if (! CancelIoEx((HANDLE) descP->sock, (OVERLAPPED*) opP)) {
+ int save_errno = sock_errno();
+ ERL_NIF_TERM tag = esock_atom_cancel;
+ ERL_NIF_TERM reason = ENO2T(env, save_errno);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvmsg_check_result(%T, %d) -> "
+ "failed cancel pending operation"
+ "\r\n %T"
+ "\r\n", sockRef, descP->sock, reason) );
+
+ eres = esock_make_error(env, MKT2(env, tag, reason));
+
+ } else {
+
+ eres = esock_atom_ok; // Will trigger {error, timeout}
+
+ }
+
+ }
+
+ } else {
+
+ eres = recvmsg_check_fail(env, descP, opP, err, sockRef);
+
+ }
+
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "recvmsg_check_result(%T) {%d} -> done\r\n",
+ sockRef, descP->sock) );
+
+ return eres;
+}
+
+
+/* *** recvmsg_check_ok ***
+ *
+ * A successful recvmsg. We *know* that in this case the buffer is filled!
+ */
+
+static
+ERL_NIF_TERM recvmsg_check_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ ERL_NIF_TERM eMsg, result;
+ DWORD read = 0, flags = 0;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvmsg_check_ok(%T) {%d} -> try get overlapped result\r\n",
+ sockRef, descP->sock) );
+
+ if (get_recv_ovl_result(descP->sock, (OVERLAPPED*) opP, &read, &flags)) {
+
+ ERL_NIF_TERM eSockAddr;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvmsg_check_ok(%T, %d) -> overlapped success result: "
+ "\r\n read: %d"
+ "\r\n flags: 0x%X"
+ "\r\n", sockRef, descP->sock, read, flags) );
+
+ (void) flags; // We should really do something with this...
+
+ encode_msg(env, descP, read,
+ &opP->data.recvmsg.msg,
+ opP->data.recvmsg.data,
+ &opP->data.recvmsg.ctrl,
+ &eMsg);
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ /* (maybe) Update max */
+ if (read > descP->readPkgMax)
+ descP->readPkgMax = read;
+
+ result = esock_make_ok2(env, eMsg);
+
+ } else {
+
+ int save_errno = sock_errno();
+
+ switch (save_errno) {
+ case WSA_IO_INCOMPLETE:
+ /*
+ * WSA_IO_INCOMPLETE
+ *
+ * Even though it (the I/O Completion Port framework) told
+ * us it was done, it was not. So we need to postpone and let
+ * the (worker) threads deal with it anyway...effing framework...
+ */
+
+ if (! IS_ZERO(recvRef)) {
+
+ result = recv_check_pending(env, descP, opP, caller,
+ sockRef, recvRef);
+
+ } else {
+
+ /* But we are not allowed to wait! => cancel */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvmsg_check_ok(%T, %d) -> "
+ "incomplete - but we are not allowed to wait => cancel"
+ "\r\n", sockRef, descP->sock) );
+
+ if (! CancelIoEx((HANDLE) descP->sock, (OVERLAPPED*) opP)) {
+ int save_errno = sock_errno();
+ ERL_NIF_TERM tag = esock_atom_cancel;
+ ERL_NIF_TERM reason = ENO2T(env, save_errno);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "recvmsg_check_ok(%T, %d) -> "
+ "failed cancel incomplete operation"
+ "\r\n %T"
+ "\r\n", sockRef, descP->sock, reason) );
+
+ result = esock_make_error(env, MKT2(env, tag, reason));
+
+ } else {
+
+ result = esock_atom_ok; // Will trigger {error, timeout}
+
+ }
+ }
+ break;
+
+ default:
+ {
+ ERL_NIF_TERM eerrno = ENO2T(env, save_errno);
+ ERL_NIF_TERM reason = MKT2(env,
+ esock_atom_get_overlapped_result,
+ eerrno);
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_fails, &descP->readFails, 1);
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.genErrs, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+ result = esock_make_error(env, reason);
+ }
+ break;
+ }
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "recvmsg_check_ok(%T) {%d} -> done with"
+ "\r\n result: %T"
+ "\r\n",
+ sockRef, descP->sock, result) );
+
+ return result;
+}
+
+
+
+/* *** recvmsg_check_fail ***
+ *
+ * Processing done upon failed 'recvmsg'.
+ * An actual failure.
+ */
+static
+ERL_NIF_TERM recvmsg_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef)
+{
+ SSDBG( descP,
+ ("WIN-ESAIO", "recvmsg_check_fail(%T) {%d} -> entry with"
+ "\r\n errno: %d"
+ "\r\n",
+ sockRef, descP->sock, saveErrno) );
+
+ FREE_BIN( &opP->data.recvmsg.data[0] );
+ FREE_BIN( &opP->data.recvmsg.ctrl );
+
+ return recv_check_failure(env, descP, opP, saveErrno, sockRef);
+}
+
+
+
+
+
+/* *******************************************************************
+ * esaio_close - Close a socket
+ *
+ * Stage 1 of the socket close
+ */
+
+extern
+ERL_NIF_TERM esaio_close(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ if (! IS_OPEN(descP->readState)) {
+ /* A bit of cheeting; maybe not closed yet - do we need a queue? */
+ return esock_make_error_closed(env);
+ }
+
+ /* Store the PID of the caller,
+ * since we need to inform it when we
+ * (that is, the stop callback function)
+ * completes.
+ */
+ ESOCK_ASSERT( enif_self(env, &descP->closerPid) != NULL );
+
+ /* If the caller is not the owner; monitor the caller,
+ * since we should complete this operation even if the caller dies
+ * (for whatever reason).
+ */
+ if (COMPARE_PIDS(&descP->closerPid, &descP->ctrlPid) != 0) {
+
+ ESOCK_ASSERT( MONP("esaio_close-check -> closer",
+ env, descP,
+ &descP->closerPid,
+ &descP->closerMon) == 0 );
+ }
+
+ /* Prepare for closing the socket */
+ descP->readState |= ESOCK_STATE_CLOSING;
+ descP->writeState |= ESOCK_STATE_CLOSING;
+ if (do_stop(env, descP)) {
+
+ // stop() has been scheduled - wait for it
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_close {%d} -> stop was scheduled\r\n",
+ descP->sock) );
+
+ // Create closeRef for the close msg that esock_stop() will send
+ descP->closeEnv = esock_alloc_env("esock_close_do - close-env");
+ descP->closeRef = MKREF(descP->closeEnv);
+
+ return esock_make_ok2(env, CP_TERM(env, descP->closeRef));
+
+ } else {
+ // The socket may be closed - tell caller to finalize
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_close {%d} -> stop was called\r\n",
+ descP->sock) );
+
+ return esock_atom_ok;
+ }
+}
+
+
+
+static
+BOOLEAN_T do_stop(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ BOOLEAN_T ret;
+ ERL_NIF_TERM sockRef;
+
+ sockRef = enif_make_resource(env, descP);
+
+ if (IS_SELECTED(descP)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "do_stop {%d} -> cancel outstanding I/O operations\r\n",
+ descP->sock) );
+
+ /* Cancel *all* outstanding I/O operations on the socket.
+ * We have to wait for the worker threads to process these ops!
+ * (will result in OPERATION_ABORTED for the threads).
+ */
+ if (! CancelIoEx((HANDLE) descP->sock, NULL) ) {
+ int save_errno = sock_errno();
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "do_stop {%d} -> cancel I/O failed: %s (%d)\r\n",
+ descP->sock, erl_errno_id(save_errno), save_errno) );
+
+ /* Only issue an error message for errors *other* than
+ * 'not found' (since 'not found' means there is no active
+ * requests = already completed => race).
+ */
+
+ if (save_errno != ERROR_NOT_FOUND)
+ esock_error_msg("Failed cancel outstanding I/O operations:"
+ "\r\n Socket: " SOCKET_FORMAT_STR
+ "\r\n Reason: %s (%d)"
+ "\r\n",
+ descP->sock,
+ erl_errno_id(save_errno), save_errno);
+
+ ret = FALSE;
+
+ } else {
+
+ /* Cancel of all active requests (to the I/O completion port
+ * machinery) has been successfully requested.
+ * The requests will be aborted and handled by the worker threads.
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "do_stop {%d} -> successfully canceled\r\n", descP->sock) );
+
+ ret = TRUE;
+ }
+
+ } else {
+
+ /* No active requests in the I/O completion port machinery */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "do_stop {%d} -> no active I/O requests\r\n", descP->sock) );
+
+ ret = FALSE;
+ }
+
+ /* We do nothing here with the requests in the various queues
+ * They are handled by the working threads, when the abort is triggered
+ * (one for each request)!
+ */
+
+ /* +++++++ Connector +++++++
+ * Note that there should not be Writers and a Connector
+ * at the same time so the check for if the
+ * current Writer/Connecter was deselected is only correct
+ * under that assumption
+ */
+
+ if (descP->connectorP != NULL) {
+
+ /* We have a Connector;
+ *
+ * The Connector will not get a select message
+ * - send it an abort message
+ */
+
+ esock_stop_handle_current(env,
+ "connector",
+ descP, sockRef, &descP->connector);
+
+ descP->connectorP = NULL;
+ }
+
+ return ret;
+}
+
+
+
+/* ========================================================================
+ * Perform the final step in the socket close.
+ */
+extern
+ERL_NIF_TERM esaio_fin_close(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ int err;
+ ErlNifPid self;
+
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ if (IS_CLOSED(descP->readState))
+ return esock_make_error_closed(env);
+
+ if (! IS_CLOSING(descP->readState)) {
+ // esock_close() has not been called
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ if (IS_SELECTED(descP) && (descP->closeEnv != NULL)) {
+ // esock_stop() is scheduled but has not been called
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ if (COMPARE_PIDS(&descP->closerPid, &self) != 0) {
+ // This process is not the closer
+ return esock_raise_invalid(env, esock_atom_state);
+ }
+
+ // Close the socket
+
+ /* Stop monitoring the closer.
+ * Demonitoring may fail since this is a dirty NIF
+ * - the caller may have died already.
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_fin_close {%d} -> demonitor closer process %T\r\n",
+ descP->sock, descP->closerPid) );
+ enif_set_pid_undefined(&descP->closerPid);
+ if (descP->closerMon.isActive) {
+ (void) DEMONP("esaio_fin_close -> closer",
+ env, descP, &descP->closerMon);
+ }
+
+ /* Stop monitoring the owner */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_fin_close {%d} -> demonitor owner process %T\r\n",
+ descP->sock, descP->ctrlPid) );
+ enif_set_pid_undefined(&descP->ctrlPid);
+ (void) DEMONP("esaio_fin_close -> ctrl",
+ env, descP, &descP->ctrlMon);
+ /* Not impossible to still get a esock_down() call from a
+ * just triggered owner monitor down
+ */
+
+ /* This nif-function is executed in a dirty scheduler just so
+ * that it can "hang" (with minimum effect on the VM) while the
+ * kernel writes our buffers. IF we have set the linger option
+ * for this ({true, integer() > 0}).
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_fin_close {%d} -> (try) close the socket\r\n",
+ descP->sock, descP->ctrlPid) );
+ err = esock_close_socket(env, descP, TRUE);
+
+ if (err != 0) {
+ if (err == ERRNO_BLOCK) {
+ /* Not all data in the buffers where sent,
+ * make sure the caller gets this.
+ */
+ return esock_make_error(env, esock_atom_timeout);
+ } else {
+ return esock_make_error_errno(env, err);
+ }
+ }
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_fin_close -> done\r\n") );
+
+ return esock_atom_ok;
+}
+
+
+
+/* ========================================================================
+ * Cancel a connect request.
+ */
+extern
+ERL_NIF_TERM esaio_cancel_connect(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+ ErlNifPid self;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_cancel_connect {%d} -> entry with"
+ "\r\n writeState: 0x%X"
+ "\r\n opRef: %T"
+ "\r\n",
+ descP->sock, descP->writeState, opRef) );
+
+ ESOCK_ASSERT( enif_self(env, &self) != NULL );
+
+ if (! IS_OPEN(descP->writeState)) {
+
+ res = esock_make_error_closed(env);
+
+ } else if ((descP->connectorP == NULL) ||
+ (COMPARE_PIDS(&self, &descP->connector.pid) != 0) ||
+ (COMPARE(opRef, descP->connector.ref) != 0)) {
+
+ res = esock_make_error(env, esock_atom_not_found);
+
+ } else {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_cancel_connect {%d} -> "
+ "try cancel connect I/O request\r\n",
+ descP->sock) );
+
+ if (! CancelIoEx((HANDLE) descP->sock,
+ (OVERLAPPED*) descP->connector.dataP)) {
+ /* What does this mean?
+ * One of the possible reasons is that the connect succeeded.
+ * In which case, one of the threads in the thread-pool will
+ * be triggered (eventually).
+ * Then we have to deal with a connected socket that no one wants...
+ */
+ int save_errno = sock_errno();
+ res = esock_make_error_errno(env, save_errno);
+ } else {
+ res = esock_atom_ok;
+ }
+
+ esock_requestor_release("esock_cancel_connect",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+ descP->writeState &= ~ESOCK_STATE_CONNECTING;
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_cancel_connect {%d} -> done when"
+ "\r\n res: %T"
+ "\r\n",
+ descP->sock, descP->writeState,
+ opRef, res) );
+
+ return res;
+}
+
+
+
+/* *** esock_cancel_accept ***
+ *
+ * We have three different cases:
+ * *) Socket is closed:
+ * return error: closed
+ * *) Active accept (found in the request store):
+ * Cancel the completion request!
+ * Success will trigger an event (delivered) to the
+ * (completion) worker threads.
+ * *) Not found (in the request store):
+ * This request has already completed (race):
+ * return: not_found
+ *
+ */
+extern
+ERL_NIF_TERM esaio_cancel_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+ ESockRequestor req;
+ ErlNifPid caller;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_cancel_accept(%T), {%d,0x%X} ->"
+ "\r\n opRef: %T"
+ "\r\n", sockRef, descP->sock, descP->readState, opRef) );
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (! IS_OPEN(descP->readState)) {
+
+ res = esock_make_error_closed(env);
+
+ } else if (esock_acceptor_get(env, descP, &opRef, &caller, &req)) {
+
+ ESOCK_ASSERT( DEMONP("esaio_cancel_accept -> acceptor",
+ env, descP, &req.mon) == 0);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_cancel_accept {%d} -> try cancel accept I/O request\r\n",
+ descP->sock) );
+
+ if (! CancelIoEx((HANDLE) descP->sock, (OVERLAPPED*) req.dataP)) {
+
+ /* What does this mean?
+ * One of the possible reasons is that the accept succeeded.
+ * In which case, one of the threads in the thread-pool will
+ * be triggered (eventually).
+ * Then we have to deal with a connected socket that no one wants...
+ */
+
+ int save_errno = sock_errno();
+ res = esock_make_error_errno(env, save_errno);
+
+ } else {
+
+ res = esock_atom_ok;
+
+ }
+
+ /* Request cleanup (demonitor already done above) */
+ esock_clear_env("esaio_cancel_accept -> req cleanup", req.env);
+ esock_free_env("esaio_cancel_accept -> req cleanup", req.env);
+
+ /* *Maybe* update listen socket (read) state
+ * (depends on if the queue is now empty)
+ */
+ if (descP->acceptorsQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_ACCEPTING;
+ }
+
+ } else {
+
+ res = esock_make_error(env, esock_atom_not_found);
+
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_cancel_accept(%T) -> done with result:"
+ "\r\n %T"
+ "\r\n", sockRef, res) );
+
+ return res;
+}
+
+
+
+
+/* *** esock_cancel_send ***
+ *
+ * We have three different cases:
+ * *) Socket is closed:
+ * return error: closed
+ * *) Active send (found in the request store):
+ * Cancel the completion request!
+ * Success will trigger an event (delivered) to the
+ * (completion) worker threads.
+ * *) Not found (in the request store):
+ * This request has already completed (race):
+ * return: not_found
+ *
+ */
+extern
+ERL_NIF_TERM esaio_cancel_send(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+ ESockRequestor req;
+ ErlNifPid caller;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_cancel_send(%T), {%d,0x%X} ->"
+ "\r\n opRef: %T"
+ "\r\n", sockRef, descP->sock, descP->readState, opRef) );
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (! IS_OPEN(descP->writeState)) {
+
+ res = esock_make_error_closed(env);
+
+ } else if (esock_writer_get(env, descP, &opRef, &caller, &req)) {
+
+ ESOCK_ASSERT( DEMONP("esaio_cancel_send -> sender",
+ env, descP, &req.mon) == 0);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_cancel_send {%d} -> try cancel send I/O request\r\n",
+ descP->sock) );
+
+ if (! CancelIoEx((HANDLE) descP->sock, (OVERLAPPED*) req.dataP)) {
+
+ /* What does this mean?
+ * One of the possible reasons is that the send succeeded.
+ * In which case, one of the threads in the thread-pool will
+ * be triggered (eventually).
+ */
+
+ int save_errno = sock_errno();
+ res = esock_make_error_errno(env, save_errno);
+
+ } else {
+
+ res = esock_atom_ok;
+
+ }
+
+ /* Request cleanup (demonitor already done above) */
+ esock_clear_env("esaio_cancel_send -> req cleanup", req.env);
+ esock_free_env("esaio_cancel_send -> req cleanup", req.env);
+
+ } else {
+
+ res = esock_make_error(env, esock_atom_not_found);
+
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_cancel_send(%T) -> done with result:"
+ "\r\n %T"
+ "\r\n", sockRef, res) );
+
+ return res;
+}
+
+
+
+
+/* *** esock_cancel_recv ***
+ *
+ * We have three different cases:
+ * *) Socket is closed:
+ * return error: closed
+ * *) Active receive (found in the request store):
+ * Cancel the completion request!
+ * Success will trigger an event (delivered) to the
+ * (completion) worker threads.
+ * *) Not found (in the request store):
+ * This request has already completed (race):
+ * return: not_found
+ *
+ */
+extern
+ERL_NIF_TERM esaio_cancel_recv(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+ ESockRequestor req;
+ ErlNifPid caller;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_cancel_recv(%T), {%d,0x%X} ->"
+ "\r\n opRef: %T"
+ "\r\n", sockRef, descP->sock, descP->readState, opRef) );
+
+ ESOCK_ASSERT( enif_self(env, &caller) != NULL );
+
+ if (! IS_OPEN(descP->readState)) {
+
+ res = esock_make_error_closed(env);
+
+ } else if (esock_reader_get(env, descP, &opRef, &caller, &req)) {
+
+ ESOCK_ASSERT( DEMONP("esaio_cancel_recv -> reader",
+ env, descP, &req.mon) == 0);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_cancel_recv {%d} -> try cancel send I/O request\r\n",
+ descP->sock) );
+
+ if (! CancelIoEx((HANDLE) descP->sock, (OVERLAPPED*) req.dataP)) {
+
+ /* What does this mean?
+ * One of the possible reasons is that the recv succeeded.
+ * In which case, one of the threads in the thread-pool will
+ * be triggered (eventually).
+ */
+
+ int save_errno = sock_errno();
+ res = esock_make_error_errno(env, save_errno);
+
+ } else {
+
+ res = esock_atom_ok;
+
+ }
+
+ /* Request cleanup (demonitor already done above) */
+ esock_clear_env("esaio_cancel_recv -> req cleanup", req.env);
+ esock_free_env("esaio_cancel_recv -> req cleanup", req.env);
+
+ } else {
+
+ res = esock_make_error(env, esock_atom_not_found);
+
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_cancel_recv(%T) -> done with result:"
+ "\r\n %T"
+ "\r\n", sockRef, res) );
+
+ return res;
+}
+
+
+
+
+/* ====================================================================
+ *
+ * The "worker" thread of the I/O Completion Port thread pool.
+ * Shall each thread have its own environment?
+ *
+ * ====================================================================
+ */
+
+static
+void* esaio_completion_main(void* threadDataP)
+{
+ char envName[64]; /* Used for building the (env-) name */
+ BOOLEAN_T done = FALSE;
+ ESAIOThreadData* dataP = (ESAIOThreadData*) threadDataP;
+ ESockDescriptor* descP = NULL;
+ ESAIOOperation* opP;
+ OVERLAPPED* olP;
+ BOOL res;
+ DWORD numBytes, flags = 0;
+ int save_errno;
+
+ SGDBG( ("WIN-ESAIO", "esaio_completion_main -> entry\r\n") );
+
+ dataP->state = ESAIO_THREAD_STATE_INITIATING;
+
+ sprintf(envName, "esaio-completion-main[%d]", dataP->id);
+ dataP->env = esock_alloc_env(envName);
+
+ dataP->state = ESAIO_THREAD_STATE_OPERATIONAL;
+
+ SGDBG( ("WIN-ESAIO", "esaio_completion_main -> initiated\r\n") );
+
+ while (!done) {
+ /*
+ * If this function *fails*, return value FALSE, the (out-) arguments:
+ * - lpNumberOfBytes (numBytes)
+ * - lpCompletionKey (descP)
+ * - lpOverlapped (olP)
+ * *can* contain particular value combinations as follows:
+ *
+ * * If *lpOverlapped is NULL, the function did not dequeue a
+ * completion packet from the completion port.
+ * In this case, the function does not store information in the
+ * variables pointed to by the lpNumberOfBytes and lpCompletionKey
+ * parameters, and their values are indeterminate.
+ *
+ * * If *lpOverlapped is not NULL and the function dequeues a
+ * completion packet for a failed I/O operation from the
+ * completion port, the function stores information about the
+ * failed operation in the variables pointed to by lpNumberOfBytes,
+ * lpCompletionKey, and lpOverlapped.
+ * To get extended error information, call GetLastError.
+ *
+ */
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> [%d] try dequeue packet\r\n",
+ dataP->cnt) );
+
+ res = GetQueuedCompletionStatus(ctrl.cport,
+ &numBytes,
+ (PULONG_PTR) &descP,
+ &olP,
+ INFINITE);
+ save_errno = NO_ERROR;
+
+ if (!res) {
+
+ save_errno = sock_errno(); // Details
+
+ if (olP == NULL) {
+
+ /* First alt.
+ * What shall we do here? Quit? Try again?
+ */
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> [failure 1]"
+ "\r\n %s (%d)"
+ "\r\n", erl_errno_id(save_errno), save_errno) );
+
+ dataP->state = ESAIO_THREAD_STATE_TERMINATING;
+ dataP->error = ESAIO_THREAD_ERROR_GET;
+ opP = NULL;
+ done = TRUE;
+ break;
+
+ } else {
+
+ /* Second alt.
+ * Dequeued a complete packet for a *failed* I/O operation.
+ */
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> [failure 2] "
+ "\r\n %s (%d)"
+ "\r\n", erl_errno_id(save_errno), save_errno) );
+
+ opP = CONTAINING_RECORD(olP, ESAIOOperation, ol);
+ esaio_completion_inc(dataP);
+
+ }
+ } else {
+ opP = CONTAINING_RECORD(olP, ESAIOOperation, ol);
+ esaio_completion_inc(dataP);
+
+ SGDBG( ("WIN-ESAIO", "esaio_completion_main -> success\r\n") );
+
+ } /* if (!res) */
+
+ dataP->latest = opP->tag;
+
+ switch (opP->tag) {
+ case ESAIO_OP_TERMINATE:
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> received terminate cmd\r\n") );
+ done = esaio_completion_terminate(dataP, (OVERLAPPED*) opP);
+ break;
+
+ case ESAIO_OP_CONNECT:
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> received connect cmd\r\n") );
+ done = esaio_completion_connect(dataP, descP, (OVERLAPPED*) opP,
+ opP->env, &opP->caller,
+ &opP->data.connect,
+ save_errno);
+ break;
+
+ case ESAIO_OP_ACCEPT:
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> received accept cmd\r\n") );
+ done = esaio_completion_accept(dataP, descP, (OVERLAPPED*) opP,
+ opP->env, &opP->caller,
+ &opP->data.accept,
+ save_errno);
+ break;
+
+ case ESAIO_OP_SEND:
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> received send cmd\r\n") );
+ done = esaio_completion_send(dataP, descP, (OVERLAPPED*) opP,
+ opP->env, &opP->caller,
+ &opP->data.send,
+ save_errno);
+ break;
+
+ case ESAIO_OP_SENDTO:
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> received sendto cmd\r\n") );
+ done = esaio_completion_sendto(dataP, descP, (OVERLAPPED*) opP,
+ opP->env, &opP->caller,
+ &opP->data.sendto,
+ save_errno);
+ break;
+
+ case ESAIO_OP_SENDMSG:
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> received sendmsg cmd\r\n") );
+ done = esaio_completion_sendmsg(dataP, descP, (OVERLAPPED*) opP,
+ opP->env, &opP->caller,
+ &opP->data.sendmsg,
+ save_errno);
+ break;
+
+ case ESAIO_OP_RECV:
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> received recv cmd\r\n") );
+ done = esaio_completion_recv(dataP, descP, (OVERLAPPED*) opP,
+ opP->env, &opP->caller,
+ &opP->data.recv,
+ save_errno);
+ break;
+
+ case ESAIO_OP_RECVFROM:
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> received recvfrom cmd\r\n") );
+ done = esaio_completion_recvfrom(dataP, descP, (OVERLAPPED*) opP,
+ opP->env, &opP->caller,
+ &opP->data.recvfrom,
+ save_errno);
+ break;
+
+ case ESAIO_OP_RECVMSG:
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> received recvmsg cmd\r\n") );
+ done = esaio_completion_recvmsg(dataP, descP, (OVERLAPPED*) opP,
+ opP->env, &opP->caller,
+ &opP->data.recvmsg,
+ save_errno);
+ break;
+
+ default:
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_main -> received unknown cmd: "
+ "\r\n %d"
+ "\r\n",
+ opP->tag) );
+ done = esaio_completion_unknown(dataP, descP, (OVERLAPPED*) opP,
+ numBytes, save_errno);
+ break;
+
+ }
+
+ FREE(opP);
+
+ } /* while (!done) */
+
+ SGDBG( ("WIN-ESAIO", "esaio_completion_main -> terminating\r\n") );
+
+ TEXIT(threadDataP);
+
+ SGDBG( ("WIN-ESAIO", "esaio_completion_main -> terminated\r\n") );
+
+ dataP->state = ESAIO_THREAD_STATE_TERMINATED;
+
+ SGDBG( ("WIN-ESAIO", "esaio_completion_main -> done\r\n") );
+
+ return threadDataP;
+}
+
+
+/* *** esaio_completion_terminate ***
+ *
+ * We are done
+ *
+ */
+static
+BOOLEAN_T esaio_completion_terminate(ESAIOThreadData* dataP,
+ OVERLAPPED* ovl)
+{
+ (void) ovl;
+
+ dataP->state = ESAIO_THREAD_STATE_TERMINATING;
+ dataP->error = ESAIO_THREAD_ERROR_CMD;
+
+ return TRUE;
+}
+
+
+/* *** esaio_completion_connect ***
+ *
+ * Handle a completed 'connect' (completion) request.
+ * Send a 'completion' message (to requestor) with the request status.
+ *
+ * Completion message:
+ * {'socket tag', socket(), completion, CompletionInfo}
+ *
+ * CompletionInfo: {CompletionHandle, CompletionStatus}
+ * CompletionHandle: reference()
+ * Result: ok | {error, Reason}
+ *
+ *
+ * There is a possibillity of a race here. That is, if the user
+ * calls socket:connect(Socket, ..., nowait), the connect is
+ * scheduled, and then just as it has completed, but before this
+ * thread has been activated to handle the 'connect completed'
+ * the user calls socket:close(Socket) (or exits).
+ * Then when this function is called, the socket is closed.
+ * What to do?
+ */
+static
+BOOLEAN_T esaio_completion_connect(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataConnect* opDataP,
+ int error)
+{
+ ErlNifEnv* env = dataP->env;
+ ERL_NIF_TERM reason;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_connect(%d) -> entry\r\n",
+ descP->sock, error) );
+
+ (void) opCaller;
+
+ switch (error) {
+ case NO_ERROR:
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_connect(%d) -> success"
+ "\r\n", descP->sock) );
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_connect_success(env, descP, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ break;
+
+ case WSA_OPERATION_ABORTED:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_connect(%d) -> operation aborted"
+ "\r\n", descP->sock) );
+ /* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_connect_aborted(env, descP, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ break;
+
+ default:
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_connect(%d) -> unknown failure:"
+ "\r\n %T"
+ "\r\n", descP->sock, ENO2T(env, error)) );
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_connect_failure(env, descP, opDataP, error);
+
+ MUNLOCK(descP->writeMtx);
+ break;
+ }
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_connect -> clear and delete op env\r\n") );
+
+ /* No need for this "stuff" anymore */
+ esock_clear_env("esaio_completion_connect", opEnv);
+ esock_free_env("esaio_completion_connect", opEnv);
+
+ SGDBG( ("WIN-ESAIO", "esaio_completion_connect -> done\r\n") );
+
+ return FALSE;
+}
+
+
+
+/* *** esaio_completion_connect_success ***
+ * The 'connect' operation was successful.
+ */
+static
+void esaio_completion_connect_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOpDataConnect* opDataP)
+{
+ if (descP->connectorP != NULL) {
+ if (IS_OPEN(descP->writeState)) {
+ esaio_completion_connect_completed(env, descP, opDataP);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+
+ /* Clean up the connector stuff, no need for that anymore */
+ esock_requestor_release("esaio_completion_connect_success -> "
+ "not active",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+
+ descP->writeState &=
+ ~(ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED);
+
+ esaio_completion_connect_not_active(descP);
+ }
+ } else {
+ /* Connect was actually completed directly
+ * (and 'connector' was therefor not initiated)
+ * => Nothing to do here, other than cleanup.
+ */
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+}
+
+
+
+/* *** esaio_completion_connect_aborted ***
+ * The 'connect' operation was aborted.
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_connect_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOpDataConnect* opDataP)
+{
+ if (descP->connectorP != NULL) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ descP->connectorP, reason);
+
+ /* Clean up the connector stuff, no need for that anymore */
+ esock_requestor_release("connect_stream_check_result -> abort",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+ descP->writeState &= ~(ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->writeState)) {
+
+ esaio_stop(env, descP);
+
+ }
+ } else {
+ descP->writeState &= ~(ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED);
+ }
+}
+
+
+/* *** esaio_completion_connect_failure *
+ * A "general" failure happened while performing the 'connect' operation.
+ */
+static
+void esaio_completion_connect_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOpDataConnect* opDataP,
+ int error)
+{
+ if (descP->connectorP != NULL) {
+ /* Figure out the reason */
+ ERL_NIF_TERM reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ descP->connectorP, reason);
+ esaio_completion_connect_fail(env, descP, error, FALSE);
+
+ /* Clean up the connector stuff, no need for that anymore */
+ esock_requestor_release("connect_stream_check_result -> failure",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+ descP->writeState &= ~(ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED);
+
+ } else {
+ esaio_completion_connect_fail(env, descP, error, TRUE);
+ }
+}
+
+
+/* *** esaio_completion_connect_completed ***
+ * The connect request has completed.
+ */
+static
+void esaio_completion_connect_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOpDataConnect* opDataP)
+{
+ ERL_NIF_TERM completionStatus, completionInfo;
+ int ucres;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_connect_completed(%d) -> "
+ "success - try update context\r\n", descP->sock) );
+
+ ucres = ESAIO_UPDATE_CONNECT_CONTEXT( descP->sock );
+
+ if (ucres == 0) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_connect_completed({%d) -> success\r\n",
+ descP->sock) );
+
+ descP->writeState &= ~(ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED);
+ descP->writeState |= ESOCK_STATE_CONNECTED;
+
+ completionStatus = esock_atom_ok;
+
+ } else {
+
+ /* It is actually possible that this is an error "we do not know"
+ * which will result in the atom 'unknown', which is not very useful...
+ * So, we should really test if is 'unknown' and if so use the actual
+ * value (the integer) instead.
+ */
+ int save_errno = sock_errno();
+ ERL_NIF_TERM tag = esock_atom_update_connect_context;
+ ERL_NIF_TERM reason = ENO2T(env, save_errno);
+
+ SSDBG( descP, ("WIN-ESAIO",
+ "esaio_completion_connect_completed(%d) -> "
+ "failed update connect context: %T\r\n",
+ descP->sock, reason) );
+
+ descP->writeState = ESOCK_STATE_CLOSED;
+
+ sock_close(descP->sock);
+
+ WSACleanup();
+
+ completionStatus = esock_make_error_t2r(descP->connector.env,
+ tag, reason);
+
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_connect_completed {%d} -> "
+ "completion status: %T\r\n",
+ descP->sock, completionStatus) );
+
+ completionInfo = MKT2(descP->connector.env,
+ descP->connector.ref,
+ completionStatus);
+
+ /* Send a 'connect' completion message */
+ esaio_send_completion_msg(env,
+ descP,
+ &descP->connector.pid,
+ descP->connector.env,
+ CP_TERM(descP->connector.env, opDataP->sockRef),
+ completionInfo);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_connect_completed {%d} -> cleanup\r\n",
+ descP->sock) );
+
+ /* Clean up the connector stuff, no need for that anymore */
+ esock_requestor_release("esaio_completion_connect_completed",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+
+}
+
+
+
+/* *** esaio_completion_connect_not_active ***
+ * A connect has completed but the operation is no longer valid.
+ */
+static
+void esaio_completion_connect_not_active(ESockDescriptor* descP)
+{
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_connect_not_active -> "
+ "success for cancelled connect\r\n") );
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.unexpectedConnects, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+}
+
+
+
+/* *** esaio_completion_connect_fail ***
+ * Unknown operation failure.
+ */
+static
+void esaio_completion_connect_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform)
+{
+ descP->writeState &= ~(ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED);
+ esaio_completion_fail(env, descP, "connect", error, inform);
+}
+
+
+
+
+/* === accept 'stuff' === */
+
+/* *** esaio_completion_accept ***
+ *
+ * Handle a completed 'accept' (completion) request.
+ * Send a 'completion' message (to requestor) with the request status.
+ *
+ * Completion message:
+ * {'socket tag', socket(), completion, CompletionInfo}
+ *
+ * CompletionInfo: {CompletionHandle, CompletionStatus}
+ * CompletionHandle: reference()
+ * Result: ok | {error, Reason}
+ *
+ *
+ * There is a possibillity of a race here. That is, if the user
+ * calls socket:accept(Socket, ..., nowait), the accept is
+ * scheduled, and then just as it has completed, but before this
+ * thread has been activated to handle the 'accept completed'
+ * the user calls socket:close(Socket) (or exits).
+ * Then when this function is called, the socket is closed.
+ * What to do?
+ */
+static
+BOOLEAN_T esaio_completion_accept(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataAccept* opDataP,
+ int error)
+{
+ ErlNifEnv* env = dataP->env;
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_accept(%d) -> entry with"
+ "\r\n error: %s (%d)"
+ "\r\n", descP->sock, erl_errno_id(error), error) );
+
+ switch (error) {
+ case NO_ERROR:
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_accept(%d) -> success"
+ "\r\n", descP->sock) );
+ MLOCK(descP->readMtx);
+
+ esaio_completion_accept_success(env, descP, opEnv, opCaller, opDataP);
+
+ MUNLOCK(descP->readMtx);
+ break;
+
+ case WSA_OPERATION_ABORTED:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept(%d) -> operation aborted"
+ "\r\n", descP->sock) );
+ /* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_accept_aborted(env, descP, opCaller, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ break;
+
+ default:
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept(%d) -> unknown failure"
+ "\r\n", descP->sock) );
+ MLOCK(descP->readMtx);
+
+ esaio_completion_accept_failure(env, descP, opCaller, opDataP, error);
+
+ MUNLOCK(descP->readMtx);
+ break;
+ }
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_completion_accept -> clear and delete op env\r\n") );
+
+ /* "Manually" allocated buffer */
+ FREE( opDataP->buf );
+
+ /* No need for this "stuff" anymore */
+ esock_clear_env("esaio_completion_accept - op cleanup", opEnv);
+ esock_free_env("esaio_completion_accept - op cleanup", opEnv);
+
+ SGDBG( ("WIN-ESAIO", "esaio_completion_accept -> done\r\n") );
+
+ return FALSE;
+
+}
+
+
+/* *** esaio_completion_accept_success ***
+ * The 'accept' operation was successful.
+ */
+static
+void esaio_completion_accept_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataAccept* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_acceptor_get(env, descP,
+ &opDataP->accRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->readState)) {
+ esaio_completion_accept_completed(env, descP,
+ opEnv, opCaller, opDataP,
+ &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_accept_not_active(descP);
+ }
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ * => But we do not free the "buffer" since it was "used up"
+ * when we (as assumed) got the result (directly)...
+ */
+ }
+
+ /* *Maybe* update socket (read) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_success(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->acceptorsQ.first == NULL)), descP->readState) );
+ if (descP->acceptorsQ.first == NULL)
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+}
+
+
+/* *** esaio_completion_accept_aborted ***
+ * The 'accept' operation was aborted.
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_accept_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataAccept* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_acceptor_get(env, descP,
+ &opDataP->accRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->lSockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->readState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->acceptorsQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->readersQ.first == NULL) &&
+ (descP->writersQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+
+ }
+
+ /* *Maybe* update socket (read) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_aborted(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->acceptorsQ.first == NULL)), descP->readState) );
+ if (descP->acceptorsQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_accept_failure *
+ * A "general" failure happened while performing the 'accept' operation.
+ */
+static
+void esaio_completion_accept_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataAccept* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ if (esock_acceptor_get(env, descP,
+ &opDataP->accRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->lSockRef,
+ &req, reason);
+ esaio_completion_accept_fail(env, descP, error, FALSE);
+ } else {
+ esaio_completion_accept_fail(env, descP, error, TRUE);
+ }
+
+ /* *Maybe* update socket (read) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_failure(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->acceptorsQ.first == NULL)), descP->readState) );
+ if (descP->acceptorsQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_accept_completed ***
+ * The accept request has completed.
+ */
+static
+void esaio_completion_accept_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataAccept* opDataP,
+ ESockRequestor* reqP)
+{
+ ERL_NIF_TERM completionStatus, completionInfo;
+ int ucres;
+ ESockDescriptor* accDescP;
+ ERL_NIF_TERM accRef, accSocket;
+
+ ESOCK_ASSERT( DEMONP("esaio_completion_accept_completed - acceptor",
+ env, descP, &reqP->mon) == 0);
+
+ /* We need to make sure peername and sockname works! */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_completed -> "
+ "success - try update context\r\n") );
+
+ ucres = ESAIO_UPDATE_ACCEPT_CONTEXT( opDataP->asock, opDataP->lsock );
+
+ if (ucres == 0) {
+
+ int save_errno;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_completed -> "
+ "create (accepted) descriptor\r\n") );
+
+ accDescP = esock_alloc_descriptor(opDataP->asock);
+
+ if (ESAIO_OK != (save_errno = esaio_add_socket(accDescP))) {
+ // See esock_dtor for what needs done!
+ ERL_NIF_TERM tag = esock_atom_add_socket;
+ ERL_NIF_TERM reason = ENO2T(opEnv, save_errno);
+
+ ESOCK_CNT_INC(env, descP, CP_TERM(env, opDataP->lSockRef),
+ esock_atom_acc_fails, &descP->accFails, 1);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_completed -> "
+ "failed adding (accepted) socket to completion port: "
+ "%T\r\n", reason) );
+
+ esock_dealloc_descriptor(env, accDescP);
+ sock_close(opDataP->asock);
+
+ /* This should really be:
+ * {error, {invalid, {add_to_completion_port, Reason}}}
+ */
+
+ completionStatus = esock_make_error_t2r(opEnv, tag, reason);
+
+ } else {
+
+ ESOCK_CNT_INC(env, descP, CP_TERM(env, opDataP->lSockRef),
+ esock_atom_acc_success, &descP->accSuccess, 1);
+
+ accDescP->domain = descP->domain;
+ accDescP->type = descP->type;
+ accDescP->protocol = descP->protocol;
+
+ MLOCK(descP->writeMtx);
+
+ accDescP->rBufSz = descP->rBufSz; // Inherit buffer size
+ accDescP->rCtrlSz = descP->rCtrlSz; // Inherit buffer size
+ accDescP->wCtrlSz = descP->wCtrlSz; // Inherit buffer size
+ accDescP->iow = descP->iow; // Inherit iow
+ accDescP->dbg = descP->dbg; // Inherit debug flag
+ accDescP->useReg = descP->useReg; // Inherit useReg flag
+ esock_inc_socket(accDescP->domain, accDescP->type,
+ accDescP->protocol);
+
+ accRef = enif_make_resource(env, accDescP);
+ enif_release_resource(accDescP);
+ accSocket = esock_mk_socket(opEnv, CP_TERM(opEnv, accRef));
+
+ accDescP->ctrlPid = *opCaller;
+
+ ESOCK_ASSERT( MONP("esaio_completion_accept_completed -> ctrl",
+ env, accDescP,
+ &accDescP->ctrlPid,
+ &accDescP->ctrlMon) == 0 );
+
+ accDescP->writeState |= ESOCK_STATE_CONNECTED;
+
+ MUNLOCK(descP->writeMtx);
+
+ /* And finally (maybe) update the registry */
+ if (descP->useReg)
+ esock_send_reg_add_msg(env, descP, accRef);
+
+ completionStatus = esock_make_ok2(opEnv, accSocket);
+
+ }
+
+ } else {
+
+ /* It is actually possible that this is an error "we do not know"
+ * which will result in the atom 'unknown', which is not very useful...
+ * So, we should really test if is 'unknown' and if so use the actual
+ * value (the integer) instead.
+ */
+ int save_errno = sock_errno();
+ ERL_NIF_TERM tag = esock_atom_update_accept_context;
+ ERL_NIF_TERM reason = ENO2T(env, save_errno);
+
+ SSDBG( descP, ("WIN-ESAIO",
+ "esaio_completion_accept_completed(%d) -> "
+ "accept context update failed: %T (%d)\r\n",
+ descP->sock, reason, save_errno) );
+
+ sock_close(descP->sock);
+ descP->writeState = ESOCK_STATE_CLOSED;
+
+ WSACleanup();
+
+ completionStatus = esock_make_error_t2r(opEnv, tag, reason);
+
+ }
+
+ completionInfo = MKT2(opEnv, opDataP->accRef, completionStatus);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_completed -> "
+ "send completion message to %T with"
+ "\r\n CompletionInfo: %T"
+ "\r\n", MKPID(env, opCaller), completionInfo) );
+
+ /* Send a 'accept' completion message */
+ esaio_send_completion_msg(env, // Send env
+ descP, // Descriptor
+ opCaller, // Msg destination
+ opEnv, // Msg env
+ opDataP->lSockRef, // Dest socket
+ completionInfo); // Info
+
+ /* *** Finalize *** */
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_accept_completed -> finalize\r\n") );
+
+ /* Request cleanup (demonitor already done above) */
+ esock_clear_env("esaio_completion_accept_completed -> req cleanup",
+ reqP->env);
+ esock_free_env("esaio_completion_accept_completed -> req cleanup",
+ reqP->env);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_accept_completed -> done\r\n") );
+}
+
+
+
+/* *** esaio_completion_accept_not_active ***
+ * A accept request has completed but the request is no longer valid.
+ */
+static
+void esaio_completion_accept_not_active(ESockDescriptor* descP)
+{
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_not_active(%d) -> "
+ "success for not active accept request\r\n", descP->sock) );
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.unexpectedAccepts, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_not_active(%d) -> done\r\n",
+ descP->sock) );
+
+}
+
+
+/* *** esaio_completion_accept_fail ***
+ * Unknown operation failure.
+ */
+static
+void esaio_completion_accept_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform)
+{
+ esaio_completion_fail(env, descP, "accept", error, inform);
+}
+
+
+
+
+/* === send 'stuff' === */
+
+/* *** esaio_completion_send ***
+ *
+ * Handle a completed 'send' (completion) request.
+ * Send a 'completion' message (to requestor) with the request status.
+ *
+ * Completion message:
+ * {'socket tag', socket(), completion, CompletionInfo}
+ *
+ * CompletionInfo: {CompletionHandle, CompletionStatus}
+ * CompletionHandle: reference()
+ * Result: ok | {error, Reason}
+ *
+ *
+ * There is a possibillity of a race here. That is, if the user
+ * calls socket:send(Socket, ..., nowait), the send is scheduled,
+ * and then just as it has completed, but before this
+ * thread has been activated to handle the 'send completed'
+ * the user calls socket:close(Socket) (or exits).
+ * Then when this function is called, the socket is closed.
+ * What to do?
+ *
+ * We need to use 'WSAGetOverlappedResult' to actually figure out the
+ * "transfer result" (how much was sent).
+ */
+static
+BOOLEAN_T esaio_completion_send(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP,
+ int error)
+{
+ ErlNifEnv* env = dataP->env;
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_send(%d) -> entry with"
+ "\r\n error: %T"
+ "\r\n", descP->sock, ENO2T(env, error)) );
+
+ switch (error) {
+ case NO_ERROR:
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_send(%d) -> no error"
+ "\r\n", descP->sock) );
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_send_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ break;
+
+ case WSA_OPERATION_ABORTED:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send(%d) -> operation aborted"
+ "\r\n", descP->sock) );
+ /* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_send_aborted(env, descP, opCaller, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ break;
+
+ default:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send(%d) -> operation unknown failure"
+ "\r\n", descP->sock) );
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_send_failure(env, descP, opCaller, opDataP, error);
+
+ MUNLOCK(descP->writeMtx);
+ break;
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send(%d) -> cleanup\r\n", descP->sock) );
+
+ FREE( opDataP->wbuf.buf );
+
+ /* No need for this "stuff" anymore */
+ esock_clear_env("esaio_completion_send - op cleanup", opEnv);
+ esock_free_env("esaio_completion_send - op cleanup", opEnv);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_send(%d) -> done\r\n",
+ descP->sock) );
+
+ return FALSE;
+
+}
+
+
+
+/* *** esaio_completion_send_success ***
+ * The 'send' operation was successful.
+ */
+static
+void esaio_completion_send_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->writeState)) {
+ esaio_completion_send_completed(env, descP, ovl, opEnv,
+ opCaller,
+ opDataP->sockRef,
+ opDataP->sendRef,
+ opDataP->wbuf.len,
+ &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_send_not_active(descP);
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_success(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+
+/* *** esaio_completion_send_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_send_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->writeState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->writersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->readersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_aborted(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+
+/* *** esaio_completion_send_failure *
+ * A "general" failure happened while performing the 'send' operation.
+ */
+static
+void esaio_completion_send_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_send_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_send_fail(env, descP, error, TRUE);
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_failure(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_send_completed ***
+ * The send request has completed.
+ */
+static
+void esaio_completion_send_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* sender,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ DWORD toWrite,
+ ESockRequestor* reqP)
+{
+ ERL_NIF_TERM completionStatus, completionInfo;
+ DWORD written;
+
+ ESOCK_ASSERT( DEMONP("esaio_completion_send_completed - sender",
+ env, descP, &reqP->mon) == 0);
+
+ /* Success, but we need to check how much we actually got.
+ * Also the 'flags' (which we currentöy ignore)
+ *
+ * CompletionStatus = ok | {ok, RestData}
+ * CompletionInfo = {ConnRef, CompletionStatus}
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_completed ->"
+ "success - try get overlapped result\r\n") );
+
+ if (get_send_ovl_result(descP->sock, ovl, &written)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_completed -> overlapped result: "
+ "\r\n written: %d"
+ "\r\n buffer size: %d"
+ "\r\n", written, toWrite) );
+
+ if (written == toWrite) {
+
+ /* Sent it all => done */
+
+ completionStatus = esaio_completion_send_done(env,
+ descP, sockRef,
+ written);
+
+ } else {
+
+ /* Only send part of the data =>
+ * needs splitting and (maybe) retry (its up to the caller)!
+ */
+
+ completionStatus = esaio_completion_send_partial(env,
+ descP,
+ sockRef,
+ written);
+ }
+
+ } else {
+
+ int save_errno = sock_errno();
+
+ /* Now what?
+ * We know we wrote "something" but we cannot figure out
+ * how much...
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_completed -> "
+ "overlapped result failure: %d\r\n", save_errno) );
+
+ completionStatus =
+ esaio_completion_get_ovl_result_fail(env, descP, save_errno);
+ }
+
+ completionInfo = MKT2(env, CP_TERM(env, sendRef), completionStatus);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_completed -> "
+ "send completion message to %T with"
+ "\r\n CompletionInfo: %T"
+ "\r\n", MKPID(env, sender), completionInfo) );
+
+ /* Send a 'send' completion message */
+ esaio_send_completion_msg(env, // Send env
+ descP, // Descriptor
+ sender, // Msg destination
+ opEnv, // Msg env
+ sockRef, // Dest socket
+ completionInfo); // Info
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_send_completed -> done\r\n") );
+}
+
+
+
+/* *** esaio_completion_send_done ***
+ *
+ * A complete write (the entire buffer was sent).
+ *
+ */
+static
+ERL_NIF_TERM esaio_completion_send_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ DWORD written)
+{
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_pkg, &descP->writePkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_byte, &descP->writeByteCnt, written);
+
+ if (written > descP->writePkgMax)
+ descP->writePkgMax = written;
+
+ return esock_atom_ok;
+}
+
+
+
+/* *** esaio_completion_send_partial ***
+ *
+ * A partial send, that is only part of the buffer was used.
+ *
+ */
+static
+ERL_NIF_TERM esaio_completion_send_partial(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ DWORD written)
+{
+ if (written > 0) {
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_pkg, &descP->writePkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_byte, &descP->writeByteCnt, written);
+
+ if (written > descP->writePkgMax)
+ descP->writePkgMax = written;
+
+ }
+
+ return esock_make_ok2(env, MKI64(env, written));
+
+}
+
+
+
+/* *** esaio_completion_send_not_active ***
+ * A send request has completed but the request is no longer valid.
+ */
+static
+void esaio_completion_send_not_active(ESockDescriptor* descP)
+{
+ /* This send request is *not* "active"!
+ * The send (send, sendto, sendmsg) operation
+ * has been (most likely) cancelled => cleanup.
+ * If the op failed, its safe to assume that the error is
+ * the result of the cancellation, and we do not actually
+ * need to do anything here.
+ * If however, the send succeeded, we need to do "some
+ * cleanup".
+ * But what can we do here?
+ * Send an abort message to the sender or/and owner?
+ * Increment a counter (unexpected acceptssends)?
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_not_active(%d) -> "
+ "success for not active send request\r\n",
+ descP->sock) );
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.unexpectedWrites, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_not_active(%d) -> done\r\n",
+ descP->sock) );
+
+}
+
+
+
+/* *** esaio_completion_send_fail ***
+ * Unknown operation failure.
+ */
+static
+void esaio_completion_send_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform)
+{
+ esaio_completion_fail(env, descP, "send", error, inform);
+}
+
+
+
+/* *** esaio_completion_sendto ***
+ *
+ * Handle a completed 'sendto' (completion) request.
+ * Send a 'completion' message (to requestor) with the request status.
+ *
+ * Completion message:
+ * {'socket tag', socket(), completion, CompletionInfo}
+ *
+ * CompletionInfo: {CompletionHandle, CompletionStatus}
+ * CompletionHandle: reference()
+ * Result: ok | {error, Reason}
+ *
+ *
+ * There is a possibillity of a race here. That is, if the user
+ * calls socket:sendto(Socket, ..., nowait), the send is scheduled,
+ * and then just as it has completed, but before this
+ * thread has been activated to handle the 'send completed'
+ * the user calls socket:close(Socket) (or exits).
+ * Then when this function is called, the socket is closed.
+ * What to do?
+ *
+ * We need to use 'WSAGetOverlappedResult' to actually figure out the
+ * "transfer result" (how much was sent).
+ */
+static
+BOOLEAN_T esaio_completion_sendto(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP,
+ int error)
+{
+ ErlNifEnv* env = dataP->env;
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_sendto(%d) -> entry"
+ "\r\n error: %T"
+ "\r\n", descP->sock, ENO2T(env, error)) );
+
+ switch (error) {
+ case NO_ERROR:
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_sendto(%d) -> no error"
+ "\r\n", descP->sock) );
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_sendto_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ break;
+
+ case WSA_OPERATION_ABORTED:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendto(%d) -> operation aborted"
+ "\r\n", descP->sock) );
+ /* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_sendto_aborted(env, descP, opCaller, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ break;
+
+ default:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendto(%d) -> operation unknown failure"
+ "\r\n", descP->sock) );
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_sendto_failure(env, descP, opCaller, opDataP, error);
+
+ MUNLOCK(descP->writeMtx);
+ break;
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendto(%d) -> cleanup\r\n",
+ descP->sock) );
+
+ FREE( opDataP->wbuf.buf );
+
+ /* No need for this "stuff" anymore */
+ esock_clear_env("esaio_completion_sendto - op cleanup", opEnv);
+ esock_free_env("esaio_completion_sendto - op cleanup", opEnv);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_sendto(%d) -> done\r\n",
+ descP->sock) );
+
+ return FALSE;
+}
+
+
+
+/* *** esaio_completion_sendto_suuccess *** */
+static
+void esaio_completion_sendto_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->writeState)) {
+ esaio_completion_send_completed(env, descP, ovl, opEnv,
+ opCaller,
+ opDataP->sockRef,
+ opDataP->sendRef,
+ opDataP->wbuf.len,
+ &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_send_not_active(descP);
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendto_success(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+
+/* *** esaio_completion_sendto_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_sendto_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->writeState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->writersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->readersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendto_aborted(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+}
+
+
+/* *** esaio_completion_sendto_failure *
+ * A "general" failure happened while performing the 'sendto' operation.
+ */
+static
+void esaio_completion_sendto_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_sendto_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_sendto_fail(env, descP, error, TRUE);
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendto_failure(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+
+/* *** esaio_completion_sendto_fail ***
+ * Unknown operation failure.
+ */
+static
+void esaio_completion_sendto_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform)
+{
+ esaio_completion_fail(env, descP, "sendto", error, inform);
+}
+
+
+
+/* *** esaio_completion_sendmsg ***
+ *
+ * Handle a completed 'sendmsg' (completion) request.
+ * Send a 'completion' message (to requestor) with the request status.
+ *
+ * Completion message:
+ * {'socket tag', socket(), completion, CompletionInfo}
+ *
+ * CompletionInfo: {CompletionHandle, CompletionStatus}
+ * CompletionHandle: reference()
+ * Result: ok | {error, Reason}
+ *
+ *
+ * There is a possibillity of a race here. That is, if the user
+ * calls socket:sendto(Socket, ..., nowait), the send is scheduled,
+ * and then just as it has completed, but before this
+ * thread has been activated to handle the 'send completed'
+ * the user calls socket:close(Socket) (or exits).
+ * Then when this function is called, the socket is closed.
+ * What to do?
+ *
+ * We need to use 'WSAGetOverlappedResult' to actually figure out the
+ * "transfer result" (how much was sent).
+ */
+static
+BOOLEAN_T esaio_completion_sendmsg(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP,
+ int error)
+{
+ ErlNifEnv* env = dataP->env;
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_sendmsg(%d) -> entry with"
+ "\r\n error: %T"
+ "\r\n", descP->sock, ENO2T(env, error)) );
+
+ switch (error) {
+ case NO_ERROR:
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_sendmsg(%d) -> no error"
+ "\r\n", descP->sock) );
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_sendmsg_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ break;
+
+ case WSA_OPERATION_ABORTED:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendmsg(%d) -> operation aborted"
+ "\r\n", descP->sock) );
+ /* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_sendmsg_aborted(env, descP, opCaller, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ break;
+
+ default:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendmsg(%d) -> operation unknown failure"
+ "\r\n", descP->sock) );
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_sendmsg_failure(env, descP, opCaller, opDataP, error);
+
+ MUNLOCK(descP->writeMtx);
+ break;
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendmsg(%d) -> cleanup\r\n", descP->sock) );
+
+ /* "Manually" allocated buffers */
+ FREE( opDataP->msg.lpBuffers );
+ if (opDataP->ctrlBuf != NULL)
+ FREE( opDataP->ctrlBuf );
+
+ /* No need for this "stuff" anymore */
+ esock_clear_env("esaio_completion_sendmsg - op cleanup", opEnv);
+ esock_free_env("esaio_completion_sendmsg - op cleanup", opEnv);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_sendmsg(%d) -> done\r\n",
+ descP->sock) );
+
+ return FALSE;
+
+}
+
+
+/* *** esaio_completion_sendmsg_suuccess *** */
+static
+void esaio_completion_sendmsg_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->writeState)) {
+
+ DWORD toWrite = 0;
+
+ /* Calculate how much data *in total*
+ * we was supposed to write */
+ for (int i = 0; i < opDataP->iovec->iovcnt; i++) {
+ toWrite += opDataP->iovec->iov[i].iov_len;
+ }
+
+ esaio_completion_send_completed(env, descP, ovl, opEnv,
+ opCaller,
+ opDataP->sockRef,
+ opDataP->sendRef,
+ toWrite,
+ &req);
+
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_send_not_active(descP);
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendmsg_success(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_sendmsg_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_sendmsg_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->writeState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->writersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->readersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendmsg_aborted(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_sendmsg_failure *
+ * A "general" failure happened while performing the 'sendmsg' operation.
+ */
+static
+void esaio_completion_sendmsg_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_sendmsg_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_sendmsg_fail(env, descP, error, TRUE);
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendmsg_success(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_sendmsg_fail ***
+ * Unknown operation failure.
+ */
+static
+void esaio_completion_sendmsg_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform)
+{
+ esaio_completion_fail(env, descP, "sendmsg", error, inform);
+}
+
+
+
+/* === receive 'stuff' === */
+
+/* *** esaio_completion_recv ***
+ *
+ * Handle a completed 'recv' (completion) request.
+ * Send a 'completion' message (to requestor) with the request status.
+ *
+ * Completion message:
+ * {'socket tag', socket(), completion, CompletionInfo}
+ *
+ * CompletionInfo: {CompletionHandle, CompletionStatus}
+ * CompletionHandle: reference()
+ * Result: ok | {error, Reason}
+ *
+ *
+ * There is a possibillity of a race here. That is, if the user
+ * calls socket:recv(Socket, ..., nowait), the recv is scheduled,
+ * and then just as it has completed, but before this
+ * thread has been activated to handle the 'recv completed'
+ * the user calls socket:close(Socket) (or exits).
+ * Then when this function is called, the socket is closed.
+ * What to do?
+ */
+static
+BOOLEAN_T esaio_completion_recv(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP,
+ int error)
+{
+ ErlNifEnv* env = dataP->env;
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recv(%d) -> entry with"
+ "\r\n error: %s (%d)"
+ "\r\n", descP->sock, erl_errno_id(error), error) );
+
+ switch (error) {
+ case NO_ERROR:
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recv(%d) -> no error"
+ "\r\n", descP->sock) );
+ MLOCK(descP->readMtx);
+
+ esaio_completion_recv_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
+
+ MUNLOCK(descP->readMtx);
+ break;
+
+ case WSA_OPERATION_ABORTED:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv(%d) -> operation aborted"
+ "\r\n", descP->sock) );
+ /* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_recv_aborted(env, descP, opCaller, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ break;
+
+ default:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv(%d) -> operation unknown failure"
+ "\r\n", descP->sock) );
+ MLOCK(descP->readMtx);
+
+ esaio_completion_recv_failure(env, descP, opCaller, opDataP, error);
+
+ MUNLOCK(descP->readMtx);
+ break;
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv {%d} -> clear and delete op env\r\n",
+ descP->sock) );
+
+ /* No need for this "stuff" anymore */
+ esock_clear_env("esaio_completion_recv - op cleanup", opEnv);
+ esock_free_env("esaio_completion_recv - op cleanup", opEnv);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recv(%d) -> done\r\n",
+ descP->sock) );
+
+ return FALSE;
+}
+
+
+static
+void esaio_completion_recv_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->readState)) {
+ esaio_completion_recv_completed(env, descP, ovl, opEnv,
+ opCaller, opDataP,
+ &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_recv_not_active(descP);
+ FREE_BIN( &opDataP->buf );
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ * => But we do not free the "buffer" since it was "used up"
+ * when we (as assumed) got the result (directly)...
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_success(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recv_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_recv_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->readState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->readersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->writersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+ }
+
+ FREE_BIN( &opDataP->buf );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_aborted(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recv_failure *
+ * A "general" failure happened while performing the 'recv' operation.
+ */
+static
+void esaio_completion_recv_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+ /* Figure out the reason */
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_recv_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_recv_fail(env, descP, error, TRUE);
+ }
+
+ FREE_BIN( &opDataP->buf );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_failure(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recv_completed ***
+ * The recv request has completed.
+ */
+static
+void esaio_completion_recv_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP,
+ ESockRequestor* reqP)
+{
+ ERL_NIF_TERM completionStatus, completionInfo;
+ DWORD read, flags;
+
+ ESOCK_ASSERT( DEMONP("esaio_completion_recv_completed - sender",
+ env, descP, &reqP->mon) == 0);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_completed ->"
+ "success - try get overlapped result\r\n") );
+
+ if (get_recv_ovl_result(descP->sock, ovl, &read, &flags)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_completed -> overlapped result: "
+ "\r\n read: %d"
+ "\r\n buffer size: %d"
+ "\r\n flags: %d"
+ "\r\n", read, opDataP->buf.size, flags) );
+
+ /* *** Success! ***
+ * CompletionStatus = {ok, {Flags, Bin}} (should be)
+ * CompletionInfo = {ConnRef, CompletionStatus}
+ */
+
+ if ((read == 0) && (descP->type == SOCK_STREAM)) {
+
+ /*
+ * When a stream socket peer has performed an orderly
+ * shutdown, the return value will be 0 (the traditional
+ * "end-of-file" return).
+ *
+ * *We* do never actually try to read 0 bytes!
+ */
+
+ ESOCK_CNT_INC(env, descP, opDataP->sockRef,
+ esock_atom_read_fails, &descP->readFails, 1);
+
+ completionStatus = esock_make_error(opEnv, esock_atom_closed);
+
+ } else {
+
+ if (read == opDataP->buf.size) {
+ /* We filled the buffer => done */
+
+ completionStatus =
+ esaio_completion_recv_done(env, descP,
+ opEnv, opDataP,
+ flags);
+
+ } else {
+
+ /* Only used a part of the buffer =>
+ * needs splitting and (maybe) retry (its up to the caller)!
+ */
+
+ completionStatus =
+ esaio_completion_recv_partial(env, descP,
+ opEnv, opDataP,
+ reqP, read, flags);
+ }
+
+ }
+
+ } else {
+
+ int save_errno = sock_errno();
+
+ /* Now what?
+ * We know we read "something" but we cannot figure out
+ * how much...
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_completed -> "
+ "overlapped result failure: %d\r\n", save_errno) );
+
+ completionStatus =
+ esaio_completion_get_ovl_result_fail(opEnv, descP, save_errno);
+ }
+
+ completionInfo = MKT2(opEnv, opDataP->recvRef, completionStatus);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_completed -> "
+ "send completion message to %T with"
+ "\r\n CompletionInfo: %T"
+ "\r\n", MKPID(env, opCaller), completionInfo) );
+
+ /* Send a 'send' completion message */
+ esaio_send_completion_msg(env, // Send env
+ descP, // Descriptor
+ opCaller, // Msg destination
+ opEnv, // Msg env
+ opDataP->sockRef, // Socket
+ completionInfo); // Info
+
+ /* *** Finalize *** */
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recv_completed -> finalize\r\n") );
+
+ /* Request cleanup (demonitor already done above) */
+ esock_clear_env("esaio_completion_recv_completed -> req cleanup",
+ reqP->env);
+ esock_free_env("esaio_completion_recv_completed -> req cleanup",
+ reqP->env);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recv_completed -> done\r\n") );
+}
+
+
+
+/* *** esaio_completion_recv_done ***
+ *
+ * A complete read (filled the provided buffer).
+ *
+ */
+static
+ERL_NIF_TERM esaio_completion_recv_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecv* opDataP,
+ DWORD flags)
+{
+ ERL_NIF_TERM data;
+ ERL_NIF_TERM sockRef = opDataP->sockRef;
+ ERL_NIF_TERM recvRef = opDataP->recvRef;
+ DWORD read = opDataP->buf.size;
+
+ (void) flags;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_done(%T) {%d} -> entry with"
+ "\r\n recvRef: %T"
+ "\r\n flags: 0x%X"
+ "\r\n", sockRef, descP->sock, recvRef, flags) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ if (read > descP->readPkgMax)
+ descP->readPkgMax = read;
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(opEnv, &opDataP->buf);
+
+ /* We ignore the flags *for now*.
+ * Needs to be passed up eventually!
+ *
+ * This should eventually be something like:
+ *
+ * {ok, {Flags, Bin}}
+ *
+ * But for now we skip the 'flags' part:
+ *
+ * {ok, Bin}
+ */
+ return esock_make_ok2(opEnv, data);
+}
+
+
+/* *** esaio_completion_recv_partial ***
+ *
+ * A partial read, that is only part of the buffer was used.
+ *
+ */
+static
+ERL_NIF_TERM esaio_completion_recv_partial(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecv* opDataP,
+ ESockRequestor* reqP,
+ DWORD read,
+ DWORD flags)
+{
+ ERL_NIF_TERM res;
+ ERL_NIF_TERM sockRef = opDataP->sockRef;
+ ERL_NIF_TERM recvRef = opDataP->recvRef;
+ DWORD toRead = opDataP->toRead;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_partial(%T) {%d} -> entry with"
+ "\r\n toRead: %ld"
+ "\r\n recvRef: %T"
+ "\r\n read: %ld"
+ "\r\n flags: 0x%X"
+ "\r\n", sockRef, descP->sock,
+ (long) toRead, recvRef, (long) read, flags) );
+
+ /* We only got part of what we wanted, but since we let the user
+ * (or possibly the read loop in socket) decide what to do,
+ * do we actually need this check? Why not just call a
+ * 'esaio_completion_recv_partial' function (that splits the
+ * binary and deliver what we got) and let the user sort it out?
+ */
+
+ if ((toRead == 0) ||
+ (descP->type != SOCK_STREAM)) {
+
+ /* +++ We only got a partial, ***
+ * *** but we should not wait for more. +++
+ * +++ Must split it into a sub-binary. +++
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_partial(%T) {%d} -> done reading\r\n",
+ sockRef, descP->sock) );
+
+ res = esaio_completion_recv_partial_done(env, descP,
+ opEnv, opDataP,
+ read, flags);
+
+ } else {
+
+ /* A stream socket with specified read size
+ * and not a polling read, we got a partial read
+ * - return a result to initiate a retry
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_partial(%T) {%d} ->"
+ " only part of data - expected more"
+ "\r\n", sockRef, descP->sock) );
+
+ res = esaio_completion_recv_partial_part(env, descP,
+ opEnv, opDataP,
+ read, flags);
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_partial(%T) {%d} -> done\r\n",
+ sockRef, descP->sock) );
+
+ return res;
+}
+
+
+
+/* *** esaio_completion_recv_partial_done ***
+ *
+ * A successful but only partial recv, which fulfilled the required read.
+ */
+
+static
+ERL_NIF_TERM esaio_completion_recv_partial_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecv* opDataP,
+ ssize_t read,
+ DWORD flags)
+{
+ ERL_NIF_TERM sockRef = opDataP->sockRef;
+ ERL_NIF_TERM data;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ if (read > descP->readPkgMax)
+ descP->readPkgMax = read;
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(opEnv, &opDataP->buf);
+ data = MKSBIN(opEnv, data, 0, read);
+
+ (void) flags;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_partial_done(%T) {%d} -> done\r\n",
+ sockRef, descP->sock) );
+
+ return esock_make_ok2(opEnv, data);
+}
+
+
+
+/* *** esaio_completion_recv_partial_part ***
+ *
+ * A successful but only partial recv, which only partly fulfilled
+ * the required read.
+ * We do *not* want to risk ending up in a "never ending" read loop
+ * here (by trying to read more data (and yet again getting partial)).
+ * [worst case, we could up with all our worker threads busy trying
+ * to read more data, and no one ready to respond to new requests].
+ * So we simply return what we got to the user and let the user
+ * decide what to do.
+ *
+ * What shall we send? {ok, Bin} | {more, Bin}
+ * Presumably the user knows how much to expect, so is therefor
+ * able to check:
+ *
+ * "Expected > byte_size(Bin)" -> read again
+ * "Expected =:= byte_size(Bin)" -> done
+ */
+
+static
+ERL_NIF_TERM esaio_completion_recv_partial_part(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecv* opDataP,
+ ssize_t read,
+ DWORD flags)
+{
+ /* This is just a "placeholder". Is this really all we need to do? */
+ return esaio_completion_recv_partial_done(env, descP,
+ opEnv, opDataP,
+ read, flags);
+}
+
+
+
+/* *** esaio_completion_recv_not_active ***
+ * A recv request has completed but the request is no longer valid.
+ */
+static
+void esaio_completion_recv_not_active(ESockDescriptor* descP)
+{
+ /* This receive request is *not* "active"!
+ * The receive (recv,recvfrom,recvmsg) operation
+ * has been (most likely) cancelled => cleanup.
+ * If the op failed, its safe to assume that the error is
+ * the result of the cancellation, and we do not actually
+ * need to do anything here.
+ * If however, the recv succeeded, we need to do "some
+ * cleanup".
+ * But what can we do here?
+ * Send an abort message to the reader or/and owner?
+ * Increment a counter (unexpected readsa)?
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_not_active {%d} -> "
+ "success for not active read request\r\n", descP->sock) );
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.unexpectedReads, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_not_active {%d} -> done\r\n",
+ descP->sock) );
+
+}
+
+
+
+/* *** esaio_completion_recv_closed ***
+ * A recv request has completed but the socket is closed.
+ * When the socket is closed, all outstanding requests
+ * are "flushed", so we do not actually need to "do" anything
+ * here (other then maybe count unexpected writes), maybe read bytes?.
+ */
+static
+void esaio_completion_recv_closed(ESockDescriptor* descP,
+ int error)
+{
+ if (error == NO_ERROR) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_closed -> "
+ "success for closed socket (%d)\r\n",
+ descP->sock) );
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.unexpectedReads, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+ }
+}
+
+
+
+/* *** esaio_completion_recv_fail ***
+ * Unknown operation failure.
+ */
+static
+void esaio_completion_recv_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform)
+{
+ esaio_completion_fail(env, descP, "recv", error, inform);
+}
+
+
+
+/* *** esaio_completion_recvfrom ***
+ *
+ * Handle a completed 'recvfrom' (completion) request.
+ * Send a 'completion' message (to requestor) with the request status.
+ *
+ * Completion message:
+ * {'socket tag', socket(), completion, CompletionInfo}
+ *
+ * CompletionInfo: {CompletionHandle, CompletionStatus}
+ * CompletionHandle: reference()
+ * Result: ok | {error, Reason}
+ *
+ *
+ * There is a possibillity of a race here. That is, if the user
+ * calls socket:recvfrom(Socket, ..., nowait), the receive is scheduled,
+ * and then just as it has completed, but before this
+ * thread has been activated to handle the 'recv completed'
+ * the user calls socket:close(Socket) (or exits).
+ * Then when this function is called, the socket is closed.
+ * What to do?
+ */
+static
+BOOLEAN_T esaio_completion_recvfrom(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ int error)
+{
+ ErlNifEnv* env = dataP->env;
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recvfrom(%d) -> entry with"
+ "\r\n error: %T, %s (%d)"
+ "\r\n",
+ descP->sock, ENO2T(env, error),
+ erl_errno_id(error), error) );
+
+ switch (error) {
+ case NO_ERROR:
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recvfrom(%d) -> no error"
+ "\r\n", descP->sock) );
+ MLOCK(descP->readMtx);
+
+ esaio_completion_recvfrom_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
+
+ MUNLOCK(descP->readMtx);
+ break;
+
+ case ERROR_MORE_DATA:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom(%d) -> more data"
+ "\r\n", descP->sock) );
+ MLOCK(descP->readMtx);
+
+ esaio_completion_recvfrom_more_data(env, descP,
+ opEnv, opCaller, opDataP,
+ error);
+
+ MUNLOCK(descP->readMtx);
+ break;
+
+ case WSA_OPERATION_ABORTED:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom(%d) -> operation aborted"
+ "\r\n", descP->sock) );
+ /* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_recvfrom_aborted(env, descP, opCaller, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ break;
+
+ default:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom(%d) -> operation unknown failure"
+ "\r\n", descP->sock) );
+ MLOCK(descP->readMtx);
+
+ esaio_completion_recvfrom_failure(env, descP, opCaller, opDataP, error);
+
+ MUNLOCK(descP->readMtx);
+ break;
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom {%d} -> clear and delete op env\r\n") );
+
+ /* No need for this "stuff" anymore */
+ esock_clear_env("esaio_completion_recvfrom - op cleanup", opEnv);
+ esock_free_env("esaio_completion_recvfrom - op cleanup", opEnv);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recvfrom {%d} -> done\r\n") );
+
+ return FALSE;
+}
+
+
+static
+void esaio_completion_recvfrom_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->readState)) {
+ esaio_completion_recvfrom_completed(env, descP,
+ ovl, opEnv, opCaller,
+ opDataP, &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_recv_not_active(descP);
+ FREE_BIN( &opDataP->buf );
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ * => But we do not free the "buffer" since it was "used up"
+ * when we (as assumed) got the result (directly)...
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_success(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+static
+void esaio_completion_recvfrom_more_data(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ int error)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->readState)) {
+ /* We do not actually need to call this function
+ * since we already know its 'more_data', but just
+ * get the same format...
+ */
+ ERL_NIF_TERM reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+ ERL_NIF_TERM completionStatus = esock_make_error(env, reason);
+ ERL_NIF_TERM completionInfo = MKT2(opEnv,
+ opDataP->recvRef,
+ completionStatus);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_more_data(%d) -> "
+ "send completion message: "
+ "\r\n Completion Status: %T"
+ "\r\n", descP->sock, completionStatus) );
+
+ /* Send a 'recvfrom' completion message */
+ esaio_send_completion_msg(env, // Send env
+ descP, // Descriptor
+ opCaller, // Msg destination
+ opEnv, // Msg env
+ opDataP->sockRef, // Dest socket
+ completionInfo); // Info
+
+ }
+
+ FREE_BIN( &opDataP->buf );
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ * => But we do not free the "buffer" since it was "used up"
+ * when we (as assumed) got the result (directly)...
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_more_data(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recvfrom_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+static
+void esaio_completion_recvfrom_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->readState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->readersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->writersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+ }
+
+ FREE_BIN( &opDataP->buf );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_aborted(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recvfrom_failure *
+ * A "general" failure happened while performing the 'recvfrom' operation.
+ */
+static
+void esaio_completion_recvfrom_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_recvfrom_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_recvfrom_fail(env, descP, error, TRUE);
+ }
+
+ FREE_BIN( &opDataP->buf );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_failure(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recvfrom_completed ***
+ * The recvfrom request has completed.
+ */
+static
+void esaio_completion_recvfrom_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ ESockRequestor* reqP)
+{
+ ERL_NIF_TERM completionStatus, completionInfo;
+ DWORD read, flags;
+
+ ESOCK_ASSERT( DEMONP("esaio_completion_recvfrom_completed - sender",
+ env, descP, &reqP->mon) == 0);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_completed ->"
+ "success - try get overlapped result\r\n") );
+
+ if (get_recv_ovl_result(descP->sock, ovl, &read, &flags)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_completed -> overlapped result: "
+ "\r\n read: %d"
+ "\r\n buffer size: %d"
+ "\r\n flags: %d"
+ "\r\n", read, opDataP->buf.size, flags) );
+
+ /* *** Success! ***
+ * CompletionStatus = {ok, {Flags, Bin}} (should be)
+ * CompletionInfo = {ConnRef, CompletionStatus}
+ */
+
+ if (read == opDataP->buf.size) {
+ /* We filled the buffer => done */
+
+ completionStatus =
+ esaio_completion_recvfrom_done(env, descP,
+ opEnv, opDataP,
+ flags);
+
+ } else {
+
+ /* Only used a part of the buffer =>
+ * needs splitting and (maybe) retry (its up to the caller)!
+ */
+
+ completionStatus =
+ esaio_completion_recvfrom_partial(env, descP,
+ opEnv, opDataP,
+ reqP, read, flags);
+ }
+
+ } else {
+
+ int save_errno = sock_errno();
+
+ /* Now what?
+ * We know we read "something" but we cannot figure out
+ * how much...
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_completed -> "
+ "overlapped result failure: %d\r\n", save_errno) );
+
+ completionStatus =
+ esaio_completion_get_ovl_result_fail(opEnv, descP, save_errno);
+
+ FREE_BIN( &opDataP->buf );
+ }
+
+ completionInfo = MKT2(opEnv, opDataP->recvRef, completionStatus);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_completed -> "
+ "send completion message to %T with"
+ "\r\n CompletionInfo: %T"
+ "\r\n", MKPID(env, opCaller), completionInfo) );
+
+ /* Send a 'send' completion message */
+ esaio_send_completion_msg(env, // Send env
+ descP, // Descriptor
+ opCaller, // Msg destination
+ opEnv, // Msg env
+ opDataP->sockRef, // Dest socket
+ completionInfo); // Info
+
+ /* *** Finalize *** */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_completed -> finalize\r\n") );
+
+ /* Request cleanup (demonitor already done above) */
+ esock_clear_env("esaio_completion_recvfrom_completed -> req cleanup",
+ reqP->env);
+ esock_free_env("esaio_completion_recvfrom_completed -> req cleanup",
+ reqP->env);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recvfrom_completed -> done\r\n") );
+}
+
+
+
+/* *** esaio_completion_recvfrom_done ***
+ *
+ * A complete read (filled the provided buffer).
+ *
+ */
+static
+ERL_NIF_TERM esaio_completion_recvfrom_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecvFrom* opDataP,
+ DWORD flags)
+{
+ ERL_NIF_TERM res, data, eSockAddr;
+ ERL_NIF_TERM sockRef = opDataP->sockRef;
+ ERL_NIF_TERM recvRef = opDataP->recvRef;
+ DWORD read = opDataP->buf.size;
+
+ (void) flags;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_done(%T) {%d} -> entry with"
+ "\r\n recvRef: %T"
+ "\r\n flags: 0x%X"
+ "\r\n", sockRef, descP->sock, recvRef, flags) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ if (read > descP->readPkgMax)
+ descP->readPkgMax = read;
+
+ esock_encode_sockaddr(opEnv,
+ &opDataP->fromAddr,
+ opDataP->addrLen,
+ &eSockAddr);
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(opEnv, &opDataP->buf);
+
+ /* We ignore the flags *for now*.
+ * Needs to be passed up eventually!
+ *
+ * This should eventually be something like:
+ *
+ * {ok, {Source, Flags, Data}}
+ *
+ * But for now we skip the 'flags' part:
+ *
+ * {ok, {Source, Data}}
+ */
+ res = esock_make_ok2(opEnv, MKT2(opEnv, eSockAddr, data));
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_done(%T) {%d} -> done\r\n",
+ sockRef, descP->sock) );
+
+ return res;
+}
+
+
+
+/* *** esaio_completion_recvfrom_partial ***
+ *
+ * A partial read, that is only part of the buffer was used.
+ *
+ */
+static
+ERL_NIF_TERM esaio_completion_recvfrom_partial(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecvFrom* opDataP,
+ ESockRequestor* reqP,
+ DWORD read,
+ DWORD flags)
+{
+ ERL_NIF_TERM res, data, eSockAddr;
+ ERL_NIF_TERM sockRef = opDataP->sockRef;
+ ERL_NIF_TERM recvRef = opDataP->recvRef;
+
+ (void) flags;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_partial(%T) {%d} -> entry with"
+ "\r\n recvRef: %T"
+ "\r\n read: %ld"
+ "\r\n flags: 0x%X"
+ "\r\n", sockRef, descP->sock, recvRef, (long) read, flags) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ if (read > descP->readPkgMax)
+ descP->readPkgMax = read;
+
+ esock_encode_sockaddr(opEnv,
+ &opDataP->fromAddr,
+ opDataP->addrLen,
+ &eSockAddr);
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(opEnv, &opDataP->buf);
+ data = MKSBIN(opEnv, data, 0, read);
+
+ /* We ignore the flags *for now*.
+ * Needs to be passed up eventually!
+ *
+ * This should eventually be something like:
+ *
+ * {ok, {Source, Flags, Data}}
+ *
+ * But for now we skip the 'flags' part:
+ *
+ * {ok, {Source, Data}}
+ */
+
+ res = esock_make_ok2(opEnv, MKT2(opEnv, eSockAddr, data));
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_partial(%T) {%d} -> done\r\n",
+ sockRef, descP->sock) );
+
+ return res;
+}
+
+
+
+/* *** esaio_completion_recvfrom_fail ***
+ * Unknown operation failure.
+ */
+static
+void esaio_completion_recvfrom_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform)
+{
+ esaio_completion_fail(env, descP, "recvfrom", error, inform);
+}
+
+
+
+/* *** esaio_completion_recvmsg ***
+ *
+ * Handle a completed 'recvmsg' (completion) request.
+ * Send a 'completion' message (to requestor) with the request status.
+ *
+ * Completion message:
+ * {'socket tag', socket(), completion, CompletionInfo}
+ *
+ * CompletionInfo: {CompletionHandle, CompletionStatus}
+ * CompletionHandle: reference()
+ * Result: ok | {error, Reason}
+ *
+ *
+ * There is a possibillity of a race here. That is, if the user
+ * calls socket:recvmsg(Socket, ..., nowait), the receive is scheduled,
+ * and then just as it has completed, but before this
+ * thread has been activated to handle the 'recv completed'
+ * the user calls socket:close(Socket) (or exits).
+ * Then when this function is called, the socket is closed.
+ * What to do?
+ */
+static
+BOOLEAN_T esaio_completion_recvmsg(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP,
+ int error)
+{
+ ErlNifEnv* env = dataP->env;
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recvmsg(%d) -> entry with"
+ "\r\n error: %T"
+ "\r\n", descP->sock, ENO2T(env, error)) );
+
+ switch (error) {
+ case NO_ERROR:
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recvmsg(%d) -> no error:"
+ "\r\n try get request %T from %T"
+ "\r\n",
+ descP->sock,
+ opDataP->recvRef, MKPID(env, opCaller)) );
+ MLOCK(descP->readMtx);
+
+ esaio_completion_recvmsg_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
+
+ MUNLOCK(descP->readMtx);
+ break;
+
+ case WSA_OPERATION_ABORTED:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg(%d) -> operation aborted"
+ "\r\n", descP->sock) );
+ /* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ esaio_completion_recvmsg_aborted(env, descP, opCaller, opDataP);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ break;
+
+ default:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg(%d) -> unknown operation failure"
+ "\r\n", descP->sock) );
+ MLOCK(descP->readMtx);
+
+ esaio_completion_recvmsg_failure(env, descP, opCaller, opDataP, error);
+
+ MUNLOCK(descP->readMtx);
+ break;
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg {%d} -> clear and delete op env\r\n",
+ descP->sock) );
+
+ /* No need for this "stuff" anymore */
+ esock_clear_env("esaio_completion_recvmsg - op cleanup", opEnv);
+ esock_free_env("esaio_completion_recvmsg - op cleanup", opEnv);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recvmsg {%d} -> done\r\n",
+ descP->sock) );
+
+ return FALSE;
+}
+
+
+static
+void esaio_completion_recvmsg_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->readState)) {
+ esaio_completion_recvmsg_completed(env, descP, ovl, opEnv,
+ opCaller, opDataP,
+ &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_recv_not_active(descP);
+ FREE_BIN( &opDataP->data[0] );
+ FREE_BIN( &opDataP->ctrl );
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ * => But we do not free the "buffer" since it was "used up"
+ * when we (as assumed) got the result (directly)...
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_success(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recvmsg_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_recvmsg_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->readState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->readersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->writersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+
+ }
+
+ FREE_BIN( &opDataP->data[0] );
+ FREE_BIN( &opDataP->ctrl );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_aborted(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recvmsg_failure *
+ * A "general" failure happened while performing the 'recvmsg' operation.
+ */
+static
+void esaio_completion_recvmsg_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_recvmsg_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_recvmsg_fail(env, descP, error, TRUE);
+ }
+
+ FREE_BIN( &opDataP->data[0] );
+ FREE_BIN( &opDataP->ctrl );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_failure(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recvmsg_completed ***
+ * The recvmsg request has completed.
+ */
+static
+void esaio_completion_recvmsg_completed(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP,
+ ESockRequestor* reqP)
+{
+ ERL_NIF_TERM completionStatus, completionInfo;
+ DWORD read, flags;
+
+ ESOCK_ASSERT( DEMONP("esaio_completion_recvmsg_completed - sender",
+ env, descP, &reqP->mon) == 0);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_completed ->"
+ "success - try get overlapped result\r\n") );
+
+ if (get_recv_ovl_result(descP->sock, ovl, &read, &flags)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_completed -> overlapped result: "
+ "\r\n read: %d"
+ "\r\n buffer size: %d"
+ "\r\n flags: %d"
+ "\r\n", read, opDataP->data[0].size, flags) );
+
+ /* *** Success! ***
+ * CompletionStatus = {ok, Msg} (should be)
+ * CompletionInfo = {ConnRef, CompletionStatus}
+ */
+
+ /* We should have "calculated" the entire size before,
+ * but since we have a vector of size one...
+ */
+ if (read == opDataP->data[0].size) {
+ /* We filled the buffer => done */
+
+ completionStatus =
+ esaio_completion_recvmsg_done(env, descP,
+ opEnv, opDataP,
+ flags);
+
+ } else {
+
+ /* Only used a part of the buffer =>
+ * needs splitting and (maybe) retry (its up to the caller)!
+ */
+
+ completionStatus =
+ esaio_completion_recvmsg_partial(env, descP,
+ opEnv, opDataP,
+ reqP, read, flags);
+ }
+
+ } else {
+
+ int save_errno = sock_errno();
+
+ /* Now what?
+ * We know we read "something" but we cannot figure out
+ * how much...
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_completed -> "
+ "overlapped result failure: %d\r\n", save_errno) );
+
+ completionStatus =
+ esaio_completion_get_ovl_result_fail(opEnv, descP, save_errno);
+
+ }
+
+ completionInfo = MKT2(opEnv, opDataP->recvRef, completionStatus);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_completed -> "
+ "send completion message to %T with"
+ "\r\n CompletionInfo: %T"
+ "\r\n", MKPID(env, opCaller), completionInfo) );
+
+ /* Send a 'send' completion message */
+ esaio_send_completion_msg(env, // Send env
+ descP, // Descriptor
+ opCaller, // Msg destination
+ opEnv, // Msg env
+ opDataP->sockRef, // Dest socket
+ completionInfo); // Info
+
+ /* *** Finalize *** */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_completed -> finalize\r\n") );
+
+ /* Request cleanup (demonitor already done above) */
+ esock_clear_env("esaio_completion_recvmsg_completed -> req cleanup",
+ reqP->env);
+ esock_free_env("esaio_completion_recvmsg_completed -> req cleanup",
+ reqP->env);
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_completion_recvmsg_completed -> done\r\n") );
+}
+
+
+
+/* *** esaio_completion_recvmsg_done ***
+ *
+ * A complete read (filled the provided buffer).
+ *
+ */
+static
+ERL_NIF_TERM esaio_completion_recvmsg_done(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecvMsg* opDataP,
+ DWORD flags)
+{
+ ERL_NIF_TERM res, eMsg;
+ ERL_NIF_TERM sockRef = opDataP->sockRef;
+ ERL_NIF_TERM recvRef = opDataP->recvRef;
+ DWORD read = opDataP->data[0].size;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_done(%T) {%d} -> entry with"
+ "\r\n recvRef: %T"
+ "\r\n flags: 0x%X"
+ "\r\n", sockRef, descP->sock, recvRef, flags) );
+
+ (void) flags;
+
+ encode_msg(opEnv, descP, read,
+ &opDataP->msg,
+ opDataP->data,
+ &opDataP->ctrl,
+ &eMsg);
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ if (read > descP->readPkgMax)
+ descP->readPkgMax = read;
+
+ res = esock_make_ok2(opEnv, eMsg);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_done(%T) {%d} -> done\r\n",
+ sockRef, descP->sock) );
+
+ return res;
+}
+
+
+
+/* *** esaio_completion_recvmsg_partial ***
+ *
+ * A partial read, that is only part of the buffer was used.
+ *
+ */
+static
+ERL_NIF_TERM esaio_completion_recvmsg_partial(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ESAIOOpDataRecvMsg* opDataP,
+ ESockRequestor* reqP,
+ DWORD read,
+ DWORD flags)
+{
+ ERL_NIF_TERM res, eMsg;
+ ERL_NIF_TERM sockRef = opDataP->sockRef;
+ ERL_NIF_TERM recvRef = opDataP->recvRef;
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_partial(%T) {%d} -> entry with"
+ "\r\n recvRef: %T"
+ "\r\n read: %ld"
+ "\r\n flags: 0x%X"
+ "\r\n", sockRef, descP->sock, recvRef, (long) read, flags) );
+
+ (void) flags;
+
+ /* This function hansles splitting the binaries */
+ encode_msg(opEnv, descP, read,
+ &opDataP->msg,
+ opDataP->data,
+ &opDataP->ctrl,
+ &eMsg);
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_pkg, &descP->readPkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_read_byte, &descP->readByteCnt, read);
+
+ if (read > descP->readPkgMax)
+ descP->readPkgMax = read;
+
+ res = esock_make_ok2(opEnv, eMsg);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_partial(%T) {%d} -> done\r\n",
+ sockRef, descP->sock) );
+
+ return res;
+}
+
+
+
+/* *** esaio_completion_recvmsg_fail ***
+ * Unknown operation failure.
+ */
+static
+void esaio_completion_recvmsg_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error,
+ BOOLEAN_T inform)
+{
+ esaio_completion_fail(env, descP, "recvmsg", error, inform);
+}
+
+
+
+/* *** esaio_completion_get_ovl_result_fail ***
+ * This function is called when the function 'WSAGetOverlappedResult' fails.
+ * It generates a result (returns) in the form of:
+ *
+ * {error, {get_overlapped_result, atom()}}
+ */
+static
+ERL_NIF_TERM esaio_completion_get_ovl_result_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int error)
+{
+ ERL_NIF_TERM eerrno = ENO2T(env, error);
+ ERL_NIF_TERM reason = MKT2(env, esock_atom_get_overlapped_result, eerrno);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_get_ovl_result_fail{%d} -> entry with"
+ "\r\n Errno: %d (%T)"
+ "\r\n", descP->sock, error, eerrno) );
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.genErrs, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+ return esock_make_error(env, reason);
+}
+
+
+
+
+/* === Unknown command 'stuff' === */
+
+/* *** esaio_completion_unknown ***
+ * What shall we actually do here?
+ * Increment counters (bytes, number of unknown packages)?
+ * Send a messge with this info to "someone"?
+ * Write a (error) message to stdout/stderr?
+ */
+static
+BOOLEAN_T esaio_completion_unknown(ESAIOThreadData* dataP,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ DWORD numBytes,
+ int error)
+{
+ (void) dataP;
+ (void) descP;
+ (void) ovl;
+ (void) numBytes;
+ (void) error;
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.unknownCmds, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+ return FALSE;
+}
+
+
+
+/* *** esaio_completion_fail ***
+ * Unknown operation failure (not 'unknown operation' failure,
+ * but an unknown 'operation failure').
+ */
+static
+void esaio_completion_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const char* opStr,
+ int error,
+ BOOLEAN_T inform)
+{
+ if (inform)
+ esock_warning_msg("[WIN-ESAIO] Unknown (%s) operation failure: "
+ "\r\n Descriptor: %d"
+ "\r\n Error: %T"
+ "\r\n",
+ opStr, descP->sock, ENO2T(env, error));
+
+ MLOCK(ctrl.cntMtx);
+
+ esock_cnt_inc(&ctrl.genErrs, 1);
+
+ MUNLOCK(ctrl.cntMtx);
+
+}
+
+
+
+static
+void esaio_completion_inc(ESAIOThreadData* dataP)
+{
+ if (dataP->cnt == ESAIO_THREAD_CNT_MAX) {
+ dataP->cnt = 0;
+ } else {
+ dataP->cnt++;
+ }
+}
+
+
+
+/* ====================================================================
+ *
+ * NIF (I/O backend) Resource callback functions: dtor, stop and down
+ *
+ * ====================================================================
+ */
+
+extern
+void esaio_dtor(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ ERL_NIF_TERM sockRef;
+
+ SGDBG( ("WIN-ESAIO", "esaio_dtor -> entry\r\n") );
+
+ /*
+ ESOCK_PRINTF("esaio_dtor -> entry when"
+ "\r\n is selected: %s"
+ "\r\n read state: 0x%X"
+ "\r\n is read-closed: %s"
+ "\r\n write state: 0x%X"
+ "\r\n is write-closed: %s"
+ "\r\n sock: %d"
+ "\r\n",
+ B2S(IS_SELECTED(descP)),
+ descP->readState,
+ B2S(IS_CLOSED(descP->readState)),
+ descP->writeState,
+ B2S(IS_CLOSED(descP->writeState)),
+ descP->sock);
+ */
+
+ if (IS_SELECTED(descP)) {
+ /* We have used the socket in the "I/O Completion Port" machinery,
+ * so we must have closed it properly to get here
+ */
+ if (! IS_CLOSED(descP->readState) )
+ esock_warning_msg("Socket Read State not CLOSED (0x%X) "
+ "at dtor\r\n", descP->readState);
+
+ if (! IS_CLOSED(descP->writeState) )
+ esock_warning_msg("Socket Write State not CLOSED (0x%X) "
+ "at dtor\r\n", descP->writeState);
+
+ if ( descP->sock != INVALID_SOCKET )
+ esock_warning_msg("Socket %d still valid\r\n", descP->sock);
+
+ ESOCK_ASSERT( IS_CLOSED(descP->readState) );
+ ESOCK_ASSERT( IS_CLOSED(descP->writeState) );
+ ESOCK_ASSERT( descP->sock == INVALID_SOCKET );
+
+ } else {
+ /* The socket is only opened, should be safe to close nonblocking */
+ (void) sock_close(descP->sock);
+ descP->sock = INVALID_SOCKET;
+ }
+
+ SGDBG( ("WIN-ESAIO", "esaio_dtor -> set state and pattern\r\n") );
+ descP->readState |= (ESOCK_STATE_DTOR | ESOCK_STATE_CLOSED);
+ descP->writeState |= (ESOCK_STATE_DTOR | ESOCK_STATE_CLOSED);
+ descP->pattern = (ESOCK_DESC_PATTERN_DTOR | ESOCK_STATE_CLOSED);
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_dtor -> try free readers request queue\r\n") );
+ esock_free_request_queue(&descP->readersQ);
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_dtor -> try free writers request queue\r\n") );
+ esock_free_request_queue(&descP->writersQ);
+
+ SGDBG( ("WIN-ESAIO",
+ "esaio_dtor -> try free acceptors request queue\r\n") );
+ esock_free_request_queue(&descP->acceptorsQ);
+
+ esock_free_env("esaio_dtor close env", descP->closeEnv);
+ descP->closeEnv = NULL;
+
+ esock_free_env("esaio_dtor meta env", descP->meta.env);
+ descP->meta.env = NULL;
+
+ SGDBG( ("WIN-ESAIO", "esaio_dtor -> done\r\n") );
+}
+
+
+
+extern
+void esaio_stop(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_stop(%d) -> entry\r\n", descP->sock) );
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ *
+ * Inform waiting Closer, or close socket
+ *
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+ if (! IS_PID_UNDEF(&descP->closerPid)) {
+ /* We have a waiting closer process after nif_close()
+ * - send message to trigger nif_finalize_close()
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_stop(%d) -> send close msg to %T\r\n",
+ descP->sock, MKPID(env, &descP->closerPid)) );
+
+ esock_send_close_msg(env, descP, &descP->closerPid);
+ /* Message send frees closeEnv */
+ descP->closeEnv = NULL;
+ descP->closeRef = esock_atom_undefined;
+
+ } else {
+ int err;
+
+ /* We do not have a closer process
+ * - have to do an unclean (non blocking) close */
+
+ err = esock_close_socket(env, descP, FALSE);
+
+ switch (err) {
+ case NO_ERROR:
+ break;
+ case WSAENOTSOCK:
+ if (descP->sock != INVALID_SOCKET)
+ esock_warning_msg("[WIN-ESAIO] Attempt to close an "
+ "already closed socket"
+ "\r\n(without a closer process): "
+ "\r\n Controlling Process: %T"
+ "\r\n socket fd: %d"
+ "\r\n",
+ descP->ctrlPid, descP->sock);
+ break;
+
+ default:
+ esock_warning_msg("[WIN-ESAIO] Failed closing socket without "
+ "closer process: "
+ "\r\n Controlling Process: %T"
+ "\r\n socket fd: %d"
+ "\r\n Errno: %T"
+ "\r\n",
+ descP->ctrlPid, descP->sock, ENO2T(env, err));
+ break;
+ }
+
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_stop(%d) -> done\r\n", descP->sock) );
+
+}
+
+
+
+
+/* A 'down' has occured.
+ * Check the possible processes we monitor in turn:
+ * closer, controlling process (owner), connector, reader, acceptor and writer.
+ *
+ */
+extern
+void esaio_down(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP)
+{
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down {%d} -> entry with:"
+ "\r\n Pid: %T"
+ "\r\n Mon: %T"
+ "\r\n", descP->sock, MKPID(env, pidP), MON2T(env, monP)) );
+
+ if (COMPARE_PIDS(&descP->closerPid, pidP) == 0) {
+
+ /* The closer process went down
+ * - it will not call nif_finalize_close
+ */
+
+ enif_set_pid_undefined(&descP->closerPid);
+
+ if (MON_EQ(&descP->closerMon, monP)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down {%d} -> closer process exit\r\n",
+ descP->sock) );
+
+ MON_INIT(&descP->closerMon);
+
+ } else {
+ // The owner is the closer so we used its monitor
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down {%d} -> closer controlling process exit\r\n",
+ descP->sock) );
+
+ ESOCK_ASSERT( MON_EQ(&descP->ctrlMon, monP) );
+ MON_INIT(&descP->ctrlMon);
+ enif_set_pid_undefined(&descP->ctrlPid);
+
+ }
+
+ /* Since the closer went down there was one,
+ * hence esock_close() must have run or scheduled esock_stop(),
+ * or the socket has never been "selected" upon.
+ */
+
+ if (descP->closeEnv == NULL) {
+ int err;
+
+ /* Since there is no closeEnv,
+ * esock_close() did not schedule esock_stop()
+ * and is about to call esock_finalize_close() but died,
+ * or esock_stop() has run, sent close_msg to the closer
+ * and cleared ->closeEnv but the closer died
+ * - we have to do an unclean (non blocking) socket close here
+ */
+
+ err = esock_close_socket(env, descP, FALSE);
+ if (err != 0)
+ esock_warning_msg("[WIN-ESAIO] "
+ "Failed closing socket for terminating "
+ "closer process: "
+ "\r\n Closer Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Errno: %d (%T)"
+ "\r\n",
+ MKPID(env, pidP), descP->sock,
+ err, ENO2T(env, err));
+ } else {
+ /* Since there is a closeEnv esock_stop() has not run yet
+ * - when it finds that there is no closer process
+ * it will close the socket and ignore the close_msg
+ *
+ * The 'stop' callback function will never be triggered on
+ * Windows...It may be explicitly called...
+ */
+ esock_clear_env("esaio_down - close-env", descP->closeEnv);
+ esock_free_env("esaio_down - close-env", descP->closeEnv);
+ descP->closeEnv = NULL;
+ descP->closeRef = esock_atom_undefined;
+ }
+
+ } else if (MON_EQ(&descP->ctrlMon, monP)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down {%d} -> controller process exit\r\n",
+ descP->sock) );
+
+ MON_INIT(&descP->ctrlMon);
+ /* The owner went down */
+ enif_set_pid_undefined(&descP->ctrlPid);
+
+ if (IS_OPEN(descP->readState)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down {%d} -> OPEN => initiate close\r\n",
+ descP->sock) );
+
+ esaio_down_ctrl(env, descP, pidP);
+
+ descP->readState |= ESOCK_STATE_CLOSING;
+ descP->writeState |= ESOCK_STATE_CLOSING;
+
+ } else {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down {%d} -> already closed or closing\r\n",
+ descP->sock) );
+
+ }
+
+ } else if (descP->connectorP != NULL &&
+ MON_EQ(&descP->connector.mon, monP)) {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down {%d} -> connector process exit\r\n",
+ descP->sock) );
+
+ MON_INIT(&descP->connector.mon);
+
+ /* connectorP is only set during connection.
+ * Forget all about the ongoing connection.
+ * We might end up connected, but the process that initiated
+ * the connection has died and will never know
+ */
+
+ esock_requestor_release("esaio_down->connector",
+ env, descP, &descP->connector);
+ descP->connectorP = NULL;
+ descP->writeState &= ~ESOCK_STATE_CONNECTING;
+
+ } else {
+ ERL_NIF_TERM sockRef = enif_make_resource(env, descP);
+
+ /* check all operation queue(s): acceptor, writer and reader.
+ *
+ * Is it really any point in doing this if the socket is closed?
+ *
+ */
+
+ if (IS_CLOSED(descP->readState)) {
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down(%T) {%d} -> stray down: %T\r\n",
+ sockRef, descP->sock, pidP) );
+ } else {
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down(%T) {%d} -> "
+ "other process - check readers, writers and acceptors\r\n",
+ sockRef, descP->sock) );
+
+ if (descP->readersQ.first != NULL)
+ esaio_down_reader(env, descP, sockRef, pidP, monP);
+ if (descP->acceptorsQ.first != NULL)
+ esaio_down_acceptor(env, descP, sockRef, pidP, monP);
+ if (descP->writersQ.first != NULL)
+ esaio_down_writer(env, descP, sockRef, pidP, monP);
+ }
+ }
+
+ SSDBG( descP, ("WIN-ESAIO", "esaio_down {%d} -> done\r\n", descP->sock) );
+
+}
+
+
+
+/* *** esaio_down_ctrl ***
+ *
+ * Stop after a downed controller (controlling process = owner process)
+ *
+ * This is 'extern' because its currently called from prim_socket_nif
+ * (esock_setopt_otp_ctrl_proc).
+ */
+extern
+void esaio_down_ctrl(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ const ErlNifPid* pidP)
+{
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_down_ctrl {%d} -> entry with"
+ "\r\n Pid: %T"
+ "\r\n", descP->sock, MKPID(env, pidP)) );
+
+ if (do_stop(env, descP)) {
+ /* esock_stop() is scheduled
+ * - it has to close the socket
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_down_ctrl {%d} -> stop was scheduled\r\n",
+ descP->sock) );
+ } else {
+ int err;
+
+ /* Socket has no *active* requests in the I/O Completion Ports machinery
+ * so esock_stop() will not be called
+ * - we have to do an unclean (non blocking) socket close here
+ */
+
+ err = esock_close_socket(env, descP, FALSE);
+ if (err != 0)
+ esock_warning_msg("[WIN-ESAIO] "
+ "Failed closing socket for terminating "
+ "owner process: "
+ "\r\n Owner Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Errno: %d (%T)"
+ "\r\n",
+ MKPID(env, pidP), descP->sock,
+ err, ENO2T(env, err));
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "esaio_down_ctrl {%d} -> done\r\n", descP->sock) );
+
+}
+
+
+
+/* *** esaio_down_acceptor ***
+ *
+ * Check and then handle a downed acceptor process.
+ *
+ */
+static
+void esaio_down_acceptor(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP)
+{
+
+ /* Maybe unqueue one of the waiting acceptors */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down_acceptor(%T) {%d} -> "
+ "maybe unqueue a waiting acceptor\r\n",
+ sockRef, descP->sock) );
+
+ esock_acceptor_unqueue(env, descP, NULL, pidP);
+
+}
+
+
+/* *** esaio_down_writer ***
+ *
+ * Check and then handle a downed writer process.
+ *
+ */
+
+static
+void esaio_down_writer(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP)
+{
+
+ /* Maybe unqueue one of the waiting writer(s) */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down_writer(%T) {%d} -> maybe unqueue a waiting writer\r\n",
+ sockRef, descP->sock) );
+
+ esock_writer_unqueue(env, descP, NULL, pidP);
+
+}
+
+
+/* *** esaio_down_reader ***
+ *
+ * Check and then handle a downed reader process.
+ *
+ */
+
+static
+void esaio_down_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pidP,
+ const ErlNifMonitor* monP)
+{
+ /* Maybe unqueue one of the waiting reader(s) */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_down_reader(%T) {%d} -> maybe unqueue a waiting reader\r\n",
+ sockRef, descP->sock) );
+
+ esock_reader_unqueue(env, descP, NULL, pidP);
+
+}
+
+
+/* ==================================================================== *
+ * *
+ * Send Utility functions *
+ * *
+ * ==================================================================== *
+ */
+
+/* *** send_check_result ***
+ *
+ * Check the result of a socket send (WSASend, WSASendTo and WSASendMsg) call.
+ *
+ */
+static
+ERL_NIF_TERM send_check_result(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ int send_result,
+ ssize_t dataSize,
+ BOOLEAN_T dataInTail,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ BOOLEAN_T* cleanup)
+{
+ ERL_NIF_TERM res;
+ BOOLEAN_T send_error;
+ int err;
+
+ if (send_result == 0) {
+
+ /* Send success already!
+ * So, no need to store the data (request, in the "queue").
+ * Note that the completion threads will use the
+ * precense or absence of this request 'record' to inform
+ * its actions.
+ */
+
+ *cleanup = FALSE;
+
+ res = send_check_ok(env, descP, dataSize, sockRef);
+
+ } else {
+
+ /* send returned error, check which */
+
+ int save_errno = sock_errno();
+
+ /* There are basically two kinds of errors:
+ * 1) Pending:
+ * An overlapped operation was successfully initiated.
+ * Completion will be "indicated" at a later time.
+ * 2) An actual error
+ */
+
+ if (save_errno == WSA_IO_PENDING) {
+
+ /* We need to store the data in the queue! */
+
+ *cleanup = FALSE;
+
+ res = send_check_pending(env, descP, opP, caller, sockRef, sendRef);
+
+ } else {
+
+ *cleanup = TRUE;
+
+ res = send_check_fail(env, descP, save_errno, sockRef);
+
+ }
+ }
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "send_check_result(%T) {%d} -> done:"
+ "\r\n res: %T"
+ "\r\n", sockRef, descP->sock, res) );
+
+ return res;
+}
+
+
+
+/* *** send_check_ok ***
+ *
+ * Processing done upon successful send.
+ */
+static
+ERL_NIF_TERM send_check_ok(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ DWORD written,
+ ERL_NIF_TERM sockRef)
+{
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_pkg, &descP->writePkgCnt, 1);
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_byte, &descP->writeByteCnt, written);
+
+ /* We can *never* have a partial successs:
+ * Either the entire buffer is sent, or the op is scheduled or we fail.
+ * But since we have a field (writePkgMaxCnt) in the descriptor
+ * we might as well use it.
+ */
+
+ descP->writePkgMaxCnt = written;
+ if (descP->writePkgMaxCnt > descP->writePkgMax)
+ descP->writePkgMax = descP->writePkgMaxCnt;
+ descP->writePkgMaxCnt = 0;
+
+ SSDBG( descP,
+ ("WIN-ESAIO", "send_check_ok(%T) {%d} -> %ld written - done\r\n",
+ sockRef, descP->sock, written) );
+
+ return esock_atom_ok;
+}
+
+
+/* *** send_check_pending ***
+ *
+ * The send operation was scheduled, that is, its now in the handls
+ * of the I/O Completion Port framework.
+ */
+static
+ERL_NIF_TERM send_check_pending(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESAIOOperation* opP,
+ ErlNifPid caller,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef)
+{
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "send_check_pending(%T, %d) -> entry with"
+ "\r\n sendRef: %T"
+ "\r\n", sockRef, descP->sock, sendRef) );
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_waits, &descP->writeWaits, 1);
+
+ descP->writeState |= ESOCK_STATE_SELECTED;
+
+ esock_writer_push(env, descP, caller, sendRef, opP);
+
+ return esock_atom_completion;
+
+}
+
+
+
+/* *** send_check_fail ***
+ *
+ * Processing done upon failed send.
+ * An actual failure.
+ */
+static
+ERL_NIF_TERM send_check_fail(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ int saveErrno,
+ ERL_NIF_TERM sockRef)
+{
+ ERL_NIF_TERM reason;
+
+ ESOCK_CNT_INC(env, descP, sockRef,
+ esock_atom_write_fails, &descP->writeFails, 1);
+
+ reason = ENO2T(env, saveErrno);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "send_check_fail(%T, %d) -> error: "
+ "\r\n %d (%T)\r\n",
+ sockRef, descP->sock, saveErrno, reason) );
+
+ return esock_make_error(env, reason);
+}
+
+
+
+/* ==================================================================== *
+ * *
+ * Utility functions *
+ * *
+ * ==================================================================== *
+ */
+
+/* *** get_send_ovl_result ***
+ *
+ * Used for all send 'overlapped' operations; send, sendto and sendmsg.
+ */
+static
+BOOL get_send_ovl_result(SOCKET sock,
+ OVERLAPPED* ovl,
+ DWORD* written)
+{
+ DWORD flags = 0;
+ BOOL result = get_ovl_result(sock, ovl, written, &flags);
+
+ (void) flags;
+
+ return result;
+}
+
+
+/* *** get_recv_ovl_result ***
+ *
+ * Used for recv *and* recvfrom 'overlapped' operations.
+ */
+static
+BOOL get_recv_ovl_result(SOCKET sock,
+ OVERLAPPED* ovl,
+ DWORD* read,
+ DWORD* flags)
+{
+ return get_ovl_result(sock, ovl, read, flags);
+}
+
+
+/* *** get_recvmsg_ovl_result ***
+ *
+ * Used for all recvmsg 'overlapped' operations.
+ */
+static
+BOOL get_recvmsg_ovl_result(SOCKET sock,
+ OVERLAPPED* ovl,
+ DWORD* read)
+{
+ DWORD flags = 0;
+ BOOL result = get_ovl_result(sock, ovl, read, &flags);
+
+ (void) flags;
+
+ return result;
+}
+
+
+/* *** get_ovl_result ***
+ *
+ * Simple wrapper function for WSAGetOverlappedResult.
+ */
+static
+BOOL get_ovl_result(SOCKET sock,
+ OVERLAPPED* ovl,
+ DWORD* transfer,
+ DWORD* flags)
+{
+ return WSAGetOverlappedResult(sock, ovl, transfer, FALSE, flags);
+}
+
+
+
+/* *** esaio_add_socket ***
+ *
+ * Add socket to I/O completion port.
+ */
+static
+int esaio_add_socket(ESockDescriptor* descP)
+{
+ int res;
+ HANDLE tmp = CreateIoCompletionPort((HANDLE) descP->sock, ctrl.cport,
+ (ULONG_PTR) descP, 0);
+
+ if (tmp != NULL) {
+ res = ESAIO_OK;
+ } else {
+ res = sock_errno();
+ }
+
+ return res;
+}
+
+
+static
+void esaio_send_completion_msg(ErlNifEnv* sendEnv,
+ ESockDescriptor* descP,
+ ErlNifPid* pid,
+ ErlNifEnv* msgEnv,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM completionInfo)
+{
+ ERL_NIF_TERM msg = mk_completion_msg(msgEnv, sockRef, completionInfo);
+
+ /* This can only fail if:
+ * - The recipient is dead.
+ * Our monitor should clear this up ... eventually.
+ * Possible race?
+ * - We (the sender) are "dead" (which we are clearly not)
+ */
+ if (! esock_send_msg(sendEnv, pid, msg, NULL)) {
+
+ /*
+ ESOCK_DBG_PRINTF( TRUE, ("IN-ESAIO",
+ "esaio_send_completion_msg(%T) {%d} failed ->"
+ "\r\n pid: %T"
+ "\r\n",
+ sockRef, descP->sock, MKPID(sendEnv, pid)) );
+ */
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_send_completion_msg(%T) {%d} failed ->"
+ "\r\n pid: %T"
+ "\r\n",
+ sockRef, descP->sock, MKPID(sendEnv, pid)) );
+ }
+}
+
+
+/* *** mk_completion_msg ***
+ *
+ * Construct a completion (socket) message. It has the form:
+ *
+ * {'$socket', Socket, completion, CompletionInfo}
+ *
+ */
+
+static
+ERL_NIF_TERM mk_completion_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM info)
+{
+ return esock_mk_socket_msg(env, sockRef,
+ esock_atom_completion, info);
+}
+
+
+#endif
diff --git a/erts/emulator/sys/common/erl_check_io.c b/erts/emulator/sys/common/erl_check_io.c
index 39b6517bcf..f821cca9c8 100644
--- a/erts/emulator/sys/common/erl_check_io.c
+++ b/erts/emulator/sys/common/erl_check_io.c
@@ -1922,6 +1922,7 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only
/* fallthrough */
case ERTS_EV_TYPE_NONE: /* Deselected ... */
case_ERTS_EV_TYPE_NONE:
+ state->flags &= ~ERTS_EV_FLAG_FALLBACK;
ASSERT(!state->events && !state->active_events && !state->flags);
check_fd_cleanup(state, &free_select, &free_nif);
break;
diff --git a/erts/emulator/sys/unix/erl_child_setup.c b/erts/emulator/sys/unix/erl_child_setup.c
index 0a2ac9abf7..ae269292e5 100644
--- a/erts/emulator/sys/unix/erl_child_setup.c
+++ b/erts/emulator/sys/unix/erl_child_setup.c
@@ -58,6 +58,7 @@
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
+#include <termios.h>
#define WANT_NONBLOCKING
@@ -432,6 +433,17 @@ static int system_properties_fd(void)
}
#endif /* __ANDROID__ */
+/*
+ If beam is terminated using kill -9 or Ctrl-C when +B is set it may not
+ cleanup the terminal properly. So to clean it up we save the initial state in
+ erl_child_setup and then reset the terminal if we detect that beam terminated.
+
+ Not all shells and OSs have this issue, but we do it on all unixes anyway as
+ it is hard for us to know where the bug exists or not and there is no hard in
+ doing it.
+ */
+static struct termios initial_tty_mode;
+
int
main(int argc, char *argv[])
{
@@ -447,6 +459,10 @@ main(int argc, char *argv[])
ABORT("Invalid arguments to child_setup");
}
+ if (isatty(0)) {
+ tcgetattr(0,&initial_tty_mode);
+ }
+
/* We close all fds except the uds from beam.
All other fds from now on will have the
CLOEXEC flags set on them. This means that we
@@ -541,12 +557,18 @@ main(int argc, char *argv[])
pipes, 3, MSG_DONTWAIT)) < 0) {
if (errno == EINTR)
continue;
+ if (isatty(0)) {
+ tcsetattr(0,TCSANOW,&initial_tty_mode);
+ }
DEBUG_PRINT("erl_child_setup failed to read from uds: %d, %d", res, errno);
_exit(0);
}
if (res == 0) {
DEBUG_PRINT("uds was closed!");
+ if (isatty(0)) {
+ tcsetattr(0,TCSANOW,&initial_tty_mode);
+ }
_exit(0);
}
/* Since we use unix domain sockets and send the entire data in
diff --git a/erts/emulator/sys/unix/erl_unix_sys.h b/erts/emulator/sys/unix/erl_unix_sys.h
index c93c0dc5cc..3614aecd17 100644
--- a/erts/emulator/sys/unix/erl_unix_sys.h
+++ b/erts/emulator/sys/unix/erl_unix_sys.h
@@ -281,7 +281,7 @@ ERTS_GLB_INLINE ErtsSysPerfCounter erts_sys_perf_counter(void);
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
ERTS_GLB_FORCE_INLINE ErtsSysPerfCounter
-erts_sys_perf_counter()
+erts_sys_perf_counter(void)
{
return (*erts_sys_time_data__.r.o.perf_counter)();
}
diff --git a/erts/emulator/sys/unix/sys.c b/erts/emulator/sys/unix/sys.c
index 7ff8425d52..210d7a5543 100644
--- a/erts/emulator/sys/unix/sys.c
+++ b/erts/emulator/sys/unix/sys.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -814,6 +814,10 @@ void sys_get_pid(char *buffer, size_t buffer_size){
erts_snprintf(buffer, buffer_size, "%lu",(unsigned long) p);
}
+int sys_get_hostname(char *buf, size_t size)
+{
+ return gethostname(buf, size);
+}
void sys_init_io(void) { }
void erts_sys_alloc_init(void) { }
@@ -1057,7 +1061,7 @@ init_smp_sig_notify(void)
{
erts_thr_opts_t thr_opts = ERTS_THR_OPTS_DEFAULT_INITER;
thr_opts.detached = 1;
- thr_opts.name = "sys_sig_dispatcher";
+ thr_opts.name = "erts_ssig_disp";
if (pipe(sig_notify_fds) < 0) {
erts_exit(ERTS_ABORT_EXIT,
@@ -1107,7 +1111,7 @@ erts_sys_main_thread(void)
#else
/* Become signal receiver thread... */
#ifdef ERTS_ENABLE_LOCK_CHECK
- erts_lc_set_thread_name("signal_receiver");
+ erts_lc_set_thread_name("main");
#endif
#endif
smp_sig_notify(0); /* Notify initialized */
diff --git a/erts/emulator/sys/unix/sys_env.c b/erts/emulator/sys/unix/sys_env.c
index 4d8301f985..eb98e7b635 100644
--- a/erts/emulator/sys/unix/sys_env.c
+++ b/erts/emulator/sys/unix/sys_env.c
@@ -16,7 +16,7 @@ extern char **environ;
static void import_initial_env(void);
-void erts_sys_env_init() {
+void erts_sys_env_init(void) {
erts_rwmtx_init(&sysenv_rwmtx, "environ", NIL,
ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
@@ -24,21 +24,21 @@ void erts_sys_env_init() {
import_initial_env();
}
-const erts_osenv_t *erts_sys_rlock_global_osenv() {
+const erts_osenv_t *erts_sys_rlock_global_osenv(void) {
erts_rwmtx_rlock(&sysenv_rwmtx);
return &sysenv_global_env;
}
-erts_osenv_t *erts_sys_rwlock_global_osenv() {
+erts_osenv_t *erts_sys_rwlock_global_osenv(void) {
erts_rwmtx_rwlock(&sysenv_rwmtx);
return &sysenv_global_env;
}
-void erts_sys_rwunlock_global_osenv() {
+void erts_sys_rwunlock_global_osenv(void) {
erts_rwmtx_rwunlock(&sysenv_rwmtx);
}
-void erts_sys_runlock_global_osenv() {
+void erts_sys_runlock_global_osenv(void) {
erts_rwmtx_runlock(&sysenv_rwmtx);
}
diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c
index 2a37c075f8..4d4b2b7fb8 100644
--- a/erts/emulator/sys/win32/sys.c
+++ b/erts/emulator/sys/win32/sys.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2022. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,10 +32,13 @@
#include "erl_sys_driver.h"
#include "global.h"
#include "erl_threads.h"
-#include "../../drivers/win32/win_con.h"
#include "erl_cpu_topology.h"
#include <malloc.h>
+#if defined(__WIN32__) && !defined(WINDOWS_H_INCLUDES_WINSOCK2_H)
+#include <winsock2.h>
+#endif
+
void erts_sys_init_float(void);
void erl_start(int, char**);
@@ -125,8 +128,6 @@ BOOL WINAPI ctrl_handler(DWORD dwCtrlType);
static int max_files = 1024;
static BOOL use_named_pipes;
-static BOOL win_console = FALSE;
-
static OSVERSIONINFO int_os_version; /* Version information for Win32. */
@@ -205,10 +206,6 @@ erts_sys_misc_mem_sz(void)
*/
void sys_tty_reset(int exit_code)
{
- if (exit_code == ERTS_ERROR_EXIT)
- ConWaitForExit();
- else
- ConNormalExit();
}
void erl_sys_args(int* argc, char** argv)
@@ -304,25 +301,16 @@ int erts_set_signal(Eterm signal, Eterm type) {
return 0;
}
+static DWORD dwOriginalOutMode = 0;
+static DWORD dwOriginalInMode = 0;
+
static void
init_console(void)
{
- char* mode = erts_read_env("ERL_CONSOLE_MODE");
-
- if (!mode || strcmp(mode, "window") == 0) {
- win_console = TRUE;
- ConInit();
- /*nohup = 0;*/
- } else if (strncmp(mode, "tty:", 4) == 0) {
- if (mode[5] == 'c') {
- setvbuf(stdout, NULL, _IONBF, 0);
- }
- if (mode[6] == 'c') {
- setvbuf(stderr, NULL, _IONBF, 0);
- }
- }
-
- erts_free_read_env(mode);
+ GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &dwOriginalOutMode);
+ GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &dwOriginalInMode);
+ setvbuf(stdout, NULL, _IONBF, 0);
+ setvbuf(stderr, NULL, _IONBF, 0);
}
int sys_max_files(void)
@@ -2196,7 +2184,6 @@ fd_start(ErlDrvPort port_num, char* name, SysDriverOpts* opts)
return ERL_DRV_ERROR_GENERAL;
}
- fd_driver_input = &(dp->in);
dp->in.flags = DF_XLAT_CR;
if (is_std_error) {
dp->out.flags |= DF_DROP_IF_INVH; /* Just drop messages if stderror
@@ -2204,6 +2191,7 @@ fd_start(ErlDrvPort port_num, char* name, SysDriverOpts* opts)
}
if ( in == 0 && out == 1) {
+ fd_driver_input = &(dp->in);
save_01_port = dp;
} else if (in == 2 && out == 2) {
save_22_port = dp;
@@ -2783,6 +2771,11 @@ void sys_get_pid(char *buffer, size_t buffer_size){
erts_snprintf(buffer, buffer_size, "%lu",(unsigned long) p);
}
+int sys_get_hostname(char *buf, size_t size)
+{
+ return gethostname(buf, size);
+}
+
void
sys_init_io(void)
{
@@ -2947,10 +2940,6 @@ sys_get_key(int fd)
{
ASSERT(fd == 0);
- if (win_console) {
- return ConGetKey();
- }
-
/*
* Black magic follows. (Code stolen from get_overlapped_result())
*/
@@ -2976,6 +2965,32 @@ sys_get_key(int fd)
}
}
}
+ else {
+ char c[64];
+ DWORD dwBytesRead, dwCurrentOutMode = 0, dwCurrentInMode = 0;
+
+ /* Get current console information */
+ GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &dwCurrentOutMode);
+ GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &dwCurrentInMode);
+
+ /* Set the a "oldstyle" terminal with line input that we can use ReadFile on */
+ SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), dwOriginalOutMode);
+ SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),
+ ENABLE_PROCESSED_INPUT |
+ ENABLE_LINE_INPUT |
+ ENABLE_ECHO_INPUT |
+ ENABLE_INSERT_MODE |
+ ENABLE_QUICK_EDIT_MODE |
+ ENABLE_AUTO_POSITION
+ );
+
+ if (ReadFile(GetStdHandle(STD_INPUT_HANDLE), &c, sizeof(c), &dwBytesRead, NULL) && dwBytesRead > 0) {
+ /* Restore original console information */
+ SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), dwCurrentOutMode);
+ SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), dwCurrentInMode);
+ return c[0];
+ }
+ }
return '*'; /* Error! */
}
diff --git a/erts/emulator/sys/win32/sys_interrupt.c b/erts/emulator/sys/win32/sys_interrupt.c
index cee269eed4..fc4f63d4bf 100644
--- a/erts/emulator/sys/win32/sys_interrupt.c
+++ b/erts/emulator/sys/win32/sys_interrupt.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1997-2018. All Rights Reserved.
+ * Copyright Ericsson AB 1997-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,11 +23,13 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
+
+#define ERTS_WANT_BREAK_HANDLING
+
#include "sys.h"
#include "erl_alloc.h"
#include "erl_thr_progress.h"
#include "erl_driver.h"
-#include "../../drivers/win32/win_con.h"
#if defined(__GNUC__)
# define WIN_SYS_INLINE __inline__
@@ -82,7 +84,6 @@ BOOL WINAPI ctrl_handler_ignore_break(DWORD dwCtrlType)
}
void erts_set_ignore_break(void) {
- ConSetCtrlHandler(ctrl_handler_ignore_break);
SetConsoleCtrlHandler(ctrl_handler_ignore_break, TRUE);
}
@@ -92,6 +93,9 @@ BOOL WINAPI ctrl_handler_replace_intr(DWORD dwCtrlType)
case CTRL_C_EVENT:
return FALSE;
case CTRL_BREAK_EVENT:
+ if (ERTS_BREAK_REQUESTED) {
+ erts_exit(ERTS_INTR_EXIT, "");
+ }
SetEvent(erts_sys_break_event);
break;
case CTRL_LOGOFF_EVENT:
@@ -110,7 +114,11 @@ BOOL WINAPI ctrl_handler_replace_intr(DWORD dwCtrlType)
/* Don't use ctrl-c for break handler but let it be
used by the shell instead (see user_drv.erl) */
void erts_replace_intr(void) {
- ConSetCtrlHandler(ctrl_handler_replace_intr);
+ HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+ DWORD dwOriginalInMode = 0;
+ if (GetConsoleMode(hIn, &dwOriginalInMode)) {
+ SetConsoleMode(hIn, dwOriginalInMode & ~ENABLE_PROCESSED_INPUT);
+ }
SetConsoleCtrlHandler(ctrl_handler_replace_intr, TRUE);
}
@@ -119,6 +127,9 @@ BOOL WINAPI ctrl_handler(DWORD dwCtrlType)
switch (dwCtrlType) {
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
+ if (ERTS_BREAK_REQUESTED) {
+ erts_exit(ERTS_INTR_EXIT, "");
+ }
SetEvent(erts_sys_break_event);
break;
case CTRL_LOGOFF_EVENT:
@@ -135,7 +146,6 @@ BOOL WINAPI ctrl_handler(DWORD dwCtrlType)
void init_break_handler()
{
- ConSetCtrlHandler(ctrl_handler);
SetConsoleCtrlHandler(ctrl_handler, TRUE);
}
diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile
index 33a80f2e53..21add610bb 100644
--- a/erts/emulator/test/Makefile
+++ b/erts/emulator/test/Makefile
@@ -123,6 +123,7 @@ MODULES= \
trace_meta_SUITE \
trace_call_count_SUITE \
trace_call_time_SUITE \
+ trace_call_memory_SUITE \
tracer_SUITE \
tracer_test \
scheduler_SUITE \
@@ -149,6 +150,16 @@ NO_OPT= bs_bincomp \
guard \
map
+R25= \
+ bs_bincomp \
+ bs_construct \
+ bs_match_bin \
+ bs_match_int \
+ bs_match_tail \
+ bs_match_misc \
+ bs_utf
+
+
NATIVE= hibernate
NO_OPT_MODULES= $(NO_OPT:%=%_no_opt_SUITE)
@@ -157,6 +168,9 @@ NO_OPT_ERL_FILES= $(NO_OPT_MODULES:%=%.erl)
NATIVE_MODULES= $(NATIVE:%=%_native_SUITE)
NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl)
+R25_MODULES= $(R25:%=%_r25_SUITE)
+R25_ERL_FILES= $(R25_MODULES:%=%.erl)
+
ERL_FILES= $(MODULES:%=%.erl)
TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
@@ -188,13 +202,17 @@ ERL_COMPILE_FLAGS := $(filter-out +deterministic,$($(ERL_COMPILE_FLAGS)))
# Targets
# ----------------------------------------------------
-make_emakefile: $(NO_OPT_ERL_FILES) $(NATIVE_ERL_FILES) $(KERNEL_ERL_FILES)
+make_emakefile: $(NO_OPT_ERL_FILES) $(NATIVE_ERL_FILES) \
+ $(KERNEL_ERL_FILES) $(R25_ERL_FILES)
$(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) +compressed -o$(EBIN) \
$(MODULES) $(KERNEL_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +no_copt +no_postopt +no_ssa_opt +no_bsm_opt \
$(ERL_COMPILE_FLAGS) -o$(EBIN) $(NO_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(NATIVE_MODULES) >> $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile +r25 \
+ $(ERL_COMPILE_FLAGS) -o$(EBIN) $(R25_MODULES) >> $(EMAKEFILE)
+
tests debug opt: make_emakefile
erl $(ERL_MAKE_FLAGS) -make
@@ -218,6 +236,9 @@ targets: $(TARGET_FILES)
%_native_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+%_r25_SUITE.erl: %_SUITE.erl
+ sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -232,6 +253,7 @@ release_tests_spec: make_emakefile
$(INSTALL_DATA) $(NO_OPT_ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(NATIVE_ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(KERNEL_ERL_FILES) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(R25_ERL_FILES) "$(RELSYSDIR)"
chmod -R u+w "$(RELSYSDIR)"
tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -)
diff --git a/erts/emulator/test/a_SUITE.erl b/erts/emulator/test/a_SUITE.erl
index 59b7839a87..0f749f629e 100644
--- a/erts/emulator/test/a_SUITE.erl
+++ b/erts/emulator/test/a_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -47,6 +47,18 @@ init_per_suite(Config) when is_list(Config) ->
%% allow other suites to use it...
inet_gethost_native:gethostbyname("localhost"),
+ %% Trigger usage of large pids and ports in 64-bit case...
+ case erlang:system_info(wordsize) of
+ 4 ->
+ ok;
+ 8 ->
+ erts_debug:set_internal_state(available_internal_state,true),
+ erts_debug:set_internal_state(next_pid, 1 bsl 32),
+ erts_debug:set_internal_state(next_port, 1 bsl 32),
+ erts_debug:set_internal_state(available_internal_state,false),
+ ok
+ end,
+
%% Start the timer server.
timer:start(),
diff --git a/erts/emulator/test/bif_SUITE.erl b/erts/emulator/test/bif_SUITE.erl
index 69e911e796..5f66122ee7 100644
--- a/erts/emulator/test/bif_SUITE.erl
+++ b/erts/emulator/test/bif_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -43,7 +43,8 @@
verify_middle_queue_save/1,
test_length/1,
fixed_apply_badarg/1,
- external_fun_apply3/1]).
+ external_fun_apply3/1,
+ node_1/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -61,7 +62,7 @@ all() ->
is_process_alive, is_process_alive_signal_from,
process_info_blast, os_env_case_sensitivity,
verify_middle_queue_save, test_length,fixed_apply_badarg,
- external_fun_apply3].
+ external_fun_apply3, node_1].
init_per_testcase(guard_bifs_in_erl_bif_types, Config) when is_list(Config) ->
skip_missing_erl_bif_types(Config);
@@ -356,6 +357,11 @@ auto_imports([], Errors) ->
extract_functions(M, Abstr) ->
[{{M,F,A},Body} || {function,_,F,A,Body} <- Abstr].
+check_stub({_,F,2}, _B) when F =:= min; F =:= max ->
+ %% In Erlang/OTP 26, min/2 and max/2 are guard BIFs. For backward
+ %% compatibility with code compiled with an earlier version, the
+ %% Erlang implementation of them is kept.
+ ok;
check_stub({_,F,A}, B) ->
try
[{clause,_,Args,[],Body}] = B,
@@ -724,33 +730,147 @@ fail_atom_to_binary(Term) ->
end.
-min_max(Config) when is_list(Config) ->
- a = erlang:min(id(a), a),
- a = erlang:min(id(a), b),
- a = erlang:min(id(b), a),
- b = erlang:min(id(b), b),
- a = erlang:max(id(a), a),
- b = erlang:max(id(a), b),
- b = erlang:max(id(b), a),
- b = erlang:max(id(b), b),
-
- 42.0 = erlang:min(42.0, 42),
- 42.0 = erlang:max(42.0, 42),
- %% And now (R14) they are also autoimported!
+min_max(Config) when is_list(Config) ->
+ Self = self(),
+ Port = hd(erlang:ports()),
+ Ref = make_ref(),
a = min(id(a), a),
a = min(id(a), b),
a = min(id(b), a),
b = min(id(b), b),
+ Ref = min(id(Self), id(Ref)),
+
+ -3 = min(id(5), -3),
+ -3 = min(-3, id(5)),
+ -3 = min(0, id(-3)),
+ -3 = min(id(-3), 0),
+ 0 = min(0, id(17)),
+
a = max(id(a), a),
b = max(id(a), b),
b = max(id(b), a),
b = max(id(b), b),
+ Self = max(id(Self), id(Ref)),
+
+ 5 = max(id(5), -3),
+ 5 = max(-3, id(5)),
+ 0 = max(0, id(-3)),
+ 0 = max(id(-3), 0),
+ 17 = max(0, id(17)),
+
+ %% Return the first argument when arguments are equal.
+ 42.0 = min(id(42.0), 42),
+ 42.0 = max(id(42.0), 42),
+
+ Min = id(min),
+ Max = id(max),
+
+ "abc" = erlang:Min("abc", "def"),
+ <<"def">> = erlang:Max(<<"abc">>, <<"def">>),
+
+ %% Make sure that the JIT doesn't do any unsafe optimizations.
+ {0, 0} = min_max_zero(0),
+ {-7, 0} = min_max_zero(-7),
+ {0, 555} = min_max_zero(555),
+ {0, 1 bsl 64} = min_max_zero(1 bsl 64),
+ {-1 bsl 64, 0} = min_max_zero(-1 bsl 64),
+
+ {-99, 23} = do_min_max(-99, 23),
+ {-10, 0} = do_min_max(0, -10),
+ {0, 77} = do_min_max(77, 0),
+ {1, 2} = do_min_max(1, 2),
+ {42, 99} = do_min_max(99, 42),
+ {100, 1 bsl 64} = do_min_max(100, 1 bsl 64),
+ {-1 bsl 64, 77} = do_min_max(77, -1 bsl 64),
+ {-1 bsl 64, 1 bsl 64} = do_min_max(1 bsl 64, -1 bsl 64),
+ {42.0, 43} = do_min_max(42.0, 43),
+ {42.0, 50.0} = do_min_max(42.0, 50.0),
+ {42.0, 42.0} = do_min_max(42.0, id(40.0 + 2.0)),
+ {{1,2}, {a,b}} = do_min_max({id(a), id(b)}, {id(1), id(2)}),
+ {{a,b}, [a,b]} = do_min_max({a,id(b)}, [a,id(b)]),
+ {{1.0,b}, {1.0,b}} = do_min_max({id(1.0), id(b)}, {id(1), id(b)}),
+ {{7,b}, {7,b}} = do_min_max({id(7), id(b)}, {id(7.0), id(b)}),
+
+ {42,Self} = do_min_max(42, Self),
+ {42,Self} = do_min_max(Self, 42),
+ {42,Port} = do_min_max(42, Port),
+ {42,Port} = do_min_max(Port, 42),
- 42.0 = min(42.0, 42),
- 42.0 = max(42.0, 42),
ok.
+min_max_zero(A0) ->
+ Result = {min(A0, 0), max(A0, 0)},
+ Result = {min(0, A0), max(0, A0)},
+ A = id(A0),
+ Result = {min(A, 0), max(A, 0)},
+ Result = {min(0, A), max(0, A)}.
+
+do_min_max(A0, B0) ->
+ Result = {min(A0, B0), max(A0, B0)},
+
+ A0 = min(id(A0), A0),
+ A0 = max(id(A0), A0),
+ B0 = min(id(B0), B0),
+ B0 = max(id(B0), B0),
+
+ A = id(A0),
+ B = id(B0),
+ Result = {min(A, B), max(A, B)},
+
+ if
+ is_integer(A), is_atom(node(B)) orelse is_integer(B) ->
+ _ = id(0),
+ Result = {min(A, B),max(A, B)};
+ is_atom(node(A)) orelse is_integer(A), is_integer(B) ->
+ _ = id(0),
+ Result = {min(A, B),max(A, B)};
+ true ->
+ ok
+ end,
+
+ if
+ is_integer(A), 0 =< A, A =< 1000, is_atom(node(B)) orelse is_integer(B) ->
+ _ = id(0),
+ Result = {min(A, B),max(A, B)};
+ is_atom(node(A)) orelse is_integer(A), is_integer(B), 0 =< B, B =< 1000 ->
+ _ = id(0),
+ Result = {min(A, B),max(A, B)};
+ true ->
+ ok
+ end,
+ Result = do_min_max_1(1, 2, 3, 4, 5, A, B).
+
+do_min_max_1(_, _, _, _, _, A, B) ->
+ if
+ is_integer(A), 0 =< A, A < 16#1_0000,
+ is_integer(B), 0 =< B, B < 16#1_0000 ->
+ Result = {min(A, B),max(A, B)},
+ Result = {min(B, A),max(B, A)},
+ _ = id(0),
+ Result = {min(A, B),max(A, B)},
+ Result = {min(B, A),max(B, A)};
+ is_integer(A), is_integer(B) ->
+ Result = {min(A, B),max(A, B)},
+ Result = {min(B, A),max(B, A)},
+ _ = id(0),
+ Result = {min(A, B),max(A, B)},
+ Result = {min(B, A),max(B, A)};
+ is_float(A), is_float(B) ->
+ Result = {min(A, B),max(A, B)},
+ Result = {min(B, A),max(B, A)},
+ _ = id(0),
+ Result = {min(A, B),max(A, B)},
+ Result = {min(B, A),max(B, A)};
+ is_number(A), is_number(B) ->
+ Result = {min(A, B),max(A, B)},
+ _ = id(0),
+ Result = {min(A, B),max(A, B)};
+ true ->
+ Result = {min(A, B),max(A, B)},
+ _ = id(0),
+ Result = {min(A, B),max(A, B)}
+ end.
erlang_halt(Config) when is_list(Config) ->
try erlang:halt(undefined) of
@@ -1445,6 +1565,73 @@ external_fun_apply3(_Config) ->
ok.
+node_1(_Config) ->
+ {ok, Peer, Node} = ?CT_PEER(),
+
+ local_node(self()),
+ LocalPort = lists:last(erlang:ports()),
+ local_node(LocalPort),
+ local_node(make_ref()),
+
+ external_node(erpc:call(Node, erlang, self, []), Node),
+ ExtPort = hd(erpc:call(Node, erlang, ports, [])),
+ external_node(ExtPort, Node),
+ external_node(erpc:call(Node, erlang, make_ref, []), Node),
+
+ node_error(a),
+ node_error(42),
+ node_error({a,b,c}),
+ node_error({tag,self()}),
+ node_error([self()]),
+ node_error(1 bsl 133),
+ node_error(#{}),
+ node_error(#{id(a) => b}),
+ node_error(<<"binary">>),
+
+ peer:stop(Peer),
+ ok.
+
+local_node(E) ->
+ test_node(E, node()).
+
+external_node(E, Node) ->
+ test_node(E, Node).
+
+test_node(E0, Node) ->
+ true = node(id(E0)) =:= Node,
+ E = id(E0),
+ if
+ node(E) =:= Node ->
+ ok
+ end,
+ test_node_2(id(E), Node).
+
+test_node_2(E, Node) when is_pid(E); is_port(E); is_reference(E) ->
+ true = node(E) =:= Node,
+ if
+ node(E) =:= Node ->
+ ok
+ end,
+ test_node_3(id(E), Node),
+ ok.
+
+test_node_3(E, Node) when is_pid(E) ->
+ true = node(E) =:= Node;
+test_node_3(E, Node) when is_port(E) ->
+ true = node(E) =:= Node;
+test_node_3(E, Node) when is_reference(E) ->
+ true = node(E) =:= Node.
+
+node_error(E0) ->
+ E = id(E0),
+ {'EXIT',{badarg,[{erlang,node,[E],_}|_]}} = catch node(E),
+ if
+ node(E) ->
+ ct:fail(should_fail);
+ true ->
+ ok
+ end.
+
%% helpers
busy_wait_go() ->
diff --git a/erts/emulator/test/binary_SUITE.erl b/erts/emulator/test/binary_SUITE.erl
index e6b178475d..9d69741b8b 100644
--- a/erts/emulator/test/binary_SUITE.erl
+++ b/erts/emulator/test/binary_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@
t_split_binary/1, bad_split/1,
terms/1, terms_float/1, float_middle_endian/1,
b2t_used_big/1, t2b_deterministic/1,
+ t2b_minor_version/1,
external_size/1, t_iolist_size/1,
t_iolist_size_huge_list/1,
t_iolist_size_huge_bad_arg_list/1,
@@ -76,7 +77,8 @@
error_after_yield/1, cmp_old_impl/1,
t2b_system_limit/1,
term_to_iovec/1,
- is_binary_test/1]).
+ is_binary_test/1,
+ local_ext/1]).
%% Internal exports.
-export([sleeper/0,trapping_loop/4]).
@@ -93,6 +95,7 @@ all() ->
t_iolist_size_huge_bad_arg_list,
{group, iolist_size_benchmarks},
b2t_used_big, t2b_deterministic,
+ t2b_minor_version,
bad_binary_to_term_2, safe_binary_to_term2,
bad_binary_to_term, bad_terms, t_hash, bad_size,
big_binary_to_term,
@@ -104,7 +107,8 @@ all() ->
bit_sized_binary_sizes, otp_6817, otp_8117, deep,
robustness, otp_8180, trapping, large,
error_after_yield, cmp_old_impl,
- is_binary_test].
+ is_binary_test,
+ local_ext].
groups() ->
[
@@ -152,6 +156,15 @@ end_per_testcase(_Func, _Config) ->
-define(heap_binary_size, 64).
+-define(MAP_EXT, 116).
+-define(SMALL_INTEGER_EXT, 97).
+-define(SMALL_ATOM_UTF8_EXT, 119).
+-define(ATOM_EXT, 100).
+-define(NIL, 106).
+-define(MAP_SMALL_MAP_LIMIT, 32).
+-define(FLOAT_EXT, 99).
+-define(NEW_FLOAT_EXT, 70).
+
copy_terms(Config) when is_list(Config) ->
Self = self(),
Pid = spawn_link(fun() -> copy_server(Self) end),
@@ -688,6 +701,53 @@ float_middle_endian(Config) when is_list(Config) ->
<<131,70,63,240,0,0,0,0,0,0>> = term_to_binary(1.0, [{minor_version,1}]),
1.0 = binary_to_term_stress(<<131,70,63,240,0,0,0,0,0,0>>).
+t2b_minor_version(_Config) ->
+ Umlaut = "ätöm",
+ UmlautLatin1 = unicode:characters_to_binary(Umlaut, latin1, latin1),
+ UmlautUtf8 = unicode:characters_to_binary(Umlaut, latin1, utf8),
+ UmlautAtom = binary_to_atom(UmlautLatin1, latin1),
+ UmlautAtom = binary_to_atom(UmlautUtf8, utf8),
+ ExoticBin = <<"ã“ã‚“ã«ã¡ã¯"/utf8>>,
+ ExoticAtom = binary_to_atom(ExoticBin, utf8),
+
+ <<131, ?SMALL_ATOM_UTF8_EXT, 4, "atom">> = term_to_binary(atom),
+ <<131, ?SMALL_ATOM_UTF8_EXT, 6, UmlautUtf8/binary>> =
+ term_to_binary(UmlautAtom),
+ <<131, ?SMALL_ATOM_UTF8_EXT, 15, ExoticBin/binary>> =
+ term_to_binary(ExoticAtom),
+
+ <<131, ?SMALL_ATOM_UTF8_EXT, 4, "atom">> =
+ term_to_binary(atom, [{minor_version,2}]),
+ <<131, ?SMALL_ATOM_UTF8_EXT, 6, UmlautUtf8/binary>> =
+ term_to_binary(UmlautAtom, [{minor_version,2}]),
+ <<131, ?SMALL_ATOM_UTF8_EXT, 15, ExoticBin/binary>> =
+ term_to_binary(ExoticAtom, [{minor_version,2}]),
+
+ <<131, ?ATOM_EXT, 4:16, "atom">> =
+ term_to_binary(atom, [{minor_version,1}]),
+ <<131, ?ATOM_EXT, 4:16, UmlautLatin1/binary>> =
+ term_to_binary(UmlautAtom, [{minor_version,1}]),
+ <<131, ?SMALL_ATOM_UTF8_EXT, 15, ExoticBin/binary>> =
+ term_to_binary(ExoticAtom, [{minor_version,1}]),
+
+ <<131, ?ATOM_EXT, 4:16, "atom">> =
+ term_to_binary(atom, [{minor_version,0}]),
+ <<131, ?ATOM_EXT, 4:16, UmlautLatin1/binary>> =
+ term_to_binary(UmlautAtom, [{minor_version,0}]),
+ <<131, ?SMALL_ATOM_UTF8_EXT, 15, ExoticBin/binary>> =
+ term_to_binary(ExoticAtom, [{minor_version,0}]),
+
+ <<131,?NEW_FLOAT_EXT,64,9,30,184,81,235,133,31>> =
+ term_to_binary(3.14),
+ <<131,?NEW_FLOAT_EXT,64,9,30,184,81,235,133,31>> =
+ term_to_binary(3.14, [{minor_version, 2}]),
+ <<131,?NEW_FLOAT_EXT,64,9,30,184,81,235,133,31>> =
+ term_to_binary(3.14, [{minor_version, 1}]),
+ <<131,?FLOAT_EXT,FloatStr:31/binary>> =
+ term_to_binary(3.14, [{minor_version, 0}]),
+ 3.14 = binary_to_float(FloatStr),
+ ok.
+
%% Test term_to_binary(Term, [deterministic]).
t2b_deterministic(_Config) ->
_ = rand:uniform(), %Seed generator
@@ -1108,10 +1168,6 @@ bad_bin_to_term(BadBin) ->
bad_bin_to_term(BadBin,Opts) ->
{'EXIT',{badarg,_}} = (catch binary_to_term_stress(BadBin,Opts)).
--define(MAP_EXT, 116).
--define(SMALL_INTEGER_EXT, 97).
--define(NIL, 106).
--define(MAP_SMALL_MAP_LIMIT, 32).
%% OTP-18343: Decode unsorted flatmap as key in hashmap
unsorted_map_in_map(Config) when is_list(Config) ->
@@ -2294,3 +2350,177 @@ list2bitstrlist([X0, X1, X2, X3, X4, X5 | Xs], Acc) when is_integer(X0), 0 =< X0
list2bitstrlist(Xs, NewAcc);
list2bitstrlist([X | Xs], Acc) ->
list2bitstrlist(Xs, [Acc,X]).
+
+local_ext(Config) when is_list(Config) ->
+ SDrv = send_term_local_drv,
+ CDrv = call_local_drv,
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ FileName = filename:join(PrivDir, "local_ext.data"),
+ Args = ["-setcookie", atom_to_list(erlang:get_cookie()),
+ "-pa", filename:dirname(code:which(?MODULE))],
+ {ok, Peer1, _} = peer:start_link(#{connection => 0, args => Args}),
+ {ok, Peer2, _} = peer:start_link(#{connection => 0, args => Args}),
+ LongNames = net_kernel:longnames(),
+ DynStartOpts = #{name_domain => if LongNames -> longnames;
+ true -> shortnames
+ end},
+ ExternalPid = self(),
+ ExternalRef = make_ref(),
+ ExternalPort = hd(erlang:ports()),
+ EncDecLocal = fun () ->
+ erl_ddll:start(),
+ ok = erl_ddll:load_driver(DataDir, SDrv),
+ SPort = open_port({spawn, SDrv}, []),
+ ok = erl_ddll:load_driver(DataDir, CDrv),
+ CPort = open_port({spawn, CDrv}, []),
+ false = erlang:is_alive(),
+ nonode@nohost = node(),
+ LocalPid = self(),
+ LocalRef = make_ref(),
+ LocalPort = hd(erlang:ports()),
+ Bin1 = <<4711:800>>,
+ Bin2 = <<4711:703>>,
+ Bin3 = <<4711:600>>,
+ Terms = [
+ LocalPid,
+ ExternalPid,
+ LocalRef,
+ ExternalRef,
+ LocalPort,
+ ExternalPort,
+ [LocalPid, Bin1, ExternalPid, LocalRef, Bin2,
+ ExternalRef, Bin3, Bin2, LocalPort,
+ ExternalPort],
+ "hej",
+ [],
+ {processes(), Bin3, erlang:ports(), Bin3},
+ #{pid => LocalPid, ref => LocalRef, port => LocalPort}
+ ],
+ {ok, FD} = file:open(FileName, [write]),
+ ETs = lists:map(fun (Term) ->
+ {enc_local(FD, Term), Term}
+ end, Terms),
+ ok = file:close(FD),
+ CheckET = fun ({LExt, Term}) ->
+ Term = binary_to_term(LExt),
+ SPort ! {self(), {command, LExt}},
+ receive
+ {SPort, Reply} ->
+ Term = Reply
+ end
+ end,
+ lists:foreach(CheckET, ETs),
+ call_local_success(CPort, ETs),
+ NodeName = peer:random_name(),
+ {ok, _} = net_kernel:start(list_to_atom(NodeName),
+ DynStartOpts),
+ true = erlang:is_alive(),
+ true = nonode@nohost /= node(),
+ lists:foreach(CheckET, ETs),
+ call_local_success(CPort, ETs),
+ ok = net_kernel:stop(),
+ false = erlang:is_alive(),
+ nonode@nohost = node(),
+ lists:foreach(CheckET, ETs),
+ call_local_success(CPort, ETs),
+ {ok, ExtList} = file:consult(FileName),
+ lists:foreach(fun (Ext) when is_binary(Ext) ->
+ _ = binary_to_term(Ext),
+ SPort ! {self(), {command, Ext}},
+ receive
+ {SPort, "bad_term_error"} ->
+ error(bad_term_error);
+ {SPort, _} ->
+ ok
+ end
+ end,
+ ExtList),
+ true = port_close(SPort),
+ true = port_close(CPort),
+ ok
+ end,
+ ok = peer:call(Peer1, erlang, apply, [EncDecLocal, []]),
+ DecOthersLocal = fun () ->
+ %% Verify that decoding of the terms encoded
+ %% on local external format by the other runtime
+ %% system instance fails on this runtime system
+ %% instance...
+ erl_ddll:start(),
+ ok = erl_ddll:load_driver(DataDir, SDrv),
+ SPort = open_port({spawn, SDrv}, []),
+ ok = erl_ddll:load_driver(DataDir, CDrv),
+ CPort = open_port({spawn, CDrv}, []),
+ false = erlang:is_alive(),
+ nonode@nohost = node(),
+ {ok, ExtList} = file:consult(FileName),
+ lists:foreach(fun (Ext) when is_binary(Ext) ->
+ try
+ Term = binary_to_term(Ext),
+ error({successful_decode, Term})
+ catch
+ error:badarg ->
+ ok
+ end,
+ SPort ! {self(), {command, Ext}},
+ receive
+ {SPort, Reply} ->
+ "bad_term_error" = Reply
+ end
+ end,
+ ExtList),
+ call_local_fail(CPort, ExtList),
+ true = port_close(SPort),
+ true = port_close(CPort),
+ ok
+ end,
+ ok = peer:call(Peer2, erlang, apply, [DecOthersLocal, []]),
+ peer:stop(Peer1),
+ peer:stop(Peer2),
+ ok.
+
+enc_local(FD, Term) ->
+ Ext = term_to_binary(Term, [local]),
+ Ext = iolist_to_binary(term_to_iovec(Term, [local])),
+ Term = binary_to_term(Ext),
+ io:format(FD, "~p.~n", [Ext]),
+ Ext.
+
+call_local_success(Port, []) ->
+ ok;
+call_local_success(Port, [{Lext1, T1}]) ->
+ Me = self(),
+ Ref = make_ref(),
+ Term = {term_to_binary(Me), Lext1, term_to_binary(Ref)},
+ {call_result, Me, 4711, T1, 17, Ref, "end_of_data"} = erlang:port_call(Port, 0, Term),
+ ok;
+call_local_success(Port, [{Lext1, T1}, {Lext3, T3} | Rest]) ->
+ Me = self(),
+ Term = {Lext1, term_to_binary(Me), Lext3},
+ {call_result, T1, 4711, Me, 17, T3, "end_of_data"} = erlang:port_call(Port, 0, Term),
+ call_local_success(Port, Rest).
+
+call_local_fail(Port, []) ->
+ ok;
+call_local_fail(Port, [Lext1]) ->
+ Me = self(),
+ Ref = make_ref(),
+ Term = {term_to_binary(Me), Lext1, term_to_binary(Ref)},
+ try
+ erlang:port_call(Port, 0, Term),
+ error(unexpected_port_call_success)
+ catch
+ error:badarg ->
+ ok
+ end;
+call_local_fail(Port, [Lext1, Lext3 | Rest]) ->
+ Me = self(),
+ Term = {Lext1, term_to_binary(Me), Lext3},
+ try
+ erlang:port_call(Port, 0, Term),
+ error(unexpected_port_call_success)
+ catch
+ error:badarg ->
+ ok
+ end,
+ call_local_fail(Port, Rest).
diff --git a/erts/emulator/test/binary_SUITE_data/Makefile.src b/erts/emulator/test/binary_SUITE_data/Makefile.src
new file mode 100644
index 0000000000..541dd6c1ad
--- /dev/null
+++ b/erts/emulator/test/binary_SUITE_data/Makefile.src
@@ -0,0 +1,33 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2023. All Rights Reserved.
+#
+# 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.
+#
+# %CopyrightEnd%
+#
+
+include @erl_interface_mk_include@
+
+CC = @CC@
+LD = @LD@
+CFLAGS = @CFLAGS@ -I@erl_include@ @DEFS@
+CROSSLDFLAGS = @CROSSLDFLAGS@
+
+SHLIB_EXTRA_CFLAGS = @EI_CFLAGS@ -I@erl_interface_include@
+SHLIB_EXTRA_LDLIBS = @erl_interface_eilib@
+
+all: send_term_local_drv@dll@ call_local_drv@dll@
+
+@SHLIB_RULES@
diff --git a/erts/emulator/test/binary_SUITE_data/call_local_drv.c b/erts/emulator/test/binary_SUITE_data/call_local_drv.c
new file mode 100644
index 0000000000..8db6c1c772
--- /dev/null
+++ b/erts/emulator/test/binary_SUITE_data/call_local_drv.c
@@ -0,0 +1,204 @@
+/* ``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.
+ *
+ * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+ * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+ * AB. All Rights Reserved.''
+ *
+ * $Id$
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "erl_driver.h"
+#include "ei.h"
+
+static ErlDrvSSizeT call(ErlDrvData drv_data,
+ unsigned int command,
+ char *buf, ErlDrvSizeT len,
+ char **rbuf, ErlDrvSizeT rlen,
+ unsigned int *flags);
+
+static ErlDrvEntry call_local_drv_entry = {
+ NULL /* init */,
+ NULL /* start */,
+ NULL /* stop */,
+ NULL /* output */,
+ NULL /* ready_input */,
+ NULL /* ready_output */,
+ "call_local_drv",
+ NULL /* finish */,
+ NULL /* handle */,
+ NULL /* control */,
+ NULL /* timeout */,
+ NULL /* outputv */,
+ NULL /* ready_async */,
+ NULL /* flush */,
+ call,
+ NULL /* event */,
+ ERL_DRV_EXTENDED_MARKER,
+ ERL_DRV_EXTENDED_MAJOR_VERSION,
+ ERL_DRV_EXTENDED_MINOR_VERSION,
+ ERL_DRV_FLAG_USE_PORT_LOCKING,
+ NULL /* handle2 */,
+ NULL /* handle_monitor */
+};
+
+DRIVER_INIT(call_local_drv)
+{
+ return &call_local_drv_entry;
+}
+
+
+static ErlDrvSSizeT call(ErlDrvData drv_data,
+ unsigned int command,
+ char *buf, ErlDrvSizeT len,
+ char **rbuf, ErlDrvSizeT rlen,
+ unsigned int *flags)
+{
+ ei_x_buff xbuf;
+ void *bin1 = NULL, *bin2 = NULL, *bin3 = NULL;
+ char *lext1, *lext2, *lext3;
+ int vsn, arity, type, ix, lix, res, err, size;
+ long size1, size2, size3;
+ ErlDrvSSizeT ret_size = (ErlDrvSSizeT) ERL_DRV_ERROR_GENERAL;
+
+ xbuf.buff = NULL;
+
+ ei_init();
+
+ ix = 0;
+ res = ei_decode_version(buf, &ix, &vsn);
+ if (res != 0 || vsn != 131)
+ goto error;
+
+ res = ei_decode_tuple_header(buf, &ix, &arity);
+ if (res != 0)
+ goto error;
+
+ /* External term 1 */
+ res = ei_get_type(buf, &ix, &type, &size);
+ if (res != 0 && type != ERL_BINARY_EXT)
+ goto error;
+
+ size1 = size;
+ bin1 = driver_alloc(size1);
+
+ res = ei_decode_binary(buf, &ix, bin1, &size1);
+ if (res != 0 && type != ERL_BINARY_EXT)
+ goto error;
+
+ lext1 = bin1;
+ lix = 0;
+ res = ei_decode_version(lext1, &lix, &vsn);
+ if (res != 0 || vsn != 131)
+ goto error;
+ lext1 += lix;
+ size1 -= lix;
+
+ /* External term 2 */
+ res = ei_get_type(buf, &ix, &type, &size);
+ if (res != 0 && type != ERL_BINARY_EXT)
+ goto error;
+
+ size2 = size;
+ bin2 = driver_alloc(size2);
+
+ res = ei_decode_binary(buf, &ix, bin2, &size2);
+ if (res != 0 && type != ERL_BINARY_EXT)
+ goto error;
+
+ lext2 = bin2;
+ lix = 0;
+ res = ei_decode_version(lext2, &lix, &vsn);
+ if (res != 0 || vsn != 131)
+ goto error;
+ lext2 += lix;
+ size2 -= lix;
+
+ /* External term 3 */
+ res = ei_get_type(buf, &ix, &type, &size);
+ if (res != 0 && type != ERL_BINARY_EXT)
+ goto error;
+
+ size3 = size;
+ bin3 = driver_alloc(size3);
+
+ res = ei_decode_binary(buf, &ix, bin3, &size3);
+ if (res != 0 && type != ERL_BINARY_EXT)
+ goto error;
+
+ lext3 = bin3;
+ lix = 0;
+ res = ei_decode_version(lext3, &lix, &vsn);
+ if (res != 0 || vsn != 131)
+ goto error;
+ lext3 += lix;
+ size3 -= lix;
+
+ /* encode result */
+
+ res = ei_x_new_with_version(&xbuf);
+ if (res != 0)
+ goto error;
+
+ res = ei_x_encode_tuple_header(&xbuf, 7);
+ if (res != 0)
+ goto error;
+
+ res = ei_x_encode_atom(&xbuf, "call_result");
+ if (res != 0)
+ goto error;
+
+ res = ei_x_append_buf(&xbuf, lext1, size1);
+ if (res != 0)
+ goto error;
+
+ res = ei_x_encode_long(&xbuf, 4711);
+ if (res != 0)
+ goto error;
+
+ res = ei_x_append_buf(&xbuf, lext2, size2);
+ if (res != 0)
+ goto error;
+
+ res = ei_x_encode_long(&xbuf, 17);
+ if (res != 0)
+ goto error;
+
+ res = ei_x_append_buf(&xbuf, lext3, size3);
+ if (res != 0)
+ goto error;
+
+ res = ei_x_encode_string(&xbuf, "end_of_data");
+ if (res != 0)
+ goto error;
+
+ /* success */
+ ret_size = xbuf.index;
+ *rbuf = driver_alloc(ret_size);
+ memcpy((void *) *rbuf, (void *) xbuf.buff, ret_size);
+
+error:
+
+ if (bin1)
+ driver_free(bin1);
+ if (bin2)
+ driver_free(bin2);
+ if (bin3)
+ driver_free(bin3);
+
+ if (xbuf.buff)
+ ei_x_free(&xbuf);
+
+ return ret_size;
+}
diff --git a/erts/emulator/test/binary_SUITE_data/send_term_local_drv.c b/erts/emulator/test/binary_SUITE_data/send_term_local_drv.c
new file mode 100644
index 0000000000..a5335d4a09
--- /dev/null
+++ b/erts/emulator/test/binary_SUITE_data/send_term_local_drv.c
@@ -0,0 +1,96 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2023. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+#include "erl_driver.h"
+#include <string.h>
+
+static void stop(ErlDrvData drv_data);
+static ErlDrvData start(ErlDrvPort port,
+ char *command);
+static void output(ErlDrvData drv_data,
+ char *buf, ErlDrvSizeT len);
+
+static ErlDrvEntry send_term_local_drv_entry = {
+ NULL /* init */,
+ start,
+ stop,
+ output,
+ NULL /* ready_input */,
+ NULL /* ready_output */,
+ "send_term_local_drv",
+ NULL /* finish */,
+ NULL /* handle */,
+ NULL /* control */,
+ NULL /* timeout */,
+ NULL /* outputv */,
+ NULL /* ready_async */,
+ NULL /* flush */,
+ NULL /* call */,
+ NULL /* event */,
+ ERL_DRV_EXTENDED_MARKER,
+ ERL_DRV_EXTENDED_MAJOR_VERSION,
+ ERL_DRV_EXTENDED_MINOR_VERSION,
+ ERL_DRV_FLAG_USE_PORT_LOCKING,
+ NULL /* handle2 */,
+ NULL /* handle_monitor */
+};
+
+DRIVER_INIT(send_term_local_drv)
+{
+ return &send_term_local_drv_entry;
+}
+
+static void stop(ErlDrvData drv_data)
+{
+}
+
+static ErlDrvData start(ErlDrvPort port,
+ char *command)
+{
+
+ return (ErlDrvData) port;
+}
+
+static void output(ErlDrvData drv_data,
+ char *buf, ErlDrvSizeT len)
+{
+ ErlDrvPort port = (ErlDrvPort) drv_data;
+ ErlDrvTermData term_port = driver_mk_port(port);
+ ErlDrvTermData caller = driver_caller(port);
+ int res;
+ ErlDrvTermData spec[] = {
+ ERL_DRV_PORT, term_port,
+ ERL_DRV_EXT2TERM, (ErlDrvTermData) buf, len,
+ ERL_DRV_TUPLE, 2
+ };
+ if (0 >= erl_drv_send_term(term_port, caller,
+ spec, sizeof(spec)/sizeof(spec[0]))) {
+ char *bad_term = "bad_term_error";
+ ErlDrvTermData spec[] = {
+ ERL_DRV_PORT, term_port,
+ ERL_DRV_STRING, (ErlDrvTermData) bad_term, strlen(bad_term),
+ ERL_DRV_TUPLE, 2
+ };
+ if (0 >= erl_drv_send_term(term_port, caller, spec,
+ sizeof(spec)/sizeof(spec[0]))) {
+ driver_failure_atom(port, "failed_to_bad_term_error");
+ }
+ }
+}
diff --git a/erts/emulator/test/bs_construct_SUITE.erl b/erts/emulator/test/bs_construct_SUITE.erl
index dfb38fc8ed..bbd942a6ec 100644
--- a/erts/emulator/test/bs_construct_SUITE.erl
+++ b/erts/emulator/test/bs_construct_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
copy_writable_binary/1, kostis/1, dynamic/1, bs_add/1,
otp_7422/1, zero_width/1, bad_append/1, bs_append_overflow/1,
bs_append_offheap/1,
- reductions/1, fp16/1, error_info/1]).
+ reductions/1, fp16/1, zero_init/1, error_info/1, little/1]).
-include_lib("common_test/include/ct.hrl").
@@ -43,7 +43,8 @@ all() ->
huge_float_field, system_limit, badarg,
copy_writable_binary, kostis, dynamic, bs_add, otp_7422, zero_width,
bad_append, bs_append_overflow, bs_append_offheap,
- reductions, fp16, error_info].
+ reductions, fp16, zero_init,
+ error_info, little].
init_per_suite(Config) ->
Config.
@@ -51,14 +52,6 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
application:stop(os_mon).
-big(1) ->
- 57285702734876389752897683.
-
-i(X) -> X.
-
-r(L) ->
- lists:reverse(L).
-
-define(T(B, L), {B, ??B, L}).
-define(N(B), {B, ??B, unknown}).
@@ -91,7 +84,7 @@ l(I_13, I_big1) ->
?T(<<57285702734876389752897684:32>>,
[138, 99, 0, 148]),
?T(<<I_big1:32/little>>,
- r([138, 99, 0, 147])),
+ lists:reverse([138, 99, 0, 147])),
?T(<<-1:17/unit:8>>,
lists:duplicate(17, 255)),
@@ -113,6 +106,9 @@ l(I_13, I_big1) ->
?T(<<4,3,<<1,2>>:1/binary>>,
[4,3,1]),
+ ?T(<< <<153,27:5>>:I_13/bits, 1:3 >>,
+ [153,217]),
+
?T(<<(256*45+47)>>,
[47]),
@@ -144,9 +140,74 @@ l(I_13, I_big1) ->
?T(<<<<5:3>>/bitstring>>, <<5:3>>),
?T(<<42,<<7:4>>/binary-unit:4>>, <<42,7:4>>),
?T(<<<<344:17>>/binary-unit:17>>, <<344:17>>),
- ?T(<<<<42,3,7656:16>>/binary-unit:16>>, <<42,3,7656:16>>)
-
- ].
+ ?T(<<<<42,3,7656:16>>/binary-unit:16>>, <<42,3,7656:16>>),
+
+ %% Different sizes and types. First without types.
+ ?T(<<I_big1:8>>, [147]),
+ ?T(<<I_big1:16>>, [0, 147]),
+ ?T(<<I_big1:24>>, [99, 0, 147]),
+ ?T(<<I_big1:32>>, [138, 99, 0, 147]),
+ ?T(<<I_big1:40>>, [5, 138, 99, 0, 147]),
+ ?T(<<I_big1:48>>, [229, 5, 138, 99, 0, 147]),
+ ?T(<<I_big1:56>>, [249, 229, 5, 138, 99, 0, 147]),
+ ?T(<<I_big1:64>>, [42, 249, 229, 5, 138, 99, 0, 147]),
+
+ %% Known integer with range.
+ ?T(<<(I_big1 band ((1 bsl 56) - 1)):8>>, [147]),
+ ?T(<<(I_big1 band ((1 bsl 56) - 1)):16>>, [0, 147]),
+ ?T(<<(I_big1 band ((1 bsl 56) - 1)):24>>, [99, 0, 147]),
+ ?T(<<(I_big1 band ((1 bsl 56) - 1)):32>>, [138, 99, 0, 147]),
+ ?T(<<(I_big1 band ((1 bsl 56) - 1)):40>>, [5, 138, 99, 0, 147]),
+ ?T(<<(I_big1 band ((1 bsl 56) - 1)):48>>, [229, 5, 138, 99, 0, 147]),
+ ?T(<<(I_big1 band ((1 bsl 56) - 1)):56>>, [249, 229, 5, 138, 99, 0, 147]),
+ ?T(<<(I_big1 band ((1 bsl 64) - 1)):64>>, [42, 249, 229, 5, 138, 99, 0, 147]),
+
+ %% Known integer with exact range.
+ ?T(<<(I_big1 band ((1 bsl 8) - 1)):8>>, [147]),
+ ?T(<<(I_big1 band ((1 bsl 16) - 1)):16>>, [0, 147]),
+ ?T(<<(I_big1 band ((1 bsl 24) - 1)):24>>, [99, 0, 147]),
+ ?T(<<(I_big1 band ((1 bsl 32) - 1)):32>>, [138, 99, 0, 147]),
+ ?T(<<(I_big1 band ((1 bsl 40) - 1)):40>>, [5, 138, 99, 0, 147]),
+ ?T(<<(I_big1 band ((1 bsl 48) - 1)):48>>, [229, 5, 138, 99, 0, 147]),
+
+ %% Known integer without range.
+ ?T(<<(I_big1 + 0):8>>, [147]),
+ ?T(<<(I_big1 + 0):16>>, [0, 147]),
+ ?T(<<(I_big1 + 0):24>>, [99, 0, 147]),
+ ?T(<<(I_big1 + 0):32>>, [138, 99, 0, 147]),
+ ?T(<<(I_big1 + 0):40>>, [5, 138, 99, 0, 147]),
+ ?T(<<(I_big1 + 0):48>>, [229, 5, 138, 99, 0, 147]),
+ ?T(<<(I_big1 + 0):56>>, [249, 229, 5, 138, 99, 0, 147]),
+ ?T(<<(I_big1 + 0):64>>, [42, 249, 229, 5, 138, 99, 0, 147]),
+
+ %% Known integer. Verify that the value does not bleed into the
+ %% previous segment.
+ ?T(<<1, (I_big1 + 0):8>>, [1, 147]),
+ ?T(<<2, (I_big1 + 0):16>>, [2, 0, 147]),
+ ?T(<<3, (I_big1 + 0):24>>, [3, 99, 0, 147]),
+ ?T(<<4, (I_big1 + 0):32>>, [4, 138, 99, 0, 147]),
+ ?T(<<5, (I_big1 + 0):40>>, [5, 5, 138, 99, 0, 147]),
+ ?T(<<6, (I_big1 + 0):48>>, [6, 229, 5, 138, 99, 0, 147]),
+ ?T(<<7, (I_big1 + 0):56>>, [7, 249, 229, 5, 138, 99, 0, 147]),
+ ?T(<<8, (I_big1 + 0):64>>, [8, 42, 249, 229, 5, 138, 99, 0, 147]),
+
+ %% Test non-byte sizes.
+ ?T(<<I_big1:33>>, <<197,49,128,73,1:1>>),
+ ?T(<<I_big1:39>>, <<11,20,198,1,19:7>>),
+
+ ?T(<<I_big1:57>>, <<124,242,130,197,49,128,73,1:1>>),
+ ?T(<<I_big1:58>>, <<190,121,65,98,152,192,36,3:2>>),
+ ?T(<<I_big1:59>>, <<95,60,160,177,76,96,18,3:3>>),
+ ?T(<<I_big1:60>>, <<175,158,80,88,166,48,9,3:4>>),
+ ?T(<<I_big1:61>>, <<87,207,40,44,83,24,4,19:5>>),
+ ?T(<<I_big1:62>>, <<171,231,148,22,41,140,2,19:6>>),
+ ?T(<<I_big1:63>>, <<85,243,202,11,20,198,1,19:7>>),
+
+ %% Test non-byte sizes and also that the value does not bleed
+ %% into the previous segment.
+ ?T(<<17, I_big1:33>>, <<17, 197,49,128,73,1:1>>),
+ ?T(<<19, I_big1:39>>, <<19, 11,20,198,1,19:7>>)
+ ].
native_3798() ->
case <<1:16/native>> of
@@ -276,10 +337,10 @@ fail_check(Res, _, _) ->
%%% Simple working cases
test1(Config) when is_list(Config) ->
- I_13 = i(13),
- I_big1 = big(1),
+ I_13 = id(13),
+ I_big1 = id(57285702734876389752897683),
Vars = [{'I_13', I_13},
- {'I_big1', I_big1}],
+ {'I_big1', I_big1}],
lists:foreach(fun one_test/1, eval_list(l(I_13, I_big1), Vars)).
%%% Misc
@@ -411,6 +472,20 @@ testf(Config) when is_list(Config) ->
?FAIL(<<<<7,8,9,3:7>>/binary-unit:16>>),
?FAIL(<<<<7,8,9,3:7>>/binary-unit:17>>),
+ %% Failures not deteced by v3_core. Those must be detected at
+ %% runtime.
+ Atom = id(ok),
+ Float = id(2.71),
+ List = id([-1,0,1]),
+ NonBinaries = [{'Atom',Atom}, {'Float',Float}, {'List',List}],
+ ?FAIL_VARS(<<Atom/bits>>, NonBinaries),
+ ?FAIL_VARS(<<Atom/bits,0>>, NonBinaries),
+ ?FAIL_VARS(<<0,Atom/bits>>, NonBinaries),
+ ?FAIL_VARS(<<Float/bits>>, NonBinaries),
+ ?FAIL_VARS(<<Float/bits,0>>, NonBinaries),
+ ?FAIL_VARS(<<List/bits>>, NonBinaries),
+ ?FAIL_VARS(<<List/bits,0>>, NonBinaries),
+
ok.
testf_1(W, B) ->
@@ -719,6 +794,21 @@ dynamic_big(Bef, N, Int, Lpad, Rpad) ->
Bin = id(<<Lpad:Bef,NumBin/bitstring,Rpad:(128-Bef-N)>>),
Bin = <<Lpad:Bef,Int:N,Rpad:(128-Bef-N)>>,
+ %% Units are seldom used with integer segments even in our test
+ %% suites, and I have never seen non-power-of-two units in
+ %% production code.
+ if
+ Bef rem 8 =:= 0 ->
+ Bin = <<Lpad:(Bef div 8)/unit:8,Int:N,Rpad:(128-Bef-N)>>;
+ Bef rem 7 =:= 0 ->
+ Bin = <<Lpad:(Bef div 7)/unit:7,Int:N,Rpad:(128-Bef-N)>>;
+ (128-Bef-N) rem 5 =:= 0 ->
+ Aft = (128 - Bef - N) div 5,
+ Bin = <<Lpad:Bef,Int:N,Rpad:Aft/unit:5>>;
+ true ->
+ ok
+ end,
+
%% Further verify the result by matching.
LpadMasked = Lpad band ((1 bsl Bef) - 1),
RpadMasked = Rpad band ((1 bsl (128-Bef-N)) - 1),
@@ -735,6 +825,20 @@ dynamic_little(Bef, N, Int, Lpad, Rpad) ->
Bin = id(<<Lpad:Bef/little,NumBin/bitstring,Rpad:(128-Bef-N)/little>>),
Bin = <<Lpad:Bef/little,Int:N/little,Rpad:(128-Bef-N)/little>>,
+ if
+ Bef rem 8 =:= 0 ->
+ Bin = <<Lpad:(Bef div 8)/little-unit:8,
+ Int:N/little,Rpad:(128-Bef-N)/little>>;
+ Bef rem 9 =:= 0 ->
+ Bin = <<Lpad:(Bef div 9)/little-unit:9,
+ Int:N/little,Rpad:(128-Bef-N)/little>>;
+ (128-Bef-N) rem 17 =:= 0 ->
+ Aft = (128 - Bef - N) div 17,
+ Bin = <<Lpad:Bef/little,Int:N/little,Rpad:Aft/little-unit:17>>;
+ true ->
+ ok
+ end,
+
%% Further verify the result by matching.
LpadMasked = Lpad band ((1 bsl Bef) - 1),
RpadMasked = Rpad band ((1 bsl (128-Bef-N)) - 1),
@@ -744,7 +848,8 @@ dynamic_little(Bef, N, Int, Lpad, Rpad) ->
%% Test that the bs_add/5 instruction handles big numbers correctly.
bs_add(Config) when is_list(Config) ->
- Mod = bs_construct_bs_add,
+ Mod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++
+ atom_to_list(?FUNCTION_NAME)),
N = 2000,
Code = [{module, Mod},
{exports, [{bs_add,2}]},
@@ -795,9 +900,12 @@ bs_add(Config) when is_list(Config) ->
%% Clean up.
ok = file:delete(AsmFile),
ok = file:delete(code:which(Mod)),
+ _ = code:delete(Mod),
+ _ = code:purge(Mod),
+
ok.
-
+
smallest_big() ->
smallest_big_1(1 bsl 24).
@@ -1030,6 +1138,233 @@ fp16(_Config) ->
?FP16(16#4000, 2.0),
ok.
+zero_init(_Config) ->
+ <<LPad:64/bits,RPad:64/bits>> = id(erlang:md5([42])),
+ Sizes = [511,512,513, 767,768,769, 1023,1024,1025,
+ 16#7fff,16#ffff,16#10000] ++ lists:seq(0, 257),
+ _ = [do_zero_init(Size, LPad, RPad) || Size <- Sizes],
+ ok.
+
+do_zero_init(Size, LPad, RPad) ->
+ try
+ do_zero_init_1(Size, LPad, RPad)
+ catch
+ C:R:Stk ->
+ io:format("Size = ~p, LPad = ~p, RPad = ~p\n", [Size, LPad, RPad]),
+ erlang:raise(C, R, Stk)
+ end.
+
+do_zero_init_1(Size, LPad, RPad) ->
+ Zeroes = id(<<0:Size>>),
+ <<0:Size>> = Zeroes,
+ Bin = id(<<LPad:64/bits, Zeroes/bits, RPad:64/bits>>),
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>),
+ Bin = id(<<LPad:(id(64))/bits, 0:Size, RPad:64/bits>>),
+
+ if
+ Size rem 11 =:= 0 ->
+ Bin = id(<<LPad:64/bits, 0:(Size div 11)/unit:11, RPad:64/bits>>);
+ true ->
+ ok
+ end,
+
+ case Size of
+ 0 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 1 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 2 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 3 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 4 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 5 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 6 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 7 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 8 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 9 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 10 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 11 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 12 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 13 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 14 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 15 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 16 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 31 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 32 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 33 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 47 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 48 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 49 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 63 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 64 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 65 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 79 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 80 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 81 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 90 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 91 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 92 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 93 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 94 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 95 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 96 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 97 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 98 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 99 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 100 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 101 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 102 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 103 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 104 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 105 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 106 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 107 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 108 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 109 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 127 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 128 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 129 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 130 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 131 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 132 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 133 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 134 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 135 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 136 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 137 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 138 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 139 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 140 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 141 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 142 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 143 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 144 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 145 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 159 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 160 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 161 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 191 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 192 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 193 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 255 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 256 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 257 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 511 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 512 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 513 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 767 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 768 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 769 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 1023 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 1024 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 1025 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+
+ 16#7fff ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 16#ffff ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ 16#10000 ->
+ Bin = id(<<LPad:64/bits, 0:Size, RPad:64/bits>>);
+ _ ->
+ ok
+ end.
+
+
-define(ERROR_INFO(Expr),
fun() ->
try Expr of
@@ -1169,6 +1504,161 @@ error_info_verify(Reason, Stk0, Expr, Overrides) ->
io:format("~ts: ~ts\n", [Expr,String]),
{Reason, Cause, String}.
+little(_Config) ->
+ _ = rand:uniform(), %Seed generator
+ io:format("Seed: ~p", [rand:export_seed()]),
+
+ RandBytes = rand:bytes(10),
+ _ = [do_little(RandBytes, N) || N <- lists:seq(0, 10*8)],
+
+ ok.
+
+do_little(Bin0, N) ->
+ <<Bin1:N/bits,Bin2/bits>> = Bin0,
+ Bin2Size = bit_size(Bin2),
+
+ <<I1:N/little-integer>> = Bin1,
+ <<I2:Bin2Size/little-integer>> = Bin2,
+
+ Bin1 = <<I1:N/little-integer>>,
+ Bin1 = do_little_1(N, id(I1)),
+
+ Bin2 = <<I2:Bin2Size/little-integer>>,
+ Bin2 = do_little_1(Bin2Size, id(I2)),
+
+ ok.
+
+do_little_1(0, I) -> <<I:0/little-integer>>;
+do_little_1(1, I) -> <<I:1/little-integer>>;
+do_little_1(2, I) -> <<I:2/little-integer>>;
+do_little_1(3, I) -> <<I:3/little-integer>>;
+do_little_1(4, I) -> <<I:4/little-integer>>;
+do_little_1(5, I) -> <<I:5/little-integer>>;
+do_little_1(6, I) -> <<I:6/little-integer>>;
+do_little_1(7, I) -> <<I:7/little-integer>>;
+do_little_1(8, I) -> <<I:8/little-integer>>;
+do_little_1(9, I) -> <<I:9/little-integer>>;
+do_little_1(10, I) -> <<I:10/little-integer>>;
+do_little_1(11, I) -> <<I:11/little-integer>>;
+do_little_1(12, I) -> <<I:12/little-integer>>;
+do_little_1(13, I) -> <<I:13/little-integer>>;
+do_little_1(14, I) -> <<I:14/little-integer>>;
+do_little_1(15, I) -> <<I:15/little-integer>>;
+do_little_1(16, I) -> <<I:16/little-integer>>;
+do_little_1(17, I) -> <<I:17/little-integer>>;
+do_little_1(18, I) -> <<I:18/little-integer>>;
+do_little_1(19, I) -> <<I:19/little-integer>>;
+do_little_1(20, I) -> <<I:20/little-integer>>;
+do_little_1(21, I) -> <<I:21/little-integer>>;
+do_little_1(22, I) -> <<I:22/little-integer>>;
+do_little_1(23, I) -> <<I:23/little-integer>>;
+do_little_1(24, I) -> <<I:24/little-integer>>;
+do_little_1(25, I) -> <<I:25/little-integer>>;
+do_little_1(26, I) -> <<I:26/little-integer>>;
+do_little_1(27, I) -> <<I:27/little-integer>>;
+do_little_1(28, I) -> <<I:28/little-integer>>;
+do_little_1(29, I) -> <<I:29/little-integer>>;
+do_little_1(30, I) -> <<I:30/little-integer>>;
+do_little_1(31, I) -> <<I:31/little-integer>>;
+do_little_1(32, I) -> <<I:32/little-integer>>;
+do_little_1(33, I) -> <<I:33/little-integer>>;
+do_little_1(34, I) -> <<I:34/little-integer>>;
+do_little_1(35, I) -> <<I:35/little-integer>>;
+do_little_1(36, I) -> <<I:36/little-integer>>;
+do_little_1(37, I) -> <<I:37/little-integer>>;
+do_little_1(38, I) -> <<I:38/little-integer>>;
+do_little_1(39, I) -> <<I:39/little-integer>>;
+do_little_1(40, I) -> <<I:40/little-integer>>;
+do_little_1(41, I) -> <<I:41/little-integer>>;
+do_little_1(42, I) -> <<I:42/little-integer>>;
+do_little_1(43, I) -> <<I:43/little-integer>>;
+do_little_1(44, I) -> <<I:44/little-integer>>;
+do_little_1(45, I) -> <<I:45/little-integer>>;
+do_little_1(46, I) -> <<I:46/little-integer>>;
+do_little_1(47, I) -> <<I:47/little-integer>>;
+do_little_1(48, I) -> <<I:48/little-integer>>;
+do_little_1(49, I) -> <<I:49/little-integer>>;
+do_little_1(50, I) -> <<I:50/little-integer>>;
+do_little_1(51, I) -> <<I:51/little-integer>>;
+do_little_1(52, I) -> <<I:52/little-integer>>;
+do_little_1(53, I) -> <<I:53/little-integer>>;
+do_little_1(54, I) -> <<I:54/little-integer>>;
+do_little_1(55, I) -> <<I:55/little-integer>>;
+do_little_1(56, I) -> <<I:56/little-integer>>;
+do_little_1(57, I) -> <<I:57/little-integer>>;
+do_little_1(58, I) -> <<I:58/little-integer>>;
+do_little_1(59, I) -> <<I:59/little-integer>>;
+do_little_1(60, I) -> <<I:60/little-integer>>;
+do_little_1(61, I) -> <<I:61/little-integer>>;
+do_little_1(62, I) -> <<I:62/little-integer>>;
+do_little_1(63, I) -> <<I:63/little-integer>>;
+do_little_1(64, I) -> <<I:64/little-integer>>;
+do_little_1(65, I) -> <<I:65/little-integer>>;
+do_little_1(66, I) -> <<I:66/little-integer>>;
+do_little_1(67, I) -> <<I:67/little-integer>>;
+do_little_1(68, I) -> <<I:68/little-integer>>;
+do_little_1(69, I) -> <<I:69/little-integer>>;
+do_little_1(70, I) -> <<I:70/little-integer>>;
+do_little_1(71, I) -> <<I:71/little-integer>>;
+do_little_1(72, I) -> <<I:72/little-integer>>;
+do_little_1(73, I) -> <<I:73/little-integer>>;
+do_little_1(74, I) -> <<I:74/little-integer>>;
+do_little_1(75, I) -> <<I:75/little-integer>>;
+do_little_1(76, I) -> <<I:76/little-integer>>;
+do_little_1(77, I) -> <<I:77/little-integer>>;
+do_little_1(78, I) -> <<I:78/little-integer>>;
+do_little_1(79, I) -> <<I:79/little-integer>>;
+do_little_1(80, I) -> <<I:80/little-integer>>;
+do_little_1(81, I) -> <<I:81/little-integer>>;
+do_little_1(82, I) -> <<I:82/little-integer>>;
+do_little_1(83, I) -> <<I:83/little-integer>>;
+do_little_1(84, I) -> <<I:84/little-integer>>;
+do_little_1(85, I) -> <<I:85/little-integer>>;
+do_little_1(86, I) -> <<I:86/little-integer>>;
+do_little_1(87, I) -> <<I:87/little-integer>>;
+do_little_1(88, I) -> <<I:88/little-integer>>;
+do_little_1(89, I) -> <<I:89/little-integer>>;
+do_little_1(90, I) -> <<I:90/little-integer>>;
+do_little_1(91, I) -> <<I:91/little-integer>>;
+do_little_1(92, I) -> <<I:92/little-integer>>;
+do_little_1(93, I) -> <<I:93/little-integer>>;
+do_little_1(94, I) -> <<I:94/little-integer>>;
+do_little_1(95, I) -> <<I:95/little-integer>>;
+do_little_1(96, I) -> <<I:96/little-integer>>;
+do_little_1(97, I) -> <<I:97/little-integer>>;
+do_little_1(98, I) -> <<I:98/little-integer>>;
+do_little_1(99, I) -> <<I:99/little-integer>>;
+do_little_1(100, I) -> <<I:100/little-integer>>;
+do_little_1(101, I) -> <<I:101/little-integer>>;
+do_little_1(102, I) -> <<I:102/little-integer>>;
+do_little_1(103, I) -> <<I:103/little-integer>>;
+do_little_1(104, I) -> <<I:104/little-integer>>;
+do_little_1(105, I) -> <<I:105/little-integer>>;
+do_little_1(106, I) -> <<I:106/little-integer>>;
+do_little_1(107, I) -> <<I:107/little-integer>>;
+do_little_1(108, I) -> <<I:108/little-integer>>;
+do_little_1(109, I) -> <<I:109/little-integer>>;
+do_little_1(110, I) -> <<I:110/little-integer>>;
+do_little_1(111, I) -> <<I:111/little-integer>>;
+do_little_1(112, I) -> <<I:112/little-integer>>;
+do_little_1(113, I) -> <<I:113/little-integer>>;
+do_little_1(114, I) -> <<I:114/little-integer>>;
+do_little_1(115, I) -> <<I:115/little-integer>>;
+do_little_1(116, I) -> <<I:116/little-integer>>;
+do_little_1(117, I) -> <<I:117/little-integer>>;
+do_little_1(118, I) -> <<I:118/little-integer>>;
+do_little_1(119, I) -> <<I:119/little-integer>>;
+do_little_1(120, I) -> <<I:120/little-integer>>;
+do_little_1(121, I) -> <<I:121/little-integer>>;
+do_little_1(122, I) -> <<I:122/little-integer>>;
+do_little_1(123, I) -> <<I:123/little-integer>>;
+do_little_1(124, I) -> <<I:124/little-integer>>;
+do_little_1(125, I) -> <<I:125/little-integer>>;
+do_little_1(126, I) -> <<I:126/little-integer>>;
+do_little_1(127, I) -> <<I:127/little-integer>>;
+do_little_1(128, I) -> <<I:128/little-integer>>.
+
+
%%%
%%% Common utilities.
%%%
diff --git a/erts/emulator/test/bs_match_bin_SUITE.erl b/erts/emulator/test/bs_match_bin_SUITE.erl
index a7f5ad2d6b..c29fbd6be7 100644
--- a/erts/emulator/test/bs_match_bin_SUITE.erl
+++ b/erts/emulator/test/bs_match_bin_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,17 +23,18 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
byte_split_binary/1,bit_split_binary/1,match_huge_bin/1,
- bs_match_string_edge_case/1]).
+ bs_match_string_edge_case/1,contexts/1,
+ empty_binary/1]).
-include_lib("common_test/include/ct.hrl").
suite() -> [{ct_hooks,[ts_install_cth]}].
-all() ->
+all() ->
[byte_split_binary, bit_split_binary, match_huge_bin,
- bs_match_string_edge_case].
+ bs_match_string_edge_case, contexts, empty_binary].
-groups() ->
+groups() ->
[].
init_per_suite(Config) ->
@@ -235,3 +236,50 @@ bs_match_string_edge_case(_Config) ->
<<?MATCH512, " ", Tail1/binary>> = Bin,
<<" ", Tail1/binary>> = id(Tail0),
ok.
+
+contexts(_Config) ->
+ Bytes = rand:bytes(12),
+ _ = [begin
+ <<B:N/binary,_/binary>> = Bytes,
+ B = id(get_binary(B))
+ end || N <- lists:seq(0, 12)],
+ ok.
+
+get_binary(Bin) ->
+ [A,B,C,D,E,F] = id([1,2,3,4,5,6]),
+ {Res,_} = get_binary_memory_ctx(A, B, C, D, E, F, Bin),
+ Res.
+
+
+get_binary_memory_ctx(A, B, C, D, E, F, Bin) ->
+ %% The match context will be in {x,6}, which is not
+ %% a X register backed by a CPU register on any platform.
+ Res = case Bin of
+ <<Res0:0/binary>> -> Res0;
+ <<Res0:1/binary>> -> Res0;
+ <<Res0:2/binary>> -> Res0;
+ <<Res0:3/binary>> -> Res0;
+ <<Res0:4/binary>> -> Res0;
+ <<Res0:5/binary>> -> Res0;
+ <<Res0:6/binary>> -> Res0;
+ <<Res0:7/binary>> -> Res0;
+ <<Res0:8/binary>> -> Res0;
+ <<Res0:9/binary>> -> Res0;
+ <<Res0:10/binary>> -> Res0;
+ <<Res0:11/binary>> -> Res0;
+ <<Res0:12/binary>> -> Res0
+ end,
+ {Res,{A,B,C,D,E,F}}.
+
+empty_binary(_Config) ->
+ _ = do_empty_binary(1_000_000),
+ ok.
+
+do_empty_binary(0) ->
+ ok;
+do_empty_binary(N) ->
+ %% The new bs_match instruction would use more heap space
+ %% than reserved when matching out an empty binary.
+ <<V1:0/bits, V1:0/bitstring, V2:0/bytes, V2:0/bits>> = <<>>,
+ [0|do_empty_binary(N-1)].
+
diff --git a/erts/emulator/test/bs_match_int_SUITE.erl b/erts/emulator/test/bs_match_int_SUITE.erl
index 08b0a72785..5d7d5f97f0 100644
--- a/erts/emulator/test/bs_match_int_SUITE.erl
+++ b/erts/emulator/test/bs_match_int_SUITE.erl
@@ -19,10 +19,10 @@
-module(bs_match_int_SUITE).
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
- integer/1,signed_integer/1,dynamic/1,more_dynamic/1,mml/1,
- match_huge_int/1,bignum/1,unaligned_32_bit/1]).
+ integer/1,mixed_sizes/1,signed_integer/1,dynamic/1,more_dynamic/1,
+ mml/1,match_huge_int/1,bignum/1,unaligned_32_bit/1,unit/1]).
-include_lib("common_test/include/ct.hrl").
@@ -30,11 +30,11 @@
suite() -> [{ct_hooks,[ts_install_cth]}].
-all() ->
- [integer, signed_integer, dynamic, more_dynamic, mml,
- match_huge_int, bignum, unaligned_32_bit].
+all() ->
+ [integer, mixed_sizes, signed_integer, dynamic, more_dynamic, mml,
+ match_huge_int, bignum, unaligned_32_bit, unit].
-groups() ->
+groups() ->
[].
init_per_suite(Config) ->
@@ -51,6 +51,9 @@ end_per_group(_GroupName, Config) ->
integer(Config) when is_list(Config) ->
+ _ = rand:uniform(), %Seed generator
+ io:format("Seed: ~p", [rand:export_seed()]),
+
0 = get_int(mkbin([])),
0 = get_int(mkbin([0])),
42 = get_int(mkbin([42])),
@@ -58,36 +61,628 @@ integer(Config) when is_list(Config) ->
256 = get_int(mkbin([1,0])),
257 = get_int(mkbin([1,1])),
258 = get_int(mkbin([1,2])),
- 258 = get_int(mkbin([1,2])),
65534 = get_int(mkbin([255,254])),
16776455 = get_int(mkbin([255,253,7])),
4245492555 = get_int(mkbin([253,13,19,75])),
4294967294 = get_int(mkbin([255,255,255,254])),
4294967295 = get_int(mkbin([255,255,255,255])),
+
+ 16#cafebeef = get_int(<<16#cafebeef:32>>),
+ 16#cafebeef42 = get_int(<<16#cafebeef42:40>>),
+ 16#cafebeeffeed = get_int(<<16#cafebeeffeed:48>>),
+ 16#cafebeeffeed42 = get_int(<<16#cafebeeffeed42:56>>),
+ 16#1cafebeeffeed42 = get_int(<<16#1cafebeeffeed42:57>>),
+ 16#2cafebeeffeed42 = get_int(<<16#2cafebeeffeed42:58>>),
+ 16#7cafebeeffeed42 = get_int(<<16#7cafebeeffeed42:59>>),
+
+ 16#beefcafefeed = get_int(<<16#beefcafefeed:60>>),
+ 16#beefcafefeed = get_int(<<16#beefcafefeed:61>>),
+ 16#beefcafefeed = get_int(<<16#beefcafefeed:62>>),
+ 16#beefcafefeed = get_int(<<16#beefcafefeed:63>>),
+
+ 16#cafebeeffeed42 = get_int(<<16#cafebeeffeed42:60>>),
+ 16#cafebeeffeed42 = get_int(<<16#cafebeeffeed42:61>>),
+ 16#cafebeeffeed42 = get_int(<<16#cafebeeffeed42:62>>),
+ 16#cafebeeffeed42 = get_int(<<16#cafebeeffeed42:63>>),
+
+ 16#acafebeeffeed42 = get_int(<<16#acafebeeffeed42:60>>),
+ 16#acafebeeffeed42 = get_int(<<16#acafebeeffeed42:61>>),
+ 16#acafebeeffeed42 = get_int(<<16#acafebeeffeed42:62>>),
+ 16#acafebeeffeed42 = get_int(<<16#acafebeeffeed42:63>>),
+
+ 16#cafebeeffeed = get_int(<<16#cafebeeffeed:64>>),
+ 16#cafebeeffeedface = get_int(<<16#cafebeeffeedface:64>>),
+
+ get_int_roundtrip(rand:bytes(12), 0),
+ get_int_roundtrip(rand:bytes(12), 0),
+
Eight = [200,1,19,128,222,42,97,111],
cmp128(Eight, uint(Eight)),
- fun_clause(catch get_int(mkbin(seq(1,5)))),
+ fun_clause(catch get_int(mkbin(seq(1, 20)))),
ok.
-get_int(Bin) ->
- I = get_int1(Bin),
- get_int(Bin, I).
+get_int_roundtrip(Bin0, Size) when Size =< 8*byte_size(Bin0) ->
+ <<Bin:Size/bits,_/bits>> = Bin0,
+ <<I:Size>> = Bin,
+ I = get_int(Bin),
+ get_int_roundtrip(Bin0, Size+1);
+get_int_roundtrip(_, _) -> ok.
+
+get_int(Bin0) ->
+ %% Note that it has become impossible to create a byte-sized sub
+ %% binary (see erts_extract_sub_binary() in erl_bits.c) of size 64
+ %% or less. Therefore, to be able to create an unaligned binary,
+ %% we'll need to base it on on a binary with more than 64 bytes.
+ Size = bit_size(Bin0),
+ Filler = rand:bytes(65),
+ UnsignedBigBin = id(<<Filler/binary,Bin0/bits>>),
+ I = get_unsigned_big(UnsignedBigBin),
+
+ %% io:format("~p ~p\n", [Size,I]),
+ if
+ Size =< 10*8 ->
+ OversizedUnsignedBig = id(<<Filler/binary,0:16,Bin0/bits>>),
+ I = get_unsigned_big(OversizedUnsignedBig);
+ true ->
+ ok
+ end,
+
+ test_unaligned(UnsignedBigBin, I, fun get_unsigned_big/1),
+
+ %% Test unsigned little-endian integers.
+
+ UnsignedLittleBin = id(<<Filler/binary,I:Size/little>>),
+ I = get_unsigned_little(UnsignedLittleBin),
+
+ test_unaligned(UnsignedLittleBin, I, fun get_unsigned_little/1),
+
+ %% Test signed big-endian integers.
+
+ SignedBigBin1 = id(<<Filler/binary,(-I):(Size+1)/big>>),
+ I = -get_signed_big(SignedBigBin1),
+
+ SignedBigBin2 = id(<<Filler/binary,I:(Size+1)/big>>),
+ I = get_signed_big(SignedBigBin2),
+
+ %% test_unaligned(SignedBigBin1, -I, fun get_signed_big/1),
+ test_unaligned(SignedBigBin2, I, fun get_signed_big/1),
+
+ %% Test signed little-endian integers.
+
+ SignedLittleBin1 = id(<<Filler/binary,(-I):(Size+1)/little>>),
+ I = -get_signed_little(SignedLittleBin1),
+
+ SignedLittleBin2 = id(<<Filler/binary,I:(Size+1)/little>>),
+ I = get_signed_little(SignedLittleBin2),
-get_int(Bin0, I) when size(Bin0) < 4 ->
- Bin = <<0,Bin0/binary>>,
- I = get_int1(Bin),
- get_int(Bin, I);
-get_int(_, I) -> I.
+ test_unaligned(SignedLittleBin1, -I, fun get_signed_little/1),
+ test_unaligned(SignedLittleBin2, I, fun get_signed_little/1),
-get_int1(<<I:0>>) -> I;
-get_int1(<<I:8>>) -> I;
-get_int1(<<I:16>>) -> I;
-get_int1(<<I:24>>) -> I;
-get_int1(<<I:32>>) -> I.
+ I.
+
+test_unaligned(Bin, I, Matcher) ->
+ <<RandBytes1:8/binary,RandBytes2:8/binary>> = rand:bytes(16),
+ Size = bit_size(Bin),
+ _ = [begin
+ <<_:(Offset+32),UnalignedBin:Size/bits,_/bits>> =
+ id(<<RandBytes1:(Offset+32)/bits,
+ Bin:Size/bits,
+ RandBytes2/binary>>),
+ I = Matcher(UnalignedBin)
+ end || Offset <- [1,2,3,4,5,6,7]],
+ ok.
+
+
+
+get_unsigned_big(Bin) ->
+ Res = get_unsigned_big_plain(Bin),
+ [A,B,C,D,E] = id([1,2,3,4,5]),
+ {Res,_} = get_unsigned_big_memory_ctx(A, B, C, D, E, Res, Bin),
+ Res.
+
+get_unsigned_big_memory_ctx(A, B, C, D, E, Res0, Bin) ->
+ %% The match context will be in {x,6}, which is not
+ %% a X register backed by a CPU register on any platform.
+ Res = case Bin of
+ <<_:65/unit:8,I:7>> -> I;
+ <<_:65/unit:8,I:8>> -> I;
+ <<_:65/unit:8,I:36>> -> I;
+ <<_:65/unit:8,I:59>> -> I;
+ <<_:65/unit:8,I:60>> -> I;
+ <<_:65/unit:8,I:61>> -> I;
+ <<_:65/unit:8,I:62>> -> I;
+ <<_:65/unit:8,I:63>> -> I;
+ <<_:65/unit:8,I:64>> -> I;
+ <<_:65/unit:8,I:65>> -> I;
+ <<_:65/unit:8,I:70>> -> I;
+ <<_:65/unit:8,I:80>> -> I;
+ <<_:65/unit:8,I:95>> -> I;
+ _ -> Res0
+ end,
+ {Res,{A,B,C,D,E}}.
+
+get_unsigned_big_plain(<<_:65/unit:8,I:0>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:1>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:2>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:3>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:4>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:5>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:6>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:7>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:8>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:9>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:10>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:11>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:12>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:13>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:14>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:15>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:16>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:17>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:18>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:19>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:20>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:21>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:22>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:23>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:24>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:25>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:26>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:27>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:28>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:29>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:30>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:31>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:32>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:33>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:34>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:35>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:36>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:37>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:38>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:39>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:40>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:41>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:42>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:43>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:44>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:45>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:46>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:47>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:48>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:49>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:50>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:51>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:52>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:53>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:54>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:55>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:56>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:57>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:58>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:59>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:60>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:61>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:62>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:63>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:64>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:65>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:66>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:67>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:68>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:69>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:70>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:71>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:72>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:73>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:74>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:75>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:76>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:77>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:78>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:79>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:80>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:81>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:82>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:83>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:84>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:85>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:86>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:87>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:88>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:89>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:90>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:91>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:92>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:93>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:94>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:95>>) -> I;
+get_unsigned_big_plain(<<_:65/unit:8,I:96>>) -> I.
+
+get_unsigned_little(<<_:65/unit:8,I:0/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:1/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:2/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:3/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:4/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:5/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:6/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:7/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:8/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:9/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:10/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:11/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:12/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:13/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:14/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:15/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:16/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:17/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:18/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:19/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:20/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:21/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:22/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:23/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:24/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:25/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:26/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:27/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:28/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:29/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:30/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:31/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:32/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:33/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:34/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:35/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:36/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:37/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:38/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:39/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:40/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:41/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:42/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:43/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:44/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:45/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:46/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:47/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:48/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:49/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:50/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:51/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:52/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:53/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:54/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:55/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:56/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:57/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:58/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:59/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:60/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:61/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:62/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:63/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:64/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:65/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:66/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:67/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:68/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:69/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:70/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:71/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:72/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:73/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:74/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:75/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:76/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:77/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:78/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:79/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:80/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:81/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:82/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:83/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:84/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:85/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:86/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:87/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:88/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:89/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:90/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:91/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:92/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:93/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:94/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:95/little>>) -> I;
+get_unsigned_little(<<_:65/unit:8,I:96/little>>) -> I.
+
+get_signed_big(<<_:65/unit:8,I:0/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:1/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:2/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:3/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:4/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:5/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:6/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:7/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:8/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:9/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:10/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:11/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:12/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:13/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:14/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:15/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:16/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:17/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:18/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:19/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:20/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:21/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:22/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:23/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:24/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:25/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:26/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:27/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:28/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:29/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:30/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:31/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:32/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:33/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:34/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:35/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:36/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:37/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:38/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:39/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:40/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:41/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:42/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:43/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:44/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:45/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:46/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:47/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:48/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:49/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:50/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:51/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:52/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:53/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:54/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:55/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:56/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:57/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:58/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:59/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:60/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:61/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:62/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:63/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:64/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:65/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:66/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:67/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:68/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:69/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:70/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:71/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:72/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:73/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:74/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:75/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:76/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:77/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:78/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:79/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:80/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:81/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:82/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:83/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:84/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:85/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:86/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:87/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:88/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:89/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:90/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:91/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:92/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:93/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:94/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:95/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:96/signed-big>>) -> I;
+get_signed_big(<<_:65/unit:8,I:97/signed-big>>) -> I.
+
+get_signed_little(<<_:65/unit:8,I:0/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:1/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:2/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:3/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:4/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:5/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:6/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:7/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:8/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:9/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:10/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:11/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:12/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:13/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:14/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:15/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:16/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:17/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:18/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:19/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:20/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:21/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:22/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:23/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:24/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:25/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:26/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:27/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:28/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:29/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:30/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:31/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:32/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:33/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:34/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:35/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:36/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:37/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:38/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:39/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:40/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:41/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:42/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:43/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:44/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:45/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:46/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:47/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:48/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:49/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:50/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:51/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:52/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:53/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:54/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:55/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:56/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:57/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:58/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:59/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:60/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:61/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:62/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:63/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:64/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:65/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:66/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:67/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:68/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:69/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:70/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:71/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:72/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:73/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:74/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:75/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:76/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:77/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:78/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:79/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:80/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:81/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:82/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:83/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:84/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:85/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:86/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:87/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:88/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:89/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:90/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:91/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:92/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:93/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:94/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:95/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:96/signed-little>>) -> I;
+get_signed_little(<<_:65/unit:8,I:97/signed-little>>) -> I.
cmp128(<<I:128>>, I) -> equal;
cmp128(_, _) -> not_equal.
-
+
+mixed_sizes(_Config) ->
+ mixed({345,42},
+ fun({A,B}) ->
+ <<A:9,1:1,B:6>>;
+ (<<A:9,_:1,B:6>>) ->
+ {A,B}
+ end),
+
+ mixed({27033,59991,16#c001cafe,12345,2},
+ fun({A,B,C,D,E}) ->
+ <<A:16,B:16,C:32,D:22,E:2>>;
+ (<<A:16,B:16,C:32,D:22,E:2>>) ->
+ {A,B,C,D,E}
+ end),
+
+ mixed({79,153,17555,50_000,777_000,36#hugebignumber,2222},
+ fun({A,B,C,D,E,F,G}) ->
+ <<A:7,B:8,C:15,0:3,D:17,E:23,F:88,G:13>>;
+ (<<A:7,B:8,C:15,_:3,D:17,E:23,F:88,G:13>>) ->
+ {A,B,C,D,E,F,G}
+ end),
+
+ mixed({16#123456789ABCDEF,13,36#hugenum,979},
+ fun({A,B,C,D}) ->
+ <<A:60,B:4,C:50,D:10>>;
+ (<<A:60,B:4,C:50,D:10>>) ->
+ {A,B,C,D}
+ end),
+
+ mixed({16#123456789ABCDEF,13,36#hugenum,979},
+ fun({A,B,C,D}) ->
+ <<A:60/little,B:4/little,C:50/little,D:10/little>>;
+ (<<A:60/little,B:4/little,C:50/little,D:10/little>>) ->
+ {A,B,C,D}
+ end),
+
+ mixed({15692284513449131826, 17798, 33798},
+ fun({A,B,C}) ->
+ <<A:64,B:15/little,C:16/little>>;
+ (<<A:64,B:15/little,C:16/little>>) ->
+ {A,B,C}
+ end),
+
+ mixed({15692344284519131826, 1779863, 13556268},
+ fun({A,B,C}) ->
+ <<A:64,B:23/little,C:24/little>>;
+ (<<A:64,B:23/little,C:24/little>>) ->
+ {A,B,C}
+ end),
+
+ mixed({15519169234428431825, 194086885, 2813274043},
+ fun({A,B,C}) ->
+ <<A:64,B:29/little,C:32/little>>;
+ (<<A:64,B:29/little,C:32/little>>) ->
+ {A,B,C}
+ end),
+
+ mixed({5,9,38759385,93},
+ fun({A,B,C,D}) ->
+ <<1:3,A:4,B:5,C:47,D:7>>;
+ (<<1:3,A:4,B:5,C:47,D:7>>) ->
+ {A,B,C,D}
+ end),
+
+ mixed({2022,8,22},
+ fun({A,B,C}) ->
+ <<A:16/little,B,C>>;
+ (<<A:16/little,B,C>>) ->
+ {A,B,C}
+ end),
+
+ mixed({2022,8,22},
+ fun({A,B,C}) ->
+ <<A:16/little,B,C>>;
+ (<<A:16/little,B,C>>) ->
+ _ = id(0),
+ {A,B,C}
+ end),
+ ok.
+
+mixed(Data, F) ->
+ Bin = F(Data),
+ Data = F(Bin),
+ true = is_bitstring(Bin).
+
signed_integer(Config) when is_list(Config) ->
{no_match,_} = sint(mkbin([])),
{no_match,_} = sint(mkbin([1,2,3])),
@@ -133,7 +728,7 @@ dynamic(Bin, S1, S2, A, B) ->
%% Extract integers at different alignments and of different sizes.
more_dynamic(Config) when is_list(Config) ->
- % Unsigned big-endian numbers.
+ %% Unsigned big-endian numbers.
Unsigned = fun(Bin, List, SkipBef, N) ->
SkipAft = 8*size(Bin) - N - SkipBef,
<<_:SkipBef,Int:N,_:SkipAft>> = Bin,
@@ -249,41 +844,46 @@ match_huge_int(Config) when is_list(Config) ->
%% because of insufficient memory.
{skip, "unoptimized code would use too much memory"};
bs_match_int_SUITE ->
- Sz = 1 bsl 27,
- Bin = <<0:Sz,13:8>>,
- skip_huge_int_1(Sz, Bin),
- 0 = match_huge_int_1(Sz, Bin),
-
- %% Test overflowing the size of an integer field.
- nomatch = overflow_huge_int_skip_32(Bin),
- case erlang:system_info(wordsize) of
- 4 ->
- nomatch = overflow_huge_int_32(Bin);
- 8 ->
- %% An attempt will be made to allocate heap space for
- %% the bignum (which will probably fail); only if the
- %% allocation succeeds will the matching fail because
- %% the binary is too small.
- ok
- end,
- nomatch = overflow_huge_int_skip_64(Bin),
- nomatch = overflow_huge_int_64(Bin),
-
- %% Test overflowing the size of an integer field using
- %% variables as sizes.
- Sizes = case erlang:system_info(wordsize) of
- 4 -> lists:seq(25, 32);
- 8 -> []
- end ++ lists:seq(50, 64),
- ok = overflow_huge_int_unit128(Bin, Sizes),
-
- %% GH-6701: [vm] crash with -emu_flavor emu:
- %% "no next heap size found: 18446744072702918678, offset 0"
- {'EXIT',{function_clause,_}} =
- (catch fun(<<X:2147483647/unit:98>>) -> X end(<<>>)),
- ok
+ do_match_huge_int();
+ bs_match_int_r25_SUITE ->
+ do_match_huge_int()
end.
+do_match_huge_int() ->
+ Sz = 1 bsl 27,
+ Bin = <<0:Sz,13:8>>,
+ skip_huge_int_1(Sz, Bin),
+ 0 = match_huge_int_1(Sz, Bin),
+
+ %% Test overflowing the size of an integer field.
+ nomatch = overflow_huge_int_skip_32(Bin),
+ case erlang:system_info(wordsize) of
+ 4 ->
+ nomatch = overflow_huge_int_32(Bin);
+ 8 ->
+ %% An attempt will be made to allocate heap space for
+ %% the bignum (which will probably fail); only if the
+ %% allocation succeeds will the matching fail because
+ %% the binary is too small.
+ ok
+ end,
+ nomatch = overflow_huge_int_skip_64(Bin),
+ nomatch = overflow_huge_int_64(Bin),
+
+ %% Test overflowing the size of an integer field using
+ %% variables as sizes.
+ Sizes = case erlang:system_info(wordsize) of
+ 4 -> lists:seq(25, 32);
+ 8 -> []
+ end ++ lists:seq(50, 64),
+ ok = overflow_huge_int_unit128(Bin, Sizes),
+
+ %% GH-6701: [vm] crash with -emu_flavor emu:
+ %% "no next heap size found: 18446744072702918678, offset 0"
+ {'EXIT',{function_clause,_}} =
+ (catch fun(<<X:2147483647/unit:98>>) -> X end(<<>>)),
+ ok.
+
overflow_huge_int_unit128(Bin, [Sz0|Sizes]) ->
Sz = id(1 bsl Sz0),
case Bin of
@@ -381,5 +981,18 @@ unaligned_32_bit_1(_) ->
unaligned_32_bit_verify([], 0) -> ok;
unaligned_32_bit_verify([4294967295|T], N) when N > 0 ->
unaligned_32_bit_verify(T, N-1).
-
+
+unit(_Config) ->
+ %% GH-6732. Would fail to match with no_ssa_opt or r25.
+ <<V1:5/integer-unit:8>> = id(<<16#cafebeef:5/integer-unit:8>>),
+ 16#cafebeef = id(V1),
+ <<V2:8/integer-unit:5>> = id(<<16#cafebeef:8/integer-unit:5>>),
+ 16#cafebeef = id(V2),
+
+ ok.
+
+%%%
+%%% Common utilities.
+%%%
+
id(I) -> I.
diff --git a/erts/emulator/test/bs_match_misc_SUITE.erl b/erts/emulator/test/bs_match_misc_SUITE.erl
index 65256dae35..83f4873dbe 100644
--- a/erts/emulator/test/bs_match_misc_SUITE.erl
+++ b/erts/emulator/test/bs_match_misc_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -24,7 +24,8 @@
kenneth/1,encode_binary/1,native/1,happi/1,
size_var/1,wiger/1,x0_context/1,huge_float_field/1,
writable_binary_matched/1,otp_7198/1,unordered_bindings/1,
- float_middle_endian/1,unsafe_get_binary_reuse/1, fp16/1]).
+ float_middle_endian/1,unsafe_get_binary_reuse/1, fp16/1,
+ bad_bs_match/1]).
-include_lib("common_test/include/ct.hrl").
@@ -37,7 +38,8 @@ all() ->
kenneth, encode_binary, native, happi, size_var, wiger,
x0_context, huge_float_field, writable_binary_matched,
otp_7198, unordered_bindings, float_middle_endian,
- unsafe_get_binary_reuse, fp16].
+ unsafe_get_binary_reuse, fp16,
+ bad_bs_match].
%% Test matching of bound variables.
@@ -579,8 +581,6 @@ unsafe_get_binary_reuse(Config) when is_list(Config) ->
ubgr_1(<<_CP/utf8, Rest/binary>>) -> id(Rest);
ubgr_1(_) -> false.
-id(I) -> I.
-
-define(FP16(EncodedInt, Float),
(fun(NlInt, NlFloat) ->
<<F1:16/float>> = <<NlInt:16>>,
@@ -638,3 +638,30 @@ fp16(_Config) ->
?FP16(16#4000, 2),
?FP16(16#4000, 2.0),
ok.
+
+bad_bs_match(_Config) ->
+ Lines = ["-module(bad_bs_match).",
+ "-export([f/1]).",
+ "f(<<X:16,_/binary>>) -> X."],
+ {Forms,_} =
+ lists:mapfoldl(fun(Line, L0) ->
+ {ok,Tokens,L} = erl_scan:string(Line ++ "\n", L0),
+ {ok,Form} = erl_parse:parse_form(Tokens),
+ {Form,L}
+ end, 1, Lines),
+ {ok,Mod,Beam0} = compile:forms(Forms, [binary]),
+
+ %% Introduce a non-existing sub command of the bs_match instruction.
+ Beam = binary:replace(Beam0, <<"ensure_at_least">>, <<"future_cool_cmd">>),
+ true = Beam0 =/= Beam,
+
+ %% There should be an error when attempting to load this BEAM
+ %% file, not a runtime crash.
+ {error,badfile} = code:load_binary(Mod, "", Beam),
+
+ ok.
+
+
+%%% Common utilities.
+id(I) -> I.
+
diff --git a/erts/emulator/test/bs_utf_SUITE.erl b/erts/emulator/test/bs_utf_SUITE.erl
index 68099c6f39..4a16ef44ab 100644
--- a/erts/emulator/test/bs_utf_SUITE.erl
+++ b/erts/emulator/test/bs_utf_SUITE.erl
@@ -20,11 +20,12 @@
-module(bs_utf_SUITE).
--export([all/0, suite/0,
+-export([all/0, suite/0, init_per_suite/1, end_per_suite/1,
utf8_roundtrip/1,utf16_roundtrip/1,utf32_roundtrip/1,
utf8_illegal_sequences/1,utf16_illegal_sequences/1,
utf32_illegal_sequences/1,
- bad_construction/1]).
+ bad_construction/1,
+ utf8_big_file/1]).
-include_lib("common_test/include/ct.hrl").
@@ -34,26 +35,93 @@ suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap, {minutes, 6}}].
-all() ->
+all() ->
[utf8_roundtrip, utf16_roundtrip, utf32_roundtrip,
utf8_illegal_sequences, utf16_illegal_sequences,
- utf32_illegal_sequences, bad_construction].
+ utf32_illegal_sequences, bad_construction,
+ utf8_big_file].
+
+init_per_suite(Config) ->
+ %% Make sure that calls to id/1 will hide types.
+ id(Config),
+ Config.
+
+end_per_suite(Config) ->
+ Config.
utf8_roundtrip(Config) when is_list(Config) ->
utf8_roundtrip(0, 16#D7FF),
utf8_roundtrip(16#E000, 16#10FFFF),
ok.
-utf8_roundtrip(First, Last) when First =< Last ->
- Bin = int_to_utf8(First),
+utf8_roundtrip(First, Last) ->
+ %% Hide types.
+ do_utf8_roundtrip(id(First), id(Last)).
+
+do_utf8_roundtrip(First, Last) when First =< Last ->
+ Bin = int_to_utf8(id(First)),
Bin = id(<<First/utf8>>),
Bin = id(<<(id(<<>>))/binary,First/utf8>>),
- Unaligned = id(<<3:2,First/utf8>>),
- <<_:2,Bin/binary>> = Unaligned,
+
+ <<0:7/unit:8,Bin/binary>> = id(<<0:7/unit:8,First/utf8>>),
+
+ %% Here a heap binary and a sub binary will be allocated. If the
+ %% write in the utf8 segment extends beyond the end of heap binary,
+ %% it will will overwrite the header for the sub binary.
+ <<-1:(64-9)/signed,Bin/binary>> = id(<<-1:(64-9),First/utf8>>),
+ <<-1:63/signed,Bin/binary>> = id(<<-1:63,First/utf8>>),
+
+ if
+ is_integer(First) ->
+ Bin = id(<<First/utf8>>)
+ end,
+
+ <<1:1,Bin/binary>> = id(<<1:1,First/utf8>>),
+ <<0:1,Bin/binary>> = id(<<0:1,First/utf8>>),
+ <<3:2,Bin/binary>> = id(<<3:2,First/utf8>>),
+ <<5:3,Bin/binary>> = id(<<5:3,First/utf8>>),
+ <<13:4,Bin/binary>> = id(<<13:4,First/utf8>>),
+ <<21:5,Bin/binary>> = id(<<21:5,First/utf8>>),
+ <<51:6,Bin/binary>> = id(<<51:6,First/utf8>>),
+ <<107:7,Bin/binary>> = id(<<107:7,First/utf8>>),
+
<<First/utf8>> = Bin,
<<First/utf8>> = make_unaligned(Bin),
- utf8_roundtrip(First+1, Last);
-utf8_roundtrip(_, _) -> ok.
+
+ %% Matching of utf8 segments use different code paths dependending
+ %% on the the number of bytes available in the binary. Make sure
+ %% we test both code paths.
+ <<First/utf8,0:64>> = id(<<Bin/binary,0:64>>),
+ <<0:3,First/utf8,0:64>> = id(<<0:3,Bin/binary,0:64>>),
+
+ unaligned_match(First),
+
+ Bin = id(<<First/utf8>>),
+ do_utf8_roundtrip(First+1, Last);
+do_utf8_roundtrip(_, _) -> ok.
+
+unaligned_match(Char) ->
+ %% We create a REFC binary so that we can create sub binaries
+ %% and control the contents just beyond the end of the binary.
+ _ = [begin
+ Bin = id(<<0:64/unit:8,0:Offset,Char/utf8>>),
+ <<0:64/unit:8,0:Offset,Char/utf8>> = Bin,
+ unaligned_match(Bin, Offset, 8)
+ end || Offset <- lists:seq(1, 7)],
+ ok.
+
+unaligned_match(_Bin, _Offset, 0) ->
+ ok;
+unaligned_match(Bin, Offset, N) ->
+ Size = bit_size(Bin),
+ <<Shorter:(Size-1)/bits,_:1>> = Bin,
+ try
+ <<0:64/unit:8,0:Offset,Char/utf8>> = Shorter,
+ ct:fail({short_binary_accepted,Shorter,Char})
+ catch
+ error:{badmatch,_} ->
+ unaligned_match(Shorter, Offset, N - 1)
+ end.
utf16_roundtrip(Config) when is_list(Config) ->
Big = fun utf16_big_roundtrip/1,
@@ -149,6 +217,7 @@ fail_range(Char, End) when Char =< End ->
{'EXIT',_} = (catch <<Char/utf8>>),
Bin = int_to_utf8(Char),
fail(Bin),
+ fail(<<Bin/binary,0:64>>),
fail_range(Char+1, End);
fail_range(_, _) -> ok.
@@ -201,24 +270,39 @@ overlong(Char, Last, NumBytes) when Char =< Last ->
overlong(_, _, _) -> ok.
overlong(Char, NumBytes) when NumBytes < 5 ->
- case int_to_utf8(Char, NumBytes) of
+ Bin = int_to_utf8(Char, NumBytes),
+ case <<(int_to_utf8(Char, NumBytes))/binary>> of
<<Char/utf8>>=Bin ->
ct:fail({illegal_encoding_accepted,Bin,Char});
<<OtherChar/utf8>>=Bin ->
ct:fail({illegal_encoding_accepted,Bin,Char,OtherChar});
_ -> ok
end,
+ case <<(int_to_utf8(Char, NumBytes))/binary,0:64>> of
+ <<Char/utf8,0:64>>=Bin2 ->
+ ct:fail({illegal_encoding_accepted,Bin2,Char});
+ <<OtherChar2/utf8,0:64>>=Bin2 ->
+ ct:fail({illegal_encoding_accepted,Bin2,Char,OtherChar2});
+ _ -> ok
+ end,
overlong(Char, NumBytes+1);
overlong(_, _) -> ok.
fail(Bin) ->
fail_1(Bin),
- fail_1(make_unaligned(Bin)).
+ fail_1(make_unaligned(Bin)),
+ BinExt = <<Bin/binary,0:64>>,
+ fail_2(BinExt),
+ fail_2(make_unaligned(BinExt)).
fail_1(<<Char/utf8>>=Bin) ->
ct:fail({illegal_encoding_accepted,Bin,Char});
fail_1(_) -> ok.
+fail_2(<<Char/utf8,0:64>>=Bin) ->
+ ct:fail({illegal_encoding_accepted,Bin,Char});
+fail_2(_) -> ok.
+
utf16_illegal_sequences(Config) when is_list(Config) ->
utf16_fail_range(16#10FFFF+1, 16#10FFFF+512), %Too large.
@@ -295,6 +379,9 @@ bad_construction(Config) when is_list(Config) ->
?FAIL(<<3.14/utf8>>),
?FAIL(<<3.1415/utf16>>),
?FAIL(<<3.1415/utf32>>),
+ {'EXIT',_} = (catch <<(id(3.14))/utf8>>),
+ {'EXIT',_} = (catch <<(id(3.1415))/utf16>>),
+ {'EXIT',_} = (catch <<(id(3.1415))/utf32>>),
?FAIL(<<(-1)/utf8>>),
?FAIL(<<(-1)/utf16>>),
@@ -305,9 +392,23 @@ bad_construction(Config) when is_list(Config) ->
?FAIL(<<16#D800/utf8>>),
?FAIL(<<16#D800/utf16>>),
?FAIL(<<16#D800/utf32>>),
+ {'EXIT',_} = (catch <<(id(16#D800))/utf8>>),
+ {'EXIT',_} = (catch <<(id(16#D800))/utf16>>),
+ {'EXIT',_} = (catch <<(id(16#D800))/utf32>>),
ok.
+utf8_big_file(Config) ->
+ DataDir = get_data_dir(Config),
+ {ok, Bin} = file:read_file(filename:join(DataDir, "NormalizationTest.txt")),
+ List = unicode:characters_to_list(Bin),
+ _ = [begin
+ io:format("~p\n", [Offset]),
+ <<0:Offset, Rest/binary>> = id(<<0:Offset, Bin/binary>>),
+ List = [Char || <<Char/utf8>> <= Rest]
+ end || Offset <- lists:seq(0, 8)],
+ ok.
+
%% This function intentionally allows construction of
%% UTF-8 sequence in illegal ranges.
int_to_utf8(I) when I =< 16#7F ->
@@ -384,4 +485,15 @@ evaluate(Str, Vars) ->
Result
end.
+%% Retrieve the original data directory for cloned modules.
+get_data_dir(Config) ->
+ Data = proplists:get_value(data_dir, Config),
+ Opts = [{return,list}],
+ Suffixes = ["_no_opt_SUITE",
+ "_r25_SUITE"],
+ lists:foldl(fun(Suffix, Acc) ->
+ Opts = [{return,list}],
+ re:replace(Acc, Suffix, "_SUITE", Opts)
+ end, Data, Suffixes).
+
id(I) -> I.
diff --git a/erts/emulator/test/bs_utf_SUITE_data/NormalizationTest.txt b/erts/emulator/test/bs_utf_SUITE_data/NormalizationTest.txt
new file mode 100644
index 0000000000..302c35f37c
--- /dev/null
+++ b/erts/emulator/test/bs_utf_SUITE_data/NormalizationTest.txt
@@ -0,0 +1,19047 @@
+# NormalizationTest-14.0.0.txt
+# Date: 2021-05-28, 21:49:12 GMT
+# © 2021 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use, see http://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+# For documentation, see http://www.unicode.org/reports/tr44/
+#
+# Normalization Test Suite
+# Format:
+#
+# Columns (c1, c2,...) are separated by semicolons
+# They have the following meaning:
+# source; NFC; NFD; NFKC; NFKD
+# Comments are indicated with hash marks
+# Each of the columns may have one or more code points.
+#
+# CONFORMANCE:
+# 1. The following invariants must be true for all conformant implementations
+#
+# NFC
+# c2 == toNFC(c1) == toNFC(c2) == toNFC(c3)
+# c4 == toNFC(c4) == toNFC(c5)
+#
+# NFD
+# c3 == toNFD(c1) == toNFD(c2) == toNFD(c3)
+# c5 == toNFD(c4) == toNFD(c5)
+#
+# NFKC
+# c4 == toNFKC(c1) == toNFKC(c2) == toNFKC(c3) == toNFKC(c4) == toNFKC(c5)
+#
+# NFKD
+# c5 == toNFKD(c1) == toNFKD(c2) == toNFKD(c3) == toNFKD(c4) == toNFKD(c5)
+#
+# 2. For every code point X assigned in this version of Unicode that is not specifically
+# listed in Part 1, the following invariants must be true for all conformant
+# implementations:
+#
+# X == toNFC(X) == toNFD(X) == toNFKC(X) == toNFKD(X)
+#
+@Part0 # Specific cases
+#
+1E0A;1E0A;0044 0307;1E0A;0044 0307; # (Ḋ; Ḋ; D◌̇; Ḋ; D◌̇; ) LATIN CAPITAL LETTER D WITH DOT ABOVE
+1E0C;1E0C;0044 0323;1E0C;0044 0323; # (Ḍ; Ḍ; D◌̣; Ḍ; D◌̣; ) LATIN CAPITAL LETTER D WITH DOT BELOW
+1E0A 0323;1E0C 0307;0044 0323 0307;1E0C 0307;0044 0323 0307; # (Ḋ◌̣; Ḍ◌̇; D◌̣◌̇; Ḍ◌̇; D◌̣◌̇; ) LATIN CAPITAL LETTER D WITH DOT ABOVE, COMBINING DOT BELOW
+1E0C 0307;1E0C 0307;0044 0323 0307;1E0C 0307;0044 0323 0307; # (Ḍ◌̇; Ḍ◌̇; D◌̣◌̇; Ḍ◌̇; D◌̣◌̇; ) LATIN CAPITAL LETTER D WITH DOT BELOW, COMBINING DOT ABOVE
+0044 0307 0323;1E0C 0307;0044 0323 0307;1E0C 0307;0044 0323 0307; # (D◌̇◌̣; Ḍ◌̇; D◌̣◌̇; Ḍ◌̇; D◌̣◌̇; ) LATIN CAPITAL LETTER D, COMBINING DOT ABOVE, COMBINING DOT BELOW
+0044 0323 0307;1E0C 0307;0044 0323 0307;1E0C 0307;0044 0323 0307; # (D◌̣◌̇; Ḍ◌̇; D◌̣◌̇; Ḍ◌̇; D◌̣◌̇; ) LATIN CAPITAL LETTER D, COMBINING DOT BELOW, COMBINING DOT ABOVE
+1E0A 031B;1E0A 031B;0044 031B 0307;1E0A 031B;0044 031B 0307; # (Ḋ◌̛; Ḋ◌̛; D◌̛◌̇; Ḋ◌̛; D◌̛◌̇; ) LATIN CAPITAL LETTER D WITH DOT ABOVE, COMBINING HORN
+1E0C 031B;1E0C 031B;0044 031B 0323;1E0C 031B;0044 031B 0323; # (Ḍ◌̛; Ḍ◌̛; D◌̛◌̣; Ḍ◌̛; D◌̛◌̣; ) LATIN CAPITAL LETTER D WITH DOT BELOW, COMBINING HORN
+1E0A 031B 0323;1E0C 031B 0307;0044 031B 0323 0307;1E0C 031B 0307;0044 031B 0323 0307; # (Ḋ◌̛◌̣; Ḍ◌̛◌̇; D◌̛◌̣◌̇; Ḍ◌̛◌̇; D◌̛◌̣◌̇; ) LATIN CAPITAL LETTER D WITH DOT ABOVE, COMBINING HORN, COMBINING DOT BELOW
+1E0C 031B 0307;1E0C 031B 0307;0044 031B 0323 0307;1E0C 031B 0307;0044 031B 0323 0307; # (Ḍ◌̛◌̇; Ḍ◌̛◌̇; D◌̛◌̣◌̇; Ḍ◌̛◌̇; D◌̛◌̣◌̇; ) LATIN CAPITAL LETTER D WITH DOT BELOW, COMBINING HORN, COMBINING DOT ABOVE
+0044 031B 0307 0323;1E0C 031B 0307;0044 031B 0323 0307;1E0C 031B 0307;0044 031B 0323 0307; # (D◌̛◌̇◌̣; Ḍ◌̛◌̇; D◌̛◌̣◌̇; Ḍ◌̛◌̇; D◌̛◌̣◌̇; ) LATIN CAPITAL LETTER D, COMBINING HORN, COMBINING DOT ABOVE, COMBINING DOT BELOW
+0044 031B 0323 0307;1E0C 031B 0307;0044 031B 0323 0307;1E0C 031B 0307;0044 031B 0323 0307; # (D◌̛◌̣◌̇; Ḍ◌̛◌̇; D◌̛◌̣◌̇; Ḍ◌̛◌̇; D◌̛◌̣◌̇; ) LATIN CAPITAL LETTER D, COMBINING HORN, COMBINING DOT BELOW, COMBINING DOT ABOVE
+00C8;00C8;0045 0300;00C8;0045 0300; # (È; È; E◌̀; È; E◌̀; ) LATIN CAPITAL LETTER E WITH GRAVE
+0112;0112;0045 0304;0112;0045 0304; # (Ē; Ē; E◌̄; Ē; E◌̄; ) LATIN CAPITAL LETTER E WITH MACRON
+0045 0300;00C8;0045 0300;00C8;0045 0300; # (E◌̀; È; E◌̀; È; E◌̀; ) LATIN CAPITAL LETTER E, COMBINING GRAVE ACCENT
+0045 0304;0112;0045 0304;0112;0045 0304; # (E◌̄; Ē; E◌̄; Ē; E◌̄; ) LATIN CAPITAL LETTER E, COMBINING MACRON
+1E14;1E14;0045 0304 0300;1E14;0045 0304 0300; # (Ḕ; Ḕ; E◌̄◌̀; Ḕ; E◌̄◌̀; ) LATIN CAPITAL LETTER E WITH MACRON AND GRAVE
+0112 0300;1E14;0045 0304 0300;1E14;0045 0304 0300; # (Ē◌̀; Ḕ; E◌̄◌̀; Ḕ; E◌̄◌̀; ) LATIN CAPITAL LETTER E WITH MACRON, COMBINING GRAVE ACCENT
+1E14 0304;1E14 0304;0045 0304 0300 0304;1E14 0304;0045 0304 0300 0304; # (Ḕ◌̄; Ḕ◌̄; E◌̄◌̀◌̄; Ḕ◌̄; E◌̄◌̀◌̄; ) LATIN CAPITAL LETTER E WITH MACRON AND GRAVE, COMBINING MACRON
+0045 0304 0300;1E14;0045 0304 0300;1E14;0045 0304 0300; # (E◌̄◌̀; Ḕ; E◌̄◌̀; Ḕ; E◌̄◌̀; ) LATIN CAPITAL LETTER E, COMBINING MACRON, COMBINING GRAVE ACCENT
+0045 0300 0304;00C8 0304;0045 0300 0304;00C8 0304;0045 0300 0304; # (E◌̀◌̄; È◌̄; E◌̀◌̄; È◌̄; E◌̀◌̄; ) LATIN CAPITAL LETTER E, COMBINING GRAVE ACCENT, COMBINING MACRON
+05B8 05B9 05B1 0591 05C3 05B0 05AC 059F;05B1 05B8 05B9 0591 05C3 05B0 05AC 059F;05B1 05B8 05B9 0591 05C3 05B0 05AC 059F;05B1 05B8 05B9 0591 05C3 05B0 05AC 059F;05B1 05B8 05B9 0591 05C3 05B0 05AC 059F; # (◌ָ◌ֹ◌ֱ◌֑׃◌ְ◌֬◌֟; ◌ֱ◌ָ◌ֹ◌֑׃◌ְ◌֬◌֟; ◌ֱ◌ָ◌ֹ◌֑׃◌ְ◌֬◌֟; ◌ֱ◌ָ◌ֹ◌֑׃◌ְ◌֬◌֟; ◌ֱ◌ָ◌ֹ◌֑׃◌ְ◌֬◌֟; ) HEBREW POINT QAMATS, HEBREW POINT HOLAM, HEBREW POINT HATAF SEGOL, HEBREW ACCENT ETNAHTA, HEBREW PUNCTUATION SOF PASUQ, HEBREW POINT SHEVA, HEBREW ACCENT ILUY, HEBREW ACCENT QARNEY PARA
+0592 05B7 05BC 05A5 05B0 05C0 05C4 05AD;05B0 05B7 05BC 05A5 0592 05C0 05AD 05C4;05B0 05B7 05BC 05A5 0592 05C0 05AD 05C4;05B0 05B7 05BC 05A5 0592 05C0 05AD 05C4;05B0 05B7 05BC 05A5 0592 05C0 05AD 05C4; # (◌֒◌ַ◌ּ◌֥◌ְ׀◌ׄ◌֭; ◌ְ◌ַ◌ּ◌֥◌֒׀◌֭◌ׄ; ◌ְ◌ַ◌ּ◌֥◌֒׀◌֭◌ׄ; ◌ְ◌ַ◌ּ◌֥◌֒׀◌֭◌ׄ; ◌ְ◌ַ◌ּ◌֥◌֒׀◌֭◌ׄ; ) HEBREW ACCENT SEGOL, HEBREW POINT PATAH, HEBREW POINT DAGESH OR MAPIQ, HEBREW ACCENT MERKHA, HEBREW POINT SHEVA, HEBREW PUNCTUATION PASEQ, HEBREW MARK UPPER DOT, HEBREW ACCENT DEHI
+1100 AC00 11A8;1100 AC01;1100 1100 1161 11A8;1100 AC01;1100 1100 1161 11A8; # (ᄀ각; á„€ê°; ᄀ각; á„€ê°; ᄀ각; ) HANGUL CHOSEONG KIYEOK, HANGUL SYLLABLE GA, HANGUL JONGSEONG KIYEOK
+1100 AC00 11A8 11A8;1100 AC01 11A8;1100 1100 1161 11A8 11A8;1100 AC01 11A8;1100 1100 1161 11A8 11A8; # (ᄀ각ᆨ; á„€ê°á†¨; ᄀ각ᆨ; á„€ê°á†¨; ᄀ각ᆨ; ) HANGUL CHOSEONG KIYEOK, HANGUL SYLLABLE GA, HANGUL JONGSEONG KIYEOK, HANGUL JONGSEONG KIYEOK
+#
+@Part1 # Character by character test
+# All characters not explicitly occurring in c1 of Part 1 have identical NFC, D, KC, KD forms.
+#
+00A0;00A0;00A0;0020;0020; # ( ;  ;  ; ; ; ) NO-BREAK SPACE
+00A8;00A8;00A8;0020 0308;0020 0308; # (¨; ¨; ¨; ◌̈; ◌̈; ) DIAERESIS
+00AA;00AA;00AA;0061;0061; # (ª; ª; ª; a; a; ) FEMININE ORDINAL INDICATOR
+00AF;00AF;00AF;0020 0304;0020 0304; # (¯; ¯; ¯; ◌̄; ◌̄; ) MACRON
+00B2;00B2;00B2;0032;0032; # (²; ²; ²; 2; 2; ) SUPERSCRIPT TWO
+00B3;00B3;00B3;0033;0033; # (³; ³; ³; 3; 3; ) SUPERSCRIPT THREE
+00B4;00B4;00B4;0020 0301;0020 0301; # (´; ´; ´; â—ŒÌ; â—ŒÌ; ) ACUTE ACCENT
+00B5;00B5;00B5;03BC;03BC; # (µ; µ; µ; μ; μ; ) MICRO SIGN
+00B8;00B8;00B8;0020 0327;0020 0327; # (¸; ¸; ¸; ◌̧; ◌̧; ) CEDILLA
+00B9;00B9;00B9;0031;0031; # (¹; ¹; ¹; 1; 1; ) SUPERSCRIPT ONE
+00BA;00BA;00BA;006F;006F; # (º; º; º; o; o; ) MASCULINE ORDINAL INDICATOR
+00BC;00BC;00BC;0031 2044 0034;0031 2044 0034; # (¼; ¼; ¼; 1â„4; 1â„4; ) VULGAR FRACTION ONE QUARTER
+00BD;00BD;00BD;0031 2044 0032;0031 2044 0032; # (½; ½; ½; 1â„2; 1â„2; ) VULGAR FRACTION ONE HALF
+00BE;00BE;00BE;0033 2044 0034;0033 2044 0034; # (¾; ¾; ¾; 3â„4; 3â„4; ) VULGAR FRACTION THREE QUARTERS
+00C0;00C0;0041 0300;00C0;0041 0300; # (À; À; A◌̀; À; A◌̀; ) LATIN CAPITAL LETTER A WITH GRAVE
+00C1;00C1;0041 0301;00C1;0041 0301; # (Ã; Ã; Aâ—ŒÌ; Ã; Aâ—ŒÌ; ) LATIN CAPITAL LETTER A WITH ACUTE
+00C2;00C2;0041 0302;00C2;0041 0302; # (Â; Â; A◌̂; Â; A◌̂; ) LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+00C3;00C3;0041 0303;00C3;0041 0303; # (Ã; Ã; A◌̃; Ã; A◌̃; ) LATIN CAPITAL LETTER A WITH TILDE
+00C4;00C4;0041 0308;00C4;0041 0308; # (Ä; Ä; A◌̈; Ä; A◌̈; ) LATIN CAPITAL LETTER A WITH DIAERESIS
+00C5;00C5;0041 030A;00C5;0041 030A; # (Å; Å; A◌̊; Å; A◌̊; ) LATIN CAPITAL LETTER A WITH RING ABOVE
+00C7;00C7;0043 0327;00C7;0043 0327; # (Ç; Ç; C◌̧; Ç; C◌̧; ) LATIN CAPITAL LETTER C WITH CEDILLA
+00C8;00C8;0045 0300;00C8;0045 0300; # (È; È; E◌̀; È; E◌̀; ) LATIN CAPITAL LETTER E WITH GRAVE
+00C9;00C9;0045 0301;00C9;0045 0301; # (É; É; Eâ—ŒÌ; É; Eâ—ŒÌ; ) LATIN CAPITAL LETTER E WITH ACUTE
+00CA;00CA;0045 0302;00CA;0045 0302; # (Ê; Ê; E◌̂; Ê; E◌̂; ) LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+00CB;00CB;0045 0308;00CB;0045 0308; # (Ë; Ë; E◌̈; Ë; E◌̈; ) LATIN CAPITAL LETTER E WITH DIAERESIS
+00CC;00CC;0049 0300;00CC;0049 0300; # (Ì; Ì; I◌̀; Ì; I◌̀; ) LATIN CAPITAL LETTER I WITH GRAVE
+00CD;00CD;0049 0301;00CD;0049 0301; # (Ã; Ã; Iâ—ŒÌ; Ã; Iâ—ŒÌ; ) LATIN CAPITAL LETTER I WITH ACUTE
+00CE;00CE;0049 0302;00CE;0049 0302; # (Î; Î; I◌̂; Î; I◌̂; ) LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+00CF;00CF;0049 0308;00CF;0049 0308; # (Ã; Ã; I◌̈; Ã; I◌̈; ) LATIN CAPITAL LETTER I WITH DIAERESIS
+00D1;00D1;004E 0303;00D1;004E 0303; # (Ñ; Ñ; N◌̃; Ñ; N◌̃; ) LATIN CAPITAL LETTER N WITH TILDE
+00D2;00D2;004F 0300;00D2;004F 0300; # (Ò; Ò; O◌̀; Ò; O◌̀; ) LATIN CAPITAL LETTER O WITH GRAVE
+00D3;00D3;004F 0301;00D3;004F 0301; # (Ó; Ó; Oâ—ŒÌ; Ó; Oâ—ŒÌ; ) LATIN CAPITAL LETTER O WITH ACUTE
+00D4;00D4;004F 0302;00D4;004F 0302; # (Ô; Ô; O◌̂; Ô; O◌̂; ) LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+00D5;00D5;004F 0303;00D5;004F 0303; # (Õ; Õ; O◌̃; Õ; O◌̃; ) LATIN CAPITAL LETTER O WITH TILDE
+00D6;00D6;004F 0308;00D6;004F 0308; # (Ö; Ö; O◌̈; Ö; O◌̈; ) LATIN CAPITAL LETTER O WITH DIAERESIS
+00D9;00D9;0055 0300;00D9;0055 0300; # (Ù; Ù; U◌̀; Ù; U◌̀; ) LATIN CAPITAL LETTER U WITH GRAVE
+00DA;00DA;0055 0301;00DA;0055 0301; # (Ú; Ú; Uâ—ŒÌ; Ú; Uâ—ŒÌ; ) LATIN CAPITAL LETTER U WITH ACUTE
+00DB;00DB;0055 0302;00DB;0055 0302; # (Û; Û; U◌̂; Û; U◌̂; ) LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+00DC;00DC;0055 0308;00DC;0055 0308; # (Ü; Ü; U◌̈; Ü; U◌̈; ) LATIN CAPITAL LETTER U WITH DIAERESIS
+00DD;00DD;0059 0301;00DD;0059 0301; # (Ã; Ã; Yâ—ŒÌ; Ã; Yâ—ŒÌ; ) LATIN CAPITAL LETTER Y WITH ACUTE
+00E0;00E0;0061 0300;00E0;0061 0300; # (à; à; a◌̀; à; a◌̀; ) LATIN SMALL LETTER A WITH GRAVE
+00E1;00E1;0061 0301;00E1;0061 0301; # (á; á; aâ—ŒÌ; á; aâ—ŒÌ; ) LATIN SMALL LETTER A WITH ACUTE
+00E2;00E2;0061 0302;00E2;0061 0302; # (â; â; a◌̂; â; a◌̂; ) LATIN SMALL LETTER A WITH CIRCUMFLEX
+00E3;00E3;0061 0303;00E3;0061 0303; # (ã; ã; a◌̃; ã; a◌̃; ) LATIN SMALL LETTER A WITH TILDE
+00E4;00E4;0061 0308;00E4;0061 0308; # (ä; ä; a◌̈; ä; a◌̈; ) LATIN SMALL LETTER A WITH DIAERESIS
+00E5;00E5;0061 030A;00E5;0061 030A; # (å; å; a◌̊; å; a◌̊; ) LATIN SMALL LETTER A WITH RING ABOVE
+00E7;00E7;0063 0327;00E7;0063 0327; # (ç; ç; c◌̧; ç; c◌̧; ) LATIN SMALL LETTER C WITH CEDILLA
+00E8;00E8;0065 0300;00E8;0065 0300; # (è; è; e◌̀; è; e◌̀; ) LATIN SMALL LETTER E WITH GRAVE
+00E9;00E9;0065 0301;00E9;0065 0301; # (é; é; eâ—ŒÌ; é; eâ—ŒÌ; ) LATIN SMALL LETTER E WITH ACUTE
+00EA;00EA;0065 0302;00EA;0065 0302; # (ê; ê; e◌̂; ê; e◌̂; ) LATIN SMALL LETTER E WITH CIRCUMFLEX
+00EB;00EB;0065 0308;00EB;0065 0308; # (ë; ë; e◌̈; ë; e◌̈; ) LATIN SMALL LETTER E WITH DIAERESIS
+00EC;00EC;0069 0300;00EC;0069 0300; # (ì; ì; i◌̀; ì; i◌̀; ) LATIN SMALL LETTER I WITH GRAVE
+00ED;00ED;0069 0301;00ED;0069 0301; # (í; í; iâ—ŒÌ; í; iâ—ŒÌ; ) LATIN SMALL LETTER I WITH ACUTE
+00EE;00EE;0069 0302;00EE;0069 0302; # (î; î; i◌̂; î; i◌̂; ) LATIN SMALL LETTER I WITH CIRCUMFLEX
+00EF;00EF;0069 0308;00EF;0069 0308; # (ï; ï; i◌̈; ï; i◌̈; ) LATIN SMALL LETTER I WITH DIAERESIS
+00F1;00F1;006E 0303;00F1;006E 0303; # (ñ; ñ; n◌̃; ñ; n◌̃; ) LATIN SMALL LETTER N WITH TILDE
+00F2;00F2;006F 0300;00F2;006F 0300; # (ò; ò; o◌̀; ò; o◌̀; ) LATIN SMALL LETTER O WITH GRAVE
+00F3;00F3;006F 0301;00F3;006F 0301; # (ó; ó; oâ—ŒÌ; ó; oâ—ŒÌ; ) LATIN SMALL LETTER O WITH ACUTE
+00F4;00F4;006F 0302;00F4;006F 0302; # (ô; ô; o◌̂; ô; o◌̂; ) LATIN SMALL LETTER O WITH CIRCUMFLEX
+00F5;00F5;006F 0303;00F5;006F 0303; # (õ; õ; o◌̃; õ; o◌̃; ) LATIN SMALL LETTER O WITH TILDE
+00F6;00F6;006F 0308;00F6;006F 0308; # (ö; ö; o◌̈; ö; o◌̈; ) LATIN SMALL LETTER O WITH DIAERESIS
+00F9;00F9;0075 0300;00F9;0075 0300; # (ù; ù; u◌̀; ù; u◌̀; ) LATIN SMALL LETTER U WITH GRAVE
+00FA;00FA;0075 0301;00FA;0075 0301; # (ú; ú; uâ—ŒÌ; ú; uâ—ŒÌ; ) LATIN SMALL LETTER U WITH ACUTE
+00FB;00FB;0075 0302;00FB;0075 0302; # (û; û; u◌̂; û; u◌̂; ) LATIN SMALL LETTER U WITH CIRCUMFLEX
+00FC;00FC;0075 0308;00FC;0075 0308; # (ü; ü; u◌̈; ü; u◌̈; ) LATIN SMALL LETTER U WITH DIAERESIS
+00FD;00FD;0079 0301;00FD;0079 0301; # (ý; ý; yâ—ŒÌ; ý; yâ—ŒÌ; ) LATIN SMALL LETTER Y WITH ACUTE
+00FF;00FF;0079 0308;00FF;0079 0308; # (ÿ; ÿ; y◌̈; ÿ; y◌̈; ) LATIN SMALL LETTER Y WITH DIAERESIS
+0100;0100;0041 0304;0100;0041 0304; # (Ā; Ā; A◌̄; Ā; A◌̄; ) LATIN CAPITAL LETTER A WITH MACRON
+0101;0101;0061 0304;0101;0061 0304; # (Ä; Ä; a◌̄; Ä; a◌̄; ) LATIN SMALL LETTER A WITH MACRON
+0102;0102;0041 0306;0102;0041 0306; # (Ă; Ă; A◌̆; Ă; A◌̆; ) LATIN CAPITAL LETTER A WITH BREVE
+0103;0103;0061 0306;0103;0061 0306; # (ă; ă; a◌̆; ă; a◌̆; ) LATIN SMALL LETTER A WITH BREVE
+0104;0104;0041 0328;0104;0041 0328; # (Ą; Ą; A◌̨; Ą; A◌̨; ) LATIN CAPITAL LETTER A WITH OGONEK
+0105;0105;0061 0328;0105;0061 0328; # (ą; ą; a◌̨; ą; a◌̨; ) LATIN SMALL LETTER A WITH OGONEK
+0106;0106;0043 0301;0106;0043 0301; # (Ć; Ć; Câ—ŒÌ; Ć; Câ—ŒÌ; ) LATIN CAPITAL LETTER C WITH ACUTE
+0107;0107;0063 0301;0107;0063 0301; # (ć; ć; câ—ŒÌ; ć; câ—ŒÌ; ) LATIN SMALL LETTER C WITH ACUTE
+0108;0108;0043 0302;0108;0043 0302; # (Ĉ; Ĉ; C◌̂; Ĉ; C◌̂; ) LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+0109;0109;0063 0302;0109;0063 0302; # (ĉ; ĉ; c◌̂; ĉ; c◌̂; ) LATIN SMALL LETTER C WITH CIRCUMFLEX
+010A;010A;0043 0307;010A;0043 0307; # (Ċ; Ċ; C◌̇; Ċ; C◌̇; ) LATIN CAPITAL LETTER C WITH DOT ABOVE
+010B;010B;0063 0307;010B;0063 0307; # (ċ; ċ; c◌̇; ċ; c◌̇; ) LATIN SMALL LETTER C WITH DOT ABOVE
+010C;010C;0043 030C;010C;0043 030C; # (Č; Č; C◌̌; Č; C◌̌; ) LATIN CAPITAL LETTER C WITH CARON
+010D;010D;0063 030C;010D;0063 030C; # (Ä; Ä; c◌̌; Ä; c◌̌; ) LATIN SMALL LETTER C WITH CARON
+010E;010E;0044 030C;010E;0044 030C; # (Ď; Ď; D◌̌; Ď; D◌̌; ) LATIN CAPITAL LETTER D WITH CARON
+010F;010F;0064 030C;010F;0064 030C; # (Ä; Ä; d◌̌; Ä; d◌̌; ) LATIN SMALL LETTER D WITH CARON
+0112;0112;0045 0304;0112;0045 0304; # (Ē; Ē; E◌̄; Ē; E◌̄; ) LATIN CAPITAL LETTER E WITH MACRON
+0113;0113;0065 0304;0113;0065 0304; # (ē; ē; e◌̄; ē; e◌̄; ) LATIN SMALL LETTER E WITH MACRON
+0114;0114;0045 0306;0114;0045 0306; # (Ĕ; Ĕ; E◌̆; Ĕ; E◌̆; ) LATIN CAPITAL LETTER E WITH BREVE
+0115;0115;0065 0306;0115;0065 0306; # (ĕ; ĕ; e◌̆; ĕ; e◌̆; ) LATIN SMALL LETTER E WITH BREVE
+0116;0116;0045 0307;0116;0045 0307; # (Ė; Ė; E◌̇; Ė; E◌̇; ) LATIN CAPITAL LETTER E WITH DOT ABOVE
+0117;0117;0065 0307;0117;0065 0307; # (ė; ė; e◌̇; ė; e◌̇; ) LATIN SMALL LETTER E WITH DOT ABOVE
+0118;0118;0045 0328;0118;0045 0328; # (Ę; Ę; E◌̨; Ę; E◌̨; ) LATIN CAPITAL LETTER E WITH OGONEK
+0119;0119;0065 0328;0119;0065 0328; # (ę; ę; e◌̨; ę; e◌̨; ) LATIN SMALL LETTER E WITH OGONEK
+011A;011A;0045 030C;011A;0045 030C; # (Ě; Ě; E◌̌; Ě; E◌̌; ) LATIN CAPITAL LETTER E WITH CARON
+011B;011B;0065 030C;011B;0065 030C; # (ě; ě; e◌̌; ě; e◌̌; ) LATIN SMALL LETTER E WITH CARON
+011C;011C;0047 0302;011C;0047 0302; # (Ĝ; Ĝ; G◌̂; Ĝ; G◌̂; ) LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+011D;011D;0067 0302;011D;0067 0302; # (Ä; Ä; g◌̂; Ä; g◌̂; ) LATIN SMALL LETTER G WITH CIRCUMFLEX
+011E;011E;0047 0306;011E;0047 0306; # (Ğ; Ğ; G◌̆; Ğ; G◌̆; ) LATIN CAPITAL LETTER G WITH BREVE
+011F;011F;0067 0306;011F;0067 0306; # (ğ; ğ; g◌̆; ğ; g◌̆; ) LATIN SMALL LETTER G WITH BREVE
+0120;0120;0047 0307;0120;0047 0307; # (Ġ; Ġ; G◌̇; Ġ; G◌̇; ) LATIN CAPITAL LETTER G WITH DOT ABOVE
+0121;0121;0067 0307;0121;0067 0307; # (ġ; ġ; g◌̇; ġ; g◌̇; ) LATIN SMALL LETTER G WITH DOT ABOVE
+0122;0122;0047 0327;0122;0047 0327; # (Ģ; Ģ; G◌̧; Ģ; G◌̧; ) LATIN CAPITAL LETTER G WITH CEDILLA
+0123;0123;0067 0327;0123;0067 0327; # (ģ; ģ; g◌̧; ģ; g◌̧; ) LATIN SMALL LETTER G WITH CEDILLA
+0124;0124;0048 0302;0124;0048 0302; # (Ĥ; Ĥ; H◌̂; Ĥ; H◌̂; ) LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+0125;0125;0068 0302;0125;0068 0302; # (ĥ; ĥ; h◌̂; ĥ; h◌̂; ) LATIN SMALL LETTER H WITH CIRCUMFLEX
+0128;0128;0049 0303;0128;0049 0303; # (Ĩ; Ĩ; I◌̃; Ĩ; I◌̃; ) LATIN CAPITAL LETTER I WITH TILDE
+0129;0129;0069 0303;0129;0069 0303; # (ĩ; ĩ; i◌̃; ĩ; i◌̃; ) LATIN SMALL LETTER I WITH TILDE
+012A;012A;0049 0304;012A;0049 0304; # (Ī; Ī; I◌̄; Ī; I◌̄; ) LATIN CAPITAL LETTER I WITH MACRON
+012B;012B;0069 0304;012B;0069 0304; # (ī; ī; i◌̄; ī; i◌̄; ) LATIN SMALL LETTER I WITH MACRON
+012C;012C;0049 0306;012C;0049 0306; # (Ĭ; Ĭ; I◌̆; Ĭ; I◌̆; ) LATIN CAPITAL LETTER I WITH BREVE
+012D;012D;0069 0306;012D;0069 0306; # (ĭ; ĭ; i◌̆; ĭ; i◌̆; ) LATIN SMALL LETTER I WITH BREVE
+012E;012E;0049 0328;012E;0049 0328; # (Į; Į; I◌̨; Į; I◌̨; ) LATIN CAPITAL LETTER I WITH OGONEK
+012F;012F;0069 0328;012F;0069 0328; # (į; į; i◌̨; į; i◌̨; ) LATIN SMALL LETTER I WITH OGONEK
+0130;0130;0049 0307;0130;0049 0307; # (İ; İ; I◌̇; İ; I◌̇; ) LATIN CAPITAL LETTER I WITH DOT ABOVE
+0132;0132;0132;0049 004A;0049 004A; # (IJ; IJ; IJ; IJ; IJ; ) LATIN CAPITAL LIGATURE IJ
+0133;0133;0133;0069 006A;0069 006A; # (ij; ij; ij; ij; ij; ) LATIN SMALL LIGATURE IJ
+0134;0134;004A 0302;0134;004A 0302; # (Ĵ; Ĵ; J◌̂; Ĵ; J◌̂; ) LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+0135;0135;006A 0302;0135;006A 0302; # (ĵ; ĵ; j◌̂; ĵ; j◌̂; ) LATIN SMALL LETTER J WITH CIRCUMFLEX
+0136;0136;004B 0327;0136;004B 0327; # (Ķ; Ķ; K◌̧; Ķ; K◌̧; ) LATIN CAPITAL LETTER K WITH CEDILLA
+0137;0137;006B 0327;0137;006B 0327; # (ķ; ķ; k◌̧; ķ; k◌̧; ) LATIN SMALL LETTER K WITH CEDILLA
+0139;0139;004C 0301;0139;004C 0301; # (Ĺ; Ĺ; Lâ—ŒÌ; Ĺ; Lâ—ŒÌ; ) LATIN CAPITAL LETTER L WITH ACUTE
+013A;013A;006C 0301;013A;006C 0301; # (ĺ; ĺ; lâ—ŒÌ; ĺ; lâ—ŒÌ; ) LATIN SMALL LETTER L WITH ACUTE
+013B;013B;004C 0327;013B;004C 0327; # (Ļ; Ļ; L◌̧; Ļ; L◌̧; ) LATIN CAPITAL LETTER L WITH CEDILLA
+013C;013C;006C 0327;013C;006C 0327; # (ļ; ļ; l◌̧; ļ; l◌̧; ) LATIN SMALL LETTER L WITH CEDILLA
+013D;013D;004C 030C;013D;004C 030C; # (Ľ; Ľ; L◌̌; Ľ; L◌̌; ) LATIN CAPITAL LETTER L WITH CARON
+013E;013E;006C 030C;013E;006C 030C; # (ľ; ľ; l◌̌; ľ; l◌̌; ) LATIN SMALL LETTER L WITH CARON
+013F;013F;013F;004C 00B7;004C 00B7; # (Ŀ; Ŀ; Ŀ; L·; L·; ) LATIN CAPITAL LETTER L WITH MIDDLE DOT
+0140;0140;0140;006C 00B7;006C 00B7; # (ŀ; ŀ; ŀ; l·; l·; ) LATIN SMALL LETTER L WITH MIDDLE DOT
+0143;0143;004E 0301;0143;004E 0301; # (Ń; Ń; Nâ—ŒÌ; Ń; Nâ—ŒÌ; ) LATIN CAPITAL LETTER N WITH ACUTE
+0144;0144;006E 0301;0144;006E 0301; # (Å„; Å„; nâ—ŒÌ; Å„; nâ—ŒÌ; ) LATIN SMALL LETTER N WITH ACUTE
+0145;0145;004E 0327;0145;004E 0327; # (Ņ; Ņ; N◌̧; Ņ; N◌̧; ) LATIN CAPITAL LETTER N WITH CEDILLA
+0146;0146;006E 0327;0146;006E 0327; # (ņ; ņ; n◌̧; ņ; n◌̧; ) LATIN SMALL LETTER N WITH CEDILLA
+0147;0147;004E 030C;0147;004E 030C; # (Ň; Ň; N◌̌; Ň; N◌̌; ) LATIN CAPITAL LETTER N WITH CARON
+0148;0148;006E 030C;0148;006E 030C; # (ň; ň; n◌̌; ň; n◌̌; ) LATIN SMALL LETTER N WITH CARON
+0149;0149;0149;02BC 006E;02BC 006E; # (ʼn; ʼn; ʼn; ʼn; ʼn; ) LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+014C;014C;004F 0304;014C;004F 0304; # (Ō; Ō; O◌̄; Ō; O◌̄; ) LATIN CAPITAL LETTER O WITH MACRON
+014D;014D;006F 0304;014D;006F 0304; # (Å; Å; o◌̄; Å; o◌̄; ) LATIN SMALL LETTER O WITH MACRON
+014E;014E;004F 0306;014E;004F 0306; # (Ŏ; Ŏ; O◌̆; Ŏ; O◌̆; ) LATIN CAPITAL LETTER O WITH BREVE
+014F;014F;006F 0306;014F;006F 0306; # (Å; Å; o◌̆; Å; o◌̆; ) LATIN SMALL LETTER O WITH BREVE
+0150;0150;004F 030B;0150;004F 030B; # (Å; Å; O◌̋; Å; O◌̋; ) LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0151;0151;006F 030B;0151;006F 030B; # (ő; ő; o◌̋; ő; o◌̋; ) LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0154;0154;0052 0301;0154;0052 0301; # (Å”; Å”; Râ—ŒÌ; Å”; Râ—ŒÌ; ) LATIN CAPITAL LETTER R WITH ACUTE
+0155;0155;0072 0301;0155;0072 0301; # (Å•; Å•; râ—ŒÌ; Å•; râ—ŒÌ; ) LATIN SMALL LETTER R WITH ACUTE
+0156;0156;0052 0327;0156;0052 0327; # (Ŗ; Ŗ; R◌̧; Ŗ; R◌̧; ) LATIN CAPITAL LETTER R WITH CEDILLA
+0157;0157;0072 0327;0157;0072 0327; # (ŗ; ŗ; r◌̧; ŗ; r◌̧; ) LATIN SMALL LETTER R WITH CEDILLA
+0158;0158;0052 030C;0158;0052 030C; # (Ř; Ř; R◌̌; Ř; R◌̌; ) LATIN CAPITAL LETTER R WITH CARON
+0159;0159;0072 030C;0159;0072 030C; # (ř; ř; r◌̌; ř; r◌̌; ) LATIN SMALL LETTER R WITH CARON
+015A;015A;0053 0301;015A;0053 0301; # (Åš; Åš; Sâ—ŒÌ; Åš; Sâ—ŒÌ; ) LATIN CAPITAL LETTER S WITH ACUTE
+015B;015B;0073 0301;015B;0073 0301; # (Å›; Å›; sâ—ŒÌ; Å›; sâ—ŒÌ; ) LATIN SMALL LETTER S WITH ACUTE
+015C;015C;0053 0302;015C;0053 0302; # (Ŝ; Ŝ; S◌̂; Ŝ; S◌̂; ) LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+015D;015D;0073 0302;015D;0073 0302; # (Å; Å; s◌̂; Å; s◌̂; ) LATIN SMALL LETTER S WITH CIRCUMFLEX
+015E;015E;0053 0327;015E;0053 0327; # (Ş; Ş; S◌̧; Ş; S◌̧; ) LATIN CAPITAL LETTER S WITH CEDILLA
+015F;015F;0073 0327;015F;0073 0327; # (ş; ş; s◌̧; ş; s◌̧; ) LATIN SMALL LETTER S WITH CEDILLA
+0160;0160;0053 030C;0160;0053 030C; # (Š; Š; S◌̌; Š; S◌̌; ) LATIN CAPITAL LETTER S WITH CARON
+0161;0161;0073 030C;0161;0073 030C; # (š; š; s◌̌; š; s◌̌; ) LATIN SMALL LETTER S WITH CARON
+0162;0162;0054 0327;0162;0054 0327; # (Ţ; Ţ; T◌̧; Ţ; T◌̧; ) LATIN CAPITAL LETTER T WITH CEDILLA
+0163;0163;0074 0327;0163;0074 0327; # (ţ; ţ; t◌̧; ţ; t◌̧; ) LATIN SMALL LETTER T WITH CEDILLA
+0164;0164;0054 030C;0164;0054 030C; # (Ť; Ť; T◌̌; Ť; T◌̌; ) LATIN CAPITAL LETTER T WITH CARON
+0165;0165;0074 030C;0165;0074 030C; # (ť; ť; t◌̌; ť; t◌̌; ) LATIN SMALL LETTER T WITH CARON
+0168;0168;0055 0303;0168;0055 0303; # (Ũ; Ũ; U◌̃; Ũ; U◌̃; ) LATIN CAPITAL LETTER U WITH TILDE
+0169;0169;0075 0303;0169;0075 0303; # (ũ; ũ; u◌̃; ũ; u◌̃; ) LATIN SMALL LETTER U WITH TILDE
+016A;016A;0055 0304;016A;0055 0304; # (Ū; Ū; U◌̄; Ū; U◌̄; ) LATIN CAPITAL LETTER U WITH MACRON
+016B;016B;0075 0304;016B;0075 0304; # (ū; ū; u◌̄; ū; u◌̄; ) LATIN SMALL LETTER U WITH MACRON
+016C;016C;0055 0306;016C;0055 0306; # (Ŭ; Ŭ; U◌̆; Ŭ; U◌̆; ) LATIN CAPITAL LETTER U WITH BREVE
+016D;016D;0075 0306;016D;0075 0306; # (ŭ; ŭ; u◌̆; ŭ; u◌̆; ) LATIN SMALL LETTER U WITH BREVE
+016E;016E;0055 030A;016E;0055 030A; # (Ů; Ů; U◌̊; Ů; U◌̊; ) LATIN CAPITAL LETTER U WITH RING ABOVE
+016F;016F;0075 030A;016F;0075 030A; # (ů; ů; u◌̊; ů; u◌̊; ) LATIN SMALL LETTER U WITH RING ABOVE
+0170;0170;0055 030B;0170;0055 030B; # (Ű; Ű; U◌̋; Ű; U◌̋; ) LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0171;0171;0075 030B;0171;0075 030B; # (ű; ű; u◌̋; ű; u◌̋; ) LATIN SMALL LETTER U WITH DOUBLE ACUTE
+0172;0172;0055 0328;0172;0055 0328; # (Ų; Ų; U◌̨; Ų; U◌̨; ) LATIN CAPITAL LETTER U WITH OGONEK
+0173;0173;0075 0328;0173;0075 0328; # (ų; ų; u◌̨; ų; u◌̨; ) LATIN SMALL LETTER U WITH OGONEK
+0174;0174;0057 0302;0174;0057 0302; # (Ŵ; Ŵ; W◌̂; Ŵ; W◌̂; ) LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+0175;0175;0077 0302;0175;0077 0302; # (ŵ; ŵ; w◌̂; ŵ; w◌̂; ) LATIN SMALL LETTER W WITH CIRCUMFLEX
+0176;0176;0059 0302;0176;0059 0302; # (Ŷ; Ŷ; Y◌̂; Ŷ; Y◌̂; ) LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+0177;0177;0079 0302;0177;0079 0302; # (ŷ; ŷ; y◌̂; ŷ; y◌̂; ) LATIN SMALL LETTER Y WITH CIRCUMFLEX
+0178;0178;0059 0308;0178;0059 0308; # (Ÿ; Ÿ; Y◌̈; Ÿ; Y◌̈; ) LATIN CAPITAL LETTER Y WITH DIAERESIS
+0179;0179;005A 0301;0179;005A 0301; # (Ź; Ź; Zâ—ŒÌ; Ź; Zâ—ŒÌ; ) LATIN CAPITAL LETTER Z WITH ACUTE
+017A;017A;007A 0301;017A;007A 0301; # (ź; ź; zâ—ŒÌ; ź; zâ—ŒÌ; ) LATIN SMALL LETTER Z WITH ACUTE
+017B;017B;005A 0307;017B;005A 0307; # (Ż; Ż; Z◌̇; Ż; Z◌̇; ) LATIN CAPITAL LETTER Z WITH DOT ABOVE
+017C;017C;007A 0307;017C;007A 0307; # (ż; ż; z◌̇; ż; z◌̇; ) LATIN SMALL LETTER Z WITH DOT ABOVE
+017D;017D;005A 030C;017D;005A 030C; # (Ž; Ž; Z◌̌; Ž; Z◌̌; ) LATIN CAPITAL LETTER Z WITH CARON
+017E;017E;007A 030C;017E;007A 030C; # (ž; ž; z◌̌; ž; z◌̌; ) LATIN SMALL LETTER Z WITH CARON
+017F;017F;017F;0073;0073; # (Å¿; Å¿; Å¿; s; s; ) LATIN SMALL LETTER LONG S
+01A0;01A0;004F 031B;01A0;004F 031B; # (Ơ; Ơ; O◌̛; Ơ; O◌̛; ) LATIN CAPITAL LETTER O WITH HORN
+01A1;01A1;006F 031B;01A1;006F 031B; # (ơ; ơ; o◌̛; ơ; o◌̛; ) LATIN SMALL LETTER O WITH HORN
+01AF;01AF;0055 031B;01AF;0055 031B; # (Ư; Ư; U◌̛; Ư; U◌̛; ) LATIN CAPITAL LETTER U WITH HORN
+01B0;01B0;0075 031B;01B0;0075 031B; # (ư; ư; u◌̛; ư; u◌̛; ) LATIN SMALL LETTER U WITH HORN
+01C4;01C4;01C4;0044 017D;0044 005A 030C; # (DŽ; DŽ; DŽ; DŽ; DZ◌̌; ) LATIN CAPITAL LETTER DZ WITH CARON
+01C5;01C5;01C5;0044 017E;0044 007A 030C; # (Dž; Dž; Dž; Dž; Dz◌̌; ) LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON
+01C6;01C6;01C6;0064 017E;0064 007A 030C; # (dž; dž; dž; dž; dz◌̌; ) LATIN SMALL LETTER DZ WITH CARON
+01C7;01C7;01C7;004C 004A;004C 004A; # (LJ; LJ; LJ; LJ; LJ; ) LATIN CAPITAL LETTER LJ
+01C8;01C8;01C8;004C 006A;004C 006A; # (Lj; Lj; Lj; Lj; Lj; ) LATIN CAPITAL LETTER L WITH SMALL LETTER J
+01C9;01C9;01C9;006C 006A;006C 006A; # (lj; lj; lj; lj; lj; ) LATIN SMALL LETTER LJ
+01CA;01CA;01CA;004E 004A;004E 004A; # (ÇŠ; ÇŠ; ÇŠ; NJ; NJ; ) LATIN CAPITAL LETTER NJ
+01CB;01CB;01CB;004E 006A;004E 006A; # (Ç‹; Ç‹; Ç‹; Nj; Nj; ) LATIN CAPITAL LETTER N WITH SMALL LETTER J
+01CC;01CC;01CC;006E 006A;006E 006A; # (nj; nj; nj; nj; nj; ) LATIN SMALL LETTER NJ
+01CD;01CD;0041 030C;01CD;0041 030C; # (Ç; Ç; A◌̌; Ç; A◌̌; ) LATIN CAPITAL LETTER A WITH CARON
+01CE;01CE;0061 030C;01CE;0061 030C; # (ǎ; ǎ; a◌̌; ǎ; a◌̌; ) LATIN SMALL LETTER A WITH CARON
+01CF;01CF;0049 030C;01CF;0049 030C; # (Ç; Ç; I◌̌; Ç; I◌̌; ) LATIN CAPITAL LETTER I WITH CARON
+01D0;01D0;0069 030C;01D0;0069 030C; # (Ç; Ç; i◌̌; Ç; i◌̌; ) LATIN SMALL LETTER I WITH CARON
+01D1;01D1;004F 030C;01D1;004F 030C; # (Ǒ; Ǒ; O◌̌; Ǒ; O◌̌; ) LATIN CAPITAL LETTER O WITH CARON
+01D2;01D2;006F 030C;01D2;006F 030C; # (ǒ; ǒ; o◌̌; ǒ; o◌̌; ) LATIN SMALL LETTER O WITH CARON
+01D3;01D3;0055 030C;01D3;0055 030C; # (Ǔ; Ǔ; U◌̌; Ǔ; U◌̌; ) LATIN CAPITAL LETTER U WITH CARON
+01D4;01D4;0075 030C;01D4;0075 030C; # (ǔ; ǔ; u◌̌; ǔ; u◌̌; ) LATIN SMALL LETTER U WITH CARON
+01D5;01D5;0055 0308 0304;01D5;0055 0308 0304; # (Ǖ; Ǖ; U◌̈◌̄; Ǖ; U◌̈◌̄; ) LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON
+01D6;01D6;0075 0308 0304;01D6;0075 0308 0304; # (ǖ; ǖ; u◌̈◌̄; ǖ; u◌̈◌̄; ) LATIN SMALL LETTER U WITH DIAERESIS AND MACRON
+01D7;01D7;0055 0308 0301;01D7;0055 0308 0301; # (Ç—; Ç—; U◌̈◌Ì; Ç—; U◌̈◌Ì; ) LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE
+01D8;01D8;0075 0308 0301;01D8;0075 0308 0301; # (ǘ; ǘ; u◌̈◌Ì; ǘ; u◌̈◌Ì; ) LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE
+01D9;01D9;0055 0308 030C;01D9;0055 0308 030C; # (Ǚ; Ǚ; U◌̈◌̌; Ǚ; U◌̈◌̌; ) LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON
+01DA;01DA;0075 0308 030C;01DA;0075 0308 030C; # (ǚ; ǚ; u◌̈◌̌; ǚ; u◌̈◌̌; ) LATIN SMALL LETTER U WITH DIAERESIS AND CARON
+01DB;01DB;0055 0308 0300;01DB;0055 0308 0300; # (Ǜ; Ǜ; U◌̈◌̀; Ǜ; U◌̈◌̀; ) LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE
+01DC;01DC;0075 0308 0300;01DC;0075 0308 0300; # (ǜ; ǜ; u◌̈◌̀; ǜ; u◌̈◌̀; ) LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE
+01DE;01DE;0041 0308 0304;01DE;0041 0308 0304; # (Ǟ; Ǟ; A◌̈◌̄; Ǟ; A◌̈◌̄; ) LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON
+01DF;01DF;0061 0308 0304;01DF;0061 0308 0304; # (ǟ; ǟ; a◌̈◌̄; ǟ; a◌̈◌̄; ) LATIN SMALL LETTER A WITH DIAERESIS AND MACRON
+01E0;01E0;0041 0307 0304;01E0;0041 0307 0304; # (Ǡ; Ǡ; A◌̇◌̄; Ǡ; A◌̇◌̄; ) LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON
+01E1;01E1;0061 0307 0304;01E1;0061 0307 0304; # (ǡ; ǡ; a◌̇◌̄; ǡ; a◌̇◌̄; ) LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON
+01E2;01E2;00C6 0304;01E2;00C6 0304; # (Ǣ; Ǣ; Æ◌̄; Ǣ; Æ◌̄; ) LATIN CAPITAL LETTER AE WITH MACRON
+01E3;01E3;00E6 0304;01E3;00E6 0304; # (ǣ; ǣ; æ◌̄; ǣ; æ◌̄; ) LATIN SMALL LETTER AE WITH MACRON
+01E6;01E6;0047 030C;01E6;0047 030C; # (Ǧ; Ǧ; G◌̌; Ǧ; G◌̌; ) LATIN CAPITAL LETTER G WITH CARON
+01E7;01E7;0067 030C;01E7;0067 030C; # (ǧ; ǧ; g◌̌; ǧ; g◌̌; ) LATIN SMALL LETTER G WITH CARON
+01E8;01E8;004B 030C;01E8;004B 030C; # (Ǩ; Ǩ; K◌̌; Ǩ; K◌̌; ) LATIN CAPITAL LETTER K WITH CARON
+01E9;01E9;006B 030C;01E9;006B 030C; # (ǩ; ǩ; k◌̌; ǩ; k◌̌; ) LATIN SMALL LETTER K WITH CARON
+01EA;01EA;004F 0328;01EA;004F 0328; # (Ǫ; Ǫ; O◌̨; Ǫ; O◌̨; ) LATIN CAPITAL LETTER O WITH OGONEK
+01EB;01EB;006F 0328;01EB;006F 0328; # (ǫ; ǫ; o◌̨; ǫ; o◌̨; ) LATIN SMALL LETTER O WITH OGONEK
+01EC;01EC;004F 0328 0304;01EC;004F 0328 0304; # (Ǭ; Ǭ; O◌̨◌̄; Ǭ; O◌̨◌̄; ) LATIN CAPITAL LETTER O WITH OGONEK AND MACRON
+01ED;01ED;006F 0328 0304;01ED;006F 0328 0304; # (ǭ; ǭ; o◌̨◌̄; ǭ; o◌̨◌̄; ) LATIN SMALL LETTER O WITH OGONEK AND MACRON
+01EE;01EE;01B7 030C;01EE;01B7 030C; # (Ǯ; Ǯ; Ʒ◌̌; Ǯ; Ʒ◌̌; ) LATIN CAPITAL LETTER EZH WITH CARON
+01EF;01EF;0292 030C;01EF;0292 030C; # (ǯ; ǯ; ʒ◌̌; ǯ; ʒ◌̌; ) LATIN SMALL LETTER EZH WITH CARON
+01F0;01F0;006A 030C;01F0;006A 030C; # (ǰ; ǰ; j◌̌; ǰ; j◌̌; ) LATIN SMALL LETTER J WITH CARON
+01F1;01F1;01F1;0044 005A;0044 005A; # (DZ; DZ; DZ; DZ; DZ; ) LATIN CAPITAL LETTER DZ
+01F2;01F2;01F2;0044 007A;0044 007A; # (Dz; Dz; Dz; Dz; Dz; ) LATIN CAPITAL LETTER D WITH SMALL LETTER Z
+01F3;01F3;01F3;0064 007A;0064 007A; # (dz; dz; dz; dz; dz; ) LATIN SMALL LETTER DZ
+01F4;01F4;0047 0301;01F4;0047 0301; # (Ç´; Ç´; Gâ—ŒÌ; Ç´; Gâ—ŒÌ; ) LATIN CAPITAL LETTER G WITH ACUTE
+01F5;01F5;0067 0301;01F5;0067 0301; # (ǵ; ǵ; gâ—ŒÌ; ǵ; gâ—ŒÌ; ) LATIN SMALL LETTER G WITH ACUTE
+01F8;01F8;004E 0300;01F8;004E 0300; # (Ǹ; Ǹ; N◌̀; Ǹ; N◌̀; ) LATIN CAPITAL LETTER N WITH GRAVE
+01F9;01F9;006E 0300;01F9;006E 0300; # (ǹ; ǹ; n◌̀; ǹ; n◌̀; ) LATIN SMALL LETTER N WITH GRAVE
+01FA;01FA;0041 030A 0301;01FA;0041 030A 0301; # (Ǻ; Ǻ; A◌̊◌Ì; Ǻ; A◌̊◌Ì; ) LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE
+01FB;01FB;0061 030A 0301;01FB;0061 030A 0301; # (Ç»; Ç»; a◌̊◌Ì; Ç»; a◌̊◌Ì; ) LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE
+01FC;01FC;00C6 0301;01FC;00C6 0301; # (Ǽ; Ǽ; Æ◌Ì; Ǽ; Æ◌Ì; ) LATIN CAPITAL LETTER AE WITH ACUTE
+01FD;01FD;00E6 0301;01FD;00E6 0301; # (ǽ; ǽ; æ◌Ì; ǽ; æ◌Ì; ) LATIN SMALL LETTER AE WITH ACUTE
+01FE;01FE;00D8 0301;01FE;00D8 0301; # (Ǿ; Ǿ; Ø◌Ì; Ǿ; Ø◌Ì; ) LATIN CAPITAL LETTER O WITH STROKE AND ACUTE
+01FF;01FF;00F8 0301;01FF;00F8 0301; # (Ç¿; Ç¿; ø◌Ì; Ç¿; ø◌Ì; ) LATIN SMALL LETTER O WITH STROKE AND ACUTE
+0200;0200;0041 030F;0200;0041 030F; # (È€; È€; Aâ—ŒÌ; È€; Aâ—ŒÌ; ) LATIN CAPITAL LETTER A WITH DOUBLE GRAVE
+0201;0201;0061 030F;0201;0061 030F; # (È; È; aâ—ŒÌ; È; aâ—ŒÌ; ) LATIN SMALL LETTER A WITH DOUBLE GRAVE
+0202;0202;0041 0311;0202;0041 0311; # (Ȃ; Ȃ; A◌̑; Ȃ; A◌̑; ) LATIN CAPITAL LETTER A WITH INVERTED BREVE
+0203;0203;0061 0311;0203;0061 0311; # (ȃ; ȃ; a◌̑; ȃ; a◌̑; ) LATIN SMALL LETTER A WITH INVERTED BREVE
+0204;0204;0045 030F;0204;0045 030F; # (È„; È„; Eâ—ŒÌ; È„; Eâ—ŒÌ; ) LATIN CAPITAL LETTER E WITH DOUBLE GRAVE
+0205;0205;0065 030F;0205;0065 030F; # (È…; È…; eâ—ŒÌ; È…; eâ—ŒÌ; ) LATIN SMALL LETTER E WITH DOUBLE GRAVE
+0206;0206;0045 0311;0206;0045 0311; # (Ȇ; Ȇ; E◌̑; Ȇ; E◌̑; ) LATIN CAPITAL LETTER E WITH INVERTED BREVE
+0207;0207;0065 0311;0207;0065 0311; # (ȇ; ȇ; e◌̑; ȇ; e◌̑; ) LATIN SMALL LETTER E WITH INVERTED BREVE
+0208;0208;0049 030F;0208;0049 030F; # (Ȉ; Ȉ; Iâ—ŒÌ; Ȉ; Iâ—ŒÌ; ) LATIN CAPITAL LETTER I WITH DOUBLE GRAVE
+0209;0209;0069 030F;0209;0069 030F; # (ȉ; ȉ; iâ—ŒÌ; ȉ; iâ—ŒÌ; ) LATIN SMALL LETTER I WITH DOUBLE GRAVE
+020A;020A;0049 0311;020A;0049 0311; # (Ȋ; Ȋ; I◌̑; Ȋ; I◌̑; ) LATIN CAPITAL LETTER I WITH INVERTED BREVE
+020B;020B;0069 0311;020B;0069 0311; # (ȋ; ȋ; i◌̑; ȋ; i◌̑; ) LATIN SMALL LETTER I WITH INVERTED BREVE
+020C;020C;004F 030F;020C;004F 030F; # (ÈŒ; ÈŒ; Oâ—ŒÌ; ÈŒ; Oâ—ŒÌ; ) LATIN CAPITAL LETTER O WITH DOUBLE GRAVE
+020D;020D;006F 030F;020D;006F 030F; # (È; È; oâ—ŒÌ; È; oâ—ŒÌ; ) LATIN SMALL LETTER O WITH DOUBLE GRAVE
+020E;020E;004F 0311;020E;004F 0311; # (Ȏ; Ȏ; O◌̑; Ȏ; O◌̑; ) LATIN CAPITAL LETTER O WITH INVERTED BREVE
+020F;020F;006F 0311;020F;006F 0311; # (È; È; o◌̑; È; o◌̑; ) LATIN SMALL LETTER O WITH INVERTED BREVE
+0210;0210;0052 030F;0210;0052 030F; # (È; È; Râ—ŒÌ; È; Râ—ŒÌ; ) LATIN CAPITAL LETTER R WITH DOUBLE GRAVE
+0211;0211;0072 030F;0211;0072 030F; # (È‘; È‘; râ—ŒÌ; È‘; râ—ŒÌ; ) LATIN SMALL LETTER R WITH DOUBLE GRAVE
+0212;0212;0052 0311;0212;0052 0311; # (Ȓ; Ȓ; R◌̑; Ȓ; R◌̑; ) LATIN CAPITAL LETTER R WITH INVERTED BREVE
+0213;0213;0072 0311;0213;0072 0311; # (ȓ; ȓ; r◌̑; ȓ; r◌̑; ) LATIN SMALL LETTER R WITH INVERTED BREVE
+0214;0214;0055 030F;0214;0055 030F; # (È”; È”; Uâ—ŒÌ; È”; Uâ—ŒÌ; ) LATIN CAPITAL LETTER U WITH DOUBLE GRAVE
+0215;0215;0075 030F;0215;0075 030F; # (È•; È•; uâ—ŒÌ; È•; uâ—ŒÌ; ) LATIN SMALL LETTER U WITH DOUBLE GRAVE
+0216;0216;0055 0311;0216;0055 0311; # (Ȗ; Ȗ; U◌̑; Ȗ; U◌̑; ) LATIN CAPITAL LETTER U WITH INVERTED BREVE
+0217;0217;0075 0311;0217;0075 0311; # (ȗ; ȗ; u◌̑; ȗ; u◌̑; ) LATIN SMALL LETTER U WITH INVERTED BREVE
+0218;0218;0053 0326;0218;0053 0326; # (Ș; Ș; S◌̦; Ș; S◌̦; ) LATIN CAPITAL LETTER S WITH COMMA BELOW
+0219;0219;0073 0326;0219;0073 0326; # (ș; ș; s◌̦; ș; s◌̦; ) LATIN SMALL LETTER S WITH COMMA BELOW
+021A;021A;0054 0326;021A;0054 0326; # (Ț; Ț; T◌̦; Ț; T◌̦; ) LATIN CAPITAL LETTER T WITH COMMA BELOW
+021B;021B;0074 0326;021B;0074 0326; # (ț; ț; t◌̦; ț; t◌̦; ) LATIN SMALL LETTER T WITH COMMA BELOW
+021E;021E;0048 030C;021E;0048 030C; # (Ȟ; Ȟ; H◌̌; Ȟ; H◌̌; ) LATIN CAPITAL LETTER H WITH CARON
+021F;021F;0068 030C;021F;0068 030C; # (ȟ; ȟ; h◌̌; ȟ; h◌̌; ) LATIN SMALL LETTER H WITH CARON
+0226;0226;0041 0307;0226;0041 0307; # (Ȧ; Ȧ; A◌̇; Ȧ; A◌̇; ) LATIN CAPITAL LETTER A WITH DOT ABOVE
+0227;0227;0061 0307;0227;0061 0307; # (ȧ; ȧ; a◌̇; ȧ; a◌̇; ) LATIN SMALL LETTER A WITH DOT ABOVE
+0228;0228;0045 0327;0228;0045 0327; # (Ȩ; Ȩ; E◌̧; Ȩ; E◌̧; ) LATIN CAPITAL LETTER E WITH CEDILLA
+0229;0229;0065 0327;0229;0065 0327; # (ȩ; ȩ; e◌̧; ȩ; e◌̧; ) LATIN SMALL LETTER E WITH CEDILLA
+022A;022A;004F 0308 0304;022A;004F 0308 0304; # (Ȫ; Ȫ; O◌̈◌̄; Ȫ; O◌̈◌̄; ) LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON
+022B;022B;006F 0308 0304;022B;006F 0308 0304; # (ȫ; ȫ; o◌̈◌̄; ȫ; o◌̈◌̄; ) LATIN SMALL LETTER O WITH DIAERESIS AND MACRON
+022C;022C;004F 0303 0304;022C;004F 0303 0304; # (Ȭ; Ȭ; O◌̃◌̄; Ȭ; O◌̃◌̄; ) LATIN CAPITAL LETTER O WITH TILDE AND MACRON
+022D;022D;006F 0303 0304;022D;006F 0303 0304; # (ȭ; ȭ; o◌̃◌̄; ȭ; o◌̃◌̄; ) LATIN SMALL LETTER O WITH TILDE AND MACRON
+022E;022E;004F 0307;022E;004F 0307; # (Ȯ; Ȯ; O◌̇; Ȯ; O◌̇; ) LATIN CAPITAL LETTER O WITH DOT ABOVE
+022F;022F;006F 0307;022F;006F 0307; # (ȯ; ȯ; o◌̇; ȯ; o◌̇; ) LATIN SMALL LETTER O WITH DOT ABOVE
+0230;0230;004F 0307 0304;0230;004F 0307 0304; # (Ȱ; Ȱ; O◌̇◌̄; Ȱ; O◌̇◌̄; ) LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON
+0231;0231;006F 0307 0304;0231;006F 0307 0304; # (ȱ; ȱ; o◌̇◌̄; ȱ; o◌̇◌̄; ) LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON
+0232;0232;0059 0304;0232;0059 0304; # (Ȳ; Ȳ; Y◌̄; Ȳ; Y◌̄; ) LATIN CAPITAL LETTER Y WITH MACRON
+0233;0233;0079 0304;0233;0079 0304; # (ȳ; ȳ; y◌̄; ȳ; y◌̄; ) LATIN SMALL LETTER Y WITH MACRON
+02B0;02B0;02B0;0068;0068; # (Ê°; Ê°; Ê°; h; h; ) MODIFIER LETTER SMALL H
+02B1;02B1;02B1;0266;0266; # (ʱ; ʱ; ʱ; ɦ; ɦ; ) MODIFIER LETTER SMALL H WITH HOOK
+02B2;02B2;02B2;006A;006A; # (ʲ; ʲ; ʲ; j; j; ) MODIFIER LETTER SMALL J
+02B3;02B3;02B3;0072;0072; # (ʳ; ʳ; ʳ; r; r; ) MODIFIER LETTER SMALL R
+02B4;02B4;02B4;0279;0279; # (ʴ; ʴ; ʴ; ɹ; ɹ; ) MODIFIER LETTER SMALL TURNED R
+02B5;02B5;02B5;027B;027B; # (ʵ; ʵ; ʵ; ɻ; ɻ; ) MODIFIER LETTER SMALL TURNED R WITH HOOK
+02B6;02B6;02B6;0281;0281; # (ʶ; ʶ; ʶ; Ê; Ê; ) MODIFIER LETTER SMALL CAPITAL INVERTED R
+02B7;02B7;02B7;0077;0077; # (Ê·; Ê·; Ê·; w; w; ) MODIFIER LETTER SMALL W
+02B8;02B8;02B8;0079;0079; # (ʸ; ʸ; ʸ; y; y; ) MODIFIER LETTER SMALL Y
+02D8;02D8;02D8;0020 0306;0020 0306; # (˘; ˘; ˘; ◌̆; ◌̆; ) BREVE
+02D9;02D9;02D9;0020 0307;0020 0307; # (˙; ˙; ˙; ◌̇; ◌̇; ) DOT ABOVE
+02DA;02DA;02DA;0020 030A;0020 030A; # (˚; ˚; ˚; ◌̊; ◌̊; ) RING ABOVE
+02DB;02DB;02DB;0020 0328;0020 0328; # (˛; ˛; ˛; ◌̨; ◌̨; ) OGONEK
+02DC;02DC;02DC;0020 0303;0020 0303; # (˜; ˜; ˜; ◌̃; ◌̃; ) SMALL TILDE
+02DD;02DD;02DD;0020 030B;0020 030B; # (Ë; Ë; Ë; ◌̋; ◌̋; ) DOUBLE ACUTE ACCENT
+02E0;02E0;02E0;0263;0263; # (ˠ; ˠ; ˠ; ɣ; ɣ; ) MODIFIER LETTER SMALL GAMMA
+02E1;02E1;02E1;006C;006C; # (Ë¡; Ë¡; Ë¡; l; l; ) MODIFIER LETTER SMALL L
+02E2;02E2;02E2;0073;0073; # (ˢ; ˢ; ˢ; s; s; ) MODIFIER LETTER SMALL S
+02E3;02E3;02E3;0078;0078; # (ˣ; ˣ; ˣ; x; x; ) MODIFIER LETTER SMALL X
+02E4;02E4;02E4;0295;0295; # (ˤ; ˤ; ˤ; ʕ; ʕ; ) MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+0340;0300;0300;0300;0300; # (◌̀; ◌̀; ◌̀; ◌̀; ◌̀; ) COMBINING GRAVE TONE MARK
+0341;0301;0301;0301;0301; # (â—ŒÍ; â—ŒÌ; â—ŒÌ; â—ŒÌ; â—ŒÌ; ) COMBINING ACUTE TONE MARK
+0343;0313;0313;0313;0313; # (◌̓; ◌̓; ◌̓; ◌̓; ◌̓; ) COMBINING GREEK KORONIS
+0344;0308 0301;0308 0301;0308 0301;0308 0301; # (◌̈́; ◌̈◌Ì; ◌̈◌Ì; ◌̈◌Ì; ◌̈◌Ì; ) COMBINING GREEK DIALYTIKA TONOS
+0374;02B9;02B9;02B9;02B9; # (ʹ; ʹ; ʹ; ʹ; ʹ; ) GREEK NUMERAL SIGN
+037A;037A;037A;0020 0345;0020 0345; # (ͺ; ͺ; ͺ; ◌ͅ; ◌ͅ; ) GREEK YPOGEGRAMMENI
+037E;003B;003B;003B;003B; # (;; ;; ;; ;; ;; ) GREEK QUESTION MARK
+0384;0384;0384;0020 0301;0020 0301; # (΄; ΄; ΄; â—ŒÌ; â—ŒÌ; ) GREEK TONOS
+0385;0385;00A8 0301;0020 0308 0301;0020 0308 0301; # (Î…; Î…; ¨◌Ì; ◌̈◌Ì; ◌̈◌Ì; ) GREEK DIALYTIKA TONOS
+0386;0386;0391 0301;0386;0391 0301; # (Ά; Ά; Α◌Ì; Ά; Α◌Ì; ) GREEK CAPITAL LETTER ALPHA WITH TONOS
+0387;00B7;00B7;00B7;00B7; # (·; ·; ·; ·; ·; ) GREEK ANO TELEIA
+0388;0388;0395 0301;0388;0395 0301; # (Έ; Έ; Ε◌Ì; Έ; Ε◌Ì; ) GREEK CAPITAL LETTER EPSILON WITH TONOS
+0389;0389;0397 0301;0389;0397 0301; # (Ή; Ή; Η◌Ì; Ή; Η◌Ì; ) GREEK CAPITAL LETTER ETA WITH TONOS
+038A;038A;0399 0301;038A;0399 0301; # (Ί; Ί; Ι◌Ì; Ί; Ι◌Ì; ) GREEK CAPITAL LETTER IOTA WITH TONOS
+038C;038C;039F 0301;038C;039F 0301; # (ÎŒ; ÎŒ; Ο◌Ì; ÎŒ; Ο◌Ì; ) GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E;038E;03A5 0301;038E;03A5 0301; # (ÎŽ; ÎŽ; Υ◌Ì; ÎŽ; Υ◌Ì; ) GREEK CAPITAL LETTER UPSILON WITH TONOS
+038F;038F;03A9 0301;038F;03A9 0301; # (Î; Î; Ω◌Ì; Î; Ω◌Ì; ) GREEK CAPITAL LETTER OMEGA WITH TONOS
+0390;0390;03B9 0308 0301;0390;03B9 0308 0301; # (Î; Î; ι◌̈◌Ì; Î; ι◌̈◌Ì; ) GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+03AA;03AA;0399 0308;03AA;0399 0308; # (Ϊ; Ϊ; Ι◌̈; Ϊ; Ι◌̈; ) GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+03AB;03AB;03A5 0308;03AB;03A5 0308; # (Ϋ; Ϋ; Υ◌̈; Ϋ; Υ◌̈; ) GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+03AC;03AC;03B1 0301;03AC;03B1 0301; # (ά; ά; α◌Ì; ά; α◌Ì; ) GREEK SMALL LETTER ALPHA WITH TONOS
+03AD;03AD;03B5 0301;03AD;03B5 0301; # (έ; έ; ε◌Ì; έ; ε◌Ì; ) GREEK SMALL LETTER EPSILON WITH TONOS
+03AE;03AE;03B7 0301;03AE;03B7 0301; # (ή; ή; η◌Ì; ή; η◌Ì; ) GREEK SMALL LETTER ETA WITH TONOS
+03AF;03AF;03B9 0301;03AF;03B9 0301; # (ί; ί; ι◌Ì; ί; ι◌Ì; ) GREEK SMALL LETTER IOTA WITH TONOS
+03B0;03B0;03C5 0308 0301;03B0;03C5 0308 0301; # (ΰ; ΰ; υ◌̈◌Ì; ΰ; υ◌̈◌Ì; ) GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+03CA;03CA;03B9 0308;03CA;03B9 0308; # (ϊ; ϊ; ι◌̈; ϊ; ι◌̈; ) GREEK SMALL LETTER IOTA WITH DIALYTIKA
+03CB;03CB;03C5 0308;03CB;03C5 0308; # (ϋ; ϋ; υ◌̈; ϋ; υ◌̈; ) GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+03CC;03CC;03BF 0301;03CC;03BF 0301; # (ÏŒ; ÏŒ; ο◌Ì; ÏŒ; ο◌Ì; ) GREEK SMALL LETTER OMICRON WITH TONOS
+03CD;03CD;03C5 0301;03CD;03C5 0301; # (Ï; Ï; Ï…â—ŒÌ; Ï; Ï…â—ŒÌ; ) GREEK SMALL LETTER UPSILON WITH TONOS
+03CE;03CE;03C9 0301;03CE;03C9 0301; # (ÏŽ; ÏŽ; ω◌Ì; ÏŽ; ω◌Ì; ) GREEK SMALL LETTER OMEGA WITH TONOS
+03D0;03D0;03D0;03B2;03B2; # (Ï; Ï; Ï; β; β; ) GREEK BETA SYMBOL
+03D1;03D1;03D1;03B8;03B8; # (ϑ; ϑ; ϑ; θ; θ; ) GREEK THETA SYMBOL
+03D2;03D2;03D2;03A5;03A5; # (ϒ; ϒ; ϒ; Υ; Υ; ) GREEK UPSILON WITH HOOK SYMBOL
+03D3;03D3;03D2 0301;038E;03A5 0301; # (Ï“; Ï“; Ï’â—ŒÌ; ÎŽ; Υ◌Ì; ) GREEK UPSILON WITH ACUTE AND HOOK SYMBOL
+03D4;03D4;03D2 0308;03AB;03A5 0308; # (ϔ; ϔ; ϒ◌̈; Ϋ; Υ◌̈; ) GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL
+03D5;03D5;03D5;03C6;03C6; # (ϕ; ϕ; ϕ; φ; φ; ) GREEK PHI SYMBOL
+03D6;03D6;03D6;03C0;03C0; # (ϖ; ϖ; ϖ; π; π; ) GREEK PI SYMBOL
+03F0;03F0;03F0;03BA;03BA; # (ϰ; ϰ; ϰ; κ; κ; ) GREEK KAPPA SYMBOL
+03F1;03F1;03F1;03C1;03C1; # (ϱ; ϱ; ϱ; Ï; Ï; ) GREEK RHO SYMBOL
+03F2;03F2;03F2;03C2;03C2; # (ϲ; ϲ; ϲ; ς; ς; ) GREEK LUNATE SIGMA SYMBOL
+03F4;03F4;03F4;0398;0398; # (ϴ; ϴ; ϴ; Θ; Θ; ) GREEK CAPITAL THETA SYMBOL
+03F5;03F5;03F5;03B5;03B5; # (ϵ; ϵ; ϵ; ε; ε; ) GREEK LUNATE EPSILON SYMBOL
+03F9;03F9;03F9;03A3;03A3; # (Ϲ; Ϲ; Ϲ; Σ; Σ; ) GREEK CAPITAL LUNATE SIGMA SYMBOL
+0400;0400;0415 0300;0400;0415 0300; # (Ѐ; Ѐ; Е◌̀; Ѐ; Е◌̀; ) CYRILLIC CAPITAL LETTER IE WITH GRAVE
+0401;0401;0415 0308;0401;0415 0308; # (Ð; Ð; Е◌̈; Ð; Е◌̈; ) CYRILLIC CAPITAL LETTER IO
+0403;0403;0413 0301;0403;0413 0301; # (Ѓ; Ѓ; Г◌Ì; Ѓ; Г◌Ì; ) CYRILLIC CAPITAL LETTER GJE
+0407;0407;0406 0308;0407;0406 0308; # (Ї; Ї; І◌̈; Ї; І◌̈; ) CYRILLIC CAPITAL LETTER YI
+040C;040C;041A 0301;040C;041A 0301; # (ÐŒ; ÐŒ; К◌Ì; ÐŒ; К◌Ì; ) CYRILLIC CAPITAL LETTER KJE
+040D;040D;0418 0300;040D;0418 0300; # (Ð; Ð; И◌̀; Ð; И◌̀; ) CYRILLIC CAPITAL LETTER I WITH GRAVE
+040E;040E;0423 0306;040E;0423 0306; # (Ў; Ў; У◌̆; Ў; У◌̆; ) CYRILLIC CAPITAL LETTER SHORT U
+0419;0419;0418 0306;0419;0418 0306; # (Й; Й; И◌̆; Й; И◌̆; ) CYRILLIC CAPITAL LETTER SHORT I
+0439;0439;0438 0306;0439;0438 0306; # (й; й; и◌̆; й; и◌̆; ) CYRILLIC SMALL LETTER SHORT I
+0450;0450;0435 0300;0450;0435 0300; # (Ñ; Ñ; е◌̀; Ñ; е◌̀; ) CYRILLIC SMALL LETTER IE WITH GRAVE
+0451;0451;0435 0308;0451;0435 0308; # (ё; ё; е◌̈; ё; е◌̈; ) CYRILLIC SMALL LETTER IO
+0453;0453;0433 0301;0453;0433 0301; # (Ñ“; Ñ“; г◌Ì; Ñ“; г◌Ì; ) CYRILLIC SMALL LETTER GJE
+0457;0457;0456 0308;0457;0456 0308; # (ї; ї; і◌̈; ї; і◌̈; ) CYRILLIC SMALL LETTER YI
+045C;045C;043A 0301;045C;043A 0301; # (Ñœ; Ñœ; к◌Ì; Ñœ; к◌Ì; ) CYRILLIC SMALL LETTER KJE
+045D;045D;0438 0300;045D;0438 0300; # (Ñ; Ñ; и◌̀; Ñ; и◌̀; ) CYRILLIC SMALL LETTER I WITH GRAVE
+045E;045E;0443 0306;045E;0443 0306; # (ў; ў; у◌̆; ў; у◌̆; ) CYRILLIC SMALL LETTER SHORT U
+0476;0476;0474 030F;0476;0474 030F; # (Ѷ; Ѷ; Ñ´â—ŒÌ; Ѷ; Ñ´â—ŒÌ; ) CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
+0477;0477;0475 030F;0477;0475 030F; # (Ñ·; Ñ·; ѵ◌Ì; Ñ·; ѵ◌Ì; ) CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
+04C1;04C1;0416 0306;04C1;0416 0306; # (Ó; Ó; Ж◌̆; Ó; Ж◌̆; ) CYRILLIC CAPITAL LETTER ZHE WITH BREVE
+04C2;04C2;0436 0306;04C2;0436 0306; # (ӂ; ӂ; ж◌̆; ӂ; ж◌̆; ) CYRILLIC SMALL LETTER ZHE WITH BREVE
+04D0;04D0;0410 0306;04D0;0410 0306; # (Ó; Ó; Ð◌̆; Ó; Ð◌̆; ) CYRILLIC CAPITAL LETTER A WITH BREVE
+04D1;04D1;0430 0306;04D1;0430 0306; # (ӑ; ӑ; а◌̆; ӑ; а◌̆; ) CYRILLIC SMALL LETTER A WITH BREVE
+04D2;04D2;0410 0308;04D2;0410 0308; # (Ó’; Ó’; Ð◌̈; Ó’; Ð◌̈; ) CYRILLIC CAPITAL LETTER A WITH DIAERESIS
+04D3;04D3;0430 0308;04D3;0430 0308; # (ӓ; ӓ; а◌̈; ӓ; а◌̈; ) CYRILLIC SMALL LETTER A WITH DIAERESIS
+04D6;04D6;0415 0306;04D6;0415 0306; # (Ӗ; Ӗ; Е◌̆; Ӗ; Е◌̆; ) CYRILLIC CAPITAL LETTER IE WITH BREVE
+04D7;04D7;0435 0306;04D7;0435 0306; # (ӗ; ӗ; е◌̆; ӗ; е◌̆; ) CYRILLIC SMALL LETTER IE WITH BREVE
+04DA;04DA;04D8 0308;04DA;04D8 0308; # (Ӛ; Ӛ; Ә◌̈; Ӛ; Ә◌̈; ) CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS
+04DB;04DB;04D9 0308;04DB;04D9 0308; # (ӛ; ӛ; ә◌̈; ӛ; ә◌̈; ) CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS
+04DC;04DC;0416 0308;04DC;0416 0308; # (Ӝ; Ӝ; Ж◌̈; Ӝ; Ж◌̈; ) CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS
+04DD;04DD;0436 0308;04DD;0436 0308; # (Ó; Ó; ж◌̈; Ó; ж◌̈; ) CYRILLIC SMALL LETTER ZHE WITH DIAERESIS
+04DE;04DE;0417 0308;04DE;0417 0308; # (Ӟ; Ӟ; З◌̈; Ӟ; З◌̈; ) CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS
+04DF;04DF;0437 0308;04DF;0437 0308; # (ӟ; ӟ; з◌̈; ӟ; з◌̈; ) CYRILLIC SMALL LETTER ZE WITH DIAERESIS
+04E2;04E2;0418 0304;04E2;0418 0304; # (Ӣ; Ӣ; И◌̄; Ӣ; И◌̄; ) CYRILLIC CAPITAL LETTER I WITH MACRON
+04E3;04E3;0438 0304;04E3;0438 0304; # (ӣ; ӣ; и◌̄; ӣ; и◌̄; ) CYRILLIC SMALL LETTER I WITH MACRON
+04E4;04E4;0418 0308;04E4;0418 0308; # (Ӥ; Ӥ; И◌̈; Ӥ; И◌̈; ) CYRILLIC CAPITAL LETTER I WITH DIAERESIS
+04E5;04E5;0438 0308;04E5;0438 0308; # (ӥ; ӥ; и◌̈; ӥ; и◌̈; ) CYRILLIC SMALL LETTER I WITH DIAERESIS
+04E6;04E6;041E 0308;04E6;041E 0308; # (Ӧ; Ӧ; О◌̈; Ӧ; О◌̈; ) CYRILLIC CAPITAL LETTER O WITH DIAERESIS
+04E7;04E7;043E 0308;04E7;043E 0308; # (ӧ; ӧ; о◌̈; ӧ; о◌̈; ) CYRILLIC SMALL LETTER O WITH DIAERESIS
+04EA;04EA;04E8 0308;04EA;04E8 0308; # (Ӫ; Ӫ; Ө◌̈; Ӫ; Ө◌̈; ) CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS
+04EB;04EB;04E9 0308;04EB;04E9 0308; # (ӫ; ӫ; ө◌̈; ӫ; ө◌̈; ) CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS
+04EC;04EC;042D 0308;04EC;042D 0308; # (Ӭ; Ӭ; Э◌̈; Ӭ; Э◌̈; ) CYRILLIC CAPITAL LETTER E WITH DIAERESIS
+04ED;04ED;044D 0308;04ED;044D 0308; # (Ó­; Ó­; Ñ◌̈; Ó­; Ñ◌̈; ) CYRILLIC SMALL LETTER E WITH DIAERESIS
+04EE;04EE;0423 0304;04EE;0423 0304; # (Ӯ; Ӯ; У◌̄; Ӯ; У◌̄; ) CYRILLIC CAPITAL LETTER U WITH MACRON
+04EF;04EF;0443 0304;04EF;0443 0304; # (ӯ; ӯ; у◌̄; ӯ; у◌̄; ) CYRILLIC SMALL LETTER U WITH MACRON
+04F0;04F0;0423 0308;04F0;0423 0308; # (Ӱ; Ӱ; У◌̈; Ӱ; У◌̈; ) CYRILLIC CAPITAL LETTER U WITH DIAERESIS
+04F1;04F1;0443 0308;04F1;0443 0308; # (ӱ; ӱ; у◌̈; ӱ; у◌̈; ) CYRILLIC SMALL LETTER U WITH DIAERESIS
+04F2;04F2;0423 030B;04F2;0423 030B; # (Ӳ; Ӳ; У◌̋; Ӳ; У◌̋; ) CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE
+04F3;04F3;0443 030B;04F3;0443 030B; # (ӳ; ӳ; у◌̋; ӳ; у◌̋; ) CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE
+04F4;04F4;0427 0308;04F4;0427 0308; # (Ӵ; Ӵ; Ч◌̈; Ӵ; Ч◌̈; ) CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS
+04F5;04F5;0447 0308;04F5;0447 0308; # (ӵ; ӵ; ч◌̈; ӵ; ч◌̈; ) CYRILLIC SMALL LETTER CHE WITH DIAERESIS
+04F8;04F8;042B 0308;04F8;042B 0308; # (Ӹ; Ӹ; Ы◌̈; Ӹ; Ы◌̈; ) CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS
+04F9;04F9;044B 0308;04F9;044B 0308; # (ӹ; ӹ; ы◌̈; ӹ; ы◌̈; ) CYRILLIC SMALL LETTER YERU WITH DIAERESIS
+0587;0587;0587;0565 0582;0565 0582; # (Ö‡; Ö‡; Ö‡; Õ¥Ö‚; Õ¥Ö‚; ) ARMENIAN SMALL LIGATURE ECH YIWN
+0622;0622;0627 0653;0622;0627 0653; # (آ; آ; ا◌ٓ; آ; ا◌ٓ; ) ARABIC LETTER ALEF WITH MADDA ABOVE
+0623;0623;0627 0654;0623;0627 0654; # (أ; أ; ا◌ٔ; أ; ا◌ٔ; ) ARABIC LETTER ALEF WITH HAMZA ABOVE
+0624;0624;0648 0654;0624;0648 0654; # (ؤ; ؤ; و◌ٔ; ؤ; و◌ٔ; ) ARABIC LETTER WAW WITH HAMZA ABOVE
+0625;0625;0627 0655;0625;0627 0655; # (إ; إ; ا◌ٕ; إ; ا◌ٕ; ) ARABIC LETTER ALEF WITH HAMZA BELOW
+0626;0626;064A 0654;0626;064A 0654; # (ئ; ئ; ي◌ٔ; ئ; ي◌ٔ; ) ARABIC LETTER YEH WITH HAMZA ABOVE
+0675;0675;0675;0627 0674;0627 0674; # (ٵ; ٵ; ٵ; اٴ; اٴ; ) ARABIC LETTER HIGH HAMZA ALEF
+0676;0676;0676;0648 0674;0648 0674; # (ٶ; ٶ; ٶ; وٴ; وٴ; ) ARABIC LETTER HIGH HAMZA WAW
+0677;0677;0677;06C7 0674;06C7 0674; # (Ù·; Ù·; Ù·; Û‡Ù´; Û‡Ù´; ) ARABIC LETTER U WITH HAMZA ABOVE
+0678;0678;0678;064A 0674;064A 0674; # (ٸ; ٸ; ٸ; يٴ; يٴ; ) ARABIC LETTER HIGH HAMZA YEH
+06C0;06C0;06D5 0654;06C0;06D5 0654; # (ۀ; ۀ; ە◌ٔ; ۀ; ە◌ٔ; ) ARABIC LETTER HEH WITH YEH ABOVE
+06C2;06C2;06C1 0654;06C2;06C1 0654; # (Û‚; Û‚; Û◌ٔ; Û‚; Û◌ٔ; ) ARABIC LETTER HEH GOAL WITH HAMZA ABOVE
+06D3;06D3;06D2 0654;06D3;06D2 0654; # (ۓ; ۓ; ے◌ٔ; ۓ; ے◌ٔ; ) ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
+0929;0929;0928 093C;0929;0928 093C; # (ऩ; ऩ; न◌़; ऩ; न◌़; ) DEVANAGARI LETTER NNNA
+0931;0931;0930 093C;0931;0930 093C; # (ऱ; ऱ; र◌़; ऱ; र◌़; ) DEVANAGARI LETTER RRA
+0934;0934;0933 093C;0934;0933 093C; # (ऴ; ऴ; ळ◌़; ऴ; ळ◌़; ) DEVANAGARI LETTER LLLA
+0958;0915 093C;0915 093C;0915 093C;0915 093C; # (क़; क◌़; क◌़; क◌़; क◌़; ) DEVANAGARI LETTER QA
+0959;0916 093C;0916 093C;0916 093C;0916 093C; # (ख़; ख◌़; ख◌़; ख◌़; ख◌़; ) DEVANAGARI LETTER KHHA
+095A;0917 093C;0917 093C;0917 093C;0917 093C; # (ग़; ग◌़; ग◌़; ग◌़; ग◌़; ) DEVANAGARI LETTER GHHA
+095B;091C 093C;091C 093C;091C 093C;091C 093C; # (ज़; ज◌़; ज◌़; ज◌़; ज◌़; ) DEVANAGARI LETTER ZA
+095C;0921 093C;0921 093C;0921 093C;0921 093C; # (ड़; ड◌़; ड◌़; ड◌़; ड◌़; ) DEVANAGARI LETTER DDDHA
+095D;0922 093C;0922 093C;0922 093C;0922 093C; # (à¥; ढ◌़; ढ◌़; ढ◌़; ढ◌़; ) DEVANAGARI LETTER RHA
+095E;092B 093C;092B 093C;092B 093C;092B 093C; # (फ़; फ◌़; फ◌़; फ◌़; फ◌़; ) DEVANAGARI LETTER FA
+095F;092F 093C;092F 093C;092F 093C;092F 093C; # (य़; य◌़; य◌़; य◌़; य◌़; ) DEVANAGARI LETTER YYA
+09CB;09CB;09C7 09BE;09CB;09C7 09BE; # (ো; ো; ো; ো; ো; ) BENGALI VOWEL SIGN O
+09CC;09CC;09C7 09D7;09CC;09C7 09D7; # (ৌ; ৌ; ৌ; ৌ; ৌ; ) BENGALI VOWEL SIGN AU
+09DC;09A1 09BC;09A1 09BC;09A1 09BC;09A1 09BC; # (ড়; ড◌়; ড◌়; ড◌়; ড◌়; ) BENGALI LETTER RRA
+09DD;09A2 09BC;09A2 09BC;09A2 09BC;09A2 09BC; # (à§; ঢ◌়; ঢ◌়; ঢ◌়; ঢ◌়; ) BENGALI LETTER RHA
+09DF;09AF 09BC;09AF 09BC;09AF 09BC;09AF 09BC; # (য়; য◌়; য◌়; য◌়; য◌়; ) BENGALI LETTER YYA
+0A33;0A32 0A3C;0A32 0A3C;0A32 0A3C;0A32 0A3C; # (ਲ਼; ਲ◌਼; ਲ◌਼; ਲ◌਼; ਲ◌਼; ) GURMUKHI LETTER LLA
+0A36;0A38 0A3C;0A38 0A3C;0A38 0A3C;0A38 0A3C; # (ਸ਼; ਸ◌਼; ਸ◌਼; ਸ◌਼; ਸ◌਼; ) GURMUKHI LETTER SHA
+0A59;0A16 0A3C;0A16 0A3C;0A16 0A3C;0A16 0A3C; # (ਖ਼; ਖ◌਼; ਖ◌਼; ਖ◌਼; ਖ◌਼; ) GURMUKHI LETTER KHHA
+0A5A;0A17 0A3C;0A17 0A3C;0A17 0A3C;0A17 0A3C; # (ਗ਼; ਗ◌਼; ਗ◌਼; ਗ◌਼; ਗ◌਼; ) GURMUKHI LETTER GHHA
+0A5B;0A1C 0A3C;0A1C 0A3C;0A1C 0A3C;0A1C 0A3C; # (ਜ਼; ਜ◌਼; ਜ◌਼; ਜ◌਼; ਜ◌਼; ) GURMUKHI LETTER ZA
+0A5E;0A2B 0A3C;0A2B 0A3C;0A2B 0A3C;0A2B 0A3C; # (ਫ਼; ਫ◌਼; ਫ◌਼; ਫ◌਼; ਫ◌਼; ) GURMUKHI LETTER FA
+0B48;0B48;0B47 0B56;0B48;0B47 0B56; # (ୈ; ୈ; େ◌ୖ; ୈ; େ◌ୖ; ) ORIYA VOWEL SIGN AI
+0B4B;0B4B;0B47 0B3E;0B4B;0B47 0B3E; # (ୋ; ୋ; ୋ; ୋ; ୋ; ) ORIYA VOWEL SIGN O
+0B4C;0B4C;0B47 0B57;0B4C;0B47 0B57; # (ୌ; ୌ; ୌ; ୌ; ୌ; ) ORIYA VOWEL SIGN AU
+0B5C;0B21 0B3C;0B21 0B3C;0B21 0B3C;0B21 0B3C; # (ଡ଼; ଡ◌଼; ଡ◌଼; ଡ◌଼; ଡ◌଼; ) ORIYA LETTER RRA
+0B5D;0B22 0B3C;0B22 0B3C;0B22 0B3C;0B22 0B3C; # (à­; ଢ◌଼; ଢ◌଼; ଢ◌଼; ଢ◌଼; ) ORIYA LETTER RHA
+0B94;0B94;0B92 0BD7;0B94;0B92 0BD7; # (ஔ; ஔ; ஔ; ஔ; ஔ; ) TAMIL LETTER AU
+0BCA;0BCA;0BC6 0BBE;0BCA;0BC6 0BBE; # (ொ; ொ; ொ; ொ; ொ; ) TAMIL VOWEL SIGN O
+0BCB;0BCB;0BC7 0BBE;0BCB;0BC7 0BBE; # (ோ; ோ; ோ; ோ; ோ; ) TAMIL VOWEL SIGN OO
+0BCC;0BCC;0BC6 0BD7;0BCC;0BC6 0BD7; # (ௌ; ௌ; ௌ; ௌ; ௌ; ) TAMIL VOWEL SIGN AU
+0C48;0C48;0C46 0C56;0C48;0C46 0C56; # (◌ై; ◌ై; ◌ె◌ౖ; ◌ై; ◌ె◌ౖ; ) TELUGU VOWEL SIGN AI
+0CC0;0CC0;0CBF 0CD5;0CC0;0CBF 0CD5; # (ೀ; ೀ; ◌ೀ; ೀ; ◌ೀ; ) KANNADA VOWEL SIGN II
+0CC7;0CC7;0CC6 0CD5;0CC7;0CC6 0CD5; # (ೇ; ೇ; ◌ೇ; ೇ; ◌ೇ; ) KANNADA VOWEL SIGN EE
+0CC8;0CC8;0CC6 0CD6;0CC8;0CC6 0CD6; # (ೈ; ೈ; ◌ೈ; ೈ; ◌ೈ; ) KANNADA VOWEL SIGN AI
+0CCA;0CCA;0CC6 0CC2;0CCA;0CC6 0CC2; # (ೊ; ೊ; ◌ೊ; ೊ; ◌ೊ; ) KANNADA VOWEL SIGN O
+0CCB;0CCB;0CC6 0CC2 0CD5;0CCB;0CC6 0CC2 0CD5; # (ೋ; ೋ; ◌ೋ; ೋ; ◌ೋ; ) KANNADA VOWEL SIGN OO
+0D4A;0D4A;0D46 0D3E;0D4A;0D46 0D3E; # (ൊ; ൊ; ൊ; ൊ; ൊ; ) MALAYALAM VOWEL SIGN O
+0D4B;0D4B;0D47 0D3E;0D4B;0D47 0D3E; # (ോ; ോ; ോ; ോ; ോ; ) MALAYALAM VOWEL SIGN OO
+0D4C;0D4C;0D46 0D57;0D4C;0D46 0D57; # (ൌ; ൌ; ൌ; ൌ; ൌ; ) MALAYALAM VOWEL SIGN AU
+0DDA;0DDA;0DD9 0DCA;0DDA;0DD9 0DCA; # (ේ; ේ; ෙ◌්; ේ; ෙ◌්; ) SINHALA VOWEL SIGN DIGA KOMBUVA
+0DDC;0DDC;0DD9 0DCF;0DDC;0DD9 0DCF; # (à·œ; à·œ; à·™à·; à·œ; à·™à·; ) SINHALA VOWEL SIGN KOMBUVA HAA AELA-PILLA
+0DDD;0DDD;0DD9 0DCF 0DCA;0DDD;0DD9 0DCF 0DCA; # (à·; à·; à·™à·â—Œà·Š; à·; à·™à·â—Œà·Š; ) SINHALA VOWEL SIGN KOMBUVA HAA DIGA AELA-PILLA
+0DDE;0DDE;0DD9 0DDF;0DDE;0DD9 0DDF; # (ෞ; ෞ; ෞ; ෞ; ෞ; ) SINHALA VOWEL SIGN KOMBUVA HAA GAYANUKITTA
+0E33;0E33;0E33;0E4D 0E32;0E4D 0E32; # (ำ; ำ; ำ; â—Œà¹à¸²; â—Œà¹à¸²; ) THAI CHARACTER SARA AM
+0EB3;0EB3;0EB3;0ECD 0EB2;0ECD 0EB2; # (ຳ; ຳ; ຳ; â—Œà»àº²; â—Œà»àº²; ) LAO VOWEL SIGN AM
+0EDC;0EDC;0EDC;0EAB 0E99;0EAB 0E99; # (ໜ; ໜ; ໜ; ຫນ; ຫນ; ) LAO HO NO
+0EDD;0EDD;0EDD;0EAB 0EA1;0EAB 0EA1; # (à»; à»; à»; ຫມ; ຫມ; ) LAO HO MO
+0F0C;0F0C;0F0C;0F0B;0F0B; # (༌; ༌; ༌; ་; ་; ) TIBETAN MARK DELIMITER TSHEG BSTAR
+0F43;0F42 0FB7;0F42 0FB7;0F42 0FB7;0F42 0FB7; # (གྷ; ག◌ྷ; ག◌ྷ; ག◌ྷ; ག◌ྷ; ) TIBETAN LETTER GHA
+0F4D;0F4C 0FB7;0F4C 0FB7;0F4C 0FB7;0F4C 0FB7; # (à½; ཌ◌ྷ; ཌ◌ྷ; ཌ◌ྷ; ཌ◌ྷ; ) TIBETAN LETTER DDHA
+0F52;0F51 0FB7;0F51 0FB7;0F51 0FB7;0F51 0FB7; # (དྷ; ད◌ྷ; ད◌ྷ; ད◌ྷ; ད◌ྷ; ) TIBETAN LETTER DHA
+0F57;0F56 0FB7;0F56 0FB7;0F56 0FB7;0F56 0FB7; # (བྷ; བ◌ྷ; བ◌ྷ; བ◌ྷ; བ◌ྷ; ) TIBETAN LETTER BHA
+0F5C;0F5B 0FB7;0F5B 0FB7;0F5B 0FB7;0F5B 0FB7; # (ཛྷ; ཛ◌ྷ; ཛ◌ྷ; ཛ◌ྷ; ཛ◌ྷ; ) TIBETAN LETTER DZHA
+0F69;0F40 0FB5;0F40 0FB5;0F40 0FB5;0F40 0FB5; # (ཀྵ; ཀ◌ྵ; ཀ◌ྵ; ཀ◌ྵ; ཀ◌ྵ; ) TIBETAN LETTER KSSA
+0F73;0F71 0F72;0F71 0F72;0F71 0F72;0F71 0F72; # (◌ཱི; ◌ཱ◌ི; ◌ཱ◌ི; ◌ཱ◌ི; ◌ཱ◌ི; ) TIBETAN VOWEL SIGN II
+0F75;0F71 0F74;0F71 0F74;0F71 0F74;0F71 0F74; # (◌ཱུ; ◌ཱ◌ུ; ◌ཱ◌ུ; ◌ཱ◌ུ; ◌ཱ◌ུ; ) TIBETAN VOWEL SIGN UU
+0F76;0FB2 0F80;0FB2 0F80;0FB2 0F80;0FB2 0F80; # (◌ྲྀ; ◌ྲ◌ྀ; ◌ྲ◌ྀ; ◌ྲ◌ྀ; ◌ྲ◌ྀ; ) TIBETAN VOWEL SIGN VOCALIC R
+0F77;0F77;0F77;0FB2 0F71 0F80;0FB2 0F71 0F80; # (◌ཷ; ◌ཷ; ◌ཷ; ◌ྲ◌ཱ◌ྀ; ◌ྲ◌ཱ◌ྀ; ) TIBETAN VOWEL SIGN VOCALIC RR
+0F78;0FB3 0F80;0FB3 0F80;0FB3 0F80;0FB3 0F80; # (◌ླྀ; ◌ླ◌ྀ; ◌ླ◌ྀ; ◌ླ◌ྀ; ◌ླ◌ྀ; ) TIBETAN VOWEL SIGN VOCALIC L
+0F79;0F79;0F79;0FB3 0F71 0F80;0FB3 0F71 0F80; # (◌ཹ; ◌ཹ; ◌ཹ; ◌ླ◌ཱ◌ྀ; ◌ླ◌ཱ◌ྀ; ) TIBETAN VOWEL SIGN VOCALIC LL
+0F81;0F71 0F80;0F71 0F80;0F71 0F80;0F71 0F80; # (â—Œà¾; ◌ཱ◌ྀ; ◌ཱ◌ྀ; ◌ཱ◌ྀ; ◌ཱ◌ྀ; ) TIBETAN VOWEL SIGN REVERSED II
+0F93;0F92 0FB7;0F92 0FB7;0F92 0FB7;0F92 0FB7; # (◌ྒྷ; ◌ྒ◌ྷ; ◌ྒ◌ྷ; ◌ྒ◌ྷ; ◌ྒ◌ྷ; ) TIBETAN SUBJOINED LETTER GHA
+0F9D;0F9C 0FB7;0F9C 0FB7;0F9C 0FB7;0F9C 0FB7; # (â—Œà¾; ◌ྜ◌ྷ; ◌ྜ◌ྷ; ◌ྜ◌ྷ; ◌ྜ◌ྷ; ) TIBETAN SUBJOINED LETTER DDHA
+0FA2;0FA1 0FB7;0FA1 0FB7;0FA1 0FB7;0FA1 0FB7; # (◌ྡྷ; ◌ྡ◌ྷ; ◌ྡ◌ྷ; ◌ྡ◌ྷ; ◌ྡ◌ྷ; ) TIBETAN SUBJOINED LETTER DHA
+0FA7;0FA6 0FB7;0FA6 0FB7;0FA6 0FB7;0FA6 0FB7; # (◌ྦྷ; ◌ྦ◌ྷ; ◌ྦ◌ྷ; ◌ྦ◌ྷ; ◌ྦ◌ྷ; ) TIBETAN SUBJOINED LETTER BHA
+0FAC;0FAB 0FB7;0FAB 0FB7;0FAB 0FB7;0FAB 0FB7; # (◌ྫྷ; ◌ྫ◌ྷ; ◌ྫ◌ྷ; ◌ྫ◌ྷ; ◌ྫ◌ྷ; ) TIBETAN SUBJOINED LETTER DZHA
+0FB9;0F90 0FB5;0F90 0FB5;0F90 0FB5;0F90 0FB5; # (◌ྐྵ; â—Œà¾â—Œà¾µ; â—Œà¾â—Œà¾µ; â—Œà¾â—Œà¾µ; â—Œà¾â—Œà¾µ; ) TIBETAN SUBJOINED LETTER KSSA
+1026;1026;1025 102E;1026;1025 102E; # (ဦ; ဦ; ဥ◌ီ; ဦ; ဥ◌ီ; ) MYANMAR LETTER UU
+10FC;10FC;10FC;10DC;10DC; # (ჼ; ჼ; ჼ; ნ; ნ; ) MODIFIER LETTER GEORGIAN NAR
+1B06;1B06;1B05 1B35;1B06;1B05 1B35; # (ᬆ; ᬆ; ᬆ; ᬆ; ᬆ; ) BALINESE LETTER AKARA TEDUNG
+1B08;1B08;1B07 1B35;1B08;1B07 1B35; # (ᬈ; ᬈ; ᬈ; ᬈ; ᬈ; ) BALINESE LETTER IKARA TEDUNG
+1B0A;1B0A;1B09 1B35;1B0A;1B09 1B35; # (ᬊ; ᬊ; ᬊ; ᬊ; ᬊ; ) BALINESE LETTER UKARA TEDUNG
+1B0C;1B0C;1B0B 1B35;1B0C;1B0B 1B35; # (ᬌ; ᬌ; ᬌ; ᬌ; ᬌ; ) BALINESE LETTER RA REPA TEDUNG
+1B0E;1B0E;1B0D 1B35;1B0E;1B0D 1B35; # (ᬎ; ᬎ; á¬á¬µ; ᬎ; á¬á¬µ; ) BALINESE LETTER LA LENGA TEDUNG
+1B12;1B12;1B11 1B35;1B12;1B11 1B35; # (ᬒ; ᬒ; ᬒ; ᬒ; ᬒ; ) BALINESE LETTER OKARA TEDUNG
+1B3B;1B3B;1B3A 1B35;1B3B;1B3A 1B35; # (ᬻ; ᬻ; ◌ᬻ; ᬻ; ◌ᬻ; ) BALINESE VOWEL SIGN RA REPA TEDUNG
+1B3D;1B3D;1B3C 1B35;1B3D;1B3C 1B35; # (ᬽ; ᬽ; ◌ᬽ; ᬽ; ◌ᬽ; ) BALINESE VOWEL SIGN LA LENGA TEDUNG
+1B40;1B40;1B3E 1B35;1B40;1B3E 1B35; # (ᭀ; ᭀ; ᭀ; ᭀ; ᭀ; ) BALINESE VOWEL SIGN TALING TEDUNG
+1B41;1B41;1B3F 1B35;1B41;1B3F 1B35; # (á­; á­; ᭁ; á­; ᭁ; ) BALINESE VOWEL SIGN TALING REPA TEDUNG
+1B43;1B43;1B42 1B35;1B43;1B42 1B35; # (ᭃ; ᭃ; ◌ᭃ; ᭃ; ◌ᭃ; ) BALINESE VOWEL SIGN PEPET TEDUNG
+1D2C;1D2C;1D2C;0041;0041; # (á´¬; á´¬; á´¬; A; A; ) MODIFIER LETTER CAPITAL A
+1D2D;1D2D;1D2D;00C6;00C6; # (ᴭ; ᴭ; ᴭ; Æ; Æ; ) MODIFIER LETTER CAPITAL AE
+1D2E;1D2E;1D2E;0042;0042; # (á´®; á´®; á´®; B; B; ) MODIFIER LETTER CAPITAL B
+1D30;1D30;1D30;0044;0044; # (á´°; á´°; á´°; D; D; ) MODIFIER LETTER CAPITAL D
+1D31;1D31;1D31;0045;0045; # (á´±; á´±; á´±; E; E; ) MODIFIER LETTER CAPITAL E
+1D32;1D32;1D32;018E;018E; # (á´²; á´²; á´²; ÆŽ; ÆŽ; ) MODIFIER LETTER CAPITAL REVERSED E
+1D33;1D33;1D33;0047;0047; # (á´³; á´³; á´³; G; G; ) MODIFIER LETTER CAPITAL G
+1D34;1D34;1D34;0048;0048; # (á´´; á´´; á´´; H; H; ) MODIFIER LETTER CAPITAL H
+1D35;1D35;1D35;0049;0049; # (á´µ; á´µ; á´µ; I; I; ) MODIFIER LETTER CAPITAL I
+1D36;1D36;1D36;004A;004A; # (á´¶; á´¶; á´¶; J; J; ) MODIFIER LETTER CAPITAL J
+1D37;1D37;1D37;004B;004B; # (á´·; á´·; á´·; K; K; ) MODIFIER LETTER CAPITAL K
+1D38;1D38;1D38;004C;004C; # (á´¸; á´¸; á´¸; L; L; ) MODIFIER LETTER CAPITAL L
+1D39;1D39;1D39;004D;004D; # (á´¹; á´¹; á´¹; M; M; ) MODIFIER LETTER CAPITAL M
+1D3A;1D3A;1D3A;004E;004E; # (á´º; á´º; á´º; N; N; ) MODIFIER LETTER CAPITAL N
+1D3C;1D3C;1D3C;004F;004F; # (á´¼; á´¼; á´¼; O; O; ) MODIFIER LETTER CAPITAL O
+1D3D;1D3D;1D3D;0222;0222; # (ᴽ; ᴽ; ᴽ; Ȣ; Ȣ; ) MODIFIER LETTER CAPITAL OU
+1D3E;1D3E;1D3E;0050;0050; # (á´¾; á´¾; á´¾; P; P; ) MODIFIER LETTER CAPITAL P
+1D3F;1D3F;1D3F;0052;0052; # (á´¿; á´¿; á´¿; R; R; ) MODIFIER LETTER CAPITAL R
+1D40;1D40;1D40;0054;0054; # (áµ€; áµ€; áµ€; T; T; ) MODIFIER LETTER CAPITAL T
+1D41;1D41;1D41;0055;0055; # (áµ; áµ; áµ; U; U; ) MODIFIER LETTER CAPITAL U
+1D42;1D42;1D42;0057;0057; # (ᵂ; ᵂ; ᵂ; W; W; ) MODIFIER LETTER CAPITAL W
+1D43;1D43;1D43;0061;0061; # (ᵃ; ᵃ; ᵃ; a; a; ) MODIFIER LETTER SMALL A
+1D44;1D44;1D44;0250;0250; # (ᵄ; ᵄ; ᵄ; É; É; ) MODIFIER LETTER SMALL TURNED A
+1D45;1D45;1D45;0251;0251; # (áµ…; áµ…; áµ…; É‘; É‘; ) MODIFIER LETTER SMALL ALPHA
+1D46;1D46;1D46;1D02;1D02; # (ᵆ; ᵆ; ᵆ; ᴂ; ᴂ; ) MODIFIER LETTER SMALL TURNED AE
+1D47;1D47;1D47;0062;0062; # (ᵇ; ᵇ; ᵇ; b; b; ) MODIFIER LETTER SMALL B
+1D48;1D48;1D48;0064;0064; # (ᵈ; ᵈ; ᵈ; d; d; ) MODIFIER LETTER SMALL D
+1D49;1D49;1D49;0065;0065; # (ᵉ; ᵉ; ᵉ; e; e; ) MODIFIER LETTER SMALL E
+1D4A;1D4A;1D4A;0259;0259; # (ᵊ; ᵊ; ᵊ; ə; ə; ) MODIFIER LETTER SMALL SCHWA
+1D4B;1D4B;1D4B;025B;025B; # (ᵋ; ᵋ; ᵋ; ɛ; ɛ; ) MODIFIER LETTER SMALL OPEN E
+1D4C;1D4C;1D4C;025C;025C; # (ᵌ; ᵌ; ᵌ; ɜ; ɜ; ) MODIFIER LETTER SMALL TURNED OPEN E
+1D4D;1D4D;1D4D;0067;0067; # (áµ; áµ; áµ; g; g; ) MODIFIER LETTER SMALL G
+1D4F;1D4F;1D4F;006B;006B; # (áµ; áµ; áµ; k; k; ) MODIFIER LETTER SMALL K
+1D50;1D50;1D50;006D;006D; # (áµ; áµ; áµ; m; m; ) MODIFIER LETTER SMALL M
+1D51;1D51;1D51;014B;014B; # (ᵑ; ᵑ; ᵑ; ŋ; ŋ; ) MODIFIER LETTER SMALL ENG
+1D52;1D52;1D52;006F;006F; # (áµ’; áµ’; áµ’; o; o; ) MODIFIER LETTER SMALL O
+1D53;1D53;1D53;0254;0254; # (ᵓ; ᵓ; ᵓ; ɔ; ɔ; ) MODIFIER LETTER SMALL OPEN O
+1D54;1D54;1D54;1D16;1D16; # (áµ”; áµ”; áµ”; á´–; á´–; ) MODIFIER LETTER SMALL TOP HALF O
+1D55;1D55;1D55;1D17;1D17; # (ᵕ; ᵕ; ᵕ; ᴗ; ᴗ; ) MODIFIER LETTER SMALL BOTTOM HALF O
+1D56;1D56;1D56;0070;0070; # (áµ–; áµ–; áµ–; p; p; ) MODIFIER LETTER SMALL P
+1D57;1D57;1D57;0074;0074; # (áµ—; áµ—; áµ—; t; t; ) MODIFIER LETTER SMALL T
+1D58;1D58;1D58;0075;0075; # (ᵘ; ᵘ; ᵘ; u; u; ) MODIFIER LETTER SMALL U
+1D59;1D59;1D59;1D1D;1D1D; # (áµ™; áµ™; áµ™; á´; á´; ) MODIFIER LETTER SMALL SIDEWAYS U
+1D5A;1D5A;1D5A;026F;026F; # (ᵚ; ᵚ; ᵚ; ɯ; ɯ; ) MODIFIER LETTER SMALL TURNED M
+1D5B;1D5B;1D5B;0076;0076; # (áµ›; áµ›; áµ›; v; v; ) MODIFIER LETTER SMALL V
+1D5C;1D5C;1D5C;1D25;1D25; # (ᵜ; ᵜ; ᵜ; ᴥ; ᴥ; ) MODIFIER LETTER SMALL AIN
+1D5D;1D5D;1D5D;03B2;03B2; # (áµ; áµ; áµ; β; β; ) MODIFIER LETTER SMALL BETA
+1D5E;1D5E;1D5E;03B3;03B3; # (ᵞ; ᵞ; ᵞ; γ; γ; ) MODIFIER LETTER SMALL GREEK GAMMA
+1D5F;1D5F;1D5F;03B4;03B4; # (ᵟ; ᵟ; ᵟ; δ; δ; ) MODIFIER LETTER SMALL DELTA
+1D60;1D60;1D60;03C6;03C6; # (ᵠ; ᵠ; ᵠ; φ; φ; ) MODIFIER LETTER SMALL GREEK PHI
+1D61;1D61;1D61;03C7;03C7; # (ᵡ; ᵡ; ᵡ; χ; χ; ) MODIFIER LETTER SMALL CHI
+1D62;1D62;1D62;0069;0069; # (áµ¢; áµ¢; áµ¢; i; i; ) LATIN SUBSCRIPT SMALL LETTER I
+1D63;1D63;1D63;0072;0072; # (áµ£; áµ£; áµ£; r; r; ) LATIN SUBSCRIPT SMALL LETTER R
+1D64;1D64;1D64;0075;0075; # (ᵤ; ᵤ; ᵤ; u; u; ) LATIN SUBSCRIPT SMALL LETTER U
+1D65;1D65;1D65;0076;0076; # (áµ¥; áµ¥; áµ¥; v; v; ) LATIN SUBSCRIPT SMALL LETTER V
+1D66;1D66;1D66;03B2;03B2; # (ᵦ; ᵦ; ᵦ; β; β; ) GREEK SUBSCRIPT SMALL LETTER BETA
+1D67;1D67;1D67;03B3;03B3; # (ᵧ; ᵧ; ᵧ; γ; γ; ) GREEK SUBSCRIPT SMALL LETTER GAMMA
+1D68;1D68;1D68;03C1;03C1; # (ᵨ; ᵨ; ᵨ; Ï; Ï; ) GREEK SUBSCRIPT SMALL LETTER RHO
+1D69;1D69;1D69;03C6;03C6; # (ᵩ; ᵩ; ᵩ; φ; φ; ) GREEK SUBSCRIPT SMALL LETTER PHI
+1D6A;1D6A;1D6A;03C7;03C7; # (ᵪ; ᵪ; ᵪ; χ; χ; ) GREEK SUBSCRIPT SMALL LETTER CHI
+1D78;1D78;1D78;043D;043D; # (ᵸ; ᵸ; ᵸ; н; н; ) MODIFIER LETTER CYRILLIC EN
+1D9B;1D9B;1D9B;0252;0252; # (ᶛ; ᶛ; ᶛ; ɒ; ɒ; ) MODIFIER LETTER SMALL TURNED ALPHA
+1D9C;1D9C;1D9C;0063;0063; # (ᶜ; ᶜ; ᶜ; c; c; ) MODIFIER LETTER SMALL C
+1D9D;1D9D;1D9D;0255;0255; # (á¶; á¶; á¶; É•; É•; ) MODIFIER LETTER SMALL C WITH CURL
+1D9E;1D9E;1D9E;00F0;00F0; # (ᶞ; ᶞ; ᶞ; ð; ð; ) MODIFIER LETTER SMALL ETH
+1D9F;1D9F;1D9F;025C;025C; # (ᶟ; ᶟ; ᶟ; ɜ; ɜ; ) MODIFIER LETTER SMALL REVERSED OPEN E
+1DA0;1DA0;1DA0;0066;0066; # (ᶠ; ᶠ; ᶠ; f; f; ) MODIFIER LETTER SMALL F
+1DA1;1DA1;1DA1;025F;025F; # (ᶡ; ᶡ; ᶡ; ɟ; ɟ; ) MODIFIER LETTER SMALL DOTLESS J WITH STROKE
+1DA2;1DA2;1DA2;0261;0261; # (ᶢ; ᶢ; ᶢ; ɡ; ɡ; ) MODIFIER LETTER SMALL SCRIPT G
+1DA3;1DA3;1DA3;0265;0265; # (ᶣ; ᶣ; ᶣ; ɥ; ɥ; ) MODIFIER LETTER SMALL TURNED H
+1DA4;1DA4;1DA4;0268;0268; # (ᶤ; ᶤ; ᶤ; ɨ; ɨ; ) MODIFIER LETTER SMALL I WITH STROKE
+1DA5;1DA5;1DA5;0269;0269; # (ᶥ; ᶥ; ᶥ; ɩ; ɩ; ) MODIFIER LETTER SMALL IOTA
+1DA6;1DA6;1DA6;026A;026A; # (ᶦ; ᶦ; ᶦ; ɪ; ɪ; ) MODIFIER LETTER SMALL CAPITAL I
+1DA7;1DA7;1DA7;1D7B;1D7B; # (ᶧ; ᶧ; ᶧ; ᵻ; ᵻ; ) MODIFIER LETTER SMALL CAPITAL I WITH STROKE
+1DA8;1DA8;1DA8;029D;029D; # (ᶨ; ᶨ; ᶨ; Ê; Ê; ) MODIFIER LETTER SMALL J WITH CROSSED-TAIL
+1DA9;1DA9;1DA9;026D;026D; # (ᶩ; ᶩ; ᶩ; ɭ; ɭ; ) MODIFIER LETTER SMALL L WITH RETROFLEX HOOK
+1DAA;1DAA;1DAA;1D85;1D85; # (ᶪ; ᶪ; ᶪ; ᶅ; ᶅ; ) MODIFIER LETTER SMALL L WITH PALATAL HOOK
+1DAB;1DAB;1DAB;029F;029F; # (ᶫ; ᶫ; ᶫ; ʟ; ʟ; ) MODIFIER LETTER SMALL CAPITAL L
+1DAC;1DAC;1DAC;0271;0271; # (ᶬ; ᶬ; ᶬ; ɱ; ɱ; ) MODIFIER LETTER SMALL M WITH HOOK
+1DAD;1DAD;1DAD;0270;0270; # (ᶭ; ᶭ; ᶭ; ɰ; ɰ; ) MODIFIER LETTER SMALL TURNED M WITH LONG LEG
+1DAE;1DAE;1DAE;0272;0272; # (ᶮ; ᶮ; ᶮ; ɲ; ɲ; ) MODIFIER LETTER SMALL N WITH LEFT HOOK
+1DAF;1DAF;1DAF;0273;0273; # (ᶯ; ᶯ; ᶯ; ɳ; ɳ; ) MODIFIER LETTER SMALL N WITH RETROFLEX HOOK
+1DB0;1DB0;1DB0;0274;0274; # (ᶰ; ᶰ; ᶰ; ɴ; ɴ; ) MODIFIER LETTER SMALL CAPITAL N
+1DB1;1DB1;1DB1;0275;0275; # (ᶱ; ᶱ; ᶱ; ɵ; ɵ; ) MODIFIER LETTER SMALL BARRED O
+1DB2;1DB2;1DB2;0278;0278; # (ᶲ; ᶲ; ᶲ; ɸ; ɸ; ) MODIFIER LETTER SMALL PHI
+1DB3;1DB3;1DB3;0282;0282; # (ᶳ; ᶳ; ᶳ; ʂ; ʂ; ) MODIFIER LETTER SMALL S WITH HOOK
+1DB4;1DB4;1DB4;0283;0283; # (ᶴ; ᶴ; ᶴ; ʃ; ʃ; ) MODIFIER LETTER SMALL ESH
+1DB5;1DB5;1DB5;01AB;01AB; # (ᶵ; ᶵ; ᶵ; ƫ; ƫ; ) MODIFIER LETTER SMALL T WITH PALATAL HOOK
+1DB6;1DB6;1DB6;0289;0289; # (ᶶ; ᶶ; ᶶ; ʉ; ʉ; ) MODIFIER LETTER SMALL U BAR
+1DB7;1DB7;1DB7;028A;028A; # (ᶷ; ᶷ; ᶷ; ʊ; ʊ; ) MODIFIER LETTER SMALL UPSILON
+1DB8;1DB8;1DB8;1D1C;1D1C; # (ᶸ; ᶸ; ᶸ; ᴜ; ᴜ; ) MODIFIER LETTER SMALL CAPITAL U
+1DB9;1DB9;1DB9;028B;028B; # (ᶹ; ᶹ; ᶹ; ʋ; ʋ; ) MODIFIER LETTER SMALL V WITH HOOK
+1DBA;1DBA;1DBA;028C;028C; # (ᶺ; ᶺ; ᶺ; ʌ; ʌ; ) MODIFIER LETTER SMALL TURNED V
+1DBB;1DBB;1DBB;007A;007A; # (ᶻ; ᶻ; ᶻ; z; z; ) MODIFIER LETTER SMALL Z
+1DBC;1DBC;1DBC;0290;0290; # (ᶼ; ᶼ; ᶼ; Ê; Ê; ) MODIFIER LETTER SMALL Z WITH RETROFLEX HOOK
+1DBD;1DBD;1DBD;0291;0291; # (ᶽ; ᶽ; ᶽ; ʑ; ʑ; ) MODIFIER LETTER SMALL Z WITH CURL
+1DBE;1DBE;1DBE;0292;0292; # (ᶾ; ᶾ; ᶾ; ʒ; ʒ; ) MODIFIER LETTER SMALL EZH
+1DBF;1DBF;1DBF;03B8;03B8; # (ᶿ; ᶿ; ᶿ; θ; θ; ) MODIFIER LETTER SMALL THETA
+1E00;1E00;0041 0325;1E00;0041 0325; # (Ḁ; Ḁ; A◌̥; Ḁ; A◌̥; ) LATIN CAPITAL LETTER A WITH RING BELOW
+1E01;1E01;0061 0325;1E01;0061 0325; # (á¸; á¸; a◌̥; á¸; a◌̥; ) LATIN SMALL LETTER A WITH RING BELOW
+1E02;1E02;0042 0307;1E02;0042 0307; # (Ḃ; Ḃ; B◌̇; Ḃ; B◌̇; ) LATIN CAPITAL LETTER B WITH DOT ABOVE
+1E03;1E03;0062 0307;1E03;0062 0307; # (ḃ; ḃ; b◌̇; ḃ; b◌̇; ) LATIN SMALL LETTER B WITH DOT ABOVE
+1E04;1E04;0042 0323;1E04;0042 0323; # (Ḅ; Ḅ; B◌̣; Ḅ; B◌̣; ) LATIN CAPITAL LETTER B WITH DOT BELOW
+1E05;1E05;0062 0323;1E05;0062 0323; # (ḅ; ḅ; b◌̣; ḅ; b◌̣; ) LATIN SMALL LETTER B WITH DOT BELOW
+1E06;1E06;0042 0331;1E06;0042 0331; # (Ḇ; Ḇ; B◌̱; Ḇ; B◌̱; ) LATIN CAPITAL LETTER B WITH LINE BELOW
+1E07;1E07;0062 0331;1E07;0062 0331; # (ḇ; ḇ; b◌̱; ḇ; b◌̱; ) LATIN SMALL LETTER B WITH LINE BELOW
+1E08;1E08;0043 0327 0301;1E08;0043 0327 0301; # (Ḉ; Ḉ; C◌̧◌Ì; Ḉ; C◌̧◌Ì; ) LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE
+1E09;1E09;0063 0327 0301;1E09;0063 0327 0301; # (ḉ; ḉ; c◌̧◌Ì; ḉ; c◌̧◌Ì; ) LATIN SMALL LETTER C WITH CEDILLA AND ACUTE
+1E0A;1E0A;0044 0307;1E0A;0044 0307; # (Ḋ; Ḋ; D◌̇; Ḋ; D◌̇; ) LATIN CAPITAL LETTER D WITH DOT ABOVE
+1E0B;1E0B;0064 0307;1E0B;0064 0307; # (ḋ; ḋ; d◌̇; ḋ; d◌̇; ) LATIN SMALL LETTER D WITH DOT ABOVE
+1E0C;1E0C;0044 0323;1E0C;0044 0323; # (Ḍ; Ḍ; D◌̣; Ḍ; D◌̣; ) LATIN CAPITAL LETTER D WITH DOT BELOW
+1E0D;1E0D;0064 0323;1E0D;0064 0323; # (á¸; á¸; d◌̣; á¸; d◌̣; ) LATIN SMALL LETTER D WITH DOT BELOW
+1E0E;1E0E;0044 0331;1E0E;0044 0331; # (Ḏ; Ḏ; D◌̱; Ḏ; D◌̱; ) LATIN CAPITAL LETTER D WITH LINE BELOW
+1E0F;1E0F;0064 0331;1E0F;0064 0331; # (á¸; á¸; d◌̱; á¸; d◌̱; ) LATIN SMALL LETTER D WITH LINE BELOW
+1E10;1E10;0044 0327;1E10;0044 0327; # (á¸; á¸; D◌̧; á¸; D◌̧; ) LATIN CAPITAL LETTER D WITH CEDILLA
+1E11;1E11;0064 0327;1E11;0064 0327; # (ḑ; ḑ; d◌̧; ḑ; d◌̧; ) LATIN SMALL LETTER D WITH CEDILLA
+1E12;1E12;0044 032D;1E12;0044 032D; # (Ḓ; Ḓ; D◌̭; Ḓ; D◌̭; ) LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW
+1E13;1E13;0064 032D;1E13;0064 032D; # (ḓ; ḓ; d◌̭; ḓ; d◌̭; ) LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW
+1E14;1E14;0045 0304 0300;1E14;0045 0304 0300; # (Ḕ; Ḕ; E◌̄◌̀; Ḕ; E◌̄◌̀; ) LATIN CAPITAL LETTER E WITH MACRON AND GRAVE
+1E15;1E15;0065 0304 0300;1E15;0065 0304 0300; # (ḕ; ḕ; e◌̄◌̀; ḕ; e◌̄◌̀; ) LATIN SMALL LETTER E WITH MACRON AND GRAVE
+1E16;1E16;0045 0304 0301;1E16;0045 0304 0301; # (Ḗ; Ḗ; E◌̄◌Ì; Ḗ; E◌̄◌Ì; ) LATIN CAPITAL LETTER E WITH MACRON AND ACUTE
+1E17;1E17;0065 0304 0301;1E17;0065 0304 0301; # (ḗ; ḗ; e◌̄◌Ì; ḗ; e◌̄◌Ì; ) LATIN SMALL LETTER E WITH MACRON AND ACUTE
+1E18;1E18;0045 032D;1E18;0045 032D; # (Ḙ; Ḙ; E◌̭; Ḙ; E◌̭; ) LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW
+1E19;1E19;0065 032D;1E19;0065 032D; # (ḙ; ḙ; e◌̭; ḙ; e◌̭; ) LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW
+1E1A;1E1A;0045 0330;1E1A;0045 0330; # (Ḛ; Ḛ; E◌̰; Ḛ; E◌̰; ) LATIN CAPITAL LETTER E WITH TILDE BELOW
+1E1B;1E1B;0065 0330;1E1B;0065 0330; # (ḛ; ḛ; e◌̰; ḛ; e◌̰; ) LATIN SMALL LETTER E WITH TILDE BELOW
+1E1C;1E1C;0045 0327 0306;1E1C;0045 0327 0306; # (Ḝ; Ḝ; E◌̧◌̆; Ḝ; E◌̧◌̆; ) LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE
+1E1D;1E1D;0065 0327 0306;1E1D;0065 0327 0306; # (á¸; á¸; e◌̧◌̆; á¸; e◌̧◌̆; ) LATIN SMALL LETTER E WITH CEDILLA AND BREVE
+1E1E;1E1E;0046 0307;1E1E;0046 0307; # (Ḟ; Ḟ; F◌̇; Ḟ; F◌̇; ) LATIN CAPITAL LETTER F WITH DOT ABOVE
+1E1F;1E1F;0066 0307;1E1F;0066 0307; # (ḟ; ḟ; f◌̇; ḟ; f◌̇; ) LATIN SMALL LETTER F WITH DOT ABOVE
+1E20;1E20;0047 0304;1E20;0047 0304; # (Ḡ; Ḡ; G◌̄; Ḡ; G◌̄; ) LATIN CAPITAL LETTER G WITH MACRON
+1E21;1E21;0067 0304;1E21;0067 0304; # (ḡ; ḡ; g◌̄; ḡ; g◌̄; ) LATIN SMALL LETTER G WITH MACRON
+1E22;1E22;0048 0307;1E22;0048 0307; # (Ḣ; Ḣ; H◌̇; Ḣ; H◌̇; ) LATIN CAPITAL LETTER H WITH DOT ABOVE
+1E23;1E23;0068 0307;1E23;0068 0307; # (ḣ; ḣ; h◌̇; ḣ; h◌̇; ) LATIN SMALL LETTER H WITH DOT ABOVE
+1E24;1E24;0048 0323;1E24;0048 0323; # (Ḥ; Ḥ; H◌̣; Ḥ; H◌̣; ) LATIN CAPITAL LETTER H WITH DOT BELOW
+1E25;1E25;0068 0323;1E25;0068 0323; # (ḥ; ḥ; h◌̣; ḥ; h◌̣; ) LATIN SMALL LETTER H WITH DOT BELOW
+1E26;1E26;0048 0308;1E26;0048 0308; # (Ḧ; Ḧ; H◌̈; Ḧ; H◌̈; ) LATIN CAPITAL LETTER H WITH DIAERESIS
+1E27;1E27;0068 0308;1E27;0068 0308; # (ḧ; ḧ; h◌̈; ḧ; h◌̈; ) LATIN SMALL LETTER H WITH DIAERESIS
+1E28;1E28;0048 0327;1E28;0048 0327; # (Ḩ; Ḩ; H◌̧; Ḩ; H◌̧; ) LATIN CAPITAL LETTER H WITH CEDILLA
+1E29;1E29;0068 0327;1E29;0068 0327; # (ḩ; ḩ; h◌̧; ḩ; h◌̧; ) LATIN SMALL LETTER H WITH CEDILLA
+1E2A;1E2A;0048 032E;1E2A;0048 032E; # (Ḫ; Ḫ; H◌̮; Ḫ; H◌̮; ) LATIN CAPITAL LETTER H WITH BREVE BELOW
+1E2B;1E2B;0068 032E;1E2B;0068 032E; # (ḫ; ḫ; h◌̮; ḫ; h◌̮; ) LATIN SMALL LETTER H WITH BREVE BELOW
+1E2C;1E2C;0049 0330;1E2C;0049 0330; # (Ḭ; Ḭ; I◌̰; Ḭ; I◌̰; ) LATIN CAPITAL LETTER I WITH TILDE BELOW
+1E2D;1E2D;0069 0330;1E2D;0069 0330; # (ḭ; ḭ; i◌̰; ḭ; i◌̰; ) LATIN SMALL LETTER I WITH TILDE BELOW
+1E2E;1E2E;0049 0308 0301;1E2E;0049 0308 0301; # (Ḯ; Ḯ; I◌̈◌Ì; Ḯ; I◌̈◌Ì; ) LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE
+1E2F;1E2F;0069 0308 0301;1E2F;0069 0308 0301; # (ḯ; ḯ; i◌̈◌Ì; ḯ; i◌̈◌Ì; ) LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE
+1E30;1E30;004B 0301;1E30;004B 0301; # (Ḱ; Ḱ; Kâ—ŒÌ; Ḱ; Kâ—ŒÌ; ) LATIN CAPITAL LETTER K WITH ACUTE
+1E31;1E31;006B 0301;1E31;006B 0301; # (ḱ; ḱ; kâ—ŒÌ; ḱ; kâ—ŒÌ; ) LATIN SMALL LETTER K WITH ACUTE
+1E32;1E32;004B 0323;1E32;004B 0323; # (Ḳ; Ḳ; K◌̣; Ḳ; K◌̣; ) LATIN CAPITAL LETTER K WITH DOT BELOW
+1E33;1E33;006B 0323;1E33;006B 0323; # (ḳ; ḳ; k◌̣; ḳ; k◌̣; ) LATIN SMALL LETTER K WITH DOT BELOW
+1E34;1E34;004B 0331;1E34;004B 0331; # (Ḵ; Ḵ; K◌̱; Ḵ; K◌̱; ) LATIN CAPITAL LETTER K WITH LINE BELOW
+1E35;1E35;006B 0331;1E35;006B 0331; # (ḵ; ḵ; k◌̱; ḵ; k◌̱; ) LATIN SMALL LETTER K WITH LINE BELOW
+1E36;1E36;004C 0323;1E36;004C 0323; # (Ḷ; Ḷ; L◌̣; Ḷ; L◌̣; ) LATIN CAPITAL LETTER L WITH DOT BELOW
+1E37;1E37;006C 0323;1E37;006C 0323; # (ḷ; ḷ; l◌̣; ḷ; l◌̣; ) LATIN SMALL LETTER L WITH DOT BELOW
+1E38;1E38;004C 0323 0304;1E38;004C 0323 0304; # (Ḹ; Ḹ; L◌̣◌̄; Ḹ; L◌̣◌̄; ) LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON
+1E39;1E39;006C 0323 0304;1E39;006C 0323 0304; # (ḹ; ḹ; l◌̣◌̄; ḹ; l◌̣◌̄; ) LATIN SMALL LETTER L WITH DOT BELOW AND MACRON
+1E3A;1E3A;004C 0331;1E3A;004C 0331; # (Ḻ; Ḻ; L◌̱; Ḻ; L◌̱; ) LATIN CAPITAL LETTER L WITH LINE BELOW
+1E3B;1E3B;006C 0331;1E3B;006C 0331; # (ḻ; ḻ; l◌̱; ḻ; l◌̱; ) LATIN SMALL LETTER L WITH LINE BELOW
+1E3C;1E3C;004C 032D;1E3C;004C 032D; # (Ḽ; Ḽ; L◌̭; Ḽ; L◌̭; ) LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW
+1E3D;1E3D;006C 032D;1E3D;006C 032D; # (ḽ; ḽ; l◌̭; ḽ; l◌̭; ) LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW
+1E3E;1E3E;004D 0301;1E3E;004D 0301; # (Ḿ; Ḿ; Mâ—ŒÌ; Ḿ; Mâ—ŒÌ; ) LATIN CAPITAL LETTER M WITH ACUTE
+1E3F;1E3F;006D 0301;1E3F;006D 0301; # (ḿ; ḿ; mâ—ŒÌ; ḿ; mâ—ŒÌ; ) LATIN SMALL LETTER M WITH ACUTE
+1E40;1E40;004D 0307;1E40;004D 0307; # (Ṁ; Ṁ; M◌̇; Ṁ; M◌̇; ) LATIN CAPITAL LETTER M WITH DOT ABOVE
+1E41;1E41;006D 0307;1E41;006D 0307; # (á¹; á¹; m◌̇; á¹; m◌̇; ) LATIN SMALL LETTER M WITH DOT ABOVE
+1E42;1E42;004D 0323;1E42;004D 0323; # (Ṃ; Ṃ; M◌̣; Ṃ; M◌̣; ) LATIN CAPITAL LETTER M WITH DOT BELOW
+1E43;1E43;006D 0323;1E43;006D 0323; # (ṃ; ṃ; m◌̣; ṃ; m◌̣; ) LATIN SMALL LETTER M WITH DOT BELOW
+1E44;1E44;004E 0307;1E44;004E 0307; # (Ṅ; Ṅ; N◌̇; Ṅ; N◌̇; ) LATIN CAPITAL LETTER N WITH DOT ABOVE
+1E45;1E45;006E 0307;1E45;006E 0307; # (ṅ; ṅ; n◌̇; ṅ; n◌̇; ) LATIN SMALL LETTER N WITH DOT ABOVE
+1E46;1E46;004E 0323;1E46;004E 0323; # (Ṇ; Ṇ; N◌̣; Ṇ; N◌̣; ) LATIN CAPITAL LETTER N WITH DOT BELOW
+1E47;1E47;006E 0323;1E47;006E 0323; # (ṇ; ṇ; n◌̣; ṇ; n◌̣; ) LATIN SMALL LETTER N WITH DOT BELOW
+1E48;1E48;004E 0331;1E48;004E 0331; # (Ṉ; Ṉ; N◌̱; Ṉ; N◌̱; ) LATIN CAPITAL LETTER N WITH LINE BELOW
+1E49;1E49;006E 0331;1E49;006E 0331; # (ṉ; ṉ; n◌̱; ṉ; n◌̱; ) LATIN SMALL LETTER N WITH LINE BELOW
+1E4A;1E4A;004E 032D;1E4A;004E 032D; # (Ṋ; Ṋ; N◌̭; Ṋ; N◌̭; ) LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW
+1E4B;1E4B;006E 032D;1E4B;006E 032D; # (ṋ; ṋ; n◌̭; ṋ; n◌̭; ) LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW
+1E4C;1E4C;004F 0303 0301;1E4C;004F 0303 0301; # (Ṍ; Ṍ; O◌̃◌Ì; Ṍ; O◌̃◌Ì; ) LATIN CAPITAL LETTER O WITH TILDE AND ACUTE
+1E4D;1E4D;006F 0303 0301;1E4D;006F 0303 0301; # (á¹; á¹; o◌̃◌Ì; á¹; o◌̃◌Ì; ) LATIN SMALL LETTER O WITH TILDE AND ACUTE
+1E4E;1E4E;004F 0303 0308;1E4E;004F 0303 0308; # (Ṏ; Ṏ; O◌̃◌̈; Ṏ; O◌̃◌̈; ) LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS
+1E4F;1E4F;006F 0303 0308;1E4F;006F 0303 0308; # (á¹; á¹; o◌̃◌̈; á¹; o◌̃◌̈; ) LATIN SMALL LETTER O WITH TILDE AND DIAERESIS
+1E50;1E50;004F 0304 0300;1E50;004F 0304 0300; # (á¹; á¹; O◌̄◌̀; á¹; O◌̄◌̀; ) LATIN CAPITAL LETTER O WITH MACRON AND GRAVE
+1E51;1E51;006F 0304 0300;1E51;006F 0304 0300; # (ṑ; ṑ; o◌̄◌̀; ṑ; o◌̄◌̀; ) LATIN SMALL LETTER O WITH MACRON AND GRAVE
+1E52;1E52;004F 0304 0301;1E52;004F 0304 0301; # (á¹’; á¹’; O◌̄◌Ì; á¹’; O◌̄◌Ì; ) LATIN CAPITAL LETTER O WITH MACRON AND ACUTE
+1E53;1E53;006F 0304 0301;1E53;006F 0304 0301; # (ṓ; ṓ; o◌̄◌Ì; ṓ; o◌̄◌Ì; ) LATIN SMALL LETTER O WITH MACRON AND ACUTE
+1E54;1E54;0050 0301;1E54;0050 0301; # (á¹”; á¹”; Pâ—ŒÌ; á¹”; Pâ—ŒÌ; ) LATIN CAPITAL LETTER P WITH ACUTE
+1E55;1E55;0070 0301;1E55;0070 0301; # (ṕ; ṕ; pâ—ŒÌ; ṕ; pâ—ŒÌ; ) LATIN SMALL LETTER P WITH ACUTE
+1E56;1E56;0050 0307;1E56;0050 0307; # (Ṗ; Ṗ; P◌̇; Ṗ; P◌̇; ) LATIN CAPITAL LETTER P WITH DOT ABOVE
+1E57;1E57;0070 0307;1E57;0070 0307; # (ṗ; ṗ; p◌̇; ṗ; p◌̇; ) LATIN SMALL LETTER P WITH DOT ABOVE
+1E58;1E58;0052 0307;1E58;0052 0307; # (Ṙ; Ṙ; R◌̇; Ṙ; R◌̇; ) LATIN CAPITAL LETTER R WITH DOT ABOVE
+1E59;1E59;0072 0307;1E59;0072 0307; # (ṙ; ṙ; r◌̇; ṙ; r◌̇; ) LATIN SMALL LETTER R WITH DOT ABOVE
+1E5A;1E5A;0052 0323;1E5A;0052 0323; # (Ṛ; Ṛ; R◌̣; Ṛ; R◌̣; ) LATIN CAPITAL LETTER R WITH DOT BELOW
+1E5B;1E5B;0072 0323;1E5B;0072 0323; # (ṛ; ṛ; r◌̣; ṛ; r◌̣; ) LATIN SMALL LETTER R WITH DOT BELOW
+1E5C;1E5C;0052 0323 0304;1E5C;0052 0323 0304; # (Ṝ; Ṝ; R◌̣◌̄; Ṝ; R◌̣◌̄; ) LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON
+1E5D;1E5D;0072 0323 0304;1E5D;0072 0323 0304; # (á¹; á¹; r◌̣◌̄; á¹; r◌̣◌̄; ) LATIN SMALL LETTER R WITH DOT BELOW AND MACRON
+1E5E;1E5E;0052 0331;1E5E;0052 0331; # (Ṟ; Ṟ; R◌̱; Ṟ; R◌̱; ) LATIN CAPITAL LETTER R WITH LINE BELOW
+1E5F;1E5F;0072 0331;1E5F;0072 0331; # (ṟ; ṟ; r◌̱; ṟ; r◌̱; ) LATIN SMALL LETTER R WITH LINE BELOW
+1E60;1E60;0053 0307;1E60;0053 0307; # (Ṡ; Ṡ; S◌̇; Ṡ; S◌̇; ) LATIN CAPITAL LETTER S WITH DOT ABOVE
+1E61;1E61;0073 0307;1E61;0073 0307; # (ṡ; ṡ; s◌̇; ṡ; s◌̇; ) LATIN SMALL LETTER S WITH DOT ABOVE
+1E62;1E62;0053 0323;1E62;0053 0323; # (Ṣ; Ṣ; S◌̣; Ṣ; S◌̣; ) LATIN CAPITAL LETTER S WITH DOT BELOW
+1E63;1E63;0073 0323;1E63;0073 0323; # (ṣ; ṣ; s◌̣; ṣ; s◌̣; ) LATIN SMALL LETTER S WITH DOT BELOW
+1E64;1E64;0053 0301 0307;1E64;0053 0301 0307; # (Ṥ; Ṥ; Sâ—ŒÌ◌̇; Ṥ; Sâ—ŒÌ◌̇; ) LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE
+1E65;1E65;0073 0301 0307;1E65;0073 0301 0307; # (á¹¥; á¹¥; sâ—ŒÌ◌̇; á¹¥; sâ—ŒÌ◌̇; ) LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE
+1E66;1E66;0053 030C 0307;1E66;0053 030C 0307; # (Ṧ; Ṧ; S◌̌◌̇; Ṧ; S◌̌◌̇; ) LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE
+1E67;1E67;0073 030C 0307;1E67;0073 030C 0307; # (ṧ; ṧ; s◌̌◌̇; ṧ; s◌̌◌̇; ) LATIN SMALL LETTER S WITH CARON AND DOT ABOVE
+1E68;1E68;0053 0323 0307;1E68;0053 0323 0307; # (Ṩ; Ṩ; S◌̣◌̇; Ṩ; S◌̣◌̇; ) LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE
+1E69;1E69;0073 0323 0307;1E69;0073 0323 0307; # (ṩ; ṩ; s◌̣◌̇; ṩ; s◌̣◌̇; ) LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE
+1E6A;1E6A;0054 0307;1E6A;0054 0307; # (Ṫ; Ṫ; T◌̇; Ṫ; T◌̇; ) LATIN CAPITAL LETTER T WITH DOT ABOVE
+1E6B;1E6B;0074 0307;1E6B;0074 0307; # (ṫ; ṫ; t◌̇; ṫ; t◌̇; ) LATIN SMALL LETTER T WITH DOT ABOVE
+1E6C;1E6C;0054 0323;1E6C;0054 0323; # (Ṭ; Ṭ; T◌̣; Ṭ; T◌̣; ) LATIN CAPITAL LETTER T WITH DOT BELOW
+1E6D;1E6D;0074 0323;1E6D;0074 0323; # (ṭ; ṭ; t◌̣; ṭ; t◌̣; ) LATIN SMALL LETTER T WITH DOT BELOW
+1E6E;1E6E;0054 0331;1E6E;0054 0331; # (Ṯ; Ṯ; T◌̱; Ṯ; T◌̱; ) LATIN CAPITAL LETTER T WITH LINE BELOW
+1E6F;1E6F;0074 0331;1E6F;0074 0331; # (ṯ; ṯ; t◌̱; ṯ; t◌̱; ) LATIN SMALL LETTER T WITH LINE BELOW
+1E70;1E70;0054 032D;1E70;0054 032D; # (Ṱ; Ṱ; T◌̭; Ṱ; T◌̭; ) LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW
+1E71;1E71;0074 032D;1E71;0074 032D; # (ṱ; ṱ; t◌̭; ṱ; t◌̭; ) LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW
+1E72;1E72;0055 0324;1E72;0055 0324; # (Ṳ; Ṳ; U◌̤; Ṳ; U◌̤; ) LATIN CAPITAL LETTER U WITH DIAERESIS BELOW
+1E73;1E73;0075 0324;1E73;0075 0324; # (ṳ; ṳ; u◌̤; ṳ; u◌̤; ) LATIN SMALL LETTER U WITH DIAERESIS BELOW
+1E74;1E74;0055 0330;1E74;0055 0330; # (Ṵ; Ṵ; U◌̰; Ṵ; U◌̰; ) LATIN CAPITAL LETTER U WITH TILDE BELOW
+1E75;1E75;0075 0330;1E75;0075 0330; # (ṵ; ṵ; u◌̰; ṵ; u◌̰; ) LATIN SMALL LETTER U WITH TILDE BELOW
+1E76;1E76;0055 032D;1E76;0055 032D; # (Ṷ; Ṷ; U◌̭; Ṷ; U◌̭; ) LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW
+1E77;1E77;0075 032D;1E77;0075 032D; # (ṷ; ṷ; u◌̭; ṷ; u◌̭; ) LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW
+1E78;1E78;0055 0303 0301;1E78;0055 0303 0301; # (Ṹ; Ṹ; U◌̃◌Ì; Ṹ; U◌̃◌Ì; ) LATIN CAPITAL LETTER U WITH TILDE AND ACUTE
+1E79;1E79;0075 0303 0301;1E79;0075 0303 0301; # (á¹¹; á¹¹; u◌̃◌Ì; á¹¹; u◌̃◌Ì; ) LATIN SMALL LETTER U WITH TILDE AND ACUTE
+1E7A;1E7A;0055 0304 0308;1E7A;0055 0304 0308; # (Ṻ; Ṻ; U◌̄◌̈; Ṻ; U◌̄◌̈; ) LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS
+1E7B;1E7B;0075 0304 0308;1E7B;0075 0304 0308; # (ṻ; ṻ; u◌̄◌̈; ṻ; u◌̄◌̈; ) LATIN SMALL LETTER U WITH MACRON AND DIAERESIS
+1E7C;1E7C;0056 0303;1E7C;0056 0303; # (Ṽ; Ṽ; V◌̃; Ṽ; V◌̃; ) LATIN CAPITAL LETTER V WITH TILDE
+1E7D;1E7D;0076 0303;1E7D;0076 0303; # (ṽ; ṽ; v◌̃; ṽ; v◌̃; ) LATIN SMALL LETTER V WITH TILDE
+1E7E;1E7E;0056 0323;1E7E;0056 0323; # (Ṿ; Ṿ; V◌̣; Ṿ; V◌̣; ) LATIN CAPITAL LETTER V WITH DOT BELOW
+1E7F;1E7F;0076 0323;1E7F;0076 0323; # (ṿ; ṿ; v◌̣; ṿ; v◌̣; ) LATIN SMALL LETTER V WITH DOT BELOW
+1E80;1E80;0057 0300;1E80;0057 0300; # (Ẁ; Ẁ; W◌̀; Ẁ; W◌̀; ) LATIN CAPITAL LETTER W WITH GRAVE
+1E81;1E81;0077 0300;1E81;0077 0300; # (áº; áº; w◌̀; áº; w◌̀; ) LATIN SMALL LETTER W WITH GRAVE
+1E82;1E82;0057 0301;1E82;0057 0301; # (Ẃ; Ẃ; Wâ—ŒÌ; Ẃ; Wâ—ŒÌ; ) LATIN CAPITAL LETTER W WITH ACUTE
+1E83;1E83;0077 0301;1E83;0077 0301; # (ẃ; ẃ; wâ—ŒÌ; ẃ; wâ—ŒÌ; ) LATIN SMALL LETTER W WITH ACUTE
+1E84;1E84;0057 0308;1E84;0057 0308; # (Ẅ; Ẅ; W◌̈; Ẅ; W◌̈; ) LATIN CAPITAL LETTER W WITH DIAERESIS
+1E85;1E85;0077 0308;1E85;0077 0308; # (ẅ; ẅ; w◌̈; ẅ; w◌̈; ) LATIN SMALL LETTER W WITH DIAERESIS
+1E86;1E86;0057 0307;1E86;0057 0307; # (Ẇ; Ẇ; W◌̇; Ẇ; W◌̇; ) LATIN CAPITAL LETTER W WITH DOT ABOVE
+1E87;1E87;0077 0307;1E87;0077 0307; # (ẇ; ẇ; w◌̇; ẇ; w◌̇; ) LATIN SMALL LETTER W WITH DOT ABOVE
+1E88;1E88;0057 0323;1E88;0057 0323; # (Ẉ; Ẉ; W◌̣; Ẉ; W◌̣; ) LATIN CAPITAL LETTER W WITH DOT BELOW
+1E89;1E89;0077 0323;1E89;0077 0323; # (ẉ; ẉ; w◌̣; ẉ; w◌̣; ) LATIN SMALL LETTER W WITH DOT BELOW
+1E8A;1E8A;0058 0307;1E8A;0058 0307; # (Ẋ; Ẋ; X◌̇; Ẋ; X◌̇; ) LATIN CAPITAL LETTER X WITH DOT ABOVE
+1E8B;1E8B;0078 0307;1E8B;0078 0307; # (ẋ; ẋ; x◌̇; ẋ; x◌̇; ) LATIN SMALL LETTER X WITH DOT ABOVE
+1E8C;1E8C;0058 0308;1E8C;0058 0308; # (Ẍ; Ẍ; X◌̈; Ẍ; X◌̈; ) LATIN CAPITAL LETTER X WITH DIAERESIS
+1E8D;1E8D;0078 0308;1E8D;0078 0308; # (áº; áº; x◌̈; áº; x◌̈; ) LATIN SMALL LETTER X WITH DIAERESIS
+1E8E;1E8E;0059 0307;1E8E;0059 0307; # (Ẏ; Ẏ; Y◌̇; Ẏ; Y◌̇; ) LATIN CAPITAL LETTER Y WITH DOT ABOVE
+1E8F;1E8F;0079 0307;1E8F;0079 0307; # (áº; áº; y◌̇; áº; y◌̇; ) LATIN SMALL LETTER Y WITH DOT ABOVE
+1E90;1E90;005A 0302;1E90;005A 0302; # (áº; áº; Z◌̂; áº; Z◌̂; ) LATIN CAPITAL LETTER Z WITH CIRCUMFLEX
+1E91;1E91;007A 0302;1E91;007A 0302; # (ẑ; ẑ; z◌̂; ẑ; z◌̂; ) LATIN SMALL LETTER Z WITH CIRCUMFLEX
+1E92;1E92;005A 0323;1E92;005A 0323; # (Ẓ; Ẓ; Z◌̣; Ẓ; Z◌̣; ) LATIN CAPITAL LETTER Z WITH DOT BELOW
+1E93;1E93;007A 0323;1E93;007A 0323; # (ẓ; ẓ; z◌̣; ẓ; z◌̣; ) LATIN SMALL LETTER Z WITH DOT BELOW
+1E94;1E94;005A 0331;1E94;005A 0331; # (Ẕ; Ẕ; Z◌̱; Ẕ; Z◌̱; ) LATIN CAPITAL LETTER Z WITH LINE BELOW
+1E95;1E95;007A 0331;1E95;007A 0331; # (ẕ; ẕ; z◌̱; ẕ; z◌̱; ) LATIN SMALL LETTER Z WITH LINE BELOW
+1E96;1E96;0068 0331;1E96;0068 0331; # (ẖ; ẖ; h◌̱; ẖ; h◌̱; ) LATIN SMALL LETTER H WITH LINE BELOW
+1E97;1E97;0074 0308;1E97;0074 0308; # (ẗ; ẗ; t◌̈; ẗ; t◌̈; ) LATIN SMALL LETTER T WITH DIAERESIS
+1E98;1E98;0077 030A;1E98;0077 030A; # (ẘ; ẘ; w◌̊; ẘ; w◌̊; ) LATIN SMALL LETTER W WITH RING ABOVE
+1E99;1E99;0079 030A;1E99;0079 030A; # (ẙ; ẙ; y◌̊; ẙ; y◌̊; ) LATIN SMALL LETTER Y WITH RING ABOVE
+1E9A;1E9A;1E9A;0061 02BE;0061 02BE; # (ẚ; ẚ; ẚ; aʾ; aʾ; ) LATIN SMALL LETTER A WITH RIGHT HALF RING
+1E9B;1E9B;017F 0307;1E61;0073 0307; # (ẛ; ẛ; ſ◌̇; ṡ; s◌̇; ) LATIN SMALL LETTER LONG S WITH DOT ABOVE
+1EA0;1EA0;0041 0323;1EA0;0041 0323; # (Ạ; Ạ; A◌̣; Ạ; A◌̣; ) LATIN CAPITAL LETTER A WITH DOT BELOW
+1EA1;1EA1;0061 0323;1EA1;0061 0323; # (ạ; ạ; a◌̣; ạ; a◌̣; ) LATIN SMALL LETTER A WITH DOT BELOW
+1EA2;1EA2;0041 0309;1EA2;0041 0309; # (Ả; Ả; A◌̉; Ả; A◌̉; ) LATIN CAPITAL LETTER A WITH HOOK ABOVE
+1EA3;1EA3;0061 0309;1EA3;0061 0309; # (ả; ả; a◌̉; ả; a◌̉; ) LATIN SMALL LETTER A WITH HOOK ABOVE
+1EA4;1EA4;0041 0302 0301;1EA4;0041 0302 0301; # (Ấ; Ấ; A◌̂◌Ì; Ấ; A◌̂◌Ì; ) LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE
+1EA5;1EA5;0061 0302 0301;1EA5;0061 0302 0301; # (ấ; ấ; a◌̂◌Ì; ấ; a◌̂◌Ì; ) LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
+1EA6;1EA6;0041 0302 0300;1EA6;0041 0302 0300; # (Ầ; Ầ; A◌̂◌̀; Ầ; A◌̂◌̀; ) LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE
+1EA7;1EA7;0061 0302 0300;1EA7;0061 0302 0300; # (ầ; ầ; a◌̂◌̀; ầ; a◌̂◌̀; ) LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE
+1EA8;1EA8;0041 0302 0309;1EA8;0041 0302 0309; # (Ẩ; Ẩ; A◌̂◌̉; Ẩ; A◌̂◌̉; ) LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+1EA9;1EA9;0061 0302 0309;1EA9;0061 0302 0309; # (ẩ; ẩ; a◌̂◌̉; ẩ; a◌̂◌̉; ) LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+1EAA;1EAA;0041 0302 0303;1EAA;0041 0302 0303; # (Ẫ; Ẫ; A◌̂◌̃; Ẫ; A◌̂◌̃; ) LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE
+1EAB;1EAB;0061 0302 0303;1EAB;0061 0302 0303; # (ẫ; ẫ; a◌̂◌̃; ẫ; a◌̂◌̃; ) LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE
+1EAC;1EAC;0041 0323 0302;1EAC;0041 0323 0302; # (Ậ; Ậ; A◌̣◌̂; Ậ; A◌̣◌̂; ) LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+1EAD;1EAD;0061 0323 0302;1EAD;0061 0323 0302; # (ậ; ậ; a◌̣◌̂; ậ; a◌̣◌̂; ) LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+1EAE;1EAE;0041 0306 0301;1EAE;0041 0306 0301; # (Ắ; Ắ; A◌̆◌Ì; Ắ; A◌̆◌Ì; ) LATIN CAPITAL LETTER A WITH BREVE AND ACUTE
+1EAF;1EAF;0061 0306 0301;1EAF;0061 0306 0301; # (ắ; ắ; a◌̆◌Ì; ắ; a◌̆◌Ì; ) LATIN SMALL LETTER A WITH BREVE AND ACUTE
+1EB0;1EB0;0041 0306 0300;1EB0;0041 0306 0300; # (Ằ; Ằ; A◌̆◌̀; Ằ; A◌̆◌̀; ) LATIN CAPITAL LETTER A WITH BREVE AND GRAVE
+1EB1;1EB1;0061 0306 0300;1EB1;0061 0306 0300; # (ằ; ằ; a◌̆◌̀; ằ; a◌̆◌̀; ) LATIN SMALL LETTER A WITH BREVE AND GRAVE
+1EB2;1EB2;0041 0306 0309;1EB2;0041 0306 0309; # (Ẳ; Ẳ; A◌̆◌̉; Ẳ; A◌̆◌̉; ) LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE
+1EB3;1EB3;0061 0306 0309;1EB3;0061 0306 0309; # (ẳ; ẳ; a◌̆◌̉; ẳ; a◌̆◌̉; ) LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE
+1EB4;1EB4;0041 0306 0303;1EB4;0041 0306 0303; # (Ẵ; Ẵ; A◌̆◌̃; Ẵ; A◌̆◌̃; ) LATIN CAPITAL LETTER A WITH BREVE AND TILDE
+1EB5;1EB5;0061 0306 0303;1EB5;0061 0306 0303; # (ẵ; ẵ; a◌̆◌̃; ẵ; a◌̆◌̃; ) LATIN SMALL LETTER A WITH BREVE AND TILDE
+1EB6;1EB6;0041 0323 0306;1EB6;0041 0323 0306; # (Ặ; Ặ; A◌̣◌̆; Ặ; A◌̣◌̆; ) LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW
+1EB7;1EB7;0061 0323 0306;1EB7;0061 0323 0306; # (ặ; ặ; a◌̣◌̆; ặ; a◌̣◌̆; ) LATIN SMALL LETTER A WITH BREVE AND DOT BELOW
+1EB8;1EB8;0045 0323;1EB8;0045 0323; # (Ẹ; Ẹ; E◌̣; Ẹ; E◌̣; ) LATIN CAPITAL LETTER E WITH DOT BELOW
+1EB9;1EB9;0065 0323;1EB9;0065 0323; # (ẹ; ẹ; e◌̣; ẹ; e◌̣; ) LATIN SMALL LETTER E WITH DOT BELOW
+1EBA;1EBA;0045 0309;1EBA;0045 0309; # (Ẻ; Ẻ; E◌̉; Ẻ; E◌̉; ) LATIN CAPITAL LETTER E WITH HOOK ABOVE
+1EBB;1EBB;0065 0309;1EBB;0065 0309; # (ẻ; ẻ; e◌̉; ẻ; e◌̉; ) LATIN SMALL LETTER E WITH HOOK ABOVE
+1EBC;1EBC;0045 0303;1EBC;0045 0303; # (Ẽ; Ẽ; E◌̃; Ẽ; E◌̃; ) LATIN CAPITAL LETTER E WITH TILDE
+1EBD;1EBD;0065 0303;1EBD;0065 0303; # (ẽ; ẽ; e◌̃; ẽ; e◌̃; ) LATIN SMALL LETTER E WITH TILDE
+1EBE;1EBE;0045 0302 0301;1EBE;0045 0302 0301; # (Ế; Ế; E◌̂◌Ì; Ế; E◌̂◌Ì; ) LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE
+1EBF;1EBF;0065 0302 0301;1EBF;0065 0302 0301; # (ế; ế; e◌̂◌Ì; ế; e◌̂◌Ì; ) LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE
+1EC0;1EC0;0045 0302 0300;1EC0;0045 0302 0300; # (Ề; Ề; E◌̂◌̀; Ề; E◌̂◌̀; ) LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE
+1EC1;1EC1;0065 0302 0300;1EC1;0065 0302 0300; # (á»; á»; e◌̂◌̀; á»; e◌̂◌̀; ) LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE
+1EC2;1EC2;0045 0302 0309;1EC2;0045 0302 0309; # (Ể; Ể; E◌̂◌̉; Ể; E◌̂◌̉; ) LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+1EC3;1EC3;0065 0302 0309;1EC3;0065 0302 0309; # (ể; ể; e◌̂◌̉; ể; e◌̂◌̉; ) LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+1EC4;1EC4;0045 0302 0303;1EC4;0045 0302 0303; # (Ễ; Ễ; E◌̂◌̃; Ễ; E◌̂◌̃; ) LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE
+1EC5;1EC5;0065 0302 0303;1EC5;0065 0302 0303; # (ễ; ễ; e◌̂◌̃; ễ; e◌̂◌̃; ) LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE
+1EC6;1EC6;0045 0323 0302;1EC6;0045 0323 0302; # (Ệ; Ệ; E◌̣◌̂; Ệ; E◌̣◌̂; ) LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+1EC7;1EC7;0065 0323 0302;1EC7;0065 0323 0302; # (ệ; ệ; e◌̣◌̂; ệ; e◌̣◌̂; ) LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+1EC8;1EC8;0049 0309;1EC8;0049 0309; # (Ỉ; Ỉ; I◌̉; Ỉ; I◌̉; ) LATIN CAPITAL LETTER I WITH HOOK ABOVE
+1EC9;1EC9;0069 0309;1EC9;0069 0309; # (ỉ; ỉ; i◌̉; ỉ; i◌̉; ) LATIN SMALL LETTER I WITH HOOK ABOVE
+1ECA;1ECA;0049 0323;1ECA;0049 0323; # (Ị; Ị; I◌̣; Ị; I◌̣; ) LATIN CAPITAL LETTER I WITH DOT BELOW
+1ECB;1ECB;0069 0323;1ECB;0069 0323; # (ị; ị; i◌̣; ị; i◌̣; ) LATIN SMALL LETTER I WITH DOT BELOW
+1ECC;1ECC;004F 0323;1ECC;004F 0323; # (Ọ; Ọ; O◌̣; Ọ; O◌̣; ) LATIN CAPITAL LETTER O WITH DOT BELOW
+1ECD;1ECD;006F 0323;1ECD;006F 0323; # (á»; á»; o◌̣; á»; o◌̣; ) LATIN SMALL LETTER O WITH DOT BELOW
+1ECE;1ECE;004F 0309;1ECE;004F 0309; # (Ỏ; Ỏ; O◌̉; Ỏ; O◌̉; ) LATIN CAPITAL LETTER O WITH HOOK ABOVE
+1ECF;1ECF;006F 0309;1ECF;006F 0309; # (á»; á»; o◌̉; á»; o◌̉; ) LATIN SMALL LETTER O WITH HOOK ABOVE
+1ED0;1ED0;004F 0302 0301;1ED0;004F 0302 0301; # (á»; á»; O◌̂◌Ì; á»; O◌̂◌Ì; ) LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE
+1ED1;1ED1;006F 0302 0301;1ED1;006F 0302 0301; # (ố; ố; o◌̂◌Ì; ố; o◌̂◌Ì; ) LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE
+1ED2;1ED2;004F 0302 0300;1ED2;004F 0302 0300; # (Ồ; Ồ; O◌̂◌̀; Ồ; O◌̂◌̀; ) LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE
+1ED3;1ED3;006F 0302 0300;1ED3;006F 0302 0300; # (ồ; ồ; o◌̂◌̀; ồ; o◌̂◌̀; ) LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE
+1ED4;1ED4;004F 0302 0309;1ED4;004F 0302 0309; # (Ổ; Ổ; O◌̂◌̉; Ổ; O◌̂◌̉; ) LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+1ED5;1ED5;006F 0302 0309;1ED5;006F 0302 0309; # (ổ; ổ; o◌̂◌̉; ổ; o◌̂◌̉; ) LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+1ED6;1ED6;004F 0302 0303;1ED6;004F 0302 0303; # (Ỗ; Ỗ; O◌̂◌̃; Ỗ; O◌̂◌̃; ) LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE
+1ED7;1ED7;006F 0302 0303;1ED7;006F 0302 0303; # (ỗ; ỗ; o◌̂◌̃; ỗ; o◌̂◌̃; ) LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE
+1ED8;1ED8;004F 0323 0302;1ED8;004F 0323 0302; # (Ộ; Ộ; O◌̣◌̂; Ộ; O◌̣◌̂; ) LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+1ED9;1ED9;006F 0323 0302;1ED9;006F 0323 0302; # (ộ; ộ; o◌̣◌̂; ộ; o◌̣◌̂; ) LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+1EDA;1EDA;004F 031B 0301;1EDA;004F 031B 0301; # (Ớ; Ớ; O◌̛◌Ì; Ớ; O◌̛◌Ì; ) LATIN CAPITAL LETTER O WITH HORN AND ACUTE
+1EDB;1EDB;006F 031B 0301;1EDB;006F 031B 0301; # (á»›; á»›; o◌̛◌Ì; á»›; o◌̛◌Ì; ) LATIN SMALL LETTER O WITH HORN AND ACUTE
+1EDC;1EDC;004F 031B 0300;1EDC;004F 031B 0300; # (Ờ; Ờ; O◌̛◌̀; Ờ; O◌̛◌̀; ) LATIN CAPITAL LETTER O WITH HORN AND GRAVE
+1EDD;1EDD;006F 031B 0300;1EDD;006F 031B 0300; # (á»; á»; o◌̛◌̀; á»; o◌̛◌̀; ) LATIN SMALL LETTER O WITH HORN AND GRAVE
+1EDE;1EDE;004F 031B 0309;1EDE;004F 031B 0309; # (Ở; Ở; O◌̛◌̉; Ở; O◌̛◌̉; ) LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE
+1EDF;1EDF;006F 031B 0309;1EDF;006F 031B 0309; # (ở; ở; o◌̛◌̉; ở; o◌̛◌̉; ) LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE
+1EE0;1EE0;004F 031B 0303;1EE0;004F 031B 0303; # (Ỡ; Ỡ; O◌̛◌̃; Ỡ; O◌̛◌̃; ) LATIN CAPITAL LETTER O WITH HORN AND TILDE
+1EE1;1EE1;006F 031B 0303;1EE1;006F 031B 0303; # (ỡ; ỡ; o◌̛◌̃; ỡ; o◌̛◌̃; ) LATIN SMALL LETTER O WITH HORN AND TILDE
+1EE2;1EE2;004F 031B 0323;1EE2;004F 031B 0323; # (Ợ; Ợ; O◌̛◌̣; Ợ; O◌̛◌̣; ) LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW
+1EE3;1EE3;006F 031B 0323;1EE3;006F 031B 0323; # (ợ; ợ; o◌̛◌̣; ợ; o◌̛◌̣; ) LATIN SMALL LETTER O WITH HORN AND DOT BELOW
+1EE4;1EE4;0055 0323;1EE4;0055 0323; # (Ụ; Ụ; U◌̣; Ụ; U◌̣; ) LATIN CAPITAL LETTER U WITH DOT BELOW
+1EE5;1EE5;0075 0323;1EE5;0075 0323; # (ụ; ụ; u◌̣; ụ; u◌̣; ) LATIN SMALL LETTER U WITH DOT BELOW
+1EE6;1EE6;0055 0309;1EE6;0055 0309; # (Ủ; Ủ; U◌̉; Ủ; U◌̉; ) LATIN CAPITAL LETTER U WITH HOOK ABOVE
+1EE7;1EE7;0075 0309;1EE7;0075 0309; # (ủ; ủ; u◌̉; ủ; u◌̉; ) LATIN SMALL LETTER U WITH HOOK ABOVE
+1EE8;1EE8;0055 031B 0301;1EE8;0055 031B 0301; # (Ứ; Ứ; U◌̛◌Ì; Ứ; U◌̛◌Ì; ) LATIN CAPITAL LETTER U WITH HORN AND ACUTE
+1EE9;1EE9;0075 031B 0301;1EE9;0075 031B 0301; # (ứ; ứ; u◌̛◌Ì; ứ; u◌̛◌Ì; ) LATIN SMALL LETTER U WITH HORN AND ACUTE
+1EEA;1EEA;0055 031B 0300;1EEA;0055 031B 0300; # (Ừ; Ừ; U◌̛◌̀; Ừ; U◌̛◌̀; ) LATIN CAPITAL LETTER U WITH HORN AND GRAVE
+1EEB;1EEB;0075 031B 0300;1EEB;0075 031B 0300; # (ừ; ừ; u◌̛◌̀; ừ; u◌̛◌̀; ) LATIN SMALL LETTER U WITH HORN AND GRAVE
+1EEC;1EEC;0055 031B 0309;1EEC;0055 031B 0309; # (Ử; Ử; U◌̛◌̉; Ử; U◌̛◌̉; ) LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE
+1EED;1EED;0075 031B 0309;1EED;0075 031B 0309; # (ử; ử; u◌̛◌̉; ử; u◌̛◌̉; ) LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE
+1EEE;1EEE;0055 031B 0303;1EEE;0055 031B 0303; # (Ữ; Ữ; U◌̛◌̃; Ữ; U◌̛◌̃; ) LATIN CAPITAL LETTER U WITH HORN AND TILDE
+1EEF;1EEF;0075 031B 0303;1EEF;0075 031B 0303; # (ữ; ữ; u◌̛◌̃; ữ; u◌̛◌̃; ) LATIN SMALL LETTER U WITH HORN AND TILDE
+1EF0;1EF0;0055 031B 0323;1EF0;0055 031B 0323; # (Ự; Ự; U◌̛◌̣; Ự; U◌̛◌̣; ) LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW
+1EF1;1EF1;0075 031B 0323;1EF1;0075 031B 0323; # (ự; ự; u◌̛◌̣; ự; u◌̛◌̣; ) LATIN SMALL LETTER U WITH HORN AND DOT BELOW
+1EF2;1EF2;0059 0300;1EF2;0059 0300; # (Ỳ; Ỳ; Y◌̀; Ỳ; Y◌̀; ) LATIN CAPITAL LETTER Y WITH GRAVE
+1EF3;1EF3;0079 0300;1EF3;0079 0300; # (ỳ; ỳ; y◌̀; ỳ; y◌̀; ) LATIN SMALL LETTER Y WITH GRAVE
+1EF4;1EF4;0059 0323;1EF4;0059 0323; # (Ỵ; Ỵ; Y◌̣; Ỵ; Y◌̣; ) LATIN CAPITAL LETTER Y WITH DOT BELOW
+1EF5;1EF5;0079 0323;1EF5;0079 0323; # (ỵ; ỵ; y◌̣; ỵ; y◌̣; ) LATIN SMALL LETTER Y WITH DOT BELOW
+1EF6;1EF6;0059 0309;1EF6;0059 0309; # (Ỷ; Ỷ; Y◌̉; Ỷ; Y◌̉; ) LATIN CAPITAL LETTER Y WITH HOOK ABOVE
+1EF7;1EF7;0079 0309;1EF7;0079 0309; # (ỷ; ỷ; y◌̉; ỷ; y◌̉; ) LATIN SMALL LETTER Y WITH HOOK ABOVE
+1EF8;1EF8;0059 0303;1EF8;0059 0303; # (Ỹ; Ỹ; Y◌̃; Ỹ; Y◌̃; ) LATIN CAPITAL LETTER Y WITH TILDE
+1EF9;1EF9;0079 0303;1EF9;0079 0303; # (ỹ; ỹ; y◌̃; ỹ; y◌̃; ) LATIN SMALL LETTER Y WITH TILDE
+1F00;1F00;03B1 0313;1F00;03B1 0313; # (ἀ; ἀ; α◌̓; ἀ; α◌̓; ) GREEK SMALL LETTER ALPHA WITH PSILI
+1F01;1F01;03B1 0314;1F01;03B1 0314; # (á¼; á¼; α◌̔; á¼; α◌̔; ) GREEK SMALL LETTER ALPHA WITH DASIA
+1F02;1F02;03B1 0313 0300;1F02;03B1 0313 0300; # (ἂ; ἂ; α◌̓◌̀; ἂ; α◌̓◌̀; ) GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA
+1F03;1F03;03B1 0314 0300;1F03;03B1 0314 0300; # (ἃ; ἃ; α◌̔◌̀; ἃ; α◌̔◌̀; ) GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA
+1F04;1F04;03B1 0313 0301;1F04;03B1 0313 0301; # (ἄ; ἄ; α◌̓◌Ì; ἄ; α◌̓◌Ì; ) GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA
+1F05;1F05;03B1 0314 0301;1F05;03B1 0314 0301; # (á¼…; á¼…; α◌̔◌Ì; á¼…; α◌̔◌Ì; ) GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA
+1F06;1F06;03B1 0313 0342;1F06;03B1 0313 0342; # (ἆ; ἆ; α◌̓◌͂; ἆ; α◌̓◌͂; ) GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI
+1F07;1F07;03B1 0314 0342;1F07;03B1 0314 0342; # (ἇ; ἇ; α◌̔◌͂; ἇ; α◌̔◌͂; ) GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI
+1F08;1F08;0391 0313;1F08;0391 0313; # (Ἀ; Ἀ; Α◌̓; Ἀ; Α◌̓; ) GREEK CAPITAL LETTER ALPHA WITH PSILI
+1F09;1F09;0391 0314;1F09;0391 0314; # (Ἁ; Ἁ; Α◌̔; Ἁ; Α◌̔; ) GREEK CAPITAL LETTER ALPHA WITH DASIA
+1F0A;1F0A;0391 0313 0300;1F0A;0391 0313 0300; # (Ἂ; Ἂ; Α◌̓◌̀; Ἂ; Α◌̓◌̀; ) GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA
+1F0B;1F0B;0391 0314 0300;1F0B;0391 0314 0300; # (Ἃ; Ἃ; Α◌̔◌̀; Ἃ; Α◌̔◌̀; ) GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA
+1F0C;1F0C;0391 0313 0301;1F0C;0391 0313 0301; # (Ἄ; Ἄ; Α◌̓◌Ì; Ἄ; Α◌̓◌Ì; ) GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA
+1F0D;1F0D;0391 0314 0301;1F0D;0391 0314 0301; # (á¼; á¼; Α◌̔◌Ì; á¼; Α◌̔◌Ì; ) GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA
+1F0E;1F0E;0391 0313 0342;1F0E;0391 0313 0342; # (Ἆ; Ἆ; Α◌̓◌͂; Ἆ; Α◌̓◌͂; ) GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI
+1F0F;1F0F;0391 0314 0342;1F0F;0391 0314 0342; # (á¼; á¼; Α◌̔◌͂; á¼; Α◌̔◌͂; ) GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI
+1F10;1F10;03B5 0313;1F10;03B5 0313; # (á¼; á¼; ε◌̓; á¼; ε◌̓; ) GREEK SMALL LETTER EPSILON WITH PSILI
+1F11;1F11;03B5 0314;1F11;03B5 0314; # (ἑ; ἑ; ε◌̔; ἑ; ε◌̔; ) GREEK SMALL LETTER EPSILON WITH DASIA
+1F12;1F12;03B5 0313 0300;1F12;03B5 0313 0300; # (ἒ; ἒ; ε◌̓◌̀; ἒ; ε◌̓◌̀; ) GREEK SMALL LETTER EPSILON WITH PSILI AND VARIA
+1F13;1F13;03B5 0314 0300;1F13;03B5 0314 0300; # (ἓ; ἓ; ε◌̔◌̀; ἓ; ε◌̔◌̀; ) GREEK SMALL LETTER EPSILON WITH DASIA AND VARIA
+1F14;1F14;03B5 0313 0301;1F14;03B5 0313 0301; # (á¼”; á¼”; ε◌̓◌Ì; á¼”; ε◌̓◌Ì; ) GREEK SMALL LETTER EPSILON WITH PSILI AND OXIA
+1F15;1F15;03B5 0314 0301;1F15;03B5 0314 0301; # (ἕ; ἕ; ε◌̔◌Ì; ἕ; ε◌̔◌Ì; ) GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F18;1F18;0395 0313;1F18;0395 0313; # (Ἐ; Ἐ; Ε◌̓; Ἐ; Ε◌̓; ) GREEK CAPITAL LETTER EPSILON WITH PSILI
+1F19;1F19;0395 0314;1F19;0395 0314; # (Ἑ; Ἑ; Ε◌̔; Ἑ; Ε◌̔; ) GREEK CAPITAL LETTER EPSILON WITH DASIA
+1F1A;1F1A;0395 0313 0300;1F1A;0395 0313 0300; # (Ἒ; Ἒ; Ε◌̓◌̀; Ἒ; Ε◌̓◌̀; ) GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA
+1F1B;1F1B;0395 0314 0300;1F1B;0395 0314 0300; # (Ἓ; Ἓ; Ε◌̔◌̀; Ἓ; Ε◌̔◌̀; ) GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA
+1F1C;1F1C;0395 0313 0301;1F1C;0395 0313 0301; # (Ἔ; Ἔ; Ε◌̓◌Ì; Ἔ; Ε◌̓◌Ì; ) GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA
+1F1D;1F1D;0395 0314 0301;1F1D;0395 0314 0301; # (á¼; á¼; Ε◌̔◌Ì; á¼; Ε◌̔◌Ì; ) GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F20;1F20;03B7 0313;1F20;03B7 0313; # (ἠ; ἠ; η◌̓; ἠ; η◌̓; ) GREEK SMALL LETTER ETA WITH PSILI
+1F21;1F21;03B7 0314;1F21;03B7 0314; # (ἡ; ἡ; η◌̔; ἡ; η◌̔; ) GREEK SMALL LETTER ETA WITH DASIA
+1F22;1F22;03B7 0313 0300;1F22;03B7 0313 0300; # (ἢ; ἢ; η◌̓◌̀; ἢ; η◌̓◌̀; ) GREEK SMALL LETTER ETA WITH PSILI AND VARIA
+1F23;1F23;03B7 0314 0300;1F23;03B7 0314 0300; # (ἣ; ἣ; η◌̔◌̀; ἣ; η◌̔◌̀; ) GREEK SMALL LETTER ETA WITH DASIA AND VARIA
+1F24;1F24;03B7 0313 0301;1F24;03B7 0313 0301; # (ἤ; ἤ; η◌̓◌Ì; ἤ; η◌̓◌Ì; ) GREEK SMALL LETTER ETA WITH PSILI AND OXIA
+1F25;1F25;03B7 0314 0301;1F25;03B7 0314 0301; # (á¼¥; á¼¥; η◌̔◌Ì; á¼¥; η◌̔◌Ì; ) GREEK SMALL LETTER ETA WITH DASIA AND OXIA
+1F26;1F26;03B7 0313 0342;1F26;03B7 0313 0342; # (ἦ; ἦ; η◌̓◌͂; ἦ; η◌̓◌͂; ) GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI
+1F27;1F27;03B7 0314 0342;1F27;03B7 0314 0342; # (ἧ; ἧ; η◌̔◌͂; ἧ; η◌̔◌͂; ) GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI
+1F28;1F28;0397 0313;1F28;0397 0313; # (Ἠ; Ἠ; Η◌̓; Ἠ; Η◌̓; ) GREEK CAPITAL LETTER ETA WITH PSILI
+1F29;1F29;0397 0314;1F29;0397 0314; # (Ἡ; Ἡ; Η◌̔; Ἡ; Η◌̔; ) GREEK CAPITAL LETTER ETA WITH DASIA
+1F2A;1F2A;0397 0313 0300;1F2A;0397 0313 0300; # (Ἢ; Ἢ; Η◌̓◌̀; Ἢ; Η◌̓◌̀; ) GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA
+1F2B;1F2B;0397 0314 0300;1F2B;0397 0314 0300; # (Ἣ; Ἣ; Η◌̔◌̀; Ἣ; Η◌̔◌̀; ) GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA
+1F2C;1F2C;0397 0313 0301;1F2C;0397 0313 0301; # (Ἤ; Ἤ; Η◌̓◌Ì; Ἤ; Η◌̓◌Ì; ) GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA
+1F2D;1F2D;0397 0314 0301;1F2D;0397 0314 0301; # (á¼­; á¼­; Η◌̔◌Ì; á¼­; Η◌̔◌Ì; ) GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA
+1F2E;1F2E;0397 0313 0342;1F2E;0397 0313 0342; # (Ἦ; Ἦ; Η◌̓◌͂; Ἦ; Η◌̓◌͂; ) GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI
+1F2F;1F2F;0397 0314 0342;1F2F;0397 0314 0342; # (Ἧ; Ἧ; Η◌̔◌͂; Ἧ; Η◌̔◌͂; ) GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI
+1F30;1F30;03B9 0313;1F30;03B9 0313; # (ἰ; ἰ; ι◌̓; ἰ; ι◌̓; ) GREEK SMALL LETTER IOTA WITH PSILI
+1F31;1F31;03B9 0314;1F31;03B9 0314; # (ἱ; ἱ; ι◌̔; ἱ; ι◌̔; ) GREEK SMALL LETTER IOTA WITH DASIA
+1F32;1F32;03B9 0313 0300;1F32;03B9 0313 0300; # (ἲ; ἲ; ι◌̓◌̀; ἲ; ι◌̓◌̀; ) GREEK SMALL LETTER IOTA WITH PSILI AND VARIA
+1F33;1F33;03B9 0314 0300;1F33;03B9 0314 0300; # (ἳ; ἳ; ι◌̔◌̀; ἳ; ι◌̔◌̀; ) GREEK SMALL LETTER IOTA WITH DASIA AND VARIA
+1F34;1F34;03B9 0313 0301;1F34;03B9 0313 0301; # (á¼´; á¼´; ι◌̓◌Ì; á¼´; ι◌̓◌Ì; ) GREEK SMALL LETTER IOTA WITH PSILI AND OXIA
+1F35;1F35;03B9 0314 0301;1F35;03B9 0314 0301; # (á¼µ; á¼µ; ι◌̔◌Ì; á¼µ; ι◌̔◌Ì; ) GREEK SMALL LETTER IOTA WITH DASIA AND OXIA
+1F36;1F36;03B9 0313 0342;1F36;03B9 0313 0342; # (ἶ; ἶ; ι◌̓◌͂; ἶ; ι◌̓◌͂; ) GREEK SMALL LETTER IOTA WITH PSILI AND PERISPOMENI
+1F37;1F37;03B9 0314 0342;1F37;03B9 0314 0342; # (ἷ; ἷ; ι◌̔◌͂; ἷ; ι◌̔◌͂; ) GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI
+1F38;1F38;0399 0313;1F38;0399 0313; # (Ἰ; Ἰ; Ι◌̓; Ἰ; Ι◌̓; ) GREEK CAPITAL LETTER IOTA WITH PSILI
+1F39;1F39;0399 0314;1F39;0399 0314; # (Ἱ; Ἱ; Ι◌̔; Ἱ; Ι◌̔; ) GREEK CAPITAL LETTER IOTA WITH DASIA
+1F3A;1F3A;0399 0313 0300;1F3A;0399 0313 0300; # (Ἲ; Ἲ; Ι◌̓◌̀; Ἲ; Ι◌̓◌̀; ) GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA
+1F3B;1F3B;0399 0314 0300;1F3B;0399 0314 0300; # (Ἳ; Ἳ; Ι◌̔◌̀; Ἳ; Ι◌̔◌̀; ) GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA
+1F3C;1F3C;0399 0313 0301;1F3C;0399 0313 0301; # (á¼¼; á¼¼; Ι◌̓◌Ì; á¼¼; Ι◌̓◌Ì; ) GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA
+1F3D;1F3D;0399 0314 0301;1F3D;0399 0314 0301; # (á¼½; á¼½; Ι◌̔◌Ì; á¼½; Ι◌̔◌Ì; ) GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA
+1F3E;1F3E;0399 0313 0342;1F3E;0399 0313 0342; # (Ἶ; Ἶ; Ι◌̓◌͂; Ἶ; Ι◌̓◌͂; ) GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI
+1F3F;1F3F;0399 0314 0342;1F3F;0399 0314 0342; # (Ἷ; Ἷ; Ι◌̔◌͂; Ἷ; Ι◌̔◌͂; ) GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI
+1F40;1F40;03BF 0313;1F40;03BF 0313; # (ὀ; ὀ; ο◌̓; ὀ; ο◌̓; ) GREEK SMALL LETTER OMICRON WITH PSILI
+1F41;1F41;03BF 0314;1F41;03BF 0314; # (á½; á½; ο◌̔; á½; ο◌̔; ) GREEK SMALL LETTER OMICRON WITH DASIA
+1F42;1F42;03BF 0313 0300;1F42;03BF 0313 0300; # (ὂ; ὂ; ο◌̓◌̀; ὂ; ο◌̓◌̀; ) GREEK SMALL LETTER OMICRON WITH PSILI AND VARIA
+1F43;1F43;03BF 0314 0300;1F43;03BF 0314 0300; # (ὃ; ὃ; ο◌̔◌̀; ὃ; ο◌̔◌̀; ) GREEK SMALL LETTER OMICRON WITH DASIA AND VARIA
+1F44;1F44;03BF 0313 0301;1F44;03BF 0313 0301; # (ὄ; ὄ; ο◌̓◌Ì; ὄ; ο◌̓◌Ì; ) GREEK SMALL LETTER OMICRON WITH PSILI AND OXIA
+1F45;1F45;03BF 0314 0301;1F45;03BF 0314 0301; # (á½…; á½…; ο◌̔◌Ì; á½…; ο◌̔◌Ì; ) GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F48;1F48;039F 0313;1F48;039F 0313; # (Ὀ; Ὀ; Ο◌̓; Ὀ; Ο◌̓; ) GREEK CAPITAL LETTER OMICRON WITH PSILI
+1F49;1F49;039F 0314;1F49;039F 0314; # (Ὁ; Ὁ; Ο◌̔; Ὁ; Ο◌̔; ) GREEK CAPITAL LETTER OMICRON WITH DASIA
+1F4A;1F4A;039F 0313 0300;1F4A;039F 0313 0300; # (Ὂ; Ὂ; Ο◌̓◌̀; Ὂ; Ο◌̓◌̀; ) GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA
+1F4B;1F4B;039F 0314 0300;1F4B;039F 0314 0300; # (Ὃ; Ὃ; Ο◌̔◌̀; Ὃ; Ο◌̔◌̀; ) GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA
+1F4C;1F4C;039F 0313 0301;1F4C;039F 0313 0301; # (Ὄ; Ὄ; Ο◌̓◌Ì; Ὄ; Ο◌̓◌Ì; ) GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA
+1F4D;1F4D;039F 0314 0301;1F4D;039F 0314 0301; # (á½; á½; Ο◌̔◌Ì; á½; Ο◌̔◌Ì; ) GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50;1F50;03C5 0313;1F50;03C5 0313; # (á½; á½; υ◌̓; á½; υ◌̓; ) GREEK SMALL LETTER UPSILON WITH PSILI
+1F51;1F51;03C5 0314;1F51;03C5 0314; # (ὑ; ὑ; υ◌̔; ὑ; υ◌̔; ) GREEK SMALL LETTER UPSILON WITH DASIA
+1F52;1F52;03C5 0313 0300;1F52;03C5 0313 0300; # (ὒ; ὒ; υ◌̓◌̀; ὒ; υ◌̓◌̀; ) GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
+1F53;1F53;03C5 0314 0300;1F53;03C5 0314 0300; # (ὓ; ὓ; υ◌̔◌̀; ὓ; υ◌̔◌̀; ) GREEK SMALL LETTER UPSILON WITH DASIA AND VARIA
+1F54;1F54;03C5 0313 0301;1F54;03C5 0313 0301; # (á½”; á½”; υ◌̓◌Ì; á½”; υ◌̓◌Ì; ) GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
+1F55;1F55;03C5 0314 0301;1F55;03C5 0314 0301; # (ὕ; ὕ; υ◌̔◌Ì; ὕ; υ◌̔◌Ì; ) GREEK SMALL LETTER UPSILON WITH DASIA AND OXIA
+1F56;1F56;03C5 0313 0342;1F56;03C5 0313 0342; # (ὖ; ὖ; υ◌̓◌͂; ὖ; υ◌̓◌͂; ) GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
+1F57;1F57;03C5 0314 0342;1F57;03C5 0314 0342; # (ὗ; ὗ; υ◌̔◌͂; ὗ; υ◌̔◌͂; ) GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F59;1F59;03A5 0314;1F59;03A5 0314; # (Ὑ; Ὑ; Υ◌̔; Ὑ; Υ◌̔; ) GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B;1F5B;03A5 0314 0300;1F5B;03A5 0314 0300; # (Ὓ; Ὓ; Υ◌̔◌̀; Ὓ; Υ◌̔◌̀; ) GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D;1F5D;03A5 0314 0301;1F5D;03A5 0314 0301; # (á½; á½; Υ◌̔◌Ì; á½; Υ◌̔◌Ì; ) GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F;1F5F;03A5 0314 0342;1F5F;03A5 0314 0342; # (Ὗ; Ὗ; Υ◌̔◌͂; Ὗ; Υ◌̔◌͂; ) GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F60;1F60;03C9 0313;1F60;03C9 0313; # (ὠ; ὠ; ω◌̓; ὠ; ω◌̓; ) GREEK SMALL LETTER OMEGA WITH PSILI
+1F61;1F61;03C9 0314;1F61;03C9 0314; # (ὡ; ὡ; ω◌̔; ὡ; ω◌̔; ) GREEK SMALL LETTER OMEGA WITH DASIA
+1F62;1F62;03C9 0313 0300;1F62;03C9 0313 0300; # (ὢ; ὢ; ω◌̓◌̀; ὢ; ω◌̓◌̀; ) GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA
+1F63;1F63;03C9 0314 0300;1F63;03C9 0314 0300; # (ὣ; ὣ; ω◌̔◌̀; ὣ; ω◌̔◌̀; ) GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA
+1F64;1F64;03C9 0313 0301;1F64;03C9 0313 0301; # (ὤ; ὤ; ω◌̓◌Ì; ὤ; ω◌̓◌Ì; ) GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA
+1F65;1F65;03C9 0314 0301;1F65;03C9 0314 0301; # (á½¥; á½¥; ω◌̔◌Ì; á½¥; ω◌̔◌Ì; ) GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA
+1F66;1F66;03C9 0313 0342;1F66;03C9 0313 0342; # (ὦ; ὦ; ω◌̓◌͂; ὦ; ω◌̓◌͂; ) GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI
+1F67;1F67;03C9 0314 0342;1F67;03C9 0314 0342; # (ὧ; ὧ; ω◌̔◌͂; ὧ; ω◌̔◌͂; ) GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI
+1F68;1F68;03A9 0313;1F68;03A9 0313; # (Ὠ; Ὠ; Ω◌̓; Ὠ; Ω◌̓; ) GREEK CAPITAL LETTER OMEGA WITH PSILI
+1F69;1F69;03A9 0314;1F69;03A9 0314; # (Ὡ; Ὡ; Ω◌̔; Ὡ; Ω◌̔; ) GREEK CAPITAL LETTER OMEGA WITH DASIA
+1F6A;1F6A;03A9 0313 0300;1F6A;03A9 0313 0300; # (Ὢ; Ὢ; Ω◌̓◌̀; Ὢ; Ω◌̓◌̀; ) GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA
+1F6B;1F6B;03A9 0314 0300;1F6B;03A9 0314 0300; # (Ὣ; Ὣ; Ω◌̔◌̀; Ὣ; Ω◌̔◌̀; ) GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA
+1F6C;1F6C;03A9 0313 0301;1F6C;03A9 0313 0301; # (Ὤ; Ὤ; Ω◌̓◌Ì; Ὤ; Ω◌̓◌Ì; ) GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA
+1F6D;1F6D;03A9 0314 0301;1F6D;03A9 0314 0301; # (á½­; á½­; Ω◌̔◌Ì; á½­; Ω◌̔◌Ì; ) GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA
+1F6E;1F6E;03A9 0313 0342;1F6E;03A9 0313 0342; # (Ὦ; Ὦ; Ω◌̓◌͂; Ὦ; Ω◌̓◌͂; ) GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI
+1F6F;1F6F;03A9 0314 0342;1F6F;03A9 0314 0342; # (Ὧ; Ὧ; Ω◌̔◌͂; Ὧ; Ω◌̔◌͂; ) GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI
+1F70;1F70;03B1 0300;1F70;03B1 0300; # (ὰ; ὰ; α◌̀; ὰ; α◌̀; ) GREEK SMALL LETTER ALPHA WITH VARIA
+1F71;03AC;03B1 0301;03AC;03B1 0301; # (á½±; ά; α◌Ì; ά; α◌Ì; ) GREEK SMALL LETTER ALPHA WITH OXIA
+1F72;1F72;03B5 0300;1F72;03B5 0300; # (ὲ; ὲ; ε◌̀; ὲ; ε◌̀; ) GREEK SMALL LETTER EPSILON WITH VARIA
+1F73;03AD;03B5 0301;03AD;03B5 0301; # (á½³; έ; ε◌Ì; έ; ε◌Ì; ) GREEK SMALL LETTER EPSILON WITH OXIA
+1F74;1F74;03B7 0300;1F74;03B7 0300; # (ὴ; ὴ; η◌̀; ὴ; η◌̀; ) GREEK SMALL LETTER ETA WITH VARIA
+1F75;03AE;03B7 0301;03AE;03B7 0301; # (á½µ; ή; η◌Ì; ή; η◌Ì; ) GREEK SMALL LETTER ETA WITH OXIA
+1F76;1F76;03B9 0300;1F76;03B9 0300; # (ὶ; ὶ; ι◌̀; ὶ; ι◌̀; ) GREEK SMALL LETTER IOTA WITH VARIA
+1F77;03AF;03B9 0301;03AF;03B9 0301; # (á½·; ί; ι◌Ì; ί; ι◌Ì; ) GREEK SMALL LETTER IOTA WITH OXIA
+1F78;1F78;03BF 0300;1F78;03BF 0300; # (ὸ; ὸ; ο◌̀; ὸ; ο◌̀; ) GREEK SMALL LETTER OMICRON WITH VARIA
+1F79;03CC;03BF 0301;03CC;03BF 0301; # (á½¹; ÏŒ; ο◌Ì; ÏŒ; ο◌Ì; ) GREEK SMALL LETTER OMICRON WITH OXIA
+1F7A;1F7A;03C5 0300;1F7A;03C5 0300; # (ὺ; ὺ; υ◌̀; ὺ; υ◌̀; ) GREEK SMALL LETTER UPSILON WITH VARIA
+1F7B;03CD;03C5 0301;03CD;03C5 0301; # (á½»; Ï; Ï…â—ŒÌ; Ï; Ï…â—ŒÌ; ) GREEK SMALL LETTER UPSILON WITH OXIA
+1F7C;1F7C;03C9 0300;1F7C;03C9 0300; # (ὼ; ὼ; ω◌̀; ὼ; ω◌̀; ) GREEK SMALL LETTER OMEGA WITH VARIA
+1F7D;03CE;03C9 0301;03CE;03C9 0301; # (á½½; ÏŽ; ω◌Ì; ÏŽ; ω◌Ì; ) GREEK SMALL LETTER OMEGA WITH OXIA
+1F80;1F80;03B1 0313 0345;1F80;03B1 0313 0345; # (ᾀ; ᾀ; α◌̓◌ͅ; ᾀ; α◌̓◌ͅ; ) GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
+1F81;1F81;03B1 0314 0345;1F81;03B1 0314 0345; # (á¾; á¾; α◌̔◌ͅ; á¾; α◌̔◌ͅ; ) GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI
+1F82;1F82;03B1 0313 0300 0345;1F82;03B1 0313 0300 0345; # (ᾂ; ᾂ; α◌̓◌̀◌ͅ; ᾂ; α◌̓◌̀◌ͅ; ) GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F83;1F83;03B1 0314 0300 0345;1F83;03B1 0314 0300 0345; # (ᾃ; ᾃ; α◌̔◌̀◌ͅ; ᾃ; α◌̔◌̀◌ͅ; ) GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F84;1F84;03B1 0313 0301 0345;1F84;03B1 0313 0301 0345; # (ᾄ; ᾄ; α◌̓◌Ì◌ͅ; ᾄ; α◌̓◌Ì◌ͅ; ) GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F85;1F85;03B1 0314 0301 0345;1F85;03B1 0314 0301 0345; # (á¾…; á¾…; α◌̔◌Ì◌ͅ; á¾…; α◌̔◌Ì◌ͅ; ) GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F86;1F86;03B1 0313 0342 0345;1F86;03B1 0313 0342 0345; # (ᾆ; ᾆ; α◌̓◌͂◌ͅ; ᾆ; α◌̓◌͂◌ͅ; ) GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F87;1F87;03B1 0314 0342 0345;1F87;03B1 0314 0342 0345; # (ᾇ; ᾇ; α◌̔◌͂◌ͅ; ᾇ; α◌̔◌͂◌ͅ; ) GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F88;1F88;0391 0313 0345;1F88;0391 0313 0345; # (ᾈ; ᾈ; Α◌̓◌ͅ; ᾈ; Α◌̓◌ͅ; ) GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
+1F89;1F89;0391 0314 0345;1F89;0391 0314 0345; # (ᾉ; ᾉ; Α◌̔◌ͅ; ᾉ; Α◌̔◌ͅ; ) GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
+1F8A;1F8A;0391 0313 0300 0345;1F8A;0391 0313 0300 0345; # (ᾊ; ᾊ; Α◌̓◌̀◌ͅ; ᾊ; Α◌̓◌̀◌ͅ; ) GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F8B;1F8B;0391 0314 0300 0345;1F8B;0391 0314 0300 0345; # (ᾋ; ᾋ; Α◌̔◌̀◌ͅ; ᾋ; Α◌̔◌̀◌ͅ; ) GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F8C;1F8C;0391 0313 0301 0345;1F8C;0391 0313 0301 0345; # (ᾌ; ᾌ; Α◌̓◌Ì◌ͅ; ᾌ; Α◌̓◌Ì◌ͅ; ) GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F8D;1F8D;0391 0314 0301 0345;1F8D;0391 0314 0301 0345; # (á¾; á¾; Α◌̔◌Ì◌ͅ; á¾; Α◌̔◌Ì◌ͅ; ) GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F8E;1F8E;0391 0313 0342 0345;1F8E;0391 0313 0342 0345; # (ᾎ; ᾎ; Α◌̓◌͂◌ͅ; ᾎ; Α◌̓◌͂◌ͅ; ) GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F8F;1F8F;0391 0314 0342 0345;1F8F;0391 0314 0342 0345; # (á¾; á¾; Α◌̔◌͂◌ͅ; á¾; Α◌̔◌͂◌ͅ; ) GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F90;1F90;03B7 0313 0345;1F90;03B7 0313 0345; # (á¾; á¾; η◌̓◌ͅ; á¾; η◌̓◌ͅ; ) GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI
+1F91;1F91;03B7 0314 0345;1F91;03B7 0314 0345; # (ᾑ; ᾑ; η◌̔◌ͅ; ᾑ; η◌̔◌ͅ; ) GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI
+1F92;1F92;03B7 0313 0300 0345;1F92;03B7 0313 0300 0345; # (ᾒ; ᾒ; η◌̓◌̀◌ͅ; ᾒ; η◌̓◌̀◌ͅ; ) GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F93;1F93;03B7 0314 0300 0345;1F93;03B7 0314 0300 0345; # (ᾓ; ᾓ; η◌̔◌̀◌ͅ; ᾓ; η◌̔◌̀◌ͅ; ) GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F94;1F94;03B7 0313 0301 0345;1F94;03B7 0313 0301 0345; # (á¾”; á¾”; η◌̓◌Ì◌ͅ; á¾”; η◌̓◌Ì◌ͅ; ) GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F95;1F95;03B7 0314 0301 0345;1F95;03B7 0314 0301 0345; # (ᾕ; ᾕ; η◌̔◌Ì◌ͅ; ᾕ; η◌̔◌Ì◌ͅ; ) GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F96;1F96;03B7 0313 0342 0345;1F96;03B7 0313 0342 0345; # (ᾖ; ᾖ; η◌̓◌͂◌ͅ; ᾖ; η◌̓◌͂◌ͅ; ) GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F97;1F97;03B7 0314 0342 0345;1F97;03B7 0314 0342 0345; # (ᾗ; ᾗ; η◌̔◌͂◌ͅ; ᾗ; η◌̔◌͂◌ͅ; ) GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F98;1F98;0397 0313 0345;1F98;0397 0313 0345; # (ᾘ; ᾘ; Η◌̓◌ͅ; ᾘ; Η◌̓◌ͅ; ) GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
+1F99;1F99;0397 0314 0345;1F99;0397 0314 0345; # (ᾙ; ᾙ; Η◌̔◌ͅ; ᾙ; Η◌̔◌ͅ; ) GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
+1F9A;1F9A;0397 0313 0300 0345;1F9A;0397 0313 0300 0345; # (ᾚ; ᾚ; Η◌̓◌̀◌ͅ; ᾚ; Η◌̓◌̀◌ͅ; ) GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F9B;1F9B;0397 0314 0300 0345;1F9B;0397 0314 0300 0345; # (ᾛ; ᾛ; Η◌̔◌̀◌ͅ; ᾛ; Η◌̔◌̀◌ͅ; ) GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F9C;1F9C;0397 0313 0301 0345;1F9C;0397 0313 0301 0345; # (ᾜ; ᾜ; Η◌̓◌Ì◌ͅ; ᾜ; Η◌̓◌Ì◌ͅ; ) GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F9D;1F9D;0397 0314 0301 0345;1F9D;0397 0314 0301 0345; # (á¾; á¾; Η◌̔◌Ì◌ͅ; á¾; Η◌̔◌Ì◌ͅ; ) GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F9E;1F9E;0397 0313 0342 0345;1F9E;0397 0313 0342 0345; # (ᾞ; ᾞ; Η◌̓◌͂◌ͅ; ᾞ; Η◌̓◌͂◌ͅ; ) GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F9F;1F9F;0397 0314 0342 0345;1F9F;0397 0314 0342 0345; # (ᾟ; ᾟ; Η◌̔◌͂◌ͅ; ᾟ; Η◌̔◌͂◌ͅ; ) GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FA0;1FA0;03C9 0313 0345;1FA0;03C9 0313 0345; # (ᾠ; ᾠ; ω◌̓◌ͅ; ᾠ; ω◌̓◌ͅ; ) GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI
+1FA1;1FA1;03C9 0314 0345;1FA1;03C9 0314 0345; # (ᾡ; ᾡ; ω◌̔◌ͅ; ᾡ; ω◌̔◌ͅ; ) GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI
+1FA2;1FA2;03C9 0313 0300 0345;1FA2;03C9 0313 0300 0345; # (ᾢ; ᾢ; ω◌̓◌̀◌ͅ; ᾢ; ω◌̓◌̀◌ͅ; ) GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1FA3;1FA3;03C9 0314 0300 0345;1FA3;03C9 0314 0300 0345; # (ᾣ; ᾣ; ω◌̔◌̀◌ͅ; ᾣ; ω◌̔◌̀◌ͅ; ) GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1FA4;1FA4;03C9 0313 0301 0345;1FA4;03C9 0313 0301 0345; # (ᾤ; ᾤ; ω◌̓◌Ì◌ͅ; ᾤ; ω◌̓◌Ì◌ͅ; ) GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1FA5;1FA5;03C9 0314 0301 0345;1FA5;03C9 0314 0301 0345; # (á¾¥; á¾¥; ω◌̔◌Ì◌ͅ; á¾¥; ω◌̔◌Ì◌ͅ; ) GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1FA6;1FA6;03C9 0313 0342 0345;1FA6;03C9 0313 0342 0345; # (ᾦ; ᾦ; ω◌̓◌͂◌ͅ; ᾦ; ω◌̓◌͂◌ͅ; ) GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1FA7;1FA7;03C9 0314 0342 0345;1FA7;03C9 0314 0342 0345; # (ᾧ; ᾧ; ω◌̔◌͂◌ͅ; ᾧ; ω◌̔◌͂◌ͅ; ) GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FA8;1FA8;03A9 0313 0345;1FA8;03A9 0313 0345; # (ᾨ; ᾨ; Ω◌̓◌ͅ; ᾨ; Ω◌̓◌ͅ; ) GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
+1FA9;1FA9;03A9 0314 0345;1FA9;03A9 0314 0345; # (ᾩ; ᾩ; Ω◌̔◌ͅ; ᾩ; Ω◌̔◌ͅ; ) GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
+1FAA;1FAA;03A9 0313 0300 0345;1FAA;03A9 0313 0300 0345; # (ᾪ; ᾪ; Ω◌̓◌̀◌ͅ; ᾪ; Ω◌̓◌̀◌ͅ; ) GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1FAB;1FAB;03A9 0314 0300 0345;1FAB;03A9 0314 0300 0345; # (ᾫ; ᾫ; Ω◌̔◌̀◌ͅ; ᾫ; Ω◌̔◌̀◌ͅ; ) GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1FAC;1FAC;03A9 0313 0301 0345;1FAC;03A9 0313 0301 0345; # (ᾬ; ᾬ; Ω◌̓◌Ì◌ͅ; ᾬ; Ω◌̓◌Ì◌ͅ; ) GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1FAD;1FAD;03A9 0314 0301 0345;1FAD;03A9 0314 0301 0345; # (á¾­; á¾­; Ω◌̔◌Ì◌ͅ; á¾­; Ω◌̔◌Ì◌ͅ; ) GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1FAE;1FAE;03A9 0313 0342 0345;1FAE;03A9 0313 0342 0345; # (ᾮ; ᾮ; Ω◌̓◌͂◌ͅ; ᾮ; Ω◌̓◌͂◌ͅ; ) GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1FAF;1FAF;03A9 0314 0342 0345;1FAF;03A9 0314 0342 0345; # (ᾯ; ᾯ; Ω◌̔◌͂◌ͅ; ᾯ; Ω◌̔◌͂◌ͅ; ) GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FB0;1FB0;03B1 0306;1FB0;03B1 0306; # (ᾰ; ᾰ; α◌̆; ᾰ; α◌̆; ) GREEK SMALL LETTER ALPHA WITH VRACHY
+1FB1;1FB1;03B1 0304;1FB1;03B1 0304; # (ᾱ; ᾱ; α◌̄; ᾱ; α◌̄; ) GREEK SMALL LETTER ALPHA WITH MACRON
+1FB2;1FB2;03B1 0300 0345;1FB2;03B1 0300 0345; # (ᾲ; ᾲ; α◌̀◌ͅ; ᾲ; α◌̀◌ͅ; ) GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
+1FB3;1FB3;03B1 0345;1FB3;03B1 0345; # (ᾳ; ᾳ; α◌ͅ; ᾳ; α◌ͅ; ) GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI
+1FB4;1FB4;03B1 0301 0345;1FB4;03B1 0301 0345; # (á¾´; á¾´; α◌Ì◌ͅ; á¾´; α◌Ì◌ͅ; ) GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6;1FB6;03B1 0342;1FB6;03B1 0342; # (ᾶ; ᾶ; α◌͂; ᾶ; α◌͂; ) GREEK SMALL LETTER ALPHA WITH PERISPOMENI
+1FB7;1FB7;03B1 0342 0345;1FB7;03B1 0342 0345; # (ᾷ; ᾷ; α◌͂◌ͅ; ᾷ; α◌͂◌ͅ; ) GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FB8;1FB8;0391 0306;1FB8;0391 0306; # (Ᾰ; Ᾰ; Α◌̆; Ᾰ; Α◌̆; ) GREEK CAPITAL LETTER ALPHA WITH VRACHY
+1FB9;1FB9;0391 0304;1FB9;0391 0304; # (Ᾱ; Ᾱ; Α◌̄; Ᾱ; Α◌̄; ) GREEK CAPITAL LETTER ALPHA WITH MACRON
+1FBA;1FBA;0391 0300;1FBA;0391 0300; # (Ὰ; Ὰ; Α◌̀; Ὰ; Α◌̀; ) GREEK CAPITAL LETTER ALPHA WITH VARIA
+1FBB;0386;0391 0301;0386;0391 0301; # (á¾»; Ά; Α◌Ì; Ά; Α◌Ì; ) GREEK CAPITAL LETTER ALPHA WITH OXIA
+1FBC;1FBC;0391 0345;1FBC;0391 0345; # (ᾼ; ᾼ; Α◌ͅ; ᾼ; Α◌ͅ; ) GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBD;1FBD;1FBD;0020 0313;0020 0313; # (᾽; ᾽; ᾽; ◌̓; ◌̓; ) GREEK KORONIS
+1FBE;03B9;03B9;03B9;03B9; # (ι; ι; ι; ι; ι; ) GREEK PROSGEGRAMMENI
+1FBF;1FBF;1FBF;0020 0313;0020 0313; # (᾿; ᾿; ᾿; ◌̓; ◌̓; ) GREEK PSILI
+1FC0;1FC0;1FC0;0020 0342;0020 0342; # (῀; ῀; ῀; ◌͂; ◌͂; ) GREEK PERISPOMENI
+1FC1;1FC1;00A8 0342;0020 0308 0342;0020 0308 0342; # (á¿; á¿; ¨◌͂; ◌̈◌͂; ◌̈◌͂; ) GREEK DIALYTIKA AND PERISPOMENI
+1FC2;1FC2;03B7 0300 0345;1FC2;03B7 0300 0345; # (ῂ; ῂ; η◌̀◌ͅ; ῂ; η◌̀◌ͅ; ) GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
+1FC3;1FC3;03B7 0345;1FC3;03B7 0345; # (ῃ; ῃ; η◌ͅ; ῃ; η◌ͅ; ) GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI
+1FC4;1FC4;03B7 0301 0345;1FC4;03B7 0301 0345; # (á¿„; á¿„; η◌Ì◌ͅ; á¿„; η◌Ì◌ͅ; ) GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6;1FC6;03B7 0342;1FC6;03B7 0342; # (ῆ; ῆ; η◌͂; ῆ; η◌͂; ) GREEK SMALL LETTER ETA WITH PERISPOMENI
+1FC7;1FC7;03B7 0342 0345;1FC7;03B7 0342 0345; # (ῇ; ῇ; η◌͂◌ͅ; ῇ; η◌͂◌ͅ; ) GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FC8;1FC8;0395 0300;1FC8;0395 0300; # (Ὲ; Ὲ; Ε◌̀; Ὲ; Ε◌̀; ) GREEK CAPITAL LETTER EPSILON WITH VARIA
+1FC9;0388;0395 0301;0388;0395 0301; # (Έ; Έ; Ε◌Ì; Έ; Ε◌Ì; ) GREEK CAPITAL LETTER EPSILON WITH OXIA
+1FCA;1FCA;0397 0300;1FCA;0397 0300; # (Ὴ; Ὴ; Η◌̀; Ὴ; Η◌̀; ) GREEK CAPITAL LETTER ETA WITH VARIA
+1FCB;0389;0397 0301;0389;0397 0301; # (á¿‹; Ή; Η◌Ì; Ή; Η◌Ì; ) GREEK CAPITAL LETTER ETA WITH OXIA
+1FCC;1FCC;0397 0345;1FCC;0397 0345; # (ῌ; ῌ; Η◌ͅ; ῌ; Η◌ͅ; ) GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FCD;1FCD;1FBF 0300;0020 0313 0300;0020 0313 0300; # (á¿; á¿; ᾿◌̀; ◌̓◌̀; ◌̓◌̀; ) GREEK PSILI AND VARIA
+1FCE;1FCE;1FBF 0301;0020 0313 0301;0020 0313 0301; # (á¿Ž; á¿Ž; ᾿◌Ì; ◌̓◌Ì; ◌̓◌Ì; ) GREEK PSILI AND OXIA
+1FCF;1FCF;1FBF 0342;0020 0313 0342;0020 0313 0342; # (á¿; á¿; ᾿◌͂; ◌̓◌͂; ◌̓◌͂; ) GREEK PSILI AND PERISPOMENI
+1FD0;1FD0;03B9 0306;1FD0;03B9 0306; # (á¿; á¿; ι◌̆; á¿; ι◌̆; ) GREEK SMALL LETTER IOTA WITH VRACHY
+1FD1;1FD1;03B9 0304;1FD1;03B9 0304; # (ῑ; ῑ; ι◌̄; ῑ; ι◌̄; ) GREEK SMALL LETTER IOTA WITH MACRON
+1FD2;1FD2;03B9 0308 0300;1FD2;03B9 0308 0300; # (ῒ; ῒ; ι◌̈◌̀; ῒ; ι◌̈◌̀; ) GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
+1FD3;0390;03B9 0308 0301;0390;03B9 0308 0301; # (á¿“; Î; ι◌̈◌Ì; Î; ι◌̈◌Ì; ) GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6;1FD6;03B9 0342;1FD6;03B9 0342; # (ῖ; ῖ; ι◌͂; ῖ; ι◌͂; ) GREEK SMALL LETTER IOTA WITH PERISPOMENI
+1FD7;1FD7;03B9 0308 0342;1FD7;03B9 0308 0342; # (ῗ; ῗ; ι◌̈◌͂; ῗ; ι◌̈◌͂; ) GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+1FD8;1FD8;0399 0306;1FD8;0399 0306; # (Ῐ; Ῐ; Ι◌̆; Ῐ; Ι◌̆; ) GREEK CAPITAL LETTER IOTA WITH VRACHY
+1FD9;1FD9;0399 0304;1FD9;0399 0304; # (Ῑ; Ῑ; Ι◌̄; Ῑ; Ι◌̄; ) GREEK CAPITAL LETTER IOTA WITH MACRON
+1FDA;1FDA;0399 0300;1FDA;0399 0300; # (Ὶ; Ὶ; Ι◌̀; Ὶ; Ι◌̀; ) GREEK CAPITAL LETTER IOTA WITH VARIA
+1FDB;038A;0399 0301;038A;0399 0301; # (á¿›; Ί; Ι◌Ì; Ί; Ι◌Ì; ) GREEK CAPITAL LETTER IOTA WITH OXIA
+1FDD;1FDD;1FFE 0300;0020 0314 0300;0020 0314 0300; # (á¿; á¿; ῾◌̀; ◌̔◌̀; ◌̔◌̀; ) GREEK DASIA AND VARIA
+1FDE;1FDE;1FFE 0301;0020 0314 0301;0020 0314 0301; # (á¿ž; á¿ž; ῾◌Ì; ◌̔◌Ì; ◌̔◌Ì; ) GREEK DASIA AND OXIA
+1FDF;1FDF;1FFE 0342;0020 0314 0342;0020 0314 0342; # (῟; ῟; ῾◌͂; ◌̔◌͂; ◌̔◌͂; ) GREEK DASIA AND PERISPOMENI
+1FE0;1FE0;03C5 0306;1FE0;03C5 0306; # (ῠ; ῠ; υ◌̆; ῠ; υ◌̆; ) GREEK SMALL LETTER UPSILON WITH VRACHY
+1FE1;1FE1;03C5 0304;1FE1;03C5 0304; # (ῡ; ῡ; υ◌̄; ῡ; υ◌̄; ) GREEK SMALL LETTER UPSILON WITH MACRON
+1FE2;1FE2;03C5 0308 0300;1FE2;03C5 0308 0300; # (ῢ; ῢ; υ◌̈◌̀; ῢ; υ◌̈◌̀; ) GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
+1FE3;03B0;03C5 0308 0301;03B0;03C5 0308 0301; # (á¿£; ΰ; υ◌̈◌Ì; ΰ; υ◌̈◌Ì; ) GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
+1FE4;1FE4;03C1 0313;1FE4;03C1 0313; # (ῤ; ῤ; Ï◌̓; ῤ; Ï◌̓; ) GREEK SMALL LETTER RHO WITH PSILI
+1FE5;1FE5;03C1 0314;1FE5;03C1 0314; # (á¿¥; á¿¥; Ï◌̔; á¿¥; Ï◌̔; ) GREEK SMALL LETTER RHO WITH DASIA
+1FE6;1FE6;03C5 0342;1FE6;03C5 0342; # (ῦ; ῦ; υ◌͂; ῦ; υ◌͂; ) GREEK SMALL LETTER UPSILON WITH PERISPOMENI
+1FE7;1FE7;03C5 0308 0342;1FE7;03C5 0308 0342; # (ῧ; ῧ; υ◌̈◌͂; ῧ; υ◌̈◌͂; ) GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+1FE8;1FE8;03A5 0306;1FE8;03A5 0306; # (Ῠ; Ῠ; Υ◌̆; Ῠ; Υ◌̆; ) GREEK CAPITAL LETTER UPSILON WITH VRACHY
+1FE9;1FE9;03A5 0304;1FE9;03A5 0304; # (Ῡ; Ῡ; Υ◌̄; Ῡ; Υ◌̄; ) GREEK CAPITAL LETTER UPSILON WITH MACRON
+1FEA;1FEA;03A5 0300;1FEA;03A5 0300; # (Ὺ; Ὺ; Υ◌̀; Ὺ; Υ◌̀; ) GREEK CAPITAL LETTER UPSILON WITH VARIA
+1FEB;038E;03A5 0301;038E;03A5 0301; # (á¿«; ÎŽ; Υ◌Ì; ÎŽ; Υ◌Ì; ) GREEK CAPITAL LETTER UPSILON WITH OXIA
+1FEC;1FEC;03A1 0314;1FEC;03A1 0314; # (Ῥ; Ῥ; Ρ◌̔; Ῥ; Ρ◌̔; ) GREEK CAPITAL LETTER RHO WITH DASIA
+1FED;1FED;00A8 0300;0020 0308 0300;0020 0308 0300; # (῭; ῭; ¨◌̀; ◌̈◌̀; ◌̈◌̀; ) GREEK DIALYTIKA AND VARIA
+1FEE;0385;00A8 0301;0020 0308 0301;0020 0308 0301; # (á¿®; Î…; ¨◌Ì; ◌̈◌Ì; ◌̈◌Ì; ) GREEK DIALYTIKA AND OXIA
+1FEF;0060;0060;0060;0060; # (`; `; `; `; `; ) GREEK VARIA
+1FF2;1FF2;03C9 0300 0345;1FF2;03C9 0300 0345; # (ῲ; ῲ; ω◌̀◌ͅ; ῲ; ω◌̀◌ͅ; ) GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
+1FF3;1FF3;03C9 0345;1FF3;03C9 0345; # (ῳ; ῳ; ω◌ͅ; ῳ; ω◌ͅ; ) GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI
+1FF4;1FF4;03C9 0301 0345;1FF4;03C9 0301 0345; # (á¿´; á¿´; ω◌Ì◌ͅ; á¿´; ω◌Ì◌ͅ; ) GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6;1FF6;03C9 0342;1FF6;03C9 0342; # (ῶ; ῶ; ω◌͂; ῶ; ω◌͂; ) GREEK SMALL LETTER OMEGA WITH PERISPOMENI
+1FF7;1FF7;03C9 0342 0345;1FF7;03C9 0342 0345; # (ῷ; ῷ; ω◌͂◌ͅ; ῷ; ω◌͂◌ͅ; ) GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FF8;1FF8;039F 0300;1FF8;039F 0300; # (Ὸ; Ὸ; Ο◌̀; Ὸ; Ο◌̀; ) GREEK CAPITAL LETTER OMICRON WITH VARIA
+1FF9;038C;039F 0301;038C;039F 0301; # (Ό; ÎŒ; Ο◌Ì; ÎŒ; Ο◌Ì; ) GREEK CAPITAL LETTER OMICRON WITH OXIA
+1FFA;1FFA;03A9 0300;1FFA;03A9 0300; # (Ὼ; Ὼ; Ω◌̀; Ὼ; Ω◌̀; ) GREEK CAPITAL LETTER OMEGA WITH VARIA
+1FFB;038F;03A9 0301;038F;03A9 0301; # (á¿»; Î; Ω◌Ì; Î; Ω◌Ì; ) GREEK CAPITAL LETTER OMEGA WITH OXIA
+1FFC;1FFC;03A9 0345;1FFC;03A9 0345; # (ῼ; ῼ; Ω◌ͅ; ῼ; Ω◌ͅ; ) GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+1FFD;00B4;00B4;0020 0301;0020 0301; # (´; ´; ´; â—ŒÌ; â—ŒÌ; ) GREEK OXIA
+1FFE;1FFE;1FFE;0020 0314;0020 0314; # (῾; ῾; ῾; ◌̔; ◌̔; ) GREEK DASIA
+2000;2002;2002;0020;0020; # ( ;  ;  ; ; ; ) EN QUAD
+2001;2003;2003;0020;0020; # (â€;  ;  ; ; ; ) EM QUAD
+2002;2002;2002;0020;0020; # ( ;  ;  ; ; ; ) EN SPACE
+2003;2003;2003;0020;0020; # ( ;  ;  ; ; ; ) EM SPACE
+2004;2004;2004;0020;0020; # ( ;  ;  ; ; ; ) THREE-PER-EM SPACE
+2005;2005;2005;0020;0020; # ( ;  ;  ; ; ; ) FOUR-PER-EM SPACE
+2006;2006;2006;0020;0020; # ( ;  ;  ; ; ; ) SIX-PER-EM SPACE
+2007;2007;2007;0020;0020; # ( ;  ;  ; ; ; ) FIGURE SPACE
+2008;2008;2008;0020;0020; # ( ;  ;  ; ; ; ) PUNCTUATION SPACE
+2009;2009;2009;0020;0020; # ( ;  ;  ; ; ; ) THIN SPACE
+200A;200A;200A;0020;0020; # ( ;  ;  ; ; ; ) HAIR SPACE
+2011;2011;2011;2010;2010; # (‑; ‑; ‑; â€; â€; ) NON-BREAKING HYPHEN
+2017;2017;2017;0020 0333;0020 0333; # (‗; ‗; ‗; ◌̳; ◌̳; ) DOUBLE LOW LINE
+2024;2024;2024;002E;002E; # (․; ․; ․; .; .; ) ONE DOT LEADER
+2025;2025;2025;002E 002E;002E 002E; # (‥; ‥; ‥; ..; ..; ) TWO DOT LEADER
+2026;2026;2026;002E 002E 002E;002E 002E 002E; # (…; …; …; ...; ...; ) HORIZONTAL ELLIPSIS
+202F;202F;202F;0020;0020; # ( ;  ;  ; ; ; ) NARROW NO-BREAK SPACE
+2033;2033;2033;2032 2032;2032 2032; # (″; ″; ″; ′′; ′′; ) DOUBLE PRIME
+2034;2034;2034;2032 2032 2032;2032 2032 2032; # (‴; ‴; ‴; ′′′; ′′′; ) TRIPLE PRIME
+2036;2036;2036;2035 2035;2035 2035; # (‶; ‶; ‶; ‵‵; ‵‵; ) REVERSED DOUBLE PRIME
+2037;2037;2037;2035 2035 2035;2035 2035 2035; # (‷; ‷; ‷; ‵‵‵; ‵‵‵; ) REVERSED TRIPLE PRIME
+203C;203C;203C;0021 0021;0021 0021; # (‼; ‼; ‼; !!; !!; ) DOUBLE EXCLAMATION MARK
+203E;203E;203E;0020 0305;0020 0305; # (‾; ‾; ‾; ◌̅; ◌̅; ) OVERLINE
+2047;2047;2047;003F 003F;003F 003F; # (â‡; â‡; â‡; ??; ??; ) DOUBLE QUESTION MARK
+2048;2048;2048;003F 0021;003F 0021; # (âˆ; âˆ; âˆ; ?!; ?!; ) QUESTION EXCLAMATION MARK
+2049;2049;2049;0021 003F;0021 003F; # (â‰; â‰; â‰; !?; !?; ) EXCLAMATION QUESTION MARK
+2057;2057;2057;2032 2032 2032 2032;2032 2032 2032 2032; # (â—; â—; â—; ′′′′; ′′′′; ) QUADRUPLE PRIME
+205F;205F;205F;0020;0020; # (âŸ; âŸ; âŸ; ; ; ) MEDIUM MATHEMATICAL SPACE
+2070;2070;2070;0030;0030; # (â°; â°; â°; 0; 0; ) SUPERSCRIPT ZERO
+2071;2071;2071;0069;0069; # (â±; â±; â±; i; i; ) SUPERSCRIPT LATIN SMALL LETTER I
+2074;2074;2074;0034;0034; # (â´; â´; â´; 4; 4; ) SUPERSCRIPT FOUR
+2075;2075;2075;0035;0035; # (âµ; âµ; âµ; 5; 5; ) SUPERSCRIPT FIVE
+2076;2076;2076;0036;0036; # (â¶; â¶; â¶; 6; 6; ) SUPERSCRIPT SIX
+2077;2077;2077;0037;0037; # (â·; â·; â·; 7; 7; ) SUPERSCRIPT SEVEN
+2078;2078;2078;0038;0038; # (â¸; â¸; â¸; 8; 8; ) SUPERSCRIPT EIGHT
+2079;2079;2079;0039;0039; # (â¹; â¹; â¹; 9; 9; ) SUPERSCRIPT NINE
+207A;207A;207A;002B;002B; # (âº; âº; âº; +; +; ) SUPERSCRIPT PLUS SIGN
+207B;207B;207B;2212;2212; # (â»; â»; â»; −; −; ) SUPERSCRIPT MINUS
+207C;207C;207C;003D;003D; # (â¼; â¼; â¼; =; =; ) SUPERSCRIPT EQUALS SIGN
+207D;207D;207D;0028;0028; # (â½; â½; â½; (; (; ) SUPERSCRIPT LEFT PARENTHESIS
+207E;207E;207E;0029;0029; # (â¾; â¾; â¾; ); ); ) SUPERSCRIPT RIGHT PARENTHESIS
+207F;207F;207F;006E;006E; # (â¿; â¿; â¿; n; n; ) SUPERSCRIPT LATIN SMALL LETTER N
+2080;2080;2080;0030;0030; # (â‚€; â‚€; â‚€; 0; 0; ) SUBSCRIPT ZERO
+2081;2081;2081;0031;0031; # (â‚; â‚; â‚; 1; 1; ) SUBSCRIPT ONE
+2082;2082;2082;0032;0032; # (â‚‚; â‚‚; â‚‚; 2; 2; ) SUBSCRIPT TWO
+2083;2083;2083;0033;0033; # (₃; ₃; ₃; 3; 3; ) SUBSCRIPT THREE
+2084;2084;2084;0034;0034; # (â‚„; â‚„; â‚„; 4; 4; ) SUBSCRIPT FOUR
+2085;2085;2085;0035;0035; # (â‚…; â‚…; â‚…; 5; 5; ) SUBSCRIPT FIVE
+2086;2086;2086;0036;0036; # (₆; ₆; ₆; 6; 6; ) SUBSCRIPT SIX
+2087;2087;2087;0037;0037; # (₇; ₇; ₇; 7; 7; ) SUBSCRIPT SEVEN
+2088;2088;2088;0038;0038; # (₈; ₈; ₈; 8; 8; ) SUBSCRIPT EIGHT
+2089;2089;2089;0039;0039; # (₉; ₉; ₉; 9; 9; ) SUBSCRIPT NINE
+208A;208A;208A;002B;002B; # (â‚Š; â‚Š; â‚Š; +; +; ) SUBSCRIPT PLUS SIGN
+208B;208B;208B;2212;2212; # (₋; ₋; ₋; −; −; ) SUBSCRIPT MINUS
+208C;208C;208C;003D;003D; # (₌; ₌; ₌; =; =; ) SUBSCRIPT EQUALS SIGN
+208D;208D;208D;0028;0028; # (â‚; â‚; â‚; (; (; ) SUBSCRIPT LEFT PARENTHESIS
+208E;208E;208E;0029;0029; # (â‚Ž; â‚Ž; â‚Ž; ); ); ) SUBSCRIPT RIGHT PARENTHESIS
+2090;2090;2090;0061;0061; # (â‚; â‚; â‚; a; a; ) LATIN SUBSCRIPT SMALL LETTER A
+2091;2091;2091;0065;0065; # (â‚‘; â‚‘; â‚‘; e; e; ) LATIN SUBSCRIPT SMALL LETTER E
+2092;2092;2092;006F;006F; # (â‚’; â‚’; â‚’; o; o; ) LATIN SUBSCRIPT SMALL LETTER O
+2093;2093;2093;0078;0078; # (â‚“; â‚“; â‚“; x; x; ) LATIN SUBSCRIPT SMALL LETTER X
+2094;2094;2094;0259;0259; # (â‚”; â‚”; â‚”; É™; É™; ) LATIN SUBSCRIPT SMALL LETTER SCHWA
+2095;2095;2095;0068;0068; # (â‚•; â‚•; â‚•; h; h; ) LATIN SUBSCRIPT SMALL LETTER H
+2096;2096;2096;006B;006B; # (â‚–; â‚–; â‚–; k; k; ) LATIN SUBSCRIPT SMALL LETTER K
+2097;2097;2097;006C;006C; # (â‚—; â‚—; â‚—; l; l; ) LATIN SUBSCRIPT SMALL LETTER L
+2098;2098;2098;006D;006D; # (ₘ; ₘ; ₘ; m; m; ) LATIN SUBSCRIPT SMALL LETTER M
+2099;2099;2099;006E;006E; # (â‚™; â‚™; â‚™; n; n; ) LATIN SUBSCRIPT SMALL LETTER N
+209A;209A;209A;0070;0070; # (â‚š; â‚š; â‚š; p; p; ) LATIN SUBSCRIPT SMALL LETTER P
+209B;209B;209B;0073;0073; # (â‚›; â‚›; â‚›; s; s; ) LATIN SUBSCRIPT SMALL LETTER S
+209C;209C;209C;0074;0074; # (ₜ; ₜ; ₜ; t; t; ) LATIN SUBSCRIPT SMALL LETTER T
+20A8;20A8;20A8;0052 0073;0052 0073; # (₨; ₨; ₨; Rs; Rs; ) RUPEE SIGN
+2100;2100;2100;0061 002F 0063;0061 002F 0063; # (â„€; â„€; â„€; a/c; a/c; ) ACCOUNT OF
+2101;2101;2101;0061 002F 0073;0061 002F 0073; # (â„; â„; â„; a/s; a/s; ) ADDRESSED TO THE SUBJECT
+2102;2102;2102;0043;0043; # (â„‚; â„‚; â„‚; C; C; ) DOUBLE-STRUCK CAPITAL C
+2103;2103;2103;00B0 0043;00B0 0043; # (℃; ℃; ℃; °C; °C; ) DEGREE CELSIUS
+2105;2105;2105;0063 002F 006F;0063 002F 006F; # (â„…; â„…; â„…; c/o; c/o; ) CARE OF
+2106;2106;2106;0063 002F 0075;0063 002F 0075; # (℆; ℆; ℆; c/u; c/u; ) CADA UNA
+2107;2107;2107;0190;0190; # (ℇ; ℇ; ℇ; Æ; Æ; ) EULER CONSTANT
+2109;2109;2109;00B0 0046;00B0 0046; # (℉; ℉; ℉; °F; °F; ) DEGREE FAHRENHEIT
+210A;210A;210A;0067;0067; # (â„Š; â„Š; â„Š; g; g; ) SCRIPT SMALL G
+210B;210B;210B;0048;0048; # (â„‹; â„‹; â„‹; H; H; ) SCRIPT CAPITAL H
+210C;210C;210C;0048;0048; # (ℌ; ℌ; ℌ; H; H; ) BLACK-LETTER CAPITAL H
+210D;210D;210D;0048;0048; # (â„; â„; â„; H; H; ) DOUBLE-STRUCK CAPITAL H
+210E;210E;210E;0068;0068; # (â„Ž; â„Ž; â„Ž; h; h; ) PLANCK CONSTANT
+210F;210F;210F;0127;0127; # (â„; â„; â„; ħ; ħ; ) PLANCK CONSTANT OVER TWO PI
+2110;2110;2110;0049;0049; # (â„; â„; â„; I; I; ) SCRIPT CAPITAL I
+2111;2111;2111;0049;0049; # (â„‘; â„‘; â„‘; I; I; ) BLACK-LETTER CAPITAL I
+2112;2112;2112;004C;004C; # (â„’; â„’; â„’; L; L; ) SCRIPT CAPITAL L
+2113;2113;2113;006C;006C; # (â„“; â„“; â„“; l; l; ) SCRIPT SMALL L
+2115;2115;2115;004E;004E; # (â„•; â„•; â„•; N; N; ) DOUBLE-STRUCK CAPITAL N
+2116;2116;2116;004E 006F;004E 006F; # (â„–; â„–; â„–; No; No; ) NUMERO SIGN
+2119;2119;2119;0050;0050; # (â„™; â„™; â„™; P; P; ) DOUBLE-STRUCK CAPITAL P
+211A;211A;211A;0051;0051; # (â„š; â„š; â„š; Q; Q; ) DOUBLE-STRUCK CAPITAL Q
+211B;211B;211B;0052;0052; # (â„›; â„›; â„›; R; R; ) SCRIPT CAPITAL R
+211C;211C;211C;0052;0052; # (ℜ; ℜ; ℜ; R; R; ) BLACK-LETTER CAPITAL R
+211D;211D;211D;0052;0052; # (â„; â„; â„; R; R; ) DOUBLE-STRUCK CAPITAL R
+2120;2120;2120;0053 004D;0053 004D; # (â„ ; â„ ; â„ ; SM; SM; ) SERVICE MARK
+2121;2121;2121;0054 0045 004C;0054 0045 004C; # (â„¡; â„¡; â„¡; TEL; TEL; ) TELEPHONE SIGN
+2122;2122;2122;0054 004D;0054 004D; # (â„¢; â„¢; â„¢; TM; TM; ) TRADE MARK SIGN
+2124;2124;2124;005A;005A; # (ℤ; ℤ; ℤ; Z; Z; ) DOUBLE-STRUCK CAPITAL Z
+2126;03A9;03A9;03A9;03A9; # (Ω; Ω; Ω; Ω; Ω; ) OHM SIGN
+2128;2128;2128;005A;005A; # (ℨ; ℨ; ℨ; Z; Z; ) BLACK-LETTER CAPITAL Z
+212A;004B;004B;004B;004B; # (K; K; K; K; K; ) KELVIN SIGN
+212B;00C5;0041 030A;00C5;0041 030A; # (Å; Å; A◌̊; Å; A◌̊; ) ANGSTROM SIGN
+212C;212C;212C;0042;0042; # (ℬ; ℬ; ℬ; B; B; ) SCRIPT CAPITAL B
+212D;212D;212D;0043;0043; # (â„­; â„­; â„­; C; C; ) BLACK-LETTER CAPITAL C
+212F;212F;212F;0065;0065; # (ℯ; ℯ; ℯ; e; e; ) SCRIPT SMALL E
+2130;2130;2130;0045;0045; # (â„°; â„°; â„°; E; E; ) SCRIPT CAPITAL E
+2131;2131;2131;0046;0046; # (ℱ; ℱ; ℱ; F; F; ) SCRIPT CAPITAL F
+2133;2133;2133;004D;004D; # (ℳ; ℳ; ℳ; M; M; ) SCRIPT CAPITAL M
+2134;2134;2134;006F;006F; # (â„´; â„´; â„´; o; o; ) SCRIPT SMALL O
+2135;2135;2135;05D0;05D0; # (ℵ; ℵ; ℵ; ×; ×; ) ALEF SYMBOL
+2136;2136;2136;05D1;05D1; # (ℶ; ℶ; ℶ; ב; ב; ) BET SYMBOL
+2137;2137;2137;05D2;05D2; # (â„·; â„·; â„·; ×’; ×’; ) GIMEL SYMBOL
+2138;2138;2138;05D3;05D3; # (ℸ; ℸ; ℸ; ד; ד; ) DALET SYMBOL
+2139;2139;2139;0069;0069; # (ℹ; ℹ; ℹ; i; i; ) INFORMATION SOURCE
+213B;213B;213B;0046 0041 0058;0046 0041 0058; # (â„»; â„»; â„»; FAX; FAX; ) FACSIMILE SIGN
+213C;213C;213C;03C0;03C0; # (ℼ; ℼ; ℼ; π; π; ) DOUBLE-STRUCK SMALL PI
+213D;213D;213D;03B3;03B3; # (ℽ; ℽ; ℽ; γ; γ; ) DOUBLE-STRUCK SMALL GAMMA
+213E;213E;213E;0393;0393; # (ℾ; ℾ; ℾ; Γ; Γ; ) DOUBLE-STRUCK CAPITAL GAMMA
+213F;213F;213F;03A0;03A0; # (ℿ; ℿ; ℿ; Π; Π; ) DOUBLE-STRUCK CAPITAL PI
+2140;2140;2140;2211;2211; # (⅀; ⅀; ⅀; ∑; ∑; ) DOUBLE-STRUCK N-ARY SUMMATION
+2145;2145;2145;0044;0044; # (â……; â……; â……; D; D; ) DOUBLE-STRUCK ITALIC CAPITAL D
+2146;2146;2146;0064;0064; # (â…†; â…†; â…†; d; d; ) DOUBLE-STRUCK ITALIC SMALL D
+2147;2147;2147;0065;0065; # (â…‡; â…‡; â…‡; e; e; ) DOUBLE-STRUCK ITALIC SMALL E
+2148;2148;2148;0069;0069; # (â…ˆ; â…ˆ; â…ˆ; i; i; ) DOUBLE-STRUCK ITALIC SMALL I
+2149;2149;2149;006A;006A; # (â…‰; â…‰; â…‰; j; j; ) DOUBLE-STRUCK ITALIC SMALL J
+2150;2150;2150;0031 2044 0037;0031 2044 0037; # (â…; â…; â…; 1â„7; 1â„7; ) VULGAR FRACTION ONE SEVENTH
+2151;2151;2151;0031 2044 0039;0031 2044 0039; # (â…‘; â…‘; â…‘; 1â„9; 1â„9; ) VULGAR FRACTION ONE NINTH
+2152;2152;2152;0031 2044 0031 0030;0031 2044 0031 0030; # (â…’; â…’; â…’; 1â„10; 1â„10; ) VULGAR FRACTION ONE TENTH
+2153;2153;2153;0031 2044 0033;0031 2044 0033; # (â…“; â…“; â…“; 1â„3; 1â„3; ) VULGAR FRACTION ONE THIRD
+2154;2154;2154;0032 2044 0033;0032 2044 0033; # (â…”; â…”; â…”; 2â„3; 2â„3; ) VULGAR FRACTION TWO THIRDS
+2155;2155;2155;0031 2044 0035;0031 2044 0035; # (â…•; â…•; â…•; 1â„5; 1â„5; ) VULGAR FRACTION ONE FIFTH
+2156;2156;2156;0032 2044 0035;0032 2044 0035; # (â…–; â…–; â…–; 2â„5; 2â„5; ) VULGAR FRACTION TWO FIFTHS
+2157;2157;2157;0033 2044 0035;0033 2044 0035; # (â…—; â…—; â…—; 3â„5; 3â„5; ) VULGAR FRACTION THREE FIFTHS
+2158;2158;2158;0034 2044 0035;0034 2044 0035; # (â…˜; â…˜; â…˜; 4â„5; 4â„5; ) VULGAR FRACTION FOUR FIFTHS
+2159;2159;2159;0031 2044 0036;0031 2044 0036; # (â…™; â…™; â…™; 1â„6; 1â„6; ) VULGAR FRACTION ONE SIXTH
+215A;215A;215A;0035 2044 0036;0035 2044 0036; # (â…š; â…š; â…š; 5â„6; 5â„6; ) VULGAR FRACTION FIVE SIXTHS
+215B;215B;215B;0031 2044 0038;0031 2044 0038; # (â…›; â…›; â…›; 1â„8; 1â„8; ) VULGAR FRACTION ONE EIGHTH
+215C;215C;215C;0033 2044 0038;0033 2044 0038; # (â…œ; â…œ; â…œ; 3â„8; 3â„8; ) VULGAR FRACTION THREE EIGHTHS
+215D;215D;215D;0035 2044 0038;0035 2044 0038; # (â…; â…; â…; 5â„8; 5â„8; ) VULGAR FRACTION FIVE EIGHTHS
+215E;215E;215E;0037 2044 0038;0037 2044 0038; # (â…ž; â…ž; â…ž; 7â„8; 7â„8; ) VULGAR FRACTION SEVEN EIGHTHS
+215F;215F;215F;0031 2044;0031 2044; # (â…Ÿ; â…Ÿ; â…Ÿ; 1â„; 1â„; ) FRACTION NUMERATOR ONE
+2160;2160;2160;0049;0049; # (â… ; â… ; â… ; I; I; ) ROMAN NUMERAL ONE
+2161;2161;2161;0049 0049;0049 0049; # (â…¡; â…¡; â…¡; II; II; ) ROMAN NUMERAL TWO
+2162;2162;2162;0049 0049 0049;0049 0049 0049; # (â…¢; â…¢; â…¢; III; III; ) ROMAN NUMERAL THREE
+2163;2163;2163;0049 0056;0049 0056; # (â…£; â…£; â…£; IV; IV; ) ROMAN NUMERAL FOUR
+2164;2164;2164;0056;0056; # (â…¤; â…¤; â…¤; V; V; ) ROMAN NUMERAL FIVE
+2165;2165;2165;0056 0049;0056 0049; # (â…¥; â…¥; â…¥; VI; VI; ) ROMAN NUMERAL SIX
+2166;2166;2166;0056 0049 0049;0056 0049 0049; # (â…¦; â…¦; â…¦; VII; VII; ) ROMAN NUMERAL SEVEN
+2167;2167;2167;0056 0049 0049 0049;0056 0049 0049 0049; # (â…§; â…§; â…§; VIII; VIII; ) ROMAN NUMERAL EIGHT
+2168;2168;2168;0049 0058;0049 0058; # (â…¨; â…¨; â…¨; IX; IX; ) ROMAN NUMERAL NINE
+2169;2169;2169;0058;0058; # (â…©; â…©; â…©; X; X; ) ROMAN NUMERAL TEN
+216A;216A;216A;0058 0049;0058 0049; # (â…ª; â…ª; â…ª; XI; XI; ) ROMAN NUMERAL ELEVEN
+216B;216B;216B;0058 0049 0049;0058 0049 0049; # (â…«; â…«; â…«; XII; XII; ) ROMAN NUMERAL TWELVE
+216C;216C;216C;004C;004C; # (â…¬; â…¬; â…¬; L; L; ) ROMAN NUMERAL FIFTY
+216D;216D;216D;0043;0043; # (â…­; â…­; â…­; C; C; ) ROMAN NUMERAL ONE HUNDRED
+216E;216E;216E;0044;0044; # (â…®; â…®; â…®; D; D; ) ROMAN NUMERAL FIVE HUNDRED
+216F;216F;216F;004D;004D; # (â…¯; â…¯; â…¯; M; M; ) ROMAN NUMERAL ONE THOUSAND
+2170;2170;2170;0069;0069; # (â…°; â…°; â…°; i; i; ) SMALL ROMAN NUMERAL ONE
+2171;2171;2171;0069 0069;0069 0069; # (â…±; â…±; â…±; ii; ii; ) SMALL ROMAN NUMERAL TWO
+2172;2172;2172;0069 0069 0069;0069 0069 0069; # (â…²; â…²; â…²; iii; iii; ) SMALL ROMAN NUMERAL THREE
+2173;2173;2173;0069 0076;0069 0076; # (â…³; â…³; â…³; iv; iv; ) SMALL ROMAN NUMERAL FOUR
+2174;2174;2174;0076;0076; # (â…´; â…´; â…´; v; v; ) SMALL ROMAN NUMERAL FIVE
+2175;2175;2175;0076 0069;0076 0069; # (â…µ; â…µ; â…µ; vi; vi; ) SMALL ROMAN NUMERAL SIX
+2176;2176;2176;0076 0069 0069;0076 0069 0069; # (â…¶; â…¶; â…¶; vii; vii; ) SMALL ROMAN NUMERAL SEVEN
+2177;2177;2177;0076 0069 0069 0069;0076 0069 0069 0069; # (â…·; â…·; â…·; viii; viii; ) SMALL ROMAN NUMERAL EIGHT
+2178;2178;2178;0069 0078;0069 0078; # (â…¸; â…¸; â…¸; ix; ix; ) SMALL ROMAN NUMERAL NINE
+2179;2179;2179;0078;0078; # (â…¹; â…¹; â…¹; x; x; ) SMALL ROMAN NUMERAL TEN
+217A;217A;217A;0078 0069;0078 0069; # (â…º; â…º; â…º; xi; xi; ) SMALL ROMAN NUMERAL ELEVEN
+217B;217B;217B;0078 0069 0069;0078 0069 0069; # (â…»; â…»; â…»; xii; xii; ) SMALL ROMAN NUMERAL TWELVE
+217C;217C;217C;006C;006C; # (â…¼; â…¼; â…¼; l; l; ) SMALL ROMAN NUMERAL FIFTY
+217D;217D;217D;0063;0063; # (â…½; â…½; â…½; c; c; ) SMALL ROMAN NUMERAL ONE HUNDRED
+217E;217E;217E;0064;0064; # (â…¾; â…¾; â…¾; d; d; ) SMALL ROMAN NUMERAL FIVE HUNDRED
+217F;217F;217F;006D;006D; # (â…¿; â…¿; â…¿; m; m; ) SMALL ROMAN NUMERAL ONE THOUSAND
+2189;2189;2189;0030 2044 0033;0030 2044 0033; # (↉; ↉; ↉; 0â„3; 0â„3; ) VULGAR FRACTION ZERO THIRDS
+219A;219A;2190 0338;219A;2190 0338; # (↚; ↚; â†â—ŒÌ¸; ↚; â†â—ŒÌ¸; ) LEFTWARDS ARROW WITH STROKE
+219B;219B;2192 0338;219B;2192 0338; # (↛; ↛; →◌̸; ↛; →◌̸; ) RIGHTWARDS ARROW WITH STROKE
+21AE;21AE;2194 0338;21AE;2194 0338; # (↮; ↮; ↔◌̸; ↮; ↔◌̸; ) LEFT RIGHT ARROW WITH STROKE
+21CD;21CD;21D0 0338;21CD;21D0 0338; # (â‡; â‡; â‡â—ŒÌ¸; â‡; â‡â—ŒÌ¸; ) LEFTWARDS DOUBLE ARROW WITH STROKE
+21CE;21CE;21D4 0338;21CE;21D4 0338; # (⇎; ⇎; ⇔◌̸; ⇎; ⇔◌̸; ) LEFT RIGHT DOUBLE ARROW WITH STROKE
+21CF;21CF;21D2 0338;21CF;21D2 0338; # (â‡; â‡; ⇒◌̸; â‡; ⇒◌̸; ) RIGHTWARDS DOUBLE ARROW WITH STROKE
+2204;2204;2203 0338;2204;2203 0338; # (∄; ∄; ∃◌̸; ∄; ∃◌̸; ) THERE DOES NOT EXIST
+2209;2209;2208 0338;2209;2208 0338; # (∉; ∉; ∈◌̸; ∉; ∈◌̸; ) NOT AN ELEMENT OF
+220C;220C;220B 0338;220C;220B 0338; # (∌; ∌; ∋◌̸; ∌; ∋◌̸; ) DOES NOT CONTAIN AS MEMBER
+2224;2224;2223 0338;2224;2223 0338; # (∤; ∤; ∣◌̸; ∤; ∣◌̸; ) DOES NOT DIVIDE
+2226;2226;2225 0338;2226;2225 0338; # (∦; ∦; ∥◌̸; ∦; ∥◌̸; ) NOT PARALLEL TO
+222C;222C;222C;222B 222B;222B 222B; # (∬; ∬; ∬; ∫∫; ∫∫; ) DOUBLE INTEGRAL
+222D;222D;222D;222B 222B 222B;222B 222B 222B; # (∭; ∭; ∭; ∫∫∫; ∫∫∫; ) TRIPLE INTEGRAL
+222F;222F;222F;222E 222E;222E 222E; # (∯; ∯; ∯; ∮∮; ∮∮; ) SURFACE INTEGRAL
+2230;2230;2230;222E 222E 222E;222E 222E 222E; # (∰; ∰; ∰; ∮∮∮; ∮∮∮; ) VOLUME INTEGRAL
+2241;2241;223C 0338;2241;223C 0338; # (â‰; â‰; ∼◌̸; â‰; ∼◌̸; ) NOT TILDE
+2244;2244;2243 0338;2244;2243 0338; # (≄; ≄; ≃◌̸; ≄; ≃◌̸; ) NOT ASYMPTOTICALLY EQUAL TO
+2247;2247;2245 0338;2247;2245 0338; # (≇; ≇; ≅◌̸; ≇; ≅◌̸; ) NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO
+2249;2249;2248 0338;2249;2248 0338; # (≉; ≉; ≈◌̸; ≉; ≈◌̸; ) NOT ALMOST EQUAL TO
+2260;2260;003D 0338;2260;003D 0338; # (≠; ≠; =◌̸; ≠; =◌̸; ) NOT EQUAL TO
+2262;2262;2261 0338;2262;2261 0338; # (≢; ≢; ≡◌̸; ≢; ≡◌̸; ) NOT IDENTICAL TO
+226D;226D;224D 0338;226D;224D 0338; # (≭; ≭; â‰â—ŒÌ¸; ≭; â‰â—ŒÌ¸; ) NOT EQUIVALENT TO
+226E;226E;003C 0338;226E;003C 0338; # (≮; ≮; <◌̸; ≮; <◌̸; ) NOT LESS-THAN
+226F;226F;003E 0338;226F;003E 0338; # (≯; ≯; >◌̸; ≯; >◌̸; ) NOT GREATER-THAN
+2270;2270;2264 0338;2270;2264 0338; # (≰; ≰; ≤◌̸; ≰; ≤◌̸; ) NEITHER LESS-THAN NOR EQUAL TO
+2271;2271;2265 0338;2271;2265 0338; # (≱; ≱; ≥◌̸; ≱; ≥◌̸; ) NEITHER GREATER-THAN NOR EQUAL TO
+2274;2274;2272 0338;2274;2272 0338; # (≴; ≴; ≲◌̸; ≴; ≲◌̸; ) NEITHER LESS-THAN NOR EQUIVALENT TO
+2275;2275;2273 0338;2275;2273 0338; # (≵; ≵; ≳◌̸; ≵; ≳◌̸; ) NEITHER GREATER-THAN NOR EQUIVALENT TO
+2278;2278;2276 0338;2278;2276 0338; # (≸; ≸; ≶◌̸; ≸; ≶◌̸; ) NEITHER LESS-THAN NOR GREATER-THAN
+2279;2279;2277 0338;2279;2277 0338; # (≹; ≹; ≷◌̸; ≹; ≷◌̸; ) NEITHER GREATER-THAN NOR LESS-THAN
+2280;2280;227A 0338;2280;227A 0338; # (⊀; ⊀; ≺◌̸; ⊀; ≺◌̸; ) DOES NOT PRECEDE
+2281;2281;227B 0338;2281;227B 0338; # (âŠ; âŠ; ≻◌̸; âŠ; ≻◌̸; ) DOES NOT SUCCEED
+2284;2284;2282 0338;2284;2282 0338; # (⊄; ⊄; ⊂◌̸; ⊄; ⊂◌̸; ) NOT A SUBSET OF
+2285;2285;2283 0338;2285;2283 0338; # (⊅; ⊅; ⊃◌̸; ⊅; ⊃◌̸; ) NOT A SUPERSET OF
+2288;2288;2286 0338;2288;2286 0338; # (⊈; ⊈; ⊆◌̸; ⊈; ⊆◌̸; ) NEITHER A SUBSET OF NOR EQUAL TO
+2289;2289;2287 0338;2289;2287 0338; # (⊉; ⊉; ⊇◌̸; ⊉; ⊇◌̸; ) NEITHER A SUPERSET OF NOR EQUAL TO
+22AC;22AC;22A2 0338;22AC;22A2 0338; # (⊬; ⊬; ⊢◌̸; ⊬; ⊢◌̸; ) DOES NOT PROVE
+22AD;22AD;22A8 0338;22AD;22A8 0338; # (⊭; ⊭; ⊨◌̸; ⊭; ⊨◌̸; ) NOT TRUE
+22AE;22AE;22A9 0338;22AE;22A9 0338; # (⊮; ⊮; ⊩◌̸; ⊮; ⊩◌̸; ) DOES NOT FORCE
+22AF;22AF;22AB 0338;22AF;22AB 0338; # (⊯; ⊯; ⊫◌̸; ⊯; ⊫◌̸; ) NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE
+22E0;22E0;227C 0338;22E0;227C 0338; # (⋠; ⋠; ≼◌̸; ⋠; ≼◌̸; ) DOES NOT PRECEDE OR EQUAL
+22E1;22E1;227D 0338;22E1;227D 0338; # (⋡; ⋡; ≽◌̸; ⋡; ≽◌̸; ) DOES NOT SUCCEED OR EQUAL
+22E2;22E2;2291 0338;22E2;2291 0338; # (⋢; ⋢; ⊑◌̸; ⋢; ⊑◌̸; ) NOT SQUARE IMAGE OF OR EQUAL TO
+22E3;22E3;2292 0338;22E3;2292 0338; # (⋣; ⋣; ⊒◌̸; ⋣; ⊒◌̸; ) NOT SQUARE ORIGINAL OF OR EQUAL TO
+22EA;22EA;22B2 0338;22EA;22B2 0338; # (⋪; ⋪; ⊲◌̸; ⋪; ⊲◌̸; ) NOT NORMAL SUBGROUP OF
+22EB;22EB;22B3 0338;22EB;22B3 0338; # (⋫; ⋫; ⊳◌̸; ⋫; ⊳◌̸; ) DOES NOT CONTAIN AS NORMAL SUBGROUP
+22EC;22EC;22B4 0338;22EC;22B4 0338; # (⋬; ⋬; ⊴◌̸; ⋬; ⊴◌̸; ) NOT NORMAL SUBGROUP OF OR EQUAL TO
+22ED;22ED;22B5 0338;22ED;22B5 0338; # (⋭; ⋭; ⊵◌̸; ⋭; ⊵◌̸; ) DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL
+2329;3008;3008;3008;3008; # (〈; 〈; 〈; 〈; 〈; ) LEFT-POINTING ANGLE BRACKET
+232A;3009;3009;3009;3009; # (〉; 〉; 〉; 〉; 〉; ) RIGHT-POINTING ANGLE BRACKET
+2460;2460;2460;0031;0031; # (â‘ ; â‘ ; â‘ ; 1; 1; ) CIRCLED DIGIT ONE
+2461;2461;2461;0032;0032; # (â‘¡; â‘¡; â‘¡; 2; 2; ) CIRCLED DIGIT TWO
+2462;2462;2462;0033;0033; # (â‘¢; â‘¢; â‘¢; 3; 3; ) CIRCLED DIGIT THREE
+2463;2463;2463;0034;0034; # (â‘£; â‘£; â‘£; 4; 4; ) CIRCLED DIGIT FOUR
+2464;2464;2464;0035;0035; # (⑤; ⑤; ⑤; 5; 5; ) CIRCLED DIGIT FIVE
+2465;2465;2465;0036;0036; # (â‘¥; â‘¥; â‘¥; 6; 6; ) CIRCLED DIGIT SIX
+2466;2466;2466;0037;0037; # (⑦; ⑦; ⑦; 7; 7; ) CIRCLED DIGIT SEVEN
+2467;2467;2467;0038;0038; # (⑧; ⑧; ⑧; 8; 8; ) CIRCLED DIGIT EIGHT
+2468;2468;2468;0039;0039; # (⑨; ⑨; ⑨; 9; 9; ) CIRCLED DIGIT NINE
+2469;2469;2469;0031 0030;0031 0030; # (â‘©; â‘©; â‘©; 10; 10; ) CIRCLED NUMBER TEN
+246A;246A;246A;0031 0031;0031 0031; # (⑪; ⑪; ⑪; 11; 11; ) CIRCLED NUMBER ELEVEN
+246B;246B;246B;0031 0032;0031 0032; # (â‘«; â‘«; â‘«; 12; 12; ) CIRCLED NUMBER TWELVE
+246C;246C;246C;0031 0033;0031 0033; # (⑬; ⑬; ⑬; 13; 13; ) CIRCLED NUMBER THIRTEEN
+246D;246D;246D;0031 0034;0031 0034; # (â‘­; â‘­; â‘­; 14; 14; ) CIRCLED NUMBER FOURTEEN
+246E;246E;246E;0031 0035;0031 0035; # (â‘®; â‘®; â‘®; 15; 15; ) CIRCLED NUMBER FIFTEEN
+246F;246F;246F;0031 0036;0031 0036; # (⑯; ⑯; ⑯; 16; 16; ) CIRCLED NUMBER SIXTEEN
+2470;2470;2470;0031 0037;0031 0037; # (â‘°; â‘°; â‘°; 17; 17; ) CIRCLED NUMBER SEVENTEEN
+2471;2471;2471;0031 0038;0031 0038; # (⑱; ⑱; ⑱; 18; 18; ) CIRCLED NUMBER EIGHTEEN
+2472;2472;2472;0031 0039;0031 0039; # (⑲; ⑲; ⑲; 19; 19; ) CIRCLED NUMBER NINETEEN
+2473;2473;2473;0032 0030;0032 0030; # (⑳; ⑳; ⑳; 20; 20; ) CIRCLED NUMBER TWENTY
+2474;2474;2474;0028 0031 0029;0028 0031 0029; # (â‘´; â‘´; â‘´; (1); (1); ) PARENTHESIZED DIGIT ONE
+2475;2475;2475;0028 0032 0029;0028 0032 0029; # (⑵; ⑵; ⑵; (2); (2); ) PARENTHESIZED DIGIT TWO
+2476;2476;2476;0028 0033 0029;0028 0033 0029; # (⑶; ⑶; ⑶; (3); (3); ) PARENTHESIZED DIGIT THREE
+2477;2477;2477;0028 0034 0029;0028 0034 0029; # (â‘·; â‘·; â‘·; (4); (4); ) PARENTHESIZED DIGIT FOUR
+2478;2478;2478;0028 0035 0029;0028 0035 0029; # (⑸; ⑸; ⑸; (5); (5); ) PARENTHESIZED DIGIT FIVE
+2479;2479;2479;0028 0036 0029;0028 0036 0029; # (⑹; ⑹; ⑹; (6); (6); ) PARENTHESIZED DIGIT SIX
+247A;247A;247A;0028 0037 0029;0028 0037 0029; # (⑺; ⑺; ⑺; (7); (7); ) PARENTHESIZED DIGIT SEVEN
+247B;247B;247B;0028 0038 0029;0028 0038 0029; # (â‘»; â‘»; â‘»; (8); (8); ) PARENTHESIZED DIGIT EIGHT
+247C;247C;247C;0028 0039 0029;0028 0039 0029; # (⑼; ⑼; ⑼; (9); (9); ) PARENTHESIZED DIGIT NINE
+247D;247D;247D;0028 0031 0030 0029;0028 0031 0030 0029; # (⑽; ⑽; ⑽; (10); (10); ) PARENTHESIZED NUMBER TEN
+247E;247E;247E;0028 0031 0031 0029;0028 0031 0031 0029; # (⑾; ⑾; ⑾; (11); (11); ) PARENTHESIZED NUMBER ELEVEN
+247F;247F;247F;0028 0031 0032 0029;0028 0031 0032 0029; # (â‘¿; â‘¿; â‘¿; (12); (12); ) PARENTHESIZED NUMBER TWELVE
+2480;2480;2480;0028 0031 0033 0029;0028 0031 0033 0029; # (â’€; â’€; â’€; (13); (13); ) PARENTHESIZED NUMBER THIRTEEN
+2481;2481;2481;0028 0031 0034 0029;0028 0031 0034 0029; # (â’; â’; â’; (14); (14); ) PARENTHESIZED NUMBER FOURTEEN
+2482;2482;2482;0028 0031 0035 0029;0028 0031 0035 0029; # (â’‚; â’‚; â’‚; (15); (15); ) PARENTHESIZED NUMBER FIFTEEN
+2483;2483;2483;0028 0031 0036 0029;0028 0031 0036 0029; # (â’ƒ; â’ƒ; â’ƒ; (16); (16); ) PARENTHESIZED NUMBER SIXTEEN
+2484;2484;2484;0028 0031 0037 0029;0028 0031 0037 0029; # (â’„; â’„; â’„; (17); (17); ) PARENTHESIZED NUMBER SEVENTEEN
+2485;2485;2485;0028 0031 0038 0029;0028 0031 0038 0029; # (â’…; â’…; â’…; (18); (18); ) PARENTHESIZED NUMBER EIGHTEEN
+2486;2486;2486;0028 0031 0039 0029;0028 0031 0039 0029; # (â’†; â’†; â’†; (19); (19); ) PARENTHESIZED NUMBER NINETEEN
+2487;2487;2487;0028 0032 0030 0029;0028 0032 0030 0029; # (â’‡; â’‡; â’‡; (20); (20); ) PARENTHESIZED NUMBER TWENTY
+2488;2488;2488;0031 002E;0031 002E; # (â’ˆ; â’ˆ; â’ˆ; 1.; 1.; ) DIGIT ONE FULL STOP
+2489;2489;2489;0032 002E;0032 002E; # (â’‰; â’‰; â’‰; 2.; 2.; ) DIGIT TWO FULL STOP
+248A;248A;248A;0033 002E;0033 002E; # (â’Š; â’Š; â’Š; 3.; 3.; ) DIGIT THREE FULL STOP
+248B;248B;248B;0034 002E;0034 002E; # (â’‹; â’‹; â’‹; 4.; 4.; ) DIGIT FOUR FULL STOP
+248C;248C;248C;0035 002E;0035 002E; # (⒌; ⒌; ⒌; 5.; 5.; ) DIGIT FIVE FULL STOP
+248D;248D;248D;0036 002E;0036 002E; # (â’; â’; â’; 6.; 6.; ) DIGIT SIX FULL STOP
+248E;248E;248E;0037 002E;0037 002E; # (â’Ž; â’Ž; â’Ž; 7.; 7.; ) DIGIT SEVEN FULL STOP
+248F;248F;248F;0038 002E;0038 002E; # (â’; â’; â’; 8.; 8.; ) DIGIT EIGHT FULL STOP
+2490;2490;2490;0039 002E;0039 002E; # (â’; â’; â’; 9.; 9.; ) DIGIT NINE FULL STOP
+2491;2491;2491;0031 0030 002E;0031 0030 002E; # (â’‘; â’‘; â’‘; 10.; 10.; ) NUMBER TEN FULL STOP
+2492;2492;2492;0031 0031 002E;0031 0031 002E; # (â’’; â’’; â’’; 11.; 11.; ) NUMBER ELEVEN FULL STOP
+2493;2493;2493;0031 0032 002E;0031 0032 002E; # (â’“; â’“; â’“; 12.; 12.; ) NUMBER TWELVE FULL STOP
+2494;2494;2494;0031 0033 002E;0031 0033 002E; # (â’”; â’”; â’”; 13.; 13.; ) NUMBER THIRTEEN FULL STOP
+2495;2495;2495;0031 0034 002E;0031 0034 002E; # (â’•; â’•; â’•; 14.; 14.; ) NUMBER FOURTEEN FULL STOP
+2496;2496;2496;0031 0035 002E;0031 0035 002E; # (â’–; â’–; â’–; 15.; 15.; ) NUMBER FIFTEEN FULL STOP
+2497;2497;2497;0031 0036 002E;0031 0036 002E; # (â’—; â’—; â’—; 16.; 16.; ) NUMBER SIXTEEN FULL STOP
+2498;2498;2498;0031 0037 002E;0031 0037 002E; # (â’˜; â’˜; â’˜; 17.; 17.; ) NUMBER SEVENTEEN FULL STOP
+2499;2499;2499;0031 0038 002E;0031 0038 002E; # (â’™; â’™; â’™; 18.; 18.; ) NUMBER EIGHTEEN FULL STOP
+249A;249A;249A;0031 0039 002E;0031 0039 002E; # (â’š; â’š; â’š; 19.; 19.; ) NUMBER NINETEEN FULL STOP
+249B;249B;249B;0032 0030 002E;0032 0030 002E; # (â’›; â’›; â’›; 20.; 20.; ) NUMBER TWENTY FULL STOP
+249C;249C;249C;0028 0061 0029;0028 0061 0029; # (⒜; ⒜; ⒜; (a); (a); ) PARENTHESIZED LATIN SMALL LETTER A
+249D;249D;249D;0028 0062 0029;0028 0062 0029; # (â’; â’; â’; (b); (b); ) PARENTHESIZED LATIN SMALL LETTER B
+249E;249E;249E;0028 0063 0029;0028 0063 0029; # (â’ž; â’ž; â’ž; (c); (c); ) PARENTHESIZED LATIN SMALL LETTER C
+249F;249F;249F;0028 0064 0029;0028 0064 0029; # (â’Ÿ; â’Ÿ; â’Ÿ; (d); (d); ) PARENTHESIZED LATIN SMALL LETTER D
+24A0;24A0;24A0;0028 0065 0029;0028 0065 0029; # (â’ ; â’ ; â’ ; (e); (e); ) PARENTHESIZED LATIN SMALL LETTER E
+24A1;24A1;24A1;0028 0066 0029;0028 0066 0029; # (â’¡; â’¡; â’¡; (f); (f); ) PARENTHESIZED LATIN SMALL LETTER F
+24A2;24A2;24A2;0028 0067 0029;0028 0067 0029; # (â’¢; â’¢; â’¢; (g); (g); ) PARENTHESIZED LATIN SMALL LETTER G
+24A3;24A3;24A3;0028 0068 0029;0028 0068 0029; # (â’£; â’£; â’£; (h); (h); ) PARENTHESIZED LATIN SMALL LETTER H
+24A4;24A4;24A4;0028 0069 0029;0028 0069 0029; # (â’¤; â’¤; â’¤; (i); (i); ) PARENTHESIZED LATIN SMALL LETTER I
+24A5;24A5;24A5;0028 006A 0029;0028 006A 0029; # (â’¥; â’¥; â’¥; (j); (j); ) PARENTHESIZED LATIN SMALL LETTER J
+24A6;24A6;24A6;0028 006B 0029;0028 006B 0029; # (â’¦; â’¦; â’¦; (k); (k); ) PARENTHESIZED LATIN SMALL LETTER K
+24A7;24A7;24A7;0028 006C 0029;0028 006C 0029; # (â’§; â’§; â’§; (l); (l); ) PARENTHESIZED LATIN SMALL LETTER L
+24A8;24A8;24A8;0028 006D 0029;0028 006D 0029; # (â’¨; â’¨; â’¨; (m); (m); ) PARENTHESIZED LATIN SMALL LETTER M
+24A9;24A9;24A9;0028 006E 0029;0028 006E 0029; # (â’©; â’©; â’©; (n); (n); ) PARENTHESIZED LATIN SMALL LETTER N
+24AA;24AA;24AA;0028 006F 0029;0028 006F 0029; # (â’ª; â’ª; â’ª; (o); (o); ) PARENTHESIZED LATIN SMALL LETTER O
+24AB;24AB;24AB;0028 0070 0029;0028 0070 0029; # (â’«; â’«; â’«; (p); (p); ) PARENTHESIZED LATIN SMALL LETTER P
+24AC;24AC;24AC;0028 0071 0029;0028 0071 0029; # (â’¬; â’¬; â’¬; (q); (q); ) PARENTHESIZED LATIN SMALL LETTER Q
+24AD;24AD;24AD;0028 0072 0029;0028 0072 0029; # (â’­; â’­; â’­; (r); (r); ) PARENTHESIZED LATIN SMALL LETTER R
+24AE;24AE;24AE;0028 0073 0029;0028 0073 0029; # (â’®; â’®; â’®; (s); (s); ) PARENTHESIZED LATIN SMALL LETTER S
+24AF;24AF;24AF;0028 0074 0029;0028 0074 0029; # (â’¯; â’¯; â’¯; (t); (t); ) PARENTHESIZED LATIN SMALL LETTER T
+24B0;24B0;24B0;0028 0075 0029;0028 0075 0029; # (â’°; â’°; â’°; (u); (u); ) PARENTHESIZED LATIN SMALL LETTER U
+24B1;24B1;24B1;0028 0076 0029;0028 0076 0029; # (â’±; â’±; â’±; (v); (v); ) PARENTHESIZED LATIN SMALL LETTER V
+24B2;24B2;24B2;0028 0077 0029;0028 0077 0029; # (â’²; â’²; â’²; (w); (w); ) PARENTHESIZED LATIN SMALL LETTER W
+24B3;24B3;24B3;0028 0078 0029;0028 0078 0029; # (â’³; â’³; â’³; (x); (x); ) PARENTHESIZED LATIN SMALL LETTER X
+24B4;24B4;24B4;0028 0079 0029;0028 0079 0029; # (â’´; â’´; â’´; (y); (y); ) PARENTHESIZED LATIN SMALL LETTER Y
+24B5;24B5;24B5;0028 007A 0029;0028 007A 0029; # (â’µ; â’µ; â’µ; (z); (z); ) PARENTHESIZED LATIN SMALL LETTER Z
+24B6;24B6;24B6;0041;0041; # (â’¶; â’¶; â’¶; A; A; ) CIRCLED LATIN CAPITAL LETTER A
+24B7;24B7;24B7;0042;0042; # (â’·; â’·; â’·; B; B; ) CIRCLED LATIN CAPITAL LETTER B
+24B8;24B8;24B8;0043;0043; # (â’¸; â’¸; â’¸; C; C; ) CIRCLED LATIN CAPITAL LETTER C
+24B9;24B9;24B9;0044;0044; # (â’¹; â’¹; â’¹; D; D; ) CIRCLED LATIN CAPITAL LETTER D
+24BA;24BA;24BA;0045;0045; # (â’º; â’º; â’º; E; E; ) CIRCLED LATIN CAPITAL LETTER E
+24BB;24BB;24BB;0046;0046; # (â’»; â’»; â’»; F; F; ) CIRCLED LATIN CAPITAL LETTER F
+24BC;24BC;24BC;0047;0047; # (â’¼; â’¼; â’¼; G; G; ) CIRCLED LATIN CAPITAL LETTER G
+24BD;24BD;24BD;0048;0048; # (â’½; â’½; â’½; H; H; ) CIRCLED LATIN CAPITAL LETTER H
+24BE;24BE;24BE;0049;0049; # (â’¾; â’¾; â’¾; I; I; ) CIRCLED LATIN CAPITAL LETTER I
+24BF;24BF;24BF;004A;004A; # (â’¿; â’¿; â’¿; J; J; ) CIRCLED LATIN CAPITAL LETTER J
+24C0;24C0;24C0;004B;004B; # (â“€; â“€; â“€; K; K; ) CIRCLED LATIN CAPITAL LETTER K
+24C1;24C1;24C1;004C;004C; # (â“; â“; â“; L; L; ) CIRCLED LATIN CAPITAL LETTER L
+24C2;24C2;24C2;004D;004D; # (â“‚; â“‚; â“‚; M; M; ) CIRCLED LATIN CAPITAL LETTER M
+24C3;24C3;24C3;004E;004E; # (Ⓝ; Ⓝ; Ⓝ; N; N; ) CIRCLED LATIN CAPITAL LETTER N
+24C4;24C4;24C4;004F;004F; # (â“„; â“„; â“„; O; O; ) CIRCLED LATIN CAPITAL LETTER O
+24C5;24C5;24C5;0050;0050; # (â“…; â“…; â“…; P; P; ) CIRCLED LATIN CAPITAL LETTER P
+24C6;24C6;24C6;0051;0051; # (Ⓠ; Ⓠ; Ⓠ; Q; Q; ) CIRCLED LATIN CAPITAL LETTER Q
+24C7;24C7;24C7;0052;0052; # (Ⓡ; Ⓡ; Ⓡ; R; R; ) CIRCLED LATIN CAPITAL LETTER R
+24C8;24C8;24C8;0053;0053; # (Ⓢ; Ⓢ; Ⓢ; S; S; ) CIRCLED LATIN CAPITAL LETTER S
+24C9;24C9;24C9;0054;0054; # (Ⓣ; Ⓣ; Ⓣ; T; T; ) CIRCLED LATIN CAPITAL LETTER T
+24CA;24CA;24CA;0055;0055; # (â“Š; â“Š; â“Š; U; U; ) CIRCLED LATIN CAPITAL LETTER U
+24CB;24CB;24CB;0056;0056; # (â“‹; â“‹; â“‹; V; V; ) CIRCLED LATIN CAPITAL LETTER V
+24CC;24CC;24CC;0057;0057; # (Ⓦ; Ⓦ; Ⓦ; W; W; ) CIRCLED LATIN CAPITAL LETTER W
+24CD;24CD;24CD;0058;0058; # (â“; â“; â“; X; X; ) CIRCLED LATIN CAPITAL LETTER X
+24CE;24CE;24CE;0059;0059; # (â“Ž; â“Ž; â“Ž; Y; Y; ) CIRCLED LATIN CAPITAL LETTER Y
+24CF;24CF;24CF;005A;005A; # (â“; â“; â“; Z; Z; ) CIRCLED LATIN CAPITAL LETTER Z
+24D0;24D0;24D0;0061;0061; # (â“; â“; â“; a; a; ) CIRCLED LATIN SMALL LETTER A
+24D1;24D1;24D1;0062;0062; # (â“‘; â“‘; â“‘; b; b; ) CIRCLED LATIN SMALL LETTER B
+24D2;24D2;24D2;0063;0063; # (â“’; â“’; â“’; c; c; ) CIRCLED LATIN SMALL LETTER C
+24D3;24D3;24D3;0064;0064; # (â““; â““; â““; d; d; ) CIRCLED LATIN SMALL LETTER D
+24D4;24D4;24D4;0065;0065; # (â“”; â“”; â“”; e; e; ) CIRCLED LATIN SMALL LETTER E
+24D5;24D5;24D5;0066;0066; # (â“•; â“•; â“•; f; f; ) CIRCLED LATIN SMALL LETTER F
+24D6;24D6;24D6;0067;0067; # (â“–; â“–; â“–; g; g; ) CIRCLED LATIN SMALL LETTER G
+24D7;24D7;24D7;0068;0068; # (â“—; â“—; â“—; h; h; ) CIRCLED LATIN SMALL LETTER H
+24D8;24D8;24D8;0069;0069; # (ⓘ; ⓘ; ⓘ; i; i; ) CIRCLED LATIN SMALL LETTER I
+24D9;24D9;24D9;006A;006A; # (â“™; â“™; â“™; j; j; ) CIRCLED LATIN SMALL LETTER J
+24DA;24DA;24DA;006B;006B; # (â“š; â“š; â“š; k; k; ) CIRCLED LATIN SMALL LETTER K
+24DB;24DB;24DB;006C;006C; # (â“›; â“›; â“›; l; l; ) CIRCLED LATIN SMALL LETTER L
+24DC;24DC;24DC;006D;006D; # (ⓜ; ⓜ; ⓜ; m; m; ) CIRCLED LATIN SMALL LETTER M
+24DD;24DD;24DD;006E;006E; # (â“; â“; â“; n; n; ) CIRCLED LATIN SMALL LETTER N
+24DE;24DE;24DE;006F;006F; # (â“ž; â“ž; â“ž; o; o; ) CIRCLED LATIN SMALL LETTER O
+24DF;24DF;24DF;0070;0070; # (â“Ÿ; â“Ÿ; â“Ÿ; p; p; ) CIRCLED LATIN SMALL LETTER P
+24E0;24E0;24E0;0071;0071; # (â“ ; â“ ; â“ ; q; q; ) CIRCLED LATIN SMALL LETTER Q
+24E1;24E1;24E1;0072;0072; # (â“¡; â“¡; â“¡; r; r; ) CIRCLED LATIN SMALL LETTER R
+24E2;24E2;24E2;0073;0073; # (â“¢; â“¢; â“¢; s; s; ) CIRCLED LATIN SMALL LETTER S
+24E3;24E3;24E3;0074;0074; # (â“£; â“£; â“£; t; t; ) CIRCLED LATIN SMALL LETTER T
+24E4;24E4;24E4;0075;0075; # (ⓤ; ⓤ; ⓤ; u; u; ) CIRCLED LATIN SMALL LETTER U
+24E5;24E5;24E5;0076;0076; # (â“¥; â“¥; â“¥; v; v; ) CIRCLED LATIN SMALL LETTER V
+24E6;24E6;24E6;0077;0077; # (ⓦ; ⓦ; ⓦ; w; w; ) CIRCLED LATIN SMALL LETTER W
+24E7;24E7;24E7;0078;0078; # (ⓧ; ⓧ; ⓧ; x; x; ) CIRCLED LATIN SMALL LETTER X
+24E8;24E8;24E8;0079;0079; # (ⓨ; ⓨ; ⓨ; y; y; ) CIRCLED LATIN SMALL LETTER Y
+24E9;24E9;24E9;007A;007A; # (â“©; â“©; â“©; z; z; ) CIRCLED LATIN SMALL LETTER Z
+24EA;24EA;24EA;0030;0030; # (⓪; ⓪; ⓪; 0; 0; ) CIRCLED DIGIT ZERO
+2A0C;2A0C;2A0C;222B 222B 222B 222B;222B 222B 222B 222B; # (⨌; ⨌; ⨌; ∫∫∫∫; ∫∫∫∫; ) QUADRUPLE INTEGRAL OPERATOR
+2A74;2A74;2A74;003A 003A 003D;003A 003A 003D; # (â©´; â©´; â©´; ::=; ::=; ) DOUBLE COLON EQUAL
+2A75;2A75;2A75;003D 003D;003D 003D; # (⩵; ⩵; ⩵; ==; ==; ) TWO CONSECUTIVE EQUALS SIGNS
+2A76;2A76;2A76;003D 003D 003D;003D 003D 003D; # (⩶; ⩶; ⩶; ===; ===; ) THREE CONSECUTIVE EQUALS SIGNS
+2ADC;2ADD 0338;2ADD 0338;2ADD 0338;2ADD 0338; # (â«œ; â«â—ŒÌ¸; â«â—ŒÌ¸; â«â—ŒÌ¸; â«â—ŒÌ¸; ) FORKING
+2C7C;2C7C;2C7C;006A;006A; # (â±¼; â±¼; â±¼; j; j; ) LATIN SUBSCRIPT SMALL LETTER J
+2C7D;2C7D;2C7D;0056;0056; # (â±½; â±½; â±½; V; V; ) MODIFIER LETTER CAPITAL V
+2D6F;2D6F;2D6F;2D61;2D61; # (ⵯ; ⵯ; ⵯ; ⵡ; ⵡ; ) TIFINAGH MODIFIER LETTER LABIALIZATION MARK
+2E9F;2E9F;2E9F;6BCD;6BCD; # (⺟; ⺟; ⺟; æ¯; æ¯; ) CJK RADICAL MOTHER
+2EF3;2EF3;2EF3;9F9F;9F9F; # (⻳; ⻳; ⻳; 龟; 龟; ) CJK RADICAL C-SIMPLIFIED TURTLE
+2F00;2F00;2F00;4E00;4E00; # (⼀; ⼀; ⼀; 一; 一; ) KANGXI RADICAL ONE
+2F01;2F01;2F01;4E28;4E28; # (â¼; â¼; â¼; 丨; 丨; ) KANGXI RADICAL LINE
+2F02;2F02;2F02;4E36;4E36; # (⼂; ⼂; ⼂; 丶; 丶; ) KANGXI RADICAL DOT
+2F03;2F03;2F03;4E3F;4E3F; # (⼃; ⼃; ⼃; 丿; 丿; ) KANGXI RADICAL SLASH
+2F04;2F04;2F04;4E59;4E59; # (⼄; ⼄; ⼄; 乙; 乙; ) KANGXI RADICAL SECOND
+2F05;2F05;2F05;4E85;4E85; # (⼅; ⼅; ⼅; 亅; 亅; ) KANGXI RADICAL HOOK
+2F06;2F06;2F06;4E8C;4E8C; # (⼆; ⼆; ⼆; 二; 二; ) KANGXI RADICAL TWO
+2F07;2F07;2F07;4EA0;4EA0; # (⼇; ⼇; ⼇; 亠; 亠; ) KANGXI RADICAL LID
+2F08;2F08;2F08;4EBA;4EBA; # (⼈; ⼈; ⼈; 人; 人; ) KANGXI RADICAL MAN
+2F09;2F09;2F09;513F;513F; # (⼉; ⼉; ⼉; 儿; 儿; ) KANGXI RADICAL LEGS
+2F0A;2F0A;2F0A;5165;5165; # (⼊; ⼊; ⼊; 入; 入; ) KANGXI RADICAL ENTER
+2F0B;2F0B;2F0B;516B;516B; # (⼋; ⼋; ⼋; 八; 八; ) KANGXI RADICAL EIGHT
+2F0C;2F0C;2F0C;5182;5182; # (⼌; ⼌; ⼌; 冂; 冂; ) KANGXI RADICAL DOWN BOX
+2F0D;2F0D;2F0D;5196;5196; # (â¼; â¼; â¼; 冖; 冖; ) KANGXI RADICAL COVER
+2F0E;2F0E;2F0E;51AB;51AB; # (⼎; ⼎; ⼎; 冫; 冫; ) KANGXI RADICAL ICE
+2F0F;2F0F;2F0F;51E0;51E0; # (â¼; â¼; â¼; 几; 几; ) KANGXI RADICAL TABLE
+2F10;2F10;2F10;51F5;51F5; # (â¼; â¼; â¼; 凵; 凵; ) KANGXI RADICAL OPEN BOX
+2F11;2F11;2F11;5200;5200; # (⼑; ⼑; ⼑; 刀; 刀; ) KANGXI RADICAL KNIFE
+2F12;2F12;2F12;529B;529B; # (⼒; ⼒; ⼒; 力; 力; ) KANGXI RADICAL POWER
+2F13;2F13;2F13;52F9;52F9; # (⼓; ⼓; ⼓; 勹; 勹; ) KANGXI RADICAL WRAP
+2F14;2F14;2F14;5315;5315; # (⼔; ⼔; ⼔; 匕; 匕; ) KANGXI RADICAL SPOON
+2F15;2F15;2F15;531A;531A; # (⼕; ⼕; ⼕; 匚; 匚; ) KANGXI RADICAL RIGHT OPEN BOX
+2F16;2F16;2F16;5338;5338; # (⼖; ⼖; ⼖; 匸; 匸; ) KANGXI RADICAL HIDING ENCLOSURE
+2F17;2F17;2F17;5341;5341; # (â¼—; â¼—; â¼—; å; å; ) KANGXI RADICAL TEN
+2F18;2F18;2F18;535C;535C; # (⼘; ⼘; ⼘; åœ; åœ; ) KANGXI RADICAL DIVINATION
+2F19;2F19;2F19;5369;5369; # (â¼™; â¼™; â¼™; å©; å©; ) KANGXI RADICAL SEAL
+2F1A;2F1A;2F1A;5382;5382; # (⼚; ⼚; ⼚; 厂; 厂; ) KANGXI RADICAL CLIFF
+2F1B;2F1B;2F1B;53B6;53B6; # (⼛; ⼛; ⼛; 厶; 厶; ) KANGXI RADICAL PRIVATE
+2F1C;2F1C;2F1C;53C8;53C8; # (⼜; ⼜; ⼜; åˆ; åˆ; ) KANGXI RADICAL AGAIN
+2F1D;2F1D;2F1D;53E3;53E3; # (â¼; â¼; â¼; å£; å£; ) KANGXI RADICAL MOUTH
+2F1E;2F1E;2F1E;56D7;56D7; # (⼞; ⼞; ⼞; 囗; 囗; ) KANGXI RADICAL ENCLOSURE
+2F1F;2F1F;2F1F;571F;571F; # (⼟; ⼟; ⼟; 土; 土; ) KANGXI RADICAL EARTH
+2F20;2F20;2F20;58EB;58EB; # (⼠; ⼠; ⼠; 士; 士; ) KANGXI RADICAL SCHOLAR
+2F21;2F21;2F21;5902;5902; # (⼡; ⼡; ⼡; 夂; 夂; ) KANGXI RADICAL GO
+2F22;2F22;2F22;590A;590A; # (⼢; ⼢; ⼢; 夊; 夊; ) KANGXI RADICAL GO SLOWLY
+2F23;2F23;2F23;5915;5915; # (⼣; ⼣; ⼣; 夕; 夕; ) KANGXI RADICAL EVENING
+2F24;2F24;2F24;5927;5927; # (⼤; ⼤; ⼤; 大; 大; ) KANGXI RADICAL BIG
+2F25;2F25;2F25;5973;5973; # (⼥; ⼥; ⼥; 女; 女; ) KANGXI RADICAL WOMAN
+2F26;2F26;2F26;5B50;5B50; # (⼦; ⼦; ⼦; å­; å­; ) KANGXI RADICAL CHILD
+2F27;2F27;2F27;5B80;5B80; # (⼧; ⼧; ⼧; 宀; 宀; ) KANGXI RADICAL ROOF
+2F28;2F28;2F28;5BF8;5BF8; # (⼨; ⼨; ⼨; 寸; 寸; ) KANGXI RADICAL INCH
+2F29;2F29;2F29;5C0F;5C0F; # (⼩; ⼩; ⼩; å°; å°; ) KANGXI RADICAL SMALL
+2F2A;2F2A;2F2A;5C22;5C22; # (⼪; ⼪; ⼪; 尢; 尢; ) KANGXI RADICAL LAME
+2F2B;2F2B;2F2B;5C38;5C38; # (⼫; ⼫; ⼫; 尸; 尸; ) KANGXI RADICAL CORPSE
+2F2C;2F2C;2F2C;5C6E;5C6E; # (⼬; ⼬; ⼬; 屮; 屮; ) KANGXI RADICAL SPROUT
+2F2D;2F2D;2F2D;5C71;5C71; # (â¼­; â¼­; â¼­; å±±; å±±; ) KANGXI RADICAL MOUNTAIN
+2F2E;2F2E;2F2E;5DDB;5DDB; # (â¼®; â¼®; â¼®; å·›; å·›; ) KANGXI RADICAL RIVER
+2F2F;2F2F;2F2F;5DE5;5DE5; # (⼯; ⼯; ⼯; 工; 工; ) KANGXI RADICAL WORK
+2F30;2F30;2F30;5DF1;5DF1; # (â¼°; â¼°; â¼°; å·±; å·±; ) KANGXI RADICAL ONESELF
+2F31;2F31;2F31;5DFE;5DFE; # (â¼±; â¼±; â¼±; å·¾; å·¾; ) KANGXI RADICAL TURBAN
+2F32;2F32;2F32;5E72;5E72; # (â¼²; â¼²; â¼²; å¹²; å¹²; ) KANGXI RADICAL DRY
+2F33;2F33;2F33;5E7A;5E7A; # (⼳; ⼳; ⼳; 幺; 幺; ) KANGXI RADICAL SHORT THREAD
+2F34;2F34;2F34;5E7F;5E7F; # (⼴; ⼴; ⼴; 广; 广; ) KANGXI RADICAL DOTTED CLIFF
+2F35;2F35;2F35;5EF4;5EF4; # (â¼µ; â¼µ; â¼µ; å»´; å»´; ) KANGXI RADICAL LONG STRIDE
+2F36;2F36;2F36;5EFE;5EFE; # (⼶; ⼶; ⼶; 廾; 廾; ) KANGXI RADICAL TWO HANDS
+2F37;2F37;2F37;5F0B;5F0B; # (⼷; ⼷; ⼷; 弋; 弋; ) KANGXI RADICAL SHOOT
+2F38;2F38;2F38;5F13;5F13; # (⼸; ⼸; ⼸; 弓; 弓; ) KANGXI RADICAL BOW
+2F39;2F39;2F39;5F50;5F50; # (â¼¹; â¼¹; â¼¹; å½; å½; ) KANGXI RADICAL SNOUT
+2F3A;2F3A;2F3A;5F61;5F61; # (⼺; ⼺; ⼺; 彡; 彡; ) KANGXI RADICAL BRISTLE
+2F3B;2F3B;2F3B;5F73;5F73; # (â¼»; â¼»; â¼»; å½³; å½³; ) KANGXI RADICAL STEP
+2F3C;2F3C;2F3C;5FC3;5FC3; # (⼼; ⼼; ⼼; 心; 心; ) KANGXI RADICAL HEART
+2F3D;2F3D;2F3D;6208;6208; # (⼽; ⼽; ⼽; 戈; 戈; ) KANGXI RADICAL HALBERD
+2F3E;2F3E;2F3E;6236;6236; # (⼾; ⼾; ⼾; 戶; 戶; ) KANGXI RADICAL DOOR
+2F3F;2F3F;2F3F;624B;624B; # (⼿; ⼿; ⼿; 手; 手; ) KANGXI RADICAL HAND
+2F40;2F40;2F40;652F;652F; # (⽀; ⽀; ⽀; 支; 支; ) KANGXI RADICAL BRANCH
+2F41;2F41;2F41;6534;6534; # (â½; â½; â½; æ”´; æ”´; ) KANGXI RADICAL RAP
+2F42;2F42;2F42;6587;6587; # (⽂; ⽂; ⽂; 文; 文; ) KANGXI RADICAL SCRIPT
+2F43;2F43;2F43;6597;6597; # (⽃; ⽃; ⽃; 斗; 斗; ) KANGXI RADICAL DIPPER
+2F44;2F44;2F44;65A4;65A4; # (⽄; ⽄; ⽄; 斤; 斤; ) KANGXI RADICAL AXE
+2F45;2F45;2F45;65B9;65B9; # (â½…; â½…; â½…; æ–¹; æ–¹; ) KANGXI RADICAL SQUARE
+2F46;2F46;2F46;65E0;65E0; # (⽆; ⽆; ⽆; 无; 无; ) KANGXI RADICAL NOT
+2F47;2F47;2F47;65E5;65E5; # (⽇; ⽇; ⽇; 日; 日; ) KANGXI RADICAL SUN
+2F48;2F48;2F48;66F0;66F0; # (⽈; ⽈; ⽈; 曰; 曰; ) KANGXI RADICAL SAY
+2F49;2F49;2F49;6708;6708; # (⽉; ⽉; ⽉; 月; 月; ) KANGXI RADICAL MOON
+2F4A;2F4A;2F4A;6728;6728; # (⽊; ⽊; ⽊; 木; 木; ) KANGXI RADICAL TREE
+2F4B;2F4B;2F4B;6B20;6B20; # (⽋; ⽋; ⽋; 欠; 欠; ) KANGXI RADICAL LACK
+2F4C;2F4C;2F4C;6B62;6B62; # (⽌; ⽌; ⽌; 止; 止; ) KANGXI RADICAL STOP
+2F4D;2F4D;2F4D;6B79;6B79; # (â½; â½; â½; æ­¹; æ­¹; ) KANGXI RADICAL DEATH
+2F4E;2F4E;2F4E;6BB3;6BB3; # (⽎; ⽎; ⽎; 殳; 殳; ) KANGXI RADICAL WEAPON
+2F4F;2F4F;2F4F;6BCB;6BCB; # (â½; â½; â½; 毋; 毋; ) KANGXI RADICAL DO NOT
+2F50;2F50;2F50;6BD4;6BD4; # (â½; â½; â½; 比; 比; ) KANGXI RADICAL COMPARE
+2F51;2F51;2F51;6BDB;6BDB; # (⽑; ⽑; ⽑; 毛; 毛; ) KANGXI RADICAL FUR
+2F52;2F52;2F52;6C0F;6C0F; # (â½’; â½’; â½’; æ°; æ°; ) KANGXI RADICAL CLAN
+2F53;2F53;2F53;6C14;6C14; # (⽓; ⽓; ⽓; 气; 气; ) KANGXI RADICAL STEAM
+2F54;2F54;2F54;6C34;6C34; # (â½”; â½”; â½”; æ°´; æ°´; ) KANGXI RADICAL WATER
+2F55;2F55;2F55;706B;706B; # (⽕; ⽕; ⽕; ç«; ç«; ) KANGXI RADICAL FIRE
+2F56;2F56;2F56;722A;722A; # (⽖; ⽖; ⽖; 爪; 爪; ) KANGXI RADICAL CLAW
+2F57;2F57;2F57;7236;7236; # (⽗; ⽗; ⽗; 父; 父; ) KANGXI RADICAL FATHER
+2F58;2F58;2F58;723B;723B; # (⽘; ⽘; ⽘; 爻; 爻; ) KANGXI RADICAL DOUBLE X
+2F59;2F59;2F59;723F;723F; # (⽙; ⽙; ⽙; 爿; 爿; ) KANGXI RADICAL HALF TREE TRUNK
+2F5A;2F5A;2F5A;7247;7247; # (⽚; ⽚; ⽚; 片; 片; ) KANGXI RADICAL SLICE
+2F5B;2F5B;2F5B;7259;7259; # (⽛; ⽛; ⽛; 牙; 牙; ) KANGXI RADICAL FANG
+2F5C;2F5C;2F5C;725B;725B; # (⽜; ⽜; ⽜; 牛; 牛; ) KANGXI RADICAL COW
+2F5D;2F5D;2F5D;72AC;72AC; # (â½; â½; â½; 犬; 犬; ) KANGXI RADICAL DOG
+2F5E;2F5E;2F5E;7384;7384; # (⽞; ⽞; ⽞; 玄; 玄; ) KANGXI RADICAL PROFOUND
+2F5F;2F5F;2F5F;7389;7389; # (⽟; ⽟; ⽟; 玉; 玉; ) KANGXI RADICAL JADE
+2F60;2F60;2F60;74DC;74DC; # (⽠; ⽠; ⽠; 瓜; 瓜; ) KANGXI RADICAL MELON
+2F61;2F61;2F61;74E6;74E6; # (⽡; ⽡; ⽡; 瓦; 瓦; ) KANGXI RADICAL TILE
+2F62;2F62;2F62;7518;7518; # (⽢; ⽢; ⽢; 甘; 甘; ) KANGXI RADICAL SWEET
+2F63;2F63;2F63;751F;751F; # (⽣; ⽣; ⽣; 生; 生; ) KANGXI RADICAL LIFE
+2F64;2F64;2F64;7528;7528; # (⽤; ⽤; ⽤; 用; 用; ) KANGXI RADICAL USE
+2F65;2F65;2F65;7530;7530; # (â½¥; â½¥; â½¥; ç”°; ç”°; ) KANGXI RADICAL FIELD
+2F66;2F66;2F66;758B;758B; # (⽦; ⽦; ⽦; 疋; 疋; ) KANGXI RADICAL BOLT OF CLOTH
+2F67;2F67;2F67;7592;7592; # (⽧; ⽧; ⽧; 疒; 疒; ) KANGXI RADICAL SICKNESS
+2F68;2F68;2F68;7676;7676; # (⽨; ⽨; ⽨; 癶; 癶; ) KANGXI RADICAL DOTTED TENT
+2F69;2F69;2F69;767D;767D; # (⽩; ⽩; ⽩; 白; 白; ) KANGXI RADICAL WHITE
+2F6A;2F6A;2F6A;76AE;76AE; # (⽪; ⽪; ⽪; 皮; 皮; ) KANGXI RADICAL SKIN
+2F6B;2F6B;2F6B;76BF;76BF; # (⽫; ⽫; ⽫; 皿; 皿; ) KANGXI RADICAL DISH
+2F6C;2F6C;2F6C;76EE;76EE; # (⽬; ⽬; ⽬; 目; 目; ) KANGXI RADICAL EYE
+2F6D;2F6D;2F6D;77DB;77DB; # (⽭; ⽭; ⽭; 矛; 矛; ) KANGXI RADICAL SPEAR
+2F6E;2F6E;2F6E;77E2;77E2; # (⽮; ⽮; ⽮; 矢; 矢; ) KANGXI RADICAL ARROW
+2F6F;2F6F;2F6F;77F3;77F3; # (⽯; ⽯; ⽯; 石; 石; ) KANGXI RADICAL STONE
+2F70;2F70;2F70;793A;793A; # (⽰; ⽰; ⽰; 示; 示; ) KANGXI RADICAL SPIRIT
+2F71;2F71;2F71;79B8;79B8; # (⽱; ⽱; ⽱; 禸; 禸; ) KANGXI RADICAL TRACK
+2F72;2F72;2F72;79BE;79BE; # (⽲; ⽲; ⽲; 禾; 禾; ) KANGXI RADICAL GRAIN
+2F73;2F73;2F73;7A74;7A74; # (â½³; â½³; â½³; ç©´; ç©´; ) KANGXI RADICAL CAVE
+2F74;2F74;2F74;7ACB;7ACB; # (â½´; â½´; â½´; ç«‹; ç«‹; ) KANGXI RADICAL STAND
+2F75;2F75;2F75;7AF9;7AF9; # (⽵; ⽵; ⽵; 竹; 竹; ) KANGXI RADICAL BAMBOO
+2F76;2F76;2F76;7C73;7C73; # (⽶; ⽶; ⽶; 米; 米; ) KANGXI RADICAL RICE
+2F77;2F77;2F77;7CF8;7CF8; # (⽷; ⽷; ⽷; 糸; 糸; ) KANGXI RADICAL SILK
+2F78;2F78;2F78;7F36;7F36; # (⽸; ⽸; ⽸; 缶; 缶; ) KANGXI RADICAL JAR
+2F79;2F79;2F79;7F51;7F51; # (⽹; ⽹; ⽹; 网; 网; ) KANGXI RADICAL NET
+2F7A;2F7A;2F7A;7F8A;7F8A; # (⽺; ⽺; ⽺; 羊; 羊; ) KANGXI RADICAL SHEEP
+2F7B;2F7B;2F7B;7FBD;7FBD; # (â½»; â½»; â½»; ç¾½; ç¾½; ) KANGXI RADICAL FEATHER
+2F7C;2F7C;2F7C;8001;8001; # (â½¼; â½¼; â½¼; è€; è€; ) KANGXI RADICAL OLD
+2F7D;2F7D;2F7D;800C;800C; # (⽽; ⽽; ⽽; 而; 而; ) KANGXI RADICAL AND
+2F7E;2F7E;2F7E;8012;8012; # (⽾; ⽾; ⽾; 耒; 耒; ) KANGXI RADICAL PLOW
+2F7F;2F7F;2F7F;8033;8033; # (⽿; ⽿; ⽿; 耳; 耳; ) KANGXI RADICAL EAR
+2F80;2F80;2F80;807F;807F; # (â¾€; â¾€; â¾€; è¿; è¿; ) KANGXI RADICAL BRUSH
+2F81;2F81;2F81;8089;8089; # (â¾; â¾; â¾; 肉; 肉; ) KANGXI RADICAL MEAT
+2F82;2F82;2F82;81E3;81E3; # (⾂; ⾂; ⾂; 臣; 臣; ) KANGXI RADICAL MINISTER
+2F83;2F83;2F83;81EA;81EA; # (⾃; ⾃; ⾃; 自; 自; ) KANGXI RADICAL SELF
+2F84;2F84;2F84;81F3;81F3; # (⾄; ⾄; ⾄; 至; 至; ) KANGXI RADICAL ARRIVE
+2F85;2F85;2F85;81FC;81FC; # (⾅; ⾅; ⾅; 臼; 臼; ) KANGXI RADICAL MORTAR
+2F86;2F86;2F86;820C;820C; # (⾆; ⾆; ⾆; 舌; 舌; ) KANGXI RADICAL TONGUE
+2F87;2F87;2F87;821B;821B; # (⾇; ⾇; ⾇; 舛; 舛; ) KANGXI RADICAL OPPOSE
+2F88;2F88;2F88;821F;821F; # (⾈; ⾈; ⾈; 舟; 舟; ) KANGXI RADICAL BOAT
+2F89;2F89;2F89;826E;826E; # (⾉; ⾉; ⾉; 艮; 艮; ) KANGXI RADICAL STOPPING
+2F8A;2F8A;2F8A;8272;8272; # (⾊; ⾊; ⾊; 色; 色; ) KANGXI RADICAL COLOR
+2F8B;2F8B;2F8B;8278;8278; # (⾋; ⾋; ⾋; 艸; 艸; ) KANGXI RADICAL GRASS
+2F8C;2F8C;2F8C;864D;864D; # (⾌; ⾌; ⾌; è™; è™; ) KANGXI RADICAL TIGER
+2F8D;2F8D;2F8D;866B;866B; # (â¾; â¾; â¾; 虫; 虫; ) KANGXI RADICAL INSECT
+2F8E;2F8E;2F8E;8840;8840; # (⾎; ⾎; ⾎; 血; 血; ) KANGXI RADICAL BLOOD
+2F8F;2F8F;2F8F;884C;884C; # (â¾; â¾; â¾; è¡Œ; è¡Œ; ) KANGXI RADICAL WALK ENCLOSURE
+2F90;2F90;2F90;8863;8863; # (â¾; â¾; â¾; è¡£; è¡£; ) KANGXI RADICAL CLOTHES
+2F91;2F91;2F91;897E;897E; # (⾑; ⾑; ⾑; 襾; 襾; ) KANGXI RADICAL WEST
+2F92;2F92;2F92;898B;898B; # (⾒; ⾒; ⾒; 見; 見; ) KANGXI RADICAL SEE
+2F93;2F93;2F93;89D2;89D2; # (⾓; ⾓; ⾓; 角; 角; ) KANGXI RADICAL HORN
+2F94;2F94;2F94;8A00;8A00; # (⾔; ⾔; ⾔; 言; 言; ) KANGXI RADICAL SPEECH
+2F95;2F95;2F95;8C37;8C37; # (⾕; ⾕; ⾕; 谷; 谷; ) KANGXI RADICAL VALLEY
+2F96;2F96;2F96;8C46;8C46; # (⾖; ⾖; ⾖; 豆; 豆; ) KANGXI RADICAL BEAN
+2F97;2F97;2F97;8C55;8C55; # (⾗; ⾗; ⾗; 豕; 豕; ) KANGXI RADICAL PIG
+2F98;2F98;2F98;8C78;8C78; # (⾘; ⾘; ⾘; 豸; 豸; ) KANGXI RADICAL BADGER
+2F99;2F99;2F99;8C9D;8C9D; # (â¾™; â¾™; â¾™; è²; è²; ) KANGXI RADICAL SHELL
+2F9A;2F9A;2F9A;8D64;8D64; # (⾚; ⾚; ⾚; 赤; 赤; ) KANGXI RADICAL RED
+2F9B;2F9B;2F9B;8D70;8D70; # (â¾›; â¾›; â¾›; èµ°; èµ°; ) KANGXI RADICAL RUN
+2F9C;2F9C;2F9C;8DB3;8DB3; # (⾜; ⾜; ⾜; 足; 足; ) KANGXI RADICAL FOOT
+2F9D;2F9D;2F9D;8EAB;8EAB; # (â¾; â¾; â¾; 身; 身; ) KANGXI RADICAL BODY
+2F9E;2F9E;2F9E;8ECA;8ECA; # (⾞; ⾞; ⾞; 車; 車; ) KANGXI RADICAL CART
+2F9F;2F9F;2F9F;8F9B;8F9B; # (⾟; ⾟; ⾟; 辛; 辛; ) KANGXI RADICAL BITTER
+2FA0;2FA0;2FA0;8FB0;8FB0; # (â¾ ; â¾ ; â¾ ; è¾°; è¾°; ) KANGXI RADICAL MORNING
+2FA1;2FA1;2FA1;8FB5;8FB5; # (⾡; ⾡; ⾡; 辵; 辵; ) KANGXI RADICAL WALK
+2FA2;2FA2;2FA2;9091;9091; # (â¾¢; â¾¢; â¾¢; é‚‘; é‚‘; ) KANGXI RADICAL CITY
+2FA3;2FA3;2FA3;9149;9149; # (â¾£; â¾£; â¾£; é…‰; é…‰; ) KANGXI RADICAL WINE
+2FA4;2FA4;2FA4;91C6;91C6; # (⾤; ⾤; ⾤; 釆; 釆; ) KANGXI RADICAL DISTINGUISH
+2FA5;2FA5;2FA5;91CC;91CC; # (⾥; ⾥; ⾥; 里; 里; ) KANGXI RADICAL VILLAGE
+2FA6;2FA6;2FA6;91D1;91D1; # (⾦; ⾦; ⾦; 金; 金; ) KANGXI RADICAL GOLD
+2FA7;2FA7;2FA7;9577;9577; # (⾧; ⾧; ⾧; 長; 長; ) KANGXI RADICAL LONG
+2FA8;2FA8;2FA8;9580;9580; # (⾨; ⾨; ⾨; 門; 門; ) KANGXI RADICAL GATE
+2FA9;2FA9;2FA9;961C;961C; # (⾩; ⾩; ⾩; 阜; 阜; ) KANGXI RADICAL MOUND
+2FAA;2FAA;2FAA;96B6;96B6; # (⾪; ⾪; ⾪; 隶; 隶; ) KANGXI RADICAL SLAVE
+2FAB;2FAB;2FAB;96B9;96B9; # (⾫; ⾫; ⾫; 隹; 隹; ) KANGXI RADICAL SHORT TAILED BIRD
+2FAC;2FAC;2FAC;96E8;96E8; # (⾬; ⾬; ⾬; 雨; 雨; ) KANGXI RADICAL RAIN
+2FAD;2FAD;2FAD;9751;9751; # (â¾­; â¾­; â¾­; é‘; é‘; ) KANGXI RADICAL BLUE
+2FAE;2FAE;2FAE;975E;975E; # (â¾®; â¾®; â¾®; éž; éž; ) KANGXI RADICAL WRONG
+2FAF;2FAF;2FAF;9762;9762; # (⾯; ⾯; ⾯; é¢; é¢; ) KANGXI RADICAL FACE
+2FB0;2FB0;2FB0;9769;9769; # (â¾°; â¾°; â¾°; é©; é©; ) KANGXI RADICAL LEATHER
+2FB1;2FB1;2FB1;97CB;97CB; # (⾱; ⾱; ⾱; 韋; 韋; ) KANGXI RADICAL TANNED LEATHER
+2FB2;2FB2;2FB2;97ED;97ED; # (⾲; ⾲; ⾲; 韭; 韭; ) KANGXI RADICAL LEEK
+2FB3;2FB3;2FB3;97F3;97F3; # (⾳; ⾳; ⾳; 音; 音; ) KANGXI RADICAL SOUND
+2FB4;2FB4;2FB4;9801;9801; # (â¾´; â¾´; â¾´; é ; é ; ) KANGXI RADICAL LEAF
+2FB5;2FB5;2FB5;98A8;98A8; # (⾵; ⾵; ⾵; 風; 風; ) KANGXI RADICAL WIND
+2FB6;2FB6;2FB6;98DB;98DB; # (⾶; ⾶; ⾶; 飛; 飛; ) KANGXI RADICAL FLY
+2FB7;2FB7;2FB7;98DF;98DF; # (⾷; ⾷; ⾷; 食; 食; ) KANGXI RADICAL EAT
+2FB8;2FB8;2FB8;9996;9996; # (⾸; ⾸; ⾸; 首; 首; ) KANGXI RADICAL HEAD
+2FB9;2FB9;2FB9;9999;9999; # (⾹; ⾹; ⾹; 香; 香; ) KANGXI RADICAL FRAGRANT
+2FBA;2FBA;2FBA;99AC;99AC; # (⾺; ⾺; ⾺; 馬; 馬; ) KANGXI RADICAL HORSE
+2FBB;2FBB;2FBB;9AA8;9AA8; # (⾻; ⾻; ⾻; 骨; 骨; ) KANGXI RADICAL BONE
+2FBC;2FBC;2FBC;9AD8;9AD8; # (⾼; ⾼; ⾼; 高; 高; ) KANGXI RADICAL TALL
+2FBD;2FBD;2FBD;9ADF;9ADF; # (â¾½; â¾½; â¾½; é«Ÿ; é«Ÿ; ) KANGXI RADICAL HAIR
+2FBE;2FBE;2FBE;9B25;9B25; # (⾾; ⾾; ⾾; 鬥; 鬥; ) KANGXI RADICAL FIGHT
+2FBF;2FBF;2FBF;9B2F;9B2F; # (⾿; ⾿; ⾿; 鬯; 鬯; ) KANGXI RADICAL SACRIFICIAL WINE
+2FC0;2FC0;2FC0;9B32;9B32; # (⿀; ⿀; ⿀; 鬲; 鬲; ) KANGXI RADICAL CAULDRON
+2FC1;2FC1;2FC1;9B3C;9B3C; # (â¿; â¿; â¿; 鬼; 鬼; ) KANGXI RADICAL GHOST
+2FC2;2FC2;2FC2;9B5A;9B5A; # (â¿‚; â¿‚; â¿‚; é­š; é­š; ) KANGXI RADICAL FISH
+2FC3;2FC3;2FC3;9CE5;9CE5; # (⿃; ⿃; ⿃; 鳥; 鳥; ) KANGXI RADICAL BIRD
+2FC4;2FC4;2FC4;9E75;9E75; # (â¿„; â¿„; â¿„; é¹µ; é¹µ; ) KANGXI RADICAL SALT
+2FC5;2FC5;2FC5;9E7F;9E7F; # (⿅; ⿅; ⿅; 鹿; 鹿; ) KANGXI RADICAL DEER
+2FC6;2FC6;2FC6;9EA5;9EA5; # (⿆; ⿆; ⿆; 麥; 麥; ) KANGXI RADICAL WHEAT
+2FC7;2FC7;2FC7;9EBB;9EBB; # (⿇; ⿇; ⿇; 麻; 麻; ) KANGXI RADICAL HEMP
+2FC8;2FC8;2FC8;9EC3;9EC3; # (⿈; ⿈; ⿈; 黃; 黃; ) KANGXI RADICAL YELLOW
+2FC9;2FC9;2FC9;9ECD;9ECD; # (⿉; ⿉; ⿉; é»; é»; ) KANGXI RADICAL MILLET
+2FCA;2FCA;2FCA;9ED1;9ED1; # (⿊; ⿊; ⿊; 黑; 黑; ) KANGXI RADICAL BLACK
+2FCB;2FCB;2FCB;9EF9;9EF9; # (⿋; ⿋; ⿋; 黹; 黹; ) KANGXI RADICAL EMBROIDERY
+2FCC;2FCC;2FCC;9EFD;9EFD; # (⿌; ⿌; ⿌; 黽; 黽; ) KANGXI RADICAL FROG
+2FCD;2FCD;2FCD;9F0E;9F0E; # (â¿; â¿; â¿; 鼎; 鼎; ) KANGXI RADICAL TRIPOD
+2FCE;2FCE;2FCE;9F13;9F13; # (⿎; ⿎; ⿎; 鼓; 鼓; ) KANGXI RADICAL DRUM
+2FCF;2FCF;2FCF;9F20;9F20; # (â¿; â¿; â¿; é¼ ; é¼ ; ) KANGXI RADICAL RAT
+2FD0;2FD0;2FD0;9F3B;9F3B; # (â¿; â¿; â¿; é¼»; é¼»; ) KANGXI RADICAL NOSE
+2FD1;2FD1;2FD1;9F4A;9F4A; # (⿑; ⿑; ⿑; 齊; 齊; ) KANGXI RADICAL EVEN
+2FD2;2FD2;2FD2;9F52;9F52; # (â¿’; â¿’; â¿’; é½’; é½’; ) KANGXI RADICAL TOOTH
+2FD3;2FD3;2FD3;9F8D;9F8D; # (â¿“; â¿“; â¿“; é¾; é¾; ) KANGXI RADICAL DRAGON
+2FD4;2FD4;2FD4;9F9C;9F9C; # (⿔; ⿔; ⿔; 龜; 龜; ) KANGXI RADICAL TURTLE
+2FD5;2FD5;2FD5;9FA0;9FA0; # (â¿•; â¿•; â¿•; é¾ ; é¾ ; ) KANGXI RADICAL FLUTE
+3000;3000;3000;0020;0020; # ( ;  ;  ; ; ; ) IDEOGRAPHIC SPACE
+3036;3036;3036;3012;3012; # (〶; 〶; 〶; 〒; 〒; ) CIRCLED POSTAL MARK
+3038;3038;3038;5341;5341; # (〸; 〸; 〸; å; å; ) HANGZHOU NUMERAL TEN
+3039;3039;3039;5344;5344; # (〹; 〹; 〹; å„; å„; ) HANGZHOU NUMERAL TWENTY
+303A;303A;303A;5345;5345; # (〺; 〺; 〺; å…; å…; ) HANGZHOU NUMERAL THIRTY
+304C;304C;304B 3099;304C;304B 3099; # (ãŒ; ãŒ; ã‹â—Œã‚™; ãŒ; ã‹â—Œã‚™; ) HIRAGANA LETTER GA
+304E;304E;304D 3099;304E;304D 3099; # (ãŽ; ãŽ; ã◌゙; ãŽ; ã◌゙; ) HIRAGANA LETTER GI
+3050;3050;304F 3099;3050;304F 3099; # (ã; ã; ã◌゙; ã; ã◌゙; ) HIRAGANA LETTER GU
+3052;3052;3051 3099;3052;3051 3099; # (ã’; ã’; ã‘◌゙; ã’; ã‘◌゙; ) HIRAGANA LETTER GE
+3054;3054;3053 3099;3054;3053 3099; # (ã”; ã”; ã“◌゙; ã”; ã“◌゙; ) HIRAGANA LETTER GO
+3056;3056;3055 3099;3056;3055 3099; # (ã–; ã–; ã•â—Œã‚™; ã–; ã•â—Œã‚™; ) HIRAGANA LETTER ZA
+3058;3058;3057 3099;3058;3057 3099; # (ã˜; ã˜; ã—◌゙; ã˜; ã—◌゙; ) HIRAGANA LETTER ZI
+305A;305A;3059 3099;305A;3059 3099; # (ãš; ãš; ã™â—Œã‚™; ãš; ã™â—Œã‚™; ) HIRAGANA LETTER ZU
+305C;305C;305B 3099;305C;305B 3099; # (ãœ; ãœ; ã›â—Œã‚™; ãœ; ã›â—Œã‚™; ) HIRAGANA LETTER ZE
+305E;305E;305D 3099;305E;305D 3099; # (ãž; ãž; ã◌゙; ãž; ã◌゙; ) HIRAGANA LETTER ZO
+3060;3060;305F 3099;3060;305F 3099; # (ã ; ã ; ãŸâ—Œã‚™; ã ; ãŸâ—Œã‚™; ) HIRAGANA LETTER DA
+3062;3062;3061 3099;3062;3061 3099; # (ã¢; ã¢; ã¡â—Œã‚™; ã¢; ã¡â—Œã‚™; ) HIRAGANA LETTER DI
+3065;3065;3064 3099;3065;3064 3099; # (ã¥; ã¥; ã¤â—Œã‚™; ã¥; ã¤â—Œã‚™; ) HIRAGANA LETTER DU
+3067;3067;3066 3099;3067;3066 3099; # (ã§; ã§; ã¦â—Œã‚™; ã§; ã¦â—Œã‚™; ) HIRAGANA LETTER DE
+3069;3069;3068 3099;3069;3068 3099; # (ã©; ã©; ã¨â—Œã‚™; ã©; ã¨â—Œã‚™; ) HIRAGANA LETTER DO
+3070;3070;306F 3099;3070;306F 3099; # (ã°; ã°; ã¯â—Œã‚™; ã°; ã¯â—Œã‚™; ) HIRAGANA LETTER BA
+3071;3071;306F 309A;3071;306F 309A; # (ã±; ã±; ã¯â—Œã‚š; ã±; ã¯â—Œã‚š; ) HIRAGANA LETTER PA
+3073;3073;3072 3099;3073;3072 3099; # (ã³; ã³; ã²â—Œã‚™; ã³; ã²â—Œã‚™; ) HIRAGANA LETTER BI
+3074;3074;3072 309A;3074;3072 309A; # (ã´; ã´; ã²â—Œã‚š; ã´; ã²â—Œã‚š; ) HIRAGANA LETTER PI
+3076;3076;3075 3099;3076;3075 3099; # (ã¶; ã¶; ãµâ—Œã‚™; ã¶; ãµâ—Œã‚™; ) HIRAGANA LETTER BU
+3077;3077;3075 309A;3077;3075 309A; # (ã·; ã·; ãµâ—Œã‚š; ã·; ãµâ—Œã‚š; ) HIRAGANA LETTER PU
+3079;3079;3078 3099;3079;3078 3099; # (ã¹; ã¹; ã¸â—Œã‚™; ã¹; ã¸â—Œã‚™; ) HIRAGANA LETTER BE
+307A;307A;3078 309A;307A;3078 309A; # (ãº; ãº; ã¸â—Œã‚š; ãº; ã¸â—Œã‚š; ) HIRAGANA LETTER PE
+307C;307C;307B 3099;307C;307B 3099; # (ã¼; ã¼; ã»â—Œã‚™; ã¼; ã»â—Œã‚™; ) HIRAGANA LETTER BO
+307D;307D;307B 309A;307D;307B 309A; # (ã½; ã½; ã»â—Œã‚š; ã½; ã»â—Œã‚š; ) HIRAGANA LETTER PO
+3094;3094;3046 3099;3094;3046 3099; # (ã‚”; ã‚”; ã†â—Œã‚™; ã‚”; ã†â—Œã‚™; ) HIRAGANA LETTER VU
+309B;309B;309B;0020 3099;0020 3099; # (゛; ゛; ゛; ◌゙; ◌゙; ) KATAKANA-HIRAGANA VOICED SOUND MARK
+309C;309C;309C;0020 309A;0020 309A; # (゜; ゜; ゜; ◌゚; ◌゚; ) KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+309E;309E;309D 3099;309E;309D 3099; # (ã‚ž; ã‚ž; ã‚◌゙; ã‚ž; ã‚◌゙; ) HIRAGANA VOICED ITERATION MARK
+309F;309F;309F;3088 308A;3088 308A; # (ゟ; ゟ; ゟ; より; より; ) HIRAGANA DIGRAPH YORI
+30AC;30AC;30AB 3099;30AC;30AB 3099; # (ガ; ガ; カ◌゙; ガ; カ◌゙; ) KATAKANA LETTER GA
+30AE;30AE;30AD 3099;30AE;30AD 3099; # (ギ; ギ; キ◌゙; ギ; キ◌゙; ) KATAKANA LETTER GI
+30B0;30B0;30AF 3099;30B0;30AF 3099; # (グ; グ; ク◌゙; グ; ク◌゙; ) KATAKANA LETTER GU
+30B2;30B2;30B1 3099;30B2;30B1 3099; # (ゲ; ゲ; ケ◌゙; ゲ; ケ◌゙; ) KATAKANA LETTER GE
+30B4;30B4;30B3 3099;30B4;30B3 3099; # (ゴ; ゴ; コ◌゙; ゴ; コ◌゙; ) KATAKANA LETTER GO
+30B6;30B6;30B5 3099;30B6;30B5 3099; # (ザ; ザ; サ◌゙; ザ; サ◌゙; ) KATAKANA LETTER ZA
+30B8;30B8;30B7 3099;30B8;30B7 3099; # (ジ; ジ; シ◌゙; ジ; シ◌゙; ) KATAKANA LETTER ZI
+30BA;30BA;30B9 3099;30BA;30B9 3099; # (ズ; ズ; ス◌゙; ズ; ス◌゙; ) KATAKANA LETTER ZU
+30BC;30BC;30BB 3099;30BC;30BB 3099; # (ゼ; ゼ; セ◌゙; ゼ; セ◌゙; ) KATAKANA LETTER ZE
+30BE;30BE;30BD 3099;30BE;30BD 3099; # (ゾ; ゾ; ソ◌゙; ゾ; ソ◌゙; ) KATAKANA LETTER ZO
+30C0;30C0;30BF 3099;30C0;30BF 3099; # (ダ; ダ; タ◌゙; ダ; タ◌゙; ) KATAKANA LETTER DA
+30C2;30C2;30C1 3099;30C2;30C1 3099; # (ヂ; ヂ; ãƒâ—Œã‚™; ヂ; ãƒâ—Œã‚™; ) KATAKANA LETTER DI
+30C5;30C5;30C4 3099;30C5;30C4 3099; # (ヅ; ヅ; ツ◌゙; ヅ; ツ◌゙; ) KATAKANA LETTER DU
+30C7;30C7;30C6 3099;30C7;30C6 3099; # (デ; デ; テ◌゙; デ; テ◌゙; ) KATAKANA LETTER DE
+30C9;30C9;30C8 3099;30C9;30C8 3099; # (ド; ド; ト◌゙; ド; ト◌゙; ) KATAKANA LETTER DO
+30D0;30D0;30CF 3099;30D0;30CF 3099; # (ãƒ; ãƒ; ãƒâ—Œã‚™; ãƒ; ãƒâ—Œã‚™; ) KATAKANA LETTER BA
+30D1;30D1;30CF 309A;30D1;30CF 309A; # (パ; パ; ãƒâ—Œã‚š; パ; ãƒâ—Œã‚š; ) KATAKANA LETTER PA
+30D3;30D3;30D2 3099;30D3;30D2 3099; # (ビ; ビ; ヒ◌゙; ビ; ヒ◌゙; ) KATAKANA LETTER BI
+30D4;30D4;30D2 309A;30D4;30D2 309A; # (ピ; ピ; ヒ◌゚; ピ; ヒ◌゚; ) KATAKANA LETTER PI
+30D6;30D6;30D5 3099;30D6;30D5 3099; # (ブ; ブ; フ◌゙; ブ; フ◌゙; ) KATAKANA LETTER BU
+30D7;30D7;30D5 309A;30D7;30D5 309A; # (プ; プ; フ◌゚; プ; フ◌゚; ) KATAKANA LETTER PU
+30D9;30D9;30D8 3099;30D9;30D8 3099; # (ベ; ベ; ヘ◌゙; ベ; ヘ◌゙; ) KATAKANA LETTER BE
+30DA;30DA;30D8 309A;30DA;30D8 309A; # (ペ; ペ; ヘ◌゚; ペ; ヘ◌゚; ) KATAKANA LETTER PE
+30DC;30DC;30DB 3099;30DC;30DB 3099; # (ボ; ボ; ホ◌゙; ボ; ホ◌゙; ) KATAKANA LETTER BO
+30DD;30DD;30DB 309A;30DD;30DB 309A; # (ãƒ; ãƒ; ホ◌゚; ãƒ; ホ◌゚; ) KATAKANA LETTER PO
+30F4;30F4;30A6 3099;30F4;30A6 3099; # (ヴ; ヴ; ウ◌゙; ヴ; ウ◌゙; ) KATAKANA LETTER VU
+30F7;30F7;30EF 3099;30F7;30EF 3099; # (ヷ; ヷ; ワ◌゙; ヷ; ワ◌゙; ) KATAKANA LETTER VA
+30F8;30F8;30F0 3099;30F8;30F0 3099; # (ヸ; ヸ; ヰ◌゙; ヸ; ヰ◌゙; ) KATAKANA LETTER VI
+30F9;30F9;30F1 3099;30F9;30F1 3099; # (ヹ; ヹ; ヱ◌゙; ヹ; ヱ◌゙; ) KATAKANA LETTER VE
+30FA;30FA;30F2 3099;30FA;30F2 3099; # (ヺ; ヺ; ヲ◌゙; ヺ; ヲ◌゙; ) KATAKANA LETTER VO
+30FE;30FE;30FD 3099;30FE;30FD 3099; # (ヾ; ヾ; ヽ◌゙; ヾ; ヽ◌゙; ) KATAKANA VOICED ITERATION MARK
+30FF;30FF;30FF;30B3 30C8;30B3 30C8; # (ヿ; ヿ; ヿ; コト; コト; ) KATAKANA DIGRAPH KOTO
+3131;3131;3131;1100;1100; # (ㄱ; ㄱ; ㄱ; ᄀ; ᄀ; ) HANGUL LETTER KIYEOK
+3132;3132;3132;1101;1101; # (ㄲ; ㄲ; ㄲ; á„; á„; ) HANGUL LETTER SSANGKIYEOK
+3133;3133;3133;11AA;11AA; # (ㄳ; ㄳ; ㄳ; ᆪ; ᆪ; ) HANGUL LETTER KIYEOK-SIOS
+3134;3134;3134;1102;1102; # (ã„´; ã„´; ã„´; á„‚; á„‚; ) HANGUL LETTER NIEUN
+3135;3135;3135;11AC;11AC; # (ㄵ; ㄵ; ㄵ; ᆬ; ᆬ; ) HANGUL LETTER NIEUN-CIEUC
+3136;3136;3136;11AD;11AD; # (ㄶ; ㄶ; ㄶ; ᆭ; ᆭ; ) HANGUL LETTER NIEUN-HIEUH
+3137;3137;3137;1103;1103; # (ㄷ; ㄷ; ㄷ; ᄃ; ᄃ; ) HANGUL LETTER TIKEUT
+3138;3138;3138;1104;1104; # (ㄸ; ㄸ; ㄸ; ᄄ; ᄄ; ) HANGUL LETTER SSANGTIKEUT
+3139;3139;3139;1105;1105; # (ㄹ; ㄹ; ㄹ; ᄅ; ᄅ; ) HANGUL LETTER RIEUL
+313A;313A;313A;11B0;11B0; # (ㄺ; ㄺ; ㄺ; ᆰ; ᆰ; ) HANGUL LETTER RIEUL-KIYEOK
+313B;313B;313B;11B1;11B1; # (ㄻ; ㄻ; ㄻ; ᆱ; ᆱ; ) HANGUL LETTER RIEUL-MIEUM
+313C;313C;313C;11B2;11B2; # (ㄼ; ㄼ; ㄼ; ᆲ; ᆲ; ) HANGUL LETTER RIEUL-PIEUP
+313D;313D;313D;11B3;11B3; # (ㄽ; ㄽ; ㄽ; ᆳ; ᆳ; ) HANGUL LETTER RIEUL-SIOS
+313E;313E;313E;11B4;11B4; # (ㄾ; ㄾ; ㄾ; ᆴ; ᆴ; ) HANGUL LETTER RIEUL-THIEUTH
+313F;313F;313F;11B5;11B5; # (ㄿ; ㄿ; ㄿ; ᆵ; ᆵ; ) HANGUL LETTER RIEUL-PHIEUPH
+3140;3140;3140;111A;111A; # (ã…€; ã…€; ã…€; á„š; á„š; ) HANGUL LETTER RIEUL-HIEUH
+3141;3141;3141;1106;1106; # (ã…; ã…; ã…; ᄆ; ᄆ; ) HANGUL LETTER MIEUM
+3142;3142;3142;1107;1107; # (ㅂ; ㅂ; ㅂ; ᄇ; ᄇ; ) HANGUL LETTER PIEUP
+3143;3143;3143;1108;1108; # (ㅃ; ㅃ; ㅃ; ᄈ; ᄈ; ) HANGUL LETTER SSANGPIEUP
+3144;3144;3144;1121;1121; # (ã…„; ã…„; ã…„; á„¡; á„¡; ) HANGUL LETTER PIEUP-SIOS
+3145;3145;3145;1109;1109; # (ㅅ; ㅅ; ㅅ; ᄉ; ᄉ; ) HANGUL LETTER SIOS
+3146;3146;3146;110A;110A; # (ã…†; ã…†; ã…†; á„Š; á„Š; ) HANGUL LETTER SSANGSIOS
+3147;3147;3147;110B;110B; # (ã…‡; ã…‡; ã…‡; á„‹; á„‹; ) HANGUL LETTER IEUNG
+3148;3148;3148;110C;110C; # (ㅈ; ㅈ; ㅈ; ᄌ; ᄌ; ) HANGUL LETTER CIEUC
+3149;3149;3149;110D;110D; # (ã…‰; ã…‰; ã…‰; á„; á„; ) HANGUL LETTER SSANGCIEUC
+314A;314A;314A;110E;110E; # (ã…Š; ã…Š; ã…Š; á„Ž; á„Ž; ) HANGUL LETTER CHIEUCH
+314B;314B;314B;110F;110F; # (ã…‹; ã…‹; ã…‹; á„; á„; ) HANGUL LETTER KHIEUKH
+314C;314C;314C;1110;1110; # (ã…Œ; ã…Œ; ã…Œ; á„; á„; ) HANGUL LETTER THIEUTH
+314D;314D;314D;1111;1111; # (ã…; ã…; ã…; á„‘; á„‘; ) HANGUL LETTER PHIEUPH
+314E;314E;314E;1112;1112; # (ã…Ž; ã…Ž; ã…Ž; á„’; á„’; ) HANGUL LETTER HIEUH
+314F;314F;314F;1161;1161; # (ã…; ã…; ã…; á…¡; á…¡; ) HANGUL LETTER A
+3150;3150;3150;1162;1162; # (ã…; ã…; ã…; á…¢; á…¢; ) HANGUL LETTER AE
+3151;3151;3151;1163;1163; # (ã…‘; ã…‘; ã…‘; á…£; á…£; ) HANGUL LETTER YA
+3152;3152;3152;1164;1164; # (ã…’; ã…’; ã…’; á…¤; á…¤; ) HANGUL LETTER YAE
+3153;3153;3153;1165;1165; # (ã…“; ã…“; ã…“; á…¥; á…¥; ) HANGUL LETTER EO
+3154;3154;3154;1166;1166; # (ã…”; ã…”; ã…”; á…¦; á…¦; ) HANGUL LETTER E
+3155;3155;3155;1167;1167; # (ã…•; ã…•; ã…•; á…§; á…§; ) HANGUL LETTER YEO
+3156;3156;3156;1168;1168; # (ã…–; ã…–; ã…–; á…¨; á…¨; ) HANGUL LETTER YE
+3157;3157;3157;1169;1169; # (ã…—; ã…—; ã…—; á…©; á…©; ) HANGUL LETTER O
+3158;3158;3158;116A;116A; # (ã…˜; ã…˜; ã…˜; á…ª; á…ª; ) HANGUL LETTER WA
+3159;3159;3159;116B;116B; # (ã…™; ã…™; ã…™; á…«; á…«; ) HANGUL LETTER WAE
+315A;315A;315A;116C;116C; # (ã…š; ã…š; ã…š; á…¬; á…¬; ) HANGUL LETTER OE
+315B;315B;315B;116D;116D; # (ã…›; ã…›; ã…›; á…­; á…­; ) HANGUL LETTER YO
+315C;315C;315C;116E;116E; # (ㅜ; ㅜ; ㅜ; ᅮ; ᅮ; ) HANGUL LETTER U
+315D;315D;315D;116F;116F; # (ã…; ã…; ã…; á…¯; á…¯; ) HANGUL LETTER WEO
+315E;315E;315E;1170;1170; # (ã…ž; ã…ž; ã…ž; á…°; á…°; ) HANGUL LETTER WE
+315F;315F;315F;1171;1171; # (ã…Ÿ; ã…Ÿ; ã…Ÿ; á…±; á…±; ) HANGUL LETTER WI
+3160;3160;3160;1172;1172; # (ã… ; ã… ; ã… ; á…²; á…²; ) HANGUL LETTER YU
+3161;3161;3161;1173;1173; # (ã…¡; ã…¡; ã…¡; á…³; á…³; ) HANGUL LETTER EU
+3162;3162;3162;1174;1174; # (ã…¢; ã…¢; ã…¢; á…´; á…´; ) HANGUL LETTER YI
+3163;3163;3163;1175;1175; # (ã…£; ã…£; ã…£; á…µ; á…µ; ) HANGUL LETTER I
+3164;3164;3164;1160;1160; # (ã…¤; ã…¤; ã…¤; á… ; á… ; ) HANGUL FILLER
+3165;3165;3165;1114;1114; # (ã…¥; ã…¥; ã…¥; á„”; á„”; ) HANGUL LETTER SSANGNIEUN
+3166;3166;3166;1115;1115; # (ã…¦; ã…¦; ã…¦; á„•; á„•; ) HANGUL LETTER NIEUN-TIKEUT
+3167;3167;3167;11C7;11C7; # (ㅧ; ㅧ; ㅧ; ᇇ; ᇇ; ) HANGUL LETTER NIEUN-SIOS
+3168;3168;3168;11C8;11C8; # (ㅨ; ㅨ; ㅨ; ᇈ; ᇈ; ) HANGUL LETTER NIEUN-PANSIOS
+3169;3169;3169;11CC;11CC; # (ㅩ; ㅩ; ㅩ; ᇌ; ᇌ; ) HANGUL LETTER RIEUL-KIYEOK-SIOS
+316A;316A;316A;11CE;11CE; # (ㅪ; ㅪ; ㅪ; ᇎ; ᇎ; ) HANGUL LETTER RIEUL-TIKEUT
+316B;316B;316B;11D3;11D3; # (ㅫ; ㅫ; ㅫ; ᇓ; ᇓ; ) HANGUL LETTER RIEUL-PIEUP-SIOS
+316C;316C;316C;11D7;11D7; # (ㅬ; ㅬ; ㅬ; ᇗ; ᇗ; ) HANGUL LETTER RIEUL-PANSIOS
+316D;316D;316D;11D9;11D9; # (ㅭ; ㅭ; ㅭ; ᇙ; ᇙ; ) HANGUL LETTER RIEUL-YEORINHIEUH
+316E;316E;316E;111C;111C; # (ㅮ; ㅮ; ㅮ; ᄜ; ᄜ; ) HANGUL LETTER MIEUM-PIEUP
+316F;316F;316F;11DD;11DD; # (ã…¯; ã…¯; ã…¯; á‡; á‡; ) HANGUL LETTER MIEUM-SIOS
+3170;3170;3170;11DF;11DF; # (ㅰ; ㅰ; ㅰ; ᇟ; ᇟ; ) HANGUL LETTER MIEUM-PANSIOS
+3171;3171;3171;111D;111D; # (ã…±; ã…±; ã…±; á„; á„; ) HANGUL LETTER KAPYEOUNMIEUM
+3172;3172;3172;111E;111E; # (ã…²; ã…²; ã…²; á„ž; á„ž; ) HANGUL LETTER PIEUP-KIYEOK
+3173;3173;3173;1120;1120; # (ã…³; ã…³; ã…³; á„ ; á„ ; ) HANGUL LETTER PIEUP-TIKEUT
+3174;3174;3174;1122;1122; # (ã…´; ã…´; ã…´; á„¢; á„¢; ) HANGUL LETTER PIEUP-SIOS-KIYEOK
+3175;3175;3175;1123;1123; # (ã…µ; ã…µ; ã…µ; á„£; á„£; ) HANGUL LETTER PIEUP-SIOS-TIKEUT
+3176;3176;3176;1127;1127; # (ㅶ; ㅶ; ㅶ; ᄧ; ᄧ; ) HANGUL LETTER PIEUP-CIEUC
+3177;3177;3177;1129;1129; # (ã…·; ã…·; ã…·; á„©; á„©; ) HANGUL LETTER PIEUP-THIEUTH
+3178;3178;3178;112B;112B; # (ã…¸; ã…¸; ã…¸; á„«; á„«; ) HANGUL LETTER KAPYEOUNPIEUP
+3179;3179;3179;112C;112C; # (ㅹ; ㅹ; ㅹ; ᄬ; ᄬ; ) HANGUL LETTER KAPYEOUNSSANGPIEUP
+317A;317A;317A;112D;112D; # (ã…º; ã…º; ã…º; á„­; á„­; ) HANGUL LETTER SIOS-KIYEOK
+317B;317B;317B;112E;112E; # (ã…»; ã…»; ã…»; á„®; á„®; ) HANGUL LETTER SIOS-NIEUN
+317C;317C;317C;112F;112F; # (ㅼ; ㅼ; ㅼ; ᄯ; ᄯ; ) HANGUL LETTER SIOS-TIKEUT
+317D;317D;317D;1132;1132; # (ㅽ; ㅽ; ㅽ; ᄲ; ᄲ; ) HANGUL LETTER SIOS-PIEUP
+317E;317E;317E;1136;1136; # (ㅾ; ㅾ; ㅾ; ᄶ; ᄶ; ) HANGUL LETTER SIOS-CIEUC
+317F;317F;317F;1140;1140; # (ã…¿; ã…¿; ã…¿; á…€; á…€; ) HANGUL LETTER PANSIOS
+3180;3180;3180;1147;1147; # (ㆀ; ㆀ; ㆀ; ᅇ; ᅇ; ) HANGUL LETTER SSANGIEUNG
+3181;3181;3181;114C;114C; # (ã†; ã†; ã†; á…Œ; á…Œ; ) HANGUL LETTER YESIEUNG
+3182;3182;3182;11F1;11F1; # (ㆂ; ㆂ; ㆂ; ᇱ; ᇱ; ) HANGUL LETTER YESIEUNG-SIOS
+3183;3183;3183;11F2;11F2; # (ㆃ; ㆃ; ㆃ; ᇲ; ᇲ; ) HANGUL LETTER YESIEUNG-PANSIOS
+3184;3184;3184;1157;1157; # (ㆄ; ㆄ; ㆄ; ᅗ; ᅗ; ) HANGUL LETTER KAPYEOUNPHIEUPH
+3185;3185;3185;1158;1158; # (ㆅ; ㆅ; ㆅ; ᅘ; ᅘ; ) HANGUL LETTER SSANGHIEUH
+3186;3186;3186;1159;1159; # (ㆆ; ㆆ; ㆆ; ᅙ; ᅙ; ) HANGUL LETTER YEORINHIEUH
+3187;3187;3187;1184;1184; # (ㆇ; ㆇ; ㆇ; ᆄ; ᆄ; ) HANGUL LETTER YO-YA
+3188;3188;3188;1185;1185; # (ㆈ; ㆈ; ㆈ; ᆅ; ᆅ; ) HANGUL LETTER YO-YAE
+3189;3189;3189;1188;1188; # (ㆉ; ㆉ; ㆉ; ᆈ; ᆈ; ) HANGUL LETTER YO-I
+318A;318A;318A;1191;1191; # (ㆊ; ㆊ; ㆊ; ᆑ; ᆑ; ) HANGUL LETTER YU-YEO
+318B;318B;318B;1192;1192; # (ㆋ; ㆋ; ㆋ; ᆒ; ᆒ; ) HANGUL LETTER YU-YE
+318C;318C;318C;1194;1194; # (ㆌ; ㆌ; ㆌ; ᆔ; ᆔ; ) HANGUL LETTER YU-I
+318D;318D;318D;119E;119E; # (ã†; ã†; ã†; ᆞ; ᆞ; ) HANGUL LETTER ARAEA
+318E;318E;318E;11A1;11A1; # (ㆎ; ㆎ; ㆎ; ᆡ; ᆡ; ) HANGUL LETTER ARAEAE
+3192;3192;3192;4E00;4E00; # (㆒; ㆒; ㆒; 一; 一; ) IDEOGRAPHIC ANNOTATION ONE MARK
+3193;3193;3193;4E8C;4E8C; # (㆓; ㆓; ㆓; 二; 二; ) IDEOGRAPHIC ANNOTATION TWO MARK
+3194;3194;3194;4E09;4E09; # (㆔; ㆔; ㆔; 三; 三; ) IDEOGRAPHIC ANNOTATION THREE MARK
+3195;3195;3195;56DB;56DB; # (㆕; ㆕; ㆕; 四; 四; ) IDEOGRAPHIC ANNOTATION FOUR MARK
+3196;3196;3196;4E0A;4E0A; # (㆖; ㆖; ㆖; 上; 上; ) IDEOGRAPHIC ANNOTATION TOP MARK
+3197;3197;3197;4E2D;4E2D; # (㆗; ㆗; ㆗; 中; 中; ) IDEOGRAPHIC ANNOTATION MIDDLE MARK
+3198;3198;3198;4E0B;4E0B; # (㆘; ㆘; ㆘; 下; 下; ) IDEOGRAPHIC ANNOTATION BOTTOM MARK
+3199;3199;3199;7532;7532; # (㆙; ㆙; ㆙; 甲; 甲; ) IDEOGRAPHIC ANNOTATION FIRST MARK
+319A;319A;319A;4E59;4E59; # (㆚; ㆚; ㆚; 乙; 乙; ) IDEOGRAPHIC ANNOTATION SECOND MARK
+319B;319B;319B;4E19;4E19; # (㆛; ㆛; ㆛; 丙; 丙; ) IDEOGRAPHIC ANNOTATION THIRD MARK
+319C;319C;319C;4E01;4E01; # (㆜; ㆜; ㆜; ä¸; ä¸; ) IDEOGRAPHIC ANNOTATION FOURTH MARK
+319D;319D;319D;5929;5929; # (ã†; ã†; ã†; 天; 天; ) IDEOGRAPHIC ANNOTATION HEAVEN MARK
+319E;319E;319E;5730;5730; # (㆞; ㆞; ㆞; 地; 地; ) IDEOGRAPHIC ANNOTATION EARTH MARK
+319F;319F;319F;4EBA;4EBA; # (㆟; ㆟; ㆟; 人; 人; ) IDEOGRAPHIC ANNOTATION MAN MARK
+3200;3200;3200;0028 1100 0029;0028 1100 0029; # (㈀; ㈀; ㈀; (ᄀ); (ᄀ); ) PARENTHESIZED HANGUL KIYEOK
+3201;3201;3201;0028 1102 0029;0028 1102 0029; # (ãˆ; ãˆ; ãˆ; (á„‚); (á„‚); ) PARENTHESIZED HANGUL NIEUN
+3202;3202;3202;0028 1103 0029;0028 1103 0029; # (㈂; ㈂; ㈂; (ᄃ); (ᄃ); ) PARENTHESIZED HANGUL TIKEUT
+3203;3203;3203;0028 1105 0029;0028 1105 0029; # (㈃; ㈃; ㈃; (ᄅ); (ᄅ); ) PARENTHESIZED HANGUL RIEUL
+3204;3204;3204;0028 1106 0029;0028 1106 0029; # (㈄; ㈄; ㈄; (ᄆ); (ᄆ); ) PARENTHESIZED HANGUL MIEUM
+3205;3205;3205;0028 1107 0029;0028 1107 0029; # (㈅; ㈅; ㈅; (ᄇ); (ᄇ); ) PARENTHESIZED HANGUL PIEUP
+3206;3206;3206;0028 1109 0029;0028 1109 0029; # (㈆; ㈆; ㈆; (ᄉ); (ᄉ); ) PARENTHESIZED HANGUL SIOS
+3207;3207;3207;0028 110B 0029;0028 110B 0029; # (㈇; ㈇; ㈇; (ᄋ); (ᄋ); ) PARENTHESIZED HANGUL IEUNG
+3208;3208;3208;0028 110C 0029;0028 110C 0029; # (㈈; ㈈; ㈈; (ᄌ); (ᄌ); ) PARENTHESIZED HANGUL CIEUC
+3209;3209;3209;0028 110E 0029;0028 110E 0029; # (㈉; ㈉; ㈉; (ᄎ); (ᄎ); ) PARENTHESIZED HANGUL CHIEUCH
+320A;320A;320A;0028 110F 0029;0028 110F 0029; # (㈊; ㈊; ㈊; (á„); (á„); ) PARENTHESIZED HANGUL KHIEUKH
+320B;320B;320B;0028 1110 0029;0028 1110 0029; # (㈋; ㈋; ㈋; (á„); (á„); ) PARENTHESIZED HANGUL THIEUTH
+320C;320C;320C;0028 1111 0029;0028 1111 0029; # (㈌; ㈌; ㈌; (ᄑ); (ᄑ); ) PARENTHESIZED HANGUL PHIEUPH
+320D;320D;320D;0028 1112 0029;0028 1112 0029; # (ãˆ; ãˆ; ãˆ; (á„’); (á„’); ) PARENTHESIZED HANGUL HIEUH
+320E;320E;320E;0028 AC00 0029;0028 1100 1161 0029; # (㈎; ㈎; ㈎; (가); (가); ) PARENTHESIZED HANGUL KIYEOK A
+320F;320F;320F;0028 B098 0029;0028 1102 1161 0029; # (ãˆ; ãˆ; ãˆ; (나); (á„‚á…¡); ) PARENTHESIZED HANGUL NIEUN A
+3210;3210;3210;0028 B2E4 0029;0028 1103 1161 0029; # (ãˆ; ãˆ; ãˆ; (다); (다); ) PARENTHESIZED HANGUL TIKEUT A
+3211;3211;3211;0028 B77C 0029;0028 1105 1161 0029; # (㈑; ㈑; ㈑; (ë¼); (á„…á…¡); ) PARENTHESIZED HANGUL RIEUL A
+3212;3212;3212;0028 B9C8 0029;0028 1106 1161 0029; # (㈒; ㈒; ㈒; (마); (마); ) PARENTHESIZED HANGUL MIEUM A
+3213;3213;3213;0028 BC14 0029;0028 1107 1161 0029; # (㈓; ㈓; ㈓; (바); (바); ) PARENTHESIZED HANGUL PIEUP A
+3214;3214;3214;0028 C0AC 0029;0028 1109 1161 0029; # (㈔; ㈔; ㈔; (사); (사); ) PARENTHESIZED HANGUL SIOS A
+3215;3215;3215;0028 C544 0029;0028 110B 1161 0029; # (㈕; ㈕; ㈕; (아); (아); ) PARENTHESIZED HANGUL IEUNG A
+3216;3216;3216;0028 C790 0029;0028 110C 1161 0029; # (㈖; ㈖; ㈖; (ìž); (자); ) PARENTHESIZED HANGUL CIEUC A
+3217;3217;3217;0028 CC28 0029;0028 110E 1161 0029; # (㈗; ㈗; ㈗; (차); (차); ) PARENTHESIZED HANGUL CHIEUCH A
+3218;3218;3218;0028 CE74 0029;0028 110F 1161 0029; # (㈘; ㈘; ㈘; (ì¹´); (á„á…¡); ) PARENTHESIZED HANGUL KHIEUKH A
+3219;3219;3219;0028 D0C0 0029;0028 1110 1161 0029; # (㈙; ㈙; ㈙; (타); (á„á…¡); ) PARENTHESIZED HANGUL THIEUTH A
+321A;321A;321A;0028 D30C 0029;0028 1111 1161 0029; # (㈚; ㈚; ㈚; (파); (파); ) PARENTHESIZED HANGUL PHIEUPH A
+321B;321B;321B;0028 D558 0029;0028 1112 1161 0029; # (㈛; ㈛; ㈛; (하); (하); ) PARENTHESIZED HANGUL HIEUH A
+321C;321C;321C;0028 C8FC 0029;0028 110C 116E 0029; # (㈜; ㈜; ㈜; (주); (주); ) PARENTHESIZED HANGUL CIEUC U
+321D;321D;321D;0028 C624 C804 0029;0028 110B 1169 110C 1165 11AB 0029; # (ãˆ; ãˆ; ãˆ; (오전); (오전); ) PARENTHESIZED KOREAN CHARACTER OJEON
+321E;321E;321E;0028 C624 D6C4 0029;0028 110B 1169 1112 116E 0029; # (㈞; ㈞; ㈞; (오후); (오후); ) PARENTHESIZED KOREAN CHARACTER O HU
+3220;3220;3220;0028 4E00 0029;0028 4E00 0029; # (㈠; ㈠; ㈠; (一); (一); ) PARENTHESIZED IDEOGRAPH ONE
+3221;3221;3221;0028 4E8C 0029;0028 4E8C 0029; # (㈡; ㈡; ㈡; (二); (二); ) PARENTHESIZED IDEOGRAPH TWO
+3222;3222;3222;0028 4E09 0029;0028 4E09 0029; # (㈢; ㈢; ㈢; (三); (三); ) PARENTHESIZED IDEOGRAPH THREE
+3223;3223;3223;0028 56DB 0029;0028 56DB 0029; # (㈣; ㈣; ㈣; (四); (四); ) PARENTHESIZED IDEOGRAPH FOUR
+3224;3224;3224;0028 4E94 0029;0028 4E94 0029; # (㈤; ㈤; ㈤; (五); (五); ) PARENTHESIZED IDEOGRAPH FIVE
+3225;3225;3225;0028 516D 0029;0028 516D 0029; # (㈥; ㈥; ㈥; (六); (六); ) PARENTHESIZED IDEOGRAPH SIX
+3226;3226;3226;0028 4E03 0029;0028 4E03 0029; # (㈦; ㈦; ㈦; (七); (七); ) PARENTHESIZED IDEOGRAPH SEVEN
+3227;3227;3227;0028 516B 0029;0028 516B 0029; # (㈧; ㈧; ㈧; (八); (八); ) PARENTHESIZED IDEOGRAPH EIGHT
+3228;3228;3228;0028 4E5D 0029;0028 4E5D 0029; # (㈨; ㈨; ㈨; (ä¹); (ä¹); ) PARENTHESIZED IDEOGRAPH NINE
+3229;3229;3229;0028 5341 0029;0028 5341 0029; # (㈩; ㈩; ㈩; (å); (å); ) PARENTHESIZED IDEOGRAPH TEN
+322A;322A;322A;0028 6708 0029;0028 6708 0029; # (㈪; ㈪; ㈪; (月); (月); ) PARENTHESIZED IDEOGRAPH MOON
+322B;322B;322B;0028 706B 0029;0028 706B 0029; # (㈫; ㈫; ㈫; (ç«); (ç«); ) PARENTHESIZED IDEOGRAPH FIRE
+322C;322C;322C;0028 6C34 0029;0028 6C34 0029; # (㈬; ㈬; ㈬; (水); (水); ) PARENTHESIZED IDEOGRAPH WATER
+322D;322D;322D;0028 6728 0029;0028 6728 0029; # (㈭; ㈭; ㈭; (木); (木); ) PARENTHESIZED IDEOGRAPH WOOD
+322E;322E;322E;0028 91D1 0029;0028 91D1 0029; # (㈮; ㈮; ㈮; (金); (金); ) PARENTHESIZED IDEOGRAPH METAL
+322F;322F;322F;0028 571F 0029;0028 571F 0029; # (㈯; ㈯; ㈯; (土); (土); ) PARENTHESIZED IDEOGRAPH EARTH
+3230;3230;3230;0028 65E5 0029;0028 65E5 0029; # (㈰; ㈰; ㈰; (日); (日); ) PARENTHESIZED IDEOGRAPH SUN
+3231;3231;3231;0028 682A 0029;0028 682A 0029; # (㈱; ㈱; ㈱; (株); (株); ) PARENTHESIZED IDEOGRAPH STOCK
+3232;3232;3232;0028 6709 0029;0028 6709 0029; # (㈲; ㈲; ㈲; (有); (有); ) PARENTHESIZED IDEOGRAPH HAVE
+3233;3233;3233;0028 793E 0029;0028 793E 0029; # (㈳; ㈳; ㈳; (社); (社); ) PARENTHESIZED IDEOGRAPH SOCIETY
+3234;3234;3234;0028 540D 0029;0028 540D 0029; # (㈴; ㈴; ㈴; (å); (å); ) PARENTHESIZED IDEOGRAPH NAME
+3235;3235;3235;0028 7279 0029;0028 7279 0029; # (㈵; ㈵; ㈵; (特); (特); ) PARENTHESIZED IDEOGRAPH SPECIAL
+3236;3236;3236;0028 8CA1 0029;0028 8CA1 0029; # (㈶; ㈶; ㈶; (財); (財); ) PARENTHESIZED IDEOGRAPH FINANCIAL
+3237;3237;3237;0028 795D 0029;0028 795D 0029; # (㈷; ㈷; ㈷; (ç¥); (ç¥); ) PARENTHESIZED IDEOGRAPH CONGRATULATION
+3238;3238;3238;0028 52B4 0029;0028 52B4 0029; # (㈸; ㈸; ㈸; (労); (労); ) PARENTHESIZED IDEOGRAPH LABOR
+3239;3239;3239;0028 4EE3 0029;0028 4EE3 0029; # (㈹; ㈹; ㈹; (代); (代); ) PARENTHESIZED IDEOGRAPH REPRESENT
+323A;323A;323A;0028 547C 0029;0028 547C 0029; # (㈺; ㈺; ㈺; (呼); (呼); ) PARENTHESIZED IDEOGRAPH CALL
+323B;323B;323B;0028 5B66 0029;0028 5B66 0029; # (㈻; ㈻; ㈻; (学); (学); ) PARENTHESIZED IDEOGRAPH STUDY
+323C;323C;323C;0028 76E3 0029;0028 76E3 0029; # (㈼; ㈼; ㈼; (監); (監); ) PARENTHESIZED IDEOGRAPH SUPERVISE
+323D;323D;323D;0028 4F01 0029;0028 4F01 0029; # (㈽; ㈽; ㈽; (ä¼); (ä¼); ) PARENTHESIZED IDEOGRAPH ENTERPRISE
+323E;323E;323E;0028 8CC7 0029;0028 8CC7 0029; # (㈾; ㈾; ㈾; (資); (資); ) PARENTHESIZED IDEOGRAPH RESOURCE
+323F;323F;323F;0028 5354 0029;0028 5354 0029; # (㈿; ㈿; ㈿; (å”); (å”); ) PARENTHESIZED IDEOGRAPH ALLIANCE
+3240;3240;3240;0028 796D 0029;0028 796D 0029; # (㉀; ㉀; ㉀; (祭); (祭); ) PARENTHESIZED IDEOGRAPH FESTIVAL
+3241;3241;3241;0028 4F11 0029;0028 4F11 0029; # (ã‰; ã‰; ã‰; (休); (休); ) PARENTHESIZED IDEOGRAPH REST
+3242;3242;3242;0028 81EA 0029;0028 81EA 0029; # (㉂; ㉂; ㉂; (自); (自); ) PARENTHESIZED IDEOGRAPH SELF
+3243;3243;3243;0028 81F3 0029;0028 81F3 0029; # (㉃; ㉃; ㉃; (至); (至); ) PARENTHESIZED IDEOGRAPH REACH
+3244;3244;3244;554F;554F; # (㉄; ㉄; ㉄; å•; å•; ) CIRCLED IDEOGRAPH QUESTION
+3245;3245;3245;5E7C;5E7C; # (㉅; ㉅; ㉅; 幼; 幼; ) CIRCLED IDEOGRAPH KINDERGARTEN
+3246;3246;3246;6587;6587; # (㉆; ㉆; ㉆; 文; 文; ) CIRCLED IDEOGRAPH SCHOOL
+3247;3247;3247;7B8F;7B8F; # (㉇; ㉇; ㉇; ç®; ç®; ) CIRCLED IDEOGRAPH KOTO
+3250;3250;3250;0050 0054 0045;0050 0054 0045; # (ã‰; ã‰; ã‰; PTE; PTE; ) PARTNERSHIP SIGN
+3251;3251;3251;0032 0031;0032 0031; # (㉑; ㉑; ㉑; 21; 21; ) CIRCLED NUMBER TWENTY ONE
+3252;3252;3252;0032 0032;0032 0032; # (㉒; ㉒; ㉒; 22; 22; ) CIRCLED NUMBER TWENTY TWO
+3253;3253;3253;0032 0033;0032 0033; # (㉓; ㉓; ㉓; 23; 23; ) CIRCLED NUMBER TWENTY THREE
+3254;3254;3254;0032 0034;0032 0034; # (㉔; ㉔; ㉔; 24; 24; ) CIRCLED NUMBER TWENTY FOUR
+3255;3255;3255;0032 0035;0032 0035; # (㉕; ㉕; ㉕; 25; 25; ) CIRCLED NUMBER TWENTY FIVE
+3256;3256;3256;0032 0036;0032 0036; # (㉖; ㉖; ㉖; 26; 26; ) CIRCLED NUMBER TWENTY SIX
+3257;3257;3257;0032 0037;0032 0037; # (㉗; ㉗; ㉗; 27; 27; ) CIRCLED NUMBER TWENTY SEVEN
+3258;3258;3258;0032 0038;0032 0038; # (㉘; ㉘; ㉘; 28; 28; ) CIRCLED NUMBER TWENTY EIGHT
+3259;3259;3259;0032 0039;0032 0039; # (㉙; ㉙; ㉙; 29; 29; ) CIRCLED NUMBER TWENTY NINE
+325A;325A;325A;0033 0030;0033 0030; # (㉚; ㉚; ㉚; 30; 30; ) CIRCLED NUMBER THIRTY
+325B;325B;325B;0033 0031;0033 0031; # (㉛; ㉛; ㉛; 31; 31; ) CIRCLED NUMBER THIRTY ONE
+325C;325C;325C;0033 0032;0033 0032; # (㉜; ㉜; ㉜; 32; 32; ) CIRCLED NUMBER THIRTY TWO
+325D;325D;325D;0033 0033;0033 0033; # (ã‰; ã‰; ã‰; 33; 33; ) CIRCLED NUMBER THIRTY THREE
+325E;325E;325E;0033 0034;0033 0034; # (㉞; ㉞; ㉞; 34; 34; ) CIRCLED NUMBER THIRTY FOUR
+325F;325F;325F;0033 0035;0033 0035; # (㉟; ㉟; ㉟; 35; 35; ) CIRCLED NUMBER THIRTY FIVE
+3260;3260;3260;1100;1100; # (㉠; ㉠; ㉠; ᄀ; ᄀ; ) CIRCLED HANGUL KIYEOK
+3261;3261;3261;1102;1102; # (㉡; ㉡; ㉡; ᄂ; ᄂ; ) CIRCLED HANGUL NIEUN
+3262;3262;3262;1103;1103; # (㉢; ㉢; ㉢; ᄃ; ᄃ; ) CIRCLED HANGUL TIKEUT
+3263;3263;3263;1105;1105; # (㉣; ㉣; ㉣; ᄅ; ᄅ; ) CIRCLED HANGUL RIEUL
+3264;3264;3264;1106;1106; # (㉤; ㉤; ㉤; ᄆ; ᄆ; ) CIRCLED HANGUL MIEUM
+3265;3265;3265;1107;1107; # (㉥; ㉥; ㉥; ᄇ; ᄇ; ) CIRCLED HANGUL PIEUP
+3266;3266;3266;1109;1109; # (㉦; ㉦; ㉦; ᄉ; ᄉ; ) CIRCLED HANGUL SIOS
+3267;3267;3267;110B;110B; # (㉧; ㉧; ㉧; ᄋ; ᄋ; ) CIRCLED HANGUL IEUNG
+3268;3268;3268;110C;110C; # (㉨; ㉨; ㉨; ᄌ; ᄌ; ) CIRCLED HANGUL CIEUC
+3269;3269;3269;110E;110E; # (㉩; ㉩; ㉩; ᄎ; ᄎ; ) CIRCLED HANGUL CHIEUCH
+326A;326A;326A;110F;110F; # (㉪; ㉪; ㉪; á„; á„; ) CIRCLED HANGUL KHIEUKH
+326B;326B;326B;1110;1110; # (㉫; ㉫; ㉫; á„; á„; ) CIRCLED HANGUL THIEUTH
+326C;326C;326C;1111;1111; # (㉬; ㉬; ㉬; ᄑ; ᄑ; ) CIRCLED HANGUL PHIEUPH
+326D;326D;326D;1112;1112; # (㉭; ㉭; ㉭; ᄒ; ᄒ; ) CIRCLED HANGUL HIEUH
+326E;326E;326E;AC00;1100 1161; # (㉮; ㉮; ㉮; 가; 가; ) CIRCLED HANGUL KIYEOK A
+326F;326F;326F;B098;1102 1161; # (㉯; ㉯; ㉯; 나; 나; ) CIRCLED HANGUL NIEUN A
+3270;3270;3270;B2E4;1103 1161; # (㉰; ㉰; ㉰; 다; 다; ) CIRCLED HANGUL TIKEUT A
+3271;3271;3271;B77C;1105 1161; # (㉱; ㉱; ㉱; ë¼; á„…á…¡; ) CIRCLED HANGUL RIEUL A
+3272;3272;3272;B9C8;1106 1161; # (㉲; ㉲; ㉲; 마; 마; ) CIRCLED HANGUL MIEUM A
+3273;3273;3273;BC14;1107 1161; # (㉳; ㉳; ㉳; 바; 바; ) CIRCLED HANGUL PIEUP A
+3274;3274;3274;C0AC;1109 1161; # (㉴; ㉴; ㉴; 사; 사; ) CIRCLED HANGUL SIOS A
+3275;3275;3275;C544;110B 1161; # (㉵; ㉵; ㉵; 아; 아; ) CIRCLED HANGUL IEUNG A
+3276;3276;3276;C790;110C 1161; # (㉶; ㉶; ㉶; ìž; 자; ) CIRCLED HANGUL CIEUC A
+3277;3277;3277;CC28;110E 1161; # (㉷; ㉷; ㉷; 차; 차; ) CIRCLED HANGUL CHIEUCH A
+3278;3278;3278;CE74;110F 1161; # (㉸; ㉸; ㉸; ì¹´; á„á…¡; ) CIRCLED HANGUL KHIEUKH A
+3279;3279;3279;D0C0;1110 1161; # (㉹; ㉹; ㉹; 타; á„á…¡; ) CIRCLED HANGUL THIEUTH A
+327A;327A;327A;D30C;1111 1161; # (㉺; ㉺; ㉺; 파; 파; ) CIRCLED HANGUL PHIEUPH A
+327B;327B;327B;D558;1112 1161; # (㉻; ㉻; ㉻; 하; 하; ) CIRCLED HANGUL HIEUH A
+327C;327C;327C;CC38 ACE0;110E 1161 11B7 1100 1169; # (㉼; ㉼; ㉼; 참고; 참고; ) CIRCLED KOREAN CHARACTER CHAMKO
+327D;327D;327D;C8FC C758;110C 116E 110B 1174; # (㉽; ㉽; ㉽; 주ì˜; 주의; ) CIRCLED KOREAN CHARACTER JUEUI
+327E;327E;327E;C6B0;110B 116E; # (㉾; ㉾; ㉾; 우; 우; ) CIRCLED HANGUL IEUNG U
+3280;3280;3280;4E00;4E00; # (㊀; ㊀; ㊀; 一; 一; ) CIRCLED IDEOGRAPH ONE
+3281;3281;3281;4E8C;4E8C; # (ãŠ; ãŠ; ãŠ; 二; 二; ) CIRCLED IDEOGRAPH TWO
+3282;3282;3282;4E09;4E09; # (㊂; ㊂; ㊂; 三; 三; ) CIRCLED IDEOGRAPH THREE
+3283;3283;3283;56DB;56DB; # (㊃; ㊃; ㊃; 四; 四; ) CIRCLED IDEOGRAPH FOUR
+3284;3284;3284;4E94;4E94; # (㊄; ㊄; ㊄; 五; 五; ) CIRCLED IDEOGRAPH FIVE
+3285;3285;3285;516D;516D; # (㊅; ㊅; ㊅; 六; 六; ) CIRCLED IDEOGRAPH SIX
+3286;3286;3286;4E03;4E03; # (㊆; ㊆; ㊆; 七; 七; ) CIRCLED IDEOGRAPH SEVEN
+3287;3287;3287;516B;516B; # (㊇; ㊇; ㊇; 八; 八; ) CIRCLED IDEOGRAPH EIGHT
+3288;3288;3288;4E5D;4E5D; # (㊈; ㊈; ㊈; ä¹; ä¹; ) CIRCLED IDEOGRAPH NINE
+3289;3289;3289;5341;5341; # (㊉; ㊉; ㊉; å; å; ) CIRCLED IDEOGRAPH TEN
+328A;328A;328A;6708;6708; # (㊊; ㊊; ㊊; 月; 月; ) CIRCLED IDEOGRAPH MOON
+328B;328B;328B;706B;706B; # (㊋; ㊋; ㊋; ç«; ç«; ) CIRCLED IDEOGRAPH FIRE
+328C;328C;328C;6C34;6C34; # (㊌; ㊌; ㊌; 水; 水; ) CIRCLED IDEOGRAPH WATER
+328D;328D;328D;6728;6728; # (ãŠ; ãŠ; ãŠ; 木; 木; ) CIRCLED IDEOGRAPH WOOD
+328E;328E;328E;91D1;91D1; # (㊎; ㊎; ㊎; 金; 金; ) CIRCLED IDEOGRAPH METAL
+328F;328F;328F;571F;571F; # (ãŠ; ãŠ; ãŠ; 土; 土; ) CIRCLED IDEOGRAPH EARTH
+3290;3290;3290;65E5;65E5; # (ãŠ; ãŠ; ãŠ; æ—¥; æ—¥; ) CIRCLED IDEOGRAPH SUN
+3291;3291;3291;682A;682A; # (㊑; ㊑; ㊑; 株; 株; ) CIRCLED IDEOGRAPH STOCK
+3292;3292;3292;6709;6709; # (㊒; ㊒; ㊒; 有; 有; ) CIRCLED IDEOGRAPH HAVE
+3293;3293;3293;793E;793E; # (㊓; ㊓; ㊓; 社; 社; ) CIRCLED IDEOGRAPH SOCIETY
+3294;3294;3294;540D;540D; # (㊔; ㊔; ㊔; å; å; ) CIRCLED IDEOGRAPH NAME
+3295;3295;3295;7279;7279; # (㊕; ㊕; ㊕; 特; 特; ) CIRCLED IDEOGRAPH SPECIAL
+3296;3296;3296;8CA1;8CA1; # (㊖; ㊖; ㊖; 財; 財; ) CIRCLED IDEOGRAPH FINANCIAL
+3297;3297;3297;795D;795D; # (㊗; ㊗; ㊗; ç¥; ç¥; ) CIRCLED IDEOGRAPH CONGRATULATION
+3298;3298;3298;52B4;52B4; # (㊘; ㊘; ㊘; 労; 労; ) CIRCLED IDEOGRAPH LABOR
+3299;3299;3299;79D8;79D8; # (㊙; ㊙; ㊙; 秘; 秘; ) CIRCLED IDEOGRAPH SECRET
+329A;329A;329A;7537;7537; # (㊚; ㊚; ㊚; 男; 男; ) CIRCLED IDEOGRAPH MALE
+329B;329B;329B;5973;5973; # (㊛; ㊛; ㊛; 女; 女; ) CIRCLED IDEOGRAPH FEMALE
+329C;329C;329C;9069;9069; # (㊜; ㊜; ㊜; é©; é©; ) CIRCLED IDEOGRAPH SUITABLE
+329D;329D;329D;512A;512A; # (ãŠ; ãŠ; ãŠ; 優; 優; ) CIRCLED IDEOGRAPH EXCELLENT
+329E;329E;329E;5370;5370; # (㊞; ㊞; ㊞; å°; å°; ) CIRCLED IDEOGRAPH PRINT
+329F;329F;329F;6CE8;6CE8; # (㊟; ㊟; ㊟; 注; 注; ) CIRCLED IDEOGRAPH ATTENTION
+32A0;32A0;32A0;9805;9805; # (㊠; ㊠; ㊠; 項; 項; ) CIRCLED IDEOGRAPH ITEM
+32A1;32A1;32A1;4F11;4F11; # (㊡; ㊡; ㊡; 休; 休; ) CIRCLED IDEOGRAPH REST
+32A2;32A2;32A2;5199;5199; # (㊢; ㊢; ㊢; 写; 写; ) CIRCLED IDEOGRAPH COPY
+32A3;32A3;32A3;6B63;6B63; # (㊣; ㊣; ㊣; 正; 正; ) CIRCLED IDEOGRAPH CORRECT
+32A4;32A4;32A4;4E0A;4E0A; # (㊤; ㊤; ㊤; 上; 上; ) CIRCLED IDEOGRAPH HIGH
+32A5;32A5;32A5;4E2D;4E2D; # (㊥; ㊥; ㊥; 中; 中; ) CIRCLED IDEOGRAPH CENTRE
+32A6;32A6;32A6;4E0B;4E0B; # (㊦; ㊦; ㊦; 下; 下; ) CIRCLED IDEOGRAPH LOW
+32A7;32A7;32A7;5DE6;5DE6; # (㊧; ㊧; ㊧; 左; 左; ) CIRCLED IDEOGRAPH LEFT
+32A8;32A8;32A8;53F3;53F3; # (㊨; ㊨; ㊨; å³; å³; ) CIRCLED IDEOGRAPH RIGHT
+32A9;32A9;32A9;533B;533B; # (㊩; ㊩; ㊩; 医; 医; ) CIRCLED IDEOGRAPH MEDICINE
+32AA;32AA;32AA;5B97;5B97; # (㊪; ㊪; ㊪; 宗; 宗; ) CIRCLED IDEOGRAPH RELIGION
+32AB;32AB;32AB;5B66;5B66; # (㊫; ㊫; ㊫; 学; 学; ) CIRCLED IDEOGRAPH STUDY
+32AC;32AC;32AC;76E3;76E3; # (㊬; ㊬; ㊬; 監; 監; ) CIRCLED IDEOGRAPH SUPERVISE
+32AD;32AD;32AD;4F01;4F01; # (㊭; ㊭; ㊭; ä¼; ä¼; ) CIRCLED IDEOGRAPH ENTERPRISE
+32AE;32AE;32AE;8CC7;8CC7; # (㊮; ㊮; ㊮; 資; 資; ) CIRCLED IDEOGRAPH RESOURCE
+32AF;32AF;32AF;5354;5354; # (㊯; ㊯; ㊯; å”; å”; ) CIRCLED IDEOGRAPH ALLIANCE
+32B0;32B0;32B0;591C;591C; # (㊰; ㊰; ㊰; 夜; 夜; ) CIRCLED IDEOGRAPH NIGHT
+32B1;32B1;32B1;0033 0036;0033 0036; # (㊱; ㊱; ㊱; 36; 36; ) CIRCLED NUMBER THIRTY SIX
+32B2;32B2;32B2;0033 0037;0033 0037; # (㊲; ㊲; ㊲; 37; 37; ) CIRCLED NUMBER THIRTY SEVEN
+32B3;32B3;32B3;0033 0038;0033 0038; # (㊳; ㊳; ㊳; 38; 38; ) CIRCLED NUMBER THIRTY EIGHT
+32B4;32B4;32B4;0033 0039;0033 0039; # (㊴; ㊴; ㊴; 39; 39; ) CIRCLED NUMBER THIRTY NINE
+32B5;32B5;32B5;0034 0030;0034 0030; # (㊵; ㊵; ㊵; 40; 40; ) CIRCLED NUMBER FORTY
+32B6;32B6;32B6;0034 0031;0034 0031; # (㊶; ㊶; ㊶; 41; 41; ) CIRCLED NUMBER FORTY ONE
+32B7;32B7;32B7;0034 0032;0034 0032; # (㊷; ㊷; ㊷; 42; 42; ) CIRCLED NUMBER FORTY TWO
+32B8;32B8;32B8;0034 0033;0034 0033; # (㊸; ㊸; ㊸; 43; 43; ) CIRCLED NUMBER FORTY THREE
+32B9;32B9;32B9;0034 0034;0034 0034; # (㊹; ㊹; ㊹; 44; 44; ) CIRCLED NUMBER FORTY FOUR
+32BA;32BA;32BA;0034 0035;0034 0035; # (㊺; ㊺; ㊺; 45; 45; ) CIRCLED NUMBER FORTY FIVE
+32BB;32BB;32BB;0034 0036;0034 0036; # (㊻; ㊻; ㊻; 46; 46; ) CIRCLED NUMBER FORTY SIX
+32BC;32BC;32BC;0034 0037;0034 0037; # (㊼; ㊼; ㊼; 47; 47; ) CIRCLED NUMBER FORTY SEVEN
+32BD;32BD;32BD;0034 0038;0034 0038; # (㊽; ㊽; ㊽; 48; 48; ) CIRCLED NUMBER FORTY EIGHT
+32BE;32BE;32BE;0034 0039;0034 0039; # (㊾; ㊾; ㊾; 49; 49; ) CIRCLED NUMBER FORTY NINE
+32BF;32BF;32BF;0035 0030;0035 0030; # (㊿; ㊿; ㊿; 50; 50; ) CIRCLED NUMBER FIFTY
+32C0;32C0;32C0;0031 6708;0031 6708; # (㋀; ㋀; ㋀; 1月; 1月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY
+32C1;32C1;32C1;0032 6708;0032 6708; # (ã‹; ã‹; ã‹; 2月; 2月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR FEBRUARY
+32C2;32C2;32C2;0033 6708;0033 6708; # (㋂; ㋂; ㋂; 3月; 3月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR MARCH
+32C3;32C3;32C3;0034 6708;0034 6708; # (㋃; ㋃; ㋃; 4月; 4月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR APRIL
+32C4;32C4;32C4;0035 6708;0035 6708; # (㋄; ㋄; ㋄; 5月; 5月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR MAY
+32C5;32C5;32C5;0036 6708;0036 6708; # (㋅; ㋅; ㋅; 6月; 6月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR JUNE
+32C6;32C6;32C6;0037 6708;0037 6708; # (㋆; ㋆; ㋆; 7月; 7月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR JULY
+32C7;32C7;32C7;0038 6708;0038 6708; # (㋇; ㋇; ㋇; 8月; 8月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR AUGUST
+32C8;32C8;32C8;0039 6708;0039 6708; # (㋈; ㋈; ㋈; 9月; 9月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR SEPTEMBER
+32C9;32C9;32C9;0031 0030 6708;0031 0030 6708; # (㋉; ㋉; ㋉; 10月; 10月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR OCTOBER
+32CA;32CA;32CA;0031 0031 6708;0031 0031 6708; # (㋊; ㋊; ㋊; 11月; 11月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR NOVEMBER
+32CB;32CB;32CB;0031 0032 6708;0031 0032 6708; # (㋋; ㋋; ㋋; 12月; 12月; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DECEMBER
+32CC;32CC;32CC;0048 0067;0048 0067; # (㋌; ㋌; ㋌; Hg; Hg; ) SQUARE HG
+32CD;32CD;32CD;0065 0072 0067;0065 0072 0067; # (ã‹; ã‹; ã‹; erg; erg; ) SQUARE ERG
+32CE;32CE;32CE;0065 0056;0065 0056; # (ã‹Ž; ã‹Ž; ã‹Ž; eV; eV; ) SQUARE EV
+32CF;32CF;32CF;004C 0054 0044;004C 0054 0044; # (ã‹; ã‹; ã‹; LTD; LTD; ) LIMITED LIABILITY SIGN
+32D0;32D0;32D0;30A2;30A2; # (ã‹; ã‹; ã‹; ã‚¢; ã‚¢; ) CIRCLED KATAKANA A
+32D1;32D1;32D1;30A4;30A4; # (㋑; ㋑; ㋑; イ; イ; ) CIRCLED KATAKANA I
+32D2;32D2;32D2;30A6;30A6; # (㋒; ㋒; ㋒; ウ; ウ; ) CIRCLED KATAKANA U
+32D3;32D3;32D3;30A8;30A8; # (㋓; ㋓; ㋓; エ; エ; ) CIRCLED KATAKANA E
+32D4;32D4;32D4;30AA;30AA; # (㋔; ㋔; ㋔; オ; オ; ) CIRCLED KATAKANA O
+32D5;32D5;32D5;30AB;30AB; # (ã‹•; ã‹•; ã‹•; ã‚«; ã‚«; ) CIRCLED KATAKANA KA
+32D6;32D6;32D6;30AD;30AD; # (ã‹–; ã‹–; ã‹–; ã‚­; ã‚­; ) CIRCLED KATAKANA KI
+32D7;32D7;32D7;30AF;30AF; # (㋗; ㋗; ㋗; ク; ク; ) CIRCLED KATAKANA KU
+32D8;32D8;32D8;30B1;30B1; # (㋘; ㋘; ㋘; ケ; ケ; ) CIRCLED KATAKANA KE
+32D9;32D9;32D9;30B3;30B3; # (㋙; ㋙; ㋙; コ; コ; ) CIRCLED KATAKANA KO
+32DA;32DA;32DA;30B5;30B5; # (㋚; ㋚; ㋚; サ; サ; ) CIRCLED KATAKANA SA
+32DB;32DB;32DB;30B7;30B7; # (ã‹›; ã‹›; ã‹›; ã‚·; ã‚·; ) CIRCLED KATAKANA SI
+32DC;32DC;32DC;30B9;30B9; # (㋜; ㋜; ㋜; ス; ス; ) CIRCLED KATAKANA SU
+32DD;32DD;32DD;30BB;30BB; # (ã‹; ã‹; ã‹; ã‚»; ã‚»; ) CIRCLED KATAKANA SE
+32DE;32DE;32DE;30BD;30BD; # (㋞; ㋞; ㋞; ソ; ソ; ) CIRCLED KATAKANA SO
+32DF;32DF;32DF;30BF;30BF; # (ã‹Ÿ; ã‹Ÿ; ã‹Ÿ; ã‚¿; ã‚¿; ) CIRCLED KATAKANA TA
+32E0;32E0;32E0;30C1;30C1; # (ã‹ ; ã‹ ; ã‹ ; ãƒ; ãƒ; ) CIRCLED KATAKANA TI
+32E1;32E1;32E1;30C4;30C4; # (㋡; ㋡; ㋡; ツ; ツ; ) CIRCLED KATAKANA TU
+32E2;32E2;32E2;30C6;30C6; # (㋢; ㋢; ㋢; テ; テ; ) CIRCLED KATAKANA TE
+32E3;32E3;32E3;30C8;30C8; # (㋣; ㋣; ㋣; ト; ト; ) CIRCLED KATAKANA TO
+32E4;32E4;32E4;30CA;30CA; # (㋤; ㋤; ㋤; ナ; ナ; ) CIRCLED KATAKANA NA
+32E5;32E5;32E5;30CB;30CB; # (㋥; ㋥; ㋥; ニ; ニ; ) CIRCLED KATAKANA NI
+32E6;32E6;32E6;30CC;30CC; # (㋦; ㋦; ㋦; ヌ; ヌ; ) CIRCLED KATAKANA NU
+32E7;32E7;32E7;30CD;30CD; # (㋧; ㋧; ㋧; ãƒ; ãƒ; ) CIRCLED KATAKANA NE
+32E8;32E8;32E8;30CE;30CE; # (㋨; ㋨; ㋨; ノ; ノ; ) CIRCLED KATAKANA NO
+32E9;32E9;32E9;30CF;30CF; # (ã‹©; ã‹©; ã‹©; ãƒ; ãƒ; ) CIRCLED KATAKANA HA
+32EA;32EA;32EA;30D2;30D2; # (㋪; ㋪; ㋪; ヒ; ヒ; ) CIRCLED KATAKANA HI
+32EB;32EB;32EB;30D5;30D5; # (㋫; ㋫; ㋫; フ; フ; ) CIRCLED KATAKANA HU
+32EC;32EC;32EC;30D8;30D8; # (㋬; ㋬; ㋬; ヘ; ヘ; ) CIRCLED KATAKANA HE
+32ED;32ED;32ED;30DB;30DB; # (㋭; ㋭; ㋭; ホ; ホ; ) CIRCLED KATAKANA HO
+32EE;32EE;32EE;30DE;30DE; # (㋮; ㋮; ㋮; マ; マ; ) CIRCLED KATAKANA MA
+32EF;32EF;32EF;30DF;30DF; # (㋯; ㋯; ㋯; ミ; ミ; ) CIRCLED KATAKANA MI
+32F0;32F0;32F0;30E0;30E0; # (㋰; ㋰; ㋰; ム; ム; ) CIRCLED KATAKANA MU
+32F1;32F1;32F1;30E1;30E1; # (㋱; ㋱; ㋱; メ; メ; ) CIRCLED KATAKANA ME
+32F2;32F2;32F2;30E2;30E2; # (㋲; ㋲; ㋲; モ; モ; ) CIRCLED KATAKANA MO
+32F3;32F3;32F3;30E4;30E4; # (㋳; ㋳; ㋳; ヤ; ヤ; ) CIRCLED KATAKANA YA
+32F4;32F4;32F4;30E6;30E6; # (㋴; ㋴; ㋴; ユ; ユ; ) CIRCLED KATAKANA YU
+32F5;32F5;32F5;30E8;30E8; # (㋵; ㋵; ㋵; ヨ; ヨ; ) CIRCLED KATAKANA YO
+32F6;32F6;32F6;30E9;30E9; # (㋶; ㋶; ㋶; ラ; ラ; ) CIRCLED KATAKANA RA
+32F7;32F7;32F7;30EA;30EA; # (㋷; ㋷; ㋷; リ; リ; ) CIRCLED KATAKANA RI
+32F8;32F8;32F8;30EB;30EB; # (㋸; ㋸; ㋸; ル; ル; ) CIRCLED KATAKANA RU
+32F9;32F9;32F9;30EC;30EC; # (㋹; ㋹; ㋹; レ; レ; ) CIRCLED KATAKANA RE
+32FA;32FA;32FA;30ED;30ED; # (㋺; ㋺; ㋺; ロ; ロ; ) CIRCLED KATAKANA RO
+32FB;32FB;32FB;30EF;30EF; # (㋻; ㋻; ㋻; ワ; ワ; ) CIRCLED KATAKANA WA
+32FC;32FC;32FC;30F0;30F0; # (㋼; ㋼; ㋼; ヰ; ヰ; ) CIRCLED KATAKANA WI
+32FD;32FD;32FD;30F1;30F1; # (㋽; ㋽; ㋽; ヱ; ヱ; ) CIRCLED KATAKANA WE
+32FE;32FE;32FE;30F2;30F2; # (㋾; ㋾; ㋾; ヲ; ヲ; ) CIRCLED KATAKANA WO
+32FF;32FF;32FF;4EE4 548C;4EE4 548C; # (㋿; ㋿; ㋿; 令和; 令和; ) SQUARE ERA NAME REIWA
+3300;3300;3300;30A2 30D1 30FC 30C8;30A2 30CF 309A 30FC 30C8; # (㌀; ㌀; ㌀; アパート; ã‚¢ãƒâ—Œã‚šãƒ¼ãƒˆ; ) SQUARE APAATO
+3301;3301;3301;30A2 30EB 30D5 30A1;30A2 30EB 30D5 30A1; # (ãŒ; ãŒ; ãŒ; アルファ; アルファ; ) SQUARE ARUHUA
+3302;3302;3302;30A2 30F3 30DA 30A2;30A2 30F3 30D8 309A 30A2; # (㌂; ㌂; ㌂; アンペア; アンヘ◌゚ア; ) SQUARE ANPEA
+3303;3303;3303;30A2 30FC 30EB;30A2 30FC 30EB; # (㌃; ㌃; ㌃; アール; アール; ) SQUARE AARU
+3304;3304;3304;30A4 30CB 30F3 30B0;30A4 30CB 30F3 30AF 3099; # (㌄; ㌄; ㌄; イニング; イニンク◌゙; ) SQUARE ININGU
+3305;3305;3305;30A4 30F3 30C1;30A4 30F3 30C1; # (㌅; ㌅; ㌅; インãƒ; インãƒ; ) SQUARE INTI
+3306;3306;3306;30A6 30A9 30F3;30A6 30A9 30F3; # (㌆; ㌆; ㌆; ウォン; ウォン; ) SQUARE UON
+3307;3307;3307;30A8 30B9 30AF 30FC 30C9;30A8 30B9 30AF 30FC 30C8 3099; # (㌇; ㌇; ㌇; エスクード; エスクート◌゙; ) SQUARE ESUKUUDO
+3308;3308;3308;30A8 30FC 30AB 30FC;30A8 30FC 30AB 30FC; # (㌈; ㌈; ㌈; エーカー; エーカー; ) SQUARE EEKAA
+3309;3309;3309;30AA 30F3 30B9;30AA 30F3 30B9; # (㌉; ㌉; ㌉; オンス; オンス; ) SQUARE ONSU
+330A;330A;330A;30AA 30FC 30E0;30AA 30FC 30E0; # (㌊; ㌊; ㌊; オーム; オーム; ) SQUARE OOMU
+330B;330B;330B;30AB 30A4 30EA;30AB 30A4 30EA; # (㌋; ㌋; ㌋; カイリ; カイリ; ) SQUARE KAIRI
+330C;330C;330C;30AB 30E9 30C3 30C8;30AB 30E9 30C3 30C8; # (㌌; ㌌; ㌌; カラット; カラット; ) SQUARE KARATTO
+330D;330D;330D;30AB 30ED 30EA 30FC;30AB 30ED 30EA 30FC; # (ãŒ; ãŒ; ãŒ; カロリー; カロリー; ) SQUARE KARORII
+330E;330E;330E;30AC 30ED 30F3;30AB 3099 30ED 30F3; # (㌎; ㌎; ㌎; ガロン; カ◌゙ロン; ) SQUARE GARON
+330F;330F;330F;30AC 30F3 30DE;30AB 3099 30F3 30DE; # (ãŒ; ãŒ; ãŒ; ガンマ; カ◌゙ンマ; ) SQUARE GANMA
+3310;3310;3310;30AE 30AC;30AD 3099 30AB 3099; # (ãŒ; ãŒ; ãŒ; ギガ; キ◌゙カ◌゙; ) SQUARE GIGA
+3311;3311;3311;30AE 30CB 30FC;30AD 3099 30CB 30FC; # (㌑; ㌑; ㌑; ギニー; キ◌゙ニー; ) SQUARE GINII
+3312;3312;3312;30AD 30E5 30EA 30FC;30AD 30E5 30EA 30FC; # (㌒; ㌒; ㌒; キュリー; キュリー; ) SQUARE KYURII
+3313;3313;3313;30AE 30EB 30C0 30FC;30AD 3099 30EB 30BF 3099 30FC; # (㌓; ㌓; ㌓; ギルダー; キ◌゙ルタ◌゙ー; ) SQUARE GIRUDAA
+3314;3314;3314;30AD 30ED;30AD 30ED; # (㌔; ㌔; ㌔; キロ; キロ; ) SQUARE KIRO
+3315;3315;3315;30AD 30ED 30B0 30E9 30E0;30AD 30ED 30AF 3099 30E9 30E0; # (㌕; ㌕; ㌕; キログラム; キロク◌゙ラム; ) SQUARE KIROGURAMU
+3316;3316;3316;30AD 30ED 30E1 30FC 30C8 30EB;30AD 30ED 30E1 30FC 30C8 30EB; # (㌖; ㌖; ㌖; キロメートル; キロメートル; ) SQUARE KIROMEETORU
+3317;3317;3317;30AD 30ED 30EF 30C3 30C8;30AD 30ED 30EF 30C3 30C8; # (㌗; ㌗; ㌗; キロワット; キロワット; ) SQUARE KIROWATTO
+3318;3318;3318;30B0 30E9 30E0;30AF 3099 30E9 30E0; # (㌘; ㌘; ㌘; グラム; ク◌゙ラム; ) SQUARE GURAMU
+3319;3319;3319;30B0 30E9 30E0 30C8 30F3;30AF 3099 30E9 30E0 30C8 30F3; # (㌙; ㌙; ㌙; グラムトン; ク◌゙ラムトン; ) SQUARE GURAMUTON
+331A;331A;331A;30AF 30EB 30BC 30A4 30ED;30AF 30EB 30BB 3099 30A4 30ED; # (㌚; ㌚; ㌚; クルゼイロ; クルセ◌゙イロ; ) SQUARE KURUZEIRO
+331B;331B;331B;30AF 30ED 30FC 30CD;30AF 30ED 30FC 30CD; # (㌛; ㌛; ㌛; クローãƒ; クローãƒ; ) SQUARE KUROONE
+331C;331C;331C;30B1 30FC 30B9;30B1 30FC 30B9; # (㌜; ㌜; ㌜; ケース; ケース; ) SQUARE KEESU
+331D;331D;331D;30B3 30EB 30CA;30B3 30EB 30CA; # (ãŒ; ãŒ; ãŒ; コルナ; コルナ; ) SQUARE KORUNA
+331E;331E;331E;30B3 30FC 30DD;30B3 30FC 30DB 309A; # (㌞; ㌞; ㌞; コーãƒ; コーホ◌゚; ) SQUARE KOOPO
+331F;331F;331F;30B5 30A4 30AF 30EB;30B5 30A4 30AF 30EB; # (㌟; ㌟; ㌟; サイクル; サイクル; ) SQUARE SAIKURU
+3320;3320;3320;30B5 30F3 30C1 30FC 30E0;30B5 30F3 30C1 30FC 30E0; # (㌠; ㌠; ㌠; サンãƒãƒ¼ãƒ ; サンãƒãƒ¼ãƒ ; ) SQUARE SANTIIMU
+3321;3321;3321;30B7 30EA 30F3 30B0;30B7 30EA 30F3 30AF 3099; # (㌡; ㌡; ㌡; シリング; シリンク◌゙; ) SQUARE SIRINGU
+3322;3322;3322;30BB 30F3 30C1;30BB 30F3 30C1; # (㌢; ㌢; ㌢; センãƒ; センãƒ; ) SQUARE SENTI
+3323;3323;3323;30BB 30F3 30C8;30BB 30F3 30C8; # (㌣; ㌣; ㌣; セント; セント; ) SQUARE SENTO
+3324;3324;3324;30C0 30FC 30B9;30BF 3099 30FC 30B9; # (㌤; ㌤; ㌤; ダース; タ◌゙ース; ) SQUARE DAASU
+3325;3325;3325;30C7 30B7;30C6 3099 30B7; # (㌥; ㌥; ㌥; デシ; テ◌゙シ; ) SQUARE DESI
+3326;3326;3326;30C9 30EB;30C8 3099 30EB; # (㌦; ㌦; ㌦; ドル; ト◌゙ル; ) SQUARE DORU
+3327;3327;3327;30C8 30F3;30C8 30F3; # (㌧; ㌧; ㌧; トン; トン; ) SQUARE TON
+3328;3328;3328;30CA 30CE;30CA 30CE; # (㌨; ㌨; ㌨; ナノ; ナノ; ) SQUARE NANO
+3329;3329;3329;30CE 30C3 30C8;30CE 30C3 30C8; # (㌩; ㌩; ㌩; ノット; ノット; ) SQUARE NOTTO
+332A;332A;332A;30CF 30A4 30C4;30CF 30A4 30C4; # (㌪; ㌪; ㌪; ãƒã‚¤ãƒ„; ãƒã‚¤ãƒ„; ) SQUARE HAITU
+332B;332B;332B;30D1 30FC 30BB 30F3 30C8;30CF 309A 30FC 30BB 30F3 30C8; # (㌫; ㌫; ㌫; パーセント; ãƒâ—Œã‚šãƒ¼ã‚»ãƒ³ãƒˆ; ) SQUARE PAASENTO
+332C;332C;332C;30D1 30FC 30C4;30CF 309A 30FC 30C4; # (㌬; ㌬; ㌬; パーツ; ãƒâ—Œã‚šãƒ¼ãƒ„; ) SQUARE PAATU
+332D;332D;332D;30D0 30FC 30EC 30EB;30CF 3099 30FC 30EC 30EB; # (㌭; ㌭; ㌭; ãƒãƒ¼ãƒ¬ãƒ«; ãƒâ—Œã‚™ãƒ¼ãƒ¬ãƒ«; ) SQUARE BAARERU
+332E;332E;332E;30D4 30A2 30B9 30C8 30EB;30D2 309A 30A2 30B9 30C8 30EB; # (㌮; ㌮; ㌮; ピアストル; ヒ◌゚アストル; ) SQUARE PIASUTORU
+332F;332F;332F;30D4 30AF 30EB;30D2 309A 30AF 30EB; # (㌯; ㌯; ㌯; ピクル; ヒ◌゚クル; ) SQUARE PIKURU
+3330;3330;3330;30D4 30B3;30D2 309A 30B3; # (㌰; ㌰; ㌰; ピコ; ヒ◌゚コ; ) SQUARE PIKO
+3331;3331;3331;30D3 30EB;30D2 3099 30EB; # (㌱; ㌱; ㌱; ビル; ヒ◌゙ル; ) SQUARE BIRU
+3332;3332;3332;30D5 30A1 30E9 30C3 30C9;30D5 30A1 30E9 30C3 30C8 3099; # (㌲; ㌲; ㌲; ファラッド; ファラット◌゙; ) SQUARE HUARADDO
+3333;3333;3333;30D5 30A3 30FC 30C8;30D5 30A3 30FC 30C8; # (㌳; ㌳; ㌳; フィート; フィート; ) SQUARE HUIITO
+3334;3334;3334;30D6 30C3 30B7 30A7 30EB;30D5 3099 30C3 30B7 30A7 30EB; # (㌴; ㌴; ㌴; ブッシェル; フ◌゙ッシェル; ) SQUARE BUSSYERU
+3335;3335;3335;30D5 30E9 30F3;30D5 30E9 30F3; # (㌵; ㌵; ㌵; フラン; フラン; ) SQUARE HURAN
+3336;3336;3336;30D8 30AF 30BF 30FC 30EB;30D8 30AF 30BF 30FC 30EB; # (㌶; ㌶; ㌶; ヘクタール; ヘクタール; ) SQUARE HEKUTAARU
+3337;3337;3337;30DA 30BD;30D8 309A 30BD; # (㌷; ㌷; ㌷; ペソ; ヘ◌゚ソ; ) SQUARE PESO
+3338;3338;3338;30DA 30CB 30D2;30D8 309A 30CB 30D2; # (㌸; ㌸; ㌸; ペニヒ; ヘ◌゚ニヒ; ) SQUARE PENIHI
+3339;3339;3339;30D8 30EB 30C4;30D8 30EB 30C4; # (㌹; ㌹; ㌹; ヘルツ; ヘルツ; ) SQUARE HERUTU
+333A;333A;333A;30DA 30F3 30B9;30D8 309A 30F3 30B9; # (㌺; ㌺; ㌺; ペンス; ヘ◌゚ンス; ) SQUARE PENSU
+333B;333B;333B;30DA 30FC 30B8;30D8 309A 30FC 30B7 3099; # (㌻; ㌻; ㌻; ページ; ヘ◌゚ーシ◌゙; ) SQUARE PEEZI
+333C;333C;333C;30D9 30FC 30BF;30D8 3099 30FC 30BF; # (㌼; ㌼; ㌼; ベータ; ヘ◌゙ータ; ) SQUARE BEETA
+333D;333D;333D;30DD 30A4 30F3 30C8;30DB 309A 30A4 30F3 30C8; # (㌽; ㌽; ㌽; ãƒã‚¤ãƒ³ãƒˆ; ホ◌゚イント; ) SQUARE POINTO
+333E;333E;333E;30DC 30EB 30C8;30DB 3099 30EB 30C8; # (㌾; ㌾; ㌾; ボルト; ホ◌゙ルト; ) SQUARE BORUTO
+333F;333F;333F;30DB 30F3;30DB 30F3; # (㌿; ㌿; ㌿; ホン; ホン; ) SQUARE HON
+3340;3340;3340;30DD 30F3 30C9;30DB 309A 30F3 30C8 3099; # (ã€; ã€; ã€; ãƒãƒ³ãƒ‰; ホ◌゚ント◌゙; ) SQUARE PONDO
+3341;3341;3341;30DB 30FC 30EB;30DB 30FC 30EB; # (ã; ã; ã; ホール; ホール; ) SQUARE HOORU
+3342;3342;3342;30DB 30FC 30F3;30DB 30FC 30F3; # (ã‚; ã‚; ã‚; ホーン; ホーン; ) SQUARE HOON
+3343;3343;3343;30DE 30A4 30AF 30ED;30DE 30A4 30AF 30ED; # (ãƒ; ãƒ; ãƒ; マイクロ; マイクロ; ) SQUARE MAIKURO
+3344;3344;3344;30DE 30A4 30EB;30DE 30A4 30EB; # (ã„; ã„; ã„; マイル; マイル; ) SQUARE MAIRU
+3345;3345;3345;30DE 30C3 30CF;30DE 30C3 30CF; # (ã…; ã…; ã…; マッãƒ; マッãƒ; ) SQUARE MAHHA
+3346;3346;3346;30DE 30EB 30AF;30DE 30EB 30AF; # (ã†; ã†; ã†; マルク; マルク; ) SQUARE MARUKU
+3347;3347;3347;30DE 30F3 30B7 30E7 30F3;30DE 30F3 30B7 30E7 30F3; # (ã‡; ã‡; ã‡; マンション; マンション; ) SQUARE MANSYON
+3348;3348;3348;30DF 30AF 30ED 30F3;30DF 30AF 30ED 30F3; # (ãˆ; ãˆ; ãˆ; ミクロン; ミクロン; ) SQUARE MIKURON
+3349;3349;3349;30DF 30EA;30DF 30EA; # (ã‰; ã‰; ã‰; ミリ; ミリ; ) SQUARE MIRI
+334A;334A;334A;30DF 30EA 30D0 30FC 30EB;30DF 30EA 30CF 3099 30FC 30EB; # (ãŠ; ãŠ; ãŠ; ミリãƒãƒ¼ãƒ«; ミリãƒâ—Œã‚™ãƒ¼ãƒ«; ) SQUARE MIRIBAARU
+334B;334B;334B;30E1 30AC;30E1 30AB 3099; # (ã‹; ã‹; ã‹; メガ; メカ◌゙; ) SQUARE MEGA
+334C;334C;334C;30E1 30AC 30C8 30F3;30E1 30AB 3099 30C8 30F3; # (ãŒ; ãŒ; ãŒ; メガトン; メカ◌゙トン; ) SQUARE MEGATON
+334D;334D;334D;30E1 30FC 30C8 30EB;30E1 30FC 30C8 30EB; # (ã; ã; ã; メートル; メートル; ) SQUARE MEETORU
+334E;334E;334E;30E4 30FC 30C9;30E4 30FC 30C8 3099; # (ãŽ; ãŽ; ãŽ; ヤード; ヤート◌゙; ) SQUARE YAADO
+334F;334F;334F;30E4 30FC 30EB;30E4 30FC 30EB; # (ã; ã; ã; ヤール; ヤール; ) SQUARE YAARU
+3350;3350;3350;30E6 30A2 30F3;30E6 30A2 30F3; # (ã; ã; ã; ユアン; ユアン; ) SQUARE YUAN
+3351;3351;3351;30EA 30C3 30C8 30EB;30EA 30C3 30C8 30EB; # (ã‘; ã‘; ã‘; リットル; リットル; ) SQUARE RITTORU
+3352;3352;3352;30EA 30E9;30EA 30E9; # (ã’; ã’; ã’; リラ; リラ; ) SQUARE RIRA
+3353;3353;3353;30EB 30D4 30FC;30EB 30D2 309A 30FC; # (ã“; ã“; ã“; ルピー; ルヒ◌゚ー; ) SQUARE RUPII
+3354;3354;3354;30EB 30FC 30D6 30EB;30EB 30FC 30D5 3099 30EB; # (ã”; ã”; ã”; ルーブル; ルーフ◌゙ル; ) SQUARE RUUBURU
+3355;3355;3355;30EC 30E0;30EC 30E0; # (ã•; ã•; ã•; レム; レム; ) SQUARE REMU
+3356;3356;3356;30EC 30F3 30C8 30B2 30F3;30EC 30F3 30C8 30B1 3099 30F3; # (ã–; ã–; ã–; レントゲン; レントケ◌゙ン; ) SQUARE RENTOGEN
+3357;3357;3357;30EF 30C3 30C8;30EF 30C3 30C8; # (ã—; ã—; ã—; ワット; ワット; ) SQUARE WATTO
+3358;3358;3358;0030 70B9;0030 70B9; # (ã˜; ã˜; ã˜; 0点; 0点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR ZERO
+3359;3359;3359;0031 70B9;0031 70B9; # (ã™; ã™; ã™; 1点; 1点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR ONE
+335A;335A;335A;0032 70B9;0032 70B9; # (ãš; ãš; ãš; 2点; 2点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWO
+335B;335B;335B;0033 70B9;0033 70B9; # (ã›; ã›; ã›; 3点; 3点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR THREE
+335C;335C;335C;0034 70B9;0034 70B9; # (ãœ; ãœ; ãœ; 4点; 4点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FOUR
+335D;335D;335D;0035 70B9;0035 70B9; # (ã; ã; ã; 5点; 5点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FIVE
+335E;335E;335E;0036 70B9;0036 70B9; # (ãž; ãž; ãž; 6点; 6点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SIX
+335F;335F;335F;0037 70B9;0037 70B9; # (ãŸ; ãŸ; ãŸ; 7点; 7点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SEVEN
+3360;3360;3360;0038 70B9;0038 70B9; # (ã ; ã ; ã ; 8点; 8点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR EIGHT
+3361;3361;3361;0039 70B9;0039 70B9; # (ã¡; ã¡; ã¡; 9点; 9点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR NINE
+3362;3362;3362;0031 0030 70B9;0031 0030 70B9; # (ã¢; ã¢; ã¢; 10点; 10点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TEN
+3363;3363;3363;0031 0031 70B9;0031 0031 70B9; # (ã£; ã£; ã£; 11点; 11点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR ELEVEN
+3364;3364;3364;0031 0032 70B9;0031 0032 70B9; # (ã¤; ã¤; ã¤; 12点; 12点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWELVE
+3365;3365;3365;0031 0033 70B9;0031 0033 70B9; # (ã¥; ã¥; ã¥; 13点; 13点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR THIRTEEN
+3366;3366;3366;0031 0034 70B9;0031 0034 70B9; # (ã¦; ã¦; ã¦; 14点; 14点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FOURTEEN
+3367;3367;3367;0031 0035 70B9;0031 0035 70B9; # (ã§; ã§; ã§; 15点; 15点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FIFTEEN
+3368;3368;3368;0031 0036 70B9;0031 0036 70B9; # (ã¨; ã¨; ã¨; 16点; 16点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SIXTEEN
+3369;3369;3369;0031 0037 70B9;0031 0037 70B9; # (ã©; ã©; ã©; 17点; 17点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SEVENTEEN
+336A;336A;336A;0031 0038 70B9;0031 0038 70B9; # (ãª; ãª; ãª; 18点; 18点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR EIGHTEEN
+336B;336B;336B;0031 0039 70B9;0031 0039 70B9; # (ã«; ã«; ã«; 19点; 19点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR NINETEEN
+336C;336C;336C;0032 0030 70B9;0032 0030 70B9; # (ã¬; ã¬; ã¬; 20点; 20点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY
+336D;336D;336D;0032 0031 70B9;0032 0031 70B9; # (ã­; ã­; ã­; 21点; 21点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-ONE
+336E;336E;336E;0032 0032 70B9;0032 0032 70B9; # (ã®; ã®; ã®; 22点; 22点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-TWO
+336F;336F;336F;0032 0033 70B9;0032 0033 70B9; # (ã¯; ã¯; ã¯; 23点; 23点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-THREE
+3370;3370;3370;0032 0034 70B9;0032 0034 70B9; # (ã°; ã°; ã°; 24点; 24点; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-FOUR
+3371;3371;3371;0068 0050 0061;0068 0050 0061; # (ã±; ã±; ã±; hPa; hPa; ) SQUARE HPA
+3372;3372;3372;0064 0061;0064 0061; # (ã²; ã²; ã²; da; da; ) SQUARE DA
+3373;3373;3373;0041 0055;0041 0055; # (ã³; ã³; ã³; AU; AU; ) SQUARE AU
+3374;3374;3374;0062 0061 0072;0062 0061 0072; # (ã´; ã´; ã´; bar; bar; ) SQUARE BAR
+3375;3375;3375;006F 0056;006F 0056; # (ãµ; ãµ; ãµ; oV; oV; ) SQUARE OV
+3376;3376;3376;0070 0063;0070 0063; # (ã¶; ã¶; ã¶; pc; pc; ) SQUARE PC
+3377;3377;3377;0064 006D;0064 006D; # (ã·; ã·; ã·; dm; dm; ) SQUARE DM
+3378;3378;3378;0064 006D 0032;0064 006D 0032; # (ã¸; ã¸; ã¸; dm2; dm2; ) SQUARE DM SQUARED
+3379;3379;3379;0064 006D 0033;0064 006D 0033; # (ã¹; ã¹; ã¹; dm3; dm3; ) SQUARE DM CUBED
+337A;337A;337A;0049 0055;0049 0055; # (ãº; ãº; ãº; IU; IU; ) SQUARE IU
+337B;337B;337B;5E73 6210;5E73 6210; # (ã»; ã»; ã»; å¹³æˆ; å¹³æˆ; ) SQUARE ERA NAME HEISEI
+337C;337C;337C;662D 548C;662D 548C; # (ã¼; ã¼; ã¼; 昭和; 昭和; ) SQUARE ERA NAME SYOUWA
+337D;337D;337D;5927 6B63;5927 6B63; # (ã½; ã½; ã½; 大正; 大正; ) SQUARE ERA NAME TAISYOU
+337E;337E;337E;660E 6CBB;660E 6CBB; # (ã¾; ã¾; ã¾; 明治; 明治; ) SQUARE ERA NAME MEIZI
+337F;337F;337F;682A 5F0F 4F1A 793E;682A 5F0F 4F1A 793E; # (ã¿; ã¿; ã¿; æ ªå¼ä¼šç¤¾; æ ªå¼ä¼šç¤¾; ) SQUARE CORPORATION
+3380;3380;3380;0070 0041;0070 0041; # (㎀; ㎀; ㎀; pA; pA; ) SQUARE PA AMPS
+3381;3381;3381;006E 0041;006E 0041; # (ãŽ; ãŽ; ãŽ; nA; nA; ) SQUARE NA
+3382;3382;3382;03BC 0041;03BC 0041; # (㎂; ㎂; ㎂; μA; μA; ) SQUARE MU A
+3383;3383;3383;006D 0041;006D 0041; # (㎃; ㎃; ㎃; mA; mA; ) SQUARE MA
+3384;3384;3384;006B 0041;006B 0041; # (㎄; ㎄; ㎄; kA; kA; ) SQUARE KA
+3385;3385;3385;004B 0042;004B 0042; # (㎅; ㎅; ㎅; KB; KB; ) SQUARE KB
+3386;3386;3386;004D 0042;004D 0042; # (㎆; ㎆; ㎆; MB; MB; ) SQUARE MB
+3387;3387;3387;0047 0042;0047 0042; # (㎇; ㎇; ㎇; GB; GB; ) SQUARE GB
+3388;3388;3388;0063 0061 006C;0063 0061 006C; # (㎈; ㎈; ㎈; cal; cal; ) SQUARE CAL
+3389;3389;3389;006B 0063 0061 006C;006B 0063 0061 006C; # (㎉; ㎉; ㎉; kcal; kcal; ) SQUARE KCAL
+338A;338A;338A;0070 0046;0070 0046; # (㎊; ㎊; ㎊; pF; pF; ) SQUARE PF
+338B;338B;338B;006E 0046;006E 0046; # (㎋; ㎋; ㎋; nF; nF; ) SQUARE NF
+338C;338C;338C;03BC 0046;03BC 0046; # (㎌; ㎌; ㎌; μF; μF; ) SQUARE MU F
+338D;338D;338D;03BC 0067;03BC 0067; # (ãŽ; ãŽ; ãŽ; μg; μg; ) SQUARE MU G
+338E;338E;338E;006D 0067;006D 0067; # (㎎; ㎎; ㎎; mg; mg; ) SQUARE MG
+338F;338F;338F;006B 0067;006B 0067; # (ãŽ; ãŽ; ãŽ; kg; kg; ) SQUARE KG
+3390;3390;3390;0048 007A;0048 007A; # (ãŽ; ãŽ; ãŽ; Hz; Hz; ) SQUARE HZ
+3391;3391;3391;006B 0048 007A;006B 0048 007A; # (㎑; ㎑; ㎑; kHz; kHz; ) SQUARE KHZ
+3392;3392;3392;004D 0048 007A;004D 0048 007A; # (㎒; ㎒; ㎒; MHz; MHz; ) SQUARE MHZ
+3393;3393;3393;0047 0048 007A;0047 0048 007A; # (㎓; ㎓; ㎓; GHz; GHz; ) SQUARE GHZ
+3394;3394;3394;0054 0048 007A;0054 0048 007A; # (㎔; ㎔; ㎔; THz; THz; ) SQUARE THZ
+3395;3395;3395;03BC 006C;03BC 006C; # (㎕; ㎕; ㎕; μl; μl; ) SQUARE MU L
+3396;3396;3396;006D 006C;006D 006C; # (㎖; ㎖; ㎖; ml; ml; ) SQUARE ML
+3397;3397;3397;0064 006C;0064 006C; # (㎗; ㎗; ㎗; dl; dl; ) SQUARE DL
+3398;3398;3398;006B 006C;006B 006C; # (㎘; ㎘; ㎘; kl; kl; ) SQUARE KL
+3399;3399;3399;0066 006D;0066 006D; # (㎙; ㎙; ㎙; fm; fm; ) SQUARE FM
+339A;339A;339A;006E 006D;006E 006D; # (㎚; ㎚; ㎚; nm; nm; ) SQUARE NM
+339B;339B;339B;03BC 006D;03BC 006D; # (㎛; ㎛; ㎛; μm; μm; ) SQUARE MU M
+339C;339C;339C;006D 006D;006D 006D; # (㎜; ㎜; ㎜; mm; mm; ) SQUARE MM
+339D;339D;339D;0063 006D;0063 006D; # (ãŽ; ãŽ; ãŽ; cm; cm; ) SQUARE CM
+339E;339E;339E;006B 006D;006B 006D; # (㎞; ㎞; ㎞; km; km; ) SQUARE KM
+339F;339F;339F;006D 006D 0032;006D 006D 0032; # (㎟; ㎟; ㎟; mm2; mm2; ) SQUARE MM SQUARED
+33A0;33A0;33A0;0063 006D 0032;0063 006D 0032; # (㎠; ㎠; ㎠; cm2; cm2; ) SQUARE CM SQUARED
+33A1;33A1;33A1;006D 0032;006D 0032; # (㎡; ㎡; ㎡; m2; m2; ) SQUARE M SQUARED
+33A2;33A2;33A2;006B 006D 0032;006B 006D 0032; # (㎢; ㎢; ㎢; km2; km2; ) SQUARE KM SQUARED
+33A3;33A3;33A3;006D 006D 0033;006D 006D 0033; # (㎣; ㎣; ㎣; mm3; mm3; ) SQUARE MM CUBED
+33A4;33A4;33A4;0063 006D 0033;0063 006D 0033; # (㎤; ㎤; ㎤; cm3; cm3; ) SQUARE CM CUBED
+33A5;33A5;33A5;006D 0033;006D 0033; # (㎥; ㎥; ㎥; m3; m3; ) SQUARE M CUBED
+33A6;33A6;33A6;006B 006D 0033;006B 006D 0033; # (㎦; ㎦; ㎦; km3; km3; ) SQUARE KM CUBED
+33A7;33A7;33A7;006D 2215 0073;006D 2215 0073; # (㎧; ㎧; ㎧; m∕s; m∕s; ) SQUARE M OVER S
+33A8;33A8;33A8;006D 2215 0073 0032;006D 2215 0073 0032; # (㎨; ㎨; ㎨; m∕s2; m∕s2; ) SQUARE M OVER S SQUARED
+33A9;33A9;33A9;0050 0061;0050 0061; # (㎩; ㎩; ㎩; Pa; Pa; ) SQUARE PA
+33AA;33AA;33AA;006B 0050 0061;006B 0050 0061; # (㎪; ㎪; ㎪; kPa; kPa; ) SQUARE KPA
+33AB;33AB;33AB;004D 0050 0061;004D 0050 0061; # (㎫; ㎫; ㎫; MPa; MPa; ) SQUARE MPA
+33AC;33AC;33AC;0047 0050 0061;0047 0050 0061; # (㎬; ㎬; ㎬; GPa; GPa; ) SQUARE GPA
+33AD;33AD;33AD;0072 0061 0064;0072 0061 0064; # (㎭; ㎭; ㎭; rad; rad; ) SQUARE RAD
+33AE;33AE;33AE;0072 0061 0064 2215 0073;0072 0061 0064 2215 0073; # (㎮; ㎮; ㎮; rad∕s; rad∕s; ) SQUARE RAD OVER S
+33AF;33AF;33AF;0072 0061 0064 2215 0073 0032;0072 0061 0064 2215 0073 0032; # (㎯; ㎯; ㎯; rad∕s2; rad∕s2; ) SQUARE RAD OVER S SQUARED
+33B0;33B0;33B0;0070 0073;0070 0073; # (㎰; ㎰; ㎰; ps; ps; ) SQUARE PS
+33B1;33B1;33B1;006E 0073;006E 0073; # (㎱; ㎱; ㎱; ns; ns; ) SQUARE NS
+33B2;33B2;33B2;03BC 0073;03BC 0073; # (㎲; ㎲; ㎲; μs; μs; ) SQUARE MU S
+33B3;33B3;33B3;006D 0073;006D 0073; # (㎳; ㎳; ㎳; ms; ms; ) SQUARE MS
+33B4;33B4;33B4;0070 0056;0070 0056; # (㎴; ㎴; ㎴; pV; pV; ) SQUARE PV
+33B5;33B5;33B5;006E 0056;006E 0056; # (㎵; ㎵; ㎵; nV; nV; ) SQUARE NV
+33B6;33B6;33B6;03BC 0056;03BC 0056; # (㎶; ㎶; ㎶; μV; μV; ) SQUARE MU V
+33B7;33B7;33B7;006D 0056;006D 0056; # (㎷; ㎷; ㎷; mV; mV; ) SQUARE MV
+33B8;33B8;33B8;006B 0056;006B 0056; # (㎸; ㎸; ㎸; kV; kV; ) SQUARE KV
+33B9;33B9;33B9;004D 0056;004D 0056; # (㎹; ㎹; ㎹; MV; MV; ) SQUARE MV MEGA
+33BA;33BA;33BA;0070 0057;0070 0057; # (㎺; ㎺; ㎺; pW; pW; ) SQUARE PW
+33BB;33BB;33BB;006E 0057;006E 0057; # (㎻; ㎻; ㎻; nW; nW; ) SQUARE NW
+33BC;33BC;33BC;03BC 0057;03BC 0057; # (㎼; ㎼; ㎼; μW; μW; ) SQUARE MU W
+33BD;33BD;33BD;006D 0057;006D 0057; # (㎽; ㎽; ㎽; mW; mW; ) SQUARE MW
+33BE;33BE;33BE;006B 0057;006B 0057; # (㎾; ㎾; ㎾; kW; kW; ) SQUARE KW
+33BF;33BF;33BF;004D 0057;004D 0057; # (㎿; ㎿; ㎿; MW; MW; ) SQUARE MW MEGA
+33C0;33C0;33C0;006B 03A9;006B 03A9; # (ã€; ã€; ã€; kΩ; kΩ; ) SQUARE K OHM
+33C1;33C1;33C1;004D 03A9;004D 03A9; # (ã; ã; ã; MΩ; MΩ; ) SQUARE M OHM
+33C2;33C2;33C2;0061 002E 006D 002E;0061 002E 006D 002E; # (ã‚; ã‚; ã‚; a.m.; a.m.; ) SQUARE AM
+33C3;33C3;33C3;0042 0071;0042 0071; # (ãƒ; ãƒ; ãƒ; Bq; Bq; ) SQUARE BQ
+33C4;33C4;33C4;0063 0063;0063 0063; # (ã„; ã„; ã„; cc; cc; ) SQUARE CC
+33C5;33C5;33C5;0063 0064;0063 0064; # (ã…; ã…; ã…; cd; cd; ) SQUARE CD
+33C6;33C6;33C6;0043 2215 006B 0067;0043 2215 006B 0067; # (ã†; ã†; ã†; C∕kg; C∕kg; ) SQUARE C OVER KG
+33C7;33C7;33C7;0043 006F 002E;0043 006F 002E; # (ã‡; ã‡; ã‡; Co.; Co.; ) SQUARE CO
+33C8;33C8;33C8;0064 0042;0064 0042; # (ãˆ; ãˆ; ãˆ; dB; dB; ) SQUARE DB
+33C9;33C9;33C9;0047 0079;0047 0079; # (ã‰; ã‰; ã‰; Gy; Gy; ) SQUARE GY
+33CA;33CA;33CA;0068 0061;0068 0061; # (ãŠ; ãŠ; ãŠ; ha; ha; ) SQUARE HA
+33CB;33CB;33CB;0048 0050;0048 0050; # (ã‹; ã‹; ã‹; HP; HP; ) SQUARE HP
+33CC;33CC;33CC;0069 006E;0069 006E; # (ãŒ; ãŒ; ãŒ; in; in; ) SQUARE IN
+33CD;33CD;33CD;004B 004B;004B 004B; # (ã; ã; ã; KK; KK; ) SQUARE KK
+33CE;33CE;33CE;004B 004D;004B 004D; # (ãŽ; ãŽ; ãŽ; KM; KM; ) SQUARE KM CAPITAL
+33CF;33CF;33CF;006B 0074;006B 0074; # (ã; ã; ã; kt; kt; ) SQUARE KT
+33D0;33D0;33D0;006C 006D;006C 006D; # (ã; ã; ã; lm; lm; ) SQUARE LM
+33D1;33D1;33D1;006C 006E;006C 006E; # (ã‘; ã‘; ã‘; ln; ln; ) SQUARE LN
+33D2;33D2;33D2;006C 006F 0067;006C 006F 0067; # (ã’; ã’; ã’; log; log; ) SQUARE LOG
+33D3;33D3;33D3;006C 0078;006C 0078; # (ã“; ã“; ã“; lx; lx; ) SQUARE LX
+33D4;33D4;33D4;006D 0062;006D 0062; # (ã”; ã”; ã”; mb; mb; ) SQUARE MB SMALL
+33D5;33D5;33D5;006D 0069 006C;006D 0069 006C; # (ã•; ã•; ã•; mil; mil; ) SQUARE MIL
+33D6;33D6;33D6;006D 006F 006C;006D 006F 006C; # (ã–; ã–; ã–; mol; mol; ) SQUARE MOL
+33D7;33D7;33D7;0050 0048;0050 0048; # (ã—; ã—; ã—; PH; PH; ) SQUARE PH
+33D8;33D8;33D8;0070 002E 006D 002E;0070 002E 006D 002E; # (ã˜; ã˜; ã˜; p.m.; p.m.; ) SQUARE PM
+33D9;33D9;33D9;0050 0050 004D;0050 0050 004D; # (ã™; ã™; ã™; PPM; PPM; ) SQUARE PPM
+33DA;33DA;33DA;0050 0052;0050 0052; # (ãš; ãš; ãš; PR; PR; ) SQUARE PR
+33DB;33DB;33DB;0073 0072;0073 0072; # (ã›; ã›; ã›; sr; sr; ) SQUARE SR
+33DC;33DC;33DC;0053 0076;0053 0076; # (ãœ; ãœ; ãœ; Sv; Sv; ) SQUARE SV
+33DD;33DD;33DD;0057 0062;0057 0062; # (ã; ã; ã; Wb; Wb; ) SQUARE WB
+33DE;33DE;33DE;0056 2215 006D;0056 2215 006D; # (ãž; ãž; ãž; V∕m; V∕m; ) SQUARE V OVER M
+33DF;33DF;33DF;0041 2215 006D;0041 2215 006D; # (ãŸ; ãŸ; ãŸ; A∕m; A∕m; ) SQUARE A OVER M
+33E0;33E0;33E0;0031 65E5;0031 65E5; # (ã ; ã ; ã ; 1æ—¥; 1æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY ONE
+33E1;33E1;33E1;0032 65E5;0032 65E5; # (ã¡; ã¡; ã¡; 2æ—¥; 2æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWO
+33E2;33E2;33E2;0033 65E5;0033 65E5; # (ã¢; ã¢; ã¢; 3æ—¥; 3æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THREE
+33E3;33E3;33E3;0034 65E5;0034 65E5; # (ã£; ã£; ã£; 4æ—¥; 4æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FOUR
+33E4;33E4;33E4;0035 65E5;0035 65E5; # (ã¤; ã¤; ã¤; 5æ—¥; 5æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FIVE
+33E5;33E5;33E5;0036 65E5;0036 65E5; # (ã¥; ã¥; ã¥; 6æ—¥; 6æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SIX
+33E6;33E6;33E6;0037 65E5;0037 65E5; # (ã¦; ã¦; ã¦; 7æ—¥; 7æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SEVEN
+33E7;33E7;33E7;0038 65E5;0038 65E5; # (ã§; ã§; ã§; 8æ—¥; 8æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY EIGHT
+33E8;33E8;33E8;0039 65E5;0039 65E5; # (ã¨; ã¨; ã¨; 9æ—¥; 9æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY NINE
+33E9;33E9;33E9;0031 0030 65E5;0031 0030 65E5; # (ã©; ã©; ã©; 10æ—¥; 10æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TEN
+33EA;33EA;33EA;0031 0031 65E5;0031 0031 65E5; # (ãª; ãª; ãª; 11æ—¥; 11æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY ELEVEN
+33EB;33EB;33EB;0031 0032 65E5;0031 0032 65E5; # (ã«; ã«; ã«; 12æ—¥; 12æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWELVE
+33EC;33EC;33EC;0031 0033 65E5;0031 0033 65E5; # (ã¬; ã¬; ã¬; 13æ—¥; 13æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THIRTEEN
+33ED;33ED;33ED;0031 0034 65E5;0031 0034 65E5; # (ã­; ã­; ã­; 14æ—¥; 14æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FOURTEEN
+33EE;33EE;33EE;0031 0035 65E5;0031 0035 65E5; # (ã®; ã®; ã®; 15æ—¥; 15æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FIFTEEN
+33EF;33EF;33EF;0031 0036 65E5;0031 0036 65E5; # (ã¯; ã¯; ã¯; 16æ—¥; 16æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SIXTEEN
+33F0;33F0;33F0;0031 0037 65E5;0031 0037 65E5; # (ã°; ã°; ã°; 17æ—¥; 17æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SEVENTEEN
+33F1;33F1;33F1;0031 0038 65E5;0031 0038 65E5; # (ã±; ã±; ã±; 18æ—¥; 18æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY EIGHTEEN
+33F2;33F2;33F2;0031 0039 65E5;0031 0039 65E5; # (ã²; ã²; ã²; 19æ—¥; 19æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY NINETEEN
+33F3;33F3;33F3;0032 0030 65E5;0032 0030 65E5; # (ã³; ã³; ã³; 20æ—¥; 20æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY
+33F4;33F4;33F4;0032 0031 65E5;0032 0031 65E5; # (ã´; ã´; ã´; 21æ—¥; 21æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-ONE
+33F5;33F5;33F5;0032 0032 65E5;0032 0032 65E5; # (ãµ; ãµ; ãµ; 22æ—¥; 22æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-TWO
+33F6;33F6;33F6;0032 0033 65E5;0032 0033 65E5; # (ã¶; ã¶; ã¶; 23æ—¥; 23æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-THREE
+33F7;33F7;33F7;0032 0034 65E5;0032 0034 65E5; # (ã·; ã·; ã·; 24æ—¥; 24æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-FOUR
+33F8;33F8;33F8;0032 0035 65E5;0032 0035 65E5; # (ã¸; ã¸; ã¸; 25æ—¥; 25æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-FIVE
+33F9;33F9;33F9;0032 0036 65E5;0032 0036 65E5; # (ã¹; ã¹; ã¹; 26æ—¥; 26æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-SIX
+33FA;33FA;33FA;0032 0037 65E5;0032 0037 65E5; # (ãº; ãº; ãº; 27æ—¥; 27æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-SEVEN
+33FB;33FB;33FB;0032 0038 65E5;0032 0038 65E5; # (ã»; ã»; ã»; 28æ—¥; 28æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-EIGHT
+33FC;33FC;33FC;0032 0039 65E5;0032 0039 65E5; # (ã¼; ã¼; ã¼; 29æ—¥; 29æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-NINE
+33FD;33FD;33FD;0033 0030 65E5;0033 0030 65E5; # (ã½; ã½; ã½; 30æ—¥; 30æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THIRTY
+33FE;33FE;33FE;0033 0031 65E5;0033 0031 65E5; # (ã¾; ã¾; ã¾; 31æ—¥; 31æ—¥; ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THIRTY-ONE
+33FF;33FF;33FF;0067 0061 006C;0067 0061 006C; # (ã¿; ã¿; ã¿; gal; gal; ) SQUARE GAL
+A69C;A69C;A69C;044A;044A; # (ꚜ; ꚜ; ꚜ; ъ; ъ; ) MODIFIER LETTER CYRILLIC HARD SIGN
+A69D;A69D;A69D;044C;044C; # (êš; êš; êš; ÑŒ; ÑŒ; ) MODIFIER LETTER CYRILLIC SOFT SIGN
+A770;A770;A770;A76F;A76F; # (ê°; ê°; ê°; ê¯; ê¯; ) MODIFIER LETTER US
+A7F2;A7F2;A7F2;0043;0043; # (ꟲ; ꟲ; ꟲ; C; C; ) MODIFIER LETTER CAPITAL C
+A7F3;A7F3;A7F3;0046;0046; # (ꟳ; ꟳ; ꟳ; F; F; ) MODIFIER LETTER CAPITAL F
+A7F4;A7F4;A7F4;0051;0051; # (ꟴ; ꟴ; ꟴ; Q; Q; ) MODIFIER LETTER CAPITAL Q
+A7F8;A7F8;A7F8;0126;0126; # (ꟸ; ꟸ; ꟸ; Ħ; Ħ; ) MODIFIER LETTER CAPITAL H WITH STROKE
+A7F9;A7F9;A7F9;0153;0153; # (ꟹ; ꟹ; ꟹ; œ; œ; ) MODIFIER LETTER SMALL LIGATURE OE
+AB5C;AB5C;AB5C;A727;A727; # (ꭜ; ꭜ; ꭜ; ꜧ; ꜧ; ) MODIFIER LETTER SMALL HENG
+AB5D;AB5D;AB5D;AB37;AB37; # (ê­; ê­; ê­; ꬷ; ꬷ; ) MODIFIER LETTER SMALL L WITH INVERTED LAZY S
+AB5E;AB5E;AB5E;026B;026B; # (ê­ž; ê­ž; ê­ž; É«; É«; ) MODIFIER LETTER SMALL L WITH MIDDLE TILDE
+AB5F;AB5F;AB5F;AB52;AB52; # (ê­Ÿ; ê­Ÿ; ê­Ÿ; ê­’; ê­’; ) MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB69;AB69;AB69;028D;028D; # (ê­©; ê­©; ê­©; Ê; Ê; ) MODIFIER LETTER SMALL TURNED W
+AC00;AC00;1100 1161;AC00;1100 1161; # (가; 가; 가; 가; 가; ) HANGUL SYLLABLE GA
+AC01;AC01;1100 1161 11A8;AC01;1100 1161 11A8; # (ê°; ê°; 각; ê°; 각; ) HANGUL SYLLABLE GAG
+AC02;AC02;1100 1161 11A9;AC02;1100 1161 11A9; # (갂; 갂; 갂; 갂; 갂; ) HANGUL SYLLABLE GAGG
+AC03;AC03;1100 1161 11AA;AC03;1100 1161 11AA; # (갃; 갃; 갃; 갃; 갃; ) HANGUL SYLLABLE GAGS
+AC04;AC04;1100 1161 11AB;AC04;1100 1161 11AB; # (간; 간; 간; 간; 간; ) HANGUL SYLLABLE GAN
+AC05;AC05;1100 1161 11AC;AC05;1100 1161 11AC; # (갅; 갅; 갅; 갅; 갅; ) HANGUL SYLLABLE GANJ
+AC06;AC06;1100 1161 11AD;AC06;1100 1161 11AD; # (갆; 갆; 갆; 갆; 갆; ) HANGUL SYLLABLE GANH
+AC07;AC07;1100 1161 11AE;AC07;1100 1161 11AE; # (갇; 갇; 갇; 갇; 갇; ) HANGUL SYLLABLE GAD
+AC08;AC08;1100 1161 11AF;AC08;1100 1161 11AF; # (갈; 갈; 갈; 갈; 갈; ) HANGUL SYLLABLE GAL
+AC09;AC09;1100 1161 11B0;AC09;1100 1161 11B0; # (갉; 갉; 갉; 갉; 갉; ) HANGUL SYLLABLE GALG
+AC0A;AC0A;1100 1161 11B1;AC0A;1100 1161 11B1; # (갊; 갊; 갊; 갊; 갊; ) HANGUL SYLLABLE GALM
+AC0B;AC0B;1100 1161 11B2;AC0B;1100 1161 11B2; # (갋; 갋; 갋; 갋; 갋; ) HANGUL SYLLABLE GALB
+AC0C;AC0C;1100 1161 11B3;AC0C;1100 1161 11B3; # (갌; 갌; 갌; 갌; 갌; ) HANGUL SYLLABLE GALS
+AC0D;AC0D;1100 1161 11B4;AC0D;1100 1161 11B4; # (ê°; ê°; 갍; ê°; 갍; ) HANGUL SYLLABLE GALT
+AC0E;AC0E;1100 1161 11B5;AC0E;1100 1161 11B5; # (갎; 갎; 갎; 갎; 갎; ) HANGUL SYLLABLE GALP
+AC0F;AC0F;1100 1161 11B6;AC0F;1100 1161 11B6; # (ê°; ê°; 갏; ê°; 갏; ) HANGUL SYLLABLE GALH
+AC10;AC10;1100 1161 11B7;AC10;1100 1161 11B7; # (ê°; ê°; 감; ê°; 감; ) HANGUL SYLLABLE GAM
+AC11;AC11;1100 1161 11B8;AC11;1100 1161 11B8; # (갑; 갑; 갑; 갑; 갑; ) HANGUL SYLLABLE GAB
+AC12;AC12;1100 1161 11B9;AC12;1100 1161 11B9; # (값; 값; 값; 값; 값; ) HANGUL SYLLABLE GABS
+AC13;AC13;1100 1161 11BA;AC13;1100 1161 11BA; # (갓; 갓; 갓; 갓; 갓; ) HANGUL SYLLABLE GAS
+AC14;AC14;1100 1161 11BB;AC14;1100 1161 11BB; # (갔; 갔; 갔; 갔; 갔; ) HANGUL SYLLABLE GASS
+AC15;AC15;1100 1161 11BC;AC15;1100 1161 11BC; # (강; 강; 강; 강; 강; ) HANGUL SYLLABLE GANG
+AC16;AC16;1100 1161 11BD;AC16;1100 1161 11BD; # (갖; 갖; 갖; 갖; 갖; ) HANGUL SYLLABLE GAJ
+AC17;AC17;1100 1161 11BE;AC17;1100 1161 11BE; # (갗; 갗; 갗; 갗; 갗; ) HANGUL SYLLABLE GAC
+AC18;AC18;1100 1161 11BF;AC18;1100 1161 11BF; # (갘; 갘; 갘; 갘; 갘; ) HANGUL SYLLABLE GAK
+AC19;AC19;1100 1161 11C0;AC19;1100 1161 11C0; # (같; 같; 같; 같; 같; ) HANGUL SYLLABLE GAT
+AC1A;AC1A;1100 1161 11C1;AC1A;1100 1161 11C1; # (ê°š; ê°š; 가á‡; ê°š; 가á‡; ) HANGUL SYLLABLE GAP
+AC1B;AC1B;1100 1161 11C2;AC1B;1100 1161 11C2; # (갛; 갛; 갛; 갛; 갛; ) HANGUL SYLLABLE GAH
+AC1C;AC1C;1100 1162;AC1C;1100 1162; # (개; 개; 개; 개; 개; ) HANGUL SYLLABLE GAE
+AC1D;AC1D;1100 1162 11A8;AC1D;1100 1162 11A8; # (ê°; ê°; 객; ê°; 객; ) HANGUL SYLLABLE GAEG
+AC1E;AC1E;1100 1162 11A9;AC1E;1100 1162 11A9; # (갞; 갞; 갞; 갞; 갞; ) HANGUL SYLLABLE GAEGG
+AC1F;AC1F;1100 1162 11AA;AC1F;1100 1162 11AA; # (갟; 갟; 갟; 갟; 갟; ) HANGUL SYLLABLE GAEGS
+AC20;AC20;1100 1162 11AB;AC20;1100 1162 11AB; # (갠; 갠; 갠; 갠; 갠; ) HANGUL SYLLABLE GAEN
+AC21;AC21;1100 1162 11AC;AC21;1100 1162 11AC; # (갡; 갡; 갡; 갡; 갡; ) HANGUL SYLLABLE GAENJ
+AC22;AC22;1100 1162 11AD;AC22;1100 1162 11AD; # (갢; 갢; 갢; 갢; 갢; ) HANGUL SYLLABLE GAENH
+AC23;AC23;1100 1162 11AE;AC23;1100 1162 11AE; # (갣; 갣; 갣; 갣; 갣; ) HANGUL SYLLABLE GAED
+AC24;AC24;1100 1162 11AF;AC24;1100 1162 11AF; # (갤; 갤; 갤; 갤; 갤; ) HANGUL SYLLABLE GAEL
+AC25;AC25;1100 1162 11B0;AC25;1100 1162 11B0; # (갥; 갥; 갥; 갥; 갥; ) HANGUL SYLLABLE GAELG
+AC26;AC26;1100 1162 11B1;AC26;1100 1162 11B1; # (갦; 갦; 갦; 갦; 갦; ) HANGUL SYLLABLE GAELM
+AC27;AC27;1100 1162 11B2;AC27;1100 1162 11B2; # (갧; 갧; 갧; 갧; 갧; ) HANGUL SYLLABLE GAELB
+AC28;AC28;1100 1162 11B3;AC28;1100 1162 11B3; # (갨; 갨; 갨; 갨; 갨; ) HANGUL SYLLABLE GAELS
+AC29;AC29;1100 1162 11B4;AC29;1100 1162 11B4; # (갩; 갩; 갩; 갩; 갩; ) HANGUL SYLLABLE GAELT
+AC2A;AC2A;1100 1162 11B5;AC2A;1100 1162 11B5; # (갪; 갪; 갪; 갪; 갪; ) HANGUL SYLLABLE GAELP
+AC2B;AC2B;1100 1162 11B6;AC2B;1100 1162 11B6; # (갫; 갫; 갫; 갫; 갫; ) HANGUL SYLLABLE GAELH
+AC2C;AC2C;1100 1162 11B7;AC2C;1100 1162 11B7; # (갬; 갬; 갬; 갬; 갬; ) HANGUL SYLLABLE GAEM
+AC2D;AC2D;1100 1162 11B8;AC2D;1100 1162 11B8; # (갭; 갭; 갭; 갭; 갭; ) HANGUL SYLLABLE GAEB
+AC2E;AC2E;1100 1162 11B9;AC2E;1100 1162 11B9; # (갮; 갮; 갮; 갮; 갮; ) HANGUL SYLLABLE GAEBS
+AC2F;AC2F;1100 1162 11BA;AC2F;1100 1162 11BA; # (갯; 갯; 갯; 갯; 갯; ) HANGUL SYLLABLE GAES
+AC30;AC30;1100 1162 11BB;AC30;1100 1162 11BB; # (갰; 갰; 갰; 갰; 갰; ) HANGUL SYLLABLE GAESS
+AC31;AC31;1100 1162 11BC;AC31;1100 1162 11BC; # (갱; 갱; 갱; 갱; 갱; ) HANGUL SYLLABLE GAENG
+AC32;AC32;1100 1162 11BD;AC32;1100 1162 11BD; # (갲; 갲; 갲; 갲; 갲; ) HANGUL SYLLABLE GAEJ
+AC33;AC33;1100 1162 11BE;AC33;1100 1162 11BE; # (갳; 갳; 갳; 갳; 갳; ) HANGUL SYLLABLE GAEC
+AC34;AC34;1100 1162 11BF;AC34;1100 1162 11BF; # (갴; 갴; 갴; 갴; 갴; ) HANGUL SYLLABLE GAEK
+AC35;AC35;1100 1162 11C0;AC35;1100 1162 11C0; # (갵; 갵; 갵; 갵; 갵; ) HANGUL SYLLABLE GAET
+AC36;AC36;1100 1162 11C1;AC36;1100 1162 11C1; # (ê°¶; ê°¶; 개á‡; ê°¶; 개á‡; ) HANGUL SYLLABLE GAEP
+AC37;AC37;1100 1162 11C2;AC37;1100 1162 11C2; # (갷; 갷; 갷; 갷; 갷; ) HANGUL SYLLABLE GAEH
+AC38;AC38;1100 1163;AC38;1100 1163; # (갸; 갸; 갸; 갸; 갸; ) HANGUL SYLLABLE GYA
+AC39;AC39;1100 1163 11A8;AC39;1100 1163 11A8; # (갹; 갹; 갹; 갹; 갹; ) HANGUL SYLLABLE GYAG
+AC3A;AC3A;1100 1163 11A9;AC3A;1100 1163 11A9; # (갺; 갺; 갺; 갺; 갺; ) HANGUL SYLLABLE GYAGG
+AC3B;AC3B;1100 1163 11AA;AC3B;1100 1163 11AA; # (갻; 갻; 갻; 갻; 갻; ) HANGUL SYLLABLE GYAGS
+AC3C;AC3C;1100 1163 11AB;AC3C;1100 1163 11AB; # (갼; 갼; 갼; 갼; 갼; ) HANGUL SYLLABLE GYAN
+AC3D;AC3D;1100 1163 11AC;AC3D;1100 1163 11AC; # (갽; 갽; 갽; 갽; 갽; ) HANGUL SYLLABLE GYANJ
+AC3E;AC3E;1100 1163 11AD;AC3E;1100 1163 11AD; # (갾; 갾; 갾; 갾; 갾; ) HANGUL SYLLABLE GYANH
+AC3F;AC3F;1100 1163 11AE;AC3F;1100 1163 11AE; # (갿; 갿; 갿; 갿; 갿; ) HANGUL SYLLABLE GYAD
+AC40;AC40;1100 1163 11AF;AC40;1100 1163 11AF; # (걀; 걀; 걀; 걀; 걀; ) HANGUL SYLLABLE GYAL
+AC41;AC41;1100 1163 11B0;AC41;1100 1163 11B0; # (ê±; ê±; 걁; ê±; 걁; ) HANGUL SYLLABLE GYALG
+AC42;AC42;1100 1163 11B1;AC42;1100 1163 11B1; # (걂; 걂; 걂; 걂; 걂; ) HANGUL SYLLABLE GYALM
+AC43;AC43;1100 1163 11B2;AC43;1100 1163 11B2; # (걃; 걃; 걃; 걃; 걃; ) HANGUL SYLLABLE GYALB
+AC44;AC44;1100 1163 11B3;AC44;1100 1163 11B3; # (걄; 걄; 걄; 걄; 걄; ) HANGUL SYLLABLE GYALS
+AC45;AC45;1100 1163 11B4;AC45;1100 1163 11B4; # (걅; 걅; 걅; 걅; 걅; ) HANGUL SYLLABLE GYALT
+AC46;AC46;1100 1163 11B5;AC46;1100 1163 11B5; # (걆; 걆; 걆; 걆; 걆; ) HANGUL SYLLABLE GYALP
+AC47;AC47;1100 1163 11B6;AC47;1100 1163 11B6; # (걇; 걇; 걇; 걇; 걇; ) HANGUL SYLLABLE GYALH
+AC48;AC48;1100 1163 11B7;AC48;1100 1163 11B7; # (걈; 걈; 걈; 걈; 걈; ) HANGUL SYLLABLE GYAM
+AC49;AC49;1100 1163 11B8;AC49;1100 1163 11B8; # (걉; 걉; 걉; 걉; 걉; ) HANGUL SYLLABLE GYAB
+AC4A;AC4A;1100 1163 11B9;AC4A;1100 1163 11B9; # (걊; 걊; 걊; 걊; 걊; ) HANGUL SYLLABLE GYABS
+AC4B;AC4B;1100 1163 11BA;AC4B;1100 1163 11BA; # (걋; 걋; 걋; 걋; 걋; ) HANGUL SYLLABLE GYAS
+AC4C;AC4C;1100 1163 11BB;AC4C;1100 1163 11BB; # (걌; 걌; 걌; 걌; 걌; ) HANGUL SYLLABLE GYASS
+AC4D;AC4D;1100 1163 11BC;AC4D;1100 1163 11BC; # (ê±; ê±; 걍; ê±; 걍; ) HANGUL SYLLABLE GYANG
+AC4E;AC4E;1100 1163 11BD;AC4E;1100 1163 11BD; # (걎; 걎; 걎; 걎; 걎; ) HANGUL SYLLABLE GYAJ
+AC4F;AC4F;1100 1163 11BE;AC4F;1100 1163 11BE; # (ê±; ê±; 걏; ê±; 걏; ) HANGUL SYLLABLE GYAC
+AC50;AC50;1100 1163 11BF;AC50;1100 1163 11BF; # (ê±; ê±; 걐; ê±; 걐; ) HANGUL SYLLABLE GYAK
+AC51;AC51;1100 1163 11C0;AC51;1100 1163 11C0; # (걑; 걑; 걑; 걑; 걑; ) HANGUL SYLLABLE GYAT
+AC52;AC52;1100 1163 11C1;AC52;1100 1163 11C1; # (ê±’; ê±’; 갸á‡; ê±’; 갸á‡; ) HANGUL SYLLABLE GYAP
+AC53;AC53;1100 1163 11C2;AC53;1100 1163 11C2; # (걓; 걓; 걓; 걓; 걓; ) HANGUL SYLLABLE GYAH
+AC54;AC54;1100 1164;AC54;1100 1164; # (걔; 걔; 걔; 걔; 걔; ) HANGUL SYLLABLE GYAE
+AC55;AC55;1100 1164 11A8;AC55;1100 1164 11A8; # (걕; 걕; 걕; 걕; 걕; ) HANGUL SYLLABLE GYAEG
+AC56;AC56;1100 1164 11A9;AC56;1100 1164 11A9; # (걖; 걖; 걖; 걖; 걖; ) HANGUL SYLLABLE GYAEGG
+AC57;AC57;1100 1164 11AA;AC57;1100 1164 11AA; # (걗; 걗; 걗; 걗; 걗; ) HANGUL SYLLABLE GYAEGS
+AC58;AC58;1100 1164 11AB;AC58;1100 1164 11AB; # (걘; 걘; 걘; 걘; 걘; ) HANGUL SYLLABLE GYAEN
+AC59;AC59;1100 1164 11AC;AC59;1100 1164 11AC; # (걙; 걙; 걙; 걙; 걙; ) HANGUL SYLLABLE GYAENJ
+AC5A;AC5A;1100 1164 11AD;AC5A;1100 1164 11AD; # (걚; 걚; 걚; 걚; 걚; ) HANGUL SYLLABLE GYAENH
+AC5B;AC5B;1100 1164 11AE;AC5B;1100 1164 11AE; # (걛; 걛; 걛; 걛; 걛; ) HANGUL SYLLABLE GYAED
+AC5C;AC5C;1100 1164 11AF;AC5C;1100 1164 11AF; # (걜; 걜; 걜; 걜; 걜; ) HANGUL SYLLABLE GYAEL
+AC5D;AC5D;1100 1164 11B0;AC5D;1100 1164 11B0; # (ê±; ê±; 걝; ê±; 걝; ) HANGUL SYLLABLE GYAELG
+AC5E;AC5E;1100 1164 11B1;AC5E;1100 1164 11B1; # (걞; 걞; 걞; 걞; 걞; ) HANGUL SYLLABLE GYAELM
+AC5F;AC5F;1100 1164 11B2;AC5F;1100 1164 11B2; # (걟; 걟; 걟; 걟; 걟; ) HANGUL SYLLABLE GYAELB
+AC60;AC60;1100 1164 11B3;AC60;1100 1164 11B3; # (걠; 걠; 걠; 걠; 걠; ) HANGUL SYLLABLE GYAELS
+AC61;AC61;1100 1164 11B4;AC61;1100 1164 11B4; # (걡; 걡; 걡; 걡; 걡; ) HANGUL SYLLABLE GYAELT
+AC62;AC62;1100 1164 11B5;AC62;1100 1164 11B5; # (걢; 걢; 걢; 걢; 걢; ) HANGUL SYLLABLE GYAELP
+AC63;AC63;1100 1164 11B6;AC63;1100 1164 11B6; # (걣; 걣; 걣; 걣; 걣; ) HANGUL SYLLABLE GYAELH
+AC64;AC64;1100 1164 11B7;AC64;1100 1164 11B7; # (걤; 걤; 걤; 걤; 걤; ) HANGUL SYLLABLE GYAEM
+AC65;AC65;1100 1164 11B8;AC65;1100 1164 11B8; # (걥; 걥; 걥; 걥; 걥; ) HANGUL SYLLABLE GYAEB
+AC66;AC66;1100 1164 11B9;AC66;1100 1164 11B9; # (걦; 걦; 걦; 걦; 걦; ) HANGUL SYLLABLE GYAEBS
+AC67;AC67;1100 1164 11BA;AC67;1100 1164 11BA; # (걧; 걧; 걧; 걧; 걧; ) HANGUL SYLLABLE GYAES
+AC68;AC68;1100 1164 11BB;AC68;1100 1164 11BB; # (걨; 걨; 걨; 걨; 걨; ) HANGUL SYLLABLE GYAESS
+AC69;AC69;1100 1164 11BC;AC69;1100 1164 11BC; # (걩; 걩; 걩; 걩; 걩; ) HANGUL SYLLABLE GYAENG
+AC6A;AC6A;1100 1164 11BD;AC6A;1100 1164 11BD; # (걪; 걪; 걪; 걪; 걪; ) HANGUL SYLLABLE GYAEJ
+AC6B;AC6B;1100 1164 11BE;AC6B;1100 1164 11BE; # (걫; 걫; 걫; 걫; 걫; ) HANGUL SYLLABLE GYAEC
+AC6C;AC6C;1100 1164 11BF;AC6C;1100 1164 11BF; # (걬; 걬; 걬; 걬; 걬; ) HANGUL SYLLABLE GYAEK
+AC6D;AC6D;1100 1164 11C0;AC6D;1100 1164 11C0; # (걭; 걭; 걭; 걭; 걭; ) HANGUL SYLLABLE GYAET
+AC6E;AC6E;1100 1164 11C1;AC6E;1100 1164 11C1; # (ê±®; ê±®; 걔á‡; ê±®; 걔á‡; ) HANGUL SYLLABLE GYAEP
+AC6F;AC6F;1100 1164 11C2;AC6F;1100 1164 11C2; # (걯; 걯; 걯; 걯; 걯; ) HANGUL SYLLABLE GYAEH
+AC70;AC70;1100 1165;AC70;1100 1165; # (거; 거; 거; 거; 거; ) HANGUL SYLLABLE GEO
+AC71;AC71;1100 1165 11A8;AC71;1100 1165 11A8; # (걱; 걱; 걱; 걱; 걱; ) HANGUL SYLLABLE GEOG
+AC72;AC72;1100 1165 11A9;AC72;1100 1165 11A9; # (걲; 걲; 걲; 걲; 걲; ) HANGUL SYLLABLE GEOGG
+AC73;AC73;1100 1165 11AA;AC73;1100 1165 11AA; # (걳; 걳; 걳; 걳; 걳; ) HANGUL SYLLABLE GEOGS
+AC74;AC74;1100 1165 11AB;AC74;1100 1165 11AB; # (건; 건; 건; 건; 건; ) HANGUL SYLLABLE GEON
+AC75;AC75;1100 1165 11AC;AC75;1100 1165 11AC; # (걵; 걵; 걵; 걵; 걵; ) HANGUL SYLLABLE GEONJ
+AC76;AC76;1100 1165 11AD;AC76;1100 1165 11AD; # (걶; 걶; 걶; 걶; 걶; ) HANGUL SYLLABLE GEONH
+AC77;AC77;1100 1165 11AE;AC77;1100 1165 11AE; # (걷; 걷; 걷; 걷; 걷; ) HANGUL SYLLABLE GEOD
+AC78;AC78;1100 1165 11AF;AC78;1100 1165 11AF; # (걸; 걸; 걸; 걸; 걸; ) HANGUL SYLLABLE GEOL
+AC79;AC79;1100 1165 11B0;AC79;1100 1165 11B0; # (걹; 걹; 걹; 걹; 걹; ) HANGUL SYLLABLE GEOLG
+AC7A;AC7A;1100 1165 11B1;AC7A;1100 1165 11B1; # (걺; 걺; 걺; 걺; 걺; ) HANGUL SYLLABLE GEOLM
+AC7B;AC7B;1100 1165 11B2;AC7B;1100 1165 11B2; # (걻; 걻; 걻; 걻; 걻; ) HANGUL SYLLABLE GEOLB
+AC7C;AC7C;1100 1165 11B3;AC7C;1100 1165 11B3; # (걼; 걼; 걼; 걼; 걼; ) HANGUL SYLLABLE GEOLS
+AC7D;AC7D;1100 1165 11B4;AC7D;1100 1165 11B4; # (걽; 걽; 걽; 걽; 걽; ) HANGUL SYLLABLE GEOLT
+AC7E;AC7E;1100 1165 11B5;AC7E;1100 1165 11B5; # (걾; 걾; 걾; 걾; 걾; ) HANGUL SYLLABLE GEOLP
+AC7F;AC7F;1100 1165 11B6;AC7F;1100 1165 11B6; # (걿; 걿; 걿; 걿; 걿; ) HANGUL SYLLABLE GEOLH
+AC80;AC80;1100 1165 11B7;AC80;1100 1165 11B7; # (검; 검; 검; 검; 검; ) HANGUL SYLLABLE GEOM
+AC81;AC81;1100 1165 11B8;AC81;1100 1165 11B8; # (ê²; ê²; 겁; ê²; 겁; ) HANGUL SYLLABLE GEOB
+AC82;AC82;1100 1165 11B9;AC82;1100 1165 11B9; # (겂; 겂; 겂; 겂; 겂; ) HANGUL SYLLABLE GEOBS
+AC83;AC83;1100 1165 11BA;AC83;1100 1165 11BA; # (것; 것; 것; 것; 것; ) HANGUL SYLLABLE GEOS
+AC84;AC84;1100 1165 11BB;AC84;1100 1165 11BB; # (겄; 겄; 겄; 겄; 겄; ) HANGUL SYLLABLE GEOSS
+AC85;AC85;1100 1165 11BC;AC85;1100 1165 11BC; # (겅; 겅; 겅; 겅; 겅; ) HANGUL SYLLABLE GEONG
+AC86;AC86;1100 1165 11BD;AC86;1100 1165 11BD; # (겆; 겆; 겆; 겆; 겆; ) HANGUL SYLLABLE GEOJ
+AC87;AC87;1100 1165 11BE;AC87;1100 1165 11BE; # (겇; 겇; 겇; 겇; 겇; ) HANGUL SYLLABLE GEOC
+AC88;AC88;1100 1165 11BF;AC88;1100 1165 11BF; # (겈; 겈; 겈; 겈; 겈; ) HANGUL SYLLABLE GEOK
+AC89;AC89;1100 1165 11C0;AC89;1100 1165 11C0; # (겉; 겉; 겉; 겉; 겉; ) HANGUL SYLLABLE GEOT
+AC8A;AC8A;1100 1165 11C1;AC8A;1100 1165 11C1; # (겊; 겊; 거á‡; 겊; 거á‡; ) HANGUL SYLLABLE GEOP
+AC8B;AC8B;1100 1165 11C2;AC8B;1100 1165 11C2; # (겋; 겋; 겋; 겋; 겋; ) HANGUL SYLLABLE GEOH
+AC8C;AC8C;1100 1166;AC8C;1100 1166; # (게; 게; 게; 게; 게; ) HANGUL SYLLABLE GE
+AC8D;AC8D;1100 1166 11A8;AC8D;1100 1166 11A8; # (ê²; ê²; 겍; ê²; 겍; ) HANGUL SYLLABLE GEG
+AC8E;AC8E;1100 1166 11A9;AC8E;1100 1166 11A9; # (겎; 겎; 겎; 겎; 겎; ) HANGUL SYLLABLE GEGG
+AC8F;AC8F;1100 1166 11AA;AC8F;1100 1166 11AA; # (ê²; ê²; 겏; ê²; 겏; ) HANGUL SYLLABLE GEGS
+AC90;AC90;1100 1166 11AB;AC90;1100 1166 11AB; # (ê²; ê²; 겐; ê²; 겐; ) HANGUL SYLLABLE GEN
+AC91;AC91;1100 1166 11AC;AC91;1100 1166 11AC; # (겑; 겑; 겑; 겑; 겑; ) HANGUL SYLLABLE GENJ
+AC92;AC92;1100 1166 11AD;AC92;1100 1166 11AD; # (겒; 겒; 겒; 겒; 겒; ) HANGUL SYLLABLE GENH
+AC93;AC93;1100 1166 11AE;AC93;1100 1166 11AE; # (겓; 겓; 겓; 겓; 겓; ) HANGUL SYLLABLE GED
+AC94;AC94;1100 1166 11AF;AC94;1100 1166 11AF; # (겔; 겔; 겔; 겔; 겔; ) HANGUL SYLLABLE GEL
+AC95;AC95;1100 1166 11B0;AC95;1100 1166 11B0; # (겕; 겕; 겕; 겕; 겕; ) HANGUL SYLLABLE GELG
+AC96;AC96;1100 1166 11B1;AC96;1100 1166 11B1; # (겖; 겖; 겖; 겖; 겖; ) HANGUL SYLLABLE GELM
+AC97;AC97;1100 1166 11B2;AC97;1100 1166 11B2; # (겗; 겗; 겗; 겗; 겗; ) HANGUL SYLLABLE GELB
+AC98;AC98;1100 1166 11B3;AC98;1100 1166 11B3; # (겘; 겘; 겘; 겘; 겘; ) HANGUL SYLLABLE GELS
+AC99;AC99;1100 1166 11B4;AC99;1100 1166 11B4; # (겙; 겙; 겙; 겙; 겙; ) HANGUL SYLLABLE GELT
+AC9A;AC9A;1100 1166 11B5;AC9A;1100 1166 11B5; # (겚; 겚; 겚; 겚; 겚; ) HANGUL SYLLABLE GELP
+AC9B;AC9B;1100 1166 11B6;AC9B;1100 1166 11B6; # (겛; 겛; 겛; 겛; 겛; ) HANGUL SYLLABLE GELH
+AC9C;AC9C;1100 1166 11B7;AC9C;1100 1166 11B7; # (겜; 겜; 겜; 겜; 겜; ) HANGUL SYLLABLE GEM
+AC9D;AC9D;1100 1166 11B8;AC9D;1100 1166 11B8; # (ê²; ê²; 겝; ê²; 겝; ) HANGUL SYLLABLE GEB
+AC9E;AC9E;1100 1166 11B9;AC9E;1100 1166 11B9; # (겞; 겞; 겞; 겞; 겞; ) HANGUL SYLLABLE GEBS
+AC9F;AC9F;1100 1166 11BA;AC9F;1100 1166 11BA; # (겟; 겟; 겟; 겟; 겟; ) HANGUL SYLLABLE GES
+ACA0;ACA0;1100 1166 11BB;ACA0;1100 1166 11BB; # (겠; 겠; 겠; 겠; 겠; ) HANGUL SYLLABLE GESS
+ACA1;ACA1;1100 1166 11BC;ACA1;1100 1166 11BC; # (겡; 겡; 겡; 겡; 겡; ) HANGUL SYLLABLE GENG
+ACA2;ACA2;1100 1166 11BD;ACA2;1100 1166 11BD; # (겢; 겢; 겢; 겢; 겢; ) HANGUL SYLLABLE GEJ
+ACA3;ACA3;1100 1166 11BE;ACA3;1100 1166 11BE; # (겣; 겣; 겣; 겣; 겣; ) HANGUL SYLLABLE GEC
+ACA4;ACA4;1100 1166 11BF;ACA4;1100 1166 11BF; # (겤; 겤; 겤; 겤; 겤; ) HANGUL SYLLABLE GEK
+ACA5;ACA5;1100 1166 11C0;ACA5;1100 1166 11C0; # (겥; 겥; 겥; 겥; 겥; ) HANGUL SYLLABLE GET
+ACA6;ACA6;1100 1166 11C1;ACA6;1100 1166 11C1; # (겦; 겦; 게á‡; 겦; 게á‡; ) HANGUL SYLLABLE GEP
+ACA7;ACA7;1100 1166 11C2;ACA7;1100 1166 11C2; # (겧; 겧; 겧; 겧; 겧; ) HANGUL SYLLABLE GEH
+ACA8;ACA8;1100 1167;ACA8;1100 1167; # (겨; 겨; 겨; 겨; 겨; ) HANGUL SYLLABLE GYEO
+ACA9;ACA9;1100 1167 11A8;ACA9;1100 1167 11A8; # (격; 격; 격; 격; 격; ) HANGUL SYLLABLE GYEOG
+ACAA;ACAA;1100 1167 11A9;ACAA;1100 1167 11A9; # (겪; 겪; 겪; 겪; 겪; ) HANGUL SYLLABLE GYEOGG
+ACAB;ACAB;1100 1167 11AA;ACAB;1100 1167 11AA; # (겫; 겫; 겫; 겫; 겫; ) HANGUL SYLLABLE GYEOGS
+ACAC;ACAC;1100 1167 11AB;ACAC;1100 1167 11AB; # (견; 견; 견; 견; 견; ) HANGUL SYLLABLE GYEON
+ACAD;ACAD;1100 1167 11AC;ACAD;1100 1167 11AC; # (겭; 겭; 겭; 겭; 겭; ) HANGUL SYLLABLE GYEONJ
+ACAE;ACAE;1100 1167 11AD;ACAE;1100 1167 11AD; # (겮; 겮; 겮; 겮; 겮; ) HANGUL SYLLABLE GYEONH
+ACAF;ACAF;1100 1167 11AE;ACAF;1100 1167 11AE; # (겯; 겯; 겯; 겯; 겯; ) HANGUL SYLLABLE GYEOD
+ACB0;ACB0;1100 1167 11AF;ACB0;1100 1167 11AF; # (결; 결; 결; 결; 결; ) HANGUL SYLLABLE GYEOL
+ACB1;ACB1;1100 1167 11B0;ACB1;1100 1167 11B0; # (겱; 겱; 겱; 겱; 겱; ) HANGUL SYLLABLE GYEOLG
+ACB2;ACB2;1100 1167 11B1;ACB2;1100 1167 11B1; # (겲; 겲; 겲; 겲; 겲; ) HANGUL SYLLABLE GYEOLM
+ACB3;ACB3;1100 1167 11B2;ACB3;1100 1167 11B2; # (겳; 겳; 겳; 겳; 겳; ) HANGUL SYLLABLE GYEOLB
+ACB4;ACB4;1100 1167 11B3;ACB4;1100 1167 11B3; # (겴; 겴; 겴; 겴; 겴; ) HANGUL SYLLABLE GYEOLS
+ACB5;ACB5;1100 1167 11B4;ACB5;1100 1167 11B4; # (겵; 겵; 겵; 겵; 겵; ) HANGUL SYLLABLE GYEOLT
+ACB6;ACB6;1100 1167 11B5;ACB6;1100 1167 11B5; # (겶; 겶; 겶; 겶; 겶; ) HANGUL SYLLABLE GYEOLP
+ACB7;ACB7;1100 1167 11B6;ACB7;1100 1167 11B6; # (겷; 겷; 겷; 겷; 겷; ) HANGUL SYLLABLE GYEOLH
+ACB8;ACB8;1100 1167 11B7;ACB8;1100 1167 11B7; # (겸; 겸; 겸; 겸; 겸; ) HANGUL SYLLABLE GYEOM
+ACB9;ACB9;1100 1167 11B8;ACB9;1100 1167 11B8; # (겹; 겹; 겹; 겹; 겹; ) HANGUL SYLLABLE GYEOB
+ACBA;ACBA;1100 1167 11B9;ACBA;1100 1167 11B9; # (겺; 겺; 겺; 겺; 겺; ) HANGUL SYLLABLE GYEOBS
+ACBB;ACBB;1100 1167 11BA;ACBB;1100 1167 11BA; # (겻; 겻; 겻; 겻; 겻; ) HANGUL SYLLABLE GYEOS
+ACBC;ACBC;1100 1167 11BB;ACBC;1100 1167 11BB; # (겼; 겼; 겼; 겼; 겼; ) HANGUL SYLLABLE GYEOSS
+ACBD;ACBD;1100 1167 11BC;ACBD;1100 1167 11BC; # (경; 경; 경; 경; 경; ) HANGUL SYLLABLE GYEONG
+ACBE;ACBE;1100 1167 11BD;ACBE;1100 1167 11BD; # (겾; 겾; 겾; 겾; 겾; ) HANGUL SYLLABLE GYEOJ
+ACBF;ACBF;1100 1167 11BE;ACBF;1100 1167 11BE; # (겿; 겿; 겿; 겿; 겿; ) HANGUL SYLLABLE GYEOC
+ACC0;ACC0;1100 1167 11BF;ACC0;1100 1167 11BF; # (곀; 곀; 곀; 곀; 곀; ) HANGUL SYLLABLE GYEOK
+ACC1;ACC1;1100 1167 11C0;ACC1;1100 1167 11C0; # (ê³; ê³; 곁; ê³; 곁; ) HANGUL SYLLABLE GYEOT
+ACC2;ACC2;1100 1167 11C1;ACC2;1100 1167 11C1; # (곂; 곂; 겨á‡; 곂; 겨á‡; ) HANGUL SYLLABLE GYEOP
+ACC3;ACC3;1100 1167 11C2;ACC3;1100 1167 11C2; # (곃; 곃; 곃; 곃; 곃; ) HANGUL SYLLABLE GYEOH
+ACC4;ACC4;1100 1168;ACC4;1100 1168; # (계; 계; 계; 계; 계; ) HANGUL SYLLABLE GYE
+ACC5;ACC5;1100 1168 11A8;ACC5;1100 1168 11A8; # (곅; 곅; 곅; 곅; 곅; ) HANGUL SYLLABLE GYEG
+ACC6;ACC6;1100 1168 11A9;ACC6;1100 1168 11A9; # (곆; 곆; 곆; 곆; 곆; ) HANGUL SYLLABLE GYEGG
+ACC7;ACC7;1100 1168 11AA;ACC7;1100 1168 11AA; # (곇; 곇; 곇; 곇; 곇; ) HANGUL SYLLABLE GYEGS
+ACC8;ACC8;1100 1168 11AB;ACC8;1100 1168 11AB; # (곈; 곈; 곈; 곈; 곈; ) HANGUL SYLLABLE GYEN
+ACC9;ACC9;1100 1168 11AC;ACC9;1100 1168 11AC; # (곉; 곉; 곉; 곉; 곉; ) HANGUL SYLLABLE GYENJ
+ACCA;ACCA;1100 1168 11AD;ACCA;1100 1168 11AD; # (곊; 곊; 곊; 곊; 곊; ) HANGUL SYLLABLE GYENH
+ACCB;ACCB;1100 1168 11AE;ACCB;1100 1168 11AE; # (곋; 곋; 곋; 곋; 곋; ) HANGUL SYLLABLE GYED
+ACCC;ACCC;1100 1168 11AF;ACCC;1100 1168 11AF; # (곌; 곌; 곌; 곌; 곌; ) HANGUL SYLLABLE GYEL
+ACCD;ACCD;1100 1168 11B0;ACCD;1100 1168 11B0; # (ê³; ê³; 곍; ê³; 곍; ) HANGUL SYLLABLE GYELG
+ACCE;ACCE;1100 1168 11B1;ACCE;1100 1168 11B1; # (곎; 곎; 곎; 곎; 곎; ) HANGUL SYLLABLE GYELM
+ACCF;ACCF;1100 1168 11B2;ACCF;1100 1168 11B2; # (ê³; ê³; 곏; ê³; 곏; ) HANGUL SYLLABLE GYELB
+ACD0;ACD0;1100 1168 11B3;ACD0;1100 1168 11B3; # (ê³; ê³; 곐; ê³; 곐; ) HANGUL SYLLABLE GYELS
+ACD1;ACD1;1100 1168 11B4;ACD1;1100 1168 11B4; # (곑; 곑; 곑; 곑; 곑; ) HANGUL SYLLABLE GYELT
+ACD2;ACD2;1100 1168 11B5;ACD2;1100 1168 11B5; # (곒; 곒; 곒; 곒; 곒; ) HANGUL SYLLABLE GYELP
+ACD3;ACD3;1100 1168 11B6;ACD3;1100 1168 11B6; # (곓; 곓; 곓; 곓; 곓; ) HANGUL SYLLABLE GYELH
+ACD4;ACD4;1100 1168 11B7;ACD4;1100 1168 11B7; # (곔; 곔; 곔; 곔; 곔; ) HANGUL SYLLABLE GYEM
+ACD5;ACD5;1100 1168 11B8;ACD5;1100 1168 11B8; # (곕; 곕; 곕; 곕; 곕; ) HANGUL SYLLABLE GYEB
+ACD6;ACD6;1100 1168 11B9;ACD6;1100 1168 11B9; # (곖; 곖; 곖; 곖; 곖; ) HANGUL SYLLABLE GYEBS
+ACD7;ACD7;1100 1168 11BA;ACD7;1100 1168 11BA; # (곗; 곗; 곗; 곗; 곗; ) HANGUL SYLLABLE GYES
+ACD8;ACD8;1100 1168 11BB;ACD8;1100 1168 11BB; # (곘; 곘; 곘; 곘; 곘; ) HANGUL SYLLABLE GYESS
+ACD9;ACD9;1100 1168 11BC;ACD9;1100 1168 11BC; # (곙; 곙; 곙; 곙; 곙; ) HANGUL SYLLABLE GYENG
+ACDA;ACDA;1100 1168 11BD;ACDA;1100 1168 11BD; # (곚; 곚; 곚; 곚; 곚; ) HANGUL SYLLABLE GYEJ
+ACDB;ACDB;1100 1168 11BE;ACDB;1100 1168 11BE; # (곛; 곛; 곛; 곛; 곛; ) HANGUL SYLLABLE GYEC
+ACDC;ACDC;1100 1168 11BF;ACDC;1100 1168 11BF; # (곜; 곜; 곜; 곜; 곜; ) HANGUL SYLLABLE GYEK
+ACDD;ACDD;1100 1168 11C0;ACDD;1100 1168 11C0; # (ê³; ê³; 곝; ê³; 곝; ) HANGUL SYLLABLE GYET
+ACDE;ACDE;1100 1168 11C1;ACDE;1100 1168 11C1; # (곞; 곞; 계á‡; 곞; 계á‡; ) HANGUL SYLLABLE GYEP
+ACDF;ACDF;1100 1168 11C2;ACDF;1100 1168 11C2; # (곟; 곟; 곟; 곟; 곟; ) HANGUL SYLLABLE GYEH
+ACE0;ACE0;1100 1169;ACE0;1100 1169; # (고; 고; 고; 고; 고; ) HANGUL SYLLABLE GO
+ACE1;ACE1;1100 1169 11A8;ACE1;1100 1169 11A8; # (곡; 곡; 곡; 곡; 곡; ) HANGUL SYLLABLE GOG
+ACE2;ACE2;1100 1169 11A9;ACE2;1100 1169 11A9; # (곢; 곢; 곢; 곢; 곢; ) HANGUL SYLLABLE GOGG
+ACE3;ACE3;1100 1169 11AA;ACE3;1100 1169 11AA; # (곣; 곣; 곣; 곣; 곣; ) HANGUL SYLLABLE GOGS
+ACE4;ACE4;1100 1169 11AB;ACE4;1100 1169 11AB; # (곤; 곤; 곤; 곤; 곤; ) HANGUL SYLLABLE GON
+ACE5;ACE5;1100 1169 11AC;ACE5;1100 1169 11AC; # (곥; 곥; 곥; 곥; 곥; ) HANGUL SYLLABLE GONJ
+ACE6;ACE6;1100 1169 11AD;ACE6;1100 1169 11AD; # (곦; 곦; 곦; 곦; 곦; ) HANGUL SYLLABLE GONH
+ACE7;ACE7;1100 1169 11AE;ACE7;1100 1169 11AE; # (곧; 곧; 곧; 곧; 곧; ) HANGUL SYLLABLE GOD
+ACE8;ACE8;1100 1169 11AF;ACE8;1100 1169 11AF; # (골; 골; 골; 골; 골; ) HANGUL SYLLABLE GOL
+ACE9;ACE9;1100 1169 11B0;ACE9;1100 1169 11B0; # (곩; 곩; 곩; 곩; 곩; ) HANGUL SYLLABLE GOLG
+ACEA;ACEA;1100 1169 11B1;ACEA;1100 1169 11B1; # (곪; 곪; 곪; 곪; 곪; ) HANGUL SYLLABLE GOLM
+ACEB;ACEB;1100 1169 11B2;ACEB;1100 1169 11B2; # (곫; 곫; 곫; 곫; 곫; ) HANGUL SYLLABLE GOLB
+ACEC;ACEC;1100 1169 11B3;ACEC;1100 1169 11B3; # (곬; 곬; 곬; 곬; 곬; ) HANGUL SYLLABLE GOLS
+ACED;ACED;1100 1169 11B4;ACED;1100 1169 11B4; # (곭; 곭; 곭; 곭; 곭; ) HANGUL SYLLABLE GOLT
+ACEE;ACEE;1100 1169 11B5;ACEE;1100 1169 11B5; # (곮; 곮; 곮; 곮; 곮; ) HANGUL SYLLABLE GOLP
+ACEF;ACEF;1100 1169 11B6;ACEF;1100 1169 11B6; # (곯; 곯; 곯; 곯; 곯; ) HANGUL SYLLABLE GOLH
+ACF0;ACF0;1100 1169 11B7;ACF0;1100 1169 11B7; # (곰; 곰; 곰; 곰; 곰; ) HANGUL SYLLABLE GOM
+ACF1;ACF1;1100 1169 11B8;ACF1;1100 1169 11B8; # (곱; 곱; 곱; 곱; 곱; ) HANGUL SYLLABLE GOB
+ACF2;ACF2;1100 1169 11B9;ACF2;1100 1169 11B9; # (곲; 곲; 곲; 곲; 곲; ) HANGUL SYLLABLE GOBS
+ACF3;ACF3;1100 1169 11BA;ACF3;1100 1169 11BA; # (곳; 곳; 곳; 곳; 곳; ) HANGUL SYLLABLE GOS
+ACF4;ACF4;1100 1169 11BB;ACF4;1100 1169 11BB; # (곴; 곴; 곴; 곴; 곴; ) HANGUL SYLLABLE GOSS
+ACF5;ACF5;1100 1169 11BC;ACF5;1100 1169 11BC; # (공; 공; 공; 공; 공; ) HANGUL SYLLABLE GONG
+ACF6;ACF6;1100 1169 11BD;ACF6;1100 1169 11BD; # (곶; 곶; 곶; 곶; 곶; ) HANGUL SYLLABLE GOJ
+ACF7;ACF7;1100 1169 11BE;ACF7;1100 1169 11BE; # (곷; 곷; 곷; 곷; 곷; ) HANGUL SYLLABLE GOC
+ACF8;ACF8;1100 1169 11BF;ACF8;1100 1169 11BF; # (곸; 곸; 곸; 곸; 곸; ) HANGUL SYLLABLE GOK
+ACF9;ACF9;1100 1169 11C0;ACF9;1100 1169 11C0; # (곹; 곹; 곹; 곹; 곹; ) HANGUL SYLLABLE GOT
+ACFA;ACFA;1100 1169 11C1;ACFA;1100 1169 11C1; # (곺; 곺; 고á‡; 곺; 고á‡; ) HANGUL SYLLABLE GOP
+ACFB;ACFB;1100 1169 11C2;ACFB;1100 1169 11C2; # (곻; 곻; 곻; 곻; 곻; ) HANGUL SYLLABLE GOH
+ACFC;ACFC;1100 116A;ACFC;1100 116A; # (과; 과; 과; 과; 과; ) HANGUL SYLLABLE GWA
+ACFD;ACFD;1100 116A 11A8;ACFD;1100 116A 11A8; # (곽; 곽; 곽; 곽; 곽; ) HANGUL SYLLABLE GWAG
+ACFE;ACFE;1100 116A 11A9;ACFE;1100 116A 11A9; # (곾; 곾; 곾; 곾; 곾; ) HANGUL SYLLABLE GWAGG
+ACFF;ACFF;1100 116A 11AA;ACFF;1100 116A 11AA; # (곿; 곿; 곿; 곿; 곿; ) HANGUL SYLLABLE GWAGS
+AD00;AD00;1100 116A 11AB;AD00;1100 116A 11AB; # (관; 관; 관; 관; 관; ) HANGUL SYLLABLE GWAN
+AD01;AD01;1100 116A 11AC;AD01;1100 116A 11AC; # (ê´; ê´; 괁; ê´; 괁; ) HANGUL SYLLABLE GWANJ
+AD02;AD02;1100 116A 11AD;AD02;1100 116A 11AD; # (괂; 괂; 괂; 괂; 괂; ) HANGUL SYLLABLE GWANH
+AD03;AD03;1100 116A 11AE;AD03;1100 116A 11AE; # (괃; 괃; 괃; 괃; 괃; ) HANGUL SYLLABLE GWAD
+AD04;AD04;1100 116A 11AF;AD04;1100 116A 11AF; # (괄; 괄; 괄; 괄; 괄; ) HANGUL SYLLABLE GWAL
+AD05;AD05;1100 116A 11B0;AD05;1100 116A 11B0; # (괅; 괅; 괅; 괅; 괅; ) HANGUL SYLLABLE GWALG
+AD06;AD06;1100 116A 11B1;AD06;1100 116A 11B1; # (괆; 괆; 괆; 괆; 괆; ) HANGUL SYLLABLE GWALM
+AD07;AD07;1100 116A 11B2;AD07;1100 116A 11B2; # (괇; 괇; 괇; 괇; 괇; ) HANGUL SYLLABLE GWALB
+AD08;AD08;1100 116A 11B3;AD08;1100 116A 11B3; # (괈; 괈; 괈; 괈; 괈; ) HANGUL SYLLABLE GWALS
+AD09;AD09;1100 116A 11B4;AD09;1100 116A 11B4; # (괉; 괉; 괉; 괉; 괉; ) HANGUL SYLLABLE GWALT
+AD0A;AD0A;1100 116A 11B5;AD0A;1100 116A 11B5; # (괊; 괊; 괊; 괊; 괊; ) HANGUL SYLLABLE GWALP
+AD0B;AD0B;1100 116A 11B6;AD0B;1100 116A 11B6; # (괋; 괋; 괋; 괋; 괋; ) HANGUL SYLLABLE GWALH
+AD0C;AD0C;1100 116A 11B7;AD0C;1100 116A 11B7; # (괌; 괌; 괌; 괌; 괌; ) HANGUL SYLLABLE GWAM
+AD0D;AD0D;1100 116A 11B8;AD0D;1100 116A 11B8; # (ê´; ê´; 괍; ê´; 괍; ) HANGUL SYLLABLE GWAB
+AD0E;AD0E;1100 116A 11B9;AD0E;1100 116A 11B9; # (괎; 괎; 괎; 괎; 괎; ) HANGUL SYLLABLE GWABS
+AD0F;AD0F;1100 116A 11BA;AD0F;1100 116A 11BA; # (ê´; ê´; 괏; ê´; 괏; ) HANGUL SYLLABLE GWAS
+AD10;AD10;1100 116A 11BB;AD10;1100 116A 11BB; # (ê´; ê´; 괐; ê´; 괐; ) HANGUL SYLLABLE GWASS
+AD11;AD11;1100 116A 11BC;AD11;1100 116A 11BC; # (광; 광; 광; 광; 광; ) HANGUL SYLLABLE GWANG
+AD12;AD12;1100 116A 11BD;AD12;1100 116A 11BD; # (괒; 괒; 괒; 괒; 괒; ) HANGUL SYLLABLE GWAJ
+AD13;AD13;1100 116A 11BE;AD13;1100 116A 11BE; # (괓; 괓; 괓; 괓; 괓; ) HANGUL SYLLABLE GWAC
+AD14;AD14;1100 116A 11BF;AD14;1100 116A 11BF; # (괔; 괔; 괔; 괔; 괔; ) HANGUL SYLLABLE GWAK
+AD15;AD15;1100 116A 11C0;AD15;1100 116A 11C0; # (괕; 괕; 괕; 괕; 괕; ) HANGUL SYLLABLE GWAT
+AD16;AD16;1100 116A 11C1;AD16;1100 116A 11C1; # (ê´–; ê´–; 과á‡; ê´–; 과á‡; ) HANGUL SYLLABLE GWAP
+AD17;AD17;1100 116A 11C2;AD17;1100 116A 11C2; # (괗; 괗; 괗; 괗; 괗; ) HANGUL SYLLABLE GWAH
+AD18;AD18;1100 116B;AD18;1100 116B; # (괘; 괘; 괘; 괘; 괘; ) HANGUL SYLLABLE GWAE
+AD19;AD19;1100 116B 11A8;AD19;1100 116B 11A8; # (괙; 괙; 괙; 괙; 괙; ) HANGUL SYLLABLE GWAEG
+AD1A;AD1A;1100 116B 11A9;AD1A;1100 116B 11A9; # (괚; 괚; 괚; 괚; 괚; ) HANGUL SYLLABLE GWAEGG
+AD1B;AD1B;1100 116B 11AA;AD1B;1100 116B 11AA; # (괛; 괛; 괛; 괛; 괛; ) HANGUL SYLLABLE GWAEGS
+AD1C;AD1C;1100 116B 11AB;AD1C;1100 116B 11AB; # (괜; 괜; 괜; 괜; 괜; ) HANGUL SYLLABLE GWAEN
+AD1D;AD1D;1100 116B 11AC;AD1D;1100 116B 11AC; # (ê´; ê´; 괝; ê´; 괝; ) HANGUL SYLLABLE GWAENJ
+AD1E;AD1E;1100 116B 11AD;AD1E;1100 116B 11AD; # (괞; 괞; 괞; 괞; 괞; ) HANGUL SYLLABLE GWAENH
+AD1F;AD1F;1100 116B 11AE;AD1F;1100 116B 11AE; # (괟; 괟; 괟; 괟; 괟; ) HANGUL SYLLABLE GWAED
+AD20;AD20;1100 116B 11AF;AD20;1100 116B 11AF; # (괠; 괠; 괠; 괠; 괠; ) HANGUL SYLLABLE GWAEL
+AD21;AD21;1100 116B 11B0;AD21;1100 116B 11B0; # (괡; 괡; 괡; 괡; 괡; ) HANGUL SYLLABLE GWAELG
+AD22;AD22;1100 116B 11B1;AD22;1100 116B 11B1; # (괢; 괢; 괢; 괢; 괢; ) HANGUL SYLLABLE GWAELM
+AD23;AD23;1100 116B 11B2;AD23;1100 116B 11B2; # (괣; 괣; 괣; 괣; 괣; ) HANGUL SYLLABLE GWAELB
+AD24;AD24;1100 116B 11B3;AD24;1100 116B 11B3; # (괤; 괤; 괤; 괤; 괤; ) HANGUL SYLLABLE GWAELS
+AD25;AD25;1100 116B 11B4;AD25;1100 116B 11B4; # (괥; 괥; 괥; 괥; 괥; ) HANGUL SYLLABLE GWAELT
+AD26;AD26;1100 116B 11B5;AD26;1100 116B 11B5; # (괦; 괦; 괦; 괦; 괦; ) HANGUL SYLLABLE GWAELP
+AD27;AD27;1100 116B 11B6;AD27;1100 116B 11B6; # (괧; 괧; 괧; 괧; 괧; ) HANGUL SYLLABLE GWAELH
+AD28;AD28;1100 116B 11B7;AD28;1100 116B 11B7; # (괨; 괨; 괨; 괨; 괨; ) HANGUL SYLLABLE GWAEM
+AD29;AD29;1100 116B 11B8;AD29;1100 116B 11B8; # (괩; 괩; 괩; 괩; 괩; ) HANGUL SYLLABLE GWAEB
+AD2A;AD2A;1100 116B 11B9;AD2A;1100 116B 11B9; # (괪; 괪; 괪; 괪; 괪; ) HANGUL SYLLABLE GWAEBS
+AD2B;AD2B;1100 116B 11BA;AD2B;1100 116B 11BA; # (괫; 괫; 괫; 괫; 괫; ) HANGUL SYLLABLE GWAES
+AD2C;AD2C;1100 116B 11BB;AD2C;1100 116B 11BB; # (괬; 괬; 괬; 괬; 괬; ) HANGUL SYLLABLE GWAESS
+AD2D;AD2D;1100 116B 11BC;AD2D;1100 116B 11BC; # (괭; 괭; 괭; 괭; 괭; ) HANGUL SYLLABLE GWAENG
+AD2E;AD2E;1100 116B 11BD;AD2E;1100 116B 11BD; # (괮; 괮; 괮; 괮; 괮; ) HANGUL SYLLABLE GWAEJ
+AD2F;AD2F;1100 116B 11BE;AD2F;1100 116B 11BE; # (괯; 괯; 괯; 괯; 괯; ) HANGUL SYLLABLE GWAEC
+AD30;AD30;1100 116B 11BF;AD30;1100 116B 11BF; # (괰; 괰; 괰; 괰; 괰; ) HANGUL SYLLABLE GWAEK
+AD31;AD31;1100 116B 11C0;AD31;1100 116B 11C0; # (괱; 괱; 괱; 괱; 괱; ) HANGUL SYLLABLE GWAET
+AD32;AD32;1100 116B 11C1;AD32;1100 116B 11C1; # (ê´²; ê´²; 괘á‡; ê´²; 괘á‡; ) HANGUL SYLLABLE GWAEP
+AD33;AD33;1100 116B 11C2;AD33;1100 116B 11C2; # (괳; 괳; 괳; 괳; 괳; ) HANGUL SYLLABLE GWAEH
+AD34;AD34;1100 116C;AD34;1100 116C; # (괴; 괴; 괴; 괴; 괴; ) HANGUL SYLLABLE GOE
+AD35;AD35;1100 116C 11A8;AD35;1100 116C 11A8; # (괵; 괵; 괵; 괵; 괵; ) HANGUL SYLLABLE GOEG
+AD36;AD36;1100 116C 11A9;AD36;1100 116C 11A9; # (괶; 괶; 괶; 괶; 괶; ) HANGUL SYLLABLE GOEGG
+AD37;AD37;1100 116C 11AA;AD37;1100 116C 11AA; # (괷; 괷; 괷; 괷; 괷; ) HANGUL SYLLABLE GOEGS
+AD38;AD38;1100 116C 11AB;AD38;1100 116C 11AB; # (괸; 괸; 괸; 괸; 괸; ) HANGUL SYLLABLE GOEN
+AD39;AD39;1100 116C 11AC;AD39;1100 116C 11AC; # (괹; 괹; 괹; 괹; 괹; ) HANGUL SYLLABLE GOENJ
+AD3A;AD3A;1100 116C 11AD;AD3A;1100 116C 11AD; # (괺; 괺; 괺; 괺; 괺; ) HANGUL SYLLABLE GOENH
+AD3B;AD3B;1100 116C 11AE;AD3B;1100 116C 11AE; # (괻; 괻; 괻; 괻; 괻; ) HANGUL SYLLABLE GOED
+AD3C;AD3C;1100 116C 11AF;AD3C;1100 116C 11AF; # (괼; 괼; 괼; 괼; 괼; ) HANGUL SYLLABLE GOEL
+AD3D;AD3D;1100 116C 11B0;AD3D;1100 116C 11B0; # (괽; 괽; 괽; 괽; 괽; ) HANGUL SYLLABLE GOELG
+AD3E;AD3E;1100 116C 11B1;AD3E;1100 116C 11B1; # (괾; 괾; 괾; 괾; 괾; ) HANGUL SYLLABLE GOELM
+AD3F;AD3F;1100 116C 11B2;AD3F;1100 116C 11B2; # (괿; 괿; 괿; 괿; 괿; ) HANGUL SYLLABLE GOELB
+AD40;AD40;1100 116C 11B3;AD40;1100 116C 11B3; # (굀; 굀; 굀; 굀; 굀; ) HANGUL SYLLABLE GOELS
+AD41;AD41;1100 116C 11B4;AD41;1100 116C 11B4; # (êµ; êµ; 굁; êµ; 굁; ) HANGUL SYLLABLE GOELT
+AD42;AD42;1100 116C 11B5;AD42;1100 116C 11B5; # (굂; 굂; 굂; 굂; 굂; ) HANGUL SYLLABLE GOELP
+AD43;AD43;1100 116C 11B6;AD43;1100 116C 11B6; # (굃; 굃; 굃; 굃; 굃; ) HANGUL SYLLABLE GOELH
+AD44;AD44;1100 116C 11B7;AD44;1100 116C 11B7; # (굄; 굄; 굄; 굄; 굄; ) HANGUL SYLLABLE GOEM
+AD45;AD45;1100 116C 11B8;AD45;1100 116C 11B8; # (굅; 굅; 굅; 굅; 굅; ) HANGUL SYLLABLE GOEB
+AD46;AD46;1100 116C 11B9;AD46;1100 116C 11B9; # (굆; 굆; 굆; 굆; 굆; ) HANGUL SYLLABLE GOEBS
+AD47;AD47;1100 116C 11BA;AD47;1100 116C 11BA; # (굇; 굇; 굇; 굇; 굇; ) HANGUL SYLLABLE GOES
+AD48;AD48;1100 116C 11BB;AD48;1100 116C 11BB; # (굈; 굈; 굈; 굈; 굈; ) HANGUL SYLLABLE GOESS
+AD49;AD49;1100 116C 11BC;AD49;1100 116C 11BC; # (굉; 굉; 굉; 굉; 굉; ) HANGUL SYLLABLE GOENG
+AD4A;AD4A;1100 116C 11BD;AD4A;1100 116C 11BD; # (굊; 굊; 굊; 굊; 굊; ) HANGUL SYLLABLE GOEJ
+AD4B;AD4B;1100 116C 11BE;AD4B;1100 116C 11BE; # (굋; 굋; 굋; 굋; 굋; ) HANGUL SYLLABLE GOEC
+AD4C;AD4C;1100 116C 11BF;AD4C;1100 116C 11BF; # (굌; 굌; 굌; 굌; 굌; ) HANGUL SYLLABLE GOEK
+AD4D;AD4D;1100 116C 11C0;AD4D;1100 116C 11C0; # (êµ; êµ; 굍; êµ; 굍; ) HANGUL SYLLABLE GOET
+AD4E;AD4E;1100 116C 11C1;AD4E;1100 116C 11C1; # (굎; 굎; 괴á‡; 굎; 괴á‡; ) HANGUL SYLLABLE GOEP
+AD4F;AD4F;1100 116C 11C2;AD4F;1100 116C 11C2; # (êµ; êµ; 굏; êµ; 굏; ) HANGUL SYLLABLE GOEH
+AD50;AD50;1100 116D;AD50;1100 116D; # (êµ; êµ; 교; êµ; 교; ) HANGUL SYLLABLE GYO
+AD51;AD51;1100 116D 11A8;AD51;1100 116D 11A8; # (굑; 굑; 굑; 굑; 굑; ) HANGUL SYLLABLE GYOG
+AD52;AD52;1100 116D 11A9;AD52;1100 116D 11A9; # (굒; 굒; 굒; 굒; 굒; ) HANGUL SYLLABLE GYOGG
+AD53;AD53;1100 116D 11AA;AD53;1100 116D 11AA; # (굓; 굓; 굓; 굓; 굓; ) HANGUL SYLLABLE GYOGS
+AD54;AD54;1100 116D 11AB;AD54;1100 116D 11AB; # (굔; 굔; 굔; 굔; 굔; ) HANGUL SYLLABLE GYON
+AD55;AD55;1100 116D 11AC;AD55;1100 116D 11AC; # (굕; 굕; 굕; 굕; 굕; ) HANGUL SYLLABLE GYONJ
+AD56;AD56;1100 116D 11AD;AD56;1100 116D 11AD; # (굖; 굖; 굖; 굖; 굖; ) HANGUL SYLLABLE GYONH
+AD57;AD57;1100 116D 11AE;AD57;1100 116D 11AE; # (굗; 굗; 굗; 굗; 굗; ) HANGUL SYLLABLE GYOD
+AD58;AD58;1100 116D 11AF;AD58;1100 116D 11AF; # (굘; 굘; 굘; 굘; 굘; ) HANGUL SYLLABLE GYOL
+AD59;AD59;1100 116D 11B0;AD59;1100 116D 11B0; # (굙; 굙; 굙; 굙; 굙; ) HANGUL SYLLABLE GYOLG
+AD5A;AD5A;1100 116D 11B1;AD5A;1100 116D 11B1; # (굚; 굚; 굚; 굚; 굚; ) HANGUL SYLLABLE GYOLM
+AD5B;AD5B;1100 116D 11B2;AD5B;1100 116D 11B2; # (굛; 굛; 굛; 굛; 굛; ) HANGUL SYLLABLE GYOLB
+AD5C;AD5C;1100 116D 11B3;AD5C;1100 116D 11B3; # (굜; 굜; 굜; 굜; 굜; ) HANGUL SYLLABLE GYOLS
+AD5D;AD5D;1100 116D 11B4;AD5D;1100 116D 11B4; # (êµ; êµ; 굝; êµ; 굝; ) HANGUL SYLLABLE GYOLT
+AD5E;AD5E;1100 116D 11B5;AD5E;1100 116D 11B5; # (굞; 굞; 굞; 굞; 굞; ) HANGUL SYLLABLE GYOLP
+AD5F;AD5F;1100 116D 11B6;AD5F;1100 116D 11B6; # (굟; 굟; 굟; 굟; 굟; ) HANGUL SYLLABLE GYOLH
+AD60;AD60;1100 116D 11B7;AD60;1100 116D 11B7; # (굠; 굠; 굠; 굠; 굠; ) HANGUL SYLLABLE GYOM
+AD61;AD61;1100 116D 11B8;AD61;1100 116D 11B8; # (굡; 굡; 굡; 굡; 굡; ) HANGUL SYLLABLE GYOB
+AD62;AD62;1100 116D 11B9;AD62;1100 116D 11B9; # (굢; 굢; 굢; 굢; 굢; ) HANGUL SYLLABLE GYOBS
+AD63;AD63;1100 116D 11BA;AD63;1100 116D 11BA; # (굣; 굣; 굣; 굣; 굣; ) HANGUL SYLLABLE GYOS
+AD64;AD64;1100 116D 11BB;AD64;1100 116D 11BB; # (굤; 굤; 굤; 굤; 굤; ) HANGUL SYLLABLE GYOSS
+AD65;AD65;1100 116D 11BC;AD65;1100 116D 11BC; # (굥; 굥; 굥; 굥; 굥; ) HANGUL SYLLABLE GYONG
+AD66;AD66;1100 116D 11BD;AD66;1100 116D 11BD; # (굦; 굦; 굦; 굦; 굦; ) HANGUL SYLLABLE GYOJ
+AD67;AD67;1100 116D 11BE;AD67;1100 116D 11BE; # (굧; 굧; 굧; 굧; 굧; ) HANGUL SYLLABLE GYOC
+AD68;AD68;1100 116D 11BF;AD68;1100 116D 11BF; # (굨; 굨; 굨; 굨; 굨; ) HANGUL SYLLABLE GYOK
+AD69;AD69;1100 116D 11C0;AD69;1100 116D 11C0; # (굩; 굩; 굩; 굩; 굩; ) HANGUL SYLLABLE GYOT
+AD6A;AD6A;1100 116D 11C1;AD6A;1100 116D 11C1; # (굪; 굪; 교á‡; 굪; 교á‡; ) HANGUL SYLLABLE GYOP
+AD6B;AD6B;1100 116D 11C2;AD6B;1100 116D 11C2; # (굫; 굫; 굫; 굫; 굫; ) HANGUL SYLLABLE GYOH
+AD6C;AD6C;1100 116E;AD6C;1100 116E; # (구; 구; 구; 구; 구; ) HANGUL SYLLABLE GU
+AD6D;AD6D;1100 116E 11A8;AD6D;1100 116E 11A8; # (국; 국; 국; 국; 국; ) HANGUL SYLLABLE GUG
+AD6E;AD6E;1100 116E 11A9;AD6E;1100 116E 11A9; # (굮; 굮; 굮; 굮; 굮; ) HANGUL SYLLABLE GUGG
+AD6F;AD6F;1100 116E 11AA;AD6F;1100 116E 11AA; # (굯; 굯; 굯; 굯; 굯; ) HANGUL SYLLABLE GUGS
+AD70;AD70;1100 116E 11AB;AD70;1100 116E 11AB; # (군; 군; 군; 군; 군; ) HANGUL SYLLABLE GUN
+AD71;AD71;1100 116E 11AC;AD71;1100 116E 11AC; # (굱; 굱; 굱; 굱; 굱; ) HANGUL SYLLABLE GUNJ
+AD72;AD72;1100 116E 11AD;AD72;1100 116E 11AD; # (굲; 굲; 굲; 굲; 굲; ) HANGUL SYLLABLE GUNH
+AD73;AD73;1100 116E 11AE;AD73;1100 116E 11AE; # (굳; 굳; 굳; 굳; 굳; ) HANGUL SYLLABLE GUD
+AD74;AD74;1100 116E 11AF;AD74;1100 116E 11AF; # (굴; 굴; 굴; 굴; 굴; ) HANGUL SYLLABLE GUL
+AD75;AD75;1100 116E 11B0;AD75;1100 116E 11B0; # (굵; 굵; 굵; 굵; 굵; ) HANGUL SYLLABLE GULG
+AD76;AD76;1100 116E 11B1;AD76;1100 116E 11B1; # (굶; 굶; 굶; 굶; 굶; ) HANGUL SYLLABLE GULM
+AD77;AD77;1100 116E 11B2;AD77;1100 116E 11B2; # (굷; 굷; 굷; 굷; 굷; ) HANGUL SYLLABLE GULB
+AD78;AD78;1100 116E 11B3;AD78;1100 116E 11B3; # (굸; 굸; 굸; 굸; 굸; ) HANGUL SYLLABLE GULS
+AD79;AD79;1100 116E 11B4;AD79;1100 116E 11B4; # (굹; 굹; 굹; 굹; 굹; ) HANGUL SYLLABLE GULT
+AD7A;AD7A;1100 116E 11B5;AD7A;1100 116E 11B5; # (굺; 굺; 굺; 굺; 굺; ) HANGUL SYLLABLE GULP
+AD7B;AD7B;1100 116E 11B6;AD7B;1100 116E 11B6; # (굻; 굻; 굻; 굻; 굻; ) HANGUL SYLLABLE GULH
+AD7C;AD7C;1100 116E 11B7;AD7C;1100 116E 11B7; # (굼; 굼; 굼; 굼; 굼; ) HANGUL SYLLABLE GUM
+AD7D;AD7D;1100 116E 11B8;AD7D;1100 116E 11B8; # (굽; 굽; 굽; 굽; 굽; ) HANGUL SYLLABLE GUB
+AD7E;AD7E;1100 116E 11B9;AD7E;1100 116E 11B9; # (굾; 굾; 굾; 굾; 굾; ) HANGUL SYLLABLE GUBS
+AD7F;AD7F;1100 116E 11BA;AD7F;1100 116E 11BA; # (굿; 굿; 굿; 굿; 굿; ) HANGUL SYLLABLE GUS
+AD80;AD80;1100 116E 11BB;AD80;1100 116E 11BB; # (궀; 궀; 궀; 궀; 궀; ) HANGUL SYLLABLE GUSS
+AD81;AD81;1100 116E 11BC;AD81;1100 116E 11BC; # (ê¶; ê¶; 궁; ê¶; 궁; ) HANGUL SYLLABLE GUNG
+AD82;AD82;1100 116E 11BD;AD82;1100 116E 11BD; # (궂; 궂; 궂; 궂; 궂; ) HANGUL SYLLABLE GUJ
+AD83;AD83;1100 116E 11BE;AD83;1100 116E 11BE; # (궃; 궃; 궃; 궃; 궃; ) HANGUL SYLLABLE GUC
+AD84;AD84;1100 116E 11BF;AD84;1100 116E 11BF; # (궄; 궄; 궄; 궄; 궄; ) HANGUL SYLLABLE GUK
+AD85;AD85;1100 116E 11C0;AD85;1100 116E 11C0; # (궅; 궅; 궅; 궅; 궅; ) HANGUL SYLLABLE GUT
+AD86;AD86;1100 116E 11C1;AD86;1100 116E 11C1; # (궆; 궆; 구á‡; 궆; 구á‡; ) HANGUL SYLLABLE GUP
+AD87;AD87;1100 116E 11C2;AD87;1100 116E 11C2; # (궇; 궇; 궇; 궇; 궇; ) HANGUL SYLLABLE GUH
+AD88;AD88;1100 116F;AD88;1100 116F; # (궈; 궈; 궈; 궈; 궈; ) HANGUL SYLLABLE GWEO
+AD89;AD89;1100 116F 11A8;AD89;1100 116F 11A8; # (궉; 궉; 궉; 궉; 궉; ) HANGUL SYLLABLE GWEOG
+AD8A;AD8A;1100 116F 11A9;AD8A;1100 116F 11A9; # (궊; 궊; 궊; 궊; 궊; ) HANGUL SYLLABLE GWEOGG
+AD8B;AD8B;1100 116F 11AA;AD8B;1100 116F 11AA; # (궋; 궋; 궋; 궋; 궋; ) HANGUL SYLLABLE GWEOGS
+AD8C;AD8C;1100 116F 11AB;AD8C;1100 116F 11AB; # (권; 권; 권; 권; 권; ) HANGUL SYLLABLE GWEON
+AD8D;AD8D;1100 116F 11AC;AD8D;1100 116F 11AC; # (ê¶; ê¶; 궍; ê¶; 궍; ) HANGUL SYLLABLE GWEONJ
+AD8E;AD8E;1100 116F 11AD;AD8E;1100 116F 11AD; # (궎; 궎; 궎; 궎; 궎; ) HANGUL SYLLABLE GWEONH
+AD8F;AD8F;1100 116F 11AE;AD8F;1100 116F 11AE; # (ê¶; ê¶; 궏; ê¶; 궏; ) HANGUL SYLLABLE GWEOD
+AD90;AD90;1100 116F 11AF;AD90;1100 116F 11AF; # (ê¶; ê¶; 궐; ê¶; 궐; ) HANGUL SYLLABLE GWEOL
+AD91;AD91;1100 116F 11B0;AD91;1100 116F 11B0; # (궑; 궑; 궑; 궑; 궑; ) HANGUL SYLLABLE GWEOLG
+AD92;AD92;1100 116F 11B1;AD92;1100 116F 11B1; # (궒; 궒; 궒; 궒; 궒; ) HANGUL SYLLABLE GWEOLM
+AD93;AD93;1100 116F 11B2;AD93;1100 116F 11B2; # (궓; 궓; 궓; 궓; 궓; ) HANGUL SYLLABLE GWEOLB
+AD94;AD94;1100 116F 11B3;AD94;1100 116F 11B3; # (궔; 궔; 궔; 궔; 궔; ) HANGUL SYLLABLE GWEOLS
+AD95;AD95;1100 116F 11B4;AD95;1100 116F 11B4; # (궕; 궕; 궕; 궕; 궕; ) HANGUL SYLLABLE GWEOLT
+AD96;AD96;1100 116F 11B5;AD96;1100 116F 11B5; # (궖; 궖; 궖; 궖; 궖; ) HANGUL SYLLABLE GWEOLP
+AD97;AD97;1100 116F 11B6;AD97;1100 116F 11B6; # (궗; 궗; 궗; 궗; 궗; ) HANGUL SYLLABLE GWEOLH
+AD98;AD98;1100 116F 11B7;AD98;1100 116F 11B7; # (궘; 궘; 궘; 궘; 궘; ) HANGUL SYLLABLE GWEOM
+AD99;AD99;1100 116F 11B8;AD99;1100 116F 11B8; # (궙; 궙; 궙; 궙; 궙; ) HANGUL SYLLABLE GWEOB
+AD9A;AD9A;1100 116F 11B9;AD9A;1100 116F 11B9; # (궚; 궚; 궚; 궚; 궚; ) HANGUL SYLLABLE GWEOBS
+AD9B;AD9B;1100 116F 11BA;AD9B;1100 116F 11BA; # (궛; 궛; 궛; 궛; 궛; ) HANGUL SYLLABLE GWEOS
+AD9C;AD9C;1100 116F 11BB;AD9C;1100 116F 11BB; # (궜; 궜; 궜; 궜; 궜; ) HANGUL SYLLABLE GWEOSS
+AD9D;AD9D;1100 116F 11BC;AD9D;1100 116F 11BC; # (ê¶; ê¶; 궝; ê¶; 궝; ) HANGUL SYLLABLE GWEONG
+AD9E;AD9E;1100 116F 11BD;AD9E;1100 116F 11BD; # (궞; 궞; 궞; 궞; 궞; ) HANGUL SYLLABLE GWEOJ
+AD9F;AD9F;1100 116F 11BE;AD9F;1100 116F 11BE; # (궟; 궟; 궟; 궟; 궟; ) HANGUL SYLLABLE GWEOC
+ADA0;ADA0;1100 116F 11BF;ADA0;1100 116F 11BF; # (궠; 궠; 궠; 궠; 궠; ) HANGUL SYLLABLE GWEOK
+ADA1;ADA1;1100 116F 11C0;ADA1;1100 116F 11C0; # (궡; 궡; 궡; 궡; 궡; ) HANGUL SYLLABLE GWEOT
+ADA2;ADA2;1100 116F 11C1;ADA2;1100 116F 11C1; # (궢; 궢; 궈á‡; 궢; 궈á‡; ) HANGUL SYLLABLE GWEOP
+ADA3;ADA3;1100 116F 11C2;ADA3;1100 116F 11C2; # (궣; 궣; 궣; 궣; 궣; ) HANGUL SYLLABLE GWEOH
+ADA4;ADA4;1100 1170;ADA4;1100 1170; # (궤; 궤; 궤; 궤; 궤; ) HANGUL SYLLABLE GWE
+ADA5;ADA5;1100 1170 11A8;ADA5;1100 1170 11A8; # (궥; 궥; 궥; 궥; 궥; ) HANGUL SYLLABLE GWEG
+ADA6;ADA6;1100 1170 11A9;ADA6;1100 1170 11A9; # (궦; 궦; 궦; 궦; 궦; ) HANGUL SYLLABLE GWEGG
+ADA7;ADA7;1100 1170 11AA;ADA7;1100 1170 11AA; # (궧; 궧; 궧; 궧; 궧; ) HANGUL SYLLABLE GWEGS
+ADA8;ADA8;1100 1170 11AB;ADA8;1100 1170 11AB; # (궨; 궨; 궨; 궨; 궨; ) HANGUL SYLLABLE GWEN
+ADA9;ADA9;1100 1170 11AC;ADA9;1100 1170 11AC; # (궩; 궩; 궩; 궩; 궩; ) HANGUL SYLLABLE GWENJ
+ADAA;ADAA;1100 1170 11AD;ADAA;1100 1170 11AD; # (궪; 궪; 궪; 궪; 궪; ) HANGUL SYLLABLE GWENH
+ADAB;ADAB;1100 1170 11AE;ADAB;1100 1170 11AE; # (궫; 궫; 궫; 궫; 궫; ) HANGUL SYLLABLE GWED
+ADAC;ADAC;1100 1170 11AF;ADAC;1100 1170 11AF; # (궬; 궬; 궬; 궬; 궬; ) HANGUL SYLLABLE GWEL
+ADAD;ADAD;1100 1170 11B0;ADAD;1100 1170 11B0; # (궭; 궭; 궭; 궭; 궭; ) HANGUL SYLLABLE GWELG
+ADAE;ADAE;1100 1170 11B1;ADAE;1100 1170 11B1; # (궮; 궮; 궮; 궮; 궮; ) HANGUL SYLLABLE GWELM
+ADAF;ADAF;1100 1170 11B2;ADAF;1100 1170 11B2; # (궯; 궯; 궯; 궯; 궯; ) HANGUL SYLLABLE GWELB
+ADB0;ADB0;1100 1170 11B3;ADB0;1100 1170 11B3; # (궰; 궰; 궰; 궰; 궰; ) HANGUL SYLLABLE GWELS
+ADB1;ADB1;1100 1170 11B4;ADB1;1100 1170 11B4; # (궱; 궱; 궱; 궱; 궱; ) HANGUL SYLLABLE GWELT
+ADB2;ADB2;1100 1170 11B5;ADB2;1100 1170 11B5; # (궲; 궲; 궲; 궲; 궲; ) HANGUL SYLLABLE GWELP
+ADB3;ADB3;1100 1170 11B6;ADB3;1100 1170 11B6; # (궳; 궳; 궳; 궳; 궳; ) HANGUL SYLLABLE GWELH
+ADB4;ADB4;1100 1170 11B7;ADB4;1100 1170 11B7; # (궴; 궴; 궴; 궴; 궴; ) HANGUL SYLLABLE GWEM
+ADB5;ADB5;1100 1170 11B8;ADB5;1100 1170 11B8; # (궵; 궵; 궵; 궵; 궵; ) HANGUL SYLLABLE GWEB
+ADB6;ADB6;1100 1170 11B9;ADB6;1100 1170 11B9; # (궶; 궶; 궶; 궶; 궶; ) HANGUL SYLLABLE GWEBS
+ADB7;ADB7;1100 1170 11BA;ADB7;1100 1170 11BA; # (궷; 궷; 궷; 궷; 궷; ) HANGUL SYLLABLE GWES
+ADB8;ADB8;1100 1170 11BB;ADB8;1100 1170 11BB; # (궸; 궸; 궸; 궸; 궸; ) HANGUL SYLLABLE GWESS
+ADB9;ADB9;1100 1170 11BC;ADB9;1100 1170 11BC; # (궹; 궹; 궹; 궹; 궹; ) HANGUL SYLLABLE GWENG
+ADBA;ADBA;1100 1170 11BD;ADBA;1100 1170 11BD; # (궺; 궺; 궺; 궺; 궺; ) HANGUL SYLLABLE GWEJ
+ADBB;ADBB;1100 1170 11BE;ADBB;1100 1170 11BE; # (궻; 궻; 궻; 궻; 궻; ) HANGUL SYLLABLE GWEC
+ADBC;ADBC;1100 1170 11BF;ADBC;1100 1170 11BF; # (궼; 궼; 궼; 궼; 궼; ) HANGUL SYLLABLE GWEK
+ADBD;ADBD;1100 1170 11C0;ADBD;1100 1170 11C0; # (궽; 궽; 궽; 궽; 궽; ) HANGUL SYLLABLE GWET
+ADBE;ADBE;1100 1170 11C1;ADBE;1100 1170 11C1; # (궾; 궾; 궤á‡; 궾; 궤á‡; ) HANGUL SYLLABLE GWEP
+ADBF;ADBF;1100 1170 11C2;ADBF;1100 1170 11C2; # (궿; 궿; 궿; 궿; 궿; ) HANGUL SYLLABLE GWEH
+ADC0;ADC0;1100 1171;ADC0;1100 1171; # (귀; 귀; 귀; 귀; 귀; ) HANGUL SYLLABLE GWI
+ADC1;ADC1;1100 1171 11A8;ADC1;1100 1171 11A8; # (ê·; ê·; 귁; ê·; 귁; ) HANGUL SYLLABLE GWIG
+ADC2;ADC2;1100 1171 11A9;ADC2;1100 1171 11A9; # (귂; 귂; 귂; 귂; 귂; ) HANGUL SYLLABLE GWIGG
+ADC3;ADC3;1100 1171 11AA;ADC3;1100 1171 11AA; # (귃; 귃; 귃; 귃; 귃; ) HANGUL SYLLABLE GWIGS
+ADC4;ADC4;1100 1171 11AB;ADC4;1100 1171 11AB; # (귄; 귄; 귄; 귄; 귄; ) HANGUL SYLLABLE GWIN
+ADC5;ADC5;1100 1171 11AC;ADC5;1100 1171 11AC; # (귅; 귅; 귅; 귅; 귅; ) HANGUL SYLLABLE GWINJ
+ADC6;ADC6;1100 1171 11AD;ADC6;1100 1171 11AD; # (귆; 귆; 귆; 귆; 귆; ) HANGUL SYLLABLE GWINH
+ADC7;ADC7;1100 1171 11AE;ADC7;1100 1171 11AE; # (귇; 귇; 귇; 귇; 귇; ) HANGUL SYLLABLE GWID
+ADC8;ADC8;1100 1171 11AF;ADC8;1100 1171 11AF; # (귈; 귈; 귈; 귈; 귈; ) HANGUL SYLLABLE GWIL
+ADC9;ADC9;1100 1171 11B0;ADC9;1100 1171 11B0; # (귉; 귉; 귉; 귉; 귉; ) HANGUL SYLLABLE GWILG
+ADCA;ADCA;1100 1171 11B1;ADCA;1100 1171 11B1; # (귊; 귊; 귊; 귊; 귊; ) HANGUL SYLLABLE GWILM
+ADCB;ADCB;1100 1171 11B2;ADCB;1100 1171 11B2; # (귋; 귋; 귋; 귋; 귋; ) HANGUL SYLLABLE GWILB
+ADCC;ADCC;1100 1171 11B3;ADCC;1100 1171 11B3; # (귌; 귌; 귌; 귌; 귌; ) HANGUL SYLLABLE GWILS
+ADCD;ADCD;1100 1171 11B4;ADCD;1100 1171 11B4; # (ê·; ê·; 귍; ê·; 귍; ) HANGUL SYLLABLE GWILT
+ADCE;ADCE;1100 1171 11B5;ADCE;1100 1171 11B5; # (귎; 귎; 귎; 귎; 귎; ) HANGUL SYLLABLE GWILP
+ADCF;ADCF;1100 1171 11B6;ADCF;1100 1171 11B6; # (ê·; ê·; 귏; ê·; 귏; ) HANGUL SYLLABLE GWILH
+ADD0;ADD0;1100 1171 11B7;ADD0;1100 1171 11B7; # (ê·; ê·; 귐; ê·; 귐; ) HANGUL SYLLABLE GWIM
+ADD1;ADD1;1100 1171 11B8;ADD1;1100 1171 11B8; # (귑; 귑; 귑; 귑; 귑; ) HANGUL SYLLABLE GWIB
+ADD2;ADD2;1100 1171 11B9;ADD2;1100 1171 11B9; # (귒; 귒; 귒; 귒; 귒; ) HANGUL SYLLABLE GWIBS
+ADD3;ADD3;1100 1171 11BA;ADD3;1100 1171 11BA; # (귓; 귓; 귓; 귓; 귓; ) HANGUL SYLLABLE GWIS
+ADD4;ADD4;1100 1171 11BB;ADD4;1100 1171 11BB; # (귔; 귔; 귔; 귔; 귔; ) HANGUL SYLLABLE GWISS
+ADD5;ADD5;1100 1171 11BC;ADD5;1100 1171 11BC; # (귕; 귕; 귕; 귕; 귕; ) HANGUL SYLLABLE GWING
+ADD6;ADD6;1100 1171 11BD;ADD6;1100 1171 11BD; # (귖; 귖; 귖; 귖; 귖; ) HANGUL SYLLABLE GWIJ
+ADD7;ADD7;1100 1171 11BE;ADD7;1100 1171 11BE; # (귗; 귗; 귗; 귗; 귗; ) HANGUL SYLLABLE GWIC
+ADD8;ADD8;1100 1171 11BF;ADD8;1100 1171 11BF; # (귘; 귘; 귘; 귘; 귘; ) HANGUL SYLLABLE GWIK
+ADD9;ADD9;1100 1171 11C0;ADD9;1100 1171 11C0; # (귙; 귙; 귙; 귙; 귙; ) HANGUL SYLLABLE GWIT
+ADDA;ADDA;1100 1171 11C1;ADDA;1100 1171 11C1; # (ê·š; ê·š; 귀á‡; ê·š; 귀á‡; ) HANGUL SYLLABLE GWIP
+ADDB;ADDB;1100 1171 11C2;ADDB;1100 1171 11C2; # (귛; 귛; 귛; 귛; 귛; ) HANGUL SYLLABLE GWIH
+ADDC;ADDC;1100 1172;ADDC;1100 1172; # (규; 규; 규; 규; 규; ) HANGUL SYLLABLE GYU
+ADDD;ADDD;1100 1172 11A8;ADDD;1100 1172 11A8; # (ê·; ê·; 귝; ê·; 귝; ) HANGUL SYLLABLE GYUG
+ADDE;ADDE;1100 1172 11A9;ADDE;1100 1172 11A9; # (귞; 귞; 귞; 귞; 귞; ) HANGUL SYLLABLE GYUGG
+ADDF;ADDF;1100 1172 11AA;ADDF;1100 1172 11AA; # (귟; 귟; 귟; 귟; 귟; ) HANGUL SYLLABLE GYUGS
+ADE0;ADE0;1100 1172 11AB;ADE0;1100 1172 11AB; # (균; 균; 균; 균; 균; ) HANGUL SYLLABLE GYUN
+ADE1;ADE1;1100 1172 11AC;ADE1;1100 1172 11AC; # (귡; 귡; 귡; 귡; 귡; ) HANGUL SYLLABLE GYUNJ
+ADE2;ADE2;1100 1172 11AD;ADE2;1100 1172 11AD; # (귢; 귢; 귢; 귢; 귢; ) HANGUL SYLLABLE GYUNH
+ADE3;ADE3;1100 1172 11AE;ADE3;1100 1172 11AE; # (귣; 귣; 귣; 귣; 귣; ) HANGUL SYLLABLE GYUD
+ADE4;ADE4;1100 1172 11AF;ADE4;1100 1172 11AF; # (귤; 귤; 귤; 귤; 귤; ) HANGUL SYLLABLE GYUL
+ADE5;ADE5;1100 1172 11B0;ADE5;1100 1172 11B0; # (귥; 귥; 귥; 귥; 귥; ) HANGUL SYLLABLE GYULG
+ADE6;ADE6;1100 1172 11B1;ADE6;1100 1172 11B1; # (귦; 귦; 귦; 귦; 귦; ) HANGUL SYLLABLE GYULM
+ADE7;ADE7;1100 1172 11B2;ADE7;1100 1172 11B2; # (귧; 귧; 귧; 귧; 귧; ) HANGUL SYLLABLE GYULB
+ADE8;ADE8;1100 1172 11B3;ADE8;1100 1172 11B3; # (귨; 귨; 귨; 귨; 귨; ) HANGUL SYLLABLE GYULS
+ADE9;ADE9;1100 1172 11B4;ADE9;1100 1172 11B4; # (귩; 귩; 귩; 귩; 귩; ) HANGUL SYLLABLE GYULT
+ADEA;ADEA;1100 1172 11B5;ADEA;1100 1172 11B5; # (귪; 귪; 귪; 귪; 귪; ) HANGUL SYLLABLE GYULP
+ADEB;ADEB;1100 1172 11B6;ADEB;1100 1172 11B6; # (귫; 귫; 귫; 귫; 귫; ) HANGUL SYLLABLE GYULH
+ADEC;ADEC;1100 1172 11B7;ADEC;1100 1172 11B7; # (귬; 귬; 귬; 귬; 귬; ) HANGUL SYLLABLE GYUM
+ADED;ADED;1100 1172 11B8;ADED;1100 1172 11B8; # (귭; 귭; 귭; 귭; 귭; ) HANGUL SYLLABLE GYUB
+ADEE;ADEE;1100 1172 11B9;ADEE;1100 1172 11B9; # (귮; 귮; 귮; 귮; 귮; ) HANGUL SYLLABLE GYUBS
+ADEF;ADEF;1100 1172 11BA;ADEF;1100 1172 11BA; # (귯; 귯; 귯; 귯; 귯; ) HANGUL SYLLABLE GYUS
+ADF0;ADF0;1100 1172 11BB;ADF0;1100 1172 11BB; # (귰; 귰; 귰; 귰; 귰; ) HANGUL SYLLABLE GYUSS
+ADF1;ADF1;1100 1172 11BC;ADF1;1100 1172 11BC; # (귱; 귱; 귱; 귱; 귱; ) HANGUL SYLLABLE GYUNG
+ADF2;ADF2;1100 1172 11BD;ADF2;1100 1172 11BD; # (귲; 귲; 귲; 귲; 귲; ) HANGUL SYLLABLE GYUJ
+ADF3;ADF3;1100 1172 11BE;ADF3;1100 1172 11BE; # (귳; 귳; 귳; 귳; 귳; ) HANGUL SYLLABLE GYUC
+ADF4;ADF4;1100 1172 11BF;ADF4;1100 1172 11BF; # (귴; 귴; 귴; 귴; 귴; ) HANGUL SYLLABLE GYUK
+ADF5;ADF5;1100 1172 11C0;ADF5;1100 1172 11C0; # (귵; 귵; 귵; 귵; 귵; ) HANGUL SYLLABLE GYUT
+ADF6;ADF6;1100 1172 11C1;ADF6;1100 1172 11C1; # (ê·¶; ê·¶; 규á‡; ê·¶; 규á‡; ) HANGUL SYLLABLE GYUP
+ADF7;ADF7;1100 1172 11C2;ADF7;1100 1172 11C2; # (귷; 귷; 귷; 귷; 귷; ) HANGUL SYLLABLE GYUH
+ADF8;ADF8;1100 1173;ADF8;1100 1173; # (그; 그; 그; 그; 그; ) HANGUL SYLLABLE GEU
+ADF9;ADF9;1100 1173 11A8;ADF9;1100 1173 11A8; # (극; 극; 극; 극; 극; ) HANGUL SYLLABLE GEUG
+ADFA;ADFA;1100 1173 11A9;ADFA;1100 1173 11A9; # (귺; 귺; 귺; 귺; 귺; ) HANGUL SYLLABLE GEUGG
+ADFB;ADFB;1100 1173 11AA;ADFB;1100 1173 11AA; # (귻; 귻; 귻; 귻; 귻; ) HANGUL SYLLABLE GEUGS
+ADFC;ADFC;1100 1173 11AB;ADFC;1100 1173 11AB; # (근; 근; 근; 근; 근; ) HANGUL SYLLABLE GEUN
+ADFD;ADFD;1100 1173 11AC;ADFD;1100 1173 11AC; # (귽; 귽; 귽; 귽; 귽; ) HANGUL SYLLABLE GEUNJ
+ADFE;ADFE;1100 1173 11AD;ADFE;1100 1173 11AD; # (귾; 귾; 귾; 귾; 귾; ) HANGUL SYLLABLE GEUNH
+ADFF;ADFF;1100 1173 11AE;ADFF;1100 1173 11AE; # (귿; 귿; 귿; 귿; 귿; ) HANGUL SYLLABLE GEUD
+AE00;AE00;1100 1173 11AF;AE00;1100 1173 11AF; # (글; 글; 글; 글; 글; ) HANGUL SYLLABLE GEUL
+AE01;AE01;1100 1173 11B0;AE01;1100 1173 11B0; # (ê¸; ê¸; 긁; ê¸; 긁; ) HANGUL SYLLABLE GEULG
+AE02;AE02;1100 1173 11B1;AE02;1100 1173 11B1; # (긂; 긂; 긂; 긂; 긂; ) HANGUL SYLLABLE GEULM
+AE03;AE03;1100 1173 11B2;AE03;1100 1173 11B2; # (긃; 긃; 긃; 긃; 긃; ) HANGUL SYLLABLE GEULB
+AE04;AE04;1100 1173 11B3;AE04;1100 1173 11B3; # (긄; 긄; 긄; 긄; 긄; ) HANGUL SYLLABLE GEULS
+AE05;AE05;1100 1173 11B4;AE05;1100 1173 11B4; # (긅; 긅; 긅; 긅; 긅; ) HANGUL SYLLABLE GEULT
+AE06;AE06;1100 1173 11B5;AE06;1100 1173 11B5; # (긆; 긆; 긆; 긆; 긆; ) HANGUL SYLLABLE GEULP
+AE07;AE07;1100 1173 11B6;AE07;1100 1173 11B6; # (긇; 긇; 긇; 긇; 긇; ) HANGUL SYLLABLE GEULH
+AE08;AE08;1100 1173 11B7;AE08;1100 1173 11B7; # (금; 금; 금; 금; 금; ) HANGUL SYLLABLE GEUM
+AE09;AE09;1100 1173 11B8;AE09;1100 1173 11B8; # (급; 급; 급; 급; 급; ) HANGUL SYLLABLE GEUB
+AE0A;AE0A;1100 1173 11B9;AE0A;1100 1173 11B9; # (긊; 긊; 긊; 긊; 긊; ) HANGUL SYLLABLE GEUBS
+AE0B;AE0B;1100 1173 11BA;AE0B;1100 1173 11BA; # (긋; 긋; 긋; 긋; 긋; ) HANGUL SYLLABLE GEUS
+AE0C;AE0C;1100 1173 11BB;AE0C;1100 1173 11BB; # (긌; 긌; 긌; 긌; 긌; ) HANGUL SYLLABLE GEUSS
+AE0D;AE0D;1100 1173 11BC;AE0D;1100 1173 11BC; # (ê¸; ê¸; 긍; ê¸; 긍; ) HANGUL SYLLABLE GEUNG
+AE0E;AE0E;1100 1173 11BD;AE0E;1100 1173 11BD; # (긎; 긎; 긎; 긎; 긎; ) HANGUL SYLLABLE GEUJ
+AE0F;AE0F;1100 1173 11BE;AE0F;1100 1173 11BE; # (ê¸; ê¸; 긏; ê¸; 긏; ) HANGUL SYLLABLE GEUC
+AE10;AE10;1100 1173 11BF;AE10;1100 1173 11BF; # (ê¸; ê¸; 긐; ê¸; 긐; ) HANGUL SYLLABLE GEUK
+AE11;AE11;1100 1173 11C0;AE11;1100 1173 11C0; # (긑; 긑; 긑; 긑; 긑; ) HANGUL SYLLABLE GEUT
+AE12;AE12;1100 1173 11C1;AE12;1100 1173 11C1; # (긒; 긒; 그á‡; 긒; 그á‡; ) HANGUL SYLLABLE GEUP
+AE13;AE13;1100 1173 11C2;AE13;1100 1173 11C2; # (긓; 긓; 긓; 긓; 긓; ) HANGUL SYLLABLE GEUH
+AE14;AE14;1100 1174;AE14;1100 1174; # (긔; 긔; 긔; 긔; 긔; ) HANGUL SYLLABLE GYI
+AE15;AE15;1100 1174 11A8;AE15;1100 1174 11A8; # (긕; 긕; 긕; 긕; 긕; ) HANGUL SYLLABLE GYIG
+AE16;AE16;1100 1174 11A9;AE16;1100 1174 11A9; # (긖; 긖; 긖; 긖; 긖; ) HANGUL SYLLABLE GYIGG
+AE17;AE17;1100 1174 11AA;AE17;1100 1174 11AA; # (긗; 긗; 긗; 긗; 긗; ) HANGUL SYLLABLE GYIGS
+AE18;AE18;1100 1174 11AB;AE18;1100 1174 11AB; # (긘; 긘; 긘; 긘; 긘; ) HANGUL SYLLABLE GYIN
+AE19;AE19;1100 1174 11AC;AE19;1100 1174 11AC; # (긙; 긙; 긙; 긙; 긙; ) HANGUL SYLLABLE GYINJ
+AE1A;AE1A;1100 1174 11AD;AE1A;1100 1174 11AD; # (긚; 긚; 긚; 긚; 긚; ) HANGUL SYLLABLE GYINH
+AE1B;AE1B;1100 1174 11AE;AE1B;1100 1174 11AE; # (긛; 긛; 긛; 긛; 긛; ) HANGUL SYLLABLE GYID
+AE1C;AE1C;1100 1174 11AF;AE1C;1100 1174 11AF; # (긜; 긜; 긜; 긜; 긜; ) HANGUL SYLLABLE GYIL
+AE1D;AE1D;1100 1174 11B0;AE1D;1100 1174 11B0; # (ê¸; ê¸; 긝; ê¸; 긝; ) HANGUL SYLLABLE GYILG
+AE1E;AE1E;1100 1174 11B1;AE1E;1100 1174 11B1; # (긞; 긞; 긞; 긞; 긞; ) HANGUL SYLLABLE GYILM
+AE1F;AE1F;1100 1174 11B2;AE1F;1100 1174 11B2; # (긟; 긟; 긟; 긟; 긟; ) HANGUL SYLLABLE GYILB
+AE20;AE20;1100 1174 11B3;AE20;1100 1174 11B3; # (긠; 긠; 긠; 긠; 긠; ) HANGUL SYLLABLE GYILS
+AE21;AE21;1100 1174 11B4;AE21;1100 1174 11B4; # (긡; 긡; 긡; 긡; 긡; ) HANGUL SYLLABLE GYILT
+AE22;AE22;1100 1174 11B5;AE22;1100 1174 11B5; # (긢; 긢; 긢; 긢; 긢; ) HANGUL SYLLABLE GYILP
+AE23;AE23;1100 1174 11B6;AE23;1100 1174 11B6; # (긣; 긣; 긣; 긣; 긣; ) HANGUL SYLLABLE GYILH
+AE24;AE24;1100 1174 11B7;AE24;1100 1174 11B7; # (긤; 긤; 긤; 긤; 긤; ) HANGUL SYLLABLE GYIM
+AE25;AE25;1100 1174 11B8;AE25;1100 1174 11B8; # (긥; 긥; 긥; 긥; 긥; ) HANGUL SYLLABLE GYIB
+AE26;AE26;1100 1174 11B9;AE26;1100 1174 11B9; # (긦; 긦; 긦; 긦; 긦; ) HANGUL SYLLABLE GYIBS
+AE27;AE27;1100 1174 11BA;AE27;1100 1174 11BA; # (긧; 긧; 긧; 긧; 긧; ) HANGUL SYLLABLE GYIS
+AE28;AE28;1100 1174 11BB;AE28;1100 1174 11BB; # (긨; 긨; 긨; 긨; 긨; ) HANGUL SYLLABLE GYISS
+AE29;AE29;1100 1174 11BC;AE29;1100 1174 11BC; # (긩; 긩; 긩; 긩; 긩; ) HANGUL SYLLABLE GYING
+AE2A;AE2A;1100 1174 11BD;AE2A;1100 1174 11BD; # (긪; 긪; 긪; 긪; 긪; ) HANGUL SYLLABLE GYIJ
+AE2B;AE2B;1100 1174 11BE;AE2B;1100 1174 11BE; # (긫; 긫; 긫; 긫; 긫; ) HANGUL SYLLABLE GYIC
+AE2C;AE2C;1100 1174 11BF;AE2C;1100 1174 11BF; # (긬; 긬; 긬; 긬; 긬; ) HANGUL SYLLABLE GYIK
+AE2D;AE2D;1100 1174 11C0;AE2D;1100 1174 11C0; # (긭; 긭; 긭; 긭; 긭; ) HANGUL SYLLABLE GYIT
+AE2E;AE2E;1100 1174 11C1;AE2E;1100 1174 11C1; # (긮; 긮; 긔á‡; 긮; 긔á‡; ) HANGUL SYLLABLE GYIP
+AE2F;AE2F;1100 1174 11C2;AE2F;1100 1174 11C2; # (긯; 긯; 긯; 긯; 긯; ) HANGUL SYLLABLE GYIH
+AE30;AE30;1100 1175;AE30;1100 1175; # (기; 기; 기; 기; 기; ) HANGUL SYLLABLE GI
+AE31;AE31;1100 1175 11A8;AE31;1100 1175 11A8; # (긱; 긱; 긱; 긱; 긱; ) HANGUL SYLLABLE GIG
+AE32;AE32;1100 1175 11A9;AE32;1100 1175 11A9; # (긲; 긲; 긲; 긲; 긲; ) HANGUL SYLLABLE GIGG
+AE33;AE33;1100 1175 11AA;AE33;1100 1175 11AA; # (긳; 긳; 긳; 긳; 긳; ) HANGUL SYLLABLE GIGS
+AE34;AE34;1100 1175 11AB;AE34;1100 1175 11AB; # (긴; 긴; 긴; 긴; 긴; ) HANGUL SYLLABLE GIN
+AE35;AE35;1100 1175 11AC;AE35;1100 1175 11AC; # (긵; 긵; 긵; 긵; 긵; ) HANGUL SYLLABLE GINJ
+AE36;AE36;1100 1175 11AD;AE36;1100 1175 11AD; # (긶; 긶; 긶; 긶; 긶; ) HANGUL SYLLABLE GINH
+AE37;AE37;1100 1175 11AE;AE37;1100 1175 11AE; # (긷; 긷; 긷; 긷; 긷; ) HANGUL SYLLABLE GID
+AE38;AE38;1100 1175 11AF;AE38;1100 1175 11AF; # (길; 길; 길; 길; 길; ) HANGUL SYLLABLE GIL
+AE39;AE39;1100 1175 11B0;AE39;1100 1175 11B0; # (긹; 긹; 긹; 긹; 긹; ) HANGUL SYLLABLE GILG
+AE3A;AE3A;1100 1175 11B1;AE3A;1100 1175 11B1; # (긺; 긺; 긺; 긺; 긺; ) HANGUL SYLLABLE GILM
+AE3B;AE3B;1100 1175 11B2;AE3B;1100 1175 11B2; # (긻; 긻; 긻; 긻; 긻; ) HANGUL SYLLABLE GILB
+AE3C;AE3C;1100 1175 11B3;AE3C;1100 1175 11B3; # (긼; 긼; 긼; 긼; 긼; ) HANGUL SYLLABLE GILS
+AE3D;AE3D;1100 1175 11B4;AE3D;1100 1175 11B4; # (긽; 긽; 긽; 긽; 긽; ) HANGUL SYLLABLE GILT
+AE3E;AE3E;1100 1175 11B5;AE3E;1100 1175 11B5; # (긾; 긾; 긾; 긾; 긾; ) HANGUL SYLLABLE GILP
+AE3F;AE3F;1100 1175 11B6;AE3F;1100 1175 11B6; # (긿; 긿; 긿; 긿; 긿; ) HANGUL SYLLABLE GILH
+AE40;AE40;1100 1175 11B7;AE40;1100 1175 11B7; # (김; 김; 김; 김; 김; ) HANGUL SYLLABLE GIM
+AE41;AE41;1100 1175 11B8;AE41;1100 1175 11B8; # (ê¹; ê¹; 깁; ê¹; 깁; ) HANGUL SYLLABLE GIB
+AE42;AE42;1100 1175 11B9;AE42;1100 1175 11B9; # (깂; 깂; 깂; 깂; 깂; ) HANGUL SYLLABLE GIBS
+AE43;AE43;1100 1175 11BA;AE43;1100 1175 11BA; # (깃; 깃; 깃; 깃; 깃; ) HANGUL SYLLABLE GIS
+AE44;AE44;1100 1175 11BB;AE44;1100 1175 11BB; # (깄; 깄; 깄; 깄; 깄; ) HANGUL SYLLABLE GISS
+AE45;AE45;1100 1175 11BC;AE45;1100 1175 11BC; # (깅; 깅; 깅; 깅; 깅; ) HANGUL SYLLABLE GING
+AE46;AE46;1100 1175 11BD;AE46;1100 1175 11BD; # (깆; 깆; 깆; 깆; 깆; ) HANGUL SYLLABLE GIJ
+AE47;AE47;1100 1175 11BE;AE47;1100 1175 11BE; # (깇; 깇; 깇; 깇; 깇; ) HANGUL SYLLABLE GIC
+AE48;AE48;1100 1175 11BF;AE48;1100 1175 11BF; # (깈; 깈; 깈; 깈; 깈; ) HANGUL SYLLABLE GIK
+AE49;AE49;1100 1175 11C0;AE49;1100 1175 11C0; # (깉; 깉; 깉; 깉; 깉; ) HANGUL SYLLABLE GIT
+AE4A;AE4A;1100 1175 11C1;AE4A;1100 1175 11C1; # (깊; 깊; 기á‡; 깊; 기á‡; ) HANGUL SYLLABLE GIP
+AE4B;AE4B;1100 1175 11C2;AE4B;1100 1175 11C2; # (깋; 깋; 깋; 깋; 깋; ) HANGUL SYLLABLE GIH
+AE4C;AE4C;1101 1161;AE4C;1101 1161; # (까; 까; á„á…¡; 까; á„á…¡; ) HANGUL SYLLABLE GGA
+AE4D;AE4D;1101 1161 11A8;AE4D;1101 1161 11A8; # (ê¹; ê¹; á„ᅡᆨ; ê¹; á„ᅡᆨ; ) HANGUL SYLLABLE GGAG
+AE4E;AE4E;1101 1161 11A9;AE4E;1101 1161 11A9; # (깎; 깎; á„ᅡᆩ; 깎; á„ᅡᆩ; ) HANGUL SYLLABLE GGAGG
+AE4F;AE4F;1101 1161 11AA;AE4F;1101 1161 11AA; # (ê¹; ê¹; á„ᅡᆪ; ê¹; á„ᅡᆪ; ) HANGUL SYLLABLE GGAGS
+AE50;AE50;1101 1161 11AB;AE50;1101 1161 11AB; # (ê¹; ê¹; á„ᅡᆫ; ê¹; á„ᅡᆫ; ) HANGUL SYLLABLE GGAN
+AE51;AE51;1101 1161 11AC;AE51;1101 1161 11AC; # (깑; 깑; á„ᅡᆬ; 깑; á„ᅡᆬ; ) HANGUL SYLLABLE GGANJ
+AE52;AE52;1101 1161 11AD;AE52;1101 1161 11AD; # (ê¹’; ê¹’; á„ᅡᆭ; ê¹’; á„ᅡᆭ; ) HANGUL SYLLABLE GGANH
+AE53;AE53;1101 1161 11AE;AE53;1101 1161 11AE; # (깓; 깓; á„ᅡᆮ; 깓; á„ᅡᆮ; ) HANGUL SYLLABLE GGAD
+AE54;AE54;1101 1161 11AF;AE54;1101 1161 11AF; # (ê¹”; ê¹”; á„ᅡᆯ; ê¹”; á„ᅡᆯ; ) HANGUL SYLLABLE GGAL
+AE55;AE55;1101 1161 11B0;AE55;1101 1161 11B0; # (깕; 깕; á„ᅡᆰ; 깕; á„ᅡᆰ; ) HANGUL SYLLABLE GGALG
+AE56;AE56;1101 1161 11B1;AE56;1101 1161 11B1; # (ê¹–; ê¹–; á„ᅡᆱ; ê¹–; á„ᅡᆱ; ) HANGUL SYLLABLE GGALM
+AE57;AE57;1101 1161 11B2;AE57;1101 1161 11B2; # (ê¹—; ê¹—; á„ᅡᆲ; ê¹—; á„ᅡᆲ; ) HANGUL SYLLABLE GGALB
+AE58;AE58;1101 1161 11B3;AE58;1101 1161 11B3; # (깘; 깘; á„ᅡᆳ; 깘; á„ᅡᆳ; ) HANGUL SYLLABLE GGALS
+AE59;AE59;1101 1161 11B4;AE59;1101 1161 11B4; # (ê¹™; ê¹™; á„ᅡᆴ; ê¹™; á„ᅡᆴ; ) HANGUL SYLLABLE GGALT
+AE5A;AE5A;1101 1161 11B5;AE5A;1101 1161 11B5; # (깚; 깚; á„ᅡᆵ; 깚; á„ᅡᆵ; ) HANGUL SYLLABLE GGALP
+AE5B;AE5B;1101 1161 11B6;AE5B;1101 1161 11B6; # (ê¹›; ê¹›; á„ᅡᆶ; ê¹›; á„ᅡᆶ; ) HANGUL SYLLABLE GGALH
+AE5C;AE5C;1101 1161 11B7;AE5C;1101 1161 11B7; # (깜; 깜; á„ᅡᆷ; 깜; á„ᅡᆷ; ) HANGUL SYLLABLE GGAM
+AE5D;AE5D;1101 1161 11B8;AE5D;1101 1161 11B8; # (ê¹; ê¹; á„ᅡᆸ; ê¹; á„ᅡᆸ; ) HANGUL SYLLABLE GGAB
+AE5E;AE5E;1101 1161 11B9;AE5E;1101 1161 11B9; # (깞; 깞; á„ᅡᆹ; 깞; á„ᅡᆹ; ) HANGUL SYLLABLE GGABS
+AE5F;AE5F;1101 1161 11BA;AE5F;1101 1161 11BA; # (깟; 깟; á„ᅡᆺ; 깟; á„ᅡᆺ; ) HANGUL SYLLABLE GGAS
+AE60;AE60;1101 1161 11BB;AE60;1101 1161 11BB; # (ê¹ ; ê¹ ; á„ᅡᆻ; ê¹ ; á„ᅡᆻ; ) HANGUL SYLLABLE GGASS
+AE61;AE61;1101 1161 11BC;AE61;1101 1161 11BC; # (깡; 깡; á„ᅡᆼ; 깡; á„ᅡᆼ; ) HANGUL SYLLABLE GGANG
+AE62;AE62;1101 1161 11BD;AE62;1101 1161 11BD; # (ê¹¢; ê¹¢; á„ᅡᆽ; ê¹¢; á„ᅡᆽ; ) HANGUL SYLLABLE GGAJ
+AE63;AE63;1101 1161 11BE;AE63;1101 1161 11BE; # (ê¹£; ê¹£; á„ᅡᆾ; ê¹£; á„ᅡᆾ; ) HANGUL SYLLABLE GGAC
+AE64;AE64;1101 1161 11BF;AE64;1101 1161 11BF; # (깤; 깤; á„ᅡᆿ; 깤; á„ᅡᆿ; ) HANGUL SYLLABLE GGAK
+AE65;AE65;1101 1161 11C0;AE65;1101 1161 11C0; # (ê¹¥; ê¹¥; á„ᅡᇀ; ê¹¥; á„ᅡᇀ; ) HANGUL SYLLABLE GGAT
+AE66;AE66;1101 1161 11C1;AE66;1101 1161 11C1; # (깦; 깦; á„á…¡á‡; 깦; á„á…¡á‡; ) HANGUL SYLLABLE GGAP
+AE67;AE67;1101 1161 11C2;AE67;1101 1161 11C2; # (깧; 깧; á„ᅡᇂ; 깧; á„ᅡᇂ; ) HANGUL SYLLABLE GGAH
+AE68;AE68;1101 1162;AE68;1101 1162; # (깨; 깨; á„á…¢; 깨; á„á…¢; ) HANGUL SYLLABLE GGAE
+AE69;AE69;1101 1162 11A8;AE69;1101 1162 11A8; # (깩; 깩; á„ᅢᆨ; 깩; á„ᅢᆨ; ) HANGUL SYLLABLE GGAEG
+AE6A;AE6A;1101 1162 11A9;AE6A;1101 1162 11A9; # (깪; 깪; á„ᅢᆩ; 깪; á„ᅢᆩ; ) HANGUL SYLLABLE GGAEGG
+AE6B;AE6B;1101 1162 11AA;AE6B;1101 1162 11AA; # (깫; 깫; á„ᅢᆪ; 깫; á„ᅢᆪ; ) HANGUL SYLLABLE GGAEGS
+AE6C;AE6C;1101 1162 11AB;AE6C;1101 1162 11AB; # (깬; 깬; á„ᅢᆫ; 깬; á„ᅢᆫ; ) HANGUL SYLLABLE GGAEN
+AE6D;AE6D;1101 1162 11AC;AE6D;1101 1162 11AC; # (ê¹­; ê¹­; á„ᅢᆬ; ê¹­; á„ᅢᆬ; ) HANGUL SYLLABLE GGAENJ
+AE6E;AE6E;1101 1162 11AD;AE6E;1101 1162 11AD; # (ê¹®; ê¹®; á„ᅢᆭ; ê¹®; á„ᅢᆭ; ) HANGUL SYLLABLE GGAENH
+AE6F;AE6F;1101 1162 11AE;AE6F;1101 1162 11AE; # (깯; 깯; á„ᅢᆮ; 깯; á„ᅢᆮ; ) HANGUL SYLLABLE GGAED
+AE70;AE70;1101 1162 11AF;AE70;1101 1162 11AF; # (ê¹°; ê¹°; á„ᅢᆯ; ê¹°; á„ᅢᆯ; ) HANGUL SYLLABLE GGAEL
+AE71;AE71;1101 1162 11B0;AE71;1101 1162 11B0; # (ê¹±; ê¹±; á„ᅢᆰ; ê¹±; á„ᅢᆰ; ) HANGUL SYLLABLE GGAELG
+AE72;AE72;1101 1162 11B1;AE72;1101 1162 11B1; # (ê¹²; ê¹²; á„ᅢᆱ; ê¹²; á„ᅢᆱ; ) HANGUL SYLLABLE GGAELM
+AE73;AE73;1101 1162 11B2;AE73;1101 1162 11B2; # (ê¹³; ê¹³; á„ᅢᆲ; ê¹³; á„ᅢᆲ; ) HANGUL SYLLABLE GGAELB
+AE74;AE74;1101 1162 11B3;AE74;1101 1162 11B3; # (ê¹´; ê¹´; á„ᅢᆳ; ê¹´; á„ᅢᆳ; ) HANGUL SYLLABLE GGAELS
+AE75;AE75;1101 1162 11B4;AE75;1101 1162 11B4; # (ê¹µ; ê¹µ; á„ᅢᆴ; ê¹µ; á„ᅢᆴ; ) HANGUL SYLLABLE GGAELT
+AE76;AE76;1101 1162 11B5;AE76;1101 1162 11B5; # (깶; 깶; á„ᅢᆵ; 깶; á„ᅢᆵ; ) HANGUL SYLLABLE GGAELP
+AE77;AE77;1101 1162 11B6;AE77;1101 1162 11B6; # (ê¹·; ê¹·; á„ᅢᆶ; ê¹·; á„ᅢᆶ; ) HANGUL SYLLABLE GGAELH
+AE78;AE78;1101 1162 11B7;AE78;1101 1162 11B7; # (깸; 깸; á„ᅢᆷ; 깸; á„ᅢᆷ; ) HANGUL SYLLABLE GGAEM
+AE79;AE79;1101 1162 11B8;AE79;1101 1162 11B8; # (ê¹¹; ê¹¹; á„ᅢᆸ; ê¹¹; á„ᅢᆸ; ) HANGUL SYLLABLE GGAEB
+AE7A;AE7A;1101 1162 11B9;AE7A;1101 1162 11B9; # (깺; 깺; á„ᅢᆹ; 깺; á„ᅢᆹ; ) HANGUL SYLLABLE GGAEBS
+AE7B;AE7B;1101 1162 11BA;AE7B;1101 1162 11BA; # (ê¹»; ê¹»; á„ᅢᆺ; ê¹»; á„ᅢᆺ; ) HANGUL SYLLABLE GGAES
+AE7C;AE7C;1101 1162 11BB;AE7C;1101 1162 11BB; # (ê¹¼; ê¹¼; á„ᅢᆻ; ê¹¼; á„ᅢᆻ; ) HANGUL SYLLABLE GGAESS
+AE7D;AE7D;1101 1162 11BC;AE7D;1101 1162 11BC; # (ê¹½; ê¹½; á„ᅢᆼ; ê¹½; á„ᅢᆼ; ) HANGUL SYLLABLE GGAENG
+AE7E;AE7E;1101 1162 11BD;AE7E;1101 1162 11BD; # (ê¹¾; ê¹¾; á„ᅢᆽ; ê¹¾; á„ᅢᆽ; ) HANGUL SYLLABLE GGAEJ
+AE7F;AE7F;1101 1162 11BE;AE7F;1101 1162 11BE; # (깿; 깿; á„ᅢᆾ; 깿; á„ᅢᆾ; ) HANGUL SYLLABLE GGAEC
+AE80;AE80;1101 1162 11BF;AE80;1101 1162 11BF; # (꺀; 꺀; á„ᅢᆿ; 꺀; á„ᅢᆿ; ) HANGUL SYLLABLE GGAEK
+AE81;AE81;1101 1162 11C0;AE81;1101 1162 11C0; # (êº; êº; á„ᅢᇀ; êº; á„ᅢᇀ; ) HANGUL SYLLABLE GGAET
+AE82;AE82;1101 1162 11C1;AE82;1101 1162 11C1; # (꺂; 꺂; á„á…¢á‡; 꺂; á„á…¢á‡; ) HANGUL SYLLABLE GGAEP
+AE83;AE83;1101 1162 11C2;AE83;1101 1162 11C2; # (꺃; 꺃; á„ᅢᇂ; 꺃; á„ᅢᇂ; ) HANGUL SYLLABLE GGAEH
+AE84;AE84;1101 1163;AE84;1101 1163; # (꺄; 꺄; á„á…£; 꺄; á„á…£; ) HANGUL SYLLABLE GGYA
+AE85;AE85;1101 1163 11A8;AE85;1101 1163 11A8; # (꺅; 꺅; á„ᅣᆨ; 꺅; á„ᅣᆨ; ) HANGUL SYLLABLE GGYAG
+AE86;AE86;1101 1163 11A9;AE86;1101 1163 11A9; # (꺆; 꺆; á„ᅣᆩ; 꺆; á„ᅣᆩ; ) HANGUL SYLLABLE GGYAGG
+AE87;AE87;1101 1163 11AA;AE87;1101 1163 11AA; # (꺇; 꺇; á„ᅣᆪ; 꺇; á„ᅣᆪ; ) HANGUL SYLLABLE GGYAGS
+AE88;AE88;1101 1163 11AB;AE88;1101 1163 11AB; # (꺈; 꺈; á„ᅣᆫ; 꺈; á„ᅣᆫ; ) HANGUL SYLLABLE GGYAN
+AE89;AE89;1101 1163 11AC;AE89;1101 1163 11AC; # (꺉; 꺉; á„ᅣᆬ; 꺉; á„ᅣᆬ; ) HANGUL SYLLABLE GGYANJ
+AE8A;AE8A;1101 1163 11AD;AE8A;1101 1163 11AD; # (꺊; 꺊; á„ᅣᆭ; 꺊; á„ᅣᆭ; ) HANGUL SYLLABLE GGYANH
+AE8B;AE8B;1101 1163 11AE;AE8B;1101 1163 11AE; # (꺋; 꺋; á„ᅣᆮ; 꺋; á„ᅣᆮ; ) HANGUL SYLLABLE GGYAD
+AE8C;AE8C;1101 1163 11AF;AE8C;1101 1163 11AF; # (꺌; 꺌; á„ᅣᆯ; 꺌; á„ᅣᆯ; ) HANGUL SYLLABLE GGYAL
+AE8D;AE8D;1101 1163 11B0;AE8D;1101 1163 11B0; # (êº; êº; á„ᅣᆰ; êº; á„ᅣᆰ; ) HANGUL SYLLABLE GGYALG
+AE8E;AE8E;1101 1163 11B1;AE8E;1101 1163 11B1; # (꺎; 꺎; á„ᅣᆱ; 꺎; á„ᅣᆱ; ) HANGUL SYLLABLE GGYALM
+AE8F;AE8F;1101 1163 11B2;AE8F;1101 1163 11B2; # (êº; êº; á„ᅣᆲ; êº; á„ᅣᆲ; ) HANGUL SYLLABLE GGYALB
+AE90;AE90;1101 1163 11B3;AE90;1101 1163 11B3; # (êº; êº; á„ᅣᆳ; êº; á„ᅣᆳ; ) HANGUL SYLLABLE GGYALS
+AE91;AE91;1101 1163 11B4;AE91;1101 1163 11B4; # (꺑; 꺑; á„ᅣᆴ; 꺑; á„ᅣᆴ; ) HANGUL SYLLABLE GGYALT
+AE92;AE92;1101 1163 11B5;AE92;1101 1163 11B5; # (꺒; 꺒; á„ᅣᆵ; 꺒; á„ᅣᆵ; ) HANGUL SYLLABLE GGYALP
+AE93;AE93;1101 1163 11B6;AE93;1101 1163 11B6; # (꺓; 꺓; á„ᅣᆶ; 꺓; á„ᅣᆶ; ) HANGUL SYLLABLE GGYALH
+AE94;AE94;1101 1163 11B7;AE94;1101 1163 11B7; # (꺔; 꺔; á„ᅣᆷ; 꺔; á„ᅣᆷ; ) HANGUL SYLLABLE GGYAM
+AE95;AE95;1101 1163 11B8;AE95;1101 1163 11B8; # (꺕; 꺕; á„ᅣᆸ; 꺕; á„ᅣᆸ; ) HANGUL SYLLABLE GGYAB
+AE96;AE96;1101 1163 11B9;AE96;1101 1163 11B9; # (꺖; 꺖; á„ᅣᆹ; 꺖; á„ᅣᆹ; ) HANGUL SYLLABLE GGYABS
+AE97;AE97;1101 1163 11BA;AE97;1101 1163 11BA; # (꺗; 꺗; á„ᅣᆺ; 꺗; á„ᅣᆺ; ) HANGUL SYLLABLE GGYAS
+AE98;AE98;1101 1163 11BB;AE98;1101 1163 11BB; # (꺘; 꺘; á„ᅣᆻ; 꺘; á„ᅣᆻ; ) HANGUL SYLLABLE GGYASS
+AE99;AE99;1101 1163 11BC;AE99;1101 1163 11BC; # (꺙; 꺙; á„ᅣᆼ; 꺙; á„ᅣᆼ; ) HANGUL SYLLABLE GGYANG
+AE9A;AE9A;1101 1163 11BD;AE9A;1101 1163 11BD; # (꺚; 꺚; á„ᅣᆽ; 꺚; á„ᅣᆽ; ) HANGUL SYLLABLE GGYAJ
+AE9B;AE9B;1101 1163 11BE;AE9B;1101 1163 11BE; # (꺛; 꺛; á„ᅣᆾ; 꺛; á„ᅣᆾ; ) HANGUL SYLLABLE GGYAC
+AE9C;AE9C;1101 1163 11BF;AE9C;1101 1163 11BF; # (꺜; 꺜; á„ᅣᆿ; 꺜; á„ᅣᆿ; ) HANGUL SYLLABLE GGYAK
+AE9D;AE9D;1101 1163 11C0;AE9D;1101 1163 11C0; # (êº; êº; á„ᅣᇀ; êº; á„ᅣᇀ; ) HANGUL SYLLABLE GGYAT
+AE9E;AE9E;1101 1163 11C1;AE9E;1101 1163 11C1; # (꺞; 꺞; á„á…£á‡; 꺞; á„á…£á‡; ) HANGUL SYLLABLE GGYAP
+AE9F;AE9F;1101 1163 11C2;AE9F;1101 1163 11C2; # (꺟; 꺟; á„ᅣᇂ; 꺟; á„ᅣᇂ; ) HANGUL SYLLABLE GGYAH
+AEA0;AEA0;1101 1164;AEA0;1101 1164; # (꺠; 꺠; á„á…¤; 꺠; á„á…¤; ) HANGUL SYLLABLE GGYAE
+AEA1;AEA1;1101 1164 11A8;AEA1;1101 1164 11A8; # (꺡; 꺡; á„ᅤᆨ; 꺡; á„ᅤᆨ; ) HANGUL SYLLABLE GGYAEG
+AEA2;AEA2;1101 1164 11A9;AEA2;1101 1164 11A9; # (꺢; 꺢; á„ᅤᆩ; 꺢; á„ᅤᆩ; ) HANGUL SYLLABLE GGYAEGG
+AEA3;AEA3;1101 1164 11AA;AEA3;1101 1164 11AA; # (꺣; 꺣; á„ᅤᆪ; 꺣; á„ᅤᆪ; ) HANGUL SYLLABLE GGYAEGS
+AEA4;AEA4;1101 1164 11AB;AEA4;1101 1164 11AB; # (꺤; 꺤; á„ᅤᆫ; 꺤; á„ᅤᆫ; ) HANGUL SYLLABLE GGYAEN
+AEA5;AEA5;1101 1164 11AC;AEA5;1101 1164 11AC; # (꺥; 꺥; á„ᅤᆬ; 꺥; á„ᅤᆬ; ) HANGUL SYLLABLE GGYAENJ
+AEA6;AEA6;1101 1164 11AD;AEA6;1101 1164 11AD; # (꺦; 꺦; á„ᅤᆭ; 꺦; á„ᅤᆭ; ) HANGUL SYLLABLE GGYAENH
+AEA7;AEA7;1101 1164 11AE;AEA7;1101 1164 11AE; # (꺧; 꺧; á„ᅤᆮ; 꺧; á„ᅤᆮ; ) HANGUL SYLLABLE GGYAED
+AEA8;AEA8;1101 1164 11AF;AEA8;1101 1164 11AF; # (꺨; 꺨; á„ᅤᆯ; 꺨; á„ᅤᆯ; ) HANGUL SYLLABLE GGYAEL
+AEA9;AEA9;1101 1164 11B0;AEA9;1101 1164 11B0; # (꺩; 꺩; á„ᅤᆰ; 꺩; á„ᅤᆰ; ) HANGUL SYLLABLE GGYAELG
+AEAA;AEAA;1101 1164 11B1;AEAA;1101 1164 11B1; # (꺪; 꺪; á„ᅤᆱ; 꺪; á„ᅤᆱ; ) HANGUL SYLLABLE GGYAELM
+AEAB;AEAB;1101 1164 11B2;AEAB;1101 1164 11B2; # (꺫; 꺫; á„ᅤᆲ; 꺫; á„ᅤᆲ; ) HANGUL SYLLABLE GGYAELB
+AEAC;AEAC;1101 1164 11B3;AEAC;1101 1164 11B3; # (꺬; 꺬; á„ᅤᆳ; 꺬; á„ᅤᆳ; ) HANGUL SYLLABLE GGYAELS
+AEAD;AEAD;1101 1164 11B4;AEAD;1101 1164 11B4; # (꺭; 꺭; á„ᅤᆴ; 꺭; á„ᅤᆴ; ) HANGUL SYLLABLE GGYAELT
+AEAE;AEAE;1101 1164 11B5;AEAE;1101 1164 11B5; # (꺮; 꺮; á„ᅤᆵ; 꺮; á„ᅤᆵ; ) HANGUL SYLLABLE GGYAELP
+AEAF;AEAF;1101 1164 11B6;AEAF;1101 1164 11B6; # (꺯; 꺯; á„ᅤᆶ; 꺯; á„ᅤᆶ; ) HANGUL SYLLABLE GGYAELH
+AEB0;AEB0;1101 1164 11B7;AEB0;1101 1164 11B7; # (꺰; 꺰; á„ᅤᆷ; 꺰; á„ᅤᆷ; ) HANGUL SYLLABLE GGYAEM
+AEB1;AEB1;1101 1164 11B8;AEB1;1101 1164 11B8; # (꺱; 꺱; á„ᅤᆸ; 꺱; á„ᅤᆸ; ) HANGUL SYLLABLE GGYAEB
+AEB2;AEB2;1101 1164 11B9;AEB2;1101 1164 11B9; # (꺲; 꺲; á„ᅤᆹ; 꺲; á„ᅤᆹ; ) HANGUL SYLLABLE GGYAEBS
+AEB3;AEB3;1101 1164 11BA;AEB3;1101 1164 11BA; # (꺳; 꺳; á„ᅤᆺ; 꺳; á„ᅤᆺ; ) HANGUL SYLLABLE GGYAES
+AEB4;AEB4;1101 1164 11BB;AEB4;1101 1164 11BB; # (꺴; 꺴; á„ᅤᆻ; 꺴; á„ᅤᆻ; ) HANGUL SYLLABLE GGYAESS
+AEB5;AEB5;1101 1164 11BC;AEB5;1101 1164 11BC; # (꺵; 꺵; á„ᅤᆼ; 꺵; á„ᅤᆼ; ) HANGUL SYLLABLE GGYAENG
+AEB6;AEB6;1101 1164 11BD;AEB6;1101 1164 11BD; # (꺶; 꺶; á„ᅤᆽ; 꺶; á„ᅤᆽ; ) HANGUL SYLLABLE GGYAEJ
+AEB7;AEB7;1101 1164 11BE;AEB7;1101 1164 11BE; # (꺷; 꺷; á„ᅤᆾ; 꺷; á„ᅤᆾ; ) HANGUL SYLLABLE GGYAEC
+AEB8;AEB8;1101 1164 11BF;AEB8;1101 1164 11BF; # (꺸; 꺸; á„ᅤᆿ; 꺸; á„ᅤᆿ; ) HANGUL SYLLABLE GGYAEK
+AEB9;AEB9;1101 1164 11C0;AEB9;1101 1164 11C0; # (꺹; 꺹; á„ᅤᇀ; 꺹; á„ᅤᇀ; ) HANGUL SYLLABLE GGYAET
+AEBA;AEBA;1101 1164 11C1;AEBA;1101 1164 11C1; # (꺺; 꺺; á„á…¤á‡; 꺺; á„á…¤á‡; ) HANGUL SYLLABLE GGYAEP
+AEBB;AEBB;1101 1164 11C2;AEBB;1101 1164 11C2; # (꺻; 꺻; á„ᅤᇂ; 꺻; á„ᅤᇂ; ) HANGUL SYLLABLE GGYAEH
+AEBC;AEBC;1101 1165;AEBC;1101 1165; # (꺼; 꺼; á„á…¥; 꺼; á„á…¥; ) HANGUL SYLLABLE GGEO
+AEBD;AEBD;1101 1165 11A8;AEBD;1101 1165 11A8; # (꺽; 꺽; á„ᅥᆨ; 꺽; á„ᅥᆨ; ) HANGUL SYLLABLE GGEOG
+AEBE;AEBE;1101 1165 11A9;AEBE;1101 1165 11A9; # (꺾; 꺾; á„ᅥᆩ; 꺾; á„ᅥᆩ; ) HANGUL SYLLABLE GGEOGG
+AEBF;AEBF;1101 1165 11AA;AEBF;1101 1165 11AA; # (꺿; 꺿; á„ᅥᆪ; 꺿; á„ᅥᆪ; ) HANGUL SYLLABLE GGEOGS
+AEC0;AEC0;1101 1165 11AB;AEC0;1101 1165 11AB; # (껀; 껀; á„ᅥᆫ; 껀; á„ᅥᆫ; ) HANGUL SYLLABLE GGEON
+AEC1;AEC1;1101 1165 11AC;AEC1;1101 1165 11AC; # (ê»; ê»; á„ᅥᆬ; ê»; á„ᅥᆬ; ) HANGUL SYLLABLE GGEONJ
+AEC2;AEC2;1101 1165 11AD;AEC2;1101 1165 11AD; # (껂; 껂; á„ᅥᆭ; 껂; á„ᅥᆭ; ) HANGUL SYLLABLE GGEONH
+AEC3;AEC3;1101 1165 11AE;AEC3;1101 1165 11AE; # (껃; 껃; á„ᅥᆮ; 껃; á„ᅥᆮ; ) HANGUL SYLLABLE GGEOD
+AEC4;AEC4;1101 1165 11AF;AEC4;1101 1165 11AF; # (껄; 껄; á„ᅥᆯ; 껄; á„ᅥᆯ; ) HANGUL SYLLABLE GGEOL
+AEC5;AEC5;1101 1165 11B0;AEC5;1101 1165 11B0; # (ê»…; ê»…; á„ᅥᆰ; ê»…; á„ᅥᆰ; ) HANGUL SYLLABLE GGEOLG
+AEC6;AEC6;1101 1165 11B1;AEC6;1101 1165 11B1; # (껆; 껆; á„ᅥᆱ; 껆; á„ᅥᆱ; ) HANGUL SYLLABLE GGEOLM
+AEC7;AEC7;1101 1165 11B2;AEC7;1101 1165 11B2; # (껇; 껇; á„ᅥᆲ; 껇; á„ᅥᆲ; ) HANGUL SYLLABLE GGEOLB
+AEC8;AEC8;1101 1165 11B3;AEC8;1101 1165 11B3; # (껈; 껈; á„ᅥᆳ; 껈; á„ᅥᆳ; ) HANGUL SYLLABLE GGEOLS
+AEC9;AEC9;1101 1165 11B4;AEC9;1101 1165 11B4; # (껉; 껉; á„ᅥᆴ; 껉; á„ᅥᆴ; ) HANGUL SYLLABLE GGEOLT
+AECA;AECA;1101 1165 11B5;AECA;1101 1165 11B5; # (껊; 껊; á„ᅥᆵ; 껊; á„ᅥᆵ; ) HANGUL SYLLABLE GGEOLP
+AECB;AECB;1101 1165 11B6;AECB;1101 1165 11B6; # (껋; 껋; á„ᅥᆶ; 껋; á„ᅥᆶ; ) HANGUL SYLLABLE GGEOLH
+AECC;AECC;1101 1165 11B7;AECC;1101 1165 11B7; # (껌; 껌; á„ᅥᆷ; 껌; á„ᅥᆷ; ) HANGUL SYLLABLE GGEOM
+AECD;AECD;1101 1165 11B8;AECD;1101 1165 11B8; # (ê»; ê»; á„ᅥᆸ; ê»; á„ᅥᆸ; ) HANGUL SYLLABLE GGEOB
+AECE;AECE;1101 1165 11B9;AECE;1101 1165 11B9; # (껎; 껎; á„ᅥᆹ; 껎; á„ᅥᆹ; ) HANGUL SYLLABLE GGEOBS
+AECF;AECF;1101 1165 11BA;AECF;1101 1165 11BA; # (ê»; ê»; á„ᅥᆺ; ê»; á„ᅥᆺ; ) HANGUL SYLLABLE GGEOS
+AED0;AED0;1101 1165 11BB;AED0;1101 1165 11BB; # (ê»; ê»; á„ᅥᆻ; ê»; á„ᅥᆻ; ) HANGUL SYLLABLE GGEOSS
+AED1;AED1;1101 1165 11BC;AED1;1101 1165 11BC; # (껑; 껑; á„ᅥᆼ; 껑; á„ᅥᆼ; ) HANGUL SYLLABLE GGEONG
+AED2;AED2;1101 1165 11BD;AED2;1101 1165 11BD; # (ê»’; ê»’; á„ᅥᆽ; ê»’; á„ᅥᆽ; ) HANGUL SYLLABLE GGEOJ
+AED3;AED3;1101 1165 11BE;AED3;1101 1165 11BE; # (껓; 껓; á„ᅥᆾ; 껓; á„ᅥᆾ; ) HANGUL SYLLABLE GGEOC
+AED4;AED4;1101 1165 11BF;AED4;1101 1165 11BF; # (ê»”; ê»”; á„ᅥᆿ; ê»”; á„ᅥᆿ; ) HANGUL SYLLABLE GGEOK
+AED5;AED5;1101 1165 11C0;AED5;1101 1165 11C0; # (껕; 껕; á„ᅥᇀ; 껕; á„ᅥᇀ; ) HANGUL SYLLABLE GGEOT
+AED6;AED6;1101 1165 11C1;AED6;1101 1165 11C1; # (ê»–; ê»–; á„á…¥á‡; ê»–; á„á…¥á‡; ) HANGUL SYLLABLE GGEOP
+AED7;AED7;1101 1165 11C2;AED7;1101 1165 11C2; # (ê»—; ê»—; á„ᅥᇂ; ê»—; á„ᅥᇂ; ) HANGUL SYLLABLE GGEOH
+AED8;AED8;1101 1166;AED8;1101 1166; # (께; 께; á„á…¦; 께; á„á…¦; ) HANGUL SYLLABLE GGE
+AED9;AED9;1101 1166 11A8;AED9;1101 1166 11A8; # (ê»™; ê»™; á„ᅦᆨ; ê»™; á„ᅦᆨ; ) HANGUL SYLLABLE GGEG
+AEDA;AEDA;1101 1166 11A9;AEDA;1101 1166 11A9; # (껚; 껚; á„ᅦᆩ; 껚; á„ᅦᆩ; ) HANGUL SYLLABLE GGEGG
+AEDB;AEDB;1101 1166 11AA;AEDB;1101 1166 11AA; # (ê»›; ê»›; á„ᅦᆪ; ê»›; á„ᅦᆪ; ) HANGUL SYLLABLE GGEGS
+AEDC;AEDC;1101 1166 11AB;AEDC;1101 1166 11AB; # (껜; 껜; á„ᅦᆫ; 껜; á„ᅦᆫ; ) HANGUL SYLLABLE GGEN
+AEDD;AEDD;1101 1166 11AC;AEDD;1101 1166 11AC; # (ê»; ê»; á„ᅦᆬ; ê»; á„ᅦᆬ; ) HANGUL SYLLABLE GGENJ
+AEDE;AEDE;1101 1166 11AD;AEDE;1101 1166 11AD; # (껞; 껞; á„ᅦᆭ; 껞; á„ᅦᆭ; ) HANGUL SYLLABLE GGENH
+AEDF;AEDF;1101 1166 11AE;AEDF;1101 1166 11AE; # (껟; 껟; á„ᅦᆮ; 껟; á„ᅦᆮ; ) HANGUL SYLLABLE GGED
+AEE0;AEE0;1101 1166 11AF;AEE0;1101 1166 11AF; # (ê» ; ê» ; á„ᅦᆯ; ê» ; á„ᅦᆯ; ) HANGUL SYLLABLE GGEL
+AEE1;AEE1;1101 1166 11B0;AEE1;1101 1166 11B0; # (껡; 껡; á„ᅦᆰ; 껡; á„ᅦᆰ; ) HANGUL SYLLABLE GGELG
+AEE2;AEE2;1101 1166 11B1;AEE2;1101 1166 11B1; # (껢; 껢; á„ᅦᆱ; 껢; á„ᅦᆱ; ) HANGUL SYLLABLE GGELM
+AEE3;AEE3;1101 1166 11B2;AEE3;1101 1166 11B2; # (껣; 껣; á„ᅦᆲ; 껣; á„ᅦᆲ; ) HANGUL SYLLABLE GGELB
+AEE4;AEE4;1101 1166 11B3;AEE4;1101 1166 11B3; # (껤; 껤; á„ᅦᆳ; 껤; á„ᅦᆳ; ) HANGUL SYLLABLE GGELS
+AEE5;AEE5;1101 1166 11B4;AEE5;1101 1166 11B4; # (껥; 껥; á„ᅦᆴ; 껥; á„ᅦᆴ; ) HANGUL SYLLABLE GGELT
+AEE6;AEE6;1101 1166 11B5;AEE6;1101 1166 11B5; # (껦; 껦; á„ᅦᆵ; 껦; á„ᅦᆵ; ) HANGUL SYLLABLE GGELP
+AEE7;AEE7;1101 1166 11B6;AEE7;1101 1166 11B6; # (껧; 껧; á„ᅦᆶ; 껧; á„ᅦᆶ; ) HANGUL SYLLABLE GGELH
+AEE8;AEE8;1101 1166 11B7;AEE8;1101 1166 11B7; # (껨; 껨; á„ᅦᆷ; 껨; á„ᅦᆷ; ) HANGUL SYLLABLE GGEM
+AEE9;AEE9;1101 1166 11B8;AEE9;1101 1166 11B8; # (껩; 껩; á„ᅦᆸ; 껩; á„ᅦᆸ; ) HANGUL SYLLABLE GGEB
+AEEA;AEEA;1101 1166 11B9;AEEA;1101 1166 11B9; # (껪; 껪; á„ᅦᆹ; 껪; á„ᅦᆹ; ) HANGUL SYLLABLE GGEBS
+AEEB;AEEB;1101 1166 11BA;AEEB;1101 1166 11BA; # (껫; 껫; á„ᅦᆺ; 껫; á„ᅦᆺ; ) HANGUL SYLLABLE GGES
+AEEC;AEEC;1101 1166 11BB;AEEC;1101 1166 11BB; # (껬; 껬; á„ᅦᆻ; 껬; á„ᅦᆻ; ) HANGUL SYLLABLE GGESS
+AEED;AEED;1101 1166 11BC;AEED;1101 1166 11BC; # (ê»­; ê»­; á„ᅦᆼ; ê»­; á„ᅦᆼ; ) HANGUL SYLLABLE GGENG
+AEEE;AEEE;1101 1166 11BD;AEEE;1101 1166 11BD; # (ê»®; ê»®; á„ᅦᆽ; ê»®; á„ᅦᆽ; ) HANGUL SYLLABLE GGEJ
+AEEF;AEEF;1101 1166 11BE;AEEF;1101 1166 11BE; # (껯; 껯; á„ᅦᆾ; 껯; á„ᅦᆾ; ) HANGUL SYLLABLE GGEC
+AEF0;AEF0;1101 1166 11BF;AEF0;1101 1166 11BF; # (ê»°; ê»°; á„ᅦᆿ; ê»°; á„ᅦᆿ; ) HANGUL SYLLABLE GGEK
+AEF1;AEF1;1101 1166 11C0;AEF1;1101 1166 11C0; # (ê»±; ê»±; á„ᅦᇀ; ê»±; á„ᅦᇀ; ) HANGUL SYLLABLE GGET
+AEF2;AEF2;1101 1166 11C1;AEF2;1101 1166 11C1; # (껲; 껲; á„á…¦á‡; 껲; á„á…¦á‡; ) HANGUL SYLLABLE GGEP
+AEF3;AEF3;1101 1166 11C2;AEF3;1101 1166 11C2; # (껳; 껳; á„ᅦᇂ; 껳; á„ᅦᇂ; ) HANGUL SYLLABLE GGEH
+AEF4;AEF4;1101 1167;AEF4;1101 1167; # (ê»´; ê»´; á„á…§; ê»´; á„á…§; ) HANGUL SYLLABLE GGYEO
+AEF5;AEF5;1101 1167 11A8;AEF5;1101 1167 11A8; # (껵; 껵; á„ᅧᆨ; 껵; á„ᅧᆨ; ) HANGUL SYLLABLE GGYEOG
+AEF6;AEF6;1101 1167 11A9;AEF6;1101 1167 11A9; # (껶; 껶; á„ᅧᆩ; 껶; á„ᅧᆩ; ) HANGUL SYLLABLE GGYEOGG
+AEF7;AEF7;1101 1167 11AA;AEF7;1101 1167 11AA; # (ê»·; ê»·; á„ᅧᆪ; ê»·; á„ᅧᆪ; ) HANGUL SYLLABLE GGYEOGS
+AEF8;AEF8;1101 1167 11AB;AEF8;1101 1167 11AB; # (껸; 껸; á„ᅧᆫ; 껸; á„ᅧᆫ; ) HANGUL SYLLABLE GGYEON
+AEF9;AEF9;1101 1167 11AC;AEF9;1101 1167 11AC; # (껹; 껹; á„ᅧᆬ; 껹; á„ᅧᆬ; ) HANGUL SYLLABLE GGYEONJ
+AEFA;AEFA;1101 1167 11AD;AEFA;1101 1167 11AD; # (껺; 껺; á„ᅧᆭ; 껺; á„ᅧᆭ; ) HANGUL SYLLABLE GGYEONH
+AEFB;AEFB;1101 1167 11AE;AEFB;1101 1167 11AE; # (ê»»; ê»»; á„ᅧᆮ; ê»»; á„ᅧᆮ; ) HANGUL SYLLABLE GGYEOD
+AEFC;AEFC;1101 1167 11AF;AEFC;1101 1167 11AF; # (껼; 껼; á„ᅧᆯ; 껼; á„ᅧᆯ; ) HANGUL SYLLABLE GGYEOL
+AEFD;AEFD;1101 1167 11B0;AEFD;1101 1167 11B0; # (껽; 껽; á„ᅧᆰ; 껽; á„ᅧᆰ; ) HANGUL SYLLABLE GGYEOLG
+AEFE;AEFE;1101 1167 11B1;AEFE;1101 1167 11B1; # (껾; 껾; á„ᅧᆱ; 껾; á„ᅧᆱ; ) HANGUL SYLLABLE GGYEOLM
+AEFF;AEFF;1101 1167 11B2;AEFF;1101 1167 11B2; # (껿; 껿; á„ᅧᆲ; 껿; á„ᅧᆲ; ) HANGUL SYLLABLE GGYEOLB
+AF00;AF00;1101 1167 11B3;AF00;1101 1167 11B3; # (ê¼€; ê¼€; á„ᅧᆳ; ê¼€; á„ᅧᆳ; ) HANGUL SYLLABLE GGYEOLS
+AF01;AF01;1101 1167 11B4;AF01;1101 1167 11B4; # (ê¼; ê¼; á„ᅧᆴ; ê¼; á„ᅧᆴ; ) HANGUL SYLLABLE GGYEOLT
+AF02;AF02;1101 1167 11B5;AF02;1101 1167 11B5; # (꼂; 꼂; á„ᅧᆵ; 꼂; á„ᅧᆵ; ) HANGUL SYLLABLE GGYEOLP
+AF03;AF03;1101 1167 11B6;AF03;1101 1167 11B6; # (꼃; 꼃; á„ᅧᆶ; 꼃; á„ᅧᆶ; ) HANGUL SYLLABLE GGYEOLH
+AF04;AF04;1101 1167 11B7;AF04;1101 1167 11B7; # (꼄; 꼄; á„ᅧᆷ; 꼄; á„ᅧᆷ; ) HANGUL SYLLABLE GGYEOM
+AF05;AF05;1101 1167 11B8;AF05;1101 1167 11B8; # (ê¼…; ê¼…; á„ᅧᆸ; ê¼…; á„ᅧᆸ; ) HANGUL SYLLABLE GGYEOB
+AF06;AF06;1101 1167 11B9;AF06;1101 1167 11B9; # (꼆; 꼆; á„ᅧᆹ; 꼆; á„ᅧᆹ; ) HANGUL SYLLABLE GGYEOBS
+AF07;AF07;1101 1167 11BA;AF07;1101 1167 11BA; # (꼇; 꼇; á„ᅧᆺ; 꼇; á„ᅧᆺ; ) HANGUL SYLLABLE GGYEOS
+AF08;AF08;1101 1167 11BB;AF08;1101 1167 11BB; # (꼈; 꼈; á„ᅧᆻ; 꼈; á„ᅧᆻ; ) HANGUL SYLLABLE GGYEOSS
+AF09;AF09;1101 1167 11BC;AF09;1101 1167 11BC; # (꼉; 꼉; á„ᅧᆼ; 꼉; á„ᅧᆼ; ) HANGUL SYLLABLE GGYEONG
+AF0A;AF0A;1101 1167 11BD;AF0A;1101 1167 11BD; # (꼊; 꼊; á„ᅧᆽ; 꼊; á„ᅧᆽ; ) HANGUL SYLLABLE GGYEOJ
+AF0B;AF0B;1101 1167 11BE;AF0B;1101 1167 11BE; # (꼋; 꼋; á„ᅧᆾ; 꼋; á„ᅧᆾ; ) HANGUL SYLLABLE GGYEOC
+AF0C;AF0C;1101 1167 11BF;AF0C;1101 1167 11BF; # (꼌; 꼌; á„ᅧᆿ; 꼌; á„ᅧᆿ; ) HANGUL SYLLABLE GGYEOK
+AF0D;AF0D;1101 1167 11C0;AF0D;1101 1167 11C0; # (ê¼; ê¼; á„ᅧᇀ; ê¼; á„ᅧᇀ; ) HANGUL SYLLABLE GGYEOT
+AF0E;AF0E;1101 1167 11C1;AF0E;1101 1167 11C1; # (꼎; 꼎; á„á…§á‡; 꼎; á„á…§á‡; ) HANGUL SYLLABLE GGYEOP
+AF0F;AF0F;1101 1167 11C2;AF0F;1101 1167 11C2; # (ê¼; ê¼; á„ᅧᇂ; ê¼; á„ᅧᇂ; ) HANGUL SYLLABLE GGYEOH
+AF10;AF10;1101 1168;AF10;1101 1168; # (ê¼; ê¼; á„á…¨; ê¼; á„á…¨; ) HANGUL SYLLABLE GGYE
+AF11;AF11;1101 1168 11A8;AF11;1101 1168 11A8; # (꼑; 꼑; á„ᅨᆨ; 꼑; á„ᅨᆨ; ) HANGUL SYLLABLE GGYEG
+AF12;AF12;1101 1168 11A9;AF12;1101 1168 11A9; # (ê¼’; ê¼’; á„ᅨᆩ; ê¼’; á„ᅨᆩ; ) HANGUL SYLLABLE GGYEGG
+AF13;AF13;1101 1168 11AA;AF13;1101 1168 11AA; # (꼓; 꼓; á„ᅨᆪ; 꼓; á„ᅨᆪ; ) HANGUL SYLLABLE GGYEGS
+AF14;AF14;1101 1168 11AB;AF14;1101 1168 11AB; # (ê¼”; ê¼”; á„ᅨᆫ; ê¼”; á„ᅨᆫ; ) HANGUL SYLLABLE GGYEN
+AF15;AF15;1101 1168 11AC;AF15;1101 1168 11AC; # (꼕; 꼕; á„ᅨᆬ; 꼕; á„ᅨᆬ; ) HANGUL SYLLABLE GGYENJ
+AF16;AF16;1101 1168 11AD;AF16;1101 1168 11AD; # (ê¼–; ê¼–; á„ᅨᆭ; ê¼–; á„ᅨᆭ; ) HANGUL SYLLABLE GGYENH
+AF17;AF17;1101 1168 11AE;AF17;1101 1168 11AE; # (ê¼—; ê¼—; á„ᅨᆮ; ê¼—; á„ᅨᆮ; ) HANGUL SYLLABLE GGYED
+AF18;AF18;1101 1168 11AF;AF18;1101 1168 11AF; # (꼘; 꼘; á„ᅨᆯ; 꼘; á„ᅨᆯ; ) HANGUL SYLLABLE GGYEL
+AF19;AF19;1101 1168 11B0;AF19;1101 1168 11B0; # (ê¼™; ê¼™; á„ᅨᆰ; ê¼™; á„ᅨᆰ; ) HANGUL SYLLABLE GGYELG
+AF1A;AF1A;1101 1168 11B1;AF1A;1101 1168 11B1; # (꼚; 꼚; á„ᅨᆱ; 꼚; á„ᅨᆱ; ) HANGUL SYLLABLE GGYELM
+AF1B;AF1B;1101 1168 11B2;AF1B;1101 1168 11B2; # (ê¼›; ê¼›; á„ᅨᆲ; ê¼›; á„ᅨᆲ; ) HANGUL SYLLABLE GGYELB
+AF1C;AF1C;1101 1168 11B3;AF1C;1101 1168 11B3; # (꼜; 꼜; á„ᅨᆳ; 꼜; á„ᅨᆳ; ) HANGUL SYLLABLE GGYELS
+AF1D;AF1D;1101 1168 11B4;AF1D;1101 1168 11B4; # (ê¼; ê¼; á„ᅨᆴ; ê¼; á„ᅨᆴ; ) HANGUL SYLLABLE GGYELT
+AF1E;AF1E;1101 1168 11B5;AF1E;1101 1168 11B5; # (꼞; 꼞; á„ᅨᆵ; 꼞; á„ᅨᆵ; ) HANGUL SYLLABLE GGYELP
+AF1F;AF1F;1101 1168 11B6;AF1F;1101 1168 11B6; # (꼟; 꼟; á„ᅨᆶ; 꼟; á„ᅨᆶ; ) HANGUL SYLLABLE GGYELH
+AF20;AF20;1101 1168 11B7;AF20;1101 1168 11B7; # (ê¼ ; ê¼ ; á„ᅨᆷ; ê¼ ; á„ᅨᆷ; ) HANGUL SYLLABLE GGYEM
+AF21;AF21;1101 1168 11B8;AF21;1101 1168 11B8; # (꼡; 꼡; á„ᅨᆸ; 꼡; á„ᅨᆸ; ) HANGUL SYLLABLE GGYEB
+AF22;AF22;1101 1168 11B9;AF22;1101 1168 11B9; # (ê¼¢; ê¼¢; á„ᅨᆹ; ê¼¢; á„ᅨᆹ; ) HANGUL SYLLABLE GGYEBS
+AF23;AF23;1101 1168 11BA;AF23;1101 1168 11BA; # (ê¼£; ê¼£; á„ᅨᆺ; ê¼£; á„ᅨᆺ; ) HANGUL SYLLABLE GGYES
+AF24;AF24;1101 1168 11BB;AF24;1101 1168 11BB; # (꼤; 꼤; á„ᅨᆻ; 꼤; á„ᅨᆻ; ) HANGUL SYLLABLE GGYESS
+AF25;AF25;1101 1168 11BC;AF25;1101 1168 11BC; # (ê¼¥; ê¼¥; á„ᅨᆼ; ê¼¥; á„ᅨᆼ; ) HANGUL SYLLABLE GGYENG
+AF26;AF26;1101 1168 11BD;AF26;1101 1168 11BD; # (꼦; 꼦; á„ᅨᆽ; 꼦; á„ᅨᆽ; ) HANGUL SYLLABLE GGYEJ
+AF27;AF27;1101 1168 11BE;AF27;1101 1168 11BE; # (꼧; 꼧; á„ᅨᆾ; 꼧; á„ᅨᆾ; ) HANGUL SYLLABLE GGYEC
+AF28;AF28;1101 1168 11BF;AF28;1101 1168 11BF; # (꼨; 꼨; á„ᅨᆿ; 꼨; á„ᅨᆿ; ) HANGUL SYLLABLE GGYEK
+AF29;AF29;1101 1168 11C0;AF29;1101 1168 11C0; # (꼩; 꼩; á„ᅨᇀ; 꼩; á„ᅨᇀ; ) HANGUL SYLLABLE GGYET
+AF2A;AF2A;1101 1168 11C1;AF2A;1101 1168 11C1; # (꼪; 꼪; á„á…¨á‡; 꼪; á„á…¨á‡; ) HANGUL SYLLABLE GGYEP
+AF2B;AF2B;1101 1168 11C2;AF2B;1101 1168 11C2; # (꼫; 꼫; á„ᅨᇂ; 꼫; á„ᅨᇂ; ) HANGUL SYLLABLE GGYEH
+AF2C;AF2C;1101 1169;AF2C;1101 1169; # (꼬; 꼬; á„á…©; 꼬; á„á…©; ) HANGUL SYLLABLE GGO
+AF2D;AF2D;1101 1169 11A8;AF2D;1101 1169 11A8; # (ê¼­; ê¼­; á„ᅩᆨ; ê¼­; á„ᅩᆨ; ) HANGUL SYLLABLE GGOG
+AF2E;AF2E;1101 1169 11A9;AF2E;1101 1169 11A9; # (ê¼®; ê¼®; á„ᅩᆩ; ê¼®; á„ᅩᆩ; ) HANGUL SYLLABLE GGOGG
+AF2F;AF2F;1101 1169 11AA;AF2F;1101 1169 11AA; # (꼯; 꼯; á„ᅩᆪ; 꼯; á„ᅩᆪ; ) HANGUL SYLLABLE GGOGS
+AF30;AF30;1101 1169 11AB;AF30;1101 1169 11AB; # (ê¼°; ê¼°; á„ᅩᆫ; ê¼°; á„ᅩᆫ; ) HANGUL SYLLABLE GGON
+AF31;AF31;1101 1169 11AC;AF31;1101 1169 11AC; # (ê¼±; ê¼±; á„ᅩᆬ; ê¼±; á„ᅩᆬ; ) HANGUL SYLLABLE GGONJ
+AF32;AF32;1101 1169 11AD;AF32;1101 1169 11AD; # (ê¼²; ê¼²; á„ᅩᆭ; ê¼²; á„ᅩᆭ; ) HANGUL SYLLABLE GGONH
+AF33;AF33;1101 1169 11AE;AF33;1101 1169 11AE; # (ê¼³; ê¼³; á„ᅩᆮ; ê¼³; á„ᅩᆮ; ) HANGUL SYLLABLE GGOD
+AF34;AF34;1101 1169 11AF;AF34;1101 1169 11AF; # (ê¼´; ê¼´; á„ᅩᆯ; ê¼´; á„ᅩᆯ; ) HANGUL SYLLABLE GGOL
+AF35;AF35;1101 1169 11B0;AF35;1101 1169 11B0; # (ê¼µ; ê¼µ; á„ᅩᆰ; ê¼µ; á„ᅩᆰ; ) HANGUL SYLLABLE GGOLG
+AF36;AF36;1101 1169 11B1;AF36;1101 1169 11B1; # (꼶; 꼶; á„ᅩᆱ; 꼶; á„ᅩᆱ; ) HANGUL SYLLABLE GGOLM
+AF37;AF37;1101 1169 11B2;AF37;1101 1169 11B2; # (ê¼·; ê¼·; á„ᅩᆲ; ê¼·; á„ᅩᆲ; ) HANGUL SYLLABLE GGOLB
+AF38;AF38;1101 1169 11B3;AF38;1101 1169 11B3; # (꼸; 꼸; á„ᅩᆳ; 꼸; á„ᅩᆳ; ) HANGUL SYLLABLE GGOLS
+AF39;AF39;1101 1169 11B4;AF39;1101 1169 11B4; # (ê¼¹; ê¼¹; á„ᅩᆴ; ê¼¹; á„ᅩᆴ; ) HANGUL SYLLABLE GGOLT
+AF3A;AF3A;1101 1169 11B5;AF3A;1101 1169 11B5; # (꼺; 꼺; á„ᅩᆵ; 꼺; á„ᅩᆵ; ) HANGUL SYLLABLE GGOLP
+AF3B;AF3B;1101 1169 11B6;AF3B;1101 1169 11B6; # (ê¼»; ê¼»; á„ᅩᆶ; ê¼»; á„ᅩᆶ; ) HANGUL SYLLABLE GGOLH
+AF3C;AF3C;1101 1169 11B7;AF3C;1101 1169 11B7; # (ê¼¼; ê¼¼; á„ᅩᆷ; ê¼¼; á„ᅩᆷ; ) HANGUL SYLLABLE GGOM
+AF3D;AF3D;1101 1169 11B8;AF3D;1101 1169 11B8; # (ê¼½; ê¼½; á„ᅩᆸ; ê¼½; á„ᅩᆸ; ) HANGUL SYLLABLE GGOB
+AF3E;AF3E;1101 1169 11B9;AF3E;1101 1169 11B9; # (ê¼¾; ê¼¾; á„ᅩᆹ; ê¼¾; á„ᅩᆹ; ) HANGUL SYLLABLE GGOBS
+AF3F;AF3F;1101 1169 11BA;AF3F;1101 1169 11BA; # (꼿; 꼿; á„ᅩᆺ; 꼿; á„ᅩᆺ; ) HANGUL SYLLABLE GGOS
+AF40;AF40;1101 1169 11BB;AF40;1101 1169 11BB; # (ê½€; ê½€; á„ᅩᆻ; ê½€; á„ᅩᆻ; ) HANGUL SYLLABLE GGOSS
+AF41;AF41;1101 1169 11BC;AF41;1101 1169 11BC; # (ê½; ê½; á„ᅩᆼ; ê½; á„ᅩᆼ; ) HANGUL SYLLABLE GGONG
+AF42;AF42;1101 1169 11BD;AF42;1101 1169 11BD; # (꽂; 꽂; á„ᅩᆽ; 꽂; á„ᅩᆽ; ) HANGUL SYLLABLE GGOJ
+AF43;AF43;1101 1169 11BE;AF43;1101 1169 11BE; # (꽃; 꽃; á„ᅩᆾ; 꽃; á„ᅩᆾ; ) HANGUL SYLLABLE GGOC
+AF44;AF44;1101 1169 11BF;AF44;1101 1169 11BF; # (꽄; 꽄; á„ᅩᆿ; 꽄; á„ᅩᆿ; ) HANGUL SYLLABLE GGOK
+AF45;AF45;1101 1169 11C0;AF45;1101 1169 11C0; # (ê½…; ê½…; á„ᅩᇀ; ê½…; á„ᅩᇀ; ) HANGUL SYLLABLE GGOT
+AF46;AF46;1101 1169 11C1;AF46;1101 1169 11C1; # (꽆; 꽆; á„á…©á‡; 꽆; á„á…©á‡; ) HANGUL SYLLABLE GGOP
+AF47;AF47;1101 1169 11C2;AF47;1101 1169 11C2; # (꽇; 꽇; á„ᅩᇂ; 꽇; á„ᅩᇂ; ) HANGUL SYLLABLE GGOH
+AF48;AF48;1101 116A;AF48;1101 116A; # (꽈; 꽈; á„á…ª; 꽈; á„á…ª; ) HANGUL SYLLABLE GGWA
+AF49;AF49;1101 116A 11A8;AF49;1101 116A 11A8; # (꽉; 꽉; á„ᅪᆨ; 꽉; á„ᅪᆨ; ) HANGUL SYLLABLE GGWAG
+AF4A;AF4A;1101 116A 11A9;AF4A;1101 116A 11A9; # (꽊; 꽊; á„ᅪᆩ; 꽊; á„ᅪᆩ; ) HANGUL SYLLABLE GGWAGG
+AF4B;AF4B;1101 116A 11AA;AF4B;1101 116A 11AA; # (꽋; 꽋; á„ᅪᆪ; 꽋; á„ᅪᆪ; ) HANGUL SYLLABLE GGWAGS
+AF4C;AF4C;1101 116A 11AB;AF4C;1101 116A 11AB; # (꽌; 꽌; á„ᅪᆫ; 꽌; á„ᅪᆫ; ) HANGUL SYLLABLE GGWAN
+AF4D;AF4D;1101 116A 11AC;AF4D;1101 116A 11AC; # (ê½; ê½; á„ᅪᆬ; ê½; á„ᅪᆬ; ) HANGUL SYLLABLE GGWANJ
+AF4E;AF4E;1101 116A 11AD;AF4E;1101 116A 11AD; # (꽎; 꽎; á„ᅪᆭ; 꽎; á„ᅪᆭ; ) HANGUL SYLLABLE GGWANH
+AF4F;AF4F;1101 116A 11AE;AF4F;1101 116A 11AE; # (ê½; ê½; á„ᅪᆮ; ê½; á„ᅪᆮ; ) HANGUL SYLLABLE GGWAD
+AF50;AF50;1101 116A 11AF;AF50;1101 116A 11AF; # (ê½; ê½; á„ᅪᆯ; ê½; á„ᅪᆯ; ) HANGUL SYLLABLE GGWAL
+AF51;AF51;1101 116A 11B0;AF51;1101 116A 11B0; # (꽑; 꽑; á„ᅪᆰ; 꽑; á„ᅪᆰ; ) HANGUL SYLLABLE GGWALG
+AF52;AF52;1101 116A 11B1;AF52;1101 116A 11B1; # (ê½’; ê½’; á„ᅪᆱ; ê½’; á„ᅪᆱ; ) HANGUL SYLLABLE GGWALM
+AF53;AF53;1101 116A 11B2;AF53;1101 116A 11B2; # (꽓; 꽓; á„ᅪᆲ; 꽓; á„ᅪᆲ; ) HANGUL SYLLABLE GGWALB
+AF54;AF54;1101 116A 11B3;AF54;1101 116A 11B3; # (ê½”; ê½”; á„ᅪᆳ; ê½”; á„ᅪᆳ; ) HANGUL SYLLABLE GGWALS
+AF55;AF55;1101 116A 11B4;AF55;1101 116A 11B4; # (꽕; 꽕; á„ᅪᆴ; 꽕; á„ᅪᆴ; ) HANGUL SYLLABLE GGWALT
+AF56;AF56;1101 116A 11B5;AF56;1101 116A 11B5; # (ê½–; ê½–; á„ᅪᆵ; ê½–; á„ᅪᆵ; ) HANGUL SYLLABLE GGWALP
+AF57;AF57;1101 116A 11B6;AF57;1101 116A 11B6; # (ê½—; ê½—; á„ᅪᆶ; ê½—; á„ᅪᆶ; ) HANGUL SYLLABLE GGWALH
+AF58;AF58;1101 116A 11B7;AF58;1101 116A 11B7; # (꽘; 꽘; á„ᅪᆷ; 꽘; á„ᅪᆷ; ) HANGUL SYLLABLE GGWAM
+AF59;AF59;1101 116A 11B8;AF59;1101 116A 11B8; # (ê½™; ê½™; á„ᅪᆸ; ê½™; á„ᅪᆸ; ) HANGUL SYLLABLE GGWAB
+AF5A;AF5A;1101 116A 11B9;AF5A;1101 116A 11B9; # (꽚; 꽚; á„ᅪᆹ; 꽚; á„ᅪᆹ; ) HANGUL SYLLABLE GGWABS
+AF5B;AF5B;1101 116A 11BA;AF5B;1101 116A 11BA; # (ê½›; ê½›; á„ᅪᆺ; ê½›; á„ᅪᆺ; ) HANGUL SYLLABLE GGWAS
+AF5C;AF5C;1101 116A 11BB;AF5C;1101 116A 11BB; # (꽜; 꽜; á„ᅪᆻ; 꽜; á„ᅪᆻ; ) HANGUL SYLLABLE GGWASS
+AF5D;AF5D;1101 116A 11BC;AF5D;1101 116A 11BC; # (ê½; ê½; á„ᅪᆼ; ê½; á„ᅪᆼ; ) HANGUL SYLLABLE GGWANG
+AF5E;AF5E;1101 116A 11BD;AF5E;1101 116A 11BD; # (꽞; 꽞; á„ᅪᆽ; 꽞; á„ᅪᆽ; ) HANGUL SYLLABLE GGWAJ
+AF5F;AF5F;1101 116A 11BE;AF5F;1101 116A 11BE; # (꽟; 꽟; á„ᅪᆾ; 꽟; á„ᅪᆾ; ) HANGUL SYLLABLE GGWAC
+AF60;AF60;1101 116A 11BF;AF60;1101 116A 11BF; # (ê½ ; ê½ ; á„ᅪᆿ; ê½ ; á„ᅪᆿ; ) HANGUL SYLLABLE GGWAK
+AF61;AF61;1101 116A 11C0;AF61;1101 116A 11C0; # (꽡; 꽡; á„ᅪᇀ; 꽡; á„ᅪᇀ; ) HANGUL SYLLABLE GGWAT
+AF62;AF62;1101 116A 11C1;AF62;1101 116A 11C1; # (ê½¢; ê½¢; á„á…ªá‡; ê½¢; á„á…ªá‡; ) HANGUL SYLLABLE GGWAP
+AF63;AF63;1101 116A 11C2;AF63;1101 116A 11C2; # (ê½£; ê½£; á„ᅪᇂ; ê½£; á„ᅪᇂ; ) HANGUL SYLLABLE GGWAH
+AF64;AF64;1101 116B;AF64;1101 116B; # (꽤; 꽤; á„á…«; 꽤; á„á…«; ) HANGUL SYLLABLE GGWAE
+AF65;AF65;1101 116B 11A8;AF65;1101 116B 11A8; # (ê½¥; ê½¥; á„ᅫᆨ; ê½¥; á„ᅫᆨ; ) HANGUL SYLLABLE GGWAEG
+AF66;AF66;1101 116B 11A9;AF66;1101 116B 11A9; # (꽦; 꽦; á„ᅫᆩ; 꽦; á„ᅫᆩ; ) HANGUL SYLLABLE GGWAEGG
+AF67;AF67;1101 116B 11AA;AF67;1101 116B 11AA; # (꽧; 꽧; á„ᅫᆪ; 꽧; á„ᅫᆪ; ) HANGUL SYLLABLE GGWAEGS
+AF68;AF68;1101 116B 11AB;AF68;1101 116B 11AB; # (꽨; 꽨; á„ᅫᆫ; 꽨; á„ᅫᆫ; ) HANGUL SYLLABLE GGWAEN
+AF69;AF69;1101 116B 11AC;AF69;1101 116B 11AC; # (꽩; 꽩; á„ᅫᆬ; 꽩; á„ᅫᆬ; ) HANGUL SYLLABLE GGWAENJ
+AF6A;AF6A;1101 116B 11AD;AF6A;1101 116B 11AD; # (꽪; 꽪; á„ᅫᆭ; 꽪; á„ᅫᆭ; ) HANGUL SYLLABLE GGWAENH
+AF6B;AF6B;1101 116B 11AE;AF6B;1101 116B 11AE; # (꽫; 꽫; á„ᅫᆮ; 꽫; á„ᅫᆮ; ) HANGUL SYLLABLE GGWAED
+AF6C;AF6C;1101 116B 11AF;AF6C;1101 116B 11AF; # (꽬; 꽬; á„ᅫᆯ; 꽬; á„ᅫᆯ; ) HANGUL SYLLABLE GGWAEL
+AF6D;AF6D;1101 116B 11B0;AF6D;1101 116B 11B0; # (ê½­; ê½­; á„ᅫᆰ; ê½­; á„ᅫᆰ; ) HANGUL SYLLABLE GGWAELG
+AF6E;AF6E;1101 116B 11B1;AF6E;1101 116B 11B1; # (ê½®; ê½®; á„ᅫᆱ; ê½®; á„ᅫᆱ; ) HANGUL SYLLABLE GGWAELM
+AF6F;AF6F;1101 116B 11B2;AF6F;1101 116B 11B2; # (꽯; 꽯; á„ᅫᆲ; 꽯; á„ᅫᆲ; ) HANGUL SYLLABLE GGWAELB
+AF70;AF70;1101 116B 11B3;AF70;1101 116B 11B3; # (ê½°; ê½°; á„ᅫᆳ; ê½°; á„ᅫᆳ; ) HANGUL SYLLABLE GGWAELS
+AF71;AF71;1101 116B 11B4;AF71;1101 116B 11B4; # (ê½±; ê½±; á„ᅫᆴ; ê½±; á„ᅫᆴ; ) HANGUL SYLLABLE GGWAELT
+AF72;AF72;1101 116B 11B5;AF72;1101 116B 11B5; # (ê½²; ê½²; á„ᅫᆵ; ê½²; á„ᅫᆵ; ) HANGUL SYLLABLE GGWAELP
+AF73;AF73;1101 116B 11B6;AF73;1101 116B 11B6; # (ê½³; ê½³; á„ᅫᆶ; ê½³; á„ᅫᆶ; ) HANGUL SYLLABLE GGWAELH
+AF74;AF74;1101 116B 11B7;AF74;1101 116B 11B7; # (ê½´; ê½´; á„ᅫᆷ; ê½´; á„ᅫᆷ; ) HANGUL SYLLABLE GGWAEM
+AF75;AF75;1101 116B 11B8;AF75;1101 116B 11B8; # (ê½µ; ê½µ; á„ᅫᆸ; ê½µ; á„ᅫᆸ; ) HANGUL SYLLABLE GGWAEB
+AF76;AF76;1101 116B 11B9;AF76;1101 116B 11B9; # (꽶; 꽶; á„ᅫᆹ; 꽶; á„ᅫᆹ; ) HANGUL SYLLABLE GGWAEBS
+AF77;AF77;1101 116B 11BA;AF77;1101 116B 11BA; # (ê½·; ê½·; á„ᅫᆺ; ê½·; á„ᅫᆺ; ) HANGUL SYLLABLE GGWAES
+AF78;AF78;1101 116B 11BB;AF78;1101 116B 11BB; # (꽸; 꽸; á„ᅫᆻ; 꽸; á„ᅫᆻ; ) HANGUL SYLLABLE GGWAESS
+AF79;AF79;1101 116B 11BC;AF79;1101 116B 11BC; # (ê½¹; ê½¹; á„ᅫᆼ; ê½¹; á„ᅫᆼ; ) HANGUL SYLLABLE GGWAENG
+AF7A;AF7A;1101 116B 11BD;AF7A;1101 116B 11BD; # (꽺; 꽺; á„ᅫᆽ; 꽺; á„ᅫᆽ; ) HANGUL SYLLABLE GGWAEJ
+AF7B;AF7B;1101 116B 11BE;AF7B;1101 116B 11BE; # (ê½»; ê½»; á„ᅫᆾ; ê½»; á„ᅫᆾ; ) HANGUL SYLLABLE GGWAEC
+AF7C;AF7C;1101 116B 11BF;AF7C;1101 116B 11BF; # (ê½¼; ê½¼; á„ᅫᆿ; ê½¼; á„ᅫᆿ; ) HANGUL SYLLABLE GGWAEK
+AF7D;AF7D;1101 116B 11C0;AF7D;1101 116B 11C0; # (ê½½; ê½½; á„ᅫᇀ; ê½½; á„ᅫᇀ; ) HANGUL SYLLABLE GGWAET
+AF7E;AF7E;1101 116B 11C1;AF7E;1101 116B 11C1; # (ê½¾; ê½¾; á„á…«á‡; ê½¾; á„á…«á‡; ) HANGUL SYLLABLE GGWAEP
+AF7F;AF7F;1101 116B 11C2;AF7F;1101 116B 11C2; # (꽿; 꽿; á„ᅫᇂ; 꽿; á„ᅫᇂ; ) HANGUL SYLLABLE GGWAEH
+AF80;AF80;1101 116C;AF80;1101 116C; # (ê¾€; ê¾€; á„á…¬; ê¾€; á„á…¬; ) HANGUL SYLLABLE GGOE
+AF81;AF81;1101 116C 11A8;AF81;1101 116C 11A8; # (ê¾; ê¾; á„ᅬᆨ; ê¾; á„ᅬᆨ; ) HANGUL SYLLABLE GGOEG
+AF82;AF82;1101 116C 11A9;AF82;1101 116C 11A9; # (꾂; 꾂; á„ᅬᆩ; 꾂; á„ᅬᆩ; ) HANGUL SYLLABLE GGOEGG
+AF83;AF83;1101 116C 11AA;AF83;1101 116C 11AA; # (꾃; 꾃; á„ᅬᆪ; 꾃; á„ᅬᆪ; ) HANGUL SYLLABLE GGOEGS
+AF84;AF84;1101 116C 11AB;AF84;1101 116C 11AB; # (꾄; 꾄; á„ᅬᆫ; 꾄; á„ᅬᆫ; ) HANGUL SYLLABLE GGOEN
+AF85;AF85;1101 116C 11AC;AF85;1101 116C 11AC; # (ê¾…; ê¾…; á„ᅬᆬ; ê¾…; á„ᅬᆬ; ) HANGUL SYLLABLE GGOENJ
+AF86;AF86;1101 116C 11AD;AF86;1101 116C 11AD; # (꾆; 꾆; á„ᅬᆭ; 꾆; á„ᅬᆭ; ) HANGUL SYLLABLE GGOENH
+AF87;AF87;1101 116C 11AE;AF87;1101 116C 11AE; # (꾇; 꾇; á„ᅬᆮ; 꾇; á„ᅬᆮ; ) HANGUL SYLLABLE GGOED
+AF88;AF88;1101 116C 11AF;AF88;1101 116C 11AF; # (꾈; 꾈; á„ᅬᆯ; 꾈; á„ᅬᆯ; ) HANGUL SYLLABLE GGOEL
+AF89;AF89;1101 116C 11B0;AF89;1101 116C 11B0; # (꾉; 꾉; á„ᅬᆰ; 꾉; á„ᅬᆰ; ) HANGUL SYLLABLE GGOELG
+AF8A;AF8A;1101 116C 11B1;AF8A;1101 116C 11B1; # (꾊; 꾊; á„ᅬᆱ; 꾊; á„ᅬᆱ; ) HANGUL SYLLABLE GGOELM
+AF8B;AF8B;1101 116C 11B2;AF8B;1101 116C 11B2; # (꾋; 꾋; á„ᅬᆲ; 꾋; á„ᅬᆲ; ) HANGUL SYLLABLE GGOELB
+AF8C;AF8C;1101 116C 11B3;AF8C;1101 116C 11B3; # (꾌; 꾌; á„ᅬᆳ; 꾌; á„ᅬᆳ; ) HANGUL SYLLABLE GGOELS
+AF8D;AF8D;1101 116C 11B4;AF8D;1101 116C 11B4; # (ê¾; ê¾; á„ᅬᆴ; ê¾; á„ᅬᆴ; ) HANGUL SYLLABLE GGOELT
+AF8E;AF8E;1101 116C 11B5;AF8E;1101 116C 11B5; # (꾎; 꾎; á„ᅬᆵ; 꾎; á„ᅬᆵ; ) HANGUL SYLLABLE GGOELP
+AF8F;AF8F;1101 116C 11B6;AF8F;1101 116C 11B6; # (ê¾; ê¾; á„ᅬᆶ; ê¾; á„ᅬᆶ; ) HANGUL SYLLABLE GGOELH
+AF90;AF90;1101 116C 11B7;AF90;1101 116C 11B7; # (ê¾; ê¾; á„ᅬᆷ; ê¾; á„ᅬᆷ; ) HANGUL SYLLABLE GGOEM
+AF91;AF91;1101 116C 11B8;AF91;1101 116C 11B8; # (꾑; 꾑; á„ᅬᆸ; 꾑; á„ᅬᆸ; ) HANGUL SYLLABLE GGOEB
+AF92;AF92;1101 116C 11B9;AF92;1101 116C 11B9; # (ê¾’; ê¾’; á„ᅬᆹ; ê¾’; á„ᅬᆹ; ) HANGUL SYLLABLE GGOEBS
+AF93;AF93;1101 116C 11BA;AF93;1101 116C 11BA; # (꾓; 꾓; á„ᅬᆺ; 꾓; á„ᅬᆺ; ) HANGUL SYLLABLE GGOES
+AF94;AF94;1101 116C 11BB;AF94;1101 116C 11BB; # (ê¾”; ê¾”; á„ᅬᆻ; ê¾”; á„ᅬᆻ; ) HANGUL SYLLABLE GGOESS
+AF95;AF95;1101 116C 11BC;AF95;1101 116C 11BC; # (꾕; 꾕; á„ᅬᆼ; 꾕; á„ᅬᆼ; ) HANGUL SYLLABLE GGOENG
+AF96;AF96;1101 116C 11BD;AF96;1101 116C 11BD; # (ê¾–; ê¾–; á„ᅬᆽ; ê¾–; á„ᅬᆽ; ) HANGUL SYLLABLE GGOEJ
+AF97;AF97;1101 116C 11BE;AF97;1101 116C 11BE; # (ê¾—; ê¾—; á„ᅬᆾ; ê¾—; á„ᅬᆾ; ) HANGUL SYLLABLE GGOEC
+AF98;AF98;1101 116C 11BF;AF98;1101 116C 11BF; # (꾘; 꾘; á„ᅬᆿ; 꾘; á„ᅬᆿ; ) HANGUL SYLLABLE GGOEK
+AF99;AF99;1101 116C 11C0;AF99;1101 116C 11C0; # (ê¾™; ê¾™; á„ᅬᇀ; ê¾™; á„ᅬᇀ; ) HANGUL SYLLABLE GGOET
+AF9A;AF9A;1101 116C 11C1;AF9A;1101 116C 11C1; # (꾚; 꾚; á„á…¬á‡; 꾚; á„á…¬á‡; ) HANGUL SYLLABLE GGOEP
+AF9B;AF9B;1101 116C 11C2;AF9B;1101 116C 11C2; # (ê¾›; ê¾›; á„ᅬᇂ; ê¾›; á„ᅬᇂ; ) HANGUL SYLLABLE GGOEH
+AF9C;AF9C;1101 116D;AF9C;1101 116D; # (꾜; 꾜; á„á…­; 꾜; á„á…­; ) HANGUL SYLLABLE GGYO
+AF9D;AF9D;1101 116D 11A8;AF9D;1101 116D 11A8; # (ê¾; ê¾; á„ᅭᆨ; ê¾; á„ᅭᆨ; ) HANGUL SYLLABLE GGYOG
+AF9E;AF9E;1101 116D 11A9;AF9E;1101 116D 11A9; # (꾞; 꾞; á„ᅭᆩ; 꾞; á„ᅭᆩ; ) HANGUL SYLLABLE GGYOGG
+AF9F;AF9F;1101 116D 11AA;AF9F;1101 116D 11AA; # (꾟; 꾟; á„ᅭᆪ; 꾟; á„ᅭᆪ; ) HANGUL SYLLABLE GGYOGS
+AFA0;AFA0;1101 116D 11AB;AFA0;1101 116D 11AB; # (ê¾ ; ê¾ ; á„ᅭᆫ; ê¾ ; á„ᅭᆫ; ) HANGUL SYLLABLE GGYON
+AFA1;AFA1;1101 116D 11AC;AFA1;1101 116D 11AC; # (꾡; 꾡; á„ᅭᆬ; 꾡; á„ᅭᆬ; ) HANGUL SYLLABLE GGYONJ
+AFA2;AFA2;1101 116D 11AD;AFA2;1101 116D 11AD; # (ê¾¢; ê¾¢; á„ᅭᆭ; ê¾¢; á„ᅭᆭ; ) HANGUL SYLLABLE GGYONH
+AFA3;AFA3;1101 116D 11AE;AFA3;1101 116D 11AE; # (ê¾£; ê¾£; á„ᅭᆮ; ê¾£; á„ᅭᆮ; ) HANGUL SYLLABLE GGYOD
+AFA4;AFA4;1101 116D 11AF;AFA4;1101 116D 11AF; # (꾤; 꾤; á„ᅭᆯ; 꾤; á„ᅭᆯ; ) HANGUL SYLLABLE GGYOL
+AFA5;AFA5;1101 116D 11B0;AFA5;1101 116D 11B0; # (ê¾¥; ê¾¥; á„ᅭᆰ; ê¾¥; á„ᅭᆰ; ) HANGUL SYLLABLE GGYOLG
+AFA6;AFA6;1101 116D 11B1;AFA6;1101 116D 11B1; # (꾦; 꾦; á„ᅭᆱ; 꾦; á„ᅭᆱ; ) HANGUL SYLLABLE GGYOLM
+AFA7;AFA7;1101 116D 11B2;AFA7;1101 116D 11B2; # (꾧; 꾧; á„ᅭᆲ; 꾧; á„ᅭᆲ; ) HANGUL SYLLABLE GGYOLB
+AFA8;AFA8;1101 116D 11B3;AFA8;1101 116D 11B3; # (꾨; 꾨; á„ᅭᆳ; 꾨; á„ᅭᆳ; ) HANGUL SYLLABLE GGYOLS
+AFA9;AFA9;1101 116D 11B4;AFA9;1101 116D 11B4; # (꾩; 꾩; á„ᅭᆴ; 꾩; á„ᅭᆴ; ) HANGUL SYLLABLE GGYOLT
+AFAA;AFAA;1101 116D 11B5;AFAA;1101 116D 11B5; # (꾪; 꾪; á„ᅭᆵ; 꾪; á„ᅭᆵ; ) HANGUL SYLLABLE GGYOLP
+AFAB;AFAB;1101 116D 11B6;AFAB;1101 116D 11B6; # (꾫; 꾫; á„ᅭᆶ; 꾫; á„ᅭᆶ; ) HANGUL SYLLABLE GGYOLH
+AFAC;AFAC;1101 116D 11B7;AFAC;1101 116D 11B7; # (꾬; 꾬; á„ᅭᆷ; 꾬; á„ᅭᆷ; ) HANGUL SYLLABLE GGYOM
+AFAD;AFAD;1101 116D 11B8;AFAD;1101 116D 11B8; # (ê¾­; ê¾­; á„ᅭᆸ; ê¾­; á„ᅭᆸ; ) HANGUL SYLLABLE GGYOB
+AFAE;AFAE;1101 116D 11B9;AFAE;1101 116D 11B9; # (ê¾®; ê¾®; á„ᅭᆹ; ê¾®; á„ᅭᆹ; ) HANGUL SYLLABLE GGYOBS
+AFAF;AFAF;1101 116D 11BA;AFAF;1101 116D 11BA; # (꾯; 꾯; á„ᅭᆺ; 꾯; á„ᅭᆺ; ) HANGUL SYLLABLE GGYOS
+AFB0;AFB0;1101 116D 11BB;AFB0;1101 116D 11BB; # (ê¾°; ê¾°; á„ᅭᆻ; ê¾°; á„ᅭᆻ; ) HANGUL SYLLABLE GGYOSS
+AFB1;AFB1;1101 116D 11BC;AFB1;1101 116D 11BC; # (ê¾±; ê¾±; á„ᅭᆼ; ê¾±; á„ᅭᆼ; ) HANGUL SYLLABLE GGYONG
+AFB2;AFB2;1101 116D 11BD;AFB2;1101 116D 11BD; # (ê¾²; ê¾²; á„ᅭᆽ; ê¾²; á„ᅭᆽ; ) HANGUL SYLLABLE GGYOJ
+AFB3;AFB3;1101 116D 11BE;AFB3;1101 116D 11BE; # (ê¾³; ê¾³; á„ᅭᆾ; ê¾³; á„ᅭᆾ; ) HANGUL SYLLABLE GGYOC
+AFB4;AFB4;1101 116D 11BF;AFB4;1101 116D 11BF; # (ê¾´; ê¾´; á„ᅭᆿ; ê¾´; á„ᅭᆿ; ) HANGUL SYLLABLE GGYOK
+AFB5;AFB5;1101 116D 11C0;AFB5;1101 116D 11C0; # (ê¾µ; ê¾µ; á„ᅭᇀ; ê¾µ; á„ᅭᇀ; ) HANGUL SYLLABLE GGYOT
+AFB6;AFB6;1101 116D 11C1;AFB6;1101 116D 11C1; # (꾶; 꾶; á„á…­á‡; 꾶; á„á…­á‡; ) HANGUL SYLLABLE GGYOP
+AFB7;AFB7;1101 116D 11C2;AFB7;1101 116D 11C2; # (ê¾·; ê¾·; á„ᅭᇂ; ê¾·; á„ᅭᇂ; ) HANGUL SYLLABLE GGYOH
+AFB8;AFB8;1101 116E;AFB8;1101 116E; # (꾸; 꾸; á„á…®; 꾸; á„á…®; ) HANGUL SYLLABLE GGU
+AFB9;AFB9;1101 116E 11A8;AFB9;1101 116E 11A8; # (ê¾¹; ê¾¹; á„ᅮᆨ; ê¾¹; á„ᅮᆨ; ) HANGUL SYLLABLE GGUG
+AFBA;AFBA;1101 116E 11A9;AFBA;1101 116E 11A9; # (꾺; 꾺; á„ᅮᆩ; 꾺; á„ᅮᆩ; ) HANGUL SYLLABLE GGUGG
+AFBB;AFBB;1101 116E 11AA;AFBB;1101 116E 11AA; # (ê¾»; ê¾»; á„ᅮᆪ; ê¾»; á„ᅮᆪ; ) HANGUL SYLLABLE GGUGS
+AFBC;AFBC;1101 116E 11AB;AFBC;1101 116E 11AB; # (ê¾¼; ê¾¼; á„ᅮᆫ; ê¾¼; á„ᅮᆫ; ) HANGUL SYLLABLE GGUN
+AFBD;AFBD;1101 116E 11AC;AFBD;1101 116E 11AC; # (ê¾½; ê¾½; á„ᅮᆬ; ê¾½; á„ᅮᆬ; ) HANGUL SYLLABLE GGUNJ
+AFBE;AFBE;1101 116E 11AD;AFBE;1101 116E 11AD; # (ê¾¾; ê¾¾; á„ᅮᆭ; ê¾¾; á„ᅮᆭ; ) HANGUL SYLLABLE GGUNH
+AFBF;AFBF;1101 116E 11AE;AFBF;1101 116E 11AE; # (꾿; 꾿; á„ᅮᆮ; 꾿; á„ᅮᆮ; ) HANGUL SYLLABLE GGUD
+AFC0;AFC0;1101 116E 11AF;AFC0;1101 116E 11AF; # (ê¿€; ê¿€; á„ᅮᆯ; ê¿€; á„ᅮᆯ; ) HANGUL SYLLABLE GGUL
+AFC1;AFC1;1101 116E 11B0;AFC1;1101 116E 11B0; # (ê¿; ê¿; á„ᅮᆰ; ê¿; á„ᅮᆰ; ) HANGUL SYLLABLE GGULG
+AFC2;AFC2;1101 116E 11B1;AFC2;1101 116E 11B1; # (ê¿‚; ê¿‚; á„ᅮᆱ; ê¿‚; á„ᅮᆱ; ) HANGUL SYLLABLE GGULM
+AFC3;AFC3;1101 116E 11B2;AFC3;1101 116E 11B2; # (꿃; 꿃; á„ᅮᆲ; 꿃; á„ᅮᆲ; ) HANGUL SYLLABLE GGULB
+AFC4;AFC4;1101 116E 11B3;AFC4;1101 116E 11B3; # (ê¿„; ê¿„; á„ᅮᆳ; ê¿„; á„ᅮᆳ; ) HANGUL SYLLABLE GGULS
+AFC5;AFC5;1101 116E 11B4;AFC5;1101 116E 11B4; # (ê¿…; ê¿…; á„ᅮᆴ; ê¿…; á„ᅮᆴ; ) HANGUL SYLLABLE GGULT
+AFC6;AFC6;1101 116E 11B5;AFC6;1101 116E 11B5; # (꿆; 꿆; á„ᅮᆵ; 꿆; á„ᅮᆵ; ) HANGUL SYLLABLE GGULP
+AFC7;AFC7;1101 116E 11B6;AFC7;1101 116E 11B6; # (꿇; 꿇; á„ᅮᆶ; 꿇; á„ᅮᆶ; ) HANGUL SYLLABLE GGULH
+AFC8;AFC8;1101 116E 11B7;AFC8;1101 116E 11B7; # (꿈; 꿈; á„ᅮᆷ; 꿈; á„ᅮᆷ; ) HANGUL SYLLABLE GGUM
+AFC9;AFC9;1101 116E 11B8;AFC9;1101 116E 11B8; # (꿉; 꿉; á„ᅮᆸ; 꿉; á„ᅮᆸ; ) HANGUL SYLLABLE GGUB
+AFCA;AFCA;1101 116E 11B9;AFCA;1101 116E 11B9; # (ê¿Š; ê¿Š; á„ᅮᆹ; ê¿Š; á„ᅮᆹ; ) HANGUL SYLLABLE GGUBS
+AFCB;AFCB;1101 116E 11BA;AFCB;1101 116E 11BA; # (ê¿‹; ê¿‹; á„ᅮᆺ; ê¿‹; á„ᅮᆺ; ) HANGUL SYLLABLE GGUS
+AFCC;AFCC;1101 116E 11BB;AFCC;1101 116E 11BB; # (ê¿Œ; ê¿Œ; á„ᅮᆻ; ê¿Œ; á„ᅮᆻ; ) HANGUL SYLLABLE GGUSS
+AFCD;AFCD;1101 116E 11BC;AFCD;1101 116E 11BC; # (ê¿; ê¿; á„ᅮᆼ; ê¿; á„ᅮᆼ; ) HANGUL SYLLABLE GGUNG
+AFCE;AFCE;1101 116E 11BD;AFCE;1101 116E 11BD; # (ê¿Ž; ê¿Ž; á„ᅮᆽ; ê¿Ž; á„ᅮᆽ; ) HANGUL SYLLABLE GGUJ
+AFCF;AFCF;1101 116E 11BE;AFCF;1101 116E 11BE; # (ê¿; ê¿; á„ᅮᆾ; ê¿; á„ᅮᆾ; ) HANGUL SYLLABLE GGUC
+AFD0;AFD0;1101 116E 11BF;AFD0;1101 116E 11BF; # (ê¿; ê¿; á„ᅮᆿ; ê¿; á„ᅮᆿ; ) HANGUL SYLLABLE GGUK
+AFD1;AFD1;1101 116E 11C0;AFD1;1101 116E 11C0; # (ê¿‘; ê¿‘; á„ᅮᇀ; ê¿‘; á„ᅮᇀ; ) HANGUL SYLLABLE GGUT
+AFD2;AFD2;1101 116E 11C1;AFD2;1101 116E 11C1; # (ê¿’; ê¿’; á„á…®á‡; ê¿’; á„á…®á‡; ) HANGUL SYLLABLE GGUP
+AFD3;AFD3;1101 116E 11C2;AFD3;1101 116E 11C2; # (ê¿“; ê¿“; á„ᅮᇂ; ê¿“; á„ᅮᇂ; ) HANGUL SYLLABLE GGUH
+AFD4;AFD4;1101 116F;AFD4;1101 116F; # (ê¿”; ê¿”; á„á…¯; ê¿”; á„á…¯; ) HANGUL SYLLABLE GGWEO
+AFD5;AFD5;1101 116F 11A8;AFD5;1101 116F 11A8; # (ê¿•; ê¿•; á„ᅯᆨ; ê¿•; á„ᅯᆨ; ) HANGUL SYLLABLE GGWEOG
+AFD6;AFD6;1101 116F 11A9;AFD6;1101 116F 11A9; # (ê¿–; ê¿–; á„ᅯᆩ; ê¿–; á„ᅯᆩ; ) HANGUL SYLLABLE GGWEOGG
+AFD7;AFD7;1101 116F 11AA;AFD7;1101 116F 11AA; # (ê¿—; ê¿—; á„ᅯᆪ; ê¿—; á„ᅯᆪ; ) HANGUL SYLLABLE GGWEOGS
+AFD8;AFD8;1101 116F 11AB;AFD8;1101 116F 11AB; # (꿘; 꿘; á„ᅯᆫ; 꿘; á„ᅯᆫ; ) HANGUL SYLLABLE GGWEON
+AFD9;AFD9;1101 116F 11AC;AFD9;1101 116F 11AC; # (ê¿™; ê¿™; á„ᅯᆬ; ê¿™; á„ᅯᆬ; ) HANGUL SYLLABLE GGWEONJ
+AFDA;AFDA;1101 116F 11AD;AFDA;1101 116F 11AD; # (ê¿š; ê¿š; á„ᅯᆭ; ê¿š; á„ᅯᆭ; ) HANGUL SYLLABLE GGWEONH
+AFDB;AFDB;1101 116F 11AE;AFDB;1101 116F 11AE; # (ê¿›; ê¿›; á„ᅯᆮ; ê¿›; á„ᅯᆮ; ) HANGUL SYLLABLE GGWEOD
+AFDC;AFDC;1101 116F 11AF;AFDC;1101 116F 11AF; # (ê¿œ; ê¿œ; á„ᅯᆯ; ê¿œ; á„ᅯᆯ; ) HANGUL SYLLABLE GGWEOL
+AFDD;AFDD;1101 116F 11B0;AFDD;1101 116F 11B0; # (ê¿; ê¿; á„ᅯᆰ; ê¿; á„ᅯᆰ; ) HANGUL SYLLABLE GGWEOLG
+AFDE;AFDE;1101 116F 11B1;AFDE;1101 116F 11B1; # (ê¿ž; ê¿ž; á„ᅯᆱ; ê¿ž; á„ᅯᆱ; ) HANGUL SYLLABLE GGWEOLM
+AFDF;AFDF;1101 116F 11B2;AFDF;1101 116F 11B2; # (ê¿Ÿ; ê¿Ÿ; á„ᅯᆲ; ê¿Ÿ; á„ᅯᆲ; ) HANGUL SYLLABLE GGWEOLB
+AFE0;AFE0;1101 116F 11B3;AFE0;1101 116F 11B3; # (ê¿ ; ê¿ ; á„ᅯᆳ; ê¿ ; á„ᅯᆳ; ) HANGUL SYLLABLE GGWEOLS
+AFE1;AFE1;1101 116F 11B4;AFE1;1101 116F 11B4; # (ê¿¡; ê¿¡; á„ᅯᆴ; ê¿¡; á„ᅯᆴ; ) HANGUL SYLLABLE GGWEOLT
+AFE2;AFE2;1101 116F 11B5;AFE2;1101 116F 11B5; # (ê¿¢; ê¿¢; á„ᅯᆵ; ê¿¢; á„ᅯᆵ; ) HANGUL SYLLABLE GGWEOLP
+AFE3;AFE3;1101 116F 11B6;AFE3;1101 116F 11B6; # (ê¿£; ê¿£; á„ᅯᆶ; ê¿£; á„ᅯᆶ; ) HANGUL SYLLABLE GGWEOLH
+AFE4;AFE4;1101 116F 11B7;AFE4;1101 116F 11B7; # (꿤; 꿤; á„ᅯᆷ; 꿤; á„ᅯᆷ; ) HANGUL SYLLABLE GGWEOM
+AFE5;AFE5;1101 116F 11B8;AFE5;1101 116F 11B8; # (ê¿¥; ê¿¥; á„ᅯᆸ; ê¿¥; á„ᅯᆸ; ) HANGUL SYLLABLE GGWEOB
+AFE6;AFE6;1101 116F 11B9;AFE6;1101 116F 11B9; # (꿦; 꿦; á„ᅯᆹ; 꿦; á„ᅯᆹ; ) HANGUL SYLLABLE GGWEOBS
+AFE7;AFE7;1101 116F 11BA;AFE7;1101 116F 11BA; # (꿧; 꿧; á„ᅯᆺ; 꿧; á„ᅯᆺ; ) HANGUL SYLLABLE GGWEOS
+AFE8;AFE8;1101 116F 11BB;AFE8;1101 116F 11BB; # (꿨; 꿨; á„ᅯᆻ; 꿨; á„ᅯᆻ; ) HANGUL SYLLABLE GGWEOSS
+AFE9;AFE9;1101 116F 11BC;AFE9;1101 116F 11BC; # (ê¿©; ê¿©; á„ᅯᆼ; ê¿©; á„ᅯᆼ; ) HANGUL SYLLABLE GGWEONG
+AFEA;AFEA;1101 116F 11BD;AFEA;1101 116F 11BD; # (꿪; 꿪; á„ᅯᆽ; 꿪; á„ᅯᆽ; ) HANGUL SYLLABLE GGWEOJ
+AFEB;AFEB;1101 116F 11BE;AFEB;1101 116F 11BE; # (ê¿«; ê¿«; á„ᅯᆾ; ê¿«; á„ᅯᆾ; ) HANGUL SYLLABLE GGWEOC
+AFEC;AFEC;1101 116F 11BF;AFEC;1101 116F 11BF; # (꿬; 꿬; á„ᅯᆿ; 꿬; á„ᅯᆿ; ) HANGUL SYLLABLE GGWEOK
+AFED;AFED;1101 116F 11C0;AFED;1101 116F 11C0; # (ê¿­; ê¿­; á„ᅯᇀ; ê¿­; á„ᅯᇀ; ) HANGUL SYLLABLE GGWEOT
+AFEE;AFEE;1101 116F 11C1;AFEE;1101 116F 11C1; # (ê¿®; ê¿®; á„á…¯á‡; ê¿®; á„á…¯á‡; ) HANGUL SYLLABLE GGWEOP
+AFEF;AFEF;1101 116F 11C2;AFEF;1101 116F 11C2; # (꿯; 꿯; á„ᅯᇂ; 꿯; á„ᅯᇂ; ) HANGUL SYLLABLE GGWEOH
+AFF0;AFF0;1101 1170;AFF0;1101 1170; # (ê¿°; ê¿°; á„á…°; ê¿°; á„á…°; ) HANGUL SYLLABLE GGWE
+AFF1;AFF1;1101 1170 11A8;AFF1;1101 1170 11A8; # (꿱; 꿱; á„ᅰᆨ; 꿱; á„ᅰᆨ; ) HANGUL SYLLABLE GGWEG
+AFF2;AFF2;1101 1170 11A9;AFF2;1101 1170 11A9; # (꿲; 꿲; á„ᅰᆩ; 꿲; á„ᅰᆩ; ) HANGUL SYLLABLE GGWEGG
+AFF3;AFF3;1101 1170 11AA;AFF3;1101 1170 11AA; # (꿳; 꿳; á„ᅰᆪ; 꿳; á„ᅰᆪ; ) HANGUL SYLLABLE GGWEGS
+AFF4;AFF4;1101 1170 11AB;AFF4;1101 1170 11AB; # (ê¿´; ê¿´; á„ᅰᆫ; ê¿´; á„ᅰᆫ; ) HANGUL SYLLABLE GGWEN
+AFF5;AFF5;1101 1170 11AC;AFF5;1101 1170 11AC; # (꿵; 꿵; á„ᅰᆬ; 꿵; á„ᅰᆬ; ) HANGUL SYLLABLE GGWENJ
+AFF6;AFF6;1101 1170 11AD;AFF6;1101 1170 11AD; # (꿶; 꿶; á„ᅰᆭ; 꿶; á„ᅰᆭ; ) HANGUL SYLLABLE GGWENH
+AFF7;AFF7;1101 1170 11AE;AFF7;1101 1170 11AE; # (ê¿·; ê¿·; á„ᅰᆮ; ê¿·; á„ᅰᆮ; ) HANGUL SYLLABLE GGWED
+AFF8;AFF8;1101 1170 11AF;AFF8;1101 1170 11AF; # (꿸; 꿸; á„ᅰᆯ; 꿸; á„ᅰᆯ; ) HANGUL SYLLABLE GGWEL
+AFF9;AFF9;1101 1170 11B0;AFF9;1101 1170 11B0; # (꿹; 꿹; á„ᅰᆰ; 꿹; á„ᅰᆰ; ) HANGUL SYLLABLE GGWELG
+AFFA;AFFA;1101 1170 11B1;AFFA;1101 1170 11B1; # (꿺; 꿺; á„ᅰᆱ; 꿺; á„ᅰᆱ; ) HANGUL SYLLABLE GGWELM
+AFFB;AFFB;1101 1170 11B2;AFFB;1101 1170 11B2; # (ê¿»; ê¿»; á„ᅰᆲ; ê¿»; á„ᅰᆲ; ) HANGUL SYLLABLE GGWELB
+AFFC;AFFC;1101 1170 11B3;AFFC;1101 1170 11B3; # (꿼; 꿼; á„ᅰᆳ; 꿼; á„ᅰᆳ; ) HANGUL SYLLABLE GGWELS
+AFFD;AFFD;1101 1170 11B4;AFFD;1101 1170 11B4; # (꿽; 꿽; á„ᅰᆴ; 꿽; á„ᅰᆴ; ) HANGUL SYLLABLE GGWELT
+AFFE;AFFE;1101 1170 11B5;AFFE;1101 1170 11B5; # (꿾; 꿾; á„ᅰᆵ; 꿾; á„ᅰᆵ; ) HANGUL SYLLABLE GGWELP
+AFFF;AFFF;1101 1170 11B6;AFFF;1101 1170 11B6; # (ê¿¿; ê¿¿; á„ᅰᆶ; ê¿¿; á„ᅰᆶ; ) HANGUL SYLLABLE GGWELH
+B000;B000;1101 1170 11B7;B000;1101 1170 11B7; # (뀀; 뀀; á„ᅰᆷ; 뀀; á„ᅰᆷ; ) HANGUL SYLLABLE GGWEM
+B001;B001;1101 1170 11B8;B001;1101 1170 11B8; # (ë€; ë€; á„ᅰᆸ; ë€; á„ᅰᆸ; ) HANGUL SYLLABLE GGWEB
+B002;B002;1101 1170 11B9;B002;1101 1170 11B9; # (뀂; 뀂; á„ᅰᆹ; 뀂; á„ᅰᆹ; ) HANGUL SYLLABLE GGWEBS
+B003;B003;1101 1170 11BA;B003;1101 1170 11BA; # (뀃; 뀃; á„ᅰᆺ; 뀃; á„ᅰᆺ; ) HANGUL SYLLABLE GGWES
+B004;B004;1101 1170 11BB;B004;1101 1170 11BB; # (뀄; 뀄; á„ᅰᆻ; 뀄; á„ᅰᆻ; ) HANGUL SYLLABLE GGWESS
+B005;B005;1101 1170 11BC;B005;1101 1170 11BC; # (뀅; 뀅; á„ᅰᆼ; 뀅; á„ᅰᆼ; ) HANGUL SYLLABLE GGWENG
+B006;B006;1101 1170 11BD;B006;1101 1170 11BD; # (뀆; 뀆; á„ᅰᆽ; 뀆; á„ᅰᆽ; ) HANGUL SYLLABLE GGWEJ
+B007;B007;1101 1170 11BE;B007;1101 1170 11BE; # (뀇; 뀇; á„ᅰᆾ; 뀇; á„ᅰᆾ; ) HANGUL SYLLABLE GGWEC
+B008;B008;1101 1170 11BF;B008;1101 1170 11BF; # (뀈; 뀈; á„ᅰᆿ; 뀈; á„ᅰᆿ; ) HANGUL SYLLABLE GGWEK
+B009;B009;1101 1170 11C0;B009;1101 1170 11C0; # (뀉; 뀉; á„ᅰᇀ; 뀉; á„ᅰᇀ; ) HANGUL SYLLABLE GGWET
+B00A;B00A;1101 1170 11C1;B00A;1101 1170 11C1; # (뀊; 뀊; á„á…°á‡; 뀊; á„á…°á‡; ) HANGUL SYLLABLE GGWEP
+B00B;B00B;1101 1170 11C2;B00B;1101 1170 11C2; # (뀋; 뀋; á„ᅰᇂ; 뀋; á„ᅰᇂ; ) HANGUL SYLLABLE GGWEH
+B00C;B00C;1101 1171;B00C;1101 1171; # (뀌; 뀌; á„á…±; 뀌; á„á…±; ) HANGUL SYLLABLE GGWI
+B00D;B00D;1101 1171 11A8;B00D;1101 1171 11A8; # (ë€; ë€; á„ᅱᆨ; ë€; á„ᅱᆨ; ) HANGUL SYLLABLE GGWIG
+B00E;B00E;1101 1171 11A9;B00E;1101 1171 11A9; # (뀎; 뀎; á„ᅱᆩ; 뀎; á„ᅱᆩ; ) HANGUL SYLLABLE GGWIGG
+B00F;B00F;1101 1171 11AA;B00F;1101 1171 11AA; # (ë€; ë€; á„ᅱᆪ; ë€; á„ᅱᆪ; ) HANGUL SYLLABLE GGWIGS
+B010;B010;1101 1171 11AB;B010;1101 1171 11AB; # (ë€; ë€; á„ᅱᆫ; ë€; á„ᅱᆫ; ) HANGUL SYLLABLE GGWIN
+B011;B011;1101 1171 11AC;B011;1101 1171 11AC; # (뀑; 뀑; á„ᅱᆬ; 뀑; á„ᅱᆬ; ) HANGUL SYLLABLE GGWINJ
+B012;B012;1101 1171 11AD;B012;1101 1171 11AD; # (뀒; 뀒; á„ᅱᆭ; 뀒; á„ᅱᆭ; ) HANGUL SYLLABLE GGWINH
+B013;B013;1101 1171 11AE;B013;1101 1171 11AE; # (뀓; 뀓; á„ᅱᆮ; 뀓; á„ᅱᆮ; ) HANGUL SYLLABLE GGWID
+B014;B014;1101 1171 11AF;B014;1101 1171 11AF; # (뀔; 뀔; á„ᅱᆯ; 뀔; á„ᅱᆯ; ) HANGUL SYLLABLE GGWIL
+B015;B015;1101 1171 11B0;B015;1101 1171 11B0; # (뀕; 뀕; á„ᅱᆰ; 뀕; á„ᅱᆰ; ) HANGUL SYLLABLE GGWILG
+B016;B016;1101 1171 11B1;B016;1101 1171 11B1; # (뀖; 뀖; á„ᅱᆱ; 뀖; á„ᅱᆱ; ) HANGUL SYLLABLE GGWILM
+B017;B017;1101 1171 11B2;B017;1101 1171 11B2; # (뀗; 뀗; á„ᅱᆲ; 뀗; á„ᅱᆲ; ) HANGUL SYLLABLE GGWILB
+B018;B018;1101 1171 11B3;B018;1101 1171 11B3; # (뀘; 뀘; á„ᅱᆳ; 뀘; á„ᅱᆳ; ) HANGUL SYLLABLE GGWILS
+B019;B019;1101 1171 11B4;B019;1101 1171 11B4; # (뀙; 뀙; á„ᅱᆴ; 뀙; á„ᅱᆴ; ) HANGUL SYLLABLE GGWILT
+B01A;B01A;1101 1171 11B5;B01A;1101 1171 11B5; # (뀚; 뀚; á„ᅱᆵ; 뀚; á„ᅱᆵ; ) HANGUL SYLLABLE GGWILP
+B01B;B01B;1101 1171 11B6;B01B;1101 1171 11B6; # (뀛; 뀛; á„ᅱᆶ; 뀛; á„ᅱᆶ; ) HANGUL SYLLABLE GGWILH
+B01C;B01C;1101 1171 11B7;B01C;1101 1171 11B7; # (뀜; 뀜; á„ᅱᆷ; 뀜; á„ᅱᆷ; ) HANGUL SYLLABLE GGWIM
+B01D;B01D;1101 1171 11B8;B01D;1101 1171 11B8; # (ë€; ë€; á„ᅱᆸ; ë€; á„ᅱᆸ; ) HANGUL SYLLABLE GGWIB
+B01E;B01E;1101 1171 11B9;B01E;1101 1171 11B9; # (뀞; 뀞; á„ᅱᆹ; 뀞; á„ᅱᆹ; ) HANGUL SYLLABLE GGWIBS
+B01F;B01F;1101 1171 11BA;B01F;1101 1171 11BA; # (뀟; 뀟; á„ᅱᆺ; 뀟; á„ᅱᆺ; ) HANGUL SYLLABLE GGWIS
+B020;B020;1101 1171 11BB;B020;1101 1171 11BB; # (뀠; 뀠; á„ᅱᆻ; 뀠; á„ᅱᆻ; ) HANGUL SYLLABLE GGWISS
+B021;B021;1101 1171 11BC;B021;1101 1171 11BC; # (뀡; 뀡; á„ᅱᆼ; 뀡; á„ᅱᆼ; ) HANGUL SYLLABLE GGWING
+B022;B022;1101 1171 11BD;B022;1101 1171 11BD; # (뀢; 뀢; á„ᅱᆽ; 뀢; á„ᅱᆽ; ) HANGUL SYLLABLE GGWIJ
+B023;B023;1101 1171 11BE;B023;1101 1171 11BE; # (뀣; 뀣; á„ᅱᆾ; 뀣; á„ᅱᆾ; ) HANGUL SYLLABLE GGWIC
+B024;B024;1101 1171 11BF;B024;1101 1171 11BF; # (뀤; 뀤; á„ᅱᆿ; 뀤; á„ᅱᆿ; ) HANGUL SYLLABLE GGWIK
+B025;B025;1101 1171 11C0;B025;1101 1171 11C0; # (뀥; 뀥; á„ᅱᇀ; 뀥; á„ᅱᇀ; ) HANGUL SYLLABLE GGWIT
+B026;B026;1101 1171 11C1;B026;1101 1171 11C1; # (뀦; 뀦; á„á…±á‡; 뀦; á„á…±á‡; ) HANGUL SYLLABLE GGWIP
+B027;B027;1101 1171 11C2;B027;1101 1171 11C2; # (뀧; 뀧; á„ᅱᇂ; 뀧; á„ᅱᇂ; ) HANGUL SYLLABLE GGWIH
+B028;B028;1101 1172;B028;1101 1172; # (뀨; 뀨; á„á…²; 뀨; á„á…²; ) HANGUL SYLLABLE GGYU
+B029;B029;1101 1172 11A8;B029;1101 1172 11A8; # (뀩; 뀩; á„ᅲᆨ; 뀩; á„ᅲᆨ; ) HANGUL SYLLABLE GGYUG
+B02A;B02A;1101 1172 11A9;B02A;1101 1172 11A9; # (뀪; 뀪; á„ᅲᆩ; 뀪; á„ᅲᆩ; ) HANGUL SYLLABLE GGYUGG
+B02B;B02B;1101 1172 11AA;B02B;1101 1172 11AA; # (뀫; 뀫; á„ᅲᆪ; 뀫; á„ᅲᆪ; ) HANGUL SYLLABLE GGYUGS
+B02C;B02C;1101 1172 11AB;B02C;1101 1172 11AB; # (뀬; 뀬; á„ᅲᆫ; 뀬; á„ᅲᆫ; ) HANGUL SYLLABLE GGYUN
+B02D;B02D;1101 1172 11AC;B02D;1101 1172 11AC; # (뀭; 뀭; á„ᅲᆬ; 뀭; á„ᅲᆬ; ) HANGUL SYLLABLE GGYUNJ
+B02E;B02E;1101 1172 11AD;B02E;1101 1172 11AD; # (뀮; 뀮; á„ᅲᆭ; 뀮; á„ᅲᆭ; ) HANGUL SYLLABLE GGYUNH
+B02F;B02F;1101 1172 11AE;B02F;1101 1172 11AE; # (뀯; 뀯; á„ᅲᆮ; 뀯; á„ᅲᆮ; ) HANGUL SYLLABLE GGYUD
+B030;B030;1101 1172 11AF;B030;1101 1172 11AF; # (뀰; 뀰; á„ᅲᆯ; 뀰; á„ᅲᆯ; ) HANGUL SYLLABLE GGYUL
+B031;B031;1101 1172 11B0;B031;1101 1172 11B0; # (뀱; 뀱; á„ᅲᆰ; 뀱; á„ᅲᆰ; ) HANGUL SYLLABLE GGYULG
+B032;B032;1101 1172 11B1;B032;1101 1172 11B1; # (뀲; 뀲; á„ᅲᆱ; 뀲; á„ᅲᆱ; ) HANGUL SYLLABLE GGYULM
+B033;B033;1101 1172 11B2;B033;1101 1172 11B2; # (뀳; 뀳; á„ᅲᆲ; 뀳; á„ᅲᆲ; ) HANGUL SYLLABLE GGYULB
+B034;B034;1101 1172 11B3;B034;1101 1172 11B3; # (뀴; 뀴; á„ᅲᆳ; 뀴; á„ᅲᆳ; ) HANGUL SYLLABLE GGYULS
+B035;B035;1101 1172 11B4;B035;1101 1172 11B4; # (뀵; 뀵; á„ᅲᆴ; 뀵; á„ᅲᆴ; ) HANGUL SYLLABLE GGYULT
+B036;B036;1101 1172 11B5;B036;1101 1172 11B5; # (뀶; 뀶; á„ᅲᆵ; 뀶; á„ᅲᆵ; ) HANGUL SYLLABLE GGYULP
+B037;B037;1101 1172 11B6;B037;1101 1172 11B6; # (뀷; 뀷; á„ᅲᆶ; 뀷; á„ᅲᆶ; ) HANGUL SYLLABLE GGYULH
+B038;B038;1101 1172 11B7;B038;1101 1172 11B7; # (뀸; 뀸; á„ᅲᆷ; 뀸; á„ᅲᆷ; ) HANGUL SYLLABLE GGYUM
+B039;B039;1101 1172 11B8;B039;1101 1172 11B8; # (뀹; 뀹; á„ᅲᆸ; 뀹; á„ᅲᆸ; ) HANGUL SYLLABLE GGYUB
+B03A;B03A;1101 1172 11B9;B03A;1101 1172 11B9; # (뀺; 뀺; á„ᅲᆹ; 뀺; á„ᅲᆹ; ) HANGUL SYLLABLE GGYUBS
+B03B;B03B;1101 1172 11BA;B03B;1101 1172 11BA; # (뀻; 뀻; á„ᅲᆺ; 뀻; á„ᅲᆺ; ) HANGUL SYLLABLE GGYUS
+B03C;B03C;1101 1172 11BB;B03C;1101 1172 11BB; # (뀼; 뀼; á„ᅲᆻ; 뀼; á„ᅲᆻ; ) HANGUL SYLLABLE GGYUSS
+B03D;B03D;1101 1172 11BC;B03D;1101 1172 11BC; # (뀽; 뀽; á„ᅲᆼ; 뀽; á„ᅲᆼ; ) HANGUL SYLLABLE GGYUNG
+B03E;B03E;1101 1172 11BD;B03E;1101 1172 11BD; # (뀾; 뀾; á„ᅲᆽ; 뀾; á„ᅲᆽ; ) HANGUL SYLLABLE GGYUJ
+B03F;B03F;1101 1172 11BE;B03F;1101 1172 11BE; # (뀿; 뀿; á„ᅲᆾ; 뀿; á„ᅲᆾ; ) HANGUL SYLLABLE GGYUC
+B040;B040;1101 1172 11BF;B040;1101 1172 11BF; # (ë€; ë€; á„ᅲᆿ; ë€; á„ᅲᆿ; ) HANGUL SYLLABLE GGYUK
+B041;B041;1101 1172 11C0;B041;1101 1172 11C0; # (ë; ë; á„ᅲᇀ; ë; á„ᅲᇀ; ) HANGUL SYLLABLE GGYUT
+B042;B042;1101 1172 11C1;B042;1101 1172 11C1; # (ë‚; ë‚; á„á…²á‡; ë‚; á„á…²á‡; ) HANGUL SYLLABLE GGYUP
+B043;B043;1101 1172 11C2;B043;1101 1172 11C2; # (ëƒ; ëƒ; á„ᅲᇂ; ëƒ; á„ᅲᇂ; ) HANGUL SYLLABLE GGYUH
+B044;B044;1101 1173;B044;1101 1173; # (ë„; ë„; á„á…³; ë„; á„á…³; ) HANGUL SYLLABLE GGEU
+B045;B045;1101 1173 11A8;B045;1101 1173 11A8; # (ë…; ë…; á„ᅳᆨ; ë…; á„ᅳᆨ; ) HANGUL SYLLABLE GGEUG
+B046;B046;1101 1173 11A9;B046;1101 1173 11A9; # (ë†; ë†; á„ᅳᆩ; ë†; á„ᅳᆩ; ) HANGUL SYLLABLE GGEUGG
+B047;B047;1101 1173 11AA;B047;1101 1173 11AA; # (ë‡; ë‡; á„ᅳᆪ; ë‡; á„ᅳᆪ; ) HANGUL SYLLABLE GGEUGS
+B048;B048;1101 1173 11AB;B048;1101 1173 11AB; # (ëˆ; ëˆ; á„ᅳᆫ; ëˆ; á„ᅳᆫ; ) HANGUL SYLLABLE GGEUN
+B049;B049;1101 1173 11AC;B049;1101 1173 11AC; # (ë‰; ë‰; á„ᅳᆬ; ë‰; á„ᅳᆬ; ) HANGUL SYLLABLE GGEUNJ
+B04A;B04A;1101 1173 11AD;B04A;1101 1173 11AD; # (ëŠ; ëŠ; á„ᅳᆭ; ëŠ; á„ᅳᆭ; ) HANGUL SYLLABLE GGEUNH
+B04B;B04B;1101 1173 11AE;B04B;1101 1173 11AE; # (ë‹; ë‹; á„ᅳᆮ; ë‹; á„ᅳᆮ; ) HANGUL SYLLABLE GGEUD
+B04C;B04C;1101 1173 11AF;B04C;1101 1173 11AF; # (ëŒ; ëŒ; á„ᅳᆯ; ëŒ; á„ᅳᆯ; ) HANGUL SYLLABLE GGEUL
+B04D;B04D;1101 1173 11B0;B04D;1101 1173 11B0; # (ë; ë; á„ᅳᆰ; ë; á„ᅳᆰ; ) HANGUL SYLLABLE GGEULG
+B04E;B04E;1101 1173 11B1;B04E;1101 1173 11B1; # (ëŽ; ëŽ; á„ᅳᆱ; ëŽ; á„ᅳᆱ; ) HANGUL SYLLABLE GGEULM
+B04F;B04F;1101 1173 11B2;B04F;1101 1173 11B2; # (ë; ë; á„ᅳᆲ; ë; á„ᅳᆲ; ) HANGUL SYLLABLE GGEULB
+B050;B050;1101 1173 11B3;B050;1101 1173 11B3; # (ë; ë; á„ᅳᆳ; ë; á„ᅳᆳ; ) HANGUL SYLLABLE GGEULS
+B051;B051;1101 1173 11B4;B051;1101 1173 11B4; # (ë‘; ë‘; á„ᅳᆴ; ë‘; á„ᅳᆴ; ) HANGUL SYLLABLE GGEULT
+B052;B052;1101 1173 11B5;B052;1101 1173 11B5; # (ë’; ë’; á„ᅳᆵ; ë’; á„ᅳᆵ; ) HANGUL SYLLABLE GGEULP
+B053;B053;1101 1173 11B6;B053;1101 1173 11B6; # (ë“; ë“; á„ᅳᆶ; ë“; á„ᅳᆶ; ) HANGUL SYLLABLE GGEULH
+B054;B054;1101 1173 11B7;B054;1101 1173 11B7; # (ë”; ë”; á„ᅳᆷ; ë”; á„ᅳᆷ; ) HANGUL SYLLABLE GGEUM
+B055;B055;1101 1173 11B8;B055;1101 1173 11B8; # (ë•; ë•; á„ᅳᆸ; ë•; á„ᅳᆸ; ) HANGUL SYLLABLE GGEUB
+B056;B056;1101 1173 11B9;B056;1101 1173 11B9; # (ë–; ë–; á„ᅳᆹ; ë–; á„ᅳᆹ; ) HANGUL SYLLABLE GGEUBS
+B057;B057;1101 1173 11BA;B057;1101 1173 11BA; # (ë—; ë—; á„ᅳᆺ; ë—; á„ᅳᆺ; ) HANGUL SYLLABLE GGEUS
+B058;B058;1101 1173 11BB;B058;1101 1173 11BB; # (ë˜; ë˜; á„ᅳᆻ; ë˜; á„ᅳᆻ; ) HANGUL SYLLABLE GGEUSS
+B059;B059;1101 1173 11BC;B059;1101 1173 11BC; # (ë™; ë™; á„ᅳᆼ; ë™; á„ᅳᆼ; ) HANGUL SYLLABLE GGEUNG
+B05A;B05A;1101 1173 11BD;B05A;1101 1173 11BD; # (ëš; ëš; á„ᅳᆽ; ëš; á„ᅳᆽ; ) HANGUL SYLLABLE GGEUJ
+B05B;B05B;1101 1173 11BE;B05B;1101 1173 11BE; # (ë›; ë›; á„ᅳᆾ; ë›; á„ᅳᆾ; ) HANGUL SYLLABLE GGEUC
+B05C;B05C;1101 1173 11BF;B05C;1101 1173 11BF; # (ëœ; ëœ; á„ᅳᆿ; ëœ; á„ᅳᆿ; ) HANGUL SYLLABLE GGEUK
+B05D;B05D;1101 1173 11C0;B05D;1101 1173 11C0; # (ë; ë; á„ᅳᇀ; ë; á„ᅳᇀ; ) HANGUL SYLLABLE GGEUT
+B05E;B05E;1101 1173 11C1;B05E;1101 1173 11C1; # (ëž; ëž; á„á…³á‡; ëž; á„á…³á‡; ) HANGUL SYLLABLE GGEUP
+B05F;B05F;1101 1173 11C2;B05F;1101 1173 11C2; # (ëŸ; ëŸ; á„ᅳᇂ; ëŸ; á„ᅳᇂ; ) HANGUL SYLLABLE GGEUH
+B060;B060;1101 1174;B060;1101 1174; # (ë ; ë ; á„á…´; ë ; á„á…´; ) HANGUL SYLLABLE GGYI
+B061;B061;1101 1174 11A8;B061;1101 1174 11A8; # (ë¡; ë¡; á„ᅴᆨ; ë¡; á„ᅴᆨ; ) HANGUL SYLLABLE GGYIG
+B062;B062;1101 1174 11A9;B062;1101 1174 11A9; # (ë¢; ë¢; á„ᅴᆩ; ë¢; á„ᅴᆩ; ) HANGUL SYLLABLE GGYIGG
+B063;B063;1101 1174 11AA;B063;1101 1174 11AA; # (ë£; ë£; á„ᅴᆪ; ë£; á„ᅴᆪ; ) HANGUL SYLLABLE GGYIGS
+B064;B064;1101 1174 11AB;B064;1101 1174 11AB; # (ë¤; ë¤; á„ᅴᆫ; ë¤; á„ᅴᆫ; ) HANGUL SYLLABLE GGYIN
+B065;B065;1101 1174 11AC;B065;1101 1174 11AC; # (ë¥; ë¥; á„ᅴᆬ; ë¥; á„ᅴᆬ; ) HANGUL SYLLABLE GGYINJ
+B066;B066;1101 1174 11AD;B066;1101 1174 11AD; # (ë¦; ë¦; á„ᅴᆭ; ë¦; á„ᅴᆭ; ) HANGUL SYLLABLE GGYINH
+B067;B067;1101 1174 11AE;B067;1101 1174 11AE; # (ë§; ë§; á„ᅴᆮ; ë§; á„ᅴᆮ; ) HANGUL SYLLABLE GGYID
+B068;B068;1101 1174 11AF;B068;1101 1174 11AF; # (ë¨; ë¨; á„ᅴᆯ; ë¨; á„ᅴᆯ; ) HANGUL SYLLABLE GGYIL
+B069;B069;1101 1174 11B0;B069;1101 1174 11B0; # (ë©; ë©; á„ᅴᆰ; ë©; á„ᅴᆰ; ) HANGUL SYLLABLE GGYILG
+B06A;B06A;1101 1174 11B1;B06A;1101 1174 11B1; # (ëª; ëª; á„ᅴᆱ; ëª; á„ᅴᆱ; ) HANGUL SYLLABLE GGYILM
+B06B;B06B;1101 1174 11B2;B06B;1101 1174 11B2; # (ë«; ë«; á„ᅴᆲ; ë«; á„ᅴᆲ; ) HANGUL SYLLABLE GGYILB
+B06C;B06C;1101 1174 11B3;B06C;1101 1174 11B3; # (ë¬; ë¬; á„ᅴᆳ; ë¬; á„ᅴᆳ; ) HANGUL SYLLABLE GGYILS
+B06D;B06D;1101 1174 11B4;B06D;1101 1174 11B4; # (ë­; ë­; á„ᅴᆴ; ë­; á„ᅴᆴ; ) HANGUL SYLLABLE GGYILT
+B06E;B06E;1101 1174 11B5;B06E;1101 1174 11B5; # (ë®; ë®; á„ᅴᆵ; ë®; á„ᅴᆵ; ) HANGUL SYLLABLE GGYILP
+B06F;B06F;1101 1174 11B6;B06F;1101 1174 11B6; # (ë¯; ë¯; á„ᅴᆶ; ë¯; á„ᅴᆶ; ) HANGUL SYLLABLE GGYILH
+B070;B070;1101 1174 11B7;B070;1101 1174 11B7; # (ë°; ë°; á„ᅴᆷ; ë°; á„ᅴᆷ; ) HANGUL SYLLABLE GGYIM
+B071;B071;1101 1174 11B8;B071;1101 1174 11B8; # (ë±; ë±; á„ᅴᆸ; ë±; á„ᅴᆸ; ) HANGUL SYLLABLE GGYIB
+B072;B072;1101 1174 11B9;B072;1101 1174 11B9; # (ë²; ë²; á„ᅴᆹ; ë²; á„ᅴᆹ; ) HANGUL SYLLABLE GGYIBS
+B073;B073;1101 1174 11BA;B073;1101 1174 11BA; # (ë³; ë³; á„ᅴᆺ; ë³; á„ᅴᆺ; ) HANGUL SYLLABLE GGYIS
+B074;B074;1101 1174 11BB;B074;1101 1174 11BB; # (ë´; ë´; á„ᅴᆻ; ë´; á„ᅴᆻ; ) HANGUL SYLLABLE GGYISS
+B075;B075;1101 1174 11BC;B075;1101 1174 11BC; # (ëµ; ëµ; á„ᅴᆼ; ëµ; á„ᅴᆼ; ) HANGUL SYLLABLE GGYING
+B076;B076;1101 1174 11BD;B076;1101 1174 11BD; # (ë¶; ë¶; á„ᅴᆽ; ë¶; á„ᅴᆽ; ) HANGUL SYLLABLE GGYIJ
+B077;B077;1101 1174 11BE;B077;1101 1174 11BE; # (ë·; ë·; á„ᅴᆾ; ë·; á„ᅴᆾ; ) HANGUL SYLLABLE GGYIC
+B078;B078;1101 1174 11BF;B078;1101 1174 11BF; # (ë¸; ë¸; á„ᅴᆿ; ë¸; á„ᅴᆿ; ) HANGUL SYLLABLE GGYIK
+B079;B079;1101 1174 11C0;B079;1101 1174 11C0; # (ë¹; ë¹; á„ᅴᇀ; ë¹; á„ᅴᇀ; ) HANGUL SYLLABLE GGYIT
+B07A;B07A;1101 1174 11C1;B07A;1101 1174 11C1; # (ëº; ëº; á„á…´á‡; ëº; á„á…´á‡; ) HANGUL SYLLABLE GGYIP
+B07B;B07B;1101 1174 11C2;B07B;1101 1174 11C2; # (ë»; ë»; á„ᅴᇂ; ë»; á„ᅴᇂ; ) HANGUL SYLLABLE GGYIH
+B07C;B07C;1101 1175;B07C;1101 1175; # (ë¼; ë¼; á„á…µ; ë¼; á„á…µ; ) HANGUL SYLLABLE GGI
+B07D;B07D;1101 1175 11A8;B07D;1101 1175 11A8; # (ë½; ë½; á„ᅵᆨ; ë½; á„ᅵᆨ; ) HANGUL SYLLABLE GGIG
+B07E;B07E;1101 1175 11A9;B07E;1101 1175 11A9; # (ë¾; ë¾; á„ᅵᆩ; ë¾; á„ᅵᆩ; ) HANGUL SYLLABLE GGIGG
+B07F;B07F;1101 1175 11AA;B07F;1101 1175 11AA; # (ë¿; ë¿; á„ᅵᆪ; ë¿; á„ᅵᆪ; ) HANGUL SYLLABLE GGIGS
+B080;B080;1101 1175 11AB;B080;1101 1175 11AB; # (ë‚€; ë‚€; á„ᅵᆫ; ë‚€; á„ᅵᆫ; ) HANGUL SYLLABLE GGIN
+B081;B081;1101 1175 11AC;B081;1101 1175 11AC; # (ë‚; ë‚; á„ᅵᆬ; ë‚; á„ᅵᆬ; ) HANGUL SYLLABLE GGINJ
+B082;B082;1101 1175 11AD;B082;1101 1175 11AD; # (ë‚‚; ë‚‚; á„ᅵᆭ; ë‚‚; á„ᅵᆭ; ) HANGUL SYLLABLE GGINH
+B083;B083;1101 1175 11AE;B083;1101 1175 11AE; # (낃; 낃; á„ᅵᆮ; 낃; á„ᅵᆮ; ) HANGUL SYLLABLE GGID
+B084;B084;1101 1175 11AF;B084;1101 1175 11AF; # (ë‚„; ë‚„; á„ᅵᆯ; ë‚„; á„ᅵᆯ; ) HANGUL SYLLABLE GGIL
+B085;B085;1101 1175 11B0;B085;1101 1175 11B0; # (ë‚…; ë‚…; á„ᅵᆰ; ë‚…; á„ᅵᆰ; ) HANGUL SYLLABLE GGILG
+B086;B086;1101 1175 11B1;B086;1101 1175 11B1; # (낆; 낆; á„ᅵᆱ; 낆; á„ᅵᆱ; ) HANGUL SYLLABLE GGILM
+B087;B087;1101 1175 11B2;B087;1101 1175 11B2; # (낇; 낇; á„ᅵᆲ; 낇; á„ᅵᆲ; ) HANGUL SYLLABLE GGILB
+B088;B088;1101 1175 11B3;B088;1101 1175 11B3; # (낈; 낈; á„ᅵᆳ; 낈; á„ᅵᆳ; ) HANGUL SYLLABLE GGILS
+B089;B089;1101 1175 11B4;B089;1101 1175 11B4; # (낉; 낉; á„ᅵᆴ; 낉; á„ᅵᆴ; ) HANGUL SYLLABLE GGILT
+B08A;B08A;1101 1175 11B5;B08A;1101 1175 11B5; # (ë‚Š; ë‚Š; á„ᅵᆵ; ë‚Š; á„ᅵᆵ; ) HANGUL SYLLABLE GGILP
+B08B;B08B;1101 1175 11B6;B08B;1101 1175 11B6; # (ë‚‹; ë‚‹; á„ᅵᆶ; ë‚‹; á„ᅵᆶ; ) HANGUL SYLLABLE GGILH
+B08C;B08C;1101 1175 11B7;B08C;1101 1175 11B7; # (ë‚Œ; ë‚Œ; á„ᅵᆷ; ë‚Œ; á„ᅵᆷ; ) HANGUL SYLLABLE GGIM
+B08D;B08D;1101 1175 11B8;B08D;1101 1175 11B8; # (ë‚; ë‚; á„ᅵᆸ; ë‚; á„ᅵᆸ; ) HANGUL SYLLABLE GGIB
+B08E;B08E;1101 1175 11B9;B08E;1101 1175 11B9; # (ë‚Ž; ë‚Ž; á„ᅵᆹ; ë‚Ž; á„ᅵᆹ; ) HANGUL SYLLABLE GGIBS
+B08F;B08F;1101 1175 11BA;B08F;1101 1175 11BA; # (ë‚; ë‚; á„ᅵᆺ; ë‚; á„ᅵᆺ; ) HANGUL SYLLABLE GGIS
+B090;B090;1101 1175 11BB;B090;1101 1175 11BB; # (ë‚; ë‚; á„ᅵᆻ; ë‚; á„ᅵᆻ; ) HANGUL SYLLABLE GGISS
+B091;B091;1101 1175 11BC;B091;1101 1175 11BC; # (ë‚‘; ë‚‘; á„ᅵᆼ; ë‚‘; á„ᅵᆼ; ) HANGUL SYLLABLE GGING
+B092;B092;1101 1175 11BD;B092;1101 1175 11BD; # (ë‚’; ë‚’; á„ᅵᆽ; ë‚’; á„ᅵᆽ; ) HANGUL SYLLABLE GGIJ
+B093;B093;1101 1175 11BE;B093;1101 1175 11BE; # (ë‚“; ë‚“; á„ᅵᆾ; ë‚“; á„ᅵᆾ; ) HANGUL SYLLABLE GGIC
+B094;B094;1101 1175 11BF;B094;1101 1175 11BF; # (ë‚”; ë‚”; á„ᅵᆿ; ë‚”; á„ᅵᆿ; ) HANGUL SYLLABLE GGIK
+B095;B095;1101 1175 11C0;B095;1101 1175 11C0; # (ë‚•; ë‚•; á„ᅵᇀ; ë‚•; á„ᅵᇀ; ) HANGUL SYLLABLE GGIT
+B096;B096;1101 1175 11C1;B096;1101 1175 11C1; # (ë‚–; ë‚–; á„á…µá‡; ë‚–; á„á…µá‡; ) HANGUL SYLLABLE GGIP
+B097;B097;1101 1175 11C2;B097;1101 1175 11C2; # (ë‚—; ë‚—; á„ᅵᇂ; ë‚—; á„ᅵᇂ; ) HANGUL SYLLABLE GGIH
+B098;B098;1102 1161;B098;1102 1161; # (나; 나; 나; 나; 나; ) HANGUL SYLLABLE NA
+B099;B099;1102 1161 11A8;B099;1102 1161 11A8; # (낙; 낙; 낙; 낙; 낙; ) HANGUL SYLLABLE NAG
+B09A;B09A;1102 1161 11A9;B09A;1102 1161 11A9; # (낚; 낚; 낚; 낚; 낚; ) HANGUL SYLLABLE NAGG
+B09B;B09B;1102 1161 11AA;B09B;1102 1161 11AA; # (낛; 낛; 낛; 낛; 낛; ) HANGUL SYLLABLE NAGS
+B09C;B09C;1102 1161 11AB;B09C;1102 1161 11AB; # (난; 난; 난; 난; 난; ) HANGUL SYLLABLE NAN
+B09D;B09D;1102 1161 11AC;B09D;1102 1161 11AC; # (ë‚; ë‚; 낝; ë‚; 낝; ) HANGUL SYLLABLE NANJ
+B09E;B09E;1102 1161 11AD;B09E;1102 1161 11AD; # (낞; 낞; 낞; 낞; 낞; ) HANGUL SYLLABLE NANH
+B09F;B09F;1102 1161 11AE;B09F;1102 1161 11AE; # (낟; 낟; 낟; 낟; 낟; ) HANGUL SYLLABLE NAD
+B0A0;B0A0;1102 1161 11AF;B0A0;1102 1161 11AF; # (날; 날; 날; 날; 날; ) HANGUL SYLLABLE NAL
+B0A1;B0A1;1102 1161 11B0;B0A1;1102 1161 11B0; # (낡; 낡; 낡; 낡; 낡; ) HANGUL SYLLABLE NALG
+B0A2;B0A2;1102 1161 11B1;B0A2;1102 1161 11B1; # (낢; 낢; 낢; 낢; 낢; ) HANGUL SYLLABLE NALM
+B0A3;B0A3;1102 1161 11B2;B0A3;1102 1161 11B2; # (낣; 낣; 낣; 낣; 낣; ) HANGUL SYLLABLE NALB
+B0A4;B0A4;1102 1161 11B3;B0A4;1102 1161 11B3; # (낤; 낤; 낤; 낤; 낤; ) HANGUL SYLLABLE NALS
+B0A5;B0A5;1102 1161 11B4;B0A5;1102 1161 11B4; # (낥; 낥; 낥; 낥; 낥; ) HANGUL SYLLABLE NALT
+B0A6;B0A6;1102 1161 11B5;B0A6;1102 1161 11B5; # (낦; 낦; 낦; 낦; 낦; ) HANGUL SYLLABLE NALP
+B0A7;B0A7;1102 1161 11B6;B0A7;1102 1161 11B6; # (낧; 낧; 낧; 낧; 낧; ) HANGUL SYLLABLE NALH
+B0A8;B0A8;1102 1161 11B7;B0A8;1102 1161 11B7; # (남; 남; 남; 남; 남; ) HANGUL SYLLABLE NAM
+B0A9;B0A9;1102 1161 11B8;B0A9;1102 1161 11B8; # (납; 납; 납; 납; 납; ) HANGUL SYLLABLE NAB
+B0AA;B0AA;1102 1161 11B9;B0AA;1102 1161 11B9; # (낪; 낪; 낪; 낪; 낪; ) HANGUL SYLLABLE NABS
+B0AB;B0AB;1102 1161 11BA;B0AB;1102 1161 11BA; # (낫; 낫; 낫; 낫; 낫; ) HANGUL SYLLABLE NAS
+B0AC;B0AC;1102 1161 11BB;B0AC;1102 1161 11BB; # (났; 났; 났; 났; 났; ) HANGUL SYLLABLE NASS
+B0AD;B0AD;1102 1161 11BC;B0AD;1102 1161 11BC; # (낭; 낭; 낭; 낭; 낭; ) HANGUL SYLLABLE NANG
+B0AE;B0AE;1102 1161 11BD;B0AE;1102 1161 11BD; # (낮; 낮; 낮; 낮; 낮; ) HANGUL SYLLABLE NAJ
+B0AF;B0AF;1102 1161 11BE;B0AF;1102 1161 11BE; # (낯; 낯; 낯; 낯; 낯; ) HANGUL SYLLABLE NAC
+B0B0;B0B0;1102 1161 11BF;B0B0;1102 1161 11BF; # (낰; 낰; 낰; 낰; 낰; ) HANGUL SYLLABLE NAK
+B0B1;B0B1;1102 1161 11C0;B0B1;1102 1161 11C0; # (낱; 낱; 낱; 낱; 낱; ) HANGUL SYLLABLE NAT
+B0B2;B0B2;1102 1161 11C1;B0B2;1102 1161 11C1; # (낲; 낲; á„‚á…¡á‡; 낲; á„‚á…¡á‡; ) HANGUL SYLLABLE NAP
+B0B3;B0B3;1102 1161 11C2;B0B3;1102 1161 11C2; # (낳; 낳; 낳; 낳; 낳; ) HANGUL SYLLABLE NAH
+B0B4;B0B4;1102 1162;B0B4;1102 1162; # (ë‚´; ë‚´; á„‚á…¢; ë‚´; á„‚á…¢; ) HANGUL SYLLABLE NAE
+B0B5;B0B5;1102 1162 11A8;B0B5;1102 1162 11A8; # (낵; 낵; 낵; 낵; 낵; ) HANGUL SYLLABLE NAEG
+B0B6;B0B6;1102 1162 11A9;B0B6;1102 1162 11A9; # (낶; 낶; 낶; 낶; 낶; ) HANGUL SYLLABLE NAEGG
+B0B7;B0B7;1102 1162 11AA;B0B7;1102 1162 11AA; # (낷; 낷; 낷; 낷; 낷; ) HANGUL SYLLABLE NAEGS
+B0B8;B0B8;1102 1162 11AB;B0B8;1102 1162 11AB; # (낸; 낸; 낸; 낸; 낸; ) HANGUL SYLLABLE NAEN
+B0B9;B0B9;1102 1162 11AC;B0B9;1102 1162 11AC; # (낹; 낹; 낹; 낹; 낹; ) HANGUL SYLLABLE NAENJ
+B0BA;B0BA;1102 1162 11AD;B0BA;1102 1162 11AD; # (낺; 낺; 낺; 낺; 낺; ) HANGUL SYLLABLE NAENH
+B0BB;B0BB;1102 1162 11AE;B0BB;1102 1162 11AE; # (낻; 낻; 낻; 낻; 낻; ) HANGUL SYLLABLE NAED
+B0BC;B0BC;1102 1162 11AF;B0BC;1102 1162 11AF; # (낼; 낼; 낼; 낼; 낼; ) HANGUL SYLLABLE NAEL
+B0BD;B0BD;1102 1162 11B0;B0BD;1102 1162 11B0; # (낽; 낽; 낽; 낽; 낽; ) HANGUL SYLLABLE NAELG
+B0BE;B0BE;1102 1162 11B1;B0BE;1102 1162 11B1; # (낾; 낾; 낾; 낾; 낾; ) HANGUL SYLLABLE NAELM
+B0BF;B0BF;1102 1162 11B2;B0BF;1102 1162 11B2; # (낿; 낿; 낿; 낿; 낿; ) HANGUL SYLLABLE NAELB
+B0C0;B0C0;1102 1162 11B3;B0C0;1102 1162 11B3; # (냀; 냀; 냀; 냀; 냀; ) HANGUL SYLLABLE NAELS
+B0C1;B0C1;1102 1162 11B4;B0C1;1102 1162 11B4; # (ëƒ; ëƒ; 냁; ëƒ; 냁; ) HANGUL SYLLABLE NAELT
+B0C2;B0C2;1102 1162 11B5;B0C2;1102 1162 11B5; # (냂; 냂; 냂; 냂; 냂; ) HANGUL SYLLABLE NAELP
+B0C3;B0C3;1102 1162 11B6;B0C3;1102 1162 11B6; # (냃; 냃; 냃; 냃; 냃; ) HANGUL SYLLABLE NAELH
+B0C4;B0C4;1102 1162 11B7;B0C4;1102 1162 11B7; # (냄; 냄; 냄; 냄; 냄; ) HANGUL SYLLABLE NAEM
+B0C5;B0C5;1102 1162 11B8;B0C5;1102 1162 11B8; # (냅; 냅; 냅; 냅; 냅; ) HANGUL SYLLABLE NAEB
+B0C6;B0C6;1102 1162 11B9;B0C6;1102 1162 11B9; # (냆; 냆; 냆; 냆; 냆; ) HANGUL SYLLABLE NAEBS
+B0C7;B0C7;1102 1162 11BA;B0C7;1102 1162 11BA; # (냇; 냇; 냇; 냇; 냇; ) HANGUL SYLLABLE NAES
+B0C8;B0C8;1102 1162 11BB;B0C8;1102 1162 11BB; # (냈; 냈; 냈; 냈; 냈; ) HANGUL SYLLABLE NAESS
+B0C9;B0C9;1102 1162 11BC;B0C9;1102 1162 11BC; # (냉; 냉; 냉; 냉; 냉; ) HANGUL SYLLABLE NAENG
+B0CA;B0CA;1102 1162 11BD;B0CA;1102 1162 11BD; # (냊; 냊; 냊; 냊; 냊; ) HANGUL SYLLABLE NAEJ
+B0CB;B0CB;1102 1162 11BE;B0CB;1102 1162 11BE; # (냋; 냋; 냋; 냋; 냋; ) HANGUL SYLLABLE NAEC
+B0CC;B0CC;1102 1162 11BF;B0CC;1102 1162 11BF; # (냌; 냌; 냌; 냌; 냌; ) HANGUL SYLLABLE NAEK
+B0CD;B0CD;1102 1162 11C0;B0CD;1102 1162 11C0; # (ëƒ; ëƒ; 냍; ëƒ; 냍; ) HANGUL SYLLABLE NAET
+B0CE;B0CE;1102 1162 11C1;B0CE;1102 1162 11C1; # (냎; 냎; á„‚á…¢á‡; 냎; á„‚á…¢á‡; ) HANGUL SYLLABLE NAEP
+B0CF;B0CF;1102 1162 11C2;B0CF;1102 1162 11C2; # (ëƒ; ëƒ; 냏; ëƒ; 냏; ) HANGUL SYLLABLE NAEH
+B0D0;B0D0;1102 1163;B0D0;1102 1163; # (ëƒ; ëƒ; á„‚á…£; ëƒ; á„‚á…£; ) HANGUL SYLLABLE NYA
+B0D1;B0D1;1102 1163 11A8;B0D1;1102 1163 11A8; # (냑; 냑; 냑; 냑; 냑; ) HANGUL SYLLABLE NYAG
+B0D2;B0D2;1102 1163 11A9;B0D2;1102 1163 11A9; # (냒; 냒; 냒; 냒; 냒; ) HANGUL SYLLABLE NYAGG
+B0D3;B0D3;1102 1163 11AA;B0D3;1102 1163 11AA; # (냓; 냓; 냓; 냓; 냓; ) HANGUL SYLLABLE NYAGS
+B0D4;B0D4;1102 1163 11AB;B0D4;1102 1163 11AB; # (냔; 냔; 냔; 냔; 냔; ) HANGUL SYLLABLE NYAN
+B0D5;B0D5;1102 1163 11AC;B0D5;1102 1163 11AC; # (냕; 냕; 냕; 냕; 냕; ) HANGUL SYLLABLE NYANJ
+B0D6;B0D6;1102 1163 11AD;B0D6;1102 1163 11AD; # (냖; 냖; 냖; 냖; 냖; ) HANGUL SYLLABLE NYANH
+B0D7;B0D7;1102 1163 11AE;B0D7;1102 1163 11AE; # (냗; 냗; 냗; 냗; 냗; ) HANGUL SYLLABLE NYAD
+B0D8;B0D8;1102 1163 11AF;B0D8;1102 1163 11AF; # (냘; 냘; 냘; 냘; 냘; ) HANGUL SYLLABLE NYAL
+B0D9;B0D9;1102 1163 11B0;B0D9;1102 1163 11B0; # (냙; 냙; 냙; 냙; 냙; ) HANGUL SYLLABLE NYALG
+B0DA;B0DA;1102 1163 11B1;B0DA;1102 1163 11B1; # (냚; 냚; 냚; 냚; 냚; ) HANGUL SYLLABLE NYALM
+B0DB;B0DB;1102 1163 11B2;B0DB;1102 1163 11B2; # (냛; 냛; 냛; 냛; 냛; ) HANGUL SYLLABLE NYALB
+B0DC;B0DC;1102 1163 11B3;B0DC;1102 1163 11B3; # (냜; 냜; 냜; 냜; 냜; ) HANGUL SYLLABLE NYALS
+B0DD;B0DD;1102 1163 11B4;B0DD;1102 1163 11B4; # (ëƒ; ëƒ; 냝; ëƒ; 냝; ) HANGUL SYLLABLE NYALT
+B0DE;B0DE;1102 1163 11B5;B0DE;1102 1163 11B5; # (냞; 냞; 냞; 냞; 냞; ) HANGUL SYLLABLE NYALP
+B0DF;B0DF;1102 1163 11B6;B0DF;1102 1163 11B6; # (냟; 냟; 냟; 냟; 냟; ) HANGUL SYLLABLE NYALH
+B0E0;B0E0;1102 1163 11B7;B0E0;1102 1163 11B7; # (냠; 냠; 냠; 냠; 냠; ) HANGUL SYLLABLE NYAM
+B0E1;B0E1;1102 1163 11B8;B0E1;1102 1163 11B8; # (냡; 냡; 냡; 냡; 냡; ) HANGUL SYLLABLE NYAB
+B0E2;B0E2;1102 1163 11B9;B0E2;1102 1163 11B9; # (냢; 냢; 냢; 냢; 냢; ) HANGUL SYLLABLE NYABS
+B0E3;B0E3;1102 1163 11BA;B0E3;1102 1163 11BA; # (냣; 냣; 냣; 냣; 냣; ) HANGUL SYLLABLE NYAS
+B0E4;B0E4;1102 1163 11BB;B0E4;1102 1163 11BB; # (냤; 냤; 냤; 냤; 냤; ) HANGUL SYLLABLE NYASS
+B0E5;B0E5;1102 1163 11BC;B0E5;1102 1163 11BC; # (냥; 냥; 냥; 냥; 냥; ) HANGUL SYLLABLE NYANG
+B0E6;B0E6;1102 1163 11BD;B0E6;1102 1163 11BD; # (냦; 냦; 냦; 냦; 냦; ) HANGUL SYLLABLE NYAJ
+B0E7;B0E7;1102 1163 11BE;B0E7;1102 1163 11BE; # (냧; 냧; 냧; 냧; 냧; ) HANGUL SYLLABLE NYAC
+B0E8;B0E8;1102 1163 11BF;B0E8;1102 1163 11BF; # (냨; 냨; 냨; 냨; 냨; ) HANGUL SYLLABLE NYAK
+B0E9;B0E9;1102 1163 11C0;B0E9;1102 1163 11C0; # (냩; 냩; 냩; 냩; 냩; ) HANGUL SYLLABLE NYAT
+B0EA;B0EA;1102 1163 11C1;B0EA;1102 1163 11C1; # (냪; 냪; á„‚á…£á‡; 냪; á„‚á…£á‡; ) HANGUL SYLLABLE NYAP
+B0EB;B0EB;1102 1163 11C2;B0EB;1102 1163 11C2; # (냫; 냫; 냫; 냫; 냫; ) HANGUL SYLLABLE NYAH
+B0EC;B0EC;1102 1164;B0EC;1102 1164; # (냬; 냬; 냬; 냬; 냬; ) HANGUL SYLLABLE NYAE
+B0ED;B0ED;1102 1164 11A8;B0ED;1102 1164 11A8; # (냭; 냭; 냭; 냭; 냭; ) HANGUL SYLLABLE NYAEG
+B0EE;B0EE;1102 1164 11A9;B0EE;1102 1164 11A9; # (냮; 냮; 냮; 냮; 냮; ) HANGUL SYLLABLE NYAEGG
+B0EF;B0EF;1102 1164 11AA;B0EF;1102 1164 11AA; # (냯; 냯; 냯; 냯; 냯; ) HANGUL SYLLABLE NYAEGS
+B0F0;B0F0;1102 1164 11AB;B0F0;1102 1164 11AB; # (냰; 냰; 냰; 냰; 냰; ) HANGUL SYLLABLE NYAEN
+B0F1;B0F1;1102 1164 11AC;B0F1;1102 1164 11AC; # (냱; 냱; 냱; 냱; 냱; ) HANGUL SYLLABLE NYAENJ
+B0F2;B0F2;1102 1164 11AD;B0F2;1102 1164 11AD; # (냲; 냲; 냲; 냲; 냲; ) HANGUL SYLLABLE NYAENH
+B0F3;B0F3;1102 1164 11AE;B0F3;1102 1164 11AE; # (냳; 냳; 냳; 냳; 냳; ) HANGUL SYLLABLE NYAED
+B0F4;B0F4;1102 1164 11AF;B0F4;1102 1164 11AF; # (냴; 냴; 냴; 냴; 냴; ) HANGUL SYLLABLE NYAEL
+B0F5;B0F5;1102 1164 11B0;B0F5;1102 1164 11B0; # (냵; 냵; 냵; 냵; 냵; ) HANGUL SYLLABLE NYAELG
+B0F6;B0F6;1102 1164 11B1;B0F6;1102 1164 11B1; # (냶; 냶; 냶; 냶; 냶; ) HANGUL SYLLABLE NYAELM
+B0F7;B0F7;1102 1164 11B2;B0F7;1102 1164 11B2; # (냷; 냷; 냷; 냷; 냷; ) HANGUL SYLLABLE NYAELB
+B0F8;B0F8;1102 1164 11B3;B0F8;1102 1164 11B3; # (냸; 냸; 냸; 냸; 냸; ) HANGUL SYLLABLE NYAELS
+B0F9;B0F9;1102 1164 11B4;B0F9;1102 1164 11B4; # (냹; 냹; 냹; 냹; 냹; ) HANGUL SYLLABLE NYAELT
+B0FA;B0FA;1102 1164 11B5;B0FA;1102 1164 11B5; # (냺; 냺; 냺; 냺; 냺; ) HANGUL SYLLABLE NYAELP
+B0FB;B0FB;1102 1164 11B6;B0FB;1102 1164 11B6; # (냻; 냻; 냻; 냻; 냻; ) HANGUL SYLLABLE NYAELH
+B0FC;B0FC;1102 1164 11B7;B0FC;1102 1164 11B7; # (냼; 냼; 냼; 냼; 냼; ) HANGUL SYLLABLE NYAEM
+B0FD;B0FD;1102 1164 11B8;B0FD;1102 1164 11B8; # (냽; 냽; 냽; 냽; 냽; ) HANGUL SYLLABLE NYAEB
+B0FE;B0FE;1102 1164 11B9;B0FE;1102 1164 11B9; # (냾; 냾; 냾; 냾; 냾; ) HANGUL SYLLABLE NYAEBS
+B0FF;B0FF;1102 1164 11BA;B0FF;1102 1164 11BA; # (냿; 냿; 냿; 냿; 냿; ) HANGUL SYLLABLE NYAES
+B100;B100;1102 1164 11BB;B100;1102 1164 11BB; # (넀; 넀; 넀; 넀; 넀; ) HANGUL SYLLABLE NYAESS
+B101;B101;1102 1164 11BC;B101;1102 1164 11BC; # (ë„; ë„; 넁; ë„; 넁; ) HANGUL SYLLABLE NYAENG
+B102;B102;1102 1164 11BD;B102;1102 1164 11BD; # (넂; 넂; 넂; 넂; 넂; ) HANGUL SYLLABLE NYAEJ
+B103;B103;1102 1164 11BE;B103;1102 1164 11BE; # (넃; 넃; 넃; 넃; 넃; ) HANGUL SYLLABLE NYAEC
+B104;B104;1102 1164 11BF;B104;1102 1164 11BF; # (넄; 넄; 넄; 넄; 넄; ) HANGUL SYLLABLE NYAEK
+B105;B105;1102 1164 11C0;B105;1102 1164 11C0; # (넅; 넅; 넅; 넅; 넅; ) HANGUL SYLLABLE NYAET
+B106;B106;1102 1164 11C1;B106;1102 1164 11C1; # (넆; 넆; á„‚á…¤á‡; 넆; á„‚á…¤á‡; ) HANGUL SYLLABLE NYAEP
+B107;B107;1102 1164 11C2;B107;1102 1164 11C2; # (넇; 넇; 넇; 넇; 넇; ) HANGUL SYLLABLE NYAEH
+B108;B108;1102 1165;B108;1102 1165; # (너; 너; 너; 너; 너; ) HANGUL SYLLABLE NEO
+B109;B109;1102 1165 11A8;B109;1102 1165 11A8; # (넉; 넉; 넉; 넉; 넉; ) HANGUL SYLLABLE NEOG
+B10A;B10A;1102 1165 11A9;B10A;1102 1165 11A9; # (넊; 넊; 넊; 넊; 넊; ) HANGUL SYLLABLE NEOGG
+B10B;B10B;1102 1165 11AA;B10B;1102 1165 11AA; # (넋; 넋; 넋; 넋; 넋; ) HANGUL SYLLABLE NEOGS
+B10C;B10C;1102 1165 11AB;B10C;1102 1165 11AB; # (넌; 넌; 넌; 넌; 넌; ) HANGUL SYLLABLE NEON
+B10D;B10D;1102 1165 11AC;B10D;1102 1165 11AC; # (ë„; ë„; 넍; ë„; 넍; ) HANGUL SYLLABLE NEONJ
+B10E;B10E;1102 1165 11AD;B10E;1102 1165 11AD; # (넎; 넎; 넎; 넎; 넎; ) HANGUL SYLLABLE NEONH
+B10F;B10F;1102 1165 11AE;B10F;1102 1165 11AE; # (ë„; ë„; 넏; ë„; 넏; ) HANGUL SYLLABLE NEOD
+B110;B110;1102 1165 11AF;B110;1102 1165 11AF; # (ë„; ë„; 널; ë„; 널; ) HANGUL SYLLABLE NEOL
+B111;B111;1102 1165 11B0;B111;1102 1165 11B0; # (넑; 넑; 넑; 넑; 넑; ) HANGUL SYLLABLE NEOLG
+B112;B112;1102 1165 11B1;B112;1102 1165 11B1; # (넒; 넒; 넒; 넒; 넒; ) HANGUL SYLLABLE NEOLM
+B113;B113;1102 1165 11B2;B113;1102 1165 11B2; # (넓; 넓; 넓; 넓; 넓; ) HANGUL SYLLABLE NEOLB
+B114;B114;1102 1165 11B3;B114;1102 1165 11B3; # (넔; 넔; 넔; 넔; 넔; ) HANGUL SYLLABLE NEOLS
+B115;B115;1102 1165 11B4;B115;1102 1165 11B4; # (넕; 넕; 넕; 넕; 넕; ) HANGUL SYLLABLE NEOLT
+B116;B116;1102 1165 11B5;B116;1102 1165 11B5; # (넖; 넖; 넖; 넖; 넖; ) HANGUL SYLLABLE NEOLP
+B117;B117;1102 1165 11B6;B117;1102 1165 11B6; # (넗; 넗; 넗; 넗; 넗; ) HANGUL SYLLABLE NEOLH
+B118;B118;1102 1165 11B7;B118;1102 1165 11B7; # (넘; 넘; 넘; 넘; 넘; ) HANGUL SYLLABLE NEOM
+B119;B119;1102 1165 11B8;B119;1102 1165 11B8; # (넙; 넙; 넙; 넙; 넙; ) HANGUL SYLLABLE NEOB
+B11A;B11A;1102 1165 11B9;B11A;1102 1165 11B9; # (넚; 넚; 넚; 넚; 넚; ) HANGUL SYLLABLE NEOBS
+B11B;B11B;1102 1165 11BA;B11B;1102 1165 11BA; # (넛; 넛; 넛; 넛; 넛; ) HANGUL SYLLABLE NEOS
+B11C;B11C;1102 1165 11BB;B11C;1102 1165 11BB; # (넜; 넜; 넜; 넜; 넜; ) HANGUL SYLLABLE NEOSS
+B11D;B11D;1102 1165 11BC;B11D;1102 1165 11BC; # (ë„; ë„; 넝; ë„; 넝; ) HANGUL SYLLABLE NEONG
+B11E;B11E;1102 1165 11BD;B11E;1102 1165 11BD; # (넞; 넞; 넞; 넞; 넞; ) HANGUL SYLLABLE NEOJ
+B11F;B11F;1102 1165 11BE;B11F;1102 1165 11BE; # (넟; 넟; 넟; 넟; 넟; ) HANGUL SYLLABLE NEOC
+B120;B120;1102 1165 11BF;B120;1102 1165 11BF; # (넠; 넠; 넠; 넠; 넠; ) HANGUL SYLLABLE NEOK
+B121;B121;1102 1165 11C0;B121;1102 1165 11C0; # (넡; 넡; 넡; 넡; 넡; ) HANGUL SYLLABLE NEOT
+B122;B122;1102 1165 11C1;B122;1102 1165 11C1; # (ë„¢; ë„¢; á„‚á…¥á‡; ë„¢; á„‚á…¥á‡; ) HANGUL SYLLABLE NEOP
+B123;B123;1102 1165 11C2;B123;1102 1165 11C2; # (넣; 넣; 넣; 넣; 넣; ) HANGUL SYLLABLE NEOH
+B124;B124;1102 1166;B124;1102 1166; # (네; 네; 네; 네; 네; ) HANGUL SYLLABLE NE
+B125;B125;1102 1166 11A8;B125;1102 1166 11A8; # (넥; 넥; 넥; 넥; 넥; ) HANGUL SYLLABLE NEG
+B126;B126;1102 1166 11A9;B126;1102 1166 11A9; # (넦; 넦; 넦; 넦; 넦; ) HANGUL SYLLABLE NEGG
+B127;B127;1102 1166 11AA;B127;1102 1166 11AA; # (넧; 넧; 넧; 넧; 넧; ) HANGUL SYLLABLE NEGS
+B128;B128;1102 1166 11AB;B128;1102 1166 11AB; # (넨; 넨; 넨; 넨; 넨; ) HANGUL SYLLABLE NEN
+B129;B129;1102 1166 11AC;B129;1102 1166 11AC; # (넩; 넩; 넩; 넩; 넩; ) HANGUL SYLLABLE NENJ
+B12A;B12A;1102 1166 11AD;B12A;1102 1166 11AD; # (넪; 넪; 넪; 넪; 넪; ) HANGUL SYLLABLE NENH
+B12B;B12B;1102 1166 11AE;B12B;1102 1166 11AE; # (넫; 넫; 넫; 넫; 넫; ) HANGUL SYLLABLE NED
+B12C;B12C;1102 1166 11AF;B12C;1102 1166 11AF; # (넬; 넬; 넬; 넬; 넬; ) HANGUL SYLLABLE NEL
+B12D;B12D;1102 1166 11B0;B12D;1102 1166 11B0; # (넭; 넭; 넭; 넭; 넭; ) HANGUL SYLLABLE NELG
+B12E;B12E;1102 1166 11B1;B12E;1102 1166 11B1; # (넮; 넮; 넮; 넮; 넮; ) HANGUL SYLLABLE NELM
+B12F;B12F;1102 1166 11B2;B12F;1102 1166 11B2; # (넯; 넯; 넯; 넯; 넯; ) HANGUL SYLLABLE NELB
+B130;B130;1102 1166 11B3;B130;1102 1166 11B3; # (넰; 넰; 넰; 넰; 넰; ) HANGUL SYLLABLE NELS
+B131;B131;1102 1166 11B4;B131;1102 1166 11B4; # (넱; 넱; 넱; 넱; 넱; ) HANGUL SYLLABLE NELT
+B132;B132;1102 1166 11B5;B132;1102 1166 11B5; # (넲; 넲; 넲; 넲; 넲; ) HANGUL SYLLABLE NELP
+B133;B133;1102 1166 11B6;B133;1102 1166 11B6; # (넳; 넳; 넳; 넳; 넳; ) HANGUL SYLLABLE NELH
+B134;B134;1102 1166 11B7;B134;1102 1166 11B7; # (넴; 넴; 넴; 넴; 넴; ) HANGUL SYLLABLE NEM
+B135;B135;1102 1166 11B8;B135;1102 1166 11B8; # (넵; 넵; 넵; 넵; 넵; ) HANGUL SYLLABLE NEB
+B136;B136;1102 1166 11B9;B136;1102 1166 11B9; # (넶; 넶; 넶; 넶; 넶; ) HANGUL SYLLABLE NEBS
+B137;B137;1102 1166 11BA;B137;1102 1166 11BA; # (넷; 넷; 넷; 넷; 넷; ) HANGUL SYLLABLE NES
+B138;B138;1102 1166 11BB;B138;1102 1166 11BB; # (넸; 넸; 넸; 넸; 넸; ) HANGUL SYLLABLE NESS
+B139;B139;1102 1166 11BC;B139;1102 1166 11BC; # (넹; 넹; 넹; 넹; 넹; ) HANGUL SYLLABLE NENG
+B13A;B13A;1102 1166 11BD;B13A;1102 1166 11BD; # (넺; 넺; 넺; 넺; 넺; ) HANGUL SYLLABLE NEJ
+B13B;B13B;1102 1166 11BE;B13B;1102 1166 11BE; # (넻; 넻; 넻; 넻; 넻; ) HANGUL SYLLABLE NEC
+B13C;B13C;1102 1166 11BF;B13C;1102 1166 11BF; # (넼; 넼; 넼; 넼; 넼; ) HANGUL SYLLABLE NEK
+B13D;B13D;1102 1166 11C0;B13D;1102 1166 11C0; # (넽; 넽; 넽; 넽; 넽; ) HANGUL SYLLABLE NET
+B13E;B13E;1102 1166 11C1;B13E;1102 1166 11C1; # (넾; 넾; á„‚á…¦á‡; 넾; á„‚á…¦á‡; ) HANGUL SYLLABLE NEP
+B13F;B13F;1102 1166 11C2;B13F;1102 1166 11C2; # (넿; 넿; 넿; 넿; 넿; ) HANGUL SYLLABLE NEH
+B140;B140;1102 1167;B140;1102 1167; # (ë…€; ë…€; á„‚á…§; ë…€; á„‚á…§; ) HANGUL SYLLABLE NYEO
+B141;B141;1102 1167 11A8;B141;1102 1167 11A8; # (ë…; ë…; 녁; ë…; 녁; ) HANGUL SYLLABLE NYEOG
+B142;B142;1102 1167 11A9;B142;1102 1167 11A9; # (녂; 녂; 녂; 녂; 녂; ) HANGUL SYLLABLE NYEOGG
+B143;B143;1102 1167 11AA;B143;1102 1167 11AA; # (녃; 녃; 녃; 녃; 녃; ) HANGUL SYLLABLE NYEOGS
+B144;B144;1102 1167 11AB;B144;1102 1167 11AB; # (년; 년; 년; 년; 년; ) HANGUL SYLLABLE NYEON
+B145;B145;1102 1167 11AC;B145;1102 1167 11AC; # (녅; 녅; 녅; 녅; 녅; ) HANGUL SYLLABLE NYEONJ
+B146;B146;1102 1167 11AD;B146;1102 1167 11AD; # (녆; 녆; 녆; 녆; 녆; ) HANGUL SYLLABLE NYEONH
+B147;B147;1102 1167 11AE;B147;1102 1167 11AE; # (녇; 녇; 녇; 녇; 녇; ) HANGUL SYLLABLE NYEOD
+B148;B148;1102 1167 11AF;B148;1102 1167 11AF; # (녈; 녈; 녈; 녈; 녈; ) HANGUL SYLLABLE NYEOL
+B149;B149;1102 1167 11B0;B149;1102 1167 11B0; # (녉; 녉; 녉; 녉; 녉; ) HANGUL SYLLABLE NYEOLG
+B14A;B14A;1102 1167 11B1;B14A;1102 1167 11B1; # (녊; 녊; 녊; 녊; 녊; ) HANGUL SYLLABLE NYEOLM
+B14B;B14B;1102 1167 11B2;B14B;1102 1167 11B2; # (녋; 녋; 녋; 녋; 녋; ) HANGUL SYLLABLE NYEOLB
+B14C;B14C;1102 1167 11B3;B14C;1102 1167 11B3; # (녌; 녌; 녌; 녌; 녌; ) HANGUL SYLLABLE NYEOLS
+B14D;B14D;1102 1167 11B4;B14D;1102 1167 11B4; # (ë…; ë…; 녍; ë…; 녍; ) HANGUL SYLLABLE NYEOLT
+B14E;B14E;1102 1167 11B5;B14E;1102 1167 11B5; # (녎; 녎; 녎; 녎; 녎; ) HANGUL SYLLABLE NYEOLP
+B14F;B14F;1102 1167 11B6;B14F;1102 1167 11B6; # (ë…; ë…; 녏; ë…; 녏; ) HANGUL SYLLABLE NYEOLH
+B150;B150;1102 1167 11B7;B150;1102 1167 11B7; # (ë…; ë…; 념; ë…; 념; ) HANGUL SYLLABLE NYEOM
+B151;B151;1102 1167 11B8;B151;1102 1167 11B8; # (녑; 녑; 녑; 녑; 녑; ) HANGUL SYLLABLE NYEOB
+B152;B152;1102 1167 11B9;B152;1102 1167 11B9; # (녒; 녒; 녒; 녒; 녒; ) HANGUL SYLLABLE NYEOBS
+B153;B153;1102 1167 11BA;B153;1102 1167 11BA; # (녓; 녓; 녓; 녓; 녓; ) HANGUL SYLLABLE NYEOS
+B154;B154;1102 1167 11BB;B154;1102 1167 11BB; # (녔; 녔; 녔; 녔; 녔; ) HANGUL SYLLABLE NYEOSS
+B155;B155;1102 1167 11BC;B155;1102 1167 11BC; # (녕; 녕; 녕; 녕; 녕; ) HANGUL SYLLABLE NYEONG
+B156;B156;1102 1167 11BD;B156;1102 1167 11BD; # (녖; 녖; 녖; 녖; 녖; ) HANGUL SYLLABLE NYEOJ
+B157;B157;1102 1167 11BE;B157;1102 1167 11BE; # (녗; 녗; 녗; 녗; 녗; ) HANGUL SYLLABLE NYEOC
+B158;B158;1102 1167 11BF;B158;1102 1167 11BF; # (녘; 녘; 녘; 녘; 녘; ) HANGUL SYLLABLE NYEOK
+B159;B159;1102 1167 11C0;B159;1102 1167 11C0; # (녙; 녙; 녙; 녙; 녙; ) HANGUL SYLLABLE NYEOT
+B15A;B15A;1102 1167 11C1;B15A;1102 1167 11C1; # (ë…š; ë…š; á„‚á…§á‡; ë…š; á„‚á…§á‡; ) HANGUL SYLLABLE NYEOP
+B15B;B15B;1102 1167 11C2;B15B;1102 1167 11C2; # (녛; 녛; 녛; 녛; 녛; ) HANGUL SYLLABLE NYEOH
+B15C;B15C;1102 1168;B15C;1102 1168; # (녜; 녜; 녜; 녜; 녜; ) HANGUL SYLLABLE NYE
+B15D;B15D;1102 1168 11A8;B15D;1102 1168 11A8; # (ë…; ë…; 녝; ë…; 녝; ) HANGUL SYLLABLE NYEG
+B15E;B15E;1102 1168 11A9;B15E;1102 1168 11A9; # (녞; 녞; 녞; 녞; 녞; ) HANGUL SYLLABLE NYEGG
+B15F;B15F;1102 1168 11AA;B15F;1102 1168 11AA; # (녟; 녟; 녟; 녟; 녟; ) HANGUL SYLLABLE NYEGS
+B160;B160;1102 1168 11AB;B160;1102 1168 11AB; # (녠; 녠; 녠; 녠; 녠; ) HANGUL SYLLABLE NYEN
+B161;B161;1102 1168 11AC;B161;1102 1168 11AC; # (녡; 녡; 녡; 녡; 녡; ) HANGUL SYLLABLE NYENJ
+B162;B162;1102 1168 11AD;B162;1102 1168 11AD; # (녢; 녢; 녢; 녢; 녢; ) HANGUL SYLLABLE NYENH
+B163;B163;1102 1168 11AE;B163;1102 1168 11AE; # (녣; 녣; 녣; 녣; 녣; ) HANGUL SYLLABLE NYED
+B164;B164;1102 1168 11AF;B164;1102 1168 11AF; # (녤; 녤; 녤; 녤; 녤; ) HANGUL SYLLABLE NYEL
+B165;B165;1102 1168 11B0;B165;1102 1168 11B0; # (녥; 녥; 녥; 녥; 녥; ) HANGUL SYLLABLE NYELG
+B166;B166;1102 1168 11B1;B166;1102 1168 11B1; # (녦; 녦; 녦; 녦; 녦; ) HANGUL SYLLABLE NYELM
+B167;B167;1102 1168 11B2;B167;1102 1168 11B2; # (녧; 녧; 녧; 녧; 녧; ) HANGUL SYLLABLE NYELB
+B168;B168;1102 1168 11B3;B168;1102 1168 11B3; # (녨; 녨; 녨; 녨; 녨; ) HANGUL SYLLABLE NYELS
+B169;B169;1102 1168 11B4;B169;1102 1168 11B4; # (녩; 녩; 녩; 녩; 녩; ) HANGUL SYLLABLE NYELT
+B16A;B16A;1102 1168 11B5;B16A;1102 1168 11B5; # (녪; 녪; 녪; 녪; 녪; ) HANGUL SYLLABLE NYELP
+B16B;B16B;1102 1168 11B6;B16B;1102 1168 11B6; # (녫; 녫; 녫; 녫; 녫; ) HANGUL SYLLABLE NYELH
+B16C;B16C;1102 1168 11B7;B16C;1102 1168 11B7; # (녬; 녬; 녬; 녬; 녬; ) HANGUL SYLLABLE NYEM
+B16D;B16D;1102 1168 11B8;B16D;1102 1168 11B8; # (녭; 녭; 녭; 녭; 녭; ) HANGUL SYLLABLE NYEB
+B16E;B16E;1102 1168 11B9;B16E;1102 1168 11B9; # (녮; 녮; 녮; 녮; 녮; ) HANGUL SYLLABLE NYEBS
+B16F;B16F;1102 1168 11BA;B16F;1102 1168 11BA; # (녯; 녯; 녯; 녯; 녯; ) HANGUL SYLLABLE NYES
+B170;B170;1102 1168 11BB;B170;1102 1168 11BB; # (녰; 녰; 녰; 녰; 녰; ) HANGUL SYLLABLE NYESS
+B171;B171;1102 1168 11BC;B171;1102 1168 11BC; # (녱; 녱; 녱; 녱; 녱; ) HANGUL SYLLABLE NYENG
+B172;B172;1102 1168 11BD;B172;1102 1168 11BD; # (녲; 녲; 녲; 녲; 녲; ) HANGUL SYLLABLE NYEJ
+B173;B173;1102 1168 11BE;B173;1102 1168 11BE; # (녳; 녳; 녳; 녳; 녳; ) HANGUL SYLLABLE NYEC
+B174;B174;1102 1168 11BF;B174;1102 1168 11BF; # (녴; 녴; 녴; 녴; 녴; ) HANGUL SYLLABLE NYEK
+B175;B175;1102 1168 11C0;B175;1102 1168 11C0; # (녵; 녵; 녵; 녵; 녵; ) HANGUL SYLLABLE NYET
+B176;B176;1102 1168 11C1;B176;1102 1168 11C1; # (ë…¶; ë…¶; á„‚á…¨á‡; ë…¶; á„‚á…¨á‡; ) HANGUL SYLLABLE NYEP
+B177;B177;1102 1168 11C2;B177;1102 1168 11C2; # (녷; 녷; 녷; 녷; 녷; ) HANGUL SYLLABLE NYEH
+B178;B178;1102 1169;B178;1102 1169; # (ë…¸; ë…¸; á„‚á…©; ë…¸; á„‚á…©; ) HANGUL SYLLABLE NO
+B179;B179;1102 1169 11A8;B179;1102 1169 11A8; # (녹; 녹; 녹; 녹; 녹; ) HANGUL SYLLABLE NOG
+B17A;B17A;1102 1169 11A9;B17A;1102 1169 11A9; # (녺; 녺; 녺; 녺; 녺; ) HANGUL SYLLABLE NOGG
+B17B;B17B;1102 1169 11AA;B17B;1102 1169 11AA; # (녻; 녻; 녻; 녻; 녻; ) HANGUL SYLLABLE NOGS
+B17C;B17C;1102 1169 11AB;B17C;1102 1169 11AB; # (논; 논; 논; 논; 논; ) HANGUL SYLLABLE NON
+B17D;B17D;1102 1169 11AC;B17D;1102 1169 11AC; # (녽; 녽; 녽; 녽; 녽; ) HANGUL SYLLABLE NONJ
+B17E;B17E;1102 1169 11AD;B17E;1102 1169 11AD; # (녾; 녾; 녾; 녾; 녾; ) HANGUL SYLLABLE NONH
+B17F;B17F;1102 1169 11AE;B17F;1102 1169 11AE; # (녿; 녿; 녿; 녿; 녿; ) HANGUL SYLLABLE NOD
+B180;B180;1102 1169 11AF;B180;1102 1169 11AF; # (놀; 놀; 놀; 놀; 놀; ) HANGUL SYLLABLE NOL
+B181;B181;1102 1169 11B0;B181;1102 1169 11B0; # (ë†; ë†; 놁; ë†; 놁; ) HANGUL SYLLABLE NOLG
+B182;B182;1102 1169 11B1;B182;1102 1169 11B1; # (놂; 놂; 놂; 놂; 놂; ) HANGUL SYLLABLE NOLM
+B183;B183;1102 1169 11B2;B183;1102 1169 11B2; # (놃; 놃; 놃; 놃; 놃; ) HANGUL SYLLABLE NOLB
+B184;B184;1102 1169 11B3;B184;1102 1169 11B3; # (놄; 놄; 놄; 놄; 놄; ) HANGUL SYLLABLE NOLS
+B185;B185;1102 1169 11B4;B185;1102 1169 11B4; # (놅; 놅; 놅; 놅; 놅; ) HANGUL SYLLABLE NOLT
+B186;B186;1102 1169 11B5;B186;1102 1169 11B5; # (놆; 놆; 놆; 놆; 놆; ) HANGUL SYLLABLE NOLP
+B187;B187;1102 1169 11B6;B187;1102 1169 11B6; # (놇; 놇; 놇; 놇; 놇; ) HANGUL SYLLABLE NOLH
+B188;B188;1102 1169 11B7;B188;1102 1169 11B7; # (놈; 놈; 놈; 놈; 놈; ) HANGUL SYLLABLE NOM
+B189;B189;1102 1169 11B8;B189;1102 1169 11B8; # (놉; 놉; 놉; 놉; 놉; ) HANGUL SYLLABLE NOB
+B18A;B18A;1102 1169 11B9;B18A;1102 1169 11B9; # (놊; 놊; 놊; 놊; 놊; ) HANGUL SYLLABLE NOBS
+B18B;B18B;1102 1169 11BA;B18B;1102 1169 11BA; # (놋; 놋; 놋; 놋; 놋; ) HANGUL SYLLABLE NOS
+B18C;B18C;1102 1169 11BB;B18C;1102 1169 11BB; # (놌; 놌; 놌; 놌; 놌; ) HANGUL SYLLABLE NOSS
+B18D;B18D;1102 1169 11BC;B18D;1102 1169 11BC; # (ë†; ë†; 농; ë†; 농; ) HANGUL SYLLABLE NONG
+B18E;B18E;1102 1169 11BD;B18E;1102 1169 11BD; # (놎; 놎; 놎; 놎; 놎; ) HANGUL SYLLABLE NOJ
+B18F;B18F;1102 1169 11BE;B18F;1102 1169 11BE; # (ë†; ë†; 놏; ë†; 놏; ) HANGUL SYLLABLE NOC
+B190;B190;1102 1169 11BF;B190;1102 1169 11BF; # (ë†; ë†; 놐; ë†; 놐; ) HANGUL SYLLABLE NOK
+B191;B191;1102 1169 11C0;B191;1102 1169 11C0; # (놑; 놑; 놑; 놑; 놑; ) HANGUL SYLLABLE NOT
+B192;B192;1102 1169 11C1;B192;1102 1169 11C1; # (높; 높; á„‚á…©á‡; 높; á„‚á…©á‡; ) HANGUL SYLLABLE NOP
+B193;B193;1102 1169 11C2;B193;1102 1169 11C2; # (놓; 놓; 놓; 놓; 놓; ) HANGUL SYLLABLE NOH
+B194;B194;1102 116A;B194;1102 116A; # (놔; 놔; 놔; 놔; 놔; ) HANGUL SYLLABLE NWA
+B195;B195;1102 116A 11A8;B195;1102 116A 11A8; # (놕; 놕; 놕; 놕; 놕; ) HANGUL SYLLABLE NWAG
+B196;B196;1102 116A 11A9;B196;1102 116A 11A9; # (놖; 놖; 놖; 놖; 놖; ) HANGUL SYLLABLE NWAGG
+B197;B197;1102 116A 11AA;B197;1102 116A 11AA; # (놗; 놗; 놗; 놗; 놗; ) HANGUL SYLLABLE NWAGS
+B198;B198;1102 116A 11AB;B198;1102 116A 11AB; # (놘; 놘; 놘; 놘; 놘; ) HANGUL SYLLABLE NWAN
+B199;B199;1102 116A 11AC;B199;1102 116A 11AC; # (놙; 놙; 놙; 놙; 놙; ) HANGUL SYLLABLE NWANJ
+B19A;B19A;1102 116A 11AD;B19A;1102 116A 11AD; # (놚; 놚; 놚; 놚; 놚; ) HANGUL SYLLABLE NWANH
+B19B;B19B;1102 116A 11AE;B19B;1102 116A 11AE; # (놛; 놛; 놛; 놛; 놛; ) HANGUL SYLLABLE NWAD
+B19C;B19C;1102 116A 11AF;B19C;1102 116A 11AF; # (놜; 놜; 놜; 놜; 놜; ) HANGUL SYLLABLE NWAL
+B19D;B19D;1102 116A 11B0;B19D;1102 116A 11B0; # (ë†; ë†; 놝; ë†; 놝; ) HANGUL SYLLABLE NWALG
+B19E;B19E;1102 116A 11B1;B19E;1102 116A 11B1; # (놞; 놞; 놞; 놞; 놞; ) HANGUL SYLLABLE NWALM
+B19F;B19F;1102 116A 11B2;B19F;1102 116A 11B2; # (놟; 놟; 놟; 놟; 놟; ) HANGUL SYLLABLE NWALB
+B1A0;B1A0;1102 116A 11B3;B1A0;1102 116A 11B3; # (놠; 놠; 놠; 놠; 놠; ) HANGUL SYLLABLE NWALS
+B1A1;B1A1;1102 116A 11B4;B1A1;1102 116A 11B4; # (놡; 놡; 놡; 놡; 놡; ) HANGUL SYLLABLE NWALT
+B1A2;B1A2;1102 116A 11B5;B1A2;1102 116A 11B5; # (놢; 놢; 놢; 놢; 놢; ) HANGUL SYLLABLE NWALP
+B1A3;B1A3;1102 116A 11B6;B1A3;1102 116A 11B6; # (놣; 놣; 놣; 놣; 놣; ) HANGUL SYLLABLE NWALH
+B1A4;B1A4;1102 116A 11B7;B1A4;1102 116A 11B7; # (놤; 놤; 놤; 놤; 놤; ) HANGUL SYLLABLE NWAM
+B1A5;B1A5;1102 116A 11B8;B1A5;1102 116A 11B8; # (놥; 놥; 놥; 놥; 놥; ) HANGUL SYLLABLE NWAB
+B1A6;B1A6;1102 116A 11B9;B1A6;1102 116A 11B9; # (놦; 놦; 놦; 놦; 놦; ) HANGUL SYLLABLE NWABS
+B1A7;B1A7;1102 116A 11BA;B1A7;1102 116A 11BA; # (놧; 놧; 놧; 놧; 놧; ) HANGUL SYLLABLE NWAS
+B1A8;B1A8;1102 116A 11BB;B1A8;1102 116A 11BB; # (놨; 놨; 놨; 놨; 놨; ) HANGUL SYLLABLE NWASS
+B1A9;B1A9;1102 116A 11BC;B1A9;1102 116A 11BC; # (놩; 놩; 놩; 놩; 놩; ) HANGUL SYLLABLE NWANG
+B1AA;B1AA;1102 116A 11BD;B1AA;1102 116A 11BD; # (놪; 놪; 놪; 놪; 놪; ) HANGUL SYLLABLE NWAJ
+B1AB;B1AB;1102 116A 11BE;B1AB;1102 116A 11BE; # (놫; 놫; 놫; 놫; 놫; ) HANGUL SYLLABLE NWAC
+B1AC;B1AC;1102 116A 11BF;B1AC;1102 116A 11BF; # (놬; 놬; 놬; 놬; 놬; ) HANGUL SYLLABLE NWAK
+B1AD;B1AD;1102 116A 11C0;B1AD;1102 116A 11C0; # (놭; 놭; 놭; 놭; 놭; ) HANGUL SYLLABLE NWAT
+B1AE;B1AE;1102 116A 11C1;B1AE;1102 116A 11C1; # (놮; 놮; á„‚á…ªá‡; 놮; á„‚á…ªá‡; ) HANGUL SYLLABLE NWAP
+B1AF;B1AF;1102 116A 11C2;B1AF;1102 116A 11C2; # (놯; 놯; 놯; 놯; 놯; ) HANGUL SYLLABLE NWAH
+B1B0;B1B0;1102 116B;B1B0;1102 116B; # (놰; 놰; 놰; 놰; 놰; ) HANGUL SYLLABLE NWAE
+B1B1;B1B1;1102 116B 11A8;B1B1;1102 116B 11A8; # (놱; 놱; 놱; 놱; 놱; ) HANGUL SYLLABLE NWAEG
+B1B2;B1B2;1102 116B 11A9;B1B2;1102 116B 11A9; # (놲; 놲; 놲; 놲; 놲; ) HANGUL SYLLABLE NWAEGG
+B1B3;B1B3;1102 116B 11AA;B1B3;1102 116B 11AA; # (놳; 놳; 놳; 놳; 놳; ) HANGUL SYLLABLE NWAEGS
+B1B4;B1B4;1102 116B 11AB;B1B4;1102 116B 11AB; # (놴; 놴; 놴; 놴; 놴; ) HANGUL SYLLABLE NWAEN
+B1B5;B1B5;1102 116B 11AC;B1B5;1102 116B 11AC; # (놵; 놵; 놵; 놵; 놵; ) HANGUL SYLLABLE NWAENJ
+B1B6;B1B6;1102 116B 11AD;B1B6;1102 116B 11AD; # (놶; 놶; 놶; 놶; 놶; ) HANGUL SYLLABLE NWAENH
+B1B7;B1B7;1102 116B 11AE;B1B7;1102 116B 11AE; # (놷; 놷; 놷; 놷; 놷; ) HANGUL SYLLABLE NWAED
+B1B8;B1B8;1102 116B 11AF;B1B8;1102 116B 11AF; # (놸; 놸; 놸; 놸; 놸; ) HANGUL SYLLABLE NWAEL
+B1B9;B1B9;1102 116B 11B0;B1B9;1102 116B 11B0; # (놹; 놹; 놹; 놹; 놹; ) HANGUL SYLLABLE NWAELG
+B1BA;B1BA;1102 116B 11B1;B1BA;1102 116B 11B1; # (놺; 놺; 놺; 놺; 놺; ) HANGUL SYLLABLE NWAELM
+B1BB;B1BB;1102 116B 11B2;B1BB;1102 116B 11B2; # (놻; 놻; 놻; 놻; 놻; ) HANGUL SYLLABLE NWAELB
+B1BC;B1BC;1102 116B 11B3;B1BC;1102 116B 11B3; # (놼; 놼; 놼; 놼; 놼; ) HANGUL SYLLABLE NWAELS
+B1BD;B1BD;1102 116B 11B4;B1BD;1102 116B 11B4; # (놽; 놽; 놽; 놽; 놽; ) HANGUL SYLLABLE NWAELT
+B1BE;B1BE;1102 116B 11B5;B1BE;1102 116B 11B5; # (놾; 놾; 놾; 놾; 놾; ) HANGUL SYLLABLE NWAELP
+B1BF;B1BF;1102 116B 11B6;B1BF;1102 116B 11B6; # (놿; 놿; 놿; 놿; 놿; ) HANGUL SYLLABLE NWAELH
+B1C0;B1C0;1102 116B 11B7;B1C0;1102 116B 11B7; # (뇀; 뇀; 뇀; 뇀; 뇀; ) HANGUL SYLLABLE NWAEM
+B1C1;B1C1;1102 116B 11B8;B1C1;1102 116B 11B8; # (ë‡; ë‡; 뇁; ë‡; 뇁; ) HANGUL SYLLABLE NWAEB
+B1C2;B1C2;1102 116B 11B9;B1C2;1102 116B 11B9; # (뇂; 뇂; 뇂; 뇂; 뇂; ) HANGUL SYLLABLE NWAEBS
+B1C3;B1C3;1102 116B 11BA;B1C3;1102 116B 11BA; # (뇃; 뇃; 뇃; 뇃; 뇃; ) HANGUL SYLLABLE NWAES
+B1C4;B1C4;1102 116B 11BB;B1C4;1102 116B 11BB; # (뇄; 뇄; 뇄; 뇄; 뇄; ) HANGUL SYLLABLE NWAESS
+B1C5;B1C5;1102 116B 11BC;B1C5;1102 116B 11BC; # (뇅; 뇅; 뇅; 뇅; 뇅; ) HANGUL SYLLABLE NWAENG
+B1C6;B1C6;1102 116B 11BD;B1C6;1102 116B 11BD; # (뇆; 뇆; 뇆; 뇆; 뇆; ) HANGUL SYLLABLE NWAEJ
+B1C7;B1C7;1102 116B 11BE;B1C7;1102 116B 11BE; # (뇇; 뇇; 뇇; 뇇; 뇇; ) HANGUL SYLLABLE NWAEC
+B1C8;B1C8;1102 116B 11BF;B1C8;1102 116B 11BF; # (뇈; 뇈; 뇈; 뇈; 뇈; ) HANGUL SYLLABLE NWAEK
+B1C9;B1C9;1102 116B 11C0;B1C9;1102 116B 11C0; # (뇉; 뇉; 뇉; 뇉; 뇉; ) HANGUL SYLLABLE NWAET
+B1CA;B1CA;1102 116B 11C1;B1CA;1102 116B 11C1; # (뇊; 뇊; á„‚á…«á‡; 뇊; á„‚á…«á‡; ) HANGUL SYLLABLE NWAEP
+B1CB;B1CB;1102 116B 11C2;B1CB;1102 116B 11C2; # (뇋; 뇋; 뇋; 뇋; 뇋; ) HANGUL SYLLABLE NWAEH
+B1CC;B1CC;1102 116C;B1CC;1102 116C; # (뇌; 뇌; 뇌; 뇌; 뇌; ) HANGUL SYLLABLE NOE
+B1CD;B1CD;1102 116C 11A8;B1CD;1102 116C 11A8; # (ë‡; ë‡; 뇍; ë‡; 뇍; ) HANGUL SYLLABLE NOEG
+B1CE;B1CE;1102 116C 11A9;B1CE;1102 116C 11A9; # (뇎; 뇎; 뇎; 뇎; 뇎; ) HANGUL SYLLABLE NOEGG
+B1CF;B1CF;1102 116C 11AA;B1CF;1102 116C 11AA; # (ë‡; ë‡; 뇏; ë‡; 뇏; ) HANGUL SYLLABLE NOEGS
+B1D0;B1D0;1102 116C 11AB;B1D0;1102 116C 11AB; # (ë‡; ë‡; 뇐; ë‡; 뇐; ) HANGUL SYLLABLE NOEN
+B1D1;B1D1;1102 116C 11AC;B1D1;1102 116C 11AC; # (뇑; 뇑; 뇑; 뇑; 뇑; ) HANGUL SYLLABLE NOENJ
+B1D2;B1D2;1102 116C 11AD;B1D2;1102 116C 11AD; # (뇒; 뇒; 뇒; 뇒; 뇒; ) HANGUL SYLLABLE NOENH
+B1D3;B1D3;1102 116C 11AE;B1D3;1102 116C 11AE; # (뇓; 뇓; 뇓; 뇓; 뇓; ) HANGUL SYLLABLE NOED
+B1D4;B1D4;1102 116C 11AF;B1D4;1102 116C 11AF; # (뇔; 뇔; 뇔; 뇔; 뇔; ) HANGUL SYLLABLE NOEL
+B1D5;B1D5;1102 116C 11B0;B1D5;1102 116C 11B0; # (뇕; 뇕; 뇕; 뇕; 뇕; ) HANGUL SYLLABLE NOELG
+B1D6;B1D6;1102 116C 11B1;B1D6;1102 116C 11B1; # (뇖; 뇖; 뇖; 뇖; 뇖; ) HANGUL SYLLABLE NOELM
+B1D7;B1D7;1102 116C 11B2;B1D7;1102 116C 11B2; # (뇗; 뇗; 뇗; 뇗; 뇗; ) HANGUL SYLLABLE NOELB
+B1D8;B1D8;1102 116C 11B3;B1D8;1102 116C 11B3; # (뇘; 뇘; 뇘; 뇘; 뇘; ) HANGUL SYLLABLE NOELS
+B1D9;B1D9;1102 116C 11B4;B1D9;1102 116C 11B4; # (뇙; 뇙; 뇙; 뇙; 뇙; ) HANGUL SYLLABLE NOELT
+B1DA;B1DA;1102 116C 11B5;B1DA;1102 116C 11B5; # (뇚; 뇚; 뇚; 뇚; 뇚; ) HANGUL SYLLABLE NOELP
+B1DB;B1DB;1102 116C 11B6;B1DB;1102 116C 11B6; # (뇛; 뇛; 뇛; 뇛; 뇛; ) HANGUL SYLLABLE NOELH
+B1DC;B1DC;1102 116C 11B7;B1DC;1102 116C 11B7; # (뇜; 뇜; 뇜; 뇜; 뇜; ) HANGUL SYLLABLE NOEM
+B1DD;B1DD;1102 116C 11B8;B1DD;1102 116C 11B8; # (ë‡; ë‡; 뇝; ë‡; 뇝; ) HANGUL SYLLABLE NOEB
+B1DE;B1DE;1102 116C 11B9;B1DE;1102 116C 11B9; # (뇞; 뇞; 뇞; 뇞; 뇞; ) HANGUL SYLLABLE NOEBS
+B1DF;B1DF;1102 116C 11BA;B1DF;1102 116C 11BA; # (뇟; 뇟; 뇟; 뇟; 뇟; ) HANGUL SYLLABLE NOES
+B1E0;B1E0;1102 116C 11BB;B1E0;1102 116C 11BB; # (뇠; 뇠; 뇠; 뇠; 뇠; ) HANGUL SYLLABLE NOESS
+B1E1;B1E1;1102 116C 11BC;B1E1;1102 116C 11BC; # (뇡; 뇡; 뇡; 뇡; 뇡; ) HANGUL SYLLABLE NOENG
+B1E2;B1E2;1102 116C 11BD;B1E2;1102 116C 11BD; # (뇢; 뇢; 뇢; 뇢; 뇢; ) HANGUL SYLLABLE NOEJ
+B1E3;B1E3;1102 116C 11BE;B1E3;1102 116C 11BE; # (뇣; 뇣; 뇣; 뇣; 뇣; ) HANGUL SYLLABLE NOEC
+B1E4;B1E4;1102 116C 11BF;B1E4;1102 116C 11BF; # (뇤; 뇤; 뇤; 뇤; 뇤; ) HANGUL SYLLABLE NOEK
+B1E5;B1E5;1102 116C 11C0;B1E5;1102 116C 11C0; # (뇥; 뇥; 뇥; 뇥; 뇥; ) HANGUL SYLLABLE NOET
+B1E6;B1E6;1102 116C 11C1;B1E6;1102 116C 11C1; # (뇦; 뇦; á„‚á…¬á‡; 뇦; á„‚á…¬á‡; ) HANGUL SYLLABLE NOEP
+B1E7;B1E7;1102 116C 11C2;B1E7;1102 116C 11C2; # (뇧; 뇧; 뇧; 뇧; 뇧; ) HANGUL SYLLABLE NOEH
+B1E8;B1E8;1102 116D;B1E8;1102 116D; # (뇨; 뇨; 뇨; 뇨; 뇨; ) HANGUL SYLLABLE NYO
+B1E9;B1E9;1102 116D 11A8;B1E9;1102 116D 11A8; # (뇩; 뇩; 뇩; 뇩; 뇩; ) HANGUL SYLLABLE NYOG
+B1EA;B1EA;1102 116D 11A9;B1EA;1102 116D 11A9; # (뇪; 뇪; 뇪; 뇪; 뇪; ) HANGUL SYLLABLE NYOGG
+B1EB;B1EB;1102 116D 11AA;B1EB;1102 116D 11AA; # (뇫; 뇫; 뇫; 뇫; 뇫; ) HANGUL SYLLABLE NYOGS
+B1EC;B1EC;1102 116D 11AB;B1EC;1102 116D 11AB; # (뇬; 뇬; 뇬; 뇬; 뇬; ) HANGUL SYLLABLE NYON
+B1ED;B1ED;1102 116D 11AC;B1ED;1102 116D 11AC; # (뇭; 뇭; 뇭; 뇭; 뇭; ) HANGUL SYLLABLE NYONJ
+B1EE;B1EE;1102 116D 11AD;B1EE;1102 116D 11AD; # (뇮; 뇮; 뇮; 뇮; 뇮; ) HANGUL SYLLABLE NYONH
+B1EF;B1EF;1102 116D 11AE;B1EF;1102 116D 11AE; # (뇯; 뇯; 뇯; 뇯; 뇯; ) HANGUL SYLLABLE NYOD
+B1F0;B1F0;1102 116D 11AF;B1F0;1102 116D 11AF; # (뇰; 뇰; 뇰; 뇰; 뇰; ) HANGUL SYLLABLE NYOL
+B1F1;B1F1;1102 116D 11B0;B1F1;1102 116D 11B0; # (뇱; 뇱; 뇱; 뇱; 뇱; ) HANGUL SYLLABLE NYOLG
+B1F2;B1F2;1102 116D 11B1;B1F2;1102 116D 11B1; # (뇲; 뇲; 뇲; 뇲; 뇲; ) HANGUL SYLLABLE NYOLM
+B1F3;B1F3;1102 116D 11B2;B1F3;1102 116D 11B2; # (뇳; 뇳; 뇳; 뇳; 뇳; ) HANGUL SYLLABLE NYOLB
+B1F4;B1F4;1102 116D 11B3;B1F4;1102 116D 11B3; # (뇴; 뇴; 뇴; 뇴; 뇴; ) HANGUL SYLLABLE NYOLS
+B1F5;B1F5;1102 116D 11B4;B1F5;1102 116D 11B4; # (뇵; 뇵; 뇵; 뇵; 뇵; ) HANGUL SYLLABLE NYOLT
+B1F6;B1F6;1102 116D 11B5;B1F6;1102 116D 11B5; # (뇶; 뇶; 뇶; 뇶; 뇶; ) HANGUL SYLLABLE NYOLP
+B1F7;B1F7;1102 116D 11B6;B1F7;1102 116D 11B6; # (뇷; 뇷; 뇷; 뇷; 뇷; ) HANGUL SYLLABLE NYOLH
+B1F8;B1F8;1102 116D 11B7;B1F8;1102 116D 11B7; # (뇸; 뇸; 뇸; 뇸; 뇸; ) HANGUL SYLLABLE NYOM
+B1F9;B1F9;1102 116D 11B8;B1F9;1102 116D 11B8; # (뇹; 뇹; 뇹; 뇹; 뇹; ) HANGUL SYLLABLE NYOB
+B1FA;B1FA;1102 116D 11B9;B1FA;1102 116D 11B9; # (뇺; 뇺; 뇺; 뇺; 뇺; ) HANGUL SYLLABLE NYOBS
+B1FB;B1FB;1102 116D 11BA;B1FB;1102 116D 11BA; # (뇻; 뇻; 뇻; 뇻; 뇻; ) HANGUL SYLLABLE NYOS
+B1FC;B1FC;1102 116D 11BB;B1FC;1102 116D 11BB; # (뇼; 뇼; 뇼; 뇼; 뇼; ) HANGUL SYLLABLE NYOSS
+B1FD;B1FD;1102 116D 11BC;B1FD;1102 116D 11BC; # (뇽; 뇽; 뇽; 뇽; 뇽; ) HANGUL SYLLABLE NYONG
+B1FE;B1FE;1102 116D 11BD;B1FE;1102 116D 11BD; # (뇾; 뇾; 뇾; 뇾; 뇾; ) HANGUL SYLLABLE NYOJ
+B1FF;B1FF;1102 116D 11BE;B1FF;1102 116D 11BE; # (뇿; 뇿; 뇿; 뇿; 뇿; ) HANGUL SYLLABLE NYOC
+B200;B200;1102 116D 11BF;B200;1102 116D 11BF; # (눀; 눀; 눀; 눀; 눀; ) HANGUL SYLLABLE NYOK
+B201;B201;1102 116D 11C0;B201;1102 116D 11C0; # (ëˆ; ëˆ; 눁; ëˆ; 눁; ) HANGUL SYLLABLE NYOT
+B202;B202;1102 116D 11C1;B202;1102 116D 11C1; # (눂; 눂; á„‚á…­á‡; 눂; á„‚á…­á‡; ) HANGUL SYLLABLE NYOP
+B203;B203;1102 116D 11C2;B203;1102 116D 11C2; # (눃; 눃; 눃; 눃; 눃; ) HANGUL SYLLABLE NYOH
+B204;B204;1102 116E;B204;1102 116E; # (누; 누; 누; 누; 누; ) HANGUL SYLLABLE NU
+B205;B205;1102 116E 11A8;B205;1102 116E 11A8; # (눅; 눅; 눅; 눅; 눅; ) HANGUL SYLLABLE NUG
+B206;B206;1102 116E 11A9;B206;1102 116E 11A9; # (눆; 눆; 눆; 눆; 눆; ) HANGUL SYLLABLE NUGG
+B207;B207;1102 116E 11AA;B207;1102 116E 11AA; # (눇; 눇; 눇; 눇; 눇; ) HANGUL SYLLABLE NUGS
+B208;B208;1102 116E 11AB;B208;1102 116E 11AB; # (눈; 눈; 눈; 눈; 눈; ) HANGUL SYLLABLE NUN
+B209;B209;1102 116E 11AC;B209;1102 116E 11AC; # (눉; 눉; 눉; 눉; 눉; ) HANGUL SYLLABLE NUNJ
+B20A;B20A;1102 116E 11AD;B20A;1102 116E 11AD; # (눊; 눊; 눊; 눊; 눊; ) HANGUL SYLLABLE NUNH
+B20B;B20B;1102 116E 11AE;B20B;1102 116E 11AE; # (눋; 눋; 눋; 눋; 눋; ) HANGUL SYLLABLE NUD
+B20C;B20C;1102 116E 11AF;B20C;1102 116E 11AF; # (눌; 눌; 눌; 눌; 눌; ) HANGUL SYLLABLE NUL
+B20D;B20D;1102 116E 11B0;B20D;1102 116E 11B0; # (ëˆ; ëˆ; 눍; ëˆ; 눍; ) HANGUL SYLLABLE NULG
+B20E;B20E;1102 116E 11B1;B20E;1102 116E 11B1; # (눎; 눎; 눎; 눎; 눎; ) HANGUL SYLLABLE NULM
+B20F;B20F;1102 116E 11B2;B20F;1102 116E 11B2; # (ëˆ; ëˆ; 눏; ëˆ; 눏; ) HANGUL SYLLABLE NULB
+B210;B210;1102 116E 11B3;B210;1102 116E 11B3; # (ëˆ; ëˆ; 눐; ëˆ; 눐; ) HANGUL SYLLABLE NULS
+B211;B211;1102 116E 11B4;B211;1102 116E 11B4; # (눑; 눑; 눑; 눑; 눑; ) HANGUL SYLLABLE NULT
+B212;B212;1102 116E 11B5;B212;1102 116E 11B5; # (눒; 눒; 눒; 눒; 눒; ) HANGUL SYLLABLE NULP
+B213;B213;1102 116E 11B6;B213;1102 116E 11B6; # (눓; 눓; 눓; 눓; 눓; ) HANGUL SYLLABLE NULH
+B214;B214;1102 116E 11B7;B214;1102 116E 11B7; # (눔; 눔; 눔; 눔; 눔; ) HANGUL SYLLABLE NUM
+B215;B215;1102 116E 11B8;B215;1102 116E 11B8; # (눕; 눕; 눕; 눕; 눕; ) HANGUL SYLLABLE NUB
+B216;B216;1102 116E 11B9;B216;1102 116E 11B9; # (눖; 눖; 눖; 눖; 눖; ) HANGUL SYLLABLE NUBS
+B217;B217;1102 116E 11BA;B217;1102 116E 11BA; # (눗; 눗; 눗; 눗; 눗; ) HANGUL SYLLABLE NUS
+B218;B218;1102 116E 11BB;B218;1102 116E 11BB; # (눘; 눘; 눘; 눘; 눘; ) HANGUL SYLLABLE NUSS
+B219;B219;1102 116E 11BC;B219;1102 116E 11BC; # (눙; 눙; 눙; 눙; 눙; ) HANGUL SYLLABLE NUNG
+B21A;B21A;1102 116E 11BD;B21A;1102 116E 11BD; # (눚; 눚; 눚; 눚; 눚; ) HANGUL SYLLABLE NUJ
+B21B;B21B;1102 116E 11BE;B21B;1102 116E 11BE; # (눛; 눛; 눛; 눛; 눛; ) HANGUL SYLLABLE NUC
+B21C;B21C;1102 116E 11BF;B21C;1102 116E 11BF; # (눜; 눜; 눜; 눜; 눜; ) HANGUL SYLLABLE NUK
+B21D;B21D;1102 116E 11C0;B21D;1102 116E 11C0; # (ëˆ; ëˆ; 눝; ëˆ; 눝; ) HANGUL SYLLABLE NUT
+B21E;B21E;1102 116E 11C1;B21E;1102 116E 11C1; # (눞; 눞; á„‚á…®á‡; 눞; á„‚á…®á‡; ) HANGUL SYLLABLE NUP
+B21F;B21F;1102 116E 11C2;B21F;1102 116E 11C2; # (눟; 눟; 눟; 눟; 눟; ) HANGUL SYLLABLE NUH
+B220;B220;1102 116F;B220;1102 116F; # (눠; 눠; 눠; 눠; 눠; ) HANGUL SYLLABLE NWEO
+B221;B221;1102 116F 11A8;B221;1102 116F 11A8; # (눡; 눡; 눡; 눡; 눡; ) HANGUL SYLLABLE NWEOG
+B222;B222;1102 116F 11A9;B222;1102 116F 11A9; # (눢; 눢; 눢; 눢; 눢; ) HANGUL SYLLABLE NWEOGG
+B223;B223;1102 116F 11AA;B223;1102 116F 11AA; # (눣; 눣; 눣; 눣; 눣; ) HANGUL SYLLABLE NWEOGS
+B224;B224;1102 116F 11AB;B224;1102 116F 11AB; # (눤; 눤; 눤; 눤; 눤; ) HANGUL SYLLABLE NWEON
+B225;B225;1102 116F 11AC;B225;1102 116F 11AC; # (눥; 눥; 눥; 눥; 눥; ) HANGUL SYLLABLE NWEONJ
+B226;B226;1102 116F 11AD;B226;1102 116F 11AD; # (눦; 눦; 눦; 눦; 눦; ) HANGUL SYLLABLE NWEONH
+B227;B227;1102 116F 11AE;B227;1102 116F 11AE; # (눧; 눧; 눧; 눧; 눧; ) HANGUL SYLLABLE NWEOD
+B228;B228;1102 116F 11AF;B228;1102 116F 11AF; # (눨; 눨; 눨; 눨; 눨; ) HANGUL SYLLABLE NWEOL
+B229;B229;1102 116F 11B0;B229;1102 116F 11B0; # (눩; 눩; 눩; 눩; 눩; ) HANGUL SYLLABLE NWEOLG
+B22A;B22A;1102 116F 11B1;B22A;1102 116F 11B1; # (눪; 눪; 눪; 눪; 눪; ) HANGUL SYLLABLE NWEOLM
+B22B;B22B;1102 116F 11B2;B22B;1102 116F 11B2; # (눫; 눫; 눫; 눫; 눫; ) HANGUL SYLLABLE NWEOLB
+B22C;B22C;1102 116F 11B3;B22C;1102 116F 11B3; # (눬; 눬; 눬; 눬; 눬; ) HANGUL SYLLABLE NWEOLS
+B22D;B22D;1102 116F 11B4;B22D;1102 116F 11B4; # (눭; 눭; 눭; 눭; 눭; ) HANGUL SYLLABLE NWEOLT
+B22E;B22E;1102 116F 11B5;B22E;1102 116F 11B5; # (눮; 눮; 눮; 눮; 눮; ) HANGUL SYLLABLE NWEOLP
+B22F;B22F;1102 116F 11B6;B22F;1102 116F 11B6; # (눯; 눯; 눯; 눯; 눯; ) HANGUL SYLLABLE NWEOLH
+B230;B230;1102 116F 11B7;B230;1102 116F 11B7; # (눰; 눰; 눰; 눰; 눰; ) HANGUL SYLLABLE NWEOM
+B231;B231;1102 116F 11B8;B231;1102 116F 11B8; # (눱; 눱; 눱; 눱; 눱; ) HANGUL SYLLABLE NWEOB
+B232;B232;1102 116F 11B9;B232;1102 116F 11B9; # (눲; 눲; 눲; 눲; 눲; ) HANGUL SYLLABLE NWEOBS
+B233;B233;1102 116F 11BA;B233;1102 116F 11BA; # (눳; 눳; 눳; 눳; 눳; ) HANGUL SYLLABLE NWEOS
+B234;B234;1102 116F 11BB;B234;1102 116F 11BB; # (눴; 눴; 눴; 눴; 눴; ) HANGUL SYLLABLE NWEOSS
+B235;B235;1102 116F 11BC;B235;1102 116F 11BC; # (눵; 눵; 눵; 눵; 눵; ) HANGUL SYLLABLE NWEONG
+B236;B236;1102 116F 11BD;B236;1102 116F 11BD; # (눶; 눶; 눶; 눶; 눶; ) HANGUL SYLLABLE NWEOJ
+B237;B237;1102 116F 11BE;B237;1102 116F 11BE; # (눷; 눷; 눷; 눷; 눷; ) HANGUL SYLLABLE NWEOC
+B238;B238;1102 116F 11BF;B238;1102 116F 11BF; # (눸; 눸; 눸; 눸; 눸; ) HANGUL SYLLABLE NWEOK
+B239;B239;1102 116F 11C0;B239;1102 116F 11C0; # (눹; 눹; 눹; 눹; 눹; ) HANGUL SYLLABLE NWEOT
+B23A;B23A;1102 116F 11C1;B23A;1102 116F 11C1; # (눺; 눺; á„‚á…¯á‡; 눺; á„‚á…¯á‡; ) HANGUL SYLLABLE NWEOP
+B23B;B23B;1102 116F 11C2;B23B;1102 116F 11C2; # (눻; 눻; 눻; 눻; 눻; ) HANGUL SYLLABLE NWEOH
+B23C;B23C;1102 1170;B23C;1102 1170; # (눼; 눼; 눼; 눼; 눼; ) HANGUL SYLLABLE NWE
+B23D;B23D;1102 1170 11A8;B23D;1102 1170 11A8; # (눽; 눽; 눽; 눽; 눽; ) HANGUL SYLLABLE NWEG
+B23E;B23E;1102 1170 11A9;B23E;1102 1170 11A9; # (눾; 눾; 눾; 눾; 눾; ) HANGUL SYLLABLE NWEGG
+B23F;B23F;1102 1170 11AA;B23F;1102 1170 11AA; # (눿; 눿; 눿; 눿; 눿; ) HANGUL SYLLABLE NWEGS
+B240;B240;1102 1170 11AB;B240;1102 1170 11AB; # (뉀; 뉀; 뉀; 뉀; 뉀; ) HANGUL SYLLABLE NWEN
+B241;B241;1102 1170 11AC;B241;1102 1170 11AC; # (ë‰; ë‰; 뉁; ë‰; 뉁; ) HANGUL SYLLABLE NWENJ
+B242;B242;1102 1170 11AD;B242;1102 1170 11AD; # (뉂; 뉂; 뉂; 뉂; 뉂; ) HANGUL SYLLABLE NWENH
+B243;B243;1102 1170 11AE;B243;1102 1170 11AE; # (뉃; 뉃; 뉃; 뉃; 뉃; ) HANGUL SYLLABLE NWED
+B244;B244;1102 1170 11AF;B244;1102 1170 11AF; # (뉄; 뉄; 뉄; 뉄; 뉄; ) HANGUL SYLLABLE NWEL
+B245;B245;1102 1170 11B0;B245;1102 1170 11B0; # (뉅; 뉅; 뉅; 뉅; 뉅; ) HANGUL SYLLABLE NWELG
+B246;B246;1102 1170 11B1;B246;1102 1170 11B1; # (뉆; 뉆; 뉆; 뉆; 뉆; ) HANGUL SYLLABLE NWELM
+B247;B247;1102 1170 11B2;B247;1102 1170 11B2; # (뉇; 뉇; 뉇; 뉇; 뉇; ) HANGUL SYLLABLE NWELB
+B248;B248;1102 1170 11B3;B248;1102 1170 11B3; # (뉈; 뉈; 뉈; 뉈; 뉈; ) HANGUL SYLLABLE NWELS
+B249;B249;1102 1170 11B4;B249;1102 1170 11B4; # (뉉; 뉉; 뉉; 뉉; 뉉; ) HANGUL SYLLABLE NWELT
+B24A;B24A;1102 1170 11B5;B24A;1102 1170 11B5; # (뉊; 뉊; 뉊; 뉊; 뉊; ) HANGUL SYLLABLE NWELP
+B24B;B24B;1102 1170 11B6;B24B;1102 1170 11B6; # (뉋; 뉋; 뉋; 뉋; 뉋; ) HANGUL SYLLABLE NWELH
+B24C;B24C;1102 1170 11B7;B24C;1102 1170 11B7; # (뉌; 뉌; 뉌; 뉌; 뉌; ) HANGUL SYLLABLE NWEM
+B24D;B24D;1102 1170 11B8;B24D;1102 1170 11B8; # (ë‰; ë‰; 뉍; ë‰; 뉍; ) HANGUL SYLLABLE NWEB
+B24E;B24E;1102 1170 11B9;B24E;1102 1170 11B9; # (뉎; 뉎; 뉎; 뉎; 뉎; ) HANGUL SYLLABLE NWEBS
+B24F;B24F;1102 1170 11BA;B24F;1102 1170 11BA; # (ë‰; ë‰; 뉏; ë‰; 뉏; ) HANGUL SYLLABLE NWES
+B250;B250;1102 1170 11BB;B250;1102 1170 11BB; # (ë‰; ë‰; 뉐; ë‰; 뉐; ) HANGUL SYLLABLE NWESS
+B251;B251;1102 1170 11BC;B251;1102 1170 11BC; # (뉑; 뉑; 뉑; 뉑; 뉑; ) HANGUL SYLLABLE NWENG
+B252;B252;1102 1170 11BD;B252;1102 1170 11BD; # (뉒; 뉒; 뉒; 뉒; 뉒; ) HANGUL SYLLABLE NWEJ
+B253;B253;1102 1170 11BE;B253;1102 1170 11BE; # (뉓; 뉓; 뉓; 뉓; 뉓; ) HANGUL SYLLABLE NWEC
+B254;B254;1102 1170 11BF;B254;1102 1170 11BF; # (뉔; 뉔; 뉔; 뉔; 뉔; ) HANGUL SYLLABLE NWEK
+B255;B255;1102 1170 11C0;B255;1102 1170 11C0; # (뉕; 뉕; 뉕; 뉕; 뉕; ) HANGUL SYLLABLE NWET
+B256;B256;1102 1170 11C1;B256;1102 1170 11C1; # (뉖; 뉖; á„‚á…°á‡; 뉖; á„‚á…°á‡; ) HANGUL SYLLABLE NWEP
+B257;B257;1102 1170 11C2;B257;1102 1170 11C2; # (뉗; 뉗; 뉗; 뉗; 뉗; ) HANGUL SYLLABLE NWEH
+B258;B258;1102 1171;B258;1102 1171; # (뉘; 뉘; 뉘; 뉘; 뉘; ) HANGUL SYLLABLE NWI
+B259;B259;1102 1171 11A8;B259;1102 1171 11A8; # (뉙; 뉙; 뉙; 뉙; 뉙; ) HANGUL SYLLABLE NWIG
+B25A;B25A;1102 1171 11A9;B25A;1102 1171 11A9; # (뉚; 뉚; 뉚; 뉚; 뉚; ) HANGUL SYLLABLE NWIGG
+B25B;B25B;1102 1171 11AA;B25B;1102 1171 11AA; # (뉛; 뉛; 뉛; 뉛; 뉛; ) HANGUL SYLLABLE NWIGS
+B25C;B25C;1102 1171 11AB;B25C;1102 1171 11AB; # (뉜; 뉜; 뉜; 뉜; 뉜; ) HANGUL SYLLABLE NWIN
+B25D;B25D;1102 1171 11AC;B25D;1102 1171 11AC; # (ë‰; ë‰; 뉝; ë‰; 뉝; ) HANGUL SYLLABLE NWINJ
+B25E;B25E;1102 1171 11AD;B25E;1102 1171 11AD; # (뉞; 뉞; 뉞; 뉞; 뉞; ) HANGUL SYLLABLE NWINH
+B25F;B25F;1102 1171 11AE;B25F;1102 1171 11AE; # (뉟; 뉟; 뉟; 뉟; 뉟; ) HANGUL SYLLABLE NWID
+B260;B260;1102 1171 11AF;B260;1102 1171 11AF; # (뉠; 뉠; 뉠; 뉠; 뉠; ) HANGUL SYLLABLE NWIL
+B261;B261;1102 1171 11B0;B261;1102 1171 11B0; # (뉡; 뉡; 뉡; 뉡; 뉡; ) HANGUL SYLLABLE NWILG
+B262;B262;1102 1171 11B1;B262;1102 1171 11B1; # (뉢; 뉢; 뉢; 뉢; 뉢; ) HANGUL SYLLABLE NWILM
+B263;B263;1102 1171 11B2;B263;1102 1171 11B2; # (뉣; 뉣; 뉣; 뉣; 뉣; ) HANGUL SYLLABLE NWILB
+B264;B264;1102 1171 11B3;B264;1102 1171 11B3; # (뉤; 뉤; 뉤; 뉤; 뉤; ) HANGUL SYLLABLE NWILS
+B265;B265;1102 1171 11B4;B265;1102 1171 11B4; # (뉥; 뉥; 뉥; 뉥; 뉥; ) HANGUL SYLLABLE NWILT
+B266;B266;1102 1171 11B5;B266;1102 1171 11B5; # (뉦; 뉦; 뉦; 뉦; 뉦; ) HANGUL SYLLABLE NWILP
+B267;B267;1102 1171 11B6;B267;1102 1171 11B6; # (뉧; 뉧; 뉧; 뉧; 뉧; ) HANGUL SYLLABLE NWILH
+B268;B268;1102 1171 11B7;B268;1102 1171 11B7; # (뉨; 뉨; 뉨; 뉨; 뉨; ) HANGUL SYLLABLE NWIM
+B269;B269;1102 1171 11B8;B269;1102 1171 11B8; # (뉩; 뉩; 뉩; 뉩; 뉩; ) HANGUL SYLLABLE NWIB
+B26A;B26A;1102 1171 11B9;B26A;1102 1171 11B9; # (뉪; 뉪; 뉪; 뉪; 뉪; ) HANGUL SYLLABLE NWIBS
+B26B;B26B;1102 1171 11BA;B26B;1102 1171 11BA; # (뉫; 뉫; 뉫; 뉫; 뉫; ) HANGUL SYLLABLE NWIS
+B26C;B26C;1102 1171 11BB;B26C;1102 1171 11BB; # (뉬; 뉬; 뉬; 뉬; 뉬; ) HANGUL SYLLABLE NWISS
+B26D;B26D;1102 1171 11BC;B26D;1102 1171 11BC; # (뉭; 뉭; 뉭; 뉭; 뉭; ) HANGUL SYLLABLE NWING
+B26E;B26E;1102 1171 11BD;B26E;1102 1171 11BD; # (뉮; 뉮; 뉮; 뉮; 뉮; ) HANGUL SYLLABLE NWIJ
+B26F;B26F;1102 1171 11BE;B26F;1102 1171 11BE; # (뉯; 뉯; 뉯; 뉯; 뉯; ) HANGUL SYLLABLE NWIC
+B270;B270;1102 1171 11BF;B270;1102 1171 11BF; # (뉰; 뉰; 뉰; 뉰; 뉰; ) HANGUL SYLLABLE NWIK
+B271;B271;1102 1171 11C0;B271;1102 1171 11C0; # (뉱; 뉱; 뉱; 뉱; 뉱; ) HANGUL SYLLABLE NWIT
+B272;B272;1102 1171 11C1;B272;1102 1171 11C1; # (뉲; 뉲; á„‚á…±á‡; 뉲; á„‚á…±á‡; ) HANGUL SYLLABLE NWIP
+B273;B273;1102 1171 11C2;B273;1102 1171 11C2; # (뉳; 뉳; 뉳; 뉳; 뉳; ) HANGUL SYLLABLE NWIH
+B274;B274;1102 1172;B274;1102 1172; # (뉴; 뉴; 뉴; 뉴; 뉴; ) HANGUL SYLLABLE NYU
+B275;B275;1102 1172 11A8;B275;1102 1172 11A8; # (뉵; 뉵; 뉵; 뉵; 뉵; ) HANGUL SYLLABLE NYUG
+B276;B276;1102 1172 11A9;B276;1102 1172 11A9; # (뉶; 뉶; 뉶; 뉶; 뉶; ) HANGUL SYLLABLE NYUGG
+B277;B277;1102 1172 11AA;B277;1102 1172 11AA; # (뉷; 뉷; 뉷; 뉷; 뉷; ) HANGUL SYLLABLE NYUGS
+B278;B278;1102 1172 11AB;B278;1102 1172 11AB; # (뉸; 뉸; 뉸; 뉸; 뉸; ) HANGUL SYLLABLE NYUN
+B279;B279;1102 1172 11AC;B279;1102 1172 11AC; # (뉹; 뉹; 뉹; 뉹; 뉹; ) HANGUL SYLLABLE NYUNJ
+B27A;B27A;1102 1172 11AD;B27A;1102 1172 11AD; # (뉺; 뉺; 뉺; 뉺; 뉺; ) HANGUL SYLLABLE NYUNH
+B27B;B27B;1102 1172 11AE;B27B;1102 1172 11AE; # (뉻; 뉻; 뉻; 뉻; 뉻; ) HANGUL SYLLABLE NYUD
+B27C;B27C;1102 1172 11AF;B27C;1102 1172 11AF; # (뉼; 뉼; 뉼; 뉼; 뉼; ) HANGUL SYLLABLE NYUL
+B27D;B27D;1102 1172 11B0;B27D;1102 1172 11B0; # (뉽; 뉽; 뉽; 뉽; 뉽; ) HANGUL SYLLABLE NYULG
+B27E;B27E;1102 1172 11B1;B27E;1102 1172 11B1; # (뉾; 뉾; 뉾; 뉾; 뉾; ) HANGUL SYLLABLE NYULM
+B27F;B27F;1102 1172 11B2;B27F;1102 1172 11B2; # (뉿; 뉿; 뉿; 뉿; 뉿; ) HANGUL SYLLABLE NYULB
+B280;B280;1102 1172 11B3;B280;1102 1172 11B3; # (늀; 늀; 늀; 늀; 늀; ) HANGUL SYLLABLE NYULS
+B281;B281;1102 1172 11B4;B281;1102 1172 11B4; # (ëŠ; ëŠ; 늁; ëŠ; 늁; ) HANGUL SYLLABLE NYULT
+B282;B282;1102 1172 11B5;B282;1102 1172 11B5; # (늂; 늂; 늂; 늂; 늂; ) HANGUL SYLLABLE NYULP
+B283;B283;1102 1172 11B6;B283;1102 1172 11B6; # (늃; 늃; 늃; 늃; 늃; ) HANGUL SYLLABLE NYULH
+B284;B284;1102 1172 11B7;B284;1102 1172 11B7; # (늄; 늄; 늄; 늄; 늄; ) HANGUL SYLLABLE NYUM
+B285;B285;1102 1172 11B8;B285;1102 1172 11B8; # (늅; 늅; 늅; 늅; 늅; ) HANGUL SYLLABLE NYUB
+B286;B286;1102 1172 11B9;B286;1102 1172 11B9; # (늆; 늆; 늆; 늆; 늆; ) HANGUL SYLLABLE NYUBS
+B287;B287;1102 1172 11BA;B287;1102 1172 11BA; # (늇; 늇; 늇; 늇; 늇; ) HANGUL SYLLABLE NYUS
+B288;B288;1102 1172 11BB;B288;1102 1172 11BB; # (늈; 늈; 늈; 늈; 늈; ) HANGUL SYLLABLE NYUSS
+B289;B289;1102 1172 11BC;B289;1102 1172 11BC; # (늉; 늉; 늉; 늉; 늉; ) HANGUL SYLLABLE NYUNG
+B28A;B28A;1102 1172 11BD;B28A;1102 1172 11BD; # (늊; 늊; 늊; 늊; 늊; ) HANGUL SYLLABLE NYUJ
+B28B;B28B;1102 1172 11BE;B28B;1102 1172 11BE; # (늋; 늋; 늋; 늋; 늋; ) HANGUL SYLLABLE NYUC
+B28C;B28C;1102 1172 11BF;B28C;1102 1172 11BF; # (늌; 늌; 늌; 늌; 늌; ) HANGUL SYLLABLE NYUK
+B28D;B28D;1102 1172 11C0;B28D;1102 1172 11C0; # (ëŠ; ëŠ; 늍; ëŠ; 늍; ) HANGUL SYLLABLE NYUT
+B28E;B28E;1102 1172 11C1;B28E;1102 1172 11C1; # (늎; 늎; á„‚á…²á‡; 늎; á„‚á…²á‡; ) HANGUL SYLLABLE NYUP
+B28F;B28F;1102 1172 11C2;B28F;1102 1172 11C2; # (ëŠ; ëŠ; 늏; ëŠ; 늏; ) HANGUL SYLLABLE NYUH
+B290;B290;1102 1173;B290;1102 1173; # (ëŠ; ëŠ; á„‚á…³; ëŠ; á„‚á…³; ) HANGUL SYLLABLE NEU
+B291;B291;1102 1173 11A8;B291;1102 1173 11A8; # (늑; 늑; 늑; 늑; 늑; ) HANGUL SYLLABLE NEUG
+B292;B292;1102 1173 11A9;B292;1102 1173 11A9; # (늒; 늒; 늒; 늒; 늒; ) HANGUL SYLLABLE NEUGG
+B293;B293;1102 1173 11AA;B293;1102 1173 11AA; # (늓; 늓; 늓; 늓; 늓; ) HANGUL SYLLABLE NEUGS
+B294;B294;1102 1173 11AB;B294;1102 1173 11AB; # (는; 는; 는; 는; 는; ) HANGUL SYLLABLE NEUN
+B295;B295;1102 1173 11AC;B295;1102 1173 11AC; # (늕; 늕; 늕; 늕; 늕; ) HANGUL SYLLABLE NEUNJ
+B296;B296;1102 1173 11AD;B296;1102 1173 11AD; # (늖; 늖; 늖; 늖; 늖; ) HANGUL SYLLABLE NEUNH
+B297;B297;1102 1173 11AE;B297;1102 1173 11AE; # (늗; 늗; 늗; 늗; 늗; ) HANGUL SYLLABLE NEUD
+B298;B298;1102 1173 11AF;B298;1102 1173 11AF; # (늘; 늘; 늘; 늘; 늘; ) HANGUL SYLLABLE NEUL
+B299;B299;1102 1173 11B0;B299;1102 1173 11B0; # (늙; 늙; 늙; 늙; 늙; ) HANGUL SYLLABLE NEULG
+B29A;B29A;1102 1173 11B1;B29A;1102 1173 11B1; # (늚; 늚; 늚; 늚; 늚; ) HANGUL SYLLABLE NEULM
+B29B;B29B;1102 1173 11B2;B29B;1102 1173 11B2; # (늛; 늛; 늛; 늛; 늛; ) HANGUL SYLLABLE NEULB
+B29C;B29C;1102 1173 11B3;B29C;1102 1173 11B3; # (늜; 늜; 늜; 늜; 늜; ) HANGUL SYLLABLE NEULS
+B29D;B29D;1102 1173 11B4;B29D;1102 1173 11B4; # (ëŠ; ëŠ; 늝; ëŠ; 늝; ) HANGUL SYLLABLE NEULT
+B29E;B29E;1102 1173 11B5;B29E;1102 1173 11B5; # (늞; 늞; 늞; 늞; 늞; ) HANGUL SYLLABLE NEULP
+B29F;B29F;1102 1173 11B6;B29F;1102 1173 11B6; # (늟; 늟; 늟; 늟; 늟; ) HANGUL SYLLABLE NEULH
+B2A0;B2A0;1102 1173 11B7;B2A0;1102 1173 11B7; # (늠; 늠; 늠; 늠; 늠; ) HANGUL SYLLABLE NEUM
+B2A1;B2A1;1102 1173 11B8;B2A1;1102 1173 11B8; # (늡; 늡; 늡; 늡; 늡; ) HANGUL SYLLABLE NEUB
+B2A2;B2A2;1102 1173 11B9;B2A2;1102 1173 11B9; # (늢; 늢; 늢; 늢; 늢; ) HANGUL SYLLABLE NEUBS
+B2A3;B2A3;1102 1173 11BA;B2A3;1102 1173 11BA; # (늣; 늣; 늣; 늣; 늣; ) HANGUL SYLLABLE NEUS
+B2A4;B2A4;1102 1173 11BB;B2A4;1102 1173 11BB; # (늤; 늤; 늤; 늤; 늤; ) HANGUL SYLLABLE NEUSS
+B2A5;B2A5;1102 1173 11BC;B2A5;1102 1173 11BC; # (능; 능; 능; 능; 능; ) HANGUL SYLLABLE NEUNG
+B2A6;B2A6;1102 1173 11BD;B2A6;1102 1173 11BD; # (늦; 늦; 늦; 늦; 늦; ) HANGUL SYLLABLE NEUJ
+B2A7;B2A7;1102 1173 11BE;B2A7;1102 1173 11BE; # (늧; 늧; 늧; 늧; 늧; ) HANGUL SYLLABLE NEUC
+B2A8;B2A8;1102 1173 11BF;B2A8;1102 1173 11BF; # (늨; 늨; 늨; 늨; 늨; ) HANGUL SYLLABLE NEUK
+B2A9;B2A9;1102 1173 11C0;B2A9;1102 1173 11C0; # (늩; 늩; 늩; 늩; 늩; ) HANGUL SYLLABLE NEUT
+B2AA;B2AA;1102 1173 11C1;B2AA;1102 1173 11C1; # (늪; 늪; á„‚á…³á‡; 늪; á„‚á…³á‡; ) HANGUL SYLLABLE NEUP
+B2AB;B2AB;1102 1173 11C2;B2AB;1102 1173 11C2; # (늫; 늫; 늫; 늫; 늫; ) HANGUL SYLLABLE NEUH
+B2AC;B2AC;1102 1174;B2AC;1102 1174; # (늬; 늬; 늬; 늬; 늬; ) HANGUL SYLLABLE NYI
+B2AD;B2AD;1102 1174 11A8;B2AD;1102 1174 11A8; # (늭; 늭; 늭; 늭; 늭; ) HANGUL SYLLABLE NYIG
+B2AE;B2AE;1102 1174 11A9;B2AE;1102 1174 11A9; # (늮; 늮; 늮; 늮; 늮; ) HANGUL SYLLABLE NYIGG
+B2AF;B2AF;1102 1174 11AA;B2AF;1102 1174 11AA; # (늯; 늯; 늯; 늯; 늯; ) HANGUL SYLLABLE NYIGS
+B2B0;B2B0;1102 1174 11AB;B2B0;1102 1174 11AB; # (늰; 늰; 늰; 늰; 늰; ) HANGUL SYLLABLE NYIN
+B2B1;B2B1;1102 1174 11AC;B2B1;1102 1174 11AC; # (늱; 늱; 늱; 늱; 늱; ) HANGUL SYLLABLE NYINJ
+B2B2;B2B2;1102 1174 11AD;B2B2;1102 1174 11AD; # (늲; 늲; 늲; 늲; 늲; ) HANGUL SYLLABLE NYINH
+B2B3;B2B3;1102 1174 11AE;B2B3;1102 1174 11AE; # (늳; 늳; 늳; 늳; 늳; ) HANGUL SYLLABLE NYID
+B2B4;B2B4;1102 1174 11AF;B2B4;1102 1174 11AF; # (늴; 늴; 늴; 늴; 늴; ) HANGUL SYLLABLE NYIL
+B2B5;B2B5;1102 1174 11B0;B2B5;1102 1174 11B0; # (늵; 늵; 늵; 늵; 늵; ) HANGUL SYLLABLE NYILG
+B2B6;B2B6;1102 1174 11B1;B2B6;1102 1174 11B1; # (늶; 늶; 늶; 늶; 늶; ) HANGUL SYLLABLE NYILM
+B2B7;B2B7;1102 1174 11B2;B2B7;1102 1174 11B2; # (늷; 늷; 늷; 늷; 늷; ) HANGUL SYLLABLE NYILB
+B2B8;B2B8;1102 1174 11B3;B2B8;1102 1174 11B3; # (늸; 늸; 늸; 늸; 늸; ) HANGUL SYLLABLE NYILS
+B2B9;B2B9;1102 1174 11B4;B2B9;1102 1174 11B4; # (늹; 늹; 늹; 늹; 늹; ) HANGUL SYLLABLE NYILT
+B2BA;B2BA;1102 1174 11B5;B2BA;1102 1174 11B5; # (늺; 늺; 늺; 늺; 늺; ) HANGUL SYLLABLE NYILP
+B2BB;B2BB;1102 1174 11B6;B2BB;1102 1174 11B6; # (늻; 늻; 늻; 늻; 늻; ) HANGUL SYLLABLE NYILH
+B2BC;B2BC;1102 1174 11B7;B2BC;1102 1174 11B7; # (늼; 늼; 늼; 늼; 늼; ) HANGUL SYLLABLE NYIM
+B2BD;B2BD;1102 1174 11B8;B2BD;1102 1174 11B8; # (늽; 늽; 늽; 늽; 늽; ) HANGUL SYLLABLE NYIB
+B2BE;B2BE;1102 1174 11B9;B2BE;1102 1174 11B9; # (늾; 늾; 늾; 늾; 늾; ) HANGUL SYLLABLE NYIBS
+B2BF;B2BF;1102 1174 11BA;B2BF;1102 1174 11BA; # (늿; 늿; 늿; 늿; 늿; ) HANGUL SYLLABLE NYIS
+B2C0;B2C0;1102 1174 11BB;B2C0;1102 1174 11BB; # (닀; 닀; 닀; 닀; 닀; ) HANGUL SYLLABLE NYISS
+B2C1;B2C1;1102 1174 11BC;B2C1;1102 1174 11BC; # (ë‹; ë‹; 닁; ë‹; 닁; ) HANGUL SYLLABLE NYING
+B2C2;B2C2;1102 1174 11BD;B2C2;1102 1174 11BD; # (닂; 닂; 닂; 닂; 닂; ) HANGUL SYLLABLE NYIJ
+B2C3;B2C3;1102 1174 11BE;B2C3;1102 1174 11BE; # (닃; 닃; 닃; 닃; 닃; ) HANGUL SYLLABLE NYIC
+B2C4;B2C4;1102 1174 11BF;B2C4;1102 1174 11BF; # (닄; 닄; 닄; 닄; 닄; ) HANGUL SYLLABLE NYIK
+B2C5;B2C5;1102 1174 11C0;B2C5;1102 1174 11C0; # (닅; 닅; 닅; 닅; 닅; ) HANGUL SYLLABLE NYIT
+B2C6;B2C6;1102 1174 11C1;B2C6;1102 1174 11C1; # (닆; 닆; á„‚á…´á‡; 닆; á„‚á…´á‡; ) HANGUL SYLLABLE NYIP
+B2C7;B2C7;1102 1174 11C2;B2C7;1102 1174 11C2; # (닇; 닇; 닇; 닇; 닇; ) HANGUL SYLLABLE NYIH
+B2C8;B2C8;1102 1175;B2C8;1102 1175; # (니; 니; 니; 니; 니; ) HANGUL SYLLABLE NI
+B2C9;B2C9;1102 1175 11A8;B2C9;1102 1175 11A8; # (닉; 닉; 닉; 닉; 닉; ) HANGUL SYLLABLE NIG
+B2CA;B2CA;1102 1175 11A9;B2CA;1102 1175 11A9; # (닊; 닊; 닊; 닊; 닊; ) HANGUL SYLLABLE NIGG
+B2CB;B2CB;1102 1175 11AA;B2CB;1102 1175 11AA; # (닋; 닋; 닋; 닋; 닋; ) HANGUL SYLLABLE NIGS
+B2CC;B2CC;1102 1175 11AB;B2CC;1102 1175 11AB; # (닌; 닌; 닌; 닌; 닌; ) HANGUL SYLLABLE NIN
+B2CD;B2CD;1102 1175 11AC;B2CD;1102 1175 11AC; # (ë‹; ë‹; 닍; ë‹; 닍; ) HANGUL SYLLABLE NINJ
+B2CE;B2CE;1102 1175 11AD;B2CE;1102 1175 11AD; # (닎; 닎; 닎; 닎; 닎; ) HANGUL SYLLABLE NINH
+B2CF;B2CF;1102 1175 11AE;B2CF;1102 1175 11AE; # (ë‹; ë‹; 닏; ë‹; 닏; ) HANGUL SYLLABLE NID
+B2D0;B2D0;1102 1175 11AF;B2D0;1102 1175 11AF; # (ë‹; ë‹; 닐; ë‹; 닐; ) HANGUL SYLLABLE NIL
+B2D1;B2D1;1102 1175 11B0;B2D1;1102 1175 11B0; # (닑; 닑; 닑; 닑; 닑; ) HANGUL SYLLABLE NILG
+B2D2;B2D2;1102 1175 11B1;B2D2;1102 1175 11B1; # (닒; 닒; 닒; 닒; 닒; ) HANGUL SYLLABLE NILM
+B2D3;B2D3;1102 1175 11B2;B2D3;1102 1175 11B2; # (닓; 닓; 닓; 닓; 닓; ) HANGUL SYLLABLE NILB
+B2D4;B2D4;1102 1175 11B3;B2D4;1102 1175 11B3; # (닔; 닔; 닔; 닔; 닔; ) HANGUL SYLLABLE NILS
+B2D5;B2D5;1102 1175 11B4;B2D5;1102 1175 11B4; # (닕; 닕; 닕; 닕; 닕; ) HANGUL SYLLABLE NILT
+B2D6;B2D6;1102 1175 11B5;B2D6;1102 1175 11B5; # (닖; 닖; 닖; 닖; 닖; ) HANGUL SYLLABLE NILP
+B2D7;B2D7;1102 1175 11B6;B2D7;1102 1175 11B6; # (닗; 닗; 닗; 닗; 닗; ) HANGUL SYLLABLE NILH
+B2D8;B2D8;1102 1175 11B7;B2D8;1102 1175 11B7; # (님; 님; 님; 님; 님; ) HANGUL SYLLABLE NIM
+B2D9;B2D9;1102 1175 11B8;B2D9;1102 1175 11B8; # (닙; 닙; 닙; 닙; 닙; ) HANGUL SYLLABLE NIB
+B2DA;B2DA;1102 1175 11B9;B2DA;1102 1175 11B9; # (닚; 닚; 닚; 닚; 닚; ) HANGUL SYLLABLE NIBS
+B2DB;B2DB;1102 1175 11BA;B2DB;1102 1175 11BA; # (닛; 닛; 닛; 닛; 닛; ) HANGUL SYLLABLE NIS
+B2DC;B2DC;1102 1175 11BB;B2DC;1102 1175 11BB; # (닜; 닜; 닜; 닜; 닜; ) HANGUL SYLLABLE NISS
+B2DD;B2DD;1102 1175 11BC;B2DD;1102 1175 11BC; # (ë‹; ë‹; 닝; ë‹; 닝; ) HANGUL SYLLABLE NING
+B2DE;B2DE;1102 1175 11BD;B2DE;1102 1175 11BD; # (닞; 닞; 닞; 닞; 닞; ) HANGUL SYLLABLE NIJ
+B2DF;B2DF;1102 1175 11BE;B2DF;1102 1175 11BE; # (닟; 닟; 닟; 닟; 닟; ) HANGUL SYLLABLE NIC
+B2E0;B2E0;1102 1175 11BF;B2E0;1102 1175 11BF; # (닠; 닠; 닠; 닠; 닠; ) HANGUL SYLLABLE NIK
+B2E1;B2E1;1102 1175 11C0;B2E1;1102 1175 11C0; # (닡; 닡; 닡; 닡; 닡; ) HANGUL SYLLABLE NIT
+B2E2;B2E2;1102 1175 11C1;B2E2;1102 1175 11C1; # (ë‹¢; ë‹¢; á„‚á…µá‡; ë‹¢; á„‚á…µá‡; ) HANGUL SYLLABLE NIP
+B2E3;B2E3;1102 1175 11C2;B2E3;1102 1175 11C2; # (닣; 닣; 닣; 닣; 닣; ) HANGUL SYLLABLE NIH
+B2E4;B2E4;1103 1161;B2E4;1103 1161; # (다; 다; 다; 다; 다; ) HANGUL SYLLABLE DA
+B2E5;B2E5;1103 1161 11A8;B2E5;1103 1161 11A8; # (닥; 닥; 닥; 닥; 닥; ) HANGUL SYLLABLE DAG
+B2E6;B2E6;1103 1161 11A9;B2E6;1103 1161 11A9; # (닦; 닦; 닦; 닦; 닦; ) HANGUL SYLLABLE DAGG
+B2E7;B2E7;1103 1161 11AA;B2E7;1103 1161 11AA; # (닧; 닧; 닧; 닧; 닧; ) HANGUL SYLLABLE DAGS
+B2E8;B2E8;1103 1161 11AB;B2E8;1103 1161 11AB; # (단; 단; 단; 단; 단; ) HANGUL SYLLABLE DAN
+B2E9;B2E9;1103 1161 11AC;B2E9;1103 1161 11AC; # (닩; 닩; 닩; 닩; 닩; ) HANGUL SYLLABLE DANJ
+B2EA;B2EA;1103 1161 11AD;B2EA;1103 1161 11AD; # (닪; 닪; 닪; 닪; 닪; ) HANGUL SYLLABLE DANH
+B2EB;B2EB;1103 1161 11AE;B2EB;1103 1161 11AE; # (닫; 닫; 닫; 닫; 닫; ) HANGUL SYLLABLE DAD
+B2EC;B2EC;1103 1161 11AF;B2EC;1103 1161 11AF; # (달; 달; 달; 달; 달; ) HANGUL SYLLABLE DAL
+B2ED;B2ED;1103 1161 11B0;B2ED;1103 1161 11B0; # (닭; 닭; 닭; 닭; 닭; ) HANGUL SYLLABLE DALG
+B2EE;B2EE;1103 1161 11B1;B2EE;1103 1161 11B1; # (닮; 닮; 닮; 닮; 닮; ) HANGUL SYLLABLE DALM
+B2EF;B2EF;1103 1161 11B2;B2EF;1103 1161 11B2; # (닯; 닯; 닯; 닯; 닯; ) HANGUL SYLLABLE DALB
+B2F0;B2F0;1103 1161 11B3;B2F0;1103 1161 11B3; # (닰; 닰; 닰; 닰; 닰; ) HANGUL SYLLABLE DALS
+B2F1;B2F1;1103 1161 11B4;B2F1;1103 1161 11B4; # (닱; 닱; 닱; 닱; 닱; ) HANGUL SYLLABLE DALT
+B2F2;B2F2;1103 1161 11B5;B2F2;1103 1161 11B5; # (닲; 닲; 닲; 닲; 닲; ) HANGUL SYLLABLE DALP
+B2F3;B2F3;1103 1161 11B6;B2F3;1103 1161 11B6; # (닳; 닳; 닳; 닳; 닳; ) HANGUL SYLLABLE DALH
+B2F4;B2F4;1103 1161 11B7;B2F4;1103 1161 11B7; # (담; 담; 담; 담; 담; ) HANGUL SYLLABLE DAM
+B2F5;B2F5;1103 1161 11B8;B2F5;1103 1161 11B8; # (답; 답; 답; 답; 답; ) HANGUL SYLLABLE DAB
+B2F6;B2F6;1103 1161 11B9;B2F6;1103 1161 11B9; # (닶; 닶; 닶; 닶; 닶; ) HANGUL SYLLABLE DABS
+B2F7;B2F7;1103 1161 11BA;B2F7;1103 1161 11BA; # (닷; 닷; 닷; 닷; 닷; ) HANGUL SYLLABLE DAS
+B2F8;B2F8;1103 1161 11BB;B2F8;1103 1161 11BB; # (닸; 닸; 닸; 닸; 닸; ) HANGUL SYLLABLE DASS
+B2F9;B2F9;1103 1161 11BC;B2F9;1103 1161 11BC; # (당; 당; 당; 당; 당; ) HANGUL SYLLABLE DANG
+B2FA;B2FA;1103 1161 11BD;B2FA;1103 1161 11BD; # (닺; 닺; 닺; 닺; 닺; ) HANGUL SYLLABLE DAJ
+B2FB;B2FB;1103 1161 11BE;B2FB;1103 1161 11BE; # (닻; 닻; 닻; 닻; 닻; ) HANGUL SYLLABLE DAC
+B2FC;B2FC;1103 1161 11BF;B2FC;1103 1161 11BF; # (닼; 닼; 닼; 닼; 닼; ) HANGUL SYLLABLE DAK
+B2FD;B2FD;1103 1161 11C0;B2FD;1103 1161 11C0; # (닽; 닽; 닽; 닽; 닽; ) HANGUL SYLLABLE DAT
+B2FE;B2FE;1103 1161 11C1;B2FE;1103 1161 11C1; # (닾; 닾; 다á‡; 닾; 다á‡; ) HANGUL SYLLABLE DAP
+B2FF;B2FF;1103 1161 11C2;B2FF;1103 1161 11C2; # (닿; 닿; 닿; 닿; 닿; ) HANGUL SYLLABLE DAH
+B300;B300;1103 1162;B300;1103 1162; # (대; 대; 대; 대; 대; ) HANGUL SYLLABLE DAE
+B301;B301;1103 1162 11A8;B301;1103 1162 11A8; # (ëŒ; ëŒ; 댁; ëŒ; 댁; ) HANGUL SYLLABLE DAEG
+B302;B302;1103 1162 11A9;B302;1103 1162 11A9; # (댂; 댂; 댂; 댂; 댂; ) HANGUL SYLLABLE DAEGG
+B303;B303;1103 1162 11AA;B303;1103 1162 11AA; # (댃; 댃; 댃; 댃; 댃; ) HANGUL SYLLABLE DAEGS
+B304;B304;1103 1162 11AB;B304;1103 1162 11AB; # (댄; 댄; 댄; 댄; 댄; ) HANGUL SYLLABLE DAEN
+B305;B305;1103 1162 11AC;B305;1103 1162 11AC; # (댅; 댅; 댅; 댅; 댅; ) HANGUL SYLLABLE DAENJ
+B306;B306;1103 1162 11AD;B306;1103 1162 11AD; # (댆; 댆; 댆; 댆; 댆; ) HANGUL SYLLABLE DAENH
+B307;B307;1103 1162 11AE;B307;1103 1162 11AE; # (댇; 댇; 댇; 댇; 댇; ) HANGUL SYLLABLE DAED
+B308;B308;1103 1162 11AF;B308;1103 1162 11AF; # (댈; 댈; 댈; 댈; 댈; ) HANGUL SYLLABLE DAEL
+B309;B309;1103 1162 11B0;B309;1103 1162 11B0; # (댉; 댉; 댉; 댉; 댉; ) HANGUL SYLLABLE DAELG
+B30A;B30A;1103 1162 11B1;B30A;1103 1162 11B1; # (댊; 댊; 댊; 댊; 댊; ) HANGUL SYLLABLE DAELM
+B30B;B30B;1103 1162 11B2;B30B;1103 1162 11B2; # (댋; 댋; 댋; 댋; 댋; ) HANGUL SYLLABLE DAELB
+B30C;B30C;1103 1162 11B3;B30C;1103 1162 11B3; # (댌; 댌; 댌; 댌; 댌; ) HANGUL SYLLABLE DAELS
+B30D;B30D;1103 1162 11B4;B30D;1103 1162 11B4; # (ëŒ; ëŒ; 댍; ëŒ; 댍; ) HANGUL SYLLABLE DAELT
+B30E;B30E;1103 1162 11B5;B30E;1103 1162 11B5; # (댎; 댎; 댎; 댎; 댎; ) HANGUL SYLLABLE DAELP
+B30F;B30F;1103 1162 11B6;B30F;1103 1162 11B6; # (ëŒ; ëŒ; 댏; ëŒ; 댏; ) HANGUL SYLLABLE DAELH
+B310;B310;1103 1162 11B7;B310;1103 1162 11B7; # (ëŒ; ëŒ; 댐; ëŒ; 댐; ) HANGUL SYLLABLE DAEM
+B311;B311;1103 1162 11B8;B311;1103 1162 11B8; # (댑; 댑; 댑; 댑; 댑; ) HANGUL SYLLABLE DAEB
+B312;B312;1103 1162 11B9;B312;1103 1162 11B9; # (댒; 댒; 댒; 댒; 댒; ) HANGUL SYLLABLE DAEBS
+B313;B313;1103 1162 11BA;B313;1103 1162 11BA; # (댓; 댓; 댓; 댓; 댓; ) HANGUL SYLLABLE DAES
+B314;B314;1103 1162 11BB;B314;1103 1162 11BB; # (댔; 댔; 댔; 댔; 댔; ) HANGUL SYLLABLE DAESS
+B315;B315;1103 1162 11BC;B315;1103 1162 11BC; # (댕; 댕; 댕; 댕; 댕; ) HANGUL SYLLABLE DAENG
+B316;B316;1103 1162 11BD;B316;1103 1162 11BD; # (댖; 댖; 댖; 댖; 댖; ) HANGUL SYLLABLE DAEJ
+B317;B317;1103 1162 11BE;B317;1103 1162 11BE; # (댗; 댗; 댗; 댗; 댗; ) HANGUL SYLLABLE DAEC
+B318;B318;1103 1162 11BF;B318;1103 1162 11BF; # (댘; 댘; 댘; 댘; 댘; ) HANGUL SYLLABLE DAEK
+B319;B319;1103 1162 11C0;B319;1103 1162 11C0; # (댙; 댙; 댙; 댙; 댙; ) HANGUL SYLLABLE DAET
+B31A;B31A;1103 1162 11C1;B31A;1103 1162 11C1; # (댚; 댚; 대á‡; 댚; 대á‡; ) HANGUL SYLLABLE DAEP
+B31B;B31B;1103 1162 11C2;B31B;1103 1162 11C2; # (댛; 댛; 댛; 댛; 댛; ) HANGUL SYLLABLE DAEH
+B31C;B31C;1103 1163;B31C;1103 1163; # (댜; 댜; 댜; 댜; 댜; ) HANGUL SYLLABLE DYA
+B31D;B31D;1103 1163 11A8;B31D;1103 1163 11A8; # (ëŒ; ëŒ; 댝; ëŒ; 댝; ) HANGUL SYLLABLE DYAG
+B31E;B31E;1103 1163 11A9;B31E;1103 1163 11A9; # (댞; 댞; 댞; 댞; 댞; ) HANGUL SYLLABLE DYAGG
+B31F;B31F;1103 1163 11AA;B31F;1103 1163 11AA; # (댟; 댟; 댟; 댟; 댟; ) HANGUL SYLLABLE DYAGS
+B320;B320;1103 1163 11AB;B320;1103 1163 11AB; # (댠; 댠; 댠; 댠; 댠; ) HANGUL SYLLABLE DYAN
+B321;B321;1103 1163 11AC;B321;1103 1163 11AC; # (댡; 댡; 댡; 댡; 댡; ) HANGUL SYLLABLE DYANJ
+B322;B322;1103 1163 11AD;B322;1103 1163 11AD; # (댢; 댢; 댢; 댢; 댢; ) HANGUL SYLLABLE DYANH
+B323;B323;1103 1163 11AE;B323;1103 1163 11AE; # (댣; 댣; 댣; 댣; 댣; ) HANGUL SYLLABLE DYAD
+B324;B324;1103 1163 11AF;B324;1103 1163 11AF; # (댤; 댤; 댤; 댤; 댤; ) HANGUL SYLLABLE DYAL
+B325;B325;1103 1163 11B0;B325;1103 1163 11B0; # (댥; 댥; 댥; 댥; 댥; ) HANGUL SYLLABLE DYALG
+B326;B326;1103 1163 11B1;B326;1103 1163 11B1; # (댦; 댦; 댦; 댦; 댦; ) HANGUL SYLLABLE DYALM
+B327;B327;1103 1163 11B2;B327;1103 1163 11B2; # (댧; 댧; 댧; 댧; 댧; ) HANGUL SYLLABLE DYALB
+B328;B328;1103 1163 11B3;B328;1103 1163 11B3; # (댨; 댨; 댨; 댨; 댨; ) HANGUL SYLLABLE DYALS
+B329;B329;1103 1163 11B4;B329;1103 1163 11B4; # (댩; 댩; 댩; 댩; 댩; ) HANGUL SYLLABLE DYALT
+B32A;B32A;1103 1163 11B5;B32A;1103 1163 11B5; # (댪; 댪; 댪; 댪; 댪; ) HANGUL SYLLABLE DYALP
+B32B;B32B;1103 1163 11B6;B32B;1103 1163 11B6; # (댫; 댫; 댫; 댫; 댫; ) HANGUL SYLLABLE DYALH
+B32C;B32C;1103 1163 11B7;B32C;1103 1163 11B7; # (댬; 댬; 댬; 댬; 댬; ) HANGUL SYLLABLE DYAM
+B32D;B32D;1103 1163 11B8;B32D;1103 1163 11B8; # (댭; 댭; 댭; 댭; 댭; ) HANGUL SYLLABLE DYAB
+B32E;B32E;1103 1163 11B9;B32E;1103 1163 11B9; # (댮; 댮; 댮; 댮; 댮; ) HANGUL SYLLABLE DYABS
+B32F;B32F;1103 1163 11BA;B32F;1103 1163 11BA; # (댯; 댯; 댯; 댯; 댯; ) HANGUL SYLLABLE DYAS
+B330;B330;1103 1163 11BB;B330;1103 1163 11BB; # (댰; 댰; 댰; 댰; 댰; ) HANGUL SYLLABLE DYASS
+B331;B331;1103 1163 11BC;B331;1103 1163 11BC; # (댱; 댱; 댱; 댱; 댱; ) HANGUL SYLLABLE DYANG
+B332;B332;1103 1163 11BD;B332;1103 1163 11BD; # (댲; 댲; 댲; 댲; 댲; ) HANGUL SYLLABLE DYAJ
+B333;B333;1103 1163 11BE;B333;1103 1163 11BE; # (댳; 댳; 댳; 댳; 댳; ) HANGUL SYLLABLE DYAC
+B334;B334;1103 1163 11BF;B334;1103 1163 11BF; # (댴; 댴; 댴; 댴; 댴; ) HANGUL SYLLABLE DYAK
+B335;B335;1103 1163 11C0;B335;1103 1163 11C0; # (댵; 댵; 댵; 댵; 댵; ) HANGUL SYLLABLE DYAT
+B336;B336;1103 1163 11C1;B336;1103 1163 11C1; # (댶; 댶; 댜á‡; 댶; 댜á‡; ) HANGUL SYLLABLE DYAP
+B337;B337;1103 1163 11C2;B337;1103 1163 11C2; # (댷; 댷; 댷; 댷; 댷; ) HANGUL SYLLABLE DYAH
+B338;B338;1103 1164;B338;1103 1164; # (댸; 댸; 댸; 댸; 댸; ) HANGUL SYLLABLE DYAE
+B339;B339;1103 1164 11A8;B339;1103 1164 11A8; # (댹; 댹; 댹; 댹; 댹; ) HANGUL SYLLABLE DYAEG
+B33A;B33A;1103 1164 11A9;B33A;1103 1164 11A9; # (댺; 댺; 댺; 댺; 댺; ) HANGUL SYLLABLE DYAEGG
+B33B;B33B;1103 1164 11AA;B33B;1103 1164 11AA; # (댻; 댻; 댻; 댻; 댻; ) HANGUL SYLLABLE DYAEGS
+B33C;B33C;1103 1164 11AB;B33C;1103 1164 11AB; # (댼; 댼; 댼; 댼; 댼; ) HANGUL SYLLABLE DYAEN
+B33D;B33D;1103 1164 11AC;B33D;1103 1164 11AC; # (댽; 댽; 댽; 댽; 댽; ) HANGUL SYLLABLE DYAENJ
+B33E;B33E;1103 1164 11AD;B33E;1103 1164 11AD; # (댾; 댾; 댾; 댾; 댾; ) HANGUL SYLLABLE DYAENH
+B33F;B33F;1103 1164 11AE;B33F;1103 1164 11AE; # (댿; 댿; 댿; 댿; 댿; ) HANGUL SYLLABLE DYAED
+B340;B340;1103 1164 11AF;B340;1103 1164 11AF; # (ë€; ë€; 덀; ë€; 덀; ) HANGUL SYLLABLE DYAEL
+B341;B341;1103 1164 11B0;B341;1103 1164 11B0; # (ë; ë; 덁; ë; 덁; ) HANGUL SYLLABLE DYAELG
+B342;B342;1103 1164 11B1;B342;1103 1164 11B1; # (ë‚; ë‚; 덂; ë‚; 덂; ) HANGUL SYLLABLE DYAELM
+B343;B343;1103 1164 11B2;B343;1103 1164 11B2; # (ëƒ; ëƒ; 덃; ëƒ; 덃; ) HANGUL SYLLABLE DYAELB
+B344;B344;1103 1164 11B3;B344;1103 1164 11B3; # (ë„; ë„; 덄; ë„; 덄; ) HANGUL SYLLABLE DYAELS
+B345;B345;1103 1164 11B4;B345;1103 1164 11B4; # (ë…; ë…; 덅; ë…; 덅; ) HANGUL SYLLABLE DYAELT
+B346;B346;1103 1164 11B5;B346;1103 1164 11B5; # (ë†; ë†; 덆; ë†; 덆; ) HANGUL SYLLABLE DYAELP
+B347;B347;1103 1164 11B6;B347;1103 1164 11B6; # (ë‡; ë‡; 덇; ë‡; 덇; ) HANGUL SYLLABLE DYAELH
+B348;B348;1103 1164 11B7;B348;1103 1164 11B7; # (ëˆ; ëˆ; 덈; ëˆ; 덈; ) HANGUL SYLLABLE DYAEM
+B349;B349;1103 1164 11B8;B349;1103 1164 11B8; # (ë‰; ë‰; 덉; ë‰; 덉; ) HANGUL SYLLABLE DYAEB
+B34A;B34A;1103 1164 11B9;B34A;1103 1164 11B9; # (ëŠ; ëŠ; 덊; ëŠ; 덊; ) HANGUL SYLLABLE DYAEBS
+B34B;B34B;1103 1164 11BA;B34B;1103 1164 11BA; # (ë‹; ë‹; 덋; ë‹; 덋; ) HANGUL SYLLABLE DYAES
+B34C;B34C;1103 1164 11BB;B34C;1103 1164 11BB; # (ëŒ; ëŒ; 덌; ëŒ; 덌; ) HANGUL SYLLABLE DYAESS
+B34D;B34D;1103 1164 11BC;B34D;1103 1164 11BC; # (ë; ë; 덍; ë; 덍; ) HANGUL SYLLABLE DYAENG
+B34E;B34E;1103 1164 11BD;B34E;1103 1164 11BD; # (ëŽ; ëŽ; 덎; ëŽ; 덎; ) HANGUL SYLLABLE DYAEJ
+B34F;B34F;1103 1164 11BE;B34F;1103 1164 11BE; # (ë; ë; 덏; ë; 덏; ) HANGUL SYLLABLE DYAEC
+B350;B350;1103 1164 11BF;B350;1103 1164 11BF; # (ë; ë; 덐; ë; 덐; ) HANGUL SYLLABLE DYAEK
+B351;B351;1103 1164 11C0;B351;1103 1164 11C0; # (ë‘; ë‘; 덑; ë‘; 덑; ) HANGUL SYLLABLE DYAET
+B352;B352;1103 1164 11C1;B352;1103 1164 11C1; # (ë’; ë’; 댸á‡; ë’; 댸á‡; ) HANGUL SYLLABLE DYAEP
+B353;B353;1103 1164 11C2;B353;1103 1164 11C2; # (ë“; ë“; 덓; ë“; 덓; ) HANGUL SYLLABLE DYAEH
+B354;B354;1103 1165;B354;1103 1165; # (ë”; ë”; 더; ë”; 더; ) HANGUL SYLLABLE DEO
+B355;B355;1103 1165 11A8;B355;1103 1165 11A8; # (ë•; ë•; 덕; ë•; 덕; ) HANGUL SYLLABLE DEOG
+B356;B356;1103 1165 11A9;B356;1103 1165 11A9; # (ë–; ë–; 덖; ë–; 덖; ) HANGUL SYLLABLE DEOGG
+B357;B357;1103 1165 11AA;B357;1103 1165 11AA; # (ë—; ë—; 덗; ë—; 덗; ) HANGUL SYLLABLE DEOGS
+B358;B358;1103 1165 11AB;B358;1103 1165 11AB; # (ë˜; ë˜; 던; ë˜; 던; ) HANGUL SYLLABLE DEON
+B359;B359;1103 1165 11AC;B359;1103 1165 11AC; # (ë™; ë™; 덙; ë™; 덙; ) HANGUL SYLLABLE DEONJ
+B35A;B35A;1103 1165 11AD;B35A;1103 1165 11AD; # (ëš; ëš; 덚; ëš; 덚; ) HANGUL SYLLABLE DEONH
+B35B;B35B;1103 1165 11AE;B35B;1103 1165 11AE; # (ë›; ë›; 덛; ë›; 덛; ) HANGUL SYLLABLE DEOD
+B35C;B35C;1103 1165 11AF;B35C;1103 1165 11AF; # (ëœ; ëœ; 덜; ëœ; 덜; ) HANGUL SYLLABLE DEOL
+B35D;B35D;1103 1165 11B0;B35D;1103 1165 11B0; # (ë; ë; 덝; ë; 덝; ) HANGUL SYLLABLE DEOLG
+B35E;B35E;1103 1165 11B1;B35E;1103 1165 11B1; # (ëž; ëž; 덞; ëž; 덞; ) HANGUL SYLLABLE DEOLM
+B35F;B35F;1103 1165 11B2;B35F;1103 1165 11B2; # (ëŸ; ëŸ; 덟; ëŸ; 덟; ) HANGUL SYLLABLE DEOLB
+B360;B360;1103 1165 11B3;B360;1103 1165 11B3; # (ë ; ë ; 덠; ë ; 덠; ) HANGUL SYLLABLE DEOLS
+B361;B361;1103 1165 11B4;B361;1103 1165 11B4; # (ë¡; ë¡; 덡; ë¡; 덡; ) HANGUL SYLLABLE DEOLT
+B362;B362;1103 1165 11B5;B362;1103 1165 11B5; # (ë¢; ë¢; 덢; ë¢; 덢; ) HANGUL SYLLABLE DEOLP
+B363;B363;1103 1165 11B6;B363;1103 1165 11B6; # (ë£; ë£; 덣; ë£; 덣; ) HANGUL SYLLABLE DEOLH
+B364;B364;1103 1165 11B7;B364;1103 1165 11B7; # (ë¤; ë¤; 덤; ë¤; 덤; ) HANGUL SYLLABLE DEOM
+B365;B365;1103 1165 11B8;B365;1103 1165 11B8; # (ë¥; ë¥; 덥; ë¥; 덥; ) HANGUL SYLLABLE DEOB
+B366;B366;1103 1165 11B9;B366;1103 1165 11B9; # (ë¦; ë¦; 덦; ë¦; 덦; ) HANGUL SYLLABLE DEOBS
+B367;B367;1103 1165 11BA;B367;1103 1165 11BA; # (ë§; ë§; 덧; ë§; 덧; ) HANGUL SYLLABLE DEOS
+B368;B368;1103 1165 11BB;B368;1103 1165 11BB; # (ë¨; ë¨; 덨; ë¨; 덨; ) HANGUL SYLLABLE DEOSS
+B369;B369;1103 1165 11BC;B369;1103 1165 11BC; # (ë©; ë©; 덩; ë©; 덩; ) HANGUL SYLLABLE DEONG
+B36A;B36A;1103 1165 11BD;B36A;1103 1165 11BD; # (ëª; ëª; 덪; ëª; 덪; ) HANGUL SYLLABLE DEOJ
+B36B;B36B;1103 1165 11BE;B36B;1103 1165 11BE; # (ë«; ë«; 덫; ë«; 덫; ) HANGUL SYLLABLE DEOC
+B36C;B36C;1103 1165 11BF;B36C;1103 1165 11BF; # (ë¬; ë¬; 덬; ë¬; 덬; ) HANGUL SYLLABLE DEOK
+B36D;B36D;1103 1165 11C0;B36D;1103 1165 11C0; # (ë­; ë­; 덭; ë­; 덭; ) HANGUL SYLLABLE DEOT
+B36E;B36E;1103 1165 11C1;B36E;1103 1165 11C1; # (ë®; ë®; 더á‡; ë®; 더á‡; ) HANGUL SYLLABLE DEOP
+B36F;B36F;1103 1165 11C2;B36F;1103 1165 11C2; # (ë¯; ë¯; 덯; ë¯; 덯; ) HANGUL SYLLABLE DEOH
+B370;B370;1103 1166;B370;1103 1166; # (ë°; ë°; 데; ë°; 데; ) HANGUL SYLLABLE DE
+B371;B371;1103 1166 11A8;B371;1103 1166 11A8; # (ë±; ë±; 덱; ë±; 덱; ) HANGUL SYLLABLE DEG
+B372;B372;1103 1166 11A9;B372;1103 1166 11A9; # (ë²; ë²; 덲; ë²; 덲; ) HANGUL SYLLABLE DEGG
+B373;B373;1103 1166 11AA;B373;1103 1166 11AA; # (ë³; ë³; 덳; ë³; 덳; ) HANGUL SYLLABLE DEGS
+B374;B374;1103 1166 11AB;B374;1103 1166 11AB; # (ë´; ë´; 덴; ë´; 덴; ) HANGUL SYLLABLE DEN
+B375;B375;1103 1166 11AC;B375;1103 1166 11AC; # (ëµ; ëµ; 덵; ëµ; 덵; ) HANGUL SYLLABLE DENJ
+B376;B376;1103 1166 11AD;B376;1103 1166 11AD; # (ë¶; ë¶; 덶; ë¶; 덶; ) HANGUL SYLLABLE DENH
+B377;B377;1103 1166 11AE;B377;1103 1166 11AE; # (ë·; ë·; 덷; ë·; 덷; ) HANGUL SYLLABLE DED
+B378;B378;1103 1166 11AF;B378;1103 1166 11AF; # (ë¸; ë¸; 델; ë¸; 델; ) HANGUL SYLLABLE DEL
+B379;B379;1103 1166 11B0;B379;1103 1166 11B0; # (ë¹; ë¹; 덹; ë¹; 덹; ) HANGUL SYLLABLE DELG
+B37A;B37A;1103 1166 11B1;B37A;1103 1166 11B1; # (ëº; ëº; 덺; ëº; 덺; ) HANGUL SYLLABLE DELM
+B37B;B37B;1103 1166 11B2;B37B;1103 1166 11B2; # (ë»; ë»; 덻; ë»; 덻; ) HANGUL SYLLABLE DELB
+B37C;B37C;1103 1166 11B3;B37C;1103 1166 11B3; # (ë¼; ë¼; 덼; ë¼; 덼; ) HANGUL SYLLABLE DELS
+B37D;B37D;1103 1166 11B4;B37D;1103 1166 11B4; # (ë½; ë½; 덽; ë½; 덽; ) HANGUL SYLLABLE DELT
+B37E;B37E;1103 1166 11B5;B37E;1103 1166 11B5; # (ë¾; ë¾; 덾; ë¾; 덾; ) HANGUL SYLLABLE DELP
+B37F;B37F;1103 1166 11B6;B37F;1103 1166 11B6; # (ë¿; ë¿; 덿; ë¿; 덿; ) HANGUL SYLLABLE DELH
+B380;B380;1103 1166 11B7;B380;1103 1166 11B7; # (뎀; 뎀; 뎀; 뎀; 뎀; ) HANGUL SYLLABLE DEM
+B381;B381;1103 1166 11B8;B381;1103 1166 11B8; # (ëŽ; ëŽ; 뎁; ëŽ; 뎁; ) HANGUL SYLLABLE DEB
+B382;B382;1103 1166 11B9;B382;1103 1166 11B9; # (뎂; 뎂; 뎂; 뎂; 뎂; ) HANGUL SYLLABLE DEBS
+B383;B383;1103 1166 11BA;B383;1103 1166 11BA; # (뎃; 뎃; 뎃; 뎃; 뎃; ) HANGUL SYLLABLE DES
+B384;B384;1103 1166 11BB;B384;1103 1166 11BB; # (뎄; 뎄; 뎄; 뎄; 뎄; ) HANGUL SYLLABLE DESS
+B385;B385;1103 1166 11BC;B385;1103 1166 11BC; # (뎅; 뎅; 뎅; 뎅; 뎅; ) HANGUL SYLLABLE DENG
+B386;B386;1103 1166 11BD;B386;1103 1166 11BD; # (뎆; 뎆; 뎆; 뎆; 뎆; ) HANGUL SYLLABLE DEJ
+B387;B387;1103 1166 11BE;B387;1103 1166 11BE; # (뎇; 뎇; 뎇; 뎇; 뎇; ) HANGUL SYLLABLE DEC
+B388;B388;1103 1166 11BF;B388;1103 1166 11BF; # (뎈; 뎈; 뎈; 뎈; 뎈; ) HANGUL SYLLABLE DEK
+B389;B389;1103 1166 11C0;B389;1103 1166 11C0; # (뎉; 뎉; 뎉; 뎉; 뎉; ) HANGUL SYLLABLE DET
+B38A;B38A;1103 1166 11C1;B38A;1103 1166 11C1; # (뎊; 뎊; 데á‡; 뎊; 데á‡; ) HANGUL SYLLABLE DEP
+B38B;B38B;1103 1166 11C2;B38B;1103 1166 11C2; # (뎋; 뎋; 뎋; 뎋; 뎋; ) HANGUL SYLLABLE DEH
+B38C;B38C;1103 1167;B38C;1103 1167; # (뎌; 뎌; 뎌; 뎌; 뎌; ) HANGUL SYLLABLE DYEO
+B38D;B38D;1103 1167 11A8;B38D;1103 1167 11A8; # (ëŽ; ëŽ; 뎍; ëŽ; 뎍; ) HANGUL SYLLABLE DYEOG
+B38E;B38E;1103 1167 11A9;B38E;1103 1167 11A9; # (뎎; 뎎; 뎎; 뎎; 뎎; ) HANGUL SYLLABLE DYEOGG
+B38F;B38F;1103 1167 11AA;B38F;1103 1167 11AA; # (ëŽ; ëŽ; 뎏; ëŽ; 뎏; ) HANGUL SYLLABLE DYEOGS
+B390;B390;1103 1167 11AB;B390;1103 1167 11AB; # (ëŽ; ëŽ; 뎐; ëŽ; 뎐; ) HANGUL SYLLABLE DYEON
+B391;B391;1103 1167 11AC;B391;1103 1167 11AC; # (뎑; 뎑; 뎑; 뎑; 뎑; ) HANGUL SYLLABLE DYEONJ
+B392;B392;1103 1167 11AD;B392;1103 1167 11AD; # (뎒; 뎒; 뎒; 뎒; 뎒; ) HANGUL SYLLABLE DYEONH
+B393;B393;1103 1167 11AE;B393;1103 1167 11AE; # (뎓; 뎓; 뎓; 뎓; 뎓; ) HANGUL SYLLABLE DYEOD
+B394;B394;1103 1167 11AF;B394;1103 1167 11AF; # (뎔; 뎔; 뎔; 뎔; 뎔; ) HANGUL SYLLABLE DYEOL
+B395;B395;1103 1167 11B0;B395;1103 1167 11B0; # (뎕; 뎕; 뎕; 뎕; 뎕; ) HANGUL SYLLABLE DYEOLG
+B396;B396;1103 1167 11B1;B396;1103 1167 11B1; # (뎖; 뎖; 뎖; 뎖; 뎖; ) HANGUL SYLLABLE DYEOLM
+B397;B397;1103 1167 11B2;B397;1103 1167 11B2; # (뎗; 뎗; 뎗; 뎗; 뎗; ) HANGUL SYLLABLE DYEOLB
+B398;B398;1103 1167 11B3;B398;1103 1167 11B3; # (뎘; 뎘; 뎘; 뎘; 뎘; ) HANGUL SYLLABLE DYEOLS
+B399;B399;1103 1167 11B4;B399;1103 1167 11B4; # (뎙; 뎙; 뎙; 뎙; 뎙; ) HANGUL SYLLABLE DYEOLT
+B39A;B39A;1103 1167 11B5;B39A;1103 1167 11B5; # (뎚; 뎚; 뎚; 뎚; 뎚; ) HANGUL SYLLABLE DYEOLP
+B39B;B39B;1103 1167 11B6;B39B;1103 1167 11B6; # (뎛; 뎛; 뎛; 뎛; 뎛; ) HANGUL SYLLABLE DYEOLH
+B39C;B39C;1103 1167 11B7;B39C;1103 1167 11B7; # (뎜; 뎜; 뎜; 뎜; 뎜; ) HANGUL SYLLABLE DYEOM
+B39D;B39D;1103 1167 11B8;B39D;1103 1167 11B8; # (ëŽ; ëŽ; 뎝; ëŽ; 뎝; ) HANGUL SYLLABLE DYEOB
+B39E;B39E;1103 1167 11B9;B39E;1103 1167 11B9; # (뎞; 뎞; 뎞; 뎞; 뎞; ) HANGUL SYLLABLE DYEOBS
+B39F;B39F;1103 1167 11BA;B39F;1103 1167 11BA; # (뎟; 뎟; 뎟; 뎟; 뎟; ) HANGUL SYLLABLE DYEOS
+B3A0;B3A0;1103 1167 11BB;B3A0;1103 1167 11BB; # (뎠; 뎠; 뎠; 뎠; 뎠; ) HANGUL SYLLABLE DYEOSS
+B3A1;B3A1;1103 1167 11BC;B3A1;1103 1167 11BC; # (뎡; 뎡; 뎡; 뎡; 뎡; ) HANGUL SYLLABLE DYEONG
+B3A2;B3A2;1103 1167 11BD;B3A2;1103 1167 11BD; # (뎢; 뎢; 뎢; 뎢; 뎢; ) HANGUL SYLLABLE DYEOJ
+B3A3;B3A3;1103 1167 11BE;B3A3;1103 1167 11BE; # (뎣; 뎣; 뎣; 뎣; 뎣; ) HANGUL SYLLABLE DYEOC
+B3A4;B3A4;1103 1167 11BF;B3A4;1103 1167 11BF; # (뎤; 뎤; 뎤; 뎤; 뎤; ) HANGUL SYLLABLE DYEOK
+B3A5;B3A5;1103 1167 11C0;B3A5;1103 1167 11C0; # (뎥; 뎥; 뎥; 뎥; 뎥; ) HANGUL SYLLABLE DYEOT
+B3A6;B3A6;1103 1167 11C1;B3A6;1103 1167 11C1; # (뎦; 뎦; 뎌á‡; 뎦; 뎌á‡; ) HANGUL SYLLABLE DYEOP
+B3A7;B3A7;1103 1167 11C2;B3A7;1103 1167 11C2; # (뎧; 뎧; 뎧; 뎧; 뎧; ) HANGUL SYLLABLE DYEOH
+B3A8;B3A8;1103 1168;B3A8;1103 1168; # (뎨; 뎨; 뎨; 뎨; 뎨; ) HANGUL SYLLABLE DYE
+B3A9;B3A9;1103 1168 11A8;B3A9;1103 1168 11A8; # (뎩; 뎩; 뎩; 뎩; 뎩; ) HANGUL SYLLABLE DYEG
+B3AA;B3AA;1103 1168 11A9;B3AA;1103 1168 11A9; # (뎪; 뎪; 뎪; 뎪; 뎪; ) HANGUL SYLLABLE DYEGG
+B3AB;B3AB;1103 1168 11AA;B3AB;1103 1168 11AA; # (뎫; 뎫; 뎫; 뎫; 뎫; ) HANGUL SYLLABLE DYEGS
+B3AC;B3AC;1103 1168 11AB;B3AC;1103 1168 11AB; # (뎬; 뎬; 뎬; 뎬; 뎬; ) HANGUL SYLLABLE DYEN
+B3AD;B3AD;1103 1168 11AC;B3AD;1103 1168 11AC; # (뎭; 뎭; 뎭; 뎭; 뎭; ) HANGUL SYLLABLE DYENJ
+B3AE;B3AE;1103 1168 11AD;B3AE;1103 1168 11AD; # (뎮; 뎮; 뎮; 뎮; 뎮; ) HANGUL SYLLABLE DYENH
+B3AF;B3AF;1103 1168 11AE;B3AF;1103 1168 11AE; # (뎯; 뎯; 뎯; 뎯; 뎯; ) HANGUL SYLLABLE DYED
+B3B0;B3B0;1103 1168 11AF;B3B0;1103 1168 11AF; # (뎰; 뎰; 뎰; 뎰; 뎰; ) HANGUL SYLLABLE DYEL
+B3B1;B3B1;1103 1168 11B0;B3B1;1103 1168 11B0; # (뎱; 뎱; 뎱; 뎱; 뎱; ) HANGUL SYLLABLE DYELG
+B3B2;B3B2;1103 1168 11B1;B3B2;1103 1168 11B1; # (뎲; 뎲; 뎲; 뎲; 뎲; ) HANGUL SYLLABLE DYELM
+B3B3;B3B3;1103 1168 11B2;B3B3;1103 1168 11B2; # (뎳; 뎳; 뎳; 뎳; 뎳; ) HANGUL SYLLABLE DYELB
+B3B4;B3B4;1103 1168 11B3;B3B4;1103 1168 11B3; # (뎴; 뎴; 뎴; 뎴; 뎴; ) HANGUL SYLLABLE DYELS
+B3B5;B3B5;1103 1168 11B4;B3B5;1103 1168 11B4; # (뎵; 뎵; 뎵; 뎵; 뎵; ) HANGUL SYLLABLE DYELT
+B3B6;B3B6;1103 1168 11B5;B3B6;1103 1168 11B5; # (뎶; 뎶; 뎶; 뎶; 뎶; ) HANGUL SYLLABLE DYELP
+B3B7;B3B7;1103 1168 11B6;B3B7;1103 1168 11B6; # (뎷; 뎷; 뎷; 뎷; 뎷; ) HANGUL SYLLABLE DYELH
+B3B8;B3B8;1103 1168 11B7;B3B8;1103 1168 11B7; # (뎸; 뎸; 뎸; 뎸; 뎸; ) HANGUL SYLLABLE DYEM
+B3B9;B3B9;1103 1168 11B8;B3B9;1103 1168 11B8; # (뎹; 뎹; 뎹; 뎹; 뎹; ) HANGUL SYLLABLE DYEB
+B3BA;B3BA;1103 1168 11B9;B3BA;1103 1168 11B9; # (뎺; 뎺; 뎺; 뎺; 뎺; ) HANGUL SYLLABLE DYEBS
+B3BB;B3BB;1103 1168 11BA;B3BB;1103 1168 11BA; # (뎻; 뎻; 뎻; 뎻; 뎻; ) HANGUL SYLLABLE DYES
+B3BC;B3BC;1103 1168 11BB;B3BC;1103 1168 11BB; # (뎼; 뎼; 뎼; 뎼; 뎼; ) HANGUL SYLLABLE DYESS
+B3BD;B3BD;1103 1168 11BC;B3BD;1103 1168 11BC; # (뎽; 뎽; 뎽; 뎽; 뎽; ) HANGUL SYLLABLE DYENG
+B3BE;B3BE;1103 1168 11BD;B3BE;1103 1168 11BD; # (뎾; 뎾; 뎾; 뎾; 뎾; ) HANGUL SYLLABLE DYEJ
+B3BF;B3BF;1103 1168 11BE;B3BF;1103 1168 11BE; # (뎿; 뎿; 뎿; 뎿; 뎿; ) HANGUL SYLLABLE DYEC
+B3C0;B3C0;1103 1168 11BF;B3C0;1103 1168 11BF; # (ë€; ë€; 돀; ë€; 돀; ) HANGUL SYLLABLE DYEK
+B3C1;B3C1;1103 1168 11C0;B3C1;1103 1168 11C0; # (ë; ë; 돁; ë; 돁; ) HANGUL SYLLABLE DYET
+B3C2;B3C2;1103 1168 11C1;B3C2;1103 1168 11C1; # (ë‚; ë‚; 뎨á‡; ë‚; 뎨á‡; ) HANGUL SYLLABLE DYEP
+B3C3;B3C3;1103 1168 11C2;B3C3;1103 1168 11C2; # (ëƒ; ëƒ; 돃; ëƒ; 돃; ) HANGUL SYLLABLE DYEH
+B3C4;B3C4;1103 1169;B3C4;1103 1169; # (ë„; ë„; 도; ë„; 도; ) HANGUL SYLLABLE DO
+B3C5;B3C5;1103 1169 11A8;B3C5;1103 1169 11A8; # (ë…; ë…; 독; ë…; 독; ) HANGUL SYLLABLE DOG
+B3C6;B3C6;1103 1169 11A9;B3C6;1103 1169 11A9; # (ë†; ë†; 돆; ë†; 돆; ) HANGUL SYLLABLE DOGG
+B3C7;B3C7;1103 1169 11AA;B3C7;1103 1169 11AA; # (ë‡; ë‡; 돇; ë‡; 돇; ) HANGUL SYLLABLE DOGS
+B3C8;B3C8;1103 1169 11AB;B3C8;1103 1169 11AB; # (ëˆ; ëˆ; 돈; ëˆ; 돈; ) HANGUL SYLLABLE DON
+B3C9;B3C9;1103 1169 11AC;B3C9;1103 1169 11AC; # (ë‰; ë‰; 돉; ë‰; 돉; ) HANGUL SYLLABLE DONJ
+B3CA;B3CA;1103 1169 11AD;B3CA;1103 1169 11AD; # (ëŠ; ëŠ; 돊; ëŠ; 돊; ) HANGUL SYLLABLE DONH
+B3CB;B3CB;1103 1169 11AE;B3CB;1103 1169 11AE; # (ë‹; ë‹; 돋; ë‹; 돋; ) HANGUL SYLLABLE DOD
+B3CC;B3CC;1103 1169 11AF;B3CC;1103 1169 11AF; # (ëŒ; ëŒ; 돌; ëŒ; 돌; ) HANGUL SYLLABLE DOL
+B3CD;B3CD;1103 1169 11B0;B3CD;1103 1169 11B0; # (ë; ë; 돍; ë; 돍; ) HANGUL SYLLABLE DOLG
+B3CE;B3CE;1103 1169 11B1;B3CE;1103 1169 11B1; # (ëŽ; ëŽ; 돎; ëŽ; 돎; ) HANGUL SYLLABLE DOLM
+B3CF;B3CF;1103 1169 11B2;B3CF;1103 1169 11B2; # (ë; ë; 돏; ë; 돏; ) HANGUL SYLLABLE DOLB
+B3D0;B3D0;1103 1169 11B3;B3D0;1103 1169 11B3; # (ë; ë; 돐; ë; 돐; ) HANGUL SYLLABLE DOLS
+B3D1;B3D1;1103 1169 11B4;B3D1;1103 1169 11B4; # (ë‘; ë‘; 돑; ë‘; 돑; ) HANGUL SYLLABLE DOLT
+B3D2;B3D2;1103 1169 11B5;B3D2;1103 1169 11B5; # (ë’; ë’; 돒; ë’; 돒; ) HANGUL SYLLABLE DOLP
+B3D3;B3D3;1103 1169 11B6;B3D3;1103 1169 11B6; # (ë“; ë“; 돓; ë“; 돓; ) HANGUL SYLLABLE DOLH
+B3D4;B3D4;1103 1169 11B7;B3D4;1103 1169 11B7; # (ë”; ë”; 돔; ë”; 돔; ) HANGUL SYLLABLE DOM
+B3D5;B3D5;1103 1169 11B8;B3D5;1103 1169 11B8; # (ë•; ë•; 돕; ë•; 돕; ) HANGUL SYLLABLE DOB
+B3D6;B3D6;1103 1169 11B9;B3D6;1103 1169 11B9; # (ë–; ë–; 돖; ë–; 돖; ) HANGUL SYLLABLE DOBS
+B3D7;B3D7;1103 1169 11BA;B3D7;1103 1169 11BA; # (ë—; ë—; 돗; ë—; 돗; ) HANGUL SYLLABLE DOS
+B3D8;B3D8;1103 1169 11BB;B3D8;1103 1169 11BB; # (ë˜; ë˜; 돘; ë˜; 돘; ) HANGUL SYLLABLE DOSS
+B3D9;B3D9;1103 1169 11BC;B3D9;1103 1169 11BC; # (ë™; ë™; 동; ë™; 동; ) HANGUL SYLLABLE DONG
+B3DA;B3DA;1103 1169 11BD;B3DA;1103 1169 11BD; # (ëš; ëš; 돚; ëš; 돚; ) HANGUL SYLLABLE DOJ
+B3DB;B3DB;1103 1169 11BE;B3DB;1103 1169 11BE; # (ë›; ë›; 돛; ë›; 돛; ) HANGUL SYLLABLE DOC
+B3DC;B3DC;1103 1169 11BF;B3DC;1103 1169 11BF; # (ëœ; ëœ; 돜; ëœ; 돜; ) HANGUL SYLLABLE DOK
+B3DD;B3DD;1103 1169 11C0;B3DD;1103 1169 11C0; # (ë; ë; 돝; ë; 돝; ) HANGUL SYLLABLE DOT
+B3DE;B3DE;1103 1169 11C1;B3DE;1103 1169 11C1; # (ëž; ëž; 도á‡; ëž; 도á‡; ) HANGUL SYLLABLE DOP
+B3DF;B3DF;1103 1169 11C2;B3DF;1103 1169 11C2; # (ëŸ; ëŸ; 돟; ëŸ; 돟; ) HANGUL SYLLABLE DOH
+B3E0;B3E0;1103 116A;B3E0;1103 116A; # (ë ; ë ; 돠; ë ; 돠; ) HANGUL SYLLABLE DWA
+B3E1;B3E1;1103 116A 11A8;B3E1;1103 116A 11A8; # (ë¡; ë¡; 돡; ë¡; 돡; ) HANGUL SYLLABLE DWAG
+B3E2;B3E2;1103 116A 11A9;B3E2;1103 116A 11A9; # (ë¢; ë¢; 돢; ë¢; 돢; ) HANGUL SYLLABLE DWAGG
+B3E3;B3E3;1103 116A 11AA;B3E3;1103 116A 11AA; # (ë£; ë£; 돣; ë£; 돣; ) HANGUL SYLLABLE DWAGS
+B3E4;B3E4;1103 116A 11AB;B3E4;1103 116A 11AB; # (ë¤; ë¤; 돤; ë¤; 돤; ) HANGUL SYLLABLE DWAN
+B3E5;B3E5;1103 116A 11AC;B3E5;1103 116A 11AC; # (ë¥; ë¥; 돥; ë¥; 돥; ) HANGUL SYLLABLE DWANJ
+B3E6;B3E6;1103 116A 11AD;B3E6;1103 116A 11AD; # (ë¦; ë¦; 돦; ë¦; 돦; ) HANGUL SYLLABLE DWANH
+B3E7;B3E7;1103 116A 11AE;B3E7;1103 116A 11AE; # (ë§; ë§; 돧; ë§; 돧; ) HANGUL SYLLABLE DWAD
+B3E8;B3E8;1103 116A 11AF;B3E8;1103 116A 11AF; # (ë¨; ë¨; 돨; ë¨; 돨; ) HANGUL SYLLABLE DWAL
+B3E9;B3E9;1103 116A 11B0;B3E9;1103 116A 11B0; # (ë©; ë©; 돩; ë©; 돩; ) HANGUL SYLLABLE DWALG
+B3EA;B3EA;1103 116A 11B1;B3EA;1103 116A 11B1; # (ëª; ëª; 돪; ëª; 돪; ) HANGUL SYLLABLE DWALM
+B3EB;B3EB;1103 116A 11B2;B3EB;1103 116A 11B2; # (ë«; ë«; 돫; ë«; 돫; ) HANGUL SYLLABLE DWALB
+B3EC;B3EC;1103 116A 11B3;B3EC;1103 116A 11B3; # (ë¬; ë¬; 돬; ë¬; 돬; ) HANGUL SYLLABLE DWALS
+B3ED;B3ED;1103 116A 11B4;B3ED;1103 116A 11B4; # (ë­; ë­; 돭; ë­; 돭; ) HANGUL SYLLABLE DWALT
+B3EE;B3EE;1103 116A 11B5;B3EE;1103 116A 11B5; # (ë®; ë®; 돮; ë®; 돮; ) HANGUL SYLLABLE DWALP
+B3EF;B3EF;1103 116A 11B6;B3EF;1103 116A 11B6; # (ë¯; ë¯; 돯; ë¯; 돯; ) HANGUL SYLLABLE DWALH
+B3F0;B3F0;1103 116A 11B7;B3F0;1103 116A 11B7; # (ë°; ë°; 돰; ë°; 돰; ) HANGUL SYLLABLE DWAM
+B3F1;B3F1;1103 116A 11B8;B3F1;1103 116A 11B8; # (ë±; ë±; 돱; ë±; 돱; ) HANGUL SYLLABLE DWAB
+B3F2;B3F2;1103 116A 11B9;B3F2;1103 116A 11B9; # (ë²; ë²; 돲; ë²; 돲; ) HANGUL SYLLABLE DWABS
+B3F3;B3F3;1103 116A 11BA;B3F3;1103 116A 11BA; # (ë³; ë³; 돳; ë³; 돳; ) HANGUL SYLLABLE DWAS
+B3F4;B3F4;1103 116A 11BB;B3F4;1103 116A 11BB; # (ë´; ë´; 돴; ë´; 돴; ) HANGUL SYLLABLE DWASS
+B3F5;B3F5;1103 116A 11BC;B3F5;1103 116A 11BC; # (ëµ; ëµ; 돵; ëµ; 돵; ) HANGUL SYLLABLE DWANG
+B3F6;B3F6;1103 116A 11BD;B3F6;1103 116A 11BD; # (ë¶; ë¶; 돶; ë¶; 돶; ) HANGUL SYLLABLE DWAJ
+B3F7;B3F7;1103 116A 11BE;B3F7;1103 116A 11BE; # (ë·; ë·; 돷; ë·; 돷; ) HANGUL SYLLABLE DWAC
+B3F8;B3F8;1103 116A 11BF;B3F8;1103 116A 11BF; # (ë¸; ë¸; 돸; ë¸; 돸; ) HANGUL SYLLABLE DWAK
+B3F9;B3F9;1103 116A 11C0;B3F9;1103 116A 11C0; # (ë¹; ë¹; 돹; ë¹; 돹; ) HANGUL SYLLABLE DWAT
+B3FA;B3FA;1103 116A 11C1;B3FA;1103 116A 11C1; # (ëº; ëº; 돠á‡; ëº; 돠á‡; ) HANGUL SYLLABLE DWAP
+B3FB;B3FB;1103 116A 11C2;B3FB;1103 116A 11C2; # (ë»; ë»; 돻; ë»; 돻; ) HANGUL SYLLABLE DWAH
+B3FC;B3FC;1103 116B;B3FC;1103 116B; # (ë¼; ë¼; 돼; ë¼; 돼; ) HANGUL SYLLABLE DWAE
+B3FD;B3FD;1103 116B 11A8;B3FD;1103 116B 11A8; # (ë½; ë½; 돽; ë½; 돽; ) HANGUL SYLLABLE DWAEG
+B3FE;B3FE;1103 116B 11A9;B3FE;1103 116B 11A9; # (ë¾; ë¾; 돾; ë¾; 돾; ) HANGUL SYLLABLE DWAEGG
+B3FF;B3FF;1103 116B 11AA;B3FF;1103 116B 11AA; # (ë¿; ë¿; 돿; ë¿; 돿; ) HANGUL SYLLABLE DWAEGS
+B400;B400;1103 116B 11AB;B400;1103 116B 11AB; # (ë€; ë€; 됀; ë€; 됀; ) HANGUL SYLLABLE DWAEN
+B401;B401;1103 116B 11AC;B401;1103 116B 11AC; # (ë; ë; 됁; ë; 됁; ) HANGUL SYLLABLE DWAENJ
+B402;B402;1103 116B 11AD;B402;1103 116B 11AD; # (ë‚; ë‚; 됂; ë‚; 됂; ) HANGUL SYLLABLE DWAENH
+B403;B403;1103 116B 11AE;B403;1103 116B 11AE; # (ëƒ; ëƒ; 됃; ëƒ; 됃; ) HANGUL SYLLABLE DWAED
+B404;B404;1103 116B 11AF;B404;1103 116B 11AF; # (ë„; ë„; 됄; ë„; 됄; ) HANGUL SYLLABLE DWAEL
+B405;B405;1103 116B 11B0;B405;1103 116B 11B0; # (ë…; ë…; 됅; ë…; 됅; ) HANGUL SYLLABLE DWAELG
+B406;B406;1103 116B 11B1;B406;1103 116B 11B1; # (ë†; ë†; 됆; ë†; 됆; ) HANGUL SYLLABLE DWAELM
+B407;B407;1103 116B 11B2;B407;1103 116B 11B2; # (ë‡; ë‡; 됇; ë‡; 됇; ) HANGUL SYLLABLE DWAELB
+B408;B408;1103 116B 11B3;B408;1103 116B 11B3; # (ëˆ; ëˆ; 됈; ëˆ; 됈; ) HANGUL SYLLABLE DWAELS
+B409;B409;1103 116B 11B4;B409;1103 116B 11B4; # (ë‰; ë‰; 됉; ë‰; 됉; ) HANGUL SYLLABLE DWAELT
+B40A;B40A;1103 116B 11B5;B40A;1103 116B 11B5; # (ëŠ; ëŠ; 됊; ëŠ; 됊; ) HANGUL SYLLABLE DWAELP
+B40B;B40B;1103 116B 11B6;B40B;1103 116B 11B6; # (ë‹; ë‹; 됋; ë‹; 됋; ) HANGUL SYLLABLE DWAELH
+B40C;B40C;1103 116B 11B7;B40C;1103 116B 11B7; # (ëŒ; ëŒ; 됌; ëŒ; 됌; ) HANGUL SYLLABLE DWAEM
+B40D;B40D;1103 116B 11B8;B40D;1103 116B 11B8; # (ë; ë; 됍; ë; 됍; ) HANGUL SYLLABLE DWAEB
+B40E;B40E;1103 116B 11B9;B40E;1103 116B 11B9; # (ëŽ; ëŽ; 됎; ëŽ; 됎; ) HANGUL SYLLABLE DWAEBS
+B40F;B40F;1103 116B 11BA;B40F;1103 116B 11BA; # (ë; ë; 됏; ë; 됏; ) HANGUL SYLLABLE DWAES
+B410;B410;1103 116B 11BB;B410;1103 116B 11BB; # (ë; ë; 됐; ë; 됐; ) HANGUL SYLLABLE DWAESS
+B411;B411;1103 116B 11BC;B411;1103 116B 11BC; # (ë‘; ë‘; 됑; ë‘; 됑; ) HANGUL SYLLABLE DWAENG
+B412;B412;1103 116B 11BD;B412;1103 116B 11BD; # (ë’; ë’; 됒; ë’; 됒; ) HANGUL SYLLABLE DWAEJ
+B413;B413;1103 116B 11BE;B413;1103 116B 11BE; # (ë“; ë“; 됓; ë“; 됓; ) HANGUL SYLLABLE DWAEC
+B414;B414;1103 116B 11BF;B414;1103 116B 11BF; # (ë”; ë”; 됔; ë”; 됔; ) HANGUL SYLLABLE DWAEK
+B415;B415;1103 116B 11C0;B415;1103 116B 11C0; # (ë•; ë•; 됕; ë•; 됕; ) HANGUL SYLLABLE DWAET
+B416;B416;1103 116B 11C1;B416;1103 116B 11C1; # (ë–; ë–; 돼á‡; ë–; 돼á‡; ) HANGUL SYLLABLE DWAEP
+B417;B417;1103 116B 11C2;B417;1103 116B 11C2; # (ë—; ë—; 됗; ë—; 됗; ) HANGUL SYLLABLE DWAEH
+B418;B418;1103 116C;B418;1103 116C; # (ë˜; ë˜; 되; ë˜; 되; ) HANGUL SYLLABLE DOE
+B419;B419;1103 116C 11A8;B419;1103 116C 11A8; # (ë™; ë™; 됙; ë™; 됙; ) HANGUL SYLLABLE DOEG
+B41A;B41A;1103 116C 11A9;B41A;1103 116C 11A9; # (ëš; ëš; 됚; ëš; 됚; ) HANGUL SYLLABLE DOEGG
+B41B;B41B;1103 116C 11AA;B41B;1103 116C 11AA; # (ë›; ë›; 됛; ë›; 됛; ) HANGUL SYLLABLE DOEGS
+B41C;B41C;1103 116C 11AB;B41C;1103 116C 11AB; # (ëœ; ëœ; 된; ëœ; 된; ) HANGUL SYLLABLE DOEN
+B41D;B41D;1103 116C 11AC;B41D;1103 116C 11AC; # (ë; ë; 됝; ë; 됝; ) HANGUL SYLLABLE DOENJ
+B41E;B41E;1103 116C 11AD;B41E;1103 116C 11AD; # (ëž; ëž; 됞; ëž; 됞; ) HANGUL SYLLABLE DOENH
+B41F;B41F;1103 116C 11AE;B41F;1103 116C 11AE; # (ëŸ; ëŸ; 됟; ëŸ; 됟; ) HANGUL SYLLABLE DOED
+B420;B420;1103 116C 11AF;B420;1103 116C 11AF; # (ë ; ë ; 될; ë ; 될; ) HANGUL SYLLABLE DOEL
+B421;B421;1103 116C 11B0;B421;1103 116C 11B0; # (ë¡; ë¡; 됡; ë¡; 됡; ) HANGUL SYLLABLE DOELG
+B422;B422;1103 116C 11B1;B422;1103 116C 11B1; # (ë¢; ë¢; 됢; ë¢; 됢; ) HANGUL SYLLABLE DOELM
+B423;B423;1103 116C 11B2;B423;1103 116C 11B2; # (ë£; ë£; 됣; ë£; 됣; ) HANGUL SYLLABLE DOELB
+B424;B424;1103 116C 11B3;B424;1103 116C 11B3; # (ë¤; ë¤; 됤; ë¤; 됤; ) HANGUL SYLLABLE DOELS
+B425;B425;1103 116C 11B4;B425;1103 116C 11B4; # (ë¥; ë¥; 됥; ë¥; 됥; ) HANGUL SYLLABLE DOELT
+B426;B426;1103 116C 11B5;B426;1103 116C 11B5; # (ë¦; ë¦; 됦; ë¦; 됦; ) HANGUL SYLLABLE DOELP
+B427;B427;1103 116C 11B6;B427;1103 116C 11B6; # (ë§; ë§; 됧; ë§; 됧; ) HANGUL SYLLABLE DOELH
+B428;B428;1103 116C 11B7;B428;1103 116C 11B7; # (ë¨; ë¨; 됨; ë¨; 됨; ) HANGUL SYLLABLE DOEM
+B429;B429;1103 116C 11B8;B429;1103 116C 11B8; # (ë©; ë©; 됩; ë©; 됩; ) HANGUL SYLLABLE DOEB
+B42A;B42A;1103 116C 11B9;B42A;1103 116C 11B9; # (ëª; ëª; 됪; ëª; 됪; ) HANGUL SYLLABLE DOEBS
+B42B;B42B;1103 116C 11BA;B42B;1103 116C 11BA; # (ë«; ë«; 됫; ë«; 됫; ) HANGUL SYLLABLE DOES
+B42C;B42C;1103 116C 11BB;B42C;1103 116C 11BB; # (ë¬; ë¬; 됬; ë¬; 됬; ) HANGUL SYLLABLE DOESS
+B42D;B42D;1103 116C 11BC;B42D;1103 116C 11BC; # (ë­; ë­; 됭; ë­; 됭; ) HANGUL SYLLABLE DOENG
+B42E;B42E;1103 116C 11BD;B42E;1103 116C 11BD; # (ë®; ë®; 됮; ë®; 됮; ) HANGUL SYLLABLE DOEJ
+B42F;B42F;1103 116C 11BE;B42F;1103 116C 11BE; # (ë¯; ë¯; 됯; ë¯; 됯; ) HANGUL SYLLABLE DOEC
+B430;B430;1103 116C 11BF;B430;1103 116C 11BF; # (ë°; ë°; 됰; ë°; 됰; ) HANGUL SYLLABLE DOEK
+B431;B431;1103 116C 11C0;B431;1103 116C 11C0; # (ë±; ë±; 됱; ë±; 됱; ) HANGUL SYLLABLE DOET
+B432;B432;1103 116C 11C1;B432;1103 116C 11C1; # (ë²; ë²; 되á‡; ë²; 되á‡; ) HANGUL SYLLABLE DOEP
+B433;B433;1103 116C 11C2;B433;1103 116C 11C2; # (ë³; ë³; 됳; ë³; 됳; ) HANGUL SYLLABLE DOEH
+B434;B434;1103 116D;B434;1103 116D; # (ë´; ë´; 됴; ë´; 됴; ) HANGUL SYLLABLE DYO
+B435;B435;1103 116D 11A8;B435;1103 116D 11A8; # (ëµ; ëµ; 됵; ëµ; 됵; ) HANGUL SYLLABLE DYOG
+B436;B436;1103 116D 11A9;B436;1103 116D 11A9; # (ë¶; ë¶; 됶; ë¶; 됶; ) HANGUL SYLLABLE DYOGG
+B437;B437;1103 116D 11AA;B437;1103 116D 11AA; # (ë·; ë·; 됷; ë·; 됷; ) HANGUL SYLLABLE DYOGS
+B438;B438;1103 116D 11AB;B438;1103 116D 11AB; # (ë¸; ë¸; 됸; ë¸; 됸; ) HANGUL SYLLABLE DYON
+B439;B439;1103 116D 11AC;B439;1103 116D 11AC; # (ë¹; ë¹; 됹; ë¹; 됹; ) HANGUL SYLLABLE DYONJ
+B43A;B43A;1103 116D 11AD;B43A;1103 116D 11AD; # (ëº; ëº; 됺; ëº; 됺; ) HANGUL SYLLABLE DYONH
+B43B;B43B;1103 116D 11AE;B43B;1103 116D 11AE; # (ë»; ë»; 됻; ë»; 됻; ) HANGUL SYLLABLE DYOD
+B43C;B43C;1103 116D 11AF;B43C;1103 116D 11AF; # (ë¼; ë¼; 됼; ë¼; 됼; ) HANGUL SYLLABLE DYOL
+B43D;B43D;1103 116D 11B0;B43D;1103 116D 11B0; # (ë½; ë½; 됽; ë½; 됽; ) HANGUL SYLLABLE DYOLG
+B43E;B43E;1103 116D 11B1;B43E;1103 116D 11B1; # (ë¾; ë¾; 됾; ë¾; 됾; ) HANGUL SYLLABLE DYOLM
+B43F;B43F;1103 116D 11B2;B43F;1103 116D 11B2; # (ë¿; ë¿; 됿; ë¿; 됿; ) HANGUL SYLLABLE DYOLB
+B440;B440;1103 116D 11B3;B440;1103 116D 11B3; # (둀; 둀; 둀; 둀; 둀; ) HANGUL SYLLABLE DYOLS
+B441;B441;1103 116D 11B4;B441;1103 116D 11B4; # (ë‘; ë‘; 둁; ë‘; 둁; ) HANGUL SYLLABLE DYOLT
+B442;B442;1103 116D 11B5;B442;1103 116D 11B5; # (둂; 둂; 둂; 둂; 둂; ) HANGUL SYLLABLE DYOLP
+B443;B443;1103 116D 11B6;B443;1103 116D 11B6; # (둃; 둃; 둃; 둃; 둃; ) HANGUL SYLLABLE DYOLH
+B444;B444;1103 116D 11B7;B444;1103 116D 11B7; # (둄; 둄; 둄; 둄; 둄; ) HANGUL SYLLABLE DYOM
+B445;B445;1103 116D 11B8;B445;1103 116D 11B8; # (둅; 둅; 둅; 둅; 둅; ) HANGUL SYLLABLE DYOB
+B446;B446;1103 116D 11B9;B446;1103 116D 11B9; # (둆; 둆; 둆; 둆; 둆; ) HANGUL SYLLABLE DYOBS
+B447;B447;1103 116D 11BA;B447;1103 116D 11BA; # (둇; 둇; 둇; 둇; 둇; ) HANGUL SYLLABLE DYOS
+B448;B448;1103 116D 11BB;B448;1103 116D 11BB; # (둈; 둈; 둈; 둈; 둈; ) HANGUL SYLLABLE DYOSS
+B449;B449;1103 116D 11BC;B449;1103 116D 11BC; # (둉; 둉; 둉; 둉; 둉; ) HANGUL SYLLABLE DYONG
+B44A;B44A;1103 116D 11BD;B44A;1103 116D 11BD; # (둊; 둊; 둊; 둊; 둊; ) HANGUL SYLLABLE DYOJ
+B44B;B44B;1103 116D 11BE;B44B;1103 116D 11BE; # (둋; 둋; 둋; 둋; 둋; ) HANGUL SYLLABLE DYOC
+B44C;B44C;1103 116D 11BF;B44C;1103 116D 11BF; # (둌; 둌; 둌; 둌; 둌; ) HANGUL SYLLABLE DYOK
+B44D;B44D;1103 116D 11C0;B44D;1103 116D 11C0; # (ë‘; ë‘; 둍; ë‘; 둍; ) HANGUL SYLLABLE DYOT
+B44E;B44E;1103 116D 11C1;B44E;1103 116D 11C1; # (ë‘Ž; ë‘Ž; 됴á‡; ë‘Ž; 됴á‡; ) HANGUL SYLLABLE DYOP
+B44F;B44F;1103 116D 11C2;B44F;1103 116D 11C2; # (ë‘; ë‘; 둏; ë‘; 둏; ) HANGUL SYLLABLE DYOH
+B450;B450;1103 116E;B450;1103 116E; # (ë‘; ë‘; 두; ë‘; 두; ) HANGUL SYLLABLE DU
+B451;B451;1103 116E 11A8;B451;1103 116E 11A8; # (둑; 둑; 둑; 둑; 둑; ) HANGUL SYLLABLE DUG
+B452;B452;1103 116E 11A9;B452;1103 116E 11A9; # (둒; 둒; 둒; 둒; 둒; ) HANGUL SYLLABLE DUGG
+B453;B453;1103 116E 11AA;B453;1103 116E 11AA; # (둓; 둓; 둓; 둓; 둓; ) HANGUL SYLLABLE DUGS
+B454;B454;1103 116E 11AB;B454;1103 116E 11AB; # (둔; 둔; 둔; 둔; 둔; ) HANGUL SYLLABLE DUN
+B455;B455;1103 116E 11AC;B455;1103 116E 11AC; # (둕; 둕; 둕; 둕; 둕; ) HANGUL SYLLABLE DUNJ
+B456;B456;1103 116E 11AD;B456;1103 116E 11AD; # (둖; 둖; 둖; 둖; 둖; ) HANGUL SYLLABLE DUNH
+B457;B457;1103 116E 11AE;B457;1103 116E 11AE; # (둗; 둗; 둗; 둗; 둗; ) HANGUL SYLLABLE DUD
+B458;B458;1103 116E 11AF;B458;1103 116E 11AF; # (둘; 둘; 둘; 둘; 둘; ) HANGUL SYLLABLE DUL
+B459;B459;1103 116E 11B0;B459;1103 116E 11B0; # (둙; 둙; 둙; 둙; 둙; ) HANGUL SYLLABLE DULG
+B45A;B45A;1103 116E 11B1;B45A;1103 116E 11B1; # (둚; 둚; 둚; 둚; 둚; ) HANGUL SYLLABLE DULM
+B45B;B45B;1103 116E 11B2;B45B;1103 116E 11B2; # (둛; 둛; 둛; 둛; 둛; ) HANGUL SYLLABLE DULB
+B45C;B45C;1103 116E 11B3;B45C;1103 116E 11B3; # (둜; 둜; 둜; 둜; 둜; ) HANGUL SYLLABLE DULS
+B45D;B45D;1103 116E 11B4;B45D;1103 116E 11B4; # (ë‘; ë‘; 둝; ë‘; 둝; ) HANGUL SYLLABLE DULT
+B45E;B45E;1103 116E 11B5;B45E;1103 116E 11B5; # (둞; 둞; 둞; 둞; 둞; ) HANGUL SYLLABLE DULP
+B45F;B45F;1103 116E 11B6;B45F;1103 116E 11B6; # (둟; 둟; 둟; 둟; 둟; ) HANGUL SYLLABLE DULH
+B460;B460;1103 116E 11B7;B460;1103 116E 11B7; # (둠; 둠; 둠; 둠; 둠; ) HANGUL SYLLABLE DUM
+B461;B461;1103 116E 11B8;B461;1103 116E 11B8; # (둡; 둡; 둡; 둡; 둡; ) HANGUL SYLLABLE DUB
+B462;B462;1103 116E 11B9;B462;1103 116E 11B9; # (둢; 둢; 둢; 둢; 둢; ) HANGUL SYLLABLE DUBS
+B463;B463;1103 116E 11BA;B463;1103 116E 11BA; # (둣; 둣; 둣; 둣; 둣; ) HANGUL SYLLABLE DUS
+B464;B464;1103 116E 11BB;B464;1103 116E 11BB; # (둤; 둤; 둤; 둤; 둤; ) HANGUL SYLLABLE DUSS
+B465;B465;1103 116E 11BC;B465;1103 116E 11BC; # (둥; 둥; 둥; 둥; 둥; ) HANGUL SYLLABLE DUNG
+B466;B466;1103 116E 11BD;B466;1103 116E 11BD; # (둦; 둦; 둦; 둦; 둦; ) HANGUL SYLLABLE DUJ
+B467;B467;1103 116E 11BE;B467;1103 116E 11BE; # (둧; 둧; 둧; 둧; 둧; ) HANGUL SYLLABLE DUC
+B468;B468;1103 116E 11BF;B468;1103 116E 11BF; # (둨; 둨; 둨; 둨; 둨; ) HANGUL SYLLABLE DUK
+B469;B469;1103 116E 11C0;B469;1103 116E 11C0; # (둩; 둩; 둩; 둩; 둩; ) HANGUL SYLLABLE DUT
+B46A;B46A;1103 116E 11C1;B46A;1103 116E 11C1; # (둪; 둪; 두á‡; 둪; 두á‡; ) HANGUL SYLLABLE DUP
+B46B;B46B;1103 116E 11C2;B46B;1103 116E 11C2; # (둫; 둫; 둫; 둫; 둫; ) HANGUL SYLLABLE DUH
+B46C;B46C;1103 116F;B46C;1103 116F; # (둬; 둬; 둬; 둬; 둬; ) HANGUL SYLLABLE DWEO
+B46D;B46D;1103 116F 11A8;B46D;1103 116F 11A8; # (둭; 둭; 둭; 둭; 둭; ) HANGUL SYLLABLE DWEOG
+B46E;B46E;1103 116F 11A9;B46E;1103 116F 11A9; # (둮; 둮; 둮; 둮; 둮; ) HANGUL SYLLABLE DWEOGG
+B46F;B46F;1103 116F 11AA;B46F;1103 116F 11AA; # (둯; 둯; 둯; 둯; 둯; ) HANGUL SYLLABLE DWEOGS
+B470;B470;1103 116F 11AB;B470;1103 116F 11AB; # (둰; 둰; 둰; 둰; 둰; ) HANGUL SYLLABLE DWEON
+B471;B471;1103 116F 11AC;B471;1103 116F 11AC; # (둱; 둱; 둱; 둱; 둱; ) HANGUL SYLLABLE DWEONJ
+B472;B472;1103 116F 11AD;B472;1103 116F 11AD; # (둲; 둲; 둲; 둲; 둲; ) HANGUL SYLLABLE DWEONH
+B473;B473;1103 116F 11AE;B473;1103 116F 11AE; # (둳; 둳; 둳; 둳; 둳; ) HANGUL SYLLABLE DWEOD
+B474;B474;1103 116F 11AF;B474;1103 116F 11AF; # (둴; 둴; 둴; 둴; 둴; ) HANGUL SYLLABLE DWEOL
+B475;B475;1103 116F 11B0;B475;1103 116F 11B0; # (둵; 둵; 둵; 둵; 둵; ) HANGUL SYLLABLE DWEOLG
+B476;B476;1103 116F 11B1;B476;1103 116F 11B1; # (둶; 둶; 둶; 둶; 둶; ) HANGUL SYLLABLE DWEOLM
+B477;B477;1103 116F 11B2;B477;1103 116F 11B2; # (둷; 둷; 둷; 둷; 둷; ) HANGUL SYLLABLE DWEOLB
+B478;B478;1103 116F 11B3;B478;1103 116F 11B3; # (둸; 둸; 둸; 둸; 둸; ) HANGUL SYLLABLE DWEOLS
+B479;B479;1103 116F 11B4;B479;1103 116F 11B4; # (둹; 둹; 둹; 둹; 둹; ) HANGUL SYLLABLE DWEOLT
+B47A;B47A;1103 116F 11B5;B47A;1103 116F 11B5; # (둺; 둺; 둺; 둺; 둺; ) HANGUL SYLLABLE DWEOLP
+B47B;B47B;1103 116F 11B6;B47B;1103 116F 11B6; # (둻; 둻; 둻; 둻; 둻; ) HANGUL SYLLABLE DWEOLH
+B47C;B47C;1103 116F 11B7;B47C;1103 116F 11B7; # (둼; 둼; 둼; 둼; 둼; ) HANGUL SYLLABLE DWEOM
+B47D;B47D;1103 116F 11B8;B47D;1103 116F 11B8; # (둽; 둽; 둽; 둽; 둽; ) HANGUL SYLLABLE DWEOB
+B47E;B47E;1103 116F 11B9;B47E;1103 116F 11B9; # (둾; 둾; 둾; 둾; 둾; ) HANGUL SYLLABLE DWEOBS
+B47F;B47F;1103 116F 11BA;B47F;1103 116F 11BA; # (둿; 둿; 둿; 둿; 둿; ) HANGUL SYLLABLE DWEOS
+B480;B480;1103 116F 11BB;B480;1103 116F 11BB; # (뒀; 뒀; 뒀; 뒀; 뒀; ) HANGUL SYLLABLE DWEOSS
+B481;B481;1103 116F 11BC;B481;1103 116F 11BC; # (ë’; ë’; 뒁; ë’; 뒁; ) HANGUL SYLLABLE DWEONG
+B482;B482;1103 116F 11BD;B482;1103 116F 11BD; # (뒂; 뒂; 뒂; 뒂; 뒂; ) HANGUL SYLLABLE DWEOJ
+B483;B483;1103 116F 11BE;B483;1103 116F 11BE; # (뒃; 뒃; 뒃; 뒃; 뒃; ) HANGUL SYLLABLE DWEOC
+B484;B484;1103 116F 11BF;B484;1103 116F 11BF; # (뒄; 뒄; 뒄; 뒄; 뒄; ) HANGUL SYLLABLE DWEOK
+B485;B485;1103 116F 11C0;B485;1103 116F 11C0; # (뒅; 뒅; 뒅; 뒅; 뒅; ) HANGUL SYLLABLE DWEOT
+B486;B486;1103 116F 11C1;B486;1103 116F 11C1; # (ë’†; ë’†; 둬á‡; ë’†; 둬á‡; ) HANGUL SYLLABLE DWEOP
+B487;B487;1103 116F 11C2;B487;1103 116F 11C2; # (뒇; 뒇; 뒇; 뒇; 뒇; ) HANGUL SYLLABLE DWEOH
+B488;B488;1103 1170;B488;1103 1170; # (뒈; 뒈; 뒈; 뒈; 뒈; ) HANGUL SYLLABLE DWE
+B489;B489;1103 1170 11A8;B489;1103 1170 11A8; # (뒉; 뒉; 뒉; 뒉; 뒉; ) HANGUL SYLLABLE DWEG
+B48A;B48A;1103 1170 11A9;B48A;1103 1170 11A9; # (뒊; 뒊; 뒊; 뒊; 뒊; ) HANGUL SYLLABLE DWEGG
+B48B;B48B;1103 1170 11AA;B48B;1103 1170 11AA; # (뒋; 뒋; 뒋; 뒋; 뒋; ) HANGUL SYLLABLE DWEGS
+B48C;B48C;1103 1170 11AB;B48C;1103 1170 11AB; # (뒌; 뒌; 뒌; 뒌; 뒌; ) HANGUL SYLLABLE DWEN
+B48D;B48D;1103 1170 11AC;B48D;1103 1170 11AC; # (ë’; ë’; 뒍; ë’; 뒍; ) HANGUL SYLLABLE DWENJ
+B48E;B48E;1103 1170 11AD;B48E;1103 1170 11AD; # (뒎; 뒎; 뒎; 뒎; 뒎; ) HANGUL SYLLABLE DWENH
+B48F;B48F;1103 1170 11AE;B48F;1103 1170 11AE; # (ë’; ë’; 뒏; ë’; 뒏; ) HANGUL SYLLABLE DWED
+B490;B490;1103 1170 11AF;B490;1103 1170 11AF; # (ë’; ë’; 뒐; ë’; 뒐; ) HANGUL SYLLABLE DWEL
+B491;B491;1103 1170 11B0;B491;1103 1170 11B0; # (뒑; 뒑; 뒑; 뒑; 뒑; ) HANGUL SYLLABLE DWELG
+B492;B492;1103 1170 11B1;B492;1103 1170 11B1; # (뒒; 뒒; 뒒; 뒒; 뒒; ) HANGUL SYLLABLE DWELM
+B493;B493;1103 1170 11B2;B493;1103 1170 11B2; # (뒓; 뒓; 뒓; 뒓; 뒓; ) HANGUL SYLLABLE DWELB
+B494;B494;1103 1170 11B3;B494;1103 1170 11B3; # (뒔; 뒔; 뒔; 뒔; 뒔; ) HANGUL SYLLABLE DWELS
+B495;B495;1103 1170 11B4;B495;1103 1170 11B4; # (뒕; 뒕; 뒕; 뒕; 뒕; ) HANGUL SYLLABLE DWELT
+B496;B496;1103 1170 11B5;B496;1103 1170 11B5; # (뒖; 뒖; 뒖; 뒖; 뒖; ) HANGUL SYLLABLE DWELP
+B497;B497;1103 1170 11B6;B497;1103 1170 11B6; # (뒗; 뒗; 뒗; 뒗; 뒗; ) HANGUL SYLLABLE DWELH
+B498;B498;1103 1170 11B7;B498;1103 1170 11B7; # (뒘; 뒘; 뒘; 뒘; 뒘; ) HANGUL SYLLABLE DWEM
+B499;B499;1103 1170 11B8;B499;1103 1170 11B8; # (뒙; 뒙; 뒙; 뒙; 뒙; ) HANGUL SYLLABLE DWEB
+B49A;B49A;1103 1170 11B9;B49A;1103 1170 11B9; # (뒚; 뒚; 뒚; 뒚; 뒚; ) HANGUL SYLLABLE DWEBS
+B49B;B49B;1103 1170 11BA;B49B;1103 1170 11BA; # (뒛; 뒛; 뒛; 뒛; 뒛; ) HANGUL SYLLABLE DWES
+B49C;B49C;1103 1170 11BB;B49C;1103 1170 11BB; # (뒜; 뒜; 뒜; 뒜; 뒜; ) HANGUL SYLLABLE DWESS
+B49D;B49D;1103 1170 11BC;B49D;1103 1170 11BC; # (ë’; ë’; 뒝; ë’; 뒝; ) HANGUL SYLLABLE DWENG
+B49E;B49E;1103 1170 11BD;B49E;1103 1170 11BD; # (뒞; 뒞; 뒞; 뒞; 뒞; ) HANGUL SYLLABLE DWEJ
+B49F;B49F;1103 1170 11BE;B49F;1103 1170 11BE; # (뒟; 뒟; 뒟; 뒟; 뒟; ) HANGUL SYLLABLE DWEC
+B4A0;B4A0;1103 1170 11BF;B4A0;1103 1170 11BF; # (뒠; 뒠; 뒠; 뒠; 뒠; ) HANGUL SYLLABLE DWEK
+B4A1;B4A1;1103 1170 11C0;B4A1;1103 1170 11C0; # (뒡; 뒡; 뒡; 뒡; 뒡; ) HANGUL SYLLABLE DWET
+B4A2;B4A2;1103 1170 11C1;B4A2;1103 1170 11C1; # (ë’¢; ë’¢; 뒈á‡; ë’¢; 뒈á‡; ) HANGUL SYLLABLE DWEP
+B4A3;B4A3;1103 1170 11C2;B4A3;1103 1170 11C2; # (뒣; 뒣; 뒣; 뒣; 뒣; ) HANGUL SYLLABLE DWEH
+B4A4;B4A4;1103 1171;B4A4;1103 1171; # (뒤; 뒤; 뒤; 뒤; 뒤; ) HANGUL SYLLABLE DWI
+B4A5;B4A5;1103 1171 11A8;B4A5;1103 1171 11A8; # (뒥; 뒥; 뒥; 뒥; 뒥; ) HANGUL SYLLABLE DWIG
+B4A6;B4A6;1103 1171 11A9;B4A6;1103 1171 11A9; # (뒦; 뒦; 뒦; 뒦; 뒦; ) HANGUL SYLLABLE DWIGG
+B4A7;B4A7;1103 1171 11AA;B4A7;1103 1171 11AA; # (뒧; 뒧; 뒧; 뒧; 뒧; ) HANGUL SYLLABLE DWIGS
+B4A8;B4A8;1103 1171 11AB;B4A8;1103 1171 11AB; # (뒨; 뒨; 뒨; 뒨; 뒨; ) HANGUL SYLLABLE DWIN
+B4A9;B4A9;1103 1171 11AC;B4A9;1103 1171 11AC; # (뒩; 뒩; 뒩; 뒩; 뒩; ) HANGUL SYLLABLE DWINJ
+B4AA;B4AA;1103 1171 11AD;B4AA;1103 1171 11AD; # (뒪; 뒪; 뒪; 뒪; 뒪; ) HANGUL SYLLABLE DWINH
+B4AB;B4AB;1103 1171 11AE;B4AB;1103 1171 11AE; # (뒫; 뒫; 뒫; 뒫; 뒫; ) HANGUL SYLLABLE DWID
+B4AC;B4AC;1103 1171 11AF;B4AC;1103 1171 11AF; # (뒬; 뒬; 뒬; 뒬; 뒬; ) HANGUL SYLLABLE DWIL
+B4AD;B4AD;1103 1171 11B0;B4AD;1103 1171 11B0; # (뒭; 뒭; 뒭; 뒭; 뒭; ) HANGUL SYLLABLE DWILG
+B4AE;B4AE;1103 1171 11B1;B4AE;1103 1171 11B1; # (뒮; 뒮; 뒮; 뒮; 뒮; ) HANGUL SYLLABLE DWILM
+B4AF;B4AF;1103 1171 11B2;B4AF;1103 1171 11B2; # (뒯; 뒯; 뒯; 뒯; 뒯; ) HANGUL SYLLABLE DWILB
+B4B0;B4B0;1103 1171 11B3;B4B0;1103 1171 11B3; # (뒰; 뒰; 뒰; 뒰; 뒰; ) HANGUL SYLLABLE DWILS
+B4B1;B4B1;1103 1171 11B4;B4B1;1103 1171 11B4; # (뒱; 뒱; 뒱; 뒱; 뒱; ) HANGUL SYLLABLE DWILT
+B4B2;B4B2;1103 1171 11B5;B4B2;1103 1171 11B5; # (뒲; 뒲; 뒲; 뒲; 뒲; ) HANGUL SYLLABLE DWILP
+B4B3;B4B3;1103 1171 11B6;B4B3;1103 1171 11B6; # (뒳; 뒳; 뒳; 뒳; 뒳; ) HANGUL SYLLABLE DWILH
+B4B4;B4B4;1103 1171 11B7;B4B4;1103 1171 11B7; # (뒴; 뒴; 뒴; 뒴; 뒴; ) HANGUL SYLLABLE DWIM
+B4B5;B4B5;1103 1171 11B8;B4B5;1103 1171 11B8; # (뒵; 뒵; 뒵; 뒵; 뒵; ) HANGUL SYLLABLE DWIB
+B4B6;B4B6;1103 1171 11B9;B4B6;1103 1171 11B9; # (뒶; 뒶; 뒶; 뒶; 뒶; ) HANGUL SYLLABLE DWIBS
+B4B7;B4B7;1103 1171 11BA;B4B7;1103 1171 11BA; # (뒷; 뒷; 뒷; 뒷; 뒷; ) HANGUL SYLLABLE DWIS
+B4B8;B4B8;1103 1171 11BB;B4B8;1103 1171 11BB; # (뒸; 뒸; 뒸; 뒸; 뒸; ) HANGUL SYLLABLE DWISS
+B4B9;B4B9;1103 1171 11BC;B4B9;1103 1171 11BC; # (뒹; 뒹; 뒹; 뒹; 뒹; ) HANGUL SYLLABLE DWING
+B4BA;B4BA;1103 1171 11BD;B4BA;1103 1171 11BD; # (뒺; 뒺; 뒺; 뒺; 뒺; ) HANGUL SYLLABLE DWIJ
+B4BB;B4BB;1103 1171 11BE;B4BB;1103 1171 11BE; # (뒻; 뒻; 뒻; 뒻; 뒻; ) HANGUL SYLLABLE DWIC
+B4BC;B4BC;1103 1171 11BF;B4BC;1103 1171 11BF; # (뒼; 뒼; 뒼; 뒼; 뒼; ) HANGUL SYLLABLE DWIK
+B4BD;B4BD;1103 1171 11C0;B4BD;1103 1171 11C0; # (뒽; 뒽; 뒽; 뒽; 뒽; ) HANGUL SYLLABLE DWIT
+B4BE;B4BE;1103 1171 11C1;B4BE;1103 1171 11C1; # (ë’¾; ë’¾; 뒤á‡; ë’¾; 뒤á‡; ) HANGUL SYLLABLE DWIP
+B4BF;B4BF;1103 1171 11C2;B4BF;1103 1171 11C2; # (뒿; 뒿; 뒿; 뒿; 뒿; ) HANGUL SYLLABLE DWIH
+B4C0;B4C0;1103 1172;B4C0;1103 1172; # (듀; 듀; 듀; 듀; 듀; ) HANGUL SYLLABLE DYU
+B4C1;B4C1;1103 1172 11A8;B4C1;1103 1172 11A8; # (ë“; ë“; 듁; ë“; 듁; ) HANGUL SYLLABLE DYUG
+B4C2;B4C2;1103 1172 11A9;B4C2;1103 1172 11A9; # (듂; 듂; 듂; 듂; 듂; ) HANGUL SYLLABLE DYUGG
+B4C3;B4C3;1103 1172 11AA;B4C3;1103 1172 11AA; # (듃; 듃; 듃; 듃; 듃; ) HANGUL SYLLABLE DYUGS
+B4C4;B4C4;1103 1172 11AB;B4C4;1103 1172 11AB; # (듄; 듄; 듄; 듄; 듄; ) HANGUL SYLLABLE DYUN
+B4C5;B4C5;1103 1172 11AC;B4C5;1103 1172 11AC; # (듅; 듅; 듅; 듅; 듅; ) HANGUL SYLLABLE DYUNJ
+B4C6;B4C6;1103 1172 11AD;B4C6;1103 1172 11AD; # (듆; 듆; 듆; 듆; 듆; ) HANGUL SYLLABLE DYUNH
+B4C7;B4C7;1103 1172 11AE;B4C7;1103 1172 11AE; # (듇; 듇; 듇; 듇; 듇; ) HANGUL SYLLABLE DYUD
+B4C8;B4C8;1103 1172 11AF;B4C8;1103 1172 11AF; # (듈; 듈; 듈; 듈; 듈; ) HANGUL SYLLABLE DYUL
+B4C9;B4C9;1103 1172 11B0;B4C9;1103 1172 11B0; # (듉; 듉; 듉; 듉; 듉; ) HANGUL SYLLABLE DYULG
+B4CA;B4CA;1103 1172 11B1;B4CA;1103 1172 11B1; # (듊; 듊; 듊; 듊; 듊; ) HANGUL SYLLABLE DYULM
+B4CB;B4CB;1103 1172 11B2;B4CB;1103 1172 11B2; # (듋; 듋; 듋; 듋; 듋; ) HANGUL SYLLABLE DYULB
+B4CC;B4CC;1103 1172 11B3;B4CC;1103 1172 11B3; # (듌; 듌; 듌; 듌; 듌; ) HANGUL SYLLABLE DYULS
+B4CD;B4CD;1103 1172 11B4;B4CD;1103 1172 11B4; # (ë“; ë“; 듍; ë“; 듍; ) HANGUL SYLLABLE DYULT
+B4CE;B4CE;1103 1172 11B5;B4CE;1103 1172 11B5; # (듎; 듎; 듎; 듎; 듎; ) HANGUL SYLLABLE DYULP
+B4CF;B4CF;1103 1172 11B6;B4CF;1103 1172 11B6; # (ë“; ë“; 듏; ë“; 듏; ) HANGUL SYLLABLE DYULH
+B4D0;B4D0;1103 1172 11B7;B4D0;1103 1172 11B7; # (ë“; ë“; 듐; ë“; 듐; ) HANGUL SYLLABLE DYUM
+B4D1;B4D1;1103 1172 11B8;B4D1;1103 1172 11B8; # (듑; 듑; 듑; 듑; 듑; ) HANGUL SYLLABLE DYUB
+B4D2;B4D2;1103 1172 11B9;B4D2;1103 1172 11B9; # (듒; 듒; 듒; 듒; 듒; ) HANGUL SYLLABLE DYUBS
+B4D3;B4D3;1103 1172 11BA;B4D3;1103 1172 11BA; # (듓; 듓; 듓; 듓; 듓; ) HANGUL SYLLABLE DYUS
+B4D4;B4D4;1103 1172 11BB;B4D4;1103 1172 11BB; # (듔; 듔; 듔; 듔; 듔; ) HANGUL SYLLABLE DYUSS
+B4D5;B4D5;1103 1172 11BC;B4D5;1103 1172 11BC; # (듕; 듕; 듕; 듕; 듕; ) HANGUL SYLLABLE DYUNG
+B4D6;B4D6;1103 1172 11BD;B4D6;1103 1172 11BD; # (듖; 듖; 듖; 듖; 듖; ) HANGUL SYLLABLE DYUJ
+B4D7;B4D7;1103 1172 11BE;B4D7;1103 1172 11BE; # (듗; 듗; 듗; 듗; 듗; ) HANGUL SYLLABLE DYUC
+B4D8;B4D8;1103 1172 11BF;B4D8;1103 1172 11BF; # (듘; 듘; 듘; 듘; 듘; ) HANGUL SYLLABLE DYUK
+B4D9;B4D9;1103 1172 11C0;B4D9;1103 1172 11C0; # (듙; 듙; 듙; 듙; 듙; ) HANGUL SYLLABLE DYUT
+B4DA;B4DA;1103 1172 11C1;B4DA;1103 1172 11C1; # (ë“š; ë“š; 듀á‡; ë“š; 듀á‡; ) HANGUL SYLLABLE DYUP
+B4DB;B4DB;1103 1172 11C2;B4DB;1103 1172 11C2; # (듛; 듛; 듛; 듛; 듛; ) HANGUL SYLLABLE DYUH
+B4DC;B4DC;1103 1173;B4DC;1103 1173; # (드; 드; 드; 드; 드; ) HANGUL SYLLABLE DEU
+B4DD;B4DD;1103 1173 11A8;B4DD;1103 1173 11A8; # (ë“; ë“; 득; ë“; 득; ) HANGUL SYLLABLE DEUG
+B4DE;B4DE;1103 1173 11A9;B4DE;1103 1173 11A9; # (듞; 듞; 듞; 듞; 듞; ) HANGUL SYLLABLE DEUGG
+B4DF;B4DF;1103 1173 11AA;B4DF;1103 1173 11AA; # (듟; 듟; 듟; 듟; 듟; ) HANGUL SYLLABLE DEUGS
+B4E0;B4E0;1103 1173 11AB;B4E0;1103 1173 11AB; # (든; 든; 든; 든; 든; ) HANGUL SYLLABLE DEUN
+B4E1;B4E1;1103 1173 11AC;B4E1;1103 1173 11AC; # (듡; 듡; 듡; 듡; 듡; ) HANGUL SYLLABLE DEUNJ
+B4E2;B4E2;1103 1173 11AD;B4E2;1103 1173 11AD; # (듢; 듢; 듢; 듢; 듢; ) HANGUL SYLLABLE DEUNH
+B4E3;B4E3;1103 1173 11AE;B4E3;1103 1173 11AE; # (듣; 듣; 듣; 듣; 듣; ) HANGUL SYLLABLE DEUD
+B4E4;B4E4;1103 1173 11AF;B4E4;1103 1173 11AF; # (들; 들; 들; 들; 들; ) HANGUL SYLLABLE DEUL
+B4E5;B4E5;1103 1173 11B0;B4E5;1103 1173 11B0; # (듥; 듥; 듥; 듥; 듥; ) HANGUL SYLLABLE DEULG
+B4E6;B4E6;1103 1173 11B1;B4E6;1103 1173 11B1; # (듦; 듦; 듦; 듦; 듦; ) HANGUL SYLLABLE DEULM
+B4E7;B4E7;1103 1173 11B2;B4E7;1103 1173 11B2; # (듧; 듧; 듧; 듧; 듧; ) HANGUL SYLLABLE DEULB
+B4E8;B4E8;1103 1173 11B3;B4E8;1103 1173 11B3; # (듨; 듨; 듨; 듨; 듨; ) HANGUL SYLLABLE DEULS
+B4E9;B4E9;1103 1173 11B4;B4E9;1103 1173 11B4; # (듩; 듩; 듩; 듩; 듩; ) HANGUL SYLLABLE DEULT
+B4EA;B4EA;1103 1173 11B5;B4EA;1103 1173 11B5; # (듪; 듪; 듪; 듪; 듪; ) HANGUL SYLLABLE DEULP
+B4EB;B4EB;1103 1173 11B6;B4EB;1103 1173 11B6; # (듫; 듫; 듫; 듫; 듫; ) HANGUL SYLLABLE DEULH
+B4EC;B4EC;1103 1173 11B7;B4EC;1103 1173 11B7; # (듬; 듬; 듬; 듬; 듬; ) HANGUL SYLLABLE DEUM
+B4ED;B4ED;1103 1173 11B8;B4ED;1103 1173 11B8; # (듭; 듭; 듭; 듭; 듭; ) HANGUL SYLLABLE DEUB
+B4EE;B4EE;1103 1173 11B9;B4EE;1103 1173 11B9; # (듮; 듮; 듮; 듮; 듮; ) HANGUL SYLLABLE DEUBS
+B4EF;B4EF;1103 1173 11BA;B4EF;1103 1173 11BA; # (듯; 듯; 듯; 듯; 듯; ) HANGUL SYLLABLE DEUS
+B4F0;B4F0;1103 1173 11BB;B4F0;1103 1173 11BB; # (듰; 듰; 듰; 듰; 듰; ) HANGUL SYLLABLE DEUSS
+B4F1;B4F1;1103 1173 11BC;B4F1;1103 1173 11BC; # (등; 등; 등; 등; 등; ) HANGUL SYLLABLE DEUNG
+B4F2;B4F2;1103 1173 11BD;B4F2;1103 1173 11BD; # (듲; 듲; 듲; 듲; 듲; ) HANGUL SYLLABLE DEUJ
+B4F3;B4F3;1103 1173 11BE;B4F3;1103 1173 11BE; # (듳; 듳; 듳; 듳; 듳; ) HANGUL SYLLABLE DEUC
+B4F4;B4F4;1103 1173 11BF;B4F4;1103 1173 11BF; # (듴; 듴; 듴; 듴; 듴; ) HANGUL SYLLABLE DEUK
+B4F5;B4F5;1103 1173 11C0;B4F5;1103 1173 11C0; # (듵; 듵; 듵; 듵; 듵; ) HANGUL SYLLABLE DEUT
+B4F6;B4F6;1103 1173 11C1;B4F6;1103 1173 11C1; # (듶; 듶; 드á‡; 듶; 드á‡; ) HANGUL SYLLABLE DEUP
+B4F7;B4F7;1103 1173 11C2;B4F7;1103 1173 11C2; # (듷; 듷; 듷; 듷; 듷; ) HANGUL SYLLABLE DEUH
+B4F8;B4F8;1103 1174;B4F8;1103 1174; # (듸; 듸; 듸; 듸; 듸; ) HANGUL SYLLABLE DYI
+B4F9;B4F9;1103 1174 11A8;B4F9;1103 1174 11A8; # (듹; 듹; 듹; 듹; 듹; ) HANGUL SYLLABLE DYIG
+B4FA;B4FA;1103 1174 11A9;B4FA;1103 1174 11A9; # (듺; 듺; 듺; 듺; 듺; ) HANGUL SYLLABLE DYIGG
+B4FB;B4FB;1103 1174 11AA;B4FB;1103 1174 11AA; # (듻; 듻; 듻; 듻; 듻; ) HANGUL SYLLABLE DYIGS
+B4FC;B4FC;1103 1174 11AB;B4FC;1103 1174 11AB; # (듼; 듼; 듼; 듼; 듼; ) HANGUL SYLLABLE DYIN
+B4FD;B4FD;1103 1174 11AC;B4FD;1103 1174 11AC; # (듽; 듽; 듽; 듽; 듽; ) HANGUL SYLLABLE DYINJ
+B4FE;B4FE;1103 1174 11AD;B4FE;1103 1174 11AD; # (듾; 듾; 듾; 듾; 듾; ) HANGUL SYLLABLE DYINH
+B4FF;B4FF;1103 1174 11AE;B4FF;1103 1174 11AE; # (듿; 듿; 듿; 듿; 듿; ) HANGUL SYLLABLE DYID
+B500;B500;1103 1174 11AF;B500;1103 1174 11AF; # (딀; 딀; 딀; 딀; 딀; ) HANGUL SYLLABLE DYIL
+B501;B501;1103 1174 11B0;B501;1103 1174 11B0; # (ë”; ë”; 딁; ë”; 딁; ) HANGUL SYLLABLE DYILG
+B502;B502;1103 1174 11B1;B502;1103 1174 11B1; # (딂; 딂; 딂; 딂; 딂; ) HANGUL SYLLABLE DYILM
+B503;B503;1103 1174 11B2;B503;1103 1174 11B2; # (딃; 딃; 딃; 딃; 딃; ) HANGUL SYLLABLE DYILB
+B504;B504;1103 1174 11B3;B504;1103 1174 11B3; # (딄; 딄; 딄; 딄; 딄; ) HANGUL SYLLABLE DYILS
+B505;B505;1103 1174 11B4;B505;1103 1174 11B4; # (딅; 딅; 딅; 딅; 딅; ) HANGUL SYLLABLE DYILT
+B506;B506;1103 1174 11B5;B506;1103 1174 11B5; # (딆; 딆; 딆; 딆; 딆; ) HANGUL SYLLABLE DYILP
+B507;B507;1103 1174 11B6;B507;1103 1174 11B6; # (딇; 딇; 딇; 딇; 딇; ) HANGUL SYLLABLE DYILH
+B508;B508;1103 1174 11B7;B508;1103 1174 11B7; # (딈; 딈; 딈; 딈; 딈; ) HANGUL SYLLABLE DYIM
+B509;B509;1103 1174 11B8;B509;1103 1174 11B8; # (딉; 딉; 딉; 딉; 딉; ) HANGUL SYLLABLE DYIB
+B50A;B50A;1103 1174 11B9;B50A;1103 1174 11B9; # (딊; 딊; 딊; 딊; 딊; ) HANGUL SYLLABLE DYIBS
+B50B;B50B;1103 1174 11BA;B50B;1103 1174 11BA; # (딋; 딋; 딋; 딋; 딋; ) HANGUL SYLLABLE DYIS
+B50C;B50C;1103 1174 11BB;B50C;1103 1174 11BB; # (딌; 딌; 딌; 딌; 딌; ) HANGUL SYLLABLE DYISS
+B50D;B50D;1103 1174 11BC;B50D;1103 1174 11BC; # (ë”; ë”; 딍; ë”; 딍; ) HANGUL SYLLABLE DYING
+B50E;B50E;1103 1174 11BD;B50E;1103 1174 11BD; # (딎; 딎; 딎; 딎; 딎; ) HANGUL SYLLABLE DYIJ
+B50F;B50F;1103 1174 11BE;B50F;1103 1174 11BE; # (ë”; ë”; 딏; ë”; 딏; ) HANGUL SYLLABLE DYIC
+B510;B510;1103 1174 11BF;B510;1103 1174 11BF; # (ë”; ë”; 딐; ë”; 딐; ) HANGUL SYLLABLE DYIK
+B511;B511;1103 1174 11C0;B511;1103 1174 11C0; # (딑; 딑; 딑; 딑; 딑; ) HANGUL SYLLABLE DYIT
+B512;B512;1103 1174 11C1;B512;1103 1174 11C1; # (ë”’; ë”’; 듸á‡; ë”’; 듸á‡; ) HANGUL SYLLABLE DYIP
+B513;B513;1103 1174 11C2;B513;1103 1174 11C2; # (딓; 딓; 딓; 딓; 딓; ) HANGUL SYLLABLE DYIH
+B514;B514;1103 1175;B514;1103 1175; # (디; 디; 디; 디; 디; ) HANGUL SYLLABLE DI
+B515;B515;1103 1175 11A8;B515;1103 1175 11A8; # (딕; 딕; 딕; 딕; 딕; ) HANGUL SYLLABLE DIG
+B516;B516;1103 1175 11A9;B516;1103 1175 11A9; # (딖; 딖; 딖; 딖; 딖; ) HANGUL SYLLABLE DIGG
+B517;B517;1103 1175 11AA;B517;1103 1175 11AA; # (딗; 딗; 딗; 딗; 딗; ) HANGUL SYLLABLE DIGS
+B518;B518;1103 1175 11AB;B518;1103 1175 11AB; # (딘; 딘; 딘; 딘; 딘; ) HANGUL SYLLABLE DIN
+B519;B519;1103 1175 11AC;B519;1103 1175 11AC; # (딙; 딙; 딙; 딙; 딙; ) HANGUL SYLLABLE DINJ
+B51A;B51A;1103 1175 11AD;B51A;1103 1175 11AD; # (딚; 딚; 딚; 딚; 딚; ) HANGUL SYLLABLE DINH
+B51B;B51B;1103 1175 11AE;B51B;1103 1175 11AE; # (딛; 딛; 딛; 딛; 딛; ) HANGUL SYLLABLE DID
+B51C;B51C;1103 1175 11AF;B51C;1103 1175 11AF; # (딜; 딜; 딜; 딜; 딜; ) HANGUL SYLLABLE DIL
+B51D;B51D;1103 1175 11B0;B51D;1103 1175 11B0; # (ë”; ë”; 딝; ë”; 딝; ) HANGUL SYLLABLE DILG
+B51E;B51E;1103 1175 11B1;B51E;1103 1175 11B1; # (딞; 딞; 딞; 딞; 딞; ) HANGUL SYLLABLE DILM
+B51F;B51F;1103 1175 11B2;B51F;1103 1175 11B2; # (딟; 딟; 딟; 딟; 딟; ) HANGUL SYLLABLE DILB
+B520;B520;1103 1175 11B3;B520;1103 1175 11B3; # (딠; 딠; 딠; 딠; 딠; ) HANGUL SYLLABLE DILS
+B521;B521;1103 1175 11B4;B521;1103 1175 11B4; # (딡; 딡; 딡; 딡; 딡; ) HANGUL SYLLABLE DILT
+B522;B522;1103 1175 11B5;B522;1103 1175 11B5; # (딢; 딢; 딢; 딢; 딢; ) HANGUL SYLLABLE DILP
+B523;B523;1103 1175 11B6;B523;1103 1175 11B6; # (딣; 딣; 딣; 딣; 딣; ) HANGUL SYLLABLE DILH
+B524;B524;1103 1175 11B7;B524;1103 1175 11B7; # (딤; 딤; 딤; 딤; 딤; ) HANGUL SYLLABLE DIM
+B525;B525;1103 1175 11B8;B525;1103 1175 11B8; # (딥; 딥; 딥; 딥; 딥; ) HANGUL SYLLABLE DIB
+B526;B526;1103 1175 11B9;B526;1103 1175 11B9; # (딦; 딦; 딦; 딦; 딦; ) HANGUL SYLLABLE DIBS
+B527;B527;1103 1175 11BA;B527;1103 1175 11BA; # (딧; 딧; 딧; 딧; 딧; ) HANGUL SYLLABLE DIS
+B528;B528;1103 1175 11BB;B528;1103 1175 11BB; # (딨; 딨; 딨; 딨; 딨; ) HANGUL SYLLABLE DISS
+B529;B529;1103 1175 11BC;B529;1103 1175 11BC; # (딩; 딩; 딩; 딩; 딩; ) HANGUL SYLLABLE DING
+B52A;B52A;1103 1175 11BD;B52A;1103 1175 11BD; # (딪; 딪; 딪; 딪; 딪; ) HANGUL SYLLABLE DIJ
+B52B;B52B;1103 1175 11BE;B52B;1103 1175 11BE; # (딫; 딫; 딫; 딫; 딫; ) HANGUL SYLLABLE DIC
+B52C;B52C;1103 1175 11BF;B52C;1103 1175 11BF; # (딬; 딬; 딬; 딬; 딬; ) HANGUL SYLLABLE DIK
+B52D;B52D;1103 1175 11C0;B52D;1103 1175 11C0; # (딭; 딭; 딭; 딭; 딭; ) HANGUL SYLLABLE DIT
+B52E;B52E;1103 1175 11C1;B52E;1103 1175 11C1; # (ë”®; ë”®; 디á‡; ë”®; 디á‡; ) HANGUL SYLLABLE DIP
+B52F;B52F;1103 1175 11C2;B52F;1103 1175 11C2; # (딯; 딯; 딯; 딯; 딯; ) HANGUL SYLLABLE DIH
+B530;B530;1104 1161;B530;1104 1161; # (ë”°; ë”°; á„„á…¡; ë”°; á„„á…¡; ) HANGUL SYLLABLE DDA
+B531;B531;1104 1161 11A8;B531;1104 1161 11A8; # (딱; 딱; 딱; 딱; 딱; ) HANGUL SYLLABLE DDAG
+B532;B532;1104 1161 11A9;B532;1104 1161 11A9; # (딲; 딲; 딲; 딲; 딲; ) HANGUL SYLLABLE DDAGG
+B533;B533;1104 1161 11AA;B533;1104 1161 11AA; # (딳; 딳; 딳; 딳; 딳; ) HANGUL SYLLABLE DDAGS
+B534;B534;1104 1161 11AB;B534;1104 1161 11AB; # (딴; 딴; 딴; 딴; 딴; ) HANGUL SYLLABLE DDAN
+B535;B535;1104 1161 11AC;B535;1104 1161 11AC; # (딵; 딵; 딵; 딵; 딵; ) HANGUL SYLLABLE DDANJ
+B536;B536;1104 1161 11AD;B536;1104 1161 11AD; # (딶; 딶; 딶; 딶; 딶; ) HANGUL SYLLABLE DDANH
+B537;B537;1104 1161 11AE;B537;1104 1161 11AE; # (딷; 딷; 딷; 딷; 딷; ) HANGUL SYLLABLE DDAD
+B538;B538;1104 1161 11AF;B538;1104 1161 11AF; # (딸; 딸; 딸; 딸; 딸; ) HANGUL SYLLABLE DDAL
+B539;B539;1104 1161 11B0;B539;1104 1161 11B0; # (딹; 딹; 딹; 딹; 딹; ) HANGUL SYLLABLE DDALG
+B53A;B53A;1104 1161 11B1;B53A;1104 1161 11B1; # (딺; 딺; 딺; 딺; 딺; ) HANGUL SYLLABLE DDALM
+B53B;B53B;1104 1161 11B2;B53B;1104 1161 11B2; # (딻; 딻; 딻; 딻; 딻; ) HANGUL SYLLABLE DDALB
+B53C;B53C;1104 1161 11B3;B53C;1104 1161 11B3; # (딼; 딼; 딼; 딼; 딼; ) HANGUL SYLLABLE DDALS
+B53D;B53D;1104 1161 11B4;B53D;1104 1161 11B4; # (딽; 딽; 딽; 딽; 딽; ) HANGUL SYLLABLE DDALT
+B53E;B53E;1104 1161 11B5;B53E;1104 1161 11B5; # (딾; 딾; 딾; 딾; 딾; ) HANGUL SYLLABLE DDALP
+B53F;B53F;1104 1161 11B6;B53F;1104 1161 11B6; # (딿; 딿; 딿; 딿; 딿; ) HANGUL SYLLABLE DDALH
+B540;B540;1104 1161 11B7;B540;1104 1161 11B7; # (땀; 땀; 땀; 땀; 땀; ) HANGUL SYLLABLE DDAM
+B541;B541;1104 1161 11B8;B541;1104 1161 11B8; # (ë•; ë•; 땁; ë•; 땁; ) HANGUL SYLLABLE DDAB
+B542;B542;1104 1161 11B9;B542;1104 1161 11B9; # (땂; 땂; 땂; 땂; 땂; ) HANGUL SYLLABLE DDABS
+B543;B543;1104 1161 11BA;B543;1104 1161 11BA; # (땃; 땃; 땃; 땃; 땃; ) HANGUL SYLLABLE DDAS
+B544;B544;1104 1161 11BB;B544;1104 1161 11BB; # (땄; 땄; 땄; 땄; 땄; ) HANGUL SYLLABLE DDASS
+B545;B545;1104 1161 11BC;B545;1104 1161 11BC; # (땅; 땅; 땅; 땅; 땅; ) HANGUL SYLLABLE DDANG
+B546;B546;1104 1161 11BD;B546;1104 1161 11BD; # (땆; 땆; 땆; 땆; 땆; ) HANGUL SYLLABLE DDAJ
+B547;B547;1104 1161 11BE;B547;1104 1161 11BE; # (땇; 땇; 땇; 땇; 땇; ) HANGUL SYLLABLE DDAC
+B548;B548;1104 1161 11BF;B548;1104 1161 11BF; # (땈; 땈; 땈; 땈; 땈; ) HANGUL SYLLABLE DDAK
+B549;B549;1104 1161 11C0;B549;1104 1161 11C0; # (땉; 땉; 땉; 땉; 땉; ) HANGUL SYLLABLE DDAT
+B54A;B54A;1104 1161 11C1;B54A;1104 1161 11C1; # (ë•Š; ë•Š; á„„á…¡á‡; ë•Š; á„„á…¡á‡; ) HANGUL SYLLABLE DDAP
+B54B;B54B;1104 1161 11C2;B54B;1104 1161 11C2; # (땋; 땋; 땋; 땋; 땋; ) HANGUL SYLLABLE DDAH
+B54C;B54C;1104 1162;B54C;1104 1162; # (때; 때; 때; 때; 때; ) HANGUL SYLLABLE DDAE
+B54D;B54D;1104 1162 11A8;B54D;1104 1162 11A8; # (ë•; ë•; 땍; ë•; 땍; ) HANGUL SYLLABLE DDAEG
+B54E;B54E;1104 1162 11A9;B54E;1104 1162 11A9; # (땎; 땎; 땎; 땎; 땎; ) HANGUL SYLLABLE DDAEGG
+B54F;B54F;1104 1162 11AA;B54F;1104 1162 11AA; # (ë•; ë•; 땏; ë•; 땏; ) HANGUL SYLLABLE DDAEGS
+B550;B550;1104 1162 11AB;B550;1104 1162 11AB; # (ë•; ë•; 땐; ë•; 땐; ) HANGUL SYLLABLE DDAEN
+B551;B551;1104 1162 11AC;B551;1104 1162 11AC; # (땑; 땑; 땑; 땑; 땑; ) HANGUL SYLLABLE DDAENJ
+B552;B552;1104 1162 11AD;B552;1104 1162 11AD; # (땒; 땒; 땒; 땒; 땒; ) HANGUL SYLLABLE DDAENH
+B553;B553;1104 1162 11AE;B553;1104 1162 11AE; # (땓; 땓; 땓; 땓; 땓; ) HANGUL SYLLABLE DDAED
+B554;B554;1104 1162 11AF;B554;1104 1162 11AF; # (땔; 땔; 땔; 땔; 땔; ) HANGUL SYLLABLE DDAEL
+B555;B555;1104 1162 11B0;B555;1104 1162 11B0; # (땕; 땕; 땕; 땕; 땕; ) HANGUL SYLLABLE DDAELG
+B556;B556;1104 1162 11B1;B556;1104 1162 11B1; # (땖; 땖; 땖; 땖; 땖; ) HANGUL SYLLABLE DDAELM
+B557;B557;1104 1162 11B2;B557;1104 1162 11B2; # (땗; 땗; 땗; 땗; 땗; ) HANGUL SYLLABLE DDAELB
+B558;B558;1104 1162 11B3;B558;1104 1162 11B3; # (땘; 땘; 땘; 땘; 땘; ) HANGUL SYLLABLE DDAELS
+B559;B559;1104 1162 11B4;B559;1104 1162 11B4; # (땙; 땙; 땙; 땙; 땙; ) HANGUL SYLLABLE DDAELT
+B55A;B55A;1104 1162 11B5;B55A;1104 1162 11B5; # (땚; 땚; 땚; 땚; 땚; ) HANGUL SYLLABLE DDAELP
+B55B;B55B;1104 1162 11B6;B55B;1104 1162 11B6; # (땛; 땛; 땛; 땛; 땛; ) HANGUL SYLLABLE DDAELH
+B55C;B55C;1104 1162 11B7;B55C;1104 1162 11B7; # (땜; 땜; 땜; 땜; 땜; ) HANGUL SYLLABLE DDAEM
+B55D;B55D;1104 1162 11B8;B55D;1104 1162 11B8; # (ë•; ë•; 땝; ë•; 땝; ) HANGUL SYLLABLE DDAEB
+B55E;B55E;1104 1162 11B9;B55E;1104 1162 11B9; # (땞; 땞; 땞; 땞; 땞; ) HANGUL SYLLABLE DDAEBS
+B55F;B55F;1104 1162 11BA;B55F;1104 1162 11BA; # (땟; 땟; 땟; 땟; 땟; ) HANGUL SYLLABLE DDAES
+B560;B560;1104 1162 11BB;B560;1104 1162 11BB; # (땠; 땠; 땠; 땠; 땠; ) HANGUL SYLLABLE DDAESS
+B561;B561;1104 1162 11BC;B561;1104 1162 11BC; # (땡; 땡; 땡; 땡; 땡; ) HANGUL SYLLABLE DDAENG
+B562;B562;1104 1162 11BD;B562;1104 1162 11BD; # (땢; 땢; 땢; 땢; 땢; ) HANGUL SYLLABLE DDAEJ
+B563;B563;1104 1162 11BE;B563;1104 1162 11BE; # (땣; 땣; 땣; 땣; 땣; ) HANGUL SYLLABLE DDAEC
+B564;B564;1104 1162 11BF;B564;1104 1162 11BF; # (땤; 땤; 땤; 땤; 땤; ) HANGUL SYLLABLE DDAEK
+B565;B565;1104 1162 11C0;B565;1104 1162 11C0; # (땥; 땥; 땥; 땥; 땥; ) HANGUL SYLLABLE DDAET
+B566;B566;1104 1162 11C1;B566;1104 1162 11C1; # (땦; 땦; á„„á…¢á‡; 땦; á„„á…¢á‡; ) HANGUL SYLLABLE DDAEP
+B567;B567;1104 1162 11C2;B567;1104 1162 11C2; # (땧; 땧; 땧; 땧; 땧; ) HANGUL SYLLABLE DDAEH
+B568;B568;1104 1163;B568;1104 1163; # (땨; 땨; 땨; 땨; 땨; ) HANGUL SYLLABLE DDYA
+B569;B569;1104 1163 11A8;B569;1104 1163 11A8; # (땩; 땩; 땩; 땩; 땩; ) HANGUL SYLLABLE DDYAG
+B56A;B56A;1104 1163 11A9;B56A;1104 1163 11A9; # (땪; 땪; 땪; 땪; 땪; ) HANGUL SYLLABLE DDYAGG
+B56B;B56B;1104 1163 11AA;B56B;1104 1163 11AA; # (땫; 땫; 땫; 땫; 땫; ) HANGUL SYLLABLE DDYAGS
+B56C;B56C;1104 1163 11AB;B56C;1104 1163 11AB; # (땬; 땬; 땬; 땬; 땬; ) HANGUL SYLLABLE DDYAN
+B56D;B56D;1104 1163 11AC;B56D;1104 1163 11AC; # (땭; 땭; 땭; 땭; 땭; ) HANGUL SYLLABLE DDYANJ
+B56E;B56E;1104 1163 11AD;B56E;1104 1163 11AD; # (땮; 땮; 땮; 땮; 땮; ) HANGUL SYLLABLE DDYANH
+B56F;B56F;1104 1163 11AE;B56F;1104 1163 11AE; # (땯; 땯; 땯; 땯; 땯; ) HANGUL SYLLABLE DDYAD
+B570;B570;1104 1163 11AF;B570;1104 1163 11AF; # (땰; 땰; 땰; 땰; 땰; ) HANGUL SYLLABLE DDYAL
+B571;B571;1104 1163 11B0;B571;1104 1163 11B0; # (땱; 땱; 땱; 땱; 땱; ) HANGUL SYLLABLE DDYALG
+B572;B572;1104 1163 11B1;B572;1104 1163 11B1; # (땲; 땲; 땲; 땲; 땲; ) HANGUL SYLLABLE DDYALM
+B573;B573;1104 1163 11B2;B573;1104 1163 11B2; # (땳; 땳; 땳; 땳; 땳; ) HANGUL SYLLABLE DDYALB
+B574;B574;1104 1163 11B3;B574;1104 1163 11B3; # (땴; 땴; 땴; 땴; 땴; ) HANGUL SYLLABLE DDYALS
+B575;B575;1104 1163 11B4;B575;1104 1163 11B4; # (땵; 땵; 땵; 땵; 땵; ) HANGUL SYLLABLE DDYALT
+B576;B576;1104 1163 11B5;B576;1104 1163 11B5; # (땶; 땶; 땶; 땶; 땶; ) HANGUL SYLLABLE DDYALP
+B577;B577;1104 1163 11B6;B577;1104 1163 11B6; # (땷; 땷; 땷; 땷; 땷; ) HANGUL SYLLABLE DDYALH
+B578;B578;1104 1163 11B7;B578;1104 1163 11B7; # (땸; 땸; 땸; 땸; 땸; ) HANGUL SYLLABLE DDYAM
+B579;B579;1104 1163 11B8;B579;1104 1163 11B8; # (땹; 땹; 땹; 땹; 땹; ) HANGUL SYLLABLE DDYAB
+B57A;B57A;1104 1163 11B9;B57A;1104 1163 11B9; # (땺; 땺; 땺; 땺; 땺; ) HANGUL SYLLABLE DDYABS
+B57B;B57B;1104 1163 11BA;B57B;1104 1163 11BA; # (땻; 땻; 땻; 땻; 땻; ) HANGUL SYLLABLE DDYAS
+B57C;B57C;1104 1163 11BB;B57C;1104 1163 11BB; # (땼; 땼; 땼; 땼; 땼; ) HANGUL SYLLABLE DDYASS
+B57D;B57D;1104 1163 11BC;B57D;1104 1163 11BC; # (땽; 땽; 땽; 땽; 땽; ) HANGUL SYLLABLE DDYANG
+B57E;B57E;1104 1163 11BD;B57E;1104 1163 11BD; # (땾; 땾; 땾; 땾; 땾; ) HANGUL SYLLABLE DDYAJ
+B57F;B57F;1104 1163 11BE;B57F;1104 1163 11BE; # (땿; 땿; 땿; 땿; 땿; ) HANGUL SYLLABLE DDYAC
+B580;B580;1104 1163 11BF;B580;1104 1163 11BF; # (떀; 떀; 떀; 떀; 떀; ) HANGUL SYLLABLE DDYAK
+B581;B581;1104 1163 11C0;B581;1104 1163 11C0; # (ë–; ë–; 떁; ë–; 떁; ) HANGUL SYLLABLE DDYAT
+B582;B582;1104 1163 11C1;B582;1104 1163 11C1; # (ë–‚; ë–‚; á„„á…£á‡; ë–‚; á„„á…£á‡; ) HANGUL SYLLABLE DDYAP
+B583;B583;1104 1163 11C2;B583;1104 1163 11C2; # (떃; 떃; 떃; 떃; 떃; ) HANGUL SYLLABLE DDYAH
+B584;B584;1104 1164;B584;1104 1164; # (ë–„; ë–„; á„„á…¤; ë–„; á„„á…¤; ) HANGUL SYLLABLE DDYAE
+B585;B585;1104 1164 11A8;B585;1104 1164 11A8; # (떅; 떅; 떅; 떅; 떅; ) HANGUL SYLLABLE DDYAEG
+B586;B586;1104 1164 11A9;B586;1104 1164 11A9; # (떆; 떆; 떆; 떆; 떆; ) HANGUL SYLLABLE DDYAEGG
+B587;B587;1104 1164 11AA;B587;1104 1164 11AA; # (떇; 떇; 떇; 떇; 떇; ) HANGUL SYLLABLE DDYAEGS
+B588;B588;1104 1164 11AB;B588;1104 1164 11AB; # (떈; 떈; 떈; 떈; 떈; ) HANGUL SYLLABLE DDYAEN
+B589;B589;1104 1164 11AC;B589;1104 1164 11AC; # (떉; 떉; 떉; 떉; 떉; ) HANGUL SYLLABLE DDYAENJ
+B58A;B58A;1104 1164 11AD;B58A;1104 1164 11AD; # (떊; 떊; 떊; 떊; 떊; ) HANGUL SYLLABLE DDYAENH
+B58B;B58B;1104 1164 11AE;B58B;1104 1164 11AE; # (떋; 떋; 떋; 떋; 떋; ) HANGUL SYLLABLE DDYAED
+B58C;B58C;1104 1164 11AF;B58C;1104 1164 11AF; # (떌; 떌; 떌; 떌; 떌; ) HANGUL SYLLABLE DDYAEL
+B58D;B58D;1104 1164 11B0;B58D;1104 1164 11B0; # (ë–; ë–; 떍; ë–; 떍; ) HANGUL SYLLABLE DDYAELG
+B58E;B58E;1104 1164 11B1;B58E;1104 1164 11B1; # (떎; 떎; 떎; 떎; 떎; ) HANGUL SYLLABLE DDYAELM
+B58F;B58F;1104 1164 11B2;B58F;1104 1164 11B2; # (ë–; ë–; 떏; ë–; 떏; ) HANGUL SYLLABLE DDYAELB
+B590;B590;1104 1164 11B3;B590;1104 1164 11B3; # (ë–; ë–; 떐; ë–; 떐; ) HANGUL SYLLABLE DDYAELS
+B591;B591;1104 1164 11B4;B591;1104 1164 11B4; # (떑; 떑; 떑; 떑; 떑; ) HANGUL SYLLABLE DDYAELT
+B592;B592;1104 1164 11B5;B592;1104 1164 11B5; # (떒; 떒; 떒; 떒; 떒; ) HANGUL SYLLABLE DDYAELP
+B593;B593;1104 1164 11B6;B593;1104 1164 11B6; # (떓; 떓; 떓; 떓; 떓; ) HANGUL SYLLABLE DDYAELH
+B594;B594;1104 1164 11B7;B594;1104 1164 11B7; # (떔; 떔; 떔; 떔; 떔; ) HANGUL SYLLABLE DDYAEM
+B595;B595;1104 1164 11B8;B595;1104 1164 11B8; # (떕; 떕; 떕; 떕; 떕; ) HANGUL SYLLABLE DDYAEB
+B596;B596;1104 1164 11B9;B596;1104 1164 11B9; # (떖; 떖; 떖; 떖; 떖; ) HANGUL SYLLABLE DDYAEBS
+B597;B597;1104 1164 11BA;B597;1104 1164 11BA; # (떗; 떗; 떗; 떗; 떗; ) HANGUL SYLLABLE DDYAES
+B598;B598;1104 1164 11BB;B598;1104 1164 11BB; # (떘; 떘; 떘; 떘; 떘; ) HANGUL SYLLABLE DDYAESS
+B599;B599;1104 1164 11BC;B599;1104 1164 11BC; # (떙; 떙; 떙; 떙; 떙; ) HANGUL SYLLABLE DDYAENG
+B59A;B59A;1104 1164 11BD;B59A;1104 1164 11BD; # (떚; 떚; 떚; 떚; 떚; ) HANGUL SYLLABLE DDYAEJ
+B59B;B59B;1104 1164 11BE;B59B;1104 1164 11BE; # (떛; 떛; 떛; 떛; 떛; ) HANGUL SYLLABLE DDYAEC
+B59C;B59C;1104 1164 11BF;B59C;1104 1164 11BF; # (떜; 떜; 떜; 떜; 떜; ) HANGUL SYLLABLE DDYAEK
+B59D;B59D;1104 1164 11C0;B59D;1104 1164 11C0; # (ë–; ë–; 떝; ë–; 떝; ) HANGUL SYLLABLE DDYAET
+B59E;B59E;1104 1164 11C1;B59E;1104 1164 11C1; # (ë–ž; ë–ž; á„„á…¤á‡; ë–ž; á„„á…¤á‡; ) HANGUL SYLLABLE DDYAEP
+B59F;B59F;1104 1164 11C2;B59F;1104 1164 11C2; # (떟; 떟; 떟; 떟; 떟; ) HANGUL SYLLABLE DDYAEH
+B5A0;B5A0;1104 1165;B5A0;1104 1165; # (ë– ; ë– ; á„„á…¥; ë– ; á„„á…¥; ) HANGUL SYLLABLE DDEO
+B5A1;B5A1;1104 1165 11A8;B5A1;1104 1165 11A8; # (떡; 떡; 떡; 떡; 떡; ) HANGUL SYLLABLE DDEOG
+B5A2;B5A2;1104 1165 11A9;B5A2;1104 1165 11A9; # (떢; 떢; 떢; 떢; 떢; ) HANGUL SYLLABLE DDEOGG
+B5A3;B5A3;1104 1165 11AA;B5A3;1104 1165 11AA; # (떣; 떣; 떣; 떣; 떣; ) HANGUL SYLLABLE DDEOGS
+B5A4;B5A4;1104 1165 11AB;B5A4;1104 1165 11AB; # (떤; 떤; 떤; 떤; 떤; ) HANGUL SYLLABLE DDEON
+B5A5;B5A5;1104 1165 11AC;B5A5;1104 1165 11AC; # (떥; 떥; 떥; 떥; 떥; ) HANGUL SYLLABLE DDEONJ
+B5A6;B5A6;1104 1165 11AD;B5A6;1104 1165 11AD; # (떦; 떦; 떦; 떦; 떦; ) HANGUL SYLLABLE DDEONH
+B5A7;B5A7;1104 1165 11AE;B5A7;1104 1165 11AE; # (떧; 떧; 떧; 떧; 떧; ) HANGUL SYLLABLE DDEOD
+B5A8;B5A8;1104 1165 11AF;B5A8;1104 1165 11AF; # (떨; 떨; 떨; 떨; 떨; ) HANGUL SYLLABLE DDEOL
+B5A9;B5A9;1104 1165 11B0;B5A9;1104 1165 11B0; # (떩; 떩; 떩; 떩; 떩; ) HANGUL SYLLABLE DDEOLG
+B5AA;B5AA;1104 1165 11B1;B5AA;1104 1165 11B1; # (떪; 떪; 떪; 떪; 떪; ) HANGUL SYLLABLE DDEOLM
+B5AB;B5AB;1104 1165 11B2;B5AB;1104 1165 11B2; # (떫; 떫; 떫; 떫; 떫; ) HANGUL SYLLABLE DDEOLB
+B5AC;B5AC;1104 1165 11B3;B5AC;1104 1165 11B3; # (떬; 떬; 떬; 떬; 떬; ) HANGUL SYLLABLE DDEOLS
+B5AD;B5AD;1104 1165 11B4;B5AD;1104 1165 11B4; # (떭; 떭; 떭; 떭; 떭; ) HANGUL SYLLABLE DDEOLT
+B5AE;B5AE;1104 1165 11B5;B5AE;1104 1165 11B5; # (떮; 떮; 떮; 떮; 떮; ) HANGUL SYLLABLE DDEOLP
+B5AF;B5AF;1104 1165 11B6;B5AF;1104 1165 11B6; # (떯; 떯; 떯; 떯; 떯; ) HANGUL SYLLABLE DDEOLH
+B5B0;B5B0;1104 1165 11B7;B5B0;1104 1165 11B7; # (떰; 떰; 떰; 떰; 떰; ) HANGUL SYLLABLE DDEOM
+B5B1;B5B1;1104 1165 11B8;B5B1;1104 1165 11B8; # (떱; 떱; 떱; 떱; 떱; ) HANGUL SYLLABLE DDEOB
+B5B2;B5B2;1104 1165 11B9;B5B2;1104 1165 11B9; # (떲; 떲; 떲; 떲; 떲; ) HANGUL SYLLABLE DDEOBS
+B5B3;B5B3;1104 1165 11BA;B5B3;1104 1165 11BA; # (떳; 떳; 떳; 떳; 떳; ) HANGUL SYLLABLE DDEOS
+B5B4;B5B4;1104 1165 11BB;B5B4;1104 1165 11BB; # (떴; 떴; 떴; 떴; 떴; ) HANGUL SYLLABLE DDEOSS
+B5B5;B5B5;1104 1165 11BC;B5B5;1104 1165 11BC; # (떵; 떵; 떵; 떵; 떵; ) HANGUL SYLLABLE DDEONG
+B5B6;B5B6;1104 1165 11BD;B5B6;1104 1165 11BD; # (떶; 떶; 떶; 떶; 떶; ) HANGUL SYLLABLE DDEOJ
+B5B7;B5B7;1104 1165 11BE;B5B7;1104 1165 11BE; # (떷; 떷; 떷; 떷; 떷; ) HANGUL SYLLABLE DDEOC
+B5B8;B5B8;1104 1165 11BF;B5B8;1104 1165 11BF; # (떸; 떸; 떸; 떸; 떸; ) HANGUL SYLLABLE DDEOK
+B5B9;B5B9;1104 1165 11C0;B5B9;1104 1165 11C0; # (떹; 떹; 떹; 떹; 떹; ) HANGUL SYLLABLE DDEOT
+B5BA;B5BA;1104 1165 11C1;B5BA;1104 1165 11C1; # (ë–º; ë–º; á„„á…¥á‡; ë–º; á„„á…¥á‡; ) HANGUL SYLLABLE DDEOP
+B5BB;B5BB;1104 1165 11C2;B5BB;1104 1165 11C2; # (떻; 떻; 떻; 떻; 떻; ) HANGUL SYLLABLE DDEOH
+B5BC;B5BC;1104 1166;B5BC;1104 1166; # (ë–¼; ë–¼; á„„á…¦; ë–¼; á„„á…¦; ) HANGUL SYLLABLE DDE
+B5BD;B5BD;1104 1166 11A8;B5BD;1104 1166 11A8; # (떽; 떽; 떽; 떽; 떽; ) HANGUL SYLLABLE DDEG
+B5BE;B5BE;1104 1166 11A9;B5BE;1104 1166 11A9; # (떾; 떾; 떾; 떾; 떾; ) HANGUL SYLLABLE DDEGG
+B5BF;B5BF;1104 1166 11AA;B5BF;1104 1166 11AA; # (떿; 떿; 떿; 떿; 떿; ) HANGUL SYLLABLE DDEGS
+B5C0;B5C0;1104 1166 11AB;B5C0;1104 1166 11AB; # (뗀; 뗀; 뗀; 뗀; 뗀; ) HANGUL SYLLABLE DDEN
+B5C1;B5C1;1104 1166 11AC;B5C1;1104 1166 11AC; # (ë—; ë—; 뗁; ë—; 뗁; ) HANGUL SYLLABLE DDENJ
+B5C2;B5C2;1104 1166 11AD;B5C2;1104 1166 11AD; # (뗂; 뗂; 뗂; 뗂; 뗂; ) HANGUL SYLLABLE DDENH
+B5C3;B5C3;1104 1166 11AE;B5C3;1104 1166 11AE; # (뗃; 뗃; 뗃; 뗃; 뗃; ) HANGUL SYLLABLE DDED
+B5C4;B5C4;1104 1166 11AF;B5C4;1104 1166 11AF; # (뗄; 뗄; 뗄; 뗄; 뗄; ) HANGUL SYLLABLE DDEL
+B5C5;B5C5;1104 1166 11B0;B5C5;1104 1166 11B0; # (뗅; 뗅; 뗅; 뗅; 뗅; ) HANGUL SYLLABLE DDELG
+B5C6;B5C6;1104 1166 11B1;B5C6;1104 1166 11B1; # (뗆; 뗆; 뗆; 뗆; 뗆; ) HANGUL SYLLABLE DDELM
+B5C7;B5C7;1104 1166 11B2;B5C7;1104 1166 11B2; # (뗇; 뗇; 뗇; 뗇; 뗇; ) HANGUL SYLLABLE DDELB
+B5C8;B5C8;1104 1166 11B3;B5C8;1104 1166 11B3; # (뗈; 뗈; 뗈; 뗈; 뗈; ) HANGUL SYLLABLE DDELS
+B5C9;B5C9;1104 1166 11B4;B5C9;1104 1166 11B4; # (뗉; 뗉; 뗉; 뗉; 뗉; ) HANGUL SYLLABLE DDELT
+B5CA;B5CA;1104 1166 11B5;B5CA;1104 1166 11B5; # (뗊; 뗊; 뗊; 뗊; 뗊; ) HANGUL SYLLABLE DDELP
+B5CB;B5CB;1104 1166 11B6;B5CB;1104 1166 11B6; # (뗋; 뗋; 뗋; 뗋; 뗋; ) HANGUL SYLLABLE DDELH
+B5CC;B5CC;1104 1166 11B7;B5CC;1104 1166 11B7; # (뗌; 뗌; 뗌; 뗌; 뗌; ) HANGUL SYLLABLE DDEM
+B5CD;B5CD;1104 1166 11B8;B5CD;1104 1166 11B8; # (ë—; ë—; 뗍; ë—; 뗍; ) HANGUL SYLLABLE DDEB
+B5CE;B5CE;1104 1166 11B9;B5CE;1104 1166 11B9; # (뗎; 뗎; 뗎; 뗎; 뗎; ) HANGUL SYLLABLE DDEBS
+B5CF;B5CF;1104 1166 11BA;B5CF;1104 1166 11BA; # (ë—; ë—; 뗏; ë—; 뗏; ) HANGUL SYLLABLE DDES
+B5D0;B5D0;1104 1166 11BB;B5D0;1104 1166 11BB; # (ë—; ë—; 뗐; ë—; 뗐; ) HANGUL SYLLABLE DDESS
+B5D1;B5D1;1104 1166 11BC;B5D1;1104 1166 11BC; # (뗑; 뗑; 뗑; 뗑; 뗑; ) HANGUL SYLLABLE DDENG
+B5D2;B5D2;1104 1166 11BD;B5D2;1104 1166 11BD; # (뗒; 뗒; 뗒; 뗒; 뗒; ) HANGUL SYLLABLE DDEJ
+B5D3;B5D3;1104 1166 11BE;B5D3;1104 1166 11BE; # (뗓; 뗓; 뗓; 뗓; 뗓; ) HANGUL SYLLABLE DDEC
+B5D4;B5D4;1104 1166 11BF;B5D4;1104 1166 11BF; # (뗔; 뗔; 뗔; 뗔; 뗔; ) HANGUL SYLLABLE DDEK
+B5D5;B5D5;1104 1166 11C0;B5D5;1104 1166 11C0; # (뗕; 뗕; 뗕; 뗕; 뗕; ) HANGUL SYLLABLE DDET
+B5D6;B5D6;1104 1166 11C1;B5D6;1104 1166 11C1; # (ë—–; ë—–; á„„á…¦á‡; ë—–; á„„á…¦á‡; ) HANGUL SYLLABLE DDEP
+B5D7;B5D7;1104 1166 11C2;B5D7;1104 1166 11C2; # (뗗; 뗗; 뗗; 뗗; 뗗; ) HANGUL SYLLABLE DDEH
+B5D8;B5D8;1104 1167;B5D8;1104 1167; # (ë—˜; ë—˜; á„„á…§; ë—˜; á„„á…§; ) HANGUL SYLLABLE DDYEO
+B5D9;B5D9;1104 1167 11A8;B5D9;1104 1167 11A8; # (뗙; 뗙; 뗙; 뗙; 뗙; ) HANGUL SYLLABLE DDYEOG
+B5DA;B5DA;1104 1167 11A9;B5DA;1104 1167 11A9; # (뗚; 뗚; 뗚; 뗚; 뗚; ) HANGUL SYLLABLE DDYEOGG
+B5DB;B5DB;1104 1167 11AA;B5DB;1104 1167 11AA; # (뗛; 뗛; 뗛; 뗛; 뗛; ) HANGUL SYLLABLE DDYEOGS
+B5DC;B5DC;1104 1167 11AB;B5DC;1104 1167 11AB; # (뗜; 뗜; 뗜; 뗜; 뗜; ) HANGUL SYLLABLE DDYEON
+B5DD;B5DD;1104 1167 11AC;B5DD;1104 1167 11AC; # (ë—; ë—; 뗝; ë—; 뗝; ) HANGUL SYLLABLE DDYEONJ
+B5DE;B5DE;1104 1167 11AD;B5DE;1104 1167 11AD; # (뗞; 뗞; 뗞; 뗞; 뗞; ) HANGUL SYLLABLE DDYEONH
+B5DF;B5DF;1104 1167 11AE;B5DF;1104 1167 11AE; # (뗟; 뗟; 뗟; 뗟; 뗟; ) HANGUL SYLLABLE DDYEOD
+B5E0;B5E0;1104 1167 11AF;B5E0;1104 1167 11AF; # (뗠; 뗠; 뗠; 뗠; 뗠; ) HANGUL SYLLABLE DDYEOL
+B5E1;B5E1;1104 1167 11B0;B5E1;1104 1167 11B0; # (뗡; 뗡; 뗡; 뗡; 뗡; ) HANGUL SYLLABLE DDYEOLG
+B5E2;B5E2;1104 1167 11B1;B5E2;1104 1167 11B1; # (뗢; 뗢; 뗢; 뗢; 뗢; ) HANGUL SYLLABLE DDYEOLM
+B5E3;B5E3;1104 1167 11B2;B5E3;1104 1167 11B2; # (뗣; 뗣; 뗣; 뗣; 뗣; ) HANGUL SYLLABLE DDYEOLB
+B5E4;B5E4;1104 1167 11B3;B5E4;1104 1167 11B3; # (뗤; 뗤; 뗤; 뗤; 뗤; ) HANGUL SYLLABLE DDYEOLS
+B5E5;B5E5;1104 1167 11B4;B5E5;1104 1167 11B4; # (뗥; 뗥; 뗥; 뗥; 뗥; ) HANGUL SYLLABLE DDYEOLT
+B5E6;B5E6;1104 1167 11B5;B5E6;1104 1167 11B5; # (뗦; 뗦; 뗦; 뗦; 뗦; ) HANGUL SYLLABLE DDYEOLP
+B5E7;B5E7;1104 1167 11B6;B5E7;1104 1167 11B6; # (뗧; 뗧; 뗧; 뗧; 뗧; ) HANGUL SYLLABLE DDYEOLH
+B5E8;B5E8;1104 1167 11B7;B5E8;1104 1167 11B7; # (뗨; 뗨; 뗨; 뗨; 뗨; ) HANGUL SYLLABLE DDYEOM
+B5E9;B5E9;1104 1167 11B8;B5E9;1104 1167 11B8; # (뗩; 뗩; 뗩; 뗩; 뗩; ) HANGUL SYLLABLE DDYEOB
+B5EA;B5EA;1104 1167 11B9;B5EA;1104 1167 11B9; # (뗪; 뗪; 뗪; 뗪; 뗪; ) HANGUL SYLLABLE DDYEOBS
+B5EB;B5EB;1104 1167 11BA;B5EB;1104 1167 11BA; # (뗫; 뗫; 뗫; 뗫; 뗫; ) HANGUL SYLLABLE DDYEOS
+B5EC;B5EC;1104 1167 11BB;B5EC;1104 1167 11BB; # (뗬; 뗬; 뗬; 뗬; 뗬; ) HANGUL SYLLABLE DDYEOSS
+B5ED;B5ED;1104 1167 11BC;B5ED;1104 1167 11BC; # (뗭; 뗭; 뗭; 뗭; 뗭; ) HANGUL SYLLABLE DDYEONG
+B5EE;B5EE;1104 1167 11BD;B5EE;1104 1167 11BD; # (뗮; 뗮; 뗮; 뗮; 뗮; ) HANGUL SYLLABLE DDYEOJ
+B5EF;B5EF;1104 1167 11BE;B5EF;1104 1167 11BE; # (뗯; 뗯; 뗯; 뗯; 뗯; ) HANGUL SYLLABLE DDYEOC
+B5F0;B5F0;1104 1167 11BF;B5F0;1104 1167 11BF; # (뗰; 뗰; 뗰; 뗰; 뗰; ) HANGUL SYLLABLE DDYEOK
+B5F1;B5F1;1104 1167 11C0;B5F1;1104 1167 11C0; # (뗱; 뗱; 뗱; 뗱; 뗱; ) HANGUL SYLLABLE DDYEOT
+B5F2;B5F2;1104 1167 11C1;B5F2;1104 1167 11C1; # (ë—²; ë—²; á„„á…§á‡; ë—²; á„„á…§á‡; ) HANGUL SYLLABLE DDYEOP
+B5F3;B5F3;1104 1167 11C2;B5F3;1104 1167 11C2; # (뗳; 뗳; 뗳; 뗳; 뗳; ) HANGUL SYLLABLE DDYEOH
+B5F4;B5F4;1104 1168;B5F4;1104 1168; # (ë—´; ë—´; á„„á…¨; ë—´; á„„á…¨; ) HANGUL SYLLABLE DDYE
+B5F5;B5F5;1104 1168 11A8;B5F5;1104 1168 11A8; # (뗵; 뗵; 뗵; 뗵; 뗵; ) HANGUL SYLLABLE DDYEG
+B5F6;B5F6;1104 1168 11A9;B5F6;1104 1168 11A9; # (뗶; 뗶; 뗶; 뗶; 뗶; ) HANGUL SYLLABLE DDYEGG
+B5F7;B5F7;1104 1168 11AA;B5F7;1104 1168 11AA; # (뗷; 뗷; 뗷; 뗷; 뗷; ) HANGUL SYLLABLE DDYEGS
+B5F8;B5F8;1104 1168 11AB;B5F8;1104 1168 11AB; # (뗸; 뗸; 뗸; 뗸; 뗸; ) HANGUL SYLLABLE DDYEN
+B5F9;B5F9;1104 1168 11AC;B5F9;1104 1168 11AC; # (뗹; 뗹; 뗹; 뗹; 뗹; ) HANGUL SYLLABLE DDYENJ
+B5FA;B5FA;1104 1168 11AD;B5FA;1104 1168 11AD; # (뗺; 뗺; 뗺; 뗺; 뗺; ) HANGUL SYLLABLE DDYENH
+B5FB;B5FB;1104 1168 11AE;B5FB;1104 1168 11AE; # (뗻; 뗻; 뗻; 뗻; 뗻; ) HANGUL SYLLABLE DDYED
+B5FC;B5FC;1104 1168 11AF;B5FC;1104 1168 11AF; # (뗼; 뗼; 뗼; 뗼; 뗼; ) HANGUL SYLLABLE DDYEL
+B5FD;B5FD;1104 1168 11B0;B5FD;1104 1168 11B0; # (뗽; 뗽; 뗽; 뗽; 뗽; ) HANGUL SYLLABLE DDYELG
+B5FE;B5FE;1104 1168 11B1;B5FE;1104 1168 11B1; # (뗾; 뗾; 뗾; 뗾; 뗾; ) HANGUL SYLLABLE DDYELM
+B5FF;B5FF;1104 1168 11B2;B5FF;1104 1168 11B2; # (뗿; 뗿; 뗿; 뗿; 뗿; ) HANGUL SYLLABLE DDYELB
+B600;B600;1104 1168 11B3;B600;1104 1168 11B3; # (똀; 똀; 똀; 똀; 똀; ) HANGUL SYLLABLE DDYELS
+B601;B601;1104 1168 11B4;B601;1104 1168 11B4; # (ë˜; ë˜; 똁; ë˜; 똁; ) HANGUL SYLLABLE DDYELT
+B602;B602;1104 1168 11B5;B602;1104 1168 11B5; # (똂; 똂; 똂; 똂; 똂; ) HANGUL SYLLABLE DDYELP
+B603;B603;1104 1168 11B6;B603;1104 1168 11B6; # (똃; 똃; 똃; 똃; 똃; ) HANGUL SYLLABLE DDYELH
+B604;B604;1104 1168 11B7;B604;1104 1168 11B7; # (똄; 똄; 똄; 똄; 똄; ) HANGUL SYLLABLE DDYEM
+B605;B605;1104 1168 11B8;B605;1104 1168 11B8; # (똅; 똅; 똅; 똅; 똅; ) HANGUL SYLLABLE DDYEB
+B606;B606;1104 1168 11B9;B606;1104 1168 11B9; # (똆; 똆; 똆; 똆; 똆; ) HANGUL SYLLABLE DDYEBS
+B607;B607;1104 1168 11BA;B607;1104 1168 11BA; # (똇; 똇; 똇; 똇; 똇; ) HANGUL SYLLABLE DDYES
+B608;B608;1104 1168 11BB;B608;1104 1168 11BB; # (똈; 똈; 똈; 똈; 똈; ) HANGUL SYLLABLE DDYESS
+B609;B609;1104 1168 11BC;B609;1104 1168 11BC; # (똉; 똉; 똉; 똉; 똉; ) HANGUL SYLLABLE DDYENG
+B60A;B60A;1104 1168 11BD;B60A;1104 1168 11BD; # (똊; 똊; 똊; 똊; 똊; ) HANGUL SYLLABLE DDYEJ
+B60B;B60B;1104 1168 11BE;B60B;1104 1168 11BE; # (똋; 똋; 똋; 똋; 똋; ) HANGUL SYLLABLE DDYEC
+B60C;B60C;1104 1168 11BF;B60C;1104 1168 11BF; # (똌; 똌; 똌; 똌; 똌; ) HANGUL SYLLABLE DDYEK
+B60D;B60D;1104 1168 11C0;B60D;1104 1168 11C0; # (ë˜; ë˜; 똍; ë˜; 똍; ) HANGUL SYLLABLE DDYET
+B60E;B60E;1104 1168 11C1;B60E;1104 1168 11C1; # (똎; 똎; á„„á…¨á‡; 똎; á„„á…¨á‡; ) HANGUL SYLLABLE DDYEP
+B60F;B60F;1104 1168 11C2;B60F;1104 1168 11C2; # (ë˜; ë˜; 똏; ë˜; 똏; ) HANGUL SYLLABLE DDYEH
+B610;B610;1104 1169;B610;1104 1169; # (ë˜; ë˜; á„„á…©; ë˜; á„„á…©; ) HANGUL SYLLABLE DDO
+B611;B611;1104 1169 11A8;B611;1104 1169 11A8; # (똑; 똑; 똑; 똑; 똑; ) HANGUL SYLLABLE DDOG
+B612;B612;1104 1169 11A9;B612;1104 1169 11A9; # (똒; 똒; 똒; 똒; 똒; ) HANGUL SYLLABLE DDOGG
+B613;B613;1104 1169 11AA;B613;1104 1169 11AA; # (똓; 똓; 똓; 똓; 똓; ) HANGUL SYLLABLE DDOGS
+B614;B614;1104 1169 11AB;B614;1104 1169 11AB; # (똔; 똔; 똔; 똔; 똔; ) HANGUL SYLLABLE DDON
+B615;B615;1104 1169 11AC;B615;1104 1169 11AC; # (똕; 똕; 똕; 똕; 똕; ) HANGUL SYLLABLE DDONJ
+B616;B616;1104 1169 11AD;B616;1104 1169 11AD; # (똖; 똖; 똖; 똖; 똖; ) HANGUL SYLLABLE DDONH
+B617;B617;1104 1169 11AE;B617;1104 1169 11AE; # (똗; 똗; 똗; 똗; 똗; ) HANGUL SYLLABLE DDOD
+B618;B618;1104 1169 11AF;B618;1104 1169 11AF; # (똘; 똘; 똘; 똘; 똘; ) HANGUL SYLLABLE DDOL
+B619;B619;1104 1169 11B0;B619;1104 1169 11B0; # (똙; 똙; 똙; 똙; 똙; ) HANGUL SYLLABLE DDOLG
+B61A;B61A;1104 1169 11B1;B61A;1104 1169 11B1; # (똚; 똚; 똚; 똚; 똚; ) HANGUL SYLLABLE DDOLM
+B61B;B61B;1104 1169 11B2;B61B;1104 1169 11B2; # (똛; 똛; 똛; 똛; 똛; ) HANGUL SYLLABLE DDOLB
+B61C;B61C;1104 1169 11B3;B61C;1104 1169 11B3; # (똜; 똜; 똜; 똜; 똜; ) HANGUL SYLLABLE DDOLS
+B61D;B61D;1104 1169 11B4;B61D;1104 1169 11B4; # (ë˜; ë˜; 똝; ë˜; 똝; ) HANGUL SYLLABLE DDOLT
+B61E;B61E;1104 1169 11B5;B61E;1104 1169 11B5; # (똞; 똞; 똞; 똞; 똞; ) HANGUL SYLLABLE DDOLP
+B61F;B61F;1104 1169 11B6;B61F;1104 1169 11B6; # (똟; 똟; 똟; 똟; 똟; ) HANGUL SYLLABLE DDOLH
+B620;B620;1104 1169 11B7;B620;1104 1169 11B7; # (똠; 똠; 똠; 똠; 똠; ) HANGUL SYLLABLE DDOM
+B621;B621;1104 1169 11B8;B621;1104 1169 11B8; # (똡; 똡; 똡; 똡; 똡; ) HANGUL SYLLABLE DDOB
+B622;B622;1104 1169 11B9;B622;1104 1169 11B9; # (똢; 똢; 똢; 똢; 똢; ) HANGUL SYLLABLE DDOBS
+B623;B623;1104 1169 11BA;B623;1104 1169 11BA; # (똣; 똣; 똣; 똣; 똣; ) HANGUL SYLLABLE DDOS
+B624;B624;1104 1169 11BB;B624;1104 1169 11BB; # (똤; 똤; 똤; 똤; 똤; ) HANGUL SYLLABLE DDOSS
+B625;B625;1104 1169 11BC;B625;1104 1169 11BC; # (똥; 똥; 똥; 똥; 똥; ) HANGUL SYLLABLE DDONG
+B626;B626;1104 1169 11BD;B626;1104 1169 11BD; # (똦; 똦; 똦; 똦; 똦; ) HANGUL SYLLABLE DDOJ
+B627;B627;1104 1169 11BE;B627;1104 1169 11BE; # (똧; 똧; 똧; 똧; 똧; ) HANGUL SYLLABLE DDOC
+B628;B628;1104 1169 11BF;B628;1104 1169 11BF; # (똨; 똨; 똨; 똨; 똨; ) HANGUL SYLLABLE DDOK
+B629;B629;1104 1169 11C0;B629;1104 1169 11C0; # (똩; 똩; 똩; 똩; 똩; ) HANGUL SYLLABLE DDOT
+B62A;B62A;1104 1169 11C1;B62A;1104 1169 11C1; # (똪; 똪; á„„á…©á‡; 똪; á„„á…©á‡; ) HANGUL SYLLABLE DDOP
+B62B;B62B;1104 1169 11C2;B62B;1104 1169 11C2; # (똫; 똫; 똫; 똫; 똫; ) HANGUL SYLLABLE DDOH
+B62C;B62C;1104 116A;B62C;1104 116A; # (똬; 똬; 똬; 똬; 똬; ) HANGUL SYLLABLE DDWA
+B62D;B62D;1104 116A 11A8;B62D;1104 116A 11A8; # (똭; 똭; 똭; 똭; 똭; ) HANGUL SYLLABLE DDWAG
+B62E;B62E;1104 116A 11A9;B62E;1104 116A 11A9; # (똮; 똮; 똮; 똮; 똮; ) HANGUL SYLLABLE DDWAGG
+B62F;B62F;1104 116A 11AA;B62F;1104 116A 11AA; # (똯; 똯; 똯; 똯; 똯; ) HANGUL SYLLABLE DDWAGS
+B630;B630;1104 116A 11AB;B630;1104 116A 11AB; # (똰; 똰; 똰; 똰; 똰; ) HANGUL SYLLABLE DDWAN
+B631;B631;1104 116A 11AC;B631;1104 116A 11AC; # (똱; 똱; 똱; 똱; 똱; ) HANGUL SYLLABLE DDWANJ
+B632;B632;1104 116A 11AD;B632;1104 116A 11AD; # (똲; 똲; 똲; 똲; 똲; ) HANGUL SYLLABLE DDWANH
+B633;B633;1104 116A 11AE;B633;1104 116A 11AE; # (똳; 똳; 똳; 똳; 똳; ) HANGUL SYLLABLE DDWAD
+B634;B634;1104 116A 11AF;B634;1104 116A 11AF; # (똴; 똴; 똴; 똴; 똴; ) HANGUL SYLLABLE DDWAL
+B635;B635;1104 116A 11B0;B635;1104 116A 11B0; # (똵; 똵; 똵; 똵; 똵; ) HANGUL SYLLABLE DDWALG
+B636;B636;1104 116A 11B1;B636;1104 116A 11B1; # (똶; 똶; 똶; 똶; 똶; ) HANGUL SYLLABLE DDWALM
+B637;B637;1104 116A 11B2;B637;1104 116A 11B2; # (똷; 똷; 똷; 똷; 똷; ) HANGUL SYLLABLE DDWALB
+B638;B638;1104 116A 11B3;B638;1104 116A 11B3; # (똸; 똸; 똸; 똸; 똸; ) HANGUL SYLLABLE DDWALS
+B639;B639;1104 116A 11B4;B639;1104 116A 11B4; # (똹; 똹; 똹; 똹; 똹; ) HANGUL SYLLABLE DDWALT
+B63A;B63A;1104 116A 11B5;B63A;1104 116A 11B5; # (똺; 똺; 똺; 똺; 똺; ) HANGUL SYLLABLE DDWALP
+B63B;B63B;1104 116A 11B6;B63B;1104 116A 11B6; # (똻; 똻; 똻; 똻; 똻; ) HANGUL SYLLABLE DDWALH
+B63C;B63C;1104 116A 11B7;B63C;1104 116A 11B7; # (똼; 똼; 똼; 똼; 똼; ) HANGUL SYLLABLE DDWAM
+B63D;B63D;1104 116A 11B8;B63D;1104 116A 11B8; # (똽; 똽; 똽; 똽; 똽; ) HANGUL SYLLABLE DDWAB
+B63E;B63E;1104 116A 11B9;B63E;1104 116A 11B9; # (똾; 똾; 똾; 똾; 똾; ) HANGUL SYLLABLE DDWABS
+B63F;B63F;1104 116A 11BA;B63F;1104 116A 11BA; # (똿; 똿; 똿; 똿; 똿; ) HANGUL SYLLABLE DDWAS
+B640;B640;1104 116A 11BB;B640;1104 116A 11BB; # (뙀; 뙀; 뙀; 뙀; 뙀; ) HANGUL SYLLABLE DDWASS
+B641;B641;1104 116A 11BC;B641;1104 116A 11BC; # (ë™; ë™; 뙁; ë™; 뙁; ) HANGUL SYLLABLE DDWANG
+B642;B642;1104 116A 11BD;B642;1104 116A 11BD; # (뙂; 뙂; 뙂; 뙂; 뙂; ) HANGUL SYLLABLE DDWAJ
+B643;B643;1104 116A 11BE;B643;1104 116A 11BE; # (뙃; 뙃; 뙃; 뙃; 뙃; ) HANGUL SYLLABLE DDWAC
+B644;B644;1104 116A 11BF;B644;1104 116A 11BF; # (뙄; 뙄; 뙄; 뙄; 뙄; ) HANGUL SYLLABLE DDWAK
+B645;B645;1104 116A 11C0;B645;1104 116A 11C0; # (뙅; 뙅; 뙅; 뙅; 뙅; ) HANGUL SYLLABLE DDWAT
+B646;B646;1104 116A 11C1;B646;1104 116A 11C1; # (뙆; 뙆; á„„á…ªá‡; 뙆; á„„á…ªá‡; ) HANGUL SYLLABLE DDWAP
+B647;B647;1104 116A 11C2;B647;1104 116A 11C2; # (뙇; 뙇; 뙇; 뙇; 뙇; ) HANGUL SYLLABLE DDWAH
+B648;B648;1104 116B;B648;1104 116B; # (뙈; 뙈; 뙈; 뙈; 뙈; ) HANGUL SYLLABLE DDWAE
+B649;B649;1104 116B 11A8;B649;1104 116B 11A8; # (뙉; 뙉; 뙉; 뙉; 뙉; ) HANGUL SYLLABLE DDWAEG
+B64A;B64A;1104 116B 11A9;B64A;1104 116B 11A9; # (뙊; 뙊; 뙊; 뙊; 뙊; ) HANGUL SYLLABLE DDWAEGG
+B64B;B64B;1104 116B 11AA;B64B;1104 116B 11AA; # (뙋; 뙋; 뙋; 뙋; 뙋; ) HANGUL SYLLABLE DDWAEGS
+B64C;B64C;1104 116B 11AB;B64C;1104 116B 11AB; # (뙌; 뙌; 뙌; 뙌; 뙌; ) HANGUL SYLLABLE DDWAEN
+B64D;B64D;1104 116B 11AC;B64D;1104 116B 11AC; # (ë™; ë™; 뙍; ë™; 뙍; ) HANGUL SYLLABLE DDWAENJ
+B64E;B64E;1104 116B 11AD;B64E;1104 116B 11AD; # (뙎; 뙎; 뙎; 뙎; 뙎; ) HANGUL SYLLABLE DDWAENH
+B64F;B64F;1104 116B 11AE;B64F;1104 116B 11AE; # (ë™; ë™; 뙏; ë™; 뙏; ) HANGUL SYLLABLE DDWAED
+B650;B650;1104 116B 11AF;B650;1104 116B 11AF; # (ë™; ë™; 뙐; ë™; 뙐; ) HANGUL SYLLABLE DDWAEL
+B651;B651;1104 116B 11B0;B651;1104 116B 11B0; # (뙑; 뙑; 뙑; 뙑; 뙑; ) HANGUL SYLLABLE DDWAELG
+B652;B652;1104 116B 11B1;B652;1104 116B 11B1; # (뙒; 뙒; 뙒; 뙒; 뙒; ) HANGUL SYLLABLE DDWAELM
+B653;B653;1104 116B 11B2;B653;1104 116B 11B2; # (뙓; 뙓; 뙓; 뙓; 뙓; ) HANGUL SYLLABLE DDWAELB
+B654;B654;1104 116B 11B3;B654;1104 116B 11B3; # (뙔; 뙔; 뙔; 뙔; 뙔; ) HANGUL SYLLABLE DDWAELS
+B655;B655;1104 116B 11B4;B655;1104 116B 11B4; # (뙕; 뙕; 뙕; 뙕; 뙕; ) HANGUL SYLLABLE DDWAELT
+B656;B656;1104 116B 11B5;B656;1104 116B 11B5; # (뙖; 뙖; 뙖; 뙖; 뙖; ) HANGUL SYLLABLE DDWAELP
+B657;B657;1104 116B 11B6;B657;1104 116B 11B6; # (뙗; 뙗; 뙗; 뙗; 뙗; ) HANGUL SYLLABLE DDWAELH
+B658;B658;1104 116B 11B7;B658;1104 116B 11B7; # (뙘; 뙘; 뙘; 뙘; 뙘; ) HANGUL SYLLABLE DDWAEM
+B659;B659;1104 116B 11B8;B659;1104 116B 11B8; # (뙙; 뙙; 뙙; 뙙; 뙙; ) HANGUL SYLLABLE DDWAEB
+B65A;B65A;1104 116B 11B9;B65A;1104 116B 11B9; # (뙚; 뙚; 뙚; 뙚; 뙚; ) HANGUL SYLLABLE DDWAEBS
+B65B;B65B;1104 116B 11BA;B65B;1104 116B 11BA; # (뙛; 뙛; 뙛; 뙛; 뙛; ) HANGUL SYLLABLE DDWAES
+B65C;B65C;1104 116B 11BB;B65C;1104 116B 11BB; # (뙜; 뙜; 뙜; 뙜; 뙜; ) HANGUL SYLLABLE DDWAESS
+B65D;B65D;1104 116B 11BC;B65D;1104 116B 11BC; # (ë™; ë™; 뙝; ë™; 뙝; ) HANGUL SYLLABLE DDWAENG
+B65E;B65E;1104 116B 11BD;B65E;1104 116B 11BD; # (뙞; 뙞; 뙞; 뙞; 뙞; ) HANGUL SYLLABLE DDWAEJ
+B65F;B65F;1104 116B 11BE;B65F;1104 116B 11BE; # (뙟; 뙟; 뙟; 뙟; 뙟; ) HANGUL SYLLABLE DDWAEC
+B660;B660;1104 116B 11BF;B660;1104 116B 11BF; # (뙠; 뙠; 뙠; 뙠; 뙠; ) HANGUL SYLLABLE DDWAEK
+B661;B661;1104 116B 11C0;B661;1104 116B 11C0; # (뙡; 뙡; 뙡; 뙡; 뙡; ) HANGUL SYLLABLE DDWAET
+B662;B662;1104 116B 11C1;B662;1104 116B 11C1; # (뙢; 뙢; á„„á…«á‡; 뙢; á„„á…«á‡; ) HANGUL SYLLABLE DDWAEP
+B663;B663;1104 116B 11C2;B663;1104 116B 11C2; # (뙣; 뙣; 뙣; 뙣; 뙣; ) HANGUL SYLLABLE DDWAEH
+B664;B664;1104 116C;B664;1104 116C; # (뙤; 뙤; 뙤; 뙤; 뙤; ) HANGUL SYLLABLE DDOE
+B665;B665;1104 116C 11A8;B665;1104 116C 11A8; # (뙥; 뙥; 뙥; 뙥; 뙥; ) HANGUL SYLLABLE DDOEG
+B666;B666;1104 116C 11A9;B666;1104 116C 11A9; # (뙦; 뙦; 뙦; 뙦; 뙦; ) HANGUL SYLLABLE DDOEGG
+B667;B667;1104 116C 11AA;B667;1104 116C 11AA; # (뙧; 뙧; 뙧; 뙧; 뙧; ) HANGUL SYLLABLE DDOEGS
+B668;B668;1104 116C 11AB;B668;1104 116C 11AB; # (뙨; 뙨; 뙨; 뙨; 뙨; ) HANGUL SYLLABLE DDOEN
+B669;B669;1104 116C 11AC;B669;1104 116C 11AC; # (뙩; 뙩; 뙩; 뙩; 뙩; ) HANGUL SYLLABLE DDOENJ
+B66A;B66A;1104 116C 11AD;B66A;1104 116C 11AD; # (뙪; 뙪; 뙪; 뙪; 뙪; ) HANGUL SYLLABLE DDOENH
+B66B;B66B;1104 116C 11AE;B66B;1104 116C 11AE; # (뙫; 뙫; 뙫; 뙫; 뙫; ) HANGUL SYLLABLE DDOED
+B66C;B66C;1104 116C 11AF;B66C;1104 116C 11AF; # (뙬; 뙬; 뙬; 뙬; 뙬; ) HANGUL SYLLABLE DDOEL
+B66D;B66D;1104 116C 11B0;B66D;1104 116C 11B0; # (뙭; 뙭; 뙭; 뙭; 뙭; ) HANGUL SYLLABLE DDOELG
+B66E;B66E;1104 116C 11B1;B66E;1104 116C 11B1; # (뙮; 뙮; 뙮; 뙮; 뙮; ) HANGUL SYLLABLE DDOELM
+B66F;B66F;1104 116C 11B2;B66F;1104 116C 11B2; # (뙯; 뙯; 뙯; 뙯; 뙯; ) HANGUL SYLLABLE DDOELB
+B670;B670;1104 116C 11B3;B670;1104 116C 11B3; # (뙰; 뙰; 뙰; 뙰; 뙰; ) HANGUL SYLLABLE DDOELS
+B671;B671;1104 116C 11B4;B671;1104 116C 11B4; # (뙱; 뙱; 뙱; 뙱; 뙱; ) HANGUL SYLLABLE DDOELT
+B672;B672;1104 116C 11B5;B672;1104 116C 11B5; # (뙲; 뙲; 뙲; 뙲; 뙲; ) HANGUL SYLLABLE DDOELP
+B673;B673;1104 116C 11B6;B673;1104 116C 11B6; # (뙳; 뙳; 뙳; 뙳; 뙳; ) HANGUL SYLLABLE DDOELH
+B674;B674;1104 116C 11B7;B674;1104 116C 11B7; # (뙴; 뙴; 뙴; 뙴; 뙴; ) HANGUL SYLLABLE DDOEM
+B675;B675;1104 116C 11B8;B675;1104 116C 11B8; # (뙵; 뙵; 뙵; 뙵; 뙵; ) HANGUL SYLLABLE DDOEB
+B676;B676;1104 116C 11B9;B676;1104 116C 11B9; # (뙶; 뙶; 뙶; 뙶; 뙶; ) HANGUL SYLLABLE DDOEBS
+B677;B677;1104 116C 11BA;B677;1104 116C 11BA; # (뙷; 뙷; 뙷; 뙷; 뙷; ) HANGUL SYLLABLE DDOES
+B678;B678;1104 116C 11BB;B678;1104 116C 11BB; # (뙸; 뙸; 뙸; 뙸; 뙸; ) HANGUL SYLLABLE DDOESS
+B679;B679;1104 116C 11BC;B679;1104 116C 11BC; # (뙹; 뙹; 뙹; 뙹; 뙹; ) HANGUL SYLLABLE DDOENG
+B67A;B67A;1104 116C 11BD;B67A;1104 116C 11BD; # (뙺; 뙺; 뙺; 뙺; 뙺; ) HANGUL SYLLABLE DDOEJ
+B67B;B67B;1104 116C 11BE;B67B;1104 116C 11BE; # (뙻; 뙻; 뙻; 뙻; 뙻; ) HANGUL SYLLABLE DDOEC
+B67C;B67C;1104 116C 11BF;B67C;1104 116C 11BF; # (뙼; 뙼; 뙼; 뙼; 뙼; ) HANGUL SYLLABLE DDOEK
+B67D;B67D;1104 116C 11C0;B67D;1104 116C 11C0; # (뙽; 뙽; 뙽; 뙽; 뙽; ) HANGUL SYLLABLE DDOET
+B67E;B67E;1104 116C 11C1;B67E;1104 116C 11C1; # (뙾; 뙾; á„„á…¬á‡; 뙾; á„„á…¬á‡; ) HANGUL SYLLABLE DDOEP
+B67F;B67F;1104 116C 11C2;B67F;1104 116C 11C2; # (뙿; 뙿; 뙿; 뙿; 뙿; ) HANGUL SYLLABLE DDOEH
+B680;B680;1104 116D;B680;1104 116D; # (뚀; 뚀; 뚀; 뚀; 뚀; ) HANGUL SYLLABLE DDYO
+B681;B681;1104 116D 11A8;B681;1104 116D 11A8; # (ëš; ëš; 뚁; ëš; 뚁; ) HANGUL SYLLABLE DDYOG
+B682;B682;1104 116D 11A9;B682;1104 116D 11A9; # (뚂; 뚂; 뚂; 뚂; 뚂; ) HANGUL SYLLABLE DDYOGG
+B683;B683;1104 116D 11AA;B683;1104 116D 11AA; # (뚃; 뚃; 뚃; 뚃; 뚃; ) HANGUL SYLLABLE DDYOGS
+B684;B684;1104 116D 11AB;B684;1104 116D 11AB; # (뚄; 뚄; 뚄; 뚄; 뚄; ) HANGUL SYLLABLE DDYON
+B685;B685;1104 116D 11AC;B685;1104 116D 11AC; # (뚅; 뚅; 뚅; 뚅; 뚅; ) HANGUL SYLLABLE DDYONJ
+B686;B686;1104 116D 11AD;B686;1104 116D 11AD; # (뚆; 뚆; 뚆; 뚆; 뚆; ) HANGUL SYLLABLE DDYONH
+B687;B687;1104 116D 11AE;B687;1104 116D 11AE; # (뚇; 뚇; 뚇; 뚇; 뚇; ) HANGUL SYLLABLE DDYOD
+B688;B688;1104 116D 11AF;B688;1104 116D 11AF; # (뚈; 뚈; 뚈; 뚈; 뚈; ) HANGUL SYLLABLE DDYOL
+B689;B689;1104 116D 11B0;B689;1104 116D 11B0; # (뚉; 뚉; 뚉; 뚉; 뚉; ) HANGUL SYLLABLE DDYOLG
+B68A;B68A;1104 116D 11B1;B68A;1104 116D 11B1; # (뚊; 뚊; 뚊; 뚊; 뚊; ) HANGUL SYLLABLE DDYOLM
+B68B;B68B;1104 116D 11B2;B68B;1104 116D 11B2; # (뚋; 뚋; 뚋; 뚋; 뚋; ) HANGUL SYLLABLE DDYOLB
+B68C;B68C;1104 116D 11B3;B68C;1104 116D 11B3; # (뚌; 뚌; 뚌; 뚌; 뚌; ) HANGUL SYLLABLE DDYOLS
+B68D;B68D;1104 116D 11B4;B68D;1104 116D 11B4; # (ëš; ëš; 뚍; ëš; 뚍; ) HANGUL SYLLABLE DDYOLT
+B68E;B68E;1104 116D 11B5;B68E;1104 116D 11B5; # (뚎; 뚎; 뚎; 뚎; 뚎; ) HANGUL SYLLABLE DDYOLP
+B68F;B68F;1104 116D 11B6;B68F;1104 116D 11B6; # (ëš; ëš; 뚏; ëš; 뚏; ) HANGUL SYLLABLE DDYOLH
+B690;B690;1104 116D 11B7;B690;1104 116D 11B7; # (ëš; ëš; 뚐; ëš; 뚐; ) HANGUL SYLLABLE DDYOM
+B691;B691;1104 116D 11B8;B691;1104 116D 11B8; # (뚑; 뚑; 뚑; 뚑; 뚑; ) HANGUL SYLLABLE DDYOB
+B692;B692;1104 116D 11B9;B692;1104 116D 11B9; # (뚒; 뚒; 뚒; 뚒; 뚒; ) HANGUL SYLLABLE DDYOBS
+B693;B693;1104 116D 11BA;B693;1104 116D 11BA; # (뚓; 뚓; 뚓; 뚓; 뚓; ) HANGUL SYLLABLE DDYOS
+B694;B694;1104 116D 11BB;B694;1104 116D 11BB; # (뚔; 뚔; 뚔; 뚔; 뚔; ) HANGUL SYLLABLE DDYOSS
+B695;B695;1104 116D 11BC;B695;1104 116D 11BC; # (뚕; 뚕; 뚕; 뚕; 뚕; ) HANGUL SYLLABLE DDYONG
+B696;B696;1104 116D 11BD;B696;1104 116D 11BD; # (뚖; 뚖; 뚖; 뚖; 뚖; ) HANGUL SYLLABLE DDYOJ
+B697;B697;1104 116D 11BE;B697;1104 116D 11BE; # (뚗; 뚗; 뚗; 뚗; 뚗; ) HANGUL SYLLABLE DDYOC
+B698;B698;1104 116D 11BF;B698;1104 116D 11BF; # (뚘; 뚘; 뚘; 뚘; 뚘; ) HANGUL SYLLABLE DDYOK
+B699;B699;1104 116D 11C0;B699;1104 116D 11C0; # (뚙; 뚙; 뚙; 뚙; 뚙; ) HANGUL SYLLABLE DDYOT
+B69A;B69A;1104 116D 11C1;B69A;1104 116D 11C1; # (ëšš; ëšš; á„„á…­á‡; ëšš; á„„á…­á‡; ) HANGUL SYLLABLE DDYOP
+B69B;B69B;1104 116D 11C2;B69B;1104 116D 11C2; # (뚛; 뚛; 뚛; 뚛; 뚛; ) HANGUL SYLLABLE DDYOH
+B69C;B69C;1104 116E;B69C;1104 116E; # (뚜; 뚜; 뚜; 뚜; 뚜; ) HANGUL SYLLABLE DDU
+B69D;B69D;1104 116E 11A8;B69D;1104 116E 11A8; # (ëš; ëš; 뚝; ëš; 뚝; ) HANGUL SYLLABLE DDUG
+B69E;B69E;1104 116E 11A9;B69E;1104 116E 11A9; # (뚞; 뚞; 뚞; 뚞; 뚞; ) HANGUL SYLLABLE DDUGG
+B69F;B69F;1104 116E 11AA;B69F;1104 116E 11AA; # (뚟; 뚟; 뚟; 뚟; 뚟; ) HANGUL SYLLABLE DDUGS
+B6A0;B6A0;1104 116E 11AB;B6A0;1104 116E 11AB; # (뚠; 뚠; 뚠; 뚠; 뚠; ) HANGUL SYLLABLE DDUN
+B6A1;B6A1;1104 116E 11AC;B6A1;1104 116E 11AC; # (뚡; 뚡; 뚡; 뚡; 뚡; ) HANGUL SYLLABLE DDUNJ
+B6A2;B6A2;1104 116E 11AD;B6A2;1104 116E 11AD; # (뚢; 뚢; 뚢; 뚢; 뚢; ) HANGUL SYLLABLE DDUNH
+B6A3;B6A3;1104 116E 11AE;B6A3;1104 116E 11AE; # (뚣; 뚣; 뚣; 뚣; 뚣; ) HANGUL SYLLABLE DDUD
+B6A4;B6A4;1104 116E 11AF;B6A4;1104 116E 11AF; # (뚤; 뚤; 뚤; 뚤; 뚤; ) HANGUL SYLLABLE DDUL
+B6A5;B6A5;1104 116E 11B0;B6A5;1104 116E 11B0; # (뚥; 뚥; 뚥; 뚥; 뚥; ) HANGUL SYLLABLE DDULG
+B6A6;B6A6;1104 116E 11B1;B6A6;1104 116E 11B1; # (뚦; 뚦; 뚦; 뚦; 뚦; ) HANGUL SYLLABLE DDULM
+B6A7;B6A7;1104 116E 11B2;B6A7;1104 116E 11B2; # (뚧; 뚧; 뚧; 뚧; 뚧; ) HANGUL SYLLABLE DDULB
+B6A8;B6A8;1104 116E 11B3;B6A8;1104 116E 11B3; # (뚨; 뚨; 뚨; 뚨; 뚨; ) HANGUL SYLLABLE DDULS
+B6A9;B6A9;1104 116E 11B4;B6A9;1104 116E 11B4; # (뚩; 뚩; 뚩; 뚩; 뚩; ) HANGUL SYLLABLE DDULT
+B6AA;B6AA;1104 116E 11B5;B6AA;1104 116E 11B5; # (뚪; 뚪; 뚪; 뚪; 뚪; ) HANGUL SYLLABLE DDULP
+B6AB;B6AB;1104 116E 11B6;B6AB;1104 116E 11B6; # (뚫; 뚫; 뚫; 뚫; 뚫; ) HANGUL SYLLABLE DDULH
+B6AC;B6AC;1104 116E 11B7;B6AC;1104 116E 11B7; # (뚬; 뚬; 뚬; 뚬; 뚬; ) HANGUL SYLLABLE DDUM
+B6AD;B6AD;1104 116E 11B8;B6AD;1104 116E 11B8; # (뚭; 뚭; 뚭; 뚭; 뚭; ) HANGUL SYLLABLE DDUB
+B6AE;B6AE;1104 116E 11B9;B6AE;1104 116E 11B9; # (뚮; 뚮; 뚮; 뚮; 뚮; ) HANGUL SYLLABLE DDUBS
+B6AF;B6AF;1104 116E 11BA;B6AF;1104 116E 11BA; # (뚯; 뚯; 뚯; 뚯; 뚯; ) HANGUL SYLLABLE DDUS
+B6B0;B6B0;1104 116E 11BB;B6B0;1104 116E 11BB; # (뚰; 뚰; 뚰; 뚰; 뚰; ) HANGUL SYLLABLE DDUSS
+B6B1;B6B1;1104 116E 11BC;B6B1;1104 116E 11BC; # (뚱; 뚱; 뚱; 뚱; 뚱; ) HANGUL SYLLABLE DDUNG
+B6B2;B6B2;1104 116E 11BD;B6B2;1104 116E 11BD; # (뚲; 뚲; 뚲; 뚲; 뚲; ) HANGUL SYLLABLE DDUJ
+B6B3;B6B3;1104 116E 11BE;B6B3;1104 116E 11BE; # (뚳; 뚳; 뚳; 뚳; 뚳; ) HANGUL SYLLABLE DDUC
+B6B4;B6B4;1104 116E 11BF;B6B4;1104 116E 11BF; # (뚴; 뚴; 뚴; 뚴; 뚴; ) HANGUL SYLLABLE DDUK
+B6B5;B6B5;1104 116E 11C0;B6B5;1104 116E 11C0; # (뚵; 뚵; 뚵; 뚵; 뚵; ) HANGUL SYLLABLE DDUT
+B6B6;B6B6;1104 116E 11C1;B6B6;1104 116E 11C1; # (뚶; 뚶; á„„á…®á‡; 뚶; á„„á…®á‡; ) HANGUL SYLLABLE DDUP
+B6B7;B6B7;1104 116E 11C2;B6B7;1104 116E 11C2; # (뚷; 뚷; 뚷; 뚷; 뚷; ) HANGUL SYLLABLE DDUH
+B6B8;B6B8;1104 116F;B6B8;1104 116F; # (뚸; 뚸; 뚸; 뚸; 뚸; ) HANGUL SYLLABLE DDWEO
+B6B9;B6B9;1104 116F 11A8;B6B9;1104 116F 11A8; # (뚹; 뚹; 뚹; 뚹; 뚹; ) HANGUL SYLLABLE DDWEOG
+B6BA;B6BA;1104 116F 11A9;B6BA;1104 116F 11A9; # (뚺; 뚺; 뚺; 뚺; 뚺; ) HANGUL SYLLABLE DDWEOGG
+B6BB;B6BB;1104 116F 11AA;B6BB;1104 116F 11AA; # (뚻; 뚻; 뚻; 뚻; 뚻; ) HANGUL SYLLABLE DDWEOGS
+B6BC;B6BC;1104 116F 11AB;B6BC;1104 116F 11AB; # (뚼; 뚼; 뚼; 뚼; 뚼; ) HANGUL SYLLABLE DDWEON
+B6BD;B6BD;1104 116F 11AC;B6BD;1104 116F 11AC; # (뚽; 뚽; 뚽; 뚽; 뚽; ) HANGUL SYLLABLE DDWEONJ
+B6BE;B6BE;1104 116F 11AD;B6BE;1104 116F 11AD; # (뚾; 뚾; 뚾; 뚾; 뚾; ) HANGUL SYLLABLE DDWEONH
+B6BF;B6BF;1104 116F 11AE;B6BF;1104 116F 11AE; # (뚿; 뚿; 뚿; 뚿; 뚿; ) HANGUL SYLLABLE DDWEOD
+B6C0;B6C0;1104 116F 11AF;B6C0;1104 116F 11AF; # (뛀; 뛀; 뛀; 뛀; 뛀; ) HANGUL SYLLABLE DDWEOL
+B6C1;B6C1;1104 116F 11B0;B6C1;1104 116F 11B0; # (ë›; ë›; 뛁; ë›; 뛁; ) HANGUL SYLLABLE DDWEOLG
+B6C2;B6C2;1104 116F 11B1;B6C2;1104 116F 11B1; # (뛂; 뛂; 뛂; 뛂; 뛂; ) HANGUL SYLLABLE DDWEOLM
+B6C3;B6C3;1104 116F 11B2;B6C3;1104 116F 11B2; # (뛃; 뛃; 뛃; 뛃; 뛃; ) HANGUL SYLLABLE DDWEOLB
+B6C4;B6C4;1104 116F 11B3;B6C4;1104 116F 11B3; # (뛄; 뛄; 뛄; 뛄; 뛄; ) HANGUL SYLLABLE DDWEOLS
+B6C5;B6C5;1104 116F 11B4;B6C5;1104 116F 11B4; # (뛅; 뛅; 뛅; 뛅; 뛅; ) HANGUL SYLLABLE DDWEOLT
+B6C6;B6C6;1104 116F 11B5;B6C6;1104 116F 11B5; # (뛆; 뛆; 뛆; 뛆; 뛆; ) HANGUL SYLLABLE DDWEOLP
+B6C7;B6C7;1104 116F 11B6;B6C7;1104 116F 11B6; # (뛇; 뛇; 뛇; 뛇; 뛇; ) HANGUL SYLLABLE DDWEOLH
+B6C8;B6C8;1104 116F 11B7;B6C8;1104 116F 11B7; # (뛈; 뛈; 뛈; 뛈; 뛈; ) HANGUL SYLLABLE DDWEOM
+B6C9;B6C9;1104 116F 11B8;B6C9;1104 116F 11B8; # (뛉; 뛉; 뛉; 뛉; 뛉; ) HANGUL SYLLABLE DDWEOB
+B6CA;B6CA;1104 116F 11B9;B6CA;1104 116F 11B9; # (뛊; 뛊; 뛊; 뛊; 뛊; ) HANGUL SYLLABLE DDWEOBS
+B6CB;B6CB;1104 116F 11BA;B6CB;1104 116F 11BA; # (뛋; 뛋; 뛋; 뛋; 뛋; ) HANGUL SYLLABLE DDWEOS
+B6CC;B6CC;1104 116F 11BB;B6CC;1104 116F 11BB; # (뛌; 뛌; 뛌; 뛌; 뛌; ) HANGUL SYLLABLE DDWEOSS
+B6CD;B6CD;1104 116F 11BC;B6CD;1104 116F 11BC; # (ë›; ë›; 뛍; ë›; 뛍; ) HANGUL SYLLABLE DDWEONG
+B6CE;B6CE;1104 116F 11BD;B6CE;1104 116F 11BD; # (뛎; 뛎; 뛎; 뛎; 뛎; ) HANGUL SYLLABLE DDWEOJ
+B6CF;B6CF;1104 116F 11BE;B6CF;1104 116F 11BE; # (ë›; ë›; 뛏; ë›; 뛏; ) HANGUL SYLLABLE DDWEOC
+B6D0;B6D0;1104 116F 11BF;B6D0;1104 116F 11BF; # (ë›; ë›; 뛐; ë›; 뛐; ) HANGUL SYLLABLE DDWEOK
+B6D1;B6D1;1104 116F 11C0;B6D1;1104 116F 11C0; # (뛑; 뛑; 뛑; 뛑; 뛑; ) HANGUL SYLLABLE DDWEOT
+B6D2;B6D2;1104 116F 11C1;B6D2;1104 116F 11C1; # (ë›’; ë›’; á„„á…¯á‡; ë›’; á„„á…¯á‡; ) HANGUL SYLLABLE DDWEOP
+B6D3;B6D3;1104 116F 11C2;B6D3;1104 116F 11C2; # (뛓; 뛓; 뛓; 뛓; 뛓; ) HANGUL SYLLABLE DDWEOH
+B6D4;B6D4;1104 1170;B6D4;1104 1170; # (ë›”; ë›”; á„„á…°; ë›”; á„„á…°; ) HANGUL SYLLABLE DDWE
+B6D5;B6D5;1104 1170 11A8;B6D5;1104 1170 11A8; # (뛕; 뛕; 뛕; 뛕; 뛕; ) HANGUL SYLLABLE DDWEG
+B6D6;B6D6;1104 1170 11A9;B6D6;1104 1170 11A9; # (뛖; 뛖; 뛖; 뛖; 뛖; ) HANGUL SYLLABLE DDWEGG
+B6D7;B6D7;1104 1170 11AA;B6D7;1104 1170 11AA; # (뛗; 뛗; 뛗; 뛗; 뛗; ) HANGUL SYLLABLE DDWEGS
+B6D8;B6D8;1104 1170 11AB;B6D8;1104 1170 11AB; # (뛘; 뛘; 뛘; 뛘; 뛘; ) HANGUL SYLLABLE DDWEN
+B6D9;B6D9;1104 1170 11AC;B6D9;1104 1170 11AC; # (뛙; 뛙; 뛙; 뛙; 뛙; ) HANGUL SYLLABLE DDWENJ
+B6DA;B6DA;1104 1170 11AD;B6DA;1104 1170 11AD; # (뛚; 뛚; 뛚; 뛚; 뛚; ) HANGUL SYLLABLE DDWENH
+B6DB;B6DB;1104 1170 11AE;B6DB;1104 1170 11AE; # (뛛; 뛛; 뛛; 뛛; 뛛; ) HANGUL SYLLABLE DDWED
+B6DC;B6DC;1104 1170 11AF;B6DC;1104 1170 11AF; # (뛜; 뛜; 뛜; 뛜; 뛜; ) HANGUL SYLLABLE DDWEL
+B6DD;B6DD;1104 1170 11B0;B6DD;1104 1170 11B0; # (ë›; ë›; 뛝; ë›; 뛝; ) HANGUL SYLLABLE DDWELG
+B6DE;B6DE;1104 1170 11B1;B6DE;1104 1170 11B1; # (뛞; 뛞; 뛞; 뛞; 뛞; ) HANGUL SYLLABLE DDWELM
+B6DF;B6DF;1104 1170 11B2;B6DF;1104 1170 11B2; # (뛟; 뛟; 뛟; 뛟; 뛟; ) HANGUL SYLLABLE DDWELB
+B6E0;B6E0;1104 1170 11B3;B6E0;1104 1170 11B3; # (뛠; 뛠; 뛠; 뛠; 뛠; ) HANGUL SYLLABLE DDWELS
+B6E1;B6E1;1104 1170 11B4;B6E1;1104 1170 11B4; # (뛡; 뛡; 뛡; 뛡; 뛡; ) HANGUL SYLLABLE DDWELT
+B6E2;B6E2;1104 1170 11B5;B6E2;1104 1170 11B5; # (뛢; 뛢; 뛢; 뛢; 뛢; ) HANGUL SYLLABLE DDWELP
+B6E3;B6E3;1104 1170 11B6;B6E3;1104 1170 11B6; # (뛣; 뛣; 뛣; 뛣; 뛣; ) HANGUL SYLLABLE DDWELH
+B6E4;B6E4;1104 1170 11B7;B6E4;1104 1170 11B7; # (뛤; 뛤; 뛤; 뛤; 뛤; ) HANGUL SYLLABLE DDWEM
+B6E5;B6E5;1104 1170 11B8;B6E5;1104 1170 11B8; # (뛥; 뛥; 뛥; 뛥; 뛥; ) HANGUL SYLLABLE DDWEB
+B6E6;B6E6;1104 1170 11B9;B6E6;1104 1170 11B9; # (뛦; 뛦; 뛦; 뛦; 뛦; ) HANGUL SYLLABLE DDWEBS
+B6E7;B6E7;1104 1170 11BA;B6E7;1104 1170 11BA; # (뛧; 뛧; 뛧; 뛧; 뛧; ) HANGUL SYLLABLE DDWES
+B6E8;B6E8;1104 1170 11BB;B6E8;1104 1170 11BB; # (뛨; 뛨; 뛨; 뛨; 뛨; ) HANGUL SYLLABLE DDWESS
+B6E9;B6E9;1104 1170 11BC;B6E9;1104 1170 11BC; # (뛩; 뛩; 뛩; 뛩; 뛩; ) HANGUL SYLLABLE DDWENG
+B6EA;B6EA;1104 1170 11BD;B6EA;1104 1170 11BD; # (뛪; 뛪; 뛪; 뛪; 뛪; ) HANGUL SYLLABLE DDWEJ
+B6EB;B6EB;1104 1170 11BE;B6EB;1104 1170 11BE; # (뛫; 뛫; 뛫; 뛫; 뛫; ) HANGUL SYLLABLE DDWEC
+B6EC;B6EC;1104 1170 11BF;B6EC;1104 1170 11BF; # (뛬; 뛬; 뛬; 뛬; 뛬; ) HANGUL SYLLABLE DDWEK
+B6ED;B6ED;1104 1170 11C0;B6ED;1104 1170 11C0; # (뛭; 뛭; 뛭; 뛭; 뛭; ) HANGUL SYLLABLE DDWET
+B6EE;B6EE;1104 1170 11C1;B6EE;1104 1170 11C1; # (ë›®; ë›®; á„„á…°á‡; ë›®; á„„á…°á‡; ) HANGUL SYLLABLE DDWEP
+B6EF;B6EF;1104 1170 11C2;B6EF;1104 1170 11C2; # (뛯; 뛯; 뛯; 뛯; 뛯; ) HANGUL SYLLABLE DDWEH
+B6F0;B6F0;1104 1171;B6F0;1104 1171; # (ë›°; ë›°; á„„á…±; ë›°; á„„á…±; ) HANGUL SYLLABLE DDWI
+B6F1;B6F1;1104 1171 11A8;B6F1;1104 1171 11A8; # (뛱; 뛱; 뛱; 뛱; 뛱; ) HANGUL SYLLABLE DDWIG
+B6F2;B6F2;1104 1171 11A9;B6F2;1104 1171 11A9; # (뛲; 뛲; 뛲; 뛲; 뛲; ) HANGUL SYLLABLE DDWIGG
+B6F3;B6F3;1104 1171 11AA;B6F3;1104 1171 11AA; # (뛳; 뛳; 뛳; 뛳; 뛳; ) HANGUL SYLLABLE DDWIGS
+B6F4;B6F4;1104 1171 11AB;B6F4;1104 1171 11AB; # (뛴; 뛴; 뛴; 뛴; 뛴; ) HANGUL SYLLABLE DDWIN
+B6F5;B6F5;1104 1171 11AC;B6F5;1104 1171 11AC; # (뛵; 뛵; 뛵; 뛵; 뛵; ) HANGUL SYLLABLE DDWINJ
+B6F6;B6F6;1104 1171 11AD;B6F6;1104 1171 11AD; # (뛶; 뛶; 뛶; 뛶; 뛶; ) HANGUL SYLLABLE DDWINH
+B6F7;B6F7;1104 1171 11AE;B6F7;1104 1171 11AE; # (뛷; 뛷; 뛷; 뛷; 뛷; ) HANGUL SYLLABLE DDWID
+B6F8;B6F8;1104 1171 11AF;B6F8;1104 1171 11AF; # (뛸; 뛸; 뛸; 뛸; 뛸; ) HANGUL SYLLABLE DDWIL
+B6F9;B6F9;1104 1171 11B0;B6F9;1104 1171 11B0; # (뛹; 뛹; 뛹; 뛹; 뛹; ) HANGUL SYLLABLE DDWILG
+B6FA;B6FA;1104 1171 11B1;B6FA;1104 1171 11B1; # (뛺; 뛺; 뛺; 뛺; 뛺; ) HANGUL SYLLABLE DDWILM
+B6FB;B6FB;1104 1171 11B2;B6FB;1104 1171 11B2; # (뛻; 뛻; 뛻; 뛻; 뛻; ) HANGUL SYLLABLE DDWILB
+B6FC;B6FC;1104 1171 11B3;B6FC;1104 1171 11B3; # (뛼; 뛼; 뛼; 뛼; 뛼; ) HANGUL SYLLABLE DDWILS
+B6FD;B6FD;1104 1171 11B4;B6FD;1104 1171 11B4; # (뛽; 뛽; 뛽; 뛽; 뛽; ) HANGUL SYLLABLE DDWILT
+B6FE;B6FE;1104 1171 11B5;B6FE;1104 1171 11B5; # (뛾; 뛾; 뛾; 뛾; 뛾; ) HANGUL SYLLABLE DDWILP
+B6FF;B6FF;1104 1171 11B6;B6FF;1104 1171 11B6; # (뛿; 뛿; 뛿; 뛿; 뛿; ) HANGUL SYLLABLE DDWILH
+B700;B700;1104 1171 11B7;B700;1104 1171 11B7; # (뜀; 뜀; 뜀; 뜀; 뜀; ) HANGUL SYLLABLE DDWIM
+B701;B701;1104 1171 11B8;B701;1104 1171 11B8; # (ëœ; ëœ; 뜁; ëœ; 뜁; ) HANGUL SYLLABLE DDWIB
+B702;B702;1104 1171 11B9;B702;1104 1171 11B9; # (뜂; 뜂; 뜂; 뜂; 뜂; ) HANGUL SYLLABLE DDWIBS
+B703;B703;1104 1171 11BA;B703;1104 1171 11BA; # (뜃; 뜃; 뜃; 뜃; 뜃; ) HANGUL SYLLABLE DDWIS
+B704;B704;1104 1171 11BB;B704;1104 1171 11BB; # (뜄; 뜄; 뜄; 뜄; 뜄; ) HANGUL SYLLABLE DDWISS
+B705;B705;1104 1171 11BC;B705;1104 1171 11BC; # (뜅; 뜅; 뜅; 뜅; 뜅; ) HANGUL SYLLABLE DDWING
+B706;B706;1104 1171 11BD;B706;1104 1171 11BD; # (뜆; 뜆; 뜆; 뜆; 뜆; ) HANGUL SYLLABLE DDWIJ
+B707;B707;1104 1171 11BE;B707;1104 1171 11BE; # (뜇; 뜇; 뜇; 뜇; 뜇; ) HANGUL SYLLABLE DDWIC
+B708;B708;1104 1171 11BF;B708;1104 1171 11BF; # (뜈; 뜈; 뜈; 뜈; 뜈; ) HANGUL SYLLABLE DDWIK
+B709;B709;1104 1171 11C0;B709;1104 1171 11C0; # (뜉; 뜉; 뜉; 뜉; 뜉; ) HANGUL SYLLABLE DDWIT
+B70A;B70A;1104 1171 11C1;B70A;1104 1171 11C1; # (뜊; 뜊; á„„á…±á‡; 뜊; á„„á…±á‡; ) HANGUL SYLLABLE DDWIP
+B70B;B70B;1104 1171 11C2;B70B;1104 1171 11C2; # (뜋; 뜋; 뜋; 뜋; 뜋; ) HANGUL SYLLABLE DDWIH
+B70C;B70C;1104 1172;B70C;1104 1172; # (뜌; 뜌; 뜌; 뜌; 뜌; ) HANGUL SYLLABLE DDYU
+B70D;B70D;1104 1172 11A8;B70D;1104 1172 11A8; # (ëœ; ëœ; 뜍; ëœ; 뜍; ) HANGUL SYLLABLE DDYUG
+B70E;B70E;1104 1172 11A9;B70E;1104 1172 11A9; # (뜎; 뜎; 뜎; 뜎; 뜎; ) HANGUL SYLLABLE DDYUGG
+B70F;B70F;1104 1172 11AA;B70F;1104 1172 11AA; # (ëœ; ëœ; 뜏; ëœ; 뜏; ) HANGUL SYLLABLE DDYUGS
+B710;B710;1104 1172 11AB;B710;1104 1172 11AB; # (ëœ; ëœ; 뜐; ëœ; 뜐; ) HANGUL SYLLABLE DDYUN
+B711;B711;1104 1172 11AC;B711;1104 1172 11AC; # (뜑; 뜑; 뜑; 뜑; 뜑; ) HANGUL SYLLABLE DDYUNJ
+B712;B712;1104 1172 11AD;B712;1104 1172 11AD; # (뜒; 뜒; 뜒; 뜒; 뜒; ) HANGUL SYLLABLE DDYUNH
+B713;B713;1104 1172 11AE;B713;1104 1172 11AE; # (뜓; 뜓; 뜓; 뜓; 뜓; ) HANGUL SYLLABLE DDYUD
+B714;B714;1104 1172 11AF;B714;1104 1172 11AF; # (뜔; 뜔; 뜔; 뜔; 뜔; ) HANGUL SYLLABLE DDYUL
+B715;B715;1104 1172 11B0;B715;1104 1172 11B0; # (뜕; 뜕; 뜕; 뜕; 뜕; ) HANGUL SYLLABLE DDYULG
+B716;B716;1104 1172 11B1;B716;1104 1172 11B1; # (뜖; 뜖; 뜖; 뜖; 뜖; ) HANGUL SYLLABLE DDYULM
+B717;B717;1104 1172 11B2;B717;1104 1172 11B2; # (뜗; 뜗; 뜗; 뜗; 뜗; ) HANGUL SYLLABLE DDYULB
+B718;B718;1104 1172 11B3;B718;1104 1172 11B3; # (뜘; 뜘; 뜘; 뜘; 뜘; ) HANGUL SYLLABLE DDYULS
+B719;B719;1104 1172 11B4;B719;1104 1172 11B4; # (뜙; 뜙; 뜙; 뜙; 뜙; ) HANGUL SYLLABLE DDYULT
+B71A;B71A;1104 1172 11B5;B71A;1104 1172 11B5; # (뜚; 뜚; 뜚; 뜚; 뜚; ) HANGUL SYLLABLE DDYULP
+B71B;B71B;1104 1172 11B6;B71B;1104 1172 11B6; # (뜛; 뜛; 뜛; 뜛; 뜛; ) HANGUL SYLLABLE DDYULH
+B71C;B71C;1104 1172 11B7;B71C;1104 1172 11B7; # (뜜; 뜜; 뜜; 뜜; 뜜; ) HANGUL SYLLABLE DDYUM
+B71D;B71D;1104 1172 11B8;B71D;1104 1172 11B8; # (ëœ; ëœ; 뜝; ëœ; 뜝; ) HANGUL SYLLABLE DDYUB
+B71E;B71E;1104 1172 11B9;B71E;1104 1172 11B9; # (뜞; 뜞; 뜞; 뜞; 뜞; ) HANGUL SYLLABLE DDYUBS
+B71F;B71F;1104 1172 11BA;B71F;1104 1172 11BA; # (뜟; 뜟; 뜟; 뜟; 뜟; ) HANGUL SYLLABLE DDYUS
+B720;B720;1104 1172 11BB;B720;1104 1172 11BB; # (뜠; 뜠; 뜠; 뜠; 뜠; ) HANGUL SYLLABLE DDYUSS
+B721;B721;1104 1172 11BC;B721;1104 1172 11BC; # (뜡; 뜡; 뜡; 뜡; 뜡; ) HANGUL SYLLABLE DDYUNG
+B722;B722;1104 1172 11BD;B722;1104 1172 11BD; # (뜢; 뜢; 뜢; 뜢; 뜢; ) HANGUL SYLLABLE DDYUJ
+B723;B723;1104 1172 11BE;B723;1104 1172 11BE; # (뜣; 뜣; 뜣; 뜣; 뜣; ) HANGUL SYLLABLE DDYUC
+B724;B724;1104 1172 11BF;B724;1104 1172 11BF; # (뜤; 뜤; 뜤; 뜤; 뜤; ) HANGUL SYLLABLE DDYUK
+B725;B725;1104 1172 11C0;B725;1104 1172 11C0; # (뜥; 뜥; 뜥; 뜥; 뜥; ) HANGUL SYLLABLE DDYUT
+B726;B726;1104 1172 11C1;B726;1104 1172 11C1; # (뜦; 뜦; á„„á…²á‡; 뜦; á„„á…²á‡; ) HANGUL SYLLABLE DDYUP
+B727;B727;1104 1172 11C2;B727;1104 1172 11C2; # (뜧; 뜧; 뜧; 뜧; 뜧; ) HANGUL SYLLABLE DDYUH
+B728;B728;1104 1173;B728;1104 1173; # (뜨; 뜨; 뜨; 뜨; 뜨; ) HANGUL SYLLABLE DDEU
+B729;B729;1104 1173 11A8;B729;1104 1173 11A8; # (뜩; 뜩; 뜩; 뜩; 뜩; ) HANGUL SYLLABLE DDEUG
+B72A;B72A;1104 1173 11A9;B72A;1104 1173 11A9; # (뜪; 뜪; 뜪; 뜪; 뜪; ) HANGUL SYLLABLE DDEUGG
+B72B;B72B;1104 1173 11AA;B72B;1104 1173 11AA; # (뜫; 뜫; 뜫; 뜫; 뜫; ) HANGUL SYLLABLE DDEUGS
+B72C;B72C;1104 1173 11AB;B72C;1104 1173 11AB; # (뜬; 뜬; 뜬; 뜬; 뜬; ) HANGUL SYLLABLE DDEUN
+B72D;B72D;1104 1173 11AC;B72D;1104 1173 11AC; # (뜭; 뜭; 뜭; 뜭; 뜭; ) HANGUL SYLLABLE DDEUNJ
+B72E;B72E;1104 1173 11AD;B72E;1104 1173 11AD; # (뜮; 뜮; 뜮; 뜮; 뜮; ) HANGUL SYLLABLE DDEUNH
+B72F;B72F;1104 1173 11AE;B72F;1104 1173 11AE; # (뜯; 뜯; 뜯; 뜯; 뜯; ) HANGUL SYLLABLE DDEUD
+B730;B730;1104 1173 11AF;B730;1104 1173 11AF; # (뜰; 뜰; 뜰; 뜰; 뜰; ) HANGUL SYLLABLE DDEUL
+B731;B731;1104 1173 11B0;B731;1104 1173 11B0; # (뜱; 뜱; 뜱; 뜱; 뜱; ) HANGUL SYLLABLE DDEULG
+B732;B732;1104 1173 11B1;B732;1104 1173 11B1; # (뜲; 뜲; 뜲; 뜲; 뜲; ) HANGUL SYLLABLE DDEULM
+B733;B733;1104 1173 11B2;B733;1104 1173 11B2; # (뜳; 뜳; 뜳; 뜳; 뜳; ) HANGUL SYLLABLE DDEULB
+B734;B734;1104 1173 11B3;B734;1104 1173 11B3; # (뜴; 뜴; 뜴; 뜴; 뜴; ) HANGUL SYLLABLE DDEULS
+B735;B735;1104 1173 11B4;B735;1104 1173 11B4; # (뜵; 뜵; 뜵; 뜵; 뜵; ) HANGUL SYLLABLE DDEULT
+B736;B736;1104 1173 11B5;B736;1104 1173 11B5; # (뜶; 뜶; 뜶; 뜶; 뜶; ) HANGUL SYLLABLE DDEULP
+B737;B737;1104 1173 11B6;B737;1104 1173 11B6; # (뜷; 뜷; 뜷; 뜷; 뜷; ) HANGUL SYLLABLE DDEULH
+B738;B738;1104 1173 11B7;B738;1104 1173 11B7; # (뜸; 뜸; 뜸; 뜸; 뜸; ) HANGUL SYLLABLE DDEUM
+B739;B739;1104 1173 11B8;B739;1104 1173 11B8; # (뜹; 뜹; 뜹; 뜹; 뜹; ) HANGUL SYLLABLE DDEUB
+B73A;B73A;1104 1173 11B9;B73A;1104 1173 11B9; # (뜺; 뜺; 뜺; 뜺; 뜺; ) HANGUL SYLLABLE DDEUBS
+B73B;B73B;1104 1173 11BA;B73B;1104 1173 11BA; # (뜻; 뜻; 뜻; 뜻; 뜻; ) HANGUL SYLLABLE DDEUS
+B73C;B73C;1104 1173 11BB;B73C;1104 1173 11BB; # (뜼; 뜼; 뜼; 뜼; 뜼; ) HANGUL SYLLABLE DDEUSS
+B73D;B73D;1104 1173 11BC;B73D;1104 1173 11BC; # (뜽; 뜽; 뜽; 뜽; 뜽; ) HANGUL SYLLABLE DDEUNG
+B73E;B73E;1104 1173 11BD;B73E;1104 1173 11BD; # (뜾; 뜾; 뜾; 뜾; 뜾; ) HANGUL SYLLABLE DDEUJ
+B73F;B73F;1104 1173 11BE;B73F;1104 1173 11BE; # (뜿; 뜿; 뜿; 뜿; 뜿; ) HANGUL SYLLABLE DDEUC
+B740;B740;1104 1173 11BF;B740;1104 1173 11BF; # (ë€; ë€; 띀; ë€; 띀; ) HANGUL SYLLABLE DDEUK
+B741;B741;1104 1173 11C0;B741;1104 1173 11C0; # (ë; ë; 띁; ë; 띁; ) HANGUL SYLLABLE DDEUT
+B742;B742;1104 1173 11C1;B742;1104 1173 11C1; # (ë‚; ë‚; á„„á…³á‡; ë‚; á„„á…³á‡; ) HANGUL SYLLABLE DDEUP
+B743;B743;1104 1173 11C2;B743;1104 1173 11C2; # (ëƒ; ëƒ; 띃; ëƒ; 띃; ) HANGUL SYLLABLE DDEUH
+B744;B744;1104 1174;B744;1104 1174; # (ë„; ë„; á„„á…´; ë„; á„„á…´; ) HANGUL SYLLABLE DDYI
+B745;B745;1104 1174 11A8;B745;1104 1174 11A8; # (ë…; ë…; 띅; ë…; 띅; ) HANGUL SYLLABLE DDYIG
+B746;B746;1104 1174 11A9;B746;1104 1174 11A9; # (ë†; ë†; 띆; ë†; 띆; ) HANGUL SYLLABLE DDYIGG
+B747;B747;1104 1174 11AA;B747;1104 1174 11AA; # (ë‡; ë‡; 띇; ë‡; 띇; ) HANGUL SYLLABLE DDYIGS
+B748;B748;1104 1174 11AB;B748;1104 1174 11AB; # (ëˆ; ëˆ; 띈; ëˆ; 띈; ) HANGUL SYLLABLE DDYIN
+B749;B749;1104 1174 11AC;B749;1104 1174 11AC; # (ë‰; ë‰; 띉; ë‰; 띉; ) HANGUL SYLLABLE DDYINJ
+B74A;B74A;1104 1174 11AD;B74A;1104 1174 11AD; # (ëŠ; ëŠ; 띊; ëŠ; 띊; ) HANGUL SYLLABLE DDYINH
+B74B;B74B;1104 1174 11AE;B74B;1104 1174 11AE; # (ë‹; ë‹; 띋; ë‹; 띋; ) HANGUL SYLLABLE DDYID
+B74C;B74C;1104 1174 11AF;B74C;1104 1174 11AF; # (ëŒ; ëŒ; 띌; ëŒ; 띌; ) HANGUL SYLLABLE DDYIL
+B74D;B74D;1104 1174 11B0;B74D;1104 1174 11B0; # (ë; ë; 띍; ë; 띍; ) HANGUL SYLLABLE DDYILG
+B74E;B74E;1104 1174 11B1;B74E;1104 1174 11B1; # (ëŽ; ëŽ; 띎; ëŽ; 띎; ) HANGUL SYLLABLE DDYILM
+B74F;B74F;1104 1174 11B2;B74F;1104 1174 11B2; # (ë; ë; 띏; ë; 띏; ) HANGUL SYLLABLE DDYILB
+B750;B750;1104 1174 11B3;B750;1104 1174 11B3; # (ë; ë; 띐; ë; 띐; ) HANGUL SYLLABLE DDYILS
+B751;B751;1104 1174 11B4;B751;1104 1174 11B4; # (ë‘; ë‘; 띑; ë‘; 띑; ) HANGUL SYLLABLE DDYILT
+B752;B752;1104 1174 11B5;B752;1104 1174 11B5; # (ë’; ë’; 띒; ë’; 띒; ) HANGUL SYLLABLE DDYILP
+B753;B753;1104 1174 11B6;B753;1104 1174 11B6; # (ë“; ë“; 띓; ë“; 띓; ) HANGUL SYLLABLE DDYILH
+B754;B754;1104 1174 11B7;B754;1104 1174 11B7; # (ë”; ë”; 띔; ë”; 띔; ) HANGUL SYLLABLE DDYIM
+B755;B755;1104 1174 11B8;B755;1104 1174 11B8; # (ë•; ë•; 띕; ë•; 띕; ) HANGUL SYLLABLE DDYIB
+B756;B756;1104 1174 11B9;B756;1104 1174 11B9; # (ë–; ë–; 띖; ë–; 띖; ) HANGUL SYLLABLE DDYIBS
+B757;B757;1104 1174 11BA;B757;1104 1174 11BA; # (ë—; ë—; 띗; ë—; 띗; ) HANGUL SYLLABLE DDYIS
+B758;B758;1104 1174 11BB;B758;1104 1174 11BB; # (ë˜; ë˜; 띘; ë˜; 띘; ) HANGUL SYLLABLE DDYISS
+B759;B759;1104 1174 11BC;B759;1104 1174 11BC; # (ë™; ë™; 띙; ë™; 띙; ) HANGUL SYLLABLE DDYING
+B75A;B75A;1104 1174 11BD;B75A;1104 1174 11BD; # (ëš; ëš; 띚; ëš; 띚; ) HANGUL SYLLABLE DDYIJ
+B75B;B75B;1104 1174 11BE;B75B;1104 1174 11BE; # (ë›; ë›; 띛; ë›; 띛; ) HANGUL SYLLABLE DDYIC
+B75C;B75C;1104 1174 11BF;B75C;1104 1174 11BF; # (ëœ; ëœ; 띜; ëœ; 띜; ) HANGUL SYLLABLE DDYIK
+B75D;B75D;1104 1174 11C0;B75D;1104 1174 11C0; # (ë; ë; 띝; ë; 띝; ) HANGUL SYLLABLE DDYIT
+B75E;B75E;1104 1174 11C1;B75E;1104 1174 11C1; # (ëž; ëž; á„„á…´á‡; ëž; á„„á…´á‡; ) HANGUL SYLLABLE DDYIP
+B75F;B75F;1104 1174 11C2;B75F;1104 1174 11C2; # (ëŸ; ëŸ; 띟; ëŸ; 띟; ) HANGUL SYLLABLE DDYIH
+B760;B760;1104 1175;B760;1104 1175; # (ë ; ë ; á„„á…µ; ë ; á„„á…µ; ) HANGUL SYLLABLE DDI
+B761;B761;1104 1175 11A8;B761;1104 1175 11A8; # (ë¡; ë¡; 띡; ë¡; 띡; ) HANGUL SYLLABLE DDIG
+B762;B762;1104 1175 11A9;B762;1104 1175 11A9; # (ë¢; ë¢; 띢; ë¢; 띢; ) HANGUL SYLLABLE DDIGG
+B763;B763;1104 1175 11AA;B763;1104 1175 11AA; # (ë£; ë£; 띣; ë£; 띣; ) HANGUL SYLLABLE DDIGS
+B764;B764;1104 1175 11AB;B764;1104 1175 11AB; # (ë¤; ë¤; 띤; ë¤; 띤; ) HANGUL SYLLABLE DDIN
+B765;B765;1104 1175 11AC;B765;1104 1175 11AC; # (ë¥; ë¥; 띥; ë¥; 띥; ) HANGUL SYLLABLE DDINJ
+B766;B766;1104 1175 11AD;B766;1104 1175 11AD; # (ë¦; ë¦; 띦; ë¦; 띦; ) HANGUL SYLLABLE DDINH
+B767;B767;1104 1175 11AE;B767;1104 1175 11AE; # (ë§; ë§; 띧; ë§; 띧; ) HANGUL SYLLABLE DDID
+B768;B768;1104 1175 11AF;B768;1104 1175 11AF; # (ë¨; ë¨; 띨; ë¨; 띨; ) HANGUL SYLLABLE DDIL
+B769;B769;1104 1175 11B0;B769;1104 1175 11B0; # (ë©; ë©; 띩; ë©; 띩; ) HANGUL SYLLABLE DDILG
+B76A;B76A;1104 1175 11B1;B76A;1104 1175 11B1; # (ëª; ëª; 띪; ëª; 띪; ) HANGUL SYLLABLE DDILM
+B76B;B76B;1104 1175 11B2;B76B;1104 1175 11B2; # (ë«; ë«; 띫; ë«; 띫; ) HANGUL SYLLABLE DDILB
+B76C;B76C;1104 1175 11B3;B76C;1104 1175 11B3; # (ë¬; ë¬; 띬; ë¬; 띬; ) HANGUL SYLLABLE DDILS
+B76D;B76D;1104 1175 11B4;B76D;1104 1175 11B4; # (ë­; ë­; 띭; ë­; 띭; ) HANGUL SYLLABLE DDILT
+B76E;B76E;1104 1175 11B5;B76E;1104 1175 11B5; # (ë®; ë®; 띮; ë®; 띮; ) HANGUL SYLLABLE DDILP
+B76F;B76F;1104 1175 11B6;B76F;1104 1175 11B6; # (ë¯; ë¯; 띯; ë¯; 띯; ) HANGUL SYLLABLE DDILH
+B770;B770;1104 1175 11B7;B770;1104 1175 11B7; # (ë°; ë°; 띰; ë°; 띰; ) HANGUL SYLLABLE DDIM
+B771;B771;1104 1175 11B8;B771;1104 1175 11B8; # (ë±; ë±; 띱; ë±; 띱; ) HANGUL SYLLABLE DDIB
+B772;B772;1104 1175 11B9;B772;1104 1175 11B9; # (ë²; ë²; 띲; ë²; 띲; ) HANGUL SYLLABLE DDIBS
+B773;B773;1104 1175 11BA;B773;1104 1175 11BA; # (ë³; ë³; 띳; ë³; 띳; ) HANGUL SYLLABLE DDIS
+B774;B774;1104 1175 11BB;B774;1104 1175 11BB; # (ë´; ë´; 띴; ë´; 띴; ) HANGUL SYLLABLE DDISS
+B775;B775;1104 1175 11BC;B775;1104 1175 11BC; # (ëµ; ëµ; 띵; ëµ; 띵; ) HANGUL SYLLABLE DDING
+B776;B776;1104 1175 11BD;B776;1104 1175 11BD; # (ë¶; ë¶; 띶; ë¶; 띶; ) HANGUL SYLLABLE DDIJ
+B777;B777;1104 1175 11BE;B777;1104 1175 11BE; # (ë·; ë·; 띷; ë·; 띷; ) HANGUL SYLLABLE DDIC
+B778;B778;1104 1175 11BF;B778;1104 1175 11BF; # (ë¸; ë¸; 띸; ë¸; 띸; ) HANGUL SYLLABLE DDIK
+B779;B779;1104 1175 11C0;B779;1104 1175 11C0; # (ë¹; ë¹; 띹; ë¹; 띹; ) HANGUL SYLLABLE DDIT
+B77A;B77A;1104 1175 11C1;B77A;1104 1175 11C1; # (ëº; ëº; á„„á…µá‡; ëº; á„„á…µá‡; ) HANGUL SYLLABLE DDIP
+B77B;B77B;1104 1175 11C2;B77B;1104 1175 11C2; # (ë»; ë»; 띻; ë»; 띻; ) HANGUL SYLLABLE DDIH
+B77C;B77C;1105 1161;B77C;1105 1161; # (ë¼; ë¼; á„…á…¡; ë¼; á„…á…¡; ) HANGUL SYLLABLE RA
+B77D;B77D;1105 1161 11A8;B77D;1105 1161 11A8; # (ë½; ë½; 락; ë½; 락; ) HANGUL SYLLABLE RAG
+B77E;B77E;1105 1161 11A9;B77E;1105 1161 11A9; # (ë¾; ë¾; 띾; ë¾; 띾; ) HANGUL SYLLABLE RAGG
+B77F;B77F;1105 1161 11AA;B77F;1105 1161 11AA; # (ë¿; ë¿; 띿; ë¿; 띿; ) HANGUL SYLLABLE RAGS
+B780;B780;1105 1161 11AB;B780;1105 1161 11AB; # (란; 란; 란; 란; 란; ) HANGUL SYLLABLE RAN
+B781;B781;1105 1161 11AC;B781;1105 1161 11AC; # (ëž; ëž; 랁; ëž; 랁; ) HANGUL SYLLABLE RANJ
+B782;B782;1105 1161 11AD;B782;1105 1161 11AD; # (랂; 랂; 랂; 랂; 랂; ) HANGUL SYLLABLE RANH
+B783;B783;1105 1161 11AE;B783;1105 1161 11AE; # (랃; 랃; 랃; 랃; 랃; ) HANGUL SYLLABLE RAD
+B784;B784;1105 1161 11AF;B784;1105 1161 11AF; # (랄; 랄; 랄; 랄; 랄; ) HANGUL SYLLABLE RAL
+B785;B785;1105 1161 11B0;B785;1105 1161 11B0; # (랅; 랅; 랅; 랅; 랅; ) HANGUL SYLLABLE RALG
+B786;B786;1105 1161 11B1;B786;1105 1161 11B1; # (랆; 랆; 랆; 랆; 랆; ) HANGUL SYLLABLE RALM
+B787;B787;1105 1161 11B2;B787;1105 1161 11B2; # (랇; 랇; 랇; 랇; 랇; ) HANGUL SYLLABLE RALB
+B788;B788;1105 1161 11B3;B788;1105 1161 11B3; # (랈; 랈; 랈; 랈; 랈; ) HANGUL SYLLABLE RALS
+B789;B789;1105 1161 11B4;B789;1105 1161 11B4; # (랉; 랉; 랉; 랉; 랉; ) HANGUL SYLLABLE RALT
+B78A;B78A;1105 1161 11B5;B78A;1105 1161 11B5; # (랊; 랊; 랊; 랊; 랊; ) HANGUL SYLLABLE RALP
+B78B;B78B;1105 1161 11B6;B78B;1105 1161 11B6; # (랋; 랋; 랋; 랋; 랋; ) HANGUL SYLLABLE RALH
+B78C;B78C;1105 1161 11B7;B78C;1105 1161 11B7; # (람; 람; 람; 람; 람; ) HANGUL SYLLABLE RAM
+B78D;B78D;1105 1161 11B8;B78D;1105 1161 11B8; # (ëž; ëž; 랍; ëž; 랍; ) HANGUL SYLLABLE RAB
+B78E;B78E;1105 1161 11B9;B78E;1105 1161 11B9; # (랎; 랎; 랎; 랎; 랎; ) HANGUL SYLLABLE RABS
+B78F;B78F;1105 1161 11BA;B78F;1105 1161 11BA; # (ëž; ëž; 랏; ëž; 랏; ) HANGUL SYLLABLE RAS
+B790;B790;1105 1161 11BB;B790;1105 1161 11BB; # (ëž; ëž; 랐; ëž; 랐; ) HANGUL SYLLABLE RASS
+B791;B791;1105 1161 11BC;B791;1105 1161 11BC; # (랑; 랑; 랑; 랑; 랑; ) HANGUL SYLLABLE RANG
+B792;B792;1105 1161 11BD;B792;1105 1161 11BD; # (랒; 랒; 랒; 랒; 랒; ) HANGUL SYLLABLE RAJ
+B793;B793;1105 1161 11BE;B793;1105 1161 11BE; # (랓; 랓; 랓; 랓; 랓; ) HANGUL SYLLABLE RAC
+B794;B794;1105 1161 11BF;B794;1105 1161 11BF; # (랔; 랔; 랔; 랔; 랔; ) HANGUL SYLLABLE RAK
+B795;B795;1105 1161 11C0;B795;1105 1161 11C0; # (랕; 랕; 랕; 랕; 랕; ) HANGUL SYLLABLE RAT
+B796;B796;1105 1161 11C1;B796;1105 1161 11C1; # (ëž–; ëž–; á„…á…¡á‡; ëž–; á„…á…¡á‡; ) HANGUL SYLLABLE RAP
+B797;B797;1105 1161 11C2;B797;1105 1161 11C2; # (랗; 랗; 랗; 랗; 랗; ) HANGUL SYLLABLE RAH
+B798;B798;1105 1162;B798;1105 1162; # (래; 래; 래; 래; 래; ) HANGUL SYLLABLE RAE
+B799;B799;1105 1162 11A8;B799;1105 1162 11A8; # (랙; 랙; 랙; 랙; 랙; ) HANGUL SYLLABLE RAEG
+B79A;B79A;1105 1162 11A9;B79A;1105 1162 11A9; # (랚; 랚; 랚; 랚; 랚; ) HANGUL SYLLABLE RAEGG
+B79B;B79B;1105 1162 11AA;B79B;1105 1162 11AA; # (랛; 랛; 랛; 랛; 랛; ) HANGUL SYLLABLE RAEGS
+B79C;B79C;1105 1162 11AB;B79C;1105 1162 11AB; # (랜; 랜; 랜; 랜; 랜; ) HANGUL SYLLABLE RAEN
+B79D;B79D;1105 1162 11AC;B79D;1105 1162 11AC; # (ëž; ëž; 랝; ëž; 랝; ) HANGUL SYLLABLE RAENJ
+B79E;B79E;1105 1162 11AD;B79E;1105 1162 11AD; # (랞; 랞; 랞; 랞; 랞; ) HANGUL SYLLABLE RAENH
+B79F;B79F;1105 1162 11AE;B79F;1105 1162 11AE; # (랟; 랟; 랟; 랟; 랟; ) HANGUL SYLLABLE RAED
+B7A0;B7A0;1105 1162 11AF;B7A0;1105 1162 11AF; # (랠; 랠; 랠; 랠; 랠; ) HANGUL SYLLABLE RAEL
+B7A1;B7A1;1105 1162 11B0;B7A1;1105 1162 11B0; # (랡; 랡; 랡; 랡; 랡; ) HANGUL SYLLABLE RAELG
+B7A2;B7A2;1105 1162 11B1;B7A2;1105 1162 11B1; # (랢; 랢; 랢; 랢; 랢; ) HANGUL SYLLABLE RAELM
+B7A3;B7A3;1105 1162 11B2;B7A3;1105 1162 11B2; # (랣; 랣; 랣; 랣; 랣; ) HANGUL SYLLABLE RAELB
+B7A4;B7A4;1105 1162 11B3;B7A4;1105 1162 11B3; # (랤; 랤; 랤; 랤; 랤; ) HANGUL SYLLABLE RAELS
+B7A5;B7A5;1105 1162 11B4;B7A5;1105 1162 11B4; # (랥; 랥; 랥; 랥; 랥; ) HANGUL SYLLABLE RAELT
+B7A6;B7A6;1105 1162 11B5;B7A6;1105 1162 11B5; # (랦; 랦; 랦; 랦; 랦; ) HANGUL SYLLABLE RAELP
+B7A7;B7A7;1105 1162 11B6;B7A7;1105 1162 11B6; # (랧; 랧; 랧; 랧; 랧; ) HANGUL SYLLABLE RAELH
+B7A8;B7A8;1105 1162 11B7;B7A8;1105 1162 11B7; # (램; 램; 램; 램; 램; ) HANGUL SYLLABLE RAEM
+B7A9;B7A9;1105 1162 11B8;B7A9;1105 1162 11B8; # (랩; 랩; 랩; 랩; 랩; ) HANGUL SYLLABLE RAEB
+B7AA;B7AA;1105 1162 11B9;B7AA;1105 1162 11B9; # (랪; 랪; 랪; 랪; 랪; ) HANGUL SYLLABLE RAEBS
+B7AB;B7AB;1105 1162 11BA;B7AB;1105 1162 11BA; # (랫; 랫; 랫; 랫; 랫; ) HANGUL SYLLABLE RAES
+B7AC;B7AC;1105 1162 11BB;B7AC;1105 1162 11BB; # (랬; 랬; 랬; 랬; 랬; ) HANGUL SYLLABLE RAESS
+B7AD;B7AD;1105 1162 11BC;B7AD;1105 1162 11BC; # (랭; 랭; 랭; 랭; 랭; ) HANGUL SYLLABLE RAENG
+B7AE;B7AE;1105 1162 11BD;B7AE;1105 1162 11BD; # (랮; 랮; 랮; 랮; 랮; ) HANGUL SYLLABLE RAEJ
+B7AF;B7AF;1105 1162 11BE;B7AF;1105 1162 11BE; # (랯; 랯; 랯; 랯; 랯; ) HANGUL SYLLABLE RAEC
+B7B0;B7B0;1105 1162 11BF;B7B0;1105 1162 11BF; # (랰; 랰; 랰; 랰; 랰; ) HANGUL SYLLABLE RAEK
+B7B1;B7B1;1105 1162 11C0;B7B1;1105 1162 11C0; # (랱; 랱; 랱; 랱; 랱; ) HANGUL SYLLABLE RAET
+B7B2;B7B2;1105 1162 11C1;B7B2;1105 1162 11C1; # (ëž²; ëž²; á„…á…¢á‡; ëž²; á„…á…¢á‡; ) HANGUL SYLLABLE RAEP
+B7B3;B7B3;1105 1162 11C2;B7B3;1105 1162 11C2; # (랳; 랳; 랳; 랳; 랳; ) HANGUL SYLLABLE RAEH
+B7B4;B7B4;1105 1163;B7B4;1105 1163; # (ëž´; ëž´; á„…á…£; ëž´; á„…á…£; ) HANGUL SYLLABLE RYA
+B7B5;B7B5;1105 1163 11A8;B7B5;1105 1163 11A8; # (략; 략; 략; 략; 략; ) HANGUL SYLLABLE RYAG
+B7B6;B7B6;1105 1163 11A9;B7B6;1105 1163 11A9; # (랶; 랶; 랶; 랶; 랶; ) HANGUL SYLLABLE RYAGG
+B7B7;B7B7;1105 1163 11AA;B7B7;1105 1163 11AA; # (랷; 랷; 랷; 랷; 랷; ) HANGUL SYLLABLE RYAGS
+B7B8;B7B8;1105 1163 11AB;B7B8;1105 1163 11AB; # (랸; 랸; 랸; 랸; 랸; ) HANGUL SYLLABLE RYAN
+B7B9;B7B9;1105 1163 11AC;B7B9;1105 1163 11AC; # (랹; 랹; 랹; 랹; 랹; ) HANGUL SYLLABLE RYANJ
+B7BA;B7BA;1105 1163 11AD;B7BA;1105 1163 11AD; # (랺; 랺; 랺; 랺; 랺; ) HANGUL SYLLABLE RYANH
+B7BB;B7BB;1105 1163 11AE;B7BB;1105 1163 11AE; # (랻; 랻; 랻; 랻; 랻; ) HANGUL SYLLABLE RYAD
+B7BC;B7BC;1105 1163 11AF;B7BC;1105 1163 11AF; # (랼; 랼; 랼; 랼; 랼; ) HANGUL SYLLABLE RYAL
+B7BD;B7BD;1105 1163 11B0;B7BD;1105 1163 11B0; # (랽; 랽; 랽; 랽; 랽; ) HANGUL SYLLABLE RYALG
+B7BE;B7BE;1105 1163 11B1;B7BE;1105 1163 11B1; # (랾; 랾; 랾; 랾; 랾; ) HANGUL SYLLABLE RYALM
+B7BF;B7BF;1105 1163 11B2;B7BF;1105 1163 11B2; # (랿; 랿; 랿; 랿; 랿; ) HANGUL SYLLABLE RYALB
+B7C0;B7C0;1105 1163 11B3;B7C0;1105 1163 11B3; # (럀; 럀; 럀; 럀; 럀; ) HANGUL SYLLABLE RYALS
+B7C1;B7C1;1105 1163 11B4;B7C1;1105 1163 11B4; # (ëŸ; ëŸ; 럁; ëŸ; 럁; ) HANGUL SYLLABLE RYALT
+B7C2;B7C2;1105 1163 11B5;B7C2;1105 1163 11B5; # (럂; 럂; 럂; 럂; 럂; ) HANGUL SYLLABLE RYALP
+B7C3;B7C3;1105 1163 11B6;B7C3;1105 1163 11B6; # (럃; 럃; 럃; 럃; 럃; ) HANGUL SYLLABLE RYALH
+B7C4;B7C4;1105 1163 11B7;B7C4;1105 1163 11B7; # (럄; 럄; 럄; 럄; 럄; ) HANGUL SYLLABLE RYAM
+B7C5;B7C5;1105 1163 11B8;B7C5;1105 1163 11B8; # (럅; 럅; 럅; 럅; 럅; ) HANGUL SYLLABLE RYAB
+B7C6;B7C6;1105 1163 11B9;B7C6;1105 1163 11B9; # (럆; 럆; 럆; 럆; 럆; ) HANGUL SYLLABLE RYABS
+B7C7;B7C7;1105 1163 11BA;B7C7;1105 1163 11BA; # (럇; 럇; 럇; 럇; 럇; ) HANGUL SYLLABLE RYAS
+B7C8;B7C8;1105 1163 11BB;B7C8;1105 1163 11BB; # (럈; 럈; 럈; 럈; 럈; ) HANGUL SYLLABLE RYASS
+B7C9;B7C9;1105 1163 11BC;B7C9;1105 1163 11BC; # (량; 량; 량; 량; 량; ) HANGUL SYLLABLE RYANG
+B7CA;B7CA;1105 1163 11BD;B7CA;1105 1163 11BD; # (럊; 럊; 럊; 럊; 럊; ) HANGUL SYLLABLE RYAJ
+B7CB;B7CB;1105 1163 11BE;B7CB;1105 1163 11BE; # (럋; 럋; 럋; 럋; 럋; ) HANGUL SYLLABLE RYAC
+B7CC;B7CC;1105 1163 11BF;B7CC;1105 1163 11BF; # (럌; 럌; 럌; 럌; 럌; ) HANGUL SYLLABLE RYAK
+B7CD;B7CD;1105 1163 11C0;B7CD;1105 1163 11C0; # (ëŸ; ëŸ; 럍; ëŸ; 럍; ) HANGUL SYLLABLE RYAT
+B7CE;B7CE;1105 1163 11C1;B7CE;1105 1163 11C1; # (럎; 럎; á„…á…£á‡; 럎; á„…á…£á‡; ) HANGUL SYLLABLE RYAP
+B7CF;B7CF;1105 1163 11C2;B7CF;1105 1163 11C2; # (ëŸ; ëŸ; 럏; ëŸ; 럏; ) HANGUL SYLLABLE RYAH
+B7D0;B7D0;1105 1164;B7D0;1105 1164; # (ëŸ; ëŸ; á„…á…¤; ëŸ; á„…á…¤; ) HANGUL SYLLABLE RYAE
+B7D1;B7D1;1105 1164 11A8;B7D1;1105 1164 11A8; # (럑; 럑; 럑; 럑; 럑; ) HANGUL SYLLABLE RYAEG
+B7D2;B7D2;1105 1164 11A9;B7D2;1105 1164 11A9; # (럒; 럒; 럒; 럒; 럒; ) HANGUL SYLLABLE RYAEGG
+B7D3;B7D3;1105 1164 11AA;B7D3;1105 1164 11AA; # (럓; 럓; 럓; 럓; 럓; ) HANGUL SYLLABLE RYAEGS
+B7D4;B7D4;1105 1164 11AB;B7D4;1105 1164 11AB; # (럔; 럔; 럔; 럔; 럔; ) HANGUL SYLLABLE RYAEN
+B7D5;B7D5;1105 1164 11AC;B7D5;1105 1164 11AC; # (럕; 럕; 럕; 럕; 럕; ) HANGUL SYLLABLE RYAENJ
+B7D6;B7D6;1105 1164 11AD;B7D6;1105 1164 11AD; # (럖; 럖; 럖; 럖; 럖; ) HANGUL SYLLABLE RYAENH
+B7D7;B7D7;1105 1164 11AE;B7D7;1105 1164 11AE; # (럗; 럗; 럗; 럗; 럗; ) HANGUL SYLLABLE RYAED
+B7D8;B7D8;1105 1164 11AF;B7D8;1105 1164 11AF; # (럘; 럘; 럘; 럘; 럘; ) HANGUL SYLLABLE RYAEL
+B7D9;B7D9;1105 1164 11B0;B7D9;1105 1164 11B0; # (럙; 럙; 럙; 럙; 럙; ) HANGUL SYLLABLE RYAELG
+B7DA;B7DA;1105 1164 11B1;B7DA;1105 1164 11B1; # (럚; 럚; 럚; 럚; 럚; ) HANGUL SYLLABLE RYAELM
+B7DB;B7DB;1105 1164 11B2;B7DB;1105 1164 11B2; # (럛; 럛; 럛; 럛; 럛; ) HANGUL SYLLABLE RYAELB
+B7DC;B7DC;1105 1164 11B3;B7DC;1105 1164 11B3; # (럜; 럜; 럜; 럜; 럜; ) HANGUL SYLLABLE RYAELS
+B7DD;B7DD;1105 1164 11B4;B7DD;1105 1164 11B4; # (ëŸ; ëŸ; 럝; ëŸ; 럝; ) HANGUL SYLLABLE RYAELT
+B7DE;B7DE;1105 1164 11B5;B7DE;1105 1164 11B5; # (럞; 럞; 럞; 럞; 럞; ) HANGUL SYLLABLE RYAELP
+B7DF;B7DF;1105 1164 11B6;B7DF;1105 1164 11B6; # (럟; 럟; 럟; 럟; 럟; ) HANGUL SYLLABLE RYAELH
+B7E0;B7E0;1105 1164 11B7;B7E0;1105 1164 11B7; # (럠; 럠; 럠; 럠; 럠; ) HANGUL SYLLABLE RYAEM
+B7E1;B7E1;1105 1164 11B8;B7E1;1105 1164 11B8; # (럡; 럡; 럡; 럡; 럡; ) HANGUL SYLLABLE RYAEB
+B7E2;B7E2;1105 1164 11B9;B7E2;1105 1164 11B9; # (럢; 럢; 럢; 럢; 럢; ) HANGUL SYLLABLE RYAEBS
+B7E3;B7E3;1105 1164 11BA;B7E3;1105 1164 11BA; # (럣; 럣; 럣; 럣; 럣; ) HANGUL SYLLABLE RYAES
+B7E4;B7E4;1105 1164 11BB;B7E4;1105 1164 11BB; # (럤; 럤; 럤; 럤; 럤; ) HANGUL SYLLABLE RYAESS
+B7E5;B7E5;1105 1164 11BC;B7E5;1105 1164 11BC; # (럥; 럥; 럥; 럥; 럥; ) HANGUL SYLLABLE RYAENG
+B7E6;B7E6;1105 1164 11BD;B7E6;1105 1164 11BD; # (럦; 럦; 럦; 럦; 럦; ) HANGUL SYLLABLE RYAEJ
+B7E7;B7E7;1105 1164 11BE;B7E7;1105 1164 11BE; # (럧; 럧; 럧; 럧; 럧; ) HANGUL SYLLABLE RYAEC
+B7E8;B7E8;1105 1164 11BF;B7E8;1105 1164 11BF; # (럨; 럨; 럨; 럨; 럨; ) HANGUL SYLLABLE RYAEK
+B7E9;B7E9;1105 1164 11C0;B7E9;1105 1164 11C0; # (럩; 럩; 럩; 럩; 럩; ) HANGUL SYLLABLE RYAET
+B7EA;B7EA;1105 1164 11C1;B7EA;1105 1164 11C1; # (럪; 럪; á„…á…¤á‡; 럪; á„…á…¤á‡; ) HANGUL SYLLABLE RYAEP
+B7EB;B7EB;1105 1164 11C2;B7EB;1105 1164 11C2; # (럫; 럫; 럫; 럫; 럫; ) HANGUL SYLLABLE RYAEH
+B7EC;B7EC;1105 1165;B7EC;1105 1165; # (러; 러; 러; 러; 러; ) HANGUL SYLLABLE REO
+B7ED;B7ED;1105 1165 11A8;B7ED;1105 1165 11A8; # (럭; 럭; 럭; 럭; 럭; ) HANGUL SYLLABLE REOG
+B7EE;B7EE;1105 1165 11A9;B7EE;1105 1165 11A9; # (럮; 럮; 럮; 럮; 럮; ) HANGUL SYLLABLE REOGG
+B7EF;B7EF;1105 1165 11AA;B7EF;1105 1165 11AA; # (럯; 럯; 럯; 럯; 럯; ) HANGUL SYLLABLE REOGS
+B7F0;B7F0;1105 1165 11AB;B7F0;1105 1165 11AB; # (런; 런; 런; 런; 런; ) HANGUL SYLLABLE REON
+B7F1;B7F1;1105 1165 11AC;B7F1;1105 1165 11AC; # (럱; 럱; 럱; 럱; 럱; ) HANGUL SYLLABLE REONJ
+B7F2;B7F2;1105 1165 11AD;B7F2;1105 1165 11AD; # (럲; 럲; 럲; 럲; 럲; ) HANGUL SYLLABLE REONH
+B7F3;B7F3;1105 1165 11AE;B7F3;1105 1165 11AE; # (럳; 럳; 럳; 럳; 럳; ) HANGUL SYLLABLE REOD
+B7F4;B7F4;1105 1165 11AF;B7F4;1105 1165 11AF; # (럴; 럴; 럴; 럴; 럴; ) HANGUL SYLLABLE REOL
+B7F5;B7F5;1105 1165 11B0;B7F5;1105 1165 11B0; # (럵; 럵; 럵; 럵; 럵; ) HANGUL SYLLABLE REOLG
+B7F6;B7F6;1105 1165 11B1;B7F6;1105 1165 11B1; # (럶; 럶; 럶; 럶; 럶; ) HANGUL SYLLABLE REOLM
+B7F7;B7F7;1105 1165 11B2;B7F7;1105 1165 11B2; # (럷; 럷; 럷; 럷; 럷; ) HANGUL SYLLABLE REOLB
+B7F8;B7F8;1105 1165 11B3;B7F8;1105 1165 11B3; # (럸; 럸; 럸; 럸; 럸; ) HANGUL SYLLABLE REOLS
+B7F9;B7F9;1105 1165 11B4;B7F9;1105 1165 11B4; # (럹; 럹; 럹; 럹; 럹; ) HANGUL SYLLABLE REOLT
+B7FA;B7FA;1105 1165 11B5;B7FA;1105 1165 11B5; # (럺; 럺; 럺; 럺; 럺; ) HANGUL SYLLABLE REOLP
+B7FB;B7FB;1105 1165 11B6;B7FB;1105 1165 11B6; # (럻; 럻; 럻; 럻; 럻; ) HANGUL SYLLABLE REOLH
+B7FC;B7FC;1105 1165 11B7;B7FC;1105 1165 11B7; # (럼; 럼; 럼; 럼; 럼; ) HANGUL SYLLABLE REOM
+B7FD;B7FD;1105 1165 11B8;B7FD;1105 1165 11B8; # (럽; 럽; 럽; 럽; 럽; ) HANGUL SYLLABLE REOB
+B7FE;B7FE;1105 1165 11B9;B7FE;1105 1165 11B9; # (럾; 럾; 럾; 럾; 럾; ) HANGUL SYLLABLE REOBS
+B7FF;B7FF;1105 1165 11BA;B7FF;1105 1165 11BA; # (럿; 럿; 럿; 럿; 럿; ) HANGUL SYLLABLE REOS
+B800;B800;1105 1165 11BB;B800;1105 1165 11BB; # (렀; 렀; 렀; 렀; 렀; ) HANGUL SYLLABLE REOSS
+B801;B801;1105 1165 11BC;B801;1105 1165 11BC; # (ë ; ë ; 렁; ë ; 렁; ) HANGUL SYLLABLE REONG
+B802;B802;1105 1165 11BD;B802;1105 1165 11BD; # (렂; 렂; 렂; 렂; 렂; ) HANGUL SYLLABLE REOJ
+B803;B803;1105 1165 11BE;B803;1105 1165 11BE; # (렃; 렃; 렃; 렃; 렃; ) HANGUL SYLLABLE REOC
+B804;B804;1105 1165 11BF;B804;1105 1165 11BF; # (렄; 렄; 렄; 렄; 렄; ) HANGUL SYLLABLE REOK
+B805;B805;1105 1165 11C0;B805;1105 1165 11C0; # (렅; 렅; 렅; 렅; 렅; ) HANGUL SYLLABLE REOT
+B806;B806;1105 1165 11C1;B806;1105 1165 11C1; # (ë †; ë †; á„…á…¥á‡; ë †; á„…á…¥á‡; ) HANGUL SYLLABLE REOP
+B807;B807;1105 1165 11C2;B807;1105 1165 11C2; # (렇; 렇; 렇; 렇; 렇; ) HANGUL SYLLABLE REOH
+B808;B808;1105 1166;B808;1105 1166; # (ë ˆ; ë ˆ; á„…á…¦; ë ˆ; á„…á…¦; ) HANGUL SYLLABLE RE
+B809;B809;1105 1166 11A8;B809;1105 1166 11A8; # (렉; 렉; 렉; 렉; 렉; ) HANGUL SYLLABLE REG
+B80A;B80A;1105 1166 11A9;B80A;1105 1166 11A9; # (렊; 렊; 렊; 렊; 렊; ) HANGUL SYLLABLE REGG
+B80B;B80B;1105 1166 11AA;B80B;1105 1166 11AA; # (렋; 렋; 렋; 렋; 렋; ) HANGUL SYLLABLE REGS
+B80C;B80C;1105 1166 11AB;B80C;1105 1166 11AB; # (렌; 렌; 렌; 렌; 렌; ) HANGUL SYLLABLE REN
+B80D;B80D;1105 1166 11AC;B80D;1105 1166 11AC; # (ë ; ë ; 렍; ë ; 렍; ) HANGUL SYLLABLE RENJ
+B80E;B80E;1105 1166 11AD;B80E;1105 1166 11AD; # (렎; 렎; 렎; 렎; 렎; ) HANGUL SYLLABLE RENH
+B80F;B80F;1105 1166 11AE;B80F;1105 1166 11AE; # (ë ; ë ; 렏; ë ; 렏; ) HANGUL SYLLABLE RED
+B810;B810;1105 1166 11AF;B810;1105 1166 11AF; # (ë ; ë ; 렐; ë ; 렐; ) HANGUL SYLLABLE REL
+B811;B811;1105 1166 11B0;B811;1105 1166 11B0; # (렑; 렑; 렑; 렑; 렑; ) HANGUL SYLLABLE RELG
+B812;B812;1105 1166 11B1;B812;1105 1166 11B1; # (렒; 렒; 렒; 렒; 렒; ) HANGUL SYLLABLE RELM
+B813;B813;1105 1166 11B2;B813;1105 1166 11B2; # (렓; 렓; 렓; 렓; 렓; ) HANGUL SYLLABLE RELB
+B814;B814;1105 1166 11B3;B814;1105 1166 11B3; # (렔; 렔; 렔; 렔; 렔; ) HANGUL SYLLABLE RELS
+B815;B815;1105 1166 11B4;B815;1105 1166 11B4; # (렕; 렕; 렕; 렕; 렕; ) HANGUL SYLLABLE RELT
+B816;B816;1105 1166 11B5;B816;1105 1166 11B5; # (렖; 렖; 렖; 렖; 렖; ) HANGUL SYLLABLE RELP
+B817;B817;1105 1166 11B6;B817;1105 1166 11B6; # (렗; 렗; 렗; 렗; 렗; ) HANGUL SYLLABLE RELH
+B818;B818;1105 1166 11B7;B818;1105 1166 11B7; # (렘; 렘; 렘; 렘; 렘; ) HANGUL SYLLABLE REM
+B819;B819;1105 1166 11B8;B819;1105 1166 11B8; # (렙; 렙; 렙; 렙; 렙; ) HANGUL SYLLABLE REB
+B81A;B81A;1105 1166 11B9;B81A;1105 1166 11B9; # (렚; 렚; 렚; 렚; 렚; ) HANGUL SYLLABLE REBS
+B81B;B81B;1105 1166 11BA;B81B;1105 1166 11BA; # (렛; 렛; 렛; 렛; 렛; ) HANGUL SYLLABLE RES
+B81C;B81C;1105 1166 11BB;B81C;1105 1166 11BB; # (렜; 렜; 렜; 렜; 렜; ) HANGUL SYLLABLE RESS
+B81D;B81D;1105 1166 11BC;B81D;1105 1166 11BC; # (ë ; ë ; 렝; ë ; 렝; ) HANGUL SYLLABLE RENG
+B81E;B81E;1105 1166 11BD;B81E;1105 1166 11BD; # (렞; 렞; 렞; 렞; 렞; ) HANGUL SYLLABLE REJ
+B81F;B81F;1105 1166 11BE;B81F;1105 1166 11BE; # (렟; 렟; 렟; 렟; 렟; ) HANGUL SYLLABLE REC
+B820;B820;1105 1166 11BF;B820;1105 1166 11BF; # (렠; 렠; 렠; 렠; 렠; ) HANGUL SYLLABLE REK
+B821;B821;1105 1166 11C0;B821;1105 1166 11C0; # (렡; 렡; 렡; 렡; 렡; ) HANGUL SYLLABLE RET
+B822;B822;1105 1166 11C1;B822;1105 1166 11C1; # (ë ¢; ë ¢; á„…á…¦á‡; ë ¢; á„…á…¦á‡; ) HANGUL SYLLABLE REP
+B823;B823;1105 1166 11C2;B823;1105 1166 11C2; # (렣; 렣; 렣; 렣; 렣; ) HANGUL SYLLABLE REH
+B824;B824;1105 1167;B824;1105 1167; # (ë ¤; ë ¤; á„…á…§; ë ¤; á„…á…§; ) HANGUL SYLLABLE RYEO
+B825;B825;1105 1167 11A8;B825;1105 1167 11A8; # (력; 력; 력; 력; 력; ) HANGUL SYLLABLE RYEOG
+B826;B826;1105 1167 11A9;B826;1105 1167 11A9; # (렦; 렦; 렦; 렦; 렦; ) HANGUL SYLLABLE RYEOGG
+B827;B827;1105 1167 11AA;B827;1105 1167 11AA; # (렧; 렧; 렧; 렧; 렧; ) HANGUL SYLLABLE RYEOGS
+B828;B828;1105 1167 11AB;B828;1105 1167 11AB; # (련; 련; 련; 련; 련; ) HANGUL SYLLABLE RYEON
+B829;B829;1105 1167 11AC;B829;1105 1167 11AC; # (렩; 렩; 렩; 렩; 렩; ) HANGUL SYLLABLE RYEONJ
+B82A;B82A;1105 1167 11AD;B82A;1105 1167 11AD; # (렪; 렪; 렪; 렪; 렪; ) HANGUL SYLLABLE RYEONH
+B82B;B82B;1105 1167 11AE;B82B;1105 1167 11AE; # (렫; 렫; 렫; 렫; 렫; ) HANGUL SYLLABLE RYEOD
+B82C;B82C;1105 1167 11AF;B82C;1105 1167 11AF; # (렬; 렬; 렬; 렬; 렬; ) HANGUL SYLLABLE RYEOL
+B82D;B82D;1105 1167 11B0;B82D;1105 1167 11B0; # (렭; 렭; 렭; 렭; 렭; ) HANGUL SYLLABLE RYEOLG
+B82E;B82E;1105 1167 11B1;B82E;1105 1167 11B1; # (렮; 렮; 렮; 렮; 렮; ) HANGUL SYLLABLE RYEOLM
+B82F;B82F;1105 1167 11B2;B82F;1105 1167 11B2; # (렯; 렯; 렯; 렯; 렯; ) HANGUL SYLLABLE RYEOLB
+B830;B830;1105 1167 11B3;B830;1105 1167 11B3; # (렰; 렰; 렰; 렰; 렰; ) HANGUL SYLLABLE RYEOLS
+B831;B831;1105 1167 11B4;B831;1105 1167 11B4; # (렱; 렱; 렱; 렱; 렱; ) HANGUL SYLLABLE RYEOLT
+B832;B832;1105 1167 11B5;B832;1105 1167 11B5; # (렲; 렲; 렲; 렲; 렲; ) HANGUL SYLLABLE RYEOLP
+B833;B833;1105 1167 11B6;B833;1105 1167 11B6; # (렳; 렳; 렳; 렳; 렳; ) HANGUL SYLLABLE RYEOLH
+B834;B834;1105 1167 11B7;B834;1105 1167 11B7; # (렴; 렴; 렴; 렴; 렴; ) HANGUL SYLLABLE RYEOM
+B835;B835;1105 1167 11B8;B835;1105 1167 11B8; # (렵; 렵; 렵; 렵; 렵; ) HANGUL SYLLABLE RYEOB
+B836;B836;1105 1167 11B9;B836;1105 1167 11B9; # (렶; 렶; 렶; 렶; 렶; ) HANGUL SYLLABLE RYEOBS
+B837;B837;1105 1167 11BA;B837;1105 1167 11BA; # (렷; 렷; 렷; 렷; 렷; ) HANGUL SYLLABLE RYEOS
+B838;B838;1105 1167 11BB;B838;1105 1167 11BB; # (렸; 렸; 렸; 렸; 렸; ) HANGUL SYLLABLE RYEOSS
+B839;B839;1105 1167 11BC;B839;1105 1167 11BC; # (령; 령; 령; 령; 령; ) HANGUL SYLLABLE RYEONG
+B83A;B83A;1105 1167 11BD;B83A;1105 1167 11BD; # (렺; 렺; 렺; 렺; 렺; ) HANGUL SYLLABLE RYEOJ
+B83B;B83B;1105 1167 11BE;B83B;1105 1167 11BE; # (렻; 렻; 렻; 렻; 렻; ) HANGUL SYLLABLE RYEOC
+B83C;B83C;1105 1167 11BF;B83C;1105 1167 11BF; # (렼; 렼; 렼; 렼; 렼; ) HANGUL SYLLABLE RYEOK
+B83D;B83D;1105 1167 11C0;B83D;1105 1167 11C0; # (렽; 렽; 렽; 렽; 렽; ) HANGUL SYLLABLE RYEOT
+B83E;B83E;1105 1167 11C1;B83E;1105 1167 11C1; # (ë ¾; ë ¾; á„…á…§á‡; ë ¾; á„…á…§á‡; ) HANGUL SYLLABLE RYEOP
+B83F;B83F;1105 1167 11C2;B83F;1105 1167 11C2; # (렿; 렿; 렿; 렿; 렿; ) HANGUL SYLLABLE RYEOH
+B840;B840;1105 1168;B840;1105 1168; # (ë¡€; ë¡€; á„…á…¨; ë¡€; á„…á…¨; ) HANGUL SYLLABLE RYE
+B841;B841;1105 1168 11A8;B841;1105 1168 11A8; # (ë¡; ë¡; 롁; ë¡; 롁; ) HANGUL SYLLABLE RYEG
+B842;B842;1105 1168 11A9;B842;1105 1168 11A9; # (롂; 롂; 롂; 롂; 롂; ) HANGUL SYLLABLE RYEGG
+B843;B843;1105 1168 11AA;B843;1105 1168 11AA; # (롃; 롃; 롃; 롃; 롃; ) HANGUL SYLLABLE RYEGS
+B844;B844;1105 1168 11AB;B844;1105 1168 11AB; # (롄; 롄; 롄; 롄; 롄; ) HANGUL SYLLABLE RYEN
+B845;B845;1105 1168 11AC;B845;1105 1168 11AC; # (롅; 롅; 롅; 롅; 롅; ) HANGUL SYLLABLE RYENJ
+B846;B846;1105 1168 11AD;B846;1105 1168 11AD; # (롆; 롆; 롆; 롆; 롆; ) HANGUL SYLLABLE RYENH
+B847;B847;1105 1168 11AE;B847;1105 1168 11AE; # (롇; 롇; 롇; 롇; 롇; ) HANGUL SYLLABLE RYED
+B848;B848;1105 1168 11AF;B848;1105 1168 11AF; # (롈; 롈; 롈; 롈; 롈; ) HANGUL SYLLABLE RYEL
+B849;B849;1105 1168 11B0;B849;1105 1168 11B0; # (롉; 롉; 롉; 롉; 롉; ) HANGUL SYLLABLE RYELG
+B84A;B84A;1105 1168 11B1;B84A;1105 1168 11B1; # (롊; 롊; 롊; 롊; 롊; ) HANGUL SYLLABLE RYELM
+B84B;B84B;1105 1168 11B2;B84B;1105 1168 11B2; # (롋; 롋; 롋; 롋; 롋; ) HANGUL SYLLABLE RYELB
+B84C;B84C;1105 1168 11B3;B84C;1105 1168 11B3; # (롌; 롌; 롌; 롌; 롌; ) HANGUL SYLLABLE RYELS
+B84D;B84D;1105 1168 11B4;B84D;1105 1168 11B4; # (ë¡; ë¡; 롍; ë¡; 롍; ) HANGUL SYLLABLE RYELT
+B84E;B84E;1105 1168 11B5;B84E;1105 1168 11B5; # (롎; 롎; 롎; 롎; 롎; ) HANGUL SYLLABLE RYELP
+B84F;B84F;1105 1168 11B6;B84F;1105 1168 11B6; # (ë¡; ë¡; 롏; ë¡; 롏; ) HANGUL SYLLABLE RYELH
+B850;B850;1105 1168 11B7;B850;1105 1168 11B7; # (ë¡; ë¡; 롐; ë¡; 롐; ) HANGUL SYLLABLE RYEM
+B851;B851;1105 1168 11B8;B851;1105 1168 11B8; # (롑; 롑; 롑; 롑; 롑; ) HANGUL SYLLABLE RYEB
+B852;B852;1105 1168 11B9;B852;1105 1168 11B9; # (롒; 롒; 롒; 롒; 롒; ) HANGUL SYLLABLE RYEBS
+B853;B853;1105 1168 11BA;B853;1105 1168 11BA; # (롓; 롓; 롓; 롓; 롓; ) HANGUL SYLLABLE RYES
+B854;B854;1105 1168 11BB;B854;1105 1168 11BB; # (롔; 롔; 롔; 롔; 롔; ) HANGUL SYLLABLE RYESS
+B855;B855;1105 1168 11BC;B855;1105 1168 11BC; # (롕; 롕; 롕; 롕; 롕; ) HANGUL SYLLABLE RYENG
+B856;B856;1105 1168 11BD;B856;1105 1168 11BD; # (롖; 롖; 롖; 롖; 롖; ) HANGUL SYLLABLE RYEJ
+B857;B857;1105 1168 11BE;B857;1105 1168 11BE; # (롗; 롗; 롗; 롗; 롗; ) HANGUL SYLLABLE RYEC
+B858;B858;1105 1168 11BF;B858;1105 1168 11BF; # (롘; 롘; 롘; 롘; 롘; ) HANGUL SYLLABLE RYEK
+B859;B859;1105 1168 11C0;B859;1105 1168 11C0; # (롙; 롙; 롙; 롙; 롙; ) HANGUL SYLLABLE RYET
+B85A;B85A;1105 1168 11C1;B85A;1105 1168 11C1; # (ë¡š; ë¡š; á„…á…¨á‡; ë¡š; á„…á…¨á‡; ) HANGUL SYLLABLE RYEP
+B85B;B85B;1105 1168 11C2;B85B;1105 1168 11C2; # (롛; 롛; 롛; 롛; 롛; ) HANGUL SYLLABLE RYEH
+B85C;B85C;1105 1169;B85C;1105 1169; # (로; 로; 로; 로; 로; ) HANGUL SYLLABLE RO
+B85D;B85D;1105 1169 11A8;B85D;1105 1169 11A8; # (ë¡; ë¡; 록; ë¡; 록; ) HANGUL SYLLABLE ROG
+B85E;B85E;1105 1169 11A9;B85E;1105 1169 11A9; # (롞; 롞; 롞; 롞; 롞; ) HANGUL SYLLABLE ROGG
+B85F;B85F;1105 1169 11AA;B85F;1105 1169 11AA; # (롟; 롟; 롟; 롟; 롟; ) HANGUL SYLLABLE ROGS
+B860;B860;1105 1169 11AB;B860;1105 1169 11AB; # (론; 론; 론; 론; 론; ) HANGUL SYLLABLE RON
+B861;B861;1105 1169 11AC;B861;1105 1169 11AC; # (롡; 롡; 롡; 롡; 롡; ) HANGUL SYLLABLE RONJ
+B862;B862;1105 1169 11AD;B862;1105 1169 11AD; # (롢; 롢; 롢; 롢; 롢; ) HANGUL SYLLABLE RONH
+B863;B863;1105 1169 11AE;B863;1105 1169 11AE; # (롣; 롣; 롣; 롣; 롣; ) HANGUL SYLLABLE ROD
+B864;B864;1105 1169 11AF;B864;1105 1169 11AF; # (롤; 롤; 롤; 롤; 롤; ) HANGUL SYLLABLE ROL
+B865;B865;1105 1169 11B0;B865;1105 1169 11B0; # (롥; 롥; 롥; 롥; 롥; ) HANGUL SYLLABLE ROLG
+B866;B866;1105 1169 11B1;B866;1105 1169 11B1; # (롦; 롦; 롦; 롦; 롦; ) HANGUL SYLLABLE ROLM
+B867;B867;1105 1169 11B2;B867;1105 1169 11B2; # (롧; 롧; 롧; 롧; 롧; ) HANGUL SYLLABLE ROLB
+B868;B868;1105 1169 11B3;B868;1105 1169 11B3; # (롨; 롨; 롨; 롨; 롨; ) HANGUL SYLLABLE ROLS
+B869;B869;1105 1169 11B4;B869;1105 1169 11B4; # (롩; 롩; 롩; 롩; 롩; ) HANGUL SYLLABLE ROLT
+B86A;B86A;1105 1169 11B5;B86A;1105 1169 11B5; # (롪; 롪; 롪; 롪; 롪; ) HANGUL SYLLABLE ROLP
+B86B;B86B;1105 1169 11B6;B86B;1105 1169 11B6; # (롫; 롫; 롫; 롫; 롫; ) HANGUL SYLLABLE ROLH
+B86C;B86C;1105 1169 11B7;B86C;1105 1169 11B7; # (롬; 롬; 롬; 롬; 롬; ) HANGUL SYLLABLE ROM
+B86D;B86D;1105 1169 11B8;B86D;1105 1169 11B8; # (롭; 롭; 롭; 롭; 롭; ) HANGUL SYLLABLE ROB
+B86E;B86E;1105 1169 11B9;B86E;1105 1169 11B9; # (롮; 롮; 롮; 롮; 롮; ) HANGUL SYLLABLE ROBS
+B86F;B86F;1105 1169 11BA;B86F;1105 1169 11BA; # (롯; 롯; 롯; 롯; 롯; ) HANGUL SYLLABLE ROS
+B870;B870;1105 1169 11BB;B870;1105 1169 11BB; # (롰; 롰; 롰; 롰; 롰; ) HANGUL SYLLABLE ROSS
+B871;B871;1105 1169 11BC;B871;1105 1169 11BC; # (롱; 롱; 롱; 롱; 롱; ) HANGUL SYLLABLE RONG
+B872;B872;1105 1169 11BD;B872;1105 1169 11BD; # (롲; 롲; 롲; 롲; 롲; ) HANGUL SYLLABLE ROJ
+B873;B873;1105 1169 11BE;B873;1105 1169 11BE; # (롳; 롳; 롳; 롳; 롳; ) HANGUL SYLLABLE ROC
+B874;B874;1105 1169 11BF;B874;1105 1169 11BF; # (롴; 롴; 롴; 롴; 롴; ) HANGUL SYLLABLE ROK
+B875;B875;1105 1169 11C0;B875;1105 1169 11C0; # (롵; 롵; 롵; 롵; 롵; ) HANGUL SYLLABLE ROT
+B876;B876;1105 1169 11C1;B876;1105 1169 11C1; # (롶; 롶; á„…á…©á‡; 롶; á„…á…©á‡; ) HANGUL SYLLABLE ROP
+B877;B877;1105 1169 11C2;B877;1105 1169 11C2; # (롷; 롷; 롷; 롷; 롷; ) HANGUL SYLLABLE ROH
+B878;B878;1105 116A;B878;1105 116A; # (롸; 롸; 롸; 롸; 롸; ) HANGUL SYLLABLE RWA
+B879;B879;1105 116A 11A8;B879;1105 116A 11A8; # (롹; 롹; 롹; 롹; 롹; ) HANGUL SYLLABLE RWAG
+B87A;B87A;1105 116A 11A9;B87A;1105 116A 11A9; # (롺; 롺; 롺; 롺; 롺; ) HANGUL SYLLABLE RWAGG
+B87B;B87B;1105 116A 11AA;B87B;1105 116A 11AA; # (롻; 롻; 롻; 롻; 롻; ) HANGUL SYLLABLE RWAGS
+B87C;B87C;1105 116A 11AB;B87C;1105 116A 11AB; # (롼; 롼; 롼; 롼; 롼; ) HANGUL SYLLABLE RWAN
+B87D;B87D;1105 116A 11AC;B87D;1105 116A 11AC; # (롽; 롽; 롽; 롽; 롽; ) HANGUL SYLLABLE RWANJ
+B87E;B87E;1105 116A 11AD;B87E;1105 116A 11AD; # (롾; 롾; 롾; 롾; 롾; ) HANGUL SYLLABLE RWANH
+B87F;B87F;1105 116A 11AE;B87F;1105 116A 11AE; # (롿; 롿; 롿; 롿; 롿; ) HANGUL SYLLABLE RWAD
+B880;B880;1105 116A 11AF;B880;1105 116A 11AF; # (뢀; 뢀; 뢀; 뢀; 뢀; ) HANGUL SYLLABLE RWAL
+B881;B881;1105 116A 11B0;B881;1105 116A 11B0; # (ë¢; ë¢; 뢁; ë¢; 뢁; ) HANGUL SYLLABLE RWALG
+B882;B882;1105 116A 11B1;B882;1105 116A 11B1; # (뢂; 뢂; 뢂; 뢂; 뢂; ) HANGUL SYLLABLE RWALM
+B883;B883;1105 116A 11B2;B883;1105 116A 11B2; # (뢃; 뢃; 뢃; 뢃; 뢃; ) HANGUL SYLLABLE RWALB
+B884;B884;1105 116A 11B3;B884;1105 116A 11B3; # (뢄; 뢄; 뢄; 뢄; 뢄; ) HANGUL SYLLABLE RWALS
+B885;B885;1105 116A 11B4;B885;1105 116A 11B4; # (뢅; 뢅; 뢅; 뢅; 뢅; ) HANGUL SYLLABLE RWALT
+B886;B886;1105 116A 11B5;B886;1105 116A 11B5; # (뢆; 뢆; 뢆; 뢆; 뢆; ) HANGUL SYLLABLE RWALP
+B887;B887;1105 116A 11B6;B887;1105 116A 11B6; # (뢇; 뢇; 뢇; 뢇; 뢇; ) HANGUL SYLLABLE RWALH
+B888;B888;1105 116A 11B7;B888;1105 116A 11B7; # (뢈; 뢈; 뢈; 뢈; 뢈; ) HANGUL SYLLABLE RWAM
+B889;B889;1105 116A 11B8;B889;1105 116A 11B8; # (뢉; 뢉; 뢉; 뢉; 뢉; ) HANGUL SYLLABLE RWAB
+B88A;B88A;1105 116A 11B9;B88A;1105 116A 11B9; # (뢊; 뢊; 뢊; 뢊; 뢊; ) HANGUL SYLLABLE RWABS
+B88B;B88B;1105 116A 11BA;B88B;1105 116A 11BA; # (뢋; 뢋; 뢋; 뢋; 뢋; ) HANGUL SYLLABLE RWAS
+B88C;B88C;1105 116A 11BB;B88C;1105 116A 11BB; # (뢌; 뢌; 뢌; 뢌; 뢌; ) HANGUL SYLLABLE RWASS
+B88D;B88D;1105 116A 11BC;B88D;1105 116A 11BC; # (ë¢; ë¢; 뢍; ë¢; 뢍; ) HANGUL SYLLABLE RWANG
+B88E;B88E;1105 116A 11BD;B88E;1105 116A 11BD; # (뢎; 뢎; 뢎; 뢎; 뢎; ) HANGUL SYLLABLE RWAJ
+B88F;B88F;1105 116A 11BE;B88F;1105 116A 11BE; # (ë¢; ë¢; 뢏; ë¢; 뢏; ) HANGUL SYLLABLE RWAC
+B890;B890;1105 116A 11BF;B890;1105 116A 11BF; # (ë¢; ë¢; 뢐; ë¢; 뢐; ) HANGUL SYLLABLE RWAK
+B891;B891;1105 116A 11C0;B891;1105 116A 11C0; # (뢑; 뢑; 뢑; 뢑; 뢑; ) HANGUL SYLLABLE RWAT
+B892;B892;1105 116A 11C1;B892;1105 116A 11C1; # (뢒; 뢒; á„…á…ªá‡; 뢒; á„…á…ªá‡; ) HANGUL SYLLABLE RWAP
+B893;B893;1105 116A 11C2;B893;1105 116A 11C2; # (뢓; 뢓; 뢓; 뢓; 뢓; ) HANGUL SYLLABLE RWAH
+B894;B894;1105 116B;B894;1105 116B; # (뢔; 뢔; 뢔; 뢔; 뢔; ) HANGUL SYLLABLE RWAE
+B895;B895;1105 116B 11A8;B895;1105 116B 11A8; # (뢕; 뢕; 뢕; 뢕; 뢕; ) HANGUL SYLLABLE RWAEG
+B896;B896;1105 116B 11A9;B896;1105 116B 11A9; # (뢖; 뢖; 뢖; 뢖; 뢖; ) HANGUL SYLLABLE RWAEGG
+B897;B897;1105 116B 11AA;B897;1105 116B 11AA; # (뢗; 뢗; 뢗; 뢗; 뢗; ) HANGUL SYLLABLE RWAEGS
+B898;B898;1105 116B 11AB;B898;1105 116B 11AB; # (뢘; 뢘; 뢘; 뢘; 뢘; ) HANGUL SYLLABLE RWAEN
+B899;B899;1105 116B 11AC;B899;1105 116B 11AC; # (뢙; 뢙; 뢙; 뢙; 뢙; ) HANGUL SYLLABLE RWAENJ
+B89A;B89A;1105 116B 11AD;B89A;1105 116B 11AD; # (뢚; 뢚; 뢚; 뢚; 뢚; ) HANGUL SYLLABLE RWAENH
+B89B;B89B;1105 116B 11AE;B89B;1105 116B 11AE; # (뢛; 뢛; 뢛; 뢛; 뢛; ) HANGUL SYLLABLE RWAED
+B89C;B89C;1105 116B 11AF;B89C;1105 116B 11AF; # (뢜; 뢜; 뢜; 뢜; 뢜; ) HANGUL SYLLABLE RWAEL
+B89D;B89D;1105 116B 11B0;B89D;1105 116B 11B0; # (ë¢; ë¢; 뢝; ë¢; 뢝; ) HANGUL SYLLABLE RWAELG
+B89E;B89E;1105 116B 11B1;B89E;1105 116B 11B1; # (뢞; 뢞; 뢞; 뢞; 뢞; ) HANGUL SYLLABLE RWAELM
+B89F;B89F;1105 116B 11B2;B89F;1105 116B 11B2; # (뢟; 뢟; 뢟; 뢟; 뢟; ) HANGUL SYLLABLE RWAELB
+B8A0;B8A0;1105 116B 11B3;B8A0;1105 116B 11B3; # (뢠; 뢠; 뢠; 뢠; 뢠; ) HANGUL SYLLABLE RWAELS
+B8A1;B8A1;1105 116B 11B4;B8A1;1105 116B 11B4; # (뢡; 뢡; 뢡; 뢡; 뢡; ) HANGUL SYLLABLE RWAELT
+B8A2;B8A2;1105 116B 11B5;B8A2;1105 116B 11B5; # (뢢; 뢢; 뢢; 뢢; 뢢; ) HANGUL SYLLABLE RWAELP
+B8A3;B8A3;1105 116B 11B6;B8A3;1105 116B 11B6; # (뢣; 뢣; 뢣; 뢣; 뢣; ) HANGUL SYLLABLE RWAELH
+B8A4;B8A4;1105 116B 11B7;B8A4;1105 116B 11B7; # (뢤; 뢤; 뢤; 뢤; 뢤; ) HANGUL SYLLABLE RWAEM
+B8A5;B8A5;1105 116B 11B8;B8A5;1105 116B 11B8; # (뢥; 뢥; 뢥; 뢥; 뢥; ) HANGUL SYLLABLE RWAEB
+B8A6;B8A6;1105 116B 11B9;B8A6;1105 116B 11B9; # (뢦; 뢦; 뢦; 뢦; 뢦; ) HANGUL SYLLABLE RWAEBS
+B8A7;B8A7;1105 116B 11BA;B8A7;1105 116B 11BA; # (뢧; 뢧; 뢧; 뢧; 뢧; ) HANGUL SYLLABLE RWAES
+B8A8;B8A8;1105 116B 11BB;B8A8;1105 116B 11BB; # (뢨; 뢨; 뢨; 뢨; 뢨; ) HANGUL SYLLABLE RWAESS
+B8A9;B8A9;1105 116B 11BC;B8A9;1105 116B 11BC; # (뢩; 뢩; 뢩; 뢩; 뢩; ) HANGUL SYLLABLE RWAENG
+B8AA;B8AA;1105 116B 11BD;B8AA;1105 116B 11BD; # (뢪; 뢪; 뢪; 뢪; 뢪; ) HANGUL SYLLABLE RWAEJ
+B8AB;B8AB;1105 116B 11BE;B8AB;1105 116B 11BE; # (뢫; 뢫; 뢫; 뢫; 뢫; ) HANGUL SYLLABLE RWAEC
+B8AC;B8AC;1105 116B 11BF;B8AC;1105 116B 11BF; # (뢬; 뢬; 뢬; 뢬; 뢬; ) HANGUL SYLLABLE RWAEK
+B8AD;B8AD;1105 116B 11C0;B8AD;1105 116B 11C0; # (뢭; 뢭; 뢭; 뢭; 뢭; ) HANGUL SYLLABLE RWAET
+B8AE;B8AE;1105 116B 11C1;B8AE;1105 116B 11C1; # (뢮; 뢮; á„…á…«á‡; 뢮; á„…á…«á‡; ) HANGUL SYLLABLE RWAEP
+B8AF;B8AF;1105 116B 11C2;B8AF;1105 116B 11C2; # (뢯; 뢯; 뢯; 뢯; 뢯; ) HANGUL SYLLABLE RWAEH
+B8B0;B8B0;1105 116C;B8B0;1105 116C; # (뢰; 뢰; 뢰; 뢰; 뢰; ) HANGUL SYLLABLE ROE
+B8B1;B8B1;1105 116C 11A8;B8B1;1105 116C 11A8; # (뢱; 뢱; 뢱; 뢱; 뢱; ) HANGUL SYLLABLE ROEG
+B8B2;B8B2;1105 116C 11A9;B8B2;1105 116C 11A9; # (뢲; 뢲; 뢲; 뢲; 뢲; ) HANGUL SYLLABLE ROEGG
+B8B3;B8B3;1105 116C 11AA;B8B3;1105 116C 11AA; # (뢳; 뢳; 뢳; 뢳; 뢳; ) HANGUL SYLLABLE ROEGS
+B8B4;B8B4;1105 116C 11AB;B8B4;1105 116C 11AB; # (뢴; 뢴; 뢴; 뢴; 뢴; ) HANGUL SYLLABLE ROEN
+B8B5;B8B5;1105 116C 11AC;B8B5;1105 116C 11AC; # (뢵; 뢵; 뢵; 뢵; 뢵; ) HANGUL SYLLABLE ROENJ
+B8B6;B8B6;1105 116C 11AD;B8B6;1105 116C 11AD; # (뢶; 뢶; 뢶; 뢶; 뢶; ) HANGUL SYLLABLE ROENH
+B8B7;B8B7;1105 116C 11AE;B8B7;1105 116C 11AE; # (뢷; 뢷; 뢷; 뢷; 뢷; ) HANGUL SYLLABLE ROED
+B8B8;B8B8;1105 116C 11AF;B8B8;1105 116C 11AF; # (뢸; 뢸; 뢸; 뢸; 뢸; ) HANGUL SYLLABLE ROEL
+B8B9;B8B9;1105 116C 11B0;B8B9;1105 116C 11B0; # (뢹; 뢹; 뢹; 뢹; 뢹; ) HANGUL SYLLABLE ROELG
+B8BA;B8BA;1105 116C 11B1;B8BA;1105 116C 11B1; # (뢺; 뢺; 뢺; 뢺; 뢺; ) HANGUL SYLLABLE ROELM
+B8BB;B8BB;1105 116C 11B2;B8BB;1105 116C 11B2; # (뢻; 뢻; 뢻; 뢻; 뢻; ) HANGUL SYLLABLE ROELB
+B8BC;B8BC;1105 116C 11B3;B8BC;1105 116C 11B3; # (뢼; 뢼; 뢼; 뢼; 뢼; ) HANGUL SYLLABLE ROELS
+B8BD;B8BD;1105 116C 11B4;B8BD;1105 116C 11B4; # (뢽; 뢽; 뢽; 뢽; 뢽; ) HANGUL SYLLABLE ROELT
+B8BE;B8BE;1105 116C 11B5;B8BE;1105 116C 11B5; # (뢾; 뢾; 뢾; 뢾; 뢾; ) HANGUL SYLLABLE ROELP
+B8BF;B8BF;1105 116C 11B6;B8BF;1105 116C 11B6; # (뢿; 뢿; 뢿; 뢿; 뢿; ) HANGUL SYLLABLE ROELH
+B8C0;B8C0;1105 116C 11B7;B8C0;1105 116C 11B7; # (룀; 룀; 룀; 룀; 룀; ) HANGUL SYLLABLE ROEM
+B8C1;B8C1;1105 116C 11B8;B8C1;1105 116C 11B8; # (ë£; ë£; 룁; ë£; 룁; ) HANGUL SYLLABLE ROEB
+B8C2;B8C2;1105 116C 11B9;B8C2;1105 116C 11B9; # (룂; 룂; 룂; 룂; 룂; ) HANGUL SYLLABLE ROEBS
+B8C3;B8C3;1105 116C 11BA;B8C3;1105 116C 11BA; # (룃; 룃; 룃; 룃; 룃; ) HANGUL SYLLABLE ROES
+B8C4;B8C4;1105 116C 11BB;B8C4;1105 116C 11BB; # (룄; 룄; 룄; 룄; 룄; ) HANGUL SYLLABLE ROESS
+B8C5;B8C5;1105 116C 11BC;B8C5;1105 116C 11BC; # (룅; 룅; 룅; 룅; 룅; ) HANGUL SYLLABLE ROENG
+B8C6;B8C6;1105 116C 11BD;B8C6;1105 116C 11BD; # (룆; 룆; 룆; 룆; 룆; ) HANGUL SYLLABLE ROEJ
+B8C7;B8C7;1105 116C 11BE;B8C7;1105 116C 11BE; # (룇; 룇; 룇; 룇; 룇; ) HANGUL SYLLABLE ROEC
+B8C8;B8C8;1105 116C 11BF;B8C8;1105 116C 11BF; # (룈; 룈; 룈; 룈; 룈; ) HANGUL SYLLABLE ROEK
+B8C9;B8C9;1105 116C 11C0;B8C9;1105 116C 11C0; # (룉; 룉; 룉; 룉; 룉; ) HANGUL SYLLABLE ROET
+B8CA;B8CA;1105 116C 11C1;B8CA;1105 116C 11C1; # (룊; 룊; á„…á…¬á‡; 룊; á„…á…¬á‡; ) HANGUL SYLLABLE ROEP
+B8CB;B8CB;1105 116C 11C2;B8CB;1105 116C 11C2; # (룋; 룋; 룋; 룋; 룋; ) HANGUL SYLLABLE ROEH
+B8CC;B8CC;1105 116D;B8CC;1105 116D; # (료; 료; 료; 료; 료; ) HANGUL SYLLABLE RYO
+B8CD;B8CD;1105 116D 11A8;B8CD;1105 116D 11A8; # (ë£; ë£; 룍; ë£; 룍; ) HANGUL SYLLABLE RYOG
+B8CE;B8CE;1105 116D 11A9;B8CE;1105 116D 11A9; # (룎; 룎; 룎; 룎; 룎; ) HANGUL SYLLABLE RYOGG
+B8CF;B8CF;1105 116D 11AA;B8CF;1105 116D 11AA; # (ë£; ë£; 룏; ë£; 룏; ) HANGUL SYLLABLE RYOGS
+B8D0;B8D0;1105 116D 11AB;B8D0;1105 116D 11AB; # (ë£; ë£; 룐; ë£; 룐; ) HANGUL SYLLABLE RYON
+B8D1;B8D1;1105 116D 11AC;B8D1;1105 116D 11AC; # (룑; 룑; 룑; 룑; 룑; ) HANGUL SYLLABLE RYONJ
+B8D2;B8D2;1105 116D 11AD;B8D2;1105 116D 11AD; # (룒; 룒; 룒; 룒; 룒; ) HANGUL SYLLABLE RYONH
+B8D3;B8D3;1105 116D 11AE;B8D3;1105 116D 11AE; # (룓; 룓; 룓; 룓; 룓; ) HANGUL SYLLABLE RYOD
+B8D4;B8D4;1105 116D 11AF;B8D4;1105 116D 11AF; # (룔; 룔; 룔; 룔; 룔; ) HANGUL SYLLABLE RYOL
+B8D5;B8D5;1105 116D 11B0;B8D5;1105 116D 11B0; # (룕; 룕; 룕; 룕; 룕; ) HANGUL SYLLABLE RYOLG
+B8D6;B8D6;1105 116D 11B1;B8D6;1105 116D 11B1; # (룖; 룖; 룖; 룖; 룖; ) HANGUL SYLLABLE RYOLM
+B8D7;B8D7;1105 116D 11B2;B8D7;1105 116D 11B2; # (룗; 룗; 룗; 룗; 룗; ) HANGUL SYLLABLE RYOLB
+B8D8;B8D8;1105 116D 11B3;B8D8;1105 116D 11B3; # (룘; 룘; 룘; 룘; 룘; ) HANGUL SYLLABLE RYOLS
+B8D9;B8D9;1105 116D 11B4;B8D9;1105 116D 11B4; # (룙; 룙; 룙; 룙; 룙; ) HANGUL SYLLABLE RYOLT
+B8DA;B8DA;1105 116D 11B5;B8DA;1105 116D 11B5; # (룚; 룚; 룚; 룚; 룚; ) HANGUL SYLLABLE RYOLP
+B8DB;B8DB;1105 116D 11B6;B8DB;1105 116D 11B6; # (룛; 룛; 룛; 룛; 룛; ) HANGUL SYLLABLE RYOLH
+B8DC;B8DC;1105 116D 11B7;B8DC;1105 116D 11B7; # (룜; 룜; 룜; 룜; 룜; ) HANGUL SYLLABLE RYOM
+B8DD;B8DD;1105 116D 11B8;B8DD;1105 116D 11B8; # (ë£; ë£; 룝; ë£; 룝; ) HANGUL SYLLABLE RYOB
+B8DE;B8DE;1105 116D 11B9;B8DE;1105 116D 11B9; # (룞; 룞; 룞; 룞; 룞; ) HANGUL SYLLABLE RYOBS
+B8DF;B8DF;1105 116D 11BA;B8DF;1105 116D 11BA; # (룟; 룟; 룟; 룟; 룟; ) HANGUL SYLLABLE RYOS
+B8E0;B8E0;1105 116D 11BB;B8E0;1105 116D 11BB; # (룠; 룠; 룠; 룠; 룠; ) HANGUL SYLLABLE RYOSS
+B8E1;B8E1;1105 116D 11BC;B8E1;1105 116D 11BC; # (룡; 룡; 룡; 룡; 룡; ) HANGUL SYLLABLE RYONG
+B8E2;B8E2;1105 116D 11BD;B8E2;1105 116D 11BD; # (룢; 룢; 룢; 룢; 룢; ) HANGUL SYLLABLE RYOJ
+B8E3;B8E3;1105 116D 11BE;B8E3;1105 116D 11BE; # (룣; 룣; 룣; 룣; 룣; ) HANGUL SYLLABLE RYOC
+B8E4;B8E4;1105 116D 11BF;B8E4;1105 116D 11BF; # (룤; 룤; 룤; 룤; 룤; ) HANGUL SYLLABLE RYOK
+B8E5;B8E5;1105 116D 11C0;B8E5;1105 116D 11C0; # (룥; 룥; 룥; 룥; 룥; ) HANGUL SYLLABLE RYOT
+B8E6;B8E6;1105 116D 11C1;B8E6;1105 116D 11C1; # (룦; 룦; á„…á…­á‡; 룦; á„…á…­á‡; ) HANGUL SYLLABLE RYOP
+B8E7;B8E7;1105 116D 11C2;B8E7;1105 116D 11C2; # (룧; 룧; 룧; 룧; 룧; ) HANGUL SYLLABLE RYOH
+B8E8;B8E8;1105 116E;B8E8;1105 116E; # (루; 루; 루; 루; 루; ) HANGUL SYLLABLE RU
+B8E9;B8E9;1105 116E 11A8;B8E9;1105 116E 11A8; # (룩; 룩; 룩; 룩; 룩; ) HANGUL SYLLABLE RUG
+B8EA;B8EA;1105 116E 11A9;B8EA;1105 116E 11A9; # (룪; 룪; 룪; 룪; 룪; ) HANGUL SYLLABLE RUGG
+B8EB;B8EB;1105 116E 11AA;B8EB;1105 116E 11AA; # (룫; 룫; 룫; 룫; 룫; ) HANGUL SYLLABLE RUGS
+B8EC;B8EC;1105 116E 11AB;B8EC;1105 116E 11AB; # (룬; 룬; 룬; 룬; 룬; ) HANGUL SYLLABLE RUN
+B8ED;B8ED;1105 116E 11AC;B8ED;1105 116E 11AC; # (룭; 룭; 룭; 룭; 룭; ) HANGUL SYLLABLE RUNJ
+B8EE;B8EE;1105 116E 11AD;B8EE;1105 116E 11AD; # (룮; 룮; 룮; 룮; 룮; ) HANGUL SYLLABLE RUNH
+B8EF;B8EF;1105 116E 11AE;B8EF;1105 116E 11AE; # (룯; 룯; 룯; 룯; 룯; ) HANGUL SYLLABLE RUD
+B8F0;B8F0;1105 116E 11AF;B8F0;1105 116E 11AF; # (룰; 룰; 룰; 룰; 룰; ) HANGUL SYLLABLE RUL
+B8F1;B8F1;1105 116E 11B0;B8F1;1105 116E 11B0; # (룱; 룱; 룱; 룱; 룱; ) HANGUL SYLLABLE RULG
+B8F2;B8F2;1105 116E 11B1;B8F2;1105 116E 11B1; # (룲; 룲; 룲; 룲; 룲; ) HANGUL SYLLABLE RULM
+B8F3;B8F3;1105 116E 11B2;B8F3;1105 116E 11B2; # (룳; 룳; 룳; 룳; 룳; ) HANGUL SYLLABLE RULB
+B8F4;B8F4;1105 116E 11B3;B8F4;1105 116E 11B3; # (룴; 룴; 룴; 룴; 룴; ) HANGUL SYLLABLE RULS
+B8F5;B8F5;1105 116E 11B4;B8F5;1105 116E 11B4; # (룵; 룵; 룵; 룵; 룵; ) HANGUL SYLLABLE RULT
+B8F6;B8F6;1105 116E 11B5;B8F6;1105 116E 11B5; # (룶; 룶; 룶; 룶; 룶; ) HANGUL SYLLABLE RULP
+B8F7;B8F7;1105 116E 11B6;B8F7;1105 116E 11B6; # (룷; 룷; 룷; 룷; 룷; ) HANGUL SYLLABLE RULH
+B8F8;B8F8;1105 116E 11B7;B8F8;1105 116E 11B7; # (룸; 룸; 룸; 룸; 룸; ) HANGUL SYLLABLE RUM
+B8F9;B8F9;1105 116E 11B8;B8F9;1105 116E 11B8; # (룹; 룹; 룹; 룹; 룹; ) HANGUL SYLLABLE RUB
+B8FA;B8FA;1105 116E 11B9;B8FA;1105 116E 11B9; # (룺; 룺; 룺; 룺; 룺; ) HANGUL SYLLABLE RUBS
+B8FB;B8FB;1105 116E 11BA;B8FB;1105 116E 11BA; # (룻; 룻; 룻; 룻; 룻; ) HANGUL SYLLABLE RUS
+B8FC;B8FC;1105 116E 11BB;B8FC;1105 116E 11BB; # (룼; 룼; 룼; 룼; 룼; ) HANGUL SYLLABLE RUSS
+B8FD;B8FD;1105 116E 11BC;B8FD;1105 116E 11BC; # (룽; 룽; 룽; 룽; 룽; ) HANGUL SYLLABLE RUNG
+B8FE;B8FE;1105 116E 11BD;B8FE;1105 116E 11BD; # (룾; 룾; 룾; 룾; 룾; ) HANGUL SYLLABLE RUJ
+B8FF;B8FF;1105 116E 11BE;B8FF;1105 116E 11BE; # (룿; 룿; 룿; 룿; 룿; ) HANGUL SYLLABLE RUC
+B900;B900;1105 116E 11BF;B900;1105 116E 11BF; # (뤀; 뤀; 뤀; 뤀; 뤀; ) HANGUL SYLLABLE RUK
+B901;B901;1105 116E 11C0;B901;1105 116E 11C0; # (ë¤; ë¤; 뤁; ë¤; 뤁; ) HANGUL SYLLABLE RUT
+B902;B902;1105 116E 11C1;B902;1105 116E 11C1; # (뤂; 뤂; á„…á…®á‡; 뤂; á„…á…®á‡; ) HANGUL SYLLABLE RUP
+B903;B903;1105 116E 11C2;B903;1105 116E 11C2; # (뤃; 뤃; 뤃; 뤃; 뤃; ) HANGUL SYLLABLE RUH
+B904;B904;1105 116F;B904;1105 116F; # (뤄; 뤄; 뤄; 뤄; 뤄; ) HANGUL SYLLABLE RWEO
+B905;B905;1105 116F 11A8;B905;1105 116F 11A8; # (뤅; 뤅; 뤅; 뤅; 뤅; ) HANGUL SYLLABLE RWEOG
+B906;B906;1105 116F 11A9;B906;1105 116F 11A9; # (뤆; 뤆; 뤆; 뤆; 뤆; ) HANGUL SYLLABLE RWEOGG
+B907;B907;1105 116F 11AA;B907;1105 116F 11AA; # (뤇; 뤇; 뤇; 뤇; 뤇; ) HANGUL SYLLABLE RWEOGS
+B908;B908;1105 116F 11AB;B908;1105 116F 11AB; # (뤈; 뤈; 뤈; 뤈; 뤈; ) HANGUL SYLLABLE RWEON
+B909;B909;1105 116F 11AC;B909;1105 116F 11AC; # (뤉; 뤉; 뤉; 뤉; 뤉; ) HANGUL SYLLABLE RWEONJ
+B90A;B90A;1105 116F 11AD;B90A;1105 116F 11AD; # (뤊; 뤊; 뤊; 뤊; 뤊; ) HANGUL SYLLABLE RWEONH
+B90B;B90B;1105 116F 11AE;B90B;1105 116F 11AE; # (뤋; 뤋; 뤋; 뤋; 뤋; ) HANGUL SYLLABLE RWEOD
+B90C;B90C;1105 116F 11AF;B90C;1105 116F 11AF; # (뤌; 뤌; 뤌; 뤌; 뤌; ) HANGUL SYLLABLE RWEOL
+B90D;B90D;1105 116F 11B0;B90D;1105 116F 11B0; # (ë¤; ë¤; 뤍; ë¤; 뤍; ) HANGUL SYLLABLE RWEOLG
+B90E;B90E;1105 116F 11B1;B90E;1105 116F 11B1; # (뤎; 뤎; 뤎; 뤎; 뤎; ) HANGUL SYLLABLE RWEOLM
+B90F;B90F;1105 116F 11B2;B90F;1105 116F 11B2; # (ë¤; ë¤; 뤏; ë¤; 뤏; ) HANGUL SYLLABLE RWEOLB
+B910;B910;1105 116F 11B3;B910;1105 116F 11B3; # (ë¤; ë¤; 뤐; ë¤; 뤐; ) HANGUL SYLLABLE RWEOLS
+B911;B911;1105 116F 11B4;B911;1105 116F 11B4; # (뤑; 뤑; 뤑; 뤑; 뤑; ) HANGUL SYLLABLE RWEOLT
+B912;B912;1105 116F 11B5;B912;1105 116F 11B5; # (뤒; 뤒; 뤒; 뤒; 뤒; ) HANGUL SYLLABLE RWEOLP
+B913;B913;1105 116F 11B6;B913;1105 116F 11B6; # (뤓; 뤓; 뤓; 뤓; 뤓; ) HANGUL SYLLABLE RWEOLH
+B914;B914;1105 116F 11B7;B914;1105 116F 11B7; # (뤔; 뤔; 뤔; 뤔; 뤔; ) HANGUL SYLLABLE RWEOM
+B915;B915;1105 116F 11B8;B915;1105 116F 11B8; # (뤕; 뤕; 뤕; 뤕; 뤕; ) HANGUL SYLLABLE RWEOB
+B916;B916;1105 116F 11B9;B916;1105 116F 11B9; # (뤖; 뤖; 뤖; 뤖; 뤖; ) HANGUL SYLLABLE RWEOBS
+B917;B917;1105 116F 11BA;B917;1105 116F 11BA; # (뤗; 뤗; 뤗; 뤗; 뤗; ) HANGUL SYLLABLE RWEOS
+B918;B918;1105 116F 11BB;B918;1105 116F 11BB; # (뤘; 뤘; 뤘; 뤘; 뤘; ) HANGUL SYLLABLE RWEOSS
+B919;B919;1105 116F 11BC;B919;1105 116F 11BC; # (뤙; 뤙; 뤙; 뤙; 뤙; ) HANGUL SYLLABLE RWEONG
+B91A;B91A;1105 116F 11BD;B91A;1105 116F 11BD; # (뤚; 뤚; 뤚; 뤚; 뤚; ) HANGUL SYLLABLE RWEOJ
+B91B;B91B;1105 116F 11BE;B91B;1105 116F 11BE; # (뤛; 뤛; 뤛; 뤛; 뤛; ) HANGUL SYLLABLE RWEOC
+B91C;B91C;1105 116F 11BF;B91C;1105 116F 11BF; # (뤜; 뤜; 뤜; 뤜; 뤜; ) HANGUL SYLLABLE RWEOK
+B91D;B91D;1105 116F 11C0;B91D;1105 116F 11C0; # (ë¤; ë¤; 뤝; ë¤; 뤝; ) HANGUL SYLLABLE RWEOT
+B91E;B91E;1105 116F 11C1;B91E;1105 116F 11C1; # (뤞; 뤞; á„…á…¯á‡; 뤞; á„…á…¯á‡; ) HANGUL SYLLABLE RWEOP
+B91F;B91F;1105 116F 11C2;B91F;1105 116F 11C2; # (뤟; 뤟; 뤟; 뤟; 뤟; ) HANGUL SYLLABLE RWEOH
+B920;B920;1105 1170;B920;1105 1170; # (뤠; 뤠; 뤠; 뤠; 뤠; ) HANGUL SYLLABLE RWE
+B921;B921;1105 1170 11A8;B921;1105 1170 11A8; # (뤡; 뤡; 뤡; 뤡; 뤡; ) HANGUL SYLLABLE RWEG
+B922;B922;1105 1170 11A9;B922;1105 1170 11A9; # (뤢; 뤢; 뤢; 뤢; 뤢; ) HANGUL SYLLABLE RWEGG
+B923;B923;1105 1170 11AA;B923;1105 1170 11AA; # (뤣; 뤣; 뤣; 뤣; 뤣; ) HANGUL SYLLABLE RWEGS
+B924;B924;1105 1170 11AB;B924;1105 1170 11AB; # (뤤; 뤤; 뤤; 뤤; 뤤; ) HANGUL SYLLABLE RWEN
+B925;B925;1105 1170 11AC;B925;1105 1170 11AC; # (뤥; 뤥; 뤥; 뤥; 뤥; ) HANGUL SYLLABLE RWENJ
+B926;B926;1105 1170 11AD;B926;1105 1170 11AD; # (뤦; 뤦; 뤦; 뤦; 뤦; ) HANGUL SYLLABLE RWENH
+B927;B927;1105 1170 11AE;B927;1105 1170 11AE; # (뤧; 뤧; 뤧; 뤧; 뤧; ) HANGUL SYLLABLE RWED
+B928;B928;1105 1170 11AF;B928;1105 1170 11AF; # (뤨; 뤨; 뤨; 뤨; 뤨; ) HANGUL SYLLABLE RWEL
+B929;B929;1105 1170 11B0;B929;1105 1170 11B0; # (뤩; 뤩; 뤩; 뤩; 뤩; ) HANGUL SYLLABLE RWELG
+B92A;B92A;1105 1170 11B1;B92A;1105 1170 11B1; # (뤪; 뤪; 뤪; 뤪; 뤪; ) HANGUL SYLLABLE RWELM
+B92B;B92B;1105 1170 11B2;B92B;1105 1170 11B2; # (뤫; 뤫; 뤫; 뤫; 뤫; ) HANGUL SYLLABLE RWELB
+B92C;B92C;1105 1170 11B3;B92C;1105 1170 11B3; # (뤬; 뤬; 뤬; 뤬; 뤬; ) HANGUL SYLLABLE RWELS
+B92D;B92D;1105 1170 11B4;B92D;1105 1170 11B4; # (뤭; 뤭; 뤭; 뤭; 뤭; ) HANGUL SYLLABLE RWELT
+B92E;B92E;1105 1170 11B5;B92E;1105 1170 11B5; # (뤮; 뤮; 뤮; 뤮; 뤮; ) HANGUL SYLLABLE RWELP
+B92F;B92F;1105 1170 11B6;B92F;1105 1170 11B6; # (뤯; 뤯; 뤯; 뤯; 뤯; ) HANGUL SYLLABLE RWELH
+B930;B930;1105 1170 11B7;B930;1105 1170 11B7; # (뤰; 뤰; 뤰; 뤰; 뤰; ) HANGUL SYLLABLE RWEM
+B931;B931;1105 1170 11B8;B931;1105 1170 11B8; # (뤱; 뤱; 뤱; 뤱; 뤱; ) HANGUL SYLLABLE RWEB
+B932;B932;1105 1170 11B9;B932;1105 1170 11B9; # (뤲; 뤲; 뤲; 뤲; 뤲; ) HANGUL SYLLABLE RWEBS
+B933;B933;1105 1170 11BA;B933;1105 1170 11BA; # (뤳; 뤳; 뤳; 뤳; 뤳; ) HANGUL SYLLABLE RWES
+B934;B934;1105 1170 11BB;B934;1105 1170 11BB; # (뤴; 뤴; 뤴; 뤴; 뤴; ) HANGUL SYLLABLE RWESS
+B935;B935;1105 1170 11BC;B935;1105 1170 11BC; # (뤵; 뤵; 뤵; 뤵; 뤵; ) HANGUL SYLLABLE RWENG
+B936;B936;1105 1170 11BD;B936;1105 1170 11BD; # (뤶; 뤶; 뤶; 뤶; 뤶; ) HANGUL SYLLABLE RWEJ
+B937;B937;1105 1170 11BE;B937;1105 1170 11BE; # (뤷; 뤷; 뤷; 뤷; 뤷; ) HANGUL SYLLABLE RWEC
+B938;B938;1105 1170 11BF;B938;1105 1170 11BF; # (뤸; 뤸; 뤸; 뤸; 뤸; ) HANGUL SYLLABLE RWEK
+B939;B939;1105 1170 11C0;B939;1105 1170 11C0; # (뤹; 뤹; 뤹; 뤹; 뤹; ) HANGUL SYLLABLE RWET
+B93A;B93A;1105 1170 11C1;B93A;1105 1170 11C1; # (뤺; 뤺; á„…á…°á‡; 뤺; á„…á…°á‡; ) HANGUL SYLLABLE RWEP
+B93B;B93B;1105 1170 11C2;B93B;1105 1170 11C2; # (뤻; 뤻; 뤻; 뤻; 뤻; ) HANGUL SYLLABLE RWEH
+B93C;B93C;1105 1171;B93C;1105 1171; # (뤼; 뤼; 뤼; 뤼; 뤼; ) HANGUL SYLLABLE RWI
+B93D;B93D;1105 1171 11A8;B93D;1105 1171 11A8; # (뤽; 뤽; 뤽; 뤽; 뤽; ) HANGUL SYLLABLE RWIG
+B93E;B93E;1105 1171 11A9;B93E;1105 1171 11A9; # (뤾; 뤾; 뤾; 뤾; 뤾; ) HANGUL SYLLABLE RWIGG
+B93F;B93F;1105 1171 11AA;B93F;1105 1171 11AA; # (뤿; 뤿; 뤿; 뤿; 뤿; ) HANGUL SYLLABLE RWIGS
+B940;B940;1105 1171 11AB;B940;1105 1171 11AB; # (륀; 륀; 륀; 륀; 륀; ) HANGUL SYLLABLE RWIN
+B941;B941;1105 1171 11AC;B941;1105 1171 11AC; # (ë¥; ë¥; 륁; ë¥; 륁; ) HANGUL SYLLABLE RWINJ
+B942;B942;1105 1171 11AD;B942;1105 1171 11AD; # (륂; 륂; 륂; 륂; 륂; ) HANGUL SYLLABLE RWINH
+B943;B943;1105 1171 11AE;B943;1105 1171 11AE; # (륃; 륃; 륃; 륃; 륃; ) HANGUL SYLLABLE RWID
+B944;B944;1105 1171 11AF;B944;1105 1171 11AF; # (륄; 륄; 륄; 륄; 륄; ) HANGUL SYLLABLE RWIL
+B945;B945;1105 1171 11B0;B945;1105 1171 11B0; # (륅; 륅; 륅; 륅; 륅; ) HANGUL SYLLABLE RWILG
+B946;B946;1105 1171 11B1;B946;1105 1171 11B1; # (륆; 륆; 륆; 륆; 륆; ) HANGUL SYLLABLE RWILM
+B947;B947;1105 1171 11B2;B947;1105 1171 11B2; # (륇; 륇; 륇; 륇; 륇; ) HANGUL SYLLABLE RWILB
+B948;B948;1105 1171 11B3;B948;1105 1171 11B3; # (륈; 륈; 륈; 륈; 륈; ) HANGUL SYLLABLE RWILS
+B949;B949;1105 1171 11B4;B949;1105 1171 11B4; # (륉; 륉; 륉; 륉; 륉; ) HANGUL SYLLABLE RWILT
+B94A;B94A;1105 1171 11B5;B94A;1105 1171 11B5; # (륊; 륊; 륊; 륊; 륊; ) HANGUL SYLLABLE RWILP
+B94B;B94B;1105 1171 11B6;B94B;1105 1171 11B6; # (륋; 륋; 륋; 륋; 륋; ) HANGUL SYLLABLE RWILH
+B94C;B94C;1105 1171 11B7;B94C;1105 1171 11B7; # (륌; 륌; 륌; 륌; 륌; ) HANGUL SYLLABLE RWIM
+B94D;B94D;1105 1171 11B8;B94D;1105 1171 11B8; # (ë¥; ë¥; 륍; ë¥; 륍; ) HANGUL SYLLABLE RWIB
+B94E;B94E;1105 1171 11B9;B94E;1105 1171 11B9; # (륎; 륎; 륎; 륎; 륎; ) HANGUL SYLLABLE RWIBS
+B94F;B94F;1105 1171 11BA;B94F;1105 1171 11BA; # (ë¥; ë¥; 륏; ë¥; 륏; ) HANGUL SYLLABLE RWIS
+B950;B950;1105 1171 11BB;B950;1105 1171 11BB; # (ë¥; ë¥; 륐; ë¥; 륐; ) HANGUL SYLLABLE RWISS
+B951;B951;1105 1171 11BC;B951;1105 1171 11BC; # (륑; 륑; 륑; 륑; 륑; ) HANGUL SYLLABLE RWING
+B952;B952;1105 1171 11BD;B952;1105 1171 11BD; # (륒; 륒; 륒; 륒; 륒; ) HANGUL SYLLABLE RWIJ
+B953;B953;1105 1171 11BE;B953;1105 1171 11BE; # (륓; 륓; 륓; 륓; 륓; ) HANGUL SYLLABLE RWIC
+B954;B954;1105 1171 11BF;B954;1105 1171 11BF; # (륔; 륔; 륔; 륔; 륔; ) HANGUL SYLLABLE RWIK
+B955;B955;1105 1171 11C0;B955;1105 1171 11C0; # (륕; 륕; 륕; 륕; 륕; ) HANGUL SYLLABLE RWIT
+B956;B956;1105 1171 11C1;B956;1105 1171 11C1; # (륖; 륖; á„…á…±á‡; 륖; á„…á…±á‡; ) HANGUL SYLLABLE RWIP
+B957;B957;1105 1171 11C2;B957;1105 1171 11C2; # (륗; 륗; 륗; 륗; 륗; ) HANGUL SYLLABLE RWIH
+B958;B958;1105 1172;B958;1105 1172; # (류; 류; 류; 류; 류; ) HANGUL SYLLABLE RYU
+B959;B959;1105 1172 11A8;B959;1105 1172 11A8; # (륙; 륙; 륙; 륙; 륙; ) HANGUL SYLLABLE RYUG
+B95A;B95A;1105 1172 11A9;B95A;1105 1172 11A9; # (륚; 륚; 륚; 륚; 륚; ) HANGUL SYLLABLE RYUGG
+B95B;B95B;1105 1172 11AA;B95B;1105 1172 11AA; # (륛; 륛; 륛; 륛; 륛; ) HANGUL SYLLABLE RYUGS
+B95C;B95C;1105 1172 11AB;B95C;1105 1172 11AB; # (륜; 륜; 륜; 륜; 륜; ) HANGUL SYLLABLE RYUN
+B95D;B95D;1105 1172 11AC;B95D;1105 1172 11AC; # (ë¥; ë¥; 륝; ë¥; 륝; ) HANGUL SYLLABLE RYUNJ
+B95E;B95E;1105 1172 11AD;B95E;1105 1172 11AD; # (륞; 륞; 륞; 륞; 륞; ) HANGUL SYLLABLE RYUNH
+B95F;B95F;1105 1172 11AE;B95F;1105 1172 11AE; # (륟; 륟; 륟; 륟; 륟; ) HANGUL SYLLABLE RYUD
+B960;B960;1105 1172 11AF;B960;1105 1172 11AF; # (률; 률; 률; 률; 률; ) HANGUL SYLLABLE RYUL
+B961;B961;1105 1172 11B0;B961;1105 1172 11B0; # (륡; 륡; 륡; 륡; 륡; ) HANGUL SYLLABLE RYULG
+B962;B962;1105 1172 11B1;B962;1105 1172 11B1; # (륢; 륢; 륢; 륢; 륢; ) HANGUL SYLLABLE RYULM
+B963;B963;1105 1172 11B2;B963;1105 1172 11B2; # (륣; 륣; 륣; 륣; 륣; ) HANGUL SYLLABLE RYULB
+B964;B964;1105 1172 11B3;B964;1105 1172 11B3; # (륤; 륤; 륤; 륤; 륤; ) HANGUL SYLLABLE RYULS
+B965;B965;1105 1172 11B4;B965;1105 1172 11B4; # (륥; 륥; 륥; 륥; 륥; ) HANGUL SYLLABLE RYULT
+B966;B966;1105 1172 11B5;B966;1105 1172 11B5; # (륦; 륦; 륦; 륦; 륦; ) HANGUL SYLLABLE RYULP
+B967;B967;1105 1172 11B6;B967;1105 1172 11B6; # (륧; 륧; 륧; 륧; 륧; ) HANGUL SYLLABLE RYULH
+B968;B968;1105 1172 11B7;B968;1105 1172 11B7; # (륨; 륨; 륨; 륨; 륨; ) HANGUL SYLLABLE RYUM
+B969;B969;1105 1172 11B8;B969;1105 1172 11B8; # (륩; 륩; 륩; 륩; 륩; ) HANGUL SYLLABLE RYUB
+B96A;B96A;1105 1172 11B9;B96A;1105 1172 11B9; # (륪; 륪; 륪; 륪; 륪; ) HANGUL SYLLABLE RYUBS
+B96B;B96B;1105 1172 11BA;B96B;1105 1172 11BA; # (륫; 륫; 륫; 륫; 륫; ) HANGUL SYLLABLE RYUS
+B96C;B96C;1105 1172 11BB;B96C;1105 1172 11BB; # (륬; 륬; 륬; 륬; 륬; ) HANGUL SYLLABLE RYUSS
+B96D;B96D;1105 1172 11BC;B96D;1105 1172 11BC; # (륭; 륭; 륭; 륭; 륭; ) HANGUL SYLLABLE RYUNG
+B96E;B96E;1105 1172 11BD;B96E;1105 1172 11BD; # (륮; 륮; 륮; 륮; 륮; ) HANGUL SYLLABLE RYUJ
+B96F;B96F;1105 1172 11BE;B96F;1105 1172 11BE; # (륯; 륯; 륯; 륯; 륯; ) HANGUL SYLLABLE RYUC
+B970;B970;1105 1172 11BF;B970;1105 1172 11BF; # (륰; 륰; 륰; 륰; 륰; ) HANGUL SYLLABLE RYUK
+B971;B971;1105 1172 11C0;B971;1105 1172 11C0; # (륱; 륱; 륱; 륱; 륱; ) HANGUL SYLLABLE RYUT
+B972;B972;1105 1172 11C1;B972;1105 1172 11C1; # (륲; 륲; á„…á…²á‡; 륲; á„…á…²á‡; ) HANGUL SYLLABLE RYUP
+B973;B973;1105 1172 11C2;B973;1105 1172 11C2; # (륳; 륳; 륳; 륳; 륳; ) HANGUL SYLLABLE RYUH
+B974;B974;1105 1173;B974;1105 1173; # (르; 르; 르; 르; 르; ) HANGUL SYLLABLE REU
+B975;B975;1105 1173 11A8;B975;1105 1173 11A8; # (륵; 륵; 륵; 륵; 륵; ) HANGUL SYLLABLE REUG
+B976;B976;1105 1173 11A9;B976;1105 1173 11A9; # (륶; 륶; 륶; 륶; 륶; ) HANGUL SYLLABLE REUGG
+B977;B977;1105 1173 11AA;B977;1105 1173 11AA; # (륷; 륷; 륷; 륷; 륷; ) HANGUL SYLLABLE REUGS
+B978;B978;1105 1173 11AB;B978;1105 1173 11AB; # (른; 른; 른; 른; 른; ) HANGUL SYLLABLE REUN
+B979;B979;1105 1173 11AC;B979;1105 1173 11AC; # (륹; 륹; 륹; 륹; 륹; ) HANGUL SYLLABLE REUNJ
+B97A;B97A;1105 1173 11AD;B97A;1105 1173 11AD; # (륺; 륺; 륺; 륺; 륺; ) HANGUL SYLLABLE REUNH
+B97B;B97B;1105 1173 11AE;B97B;1105 1173 11AE; # (륻; 륻; 륻; 륻; 륻; ) HANGUL SYLLABLE REUD
+B97C;B97C;1105 1173 11AF;B97C;1105 1173 11AF; # (를; 를; 를; 를; 를; ) HANGUL SYLLABLE REUL
+B97D;B97D;1105 1173 11B0;B97D;1105 1173 11B0; # (륽; 륽; 륽; 륽; 륽; ) HANGUL SYLLABLE REULG
+B97E;B97E;1105 1173 11B1;B97E;1105 1173 11B1; # (륾; 륾; 륾; 륾; 륾; ) HANGUL SYLLABLE REULM
+B97F;B97F;1105 1173 11B2;B97F;1105 1173 11B2; # (륿; 륿; 륿; 륿; 륿; ) HANGUL SYLLABLE REULB
+B980;B980;1105 1173 11B3;B980;1105 1173 11B3; # (릀; 릀; 릀; 릀; 릀; ) HANGUL SYLLABLE REULS
+B981;B981;1105 1173 11B4;B981;1105 1173 11B4; # (ë¦; ë¦; 릁; ë¦; 릁; ) HANGUL SYLLABLE REULT
+B982;B982;1105 1173 11B5;B982;1105 1173 11B5; # (릂; 릂; 릂; 릂; 릂; ) HANGUL SYLLABLE REULP
+B983;B983;1105 1173 11B6;B983;1105 1173 11B6; # (릃; 릃; 릃; 릃; 릃; ) HANGUL SYLLABLE REULH
+B984;B984;1105 1173 11B7;B984;1105 1173 11B7; # (름; 름; 름; 름; 름; ) HANGUL SYLLABLE REUM
+B985;B985;1105 1173 11B8;B985;1105 1173 11B8; # (릅; 릅; 릅; 릅; 릅; ) HANGUL SYLLABLE REUB
+B986;B986;1105 1173 11B9;B986;1105 1173 11B9; # (릆; 릆; 릆; 릆; 릆; ) HANGUL SYLLABLE REUBS
+B987;B987;1105 1173 11BA;B987;1105 1173 11BA; # (릇; 릇; 릇; 릇; 릇; ) HANGUL SYLLABLE REUS
+B988;B988;1105 1173 11BB;B988;1105 1173 11BB; # (릈; 릈; 릈; 릈; 릈; ) HANGUL SYLLABLE REUSS
+B989;B989;1105 1173 11BC;B989;1105 1173 11BC; # (릉; 릉; 릉; 릉; 릉; ) HANGUL SYLLABLE REUNG
+B98A;B98A;1105 1173 11BD;B98A;1105 1173 11BD; # (릊; 릊; 릊; 릊; 릊; ) HANGUL SYLLABLE REUJ
+B98B;B98B;1105 1173 11BE;B98B;1105 1173 11BE; # (릋; 릋; 릋; 릋; 릋; ) HANGUL SYLLABLE REUC
+B98C;B98C;1105 1173 11BF;B98C;1105 1173 11BF; # (릌; 릌; 릌; 릌; 릌; ) HANGUL SYLLABLE REUK
+B98D;B98D;1105 1173 11C0;B98D;1105 1173 11C0; # (ë¦; ë¦; 릍; ë¦; 릍; ) HANGUL SYLLABLE REUT
+B98E;B98E;1105 1173 11C1;B98E;1105 1173 11C1; # (릎; 릎; á„…á…³á‡; 릎; á„…á…³á‡; ) HANGUL SYLLABLE REUP
+B98F;B98F;1105 1173 11C2;B98F;1105 1173 11C2; # (ë¦; ë¦; 릏; ë¦; 릏; ) HANGUL SYLLABLE REUH
+B990;B990;1105 1174;B990;1105 1174; # (ë¦; ë¦; á„…á…´; ë¦; á„…á…´; ) HANGUL SYLLABLE RYI
+B991;B991;1105 1174 11A8;B991;1105 1174 11A8; # (릑; 릑; 릑; 릑; 릑; ) HANGUL SYLLABLE RYIG
+B992;B992;1105 1174 11A9;B992;1105 1174 11A9; # (릒; 릒; 릒; 릒; 릒; ) HANGUL SYLLABLE RYIGG
+B993;B993;1105 1174 11AA;B993;1105 1174 11AA; # (릓; 릓; 릓; 릓; 릓; ) HANGUL SYLLABLE RYIGS
+B994;B994;1105 1174 11AB;B994;1105 1174 11AB; # (릔; 릔; 릔; 릔; 릔; ) HANGUL SYLLABLE RYIN
+B995;B995;1105 1174 11AC;B995;1105 1174 11AC; # (릕; 릕; 릕; 릕; 릕; ) HANGUL SYLLABLE RYINJ
+B996;B996;1105 1174 11AD;B996;1105 1174 11AD; # (릖; 릖; 릖; 릖; 릖; ) HANGUL SYLLABLE RYINH
+B997;B997;1105 1174 11AE;B997;1105 1174 11AE; # (릗; 릗; 릗; 릗; 릗; ) HANGUL SYLLABLE RYID
+B998;B998;1105 1174 11AF;B998;1105 1174 11AF; # (릘; 릘; 릘; 릘; 릘; ) HANGUL SYLLABLE RYIL
+B999;B999;1105 1174 11B0;B999;1105 1174 11B0; # (릙; 릙; 릙; 릙; 릙; ) HANGUL SYLLABLE RYILG
+B99A;B99A;1105 1174 11B1;B99A;1105 1174 11B1; # (릚; 릚; 릚; 릚; 릚; ) HANGUL SYLLABLE RYILM
+B99B;B99B;1105 1174 11B2;B99B;1105 1174 11B2; # (릛; 릛; 릛; 릛; 릛; ) HANGUL SYLLABLE RYILB
+B99C;B99C;1105 1174 11B3;B99C;1105 1174 11B3; # (릜; 릜; 릜; 릜; 릜; ) HANGUL SYLLABLE RYILS
+B99D;B99D;1105 1174 11B4;B99D;1105 1174 11B4; # (ë¦; ë¦; 릝; ë¦; 릝; ) HANGUL SYLLABLE RYILT
+B99E;B99E;1105 1174 11B5;B99E;1105 1174 11B5; # (릞; 릞; 릞; 릞; 릞; ) HANGUL SYLLABLE RYILP
+B99F;B99F;1105 1174 11B6;B99F;1105 1174 11B6; # (릟; 릟; 릟; 릟; 릟; ) HANGUL SYLLABLE RYILH
+B9A0;B9A0;1105 1174 11B7;B9A0;1105 1174 11B7; # (릠; 릠; 릠; 릠; 릠; ) HANGUL SYLLABLE RYIM
+B9A1;B9A1;1105 1174 11B8;B9A1;1105 1174 11B8; # (릡; 릡; 릡; 릡; 릡; ) HANGUL SYLLABLE RYIB
+B9A2;B9A2;1105 1174 11B9;B9A2;1105 1174 11B9; # (릢; 릢; 릢; 릢; 릢; ) HANGUL SYLLABLE RYIBS
+B9A3;B9A3;1105 1174 11BA;B9A3;1105 1174 11BA; # (릣; 릣; 릣; 릣; 릣; ) HANGUL SYLLABLE RYIS
+B9A4;B9A4;1105 1174 11BB;B9A4;1105 1174 11BB; # (릤; 릤; 릤; 릤; 릤; ) HANGUL SYLLABLE RYISS
+B9A5;B9A5;1105 1174 11BC;B9A5;1105 1174 11BC; # (릥; 릥; 릥; 릥; 릥; ) HANGUL SYLLABLE RYING
+B9A6;B9A6;1105 1174 11BD;B9A6;1105 1174 11BD; # (릦; 릦; 릦; 릦; 릦; ) HANGUL SYLLABLE RYIJ
+B9A7;B9A7;1105 1174 11BE;B9A7;1105 1174 11BE; # (릧; 릧; 릧; 릧; 릧; ) HANGUL SYLLABLE RYIC
+B9A8;B9A8;1105 1174 11BF;B9A8;1105 1174 11BF; # (릨; 릨; 릨; 릨; 릨; ) HANGUL SYLLABLE RYIK
+B9A9;B9A9;1105 1174 11C0;B9A9;1105 1174 11C0; # (릩; 릩; 릩; 릩; 릩; ) HANGUL SYLLABLE RYIT
+B9AA;B9AA;1105 1174 11C1;B9AA;1105 1174 11C1; # (릪; 릪; á„…á…´á‡; 릪; á„…á…´á‡; ) HANGUL SYLLABLE RYIP
+B9AB;B9AB;1105 1174 11C2;B9AB;1105 1174 11C2; # (릫; 릫; 릫; 릫; 릫; ) HANGUL SYLLABLE RYIH
+B9AC;B9AC;1105 1175;B9AC;1105 1175; # (리; 리; 리; 리; 리; ) HANGUL SYLLABLE RI
+B9AD;B9AD;1105 1175 11A8;B9AD;1105 1175 11A8; # (릭; 릭; 릭; 릭; 릭; ) HANGUL SYLLABLE RIG
+B9AE;B9AE;1105 1175 11A9;B9AE;1105 1175 11A9; # (릮; 릮; 릮; 릮; 릮; ) HANGUL SYLLABLE RIGG
+B9AF;B9AF;1105 1175 11AA;B9AF;1105 1175 11AA; # (릯; 릯; 릯; 릯; 릯; ) HANGUL SYLLABLE RIGS
+B9B0;B9B0;1105 1175 11AB;B9B0;1105 1175 11AB; # (린; 린; 린; 린; 린; ) HANGUL SYLLABLE RIN
+B9B1;B9B1;1105 1175 11AC;B9B1;1105 1175 11AC; # (릱; 릱; 릱; 릱; 릱; ) HANGUL SYLLABLE RINJ
+B9B2;B9B2;1105 1175 11AD;B9B2;1105 1175 11AD; # (릲; 릲; 릲; 릲; 릲; ) HANGUL SYLLABLE RINH
+B9B3;B9B3;1105 1175 11AE;B9B3;1105 1175 11AE; # (릳; 릳; 릳; 릳; 릳; ) HANGUL SYLLABLE RID
+B9B4;B9B4;1105 1175 11AF;B9B4;1105 1175 11AF; # (릴; 릴; 릴; 릴; 릴; ) HANGUL SYLLABLE RIL
+B9B5;B9B5;1105 1175 11B0;B9B5;1105 1175 11B0; # (릵; 릵; 릵; 릵; 릵; ) HANGUL SYLLABLE RILG
+B9B6;B9B6;1105 1175 11B1;B9B6;1105 1175 11B1; # (릶; 릶; 릶; 릶; 릶; ) HANGUL SYLLABLE RILM
+B9B7;B9B7;1105 1175 11B2;B9B7;1105 1175 11B2; # (릷; 릷; 릷; 릷; 릷; ) HANGUL SYLLABLE RILB
+B9B8;B9B8;1105 1175 11B3;B9B8;1105 1175 11B3; # (릸; 릸; 릸; 릸; 릸; ) HANGUL SYLLABLE RILS
+B9B9;B9B9;1105 1175 11B4;B9B9;1105 1175 11B4; # (릹; 릹; 릹; 릹; 릹; ) HANGUL SYLLABLE RILT
+B9BA;B9BA;1105 1175 11B5;B9BA;1105 1175 11B5; # (릺; 릺; 릺; 릺; 릺; ) HANGUL SYLLABLE RILP
+B9BB;B9BB;1105 1175 11B6;B9BB;1105 1175 11B6; # (릻; 릻; 릻; 릻; 릻; ) HANGUL SYLLABLE RILH
+B9BC;B9BC;1105 1175 11B7;B9BC;1105 1175 11B7; # (림; 림; 림; 림; 림; ) HANGUL SYLLABLE RIM
+B9BD;B9BD;1105 1175 11B8;B9BD;1105 1175 11B8; # (립; 립; 립; 립; 립; ) HANGUL SYLLABLE RIB
+B9BE;B9BE;1105 1175 11B9;B9BE;1105 1175 11B9; # (릾; 릾; 릾; 릾; 릾; ) HANGUL SYLLABLE RIBS
+B9BF;B9BF;1105 1175 11BA;B9BF;1105 1175 11BA; # (릿; 릿; 릿; 릿; 릿; ) HANGUL SYLLABLE RIS
+B9C0;B9C0;1105 1175 11BB;B9C0;1105 1175 11BB; # (맀; 맀; 맀; 맀; 맀; ) HANGUL SYLLABLE RISS
+B9C1;B9C1;1105 1175 11BC;B9C1;1105 1175 11BC; # (ë§; ë§; 링; ë§; 링; ) HANGUL SYLLABLE RING
+B9C2;B9C2;1105 1175 11BD;B9C2;1105 1175 11BD; # (맂; 맂; 맂; 맂; 맂; ) HANGUL SYLLABLE RIJ
+B9C3;B9C3;1105 1175 11BE;B9C3;1105 1175 11BE; # (맃; 맃; 맃; 맃; 맃; ) HANGUL SYLLABLE RIC
+B9C4;B9C4;1105 1175 11BF;B9C4;1105 1175 11BF; # (맄; 맄; 맄; 맄; 맄; ) HANGUL SYLLABLE RIK
+B9C5;B9C5;1105 1175 11C0;B9C5;1105 1175 11C0; # (맅; 맅; 맅; 맅; 맅; ) HANGUL SYLLABLE RIT
+B9C6;B9C6;1105 1175 11C1;B9C6;1105 1175 11C1; # (맆; 맆; á„…á…µá‡; 맆; á„…á…µá‡; ) HANGUL SYLLABLE RIP
+B9C7;B9C7;1105 1175 11C2;B9C7;1105 1175 11C2; # (맇; 맇; 맇; 맇; 맇; ) HANGUL SYLLABLE RIH
+B9C8;B9C8;1106 1161;B9C8;1106 1161; # (마; 마; 마; 마; 마; ) HANGUL SYLLABLE MA
+B9C9;B9C9;1106 1161 11A8;B9C9;1106 1161 11A8; # (막; 막; 막; 막; 막; ) HANGUL SYLLABLE MAG
+B9CA;B9CA;1106 1161 11A9;B9CA;1106 1161 11A9; # (맊; 맊; 맊; 맊; 맊; ) HANGUL SYLLABLE MAGG
+B9CB;B9CB;1106 1161 11AA;B9CB;1106 1161 11AA; # (맋; 맋; 맋; 맋; 맋; ) HANGUL SYLLABLE MAGS
+B9CC;B9CC;1106 1161 11AB;B9CC;1106 1161 11AB; # (만; 만; 만; 만; 만; ) HANGUL SYLLABLE MAN
+B9CD;B9CD;1106 1161 11AC;B9CD;1106 1161 11AC; # (ë§; ë§; 맍; ë§; 맍; ) HANGUL SYLLABLE MANJ
+B9CE;B9CE;1106 1161 11AD;B9CE;1106 1161 11AD; # (많; 많; 많; 많; 많; ) HANGUL SYLLABLE MANH
+B9CF;B9CF;1106 1161 11AE;B9CF;1106 1161 11AE; # (ë§; ë§; 맏; ë§; 맏; ) HANGUL SYLLABLE MAD
+B9D0;B9D0;1106 1161 11AF;B9D0;1106 1161 11AF; # (ë§; ë§; 말; ë§; 말; ) HANGUL SYLLABLE MAL
+B9D1;B9D1;1106 1161 11B0;B9D1;1106 1161 11B0; # (맑; 맑; 맑; 맑; 맑; ) HANGUL SYLLABLE MALG
+B9D2;B9D2;1106 1161 11B1;B9D2;1106 1161 11B1; # (맒; 맒; 맒; 맒; 맒; ) HANGUL SYLLABLE MALM
+B9D3;B9D3;1106 1161 11B2;B9D3;1106 1161 11B2; # (맓; 맓; 맓; 맓; 맓; ) HANGUL SYLLABLE MALB
+B9D4;B9D4;1106 1161 11B3;B9D4;1106 1161 11B3; # (맔; 맔; 맔; 맔; 맔; ) HANGUL SYLLABLE MALS
+B9D5;B9D5;1106 1161 11B4;B9D5;1106 1161 11B4; # (맕; 맕; 맕; 맕; 맕; ) HANGUL SYLLABLE MALT
+B9D6;B9D6;1106 1161 11B5;B9D6;1106 1161 11B5; # (맖; 맖; 맖; 맖; 맖; ) HANGUL SYLLABLE MALP
+B9D7;B9D7;1106 1161 11B6;B9D7;1106 1161 11B6; # (맗; 맗; 맗; 맗; 맗; ) HANGUL SYLLABLE MALH
+B9D8;B9D8;1106 1161 11B7;B9D8;1106 1161 11B7; # (맘; 맘; 맘; 맘; 맘; ) HANGUL SYLLABLE MAM
+B9D9;B9D9;1106 1161 11B8;B9D9;1106 1161 11B8; # (맙; 맙; 맙; 맙; 맙; ) HANGUL SYLLABLE MAB
+B9DA;B9DA;1106 1161 11B9;B9DA;1106 1161 11B9; # (맚; 맚; 맚; 맚; 맚; ) HANGUL SYLLABLE MABS
+B9DB;B9DB;1106 1161 11BA;B9DB;1106 1161 11BA; # (맛; 맛; 맛; 맛; 맛; ) HANGUL SYLLABLE MAS
+B9DC;B9DC;1106 1161 11BB;B9DC;1106 1161 11BB; # (맜; 맜; 맜; 맜; 맜; ) HANGUL SYLLABLE MASS
+B9DD;B9DD;1106 1161 11BC;B9DD;1106 1161 11BC; # (ë§; ë§; 망; ë§; 망; ) HANGUL SYLLABLE MANG
+B9DE;B9DE;1106 1161 11BD;B9DE;1106 1161 11BD; # (맞; 맞; 맞; 맞; 맞; ) HANGUL SYLLABLE MAJ
+B9DF;B9DF;1106 1161 11BE;B9DF;1106 1161 11BE; # (맟; 맟; 맟; 맟; 맟; ) HANGUL SYLLABLE MAC
+B9E0;B9E0;1106 1161 11BF;B9E0;1106 1161 11BF; # (맠; 맠; 맠; 맠; 맠; ) HANGUL SYLLABLE MAK
+B9E1;B9E1;1106 1161 11C0;B9E1;1106 1161 11C0; # (맡; 맡; 맡; 맡; 맡; ) HANGUL SYLLABLE MAT
+B9E2;B9E2;1106 1161 11C1;B9E2;1106 1161 11C1; # (맢; 맢; 마á‡; 맢; 마á‡; ) HANGUL SYLLABLE MAP
+B9E3;B9E3;1106 1161 11C2;B9E3;1106 1161 11C2; # (맣; 맣; 맣; 맣; 맣; ) HANGUL SYLLABLE MAH
+B9E4;B9E4;1106 1162;B9E4;1106 1162; # (매; 매; 매; 매; 매; ) HANGUL SYLLABLE MAE
+B9E5;B9E5;1106 1162 11A8;B9E5;1106 1162 11A8; # (맥; 맥; 맥; 맥; 맥; ) HANGUL SYLLABLE MAEG
+B9E6;B9E6;1106 1162 11A9;B9E6;1106 1162 11A9; # (맦; 맦; 맦; 맦; 맦; ) HANGUL SYLLABLE MAEGG
+B9E7;B9E7;1106 1162 11AA;B9E7;1106 1162 11AA; # (맧; 맧; 맧; 맧; 맧; ) HANGUL SYLLABLE MAEGS
+B9E8;B9E8;1106 1162 11AB;B9E8;1106 1162 11AB; # (맨; 맨; 맨; 맨; 맨; ) HANGUL SYLLABLE MAEN
+B9E9;B9E9;1106 1162 11AC;B9E9;1106 1162 11AC; # (맩; 맩; 맩; 맩; 맩; ) HANGUL SYLLABLE MAENJ
+B9EA;B9EA;1106 1162 11AD;B9EA;1106 1162 11AD; # (맪; 맪; 맪; 맪; 맪; ) HANGUL SYLLABLE MAENH
+B9EB;B9EB;1106 1162 11AE;B9EB;1106 1162 11AE; # (맫; 맫; 맫; 맫; 맫; ) HANGUL SYLLABLE MAED
+B9EC;B9EC;1106 1162 11AF;B9EC;1106 1162 11AF; # (맬; 맬; 맬; 맬; 맬; ) HANGUL SYLLABLE MAEL
+B9ED;B9ED;1106 1162 11B0;B9ED;1106 1162 11B0; # (맭; 맭; 맭; 맭; 맭; ) HANGUL SYLLABLE MAELG
+B9EE;B9EE;1106 1162 11B1;B9EE;1106 1162 11B1; # (맮; 맮; 맮; 맮; 맮; ) HANGUL SYLLABLE MAELM
+B9EF;B9EF;1106 1162 11B2;B9EF;1106 1162 11B2; # (맯; 맯; 맯; 맯; 맯; ) HANGUL SYLLABLE MAELB
+B9F0;B9F0;1106 1162 11B3;B9F0;1106 1162 11B3; # (맰; 맰; 맰; 맰; 맰; ) HANGUL SYLLABLE MAELS
+B9F1;B9F1;1106 1162 11B4;B9F1;1106 1162 11B4; # (맱; 맱; 맱; 맱; 맱; ) HANGUL SYLLABLE MAELT
+B9F2;B9F2;1106 1162 11B5;B9F2;1106 1162 11B5; # (맲; 맲; 맲; 맲; 맲; ) HANGUL SYLLABLE MAELP
+B9F3;B9F3;1106 1162 11B6;B9F3;1106 1162 11B6; # (맳; 맳; 맳; 맳; 맳; ) HANGUL SYLLABLE MAELH
+B9F4;B9F4;1106 1162 11B7;B9F4;1106 1162 11B7; # (맴; 맴; 맴; 맴; 맴; ) HANGUL SYLLABLE MAEM
+B9F5;B9F5;1106 1162 11B8;B9F5;1106 1162 11B8; # (맵; 맵; 맵; 맵; 맵; ) HANGUL SYLLABLE MAEB
+B9F6;B9F6;1106 1162 11B9;B9F6;1106 1162 11B9; # (맶; 맶; 맶; 맶; 맶; ) HANGUL SYLLABLE MAEBS
+B9F7;B9F7;1106 1162 11BA;B9F7;1106 1162 11BA; # (맷; 맷; 맷; 맷; 맷; ) HANGUL SYLLABLE MAES
+B9F8;B9F8;1106 1162 11BB;B9F8;1106 1162 11BB; # (맸; 맸; 맸; 맸; 맸; ) HANGUL SYLLABLE MAESS
+B9F9;B9F9;1106 1162 11BC;B9F9;1106 1162 11BC; # (맹; 맹; 맹; 맹; 맹; ) HANGUL SYLLABLE MAENG
+B9FA;B9FA;1106 1162 11BD;B9FA;1106 1162 11BD; # (맺; 맺; 맺; 맺; 맺; ) HANGUL SYLLABLE MAEJ
+B9FB;B9FB;1106 1162 11BE;B9FB;1106 1162 11BE; # (맻; 맻; 맻; 맻; 맻; ) HANGUL SYLLABLE MAEC
+B9FC;B9FC;1106 1162 11BF;B9FC;1106 1162 11BF; # (맼; 맼; 맼; 맼; 맼; ) HANGUL SYLLABLE MAEK
+B9FD;B9FD;1106 1162 11C0;B9FD;1106 1162 11C0; # (맽; 맽; 맽; 맽; 맽; ) HANGUL SYLLABLE MAET
+B9FE;B9FE;1106 1162 11C1;B9FE;1106 1162 11C1; # (맾; 맾; 매á‡; 맾; 매á‡; ) HANGUL SYLLABLE MAEP
+B9FF;B9FF;1106 1162 11C2;B9FF;1106 1162 11C2; # (맿; 맿; 맿; 맿; 맿; ) HANGUL SYLLABLE MAEH
+BA00;BA00;1106 1163;BA00;1106 1163; # (먀; 먀; 먀; 먀; 먀; ) HANGUL SYLLABLE MYA
+BA01;BA01;1106 1163 11A8;BA01;1106 1163 11A8; # (ë¨; ë¨; 먁; ë¨; 먁; ) HANGUL SYLLABLE MYAG
+BA02;BA02;1106 1163 11A9;BA02;1106 1163 11A9; # (먂; 먂; 먂; 먂; 먂; ) HANGUL SYLLABLE MYAGG
+BA03;BA03;1106 1163 11AA;BA03;1106 1163 11AA; # (먃; 먃; 먃; 먃; 먃; ) HANGUL SYLLABLE MYAGS
+BA04;BA04;1106 1163 11AB;BA04;1106 1163 11AB; # (먄; 먄; 먄; 먄; 먄; ) HANGUL SYLLABLE MYAN
+BA05;BA05;1106 1163 11AC;BA05;1106 1163 11AC; # (먅; 먅; 먅; 먅; 먅; ) HANGUL SYLLABLE MYANJ
+BA06;BA06;1106 1163 11AD;BA06;1106 1163 11AD; # (먆; 먆; 먆; 먆; 먆; ) HANGUL SYLLABLE MYANH
+BA07;BA07;1106 1163 11AE;BA07;1106 1163 11AE; # (먇; 먇; 먇; 먇; 먇; ) HANGUL SYLLABLE MYAD
+BA08;BA08;1106 1163 11AF;BA08;1106 1163 11AF; # (먈; 먈; 먈; 먈; 먈; ) HANGUL SYLLABLE MYAL
+BA09;BA09;1106 1163 11B0;BA09;1106 1163 11B0; # (먉; 먉; 먉; 먉; 먉; ) HANGUL SYLLABLE MYALG
+BA0A;BA0A;1106 1163 11B1;BA0A;1106 1163 11B1; # (먊; 먊; 먊; 먊; 먊; ) HANGUL SYLLABLE MYALM
+BA0B;BA0B;1106 1163 11B2;BA0B;1106 1163 11B2; # (먋; 먋; 먋; 먋; 먋; ) HANGUL SYLLABLE MYALB
+BA0C;BA0C;1106 1163 11B3;BA0C;1106 1163 11B3; # (먌; 먌; 먌; 먌; 먌; ) HANGUL SYLLABLE MYALS
+BA0D;BA0D;1106 1163 11B4;BA0D;1106 1163 11B4; # (ë¨; ë¨; 먍; ë¨; 먍; ) HANGUL SYLLABLE MYALT
+BA0E;BA0E;1106 1163 11B5;BA0E;1106 1163 11B5; # (먎; 먎; 먎; 먎; 먎; ) HANGUL SYLLABLE MYALP
+BA0F;BA0F;1106 1163 11B6;BA0F;1106 1163 11B6; # (ë¨; ë¨; 먏; ë¨; 먏; ) HANGUL SYLLABLE MYALH
+BA10;BA10;1106 1163 11B7;BA10;1106 1163 11B7; # (ë¨; ë¨; 먐; ë¨; 먐; ) HANGUL SYLLABLE MYAM
+BA11;BA11;1106 1163 11B8;BA11;1106 1163 11B8; # (먑; 먑; 먑; 먑; 먑; ) HANGUL SYLLABLE MYAB
+BA12;BA12;1106 1163 11B9;BA12;1106 1163 11B9; # (먒; 먒; 먒; 먒; 먒; ) HANGUL SYLLABLE MYABS
+BA13;BA13;1106 1163 11BA;BA13;1106 1163 11BA; # (먓; 먓; 먓; 먓; 먓; ) HANGUL SYLLABLE MYAS
+BA14;BA14;1106 1163 11BB;BA14;1106 1163 11BB; # (먔; 먔; 먔; 먔; 먔; ) HANGUL SYLLABLE MYASS
+BA15;BA15;1106 1163 11BC;BA15;1106 1163 11BC; # (먕; 먕; 먕; 먕; 먕; ) HANGUL SYLLABLE MYANG
+BA16;BA16;1106 1163 11BD;BA16;1106 1163 11BD; # (먖; 먖; 먖; 먖; 먖; ) HANGUL SYLLABLE MYAJ
+BA17;BA17;1106 1163 11BE;BA17;1106 1163 11BE; # (먗; 먗; 먗; 먗; 먗; ) HANGUL SYLLABLE MYAC
+BA18;BA18;1106 1163 11BF;BA18;1106 1163 11BF; # (먘; 먘; 먘; 먘; 먘; ) HANGUL SYLLABLE MYAK
+BA19;BA19;1106 1163 11C0;BA19;1106 1163 11C0; # (먙; 먙; 먙; 먙; 먙; ) HANGUL SYLLABLE MYAT
+BA1A;BA1A;1106 1163 11C1;BA1A;1106 1163 11C1; # (먚; 먚; 먀á‡; 먚; 먀á‡; ) HANGUL SYLLABLE MYAP
+BA1B;BA1B;1106 1163 11C2;BA1B;1106 1163 11C2; # (먛; 먛; 먛; 먛; 먛; ) HANGUL SYLLABLE MYAH
+BA1C;BA1C;1106 1164;BA1C;1106 1164; # (먜; 먜; 먜; 먜; 먜; ) HANGUL SYLLABLE MYAE
+BA1D;BA1D;1106 1164 11A8;BA1D;1106 1164 11A8; # (ë¨; ë¨; 먝; ë¨; 먝; ) HANGUL SYLLABLE MYAEG
+BA1E;BA1E;1106 1164 11A9;BA1E;1106 1164 11A9; # (먞; 먞; 먞; 먞; 먞; ) HANGUL SYLLABLE MYAEGG
+BA1F;BA1F;1106 1164 11AA;BA1F;1106 1164 11AA; # (먟; 먟; 먟; 먟; 먟; ) HANGUL SYLLABLE MYAEGS
+BA20;BA20;1106 1164 11AB;BA20;1106 1164 11AB; # (먠; 먠; 먠; 먠; 먠; ) HANGUL SYLLABLE MYAEN
+BA21;BA21;1106 1164 11AC;BA21;1106 1164 11AC; # (먡; 먡; 먡; 먡; 먡; ) HANGUL SYLLABLE MYAENJ
+BA22;BA22;1106 1164 11AD;BA22;1106 1164 11AD; # (먢; 먢; 먢; 먢; 먢; ) HANGUL SYLLABLE MYAENH
+BA23;BA23;1106 1164 11AE;BA23;1106 1164 11AE; # (먣; 먣; 먣; 먣; 먣; ) HANGUL SYLLABLE MYAED
+BA24;BA24;1106 1164 11AF;BA24;1106 1164 11AF; # (먤; 먤; 먤; 먤; 먤; ) HANGUL SYLLABLE MYAEL
+BA25;BA25;1106 1164 11B0;BA25;1106 1164 11B0; # (먥; 먥; 먥; 먥; 먥; ) HANGUL SYLLABLE MYAELG
+BA26;BA26;1106 1164 11B1;BA26;1106 1164 11B1; # (먦; 먦; 먦; 먦; 먦; ) HANGUL SYLLABLE MYAELM
+BA27;BA27;1106 1164 11B2;BA27;1106 1164 11B2; # (먧; 먧; 먧; 먧; 먧; ) HANGUL SYLLABLE MYAELB
+BA28;BA28;1106 1164 11B3;BA28;1106 1164 11B3; # (먨; 먨; 먨; 먨; 먨; ) HANGUL SYLLABLE MYAELS
+BA29;BA29;1106 1164 11B4;BA29;1106 1164 11B4; # (먩; 먩; 먩; 먩; 먩; ) HANGUL SYLLABLE MYAELT
+BA2A;BA2A;1106 1164 11B5;BA2A;1106 1164 11B5; # (먪; 먪; 먪; 먪; 먪; ) HANGUL SYLLABLE MYAELP
+BA2B;BA2B;1106 1164 11B6;BA2B;1106 1164 11B6; # (먫; 먫; 먫; 먫; 먫; ) HANGUL SYLLABLE MYAELH
+BA2C;BA2C;1106 1164 11B7;BA2C;1106 1164 11B7; # (먬; 먬; 먬; 먬; 먬; ) HANGUL SYLLABLE MYAEM
+BA2D;BA2D;1106 1164 11B8;BA2D;1106 1164 11B8; # (먭; 먭; 먭; 먭; 먭; ) HANGUL SYLLABLE MYAEB
+BA2E;BA2E;1106 1164 11B9;BA2E;1106 1164 11B9; # (먮; 먮; 먮; 먮; 먮; ) HANGUL SYLLABLE MYAEBS
+BA2F;BA2F;1106 1164 11BA;BA2F;1106 1164 11BA; # (먯; 먯; 먯; 먯; 먯; ) HANGUL SYLLABLE MYAES
+BA30;BA30;1106 1164 11BB;BA30;1106 1164 11BB; # (먰; 먰; 먰; 먰; 먰; ) HANGUL SYLLABLE MYAESS
+BA31;BA31;1106 1164 11BC;BA31;1106 1164 11BC; # (먱; 먱; 먱; 먱; 먱; ) HANGUL SYLLABLE MYAENG
+BA32;BA32;1106 1164 11BD;BA32;1106 1164 11BD; # (먲; 먲; 먲; 먲; 먲; ) HANGUL SYLLABLE MYAEJ
+BA33;BA33;1106 1164 11BE;BA33;1106 1164 11BE; # (먳; 먳; 먳; 먳; 먳; ) HANGUL SYLLABLE MYAEC
+BA34;BA34;1106 1164 11BF;BA34;1106 1164 11BF; # (먴; 먴; 먴; 먴; 먴; ) HANGUL SYLLABLE MYAEK
+BA35;BA35;1106 1164 11C0;BA35;1106 1164 11C0; # (먵; 먵; 먵; 먵; 먵; ) HANGUL SYLLABLE MYAET
+BA36;BA36;1106 1164 11C1;BA36;1106 1164 11C1; # (먶; 먶; 먜á‡; 먶; 먜á‡; ) HANGUL SYLLABLE MYAEP
+BA37;BA37;1106 1164 11C2;BA37;1106 1164 11C2; # (먷; 먷; 먷; 먷; 먷; ) HANGUL SYLLABLE MYAEH
+BA38;BA38;1106 1165;BA38;1106 1165; # (머; 머; 머; 머; 머; ) HANGUL SYLLABLE MEO
+BA39;BA39;1106 1165 11A8;BA39;1106 1165 11A8; # (먹; 먹; 먹; 먹; 먹; ) HANGUL SYLLABLE MEOG
+BA3A;BA3A;1106 1165 11A9;BA3A;1106 1165 11A9; # (먺; 먺; 먺; 먺; 먺; ) HANGUL SYLLABLE MEOGG
+BA3B;BA3B;1106 1165 11AA;BA3B;1106 1165 11AA; # (먻; 먻; 먻; 먻; 먻; ) HANGUL SYLLABLE MEOGS
+BA3C;BA3C;1106 1165 11AB;BA3C;1106 1165 11AB; # (먼; 먼; 먼; 먼; 먼; ) HANGUL SYLLABLE MEON
+BA3D;BA3D;1106 1165 11AC;BA3D;1106 1165 11AC; # (먽; 먽; 먽; 먽; 먽; ) HANGUL SYLLABLE MEONJ
+BA3E;BA3E;1106 1165 11AD;BA3E;1106 1165 11AD; # (먾; 먾; 먾; 먾; 먾; ) HANGUL SYLLABLE MEONH
+BA3F;BA3F;1106 1165 11AE;BA3F;1106 1165 11AE; # (먿; 먿; 먿; 먿; 먿; ) HANGUL SYLLABLE MEOD
+BA40;BA40;1106 1165 11AF;BA40;1106 1165 11AF; # (멀; 멀; 멀; 멀; 멀; ) HANGUL SYLLABLE MEOL
+BA41;BA41;1106 1165 11B0;BA41;1106 1165 11B0; # (ë©; ë©; 멁; ë©; 멁; ) HANGUL SYLLABLE MEOLG
+BA42;BA42;1106 1165 11B1;BA42;1106 1165 11B1; # (멂; 멂; 멂; 멂; 멂; ) HANGUL SYLLABLE MEOLM
+BA43;BA43;1106 1165 11B2;BA43;1106 1165 11B2; # (멃; 멃; 멃; 멃; 멃; ) HANGUL SYLLABLE MEOLB
+BA44;BA44;1106 1165 11B3;BA44;1106 1165 11B3; # (멄; 멄; 멄; 멄; 멄; ) HANGUL SYLLABLE MEOLS
+BA45;BA45;1106 1165 11B4;BA45;1106 1165 11B4; # (멅; 멅; 멅; 멅; 멅; ) HANGUL SYLLABLE MEOLT
+BA46;BA46;1106 1165 11B5;BA46;1106 1165 11B5; # (멆; 멆; 멆; 멆; 멆; ) HANGUL SYLLABLE MEOLP
+BA47;BA47;1106 1165 11B6;BA47;1106 1165 11B6; # (멇; 멇; 멇; 멇; 멇; ) HANGUL SYLLABLE MEOLH
+BA48;BA48;1106 1165 11B7;BA48;1106 1165 11B7; # (멈; 멈; 멈; 멈; 멈; ) HANGUL SYLLABLE MEOM
+BA49;BA49;1106 1165 11B8;BA49;1106 1165 11B8; # (멉; 멉; 멉; 멉; 멉; ) HANGUL SYLLABLE MEOB
+BA4A;BA4A;1106 1165 11B9;BA4A;1106 1165 11B9; # (멊; 멊; 멊; 멊; 멊; ) HANGUL SYLLABLE MEOBS
+BA4B;BA4B;1106 1165 11BA;BA4B;1106 1165 11BA; # (멋; 멋; 멋; 멋; 멋; ) HANGUL SYLLABLE MEOS
+BA4C;BA4C;1106 1165 11BB;BA4C;1106 1165 11BB; # (멌; 멌; 멌; 멌; 멌; ) HANGUL SYLLABLE MEOSS
+BA4D;BA4D;1106 1165 11BC;BA4D;1106 1165 11BC; # (ë©; ë©; 멍; ë©; 멍; ) HANGUL SYLLABLE MEONG
+BA4E;BA4E;1106 1165 11BD;BA4E;1106 1165 11BD; # (멎; 멎; 멎; 멎; 멎; ) HANGUL SYLLABLE MEOJ
+BA4F;BA4F;1106 1165 11BE;BA4F;1106 1165 11BE; # (ë©; ë©; 멏; ë©; 멏; ) HANGUL SYLLABLE MEOC
+BA50;BA50;1106 1165 11BF;BA50;1106 1165 11BF; # (ë©; ë©; 멐; ë©; 멐; ) HANGUL SYLLABLE MEOK
+BA51;BA51;1106 1165 11C0;BA51;1106 1165 11C0; # (멑; 멑; 멑; 멑; 멑; ) HANGUL SYLLABLE MEOT
+BA52;BA52;1106 1165 11C1;BA52;1106 1165 11C1; # (ë©’; ë©’; 머á‡; ë©’; 머á‡; ) HANGUL SYLLABLE MEOP
+BA53;BA53;1106 1165 11C2;BA53;1106 1165 11C2; # (멓; 멓; 멓; 멓; 멓; ) HANGUL SYLLABLE MEOH
+BA54;BA54;1106 1166;BA54;1106 1166; # (메; 메; 메; 메; 메; ) HANGUL SYLLABLE ME
+BA55;BA55;1106 1166 11A8;BA55;1106 1166 11A8; # (멕; 멕; 멕; 멕; 멕; ) HANGUL SYLLABLE MEG
+BA56;BA56;1106 1166 11A9;BA56;1106 1166 11A9; # (멖; 멖; 멖; 멖; 멖; ) HANGUL SYLLABLE MEGG
+BA57;BA57;1106 1166 11AA;BA57;1106 1166 11AA; # (멗; 멗; 멗; 멗; 멗; ) HANGUL SYLLABLE MEGS
+BA58;BA58;1106 1166 11AB;BA58;1106 1166 11AB; # (멘; 멘; 멘; 멘; 멘; ) HANGUL SYLLABLE MEN
+BA59;BA59;1106 1166 11AC;BA59;1106 1166 11AC; # (멙; 멙; 멙; 멙; 멙; ) HANGUL SYLLABLE MENJ
+BA5A;BA5A;1106 1166 11AD;BA5A;1106 1166 11AD; # (멚; 멚; 멚; 멚; 멚; ) HANGUL SYLLABLE MENH
+BA5B;BA5B;1106 1166 11AE;BA5B;1106 1166 11AE; # (멛; 멛; 멛; 멛; 멛; ) HANGUL SYLLABLE MED
+BA5C;BA5C;1106 1166 11AF;BA5C;1106 1166 11AF; # (멜; 멜; 멜; 멜; 멜; ) HANGUL SYLLABLE MEL
+BA5D;BA5D;1106 1166 11B0;BA5D;1106 1166 11B0; # (ë©; ë©; 멝; ë©; 멝; ) HANGUL SYLLABLE MELG
+BA5E;BA5E;1106 1166 11B1;BA5E;1106 1166 11B1; # (멞; 멞; 멞; 멞; 멞; ) HANGUL SYLLABLE MELM
+BA5F;BA5F;1106 1166 11B2;BA5F;1106 1166 11B2; # (멟; 멟; 멟; 멟; 멟; ) HANGUL SYLLABLE MELB
+BA60;BA60;1106 1166 11B3;BA60;1106 1166 11B3; # (멠; 멠; 멠; 멠; 멠; ) HANGUL SYLLABLE MELS
+BA61;BA61;1106 1166 11B4;BA61;1106 1166 11B4; # (멡; 멡; 멡; 멡; 멡; ) HANGUL SYLLABLE MELT
+BA62;BA62;1106 1166 11B5;BA62;1106 1166 11B5; # (멢; 멢; 멢; 멢; 멢; ) HANGUL SYLLABLE MELP
+BA63;BA63;1106 1166 11B6;BA63;1106 1166 11B6; # (멣; 멣; 멣; 멣; 멣; ) HANGUL SYLLABLE MELH
+BA64;BA64;1106 1166 11B7;BA64;1106 1166 11B7; # (멤; 멤; 멤; 멤; 멤; ) HANGUL SYLLABLE MEM
+BA65;BA65;1106 1166 11B8;BA65;1106 1166 11B8; # (멥; 멥; 멥; 멥; 멥; ) HANGUL SYLLABLE MEB
+BA66;BA66;1106 1166 11B9;BA66;1106 1166 11B9; # (멦; 멦; 멦; 멦; 멦; ) HANGUL SYLLABLE MEBS
+BA67;BA67;1106 1166 11BA;BA67;1106 1166 11BA; # (멧; 멧; 멧; 멧; 멧; ) HANGUL SYLLABLE MES
+BA68;BA68;1106 1166 11BB;BA68;1106 1166 11BB; # (멨; 멨; 멨; 멨; 멨; ) HANGUL SYLLABLE MESS
+BA69;BA69;1106 1166 11BC;BA69;1106 1166 11BC; # (멩; 멩; 멩; 멩; 멩; ) HANGUL SYLLABLE MENG
+BA6A;BA6A;1106 1166 11BD;BA6A;1106 1166 11BD; # (멪; 멪; 멪; 멪; 멪; ) HANGUL SYLLABLE MEJ
+BA6B;BA6B;1106 1166 11BE;BA6B;1106 1166 11BE; # (멫; 멫; 멫; 멫; 멫; ) HANGUL SYLLABLE MEC
+BA6C;BA6C;1106 1166 11BF;BA6C;1106 1166 11BF; # (멬; 멬; 멬; 멬; 멬; ) HANGUL SYLLABLE MEK
+BA6D;BA6D;1106 1166 11C0;BA6D;1106 1166 11C0; # (멭; 멭; 멭; 멭; 멭; ) HANGUL SYLLABLE MET
+BA6E;BA6E;1106 1166 11C1;BA6E;1106 1166 11C1; # (ë©®; ë©®; 메á‡; ë©®; 메á‡; ) HANGUL SYLLABLE MEP
+BA6F;BA6F;1106 1166 11C2;BA6F;1106 1166 11C2; # (멯; 멯; 멯; 멯; 멯; ) HANGUL SYLLABLE MEH
+BA70;BA70;1106 1167;BA70;1106 1167; # (며; 며; 며; 며; 며; ) HANGUL SYLLABLE MYEO
+BA71;BA71;1106 1167 11A8;BA71;1106 1167 11A8; # (멱; 멱; 멱; 멱; 멱; ) HANGUL SYLLABLE MYEOG
+BA72;BA72;1106 1167 11A9;BA72;1106 1167 11A9; # (멲; 멲; 멲; 멲; 멲; ) HANGUL SYLLABLE MYEOGG
+BA73;BA73;1106 1167 11AA;BA73;1106 1167 11AA; # (멳; 멳; 멳; 멳; 멳; ) HANGUL SYLLABLE MYEOGS
+BA74;BA74;1106 1167 11AB;BA74;1106 1167 11AB; # (면; 면; 면; 면; 면; ) HANGUL SYLLABLE MYEON
+BA75;BA75;1106 1167 11AC;BA75;1106 1167 11AC; # (멵; 멵; 멵; 멵; 멵; ) HANGUL SYLLABLE MYEONJ
+BA76;BA76;1106 1167 11AD;BA76;1106 1167 11AD; # (멶; 멶; 멶; 멶; 멶; ) HANGUL SYLLABLE MYEONH
+BA77;BA77;1106 1167 11AE;BA77;1106 1167 11AE; # (멷; 멷; 멷; 멷; 멷; ) HANGUL SYLLABLE MYEOD
+BA78;BA78;1106 1167 11AF;BA78;1106 1167 11AF; # (멸; 멸; 멸; 멸; 멸; ) HANGUL SYLLABLE MYEOL
+BA79;BA79;1106 1167 11B0;BA79;1106 1167 11B0; # (멹; 멹; 멹; 멹; 멹; ) HANGUL SYLLABLE MYEOLG
+BA7A;BA7A;1106 1167 11B1;BA7A;1106 1167 11B1; # (멺; 멺; 멺; 멺; 멺; ) HANGUL SYLLABLE MYEOLM
+BA7B;BA7B;1106 1167 11B2;BA7B;1106 1167 11B2; # (멻; 멻; 멻; 멻; 멻; ) HANGUL SYLLABLE MYEOLB
+BA7C;BA7C;1106 1167 11B3;BA7C;1106 1167 11B3; # (멼; 멼; 멼; 멼; 멼; ) HANGUL SYLLABLE MYEOLS
+BA7D;BA7D;1106 1167 11B4;BA7D;1106 1167 11B4; # (멽; 멽; 멽; 멽; 멽; ) HANGUL SYLLABLE MYEOLT
+BA7E;BA7E;1106 1167 11B5;BA7E;1106 1167 11B5; # (멾; 멾; 멾; 멾; 멾; ) HANGUL SYLLABLE MYEOLP
+BA7F;BA7F;1106 1167 11B6;BA7F;1106 1167 11B6; # (멿; 멿; 멿; 멿; 멿; ) HANGUL SYLLABLE MYEOLH
+BA80;BA80;1106 1167 11B7;BA80;1106 1167 11B7; # (몀; 몀; 몀; 몀; 몀; ) HANGUL SYLLABLE MYEOM
+BA81;BA81;1106 1167 11B8;BA81;1106 1167 11B8; # (ëª; ëª; 몁; ëª; 몁; ) HANGUL SYLLABLE MYEOB
+BA82;BA82;1106 1167 11B9;BA82;1106 1167 11B9; # (몂; 몂; 몂; 몂; 몂; ) HANGUL SYLLABLE MYEOBS
+BA83;BA83;1106 1167 11BA;BA83;1106 1167 11BA; # (몃; 몃; 몃; 몃; 몃; ) HANGUL SYLLABLE MYEOS
+BA84;BA84;1106 1167 11BB;BA84;1106 1167 11BB; # (몄; 몄; 몄; 몄; 몄; ) HANGUL SYLLABLE MYEOSS
+BA85;BA85;1106 1167 11BC;BA85;1106 1167 11BC; # (명; 명; 명; 명; 명; ) HANGUL SYLLABLE MYEONG
+BA86;BA86;1106 1167 11BD;BA86;1106 1167 11BD; # (몆; 몆; 몆; 몆; 몆; ) HANGUL SYLLABLE MYEOJ
+BA87;BA87;1106 1167 11BE;BA87;1106 1167 11BE; # (몇; 몇; 몇; 몇; 몇; ) HANGUL SYLLABLE MYEOC
+BA88;BA88;1106 1167 11BF;BA88;1106 1167 11BF; # (몈; 몈; 몈; 몈; 몈; ) HANGUL SYLLABLE MYEOK
+BA89;BA89;1106 1167 11C0;BA89;1106 1167 11C0; # (몉; 몉; 몉; 몉; 몉; ) HANGUL SYLLABLE MYEOT
+BA8A;BA8A;1106 1167 11C1;BA8A;1106 1167 11C1; # (몊; 몊; 며á‡; 몊; 며á‡; ) HANGUL SYLLABLE MYEOP
+BA8B;BA8B;1106 1167 11C2;BA8B;1106 1167 11C2; # (몋; 몋; 몋; 몋; 몋; ) HANGUL SYLLABLE MYEOH
+BA8C;BA8C;1106 1168;BA8C;1106 1168; # (몌; 몌; 몌; 몌; 몌; ) HANGUL SYLLABLE MYE
+BA8D;BA8D;1106 1168 11A8;BA8D;1106 1168 11A8; # (ëª; ëª; 몍; ëª; 몍; ) HANGUL SYLLABLE MYEG
+BA8E;BA8E;1106 1168 11A9;BA8E;1106 1168 11A9; # (몎; 몎; 몎; 몎; 몎; ) HANGUL SYLLABLE MYEGG
+BA8F;BA8F;1106 1168 11AA;BA8F;1106 1168 11AA; # (ëª; ëª; 몏; ëª; 몏; ) HANGUL SYLLABLE MYEGS
+BA90;BA90;1106 1168 11AB;BA90;1106 1168 11AB; # (ëª; ëª; 몐; ëª; 몐; ) HANGUL SYLLABLE MYEN
+BA91;BA91;1106 1168 11AC;BA91;1106 1168 11AC; # (몑; 몑; 몑; 몑; 몑; ) HANGUL SYLLABLE MYENJ
+BA92;BA92;1106 1168 11AD;BA92;1106 1168 11AD; # (몒; 몒; 몒; 몒; 몒; ) HANGUL SYLLABLE MYENH
+BA93;BA93;1106 1168 11AE;BA93;1106 1168 11AE; # (몓; 몓; 몓; 몓; 몓; ) HANGUL SYLLABLE MYED
+BA94;BA94;1106 1168 11AF;BA94;1106 1168 11AF; # (몔; 몔; 몔; 몔; 몔; ) HANGUL SYLLABLE MYEL
+BA95;BA95;1106 1168 11B0;BA95;1106 1168 11B0; # (몕; 몕; 몕; 몕; 몕; ) HANGUL SYLLABLE MYELG
+BA96;BA96;1106 1168 11B1;BA96;1106 1168 11B1; # (몖; 몖; 몖; 몖; 몖; ) HANGUL SYLLABLE MYELM
+BA97;BA97;1106 1168 11B2;BA97;1106 1168 11B2; # (몗; 몗; 몗; 몗; 몗; ) HANGUL SYLLABLE MYELB
+BA98;BA98;1106 1168 11B3;BA98;1106 1168 11B3; # (몘; 몘; 몘; 몘; 몘; ) HANGUL SYLLABLE MYELS
+BA99;BA99;1106 1168 11B4;BA99;1106 1168 11B4; # (몙; 몙; 몙; 몙; 몙; ) HANGUL SYLLABLE MYELT
+BA9A;BA9A;1106 1168 11B5;BA9A;1106 1168 11B5; # (몚; 몚; 몚; 몚; 몚; ) HANGUL SYLLABLE MYELP
+BA9B;BA9B;1106 1168 11B6;BA9B;1106 1168 11B6; # (몛; 몛; 몛; 몛; 몛; ) HANGUL SYLLABLE MYELH
+BA9C;BA9C;1106 1168 11B7;BA9C;1106 1168 11B7; # (몜; 몜; 몜; 몜; 몜; ) HANGUL SYLLABLE MYEM
+BA9D;BA9D;1106 1168 11B8;BA9D;1106 1168 11B8; # (ëª; ëª; 몝; ëª; 몝; ) HANGUL SYLLABLE MYEB
+BA9E;BA9E;1106 1168 11B9;BA9E;1106 1168 11B9; # (몞; 몞; 몞; 몞; 몞; ) HANGUL SYLLABLE MYEBS
+BA9F;BA9F;1106 1168 11BA;BA9F;1106 1168 11BA; # (몟; 몟; 몟; 몟; 몟; ) HANGUL SYLLABLE MYES
+BAA0;BAA0;1106 1168 11BB;BAA0;1106 1168 11BB; # (몠; 몠; 몠; 몠; 몠; ) HANGUL SYLLABLE MYESS
+BAA1;BAA1;1106 1168 11BC;BAA1;1106 1168 11BC; # (몡; 몡; 몡; 몡; 몡; ) HANGUL SYLLABLE MYENG
+BAA2;BAA2;1106 1168 11BD;BAA2;1106 1168 11BD; # (몢; 몢; 몢; 몢; 몢; ) HANGUL SYLLABLE MYEJ
+BAA3;BAA3;1106 1168 11BE;BAA3;1106 1168 11BE; # (몣; 몣; 몣; 몣; 몣; ) HANGUL SYLLABLE MYEC
+BAA4;BAA4;1106 1168 11BF;BAA4;1106 1168 11BF; # (몤; 몤; 몤; 몤; 몤; ) HANGUL SYLLABLE MYEK
+BAA5;BAA5;1106 1168 11C0;BAA5;1106 1168 11C0; # (몥; 몥; 몥; 몥; 몥; ) HANGUL SYLLABLE MYET
+BAA6;BAA6;1106 1168 11C1;BAA6;1106 1168 11C1; # (몦; 몦; 몌á‡; 몦; 몌á‡; ) HANGUL SYLLABLE MYEP
+BAA7;BAA7;1106 1168 11C2;BAA7;1106 1168 11C2; # (몧; 몧; 몧; 몧; 몧; ) HANGUL SYLLABLE MYEH
+BAA8;BAA8;1106 1169;BAA8;1106 1169; # (모; 모; 모; 모; 모; ) HANGUL SYLLABLE MO
+BAA9;BAA9;1106 1169 11A8;BAA9;1106 1169 11A8; # (목; 목; 목; 목; 목; ) HANGUL SYLLABLE MOG
+BAAA;BAAA;1106 1169 11A9;BAAA;1106 1169 11A9; # (몪; 몪; 몪; 몪; 몪; ) HANGUL SYLLABLE MOGG
+BAAB;BAAB;1106 1169 11AA;BAAB;1106 1169 11AA; # (몫; 몫; 몫; 몫; 몫; ) HANGUL SYLLABLE MOGS
+BAAC;BAAC;1106 1169 11AB;BAAC;1106 1169 11AB; # (몬; 몬; 몬; 몬; 몬; ) HANGUL SYLLABLE MON
+BAAD;BAAD;1106 1169 11AC;BAAD;1106 1169 11AC; # (몭; 몭; 몭; 몭; 몭; ) HANGUL SYLLABLE MONJ
+BAAE;BAAE;1106 1169 11AD;BAAE;1106 1169 11AD; # (몮; 몮; 몮; 몮; 몮; ) HANGUL SYLLABLE MONH
+BAAF;BAAF;1106 1169 11AE;BAAF;1106 1169 11AE; # (몯; 몯; 몯; 몯; 몯; ) HANGUL SYLLABLE MOD
+BAB0;BAB0;1106 1169 11AF;BAB0;1106 1169 11AF; # (몰; 몰; 몰; 몰; 몰; ) HANGUL SYLLABLE MOL
+BAB1;BAB1;1106 1169 11B0;BAB1;1106 1169 11B0; # (몱; 몱; 몱; 몱; 몱; ) HANGUL SYLLABLE MOLG
+BAB2;BAB2;1106 1169 11B1;BAB2;1106 1169 11B1; # (몲; 몲; 몲; 몲; 몲; ) HANGUL SYLLABLE MOLM
+BAB3;BAB3;1106 1169 11B2;BAB3;1106 1169 11B2; # (몳; 몳; 몳; 몳; 몳; ) HANGUL SYLLABLE MOLB
+BAB4;BAB4;1106 1169 11B3;BAB4;1106 1169 11B3; # (몴; 몴; 몴; 몴; 몴; ) HANGUL SYLLABLE MOLS
+BAB5;BAB5;1106 1169 11B4;BAB5;1106 1169 11B4; # (몵; 몵; 몵; 몵; 몵; ) HANGUL SYLLABLE MOLT
+BAB6;BAB6;1106 1169 11B5;BAB6;1106 1169 11B5; # (몶; 몶; 몶; 몶; 몶; ) HANGUL SYLLABLE MOLP
+BAB7;BAB7;1106 1169 11B6;BAB7;1106 1169 11B6; # (몷; 몷; 몷; 몷; 몷; ) HANGUL SYLLABLE MOLH
+BAB8;BAB8;1106 1169 11B7;BAB8;1106 1169 11B7; # (몸; 몸; 몸; 몸; 몸; ) HANGUL SYLLABLE MOM
+BAB9;BAB9;1106 1169 11B8;BAB9;1106 1169 11B8; # (몹; 몹; 몹; 몹; 몹; ) HANGUL SYLLABLE MOB
+BABA;BABA;1106 1169 11B9;BABA;1106 1169 11B9; # (몺; 몺; 몺; 몺; 몺; ) HANGUL SYLLABLE MOBS
+BABB;BABB;1106 1169 11BA;BABB;1106 1169 11BA; # (못; 못; 못; 못; 못; ) HANGUL SYLLABLE MOS
+BABC;BABC;1106 1169 11BB;BABC;1106 1169 11BB; # (몼; 몼; 몼; 몼; 몼; ) HANGUL SYLLABLE MOSS
+BABD;BABD;1106 1169 11BC;BABD;1106 1169 11BC; # (몽; 몽; 몽; 몽; 몽; ) HANGUL SYLLABLE MONG
+BABE;BABE;1106 1169 11BD;BABE;1106 1169 11BD; # (몾; 몾; 몾; 몾; 몾; ) HANGUL SYLLABLE MOJ
+BABF;BABF;1106 1169 11BE;BABF;1106 1169 11BE; # (몿; 몿; 몿; 몿; 몿; ) HANGUL SYLLABLE MOC
+BAC0;BAC0;1106 1169 11BF;BAC0;1106 1169 11BF; # (뫀; 뫀; 뫀; 뫀; 뫀; ) HANGUL SYLLABLE MOK
+BAC1;BAC1;1106 1169 11C0;BAC1;1106 1169 11C0; # (ë«; ë«; 뫁; ë«; 뫁; ) HANGUL SYLLABLE MOT
+BAC2;BAC2;1106 1169 11C1;BAC2;1106 1169 11C1; # (ë«‚; ë«‚; 모á‡; ë«‚; 모á‡; ) HANGUL SYLLABLE MOP
+BAC3;BAC3;1106 1169 11C2;BAC3;1106 1169 11C2; # (뫃; 뫃; 뫃; 뫃; 뫃; ) HANGUL SYLLABLE MOH
+BAC4;BAC4;1106 116A;BAC4;1106 116A; # (뫄; 뫄; 뫄; 뫄; 뫄; ) HANGUL SYLLABLE MWA
+BAC5;BAC5;1106 116A 11A8;BAC5;1106 116A 11A8; # (뫅; 뫅; 뫅; 뫅; 뫅; ) HANGUL SYLLABLE MWAG
+BAC6;BAC6;1106 116A 11A9;BAC6;1106 116A 11A9; # (뫆; 뫆; 뫆; 뫆; 뫆; ) HANGUL SYLLABLE MWAGG
+BAC7;BAC7;1106 116A 11AA;BAC7;1106 116A 11AA; # (뫇; 뫇; 뫇; 뫇; 뫇; ) HANGUL SYLLABLE MWAGS
+BAC8;BAC8;1106 116A 11AB;BAC8;1106 116A 11AB; # (뫈; 뫈; 뫈; 뫈; 뫈; ) HANGUL SYLLABLE MWAN
+BAC9;BAC9;1106 116A 11AC;BAC9;1106 116A 11AC; # (뫉; 뫉; 뫉; 뫉; 뫉; ) HANGUL SYLLABLE MWANJ
+BACA;BACA;1106 116A 11AD;BACA;1106 116A 11AD; # (뫊; 뫊; 뫊; 뫊; 뫊; ) HANGUL SYLLABLE MWANH
+BACB;BACB;1106 116A 11AE;BACB;1106 116A 11AE; # (뫋; 뫋; 뫋; 뫋; 뫋; ) HANGUL SYLLABLE MWAD
+BACC;BACC;1106 116A 11AF;BACC;1106 116A 11AF; # (뫌; 뫌; 뫌; 뫌; 뫌; ) HANGUL SYLLABLE MWAL
+BACD;BACD;1106 116A 11B0;BACD;1106 116A 11B0; # (ë«; ë«; 뫍; ë«; 뫍; ) HANGUL SYLLABLE MWALG
+BACE;BACE;1106 116A 11B1;BACE;1106 116A 11B1; # (뫎; 뫎; 뫎; 뫎; 뫎; ) HANGUL SYLLABLE MWALM
+BACF;BACF;1106 116A 11B2;BACF;1106 116A 11B2; # (ë«; ë«; 뫏; ë«; 뫏; ) HANGUL SYLLABLE MWALB
+BAD0;BAD0;1106 116A 11B3;BAD0;1106 116A 11B3; # (ë«; ë«; 뫐; ë«; 뫐; ) HANGUL SYLLABLE MWALS
+BAD1;BAD1;1106 116A 11B4;BAD1;1106 116A 11B4; # (뫑; 뫑; 뫑; 뫑; 뫑; ) HANGUL SYLLABLE MWALT
+BAD2;BAD2;1106 116A 11B5;BAD2;1106 116A 11B5; # (뫒; 뫒; 뫒; 뫒; 뫒; ) HANGUL SYLLABLE MWALP
+BAD3;BAD3;1106 116A 11B6;BAD3;1106 116A 11B6; # (뫓; 뫓; 뫓; 뫓; 뫓; ) HANGUL SYLLABLE MWALH
+BAD4;BAD4;1106 116A 11B7;BAD4;1106 116A 11B7; # (뫔; 뫔; 뫔; 뫔; 뫔; ) HANGUL SYLLABLE MWAM
+BAD5;BAD5;1106 116A 11B8;BAD5;1106 116A 11B8; # (뫕; 뫕; 뫕; 뫕; 뫕; ) HANGUL SYLLABLE MWAB
+BAD6;BAD6;1106 116A 11B9;BAD6;1106 116A 11B9; # (뫖; 뫖; 뫖; 뫖; 뫖; ) HANGUL SYLLABLE MWABS
+BAD7;BAD7;1106 116A 11BA;BAD7;1106 116A 11BA; # (뫗; 뫗; 뫗; 뫗; 뫗; ) HANGUL SYLLABLE MWAS
+BAD8;BAD8;1106 116A 11BB;BAD8;1106 116A 11BB; # (뫘; 뫘; 뫘; 뫘; 뫘; ) HANGUL SYLLABLE MWASS
+BAD9;BAD9;1106 116A 11BC;BAD9;1106 116A 11BC; # (뫙; 뫙; 뫙; 뫙; 뫙; ) HANGUL SYLLABLE MWANG
+BADA;BADA;1106 116A 11BD;BADA;1106 116A 11BD; # (뫚; 뫚; 뫚; 뫚; 뫚; ) HANGUL SYLLABLE MWAJ
+BADB;BADB;1106 116A 11BE;BADB;1106 116A 11BE; # (뫛; 뫛; 뫛; 뫛; 뫛; ) HANGUL SYLLABLE MWAC
+BADC;BADC;1106 116A 11BF;BADC;1106 116A 11BF; # (뫜; 뫜; 뫜; 뫜; 뫜; ) HANGUL SYLLABLE MWAK
+BADD;BADD;1106 116A 11C0;BADD;1106 116A 11C0; # (ë«; ë«; 뫝; ë«; 뫝; ) HANGUL SYLLABLE MWAT
+BADE;BADE;1106 116A 11C1;BADE;1106 116A 11C1; # (ë«ž; ë«ž; 뫄á‡; ë«ž; 뫄á‡; ) HANGUL SYLLABLE MWAP
+BADF;BADF;1106 116A 11C2;BADF;1106 116A 11C2; # (뫟; 뫟; 뫟; 뫟; 뫟; ) HANGUL SYLLABLE MWAH
+BAE0;BAE0;1106 116B;BAE0;1106 116B; # (뫠; 뫠; 뫠; 뫠; 뫠; ) HANGUL SYLLABLE MWAE
+BAE1;BAE1;1106 116B 11A8;BAE1;1106 116B 11A8; # (뫡; 뫡; 뫡; 뫡; 뫡; ) HANGUL SYLLABLE MWAEG
+BAE2;BAE2;1106 116B 11A9;BAE2;1106 116B 11A9; # (뫢; 뫢; 뫢; 뫢; 뫢; ) HANGUL SYLLABLE MWAEGG
+BAE3;BAE3;1106 116B 11AA;BAE3;1106 116B 11AA; # (뫣; 뫣; 뫣; 뫣; 뫣; ) HANGUL SYLLABLE MWAEGS
+BAE4;BAE4;1106 116B 11AB;BAE4;1106 116B 11AB; # (뫤; 뫤; 뫤; 뫤; 뫤; ) HANGUL SYLLABLE MWAEN
+BAE5;BAE5;1106 116B 11AC;BAE5;1106 116B 11AC; # (뫥; 뫥; 뫥; 뫥; 뫥; ) HANGUL SYLLABLE MWAENJ
+BAE6;BAE6;1106 116B 11AD;BAE6;1106 116B 11AD; # (뫦; 뫦; 뫦; 뫦; 뫦; ) HANGUL SYLLABLE MWAENH
+BAE7;BAE7;1106 116B 11AE;BAE7;1106 116B 11AE; # (뫧; 뫧; 뫧; 뫧; 뫧; ) HANGUL SYLLABLE MWAED
+BAE8;BAE8;1106 116B 11AF;BAE8;1106 116B 11AF; # (뫨; 뫨; 뫨; 뫨; 뫨; ) HANGUL SYLLABLE MWAEL
+BAE9;BAE9;1106 116B 11B0;BAE9;1106 116B 11B0; # (뫩; 뫩; 뫩; 뫩; 뫩; ) HANGUL SYLLABLE MWAELG
+BAEA;BAEA;1106 116B 11B1;BAEA;1106 116B 11B1; # (뫪; 뫪; 뫪; 뫪; 뫪; ) HANGUL SYLLABLE MWAELM
+BAEB;BAEB;1106 116B 11B2;BAEB;1106 116B 11B2; # (뫫; 뫫; 뫫; 뫫; 뫫; ) HANGUL SYLLABLE MWAELB
+BAEC;BAEC;1106 116B 11B3;BAEC;1106 116B 11B3; # (뫬; 뫬; 뫬; 뫬; 뫬; ) HANGUL SYLLABLE MWAELS
+BAED;BAED;1106 116B 11B4;BAED;1106 116B 11B4; # (뫭; 뫭; 뫭; 뫭; 뫭; ) HANGUL SYLLABLE MWAELT
+BAEE;BAEE;1106 116B 11B5;BAEE;1106 116B 11B5; # (뫮; 뫮; 뫮; 뫮; 뫮; ) HANGUL SYLLABLE MWAELP
+BAEF;BAEF;1106 116B 11B6;BAEF;1106 116B 11B6; # (뫯; 뫯; 뫯; 뫯; 뫯; ) HANGUL SYLLABLE MWAELH
+BAF0;BAF0;1106 116B 11B7;BAF0;1106 116B 11B7; # (뫰; 뫰; 뫰; 뫰; 뫰; ) HANGUL SYLLABLE MWAEM
+BAF1;BAF1;1106 116B 11B8;BAF1;1106 116B 11B8; # (뫱; 뫱; 뫱; 뫱; 뫱; ) HANGUL SYLLABLE MWAEB
+BAF2;BAF2;1106 116B 11B9;BAF2;1106 116B 11B9; # (뫲; 뫲; 뫲; 뫲; 뫲; ) HANGUL SYLLABLE MWAEBS
+BAF3;BAF3;1106 116B 11BA;BAF3;1106 116B 11BA; # (뫳; 뫳; 뫳; 뫳; 뫳; ) HANGUL SYLLABLE MWAES
+BAF4;BAF4;1106 116B 11BB;BAF4;1106 116B 11BB; # (뫴; 뫴; 뫴; 뫴; 뫴; ) HANGUL SYLLABLE MWAESS
+BAF5;BAF5;1106 116B 11BC;BAF5;1106 116B 11BC; # (뫵; 뫵; 뫵; 뫵; 뫵; ) HANGUL SYLLABLE MWAENG
+BAF6;BAF6;1106 116B 11BD;BAF6;1106 116B 11BD; # (뫶; 뫶; 뫶; 뫶; 뫶; ) HANGUL SYLLABLE MWAEJ
+BAF7;BAF7;1106 116B 11BE;BAF7;1106 116B 11BE; # (뫷; 뫷; 뫷; 뫷; 뫷; ) HANGUL SYLLABLE MWAEC
+BAF8;BAF8;1106 116B 11BF;BAF8;1106 116B 11BF; # (뫸; 뫸; 뫸; 뫸; 뫸; ) HANGUL SYLLABLE MWAEK
+BAF9;BAF9;1106 116B 11C0;BAF9;1106 116B 11C0; # (뫹; 뫹; 뫹; 뫹; 뫹; ) HANGUL SYLLABLE MWAET
+BAFA;BAFA;1106 116B 11C1;BAFA;1106 116B 11C1; # (뫺; 뫺; 뫠á‡; 뫺; 뫠á‡; ) HANGUL SYLLABLE MWAEP
+BAFB;BAFB;1106 116B 11C2;BAFB;1106 116B 11C2; # (뫻; 뫻; 뫻; 뫻; 뫻; ) HANGUL SYLLABLE MWAEH
+BAFC;BAFC;1106 116C;BAFC;1106 116C; # (뫼; 뫼; 뫼; 뫼; 뫼; ) HANGUL SYLLABLE MOE
+BAFD;BAFD;1106 116C 11A8;BAFD;1106 116C 11A8; # (뫽; 뫽; 뫽; 뫽; 뫽; ) HANGUL SYLLABLE MOEG
+BAFE;BAFE;1106 116C 11A9;BAFE;1106 116C 11A9; # (뫾; 뫾; 뫾; 뫾; 뫾; ) HANGUL SYLLABLE MOEGG
+BAFF;BAFF;1106 116C 11AA;BAFF;1106 116C 11AA; # (뫿; 뫿; 뫿; 뫿; 뫿; ) HANGUL SYLLABLE MOEGS
+BB00;BB00;1106 116C 11AB;BB00;1106 116C 11AB; # (묀; 묀; 묀; 묀; 묀; ) HANGUL SYLLABLE MOEN
+BB01;BB01;1106 116C 11AC;BB01;1106 116C 11AC; # (ë¬; ë¬; 묁; ë¬; 묁; ) HANGUL SYLLABLE MOENJ
+BB02;BB02;1106 116C 11AD;BB02;1106 116C 11AD; # (묂; 묂; 묂; 묂; 묂; ) HANGUL SYLLABLE MOENH
+BB03;BB03;1106 116C 11AE;BB03;1106 116C 11AE; # (묃; 묃; 묃; 묃; 묃; ) HANGUL SYLLABLE MOED
+BB04;BB04;1106 116C 11AF;BB04;1106 116C 11AF; # (묄; 묄; 묄; 묄; 묄; ) HANGUL SYLLABLE MOEL
+BB05;BB05;1106 116C 11B0;BB05;1106 116C 11B0; # (묅; 묅; 묅; 묅; 묅; ) HANGUL SYLLABLE MOELG
+BB06;BB06;1106 116C 11B1;BB06;1106 116C 11B1; # (묆; 묆; 묆; 묆; 묆; ) HANGUL SYLLABLE MOELM
+BB07;BB07;1106 116C 11B2;BB07;1106 116C 11B2; # (묇; 묇; 묇; 묇; 묇; ) HANGUL SYLLABLE MOELB
+BB08;BB08;1106 116C 11B3;BB08;1106 116C 11B3; # (묈; 묈; 묈; 묈; 묈; ) HANGUL SYLLABLE MOELS
+BB09;BB09;1106 116C 11B4;BB09;1106 116C 11B4; # (묉; 묉; 묉; 묉; 묉; ) HANGUL SYLLABLE MOELT
+BB0A;BB0A;1106 116C 11B5;BB0A;1106 116C 11B5; # (묊; 묊; 묊; 묊; 묊; ) HANGUL SYLLABLE MOELP
+BB0B;BB0B;1106 116C 11B6;BB0B;1106 116C 11B6; # (묋; 묋; 묋; 묋; 묋; ) HANGUL SYLLABLE MOELH
+BB0C;BB0C;1106 116C 11B7;BB0C;1106 116C 11B7; # (묌; 묌; 묌; 묌; 묌; ) HANGUL SYLLABLE MOEM
+BB0D;BB0D;1106 116C 11B8;BB0D;1106 116C 11B8; # (ë¬; ë¬; 묍; ë¬; 묍; ) HANGUL SYLLABLE MOEB
+BB0E;BB0E;1106 116C 11B9;BB0E;1106 116C 11B9; # (묎; 묎; 묎; 묎; 묎; ) HANGUL SYLLABLE MOEBS
+BB0F;BB0F;1106 116C 11BA;BB0F;1106 116C 11BA; # (ë¬; ë¬; 묏; ë¬; 묏; ) HANGUL SYLLABLE MOES
+BB10;BB10;1106 116C 11BB;BB10;1106 116C 11BB; # (ë¬; ë¬; 묐; ë¬; 묐; ) HANGUL SYLLABLE MOESS
+BB11;BB11;1106 116C 11BC;BB11;1106 116C 11BC; # (묑; 묑; 묑; 묑; 묑; ) HANGUL SYLLABLE MOENG
+BB12;BB12;1106 116C 11BD;BB12;1106 116C 11BD; # (묒; 묒; 묒; 묒; 묒; ) HANGUL SYLLABLE MOEJ
+BB13;BB13;1106 116C 11BE;BB13;1106 116C 11BE; # (묓; 묓; 묓; 묓; 묓; ) HANGUL SYLLABLE MOEC
+BB14;BB14;1106 116C 11BF;BB14;1106 116C 11BF; # (묔; 묔; 묔; 묔; 묔; ) HANGUL SYLLABLE MOEK
+BB15;BB15;1106 116C 11C0;BB15;1106 116C 11C0; # (묕; 묕; 묕; 묕; 묕; ) HANGUL SYLLABLE MOET
+BB16;BB16;1106 116C 11C1;BB16;1106 116C 11C1; # (묖; 묖; 뫼á‡; 묖; 뫼á‡; ) HANGUL SYLLABLE MOEP
+BB17;BB17;1106 116C 11C2;BB17;1106 116C 11C2; # (묗; 묗; 묗; 묗; 묗; ) HANGUL SYLLABLE MOEH
+BB18;BB18;1106 116D;BB18;1106 116D; # (묘; 묘; 묘; 묘; 묘; ) HANGUL SYLLABLE MYO
+BB19;BB19;1106 116D 11A8;BB19;1106 116D 11A8; # (묙; 묙; 묙; 묙; 묙; ) HANGUL SYLLABLE MYOG
+BB1A;BB1A;1106 116D 11A9;BB1A;1106 116D 11A9; # (묚; 묚; 묚; 묚; 묚; ) HANGUL SYLLABLE MYOGG
+BB1B;BB1B;1106 116D 11AA;BB1B;1106 116D 11AA; # (묛; 묛; 묛; 묛; 묛; ) HANGUL SYLLABLE MYOGS
+BB1C;BB1C;1106 116D 11AB;BB1C;1106 116D 11AB; # (묜; 묜; 묜; 묜; 묜; ) HANGUL SYLLABLE MYON
+BB1D;BB1D;1106 116D 11AC;BB1D;1106 116D 11AC; # (ë¬; ë¬; 묝; ë¬; 묝; ) HANGUL SYLLABLE MYONJ
+BB1E;BB1E;1106 116D 11AD;BB1E;1106 116D 11AD; # (묞; 묞; 묞; 묞; 묞; ) HANGUL SYLLABLE MYONH
+BB1F;BB1F;1106 116D 11AE;BB1F;1106 116D 11AE; # (묟; 묟; 묟; 묟; 묟; ) HANGUL SYLLABLE MYOD
+BB20;BB20;1106 116D 11AF;BB20;1106 116D 11AF; # (묠; 묠; 묠; 묠; 묠; ) HANGUL SYLLABLE MYOL
+BB21;BB21;1106 116D 11B0;BB21;1106 116D 11B0; # (묡; 묡; 묡; 묡; 묡; ) HANGUL SYLLABLE MYOLG
+BB22;BB22;1106 116D 11B1;BB22;1106 116D 11B1; # (묢; 묢; 묢; 묢; 묢; ) HANGUL SYLLABLE MYOLM
+BB23;BB23;1106 116D 11B2;BB23;1106 116D 11B2; # (묣; 묣; 묣; 묣; 묣; ) HANGUL SYLLABLE MYOLB
+BB24;BB24;1106 116D 11B3;BB24;1106 116D 11B3; # (묤; 묤; 묤; 묤; 묤; ) HANGUL SYLLABLE MYOLS
+BB25;BB25;1106 116D 11B4;BB25;1106 116D 11B4; # (묥; 묥; 묥; 묥; 묥; ) HANGUL SYLLABLE MYOLT
+BB26;BB26;1106 116D 11B5;BB26;1106 116D 11B5; # (묦; 묦; 묦; 묦; 묦; ) HANGUL SYLLABLE MYOLP
+BB27;BB27;1106 116D 11B6;BB27;1106 116D 11B6; # (묧; 묧; 묧; 묧; 묧; ) HANGUL SYLLABLE MYOLH
+BB28;BB28;1106 116D 11B7;BB28;1106 116D 11B7; # (묨; 묨; 묨; 묨; 묨; ) HANGUL SYLLABLE MYOM
+BB29;BB29;1106 116D 11B8;BB29;1106 116D 11B8; # (묩; 묩; 묩; 묩; 묩; ) HANGUL SYLLABLE MYOB
+BB2A;BB2A;1106 116D 11B9;BB2A;1106 116D 11B9; # (묪; 묪; 묪; 묪; 묪; ) HANGUL SYLLABLE MYOBS
+BB2B;BB2B;1106 116D 11BA;BB2B;1106 116D 11BA; # (묫; 묫; 묫; 묫; 묫; ) HANGUL SYLLABLE MYOS
+BB2C;BB2C;1106 116D 11BB;BB2C;1106 116D 11BB; # (묬; 묬; 묬; 묬; 묬; ) HANGUL SYLLABLE MYOSS
+BB2D;BB2D;1106 116D 11BC;BB2D;1106 116D 11BC; # (묭; 묭; 묭; 묭; 묭; ) HANGUL SYLLABLE MYONG
+BB2E;BB2E;1106 116D 11BD;BB2E;1106 116D 11BD; # (묮; 묮; 묮; 묮; 묮; ) HANGUL SYLLABLE MYOJ
+BB2F;BB2F;1106 116D 11BE;BB2F;1106 116D 11BE; # (묯; 묯; 묯; 묯; 묯; ) HANGUL SYLLABLE MYOC
+BB30;BB30;1106 116D 11BF;BB30;1106 116D 11BF; # (묰; 묰; 묰; 묰; 묰; ) HANGUL SYLLABLE MYOK
+BB31;BB31;1106 116D 11C0;BB31;1106 116D 11C0; # (묱; 묱; 묱; 묱; 묱; ) HANGUL SYLLABLE MYOT
+BB32;BB32;1106 116D 11C1;BB32;1106 116D 11C1; # (묲; 묲; 묘á‡; 묲; 묘á‡; ) HANGUL SYLLABLE MYOP
+BB33;BB33;1106 116D 11C2;BB33;1106 116D 11C2; # (묳; 묳; 묳; 묳; 묳; ) HANGUL SYLLABLE MYOH
+BB34;BB34;1106 116E;BB34;1106 116E; # (무; 무; 무; 무; 무; ) HANGUL SYLLABLE MU
+BB35;BB35;1106 116E 11A8;BB35;1106 116E 11A8; # (묵; 묵; 묵; 묵; 묵; ) HANGUL SYLLABLE MUG
+BB36;BB36;1106 116E 11A9;BB36;1106 116E 11A9; # (묶; 묶; 묶; 묶; 묶; ) HANGUL SYLLABLE MUGG
+BB37;BB37;1106 116E 11AA;BB37;1106 116E 11AA; # (묷; 묷; 묷; 묷; 묷; ) HANGUL SYLLABLE MUGS
+BB38;BB38;1106 116E 11AB;BB38;1106 116E 11AB; # (문; 문; 문; 문; 문; ) HANGUL SYLLABLE MUN
+BB39;BB39;1106 116E 11AC;BB39;1106 116E 11AC; # (묹; 묹; 묹; 묹; 묹; ) HANGUL SYLLABLE MUNJ
+BB3A;BB3A;1106 116E 11AD;BB3A;1106 116E 11AD; # (묺; 묺; 묺; 묺; 묺; ) HANGUL SYLLABLE MUNH
+BB3B;BB3B;1106 116E 11AE;BB3B;1106 116E 11AE; # (묻; 묻; 묻; 묻; 묻; ) HANGUL SYLLABLE MUD
+BB3C;BB3C;1106 116E 11AF;BB3C;1106 116E 11AF; # (물; 물; 물; 물; 물; ) HANGUL SYLLABLE MUL
+BB3D;BB3D;1106 116E 11B0;BB3D;1106 116E 11B0; # (묽; 묽; 묽; 묽; 묽; ) HANGUL SYLLABLE MULG
+BB3E;BB3E;1106 116E 11B1;BB3E;1106 116E 11B1; # (묾; 묾; 묾; 묾; 묾; ) HANGUL SYLLABLE MULM
+BB3F;BB3F;1106 116E 11B2;BB3F;1106 116E 11B2; # (묿; 묿; 묿; 묿; 묿; ) HANGUL SYLLABLE MULB
+BB40;BB40;1106 116E 11B3;BB40;1106 116E 11B3; # (뭀; 뭀; 뭀; 뭀; 뭀; ) HANGUL SYLLABLE MULS
+BB41;BB41;1106 116E 11B4;BB41;1106 116E 11B4; # (ë­; ë­; 뭁; ë­; 뭁; ) HANGUL SYLLABLE MULT
+BB42;BB42;1106 116E 11B5;BB42;1106 116E 11B5; # (뭂; 뭂; 뭂; 뭂; 뭂; ) HANGUL SYLLABLE MULP
+BB43;BB43;1106 116E 11B6;BB43;1106 116E 11B6; # (뭃; 뭃; 뭃; 뭃; 뭃; ) HANGUL SYLLABLE MULH
+BB44;BB44;1106 116E 11B7;BB44;1106 116E 11B7; # (뭄; 뭄; 뭄; 뭄; 뭄; ) HANGUL SYLLABLE MUM
+BB45;BB45;1106 116E 11B8;BB45;1106 116E 11B8; # (뭅; 뭅; 뭅; 뭅; 뭅; ) HANGUL SYLLABLE MUB
+BB46;BB46;1106 116E 11B9;BB46;1106 116E 11B9; # (뭆; 뭆; 뭆; 뭆; 뭆; ) HANGUL SYLLABLE MUBS
+BB47;BB47;1106 116E 11BA;BB47;1106 116E 11BA; # (뭇; 뭇; 뭇; 뭇; 뭇; ) HANGUL SYLLABLE MUS
+BB48;BB48;1106 116E 11BB;BB48;1106 116E 11BB; # (뭈; 뭈; 뭈; 뭈; 뭈; ) HANGUL SYLLABLE MUSS
+BB49;BB49;1106 116E 11BC;BB49;1106 116E 11BC; # (뭉; 뭉; 뭉; 뭉; 뭉; ) HANGUL SYLLABLE MUNG
+BB4A;BB4A;1106 116E 11BD;BB4A;1106 116E 11BD; # (뭊; 뭊; 뭊; 뭊; 뭊; ) HANGUL SYLLABLE MUJ
+BB4B;BB4B;1106 116E 11BE;BB4B;1106 116E 11BE; # (뭋; 뭋; 뭋; 뭋; 뭋; ) HANGUL SYLLABLE MUC
+BB4C;BB4C;1106 116E 11BF;BB4C;1106 116E 11BF; # (뭌; 뭌; 뭌; 뭌; 뭌; ) HANGUL SYLLABLE MUK
+BB4D;BB4D;1106 116E 11C0;BB4D;1106 116E 11C0; # (ë­; ë­; 뭍; ë­; 뭍; ) HANGUL SYLLABLE MUT
+BB4E;BB4E;1106 116E 11C1;BB4E;1106 116E 11C1; # (ë­Ž; ë­Ž; 무á‡; ë­Ž; 무á‡; ) HANGUL SYLLABLE MUP
+BB4F;BB4F;1106 116E 11C2;BB4F;1106 116E 11C2; # (ë­; ë­; 뭏; ë­; 뭏; ) HANGUL SYLLABLE MUH
+BB50;BB50;1106 116F;BB50;1106 116F; # (ë­; ë­; 뭐; ë­; 뭐; ) HANGUL SYLLABLE MWEO
+BB51;BB51;1106 116F 11A8;BB51;1106 116F 11A8; # (뭑; 뭑; 뭑; 뭑; 뭑; ) HANGUL SYLLABLE MWEOG
+BB52;BB52;1106 116F 11A9;BB52;1106 116F 11A9; # (뭒; 뭒; 뭒; 뭒; 뭒; ) HANGUL SYLLABLE MWEOGG
+BB53;BB53;1106 116F 11AA;BB53;1106 116F 11AA; # (뭓; 뭓; 뭓; 뭓; 뭓; ) HANGUL SYLLABLE MWEOGS
+BB54;BB54;1106 116F 11AB;BB54;1106 116F 11AB; # (뭔; 뭔; 뭔; 뭔; 뭔; ) HANGUL SYLLABLE MWEON
+BB55;BB55;1106 116F 11AC;BB55;1106 116F 11AC; # (뭕; 뭕; 뭕; 뭕; 뭕; ) HANGUL SYLLABLE MWEONJ
+BB56;BB56;1106 116F 11AD;BB56;1106 116F 11AD; # (뭖; 뭖; 뭖; 뭖; 뭖; ) HANGUL SYLLABLE MWEONH
+BB57;BB57;1106 116F 11AE;BB57;1106 116F 11AE; # (뭗; 뭗; 뭗; 뭗; 뭗; ) HANGUL SYLLABLE MWEOD
+BB58;BB58;1106 116F 11AF;BB58;1106 116F 11AF; # (뭘; 뭘; 뭘; 뭘; 뭘; ) HANGUL SYLLABLE MWEOL
+BB59;BB59;1106 116F 11B0;BB59;1106 116F 11B0; # (뭙; 뭙; 뭙; 뭙; 뭙; ) HANGUL SYLLABLE MWEOLG
+BB5A;BB5A;1106 116F 11B1;BB5A;1106 116F 11B1; # (뭚; 뭚; 뭚; 뭚; 뭚; ) HANGUL SYLLABLE MWEOLM
+BB5B;BB5B;1106 116F 11B2;BB5B;1106 116F 11B2; # (뭛; 뭛; 뭛; 뭛; 뭛; ) HANGUL SYLLABLE MWEOLB
+BB5C;BB5C;1106 116F 11B3;BB5C;1106 116F 11B3; # (뭜; 뭜; 뭜; 뭜; 뭜; ) HANGUL SYLLABLE MWEOLS
+BB5D;BB5D;1106 116F 11B4;BB5D;1106 116F 11B4; # (ë­; ë­; 뭝; ë­; 뭝; ) HANGUL SYLLABLE MWEOLT
+BB5E;BB5E;1106 116F 11B5;BB5E;1106 116F 11B5; # (뭞; 뭞; 뭞; 뭞; 뭞; ) HANGUL SYLLABLE MWEOLP
+BB5F;BB5F;1106 116F 11B6;BB5F;1106 116F 11B6; # (뭟; 뭟; 뭟; 뭟; 뭟; ) HANGUL SYLLABLE MWEOLH
+BB60;BB60;1106 116F 11B7;BB60;1106 116F 11B7; # (뭠; 뭠; 뭠; 뭠; 뭠; ) HANGUL SYLLABLE MWEOM
+BB61;BB61;1106 116F 11B8;BB61;1106 116F 11B8; # (뭡; 뭡; 뭡; 뭡; 뭡; ) HANGUL SYLLABLE MWEOB
+BB62;BB62;1106 116F 11B9;BB62;1106 116F 11B9; # (뭢; 뭢; 뭢; 뭢; 뭢; ) HANGUL SYLLABLE MWEOBS
+BB63;BB63;1106 116F 11BA;BB63;1106 116F 11BA; # (뭣; 뭣; 뭣; 뭣; 뭣; ) HANGUL SYLLABLE MWEOS
+BB64;BB64;1106 116F 11BB;BB64;1106 116F 11BB; # (뭤; 뭤; 뭤; 뭤; 뭤; ) HANGUL SYLLABLE MWEOSS
+BB65;BB65;1106 116F 11BC;BB65;1106 116F 11BC; # (뭥; 뭥; 뭥; 뭥; 뭥; ) HANGUL SYLLABLE MWEONG
+BB66;BB66;1106 116F 11BD;BB66;1106 116F 11BD; # (뭦; 뭦; 뭦; 뭦; 뭦; ) HANGUL SYLLABLE MWEOJ
+BB67;BB67;1106 116F 11BE;BB67;1106 116F 11BE; # (뭧; 뭧; 뭧; 뭧; 뭧; ) HANGUL SYLLABLE MWEOC
+BB68;BB68;1106 116F 11BF;BB68;1106 116F 11BF; # (뭨; 뭨; 뭨; 뭨; 뭨; ) HANGUL SYLLABLE MWEOK
+BB69;BB69;1106 116F 11C0;BB69;1106 116F 11C0; # (뭩; 뭩; 뭩; 뭩; 뭩; ) HANGUL SYLLABLE MWEOT
+BB6A;BB6A;1106 116F 11C1;BB6A;1106 116F 11C1; # (ë­ª; ë­ª; 뭐á‡; ë­ª; 뭐á‡; ) HANGUL SYLLABLE MWEOP
+BB6B;BB6B;1106 116F 11C2;BB6B;1106 116F 11C2; # (뭫; 뭫; 뭫; 뭫; 뭫; ) HANGUL SYLLABLE MWEOH
+BB6C;BB6C;1106 1170;BB6C;1106 1170; # (뭬; 뭬; 뭬; 뭬; 뭬; ) HANGUL SYLLABLE MWE
+BB6D;BB6D;1106 1170 11A8;BB6D;1106 1170 11A8; # (뭭; 뭭; 뭭; 뭭; 뭭; ) HANGUL SYLLABLE MWEG
+BB6E;BB6E;1106 1170 11A9;BB6E;1106 1170 11A9; # (뭮; 뭮; 뭮; 뭮; 뭮; ) HANGUL SYLLABLE MWEGG
+BB6F;BB6F;1106 1170 11AA;BB6F;1106 1170 11AA; # (뭯; 뭯; 뭯; 뭯; 뭯; ) HANGUL SYLLABLE MWEGS
+BB70;BB70;1106 1170 11AB;BB70;1106 1170 11AB; # (뭰; 뭰; 뭰; 뭰; 뭰; ) HANGUL SYLLABLE MWEN
+BB71;BB71;1106 1170 11AC;BB71;1106 1170 11AC; # (뭱; 뭱; 뭱; 뭱; 뭱; ) HANGUL SYLLABLE MWENJ
+BB72;BB72;1106 1170 11AD;BB72;1106 1170 11AD; # (뭲; 뭲; 뭲; 뭲; 뭲; ) HANGUL SYLLABLE MWENH
+BB73;BB73;1106 1170 11AE;BB73;1106 1170 11AE; # (뭳; 뭳; 뭳; 뭳; 뭳; ) HANGUL SYLLABLE MWED
+BB74;BB74;1106 1170 11AF;BB74;1106 1170 11AF; # (뭴; 뭴; 뭴; 뭴; 뭴; ) HANGUL SYLLABLE MWEL
+BB75;BB75;1106 1170 11B0;BB75;1106 1170 11B0; # (뭵; 뭵; 뭵; 뭵; 뭵; ) HANGUL SYLLABLE MWELG
+BB76;BB76;1106 1170 11B1;BB76;1106 1170 11B1; # (뭶; 뭶; 뭶; 뭶; 뭶; ) HANGUL SYLLABLE MWELM
+BB77;BB77;1106 1170 11B2;BB77;1106 1170 11B2; # (뭷; 뭷; 뭷; 뭷; 뭷; ) HANGUL SYLLABLE MWELB
+BB78;BB78;1106 1170 11B3;BB78;1106 1170 11B3; # (뭸; 뭸; 뭸; 뭸; 뭸; ) HANGUL SYLLABLE MWELS
+BB79;BB79;1106 1170 11B4;BB79;1106 1170 11B4; # (뭹; 뭹; 뭹; 뭹; 뭹; ) HANGUL SYLLABLE MWELT
+BB7A;BB7A;1106 1170 11B5;BB7A;1106 1170 11B5; # (뭺; 뭺; 뭺; 뭺; 뭺; ) HANGUL SYLLABLE MWELP
+BB7B;BB7B;1106 1170 11B6;BB7B;1106 1170 11B6; # (뭻; 뭻; 뭻; 뭻; 뭻; ) HANGUL SYLLABLE MWELH
+BB7C;BB7C;1106 1170 11B7;BB7C;1106 1170 11B7; # (뭼; 뭼; 뭼; 뭼; 뭼; ) HANGUL SYLLABLE MWEM
+BB7D;BB7D;1106 1170 11B8;BB7D;1106 1170 11B8; # (뭽; 뭽; 뭽; 뭽; 뭽; ) HANGUL SYLLABLE MWEB
+BB7E;BB7E;1106 1170 11B9;BB7E;1106 1170 11B9; # (뭾; 뭾; 뭾; 뭾; 뭾; ) HANGUL SYLLABLE MWEBS
+BB7F;BB7F;1106 1170 11BA;BB7F;1106 1170 11BA; # (뭿; 뭿; 뭿; 뭿; 뭿; ) HANGUL SYLLABLE MWES
+BB80;BB80;1106 1170 11BB;BB80;1106 1170 11BB; # (뮀; 뮀; 뮀; 뮀; 뮀; ) HANGUL SYLLABLE MWESS
+BB81;BB81;1106 1170 11BC;BB81;1106 1170 11BC; # (ë®; ë®; 뮁; ë®; 뮁; ) HANGUL SYLLABLE MWENG
+BB82;BB82;1106 1170 11BD;BB82;1106 1170 11BD; # (뮂; 뮂; 뮂; 뮂; 뮂; ) HANGUL SYLLABLE MWEJ
+BB83;BB83;1106 1170 11BE;BB83;1106 1170 11BE; # (뮃; 뮃; 뮃; 뮃; 뮃; ) HANGUL SYLLABLE MWEC
+BB84;BB84;1106 1170 11BF;BB84;1106 1170 11BF; # (뮄; 뮄; 뮄; 뮄; 뮄; ) HANGUL SYLLABLE MWEK
+BB85;BB85;1106 1170 11C0;BB85;1106 1170 11C0; # (뮅; 뮅; 뮅; 뮅; 뮅; ) HANGUL SYLLABLE MWET
+BB86;BB86;1106 1170 11C1;BB86;1106 1170 11C1; # (뮆; 뮆; 뭬á‡; 뮆; 뭬á‡; ) HANGUL SYLLABLE MWEP
+BB87;BB87;1106 1170 11C2;BB87;1106 1170 11C2; # (뮇; 뮇; 뮇; 뮇; 뮇; ) HANGUL SYLLABLE MWEH
+BB88;BB88;1106 1171;BB88;1106 1171; # (뮈; 뮈; 뮈; 뮈; 뮈; ) HANGUL SYLLABLE MWI
+BB89;BB89;1106 1171 11A8;BB89;1106 1171 11A8; # (뮉; 뮉; 뮉; 뮉; 뮉; ) HANGUL SYLLABLE MWIG
+BB8A;BB8A;1106 1171 11A9;BB8A;1106 1171 11A9; # (뮊; 뮊; 뮊; 뮊; 뮊; ) HANGUL SYLLABLE MWIGG
+BB8B;BB8B;1106 1171 11AA;BB8B;1106 1171 11AA; # (뮋; 뮋; 뮋; 뮋; 뮋; ) HANGUL SYLLABLE MWIGS
+BB8C;BB8C;1106 1171 11AB;BB8C;1106 1171 11AB; # (뮌; 뮌; 뮌; 뮌; 뮌; ) HANGUL SYLLABLE MWIN
+BB8D;BB8D;1106 1171 11AC;BB8D;1106 1171 11AC; # (ë®; ë®; 뮍; ë®; 뮍; ) HANGUL SYLLABLE MWINJ
+BB8E;BB8E;1106 1171 11AD;BB8E;1106 1171 11AD; # (뮎; 뮎; 뮎; 뮎; 뮎; ) HANGUL SYLLABLE MWINH
+BB8F;BB8F;1106 1171 11AE;BB8F;1106 1171 11AE; # (ë®; ë®; 뮏; ë®; 뮏; ) HANGUL SYLLABLE MWID
+BB90;BB90;1106 1171 11AF;BB90;1106 1171 11AF; # (ë®; ë®; 뮐; ë®; 뮐; ) HANGUL SYLLABLE MWIL
+BB91;BB91;1106 1171 11B0;BB91;1106 1171 11B0; # (뮑; 뮑; 뮑; 뮑; 뮑; ) HANGUL SYLLABLE MWILG
+BB92;BB92;1106 1171 11B1;BB92;1106 1171 11B1; # (뮒; 뮒; 뮒; 뮒; 뮒; ) HANGUL SYLLABLE MWILM
+BB93;BB93;1106 1171 11B2;BB93;1106 1171 11B2; # (뮓; 뮓; 뮓; 뮓; 뮓; ) HANGUL SYLLABLE MWILB
+BB94;BB94;1106 1171 11B3;BB94;1106 1171 11B3; # (뮔; 뮔; 뮔; 뮔; 뮔; ) HANGUL SYLLABLE MWILS
+BB95;BB95;1106 1171 11B4;BB95;1106 1171 11B4; # (뮕; 뮕; 뮕; 뮕; 뮕; ) HANGUL SYLLABLE MWILT
+BB96;BB96;1106 1171 11B5;BB96;1106 1171 11B5; # (뮖; 뮖; 뮖; 뮖; 뮖; ) HANGUL SYLLABLE MWILP
+BB97;BB97;1106 1171 11B6;BB97;1106 1171 11B6; # (뮗; 뮗; 뮗; 뮗; 뮗; ) HANGUL SYLLABLE MWILH
+BB98;BB98;1106 1171 11B7;BB98;1106 1171 11B7; # (뮘; 뮘; 뮘; 뮘; 뮘; ) HANGUL SYLLABLE MWIM
+BB99;BB99;1106 1171 11B8;BB99;1106 1171 11B8; # (뮙; 뮙; 뮙; 뮙; 뮙; ) HANGUL SYLLABLE MWIB
+BB9A;BB9A;1106 1171 11B9;BB9A;1106 1171 11B9; # (뮚; 뮚; 뮚; 뮚; 뮚; ) HANGUL SYLLABLE MWIBS
+BB9B;BB9B;1106 1171 11BA;BB9B;1106 1171 11BA; # (뮛; 뮛; 뮛; 뮛; 뮛; ) HANGUL SYLLABLE MWIS
+BB9C;BB9C;1106 1171 11BB;BB9C;1106 1171 11BB; # (뮜; 뮜; 뮜; 뮜; 뮜; ) HANGUL SYLLABLE MWISS
+BB9D;BB9D;1106 1171 11BC;BB9D;1106 1171 11BC; # (ë®; ë®; 뮝; ë®; 뮝; ) HANGUL SYLLABLE MWING
+BB9E;BB9E;1106 1171 11BD;BB9E;1106 1171 11BD; # (뮞; 뮞; 뮞; 뮞; 뮞; ) HANGUL SYLLABLE MWIJ
+BB9F;BB9F;1106 1171 11BE;BB9F;1106 1171 11BE; # (뮟; 뮟; 뮟; 뮟; 뮟; ) HANGUL SYLLABLE MWIC
+BBA0;BBA0;1106 1171 11BF;BBA0;1106 1171 11BF; # (뮠; 뮠; 뮠; 뮠; 뮠; ) HANGUL SYLLABLE MWIK
+BBA1;BBA1;1106 1171 11C0;BBA1;1106 1171 11C0; # (뮡; 뮡; 뮡; 뮡; 뮡; ) HANGUL SYLLABLE MWIT
+BBA2;BBA2;1106 1171 11C1;BBA2;1106 1171 11C1; # (뮢; 뮢; 뮈á‡; 뮢; 뮈á‡; ) HANGUL SYLLABLE MWIP
+BBA3;BBA3;1106 1171 11C2;BBA3;1106 1171 11C2; # (뮣; 뮣; 뮣; 뮣; 뮣; ) HANGUL SYLLABLE MWIH
+BBA4;BBA4;1106 1172;BBA4;1106 1172; # (뮤; 뮤; 뮤; 뮤; 뮤; ) HANGUL SYLLABLE MYU
+BBA5;BBA5;1106 1172 11A8;BBA5;1106 1172 11A8; # (뮥; 뮥; 뮥; 뮥; 뮥; ) HANGUL SYLLABLE MYUG
+BBA6;BBA6;1106 1172 11A9;BBA6;1106 1172 11A9; # (뮦; 뮦; 뮦; 뮦; 뮦; ) HANGUL SYLLABLE MYUGG
+BBA7;BBA7;1106 1172 11AA;BBA7;1106 1172 11AA; # (뮧; 뮧; 뮧; 뮧; 뮧; ) HANGUL SYLLABLE MYUGS
+BBA8;BBA8;1106 1172 11AB;BBA8;1106 1172 11AB; # (뮨; 뮨; 뮨; 뮨; 뮨; ) HANGUL SYLLABLE MYUN
+BBA9;BBA9;1106 1172 11AC;BBA9;1106 1172 11AC; # (뮩; 뮩; 뮩; 뮩; 뮩; ) HANGUL SYLLABLE MYUNJ
+BBAA;BBAA;1106 1172 11AD;BBAA;1106 1172 11AD; # (뮪; 뮪; 뮪; 뮪; 뮪; ) HANGUL SYLLABLE MYUNH
+BBAB;BBAB;1106 1172 11AE;BBAB;1106 1172 11AE; # (뮫; 뮫; 뮫; 뮫; 뮫; ) HANGUL SYLLABLE MYUD
+BBAC;BBAC;1106 1172 11AF;BBAC;1106 1172 11AF; # (뮬; 뮬; 뮬; 뮬; 뮬; ) HANGUL SYLLABLE MYUL
+BBAD;BBAD;1106 1172 11B0;BBAD;1106 1172 11B0; # (뮭; 뮭; 뮭; 뮭; 뮭; ) HANGUL SYLLABLE MYULG
+BBAE;BBAE;1106 1172 11B1;BBAE;1106 1172 11B1; # (뮮; 뮮; 뮮; 뮮; 뮮; ) HANGUL SYLLABLE MYULM
+BBAF;BBAF;1106 1172 11B2;BBAF;1106 1172 11B2; # (뮯; 뮯; 뮯; 뮯; 뮯; ) HANGUL SYLLABLE MYULB
+BBB0;BBB0;1106 1172 11B3;BBB0;1106 1172 11B3; # (뮰; 뮰; 뮰; 뮰; 뮰; ) HANGUL SYLLABLE MYULS
+BBB1;BBB1;1106 1172 11B4;BBB1;1106 1172 11B4; # (뮱; 뮱; 뮱; 뮱; 뮱; ) HANGUL SYLLABLE MYULT
+BBB2;BBB2;1106 1172 11B5;BBB2;1106 1172 11B5; # (뮲; 뮲; 뮲; 뮲; 뮲; ) HANGUL SYLLABLE MYULP
+BBB3;BBB3;1106 1172 11B6;BBB3;1106 1172 11B6; # (뮳; 뮳; 뮳; 뮳; 뮳; ) HANGUL SYLLABLE MYULH
+BBB4;BBB4;1106 1172 11B7;BBB4;1106 1172 11B7; # (뮴; 뮴; 뮴; 뮴; 뮴; ) HANGUL SYLLABLE MYUM
+BBB5;BBB5;1106 1172 11B8;BBB5;1106 1172 11B8; # (뮵; 뮵; 뮵; 뮵; 뮵; ) HANGUL SYLLABLE MYUB
+BBB6;BBB6;1106 1172 11B9;BBB6;1106 1172 11B9; # (뮶; 뮶; 뮶; 뮶; 뮶; ) HANGUL SYLLABLE MYUBS
+BBB7;BBB7;1106 1172 11BA;BBB7;1106 1172 11BA; # (뮷; 뮷; 뮷; 뮷; 뮷; ) HANGUL SYLLABLE MYUS
+BBB8;BBB8;1106 1172 11BB;BBB8;1106 1172 11BB; # (뮸; 뮸; 뮸; 뮸; 뮸; ) HANGUL SYLLABLE MYUSS
+BBB9;BBB9;1106 1172 11BC;BBB9;1106 1172 11BC; # (뮹; 뮹; 뮹; 뮹; 뮹; ) HANGUL SYLLABLE MYUNG
+BBBA;BBBA;1106 1172 11BD;BBBA;1106 1172 11BD; # (뮺; 뮺; 뮺; 뮺; 뮺; ) HANGUL SYLLABLE MYUJ
+BBBB;BBBB;1106 1172 11BE;BBBB;1106 1172 11BE; # (뮻; 뮻; 뮻; 뮻; 뮻; ) HANGUL SYLLABLE MYUC
+BBBC;BBBC;1106 1172 11BF;BBBC;1106 1172 11BF; # (뮼; 뮼; 뮼; 뮼; 뮼; ) HANGUL SYLLABLE MYUK
+BBBD;BBBD;1106 1172 11C0;BBBD;1106 1172 11C0; # (뮽; 뮽; 뮽; 뮽; 뮽; ) HANGUL SYLLABLE MYUT
+BBBE;BBBE;1106 1172 11C1;BBBE;1106 1172 11C1; # (뮾; 뮾; 뮤á‡; 뮾; 뮤á‡; ) HANGUL SYLLABLE MYUP
+BBBF;BBBF;1106 1172 11C2;BBBF;1106 1172 11C2; # (뮿; 뮿; 뮿; 뮿; 뮿; ) HANGUL SYLLABLE MYUH
+BBC0;BBC0;1106 1173;BBC0;1106 1173; # (므; 므; 므; 므; 므; ) HANGUL SYLLABLE MEU
+BBC1;BBC1;1106 1173 11A8;BBC1;1106 1173 11A8; # (ë¯; ë¯; 믁; ë¯; 믁; ) HANGUL SYLLABLE MEUG
+BBC2;BBC2;1106 1173 11A9;BBC2;1106 1173 11A9; # (믂; 믂; 믂; 믂; 믂; ) HANGUL SYLLABLE MEUGG
+BBC3;BBC3;1106 1173 11AA;BBC3;1106 1173 11AA; # (믃; 믃; 믃; 믃; 믃; ) HANGUL SYLLABLE MEUGS
+BBC4;BBC4;1106 1173 11AB;BBC4;1106 1173 11AB; # (믄; 믄; 믄; 믄; 믄; ) HANGUL SYLLABLE MEUN
+BBC5;BBC5;1106 1173 11AC;BBC5;1106 1173 11AC; # (믅; 믅; 믅; 믅; 믅; ) HANGUL SYLLABLE MEUNJ
+BBC6;BBC6;1106 1173 11AD;BBC6;1106 1173 11AD; # (믆; 믆; 믆; 믆; 믆; ) HANGUL SYLLABLE MEUNH
+BBC7;BBC7;1106 1173 11AE;BBC7;1106 1173 11AE; # (믇; 믇; 믇; 믇; 믇; ) HANGUL SYLLABLE MEUD
+BBC8;BBC8;1106 1173 11AF;BBC8;1106 1173 11AF; # (믈; 믈; 믈; 믈; 믈; ) HANGUL SYLLABLE MEUL
+BBC9;BBC9;1106 1173 11B0;BBC9;1106 1173 11B0; # (믉; 믉; 믉; 믉; 믉; ) HANGUL SYLLABLE MEULG
+BBCA;BBCA;1106 1173 11B1;BBCA;1106 1173 11B1; # (믊; 믊; 믊; 믊; 믊; ) HANGUL SYLLABLE MEULM
+BBCB;BBCB;1106 1173 11B2;BBCB;1106 1173 11B2; # (믋; 믋; 믋; 믋; 믋; ) HANGUL SYLLABLE MEULB
+BBCC;BBCC;1106 1173 11B3;BBCC;1106 1173 11B3; # (믌; 믌; 믌; 믌; 믌; ) HANGUL SYLLABLE MEULS
+BBCD;BBCD;1106 1173 11B4;BBCD;1106 1173 11B4; # (ë¯; ë¯; 믍; ë¯; 믍; ) HANGUL SYLLABLE MEULT
+BBCE;BBCE;1106 1173 11B5;BBCE;1106 1173 11B5; # (믎; 믎; 믎; 믎; 믎; ) HANGUL SYLLABLE MEULP
+BBCF;BBCF;1106 1173 11B6;BBCF;1106 1173 11B6; # (ë¯; ë¯; 믏; ë¯; 믏; ) HANGUL SYLLABLE MEULH
+BBD0;BBD0;1106 1173 11B7;BBD0;1106 1173 11B7; # (ë¯; ë¯; 믐; ë¯; 믐; ) HANGUL SYLLABLE MEUM
+BBD1;BBD1;1106 1173 11B8;BBD1;1106 1173 11B8; # (믑; 믑; 믑; 믑; 믑; ) HANGUL SYLLABLE MEUB
+BBD2;BBD2;1106 1173 11B9;BBD2;1106 1173 11B9; # (믒; 믒; 믒; 믒; 믒; ) HANGUL SYLLABLE MEUBS
+BBD3;BBD3;1106 1173 11BA;BBD3;1106 1173 11BA; # (믓; 믓; 믓; 믓; 믓; ) HANGUL SYLLABLE MEUS
+BBD4;BBD4;1106 1173 11BB;BBD4;1106 1173 11BB; # (믔; 믔; 믔; 믔; 믔; ) HANGUL SYLLABLE MEUSS
+BBD5;BBD5;1106 1173 11BC;BBD5;1106 1173 11BC; # (믕; 믕; 믕; 믕; 믕; ) HANGUL SYLLABLE MEUNG
+BBD6;BBD6;1106 1173 11BD;BBD6;1106 1173 11BD; # (믖; 믖; 믖; 믖; 믖; ) HANGUL SYLLABLE MEUJ
+BBD7;BBD7;1106 1173 11BE;BBD7;1106 1173 11BE; # (믗; 믗; 믗; 믗; 믗; ) HANGUL SYLLABLE MEUC
+BBD8;BBD8;1106 1173 11BF;BBD8;1106 1173 11BF; # (믘; 믘; 믘; 믘; 믘; ) HANGUL SYLLABLE MEUK
+BBD9;BBD9;1106 1173 11C0;BBD9;1106 1173 11C0; # (믙; 믙; 믙; 믙; 믙; ) HANGUL SYLLABLE MEUT
+BBDA;BBDA;1106 1173 11C1;BBDA;1106 1173 11C1; # (믚; 믚; 므á‡; 믚; 므á‡; ) HANGUL SYLLABLE MEUP
+BBDB;BBDB;1106 1173 11C2;BBDB;1106 1173 11C2; # (믛; 믛; 믛; 믛; 믛; ) HANGUL SYLLABLE MEUH
+BBDC;BBDC;1106 1174;BBDC;1106 1174; # (믜; 믜; 믜; 믜; 믜; ) HANGUL SYLLABLE MYI
+BBDD;BBDD;1106 1174 11A8;BBDD;1106 1174 11A8; # (ë¯; ë¯; 믝; ë¯; 믝; ) HANGUL SYLLABLE MYIG
+BBDE;BBDE;1106 1174 11A9;BBDE;1106 1174 11A9; # (믞; 믞; 믞; 믞; 믞; ) HANGUL SYLLABLE MYIGG
+BBDF;BBDF;1106 1174 11AA;BBDF;1106 1174 11AA; # (믟; 믟; 믟; 믟; 믟; ) HANGUL SYLLABLE MYIGS
+BBE0;BBE0;1106 1174 11AB;BBE0;1106 1174 11AB; # (믠; 믠; 믠; 믠; 믠; ) HANGUL SYLLABLE MYIN
+BBE1;BBE1;1106 1174 11AC;BBE1;1106 1174 11AC; # (믡; 믡; 믡; 믡; 믡; ) HANGUL SYLLABLE MYINJ
+BBE2;BBE2;1106 1174 11AD;BBE2;1106 1174 11AD; # (믢; 믢; 믢; 믢; 믢; ) HANGUL SYLLABLE MYINH
+BBE3;BBE3;1106 1174 11AE;BBE3;1106 1174 11AE; # (믣; 믣; 믣; 믣; 믣; ) HANGUL SYLLABLE MYID
+BBE4;BBE4;1106 1174 11AF;BBE4;1106 1174 11AF; # (믤; 믤; 믤; 믤; 믤; ) HANGUL SYLLABLE MYIL
+BBE5;BBE5;1106 1174 11B0;BBE5;1106 1174 11B0; # (믥; 믥; 믥; 믥; 믥; ) HANGUL SYLLABLE MYILG
+BBE6;BBE6;1106 1174 11B1;BBE6;1106 1174 11B1; # (믦; 믦; 믦; 믦; 믦; ) HANGUL SYLLABLE MYILM
+BBE7;BBE7;1106 1174 11B2;BBE7;1106 1174 11B2; # (믧; 믧; 믧; 믧; 믧; ) HANGUL SYLLABLE MYILB
+BBE8;BBE8;1106 1174 11B3;BBE8;1106 1174 11B3; # (믨; 믨; 믨; 믨; 믨; ) HANGUL SYLLABLE MYILS
+BBE9;BBE9;1106 1174 11B4;BBE9;1106 1174 11B4; # (믩; 믩; 믩; 믩; 믩; ) HANGUL SYLLABLE MYILT
+BBEA;BBEA;1106 1174 11B5;BBEA;1106 1174 11B5; # (믪; 믪; 믪; 믪; 믪; ) HANGUL SYLLABLE MYILP
+BBEB;BBEB;1106 1174 11B6;BBEB;1106 1174 11B6; # (믫; 믫; 믫; 믫; 믫; ) HANGUL SYLLABLE MYILH
+BBEC;BBEC;1106 1174 11B7;BBEC;1106 1174 11B7; # (믬; 믬; 믬; 믬; 믬; ) HANGUL SYLLABLE MYIM
+BBED;BBED;1106 1174 11B8;BBED;1106 1174 11B8; # (믭; 믭; 믭; 믭; 믭; ) HANGUL SYLLABLE MYIB
+BBEE;BBEE;1106 1174 11B9;BBEE;1106 1174 11B9; # (믮; 믮; 믮; 믮; 믮; ) HANGUL SYLLABLE MYIBS
+BBEF;BBEF;1106 1174 11BA;BBEF;1106 1174 11BA; # (믯; 믯; 믯; 믯; 믯; ) HANGUL SYLLABLE MYIS
+BBF0;BBF0;1106 1174 11BB;BBF0;1106 1174 11BB; # (믰; 믰; 믰; 믰; 믰; ) HANGUL SYLLABLE MYISS
+BBF1;BBF1;1106 1174 11BC;BBF1;1106 1174 11BC; # (믱; 믱; 믱; 믱; 믱; ) HANGUL SYLLABLE MYING
+BBF2;BBF2;1106 1174 11BD;BBF2;1106 1174 11BD; # (믲; 믲; 믲; 믲; 믲; ) HANGUL SYLLABLE MYIJ
+BBF3;BBF3;1106 1174 11BE;BBF3;1106 1174 11BE; # (믳; 믳; 믳; 믳; 믳; ) HANGUL SYLLABLE MYIC
+BBF4;BBF4;1106 1174 11BF;BBF4;1106 1174 11BF; # (믴; 믴; 믴; 믴; 믴; ) HANGUL SYLLABLE MYIK
+BBF5;BBF5;1106 1174 11C0;BBF5;1106 1174 11C0; # (믵; 믵; 믵; 믵; 믵; ) HANGUL SYLLABLE MYIT
+BBF6;BBF6;1106 1174 11C1;BBF6;1106 1174 11C1; # (믶; 믶; 믜á‡; 믶; 믜á‡; ) HANGUL SYLLABLE MYIP
+BBF7;BBF7;1106 1174 11C2;BBF7;1106 1174 11C2; # (믷; 믷; 믷; 믷; 믷; ) HANGUL SYLLABLE MYIH
+BBF8;BBF8;1106 1175;BBF8;1106 1175; # (미; 미; 미; 미; 미; ) HANGUL SYLLABLE MI
+BBF9;BBF9;1106 1175 11A8;BBF9;1106 1175 11A8; # (믹; 믹; 믹; 믹; 믹; ) HANGUL SYLLABLE MIG
+BBFA;BBFA;1106 1175 11A9;BBFA;1106 1175 11A9; # (믺; 믺; 믺; 믺; 믺; ) HANGUL SYLLABLE MIGG
+BBFB;BBFB;1106 1175 11AA;BBFB;1106 1175 11AA; # (믻; 믻; 믻; 믻; 믻; ) HANGUL SYLLABLE MIGS
+BBFC;BBFC;1106 1175 11AB;BBFC;1106 1175 11AB; # (민; 민; 민; 민; 민; ) HANGUL SYLLABLE MIN
+BBFD;BBFD;1106 1175 11AC;BBFD;1106 1175 11AC; # (믽; 믽; 믽; 믽; 믽; ) HANGUL SYLLABLE MINJ
+BBFE;BBFE;1106 1175 11AD;BBFE;1106 1175 11AD; # (믾; 믾; 믾; 믾; 믾; ) HANGUL SYLLABLE MINH
+BBFF;BBFF;1106 1175 11AE;BBFF;1106 1175 11AE; # (믿; 믿; 믿; 믿; 믿; ) HANGUL SYLLABLE MID
+BC00;BC00;1106 1175 11AF;BC00;1106 1175 11AF; # (밀; 밀; 밀; 밀; 밀; ) HANGUL SYLLABLE MIL
+BC01;BC01;1106 1175 11B0;BC01;1106 1175 11B0; # (ë°; ë°; 밁; ë°; 밁; ) HANGUL SYLLABLE MILG
+BC02;BC02;1106 1175 11B1;BC02;1106 1175 11B1; # (밂; 밂; 밂; 밂; 밂; ) HANGUL SYLLABLE MILM
+BC03;BC03;1106 1175 11B2;BC03;1106 1175 11B2; # (밃; 밃; 밃; 밃; 밃; ) HANGUL SYLLABLE MILB
+BC04;BC04;1106 1175 11B3;BC04;1106 1175 11B3; # (밄; 밄; 밄; 밄; 밄; ) HANGUL SYLLABLE MILS
+BC05;BC05;1106 1175 11B4;BC05;1106 1175 11B4; # (밅; 밅; 밅; 밅; 밅; ) HANGUL SYLLABLE MILT
+BC06;BC06;1106 1175 11B5;BC06;1106 1175 11B5; # (밆; 밆; 밆; 밆; 밆; ) HANGUL SYLLABLE MILP
+BC07;BC07;1106 1175 11B6;BC07;1106 1175 11B6; # (밇; 밇; 밇; 밇; 밇; ) HANGUL SYLLABLE MILH
+BC08;BC08;1106 1175 11B7;BC08;1106 1175 11B7; # (밈; 밈; 밈; 밈; 밈; ) HANGUL SYLLABLE MIM
+BC09;BC09;1106 1175 11B8;BC09;1106 1175 11B8; # (밉; 밉; 밉; 밉; 밉; ) HANGUL SYLLABLE MIB
+BC0A;BC0A;1106 1175 11B9;BC0A;1106 1175 11B9; # (밊; 밊; 밊; 밊; 밊; ) HANGUL SYLLABLE MIBS
+BC0B;BC0B;1106 1175 11BA;BC0B;1106 1175 11BA; # (밋; 밋; 밋; 밋; 밋; ) HANGUL SYLLABLE MIS
+BC0C;BC0C;1106 1175 11BB;BC0C;1106 1175 11BB; # (밌; 밌; 밌; 밌; 밌; ) HANGUL SYLLABLE MISS
+BC0D;BC0D;1106 1175 11BC;BC0D;1106 1175 11BC; # (ë°; ë°; 밍; ë°; 밍; ) HANGUL SYLLABLE MING
+BC0E;BC0E;1106 1175 11BD;BC0E;1106 1175 11BD; # (밎; 밎; 밎; 밎; 밎; ) HANGUL SYLLABLE MIJ
+BC0F;BC0F;1106 1175 11BE;BC0F;1106 1175 11BE; # (ë°; ë°; 및; ë°; 및; ) HANGUL SYLLABLE MIC
+BC10;BC10;1106 1175 11BF;BC10;1106 1175 11BF; # (ë°; ë°; 밐; ë°; 밐; ) HANGUL SYLLABLE MIK
+BC11;BC11;1106 1175 11C0;BC11;1106 1175 11C0; # (밑; 밑; 밑; 밑; 밑; ) HANGUL SYLLABLE MIT
+BC12;BC12;1106 1175 11C1;BC12;1106 1175 11C1; # (ë°’; ë°’; 미á‡; ë°’; 미á‡; ) HANGUL SYLLABLE MIP
+BC13;BC13;1106 1175 11C2;BC13;1106 1175 11C2; # (밓; 밓; 밓; 밓; 밓; ) HANGUL SYLLABLE MIH
+BC14;BC14;1107 1161;BC14;1107 1161; # (바; 바; 바; 바; 바; ) HANGUL SYLLABLE BA
+BC15;BC15;1107 1161 11A8;BC15;1107 1161 11A8; # (박; 박; 박; 박; 박; ) HANGUL SYLLABLE BAG
+BC16;BC16;1107 1161 11A9;BC16;1107 1161 11A9; # (밖; 밖; 밖; 밖; 밖; ) HANGUL SYLLABLE BAGG
+BC17;BC17;1107 1161 11AA;BC17;1107 1161 11AA; # (밗; 밗; 밗; 밗; 밗; ) HANGUL SYLLABLE BAGS
+BC18;BC18;1107 1161 11AB;BC18;1107 1161 11AB; # (반; 반; 반; 반; 반; ) HANGUL SYLLABLE BAN
+BC19;BC19;1107 1161 11AC;BC19;1107 1161 11AC; # (밙; 밙; 밙; 밙; 밙; ) HANGUL SYLLABLE BANJ
+BC1A;BC1A;1107 1161 11AD;BC1A;1107 1161 11AD; # (밚; 밚; 밚; 밚; 밚; ) HANGUL SYLLABLE BANH
+BC1B;BC1B;1107 1161 11AE;BC1B;1107 1161 11AE; # (받; 받; 받; 받; 받; ) HANGUL SYLLABLE BAD
+BC1C;BC1C;1107 1161 11AF;BC1C;1107 1161 11AF; # (발; 발; 발; 발; 발; ) HANGUL SYLLABLE BAL
+BC1D;BC1D;1107 1161 11B0;BC1D;1107 1161 11B0; # (ë°; ë°; 밝; ë°; 밝; ) HANGUL SYLLABLE BALG
+BC1E;BC1E;1107 1161 11B1;BC1E;1107 1161 11B1; # (밞; 밞; 밞; 밞; 밞; ) HANGUL SYLLABLE BALM
+BC1F;BC1F;1107 1161 11B2;BC1F;1107 1161 11B2; # (밟; 밟; 밟; 밟; 밟; ) HANGUL SYLLABLE BALB
+BC20;BC20;1107 1161 11B3;BC20;1107 1161 11B3; # (밠; 밠; 밠; 밠; 밠; ) HANGUL SYLLABLE BALS
+BC21;BC21;1107 1161 11B4;BC21;1107 1161 11B4; # (밡; 밡; 밡; 밡; 밡; ) HANGUL SYLLABLE BALT
+BC22;BC22;1107 1161 11B5;BC22;1107 1161 11B5; # (밢; 밢; 밢; 밢; 밢; ) HANGUL SYLLABLE BALP
+BC23;BC23;1107 1161 11B6;BC23;1107 1161 11B6; # (밣; 밣; 밣; 밣; 밣; ) HANGUL SYLLABLE BALH
+BC24;BC24;1107 1161 11B7;BC24;1107 1161 11B7; # (밤; 밤; 밤; 밤; 밤; ) HANGUL SYLLABLE BAM
+BC25;BC25;1107 1161 11B8;BC25;1107 1161 11B8; # (밥; 밥; 밥; 밥; 밥; ) HANGUL SYLLABLE BAB
+BC26;BC26;1107 1161 11B9;BC26;1107 1161 11B9; # (밦; 밦; 밦; 밦; 밦; ) HANGUL SYLLABLE BABS
+BC27;BC27;1107 1161 11BA;BC27;1107 1161 11BA; # (밧; 밧; 밧; 밧; 밧; ) HANGUL SYLLABLE BAS
+BC28;BC28;1107 1161 11BB;BC28;1107 1161 11BB; # (밨; 밨; 밨; 밨; 밨; ) HANGUL SYLLABLE BASS
+BC29;BC29;1107 1161 11BC;BC29;1107 1161 11BC; # (방; 방; 방; 방; 방; ) HANGUL SYLLABLE BANG
+BC2A;BC2A;1107 1161 11BD;BC2A;1107 1161 11BD; # (밪; 밪; 밪; 밪; 밪; ) HANGUL SYLLABLE BAJ
+BC2B;BC2B;1107 1161 11BE;BC2B;1107 1161 11BE; # (밫; 밫; 밫; 밫; 밫; ) HANGUL SYLLABLE BAC
+BC2C;BC2C;1107 1161 11BF;BC2C;1107 1161 11BF; # (밬; 밬; 밬; 밬; 밬; ) HANGUL SYLLABLE BAK
+BC2D;BC2D;1107 1161 11C0;BC2D;1107 1161 11C0; # (밭; 밭; 밭; 밭; 밭; ) HANGUL SYLLABLE BAT
+BC2E;BC2E;1107 1161 11C1;BC2E;1107 1161 11C1; # (ë°®; ë°®; 바á‡; ë°®; 바á‡; ) HANGUL SYLLABLE BAP
+BC2F;BC2F;1107 1161 11C2;BC2F;1107 1161 11C2; # (밯; 밯; 밯; 밯; 밯; ) HANGUL SYLLABLE BAH
+BC30;BC30;1107 1162;BC30;1107 1162; # (배; 배; 배; 배; 배; ) HANGUL SYLLABLE BAE
+BC31;BC31;1107 1162 11A8;BC31;1107 1162 11A8; # (백; 백; 백; 백; 백; ) HANGUL SYLLABLE BAEG
+BC32;BC32;1107 1162 11A9;BC32;1107 1162 11A9; # (밲; 밲; 밲; 밲; 밲; ) HANGUL SYLLABLE BAEGG
+BC33;BC33;1107 1162 11AA;BC33;1107 1162 11AA; # (밳; 밳; 밳; 밳; 밳; ) HANGUL SYLLABLE BAEGS
+BC34;BC34;1107 1162 11AB;BC34;1107 1162 11AB; # (밴; 밴; 밴; 밴; 밴; ) HANGUL SYLLABLE BAEN
+BC35;BC35;1107 1162 11AC;BC35;1107 1162 11AC; # (밵; 밵; 밵; 밵; 밵; ) HANGUL SYLLABLE BAENJ
+BC36;BC36;1107 1162 11AD;BC36;1107 1162 11AD; # (밶; 밶; 밶; 밶; 밶; ) HANGUL SYLLABLE BAENH
+BC37;BC37;1107 1162 11AE;BC37;1107 1162 11AE; # (밷; 밷; 밷; 밷; 밷; ) HANGUL SYLLABLE BAED
+BC38;BC38;1107 1162 11AF;BC38;1107 1162 11AF; # (밸; 밸; 밸; 밸; 밸; ) HANGUL SYLLABLE BAEL
+BC39;BC39;1107 1162 11B0;BC39;1107 1162 11B0; # (밹; 밹; 밹; 밹; 밹; ) HANGUL SYLLABLE BAELG
+BC3A;BC3A;1107 1162 11B1;BC3A;1107 1162 11B1; # (밺; 밺; 밺; 밺; 밺; ) HANGUL SYLLABLE BAELM
+BC3B;BC3B;1107 1162 11B2;BC3B;1107 1162 11B2; # (밻; 밻; 밻; 밻; 밻; ) HANGUL SYLLABLE BAELB
+BC3C;BC3C;1107 1162 11B3;BC3C;1107 1162 11B3; # (밼; 밼; 밼; 밼; 밼; ) HANGUL SYLLABLE BAELS
+BC3D;BC3D;1107 1162 11B4;BC3D;1107 1162 11B4; # (밽; 밽; 밽; 밽; 밽; ) HANGUL SYLLABLE BAELT
+BC3E;BC3E;1107 1162 11B5;BC3E;1107 1162 11B5; # (밾; 밾; 밾; 밾; 밾; ) HANGUL SYLLABLE BAELP
+BC3F;BC3F;1107 1162 11B6;BC3F;1107 1162 11B6; # (밿; 밿; 밿; 밿; 밿; ) HANGUL SYLLABLE BAELH
+BC40;BC40;1107 1162 11B7;BC40;1107 1162 11B7; # (뱀; 뱀; 뱀; 뱀; 뱀; ) HANGUL SYLLABLE BAEM
+BC41;BC41;1107 1162 11B8;BC41;1107 1162 11B8; # (ë±; ë±; 뱁; ë±; 뱁; ) HANGUL SYLLABLE BAEB
+BC42;BC42;1107 1162 11B9;BC42;1107 1162 11B9; # (뱂; 뱂; 뱂; 뱂; 뱂; ) HANGUL SYLLABLE BAEBS
+BC43;BC43;1107 1162 11BA;BC43;1107 1162 11BA; # (뱃; 뱃; 뱃; 뱃; 뱃; ) HANGUL SYLLABLE BAES
+BC44;BC44;1107 1162 11BB;BC44;1107 1162 11BB; # (뱄; 뱄; 뱄; 뱄; 뱄; ) HANGUL SYLLABLE BAESS
+BC45;BC45;1107 1162 11BC;BC45;1107 1162 11BC; # (뱅; 뱅; 뱅; 뱅; 뱅; ) HANGUL SYLLABLE BAENG
+BC46;BC46;1107 1162 11BD;BC46;1107 1162 11BD; # (뱆; 뱆; 뱆; 뱆; 뱆; ) HANGUL SYLLABLE BAEJ
+BC47;BC47;1107 1162 11BE;BC47;1107 1162 11BE; # (뱇; 뱇; 뱇; 뱇; 뱇; ) HANGUL SYLLABLE BAEC
+BC48;BC48;1107 1162 11BF;BC48;1107 1162 11BF; # (뱈; 뱈; 뱈; 뱈; 뱈; ) HANGUL SYLLABLE BAEK
+BC49;BC49;1107 1162 11C0;BC49;1107 1162 11C0; # (뱉; 뱉; 뱉; 뱉; 뱉; ) HANGUL SYLLABLE BAET
+BC4A;BC4A;1107 1162 11C1;BC4A;1107 1162 11C1; # (뱊; 뱊; 배á‡; 뱊; 배á‡; ) HANGUL SYLLABLE BAEP
+BC4B;BC4B;1107 1162 11C2;BC4B;1107 1162 11C2; # (뱋; 뱋; 뱋; 뱋; 뱋; ) HANGUL SYLLABLE BAEH
+BC4C;BC4C;1107 1163;BC4C;1107 1163; # (뱌; 뱌; 뱌; 뱌; 뱌; ) HANGUL SYLLABLE BYA
+BC4D;BC4D;1107 1163 11A8;BC4D;1107 1163 11A8; # (ë±; ë±; 뱍; ë±; 뱍; ) HANGUL SYLLABLE BYAG
+BC4E;BC4E;1107 1163 11A9;BC4E;1107 1163 11A9; # (뱎; 뱎; 뱎; 뱎; 뱎; ) HANGUL SYLLABLE BYAGG
+BC4F;BC4F;1107 1163 11AA;BC4F;1107 1163 11AA; # (ë±; ë±; 뱏; ë±; 뱏; ) HANGUL SYLLABLE BYAGS
+BC50;BC50;1107 1163 11AB;BC50;1107 1163 11AB; # (ë±; ë±; 뱐; ë±; 뱐; ) HANGUL SYLLABLE BYAN
+BC51;BC51;1107 1163 11AC;BC51;1107 1163 11AC; # (뱑; 뱑; 뱑; 뱑; 뱑; ) HANGUL SYLLABLE BYANJ
+BC52;BC52;1107 1163 11AD;BC52;1107 1163 11AD; # (뱒; 뱒; 뱒; 뱒; 뱒; ) HANGUL SYLLABLE BYANH
+BC53;BC53;1107 1163 11AE;BC53;1107 1163 11AE; # (뱓; 뱓; 뱓; 뱓; 뱓; ) HANGUL SYLLABLE BYAD
+BC54;BC54;1107 1163 11AF;BC54;1107 1163 11AF; # (뱔; 뱔; 뱔; 뱔; 뱔; ) HANGUL SYLLABLE BYAL
+BC55;BC55;1107 1163 11B0;BC55;1107 1163 11B0; # (뱕; 뱕; 뱕; 뱕; 뱕; ) HANGUL SYLLABLE BYALG
+BC56;BC56;1107 1163 11B1;BC56;1107 1163 11B1; # (뱖; 뱖; 뱖; 뱖; 뱖; ) HANGUL SYLLABLE BYALM
+BC57;BC57;1107 1163 11B2;BC57;1107 1163 11B2; # (뱗; 뱗; 뱗; 뱗; 뱗; ) HANGUL SYLLABLE BYALB
+BC58;BC58;1107 1163 11B3;BC58;1107 1163 11B3; # (뱘; 뱘; 뱘; 뱘; 뱘; ) HANGUL SYLLABLE BYALS
+BC59;BC59;1107 1163 11B4;BC59;1107 1163 11B4; # (뱙; 뱙; 뱙; 뱙; 뱙; ) HANGUL SYLLABLE BYALT
+BC5A;BC5A;1107 1163 11B5;BC5A;1107 1163 11B5; # (뱚; 뱚; 뱚; 뱚; 뱚; ) HANGUL SYLLABLE BYALP
+BC5B;BC5B;1107 1163 11B6;BC5B;1107 1163 11B6; # (뱛; 뱛; 뱛; 뱛; 뱛; ) HANGUL SYLLABLE BYALH
+BC5C;BC5C;1107 1163 11B7;BC5C;1107 1163 11B7; # (뱜; 뱜; 뱜; 뱜; 뱜; ) HANGUL SYLLABLE BYAM
+BC5D;BC5D;1107 1163 11B8;BC5D;1107 1163 11B8; # (ë±; ë±; 뱝; ë±; 뱝; ) HANGUL SYLLABLE BYAB
+BC5E;BC5E;1107 1163 11B9;BC5E;1107 1163 11B9; # (뱞; 뱞; 뱞; 뱞; 뱞; ) HANGUL SYLLABLE BYABS
+BC5F;BC5F;1107 1163 11BA;BC5F;1107 1163 11BA; # (뱟; 뱟; 뱟; 뱟; 뱟; ) HANGUL SYLLABLE BYAS
+BC60;BC60;1107 1163 11BB;BC60;1107 1163 11BB; # (뱠; 뱠; 뱠; 뱠; 뱠; ) HANGUL SYLLABLE BYASS
+BC61;BC61;1107 1163 11BC;BC61;1107 1163 11BC; # (뱡; 뱡; 뱡; 뱡; 뱡; ) HANGUL SYLLABLE BYANG
+BC62;BC62;1107 1163 11BD;BC62;1107 1163 11BD; # (뱢; 뱢; 뱢; 뱢; 뱢; ) HANGUL SYLLABLE BYAJ
+BC63;BC63;1107 1163 11BE;BC63;1107 1163 11BE; # (뱣; 뱣; 뱣; 뱣; 뱣; ) HANGUL SYLLABLE BYAC
+BC64;BC64;1107 1163 11BF;BC64;1107 1163 11BF; # (뱤; 뱤; 뱤; 뱤; 뱤; ) HANGUL SYLLABLE BYAK
+BC65;BC65;1107 1163 11C0;BC65;1107 1163 11C0; # (뱥; 뱥; 뱥; 뱥; 뱥; ) HANGUL SYLLABLE BYAT
+BC66;BC66;1107 1163 11C1;BC66;1107 1163 11C1; # (뱦; 뱦; 뱌á‡; 뱦; 뱌á‡; ) HANGUL SYLLABLE BYAP
+BC67;BC67;1107 1163 11C2;BC67;1107 1163 11C2; # (뱧; 뱧; 뱧; 뱧; 뱧; ) HANGUL SYLLABLE BYAH
+BC68;BC68;1107 1164;BC68;1107 1164; # (뱨; 뱨; 뱨; 뱨; 뱨; ) HANGUL SYLLABLE BYAE
+BC69;BC69;1107 1164 11A8;BC69;1107 1164 11A8; # (뱩; 뱩; 뱩; 뱩; 뱩; ) HANGUL SYLLABLE BYAEG
+BC6A;BC6A;1107 1164 11A9;BC6A;1107 1164 11A9; # (뱪; 뱪; 뱪; 뱪; 뱪; ) HANGUL SYLLABLE BYAEGG
+BC6B;BC6B;1107 1164 11AA;BC6B;1107 1164 11AA; # (뱫; 뱫; 뱫; 뱫; 뱫; ) HANGUL SYLLABLE BYAEGS
+BC6C;BC6C;1107 1164 11AB;BC6C;1107 1164 11AB; # (뱬; 뱬; 뱬; 뱬; 뱬; ) HANGUL SYLLABLE BYAEN
+BC6D;BC6D;1107 1164 11AC;BC6D;1107 1164 11AC; # (뱭; 뱭; 뱭; 뱭; 뱭; ) HANGUL SYLLABLE BYAENJ
+BC6E;BC6E;1107 1164 11AD;BC6E;1107 1164 11AD; # (뱮; 뱮; 뱮; 뱮; 뱮; ) HANGUL SYLLABLE BYAENH
+BC6F;BC6F;1107 1164 11AE;BC6F;1107 1164 11AE; # (뱯; 뱯; 뱯; 뱯; 뱯; ) HANGUL SYLLABLE BYAED
+BC70;BC70;1107 1164 11AF;BC70;1107 1164 11AF; # (뱰; 뱰; 뱰; 뱰; 뱰; ) HANGUL SYLLABLE BYAEL
+BC71;BC71;1107 1164 11B0;BC71;1107 1164 11B0; # (뱱; 뱱; 뱱; 뱱; 뱱; ) HANGUL SYLLABLE BYAELG
+BC72;BC72;1107 1164 11B1;BC72;1107 1164 11B1; # (뱲; 뱲; 뱲; 뱲; 뱲; ) HANGUL SYLLABLE BYAELM
+BC73;BC73;1107 1164 11B2;BC73;1107 1164 11B2; # (뱳; 뱳; 뱳; 뱳; 뱳; ) HANGUL SYLLABLE BYAELB
+BC74;BC74;1107 1164 11B3;BC74;1107 1164 11B3; # (뱴; 뱴; 뱴; 뱴; 뱴; ) HANGUL SYLLABLE BYAELS
+BC75;BC75;1107 1164 11B4;BC75;1107 1164 11B4; # (뱵; 뱵; 뱵; 뱵; 뱵; ) HANGUL SYLLABLE BYAELT
+BC76;BC76;1107 1164 11B5;BC76;1107 1164 11B5; # (뱶; 뱶; 뱶; 뱶; 뱶; ) HANGUL SYLLABLE BYAELP
+BC77;BC77;1107 1164 11B6;BC77;1107 1164 11B6; # (뱷; 뱷; 뱷; 뱷; 뱷; ) HANGUL SYLLABLE BYAELH
+BC78;BC78;1107 1164 11B7;BC78;1107 1164 11B7; # (뱸; 뱸; 뱸; 뱸; 뱸; ) HANGUL SYLLABLE BYAEM
+BC79;BC79;1107 1164 11B8;BC79;1107 1164 11B8; # (뱹; 뱹; 뱹; 뱹; 뱹; ) HANGUL SYLLABLE BYAEB
+BC7A;BC7A;1107 1164 11B9;BC7A;1107 1164 11B9; # (뱺; 뱺; 뱺; 뱺; 뱺; ) HANGUL SYLLABLE BYAEBS
+BC7B;BC7B;1107 1164 11BA;BC7B;1107 1164 11BA; # (뱻; 뱻; 뱻; 뱻; 뱻; ) HANGUL SYLLABLE BYAES
+BC7C;BC7C;1107 1164 11BB;BC7C;1107 1164 11BB; # (뱼; 뱼; 뱼; 뱼; 뱼; ) HANGUL SYLLABLE BYAESS
+BC7D;BC7D;1107 1164 11BC;BC7D;1107 1164 11BC; # (뱽; 뱽; 뱽; 뱽; 뱽; ) HANGUL SYLLABLE BYAENG
+BC7E;BC7E;1107 1164 11BD;BC7E;1107 1164 11BD; # (뱾; 뱾; 뱾; 뱾; 뱾; ) HANGUL SYLLABLE BYAEJ
+BC7F;BC7F;1107 1164 11BE;BC7F;1107 1164 11BE; # (뱿; 뱿; 뱿; 뱿; 뱿; ) HANGUL SYLLABLE BYAEC
+BC80;BC80;1107 1164 11BF;BC80;1107 1164 11BF; # (벀; 벀; 벀; 벀; 벀; ) HANGUL SYLLABLE BYAEK
+BC81;BC81;1107 1164 11C0;BC81;1107 1164 11C0; # (ë²; ë²; 벁; ë²; 벁; ) HANGUL SYLLABLE BYAET
+BC82;BC82;1107 1164 11C1;BC82;1107 1164 11C1; # (벂; 벂; 뱨á‡; 벂; 뱨á‡; ) HANGUL SYLLABLE BYAEP
+BC83;BC83;1107 1164 11C2;BC83;1107 1164 11C2; # (벃; 벃; 벃; 벃; 벃; ) HANGUL SYLLABLE BYAEH
+BC84;BC84;1107 1165;BC84;1107 1165; # (버; 버; 버; 버; 버; ) HANGUL SYLLABLE BEO
+BC85;BC85;1107 1165 11A8;BC85;1107 1165 11A8; # (벅; 벅; 벅; 벅; 벅; ) HANGUL SYLLABLE BEOG
+BC86;BC86;1107 1165 11A9;BC86;1107 1165 11A9; # (벆; 벆; 벆; 벆; 벆; ) HANGUL SYLLABLE BEOGG
+BC87;BC87;1107 1165 11AA;BC87;1107 1165 11AA; # (벇; 벇; 벇; 벇; 벇; ) HANGUL SYLLABLE BEOGS
+BC88;BC88;1107 1165 11AB;BC88;1107 1165 11AB; # (번; 번; 번; 번; 번; ) HANGUL SYLLABLE BEON
+BC89;BC89;1107 1165 11AC;BC89;1107 1165 11AC; # (벉; 벉; 벉; 벉; 벉; ) HANGUL SYLLABLE BEONJ
+BC8A;BC8A;1107 1165 11AD;BC8A;1107 1165 11AD; # (벊; 벊; 벊; 벊; 벊; ) HANGUL SYLLABLE BEONH
+BC8B;BC8B;1107 1165 11AE;BC8B;1107 1165 11AE; # (벋; 벋; 벋; 벋; 벋; ) HANGUL SYLLABLE BEOD
+BC8C;BC8C;1107 1165 11AF;BC8C;1107 1165 11AF; # (벌; 벌; 벌; 벌; 벌; ) HANGUL SYLLABLE BEOL
+BC8D;BC8D;1107 1165 11B0;BC8D;1107 1165 11B0; # (ë²; ë²; 벍; ë²; 벍; ) HANGUL SYLLABLE BEOLG
+BC8E;BC8E;1107 1165 11B1;BC8E;1107 1165 11B1; # (벎; 벎; 벎; 벎; 벎; ) HANGUL SYLLABLE BEOLM
+BC8F;BC8F;1107 1165 11B2;BC8F;1107 1165 11B2; # (ë²; ë²; 벏; ë²; 벏; ) HANGUL SYLLABLE BEOLB
+BC90;BC90;1107 1165 11B3;BC90;1107 1165 11B3; # (ë²; ë²; 벐; ë²; 벐; ) HANGUL SYLLABLE BEOLS
+BC91;BC91;1107 1165 11B4;BC91;1107 1165 11B4; # (벑; 벑; 벑; 벑; 벑; ) HANGUL SYLLABLE BEOLT
+BC92;BC92;1107 1165 11B5;BC92;1107 1165 11B5; # (벒; 벒; 벒; 벒; 벒; ) HANGUL SYLLABLE BEOLP
+BC93;BC93;1107 1165 11B6;BC93;1107 1165 11B6; # (벓; 벓; 벓; 벓; 벓; ) HANGUL SYLLABLE BEOLH
+BC94;BC94;1107 1165 11B7;BC94;1107 1165 11B7; # (범; 범; 범; 범; 범; ) HANGUL SYLLABLE BEOM
+BC95;BC95;1107 1165 11B8;BC95;1107 1165 11B8; # (법; 법; 법; 법; 법; ) HANGUL SYLLABLE BEOB
+BC96;BC96;1107 1165 11B9;BC96;1107 1165 11B9; # (벖; 벖; 벖; 벖; 벖; ) HANGUL SYLLABLE BEOBS
+BC97;BC97;1107 1165 11BA;BC97;1107 1165 11BA; # (벗; 벗; 벗; 벗; 벗; ) HANGUL SYLLABLE BEOS
+BC98;BC98;1107 1165 11BB;BC98;1107 1165 11BB; # (벘; 벘; 벘; 벘; 벘; ) HANGUL SYLLABLE BEOSS
+BC99;BC99;1107 1165 11BC;BC99;1107 1165 11BC; # (벙; 벙; 벙; 벙; 벙; ) HANGUL SYLLABLE BEONG
+BC9A;BC9A;1107 1165 11BD;BC9A;1107 1165 11BD; # (벚; 벚; 벚; 벚; 벚; ) HANGUL SYLLABLE BEOJ
+BC9B;BC9B;1107 1165 11BE;BC9B;1107 1165 11BE; # (벛; 벛; 벛; 벛; 벛; ) HANGUL SYLLABLE BEOC
+BC9C;BC9C;1107 1165 11BF;BC9C;1107 1165 11BF; # (벜; 벜; 벜; 벜; 벜; ) HANGUL SYLLABLE BEOK
+BC9D;BC9D;1107 1165 11C0;BC9D;1107 1165 11C0; # (ë²; ë²; 벝; ë²; 벝; ) HANGUL SYLLABLE BEOT
+BC9E;BC9E;1107 1165 11C1;BC9E;1107 1165 11C1; # (벞; 벞; 버á‡; 벞; 버á‡; ) HANGUL SYLLABLE BEOP
+BC9F;BC9F;1107 1165 11C2;BC9F;1107 1165 11C2; # (벟; 벟; 벟; 벟; 벟; ) HANGUL SYLLABLE BEOH
+BCA0;BCA0;1107 1166;BCA0;1107 1166; # (베; 베; 베; 베; 베; ) HANGUL SYLLABLE BE
+BCA1;BCA1;1107 1166 11A8;BCA1;1107 1166 11A8; # (벡; 벡; 벡; 벡; 벡; ) HANGUL SYLLABLE BEG
+BCA2;BCA2;1107 1166 11A9;BCA2;1107 1166 11A9; # (벢; 벢; 벢; 벢; 벢; ) HANGUL SYLLABLE BEGG
+BCA3;BCA3;1107 1166 11AA;BCA3;1107 1166 11AA; # (벣; 벣; 벣; 벣; 벣; ) HANGUL SYLLABLE BEGS
+BCA4;BCA4;1107 1166 11AB;BCA4;1107 1166 11AB; # (벤; 벤; 벤; 벤; 벤; ) HANGUL SYLLABLE BEN
+BCA5;BCA5;1107 1166 11AC;BCA5;1107 1166 11AC; # (벥; 벥; 벥; 벥; 벥; ) HANGUL SYLLABLE BENJ
+BCA6;BCA6;1107 1166 11AD;BCA6;1107 1166 11AD; # (벦; 벦; 벦; 벦; 벦; ) HANGUL SYLLABLE BENH
+BCA7;BCA7;1107 1166 11AE;BCA7;1107 1166 11AE; # (벧; 벧; 벧; 벧; 벧; ) HANGUL SYLLABLE BED
+BCA8;BCA8;1107 1166 11AF;BCA8;1107 1166 11AF; # (벨; 벨; 벨; 벨; 벨; ) HANGUL SYLLABLE BEL
+BCA9;BCA9;1107 1166 11B0;BCA9;1107 1166 11B0; # (벩; 벩; 벩; 벩; 벩; ) HANGUL SYLLABLE BELG
+BCAA;BCAA;1107 1166 11B1;BCAA;1107 1166 11B1; # (벪; 벪; 벪; 벪; 벪; ) HANGUL SYLLABLE BELM
+BCAB;BCAB;1107 1166 11B2;BCAB;1107 1166 11B2; # (벫; 벫; 벫; 벫; 벫; ) HANGUL SYLLABLE BELB
+BCAC;BCAC;1107 1166 11B3;BCAC;1107 1166 11B3; # (벬; 벬; 벬; 벬; 벬; ) HANGUL SYLLABLE BELS
+BCAD;BCAD;1107 1166 11B4;BCAD;1107 1166 11B4; # (벭; 벭; 벭; 벭; 벭; ) HANGUL SYLLABLE BELT
+BCAE;BCAE;1107 1166 11B5;BCAE;1107 1166 11B5; # (벮; 벮; 벮; 벮; 벮; ) HANGUL SYLLABLE BELP
+BCAF;BCAF;1107 1166 11B6;BCAF;1107 1166 11B6; # (벯; 벯; 벯; 벯; 벯; ) HANGUL SYLLABLE BELH
+BCB0;BCB0;1107 1166 11B7;BCB0;1107 1166 11B7; # (벰; 벰; 벰; 벰; 벰; ) HANGUL SYLLABLE BEM
+BCB1;BCB1;1107 1166 11B8;BCB1;1107 1166 11B8; # (벱; 벱; 벱; 벱; 벱; ) HANGUL SYLLABLE BEB
+BCB2;BCB2;1107 1166 11B9;BCB2;1107 1166 11B9; # (벲; 벲; 벲; 벲; 벲; ) HANGUL SYLLABLE BEBS
+BCB3;BCB3;1107 1166 11BA;BCB3;1107 1166 11BA; # (벳; 벳; 벳; 벳; 벳; ) HANGUL SYLLABLE BES
+BCB4;BCB4;1107 1166 11BB;BCB4;1107 1166 11BB; # (벴; 벴; 벴; 벴; 벴; ) HANGUL SYLLABLE BESS
+BCB5;BCB5;1107 1166 11BC;BCB5;1107 1166 11BC; # (벵; 벵; 벵; 벵; 벵; ) HANGUL SYLLABLE BENG
+BCB6;BCB6;1107 1166 11BD;BCB6;1107 1166 11BD; # (벶; 벶; 벶; 벶; 벶; ) HANGUL SYLLABLE BEJ
+BCB7;BCB7;1107 1166 11BE;BCB7;1107 1166 11BE; # (벷; 벷; 벷; 벷; 벷; ) HANGUL SYLLABLE BEC
+BCB8;BCB8;1107 1166 11BF;BCB8;1107 1166 11BF; # (벸; 벸; 벸; 벸; 벸; ) HANGUL SYLLABLE BEK
+BCB9;BCB9;1107 1166 11C0;BCB9;1107 1166 11C0; # (벹; 벹; 벹; 벹; 벹; ) HANGUL SYLLABLE BET
+BCBA;BCBA;1107 1166 11C1;BCBA;1107 1166 11C1; # (벺; 벺; 베á‡; 벺; 베á‡; ) HANGUL SYLLABLE BEP
+BCBB;BCBB;1107 1166 11C2;BCBB;1107 1166 11C2; # (벻; 벻; 벻; 벻; 벻; ) HANGUL SYLLABLE BEH
+BCBC;BCBC;1107 1167;BCBC;1107 1167; # (벼; 벼; 벼; 벼; 벼; ) HANGUL SYLLABLE BYEO
+BCBD;BCBD;1107 1167 11A8;BCBD;1107 1167 11A8; # (벽; 벽; 벽; 벽; 벽; ) HANGUL SYLLABLE BYEOG
+BCBE;BCBE;1107 1167 11A9;BCBE;1107 1167 11A9; # (벾; 벾; 벾; 벾; 벾; ) HANGUL SYLLABLE BYEOGG
+BCBF;BCBF;1107 1167 11AA;BCBF;1107 1167 11AA; # (벿; 벿; 벿; 벿; 벿; ) HANGUL SYLLABLE BYEOGS
+BCC0;BCC0;1107 1167 11AB;BCC0;1107 1167 11AB; # (변; 변; 변; 변; 변; ) HANGUL SYLLABLE BYEON
+BCC1;BCC1;1107 1167 11AC;BCC1;1107 1167 11AC; # (ë³; ë³; 볁; ë³; 볁; ) HANGUL SYLLABLE BYEONJ
+BCC2;BCC2;1107 1167 11AD;BCC2;1107 1167 11AD; # (볂; 볂; 볂; 볂; 볂; ) HANGUL SYLLABLE BYEONH
+BCC3;BCC3;1107 1167 11AE;BCC3;1107 1167 11AE; # (볃; 볃; 볃; 볃; 볃; ) HANGUL SYLLABLE BYEOD
+BCC4;BCC4;1107 1167 11AF;BCC4;1107 1167 11AF; # (별; 별; 별; 별; 별; ) HANGUL SYLLABLE BYEOL
+BCC5;BCC5;1107 1167 11B0;BCC5;1107 1167 11B0; # (볅; 볅; 볅; 볅; 볅; ) HANGUL SYLLABLE BYEOLG
+BCC6;BCC6;1107 1167 11B1;BCC6;1107 1167 11B1; # (볆; 볆; 볆; 볆; 볆; ) HANGUL SYLLABLE BYEOLM
+BCC7;BCC7;1107 1167 11B2;BCC7;1107 1167 11B2; # (볇; 볇; 볇; 볇; 볇; ) HANGUL SYLLABLE BYEOLB
+BCC8;BCC8;1107 1167 11B3;BCC8;1107 1167 11B3; # (볈; 볈; 볈; 볈; 볈; ) HANGUL SYLLABLE BYEOLS
+BCC9;BCC9;1107 1167 11B4;BCC9;1107 1167 11B4; # (볉; 볉; 볉; 볉; 볉; ) HANGUL SYLLABLE BYEOLT
+BCCA;BCCA;1107 1167 11B5;BCCA;1107 1167 11B5; # (볊; 볊; 볊; 볊; 볊; ) HANGUL SYLLABLE BYEOLP
+BCCB;BCCB;1107 1167 11B6;BCCB;1107 1167 11B6; # (볋; 볋; 볋; 볋; 볋; ) HANGUL SYLLABLE BYEOLH
+BCCC;BCCC;1107 1167 11B7;BCCC;1107 1167 11B7; # (볌; 볌; 볌; 볌; 볌; ) HANGUL SYLLABLE BYEOM
+BCCD;BCCD;1107 1167 11B8;BCCD;1107 1167 11B8; # (ë³; ë³; 볍; ë³; 볍; ) HANGUL SYLLABLE BYEOB
+BCCE;BCCE;1107 1167 11B9;BCCE;1107 1167 11B9; # (볎; 볎; 볎; 볎; 볎; ) HANGUL SYLLABLE BYEOBS
+BCCF;BCCF;1107 1167 11BA;BCCF;1107 1167 11BA; # (ë³; ë³; 볏; ë³; 볏; ) HANGUL SYLLABLE BYEOS
+BCD0;BCD0;1107 1167 11BB;BCD0;1107 1167 11BB; # (ë³; ë³; 볐; ë³; 볐; ) HANGUL SYLLABLE BYEOSS
+BCD1;BCD1;1107 1167 11BC;BCD1;1107 1167 11BC; # (병; 병; 병; 병; 병; ) HANGUL SYLLABLE BYEONG
+BCD2;BCD2;1107 1167 11BD;BCD2;1107 1167 11BD; # (볒; 볒; 볒; 볒; 볒; ) HANGUL SYLLABLE BYEOJ
+BCD3;BCD3;1107 1167 11BE;BCD3;1107 1167 11BE; # (볓; 볓; 볓; 볓; 볓; ) HANGUL SYLLABLE BYEOC
+BCD4;BCD4;1107 1167 11BF;BCD4;1107 1167 11BF; # (볔; 볔; 볔; 볔; 볔; ) HANGUL SYLLABLE BYEOK
+BCD5;BCD5;1107 1167 11C0;BCD5;1107 1167 11C0; # (볕; 볕; 볕; 볕; 볕; ) HANGUL SYLLABLE BYEOT
+BCD6;BCD6;1107 1167 11C1;BCD6;1107 1167 11C1; # (ë³–; ë³–; 벼á‡; ë³–; 벼á‡; ) HANGUL SYLLABLE BYEOP
+BCD7;BCD7;1107 1167 11C2;BCD7;1107 1167 11C2; # (볗; 볗; 볗; 볗; 볗; ) HANGUL SYLLABLE BYEOH
+BCD8;BCD8;1107 1168;BCD8;1107 1168; # (볘; 볘; 볘; 볘; 볘; ) HANGUL SYLLABLE BYE
+BCD9;BCD9;1107 1168 11A8;BCD9;1107 1168 11A8; # (볙; 볙; 볙; 볙; 볙; ) HANGUL SYLLABLE BYEG
+BCDA;BCDA;1107 1168 11A9;BCDA;1107 1168 11A9; # (볚; 볚; 볚; 볚; 볚; ) HANGUL SYLLABLE BYEGG
+BCDB;BCDB;1107 1168 11AA;BCDB;1107 1168 11AA; # (볛; 볛; 볛; 볛; 볛; ) HANGUL SYLLABLE BYEGS
+BCDC;BCDC;1107 1168 11AB;BCDC;1107 1168 11AB; # (볜; 볜; 볜; 볜; 볜; ) HANGUL SYLLABLE BYEN
+BCDD;BCDD;1107 1168 11AC;BCDD;1107 1168 11AC; # (ë³; ë³; 볝; ë³; 볝; ) HANGUL SYLLABLE BYENJ
+BCDE;BCDE;1107 1168 11AD;BCDE;1107 1168 11AD; # (볞; 볞; 볞; 볞; 볞; ) HANGUL SYLLABLE BYENH
+BCDF;BCDF;1107 1168 11AE;BCDF;1107 1168 11AE; # (볟; 볟; 볟; 볟; 볟; ) HANGUL SYLLABLE BYED
+BCE0;BCE0;1107 1168 11AF;BCE0;1107 1168 11AF; # (볠; 볠; 볠; 볠; 볠; ) HANGUL SYLLABLE BYEL
+BCE1;BCE1;1107 1168 11B0;BCE1;1107 1168 11B0; # (볡; 볡; 볡; 볡; 볡; ) HANGUL SYLLABLE BYELG
+BCE2;BCE2;1107 1168 11B1;BCE2;1107 1168 11B1; # (볢; 볢; 볢; 볢; 볢; ) HANGUL SYLLABLE BYELM
+BCE3;BCE3;1107 1168 11B2;BCE3;1107 1168 11B2; # (볣; 볣; 볣; 볣; 볣; ) HANGUL SYLLABLE BYELB
+BCE4;BCE4;1107 1168 11B3;BCE4;1107 1168 11B3; # (볤; 볤; 볤; 볤; 볤; ) HANGUL SYLLABLE BYELS
+BCE5;BCE5;1107 1168 11B4;BCE5;1107 1168 11B4; # (볥; 볥; 볥; 볥; 볥; ) HANGUL SYLLABLE BYELT
+BCE6;BCE6;1107 1168 11B5;BCE6;1107 1168 11B5; # (볦; 볦; 볦; 볦; 볦; ) HANGUL SYLLABLE BYELP
+BCE7;BCE7;1107 1168 11B6;BCE7;1107 1168 11B6; # (볧; 볧; 볧; 볧; 볧; ) HANGUL SYLLABLE BYELH
+BCE8;BCE8;1107 1168 11B7;BCE8;1107 1168 11B7; # (볨; 볨; 볨; 볨; 볨; ) HANGUL SYLLABLE BYEM
+BCE9;BCE9;1107 1168 11B8;BCE9;1107 1168 11B8; # (볩; 볩; 볩; 볩; 볩; ) HANGUL SYLLABLE BYEB
+BCEA;BCEA;1107 1168 11B9;BCEA;1107 1168 11B9; # (볪; 볪; 볪; 볪; 볪; ) HANGUL SYLLABLE BYEBS
+BCEB;BCEB;1107 1168 11BA;BCEB;1107 1168 11BA; # (볫; 볫; 볫; 볫; 볫; ) HANGUL SYLLABLE BYES
+BCEC;BCEC;1107 1168 11BB;BCEC;1107 1168 11BB; # (볬; 볬; 볬; 볬; 볬; ) HANGUL SYLLABLE BYESS
+BCED;BCED;1107 1168 11BC;BCED;1107 1168 11BC; # (볭; 볭; 볭; 볭; 볭; ) HANGUL SYLLABLE BYENG
+BCEE;BCEE;1107 1168 11BD;BCEE;1107 1168 11BD; # (볮; 볮; 볮; 볮; 볮; ) HANGUL SYLLABLE BYEJ
+BCEF;BCEF;1107 1168 11BE;BCEF;1107 1168 11BE; # (볯; 볯; 볯; 볯; 볯; ) HANGUL SYLLABLE BYEC
+BCF0;BCF0;1107 1168 11BF;BCF0;1107 1168 11BF; # (볰; 볰; 볰; 볰; 볰; ) HANGUL SYLLABLE BYEK
+BCF1;BCF1;1107 1168 11C0;BCF1;1107 1168 11C0; # (볱; 볱; 볱; 볱; 볱; ) HANGUL SYLLABLE BYET
+BCF2;BCF2;1107 1168 11C1;BCF2;1107 1168 11C1; # (ë³²; ë³²; 볘á‡; ë³²; 볘á‡; ) HANGUL SYLLABLE BYEP
+BCF3;BCF3;1107 1168 11C2;BCF3;1107 1168 11C2; # (볳; 볳; 볳; 볳; 볳; ) HANGUL SYLLABLE BYEH
+BCF4;BCF4;1107 1169;BCF4;1107 1169; # (보; 보; 보; 보; 보; ) HANGUL SYLLABLE BO
+BCF5;BCF5;1107 1169 11A8;BCF5;1107 1169 11A8; # (복; 복; 복; 복; 복; ) HANGUL SYLLABLE BOG
+BCF6;BCF6;1107 1169 11A9;BCF6;1107 1169 11A9; # (볶; 볶; 볶; 볶; 볶; ) HANGUL SYLLABLE BOGG
+BCF7;BCF7;1107 1169 11AA;BCF7;1107 1169 11AA; # (볷; 볷; 볷; 볷; 볷; ) HANGUL SYLLABLE BOGS
+BCF8;BCF8;1107 1169 11AB;BCF8;1107 1169 11AB; # (본; 본; 본; 본; 본; ) HANGUL SYLLABLE BON
+BCF9;BCF9;1107 1169 11AC;BCF9;1107 1169 11AC; # (볹; 볹; 볹; 볹; 볹; ) HANGUL SYLLABLE BONJ
+BCFA;BCFA;1107 1169 11AD;BCFA;1107 1169 11AD; # (볺; 볺; 볺; 볺; 볺; ) HANGUL SYLLABLE BONH
+BCFB;BCFB;1107 1169 11AE;BCFB;1107 1169 11AE; # (볻; 볻; 볻; 볻; 볻; ) HANGUL SYLLABLE BOD
+BCFC;BCFC;1107 1169 11AF;BCFC;1107 1169 11AF; # (볼; 볼; 볼; 볼; 볼; ) HANGUL SYLLABLE BOL
+BCFD;BCFD;1107 1169 11B0;BCFD;1107 1169 11B0; # (볽; 볽; 볽; 볽; 볽; ) HANGUL SYLLABLE BOLG
+BCFE;BCFE;1107 1169 11B1;BCFE;1107 1169 11B1; # (볾; 볾; 볾; 볾; 볾; ) HANGUL SYLLABLE BOLM
+BCFF;BCFF;1107 1169 11B2;BCFF;1107 1169 11B2; # (볿; 볿; 볿; 볿; 볿; ) HANGUL SYLLABLE BOLB
+BD00;BD00;1107 1169 11B3;BD00;1107 1169 11B3; # (봀; 봀; 봀; 봀; 봀; ) HANGUL SYLLABLE BOLS
+BD01;BD01;1107 1169 11B4;BD01;1107 1169 11B4; # (ë´; ë´; 봁; ë´; 봁; ) HANGUL SYLLABLE BOLT
+BD02;BD02;1107 1169 11B5;BD02;1107 1169 11B5; # (봂; 봂; 봂; 봂; 봂; ) HANGUL SYLLABLE BOLP
+BD03;BD03;1107 1169 11B6;BD03;1107 1169 11B6; # (봃; 봃; 봃; 봃; 봃; ) HANGUL SYLLABLE BOLH
+BD04;BD04;1107 1169 11B7;BD04;1107 1169 11B7; # (봄; 봄; 봄; 봄; 봄; ) HANGUL SYLLABLE BOM
+BD05;BD05;1107 1169 11B8;BD05;1107 1169 11B8; # (봅; 봅; 봅; 봅; 봅; ) HANGUL SYLLABLE BOB
+BD06;BD06;1107 1169 11B9;BD06;1107 1169 11B9; # (봆; 봆; 봆; 봆; 봆; ) HANGUL SYLLABLE BOBS
+BD07;BD07;1107 1169 11BA;BD07;1107 1169 11BA; # (봇; 봇; 봇; 봇; 봇; ) HANGUL SYLLABLE BOS
+BD08;BD08;1107 1169 11BB;BD08;1107 1169 11BB; # (봈; 봈; 봈; 봈; 봈; ) HANGUL SYLLABLE BOSS
+BD09;BD09;1107 1169 11BC;BD09;1107 1169 11BC; # (봉; 봉; 봉; 봉; 봉; ) HANGUL SYLLABLE BONG
+BD0A;BD0A;1107 1169 11BD;BD0A;1107 1169 11BD; # (봊; 봊; 봊; 봊; 봊; ) HANGUL SYLLABLE BOJ
+BD0B;BD0B;1107 1169 11BE;BD0B;1107 1169 11BE; # (봋; 봋; 봋; 봋; 봋; ) HANGUL SYLLABLE BOC
+BD0C;BD0C;1107 1169 11BF;BD0C;1107 1169 11BF; # (봌; 봌; 봌; 봌; 봌; ) HANGUL SYLLABLE BOK
+BD0D;BD0D;1107 1169 11C0;BD0D;1107 1169 11C0; # (ë´; ë´; 봍; ë´; 봍; ) HANGUL SYLLABLE BOT
+BD0E;BD0E;1107 1169 11C1;BD0E;1107 1169 11C1; # (ë´Ž; ë´Ž; 보á‡; ë´Ž; 보á‡; ) HANGUL SYLLABLE BOP
+BD0F;BD0F;1107 1169 11C2;BD0F;1107 1169 11C2; # (ë´; ë´; 봏; ë´; 봏; ) HANGUL SYLLABLE BOH
+BD10;BD10;1107 116A;BD10;1107 116A; # (ë´; ë´; 봐; ë´; 봐; ) HANGUL SYLLABLE BWA
+BD11;BD11;1107 116A 11A8;BD11;1107 116A 11A8; # (봑; 봑; 봑; 봑; 봑; ) HANGUL SYLLABLE BWAG
+BD12;BD12;1107 116A 11A9;BD12;1107 116A 11A9; # (봒; 봒; 봒; 봒; 봒; ) HANGUL SYLLABLE BWAGG
+BD13;BD13;1107 116A 11AA;BD13;1107 116A 11AA; # (봓; 봓; 봓; 봓; 봓; ) HANGUL SYLLABLE BWAGS
+BD14;BD14;1107 116A 11AB;BD14;1107 116A 11AB; # (봔; 봔; 봔; 봔; 봔; ) HANGUL SYLLABLE BWAN
+BD15;BD15;1107 116A 11AC;BD15;1107 116A 11AC; # (봕; 봕; 봕; 봕; 봕; ) HANGUL SYLLABLE BWANJ
+BD16;BD16;1107 116A 11AD;BD16;1107 116A 11AD; # (봖; 봖; 봖; 봖; 봖; ) HANGUL SYLLABLE BWANH
+BD17;BD17;1107 116A 11AE;BD17;1107 116A 11AE; # (봗; 봗; 봗; 봗; 봗; ) HANGUL SYLLABLE BWAD
+BD18;BD18;1107 116A 11AF;BD18;1107 116A 11AF; # (봘; 봘; 봘; 봘; 봘; ) HANGUL SYLLABLE BWAL
+BD19;BD19;1107 116A 11B0;BD19;1107 116A 11B0; # (봙; 봙; 봙; 봙; 봙; ) HANGUL SYLLABLE BWALG
+BD1A;BD1A;1107 116A 11B1;BD1A;1107 116A 11B1; # (봚; 봚; 봚; 봚; 봚; ) HANGUL SYLLABLE BWALM
+BD1B;BD1B;1107 116A 11B2;BD1B;1107 116A 11B2; # (봛; 봛; 봛; 봛; 봛; ) HANGUL SYLLABLE BWALB
+BD1C;BD1C;1107 116A 11B3;BD1C;1107 116A 11B3; # (봜; 봜; 봜; 봜; 봜; ) HANGUL SYLLABLE BWALS
+BD1D;BD1D;1107 116A 11B4;BD1D;1107 116A 11B4; # (ë´; ë´; 봝; ë´; 봝; ) HANGUL SYLLABLE BWALT
+BD1E;BD1E;1107 116A 11B5;BD1E;1107 116A 11B5; # (봞; 봞; 봞; 봞; 봞; ) HANGUL SYLLABLE BWALP
+BD1F;BD1F;1107 116A 11B6;BD1F;1107 116A 11B6; # (봟; 봟; 봟; 봟; 봟; ) HANGUL SYLLABLE BWALH
+BD20;BD20;1107 116A 11B7;BD20;1107 116A 11B7; # (봠; 봠; 봠; 봠; 봠; ) HANGUL SYLLABLE BWAM
+BD21;BD21;1107 116A 11B8;BD21;1107 116A 11B8; # (봡; 봡; 봡; 봡; 봡; ) HANGUL SYLLABLE BWAB
+BD22;BD22;1107 116A 11B9;BD22;1107 116A 11B9; # (봢; 봢; 봢; 봢; 봢; ) HANGUL SYLLABLE BWABS
+BD23;BD23;1107 116A 11BA;BD23;1107 116A 11BA; # (봣; 봣; 봣; 봣; 봣; ) HANGUL SYLLABLE BWAS
+BD24;BD24;1107 116A 11BB;BD24;1107 116A 11BB; # (봤; 봤; 봤; 봤; 봤; ) HANGUL SYLLABLE BWASS
+BD25;BD25;1107 116A 11BC;BD25;1107 116A 11BC; # (봥; 봥; 봥; 봥; 봥; ) HANGUL SYLLABLE BWANG
+BD26;BD26;1107 116A 11BD;BD26;1107 116A 11BD; # (봦; 봦; 봦; 봦; 봦; ) HANGUL SYLLABLE BWAJ
+BD27;BD27;1107 116A 11BE;BD27;1107 116A 11BE; # (봧; 봧; 봧; 봧; 봧; ) HANGUL SYLLABLE BWAC
+BD28;BD28;1107 116A 11BF;BD28;1107 116A 11BF; # (봨; 봨; 봨; 봨; 봨; ) HANGUL SYLLABLE BWAK
+BD29;BD29;1107 116A 11C0;BD29;1107 116A 11C0; # (봩; 봩; 봩; 봩; 봩; ) HANGUL SYLLABLE BWAT
+BD2A;BD2A;1107 116A 11C1;BD2A;1107 116A 11C1; # (ë´ª; ë´ª; 봐á‡; ë´ª; 봐á‡; ) HANGUL SYLLABLE BWAP
+BD2B;BD2B;1107 116A 11C2;BD2B;1107 116A 11C2; # (봫; 봫; 봫; 봫; 봫; ) HANGUL SYLLABLE BWAH
+BD2C;BD2C;1107 116B;BD2C;1107 116B; # (봬; 봬; 봬; 봬; 봬; ) HANGUL SYLLABLE BWAE
+BD2D;BD2D;1107 116B 11A8;BD2D;1107 116B 11A8; # (봭; 봭; 봭; 봭; 봭; ) HANGUL SYLLABLE BWAEG
+BD2E;BD2E;1107 116B 11A9;BD2E;1107 116B 11A9; # (봮; 봮; 봮; 봮; 봮; ) HANGUL SYLLABLE BWAEGG
+BD2F;BD2F;1107 116B 11AA;BD2F;1107 116B 11AA; # (봯; 봯; 봯; 봯; 봯; ) HANGUL SYLLABLE BWAEGS
+BD30;BD30;1107 116B 11AB;BD30;1107 116B 11AB; # (봰; 봰; 봰; 봰; 봰; ) HANGUL SYLLABLE BWAEN
+BD31;BD31;1107 116B 11AC;BD31;1107 116B 11AC; # (봱; 봱; 봱; 봱; 봱; ) HANGUL SYLLABLE BWAENJ
+BD32;BD32;1107 116B 11AD;BD32;1107 116B 11AD; # (봲; 봲; 봲; 봲; 봲; ) HANGUL SYLLABLE BWAENH
+BD33;BD33;1107 116B 11AE;BD33;1107 116B 11AE; # (봳; 봳; 봳; 봳; 봳; ) HANGUL SYLLABLE BWAED
+BD34;BD34;1107 116B 11AF;BD34;1107 116B 11AF; # (봴; 봴; 봴; 봴; 봴; ) HANGUL SYLLABLE BWAEL
+BD35;BD35;1107 116B 11B0;BD35;1107 116B 11B0; # (봵; 봵; 봵; 봵; 봵; ) HANGUL SYLLABLE BWAELG
+BD36;BD36;1107 116B 11B1;BD36;1107 116B 11B1; # (봶; 봶; 봶; 봶; 봶; ) HANGUL SYLLABLE BWAELM
+BD37;BD37;1107 116B 11B2;BD37;1107 116B 11B2; # (봷; 봷; 봷; 봷; 봷; ) HANGUL SYLLABLE BWAELB
+BD38;BD38;1107 116B 11B3;BD38;1107 116B 11B3; # (봸; 봸; 봸; 봸; 봸; ) HANGUL SYLLABLE BWAELS
+BD39;BD39;1107 116B 11B4;BD39;1107 116B 11B4; # (봹; 봹; 봹; 봹; 봹; ) HANGUL SYLLABLE BWAELT
+BD3A;BD3A;1107 116B 11B5;BD3A;1107 116B 11B5; # (봺; 봺; 봺; 봺; 봺; ) HANGUL SYLLABLE BWAELP
+BD3B;BD3B;1107 116B 11B6;BD3B;1107 116B 11B6; # (봻; 봻; 봻; 봻; 봻; ) HANGUL SYLLABLE BWAELH
+BD3C;BD3C;1107 116B 11B7;BD3C;1107 116B 11B7; # (봼; 봼; 봼; 봼; 봼; ) HANGUL SYLLABLE BWAEM
+BD3D;BD3D;1107 116B 11B8;BD3D;1107 116B 11B8; # (봽; 봽; 봽; 봽; 봽; ) HANGUL SYLLABLE BWAEB
+BD3E;BD3E;1107 116B 11B9;BD3E;1107 116B 11B9; # (봾; 봾; 봾; 봾; 봾; ) HANGUL SYLLABLE BWAEBS
+BD3F;BD3F;1107 116B 11BA;BD3F;1107 116B 11BA; # (봿; 봿; 봿; 봿; 봿; ) HANGUL SYLLABLE BWAES
+BD40;BD40;1107 116B 11BB;BD40;1107 116B 11BB; # (뵀; 뵀; 뵀; 뵀; 뵀; ) HANGUL SYLLABLE BWAESS
+BD41;BD41;1107 116B 11BC;BD41;1107 116B 11BC; # (ëµ; ëµ; 뵁; ëµ; 뵁; ) HANGUL SYLLABLE BWAENG
+BD42;BD42;1107 116B 11BD;BD42;1107 116B 11BD; # (뵂; 뵂; 뵂; 뵂; 뵂; ) HANGUL SYLLABLE BWAEJ
+BD43;BD43;1107 116B 11BE;BD43;1107 116B 11BE; # (뵃; 뵃; 뵃; 뵃; 뵃; ) HANGUL SYLLABLE BWAEC
+BD44;BD44;1107 116B 11BF;BD44;1107 116B 11BF; # (뵄; 뵄; 뵄; 뵄; 뵄; ) HANGUL SYLLABLE BWAEK
+BD45;BD45;1107 116B 11C0;BD45;1107 116B 11C0; # (뵅; 뵅; 뵅; 뵅; 뵅; ) HANGUL SYLLABLE BWAET
+BD46;BD46;1107 116B 11C1;BD46;1107 116B 11C1; # (뵆; 뵆; 봬á‡; 뵆; 봬á‡; ) HANGUL SYLLABLE BWAEP
+BD47;BD47;1107 116B 11C2;BD47;1107 116B 11C2; # (뵇; 뵇; 뵇; 뵇; 뵇; ) HANGUL SYLLABLE BWAEH
+BD48;BD48;1107 116C;BD48;1107 116C; # (뵈; 뵈; 뵈; 뵈; 뵈; ) HANGUL SYLLABLE BOE
+BD49;BD49;1107 116C 11A8;BD49;1107 116C 11A8; # (뵉; 뵉; 뵉; 뵉; 뵉; ) HANGUL SYLLABLE BOEG
+BD4A;BD4A;1107 116C 11A9;BD4A;1107 116C 11A9; # (뵊; 뵊; 뵊; 뵊; 뵊; ) HANGUL SYLLABLE BOEGG
+BD4B;BD4B;1107 116C 11AA;BD4B;1107 116C 11AA; # (뵋; 뵋; 뵋; 뵋; 뵋; ) HANGUL SYLLABLE BOEGS
+BD4C;BD4C;1107 116C 11AB;BD4C;1107 116C 11AB; # (뵌; 뵌; 뵌; 뵌; 뵌; ) HANGUL SYLLABLE BOEN
+BD4D;BD4D;1107 116C 11AC;BD4D;1107 116C 11AC; # (ëµ; ëµ; 뵍; ëµ; 뵍; ) HANGUL SYLLABLE BOENJ
+BD4E;BD4E;1107 116C 11AD;BD4E;1107 116C 11AD; # (뵎; 뵎; 뵎; 뵎; 뵎; ) HANGUL SYLLABLE BOENH
+BD4F;BD4F;1107 116C 11AE;BD4F;1107 116C 11AE; # (ëµ; ëµ; 뵏; ëµ; 뵏; ) HANGUL SYLLABLE BOED
+BD50;BD50;1107 116C 11AF;BD50;1107 116C 11AF; # (ëµ; ëµ; 뵐; ëµ; 뵐; ) HANGUL SYLLABLE BOEL
+BD51;BD51;1107 116C 11B0;BD51;1107 116C 11B0; # (뵑; 뵑; 뵑; 뵑; 뵑; ) HANGUL SYLLABLE BOELG
+BD52;BD52;1107 116C 11B1;BD52;1107 116C 11B1; # (뵒; 뵒; 뵒; 뵒; 뵒; ) HANGUL SYLLABLE BOELM
+BD53;BD53;1107 116C 11B2;BD53;1107 116C 11B2; # (뵓; 뵓; 뵓; 뵓; 뵓; ) HANGUL SYLLABLE BOELB
+BD54;BD54;1107 116C 11B3;BD54;1107 116C 11B3; # (뵔; 뵔; 뵔; 뵔; 뵔; ) HANGUL SYLLABLE BOELS
+BD55;BD55;1107 116C 11B4;BD55;1107 116C 11B4; # (뵕; 뵕; 뵕; 뵕; 뵕; ) HANGUL SYLLABLE BOELT
+BD56;BD56;1107 116C 11B5;BD56;1107 116C 11B5; # (뵖; 뵖; 뵖; 뵖; 뵖; ) HANGUL SYLLABLE BOELP
+BD57;BD57;1107 116C 11B6;BD57;1107 116C 11B6; # (뵗; 뵗; 뵗; 뵗; 뵗; ) HANGUL SYLLABLE BOELH
+BD58;BD58;1107 116C 11B7;BD58;1107 116C 11B7; # (뵘; 뵘; 뵘; 뵘; 뵘; ) HANGUL SYLLABLE BOEM
+BD59;BD59;1107 116C 11B8;BD59;1107 116C 11B8; # (뵙; 뵙; 뵙; 뵙; 뵙; ) HANGUL SYLLABLE BOEB
+BD5A;BD5A;1107 116C 11B9;BD5A;1107 116C 11B9; # (뵚; 뵚; 뵚; 뵚; 뵚; ) HANGUL SYLLABLE BOEBS
+BD5B;BD5B;1107 116C 11BA;BD5B;1107 116C 11BA; # (뵛; 뵛; 뵛; 뵛; 뵛; ) HANGUL SYLLABLE BOES
+BD5C;BD5C;1107 116C 11BB;BD5C;1107 116C 11BB; # (뵜; 뵜; 뵜; 뵜; 뵜; ) HANGUL SYLLABLE BOESS
+BD5D;BD5D;1107 116C 11BC;BD5D;1107 116C 11BC; # (ëµ; ëµ; 뵝; ëµ; 뵝; ) HANGUL SYLLABLE BOENG
+BD5E;BD5E;1107 116C 11BD;BD5E;1107 116C 11BD; # (뵞; 뵞; 뵞; 뵞; 뵞; ) HANGUL SYLLABLE BOEJ
+BD5F;BD5F;1107 116C 11BE;BD5F;1107 116C 11BE; # (뵟; 뵟; 뵟; 뵟; 뵟; ) HANGUL SYLLABLE BOEC
+BD60;BD60;1107 116C 11BF;BD60;1107 116C 11BF; # (뵠; 뵠; 뵠; 뵠; 뵠; ) HANGUL SYLLABLE BOEK
+BD61;BD61;1107 116C 11C0;BD61;1107 116C 11C0; # (뵡; 뵡; 뵡; 뵡; 뵡; ) HANGUL SYLLABLE BOET
+BD62;BD62;1107 116C 11C1;BD62;1107 116C 11C1; # (ëµ¢; ëµ¢; 뵈á‡; ëµ¢; 뵈á‡; ) HANGUL SYLLABLE BOEP
+BD63;BD63;1107 116C 11C2;BD63;1107 116C 11C2; # (뵣; 뵣; 뵣; 뵣; 뵣; ) HANGUL SYLLABLE BOEH
+BD64;BD64;1107 116D;BD64;1107 116D; # (뵤; 뵤; 뵤; 뵤; 뵤; ) HANGUL SYLLABLE BYO
+BD65;BD65;1107 116D 11A8;BD65;1107 116D 11A8; # (뵥; 뵥; 뵥; 뵥; 뵥; ) HANGUL SYLLABLE BYOG
+BD66;BD66;1107 116D 11A9;BD66;1107 116D 11A9; # (뵦; 뵦; 뵦; 뵦; 뵦; ) HANGUL SYLLABLE BYOGG
+BD67;BD67;1107 116D 11AA;BD67;1107 116D 11AA; # (뵧; 뵧; 뵧; 뵧; 뵧; ) HANGUL SYLLABLE BYOGS
+BD68;BD68;1107 116D 11AB;BD68;1107 116D 11AB; # (뵨; 뵨; 뵨; 뵨; 뵨; ) HANGUL SYLLABLE BYON
+BD69;BD69;1107 116D 11AC;BD69;1107 116D 11AC; # (뵩; 뵩; 뵩; 뵩; 뵩; ) HANGUL SYLLABLE BYONJ
+BD6A;BD6A;1107 116D 11AD;BD6A;1107 116D 11AD; # (뵪; 뵪; 뵪; 뵪; 뵪; ) HANGUL SYLLABLE BYONH
+BD6B;BD6B;1107 116D 11AE;BD6B;1107 116D 11AE; # (뵫; 뵫; 뵫; 뵫; 뵫; ) HANGUL SYLLABLE BYOD
+BD6C;BD6C;1107 116D 11AF;BD6C;1107 116D 11AF; # (뵬; 뵬; 뵬; 뵬; 뵬; ) HANGUL SYLLABLE BYOL
+BD6D;BD6D;1107 116D 11B0;BD6D;1107 116D 11B0; # (뵭; 뵭; 뵭; 뵭; 뵭; ) HANGUL SYLLABLE BYOLG
+BD6E;BD6E;1107 116D 11B1;BD6E;1107 116D 11B1; # (뵮; 뵮; 뵮; 뵮; 뵮; ) HANGUL SYLLABLE BYOLM
+BD6F;BD6F;1107 116D 11B2;BD6F;1107 116D 11B2; # (뵯; 뵯; 뵯; 뵯; 뵯; ) HANGUL SYLLABLE BYOLB
+BD70;BD70;1107 116D 11B3;BD70;1107 116D 11B3; # (뵰; 뵰; 뵰; 뵰; 뵰; ) HANGUL SYLLABLE BYOLS
+BD71;BD71;1107 116D 11B4;BD71;1107 116D 11B4; # (뵱; 뵱; 뵱; 뵱; 뵱; ) HANGUL SYLLABLE BYOLT
+BD72;BD72;1107 116D 11B5;BD72;1107 116D 11B5; # (뵲; 뵲; 뵲; 뵲; 뵲; ) HANGUL SYLLABLE BYOLP
+BD73;BD73;1107 116D 11B6;BD73;1107 116D 11B6; # (뵳; 뵳; 뵳; 뵳; 뵳; ) HANGUL SYLLABLE BYOLH
+BD74;BD74;1107 116D 11B7;BD74;1107 116D 11B7; # (뵴; 뵴; 뵴; 뵴; 뵴; ) HANGUL SYLLABLE BYOM
+BD75;BD75;1107 116D 11B8;BD75;1107 116D 11B8; # (뵵; 뵵; 뵵; 뵵; 뵵; ) HANGUL SYLLABLE BYOB
+BD76;BD76;1107 116D 11B9;BD76;1107 116D 11B9; # (뵶; 뵶; 뵶; 뵶; 뵶; ) HANGUL SYLLABLE BYOBS
+BD77;BD77;1107 116D 11BA;BD77;1107 116D 11BA; # (뵷; 뵷; 뵷; 뵷; 뵷; ) HANGUL SYLLABLE BYOS
+BD78;BD78;1107 116D 11BB;BD78;1107 116D 11BB; # (뵸; 뵸; 뵸; 뵸; 뵸; ) HANGUL SYLLABLE BYOSS
+BD79;BD79;1107 116D 11BC;BD79;1107 116D 11BC; # (뵹; 뵹; 뵹; 뵹; 뵹; ) HANGUL SYLLABLE BYONG
+BD7A;BD7A;1107 116D 11BD;BD7A;1107 116D 11BD; # (뵺; 뵺; 뵺; 뵺; 뵺; ) HANGUL SYLLABLE BYOJ
+BD7B;BD7B;1107 116D 11BE;BD7B;1107 116D 11BE; # (뵻; 뵻; 뵻; 뵻; 뵻; ) HANGUL SYLLABLE BYOC
+BD7C;BD7C;1107 116D 11BF;BD7C;1107 116D 11BF; # (뵼; 뵼; 뵼; 뵼; 뵼; ) HANGUL SYLLABLE BYOK
+BD7D;BD7D;1107 116D 11C0;BD7D;1107 116D 11C0; # (뵽; 뵽; 뵽; 뵽; 뵽; ) HANGUL SYLLABLE BYOT
+BD7E;BD7E;1107 116D 11C1;BD7E;1107 116D 11C1; # (ëµ¾; ëµ¾; 뵤á‡; ëµ¾; 뵤á‡; ) HANGUL SYLLABLE BYOP
+BD7F;BD7F;1107 116D 11C2;BD7F;1107 116D 11C2; # (뵿; 뵿; 뵿; 뵿; 뵿; ) HANGUL SYLLABLE BYOH
+BD80;BD80;1107 116E;BD80;1107 116E; # (부; 부; 부; 부; 부; ) HANGUL SYLLABLE BU
+BD81;BD81;1107 116E 11A8;BD81;1107 116E 11A8; # (ë¶; ë¶; 북; ë¶; 북; ) HANGUL SYLLABLE BUG
+BD82;BD82;1107 116E 11A9;BD82;1107 116E 11A9; # (붂; 붂; 붂; 붂; 붂; ) HANGUL SYLLABLE BUGG
+BD83;BD83;1107 116E 11AA;BD83;1107 116E 11AA; # (붃; 붃; 붃; 붃; 붃; ) HANGUL SYLLABLE BUGS
+BD84;BD84;1107 116E 11AB;BD84;1107 116E 11AB; # (분; 분; 분; 분; 분; ) HANGUL SYLLABLE BUN
+BD85;BD85;1107 116E 11AC;BD85;1107 116E 11AC; # (붅; 붅; 붅; 붅; 붅; ) HANGUL SYLLABLE BUNJ
+BD86;BD86;1107 116E 11AD;BD86;1107 116E 11AD; # (붆; 붆; 붆; 붆; 붆; ) HANGUL SYLLABLE BUNH
+BD87;BD87;1107 116E 11AE;BD87;1107 116E 11AE; # (붇; 붇; 붇; 붇; 붇; ) HANGUL SYLLABLE BUD
+BD88;BD88;1107 116E 11AF;BD88;1107 116E 11AF; # (불; 불; 불; 불; 불; ) HANGUL SYLLABLE BUL
+BD89;BD89;1107 116E 11B0;BD89;1107 116E 11B0; # (붉; 붉; 붉; 붉; 붉; ) HANGUL SYLLABLE BULG
+BD8A;BD8A;1107 116E 11B1;BD8A;1107 116E 11B1; # (붊; 붊; 붊; 붊; 붊; ) HANGUL SYLLABLE BULM
+BD8B;BD8B;1107 116E 11B2;BD8B;1107 116E 11B2; # (붋; 붋; 붋; 붋; 붋; ) HANGUL SYLLABLE BULB
+BD8C;BD8C;1107 116E 11B3;BD8C;1107 116E 11B3; # (붌; 붌; 붌; 붌; 붌; ) HANGUL SYLLABLE BULS
+BD8D;BD8D;1107 116E 11B4;BD8D;1107 116E 11B4; # (ë¶; ë¶; 붍; ë¶; 붍; ) HANGUL SYLLABLE BULT
+BD8E;BD8E;1107 116E 11B5;BD8E;1107 116E 11B5; # (붎; 붎; 붎; 붎; 붎; ) HANGUL SYLLABLE BULP
+BD8F;BD8F;1107 116E 11B6;BD8F;1107 116E 11B6; # (ë¶; ë¶; 붏; ë¶; 붏; ) HANGUL SYLLABLE BULH
+BD90;BD90;1107 116E 11B7;BD90;1107 116E 11B7; # (ë¶; ë¶; 붐; ë¶; 붐; ) HANGUL SYLLABLE BUM
+BD91;BD91;1107 116E 11B8;BD91;1107 116E 11B8; # (붑; 붑; 붑; 붑; 붑; ) HANGUL SYLLABLE BUB
+BD92;BD92;1107 116E 11B9;BD92;1107 116E 11B9; # (붒; 붒; 붒; 붒; 붒; ) HANGUL SYLLABLE BUBS
+BD93;BD93;1107 116E 11BA;BD93;1107 116E 11BA; # (붓; 붓; 붓; 붓; 붓; ) HANGUL SYLLABLE BUS
+BD94;BD94;1107 116E 11BB;BD94;1107 116E 11BB; # (붔; 붔; 붔; 붔; 붔; ) HANGUL SYLLABLE BUSS
+BD95;BD95;1107 116E 11BC;BD95;1107 116E 11BC; # (붕; 붕; 붕; 붕; 붕; ) HANGUL SYLLABLE BUNG
+BD96;BD96;1107 116E 11BD;BD96;1107 116E 11BD; # (붖; 붖; 붖; 붖; 붖; ) HANGUL SYLLABLE BUJ
+BD97;BD97;1107 116E 11BE;BD97;1107 116E 11BE; # (붗; 붗; 붗; 붗; 붗; ) HANGUL SYLLABLE BUC
+BD98;BD98;1107 116E 11BF;BD98;1107 116E 11BF; # (붘; 붘; 붘; 붘; 붘; ) HANGUL SYLLABLE BUK
+BD99;BD99;1107 116E 11C0;BD99;1107 116E 11C0; # (붙; 붙; 붙; 붙; 붙; ) HANGUL SYLLABLE BUT
+BD9A;BD9A;1107 116E 11C1;BD9A;1107 116E 11C1; # (붚; 붚; 부á‡; 붚; 부á‡; ) HANGUL SYLLABLE BUP
+BD9B;BD9B;1107 116E 11C2;BD9B;1107 116E 11C2; # (붛; 붛; 붛; 붛; 붛; ) HANGUL SYLLABLE BUH
+BD9C;BD9C;1107 116F;BD9C;1107 116F; # (붜; 붜; 붜; 붜; 붜; ) HANGUL SYLLABLE BWEO
+BD9D;BD9D;1107 116F 11A8;BD9D;1107 116F 11A8; # (ë¶; ë¶; 붝; ë¶; 붝; ) HANGUL SYLLABLE BWEOG
+BD9E;BD9E;1107 116F 11A9;BD9E;1107 116F 11A9; # (붞; 붞; 붞; 붞; 붞; ) HANGUL SYLLABLE BWEOGG
+BD9F;BD9F;1107 116F 11AA;BD9F;1107 116F 11AA; # (붟; 붟; 붟; 붟; 붟; ) HANGUL SYLLABLE BWEOGS
+BDA0;BDA0;1107 116F 11AB;BDA0;1107 116F 11AB; # (붠; 붠; 붠; 붠; 붠; ) HANGUL SYLLABLE BWEON
+BDA1;BDA1;1107 116F 11AC;BDA1;1107 116F 11AC; # (붡; 붡; 붡; 붡; 붡; ) HANGUL SYLLABLE BWEONJ
+BDA2;BDA2;1107 116F 11AD;BDA2;1107 116F 11AD; # (붢; 붢; 붢; 붢; 붢; ) HANGUL SYLLABLE BWEONH
+BDA3;BDA3;1107 116F 11AE;BDA3;1107 116F 11AE; # (붣; 붣; 붣; 붣; 붣; ) HANGUL SYLLABLE BWEOD
+BDA4;BDA4;1107 116F 11AF;BDA4;1107 116F 11AF; # (붤; 붤; 붤; 붤; 붤; ) HANGUL SYLLABLE BWEOL
+BDA5;BDA5;1107 116F 11B0;BDA5;1107 116F 11B0; # (붥; 붥; 붥; 붥; 붥; ) HANGUL SYLLABLE BWEOLG
+BDA6;BDA6;1107 116F 11B1;BDA6;1107 116F 11B1; # (붦; 붦; 붦; 붦; 붦; ) HANGUL SYLLABLE BWEOLM
+BDA7;BDA7;1107 116F 11B2;BDA7;1107 116F 11B2; # (붧; 붧; 붧; 붧; 붧; ) HANGUL SYLLABLE BWEOLB
+BDA8;BDA8;1107 116F 11B3;BDA8;1107 116F 11B3; # (붨; 붨; 붨; 붨; 붨; ) HANGUL SYLLABLE BWEOLS
+BDA9;BDA9;1107 116F 11B4;BDA9;1107 116F 11B4; # (붩; 붩; 붩; 붩; 붩; ) HANGUL SYLLABLE BWEOLT
+BDAA;BDAA;1107 116F 11B5;BDAA;1107 116F 11B5; # (붪; 붪; 붪; 붪; 붪; ) HANGUL SYLLABLE BWEOLP
+BDAB;BDAB;1107 116F 11B6;BDAB;1107 116F 11B6; # (붫; 붫; 붫; 붫; 붫; ) HANGUL SYLLABLE BWEOLH
+BDAC;BDAC;1107 116F 11B7;BDAC;1107 116F 11B7; # (붬; 붬; 붬; 붬; 붬; ) HANGUL SYLLABLE BWEOM
+BDAD;BDAD;1107 116F 11B8;BDAD;1107 116F 11B8; # (붭; 붭; 붭; 붭; 붭; ) HANGUL SYLLABLE BWEOB
+BDAE;BDAE;1107 116F 11B9;BDAE;1107 116F 11B9; # (붮; 붮; 붮; 붮; 붮; ) HANGUL SYLLABLE BWEOBS
+BDAF;BDAF;1107 116F 11BA;BDAF;1107 116F 11BA; # (붯; 붯; 붯; 붯; 붯; ) HANGUL SYLLABLE BWEOS
+BDB0;BDB0;1107 116F 11BB;BDB0;1107 116F 11BB; # (붰; 붰; 붰; 붰; 붰; ) HANGUL SYLLABLE BWEOSS
+BDB1;BDB1;1107 116F 11BC;BDB1;1107 116F 11BC; # (붱; 붱; 붱; 붱; 붱; ) HANGUL SYLLABLE BWEONG
+BDB2;BDB2;1107 116F 11BD;BDB2;1107 116F 11BD; # (붲; 붲; 붲; 붲; 붲; ) HANGUL SYLLABLE BWEOJ
+BDB3;BDB3;1107 116F 11BE;BDB3;1107 116F 11BE; # (붳; 붳; 붳; 붳; 붳; ) HANGUL SYLLABLE BWEOC
+BDB4;BDB4;1107 116F 11BF;BDB4;1107 116F 11BF; # (붴; 붴; 붴; 붴; 붴; ) HANGUL SYLLABLE BWEOK
+BDB5;BDB5;1107 116F 11C0;BDB5;1107 116F 11C0; # (붵; 붵; 붵; 붵; 붵; ) HANGUL SYLLABLE BWEOT
+BDB6;BDB6;1107 116F 11C1;BDB6;1107 116F 11C1; # (붶; 붶; 붜á‡; 붶; 붜á‡; ) HANGUL SYLLABLE BWEOP
+BDB7;BDB7;1107 116F 11C2;BDB7;1107 116F 11C2; # (붷; 붷; 붷; 붷; 붷; ) HANGUL SYLLABLE BWEOH
+BDB8;BDB8;1107 1170;BDB8;1107 1170; # (붸; 붸; 붸; 붸; 붸; ) HANGUL SYLLABLE BWE
+BDB9;BDB9;1107 1170 11A8;BDB9;1107 1170 11A8; # (붹; 붹; 붹; 붹; 붹; ) HANGUL SYLLABLE BWEG
+BDBA;BDBA;1107 1170 11A9;BDBA;1107 1170 11A9; # (붺; 붺; 붺; 붺; 붺; ) HANGUL SYLLABLE BWEGG
+BDBB;BDBB;1107 1170 11AA;BDBB;1107 1170 11AA; # (붻; 붻; 붻; 붻; 붻; ) HANGUL SYLLABLE BWEGS
+BDBC;BDBC;1107 1170 11AB;BDBC;1107 1170 11AB; # (붼; 붼; 붼; 붼; 붼; ) HANGUL SYLLABLE BWEN
+BDBD;BDBD;1107 1170 11AC;BDBD;1107 1170 11AC; # (붽; 붽; 붽; 붽; 붽; ) HANGUL SYLLABLE BWENJ
+BDBE;BDBE;1107 1170 11AD;BDBE;1107 1170 11AD; # (붾; 붾; 붾; 붾; 붾; ) HANGUL SYLLABLE BWENH
+BDBF;BDBF;1107 1170 11AE;BDBF;1107 1170 11AE; # (붿; 붿; 붿; 붿; 붿; ) HANGUL SYLLABLE BWED
+BDC0;BDC0;1107 1170 11AF;BDC0;1107 1170 11AF; # (뷀; 뷀; 뷀; 뷀; 뷀; ) HANGUL SYLLABLE BWEL
+BDC1;BDC1;1107 1170 11B0;BDC1;1107 1170 11B0; # (ë·; ë·; 뷁; ë·; 뷁; ) HANGUL SYLLABLE BWELG
+BDC2;BDC2;1107 1170 11B1;BDC2;1107 1170 11B1; # (뷂; 뷂; 뷂; 뷂; 뷂; ) HANGUL SYLLABLE BWELM
+BDC3;BDC3;1107 1170 11B2;BDC3;1107 1170 11B2; # (뷃; 뷃; 뷃; 뷃; 뷃; ) HANGUL SYLLABLE BWELB
+BDC4;BDC4;1107 1170 11B3;BDC4;1107 1170 11B3; # (뷄; 뷄; 뷄; 뷄; 뷄; ) HANGUL SYLLABLE BWELS
+BDC5;BDC5;1107 1170 11B4;BDC5;1107 1170 11B4; # (뷅; 뷅; 뷅; 뷅; 뷅; ) HANGUL SYLLABLE BWELT
+BDC6;BDC6;1107 1170 11B5;BDC6;1107 1170 11B5; # (뷆; 뷆; 뷆; 뷆; 뷆; ) HANGUL SYLLABLE BWELP
+BDC7;BDC7;1107 1170 11B6;BDC7;1107 1170 11B6; # (뷇; 뷇; 뷇; 뷇; 뷇; ) HANGUL SYLLABLE BWELH
+BDC8;BDC8;1107 1170 11B7;BDC8;1107 1170 11B7; # (뷈; 뷈; 뷈; 뷈; 뷈; ) HANGUL SYLLABLE BWEM
+BDC9;BDC9;1107 1170 11B8;BDC9;1107 1170 11B8; # (뷉; 뷉; 뷉; 뷉; 뷉; ) HANGUL SYLLABLE BWEB
+BDCA;BDCA;1107 1170 11B9;BDCA;1107 1170 11B9; # (뷊; 뷊; 뷊; 뷊; 뷊; ) HANGUL SYLLABLE BWEBS
+BDCB;BDCB;1107 1170 11BA;BDCB;1107 1170 11BA; # (뷋; 뷋; 뷋; 뷋; 뷋; ) HANGUL SYLLABLE BWES
+BDCC;BDCC;1107 1170 11BB;BDCC;1107 1170 11BB; # (뷌; 뷌; 뷌; 뷌; 뷌; ) HANGUL SYLLABLE BWESS
+BDCD;BDCD;1107 1170 11BC;BDCD;1107 1170 11BC; # (ë·; ë·; 뷍; ë·; 뷍; ) HANGUL SYLLABLE BWENG
+BDCE;BDCE;1107 1170 11BD;BDCE;1107 1170 11BD; # (뷎; 뷎; 뷎; 뷎; 뷎; ) HANGUL SYLLABLE BWEJ
+BDCF;BDCF;1107 1170 11BE;BDCF;1107 1170 11BE; # (ë·; ë·; 뷏; ë·; 뷏; ) HANGUL SYLLABLE BWEC
+BDD0;BDD0;1107 1170 11BF;BDD0;1107 1170 11BF; # (ë·; ë·; 뷐; ë·; 뷐; ) HANGUL SYLLABLE BWEK
+BDD1;BDD1;1107 1170 11C0;BDD1;1107 1170 11C0; # (뷑; 뷑; 뷑; 뷑; 뷑; ) HANGUL SYLLABLE BWET
+BDD2;BDD2;1107 1170 11C1;BDD2;1107 1170 11C1; # (ë·’; ë·’; 붸á‡; ë·’; 붸á‡; ) HANGUL SYLLABLE BWEP
+BDD3;BDD3;1107 1170 11C2;BDD3;1107 1170 11C2; # (뷓; 뷓; 뷓; 뷓; 뷓; ) HANGUL SYLLABLE BWEH
+BDD4;BDD4;1107 1171;BDD4;1107 1171; # (뷔; 뷔; 뷔; 뷔; 뷔; ) HANGUL SYLLABLE BWI
+BDD5;BDD5;1107 1171 11A8;BDD5;1107 1171 11A8; # (뷕; 뷕; 뷕; 뷕; 뷕; ) HANGUL SYLLABLE BWIG
+BDD6;BDD6;1107 1171 11A9;BDD6;1107 1171 11A9; # (뷖; 뷖; 뷖; 뷖; 뷖; ) HANGUL SYLLABLE BWIGG
+BDD7;BDD7;1107 1171 11AA;BDD7;1107 1171 11AA; # (뷗; 뷗; 뷗; 뷗; 뷗; ) HANGUL SYLLABLE BWIGS
+BDD8;BDD8;1107 1171 11AB;BDD8;1107 1171 11AB; # (뷘; 뷘; 뷘; 뷘; 뷘; ) HANGUL SYLLABLE BWIN
+BDD9;BDD9;1107 1171 11AC;BDD9;1107 1171 11AC; # (뷙; 뷙; 뷙; 뷙; 뷙; ) HANGUL SYLLABLE BWINJ
+BDDA;BDDA;1107 1171 11AD;BDDA;1107 1171 11AD; # (뷚; 뷚; 뷚; 뷚; 뷚; ) HANGUL SYLLABLE BWINH
+BDDB;BDDB;1107 1171 11AE;BDDB;1107 1171 11AE; # (뷛; 뷛; 뷛; 뷛; 뷛; ) HANGUL SYLLABLE BWID
+BDDC;BDDC;1107 1171 11AF;BDDC;1107 1171 11AF; # (뷜; 뷜; 뷜; 뷜; 뷜; ) HANGUL SYLLABLE BWIL
+BDDD;BDDD;1107 1171 11B0;BDDD;1107 1171 11B0; # (ë·; ë·; 뷝; ë·; 뷝; ) HANGUL SYLLABLE BWILG
+BDDE;BDDE;1107 1171 11B1;BDDE;1107 1171 11B1; # (뷞; 뷞; 뷞; 뷞; 뷞; ) HANGUL SYLLABLE BWILM
+BDDF;BDDF;1107 1171 11B2;BDDF;1107 1171 11B2; # (뷟; 뷟; 뷟; 뷟; 뷟; ) HANGUL SYLLABLE BWILB
+BDE0;BDE0;1107 1171 11B3;BDE0;1107 1171 11B3; # (뷠; 뷠; 뷠; 뷠; 뷠; ) HANGUL SYLLABLE BWILS
+BDE1;BDE1;1107 1171 11B4;BDE1;1107 1171 11B4; # (뷡; 뷡; 뷡; 뷡; 뷡; ) HANGUL SYLLABLE BWILT
+BDE2;BDE2;1107 1171 11B5;BDE2;1107 1171 11B5; # (뷢; 뷢; 뷢; 뷢; 뷢; ) HANGUL SYLLABLE BWILP
+BDE3;BDE3;1107 1171 11B6;BDE3;1107 1171 11B6; # (뷣; 뷣; 뷣; 뷣; 뷣; ) HANGUL SYLLABLE BWILH
+BDE4;BDE4;1107 1171 11B7;BDE4;1107 1171 11B7; # (뷤; 뷤; 뷤; 뷤; 뷤; ) HANGUL SYLLABLE BWIM
+BDE5;BDE5;1107 1171 11B8;BDE5;1107 1171 11B8; # (뷥; 뷥; 뷥; 뷥; 뷥; ) HANGUL SYLLABLE BWIB
+BDE6;BDE6;1107 1171 11B9;BDE6;1107 1171 11B9; # (뷦; 뷦; 뷦; 뷦; 뷦; ) HANGUL SYLLABLE BWIBS
+BDE7;BDE7;1107 1171 11BA;BDE7;1107 1171 11BA; # (뷧; 뷧; 뷧; 뷧; 뷧; ) HANGUL SYLLABLE BWIS
+BDE8;BDE8;1107 1171 11BB;BDE8;1107 1171 11BB; # (뷨; 뷨; 뷨; 뷨; 뷨; ) HANGUL SYLLABLE BWISS
+BDE9;BDE9;1107 1171 11BC;BDE9;1107 1171 11BC; # (뷩; 뷩; 뷩; 뷩; 뷩; ) HANGUL SYLLABLE BWING
+BDEA;BDEA;1107 1171 11BD;BDEA;1107 1171 11BD; # (뷪; 뷪; 뷪; 뷪; 뷪; ) HANGUL SYLLABLE BWIJ
+BDEB;BDEB;1107 1171 11BE;BDEB;1107 1171 11BE; # (뷫; 뷫; 뷫; 뷫; 뷫; ) HANGUL SYLLABLE BWIC
+BDEC;BDEC;1107 1171 11BF;BDEC;1107 1171 11BF; # (뷬; 뷬; 뷬; 뷬; 뷬; ) HANGUL SYLLABLE BWIK
+BDED;BDED;1107 1171 11C0;BDED;1107 1171 11C0; # (뷭; 뷭; 뷭; 뷭; 뷭; ) HANGUL SYLLABLE BWIT
+BDEE;BDEE;1107 1171 11C1;BDEE;1107 1171 11C1; # (ë·®; ë·®; 뷔á‡; ë·®; 뷔á‡; ) HANGUL SYLLABLE BWIP
+BDEF;BDEF;1107 1171 11C2;BDEF;1107 1171 11C2; # (뷯; 뷯; 뷯; 뷯; 뷯; ) HANGUL SYLLABLE BWIH
+BDF0;BDF0;1107 1172;BDF0;1107 1172; # (뷰; 뷰; 뷰; 뷰; 뷰; ) HANGUL SYLLABLE BYU
+BDF1;BDF1;1107 1172 11A8;BDF1;1107 1172 11A8; # (뷱; 뷱; 뷱; 뷱; 뷱; ) HANGUL SYLLABLE BYUG
+BDF2;BDF2;1107 1172 11A9;BDF2;1107 1172 11A9; # (뷲; 뷲; 뷲; 뷲; 뷲; ) HANGUL SYLLABLE BYUGG
+BDF3;BDF3;1107 1172 11AA;BDF3;1107 1172 11AA; # (뷳; 뷳; 뷳; 뷳; 뷳; ) HANGUL SYLLABLE BYUGS
+BDF4;BDF4;1107 1172 11AB;BDF4;1107 1172 11AB; # (뷴; 뷴; 뷴; 뷴; 뷴; ) HANGUL SYLLABLE BYUN
+BDF5;BDF5;1107 1172 11AC;BDF5;1107 1172 11AC; # (뷵; 뷵; 뷵; 뷵; 뷵; ) HANGUL SYLLABLE BYUNJ
+BDF6;BDF6;1107 1172 11AD;BDF6;1107 1172 11AD; # (뷶; 뷶; 뷶; 뷶; 뷶; ) HANGUL SYLLABLE BYUNH
+BDF7;BDF7;1107 1172 11AE;BDF7;1107 1172 11AE; # (뷷; 뷷; 뷷; 뷷; 뷷; ) HANGUL SYLLABLE BYUD
+BDF8;BDF8;1107 1172 11AF;BDF8;1107 1172 11AF; # (뷸; 뷸; 뷸; 뷸; 뷸; ) HANGUL SYLLABLE BYUL
+BDF9;BDF9;1107 1172 11B0;BDF9;1107 1172 11B0; # (뷹; 뷹; 뷹; 뷹; 뷹; ) HANGUL SYLLABLE BYULG
+BDFA;BDFA;1107 1172 11B1;BDFA;1107 1172 11B1; # (뷺; 뷺; 뷺; 뷺; 뷺; ) HANGUL SYLLABLE BYULM
+BDFB;BDFB;1107 1172 11B2;BDFB;1107 1172 11B2; # (뷻; 뷻; 뷻; 뷻; 뷻; ) HANGUL SYLLABLE BYULB
+BDFC;BDFC;1107 1172 11B3;BDFC;1107 1172 11B3; # (뷼; 뷼; 뷼; 뷼; 뷼; ) HANGUL SYLLABLE BYULS
+BDFD;BDFD;1107 1172 11B4;BDFD;1107 1172 11B4; # (뷽; 뷽; 뷽; 뷽; 뷽; ) HANGUL SYLLABLE BYULT
+BDFE;BDFE;1107 1172 11B5;BDFE;1107 1172 11B5; # (뷾; 뷾; 뷾; 뷾; 뷾; ) HANGUL SYLLABLE BYULP
+BDFF;BDFF;1107 1172 11B6;BDFF;1107 1172 11B6; # (뷿; 뷿; 뷿; 뷿; 뷿; ) HANGUL SYLLABLE BYULH
+BE00;BE00;1107 1172 11B7;BE00;1107 1172 11B7; # (븀; 븀; 븀; 븀; 븀; ) HANGUL SYLLABLE BYUM
+BE01;BE01;1107 1172 11B8;BE01;1107 1172 11B8; # (ë¸; ë¸; 븁; ë¸; 븁; ) HANGUL SYLLABLE BYUB
+BE02;BE02;1107 1172 11B9;BE02;1107 1172 11B9; # (븂; 븂; 븂; 븂; 븂; ) HANGUL SYLLABLE BYUBS
+BE03;BE03;1107 1172 11BA;BE03;1107 1172 11BA; # (븃; 븃; 븃; 븃; 븃; ) HANGUL SYLLABLE BYUS
+BE04;BE04;1107 1172 11BB;BE04;1107 1172 11BB; # (븄; 븄; 븄; 븄; 븄; ) HANGUL SYLLABLE BYUSS
+BE05;BE05;1107 1172 11BC;BE05;1107 1172 11BC; # (븅; 븅; 븅; 븅; 븅; ) HANGUL SYLLABLE BYUNG
+BE06;BE06;1107 1172 11BD;BE06;1107 1172 11BD; # (븆; 븆; 븆; 븆; 븆; ) HANGUL SYLLABLE BYUJ
+BE07;BE07;1107 1172 11BE;BE07;1107 1172 11BE; # (븇; 븇; 븇; 븇; 븇; ) HANGUL SYLLABLE BYUC
+BE08;BE08;1107 1172 11BF;BE08;1107 1172 11BF; # (븈; 븈; 븈; 븈; 븈; ) HANGUL SYLLABLE BYUK
+BE09;BE09;1107 1172 11C0;BE09;1107 1172 11C0; # (븉; 븉; 븉; 븉; 븉; ) HANGUL SYLLABLE BYUT
+BE0A;BE0A;1107 1172 11C1;BE0A;1107 1172 11C1; # (븊; 븊; 뷰á‡; 븊; 뷰á‡; ) HANGUL SYLLABLE BYUP
+BE0B;BE0B;1107 1172 11C2;BE0B;1107 1172 11C2; # (븋; 븋; 븋; 븋; 븋; ) HANGUL SYLLABLE BYUH
+BE0C;BE0C;1107 1173;BE0C;1107 1173; # (브; 브; 브; 브; 브; ) HANGUL SYLLABLE BEU
+BE0D;BE0D;1107 1173 11A8;BE0D;1107 1173 11A8; # (ë¸; ë¸; 븍; ë¸; 븍; ) HANGUL SYLLABLE BEUG
+BE0E;BE0E;1107 1173 11A9;BE0E;1107 1173 11A9; # (븎; 븎; 븎; 븎; 븎; ) HANGUL SYLLABLE BEUGG
+BE0F;BE0F;1107 1173 11AA;BE0F;1107 1173 11AA; # (ë¸; ë¸; 븏; ë¸; 븏; ) HANGUL SYLLABLE BEUGS
+BE10;BE10;1107 1173 11AB;BE10;1107 1173 11AB; # (ë¸; ë¸; 븐; ë¸; 븐; ) HANGUL SYLLABLE BEUN
+BE11;BE11;1107 1173 11AC;BE11;1107 1173 11AC; # (븑; 븑; 븑; 븑; 븑; ) HANGUL SYLLABLE BEUNJ
+BE12;BE12;1107 1173 11AD;BE12;1107 1173 11AD; # (븒; 븒; 븒; 븒; 븒; ) HANGUL SYLLABLE BEUNH
+BE13;BE13;1107 1173 11AE;BE13;1107 1173 11AE; # (븓; 븓; 븓; 븓; 븓; ) HANGUL SYLLABLE BEUD
+BE14;BE14;1107 1173 11AF;BE14;1107 1173 11AF; # (블; 블; 블; 블; 블; ) HANGUL SYLLABLE BEUL
+BE15;BE15;1107 1173 11B0;BE15;1107 1173 11B0; # (븕; 븕; 븕; 븕; 븕; ) HANGUL SYLLABLE BEULG
+BE16;BE16;1107 1173 11B1;BE16;1107 1173 11B1; # (븖; 븖; 븖; 븖; 븖; ) HANGUL SYLLABLE BEULM
+BE17;BE17;1107 1173 11B2;BE17;1107 1173 11B2; # (븗; 븗; 븗; 븗; 븗; ) HANGUL SYLLABLE BEULB
+BE18;BE18;1107 1173 11B3;BE18;1107 1173 11B3; # (븘; 븘; 븘; 븘; 븘; ) HANGUL SYLLABLE BEULS
+BE19;BE19;1107 1173 11B4;BE19;1107 1173 11B4; # (븙; 븙; 븙; 븙; 븙; ) HANGUL SYLLABLE BEULT
+BE1A;BE1A;1107 1173 11B5;BE1A;1107 1173 11B5; # (븚; 븚; 븚; 븚; 븚; ) HANGUL SYLLABLE BEULP
+BE1B;BE1B;1107 1173 11B6;BE1B;1107 1173 11B6; # (븛; 븛; 븛; 븛; 븛; ) HANGUL SYLLABLE BEULH
+BE1C;BE1C;1107 1173 11B7;BE1C;1107 1173 11B7; # (븜; 븜; 븜; 븜; 븜; ) HANGUL SYLLABLE BEUM
+BE1D;BE1D;1107 1173 11B8;BE1D;1107 1173 11B8; # (ë¸; ë¸; 븝; ë¸; 븝; ) HANGUL SYLLABLE BEUB
+BE1E;BE1E;1107 1173 11B9;BE1E;1107 1173 11B9; # (븞; 븞; 븞; 븞; 븞; ) HANGUL SYLLABLE BEUBS
+BE1F;BE1F;1107 1173 11BA;BE1F;1107 1173 11BA; # (븟; 븟; 븟; 븟; 븟; ) HANGUL SYLLABLE BEUS
+BE20;BE20;1107 1173 11BB;BE20;1107 1173 11BB; # (븠; 븠; 븠; 븠; 븠; ) HANGUL SYLLABLE BEUSS
+BE21;BE21;1107 1173 11BC;BE21;1107 1173 11BC; # (븡; 븡; 븡; 븡; 븡; ) HANGUL SYLLABLE BEUNG
+BE22;BE22;1107 1173 11BD;BE22;1107 1173 11BD; # (븢; 븢; 븢; 븢; 븢; ) HANGUL SYLLABLE BEUJ
+BE23;BE23;1107 1173 11BE;BE23;1107 1173 11BE; # (븣; 븣; 븣; 븣; 븣; ) HANGUL SYLLABLE BEUC
+BE24;BE24;1107 1173 11BF;BE24;1107 1173 11BF; # (븤; 븤; 븤; 븤; 븤; ) HANGUL SYLLABLE BEUK
+BE25;BE25;1107 1173 11C0;BE25;1107 1173 11C0; # (븥; 븥; 븥; 븥; 븥; ) HANGUL SYLLABLE BEUT
+BE26;BE26;1107 1173 11C1;BE26;1107 1173 11C1; # (븦; 븦; 브á‡; 븦; 브á‡; ) HANGUL SYLLABLE BEUP
+BE27;BE27;1107 1173 11C2;BE27;1107 1173 11C2; # (븧; 븧; 븧; 븧; 븧; ) HANGUL SYLLABLE BEUH
+BE28;BE28;1107 1174;BE28;1107 1174; # (븨; 븨; 븨; 븨; 븨; ) HANGUL SYLLABLE BYI
+BE29;BE29;1107 1174 11A8;BE29;1107 1174 11A8; # (븩; 븩; 븩; 븩; 븩; ) HANGUL SYLLABLE BYIG
+BE2A;BE2A;1107 1174 11A9;BE2A;1107 1174 11A9; # (븪; 븪; 븪; 븪; 븪; ) HANGUL SYLLABLE BYIGG
+BE2B;BE2B;1107 1174 11AA;BE2B;1107 1174 11AA; # (븫; 븫; 븫; 븫; 븫; ) HANGUL SYLLABLE BYIGS
+BE2C;BE2C;1107 1174 11AB;BE2C;1107 1174 11AB; # (븬; 븬; 븬; 븬; 븬; ) HANGUL SYLLABLE BYIN
+BE2D;BE2D;1107 1174 11AC;BE2D;1107 1174 11AC; # (븭; 븭; 븭; 븭; 븭; ) HANGUL SYLLABLE BYINJ
+BE2E;BE2E;1107 1174 11AD;BE2E;1107 1174 11AD; # (븮; 븮; 븮; 븮; 븮; ) HANGUL SYLLABLE BYINH
+BE2F;BE2F;1107 1174 11AE;BE2F;1107 1174 11AE; # (븯; 븯; 븯; 븯; 븯; ) HANGUL SYLLABLE BYID
+BE30;BE30;1107 1174 11AF;BE30;1107 1174 11AF; # (븰; 븰; 븰; 븰; 븰; ) HANGUL SYLLABLE BYIL
+BE31;BE31;1107 1174 11B0;BE31;1107 1174 11B0; # (븱; 븱; 븱; 븱; 븱; ) HANGUL SYLLABLE BYILG
+BE32;BE32;1107 1174 11B1;BE32;1107 1174 11B1; # (븲; 븲; 븲; 븲; 븲; ) HANGUL SYLLABLE BYILM
+BE33;BE33;1107 1174 11B2;BE33;1107 1174 11B2; # (븳; 븳; 븳; 븳; 븳; ) HANGUL SYLLABLE BYILB
+BE34;BE34;1107 1174 11B3;BE34;1107 1174 11B3; # (븴; 븴; 븴; 븴; 븴; ) HANGUL SYLLABLE BYILS
+BE35;BE35;1107 1174 11B4;BE35;1107 1174 11B4; # (븵; 븵; 븵; 븵; 븵; ) HANGUL SYLLABLE BYILT
+BE36;BE36;1107 1174 11B5;BE36;1107 1174 11B5; # (븶; 븶; 븶; 븶; 븶; ) HANGUL SYLLABLE BYILP
+BE37;BE37;1107 1174 11B6;BE37;1107 1174 11B6; # (븷; 븷; 븷; 븷; 븷; ) HANGUL SYLLABLE BYILH
+BE38;BE38;1107 1174 11B7;BE38;1107 1174 11B7; # (븸; 븸; 븸; 븸; 븸; ) HANGUL SYLLABLE BYIM
+BE39;BE39;1107 1174 11B8;BE39;1107 1174 11B8; # (븹; 븹; 븹; 븹; 븹; ) HANGUL SYLLABLE BYIB
+BE3A;BE3A;1107 1174 11B9;BE3A;1107 1174 11B9; # (븺; 븺; 븺; 븺; 븺; ) HANGUL SYLLABLE BYIBS
+BE3B;BE3B;1107 1174 11BA;BE3B;1107 1174 11BA; # (븻; 븻; 븻; 븻; 븻; ) HANGUL SYLLABLE BYIS
+BE3C;BE3C;1107 1174 11BB;BE3C;1107 1174 11BB; # (븼; 븼; 븼; 븼; 븼; ) HANGUL SYLLABLE BYISS
+BE3D;BE3D;1107 1174 11BC;BE3D;1107 1174 11BC; # (븽; 븽; 븽; 븽; 븽; ) HANGUL SYLLABLE BYING
+BE3E;BE3E;1107 1174 11BD;BE3E;1107 1174 11BD; # (븾; 븾; 븾; 븾; 븾; ) HANGUL SYLLABLE BYIJ
+BE3F;BE3F;1107 1174 11BE;BE3F;1107 1174 11BE; # (븿; 븿; 븿; 븿; 븿; ) HANGUL SYLLABLE BYIC
+BE40;BE40;1107 1174 11BF;BE40;1107 1174 11BF; # (빀; 빀; 빀; 빀; 빀; ) HANGUL SYLLABLE BYIK
+BE41;BE41;1107 1174 11C0;BE41;1107 1174 11C0; # (ë¹; ë¹; 빁; ë¹; 빁; ) HANGUL SYLLABLE BYIT
+BE42;BE42;1107 1174 11C1;BE42;1107 1174 11C1; # (빂; 빂; 븨á‡; 빂; 븨á‡; ) HANGUL SYLLABLE BYIP
+BE43;BE43;1107 1174 11C2;BE43;1107 1174 11C2; # (빃; 빃; 빃; 빃; 빃; ) HANGUL SYLLABLE BYIH
+BE44;BE44;1107 1175;BE44;1107 1175; # (비; 비; 비; 비; 비; ) HANGUL SYLLABLE BI
+BE45;BE45;1107 1175 11A8;BE45;1107 1175 11A8; # (빅; 빅; 빅; 빅; 빅; ) HANGUL SYLLABLE BIG
+BE46;BE46;1107 1175 11A9;BE46;1107 1175 11A9; # (빆; 빆; 빆; 빆; 빆; ) HANGUL SYLLABLE BIGG
+BE47;BE47;1107 1175 11AA;BE47;1107 1175 11AA; # (빇; 빇; 빇; 빇; 빇; ) HANGUL SYLLABLE BIGS
+BE48;BE48;1107 1175 11AB;BE48;1107 1175 11AB; # (빈; 빈; 빈; 빈; 빈; ) HANGUL SYLLABLE BIN
+BE49;BE49;1107 1175 11AC;BE49;1107 1175 11AC; # (빉; 빉; 빉; 빉; 빉; ) HANGUL SYLLABLE BINJ
+BE4A;BE4A;1107 1175 11AD;BE4A;1107 1175 11AD; # (빊; 빊; 빊; 빊; 빊; ) HANGUL SYLLABLE BINH
+BE4B;BE4B;1107 1175 11AE;BE4B;1107 1175 11AE; # (빋; 빋; 빋; 빋; 빋; ) HANGUL SYLLABLE BID
+BE4C;BE4C;1107 1175 11AF;BE4C;1107 1175 11AF; # (빌; 빌; 빌; 빌; 빌; ) HANGUL SYLLABLE BIL
+BE4D;BE4D;1107 1175 11B0;BE4D;1107 1175 11B0; # (ë¹; ë¹; 빍; ë¹; 빍; ) HANGUL SYLLABLE BILG
+BE4E;BE4E;1107 1175 11B1;BE4E;1107 1175 11B1; # (빎; 빎; 빎; 빎; 빎; ) HANGUL SYLLABLE BILM
+BE4F;BE4F;1107 1175 11B2;BE4F;1107 1175 11B2; # (ë¹; ë¹; 빏; ë¹; 빏; ) HANGUL SYLLABLE BILB
+BE50;BE50;1107 1175 11B3;BE50;1107 1175 11B3; # (ë¹; ë¹; 빐; ë¹; 빐; ) HANGUL SYLLABLE BILS
+BE51;BE51;1107 1175 11B4;BE51;1107 1175 11B4; # (빑; 빑; 빑; 빑; 빑; ) HANGUL SYLLABLE BILT
+BE52;BE52;1107 1175 11B5;BE52;1107 1175 11B5; # (빒; 빒; 빒; 빒; 빒; ) HANGUL SYLLABLE BILP
+BE53;BE53;1107 1175 11B6;BE53;1107 1175 11B6; # (빓; 빓; 빓; 빓; 빓; ) HANGUL SYLLABLE BILH
+BE54;BE54;1107 1175 11B7;BE54;1107 1175 11B7; # (빔; 빔; 빔; 빔; 빔; ) HANGUL SYLLABLE BIM
+BE55;BE55;1107 1175 11B8;BE55;1107 1175 11B8; # (빕; 빕; 빕; 빕; 빕; ) HANGUL SYLLABLE BIB
+BE56;BE56;1107 1175 11B9;BE56;1107 1175 11B9; # (빖; 빖; 빖; 빖; 빖; ) HANGUL SYLLABLE BIBS
+BE57;BE57;1107 1175 11BA;BE57;1107 1175 11BA; # (빗; 빗; 빗; 빗; 빗; ) HANGUL SYLLABLE BIS
+BE58;BE58;1107 1175 11BB;BE58;1107 1175 11BB; # (빘; 빘; 빘; 빘; 빘; ) HANGUL SYLLABLE BISS
+BE59;BE59;1107 1175 11BC;BE59;1107 1175 11BC; # (빙; 빙; 빙; 빙; 빙; ) HANGUL SYLLABLE BING
+BE5A;BE5A;1107 1175 11BD;BE5A;1107 1175 11BD; # (빚; 빚; 빚; 빚; 빚; ) HANGUL SYLLABLE BIJ
+BE5B;BE5B;1107 1175 11BE;BE5B;1107 1175 11BE; # (빛; 빛; 빛; 빛; 빛; ) HANGUL SYLLABLE BIC
+BE5C;BE5C;1107 1175 11BF;BE5C;1107 1175 11BF; # (빜; 빜; 빜; 빜; 빜; ) HANGUL SYLLABLE BIK
+BE5D;BE5D;1107 1175 11C0;BE5D;1107 1175 11C0; # (ë¹; ë¹; 빝; ë¹; 빝; ) HANGUL SYLLABLE BIT
+BE5E;BE5E;1107 1175 11C1;BE5E;1107 1175 11C1; # (빞; 빞; 비á‡; 빞; 비á‡; ) HANGUL SYLLABLE BIP
+BE5F;BE5F;1107 1175 11C2;BE5F;1107 1175 11C2; # (빟; 빟; 빟; 빟; 빟; ) HANGUL SYLLABLE BIH
+BE60;BE60;1108 1161;BE60;1108 1161; # (빠; 빠; 빠; 빠; 빠; ) HANGUL SYLLABLE BBA
+BE61;BE61;1108 1161 11A8;BE61;1108 1161 11A8; # (빡; 빡; 빡; 빡; 빡; ) HANGUL SYLLABLE BBAG
+BE62;BE62;1108 1161 11A9;BE62;1108 1161 11A9; # (빢; 빢; 빢; 빢; 빢; ) HANGUL SYLLABLE BBAGG
+BE63;BE63;1108 1161 11AA;BE63;1108 1161 11AA; # (빣; 빣; 빣; 빣; 빣; ) HANGUL SYLLABLE BBAGS
+BE64;BE64;1108 1161 11AB;BE64;1108 1161 11AB; # (빤; 빤; 빤; 빤; 빤; ) HANGUL SYLLABLE BBAN
+BE65;BE65;1108 1161 11AC;BE65;1108 1161 11AC; # (빥; 빥; 빥; 빥; 빥; ) HANGUL SYLLABLE BBANJ
+BE66;BE66;1108 1161 11AD;BE66;1108 1161 11AD; # (빦; 빦; 빦; 빦; 빦; ) HANGUL SYLLABLE BBANH
+BE67;BE67;1108 1161 11AE;BE67;1108 1161 11AE; # (빧; 빧; 빧; 빧; 빧; ) HANGUL SYLLABLE BBAD
+BE68;BE68;1108 1161 11AF;BE68;1108 1161 11AF; # (빨; 빨; 빨; 빨; 빨; ) HANGUL SYLLABLE BBAL
+BE69;BE69;1108 1161 11B0;BE69;1108 1161 11B0; # (빩; 빩; 빩; 빩; 빩; ) HANGUL SYLLABLE BBALG
+BE6A;BE6A;1108 1161 11B1;BE6A;1108 1161 11B1; # (빪; 빪; 빪; 빪; 빪; ) HANGUL SYLLABLE BBALM
+BE6B;BE6B;1108 1161 11B2;BE6B;1108 1161 11B2; # (빫; 빫; 빫; 빫; 빫; ) HANGUL SYLLABLE BBALB
+BE6C;BE6C;1108 1161 11B3;BE6C;1108 1161 11B3; # (빬; 빬; 빬; 빬; 빬; ) HANGUL SYLLABLE BBALS
+BE6D;BE6D;1108 1161 11B4;BE6D;1108 1161 11B4; # (빭; 빭; 빭; 빭; 빭; ) HANGUL SYLLABLE BBALT
+BE6E;BE6E;1108 1161 11B5;BE6E;1108 1161 11B5; # (빮; 빮; 빮; 빮; 빮; ) HANGUL SYLLABLE BBALP
+BE6F;BE6F;1108 1161 11B6;BE6F;1108 1161 11B6; # (빯; 빯; 빯; 빯; 빯; ) HANGUL SYLLABLE BBALH
+BE70;BE70;1108 1161 11B7;BE70;1108 1161 11B7; # (빰; 빰; 빰; 빰; 빰; ) HANGUL SYLLABLE BBAM
+BE71;BE71;1108 1161 11B8;BE71;1108 1161 11B8; # (빱; 빱; 빱; 빱; 빱; ) HANGUL SYLLABLE BBAB
+BE72;BE72;1108 1161 11B9;BE72;1108 1161 11B9; # (빲; 빲; 빲; 빲; 빲; ) HANGUL SYLLABLE BBABS
+BE73;BE73;1108 1161 11BA;BE73;1108 1161 11BA; # (빳; 빳; 빳; 빳; 빳; ) HANGUL SYLLABLE BBAS
+BE74;BE74;1108 1161 11BB;BE74;1108 1161 11BB; # (빴; 빴; 빴; 빴; 빴; ) HANGUL SYLLABLE BBASS
+BE75;BE75;1108 1161 11BC;BE75;1108 1161 11BC; # (빵; 빵; 빵; 빵; 빵; ) HANGUL SYLLABLE BBANG
+BE76;BE76;1108 1161 11BD;BE76;1108 1161 11BD; # (빶; 빶; 빶; 빶; 빶; ) HANGUL SYLLABLE BBAJ
+BE77;BE77;1108 1161 11BE;BE77;1108 1161 11BE; # (빷; 빷; 빷; 빷; 빷; ) HANGUL SYLLABLE BBAC
+BE78;BE78;1108 1161 11BF;BE78;1108 1161 11BF; # (빸; 빸; 빸; 빸; 빸; ) HANGUL SYLLABLE BBAK
+BE79;BE79;1108 1161 11C0;BE79;1108 1161 11C0; # (빹; 빹; 빹; 빹; 빹; ) HANGUL SYLLABLE BBAT
+BE7A;BE7A;1108 1161 11C1;BE7A;1108 1161 11C1; # (빺; 빺; 빠á‡; 빺; 빠á‡; ) HANGUL SYLLABLE BBAP
+BE7B;BE7B;1108 1161 11C2;BE7B;1108 1161 11C2; # (빻; 빻; 빻; 빻; 빻; ) HANGUL SYLLABLE BBAH
+BE7C;BE7C;1108 1162;BE7C;1108 1162; # (빼; 빼; 빼; 빼; 빼; ) HANGUL SYLLABLE BBAE
+BE7D;BE7D;1108 1162 11A8;BE7D;1108 1162 11A8; # (빽; 빽; 빽; 빽; 빽; ) HANGUL SYLLABLE BBAEG
+BE7E;BE7E;1108 1162 11A9;BE7E;1108 1162 11A9; # (빾; 빾; 빾; 빾; 빾; ) HANGUL SYLLABLE BBAEGG
+BE7F;BE7F;1108 1162 11AA;BE7F;1108 1162 11AA; # (빿; 빿; 빿; 빿; 빿; ) HANGUL SYLLABLE BBAEGS
+BE80;BE80;1108 1162 11AB;BE80;1108 1162 11AB; # (뺀; 뺀; 뺀; 뺀; 뺀; ) HANGUL SYLLABLE BBAEN
+BE81;BE81;1108 1162 11AC;BE81;1108 1162 11AC; # (ëº; ëº; 뺁; ëº; 뺁; ) HANGUL SYLLABLE BBAENJ
+BE82;BE82;1108 1162 11AD;BE82;1108 1162 11AD; # (뺂; 뺂; 뺂; 뺂; 뺂; ) HANGUL SYLLABLE BBAENH
+BE83;BE83;1108 1162 11AE;BE83;1108 1162 11AE; # (뺃; 뺃; 뺃; 뺃; 뺃; ) HANGUL SYLLABLE BBAED
+BE84;BE84;1108 1162 11AF;BE84;1108 1162 11AF; # (뺄; 뺄; 뺄; 뺄; 뺄; ) HANGUL SYLLABLE BBAEL
+BE85;BE85;1108 1162 11B0;BE85;1108 1162 11B0; # (뺅; 뺅; 뺅; 뺅; 뺅; ) HANGUL SYLLABLE BBAELG
+BE86;BE86;1108 1162 11B1;BE86;1108 1162 11B1; # (뺆; 뺆; 뺆; 뺆; 뺆; ) HANGUL SYLLABLE BBAELM
+BE87;BE87;1108 1162 11B2;BE87;1108 1162 11B2; # (뺇; 뺇; 뺇; 뺇; 뺇; ) HANGUL SYLLABLE BBAELB
+BE88;BE88;1108 1162 11B3;BE88;1108 1162 11B3; # (뺈; 뺈; 뺈; 뺈; 뺈; ) HANGUL SYLLABLE BBAELS
+BE89;BE89;1108 1162 11B4;BE89;1108 1162 11B4; # (뺉; 뺉; 뺉; 뺉; 뺉; ) HANGUL SYLLABLE BBAELT
+BE8A;BE8A;1108 1162 11B5;BE8A;1108 1162 11B5; # (뺊; 뺊; 뺊; 뺊; 뺊; ) HANGUL SYLLABLE BBAELP
+BE8B;BE8B;1108 1162 11B6;BE8B;1108 1162 11B6; # (뺋; 뺋; 뺋; 뺋; 뺋; ) HANGUL SYLLABLE BBAELH
+BE8C;BE8C;1108 1162 11B7;BE8C;1108 1162 11B7; # (뺌; 뺌; 뺌; 뺌; 뺌; ) HANGUL SYLLABLE BBAEM
+BE8D;BE8D;1108 1162 11B8;BE8D;1108 1162 11B8; # (ëº; ëº; 뺍; ëº; 뺍; ) HANGUL SYLLABLE BBAEB
+BE8E;BE8E;1108 1162 11B9;BE8E;1108 1162 11B9; # (뺎; 뺎; 뺎; 뺎; 뺎; ) HANGUL SYLLABLE BBAEBS
+BE8F;BE8F;1108 1162 11BA;BE8F;1108 1162 11BA; # (ëº; ëº; 뺏; ëº; 뺏; ) HANGUL SYLLABLE BBAES
+BE90;BE90;1108 1162 11BB;BE90;1108 1162 11BB; # (ëº; ëº; 뺐; ëº; 뺐; ) HANGUL SYLLABLE BBAESS
+BE91;BE91;1108 1162 11BC;BE91;1108 1162 11BC; # (뺑; 뺑; 뺑; 뺑; 뺑; ) HANGUL SYLLABLE BBAENG
+BE92;BE92;1108 1162 11BD;BE92;1108 1162 11BD; # (뺒; 뺒; 뺒; 뺒; 뺒; ) HANGUL SYLLABLE BBAEJ
+BE93;BE93;1108 1162 11BE;BE93;1108 1162 11BE; # (뺓; 뺓; 뺓; 뺓; 뺓; ) HANGUL SYLLABLE BBAEC
+BE94;BE94;1108 1162 11BF;BE94;1108 1162 11BF; # (뺔; 뺔; 뺔; 뺔; 뺔; ) HANGUL SYLLABLE BBAEK
+BE95;BE95;1108 1162 11C0;BE95;1108 1162 11C0; # (뺕; 뺕; 뺕; 뺕; 뺕; ) HANGUL SYLLABLE BBAET
+BE96;BE96;1108 1162 11C1;BE96;1108 1162 11C1; # (뺖; 뺖; 빼á‡; 뺖; 빼á‡; ) HANGUL SYLLABLE BBAEP
+BE97;BE97;1108 1162 11C2;BE97;1108 1162 11C2; # (뺗; 뺗; 뺗; 뺗; 뺗; ) HANGUL SYLLABLE BBAEH
+BE98;BE98;1108 1163;BE98;1108 1163; # (뺘; 뺘; 뺘; 뺘; 뺘; ) HANGUL SYLLABLE BBYA
+BE99;BE99;1108 1163 11A8;BE99;1108 1163 11A8; # (뺙; 뺙; 뺙; 뺙; 뺙; ) HANGUL SYLLABLE BBYAG
+BE9A;BE9A;1108 1163 11A9;BE9A;1108 1163 11A9; # (뺚; 뺚; 뺚; 뺚; 뺚; ) HANGUL SYLLABLE BBYAGG
+BE9B;BE9B;1108 1163 11AA;BE9B;1108 1163 11AA; # (뺛; 뺛; 뺛; 뺛; 뺛; ) HANGUL SYLLABLE BBYAGS
+BE9C;BE9C;1108 1163 11AB;BE9C;1108 1163 11AB; # (뺜; 뺜; 뺜; 뺜; 뺜; ) HANGUL SYLLABLE BBYAN
+BE9D;BE9D;1108 1163 11AC;BE9D;1108 1163 11AC; # (ëº; ëº; 뺝; ëº; 뺝; ) HANGUL SYLLABLE BBYANJ
+BE9E;BE9E;1108 1163 11AD;BE9E;1108 1163 11AD; # (뺞; 뺞; 뺞; 뺞; 뺞; ) HANGUL SYLLABLE BBYANH
+BE9F;BE9F;1108 1163 11AE;BE9F;1108 1163 11AE; # (뺟; 뺟; 뺟; 뺟; 뺟; ) HANGUL SYLLABLE BBYAD
+BEA0;BEA0;1108 1163 11AF;BEA0;1108 1163 11AF; # (뺠; 뺠; 뺠; 뺠; 뺠; ) HANGUL SYLLABLE BBYAL
+BEA1;BEA1;1108 1163 11B0;BEA1;1108 1163 11B0; # (뺡; 뺡; 뺡; 뺡; 뺡; ) HANGUL SYLLABLE BBYALG
+BEA2;BEA2;1108 1163 11B1;BEA2;1108 1163 11B1; # (뺢; 뺢; 뺢; 뺢; 뺢; ) HANGUL SYLLABLE BBYALM
+BEA3;BEA3;1108 1163 11B2;BEA3;1108 1163 11B2; # (뺣; 뺣; 뺣; 뺣; 뺣; ) HANGUL SYLLABLE BBYALB
+BEA4;BEA4;1108 1163 11B3;BEA4;1108 1163 11B3; # (뺤; 뺤; 뺤; 뺤; 뺤; ) HANGUL SYLLABLE BBYALS
+BEA5;BEA5;1108 1163 11B4;BEA5;1108 1163 11B4; # (뺥; 뺥; 뺥; 뺥; 뺥; ) HANGUL SYLLABLE BBYALT
+BEA6;BEA6;1108 1163 11B5;BEA6;1108 1163 11B5; # (뺦; 뺦; 뺦; 뺦; 뺦; ) HANGUL SYLLABLE BBYALP
+BEA7;BEA7;1108 1163 11B6;BEA7;1108 1163 11B6; # (뺧; 뺧; 뺧; 뺧; 뺧; ) HANGUL SYLLABLE BBYALH
+BEA8;BEA8;1108 1163 11B7;BEA8;1108 1163 11B7; # (뺨; 뺨; 뺨; 뺨; 뺨; ) HANGUL SYLLABLE BBYAM
+BEA9;BEA9;1108 1163 11B8;BEA9;1108 1163 11B8; # (뺩; 뺩; 뺩; 뺩; 뺩; ) HANGUL SYLLABLE BBYAB
+BEAA;BEAA;1108 1163 11B9;BEAA;1108 1163 11B9; # (뺪; 뺪; 뺪; 뺪; 뺪; ) HANGUL SYLLABLE BBYABS
+BEAB;BEAB;1108 1163 11BA;BEAB;1108 1163 11BA; # (뺫; 뺫; 뺫; 뺫; 뺫; ) HANGUL SYLLABLE BBYAS
+BEAC;BEAC;1108 1163 11BB;BEAC;1108 1163 11BB; # (뺬; 뺬; 뺬; 뺬; 뺬; ) HANGUL SYLLABLE BBYASS
+BEAD;BEAD;1108 1163 11BC;BEAD;1108 1163 11BC; # (뺭; 뺭; 뺭; 뺭; 뺭; ) HANGUL SYLLABLE BBYANG
+BEAE;BEAE;1108 1163 11BD;BEAE;1108 1163 11BD; # (뺮; 뺮; 뺮; 뺮; 뺮; ) HANGUL SYLLABLE BBYAJ
+BEAF;BEAF;1108 1163 11BE;BEAF;1108 1163 11BE; # (뺯; 뺯; 뺯; 뺯; 뺯; ) HANGUL SYLLABLE BBYAC
+BEB0;BEB0;1108 1163 11BF;BEB0;1108 1163 11BF; # (뺰; 뺰; 뺰; 뺰; 뺰; ) HANGUL SYLLABLE BBYAK
+BEB1;BEB1;1108 1163 11C0;BEB1;1108 1163 11C0; # (뺱; 뺱; 뺱; 뺱; 뺱; ) HANGUL SYLLABLE BBYAT
+BEB2;BEB2;1108 1163 11C1;BEB2;1108 1163 11C1; # (뺲; 뺲; 뺘á‡; 뺲; 뺘á‡; ) HANGUL SYLLABLE BBYAP
+BEB3;BEB3;1108 1163 11C2;BEB3;1108 1163 11C2; # (뺳; 뺳; 뺳; 뺳; 뺳; ) HANGUL SYLLABLE BBYAH
+BEB4;BEB4;1108 1164;BEB4;1108 1164; # (뺴; 뺴; 뺴; 뺴; 뺴; ) HANGUL SYLLABLE BBYAE
+BEB5;BEB5;1108 1164 11A8;BEB5;1108 1164 11A8; # (뺵; 뺵; 뺵; 뺵; 뺵; ) HANGUL SYLLABLE BBYAEG
+BEB6;BEB6;1108 1164 11A9;BEB6;1108 1164 11A9; # (뺶; 뺶; 뺶; 뺶; 뺶; ) HANGUL SYLLABLE BBYAEGG
+BEB7;BEB7;1108 1164 11AA;BEB7;1108 1164 11AA; # (뺷; 뺷; 뺷; 뺷; 뺷; ) HANGUL SYLLABLE BBYAEGS
+BEB8;BEB8;1108 1164 11AB;BEB8;1108 1164 11AB; # (뺸; 뺸; 뺸; 뺸; 뺸; ) HANGUL SYLLABLE BBYAEN
+BEB9;BEB9;1108 1164 11AC;BEB9;1108 1164 11AC; # (뺹; 뺹; 뺹; 뺹; 뺹; ) HANGUL SYLLABLE BBYAENJ
+BEBA;BEBA;1108 1164 11AD;BEBA;1108 1164 11AD; # (뺺; 뺺; 뺺; 뺺; 뺺; ) HANGUL SYLLABLE BBYAENH
+BEBB;BEBB;1108 1164 11AE;BEBB;1108 1164 11AE; # (뺻; 뺻; 뺻; 뺻; 뺻; ) HANGUL SYLLABLE BBYAED
+BEBC;BEBC;1108 1164 11AF;BEBC;1108 1164 11AF; # (뺼; 뺼; 뺼; 뺼; 뺼; ) HANGUL SYLLABLE BBYAEL
+BEBD;BEBD;1108 1164 11B0;BEBD;1108 1164 11B0; # (뺽; 뺽; 뺽; 뺽; 뺽; ) HANGUL SYLLABLE BBYAELG
+BEBE;BEBE;1108 1164 11B1;BEBE;1108 1164 11B1; # (뺾; 뺾; 뺾; 뺾; 뺾; ) HANGUL SYLLABLE BBYAELM
+BEBF;BEBF;1108 1164 11B2;BEBF;1108 1164 11B2; # (뺿; 뺿; 뺿; 뺿; 뺿; ) HANGUL SYLLABLE BBYAELB
+BEC0;BEC0;1108 1164 11B3;BEC0;1108 1164 11B3; # (뻀; 뻀; 뻀; 뻀; 뻀; ) HANGUL SYLLABLE BBYAELS
+BEC1;BEC1;1108 1164 11B4;BEC1;1108 1164 11B4; # (ë»; ë»; 뻁; ë»; 뻁; ) HANGUL SYLLABLE BBYAELT
+BEC2;BEC2;1108 1164 11B5;BEC2;1108 1164 11B5; # (뻂; 뻂; 뻂; 뻂; 뻂; ) HANGUL SYLLABLE BBYAELP
+BEC3;BEC3;1108 1164 11B6;BEC3;1108 1164 11B6; # (뻃; 뻃; 뻃; 뻃; 뻃; ) HANGUL SYLLABLE BBYAELH
+BEC4;BEC4;1108 1164 11B7;BEC4;1108 1164 11B7; # (뻄; 뻄; 뻄; 뻄; 뻄; ) HANGUL SYLLABLE BBYAEM
+BEC5;BEC5;1108 1164 11B8;BEC5;1108 1164 11B8; # (뻅; 뻅; 뻅; 뻅; 뻅; ) HANGUL SYLLABLE BBYAEB
+BEC6;BEC6;1108 1164 11B9;BEC6;1108 1164 11B9; # (뻆; 뻆; 뻆; 뻆; 뻆; ) HANGUL SYLLABLE BBYAEBS
+BEC7;BEC7;1108 1164 11BA;BEC7;1108 1164 11BA; # (뻇; 뻇; 뻇; 뻇; 뻇; ) HANGUL SYLLABLE BBYAES
+BEC8;BEC8;1108 1164 11BB;BEC8;1108 1164 11BB; # (뻈; 뻈; 뻈; 뻈; 뻈; ) HANGUL SYLLABLE BBYAESS
+BEC9;BEC9;1108 1164 11BC;BEC9;1108 1164 11BC; # (뻉; 뻉; 뻉; 뻉; 뻉; ) HANGUL SYLLABLE BBYAENG
+BECA;BECA;1108 1164 11BD;BECA;1108 1164 11BD; # (뻊; 뻊; 뻊; 뻊; 뻊; ) HANGUL SYLLABLE BBYAEJ
+BECB;BECB;1108 1164 11BE;BECB;1108 1164 11BE; # (뻋; 뻋; 뻋; 뻋; 뻋; ) HANGUL SYLLABLE BBYAEC
+BECC;BECC;1108 1164 11BF;BECC;1108 1164 11BF; # (뻌; 뻌; 뻌; 뻌; 뻌; ) HANGUL SYLLABLE BBYAEK
+BECD;BECD;1108 1164 11C0;BECD;1108 1164 11C0; # (ë»; ë»; 뻍; ë»; 뻍; ) HANGUL SYLLABLE BBYAET
+BECE;BECE;1108 1164 11C1;BECE;1108 1164 11C1; # (뻎; 뻎; 뺴á‡; 뻎; 뺴á‡; ) HANGUL SYLLABLE BBYAEP
+BECF;BECF;1108 1164 11C2;BECF;1108 1164 11C2; # (ë»; ë»; 뻏; ë»; 뻏; ) HANGUL SYLLABLE BBYAEH
+BED0;BED0;1108 1165;BED0;1108 1165; # (ë»; ë»; 뻐; ë»; 뻐; ) HANGUL SYLLABLE BBEO
+BED1;BED1;1108 1165 11A8;BED1;1108 1165 11A8; # (뻑; 뻑; 뻑; 뻑; 뻑; ) HANGUL SYLLABLE BBEOG
+BED2;BED2;1108 1165 11A9;BED2;1108 1165 11A9; # (뻒; 뻒; 뻒; 뻒; 뻒; ) HANGUL SYLLABLE BBEOGG
+BED3;BED3;1108 1165 11AA;BED3;1108 1165 11AA; # (뻓; 뻓; 뻓; 뻓; 뻓; ) HANGUL SYLLABLE BBEOGS
+BED4;BED4;1108 1165 11AB;BED4;1108 1165 11AB; # (뻔; 뻔; 뻔; 뻔; 뻔; ) HANGUL SYLLABLE BBEON
+BED5;BED5;1108 1165 11AC;BED5;1108 1165 11AC; # (뻕; 뻕; 뻕; 뻕; 뻕; ) HANGUL SYLLABLE BBEONJ
+BED6;BED6;1108 1165 11AD;BED6;1108 1165 11AD; # (뻖; 뻖; 뻖; 뻖; 뻖; ) HANGUL SYLLABLE BBEONH
+BED7;BED7;1108 1165 11AE;BED7;1108 1165 11AE; # (뻗; 뻗; 뻗; 뻗; 뻗; ) HANGUL SYLLABLE BBEOD
+BED8;BED8;1108 1165 11AF;BED8;1108 1165 11AF; # (뻘; 뻘; 뻘; 뻘; 뻘; ) HANGUL SYLLABLE BBEOL
+BED9;BED9;1108 1165 11B0;BED9;1108 1165 11B0; # (뻙; 뻙; 뻙; 뻙; 뻙; ) HANGUL SYLLABLE BBEOLG
+BEDA;BEDA;1108 1165 11B1;BEDA;1108 1165 11B1; # (뻚; 뻚; 뻚; 뻚; 뻚; ) HANGUL SYLLABLE BBEOLM
+BEDB;BEDB;1108 1165 11B2;BEDB;1108 1165 11B2; # (뻛; 뻛; 뻛; 뻛; 뻛; ) HANGUL SYLLABLE BBEOLB
+BEDC;BEDC;1108 1165 11B3;BEDC;1108 1165 11B3; # (뻜; 뻜; 뻜; 뻜; 뻜; ) HANGUL SYLLABLE BBEOLS
+BEDD;BEDD;1108 1165 11B4;BEDD;1108 1165 11B4; # (ë»; ë»; 뻝; ë»; 뻝; ) HANGUL SYLLABLE BBEOLT
+BEDE;BEDE;1108 1165 11B5;BEDE;1108 1165 11B5; # (뻞; 뻞; 뻞; 뻞; 뻞; ) HANGUL SYLLABLE BBEOLP
+BEDF;BEDF;1108 1165 11B6;BEDF;1108 1165 11B6; # (뻟; 뻟; 뻟; 뻟; 뻟; ) HANGUL SYLLABLE BBEOLH
+BEE0;BEE0;1108 1165 11B7;BEE0;1108 1165 11B7; # (뻠; 뻠; 뻠; 뻠; 뻠; ) HANGUL SYLLABLE BBEOM
+BEE1;BEE1;1108 1165 11B8;BEE1;1108 1165 11B8; # (뻡; 뻡; 뻡; 뻡; 뻡; ) HANGUL SYLLABLE BBEOB
+BEE2;BEE2;1108 1165 11B9;BEE2;1108 1165 11B9; # (뻢; 뻢; 뻢; 뻢; 뻢; ) HANGUL SYLLABLE BBEOBS
+BEE3;BEE3;1108 1165 11BA;BEE3;1108 1165 11BA; # (뻣; 뻣; 뻣; 뻣; 뻣; ) HANGUL SYLLABLE BBEOS
+BEE4;BEE4;1108 1165 11BB;BEE4;1108 1165 11BB; # (뻤; 뻤; 뻤; 뻤; 뻤; ) HANGUL SYLLABLE BBEOSS
+BEE5;BEE5;1108 1165 11BC;BEE5;1108 1165 11BC; # (뻥; 뻥; 뻥; 뻥; 뻥; ) HANGUL SYLLABLE BBEONG
+BEE6;BEE6;1108 1165 11BD;BEE6;1108 1165 11BD; # (뻦; 뻦; 뻦; 뻦; 뻦; ) HANGUL SYLLABLE BBEOJ
+BEE7;BEE7;1108 1165 11BE;BEE7;1108 1165 11BE; # (뻧; 뻧; 뻧; 뻧; 뻧; ) HANGUL SYLLABLE BBEOC
+BEE8;BEE8;1108 1165 11BF;BEE8;1108 1165 11BF; # (뻨; 뻨; 뻨; 뻨; 뻨; ) HANGUL SYLLABLE BBEOK
+BEE9;BEE9;1108 1165 11C0;BEE9;1108 1165 11C0; # (뻩; 뻩; 뻩; 뻩; 뻩; ) HANGUL SYLLABLE BBEOT
+BEEA;BEEA;1108 1165 11C1;BEEA;1108 1165 11C1; # (뻪; 뻪; 뻐á‡; 뻪; 뻐á‡; ) HANGUL SYLLABLE BBEOP
+BEEB;BEEB;1108 1165 11C2;BEEB;1108 1165 11C2; # (뻫; 뻫; 뻫; 뻫; 뻫; ) HANGUL SYLLABLE BBEOH
+BEEC;BEEC;1108 1166;BEEC;1108 1166; # (뻬; 뻬; 뻬; 뻬; 뻬; ) HANGUL SYLLABLE BBE
+BEED;BEED;1108 1166 11A8;BEED;1108 1166 11A8; # (뻭; 뻭; 뻭; 뻭; 뻭; ) HANGUL SYLLABLE BBEG
+BEEE;BEEE;1108 1166 11A9;BEEE;1108 1166 11A9; # (뻮; 뻮; 뻮; 뻮; 뻮; ) HANGUL SYLLABLE BBEGG
+BEEF;BEEF;1108 1166 11AA;BEEF;1108 1166 11AA; # (뻯; 뻯; 뻯; 뻯; 뻯; ) HANGUL SYLLABLE BBEGS
+BEF0;BEF0;1108 1166 11AB;BEF0;1108 1166 11AB; # (뻰; 뻰; 뻰; 뻰; 뻰; ) HANGUL SYLLABLE BBEN
+BEF1;BEF1;1108 1166 11AC;BEF1;1108 1166 11AC; # (뻱; 뻱; 뻱; 뻱; 뻱; ) HANGUL SYLLABLE BBENJ
+BEF2;BEF2;1108 1166 11AD;BEF2;1108 1166 11AD; # (뻲; 뻲; 뻲; 뻲; 뻲; ) HANGUL SYLLABLE BBENH
+BEF3;BEF3;1108 1166 11AE;BEF3;1108 1166 11AE; # (뻳; 뻳; 뻳; 뻳; 뻳; ) HANGUL SYLLABLE BBED
+BEF4;BEF4;1108 1166 11AF;BEF4;1108 1166 11AF; # (뻴; 뻴; 뻴; 뻴; 뻴; ) HANGUL SYLLABLE BBEL
+BEF5;BEF5;1108 1166 11B0;BEF5;1108 1166 11B0; # (뻵; 뻵; 뻵; 뻵; 뻵; ) HANGUL SYLLABLE BBELG
+BEF6;BEF6;1108 1166 11B1;BEF6;1108 1166 11B1; # (뻶; 뻶; 뻶; 뻶; 뻶; ) HANGUL SYLLABLE BBELM
+BEF7;BEF7;1108 1166 11B2;BEF7;1108 1166 11B2; # (뻷; 뻷; 뻷; 뻷; 뻷; ) HANGUL SYLLABLE BBELB
+BEF8;BEF8;1108 1166 11B3;BEF8;1108 1166 11B3; # (뻸; 뻸; 뻸; 뻸; 뻸; ) HANGUL SYLLABLE BBELS
+BEF9;BEF9;1108 1166 11B4;BEF9;1108 1166 11B4; # (뻹; 뻹; 뻹; 뻹; 뻹; ) HANGUL SYLLABLE BBELT
+BEFA;BEFA;1108 1166 11B5;BEFA;1108 1166 11B5; # (뻺; 뻺; 뻺; 뻺; 뻺; ) HANGUL SYLLABLE BBELP
+BEFB;BEFB;1108 1166 11B6;BEFB;1108 1166 11B6; # (뻻; 뻻; 뻻; 뻻; 뻻; ) HANGUL SYLLABLE BBELH
+BEFC;BEFC;1108 1166 11B7;BEFC;1108 1166 11B7; # (뻼; 뻼; 뻼; 뻼; 뻼; ) HANGUL SYLLABLE BBEM
+BEFD;BEFD;1108 1166 11B8;BEFD;1108 1166 11B8; # (뻽; 뻽; 뻽; 뻽; 뻽; ) HANGUL SYLLABLE BBEB
+BEFE;BEFE;1108 1166 11B9;BEFE;1108 1166 11B9; # (뻾; 뻾; 뻾; 뻾; 뻾; ) HANGUL SYLLABLE BBEBS
+BEFF;BEFF;1108 1166 11BA;BEFF;1108 1166 11BA; # (뻿; 뻿; 뻿; 뻿; 뻿; ) HANGUL SYLLABLE BBES
+BF00;BF00;1108 1166 11BB;BF00;1108 1166 11BB; # (뼀; 뼀; 뼀; 뼀; 뼀; ) HANGUL SYLLABLE BBESS
+BF01;BF01;1108 1166 11BC;BF01;1108 1166 11BC; # (ë¼; ë¼; 뼁; ë¼; 뼁; ) HANGUL SYLLABLE BBENG
+BF02;BF02;1108 1166 11BD;BF02;1108 1166 11BD; # (뼂; 뼂; 뼂; 뼂; 뼂; ) HANGUL SYLLABLE BBEJ
+BF03;BF03;1108 1166 11BE;BF03;1108 1166 11BE; # (뼃; 뼃; 뼃; 뼃; 뼃; ) HANGUL SYLLABLE BBEC
+BF04;BF04;1108 1166 11BF;BF04;1108 1166 11BF; # (뼄; 뼄; 뼄; 뼄; 뼄; ) HANGUL SYLLABLE BBEK
+BF05;BF05;1108 1166 11C0;BF05;1108 1166 11C0; # (뼅; 뼅; 뼅; 뼅; 뼅; ) HANGUL SYLLABLE BBET
+BF06;BF06;1108 1166 11C1;BF06;1108 1166 11C1; # (뼆; 뼆; 뻬á‡; 뼆; 뻬á‡; ) HANGUL SYLLABLE BBEP
+BF07;BF07;1108 1166 11C2;BF07;1108 1166 11C2; # (뼇; 뼇; 뼇; 뼇; 뼇; ) HANGUL SYLLABLE BBEH
+BF08;BF08;1108 1167;BF08;1108 1167; # (뼈; 뼈; 뼈; 뼈; 뼈; ) HANGUL SYLLABLE BBYEO
+BF09;BF09;1108 1167 11A8;BF09;1108 1167 11A8; # (뼉; 뼉; 뼉; 뼉; 뼉; ) HANGUL SYLLABLE BBYEOG
+BF0A;BF0A;1108 1167 11A9;BF0A;1108 1167 11A9; # (뼊; 뼊; 뼊; 뼊; 뼊; ) HANGUL SYLLABLE BBYEOGG
+BF0B;BF0B;1108 1167 11AA;BF0B;1108 1167 11AA; # (뼋; 뼋; 뼋; 뼋; 뼋; ) HANGUL SYLLABLE BBYEOGS
+BF0C;BF0C;1108 1167 11AB;BF0C;1108 1167 11AB; # (뼌; 뼌; 뼌; 뼌; 뼌; ) HANGUL SYLLABLE BBYEON
+BF0D;BF0D;1108 1167 11AC;BF0D;1108 1167 11AC; # (ë¼; ë¼; 뼍; ë¼; 뼍; ) HANGUL SYLLABLE BBYEONJ
+BF0E;BF0E;1108 1167 11AD;BF0E;1108 1167 11AD; # (뼎; 뼎; 뼎; 뼎; 뼎; ) HANGUL SYLLABLE BBYEONH
+BF0F;BF0F;1108 1167 11AE;BF0F;1108 1167 11AE; # (ë¼; ë¼; 뼏; ë¼; 뼏; ) HANGUL SYLLABLE BBYEOD
+BF10;BF10;1108 1167 11AF;BF10;1108 1167 11AF; # (ë¼; ë¼; 뼐; ë¼; 뼐; ) HANGUL SYLLABLE BBYEOL
+BF11;BF11;1108 1167 11B0;BF11;1108 1167 11B0; # (뼑; 뼑; 뼑; 뼑; 뼑; ) HANGUL SYLLABLE BBYEOLG
+BF12;BF12;1108 1167 11B1;BF12;1108 1167 11B1; # (뼒; 뼒; 뼒; 뼒; 뼒; ) HANGUL SYLLABLE BBYEOLM
+BF13;BF13;1108 1167 11B2;BF13;1108 1167 11B2; # (뼓; 뼓; 뼓; 뼓; 뼓; ) HANGUL SYLLABLE BBYEOLB
+BF14;BF14;1108 1167 11B3;BF14;1108 1167 11B3; # (뼔; 뼔; 뼔; 뼔; 뼔; ) HANGUL SYLLABLE BBYEOLS
+BF15;BF15;1108 1167 11B4;BF15;1108 1167 11B4; # (뼕; 뼕; 뼕; 뼕; 뼕; ) HANGUL SYLLABLE BBYEOLT
+BF16;BF16;1108 1167 11B5;BF16;1108 1167 11B5; # (뼖; 뼖; 뼖; 뼖; 뼖; ) HANGUL SYLLABLE BBYEOLP
+BF17;BF17;1108 1167 11B6;BF17;1108 1167 11B6; # (뼗; 뼗; 뼗; 뼗; 뼗; ) HANGUL SYLLABLE BBYEOLH
+BF18;BF18;1108 1167 11B7;BF18;1108 1167 11B7; # (뼘; 뼘; 뼘; 뼘; 뼘; ) HANGUL SYLLABLE BBYEOM
+BF19;BF19;1108 1167 11B8;BF19;1108 1167 11B8; # (뼙; 뼙; 뼙; 뼙; 뼙; ) HANGUL SYLLABLE BBYEOB
+BF1A;BF1A;1108 1167 11B9;BF1A;1108 1167 11B9; # (뼚; 뼚; 뼚; 뼚; 뼚; ) HANGUL SYLLABLE BBYEOBS
+BF1B;BF1B;1108 1167 11BA;BF1B;1108 1167 11BA; # (뼛; 뼛; 뼛; 뼛; 뼛; ) HANGUL SYLLABLE BBYEOS
+BF1C;BF1C;1108 1167 11BB;BF1C;1108 1167 11BB; # (뼜; 뼜; 뼜; 뼜; 뼜; ) HANGUL SYLLABLE BBYEOSS
+BF1D;BF1D;1108 1167 11BC;BF1D;1108 1167 11BC; # (ë¼; ë¼; 뼝; ë¼; 뼝; ) HANGUL SYLLABLE BBYEONG
+BF1E;BF1E;1108 1167 11BD;BF1E;1108 1167 11BD; # (뼞; 뼞; 뼞; 뼞; 뼞; ) HANGUL SYLLABLE BBYEOJ
+BF1F;BF1F;1108 1167 11BE;BF1F;1108 1167 11BE; # (뼟; 뼟; 뼟; 뼟; 뼟; ) HANGUL SYLLABLE BBYEOC
+BF20;BF20;1108 1167 11BF;BF20;1108 1167 11BF; # (뼠; 뼠; 뼠; 뼠; 뼠; ) HANGUL SYLLABLE BBYEOK
+BF21;BF21;1108 1167 11C0;BF21;1108 1167 11C0; # (뼡; 뼡; 뼡; 뼡; 뼡; ) HANGUL SYLLABLE BBYEOT
+BF22;BF22;1108 1167 11C1;BF22;1108 1167 11C1; # (ë¼¢; ë¼¢; 뼈á‡; ë¼¢; 뼈á‡; ) HANGUL SYLLABLE BBYEOP
+BF23;BF23;1108 1167 11C2;BF23;1108 1167 11C2; # (뼣; 뼣; 뼣; 뼣; 뼣; ) HANGUL SYLLABLE BBYEOH
+BF24;BF24;1108 1168;BF24;1108 1168; # (뼤; 뼤; 뼤; 뼤; 뼤; ) HANGUL SYLLABLE BBYE
+BF25;BF25;1108 1168 11A8;BF25;1108 1168 11A8; # (뼥; 뼥; 뼥; 뼥; 뼥; ) HANGUL SYLLABLE BBYEG
+BF26;BF26;1108 1168 11A9;BF26;1108 1168 11A9; # (뼦; 뼦; 뼦; 뼦; 뼦; ) HANGUL SYLLABLE BBYEGG
+BF27;BF27;1108 1168 11AA;BF27;1108 1168 11AA; # (뼧; 뼧; 뼧; 뼧; 뼧; ) HANGUL SYLLABLE BBYEGS
+BF28;BF28;1108 1168 11AB;BF28;1108 1168 11AB; # (뼨; 뼨; 뼨; 뼨; 뼨; ) HANGUL SYLLABLE BBYEN
+BF29;BF29;1108 1168 11AC;BF29;1108 1168 11AC; # (뼩; 뼩; 뼩; 뼩; 뼩; ) HANGUL SYLLABLE BBYENJ
+BF2A;BF2A;1108 1168 11AD;BF2A;1108 1168 11AD; # (뼪; 뼪; 뼪; 뼪; 뼪; ) HANGUL SYLLABLE BBYENH
+BF2B;BF2B;1108 1168 11AE;BF2B;1108 1168 11AE; # (뼫; 뼫; 뼫; 뼫; 뼫; ) HANGUL SYLLABLE BBYED
+BF2C;BF2C;1108 1168 11AF;BF2C;1108 1168 11AF; # (뼬; 뼬; 뼬; 뼬; 뼬; ) HANGUL SYLLABLE BBYEL
+BF2D;BF2D;1108 1168 11B0;BF2D;1108 1168 11B0; # (뼭; 뼭; 뼭; 뼭; 뼭; ) HANGUL SYLLABLE BBYELG
+BF2E;BF2E;1108 1168 11B1;BF2E;1108 1168 11B1; # (뼮; 뼮; 뼮; 뼮; 뼮; ) HANGUL SYLLABLE BBYELM
+BF2F;BF2F;1108 1168 11B2;BF2F;1108 1168 11B2; # (뼯; 뼯; 뼯; 뼯; 뼯; ) HANGUL SYLLABLE BBYELB
+BF30;BF30;1108 1168 11B3;BF30;1108 1168 11B3; # (뼰; 뼰; 뼰; 뼰; 뼰; ) HANGUL SYLLABLE BBYELS
+BF31;BF31;1108 1168 11B4;BF31;1108 1168 11B4; # (뼱; 뼱; 뼱; 뼱; 뼱; ) HANGUL SYLLABLE BBYELT
+BF32;BF32;1108 1168 11B5;BF32;1108 1168 11B5; # (뼲; 뼲; 뼲; 뼲; 뼲; ) HANGUL SYLLABLE BBYELP
+BF33;BF33;1108 1168 11B6;BF33;1108 1168 11B6; # (뼳; 뼳; 뼳; 뼳; 뼳; ) HANGUL SYLLABLE BBYELH
+BF34;BF34;1108 1168 11B7;BF34;1108 1168 11B7; # (뼴; 뼴; 뼴; 뼴; 뼴; ) HANGUL SYLLABLE BBYEM
+BF35;BF35;1108 1168 11B8;BF35;1108 1168 11B8; # (뼵; 뼵; 뼵; 뼵; 뼵; ) HANGUL SYLLABLE BBYEB
+BF36;BF36;1108 1168 11B9;BF36;1108 1168 11B9; # (뼶; 뼶; 뼶; 뼶; 뼶; ) HANGUL SYLLABLE BBYEBS
+BF37;BF37;1108 1168 11BA;BF37;1108 1168 11BA; # (뼷; 뼷; 뼷; 뼷; 뼷; ) HANGUL SYLLABLE BBYES
+BF38;BF38;1108 1168 11BB;BF38;1108 1168 11BB; # (뼸; 뼸; 뼸; 뼸; 뼸; ) HANGUL SYLLABLE BBYESS
+BF39;BF39;1108 1168 11BC;BF39;1108 1168 11BC; # (뼹; 뼹; 뼹; 뼹; 뼹; ) HANGUL SYLLABLE BBYENG
+BF3A;BF3A;1108 1168 11BD;BF3A;1108 1168 11BD; # (뼺; 뼺; 뼺; 뼺; 뼺; ) HANGUL SYLLABLE BBYEJ
+BF3B;BF3B;1108 1168 11BE;BF3B;1108 1168 11BE; # (뼻; 뼻; 뼻; 뼻; 뼻; ) HANGUL SYLLABLE BBYEC
+BF3C;BF3C;1108 1168 11BF;BF3C;1108 1168 11BF; # (뼼; 뼼; 뼼; 뼼; 뼼; ) HANGUL SYLLABLE BBYEK
+BF3D;BF3D;1108 1168 11C0;BF3D;1108 1168 11C0; # (뼽; 뼽; 뼽; 뼽; 뼽; ) HANGUL SYLLABLE BBYET
+BF3E;BF3E;1108 1168 11C1;BF3E;1108 1168 11C1; # (ë¼¾; ë¼¾; 뼤á‡; ë¼¾; 뼤á‡; ) HANGUL SYLLABLE BBYEP
+BF3F;BF3F;1108 1168 11C2;BF3F;1108 1168 11C2; # (뼿; 뼿; 뼿; 뼿; 뼿; ) HANGUL SYLLABLE BBYEH
+BF40;BF40;1108 1169;BF40;1108 1169; # (뽀; 뽀; 뽀; 뽀; 뽀; ) HANGUL SYLLABLE BBO
+BF41;BF41;1108 1169 11A8;BF41;1108 1169 11A8; # (ë½; ë½; 뽁; ë½; 뽁; ) HANGUL SYLLABLE BBOG
+BF42;BF42;1108 1169 11A9;BF42;1108 1169 11A9; # (뽂; 뽂; 뽂; 뽂; 뽂; ) HANGUL SYLLABLE BBOGG
+BF43;BF43;1108 1169 11AA;BF43;1108 1169 11AA; # (뽃; 뽃; 뽃; 뽃; 뽃; ) HANGUL SYLLABLE BBOGS
+BF44;BF44;1108 1169 11AB;BF44;1108 1169 11AB; # (뽄; 뽄; 뽄; 뽄; 뽄; ) HANGUL SYLLABLE BBON
+BF45;BF45;1108 1169 11AC;BF45;1108 1169 11AC; # (뽅; 뽅; 뽅; 뽅; 뽅; ) HANGUL SYLLABLE BBONJ
+BF46;BF46;1108 1169 11AD;BF46;1108 1169 11AD; # (뽆; 뽆; 뽆; 뽆; 뽆; ) HANGUL SYLLABLE BBONH
+BF47;BF47;1108 1169 11AE;BF47;1108 1169 11AE; # (뽇; 뽇; 뽇; 뽇; 뽇; ) HANGUL SYLLABLE BBOD
+BF48;BF48;1108 1169 11AF;BF48;1108 1169 11AF; # (뽈; 뽈; 뽈; 뽈; 뽈; ) HANGUL SYLLABLE BBOL
+BF49;BF49;1108 1169 11B0;BF49;1108 1169 11B0; # (뽉; 뽉; 뽉; 뽉; 뽉; ) HANGUL SYLLABLE BBOLG
+BF4A;BF4A;1108 1169 11B1;BF4A;1108 1169 11B1; # (뽊; 뽊; 뽊; 뽊; 뽊; ) HANGUL SYLLABLE BBOLM
+BF4B;BF4B;1108 1169 11B2;BF4B;1108 1169 11B2; # (뽋; 뽋; 뽋; 뽋; 뽋; ) HANGUL SYLLABLE BBOLB
+BF4C;BF4C;1108 1169 11B3;BF4C;1108 1169 11B3; # (뽌; 뽌; 뽌; 뽌; 뽌; ) HANGUL SYLLABLE BBOLS
+BF4D;BF4D;1108 1169 11B4;BF4D;1108 1169 11B4; # (ë½; ë½; 뽍; ë½; 뽍; ) HANGUL SYLLABLE BBOLT
+BF4E;BF4E;1108 1169 11B5;BF4E;1108 1169 11B5; # (뽎; 뽎; 뽎; 뽎; 뽎; ) HANGUL SYLLABLE BBOLP
+BF4F;BF4F;1108 1169 11B6;BF4F;1108 1169 11B6; # (ë½; ë½; 뽏; ë½; 뽏; ) HANGUL SYLLABLE BBOLH
+BF50;BF50;1108 1169 11B7;BF50;1108 1169 11B7; # (ë½; ë½; 뽐; ë½; 뽐; ) HANGUL SYLLABLE BBOM
+BF51;BF51;1108 1169 11B8;BF51;1108 1169 11B8; # (뽑; 뽑; 뽑; 뽑; 뽑; ) HANGUL SYLLABLE BBOB
+BF52;BF52;1108 1169 11B9;BF52;1108 1169 11B9; # (뽒; 뽒; 뽒; 뽒; 뽒; ) HANGUL SYLLABLE BBOBS
+BF53;BF53;1108 1169 11BA;BF53;1108 1169 11BA; # (뽓; 뽓; 뽓; 뽓; 뽓; ) HANGUL SYLLABLE BBOS
+BF54;BF54;1108 1169 11BB;BF54;1108 1169 11BB; # (뽔; 뽔; 뽔; 뽔; 뽔; ) HANGUL SYLLABLE BBOSS
+BF55;BF55;1108 1169 11BC;BF55;1108 1169 11BC; # (뽕; 뽕; 뽕; 뽕; 뽕; ) HANGUL SYLLABLE BBONG
+BF56;BF56;1108 1169 11BD;BF56;1108 1169 11BD; # (뽖; 뽖; 뽖; 뽖; 뽖; ) HANGUL SYLLABLE BBOJ
+BF57;BF57;1108 1169 11BE;BF57;1108 1169 11BE; # (뽗; 뽗; 뽗; 뽗; 뽗; ) HANGUL SYLLABLE BBOC
+BF58;BF58;1108 1169 11BF;BF58;1108 1169 11BF; # (뽘; 뽘; 뽘; 뽘; 뽘; ) HANGUL SYLLABLE BBOK
+BF59;BF59;1108 1169 11C0;BF59;1108 1169 11C0; # (뽙; 뽙; 뽙; 뽙; 뽙; ) HANGUL SYLLABLE BBOT
+BF5A;BF5A;1108 1169 11C1;BF5A;1108 1169 11C1; # (뽚; 뽚; 뽀á‡; 뽚; 뽀á‡; ) HANGUL SYLLABLE BBOP
+BF5B;BF5B;1108 1169 11C2;BF5B;1108 1169 11C2; # (뽛; 뽛; 뽛; 뽛; 뽛; ) HANGUL SYLLABLE BBOH
+BF5C;BF5C;1108 116A;BF5C;1108 116A; # (뽜; 뽜; 뽜; 뽜; 뽜; ) HANGUL SYLLABLE BBWA
+BF5D;BF5D;1108 116A 11A8;BF5D;1108 116A 11A8; # (ë½; ë½; 뽝; ë½; 뽝; ) HANGUL SYLLABLE BBWAG
+BF5E;BF5E;1108 116A 11A9;BF5E;1108 116A 11A9; # (뽞; 뽞; 뽞; 뽞; 뽞; ) HANGUL SYLLABLE BBWAGG
+BF5F;BF5F;1108 116A 11AA;BF5F;1108 116A 11AA; # (뽟; 뽟; 뽟; 뽟; 뽟; ) HANGUL SYLLABLE BBWAGS
+BF60;BF60;1108 116A 11AB;BF60;1108 116A 11AB; # (뽠; 뽠; 뽠; 뽠; 뽠; ) HANGUL SYLLABLE BBWAN
+BF61;BF61;1108 116A 11AC;BF61;1108 116A 11AC; # (뽡; 뽡; 뽡; 뽡; 뽡; ) HANGUL SYLLABLE BBWANJ
+BF62;BF62;1108 116A 11AD;BF62;1108 116A 11AD; # (뽢; 뽢; 뽢; 뽢; 뽢; ) HANGUL SYLLABLE BBWANH
+BF63;BF63;1108 116A 11AE;BF63;1108 116A 11AE; # (뽣; 뽣; 뽣; 뽣; 뽣; ) HANGUL SYLLABLE BBWAD
+BF64;BF64;1108 116A 11AF;BF64;1108 116A 11AF; # (뽤; 뽤; 뽤; 뽤; 뽤; ) HANGUL SYLLABLE BBWAL
+BF65;BF65;1108 116A 11B0;BF65;1108 116A 11B0; # (뽥; 뽥; 뽥; 뽥; 뽥; ) HANGUL SYLLABLE BBWALG
+BF66;BF66;1108 116A 11B1;BF66;1108 116A 11B1; # (뽦; 뽦; 뽦; 뽦; 뽦; ) HANGUL SYLLABLE BBWALM
+BF67;BF67;1108 116A 11B2;BF67;1108 116A 11B2; # (뽧; 뽧; 뽧; 뽧; 뽧; ) HANGUL SYLLABLE BBWALB
+BF68;BF68;1108 116A 11B3;BF68;1108 116A 11B3; # (뽨; 뽨; 뽨; 뽨; 뽨; ) HANGUL SYLLABLE BBWALS
+BF69;BF69;1108 116A 11B4;BF69;1108 116A 11B4; # (뽩; 뽩; 뽩; 뽩; 뽩; ) HANGUL SYLLABLE BBWALT
+BF6A;BF6A;1108 116A 11B5;BF6A;1108 116A 11B5; # (뽪; 뽪; 뽪; 뽪; 뽪; ) HANGUL SYLLABLE BBWALP
+BF6B;BF6B;1108 116A 11B6;BF6B;1108 116A 11B6; # (뽫; 뽫; 뽫; 뽫; 뽫; ) HANGUL SYLLABLE BBWALH
+BF6C;BF6C;1108 116A 11B7;BF6C;1108 116A 11B7; # (뽬; 뽬; 뽬; 뽬; 뽬; ) HANGUL SYLLABLE BBWAM
+BF6D;BF6D;1108 116A 11B8;BF6D;1108 116A 11B8; # (뽭; 뽭; 뽭; 뽭; 뽭; ) HANGUL SYLLABLE BBWAB
+BF6E;BF6E;1108 116A 11B9;BF6E;1108 116A 11B9; # (뽮; 뽮; 뽮; 뽮; 뽮; ) HANGUL SYLLABLE BBWABS
+BF6F;BF6F;1108 116A 11BA;BF6F;1108 116A 11BA; # (뽯; 뽯; 뽯; 뽯; 뽯; ) HANGUL SYLLABLE BBWAS
+BF70;BF70;1108 116A 11BB;BF70;1108 116A 11BB; # (뽰; 뽰; 뽰; 뽰; 뽰; ) HANGUL SYLLABLE BBWASS
+BF71;BF71;1108 116A 11BC;BF71;1108 116A 11BC; # (뽱; 뽱; 뽱; 뽱; 뽱; ) HANGUL SYLLABLE BBWANG
+BF72;BF72;1108 116A 11BD;BF72;1108 116A 11BD; # (뽲; 뽲; 뽲; 뽲; 뽲; ) HANGUL SYLLABLE BBWAJ
+BF73;BF73;1108 116A 11BE;BF73;1108 116A 11BE; # (뽳; 뽳; 뽳; 뽳; 뽳; ) HANGUL SYLLABLE BBWAC
+BF74;BF74;1108 116A 11BF;BF74;1108 116A 11BF; # (뽴; 뽴; 뽴; 뽴; 뽴; ) HANGUL SYLLABLE BBWAK
+BF75;BF75;1108 116A 11C0;BF75;1108 116A 11C0; # (뽵; 뽵; 뽵; 뽵; 뽵; ) HANGUL SYLLABLE BBWAT
+BF76;BF76;1108 116A 11C1;BF76;1108 116A 11C1; # (뽶; 뽶; 뽜á‡; 뽶; 뽜á‡; ) HANGUL SYLLABLE BBWAP
+BF77;BF77;1108 116A 11C2;BF77;1108 116A 11C2; # (뽷; 뽷; 뽷; 뽷; 뽷; ) HANGUL SYLLABLE BBWAH
+BF78;BF78;1108 116B;BF78;1108 116B; # (뽸; 뽸; 뽸; 뽸; 뽸; ) HANGUL SYLLABLE BBWAE
+BF79;BF79;1108 116B 11A8;BF79;1108 116B 11A8; # (뽹; 뽹; 뽹; 뽹; 뽹; ) HANGUL SYLLABLE BBWAEG
+BF7A;BF7A;1108 116B 11A9;BF7A;1108 116B 11A9; # (뽺; 뽺; 뽺; 뽺; 뽺; ) HANGUL SYLLABLE BBWAEGG
+BF7B;BF7B;1108 116B 11AA;BF7B;1108 116B 11AA; # (뽻; 뽻; 뽻; 뽻; 뽻; ) HANGUL SYLLABLE BBWAEGS
+BF7C;BF7C;1108 116B 11AB;BF7C;1108 116B 11AB; # (뽼; 뽼; 뽼; 뽼; 뽼; ) HANGUL SYLLABLE BBWAEN
+BF7D;BF7D;1108 116B 11AC;BF7D;1108 116B 11AC; # (뽽; 뽽; 뽽; 뽽; 뽽; ) HANGUL SYLLABLE BBWAENJ
+BF7E;BF7E;1108 116B 11AD;BF7E;1108 116B 11AD; # (뽾; 뽾; 뽾; 뽾; 뽾; ) HANGUL SYLLABLE BBWAENH
+BF7F;BF7F;1108 116B 11AE;BF7F;1108 116B 11AE; # (뽿; 뽿; 뽿; 뽿; 뽿; ) HANGUL SYLLABLE BBWAED
+BF80;BF80;1108 116B 11AF;BF80;1108 116B 11AF; # (뾀; 뾀; 뾀; 뾀; 뾀; ) HANGUL SYLLABLE BBWAEL
+BF81;BF81;1108 116B 11B0;BF81;1108 116B 11B0; # (ë¾; ë¾; 뾁; ë¾; 뾁; ) HANGUL SYLLABLE BBWAELG
+BF82;BF82;1108 116B 11B1;BF82;1108 116B 11B1; # (뾂; 뾂; 뾂; 뾂; 뾂; ) HANGUL SYLLABLE BBWAELM
+BF83;BF83;1108 116B 11B2;BF83;1108 116B 11B2; # (뾃; 뾃; 뾃; 뾃; 뾃; ) HANGUL SYLLABLE BBWAELB
+BF84;BF84;1108 116B 11B3;BF84;1108 116B 11B3; # (뾄; 뾄; 뾄; 뾄; 뾄; ) HANGUL SYLLABLE BBWAELS
+BF85;BF85;1108 116B 11B4;BF85;1108 116B 11B4; # (뾅; 뾅; 뾅; 뾅; 뾅; ) HANGUL SYLLABLE BBWAELT
+BF86;BF86;1108 116B 11B5;BF86;1108 116B 11B5; # (뾆; 뾆; 뾆; 뾆; 뾆; ) HANGUL SYLLABLE BBWAELP
+BF87;BF87;1108 116B 11B6;BF87;1108 116B 11B6; # (뾇; 뾇; 뾇; 뾇; 뾇; ) HANGUL SYLLABLE BBWAELH
+BF88;BF88;1108 116B 11B7;BF88;1108 116B 11B7; # (뾈; 뾈; 뾈; 뾈; 뾈; ) HANGUL SYLLABLE BBWAEM
+BF89;BF89;1108 116B 11B8;BF89;1108 116B 11B8; # (뾉; 뾉; 뾉; 뾉; 뾉; ) HANGUL SYLLABLE BBWAEB
+BF8A;BF8A;1108 116B 11B9;BF8A;1108 116B 11B9; # (뾊; 뾊; 뾊; 뾊; 뾊; ) HANGUL SYLLABLE BBWAEBS
+BF8B;BF8B;1108 116B 11BA;BF8B;1108 116B 11BA; # (뾋; 뾋; 뾋; 뾋; 뾋; ) HANGUL SYLLABLE BBWAES
+BF8C;BF8C;1108 116B 11BB;BF8C;1108 116B 11BB; # (뾌; 뾌; 뾌; 뾌; 뾌; ) HANGUL SYLLABLE BBWAESS
+BF8D;BF8D;1108 116B 11BC;BF8D;1108 116B 11BC; # (ë¾; ë¾; 뾍; ë¾; 뾍; ) HANGUL SYLLABLE BBWAENG
+BF8E;BF8E;1108 116B 11BD;BF8E;1108 116B 11BD; # (뾎; 뾎; 뾎; 뾎; 뾎; ) HANGUL SYLLABLE BBWAEJ
+BF8F;BF8F;1108 116B 11BE;BF8F;1108 116B 11BE; # (ë¾; ë¾; 뾏; ë¾; 뾏; ) HANGUL SYLLABLE BBWAEC
+BF90;BF90;1108 116B 11BF;BF90;1108 116B 11BF; # (ë¾; ë¾; 뾐; ë¾; 뾐; ) HANGUL SYLLABLE BBWAEK
+BF91;BF91;1108 116B 11C0;BF91;1108 116B 11C0; # (뾑; 뾑; 뾑; 뾑; 뾑; ) HANGUL SYLLABLE BBWAET
+BF92;BF92;1108 116B 11C1;BF92;1108 116B 11C1; # (ë¾’; ë¾’; 뽸á‡; ë¾’; 뽸á‡; ) HANGUL SYLLABLE BBWAEP
+BF93;BF93;1108 116B 11C2;BF93;1108 116B 11C2; # (뾓; 뾓; 뾓; 뾓; 뾓; ) HANGUL SYLLABLE BBWAEH
+BF94;BF94;1108 116C;BF94;1108 116C; # (뾔; 뾔; 뾔; 뾔; 뾔; ) HANGUL SYLLABLE BBOE
+BF95;BF95;1108 116C 11A8;BF95;1108 116C 11A8; # (뾕; 뾕; 뾕; 뾕; 뾕; ) HANGUL SYLLABLE BBOEG
+BF96;BF96;1108 116C 11A9;BF96;1108 116C 11A9; # (뾖; 뾖; 뾖; 뾖; 뾖; ) HANGUL SYLLABLE BBOEGG
+BF97;BF97;1108 116C 11AA;BF97;1108 116C 11AA; # (뾗; 뾗; 뾗; 뾗; 뾗; ) HANGUL SYLLABLE BBOEGS
+BF98;BF98;1108 116C 11AB;BF98;1108 116C 11AB; # (뾘; 뾘; 뾘; 뾘; 뾘; ) HANGUL SYLLABLE BBOEN
+BF99;BF99;1108 116C 11AC;BF99;1108 116C 11AC; # (뾙; 뾙; 뾙; 뾙; 뾙; ) HANGUL SYLLABLE BBOENJ
+BF9A;BF9A;1108 116C 11AD;BF9A;1108 116C 11AD; # (뾚; 뾚; 뾚; 뾚; 뾚; ) HANGUL SYLLABLE BBOENH
+BF9B;BF9B;1108 116C 11AE;BF9B;1108 116C 11AE; # (뾛; 뾛; 뾛; 뾛; 뾛; ) HANGUL SYLLABLE BBOED
+BF9C;BF9C;1108 116C 11AF;BF9C;1108 116C 11AF; # (뾜; 뾜; 뾜; 뾜; 뾜; ) HANGUL SYLLABLE BBOEL
+BF9D;BF9D;1108 116C 11B0;BF9D;1108 116C 11B0; # (ë¾; ë¾; 뾝; ë¾; 뾝; ) HANGUL SYLLABLE BBOELG
+BF9E;BF9E;1108 116C 11B1;BF9E;1108 116C 11B1; # (뾞; 뾞; 뾞; 뾞; 뾞; ) HANGUL SYLLABLE BBOELM
+BF9F;BF9F;1108 116C 11B2;BF9F;1108 116C 11B2; # (뾟; 뾟; 뾟; 뾟; 뾟; ) HANGUL SYLLABLE BBOELB
+BFA0;BFA0;1108 116C 11B3;BFA0;1108 116C 11B3; # (뾠; 뾠; 뾠; 뾠; 뾠; ) HANGUL SYLLABLE BBOELS
+BFA1;BFA1;1108 116C 11B4;BFA1;1108 116C 11B4; # (뾡; 뾡; 뾡; 뾡; 뾡; ) HANGUL SYLLABLE BBOELT
+BFA2;BFA2;1108 116C 11B5;BFA2;1108 116C 11B5; # (뾢; 뾢; 뾢; 뾢; 뾢; ) HANGUL SYLLABLE BBOELP
+BFA3;BFA3;1108 116C 11B6;BFA3;1108 116C 11B6; # (뾣; 뾣; 뾣; 뾣; 뾣; ) HANGUL SYLLABLE BBOELH
+BFA4;BFA4;1108 116C 11B7;BFA4;1108 116C 11B7; # (뾤; 뾤; 뾤; 뾤; 뾤; ) HANGUL SYLLABLE BBOEM
+BFA5;BFA5;1108 116C 11B8;BFA5;1108 116C 11B8; # (뾥; 뾥; 뾥; 뾥; 뾥; ) HANGUL SYLLABLE BBOEB
+BFA6;BFA6;1108 116C 11B9;BFA6;1108 116C 11B9; # (뾦; 뾦; 뾦; 뾦; 뾦; ) HANGUL SYLLABLE BBOEBS
+BFA7;BFA7;1108 116C 11BA;BFA7;1108 116C 11BA; # (뾧; 뾧; 뾧; 뾧; 뾧; ) HANGUL SYLLABLE BBOES
+BFA8;BFA8;1108 116C 11BB;BFA8;1108 116C 11BB; # (뾨; 뾨; 뾨; 뾨; 뾨; ) HANGUL SYLLABLE BBOESS
+BFA9;BFA9;1108 116C 11BC;BFA9;1108 116C 11BC; # (뾩; 뾩; 뾩; 뾩; 뾩; ) HANGUL SYLLABLE BBOENG
+BFAA;BFAA;1108 116C 11BD;BFAA;1108 116C 11BD; # (뾪; 뾪; 뾪; 뾪; 뾪; ) HANGUL SYLLABLE BBOEJ
+BFAB;BFAB;1108 116C 11BE;BFAB;1108 116C 11BE; # (뾫; 뾫; 뾫; 뾫; 뾫; ) HANGUL SYLLABLE BBOEC
+BFAC;BFAC;1108 116C 11BF;BFAC;1108 116C 11BF; # (뾬; 뾬; 뾬; 뾬; 뾬; ) HANGUL SYLLABLE BBOEK
+BFAD;BFAD;1108 116C 11C0;BFAD;1108 116C 11C0; # (뾭; 뾭; 뾭; 뾭; 뾭; ) HANGUL SYLLABLE BBOET
+BFAE;BFAE;1108 116C 11C1;BFAE;1108 116C 11C1; # (ë¾®; ë¾®; 뾔á‡; ë¾®; 뾔á‡; ) HANGUL SYLLABLE BBOEP
+BFAF;BFAF;1108 116C 11C2;BFAF;1108 116C 11C2; # (뾯; 뾯; 뾯; 뾯; 뾯; ) HANGUL SYLLABLE BBOEH
+BFB0;BFB0;1108 116D;BFB0;1108 116D; # (뾰; 뾰; 뾰; 뾰; 뾰; ) HANGUL SYLLABLE BBYO
+BFB1;BFB1;1108 116D 11A8;BFB1;1108 116D 11A8; # (뾱; 뾱; 뾱; 뾱; 뾱; ) HANGUL SYLLABLE BBYOG
+BFB2;BFB2;1108 116D 11A9;BFB2;1108 116D 11A9; # (뾲; 뾲; 뾲; 뾲; 뾲; ) HANGUL SYLLABLE BBYOGG
+BFB3;BFB3;1108 116D 11AA;BFB3;1108 116D 11AA; # (뾳; 뾳; 뾳; 뾳; 뾳; ) HANGUL SYLLABLE BBYOGS
+BFB4;BFB4;1108 116D 11AB;BFB4;1108 116D 11AB; # (뾴; 뾴; 뾴; 뾴; 뾴; ) HANGUL SYLLABLE BBYON
+BFB5;BFB5;1108 116D 11AC;BFB5;1108 116D 11AC; # (뾵; 뾵; 뾵; 뾵; 뾵; ) HANGUL SYLLABLE BBYONJ
+BFB6;BFB6;1108 116D 11AD;BFB6;1108 116D 11AD; # (뾶; 뾶; 뾶; 뾶; 뾶; ) HANGUL SYLLABLE BBYONH
+BFB7;BFB7;1108 116D 11AE;BFB7;1108 116D 11AE; # (뾷; 뾷; 뾷; 뾷; 뾷; ) HANGUL SYLLABLE BBYOD
+BFB8;BFB8;1108 116D 11AF;BFB8;1108 116D 11AF; # (뾸; 뾸; 뾸; 뾸; 뾸; ) HANGUL SYLLABLE BBYOL
+BFB9;BFB9;1108 116D 11B0;BFB9;1108 116D 11B0; # (뾹; 뾹; 뾹; 뾹; 뾹; ) HANGUL SYLLABLE BBYOLG
+BFBA;BFBA;1108 116D 11B1;BFBA;1108 116D 11B1; # (뾺; 뾺; 뾺; 뾺; 뾺; ) HANGUL SYLLABLE BBYOLM
+BFBB;BFBB;1108 116D 11B2;BFBB;1108 116D 11B2; # (뾻; 뾻; 뾻; 뾻; 뾻; ) HANGUL SYLLABLE BBYOLB
+BFBC;BFBC;1108 116D 11B3;BFBC;1108 116D 11B3; # (뾼; 뾼; 뾼; 뾼; 뾼; ) HANGUL SYLLABLE BBYOLS
+BFBD;BFBD;1108 116D 11B4;BFBD;1108 116D 11B4; # (뾽; 뾽; 뾽; 뾽; 뾽; ) HANGUL SYLLABLE BBYOLT
+BFBE;BFBE;1108 116D 11B5;BFBE;1108 116D 11B5; # (뾾; 뾾; 뾾; 뾾; 뾾; ) HANGUL SYLLABLE BBYOLP
+BFBF;BFBF;1108 116D 11B6;BFBF;1108 116D 11B6; # (뾿; 뾿; 뾿; 뾿; 뾿; ) HANGUL SYLLABLE BBYOLH
+BFC0;BFC0;1108 116D 11B7;BFC0;1108 116D 11B7; # (뿀; 뿀; 뿀; 뿀; 뿀; ) HANGUL SYLLABLE BBYOM
+BFC1;BFC1;1108 116D 11B8;BFC1;1108 116D 11B8; # (ë¿; ë¿; 뿁; ë¿; 뿁; ) HANGUL SYLLABLE BBYOB
+BFC2;BFC2;1108 116D 11B9;BFC2;1108 116D 11B9; # (뿂; 뿂; 뿂; 뿂; 뿂; ) HANGUL SYLLABLE BBYOBS
+BFC3;BFC3;1108 116D 11BA;BFC3;1108 116D 11BA; # (뿃; 뿃; 뿃; 뿃; 뿃; ) HANGUL SYLLABLE BBYOS
+BFC4;BFC4;1108 116D 11BB;BFC4;1108 116D 11BB; # (뿄; 뿄; 뿄; 뿄; 뿄; ) HANGUL SYLLABLE BBYOSS
+BFC5;BFC5;1108 116D 11BC;BFC5;1108 116D 11BC; # (뿅; 뿅; 뿅; 뿅; 뿅; ) HANGUL SYLLABLE BBYONG
+BFC6;BFC6;1108 116D 11BD;BFC6;1108 116D 11BD; # (뿆; 뿆; 뿆; 뿆; 뿆; ) HANGUL SYLLABLE BBYOJ
+BFC7;BFC7;1108 116D 11BE;BFC7;1108 116D 11BE; # (뿇; 뿇; 뿇; 뿇; 뿇; ) HANGUL SYLLABLE BBYOC
+BFC8;BFC8;1108 116D 11BF;BFC8;1108 116D 11BF; # (뿈; 뿈; 뿈; 뿈; 뿈; ) HANGUL SYLLABLE BBYOK
+BFC9;BFC9;1108 116D 11C0;BFC9;1108 116D 11C0; # (뿉; 뿉; 뿉; 뿉; 뿉; ) HANGUL SYLLABLE BBYOT
+BFCA;BFCA;1108 116D 11C1;BFCA;1108 116D 11C1; # (ë¿Š; ë¿Š; 뾰á‡; ë¿Š; 뾰á‡; ) HANGUL SYLLABLE BBYOP
+BFCB;BFCB;1108 116D 11C2;BFCB;1108 116D 11C2; # (뿋; 뿋; 뿋; 뿋; 뿋; ) HANGUL SYLLABLE BBYOH
+BFCC;BFCC;1108 116E;BFCC;1108 116E; # (뿌; 뿌; 뿌; 뿌; 뿌; ) HANGUL SYLLABLE BBU
+BFCD;BFCD;1108 116E 11A8;BFCD;1108 116E 11A8; # (ë¿; ë¿; 뿍; ë¿; 뿍; ) HANGUL SYLLABLE BBUG
+BFCE;BFCE;1108 116E 11A9;BFCE;1108 116E 11A9; # (뿎; 뿎; 뿎; 뿎; 뿎; ) HANGUL SYLLABLE BBUGG
+BFCF;BFCF;1108 116E 11AA;BFCF;1108 116E 11AA; # (ë¿; ë¿; 뿏; ë¿; 뿏; ) HANGUL SYLLABLE BBUGS
+BFD0;BFD0;1108 116E 11AB;BFD0;1108 116E 11AB; # (ë¿; ë¿; 뿐; ë¿; 뿐; ) HANGUL SYLLABLE BBUN
+BFD1;BFD1;1108 116E 11AC;BFD1;1108 116E 11AC; # (뿑; 뿑; 뿑; 뿑; 뿑; ) HANGUL SYLLABLE BBUNJ
+BFD2;BFD2;1108 116E 11AD;BFD2;1108 116E 11AD; # (뿒; 뿒; 뿒; 뿒; 뿒; ) HANGUL SYLLABLE BBUNH
+BFD3;BFD3;1108 116E 11AE;BFD3;1108 116E 11AE; # (뿓; 뿓; 뿓; 뿓; 뿓; ) HANGUL SYLLABLE BBUD
+BFD4;BFD4;1108 116E 11AF;BFD4;1108 116E 11AF; # (뿔; 뿔; 뿔; 뿔; 뿔; ) HANGUL SYLLABLE BBUL
+BFD5;BFD5;1108 116E 11B0;BFD5;1108 116E 11B0; # (뿕; 뿕; 뿕; 뿕; 뿕; ) HANGUL SYLLABLE BBULG
+BFD6;BFD6;1108 116E 11B1;BFD6;1108 116E 11B1; # (뿖; 뿖; 뿖; 뿖; 뿖; ) HANGUL SYLLABLE BBULM
+BFD7;BFD7;1108 116E 11B2;BFD7;1108 116E 11B2; # (뿗; 뿗; 뿗; 뿗; 뿗; ) HANGUL SYLLABLE BBULB
+BFD8;BFD8;1108 116E 11B3;BFD8;1108 116E 11B3; # (뿘; 뿘; 뿘; 뿘; 뿘; ) HANGUL SYLLABLE BBULS
+BFD9;BFD9;1108 116E 11B4;BFD9;1108 116E 11B4; # (뿙; 뿙; 뿙; 뿙; 뿙; ) HANGUL SYLLABLE BBULT
+BFDA;BFDA;1108 116E 11B5;BFDA;1108 116E 11B5; # (뿚; 뿚; 뿚; 뿚; 뿚; ) HANGUL SYLLABLE BBULP
+BFDB;BFDB;1108 116E 11B6;BFDB;1108 116E 11B6; # (뿛; 뿛; 뿛; 뿛; 뿛; ) HANGUL SYLLABLE BBULH
+BFDC;BFDC;1108 116E 11B7;BFDC;1108 116E 11B7; # (뿜; 뿜; 뿜; 뿜; 뿜; ) HANGUL SYLLABLE BBUM
+BFDD;BFDD;1108 116E 11B8;BFDD;1108 116E 11B8; # (ë¿; ë¿; 뿝; ë¿; 뿝; ) HANGUL SYLLABLE BBUB
+BFDE;BFDE;1108 116E 11B9;BFDE;1108 116E 11B9; # (뿞; 뿞; 뿞; 뿞; 뿞; ) HANGUL SYLLABLE BBUBS
+BFDF;BFDF;1108 116E 11BA;BFDF;1108 116E 11BA; # (뿟; 뿟; 뿟; 뿟; 뿟; ) HANGUL SYLLABLE BBUS
+BFE0;BFE0;1108 116E 11BB;BFE0;1108 116E 11BB; # (뿠; 뿠; 뿠; 뿠; 뿠; ) HANGUL SYLLABLE BBUSS
+BFE1;BFE1;1108 116E 11BC;BFE1;1108 116E 11BC; # (뿡; 뿡; 뿡; 뿡; 뿡; ) HANGUL SYLLABLE BBUNG
+BFE2;BFE2;1108 116E 11BD;BFE2;1108 116E 11BD; # (뿢; 뿢; 뿢; 뿢; 뿢; ) HANGUL SYLLABLE BBUJ
+BFE3;BFE3;1108 116E 11BE;BFE3;1108 116E 11BE; # (뿣; 뿣; 뿣; 뿣; 뿣; ) HANGUL SYLLABLE BBUC
+BFE4;BFE4;1108 116E 11BF;BFE4;1108 116E 11BF; # (뿤; 뿤; 뿤; 뿤; 뿤; ) HANGUL SYLLABLE BBUK
+BFE5;BFE5;1108 116E 11C0;BFE5;1108 116E 11C0; # (뿥; 뿥; 뿥; 뿥; 뿥; ) HANGUL SYLLABLE BBUT
+BFE6;BFE6;1108 116E 11C1;BFE6;1108 116E 11C1; # (뿦; 뿦; 뿌á‡; 뿦; 뿌á‡; ) HANGUL SYLLABLE BBUP
+BFE7;BFE7;1108 116E 11C2;BFE7;1108 116E 11C2; # (뿧; 뿧; 뿧; 뿧; 뿧; ) HANGUL SYLLABLE BBUH
+BFE8;BFE8;1108 116F;BFE8;1108 116F; # (뿨; 뿨; 뿨; 뿨; 뿨; ) HANGUL SYLLABLE BBWEO
+BFE9;BFE9;1108 116F 11A8;BFE9;1108 116F 11A8; # (뿩; 뿩; 뿩; 뿩; 뿩; ) HANGUL SYLLABLE BBWEOG
+BFEA;BFEA;1108 116F 11A9;BFEA;1108 116F 11A9; # (뿪; 뿪; 뿪; 뿪; 뿪; ) HANGUL SYLLABLE BBWEOGG
+BFEB;BFEB;1108 116F 11AA;BFEB;1108 116F 11AA; # (뿫; 뿫; 뿫; 뿫; 뿫; ) HANGUL SYLLABLE BBWEOGS
+BFEC;BFEC;1108 116F 11AB;BFEC;1108 116F 11AB; # (뿬; 뿬; 뿬; 뿬; 뿬; ) HANGUL SYLLABLE BBWEON
+BFED;BFED;1108 116F 11AC;BFED;1108 116F 11AC; # (뿭; 뿭; 뿭; 뿭; 뿭; ) HANGUL SYLLABLE BBWEONJ
+BFEE;BFEE;1108 116F 11AD;BFEE;1108 116F 11AD; # (뿮; 뿮; 뿮; 뿮; 뿮; ) HANGUL SYLLABLE BBWEONH
+BFEF;BFEF;1108 116F 11AE;BFEF;1108 116F 11AE; # (뿯; 뿯; 뿯; 뿯; 뿯; ) HANGUL SYLLABLE BBWEOD
+BFF0;BFF0;1108 116F 11AF;BFF0;1108 116F 11AF; # (뿰; 뿰; 뿰; 뿰; 뿰; ) HANGUL SYLLABLE BBWEOL
+BFF1;BFF1;1108 116F 11B0;BFF1;1108 116F 11B0; # (뿱; 뿱; 뿱; 뿱; 뿱; ) HANGUL SYLLABLE BBWEOLG
+BFF2;BFF2;1108 116F 11B1;BFF2;1108 116F 11B1; # (뿲; 뿲; 뿲; 뿲; 뿲; ) HANGUL SYLLABLE BBWEOLM
+BFF3;BFF3;1108 116F 11B2;BFF3;1108 116F 11B2; # (뿳; 뿳; 뿳; 뿳; 뿳; ) HANGUL SYLLABLE BBWEOLB
+BFF4;BFF4;1108 116F 11B3;BFF4;1108 116F 11B3; # (뿴; 뿴; 뿴; 뿴; 뿴; ) HANGUL SYLLABLE BBWEOLS
+BFF5;BFF5;1108 116F 11B4;BFF5;1108 116F 11B4; # (뿵; 뿵; 뿵; 뿵; 뿵; ) HANGUL SYLLABLE BBWEOLT
+BFF6;BFF6;1108 116F 11B5;BFF6;1108 116F 11B5; # (뿶; 뿶; 뿶; 뿶; 뿶; ) HANGUL SYLLABLE BBWEOLP
+BFF7;BFF7;1108 116F 11B6;BFF7;1108 116F 11B6; # (뿷; 뿷; 뿷; 뿷; 뿷; ) HANGUL SYLLABLE BBWEOLH
+BFF8;BFF8;1108 116F 11B7;BFF8;1108 116F 11B7; # (뿸; 뿸; 뿸; 뿸; 뿸; ) HANGUL SYLLABLE BBWEOM
+BFF9;BFF9;1108 116F 11B8;BFF9;1108 116F 11B8; # (뿹; 뿹; 뿹; 뿹; 뿹; ) HANGUL SYLLABLE BBWEOB
+BFFA;BFFA;1108 116F 11B9;BFFA;1108 116F 11B9; # (뿺; 뿺; 뿺; 뿺; 뿺; ) HANGUL SYLLABLE BBWEOBS
+BFFB;BFFB;1108 116F 11BA;BFFB;1108 116F 11BA; # (뿻; 뿻; 뿻; 뿻; 뿻; ) HANGUL SYLLABLE BBWEOS
+BFFC;BFFC;1108 116F 11BB;BFFC;1108 116F 11BB; # (뿼; 뿼; 뿼; 뿼; 뿼; ) HANGUL SYLLABLE BBWEOSS
+BFFD;BFFD;1108 116F 11BC;BFFD;1108 116F 11BC; # (뿽; 뿽; 뿽; 뿽; 뿽; ) HANGUL SYLLABLE BBWEONG
+BFFE;BFFE;1108 116F 11BD;BFFE;1108 116F 11BD; # (뿾; 뿾; 뿾; 뿾; 뿾; ) HANGUL SYLLABLE BBWEOJ
+BFFF;BFFF;1108 116F 11BE;BFFF;1108 116F 11BE; # (뿿; 뿿; 뿿; 뿿; 뿿; ) HANGUL SYLLABLE BBWEOC
+C000;C000;1108 116F 11BF;C000;1108 116F 11BF; # (쀀; 쀀; 쀀; 쀀; 쀀; ) HANGUL SYLLABLE BBWEOK
+C001;C001;1108 116F 11C0;C001;1108 116F 11C0; # (ì€; ì€; 쀁; ì€; 쀁; ) HANGUL SYLLABLE BBWEOT
+C002;C002;1108 116F 11C1;C002;1108 116F 11C1; # (쀂; 쀂; 뿨á‡; 쀂; 뿨á‡; ) HANGUL SYLLABLE BBWEOP
+C003;C003;1108 116F 11C2;C003;1108 116F 11C2; # (쀃; 쀃; 쀃; 쀃; 쀃; ) HANGUL SYLLABLE BBWEOH
+C004;C004;1108 1170;C004;1108 1170; # (쀄; 쀄; 쀄; 쀄; 쀄; ) HANGUL SYLLABLE BBWE
+C005;C005;1108 1170 11A8;C005;1108 1170 11A8; # (쀅; 쀅; 쀅; 쀅; 쀅; ) HANGUL SYLLABLE BBWEG
+C006;C006;1108 1170 11A9;C006;1108 1170 11A9; # (쀆; 쀆; 쀆; 쀆; 쀆; ) HANGUL SYLLABLE BBWEGG
+C007;C007;1108 1170 11AA;C007;1108 1170 11AA; # (쀇; 쀇; 쀇; 쀇; 쀇; ) HANGUL SYLLABLE BBWEGS
+C008;C008;1108 1170 11AB;C008;1108 1170 11AB; # (쀈; 쀈; 쀈; 쀈; 쀈; ) HANGUL SYLLABLE BBWEN
+C009;C009;1108 1170 11AC;C009;1108 1170 11AC; # (쀉; 쀉; 쀉; 쀉; 쀉; ) HANGUL SYLLABLE BBWENJ
+C00A;C00A;1108 1170 11AD;C00A;1108 1170 11AD; # (쀊; 쀊; 쀊; 쀊; 쀊; ) HANGUL SYLLABLE BBWENH
+C00B;C00B;1108 1170 11AE;C00B;1108 1170 11AE; # (쀋; 쀋; 쀋; 쀋; 쀋; ) HANGUL SYLLABLE BBWED
+C00C;C00C;1108 1170 11AF;C00C;1108 1170 11AF; # (쀌; 쀌; 쀌; 쀌; 쀌; ) HANGUL SYLLABLE BBWEL
+C00D;C00D;1108 1170 11B0;C00D;1108 1170 11B0; # (ì€; ì€; 쀍; ì€; 쀍; ) HANGUL SYLLABLE BBWELG
+C00E;C00E;1108 1170 11B1;C00E;1108 1170 11B1; # (쀎; 쀎; 쀎; 쀎; 쀎; ) HANGUL SYLLABLE BBWELM
+C00F;C00F;1108 1170 11B2;C00F;1108 1170 11B2; # (ì€; ì€; 쀏; ì€; 쀏; ) HANGUL SYLLABLE BBWELB
+C010;C010;1108 1170 11B3;C010;1108 1170 11B3; # (ì€; ì€; 쀐; ì€; 쀐; ) HANGUL SYLLABLE BBWELS
+C011;C011;1108 1170 11B4;C011;1108 1170 11B4; # (쀑; 쀑; 쀑; 쀑; 쀑; ) HANGUL SYLLABLE BBWELT
+C012;C012;1108 1170 11B5;C012;1108 1170 11B5; # (쀒; 쀒; 쀒; 쀒; 쀒; ) HANGUL SYLLABLE BBWELP
+C013;C013;1108 1170 11B6;C013;1108 1170 11B6; # (쀓; 쀓; 쀓; 쀓; 쀓; ) HANGUL SYLLABLE BBWELH
+C014;C014;1108 1170 11B7;C014;1108 1170 11B7; # (쀔; 쀔; 쀔; 쀔; 쀔; ) HANGUL SYLLABLE BBWEM
+C015;C015;1108 1170 11B8;C015;1108 1170 11B8; # (쀕; 쀕; 쀕; 쀕; 쀕; ) HANGUL SYLLABLE BBWEB
+C016;C016;1108 1170 11B9;C016;1108 1170 11B9; # (쀖; 쀖; 쀖; 쀖; 쀖; ) HANGUL SYLLABLE BBWEBS
+C017;C017;1108 1170 11BA;C017;1108 1170 11BA; # (쀗; 쀗; 쀗; 쀗; 쀗; ) HANGUL SYLLABLE BBWES
+C018;C018;1108 1170 11BB;C018;1108 1170 11BB; # (쀘; 쀘; 쀘; 쀘; 쀘; ) HANGUL SYLLABLE BBWESS
+C019;C019;1108 1170 11BC;C019;1108 1170 11BC; # (쀙; 쀙; 쀙; 쀙; 쀙; ) HANGUL SYLLABLE BBWENG
+C01A;C01A;1108 1170 11BD;C01A;1108 1170 11BD; # (쀚; 쀚; 쀚; 쀚; 쀚; ) HANGUL SYLLABLE BBWEJ
+C01B;C01B;1108 1170 11BE;C01B;1108 1170 11BE; # (쀛; 쀛; 쀛; 쀛; 쀛; ) HANGUL SYLLABLE BBWEC
+C01C;C01C;1108 1170 11BF;C01C;1108 1170 11BF; # (쀜; 쀜; 쀜; 쀜; 쀜; ) HANGUL SYLLABLE BBWEK
+C01D;C01D;1108 1170 11C0;C01D;1108 1170 11C0; # (ì€; ì€; 쀝; ì€; 쀝; ) HANGUL SYLLABLE BBWET
+C01E;C01E;1108 1170 11C1;C01E;1108 1170 11C1; # (쀞; 쀞; 쀄á‡; 쀞; 쀄á‡; ) HANGUL SYLLABLE BBWEP
+C01F;C01F;1108 1170 11C2;C01F;1108 1170 11C2; # (쀟; 쀟; 쀟; 쀟; 쀟; ) HANGUL SYLLABLE BBWEH
+C020;C020;1108 1171;C020;1108 1171; # (쀠; 쀠; 쀠; 쀠; 쀠; ) HANGUL SYLLABLE BBWI
+C021;C021;1108 1171 11A8;C021;1108 1171 11A8; # (쀡; 쀡; 쀡; 쀡; 쀡; ) HANGUL SYLLABLE BBWIG
+C022;C022;1108 1171 11A9;C022;1108 1171 11A9; # (쀢; 쀢; 쀢; 쀢; 쀢; ) HANGUL SYLLABLE BBWIGG
+C023;C023;1108 1171 11AA;C023;1108 1171 11AA; # (쀣; 쀣; 쀣; 쀣; 쀣; ) HANGUL SYLLABLE BBWIGS
+C024;C024;1108 1171 11AB;C024;1108 1171 11AB; # (쀤; 쀤; 쀤; 쀤; 쀤; ) HANGUL SYLLABLE BBWIN
+C025;C025;1108 1171 11AC;C025;1108 1171 11AC; # (쀥; 쀥; 쀥; 쀥; 쀥; ) HANGUL SYLLABLE BBWINJ
+C026;C026;1108 1171 11AD;C026;1108 1171 11AD; # (쀦; 쀦; 쀦; 쀦; 쀦; ) HANGUL SYLLABLE BBWINH
+C027;C027;1108 1171 11AE;C027;1108 1171 11AE; # (쀧; 쀧; 쀧; 쀧; 쀧; ) HANGUL SYLLABLE BBWID
+C028;C028;1108 1171 11AF;C028;1108 1171 11AF; # (쀨; 쀨; 쀨; 쀨; 쀨; ) HANGUL SYLLABLE BBWIL
+C029;C029;1108 1171 11B0;C029;1108 1171 11B0; # (쀩; 쀩; 쀩; 쀩; 쀩; ) HANGUL SYLLABLE BBWILG
+C02A;C02A;1108 1171 11B1;C02A;1108 1171 11B1; # (쀪; 쀪; 쀪; 쀪; 쀪; ) HANGUL SYLLABLE BBWILM
+C02B;C02B;1108 1171 11B2;C02B;1108 1171 11B2; # (쀫; 쀫; 쀫; 쀫; 쀫; ) HANGUL SYLLABLE BBWILB
+C02C;C02C;1108 1171 11B3;C02C;1108 1171 11B3; # (쀬; 쀬; 쀬; 쀬; 쀬; ) HANGUL SYLLABLE BBWILS
+C02D;C02D;1108 1171 11B4;C02D;1108 1171 11B4; # (쀭; 쀭; 쀭; 쀭; 쀭; ) HANGUL SYLLABLE BBWILT
+C02E;C02E;1108 1171 11B5;C02E;1108 1171 11B5; # (쀮; 쀮; 쀮; 쀮; 쀮; ) HANGUL SYLLABLE BBWILP
+C02F;C02F;1108 1171 11B6;C02F;1108 1171 11B6; # (쀯; 쀯; 쀯; 쀯; 쀯; ) HANGUL SYLLABLE BBWILH
+C030;C030;1108 1171 11B7;C030;1108 1171 11B7; # (쀰; 쀰; 쀰; 쀰; 쀰; ) HANGUL SYLLABLE BBWIM
+C031;C031;1108 1171 11B8;C031;1108 1171 11B8; # (쀱; 쀱; 쀱; 쀱; 쀱; ) HANGUL SYLLABLE BBWIB
+C032;C032;1108 1171 11B9;C032;1108 1171 11B9; # (쀲; 쀲; 쀲; 쀲; 쀲; ) HANGUL SYLLABLE BBWIBS
+C033;C033;1108 1171 11BA;C033;1108 1171 11BA; # (쀳; 쀳; 쀳; 쀳; 쀳; ) HANGUL SYLLABLE BBWIS
+C034;C034;1108 1171 11BB;C034;1108 1171 11BB; # (쀴; 쀴; 쀴; 쀴; 쀴; ) HANGUL SYLLABLE BBWISS
+C035;C035;1108 1171 11BC;C035;1108 1171 11BC; # (쀵; 쀵; 쀵; 쀵; 쀵; ) HANGUL SYLLABLE BBWING
+C036;C036;1108 1171 11BD;C036;1108 1171 11BD; # (쀶; 쀶; 쀶; 쀶; 쀶; ) HANGUL SYLLABLE BBWIJ
+C037;C037;1108 1171 11BE;C037;1108 1171 11BE; # (쀷; 쀷; 쀷; 쀷; 쀷; ) HANGUL SYLLABLE BBWIC
+C038;C038;1108 1171 11BF;C038;1108 1171 11BF; # (쀸; 쀸; 쀸; 쀸; 쀸; ) HANGUL SYLLABLE BBWIK
+C039;C039;1108 1171 11C0;C039;1108 1171 11C0; # (쀹; 쀹; 쀹; 쀹; 쀹; ) HANGUL SYLLABLE BBWIT
+C03A;C03A;1108 1171 11C1;C03A;1108 1171 11C1; # (쀺; 쀺; 쀠á‡; 쀺; 쀠á‡; ) HANGUL SYLLABLE BBWIP
+C03B;C03B;1108 1171 11C2;C03B;1108 1171 11C2; # (쀻; 쀻; 쀻; 쀻; 쀻; ) HANGUL SYLLABLE BBWIH
+C03C;C03C;1108 1172;C03C;1108 1172; # (쀼; 쀼; 쀼; 쀼; 쀼; ) HANGUL SYLLABLE BBYU
+C03D;C03D;1108 1172 11A8;C03D;1108 1172 11A8; # (쀽; 쀽; 쀽; 쀽; 쀽; ) HANGUL SYLLABLE BBYUG
+C03E;C03E;1108 1172 11A9;C03E;1108 1172 11A9; # (쀾; 쀾; 쀾; 쀾; 쀾; ) HANGUL SYLLABLE BBYUGG
+C03F;C03F;1108 1172 11AA;C03F;1108 1172 11AA; # (쀿; 쀿; 쀿; 쀿; 쀿; ) HANGUL SYLLABLE BBYUGS
+C040;C040;1108 1172 11AB;C040;1108 1172 11AB; # (ì€; ì€; 쁀; ì€; 쁀; ) HANGUL SYLLABLE BBYUN
+C041;C041;1108 1172 11AC;C041;1108 1172 11AC; # (ì; ì; 쁁; ì; 쁁; ) HANGUL SYLLABLE BBYUNJ
+C042;C042;1108 1172 11AD;C042;1108 1172 11AD; # (ì‚; ì‚; 쁂; ì‚; 쁂; ) HANGUL SYLLABLE BBYUNH
+C043;C043;1108 1172 11AE;C043;1108 1172 11AE; # (ìƒ; ìƒ; 쁃; ìƒ; 쁃; ) HANGUL SYLLABLE BBYUD
+C044;C044;1108 1172 11AF;C044;1108 1172 11AF; # (ì„; ì„; 쁄; ì„; 쁄; ) HANGUL SYLLABLE BBYUL
+C045;C045;1108 1172 11B0;C045;1108 1172 11B0; # (ì…; ì…; 쁅; ì…; 쁅; ) HANGUL SYLLABLE BBYULG
+C046;C046;1108 1172 11B1;C046;1108 1172 11B1; # (ì†; ì†; 쁆; ì†; 쁆; ) HANGUL SYLLABLE BBYULM
+C047;C047;1108 1172 11B2;C047;1108 1172 11B2; # (ì‡; ì‡; 쁇; ì‡; 쁇; ) HANGUL SYLLABLE BBYULB
+C048;C048;1108 1172 11B3;C048;1108 1172 11B3; # (ìˆ; ìˆ; 쁈; ìˆ; 쁈; ) HANGUL SYLLABLE BBYULS
+C049;C049;1108 1172 11B4;C049;1108 1172 11B4; # (ì‰; ì‰; 쁉; ì‰; 쁉; ) HANGUL SYLLABLE BBYULT
+C04A;C04A;1108 1172 11B5;C04A;1108 1172 11B5; # (ìŠ; ìŠ; 쁊; ìŠ; 쁊; ) HANGUL SYLLABLE BBYULP
+C04B;C04B;1108 1172 11B6;C04B;1108 1172 11B6; # (ì‹; ì‹; 쁋; ì‹; 쁋; ) HANGUL SYLLABLE BBYULH
+C04C;C04C;1108 1172 11B7;C04C;1108 1172 11B7; # (ìŒ; ìŒ; 쁌; ìŒ; 쁌; ) HANGUL SYLLABLE BBYUM
+C04D;C04D;1108 1172 11B8;C04D;1108 1172 11B8; # (ì; ì; 쁍; ì; 쁍; ) HANGUL SYLLABLE BBYUB
+C04E;C04E;1108 1172 11B9;C04E;1108 1172 11B9; # (ìŽ; ìŽ; 쁎; ìŽ; 쁎; ) HANGUL SYLLABLE BBYUBS
+C04F;C04F;1108 1172 11BA;C04F;1108 1172 11BA; # (ì; ì; 쁏; ì; 쁏; ) HANGUL SYLLABLE BBYUS
+C050;C050;1108 1172 11BB;C050;1108 1172 11BB; # (ì; ì; 쁐; ì; 쁐; ) HANGUL SYLLABLE BBYUSS
+C051;C051;1108 1172 11BC;C051;1108 1172 11BC; # (ì‘; ì‘; 쁑; ì‘; 쁑; ) HANGUL SYLLABLE BBYUNG
+C052;C052;1108 1172 11BD;C052;1108 1172 11BD; # (ì’; ì’; 쁒; ì’; 쁒; ) HANGUL SYLLABLE BBYUJ
+C053;C053;1108 1172 11BE;C053;1108 1172 11BE; # (ì“; ì“; 쁓; ì“; 쁓; ) HANGUL SYLLABLE BBYUC
+C054;C054;1108 1172 11BF;C054;1108 1172 11BF; # (ì”; ì”; 쁔; ì”; 쁔; ) HANGUL SYLLABLE BBYUK
+C055;C055;1108 1172 11C0;C055;1108 1172 11C0; # (ì•; ì•; 쁕; ì•; 쁕; ) HANGUL SYLLABLE BBYUT
+C056;C056;1108 1172 11C1;C056;1108 1172 11C1; # (ì–; ì–; 쀼á‡; ì–; 쀼á‡; ) HANGUL SYLLABLE BBYUP
+C057;C057;1108 1172 11C2;C057;1108 1172 11C2; # (ì—; ì—; 쁗; ì—; 쁗; ) HANGUL SYLLABLE BBYUH
+C058;C058;1108 1173;C058;1108 1173; # (ì˜; ì˜; 쁘; ì˜; 쁘; ) HANGUL SYLLABLE BBEU
+C059;C059;1108 1173 11A8;C059;1108 1173 11A8; # (ì™; ì™; 쁙; ì™; 쁙; ) HANGUL SYLLABLE BBEUG
+C05A;C05A;1108 1173 11A9;C05A;1108 1173 11A9; # (ìš; ìš; 쁚; ìš; 쁚; ) HANGUL SYLLABLE BBEUGG
+C05B;C05B;1108 1173 11AA;C05B;1108 1173 11AA; # (ì›; ì›; 쁛; ì›; 쁛; ) HANGUL SYLLABLE BBEUGS
+C05C;C05C;1108 1173 11AB;C05C;1108 1173 11AB; # (ìœ; ìœ; 쁜; ìœ; 쁜; ) HANGUL SYLLABLE BBEUN
+C05D;C05D;1108 1173 11AC;C05D;1108 1173 11AC; # (ì; ì; 쁝; ì; 쁝; ) HANGUL SYLLABLE BBEUNJ
+C05E;C05E;1108 1173 11AD;C05E;1108 1173 11AD; # (ìž; ìž; 쁞; ìž; 쁞; ) HANGUL SYLLABLE BBEUNH
+C05F;C05F;1108 1173 11AE;C05F;1108 1173 11AE; # (ìŸ; ìŸ; 쁟; ìŸ; 쁟; ) HANGUL SYLLABLE BBEUD
+C060;C060;1108 1173 11AF;C060;1108 1173 11AF; # (ì ; ì ; 쁠; ì ; 쁠; ) HANGUL SYLLABLE BBEUL
+C061;C061;1108 1173 11B0;C061;1108 1173 11B0; # (ì¡; ì¡; 쁡; ì¡; 쁡; ) HANGUL SYLLABLE BBEULG
+C062;C062;1108 1173 11B1;C062;1108 1173 11B1; # (ì¢; ì¢; 쁢; ì¢; 쁢; ) HANGUL SYLLABLE BBEULM
+C063;C063;1108 1173 11B2;C063;1108 1173 11B2; # (ì£; ì£; 쁣; ì£; 쁣; ) HANGUL SYLLABLE BBEULB
+C064;C064;1108 1173 11B3;C064;1108 1173 11B3; # (ì¤; ì¤; 쁤; ì¤; 쁤; ) HANGUL SYLLABLE BBEULS
+C065;C065;1108 1173 11B4;C065;1108 1173 11B4; # (ì¥; ì¥; 쁥; ì¥; 쁥; ) HANGUL SYLLABLE BBEULT
+C066;C066;1108 1173 11B5;C066;1108 1173 11B5; # (ì¦; ì¦; 쁦; ì¦; 쁦; ) HANGUL SYLLABLE BBEULP
+C067;C067;1108 1173 11B6;C067;1108 1173 11B6; # (ì§; ì§; 쁧; ì§; 쁧; ) HANGUL SYLLABLE BBEULH
+C068;C068;1108 1173 11B7;C068;1108 1173 11B7; # (ì¨; ì¨; 쁨; ì¨; 쁨; ) HANGUL SYLLABLE BBEUM
+C069;C069;1108 1173 11B8;C069;1108 1173 11B8; # (ì©; ì©; 쁩; ì©; 쁩; ) HANGUL SYLLABLE BBEUB
+C06A;C06A;1108 1173 11B9;C06A;1108 1173 11B9; # (ìª; ìª; 쁪; ìª; 쁪; ) HANGUL SYLLABLE BBEUBS
+C06B;C06B;1108 1173 11BA;C06B;1108 1173 11BA; # (ì«; ì«; 쁫; ì«; 쁫; ) HANGUL SYLLABLE BBEUS
+C06C;C06C;1108 1173 11BB;C06C;1108 1173 11BB; # (ì¬; ì¬; 쁬; ì¬; 쁬; ) HANGUL SYLLABLE BBEUSS
+C06D;C06D;1108 1173 11BC;C06D;1108 1173 11BC; # (ì­; ì­; 쁭; ì­; 쁭; ) HANGUL SYLLABLE BBEUNG
+C06E;C06E;1108 1173 11BD;C06E;1108 1173 11BD; # (ì®; ì®; 쁮; ì®; 쁮; ) HANGUL SYLLABLE BBEUJ
+C06F;C06F;1108 1173 11BE;C06F;1108 1173 11BE; # (ì¯; ì¯; 쁯; ì¯; 쁯; ) HANGUL SYLLABLE BBEUC
+C070;C070;1108 1173 11BF;C070;1108 1173 11BF; # (ì°; ì°; 쁰; ì°; 쁰; ) HANGUL SYLLABLE BBEUK
+C071;C071;1108 1173 11C0;C071;1108 1173 11C0; # (ì±; ì±; 쁱; ì±; 쁱; ) HANGUL SYLLABLE BBEUT
+C072;C072;1108 1173 11C1;C072;1108 1173 11C1; # (ì²; ì²; 쁘á‡; ì²; 쁘á‡; ) HANGUL SYLLABLE BBEUP
+C073;C073;1108 1173 11C2;C073;1108 1173 11C2; # (ì³; ì³; 쁳; ì³; 쁳; ) HANGUL SYLLABLE BBEUH
+C074;C074;1108 1174;C074;1108 1174; # (ì´; ì´; 쁴; ì´; 쁴; ) HANGUL SYLLABLE BBYI
+C075;C075;1108 1174 11A8;C075;1108 1174 11A8; # (ìµ; ìµ; 쁵; ìµ; 쁵; ) HANGUL SYLLABLE BBYIG
+C076;C076;1108 1174 11A9;C076;1108 1174 11A9; # (ì¶; ì¶; 쁶; ì¶; 쁶; ) HANGUL SYLLABLE BBYIGG
+C077;C077;1108 1174 11AA;C077;1108 1174 11AA; # (ì·; ì·; 쁷; ì·; 쁷; ) HANGUL SYLLABLE BBYIGS
+C078;C078;1108 1174 11AB;C078;1108 1174 11AB; # (ì¸; ì¸; 쁸; ì¸; 쁸; ) HANGUL SYLLABLE BBYIN
+C079;C079;1108 1174 11AC;C079;1108 1174 11AC; # (ì¹; ì¹; 쁹; ì¹; 쁹; ) HANGUL SYLLABLE BBYINJ
+C07A;C07A;1108 1174 11AD;C07A;1108 1174 11AD; # (ìº; ìº; 쁺; ìº; 쁺; ) HANGUL SYLLABLE BBYINH
+C07B;C07B;1108 1174 11AE;C07B;1108 1174 11AE; # (ì»; ì»; 쁻; ì»; 쁻; ) HANGUL SYLLABLE BBYID
+C07C;C07C;1108 1174 11AF;C07C;1108 1174 11AF; # (ì¼; ì¼; 쁼; ì¼; 쁼; ) HANGUL SYLLABLE BBYIL
+C07D;C07D;1108 1174 11B0;C07D;1108 1174 11B0; # (ì½; ì½; 쁽; ì½; 쁽; ) HANGUL SYLLABLE BBYILG
+C07E;C07E;1108 1174 11B1;C07E;1108 1174 11B1; # (ì¾; ì¾; 쁾; ì¾; 쁾; ) HANGUL SYLLABLE BBYILM
+C07F;C07F;1108 1174 11B2;C07F;1108 1174 11B2; # (ì¿; ì¿; 쁿; ì¿; 쁿; ) HANGUL SYLLABLE BBYILB
+C080;C080;1108 1174 11B3;C080;1108 1174 11B3; # (삀; 삀; 삀; 삀; 삀; ) HANGUL SYLLABLE BBYILS
+C081;C081;1108 1174 11B4;C081;1108 1174 11B4; # (ì‚; ì‚; 삁; ì‚; 삁; ) HANGUL SYLLABLE BBYILT
+C082;C082;1108 1174 11B5;C082;1108 1174 11B5; # (삂; 삂; 삂; 삂; 삂; ) HANGUL SYLLABLE BBYILP
+C083;C083;1108 1174 11B6;C083;1108 1174 11B6; # (삃; 삃; 삃; 삃; 삃; ) HANGUL SYLLABLE BBYILH
+C084;C084;1108 1174 11B7;C084;1108 1174 11B7; # (삄; 삄; 삄; 삄; 삄; ) HANGUL SYLLABLE BBYIM
+C085;C085;1108 1174 11B8;C085;1108 1174 11B8; # (삅; 삅; 삅; 삅; 삅; ) HANGUL SYLLABLE BBYIB
+C086;C086;1108 1174 11B9;C086;1108 1174 11B9; # (삆; 삆; 삆; 삆; 삆; ) HANGUL SYLLABLE BBYIBS
+C087;C087;1108 1174 11BA;C087;1108 1174 11BA; # (삇; 삇; 삇; 삇; 삇; ) HANGUL SYLLABLE BBYIS
+C088;C088;1108 1174 11BB;C088;1108 1174 11BB; # (삈; 삈; 삈; 삈; 삈; ) HANGUL SYLLABLE BBYISS
+C089;C089;1108 1174 11BC;C089;1108 1174 11BC; # (삉; 삉; 삉; 삉; 삉; ) HANGUL SYLLABLE BBYING
+C08A;C08A;1108 1174 11BD;C08A;1108 1174 11BD; # (삊; 삊; 삊; 삊; 삊; ) HANGUL SYLLABLE BBYIJ
+C08B;C08B;1108 1174 11BE;C08B;1108 1174 11BE; # (삋; 삋; 삋; 삋; 삋; ) HANGUL SYLLABLE BBYIC
+C08C;C08C;1108 1174 11BF;C08C;1108 1174 11BF; # (삌; 삌; 삌; 삌; 삌; ) HANGUL SYLLABLE BBYIK
+C08D;C08D;1108 1174 11C0;C08D;1108 1174 11C0; # (ì‚; ì‚; 삍; ì‚; 삍; ) HANGUL SYLLABLE BBYIT
+C08E;C08E;1108 1174 11C1;C08E;1108 1174 11C1; # (ì‚Ž; ì‚Ž; 쁴á‡; ì‚Ž; 쁴á‡; ) HANGUL SYLLABLE BBYIP
+C08F;C08F;1108 1174 11C2;C08F;1108 1174 11C2; # (ì‚; ì‚; 삏; ì‚; 삏; ) HANGUL SYLLABLE BBYIH
+C090;C090;1108 1175;C090;1108 1175; # (ì‚; ì‚; 삐; ì‚; 삐; ) HANGUL SYLLABLE BBI
+C091;C091;1108 1175 11A8;C091;1108 1175 11A8; # (삑; 삑; 삑; 삑; 삑; ) HANGUL SYLLABLE BBIG
+C092;C092;1108 1175 11A9;C092;1108 1175 11A9; # (삒; 삒; 삒; 삒; 삒; ) HANGUL SYLLABLE BBIGG
+C093;C093;1108 1175 11AA;C093;1108 1175 11AA; # (삓; 삓; 삓; 삓; 삓; ) HANGUL SYLLABLE BBIGS
+C094;C094;1108 1175 11AB;C094;1108 1175 11AB; # (삔; 삔; 삔; 삔; 삔; ) HANGUL SYLLABLE BBIN
+C095;C095;1108 1175 11AC;C095;1108 1175 11AC; # (삕; 삕; 삕; 삕; 삕; ) HANGUL SYLLABLE BBINJ
+C096;C096;1108 1175 11AD;C096;1108 1175 11AD; # (삖; 삖; 삖; 삖; 삖; ) HANGUL SYLLABLE BBINH
+C097;C097;1108 1175 11AE;C097;1108 1175 11AE; # (삗; 삗; 삗; 삗; 삗; ) HANGUL SYLLABLE BBID
+C098;C098;1108 1175 11AF;C098;1108 1175 11AF; # (삘; 삘; 삘; 삘; 삘; ) HANGUL SYLLABLE BBIL
+C099;C099;1108 1175 11B0;C099;1108 1175 11B0; # (삙; 삙; 삙; 삙; 삙; ) HANGUL SYLLABLE BBILG
+C09A;C09A;1108 1175 11B1;C09A;1108 1175 11B1; # (삚; 삚; 삚; 삚; 삚; ) HANGUL SYLLABLE BBILM
+C09B;C09B;1108 1175 11B2;C09B;1108 1175 11B2; # (삛; 삛; 삛; 삛; 삛; ) HANGUL SYLLABLE BBILB
+C09C;C09C;1108 1175 11B3;C09C;1108 1175 11B3; # (삜; 삜; 삜; 삜; 삜; ) HANGUL SYLLABLE BBILS
+C09D;C09D;1108 1175 11B4;C09D;1108 1175 11B4; # (ì‚; ì‚; 삝; ì‚; 삝; ) HANGUL SYLLABLE BBILT
+C09E;C09E;1108 1175 11B5;C09E;1108 1175 11B5; # (삞; 삞; 삞; 삞; 삞; ) HANGUL SYLLABLE BBILP
+C09F;C09F;1108 1175 11B6;C09F;1108 1175 11B6; # (삟; 삟; 삟; 삟; 삟; ) HANGUL SYLLABLE BBILH
+C0A0;C0A0;1108 1175 11B7;C0A0;1108 1175 11B7; # (삠; 삠; 삠; 삠; 삠; ) HANGUL SYLLABLE BBIM
+C0A1;C0A1;1108 1175 11B8;C0A1;1108 1175 11B8; # (삡; 삡; 삡; 삡; 삡; ) HANGUL SYLLABLE BBIB
+C0A2;C0A2;1108 1175 11B9;C0A2;1108 1175 11B9; # (삢; 삢; 삢; 삢; 삢; ) HANGUL SYLLABLE BBIBS
+C0A3;C0A3;1108 1175 11BA;C0A3;1108 1175 11BA; # (삣; 삣; 삣; 삣; 삣; ) HANGUL SYLLABLE BBIS
+C0A4;C0A4;1108 1175 11BB;C0A4;1108 1175 11BB; # (삤; 삤; 삤; 삤; 삤; ) HANGUL SYLLABLE BBISS
+C0A5;C0A5;1108 1175 11BC;C0A5;1108 1175 11BC; # (삥; 삥; 삥; 삥; 삥; ) HANGUL SYLLABLE BBING
+C0A6;C0A6;1108 1175 11BD;C0A6;1108 1175 11BD; # (삦; 삦; 삦; 삦; 삦; ) HANGUL SYLLABLE BBIJ
+C0A7;C0A7;1108 1175 11BE;C0A7;1108 1175 11BE; # (삧; 삧; 삧; 삧; 삧; ) HANGUL SYLLABLE BBIC
+C0A8;C0A8;1108 1175 11BF;C0A8;1108 1175 11BF; # (삨; 삨; 삨; 삨; 삨; ) HANGUL SYLLABLE BBIK
+C0A9;C0A9;1108 1175 11C0;C0A9;1108 1175 11C0; # (삩; 삩; 삩; 삩; 삩; ) HANGUL SYLLABLE BBIT
+C0AA;C0AA;1108 1175 11C1;C0AA;1108 1175 11C1; # (삪; 삪; 삐á‡; 삪; 삐á‡; ) HANGUL SYLLABLE BBIP
+C0AB;C0AB;1108 1175 11C2;C0AB;1108 1175 11C2; # (삫; 삫; 삫; 삫; 삫; ) HANGUL SYLLABLE BBIH
+C0AC;C0AC;1109 1161;C0AC;1109 1161; # (사; 사; 사; 사; 사; ) HANGUL SYLLABLE SA
+C0AD;C0AD;1109 1161 11A8;C0AD;1109 1161 11A8; # (삭; 삭; 삭; 삭; 삭; ) HANGUL SYLLABLE SAG
+C0AE;C0AE;1109 1161 11A9;C0AE;1109 1161 11A9; # (삮; 삮; 삮; 삮; 삮; ) HANGUL SYLLABLE SAGG
+C0AF;C0AF;1109 1161 11AA;C0AF;1109 1161 11AA; # (삯; 삯; 삯; 삯; 삯; ) HANGUL SYLLABLE SAGS
+C0B0;C0B0;1109 1161 11AB;C0B0;1109 1161 11AB; # (산; 산; 산; 산; 산; ) HANGUL SYLLABLE SAN
+C0B1;C0B1;1109 1161 11AC;C0B1;1109 1161 11AC; # (삱; 삱; 삱; 삱; 삱; ) HANGUL SYLLABLE SANJ
+C0B2;C0B2;1109 1161 11AD;C0B2;1109 1161 11AD; # (삲; 삲; 삲; 삲; 삲; ) HANGUL SYLLABLE SANH
+C0B3;C0B3;1109 1161 11AE;C0B3;1109 1161 11AE; # (삳; 삳; 삳; 삳; 삳; ) HANGUL SYLLABLE SAD
+C0B4;C0B4;1109 1161 11AF;C0B4;1109 1161 11AF; # (살; 살; 살; 살; 살; ) HANGUL SYLLABLE SAL
+C0B5;C0B5;1109 1161 11B0;C0B5;1109 1161 11B0; # (삵; 삵; 삵; 삵; 삵; ) HANGUL SYLLABLE SALG
+C0B6;C0B6;1109 1161 11B1;C0B6;1109 1161 11B1; # (삶; 삶; 삶; 삶; 삶; ) HANGUL SYLLABLE SALM
+C0B7;C0B7;1109 1161 11B2;C0B7;1109 1161 11B2; # (삷; 삷; 삷; 삷; 삷; ) HANGUL SYLLABLE SALB
+C0B8;C0B8;1109 1161 11B3;C0B8;1109 1161 11B3; # (삸; 삸; 삸; 삸; 삸; ) HANGUL SYLLABLE SALS
+C0B9;C0B9;1109 1161 11B4;C0B9;1109 1161 11B4; # (삹; 삹; 삹; 삹; 삹; ) HANGUL SYLLABLE SALT
+C0BA;C0BA;1109 1161 11B5;C0BA;1109 1161 11B5; # (삺; 삺; 삺; 삺; 삺; ) HANGUL SYLLABLE SALP
+C0BB;C0BB;1109 1161 11B6;C0BB;1109 1161 11B6; # (삻; 삻; 삻; 삻; 삻; ) HANGUL SYLLABLE SALH
+C0BC;C0BC;1109 1161 11B7;C0BC;1109 1161 11B7; # (삼; 삼; 삼; 삼; 삼; ) HANGUL SYLLABLE SAM
+C0BD;C0BD;1109 1161 11B8;C0BD;1109 1161 11B8; # (삽; 삽; 삽; 삽; 삽; ) HANGUL SYLLABLE SAB
+C0BE;C0BE;1109 1161 11B9;C0BE;1109 1161 11B9; # (삾; 삾; 삾; 삾; 삾; ) HANGUL SYLLABLE SABS
+C0BF;C0BF;1109 1161 11BA;C0BF;1109 1161 11BA; # (삿; 삿; 삿; 삿; 삿; ) HANGUL SYLLABLE SAS
+C0C0;C0C0;1109 1161 11BB;C0C0;1109 1161 11BB; # (샀; 샀; 샀; 샀; 샀; ) HANGUL SYLLABLE SASS
+C0C1;C0C1;1109 1161 11BC;C0C1;1109 1161 11BC; # (ìƒ; ìƒ; 상; ìƒ; 상; ) HANGUL SYLLABLE SANG
+C0C2;C0C2;1109 1161 11BD;C0C2;1109 1161 11BD; # (샂; 샂; 샂; 샂; 샂; ) HANGUL SYLLABLE SAJ
+C0C3;C0C3;1109 1161 11BE;C0C3;1109 1161 11BE; # (샃; 샃; 샃; 샃; 샃; ) HANGUL SYLLABLE SAC
+C0C4;C0C4;1109 1161 11BF;C0C4;1109 1161 11BF; # (샄; 샄; 샄; 샄; 샄; ) HANGUL SYLLABLE SAK
+C0C5;C0C5;1109 1161 11C0;C0C5;1109 1161 11C0; # (샅; 샅; 샅; 샅; 샅; ) HANGUL SYLLABLE SAT
+C0C6;C0C6;1109 1161 11C1;C0C6;1109 1161 11C1; # (샆; 샆; 사á‡; 샆; 사á‡; ) HANGUL SYLLABLE SAP
+C0C7;C0C7;1109 1161 11C2;C0C7;1109 1161 11C2; # (샇; 샇; 샇; 샇; 샇; ) HANGUL SYLLABLE SAH
+C0C8;C0C8;1109 1162;C0C8;1109 1162; # (새; 새; 새; 새; 새; ) HANGUL SYLLABLE SAE
+C0C9;C0C9;1109 1162 11A8;C0C9;1109 1162 11A8; # (색; 색; 색; 색; 색; ) HANGUL SYLLABLE SAEG
+C0CA;C0CA;1109 1162 11A9;C0CA;1109 1162 11A9; # (샊; 샊; 샊; 샊; 샊; ) HANGUL SYLLABLE SAEGG
+C0CB;C0CB;1109 1162 11AA;C0CB;1109 1162 11AA; # (샋; 샋; 샋; 샋; 샋; ) HANGUL SYLLABLE SAEGS
+C0CC;C0CC;1109 1162 11AB;C0CC;1109 1162 11AB; # (샌; 샌; 샌; 샌; 샌; ) HANGUL SYLLABLE SAEN
+C0CD;C0CD;1109 1162 11AC;C0CD;1109 1162 11AC; # (ìƒ; ìƒ; 샍; ìƒ; 샍; ) HANGUL SYLLABLE SAENJ
+C0CE;C0CE;1109 1162 11AD;C0CE;1109 1162 11AD; # (샎; 샎; 샎; 샎; 샎; ) HANGUL SYLLABLE SAENH
+C0CF;C0CF;1109 1162 11AE;C0CF;1109 1162 11AE; # (ìƒ; ìƒ; 샏; ìƒ; 샏; ) HANGUL SYLLABLE SAED
+C0D0;C0D0;1109 1162 11AF;C0D0;1109 1162 11AF; # (ìƒ; ìƒ; 샐; ìƒ; 샐; ) HANGUL SYLLABLE SAEL
+C0D1;C0D1;1109 1162 11B0;C0D1;1109 1162 11B0; # (샑; 샑; 샑; 샑; 샑; ) HANGUL SYLLABLE SAELG
+C0D2;C0D2;1109 1162 11B1;C0D2;1109 1162 11B1; # (샒; 샒; 샒; 샒; 샒; ) HANGUL SYLLABLE SAELM
+C0D3;C0D3;1109 1162 11B2;C0D3;1109 1162 11B2; # (샓; 샓; 샓; 샓; 샓; ) HANGUL SYLLABLE SAELB
+C0D4;C0D4;1109 1162 11B3;C0D4;1109 1162 11B3; # (샔; 샔; 샔; 샔; 샔; ) HANGUL SYLLABLE SAELS
+C0D5;C0D5;1109 1162 11B4;C0D5;1109 1162 11B4; # (샕; 샕; 샕; 샕; 샕; ) HANGUL SYLLABLE SAELT
+C0D6;C0D6;1109 1162 11B5;C0D6;1109 1162 11B5; # (샖; 샖; 샖; 샖; 샖; ) HANGUL SYLLABLE SAELP
+C0D7;C0D7;1109 1162 11B6;C0D7;1109 1162 11B6; # (샗; 샗; 샗; 샗; 샗; ) HANGUL SYLLABLE SAELH
+C0D8;C0D8;1109 1162 11B7;C0D8;1109 1162 11B7; # (샘; 샘; 샘; 샘; 샘; ) HANGUL SYLLABLE SAEM
+C0D9;C0D9;1109 1162 11B8;C0D9;1109 1162 11B8; # (샙; 샙; 샙; 샙; 샙; ) HANGUL SYLLABLE SAEB
+C0DA;C0DA;1109 1162 11B9;C0DA;1109 1162 11B9; # (샚; 샚; 샚; 샚; 샚; ) HANGUL SYLLABLE SAEBS
+C0DB;C0DB;1109 1162 11BA;C0DB;1109 1162 11BA; # (샛; 샛; 샛; 샛; 샛; ) HANGUL SYLLABLE SAES
+C0DC;C0DC;1109 1162 11BB;C0DC;1109 1162 11BB; # (샜; 샜; 샜; 샜; 샜; ) HANGUL SYLLABLE SAESS
+C0DD;C0DD;1109 1162 11BC;C0DD;1109 1162 11BC; # (ìƒ; ìƒ; 생; ìƒ; 생; ) HANGUL SYLLABLE SAENG
+C0DE;C0DE;1109 1162 11BD;C0DE;1109 1162 11BD; # (샞; 샞; 샞; 샞; 샞; ) HANGUL SYLLABLE SAEJ
+C0DF;C0DF;1109 1162 11BE;C0DF;1109 1162 11BE; # (샟; 샟; 샟; 샟; 샟; ) HANGUL SYLLABLE SAEC
+C0E0;C0E0;1109 1162 11BF;C0E0;1109 1162 11BF; # (샠; 샠; 샠; 샠; 샠; ) HANGUL SYLLABLE SAEK
+C0E1;C0E1;1109 1162 11C0;C0E1;1109 1162 11C0; # (샡; 샡; 샡; 샡; 샡; ) HANGUL SYLLABLE SAET
+C0E2;C0E2;1109 1162 11C1;C0E2;1109 1162 11C1; # (샢; 샢; 새á‡; 샢; 새á‡; ) HANGUL SYLLABLE SAEP
+C0E3;C0E3;1109 1162 11C2;C0E3;1109 1162 11C2; # (샣; 샣; 샣; 샣; 샣; ) HANGUL SYLLABLE SAEH
+C0E4;C0E4;1109 1163;C0E4;1109 1163; # (샤; 샤; 샤; 샤; 샤; ) HANGUL SYLLABLE SYA
+C0E5;C0E5;1109 1163 11A8;C0E5;1109 1163 11A8; # (샥; 샥; 샥; 샥; 샥; ) HANGUL SYLLABLE SYAG
+C0E6;C0E6;1109 1163 11A9;C0E6;1109 1163 11A9; # (샦; 샦; 샦; 샦; 샦; ) HANGUL SYLLABLE SYAGG
+C0E7;C0E7;1109 1163 11AA;C0E7;1109 1163 11AA; # (샧; 샧; 샧; 샧; 샧; ) HANGUL SYLLABLE SYAGS
+C0E8;C0E8;1109 1163 11AB;C0E8;1109 1163 11AB; # (샨; 샨; 샨; 샨; 샨; ) HANGUL SYLLABLE SYAN
+C0E9;C0E9;1109 1163 11AC;C0E9;1109 1163 11AC; # (샩; 샩; 샩; 샩; 샩; ) HANGUL SYLLABLE SYANJ
+C0EA;C0EA;1109 1163 11AD;C0EA;1109 1163 11AD; # (샪; 샪; 샪; 샪; 샪; ) HANGUL SYLLABLE SYANH
+C0EB;C0EB;1109 1163 11AE;C0EB;1109 1163 11AE; # (샫; 샫; 샫; 샫; 샫; ) HANGUL SYLLABLE SYAD
+C0EC;C0EC;1109 1163 11AF;C0EC;1109 1163 11AF; # (샬; 샬; 샬; 샬; 샬; ) HANGUL SYLLABLE SYAL
+C0ED;C0ED;1109 1163 11B0;C0ED;1109 1163 11B0; # (샭; 샭; 샭; 샭; 샭; ) HANGUL SYLLABLE SYALG
+C0EE;C0EE;1109 1163 11B1;C0EE;1109 1163 11B1; # (샮; 샮; 샮; 샮; 샮; ) HANGUL SYLLABLE SYALM
+C0EF;C0EF;1109 1163 11B2;C0EF;1109 1163 11B2; # (샯; 샯; 샯; 샯; 샯; ) HANGUL SYLLABLE SYALB
+C0F0;C0F0;1109 1163 11B3;C0F0;1109 1163 11B3; # (샰; 샰; 샰; 샰; 샰; ) HANGUL SYLLABLE SYALS
+C0F1;C0F1;1109 1163 11B4;C0F1;1109 1163 11B4; # (샱; 샱; 샱; 샱; 샱; ) HANGUL SYLLABLE SYALT
+C0F2;C0F2;1109 1163 11B5;C0F2;1109 1163 11B5; # (샲; 샲; 샲; 샲; 샲; ) HANGUL SYLLABLE SYALP
+C0F3;C0F3;1109 1163 11B6;C0F3;1109 1163 11B6; # (샳; 샳; 샳; 샳; 샳; ) HANGUL SYLLABLE SYALH
+C0F4;C0F4;1109 1163 11B7;C0F4;1109 1163 11B7; # (샴; 샴; 샴; 샴; 샴; ) HANGUL SYLLABLE SYAM
+C0F5;C0F5;1109 1163 11B8;C0F5;1109 1163 11B8; # (샵; 샵; 샵; 샵; 샵; ) HANGUL SYLLABLE SYAB
+C0F6;C0F6;1109 1163 11B9;C0F6;1109 1163 11B9; # (샶; 샶; 샶; 샶; 샶; ) HANGUL SYLLABLE SYABS
+C0F7;C0F7;1109 1163 11BA;C0F7;1109 1163 11BA; # (샷; 샷; 샷; 샷; 샷; ) HANGUL SYLLABLE SYAS
+C0F8;C0F8;1109 1163 11BB;C0F8;1109 1163 11BB; # (샸; 샸; 샸; 샸; 샸; ) HANGUL SYLLABLE SYASS
+C0F9;C0F9;1109 1163 11BC;C0F9;1109 1163 11BC; # (샹; 샹; 샹; 샹; 샹; ) HANGUL SYLLABLE SYANG
+C0FA;C0FA;1109 1163 11BD;C0FA;1109 1163 11BD; # (샺; 샺; 샺; 샺; 샺; ) HANGUL SYLLABLE SYAJ
+C0FB;C0FB;1109 1163 11BE;C0FB;1109 1163 11BE; # (샻; 샻; 샻; 샻; 샻; ) HANGUL SYLLABLE SYAC
+C0FC;C0FC;1109 1163 11BF;C0FC;1109 1163 11BF; # (샼; 샼; 샼; 샼; 샼; ) HANGUL SYLLABLE SYAK
+C0FD;C0FD;1109 1163 11C0;C0FD;1109 1163 11C0; # (샽; 샽; 샽; 샽; 샽; ) HANGUL SYLLABLE SYAT
+C0FE;C0FE;1109 1163 11C1;C0FE;1109 1163 11C1; # (샾; 샾; 샤á‡; 샾; 샤á‡; ) HANGUL SYLLABLE SYAP
+C0FF;C0FF;1109 1163 11C2;C0FF;1109 1163 11C2; # (샿; 샿; 샿; 샿; 샿; ) HANGUL SYLLABLE SYAH
+C100;C100;1109 1164;C100;1109 1164; # (섀; 섀; 섀; 섀; 섀; ) HANGUL SYLLABLE SYAE
+C101;C101;1109 1164 11A8;C101;1109 1164 11A8; # (ì„; ì„; 섁; ì„; 섁; ) HANGUL SYLLABLE SYAEG
+C102;C102;1109 1164 11A9;C102;1109 1164 11A9; # (섂; 섂; 섂; 섂; 섂; ) HANGUL SYLLABLE SYAEGG
+C103;C103;1109 1164 11AA;C103;1109 1164 11AA; # (섃; 섃; 섃; 섃; 섃; ) HANGUL SYLLABLE SYAEGS
+C104;C104;1109 1164 11AB;C104;1109 1164 11AB; # (섄; 섄; 섄; 섄; 섄; ) HANGUL SYLLABLE SYAEN
+C105;C105;1109 1164 11AC;C105;1109 1164 11AC; # (섅; 섅; 섅; 섅; 섅; ) HANGUL SYLLABLE SYAENJ
+C106;C106;1109 1164 11AD;C106;1109 1164 11AD; # (섆; 섆; 섆; 섆; 섆; ) HANGUL SYLLABLE SYAENH
+C107;C107;1109 1164 11AE;C107;1109 1164 11AE; # (섇; 섇; 섇; 섇; 섇; ) HANGUL SYLLABLE SYAED
+C108;C108;1109 1164 11AF;C108;1109 1164 11AF; # (섈; 섈; 섈; 섈; 섈; ) HANGUL SYLLABLE SYAEL
+C109;C109;1109 1164 11B0;C109;1109 1164 11B0; # (섉; 섉; 섉; 섉; 섉; ) HANGUL SYLLABLE SYAELG
+C10A;C10A;1109 1164 11B1;C10A;1109 1164 11B1; # (섊; 섊; 섊; 섊; 섊; ) HANGUL SYLLABLE SYAELM
+C10B;C10B;1109 1164 11B2;C10B;1109 1164 11B2; # (섋; 섋; 섋; 섋; 섋; ) HANGUL SYLLABLE SYAELB
+C10C;C10C;1109 1164 11B3;C10C;1109 1164 11B3; # (섌; 섌; 섌; 섌; 섌; ) HANGUL SYLLABLE SYAELS
+C10D;C10D;1109 1164 11B4;C10D;1109 1164 11B4; # (ì„; ì„; 섍; ì„; 섍; ) HANGUL SYLLABLE SYAELT
+C10E;C10E;1109 1164 11B5;C10E;1109 1164 11B5; # (섎; 섎; 섎; 섎; 섎; ) HANGUL SYLLABLE SYAELP
+C10F;C10F;1109 1164 11B6;C10F;1109 1164 11B6; # (ì„; ì„; 섏; ì„; 섏; ) HANGUL SYLLABLE SYAELH
+C110;C110;1109 1164 11B7;C110;1109 1164 11B7; # (ì„; ì„; 섐; ì„; 섐; ) HANGUL SYLLABLE SYAEM
+C111;C111;1109 1164 11B8;C111;1109 1164 11B8; # (섑; 섑; 섑; 섑; 섑; ) HANGUL SYLLABLE SYAEB
+C112;C112;1109 1164 11B9;C112;1109 1164 11B9; # (섒; 섒; 섒; 섒; 섒; ) HANGUL SYLLABLE SYAEBS
+C113;C113;1109 1164 11BA;C113;1109 1164 11BA; # (섓; 섓; 섓; 섓; 섓; ) HANGUL SYLLABLE SYAES
+C114;C114;1109 1164 11BB;C114;1109 1164 11BB; # (섔; 섔; 섔; 섔; 섔; ) HANGUL SYLLABLE SYAESS
+C115;C115;1109 1164 11BC;C115;1109 1164 11BC; # (섕; 섕; 섕; 섕; 섕; ) HANGUL SYLLABLE SYAENG
+C116;C116;1109 1164 11BD;C116;1109 1164 11BD; # (섖; 섖; 섖; 섖; 섖; ) HANGUL SYLLABLE SYAEJ
+C117;C117;1109 1164 11BE;C117;1109 1164 11BE; # (섗; 섗; 섗; 섗; 섗; ) HANGUL SYLLABLE SYAEC
+C118;C118;1109 1164 11BF;C118;1109 1164 11BF; # (섘; 섘; 섘; 섘; 섘; ) HANGUL SYLLABLE SYAEK
+C119;C119;1109 1164 11C0;C119;1109 1164 11C0; # (섙; 섙; 섙; 섙; 섙; ) HANGUL SYLLABLE SYAET
+C11A;C11A;1109 1164 11C1;C11A;1109 1164 11C1; # (ì„š; ì„š; 섀á‡; ì„š; 섀á‡; ) HANGUL SYLLABLE SYAEP
+C11B;C11B;1109 1164 11C2;C11B;1109 1164 11C2; # (섛; 섛; 섛; 섛; 섛; ) HANGUL SYLLABLE SYAEH
+C11C;C11C;1109 1165;C11C;1109 1165; # (서; 서; 서; 서; 서; ) HANGUL SYLLABLE SEO
+C11D;C11D;1109 1165 11A8;C11D;1109 1165 11A8; # (ì„; ì„; 석; ì„; 석; ) HANGUL SYLLABLE SEOG
+C11E;C11E;1109 1165 11A9;C11E;1109 1165 11A9; # (섞; 섞; 섞; 섞; 섞; ) HANGUL SYLLABLE SEOGG
+C11F;C11F;1109 1165 11AA;C11F;1109 1165 11AA; # (섟; 섟; 섟; 섟; 섟; ) HANGUL SYLLABLE SEOGS
+C120;C120;1109 1165 11AB;C120;1109 1165 11AB; # (선; 선; 선; 선; 선; ) HANGUL SYLLABLE SEON
+C121;C121;1109 1165 11AC;C121;1109 1165 11AC; # (섡; 섡; 섡; 섡; 섡; ) HANGUL SYLLABLE SEONJ
+C122;C122;1109 1165 11AD;C122;1109 1165 11AD; # (섢; 섢; 섢; 섢; 섢; ) HANGUL SYLLABLE SEONH
+C123;C123;1109 1165 11AE;C123;1109 1165 11AE; # (섣; 섣; 섣; 섣; 섣; ) HANGUL SYLLABLE SEOD
+C124;C124;1109 1165 11AF;C124;1109 1165 11AF; # (설; 설; 설; 설; 설; ) HANGUL SYLLABLE SEOL
+C125;C125;1109 1165 11B0;C125;1109 1165 11B0; # (섥; 섥; 섥; 섥; 섥; ) HANGUL SYLLABLE SEOLG
+C126;C126;1109 1165 11B1;C126;1109 1165 11B1; # (섦; 섦; 섦; 섦; 섦; ) HANGUL SYLLABLE SEOLM
+C127;C127;1109 1165 11B2;C127;1109 1165 11B2; # (섧; 섧; 섧; 섧; 섧; ) HANGUL SYLLABLE SEOLB
+C128;C128;1109 1165 11B3;C128;1109 1165 11B3; # (섨; 섨; 섨; 섨; 섨; ) HANGUL SYLLABLE SEOLS
+C129;C129;1109 1165 11B4;C129;1109 1165 11B4; # (섩; 섩; 섩; 섩; 섩; ) HANGUL SYLLABLE SEOLT
+C12A;C12A;1109 1165 11B5;C12A;1109 1165 11B5; # (섪; 섪; 섪; 섪; 섪; ) HANGUL SYLLABLE SEOLP
+C12B;C12B;1109 1165 11B6;C12B;1109 1165 11B6; # (섫; 섫; 섫; 섫; 섫; ) HANGUL SYLLABLE SEOLH
+C12C;C12C;1109 1165 11B7;C12C;1109 1165 11B7; # (섬; 섬; 섬; 섬; 섬; ) HANGUL SYLLABLE SEOM
+C12D;C12D;1109 1165 11B8;C12D;1109 1165 11B8; # (섭; 섭; 섭; 섭; 섭; ) HANGUL SYLLABLE SEOB
+C12E;C12E;1109 1165 11B9;C12E;1109 1165 11B9; # (섮; 섮; 섮; 섮; 섮; ) HANGUL SYLLABLE SEOBS
+C12F;C12F;1109 1165 11BA;C12F;1109 1165 11BA; # (섯; 섯; 섯; 섯; 섯; ) HANGUL SYLLABLE SEOS
+C130;C130;1109 1165 11BB;C130;1109 1165 11BB; # (섰; 섰; 섰; 섰; 섰; ) HANGUL SYLLABLE SEOSS
+C131;C131;1109 1165 11BC;C131;1109 1165 11BC; # (성; 성; 성; 성; 성; ) HANGUL SYLLABLE SEONG
+C132;C132;1109 1165 11BD;C132;1109 1165 11BD; # (섲; 섲; 섲; 섲; 섲; ) HANGUL SYLLABLE SEOJ
+C133;C133;1109 1165 11BE;C133;1109 1165 11BE; # (섳; 섳; 섳; 섳; 섳; ) HANGUL SYLLABLE SEOC
+C134;C134;1109 1165 11BF;C134;1109 1165 11BF; # (섴; 섴; 섴; 섴; 섴; ) HANGUL SYLLABLE SEOK
+C135;C135;1109 1165 11C0;C135;1109 1165 11C0; # (섵; 섵; 섵; 섵; 섵; ) HANGUL SYLLABLE SEOT
+C136;C136;1109 1165 11C1;C136;1109 1165 11C1; # (섶; 섶; 서á‡; 섶; 서á‡; ) HANGUL SYLLABLE SEOP
+C137;C137;1109 1165 11C2;C137;1109 1165 11C2; # (섷; 섷; 섷; 섷; 섷; ) HANGUL SYLLABLE SEOH
+C138;C138;1109 1166;C138;1109 1166; # (세; 세; 세; 세; 세; ) HANGUL SYLLABLE SE
+C139;C139;1109 1166 11A8;C139;1109 1166 11A8; # (섹; 섹; 섹; 섹; 섹; ) HANGUL SYLLABLE SEG
+C13A;C13A;1109 1166 11A9;C13A;1109 1166 11A9; # (섺; 섺; 섺; 섺; 섺; ) HANGUL SYLLABLE SEGG
+C13B;C13B;1109 1166 11AA;C13B;1109 1166 11AA; # (섻; 섻; 섻; 섻; 섻; ) HANGUL SYLLABLE SEGS
+C13C;C13C;1109 1166 11AB;C13C;1109 1166 11AB; # (센; 센; 센; 센; 센; ) HANGUL SYLLABLE SEN
+C13D;C13D;1109 1166 11AC;C13D;1109 1166 11AC; # (섽; 섽; 섽; 섽; 섽; ) HANGUL SYLLABLE SENJ
+C13E;C13E;1109 1166 11AD;C13E;1109 1166 11AD; # (섾; 섾; 섾; 섾; 섾; ) HANGUL SYLLABLE SENH
+C13F;C13F;1109 1166 11AE;C13F;1109 1166 11AE; # (섿; 섿; 섿; 섿; 섿; ) HANGUL SYLLABLE SED
+C140;C140;1109 1166 11AF;C140;1109 1166 11AF; # (셀; 셀; 셀; 셀; 셀; ) HANGUL SYLLABLE SEL
+C141;C141;1109 1166 11B0;C141;1109 1166 11B0; # (ì…; ì…; 셁; ì…; 셁; ) HANGUL SYLLABLE SELG
+C142;C142;1109 1166 11B1;C142;1109 1166 11B1; # (셂; 셂; 셂; 셂; 셂; ) HANGUL SYLLABLE SELM
+C143;C143;1109 1166 11B2;C143;1109 1166 11B2; # (셃; 셃; 셃; 셃; 셃; ) HANGUL SYLLABLE SELB
+C144;C144;1109 1166 11B3;C144;1109 1166 11B3; # (셄; 셄; 셄; 셄; 셄; ) HANGUL SYLLABLE SELS
+C145;C145;1109 1166 11B4;C145;1109 1166 11B4; # (셅; 셅; 셅; 셅; 셅; ) HANGUL SYLLABLE SELT
+C146;C146;1109 1166 11B5;C146;1109 1166 11B5; # (셆; 셆; 셆; 셆; 셆; ) HANGUL SYLLABLE SELP
+C147;C147;1109 1166 11B6;C147;1109 1166 11B6; # (셇; 셇; 셇; 셇; 셇; ) HANGUL SYLLABLE SELH
+C148;C148;1109 1166 11B7;C148;1109 1166 11B7; # (셈; 셈; 셈; 셈; 셈; ) HANGUL SYLLABLE SEM
+C149;C149;1109 1166 11B8;C149;1109 1166 11B8; # (셉; 셉; 셉; 셉; 셉; ) HANGUL SYLLABLE SEB
+C14A;C14A;1109 1166 11B9;C14A;1109 1166 11B9; # (셊; 셊; 셊; 셊; 셊; ) HANGUL SYLLABLE SEBS
+C14B;C14B;1109 1166 11BA;C14B;1109 1166 11BA; # (셋; 셋; 셋; 셋; 셋; ) HANGUL SYLLABLE SES
+C14C;C14C;1109 1166 11BB;C14C;1109 1166 11BB; # (셌; 셌; 셌; 셌; 셌; ) HANGUL SYLLABLE SESS
+C14D;C14D;1109 1166 11BC;C14D;1109 1166 11BC; # (ì…; ì…; 셍; ì…; 셍; ) HANGUL SYLLABLE SENG
+C14E;C14E;1109 1166 11BD;C14E;1109 1166 11BD; # (셎; 셎; 셎; 셎; 셎; ) HANGUL SYLLABLE SEJ
+C14F;C14F;1109 1166 11BE;C14F;1109 1166 11BE; # (ì…; ì…; 셏; ì…; 셏; ) HANGUL SYLLABLE SEC
+C150;C150;1109 1166 11BF;C150;1109 1166 11BF; # (ì…; ì…; 셐; ì…; 셐; ) HANGUL SYLLABLE SEK
+C151;C151;1109 1166 11C0;C151;1109 1166 11C0; # (셑; 셑; 셑; 셑; 셑; ) HANGUL SYLLABLE SET
+C152;C152;1109 1166 11C1;C152;1109 1166 11C1; # (ì…’; ì…’; 세á‡; ì…’; 세á‡; ) HANGUL SYLLABLE SEP
+C153;C153;1109 1166 11C2;C153;1109 1166 11C2; # (셓; 셓; 셓; 셓; 셓; ) HANGUL SYLLABLE SEH
+C154;C154;1109 1167;C154;1109 1167; # (셔; 셔; 셔; 셔; 셔; ) HANGUL SYLLABLE SYEO
+C155;C155;1109 1167 11A8;C155;1109 1167 11A8; # (셕; 셕; 셕; 셕; 셕; ) HANGUL SYLLABLE SYEOG
+C156;C156;1109 1167 11A9;C156;1109 1167 11A9; # (셖; 셖; 셖; 셖; 셖; ) HANGUL SYLLABLE SYEOGG
+C157;C157;1109 1167 11AA;C157;1109 1167 11AA; # (셗; 셗; 셗; 셗; 셗; ) HANGUL SYLLABLE SYEOGS
+C158;C158;1109 1167 11AB;C158;1109 1167 11AB; # (션; 션; 션; 션; 션; ) HANGUL SYLLABLE SYEON
+C159;C159;1109 1167 11AC;C159;1109 1167 11AC; # (셙; 셙; 셙; 셙; 셙; ) HANGUL SYLLABLE SYEONJ
+C15A;C15A;1109 1167 11AD;C15A;1109 1167 11AD; # (셚; 셚; 셚; 셚; 셚; ) HANGUL SYLLABLE SYEONH
+C15B;C15B;1109 1167 11AE;C15B;1109 1167 11AE; # (셛; 셛; 셛; 셛; 셛; ) HANGUL SYLLABLE SYEOD
+C15C;C15C;1109 1167 11AF;C15C;1109 1167 11AF; # (셜; 셜; 셜; 셜; 셜; ) HANGUL SYLLABLE SYEOL
+C15D;C15D;1109 1167 11B0;C15D;1109 1167 11B0; # (ì…; ì…; 셝; ì…; 셝; ) HANGUL SYLLABLE SYEOLG
+C15E;C15E;1109 1167 11B1;C15E;1109 1167 11B1; # (셞; 셞; 셞; 셞; 셞; ) HANGUL SYLLABLE SYEOLM
+C15F;C15F;1109 1167 11B2;C15F;1109 1167 11B2; # (셟; 셟; 셟; 셟; 셟; ) HANGUL SYLLABLE SYEOLB
+C160;C160;1109 1167 11B3;C160;1109 1167 11B3; # (셠; 셠; 셠; 셠; 셠; ) HANGUL SYLLABLE SYEOLS
+C161;C161;1109 1167 11B4;C161;1109 1167 11B4; # (셡; 셡; 셡; 셡; 셡; ) HANGUL SYLLABLE SYEOLT
+C162;C162;1109 1167 11B5;C162;1109 1167 11B5; # (셢; 셢; 셢; 셢; 셢; ) HANGUL SYLLABLE SYEOLP
+C163;C163;1109 1167 11B6;C163;1109 1167 11B6; # (셣; 셣; 셣; 셣; 셣; ) HANGUL SYLLABLE SYEOLH
+C164;C164;1109 1167 11B7;C164;1109 1167 11B7; # (셤; 셤; 셤; 셤; 셤; ) HANGUL SYLLABLE SYEOM
+C165;C165;1109 1167 11B8;C165;1109 1167 11B8; # (셥; 셥; 셥; 셥; 셥; ) HANGUL SYLLABLE SYEOB
+C166;C166;1109 1167 11B9;C166;1109 1167 11B9; # (셦; 셦; 셦; 셦; 셦; ) HANGUL SYLLABLE SYEOBS
+C167;C167;1109 1167 11BA;C167;1109 1167 11BA; # (셧; 셧; 셧; 셧; 셧; ) HANGUL SYLLABLE SYEOS
+C168;C168;1109 1167 11BB;C168;1109 1167 11BB; # (셨; 셨; 셨; 셨; 셨; ) HANGUL SYLLABLE SYEOSS
+C169;C169;1109 1167 11BC;C169;1109 1167 11BC; # (셩; 셩; 셩; 셩; 셩; ) HANGUL SYLLABLE SYEONG
+C16A;C16A;1109 1167 11BD;C16A;1109 1167 11BD; # (셪; 셪; 셪; 셪; 셪; ) HANGUL SYLLABLE SYEOJ
+C16B;C16B;1109 1167 11BE;C16B;1109 1167 11BE; # (셫; 셫; 셫; 셫; 셫; ) HANGUL SYLLABLE SYEOC
+C16C;C16C;1109 1167 11BF;C16C;1109 1167 11BF; # (셬; 셬; 셬; 셬; 셬; ) HANGUL SYLLABLE SYEOK
+C16D;C16D;1109 1167 11C0;C16D;1109 1167 11C0; # (셭; 셭; 셭; 셭; 셭; ) HANGUL SYLLABLE SYEOT
+C16E;C16E;1109 1167 11C1;C16E;1109 1167 11C1; # (ì…®; ì…®; 셔á‡; ì…®; 셔á‡; ) HANGUL SYLLABLE SYEOP
+C16F;C16F;1109 1167 11C2;C16F;1109 1167 11C2; # (셯; 셯; 셯; 셯; 셯; ) HANGUL SYLLABLE SYEOH
+C170;C170;1109 1168;C170;1109 1168; # (셰; 셰; 셰; 셰; 셰; ) HANGUL SYLLABLE SYE
+C171;C171;1109 1168 11A8;C171;1109 1168 11A8; # (셱; 셱; 셱; 셱; 셱; ) HANGUL SYLLABLE SYEG
+C172;C172;1109 1168 11A9;C172;1109 1168 11A9; # (셲; 셲; 셲; 셲; 셲; ) HANGUL SYLLABLE SYEGG
+C173;C173;1109 1168 11AA;C173;1109 1168 11AA; # (셳; 셳; 셳; 셳; 셳; ) HANGUL SYLLABLE SYEGS
+C174;C174;1109 1168 11AB;C174;1109 1168 11AB; # (셴; 셴; 셴; 셴; 셴; ) HANGUL SYLLABLE SYEN
+C175;C175;1109 1168 11AC;C175;1109 1168 11AC; # (셵; 셵; 셵; 셵; 셵; ) HANGUL SYLLABLE SYENJ
+C176;C176;1109 1168 11AD;C176;1109 1168 11AD; # (셶; 셶; 셶; 셶; 셶; ) HANGUL SYLLABLE SYENH
+C177;C177;1109 1168 11AE;C177;1109 1168 11AE; # (셷; 셷; 셷; 셷; 셷; ) HANGUL SYLLABLE SYED
+C178;C178;1109 1168 11AF;C178;1109 1168 11AF; # (셸; 셸; 셸; 셸; 셸; ) HANGUL SYLLABLE SYEL
+C179;C179;1109 1168 11B0;C179;1109 1168 11B0; # (셹; 셹; 셹; 셹; 셹; ) HANGUL SYLLABLE SYELG
+C17A;C17A;1109 1168 11B1;C17A;1109 1168 11B1; # (셺; 셺; 셺; 셺; 셺; ) HANGUL SYLLABLE SYELM
+C17B;C17B;1109 1168 11B2;C17B;1109 1168 11B2; # (셻; 셻; 셻; 셻; 셻; ) HANGUL SYLLABLE SYELB
+C17C;C17C;1109 1168 11B3;C17C;1109 1168 11B3; # (셼; 셼; 셼; 셼; 셼; ) HANGUL SYLLABLE SYELS
+C17D;C17D;1109 1168 11B4;C17D;1109 1168 11B4; # (셽; 셽; 셽; 셽; 셽; ) HANGUL SYLLABLE SYELT
+C17E;C17E;1109 1168 11B5;C17E;1109 1168 11B5; # (셾; 셾; 셾; 셾; 셾; ) HANGUL SYLLABLE SYELP
+C17F;C17F;1109 1168 11B6;C17F;1109 1168 11B6; # (셿; 셿; 셿; 셿; 셿; ) HANGUL SYLLABLE SYELH
+C180;C180;1109 1168 11B7;C180;1109 1168 11B7; # (솀; 솀; 솀; 솀; 솀; ) HANGUL SYLLABLE SYEM
+C181;C181;1109 1168 11B8;C181;1109 1168 11B8; # (ì†; ì†; 솁; ì†; 솁; ) HANGUL SYLLABLE SYEB
+C182;C182;1109 1168 11B9;C182;1109 1168 11B9; # (솂; 솂; 솂; 솂; 솂; ) HANGUL SYLLABLE SYEBS
+C183;C183;1109 1168 11BA;C183;1109 1168 11BA; # (솃; 솃; 솃; 솃; 솃; ) HANGUL SYLLABLE SYES
+C184;C184;1109 1168 11BB;C184;1109 1168 11BB; # (솄; 솄; 솄; 솄; 솄; ) HANGUL SYLLABLE SYESS
+C185;C185;1109 1168 11BC;C185;1109 1168 11BC; # (솅; 솅; 솅; 솅; 솅; ) HANGUL SYLLABLE SYENG
+C186;C186;1109 1168 11BD;C186;1109 1168 11BD; # (솆; 솆; 솆; 솆; 솆; ) HANGUL SYLLABLE SYEJ
+C187;C187;1109 1168 11BE;C187;1109 1168 11BE; # (솇; 솇; 솇; 솇; 솇; ) HANGUL SYLLABLE SYEC
+C188;C188;1109 1168 11BF;C188;1109 1168 11BF; # (솈; 솈; 솈; 솈; 솈; ) HANGUL SYLLABLE SYEK
+C189;C189;1109 1168 11C0;C189;1109 1168 11C0; # (솉; 솉; 솉; 솉; 솉; ) HANGUL SYLLABLE SYET
+C18A;C18A;1109 1168 11C1;C18A;1109 1168 11C1; # (솊; 솊; 셰á‡; 솊; 셰á‡; ) HANGUL SYLLABLE SYEP
+C18B;C18B;1109 1168 11C2;C18B;1109 1168 11C2; # (솋; 솋; 솋; 솋; 솋; ) HANGUL SYLLABLE SYEH
+C18C;C18C;1109 1169;C18C;1109 1169; # (소; 소; 소; 소; 소; ) HANGUL SYLLABLE SO
+C18D;C18D;1109 1169 11A8;C18D;1109 1169 11A8; # (ì†; ì†; 속; ì†; 속; ) HANGUL SYLLABLE SOG
+C18E;C18E;1109 1169 11A9;C18E;1109 1169 11A9; # (솎; 솎; 솎; 솎; 솎; ) HANGUL SYLLABLE SOGG
+C18F;C18F;1109 1169 11AA;C18F;1109 1169 11AA; # (ì†; ì†; 솏; ì†; 솏; ) HANGUL SYLLABLE SOGS
+C190;C190;1109 1169 11AB;C190;1109 1169 11AB; # (ì†; ì†; 손; ì†; 손; ) HANGUL SYLLABLE SON
+C191;C191;1109 1169 11AC;C191;1109 1169 11AC; # (솑; 솑; 솑; 솑; 솑; ) HANGUL SYLLABLE SONJ
+C192;C192;1109 1169 11AD;C192;1109 1169 11AD; # (솒; 솒; 솒; 솒; 솒; ) HANGUL SYLLABLE SONH
+C193;C193;1109 1169 11AE;C193;1109 1169 11AE; # (솓; 솓; 솓; 솓; 솓; ) HANGUL SYLLABLE SOD
+C194;C194;1109 1169 11AF;C194;1109 1169 11AF; # (솔; 솔; 솔; 솔; 솔; ) HANGUL SYLLABLE SOL
+C195;C195;1109 1169 11B0;C195;1109 1169 11B0; # (솕; 솕; 솕; 솕; 솕; ) HANGUL SYLLABLE SOLG
+C196;C196;1109 1169 11B1;C196;1109 1169 11B1; # (솖; 솖; 솖; 솖; 솖; ) HANGUL SYLLABLE SOLM
+C197;C197;1109 1169 11B2;C197;1109 1169 11B2; # (솗; 솗; 솗; 솗; 솗; ) HANGUL SYLLABLE SOLB
+C198;C198;1109 1169 11B3;C198;1109 1169 11B3; # (솘; 솘; 솘; 솘; 솘; ) HANGUL SYLLABLE SOLS
+C199;C199;1109 1169 11B4;C199;1109 1169 11B4; # (솙; 솙; 솙; 솙; 솙; ) HANGUL SYLLABLE SOLT
+C19A;C19A;1109 1169 11B5;C19A;1109 1169 11B5; # (솚; 솚; 솚; 솚; 솚; ) HANGUL SYLLABLE SOLP
+C19B;C19B;1109 1169 11B6;C19B;1109 1169 11B6; # (솛; 솛; 솛; 솛; 솛; ) HANGUL SYLLABLE SOLH
+C19C;C19C;1109 1169 11B7;C19C;1109 1169 11B7; # (솜; 솜; 솜; 솜; 솜; ) HANGUL SYLLABLE SOM
+C19D;C19D;1109 1169 11B8;C19D;1109 1169 11B8; # (ì†; ì†; 솝; ì†; 솝; ) HANGUL SYLLABLE SOB
+C19E;C19E;1109 1169 11B9;C19E;1109 1169 11B9; # (솞; 솞; 솞; 솞; 솞; ) HANGUL SYLLABLE SOBS
+C19F;C19F;1109 1169 11BA;C19F;1109 1169 11BA; # (솟; 솟; 솟; 솟; 솟; ) HANGUL SYLLABLE SOS
+C1A0;C1A0;1109 1169 11BB;C1A0;1109 1169 11BB; # (솠; 솠; 솠; 솠; 솠; ) HANGUL SYLLABLE SOSS
+C1A1;C1A1;1109 1169 11BC;C1A1;1109 1169 11BC; # (송; 송; 송; 송; 송; ) HANGUL SYLLABLE SONG
+C1A2;C1A2;1109 1169 11BD;C1A2;1109 1169 11BD; # (솢; 솢; 솢; 솢; 솢; ) HANGUL SYLLABLE SOJ
+C1A3;C1A3;1109 1169 11BE;C1A3;1109 1169 11BE; # (솣; 솣; 솣; 솣; 솣; ) HANGUL SYLLABLE SOC
+C1A4;C1A4;1109 1169 11BF;C1A4;1109 1169 11BF; # (솤; 솤; 솤; 솤; 솤; ) HANGUL SYLLABLE SOK
+C1A5;C1A5;1109 1169 11C0;C1A5;1109 1169 11C0; # (솥; 솥; 솥; 솥; 솥; ) HANGUL SYLLABLE SOT
+C1A6;C1A6;1109 1169 11C1;C1A6;1109 1169 11C1; # (솦; 솦; 소á‡; 솦; 소á‡; ) HANGUL SYLLABLE SOP
+C1A7;C1A7;1109 1169 11C2;C1A7;1109 1169 11C2; # (솧; 솧; 솧; 솧; 솧; ) HANGUL SYLLABLE SOH
+C1A8;C1A8;1109 116A;C1A8;1109 116A; # (솨; 솨; 솨; 솨; 솨; ) HANGUL SYLLABLE SWA
+C1A9;C1A9;1109 116A 11A8;C1A9;1109 116A 11A8; # (솩; 솩; 솩; 솩; 솩; ) HANGUL SYLLABLE SWAG
+C1AA;C1AA;1109 116A 11A9;C1AA;1109 116A 11A9; # (솪; 솪; 솪; 솪; 솪; ) HANGUL SYLLABLE SWAGG
+C1AB;C1AB;1109 116A 11AA;C1AB;1109 116A 11AA; # (솫; 솫; 솫; 솫; 솫; ) HANGUL SYLLABLE SWAGS
+C1AC;C1AC;1109 116A 11AB;C1AC;1109 116A 11AB; # (솬; 솬; 솬; 솬; 솬; ) HANGUL SYLLABLE SWAN
+C1AD;C1AD;1109 116A 11AC;C1AD;1109 116A 11AC; # (솭; 솭; 솭; 솭; 솭; ) HANGUL SYLLABLE SWANJ
+C1AE;C1AE;1109 116A 11AD;C1AE;1109 116A 11AD; # (솮; 솮; 솮; 솮; 솮; ) HANGUL SYLLABLE SWANH
+C1AF;C1AF;1109 116A 11AE;C1AF;1109 116A 11AE; # (솯; 솯; 솯; 솯; 솯; ) HANGUL SYLLABLE SWAD
+C1B0;C1B0;1109 116A 11AF;C1B0;1109 116A 11AF; # (솰; 솰; 솰; 솰; 솰; ) HANGUL SYLLABLE SWAL
+C1B1;C1B1;1109 116A 11B0;C1B1;1109 116A 11B0; # (솱; 솱; 솱; 솱; 솱; ) HANGUL SYLLABLE SWALG
+C1B2;C1B2;1109 116A 11B1;C1B2;1109 116A 11B1; # (솲; 솲; 솲; 솲; 솲; ) HANGUL SYLLABLE SWALM
+C1B3;C1B3;1109 116A 11B2;C1B3;1109 116A 11B2; # (솳; 솳; 솳; 솳; 솳; ) HANGUL SYLLABLE SWALB
+C1B4;C1B4;1109 116A 11B3;C1B4;1109 116A 11B3; # (솴; 솴; 솴; 솴; 솴; ) HANGUL SYLLABLE SWALS
+C1B5;C1B5;1109 116A 11B4;C1B5;1109 116A 11B4; # (솵; 솵; 솵; 솵; 솵; ) HANGUL SYLLABLE SWALT
+C1B6;C1B6;1109 116A 11B5;C1B6;1109 116A 11B5; # (솶; 솶; 솶; 솶; 솶; ) HANGUL SYLLABLE SWALP
+C1B7;C1B7;1109 116A 11B6;C1B7;1109 116A 11B6; # (솷; 솷; 솷; 솷; 솷; ) HANGUL SYLLABLE SWALH
+C1B8;C1B8;1109 116A 11B7;C1B8;1109 116A 11B7; # (솸; 솸; 솸; 솸; 솸; ) HANGUL SYLLABLE SWAM
+C1B9;C1B9;1109 116A 11B8;C1B9;1109 116A 11B8; # (솹; 솹; 솹; 솹; 솹; ) HANGUL SYLLABLE SWAB
+C1BA;C1BA;1109 116A 11B9;C1BA;1109 116A 11B9; # (솺; 솺; 솺; 솺; 솺; ) HANGUL SYLLABLE SWABS
+C1BB;C1BB;1109 116A 11BA;C1BB;1109 116A 11BA; # (솻; 솻; 솻; 솻; 솻; ) HANGUL SYLLABLE SWAS
+C1BC;C1BC;1109 116A 11BB;C1BC;1109 116A 11BB; # (솼; 솼; 솼; 솼; 솼; ) HANGUL SYLLABLE SWASS
+C1BD;C1BD;1109 116A 11BC;C1BD;1109 116A 11BC; # (솽; 솽; 솽; 솽; 솽; ) HANGUL SYLLABLE SWANG
+C1BE;C1BE;1109 116A 11BD;C1BE;1109 116A 11BD; # (솾; 솾; 솾; 솾; 솾; ) HANGUL SYLLABLE SWAJ
+C1BF;C1BF;1109 116A 11BE;C1BF;1109 116A 11BE; # (솿; 솿; 솿; 솿; 솿; ) HANGUL SYLLABLE SWAC
+C1C0;C1C0;1109 116A 11BF;C1C0;1109 116A 11BF; # (쇀; 쇀; 쇀; 쇀; 쇀; ) HANGUL SYLLABLE SWAK
+C1C1;C1C1;1109 116A 11C0;C1C1;1109 116A 11C0; # (ì‡; ì‡; 쇁; ì‡; 쇁; ) HANGUL SYLLABLE SWAT
+C1C2;C1C2;1109 116A 11C1;C1C2;1109 116A 11C1; # (쇂; 쇂; 솨á‡; 쇂; 솨á‡; ) HANGUL SYLLABLE SWAP
+C1C3;C1C3;1109 116A 11C2;C1C3;1109 116A 11C2; # (쇃; 쇃; 쇃; 쇃; 쇃; ) HANGUL SYLLABLE SWAH
+C1C4;C1C4;1109 116B;C1C4;1109 116B; # (쇄; 쇄; 쇄; 쇄; 쇄; ) HANGUL SYLLABLE SWAE
+C1C5;C1C5;1109 116B 11A8;C1C5;1109 116B 11A8; # (쇅; 쇅; 쇅; 쇅; 쇅; ) HANGUL SYLLABLE SWAEG
+C1C6;C1C6;1109 116B 11A9;C1C6;1109 116B 11A9; # (쇆; 쇆; 쇆; 쇆; 쇆; ) HANGUL SYLLABLE SWAEGG
+C1C7;C1C7;1109 116B 11AA;C1C7;1109 116B 11AA; # (쇇; 쇇; 쇇; 쇇; 쇇; ) HANGUL SYLLABLE SWAEGS
+C1C8;C1C8;1109 116B 11AB;C1C8;1109 116B 11AB; # (쇈; 쇈; 쇈; 쇈; 쇈; ) HANGUL SYLLABLE SWAEN
+C1C9;C1C9;1109 116B 11AC;C1C9;1109 116B 11AC; # (쇉; 쇉; 쇉; 쇉; 쇉; ) HANGUL SYLLABLE SWAENJ
+C1CA;C1CA;1109 116B 11AD;C1CA;1109 116B 11AD; # (쇊; 쇊; 쇊; 쇊; 쇊; ) HANGUL SYLLABLE SWAENH
+C1CB;C1CB;1109 116B 11AE;C1CB;1109 116B 11AE; # (쇋; 쇋; 쇋; 쇋; 쇋; ) HANGUL SYLLABLE SWAED
+C1CC;C1CC;1109 116B 11AF;C1CC;1109 116B 11AF; # (쇌; 쇌; 쇌; 쇌; 쇌; ) HANGUL SYLLABLE SWAEL
+C1CD;C1CD;1109 116B 11B0;C1CD;1109 116B 11B0; # (ì‡; ì‡; 쇍; ì‡; 쇍; ) HANGUL SYLLABLE SWAELG
+C1CE;C1CE;1109 116B 11B1;C1CE;1109 116B 11B1; # (쇎; 쇎; 쇎; 쇎; 쇎; ) HANGUL SYLLABLE SWAELM
+C1CF;C1CF;1109 116B 11B2;C1CF;1109 116B 11B2; # (ì‡; ì‡; 쇏; ì‡; 쇏; ) HANGUL SYLLABLE SWAELB
+C1D0;C1D0;1109 116B 11B3;C1D0;1109 116B 11B3; # (ì‡; ì‡; 쇐; ì‡; 쇐; ) HANGUL SYLLABLE SWAELS
+C1D1;C1D1;1109 116B 11B4;C1D1;1109 116B 11B4; # (쇑; 쇑; 쇑; 쇑; 쇑; ) HANGUL SYLLABLE SWAELT
+C1D2;C1D2;1109 116B 11B5;C1D2;1109 116B 11B5; # (쇒; 쇒; 쇒; 쇒; 쇒; ) HANGUL SYLLABLE SWAELP
+C1D3;C1D3;1109 116B 11B6;C1D3;1109 116B 11B6; # (쇓; 쇓; 쇓; 쇓; 쇓; ) HANGUL SYLLABLE SWAELH
+C1D4;C1D4;1109 116B 11B7;C1D4;1109 116B 11B7; # (쇔; 쇔; 쇔; 쇔; 쇔; ) HANGUL SYLLABLE SWAEM
+C1D5;C1D5;1109 116B 11B8;C1D5;1109 116B 11B8; # (쇕; 쇕; 쇕; 쇕; 쇕; ) HANGUL SYLLABLE SWAEB
+C1D6;C1D6;1109 116B 11B9;C1D6;1109 116B 11B9; # (쇖; 쇖; 쇖; 쇖; 쇖; ) HANGUL SYLLABLE SWAEBS
+C1D7;C1D7;1109 116B 11BA;C1D7;1109 116B 11BA; # (쇗; 쇗; 쇗; 쇗; 쇗; ) HANGUL SYLLABLE SWAES
+C1D8;C1D8;1109 116B 11BB;C1D8;1109 116B 11BB; # (쇘; 쇘; 쇘; 쇘; 쇘; ) HANGUL SYLLABLE SWAESS
+C1D9;C1D9;1109 116B 11BC;C1D9;1109 116B 11BC; # (쇙; 쇙; 쇙; 쇙; 쇙; ) HANGUL SYLLABLE SWAENG
+C1DA;C1DA;1109 116B 11BD;C1DA;1109 116B 11BD; # (쇚; 쇚; 쇚; 쇚; 쇚; ) HANGUL SYLLABLE SWAEJ
+C1DB;C1DB;1109 116B 11BE;C1DB;1109 116B 11BE; # (쇛; 쇛; 쇛; 쇛; 쇛; ) HANGUL SYLLABLE SWAEC
+C1DC;C1DC;1109 116B 11BF;C1DC;1109 116B 11BF; # (쇜; 쇜; 쇜; 쇜; 쇜; ) HANGUL SYLLABLE SWAEK
+C1DD;C1DD;1109 116B 11C0;C1DD;1109 116B 11C0; # (ì‡; ì‡; 쇝; ì‡; 쇝; ) HANGUL SYLLABLE SWAET
+C1DE;C1DE;1109 116B 11C1;C1DE;1109 116B 11C1; # (쇞; 쇞; 쇄á‡; 쇞; 쇄á‡; ) HANGUL SYLLABLE SWAEP
+C1DF;C1DF;1109 116B 11C2;C1DF;1109 116B 11C2; # (쇟; 쇟; 쇟; 쇟; 쇟; ) HANGUL SYLLABLE SWAEH
+C1E0;C1E0;1109 116C;C1E0;1109 116C; # (쇠; 쇠; 쇠; 쇠; 쇠; ) HANGUL SYLLABLE SOE
+C1E1;C1E1;1109 116C 11A8;C1E1;1109 116C 11A8; # (쇡; 쇡; 쇡; 쇡; 쇡; ) HANGUL SYLLABLE SOEG
+C1E2;C1E2;1109 116C 11A9;C1E2;1109 116C 11A9; # (쇢; 쇢; 쇢; 쇢; 쇢; ) HANGUL SYLLABLE SOEGG
+C1E3;C1E3;1109 116C 11AA;C1E3;1109 116C 11AA; # (쇣; 쇣; 쇣; 쇣; 쇣; ) HANGUL SYLLABLE SOEGS
+C1E4;C1E4;1109 116C 11AB;C1E4;1109 116C 11AB; # (쇤; 쇤; 쇤; 쇤; 쇤; ) HANGUL SYLLABLE SOEN
+C1E5;C1E5;1109 116C 11AC;C1E5;1109 116C 11AC; # (쇥; 쇥; 쇥; 쇥; 쇥; ) HANGUL SYLLABLE SOENJ
+C1E6;C1E6;1109 116C 11AD;C1E6;1109 116C 11AD; # (쇦; 쇦; 쇦; 쇦; 쇦; ) HANGUL SYLLABLE SOENH
+C1E7;C1E7;1109 116C 11AE;C1E7;1109 116C 11AE; # (쇧; 쇧; 쇧; 쇧; 쇧; ) HANGUL SYLLABLE SOED
+C1E8;C1E8;1109 116C 11AF;C1E8;1109 116C 11AF; # (쇨; 쇨; 쇨; 쇨; 쇨; ) HANGUL SYLLABLE SOEL
+C1E9;C1E9;1109 116C 11B0;C1E9;1109 116C 11B0; # (쇩; 쇩; 쇩; 쇩; 쇩; ) HANGUL SYLLABLE SOELG
+C1EA;C1EA;1109 116C 11B1;C1EA;1109 116C 11B1; # (쇪; 쇪; 쇪; 쇪; 쇪; ) HANGUL SYLLABLE SOELM
+C1EB;C1EB;1109 116C 11B2;C1EB;1109 116C 11B2; # (쇫; 쇫; 쇫; 쇫; 쇫; ) HANGUL SYLLABLE SOELB
+C1EC;C1EC;1109 116C 11B3;C1EC;1109 116C 11B3; # (쇬; 쇬; 쇬; 쇬; 쇬; ) HANGUL SYLLABLE SOELS
+C1ED;C1ED;1109 116C 11B4;C1ED;1109 116C 11B4; # (쇭; 쇭; 쇭; 쇭; 쇭; ) HANGUL SYLLABLE SOELT
+C1EE;C1EE;1109 116C 11B5;C1EE;1109 116C 11B5; # (쇮; 쇮; 쇮; 쇮; 쇮; ) HANGUL SYLLABLE SOELP
+C1EF;C1EF;1109 116C 11B6;C1EF;1109 116C 11B6; # (쇯; 쇯; 쇯; 쇯; 쇯; ) HANGUL SYLLABLE SOELH
+C1F0;C1F0;1109 116C 11B7;C1F0;1109 116C 11B7; # (쇰; 쇰; 쇰; 쇰; 쇰; ) HANGUL SYLLABLE SOEM
+C1F1;C1F1;1109 116C 11B8;C1F1;1109 116C 11B8; # (쇱; 쇱; 쇱; 쇱; 쇱; ) HANGUL SYLLABLE SOEB
+C1F2;C1F2;1109 116C 11B9;C1F2;1109 116C 11B9; # (쇲; 쇲; 쇲; 쇲; 쇲; ) HANGUL SYLLABLE SOEBS
+C1F3;C1F3;1109 116C 11BA;C1F3;1109 116C 11BA; # (쇳; 쇳; 쇳; 쇳; 쇳; ) HANGUL SYLLABLE SOES
+C1F4;C1F4;1109 116C 11BB;C1F4;1109 116C 11BB; # (쇴; 쇴; 쇴; 쇴; 쇴; ) HANGUL SYLLABLE SOESS
+C1F5;C1F5;1109 116C 11BC;C1F5;1109 116C 11BC; # (쇵; 쇵; 쇵; 쇵; 쇵; ) HANGUL SYLLABLE SOENG
+C1F6;C1F6;1109 116C 11BD;C1F6;1109 116C 11BD; # (쇶; 쇶; 쇶; 쇶; 쇶; ) HANGUL SYLLABLE SOEJ
+C1F7;C1F7;1109 116C 11BE;C1F7;1109 116C 11BE; # (쇷; 쇷; 쇷; 쇷; 쇷; ) HANGUL SYLLABLE SOEC
+C1F8;C1F8;1109 116C 11BF;C1F8;1109 116C 11BF; # (쇸; 쇸; 쇸; 쇸; 쇸; ) HANGUL SYLLABLE SOEK
+C1F9;C1F9;1109 116C 11C0;C1F9;1109 116C 11C0; # (쇹; 쇹; 쇹; 쇹; 쇹; ) HANGUL SYLLABLE SOET
+C1FA;C1FA;1109 116C 11C1;C1FA;1109 116C 11C1; # (쇺; 쇺; 쇠á‡; 쇺; 쇠á‡; ) HANGUL SYLLABLE SOEP
+C1FB;C1FB;1109 116C 11C2;C1FB;1109 116C 11C2; # (쇻; 쇻; 쇻; 쇻; 쇻; ) HANGUL SYLLABLE SOEH
+C1FC;C1FC;1109 116D;C1FC;1109 116D; # (쇼; 쇼; 쇼; 쇼; 쇼; ) HANGUL SYLLABLE SYO
+C1FD;C1FD;1109 116D 11A8;C1FD;1109 116D 11A8; # (쇽; 쇽; 쇽; 쇽; 쇽; ) HANGUL SYLLABLE SYOG
+C1FE;C1FE;1109 116D 11A9;C1FE;1109 116D 11A9; # (쇾; 쇾; 쇾; 쇾; 쇾; ) HANGUL SYLLABLE SYOGG
+C1FF;C1FF;1109 116D 11AA;C1FF;1109 116D 11AA; # (쇿; 쇿; 쇿; 쇿; 쇿; ) HANGUL SYLLABLE SYOGS
+C200;C200;1109 116D 11AB;C200;1109 116D 11AB; # (숀; 숀; 숀; 숀; 숀; ) HANGUL SYLLABLE SYON
+C201;C201;1109 116D 11AC;C201;1109 116D 11AC; # (ìˆ; ìˆ; 숁; ìˆ; 숁; ) HANGUL SYLLABLE SYONJ
+C202;C202;1109 116D 11AD;C202;1109 116D 11AD; # (숂; 숂; 숂; 숂; 숂; ) HANGUL SYLLABLE SYONH
+C203;C203;1109 116D 11AE;C203;1109 116D 11AE; # (숃; 숃; 숃; 숃; 숃; ) HANGUL SYLLABLE SYOD
+C204;C204;1109 116D 11AF;C204;1109 116D 11AF; # (숄; 숄; 숄; 숄; 숄; ) HANGUL SYLLABLE SYOL
+C205;C205;1109 116D 11B0;C205;1109 116D 11B0; # (숅; 숅; 숅; 숅; 숅; ) HANGUL SYLLABLE SYOLG
+C206;C206;1109 116D 11B1;C206;1109 116D 11B1; # (숆; 숆; 숆; 숆; 숆; ) HANGUL SYLLABLE SYOLM
+C207;C207;1109 116D 11B2;C207;1109 116D 11B2; # (숇; 숇; 숇; 숇; 숇; ) HANGUL SYLLABLE SYOLB
+C208;C208;1109 116D 11B3;C208;1109 116D 11B3; # (숈; 숈; 숈; 숈; 숈; ) HANGUL SYLLABLE SYOLS
+C209;C209;1109 116D 11B4;C209;1109 116D 11B4; # (숉; 숉; 숉; 숉; 숉; ) HANGUL SYLLABLE SYOLT
+C20A;C20A;1109 116D 11B5;C20A;1109 116D 11B5; # (숊; 숊; 숊; 숊; 숊; ) HANGUL SYLLABLE SYOLP
+C20B;C20B;1109 116D 11B6;C20B;1109 116D 11B6; # (숋; 숋; 숋; 숋; 숋; ) HANGUL SYLLABLE SYOLH
+C20C;C20C;1109 116D 11B7;C20C;1109 116D 11B7; # (숌; 숌; 숌; 숌; 숌; ) HANGUL SYLLABLE SYOM
+C20D;C20D;1109 116D 11B8;C20D;1109 116D 11B8; # (ìˆ; ìˆ; 숍; ìˆ; 숍; ) HANGUL SYLLABLE SYOB
+C20E;C20E;1109 116D 11B9;C20E;1109 116D 11B9; # (숎; 숎; 숎; 숎; 숎; ) HANGUL SYLLABLE SYOBS
+C20F;C20F;1109 116D 11BA;C20F;1109 116D 11BA; # (ìˆ; ìˆ; 숏; ìˆ; 숏; ) HANGUL SYLLABLE SYOS
+C210;C210;1109 116D 11BB;C210;1109 116D 11BB; # (ìˆ; ìˆ; 숐; ìˆ; 숐; ) HANGUL SYLLABLE SYOSS
+C211;C211;1109 116D 11BC;C211;1109 116D 11BC; # (숑; 숑; 숑; 숑; 숑; ) HANGUL SYLLABLE SYONG
+C212;C212;1109 116D 11BD;C212;1109 116D 11BD; # (숒; 숒; 숒; 숒; 숒; ) HANGUL SYLLABLE SYOJ
+C213;C213;1109 116D 11BE;C213;1109 116D 11BE; # (숓; 숓; 숓; 숓; 숓; ) HANGUL SYLLABLE SYOC
+C214;C214;1109 116D 11BF;C214;1109 116D 11BF; # (숔; 숔; 숔; 숔; 숔; ) HANGUL SYLLABLE SYOK
+C215;C215;1109 116D 11C0;C215;1109 116D 11C0; # (숕; 숕; 숕; 숕; 숕; ) HANGUL SYLLABLE SYOT
+C216;C216;1109 116D 11C1;C216;1109 116D 11C1; # (숖; 숖; 쇼á‡; 숖; 쇼á‡; ) HANGUL SYLLABLE SYOP
+C217;C217;1109 116D 11C2;C217;1109 116D 11C2; # (숗; 숗; 숗; 숗; 숗; ) HANGUL SYLLABLE SYOH
+C218;C218;1109 116E;C218;1109 116E; # (수; 수; 수; 수; 수; ) HANGUL SYLLABLE SU
+C219;C219;1109 116E 11A8;C219;1109 116E 11A8; # (숙; 숙; 숙; 숙; 숙; ) HANGUL SYLLABLE SUG
+C21A;C21A;1109 116E 11A9;C21A;1109 116E 11A9; # (숚; 숚; 숚; 숚; 숚; ) HANGUL SYLLABLE SUGG
+C21B;C21B;1109 116E 11AA;C21B;1109 116E 11AA; # (숛; 숛; 숛; 숛; 숛; ) HANGUL SYLLABLE SUGS
+C21C;C21C;1109 116E 11AB;C21C;1109 116E 11AB; # (순; 순; 순; 순; 순; ) HANGUL SYLLABLE SUN
+C21D;C21D;1109 116E 11AC;C21D;1109 116E 11AC; # (ìˆ; ìˆ; 숝; ìˆ; 숝; ) HANGUL SYLLABLE SUNJ
+C21E;C21E;1109 116E 11AD;C21E;1109 116E 11AD; # (숞; 숞; 숞; 숞; 숞; ) HANGUL SYLLABLE SUNH
+C21F;C21F;1109 116E 11AE;C21F;1109 116E 11AE; # (숟; 숟; 숟; 숟; 숟; ) HANGUL SYLLABLE SUD
+C220;C220;1109 116E 11AF;C220;1109 116E 11AF; # (술; 술; 술; 술; 술; ) HANGUL SYLLABLE SUL
+C221;C221;1109 116E 11B0;C221;1109 116E 11B0; # (숡; 숡; 숡; 숡; 숡; ) HANGUL SYLLABLE SULG
+C222;C222;1109 116E 11B1;C222;1109 116E 11B1; # (숢; 숢; 숢; 숢; 숢; ) HANGUL SYLLABLE SULM
+C223;C223;1109 116E 11B2;C223;1109 116E 11B2; # (숣; 숣; 숣; 숣; 숣; ) HANGUL SYLLABLE SULB
+C224;C224;1109 116E 11B3;C224;1109 116E 11B3; # (숤; 숤; 숤; 숤; 숤; ) HANGUL SYLLABLE SULS
+C225;C225;1109 116E 11B4;C225;1109 116E 11B4; # (숥; 숥; 숥; 숥; 숥; ) HANGUL SYLLABLE SULT
+C226;C226;1109 116E 11B5;C226;1109 116E 11B5; # (숦; 숦; 숦; 숦; 숦; ) HANGUL SYLLABLE SULP
+C227;C227;1109 116E 11B6;C227;1109 116E 11B6; # (숧; 숧; 숧; 숧; 숧; ) HANGUL SYLLABLE SULH
+C228;C228;1109 116E 11B7;C228;1109 116E 11B7; # (숨; 숨; 숨; 숨; 숨; ) HANGUL SYLLABLE SUM
+C229;C229;1109 116E 11B8;C229;1109 116E 11B8; # (숩; 숩; 숩; 숩; 숩; ) HANGUL SYLLABLE SUB
+C22A;C22A;1109 116E 11B9;C22A;1109 116E 11B9; # (숪; 숪; 숪; 숪; 숪; ) HANGUL SYLLABLE SUBS
+C22B;C22B;1109 116E 11BA;C22B;1109 116E 11BA; # (숫; 숫; 숫; 숫; 숫; ) HANGUL SYLLABLE SUS
+C22C;C22C;1109 116E 11BB;C22C;1109 116E 11BB; # (숬; 숬; 숬; 숬; 숬; ) HANGUL SYLLABLE SUSS
+C22D;C22D;1109 116E 11BC;C22D;1109 116E 11BC; # (숭; 숭; 숭; 숭; 숭; ) HANGUL SYLLABLE SUNG
+C22E;C22E;1109 116E 11BD;C22E;1109 116E 11BD; # (숮; 숮; 숮; 숮; 숮; ) HANGUL SYLLABLE SUJ
+C22F;C22F;1109 116E 11BE;C22F;1109 116E 11BE; # (숯; 숯; 숯; 숯; 숯; ) HANGUL SYLLABLE SUC
+C230;C230;1109 116E 11BF;C230;1109 116E 11BF; # (숰; 숰; 숰; 숰; 숰; ) HANGUL SYLLABLE SUK
+C231;C231;1109 116E 11C0;C231;1109 116E 11C0; # (숱; 숱; 숱; 숱; 숱; ) HANGUL SYLLABLE SUT
+C232;C232;1109 116E 11C1;C232;1109 116E 11C1; # (숲; 숲; 수á‡; 숲; 수á‡; ) HANGUL SYLLABLE SUP
+C233;C233;1109 116E 11C2;C233;1109 116E 11C2; # (숳; 숳; 숳; 숳; 숳; ) HANGUL SYLLABLE SUH
+C234;C234;1109 116F;C234;1109 116F; # (숴; 숴; 숴; 숴; 숴; ) HANGUL SYLLABLE SWEO
+C235;C235;1109 116F 11A8;C235;1109 116F 11A8; # (숵; 숵; 숵; 숵; 숵; ) HANGUL SYLLABLE SWEOG
+C236;C236;1109 116F 11A9;C236;1109 116F 11A9; # (숶; 숶; 숶; 숶; 숶; ) HANGUL SYLLABLE SWEOGG
+C237;C237;1109 116F 11AA;C237;1109 116F 11AA; # (숷; 숷; 숷; 숷; 숷; ) HANGUL SYLLABLE SWEOGS
+C238;C238;1109 116F 11AB;C238;1109 116F 11AB; # (숸; 숸; 숸; 숸; 숸; ) HANGUL SYLLABLE SWEON
+C239;C239;1109 116F 11AC;C239;1109 116F 11AC; # (숹; 숹; 숹; 숹; 숹; ) HANGUL SYLLABLE SWEONJ
+C23A;C23A;1109 116F 11AD;C23A;1109 116F 11AD; # (숺; 숺; 숺; 숺; 숺; ) HANGUL SYLLABLE SWEONH
+C23B;C23B;1109 116F 11AE;C23B;1109 116F 11AE; # (숻; 숻; 숻; 숻; 숻; ) HANGUL SYLLABLE SWEOD
+C23C;C23C;1109 116F 11AF;C23C;1109 116F 11AF; # (숼; 숼; 숼; 숼; 숼; ) HANGUL SYLLABLE SWEOL
+C23D;C23D;1109 116F 11B0;C23D;1109 116F 11B0; # (숽; 숽; 숽; 숽; 숽; ) HANGUL SYLLABLE SWEOLG
+C23E;C23E;1109 116F 11B1;C23E;1109 116F 11B1; # (숾; 숾; 숾; 숾; 숾; ) HANGUL SYLLABLE SWEOLM
+C23F;C23F;1109 116F 11B2;C23F;1109 116F 11B2; # (숿; 숿; 숿; 숿; 숿; ) HANGUL SYLLABLE SWEOLB
+C240;C240;1109 116F 11B3;C240;1109 116F 11B3; # (쉀; 쉀; 쉀; 쉀; 쉀; ) HANGUL SYLLABLE SWEOLS
+C241;C241;1109 116F 11B4;C241;1109 116F 11B4; # (ì‰; ì‰; 쉁; ì‰; 쉁; ) HANGUL SYLLABLE SWEOLT
+C242;C242;1109 116F 11B5;C242;1109 116F 11B5; # (쉂; 쉂; 쉂; 쉂; 쉂; ) HANGUL SYLLABLE SWEOLP
+C243;C243;1109 116F 11B6;C243;1109 116F 11B6; # (쉃; 쉃; 쉃; 쉃; 쉃; ) HANGUL SYLLABLE SWEOLH
+C244;C244;1109 116F 11B7;C244;1109 116F 11B7; # (쉄; 쉄; 쉄; 쉄; 쉄; ) HANGUL SYLLABLE SWEOM
+C245;C245;1109 116F 11B8;C245;1109 116F 11B8; # (쉅; 쉅; 쉅; 쉅; 쉅; ) HANGUL SYLLABLE SWEOB
+C246;C246;1109 116F 11B9;C246;1109 116F 11B9; # (쉆; 쉆; 쉆; 쉆; 쉆; ) HANGUL SYLLABLE SWEOBS
+C247;C247;1109 116F 11BA;C247;1109 116F 11BA; # (쉇; 쉇; 쉇; 쉇; 쉇; ) HANGUL SYLLABLE SWEOS
+C248;C248;1109 116F 11BB;C248;1109 116F 11BB; # (쉈; 쉈; 쉈; 쉈; 쉈; ) HANGUL SYLLABLE SWEOSS
+C249;C249;1109 116F 11BC;C249;1109 116F 11BC; # (쉉; 쉉; 쉉; 쉉; 쉉; ) HANGUL SYLLABLE SWEONG
+C24A;C24A;1109 116F 11BD;C24A;1109 116F 11BD; # (쉊; 쉊; 쉊; 쉊; 쉊; ) HANGUL SYLLABLE SWEOJ
+C24B;C24B;1109 116F 11BE;C24B;1109 116F 11BE; # (쉋; 쉋; 쉋; 쉋; 쉋; ) HANGUL SYLLABLE SWEOC
+C24C;C24C;1109 116F 11BF;C24C;1109 116F 11BF; # (쉌; 쉌; 쉌; 쉌; 쉌; ) HANGUL SYLLABLE SWEOK
+C24D;C24D;1109 116F 11C0;C24D;1109 116F 11C0; # (ì‰; ì‰; 쉍; ì‰; 쉍; ) HANGUL SYLLABLE SWEOT
+C24E;C24E;1109 116F 11C1;C24E;1109 116F 11C1; # (쉎; 쉎; 숴á‡; 쉎; 숴á‡; ) HANGUL SYLLABLE SWEOP
+C24F;C24F;1109 116F 11C2;C24F;1109 116F 11C2; # (ì‰; ì‰; 쉏; ì‰; 쉏; ) HANGUL SYLLABLE SWEOH
+C250;C250;1109 1170;C250;1109 1170; # (ì‰; ì‰; 쉐; ì‰; 쉐; ) HANGUL SYLLABLE SWE
+C251;C251;1109 1170 11A8;C251;1109 1170 11A8; # (쉑; 쉑; 쉑; 쉑; 쉑; ) HANGUL SYLLABLE SWEG
+C252;C252;1109 1170 11A9;C252;1109 1170 11A9; # (쉒; 쉒; 쉒; 쉒; 쉒; ) HANGUL SYLLABLE SWEGG
+C253;C253;1109 1170 11AA;C253;1109 1170 11AA; # (쉓; 쉓; 쉓; 쉓; 쉓; ) HANGUL SYLLABLE SWEGS
+C254;C254;1109 1170 11AB;C254;1109 1170 11AB; # (쉔; 쉔; 쉔; 쉔; 쉔; ) HANGUL SYLLABLE SWEN
+C255;C255;1109 1170 11AC;C255;1109 1170 11AC; # (쉕; 쉕; 쉕; 쉕; 쉕; ) HANGUL SYLLABLE SWENJ
+C256;C256;1109 1170 11AD;C256;1109 1170 11AD; # (쉖; 쉖; 쉖; 쉖; 쉖; ) HANGUL SYLLABLE SWENH
+C257;C257;1109 1170 11AE;C257;1109 1170 11AE; # (쉗; 쉗; 쉗; 쉗; 쉗; ) HANGUL SYLLABLE SWED
+C258;C258;1109 1170 11AF;C258;1109 1170 11AF; # (쉘; 쉘; 쉘; 쉘; 쉘; ) HANGUL SYLLABLE SWEL
+C259;C259;1109 1170 11B0;C259;1109 1170 11B0; # (쉙; 쉙; 쉙; 쉙; 쉙; ) HANGUL SYLLABLE SWELG
+C25A;C25A;1109 1170 11B1;C25A;1109 1170 11B1; # (쉚; 쉚; 쉚; 쉚; 쉚; ) HANGUL SYLLABLE SWELM
+C25B;C25B;1109 1170 11B2;C25B;1109 1170 11B2; # (쉛; 쉛; 쉛; 쉛; 쉛; ) HANGUL SYLLABLE SWELB
+C25C;C25C;1109 1170 11B3;C25C;1109 1170 11B3; # (쉜; 쉜; 쉜; 쉜; 쉜; ) HANGUL SYLLABLE SWELS
+C25D;C25D;1109 1170 11B4;C25D;1109 1170 11B4; # (ì‰; ì‰; 쉝; ì‰; 쉝; ) HANGUL SYLLABLE SWELT
+C25E;C25E;1109 1170 11B5;C25E;1109 1170 11B5; # (쉞; 쉞; 쉞; 쉞; 쉞; ) HANGUL SYLLABLE SWELP
+C25F;C25F;1109 1170 11B6;C25F;1109 1170 11B6; # (쉟; 쉟; 쉟; 쉟; 쉟; ) HANGUL SYLLABLE SWELH
+C260;C260;1109 1170 11B7;C260;1109 1170 11B7; # (쉠; 쉠; 쉠; 쉠; 쉠; ) HANGUL SYLLABLE SWEM
+C261;C261;1109 1170 11B8;C261;1109 1170 11B8; # (쉡; 쉡; 쉡; 쉡; 쉡; ) HANGUL SYLLABLE SWEB
+C262;C262;1109 1170 11B9;C262;1109 1170 11B9; # (쉢; 쉢; 쉢; 쉢; 쉢; ) HANGUL SYLLABLE SWEBS
+C263;C263;1109 1170 11BA;C263;1109 1170 11BA; # (쉣; 쉣; 쉣; 쉣; 쉣; ) HANGUL SYLLABLE SWES
+C264;C264;1109 1170 11BB;C264;1109 1170 11BB; # (쉤; 쉤; 쉤; 쉤; 쉤; ) HANGUL SYLLABLE SWESS
+C265;C265;1109 1170 11BC;C265;1109 1170 11BC; # (쉥; 쉥; 쉥; 쉥; 쉥; ) HANGUL SYLLABLE SWENG
+C266;C266;1109 1170 11BD;C266;1109 1170 11BD; # (쉦; 쉦; 쉦; 쉦; 쉦; ) HANGUL SYLLABLE SWEJ
+C267;C267;1109 1170 11BE;C267;1109 1170 11BE; # (쉧; 쉧; 쉧; 쉧; 쉧; ) HANGUL SYLLABLE SWEC
+C268;C268;1109 1170 11BF;C268;1109 1170 11BF; # (쉨; 쉨; 쉨; 쉨; 쉨; ) HANGUL SYLLABLE SWEK
+C269;C269;1109 1170 11C0;C269;1109 1170 11C0; # (쉩; 쉩; 쉩; 쉩; 쉩; ) HANGUL SYLLABLE SWET
+C26A;C26A;1109 1170 11C1;C26A;1109 1170 11C1; # (쉪; 쉪; 쉐á‡; 쉪; 쉐á‡; ) HANGUL SYLLABLE SWEP
+C26B;C26B;1109 1170 11C2;C26B;1109 1170 11C2; # (쉫; 쉫; 쉫; 쉫; 쉫; ) HANGUL SYLLABLE SWEH
+C26C;C26C;1109 1171;C26C;1109 1171; # (쉬; 쉬; 쉬; 쉬; 쉬; ) HANGUL SYLLABLE SWI
+C26D;C26D;1109 1171 11A8;C26D;1109 1171 11A8; # (쉭; 쉭; 쉭; 쉭; 쉭; ) HANGUL SYLLABLE SWIG
+C26E;C26E;1109 1171 11A9;C26E;1109 1171 11A9; # (쉮; 쉮; 쉮; 쉮; 쉮; ) HANGUL SYLLABLE SWIGG
+C26F;C26F;1109 1171 11AA;C26F;1109 1171 11AA; # (쉯; 쉯; 쉯; 쉯; 쉯; ) HANGUL SYLLABLE SWIGS
+C270;C270;1109 1171 11AB;C270;1109 1171 11AB; # (쉰; 쉰; 쉰; 쉰; 쉰; ) HANGUL SYLLABLE SWIN
+C271;C271;1109 1171 11AC;C271;1109 1171 11AC; # (쉱; 쉱; 쉱; 쉱; 쉱; ) HANGUL SYLLABLE SWINJ
+C272;C272;1109 1171 11AD;C272;1109 1171 11AD; # (쉲; 쉲; 쉲; 쉲; 쉲; ) HANGUL SYLLABLE SWINH
+C273;C273;1109 1171 11AE;C273;1109 1171 11AE; # (쉳; 쉳; 쉳; 쉳; 쉳; ) HANGUL SYLLABLE SWID
+C274;C274;1109 1171 11AF;C274;1109 1171 11AF; # (쉴; 쉴; 쉴; 쉴; 쉴; ) HANGUL SYLLABLE SWIL
+C275;C275;1109 1171 11B0;C275;1109 1171 11B0; # (쉵; 쉵; 쉵; 쉵; 쉵; ) HANGUL SYLLABLE SWILG
+C276;C276;1109 1171 11B1;C276;1109 1171 11B1; # (쉶; 쉶; 쉶; 쉶; 쉶; ) HANGUL SYLLABLE SWILM
+C277;C277;1109 1171 11B2;C277;1109 1171 11B2; # (쉷; 쉷; 쉷; 쉷; 쉷; ) HANGUL SYLLABLE SWILB
+C278;C278;1109 1171 11B3;C278;1109 1171 11B3; # (쉸; 쉸; 쉸; 쉸; 쉸; ) HANGUL SYLLABLE SWILS
+C279;C279;1109 1171 11B4;C279;1109 1171 11B4; # (쉹; 쉹; 쉹; 쉹; 쉹; ) HANGUL SYLLABLE SWILT
+C27A;C27A;1109 1171 11B5;C27A;1109 1171 11B5; # (쉺; 쉺; 쉺; 쉺; 쉺; ) HANGUL SYLLABLE SWILP
+C27B;C27B;1109 1171 11B6;C27B;1109 1171 11B6; # (쉻; 쉻; 쉻; 쉻; 쉻; ) HANGUL SYLLABLE SWILH
+C27C;C27C;1109 1171 11B7;C27C;1109 1171 11B7; # (쉼; 쉼; 쉼; 쉼; 쉼; ) HANGUL SYLLABLE SWIM
+C27D;C27D;1109 1171 11B8;C27D;1109 1171 11B8; # (쉽; 쉽; 쉽; 쉽; 쉽; ) HANGUL SYLLABLE SWIB
+C27E;C27E;1109 1171 11B9;C27E;1109 1171 11B9; # (쉾; 쉾; 쉾; 쉾; 쉾; ) HANGUL SYLLABLE SWIBS
+C27F;C27F;1109 1171 11BA;C27F;1109 1171 11BA; # (쉿; 쉿; 쉿; 쉿; 쉿; ) HANGUL SYLLABLE SWIS
+C280;C280;1109 1171 11BB;C280;1109 1171 11BB; # (슀; 슀; 슀; 슀; 슀; ) HANGUL SYLLABLE SWISS
+C281;C281;1109 1171 11BC;C281;1109 1171 11BC; # (ìŠ; ìŠ; 슁; ìŠ; 슁; ) HANGUL SYLLABLE SWING
+C282;C282;1109 1171 11BD;C282;1109 1171 11BD; # (슂; 슂; 슂; 슂; 슂; ) HANGUL SYLLABLE SWIJ
+C283;C283;1109 1171 11BE;C283;1109 1171 11BE; # (슃; 슃; 슃; 슃; 슃; ) HANGUL SYLLABLE SWIC
+C284;C284;1109 1171 11BF;C284;1109 1171 11BF; # (슄; 슄; 슄; 슄; 슄; ) HANGUL SYLLABLE SWIK
+C285;C285;1109 1171 11C0;C285;1109 1171 11C0; # (슅; 슅; 슅; 슅; 슅; ) HANGUL SYLLABLE SWIT
+C286;C286;1109 1171 11C1;C286;1109 1171 11C1; # (슆; 슆; 쉬á‡; 슆; 쉬á‡; ) HANGUL SYLLABLE SWIP
+C287;C287;1109 1171 11C2;C287;1109 1171 11C2; # (슇; 슇; 슇; 슇; 슇; ) HANGUL SYLLABLE SWIH
+C288;C288;1109 1172;C288;1109 1172; # (슈; 슈; 슈; 슈; 슈; ) HANGUL SYLLABLE SYU
+C289;C289;1109 1172 11A8;C289;1109 1172 11A8; # (슉; 슉; 슉; 슉; 슉; ) HANGUL SYLLABLE SYUG
+C28A;C28A;1109 1172 11A9;C28A;1109 1172 11A9; # (슊; 슊; 슊; 슊; 슊; ) HANGUL SYLLABLE SYUGG
+C28B;C28B;1109 1172 11AA;C28B;1109 1172 11AA; # (슋; 슋; 슋; 슋; 슋; ) HANGUL SYLLABLE SYUGS
+C28C;C28C;1109 1172 11AB;C28C;1109 1172 11AB; # (슌; 슌; 슌; 슌; 슌; ) HANGUL SYLLABLE SYUN
+C28D;C28D;1109 1172 11AC;C28D;1109 1172 11AC; # (ìŠ; ìŠ; 슍; ìŠ; 슍; ) HANGUL SYLLABLE SYUNJ
+C28E;C28E;1109 1172 11AD;C28E;1109 1172 11AD; # (슎; 슎; 슎; 슎; 슎; ) HANGUL SYLLABLE SYUNH
+C28F;C28F;1109 1172 11AE;C28F;1109 1172 11AE; # (ìŠ; ìŠ; 슏; ìŠ; 슏; ) HANGUL SYLLABLE SYUD
+C290;C290;1109 1172 11AF;C290;1109 1172 11AF; # (ìŠ; ìŠ; 슐; ìŠ; 슐; ) HANGUL SYLLABLE SYUL
+C291;C291;1109 1172 11B0;C291;1109 1172 11B0; # (슑; 슑; 슑; 슑; 슑; ) HANGUL SYLLABLE SYULG
+C292;C292;1109 1172 11B1;C292;1109 1172 11B1; # (슒; 슒; 슒; 슒; 슒; ) HANGUL SYLLABLE SYULM
+C293;C293;1109 1172 11B2;C293;1109 1172 11B2; # (슓; 슓; 슓; 슓; 슓; ) HANGUL SYLLABLE SYULB
+C294;C294;1109 1172 11B3;C294;1109 1172 11B3; # (슔; 슔; 슔; 슔; 슔; ) HANGUL SYLLABLE SYULS
+C295;C295;1109 1172 11B4;C295;1109 1172 11B4; # (슕; 슕; 슕; 슕; 슕; ) HANGUL SYLLABLE SYULT
+C296;C296;1109 1172 11B5;C296;1109 1172 11B5; # (슖; 슖; 슖; 슖; 슖; ) HANGUL SYLLABLE SYULP
+C297;C297;1109 1172 11B6;C297;1109 1172 11B6; # (슗; 슗; 슗; 슗; 슗; ) HANGUL SYLLABLE SYULH
+C298;C298;1109 1172 11B7;C298;1109 1172 11B7; # (슘; 슘; 슘; 슘; 슘; ) HANGUL SYLLABLE SYUM
+C299;C299;1109 1172 11B8;C299;1109 1172 11B8; # (슙; 슙; 슙; 슙; 슙; ) HANGUL SYLLABLE SYUB
+C29A;C29A;1109 1172 11B9;C29A;1109 1172 11B9; # (슚; 슚; 슚; 슚; 슚; ) HANGUL SYLLABLE SYUBS
+C29B;C29B;1109 1172 11BA;C29B;1109 1172 11BA; # (슛; 슛; 슛; 슛; 슛; ) HANGUL SYLLABLE SYUS
+C29C;C29C;1109 1172 11BB;C29C;1109 1172 11BB; # (슜; 슜; 슜; 슜; 슜; ) HANGUL SYLLABLE SYUSS
+C29D;C29D;1109 1172 11BC;C29D;1109 1172 11BC; # (ìŠ; ìŠ; 슝; ìŠ; 슝; ) HANGUL SYLLABLE SYUNG
+C29E;C29E;1109 1172 11BD;C29E;1109 1172 11BD; # (슞; 슞; 슞; 슞; 슞; ) HANGUL SYLLABLE SYUJ
+C29F;C29F;1109 1172 11BE;C29F;1109 1172 11BE; # (슟; 슟; 슟; 슟; 슟; ) HANGUL SYLLABLE SYUC
+C2A0;C2A0;1109 1172 11BF;C2A0;1109 1172 11BF; # (슠; 슠; 슠; 슠; 슠; ) HANGUL SYLLABLE SYUK
+C2A1;C2A1;1109 1172 11C0;C2A1;1109 1172 11C0; # (슡; 슡; 슡; 슡; 슡; ) HANGUL SYLLABLE SYUT
+C2A2;C2A2;1109 1172 11C1;C2A2;1109 1172 11C1; # (슢; 슢; 슈á‡; 슢; 슈á‡; ) HANGUL SYLLABLE SYUP
+C2A3;C2A3;1109 1172 11C2;C2A3;1109 1172 11C2; # (슣; 슣; 슣; 슣; 슣; ) HANGUL SYLLABLE SYUH
+C2A4;C2A4;1109 1173;C2A4;1109 1173; # (스; 스; 스; 스; 스; ) HANGUL SYLLABLE SEU
+C2A5;C2A5;1109 1173 11A8;C2A5;1109 1173 11A8; # (슥; 슥; 슥; 슥; 슥; ) HANGUL SYLLABLE SEUG
+C2A6;C2A6;1109 1173 11A9;C2A6;1109 1173 11A9; # (슦; 슦; 슦; 슦; 슦; ) HANGUL SYLLABLE SEUGG
+C2A7;C2A7;1109 1173 11AA;C2A7;1109 1173 11AA; # (슧; 슧; 슧; 슧; 슧; ) HANGUL SYLLABLE SEUGS
+C2A8;C2A8;1109 1173 11AB;C2A8;1109 1173 11AB; # (슨; 슨; 슨; 슨; 슨; ) HANGUL SYLLABLE SEUN
+C2A9;C2A9;1109 1173 11AC;C2A9;1109 1173 11AC; # (슩; 슩; 슩; 슩; 슩; ) HANGUL SYLLABLE SEUNJ
+C2AA;C2AA;1109 1173 11AD;C2AA;1109 1173 11AD; # (슪; 슪; 슪; 슪; 슪; ) HANGUL SYLLABLE SEUNH
+C2AB;C2AB;1109 1173 11AE;C2AB;1109 1173 11AE; # (슫; 슫; 슫; 슫; 슫; ) HANGUL SYLLABLE SEUD
+C2AC;C2AC;1109 1173 11AF;C2AC;1109 1173 11AF; # (슬; 슬; 슬; 슬; 슬; ) HANGUL SYLLABLE SEUL
+C2AD;C2AD;1109 1173 11B0;C2AD;1109 1173 11B0; # (슭; 슭; 슭; 슭; 슭; ) HANGUL SYLLABLE SEULG
+C2AE;C2AE;1109 1173 11B1;C2AE;1109 1173 11B1; # (슮; 슮; 슮; 슮; 슮; ) HANGUL SYLLABLE SEULM
+C2AF;C2AF;1109 1173 11B2;C2AF;1109 1173 11B2; # (슯; 슯; 슯; 슯; 슯; ) HANGUL SYLLABLE SEULB
+C2B0;C2B0;1109 1173 11B3;C2B0;1109 1173 11B3; # (슰; 슰; 슰; 슰; 슰; ) HANGUL SYLLABLE SEULS
+C2B1;C2B1;1109 1173 11B4;C2B1;1109 1173 11B4; # (슱; 슱; 슱; 슱; 슱; ) HANGUL SYLLABLE SEULT
+C2B2;C2B2;1109 1173 11B5;C2B2;1109 1173 11B5; # (슲; 슲; 슲; 슲; 슲; ) HANGUL SYLLABLE SEULP
+C2B3;C2B3;1109 1173 11B6;C2B3;1109 1173 11B6; # (슳; 슳; 슳; 슳; 슳; ) HANGUL SYLLABLE SEULH
+C2B4;C2B4;1109 1173 11B7;C2B4;1109 1173 11B7; # (슴; 슴; 슴; 슴; 슴; ) HANGUL SYLLABLE SEUM
+C2B5;C2B5;1109 1173 11B8;C2B5;1109 1173 11B8; # (습; 습; 습; 습; 습; ) HANGUL SYLLABLE SEUB
+C2B6;C2B6;1109 1173 11B9;C2B6;1109 1173 11B9; # (슶; 슶; 슶; 슶; 슶; ) HANGUL SYLLABLE SEUBS
+C2B7;C2B7;1109 1173 11BA;C2B7;1109 1173 11BA; # (슷; 슷; 슷; 슷; 슷; ) HANGUL SYLLABLE SEUS
+C2B8;C2B8;1109 1173 11BB;C2B8;1109 1173 11BB; # (슸; 슸; 슸; 슸; 슸; ) HANGUL SYLLABLE SEUSS
+C2B9;C2B9;1109 1173 11BC;C2B9;1109 1173 11BC; # (승; 승; 승; 승; 승; ) HANGUL SYLLABLE SEUNG
+C2BA;C2BA;1109 1173 11BD;C2BA;1109 1173 11BD; # (슺; 슺; 슺; 슺; 슺; ) HANGUL SYLLABLE SEUJ
+C2BB;C2BB;1109 1173 11BE;C2BB;1109 1173 11BE; # (슻; 슻; 슻; 슻; 슻; ) HANGUL SYLLABLE SEUC
+C2BC;C2BC;1109 1173 11BF;C2BC;1109 1173 11BF; # (슼; 슼; 슼; 슼; 슼; ) HANGUL SYLLABLE SEUK
+C2BD;C2BD;1109 1173 11C0;C2BD;1109 1173 11C0; # (슽; 슽; 슽; 슽; 슽; ) HANGUL SYLLABLE SEUT
+C2BE;C2BE;1109 1173 11C1;C2BE;1109 1173 11C1; # (슾; 슾; 스á‡; 슾; 스á‡; ) HANGUL SYLLABLE SEUP
+C2BF;C2BF;1109 1173 11C2;C2BF;1109 1173 11C2; # (슿; 슿; 슿; 슿; 슿; ) HANGUL SYLLABLE SEUH
+C2C0;C2C0;1109 1174;C2C0;1109 1174; # (싀; 싀; 싀; 싀; 싀; ) HANGUL SYLLABLE SYI
+C2C1;C2C1;1109 1174 11A8;C2C1;1109 1174 11A8; # (ì‹; ì‹; 싁; ì‹; 싁; ) HANGUL SYLLABLE SYIG
+C2C2;C2C2;1109 1174 11A9;C2C2;1109 1174 11A9; # (싂; 싂; 싂; 싂; 싂; ) HANGUL SYLLABLE SYIGG
+C2C3;C2C3;1109 1174 11AA;C2C3;1109 1174 11AA; # (싃; 싃; 싃; 싃; 싃; ) HANGUL SYLLABLE SYIGS
+C2C4;C2C4;1109 1174 11AB;C2C4;1109 1174 11AB; # (싄; 싄; 싄; 싄; 싄; ) HANGUL SYLLABLE SYIN
+C2C5;C2C5;1109 1174 11AC;C2C5;1109 1174 11AC; # (싅; 싅; 싅; 싅; 싅; ) HANGUL SYLLABLE SYINJ
+C2C6;C2C6;1109 1174 11AD;C2C6;1109 1174 11AD; # (싆; 싆; 싆; 싆; 싆; ) HANGUL SYLLABLE SYINH
+C2C7;C2C7;1109 1174 11AE;C2C7;1109 1174 11AE; # (싇; 싇; 싇; 싇; 싇; ) HANGUL SYLLABLE SYID
+C2C8;C2C8;1109 1174 11AF;C2C8;1109 1174 11AF; # (싈; 싈; 싈; 싈; 싈; ) HANGUL SYLLABLE SYIL
+C2C9;C2C9;1109 1174 11B0;C2C9;1109 1174 11B0; # (싉; 싉; 싉; 싉; 싉; ) HANGUL SYLLABLE SYILG
+C2CA;C2CA;1109 1174 11B1;C2CA;1109 1174 11B1; # (싊; 싊; 싊; 싊; 싊; ) HANGUL SYLLABLE SYILM
+C2CB;C2CB;1109 1174 11B2;C2CB;1109 1174 11B2; # (싋; 싋; 싋; 싋; 싋; ) HANGUL SYLLABLE SYILB
+C2CC;C2CC;1109 1174 11B3;C2CC;1109 1174 11B3; # (싌; 싌; 싌; 싌; 싌; ) HANGUL SYLLABLE SYILS
+C2CD;C2CD;1109 1174 11B4;C2CD;1109 1174 11B4; # (ì‹; ì‹; 싍; ì‹; 싍; ) HANGUL SYLLABLE SYILT
+C2CE;C2CE;1109 1174 11B5;C2CE;1109 1174 11B5; # (싎; 싎; 싎; 싎; 싎; ) HANGUL SYLLABLE SYILP
+C2CF;C2CF;1109 1174 11B6;C2CF;1109 1174 11B6; # (ì‹; ì‹; 싏; ì‹; 싏; ) HANGUL SYLLABLE SYILH
+C2D0;C2D0;1109 1174 11B7;C2D0;1109 1174 11B7; # (ì‹; ì‹; 싐; ì‹; 싐; ) HANGUL SYLLABLE SYIM
+C2D1;C2D1;1109 1174 11B8;C2D1;1109 1174 11B8; # (싑; 싑; 싑; 싑; 싑; ) HANGUL SYLLABLE SYIB
+C2D2;C2D2;1109 1174 11B9;C2D2;1109 1174 11B9; # (싒; 싒; 싒; 싒; 싒; ) HANGUL SYLLABLE SYIBS
+C2D3;C2D3;1109 1174 11BA;C2D3;1109 1174 11BA; # (싓; 싓; 싓; 싓; 싓; ) HANGUL SYLLABLE SYIS
+C2D4;C2D4;1109 1174 11BB;C2D4;1109 1174 11BB; # (싔; 싔; 싔; 싔; 싔; ) HANGUL SYLLABLE SYISS
+C2D5;C2D5;1109 1174 11BC;C2D5;1109 1174 11BC; # (싕; 싕; 싕; 싕; 싕; ) HANGUL SYLLABLE SYING
+C2D6;C2D6;1109 1174 11BD;C2D6;1109 1174 11BD; # (싖; 싖; 싖; 싖; 싖; ) HANGUL SYLLABLE SYIJ
+C2D7;C2D7;1109 1174 11BE;C2D7;1109 1174 11BE; # (싗; 싗; 싗; 싗; 싗; ) HANGUL SYLLABLE SYIC
+C2D8;C2D8;1109 1174 11BF;C2D8;1109 1174 11BF; # (싘; 싘; 싘; 싘; 싘; ) HANGUL SYLLABLE SYIK
+C2D9;C2D9;1109 1174 11C0;C2D9;1109 1174 11C0; # (싙; 싙; 싙; 싙; 싙; ) HANGUL SYLLABLE SYIT
+C2DA;C2DA;1109 1174 11C1;C2DA;1109 1174 11C1; # (ì‹š; ì‹š; 싀á‡; ì‹š; 싀á‡; ) HANGUL SYLLABLE SYIP
+C2DB;C2DB;1109 1174 11C2;C2DB;1109 1174 11C2; # (싛; 싛; 싛; 싛; 싛; ) HANGUL SYLLABLE SYIH
+C2DC;C2DC;1109 1175;C2DC;1109 1175; # (시; 시; 시; 시; 시; ) HANGUL SYLLABLE SI
+C2DD;C2DD;1109 1175 11A8;C2DD;1109 1175 11A8; # (ì‹; ì‹; 식; ì‹; 식; ) HANGUL SYLLABLE SIG
+C2DE;C2DE;1109 1175 11A9;C2DE;1109 1175 11A9; # (싞; 싞; 싞; 싞; 싞; ) HANGUL SYLLABLE SIGG
+C2DF;C2DF;1109 1175 11AA;C2DF;1109 1175 11AA; # (싟; 싟; 싟; 싟; 싟; ) HANGUL SYLLABLE SIGS
+C2E0;C2E0;1109 1175 11AB;C2E0;1109 1175 11AB; # (신; 신; 신; 신; 신; ) HANGUL SYLLABLE SIN
+C2E1;C2E1;1109 1175 11AC;C2E1;1109 1175 11AC; # (싡; 싡; 싡; 싡; 싡; ) HANGUL SYLLABLE SINJ
+C2E2;C2E2;1109 1175 11AD;C2E2;1109 1175 11AD; # (싢; 싢; 싢; 싢; 싢; ) HANGUL SYLLABLE SINH
+C2E3;C2E3;1109 1175 11AE;C2E3;1109 1175 11AE; # (싣; 싣; 싣; 싣; 싣; ) HANGUL SYLLABLE SID
+C2E4;C2E4;1109 1175 11AF;C2E4;1109 1175 11AF; # (실; 실; 실; 실; 실; ) HANGUL SYLLABLE SIL
+C2E5;C2E5;1109 1175 11B0;C2E5;1109 1175 11B0; # (싥; 싥; 싥; 싥; 싥; ) HANGUL SYLLABLE SILG
+C2E6;C2E6;1109 1175 11B1;C2E6;1109 1175 11B1; # (싦; 싦; 싦; 싦; 싦; ) HANGUL SYLLABLE SILM
+C2E7;C2E7;1109 1175 11B2;C2E7;1109 1175 11B2; # (싧; 싧; 싧; 싧; 싧; ) HANGUL SYLLABLE SILB
+C2E8;C2E8;1109 1175 11B3;C2E8;1109 1175 11B3; # (싨; 싨; 싨; 싨; 싨; ) HANGUL SYLLABLE SILS
+C2E9;C2E9;1109 1175 11B4;C2E9;1109 1175 11B4; # (싩; 싩; 싩; 싩; 싩; ) HANGUL SYLLABLE SILT
+C2EA;C2EA;1109 1175 11B5;C2EA;1109 1175 11B5; # (싪; 싪; 싪; 싪; 싪; ) HANGUL SYLLABLE SILP
+C2EB;C2EB;1109 1175 11B6;C2EB;1109 1175 11B6; # (싫; 싫; 싫; 싫; 싫; ) HANGUL SYLLABLE SILH
+C2EC;C2EC;1109 1175 11B7;C2EC;1109 1175 11B7; # (심; 심; 심; 심; 심; ) HANGUL SYLLABLE SIM
+C2ED;C2ED;1109 1175 11B8;C2ED;1109 1175 11B8; # (십; 십; 십; 십; 십; ) HANGUL SYLLABLE SIB
+C2EE;C2EE;1109 1175 11B9;C2EE;1109 1175 11B9; # (싮; 싮; 싮; 싮; 싮; ) HANGUL SYLLABLE SIBS
+C2EF;C2EF;1109 1175 11BA;C2EF;1109 1175 11BA; # (싯; 싯; 싯; 싯; 싯; ) HANGUL SYLLABLE SIS
+C2F0;C2F0;1109 1175 11BB;C2F0;1109 1175 11BB; # (싰; 싰; 싰; 싰; 싰; ) HANGUL SYLLABLE SISS
+C2F1;C2F1;1109 1175 11BC;C2F1;1109 1175 11BC; # (싱; 싱; 싱; 싱; 싱; ) HANGUL SYLLABLE SING
+C2F2;C2F2;1109 1175 11BD;C2F2;1109 1175 11BD; # (싲; 싲; 싲; 싲; 싲; ) HANGUL SYLLABLE SIJ
+C2F3;C2F3;1109 1175 11BE;C2F3;1109 1175 11BE; # (싳; 싳; 싳; 싳; 싳; ) HANGUL SYLLABLE SIC
+C2F4;C2F4;1109 1175 11BF;C2F4;1109 1175 11BF; # (싴; 싴; 싴; 싴; 싴; ) HANGUL SYLLABLE SIK
+C2F5;C2F5;1109 1175 11C0;C2F5;1109 1175 11C0; # (싵; 싵; 싵; 싵; 싵; ) HANGUL SYLLABLE SIT
+C2F6;C2F6;1109 1175 11C1;C2F6;1109 1175 11C1; # (싶; 싶; 시á‡; 싶; 시á‡; ) HANGUL SYLLABLE SIP
+C2F7;C2F7;1109 1175 11C2;C2F7;1109 1175 11C2; # (싷; 싷; 싷; 싷; 싷; ) HANGUL SYLLABLE SIH
+C2F8;C2F8;110A 1161;C2F8;110A 1161; # (싸; 싸; 싸; 싸; 싸; ) HANGUL SYLLABLE SSA
+C2F9;C2F9;110A 1161 11A8;C2F9;110A 1161 11A8; # (싹; 싹; 싹; 싹; 싹; ) HANGUL SYLLABLE SSAG
+C2FA;C2FA;110A 1161 11A9;C2FA;110A 1161 11A9; # (싺; 싺; 싺; 싺; 싺; ) HANGUL SYLLABLE SSAGG
+C2FB;C2FB;110A 1161 11AA;C2FB;110A 1161 11AA; # (싻; 싻; 싻; 싻; 싻; ) HANGUL SYLLABLE SSAGS
+C2FC;C2FC;110A 1161 11AB;C2FC;110A 1161 11AB; # (싼; 싼; 싼; 싼; 싼; ) HANGUL SYLLABLE SSAN
+C2FD;C2FD;110A 1161 11AC;C2FD;110A 1161 11AC; # (싽; 싽; 싽; 싽; 싽; ) HANGUL SYLLABLE SSANJ
+C2FE;C2FE;110A 1161 11AD;C2FE;110A 1161 11AD; # (싾; 싾; 싾; 싾; 싾; ) HANGUL SYLLABLE SSANH
+C2FF;C2FF;110A 1161 11AE;C2FF;110A 1161 11AE; # (싿; 싿; 싿; 싿; 싿; ) HANGUL SYLLABLE SSAD
+C300;C300;110A 1161 11AF;C300;110A 1161 11AF; # (쌀; 쌀; 쌀; 쌀; 쌀; ) HANGUL SYLLABLE SSAL
+C301;C301;110A 1161 11B0;C301;110A 1161 11B0; # (ìŒ; ìŒ; 쌁; ìŒ; 쌁; ) HANGUL SYLLABLE SSALG
+C302;C302;110A 1161 11B1;C302;110A 1161 11B1; # (쌂; 쌂; 쌂; 쌂; 쌂; ) HANGUL SYLLABLE SSALM
+C303;C303;110A 1161 11B2;C303;110A 1161 11B2; # (쌃; 쌃; 쌃; 쌃; 쌃; ) HANGUL SYLLABLE SSALB
+C304;C304;110A 1161 11B3;C304;110A 1161 11B3; # (쌄; 쌄; 쌄; 쌄; 쌄; ) HANGUL SYLLABLE SSALS
+C305;C305;110A 1161 11B4;C305;110A 1161 11B4; # (쌅; 쌅; 쌅; 쌅; 쌅; ) HANGUL SYLLABLE SSALT
+C306;C306;110A 1161 11B5;C306;110A 1161 11B5; # (쌆; 쌆; 쌆; 쌆; 쌆; ) HANGUL SYLLABLE SSALP
+C307;C307;110A 1161 11B6;C307;110A 1161 11B6; # (쌇; 쌇; 쌇; 쌇; 쌇; ) HANGUL SYLLABLE SSALH
+C308;C308;110A 1161 11B7;C308;110A 1161 11B7; # (쌈; 쌈; 쌈; 쌈; 쌈; ) HANGUL SYLLABLE SSAM
+C309;C309;110A 1161 11B8;C309;110A 1161 11B8; # (쌉; 쌉; 쌉; 쌉; 쌉; ) HANGUL SYLLABLE SSAB
+C30A;C30A;110A 1161 11B9;C30A;110A 1161 11B9; # (쌊; 쌊; 쌊; 쌊; 쌊; ) HANGUL SYLLABLE SSABS
+C30B;C30B;110A 1161 11BA;C30B;110A 1161 11BA; # (쌋; 쌋; 쌋; 쌋; 쌋; ) HANGUL SYLLABLE SSAS
+C30C;C30C;110A 1161 11BB;C30C;110A 1161 11BB; # (쌌; 쌌; 쌌; 쌌; 쌌; ) HANGUL SYLLABLE SSASS
+C30D;C30D;110A 1161 11BC;C30D;110A 1161 11BC; # (ìŒ; ìŒ; 쌍; ìŒ; 쌍; ) HANGUL SYLLABLE SSANG
+C30E;C30E;110A 1161 11BD;C30E;110A 1161 11BD; # (쌎; 쌎; 쌎; 쌎; 쌎; ) HANGUL SYLLABLE SSAJ
+C30F;C30F;110A 1161 11BE;C30F;110A 1161 11BE; # (ìŒ; ìŒ; 쌏; ìŒ; 쌏; ) HANGUL SYLLABLE SSAC
+C310;C310;110A 1161 11BF;C310;110A 1161 11BF; # (ìŒ; ìŒ; 쌐; ìŒ; 쌐; ) HANGUL SYLLABLE SSAK
+C311;C311;110A 1161 11C0;C311;110A 1161 11C0; # (쌑; 쌑; 쌑; 쌑; 쌑; ) HANGUL SYLLABLE SSAT
+C312;C312;110A 1161 11C1;C312;110A 1161 11C1; # (쌒; 쌒; á„Šá…¡á‡; 쌒; á„Šá…¡á‡; ) HANGUL SYLLABLE SSAP
+C313;C313;110A 1161 11C2;C313;110A 1161 11C2; # (쌓; 쌓; 쌓; 쌓; 쌓; ) HANGUL SYLLABLE SSAH
+C314;C314;110A 1162;C314;110A 1162; # (쌔; 쌔; 쌔; 쌔; 쌔; ) HANGUL SYLLABLE SSAE
+C315;C315;110A 1162 11A8;C315;110A 1162 11A8; # (쌕; 쌕; 쌕; 쌕; 쌕; ) HANGUL SYLLABLE SSAEG
+C316;C316;110A 1162 11A9;C316;110A 1162 11A9; # (쌖; 쌖; 쌖; 쌖; 쌖; ) HANGUL SYLLABLE SSAEGG
+C317;C317;110A 1162 11AA;C317;110A 1162 11AA; # (쌗; 쌗; 쌗; 쌗; 쌗; ) HANGUL SYLLABLE SSAEGS
+C318;C318;110A 1162 11AB;C318;110A 1162 11AB; # (쌘; 쌘; 쌘; 쌘; 쌘; ) HANGUL SYLLABLE SSAEN
+C319;C319;110A 1162 11AC;C319;110A 1162 11AC; # (쌙; 쌙; 쌙; 쌙; 쌙; ) HANGUL SYLLABLE SSAENJ
+C31A;C31A;110A 1162 11AD;C31A;110A 1162 11AD; # (쌚; 쌚; 쌚; 쌚; 쌚; ) HANGUL SYLLABLE SSAENH
+C31B;C31B;110A 1162 11AE;C31B;110A 1162 11AE; # (쌛; 쌛; 쌛; 쌛; 쌛; ) HANGUL SYLLABLE SSAED
+C31C;C31C;110A 1162 11AF;C31C;110A 1162 11AF; # (쌜; 쌜; 쌜; 쌜; 쌜; ) HANGUL SYLLABLE SSAEL
+C31D;C31D;110A 1162 11B0;C31D;110A 1162 11B0; # (ìŒ; ìŒ; 쌝; ìŒ; 쌝; ) HANGUL SYLLABLE SSAELG
+C31E;C31E;110A 1162 11B1;C31E;110A 1162 11B1; # (쌞; 쌞; 쌞; 쌞; 쌞; ) HANGUL SYLLABLE SSAELM
+C31F;C31F;110A 1162 11B2;C31F;110A 1162 11B2; # (쌟; 쌟; 쌟; 쌟; 쌟; ) HANGUL SYLLABLE SSAELB
+C320;C320;110A 1162 11B3;C320;110A 1162 11B3; # (쌠; 쌠; 쌠; 쌠; 쌠; ) HANGUL SYLLABLE SSAELS
+C321;C321;110A 1162 11B4;C321;110A 1162 11B4; # (쌡; 쌡; 쌡; 쌡; 쌡; ) HANGUL SYLLABLE SSAELT
+C322;C322;110A 1162 11B5;C322;110A 1162 11B5; # (쌢; 쌢; 쌢; 쌢; 쌢; ) HANGUL SYLLABLE SSAELP
+C323;C323;110A 1162 11B6;C323;110A 1162 11B6; # (쌣; 쌣; 쌣; 쌣; 쌣; ) HANGUL SYLLABLE SSAELH
+C324;C324;110A 1162 11B7;C324;110A 1162 11B7; # (쌤; 쌤; 쌤; 쌤; 쌤; ) HANGUL SYLLABLE SSAEM
+C325;C325;110A 1162 11B8;C325;110A 1162 11B8; # (쌥; 쌥; 쌥; 쌥; 쌥; ) HANGUL SYLLABLE SSAEB
+C326;C326;110A 1162 11B9;C326;110A 1162 11B9; # (쌦; 쌦; 쌦; 쌦; 쌦; ) HANGUL SYLLABLE SSAEBS
+C327;C327;110A 1162 11BA;C327;110A 1162 11BA; # (쌧; 쌧; 쌧; 쌧; 쌧; ) HANGUL SYLLABLE SSAES
+C328;C328;110A 1162 11BB;C328;110A 1162 11BB; # (쌨; 쌨; 쌨; 쌨; 쌨; ) HANGUL SYLLABLE SSAESS
+C329;C329;110A 1162 11BC;C329;110A 1162 11BC; # (쌩; 쌩; 쌩; 쌩; 쌩; ) HANGUL SYLLABLE SSAENG
+C32A;C32A;110A 1162 11BD;C32A;110A 1162 11BD; # (쌪; 쌪; 쌪; 쌪; 쌪; ) HANGUL SYLLABLE SSAEJ
+C32B;C32B;110A 1162 11BE;C32B;110A 1162 11BE; # (쌫; 쌫; 쌫; 쌫; 쌫; ) HANGUL SYLLABLE SSAEC
+C32C;C32C;110A 1162 11BF;C32C;110A 1162 11BF; # (쌬; 쌬; 쌬; 쌬; 쌬; ) HANGUL SYLLABLE SSAEK
+C32D;C32D;110A 1162 11C0;C32D;110A 1162 11C0; # (쌭; 쌭; 쌭; 쌭; 쌭; ) HANGUL SYLLABLE SSAET
+C32E;C32E;110A 1162 11C1;C32E;110A 1162 11C1; # (쌮; 쌮; á„Šá…¢á‡; 쌮; á„Šá…¢á‡; ) HANGUL SYLLABLE SSAEP
+C32F;C32F;110A 1162 11C2;C32F;110A 1162 11C2; # (쌯; 쌯; 쌯; 쌯; 쌯; ) HANGUL SYLLABLE SSAEH
+C330;C330;110A 1163;C330;110A 1163; # (쌰; 쌰; 쌰; 쌰; 쌰; ) HANGUL SYLLABLE SSYA
+C331;C331;110A 1163 11A8;C331;110A 1163 11A8; # (쌱; 쌱; 쌱; 쌱; 쌱; ) HANGUL SYLLABLE SSYAG
+C332;C332;110A 1163 11A9;C332;110A 1163 11A9; # (쌲; 쌲; 쌲; 쌲; 쌲; ) HANGUL SYLLABLE SSYAGG
+C333;C333;110A 1163 11AA;C333;110A 1163 11AA; # (쌳; 쌳; 쌳; 쌳; 쌳; ) HANGUL SYLLABLE SSYAGS
+C334;C334;110A 1163 11AB;C334;110A 1163 11AB; # (쌴; 쌴; 쌴; 쌴; 쌴; ) HANGUL SYLLABLE SSYAN
+C335;C335;110A 1163 11AC;C335;110A 1163 11AC; # (쌵; 쌵; 쌵; 쌵; 쌵; ) HANGUL SYLLABLE SSYANJ
+C336;C336;110A 1163 11AD;C336;110A 1163 11AD; # (쌶; 쌶; 쌶; 쌶; 쌶; ) HANGUL SYLLABLE SSYANH
+C337;C337;110A 1163 11AE;C337;110A 1163 11AE; # (쌷; 쌷; 쌷; 쌷; 쌷; ) HANGUL SYLLABLE SSYAD
+C338;C338;110A 1163 11AF;C338;110A 1163 11AF; # (쌸; 쌸; 쌸; 쌸; 쌸; ) HANGUL SYLLABLE SSYAL
+C339;C339;110A 1163 11B0;C339;110A 1163 11B0; # (쌹; 쌹; 쌹; 쌹; 쌹; ) HANGUL SYLLABLE SSYALG
+C33A;C33A;110A 1163 11B1;C33A;110A 1163 11B1; # (쌺; 쌺; 쌺; 쌺; 쌺; ) HANGUL SYLLABLE SSYALM
+C33B;C33B;110A 1163 11B2;C33B;110A 1163 11B2; # (쌻; 쌻; 쌻; 쌻; 쌻; ) HANGUL SYLLABLE SSYALB
+C33C;C33C;110A 1163 11B3;C33C;110A 1163 11B3; # (쌼; 쌼; 쌼; 쌼; 쌼; ) HANGUL SYLLABLE SSYALS
+C33D;C33D;110A 1163 11B4;C33D;110A 1163 11B4; # (쌽; 쌽; 쌽; 쌽; 쌽; ) HANGUL SYLLABLE SSYALT
+C33E;C33E;110A 1163 11B5;C33E;110A 1163 11B5; # (쌾; 쌾; 쌾; 쌾; 쌾; ) HANGUL SYLLABLE SSYALP
+C33F;C33F;110A 1163 11B6;C33F;110A 1163 11B6; # (쌿; 쌿; 쌿; 쌿; 쌿; ) HANGUL SYLLABLE SSYALH
+C340;C340;110A 1163 11B7;C340;110A 1163 11B7; # (ì€; ì€; 썀; ì€; 썀; ) HANGUL SYLLABLE SSYAM
+C341;C341;110A 1163 11B8;C341;110A 1163 11B8; # (ì; ì; 썁; ì; 썁; ) HANGUL SYLLABLE SSYAB
+C342;C342;110A 1163 11B9;C342;110A 1163 11B9; # (ì‚; ì‚; 썂; ì‚; 썂; ) HANGUL SYLLABLE SSYABS
+C343;C343;110A 1163 11BA;C343;110A 1163 11BA; # (ìƒ; ìƒ; 썃; ìƒ; 썃; ) HANGUL SYLLABLE SSYAS
+C344;C344;110A 1163 11BB;C344;110A 1163 11BB; # (ì„; ì„; 썄; ì„; 썄; ) HANGUL SYLLABLE SSYASS
+C345;C345;110A 1163 11BC;C345;110A 1163 11BC; # (ì…; ì…; 썅; ì…; 썅; ) HANGUL SYLLABLE SSYANG
+C346;C346;110A 1163 11BD;C346;110A 1163 11BD; # (ì†; ì†; 썆; ì†; 썆; ) HANGUL SYLLABLE SSYAJ
+C347;C347;110A 1163 11BE;C347;110A 1163 11BE; # (ì‡; ì‡; 썇; ì‡; 썇; ) HANGUL SYLLABLE SSYAC
+C348;C348;110A 1163 11BF;C348;110A 1163 11BF; # (ìˆ; ìˆ; 썈; ìˆ; 썈; ) HANGUL SYLLABLE SSYAK
+C349;C349;110A 1163 11C0;C349;110A 1163 11C0; # (ì‰; ì‰; 썉; ì‰; 썉; ) HANGUL SYLLABLE SSYAT
+C34A;C34A;110A 1163 11C1;C34A;110A 1163 11C1; # (ìŠ; ìŠ; á„Šá…£á‡; ìŠ; á„Šá…£á‡; ) HANGUL SYLLABLE SSYAP
+C34B;C34B;110A 1163 11C2;C34B;110A 1163 11C2; # (ì‹; ì‹; 썋; ì‹; 썋; ) HANGUL SYLLABLE SSYAH
+C34C;C34C;110A 1164;C34C;110A 1164; # (ìŒ; ìŒ; á„Šá…¤; ìŒ; á„Šá…¤; ) HANGUL SYLLABLE SSYAE
+C34D;C34D;110A 1164 11A8;C34D;110A 1164 11A8; # (ì; ì; 썍; ì; 썍; ) HANGUL SYLLABLE SSYAEG
+C34E;C34E;110A 1164 11A9;C34E;110A 1164 11A9; # (ìŽ; ìŽ; 썎; ìŽ; 썎; ) HANGUL SYLLABLE SSYAEGG
+C34F;C34F;110A 1164 11AA;C34F;110A 1164 11AA; # (ì; ì; 썏; ì; 썏; ) HANGUL SYLLABLE SSYAEGS
+C350;C350;110A 1164 11AB;C350;110A 1164 11AB; # (ì; ì; 썐; ì; 썐; ) HANGUL SYLLABLE SSYAEN
+C351;C351;110A 1164 11AC;C351;110A 1164 11AC; # (ì‘; ì‘; 썑; ì‘; 썑; ) HANGUL SYLLABLE SSYAENJ
+C352;C352;110A 1164 11AD;C352;110A 1164 11AD; # (ì’; ì’; 썒; ì’; 썒; ) HANGUL SYLLABLE SSYAENH
+C353;C353;110A 1164 11AE;C353;110A 1164 11AE; # (ì“; ì“; 썓; ì“; 썓; ) HANGUL SYLLABLE SSYAED
+C354;C354;110A 1164 11AF;C354;110A 1164 11AF; # (ì”; ì”; 썔; ì”; 썔; ) HANGUL SYLLABLE SSYAEL
+C355;C355;110A 1164 11B0;C355;110A 1164 11B0; # (ì•; ì•; 썕; ì•; 썕; ) HANGUL SYLLABLE SSYAELG
+C356;C356;110A 1164 11B1;C356;110A 1164 11B1; # (ì–; ì–; 썖; ì–; 썖; ) HANGUL SYLLABLE SSYAELM
+C357;C357;110A 1164 11B2;C357;110A 1164 11B2; # (ì—; ì—; 썗; ì—; 썗; ) HANGUL SYLLABLE SSYAELB
+C358;C358;110A 1164 11B3;C358;110A 1164 11B3; # (ì˜; ì˜; 썘; ì˜; 썘; ) HANGUL SYLLABLE SSYAELS
+C359;C359;110A 1164 11B4;C359;110A 1164 11B4; # (ì™; ì™; 썙; ì™; 썙; ) HANGUL SYLLABLE SSYAELT
+C35A;C35A;110A 1164 11B5;C35A;110A 1164 11B5; # (ìš; ìš; 썚; ìš; 썚; ) HANGUL SYLLABLE SSYAELP
+C35B;C35B;110A 1164 11B6;C35B;110A 1164 11B6; # (ì›; ì›; 썛; ì›; 썛; ) HANGUL SYLLABLE SSYAELH
+C35C;C35C;110A 1164 11B7;C35C;110A 1164 11B7; # (ìœ; ìœ; 썜; ìœ; 썜; ) HANGUL SYLLABLE SSYAEM
+C35D;C35D;110A 1164 11B8;C35D;110A 1164 11B8; # (ì; ì; 썝; ì; 썝; ) HANGUL SYLLABLE SSYAEB
+C35E;C35E;110A 1164 11B9;C35E;110A 1164 11B9; # (ìž; ìž; 썞; ìž; 썞; ) HANGUL SYLLABLE SSYAEBS
+C35F;C35F;110A 1164 11BA;C35F;110A 1164 11BA; # (ìŸ; ìŸ; 썟; ìŸ; 썟; ) HANGUL SYLLABLE SSYAES
+C360;C360;110A 1164 11BB;C360;110A 1164 11BB; # (ì ; ì ; 썠; ì ; 썠; ) HANGUL SYLLABLE SSYAESS
+C361;C361;110A 1164 11BC;C361;110A 1164 11BC; # (ì¡; ì¡; 썡; ì¡; 썡; ) HANGUL SYLLABLE SSYAENG
+C362;C362;110A 1164 11BD;C362;110A 1164 11BD; # (ì¢; ì¢; 썢; ì¢; 썢; ) HANGUL SYLLABLE SSYAEJ
+C363;C363;110A 1164 11BE;C363;110A 1164 11BE; # (ì£; ì£; 썣; ì£; 썣; ) HANGUL SYLLABLE SSYAEC
+C364;C364;110A 1164 11BF;C364;110A 1164 11BF; # (ì¤; ì¤; 썤; ì¤; 썤; ) HANGUL SYLLABLE SSYAEK
+C365;C365;110A 1164 11C0;C365;110A 1164 11C0; # (ì¥; ì¥; 썥; ì¥; 썥; ) HANGUL SYLLABLE SSYAET
+C366;C366;110A 1164 11C1;C366;110A 1164 11C1; # (ì¦; ì¦; á„Šá…¤á‡; ì¦; á„Šá…¤á‡; ) HANGUL SYLLABLE SSYAEP
+C367;C367;110A 1164 11C2;C367;110A 1164 11C2; # (ì§; ì§; 썧; ì§; 썧; ) HANGUL SYLLABLE SSYAEH
+C368;C368;110A 1165;C368;110A 1165; # (ì¨; ì¨; á„Šá…¥; ì¨; á„Šá…¥; ) HANGUL SYLLABLE SSEO
+C369;C369;110A 1165 11A8;C369;110A 1165 11A8; # (ì©; ì©; 썩; ì©; 썩; ) HANGUL SYLLABLE SSEOG
+C36A;C36A;110A 1165 11A9;C36A;110A 1165 11A9; # (ìª; ìª; 썪; ìª; 썪; ) HANGUL SYLLABLE SSEOGG
+C36B;C36B;110A 1165 11AA;C36B;110A 1165 11AA; # (ì«; ì«; 썫; ì«; 썫; ) HANGUL SYLLABLE SSEOGS
+C36C;C36C;110A 1165 11AB;C36C;110A 1165 11AB; # (ì¬; ì¬; 썬; ì¬; 썬; ) HANGUL SYLLABLE SSEON
+C36D;C36D;110A 1165 11AC;C36D;110A 1165 11AC; # (ì­; ì­; 썭; ì­; 썭; ) HANGUL SYLLABLE SSEONJ
+C36E;C36E;110A 1165 11AD;C36E;110A 1165 11AD; # (ì®; ì®; 썮; ì®; 썮; ) HANGUL SYLLABLE SSEONH
+C36F;C36F;110A 1165 11AE;C36F;110A 1165 11AE; # (ì¯; ì¯; 썯; ì¯; 썯; ) HANGUL SYLLABLE SSEOD
+C370;C370;110A 1165 11AF;C370;110A 1165 11AF; # (ì°; ì°; 썰; ì°; 썰; ) HANGUL SYLLABLE SSEOL
+C371;C371;110A 1165 11B0;C371;110A 1165 11B0; # (ì±; ì±; 썱; ì±; 썱; ) HANGUL SYLLABLE SSEOLG
+C372;C372;110A 1165 11B1;C372;110A 1165 11B1; # (ì²; ì²; 썲; ì²; 썲; ) HANGUL SYLLABLE SSEOLM
+C373;C373;110A 1165 11B2;C373;110A 1165 11B2; # (ì³; ì³; 썳; ì³; 썳; ) HANGUL SYLLABLE SSEOLB
+C374;C374;110A 1165 11B3;C374;110A 1165 11B3; # (ì´; ì´; 썴; ì´; 썴; ) HANGUL SYLLABLE SSEOLS
+C375;C375;110A 1165 11B4;C375;110A 1165 11B4; # (ìµ; ìµ; 썵; ìµ; 썵; ) HANGUL SYLLABLE SSEOLT
+C376;C376;110A 1165 11B5;C376;110A 1165 11B5; # (ì¶; ì¶; 썶; ì¶; 썶; ) HANGUL SYLLABLE SSEOLP
+C377;C377;110A 1165 11B6;C377;110A 1165 11B6; # (ì·; ì·; 썷; ì·; 썷; ) HANGUL SYLLABLE SSEOLH
+C378;C378;110A 1165 11B7;C378;110A 1165 11B7; # (ì¸; ì¸; 썸; ì¸; 썸; ) HANGUL SYLLABLE SSEOM
+C379;C379;110A 1165 11B8;C379;110A 1165 11B8; # (ì¹; ì¹; 썹; ì¹; 썹; ) HANGUL SYLLABLE SSEOB
+C37A;C37A;110A 1165 11B9;C37A;110A 1165 11B9; # (ìº; ìº; 썺; ìº; 썺; ) HANGUL SYLLABLE SSEOBS
+C37B;C37B;110A 1165 11BA;C37B;110A 1165 11BA; # (ì»; ì»; 썻; ì»; 썻; ) HANGUL SYLLABLE SSEOS
+C37C;C37C;110A 1165 11BB;C37C;110A 1165 11BB; # (ì¼; ì¼; 썼; ì¼; 썼; ) HANGUL SYLLABLE SSEOSS
+C37D;C37D;110A 1165 11BC;C37D;110A 1165 11BC; # (ì½; ì½; 썽; ì½; 썽; ) HANGUL SYLLABLE SSEONG
+C37E;C37E;110A 1165 11BD;C37E;110A 1165 11BD; # (ì¾; ì¾; 썾; ì¾; 썾; ) HANGUL SYLLABLE SSEOJ
+C37F;C37F;110A 1165 11BE;C37F;110A 1165 11BE; # (ì¿; ì¿; 썿; ì¿; 썿; ) HANGUL SYLLABLE SSEOC
+C380;C380;110A 1165 11BF;C380;110A 1165 11BF; # (쎀; 쎀; 쎀; 쎀; 쎀; ) HANGUL SYLLABLE SSEOK
+C381;C381;110A 1165 11C0;C381;110A 1165 11C0; # (ìŽ; ìŽ; 쎁; ìŽ; 쎁; ) HANGUL SYLLABLE SSEOT
+C382;C382;110A 1165 11C1;C382;110A 1165 11C1; # (쎂; 쎂; á„Šá…¥á‡; 쎂; á„Šá…¥á‡; ) HANGUL SYLLABLE SSEOP
+C383;C383;110A 1165 11C2;C383;110A 1165 11C2; # (쎃; 쎃; 쎃; 쎃; 쎃; ) HANGUL SYLLABLE SSEOH
+C384;C384;110A 1166;C384;110A 1166; # (쎄; 쎄; 쎄; 쎄; 쎄; ) HANGUL SYLLABLE SSE
+C385;C385;110A 1166 11A8;C385;110A 1166 11A8; # (쎅; 쎅; 쎅; 쎅; 쎅; ) HANGUL SYLLABLE SSEG
+C386;C386;110A 1166 11A9;C386;110A 1166 11A9; # (쎆; 쎆; 쎆; 쎆; 쎆; ) HANGUL SYLLABLE SSEGG
+C387;C387;110A 1166 11AA;C387;110A 1166 11AA; # (쎇; 쎇; 쎇; 쎇; 쎇; ) HANGUL SYLLABLE SSEGS
+C388;C388;110A 1166 11AB;C388;110A 1166 11AB; # (쎈; 쎈; 쎈; 쎈; 쎈; ) HANGUL SYLLABLE SSEN
+C389;C389;110A 1166 11AC;C389;110A 1166 11AC; # (쎉; 쎉; 쎉; 쎉; 쎉; ) HANGUL SYLLABLE SSENJ
+C38A;C38A;110A 1166 11AD;C38A;110A 1166 11AD; # (쎊; 쎊; 쎊; 쎊; 쎊; ) HANGUL SYLLABLE SSENH
+C38B;C38B;110A 1166 11AE;C38B;110A 1166 11AE; # (쎋; 쎋; 쎋; 쎋; 쎋; ) HANGUL SYLLABLE SSED
+C38C;C38C;110A 1166 11AF;C38C;110A 1166 11AF; # (쎌; 쎌; 쎌; 쎌; 쎌; ) HANGUL SYLLABLE SSEL
+C38D;C38D;110A 1166 11B0;C38D;110A 1166 11B0; # (ìŽ; ìŽ; 쎍; ìŽ; 쎍; ) HANGUL SYLLABLE SSELG
+C38E;C38E;110A 1166 11B1;C38E;110A 1166 11B1; # (쎎; 쎎; 쎎; 쎎; 쎎; ) HANGUL SYLLABLE SSELM
+C38F;C38F;110A 1166 11B2;C38F;110A 1166 11B2; # (ìŽ; ìŽ; 쎏; ìŽ; 쎏; ) HANGUL SYLLABLE SSELB
+C390;C390;110A 1166 11B3;C390;110A 1166 11B3; # (ìŽ; ìŽ; 쎐; ìŽ; 쎐; ) HANGUL SYLLABLE SSELS
+C391;C391;110A 1166 11B4;C391;110A 1166 11B4; # (쎑; 쎑; 쎑; 쎑; 쎑; ) HANGUL SYLLABLE SSELT
+C392;C392;110A 1166 11B5;C392;110A 1166 11B5; # (쎒; 쎒; 쎒; 쎒; 쎒; ) HANGUL SYLLABLE SSELP
+C393;C393;110A 1166 11B6;C393;110A 1166 11B6; # (쎓; 쎓; 쎓; 쎓; 쎓; ) HANGUL SYLLABLE SSELH
+C394;C394;110A 1166 11B7;C394;110A 1166 11B7; # (쎔; 쎔; 쎔; 쎔; 쎔; ) HANGUL SYLLABLE SSEM
+C395;C395;110A 1166 11B8;C395;110A 1166 11B8; # (쎕; 쎕; 쎕; 쎕; 쎕; ) HANGUL SYLLABLE SSEB
+C396;C396;110A 1166 11B9;C396;110A 1166 11B9; # (쎖; 쎖; 쎖; 쎖; 쎖; ) HANGUL SYLLABLE SSEBS
+C397;C397;110A 1166 11BA;C397;110A 1166 11BA; # (쎗; 쎗; 쎗; 쎗; 쎗; ) HANGUL SYLLABLE SSES
+C398;C398;110A 1166 11BB;C398;110A 1166 11BB; # (쎘; 쎘; 쎘; 쎘; 쎘; ) HANGUL SYLLABLE SSESS
+C399;C399;110A 1166 11BC;C399;110A 1166 11BC; # (쎙; 쎙; 쎙; 쎙; 쎙; ) HANGUL SYLLABLE SSENG
+C39A;C39A;110A 1166 11BD;C39A;110A 1166 11BD; # (쎚; 쎚; 쎚; 쎚; 쎚; ) HANGUL SYLLABLE SSEJ
+C39B;C39B;110A 1166 11BE;C39B;110A 1166 11BE; # (쎛; 쎛; 쎛; 쎛; 쎛; ) HANGUL SYLLABLE SSEC
+C39C;C39C;110A 1166 11BF;C39C;110A 1166 11BF; # (쎜; 쎜; 쎜; 쎜; 쎜; ) HANGUL SYLLABLE SSEK
+C39D;C39D;110A 1166 11C0;C39D;110A 1166 11C0; # (ìŽ; ìŽ; 쎝; ìŽ; 쎝; ) HANGUL SYLLABLE SSET
+C39E;C39E;110A 1166 11C1;C39E;110A 1166 11C1; # (쎞; 쎞; á„Šá…¦á‡; 쎞; á„Šá…¦á‡; ) HANGUL SYLLABLE SSEP
+C39F;C39F;110A 1166 11C2;C39F;110A 1166 11C2; # (쎟; 쎟; 쎟; 쎟; 쎟; ) HANGUL SYLLABLE SSEH
+C3A0;C3A0;110A 1167;C3A0;110A 1167; # (쎠; 쎠; 쎠; 쎠; 쎠; ) HANGUL SYLLABLE SSYEO
+C3A1;C3A1;110A 1167 11A8;C3A1;110A 1167 11A8; # (쎡; 쎡; 쎡; 쎡; 쎡; ) HANGUL SYLLABLE SSYEOG
+C3A2;C3A2;110A 1167 11A9;C3A2;110A 1167 11A9; # (쎢; 쎢; 쎢; 쎢; 쎢; ) HANGUL SYLLABLE SSYEOGG
+C3A3;C3A3;110A 1167 11AA;C3A3;110A 1167 11AA; # (쎣; 쎣; 쎣; 쎣; 쎣; ) HANGUL SYLLABLE SSYEOGS
+C3A4;C3A4;110A 1167 11AB;C3A4;110A 1167 11AB; # (쎤; 쎤; 쎤; 쎤; 쎤; ) HANGUL SYLLABLE SSYEON
+C3A5;C3A5;110A 1167 11AC;C3A5;110A 1167 11AC; # (쎥; 쎥; 쎥; 쎥; 쎥; ) HANGUL SYLLABLE SSYEONJ
+C3A6;C3A6;110A 1167 11AD;C3A6;110A 1167 11AD; # (쎦; 쎦; 쎦; 쎦; 쎦; ) HANGUL SYLLABLE SSYEONH
+C3A7;C3A7;110A 1167 11AE;C3A7;110A 1167 11AE; # (쎧; 쎧; 쎧; 쎧; 쎧; ) HANGUL SYLLABLE SSYEOD
+C3A8;C3A8;110A 1167 11AF;C3A8;110A 1167 11AF; # (쎨; 쎨; 쎨; 쎨; 쎨; ) HANGUL SYLLABLE SSYEOL
+C3A9;C3A9;110A 1167 11B0;C3A9;110A 1167 11B0; # (쎩; 쎩; 쎩; 쎩; 쎩; ) HANGUL SYLLABLE SSYEOLG
+C3AA;C3AA;110A 1167 11B1;C3AA;110A 1167 11B1; # (쎪; 쎪; 쎪; 쎪; 쎪; ) HANGUL SYLLABLE SSYEOLM
+C3AB;C3AB;110A 1167 11B2;C3AB;110A 1167 11B2; # (쎫; 쎫; 쎫; 쎫; 쎫; ) HANGUL SYLLABLE SSYEOLB
+C3AC;C3AC;110A 1167 11B3;C3AC;110A 1167 11B3; # (쎬; 쎬; 쎬; 쎬; 쎬; ) HANGUL SYLLABLE SSYEOLS
+C3AD;C3AD;110A 1167 11B4;C3AD;110A 1167 11B4; # (쎭; 쎭; 쎭; 쎭; 쎭; ) HANGUL SYLLABLE SSYEOLT
+C3AE;C3AE;110A 1167 11B5;C3AE;110A 1167 11B5; # (쎮; 쎮; 쎮; 쎮; 쎮; ) HANGUL SYLLABLE SSYEOLP
+C3AF;C3AF;110A 1167 11B6;C3AF;110A 1167 11B6; # (쎯; 쎯; 쎯; 쎯; 쎯; ) HANGUL SYLLABLE SSYEOLH
+C3B0;C3B0;110A 1167 11B7;C3B0;110A 1167 11B7; # (쎰; 쎰; 쎰; 쎰; 쎰; ) HANGUL SYLLABLE SSYEOM
+C3B1;C3B1;110A 1167 11B8;C3B1;110A 1167 11B8; # (쎱; 쎱; 쎱; 쎱; 쎱; ) HANGUL SYLLABLE SSYEOB
+C3B2;C3B2;110A 1167 11B9;C3B2;110A 1167 11B9; # (쎲; 쎲; 쎲; 쎲; 쎲; ) HANGUL SYLLABLE SSYEOBS
+C3B3;C3B3;110A 1167 11BA;C3B3;110A 1167 11BA; # (쎳; 쎳; 쎳; 쎳; 쎳; ) HANGUL SYLLABLE SSYEOS
+C3B4;C3B4;110A 1167 11BB;C3B4;110A 1167 11BB; # (쎴; 쎴; 쎴; 쎴; 쎴; ) HANGUL SYLLABLE SSYEOSS
+C3B5;C3B5;110A 1167 11BC;C3B5;110A 1167 11BC; # (쎵; 쎵; 쎵; 쎵; 쎵; ) HANGUL SYLLABLE SSYEONG
+C3B6;C3B6;110A 1167 11BD;C3B6;110A 1167 11BD; # (쎶; 쎶; 쎶; 쎶; 쎶; ) HANGUL SYLLABLE SSYEOJ
+C3B7;C3B7;110A 1167 11BE;C3B7;110A 1167 11BE; # (쎷; 쎷; 쎷; 쎷; 쎷; ) HANGUL SYLLABLE SSYEOC
+C3B8;C3B8;110A 1167 11BF;C3B8;110A 1167 11BF; # (쎸; 쎸; 쎸; 쎸; 쎸; ) HANGUL SYLLABLE SSYEOK
+C3B9;C3B9;110A 1167 11C0;C3B9;110A 1167 11C0; # (쎹; 쎹; 쎹; 쎹; 쎹; ) HANGUL SYLLABLE SSYEOT
+C3BA;C3BA;110A 1167 11C1;C3BA;110A 1167 11C1; # (쎺; 쎺; á„Šá…§á‡; 쎺; á„Šá…§á‡; ) HANGUL SYLLABLE SSYEOP
+C3BB;C3BB;110A 1167 11C2;C3BB;110A 1167 11C2; # (쎻; 쎻; 쎻; 쎻; 쎻; ) HANGUL SYLLABLE SSYEOH
+C3BC;C3BC;110A 1168;C3BC;110A 1168; # (쎼; 쎼; 쎼; 쎼; 쎼; ) HANGUL SYLLABLE SSYE
+C3BD;C3BD;110A 1168 11A8;C3BD;110A 1168 11A8; # (쎽; 쎽; 쎽; 쎽; 쎽; ) HANGUL SYLLABLE SSYEG
+C3BE;C3BE;110A 1168 11A9;C3BE;110A 1168 11A9; # (쎾; 쎾; 쎾; 쎾; 쎾; ) HANGUL SYLLABLE SSYEGG
+C3BF;C3BF;110A 1168 11AA;C3BF;110A 1168 11AA; # (쎿; 쎿; 쎿; 쎿; 쎿; ) HANGUL SYLLABLE SSYEGS
+C3C0;C3C0;110A 1168 11AB;C3C0;110A 1168 11AB; # (ì€; ì€; 쏀; ì€; 쏀; ) HANGUL SYLLABLE SSYEN
+C3C1;C3C1;110A 1168 11AC;C3C1;110A 1168 11AC; # (ì; ì; 쏁; ì; 쏁; ) HANGUL SYLLABLE SSYENJ
+C3C2;C3C2;110A 1168 11AD;C3C2;110A 1168 11AD; # (ì‚; ì‚; 쏂; ì‚; 쏂; ) HANGUL SYLLABLE SSYENH
+C3C3;C3C3;110A 1168 11AE;C3C3;110A 1168 11AE; # (ìƒ; ìƒ; 쏃; ìƒ; 쏃; ) HANGUL SYLLABLE SSYED
+C3C4;C3C4;110A 1168 11AF;C3C4;110A 1168 11AF; # (ì„; ì„; 쏄; ì„; 쏄; ) HANGUL SYLLABLE SSYEL
+C3C5;C3C5;110A 1168 11B0;C3C5;110A 1168 11B0; # (ì…; ì…; 쏅; ì…; 쏅; ) HANGUL SYLLABLE SSYELG
+C3C6;C3C6;110A 1168 11B1;C3C6;110A 1168 11B1; # (ì†; ì†; 쏆; ì†; 쏆; ) HANGUL SYLLABLE SSYELM
+C3C7;C3C7;110A 1168 11B2;C3C7;110A 1168 11B2; # (ì‡; ì‡; 쏇; ì‡; 쏇; ) HANGUL SYLLABLE SSYELB
+C3C8;C3C8;110A 1168 11B3;C3C8;110A 1168 11B3; # (ìˆ; ìˆ; 쏈; ìˆ; 쏈; ) HANGUL SYLLABLE SSYELS
+C3C9;C3C9;110A 1168 11B4;C3C9;110A 1168 11B4; # (ì‰; ì‰; 쏉; ì‰; 쏉; ) HANGUL SYLLABLE SSYELT
+C3CA;C3CA;110A 1168 11B5;C3CA;110A 1168 11B5; # (ìŠ; ìŠ; 쏊; ìŠ; 쏊; ) HANGUL SYLLABLE SSYELP
+C3CB;C3CB;110A 1168 11B6;C3CB;110A 1168 11B6; # (ì‹; ì‹; 쏋; ì‹; 쏋; ) HANGUL SYLLABLE SSYELH
+C3CC;C3CC;110A 1168 11B7;C3CC;110A 1168 11B7; # (ìŒ; ìŒ; 쏌; ìŒ; 쏌; ) HANGUL SYLLABLE SSYEM
+C3CD;C3CD;110A 1168 11B8;C3CD;110A 1168 11B8; # (ì; ì; 쏍; ì; 쏍; ) HANGUL SYLLABLE SSYEB
+C3CE;C3CE;110A 1168 11B9;C3CE;110A 1168 11B9; # (ìŽ; ìŽ; 쏎; ìŽ; 쏎; ) HANGUL SYLLABLE SSYEBS
+C3CF;C3CF;110A 1168 11BA;C3CF;110A 1168 11BA; # (ì; ì; 쏏; ì; 쏏; ) HANGUL SYLLABLE SSYES
+C3D0;C3D0;110A 1168 11BB;C3D0;110A 1168 11BB; # (ì; ì; 쏐; ì; 쏐; ) HANGUL SYLLABLE SSYESS
+C3D1;C3D1;110A 1168 11BC;C3D1;110A 1168 11BC; # (ì‘; ì‘; 쏑; ì‘; 쏑; ) HANGUL SYLLABLE SSYENG
+C3D2;C3D2;110A 1168 11BD;C3D2;110A 1168 11BD; # (ì’; ì’; 쏒; ì’; 쏒; ) HANGUL SYLLABLE SSYEJ
+C3D3;C3D3;110A 1168 11BE;C3D3;110A 1168 11BE; # (ì“; ì“; 쏓; ì“; 쏓; ) HANGUL SYLLABLE SSYEC
+C3D4;C3D4;110A 1168 11BF;C3D4;110A 1168 11BF; # (ì”; ì”; 쏔; ì”; 쏔; ) HANGUL SYLLABLE SSYEK
+C3D5;C3D5;110A 1168 11C0;C3D5;110A 1168 11C0; # (ì•; ì•; 쏕; ì•; 쏕; ) HANGUL SYLLABLE SSYET
+C3D6;C3D6;110A 1168 11C1;C3D6;110A 1168 11C1; # (ì–; ì–; á„Šá…¨á‡; ì–; á„Šá…¨á‡; ) HANGUL SYLLABLE SSYEP
+C3D7;C3D7;110A 1168 11C2;C3D7;110A 1168 11C2; # (ì—; ì—; 쏗; ì—; 쏗; ) HANGUL SYLLABLE SSYEH
+C3D8;C3D8;110A 1169;C3D8;110A 1169; # (ì˜; ì˜; á„Šá…©; ì˜; á„Šá…©; ) HANGUL SYLLABLE SSO
+C3D9;C3D9;110A 1169 11A8;C3D9;110A 1169 11A8; # (ì™; ì™; 쏙; ì™; 쏙; ) HANGUL SYLLABLE SSOG
+C3DA;C3DA;110A 1169 11A9;C3DA;110A 1169 11A9; # (ìš; ìš; 쏚; ìš; 쏚; ) HANGUL SYLLABLE SSOGG
+C3DB;C3DB;110A 1169 11AA;C3DB;110A 1169 11AA; # (ì›; ì›; 쏛; ì›; 쏛; ) HANGUL SYLLABLE SSOGS
+C3DC;C3DC;110A 1169 11AB;C3DC;110A 1169 11AB; # (ìœ; ìœ; 쏜; ìœ; 쏜; ) HANGUL SYLLABLE SSON
+C3DD;C3DD;110A 1169 11AC;C3DD;110A 1169 11AC; # (ì; ì; 쏝; ì; 쏝; ) HANGUL SYLLABLE SSONJ
+C3DE;C3DE;110A 1169 11AD;C3DE;110A 1169 11AD; # (ìž; ìž; 쏞; ìž; 쏞; ) HANGUL SYLLABLE SSONH
+C3DF;C3DF;110A 1169 11AE;C3DF;110A 1169 11AE; # (ìŸ; ìŸ; 쏟; ìŸ; 쏟; ) HANGUL SYLLABLE SSOD
+C3E0;C3E0;110A 1169 11AF;C3E0;110A 1169 11AF; # (ì ; ì ; 쏠; ì ; 쏠; ) HANGUL SYLLABLE SSOL
+C3E1;C3E1;110A 1169 11B0;C3E1;110A 1169 11B0; # (ì¡; ì¡; 쏡; ì¡; 쏡; ) HANGUL SYLLABLE SSOLG
+C3E2;C3E2;110A 1169 11B1;C3E2;110A 1169 11B1; # (ì¢; ì¢; 쏢; ì¢; 쏢; ) HANGUL SYLLABLE SSOLM
+C3E3;C3E3;110A 1169 11B2;C3E3;110A 1169 11B2; # (ì£; ì£; 쏣; ì£; 쏣; ) HANGUL SYLLABLE SSOLB
+C3E4;C3E4;110A 1169 11B3;C3E4;110A 1169 11B3; # (ì¤; ì¤; 쏤; ì¤; 쏤; ) HANGUL SYLLABLE SSOLS
+C3E5;C3E5;110A 1169 11B4;C3E5;110A 1169 11B4; # (ì¥; ì¥; 쏥; ì¥; 쏥; ) HANGUL SYLLABLE SSOLT
+C3E6;C3E6;110A 1169 11B5;C3E6;110A 1169 11B5; # (ì¦; ì¦; 쏦; ì¦; 쏦; ) HANGUL SYLLABLE SSOLP
+C3E7;C3E7;110A 1169 11B6;C3E7;110A 1169 11B6; # (ì§; ì§; 쏧; ì§; 쏧; ) HANGUL SYLLABLE SSOLH
+C3E8;C3E8;110A 1169 11B7;C3E8;110A 1169 11B7; # (ì¨; ì¨; 쏨; ì¨; 쏨; ) HANGUL SYLLABLE SSOM
+C3E9;C3E9;110A 1169 11B8;C3E9;110A 1169 11B8; # (ì©; ì©; 쏩; ì©; 쏩; ) HANGUL SYLLABLE SSOB
+C3EA;C3EA;110A 1169 11B9;C3EA;110A 1169 11B9; # (ìª; ìª; 쏪; ìª; 쏪; ) HANGUL SYLLABLE SSOBS
+C3EB;C3EB;110A 1169 11BA;C3EB;110A 1169 11BA; # (ì«; ì«; 쏫; ì«; 쏫; ) HANGUL SYLLABLE SSOS
+C3EC;C3EC;110A 1169 11BB;C3EC;110A 1169 11BB; # (ì¬; ì¬; 쏬; ì¬; 쏬; ) HANGUL SYLLABLE SSOSS
+C3ED;C3ED;110A 1169 11BC;C3ED;110A 1169 11BC; # (ì­; ì­; 쏭; ì­; 쏭; ) HANGUL SYLLABLE SSONG
+C3EE;C3EE;110A 1169 11BD;C3EE;110A 1169 11BD; # (ì®; ì®; 쏮; ì®; 쏮; ) HANGUL SYLLABLE SSOJ
+C3EF;C3EF;110A 1169 11BE;C3EF;110A 1169 11BE; # (ì¯; ì¯; 쏯; ì¯; 쏯; ) HANGUL SYLLABLE SSOC
+C3F0;C3F0;110A 1169 11BF;C3F0;110A 1169 11BF; # (ì°; ì°; 쏰; ì°; 쏰; ) HANGUL SYLLABLE SSOK
+C3F1;C3F1;110A 1169 11C0;C3F1;110A 1169 11C0; # (ì±; ì±; 쏱; ì±; 쏱; ) HANGUL SYLLABLE SSOT
+C3F2;C3F2;110A 1169 11C1;C3F2;110A 1169 11C1; # (ì²; ì²; á„Šá…©á‡; ì²; á„Šá…©á‡; ) HANGUL SYLLABLE SSOP
+C3F3;C3F3;110A 1169 11C2;C3F3;110A 1169 11C2; # (ì³; ì³; 쏳; ì³; 쏳; ) HANGUL SYLLABLE SSOH
+C3F4;C3F4;110A 116A;C3F4;110A 116A; # (ì´; ì´; á„Šá…ª; ì´; á„Šá…ª; ) HANGUL SYLLABLE SSWA
+C3F5;C3F5;110A 116A 11A8;C3F5;110A 116A 11A8; # (ìµ; ìµ; 쏵; ìµ; 쏵; ) HANGUL SYLLABLE SSWAG
+C3F6;C3F6;110A 116A 11A9;C3F6;110A 116A 11A9; # (ì¶; ì¶; 쏶; ì¶; 쏶; ) HANGUL SYLLABLE SSWAGG
+C3F7;C3F7;110A 116A 11AA;C3F7;110A 116A 11AA; # (ì·; ì·; 쏷; ì·; 쏷; ) HANGUL SYLLABLE SSWAGS
+C3F8;C3F8;110A 116A 11AB;C3F8;110A 116A 11AB; # (ì¸; ì¸; 쏸; ì¸; 쏸; ) HANGUL SYLLABLE SSWAN
+C3F9;C3F9;110A 116A 11AC;C3F9;110A 116A 11AC; # (ì¹; ì¹; 쏹; ì¹; 쏹; ) HANGUL SYLLABLE SSWANJ
+C3FA;C3FA;110A 116A 11AD;C3FA;110A 116A 11AD; # (ìº; ìº; 쏺; ìº; 쏺; ) HANGUL SYLLABLE SSWANH
+C3FB;C3FB;110A 116A 11AE;C3FB;110A 116A 11AE; # (ì»; ì»; 쏻; ì»; 쏻; ) HANGUL SYLLABLE SSWAD
+C3FC;C3FC;110A 116A 11AF;C3FC;110A 116A 11AF; # (ì¼; ì¼; 쏼; ì¼; 쏼; ) HANGUL SYLLABLE SSWAL
+C3FD;C3FD;110A 116A 11B0;C3FD;110A 116A 11B0; # (ì½; ì½; 쏽; ì½; 쏽; ) HANGUL SYLLABLE SSWALG
+C3FE;C3FE;110A 116A 11B1;C3FE;110A 116A 11B1; # (ì¾; ì¾; 쏾; ì¾; 쏾; ) HANGUL SYLLABLE SSWALM
+C3FF;C3FF;110A 116A 11B2;C3FF;110A 116A 11B2; # (ì¿; ì¿; 쏿; ì¿; 쏿; ) HANGUL SYLLABLE SSWALB
+C400;C400;110A 116A 11B3;C400;110A 116A 11B3; # (ì€; ì€; 쐀; ì€; 쐀; ) HANGUL SYLLABLE SSWALS
+C401;C401;110A 116A 11B4;C401;110A 116A 11B4; # (ì; ì; 쐁; ì; 쐁; ) HANGUL SYLLABLE SSWALT
+C402;C402;110A 116A 11B5;C402;110A 116A 11B5; # (ì‚; ì‚; 쐂; ì‚; 쐂; ) HANGUL SYLLABLE SSWALP
+C403;C403;110A 116A 11B6;C403;110A 116A 11B6; # (ìƒ; ìƒ; 쐃; ìƒ; 쐃; ) HANGUL SYLLABLE SSWALH
+C404;C404;110A 116A 11B7;C404;110A 116A 11B7; # (ì„; ì„; 쐄; ì„; 쐄; ) HANGUL SYLLABLE SSWAM
+C405;C405;110A 116A 11B8;C405;110A 116A 11B8; # (ì…; ì…; 쐅; ì…; 쐅; ) HANGUL SYLLABLE SSWAB
+C406;C406;110A 116A 11B9;C406;110A 116A 11B9; # (ì†; ì†; 쐆; ì†; 쐆; ) HANGUL SYLLABLE SSWABS
+C407;C407;110A 116A 11BA;C407;110A 116A 11BA; # (ì‡; ì‡; 쐇; ì‡; 쐇; ) HANGUL SYLLABLE SSWAS
+C408;C408;110A 116A 11BB;C408;110A 116A 11BB; # (ìˆ; ìˆ; 쐈; ìˆ; 쐈; ) HANGUL SYLLABLE SSWASS
+C409;C409;110A 116A 11BC;C409;110A 116A 11BC; # (ì‰; ì‰; 쐉; ì‰; 쐉; ) HANGUL SYLLABLE SSWANG
+C40A;C40A;110A 116A 11BD;C40A;110A 116A 11BD; # (ìŠ; ìŠ; 쐊; ìŠ; 쐊; ) HANGUL SYLLABLE SSWAJ
+C40B;C40B;110A 116A 11BE;C40B;110A 116A 11BE; # (ì‹; ì‹; 쐋; ì‹; 쐋; ) HANGUL SYLLABLE SSWAC
+C40C;C40C;110A 116A 11BF;C40C;110A 116A 11BF; # (ìŒ; ìŒ; 쐌; ìŒ; 쐌; ) HANGUL SYLLABLE SSWAK
+C40D;C40D;110A 116A 11C0;C40D;110A 116A 11C0; # (ì; ì; 쐍; ì; 쐍; ) HANGUL SYLLABLE SSWAT
+C40E;C40E;110A 116A 11C1;C40E;110A 116A 11C1; # (ìŽ; ìŽ; á„Šá…ªá‡; ìŽ; á„Šá…ªá‡; ) HANGUL SYLLABLE SSWAP
+C40F;C40F;110A 116A 11C2;C40F;110A 116A 11C2; # (ì; ì; 쐏; ì; 쐏; ) HANGUL SYLLABLE SSWAH
+C410;C410;110A 116B;C410;110A 116B; # (ì; ì; á„Šá…«; ì; á„Šá…«; ) HANGUL SYLLABLE SSWAE
+C411;C411;110A 116B 11A8;C411;110A 116B 11A8; # (ì‘; ì‘; 쐑; ì‘; 쐑; ) HANGUL SYLLABLE SSWAEG
+C412;C412;110A 116B 11A9;C412;110A 116B 11A9; # (ì’; ì’; 쐒; ì’; 쐒; ) HANGUL SYLLABLE SSWAEGG
+C413;C413;110A 116B 11AA;C413;110A 116B 11AA; # (ì“; ì“; 쐓; ì“; 쐓; ) HANGUL SYLLABLE SSWAEGS
+C414;C414;110A 116B 11AB;C414;110A 116B 11AB; # (ì”; ì”; 쐔; ì”; 쐔; ) HANGUL SYLLABLE SSWAEN
+C415;C415;110A 116B 11AC;C415;110A 116B 11AC; # (ì•; ì•; 쐕; ì•; 쐕; ) HANGUL SYLLABLE SSWAENJ
+C416;C416;110A 116B 11AD;C416;110A 116B 11AD; # (ì–; ì–; 쐖; ì–; 쐖; ) HANGUL SYLLABLE SSWAENH
+C417;C417;110A 116B 11AE;C417;110A 116B 11AE; # (ì—; ì—; 쐗; ì—; 쐗; ) HANGUL SYLLABLE SSWAED
+C418;C418;110A 116B 11AF;C418;110A 116B 11AF; # (ì˜; ì˜; 쐘; ì˜; 쐘; ) HANGUL SYLLABLE SSWAEL
+C419;C419;110A 116B 11B0;C419;110A 116B 11B0; # (ì™; ì™; 쐙; ì™; 쐙; ) HANGUL SYLLABLE SSWAELG
+C41A;C41A;110A 116B 11B1;C41A;110A 116B 11B1; # (ìš; ìš; 쐚; ìš; 쐚; ) HANGUL SYLLABLE SSWAELM
+C41B;C41B;110A 116B 11B2;C41B;110A 116B 11B2; # (ì›; ì›; 쐛; ì›; 쐛; ) HANGUL SYLLABLE SSWAELB
+C41C;C41C;110A 116B 11B3;C41C;110A 116B 11B3; # (ìœ; ìœ; 쐜; ìœ; 쐜; ) HANGUL SYLLABLE SSWAELS
+C41D;C41D;110A 116B 11B4;C41D;110A 116B 11B4; # (ì; ì; 쐝; ì; 쐝; ) HANGUL SYLLABLE SSWAELT
+C41E;C41E;110A 116B 11B5;C41E;110A 116B 11B5; # (ìž; ìž; 쐞; ìž; 쐞; ) HANGUL SYLLABLE SSWAELP
+C41F;C41F;110A 116B 11B6;C41F;110A 116B 11B6; # (ìŸ; ìŸ; 쐟; ìŸ; 쐟; ) HANGUL SYLLABLE SSWAELH
+C420;C420;110A 116B 11B7;C420;110A 116B 11B7; # (ì ; ì ; 쐠; ì ; 쐠; ) HANGUL SYLLABLE SSWAEM
+C421;C421;110A 116B 11B8;C421;110A 116B 11B8; # (ì¡; ì¡; 쐡; ì¡; 쐡; ) HANGUL SYLLABLE SSWAEB
+C422;C422;110A 116B 11B9;C422;110A 116B 11B9; # (ì¢; ì¢; 쐢; ì¢; 쐢; ) HANGUL SYLLABLE SSWAEBS
+C423;C423;110A 116B 11BA;C423;110A 116B 11BA; # (ì£; ì£; 쐣; ì£; 쐣; ) HANGUL SYLLABLE SSWAES
+C424;C424;110A 116B 11BB;C424;110A 116B 11BB; # (ì¤; ì¤; 쐤; ì¤; 쐤; ) HANGUL SYLLABLE SSWAESS
+C425;C425;110A 116B 11BC;C425;110A 116B 11BC; # (ì¥; ì¥; 쐥; ì¥; 쐥; ) HANGUL SYLLABLE SSWAENG
+C426;C426;110A 116B 11BD;C426;110A 116B 11BD; # (ì¦; ì¦; 쐦; ì¦; 쐦; ) HANGUL SYLLABLE SSWAEJ
+C427;C427;110A 116B 11BE;C427;110A 116B 11BE; # (ì§; ì§; 쐧; ì§; 쐧; ) HANGUL SYLLABLE SSWAEC
+C428;C428;110A 116B 11BF;C428;110A 116B 11BF; # (ì¨; ì¨; 쐨; ì¨; 쐨; ) HANGUL SYLLABLE SSWAEK
+C429;C429;110A 116B 11C0;C429;110A 116B 11C0; # (ì©; ì©; 쐩; ì©; 쐩; ) HANGUL SYLLABLE SSWAET
+C42A;C42A;110A 116B 11C1;C42A;110A 116B 11C1; # (ìª; ìª; á„Šá…«á‡; ìª; á„Šá…«á‡; ) HANGUL SYLLABLE SSWAEP
+C42B;C42B;110A 116B 11C2;C42B;110A 116B 11C2; # (ì«; ì«; 쐫; ì«; 쐫; ) HANGUL SYLLABLE SSWAEH
+C42C;C42C;110A 116C;C42C;110A 116C; # (ì¬; ì¬; á„Šá…¬; ì¬; á„Šá…¬; ) HANGUL SYLLABLE SSOE
+C42D;C42D;110A 116C 11A8;C42D;110A 116C 11A8; # (ì­; ì­; 쐭; ì­; 쐭; ) HANGUL SYLLABLE SSOEG
+C42E;C42E;110A 116C 11A9;C42E;110A 116C 11A9; # (ì®; ì®; 쐮; ì®; 쐮; ) HANGUL SYLLABLE SSOEGG
+C42F;C42F;110A 116C 11AA;C42F;110A 116C 11AA; # (ì¯; ì¯; 쐯; ì¯; 쐯; ) HANGUL SYLLABLE SSOEGS
+C430;C430;110A 116C 11AB;C430;110A 116C 11AB; # (ì°; ì°; 쐰; ì°; 쐰; ) HANGUL SYLLABLE SSOEN
+C431;C431;110A 116C 11AC;C431;110A 116C 11AC; # (ì±; ì±; 쐱; ì±; 쐱; ) HANGUL SYLLABLE SSOENJ
+C432;C432;110A 116C 11AD;C432;110A 116C 11AD; # (ì²; ì²; 쐲; ì²; 쐲; ) HANGUL SYLLABLE SSOENH
+C433;C433;110A 116C 11AE;C433;110A 116C 11AE; # (ì³; ì³; 쐳; ì³; 쐳; ) HANGUL SYLLABLE SSOED
+C434;C434;110A 116C 11AF;C434;110A 116C 11AF; # (ì´; ì´; 쐴; ì´; 쐴; ) HANGUL SYLLABLE SSOEL
+C435;C435;110A 116C 11B0;C435;110A 116C 11B0; # (ìµ; ìµ; 쐵; ìµ; 쐵; ) HANGUL SYLLABLE SSOELG
+C436;C436;110A 116C 11B1;C436;110A 116C 11B1; # (ì¶; ì¶; 쐶; ì¶; 쐶; ) HANGUL SYLLABLE SSOELM
+C437;C437;110A 116C 11B2;C437;110A 116C 11B2; # (ì·; ì·; 쐷; ì·; 쐷; ) HANGUL SYLLABLE SSOELB
+C438;C438;110A 116C 11B3;C438;110A 116C 11B3; # (ì¸; ì¸; 쐸; ì¸; 쐸; ) HANGUL SYLLABLE SSOELS
+C439;C439;110A 116C 11B4;C439;110A 116C 11B4; # (ì¹; ì¹; 쐹; ì¹; 쐹; ) HANGUL SYLLABLE SSOELT
+C43A;C43A;110A 116C 11B5;C43A;110A 116C 11B5; # (ìº; ìº; 쐺; ìº; 쐺; ) HANGUL SYLLABLE SSOELP
+C43B;C43B;110A 116C 11B6;C43B;110A 116C 11B6; # (ì»; ì»; 쐻; ì»; 쐻; ) HANGUL SYLLABLE SSOELH
+C43C;C43C;110A 116C 11B7;C43C;110A 116C 11B7; # (ì¼; ì¼; 쐼; ì¼; 쐼; ) HANGUL SYLLABLE SSOEM
+C43D;C43D;110A 116C 11B8;C43D;110A 116C 11B8; # (ì½; ì½; 쐽; ì½; 쐽; ) HANGUL SYLLABLE SSOEB
+C43E;C43E;110A 116C 11B9;C43E;110A 116C 11B9; # (ì¾; ì¾; 쐾; ì¾; 쐾; ) HANGUL SYLLABLE SSOEBS
+C43F;C43F;110A 116C 11BA;C43F;110A 116C 11BA; # (ì¿; ì¿; 쐿; ì¿; 쐿; ) HANGUL SYLLABLE SSOES
+C440;C440;110A 116C 11BB;C440;110A 116C 11BB; # (쑀; 쑀; 쑀; 쑀; 쑀; ) HANGUL SYLLABLE SSOESS
+C441;C441;110A 116C 11BC;C441;110A 116C 11BC; # (ì‘; ì‘; 쑁; ì‘; 쑁; ) HANGUL SYLLABLE SSOENG
+C442;C442;110A 116C 11BD;C442;110A 116C 11BD; # (쑂; 쑂; 쑂; 쑂; 쑂; ) HANGUL SYLLABLE SSOEJ
+C443;C443;110A 116C 11BE;C443;110A 116C 11BE; # (쑃; 쑃; 쑃; 쑃; 쑃; ) HANGUL SYLLABLE SSOEC
+C444;C444;110A 116C 11BF;C444;110A 116C 11BF; # (쑄; 쑄; 쑄; 쑄; 쑄; ) HANGUL SYLLABLE SSOEK
+C445;C445;110A 116C 11C0;C445;110A 116C 11C0; # (쑅; 쑅; 쑅; 쑅; 쑅; ) HANGUL SYLLABLE SSOET
+C446;C446;110A 116C 11C1;C446;110A 116C 11C1; # (쑆; 쑆; á„Šá…¬á‡; 쑆; á„Šá…¬á‡; ) HANGUL SYLLABLE SSOEP
+C447;C447;110A 116C 11C2;C447;110A 116C 11C2; # (쑇; 쑇; 쑇; 쑇; 쑇; ) HANGUL SYLLABLE SSOEH
+C448;C448;110A 116D;C448;110A 116D; # (쑈; 쑈; 쑈; 쑈; 쑈; ) HANGUL SYLLABLE SSYO
+C449;C449;110A 116D 11A8;C449;110A 116D 11A8; # (쑉; 쑉; 쑉; 쑉; 쑉; ) HANGUL SYLLABLE SSYOG
+C44A;C44A;110A 116D 11A9;C44A;110A 116D 11A9; # (쑊; 쑊; 쑊; 쑊; 쑊; ) HANGUL SYLLABLE SSYOGG
+C44B;C44B;110A 116D 11AA;C44B;110A 116D 11AA; # (쑋; 쑋; 쑋; 쑋; 쑋; ) HANGUL SYLLABLE SSYOGS
+C44C;C44C;110A 116D 11AB;C44C;110A 116D 11AB; # (쑌; 쑌; 쑌; 쑌; 쑌; ) HANGUL SYLLABLE SSYON
+C44D;C44D;110A 116D 11AC;C44D;110A 116D 11AC; # (ì‘; ì‘; 쑍; ì‘; 쑍; ) HANGUL SYLLABLE SSYONJ
+C44E;C44E;110A 116D 11AD;C44E;110A 116D 11AD; # (쑎; 쑎; 쑎; 쑎; 쑎; ) HANGUL SYLLABLE SSYONH
+C44F;C44F;110A 116D 11AE;C44F;110A 116D 11AE; # (ì‘; ì‘; 쑏; ì‘; 쑏; ) HANGUL SYLLABLE SSYOD
+C450;C450;110A 116D 11AF;C450;110A 116D 11AF; # (ì‘; ì‘; 쑐; ì‘; 쑐; ) HANGUL SYLLABLE SSYOL
+C451;C451;110A 116D 11B0;C451;110A 116D 11B0; # (쑑; 쑑; 쑑; 쑑; 쑑; ) HANGUL SYLLABLE SSYOLG
+C452;C452;110A 116D 11B1;C452;110A 116D 11B1; # (쑒; 쑒; 쑒; 쑒; 쑒; ) HANGUL SYLLABLE SSYOLM
+C453;C453;110A 116D 11B2;C453;110A 116D 11B2; # (쑓; 쑓; 쑓; 쑓; 쑓; ) HANGUL SYLLABLE SSYOLB
+C454;C454;110A 116D 11B3;C454;110A 116D 11B3; # (쑔; 쑔; 쑔; 쑔; 쑔; ) HANGUL SYLLABLE SSYOLS
+C455;C455;110A 116D 11B4;C455;110A 116D 11B4; # (쑕; 쑕; 쑕; 쑕; 쑕; ) HANGUL SYLLABLE SSYOLT
+C456;C456;110A 116D 11B5;C456;110A 116D 11B5; # (쑖; 쑖; 쑖; 쑖; 쑖; ) HANGUL SYLLABLE SSYOLP
+C457;C457;110A 116D 11B6;C457;110A 116D 11B6; # (쑗; 쑗; 쑗; 쑗; 쑗; ) HANGUL SYLLABLE SSYOLH
+C458;C458;110A 116D 11B7;C458;110A 116D 11B7; # (쑘; 쑘; 쑘; 쑘; 쑘; ) HANGUL SYLLABLE SSYOM
+C459;C459;110A 116D 11B8;C459;110A 116D 11B8; # (쑙; 쑙; 쑙; 쑙; 쑙; ) HANGUL SYLLABLE SSYOB
+C45A;C45A;110A 116D 11B9;C45A;110A 116D 11B9; # (쑚; 쑚; 쑚; 쑚; 쑚; ) HANGUL SYLLABLE SSYOBS
+C45B;C45B;110A 116D 11BA;C45B;110A 116D 11BA; # (쑛; 쑛; 쑛; 쑛; 쑛; ) HANGUL SYLLABLE SSYOS
+C45C;C45C;110A 116D 11BB;C45C;110A 116D 11BB; # (쑜; 쑜; 쑜; 쑜; 쑜; ) HANGUL SYLLABLE SSYOSS
+C45D;C45D;110A 116D 11BC;C45D;110A 116D 11BC; # (ì‘; ì‘; 쑝; ì‘; 쑝; ) HANGUL SYLLABLE SSYONG
+C45E;C45E;110A 116D 11BD;C45E;110A 116D 11BD; # (쑞; 쑞; 쑞; 쑞; 쑞; ) HANGUL SYLLABLE SSYOJ
+C45F;C45F;110A 116D 11BE;C45F;110A 116D 11BE; # (쑟; 쑟; 쑟; 쑟; 쑟; ) HANGUL SYLLABLE SSYOC
+C460;C460;110A 116D 11BF;C460;110A 116D 11BF; # (쑠; 쑠; 쑠; 쑠; 쑠; ) HANGUL SYLLABLE SSYOK
+C461;C461;110A 116D 11C0;C461;110A 116D 11C0; # (쑡; 쑡; 쑡; 쑡; 쑡; ) HANGUL SYLLABLE SSYOT
+C462;C462;110A 116D 11C1;C462;110A 116D 11C1; # (ì‘¢; ì‘¢; á„Šá…­á‡; ì‘¢; á„Šá…­á‡; ) HANGUL SYLLABLE SSYOP
+C463;C463;110A 116D 11C2;C463;110A 116D 11C2; # (쑣; 쑣; 쑣; 쑣; 쑣; ) HANGUL SYLLABLE SSYOH
+C464;C464;110A 116E;C464;110A 116E; # (쑤; 쑤; 쑤; 쑤; 쑤; ) HANGUL SYLLABLE SSU
+C465;C465;110A 116E 11A8;C465;110A 116E 11A8; # (쑥; 쑥; 쑥; 쑥; 쑥; ) HANGUL SYLLABLE SSUG
+C466;C466;110A 116E 11A9;C466;110A 116E 11A9; # (쑦; 쑦; 쑦; 쑦; 쑦; ) HANGUL SYLLABLE SSUGG
+C467;C467;110A 116E 11AA;C467;110A 116E 11AA; # (쑧; 쑧; 쑧; 쑧; 쑧; ) HANGUL SYLLABLE SSUGS
+C468;C468;110A 116E 11AB;C468;110A 116E 11AB; # (쑨; 쑨; 쑨; 쑨; 쑨; ) HANGUL SYLLABLE SSUN
+C469;C469;110A 116E 11AC;C469;110A 116E 11AC; # (쑩; 쑩; 쑩; 쑩; 쑩; ) HANGUL SYLLABLE SSUNJ
+C46A;C46A;110A 116E 11AD;C46A;110A 116E 11AD; # (쑪; 쑪; 쑪; 쑪; 쑪; ) HANGUL SYLLABLE SSUNH
+C46B;C46B;110A 116E 11AE;C46B;110A 116E 11AE; # (쑫; 쑫; 쑫; 쑫; 쑫; ) HANGUL SYLLABLE SSUD
+C46C;C46C;110A 116E 11AF;C46C;110A 116E 11AF; # (쑬; 쑬; 쑬; 쑬; 쑬; ) HANGUL SYLLABLE SSUL
+C46D;C46D;110A 116E 11B0;C46D;110A 116E 11B0; # (쑭; 쑭; 쑭; 쑭; 쑭; ) HANGUL SYLLABLE SSULG
+C46E;C46E;110A 116E 11B1;C46E;110A 116E 11B1; # (쑮; 쑮; 쑮; 쑮; 쑮; ) HANGUL SYLLABLE SSULM
+C46F;C46F;110A 116E 11B2;C46F;110A 116E 11B2; # (쑯; 쑯; 쑯; 쑯; 쑯; ) HANGUL SYLLABLE SSULB
+C470;C470;110A 116E 11B3;C470;110A 116E 11B3; # (쑰; 쑰; 쑰; 쑰; 쑰; ) HANGUL SYLLABLE SSULS
+C471;C471;110A 116E 11B4;C471;110A 116E 11B4; # (쑱; 쑱; 쑱; 쑱; 쑱; ) HANGUL SYLLABLE SSULT
+C472;C472;110A 116E 11B5;C472;110A 116E 11B5; # (쑲; 쑲; 쑲; 쑲; 쑲; ) HANGUL SYLLABLE SSULP
+C473;C473;110A 116E 11B6;C473;110A 116E 11B6; # (쑳; 쑳; 쑳; 쑳; 쑳; ) HANGUL SYLLABLE SSULH
+C474;C474;110A 116E 11B7;C474;110A 116E 11B7; # (쑴; 쑴; 쑴; 쑴; 쑴; ) HANGUL SYLLABLE SSUM
+C475;C475;110A 116E 11B8;C475;110A 116E 11B8; # (쑵; 쑵; 쑵; 쑵; 쑵; ) HANGUL SYLLABLE SSUB
+C476;C476;110A 116E 11B9;C476;110A 116E 11B9; # (쑶; 쑶; 쑶; 쑶; 쑶; ) HANGUL SYLLABLE SSUBS
+C477;C477;110A 116E 11BA;C477;110A 116E 11BA; # (쑷; 쑷; 쑷; 쑷; 쑷; ) HANGUL SYLLABLE SSUS
+C478;C478;110A 116E 11BB;C478;110A 116E 11BB; # (쑸; 쑸; 쑸; 쑸; 쑸; ) HANGUL SYLLABLE SSUSS
+C479;C479;110A 116E 11BC;C479;110A 116E 11BC; # (쑹; 쑹; 쑹; 쑹; 쑹; ) HANGUL SYLLABLE SSUNG
+C47A;C47A;110A 116E 11BD;C47A;110A 116E 11BD; # (쑺; 쑺; 쑺; 쑺; 쑺; ) HANGUL SYLLABLE SSUJ
+C47B;C47B;110A 116E 11BE;C47B;110A 116E 11BE; # (쑻; 쑻; 쑻; 쑻; 쑻; ) HANGUL SYLLABLE SSUC
+C47C;C47C;110A 116E 11BF;C47C;110A 116E 11BF; # (쑼; 쑼; 쑼; 쑼; 쑼; ) HANGUL SYLLABLE SSUK
+C47D;C47D;110A 116E 11C0;C47D;110A 116E 11C0; # (쑽; 쑽; 쑽; 쑽; 쑽; ) HANGUL SYLLABLE SSUT
+C47E;C47E;110A 116E 11C1;C47E;110A 116E 11C1; # (쑾; 쑾; á„Šá…®á‡; 쑾; á„Šá…®á‡; ) HANGUL SYLLABLE SSUP
+C47F;C47F;110A 116E 11C2;C47F;110A 116E 11C2; # (쑿; 쑿; 쑿; 쑿; 쑿; ) HANGUL SYLLABLE SSUH
+C480;C480;110A 116F;C480;110A 116F; # (ì’€; ì’€; á„Šá…¯; ì’€; á„Šá…¯; ) HANGUL SYLLABLE SSWEO
+C481;C481;110A 116F 11A8;C481;110A 116F 11A8; # (ì’; ì’; 쒁; ì’; 쒁; ) HANGUL SYLLABLE SSWEOG
+C482;C482;110A 116F 11A9;C482;110A 116F 11A9; # (쒂; 쒂; 쒂; 쒂; 쒂; ) HANGUL SYLLABLE SSWEOGG
+C483;C483;110A 116F 11AA;C483;110A 116F 11AA; # (쒃; 쒃; 쒃; 쒃; 쒃; ) HANGUL SYLLABLE SSWEOGS
+C484;C484;110A 116F 11AB;C484;110A 116F 11AB; # (쒄; 쒄; 쒄; 쒄; 쒄; ) HANGUL SYLLABLE SSWEON
+C485;C485;110A 116F 11AC;C485;110A 116F 11AC; # (쒅; 쒅; 쒅; 쒅; 쒅; ) HANGUL SYLLABLE SSWEONJ
+C486;C486;110A 116F 11AD;C486;110A 116F 11AD; # (쒆; 쒆; 쒆; 쒆; 쒆; ) HANGUL SYLLABLE SSWEONH
+C487;C487;110A 116F 11AE;C487;110A 116F 11AE; # (쒇; 쒇; 쒇; 쒇; 쒇; ) HANGUL SYLLABLE SSWEOD
+C488;C488;110A 116F 11AF;C488;110A 116F 11AF; # (쒈; 쒈; 쒈; 쒈; 쒈; ) HANGUL SYLLABLE SSWEOL
+C489;C489;110A 116F 11B0;C489;110A 116F 11B0; # (쒉; 쒉; 쒉; 쒉; 쒉; ) HANGUL SYLLABLE SSWEOLG
+C48A;C48A;110A 116F 11B1;C48A;110A 116F 11B1; # (쒊; 쒊; 쒊; 쒊; 쒊; ) HANGUL SYLLABLE SSWEOLM
+C48B;C48B;110A 116F 11B2;C48B;110A 116F 11B2; # (쒋; 쒋; 쒋; 쒋; 쒋; ) HANGUL SYLLABLE SSWEOLB
+C48C;C48C;110A 116F 11B3;C48C;110A 116F 11B3; # (쒌; 쒌; 쒌; 쒌; 쒌; ) HANGUL SYLLABLE SSWEOLS
+C48D;C48D;110A 116F 11B4;C48D;110A 116F 11B4; # (ì’; ì’; 쒍; ì’; 쒍; ) HANGUL SYLLABLE SSWEOLT
+C48E;C48E;110A 116F 11B5;C48E;110A 116F 11B5; # (쒎; 쒎; 쒎; 쒎; 쒎; ) HANGUL SYLLABLE SSWEOLP
+C48F;C48F;110A 116F 11B6;C48F;110A 116F 11B6; # (ì’; ì’; 쒏; ì’; 쒏; ) HANGUL SYLLABLE SSWEOLH
+C490;C490;110A 116F 11B7;C490;110A 116F 11B7; # (ì’; ì’; 쒐; ì’; 쒐; ) HANGUL SYLLABLE SSWEOM
+C491;C491;110A 116F 11B8;C491;110A 116F 11B8; # (쒑; 쒑; 쒑; 쒑; 쒑; ) HANGUL SYLLABLE SSWEOB
+C492;C492;110A 116F 11B9;C492;110A 116F 11B9; # (쒒; 쒒; 쒒; 쒒; 쒒; ) HANGUL SYLLABLE SSWEOBS
+C493;C493;110A 116F 11BA;C493;110A 116F 11BA; # (쒓; 쒓; 쒓; 쒓; 쒓; ) HANGUL SYLLABLE SSWEOS
+C494;C494;110A 116F 11BB;C494;110A 116F 11BB; # (쒔; 쒔; 쒔; 쒔; 쒔; ) HANGUL SYLLABLE SSWEOSS
+C495;C495;110A 116F 11BC;C495;110A 116F 11BC; # (쒕; 쒕; 쒕; 쒕; 쒕; ) HANGUL SYLLABLE SSWEONG
+C496;C496;110A 116F 11BD;C496;110A 116F 11BD; # (쒖; 쒖; 쒖; 쒖; 쒖; ) HANGUL SYLLABLE SSWEOJ
+C497;C497;110A 116F 11BE;C497;110A 116F 11BE; # (쒗; 쒗; 쒗; 쒗; 쒗; ) HANGUL SYLLABLE SSWEOC
+C498;C498;110A 116F 11BF;C498;110A 116F 11BF; # (쒘; 쒘; 쒘; 쒘; 쒘; ) HANGUL SYLLABLE SSWEOK
+C499;C499;110A 116F 11C0;C499;110A 116F 11C0; # (쒙; 쒙; 쒙; 쒙; 쒙; ) HANGUL SYLLABLE SSWEOT
+C49A;C49A;110A 116F 11C1;C49A;110A 116F 11C1; # (ì’š; ì’š; á„Šá…¯á‡; ì’š; á„Šá…¯á‡; ) HANGUL SYLLABLE SSWEOP
+C49B;C49B;110A 116F 11C2;C49B;110A 116F 11C2; # (쒛; 쒛; 쒛; 쒛; 쒛; ) HANGUL SYLLABLE SSWEOH
+C49C;C49C;110A 1170;C49C;110A 1170; # (쒜; 쒜; 쒜; 쒜; 쒜; ) HANGUL SYLLABLE SSWE
+C49D;C49D;110A 1170 11A8;C49D;110A 1170 11A8; # (ì’; ì’; 쒝; ì’; 쒝; ) HANGUL SYLLABLE SSWEG
+C49E;C49E;110A 1170 11A9;C49E;110A 1170 11A9; # (쒞; 쒞; 쒞; 쒞; 쒞; ) HANGUL SYLLABLE SSWEGG
+C49F;C49F;110A 1170 11AA;C49F;110A 1170 11AA; # (쒟; 쒟; 쒟; 쒟; 쒟; ) HANGUL SYLLABLE SSWEGS
+C4A0;C4A0;110A 1170 11AB;C4A0;110A 1170 11AB; # (쒠; 쒠; 쒠; 쒠; 쒠; ) HANGUL SYLLABLE SSWEN
+C4A1;C4A1;110A 1170 11AC;C4A1;110A 1170 11AC; # (쒡; 쒡; 쒡; 쒡; 쒡; ) HANGUL SYLLABLE SSWENJ
+C4A2;C4A2;110A 1170 11AD;C4A2;110A 1170 11AD; # (쒢; 쒢; 쒢; 쒢; 쒢; ) HANGUL SYLLABLE SSWENH
+C4A3;C4A3;110A 1170 11AE;C4A3;110A 1170 11AE; # (쒣; 쒣; 쒣; 쒣; 쒣; ) HANGUL SYLLABLE SSWED
+C4A4;C4A4;110A 1170 11AF;C4A4;110A 1170 11AF; # (쒤; 쒤; 쒤; 쒤; 쒤; ) HANGUL SYLLABLE SSWEL
+C4A5;C4A5;110A 1170 11B0;C4A5;110A 1170 11B0; # (쒥; 쒥; 쒥; 쒥; 쒥; ) HANGUL SYLLABLE SSWELG
+C4A6;C4A6;110A 1170 11B1;C4A6;110A 1170 11B1; # (쒦; 쒦; 쒦; 쒦; 쒦; ) HANGUL SYLLABLE SSWELM
+C4A7;C4A7;110A 1170 11B2;C4A7;110A 1170 11B2; # (쒧; 쒧; 쒧; 쒧; 쒧; ) HANGUL SYLLABLE SSWELB
+C4A8;C4A8;110A 1170 11B3;C4A8;110A 1170 11B3; # (쒨; 쒨; 쒨; 쒨; 쒨; ) HANGUL SYLLABLE SSWELS
+C4A9;C4A9;110A 1170 11B4;C4A9;110A 1170 11B4; # (쒩; 쒩; 쒩; 쒩; 쒩; ) HANGUL SYLLABLE SSWELT
+C4AA;C4AA;110A 1170 11B5;C4AA;110A 1170 11B5; # (쒪; 쒪; 쒪; 쒪; 쒪; ) HANGUL SYLLABLE SSWELP
+C4AB;C4AB;110A 1170 11B6;C4AB;110A 1170 11B6; # (쒫; 쒫; 쒫; 쒫; 쒫; ) HANGUL SYLLABLE SSWELH
+C4AC;C4AC;110A 1170 11B7;C4AC;110A 1170 11B7; # (쒬; 쒬; 쒬; 쒬; 쒬; ) HANGUL SYLLABLE SSWEM
+C4AD;C4AD;110A 1170 11B8;C4AD;110A 1170 11B8; # (쒭; 쒭; 쒭; 쒭; 쒭; ) HANGUL SYLLABLE SSWEB
+C4AE;C4AE;110A 1170 11B9;C4AE;110A 1170 11B9; # (쒮; 쒮; 쒮; 쒮; 쒮; ) HANGUL SYLLABLE SSWEBS
+C4AF;C4AF;110A 1170 11BA;C4AF;110A 1170 11BA; # (쒯; 쒯; 쒯; 쒯; 쒯; ) HANGUL SYLLABLE SSWES
+C4B0;C4B0;110A 1170 11BB;C4B0;110A 1170 11BB; # (쒰; 쒰; 쒰; 쒰; 쒰; ) HANGUL SYLLABLE SSWESS
+C4B1;C4B1;110A 1170 11BC;C4B1;110A 1170 11BC; # (쒱; 쒱; 쒱; 쒱; 쒱; ) HANGUL SYLLABLE SSWENG
+C4B2;C4B2;110A 1170 11BD;C4B2;110A 1170 11BD; # (쒲; 쒲; 쒲; 쒲; 쒲; ) HANGUL SYLLABLE SSWEJ
+C4B3;C4B3;110A 1170 11BE;C4B3;110A 1170 11BE; # (쒳; 쒳; 쒳; 쒳; 쒳; ) HANGUL SYLLABLE SSWEC
+C4B4;C4B4;110A 1170 11BF;C4B4;110A 1170 11BF; # (쒴; 쒴; 쒴; 쒴; 쒴; ) HANGUL SYLLABLE SSWEK
+C4B5;C4B5;110A 1170 11C0;C4B5;110A 1170 11C0; # (쒵; 쒵; 쒵; 쒵; 쒵; ) HANGUL SYLLABLE SSWET
+C4B6;C4B6;110A 1170 11C1;C4B6;110A 1170 11C1; # (ì’¶; ì’¶; á„Šá…°á‡; ì’¶; á„Šá…°á‡; ) HANGUL SYLLABLE SSWEP
+C4B7;C4B7;110A 1170 11C2;C4B7;110A 1170 11C2; # (쒷; 쒷; 쒷; 쒷; 쒷; ) HANGUL SYLLABLE SSWEH
+C4B8;C4B8;110A 1171;C4B8;110A 1171; # (ì’¸; ì’¸; á„Šá…±; ì’¸; á„Šá…±; ) HANGUL SYLLABLE SSWI
+C4B9;C4B9;110A 1171 11A8;C4B9;110A 1171 11A8; # (쒹; 쒹; 쒹; 쒹; 쒹; ) HANGUL SYLLABLE SSWIG
+C4BA;C4BA;110A 1171 11A9;C4BA;110A 1171 11A9; # (쒺; 쒺; 쒺; 쒺; 쒺; ) HANGUL SYLLABLE SSWIGG
+C4BB;C4BB;110A 1171 11AA;C4BB;110A 1171 11AA; # (쒻; 쒻; 쒻; 쒻; 쒻; ) HANGUL SYLLABLE SSWIGS
+C4BC;C4BC;110A 1171 11AB;C4BC;110A 1171 11AB; # (쒼; 쒼; 쒼; 쒼; 쒼; ) HANGUL SYLLABLE SSWIN
+C4BD;C4BD;110A 1171 11AC;C4BD;110A 1171 11AC; # (쒽; 쒽; 쒽; 쒽; 쒽; ) HANGUL SYLLABLE SSWINJ
+C4BE;C4BE;110A 1171 11AD;C4BE;110A 1171 11AD; # (쒾; 쒾; 쒾; 쒾; 쒾; ) HANGUL SYLLABLE SSWINH
+C4BF;C4BF;110A 1171 11AE;C4BF;110A 1171 11AE; # (쒿; 쒿; 쒿; 쒿; 쒿; ) HANGUL SYLLABLE SSWID
+C4C0;C4C0;110A 1171 11AF;C4C0;110A 1171 11AF; # (쓀; 쓀; 쓀; 쓀; 쓀; ) HANGUL SYLLABLE SSWIL
+C4C1;C4C1;110A 1171 11B0;C4C1;110A 1171 11B0; # (ì“; ì“; 쓁; ì“; 쓁; ) HANGUL SYLLABLE SSWILG
+C4C2;C4C2;110A 1171 11B1;C4C2;110A 1171 11B1; # (쓂; 쓂; 쓂; 쓂; 쓂; ) HANGUL SYLLABLE SSWILM
+C4C3;C4C3;110A 1171 11B2;C4C3;110A 1171 11B2; # (쓃; 쓃; 쓃; 쓃; 쓃; ) HANGUL SYLLABLE SSWILB
+C4C4;C4C4;110A 1171 11B3;C4C4;110A 1171 11B3; # (쓄; 쓄; 쓄; 쓄; 쓄; ) HANGUL SYLLABLE SSWILS
+C4C5;C4C5;110A 1171 11B4;C4C5;110A 1171 11B4; # (쓅; 쓅; 쓅; 쓅; 쓅; ) HANGUL SYLLABLE SSWILT
+C4C6;C4C6;110A 1171 11B5;C4C6;110A 1171 11B5; # (쓆; 쓆; 쓆; 쓆; 쓆; ) HANGUL SYLLABLE SSWILP
+C4C7;C4C7;110A 1171 11B6;C4C7;110A 1171 11B6; # (쓇; 쓇; 쓇; 쓇; 쓇; ) HANGUL SYLLABLE SSWILH
+C4C8;C4C8;110A 1171 11B7;C4C8;110A 1171 11B7; # (쓈; 쓈; 쓈; 쓈; 쓈; ) HANGUL SYLLABLE SSWIM
+C4C9;C4C9;110A 1171 11B8;C4C9;110A 1171 11B8; # (쓉; 쓉; 쓉; 쓉; 쓉; ) HANGUL SYLLABLE SSWIB
+C4CA;C4CA;110A 1171 11B9;C4CA;110A 1171 11B9; # (쓊; 쓊; 쓊; 쓊; 쓊; ) HANGUL SYLLABLE SSWIBS
+C4CB;C4CB;110A 1171 11BA;C4CB;110A 1171 11BA; # (쓋; 쓋; 쓋; 쓋; 쓋; ) HANGUL SYLLABLE SSWIS
+C4CC;C4CC;110A 1171 11BB;C4CC;110A 1171 11BB; # (쓌; 쓌; 쓌; 쓌; 쓌; ) HANGUL SYLLABLE SSWISS
+C4CD;C4CD;110A 1171 11BC;C4CD;110A 1171 11BC; # (ì“; ì“; 쓍; ì“; 쓍; ) HANGUL SYLLABLE SSWING
+C4CE;C4CE;110A 1171 11BD;C4CE;110A 1171 11BD; # (쓎; 쓎; 쓎; 쓎; 쓎; ) HANGUL SYLLABLE SSWIJ
+C4CF;C4CF;110A 1171 11BE;C4CF;110A 1171 11BE; # (ì“; ì“; 쓏; ì“; 쓏; ) HANGUL SYLLABLE SSWIC
+C4D0;C4D0;110A 1171 11BF;C4D0;110A 1171 11BF; # (ì“; ì“; 쓐; ì“; 쓐; ) HANGUL SYLLABLE SSWIK
+C4D1;C4D1;110A 1171 11C0;C4D1;110A 1171 11C0; # (쓑; 쓑; 쓑; 쓑; 쓑; ) HANGUL SYLLABLE SSWIT
+C4D2;C4D2;110A 1171 11C1;C4D2;110A 1171 11C1; # (ì“’; ì“’; á„Šá…±á‡; ì“’; á„Šá…±á‡; ) HANGUL SYLLABLE SSWIP
+C4D3;C4D3;110A 1171 11C2;C4D3;110A 1171 11C2; # (쓓; 쓓; 쓓; 쓓; 쓓; ) HANGUL SYLLABLE SSWIH
+C4D4;C4D4;110A 1172;C4D4;110A 1172; # (ì“”; ì“”; á„Šá…²; ì“”; á„Šá…²; ) HANGUL SYLLABLE SSYU
+C4D5;C4D5;110A 1172 11A8;C4D5;110A 1172 11A8; # (쓕; 쓕; 쓕; 쓕; 쓕; ) HANGUL SYLLABLE SSYUG
+C4D6;C4D6;110A 1172 11A9;C4D6;110A 1172 11A9; # (쓖; 쓖; 쓖; 쓖; 쓖; ) HANGUL SYLLABLE SSYUGG
+C4D7;C4D7;110A 1172 11AA;C4D7;110A 1172 11AA; # (쓗; 쓗; 쓗; 쓗; 쓗; ) HANGUL SYLLABLE SSYUGS
+C4D8;C4D8;110A 1172 11AB;C4D8;110A 1172 11AB; # (쓘; 쓘; 쓘; 쓘; 쓘; ) HANGUL SYLLABLE SSYUN
+C4D9;C4D9;110A 1172 11AC;C4D9;110A 1172 11AC; # (쓙; 쓙; 쓙; 쓙; 쓙; ) HANGUL SYLLABLE SSYUNJ
+C4DA;C4DA;110A 1172 11AD;C4DA;110A 1172 11AD; # (쓚; 쓚; 쓚; 쓚; 쓚; ) HANGUL SYLLABLE SSYUNH
+C4DB;C4DB;110A 1172 11AE;C4DB;110A 1172 11AE; # (쓛; 쓛; 쓛; 쓛; 쓛; ) HANGUL SYLLABLE SSYUD
+C4DC;C4DC;110A 1172 11AF;C4DC;110A 1172 11AF; # (쓜; 쓜; 쓜; 쓜; 쓜; ) HANGUL SYLLABLE SSYUL
+C4DD;C4DD;110A 1172 11B0;C4DD;110A 1172 11B0; # (ì“; ì“; 쓝; ì“; 쓝; ) HANGUL SYLLABLE SSYULG
+C4DE;C4DE;110A 1172 11B1;C4DE;110A 1172 11B1; # (쓞; 쓞; 쓞; 쓞; 쓞; ) HANGUL SYLLABLE SSYULM
+C4DF;C4DF;110A 1172 11B2;C4DF;110A 1172 11B2; # (쓟; 쓟; 쓟; 쓟; 쓟; ) HANGUL SYLLABLE SSYULB
+C4E0;C4E0;110A 1172 11B3;C4E0;110A 1172 11B3; # (쓠; 쓠; 쓠; 쓠; 쓠; ) HANGUL SYLLABLE SSYULS
+C4E1;C4E1;110A 1172 11B4;C4E1;110A 1172 11B4; # (쓡; 쓡; 쓡; 쓡; 쓡; ) HANGUL SYLLABLE SSYULT
+C4E2;C4E2;110A 1172 11B5;C4E2;110A 1172 11B5; # (쓢; 쓢; 쓢; 쓢; 쓢; ) HANGUL SYLLABLE SSYULP
+C4E3;C4E3;110A 1172 11B6;C4E3;110A 1172 11B6; # (쓣; 쓣; 쓣; 쓣; 쓣; ) HANGUL SYLLABLE SSYULH
+C4E4;C4E4;110A 1172 11B7;C4E4;110A 1172 11B7; # (쓤; 쓤; 쓤; 쓤; 쓤; ) HANGUL SYLLABLE SSYUM
+C4E5;C4E5;110A 1172 11B8;C4E5;110A 1172 11B8; # (쓥; 쓥; 쓥; 쓥; 쓥; ) HANGUL SYLLABLE SSYUB
+C4E6;C4E6;110A 1172 11B9;C4E6;110A 1172 11B9; # (쓦; 쓦; 쓦; 쓦; 쓦; ) HANGUL SYLLABLE SSYUBS
+C4E7;C4E7;110A 1172 11BA;C4E7;110A 1172 11BA; # (쓧; 쓧; 쓧; 쓧; 쓧; ) HANGUL SYLLABLE SSYUS
+C4E8;C4E8;110A 1172 11BB;C4E8;110A 1172 11BB; # (쓨; 쓨; 쓨; 쓨; 쓨; ) HANGUL SYLLABLE SSYUSS
+C4E9;C4E9;110A 1172 11BC;C4E9;110A 1172 11BC; # (쓩; 쓩; 쓩; 쓩; 쓩; ) HANGUL SYLLABLE SSYUNG
+C4EA;C4EA;110A 1172 11BD;C4EA;110A 1172 11BD; # (쓪; 쓪; 쓪; 쓪; 쓪; ) HANGUL SYLLABLE SSYUJ
+C4EB;C4EB;110A 1172 11BE;C4EB;110A 1172 11BE; # (쓫; 쓫; 쓫; 쓫; 쓫; ) HANGUL SYLLABLE SSYUC
+C4EC;C4EC;110A 1172 11BF;C4EC;110A 1172 11BF; # (쓬; 쓬; 쓬; 쓬; 쓬; ) HANGUL SYLLABLE SSYUK
+C4ED;C4ED;110A 1172 11C0;C4ED;110A 1172 11C0; # (쓭; 쓭; 쓭; 쓭; 쓭; ) HANGUL SYLLABLE SSYUT
+C4EE;C4EE;110A 1172 11C1;C4EE;110A 1172 11C1; # (ì“®; ì“®; á„Šá…²á‡; ì“®; á„Šá…²á‡; ) HANGUL SYLLABLE SSYUP
+C4EF;C4EF;110A 1172 11C2;C4EF;110A 1172 11C2; # (쓯; 쓯; 쓯; 쓯; 쓯; ) HANGUL SYLLABLE SSYUH
+C4F0;C4F0;110A 1173;C4F0;110A 1173; # (ì“°; ì“°; á„Šá…³; ì“°; á„Šá…³; ) HANGUL SYLLABLE SSEU
+C4F1;C4F1;110A 1173 11A8;C4F1;110A 1173 11A8; # (쓱; 쓱; 쓱; 쓱; 쓱; ) HANGUL SYLLABLE SSEUG
+C4F2;C4F2;110A 1173 11A9;C4F2;110A 1173 11A9; # (쓲; 쓲; 쓲; 쓲; 쓲; ) HANGUL SYLLABLE SSEUGG
+C4F3;C4F3;110A 1173 11AA;C4F3;110A 1173 11AA; # (쓳; 쓳; 쓳; 쓳; 쓳; ) HANGUL SYLLABLE SSEUGS
+C4F4;C4F4;110A 1173 11AB;C4F4;110A 1173 11AB; # (쓴; 쓴; 쓴; 쓴; 쓴; ) HANGUL SYLLABLE SSEUN
+C4F5;C4F5;110A 1173 11AC;C4F5;110A 1173 11AC; # (쓵; 쓵; 쓵; 쓵; 쓵; ) HANGUL SYLLABLE SSEUNJ
+C4F6;C4F6;110A 1173 11AD;C4F6;110A 1173 11AD; # (쓶; 쓶; 쓶; 쓶; 쓶; ) HANGUL SYLLABLE SSEUNH
+C4F7;C4F7;110A 1173 11AE;C4F7;110A 1173 11AE; # (쓷; 쓷; 쓷; 쓷; 쓷; ) HANGUL SYLLABLE SSEUD
+C4F8;C4F8;110A 1173 11AF;C4F8;110A 1173 11AF; # (쓸; 쓸; 쓸; 쓸; 쓸; ) HANGUL SYLLABLE SSEUL
+C4F9;C4F9;110A 1173 11B0;C4F9;110A 1173 11B0; # (쓹; 쓹; 쓹; 쓹; 쓹; ) HANGUL SYLLABLE SSEULG
+C4FA;C4FA;110A 1173 11B1;C4FA;110A 1173 11B1; # (쓺; 쓺; 쓺; 쓺; 쓺; ) HANGUL SYLLABLE SSEULM
+C4FB;C4FB;110A 1173 11B2;C4FB;110A 1173 11B2; # (쓻; 쓻; 쓻; 쓻; 쓻; ) HANGUL SYLLABLE SSEULB
+C4FC;C4FC;110A 1173 11B3;C4FC;110A 1173 11B3; # (쓼; 쓼; 쓼; 쓼; 쓼; ) HANGUL SYLLABLE SSEULS
+C4FD;C4FD;110A 1173 11B4;C4FD;110A 1173 11B4; # (쓽; 쓽; 쓽; 쓽; 쓽; ) HANGUL SYLLABLE SSEULT
+C4FE;C4FE;110A 1173 11B5;C4FE;110A 1173 11B5; # (쓾; 쓾; 쓾; 쓾; 쓾; ) HANGUL SYLLABLE SSEULP
+C4FF;C4FF;110A 1173 11B6;C4FF;110A 1173 11B6; # (쓿; 쓿; 쓿; 쓿; 쓿; ) HANGUL SYLLABLE SSEULH
+C500;C500;110A 1173 11B7;C500;110A 1173 11B7; # (씀; 씀; 씀; 씀; 씀; ) HANGUL SYLLABLE SSEUM
+C501;C501;110A 1173 11B8;C501;110A 1173 11B8; # (ì”; ì”; 씁; ì”; 씁; ) HANGUL SYLLABLE SSEUB
+C502;C502;110A 1173 11B9;C502;110A 1173 11B9; # (씂; 씂; 씂; 씂; 씂; ) HANGUL SYLLABLE SSEUBS
+C503;C503;110A 1173 11BA;C503;110A 1173 11BA; # (씃; 씃; 씃; 씃; 씃; ) HANGUL SYLLABLE SSEUS
+C504;C504;110A 1173 11BB;C504;110A 1173 11BB; # (씄; 씄; 씄; 씄; 씄; ) HANGUL SYLLABLE SSEUSS
+C505;C505;110A 1173 11BC;C505;110A 1173 11BC; # (씅; 씅; 씅; 씅; 씅; ) HANGUL SYLLABLE SSEUNG
+C506;C506;110A 1173 11BD;C506;110A 1173 11BD; # (씆; 씆; 씆; 씆; 씆; ) HANGUL SYLLABLE SSEUJ
+C507;C507;110A 1173 11BE;C507;110A 1173 11BE; # (씇; 씇; 씇; 씇; 씇; ) HANGUL SYLLABLE SSEUC
+C508;C508;110A 1173 11BF;C508;110A 1173 11BF; # (씈; 씈; 씈; 씈; 씈; ) HANGUL SYLLABLE SSEUK
+C509;C509;110A 1173 11C0;C509;110A 1173 11C0; # (씉; 씉; 씉; 씉; 씉; ) HANGUL SYLLABLE SSEUT
+C50A;C50A;110A 1173 11C1;C50A;110A 1173 11C1; # (씊; 씊; á„Šá…³á‡; 씊; á„Šá…³á‡; ) HANGUL SYLLABLE SSEUP
+C50B;C50B;110A 1173 11C2;C50B;110A 1173 11C2; # (씋; 씋; 씋; 씋; 씋; ) HANGUL SYLLABLE SSEUH
+C50C;C50C;110A 1174;C50C;110A 1174; # (씌; 씌; 씌; 씌; 씌; ) HANGUL SYLLABLE SSYI
+C50D;C50D;110A 1174 11A8;C50D;110A 1174 11A8; # (ì”; ì”; 씍; ì”; 씍; ) HANGUL SYLLABLE SSYIG
+C50E;C50E;110A 1174 11A9;C50E;110A 1174 11A9; # (씎; 씎; 씎; 씎; 씎; ) HANGUL SYLLABLE SSYIGG
+C50F;C50F;110A 1174 11AA;C50F;110A 1174 11AA; # (ì”; ì”; 씏; ì”; 씏; ) HANGUL SYLLABLE SSYIGS
+C510;C510;110A 1174 11AB;C510;110A 1174 11AB; # (ì”; ì”; 씐; ì”; 씐; ) HANGUL SYLLABLE SSYIN
+C511;C511;110A 1174 11AC;C511;110A 1174 11AC; # (씑; 씑; 씑; 씑; 씑; ) HANGUL SYLLABLE SSYINJ
+C512;C512;110A 1174 11AD;C512;110A 1174 11AD; # (씒; 씒; 씒; 씒; 씒; ) HANGUL SYLLABLE SSYINH
+C513;C513;110A 1174 11AE;C513;110A 1174 11AE; # (씓; 씓; 씓; 씓; 씓; ) HANGUL SYLLABLE SSYID
+C514;C514;110A 1174 11AF;C514;110A 1174 11AF; # (씔; 씔; 씔; 씔; 씔; ) HANGUL SYLLABLE SSYIL
+C515;C515;110A 1174 11B0;C515;110A 1174 11B0; # (씕; 씕; 씕; 씕; 씕; ) HANGUL SYLLABLE SSYILG
+C516;C516;110A 1174 11B1;C516;110A 1174 11B1; # (씖; 씖; 씖; 씖; 씖; ) HANGUL SYLLABLE SSYILM
+C517;C517;110A 1174 11B2;C517;110A 1174 11B2; # (씗; 씗; 씗; 씗; 씗; ) HANGUL SYLLABLE SSYILB
+C518;C518;110A 1174 11B3;C518;110A 1174 11B3; # (씘; 씘; 씘; 씘; 씘; ) HANGUL SYLLABLE SSYILS
+C519;C519;110A 1174 11B4;C519;110A 1174 11B4; # (씙; 씙; 씙; 씙; 씙; ) HANGUL SYLLABLE SSYILT
+C51A;C51A;110A 1174 11B5;C51A;110A 1174 11B5; # (씚; 씚; 씚; 씚; 씚; ) HANGUL SYLLABLE SSYILP
+C51B;C51B;110A 1174 11B6;C51B;110A 1174 11B6; # (씛; 씛; 씛; 씛; 씛; ) HANGUL SYLLABLE SSYILH
+C51C;C51C;110A 1174 11B7;C51C;110A 1174 11B7; # (씜; 씜; 씜; 씜; 씜; ) HANGUL SYLLABLE SSYIM
+C51D;C51D;110A 1174 11B8;C51D;110A 1174 11B8; # (ì”; ì”; 씝; ì”; 씝; ) HANGUL SYLLABLE SSYIB
+C51E;C51E;110A 1174 11B9;C51E;110A 1174 11B9; # (씞; 씞; 씞; 씞; 씞; ) HANGUL SYLLABLE SSYIBS
+C51F;C51F;110A 1174 11BA;C51F;110A 1174 11BA; # (씟; 씟; 씟; 씟; 씟; ) HANGUL SYLLABLE SSYIS
+C520;C520;110A 1174 11BB;C520;110A 1174 11BB; # (씠; 씠; 씠; 씠; 씠; ) HANGUL SYLLABLE SSYISS
+C521;C521;110A 1174 11BC;C521;110A 1174 11BC; # (씡; 씡; 씡; 씡; 씡; ) HANGUL SYLLABLE SSYING
+C522;C522;110A 1174 11BD;C522;110A 1174 11BD; # (씢; 씢; 씢; 씢; 씢; ) HANGUL SYLLABLE SSYIJ
+C523;C523;110A 1174 11BE;C523;110A 1174 11BE; # (씣; 씣; 씣; 씣; 씣; ) HANGUL SYLLABLE SSYIC
+C524;C524;110A 1174 11BF;C524;110A 1174 11BF; # (씤; 씤; 씤; 씤; 씤; ) HANGUL SYLLABLE SSYIK
+C525;C525;110A 1174 11C0;C525;110A 1174 11C0; # (씥; 씥; 씥; 씥; 씥; ) HANGUL SYLLABLE SSYIT
+C526;C526;110A 1174 11C1;C526;110A 1174 11C1; # (씦; 씦; á„Šá…´á‡; 씦; á„Šá…´á‡; ) HANGUL SYLLABLE SSYIP
+C527;C527;110A 1174 11C2;C527;110A 1174 11C2; # (씧; 씧; 씧; 씧; 씧; ) HANGUL SYLLABLE SSYIH
+C528;C528;110A 1175;C528;110A 1175; # (씨; 씨; 씨; 씨; 씨; ) HANGUL SYLLABLE SSI
+C529;C529;110A 1175 11A8;C529;110A 1175 11A8; # (씩; 씩; 씩; 씩; 씩; ) HANGUL SYLLABLE SSIG
+C52A;C52A;110A 1175 11A9;C52A;110A 1175 11A9; # (씪; 씪; 씪; 씪; 씪; ) HANGUL SYLLABLE SSIGG
+C52B;C52B;110A 1175 11AA;C52B;110A 1175 11AA; # (씫; 씫; 씫; 씫; 씫; ) HANGUL SYLLABLE SSIGS
+C52C;C52C;110A 1175 11AB;C52C;110A 1175 11AB; # (씬; 씬; 씬; 씬; 씬; ) HANGUL SYLLABLE SSIN
+C52D;C52D;110A 1175 11AC;C52D;110A 1175 11AC; # (씭; 씭; 씭; 씭; 씭; ) HANGUL SYLLABLE SSINJ
+C52E;C52E;110A 1175 11AD;C52E;110A 1175 11AD; # (씮; 씮; 씮; 씮; 씮; ) HANGUL SYLLABLE SSINH
+C52F;C52F;110A 1175 11AE;C52F;110A 1175 11AE; # (씯; 씯; 씯; 씯; 씯; ) HANGUL SYLLABLE SSID
+C530;C530;110A 1175 11AF;C530;110A 1175 11AF; # (씰; 씰; 씰; 씰; 씰; ) HANGUL SYLLABLE SSIL
+C531;C531;110A 1175 11B0;C531;110A 1175 11B0; # (씱; 씱; 씱; 씱; 씱; ) HANGUL SYLLABLE SSILG
+C532;C532;110A 1175 11B1;C532;110A 1175 11B1; # (씲; 씲; 씲; 씲; 씲; ) HANGUL SYLLABLE SSILM
+C533;C533;110A 1175 11B2;C533;110A 1175 11B2; # (씳; 씳; 씳; 씳; 씳; ) HANGUL SYLLABLE SSILB
+C534;C534;110A 1175 11B3;C534;110A 1175 11B3; # (씴; 씴; 씴; 씴; 씴; ) HANGUL SYLLABLE SSILS
+C535;C535;110A 1175 11B4;C535;110A 1175 11B4; # (씵; 씵; 씵; 씵; 씵; ) HANGUL SYLLABLE SSILT
+C536;C536;110A 1175 11B5;C536;110A 1175 11B5; # (씶; 씶; 씶; 씶; 씶; ) HANGUL SYLLABLE SSILP
+C537;C537;110A 1175 11B6;C537;110A 1175 11B6; # (씷; 씷; 씷; 씷; 씷; ) HANGUL SYLLABLE SSILH
+C538;C538;110A 1175 11B7;C538;110A 1175 11B7; # (씸; 씸; 씸; 씸; 씸; ) HANGUL SYLLABLE SSIM
+C539;C539;110A 1175 11B8;C539;110A 1175 11B8; # (씹; 씹; 씹; 씹; 씹; ) HANGUL SYLLABLE SSIB
+C53A;C53A;110A 1175 11B9;C53A;110A 1175 11B9; # (씺; 씺; 씺; 씺; 씺; ) HANGUL SYLLABLE SSIBS
+C53B;C53B;110A 1175 11BA;C53B;110A 1175 11BA; # (씻; 씻; 씻; 씻; 씻; ) HANGUL SYLLABLE SSIS
+C53C;C53C;110A 1175 11BB;C53C;110A 1175 11BB; # (씼; 씼; 씼; 씼; 씼; ) HANGUL SYLLABLE SSISS
+C53D;C53D;110A 1175 11BC;C53D;110A 1175 11BC; # (씽; 씽; 씽; 씽; 씽; ) HANGUL SYLLABLE SSING
+C53E;C53E;110A 1175 11BD;C53E;110A 1175 11BD; # (씾; 씾; 씾; 씾; 씾; ) HANGUL SYLLABLE SSIJ
+C53F;C53F;110A 1175 11BE;C53F;110A 1175 11BE; # (씿; 씿; 씿; 씿; 씿; ) HANGUL SYLLABLE SSIC
+C540;C540;110A 1175 11BF;C540;110A 1175 11BF; # (앀; 앀; 앀; 앀; 앀; ) HANGUL SYLLABLE SSIK
+C541;C541;110A 1175 11C0;C541;110A 1175 11C0; # (ì•; ì•; 앁; ì•; 앁; ) HANGUL SYLLABLE SSIT
+C542;C542;110A 1175 11C1;C542;110A 1175 11C1; # (ì•‚; ì•‚; á„Šá…µá‡; ì•‚; á„Šá…µá‡; ) HANGUL SYLLABLE SSIP
+C543;C543;110A 1175 11C2;C543;110A 1175 11C2; # (앃; 앃; 앃; 앃; 앃; ) HANGUL SYLLABLE SSIH
+C544;C544;110B 1161;C544;110B 1161; # (ì•„; ì•„; á„‹á…¡; ì•„; á„‹á…¡; ) HANGUL SYLLABLE A
+C545;C545;110B 1161 11A8;C545;110B 1161 11A8; # (악; 악; 악; 악; 악; ) HANGUL SYLLABLE AG
+C546;C546;110B 1161 11A9;C546;110B 1161 11A9; # (앆; 앆; 앆; 앆; 앆; ) HANGUL SYLLABLE AGG
+C547;C547;110B 1161 11AA;C547;110B 1161 11AA; # (앇; 앇; 앇; 앇; 앇; ) HANGUL SYLLABLE AGS
+C548;C548;110B 1161 11AB;C548;110B 1161 11AB; # (안; 안; 안; 안; 안; ) HANGUL SYLLABLE AN
+C549;C549;110B 1161 11AC;C549;110B 1161 11AC; # (앉; 앉; 앉; 앉; 앉; ) HANGUL SYLLABLE ANJ
+C54A;C54A;110B 1161 11AD;C54A;110B 1161 11AD; # (않; 않; 않; 않; 않; ) HANGUL SYLLABLE ANH
+C54B;C54B;110B 1161 11AE;C54B;110B 1161 11AE; # (앋; 앋; 앋; 앋; 앋; ) HANGUL SYLLABLE AD
+C54C;C54C;110B 1161 11AF;C54C;110B 1161 11AF; # (알; 알; 알; 알; 알; ) HANGUL SYLLABLE AL
+C54D;C54D;110B 1161 11B0;C54D;110B 1161 11B0; # (ì•; ì•; 앍; ì•; 앍; ) HANGUL SYLLABLE ALG
+C54E;C54E;110B 1161 11B1;C54E;110B 1161 11B1; # (앎; 앎; 앎; 앎; 앎; ) HANGUL SYLLABLE ALM
+C54F;C54F;110B 1161 11B2;C54F;110B 1161 11B2; # (ì•; ì•; 앏; ì•; 앏; ) HANGUL SYLLABLE ALB
+C550;C550;110B 1161 11B3;C550;110B 1161 11B3; # (ì•; ì•; 앐; ì•; 앐; ) HANGUL SYLLABLE ALS
+C551;C551;110B 1161 11B4;C551;110B 1161 11B4; # (앑; 앑; 앑; 앑; 앑; ) HANGUL SYLLABLE ALT
+C552;C552;110B 1161 11B5;C552;110B 1161 11B5; # (앒; 앒; 앒; 앒; 앒; ) HANGUL SYLLABLE ALP
+C553;C553;110B 1161 11B6;C553;110B 1161 11B6; # (앓; 앓; 앓; 앓; 앓; ) HANGUL SYLLABLE ALH
+C554;C554;110B 1161 11B7;C554;110B 1161 11B7; # (암; 암; 암; 암; 암; ) HANGUL SYLLABLE AM
+C555;C555;110B 1161 11B8;C555;110B 1161 11B8; # (압; 압; 압; 압; 압; ) HANGUL SYLLABLE AB
+C556;C556;110B 1161 11B9;C556;110B 1161 11B9; # (앖; 앖; 앖; 앖; 앖; ) HANGUL SYLLABLE ABS
+C557;C557;110B 1161 11BA;C557;110B 1161 11BA; # (앗; 앗; 앗; 앗; 앗; ) HANGUL SYLLABLE AS
+C558;C558;110B 1161 11BB;C558;110B 1161 11BB; # (았; 았; 았; 았; 았; ) HANGUL SYLLABLE ASS
+C559;C559;110B 1161 11BC;C559;110B 1161 11BC; # (앙; 앙; 앙; 앙; 앙; ) HANGUL SYLLABLE ANG
+C55A;C55A;110B 1161 11BD;C55A;110B 1161 11BD; # (앚; 앚; 앚; 앚; 앚; ) HANGUL SYLLABLE AJ
+C55B;C55B;110B 1161 11BE;C55B;110B 1161 11BE; # (앛; 앛; 앛; 앛; 앛; ) HANGUL SYLLABLE AC
+C55C;C55C;110B 1161 11BF;C55C;110B 1161 11BF; # (앜; 앜; 앜; 앜; 앜; ) HANGUL SYLLABLE AK
+C55D;C55D;110B 1161 11C0;C55D;110B 1161 11C0; # (ì•; ì•; 앝; ì•; 앝; ) HANGUL SYLLABLE AT
+C55E;C55E;110B 1161 11C1;C55E;110B 1161 11C1; # (ì•ž; ì•ž; á„‹á…¡á‡; ì•ž; á„‹á…¡á‡; ) HANGUL SYLLABLE AP
+C55F;C55F;110B 1161 11C2;C55F;110B 1161 11C2; # (앟; 앟; 앟; 앟; 앟; ) HANGUL SYLLABLE AH
+C560;C560;110B 1162;C560;110B 1162; # (ì• ; ì• ; á„‹á…¢; ì• ; á„‹á…¢; ) HANGUL SYLLABLE AE
+C561;C561;110B 1162 11A8;C561;110B 1162 11A8; # (액; 액; 액; 액; 액; ) HANGUL SYLLABLE AEG
+C562;C562;110B 1162 11A9;C562;110B 1162 11A9; # (앢; 앢; 앢; 앢; 앢; ) HANGUL SYLLABLE AEGG
+C563;C563;110B 1162 11AA;C563;110B 1162 11AA; # (앣; 앣; 앣; 앣; 앣; ) HANGUL SYLLABLE AEGS
+C564;C564;110B 1162 11AB;C564;110B 1162 11AB; # (앤; 앤; 앤; 앤; 앤; ) HANGUL SYLLABLE AEN
+C565;C565;110B 1162 11AC;C565;110B 1162 11AC; # (앥; 앥; 앥; 앥; 앥; ) HANGUL SYLLABLE AENJ
+C566;C566;110B 1162 11AD;C566;110B 1162 11AD; # (앦; 앦; 앦; 앦; 앦; ) HANGUL SYLLABLE AENH
+C567;C567;110B 1162 11AE;C567;110B 1162 11AE; # (앧; 앧; 앧; 앧; 앧; ) HANGUL SYLLABLE AED
+C568;C568;110B 1162 11AF;C568;110B 1162 11AF; # (앨; 앨; 앨; 앨; 앨; ) HANGUL SYLLABLE AEL
+C569;C569;110B 1162 11B0;C569;110B 1162 11B0; # (앩; 앩; 앩; 앩; 앩; ) HANGUL SYLLABLE AELG
+C56A;C56A;110B 1162 11B1;C56A;110B 1162 11B1; # (앪; 앪; 앪; 앪; 앪; ) HANGUL SYLLABLE AELM
+C56B;C56B;110B 1162 11B2;C56B;110B 1162 11B2; # (앫; 앫; 앫; 앫; 앫; ) HANGUL SYLLABLE AELB
+C56C;C56C;110B 1162 11B3;C56C;110B 1162 11B3; # (앬; 앬; 앬; 앬; 앬; ) HANGUL SYLLABLE AELS
+C56D;C56D;110B 1162 11B4;C56D;110B 1162 11B4; # (앭; 앭; 앭; 앭; 앭; ) HANGUL SYLLABLE AELT
+C56E;C56E;110B 1162 11B5;C56E;110B 1162 11B5; # (앮; 앮; 앮; 앮; 앮; ) HANGUL SYLLABLE AELP
+C56F;C56F;110B 1162 11B6;C56F;110B 1162 11B6; # (앯; 앯; 앯; 앯; 앯; ) HANGUL SYLLABLE AELH
+C570;C570;110B 1162 11B7;C570;110B 1162 11B7; # (앰; 앰; 앰; 앰; 앰; ) HANGUL SYLLABLE AEM
+C571;C571;110B 1162 11B8;C571;110B 1162 11B8; # (앱; 앱; 앱; 앱; 앱; ) HANGUL SYLLABLE AEB
+C572;C572;110B 1162 11B9;C572;110B 1162 11B9; # (앲; 앲; 앲; 앲; 앲; ) HANGUL SYLLABLE AEBS
+C573;C573;110B 1162 11BA;C573;110B 1162 11BA; # (앳; 앳; 앳; 앳; 앳; ) HANGUL SYLLABLE AES
+C574;C574;110B 1162 11BB;C574;110B 1162 11BB; # (앴; 앴; 앴; 앴; 앴; ) HANGUL SYLLABLE AESS
+C575;C575;110B 1162 11BC;C575;110B 1162 11BC; # (앵; 앵; 앵; 앵; 앵; ) HANGUL SYLLABLE AENG
+C576;C576;110B 1162 11BD;C576;110B 1162 11BD; # (앶; 앶; 앶; 앶; 앶; ) HANGUL SYLLABLE AEJ
+C577;C577;110B 1162 11BE;C577;110B 1162 11BE; # (앷; 앷; 앷; 앷; 앷; ) HANGUL SYLLABLE AEC
+C578;C578;110B 1162 11BF;C578;110B 1162 11BF; # (앸; 앸; 앸; 앸; 앸; ) HANGUL SYLLABLE AEK
+C579;C579;110B 1162 11C0;C579;110B 1162 11C0; # (앹; 앹; 앹; 앹; 앹; ) HANGUL SYLLABLE AET
+C57A;C57A;110B 1162 11C1;C57A;110B 1162 11C1; # (앺; 앺; á„‹á…¢á‡; 앺; á„‹á…¢á‡; ) HANGUL SYLLABLE AEP
+C57B;C57B;110B 1162 11C2;C57B;110B 1162 11C2; # (앻; 앻; 앻; 앻; 앻; ) HANGUL SYLLABLE AEH
+C57C;C57C;110B 1163;C57C;110B 1163; # (야; 야; 야; 야; 야; ) HANGUL SYLLABLE YA
+C57D;C57D;110B 1163 11A8;C57D;110B 1163 11A8; # (약; 약; 약; 약; 약; ) HANGUL SYLLABLE YAG
+C57E;C57E;110B 1163 11A9;C57E;110B 1163 11A9; # (앾; 앾; 앾; 앾; 앾; ) HANGUL SYLLABLE YAGG
+C57F;C57F;110B 1163 11AA;C57F;110B 1163 11AA; # (앿; 앿; 앿; 앿; 앿; ) HANGUL SYLLABLE YAGS
+C580;C580;110B 1163 11AB;C580;110B 1163 11AB; # (얀; 얀; 얀; 얀; 얀; ) HANGUL SYLLABLE YAN
+C581;C581;110B 1163 11AC;C581;110B 1163 11AC; # (ì–; ì–; 얁; ì–; 얁; ) HANGUL SYLLABLE YANJ
+C582;C582;110B 1163 11AD;C582;110B 1163 11AD; # (얂; 얂; 얂; 얂; 얂; ) HANGUL SYLLABLE YANH
+C583;C583;110B 1163 11AE;C583;110B 1163 11AE; # (얃; 얃; 얃; 얃; 얃; ) HANGUL SYLLABLE YAD
+C584;C584;110B 1163 11AF;C584;110B 1163 11AF; # (얄; 얄; 얄; 얄; 얄; ) HANGUL SYLLABLE YAL
+C585;C585;110B 1163 11B0;C585;110B 1163 11B0; # (얅; 얅; 얅; 얅; 얅; ) HANGUL SYLLABLE YALG
+C586;C586;110B 1163 11B1;C586;110B 1163 11B1; # (얆; 얆; 얆; 얆; 얆; ) HANGUL SYLLABLE YALM
+C587;C587;110B 1163 11B2;C587;110B 1163 11B2; # (얇; 얇; 얇; 얇; 얇; ) HANGUL SYLLABLE YALB
+C588;C588;110B 1163 11B3;C588;110B 1163 11B3; # (얈; 얈; 얈; 얈; 얈; ) HANGUL SYLLABLE YALS
+C589;C589;110B 1163 11B4;C589;110B 1163 11B4; # (얉; 얉; 얉; 얉; 얉; ) HANGUL SYLLABLE YALT
+C58A;C58A;110B 1163 11B5;C58A;110B 1163 11B5; # (얊; 얊; 얊; 얊; 얊; ) HANGUL SYLLABLE YALP
+C58B;C58B;110B 1163 11B6;C58B;110B 1163 11B6; # (얋; 얋; 얋; 얋; 얋; ) HANGUL SYLLABLE YALH
+C58C;C58C;110B 1163 11B7;C58C;110B 1163 11B7; # (얌; 얌; 얌; 얌; 얌; ) HANGUL SYLLABLE YAM
+C58D;C58D;110B 1163 11B8;C58D;110B 1163 11B8; # (ì–; ì–; 얍; ì–; 얍; ) HANGUL SYLLABLE YAB
+C58E;C58E;110B 1163 11B9;C58E;110B 1163 11B9; # (얎; 얎; 얎; 얎; 얎; ) HANGUL SYLLABLE YABS
+C58F;C58F;110B 1163 11BA;C58F;110B 1163 11BA; # (ì–; ì–; 얏; ì–; 얏; ) HANGUL SYLLABLE YAS
+C590;C590;110B 1163 11BB;C590;110B 1163 11BB; # (ì–; ì–; 얐; ì–; 얐; ) HANGUL SYLLABLE YASS
+C591;C591;110B 1163 11BC;C591;110B 1163 11BC; # (양; 양; 양; 양; 양; ) HANGUL SYLLABLE YANG
+C592;C592;110B 1163 11BD;C592;110B 1163 11BD; # (얒; 얒; 얒; 얒; 얒; ) HANGUL SYLLABLE YAJ
+C593;C593;110B 1163 11BE;C593;110B 1163 11BE; # (얓; 얓; 얓; 얓; 얓; ) HANGUL SYLLABLE YAC
+C594;C594;110B 1163 11BF;C594;110B 1163 11BF; # (얔; 얔; 얔; 얔; 얔; ) HANGUL SYLLABLE YAK
+C595;C595;110B 1163 11C0;C595;110B 1163 11C0; # (얕; 얕; 얕; 얕; 얕; ) HANGUL SYLLABLE YAT
+C596;C596;110B 1163 11C1;C596;110B 1163 11C1; # (ì––; ì––; á„‹á…£á‡; ì––; á„‹á…£á‡; ) HANGUL SYLLABLE YAP
+C597;C597;110B 1163 11C2;C597;110B 1163 11C2; # (얗; 얗; 얗; 얗; 얗; ) HANGUL SYLLABLE YAH
+C598;C598;110B 1164;C598;110B 1164; # (ì–˜; ì–˜; á„‹á…¤; ì–˜; á„‹á…¤; ) HANGUL SYLLABLE YAE
+C599;C599;110B 1164 11A8;C599;110B 1164 11A8; # (얙; 얙; 얙; 얙; 얙; ) HANGUL SYLLABLE YAEG
+C59A;C59A;110B 1164 11A9;C59A;110B 1164 11A9; # (얚; 얚; 얚; 얚; 얚; ) HANGUL SYLLABLE YAEGG
+C59B;C59B;110B 1164 11AA;C59B;110B 1164 11AA; # (얛; 얛; 얛; 얛; 얛; ) HANGUL SYLLABLE YAEGS
+C59C;C59C;110B 1164 11AB;C59C;110B 1164 11AB; # (얜; 얜; 얜; 얜; 얜; ) HANGUL SYLLABLE YAEN
+C59D;C59D;110B 1164 11AC;C59D;110B 1164 11AC; # (ì–; ì–; 얝; ì–; 얝; ) HANGUL SYLLABLE YAENJ
+C59E;C59E;110B 1164 11AD;C59E;110B 1164 11AD; # (얞; 얞; 얞; 얞; 얞; ) HANGUL SYLLABLE YAENH
+C59F;C59F;110B 1164 11AE;C59F;110B 1164 11AE; # (얟; 얟; 얟; 얟; 얟; ) HANGUL SYLLABLE YAED
+C5A0;C5A0;110B 1164 11AF;C5A0;110B 1164 11AF; # (얠; 얠; 얠; 얠; 얠; ) HANGUL SYLLABLE YAEL
+C5A1;C5A1;110B 1164 11B0;C5A1;110B 1164 11B0; # (얡; 얡; 얡; 얡; 얡; ) HANGUL SYLLABLE YAELG
+C5A2;C5A2;110B 1164 11B1;C5A2;110B 1164 11B1; # (얢; 얢; 얢; 얢; 얢; ) HANGUL SYLLABLE YAELM
+C5A3;C5A3;110B 1164 11B2;C5A3;110B 1164 11B2; # (얣; 얣; 얣; 얣; 얣; ) HANGUL SYLLABLE YAELB
+C5A4;C5A4;110B 1164 11B3;C5A4;110B 1164 11B3; # (얤; 얤; 얤; 얤; 얤; ) HANGUL SYLLABLE YAELS
+C5A5;C5A5;110B 1164 11B4;C5A5;110B 1164 11B4; # (얥; 얥; 얥; 얥; 얥; ) HANGUL SYLLABLE YAELT
+C5A6;C5A6;110B 1164 11B5;C5A6;110B 1164 11B5; # (얦; 얦; 얦; 얦; 얦; ) HANGUL SYLLABLE YAELP
+C5A7;C5A7;110B 1164 11B6;C5A7;110B 1164 11B6; # (얧; 얧; 얧; 얧; 얧; ) HANGUL SYLLABLE YAELH
+C5A8;C5A8;110B 1164 11B7;C5A8;110B 1164 11B7; # (얨; 얨; 얨; 얨; 얨; ) HANGUL SYLLABLE YAEM
+C5A9;C5A9;110B 1164 11B8;C5A9;110B 1164 11B8; # (얩; 얩; 얩; 얩; 얩; ) HANGUL SYLLABLE YAEB
+C5AA;C5AA;110B 1164 11B9;C5AA;110B 1164 11B9; # (얪; 얪; 얪; 얪; 얪; ) HANGUL SYLLABLE YAEBS
+C5AB;C5AB;110B 1164 11BA;C5AB;110B 1164 11BA; # (얫; 얫; 얫; 얫; 얫; ) HANGUL SYLLABLE YAES
+C5AC;C5AC;110B 1164 11BB;C5AC;110B 1164 11BB; # (얬; 얬; 얬; 얬; 얬; ) HANGUL SYLLABLE YAESS
+C5AD;C5AD;110B 1164 11BC;C5AD;110B 1164 11BC; # (얭; 얭; 얭; 얭; 얭; ) HANGUL SYLLABLE YAENG
+C5AE;C5AE;110B 1164 11BD;C5AE;110B 1164 11BD; # (얮; 얮; 얮; 얮; 얮; ) HANGUL SYLLABLE YAEJ
+C5AF;C5AF;110B 1164 11BE;C5AF;110B 1164 11BE; # (얯; 얯; 얯; 얯; 얯; ) HANGUL SYLLABLE YAEC
+C5B0;C5B0;110B 1164 11BF;C5B0;110B 1164 11BF; # (얰; 얰; 얰; 얰; 얰; ) HANGUL SYLLABLE YAEK
+C5B1;C5B1;110B 1164 11C0;C5B1;110B 1164 11C0; # (얱; 얱; 얱; 얱; 얱; ) HANGUL SYLLABLE YAET
+C5B2;C5B2;110B 1164 11C1;C5B2;110B 1164 11C1; # (ì–²; ì–²; á„‹á…¤á‡; ì–²; á„‹á…¤á‡; ) HANGUL SYLLABLE YAEP
+C5B3;C5B3;110B 1164 11C2;C5B3;110B 1164 11C2; # (얳; 얳; 얳; 얳; 얳; ) HANGUL SYLLABLE YAEH
+C5B4;C5B4;110B 1165;C5B4;110B 1165; # (ì–´; ì–´; á„‹á…¥; ì–´; á„‹á…¥; ) HANGUL SYLLABLE EO
+C5B5;C5B5;110B 1165 11A8;C5B5;110B 1165 11A8; # (억; 억; 억; 억; 억; ) HANGUL SYLLABLE EOG
+C5B6;C5B6;110B 1165 11A9;C5B6;110B 1165 11A9; # (얶; 얶; 얶; 얶; 얶; ) HANGUL SYLLABLE EOGG
+C5B7;C5B7;110B 1165 11AA;C5B7;110B 1165 11AA; # (얷; 얷; 얷; 얷; 얷; ) HANGUL SYLLABLE EOGS
+C5B8;C5B8;110B 1165 11AB;C5B8;110B 1165 11AB; # (언; 언; 언; 언; 언; ) HANGUL SYLLABLE EON
+C5B9;C5B9;110B 1165 11AC;C5B9;110B 1165 11AC; # (얹; 얹; 얹; 얹; 얹; ) HANGUL SYLLABLE EONJ
+C5BA;C5BA;110B 1165 11AD;C5BA;110B 1165 11AD; # (얺; 얺; 얺; 얺; 얺; ) HANGUL SYLLABLE EONH
+C5BB;C5BB;110B 1165 11AE;C5BB;110B 1165 11AE; # (얻; 얻; 얻; 얻; 얻; ) HANGUL SYLLABLE EOD
+C5BC;C5BC;110B 1165 11AF;C5BC;110B 1165 11AF; # (얼; 얼; 얼; 얼; 얼; ) HANGUL SYLLABLE EOL
+C5BD;C5BD;110B 1165 11B0;C5BD;110B 1165 11B0; # (얽; 얽; 얽; 얽; 얽; ) HANGUL SYLLABLE EOLG
+C5BE;C5BE;110B 1165 11B1;C5BE;110B 1165 11B1; # (얾; 얾; 얾; 얾; 얾; ) HANGUL SYLLABLE EOLM
+C5BF;C5BF;110B 1165 11B2;C5BF;110B 1165 11B2; # (얿; 얿; 얿; 얿; 얿; ) HANGUL SYLLABLE EOLB
+C5C0;C5C0;110B 1165 11B3;C5C0;110B 1165 11B3; # (엀; 엀; 엀; 엀; 엀; ) HANGUL SYLLABLE EOLS
+C5C1;C5C1;110B 1165 11B4;C5C1;110B 1165 11B4; # (ì—; ì—; 엁; ì—; 엁; ) HANGUL SYLLABLE EOLT
+C5C2;C5C2;110B 1165 11B5;C5C2;110B 1165 11B5; # (엂; 엂; 엂; 엂; 엂; ) HANGUL SYLLABLE EOLP
+C5C3;C5C3;110B 1165 11B6;C5C3;110B 1165 11B6; # (엃; 엃; 엃; 엃; 엃; ) HANGUL SYLLABLE EOLH
+C5C4;C5C4;110B 1165 11B7;C5C4;110B 1165 11B7; # (엄; 엄; 엄; 엄; 엄; ) HANGUL SYLLABLE EOM
+C5C5;C5C5;110B 1165 11B8;C5C5;110B 1165 11B8; # (업; 업; 업; 업; 업; ) HANGUL SYLLABLE EOB
+C5C6;C5C6;110B 1165 11B9;C5C6;110B 1165 11B9; # (없; 없; 없; 없; 없; ) HANGUL SYLLABLE EOBS
+C5C7;C5C7;110B 1165 11BA;C5C7;110B 1165 11BA; # (엇; 엇; 엇; 엇; 엇; ) HANGUL SYLLABLE EOS
+C5C8;C5C8;110B 1165 11BB;C5C8;110B 1165 11BB; # (었; 었; 었; 었; 었; ) HANGUL SYLLABLE EOSS
+C5C9;C5C9;110B 1165 11BC;C5C9;110B 1165 11BC; # (엉; 엉; 엉; 엉; 엉; ) HANGUL SYLLABLE EONG
+C5CA;C5CA;110B 1165 11BD;C5CA;110B 1165 11BD; # (엊; 엊; 엊; 엊; 엊; ) HANGUL SYLLABLE EOJ
+C5CB;C5CB;110B 1165 11BE;C5CB;110B 1165 11BE; # (엋; 엋; 엋; 엋; 엋; ) HANGUL SYLLABLE EOC
+C5CC;C5CC;110B 1165 11BF;C5CC;110B 1165 11BF; # (엌; 엌; 엌; 엌; 엌; ) HANGUL SYLLABLE EOK
+C5CD;C5CD;110B 1165 11C0;C5CD;110B 1165 11C0; # (ì—; ì—; 엍; ì—; 엍; ) HANGUL SYLLABLE EOT
+C5CE;C5CE;110B 1165 11C1;C5CE;110B 1165 11C1; # (ì—Ž; ì—Ž; á„‹á…¥á‡; ì—Ž; á„‹á…¥á‡; ) HANGUL SYLLABLE EOP
+C5CF;C5CF;110B 1165 11C2;C5CF;110B 1165 11C2; # (ì—; ì—; 엏; ì—; 엏; ) HANGUL SYLLABLE EOH
+C5D0;C5D0;110B 1166;C5D0;110B 1166; # (ì—; ì—; á„‹á…¦; ì—; á„‹á…¦; ) HANGUL SYLLABLE E
+C5D1;C5D1;110B 1166 11A8;C5D1;110B 1166 11A8; # (엑; 엑; 엑; 엑; 엑; ) HANGUL SYLLABLE EG
+C5D2;C5D2;110B 1166 11A9;C5D2;110B 1166 11A9; # (엒; 엒; 엒; 엒; 엒; ) HANGUL SYLLABLE EGG
+C5D3;C5D3;110B 1166 11AA;C5D3;110B 1166 11AA; # (엓; 엓; 엓; 엓; 엓; ) HANGUL SYLLABLE EGS
+C5D4;C5D4;110B 1166 11AB;C5D4;110B 1166 11AB; # (엔; 엔; 엔; 엔; 엔; ) HANGUL SYLLABLE EN
+C5D5;C5D5;110B 1166 11AC;C5D5;110B 1166 11AC; # (엕; 엕; 엕; 엕; 엕; ) HANGUL SYLLABLE ENJ
+C5D6;C5D6;110B 1166 11AD;C5D6;110B 1166 11AD; # (엖; 엖; 엖; 엖; 엖; ) HANGUL SYLLABLE ENH
+C5D7;C5D7;110B 1166 11AE;C5D7;110B 1166 11AE; # (엗; 엗; 엗; 엗; 엗; ) HANGUL SYLLABLE ED
+C5D8;C5D8;110B 1166 11AF;C5D8;110B 1166 11AF; # (엘; 엘; 엘; 엘; 엘; ) HANGUL SYLLABLE EL
+C5D9;C5D9;110B 1166 11B0;C5D9;110B 1166 11B0; # (엙; 엙; 엙; 엙; 엙; ) HANGUL SYLLABLE ELG
+C5DA;C5DA;110B 1166 11B1;C5DA;110B 1166 11B1; # (엚; 엚; 엚; 엚; 엚; ) HANGUL SYLLABLE ELM
+C5DB;C5DB;110B 1166 11B2;C5DB;110B 1166 11B2; # (엛; 엛; 엛; 엛; 엛; ) HANGUL SYLLABLE ELB
+C5DC;C5DC;110B 1166 11B3;C5DC;110B 1166 11B3; # (엜; 엜; 엜; 엜; 엜; ) HANGUL SYLLABLE ELS
+C5DD;C5DD;110B 1166 11B4;C5DD;110B 1166 11B4; # (ì—; ì—; 엝; ì—; 엝; ) HANGUL SYLLABLE ELT
+C5DE;C5DE;110B 1166 11B5;C5DE;110B 1166 11B5; # (엞; 엞; 엞; 엞; 엞; ) HANGUL SYLLABLE ELP
+C5DF;C5DF;110B 1166 11B6;C5DF;110B 1166 11B6; # (엟; 엟; 엟; 엟; 엟; ) HANGUL SYLLABLE ELH
+C5E0;C5E0;110B 1166 11B7;C5E0;110B 1166 11B7; # (엠; 엠; 엠; 엠; 엠; ) HANGUL SYLLABLE EM
+C5E1;C5E1;110B 1166 11B8;C5E1;110B 1166 11B8; # (엡; 엡; 엡; 엡; 엡; ) HANGUL SYLLABLE EB
+C5E2;C5E2;110B 1166 11B9;C5E2;110B 1166 11B9; # (엢; 엢; 엢; 엢; 엢; ) HANGUL SYLLABLE EBS
+C5E3;C5E3;110B 1166 11BA;C5E3;110B 1166 11BA; # (엣; 엣; 엣; 엣; 엣; ) HANGUL SYLLABLE ES
+C5E4;C5E4;110B 1166 11BB;C5E4;110B 1166 11BB; # (엤; 엤; 엤; 엤; 엤; ) HANGUL SYLLABLE ESS
+C5E5;C5E5;110B 1166 11BC;C5E5;110B 1166 11BC; # (엥; 엥; 엥; 엥; 엥; ) HANGUL SYLLABLE ENG
+C5E6;C5E6;110B 1166 11BD;C5E6;110B 1166 11BD; # (엦; 엦; 엦; 엦; 엦; ) HANGUL SYLLABLE EJ
+C5E7;C5E7;110B 1166 11BE;C5E7;110B 1166 11BE; # (엧; 엧; 엧; 엧; 엧; ) HANGUL SYLLABLE EC
+C5E8;C5E8;110B 1166 11BF;C5E8;110B 1166 11BF; # (엨; 엨; 엨; 엨; 엨; ) HANGUL SYLLABLE EK
+C5E9;C5E9;110B 1166 11C0;C5E9;110B 1166 11C0; # (엩; 엩; 엩; 엩; 엩; ) HANGUL SYLLABLE ET
+C5EA;C5EA;110B 1166 11C1;C5EA;110B 1166 11C1; # (ì—ª; ì—ª; á„‹á…¦á‡; ì—ª; á„‹á…¦á‡; ) HANGUL SYLLABLE EP
+C5EB;C5EB;110B 1166 11C2;C5EB;110B 1166 11C2; # (엫; 엫; 엫; 엫; 엫; ) HANGUL SYLLABLE EH
+C5EC;C5EC;110B 1167;C5EC;110B 1167; # (ì—¬; ì—¬; á„‹á…§; ì—¬; á„‹á…§; ) HANGUL SYLLABLE YEO
+C5ED;C5ED;110B 1167 11A8;C5ED;110B 1167 11A8; # (역; 역; 역; 역; 역; ) HANGUL SYLLABLE YEOG
+C5EE;C5EE;110B 1167 11A9;C5EE;110B 1167 11A9; # (엮; 엮; 엮; 엮; 엮; ) HANGUL SYLLABLE YEOGG
+C5EF;C5EF;110B 1167 11AA;C5EF;110B 1167 11AA; # (엯; 엯; 엯; 엯; 엯; ) HANGUL SYLLABLE YEOGS
+C5F0;C5F0;110B 1167 11AB;C5F0;110B 1167 11AB; # (연; 연; 연; 연; 연; ) HANGUL SYLLABLE YEON
+C5F1;C5F1;110B 1167 11AC;C5F1;110B 1167 11AC; # (엱; 엱; 엱; 엱; 엱; ) HANGUL SYLLABLE YEONJ
+C5F2;C5F2;110B 1167 11AD;C5F2;110B 1167 11AD; # (엲; 엲; 엲; 엲; 엲; ) HANGUL SYLLABLE YEONH
+C5F3;C5F3;110B 1167 11AE;C5F3;110B 1167 11AE; # (엳; 엳; 엳; 엳; 엳; ) HANGUL SYLLABLE YEOD
+C5F4;C5F4;110B 1167 11AF;C5F4;110B 1167 11AF; # (열; 열; 열; 열; 열; ) HANGUL SYLLABLE YEOL
+C5F5;C5F5;110B 1167 11B0;C5F5;110B 1167 11B0; # (엵; 엵; 엵; 엵; 엵; ) HANGUL SYLLABLE YEOLG
+C5F6;C5F6;110B 1167 11B1;C5F6;110B 1167 11B1; # (엶; 엶; 엶; 엶; 엶; ) HANGUL SYLLABLE YEOLM
+C5F7;C5F7;110B 1167 11B2;C5F7;110B 1167 11B2; # (엷; 엷; 엷; 엷; 엷; ) HANGUL SYLLABLE YEOLB
+C5F8;C5F8;110B 1167 11B3;C5F8;110B 1167 11B3; # (엸; 엸; 엸; 엸; 엸; ) HANGUL SYLLABLE YEOLS
+C5F9;C5F9;110B 1167 11B4;C5F9;110B 1167 11B4; # (엹; 엹; 엹; 엹; 엹; ) HANGUL SYLLABLE YEOLT
+C5FA;C5FA;110B 1167 11B5;C5FA;110B 1167 11B5; # (엺; 엺; 엺; 엺; 엺; ) HANGUL SYLLABLE YEOLP
+C5FB;C5FB;110B 1167 11B6;C5FB;110B 1167 11B6; # (엻; 엻; 엻; 엻; 엻; ) HANGUL SYLLABLE YEOLH
+C5FC;C5FC;110B 1167 11B7;C5FC;110B 1167 11B7; # (염; 염; 염; 염; 염; ) HANGUL SYLLABLE YEOM
+C5FD;C5FD;110B 1167 11B8;C5FD;110B 1167 11B8; # (엽; 엽; 엽; 엽; 엽; ) HANGUL SYLLABLE YEOB
+C5FE;C5FE;110B 1167 11B9;C5FE;110B 1167 11B9; # (엾; 엾; 엾; 엾; 엾; ) HANGUL SYLLABLE YEOBS
+C5FF;C5FF;110B 1167 11BA;C5FF;110B 1167 11BA; # (엿; 엿; 엿; 엿; 엿; ) HANGUL SYLLABLE YEOS
+C600;C600;110B 1167 11BB;C600;110B 1167 11BB; # (였; 였; 였; 였; 였; ) HANGUL SYLLABLE YEOSS
+C601;C601;110B 1167 11BC;C601;110B 1167 11BC; # (ì˜; ì˜; 영; ì˜; 영; ) HANGUL SYLLABLE YEONG
+C602;C602;110B 1167 11BD;C602;110B 1167 11BD; # (옂; 옂; 옂; 옂; 옂; ) HANGUL SYLLABLE YEOJ
+C603;C603;110B 1167 11BE;C603;110B 1167 11BE; # (옃; 옃; 옃; 옃; 옃; ) HANGUL SYLLABLE YEOC
+C604;C604;110B 1167 11BF;C604;110B 1167 11BF; # (옄; 옄; 옄; 옄; 옄; ) HANGUL SYLLABLE YEOK
+C605;C605;110B 1167 11C0;C605;110B 1167 11C0; # (옅; 옅; 옅; 옅; 옅; ) HANGUL SYLLABLE YEOT
+C606;C606;110B 1167 11C1;C606;110B 1167 11C1; # (옆; 옆; á„‹á…§á‡; 옆; á„‹á…§á‡; ) HANGUL SYLLABLE YEOP
+C607;C607;110B 1167 11C2;C607;110B 1167 11C2; # (옇; 옇; 옇; 옇; 옇; ) HANGUL SYLLABLE YEOH
+C608;C608;110B 1168;C608;110B 1168; # (예; 예; 예; 예; 예; ) HANGUL SYLLABLE YE
+C609;C609;110B 1168 11A8;C609;110B 1168 11A8; # (옉; 옉; 옉; 옉; 옉; ) HANGUL SYLLABLE YEG
+C60A;C60A;110B 1168 11A9;C60A;110B 1168 11A9; # (옊; 옊; 옊; 옊; 옊; ) HANGUL SYLLABLE YEGG
+C60B;C60B;110B 1168 11AA;C60B;110B 1168 11AA; # (옋; 옋; 옋; 옋; 옋; ) HANGUL SYLLABLE YEGS
+C60C;C60C;110B 1168 11AB;C60C;110B 1168 11AB; # (옌; 옌; 옌; 옌; 옌; ) HANGUL SYLLABLE YEN
+C60D;C60D;110B 1168 11AC;C60D;110B 1168 11AC; # (ì˜; ì˜; 옍; ì˜; 옍; ) HANGUL SYLLABLE YENJ
+C60E;C60E;110B 1168 11AD;C60E;110B 1168 11AD; # (옎; 옎; 옎; 옎; 옎; ) HANGUL SYLLABLE YENH
+C60F;C60F;110B 1168 11AE;C60F;110B 1168 11AE; # (ì˜; ì˜; 옏; ì˜; 옏; ) HANGUL SYLLABLE YED
+C610;C610;110B 1168 11AF;C610;110B 1168 11AF; # (ì˜; ì˜; 옐; ì˜; 옐; ) HANGUL SYLLABLE YEL
+C611;C611;110B 1168 11B0;C611;110B 1168 11B0; # (옑; 옑; 옑; 옑; 옑; ) HANGUL SYLLABLE YELG
+C612;C612;110B 1168 11B1;C612;110B 1168 11B1; # (옒; 옒; 옒; 옒; 옒; ) HANGUL SYLLABLE YELM
+C613;C613;110B 1168 11B2;C613;110B 1168 11B2; # (옓; 옓; 옓; 옓; 옓; ) HANGUL SYLLABLE YELB
+C614;C614;110B 1168 11B3;C614;110B 1168 11B3; # (옔; 옔; 옔; 옔; 옔; ) HANGUL SYLLABLE YELS
+C615;C615;110B 1168 11B4;C615;110B 1168 11B4; # (옕; 옕; 옕; 옕; 옕; ) HANGUL SYLLABLE YELT
+C616;C616;110B 1168 11B5;C616;110B 1168 11B5; # (옖; 옖; 옖; 옖; 옖; ) HANGUL SYLLABLE YELP
+C617;C617;110B 1168 11B6;C617;110B 1168 11B6; # (옗; 옗; 옗; 옗; 옗; ) HANGUL SYLLABLE YELH
+C618;C618;110B 1168 11B7;C618;110B 1168 11B7; # (옘; 옘; 옘; 옘; 옘; ) HANGUL SYLLABLE YEM
+C619;C619;110B 1168 11B8;C619;110B 1168 11B8; # (옙; 옙; 옙; 옙; 옙; ) HANGUL SYLLABLE YEB
+C61A;C61A;110B 1168 11B9;C61A;110B 1168 11B9; # (옚; 옚; 옚; 옚; 옚; ) HANGUL SYLLABLE YEBS
+C61B;C61B;110B 1168 11BA;C61B;110B 1168 11BA; # (옛; 옛; 옛; 옛; 옛; ) HANGUL SYLLABLE YES
+C61C;C61C;110B 1168 11BB;C61C;110B 1168 11BB; # (옜; 옜; 옜; 옜; 옜; ) HANGUL SYLLABLE YESS
+C61D;C61D;110B 1168 11BC;C61D;110B 1168 11BC; # (ì˜; ì˜; 옝; ì˜; 옝; ) HANGUL SYLLABLE YENG
+C61E;C61E;110B 1168 11BD;C61E;110B 1168 11BD; # (옞; 옞; 옞; 옞; 옞; ) HANGUL SYLLABLE YEJ
+C61F;C61F;110B 1168 11BE;C61F;110B 1168 11BE; # (옟; 옟; 옟; 옟; 옟; ) HANGUL SYLLABLE YEC
+C620;C620;110B 1168 11BF;C620;110B 1168 11BF; # (옠; 옠; 옠; 옠; 옠; ) HANGUL SYLLABLE YEK
+C621;C621;110B 1168 11C0;C621;110B 1168 11C0; # (옡; 옡; 옡; 옡; 옡; ) HANGUL SYLLABLE YET
+C622;C622;110B 1168 11C1;C622;110B 1168 11C1; # (옢; 옢; á„‹á…¨á‡; 옢; á„‹á…¨á‡; ) HANGUL SYLLABLE YEP
+C623;C623;110B 1168 11C2;C623;110B 1168 11C2; # (옣; 옣; 옣; 옣; 옣; ) HANGUL SYLLABLE YEH
+C624;C624;110B 1169;C624;110B 1169; # (오; 오; 오; 오; 오; ) HANGUL SYLLABLE O
+C625;C625;110B 1169 11A8;C625;110B 1169 11A8; # (옥; 옥; 옥; 옥; 옥; ) HANGUL SYLLABLE OG
+C626;C626;110B 1169 11A9;C626;110B 1169 11A9; # (옦; 옦; 옦; 옦; 옦; ) HANGUL SYLLABLE OGG
+C627;C627;110B 1169 11AA;C627;110B 1169 11AA; # (옧; 옧; 옧; 옧; 옧; ) HANGUL SYLLABLE OGS
+C628;C628;110B 1169 11AB;C628;110B 1169 11AB; # (온; 온; 온; 온; 온; ) HANGUL SYLLABLE ON
+C629;C629;110B 1169 11AC;C629;110B 1169 11AC; # (옩; 옩; 옩; 옩; 옩; ) HANGUL SYLLABLE ONJ
+C62A;C62A;110B 1169 11AD;C62A;110B 1169 11AD; # (옪; 옪; 옪; 옪; 옪; ) HANGUL SYLLABLE ONH
+C62B;C62B;110B 1169 11AE;C62B;110B 1169 11AE; # (옫; 옫; 옫; 옫; 옫; ) HANGUL SYLLABLE OD
+C62C;C62C;110B 1169 11AF;C62C;110B 1169 11AF; # (올; 올; 올; 올; 올; ) HANGUL SYLLABLE OL
+C62D;C62D;110B 1169 11B0;C62D;110B 1169 11B0; # (옭; 옭; 옭; 옭; 옭; ) HANGUL SYLLABLE OLG
+C62E;C62E;110B 1169 11B1;C62E;110B 1169 11B1; # (옮; 옮; 옮; 옮; 옮; ) HANGUL SYLLABLE OLM
+C62F;C62F;110B 1169 11B2;C62F;110B 1169 11B2; # (옯; 옯; 옯; 옯; 옯; ) HANGUL SYLLABLE OLB
+C630;C630;110B 1169 11B3;C630;110B 1169 11B3; # (옰; 옰; 옰; 옰; 옰; ) HANGUL SYLLABLE OLS
+C631;C631;110B 1169 11B4;C631;110B 1169 11B4; # (옱; 옱; 옱; 옱; 옱; ) HANGUL SYLLABLE OLT
+C632;C632;110B 1169 11B5;C632;110B 1169 11B5; # (옲; 옲; 옲; 옲; 옲; ) HANGUL SYLLABLE OLP
+C633;C633;110B 1169 11B6;C633;110B 1169 11B6; # (옳; 옳; 옳; 옳; 옳; ) HANGUL SYLLABLE OLH
+C634;C634;110B 1169 11B7;C634;110B 1169 11B7; # (옴; 옴; 옴; 옴; 옴; ) HANGUL SYLLABLE OM
+C635;C635;110B 1169 11B8;C635;110B 1169 11B8; # (옵; 옵; 옵; 옵; 옵; ) HANGUL SYLLABLE OB
+C636;C636;110B 1169 11B9;C636;110B 1169 11B9; # (옶; 옶; 옶; 옶; 옶; ) HANGUL SYLLABLE OBS
+C637;C637;110B 1169 11BA;C637;110B 1169 11BA; # (옷; 옷; 옷; 옷; 옷; ) HANGUL SYLLABLE OS
+C638;C638;110B 1169 11BB;C638;110B 1169 11BB; # (옸; 옸; 옸; 옸; 옸; ) HANGUL SYLLABLE OSS
+C639;C639;110B 1169 11BC;C639;110B 1169 11BC; # (옹; 옹; 옹; 옹; 옹; ) HANGUL SYLLABLE ONG
+C63A;C63A;110B 1169 11BD;C63A;110B 1169 11BD; # (옺; 옺; 옺; 옺; 옺; ) HANGUL SYLLABLE OJ
+C63B;C63B;110B 1169 11BE;C63B;110B 1169 11BE; # (옻; 옻; 옻; 옻; 옻; ) HANGUL SYLLABLE OC
+C63C;C63C;110B 1169 11BF;C63C;110B 1169 11BF; # (옼; 옼; 옼; 옼; 옼; ) HANGUL SYLLABLE OK
+C63D;C63D;110B 1169 11C0;C63D;110B 1169 11C0; # (옽; 옽; 옽; 옽; 옽; ) HANGUL SYLLABLE OT
+C63E;C63E;110B 1169 11C1;C63E;110B 1169 11C1; # (옾; 옾; á„‹á…©á‡; 옾; á„‹á…©á‡; ) HANGUL SYLLABLE OP
+C63F;C63F;110B 1169 11C2;C63F;110B 1169 11C2; # (옿; 옿; 옿; 옿; 옿; ) HANGUL SYLLABLE OH
+C640;C640;110B 116A;C640;110B 116A; # (와; 와; 와; 와; 와; ) HANGUL SYLLABLE WA
+C641;C641;110B 116A 11A8;C641;110B 116A 11A8; # (ì™; ì™; 왁; ì™; 왁; ) HANGUL SYLLABLE WAG
+C642;C642;110B 116A 11A9;C642;110B 116A 11A9; # (왂; 왂; 왂; 왂; 왂; ) HANGUL SYLLABLE WAGG
+C643;C643;110B 116A 11AA;C643;110B 116A 11AA; # (왃; 왃; 왃; 왃; 왃; ) HANGUL SYLLABLE WAGS
+C644;C644;110B 116A 11AB;C644;110B 116A 11AB; # (완; 완; 완; 완; 완; ) HANGUL SYLLABLE WAN
+C645;C645;110B 116A 11AC;C645;110B 116A 11AC; # (왅; 왅; 왅; 왅; 왅; ) HANGUL SYLLABLE WANJ
+C646;C646;110B 116A 11AD;C646;110B 116A 11AD; # (왆; 왆; 왆; 왆; 왆; ) HANGUL SYLLABLE WANH
+C647;C647;110B 116A 11AE;C647;110B 116A 11AE; # (왇; 왇; 왇; 왇; 왇; ) HANGUL SYLLABLE WAD
+C648;C648;110B 116A 11AF;C648;110B 116A 11AF; # (왈; 왈; 왈; 왈; 왈; ) HANGUL SYLLABLE WAL
+C649;C649;110B 116A 11B0;C649;110B 116A 11B0; # (왉; 왉; 왉; 왉; 왉; ) HANGUL SYLLABLE WALG
+C64A;C64A;110B 116A 11B1;C64A;110B 116A 11B1; # (왊; 왊; 왊; 왊; 왊; ) HANGUL SYLLABLE WALM
+C64B;C64B;110B 116A 11B2;C64B;110B 116A 11B2; # (왋; 왋; 왋; 왋; 왋; ) HANGUL SYLLABLE WALB
+C64C;C64C;110B 116A 11B3;C64C;110B 116A 11B3; # (왌; 왌; 왌; 왌; 왌; ) HANGUL SYLLABLE WALS
+C64D;C64D;110B 116A 11B4;C64D;110B 116A 11B4; # (ì™; ì™; 왍; ì™; 왍; ) HANGUL SYLLABLE WALT
+C64E;C64E;110B 116A 11B5;C64E;110B 116A 11B5; # (왎; 왎; 왎; 왎; 왎; ) HANGUL SYLLABLE WALP
+C64F;C64F;110B 116A 11B6;C64F;110B 116A 11B6; # (ì™; ì™; 왏; ì™; 왏; ) HANGUL SYLLABLE WALH
+C650;C650;110B 116A 11B7;C650;110B 116A 11B7; # (ì™; ì™; 왐; ì™; 왐; ) HANGUL SYLLABLE WAM
+C651;C651;110B 116A 11B8;C651;110B 116A 11B8; # (왑; 왑; 왑; 왑; 왑; ) HANGUL SYLLABLE WAB
+C652;C652;110B 116A 11B9;C652;110B 116A 11B9; # (왒; 왒; 왒; 왒; 왒; ) HANGUL SYLLABLE WABS
+C653;C653;110B 116A 11BA;C653;110B 116A 11BA; # (왓; 왓; 왓; 왓; 왓; ) HANGUL SYLLABLE WAS
+C654;C654;110B 116A 11BB;C654;110B 116A 11BB; # (왔; 왔; 왔; 왔; 왔; ) HANGUL SYLLABLE WASS
+C655;C655;110B 116A 11BC;C655;110B 116A 11BC; # (왕; 왕; 왕; 왕; 왕; ) HANGUL SYLLABLE WANG
+C656;C656;110B 116A 11BD;C656;110B 116A 11BD; # (왖; 왖; 왖; 왖; 왖; ) HANGUL SYLLABLE WAJ
+C657;C657;110B 116A 11BE;C657;110B 116A 11BE; # (왗; 왗; 왗; 왗; 왗; ) HANGUL SYLLABLE WAC
+C658;C658;110B 116A 11BF;C658;110B 116A 11BF; # (왘; 왘; 왘; 왘; 왘; ) HANGUL SYLLABLE WAK
+C659;C659;110B 116A 11C0;C659;110B 116A 11C0; # (왙; 왙; 왙; 왙; 왙; ) HANGUL SYLLABLE WAT
+C65A;C65A;110B 116A 11C1;C65A;110B 116A 11C1; # (왚; 왚; á„‹á…ªá‡; 왚; á„‹á…ªá‡; ) HANGUL SYLLABLE WAP
+C65B;C65B;110B 116A 11C2;C65B;110B 116A 11C2; # (왛; 왛; 왛; 왛; 왛; ) HANGUL SYLLABLE WAH
+C65C;C65C;110B 116B;C65C;110B 116B; # (왜; 왜; 왜; 왜; 왜; ) HANGUL SYLLABLE WAE
+C65D;C65D;110B 116B 11A8;C65D;110B 116B 11A8; # (ì™; ì™; 왝; ì™; 왝; ) HANGUL SYLLABLE WAEG
+C65E;C65E;110B 116B 11A9;C65E;110B 116B 11A9; # (왞; 왞; 왞; 왞; 왞; ) HANGUL SYLLABLE WAEGG
+C65F;C65F;110B 116B 11AA;C65F;110B 116B 11AA; # (왟; 왟; 왟; 왟; 왟; ) HANGUL SYLLABLE WAEGS
+C660;C660;110B 116B 11AB;C660;110B 116B 11AB; # (왠; 왠; 왠; 왠; 왠; ) HANGUL SYLLABLE WAEN
+C661;C661;110B 116B 11AC;C661;110B 116B 11AC; # (왡; 왡; 왡; 왡; 왡; ) HANGUL SYLLABLE WAENJ
+C662;C662;110B 116B 11AD;C662;110B 116B 11AD; # (왢; 왢; 왢; 왢; 왢; ) HANGUL SYLLABLE WAENH
+C663;C663;110B 116B 11AE;C663;110B 116B 11AE; # (왣; 왣; 왣; 왣; 왣; ) HANGUL SYLLABLE WAED
+C664;C664;110B 116B 11AF;C664;110B 116B 11AF; # (왤; 왤; 왤; 왤; 왤; ) HANGUL SYLLABLE WAEL
+C665;C665;110B 116B 11B0;C665;110B 116B 11B0; # (왥; 왥; 왥; 왥; 왥; ) HANGUL SYLLABLE WAELG
+C666;C666;110B 116B 11B1;C666;110B 116B 11B1; # (왦; 왦; 왦; 왦; 왦; ) HANGUL SYLLABLE WAELM
+C667;C667;110B 116B 11B2;C667;110B 116B 11B2; # (왧; 왧; 왧; 왧; 왧; ) HANGUL SYLLABLE WAELB
+C668;C668;110B 116B 11B3;C668;110B 116B 11B3; # (왨; 왨; 왨; 왨; 왨; ) HANGUL SYLLABLE WAELS
+C669;C669;110B 116B 11B4;C669;110B 116B 11B4; # (왩; 왩; 왩; 왩; 왩; ) HANGUL SYLLABLE WAELT
+C66A;C66A;110B 116B 11B5;C66A;110B 116B 11B5; # (왪; 왪; 왪; 왪; 왪; ) HANGUL SYLLABLE WAELP
+C66B;C66B;110B 116B 11B6;C66B;110B 116B 11B6; # (왫; 왫; 왫; 왫; 왫; ) HANGUL SYLLABLE WAELH
+C66C;C66C;110B 116B 11B7;C66C;110B 116B 11B7; # (왬; 왬; 왬; 왬; 왬; ) HANGUL SYLLABLE WAEM
+C66D;C66D;110B 116B 11B8;C66D;110B 116B 11B8; # (왭; 왭; 왭; 왭; 왭; ) HANGUL SYLLABLE WAEB
+C66E;C66E;110B 116B 11B9;C66E;110B 116B 11B9; # (왮; 왮; 왮; 왮; 왮; ) HANGUL SYLLABLE WAEBS
+C66F;C66F;110B 116B 11BA;C66F;110B 116B 11BA; # (왯; 왯; 왯; 왯; 왯; ) HANGUL SYLLABLE WAES
+C670;C670;110B 116B 11BB;C670;110B 116B 11BB; # (왰; 왰; 왰; 왰; 왰; ) HANGUL SYLLABLE WAESS
+C671;C671;110B 116B 11BC;C671;110B 116B 11BC; # (왱; 왱; 왱; 왱; 왱; ) HANGUL SYLLABLE WAENG
+C672;C672;110B 116B 11BD;C672;110B 116B 11BD; # (왲; 왲; 왲; 왲; 왲; ) HANGUL SYLLABLE WAEJ
+C673;C673;110B 116B 11BE;C673;110B 116B 11BE; # (왳; 왳; 왳; 왳; 왳; ) HANGUL SYLLABLE WAEC
+C674;C674;110B 116B 11BF;C674;110B 116B 11BF; # (왴; 왴; 왴; 왴; 왴; ) HANGUL SYLLABLE WAEK
+C675;C675;110B 116B 11C0;C675;110B 116B 11C0; # (왵; 왵; 왵; 왵; 왵; ) HANGUL SYLLABLE WAET
+C676;C676;110B 116B 11C1;C676;110B 116B 11C1; # (왶; 왶; á„‹á…«á‡; 왶; á„‹á…«á‡; ) HANGUL SYLLABLE WAEP
+C677;C677;110B 116B 11C2;C677;110B 116B 11C2; # (왷; 왷; 왷; 왷; 왷; ) HANGUL SYLLABLE WAEH
+C678;C678;110B 116C;C678;110B 116C; # (외; 외; 외; 외; 외; ) HANGUL SYLLABLE OE
+C679;C679;110B 116C 11A8;C679;110B 116C 11A8; # (왹; 왹; 왹; 왹; 왹; ) HANGUL SYLLABLE OEG
+C67A;C67A;110B 116C 11A9;C67A;110B 116C 11A9; # (왺; 왺; 왺; 왺; 왺; ) HANGUL SYLLABLE OEGG
+C67B;C67B;110B 116C 11AA;C67B;110B 116C 11AA; # (왻; 왻; 왻; 왻; 왻; ) HANGUL SYLLABLE OEGS
+C67C;C67C;110B 116C 11AB;C67C;110B 116C 11AB; # (왼; 왼; 왼; 왼; 왼; ) HANGUL SYLLABLE OEN
+C67D;C67D;110B 116C 11AC;C67D;110B 116C 11AC; # (왽; 왽; 왽; 왽; 왽; ) HANGUL SYLLABLE OENJ
+C67E;C67E;110B 116C 11AD;C67E;110B 116C 11AD; # (왾; 왾; 왾; 왾; 왾; ) HANGUL SYLLABLE OENH
+C67F;C67F;110B 116C 11AE;C67F;110B 116C 11AE; # (왿; 왿; 왿; 왿; 왿; ) HANGUL SYLLABLE OED
+C680;C680;110B 116C 11AF;C680;110B 116C 11AF; # (욀; 욀; 욀; 욀; 욀; ) HANGUL SYLLABLE OEL
+C681;C681;110B 116C 11B0;C681;110B 116C 11B0; # (ìš; ìš; 욁; ìš; 욁; ) HANGUL SYLLABLE OELG
+C682;C682;110B 116C 11B1;C682;110B 116C 11B1; # (욂; 욂; 욂; 욂; 욂; ) HANGUL SYLLABLE OELM
+C683;C683;110B 116C 11B2;C683;110B 116C 11B2; # (욃; 욃; 욃; 욃; 욃; ) HANGUL SYLLABLE OELB
+C684;C684;110B 116C 11B3;C684;110B 116C 11B3; # (욄; 욄; 욄; 욄; 욄; ) HANGUL SYLLABLE OELS
+C685;C685;110B 116C 11B4;C685;110B 116C 11B4; # (욅; 욅; 욅; 욅; 욅; ) HANGUL SYLLABLE OELT
+C686;C686;110B 116C 11B5;C686;110B 116C 11B5; # (욆; 욆; 욆; 욆; 욆; ) HANGUL SYLLABLE OELP
+C687;C687;110B 116C 11B6;C687;110B 116C 11B6; # (욇; 욇; 욇; 욇; 욇; ) HANGUL SYLLABLE OELH
+C688;C688;110B 116C 11B7;C688;110B 116C 11B7; # (욈; 욈; 욈; 욈; 욈; ) HANGUL SYLLABLE OEM
+C689;C689;110B 116C 11B8;C689;110B 116C 11B8; # (욉; 욉; 욉; 욉; 욉; ) HANGUL SYLLABLE OEB
+C68A;C68A;110B 116C 11B9;C68A;110B 116C 11B9; # (욊; 욊; 욊; 욊; 욊; ) HANGUL SYLLABLE OEBS
+C68B;C68B;110B 116C 11BA;C68B;110B 116C 11BA; # (욋; 욋; 욋; 욋; 욋; ) HANGUL SYLLABLE OES
+C68C;C68C;110B 116C 11BB;C68C;110B 116C 11BB; # (욌; 욌; 욌; 욌; 욌; ) HANGUL SYLLABLE OESS
+C68D;C68D;110B 116C 11BC;C68D;110B 116C 11BC; # (ìš; ìš; 욍; ìš; 욍; ) HANGUL SYLLABLE OENG
+C68E;C68E;110B 116C 11BD;C68E;110B 116C 11BD; # (욎; 욎; 욎; 욎; 욎; ) HANGUL SYLLABLE OEJ
+C68F;C68F;110B 116C 11BE;C68F;110B 116C 11BE; # (ìš; ìš; 욏; ìš; 욏; ) HANGUL SYLLABLE OEC
+C690;C690;110B 116C 11BF;C690;110B 116C 11BF; # (ìš; ìš; 욐; ìš; 욐; ) HANGUL SYLLABLE OEK
+C691;C691;110B 116C 11C0;C691;110B 116C 11C0; # (욑; 욑; 욑; 욑; 욑; ) HANGUL SYLLABLE OET
+C692;C692;110B 116C 11C1;C692;110B 116C 11C1; # (ìš’; ìš’; á„‹á…¬á‡; ìš’; á„‹á…¬á‡; ) HANGUL SYLLABLE OEP
+C693;C693;110B 116C 11C2;C693;110B 116C 11C2; # (욓; 욓; 욓; 욓; 욓; ) HANGUL SYLLABLE OEH
+C694;C694;110B 116D;C694;110B 116D; # (ìš”; ìš”; á„‹á…­; ìš”; á„‹á…­; ) HANGUL SYLLABLE YO
+C695;C695;110B 116D 11A8;C695;110B 116D 11A8; # (욕; 욕; 욕; 욕; 욕; ) HANGUL SYLLABLE YOG
+C696;C696;110B 116D 11A9;C696;110B 116D 11A9; # (욖; 욖; 욖; 욖; 욖; ) HANGUL SYLLABLE YOGG
+C697;C697;110B 116D 11AA;C697;110B 116D 11AA; # (욗; 욗; 욗; 욗; 욗; ) HANGUL SYLLABLE YOGS
+C698;C698;110B 116D 11AB;C698;110B 116D 11AB; # (욘; 욘; 욘; 욘; 욘; ) HANGUL SYLLABLE YON
+C699;C699;110B 116D 11AC;C699;110B 116D 11AC; # (욙; 욙; 욙; 욙; 욙; ) HANGUL SYLLABLE YONJ
+C69A;C69A;110B 116D 11AD;C69A;110B 116D 11AD; # (욚; 욚; 욚; 욚; 욚; ) HANGUL SYLLABLE YONH
+C69B;C69B;110B 116D 11AE;C69B;110B 116D 11AE; # (욛; 욛; 욛; 욛; 욛; ) HANGUL SYLLABLE YOD
+C69C;C69C;110B 116D 11AF;C69C;110B 116D 11AF; # (욜; 욜; 욜; 욜; 욜; ) HANGUL SYLLABLE YOL
+C69D;C69D;110B 116D 11B0;C69D;110B 116D 11B0; # (ìš; ìš; 욝; ìš; 욝; ) HANGUL SYLLABLE YOLG
+C69E;C69E;110B 116D 11B1;C69E;110B 116D 11B1; # (욞; 욞; 욞; 욞; 욞; ) HANGUL SYLLABLE YOLM
+C69F;C69F;110B 116D 11B2;C69F;110B 116D 11B2; # (욟; 욟; 욟; 욟; 욟; ) HANGUL SYLLABLE YOLB
+C6A0;C6A0;110B 116D 11B3;C6A0;110B 116D 11B3; # (욠; 욠; 욠; 욠; 욠; ) HANGUL SYLLABLE YOLS
+C6A1;C6A1;110B 116D 11B4;C6A1;110B 116D 11B4; # (욡; 욡; 욡; 욡; 욡; ) HANGUL SYLLABLE YOLT
+C6A2;C6A2;110B 116D 11B5;C6A2;110B 116D 11B5; # (욢; 욢; 욢; 욢; 욢; ) HANGUL SYLLABLE YOLP
+C6A3;C6A3;110B 116D 11B6;C6A3;110B 116D 11B6; # (욣; 욣; 욣; 욣; 욣; ) HANGUL SYLLABLE YOLH
+C6A4;C6A4;110B 116D 11B7;C6A4;110B 116D 11B7; # (욤; 욤; 욤; 욤; 욤; ) HANGUL SYLLABLE YOM
+C6A5;C6A5;110B 116D 11B8;C6A5;110B 116D 11B8; # (욥; 욥; 욥; 욥; 욥; ) HANGUL SYLLABLE YOB
+C6A6;C6A6;110B 116D 11B9;C6A6;110B 116D 11B9; # (욦; 욦; 욦; 욦; 욦; ) HANGUL SYLLABLE YOBS
+C6A7;C6A7;110B 116D 11BA;C6A7;110B 116D 11BA; # (욧; 욧; 욧; 욧; 욧; ) HANGUL SYLLABLE YOS
+C6A8;C6A8;110B 116D 11BB;C6A8;110B 116D 11BB; # (욨; 욨; 욨; 욨; 욨; ) HANGUL SYLLABLE YOSS
+C6A9;C6A9;110B 116D 11BC;C6A9;110B 116D 11BC; # (용; 용; 용; 용; 용; ) HANGUL SYLLABLE YONG
+C6AA;C6AA;110B 116D 11BD;C6AA;110B 116D 11BD; # (욪; 욪; 욪; 욪; 욪; ) HANGUL SYLLABLE YOJ
+C6AB;C6AB;110B 116D 11BE;C6AB;110B 116D 11BE; # (욫; 욫; 욫; 욫; 욫; ) HANGUL SYLLABLE YOC
+C6AC;C6AC;110B 116D 11BF;C6AC;110B 116D 11BF; # (욬; 욬; 욬; 욬; 욬; ) HANGUL SYLLABLE YOK
+C6AD;C6AD;110B 116D 11C0;C6AD;110B 116D 11C0; # (욭; 욭; 욭; 욭; 욭; ) HANGUL SYLLABLE YOT
+C6AE;C6AE;110B 116D 11C1;C6AE;110B 116D 11C1; # (ìš®; ìš®; á„‹á…­á‡; ìš®; á„‹á…­á‡; ) HANGUL SYLLABLE YOP
+C6AF;C6AF;110B 116D 11C2;C6AF;110B 116D 11C2; # (욯; 욯; 욯; 욯; 욯; ) HANGUL SYLLABLE YOH
+C6B0;C6B0;110B 116E;C6B0;110B 116E; # (ìš°; ìš°; á„‹á…®; ìš°; á„‹á…®; ) HANGUL SYLLABLE U
+C6B1;C6B1;110B 116E 11A8;C6B1;110B 116E 11A8; # (욱; 욱; 욱; 욱; 욱; ) HANGUL SYLLABLE UG
+C6B2;C6B2;110B 116E 11A9;C6B2;110B 116E 11A9; # (욲; 욲; 욲; 욲; 욲; ) HANGUL SYLLABLE UGG
+C6B3;C6B3;110B 116E 11AA;C6B3;110B 116E 11AA; # (욳; 욳; 욳; 욳; 욳; ) HANGUL SYLLABLE UGS
+C6B4;C6B4;110B 116E 11AB;C6B4;110B 116E 11AB; # (운; 운; 운; 운; 운; ) HANGUL SYLLABLE UN
+C6B5;C6B5;110B 116E 11AC;C6B5;110B 116E 11AC; # (욵; 욵; 욵; 욵; 욵; ) HANGUL SYLLABLE UNJ
+C6B6;C6B6;110B 116E 11AD;C6B6;110B 116E 11AD; # (욶; 욶; 욶; 욶; 욶; ) HANGUL SYLLABLE UNH
+C6B7;C6B7;110B 116E 11AE;C6B7;110B 116E 11AE; # (욷; 욷; 욷; 욷; 욷; ) HANGUL SYLLABLE UD
+C6B8;C6B8;110B 116E 11AF;C6B8;110B 116E 11AF; # (울; 울; 울; 울; 울; ) HANGUL SYLLABLE UL
+C6B9;C6B9;110B 116E 11B0;C6B9;110B 116E 11B0; # (욹; 욹; 욹; 욹; 욹; ) HANGUL SYLLABLE ULG
+C6BA;C6BA;110B 116E 11B1;C6BA;110B 116E 11B1; # (욺; 욺; 욺; 욺; 욺; ) HANGUL SYLLABLE ULM
+C6BB;C6BB;110B 116E 11B2;C6BB;110B 116E 11B2; # (욻; 욻; 욻; 욻; 욻; ) HANGUL SYLLABLE ULB
+C6BC;C6BC;110B 116E 11B3;C6BC;110B 116E 11B3; # (욼; 욼; 욼; 욼; 욼; ) HANGUL SYLLABLE ULS
+C6BD;C6BD;110B 116E 11B4;C6BD;110B 116E 11B4; # (욽; 욽; 욽; 욽; 욽; ) HANGUL SYLLABLE ULT
+C6BE;C6BE;110B 116E 11B5;C6BE;110B 116E 11B5; # (욾; 욾; 욾; 욾; 욾; ) HANGUL SYLLABLE ULP
+C6BF;C6BF;110B 116E 11B6;C6BF;110B 116E 11B6; # (욿; 욿; 욿; 욿; 욿; ) HANGUL SYLLABLE ULH
+C6C0;C6C0;110B 116E 11B7;C6C0;110B 116E 11B7; # (움; 움; 움; 움; 움; ) HANGUL SYLLABLE UM
+C6C1;C6C1;110B 116E 11B8;C6C1;110B 116E 11B8; # (ì›; ì›; 웁; ì›; 웁; ) HANGUL SYLLABLE UB
+C6C2;C6C2;110B 116E 11B9;C6C2;110B 116E 11B9; # (웂; 웂; 웂; 웂; 웂; ) HANGUL SYLLABLE UBS
+C6C3;C6C3;110B 116E 11BA;C6C3;110B 116E 11BA; # (웃; 웃; 웃; 웃; 웃; ) HANGUL SYLLABLE US
+C6C4;C6C4;110B 116E 11BB;C6C4;110B 116E 11BB; # (웄; 웄; 웄; 웄; 웄; ) HANGUL SYLLABLE USS
+C6C5;C6C5;110B 116E 11BC;C6C5;110B 116E 11BC; # (웅; 웅; 웅; 웅; 웅; ) HANGUL SYLLABLE UNG
+C6C6;C6C6;110B 116E 11BD;C6C6;110B 116E 11BD; # (웆; 웆; 웆; 웆; 웆; ) HANGUL SYLLABLE UJ
+C6C7;C6C7;110B 116E 11BE;C6C7;110B 116E 11BE; # (웇; 웇; 웇; 웇; 웇; ) HANGUL SYLLABLE UC
+C6C8;C6C8;110B 116E 11BF;C6C8;110B 116E 11BF; # (웈; 웈; 웈; 웈; 웈; ) HANGUL SYLLABLE UK
+C6C9;C6C9;110B 116E 11C0;C6C9;110B 116E 11C0; # (웉; 웉; 웉; 웉; 웉; ) HANGUL SYLLABLE UT
+C6CA;C6CA;110B 116E 11C1;C6CA;110B 116E 11C1; # (웊; 웊; á„‹á…®á‡; 웊; á„‹á…®á‡; ) HANGUL SYLLABLE UP
+C6CB;C6CB;110B 116E 11C2;C6CB;110B 116E 11C2; # (웋; 웋; 웋; 웋; 웋; ) HANGUL SYLLABLE UH
+C6CC;C6CC;110B 116F;C6CC;110B 116F; # (워; 워; 워; 워; 워; ) HANGUL SYLLABLE WEO
+C6CD;C6CD;110B 116F 11A8;C6CD;110B 116F 11A8; # (ì›; ì›; 웍; ì›; 웍; ) HANGUL SYLLABLE WEOG
+C6CE;C6CE;110B 116F 11A9;C6CE;110B 116F 11A9; # (웎; 웎; 웎; 웎; 웎; ) HANGUL SYLLABLE WEOGG
+C6CF;C6CF;110B 116F 11AA;C6CF;110B 116F 11AA; # (ì›; ì›; 웏; ì›; 웏; ) HANGUL SYLLABLE WEOGS
+C6D0;C6D0;110B 116F 11AB;C6D0;110B 116F 11AB; # (ì›; ì›; 원; ì›; 원; ) HANGUL SYLLABLE WEON
+C6D1;C6D1;110B 116F 11AC;C6D1;110B 116F 11AC; # (웑; 웑; 웑; 웑; 웑; ) HANGUL SYLLABLE WEONJ
+C6D2;C6D2;110B 116F 11AD;C6D2;110B 116F 11AD; # (웒; 웒; 웒; 웒; 웒; ) HANGUL SYLLABLE WEONH
+C6D3;C6D3;110B 116F 11AE;C6D3;110B 116F 11AE; # (웓; 웓; 웓; 웓; 웓; ) HANGUL SYLLABLE WEOD
+C6D4;C6D4;110B 116F 11AF;C6D4;110B 116F 11AF; # (월; 월; 월; 월; 월; ) HANGUL SYLLABLE WEOL
+C6D5;C6D5;110B 116F 11B0;C6D5;110B 116F 11B0; # (웕; 웕; 웕; 웕; 웕; ) HANGUL SYLLABLE WEOLG
+C6D6;C6D6;110B 116F 11B1;C6D6;110B 116F 11B1; # (웖; 웖; 웖; 웖; 웖; ) HANGUL SYLLABLE WEOLM
+C6D7;C6D7;110B 116F 11B2;C6D7;110B 116F 11B2; # (웗; 웗; 웗; 웗; 웗; ) HANGUL SYLLABLE WEOLB
+C6D8;C6D8;110B 116F 11B3;C6D8;110B 116F 11B3; # (웘; 웘; 웘; 웘; 웘; ) HANGUL SYLLABLE WEOLS
+C6D9;C6D9;110B 116F 11B4;C6D9;110B 116F 11B4; # (웙; 웙; 웙; 웙; 웙; ) HANGUL SYLLABLE WEOLT
+C6DA;C6DA;110B 116F 11B5;C6DA;110B 116F 11B5; # (웚; 웚; 웚; 웚; 웚; ) HANGUL SYLLABLE WEOLP
+C6DB;C6DB;110B 116F 11B6;C6DB;110B 116F 11B6; # (웛; 웛; 웛; 웛; 웛; ) HANGUL SYLLABLE WEOLH
+C6DC;C6DC;110B 116F 11B7;C6DC;110B 116F 11B7; # (웜; 웜; 웜; 웜; 웜; ) HANGUL SYLLABLE WEOM
+C6DD;C6DD;110B 116F 11B8;C6DD;110B 116F 11B8; # (ì›; ì›; 웝; ì›; 웝; ) HANGUL SYLLABLE WEOB
+C6DE;C6DE;110B 116F 11B9;C6DE;110B 116F 11B9; # (웞; 웞; 웞; 웞; 웞; ) HANGUL SYLLABLE WEOBS
+C6DF;C6DF;110B 116F 11BA;C6DF;110B 116F 11BA; # (웟; 웟; 웟; 웟; 웟; ) HANGUL SYLLABLE WEOS
+C6E0;C6E0;110B 116F 11BB;C6E0;110B 116F 11BB; # (웠; 웠; 웠; 웠; 웠; ) HANGUL SYLLABLE WEOSS
+C6E1;C6E1;110B 116F 11BC;C6E1;110B 116F 11BC; # (웡; 웡; 웡; 웡; 웡; ) HANGUL SYLLABLE WEONG
+C6E2;C6E2;110B 116F 11BD;C6E2;110B 116F 11BD; # (웢; 웢; 웢; 웢; 웢; ) HANGUL SYLLABLE WEOJ
+C6E3;C6E3;110B 116F 11BE;C6E3;110B 116F 11BE; # (웣; 웣; 웣; 웣; 웣; ) HANGUL SYLLABLE WEOC
+C6E4;C6E4;110B 116F 11BF;C6E4;110B 116F 11BF; # (웤; 웤; 웤; 웤; 웤; ) HANGUL SYLLABLE WEOK
+C6E5;C6E5;110B 116F 11C0;C6E5;110B 116F 11C0; # (웥; 웥; 웥; 웥; 웥; ) HANGUL SYLLABLE WEOT
+C6E6;C6E6;110B 116F 11C1;C6E6;110B 116F 11C1; # (웦; 웦; á„‹á…¯á‡; 웦; á„‹á…¯á‡; ) HANGUL SYLLABLE WEOP
+C6E7;C6E7;110B 116F 11C2;C6E7;110B 116F 11C2; # (웧; 웧; 웧; 웧; 웧; ) HANGUL SYLLABLE WEOH
+C6E8;C6E8;110B 1170;C6E8;110B 1170; # (웨; 웨; 웨; 웨; 웨; ) HANGUL SYLLABLE WE
+C6E9;C6E9;110B 1170 11A8;C6E9;110B 1170 11A8; # (웩; 웩; 웩; 웩; 웩; ) HANGUL SYLLABLE WEG
+C6EA;C6EA;110B 1170 11A9;C6EA;110B 1170 11A9; # (웪; 웪; 웪; 웪; 웪; ) HANGUL SYLLABLE WEGG
+C6EB;C6EB;110B 1170 11AA;C6EB;110B 1170 11AA; # (웫; 웫; 웫; 웫; 웫; ) HANGUL SYLLABLE WEGS
+C6EC;C6EC;110B 1170 11AB;C6EC;110B 1170 11AB; # (웬; 웬; 웬; 웬; 웬; ) HANGUL SYLLABLE WEN
+C6ED;C6ED;110B 1170 11AC;C6ED;110B 1170 11AC; # (웭; 웭; 웭; 웭; 웭; ) HANGUL SYLLABLE WENJ
+C6EE;C6EE;110B 1170 11AD;C6EE;110B 1170 11AD; # (웮; 웮; 웮; 웮; 웮; ) HANGUL SYLLABLE WENH
+C6EF;C6EF;110B 1170 11AE;C6EF;110B 1170 11AE; # (웯; 웯; 웯; 웯; 웯; ) HANGUL SYLLABLE WED
+C6F0;C6F0;110B 1170 11AF;C6F0;110B 1170 11AF; # (웰; 웰; 웰; 웰; 웰; ) HANGUL SYLLABLE WEL
+C6F1;C6F1;110B 1170 11B0;C6F1;110B 1170 11B0; # (웱; 웱; 웱; 웱; 웱; ) HANGUL SYLLABLE WELG
+C6F2;C6F2;110B 1170 11B1;C6F2;110B 1170 11B1; # (웲; 웲; 웲; 웲; 웲; ) HANGUL SYLLABLE WELM
+C6F3;C6F3;110B 1170 11B2;C6F3;110B 1170 11B2; # (웳; 웳; 웳; 웳; 웳; ) HANGUL SYLLABLE WELB
+C6F4;C6F4;110B 1170 11B3;C6F4;110B 1170 11B3; # (웴; 웴; 웴; 웴; 웴; ) HANGUL SYLLABLE WELS
+C6F5;C6F5;110B 1170 11B4;C6F5;110B 1170 11B4; # (웵; 웵; 웵; 웵; 웵; ) HANGUL SYLLABLE WELT
+C6F6;C6F6;110B 1170 11B5;C6F6;110B 1170 11B5; # (웶; 웶; 웶; 웶; 웶; ) HANGUL SYLLABLE WELP
+C6F7;C6F7;110B 1170 11B6;C6F7;110B 1170 11B6; # (웷; 웷; 웷; 웷; 웷; ) HANGUL SYLLABLE WELH
+C6F8;C6F8;110B 1170 11B7;C6F8;110B 1170 11B7; # (웸; 웸; 웸; 웸; 웸; ) HANGUL SYLLABLE WEM
+C6F9;C6F9;110B 1170 11B8;C6F9;110B 1170 11B8; # (웹; 웹; 웹; 웹; 웹; ) HANGUL SYLLABLE WEB
+C6FA;C6FA;110B 1170 11B9;C6FA;110B 1170 11B9; # (웺; 웺; 웺; 웺; 웺; ) HANGUL SYLLABLE WEBS
+C6FB;C6FB;110B 1170 11BA;C6FB;110B 1170 11BA; # (웻; 웻; 웻; 웻; 웻; ) HANGUL SYLLABLE WES
+C6FC;C6FC;110B 1170 11BB;C6FC;110B 1170 11BB; # (웼; 웼; 웼; 웼; 웼; ) HANGUL SYLLABLE WESS
+C6FD;C6FD;110B 1170 11BC;C6FD;110B 1170 11BC; # (웽; 웽; 웽; 웽; 웽; ) HANGUL SYLLABLE WENG
+C6FE;C6FE;110B 1170 11BD;C6FE;110B 1170 11BD; # (웾; 웾; 웾; 웾; 웾; ) HANGUL SYLLABLE WEJ
+C6FF;C6FF;110B 1170 11BE;C6FF;110B 1170 11BE; # (웿; 웿; 웿; 웿; 웿; ) HANGUL SYLLABLE WEC
+C700;C700;110B 1170 11BF;C700;110B 1170 11BF; # (윀; 윀; 윀; 윀; 윀; ) HANGUL SYLLABLE WEK
+C701;C701;110B 1170 11C0;C701;110B 1170 11C0; # (ìœ; ìœ; 윁; ìœ; 윁; ) HANGUL SYLLABLE WET
+C702;C702;110B 1170 11C1;C702;110B 1170 11C1; # (윂; 윂; á„‹á…°á‡; 윂; á„‹á…°á‡; ) HANGUL SYLLABLE WEP
+C703;C703;110B 1170 11C2;C703;110B 1170 11C2; # (윃; 윃; 윃; 윃; 윃; ) HANGUL SYLLABLE WEH
+C704;C704;110B 1171;C704;110B 1171; # (위; 위; 위; 위; 위; ) HANGUL SYLLABLE WI
+C705;C705;110B 1171 11A8;C705;110B 1171 11A8; # (윅; 윅; 윅; 윅; 윅; ) HANGUL SYLLABLE WIG
+C706;C706;110B 1171 11A9;C706;110B 1171 11A9; # (윆; 윆; 윆; 윆; 윆; ) HANGUL SYLLABLE WIGG
+C707;C707;110B 1171 11AA;C707;110B 1171 11AA; # (윇; 윇; 윇; 윇; 윇; ) HANGUL SYLLABLE WIGS
+C708;C708;110B 1171 11AB;C708;110B 1171 11AB; # (윈; 윈; 윈; 윈; 윈; ) HANGUL SYLLABLE WIN
+C709;C709;110B 1171 11AC;C709;110B 1171 11AC; # (윉; 윉; 윉; 윉; 윉; ) HANGUL SYLLABLE WINJ
+C70A;C70A;110B 1171 11AD;C70A;110B 1171 11AD; # (윊; 윊; 윊; 윊; 윊; ) HANGUL SYLLABLE WINH
+C70B;C70B;110B 1171 11AE;C70B;110B 1171 11AE; # (윋; 윋; 윋; 윋; 윋; ) HANGUL SYLLABLE WID
+C70C;C70C;110B 1171 11AF;C70C;110B 1171 11AF; # (윌; 윌; 윌; 윌; 윌; ) HANGUL SYLLABLE WIL
+C70D;C70D;110B 1171 11B0;C70D;110B 1171 11B0; # (ìœ; ìœ; 윍; ìœ; 윍; ) HANGUL SYLLABLE WILG
+C70E;C70E;110B 1171 11B1;C70E;110B 1171 11B1; # (윎; 윎; 윎; 윎; 윎; ) HANGUL SYLLABLE WILM
+C70F;C70F;110B 1171 11B2;C70F;110B 1171 11B2; # (ìœ; ìœ; 윏; ìœ; 윏; ) HANGUL SYLLABLE WILB
+C710;C710;110B 1171 11B3;C710;110B 1171 11B3; # (ìœ; ìœ; 윐; ìœ; 윐; ) HANGUL SYLLABLE WILS
+C711;C711;110B 1171 11B4;C711;110B 1171 11B4; # (윑; 윑; 윑; 윑; 윑; ) HANGUL SYLLABLE WILT
+C712;C712;110B 1171 11B5;C712;110B 1171 11B5; # (윒; 윒; 윒; 윒; 윒; ) HANGUL SYLLABLE WILP
+C713;C713;110B 1171 11B6;C713;110B 1171 11B6; # (윓; 윓; 윓; 윓; 윓; ) HANGUL SYLLABLE WILH
+C714;C714;110B 1171 11B7;C714;110B 1171 11B7; # (윔; 윔; 윔; 윔; 윔; ) HANGUL SYLLABLE WIM
+C715;C715;110B 1171 11B8;C715;110B 1171 11B8; # (윕; 윕; 윕; 윕; 윕; ) HANGUL SYLLABLE WIB
+C716;C716;110B 1171 11B9;C716;110B 1171 11B9; # (윖; 윖; 윖; 윖; 윖; ) HANGUL SYLLABLE WIBS
+C717;C717;110B 1171 11BA;C717;110B 1171 11BA; # (윗; 윗; 윗; 윗; 윗; ) HANGUL SYLLABLE WIS
+C718;C718;110B 1171 11BB;C718;110B 1171 11BB; # (윘; 윘; 윘; 윘; 윘; ) HANGUL SYLLABLE WISS
+C719;C719;110B 1171 11BC;C719;110B 1171 11BC; # (윙; 윙; 윙; 윙; 윙; ) HANGUL SYLLABLE WING
+C71A;C71A;110B 1171 11BD;C71A;110B 1171 11BD; # (윚; 윚; 윚; 윚; 윚; ) HANGUL SYLLABLE WIJ
+C71B;C71B;110B 1171 11BE;C71B;110B 1171 11BE; # (윛; 윛; 윛; 윛; 윛; ) HANGUL SYLLABLE WIC
+C71C;C71C;110B 1171 11BF;C71C;110B 1171 11BF; # (윜; 윜; 윜; 윜; 윜; ) HANGUL SYLLABLE WIK
+C71D;C71D;110B 1171 11C0;C71D;110B 1171 11C0; # (ìœ; ìœ; 윝; ìœ; 윝; ) HANGUL SYLLABLE WIT
+C71E;C71E;110B 1171 11C1;C71E;110B 1171 11C1; # (윞; 윞; á„‹á…±á‡; 윞; á„‹á…±á‡; ) HANGUL SYLLABLE WIP
+C71F;C71F;110B 1171 11C2;C71F;110B 1171 11C2; # (윟; 윟; 윟; 윟; 윟; ) HANGUL SYLLABLE WIH
+C720;C720;110B 1172;C720;110B 1172; # (유; 유; 유; 유; 유; ) HANGUL SYLLABLE YU
+C721;C721;110B 1172 11A8;C721;110B 1172 11A8; # (육; 육; 육; 육; 육; ) HANGUL SYLLABLE YUG
+C722;C722;110B 1172 11A9;C722;110B 1172 11A9; # (윢; 윢; 윢; 윢; 윢; ) HANGUL SYLLABLE YUGG
+C723;C723;110B 1172 11AA;C723;110B 1172 11AA; # (윣; 윣; 윣; 윣; 윣; ) HANGUL SYLLABLE YUGS
+C724;C724;110B 1172 11AB;C724;110B 1172 11AB; # (윤; 윤; 윤; 윤; 윤; ) HANGUL SYLLABLE YUN
+C725;C725;110B 1172 11AC;C725;110B 1172 11AC; # (윥; 윥; 윥; 윥; 윥; ) HANGUL SYLLABLE YUNJ
+C726;C726;110B 1172 11AD;C726;110B 1172 11AD; # (윦; 윦; 윦; 윦; 윦; ) HANGUL SYLLABLE YUNH
+C727;C727;110B 1172 11AE;C727;110B 1172 11AE; # (윧; 윧; 윧; 윧; 윧; ) HANGUL SYLLABLE YUD
+C728;C728;110B 1172 11AF;C728;110B 1172 11AF; # (율; 율; 율; 율; 율; ) HANGUL SYLLABLE YUL
+C729;C729;110B 1172 11B0;C729;110B 1172 11B0; # (윩; 윩; 윩; 윩; 윩; ) HANGUL SYLLABLE YULG
+C72A;C72A;110B 1172 11B1;C72A;110B 1172 11B1; # (윪; 윪; 윪; 윪; 윪; ) HANGUL SYLLABLE YULM
+C72B;C72B;110B 1172 11B2;C72B;110B 1172 11B2; # (윫; 윫; 윫; 윫; 윫; ) HANGUL SYLLABLE YULB
+C72C;C72C;110B 1172 11B3;C72C;110B 1172 11B3; # (윬; 윬; 윬; 윬; 윬; ) HANGUL SYLLABLE YULS
+C72D;C72D;110B 1172 11B4;C72D;110B 1172 11B4; # (윭; 윭; 윭; 윭; 윭; ) HANGUL SYLLABLE YULT
+C72E;C72E;110B 1172 11B5;C72E;110B 1172 11B5; # (윮; 윮; 윮; 윮; 윮; ) HANGUL SYLLABLE YULP
+C72F;C72F;110B 1172 11B6;C72F;110B 1172 11B6; # (윯; 윯; 윯; 윯; 윯; ) HANGUL SYLLABLE YULH
+C730;C730;110B 1172 11B7;C730;110B 1172 11B7; # (윰; 윰; 윰; 윰; 윰; ) HANGUL SYLLABLE YUM
+C731;C731;110B 1172 11B8;C731;110B 1172 11B8; # (윱; 윱; 윱; 윱; 윱; ) HANGUL SYLLABLE YUB
+C732;C732;110B 1172 11B9;C732;110B 1172 11B9; # (윲; 윲; 윲; 윲; 윲; ) HANGUL SYLLABLE YUBS
+C733;C733;110B 1172 11BA;C733;110B 1172 11BA; # (윳; 윳; 윳; 윳; 윳; ) HANGUL SYLLABLE YUS
+C734;C734;110B 1172 11BB;C734;110B 1172 11BB; # (윴; 윴; 윴; 윴; 윴; ) HANGUL SYLLABLE YUSS
+C735;C735;110B 1172 11BC;C735;110B 1172 11BC; # (융; 융; 융; 융; 융; ) HANGUL SYLLABLE YUNG
+C736;C736;110B 1172 11BD;C736;110B 1172 11BD; # (윶; 윶; 윶; 윶; 윶; ) HANGUL SYLLABLE YUJ
+C737;C737;110B 1172 11BE;C737;110B 1172 11BE; # (윷; 윷; 윷; 윷; 윷; ) HANGUL SYLLABLE YUC
+C738;C738;110B 1172 11BF;C738;110B 1172 11BF; # (윸; 윸; 윸; 윸; 윸; ) HANGUL SYLLABLE YUK
+C739;C739;110B 1172 11C0;C739;110B 1172 11C0; # (윹; 윹; 윹; 윹; 윹; ) HANGUL SYLLABLE YUT
+C73A;C73A;110B 1172 11C1;C73A;110B 1172 11C1; # (윺; 윺; á„‹á…²á‡; 윺; á„‹á…²á‡; ) HANGUL SYLLABLE YUP
+C73B;C73B;110B 1172 11C2;C73B;110B 1172 11C2; # (윻; 윻; 윻; 윻; 윻; ) HANGUL SYLLABLE YUH
+C73C;C73C;110B 1173;C73C;110B 1173; # (으; 으; 으; 으; 으; ) HANGUL SYLLABLE EU
+C73D;C73D;110B 1173 11A8;C73D;110B 1173 11A8; # (윽; 윽; 윽; 윽; 윽; ) HANGUL SYLLABLE EUG
+C73E;C73E;110B 1173 11A9;C73E;110B 1173 11A9; # (윾; 윾; 윾; 윾; 윾; ) HANGUL SYLLABLE EUGG
+C73F;C73F;110B 1173 11AA;C73F;110B 1173 11AA; # (윿; 윿; 윿; 윿; 윿; ) HANGUL SYLLABLE EUGS
+C740;C740;110B 1173 11AB;C740;110B 1173 11AB; # (ì€; ì€; 은; ì€; 은; ) HANGUL SYLLABLE EUN
+C741;C741;110B 1173 11AC;C741;110B 1173 11AC; # (ì; ì; 읁; ì; 읁; ) HANGUL SYLLABLE EUNJ
+C742;C742;110B 1173 11AD;C742;110B 1173 11AD; # (ì‚; ì‚; 읂; ì‚; 읂; ) HANGUL SYLLABLE EUNH
+C743;C743;110B 1173 11AE;C743;110B 1173 11AE; # (ìƒ; ìƒ; 읃; ìƒ; 읃; ) HANGUL SYLLABLE EUD
+C744;C744;110B 1173 11AF;C744;110B 1173 11AF; # (ì„; ì„; 을; ì„; 을; ) HANGUL SYLLABLE EUL
+C745;C745;110B 1173 11B0;C745;110B 1173 11B0; # (ì…; ì…; 읅; ì…; 읅; ) HANGUL SYLLABLE EULG
+C746;C746;110B 1173 11B1;C746;110B 1173 11B1; # (ì†; ì†; 읆; ì†; 읆; ) HANGUL SYLLABLE EULM
+C747;C747;110B 1173 11B2;C747;110B 1173 11B2; # (ì‡; ì‡; 읇; ì‡; 읇; ) HANGUL SYLLABLE EULB
+C748;C748;110B 1173 11B3;C748;110B 1173 11B3; # (ìˆ; ìˆ; 읈; ìˆ; 읈; ) HANGUL SYLLABLE EULS
+C749;C749;110B 1173 11B4;C749;110B 1173 11B4; # (ì‰; ì‰; 읉; ì‰; 읉; ) HANGUL SYLLABLE EULT
+C74A;C74A;110B 1173 11B5;C74A;110B 1173 11B5; # (ìŠ; ìŠ; 읊; ìŠ; 읊; ) HANGUL SYLLABLE EULP
+C74B;C74B;110B 1173 11B6;C74B;110B 1173 11B6; # (ì‹; ì‹; 읋; ì‹; 읋; ) HANGUL SYLLABLE EULH
+C74C;C74C;110B 1173 11B7;C74C;110B 1173 11B7; # (ìŒ; ìŒ; 음; ìŒ; 음; ) HANGUL SYLLABLE EUM
+C74D;C74D;110B 1173 11B8;C74D;110B 1173 11B8; # (ì; ì; 읍; ì; 읍; ) HANGUL SYLLABLE EUB
+C74E;C74E;110B 1173 11B9;C74E;110B 1173 11B9; # (ìŽ; ìŽ; 읎; ìŽ; 읎; ) HANGUL SYLLABLE EUBS
+C74F;C74F;110B 1173 11BA;C74F;110B 1173 11BA; # (ì; ì; 읏; ì; 읏; ) HANGUL SYLLABLE EUS
+C750;C750;110B 1173 11BB;C750;110B 1173 11BB; # (ì; ì; 읐; ì; 읐; ) HANGUL SYLLABLE EUSS
+C751;C751;110B 1173 11BC;C751;110B 1173 11BC; # (ì‘; ì‘; 응; ì‘; 응; ) HANGUL SYLLABLE EUNG
+C752;C752;110B 1173 11BD;C752;110B 1173 11BD; # (ì’; ì’; 읒; ì’; 읒; ) HANGUL SYLLABLE EUJ
+C753;C753;110B 1173 11BE;C753;110B 1173 11BE; # (ì“; ì“; 읓; ì“; 읓; ) HANGUL SYLLABLE EUC
+C754;C754;110B 1173 11BF;C754;110B 1173 11BF; # (ì”; ì”; 읔; ì”; 읔; ) HANGUL SYLLABLE EUK
+C755;C755;110B 1173 11C0;C755;110B 1173 11C0; # (ì•; ì•; 읕; ì•; 읕; ) HANGUL SYLLABLE EUT
+C756;C756;110B 1173 11C1;C756;110B 1173 11C1; # (ì–; ì–; á„‹á…³á‡; ì–; á„‹á…³á‡; ) HANGUL SYLLABLE EUP
+C757;C757;110B 1173 11C2;C757;110B 1173 11C2; # (ì—; ì—; 읗; ì—; 읗; ) HANGUL SYLLABLE EUH
+C758;C758;110B 1174;C758;110B 1174; # (ì˜; ì˜; á„‹á…´; ì˜; á„‹á…´; ) HANGUL SYLLABLE YI
+C759;C759;110B 1174 11A8;C759;110B 1174 11A8; # (ì™; ì™; 읙; ì™; 읙; ) HANGUL SYLLABLE YIG
+C75A;C75A;110B 1174 11A9;C75A;110B 1174 11A9; # (ìš; ìš; 읚; ìš; 읚; ) HANGUL SYLLABLE YIGG
+C75B;C75B;110B 1174 11AA;C75B;110B 1174 11AA; # (ì›; ì›; 읛; ì›; 읛; ) HANGUL SYLLABLE YIGS
+C75C;C75C;110B 1174 11AB;C75C;110B 1174 11AB; # (ìœ; ìœ; 읜; ìœ; 읜; ) HANGUL SYLLABLE YIN
+C75D;C75D;110B 1174 11AC;C75D;110B 1174 11AC; # (ì; ì; 읝; ì; 읝; ) HANGUL SYLLABLE YINJ
+C75E;C75E;110B 1174 11AD;C75E;110B 1174 11AD; # (ìž; ìž; 읞; ìž; 읞; ) HANGUL SYLLABLE YINH
+C75F;C75F;110B 1174 11AE;C75F;110B 1174 11AE; # (ìŸ; ìŸ; 읟; ìŸ; 읟; ) HANGUL SYLLABLE YID
+C760;C760;110B 1174 11AF;C760;110B 1174 11AF; # (ì ; ì ; 읠; ì ; 읠; ) HANGUL SYLLABLE YIL
+C761;C761;110B 1174 11B0;C761;110B 1174 11B0; # (ì¡; ì¡; 읡; ì¡; 읡; ) HANGUL SYLLABLE YILG
+C762;C762;110B 1174 11B1;C762;110B 1174 11B1; # (ì¢; ì¢; 읢; ì¢; 읢; ) HANGUL SYLLABLE YILM
+C763;C763;110B 1174 11B2;C763;110B 1174 11B2; # (ì£; ì£; 읣; ì£; 읣; ) HANGUL SYLLABLE YILB
+C764;C764;110B 1174 11B3;C764;110B 1174 11B3; # (ì¤; ì¤; 읤; ì¤; 읤; ) HANGUL SYLLABLE YILS
+C765;C765;110B 1174 11B4;C765;110B 1174 11B4; # (ì¥; ì¥; 읥; ì¥; 읥; ) HANGUL SYLLABLE YILT
+C766;C766;110B 1174 11B5;C766;110B 1174 11B5; # (ì¦; ì¦; 읦; ì¦; 읦; ) HANGUL SYLLABLE YILP
+C767;C767;110B 1174 11B6;C767;110B 1174 11B6; # (ì§; ì§; 읧; ì§; 읧; ) HANGUL SYLLABLE YILH
+C768;C768;110B 1174 11B7;C768;110B 1174 11B7; # (ì¨; ì¨; 읨; ì¨; 읨; ) HANGUL SYLLABLE YIM
+C769;C769;110B 1174 11B8;C769;110B 1174 11B8; # (ì©; ì©; 읩; ì©; 읩; ) HANGUL SYLLABLE YIB
+C76A;C76A;110B 1174 11B9;C76A;110B 1174 11B9; # (ìª; ìª; 읪; ìª; 읪; ) HANGUL SYLLABLE YIBS
+C76B;C76B;110B 1174 11BA;C76B;110B 1174 11BA; # (ì«; ì«; 읫; ì«; 읫; ) HANGUL SYLLABLE YIS
+C76C;C76C;110B 1174 11BB;C76C;110B 1174 11BB; # (ì¬; ì¬; 읬; ì¬; 읬; ) HANGUL SYLLABLE YISS
+C76D;C76D;110B 1174 11BC;C76D;110B 1174 11BC; # (ì­; ì­; 읭; ì­; 읭; ) HANGUL SYLLABLE YING
+C76E;C76E;110B 1174 11BD;C76E;110B 1174 11BD; # (ì®; ì®; 읮; ì®; 읮; ) HANGUL SYLLABLE YIJ
+C76F;C76F;110B 1174 11BE;C76F;110B 1174 11BE; # (ì¯; ì¯; 읯; ì¯; 읯; ) HANGUL SYLLABLE YIC
+C770;C770;110B 1174 11BF;C770;110B 1174 11BF; # (ì°; ì°; 읰; ì°; 읰; ) HANGUL SYLLABLE YIK
+C771;C771;110B 1174 11C0;C771;110B 1174 11C0; # (ì±; ì±; 읱; ì±; 읱; ) HANGUL SYLLABLE YIT
+C772;C772;110B 1174 11C1;C772;110B 1174 11C1; # (ì²; ì²; á„‹á…´á‡; ì²; á„‹á…´á‡; ) HANGUL SYLLABLE YIP
+C773;C773;110B 1174 11C2;C773;110B 1174 11C2; # (ì³; ì³; 읳; ì³; 읳; ) HANGUL SYLLABLE YIH
+C774;C774;110B 1175;C774;110B 1175; # (ì´; ì´; á„‹á…µ; ì´; á„‹á…µ; ) HANGUL SYLLABLE I
+C775;C775;110B 1175 11A8;C775;110B 1175 11A8; # (ìµ; ìµ; 익; ìµ; 익; ) HANGUL SYLLABLE IG
+C776;C776;110B 1175 11A9;C776;110B 1175 11A9; # (ì¶; ì¶; 읶; ì¶; 읶; ) HANGUL SYLLABLE IGG
+C777;C777;110B 1175 11AA;C777;110B 1175 11AA; # (ì·; ì·; 읷; ì·; 읷; ) HANGUL SYLLABLE IGS
+C778;C778;110B 1175 11AB;C778;110B 1175 11AB; # (ì¸; ì¸; 인; ì¸; 인; ) HANGUL SYLLABLE IN
+C779;C779;110B 1175 11AC;C779;110B 1175 11AC; # (ì¹; ì¹; 읹; ì¹; 읹; ) HANGUL SYLLABLE INJ
+C77A;C77A;110B 1175 11AD;C77A;110B 1175 11AD; # (ìº; ìº; 읺; ìº; 읺; ) HANGUL SYLLABLE INH
+C77B;C77B;110B 1175 11AE;C77B;110B 1175 11AE; # (ì»; ì»; 읻; ì»; 읻; ) HANGUL SYLLABLE ID
+C77C;C77C;110B 1175 11AF;C77C;110B 1175 11AF; # (ì¼; ì¼; 일; ì¼; 일; ) HANGUL SYLLABLE IL
+C77D;C77D;110B 1175 11B0;C77D;110B 1175 11B0; # (ì½; ì½; 읽; ì½; 읽; ) HANGUL SYLLABLE ILG
+C77E;C77E;110B 1175 11B1;C77E;110B 1175 11B1; # (ì¾; ì¾; 읾; ì¾; 읾; ) HANGUL SYLLABLE ILM
+C77F;C77F;110B 1175 11B2;C77F;110B 1175 11B2; # (ì¿; ì¿; 읿; ì¿; 읿; ) HANGUL SYLLABLE ILB
+C780;C780;110B 1175 11B3;C780;110B 1175 11B3; # (잀; 잀; 잀; 잀; 잀; ) HANGUL SYLLABLE ILS
+C781;C781;110B 1175 11B4;C781;110B 1175 11B4; # (ìž; ìž; 잁; ìž; 잁; ) HANGUL SYLLABLE ILT
+C782;C782;110B 1175 11B5;C782;110B 1175 11B5; # (잂; 잂; 잂; 잂; 잂; ) HANGUL SYLLABLE ILP
+C783;C783;110B 1175 11B6;C783;110B 1175 11B6; # (잃; 잃; 잃; 잃; 잃; ) HANGUL SYLLABLE ILH
+C784;C784;110B 1175 11B7;C784;110B 1175 11B7; # (임; 임; 임; 임; 임; ) HANGUL SYLLABLE IM
+C785;C785;110B 1175 11B8;C785;110B 1175 11B8; # (입; 입; 입; 입; 입; ) HANGUL SYLLABLE IB
+C786;C786;110B 1175 11B9;C786;110B 1175 11B9; # (잆; 잆; 잆; 잆; 잆; ) HANGUL SYLLABLE IBS
+C787;C787;110B 1175 11BA;C787;110B 1175 11BA; # (잇; 잇; 잇; 잇; 잇; ) HANGUL SYLLABLE IS
+C788;C788;110B 1175 11BB;C788;110B 1175 11BB; # (있; 있; 있; 있; 있; ) HANGUL SYLLABLE ISS
+C789;C789;110B 1175 11BC;C789;110B 1175 11BC; # (잉; 잉; 잉; 잉; 잉; ) HANGUL SYLLABLE ING
+C78A;C78A;110B 1175 11BD;C78A;110B 1175 11BD; # (잊; 잊; 잊; 잊; 잊; ) HANGUL SYLLABLE IJ
+C78B;C78B;110B 1175 11BE;C78B;110B 1175 11BE; # (잋; 잋; 잋; 잋; 잋; ) HANGUL SYLLABLE IC
+C78C;C78C;110B 1175 11BF;C78C;110B 1175 11BF; # (잌; 잌; 잌; 잌; 잌; ) HANGUL SYLLABLE IK
+C78D;C78D;110B 1175 11C0;C78D;110B 1175 11C0; # (ìž; ìž; 잍; ìž; 잍; ) HANGUL SYLLABLE IT
+C78E;C78E;110B 1175 11C1;C78E;110B 1175 11C1; # (잎; 잎; á„‹á…µá‡; 잎; á„‹á…µá‡; ) HANGUL SYLLABLE IP
+C78F;C78F;110B 1175 11C2;C78F;110B 1175 11C2; # (ìž; ìž; 잏; ìž; 잏; ) HANGUL SYLLABLE IH
+C790;C790;110C 1161;C790;110C 1161; # (ìž; ìž; 자; ìž; 자; ) HANGUL SYLLABLE JA
+C791;C791;110C 1161 11A8;C791;110C 1161 11A8; # (작; 작; 작; 작; 작; ) HANGUL SYLLABLE JAG
+C792;C792;110C 1161 11A9;C792;110C 1161 11A9; # (잒; 잒; 잒; 잒; 잒; ) HANGUL SYLLABLE JAGG
+C793;C793;110C 1161 11AA;C793;110C 1161 11AA; # (잓; 잓; 잓; 잓; 잓; ) HANGUL SYLLABLE JAGS
+C794;C794;110C 1161 11AB;C794;110C 1161 11AB; # (잔; 잔; 잔; 잔; 잔; ) HANGUL SYLLABLE JAN
+C795;C795;110C 1161 11AC;C795;110C 1161 11AC; # (잕; 잕; 잕; 잕; 잕; ) HANGUL SYLLABLE JANJ
+C796;C796;110C 1161 11AD;C796;110C 1161 11AD; # (잖; 잖; 잖; 잖; 잖; ) HANGUL SYLLABLE JANH
+C797;C797;110C 1161 11AE;C797;110C 1161 11AE; # (잗; 잗; 잗; 잗; 잗; ) HANGUL SYLLABLE JAD
+C798;C798;110C 1161 11AF;C798;110C 1161 11AF; # (잘; 잘; 잘; 잘; 잘; ) HANGUL SYLLABLE JAL
+C799;C799;110C 1161 11B0;C799;110C 1161 11B0; # (잙; 잙; 잙; 잙; 잙; ) HANGUL SYLLABLE JALG
+C79A;C79A;110C 1161 11B1;C79A;110C 1161 11B1; # (잚; 잚; 잚; 잚; 잚; ) HANGUL SYLLABLE JALM
+C79B;C79B;110C 1161 11B2;C79B;110C 1161 11B2; # (잛; 잛; 잛; 잛; 잛; ) HANGUL SYLLABLE JALB
+C79C;C79C;110C 1161 11B3;C79C;110C 1161 11B3; # (잜; 잜; 잜; 잜; 잜; ) HANGUL SYLLABLE JALS
+C79D;C79D;110C 1161 11B4;C79D;110C 1161 11B4; # (ìž; ìž; 잝; ìž; 잝; ) HANGUL SYLLABLE JALT
+C79E;C79E;110C 1161 11B5;C79E;110C 1161 11B5; # (잞; 잞; 잞; 잞; 잞; ) HANGUL SYLLABLE JALP
+C79F;C79F;110C 1161 11B6;C79F;110C 1161 11B6; # (잟; 잟; 잟; 잟; 잟; ) HANGUL SYLLABLE JALH
+C7A0;C7A0;110C 1161 11B7;C7A0;110C 1161 11B7; # (잠; 잠; 잠; 잠; 잠; ) HANGUL SYLLABLE JAM
+C7A1;C7A1;110C 1161 11B8;C7A1;110C 1161 11B8; # (잡; 잡; 잡; 잡; 잡; ) HANGUL SYLLABLE JAB
+C7A2;C7A2;110C 1161 11B9;C7A2;110C 1161 11B9; # (잢; 잢; 잢; 잢; 잢; ) HANGUL SYLLABLE JABS
+C7A3;C7A3;110C 1161 11BA;C7A3;110C 1161 11BA; # (잣; 잣; 잣; 잣; 잣; ) HANGUL SYLLABLE JAS
+C7A4;C7A4;110C 1161 11BB;C7A4;110C 1161 11BB; # (잤; 잤; 잤; 잤; 잤; ) HANGUL SYLLABLE JASS
+C7A5;C7A5;110C 1161 11BC;C7A5;110C 1161 11BC; # (장; 장; 장; 장; 장; ) HANGUL SYLLABLE JANG
+C7A6;C7A6;110C 1161 11BD;C7A6;110C 1161 11BD; # (잦; 잦; 잦; 잦; 잦; ) HANGUL SYLLABLE JAJ
+C7A7;C7A7;110C 1161 11BE;C7A7;110C 1161 11BE; # (잧; 잧; 잧; 잧; 잧; ) HANGUL SYLLABLE JAC
+C7A8;C7A8;110C 1161 11BF;C7A8;110C 1161 11BF; # (잨; 잨; 잨; 잨; 잨; ) HANGUL SYLLABLE JAK
+C7A9;C7A9;110C 1161 11C0;C7A9;110C 1161 11C0; # (잩; 잩; 잩; 잩; 잩; ) HANGUL SYLLABLE JAT
+C7AA;C7AA;110C 1161 11C1;C7AA;110C 1161 11C1; # (잪; 잪; 자á‡; 잪; 자á‡; ) HANGUL SYLLABLE JAP
+C7AB;C7AB;110C 1161 11C2;C7AB;110C 1161 11C2; # (잫; 잫; 잫; 잫; 잫; ) HANGUL SYLLABLE JAH
+C7AC;C7AC;110C 1162;C7AC;110C 1162; # (재; 재; 재; 재; 재; ) HANGUL SYLLABLE JAE
+C7AD;C7AD;110C 1162 11A8;C7AD;110C 1162 11A8; # (잭; 잭; 잭; 잭; 잭; ) HANGUL SYLLABLE JAEG
+C7AE;C7AE;110C 1162 11A9;C7AE;110C 1162 11A9; # (잮; 잮; 잮; 잮; 잮; ) HANGUL SYLLABLE JAEGG
+C7AF;C7AF;110C 1162 11AA;C7AF;110C 1162 11AA; # (잯; 잯; 잯; 잯; 잯; ) HANGUL SYLLABLE JAEGS
+C7B0;C7B0;110C 1162 11AB;C7B0;110C 1162 11AB; # (잰; 잰; 잰; 잰; 잰; ) HANGUL SYLLABLE JAEN
+C7B1;C7B1;110C 1162 11AC;C7B1;110C 1162 11AC; # (잱; 잱; 잱; 잱; 잱; ) HANGUL SYLLABLE JAENJ
+C7B2;C7B2;110C 1162 11AD;C7B2;110C 1162 11AD; # (잲; 잲; 잲; 잲; 잲; ) HANGUL SYLLABLE JAENH
+C7B3;C7B3;110C 1162 11AE;C7B3;110C 1162 11AE; # (잳; 잳; 잳; 잳; 잳; ) HANGUL SYLLABLE JAED
+C7B4;C7B4;110C 1162 11AF;C7B4;110C 1162 11AF; # (잴; 잴; 잴; 잴; 잴; ) HANGUL SYLLABLE JAEL
+C7B5;C7B5;110C 1162 11B0;C7B5;110C 1162 11B0; # (잵; 잵; 잵; 잵; 잵; ) HANGUL SYLLABLE JAELG
+C7B6;C7B6;110C 1162 11B1;C7B6;110C 1162 11B1; # (잶; 잶; 잶; 잶; 잶; ) HANGUL SYLLABLE JAELM
+C7B7;C7B7;110C 1162 11B2;C7B7;110C 1162 11B2; # (잷; 잷; 잷; 잷; 잷; ) HANGUL SYLLABLE JAELB
+C7B8;C7B8;110C 1162 11B3;C7B8;110C 1162 11B3; # (잸; 잸; 잸; 잸; 잸; ) HANGUL SYLLABLE JAELS
+C7B9;C7B9;110C 1162 11B4;C7B9;110C 1162 11B4; # (잹; 잹; 잹; 잹; 잹; ) HANGUL SYLLABLE JAELT
+C7BA;C7BA;110C 1162 11B5;C7BA;110C 1162 11B5; # (잺; 잺; 잺; 잺; 잺; ) HANGUL SYLLABLE JAELP
+C7BB;C7BB;110C 1162 11B6;C7BB;110C 1162 11B6; # (잻; 잻; 잻; 잻; 잻; ) HANGUL SYLLABLE JAELH
+C7BC;C7BC;110C 1162 11B7;C7BC;110C 1162 11B7; # (잼; 잼; 잼; 잼; 잼; ) HANGUL SYLLABLE JAEM
+C7BD;C7BD;110C 1162 11B8;C7BD;110C 1162 11B8; # (잽; 잽; 잽; 잽; 잽; ) HANGUL SYLLABLE JAEB
+C7BE;C7BE;110C 1162 11B9;C7BE;110C 1162 11B9; # (잾; 잾; 잾; 잾; 잾; ) HANGUL SYLLABLE JAEBS
+C7BF;C7BF;110C 1162 11BA;C7BF;110C 1162 11BA; # (잿; 잿; 잿; 잿; 잿; ) HANGUL SYLLABLE JAES
+C7C0;C7C0;110C 1162 11BB;C7C0;110C 1162 11BB; # (쟀; 쟀; 쟀; 쟀; 쟀; ) HANGUL SYLLABLE JAESS
+C7C1;C7C1;110C 1162 11BC;C7C1;110C 1162 11BC; # (ìŸ; ìŸ; 쟁; ìŸ; 쟁; ) HANGUL SYLLABLE JAENG
+C7C2;C7C2;110C 1162 11BD;C7C2;110C 1162 11BD; # (쟂; 쟂; 쟂; 쟂; 쟂; ) HANGUL SYLLABLE JAEJ
+C7C3;C7C3;110C 1162 11BE;C7C3;110C 1162 11BE; # (쟃; 쟃; 쟃; 쟃; 쟃; ) HANGUL SYLLABLE JAEC
+C7C4;C7C4;110C 1162 11BF;C7C4;110C 1162 11BF; # (쟄; 쟄; 쟄; 쟄; 쟄; ) HANGUL SYLLABLE JAEK
+C7C5;C7C5;110C 1162 11C0;C7C5;110C 1162 11C0; # (쟅; 쟅; 쟅; 쟅; 쟅; ) HANGUL SYLLABLE JAET
+C7C6;C7C6;110C 1162 11C1;C7C6;110C 1162 11C1; # (쟆; 쟆; 재á‡; 쟆; 재á‡; ) HANGUL SYLLABLE JAEP
+C7C7;C7C7;110C 1162 11C2;C7C7;110C 1162 11C2; # (쟇; 쟇; 쟇; 쟇; 쟇; ) HANGUL SYLLABLE JAEH
+C7C8;C7C8;110C 1163;C7C8;110C 1163; # (쟈; 쟈; 쟈; 쟈; 쟈; ) HANGUL SYLLABLE JYA
+C7C9;C7C9;110C 1163 11A8;C7C9;110C 1163 11A8; # (쟉; 쟉; 쟉; 쟉; 쟉; ) HANGUL SYLLABLE JYAG
+C7CA;C7CA;110C 1163 11A9;C7CA;110C 1163 11A9; # (쟊; 쟊; 쟊; 쟊; 쟊; ) HANGUL SYLLABLE JYAGG
+C7CB;C7CB;110C 1163 11AA;C7CB;110C 1163 11AA; # (쟋; 쟋; 쟋; 쟋; 쟋; ) HANGUL SYLLABLE JYAGS
+C7CC;C7CC;110C 1163 11AB;C7CC;110C 1163 11AB; # (쟌; 쟌; 쟌; 쟌; 쟌; ) HANGUL SYLLABLE JYAN
+C7CD;C7CD;110C 1163 11AC;C7CD;110C 1163 11AC; # (ìŸ; ìŸ; 쟍; ìŸ; 쟍; ) HANGUL SYLLABLE JYANJ
+C7CE;C7CE;110C 1163 11AD;C7CE;110C 1163 11AD; # (쟎; 쟎; 쟎; 쟎; 쟎; ) HANGUL SYLLABLE JYANH
+C7CF;C7CF;110C 1163 11AE;C7CF;110C 1163 11AE; # (ìŸ; ìŸ; 쟏; ìŸ; 쟏; ) HANGUL SYLLABLE JYAD
+C7D0;C7D0;110C 1163 11AF;C7D0;110C 1163 11AF; # (ìŸ; ìŸ; 쟐; ìŸ; 쟐; ) HANGUL SYLLABLE JYAL
+C7D1;C7D1;110C 1163 11B0;C7D1;110C 1163 11B0; # (쟑; 쟑; 쟑; 쟑; 쟑; ) HANGUL SYLLABLE JYALG
+C7D2;C7D2;110C 1163 11B1;C7D2;110C 1163 11B1; # (쟒; 쟒; 쟒; 쟒; 쟒; ) HANGUL SYLLABLE JYALM
+C7D3;C7D3;110C 1163 11B2;C7D3;110C 1163 11B2; # (쟓; 쟓; 쟓; 쟓; 쟓; ) HANGUL SYLLABLE JYALB
+C7D4;C7D4;110C 1163 11B3;C7D4;110C 1163 11B3; # (쟔; 쟔; 쟔; 쟔; 쟔; ) HANGUL SYLLABLE JYALS
+C7D5;C7D5;110C 1163 11B4;C7D5;110C 1163 11B4; # (쟕; 쟕; 쟕; 쟕; 쟕; ) HANGUL SYLLABLE JYALT
+C7D6;C7D6;110C 1163 11B5;C7D6;110C 1163 11B5; # (쟖; 쟖; 쟖; 쟖; 쟖; ) HANGUL SYLLABLE JYALP
+C7D7;C7D7;110C 1163 11B6;C7D7;110C 1163 11B6; # (쟗; 쟗; 쟗; 쟗; 쟗; ) HANGUL SYLLABLE JYALH
+C7D8;C7D8;110C 1163 11B7;C7D8;110C 1163 11B7; # (쟘; 쟘; 쟘; 쟘; 쟘; ) HANGUL SYLLABLE JYAM
+C7D9;C7D9;110C 1163 11B8;C7D9;110C 1163 11B8; # (쟙; 쟙; 쟙; 쟙; 쟙; ) HANGUL SYLLABLE JYAB
+C7DA;C7DA;110C 1163 11B9;C7DA;110C 1163 11B9; # (쟚; 쟚; 쟚; 쟚; 쟚; ) HANGUL SYLLABLE JYABS
+C7DB;C7DB;110C 1163 11BA;C7DB;110C 1163 11BA; # (쟛; 쟛; 쟛; 쟛; 쟛; ) HANGUL SYLLABLE JYAS
+C7DC;C7DC;110C 1163 11BB;C7DC;110C 1163 11BB; # (쟜; 쟜; 쟜; 쟜; 쟜; ) HANGUL SYLLABLE JYASS
+C7DD;C7DD;110C 1163 11BC;C7DD;110C 1163 11BC; # (ìŸ; ìŸ; 쟝; ìŸ; 쟝; ) HANGUL SYLLABLE JYANG
+C7DE;C7DE;110C 1163 11BD;C7DE;110C 1163 11BD; # (쟞; 쟞; 쟞; 쟞; 쟞; ) HANGUL SYLLABLE JYAJ
+C7DF;C7DF;110C 1163 11BE;C7DF;110C 1163 11BE; # (쟟; 쟟; 쟟; 쟟; 쟟; ) HANGUL SYLLABLE JYAC
+C7E0;C7E0;110C 1163 11BF;C7E0;110C 1163 11BF; # (쟠; 쟠; 쟠; 쟠; 쟠; ) HANGUL SYLLABLE JYAK
+C7E1;C7E1;110C 1163 11C0;C7E1;110C 1163 11C0; # (쟡; 쟡; 쟡; 쟡; 쟡; ) HANGUL SYLLABLE JYAT
+C7E2;C7E2;110C 1163 11C1;C7E2;110C 1163 11C1; # (쟢; 쟢; 쟈á‡; 쟢; 쟈á‡; ) HANGUL SYLLABLE JYAP
+C7E3;C7E3;110C 1163 11C2;C7E3;110C 1163 11C2; # (쟣; 쟣; 쟣; 쟣; 쟣; ) HANGUL SYLLABLE JYAH
+C7E4;C7E4;110C 1164;C7E4;110C 1164; # (쟤; 쟤; 쟤; 쟤; 쟤; ) HANGUL SYLLABLE JYAE
+C7E5;C7E5;110C 1164 11A8;C7E5;110C 1164 11A8; # (쟥; 쟥; 쟥; 쟥; 쟥; ) HANGUL SYLLABLE JYAEG
+C7E6;C7E6;110C 1164 11A9;C7E6;110C 1164 11A9; # (쟦; 쟦; 쟦; 쟦; 쟦; ) HANGUL SYLLABLE JYAEGG
+C7E7;C7E7;110C 1164 11AA;C7E7;110C 1164 11AA; # (쟧; 쟧; 쟧; 쟧; 쟧; ) HANGUL SYLLABLE JYAEGS
+C7E8;C7E8;110C 1164 11AB;C7E8;110C 1164 11AB; # (쟨; 쟨; 쟨; 쟨; 쟨; ) HANGUL SYLLABLE JYAEN
+C7E9;C7E9;110C 1164 11AC;C7E9;110C 1164 11AC; # (쟩; 쟩; 쟩; 쟩; 쟩; ) HANGUL SYLLABLE JYAENJ
+C7EA;C7EA;110C 1164 11AD;C7EA;110C 1164 11AD; # (쟪; 쟪; 쟪; 쟪; 쟪; ) HANGUL SYLLABLE JYAENH
+C7EB;C7EB;110C 1164 11AE;C7EB;110C 1164 11AE; # (쟫; 쟫; 쟫; 쟫; 쟫; ) HANGUL SYLLABLE JYAED
+C7EC;C7EC;110C 1164 11AF;C7EC;110C 1164 11AF; # (쟬; 쟬; 쟬; 쟬; 쟬; ) HANGUL SYLLABLE JYAEL
+C7ED;C7ED;110C 1164 11B0;C7ED;110C 1164 11B0; # (쟭; 쟭; 쟭; 쟭; 쟭; ) HANGUL SYLLABLE JYAELG
+C7EE;C7EE;110C 1164 11B1;C7EE;110C 1164 11B1; # (쟮; 쟮; 쟮; 쟮; 쟮; ) HANGUL SYLLABLE JYAELM
+C7EF;C7EF;110C 1164 11B2;C7EF;110C 1164 11B2; # (쟯; 쟯; 쟯; 쟯; 쟯; ) HANGUL SYLLABLE JYAELB
+C7F0;C7F0;110C 1164 11B3;C7F0;110C 1164 11B3; # (쟰; 쟰; 쟰; 쟰; 쟰; ) HANGUL SYLLABLE JYAELS
+C7F1;C7F1;110C 1164 11B4;C7F1;110C 1164 11B4; # (쟱; 쟱; 쟱; 쟱; 쟱; ) HANGUL SYLLABLE JYAELT
+C7F2;C7F2;110C 1164 11B5;C7F2;110C 1164 11B5; # (쟲; 쟲; 쟲; 쟲; 쟲; ) HANGUL SYLLABLE JYAELP
+C7F3;C7F3;110C 1164 11B6;C7F3;110C 1164 11B6; # (쟳; 쟳; 쟳; 쟳; 쟳; ) HANGUL SYLLABLE JYAELH
+C7F4;C7F4;110C 1164 11B7;C7F4;110C 1164 11B7; # (쟴; 쟴; 쟴; 쟴; 쟴; ) HANGUL SYLLABLE JYAEM
+C7F5;C7F5;110C 1164 11B8;C7F5;110C 1164 11B8; # (쟵; 쟵; 쟵; 쟵; 쟵; ) HANGUL SYLLABLE JYAEB
+C7F6;C7F6;110C 1164 11B9;C7F6;110C 1164 11B9; # (쟶; 쟶; 쟶; 쟶; 쟶; ) HANGUL SYLLABLE JYAEBS
+C7F7;C7F7;110C 1164 11BA;C7F7;110C 1164 11BA; # (쟷; 쟷; 쟷; 쟷; 쟷; ) HANGUL SYLLABLE JYAES
+C7F8;C7F8;110C 1164 11BB;C7F8;110C 1164 11BB; # (쟸; 쟸; 쟸; 쟸; 쟸; ) HANGUL SYLLABLE JYAESS
+C7F9;C7F9;110C 1164 11BC;C7F9;110C 1164 11BC; # (쟹; 쟹; 쟹; 쟹; 쟹; ) HANGUL SYLLABLE JYAENG
+C7FA;C7FA;110C 1164 11BD;C7FA;110C 1164 11BD; # (쟺; 쟺; 쟺; 쟺; 쟺; ) HANGUL SYLLABLE JYAEJ
+C7FB;C7FB;110C 1164 11BE;C7FB;110C 1164 11BE; # (쟻; 쟻; 쟻; 쟻; 쟻; ) HANGUL SYLLABLE JYAEC
+C7FC;C7FC;110C 1164 11BF;C7FC;110C 1164 11BF; # (쟼; 쟼; 쟼; 쟼; 쟼; ) HANGUL SYLLABLE JYAEK
+C7FD;C7FD;110C 1164 11C0;C7FD;110C 1164 11C0; # (쟽; 쟽; 쟽; 쟽; 쟽; ) HANGUL SYLLABLE JYAET
+C7FE;C7FE;110C 1164 11C1;C7FE;110C 1164 11C1; # (쟾; 쟾; 쟤á‡; 쟾; 쟤á‡; ) HANGUL SYLLABLE JYAEP
+C7FF;C7FF;110C 1164 11C2;C7FF;110C 1164 11C2; # (쟿; 쟿; 쟿; 쟿; 쟿; ) HANGUL SYLLABLE JYAEH
+C800;C800;110C 1165;C800;110C 1165; # (저; 저; 저; 저; 저; ) HANGUL SYLLABLE JEO
+C801;C801;110C 1165 11A8;C801;110C 1165 11A8; # (ì ; ì ; 적; ì ; 적; ) HANGUL SYLLABLE JEOG
+C802;C802;110C 1165 11A9;C802;110C 1165 11A9; # (젂; 젂; 젂; 젂; 젂; ) HANGUL SYLLABLE JEOGG
+C803;C803;110C 1165 11AA;C803;110C 1165 11AA; # (젃; 젃; 젃; 젃; 젃; ) HANGUL SYLLABLE JEOGS
+C804;C804;110C 1165 11AB;C804;110C 1165 11AB; # (전; 전; 전; 전; 전; ) HANGUL SYLLABLE JEON
+C805;C805;110C 1165 11AC;C805;110C 1165 11AC; # (젅; 젅; 젅; 젅; 젅; ) HANGUL SYLLABLE JEONJ
+C806;C806;110C 1165 11AD;C806;110C 1165 11AD; # (젆; 젆; 젆; 젆; 젆; ) HANGUL SYLLABLE JEONH
+C807;C807;110C 1165 11AE;C807;110C 1165 11AE; # (젇; 젇; 젇; 젇; 젇; ) HANGUL SYLLABLE JEOD
+C808;C808;110C 1165 11AF;C808;110C 1165 11AF; # (절; 절; 절; 절; 절; ) HANGUL SYLLABLE JEOL
+C809;C809;110C 1165 11B0;C809;110C 1165 11B0; # (젉; 젉; 젉; 젉; 젉; ) HANGUL SYLLABLE JEOLG
+C80A;C80A;110C 1165 11B1;C80A;110C 1165 11B1; # (젊; 젊; 젊; 젊; 젊; ) HANGUL SYLLABLE JEOLM
+C80B;C80B;110C 1165 11B2;C80B;110C 1165 11B2; # (젋; 젋; 젋; 젋; 젋; ) HANGUL SYLLABLE JEOLB
+C80C;C80C;110C 1165 11B3;C80C;110C 1165 11B3; # (젌; 젌; 젌; 젌; 젌; ) HANGUL SYLLABLE JEOLS
+C80D;C80D;110C 1165 11B4;C80D;110C 1165 11B4; # (ì ; ì ; 젍; ì ; 젍; ) HANGUL SYLLABLE JEOLT
+C80E;C80E;110C 1165 11B5;C80E;110C 1165 11B5; # (젎; 젎; 젎; 젎; 젎; ) HANGUL SYLLABLE JEOLP
+C80F;C80F;110C 1165 11B6;C80F;110C 1165 11B6; # (ì ; ì ; 젏; ì ; 젏; ) HANGUL SYLLABLE JEOLH
+C810;C810;110C 1165 11B7;C810;110C 1165 11B7; # (ì ; ì ; 점; ì ; 점; ) HANGUL SYLLABLE JEOM
+C811;C811;110C 1165 11B8;C811;110C 1165 11B8; # (접; 접; 접; 접; 접; ) HANGUL SYLLABLE JEOB
+C812;C812;110C 1165 11B9;C812;110C 1165 11B9; # (젒; 젒; 젒; 젒; 젒; ) HANGUL SYLLABLE JEOBS
+C813;C813;110C 1165 11BA;C813;110C 1165 11BA; # (젓; 젓; 젓; 젓; 젓; ) HANGUL SYLLABLE JEOS
+C814;C814;110C 1165 11BB;C814;110C 1165 11BB; # (젔; 젔; 젔; 젔; 젔; ) HANGUL SYLLABLE JEOSS
+C815;C815;110C 1165 11BC;C815;110C 1165 11BC; # (정; 정; 정; 정; 정; ) HANGUL SYLLABLE JEONG
+C816;C816;110C 1165 11BD;C816;110C 1165 11BD; # (젖; 젖; 젖; 젖; 젖; ) HANGUL SYLLABLE JEOJ
+C817;C817;110C 1165 11BE;C817;110C 1165 11BE; # (젗; 젗; 젗; 젗; 젗; ) HANGUL SYLLABLE JEOC
+C818;C818;110C 1165 11BF;C818;110C 1165 11BF; # (젘; 젘; 젘; 젘; 젘; ) HANGUL SYLLABLE JEOK
+C819;C819;110C 1165 11C0;C819;110C 1165 11C0; # (젙; 젙; 젙; 젙; 젙; ) HANGUL SYLLABLE JEOT
+C81A;C81A;110C 1165 11C1;C81A;110C 1165 11C1; # (ì š; ì š; 저á‡; ì š; 저á‡; ) HANGUL SYLLABLE JEOP
+C81B;C81B;110C 1165 11C2;C81B;110C 1165 11C2; # (젛; 젛; 젛; 젛; 젛; ) HANGUL SYLLABLE JEOH
+C81C;C81C;110C 1166;C81C;110C 1166; # (제; 제; 제; 제; 제; ) HANGUL SYLLABLE JE
+C81D;C81D;110C 1166 11A8;C81D;110C 1166 11A8; # (ì ; ì ; 젝; ì ; 젝; ) HANGUL SYLLABLE JEG
+C81E;C81E;110C 1166 11A9;C81E;110C 1166 11A9; # (젞; 젞; 젞; 젞; 젞; ) HANGUL SYLLABLE JEGG
+C81F;C81F;110C 1166 11AA;C81F;110C 1166 11AA; # (젟; 젟; 젟; 젟; 젟; ) HANGUL SYLLABLE JEGS
+C820;C820;110C 1166 11AB;C820;110C 1166 11AB; # (젠; 젠; 젠; 젠; 젠; ) HANGUL SYLLABLE JEN
+C821;C821;110C 1166 11AC;C821;110C 1166 11AC; # (젡; 젡; 젡; 젡; 젡; ) HANGUL SYLLABLE JENJ
+C822;C822;110C 1166 11AD;C822;110C 1166 11AD; # (젢; 젢; 젢; 젢; 젢; ) HANGUL SYLLABLE JENH
+C823;C823;110C 1166 11AE;C823;110C 1166 11AE; # (젣; 젣; 젣; 젣; 젣; ) HANGUL SYLLABLE JED
+C824;C824;110C 1166 11AF;C824;110C 1166 11AF; # (젤; 젤; 젤; 젤; 젤; ) HANGUL SYLLABLE JEL
+C825;C825;110C 1166 11B0;C825;110C 1166 11B0; # (젥; 젥; 젥; 젥; 젥; ) HANGUL SYLLABLE JELG
+C826;C826;110C 1166 11B1;C826;110C 1166 11B1; # (젦; 젦; 젦; 젦; 젦; ) HANGUL SYLLABLE JELM
+C827;C827;110C 1166 11B2;C827;110C 1166 11B2; # (젧; 젧; 젧; 젧; 젧; ) HANGUL SYLLABLE JELB
+C828;C828;110C 1166 11B3;C828;110C 1166 11B3; # (젨; 젨; 젨; 젨; 젨; ) HANGUL SYLLABLE JELS
+C829;C829;110C 1166 11B4;C829;110C 1166 11B4; # (젩; 젩; 젩; 젩; 젩; ) HANGUL SYLLABLE JELT
+C82A;C82A;110C 1166 11B5;C82A;110C 1166 11B5; # (젪; 젪; 젪; 젪; 젪; ) HANGUL SYLLABLE JELP
+C82B;C82B;110C 1166 11B6;C82B;110C 1166 11B6; # (젫; 젫; 젫; 젫; 젫; ) HANGUL SYLLABLE JELH
+C82C;C82C;110C 1166 11B7;C82C;110C 1166 11B7; # (젬; 젬; 젬; 젬; 젬; ) HANGUL SYLLABLE JEM
+C82D;C82D;110C 1166 11B8;C82D;110C 1166 11B8; # (젭; 젭; 젭; 젭; 젭; ) HANGUL SYLLABLE JEB
+C82E;C82E;110C 1166 11B9;C82E;110C 1166 11B9; # (젮; 젮; 젮; 젮; 젮; ) HANGUL SYLLABLE JEBS
+C82F;C82F;110C 1166 11BA;C82F;110C 1166 11BA; # (젯; 젯; 젯; 젯; 젯; ) HANGUL SYLLABLE JES
+C830;C830;110C 1166 11BB;C830;110C 1166 11BB; # (젰; 젰; 젰; 젰; 젰; ) HANGUL SYLLABLE JESS
+C831;C831;110C 1166 11BC;C831;110C 1166 11BC; # (젱; 젱; 젱; 젱; 젱; ) HANGUL SYLLABLE JENG
+C832;C832;110C 1166 11BD;C832;110C 1166 11BD; # (젲; 젲; 젲; 젲; 젲; ) HANGUL SYLLABLE JEJ
+C833;C833;110C 1166 11BE;C833;110C 1166 11BE; # (젳; 젳; 젳; 젳; 젳; ) HANGUL SYLLABLE JEC
+C834;C834;110C 1166 11BF;C834;110C 1166 11BF; # (젴; 젴; 젴; 젴; 젴; ) HANGUL SYLLABLE JEK
+C835;C835;110C 1166 11C0;C835;110C 1166 11C0; # (젵; 젵; 젵; 젵; 젵; ) HANGUL SYLLABLE JET
+C836;C836;110C 1166 11C1;C836;110C 1166 11C1; # (ì ¶; ì ¶; 제á‡; ì ¶; 제á‡; ) HANGUL SYLLABLE JEP
+C837;C837;110C 1166 11C2;C837;110C 1166 11C2; # (젷; 젷; 젷; 젷; 젷; ) HANGUL SYLLABLE JEH
+C838;C838;110C 1167;C838;110C 1167; # (져; 져; 져; 져; 져; ) HANGUL SYLLABLE JYEO
+C839;C839;110C 1167 11A8;C839;110C 1167 11A8; # (젹; 젹; 젹; 젹; 젹; ) HANGUL SYLLABLE JYEOG
+C83A;C83A;110C 1167 11A9;C83A;110C 1167 11A9; # (젺; 젺; 젺; 젺; 젺; ) HANGUL SYLLABLE JYEOGG
+C83B;C83B;110C 1167 11AA;C83B;110C 1167 11AA; # (젻; 젻; 젻; 젻; 젻; ) HANGUL SYLLABLE JYEOGS
+C83C;C83C;110C 1167 11AB;C83C;110C 1167 11AB; # (젼; 젼; 젼; 젼; 젼; ) HANGUL SYLLABLE JYEON
+C83D;C83D;110C 1167 11AC;C83D;110C 1167 11AC; # (젽; 젽; 젽; 젽; 젽; ) HANGUL SYLLABLE JYEONJ
+C83E;C83E;110C 1167 11AD;C83E;110C 1167 11AD; # (젾; 젾; 젾; 젾; 젾; ) HANGUL SYLLABLE JYEONH
+C83F;C83F;110C 1167 11AE;C83F;110C 1167 11AE; # (젿; 젿; 젿; 젿; 젿; ) HANGUL SYLLABLE JYEOD
+C840;C840;110C 1167 11AF;C840;110C 1167 11AF; # (졀; 졀; 졀; 졀; 졀; ) HANGUL SYLLABLE JYEOL
+C841;C841;110C 1167 11B0;C841;110C 1167 11B0; # (ì¡; ì¡; 졁; ì¡; 졁; ) HANGUL SYLLABLE JYEOLG
+C842;C842;110C 1167 11B1;C842;110C 1167 11B1; # (졂; 졂; 졂; 졂; 졂; ) HANGUL SYLLABLE JYEOLM
+C843;C843;110C 1167 11B2;C843;110C 1167 11B2; # (졃; 졃; 졃; 졃; 졃; ) HANGUL SYLLABLE JYEOLB
+C844;C844;110C 1167 11B3;C844;110C 1167 11B3; # (졄; 졄; 졄; 졄; 졄; ) HANGUL SYLLABLE JYEOLS
+C845;C845;110C 1167 11B4;C845;110C 1167 11B4; # (졅; 졅; 졅; 졅; 졅; ) HANGUL SYLLABLE JYEOLT
+C846;C846;110C 1167 11B5;C846;110C 1167 11B5; # (졆; 졆; 졆; 졆; 졆; ) HANGUL SYLLABLE JYEOLP
+C847;C847;110C 1167 11B6;C847;110C 1167 11B6; # (졇; 졇; 졇; 졇; 졇; ) HANGUL SYLLABLE JYEOLH
+C848;C848;110C 1167 11B7;C848;110C 1167 11B7; # (졈; 졈; 졈; 졈; 졈; ) HANGUL SYLLABLE JYEOM
+C849;C849;110C 1167 11B8;C849;110C 1167 11B8; # (졉; 졉; 졉; 졉; 졉; ) HANGUL SYLLABLE JYEOB
+C84A;C84A;110C 1167 11B9;C84A;110C 1167 11B9; # (졊; 졊; 졊; 졊; 졊; ) HANGUL SYLLABLE JYEOBS
+C84B;C84B;110C 1167 11BA;C84B;110C 1167 11BA; # (졋; 졋; 졋; 졋; 졋; ) HANGUL SYLLABLE JYEOS
+C84C;C84C;110C 1167 11BB;C84C;110C 1167 11BB; # (졌; 졌; 졌; 졌; 졌; ) HANGUL SYLLABLE JYEOSS
+C84D;C84D;110C 1167 11BC;C84D;110C 1167 11BC; # (ì¡; ì¡; 졍; ì¡; 졍; ) HANGUL SYLLABLE JYEONG
+C84E;C84E;110C 1167 11BD;C84E;110C 1167 11BD; # (졎; 졎; 졎; 졎; 졎; ) HANGUL SYLLABLE JYEOJ
+C84F;C84F;110C 1167 11BE;C84F;110C 1167 11BE; # (ì¡; ì¡; 졏; ì¡; 졏; ) HANGUL SYLLABLE JYEOC
+C850;C850;110C 1167 11BF;C850;110C 1167 11BF; # (ì¡; ì¡; 졐; ì¡; 졐; ) HANGUL SYLLABLE JYEOK
+C851;C851;110C 1167 11C0;C851;110C 1167 11C0; # (졑; 졑; 졑; 졑; 졑; ) HANGUL SYLLABLE JYEOT
+C852;C852;110C 1167 11C1;C852;110C 1167 11C1; # (ì¡’; ì¡’; 져á‡; ì¡’; 져á‡; ) HANGUL SYLLABLE JYEOP
+C853;C853;110C 1167 11C2;C853;110C 1167 11C2; # (졓; 졓; 졓; 졓; 졓; ) HANGUL SYLLABLE JYEOH
+C854;C854;110C 1168;C854;110C 1168; # (졔; 졔; 졔; 졔; 졔; ) HANGUL SYLLABLE JYE
+C855;C855;110C 1168 11A8;C855;110C 1168 11A8; # (졕; 졕; 졕; 졕; 졕; ) HANGUL SYLLABLE JYEG
+C856;C856;110C 1168 11A9;C856;110C 1168 11A9; # (졖; 졖; 졖; 졖; 졖; ) HANGUL SYLLABLE JYEGG
+C857;C857;110C 1168 11AA;C857;110C 1168 11AA; # (졗; 졗; 졗; 졗; 졗; ) HANGUL SYLLABLE JYEGS
+C858;C858;110C 1168 11AB;C858;110C 1168 11AB; # (졘; 졘; 졘; 졘; 졘; ) HANGUL SYLLABLE JYEN
+C859;C859;110C 1168 11AC;C859;110C 1168 11AC; # (졙; 졙; 졙; 졙; 졙; ) HANGUL SYLLABLE JYENJ
+C85A;C85A;110C 1168 11AD;C85A;110C 1168 11AD; # (졚; 졚; 졚; 졚; 졚; ) HANGUL SYLLABLE JYENH
+C85B;C85B;110C 1168 11AE;C85B;110C 1168 11AE; # (졛; 졛; 졛; 졛; 졛; ) HANGUL SYLLABLE JYED
+C85C;C85C;110C 1168 11AF;C85C;110C 1168 11AF; # (졜; 졜; 졜; 졜; 졜; ) HANGUL SYLLABLE JYEL
+C85D;C85D;110C 1168 11B0;C85D;110C 1168 11B0; # (ì¡; ì¡; 졝; ì¡; 졝; ) HANGUL SYLLABLE JYELG
+C85E;C85E;110C 1168 11B1;C85E;110C 1168 11B1; # (졞; 졞; 졞; 졞; 졞; ) HANGUL SYLLABLE JYELM
+C85F;C85F;110C 1168 11B2;C85F;110C 1168 11B2; # (졟; 졟; 졟; 졟; 졟; ) HANGUL SYLLABLE JYELB
+C860;C860;110C 1168 11B3;C860;110C 1168 11B3; # (졠; 졠; 졠; 졠; 졠; ) HANGUL SYLLABLE JYELS
+C861;C861;110C 1168 11B4;C861;110C 1168 11B4; # (졡; 졡; 졡; 졡; 졡; ) HANGUL SYLLABLE JYELT
+C862;C862;110C 1168 11B5;C862;110C 1168 11B5; # (졢; 졢; 졢; 졢; 졢; ) HANGUL SYLLABLE JYELP
+C863;C863;110C 1168 11B6;C863;110C 1168 11B6; # (졣; 졣; 졣; 졣; 졣; ) HANGUL SYLLABLE JYELH
+C864;C864;110C 1168 11B7;C864;110C 1168 11B7; # (졤; 졤; 졤; 졤; 졤; ) HANGUL SYLLABLE JYEM
+C865;C865;110C 1168 11B8;C865;110C 1168 11B8; # (졥; 졥; 졥; 졥; 졥; ) HANGUL SYLLABLE JYEB
+C866;C866;110C 1168 11B9;C866;110C 1168 11B9; # (졦; 졦; 졦; 졦; 졦; ) HANGUL SYLLABLE JYEBS
+C867;C867;110C 1168 11BA;C867;110C 1168 11BA; # (졧; 졧; 졧; 졧; 졧; ) HANGUL SYLLABLE JYES
+C868;C868;110C 1168 11BB;C868;110C 1168 11BB; # (졨; 졨; 졨; 졨; 졨; ) HANGUL SYLLABLE JYESS
+C869;C869;110C 1168 11BC;C869;110C 1168 11BC; # (졩; 졩; 졩; 졩; 졩; ) HANGUL SYLLABLE JYENG
+C86A;C86A;110C 1168 11BD;C86A;110C 1168 11BD; # (졪; 졪; 졪; 졪; 졪; ) HANGUL SYLLABLE JYEJ
+C86B;C86B;110C 1168 11BE;C86B;110C 1168 11BE; # (졫; 졫; 졫; 졫; 졫; ) HANGUL SYLLABLE JYEC
+C86C;C86C;110C 1168 11BF;C86C;110C 1168 11BF; # (졬; 졬; 졬; 졬; 졬; ) HANGUL SYLLABLE JYEK
+C86D;C86D;110C 1168 11C0;C86D;110C 1168 11C0; # (졭; 졭; 졭; 졭; 졭; ) HANGUL SYLLABLE JYET
+C86E;C86E;110C 1168 11C1;C86E;110C 1168 11C1; # (ì¡®; ì¡®; 졔á‡; ì¡®; 졔á‡; ) HANGUL SYLLABLE JYEP
+C86F;C86F;110C 1168 11C2;C86F;110C 1168 11C2; # (졯; 졯; 졯; 졯; 졯; ) HANGUL SYLLABLE JYEH
+C870;C870;110C 1169;C870;110C 1169; # (조; 조; 조; 조; 조; ) HANGUL SYLLABLE JO
+C871;C871;110C 1169 11A8;C871;110C 1169 11A8; # (족; 족; 족; 족; 족; ) HANGUL SYLLABLE JOG
+C872;C872;110C 1169 11A9;C872;110C 1169 11A9; # (졲; 졲; 졲; 졲; 졲; ) HANGUL SYLLABLE JOGG
+C873;C873;110C 1169 11AA;C873;110C 1169 11AA; # (졳; 졳; 졳; 졳; 졳; ) HANGUL SYLLABLE JOGS
+C874;C874;110C 1169 11AB;C874;110C 1169 11AB; # (존; 존; 존; 존; 존; ) HANGUL SYLLABLE JON
+C875;C875;110C 1169 11AC;C875;110C 1169 11AC; # (졵; 졵; 졵; 졵; 졵; ) HANGUL SYLLABLE JONJ
+C876;C876;110C 1169 11AD;C876;110C 1169 11AD; # (졶; 졶; 졶; 졶; 졶; ) HANGUL SYLLABLE JONH
+C877;C877;110C 1169 11AE;C877;110C 1169 11AE; # (졷; 졷; 졷; 졷; 졷; ) HANGUL SYLLABLE JOD
+C878;C878;110C 1169 11AF;C878;110C 1169 11AF; # (졸; 졸; 졸; 졸; 졸; ) HANGUL SYLLABLE JOL
+C879;C879;110C 1169 11B0;C879;110C 1169 11B0; # (졹; 졹; 졹; 졹; 졹; ) HANGUL SYLLABLE JOLG
+C87A;C87A;110C 1169 11B1;C87A;110C 1169 11B1; # (졺; 졺; 졺; 졺; 졺; ) HANGUL SYLLABLE JOLM
+C87B;C87B;110C 1169 11B2;C87B;110C 1169 11B2; # (졻; 졻; 졻; 졻; 졻; ) HANGUL SYLLABLE JOLB
+C87C;C87C;110C 1169 11B3;C87C;110C 1169 11B3; # (졼; 졼; 졼; 졼; 졼; ) HANGUL SYLLABLE JOLS
+C87D;C87D;110C 1169 11B4;C87D;110C 1169 11B4; # (졽; 졽; 졽; 졽; 졽; ) HANGUL SYLLABLE JOLT
+C87E;C87E;110C 1169 11B5;C87E;110C 1169 11B5; # (졾; 졾; 졾; 졾; 졾; ) HANGUL SYLLABLE JOLP
+C87F;C87F;110C 1169 11B6;C87F;110C 1169 11B6; # (졿; 졿; 졿; 졿; 졿; ) HANGUL SYLLABLE JOLH
+C880;C880;110C 1169 11B7;C880;110C 1169 11B7; # (좀; 좀; 좀; 좀; 좀; ) HANGUL SYLLABLE JOM
+C881;C881;110C 1169 11B8;C881;110C 1169 11B8; # (ì¢; ì¢; 좁; ì¢; 좁; ) HANGUL SYLLABLE JOB
+C882;C882;110C 1169 11B9;C882;110C 1169 11B9; # (좂; 좂; 좂; 좂; 좂; ) HANGUL SYLLABLE JOBS
+C883;C883;110C 1169 11BA;C883;110C 1169 11BA; # (좃; 좃; 좃; 좃; 좃; ) HANGUL SYLLABLE JOS
+C884;C884;110C 1169 11BB;C884;110C 1169 11BB; # (좄; 좄; 좄; 좄; 좄; ) HANGUL SYLLABLE JOSS
+C885;C885;110C 1169 11BC;C885;110C 1169 11BC; # (종; 종; 종; 종; 종; ) HANGUL SYLLABLE JONG
+C886;C886;110C 1169 11BD;C886;110C 1169 11BD; # (좆; 좆; 좆; 좆; 좆; ) HANGUL SYLLABLE JOJ
+C887;C887;110C 1169 11BE;C887;110C 1169 11BE; # (좇; 좇; 좇; 좇; 좇; ) HANGUL SYLLABLE JOC
+C888;C888;110C 1169 11BF;C888;110C 1169 11BF; # (좈; 좈; 좈; 좈; 좈; ) HANGUL SYLLABLE JOK
+C889;C889;110C 1169 11C0;C889;110C 1169 11C0; # (좉; 좉; 좉; 좉; 좉; ) HANGUL SYLLABLE JOT
+C88A;C88A;110C 1169 11C1;C88A;110C 1169 11C1; # (좊; 좊; 조á‡; 좊; 조á‡; ) HANGUL SYLLABLE JOP
+C88B;C88B;110C 1169 11C2;C88B;110C 1169 11C2; # (좋; 좋; 좋; 좋; 좋; ) HANGUL SYLLABLE JOH
+C88C;C88C;110C 116A;C88C;110C 116A; # (좌; 좌; 좌; 좌; 좌; ) HANGUL SYLLABLE JWA
+C88D;C88D;110C 116A 11A8;C88D;110C 116A 11A8; # (ì¢; ì¢; 좍; ì¢; 좍; ) HANGUL SYLLABLE JWAG
+C88E;C88E;110C 116A 11A9;C88E;110C 116A 11A9; # (좎; 좎; 좎; 좎; 좎; ) HANGUL SYLLABLE JWAGG
+C88F;C88F;110C 116A 11AA;C88F;110C 116A 11AA; # (ì¢; ì¢; 좏; ì¢; 좏; ) HANGUL SYLLABLE JWAGS
+C890;C890;110C 116A 11AB;C890;110C 116A 11AB; # (ì¢; ì¢; 좐; ì¢; 좐; ) HANGUL SYLLABLE JWAN
+C891;C891;110C 116A 11AC;C891;110C 116A 11AC; # (좑; 좑; 좑; 좑; 좑; ) HANGUL SYLLABLE JWANJ
+C892;C892;110C 116A 11AD;C892;110C 116A 11AD; # (좒; 좒; 좒; 좒; 좒; ) HANGUL SYLLABLE JWANH
+C893;C893;110C 116A 11AE;C893;110C 116A 11AE; # (좓; 좓; 좓; 좓; 좓; ) HANGUL SYLLABLE JWAD
+C894;C894;110C 116A 11AF;C894;110C 116A 11AF; # (좔; 좔; 좔; 좔; 좔; ) HANGUL SYLLABLE JWAL
+C895;C895;110C 116A 11B0;C895;110C 116A 11B0; # (좕; 좕; 좕; 좕; 좕; ) HANGUL SYLLABLE JWALG
+C896;C896;110C 116A 11B1;C896;110C 116A 11B1; # (좖; 좖; 좖; 좖; 좖; ) HANGUL SYLLABLE JWALM
+C897;C897;110C 116A 11B2;C897;110C 116A 11B2; # (좗; 좗; 좗; 좗; 좗; ) HANGUL SYLLABLE JWALB
+C898;C898;110C 116A 11B3;C898;110C 116A 11B3; # (좘; 좘; 좘; 좘; 좘; ) HANGUL SYLLABLE JWALS
+C899;C899;110C 116A 11B4;C899;110C 116A 11B4; # (좙; 좙; 좙; 좙; 좙; ) HANGUL SYLLABLE JWALT
+C89A;C89A;110C 116A 11B5;C89A;110C 116A 11B5; # (좚; 좚; 좚; 좚; 좚; ) HANGUL SYLLABLE JWALP
+C89B;C89B;110C 116A 11B6;C89B;110C 116A 11B6; # (좛; 좛; 좛; 좛; 좛; ) HANGUL SYLLABLE JWALH
+C89C;C89C;110C 116A 11B7;C89C;110C 116A 11B7; # (좜; 좜; 좜; 좜; 좜; ) HANGUL SYLLABLE JWAM
+C89D;C89D;110C 116A 11B8;C89D;110C 116A 11B8; # (ì¢; ì¢; 좝; ì¢; 좝; ) HANGUL SYLLABLE JWAB
+C89E;C89E;110C 116A 11B9;C89E;110C 116A 11B9; # (좞; 좞; 좞; 좞; 좞; ) HANGUL SYLLABLE JWABS
+C89F;C89F;110C 116A 11BA;C89F;110C 116A 11BA; # (좟; 좟; 좟; 좟; 좟; ) HANGUL SYLLABLE JWAS
+C8A0;C8A0;110C 116A 11BB;C8A0;110C 116A 11BB; # (좠; 좠; 좠; 좠; 좠; ) HANGUL SYLLABLE JWASS
+C8A1;C8A1;110C 116A 11BC;C8A1;110C 116A 11BC; # (좡; 좡; 좡; 좡; 좡; ) HANGUL SYLLABLE JWANG
+C8A2;C8A2;110C 116A 11BD;C8A2;110C 116A 11BD; # (좢; 좢; 좢; 좢; 좢; ) HANGUL SYLLABLE JWAJ
+C8A3;C8A3;110C 116A 11BE;C8A3;110C 116A 11BE; # (좣; 좣; 좣; 좣; 좣; ) HANGUL SYLLABLE JWAC
+C8A4;C8A4;110C 116A 11BF;C8A4;110C 116A 11BF; # (좤; 좤; 좤; 좤; 좤; ) HANGUL SYLLABLE JWAK
+C8A5;C8A5;110C 116A 11C0;C8A5;110C 116A 11C0; # (좥; 좥; 좥; 좥; 좥; ) HANGUL SYLLABLE JWAT
+C8A6;C8A6;110C 116A 11C1;C8A6;110C 116A 11C1; # (좦; 좦; 좌á‡; 좦; 좌á‡; ) HANGUL SYLLABLE JWAP
+C8A7;C8A7;110C 116A 11C2;C8A7;110C 116A 11C2; # (좧; 좧; 좧; 좧; 좧; ) HANGUL SYLLABLE JWAH
+C8A8;C8A8;110C 116B;C8A8;110C 116B; # (좨; 좨; 좨; 좨; 좨; ) HANGUL SYLLABLE JWAE
+C8A9;C8A9;110C 116B 11A8;C8A9;110C 116B 11A8; # (좩; 좩; 좩; 좩; 좩; ) HANGUL SYLLABLE JWAEG
+C8AA;C8AA;110C 116B 11A9;C8AA;110C 116B 11A9; # (좪; 좪; 좪; 좪; 좪; ) HANGUL SYLLABLE JWAEGG
+C8AB;C8AB;110C 116B 11AA;C8AB;110C 116B 11AA; # (좫; 좫; 좫; 좫; 좫; ) HANGUL SYLLABLE JWAEGS
+C8AC;C8AC;110C 116B 11AB;C8AC;110C 116B 11AB; # (좬; 좬; 좬; 좬; 좬; ) HANGUL SYLLABLE JWAEN
+C8AD;C8AD;110C 116B 11AC;C8AD;110C 116B 11AC; # (좭; 좭; 좭; 좭; 좭; ) HANGUL SYLLABLE JWAENJ
+C8AE;C8AE;110C 116B 11AD;C8AE;110C 116B 11AD; # (좮; 좮; 좮; 좮; 좮; ) HANGUL SYLLABLE JWAENH
+C8AF;C8AF;110C 116B 11AE;C8AF;110C 116B 11AE; # (좯; 좯; 좯; 좯; 좯; ) HANGUL SYLLABLE JWAED
+C8B0;C8B0;110C 116B 11AF;C8B0;110C 116B 11AF; # (좰; 좰; 좰; 좰; 좰; ) HANGUL SYLLABLE JWAEL
+C8B1;C8B1;110C 116B 11B0;C8B1;110C 116B 11B0; # (좱; 좱; 좱; 좱; 좱; ) HANGUL SYLLABLE JWAELG
+C8B2;C8B2;110C 116B 11B1;C8B2;110C 116B 11B1; # (좲; 좲; 좲; 좲; 좲; ) HANGUL SYLLABLE JWAELM
+C8B3;C8B3;110C 116B 11B2;C8B3;110C 116B 11B2; # (좳; 좳; 좳; 좳; 좳; ) HANGUL SYLLABLE JWAELB
+C8B4;C8B4;110C 116B 11B3;C8B4;110C 116B 11B3; # (좴; 좴; 좴; 좴; 좴; ) HANGUL SYLLABLE JWAELS
+C8B5;C8B5;110C 116B 11B4;C8B5;110C 116B 11B4; # (좵; 좵; 좵; 좵; 좵; ) HANGUL SYLLABLE JWAELT
+C8B6;C8B6;110C 116B 11B5;C8B6;110C 116B 11B5; # (좶; 좶; 좶; 좶; 좶; ) HANGUL SYLLABLE JWAELP
+C8B7;C8B7;110C 116B 11B6;C8B7;110C 116B 11B6; # (좷; 좷; 좷; 좷; 좷; ) HANGUL SYLLABLE JWAELH
+C8B8;C8B8;110C 116B 11B7;C8B8;110C 116B 11B7; # (좸; 좸; 좸; 좸; 좸; ) HANGUL SYLLABLE JWAEM
+C8B9;C8B9;110C 116B 11B8;C8B9;110C 116B 11B8; # (좹; 좹; 좹; 좹; 좹; ) HANGUL SYLLABLE JWAEB
+C8BA;C8BA;110C 116B 11B9;C8BA;110C 116B 11B9; # (좺; 좺; 좺; 좺; 좺; ) HANGUL SYLLABLE JWAEBS
+C8BB;C8BB;110C 116B 11BA;C8BB;110C 116B 11BA; # (좻; 좻; 좻; 좻; 좻; ) HANGUL SYLLABLE JWAES
+C8BC;C8BC;110C 116B 11BB;C8BC;110C 116B 11BB; # (좼; 좼; 좼; 좼; 좼; ) HANGUL SYLLABLE JWAESS
+C8BD;C8BD;110C 116B 11BC;C8BD;110C 116B 11BC; # (좽; 좽; 좽; 좽; 좽; ) HANGUL SYLLABLE JWAENG
+C8BE;C8BE;110C 116B 11BD;C8BE;110C 116B 11BD; # (좾; 좾; 좾; 좾; 좾; ) HANGUL SYLLABLE JWAEJ
+C8BF;C8BF;110C 116B 11BE;C8BF;110C 116B 11BE; # (좿; 좿; 좿; 좿; 좿; ) HANGUL SYLLABLE JWAEC
+C8C0;C8C0;110C 116B 11BF;C8C0;110C 116B 11BF; # (죀; 죀; 죀; 죀; 죀; ) HANGUL SYLLABLE JWAEK
+C8C1;C8C1;110C 116B 11C0;C8C1;110C 116B 11C0; # (ì£; ì£; 죁; ì£; 죁; ) HANGUL SYLLABLE JWAET
+C8C2;C8C2;110C 116B 11C1;C8C2;110C 116B 11C1; # (죂; 죂; 좨á‡; 죂; 좨á‡; ) HANGUL SYLLABLE JWAEP
+C8C3;C8C3;110C 116B 11C2;C8C3;110C 116B 11C2; # (죃; 죃; 죃; 죃; 죃; ) HANGUL SYLLABLE JWAEH
+C8C4;C8C4;110C 116C;C8C4;110C 116C; # (죄; 죄; 죄; 죄; 죄; ) HANGUL SYLLABLE JOE
+C8C5;C8C5;110C 116C 11A8;C8C5;110C 116C 11A8; # (죅; 죅; 죅; 죅; 죅; ) HANGUL SYLLABLE JOEG
+C8C6;C8C6;110C 116C 11A9;C8C6;110C 116C 11A9; # (죆; 죆; 죆; 죆; 죆; ) HANGUL SYLLABLE JOEGG
+C8C7;C8C7;110C 116C 11AA;C8C7;110C 116C 11AA; # (죇; 죇; 죇; 죇; 죇; ) HANGUL SYLLABLE JOEGS
+C8C8;C8C8;110C 116C 11AB;C8C8;110C 116C 11AB; # (죈; 죈; 죈; 죈; 죈; ) HANGUL SYLLABLE JOEN
+C8C9;C8C9;110C 116C 11AC;C8C9;110C 116C 11AC; # (죉; 죉; 죉; 죉; 죉; ) HANGUL SYLLABLE JOENJ
+C8CA;C8CA;110C 116C 11AD;C8CA;110C 116C 11AD; # (죊; 죊; 죊; 죊; 죊; ) HANGUL SYLLABLE JOENH
+C8CB;C8CB;110C 116C 11AE;C8CB;110C 116C 11AE; # (죋; 죋; 죋; 죋; 죋; ) HANGUL SYLLABLE JOED
+C8CC;C8CC;110C 116C 11AF;C8CC;110C 116C 11AF; # (죌; 죌; 죌; 죌; 죌; ) HANGUL SYLLABLE JOEL
+C8CD;C8CD;110C 116C 11B0;C8CD;110C 116C 11B0; # (ì£; ì£; 죍; ì£; 죍; ) HANGUL SYLLABLE JOELG
+C8CE;C8CE;110C 116C 11B1;C8CE;110C 116C 11B1; # (죎; 죎; 죎; 죎; 죎; ) HANGUL SYLLABLE JOELM
+C8CF;C8CF;110C 116C 11B2;C8CF;110C 116C 11B2; # (ì£; ì£; 죏; ì£; 죏; ) HANGUL SYLLABLE JOELB
+C8D0;C8D0;110C 116C 11B3;C8D0;110C 116C 11B3; # (ì£; ì£; 죐; ì£; 죐; ) HANGUL SYLLABLE JOELS
+C8D1;C8D1;110C 116C 11B4;C8D1;110C 116C 11B4; # (죑; 죑; 죑; 죑; 죑; ) HANGUL SYLLABLE JOELT
+C8D2;C8D2;110C 116C 11B5;C8D2;110C 116C 11B5; # (죒; 죒; 죒; 죒; 죒; ) HANGUL SYLLABLE JOELP
+C8D3;C8D3;110C 116C 11B6;C8D3;110C 116C 11B6; # (죓; 죓; 죓; 죓; 죓; ) HANGUL SYLLABLE JOELH
+C8D4;C8D4;110C 116C 11B7;C8D4;110C 116C 11B7; # (죔; 죔; 죔; 죔; 죔; ) HANGUL SYLLABLE JOEM
+C8D5;C8D5;110C 116C 11B8;C8D5;110C 116C 11B8; # (죕; 죕; 죕; 죕; 죕; ) HANGUL SYLLABLE JOEB
+C8D6;C8D6;110C 116C 11B9;C8D6;110C 116C 11B9; # (죖; 죖; 죖; 죖; 죖; ) HANGUL SYLLABLE JOEBS
+C8D7;C8D7;110C 116C 11BA;C8D7;110C 116C 11BA; # (죗; 죗; 죗; 죗; 죗; ) HANGUL SYLLABLE JOES
+C8D8;C8D8;110C 116C 11BB;C8D8;110C 116C 11BB; # (죘; 죘; 죘; 죘; 죘; ) HANGUL SYLLABLE JOESS
+C8D9;C8D9;110C 116C 11BC;C8D9;110C 116C 11BC; # (죙; 죙; 죙; 죙; 죙; ) HANGUL SYLLABLE JOENG
+C8DA;C8DA;110C 116C 11BD;C8DA;110C 116C 11BD; # (죚; 죚; 죚; 죚; 죚; ) HANGUL SYLLABLE JOEJ
+C8DB;C8DB;110C 116C 11BE;C8DB;110C 116C 11BE; # (죛; 죛; 죛; 죛; 죛; ) HANGUL SYLLABLE JOEC
+C8DC;C8DC;110C 116C 11BF;C8DC;110C 116C 11BF; # (죜; 죜; 죜; 죜; 죜; ) HANGUL SYLLABLE JOEK
+C8DD;C8DD;110C 116C 11C0;C8DD;110C 116C 11C0; # (ì£; ì£; 죝; ì£; 죝; ) HANGUL SYLLABLE JOET
+C8DE;C8DE;110C 116C 11C1;C8DE;110C 116C 11C1; # (죞; 죞; 죄á‡; 죞; 죄á‡; ) HANGUL SYLLABLE JOEP
+C8DF;C8DF;110C 116C 11C2;C8DF;110C 116C 11C2; # (죟; 죟; 죟; 죟; 죟; ) HANGUL SYLLABLE JOEH
+C8E0;C8E0;110C 116D;C8E0;110C 116D; # (죠; 죠; 죠; 죠; 죠; ) HANGUL SYLLABLE JYO
+C8E1;C8E1;110C 116D 11A8;C8E1;110C 116D 11A8; # (죡; 죡; 죡; 죡; 죡; ) HANGUL SYLLABLE JYOG
+C8E2;C8E2;110C 116D 11A9;C8E2;110C 116D 11A9; # (죢; 죢; 죢; 죢; 죢; ) HANGUL SYLLABLE JYOGG
+C8E3;C8E3;110C 116D 11AA;C8E3;110C 116D 11AA; # (죣; 죣; 죣; 죣; 죣; ) HANGUL SYLLABLE JYOGS
+C8E4;C8E4;110C 116D 11AB;C8E4;110C 116D 11AB; # (죤; 죤; 죤; 죤; 죤; ) HANGUL SYLLABLE JYON
+C8E5;C8E5;110C 116D 11AC;C8E5;110C 116D 11AC; # (죥; 죥; 죥; 죥; 죥; ) HANGUL SYLLABLE JYONJ
+C8E6;C8E6;110C 116D 11AD;C8E6;110C 116D 11AD; # (죦; 죦; 죦; 죦; 죦; ) HANGUL SYLLABLE JYONH
+C8E7;C8E7;110C 116D 11AE;C8E7;110C 116D 11AE; # (죧; 죧; 죧; 죧; 죧; ) HANGUL SYLLABLE JYOD
+C8E8;C8E8;110C 116D 11AF;C8E8;110C 116D 11AF; # (죨; 죨; 죨; 죨; 죨; ) HANGUL SYLLABLE JYOL
+C8E9;C8E9;110C 116D 11B0;C8E9;110C 116D 11B0; # (죩; 죩; 죩; 죩; 죩; ) HANGUL SYLLABLE JYOLG
+C8EA;C8EA;110C 116D 11B1;C8EA;110C 116D 11B1; # (죪; 죪; 죪; 죪; 죪; ) HANGUL SYLLABLE JYOLM
+C8EB;C8EB;110C 116D 11B2;C8EB;110C 116D 11B2; # (죫; 죫; 죫; 죫; 죫; ) HANGUL SYLLABLE JYOLB
+C8EC;C8EC;110C 116D 11B3;C8EC;110C 116D 11B3; # (죬; 죬; 죬; 죬; 죬; ) HANGUL SYLLABLE JYOLS
+C8ED;C8ED;110C 116D 11B4;C8ED;110C 116D 11B4; # (죭; 죭; 죭; 죭; 죭; ) HANGUL SYLLABLE JYOLT
+C8EE;C8EE;110C 116D 11B5;C8EE;110C 116D 11B5; # (죮; 죮; 죮; 죮; 죮; ) HANGUL SYLLABLE JYOLP
+C8EF;C8EF;110C 116D 11B6;C8EF;110C 116D 11B6; # (죯; 죯; 죯; 죯; 죯; ) HANGUL SYLLABLE JYOLH
+C8F0;C8F0;110C 116D 11B7;C8F0;110C 116D 11B7; # (죰; 죰; 죰; 죰; 죰; ) HANGUL SYLLABLE JYOM
+C8F1;C8F1;110C 116D 11B8;C8F1;110C 116D 11B8; # (죱; 죱; 죱; 죱; 죱; ) HANGUL SYLLABLE JYOB
+C8F2;C8F2;110C 116D 11B9;C8F2;110C 116D 11B9; # (죲; 죲; 죲; 죲; 죲; ) HANGUL SYLLABLE JYOBS
+C8F3;C8F3;110C 116D 11BA;C8F3;110C 116D 11BA; # (죳; 죳; 죳; 죳; 죳; ) HANGUL SYLLABLE JYOS
+C8F4;C8F4;110C 116D 11BB;C8F4;110C 116D 11BB; # (죴; 죴; 죴; 죴; 죴; ) HANGUL SYLLABLE JYOSS
+C8F5;C8F5;110C 116D 11BC;C8F5;110C 116D 11BC; # (죵; 죵; 죵; 죵; 죵; ) HANGUL SYLLABLE JYONG
+C8F6;C8F6;110C 116D 11BD;C8F6;110C 116D 11BD; # (죶; 죶; 죶; 죶; 죶; ) HANGUL SYLLABLE JYOJ
+C8F7;C8F7;110C 116D 11BE;C8F7;110C 116D 11BE; # (죷; 죷; 죷; 죷; 죷; ) HANGUL SYLLABLE JYOC
+C8F8;C8F8;110C 116D 11BF;C8F8;110C 116D 11BF; # (죸; 죸; 죸; 죸; 죸; ) HANGUL SYLLABLE JYOK
+C8F9;C8F9;110C 116D 11C0;C8F9;110C 116D 11C0; # (죹; 죹; 죹; 죹; 죹; ) HANGUL SYLLABLE JYOT
+C8FA;C8FA;110C 116D 11C1;C8FA;110C 116D 11C1; # (죺; 죺; 죠á‡; 죺; 죠á‡; ) HANGUL SYLLABLE JYOP
+C8FB;C8FB;110C 116D 11C2;C8FB;110C 116D 11C2; # (죻; 죻; 죻; 죻; 죻; ) HANGUL SYLLABLE JYOH
+C8FC;C8FC;110C 116E;C8FC;110C 116E; # (주; 주; 주; 주; 주; ) HANGUL SYLLABLE JU
+C8FD;C8FD;110C 116E 11A8;C8FD;110C 116E 11A8; # (죽; 죽; 죽; 죽; 죽; ) HANGUL SYLLABLE JUG
+C8FE;C8FE;110C 116E 11A9;C8FE;110C 116E 11A9; # (죾; 죾; 죾; 죾; 죾; ) HANGUL SYLLABLE JUGG
+C8FF;C8FF;110C 116E 11AA;C8FF;110C 116E 11AA; # (죿; 죿; 죿; 죿; 죿; ) HANGUL SYLLABLE JUGS
+C900;C900;110C 116E 11AB;C900;110C 116E 11AB; # (준; 준; 준; 준; 준; ) HANGUL SYLLABLE JUN
+C901;C901;110C 116E 11AC;C901;110C 116E 11AC; # (ì¤; ì¤; 줁; ì¤; 줁; ) HANGUL SYLLABLE JUNJ
+C902;C902;110C 116E 11AD;C902;110C 116E 11AD; # (줂; 줂; 줂; 줂; 줂; ) HANGUL SYLLABLE JUNH
+C903;C903;110C 116E 11AE;C903;110C 116E 11AE; # (줃; 줃; 줃; 줃; 줃; ) HANGUL SYLLABLE JUD
+C904;C904;110C 116E 11AF;C904;110C 116E 11AF; # (줄; 줄; 줄; 줄; 줄; ) HANGUL SYLLABLE JUL
+C905;C905;110C 116E 11B0;C905;110C 116E 11B0; # (줅; 줅; 줅; 줅; 줅; ) HANGUL SYLLABLE JULG
+C906;C906;110C 116E 11B1;C906;110C 116E 11B1; # (줆; 줆; 줆; 줆; 줆; ) HANGUL SYLLABLE JULM
+C907;C907;110C 116E 11B2;C907;110C 116E 11B2; # (줇; 줇; 줇; 줇; 줇; ) HANGUL SYLLABLE JULB
+C908;C908;110C 116E 11B3;C908;110C 116E 11B3; # (줈; 줈; 줈; 줈; 줈; ) HANGUL SYLLABLE JULS
+C909;C909;110C 116E 11B4;C909;110C 116E 11B4; # (줉; 줉; 줉; 줉; 줉; ) HANGUL SYLLABLE JULT
+C90A;C90A;110C 116E 11B5;C90A;110C 116E 11B5; # (줊; 줊; 줊; 줊; 줊; ) HANGUL SYLLABLE JULP
+C90B;C90B;110C 116E 11B6;C90B;110C 116E 11B6; # (줋; 줋; 줋; 줋; 줋; ) HANGUL SYLLABLE JULH
+C90C;C90C;110C 116E 11B7;C90C;110C 116E 11B7; # (줌; 줌; 줌; 줌; 줌; ) HANGUL SYLLABLE JUM
+C90D;C90D;110C 116E 11B8;C90D;110C 116E 11B8; # (ì¤; ì¤; 줍; ì¤; 줍; ) HANGUL SYLLABLE JUB
+C90E;C90E;110C 116E 11B9;C90E;110C 116E 11B9; # (줎; 줎; 줎; 줎; 줎; ) HANGUL SYLLABLE JUBS
+C90F;C90F;110C 116E 11BA;C90F;110C 116E 11BA; # (ì¤; ì¤; 줏; ì¤; 줏; ) HANGUL SYLLABLE JUS
+C910;C910;110C 116E 11BB;C910;110C 116E 11BB; # (ì¤; ì¤; 줐; ì¤; 줐; ) HANGUL SYLLABLE JUSS
+C911;C911;110C 116E 11BC;C911;110C 116E 11BC; # (중; 중; 중; 중; 중; ) HANGUL SYLLABLE JUNG
+C912;C912;110C 116E 11BD;C912;110C 116E 11BD; # (줒; 줒; 줒; 줒; 줒; ) HANGUL SYLLABLE JUJ
+C913;C913;110C 116E 11BE;C913;110C 116E 11BE; # (줓; 줓; 줓; 줓; 줓; ) HANGUL SYLLABLE JUC
+C914;C914;110C 116E 11BF;C914;110C 116E 11BF; # (줔; 줔; 줔; 줔; 줔; ) HANGUL SYLLABLE JUK
+C915;C915;110C 116E 11C0;C915;110C 116E 11C0; # (줕; 줕; 줕; 줕; 줕; ) HANGUL SYLLABLE JUT
+C916;C916;110C 116E 11C1;C916;110C 116E 11C1; # (줖; 줖; 주á‡; 줖; 주á‡; ) HANGUL SYLLABLE JUP
+C917;C917;110C 116E 11C2;C917;110C 116E 11C2; # (줗; 줗; 줗; 줗; 줗; ) HANGUL SYLLABLE JUH
+C918;C918;110C 116F;C918;110C 116F; # (줘; 줘; 줘; 줘; 줘; ) HANGUL SYLLABLE JWEO
+C919;C919;110C 116F 11A8;C919;110C 116F 11A8; # (줙; 줙; 줙; 줙; 줙; ) HANGUL SYLLABLE JWEOG
+C91A;C91A;110C 116F 11A9;C91A;110C 116F 11A9; # (줚; 줚; 줚; 줚; 줚; ) HANGUL SYLLABLE JWEOGG
+C91B;C91B;110C 116F 11AA;C91B;110C 116F 11AA; # (줛; 줛; 줛; 줛; 줛; ) HANGUL SYLLABLE JWEOGS
+C91C;C91C;110C 116F 11AB;C91C;110C 116F 11AB; # (줜; 줜; 줜; 줜; 줜; ) HANGUL SYLLABLE JWEON
+C91D;C91D;110C 116F 11AC;C91D;110C 116F 11AC; # (ì¤; ì¤; 줝; ì¤; 줝; ) HANGUL SYLLABLE JWEONJ
+C91E;C91E;110C 116F 11AD;C91E;110C 116F 11AD; # (줞; 줞; 줞; 줞; 줞; ) HANGUL SYLLABLE JWEONH
+C91F;C91F;110C 116F 11AE;C91F;110C 116F 11AE; # (줟; 줟; 줟; 줟; 줟; ) HANGUL SYLLABLE JWEOD
+C920;C920;110C 116F 11AF;C920;110C 116F 11AF; # (줠; 줠; 줠; 줠; 줠; ) HANGUL SYLLABLE JWEOL
+C921;C921;110C 116F 11B0;C921;110C 116F 11B0; # (줡; 줡; 줡; 줡; 줡; ) HANGUL SYLLABLE JWEOLG
+C922;C922;110C 116F 11B1;C922;110C 116F 11B1; # (줢; 줢; 줢; 줢; 줢; ) HANGUL SYLLABLE JWEOLM
+C923;C923;110C 116F 11B2;C923;110C 116F 11B2; # (줣; 줣; 줣; 줣; 줣; ) HANGUL SYLLABLE JWEOLB
+C924;C924;110C 116F 11B3;C924;110C 116F 11B3; # (줤; 줤; 줤; 줤; 줤; ) HANGUL SYLLABLE JWEOLS
+C925;C925;110C 116F 11B4;C925;110C 116F 11B4; # (줥; 줥; 줥; 줥; 줥; ) HANGUL SYLLABLE JWEOLT
+C926;C926;110C 116F 11B5;C926;110C 116F 11B5; # (줦; 줦; 줦; 줦; 줦; ) HANGUL SYLLABLE JWEOLP
+C927;C927;110C 116F 11B6;C927;110C 116F 11B6; # (줧; 줧; 줧; 줧; 줧; ) HANGUL SYLLABLE JWEOLH
+C928;C928;110C 116F 11B7;C928;110C 116F 11B7; # (줨; 줨; 줨; 줨; 줨; ) HANGUL SYLLABLE JWEOM
+C929;C929;110C 116F 11B8;C929;110C 116F 11B8; # (줩; 줩; 줩; 줩; 줩; ) HANGUL SYLLABLE JWEOB
+C92A;C92A;110C 116F 11B9;C92A;110C 116F 11B9; # (줪; 줪; 줪; 줪; 줪; ) HANGUL SYLLABLE JWEOBS
+C92B;C92B;110C 116F 11BA;C92B;110C 116F 11BA; # (줫; 줫; 줫; 줫; 줫; ) HANGUL SYLLABLE JWEOS
+C92C;C92C;110C 116F 11BB;C92C;110C 116F 11BB; # (줬; 줬; 줬; 줬; 줬; ) HANGUL SYLLABLE JWEOSS
+C92D;C92D;110C 116F 11BC;C92D;110C 116F 11BC; # (줭; 줭; 줭; 줭; 줭; ) HANGUL SYLLABLE JWEONG
+C92E;C92E;110C 116F 11BD;C92E;110C 116F 11BD; # (줮; 줮; 줮; 줮; 줮; ) HANGUL SYLLABLE JWEOJ
+C92F;C92F;110C 116F 11BE;C92F;110C 116F 11BE; # (줯; 줯; 줯; 줯; 줯; ) HANGUL SYLLABLE JWEOC
+C930;C930;110C 116F 11BF;C930;110C 116F 11BF; # (줰; 줰; 줰; 줰; 줰; ) HANGUL SYLLABLE JWEOK
+C931;C931;110C 116F 11C0;C931;110C 116F 11C0; # (줱; 줱; 줱; 줱; 줱; ) HANGUL SYLLABLE JWEOT
+C932;C932;110C 116F 11C1;C932;110C 116F 11C1; # (줲; 줲; 줘á‡; 줲; 줘á‡; ) HANGUL SYLLABLE JWEOP
+C933;C933;110C 116F 11C2;C933;110C 116F 11C2; # (줳; 줳; 줳; 줳; 줳; ) HANGUL SYLLABLE JWEOH
+C934;C934;110C 1170;C934;110C 1170; # (줴; 줴; 줴; 줴; 줴; ) HANGUL SYLLABLE JWE
+C935;C935;110C 1170 11A8;C935;110C 1170 11A8; # (줵; 줵; 줵; 줵; 줵; ) HANGUL SYLLABLE JWEG
+C936;C936;110C 1170 11A9;C936;110C 1170 11A9; # (줶; 줶; 줶; 줶; 줶; ) HANGUL SYLLABLE JWEGG
+C937;C937;110C 1170 11AA;C937;110C 1170 11AA; # (줷; 줷; 줷; 줷; 줷; ) HANGUL SYLLABLE JWEGS
+C938;C938;110C 1170 11AB;C938;110C 1170 11AB; # (줸; 줸; 줸; 줸; 줸; ) HANGUL SYLLABLE JWEN
+C939;C939;110C 1170 11AC;C939;110C 1170 11AC; # (줹; 줹; 줹; 줹; 줹; ) HANGUL SYLLABLE JWENJ
+C93A;C93A;110C 1170 11AD;C93A;110C 1170 11AD; # (줺; 줺; 줺; 줺; 줺; ) HANGUL SYLLABLE JWENH
+C93B;C93B;110C 1170 11AE;C93B;110C 1170 11AE; # (줻; 줻; 줻; 줻; 줻; ) HANGUL SYLLABLE JWED
+C93C;C93C;110C 1170 11AF;C93C;110C 1170 11AF; # (줼; 줼; 줼; 줼; 줼; ) HANGUL SYLLABLE JWEL
+C93D;C93D;110C 1170 11B0;C93D;110C 1170 11B0; # (줽; 줽; 줽; 줽; 줽; ) HANGUL SYLLABLE JWELG
+C93E;C93E;110C 1170 11B1;C93E;110C 1170 11B1; # (줾; 줾; 줾; 줾; 줾; ) HANGUL SYLLABLE JWELM
+C93F;C93F;110C 1170 11B2;C93F;110C 1170 11B2; # (줿; 줿; 줿; 줿; 줿; ) HANGUL SYLLABLE JWELB
+C940;C940;110C 1170 11B3;C940;110C 1170 11B3; # (쥀; 쥀; 쥀; 쥀; 쥀; ) HANGUL SYLLABLE JWELS
+C941;C941;110C 1170 11B4;C941;110C 1170 11B4; # (ì¥; ì¥; 쥁; ì¥; 쥁; ) HANGUL SYLLABLE JWELT
+C942;C942;110C 1170 11B5;C942;110C 1170 11B5; # (쥂; 쥂; 쥂; 쥂; 쥂; ) HANGUL SYLLABLE JWELP
+C943;C943;110C 1170 11B6;C943;110C 1170 11B6; # (쥃; 쥃; 쥃; 쥃; 쥃; ) HANGUL SYLLABLE JWELH
+C944;C944;110C 1170 11B7;C944;110C 1170 11B7; # (쥄; 쥄; 쥄; 쥄; 쥄; ) HANGUL SYLLABLE JWEM
+C945;C945;110C 1170 11B8;C945;110C 1170 11B8; # (쥅; 쥅; 쥅; 쥅; 쥅; ) HANGUL SYLLABLE JWEB
+C946;C946;110C 1170 11B9;C946;110C 1170 11B9; # (쥆; 쥆; 쥆; 쥆; 쥆; ) HANGUL SYLLABLE JWEBS
+C947;C947;110C 1170 11BA;C947;110C 1170 11BA; # (쥇; 쥇; 쥇; 쥇; 쥇; ) HANGUL SYLLABLE JWES
+C948;C948;110C 1170 11BB;C948;110C 1170 11BB; # (쥈; 쥈; 쥈; 쥈; 쥈; ) HANGUL SYLLABLE JWESS
+C949;C949;110C 1170 11BC;C949;110C 1170 11BC; # (쥉; 쥉; 쥉; 쥉; 쥉; ) HANGUL SYLLABLE JWENG
+C94A;C94A;110C 1170 11BD;C94A;110C 1170 11BD; # (쥊; 쥊; 쥊; 쥊; 쥊; ) HANGUL SYLLABLE JWEJ
+C94B;C94B;110C 1170 11BE;C94B;110C 1170 11BE; # (쥋; 쥋; 쥋; 쥋; 쥋; ) HANGUL SYLLABLE JWEC
+C94C;C94C;110C 1170 11BF;C94C;110C 1170 11BF; # (쥌; 쥌; 쥌; 쥌; 쥌; ) HANGUL SYLLABLE JWEK
+C94D;C94D;110C 1170 11C0;C94D;110C 1170 11C0; # (ì¥; ì¥; 쥍; ì¥; 쥍; ) HANGUL SYLLABLE JWET
+C94E;C94E;110C 1170 11C1;C94E;110C 1170 11C1; # (쥎; 쥎; 줴á‡; 쥎; 줴á‡; ) HANGUL SYLLABLE JWEP
+C94F;C94F;110C 1170 11C2;C94F;110C 1170 11C2; # (ì¥; ì¥; 쥏; ì¥; 쥏; ) HANGUL SYLLABLE JWEH
+C950;C950;110C 1171;C950;110C 1171; # (ì¥; ì¥; 쥐; ì¥; 쥐; ) HANGUL SYLLABLE JWI
+C951;C951;110C 1171 11A8;C951;110C 1171 11A8; # (쥑; 쥑; 쥑; 쥑; 쥑; ) HANGUL SYLLABLE JWIG
+C952;C952;110C 1171 11A9;C952;110C 1171 11A9; # (쥒; 쥒; 쥒; 쥒; 쥒; ) HANGUL SYLLABLE JWIGG
+C953;C953;110C 1171 11AA;C953;110C 1171 11AA; # (쥓; 쥓; 쥓; 쥓; 쥓; ) HANGUL SYLLABLE JWIGS
+C954;C954;110C 1171 11AB;C954;110C 1171 11AB; # (쥔; 쥔; 쥔; 쥔; 쥔; ) HANGUL SYLLABLE JWIN
+C955;C955;110C 1171 11AC;C955;110C 1171 11AC; # (쥕; 쥕; 쥕; 쥕; 쥕; ) HANGUL SYLLABLE JWINJ
+C956;C956;110C 1171 11AD;C956;110C 1171 11AD; # (쥖; 쥖; 쥖; 쥖; 쥖; ) HANGUL SYLLABLE JWINH
+C957;C957;110C 1171 11AE;C957;110C 1171 11AE; # (쥗; 쥗; 쥗; 쥗; 쥗; ) HANGUL SYLLABLE JWID
+C958;C958;110C 1171 11AF;C958;110C 1171 11AF; # (쥘; 쥘; 쥘; 쥘; 쥘; ) HANGUL SYLLABLE JWIL
+C959;C959;110C 1171 11B0;C959;110C 1171 11B0; # (쥙; 쥙; 쥙; 쥙; 쥙; ) HANGUL SYLLABLE JWILG
+C95A;C95A;110C 1171 11B1;C95A;110C 1171 11B1; # (쥚; 쥚; 쥚; 쥚; 쥚; ) HANGUL SYLLABLE JWILM
+C95B;C95B;110C 1171 11B2;C95B;110C 1171 11B2; # (쥛; 쥛; 쥛; 쥛; 쥛; ) HANGUL SYLLABLE JWILB
+C95C;C95C;110C 1171 11B3;C95C;110C 1171 11B3; # (쥜; 쥜; 쥜; 쥜; 쥜; ) HANGUL SYLLABLE JWILS
+C95D;C95D;110C 1171 11B4;C95D;110C 1171 11B4; # (ì¥; ì¥; 쥝; ì¥; 쥝; ) HANGUL SYLLABLE JWILT
+C95E;C95E;110C 1171 11B5;C95E;110C 1171 11B5; # (쥞; 쥞; 쥞; 쥞; 쥞; ) HANGUL SYLLABLE JWILP
+C95F;C95F;110C 1171 11B6;C95F;110C 1171 11B6; # (쥟; 쥟; 쥟; 쥟; 쥟; ) HANGUL SYLLABLE JWILH
+C960;C960;110C 1171 11B7;C960;110C 1171 11B7; # (쥠; 쥠; 쥠; 쥠; 쥠; ) HANGUL SYLLABLE JWIM
+C961;C961;110C 1171 11B8;C961;110C 1171 11B8; # (쥡; 쥡; 쥡; 쥡; 쥡; ) HANGUL SYLLABLE JWIB
+C962;C962;110C 1171 11B9;C962;110C 1171 11B9; # (쥢; 쥢; 쥢; 쥢; 쥢; ) HANGUL SYLLABLE JWIBS
+C963;C963;110C 1171 11BA;C963;110C 1171 11BA; # (쥣; 쥣; 쥣; 쥣; 쥣; ) HANGUL SYLLABLE JWIS
+C964;C964;110C 1171 11BB;C964;110C 1171 11BB; # (쥤; 쥤; 쥤; 쥤; 쥤; ) HANGUL SYLLABLE JWISS
+C965;C965;110C 1171 11BC;C965;110C 1171 11BC; # (쥥; 쥥; 쥥; 쥥; 쥥; ) HANGUL SYLLABLE JWING
+C966;C966;110C 1171 11BD;C966;110C 1171 11BD; # (쥦; 쥦; 쥦; 쥦; 쥦; ) HANGUL SYLLABLE JWIJ
+C967;C967;110C 1171 11BE;C967;110C 1171 11BE; # (쥧; 쥧; 쥧; 쥧; 쥧; ) HANGUL SYLLABLE JWIC
+C968;C968;110C 1171 11BF;C968;110C 1171 11BF; # (쥨; 쥨; 쥨; 쥨; 쥨; ) HANGUL SYLLABLE JWIK
+C969;C969;110C 1171 11C0;C969;110C 1171 11C0; # (쥩; 쥩; 쥩; 쥩; 쥩; ) HANGUL SYLLABLE JWIT
+C96A;C96A;110C 1171 11C1;C96A;110C 1171 11C1; # (쥪; 쥪; 쥐á‡; 쥪; 쥐á‡; ) HANGUL SYLLABLE JWIP
+C96B;C96B;110C 1171 11C2;C96B;110C 1171 11C2; # (쥫; 쥫; 쥫; 쥫; 쥫; ) HANGUL SYLLABLE JWIH
+C96C;C96C;110C 1172;C96C;110C 1172; # (쥬; 쥬; 쥬; 쥬; 쥬; ) HANGUL SYLLABLE JYU
+C96D;C96D;110C 1172 11A8;C96D;110C 1172 11A8; # (쥭; 쥭; 쥭; 쥭; 쥭; ) HANGUL SYLLABLE JYUG
+C96E;C96E;110C 1172 11A9;C96E;110C 1172 11A9; # (쥮; 쥮; 쥮; 쥮; 쥮; ) HANGUL SYLLABLE JYUGG
+C96F;C96F;110C 1172 11AA;C96F;110C 1172 11AA; # (쥯; 쥯; 쥯; 쥯; 쥯; ) HANGUL SYLLABLE JYUGS
+C970;C970;110C 1172 11AB;C970;110C 1172 11AB; # (쥰; 쥰; 쥰; 쥰; 쥰; ) HANGUL SYLLABLE JYUN
+C971;C971;110C 1172 11AC;C971;110C 1172 11AC; # (쥱; 쥱; 쥱; 쥱; 쥱; ) HANGUL SYLLABLE JYUNJ
+C972;C972;110C 1172 11AD;C972;110C 1172 11AD; # (쥲; 쥲; 쥲; 쥲; 쥲; ) HANGUL SYLLABLE JYUNH
+C973;C973;110C 1172 11AE;C973;110C 1172 11AE; # (쥳; 쥳; 쥳; 쥳; 쥳; ) HANGUL SYLLABLE JYUD
+C974;C974;110C 1172 11AF;C974;110C 1172 11AF; # (쥴; 쥴; 쥴; 쥴; 쥴; ) HANGUL SYLLABLE JYUL
+C975;C975;110C 1172 11B0;C975;110C 1172 11B0; # (쥵; 쥵; 쥵; 쥵; 쥵; ) HANGUL SYLLABLE JYULG
+C976;C976;110C 1172 11B1;C976;110C 1172 11B1; # (쥶; 쥶; 쥶; 쥶; 쥶; ) HANGUL SYLLABLE JYULM
+C977;C977;110C 1172 11B2;C977;110C 1172 11B2; # (쥷; 쥷; 쥷; 쥷; 쥷; ) HANGUL SYLLABLE JYULB
+C978;C978;110C 1172 11B3;C978;110C 1172 11B3; # (쥸; 쥸; 쥸; 쥸; 쥸; ) HANGUL SYLLABLE JYULS
+C979;C979;110C 1172 11B4;C979;110C 1172 11B4; # (쥹; 쥹; 쥹; 쥹; 쥹; ) HANGUL SYLLABLE JYULT
+C97A;C97A;110C 1172 11B5;C97A;110C 1172 11B5; # (쥺; 쥺; 쥺; 쥺; 쥺; ) HANGUL SYLLABLE JYULP
+C97B;C97B;110C 1172 11B6;C97B;110C 1172 11B6; # (쥻; 쥻; 쥻; 쥻; 쥻; ) HANGUL SYLLABLE JYULH
+C97C;C97C;110C 1172 11B7;C97C;110C 1172 11B7; # (쥼; 쥼; 쥼; 쥼; 쥼; ) HANGUL SYLLABLE JYUM
+C97D;C97D;110C 1172 11B8;C97D;110C 1172 11B8; # (쥽; 쥽; 쥽; 쥽; 쥽; ) HANGUL SYLLABLE JYUB
+C97E;C97E;110C 1172 11B9;C97E;110C 1172 11B9; # (쥾; 쥾; 쥾; 쥾; 쥾; ) HANGUL SYLLABLE JYUBS
+C97F;C97F;110C 1172 11BA;C97F;110C 1172 11BA; # (쥿; 쥿; 쥿; 쥿; 쥿; ) HANGUL SYLLABLE JYUS
+C980;C980;110C 1172 11BB;C980;110C 1172 11BB; # (즀; 즀; 즀; 즀; 즀; ) HANGUL SYLLABLE JYUSS
+C981;C981;110C 1172 11BC;C981;110C 1172 11BC; # (ì¦; ì¦; 즁; ì¦; 즁; ) HANGUL SYLLABLE JYUNG
+C982;C982;110C 1172 11BD;C982;110C 1172 11BD; # (즂; 즂; 즂; 즂; 즂; ) HANGUL SYLLABLE JYUJ
+C983;C983;110C 1172 11BE;C983;110C 1172 11BE; # (즃; 즃; 즃; 즃; 즃; ) HANGUL SYLLABLE JYUC
+C984;C984;110C 1172 11BF;C984;110C 1172 11BF; # (즄; 즄; 즄; 즄; 즄; ) HANGUL SYLLABLE JYUK
+C985;C985;110C 1172 11C0;C985;110C 1172 11C0; # (즅; 즅; 즅; 즅; 즅; ) HANGUL SYLLABLE JYUT
+C986;C986;110C 1172 11C1;C986;110C 1172 11C1; # (즆; 즆; 쥬á‡; 즆; 쥬á‡; ) HANGUL SYLLABLE JYUP
+C987;C987;110C 1172 11C2;C987;110C 1172 11C2; # (즇; 즇; 즇; 즇; 즇; ) HANGUL SYLLABLE JYUH
+C988;C988;110C 1173;C988;110C 1173; # (즈; 즈; 즈; 즈; 즈; ) HANGUL SYLLABLE JEU
+C989;C989;110C 1173 11A8;C989;110C 1173 11A8; # (즉; 즉; 즉; 즉; 즉; ) HANGUL SYLLABLE JEUG
+C98A;C98A;110C 1173 11A9;C98A;110C 1173 11A9; # (즊; 즊; 즊; 즊; 즊; ) HANGUL SYLLABLE JEUGG
+C98B;C98B;110C 1173 11AA;C98B;110C 1173 11AA; # (즋; 즋; 즋; 즋; 즋; ) HANGUL SYLLABLE JEUGS
+C98C;C98C;110C 1173 11AB;C98C;110C 1173 11AB; # (즌; 즌; 즌; 즌; 즌; ) HANGUL SYLLABLE JEUN
+C98D;C98D;110C 1173 11AC;C98D;110C 1173 11AC; # (ì¦; ì¦; 즍; ì¦; 즍; ) HANGUL SYLLABLE JEUNJ
+C98E;C98E;110C 1173 11AD;C98E;110C 1173 11AD; # (즎; 즎; 즎; 즎; 즎; ) HANGUL SYLLABLE JEUNH
+C98F;C98F;110C 1173 11AE;C98F;110C 1173 11AE; # (ì¦; ì¦; 즏; ì¦; 즏; ) HANGUL SYLLABLE JEUD
+C990;C990;110C 1173 11AF;C990;110C 1173 11AF; # (ì¦; ì¦; 즐; ì¦; 즐; ) HANGUL SYLLABLE JEUL
+C991;C991;110C 1173 11B0;C991;110C 1173 11B0; # (즑; 즑; 즑; 즑; 즑; ) HANGUL SYLLABLE JEULG
+C992;C992;110C 1173 11B1;C992;110C 1173 11B1; # (즒; 즒; 즒; 즒; 즒; ) HANGUL SYLLABLE JEULM
+C993;C993;110C 1173 11B2;C993;110C 1173 11B2; # (즓; 즓; 즓; 즓; 즓; ) HANGUL SYLLABLE JEULB
+C994;C994;110C 1173 11B3;C994;110C 1173 11B3; # (즔; 즔; 즔; 즔; 즔; ) HANGUL SYLLABLE JEULS
+C995;C995;110C 1173 11B4;C995;110C 1173 11B4; # (즕; 즕; 즕; 즕; 즕; ) HANGUL SYLLABLE JEULT
+C996;C996;110C 1173 11B5;C996;110C 1173 11B5; # (즖; 즖; 즖; 즖; 즖; ) HANGUL SYLLABLE JEULP
+C997;C997;110C 1173 11B6;C997;110C 1173 11B6; # (즗; 즗; 즗; 즗; 즗; ) HANGUL SYLLABLE JEULH
+C998;C998;110C 1173 11B7;C998;110C 1173 11B7; # (즘; 즘; 즘; 즘; 즘; ) HANGUL SYLLABLE JEUM
+C999;C999;110C 1173 11B8;C999;110C 1173 11B8; # (즙; 즙; 즙; 즙; 즙; ) HANGUL SYLLABLE JEUB
+C99A;C99A;110C 1173 11B9;C99A;110C 1173 11B9; # (즚; 즚; 즚; 즚; 즚; ) HANGUL SYLLABLE JEUBS
+C99B;C99B;110C 1173 11BA;C99B;110C 1173 11BA; # (즛; 즛; 즛; 즛; 즛; ) HANGUL SYLLABLE JEUS
+C99C;C99C;110C 1173 11BB;C99C;110C 1173 11BB; # (즜; 즜; 즜; 즜; 즜; ) HANGUL SYLLABLE JEUSS
+C99D;C99D;110C 1173 11BC;C99D;110C 1173 11BC; # (ì¦; ì¦; 증; ì¦; 증; ) HANGUL SYLLABLE JEUNG
+C99E;C99E;110C 1173 11BD;C99E;110C 1173 11BD; # (즞; 즞; 즞; 즞; 즞; ) HANGUL SYLLABLE JEUJ
+C99F;C99F;110C 1173 11BE;C99F;110C 1173 11BE; # (즟; 즟; 즟; 즟; 즟; ) HANGUL SYLLABLE JEUC
+C9A0;C9A0;110C 1173 11BF;C9A0;110C 1173 11BF; # (즠; 즠; 즠; 즠; 즠; ) HANGUL SYLLABLE JEUK
+C9A1;C9A1;110C 1173 11C0;C9A1;110C 1173 11C0; # (즡; 즡; 즡; 즡; 즡; ) HANGUL SYLLABLE JEUT
+C9A2;C9A2;110C 1173 11C1;C9A2;110C 1173 11C1; # (즢; 즢; 즈á‡; 즢; 즈á‡; ) HANGUL SYLLABLE JEUP
+C9A3;C9A3;110C 1173 11C2;C9A3;110C 1173 11C2; # (즣; 즣; 즣; 즣; 즣; ) HANGUL SYLLABLE JEUH
+C9A4;C9A4;110C 1174;C9A4;110C 1174; # (즤; 즤; 즤; 즤; 즤; ) HANGUL SYLLABLE JYI
+C9A5;C9A5;110C 1174 11A8;C9A5;110C 1174 11A8; # (즥; 즥; 즥; 즥; 즥; ) HANGUL SYLLABLE JYIG
+C9A6;C9A6;110C 1174 11A9;C9A6;110C 1174 11A9; # (즦; 즦; 즦; 즦; 즦; ) HANGUL SYLLABLE JYIGG
+C9A7;C9A7;110C 1174 11AA;C9A7;110C 1174 11AA; # (즧; 즧; 즧; 즧; 즧; ) HANGUL SYLLABLE JYIGS
+C9A8;C9A8;110C 1174 11AB;C9A8;110C 1174 11AB; # (즨; 즨; 즨; 즨; 즨; ) HANGUL SYLLABLE JYIN
+C9A9;C9A9;110C 1174 11AC;C9A9;110C 1174 11AC; # (즩; 즩; 즩; 즩; 즩; ) HANGUL SYLLABLE JYINJ
+C9AA;C9AA;110C 1174 11AD;C9AA;110C 1174 11AD; # (즪; 즪; 즪; 즪; 즪; ) HANGUL SYLLABLE JYINH
+C9AB;C9AB;110C 1174 11AE;C9AB;110C 1174 11AE; # (즫; 즫; 즫; 즫; 즫; ) HANGUL SYLLABLE JYID
+C9AC;C9AC;110C 1174 11AF;C9AC;110C 1174 11AF; # (즬; 즬; 즬; 즬; 즬; ) HANGUL SYLLABLE JYIL
+C9AD;C9AD;110C 1174 11B0;C9AD;110C 1174 11B0; # (즭; 즭; 즭; 즭; 즭; ) HANGUL SYLLABLE JYILG
+C9AE;C9AE;110C 1174 11B1;C9AE;110C 1174 11B1; # (즮; 즮; 즮; 즮; 즮; ) HANGUL SYLLABLE JYILM
+C9AF;C9AF;110C 1174 11B2;C9AF;110C 1174 11B2; # (즯; 즯; 즯; 즯; 즯; ) HANGUL SYLLABLE JYILB
+C9B0;C9B0;110C 1174 11B3;C9B0;110C 1174 11B3; # (즰; 즰; 즰; 즰; 즰; ) HANGUL SYLLABLE JYILS
+C9B1;C9B1;110C 1174 11B4;C9B1;110C 1174 11B4; # (즱; 즱; 즱; 즱; 즱; ) HANGUL SYLLABLE JYILT
+C9B2;C9B2;110C 1174 11B5;C9B2;110C 1174 11B5; # (즲; 즲; 즲; 즲; 즲; ) HANGUL SYLLABLE JYILP
+C9B3;C9B3;110C 1174 11B6;C9B3;110C 1174 11B6; # (즳; 즳; 즳; 즳; 즳; ) HANGUL SYLLABLE JYILH
+C9B4;C9B4;110C 1174 11B7;C9B4;110C 1174 11B7; # (즴; 즴; 즴; 즴; 즴; ) HANGUL SYLLABLE JYIM
+C9B5;C9B5;110C 1174 11B8;C9B5;110C 1174 11B8; # (즵; 즵; 즵; 즵; 즵; ) HANGUL SYLLABLE JYIB
+C9B6;C9B6;110C 1174 11B9;C9B6;110C 1174 11B9; # (즶; 즶; 즶; 즶; 즶; ) HANGUL SYLLABLE JYIBS
+C9B7;C9B7;110C 1174 11BA;C9B7;110C 1174 11BA; # (즷; 즷; 즷; 즷; 즷; ) HANGUL SYLLABLE JYIS
+C9B8;C9B8;110C 1174 11BB;C9B8;110C 1174 11BB; # (즸; 즸; 즸; 즸; 즸; ) HANGUL SYLLABLE JYISS
+C9B9;C9B9;110C 1174 11BC;C9B9;110C 1174 11BC; # (즹; 즹; 즹; 즹; 즹; ) HANGUL SYLLABLE JYING
+C9BA;C9BA;110C 1174 11BD;C9BA;110C 1174 11BD; # (즺; 즺; 즺; 즺; 즺; ) HANGUL SYLLABLE JYIJ
+C9BB;C9BB;110C 1174 11BE;C9BB;110C 1174 11BE; # (즻; 즻; 즻; 즻; 즻; ) HANGUL SYLLABLE JYIC
+C9BC;C9BC;110C 1174 11BF;C9BC;110C 1174 11BF; # (즼; 즼; 즼; 즼; 즼; ) HANGUL SYLLABLE JYIK
+C9BD;C9BD;110C 1174 11C0;C9BD;110C 1174 11C0; # (즽; 즽; 즽; 즽; 즽; ) HANGUL SYLLABLE JYIT
+C9BE;C9BE;110C 1174 11C1;C9BE;110C 1174 11C1; # (즾; 즾; 즤á‡; 즾; 즤á‡; ) HANGUL SYLLABLE JYIP
+C9BF;C9BF;110C 1174 11C2;C9BF;110C 1174 11C2; # (즿; 즿; 즿; 즿; 즿; ) HANGUL SYLLABLE JYIH
+C9C0;C9C0;110C 1175;C9C0;110C 1175; # (지; 지; 지; 지; 지; ) HANGUL SYLLABLE JI
+C9C1;C9C1;110C 1175 11A8;C9C1;110C 1175 11A8; # (ì§; ì§; 직; ì§; 직; ) HANGUL SYLLABLE JIG
+C9C2;C9C2;110C 1175 11A9;C9C2;110C 1175 11A9; # (짂; 짂; 짂; 짂; 짂; ) HANGUL SYLLABLE JIGG
+C9C3;C9C3;110C 1175 11AA;C9C3;110C 1175 11AA; # (짃; 짃; 짃; 짃; 짃; ) HANGUL SYLLABLE JIGS
+C9C4;C9C4;110C 1175 11AB;C9C4;110C 1175 11AB; # (진; 진; 진; 진; 진; ) HANGUL SYLLABLE JIN
+C9C5;C9C5;110C 1175 11AC;C9C5;110C 1175 11AC; # (짅; 짅; 짅; 짅; 짅; ) HANGUL SYLLABLE JINJ
+C9C6;C9C6;110C 1175 11AD;C9C6;110C 1175 11AD; # (짆; 짆; 짆; 짆; 짆; ) HANGUL SYLLABLE JINH
+C9C7;C9C7;110C 1175 11AE;C9C7;110C 1175 11AE; # (짇; 짇; 짇; 짇; 짇; ) HANGUL SYLLABLE JID
+C9C8;C9C8;110C 1175 11AF;C9C8;110C 1175 11AF; # (질; 질; 질; 질; 질; ) HANGUL SYLLABLE JIL
+C9C9;C9C9;110C 1175 11B0;C9C9;110C 1175 11B0; # (짉; 짉; 짉; 짉; 짉; ) HANGUL SYLLABLE JILG
+C9CA;C9CA;110C 1175 11B1;C9CA;110C 1175 11B1; # (짊; 짊; 짊; 짊; 짊; ) HANGUL SYLLABLE JILM
+C9CB;C9CB;110C 1175 11B2;C9CB;110C 1175 11B2; # (짋; 짋; 짋; 짋; 짋; ) HANGUL SYLLABLE JILB
+C9CC;C9CC;110C 1175 11B3;C9CC;110C 1175 11B3; # (짌; 짌; 짌; 짌; 짌; ) HANGUL SYLLABLE JILS
+C9CD;C9CD;110C 1175 11B4;C9CD;110C 1175 11B4; # (ì§; ì§; 짍; ì§; 짍; ) HANGUL SYLLABLE JILT
+C9CE;C9CE;110C 1175 11B5;C9CE;110C 1175 11B5; # (짎; 짎; 짎; 짎; 짎; ) HANGUL SYLLABLE JILP
+C9CF;C9CF;110C 1175 11B6;C9CF;110C 1175 11B6; # (ì§; ì§; 짏; ì§; 짏; ) HANGUL SYLLABLE JILH
+C9D0;C9D0;110C 1175 11B7;C9D0;110C 1175 11B7; # (ì§; ì§; 짐; ì§; 짐; ) HANGUL SYLLABLE JIM
+C9D1;C9D1;110C 1175 11B8;C9D1;110C 1175 11B8; # (집; 집; 집; 집; 집; ) HANGUL SYLLABLE JIB
+C9D2;C9D2;110C 1175 11B9;C9D2;110C 1175 11B9; # (짒; 짒; 짒; 짒; 짒; ) HANGUL SYLLABLE JIBS
+C9D3;C9D3;110C 1175 11BA;C9D3;110C 1175 11BA; # (짓; 짓; 짓; 짓; 짓; ) HANGUL SYLLABLE JIS
+C9D4;C9D4;110C 1175 11BB;C9D4;110C 1175 11BB; # (짔; 짔; 짔; 짔; 짔; ) HANGUL SYLLABLE JISS
+C9D5;C9D5;110C 1175 11BC;C9D5;110C 1175 11BC; # (징; 징; 징; 징; 징; ) HANGUL SYLLABLE JING
+C9D6;C9D6;110C 1175 11BD;C9D6;110C 1175 11BD; # (짖; 짖; 짖; 짖; 짖; ) HANGUL SYLLABLE JIJ
+C9D7;C9D7;110C 1175 11BE;C9D7;110C 1175 11BE; # (짗; 짗; 짗; 짗; 짗; ) HANGUL SYLLABLE JIC
+C9D8;C9D8;110C 1175 11BF;C9D8;110C 1175 11BF; # (짘; 짘; 짘; 짘; 짘; ) HANGUL SYLLABLE JIK
+C9D9;C9D9;110C 1175 11C0;C9D9;110C 1175 11C0; # (짙; 짙; 짙; 짙; 짙; ) HANGUL SYLLABLE JIT
+C9DA;C9DA;110C 1175 11C1;C9DA;110C 1175 11C1; # (짚; 짚; 지á‡; 짚; 지á‡; ) HANGUL SYLLABLE JIP
+C9DB;C9DB;110C 1175 11C2;C9DB;110C 1175 11C2; # (짛; 짛; 짛; 짛; 짛; ) HANGUL SYLLABLE JIH
+C9DC;C9DC;110D 1161;C9DC;110D 1161; # (짜; 짜; á„á…¡; 짜; á„á…¡; ) HANGUL SYLLABLE JJA
+C9DD;C9DD;110D 1161 11A8;C9DD;110D 1161 11A8; # (ì§; ì§; á„ᅡᆨ; ì§; á„ᅡᆨ; ) HANGUL SYLLABLE JJAG
+C9DE;C9DE;110D 1161 11A9;C9DE;110D 1161 11A9; # (짞; 짞; á„ᅡᆩ; 짞; á„ᅡᆩ; ) HANGUL SYLLABLE JJAGG
+C9DF;C9DF;110D 1161 11AA;C9DF;110D 1161 11AA; # (짟; 짟; á„ᅡᆪ; 짟; á„ᅡᆪ; ) HANGUL SYLLABLE JJAGS
+C9E0;C9E0;110D 1161 11AB;C9E0;110D 1161 11AB; # (짠; 짠; á„ᅡᆫ; 짠; á„ᅡᆫ; ) HANGUL SYLLABLE JJAN
+C9E1;C9E1;110D 1161 11AC;C9E1;110D 1161 11AC; # (짡; 짡; á„ᅡᆬ; 짡; á„ᅡᆬ; ) HANGUL SYLLABLE JJANJ
+C9E2;C9E2;110D 1161 11AD;C9E2;110D 1161 11AD; # (짢; 짢; á„ᅡᆭ; 짢; á„ᅡᆭ; ) HANGUL SYLLABLE JJANH
+C9E3;C9E3;110D 1161 11AE;C9E3;110D 1161 11AE; # (짣; 짣; á„ᅡᆮ; 짣; á„ᅡᆮ; ) HANGUL SYLLABLE JJAD
+C9E4;C9E4;110D 1161 11AF;C9E4;110D 1161 11AF; # (짤; 짤; á„ᅡᆯ; 짤; á„ᅡᆯ; ) HANGUL SYLLABLE JJAL
+C9E5;C9E5;110D 1161 11B0;C9E5;110D 1161 11B0; # (짥; 짥; á„ᅡᆰ; 짥; á„ᅡᆰ; ) HANGUL SYLLABLE JJALG
+C9E6;C9E6;110D 1161 11B1;C9E6;110D 1161 11B1; # (짦; 짦; á„ᅡᆱ; 짦; á„ᅡᆱ; ) HANGUL SYLLABLE JJALM
+C9E7;C9E7;110D 1161 11B2;C9E7;110D 1161 11B2; # (짧; 짧; á„ᅡᆲ; 짧; á„ᅡᆲ; ) HANGUL SYLLABLE JJALB
+C9E8;C9E8;110D 1161 11B3;C9E8;110D 1161 11B3; # (짨; 짨; á„ᅡᆳ; 짨; á„ᅡᆳ; ) HANGUL SYLLABLE JJALS
+C9E9;C9E9;110D 1161 11B4;C9E9;110D 1161 11B4; # (짩; 짩; á„ᅡᆴ; 짩; á„ᅡᆴ; ) HANGUL SYLLABLE JJALT
+C9EA;C9EA;110D 1161 11B5;C9EA;110D 1161 11B5; # (짪; 짪; á„ᅡᆵ; 짪; á„ᅡᆵ; ) HANGUL SYLLABLE JJALP
+C9EB;C9EB;110D 1161 11B6;C9EB;110D 1161 11B6; # (짫; 짫; á„ᅡᆶ; 짫; á„ᅡᆶ; ) HANGUL SYLLABLE JJALH
+C9EC;C9EC;110D 1161 11B7;C9EC;110D 1161 11B7; # (짬; 짬; á„ᅡᆷ; 짬; á„ᅡᆷ; ) HANGUL SYLLABLE JJAM
+C9ED;C9ED;110D 1161 11B8;C9ED;110D 1161 11B8; # (짭; 짭; á„ᅡᆸ; 짭; á„ᅡᆸ; ) HANGUL SYLLABLE JJAB
+C9EE;C9EE;110D 1161 11B9;C9EE;110D 1161 11B9; # (짮; 짮; á„ᅡᆹ; 짮; á„ᅡᆹ; ) HANGUL SYLLABLE JJABS
+C9EF;C9EF;110D 1161 11BA;C9EF;110D 1161 11BA; # (짯; 짯; á„ᅡᆺ; 짯; á„ᅡᆺ; ) HANGUL SYLLABLE JJAS
+C9F0;C9F0;110D 1161 11BB;C9F0;110D 1161 11BB; # (짰; 짰; á„ᅡᆻ; 짰; á„ᅡᆻ; ) HANGUL SYLLABLE JJASS
+C9F1;C9F1;110D 1161 11BC;C9F1;110D 1161 11BC; # (짱; 짱; á„ᅡᆼ; 짱; á„ᅡᆼ; ) HANGUL SYLLABLE JJANG
+C9F2;C9F2;110D 1161 11BD;C9F2;110D 1161 11BD; # (짲; 짲; á„ᅡᆽ; 짲; á„ᅡᆽ; ) HANGUL SYLLABLE JJAJ
+C9F3;C9F3;110D 1161 11BE;C9F3;110D 1161 11BE; # (짳; 짳; á„ᅡᆾ; 짳; á„ᅡᆾ; ) HANGUL SYLLABLE JJAC
+C9F4;C9F4;110D 1161 11BF;C9F4;110D 1161 11BF; # (짴; 짴; á„ᅡᆿ; 짴; á„ᅡᆿ; ) HANGUL SYLLABLE JJAK
+C9F5;C9F5;110D 1161 11C0;C9F5;110D 1161 11C0; # (짵; 짵; á„ᅡᇀ; 짵; á„ᅡᇀ; ) HANGUL SYLLABLE JJAT
+C9F6;C9F6;110D 1161 11C1;C9F6;110D 1161 11C1; # (짶; 짶; á„á…¡á‡; 짶; á„á…¡á‡; ) HANGUL SYLLABLE JJAP
+C9F7;C9F7;110D 1161 11C2;C9F7;110D 1161 11C2; # (짷; 짷; á„ᅡᇂ; 짷; á„ᅡᇂ; ) HANGUL SYLLABLE JJAH
+C9F8;C9F8;110D 1162;C9F8;110D 1162; # (째; 째; á„á…¢; 째; á„á…¢; ) HANGUL SYLLABLE JJAE
+C9F9;C9F9;110D 1162 11A8;C9F9;110D 1162 11A8; # (짹; 짹; á„ᅢᆨ; 짹; á„ᅢᆨ; ) HANGUL SYLLABLE JJAEG
+C9FA;C9FA;110D 1162 11A9;C9FA;110D 1162 11A9; # (짺; 짺; á„ᅢᆩ; 짺; á„ᅢᆩ; ) HANGUL SYLLABLE JJAEGG
+C9FB;C9FB;110D 1162 11AA;C9FB;110D 1162 11AA; # (짻; 짻; á„ᅢᆪ; 짻; á„ᅢᆪ; ) HANGUL SYLLABLE JJAEGS
+C9FC;C9FC;110D 1162 11AB;C9FC;110D 1162 11AB; # (짼; 짼; á„ᅢᆫ; 짼; á„ᅢᆫ; ) HANGUL SYLLABLE JJAEN
+C9FD;C9FD;110D 1162 11AC;C9FD;110D 1162 11AC; # (짽; 짽; á„ᅢᆬ; 짽; á„ᅢᆬ; ) HANGUL SYLLABLE JJAENJ
+C9FE;C9FE;110D 1162 11AD;C9FE;110D 1162 11AD; # (짾; 짾; á„ᅢᆭ; 짾; á„ᅢᆭ; ) HANGUL SYLLABLE JJAENH
+C9FF;C9FF;110D 1162 11AE;C9FF;110D 1162 11AE; # (짿; 짿; á„ᅢᆮ; 짿; á„ᅢᆮ; ) HANGUL SYLLABLE JJAED
+CA00;CA00;110D 1162 11AF;CA00;110D 1162 11AF; # (쨀; 쨀; á„ᅢᆯ; 쨀; á„ᅢᆯ; ) HANGUL SYLLABLE JJAEL
+CA01;CA01;110D 1162 11B0;CA01;110D 1162 11B0; # (ì¨; ì¨; á„ᅢᆰ; ì¨; á„ᅢᆰ; ) HANGUL SYLLABLE JJAELG
+CA02;CA02;110D 1162 11B1;CA02;110D 1162 11B1; # (쨂; 쨂; á„ᅢᆱ; 쨂; á„ᅢᆱ; ) HANGUL SYLLABLE JJAELM
+CA03;CA03;110D 1162 11B2;CA03;110D 1162 11B2; # (쨃; 쨃; á„ᅢᆲ; 쨃; á„ᅢᆲ; ) HANGUL SYLLABLE JJAELB
+CA04;CA04;110D 1162 11B3;CA04;110D 1162 11B3; # (쨄; 쨄; á„ᅢᆳ; 쨄; á„ᅢᆳ; ) HANGUL SYLLABLE JJAELS
+CA05;CA05;110D 1162 11B4;CA05;110D 1162 11B4; # (쨅; 쨅; á„ᅢᆴ; 쨅; á„ᅢᆴ; ) HANGUL SYLLABLE JJAELT
+CA06;CA06;110D 1162 11B5;CA06;110D 1162 11B5; # (쨆; 쨆; á„ᅢᆵ; 쨆; á„ᅢᆵ; ) HANGUL SYLLABLE JJAELP
+CA07;CA07;110D 1162 11B6;CA07;110D 1162 11B6; # (쨇; 쨇; á„ᅢᆶ; 쨇; á„ᅢᆶ; ) HANGUL SYLLABLE JJAELH
+CA08;CA08;110D 1162 11B7;CA08;110D 1162 11B7; # (쨈; 쨈; á„ᅢᆷ; 쨈; á„ᅢᆷ; ) HANGUL SYLLABLE JJAEM
+CA09;CA09;110D 1162 11B8;CA09;110D 1162 11B8; # (쨉; 쨉; á„ᅢᆸ; 쨉; á„ᅢᆸ; ) HANGUL SYLLABLE JJAEB
+CA0A;CA0A;110D 1162 11B9;CA0A;110D 1162 11B9; # (쨊; 쨊; á„ᅢᆹ; 쨊; á„ᅢᆹ; ) HANGUL SYLLABLE JJAEBS
+CA0B;CA0B;110D 1162 11BA;CA0B;110D 1162 11BA; # (쨋; 쨋; á„ᅢᆺ; 쨋; á„ᅢᆺ; ) HANGUL SYLLABLE JJAES
+CA0C;CA0C;110D 1162 11BB;CA0C;110D 1162 11BB; # (쨌; 쨌; á„ᅢᆻ; 쨌; á„ᅢᆻ; ) HANGUL SYLLABLE JJAESS
+CA0D;CA0D;110D 1162 11BC;CA0D;110D 1162 11BC; # (ì¨; ì¨; á„ᅢᆼ; ì¨; á„ᅢᆼ; ) HANGUL SYLLABLE JJAENG
+CA0E;CA0E;110D 1162 11BD;CA0E;110D 1162 11BD; # (쨎; 쨎; á„ᅢᆽ; 쨎; á„ᅢᆽ; ) HANGUL SYLLABLE JJAEJ
+CA0F;CA0F;110D 1162 11BE;CA0F;110D 1162 11BE; # (ì¨; ì¨; á„ᅢᆾ; ì¨; á„ᅢᆾ; ) HANGUL SYLLABLE JJAEC
+CA10;CA10;110D 1162 11BF;CA10;110D 1162 11BF; # (ì¨; ì¨; á„ᅢᆿ; ì¨; á„ᅢᆿ; ) HANGUL SYLLABLE JJAEK
+CA11;CA11;110D 1162 11C0;CA11;110D 1162 11C0; # (쨑; 쨑; á„ᅢᇀ; 쨑; á„ᅢᇀ; ) HANGUL SYLLABLE JJAET
+CA12;CA12;110D 1162 11C1;CA12;110D 1162 11C1; # (쨒; 쨒; á„á…¢á‡; 쨒; á„á…¢á‡; ) HANGUL SYLLABLE JJAEP
+CA13;CA13;110D 1162 11C2;CA13;110D 1162 11C2; # (쨓; 쨓; á„ᅢᇂ; 쨓; á„ᅢᇂ; ) HANGUL SYLLABLE JJAEH
+CA14;CA14;110D 1163;CA14;110D 1163; # (쨔; 쨔; á„á…£; 쨔; á„á…£; ) HANGUL SYLLABLE JJYA
+CA15;CA15;110D 1163 11A8;CA15;110D 1163 11A8; # (쨕; 쨕; á„ᅣᆨ; 쨕; á„ᅣᆨ; ) HANGUL SYLLABLE JJYAG
+CA16;CA16;110D 1163 11A9;CA16;110D 1163 11A9; # (쨖; 쨖; á„ᅣᆩ; 쨖; á„ᅣᆩ; ) HANGUL SYLLABLE JJYAGG
+CA17;CA17;110D 1163 11AA;CA17;110D 1163 11AA; # (쨗; 쨗; á„ᅣᆪ; 쨗; á„ᅣᆪ; ) HANGUL SYLLABLE JJYAGS
+CA18;CA18;110D 1163 11AB;CA18;110D 1163 11AB; # (쨘; 쨘; á„ᅣᆫ; 쨘; á„ᅣᆫ; ) HANGUL SYLLABLE JJYAN
+CA19;CA19;110D 1163 11AC;CA19;110D 1163 11AC; # (쨙; 쨙; á„ᅣᆬ; 쨙; á„ᅣᆬ; ) HANGUL SYLLABLE JJYANJ
+CA1A;CA1A;110D 1163 11AD;CA1A;110D 1163 11AD; # (쨚; 쨚; á„ᅣᆭ; 쨚; á„ᅣᆭ; ) HANGUL SYLLABLE JJYANH
+CA1B;CA1B;110D 1163 11AE;CA1B;110D 1163 11AE; # (쨛; 쨛; á„ᅣᆮ; 쨛; á„ᅣᆮ; ) HANGUL SYLLABLE JJYAD
+CA1C;CA1C;110D 1163 11AF;CA1C;110D 1163 11AF; # (쨜; 쨜; á„ᅣᆯ; 쨜; á„ᅣᆯ; ) HANGUL SYLLABLE JJYAL
+CA1D;CA1D;110D 1163 11B0;CA1D;110D 1163 11B0; # (ì¨; ì¨; á„ᅣᆰ; ì¨; á„ᅣᆰ; ) HANGUL SYLLABLE JJYALG
+CA1E;CA1E;110D 1163 11B1;CA1E;110D 1163 11B1; # (쨞; 쨞; á„ᅣᆱ; 쨞; á„ᅣᆱ; ) HANGUL SYLLABLE JJYALM
+CA1F;CA1F;110D 1163 11B2;CA1F;110D 1163 11B2; # (쨟; 쨟; á„ᅣᆲ; 쨟; á„ᅣᆲ; ) HANGUL SYLLABLE JJYALB
+CA20;CA20;110D 1163 11B3;CA20;110D 1163 11B3; # (쨠; 쨠; á„ᅣᆳ; 쨠; á„ᅣᆳ; ) HANGUL SYLLABLE JJYALS
+CA21;CA21;110D 1163 11B4;CA21;110D 1163 11B4; # (쨡; 쨡; á„ᅣᆴ; 쨡; á„ᅣᆴ; ) HANGUL SYLLABLE JJYALT
+CA22;CA22;110D 1163 11B5;CA22;110D 1163 11B5; # (쨢; 쨢; á„ᅣᆵ; 쨢; á„ᅣᆵ; ) HANGUL SYLLABLE JJYALP
+CA23;CA23;110D 1163 11B6;CA23;110D 1163 11B6; # (쨣; 쨣; á„ᅣᆶ; 쨣; á„ᅣᆶ; ) HANGUL SYLLABLE JJYALH
+CA24;CA24;110D 1163 11B7;CA24;110D 1163 11B7; # (쨤; 쨤; á„ᅣᆷ; 쨤; á„ᅣᆷ; ) HANGUL SYLLABLE JJYAM
+CA25;CA25;110D 1163 11B8;CA25;110D 1163 11B8; # (쨥; 쨥; á„ᅣᆸ; 쨥; á„ᅣᆸ; ) HANGUL SYLLABLE JJYAB
+CA26;CA26;110D 1163 11B9;CA26;110D 1163 11B9; # (쨦; 쨦; á„ᅣᆹ; 쨦; á„ᅣᆹ; ) HANGUL SYLLABLE JJYABS
+CA27;CA27;110D 1163 11BA;CA27;110D 1163 11BA; # (쨧; 쨧; á„ᅣᆺ; 쨧; á„ᅣᆺ; ) HANGUL SYLLABLE JJYAS
+CA28;CA28;110D 1163 11BB;CA28;110D 1163 11BB; # (쨨; 쨨; á„ᅣᆻ; 쨨; á„ᅣᆻ; ) HANGUL SYLLABLE JJYASS
+CA29;CA29;110D 1163 11BC;CA29;110D 1163 11BC; # (쨩; 쨩; á„ᅣᆼ; 쨩; á„ᅣᆼ; ) HANGUL SYLLABLE JJYANG
+CA2A;CA2A;110D 1163 11BD;CA2A;110D 1163 11BD; # (쨪; 쨪; á„ᅣᆽ; 쨪; á„ᅣᆽ; ) HANGUL SYLLABLE JJYAJ
+CA2B;CA2B;110D 1163 11BE;CA2B;110D 1163 11BE; # (쨫; 쨫; á„ᅣᆾ; 쨫; á„ᅣᆾ; ) HANGUL SYLLABLE JJYAC
+CA2C;CA2C;110D 1163 11BF;CA2C;110D 1163 11BF; # (쨬; 쨬; á„ᅣᆿ; 쨬; á„ᅣᆿ; ) HANGUL SYLLABLE JJYAK
+CA2D;CA2D;110D 1163 11C0;CA2D;110D 1163 11C0; # (쨭; 쨭; á„ᅣᇀ; 쨭; á„ᅣᇀ; ) HANGUL SYLLABLE JJYAT
+CA2E;CA2E;110D 1163 11C1;CA2E;110D 1163 11C1; # (쨮; 쨮; á„á…£á‡; 쨮; á„á…£á‡; ) HANGUL SYLLABLE JJYAP
+CA2F;CA2F;110D 1163 11C2;CA2F;110D 1163 11C2; # (쨯; 쨯; á„ᅣᇂ; 쨯; á„ᅣᇂ; ) HANGUL SYLLABLE JJYAH
+CA30;CA30;110D 1164;CA30;110D 1164; # (쨰; 쨰; á„á…¤; 쨰; á„á…¤; ) HANGUL SYLLABLE JJYAE
+CA31;CA31;110D 1164 11A8;CA31;110D 1164 11A8; # (쨱; 쨱; á„ᅤᆨ; 쨱; á„ᅤᆨ; ) HANGUL SYLLABLE JJYAEG
+CA32;CA32;110D 1164 11A9;CA32;110D 1164 11A9; # (쨲; 쨲; á„ᅤᆩ; 쨲; á„ᅤᆩ; ) HANGUL SYLLABLE JJYAEGG
+CA33;CA33;110D 1164 11AA;CA33;110D 1164 11AA; # (쨳; 쨳; á„ᅤᆪ; 쨳; á„ᅤᆪ; ) HANGUL SYLLABLE JJYAEGS
+CA34;CA34;110D 1164 11AB;CA34;110D 1164 11AB; # (쨴; 쨴; á„ᅤᆫ; 쨴; á„ᅤᆫ; ) HANGUL SYLLABLE JJYAEN
+CA35;CA35;110D 1164 11AC;CA35;110D 1164 11AC; # (쨵; 쨵; á„ᅤᆬ; 쨵; á„ᅤᆬ; ) HANGUL SYLLABLE JJYAENJ
+CA36;CA36;110D 1164 11AD;CA36;110D 1164 11AD; # (쨶; 쨶; á„ᅤᆭ; 쨶; á„ᅤᆭ; ) HANGUL SYLLABLE JJYAENH
+CA37;CA37;110D 1164 11AE;CA37;110D 1164 11AE; # (쨷; 쨷; á„ᅤᆮ; 쨷; á„ᅤᆮ; ) HANGUL SYLLABLE JJYAED
+CA38;CA38;110D 1164 11AF;CA38;110D 1164 11AF; # (쨸; 쨸; á„ᅤᆯ; 쨸; á„ᅤᆯ; ) HANGUL SYLLABLE JJYAEL
+CA39;CA39;110D 1164 11B0;CA39;110D 1164 11B0; # (쨹; 쨹; á„ᅤᆰ; 쨹; á„ᅤᆰ; ) HANGUL SYLLABLE JJYAELG
+CA3A;CA3A;110D 1164 11B1;CA3A;110D 1164 11B1; # (쨺; 쨺; á„ᅤᆱ; 쨺; á„ᅤᆱ; ) HANGUL SYLLABLE JJYAELM
+CA3B;CA3B;110D 1164 11B2;CA3B;110D 1164 11B2; # (쨻; 쨻; á„ᅤᆲ; 쨻; á„ᅤᆲ; ) HANGUL SYLLABLE JJYAELB
+CA3C;CA3C;110D 1164 11B3;CA3C;110D 1164 11B3; # (쨼; 쨼; á„ᅤᆳ; 쨼; á„ᅤᆳ; ) HANGUL SYLLABLE JJYAELS
+CA3D;CA3D;110D 1164 11B4;CA3D;110D 1164 11B4; # (쨽; 쨽; á„ᅤᆴ; 쨽; á„ᅤᆴ; ) HANGUL SYLLABLE JJYAELT
+CA3E;CA3E;110D 1164 11B5;CA3E;110D 1164 11B5; # (쨾; 쨾; á„ᅤᆵ; 쨾; á„ᅤᆵ; ) HANGUL SYLLABLE JJYAELP
+CA3F;CA3F;110D 1164 11B6;CA3F;110D 1164 11B6; # (쨿; 쨿; á„ᅤᆶ; 쨿; á„ᅤᆶ; ) HANGUL SYLLABLE JJYAELH
+CA40;CA40;110D 1164 11B7;CA40;110D 1164 11B7; # (ì©€; ì©€; á„ᅤᆷ; ì©€; á„ᅤᆷ; ) HANGUL SYLLABLE JJYAEM
+CA41;CA41;110D 1164 11B8;CA41;110D 1164 11B8; # (ì©; ì©; á„ᅤᆸ; ì©; á„ᅤᆸ; ) HANGUL SYLLABLE JJYAEB
+CA42;CA42;110D 1164 11B9;CA42;110D 1164 11B9; # (ì©‚; ì©‚; á„ᅤᆹ; ì©‚; á„ᅤᆹ; ) HANGUL SYLLABLE JJYAEBS
+CA43;CA43;110D 1164 11BA;CA43;110D 1164 11BA; # (쩃; 쩃; á„ᅤᆺ; 쩃; á„ᅤᆺ; ) HANGUL SYLLABLE JJYAES
+CA44;CA44;110D 1164 11BB;CA44;110D 1164 11BB; # (ì©„; ì©„; á„ᅤᆻ; ì©„; á„ᅤᆻ; ) HANGUL SYLLABLE JJYAESS
+CA45;CA45;110D 1164 11BC;CA45;110D 1164 11BC; # (ì©…; ì©…; á„ᅤᆼ; ì©…; á„ᅤᆼ; ) HANGUL SYLLABLE JJYAENG
+CA46;CA46;110D 1164 11BD;CA46;110D 1164 11BD; # (쩆; 쩆; á„ᅤᆽ; 쩆; á„ᅤᆽ; ) HANGUL SYLLABLE JJYAEJ
+CA47;CA47;110D 1164 11BE;CA47;110D 1164 11BE; # (쩇; 쩇; á„ᅤᆾ; 쩇; á„ᅤᆾ; ) HANGUL SYLLABLE JJYAEC
+CA48;CA48;110D 1164 11BF;CA48;110D 1164 11BF; # (쩈; 쩈; á„ᅤᆿ; 쩈; á„ᅤᆿ; ) HANGUL SYLLABLE JJYAEK
+CA49;CA49;110D 1164 11C0;CA49;110D 1164 11C0; # (쩉; 쩉; á„ᅤᇀ; 쩉; á„ᅤᇀ; ) HANGUL SYLLABLE JJYAET
+CA4A;CA4A;110D 1164 11C1;CA4A;110D 1164 11C1; # (ì©Š; ì©Š; á„á…¤á‡; ì©Š; á„á…¤á‡; ) HANGUL SYLLABLE JJYAEP
+CA4B;CA4B;110D 1164 11C2;CA4B;110D 1164 11C2; # (ì©‹; ì©‹; á„ᅤᇂ; ì©‹; á„ᅤᇂ; ) HANGUL SYLLABLE JJYAEH
+CA4C;CA4C;110D 1165;CA4C;110D 1165; # (ì©Œ; ì©Œ; á„á…¥; ì©Œ; á„á…¥; ) HANGUL SYLLABLE JJEO
+CA4D;CA4D;110D 1165 11A8;CA4D;110D 1165 11A8; # (ì©; ì©; á„ᅥᆨ; ì©; á„ᅥᆨ; ) HANGUL SYLLABLE JJEOG
+CA4E;CA4E;110D 1165 11A9;CA4E;110D 1165 11A9; # (ì©Ž; ì©Ž; á„ᅥᆩ; ì©Ž; á„ᅥᆩ; ) HANGUL SYLLABLE JJEOGG
+CA4F;CA4F;110D 1165 11AA;CA4F;110D 1165 11AA; # (ì©; ì©; á„ᅥᆪ; ì©; á„ᅥᆪ; ) HANGUL SYLLABLE JJEOGS
+CA50;CA50;110D 1165 11AB;CA50;110D 1165 11AB; # (ì©; ì©; á„ᅥᆫ; ì©; á„ᅥᆫ; ) HANGUL SYLLABLE JJEON
+CA51;CA51;110D 1165 11AC;CA51;110D 1165 11AC; # (ì©‘; ì©‘; á„ᅥᆬ; ì©‘; á„ᅥᆬ; ) HANGUL SYLLABLE JJEONJ
+CA52;CA52;110D 1165 11AD;CA52;110D 1165 11AD; # (ì©’; ì©’; á„ᅥᆭ; ì©’; á„ᅥᆭ; ) HANGUL SYLLABLE JJEONH
+CA53;CA53;110D 1165 11AE;CA53;110D 1165 11AE; # (ì©“; ì©“; á„ᅥᆮ; ì©“; á„ᅥᆮ; ) HANGUL SYLLABLE JJEOD
+CA54;CA54;110D 1165 11AF;CA54;110D 1165 11AF; # (ì©”; ì©”; á„ᅥᆯ; ì©”; á„ᅥᆯ; ) HANGUL SYLLABLE JJEOL
+CA55;CA55;110D 1165 11B0;CA55;110D 1165 11B0; # (ì©•; ì©•; á„ᅥᆰ; ì©•; á„ᅥᆰ; ) HANGUL SYLLABLE JJEOLG
+CA56;CA56;110D 1165 11B1;CA56;110D 1165 11B1; # (ì©–; ì©–; á„ᅥᆱ; ì©–; á„ᅥᆱ; ) HANGUL SYLLABLE JJEOLM
+CA57;CA57;110D 1165 11B2;CA57;110D 1165 11B2; # (ì©—; ì©—; á„ᅥᆲ; ì©—; á„ᅥᆲ; ) HANGUL SYLLABLE JJEOLB
+CA58;CA58;110D 1165 11B3;CA58;110D 1165 11B3; # (쩘; 쩘; á„ᅥᆳ; 쩘; á„ᅥᆳ; ) HANGUL SYLLABLE JJEOLS
+CA59;CA59;110D 1165 11B4;CA59;110D 1165 11B4; # (ì©™; ì©™; á„ᅥᆴ; ì©™; á„ᅥᆴ; ) HANGUL SYLLABLE JJEOLT
+CA5A;CA5A;110D 1165 11B5;CA5A;110D 1165 11B5; # (ì©š; ì©š; á„ᅥᆵ; ì©š; á„ᅥᆵ; ) HANGUL SYLLABLE JJEOLP
+CA5B;CA5B;110D 1165 11B6;CA5B;110D 1165 11B6; # (ì©›; ì©›; á„ᅥᆶ; ì©›; á„ᅥᆶ; ) HANGUL SYLLABLE JJEOLH
+CA5C;CA5C;110D 1165 11B7;CA5C;110D 1165 11B7; # (ì©œ; ì©œ; á„ᅥᆷ; ì©œ; á„ᅥᆷ; ) HANGUL SYLLABLE JJEOM
+CA5D;CA5D;110D 1165 11B8;CA5D;110D 1165 11B8; # (ì©; ì©; á„ᅥᆸ; ì©; á„ᅥᆸ; ) HANGUL SYLLABLE JJEOB
+CA5E;CA5E;110D 1165 11B9;CA5E;110D 1165 11B9; # (ì©ž; ì©ž; á„ᅥᆹ; ì©ž; á„ᅥᆹ; ) HANGUL SYLLABLE JJEOBS
+CA5F;CA5F;110D 1165 11BA;CA5F;110D 1165 11BA; # (ì©Ÿ; ì©Ÿ; á„ᅥᆺ; ì©Ÿ; á„ᅥᆺ; ) HANGUL SYLLABLE JJEOS
+CA60;CA60;110D 1165 11BB;CA60;110D 1165 11BB; # (ì© ; ì© ; á„ᅥᆻ; ì© ; á„ᅥᆻ; ) HANGUL SYLLABLE JJEOSS
+CA61;CA61;110D 1165 11BC;CA61;110D 1165 11BC; # (ì©¡; ì©¡; á„ᅥᆼ; ì©¡; á„ᅥᆼ; ) HANGUL SYLLABLE JJEONG
+CA62;CA62;110D 1165 11BD;CA62;110D 1165 11BD; # (ì©¢; ì©¢; á„ᅥᆽ; ì©¢; á„ᅥᆽ; ) HANGUL SYLLABLE JJEOJ
+CA63;CA63;110D 1165 11BE;CA63;110D 1165 11BE; # (ì©£; ì©£; á„ᅥᆾ; ì©£; á„ᅥᆾ; ) HANGUL SYLLABLE JJEOC
+CA64;CA64;110D 1165 11BF;CA64;110D 1165 11BF; # (쩤; 쩤; á„ᅥᆿ; 쩤; á„ᅥᆿ; ) HANGUL SYLLABLE JJEOK
+CA65;CA65;110D 1165 11C0;CA65;110D 1165 11C0; # (ì©¥; ì©¥; á„ᅥᇀ; ì©¥; á„ᅥᇀ; ) HANGUL SYLLABLE JJEOT
+CA66;CA66;110D 1165 11C1;CA66;110D 1165 11C1; # (쩦; 쩦; á„á…¥á‡; 쩦; á„á…¥á‡; ) HANGUL SYLLABLE JJEOP
+CA67;CA67;110D 1165 11C2;CA67;110D 1165 11C2; # (쩧; 쩧; á„ᅥᇂ; 쩧; á„ᅥᇂ; ) HANGUL SYLLABLE JJEOH
+CA68;CA68;110D 1166;CA68;110D 1166; # (쩨; 쩨; á„á…¦; 쩨; á„á…¦; ) HANGUL SYLLABLE JJE
+CA69;CA69;110D 1166 11A8;CA69;110D 1166 11A8; # (ì©©; ì©©; á„ᅦᆨ; ì©©; á„ᅦᆨ; ) HANGUL SYLLABLE JJEG
+CA6A;CA6A;110D 1166 11A9;CA6A;110D 1166 11A9; # (쩪; 쩪; á„ᅦᆩ; 쩪; á„ᅦᆩ; ) HANGUL SYLLABLE JJEGG
+CA6B;CA6B;110D 1166 11AA;CA6B;110D 1166 11AA; # (ì©«; ì©«; á„ᅦᆪ; ì©«; á„ᅦᆪ; ) HANGUL SYLLABLE JJEGS
+CA6C;CA6C;110D 1166 11AB;CA6C;110D 1166 11AB; # (쩬; 쩬; á„ᅦᆫ; 쩬; á„ᅦᆫ; ) HANGUL SYLLABLE JJEN
+CA6D;CA6D;110D 1166 11AC;CA6D;110D 1166 11AC; # (ì©­; ì©­; á„ᅦᆬ; ì©­; á„ᅦᆬ; ) HANGUL SYLLABLE JJENJ
+CA6E;CA6E;110D 1166 11AD;CA6E;110D 1166 11AD; # (ì©®; ì©®; á„ᅦᆭ; ì©®; á„ᅦᆭ; ) HANGUL SYLLABLE JJENH
+CA6F;CA6F;110D 1166 11AE;CA6F;110D 1166 11AE; # (쩯; 쩯; á„ᅦᆮ; 쩯; á„ᅦᆮ; ) HANGUL SYLLABLE JJED
+CA70;CA70;110D 1166 11AF;CA70;110D 1166 11AF; # (ì©°; ì©°; á„ᅦᆯ; ì©°; á„ᅦᆯ; ) HANGUL SYLLABLE JJEL
+CA71;CA71;110D 1166 11B0;CA71;110D 1166 11B0; # (쩱; 쩱; á„ᅦᆰ; 쩱; á„ᅦᆰ; ) HANGUL SYLLABLE JJELG
+CA72;CA72;110D 1166 11B1;CA72;110D 1166 11B1; # (쩲; 쩲; á„ᅦᆱ; 쩲; á„ᅦᆱ; ) HANGUL SYLLABLE JJELM
+CA73;CA73;110D 1166 11B2;CA73;110D 1166 11B2; # (쩳; 쩳; á„ᅦᆲ; 쩳; á„ᅦᆲ; ) HANGUL SYLLABLE JJELB
+CA74;CA74;110D 1166 11B3;CA74;110D 1166 11B3; # (ì©´; ì©´; á„ᅦᆳ; ì©´; á„ᅦᆳ; ) HANGUL SYLLABLE JJELS
+CA75;CA75;110D 1166 11B4;CA75;110D 1166 11B4; # (쩵; 쩵; á„ᅦᆴ; 쩵; á„ᅦᆴ; ) HANGUL SYLLABLE JJELT
+CA76;CA76;110D 1166 11B5;CA76;110D 1166 11B5; # (쩶; 쩶; á„ᅦᆵ; 쩶; á„ᅦᆵ; ) HANGUL SYLLABLE JJELP
+CA77;CA77;110D 1166 11B6;CA77;110D 1166 11B6; # (ì©·; ì©·; á„ᅦᆶ; ì©·; á„ᅦᆶ; ) HANGUL SYLLABLE JJELH
+CA78;CA78;110D 1166 11B7;CA78;110D 1166 11B7; # (쩸; 쩸; á„ᅦᆷ; 쩸; á„ᅦᆷ; ) HANGUL SYLLABLE JJEM
+CA79;CA79;110D 1166 11B8;CA79;110D 1166 11B8; # (쩹; 쩹; á„ᅦᆸ; 쩹; á„ᅦᆸ; ) HANGUL SYLLABLE JJEB
+CA7A;CA7A;110D 1166 11B9;CA7A;110D 1166 11B9; # (쩺; 쩺; á„ᅦᆹ; 쩺; á„ᅦᆹ; ) HANGUL SYLLABLE JJEBS
+CA7B;CA7B;110D 1166 11BA;CA7B;110D 1166 11BA; # (ì©»; ì©»; á„ᅦᆺ; ì©»; á„ᅦᆺ; ) HANGUL SYLLABLE JJES
+CA7C;CA7C;110D 1166 11BB;CA7C;110D 1166 11BB; # (쩼; 쩼; á„ᅦᆻ; 쩼; á„ᅦᆻ; ) HANGUL SYLLABLE JJESS
+CA7D;CA7D;110D 1166 11BC;CA7D;110D 1166 11BC; # (쩽; 쩽; á„ᅦᆼ; 쩽; á„ᅦᆼ; ) HANGUL SYLLABLE JJENG
+CA7E;CA7E;110D 1166 11BD;CA7E;110D 1166 11BD; # (쩾; 쩾; á„ᅦᆽ; 쩾; á„ᅦᆽ; ) HANGUL SYLLABLE JJEJ
+CA7F;CA7F;110D 1166 11BE;CA7F;110D 1166 11BE; # (ì©¿; ì©¿; á„ᅦᆾ; ì©¿; á„ᅦᆾ; ) HANGUL SYLLABLE JJEC
+CA80;CA80;110D 1166 11BF;CA80;110D 1166 11BF; # (쪀; 쪀; á„ᅦᆿ; 쪀; á„ᅦᆿ; ) HANGUL SYLLABLE JJEK
+CA81;CA81;110D 1166 11C0;CA81;110D 1166 11C0; # (ìª; ìª; á„ᅦᇀ; ìª; á„ᅦᇀ; ) HANGUL SYLLABLE JJET
+CA82;CA82;110D 1166 11C1;CA82;110D 1166 11C1; # (쪂; 쪂; á„á…¦á‡; 쪂; á„á…¦á‡; ) HANGUL SYLLABLE JJEP
+CA83;CA83;110D 1166 11C2;CA83;110D 1166 11C2; # (쪃; 쪃; á„ᅦᇂ; 쪃; á„ᅦᇂ; ) HANGUL SYLLABLE JJEH
+CA84;CA84;110D 1167;CA84;110D 1167; # (쪄; 쪄; á„á…§; 쪄; á„á…§; ) HANGUL SYLLABLE JJYEO
+CA85;CA85;110D 1167 11A8;CA85;110D 1167 11A8; # (쪅; 쪅; á„ᅧᆨ; 쪅; á„ᅧᆨ; ) HANGUL SYLLABLE JJYEOG
+CA86;CA86;110D 1167 11A9;CA86;110D 1167 11A9; # (쪆; 쪆; á„ᅧᆩ; 쪆; á„ᅧᆩ; ) HANGUL SYLLABLE JJYEOGG
+CA87;CA87;110D 1167 11AA;CA87;110D 1167 11AA; # (쪇; 쪇; á„ᅧᆪ; 쪇; á„ᅧᆪ; ) HANGUL SYLLABLE JJYEOGS
+CA88;CA88;110D 1167 11AB;CA88;110D 1167 11AB; # (쪈; 쪈; á„ᅧᆫ; 쪈; á„ᅧᆫ; ) HANGUL SYLLABLE JJYEON
+CA89;CA89;110D 1167 11AC;CA89;110D 1167 11AC; # (쪉; 쪉; á„ᅧᆬ; 쪉; á„ᅧᆬ; ) HANGUL SYLLABLE JJYEONJ
+CA8A;CA8A;110D 1167 11AD;CA8A;110D 1167 11AD; # (쪊; 쪊; á„ᅧᆭ; 쪊; á„ᅧᆭ; ) HANGUL SYLLABLE JJYEONH
+CA8B;CA8B;110D 1167 11AE;CA8B;110D 1167 11AE; # (쪋; 쪋; á„ᅧᆮ; 쪋; á„ᅧᆮ; ) HANGUL SYLLABLE JJYEOD
+CA8C;CA8C;110D 1167 11AF;CA8C;110D 1167 11AF; # (쪌; 쪌; á„ᅧᆯ; 쪌; á„ᅧᆯ; ) HANGUL SYLLABLE JJYEOL
+CA8D;CA8D;110D 1167 11B0;CA8D;110D 1167 11B0; # (ìª; ìª; á„ᅧᆰ; ìª; á„ᅧᆰ; ) HANGUL SYLLABLE JJYEOLG
+CA8E;CA8E;110D 1167 11B1;CA8E;110D 1167 11B1; # (쪎; 쪎; á„ᅧᆱ; 쪎; á„ᅧᆱ; ) HANGUL SYLLABLE JJYEOLM
+CA8F;CA8F;110D 1167 11B2;CA8F;110D 1167 11B2; # (ìª; ìª; á„ᅧᆲ; ìª; á„ᅧᆲ; ) HANGUL SYLLABLE JJYEOLB
+CA90;CA90;110D 1167 11B3;CA90;110D 1167 11B3; # (ìª; ìª; á„ᅧᆳ; ìª; á„ᅧᆳ; ) HANGUL SYLLABLE JJYEOLS
+CA91;CA91;110D 1167 11B4;CA91;110D 1167 11B4; # (쪑; 쪑; á„ᅧᆴ; 쪑; á„ᅧᆴ; ) HANGUL SYLLABLE JJYEOLT
+CA92;CA92;110D 1167 11B5;CA92;110D 1167 11B5; # (쪒; 쪒; á„ᅧᆵ; 쪒; á„ᅧᆵ; ) HANGUL SYLLABLE JJYEOLP
+CA93;CA93;110D 1167 11B6;CA93;110D 1167 11B6; # (쪓; 쪓; á„ᅧᆶ; 쪓; á„ᅧᆶ; ) HANGUL SYLLABLE JJYEOLH
+CA94;CA94;110D 1167 11B7;CA94;110D 1167 11B7; # (쪔; 쪔; á„ᅧᆷ; 쪔; á„ᅧᆷ; ) HANGUL SYLLABLE JJYEOM
+CA95;CA95;110D 1167 11B8;CA95;110D 1167 11B8; # (쪕; 쪕; á„ᅧᆸ; 쪕; á„ᅧᆸ; ) HANGUL SYLLABLE JJYEOB
+CA96;CA96;110D 1167 11B9;CA96;110D 1167 11B9; # (쪖; 쪖; á„ᅧᆹ; 쪖; á„ᅧᆹ; ) HANGUL SYLLABLE JJYEOBS
+CA97;CA97;110D 1167 11BA;CA97;110D 1167 11BA; # (쪗; 쪗; á„ᅧᆺ; 쪗; á„ᅧᆺ; ) HANGUL SYLLABLE JJYEOS
+CA98;CA98;110D 1167 11BB;CA98;110D 1167 11BB; # (쪘; 쪘; á„ᅧᆻ; 쪘; á„ᅧᆻ; ) HANGUL SYLLABLE JJYEOSS
+CA99;CA99;110D 1167 11BC;CA99;110D 1167 11BC; # (쪙; 쪙; á„ᅧᆼ; 쪙; á„ᅧᆼ; ) HANGUL SYLLABLE JJYEONG
+CA9A;CA9A;110D 1167 11BD;CA9A;110D 1167 11BD; # (쪚; 쪚; á„ᅧᆽ; 쪚; á„ᅧᆽ; ) HANGUL SYLLABLE JJYEOJ
+CA9B;CA9B;110D 1167 11BE;CA9B;110D 1167 11BE; # (쪛; 쪛; á„ᅧᆾ; 쪛; á„ᅧᆾ; ) HANGUL SYLLABLE JJYEOC
+CA9C;CA9C;110D 1167 11BF;CA9C;110D 1167 11BF; # (쪜; 쪜; á„ᅧᆿ; 쪜; á„ᅧᆿ; ) HANGUL SYLLABLE JJYEOK
+CA9D;CA9D;110D 1167 11C0;CA9D;110D 1167 11C0; # (ìª; ìª; á„ᅧᇀ; ìª; á„ᅧᇀ; ) HANGUL SYLLABLE JJYEOT
+CA9E;CA9E;110D 1167 11C1;CA9E;110D 1167 11C1; # (쪞; 쪞; á„á…§á‡; 쪞; á„á…§á‡; ) HANGUL SYLLABLE JJYEOP
+CA9F;CA9F;110D 1167 11C2;CA9F;110D 1167 11C2; # (쪟; 쪟; á„ᅧᇂ; 쪟; á„ᅧᇂ; ) HANGUL SYLLABLE JJYEOH
+CAA0;CAA0;110D 1168;CAA0;110D 1168; # (쪠; 쪠; á„á…¨; 쪠; á„á…¨; ) HANGUL SYLLABLE JJYE
+CAA1;CAA1;110D 1168 11A8;CAA1;110D 1168 11A8; # (쪡; 쪡; á„ᅨᆨ; 쪡; á„ᅨᆨ; ) HANGUL SYLLABLE JJYEG
+CAA2;CAA2;110D 1168 11A9;CAA2;110D 1168 11A9; # (쪢; 쪢; á„ᅨᆩ; 쪢; á„ᅨᆩ; ) HANGUL SYLLABLE JJYEGG
+CAA3;CAA3;110D 1168 11AA;CAA3;110D 1168 11AA; # (쪣; 쪣; á„ᅨᆪ; 쪣; á„ᅨᆪ; ) HANGUL SYLLABLE JJYEGS
+CAA4;CAA4;110D 1168 11AB;CAA4;110D 1168 11AB; # (쪤; 쪤; á„ᅨᆫ; 쪤; á„ᅨᆫ; ) HANGUL SYLLABLE JJYEN
+CAA5;CAA5;110D 1168 11AC;CAA5;110D 1168 11AC; # (쪥; 쪥; á„ᅨᆬ; 쪥; á„ᅨᆬ; ) HANGUL SYLLABLE JJYENJ
+CAA6;CAA6;110D 1168 11AD;CAA6;110D 1168 11AD; # (쪦; 쪦; á„ᅨᆭ; 쪦; á„ᅨᆭ; ) HANGUL SYLLABLE JJYENH
+CAA7;CAA7;110D 1168 11AE;CAA7;110D 1168 11AE; # (쪧; 쪧; á„ᅨᆮ; 쪧; á„ᅨᆮ; ) HANGUL SYLLABLE JJYED
+CAA8;CAA8;110D 1168 11AF;CAA8;110D 1168 11AF; # (쪨; 쪨; á„ᅨᆯ; 쪨; á„ᅨᆯ; ) HANGUL SYLLABLE JJYEL
+CAA9;CAA9;110D 1168 11B0;CAA9;110D 1168 11B0; # (쪩; 쪩; á„ᅨᆰ; 쪩; á„ᅨᆰ; ) HANGUL SYLLABLE JJYELG
+CAAA;CAAA;110D 1168 11B1;CAAA;110D 1168 11B1; # (쪪; 쪪; á„ᅨᆱ; 쪪; á„ᅨᆱ; ) HANGUL SYLLABLE JJYELM
+CAAB;CAAB;110D 1168 11B2;CAAB;110D 1168 11B2; # (쪫; 쪫; á„ᅨᆲ; 쪫; á„ᅨᆲ; ) HANGUL SYLLABLE JJYELB
+CAAC;CAAC;110D 1168 11B3;CAAC;110D 1168 11B3; # (쪬; 쪬; á„ᅨᆳ; 쪬; á„ᅨᆳ; ) HANGUL SYLLABLE JJYELS
+CAAD;CAAD;110D 1168 11B4;CAAD;110D 1168 11B4; # (쪭; 쪭; á„ᅨᆴ; 쪭; á„ᅨᆴ; ) HANGUL SYLLABLE JJYELT
+CAAE;CAAE;110D 1168 11B5;CAAE;110D 1168 11B5; # (쪮; 쪮; á„ᅨᆵ; 쪮; á„ᅨᆵ; ) HANGUL SYLLABLE JJYELP
+CAAF;CAAF;110D 1168 11B6;CAAF;110D 1168 11B6; # (쪯; 쪯; á„ᅨᆶ; 쪯; á„ᅨᆶ; ) HANGUL SYLLABLE JJYELH
+CAB0;CAB0;110D 1168 11B7;CAB0;110D 1168 11B7; # (쪰; 쪰; á„ᅨᆷ; 쪰; á„ᅨᆷ; ) HANGUL SYLLABLE JJYEM
+CAB1;CAB1;110D 1168 11B8;CAB1;110D 1168 11B8; # (쪱; 쪱; á„ᅨᆸ; 쪱; á„ᅨᆸ; ) HANGUL SYLLABLE JJYEB
+CAB2;CAB2;110D 1168 11B9;CAB2;110D 1168 11B9; # (쪲; 쪲; á„ᅨᆹ; 쪲; á„ᅨᆹ; ) HANGUL SYLLABLE JJYEBS
+CAB3;CAB3;110D 1168 11BA;CAB3;110D 1168 11BA; # (쪳; 쪳; á„ᅨᆺ; 쪳; á„ᅨᆺ; ) HANGUL SYLLABLE JJYES
+CAB4;CAB4;110D 1168 11BB;CAB4;110D 1168 11BB; # (쪴; 쪴; á„ᅨᆻ; 쪴; á„ᅨᆻ; ) HANGUL SYLLABLE JJYESS
+CAB5;CAB5;110D 1168 11BC;CAB5;110D 1168 11BC; # (쪵; 쪵; á„ᅨᆼ; 쪵; á„ᅨᆼ; ) HANGUL SYLLABLE JJYENG
+CAB6;CAB6;110D 1168 11BD;CAB6;110D 1168 11BD; # (쪶; 쪶; á„ᅨᆽ; 쪶; á„ᅨᆽ; ) HANGUL SYLLABLE JJYEJ
+CAB7;CAB7;110D 1168 11BE;CAB7;110D 1168 11BE; # (쪷; 쪷; á„ᅨᆾ; 쪷; á„ᅨᆾ; ) HANGUL SYLLABLE JJYEC
+CAB8;CAB8;110D 1168 11BF;CAB8;110D 1168 11BF; # (쪸; 쪸; á„ᅨᆿ; 쪸; á„ᅨᆿ; ) HANGUL SYLLABLE JJYEK
+CAB9;CAB9;110D 1168 11C0;CAB9;110D 1168 11C0; # (쪹; 쪹; á„ᅨᇀ; 쪹; á„ᅨᇀ; ) HANGUL SYLLABLE JJYET
+CABA;CABA;110D 1168 11C1;CABA;110D 1168 11C1; # (쪺; 쪺; á„á…¨á‡; 쪺; á„á…¨á‡; ) HANGUL SYLLABLE JJYEP
+CABB;CABB;110D 1168 11C2;CABB;110D 1168 11C2; # (쪻; 쪻; á„ᅨᇂ; 쪻; á„ᅨᇂ; ) HANGUL SYLLABLE JJYEH
+CABC;CABC;110D 1169;CABC;110D 1169; # (쪼; 쪼; á„á…©; 쪼; á„á…©; ) HANGUL SYLLABLE JJO
+CABD;CABD;110D 1169 11A8;CABD;110D 1169 11A8; # (쪽; 쪽; á„ᅩᆨ; 쪽; á„ᅩᆨ; ) HANGUL SYLLABLE JJOG
+CABE;CABE;110D 1169 11A9;CABE;110D 1169 11A9; # (쪾; 쪾; á„ᅩᆩ; 쪾; á„ᅩᆩ; ) HANGUL SYLLABLE JJOGG
+CABF;CABF;110D 1169 11AA;CABF;110D 1169 11AA; # (쪿; 쪿; á„ᅩᆪ; 쪿; á„ᅩᆪ; ) HANGUL SYLLABLE JJOGS
+CAC0;CAC0;110D 1169 11AB;CAC0;110D 1169 11AB; # (ì«€; ì«€; á„ᅩᆫ; ì«€; á„ᅩᆫ; ) HANGUL SYLLABLE JJON
+CAC1;CAC1;110D 1169 11AC;CAC1;110D 1169 11AC; # (ì«; ì«; á„ᅩᆬ; ì«; á„ᅩᆬ; ) HANGUL SYLLABLE JJONJ
+CAC2;CAC2;110D 1169 11AD;CAC2;110D 1169 11AD; # (ì«‚; ì«‚; á„ᅩᆭ; ì«‚; á„ᅩᆭ; ) HANGUL SYLLABLE JJONH
+CAC3;CAC3;110D 1169 11AE;CAC3;110D 1169 11AE; # (쫃; 쫃; á„ᅩᆮ; 쫃; á„ᅩᆮ; ) HANGUL SYLLABLE JJOD
+CAC4;CAC4;110D 1169 11AF;CAC4;110D 1169 11AF; # (ì«„; ì«„; á„ᅩᆯ; ì«„; á„ᅩᆯ; ) HANGUL SYLLABLE JJOL
+CAC5;CAC5;110D 1169 11B0;CAC5;110D 1169 11B0; # (ì«…; ì«…; á„ᅩᆰ; ì«…; á„ᅩᆰ; ) HANGUL SYLLABLE JJOLG
+CAC6;CAC6;110D 1169 11B1;CAC6;110D 1169 11B1; # (쫆; 쫆; á„ᅩᆱ; 쫆; á„ᅩᆱ; ) HANGUL SYLLABLE JJOLM
+CAC7;CAC7;110D 1169 11B2;CAC7;110D 1169 11B2; # (쫇; 쫇; á„ᅩᆲ; 쫇; á„ᅩᆲ; ) HANGUL SYLLABLE JJOLB
+CAC8;CAC8;110D 1169 11B3;CAC8;110D 1169 11B3; # (쫈; 쫈; á„ᅩᆳ; 쫈; á„ᅩᆳ; ) HANGUL SYLLABLE JJOLS
+CAC9;CAC9;110D 1169 11B4;CAC9;110D 1169 11B4; # (쫉; 쫉; á„ᅩᆴ; 쫉; á„ᅩᆴ; ) HANGUL SYLLABLE JJOLT
+CACA;CACA;110D 1169 11B5;CACA;110D 1169 11B5; # (ì«Š; ì«Š; á„ᅩᆵ; ì«Š; á„ᅩᆵ; ) HANGUL SYLLABLE JJOLP
+CACB;CACB;110D 1169 11B6;CACB;110D 1169 11B6; # (ì«‹; ì«‹; á„ᅩᆶ; ì«‹; á„ᅩᆶ; ) HANGUL SYLLABLE JJOLH
+CACC;CACC;110D 1169 11B7;CACC;110D 1169 11B7; # (ì«Œ; ì«Œ; á„ᅩᆷ; ì«Œ; á„ᅩᆷ; ) HANGUL SYLLABLE JJOM
+CACD;CACD;110D 1169 11B8;CACD;110D 1169 11B8; # (ì«; ì«; á„ᅩᆸ; ì«; á„ᅩᆸ; ) HANGUL SYLLABLE JJOB
+CACE;CACE;110D 1169 11B9;CACE;110D 1169 11B9; # (ì«Ž; ì«Ž; á„ᅩᆹ; ì«Ž; á„ᅩᆹ; ) HANGUL SYLLABLE JJOBS
+CACF;CACF;110D 1169 11BA;CACF;110D 1169 11BA; # (ì«; ì«; á„ᅩᆺ; ì«; á„ᅩᆺ; ) HANGUL SYLLABLE JJOS
+CAD0;CAD0;110D 1169 11BB;CAD0;110D 1169 11BB; # (ì«; ì«; á„ᅩᆻ; ì«; á„ᅩᆻ; ) HANGUL SYLLABLE JJOSS
+CAD1;CAD1;110D 1169 11BC;CAD1;110D 1169 11BC; # (ì«‘; ì«‘; á„ᅩᆼ; ì«‘; á„ᅩᆼ; ) HANGUL SYLLABLE JJONG
+CAD2;CAD2;110D 1169 11BD;CAD2;110D 1169 11BD; # (ì«’; ì«’; á„ᅩᆽ; ì«’; á„ᅩᆽ; ) HANGUL SYLLABLE JJOJ
+CAD3;CAD3;110D 1169 11BE;CAD3;110D 1169 11BE; # (ì«“; ì«“; á„ᅩᆾ; ì«“; á„ᅩᆾ; ) HANGUL SYLLABLE JJOC
+CAD4;CAD4;110D 1169 11BF;CAD4;110D 1169 11BF; # (ì«”; ì«”; á„ᅩᆿ; ì«”; á„ᅩᆿ; ) HANGUL SYLLABLE JJOK
+CAD5;CAD5;110D 1169 11C0;CAD5;110D 1169 11C0; # (ì«•; ì«•; á„ᅩᇀ; ì«•; á„ᅩᇀ; ) HANGUL SYLLABLE JJOT
+CAD6;CAD6;110D 1169 11C1;CAD6;110D 1169 11C1; # (ì«–; ì«–; á„á…©á‡; ì«–; á„á…©á‡; ) HANGUL SYLLABLE JJOP
+CAD7;CAD7;110D 1169 11C2;CAD7;110D 1169 11C2; # (ì«—; ì«—; á„ᅩᇂ; ì«—; á„ᅩᇂ; ) HANGUL SYLLABLE JJOH
+CAD8;CAD8;110D 116A;CAD8;110D 116A; # (쫘; 쫘; á„á…ª; 쫘; á„á…ª; ) HANGUL SYLLABLE JJWA
+CAD9;CAD9;110D 116A 11A8;CAD9;110D 116A 11A8; # (ì«™; ì«™; á„ᅪᆨ; ì«™; á„ᅪᆨ; ) HANGUL SYLLABLE JJWAG
+CADA;CADA;110D 116A 11A9;CADA;110D 116A 11A9; # (ì«š; ì«š; á„ᅪᆩ; ì«š; á„ᅪᆩ; ) HANGUL SYLLABLE JJWAGG
+CADB;CADB;110D 116A 11AA;CADB;110D 116A 11AA; # (ì«›; ì«›; á„ᅪᆪ; ì«›; á„ᅪᆪ; ) HANGUL SYLLABLE JJWAGS
+CADC;CADC;110D 116A 11AB;CADC;110D 116A 11AB; # (ì«œ; ì«œ; á„ᅪᆫ; ì«œ; á„ᅪᆫ; ) HANGUL SYLLABLE JJWAN
+CADD;CADD;110D 116A 11AC;CADD;110D 116A 11AC; # (ì«; ì«; á„ᅪᆬ; ì«; á„ᅪᆬ; ) HANGUL SYLLABLE JJWANJ
+CADE;CADE;110D 116A 11AD;CADE;110D 116A 11AD; # (ì«ž; ì«ž; á„ᅪᆭ; ì«ž; á„ᅪᆭ; ) HANGUL SYLLABLE JJWANH
+CADF;CADF;110D 116A 11AE;CADF;110D 116A 11AE; # (ì«Ÿ; ì«Ÿ; á„ᅪᆮ; ì«Ÿ; á„ᅪᆮ; ) HANGUL SYLLABLE JJWAD
+CAE0;CAE0;110D 116A 11AF;CAE0;110D 116A 11AF; # (ì« ; ì« ; á„ᅪᆯ; ì« ; á„ᅪᆯ; ) HANGUL SYLLABLE JJWAL
+CAE1;CAE1;110D 116A 11B0;CAE1;110D 116A 11B0; # (ì«¡; ì«¡; á„ᅪᆰ; ì«¡; á„ᅪᆰ; ) HANGUL SYLLABLE JJWALG
+CAE2;CAE2;110D 116A 11B1;CAE2;110D 116A 11B1; # (ì«¢; ì«¢; á„ᅪᆱ; ì«¢; á„ᅪᆱ; ) HANGUL SYLLABLE JJWALM
+CAE3;CAE3;110D 116A 11B2;CAE3;110D 116A 11B2; # (ì«£; ì«£; á„ᅪᆲ; ì«£; á„ᅪᆲ; ) HANGUL SYLLABLE JJWALB
+CAE4;CAE4;110D 116A 11B3;CAE4;110D 116A 11B3; # (쫤; 쫤; á„ᅪᆳ; 쫤; á„ᅪᆳ; ) HANGUL SYLLABLE JJWALS
+CAE5;CAE5;110D 116A 11B4;CAE5;110D 116A 11B4; # (ì«¥; ì«¥; á„ᅪᆴ; ì«¥; á„ᅪᆴ; ) HANGUL SYLLABLE JJWALT
+CAE6;CAE6;110D 116A 11B5;CAE6;110D 116A 11B5; # (쫦; 쫦; á„ᅪᆵ; 쫦; á„ᅪᆵ; ) HANGUL SYLLABLE JJWALP
+CAE7;CAE7;110D 116A 11B6;CAE7;110D 116A 11B6; # (쫧; 쫧; á„ᅪᆶ; 쫧; á„ᅪᆶ; ) HANGUL SYLLABLE JJWALH
+CAE8;CAE8;110D 116A 11B7;CAE8;110D 116A 11B7; # (쫨; 쫨; á„ᅪᆷ; 쫨; á„ᅪᆷ; ) HANGUL SYLLABLE JJWAM
+CAE9;CAE9;110D 116A 11B8;CAE9;110D 116A 11B8; # (ì«©; ì«©; á„ᅪᆸ; ì«©; á„ᅪᆸ; ) HANGUL SYLLABLE JJWAB
+CAEA;CAEA;110D 116A 11B9;CAEA;110D 116A 11B9; # (쫪; 쫪; á„ᅪᆹ; 쫪; á„ᅪᆹ; ) HANGUL SYLLABLE JJWABS
+CAEB;CAEB;110D 116A 11BA;CAEB;110D 116A 11BA; # (ì««; ì««; á„ᅪᆺ; ì««; á„ᅪᆺ; ) HANGUL SYLLABLE JJWAS
+CAEC;CAEC;110D 116A 11BB;CAEC;110D 116A 11BB; # (쫬; 쫬; á„ᅪᆻ; 쫬; á„ᅪᆻ; ) HANGUL SYLLABLE JJWASS
+CAED;CAED;110D 116A 11BC;CAED;110D 116A 11BC; # (ì«­; ì«­; á„ᅪᆼ; ì«­; á„ᅪᆼ; ) HANGUL SYLLABLE JJWANG
+CAEE;CAEE;110D 116A 11BD;CAEE;110D 116A 11BD; # (ì«®; ì«®; á„ᅪᆽ; ì«®; á„ᅪᆽ; ) HANGUL SYLLABLE JJWAJ
+CAEF;CAEF;110D 116A 11BE;CAEF;110D 116A 11BE; # (쫯; 쫯; á„ᅪᆾ; 쫯; á„ᅪᆾ; ) HANGUL SYLLABLE JJWAC
+CAF0;CAF0;110D 116A 11BF;CAF0;110D 116A 11BF; # (ì«°; ì«°; á„ᅪᆿ; ì«°; á„ᅪᆿ; ) HANGUL SYLLABLE JJWAK
+CAF1;CAF1;110D 116A 11C0;CAF1;110D 116A 11C0; # (쫱; 쫱; á„ᅪᇀ; 쫱; á„ᅪᇀ; ) HANGUL SYLLABLE JJWAT
+CAF2;CAF2;110D 116A 11C1;CAF2;110D 116A 11C1; # (쫲; 쫲; á„á…ªá‡; 쫲; á„á…ªá‡; ) HANGUL SYLLABLE JJWAP
+CAF3;CAF3;110D 116A 11C2;CAF3;110D 116A 11C2; # (쫳; 쫳; á„ᅪᇂ; 쫳; á„ᅪᇂ; ) HANGUL SYLLABLE JJWAH
+CAF4;CAF4;110D 116B;CAF4;110D 116B; # (ì«´; ì«´; á„á…«; ì«´; á„á…«; ) HANGUL SYLLABLE JJWAE
+CAF5;CAF5;110D 116B 11A8;CAF5;110D 116B 11A8; # (쫵; 쫵; á„ᅫᆨ; 쫵; á„ᅫᆨ; ) HANGUL SYLLABLE JJWAEG
+CAF6;CAF6;110D 116B 11A9;CAF6;110D 116B 11A9; # (쫶; 쫶; á„ᅫᆩ; 쫶; á„ᅫᆩ; ) HANGUL SYLLABLE JJWAEGG
+CAF7;CAF7;110D 116B 11AA;CAF7;110D 116B 11AA; # (ì«·; ì«·; á„ᅫᆪ; ì«·; á„ᅫᆪ; ) HANGUL SYLLABLE JJWAEGS
+CAF8;CAF8;110D 116B 11AB;CAF8;110D 116B 11AB; # (쫸; 쫸; á„ᅫᆫ; 쫸; á„ᅫᆫ; ) HANGUL SYLLABLE JJWAEN
+CAF9;CAF9;110D 116B 11AC;CAF9;110D 116B 11AC; # (쫹; 쫹; á„ᅫᆬ; 쫹; á„ᅫᆬ; ) HANGUL SYLLABLE JJWAENJ
+CAFA;CAFA;110D 116B 11AD;CAFA;110D 116B 11AD; # (쫺; 쫺; á„ᅫᆭ; 쫺; á„ᅫᆭ; ) HANGUL SYLLABLE JJWAENH
+CAFB;CAFB;110D 116B 11AE;CAFB;110D 116B 11AE; # (ì«»; ì«»; á„ᅫᆮ; ì«»; á„ᅫᆮ; ) HANGUL SYLLABLE JJWAED
+CAFC;CAFC;110D 116B 11AF;CAFC;110D 116B 11AF; # (쫼; 쫼; á„ᅫᆯ; 쫼; á„ᅫᆯ; ) HANGUL SYLLABLE JJWAEL
+CAFD;CAFD;110D 116B 11B0;CAFD;110D 116B 11B0; # (쫽; 쫽; á„ᅫᆰ; 쫽; á„ᅫᆰ; ) HANGUL SYLLABLE JJWAELG
+CAFE;CAFE;110D 116B 11B1;CAFE;110D 116B 11B1; # (쫾; 쫾; á„ᅫᆱ; 쫾; á„ᅫᆱ; ) HANGUL SYLLABLE JJWAELM
+CAFF;CAFF;110D 116B 11B2;CAFF;110D 116B 11B2; # (ì«¿; ì«¿; á„ᅫᆲ; ì«¿; á„ᅫᆲ; ) HANGUL SYLLABLE JJWAELB
+CB00;CB00;110D 116B 11B3;CB00;110D 116B 11B3; # (쬀; 쬀; á„ᅫᆳ; 쬀; á„ᅫᆳ; ) HANGUL SYLLABLE JJWAELS
+CB01;CB01;110D 116B 11B4;CB01;110D 116B 11B4; # (ì¬; ì¬; á„ᅫᆴ; ì¬; á„ᅫᆴ; ) HANGUL SYLLABLE JJWAELT
+CB02;CB02;110D 116B 11B5;CB02;110D 116B 11B5; # (쬂; 쬂; á„ᅫᆵ; 쬂; á„ᅫᆵ; ) HANGUL SYLLABLE JJWAELP
+CB03;CB03;110D 116B 11B6;CB03;110D 116B 11B6; # (쬃; 쬃; á„ᅫᆶ; 쬃; á„ᅫᆶ; ) HANGUL SYLLABLE JJWAELH
+CB04;CB04;110D 116B 11B7;CB04;110D 116B 11B7; # (쬄; 쬄; á„ᅫᆷ; 쬄; á„ᅫᆷ; ) HANGUL SYLLABLE JJWAEM
+CB05;CB05;110D 116B 11B8;CB05;110D 116B 11B8; # (쬅; 쬅; á„ᅫᆸ; 쬅; á„ᅫᆸ; ) HANGUL SYLLABLE JJWAEB
+CB06;CB06;110D 116B 11B9;CB06;110D 116B 11B9; # (쬆; 쬆; á„ᅫᆹ; 쬆; á„ᅫᆹ; ) HANGUL SYLLABLE JJWAEBS
+CB07;CB07;110D 116B 11BA;CB07;110D 116B 11BA; # (쬇; 쬇; á„ᅫᆺ; 쬇; á„ᅫᆺ; ) HANGUL SYLLABLE JJWAES
+CB08;CB08;110D 116B 11BB;CB08;110D 116B 11BB; # (쬈; 쬈; á„ᅫᆻ; 쬈; á„ᅫᆻ; ) HANGUL SYLLABLE JJWAESS
+CB09;CB09;110D 116B 11BC;CB09;110D 116B 11BC; # (쬉; 쬉; á„ᅫᆼ; 쬉; á„ᅫᆼ; ) HANGUL SYLLABLE JJWAENG
+CB0A;CB0A;110D 116B 11BD;CB0A;110D 116B 11BD; # (쬊; 쬊; á„ᅫᆽ; 쬊; á„ᅫᆽ; ) HANGUL SYLLABLE JJWAEJ
+CB0B;CB0B;110D 116B 11BE;CB0B;110D 116B 11BE; # (쬋; 쬋; á„ᅫᆾ; 쬋; á„ᅫᆾ; ) HANGUL SYLLABLE JJWAEC
+CB0C;CB0C;110D 116B 11BF;CB0C;110D 116B 11BF; # (쬌; 쬌; á„ᅫᆿ; 쬌; á„ᅫᆿ; ) HANGUL SYLLABLE JJWAEK
+CB0D;CB0D;110D 116B 11C0;CB0D;110D 116B 11C0; # (ì¬; ì¬; á„ᅫᇀ; ì¬; á„ᅫᇀ; ) HANGUL SYLLABLE JJWAET
+CB0E;CB0E;110D 116B 11C1;CB0E;110D 116B 11C1; # (쬎; 쬎; á„á…«á‡; 쬎; á„á…«á‡; ) HANGUL SYLLABLE JJWAEP
+CB0F;CB0F;110D 116B 11C2;CB0F;110D 116B 11C2; # (ì¬; ì¬; á„ᅫᇂ; ì¬; á„ᅫᇂ; ) HANGUL SYLLABLE JJWAEH
+CB10;CB10;110D 116C;CB10;110D 116C; # (ì¬; ì¬; á„á…¬; ì¬; á„á…¬; ) HANGUL SYLLABLE JJOE
+CB11;CB11;110D 116C 11A8;CB11;110D 116C 11A8; # (쬑; 쬑; á„ᅬᆨ; 쬑; á„ᅬᆨ; ) HANGUL SYLLABLE JJOEG
+CB12;CB12;110D 116C 11A9;CB12;110D 116C 11A9; # (쬒; 쬒; á„ᅬᆩ; 쬒; á„ᅬᆩ; ) HANGUL SYLLABLE JJOEGG
+CB13;CB13;110D 116C 11AA;CB13;110D 116C 11AA; # (쬓; 쬓; á„ᅬᆪ; 쬓; á„ᅬᆪ; ) HANGUL SYLLABLE JJOEGS
+CB14;CB14;110D 116C 11AB;CB14;110D 116C 11AB; # (쬔; 쬔; á„ᅬᆫ; 쬔; á„ᅬᆫ; ) HANGUL SYLLABLE JJOEN
+CB15;CB15;110D 116C 11AC;CB15;110D 116C 11AC; # (쬕; 쬕; á„ᅬᆬ; 쬕; á„ᅬᆬ; ) HANGUL SYLLABLE JJOENJ
+CB16;CB16;110D 116C 11AD;CB16;110D 116C 11AD; # (쬖; 쬖; á„ᅬᆭ; 쬖; á„ᅬᆭ; ) HANGUL SYLLABLE JJOENH
+CB17;CB17;110D 116C 11AE;CB17;110D 116C 11AE; # (쬗; 쬗; á„ᅬᆮ; 쬗; á„ᅬᆮ; ) HANGUL SYLLABLE JJOED
+CB18;CB18;110D 116C 11AF;CB18;110D 116C 11AF; # (쬘; 쬘; á„ᅬᆯ; 쬘; á„ᅬᆯ; ) HANGUL SYLLABLE JJOEL
+CB19;CB19;110D 116C 11B0;CB19;110D 116C 11B0; # (쬙; 쬙; á„ᅬᆰ; 쬙; á„ᅬᆰ; ) HANGUL SYLLABLE JJOELG
+CB1A;CB1A;110D 116C 11B1;CB1A;110D 116C 11B1; # (쬚; 쬚; á„ᅬᆱ; 쬚; á„ᅬᆱ; ) HANGUL SYLLABLE JJOELM
+CB1B;CB1B;110D 116C 11B2;CB1B;110D 116C 11B2; # (쬛; 쬛; á„ᅬᆲ; 쬛; á„ᅬᆲ; ) HANGUL SYLLABLE JJOELB
+CB1C;CB1C;110D 116C 11B3;CB1C;110D 116C 11B3; # (쬜; 쬜; á„ᅬᆳ; 쬜; á„ᅬᆳ; ) HANGUL SYLLABLE JJOELS
+CB1D;CB1D;110D 116C 11B4;CB1D;110D 116C 11B4; # (ì¬; ì¬; á„ᅬᆴ; ì¬; á„ᅬᆴ; ) HANGUL SYLLABLE JJOELT
+CB1E;CB1E;110D 116C 11B5;CB1E;110D 116C 11B5; # (쬞; 쬞; á„ᅬᆵ; 쬞; á„ᅬᆵ; ) HANGUL SYLLABLE JJOELP
+CB1F;CB1F;110D 116C 11B6;CB1F;110D 116C 11B6; # (쬟; 쬟; á„ᅬᆶ; 쬟; á„ᅬᆶ; ) HANGUL SYLLABLE JJOELH
+CB20;CB20;110D 116C 11B7;CB20;110D 116C 11B7; # (쬠; 쬠; á„ᅬᆷ; 쬠; á„ᅬᆷ; ) HANGUL SYLLABLE JJOEM
+CB21;CB21;110D 116C 11B8;CB21;110D 116C 11B8; # (쬡; 쬡; á„ᅬᆸ; 쬡; á„ᅬᆸ; ) HANGUL SYLLABLE JJOEB
+CB22;CB22;110D 116C 11B9;CB22;110D 116C 11B9; # (쬢; 쬢; á„ᅬᆹ; 쬢; á„ᅬᆹ; ) HANGUL SYLLABLE JJOEBS
+CB23;CB23;110D 116C 11BA;CB23;110D 116C 11BA; # (쬣; 쬣; á„ᅬᆺ; 쬣; á„ᅬᆺ; ) HANGUL SYLLABLE JJOES
+CB24;CB24;110D 116C 11BB;CB24;110D 116C 11BB; # (쬤; 쬤; á„ᅬᆻ; 쬤; á„ᅬᆻ; ) HANGUL SYLLABLE JJOESS
+CB25;CB25;110D 116C 11BC;CB25;110D 116C 11BC; # (쬥; 쬥; á„ᅬᆼ; 쬥; á„ᅬᆼ; ) HANGUL SYLLABLE JJOENG
+CB26;CB26;110D 116C 11BD;CB26;110D 116C 11BD; # (쬦; 쬦; á„ᅬᆽ; 쬦; á„ᅬᆽ; ) HANGUL SYLLABLE JJOEJ
+CB27;CB27;110D 116C 11BE;CB27;110D 116C 11BE; # (쬧; 쬧; á„ᅬᆾ; 쬧; á„ᅬᆾ; ) HANGUL SYLLABLE JJOEC
+CB28;CB28;110D 116C 11BF;CB28;110D 116C 11BF; # (쬨; 쬨; á„ᅬᆿ; 쬨; á„ᅬᆿ; ) HANGUL SYLLABLE JJOEK
+CB29;CB29;110D 116C 11C0;CB29;110D 116C 11C0; # (쬩; 쬩; á„ᅬᇀ; 쬩; á„ᅬᇀ; ) HANGUL SYLLABLE JJOET
+CB2A;CB2A;110D 116C 11C1;CB2A;110D 116C 11C1; # (쬪; 쬪; á„á…¬á‡; 쬪; á„á…¬á‡; ) HANGUL SYLLABLE JJOEP
+CB2B;CB2B;110D 116C 11C2;CB2B;110D 116C 11C2; # (쬫; 쬫; á„ᅬᇂ; 쬫; á„ᅬᇂ; ) HANGUL SYLLABLE JJOEH
+CB2C;CB2C;110D 116D;CB2C;110D 116D; # (쬬; 쬬; á„á…­; 쬬; á„á…­; ) HANGUL SYLLABLE JJYO
+CB2D;CB2D;110D 116D 11A8;CB2D;110D 116D 11A8; # (쬭; 쬭; á„ᅭᆨ; 쬭; á„ᅭᆨ; ) HANGUL SYLLABLE JJYOG
+CB2E;CB2E;110D 116D 11A9;CB2E;110D 116D 11A9; # (쬮; 쬮; á„ᅭᆩ; 쬮; á„ᅭᆩ; ) HANGUL SYLLABLE JJYOGG
+CB2F;CB2F;110D 116D 11AA;CB2F;110D 116D 11AA; # (쬯; 쬯; á„ᅭᆪ; 쬯; á„ᅭᆪ; ) HANGUL SYLLABLE JJYOGS
+CB30;CB30;110D 116D 11AB;CB30;110D 116D 11AB; # (쬰; 쬰; á„ᅭᆫ; 쬰; á„ᅭᆫ; ) HANGUL SYLLABLE JJYON
+CB31;CB31;110D 116D 11AC;CB31;110D 116D 11AC; # (쬱; 쬱; á„ᅭᆬ; 쬱; á„ᅭᆬ; ) HANGUL SYLLABLE JJYONJ
+CB32;CB32;110D 116D 11AD;CB32;110D 116D 11AD; # (쬲; 쬲; á„ᅭᆭ; 쬲; á„ᅭᆭ; ) HANGUL SYLLABLE JJYONH
+CB33;CB33;110D 116D 11AE;CB33;110D 116D 11AE; # (쬳; 쬳; á„ᅭᆮ; 쬳; á„ᅭᆮ; ) HANGUL SYLLABLE JJYOD
+CB34;CB34;110D 116D 11AF;CB34;110D 116D 11AF; # (쬴; 쬴; á„ᅭᆯ; 쬴; á„ᅭᆯ; ) HANGUL SYLLABLE JJYOL
+CB35;CB35;110D 116D 11B0;CB35;110D 116D 11B0; # (쬵; 쬵; á„ᅭᆰ; 쬵; á„ᅭᆰ; ) HANGUL SYLLABLE JJYOLG
+CB36;CB36;110D 116D 11B1;CB36;110D 116D 11B1; # (쬶; 쬶; á„ᅭᆱ; 쬶; á„ᅭᆱ; ) HANGUL SYLLABLE JJYOLM
+CB37;CB37;110D 116D 11B2;CB37;110D 116D 11B2; # (쬷; 쬷; á„ᅭᆲ; 쬷; á„ᅭᆲ; ) HANGUL SYLLABLE JJYOLB
+CB38;CB38;110D 116D 11B3;CB38;110D 116D 11B3; # (쬸; 쬸; á„ᅭᆳ; 쬸; á„ᅭᆳ; ) HANGUL SYLLABLE JJYOLS
+CB39;CB39;110D 116D 11B4;CB39;110D 116D 11B4; # (쬹; 쬹; á„ᅭᆴ; 쬹; á„ᅭᆴ; ) HANGUL SYLLABLE JJYOLT
+CB3A;CB3A;110D 116D 11B5;CB3A;110D 116D 11B5; # (쬺; 쬺; á„ᅭᆵ; 쬺; á„ᅭᆵ; ) HANGUL SYLLABLE JJYOLP
+CB3B;CB3B;110D 116D 11B6;CB3B;110D 116D 11B6; # (쬻; 쬻; á„ᅭᆶ; 쬻; á„ᅭᆶ; ) HANGUL SYLLABLE JJYOLH
+CB3C;CB3C;110D 116D 11B7;CB3C;110D 116D 11B7; # (쬼; 쬼; á„ᅭᆷ; 쬼; á„ᅭᆷ; ) HANGUL SYLLABLE JJYOM
+CB3D;CB3D;110D 116D 11B8;CB3D;110D 116D 11B8; # (쬽; 쬽; á„ᅭᆸ; 쬽; á„ᅭᆸ; ) HANGUL SYLLABLE JJYOB
+CB3E;CB3E;110D 116D 11B9;CB3E;110D 116D 11B9; # (쬾; 쬾; á„ᅭᆹ; 쬾; á„ᅭᆹ; ) HANGUL SYLLABLE JJYOBS
+CB3F;CB3F;110D 116D 11BA;CB3F;110D 116D 11BA; # (쬿; 쬿; á„ᅭᆺ; 쬿; á„ᅭᆺ; ) HANGUL SYLLABLE JJYOS
+CB40;CB40;110D 116D 11BB;CB40;110D 116D 11BB; # (ì­€; ì­€; á„ᅭᆻ; ì­€; á„ᅭᆻ; ) HANGUL SYLLABLE JJYOSS
+CB41;CB41;110D 116D 11BC;CB41;110D 116D 11BC; # (ì­; ì­; á„ᅭᆼ; ì­; á„ᅭᆼ; ) HANGUL SYLLABLE JJYONG
+CB42;CB42;110D 116D 11BD;CB42;110D 116D 11BD; # (ì­‚; ì­‚; á„ᅭᆽ; ì­‚; á„ᅭᆽ; ) HANGUL SYLLABLE JJYOJ
+CB43;CB43;110D 116D 11BE;CB43;110D 116D 11BE; # (ì­ƒ; ì­ƒ; á„ᅭᆾ; ì­ƒ; á„ᅭᆾ; ) HANGUL SYLLABLE JJYOC
+CB44;CB44;110D 116D 11BF;CB44;110D 116D 11BF; # (ì­„; ì­„; á„ᅭᆿ; ì­„; á„ᅭᆿ; ) HANGUL SYLLABLE JJYOK
+CB45;CB45;110D 116D 11C0;CB45;110D 116D 11C0; # (ì­…; ì­…; á„ᅭᇀ; ì­…; á„ᅭᇀ; ) HANGUL SYLLABLE JJYOT
+CB46;CB46;110D 116D 11C1;CB46;110D 116D 11C1; # (ì­†; ì­†; á„á…­á‡; ì­†; á„á…­á‡; ) HANGUL SYLLABLE JJYOP
+CB47;CB47;110D 116D 11C2;CB47;110D 116D 11C2; # (ì­‡; ì­‡; á„ᅭᇂ; ì­‡; á„ᅭᇂ; ) HANGUL SYLLABLE JJYOH
+CB48;CB48;110D 116E;CB48;110D 116E; # (ì­ˆ; ì­ˆ; á„á…®; ì­ˆ; á„á…®; ) HANGUL SYLLABLE JJU
+CB49;CB49;110D 116E 11A8;CB49;110D 116E 11A8; # (ì­‰; ì­‰; á„ᅮᆨ; ì­‰; á„ᅮᆨ; ) HANGUL SYLLABLE JJUG
+CB4A;CB4A;110D 116E 11A9;CB4A;110D 116E 11A9; # (ì­Š; ì­Š; á„ᅮᆩ; ì­Š; á„ᅮᆩ; ) HANGUL SYLLABLE JJUGG
+CB4B;CB4B;110D 116E 11AA;CB4B;110D 116E 11AA; # (ì­‹; ì­‹; á„ᅮᆪ; ì­‹; á„ᅮᆪ; ) HANGUL SYLLABLE JJUGS
+CB4C;CB4C;110D 116E 11AB;CB4C;110D 116E 11AB; # (ì­Œ; ì­Œ; á„ᅮᆫ; ì­Œ; á„ᅮᆫ; ) HANGUL SYLLABLE JJUN
+CB4D;CB4D;110D 116E 11AC;CB4D;110D 116E 11AC; # (ì­; ì­; á„ᅮᆬ; ì­; á„ᅮᆬ; ) HANGUL SYLLABLE JJUNJ
+CB4E;CB4E;110D 116E 11AD;CB4E;110D 116E 11AD; # (ì­Ž; ì­Ž; á„ᅮᆭ; ì­Ž; á„ᅮᆭ; ) HANGUL SYLLABLE JJUNH
+CB4F;CB4F;110D 116E 11AE;CB4F;110D 116E 11AE; # (ì­; ì­; á„ᅮᆮ; ì­; á„ᅮᆮ; ) HANGUL SYLLABLE JJUD
+CB50;CB50;110D 116E 11AF;CB50;110D 116E 11AF; # (ì­; ì­; á„ᅮᆯ; ì­; á„ᅮᆯ; ) HANGUL SYLLABLE JJUL
+CB51;CB51;110D 116E 11B0;CB51;110D 116E 11B0; # (ì­‘; ì­‘; á„ᅮᆰ; ì­‘; á„ᅮᆰ; ) HANGUL SYLLABLE JJULG
+CB52;CB52;110D 116E 11B1;CB52;110D 116E 11B1; # (ì­’; ì­’; á„ᅮᆱ; ì­’; á„ᅮᆱ; ) HANGUL SYLLABLE JJULM
+CB53;CB53;110D 116E 11B2;CB53;110D 116E 11B2; # (ì­“; ì­“; á„ᅮᆲ; ì­“; á„ᅮᆲ; ) HANGUL SYLLABLE JJULB
+CB54;CB54;110D 116E 11B3;CB54;110D 116E 11B3; # (ì­”; ì­”; á„ᅮᆳ; ì­”; á„ᅮᆳ; ) HANGUL SYLLABLE JJULS
+CB55;CB55;110D 116E 11B4;CB55;110D 116E 11B4; # (ì­•; ì­•; á„ᅮᆴ; ì­•; á„ᅮᆴ; ) HANGUL SYLLABLE JJULT
+CB56;CB56;110D 116E 11B5;CB56;110D 116E 11B5; # (ì­–; ì­–; á„ᅮᆵ; ì­–; á„ᅮᆵ; ) HANGUL SYLLABLE JJULP
+CB57;CB57;110D 116E 11B6;CB57;110D 116E 11B6; # (ì­—; ì­—; á„ᅮᆶ; ì­—; á„ᅮᆶ; ) HANGUL SYLLABLE JJULH
+CB58;CB58;110D 116E 11B7;CB58;110D 116E 11B7; # (ì­˜; ì­˜; á„ᅮᆷ; ì­˜; á„ᅮᆷ; ) HANGUL SYLLABLE JJUM
+CB59;CB59;110D 116E 11B8;CB59;110D 116E 11B8; # (ì­™; ì­™; á„ᅮᆸ; ì­™; á„ᅮᆸ; ) HANGUL SYLLABLE JJUB
+CB5A;CB5A;110D 116E 11B9;CB5A;110D 116E 11B9; # (ì­š; ì­š; á„ᅮᆹ; ì­š; á„ᅮᆹ; ) HANGUL SYLLABLE JJUBS
+CB5B;CB5B;110D 116E 11BA;CB5B;110D 116E 11BA; # (ì­›; ì­›; á„ᅮᆺ; ì­›; á„ᅮᆺ; ) HANGUL SYLLABLE JJUS
+CB5C;CB5C;110D 116E 11BB;CB5C;110D 116E 11BB; # (ì­œ; ì­œ; á„ᅮᆻ; ì­œ; á„ᅮᆻ; ) HANGUL SYLLABLE JJUSS
+CB5D;CB5D;110D 116E 11BC;CB5D;110D 116E 11BC; # (ì­; ì­; á„ᅮᆼ; ì­; á„ᅮᆼ; ) HANGUL SYLLABLE JJUNG
+CB5E;CB5E;110D 116E 11BD;CB5E;110D 116E 11BD; # (ì­ž; ì­ž; á„ᅮᆽ; ì­ž; á„ᅮᆽ; ) HANGUL SYLLABLE JJUJ
+CB5F;CB5F;110D 116E 11BE;CB5F;110D 116E 11BE; # (ì­Ÿ; ì­Ÿ; á„ᅮᆾ; ì­Ÿ; á„ᅮᆾ; ) HANGUL SYLLABLE JJUC
+CB60;CB60;110D 116E 11BF;CB60;110D 116E 11BF; # (ì­ ; ì­ ; á„ᅮᆿ; ì­ ; á„ᅮᆿ; ) HANGUL SYLLABLE JJUK
+CB61;CB61;110D 116E 11C0;CB61;110D 116E 11C0; # (ì­¡; ì­¡; á„ᅮᇀ; ì­¡; á„ᅮᇀ; ) HANGUL SYLLABLE JJUT
+CB62;CB62;110D 116E 11C1;CB62;110D 116E 11C1; # (ì­¢; ì­¢; á„á…®á‡; ì­¢; á„á…®á‡; ) HANGUL SYLLABLE JJUP
+CB63;CB63;110D 116E 11C2;CB63;110D 116E 11C2; # (ì­£; ì­£; á„ᅮᇂ; ì­£; á„ᅮᇂ; ) HANGUL SYLLABLE JJUH
+CB64;CB64;110D 116F;CB64;110D 116F; # (ì­¤; ì­¤; á„á…¯; ì­¤; á„á…¯; ) HANGUL SYLLABLE JJWEO
+CB65;CB65;110D 116F 11A8;CB65;110D 116F 11A8; # (ì­¥; ì­¥; á„ᅯᆨ; ì­¥; á„ᅯᆨ; ) HANGUL SYLLABLE JJWEOG
+CB66;CB66;110D 116F 11A9;CB66;110D 116F 11A9; # (ì­¦; ì­¦; á„ᅯᆩ; ì­¦; á„ᅯᆩ; ) HANGUL SYLLABLE JJWEOGG
+CB67;CB67;110D 116F 11AA;CB67;110D 116F 11AA; # (ì­§; ì­§; á„ᅯᆪ; ì­§; á„ᅯᆪ; ) HANGUL SYLLABLE JJWEOGS
+CB68;CB68;110D 116F 11AB;CB68;110D 116F 11AB; # (ì­¨; ì­¨; á„ᅯᆫ; ì­¨; á„ᅯᆫ; ) HANGUL SYLLABLE JJWEON
+CB69;CB69;110D 116F 11AC;CB69;110D 116F 11AC; # (ì­©; ì­©; á„ᅯᆬ; ì­©; á„ᅯᆬ; ) HANGUL SYLLABLE JJWEONJ
+CB6A;CB6A;110D 116F 11AD;CB6A;110D 116F 11AD; # (ì­ª; ì­ª; á„ᅯᆭ; ì­ª; á„ᅯᆭ; ) HANGUL SYLLABLE JJWEONH
+CB6B;CB6B;110D 116F 11AE;CB6B;110D 116F 11AE; # (ì­«; ì­«; á„ᅯᆮ; ì­«; á„ᅯᆮ; ) HANGUL SYLLABLE JJWEOD
+CB6C;CB6C;110D 116F 11AF;CB6C;110D 116F 11AF; # (ì­¬; ì­¬; á„ᅯᆯ; ì­¬; á„ᅯᆯ; ) HANGUL SYLLABLE JJWEOL
+CB6D;CB6D;110D 116F 11B0;CB6D;110D 116F 11B0; # (ì­­; ì­­; á„ᅯᆰ; ì­­; á„ᅯᆰ; ) HANGUL SYLLABLE JJWEOLG
+CB6E;CB6E;110D 116F 11B1;CB6E;110D 116F 11B1; # (ì­®; ì­®; á„ᅯᆱ; ì­®; á„ᅯᆱ; ) HANGUL SYLLABLE JJWEOLM
+CB6F;CB6F;110D 116F 11B2;CB6F;110D 116F 11B2; # (ì­¯; ì­¯; á„ᅯᆲ; ì­¯; á„ᅯᆲ; ) HANGUL SYLLABLE JJWEOLB
+CB70;CB70;110D 116F 11B3;CB70;110D 116F 11B3; # (ì­°; ì­°; á„ᅯᆳ; ì­°; á„ᅯᆳ; ) HANGUL SYLLABLE JJWEOLS
+CB71;CB71;110D 116F 11B4;CB71;110D 116F 11B4; # (ì­±; ì­±; á„ᅯᆴ; ì­±; á„ᅯᆴ; ) HANGUL SYLLABLE JJWEOLT
+CB72;CB72;110D 116F 11B5;CB72;110D 116F 11B5; # (ì­²; ì­²; á„ᅯᆵ; ì­²; á„ᅯᆵ; ) HANGUL SYLLABLE JJWEOLP
+CB73;CB73;110D 116F 11B6;CB73;110D 116F 11B6; # (ì­³; ì­³; á„ᅯᆶ; ì­³; á„ᅯᆶ; ) HANGUL SYLLABLE JJWEOLH
+CB74;CB74;110D 116F 11B7;CB74;110D 116F 11B7; # (ì­´; ì­´; á„ᅯᆷ; ì­´; á„ᅯᆷ; ) HANGUL SYLLABLE JJWEOM
+CB75;CB75;110D 116F 11B8;CB75;110D 116F 11B8; # (ì­µ; ì­µ; á„ᅯᆸ; ì­µ; á„ᅯᆸ; ) HANGUL SYLLABLE JJWEOB
+CB76;CB76;110D 116F 11B9;CB76;110D 116F 11B9; # (ì­¶; ì­¶; á„ᅯᆹ; ì­¶; á„ᅯᆹ; ) HANGUL SYLLABLE JJWEOBS
+CB77;CB77;110D 116F 11BA;CB77;110D 116F 11BA; # (ì­·; ì­·; á„ᅯᆺ; ì­·; á„ᅯᆺ; ) HANGUL SYLLABLE JJWEOS
+CB78;CB78;110D 116F 11BB;CB78;110D 116F 11BB; # (ì­¸; ì­¸; á„ᅯᆻ; ì­¸; á„ᅯᆻ; ) HANGUL SYLLABLE JJWEOSS
+CB79;CB79;110D 116F 11BC;CB79;110D 116F 11BC; # (ì­¹; ì­¹; á„ᅯᆼ; ì­¹; á„ᅯᆼ; ) HANGUL SYLLABLE JJWEONG
+CB7A;CB7A;110D 116F 11BD;CB7A;110D 116F 11BD; # (ì­º; ì­º; á„ᅯᆽ; ì­º; á„ᅯᆽ; ) HANGUL SYLLABLE JJWEOJ
+CB7B;CB7B;110D 116F 11BE;CB7B;110D 116F 11BE; # (ì­»; ì­»; á„ᅯᆾ; ì­»; á„ᅯᆾ; ) HANGUL SYLLABLE JJWEOC
+CB7C;CB7C;110D 116F 11BF;CB7C;110D 116F 11BF; # (ì­¼; ì­¼; á„ᅯᆿ; ì­¼; á„ᅯᆿ; ) HANGUL SYLLABLE JJWEOK
+CB7D;CB7D;110D 116F 11C0;CB7D;110D 116F 11C0; # (ì­½; ì­½; á„ᅯᇀ; ì­½; á„ᅯᇀ; ) HANGUL SYLLABLE JJWEOT
+CB7E;CB7E;110D 116F 11C1;CB7E;110D 116F 11C1; # (ì­¾; ì­¾; á„á…¯á‡; ì­¾; á„á…¯á‡; ) HANGUL SYLLABLE JJWEOP
+CB7F;CB7F;110D 116F 11C2;CB7F;110D 116F 11C2; # (ì­¿; ì­¿; á„ᅯᇂ; ì­¿; á„ᅯᇂ; ) HANGUL SYLLABLE JJWEOH
+CB80;CB80;110D 1170;CB80;110D 1170; # (쮀; 쮀; á„á…°; 쮀; á„á…°; ) HANGUL SYLLABLE JJWE
+CB81;CB81;110D 1170 11A8;CB81;110D 1170 11A8; # (ì®; ì®; á„ᅰᆨ; ì®; á„ᅰᆨ; ) HANGUL SYLLABLE JJWEG
+CB82;CB82;110D 1170 11A9;CB82;110D 1170 11A9; # (쮂; 쮂; á„ᅰᆩ; 쮂; á„ᅰᆩ; ) HANGUL SYLLABLE JJWEGG
+CB83;CB83;110D 1170 11AA;CB83;110D 1170 11AA; # (쮃; 쮃; á„ᅰᆪ; 쮃; á„ᅰᆪ; ) HANGUL SYLLABLE JJWEGS
+CB84;CB84;110D 1170 11AB;CB84;110D 1170 11AB; # (쮄; 쮄; á„ᅰᆫ; 쮄; á„ᅰᆫ; ) HANGUL SYLLABLE JJWEN
+CB85;CB85;110D 1170 11AC;CB85;110D 1170 11AC; # (ì®…; ì®…; á„ᅰᆬ; ì®…; á„ᅰᆬ; ) HANGUL SYLLABLE JJWENJ
+CB86;CB86;110D 1170 11AD;CB86;110D 1170 11AD; # (쮆; 쮆; á„ᅰᆭ; 쮆; á„ᅰᆭ; ) HANGUL SYLLABLE JJWENH
+CB87;CB87;110D 1170 11AE;CB87;110D 1170 11AE; # (쮇; 쮇; á„ᅰᆮ; 쮇; á„ᅰᆮ; ) HANGUL SYLLABLE JJWED
+CB88;CB88;110D 1170 11AF;CB88;110D 1170 11AF; # (쮈; 쮈; á„ᅰᆯ; 쮈; á„ᅰᆯ; ) HANGUL SYLLABLE JJWEL
+CB89;CB89;110D 1170 11B0;CB89;110D 1170 11B0; # (쮉; 쮉; á„ᅰᆰ; 쮉; á„ᅰᆰ; ) HANGUL SYLLABLE JJWELG
+CB8A;CB8A;110D 1170 11B1;CB8A;110D 1170 11B1; # (쮊; 쮊; á„ᅰᆱ; 쮊; á„ᅰᆱ; ) HANGUL SYLLABLE JJWELM
+CB8B;CB8B;110D 1170 11B2;CB8B;110D 1170 11B2; # (쮋; 쮋; á„ᅰᆲ; 쮋; á„ᅰᆲ; ) HANGUL SYLLABLE JJWELB
+CB8C;CB8C;110D 1170 11B3;CB8C;110D 1170 11B3; # (쮌; 쮌; á„ᅰᆳ; 쮌; á„ᅰᆳ; ) HANGUL SYLLABLE JJWELS
+CB8D;CB8D;110D 1170 11B4;CB8D;110D 1170 11B4; # (ì®; ì®; á„ᅰᆴ; ì®; á„ᅰᆴ; ) HANGUL SYLLABLE JJWELT
+CB8E;CB8E;110D 1170 11B5;CB8E;110D 1170 11B5; # (쮎; 쮎; á„ᅰᆵ; 쮎; á„ᅰᆵ; ) HANGUL SYLLABLE JJWELP
+CB8F;CB8F;110D 1170 11B6;CB8F;110D 1170 11B6; # (ì®; ì®; á„ᅰᆶ; ì®; á„ᅰᆶ; ) HANGUL SYLLABLE JJWELH
+CB90;CB90;110D 1170 11B7;CB90;110D 1170 11B7; # (ì®; ì®; á„ᅰᆷ; ì®; á„ᅰᆷ; ) HANGUL SYLLABLE JJWEM
+CB91;CB91;110D 1170 11B8;CB91;110D 1170 11B8; # (쮑; 쮑; á„ᅰᆸ; 쮑; á„ᅰᆸ; ) HANGUL SYLLABLE JJWEB
+CB92;CB92;110D 1170 11B9;CB92;110D 1170 11B9; # (ì®’; ì®’; á„ᅰᆹ; ì®’; á„ᅰᆹ; ) HANGUL SYLLABLE JJWEBS
+CB93;CB93;110D 1170 11BA;CB93;110D 1170 11BA; # (쮓; 쮓; á„ᅰᆺ; 쮓; á„ᅰᆺ; ) HANGUL SYLLABLE JJWES
+CB94;CB94;110D 1170 11BB;CB94;110D 1170 11BB; # (ì®”; ì®”; á„ᅰᆻ; ì®”; á„ᅰᆻ; ) HANGUL SYLLABLE JJWESS
+CB95;CB95;110D 1170 11BC;CB95;110D 1170 11BC; # (쮕; 쮕; á„ᅰᆼ; 쮕; á„ᅰᆼ; ) HANGUL SYLLABLE JJWENG
+CB96;CB96;110D 1170 11BD;CB96;110D 1170 11BD; # (ì®–; ì®–; á„ᅰᆽ; ì®–; á„ᅰᆽ; ) HANGUL SYLLABLE JJWEJ
+CB97;CB97;110D 1170 11BE;CB97;110D 1170 11BE; # (ì®—; ì®—; á„ᅰᆾ; ì®—; á„ᅰᆾ; ) HANGUL SYLLABLE JJWEC
+CB98;CB98;110D 1170 11BF;CB98;110D 1170 11BF; # (쮘; 쮘; á„ᅰᆿ; 쮘; á„ᅰᆿ; ) HANGUL SYLLABLE JJWEK
+CB99;CB99;110D 1170 11C0;CB99;110D 1170 11C0; # (ì®™; ì®™; á„ᅰᇀ; ì®™; á„ᅰᇀ; ) HANGUL SYLLABLE JJWET
+CB9A;CB9A;110D 1170 11C1;CB9A;110D 1170 11C1; # (쮚; 쮚; á„á…°á‡; 쮚; á„á…°á‡; ) HANGUL SYLLABLE JJWEP
+CB9B;CB9B;110D 1170 11C2;CB9B;110D 1170 11C2; # (ì®›; ì®›; á„ᅰᇂ; ì®›; á„ᅰᇂ; ) HANGUL SYLLABLE JJWEH
+CB9C;CB9C;110D 1171;CB9C;110D 1171; # (쮜; 쮜; á„á…±; 쮜; á„á…±; ) HANGUL SYLLABLE JJWI
+CB9D;CB9D;110D 1171 11A8;CB9D;110D 1171 11A8; # (ì®; ì®; á„ᅱᆨ; ì®; á„ᅱᆨ; ) HANGUL SYLLABLE JJWIG
+CB9E;CB9E;110D 1171 11A9;CB9E;110D 1171 11A9; # (쮞; 쮞; á„ᅱᆩ; 쮞; á„ᅱᆩ; ) HANGUL SYLLABLE JJWIGG
+CB9F;CB9F;110D 1171 11AA;CB9F;110D 1171 11AA; # (쮟; 쮟; á„ᅱᆪ; 쮟; á„ᅱᆪ; ) HANGUL SYLLABLE JJWIGS
+CBA0;CBA0;110D 1171 11AB;CBA0;110D 1171 11AB; # (ì® ; ì® ; á„ᅱᆫ; ì® ; á„ᅱᆫ; ) HANGUL SYLLABLE JJWIN
+CBA1;CBA1;110D 1171 11AC;CBA1;110D 1171 11AC; # (쮡; 쮡; á„ᅱᆬ; 쮡; á„ᅱᆬ; ) HANGUL SYLLABLE JJWINJ
+CBA2;CBA2;110D 1171 11AD;CBA2;110D 1171 11AD; # (쮢; 쮢; á„ᅱᆭ; 쮢; á„ᅱᆭ; ) HANGUL SYLLABLE JJWINH
+CBA3;CBA3;110D 1171 11AE;CBA3;110D 1171 11AE; # (쮣; 쮣; á„ᅱᆮ; 쮣; á„ᅱᆮ; ) HANGUL SYLLABLE JJWID
+CBA4;CBA4;110D 1171 11AF;CBA4;110D 1171 11AF; # (쮤; 쮤; á„ᅱᆯ; 쮤; á„ᅱᆯ; ) HANGUL SYLLABLE JJWIL
+CBA5;CBA5;110D 1171 11B0;CBA5;110D 1171 11B0; # (쮥; 쮥; á„ᅱᆰ; 쮥; á„ᅱᆰ; ) HANGUL SYLLABLE JJWILG
+CBA6;CBA6;110D 1171 11B1;CBA6;110D 1171 11B1; # (쮦; 쮦; á„ᅱᆱ; 쮦; á„ᅱᆱ; ) HANGUL SYLLABLE JJWILM
+CBA7;CBA7;110D 1171 11B2;CBA7;110D 1171 11B2; # (쮧; 쮧; á„ᅱᆲ; 쮧; á„ᅱᆲ; ) HANGUL SYLLABLE JJWILB
+CBA8;CBA8;110D 1171 11B3;CBA8;110D 1171 11B3; # (쮨; 쮨; á„ᅱᆳ; 쮨; á„ᅱᆳ; ) HANGUL SYLLABLE JJWILS
+CBA9;CBA9;110D 1171 11B4;CBA9;110D 1171 11B4; # (쮩; 쮩; á„ᅱᆴ; 쮩; á„ᅱᆴ; ) HANGUL SYLLABLE JJWILT
+CBAA;CBAA;110D 1171 11B5;CBAA;110D 1171 11B5; # (쮪; 쮪; á„ᅱᆵ; 쮪; á„ᅱᆵ; ) HANGUL SYLLABLE JJWILP
+CBAB;CBAB;110D 1171 11B6;CBAB;110D 1171 11B6; # (쮫; 쮫; á„ᅱᆶ; 쮫; á„ᅱᆶ; ) HANGUL SYLLABLE JJWILH
+CBAC;CBAC;110D 1171 11B7;CBAC;110D 1171 11B7; # (쮬; 쮬; á„ᅱᆷ; 쮬; á„ᅱᆷ; ) HANGUL SYLLABLE JJWIM
+CBAD;CBAD;110D 1171 11B8;CBAD;110D 1171 11B8; # (ì®­; ì®­; á„ᅱᆸ; ì®­; á„ᅱᆸ; ) HANGUL SYLLABLE JJWIB
+CBAE;CBAE;110D 1171 11B9;CBAE;110D 1171 11B9; # (ì®®; ì®®; á„ᅱᆹ; ì®®; á„ᅱᆹ; ) HANGUL SYLLABLE JJWIBS
+CBAF;CBAF;110D 1171 11BA;CBAF;110D 1171 11BA; # (쮯; 쮯; á„ᅱᆺ; 쮯; á„ᅱᆺ; ) HANGUL SYLLABLE JJWIS
+CBB0;CBB0;110D 1171 11BB;CBB0;110D 1171 11BB; # (ì®°; ì®°; á„ᅱᆻ; ì®°; á„ᅱᆻ; ) HANGUL SYLLABLE JJWISS
+CBB1;CBB1;110D 1171 11BC;CBB1;110D 1171 11BC; # (ì®±; ì®±; á„ᅱᆼ; ì®±; á„ᅱᆼ; ) HANGUL SYLLABLE JJWING
+CBB2;CBB2;110D 1171 11BD;CBB2;110D 1171 11BD; # (쮲; 쮲; á„ᅱᆽ; 쮲; á„ᅱᆽ; ) HANGUL SYLLABLE JJWIJ
+CBB3;CBB3;110D 1171 11BE;CBB3;110D 1171 11BE; # (쮳; 쮳; á„ᅱᆾ; 쮳; á„ᅱᆾ; ) HANGUL SYLLABLE JJWIC
+CBB4;CBB4;110D 1171 11BF;CBB4;110D 1171 11BF; # (ì®´; ì®´; á„ᅱᆿ; ì®´; á„ᅱᆿ; ) HANGUL SYLLABLE JJWIK
+CBB5;CBB5;110D 1171 11C0;CBB5;110D 1171 11C0; # (쮵; 쮵; á„ᅱᇀ; 쮵; á„ᅱᇀ; ) HANGUL SYLLABLE JJWIT
+CBB6;CBB6;110D 1171 11C1;CBB6;110D 1171 11C1; # (쮶; 쮶; á„á…±á‡; 쮶; á„á…±á‡; ) HANGUL SYLLABLE JJWIP
+CBB7;CBB7;110D 1171 11C2;CBB7;110D 1171 11C2; # (ì®·; ì®·; á„ᅱᇂ; ì®·; á„ᅱᇂ; ) HANGUL SYLLABLE JJWIH
+CBB8;CBB8;110D 1172;CBB8;110D 1172; # (쮸; 쮸; á„á…²; 쮸; á„á…²; ) HANGUL SYLLABLE JJYU
+CBB9;CBB9;110D 1172 11A8;CBB9;110D 1172 11A8; # (쮹; 쮹; á„ᅲᆨ; 쮹; á„ᅲᆨ; ) HANGUL SYLLABLE JJYUG
+CBBA;CBBA;110D 1172 11A9;CBBA;110D 1172 11A9; # (쮺; 쮺; á„ᅲᆩ; 쮺; á„ᅲᆩ; ) HANGUL SYLLABLE JJYUGG
+CBBB;CBBB;110D 1172 11AA;CBBB;110D 1172 11AA; # (ì®»; ì®»; á„ᅲᆪ; ì®»; á„ᅲᆪ; ) HANGUL SYLLABLE JJYUGS
+CBBC;CBBC;110D 1172 11AB;CBBC;110D 1172 11AB; # (쮼; 쮼; á„ᅲᆫ; 쮼; á„ᅲᆫ; ) HANGUL SYLLABLE JJYUN
+CBBD;CBBD;110D 1172 11AC;CBBD;110D 1172 11AC; # (쮽; 쮽; á„ᅲᆬ; 쮽; á„ᅲᆬ; ) HANGUL SYLLABLE JJYUNJ
+CBBE;CBBE;110D 1172 11AD;CBBE;110D 1172 11AD; # (쮾; 쮾; á„ᅲᆭ; 쮾; á„ᅲᆭ; ) HANGUL SYLLABLE JJYUNH
+CBBF;CBBF;110D 1172 11AE;CBBF;110D 1172 11AE; # (쮿; 쮿; á„ᅲᆮ; 쮿; á„ᅲᆮ; ) HANGUL SYLLABLE JJYUD
+CBC0;CBC0;110D 1172 11AF;CBC0;110D 1172 11AF; # (쯀; 쯀; á„ᅲᆯ; 쯀; á„ᅲᆯ; ) HANGUL SYLLABLE JJYUL
+CBC1;CBC1;110D 1172 11B0;CBC1;110D 1172 11B0; # (ì¯; ì¯; á„ᅲᆰ; ì¯; á„ᅲᆰ; ) HANGUL SYLLABLE JJYULG
+CBC2;CBC2;110D 1172 11B1;CBC2;110D 1172 11B1; # (쯂; 쯂; á„ᅲᆱ; 쯂; á„ᅲᆱ; ) HANGUL SYLLABLE JJYULM
+CBC3;CBC3;110D 1172 11B2;CBC3;110D 1172 11B2; # (쯃; 쯃; á„ᅲᆲ; 쯃; á„ᅲᆲ; ) HANGUL SYLLABLE JJYULB
+CBC4;CBC4;110D 1172 11B3;CBC4;110D 1172 11B3; # (쯄; 쯄; á„ᅲᆳ; 쯄; á„ᅲᆳ; ) HANGUL SYLLABLE JJYULS
+CBC5;CBC5;110D 1172 11B4;CBC5;110D 1172 11B4; # (쯅; 쯅; á„ᅲᆴ; 쯅; á„ᅲᆴ; ) HANGUL SYLLABLE JJYULT
+CBC6;CBC6;110D 1172 11B5;CBC6;110D 1172 11B5; # (쯆; 쯆; á„ᅲᆵ; 쯆; á„ᅲᆵ; ) HANGUL SYLLABLE JJYULP
+CBC7;CBC7;110D 1172 11B6;CBC7;110D 1172 11B6; # (쯇; 쯇; á„ᅲᆶ; 쯇; á„ᅲᆶ; ) HANGUL SYLLABLE JJYULH
+CBC8;CBC8;110D 1172 11B7;CBC8;110D 1172 11B7; # (쯈; 쯈; á„ᅲᆷ; 쯈; á„ᅲᆷ; ) HANGUL SYLLABLE JJYUM
+CBC9;CBC9;110D 1172 11B8;CBC9;110D 1172 11B8; # (쯉; 쯉; á„ᅲᆸ; 쯉; á„ᅲᆸ; ) HANGUL SYLLABLE JJYUB
+CBCA;CBCA;110D 1172 11B9;CBCA;110D 1172 11B9; # (쯊; 쯊; á„ᅲᆹ; 쯊; á„ᅲᆹ; ) HANGUL SYLLABLE JJYUBS
+CBCB;CBCB;110D 1172 11BA;CBCB;110D 1172 11BA; # (쯋; 쯋; á„ᅲᆺ; 쯋; á„ᅲᆺ; ) HANGUL SYLLABLE JJYUS
+CBCC;CBCC;110D 1172 11BB;CBCC;110D 1172 11BB; # (쯌; 쯌; á„ᅲᆻ; 쯌; á„ᅲᆻ; ) HANGUL SYLLABLE JJYUSS
+CBCD;CBCD;110D 1172 11BC;CBCD;110D 1172 11BC; # (ì¯; ì¯; á„ᅲᆼ; ì¯; á„ᅲᆼ; ) HANGUL SYLLABLE JJYUNG
+CBCE;CBCE;110D 1172 11BD;CBCE;110D 1172 11BD; # (쯎; 쯎; á„ᅲᆽ; 쯎; á„ᅲᆽ; ) HANGUL SYLLABLE JJYUJ
+CBCF;CBCF;110D 1172 11BE;CBCF;110D 1172 11BE; # (ì¯; ì¯; á„ᅲᆾ; ì¯; á„ᅲᆾ; ) HANGUL SYLLABLE JJYUC
+CBD0;CBD0;110D 1172 11BF;CBD0;110D 1172 11BF; # (ì¯; ì¯; á„ᅲᆿ; ì¯; á„ᅲᆿ; ) HANGUL SYLLABLE JJYUK
+CBD1;CBD1;110D 1172 11C0;CBD1;110D 1172 11C0; # (쯑; 쯑; á„ᅲᇀ; 쯑; á„ᅲᇀ; ) HANGUL SYLLABLE JJYUT
+CBD2;CBD2;110D 1172 11C1;CBD2;110D 1172 11C1; # (쯒; 쯒; á„á…²á‡; 쯒; á„á…²á‡; ) HANGUL SYLLABLE JJYUP
+CBD3;CBD3;110D 1172 11C2;CBD3;110D 1172 11C2; # (쯓; 쯓; á„ᅲᇂ; 쯓; á„ᅲᇂ; ) HANGUL SYLLABLE JJYUH
+CBD4;CBD4;110D 1173;CBD4;110D 1173; # (쯔; 쯔; á„á…³; 쯔; á„á…³; ) HANGUL SYLLABLE JJEU
+CBD5;CBD5;110D 1173 11A8;CBD5;110D 1173 11A8; # (쯕; 쯕; á„ᅳᆨ; 쯕; á„ᅳᆨ; ) HANGUL SYLLABLE JJEUG
+CBD6;CBD6;110D 1173 11A9;CBD6;110D 1173 11A9; # (쯖; 쯖; á„ᅳᆩ; 쯖; á„ᅳᆩ; ) HANGUL SYLLABLE JJEUGG
+CBD7;CBD7;110D 1173 11AA;CBD7;110D 1173 11AA; # (쯗; 쯗; á„ᅳᆪ; 쯗; á„ᅳᆪ; ) HANGUL SYLLABLE JJEUGS
+CBD8;CBD8;110D 1173 11AB;CBD8;110D 1173 11AB; # (쯘; 쯘; á„ᅳᆫ; 쯘; á„ᅳᆫ; ) HANGUL SYLLABLE JJEUN
+CBD9;CBD9;110D 1173 11AC;CBD9;110D 1173 11AC; # (쯙; 쯙; á„ᅳᆬ; 쯙; á„ᅳᆬ; ) HANGUL SYLLABLE JJEUNJ
+CBDA;CBDA;110D 1173 11AD;CBDA;110D 1173 11AD; # (쯚; 쯚; á„ᅳᆭ; 쯚; á„ᅳᆭ; ) HANGUL SYLLABLE JJEUNH
+CBDB;CBDB;110D 1173 11AE;CBDB;110D 1173 11AE; # (쯛; 쯛; á„ᅳᆮ; 쯛; á„ᅳᆮ; ) HANGUL SYLLABLE JJEUD
+CBDC;CBDC;110D 1173 11AF;CBDC;110D 1173 11AF; # (쯜; 쯜; á„ᅳᆯ; 쯜; á„ᅳᆯ; ) HANGUL SYLLABLE JJEUL
+CBDD;CBDD;110D 1173 11B0;CBDD;110D 1173 11B0; # (ì¯; ì¯; á„ᅳᆰ; ì¯; á„ᅳᆰ; ) HANGUL SYLLABLE JJEULG
+CBDE;CBDE;110D 1173 11B1;CBDE;110D 1173 11B1; # (쯞; 쯞; á„ᅳᆱ; 쯞; á„ᅳᆱ; ) HANGUL SYLLABLE JJEULM
+CBDF;CBDF;110D 1173 11B2;CBDF;110D 1173 11B2; # (쯟; 쯟; á„ᅳᆲ; 쯟; á„ᅳᆲ; ) HANGUL SYLLABLE JJEULB
+CBE0;CBE0;110D 1173 11B3;CBE0;110D 1173 11B3; # (쯠; 쯠; á„ᅳᆳ; 쯠; á„ᅳᆳ; ) HANGUL SYLLABLE JJEULS
+CBE1;CBE1;110D 1173 11B4;CBE1;110D 1173 11B4; # (쯡; 쯡; á„ᅳᆴ; 쯡; á„ᅳᆴ; ) HANGUL SYLLABLE JJEULT
+CBE2;CBE2;110D 1173 11B5;CBE2;110D 1173 11B5; # (쯢; 쯢; á„ᅳᆵ; 쯢; á„ᅳᆵ; ) HANGUL SYLLABLE JJEULP
+CBE3;CBE3;110D 1173 11B6;CBE3;110D 1173 11B6; # (쯣; 쯣; á„ᅳᆶ; 쯣; á„ᅳᆶ; ) HANGUL SYLLABLE JJEULH
+CBE4;CBE4;110D 1173 11B7;CBE4;110D 1173 11B7; # (쯤; 쯤; á„ᅳᆷ; 쯤; á„ᅳᆷ; ) HANGUL SYLLABLE JJEUM
+CBE5;CBE5;110D 1173 11B8;CBE5;110D 1173 11B8; # (쯥; 쯥; á„ᅳᆸ; 쯥; á„ᅳᆸ; ) HANGUL SYLLABLE JJEUB
+CBE6;CBE6;110D 1173 11B9;CBE6;110D 1173 11B9; # (쯦; 쯦; á„ᅳᆹ; 쯦; á„ᅳᆹ; ) HANGUL SYLLABLE JJEUBS
+CBE7;CBE7;110D 1173 11BA;CBE7;110D 1173 11BA; # (쯧; 쯧; á„ᅳᆺ; 쯧; á„ᅳᆺ; ) HANGUL SYLLABLE JJEUS
+CBE8;CBE8;110D 1173 11BB;CBE8;110D 1173 11BB; # (쯨; 쯨; á„ᅳᆻ; 쯨; á„ᅳᆻ; ) HANGUL SYLLABLE JJEUSS
+CBE9;CBE9;110D 1173 11BC;CBE9;110D 1173 11BC; # (쯩; 쯩; á„ᅳᆼ; 쯩; á„ᅳᆼ; ) HANGUL SYLLABLE JJEUNG
+CBEA;CBEA;110D 1173 11BD;CBEA;110D 1173 11BD; # (쯪; 쯪; á„ᅳᆽ; 쯪; á„ᅳᆽ; ) HANGUL SYLLABLE JJEUJ
+CBEB;CBEB;110D 1173 11BE;CBEB;110D 1173 11BE; # (쯫; 쯫; á„ᅳᆾ; 쯫; á„ᅳᆾ; ) HANGUL SYLLABLE JJEUC
+CBEC;CBEC;110D 1173 11BF;CBEC;110D 1173 11BF; # (쯬; 쯬; á„ᅳᆿ; 쯬; á„ᅳᆿ; ) HANGUL SYLLABLE JJEUK
+CBED;CBED;110D 1173 11C0;CBED;110D 1173 11C0; # (쯭; 쯭; á„ᅳᇀ; 쯭; á„ᅳᇀ; ) HANGUL SYLLABLE JJEUT
+CBEE;CBEE;110D 1173 11C1;CBEE;110D 1173 11C1; # (쯮; 쯮; á„á…³á‡; 쯮; á„á…³á‡; ) HANGUL SYLLABLE JJEUP
+CBEF;CBEF;110D 1173 11C2;CBEF;110D 1173 11C2; # (쯯; 쯯; á„ᅳᇂ; 쯯; á„ᅳᇂ; ) HANGUL SYLLABLE JJEUH
+CBF0;CBF0;110D 1174;CBF0;110D 1174; # (쯰; 쯰; á„á…´; 쯰; á„á…´; ) HANGUL SYLLABLE JJYI
+CBF1;CBF1;110D 1174 11A8;CBF1;110D 1174 11A8; # (쯱; 쯱; á„ᅴᆨ; 쯱; á„ᅴᆨ; ) HANGUL SYLLABLE JJYIG
+CBF2;CBF2;110D 1174 11A9;CBF2;110D 1174 11A9; # (쯲; 쯲; á„ᅴᆩ; 쯲; á„ᅴᆩ; ) HANGUL SYLLABLE JJYIGG
+CBF3;CBF3;110D 1174 11AA;CBF3;110D 1174 11AA; # (쯳; 쯳; á„ᅴᆪ; 쯳; á„ᅴᆪ; ) HANGUL SYLLABLE JJYIGS
+CBF4;CBF4;110D 1174 11AB;CBF4;110D 1174 11AB; # (쯴; 쯴; á„ᅴᆫ; 쯴; á„ᅴᆫ; ) HANGUL SYLLABLE JJYIN
+CBF5;CBF5;110D 1174 11AC;CBF5;110D 1174 11AC; # (쯵; 쯵; á„ᅴᆬ; 쯵; á„ᅴᆬ; ) HANGUL SYLLABLE JJYINJ
+CBF6;CBF6;110D 1174 11AD;CBF6;110D 1174 11AD; # (쯶; 쯶; á„ᅴᆭ; 쯶; á„ᅴᆭ; ) HANGUL SYLLABLE JJYINH
+CBF7;CBF7;110D 1174 11AE;CBF7;110D 1174 11AE; # (쯷; 쯷; á„ᅴᆮ; 쯷; á„ᅴᆮ; ) HANGUL SYLLABLE JJYID
+CBF8;CBF8;110D 1174 11AF;CBF8;110D 1174 11AF; # (쯸; 쯸; á„ᅴᆯ; 쯸; á„ᅴᆯ; ) HANGUL SYLLABLE JJYIL
+CBF9;CBF9;110D 1174 11B0;CBF9;110D 1174 11B0; # (쯹; 쯹; á„ᅴᆰ; 쯹; á„ᅴᆰ; ) HANGUL SYLLABLE JJYILG
+CBFA;CBFA;110D 1174 11B1;CBFA;110D 1174 11B1; # (쯺; 쯺; á„ᅴᆱ; 쯺; á„ᅴᆱ; ) HANGUL SYLLABLE JJYILM
+CBFB;CBFB;110D 1174 11B2;CBFB;110D 1174 11B2; # (쯻; 쯻; á„ᅴᆲ; 쯻; á„ᅴᆲ; ) HANGUL SYLLABLE JJYILB
+CBFC;CBFC;110D 1174 11B3;CBFC;110D 1174 11B3; # (쯼; 쯼; á„ᅴᆳ; 쯼; á„ᅴᆳ; ) HANGUL SYLLABLE JJYILS
+CBFD;CBFD;110D 1174 11B4;CBFD;110D 1174 11B4; # (쯽; 쯽; á„ᅴᆴ; 쯽; á„ᅴᆴ; ) HANGUL SYLLABLE JJYILT
+CBFE;CBFE;110D 1174 11B5;CBFE;110D 1174 11B5; # (쯾; 쯾; á„ᅴᆵ; 쯾; á„ᅴᆵ; ) HANGUL SYLLABLE JJYILP
+CBFF;CBFF;110D 1174 11B6;CBFF;110D 1174 11B6; # (쯿; 쯿; á„ᅴᆶ; 쯿; á„ᅴᆶ; ) HANGUL SYLLABLE JJYILH
+CC00;CC00;110D 1174 11B7;CC00;110D 1174 11B7; # (ì°€; ì°€; á„ᅴᆷ; ì°€; á„ᅴᆷ; ) HANGUL SYLLABLE JJYIM
+CC01;CC01;110D 1174 11B8;CC01;110D 1174 11B8; # (ì°; ì°; á„ᅴᆸ; ì°; á„ᅴᆸ; ) HANGUL SYLLABLE JJYIB
+CC02;CC02;110D 1174 11B9;CC02;110D 1174 11B9; # (ì°‚; ì°‚; á„ᅴᆹ; ì°‚; á„ᅴᆹ; ) HANGUL SYLLABLE JJYIBS
+CC03;CC03;110D 1174 11BA;CC03;110D 1174 11BA; # (ì°ƒ; ì°ƒ; á„ᅴᆺ; ì°ƒ; á„ᅴᆺ; ) HANGUL SYLLABLE JJYIS
+CC04;CC04;110D 1174 11BB;CC04;110D 1174 11BB; # (ì°„; ì°„; á„ᅴᆻ; ì°„; á„ᅴᆻ; ) HANGUL SYLLABLE JJYISS
+CC05;CC05;110D 1174 11BC;CC05;110D 1174 11BC; # (ì°…; ì°…; á„ᅴᆼ; ì°…; á„ᅴᆼ; ) HANGUL SYLLABLE JJYING
+CC06;CC06;110D 1174 11BD;CC06;110D 1174 11BD; # (ì°†; ì°†; á„ᅴᆽ; ì°†; á„ᅴᆽ; ) HANGUL SYLLABLE JJYIJ
+CC07;CC07;110D 1174 11BE;CC07;110D 1174 11BE; # (ì°‡; ì°‡; á„ᅴᆾ; ì°‡; á„ᅴᆾ; ) HANGUL SYLLABLE JJYIC
+CC08;CC08;110D 1174 11BF;CC08;110D 1174 11BF; # (ì°ˆ; ì°ˆ; á„ᅴᆿ; ì°ˆ; á„ᅴᆿ; ) HANGUL SYLLABLE JJYIK
+CC09;CC09;110D 1174 11C0;CC09;110D 1174 11C0; # (ì°‰; ì°‰; á„ᅴᇀ; ì°‰; á„ᅴᇀ; ) HANGUL SYLLABLE JJYIT
+CC0A;CC0A;110D 1174 11C1;CC0A;110D 1174 11C1; # (ì°Š; ì°Š; á„á…´á‡; ì°Š; á„á…´á‡; ) HANGUL SYLLABLE JJYIP
+CC0B;CC0B;110D 1174 11C2;CC0B;110D 1174 11C2; # (ì°‹; ì°‹; á„ᅴᇂ; ì°‹; á„ᅴᇂ; ) HANGUL SYLLABLE JJYIH
+CC0C;CC0C;110D 1175;CC0C;110D 1175; # (ì°Œ; ì°Œ; á„á…µ; ì°Œ; á„á…µ; ) HANGUL SYLLABLE JJI
+CC0D;CC0D;110D 1175 11A8;CC0D;110D 1175 11A8; # (ì°; ì°; á„ᅵᆨ; ì°; á„ᅵᆨ; ) HANGUL SYLLABLE JJIG
+CC0E;CC0E;110D 1175 11A9;CC0E;110D 1175 11A9; # (ì°Ž; ì°Ž; á„ᅵᆩ; ì°Ž; á„ᅵᆩ; ) HANGUL SYLLABLE JJIGG
+CC0F;CC0F;110D 1175 11AA;CC0F;110D 1175 11AA; # (ì°; ì°; á„ᅵᆪ; ì°; á„ᅵᆪ; ) HANGUL SYLLABLE JJIGS
+CC10;CC10;110D 1175 11AB;CC10;110D 1175 11AB; # (ì°; ì°; á„ᅵᆫ; ì°; á„ᅵᆫ; ) HANGUL SYLLABLE JJIN
+CC11;CC11;110D 1175 11AC;CC11;110D 1175 11AC; # (ì°‘; ì°‘; á„ᅵᆬ; ì°‘; á„ᅵᆬ; ) HANGUL SYLLABLE JJINJ
+CC12;CC12;110D 1175 11AD;CC12;110D 1175 11AD; # (ì°’; ì°’; á„ᅵᆭ; ì°’; á„ᅵᆭ; ) HANGUL SYLLABLE JJINH
+CC13;CC13;110D 1175 11AE;CC13;110D 1175 11AE; # (ì°“; ì°“; á„ᅵᆮ; ì°“; á„ᅵᆮ; ) HANGUL SYLLABLE JJID
+CC14;CC14;110D 1175 11AF;CC14;110D 1175 11AF; # (ì°”; ì°”; á„ᅵᆯ; ì°”; á„ᅵᆯ; ) HANGUL SYLLABLE JJIL
+CC15;CC15;110D 1175 11B0;CC15;110D 1175 11B0; # (ì°•; ì°•; á„ᅵᆰ; ì°•; á„ᅵᆰ; ) HANGUL SYLLABLE JJILG
+CC16;CC16;110D 1175 11B1;CC16;110D 1175 11B1; # (ì°–; ì°–; á„ᅵᆱ; ì°–; á„ᅵᆱ; ) HANGUL SYLLABLE JJILM
+CC17;CC17;110D 1175 11B2;CC17;110D 1175 11B2; # (ì°—; ì°—; á„ᅵᆲ; ì°—; á„ᅵᆲ; ) HANGUL SYLLABLE JJILB
+CC18;CC18;110D 1175 11B3;CC18;110D 1175 11B3; # (ì°˜; ì°˜; á„ᅵᆳ; ì°˜; á„ᅵᆳ; ) HANGUL SYLLABLE JJILS
+CC19;CC19;110D 1175 11B4;CC19;110D 1175 11B4; # (ì°™; ì°™; á„ᅵᆴ; ì°™; á„ᅵᆴ; ) HANGUL SYLLABLE JJILT
+CC1A;CC1A;110D 1175 11B5;CC1A;110D 1175 11B5; # (ì°š; ì°š; á„ᅵᆵ; ì°š; á„ᅵᆵ; ) HANGUL SYLLABLE JJILP
+CC1B;CC1B;110D 1175 11B6;CC1B;110D 1175 11B6; # (ì°›; ì°›; á„ᅵᆶ; ì°›; á„ᅵᆶ; ) HANGUL SYLLABLE JJILH
+CC1C;CC1C;110D 1175 11B7;CC1C;110D 1175 11B7; # (ì°œ; ì°œ; á„ᅵᆷ; ì°œ; á„ᅵᆷ; ) HANGUL SYLLABLE JJIM
+CC1D;CC1D;110D 1175 11B8;CC1D;110D 1175 11B8; # (ì°; ì°; á„ᅵᆸ; ì°; á„ᅵᆸ; ) HANGUL SYLLABLE JJIB
+CC1E;CC1E;110D 1175 11B9;CC1E;110D 1175 11B9; # (ì°ž; ì°ž; á„ᅵᆹ; ì°ž; á„ᅵᆹ; ) HANGUL SYLLABLE JJIBS
+CC1F;CC1F;110D 1175 11BA;CC1F;110D 1175 11BA; # (ì°Ÿ; ì°Ÿ; á„ᅵᆺ; ì°Ÿ; á„ᅵᆺ; ) HANGUL SYLLABLE JJIS
+CC20;CC20;110D 1175 11BB;CC20;110D 1175 11BB; # (ì° ; ì° ; á„ᅵᆻ; ì° ; á„ᅵᆻ; ) HANGUL SYLLABLE JJISS
+CC21;CC21;110D 1175 11BC;CC21;110D 1175 11BC; # (ì°¡; ì°¡; á„ᅵᆼ; ì°¡; á„ᅵᆼ; ) HANGUL SYLLABLE JJING
+CC22;CC22;110D 1175 11BD;CC22;110D 1175 11BD; # (ì°¢; ì°¢; á„ᅵᆽ; ì°¢; á„ᅵᆽ; ) HANGUL SYLLABLE JJIJ
+CC23;CC23;110D 1175 11BE;CC23;110D 1175 11BE; # (ì°£; ì°£; á„ᅵᆾ; ì°£; á„ᅵᆾ; ) HANGUL SYLLABLE JJIC
+CC24;CC24;110D 1175 11BF;CC24;110D 1175 11BF; # (ì°¤; ì°¤; á„ᅵᆿ; ì°¤; á„ᅵᆿ; ) HANGUL SYLLABLE JJIK
+CC25;CC25;110D 1175 11C0;CC25;110D 1175 11C0; # (ì°¥; ì°¥; á„ᅵᇀ; ì°¥; á„ᅵᇀ; ) HANGUL SYLLABLE JJIT
+CC26;CC26;110D 1175 11C1;CC26;110D 1175 11C1; # (ì°¦; ì°¦; á„á…µá‡; ì°¦; á„á…µá‡; ) HANGUL SYLLABLE JJIP
+CC27;CC27;110D 1175 11C2;CC27;110D 1175 11C2; # (ì°§; ì°§; á„ᅵᇂ; ì°§; á„ᅵᇂ; ) HANGUL SYLLABLE JJIH
+CC28;CC28;110E 1161;CC28;110E 1161; # (ì°¨; ì°¨; á„Žá…¡; ì°¨; á„Žá…¡; ) HANGUL SYLLABLE CA
+CC29;CC29;110E 1161 11A8;CC29;110E 1161 11A8; # (착; 착; 착; 착; 착; ) HANGUL SYLLABLE CAG
+CC2A;CC2A;110E 1161 11A9;CC2A;110E 1161 11A9; # (찪; 찪; 찪; 찪; 찪; ) HANGUL SYLLABLE CAGG
+CC2B;CC2B;110E 1161 11AA;CC2B;110E 1161 11AA; # (찫; 찫; 찫; 찫; 찫; ) HANGUL SYLLABLE CAGS
+CC2C;CC2C;110E 1161 11AB;CC2C;110E 1161 11AB; # (찬; 찬; 찬; 찬; 찬; ) HANGUL SYLLABLE CAN
+CC2D;CC2D;110E 1161 11AC;CC2D;110E 1161 11AC; # (찭; 찭; 찭; 찭; 찭; ) HANGUL SYLLABLE CANJ
+CC2E;CC2E;110E 1161 11AD;CC2E;110E 1161 11AD; # (찮; 찮; 찮; 찮; 찮; ) HANGUL SYLLABLE CANH
+CC2F;CC2F;110E 1161 11AE;CC2F;110E 1161 11AE; # (찯; 찯; 찯; 찯; 찯; ) HANGUL SYLLABLE CAD
+CC30;CC30;110E 1161 11AF;CC30;110E 1161 11AF; # (찰; 찰; 찰; 찰; 찰; ) HANGUL SYLLABLE CAL
+CC31;CC31;110E 1161 11B0;CC31;110E 1161 11B0; # (찱; 찱; 찱; 찱; 찱; ) HANGUL SYLLABLE CALG
+CC32;CC32;110E 1161 11B1;CC32;110E 1161 11B1; # (찲; 찲; 찲; 찲; 찲; ) HANGUL SYLLABLE CALM
+CC33;CC33;110E 1161 11B2;CC33;110E 1161 11B2; # (찳; 찳; 찳; 찳; 찳; ) HANGUL SYLLABLE CALB
+CC34;CC34;110E 1161 11B3;CC34;110E 1161 11B3; # (찴; 찴; 찴; 찴; 찴; ) HANGUL SYLLABLE CALS
+CC35;CC35;110E 1161 11B4;CC35;110E 1161 11B4; # (찵; 찵; 찵; 찵; 찵; ) HANGUL SYLLABLE CALT
+CC36;CC36;110E 1161 11B5;CC36;110E 1161 11B5; # (찶; 찶; 찶; 찶; 찶; ) HANGUL SYLLABLE CALP
+CC37;CC37;110E 1161 11B6;CC37;110E 1161 11B6; # (찷; 찷; 찷; 찷; 찷; ) HANGUL SYLLABLE CALH
+CC38;CC38;110E 1161 11B7;CC38;110E 1161 11B7; # (참; 참; 참; 참; 참; ) HANGUL SYLLABLE CAM
+CC39;CC39;110E 1161 11B8;CC39;110E 1161 11B8; # (찹; 찹; 찹; 찹; 찹; ) HANGUL SYLLABLE CAB
+CC3A;CC3A;110E 1161 11B9;CC3A;110E 1161 11B9; # (찺; 찺; 찺; 찺; 찺; ) HANGUL SYLLABLE CABS
+CC3B;CC3B;110E 1161 11BA;CC3B;110E 1161 11BA; # (찻; 찻; 찻; 찻; 찻; ) HANGUL SYLLABLE CAS
+CC3C;CC3C;110E 1161 11BB;CC3C;110E 1161 11BB; # (찼; 찼; 찼; 찼; 찼; ) HANGUL SYLLABLE CASS
+CC3D;CC3D;110E 1161 11BC;CC3D;110E 1161 11BC; # (창; 창; 창; 창; 창; ) HANGUL SYLLABLE CANG
+CC3E;CC3E;110E 1161 11BD;CC3E;110E 1161 11BD; # (찾; 찾; 찾; 찾; 찾; ) HANGUL SYLLABLE CAJ
+CC3F;CC3F;110E 1161 11BE;CC3F;110E 1161 11BE; # (찿; 찿; 찿; 찿; 찿; ) HANGUL SYLLABLE CAC
+CC40;CC40;110E 1161 11BF;CC40;110E 1161 11BF; # (챀; 챀; 챀; 챀; 챀; ) HANGUL SYLLABLE CAK
+CC41;CC41;110E 1161 11C0;CC41;110E 1161 11C0; # (ì±; ì±; 챁; ì±; 챁; ) HANGUL SYLLABLE CAT
+CC42;CC42;110E 1161 11C1;CC42;110E 1161 11C1; # (챂; 챂; á„Žá…¡á‡; 챂; á„Žá…¡á‡; ) HANGUL SYLLABLE CAP
+CC43;CC43;110E 1161 11C2;CC43;110E 1161 11C2; # (챃; 챃; 챃; 챃; 챃; ) HANGUL SYLLABLE CAH
+CC44;CC44;110E 1162;CC44;110E 1162; # (채; 채; 채; 채; 채; ) HANGUL SYLLABLE CAE
+CC45;CC45;110E 1162 11A8;CC45;110E 1162 11A8; # (책; 책; 책; 책; 책; ) HANGUL SYLLABLE CAEG
+CC46;CC46;110E 1162 11A9;CC46;110E 1162 11A9; # (챆; 챆; 챆; 챆; 챆; ) HANGUL SYLLABLE CAEGG
+CC47;CC47;110E 1162 11AA;CC47;110E 1162 11AA; # (챇; 챇; 챇; 챇; 챇; ) HANGUL SYLLABLE CAEGS
+CC48;CC48;110E 1162 11AB;CC48;110E 1162 11AB; # (챈; 챈; 챈; 챈; 챈; ) HANGUL SYLLABLE CAEN
+CC49;CC49;110E 1162 11AC;CC49;110E 1162 11AC; # (챉; 챉; 챉; 챉; 챉; ) HANGUL SYLLABLE CAENJ
+CC4A;CC4A;110E 1162 11AD;CC4A;110E 1162 11AD; # (챊; 챊; 챊; 챊; 챊; ) HANGUL SYLLABLE CAENH
+CC4B;CC4B;110E 1162 11AE;CC4B;110E 1162 11AE; # (챋; 챋; 챋; 챋; 챋; ) HANGUL SYLLABLE CAED
+CC4C;CC4C;110E 1162 11AF;CC4C;110E 1162 11AF; # (챌; 챌; 챌; 챌; 챌; ) HANGUL SYLLABLE CAEL
+CC4D;CC4D;110E 1162 11B0;CC4D;110E 1162 11B0; # (ì±; ì±; 챍; ì±; 챍; ) HANGUL SYLLABLE CAELG
+CC4E;CC4E;110E 1162 11B1;CC4E;110E 1162 11B1; # (챎; 챎; 챎; 챎; 챎; ) HANGUL SYLLABLE CAELM
+CC4F;CC4F;110E 1162 11B2;CC4F;110E 1162 11B2; # (ì±; ì±; 챏; ì±; 챏; ) HANGUL SYLLABLE CAELB
+CC50;CC50;110E 1162 11B3;CC50;110E 1162 11B3; # (ì±; ì±; 챐; ì±; 챐; ) HANGUL SYLLABLE CAELS
+CC51;CC51;110E 1162 11B4;CC51;110E 1162 11B4; # (챑; 챑; 챑; 챑; 챑; ) HANGUL SYLLABLE CAELT
+CC52;CC52;110E 1162 11B5;CC52;110E 1162 11B5; # (챒; 챒; 챒; 챒; 챒; ) HANGUL SYLLABLE CAELP
+CC53;CC53;110E 1162 11B6;CC53;110E 1162 11B6; # (챓; 챓; 챓; 챓; 챓; ) HANGUL SYLLABLE CAELH
+CC54;CC54;110E 1162 11B7;CC54;110E 1162 11B7; # (챔; 챔; 챔; 챔; 챔; ) HANGUL SYLLABLE CAEM
+CC55;CC55;110E 1162 11B8;CC55;110E 1162 11B8; # (챕; 챕; 챕; 챕; 챕; ) HANGUL SYLLABLE CAEB
+CC56;CC56;110E 1162 11B9;CC56;110E 1162 11B9; # (챖; 챖; 챖; 챖; 챖; ) HANGUL SYLLABLE CAEBS
+CC57;CC57;110E 1162 11BA;CC57;110E 1162 11BA; # (챗; 챗; 챗; 챗; 챗; ) HANGUL SYLLABLE CAES
+CC58;CC58;110E 1162 11BB;CC58;110E 1162 11BB; # (챘; 챘; 챘; 챘; 챘; ) HANGUL SYLLABLE CAESS
+CC59;CC59;110E 1162 11BC;CC59;110E 1162 11BC; # (챙; 챙; 챙; 챙; 챙; ) HANGUL SYLLABLE CAENG
+CC5A;CC5A;110E 1162 11BD;CC5A;110E 1162 11BD; # (챚; 챚; 챚; 챚; 챚; ) HANGUL SYLLABLE CAEJ
+CC5B;CC5B;110E 1162 11BE;CC5B;110E 1162 11BE; # (챛; 챛; 챛; 챛; 챛; ) HANGUL SYLLABLE CAEC
+CC5C;CC5C;110E 1162 11BF;CC5C;110E 1162 11BF; # (챜; 챜; 챜; 챜; 챜; ) HANGUL SYLLABLE CAEK
+CC5D;CC5D;110E 1162 11C0;CC5D;110E 1162 11C0; # (ì±; ì±; 챝; ì±; 챝; ) HANGUL SYLLABLE CAET
+CC5E;CC5E;110E 1162 11C1;CC5E;110E 1162 11C1; # (챞; 챞; á„Žá…¢á‡; 챞; á„Žá…¢á‡; ) HANGUL SYLLABLE CAEP
+CC5F;CC5F;110E 1162 11C2;CC5F;110E 1162 11C2; # (챟; 챟; 챟; 챟; 챟; ) HANGUL SYLLABLE CAEH
+CC60;CC60;110E 1163;CC60;110E 1163; # (ì± ; ì± ; á„Žá…£; ì± ; á„Žá…£; ) HANGUL SYLLABLE CYA
+CC61;CC61;110E 1163 11A8;CC61;110E 1163 11A8; # (챡; 챡; 챡; 챡; 챡; ) HANGUL SYLLABLE CYAG
+CC62;CC62;110E 1163 11A9;CC62;110E 1163 11A9; # (챢; 챢; 챢; 챢; 챢; ) HANGUL SYLLABLE CYAGG
+CC63;CC63;110E 1163 11AA;CC63;110E 1163 11AA; # (챣; 챣; 챣; 챣; 챣; ) HANGUL SYLLABLE CYAGS
+CC64;CC64;110E 1163 11AB;CC64;110E 1163 11AB; # (챤; 챤; 챤; 챤; 챤; ) HANGUL SYLLABLE CYAN
+CC65;CC65;110E 1163 11AC;CC65;110E 1163 11AC; # (챥; 챥; 챥; 챥; 챥; ) HANGUL SYLLABLE CYANJ
+CC66;CC66;110E 1163 11AD;CC66;110E 1163 11AD; # (챦; 챦; 챦; 챦; 챦; ) HANGUL SYLLABLE CYANH
+CC67;CC67;110E 1163 11AE;CC67;110E 1163 11AE; # (챧; 챧; 챧; 챧; 챧; ) HANGUL SYLLABLE CYAD
+CC68;CC68;110E 1163 11AF;CC68;110E 1163 11AF; # (챨; 챨; 챨; 챨; 챨; ) HANGUL SYLLABLE CYAL
+CC69;CC69;110E 1163 11B0;CC69;110E 1163 11B0; # (챩; 챩; 챩; 챩; 챩; ) HANGUL SYLLABLE CYALG
+CC6A;CC6A;110E 1163 11B1;CC6A;110E 1163 11B1; # (챪; 챪; 챪; 챪; 챪; ) HANGUL SYLLABLE CYALM
+CC6B;CC6B;110E 1163 11B2;CC6B;110E 1163 11B2; # (챫; 챫; 챫; 챫; 챫; ) HANGUL SYLLABLE CYALB
+CC6C;CC6C;110E 1163 11B3;CC6C;110E 1163 11B3; # (챬; 챬; 챬; 챬; 챬; ) HANGUL SYLLABLE CYALS
+CC6D;CC6D;110E 1163 11B4;CC6D;110E 1163 11B4; # (챭; 챭; 챭; 챭; 챭; ) HANGUL SYLLABLE CYALT
+CC6E;CC6E;110E 1163 11B5;CC6E;110E 1163 11B5; # (챮; 챮; 챮; 챮; 챮; ) HANGUL SYLLABLE CYALP
+CC6F;CC6F;110E 1163 11B6;CC6F;110E 1163 11B6; # (챯; 챯; 챯; 챯; 챯; ) HANGUL SYLLABLE CYALH
+CC70;CC70;110E 1163 11B7;CC70;110E 1163 11B7; # (챰; 챰; 챰; 챰; 챰; ) HANGUL SYLLABLE CYAM
+CC71;CC71;110E 1163 11B8;CC71;110E 1163 11B8; # (챱; 챱; 챱; 챱; 챱; ) HANGUL SYLLABLE CYAB
+CC72;CC72;110E 1163 11B9;CC72;110E 1163 11B9; # (챲; 챲; 챲; 챲; 챲; ) HANGUL SYLLABLE CYABS
+CC73;CC73;110E 1163 11BA;CC73;110E 1163 11BA; # (챳; 챳; 챳; 챳; 챳; ) HANGUL SYLLABLE CYAS
+CC74;CC74;110E 1163 11BB;CC74;110E 1163 11BB; # (챴; 챴; 챴; 챴; 챴; ) HANGUL SYLLABLE CYASS
+CC75;CC75;110E 1163 11BC;CC75;110E 1163 11BC; # (챵; 챵; 챵; 챵; 챵; ) HANGUL SYLLABLE CYANG
+CC76;CC76;110E 1163 11BD;CC76;110E 1163 11BD; # (챶; 챶; 챶; 챶; 챶; ) HANGUL SYLLABLE CYAJ
+CC77;CC77;110E 1163 11BE;CC77;110E 1163 11BE; # (챷; 챷; 챷; 챷; 챷; ) HANGUL SYLLABLE CYAC
+CC78;CC78;110E 1163 11BF;CC78;110E 1163 11BF; # (챸; 챸; 챸; 챸; 챸; ) HANGUL SYLLABLE CYAK
+CC79;CC79;110E 1163 11C0;CC79;110E 1163 11C0; # (챹; 챹; 챹; 챹; 챹; ) HANGUL SYLLABLE CYAT
+CC7A;CC7A;110E 1163 11C1;CC7A;110E 1163 11C1; # (챺; 챺; á„Žá…£á‡; 챺; á„Žá…£á‡; ) HANGUL SYLLABLE CYAP
+CC7B;CC7B;110E 1163 11C2;CC7B;110E 1163 11C2; # (챻; 챻; 챻; 챻; 챻; ) HANGUL SYLLABLE CYAH
+CC7C;CC7C;110E 1164;CC7C;110E 1164; # (ì±¼; ì±¼; á„Žá…¤; ì±¼; á„Žá…¤; ) HANGUL SYLLABLE CYAE
+CC7D;CC7D;110E 1164 11A8;CC7D;110E 1164 11A8; # (챽; 챽; 챽; 챽; 챽; ) HANGUL SYLLABLE CYAEG
+CC7E;CC7E;110E 1164 11A9;CC7E;110E 1164 11A9; # (챾; 챾; 챾; 챾; 챾; ) HANGUL SYLLABLE CYAEGG
+CC7F;CC7F;110E 1164 11AA;CC7F;110E 1164 11AA; # (챿; 챿; 챿; 챿; 챿; ) HANGUL SYLLABLE CYAEGS
+CC80;CC80;110E 1164 11AB;CC80;110E 1164 11AB; # (첀; 첀; 첀; 첀; 첀; ) HANGUL SYLLABLE CYAEN
+CC81;CC81;110E 1164 11AC;CC81;110E 1164 11AC; # (ì²; ì²; 첁; ì²; 첁; ) HANGUL SYLLABLE CYAENJ
+CC82;CC82;110E 1164 11AD;CC82;110E 1164 11AD; # (첂; 첂; 첂; 첂; 첂; ) HANGUL SYLLABLE CYAENH
+CC83;CC83;110E 1164 11AE;CC83;110E 1164 11AE; # (첃; 첃; 첃; 첃; 첃; ) HANGUL SYLLABLE CYAED
+CC84;CC84;110E 1164 11AF;CC84;110E 1164 11AF; # (첄; 첄; 첄; 첄; 첄; ) HANGUL SYLLABLE CYAEL
+CC85;CC85;110E 1164 11B0;CC85;110E 1164 11B0; # (첅; 첅; 첅; 첅; 첅; ) HANGUL SYLLABLE CYAELG
+CC86;CC86;110E 1164 11B1;CC86;110E 1164 11B1; # (첆; 첆; 첆; 첆; 첆; ) HANGUL SYLLABLE CYAELM
+CC87;CC87;110E 1164 11B2;CC87;110E 1164 11B2; # (첇; 첇; 첇; 첇; 첇; ) HANGUL SYLLABLE CYAELB
+CC88;CC88;110E 1164 11B3;CC88;110E 1164 11B3; # (첈; 첈; 첈; 첈; 첈; ) HANGUL SYLLABLE CYAELS
+CC89;CC89;110E 1164 11B4;CC89;110E 1164 11B4; # (첉; 첉; 첉; 첉; 첉; ) HANGUL SYLLABLE CYAELT
+CC8A;CC8A;110E 1164 11B5;CC8A;110E 1164 11B5; # (첊; 첊; 첊; 첊; 첊; ) HANGUL SYLLABLE CYAELP
+CC8B;CC8B;110E 1164 11B6;CC8B;110E 1164 11B6; # (첋; 첋; 첋; 첋; 첋; ) HANGUL SYLLABLE CYAELH
+CC8C;CC8C;110E 1164 11B7;CC8C;110E 1164 11B7; # (첌; 첌; 첌; 첌; 첌; ) HANGUL SYLLABLE CYAEM
+CC8D;CC8D;110E 1164 11B8;CC8D;110E 1164 11B8; # (ì²; ì²; 첍; ì²; 첍; ) HANGUL SYLLABLE CYAEB
+CC8E;CC8E;110E 1164 11B9;CC8E;110E 1164 11B9; # (첎; 첎; 첎; 첎; 첎; ) HANGUL SYLLABLE CYAEBS
+CC8F;CC8F;110E 1164 11BA;CC8F;110E 1164 11BA; # (ì²; ì²; 첏; ì²; 첏; ) HANGUL SYLLABLE CYAES
+CC90;CC90;110E 1164 11BB;CC90;110E 1164 11BB; # (ì²; ì²; 첐; ì²; 첐; ) HANGUL SYLLABLE CYAESS
+CC91;CC91;110E 1164 11BC;CC91;110E 1164 11BC; # (첑; 첑; 첑; 첑; 첑; ) HANGUL SYLLABLE CYAENG
+CC92;CC92;110E 1164 11BD;CC92;110E 1164 11BD; # (첒; 첒; 첒; 첒; 첒; ) HANGUL SYLLABLE CYAEJ
+CC93;CC93;110E 1164 11BE;CC93;110E 1164 11BE; # (첓; 첓; 첓; 첓; 첓; ) HANGUL SYLLABLE CYAEC
+CC94;CC94;110E 1164 11BF;CC94;110E 1164 11BF; # (첔; 첔; 첔; 첔; 첔; ) HANGUL SYLLABLE CYAEK
+CC95;CC95;110E 1164 11C0;CC95;110E 1164 11C0; # (첕; 첕; 첕; 첕; 첕; ) HANGUL SYLLABLE CYAET
+CC96;CC96;110E 1164 11C1;CC96;110E 1164 11C1; # (ì²–; ì²–; á„Žá…¤á‡; ì²–; á„Žá…¤á‡; ) HANGUL SYLLABLE CYAEP
+CC97;CC97;110E 1164 11C2;CC97;110E 1164 11C2; # (첗; 첗; 첗; 첗; 첗; ) HANGUL SYLLABLE CYAEH
+CC98;CC98;110E 1165;CC98;110E 1165; # (처; 처; 처; 처; 처; ) HANGUL SYLLABLE CEO
+CC99;CC99;110E 1165 11A8;CC99;110E 1165 11A8; # (척; 척; 척; 척; 척; ) HANGUL SYLLABLE CEOG
+CC9A;CC9A;110E 1165 11A9;CC9A;110E 1165 11A9; # (첚; 첚; 첚; 첚; 첚; ) HANGUL SYLLABLE CEOGG
+CC9B;CC9B;110E 1165 11AA;CC9B;110E 1165 11AA; # (첛; 첛; 첛; 첛; 첛; ) HANGUL SYLLABLE CEOGS
+CC9C;CC9C;110E 1165 11AB;CC9C;110E 1165 11AB; # (천; 천; 천; 천; 천; ) HANGUL SYLLABLE CEON
+CC9D;CC9D;110E 1165 11AC;CC9D;110E 1165 11AC; # (ì²; ì²; 첝; ì²; 첝; ) HANGUL SYLLABLE CEONJ
+CC9E;CC9E;110E 1165 11AD;CC9E;110E 1165 11AD; # (첞; 첞; 첞; 첞; 첞; ) HANGUL SYLLABLE CEONH
+CC9F;CC9F;110E 1165 11AE;CC9F;110E 1165 11AE; # (첟; 첟; 첟; 첟; 첟; ) HANGUL SYLLABLE CEOD
+CCA0;CCA0;110E 1165 11AF;CCA0;110E 1165 11AF; # (철; 철; 철; 철; 철; ) HANGUL SYLLABLE CEOL
+CCA1;CCA1;110E 1165 11B0;CCA1;110E 1165 11B0; # (첡; 첡; 첡; 첡; 첡; ) HANGUL SYLLABLE CEOLG
+CCA2;CCA2;110E 1165 11B1;CCA2;110E 1165 11B1; # (첢; 첢; 첢; 첢; 첢; ) HANGUL SYLLABLE CEOLM
+CCA3;CCA3;110E 1165 11B2;CCA3;110E 1165 11B2; # (첣; 첣; 첣; 첣; 첣; ) HANGUL SYLLABLE CEOLB
+CCA4;CCA4;110E 1165 11B3;CCA4;110E 1165 11B3; # (첤; 첤; 첤; 첤; 첤; ) HANGUL SYLLABLE CEOLS
+CCA5;CCA5;110E 1165 11B4;CCA5;110E 1165 11B4; # (첥; 첥; 첥; 첥; 첥; ) HANGUL SYLLABLE CEOLT
+CCA6;CCA6;110E 1165 11B5;CCA6;110E 1165 11B5; # (첦; 첦; 첦; 첦; 첦; ) HANGUL SYLLABLE CEOLP
+CCA7;CCA7;110E 1165 11B6;CCA7;110E 1165 11B6; # (첧; 첧; 첧; 첧; 첧; ) HANGUL SYLLABLE CEOLH
+CCA8;CCA8;110E 1165 11B7;CCA8;110E 1165 11B7; # (첨; 첨; 첨; 첨; 첨; ) HANGUL SYLLABLE CEOM
+CCA9;CCA9;110E 1165 11B8;CCA9;110E 1165 11B8; # (첩; 첩; 첩; 첩; 첩; ) HANGUL SYLLABLE CEOB
+CCAA;CCAA;110E 1165 11B9;CCAA;110E 1165 11B9; # (첪; 첪; 첪; 첪; 첪; ) HANGUL SYLLABLE CEOBS
+CCAB;CCAB;110E 1165 11BA;CCAB;110E 1165 11BA; # (첫; 첫; 첫; 첫; 첫; ) HANGUL SYLLABLE CEOS
+CCAC;CCAC;110E 1165 11BB;CCAC;110E 1165 11BB; # (첬; 첬; 첬; 첬; 첬; ) HANGUL SYLLABLE CEOSS
+CCAD;CCAD;110E 1165 11BC;CCAD;110E 1165 11BC; # (청; 청; 청; 청; 청; ) HANGUL SYLLABLE CEONG
+CCAE;CCAE;110E 1165 11BD;CCAE;110E 1165 11BD; # (첮; 첮; 첮; 첮; 첮; ) HANGUL SYLLABLE CEOJ
+CCAF;CCAF;110E 1165 11BE;CCAF;110E 1165 11BE; # (첯; 첯; 첯; 첯; 첯; ) HANGUL SYLLABLE CEOC
+CCB0;CCB0;110E 1165 11BF;CCB0;110E 1165 11BF; # (첰; 첰; 첰; 첰; 첰; ) HANGUL SYLLABLE CEOK
+CCB1;CCB1;110E 1165 11C0;CCB1;110E 1165 11C0; # (첱; 첱; 첱; 첱; 첱; ) HANGUL SYLLABLE CEOT
+CCB2;CCB2;110E 1165 11C1;CCB2;110E 1165 11C1; # (ì²²; ì²²; á„Žá…¥á‡; ì²²; á„Žá…¥á‡; ) HANGUL SYLLABLE CEOP
+CCB3;CCB3;110E 1165 11C2;CCB3;110E 1165 11C2; # (첳; 첳; 첳; 첳; 첳; ) HANGUL SYLLABLE CEOH
+CCB4;CCB4;110E 1166;CCB4;110E 1166; # (ì²´; ì²´; á„Žá…¦; ì²´; á„Žá…¦; ) HANGUL SYLLABLE CE
+CCB5;CCB5;110E 1166 11A8;CCB5;110E 1166 11A8; # (첵; 첵; 첵; 첵; 첵; ) HANGUL SYLLABLE CEG
+CCB6;CCB6;110E 1166 11A9;CCB6;110E 1166 11A9; # (첶; 첶; 첶; 첶; 첶; ) HANGUL SYLLABLE CEGG
+CCB7;CCB7;110E 1166 11AA;CCB7;110E 1166 11AA; # (첷; 첷; 첷; 첷; 첷; ) HANGUL SYLLABLE CEGS
+CCB8;CCB8;110E 1166 11AB;CCB8;110E 1166 11AB; # (첸; 첸; 첸; 첸; 첸; ) HANGUL SYLLABLE CEN
+CCB9;CCB9;110E 1166 11AC;CCB9;110E 1166 11AC; # (첹; 첹; 첹; 첹; 첹; ) HANGUL SYLLABLE CENJ
+CCBA;CCBA;110E 1166 11AD;CCBA;110E 1166 11AD; # (첺; 첺; 첺; 첺; 첺; ) HANGUL SYLLABLE CENH
+CCBB;CCBB;110E 1166 11AE;CCBB;110E 1166 11AE; # (첻; 첻; 첻; 첻; 첻; ) HANGUL SYLLABLE CED
+CCBC;CCBC;110E 1166 11AF;CCBC;110E 1166 11AF; # (첼; 첼; 첼; 첼; 첼; ) HANGUL SYLLABLE CEL
+CCBD;CCBD;110E 1166 11B0;CCBD;110E 1166 11B0; # (첽; 첽; 첽; 첽; 첽; ) HANGUL SYLLABLE CELG
+CCBE;CCBE;110E 1166 11B1;CCBE;110E 1166 11B1; # (첾; 첾; 첾; 첾; 첾; ) HANGUL SYLLABLE CELM
+CCBF;CCBF;110E 1166 11B2;CCBF;110E 1166 11B2; # (첿; 첿; 첿; 첿; 첿; ) HANGUL SYLLABLE CELB
+CCC0;CCC0;110E 1166 11B3;CCC0;110E 1166 11B3; # (쳀; 쳀; 쳀; 쳀; 쳀; ) HANGUL SYLLABLE CELS
+CCC1;CCC1;110E 1166 11B4;CCC1;110E 1166 11B4; # (ì³; ì³; 쳁; ì³; 쳁; ) HANGUL SYLLABLE CELT
+CCC2;CCC2;110E 1166 11B5;CCC2;110E 1166 11B5; # (쳂; 쳂; 쳂; 쳂; 쳂; ) HANGUL SYLLABLE CELP
+CCC3;CCC3;110E 1166 11B6;CCC3;110E 1166 11B6; # (쳃; 쳃; 쳃; 쳃; 쳃; ) HANGUL SYLLABLE CELH
+CCC4;CCC4;110E 1166 11B7;CCC4;110E 1166 11B7; # (쳄; 쳄; 쳄; 쳄; 쳄; ) HANGUL SYLLABLE CEM
+CCC5;CCC5;110E 1166 11B8;CCC5;110E 1166 11B8; # (쳅; 쳅; 쳅; 쳅; 쳅; ) HANGUL SYLLABLE CEB
+CCC6;CCC6;110E 1166 11B9;CCC6;110E 1166 11B9; # (쳆; 쳆; 쳆; 쳆; 쳆; ) HANGUL SYLLABLE CEBS
+CCC7;CCC7;110E 1166 11BA;CCC7;110E 1166 11BA; # (쳇; 쳇; 쳇; 쳇; 쳇; ) HANGUL SYLLABLE CES
+CCC8;CCC8;110E 1166 11BB;CCC8;110E 1166 11BB; # (쳈; 쳈; 쳈; 쳈; 쳈; ) HANGUL SYLLABLE CESS
+CCC9;CCC9;110E 1166 11BC;CCC9;110E 1166 11BC; # (쳉; 쳉; 쳉; 쳉; 쳉; ) HANGUL SYLLABLE CENG
+CCCA;CCCA;110E 1166 11BD;CCCA;110E 1166 11BD; # (쳊; 쳊; 쳊; 쳊; 쳊; ) HANGUL SYLLABLE CEJ
+CCCB;CCCB;110E 1166 11BE;CCCB;110E 1166 11BE; # (쳋; 쳋; 쳋; 쳋; 쳋; ) HANGUL SYLLABLE CEC
+CCCC;CCCC;110E 1166 11BF;CCCC;110E 1166 11BF; # (쳌; 쳌; 쳌; 쳌; 쳌; ) HANGUL SYLLABLE CEK
+CCCD;CCCD;110E 1166 11C0;CCCD;110E 1166 11C0; # (ì³; ì³; 쳍; ì³; 쳍; ) HANGUL SYLLABLE CET
+CCCE;CCCE;110E 1166 11C1;CCCE;110E 1166 11C1; # (쳎; 쳎; á„Žá…¦á‡; 쳎; á„Žá…¦á‡; ) HANGUL SYLLABLE CEP
+CCCF;CCCF;110E 1166 11C2;CCCF;110E 1166 11C2; # (ì³; ì³; 쳏; ì³; 쳏; ) HANGUL SYLLABLE CEH
+CCD0;CCD0;110E 1167;CCD0;110E 1167; # (ì³; ì³; á„Žá…§; ì³; á„Žá…§; ) HANGUL SYLLABLE CYEO
+CCD1;CCD1;110E 1167 11A8;CCD1;110E 1167 11A8; # (쳑; 쳑; 쳑; 쳑; 쳑; ) HANGUL SYLLABLE CYEOG
+CCD2;CCD2;110E 1167 11A9;CCD2;110E 1167 11A9; # (쳒; 쳒; 쳒; 쳒; 쳒; ) HANGUL SYLLABLE CYEOGG
+CCD3;CCD3;110E 1167 11AA;CCD3;110E 1167 11AA; # (쳓; 쳓; 쳓; 쳓; 쳓; ) HANGUL SYLLABLE CYEOGS
+CCD4;CCD4;110E 1167 11AB;CCD4;110E 1167 11AB; # (쳔; 쳔; 쳔; 쳔; 쳔; ) HANGUL SYLLABLE CYEON
+CCD5;CCD5;110E 1167 11AC;CCD5;110E 1167 11AC; # (쳕; 쳕; 쳕; 쳕; 쳕; ) HANGUL SYLLABLE CYEONJ
+CCD6;CCD6;110E 1167 11AD;CCD6;110E 1167 11AD; # (쳖; 쳖; 쳖; 쳖; 쳖; ) HANGUL SYLLABLE CYEONH
+CCD7;CCD7;110E 1167 11AE;CCD7;110E 1167 11AE; # (쳗; 쳗; 쳗; 쳗; 쳗; ) HANGUL SYLLABLE CYEOD
+CCD8;CCD8;110E 1167 11AF;CCD8;110E 1167 11AF; # (쳘; 쳘; 쳘; 쳘; 쳘; ) HANGUL SYLLABLE CYEOL
+CCD9;CCD9;110E 1167 11B0;CCD9;110E 1167 11B0; # (쳙; 쳙; 쳙; 쳙; 쳙; ) HANGUL SYLLABLE CYEOLG
+CCDA;CCDA;110E 1167 11B1;CCDA;110E 1167 11B1; # (쳚; 쳚; 쳚; 쳚; 쳚; ) HANGUL SYLLABLE CYEOLM
+CCDB;CCDB;110E 1167 11B2;CCDB;110E 1167 11B2; # (쳛; 쳛; 쳛; 쳛; 쳛; ) HANGUL SYLLABLE CYEOLB
+CCDC;CCDC;110E 1167 11B3;CCDC;110E 1167 11B3; # (쳜; 쳜; 쳜; 쳜; 쳜; ) HANGUL SYLLABLE CYEOLS
+CCDD;CCDD;110E 1167 11B4;CCDD;110E 1167 11B4; # (ì³; ì³; 쳝; ì³; 쳝; ) HANGUL SYLLABLE CYEOLT
+CCDE;CCDE;110E 1167 11B5;CCDE;110E 1167 11B5; # (쳞; 쳞; 쳞; 쳞; 쳞; ) HANGUL SYLLABLE CYEOLP
+CCDF;CCDF;110E 1167 11B6;CCDF;110E 1167 11B6; # (쳟; 쳟; 쳟; 쳟; 쳟; ) HANGUL SYLLABLE CYEOLH
+CCE0;CCE0;110E 1167 11B7;CCE0;110E 1167 11B7; # (쳠; 쳠; 쳠; 쳠; 쳠; ) HANGUL SYLLABLE CYEOM
+CCE1;CCE1;110E 1167 11B8;CCE1;110E 1167 11B8; # (쳡; 쳡; 쳡; 쳡; 쳡; ) HANGUL SYLLABLE CYEOB
+CCE2;CCE2;110E 1167 11B9;CCE2;110E 1167 11B9; # (쳢; 쳢; 쳢; 쳢; 쳢; ) HANGUL SYLLABLE CYEOBS
+CCE3;CCE3;110E 1167 11BA;CCE3;110E 1167 11BA; # (쳣; 쳣; 쳣; 쳣; 쳣; ) HANGUL SYLLABLE CYEOS
+CCE4;CCE4;110E 1167 11BB;CCE4;110E 1167 11BB; # (쳤; 쳤; 쳤; 쳤; 쳤; ) HANGUL SYLLABLE CYEOSS
+CCE5;CCE5;110E 1167 11BC;CCE5;110E 1167 11BC; # (쳥; 쳥; 쳥; 쳥; 쳥; ) HANGUL SYLLABLE CYEONG
+CCE6;CCE6;110E 1167 11BD;CCE6;110E 1167 11BD; # (쳦; 쳦; 쳦; 쳦; 쳦; ) HANGUL SYLLABLE CYEOJ
+CCE7;CCE7;110E 1167 11BE;CCE7;110E 1167 11BE; # (쳧; 쳧; 쳧; 쳧; 쳧; ) HANGUL SYLLABLE CYEOC
+CCE8;CCE8;110E 1167 11BF;CCE8;110E 1167 11BF; # (쳨; 쳨; 쳨; 쳨; 쳨; ) HANGUL SYLLABLE CYEOK
+CCE9;CCE9;110E 1167 11C0;CCE9;110E 1167 11C0; # (쳩; 쳩; 쳩; 쳩; 쳩; ) HANGUL SYLLABLE CYEOT
+CCEA;CCEA;110E 1167 11C1;CCEA;110E 1167 11C1; # (쳪; 쳪; á„Žá…§á‡; 쳪; á„Žá…§á‡; ) HANGUL SYLLABLE CYEOP
+CCEB;CCEB;110E 1167 11C2;CCEB;110E 1167 11C2; # (쳫; 쳫; 쳫; 쳫; 쳫; ) HANGUL SYLLABLE CYEOH
+CCEC;CCEC;110E 1168;CCEC;110E 1168; # (쳬; 쳬; 쳬; 쳬; 쳬; ) HANGUL SYLLABLE CYE
+CCED;CCED;110E 1168 11A8;CCED;110E 1168 11A8; # (쳭; 쳭; 쳭; 쳭; 쳭; ) HANGUL SYLLABLE CYEG
+CCEE;CCEE;110E 1168 11A9;CCEE;110E 1168 11A9; # (쳮; 쳮; 쳮; 쳮; 쳮; ) HANGUL SYLLABLE CYEGG
+CCEF;CCEF;110E 1168 11AA;CCEF;110E 1168 11AA; # (쳯; 쳯; 쳯; 쳯; 쳯; ) HANGUL SYLLABLE CYEGS
+CCF0;CCF0;110E 1168 11AB;CCF0;110E 1168 11AB; # (쳰; 쳰; 쳰; 쳰; 쳰; ) HANGUL SYLLABLE CYEN
+CCF1;CCF1;110E 1168 11AC;CCF1;110E 1168 11AC; # (쳱; 쳱; 쳱; 쳱; 쳱; ) HANGUL SYLLABLE CYENJ
+CCF2;CCF2;110E 1168 11AD;CCF2;110E 1168 11AD; # (쳲; 쳲; 쳲; 쳲; 쳲; ) HANGUL SYLLABLE CYENH
+CCF3;CCF3;110E 1168 11AE;CCF3;110E 1168 11AE; # (쳳; 쳳; 쳳; 쳳; 쳳; ) HANGUL SYLLABLE CYED
+CCF4;CCF4;110E 1168 11AF;CCF4;110E 1168 11AF; # (쳴; 쳴; 쳴; 쳴; 쳴; ) HANGUL SYLLABLE CYEL
+CCF5;CCF5;110E 1168 11B0;CCF5;110E 1168 11B0; # (쳵; 쳵; 쳵; 쳵; 쳵; ) HANGUL SYLLABLE CYELG
+CCF6;CCF6;110E 1168 11B1;CCF6;110E 1168 11B1; # (쳶; 쳶; 쳶; 쳶; 쳶; ) HANGUL SYLLABLE CYELM
+CCF7;CCF7;110E 1168 11B2;CCF7;110E 1168 11B2; # (쳷; 쳷; 쳷; 쳷; 쳷; ) HANGUL SYLLABLE CYELB
+CCF8;CCF8;110E 1168 11B3;CCF8;110E 1168 11B3; # (쳸; 쳸; 쳸; 쳸; 쳸; ) HANGUL SYLLABLE CYELS
+CCF9;CCF9;110E 1168 11B4;CCF9;110E 1168 11B4; # (쳹; 쳹; 쳹; 쳹; 쳹; ) HANGUL SYLLABLE CYELT
+CCFA;CCFA;110E 1168 11B5;CCFA;110E 1168 11B5; # (쳺; 쳺; 쳺; 쳺; 쳺; ) HANGUL SYLLABLE CYELP
+CCFB;CCFB;110E 1168 11B6;CCFB;110E 1168 11B6; # (쳻; 쳻; 쳻; 쳻; 쳻; ) HANGUL SYLLABLE CYELH
+CCFC;CCFC;110E 1168 11B7;CCFC;110E 1168 11B7; # (쳼; 쳼; 쳼; 쳼; 쳼; ) HANGUL SYLLABLE CYEM
+CCFD;CCFD;110E 1168 11B8;CCFD;110E 1168 11B8; # (쳽; 쳽; 쳽; 쳽; 쳽; ) HANGUL SYLLABLE CYEB
+CCFE;CCFE;110E 1168 11B9;CCFE;110E 1168 11B9; # (쳾; 쳾; 쳾; 쳾; 쳾; ) HANGUL SYLLABLE CYEBS
+CCFF;CCFF;110E 1168 11BA;CCFF;110E 1168 11BA; # (쳿; 쳿; 쳿; 쳿; 쳿; ) HANGUL SYLLABLE CYES
+CD00;CD00;110E 1168 11BB;CD00;110E 1168 11BB; # (촀; 촀; 촀; 촀; 촀; ) HANGUL SYLLABLE CYESS
+CD01;CD01;110E 1168 11BC;CD01;110E 1168 11BC; # (ì´; ì´; 촁; ì´; 촁; ) HANGUL SYLLABLE CYENG
+CD02;CD02;110E 1168 11BD;CD02;110E 1168 11BD; # (촂; 촂; 촂; 촂; 촂; ) HANGUL SYLLABLE CYEJ
+CD03;CD03;110E 1168 11BE;CD03;110E 1168 11BE; # (촃; 촃; 촃; 촃; 촃; ) HANGUL SYLLABLE CYEC
+CD04;CD04;110E 1168 11BF;CD04;110E 1168 11BF; # (촄; 촄; 촄; 촄; 촄; ) HANGUL SYLLABLE CYEK
+CD05;CD05;110E 1168 11C0;CD05;110E 1168 11C0; # (촅; 촅; 촅; 촅; 촅; ) HANGUL SYLLABLE CYET
+CD06;CD06;110E 1168 11C1;CD06;110E 1168 11C1; # (ì´†; ì´†; á„Žá…¨á‡; ì´†; á„Žá…¨á‡; ) HANGUL SYLLABLE CYEP
+CD07;CD07;110E 1168 11C2;CD07;110E 1168 11C2; # (촇; 촇; 촇; 촇; 촇; ) HANGUL SYLLABLE CYEH
+CD08;CD08;110E 1169;CD08;110E 1169; # (ì´ˆ; ì´ˆ; á„Žá…©; ì´ˆ; á„Žá…©; ) HANGUL SYLLABLE CO
+CD09;CD09;110E 1169 11A8;CD09;110E 1169 11A8; # (촉; 촉; 촉; 촉; 촉; ) HANGUL SYLLABLE COG
+CD0A;CD0A;110E 1169 11A9;CD0A;110E 1169 11A9; # (촊; 촊; 촊; 촊; 촊; ) HANGUL SYLLABLE COGG
+CD0B;CD0B;110E 1169 11AA;CD0B;110E 1169 11AA; # (촋; 촋; 촋; 촋; 촋; ) HANGUL SYLLABLE COGS
+CD0C;CD0C;110E 1169 11AB;CD0C;110E 1169 11AB; # (촌; 촌; 촌; 촌; 촌; ) HANGUL SYLLABLE CON
+CD0D;CD0D;110E 1169 11AC;CD0D;110E 1169 11AC; # (ì´; ì´; 촍; ì´; 촍; ) HANGUL SYLLABLE CONJ
+CD0E;CD0E;110E 1169 11AD;CD0E;110E 1169 11AD; # (촎; 촎; 촎; 촎; 촎; ) HANGUL SYLLABLE CONH
+CD0F;CD0F;110E 1169 11AE;CD0F;110E 1169 11AE; # (ì´; ì´; 촏; ì´; 촏; ) HANGUL SYLLABLE COD
+CD10;CD10;110E 1169 11AF;CD10;110E 1169 11AF; # (ì´; ì´; 촐; ì´; 촐; ) HANGUL SYLLABLE COL
+CD11;CD11;110E 1169 11B0;CD11;110E 1169 11B0; # (촑; 촑; 촑; 촑; 촑; ) HANGUL SYLLABLE COLG
+CD12;CD12;110E 1169 11B1;CD12;110E 1169 11B1; # (촒; 촒; 촒; 촒; 촒; ) HANGUL SYLLABLE COLM
+CD13;CD13;110E 1169 11B2;CD13;110E 1169 11B2; # (촓; 촓; 촓; 촓; 촓; ) HANGUL SYLLABLE COLB
+CD14;CD14;110E 1169 11B3;CD14;110E 1169 11B3; # (촔; 촔; 촔; 촔; 촔; ) HANGUL SYLLABLE COLS
+CD15;CD15;110E 1169 11B4;CD15;110E 1169 11B4; # (촕; 촕; 촕; 촕; 촕; ) HANGUL SYLLABLE COLT
+CD16;CD16;110E 1169 11B5;CD16;110E 1169 11B5; # (촖; 촖; 촖; 촖; 촖; ) HANGUL SYLLABLE COLP
+CD17;CD17;110E 1169 11B6;CD17;110E 1169 11B6; # (촗; 촗; 촗; 촗; 촗; ) HANGUL SYLLABLE COLH
+CD18;CD18;110E 1169 11B7;CD18;110E 1169 11B7; # (촘; 촘; 촘; 촘; 촘; ) HANGUL SYLLABLE COM
+CD19;CD19;110E 1169 11B8;CD19;110E 1169 11B8; # (촙; 촙; 촙; 촙; 촙; ) HANGUL SYLLABLE COB
+CD1A;CD1A;110E 1169 11B9;CD1A;110E 1169 11B9; # (촚; 촚; 촚; 촚; 촚; ) HANGUL SYLLABLE COBS
+CD1B;CD1B;110E 1169 11BA;CD1B;110E 1169 11BA; # (촛; 촛; 촛; 촛; 촛; ) HANGUL SYLLABLE COS
+CD1C;CD1C;110E 1169 11BB;CD1C;110E 1169 11BB; # (촜; 촜; 촜; 촜; 촜; ) HANGUL SYLLABLE COSS
+CD1D;CD1D;110E 1169 11BC;CD1D;110E 1169 11BC; # (ì´; ì´; 총; ì´; 총; ) HANGUL SYLLABLE CONG
+CD1E;CD1E;110E 1169 11BD;CD1E;110E 1169 11BD; # (촞; 촞; 촞; 촞; 촞; ) HANGUL SYLLABLE COJ
+CD1F;CD1F;110E 1169 11BE;CD1F;110E 1169 11BE; # (촟; 촟; 촟; 촟; 촟; ) HANGUL SYLLABLE COC
+CD20;CD20;110E 1169 11BF;CD20;110E 1169 11BF; # (촠; 촠; 촠; 촠; 촠; ) HANGUL SYLLABLE COK
+CD21;CD21;110E 1169 11C0;CD21;110E 1169 11C0; # (촡; 촡; 촡; 촡; 촡; ) HANGUL SYLLABLE COT
+CD22;CD22;110E 1169 11C1;CD22;110E 1169 11C1; # (ì´¢; ì´¢; á„Žá…©á‡; ì´¢; á„Žá…©á‡; ) HANGUL SYLLABLE COP
+CD23;CD23;110E 1169 11C2;CD23;110E 1169 11C2; # (촣; 촣; 촣; 촣; 촣; ) HANGUL SYLLABLE COH
+CD24;CD24;110E 116A;CD24;110E 116A; # (ì´¤; ì´¤; á„Žá…ª; ì´¤; á„Žá…ª; ) HANGUL SYLLABLE CWA
+CD25;CD25;110E 116A 11A8;CD25;110E 116A 11A8; # (촥; 촥; 촥; 촥; 촥; ) HANGUL SYLLABLE CWAG
+CD26;CD26;110E 116A 11A9;CD26;110E 116A 11A9; # (촦; 촦; 촦; 촦; 촦; ) HANGUL SYLLABLE CWAGG
+CD27;CD27;110E 116A 11AA;CD27;110E 116A 11AA; # (촧; 촧; 촧; 촧; 촧; ) HANGUL SYLLABLE CWAGS
+CD28;CD28;110E 116A 11AB;CD28;110E 116A 11AB; # (촨; 촨; 촨; 촨; 촨; ) HANGUL SYLLABLE CWAN
+CD29;CD29;110E 116A 11AC;CD29;110E 116A 11AC; # (촩; 촩; 촩; 촩; 촩; ) HANGUL SYLLABLE CWANJ
+CD2A;CD2A;110E 116A 11AD;CD2A;110E 116A 11AD; # (촪; 촪; 촪; 촪; 촪; ) HANGUL SYLLABLE CWANH
+CD2B;CD2B;110E 116A 11AE;CD2B;110E 116A 11AE; # (촫; 촫; 촫; 촫; 촫; ) HANGUL SYLLABLE CWAD
+CD2C;CD2C;110E 116A 11AF;CD2C;110E 116A 11AF; # (촬; 촬; 촬; 촬; 촬; ) HANGUL SYLLABLE CWAL
+CD2D;CD2D;110E 116A 11B0;CD2D;110E 116A 11B0; # (촭; 촭; 촭; 촭; 촭; ) HANGUL SYLLABLE CWALG
+CD2E;CD2E;110E 116A 11B1;CD2E;110E 116A 11B1; # (촮; 촮; 촮; 촮; 촮; ) HANGUL SYLLABLE CWALM
+CD2F;CD2F;110E 116A 11B2;CD2F;110E 116A 11B2; # (촯; 촯; 촯; 촯; 촯; ) HANGUL SYLLABLE CWALB
+CD30;CD30;110E 116A 11B3;CD30;110E 116A 11B3; # (촰; 촰; 촰; 촰; 촰; ) HANGUL SYLLABLE CWALS
+CD31;CD31;110E 116A 11B4;CD31;110E 116A 11B4; # (촱; 촱; 촱; 촱; 촱; ) HANGUL SYLLABLE CWALT
+CD32;CD32;110E 116A 11B5;CD32;110E 116A 11B5; # (촲; 촲; 촲; 촲; 촲; ) HANGUL SYLLABLE CWALP
+CD33;CD33;110E 116A 11B6;CD33;110E 116A 11B6; # (촳; 촳; 촳; 촳; 촳; ) HANGUL SYLLABLE CWALH
+CD34;CD34;110E 116A 11B7;CD34;110E 116A 11B7; # (촴; 촴; 촴; 촴; 촴; ) HANGUL SYLLABLE CWAM
+CD35;CD35;110E 116A 11B8;CD35;110E 116A 11B8; # (촵; 촵; 촵; 촵; 촵; ) HANGUL SYLLABLE CWAB
+CD36;CD36;110E 116A 11B9;CD36;110E 116A 11B9; # (촶; 촶; 촶; 촶; 촶; ) HANGUL SYLLABLE CWABS
+CD37;CD37;110E 116A 11BA;CD37;110E 116A 11BA; # (촷; 촷; 촷; 촷; 촷; ) HANGUL SYLLABLE CWAS
+CD38;CD38;110E 116A 11BB;CD38;110E 116A 11BB; # (촸; 촸; 촸; 촸; 촸; ) HANGUL SYLLABLE CWASS
+CD39;CD39;110E 116A 11BC;CD39;110E 116A 11BC; # (촹; 촹; 촹; 촹; 촹; ) HANGUL SYLLABLE CWANG
+CD3A;CD3A;110E 116A 11BD;CD3A;110E 116A 11BD; # (촺; 촺; 촺; 촺; 촺; ) HANGUL SYLLABLE CWAJ
+CD3B;CD3B;110E 116A 11BE;CD3B;110E 116A 11BE; # (촻; 촻; 촻; 촻; 촻; ) HANGUL SYLLABLE CWAC
+CD3C;CD3C;110E 116A 11BF;CD3C;110E 116A 11BF; # (촼; 촼; 촼; 촼; 촼; ) HANGUL SYLLABLE CWAK
+CD3D;CD3D;110E 116A 11C0;CD3D;110E 116A 11C0; # (촽; 촽; 촽; 촽; 촽; ) HANGUL SYLLABLE CWAT
+CD3E;CD3E;110E 116A 11C1;CD3E;110E 116A 11C1; # (ì´¾; ì´¾; á„Žá…ªá‡; ì´¾; á„Žá…ªá‡; ) HANGUL SYLLABLE CWAP
+CD3F;CD3F;110E 116A 11C2;CD3F;110E 116A 11C2; # (촿; 촿; 촿; 촿; 촿; ) HANGUL SYLLABLE CWAH
+CD40;CD40;110E 116B;CD40;110E 116B; # (ìµ€; ìµ€; á„Žá…«; ìµ€; á„Žá…«; ) HANGUL SYLLABLE CWAE
+CD41;CD41;110E 116B 11A8;CD41;110E 116B 11A8; # (ìµ; ìµ; 쵁; ìµ; 쵁; ) HANGUL SYLLABLE CWAEG
+CD42;CD42;110E 116B 11A9;CD42;110E 116B 11A9; # (쵂; 쵂; 쵂; 쵂; 쵂; ) HANGUL SYLLABLE CWAEGG
+CD43;CD43;110E 116B 11AA;CD43;110E 116B 11AA; # (쵃; 쵃; 쵃; 쵃; 쵃; ) HANGUL SYLLABLE CWAEGS
+CD44;CD44;110E 116B 11AB;CD44;110E 116B 11AB; # (쵄; 쵄; 쵄; 쵄; 쵄; ) HANGUL SYLLABLE CWAEN
+CD45;CD45;110E 116B 11AC;CD45;110E 116B 11AC; # (쵅; 쵅; 쵅; 쵅; 쵅; ) HANGUL SYLLABLE CWAENJ
+CD46;CD46;110E 116B 11AD;CD46;110E 116B 11AD; # (쵆; 쵆; 쵆; 쵆; 쵆; ) HANGUL SYLLABLE CWAENH
+CD47;CD47;110E 116B 11AE;CD47;110E 116B 11AE; # (쵇; 쵇; 쵇; 쵇; 쵇; ) HANGUL SYLLABLE CWAED
+CD48;CD48;110E 116B 11AF;CD48;110E 116B 11AF; # (쵈; 쵈; 쵈; 쵈; 쵈; ) HANGUL SYLLABLE CWAEL
+CD49;CD49;110E 116B 11B0;CD49;110E 116B 11B0; # (쵉; 쵉; 쵉; 쵉; 쵉; ) HANGUL SYLLABLE CWAELG
+CD4A;CD4A;110E 116B 11B1;CD4A;110E 116B 11B1; # (쵊; 쵊; 쵊; 쵊; 쵊; ) HANGUL SYLLABLE CWAELM
+CD4B;CD4B;110E 116B 11B2;CD4B;110E 116B 11B2; # (쵋; 쵋; 쵋; 쵋; 쵋; ) HANGUL SYLLABLE CWAELB
+CD4C;CD4C;110E 116B 11B3;CD4C;110E 116B 11B3; # (쵌; 쵌; 쵌; 쵌; 쵌; ) HANGUL SYLLABLE CWAELS
+CD4D;CD4D;110E 116B 11B4;CD4D;110E 116B 11B4; # (ìµ; ìµ; 쵍; ìµ; 쵍; ) HANGUL SYLLABLE CWAELT
+CD4E;CD4E;110E 116B 11B5;CD4E;110E 116B 11B5; # (쵎; 쵎; 쵎; 쵎; 쵎; ) HANGUL SYLLABLE CWAELP
+CD4F;CD4F;110E 116B 11B6;CD4F;110E 116B 11B6; # (ìµ; ìµ; 쵏; ìµ; 쵏; ) HANGUL SYLLABLE CWAELH
+CD50;CD50;110E 116B 11B7;CD50;110E 116B 11B7; # (ìµ; ìµ; 쵐; ìµ; 쵐; ) HANGUL SYLLABLE CWAEM
+CD51;CD51;110E 116B 11B8;CD51;110E 116B 11B8; # (쵑; 쵑; 쵑; 쵑; 쵑; ) HANGUL SYLLABLE CWAEB
+CD52;CD52;110E 116B 11B9;CD52;110E 116B 11B9; # (쵒; 쵒; 쵒; 쵒; 쵒; ) HANGUL SYLLABLE CWAEBS
+CD53;CD53;110E 116B 11BA;CD53;110E 116B 11BA; # (쵓; 쵓; 쵓; 쵓; 쵓; ) HANGUL SYLLABLE CWAES
+CD54;CD54;110E 116B 11BB;CD54;110E 116B 11BB; # (쵔; 쵔; 쵔; 쵔; 쵔; ) HANGUL SYLLABLE CWAESS
+CD55;CD55;110E 116B 11BC;CD55;110E 116B 11BC; # (쵕; 쵕; 쵕; 쵕; 쵕; ) HANGUL SYLLABLE CWAENG
+CD56;CD56;110E 116B 11BD;CD56;110E 116B 11BD; # (쵖; 쵖; 쵖; 쵖; 쵖; ) HANGUL SYLLABLE CWAEJ
+CD57;CD57;110E 116B 11BE;CD57;110E 116B 11BE; # (쵗; 쵗; 쵗; 쵗; 쵗; ) HANGUL SYLLABLE CWAEC
+CD58;CD58;110E 116B 11BF;CD58;110E 116B 11BF; # (쵘; 쵘; 쵘; 쵘; 쵘; ) HANGUL SYLLABLE CWAEK
+CD59;CD59;110E 116B 11C0;CD59;110E 116B 11C0; # (쵙; 쵙; 쵙; 쵙; 쵙; ) HANGUL SYLLABLE CWAET
+CD5A;CD5A;110E 116B 11C1;CD5A;110E 116B 11C1; # (쵚; 쵚; á„Žá…«á‡; 쵚; á„Žá…«á‡; ) HANGUL SYLLABLE CWAEP
+CD5B;CD5B;110E 116B 11C2;CD5B;110E 116B 11C2; # (쵛; 쵛; 쵛; 쵛; 쵛; ) HANGUL SYLLABLE CWAEH
+CD5C;CD5C;110E 116C;CD5C;110E 116C; # (최; 최; 최; 최; 최; ) HANGUL SYLLABLE COE
+CD5D;CD5D;110E 116C 11A8;CD5D;110E 116C 11A8; # (ìµ; ìµ; 쵝; ìµ; 쵝; ) HANGUL SYLLABLE COEG
+CD5E;CD5E;110E 116C 11A9;CD5E;110E 116C 11A9; # (쵞; 쵞; 쵞; 쵞; 쵞; ) HANGUL SYLLABLE COEGG
+CD5F;CD5F;110E 116C 11AA;CD5F;110E 116C 11AA; # (쵟; 쵟; 쵟; 쵟; 쵟; ) HANGUL SYLLABLE COEGS
+CD60;CD60;110E 116C 11AB;CD60;110E 116C 11AB; # (쵠; 쵠; 쵠; 쵠; 쵠; ) HANGUL SYLLABLE COEN
+CD61;CD61;110E 116C 11AC;CD61;110E 116C 11AC; # (쵡; 쵡; 쵡; 쵡; 쵡; ) HANGUL SYLLABLE COENJ
+CD62;CD62;110E 116C 11AD;CD62;110E 116C 11AD; # (쵢; 쵢; 쵢; 쵢; 쵢; ) HANGUL SYLLABLE COENH
+CD63;CD63;110E 116C 11AE;CD63;110E 116C 11AE; # (쵣; 쵣; 쵣; 쵣; 쵣; ) HANGUL SYLLABLE COED
+CD64;CD64;110E 116C 11AF;CD64;110E 116C 11AF; # (쵤; 쵤; 쵤; 쵤; 쵤; ) HANGUL SYLLABLE COEL
+CD65;CD65;110E 116C 11B0;CD65;110E 116C 11B0; # (쵥; 쵥; 쵥; 쵥; 쵥; ) HANGUL SYLLABLE COELG
+CD66;CD66;110E 116C 11B1;CD66;110E 116C 11B1; # (쵦; 쵦; 쵦; 쵦; 쵦; ) HANGUL SYLLABLE COELM
+CD67;CD67;110E 116C 11B2;CD67;110E 116C 11B2; # (쵧; 쵧; 쵧; 쵧; 쵧; ) HANGUL SYLLABLE COELB
+CD68;CD68;110E 116C 11B3;CD68;110E 116C 11B3; # (쵨; 쵨; 쵨; 쵨; 쵨; ) HANGUL SYLLABLE COELS
+CD69;CD69;110E 116C 11B4;CD69;110E 116C 11B4; # (쵩; 쵩; 쵩; 쵩; 쵩; ) HANGUL SYLLABLE COELT
+CD6A;CD6A;110E 116C 11B5;CD6A;110E 116C 11B5; # (쵪; 쵪; 쵪; 쵪; 쵪; ) HANGUL SYLLABLE COELP
+CD6B;CD6B;110E 116C 11B6;CD6B;110E 116C 11B6; # (쵫; 쵫; 쵫; 쵫; 쵫; ) HANGUL SYLLABLE COELH
+CD6C;CD6C;110E 116C 11B7;CD6C;110E 116C 11B7; # (쵬; 쵬; 쵬; 쵬; 쵬; ) HANGUL SYLLABLE COEM
+CD6D;CD6D;110E 116C 11B8;CD6D;110E 116C 11B8; # (쵭; 쵭; 쵭; 쵭; 쵭; ) HANGUL SYLLABLE COEB
+CD6E;CD6E;110E 116C 11B9;CD6E;110E 116C 11B9; # (쵮; 쵮; 쵮; 쵮; 쵮; ) HANGUL SYLLABLE COEBS
+CD6F;CD6F;110E 116C 11BA;CD6F;110E 116C 11BA; # (쵯; 쵯; 쵯; 쵯; 쵯; ) HANGUL SYLLABLE COES
+CD70;CD70;110E 116C 11BB;CD70;110E 116C 11BB; # (쵰; 쵰; 쵰; 쵰; 쵰; ) HANGUL SYLLABLE COESS
+CD71;CD71;110E 116C 11BC;CD71;110E 116C 11BC; # (쵱; 쵱; 쵱; 쵱; 쵱; ) HANGUL SYLLABLE COENG
+CD72;CD72;110E 116C 11BD;CD72;110E 116C 11BD; # (쵲; 쵲; 쵲; 쵲; 쵲; ) HANGUL SYLLABLE COEJ
+CD73;CD73;110E 116C 11BE;CD73;110E 116C 11BE; # (쵳; 쵳; 쵳; 쵳; 쵳; ) HANGUL SYLLABLE COEC
+CD74;CD74;110E 116C 11BF;CD74;110E 116C 11BF; # (쵴; 쵴; 쵴; 쵴; 쵴; ) HANGUL SYLLABLE COEK
+CD75;CD75;110E 116C 11C0;CD75;110E 116C 11C0; # (쵵; 쵵; 쵵; 쵵; 쵵; ) HANGUL SYLLABLE COET
+CD76;CD76;110E 116C 11C1;CD76;110E 116C 11C1; # (쵶; 쵶; á„Žá…¬á‡; 쵶; á„Žá…¬á‡; ) HANGUL SYLLABLE COEP
+CD77;CD77;110E 116C 11C2;CD77;110E 116C 11C2; # (쵷; 쵷; 쵷; 쵷; 쵷; ) HANGUL SYLLABLE COEH
+CD78;CD78;110E 116D;CD78;110E 116D; # (쵸; 쵸; 쵸; 쵸; 쵸; ) HANGUL SYLLABLE CYO
+CD79;CD79;110E 116D 11A8;CD79;110E 116D 11A8; # (쵹; 쵹; 쵹; 쵹; 쵹; ) HANGUL SYLLABLE CYOG
+CD7A;CD7A;110E 116D 11A9;CD7A;110E 116D 11A9; # (쵺; 쵺; 쵺; 쵺; 쵺; ) HANGUL SYLLABLE CYOGG
+CD7B;CD7B;110E 116D 11AA;CD7B;110E 116D 11AA; # (쵻; 쵻; 쵻; 쵻; 쵻; ) HANGUL SYLLABLE CYOGS
+CD7C;CD7C;110E 116D 11AB;CD7C;110E 116D 11AB; # (쵼; 쵼; 쵼; 쵼; 쵼; ) HANGUL SYLLABLE CYON
+CD7D;CD7D;110E 116D 11AC;CD7D;110E 116D 11AC; # (쵽; 쵽; 쵽; 쵽; 쵽; ) HANGUL SYLLABLE CYONJ
+CD7E;CD7E;110E 116D 11AD;CD7E;110E 116D 11AD; # (쵾; 쵾; 쵾; 쵾; 쵾; ) HANGUL SYLLABLE CYONH
+CD7F;CD7F;110E 116D 11AE;CD7F;110E 116D 11AE; # (쵿; 쵿; 쵿; 쵿; 쵿; ) HANGUL SYLLABLE CYOD
+CD80;CD80;110E 116D 11AF;CD80;110E 116D 11AF; # (춀; 춀; 춀; 춀; 춀; ) HANGUL SYLLABLE CYOL
+CD81;CD81;110E 116D 11B0;CD81;110E 116D 11B0; # (ì¶; ì¶; 춁; ì¶; 춁; ) HANGUL SYLLABLE CYOLG
+CD82;CD82;110E 116D 11B1;CD82;110E 116D 11B1; # (춂; 춂; 춂; 춂; 춂; ) HANGUL SYLLABLE CYOLM
+CD83;CD83;110E 116D 11B2;CD83;110E 116D 11B2; # (춃; 춃; 춃; 춃; 춃; ) HANGUL SYLLABLE CYOLB
+CD84;CD84;110E 116D 11B3;CD84;110E 116D 11B3; # (춄; 춄; 춄; 춄; 춄; ) HANGUL SYLLABLE CYOLS
+CD85;CD85;110E 116D 11B4;CD85;110E 116D 11B4; # (춅; 춅; 춅; 춅; 춅; ) HANGUL SYLLABLE CYOLT
+CD86;CD86;110E 116D 11B5;CD86;110E 116D 11B5; # (춆; 춆; 춆; 춆; 춆; ) HANGUL SYLLABLE CYOLP
+CD87;CD87;110E 116D 11B6;CD87;110E 116D 11B6; # (춇; 춇; 춇; 춇; 춇; ) HANGUL SYLLABLE CYOLH
+CD88;CD88;110E 116D 11B7;CD88;110E 116D 11B7; # (춈; 춈; 춈; 춈; 춈; ) HANGUL SYLLABLE CYOM
+CD89;CD89;110E 116D 11B8;CD89;110E 116D 11B8; # (춉; 춉; 춉; 춉; 춉; ) HANGUL SYLLABLE CYOB
+CD8A;CD8A;110E 116D 11B9;CD8A;110E 116D 11B9; # (춊; 춊; 춊; 춊; 춊; ) HANGUL SYLLABLE CYOBS
+CD8B;CD8B;110E 116D 11BA;CD8B;110E 116D 11BA; # (춋; 춋; 춋; 춋; 춋; ) HANGUL SYLLABLE CYOS
+CD8C;CD8C;110E 116D 11BB;CD8C;110E 116D 11BB; # (춌; 춌; 춌; 춌; 춌; ) HANGUL SYLLABLE CYOSS
+CD8D;CD8D;110E 116D 11BC;CD8D;110E 116D 11BC; # (ì¶; ì¶; 춍; ì¶; 춍; ) HANGUL SYLLABLE CYONG
+CD8E;CD8E;110E 116D 11BD;CD8E;110E 116D 11BD; # (춎; 춎; 춎; 춎; 춎; ) HANGUL SYLLABLE CYOJ
+CD8F;CD8F;110E 116D 11BE;CD8F;110E 116D 11BE; # (ì¶; ì¶; 춏; ì¶; 춏; ) HANGUL SYLLABLE CYOC
+CD90;CD90;110E 116D 11BF;CD90;110E 116D 11BF; # (ì¶; ì¶; 춐; ì¶; 춐; ) HANGUL SYLLABLE CYOK
+CD91;CD91;110E 116D 11C0;CD91;110E 116D 11C0; # (춑; 춑; 춑; 춑; 춑; ) HANGUL SYLLABLE CYOT
+CD92;CD92;110E 116D 11C1;CD92;110E 116D 11C1; # (춒; 춒; á„Žá…­á‡; 춒; á„Žá…­á‡; ) HANGUL SYLLABLE CYOP
+CD93;CD93;110E 116D 11C2;CD93;110E 116D 11C2; # (춓; 춓; 춓; 춓; 춓; ) HANGUL SYLLABLE CYOH
+CD94;CD94;110E 116E;CD94;110E 116E; # (추; 추; 추; 추; 추; ) HANGUL SYLLABLE CU
+CD95;CD95;110E 116E 11A8;CD95;110E 116E 11A8; # (축; 축; 축; 축; 축; ) HANGUL SYLLABLE CUG
+CD96;CD96;110E 116E 11A9;CD96;110E 116E 11A9; # (춖; 춖; 춖; 춖; 춖; ) HANGUL SYLLABLE CUGG
+CD97;CD97;110E 116E 11AA;CD97;110E 116E 11AA; # (춗; 춗; 춗; 춗; 춗; ) HANGUL SYLLABLE CUGS
+CD98;CD98;110E 116E 11AB;CD98;110E 116E 11AB; # (춘; 춘; 춘; 춘; 춘; ) HANGUL SYLLABLE CUN
+CD99;CD99;110E 116E 11AC;CD99;110E 116E 11AC; # (춙; 춙; 춙; 춙; 춙; ) HANGUL SYLLABLE CUNJ
+CD9A;CD9A;110E 116E 11AD;CD9A;110E 116E 11AD; # (춚; 춚; 춚; 춚; 춚; ) HANGUL SYLLABLE CUNH
+CD9B;CD9B;110E 116E 11AE;CD9B;110E 116E 11AE; # (춛; 춛; 춛; 춛; 춛; ) HANGUL SYLLABLE CUD
+CD9C;CD9C;110E 116E 11AF;CD9C;110E 116E 11AF; # (출; 출; 출; 출; 출; ) HANGUL SYLLABLE CUL
+CD9D;CD9D;110E 116E 11B0;CD9D;110E 116E 11B0; # (ì¶; ì¶; 춝; ì¶; 춝; ) HANGUL SYLLABLE CULG
+CD9E;CD9E;110E 116E 11B1;CD9E;110E 116E 11B1; # (춞; 춞; 춞; 춞; 춞; ) HANGUL SYLLABLE CULM
+CD9F;CD9F;110E 116E 11B2;CD9F;110E 116E 11B2; # (춟; 춟; 춟; 춟; 춟; ) HANGUL SYLLABLE CULB
+CDA0;CDA0;110E 116E 11B3;CDA0;110E 116E 11B3; # (춠; 춠; 춠; 춠; 춠; ) HANGUL SYLLABLE CULS
+CDA1;CDA1;110E 116E 11B4;CDA1;110E 116E 11B4; # (춡; 춡; 춡; 춡; 춡; ) HANGUL SYLLABLE CULT
+CDA2;CDA2;110E 116E 11B5;CDA2;110E 116E 11B5; # (춢; 춢; 춢; 춢; 춢; ) HANGUL SYLLABLE CULP
+CDA3;CDA3;110E 116E 11B6;CDA3;110E 116E 11B6; # (춣; 춣; 춣; 춣; 춣; ) HANGUL SYLLABLE CULH
+CDA4;CDA4;110E 116E 11B7;CDA4;110E 116E 11B7; # (춤; 춤; 춤; 춤; 춤; ) HANGUL SYLLABLE CUM
+CDA5;CDA5;110E 116E 11B8;CDA5;110E 116E 11B8; # (춥; 춥; 춥; 춥; 춥; ) HANGUL SYLLABLE CUB
+CDA6;CDA6;110E 116E 11B9;CDA6;110E 116E 11B9; # (춦; 춦; 춦; 춦; 춦; ) HANGUL SYLLABLE CUBS
+CDA7;CDA7;110E 116E 11BA;CDA7;110E 116E 11BA; # (춧; 춧; 춧; 춧; 춧; ) HANGUL SYLLABLE CUS
+CDA8;CDA8;110E 116E 11BB;CDA8;110E 116E 11BB; # (춨; 춨; 춨; 춨; 춨; ) HANGUL SYLLABLE CUSS
+CDA9;CDA9;110E 116E 11BC;CDA9;110E 116E 11BC; # (충; 충; 충; 충; 충; ) HANGUL SYLLABLE CUNG
+CDAA;CDAA;110E 116E 11BD;CDAA;110E 116E 11BD; # (춪; 춪; 춪; 춪; 춪; ) HANGUL SYLLABLE CUJ
+CDAB;CDAB;110E 116E 11BE;CDAB;110E 116E 11BE; # (춫; 춫; 춫; 춫; 춫; ) HANGUL SYLLABLE CUC
+CDAC;CDAC;110E 116E 11BF;CDAC;110E 116E 11BF; # (춬; 춬; 춬; 춬; 춬; ) HANGUL SYLLABLE CUK
+CDAD;CDAD;110E 116E 11C0;CDAD;110E 116E 11C0; # (춭; 춭; 춭; 춭; 춭; ) HANGUL SYLLABLE CUT
+CDAE;CDAE;110E 116E 11C1;CDAE;110E 116E 11C1; # (춮; 춮; á„Žá…®á‡; 춮; á„Žá…®á‡; ) HANGUL SYLLABLE CUP
+CDAF;CDAF;110E 116E 11C2;CDAF;110E 116E 11C2; # (춯; 춯; 춯; 춯; 춯; ) HANGUL SYLLABLE CUH
+CDB0;CDB0;110E 116F;CDB0;110E 116F; # (춰; 춰; 춰; 춰; 춰; ) HANGUL SYLLABLE CWEO
+CDB1;CDB1;110E 116F 11A8;CDB1;110E 116F 11A8; # (춱; 춱; 춱; 춱; 춱; ) HANGUL SYLLABLE CWEOG
+CDB2;CDB2;110E 116F 11A9;CDB2;110E 116F 11A9; # (춲; 춲; 춲; 춲; 춲; ) HANGUL SYLLABLE CWEOGG
+CDB3;CDB3;110E 116F 11AA;CDB3;110E 116F 11AA; # (춳; 춳; 춳; 춳; 춳; ) HANGUL SYLLABLE CWEOGS
+CDB4;CDB4;110E 116F 11AB;CDB4;110E 116F 11AB; # (춴; 춴; 춴; 춴; 춴; ) HANGUL SYLLABLE CWEON
+CDB5;CDB5;110E 116F 11AC;CDB5;110E 116F 11AC; # (춵; 춵; 춵; 춵; 춵; ) HANGUL SYLLABLE CWEONJ
+CDB6;CDB6;110E 116F 11AD;CDB6;110E 116F 11AD; # (춶; 춶; 춶; 춶; 춶; ) HANGUL SYLLABLE CWEONH
+CDB7;CDB7;110E 116F 11AE;CDB7;110E 116F 11AE; # (춷; 춷; 춷; 춷; 춷; ) HANGUL SYLLABLE CWEOD
+CDB8;CDB8;110E 116F 11AF;CDB8;110E 116F 11AF; # (춸; 춸; 춸; 춸; 춸; ) HANGUL SYLLABLE CWEOL
+CDB9;CDB9;110E 116F 11B0;CDB9;110E 116F 11B0; # (춹; 춹; 춹; 춹; 춹; ) HANGUL SYLLABLE CWEOLG
+CDBA;CDBA;110E 116F 11B1;CDBA;110E 116F 11B1; # (춺; 춺; 춺; 춺; 춺; ) HANGUL SYLLABLE CWEOLM
+CDBB;CDBB;110E 116F 11B2;CDBB;110E 116F 11B2; # (춻; 춻; 춻; 춻; 춻; ) HANGUL SYLLABLE CWEOLB
+CDBC;CDBC;110E 116F 11B3;CDBC;110E 116F 11B3; # (춼; 춼; 춼; 춼; 춼; ) HANGUL SYLLABLE CWEOLS
+CDBD;CDBD;110E 116F 11B4;CDBD;110E 116F 11B4; # (춽; 춽; 춽; 춽; 춽; ) HANGUL SYLLABLE CWEOLT
+CDBE;CDBE;110E 116F 11B5;CDBE;110E 116F 11B5; # (춾; 춾; 춾; 춾; 춾; ) HANGUL SYLLABLE CWEOLP
+CDBF;CDBF;110E 116F 11B6;CDBF;110E 116F 11B6; # (춿; 춿; 춿; 춿; 춿; ) HANGUL SYLLABLE CWEOLH
+CDC0;CDC0;110E 116F 11B7;CDC0;110E 116F 11B7; # (췀; 췀; 췀; 췀; 췀; ) HANGUL SYLLABLE CWEOM
+CDC1;CDC1;110E 116F 11B8;CDC1;110E 116F 11B8; # (ì·; ì·; 췁; ì·; 췁; ) HANGUL SYLLABLE CWEOB
+CDC2;CDC2;110E 116F 11B9;CDC2;110E 116F 11B9; # (췂; 췂; 췂; 췂; 췂; ) HANGUL SYLLABLE CWEOBS
+CDC3;CDC3;110E 116F 11BA;CDC3;110E 116F 11BA; # (췃; 췃; 췃; 췃; 췃; ) HANGUL SYLLABLE CWEOS
+CDC4;CDC4;110E 116F 11BB;CDC4;110E 116F 11BB; # (췄; 췄; 췄; 췄; 췄; ) HANGUL SYLLABLE CWEOSS
+CDC5;CDC5;110E 116F 11BC;CDC5;110E 116F 11BC; # (췅; 췅; 췅; 췅; 췅; ) HANGUL SYLLABLE CWEONG
+CDC6;CDC6;110E 116F 11BD;CDC6;110E 116F 11BD; # (췆; 췆; 췆; 췆; 췆; ) HANGUL SYLLABLE CWEOJ
+CDC7;CDC7;110E 116F 11BE;CDC7;110E 116F 11BE; # (췇; 췇; 췇; 췇; 췇; ) HANGUL SYLLABLE CWEOC
+CDC8;CDC8;110E 116F 11BF;CDC8;110E 116F 11BF; # (췈; 췈; 췈; 췈; 췈; ) HANGUL SYLLABLE CWEOK
+CDC9;CDC9;110E 116F 11C0;CDC9;110E 116F 11C0; # (췉; 췉; 췉; 췉; 췉; ) HANGUL SYLLABLE CWEOT
+CDCA;CDCA;110E 116F 11C1;CDCA;110E 116F 11C1; # (ì·Š; ì·Š; á„Žá…¯á‡; ì·Š; á„Žá…¯á‡; ) HANGUL SYLLABLE CWEOP
+CDCB;CDCB;110E 116F 11C2;CDCB;110E 116F 11C2; # (췋; 췋; 췋; 췋; 췋; ) HANGUL SYLLABLE CWEOH
+CDCC;CDCC;110E 1170;CDCC;110E 1170; # (췌; 췌; 췌; 췌; 췌; ) HANGUL SYLLABLE CWE
+CDCD;CDCD;110E 1170 11A8;CDCD;110E 1170 11A8; # (ì·; ì·; 췍; ì·; 췍; ) HANGUL SYLLABLE CWEG
+CDCE;CDCE;110E 1170 11A9;CDCE;110E 1170 11A9; # (췎; 췎; 췎; 췎; 췎; ) HANGUL SYLLABLE CWEGG
+CDCF;CDCF;110E 1170 11AA;CDCF;110E 1170 11AA; # (ì·; ì·; 췏; ì·; 췏; ) HANGUL SYLLABLE CWEGS
+CDD0;CDD0;110E 1170 11AB;CDD0;110E 1170 11AB; # (ì·; ì·; 췐; ì·; 췐; ) HANGUL SYLLABLE CWEN
+CDD1;CDD1;110E 1170 11AC;CDD1;110E 1170 11AC; # (췑; 췑; 췑; 췑; 췑; ) HANGUL SYLLABLE CWENJ
+CDD2;CDD2;110E 1170 11AD;CDD2;110E 1170 11AD; # (췒; 췒; 췒; 췒; 췒; ) HANGUL SYLLABLE CWENH
+CDD3;CDD3;110E 1170 11AE;CDD3;110E 1170 11AE; # (췓; 췓; 췓; 췓; 췓; ) HANGUL SYLLABLE CWED
+CDD4;CDD4;110E 1170 11AF;CDD4;110E 1170 11AF; # (췔; 췔; 췔; 췔; 췔; ) HANGUL SYLLABLE CWEL
+CDD5;CDD5;110E 1170 11B0;CDD5;110E 1170 11B0; # (췕; 췕; 췕; 췕; 췕; ) HANGUL SYLLABLE CWELG
+CDD6;CDD6;110E 1170 11B1;CDD6;110E 1170 11B1; # (췖; 췖; 췖; 췖; 췖; ) HANGUL SYLLABLE CWELM
+CDD7;CDD7;110E 1170 11B2;CDD7;110E 1170 11B2; # (췗; 췗; 췗; 췗; 췗; ) HANGUL SYLLABLE CWELB
+CDD8;CDD8;110E 1170 11B3;CDD8;110E 1170 11B3; # (췘; 췘; 췘; 췘; 췘; ) HANGUL SYLLABLE CWELS
+CDD9;CDD9;110E 1170 11B4;CDD9;110E 1170 11B4; # (췙; 췙; 췙; 췙; 췙; ) HANGUL SYLLABLE CWELT
+CDDA;CDDA;110E 1170 11B5;CDDA;110E 1170 11B5; # (췚; 췚; 췚; 췚; 췚; ) HANGUL SYLLABLE CWELP
+CDDB;CDDB;110E 1170 11B6;CDDB;110E 1170 11B6; # (췛; 췛; 췛; 췛; 췛; ) HANGUL SYLLABLE CWELH
+CDDC;CDDC;110E 1170 11B7;CDDC;110E 1170 11B7; # (췜; 췜; 췜; 췜; 췜; ) HANGUL SYLLABLE CWEM
+CDDD;CDDD;110E 1170 11B8;CDDD;110E 1170 11B8; # (ì·; ì·; 췝; ì·; 췝; ) HANGUL SYLLABLE CWEB
+CDDE;CDDE;110E 1170 11B9;CDDE;110E 1170 11B9; # (췞; 췞; 췞; 췞; 췞; ) HANGUL SYLLABLE CWEBS
+CDDF;CDDF;110E 1170 11BA;CDDF;110E 1170 11BA; # (췟; 췟; 췟; 췟; 췟; ) HANGUL SYLLABLE CWES
+CDE0;CDE0;110E 1170 11BB;CDE0;110E 1170 11BB; # (췠; 췠; 췠; 췠; 췠; ) HANGUL SYLLABLE CWESS
+CDE1;CDE1;110E 1170 11BC;CDE1;110E 1170 11BC; # (췡; 췡; 췡; 췡; 췡; ) HANGUL SYLLABLE CWENG
+CDE2;CDE2;110E 1170 11BD;CDE2;110E 1170 11BD; # (췢; 췢; 췢; 췢; 췢; ) HANGUL SYLLABLE CWEJ
+CDE3;CDE3;110E 1170 11BE;CDE3;110E 1170 11BE; # (췣; 췣; 췣; 췣; 췣; ) HANGUL SYLLABLE CWEC
+CDE4;CDE4;110E 1170 11BF;CDE4;110E 1170 11BF; # (췤; 췤; 췤; 췤; 췤; ) HANGUL SYLLABLE CWEK
+CDE5;CDE5;110E 1170 11C0;CDE5;110E 1170 11C0; # (췥; 췥; 췥; 췥; 췥; ) HANGUL SYLLABLE CWET
+CDE6;CDE6;110E 1170 11C1;CDE6;110E 1170 11C1; # (ì·¦; ì·¦; á„Žá…°á‡; ì·¦; á„Žá…°á‡; ) HANGUL SYLLABLE CWEP
+CDE7;CDE7;110E 1170 11C2;CDE7;110E 1170 11C2; # (췧; 췧; 췧; 췧; 췧; ) HANGUL SYLLABLE CWEH
+CDE8;CDE8;110E 1171;CDE8;110E 1171; # (ì·¨; ì·¨; á„Žá…±; ì·¨; á„Žá…±; ) HANGUL SYLLABLE CWI
+CDE9;CDE9;110E 1171 11A8;CDE9;110E 1171 11A8; # (췩; 췩; 췩; 췩; 췩; ) HANGUL SYLLABLE CWIG
+CDEA;CDEA;110E 1171 11A9;CDEA;110E 1171 11A9; # (췪; 췪; 췪; 췪; 췪; ) HANGUL SYLLABLE CWIGG
+CDEB;CDEB;110E 1171 11AA;CDEB;110E 1171 11AA; # (췫; 췫; 췫; 췫; 췫; ) HANGUL SYLLABLE CWIGS
+CDEC;CDEC;110E 1171 11AB;CDEC;110E 1171 11AB; # (췬; 췬; 췬; 췬; 췬; ) HANGUL SYLLABLE CWIN
+CDED;CDED;110E 1171 11AC;CDED;110E 1171 11AC; # (췭; 췭; 췭; 췭; 췭; ) HANGUL SYLLABLE CWINJ
+CDEE;CDEE;110E 1171 11AD;CDEE;110E 1171 11AD; # (췮; 췮; 췮; 췮; 췮; ) HANGUL SYLLABLE CWINH
+CDEF;CDEF;110E 1171 11AE;CDEF;110E 1171 11AE; # (췯; 췯; 췯; 췯; 췯; ) HANGUL SYLLABLE CWID
+CDF0;CDF0;110E 1171 11AF;CDF0;110E 1171 11AF; # (췰; 췰; 췰; 췰; 췰; ) HANGUL SYLLABLE CWIL
+CDF1;CDF1;110E 1171 11B0;CDF1;110E 1171 11B0; # (췱; 췱; 췱; 췱; 췱; ) HANGUL SYLLABLE CWILG
+CDF2;CDF2;110E 1171 11B1;CDF2;110E 1171 11B1; # (췲; 췲; 췲; 췲; 췲; ) HANGUL SYLLABLE CWILM
+CDF3;CDF3;110E 1171 11B2;CDF3;110E 1171 11B2; # (췳; 췳; 췳; 췳; 췳; ) HANGUL SYLLABLE CWILB
+CDF4;CDF4;110E 1171 11B3;CDF4;110E 1171 11B3; # (췴; 췴; 췴; 췴; 췴; ) HANGUL SYLLABLE CWILS
+CDF5;CDF5;110E 1171 11B4;CDF5;110E 1171 11B4; # (췵; 췵; 췵; 췵; 췵; ) HANGUL SYLLABLE CWILT
+CDF6;CDF6;110E 1171 11B5;CDF6;110E 1171 11B5; # (췶; 췶; 췶; 췶; 췶; ) HANGUL SYLLABLE CWILP
+CDF7;CDF7;110E 1171 11B6;CDF7;110E 1171 11B6; # (췷; 췷; 췷; 췷; 췷; ) HANGUL SYLLABLE CWILH
+CDF8;CDF8;110E 1171 11B7;CDF8;110E 1171 11B7; # (췸; 췸; 췸; 췸; 췸; ) HANGUL SYLLABLE CWIM
+CDF9;CDF9;110E 1171 11B8;CDF9;110E 1171 11B8; # (췹; 췹; 췹; 췹; 췹; ) HANGUL SYLLABLE CWIB
+CDFA;CDFA;110E 1171 11B9;CDFA;110E 1171 11B9; # (췺; 췺; 췺; 췺; 췺; ) HANGUL SYLLABLE CWIBS
+CDFB;CDFB;110E 1171 11BA;CDFB;110E 1171 11BA; # (췻; 췻; 췻; 췻; 췻; ) HANGUL SYLLABLE CWIS
+CDFC;CDFC;110E 1171 11BB;CDFC;110E 1171 11BB; # (췼; 췼; 췼; 췼; 췼; ) HANGUL SYLLABLE CWISS
+CDFD;CDFD;110E 1171 11BC;CDFD;110E 1171 11BC; # (췽; 췽; 췽; 췽; 췽; ) HANGUL SYLLABLE CWING
+CDFE;CDFE;110E 1171 11BD;CDFE;110E 1171 11BD; # (췾; 췾; 췾; 췾; 췾; ) HANGUL SYLLABLE CWIJ
+CDFF;CDFF;110E 1171 11BE;CDFF;110E 1171 11BE; # (췿; 췿; 췿; 췿; 췿; ) HANGUL SYLLABLE CWIC
+CE00;CE00;110E 1171 11BF;CE00;110E 1171 11BF; # (츀; 츀; 츀; 츀; 츀; ) HANGUL SYLLABLE CWIK
+CE01;CE01;110E 1171 11C0;CE01;110E 1171 11C0; # (ì¸; ì¸; 츁; ì¸; 츁; ) HANGUL SYLLABLE CWIT
+CE02;CE02;110E 1171 11C1;CE02;110E 1171 11C1; # (츂; 츂; á„Žá…±á‡; 츂; á„Žá…±á‡; ) HANGUL SYLLABLE CWIP
+CE03;CE03;110E 1171 11C2;CE03;110E 1171 11C2; # (츃; 츃; 츃; 츃; 츃; ) HANGUL SYLLABLE CWIH
+CE04;CE04;110E 1172;CE04;110E 1172; # (츄; 츄; 츄; 츄; 츄; ) HANGUL SYLLABLE CYU
+CE05;CE05;110E 1172 11A8;CE05;110E 1172 11A8; # (츅; 츅; 츅; 츅; 츅; ) HANGUL SYLLABLE CYUG
+CE06;CE06;110E 1172 11A9;CE06;110E 1172 11A9; # (츆; 츆; 츆; 츆; 츆; ) HANGUL SYLLABLE CYUGG
+CE07;CE07;110E 1172 11AA;CE07;110E 1172 11AA; # (츇; 츇; 츇; 츇; 츇; ) HANGUL SYLLABLE CYUGS
+CE08;CE08;110E 1172 11AB;CE08;110E 1172 11AB; # (츈; 츈; 츈; 츈; 츈; ) HANGUL SYLLABLE CYUN
+CE09;CE09;110E 1172 11AC;CE09;110E 1172 11AC; # (츉; 츉; 츉; 츉; 츉; ) HANGUL SYLLABLE CYUNJ
+CE0A;CE0A;110E 1172 11AD;CE0A;110E 1172 11AD; # (츊; 츊; 츊; 츊; 츊; ) HANGUL SYLLABLE CYUNH
+CE0B;CE0B;110E 1172 11AE;CE0B;110E 1172 11AE; # (츋; 츋; 츋; 츋; 츋; ) HANGUL SYLLABLE CYUD
+CE0C;CE0C;110E 1172 11AF;CE0C;110E 1172 11AF; # (츌; 츌; 츌; 츌; 츌; ) HANGUL SYLLABLE CYUL
+CE0D;CE0D;110E 1172 11B0;CE0D;110E 1172 11B0; # (ì¸; ì¸; 츍; ì¸; 츍; ) HANGUL SYLLABLE CYULG
+CE0E;CE0E;110E 1172 11B1;CE0E;110E 1172 11B1; # (츎; 츎; 츎; 츎; 츎; ) HANGUL SYLLABLE CYULM
+CE0F;CE0F;110E 1172 11B2;CE0F;110E 1172 11B2; # (ì¸; ì¸; 츏; ì¸; 츏; ) HANGUL SYLLABLE CYULB
+CE10;CE10;110E 1172 11B3;CE10;110E 1172 11B3; # (ì¸; ì¸; 츐; ì¸; 츐; ) HANGUL SYLLABLE CYULS
+CE11;CE11;110E 1172 11B4;CE11;110E 1172 11B4; # (츑; 츑; 츑; 츑; 츑; ) HANGUL SYLLABLE CYULT
+CE12;CE12;110E 1172 11B5;CE12;110E 1172 11B5; # (츒; 츒; 츒; 츒; 츒; ) HANGUL SYLLABLE CYULP
+CE13;CE13;110E 1172 11B6;CE13;110E 1172 11B6; # (츓; 츓; 츓; 츓; 츓; ) HANGUL SYLLABLE CYULH
+CE14;CE14;110E 1172 11B7;CE14;110E 1172 11B7; # (츔; 츔; 츔; 츔; 츔; ) HANGUL SYLLABLE CYUM
+CE15;CE15;110E 1172 11B8;CE15;110E 1172 11B8; # (츕; 츕; 츕; 츕; 츕; ) HANGUL SYLLABLE CYUB
+CE16;CE16;110E 1172 11B9;CE16;110E 1172 11B9; # (츖; 츖; 츖; 츖; 츖; ) HANGUL SYLLABLE CYUBS
+CE17;CE17;110E 1172 11BA;CE17;110E 1172 11BA; # (츗; 츗; 츗; 츗; 츗; ) HANGUL SYLLABLE CYUS
+CE18;CE18;110E 1172 11BB;CE18;110E 1172 11BB; # (츘; 츘; 츘; 츘; 츘; ) HANGUL SYLLABLE CYUSS
+CE19;CE19;110E 1172 11BC;CE19;110E 1172 11BC; # (츙; 츙; 츙; 츙; 츙; ) HANGUL SYLLABLE CYUNG
+CE1A;CE1A;110E 1172 11BD;CE1A;110E 1172 11BD; # (츚; 츚; 츚; 츚; 츚; ) HANGUL SYLLABLE CYUJ
+CE1B;CE1B;110E 1172 11BE;CE1B;110E 1172 11BE; # (츛; 츛; 츛; 츛; 츛; ) HANGUL SYLLABLE CYUC
+CE1C;CE1C;110E 1172 11BF;CE1C;110E 1172 11BF; # (츜; 츜; 츜; 츜; 츜; ) HANGUL SYLLABLE CYUK
+CE1D;CE1D;110E 1172 11C0;CE1D;110E 1172 11C0; # (ì¸; ì¸; 츝; ì¸; 츝; ) HANGUL SYLLABLE CYUT
+CE1E;CE1E;110E 1172 11C1;CE1E;110E 1172 11C1; # (츞; 츞; á„Žá…²á‡; 츞; á„Žá…²á‡; ) HANGUL SYLLABLE CYUP
+CE1F;CE1F;110E 1172 11C2;CE1F;110E 1172 11C2; # (츟; 츟; 츟; 츟; 츟; ) HANGUL SYLLABLE CYUH
+CE20;CE20;110E 1173;CE20;110E 1173; # (츠; 츠; 츠; 츠; 츠; ) HANGUL SYLLABLE CEU
+CE21;CE21;110E 1173 11A8;CE21;110E 1173 11A8; # (측; 측; 측; 측; 측; ) HANGUL SYLLABLE CEUG
+CE22;CE22;110E 1173 11A9;CE22;110E 1173 11A9; # (츢; 츢; 츢; 츢; 츢; ) HANGUL SYLLABLE CEUGG
+CE23;CE23;110E 1173 11AA;CE23;110E 1173 11AA; # (츣; 츣; 츣; 츣; 츣; ) HANGUL SYLLABLE CEUGS
+CE24;CE24;110E 1173 11AB;CE24;110E 1173 11AB; # (츤; 츤; 츤; 츤; 츤; ) HANGUL SYLLABLE CEUN
+CE25;CE25;110E 1173 11AC;CE25;110E 1173 11AC; # (츥; 츥; 츥; 츥; 츥; ) HANGUL SYLLABLE CEUNJ
+CE26;CE26;110E 1173 11AD;CE26;110E 1173 11AD; # (츦; 츦; 츦; 츦; 츦; ) HANGUL SYLLABLE CEUNH
+CE27;CE27;110E 1173 11AE;CE27;110E 1173 11AE; # (츧; 츧; 츧; 츧; 츧; ) HANGUL SYLLABLE CEUD
+CE28;CE28;110E 1173 11AF;CE28;110E 1173 11AF; # (츨; 츨; 츨; 츨; 츨; ) HANGUL SYLLABLE CEUL
+CE29;CE29;110E 1173 11B0;CE29;110E 1173 11B0; # (츩; 츩; 츩; 츩; 츩; ) HANGUL SYLLABLE CEULG
+CE2A;CE2A;110E 1173 11B1;CE2A;110E 1173 11B1; # (츪; 츪; 츪; 츪; 츪; ) HANGUL SYLLABLE CEULM
+CE2B;CE2B;110E 1173 11B2;CE2B;110E 1173 11B2; # (츫; 츫; 츫; 츫; 츫; ) HANGUL SYLLABLE CEULB
+CE2C;CE2C;110E 1173 11B3;CE2C;110E 1173 11B3; # (츬; 츬; 츬; 츬; 츬; ) HANGUL SYLLABLE CEULS
+CE2D;CE2D;110E 1173 11B4;CE2D;110E 1173 11B4; # (츭; 츭; 츭; 츭; 츭; ) HANGUL SYLLABLE CEULT
+CE2E;CE2E;110E 1173 11B5;CE2E;110E 1173 11B5; # (츮; 츮; 츮; 츮; 츮; ) HANGUL SYLLABLE CEULP
+CE2F;CE2F;110E 1173 11B6;CE2F;110E 1173 11B6; # (츯; 츯; 츯; 츯; 츯; ) HANGUL SYLLABLE CEULH
+CE30;CE30;110E 1173 11B7;CE30;110E 1173 11B7; # (츰; 츰; 츰; 츰; 츰; ) HANGUL SYLLABLE CEUM
+CE31;CE31;110E 1173 11B8;CE31;110E 1173 11B8; # (츱; 츱; 츱; 츱; 츱; ) HANGUL SYLLABLE CEUB
+CE32;CE32;110E 1173 11B9;CE32;110E 1173 11B9; # (츲; 츲; 츲; 츲; 츲; ) HANGUL SYLLABLE CEUBS
+CE33;CE33;110E 1173 11BA;CE33;110E 1173 11BA; # (츳; 츳; 츳; 츳; 츳; ) HANGUL SYLLABLE CEUS
+CE34;CE34;110E 1173 11BB;CE34;110E 1173 11BB; # (츴; 츴; 츴; 츴; 츴; ) HANGUL SYLLABLE CEUSS
+CE35;CE35;110E 1173 11BC;CE35;110E 1173 11BC; # (층; 층; 층; 층; 층; ) HANGUL SYLLABLE CEUNG
+CE36;CE36;110E 1173 11BD;CE36;110E 1173 11BD; # (츶; 츶; 츶; 츶; 츶; ) HANGUL SYLLABLE CEUJ
+CE37;CE37;110E 1173 11BE;CE37;110E 1173 11BE; # (츷; 츷; 츷; 츷; 츷; ) HANGUL SYLLABLE CEUC
+CE38;CE38;110E 1173 11BF;CE38;110E 1173 11BF; # (츸; 츸; 츸; 츸; 츸; ) HANGUL SYLLABLE CEUK
+CE39;CE39;110E 1173 11C0;CE39;110E 1173 11C0; # (츹; 츹; 츹; 츹; 츹; ) HANGUL SYLLABLE CEUT
+CE3A;CE3A;110E 1173 11C1;CE3A;110E 1173 11C1; # (츺; 츺; á„Žá…³á‡; 츺; á„Žá…³á‡; ) HANGUL SYLLABLE CEUP
+CE3B;CE3B;110E 1173 11C2;CE3B;110E 1173 11C2; # (츻; 츻; 츻; 츻; 츻; ) HANGUL SYLLABLE CEUH
+CE3C;CE3C;110E 1174;CE3C;110E 1174; # (츼; 츼; 츼; 츼; 츼; ) HANGUL SYLLABLE CYI
+CE3D;CE3D;110E 1174 11A8;CE3D;110E 1174 11A8; # (츽; 츽; 츽; 츽; 츽; ) HANGUL SYLLABLE CYIG
+CE3E;CE3E;110E 1174 11A9;CE3E;110E 1174 11A9; # (츾; 츾; 츾; 츾; 츾; ) HANGUL SYLLABLE CYIGG
+CE3F;CE3F;110E 1174 11AA;CE3F;110E 1174 11AA; # (츿; 츿; 츿; 츿; 츿; ) HANGUL SYLLABLE CYIGS
+CE40;CE40;110E 1174 11AB;CE40;110E 1174 11AB; # (칀; 칀; 칀; 칀; 칀; ) HANGUL SYLLABLE CYIN
+CE41;CE41;110E 1174 11AC;CE41;110E 1174 11AC; # (ì¹; ì¹; 칁; ì¹; 칁; ) HANGUL SYLLABLE CYINJ
+CE42;CE42;110E 1174 11AD;CE42;110E 1174 11AD; # (칂; 칂; 칂; 칂; 칂; ) HANGUL SYLLABLE CYINH
+CE43;CE43;110E 1174 11AE;CE43;110E 1174 11AE; # (칃; 칃; 칃; 칃; 칃; ) HANGUL SYLLABLE CYID
+CE44;CE44;110E 1174 11AF;CE44;110E 1174 11AF; # (칄; 칄; 칄; 칄; 칄; ) HANGUL SYLLABLE CYIL
+CE45;CE45;110E 1174 11B0;CE45;110E 1174 11B0; # (칅; 칅; 칅; 칅; 칅; ) HANGUL SYLLABLE CYILG
+CE46;CE46;110E 1174 11B1;CE46;110E 1174 11B1; # (칆; 칆; 칆; 칆; 칆; ) HANGUL SYLLABLE CYILM
+CE47;CE47;110E 1174 11B2;CE47;110E 1174 11B2; # (칇; 칇; 칇; 칇; 칇; ) HANGUL SYLLABLE CYILB
+CE48;CE48;110E 1174 11B3;CE48;110E 1174 11B3; # (칈; 칈; 칈; 칈; 칈; ) HANGUL SYLLABLE CYILS
+CE49;CE49;110E 1174 11B4;CE49;110E 1174 11B4; # (칉; 칉; 칉; 칉; 칉; ) HANGUL SYLLABLE CYILT
+CE4A;CE4A;110E 1174 11B5;CE4A;110E 1174 11B5; # (칊; 칊; 칊; 칊; 칊; ) HANGUL SYLLABLE CYILP
+CE4B;CE4B;110E 1174 11B6;CE4B;110E 1174 11B6; # (칋; 칋; 칋; 칋; 칋; ) HANGUL SYLLABLE CYILH
+CE4C;CE4C;110E 1174 11B7;CE4C;110E 1174 11B7; # (칌; 칌; 칌; 칌; 칌; ) HANGUL SYLLABLE CYIM
+CE4D;CE4D;110E 1174 11B8;CE4D;110E 1174 11B8; # (ì¹; ì¹; 칍; ì¹; 칍; ) HANGUL SYLLABLE CYIB
+CE4E;CE4E;110E 1174 11B9;CE4E;110E 1174 11B9; # (칎; 칎; 칎; 칎; 칎; ) HANGUL SYLLABLE CYIBS
+CE4F;CE4F;110E 1174 11BA;CE4F;110E 1174 11BA; # (ì¹; ì¹; 칏; ì¹; 칏; ) HANGUL SYLLABLE CYIS
+CE50;CE50;110E 1174 11BB;CE50;110E 1174 11BB; # (ì¹; ì¹; 칐; ì¹; 칐; ) HANGUL SYLLABLE CYISS
+CE51;CE51;110E 1174 11BC;CE51;110E 1174 11BC; # (칑; 칑; 칑; 칑; 칑; ) HANGUL SYLLABLE CYING
+CE52;CE52;110E 1174 11BD;CE52;110E 1174 11BD; # (칒; 칒; 칒; 칒; 칒; ) HANGUL SYLLABLE CYIJ
+CE53;CE53;110E 1174 11BE;CE53;110E 1174 11BE; # (칓; 칓; 칓; 칓; 칓; ) HANGUL SYLLABLE CYIC
+CE54;CE54;110E 1174 11BF;CE54;110E 1174 11BF; # (칔; 칔; 칔; 칔; 칔; ) HANGUL SYLLABLE CYIK
+CE55;CE55;110E 1174 11C0;CE55;110E 1174 11C0; # (칕; 칕; 칕; 칕; 칕; ) HANGUL SYLLABLE CYIT
+CE56;CE56;110E 1174 11C1;CE56;110E 1174 11C1; # (ì¹–; ì¹–; á„Žá…´á‡; ì¹–; á„Žá…´á‡; ) HANGUL SYLLABLE CYIP
+CE57;CE57;110E 1174 11C2;CE57;110E 1174 11C2; # (칗; 칗; 칗; 칗; 칗; ) HANGUL SYLLABLE CYIH
+CE58;CE58;110E 1175;CE58;110E 1175; # (치; 치; 치; 치; 치; ) HANGUL SYLLABLE CI
+CE59;CE59;110E 1175 11A8;CE59;110E 1175 11A8; # (칙; 칙; 칙; 칙; 칙; ) HANGUL SYLLABLE CIG
+CE5A;CE5A;110E 1175 11A9;CE5A;110E 1175 11A9; # (칚; 칚; 칚; 칚; 칚; ) HANGUL SYLLABLE CIGG
+CE5B;CE5B;110E 1175 11AA;CE5B;110E 1175 11AA; # (칛; 칛; 칛; 칛; 칛; ) HANGUL SYLLABLE CIGS
+CE5C;CE5C;110E 1175 11AB;CE5C;110E 1175 11AB; # (친; 친; 친; 친; 친; ) HANGUL SYLLABLE CIN
+CE5D;CE5D;110E 1175 11AC;CE5D;110E 1175 11AC; # (ì¹; ì¹; 칝; ì¹; 칝; ) HANGUL SYLLABLE CINJ
+CE5E;CE5E;110E 1175 11AD;CE5E;110E 1175 11AD; # (칞; 칞; 칞; 칞; 칞; ) HANGUL SYLLABLE CINH
+CE5F;CE5F;110E 1175 11AE;CE5F;110E 1175 11AE; # (칟; 칟; 칟; 칟; 칟; ) HANGUL SYLLABLE CID
+CE60;CE60;110E 1175 11AF;CE60;110E 1175 11AF; # (칠; 칠; 칠; 칠; 칠; ) HANGUL SYLLABLE CIL
+CE61;CE61;110E 1175 11B0;CE61;110E 1175 11B0; # (칡; 칡; 칡; 칡; 칡; ) HANGUL SYLLABLE CILG
+CE62;CE62;110E 1175 11B1;CE62;110E 1175 11B1; # (칢; 칢; 칢; 칢; 칢; ) HANGUL SYLLABLE CILM
+CE63;CE63;110E 1175 11B2;CE63;110E 1175 11B2; # (칣; 칣; 칣; 칣; 칣; ) HANGUL SYLLABLE CILB
+CE64;CE64;110E 1175 11B3;CE64;110E 1175 11B3; # (칤; 칤; 칤; 칤; 칤; ) HANGUL SYLLABLE CILS
+CE65;CE65;110E 1175 11B4;CE65;110E 1175 11B4; # (칥; 칥; 칥; 칥; 칥; ) HANGUL SYLLABLE CILT
+CE66;CE66;110E 1175 11B5;CE66;110E 1175 11B5; # (칦; 칦; 칦; 칦; 칦; ) HANGUL SYLLABLE CILP
+CE67;CE67;110E 1175 11B6;CE67;110E 1175 11B6; # (칧; 칧; 칧; 칧; 칧; ) HANGUL SYLLABLE CILH
+CE68;CE68;110E 1175 11B7;CE68;110E 1175 11B7; # (침; 침; 침; 침; 침; ) HANGUL SYLLABLE CIM
+CE69;CE69;110E 1175 11B8;CE69;110E 1175 11B8; # (칩; 칩; 칩; 칩; 칩; ) HANGUL SYLLABLE CIB
+CE6A;CE6A;110E 1175 11B9;CE6A;110E 1175 11B9; # (칪; 칪; 칪; 칪; 칪; ) HANGUL SYLLABLE CIBS
+CE6B;CE6B;110E 1175 11BA;CE6B;110E 1175 11BA; # (칫; 칫; 칫; 칫; 칫; ) HANGUL SYLLABLE CIS
+CE6C;CE6C;110E 1175 11BB;CE6C;110E 1175 11BB; # (칬; 칬; 칬; 칬; 칬; ) HANGUL SYLLABLE CISS
+CE6D;CE6D;110E 1175 11BC;CE6D;110E 1175 11BC; # (칭; 칭; 칭; 칭; 칭; ) HANGUL SYLLABLE CING
+CE6E;CE6E;110E 1175 11BD;CE6E;110E 1175 11BD; # (칮; 칮; 칮; 칮; 칮; ) HANGUL SYLLABLE CIJ
+CE6F;CE6F;110E 1175 11BE;CE6F;110E 1175 11BE; # (칯; 칯; 칯; 칯; 칯; ) HANGUL SYLLABLE CIC
+CE70;CE70;110E 1175 11BF;CE70;110E 1175 11BF; # (칰; 칰; 칰; 칰; 칰; ) HANGUL SYLLABLE CIK
+CE71;CE71;110E 1175 11C0;CE71;110E 1175 11C0; # (칱; 칱; 칱; 칱; 칱; ) HANGUL SYLLABLE CIT
+CE72;CE72;110E 1175 11C1;CE72;110E 1175 11C1; # (ì¹²; ì¹²; á„Žá…µá‡; ì¹²; á„Žá…µá‡; ) HANGUL SYLLABLE CIP
+CE73;CE73;110E 1175 11C2;CE73;110E 1175 11C2; # (칳; 칳; 칳; 칳; 칳; ) HANGUL SYLLABLE CIH
+CE74;CE74;110F 1161;CE74;110F 1161; # (ì¹´; ì¹´; á„á…¡; ì¹´; á„á…¡; ) HANGUL SYLLABLE KA
+CE75;CE75;110F 1161 11A8;CE75;110F 1161 11A8; # (ì¹µ; ì¹µ; á„ᅡᆨ; ì¹µ; á„ᅡᆨ; ) HANGUL SYLLABLE KAG
+CE76;CE76;110F 1161 11A9;CE76;110F 1161 11A9; # (칶; 칶; á„ᅡᆩ; 칶; á„ᅡᆩ; ) HANGUL SYLLABLE KAGG
+CE77;CE77;110F 1161 11AA;CE77;110F 1161 11AA; # (ì¹·; ì¹·; á„ᅡᆪ; ì¹·; á„ᅡᆪ; ) HANGUL SYLLABLE KAGS
+CE78;CE78;110F 1161 11AB;CE78;110F 1161 11AB; # (칸; 칸; á„ᅡᆫ; 칸; á„ᅡᆫ; ) HANGUL SYLLABLE KAN
+CE79;CE79;110F 1161 11AC;CE79;110F 1161 11AC; # (ì¹¹; ì¹¹; á„ᅡᆬ; ì¹¹; á„ᅡᆬ; ) HANGUL SYLLABLE KANJ
+CE7A;CE7A;110F 1161 11AD;CE7A;110F 1161 11AD; # (칺; 칺; á„ᅡᆭ; 칺; á„ᅡᆭ; ) HANGUL SYLLABLE KANH
+CE7B;CE7B;110F 1161 11AE;CE7B;110F 1161 11AE; # (ì¹»; ì¹»; á„ᅡᆮ; ì¹»; á„ᅡᆮ; ) HANGUL SYLLABLE KAD
+CE7C;CE7C;110F 1161 11AF;CE7C;110F 1161 11AF; # (ì¹¼; ì¹¼; á„ᅡᆯ; ì¹¼; á„ᅡᆯ; ) HANGUL SYLLABLE KAL
+CE7D;CE7D;110F 1161 11B0;CE7D;110F 1161 11B0; # (ì¹½; ì¹½; á„ᅡᆰ; ì¹½; á„ᅡᆰ; ) HANGUL SYLLABLE KALG
+CE7E;CE7E;110F 1161 11B1;CE7E;110F 1161 11B1; # (ì¹¾; ì¹¾; á„ᅡᆱ; ì¹¾; á„ᅡᆱ; ) HANGUL SYLLABLE KALM
+CE7F;CE7F;110F 1161 11B2;CE7F;110F 1161 11B2; # (칿; 칿; á„ᅡᆲ; 칿; á„ᅡᆲ; ) HANGUL SYLLABLE KALB
+CE80;CE80;110F 1161 11B3;CE80;110F 1161 11B3; # (캀; 캀; á„ᅡᆳ; 캀; á„ᅡᆳ; ) HANGUL SYLLABLE KALS
+CE81;CE81;110F 1161 11B4;CE81;110F 1161 11B4; # (ìº; ìº; á„ᅡᆴ; ìº; á„ᅡᆴ; ) HANGUL SYLLABLE KALT
+CE82;CE82;110F 1161 11B5;CE82;110F 1161 11B5; # (캂; 캂; á„ᅡᆵ; 캂; á„ᅡᆵ; ) HANGUL SYLLABLE KALP
+CE83;CE83;110F 1161 11B6;CE83;110F 1161 11B6; # (캃; 캃; á„ᅡᆶ; 캃; á„ᅡᆶ; ) HANGUL SYLLABLE KALH
+CE84;CE84;110F 1161 11B7;CE84;110F 1161 11B7; # (캄; 캄; á„ᅡᆷ; 캄; á„ᅡᆷ; ) HANGUL SYLLABLE KAM
+CE85;CE85;110F 1161 11B8;CE85;110F 1161 11B8; # (캅; 캅; á„ᅡᆸ; 캅; á„ᅡᆸ; ) HANGUL SYLLABLE KAB
+CE86;CE86;110F 1161 11B9;CE86;110F 1161 11B9; # (캆; 캆; á„ᅡᆹ; 캆; á„ᅡᆹ; ) HANGUL SYLLABLE KABS
+CE87;CE87;110F 1161 11BA;CE87;110F 1161 11BA; # (캇; 캇; á„ᅡᆺ; 캇; á„ᅡᆺ; ) HANGUL SYLLABLE KAS
+CE88;CE88;110F 1161 11BB;CE88;110F 1161 11BB; # (캈; 캈; á„ᅡᆻ; 캈; á„ᅡᆻ; ) HANGUL SYLLABLE KASS
+CE89;CE89;110F 1161 11BC;CE89;110F 1161 11BC; # (캉; 캉; á„ᅡᆼ; 캉; á„ᅡᆼ; ) HANGUL SYLLABLE KANG
+CE8A;CE8A;110F 1161 11BD;CE8A;110F 1161 11BD; # (캊; 캊; á„ᅡᆽ; 캊; á„ᅡᆽ; ) HANGUL SYLLABLE KAJ
+CE8B;CE8B;110F 1161 11BE;CE8B;110F 1161 11BE; # (캋; 캋; á„ᅡᆾ; 캋; á„ᅡᆾ; ) HANGUL SYLLABLE KAC
+CE8C;CE8C;110F 1161 11BF;CE8C;110F 1161 11BF; # (캌; 캌; á„ᅡᆿ; 캌; á„ᅡᆿ; ) HANGUL SYLLABLE KAK
+CE8D;CE8D;110F 1161 11C0;CE8D;110F 1161 11C0; # (ìº; ìº; á„ᅡᇀ; ìº; á„ᅡᇀ; ) HANGUL SYLLABLE KAT
+CE8E;CE8E;110F 1161 11C1;CE8E;110F 1161 11C1; # (캎; 캎; á„á…¡á‡; 캎; á„á…¡á‡; ) HANGUL SYLLABLE KAP
+CE8F;CE8F;110F 1161 11C2;CE8F;110F 1161 11C2; # (ìº; ìº; á„ᅡᇂ; ìº; á„ᅡᇂ; ) HANGUL SYLLABLE KAH
+CE90;CE90;110F 1162;CE90;110F 1162; # (ìº; ìº; á„á…¢; ìº; á„á…¢; ) HANGUL SYLLABLE KAE
+CE91;CE91;110F 1162 11A8;CE91;110F 1162 11A8; # (캑; 캑; á„ᅢᆨ; 캑; á„ᅢᆨ; ) HANGUL SYLLABLE KAEG
+CE92;CE92;110F 1162 11A9;CE92;110F 1162 11A9; # (캒; 캒; á„ᅢᆩ; 캒; á„ᅢᆩ; ) HANGUL SYLLABLE KAEGG
+CE93;CE93;110F 1162 11AA;CE93;110F 1162 11AA; # (캓; 캓; á„ᅢᆪ; 캓; á„ᅢᆪ; ) HANGUL SYLLABLE KAEGS
+CE94;CE94;110F 1162 11AB;CE94;110F 1162 11AB; # (캔; 캔; á„ᅢᆫ; 캔; á„ᅢᆫ; ) HANGUL SYLLABLE KAEN
+CE95;CE95;110F 1162 11AC;CE95;110F 1162 11AC; # (캕; 캕; á„ᅢᆬ; 캕; á„ᅢᆬ; ) HANGUL SYLLABLE KAENJ
+CE96;CE96;110F 1162 11AD;CE96;110F 1162 11AD; # (캖; 캖; á„ᅢᆭ; 캖; á„ᅢᆭ; ) HANGUL SYLLABLE KAENH
+CE97;CE97;110F 1162 11AE;CE97;110F 1162 11AE; # (캗; 캗; á„ᅢᆮ; 캗; á„ᅢᆮ; ) HANGUL SYLLABLE KAED
+CE98;CE98;110F 1162 11AF;CE98;110F 1162 11AF; # (캘; 캘; á„ᅢᆯ; 캘; á„ᅢᆯ; ) HANGUL SYLLABLE KAEL
+CE99;CE99;110F 1162 11B0;CE99;110F 1162 11B0; # (캙; 캙; á„ᅢᆰ; 캙; á„ᅢᆰ; ) HANGUL SYLLABLE KAELG
+CE9A;CE9A;110F 1162 11B1;CE9A;110F 1162 11B1; # (캚; 캚; á„ᅢᆱ; 캚; á„ᅢᆱ; ) HANGUL SYLLABLE KAELM
+CE9B;CE9B;110F 1162 11B2;CE9B;110F 1162 11B2; # (캛; 캛; á„ᅢᆲ; 캛; á„ᅢᆲ; ) HANGUL SYLLABLE KAELB
+CE9C;CE9C;110F 1162 11B3;CE9C;110F 1162 11B3; # (캜; 캜; á„ᅢᆳ; 캜; á„ᅢᆳ; ) HANGUL SYLLABLE KAELS
+CE9D;CE9D;110F 1162 11B4;CE9D;110F 1162 11B4; # (ìº; ìº; á„ᅢᆴ; ìº; á„ᅢᆴ; ) HANGUL SYLLABLE KAELT
+CE9E;CE9E;110F 1162 11B5;CE9E;110F 1162 11B5; # (캞; 캞; á„ᅢᆵ; 캞; á„ᅢᆵ; ) HANGUL SYLLABLE KAELP
+CE9F;CE9F;110F 1162 11B6;CE9F;110F 1162 11B6; # (캟; 캟; á„ᅢᆶ; 캟; á„ᅢᆶ; ) HANGUL SYLLABLE KAELH
+CEA0;CEA0;110F 1162 11B7;CEA0;110F 1162 11B7; # (캠; 캠; á„ᅢᆷ; 캠; á„ᅢᆷ; ) HANGUL SYLLABLE KAEM
+CEA1;CEA1;110F 1162 11B8;CEA1;110F 1162 11B8; # (캡; 캡; á„ᅢᆸ; 캡; á„ᅢᆸ; ) HANGUL SYLLABLE KAEB
+CEA2;CEA2;110F 1162 11B9;CEA2;110F 1162 11B9; # (캢; 캢; á„ᅢᆹ; 캢; á„ᅢᆹ; ) HANGUL SYLLABLE KAEBS
+CEA3;CEA3;110F 1162 11BA;CEA3;110F 1162 11BA; # (캣; 캣; á„ᅢᆺ; 캣; á„ᅢᆺ; ) HANGUL SYLLABLE KAES
+CEA4;CEA4;110F 1162 11BB;CEA4;110F 1162 11BB; # (캤; 캤; á„ᅢᆻ; 캤; á„ᅢᆻ; ) HANGUL SYLLABLE KAESS
+CEA5;CEA5;110F 1162 11BC;CEA5;110F 1162 11BC; # (캥; 캥; á„ᅢᆼ; 캥; á„ᅢᆼ; ) HANGUL SYLLABLE KAENG
+CEA6;CEA6;110F 1162 11BD;CEA6;110F 1162 11BD; # (캦; 캦; á„ᅢᆽ; 캦; á„ᅢᆽ; ) HANGUL SYLLABLE KAEJ
+CEA7;CEA7;110F 1162 11BE;CEA7;110F 1162 11BE; # (캧; 캧; á„ᅢᆾ; 캧; á„ᅢᆾ; ) HANGUL SYLLABLE KAEC
+CEA8;CEA8;110F 1162 11BF;CEA8;110F 1162 11BF; # (캨; 캨; á„ᅢᆿ; 캨; á„ᅢᆿ; ) HANGUL SYLLABLE KAEK
+CEA9;CEA9;110F 1162 11C0;CEA9;110F 1162 11C0; # (캩; 캩; á„ᅢᇀ; 캩; á„ᅢᇀ; ) HANGUL SYLLABLE KAET
+CEAA;CEAA;110F 1162 11C1;CEAA;110F 1162 11C1; # (캪; 캪; á„á…¢á‡; 캪; á„á…¢á‡; ) HANGUL SYLLABLE KAEP
+CEAB;CEAB;110F 1162 11C2;CEAB;110F 1162 11C2; # (캫; 캫; á„ᅢᇂ; 캫; á„ᅢᇂ; ) HANGUL SYLLABLE KAEH
+CEAC;CEAC;110F 1163;CEAC;110F 1163; # (캬; 캬; á„á…£; 캬; á„á…£; ) HANGUL SYLLABLE KYA
+CEAD;CEAD;110F 1163 11A8;CEAD;110F 1163 11A8; # (캭; 캭; á„ᅣᆨ; 캭; á„ᅣᆨ; ) HANGUL SYLLABLE KYAG
+CEAE;CEAE;110F 1163 11A9;CEAE;110F 1163 11A9; # (캮; 캮; á„ᅣᆩ; 캮; á„ᅣᆩ; ) HANGUL SYLLABLE KYAGG
+CEAF;CEAF;110F 1163 11AA;CEAF;110F 1163 11AA; # (캯; 캯; á„ᅣᆪ; 캯; á„ᅣᆪ; ) HANGUL SYLLABLE KYAGS
+CEB0;CEB0;110F 1163 11AB;CEB0;110F 1163 11AB; # (캰; 캰; á„ᅣᆫ; 캰; á„ᅣᆫ; ) HANGUL SYLLABLE KYAN
+CEB1;CEB1;110F 1163 11AC;CEB1;110F 1163 11AC; # (캱; 캱; á„ᅣᆬ; 캱; á„ᅣᆬ; ) HANGUL SYLLABLE KYANJ
+CEB2;CEB2;110F 1163 11AD;CEB2;110F 1163 11AD; # (캲; 캲; á„ᅣᆭ; 캲; á„ᅣᆭ; ) HANGUL SYLLABLE KYANH
+CEB3;CEB3;110F 1163 11AE;CEB3;110F 1163 11AE; # (캳; 캳; á„ᅣᆮ; 캳; á„ᅣᆮ; ) HANGUL SYLLABLE KYAD
+CEB4;CEB4;110F 1163 11AF;CEB4;110F 1163 11AF; # (캴; 캴; á„ᅣᆯ; 캴; á„ᅣᆯ; ) HANGUL SYLLABLE KYAL
+CEB5;CEB5;110F 1163 11B0;CEB5;110F 1163 11B0; # (캵; 캵; á„ᅣᆰ; 캵; á„ᅣᆰ; ) HANGUL SYLLABLE KYALG
+CEB6;CEB6;110F 1163 11B1;CEB6;110F 1163 11B1; # (캶; 캶; á„ᅣᆱ; 캶; á„ᅣᆱ; ) HANGUL SYLLABLE KYALM
+CEB7;CEB7;110F 1163 11B2;CEB7;110F 1163 11B2; # (캷; 캷; á„ᅣᆲ; 캷; á„ᅣᆲ; ) HANGUL SYLLABLE KYALB
+CEB8;CEB8;110F 1163 11B3;CEB8;110F 1163 11B3; # (캸; 캸; á„ᅣᆳ; 캸; á„ᅣᆳ; ) HANGUL SYLLABLE KYALS
+CEB9;CEB9;110F 1163 11B4;CEB9;110F 1163 11B4; # (캹; 캹; á„ᅣᆴ; 캹; á„ᅣᆴ; ) HANGUL SYLLABLE KYALT
+CEBA;CEBA;110F 1163 11B5;CEBA;110F 1163 11B5; # (캺; 캺; á„ᅣᆵ; 캺; á„ᅣᆵ; ) HANGUL SYLLABLE KYALP
+CEBB;CEBB;110F 1163 11B6;CEBB;110F 1163 11B6; # (캻; 캻; á„ᅣᆶ; 캻; á„ᅣᆶ; ) HANGUL SYLLABLE KYALH
+CEBC;CEBC;110F 1163 11B7;CEBC;110F 1163 11B7; # (캼; 캼; á„ᅣᆷ; 캼; á„ᅣᆷ; ) HANGUL SYLLABLE KYAM
+CEBD;CEBD;110F 1163 11B8;CEBD;110F 1163 11B8; # (캽; 캽; á„ᅣᆸ; 캽; á„ᅣᆸ; ) HANGUL SYLLABLE KYAB
+CEBE;CEBE;110F 1163 11B9;CEBE;110F 1163 11B9; # (캾; 캾; á„ᅣᆹ; 캾; á„ᅣᆹ; ) HANGUL SYLLABLE KYABS
+CEBF;CEBF;110F 1163 11BA;CEBF;110F 1163 11BA; # (캿; 캿; á„ᅣᆺ; 캿; á„ᅣᆺ; ) HANGUL SYLLABLE KYAS
+CEC0;CEC0;110F 1163 11BB;CEC0;110F 1163 11BB; # (컀; 컀; á„ᅣᆻ; 컀; á„ᅣᆻ; ) HANGUL SYLLABLE KYASS
+CEC1;CEC1;110F 1163 11BC;CEC1;110F 1163 11BC; # (ì»; ì»; á„ᅣᆼ; ì»; á„ᅣᆼ; ) HANGUL SYLLABLE KYANG
+CEC2;CEC2;110F 1163 11BD;CEC2;110F 1163 11BD; # (컂; 컂; á„ᅣᆽ; 컂; á„ᅣᆽ; ) HANGUL SYLLABLE KYAJ
+CEC3;CEC3;110F 1163 11BE;CEC3;110F 1163 11BE; # (컃; 컃; á„ᅣᆾ; 컃; á„ᅣᆾ; ) HANGUL SYLLABLE KYAC
+CEC4;CEC4;110F 1163 11BF;CEC4;110F 1163 11BF; # (컄; 컄; á„ᅣᆿ; 컄; á„ᅣᆿ; ) HANGUL SYLLABLE KYAK
+CEC5;CEC5;110F 1163 11C0;CEC5;110F 1163 11C0; # (ì»…; ì»…; á„ᅣᇀ; ì»…; á„ᅣᇀ; ) HANGUL SYLLABLE KYAT
+CEC6;CEC6;110F 1163 11C1;CEC6;110F 1163 11C1; # (컆; 컆; á„á…£á‡; 컆; á„á…£á‡; ) HANGUL SYLLABLE KYAP
+CEC7;CEC7;110F 1163 11C2;CEC7;110F 1163 11C2; # (컇; 컇; á„ᅣᇂ; 컇; á„ᅣᇂ; ) HANGUL SYLLABLE KYAH
+CEC8;CEC8;110F 1164;CEC8;110F 1164; # (컈; 컈; á„á…¤; 컈; á„á…¤; ) HANGUL SYLLABLE KYAE
+CEC9;CEC9;110F 1164 11A8;CEC9;110F 1164 11A8; # (컉; 컉; á„ᅤᆨ; 컉; á„ᅤᆨ; ) HANGUL SYLLABLE KYAEG
+CECA;CECA;110F 1164 11A9;CECA;110F 1164 11A9; # (컊; 컊; á„ᅤᆩ; 컊; á„ᅤᆩ; ) HANGUL SYLLABLE KYAEGG
+CECB;CECB;110F 1164 11AA;CECB;110F 1164 11AA; # (컋; 컋; á„ᅤᆪ; 컋; á„ᅤᆪ; ) HANGUL SYLLABLE KYAEGS
+CECC;CECC;110F 1164 11AB;CECC;110F 1164 11AB; # (컌; 컌; á„ᅤᆫ; 컌; á„ᅤᆫ; ) HANGUL SYLLABLE KYAEN
+CECD;CECD;110F 1164 11AC;CECD;110F 1164 11AC; # (ì»; ì»; á„ᅤᆬ; ì»; á„ᅤᆬ; ) HANGUL SYLLABLE KYAENJ
+CECE;CECE;110F 1164 11AD;CECE;110F 1164 11AD; # (컎; 컎; á„ᅤᆭ; 컎; á„ᅤᆭ; ) HANGUL SYLLABLE KYAENH
+CECF;CECF;110F 1164 11AE;CECF;110F 1164 11AE; # (ì»; ì»; á„ᅤᆮ; ì»; á„ᅤᆮ; ) HANGUL SYLLABLE KYAED
+CED0;CED0;110F 1164 11AF;CED0;110F 1164 11AF; # (ì»; ì»; á„ᅤᆯ; ì»; á„ᅤᆯ; ) HANGUL SYLLABLE KYAEL
+CED1;CED1;110F 1164 11B0;CED1;110F 1164 11B0; # (컑; 컑; á„ᅤᆰ; 컑; á„ᅤᆰ; ) HANGUL SYLLABLE KYAELG
+CED2;CED2;110F 1164 11B1;CED2;110F 1164 11B1; # (ì»’; ì»’; á„ᅤᆱ; ì»’; á„ᅤᆱ; ) HANGUL SYLLABLE KYAELM
+CED3;CED3;110F 1164 11B2;CED3;110F 1164 11B2; # (컓; 컓; á„ᅤᆲ; 컓; á„ᅤᆲ; ) HANGUL SYLLABLE KYAELB
+CED4;CED4;110F 1164 11B3;CED4;110F 1164 11B3; # (ì»”; ì»”; á„ᅤᆳ; ì»”; á„ᅤᆳ; ) HANGUL SYLLABLE KYAELS
+CED5;CED5;110F 1164 11B4;CED5;110F 1164 11B4; # (컕; 컕; á„ᅤᆴ; 컕; á„ᅤᆴ; ) HANGUL SYLLABLE KYAELT
+CED6;CED6;110F 1164 11B5;CED6;110F 1164 11B5; # (ì»–; ì»–; á„ᅤᆵ; ì»–; á„ᅤᆵ; ) HANGUL SYLLABLE KYAELP
+CED7;CED7;110F 1164 11B6;CED7;110F 1164 11B6; # (ì»—; ì»—; á„ᅤᆶ; ì»—; á„ᅤᆶ; ) HANGUL SYLLABLE KYAELH
+CED8;CED8;110F 1164 11B7;CED8;110F 1164 11B7; # (컘; 컘; á„ᅤᆷ; 컘; á„ᅤᆷ; ) HANGUL SYLLABLE KYAEM
+CED9;CED9;110F 1164 11B8;CED9;110F 1164 11B8; # (ì»™; ì»™; á„ᅤᆸ; ì»™; á„ᅤᆸ; ) HANGUL SYLLABLE KYAEB
+CEDA;CEDA;110F 1164 11B9;CEDA;110F 1164 11B9; # (컚; 컚; á„ᅤᆹ; 컚; á„ᅤᆹ; ) HANGUL SYLLABLE KYAEBS
+CEDB;CEDB;110F 1164 11BA;CEDB;110F 1164 11BA; # (ì»›; ì»›; á„ᅤᆺ; ì»›; á„ᅤᆺ; ) HANGUL SYLLABLE KYAES
+CEDC;CEDC;110F 1164 11BB;CEDC;110F 1164 11BB; # (컜; 컜; á„ᅤᆻ; 컜; á„ᅤᆻ; ) HANGUL SYLLABLE KYAESS
+CEDD;CEDD;110F 1164 11BC;CEDD;110F 1164 11BC; # (ì»; ì»; á„ᅤᆼ; ì»; á„ᅤᆼ; ) HANGUL SYLLABLE KYAENG
+CEDE;CEDE;110F 1164 11BD;CEDE;110F 1164 11BD; # (컞; 컞; á„ᅤᆽ; 컞; á„ᅤᆽ; ) HANGUL SYLLABLE KYAEJ
+CEDF;CEDF;110F 1164 11BE;CEDF;110F 1164 11BE; # (컟; 컟; á„ᅤᆾ; 컟; á„ᅤᆾ; ) HANGUL SYLLABLE KYAEC
+CEE0;CEE0;110F 1164 11BF;CEE0;110F 1164 11BF; # (ì» ; ì» ; á„ᅤᆿ; ì» ; á„ᅤᆿ; ) HANGUL SYLLABLE KYAEK
+CEE1;CEE1;110F 1164 11C0;CEE1;110F 1164 11C0; # (컡; 컡; á„ᅤᇀ; 컡; á„ᅤᇀ; ) HANGUL SYLLABLE KYAET
+CEE2;CEE2;110F 1164 11C1;CEE2;110F 1164 11C1; # (컢; 컢; á„á…¤á‡; 컢; á„á…¤á‡; ) HANGUL SYLLABLE KYAEP
+CEE3;CEE3;110F 1164 11C2;CEE3;110F 1164 11C2; # (컣; 컣; á„ᅤᇂ; 컣; á„ᅤᇂ; ) HANGUL SYLLABLE KYAEH
+CEE4;CEE4;110F 1165;CEE4;110F 1165; # (커; 커; á„á…¥; 커; á„á…¥; ) HANGUL SYLLABLE KEO
+CEE5;CEE5;110F 1165 11A8;CEE5;110F 1165 11A8; # (컥; 컥; á„ᅥᆨ; 컥; á„ᅥᆨ; ) HANGUL SYLLABLE KEOG
+CEE6;CEE6;110F 1165 11A9;CEE6;110F 1165 11A9; # (컦; 컦; á„ᅥᆩ; 컦; á„ᅥᆩ; ) HANGUL SYLLABLE KEOGG
+CEE7;CEE7;110F 1165 11AA;CEE7;110F 1165 11AA; # (컧; 컧; á„ᅥᆪ; 컧; á„ᅥᆪ; ) HANGUL SYLLABLE KEOGS
+CEE8;CEE8;110F 1165 11AB;CEE8;110F 1165 11AB; # (컨; 컨; á„ᅥᆫ; 컨; á„ᅥᆫ; ) HANGUL SYLLABLE KEON
+CEE9;CEE9;110F 1165 11AC;CEE9;110F 1165 11AC; # (컩; 컩; á„ᅥᆬ; 컩; á„ᅥᆬ; ) HANGUL SYLLABLE KEONJ
+CEEA;CEEA;110F 1165 11AD;CEEA;110F 1165 11AD; # (컪; 컪; á„ᅥᆭ; 컪; á„ᅥᆭ; ) HANGUL SYLLABLE KEONH
+CEEB;CEEB;110F 1165 11AE;CEEB;110F 1165 11AE; # (컫; 컫; á„ᅥᆮ; 컫; á„ᅥᆮ; ) HANGUL SYLLABLE KEOD
+CEEC;CEEC;110F 1165 11AF;CEEC;110F 1165 11AF; # (컬; 컬; á„ᅥᆯ; 컬; á„ᅥᆯ; ) HANGUL SYLLABLE KEOL
+CEED;CEED;110F 1165 11B0;CEED;110F 1165 11B0; # (ì»­; ì»­; á„ᅥᆰ; ì»­; á„ᅥᆰ; ) HANGUL SYLLABLE KEOLG
+CEEE;CEEE;110F 1165 11B1;CEEE;110F 1165 11B1; # (ì»®; ì»®; á„ᅥᆱ; ì»®; á„ᅥᆱ; ) HANGUL SYLLABLE KEOLM
+CEEF;CEEF;110F 1165 11B2;CEEF;110F 1165 11B2; # (컯; 컯; á„ᅥᆲ; 컯; á„ᅥᆲ; ) HANGUL SYLLABLE KEOLB
+CEF0;CEF0;110F 1165 11B3;CEF0;110F 1165 11B3; # (ì»°; ì»°; á„ᅥᆳ; ì»°; á„ᅥᆳ; ) HANGUL SYLLABLE KEOLS
+CEF1;CEF1;110F 1165 11B4;CEF1;110F 1165 11B4; # (ì»±; ì»±; á„ᅥᆴ; ì»±; á„ᅥᆴ; ) HANGUL SYLLABLE KEOLT
+CEF2;CEF2;110F 1165 11B5;CEF2;110F 1165 11B5; # (컲; 컲; á„ᅥᆵ; 컲; á„ᅥᆵ; ) HANGUL SYLLABLE KEOLP
+CEF3;CEF3;110F 1165 11B6;CEF3;110F 1165 11B6; # (컳; 컳; á„ᅥᆶ; 컳; á„ᅥᆶ; ) HANGUL SYLLABLE KEOLH
+CEF4;CEF4;110F 1165 11B7;CEF4;110F 1165 11B7; # (ì»´; ì»´; á„ᅥᆷ; ì»´; á„ᅥᆷ; ) HANGUL SYLLABLE KEOM
+CEF5;CEF5;110F 1165 11B8;CEF5;110F 1165 11B8; # (컵; 컵; á„ᅥᆸ; 컵; á„ᅥᆸ; ) HANGUL SYLLABLE KEOB
+CEF6;CEF6;110F 1165 11B9;CEF6;110F 1165 11B9; # (컶; 컶; á„ᅥᆹ; 컶; á„ᅥᆹ; ) HANGUL SYLLABLE KEOBS
+CEF7;CEF7;110F 1165 11BA;CEF7;110F 1165 11BA; # (ì»·; ì»·; á„ᅥᆺ; ì»·; á„ᅥᆺ; ) HANGUL SYLLABLE KEOS
+CEF8;CEF8;110F 1165 11BB;CEF8;110F 1165 11BB; # (컸; 컸; á„ᅥᆻ; 컸; á„ᅥᆻ; ) HANGUL SYLLABLE KEOSS
+CEF9;CEF9;110F 1165 11BC;CEF9;110F 1165 11BC; # (컹; 컹; á„ᅥᆼ; 컹; á„ᅥᆼ; ) HANGUL SYLLABLE KEONG
+CEFA;CEFA;110F 1165 11BD;CEFA;110F 1165 11BD; # (컺; 컺; á„ᅥᆽ; 컺; á„ᅥᆽ; ) HANGUL SYLLABLE KEOJ
+CEFB;CEFB;110F 1165 11BE;CEFB;110F 1165 11BE; # (ì»»; ì»»; á„ᅥᆾ; ì»»; á„ᅥᆾ; ) HANGUL SYLLABLE KEOC
+CEFC;CEFC;110F 1165 11BF;CEFC;110F 1165 11BF; # (컼; 컼; á„ᅥᆿ; 컼; á„ᅥᆿ; ) HANGUL SYLLABLE KEOK
+CEFD;CEFD;110F 1165 11C0;CEFD;110F 1165 11C0; # (컽; 컽; á„ᅥᇀ; 컽; á„ᅥᇀ; ) HANGUL SYLLABLE KEOT
+CEFE;CEFE;110F 1165 11C1;CEFE;110F 1165 11C1; # (컾; 컾; á„á…¥á‡; 컾; á„á…¥á‡; ) HANGUL SYLLABLE KEOP
+CEFF;CEFF;110F 1165 11C2;CEFF;110F 1165 11C2; # (컿; 컿; á„ᅥᇂ; 컿; á„ᅥᇂ; ) HANGUL SYLLABLE KEOH
+CF00;CF00;110F 1166;CF00;110F 1166; # (ì¼€; ì¼€; á„á…¦; ì¼€; á„á…¦; ) HANGUL SYLLABLE KE
+CF01;CF01;110F 1166 11A8;CF01;110F 1166 11A8; # (ì¼; ì¼; á„ᅦᆨ; ì¼; á„ᅦᆨ; ) HANGUL SYLLABLE KEG
+CF02;CF02;110F 1166 11A9;CF02;110F 1166 11A9; # (켂; 켂; á„ᅦᆩ; 켂; á„ᅦᆩ; ) HANGUL SYLLABLE KEGG
+CF03;CF03;110F 1166 11AA;CF03;110F 1166 11AA; # (켃; 켃; á„ᅦᆪ; 켃; á„ᅦᆪ; ) HANGUL SYLLABLE KEGS
+CF04;CF04;110F 1166 11AB;CF04;110F 1166 11AB; # (켄; 켄; á„ᅦᆫ; 켄; á„ᅦᆫ; ) HANGUL SYLLABLE KEN
+CF05;CF05;110F 1166 11AC;CF05;110F 1166 11AC; # (ì¼…; ì¼…; á„ᅦᆬ; ì¼…; á„ᅦᆬ; ) HANGUL SYLLABLE KENJ
+CF06;CF06;110F 1166 11AD;CF06;110F 1166 11AD; # (켆; 켆; á„ᅦᆭ; 켆; á„ᅦᆭ; ) HANGUL SYLLABLE KENH
+CF07;CF07;110F 1166 11AE;CF07;110F 1166 11AE; # (켇; 켇; á„ᅦᆮ; 켇; á„ᅦᆮ; ) HANGUL SYLLABLE KED
+CF08;CF08;110F 1166 11AF;CF08;110F 1166 11AF; # (켈; 켈; á„ᅦᆯ; 켈; á„ᅦᆯ; ) HANGUL SYLLABLE KEL
+CF09;CF09;110F 1166 11B0;CF09;110F 1166 11B0; # (켉; 켉; á„ᅦᆰ; 켉; á„ᅦᆰ; ) HANGUL SYLLABLE KELG
+CF0A;CF0A;110F 1166 11B1;CF0A;110F 1166 11B1; # (켊; 켊; á„ᅦᆱ; 켊; á„ᅦᆱ; ) HANGUL SYLLABLE KELM
+CF0B;CF0B;110F 1166 11B2;CF0B;110F 1166 11B2; # (켋; 켋; á„ᅦᆲ; 켋; á„ᅦᆲ; ) HANGUL SYLLABLE KELB
+CF0C;CF0C;110F 1166 11B3;CF0C;110F 1166 11B3; # (켌; 켌; á„ᅦᆳ; 켌; á„ᅦᆳ; ) HANGUL SYLLABLE KELS
+CF0D;CF0D;110F 1166 11B4;CF0D;110F 1166 11B4; # (ì¼; ì¼; á„ᅦᆴ; ì¼; á„ᅦᆴ; ) HANGUL SYLLABLE KELT
+CF0E;CF0E;110F 1166 11B5;CF0E;110F 1166 11B5; # (켎; 켎; á„ᅦᆵ; 켎; á„ᅦᆵ; ) HANGUL SYLLABLE KELP
+CF0F;CF0F;110F 1166 11B6;CF0F;110F 1166 11B6; # (ì¼; ì¼; á„ᅦᆶ; ì¼; á„ᅦᆶ; ) HANGUL SYLLABLE KELH
+CF10;CF10;110F 1166 11B7;CF10;110F 1166 11B7; # (ì¼; ì¼; á„ᅦᆷ; ì¼; á„ᅦᆷ; ) HANGUL SYLLABLE KEM
+CF11;CF11;110F 1166 11B8;CF11;110F 1166 11B8; # (켑; 켑; á„ᅦᆸ; 켑; á„ᅦᆸ; ) HANGUL SYLLABLE KEB
+CF12;CF12;110F 1166 11B9;CF12;110F 1166 11B9; # (ì¼’; ì¼’; á„ᅦᆹ; ì¼’; á„ᅦᆹ; ) HANGUL SYLLABLE KEBS
+CF13;CF13;110F 1166 11BA;CF13;110F 1166 11BA; # (켓; 켓; á„ᅦᆺ; 켓; á„ᅦᆺ; ) HANGUL SYLLABLE KES
+CF14;CF14;110F 1166 11BB;CF14;110F 1166 11BB; # (ì¼”; ì¼”; á„ᅦᆻ; ì¼”; á„ᅦᆻ; ) HANGUL SYLLABLE KESS
+CF15;CF15;110F 1166 11BC;CF15;110F 1166 11BC; # (켕; 켕; á„ᅦᆼ; 켕; á„ᅦᆼ; ) HANGUL SYLLABLE KENG
+CF16;CF16;110F 1166 11BD;CF16;110F 1166 11BD; # (ì¼–; ì¼–; á„ᅦᆽ; ì¼–; á„ᅦᆽ; ) HANGUL SYLLABLE KEJ
+CF17;CF17;110F 1166 11BE;CF17;110F 1166 11BE; # (ì¼—; ì¼—; á„ᅦᆾ; ì¼—; á„ᅦᆾ; ) HANGUL SYLLABLE KEC
+CF18;CF18;110F 1166 11BF;CF18;110F 1166 11BF; # (켘; 켘; á„ᅦᆿ; 켘; á„ᅦᆿ; ) HANGUL SYLLABLE KEK
+CF19;CF19;110F 1166 11C0;CF19;110F 1166 11C0; # (ì¼™; ì¼™; á„ᅦᇀ; ì¼™; á„ᅦᇀ; ) HANGUL SYLLABLE KET
+CF1A;CF1A;110F 1166 11C1;CF1A;110F 1166 11C1; # (켚; 켚; á„á…¦á‡; 켚; á„á…¦á‡; ) HANGUL SYLLABLE KEP
+CF1B;CF1B;110F 1166 11C2;CF1B;110F 1166 11C2; # (ì¼›; ì¼›; á„ᅦᇂ; ì¼›; á„ᅦᇂ; ) HANGUL SYLLABLE KEH
+CF1C;CF1C;110F 1167;CF1C;110F 1167; # (켜; 켜; á„á…§; 켜; á„á…§; ) HANGUL SYLLABLE KYEO
+CF1D;CF1D;110F 1167 11A8;CF1D;110F 1167 11A8; # (ì¼; ì¼; á„ᅧᆨ; ì¼; á„ᅧᆨ; ) HANGUL SYLLABLE KYEOG
+CF1E;CF1E;110F 1167 11A9;CF1E;110F 1167 11A9; # (켞; 켞; á„ᅧᆩ; 켞; á„ᅧᆩ; ) HANGUL SYLLABLE KYEOGG
+CF1F;CF1F;110F 1167 11AA;CF1F;110F 1167 11AA; # (켟; 켟; á„ᅧᆪ; 켟; á„ᅧᆪ; ) HANGUL SYLLABLE KYEOGS
+CF20;CF20;110F 1167 11AB;CF20;110F 1167 11AB; # (ì¼ ; ì¼ ; á„ᅧᆫ; ì¼ ; á„ᅧᆫ; ) HANGUL SYLLABLE KYEON
+CF21;CF21;110F 1167 11AC;CF21;110F 1167 11AC; # (켡; 켡; á„ᅧᆬ; 켡; á„ᅧᆬ; ) HANGUL SYLLABLE KYEONJ
+CF22;CF22;110F 1167 11AD;CF22;110F 1167 11AD; # (ì¼¢; ì¼¢; á„ᅧᆭ; ì¼¢; á„ᅧᆭ; ) HANGUL SYLLABLE KYEONH
+CF23;CF23;110F 1167 11AE;CF23;110F 1167 11AE; # (ì¼£; ì¼£; á„ᅧᆮ; ì¼£; á„ᅧᆮ; ) HANGUL SYLLABLE KYEOD
+CF24;CF24;110F 1167 11AF;CF24;110F 1167 11AF; # (켤; 켤; á„ᅧᆯ; 켤; á„ᅧᆯ; ) HANGUL SYLLABLE KYEOL
+CF25;CF25;110F 1167 11B0;CF25;110F 1167 11B0; # (ì¼¥; ì¼¥; á„ᅧᆰ; ì¼¥; á„ᅧᆰ; ) HANGUL SYLLABLE KYEOLG
+CF26;CF26;110F 1167 11B1;CF26;110F 1167 11B1; # (켦; 켦; á„ᅧᆱ; 켦; á„ᅧᆱ; ) HANGUL SYLLABLE KYEOLM
+CF27;CF27;110F 1167 11B2;CF27;110F 1167 11B2; # (켧; 켧; á„ᅧᆲ; 켧; á„ᅧᆲ; ) HANGUL SYLLABLE KYEOLB
+CF28;CF28;110F 1167 11B3;CF28;110F 1167 11B3; # (켨; 켨; á„ᅧᆳ; 켨; á„ᅧᆳ; ) HANGUL SYLLABLE KYEOLS
+CF29;CF29;110F 1167 11B4;CF29;110F 1167 11B4; # (켩; 켩; á„ᅧᆴ; 켩; á„ᅧᆴ; ) HANGUL SYLLABLE KYEOLT
+CF2A;CF2A;110F 1167 11B5;CF2A;110F 1167 11B5; # (켪; 켪; á„ᅧᆵ; 켪; á„ᅧᆵ; ) HANGUL SYLLABLE KYEOLP
+CF2B;CF2B;110F 1167 11B6;CF2B;110F 1167 11B6; # (켫; 켫; á„ᅧᆶ; 켫; á„ᅧᆶ; ) HANGUL SYLLABLE KYEOLH
+CF2C;CF2C;110F 1167 11B7;CF2C;110F 1167 11B7; # (켬; 켬; á„ᅧᆷ; 켬; á„ᅧᆷ; ) HANGUL SYLLABLE KYEOM
+CF2D;CF2D;110F 1167 11B8;CF2D;110F 1167 11B8; # (ì¼­; ì¼­; á„ᅧᆸ; ì¼­; á„ᅧᆸ; ) HANGUL SYLLABLE KYEOB
+CF2E;CF2E;110F 1167 11B9;CF2E;110F 1167 11B9; # (ì¼®; ì¼®; á„ᅧᆹ; ì¼®; á„ᅧᆹ; ) HANGUL SYLLABLE KYEOBS
+CF2F;CF2F;110F 1167 11BA;CF2F;110F 1167 11BA; # (켯; 켯; á„ᅧᆺ; 켯; á„ᅧᆺ; ) HANGUL SYLLABLE KYEOS
+CF30;CF30;110F 1167 11BB;CF30;110F 1167 11BB; # (ì¼°; ì¼°; á„ᅧᆻ; ì¼°; á„ᅧᆻ; ) HANGUL SYLLABLE KYEOSS
+CF31;CF31;110F 1167 11BC;CF31;110F 1167 11BC; # (ì¼±; ì¼±; á„ᅧᆼ; ì¼±; á„ᅧᆼ; ) HANGUL SYLLABLE KYEONG
+CF32;CF32;110F 1167 11BD;CF32;110F 1167 11BD; # (ì¼²; ì¼²; á„ᅧᆽ; ì¼²; á„ᅧᆽ; ) HANGUL SYLLABLE KYEOJ
+CF33;CF33;110F 1167 11BE;CF33;110F 1167 11BE; # (ì¼³; ì¼³; á„ᅧᆾ; ì¼³; á„ᅧᆾ; ) HANGUL SYLLABLE KYEOC
+CF34;CF34;110F 1167 11BF;CF34;110F 1167 11BF; # (ì¼´; ì¼´; á„ᅧᆿ; ì¼´; á„ᅧᆿ; ) HANGUL SYLLABLE KYEOK
+CF35;CF35;110F 1167 11C0;CF35;110F 1167 11C0; # (ì¼µ; ì¼µ; á„ᅧᇀ; ì¼µ; á„ᅧᇀ; ) HANGUL SYLLABLE KYEOT
+CF36;CF36;110F 1167 11C1;CF36;110F 1167 11C1; # (켶; 켶; á„á…§á‡; 켶; á„á…§á‡; ) HANGUL SYLLABLE KYEOP
+CF37;CF37;110F 1167 11C2;CF37;110F 1167 11C2; # (ì¼·; ì¼·; á„ᅧᇂ; ì¼·; á„ᅧᇂ; ) HANGUL SYLLABLE KYEOH
+CF38;CF38;110F 1168;CF38;110F 1168; # (켸; 켸; á„á…¨; 켸; á„á…¨; ) HANGUL SYLLABLE KYE
+CF39;CF39;110F 1168 11A8;CF39;110F 1168 11A8; # (ì¼¹; ì¼¹; á„ᅨᆨ; ì¼¹; á„ᅨᆨ; ) HANGUL SYLLABLE KYEG
+CF3A;CF3A;110F 1168 11A9;CF3A;110F 1168 11A9; # (켺; 켺; á„ᅨᆩ; 켺; á„ᅨᆩ; ) HANGUL SYLLABLE KYEGG
+CF3B;CF3B;110F 1168 11AA;CF3B;110F 1168 11AA; # (ì¼»; ì¼»; á„ᅨᆪ; ì¼»; á„ᅨᆪ; ) HANGUL SYLLABLE KYEGS
+CF3C;CF3C;110F 1168 11AB;CF3C;110F 1168 11AB; # (ì¼¼; ì¼¼; á„ᅨᆫ; ì¼¼; á„ᅨᆫ; ) HANGUL SYLLABLE KYEN
+CF3D;CF3D;110F 1168 11AC;CF3D;110F 1168 11AC; # (ì¼½; ì¼½; á„ᅨᆬ; ì¼½; á„ᅨᆬ; ) HANGUL SYLLABLE KYENJ
+CF3E;CF3E;110F 1168 11AD;CF3E;110F 1168 11AD; # (ì¼¾; ì¼¾; á„ᅨᆭ; ì¼¾; á„ᅨᆭ; ) HANGUL SYLLABLE KYENH
+CF3F;CF3F;110F 1168 11AE;CF3F;110F 1168 11AE; # (켿; 켿; á„ᅨᆮ; 켿; á„ᅨᆮ; ) HANGUL SYLLABLE KYED
+CF40;CF40;110F 1168 11AF;CF40;110F 1168 11AF; # (ì½€; ì½€; á„ᅨᆯ; ì½€; á„ᅨᆯ; ) HANGUL SYLLABLE KYEL
+CF41;CF41;110F 1168 11B0;CF41;110F 1168 11B0; # (ì½; ì½; á„ᅨᆰ; ì½; á„ᅨᆰ; ) HANGUL SYLLABLE KYELG
+CF42;CF42;110F 1168 11B1;CF42;110F 1168 11B1; # (콂; 콂; á„ᅨᆱ; 콂; á„ᅨᆱ; ) HANGUL SYLLABLE KYELM
+CF43;CF43;110F 1168 11B2;CF43;110F 1168 11B2; # (콃; 콃; á„ᅨᆲ; 콃; á„ᅨᆲ; ) HANGUL SYLLABLE KYELB
+CF44;CF44;110F 1168 11B3;CF44;110F 1168 11B3; # (콄; 콄; á„ᅨᆳ; 콄; á„ᅨᆳ; ) HANGUL SYLLABLE KYELS
+CF45;CF45;110F 1168 11B4;CF45;110F 1168 11B4; # (ì½…; ì½…; á„ᅨᆴ; ì½…; á„ᅨᆴ; ) HANGUL SYLLABLE KYELT
+CF46;CF46;110F 1168 11B5;CF46;110F 1168 11B5; # (콆; 콆; á„ᅨᆵ; 콆; á„ᅨᆵ; ) HANGUL SYLLABLE KYELP
+CF47;CF47;110F 1168 11B6;CF47;110F 1168 11B6; # (콇; 콇; á„ᅨᆶ; 콇; á„ᅨᆶ; ) HANGUL SYLLABLE KYELH
+CF48;CF48;110F 1168 11B7;CF48;110F 1168 11B7; # (콈; 콈; á„ᅨᆷ; 콈; á„ᅨᆷ; ) HANGUL SYLLABLE KYEM
+CF49;CF49;110F 1168 11B8;CF49;110F 1168 11B8; # (콉; 콉; á„ᅨᆸ; 콉; á„ᅨᆸ; ) HANGUL SYLLABLE KYEB
+CF4A;CF4A;110F 1168 11B9;CF4A;110F 1168 11B9; # (콊; 콊; á„ᅨᆹ; 콊; á„ᅨᆹ; ) HANGUL SYLLABLE KYEBS
+CF4B;CF4B;110F 1168 11BA;CF4B;110F 1168 11BA; # (콋; 콋; á„ᅨᆺ; 콋; á„ᅨᆺ; ) HANGUL SYLLABLE KYES
+CF4C;CF4C;110F 1168 11BB;CF4C;110F 1168 11BB; # (콌; 콌; á„ᅨᆻ; 콌; á„ᅨᆻ; ) HANGUL SYLLABLE KYESS
+CF4D;CF4D;110F 1168 11BC;CF4D;110F 1168 11BC; # (ì½; ì½; á„ᅨᆼ; ì½; á„ᅨᆼ; ) HANGUL SYLLABLE KYENG
+CF4E;CF4E;110F 1168 11BD;CF4E;110F 1168 11BD; # (콎; 콎; á„ᅨᆽ; 콎; á„ᅨᆽ; ) HANGUL SYLLABLE KYEJ
+CF4F;CF4F;110F 1168 11BE;CF4F;110F 1168 11BE; # (ì½; ì½; á„ᅨᆾ; ì½; á„ᅨᆾ; ) HANGUL SYLLABLE KYEC
+CF50;CF50;110F 1168 11BF;CF50;110F 1168 11BF; # (ì½; ì½; á„ᅨᆿ; ì½; á„ᅨᆿ; ) HANGUL SYLLABLE KYEK
+CF51;CF51;110F 1168 11C0;CF51;110F 1168 11C0; # (콑; 콑; á„ᅨᇀ; 콑; á„ᅨᇀ; ) HANGUL SYLLABLE KYET
+CF52;CF52;110F 1168 11C1;CF52;110F 1168 11C1; # (ì½’; ì½’; á„á…¨á‡; ì½’; á„á…¨á‡; ) HANGUL SYLLABLE KYEP
+CF53;CF53;110F 1168 11C2;CF53;110F 1168 11C2; # (콓; 콓; á„ᅨᇂ; 콓; á„ᅨᇂ; ) HANGUL SYLLABLE KYEH
+CF54;CF54;110F 1169;CF54;110F 1169; # (ì½”; ì½”; á„á…©; ì½”; á„á…©; ) HANGUL SYLLABLE KO
+CF55;CF55;110F 1169 11A8;CF55;110F 1169 11A8; # (콕; 콕; á„ᅩᆨ; 콕; á„ᅩᆨ; ) HANGUL SYLLABLE KOG
+CF56;CF56;110F 1169 11A9;CF56;110F 1169 11A9; # (ì½–; ì½–; á„ᅩᆩ; ì½–; á„ᅩᆩ; ) HANGUL SYLLABLE KOGG
+CF57;CF57;110F 1169 11AA;CF57;110F 1169 11AA; # (ì½—; ì½—; á„ᅩᆪ; ì½—; á„ᅩᆪ; ) HANGUL SYLLABLE KOGS
+CF58;CF58;110F 1169 11AB;CF58;110F 1169 11AB; # (콘; 콘; á„ᅩᆫ; 콘; á„ᅩᆫ; ) HANGUL SYLLABLE KON
+CF59;CF59;110F 1169 11AC;CF59;110F 1169 11AC; # (ì½™; ì½™; á„ᅩᆬ; ì½™; á„ᅩᆬ; ) HANGUL SYLLABLE KONJ
+CF5A;CF5A;110F 1169 11AD;CF5A;110F 1169 11AD; # (콚; 콚; á„ᅩᆭ; 콚; á„ᅩᆭ; ) HANGUL SYLLABLE KONH
+CF5B;CF5B;110F 1169 11AE;CF5B;110F 1169 11AE; # (ì½›; ì½›; á„ᅩᆮ; ì½›; á„ᅩᆮ; ) HANGUL SYLLABLE KOD
+CF5C;CF5C;110F 1169 11AF;CF5C;110F 1169 11AF; # (콜; 콜; á„ᅩᆯ; 콜; á„ᅩᆯ; ) HANGUL SYLLABLE KOL
+CF5D;CF5D;110F 1169 11B0;CF5D;110F 1169 11B0; # (ì½; ì½; á„ᅩᆰ; ì½; á„ᅩᆰ; ) HANGUL SYLLABLE KOLG
+CF5E;CF5E;110F 1169 11B1;CF5E;110F 1169 11B1; # (콞; 콞; á„ᅩᆱ; 콞; á„ᅩᆱ; ) HANGUL SYLLABLE KOLM
+CF5F;CF5F;110F 1169 11B2;CF5F;110F 1169 11B2; # (콟; 콟; á„ᅩᆲ; 콟; á„ᅩᆲ; ) HANGUL SYLLABLE KOLB
+CF60;CF60;110F 1169 11B3;CF60;110F 1169 11B3; # (ì½ ; ì½ ; á„ᅩᆳ; ì½ ; á„ᅩᆳ; ) HANGUL SYLLABLE KOLS
+CF61;CF61;110F 1169 11B4;CF61;110F 1169 11B4; # (콡; 콡; á„ᅩᆴ; 콡; á„ᅩᆴ; ) HANGUL SYLLABLE KOLT
+CF62;CF62;110F 1169 11B5;CF62;110F 1169 11B5; # (ì½¢; ì½¢; á„ᅩᆵ; ì½¢; á„ᅩᆵ; ) HANGUL SYLLABLE KOLP
+CF63;CF63;110F 1169 11B6;CF63;110F 1169 11B6; # (ì½£; ì½£; á„ᅩᆶ; ì½£; á„ᅩᆶ; ) HANGUL SYLLABLE KOLH
+CF64;CF64;110F 1169 11B7;CF64;110F 1169 11B7; # (콤; 콤; á„ᅩᆷ; 콤; á„ᅩᆷ; ) HANGUL SYLLABLE KOM
+CF65;CF65;110F 1169 11B8;CF65;110F 1169 11B8; # (ì½¥; ì½¥; á„ᅩᆸ; ì½¥; á„ᅩᆸ; ) HANGUL SYLLABLE KOB
+CF66;CF66;110F 1169 11B9;CF66;110F 1169 11B9; # (콦; 콦; á„ᅩᆹ; 콦; á„ᅩᆹ; ) HANGUL SYLLABLE KOBS
+CF67;CF67;110F 1169 11BA;CF67;110F 1169 11BA; # (콧; 콧; á„ᅩᆺ; 콧; á„ᅩᆺ; ) HANGUL SYLLABLE KOS
+CF68;CF68;110F 1169 11BB;CF68;110F 1169 11BB; # (콨; 콨; á„ᅩᆻ; 콨; á„ᅩᆻ; ) HANGUL SYLLABLE KOSS
+CF69;CF69;110F 1169 11BC;CF69;110F 1169 11BC; # (콩; 콩; á„ᅩᆼ; 콩; á„ᅩᆼ; ) HANGUL SYLLABLE KONG
+CF6A;CF6A;110F 1169 11BD;CF6A;110F 1169 11BD; # (콪; 콪; á„ᅩᆽ; 콪; á„ᅩᆽ; ) HANGUL SYLLABLE KOJ
+CF6B;CF6B;110F 1169 11BE;CF6B;110F 1169 11BE; # (콫; 콫; á„ᅩᆾ; 콫; á„ᅩᆾ; ) HANGUL SYLLABLE KOC
+CF6C;CF6C;110F 1169 11BF;CF6C;110F 1169 11BF; # (콬; 콬; á„ᅩᆿ; 콬; á„ᅩᆿ; ) HANGUL SYLLABLE KOK
+CF6D;CF6D;110F 1169 11C0;CF6D;110F 1169 11C0; # (ì½­; ì½­; á„ᅩᇀ; ì½­; á„ᅩᇀ; ) HANGUL SYLLABLE KOT
+CF6E;CF6E;110F 1169 11C1;CF6E;110F 1169 11C1; # (ì½®; ì½®; á„á…©á‡; ì½®; á„á…©á‡; ) HANGUL SYLLABLE KOP
+CF6F;CF6F;110F 1169 11C2;CF6F;110F 1169 11C2; # (콯; 콯; á„ᅩᇂ; 콯; á„ᅩᇂ; ) HANGUL SYLLABLE KOH
+CF70;CF70;110F 116A;CF70;110F 116A; # (ì½°; ì½°; á„á…ª; ì½°; á„á…ª; ) HANGUL SYLLABLE KWA
+CF71;CF71;110F 116A 11A8;CF71;110F 116A 11A8; # (ì½±; ì½±; á„ᅪᆨ; ì½±; á„ᅪᆨ; ) HANGUL SYLLABLE KWAG
+CF72;CF72;110F 116A 11A9;CF72;110F 116A 11A9; # (ì½²; ì½²; á„ᅪᆩ; ì½²; á„ᅪᆩ; ) HANGUL SYLLABLE KWAGG
+CF73;CF73;110F 116A 11AA;CF73;110F 116A 11AA; # (ì½³; ì½³; á„ᅪᆪ; ì½³; á„ᅪᆪ; ) HANGUL SYLLABLE KWAGS
+CF74;CF74;110F 116A 11AB;CF74;110F 116A 11AB; # (ì½´; ì½´; á„ᅪᆫ; ì½´; á„ᅪᆫ; ) HANGUL SYLLABLE KWAN
+CF75;CF75;110F 116A 11AC;CF75;110F 116A 11AC; # (ì½µ; ì½µ; á„ᅪᆬ; ì½µ; á„ᅪᆬ; ) HANGUL SYLLABLE KWANJ
+CF76;CF76;110F 116A 11AD;CF76;110F 116A 11AD; # (콶; 콶; á„ᅪᆭ; 콶; á„ᅪᆭ; ) HANGUL SYLLABLE KWANH
+CF77;CF77;110F 116A 11AE;CF77;110F 116A 11AE; # (ì½·; ì½·; á„ᅪᆮ; ì½·; á„ᅪᆮ; ) HANGUL SYLLABLE KWAD
+CF78;CF78;110F 116A 11AF;CF78;110F 116A 11AF; # (콸; 콸; á„ᅪᆯ; 콸; á„ᅪᆯ; ) HANGUL SYLLABLE KWAL
+CF79;CF79;110F 116A 11B0;CF79;110F 116A 11B0; # (ì½¹; ì½¹; á„ᅪᆰ; ì½¹; á„ᅪᆰ; ) HANGUL SYLLABLE KWALG
+CF7A;CF7A;110F 116A 11B1;CF7A;110F 116A 11B1; # (콺; 콺; á„ᅪᆱ; 콺; á„ᅪᆱ; ) HANGUL SYLLABLE KWALM
+CF7B;CF7B;110F 116A 11B2;CF7B;110F 116A 11B2; # (ì½»; ì½»; á„ᅪᆲ; ì½»; á„ᅪᆲ; ) HANGUL SYLLABLE KWALB
+CF7C;CF7C;110F 116A 11B3;CF7C;110F 116A 11B3; # (ì½¼; ì½¼; á„ᅪᆳ; ì½¼; á„ᅪᆳ; ) HANGUL SYLLABLE KWALS
+CF7D;CF7D;110F 116A 11B4;CF7D;110F 116A 11B4; # (ì½½; ì½½; á„ᅪᆴ; ì½½; á„ᅪᆴ; ) HANGUL SYLLABLE KWALT
+CF7E;CF7E;110F 116A 11B5;CF7E;110F 116A 11B5; # (ì½¾; ì½¾; á„ᅪᆵ; ì½¾; á„ᅪᆵ; ) HANGUL SYLLABLE KWALP
+CF7F;CF7F;110F 116A 11B6;CF7F;110F 116A 11B6; # (콿; 콿; á„ᅪᆶ; 콿; á„ᅪᆶ; ) HANGUL SYLLABLE KWALH
+CF80;CF80;110F 116A 11B7;CF80;110F 116A 11B7; # (ì¾€; ì¾€; á„ᅪᆷ; ì¾€; á„ᅪᆷ; ) HANGUL SYLLABLE KWAM
+CF81;CF81;110F 116A 11B8;CF81;110F 116A 11B8; # (ì¾; ì¾; á„ᅪᆸ; ì¾; á„ᅪᆸ; ) HANGUL SYLLABLE KWAB
+CF82;CF82;110F 116A 11B9;CF82;110F 116A 11B9; # (쾂; 쾂; á„ᅪᆹ; 쾂; á„ᅪᆹ; ) HANGUL SYLLABLE KWABS
+CF83;CF83;110F 116A 11BA;CF83;110F 116A 11BA; # (쾃; 쾃; á„ᅪᆺ; 쾃; á„ᅪᆺ; ) HANGUL SYLLABLE KWAS
+CF84;CF84;110F 116A 11BB;CF84;110F 116A 11BB; # (쾄; 쾄; á„ᅪᆻ; 쾄; á„ᅪᆻ; ) HANGUL SYLLABLE KWASS
+CF85;CF85;110F 116A 11BC;CF85;110F 116A 11BC; # (ì¾…; ì¾…; á„ᅪᆼ; ì¾…; á„ᅪᆼ; ) HANGUL SYLLABLE KWANG
+CF86;CF86;110F 116A 11BD;CF86;110F 116A 11BD; # (쾆; 쾆; á„ᅪᆽ; 쾆; á„ᅪᆽ; ) HANGUL SYLLABLE KWAJ
+CF87;CF87;110F 116A 11BE;CF87;110F 116A 11BE; # (쾇; 쾇; á„ᅪᆾ; 쾇; á„ᅪᆾ; ) HANGUL SYLLABLE KWAC
+CF88;CF88;110F 116A 11BF;CF88;110F 116A 11BF; # (쾈; 쾈; á„ᅪᆿ; 쾈; á„ᅪᆿ; ) HANGUL SYLLABLE KWAK
+CF89;CF89;110F 116A 11C0;CF89;110F 116A 11C0; # (쾉; 쾉; á„ᅪᇀ; 쾉; á„ᅪᇀ; ) HANGUL SYLLABLE KWAT
+CF8A;CF8A;110F 116A 11C1;CF8A;110F 116A 11C1; # (쾊; 쾊; á„á…ªá‡; 쾊; á„á…ªá‡; ) HANGUL SYLLABLE KWAP
+CF8B;CF8B;110F 116A 11C2;CF8B;110F 116A 11C2; # (쾋; 쾋; á„ᅪᇂ; 쾋; á„ᅪᇂ; ) HANGUL SYLLABLE KWAH
+CF8C;CF8C;110F 116B;CF8C;110F 116B; # (쾌; 쾌; á„á…«; 쾌; á„á…«; ) HANGUL SYLLABLE KWAE
+CF8D;CF8D;110F 116B 11A8;CF8D;110F 116B 11A8; # (ì¾; ì¾; á„ᅫᆨ; ì¾; á„ᅫᆨ; ) HANGUL SYLLABLE KWAEG
+CF8E;CF8E;110F 116B 11A9;CF8E;110F 116B 11A9; # (쾎; 쾎; á„ᅫᆩ; 쾎; á„ᅫᆩ; ) HANGUL SYLLABLE KWAEGG
+CF8F;CF8F;110F 116B 11AA;CF8F;110F 116B 11AA; # (ì¾; ì¾; á„ᅫᆪ; ì¾; á„ᅫᆪ; ) HANGUL SYLLABLE KWAEGS
+CF90;CF90;110F 116B 11AB;CF90;110F 116B 11AB; # (ì¾; ì¾; á„ᅫᆫ; ì¾; á„ᅫᆫ; ) HANGUL SYLLABLE KWAEN
+CF91;CF91;110F 116B 11AC;CF91;110F 116B 11AC; # (쾑; 쾑; á„ᅫᆬ; 쾑; á„ᅫᆬ; ) HANGUL SYLLABLE KWAENJ
+CF92;CF92;110F 116B 11AD;CF92;110F 116B 11AD; # (ì¾’; ì¾’; á„ᅫᆭ; ì¾’; á„ᅫᆭ; ) HANGUL SYLLABLE KWAENH
+CF93;CF93;110F 116B 11AE;CF93;110F 116B 11AE; # (쾓; 쾓; á„ᅫᆮ; 쾓; á„ᅫᆮ; ) HANGUL SYLLABLE KWAED
+CF94;CF94;110F 116B 11AF;CF94;110F 116B 11AF; # (ì¾”; ì¾”; á„ᅫᆯ; ì¾”; á„ᅫᆯ; ) HANGUL SYLLABLE KWAEL
+CF95;CF95;110F 116B 11B0;CF95;110F 116B 11B0; # (쾕; 쾕; á„ᅫᆰ; 쾕; á„ᅫᆰ; ) HANGUL SYLLABLE KWAELG
+CF96;CF96;110F 116B 11B1;CF96;110F 116B 11B1; # (ì¾–; ì¾–; á„ᅫᆱ; ì¾–; á„ᅫᆱ; ) HANGUL SYLLABLE KWAELM
+CF97;CF97;110F 116B 11B2;CF97;110F 116B 11B2; # (ì¾—; ì¾—; á„ᅫᆲ; ì¾—; á„ᅫᆲ; ) HANGUL SYLLABLE KWAELB
+CF98;CF98;110F 116B 11B3;CF98;110F 116B 11B3; # (쾘; 쾘; á„ᅫᆳ; 쾘; á„ᅫᆳ; ) HANGUL SYLLABLE KWAELS
+CF99;CF99;110F 116B 11B4;CF99;110F 116B 11B4; # (ì¾™; ì¾™; á„ᅫᆴ; ì¾™; á„ᅫᆴ; ) HANGUL SYLLABLE KWAELT
+CF9A;CF9A;110F 116B 11B5;CF9A;110F 116B 11B5; # (쾚; 쾚; á„ᅫᆵ; 쾚; á„ᅫᆵ; ) HANGUL SYLLABLE KWAELP
+CF9B;CF9B;110F 116B 11B6;CF9B;110F 116B 11B6; # (ì¾›; ì¾›; á„ᅫᆶ; ì¾›; á„ᅫᆶ; ) HANGUL SYLLABLE KWAELH
+CF9C;CF9C;110F 116B 11B7;CF9C;110F 116B 11B7; # (쾜; 쾜; á„ᅫᆷ; 쾜; á„ᅫᆷ; ) HANGUL SYLLABLE KWAEM
+CF9D;CF9D;110F 116B 11B8;CF9D;110F 116B 11B8; # (ì¾; ì¾; á„ᅫᆸ; ì¾; á„ᅫᆸ; ) HANGUL SYLLABLE KWAEB
+CF9E;CF9E;110F 116B 11B9;CF9E;110F 116B 11B9; # (쾞; 쾞; á„ᅫᆹ; 쾞; á„ᅫᆹ; ) HANGUL SYLLABLE KWAEBS
+CF9F;CF9F;110F 116B 11BA;CF9F;110F 116B 11BA; # (쾟; 쾟; á„ᅫᆺ; 쾟; á„ᅫᆺ; ) HANGUL SYLLABLE KWAES
+CFA0;CFA0;110F 116B 11BB;CFA0;110F 116B 11BB; # (ì¾ ; ì¾ ; á„ᅫᆻ; ì¾ ; á„ᅫᆻ; ) HANGUL SYLLABLE KWAESS
+CFA1;CFA1;110F 116B 11BC;CFA1;110F 116B 11BC; # (쾡; 쾡; á„ᅫᆼ; 쾡; á„ᅫᆼ; ) HANGUL SYLLABLE KWAENG
+CFA2;CFA2;110F 116B 11BD;CFA2;110F 116B 11BD; # (ì¾¢; ì¾¢; á„ᅫᆽ; ì¾¢; á„ᅫᆽ; ) HANGUL SYLLABLE KWAEJ
+CFA3;CFA3;110F 116B 11BE;CFA3;110F 116B 11BE; # (ì¾£; ì¾£; á„ᅫᆾ; ì¾£; á„ᅫᆾ; ) HANGUL SYLLABLE KWAEC
+CFA4;CFA4;110F 116B 11BF;CFA4;110F 116B 11BF; # (쾤; 쾤; á„ᅫᆿ; 쾤; á„ᅫᆿ; ) HANGUL SYLLABLE KWAEK
+CFA5;CFA5;110F 116B 11C0;CFA5;110F 116B 11C0; # (ì¾¥; ì¾¥; á„ᅫᇀ; ì¾¥; á„ᅫᇀ; ) HANGUL SYLLABLE KWAET
+CFA6;CFA6;110F 116B 11C1;CFA6;110F 116B 11C1; # (쾦; 쾦; á„á…«á‡; 쾦; á„á…«á‡; ) HANGUL SYLLABLE KWAEP
+CFA7;CFA7;110F 116B 11C2;CFA7;110F 116B 11C2; # (쾧; 쾧; á„ᅫᇂ; 쾧; á„ᅫᇂ; ) HANGUL SYLLABLE KWAEH
+CFA8;CFA8;110F 116C;CFA8;110F 116C; # (쾨; 쾨; á„á…¬; 쾨; á„á…¬; ) HANGUL SYLLABLE KOE
+CFA9;CFA9;110F 116C 11A8;CFA9;110F 116C 11A8; # (쾩; 쾩; á„ᅬᆨ; 쾩; á„ᅬᆨ; ) HANGUL SYLLABLE KOEG
+CFAA;CFAA;110F 116C 11A9;CFAA;110F 116C 11A9; # (쾪; 쾪; á„ᅬᆩ; 쾪; á„ᅬᆩ; ) HANGUL SYLLABLE KOEGG
+CFAB;CFAB;110F 116C 11AA;CFAB;110F 116C 11AA; # (쾫; 쾫; á„ᅬᆪ; 쾫; á„ᅬᆪ; ) HANGUL SYLLABLE KOEGS
+CFAC;CFAC;110F 116C 11AB;CFAC;110F 116C 11AB; # (쾬; 쾬; á„ᅬᆫ; 쾬; á„ᅬᆫ; ) HANGUL SYLLABLE KOEN
+CFAD;CFAD;110F 116C 11AC;CFAD;110F 116C 11AC; # (ì¾­; ì¾­; á„ᅬᆬ; ì¾­; á„ᅬᆬ; ) HANGUL SYLLABLE KOENJ
+CFAE;CFAE;110F 116C 11AD;CFAE;110F 116C 11AD; # (ì¾®; ì¾®; á„ᅬᆭ; ì¾®; á„ᅬᆭ; ) HANGUL SYLLABLE KOENH
+CFAF;CFAF;110F 116C 11AE;CFAF;110F 116C 11AE; # (쾯; 쾯; á„ᅬᆮ; 쾯; á„ᅬᆮ; ) HANGUL SYLLABLE KOED
+CFB0;CFB0;110F 116C 11AF;CFB0;110F 116C 11AF; # (ì¾°; ì¾°; á„ᅬᆯ; ì¾°; á„ᅬᆯ; ) HANGUL SYLLABLE KOEL
+CFB1;CFB1;110F 116C 11B0;CFB1;110F 116C 11B0; # (ì¾±; ì¾±; á„ᅬᆰ; ì¾±; á„ᅬᆰ; ) HANGUL SYLLABLE KOELG
+CFB2;CFB2;110F 116C 11B1;CFB2;110F 116C 11B1; # (ì¾²; ì¾²; á„ᅬᆱ; ì¾²; á„ᅬᆱ; ) HANGUL SYLLABLE KOELM
+CFB3;CFB3;110F 116C 11B2;CFB3;110F 116C 11B2; # (ì¾³; ì¾³; á„ᅬᆲ; ì¾³; á„ᅬᆲ; ) HANGUL SYLLABLE KOELB
+CFB4;CFB4;110F 116C 11B3;CFB4;110F 116C 11B3; # (ì¾´; ì¾´; á„ᅬᆳ; ì¾´; á„ᅬᆳ; ) HANGUL SYLLABLE KOELS
+CFB5;CFB5;110F 116C 11B4;CFB5;110F 116C 11B4; # (ì¾µ; ì¾µ; á„ᅬᆴ; ì¾µ; á„ᅬᆴ; ) HANGUL SYLLABLE KOELT
+CFB6;CFB6;110F 116C 11B5;CFB6;110F 116C 11B5; # (쾶; 쾶; á„ᅬᆵ; 쾶; á„ᅬᆵ; ) HANGUL SYLLABLE KOELP
+CFB7;CFB7;110F 116C 11B6;CFB7;110F 116C 11B6; # (ì¾·; ì¾·; á„ᅬᆶ; ì¾·; á„ᅬᆶ; ) HANGUL SYLLABLE KOELH
+CFB8;CFB8;110F 116C 11B7;CFB8;110F 116C 11B7; # (쾸; 쾸; á„ᅬᆷ; 쾸; á„ᅬᆷ; ) HANGUL SYLLABLE KOEM
+CFB9;CFB9;110F 116C 11B8;CFB9;110F 116C 11B8; # (ì¾¹; ì¾¹; á„ᅬᆸ; ì¾¹; á„ᅬᆸ; ) HANGUL SYLLABLE KOEB
+CFBA;CFBA;110F 116C 11B9;CFBA;110F 116C 11B9; # (쾺; 쾺; á„ᅬᆹ; 쾺; á„ᅬᆹ; ) HANGUL SYLLABLE KOEBS
+CFBB;CFBB;110F 116C 11BA;CFBB;110F 116C 11BA; # (ì¾»; ì¾»; á„ᅬᆺ; ì¾»; á„ᅬᆺ; ) HANGUL SYLLABLE KOES
+CFBC;CFBC;110F 116C 11BB;CFBC;110F 116C 11BB; # (ì¾¼; ì¾¼; á„ᅬᆻ; ì¾¼; á„ᅬᆻ; ) HANGUL SYLLABLE KOESS
+CFBD;CFBD;110F 116C 11BC;CFBD;110F 116C 11BC; # (ì¾½; ì¾½; á„ᅬᆼ; ì¾½; á„ᅬᆼ; ) HANGUL SYLLABLE KOENG
+CFBE;CFBE;110F 116C 11BD;CFBE;110F 116C 11BD; # (ì¾¾; ì¾¾; á„ᅬᆽ; ì¾¾; á„ᅬᆽ; ) HANGUL SYLLABLE KOEJ
+CFBF;CFBF;110F 116C 11BE;CFBF;110F 116C 11BE; # (쾿; 쾿; á„ᅬᆾ; 쾿; á„ᅬᆾ; ) HANGUL SYLLABLE KOEC
+CFC0;CFC0;110F 116C 11BF;CFC0;110F 116C 11BF; # (ì¿€; ì¿€; á„ᅬᆿ; ì¿€; á„ᅬᆿ; ) HANGUL SYLLABLE KOEK
+CFC1;CFC1;110F 116C 11C0;CFC1;110F 116C 11C0; # (ì¿; ì¿; á„ᅬᇀ; ì¿; á„ᅬᇀ; ) HANGUL SYLLABLE KOET
+CFC2;CFC2;110F 116C 11C1;CFC2;110F 116C 11C1; # (ì¿‚; ì¿‚; á„á…¬á‡; ì¿‚; á„á…¬á‡; ) HANGUL SYLLABLE KOEP
+CFC3;CFC3;110F 116C 11C2;CFC3;110F 116C 11C2; # (쿃; 쿃; á„ᅬᇂ; 쿃; á„ᅬᇂ; ) HANGUL SYLLABLE KOEH
+CFC4;CFC4;110F 116D;CFC4;110F 116D; # (ì¿„; ì¿„; á„á…­; ì¿„; á„á…­; ) HANGUL SYLLABLE KYO
+CFC5;CFC5;110F 116D 11A8;CFC5;110F 116D 11A8; # (ì¿…; ì¿…; á„ᅭᆨ; ì¿…; á„ᅭᆨ; ) HANGUL SYLLABLE KYOG
+CFC6;CFC6;110F 116D 11A9;CFC6;110F 116D 11A9; # (쿆; 쿆; á„ᅭᆩ; 쿆; á„ᅭᆩ; ) HANGUL SYLLABLE KYOGG
+CFC7;CFC7;110F 116D 11AA;CFC7;110F 116D 11AA; # (쿇; 쿇; á„ᅭᆪ; 쿇; á„ᅭᆪ; ) HANGUL SYLLABLE KYOGS
+CFC8;CFC8;110F 116D 11AB;CFC8;110F 116D 11AB; # (쿈; 쿈; á„ᅭᆫ; 쿈; á„ᅭᆫ; ) HANGUL SYLLABLE KYON
+CFC9;CFC9;110F 116D 11AC;CFC9;110F 116D 11AC; # (쿉; 쿉; á„ᅭᆬ; 쿉; á„ᅭᆬ; ) HANGUL SYLLABLE KYONJ
+CFCA;CFCA;110F 116D 11AD;CFCA;110F 116D 11AD; # (ì¿Š; ì¿Š; á„ᅭᆭ; ì¿Š; á„ᅭᆭ; ) HANGUL SYLLABLE KYONH
+CFCB;CFCB;110F 116D 11AE;CFCB;110F 116D 11AE; # (ì¿‹; ì¿‹; á„ᅭᆮ; ì¿‹; á„ᅭᆮ; ) HANGUL SYLLABLE KYOD
+CFCC;CFCC;110F 116D 11AF;CFCC;110F 116D 11AF; # (ì¿Œ; ì¿Œ; á„ᅭᆯ; ì¿Œ; á„ᅭᆯ; ) HANGUL SYLLABLE KYOL
+CFCD;CFCD;110F 116D 11B0;CFCD;110F 116D 11B0; # (ì¿; ì¿; á„ᅭᆰ; ì¿; á„ᅭᆰ; ) HANGUL SYLLABLE KYOLG
+CFCE;CFCE;110F 116D 11B1;CFCE;110F 116D 11B1; # (ì¿Ž; ì¿Ž; á„ᅭᆱ; ì¿Ž; á„ᅭᆱ; ) HANGUL SYLLABLE KYOLM
+CFCF;CFCF;110F 116D 11B2;CFCF;110F 116D 11B2; # (ì¿; ì¿; á„ᅭᆲ; ì¿; á„ᅭᆲ; ) HANGUL SYLLABLE KYOLB
+CFD0;CFD0;110F 116D 11B3;CFD0;110F 116D 11B3; # (ì¿; ì¿; á„ᅭᆳ; ì¿; á„ᅭᆳ; ) HANGUL SYLLABLE KYOLS
+CFD1;CFD1;110F 116D 11B4;CFD1;110F 116D 11B4; # (ì¿‘; ì¿‘; á„ᅭᆴ; ì¿‘; á„ᅭᆴ; ) HANGUL SYLLABLE KYOLT
+CFD2;CFD2;110F 116D 11B5;CFD2;110F 116D 11B5; # (ì¿’; ì¿’; á„ᅭᆵ; ì¿’; á„ᅭᆵ; ) HANGUL SYLLABLE KYOLP
+CFD3;CFD3;110F 116D 11B6;CFD3;110F 116D 11B6; # (ì¿“; ì¿“; á„ᅭᆶ; ì¿“; á„ᅭᆶ; ) HANGUL SYLLABLE KYOLH
+CFD4;CFD4;110F 116D 11B7;CFD4;110F 116D 11B7; # (ì¿”; ì¿”; á„ᅭᆷ; ì¿”; á„ᅭᆷ; ) HANGUL SYLLABLE KYOM
+CFD5;CFD5;110F 116D 11B8;CFD5;110F 116D 11B8; # (ì¿•; ì¿•; á„ᅭᆸ; ì¿•; á„ᅭᆸ; ) HANGUL SYLLABLE KYOB
+CFD6;CFD6;110F 116D 11B9;CFD6;110F 116D 11B9; # (ì¿–; ì¿–; á„ᅭᆹ; ì¿–; á„ᅭᆹ; ) HANGUL SYLLABLE KYOBS
+CFD7;CFD7;110F 116D 11BA;CFD7;110F 116D 11BA; # (ì¿—; ì¿—; á„ᅭᆺ; ì¿—; á„ᅭᆺ; ) HANGUL SYLLABLE KYOS
+CFD8;CFD8;110F 116D 11BB;CFD8;110F 116D 11BB; # (쿘; 쿘; á„ᅭᆻ; 쿘; á„ᅭᆻ; ) HANGUL SYLLABLE KYOSS
+CFD9;CFD9;110F 116D 11BC;CFD9;110F 116D 11BC; # (ì¿™; ì¿™; á„ᅭᆼ; ì¿™; á„ᅭᆼ; ) HANGUL SYLLABLE KYONG
+CFDA;CFDA;110F 116D 11BD;CFDA;110F 116D 11BD; # (ì¿š; ì¿š; á„ᅭᆽ; ì¿š; á„ᅭᆽ; ) HANGUL SYLLABLE KYOJ
+CFDB;CFDB;110F 116D 11BE;CFDB;110F 116D 11BE; # (ì¿›; ì¿›; á„ᅭᆾ; ì¿›; á„ᅭᆾ; ) HANGUL SYLLABLE KYOC
+CFDC;CFDC;110F 116D 11BF;CFDC;110F 116D 11BF; # (ì¿œ; ì¿œ; á„ᅭᆿ; ì¿œ; á„ᅭᆿ; ) HANGUL SYLLABLE KYOK
+CFDD;CFDD;110F 116D 11C0;CFDD;110F 116D 11C0; # (ì¿; ì¿; á„ᅭᇀ; ì¿; á„ᅭᇀ; ) HANGUL SYLLABLE KYOT
+CFDE;CFDE;110F 116D 11C1;CFDE;110F 116D 11C1; # (ì¿ž; ì¿ž; á„á…­á‡; ì¿ž; á„á…­á‡; ) HANGUL SYLLABLE KYOP
+CFDF;CFDF;110F 116D 11C2;CFDF;110F 116D 11C2; # (ì¿Ÿ; ì¿Ÿ; á„ᅭᇂ; ì¿Ÿ; á„ᅭᇂ; ) HANGUL SYLLABLE KYOH
+CFE0;CFE0;110F 116E;CFE0;110F 116E; # (ì¿ ; ì¿ ; á„á…®; ì¿ ; á„á…®; ) HANGUL SYLLABLE KU
+CFE1;CFE1;110F 116E 11A8;CFE1;110F 116E 11A8; # (ì¿¡; ì¿¡; á„ᅮᆨ; ì¿¡; á„ᅮᆨ; ) HANGUL SYLLABLE KUG
+CFE2;CFE2;110F 116E 11A9;CFE2;110F 116E 11A9; # (ì¿¢; ì¿¢; á„ᅮᆩ; ì¿¢; á„ᅮᆩ; ) HANGUL SYLLABLE KUGG
+CFE3;CFE3;110F 116E 11AA;CFE3;110F 116E 11AA; # (ì¿£; ì¿£; á„ᅮᆪ; ì¿£; á„ᅮᆪ; ) HANGUL SYLLABLE KUGS
+CFE4;CFE4;110F 116E 11AB;CFE4;110F 116E 11AB; # (쿤; 쿤; á„ᅮᆫ; 쿤; á„ᅮᆫ; ) HANGUL SYLLABLE KUN
+CFE5;CFE5;110F 116E 11AC;CFE5;110F 116E 11AC; # (ì¿¥; ì¿¥; á„ᅮᆬ; ì¿¥; á„ᅮᆬ; ) HANGUL SYLLABLE KUNJ
+CFE6;CFE6;110F 116E 11AD;CFE6;110F 116E 11AD; # (쿦; 쿦; á„ᅮᆭ; 쿦; á„ᅮᆭ; ) HANGUL SYLLABLE KUNH
+CFE7;CFE7;110F 116E 11AE;CFE7;110F 116E 11AE; # (쿧; 쿧; á„ᅮᆮ; 쿧; á„ᅮᆮ; ) HANGUL SYLLABLE KUD
+CFE8;CFE8;110F 116E 11AF;CFE8;110F 116E 11AF; # (쿨; 쿨; á„ᅮᆯ; 쿨; á„ᅮᆯ; ) HANGUL SYLLABLE KUL
+CFE9;CFE9;110F 116E 11B0;CFE9;110F 116E 11B0; # (ì¿©; ì¿©; á„ᅮᆰ; ì¿©; á„ᅮᆰ; ) HANGUL SYLLABLE KULG
+CFEA;CFEA;110F 116E 11B1;CFEA;110F 116E 11B1; # (쿪; 쿪; á„ᅮᆱ; 쿪; á„ᅮᆱ; ) HANGUL SYLLABLE KULM
+CFEB;CFEB;110F 116E 11B2;CFEB;110F 116E 11B2; # (ì¿«; ì¿«; á„ᅮᆲ; ì¿«; á„ᅮᆲ; ) HANGUL SYLLABLE KULB
+CFEC;CFEC;110F 116E 11B3;CFEC;110F 116E 11B3; # (쿬; 쿬; á„ᅮᆳ; 쿬; á„ᅮᆳ; ) HANGUL SYLLABLE KULS
+CFED;CFED;110F 116E 11B4;CFED;110F 116E 11B4; # (ì¿­; ì¿­; á„ᅮᆴ; ì¿­; á„ᅮᆴ; ) HANGUL SYLLABLE KULT
+CFEE;CFEE;110F 116E 11B5;CFEE;110F 116E 11B5; # (ì¿®; ì¿®; á„ᅮᆵ; ì¿®; á„ᅮᆵ; ) HANGUL SYLLABLE KULP
+CFEF;CFEF;110F 116E 11B6;CFEF;110F 116E 11B6; # (쿯; 쿯; á„ᅮᆶ; 쿯; á„ᅮᆶ; ) HANGUL SYLLABLE KULH
+CFF0;CFF0;110F 116E 11B7;CFF0;110F 116E 11B7; # (ì¿°; ì¿°; á„ᅮᆷ; ì¿°; á„ᅮᆷ; ) HANGUL SYLLABLE KUM
+CFF1;CFF1;110F 116E 11B8;CFF1;110F 116E 11B8; # (쿱; 쿱; á„ᅮᆸ; 쿱; á„ᅮᆸ; ) HANGUL SYLLABLE KUB
+CFF2;CFF2;110F 116E 11B9;CFF2;110F 116E 11B9; # (쿲; 쿲; á„ᅮᆹ; 쿲; á„ᅮᆹ; ) HANGUL SYLLABLE KUBS
+CFF3;CFF3;110F 116E 11BA;CFF3;110F 116E 11BA; # (쿳; 쿳; á„ᅮᆺ; 쿳; á„ᅮᆺ; ) HANGUL SYLLABLE KUS
+CFF4;CFF4;110F 116E 11BB;CFF4;110F 116E 11BB; # (ì¿´; ì¿´; á„ᅮᆻ; ì¿´; á„ᅮᆻ; ) HANGUL SYLLABLE KUSS
+CFF5;CFF5;110F 116E 11BC;CFF5;110F 116E 11BC; # (쿵; 쿵; á„ᅮᆼ; 쿵; á„ᅮᆼ; ) HANGUL SYLLABLE KUNG
+CFF6;CFF6;110F 116E 11BD;CFF6;110F 116E 11BD; # (쿶; 쿶; á„ᅮᆽ; 쿶; á„ᅮᆽ; ) HANGUL SYLLABLE KUJ
+CFF7;CFF7;110F 116E 11BE;CFF7;110F 116E 11BE; # (ì¿·; ì¿·; á„ᅮᆾ; ì¿·; á„ᅮᆾ; ) HANGUL SYLLABLE KUC
+CFF8;CFF8;110F 116E 11BF;CFF8;110F 116E 11BF; # (쿸; 쿸; á„ᅮᆿ; 쿸; á„ᅮᆿ; ) HANGUL SYLLABLE KUK
+CFF9;CFF9;110F 116E 11C0;CFF9;110F 116E 11C0; # (쿹; 쿹; á„ᅮᇀ; 쿹; á„ᅮᇀ; ) HANGUL SYLLABLE KUT
+CFFA;CFFA;110F 116E 11C1;CFFA;110F 116E 11C1; # (쿺; 쿺; á„á…®á‡; 쿺; á„á…®á‡; ) HANGUL SYLLABLE KUP
+CFFB;CFFB;110F 116E 11C2;CFFB;110F 116E 11C2; # (ì¿»; ì¿»; á„ᅮᇂ; ì¿»; á„ᅮᇂ; ) HANGUL SYLLABLE KUH
+CFFC;CFFC;110F 116F;CFFC;110F 116F; # (쿼; 쿼; á„á…¯; 쿼; á„á…¯; ) HANGUL SYLLABLE KWEO
+CFFD;CFFD;110F 116F 11A8;CFFD;110F 116F 11A8; # (쿽; 쿽; á„ᅯᆨ; 쿽; á„ᅯᆨ; ) HANGUL SYLLABLE KWEOG
+CFFE;CFFE;110F 116F 11A9;CFFE;110F 116F 11A9; # (쿾; 쿾; á„ᅯᆩ; 쿾; á„ᅯᆩ; ) HANGUL SYLLABLE KWEOGG
+CFFF;CFFF;110F 116F 11AA;CFFF;110F 116F 11AA; # (ì¿¿; ì¿¿; á„ᅯᆪ; ì¿¿; á„ᅯᆪ; ) HANGUL SYLLABLE KWEOGS
+D000;D000;110F 116F 11AB;D000;110F 116F 11AB; # (퀀; 퀀; á„ᅯᆫ; 퀀; á„ᅯᆫ; ) HANGUL SYLLABLE KWEON
+D001;D001;110F 116F 11AC;D001;110F 116F 11AC; # (í€; í€; á„ᅯᆬ; í€; á„ᅯᆬ; ) HANGUL SYLLABLE KWEONJ
+D002;D002;110F 116F 11AD;D002;110F 116F 11AD; # (퀂; 퀂; á„ᅯᆭ; 퀂; á„ᅯᆭ; ) HANGUL SYLLABLE KWEONH
+D003;D003;110F 116F 11AE;D003;110F 116F 11AE; # (퀃; 퀃; á„ᅯᆮ; 퀃; á„ᅯᆮ; ) HANGUL SYLLABLE KWEOD
+D004;D004;110F 116F 11AF;D004;110F 116F 11AF; # (퀄; 퀄; á„ᅯᆯ; 퀄; á„ᅯᆯ; ) HANGUL SYLLABLE KWEOL
+D005;D005;110F 116F 11B0;D005;110F 116F 11B0; # (퀅; 퀅; á„ᅯᆰ; 퀅; á„ᅯᆰ; ) HANGUL SYLLABLE KWEOLG
+D006;D006;110F 116F 11B1;D006;110F 116F 11B1; # (퀆; 퀆; á„ᅯᆱ; 퀆; á„ᅯᆱ; ) HANGUL SYLLABLE KWEOLM
+D007;D007;110F 116F 11B2;D007;110F 116F 11B2; # (퀇; 퀇; á„ᅯᆲ; 퀇; á„ᅯᆲ; ) HANGUL SYLLABLE KWEOLB
+D008;D008;110F 116F 11B3;D008;110F 116F 11B3; # (퀈; 퀈; á„ᅯᆳ; 퀈; á„ᅯᆳ; ) HANGUL SYLLABLE KWEOLS
+D009;D009;110F 116F 11B4;D009;110F 116F 11B4; # (퀉; 퀉; á„ᅯᆴ; 퀉; á„ᅯᆴ; ) HANGUL SYLLABLE KWEOLT
+D00A;D00A;110F 116F 11B5;D00A;110F 116F 11B5; # (퀊; 퀊; á„ᅯᆵ; 퀊; á„ᅯᆵ; ) HANGUL SYLLABLE KWEOLP
+D00B;D00B;110F 116F 11B6;D00B;110F 116F 11B6; # (퀋; 퀋; á„ᅯᆶ; 퀋; á„ᅯᆶ; ) HANGUL SYLLABLE KWEOLH
+D00C;D00C;110F 116F 11B7;D00C;110F 116F 11B7; # (퀌; 퀌; á„ᅯᆷ; 퀌; á„ᅯᆷ; ) HANGUL SYLLABLE KWEOM
+D00D;D00D;110F 116F 11B8;D00D;110F 116F 11B8; # (í€; í€; á„ᅯᆸ; í€; á„ᅯᆸ; ) HANGUL SYLLABLE KWEOB
+D00E;D00E;110F 116F 11B9;D00E;110F 116F 11B9; # (퀎; 퀎; á„ᅯᆹ; 퀎; á„ᅯᆹ; ) HANGUL SYLLABLE KWEOBS
+D00F;D00F;110F 116F 11BA;D00F;110F 116F 11BA; # (í€; í€; á„ᅯᆺ; í€; á„ᅯᆺ; ) HANGUL SYLLABLE KWEOS
+D010;D010;110F 116F 11BB;D010;110F 116F 11BB; # (í€; í€; á„ᅯᆻ; í€; á„ᅯᆻ; ) HANGUL SYLLABLE KWEOSS
+D011;D011;110F 116F 11BC;D011;110F 116F 11BC; # (퀑; 퀑; á„ᅯᆼ; 퀑; á„ᅯᆼ; ) HANGUL SYLLABLE KWEONG
+D012;D012;110F 116F 11BD;D012;110F 116F 11BD; # (퀒; 퀒; á„ᅯᆽ; 퀒; á„ᅯᆽ; ) HANGUL SYLLABLE KWEOJ
+D013;D013;110F 116F 11BE;D013;110F 116F 11BE; # (퀓; 퀓; á„ᅯᆾ; 퀓; á„ᅯᆾ; ) HANGUL SYLLABLE KWEOC
+D014;D014;110F 116F 11BF;D014;110F 116F 11BF; # (퀔; 퀔; á„ᅯᆿ; 퀔; á„ᅯᆿ; ) HANGUL SYLLABLE KWEOK
+D015;D015;110F 116F 11C0;D015;110F 116F 11C0; # (퀕; 퀕; á„ᅯᇀ; 퀕; á„ᅯᇀ; ) HANGUL SYLLABLE KWEOT
+D016;D016;110F 116F 11C1;D016;110F 116F 11C1; # (퀖; 퀖; á„á…¯á‡; 퀖; á„á…¯á‡; ) HANGUL SYLLABLE KWEOP
+D017;D017;110F 116F 11C2;D017;110F 116F 11C2; # (퀗; 퀗; á„ᅯᇂ; 퀗; á„ᅯᇂ; ) HANGUL SYLLABLE KWEOH
+D018;D018;110F 1170;D018;110F 1170; # (퀘; 퀘; á„á…°; 퀘; á„á…°; ) HANGUL SYLLABLE KWE
+D019;D019;110F 1170 11A8;D019;110F 1170 11A8; # (퀙; 퀙; á„ᅰᆨ; 퀙; á„ᅰᆨ; ) HANGUL SYLLABLE KWEG
+D01A;D01A;110F 1170 11A9;D01A;110F 1170 11A9; # (퀚; 퀚; á„ᅰᆩ; 퀚; á„ᅰᆩ; ) HANGUL SYLLABLE KWEGG
+D01B;D01B;110F 1170 11AA;D01B;110F 1170 11AA; # (퀛; 퀛; á„ᅰᆪ; 퀛; á„ᅰᆪ; ) HANGUL SYLLABLE KWEGS
+D01C;D01C;110F 1170 11AB;D01C;110F 1170 11AB; # (퀜; 퀜; á„ᅰᆫ; 퀜; á„ᅰᆫ; ) HANGUL SYLLABLE KWEN
+D01D;D01D;110F 1170 11AC;D01D;110F 1170 11AC; # (í€; í€; á„ᅰᆬ; í€; á„ᅰᆬ; ) HANGUL SYLLABLE KWENJ
+D01E;D01E;110F 1170 11AD;D01E;110F 1170 11AD; # (퀞; 퀞; á„ᅰᆭ; 퀞; á„ᅰᆭ; ) HANGUL SYLLABLE KWENH
+D01F;D01F;110F 1170 11AE;D01F;110F 1170 11AE; # (퀟; 퀟; á„ᅰᆮ; 퀟; á„ᅰᆮ; ) HANGUL SYLLABLE KWED
+D020;D020;110F 1170 11AF;D020;110F 1170 11AF; # (퀠; 퀠; á„ᅰᆯ; 퀠; á„ᅰᆯ; ) HANGUL SYLLABLE KWEL
+D021;D021;110F 1170 11B0;D021;110F 1170 11B0; # (퀡; 퀡; á„ᅰᆰ; 퀡; á„ᅰᆰ; ) HANGUL SYLLABLE KWELG
+D022;D022;110F 1170 11B1;D022;110F 1170 11B1; # (퀢; 퀢; á„ᅰᆱ; 퀢; á„ᅰᆱ; ) HANGUL SYLLABLE KWELM
+D023;D023;110F 1170 11B2;D023;110F 1170 11B2; # (퀣; 퀣; á„ᅰᆲ; 퀣; á„ᅰᆲ; ) HANGUL SYLLABLE KWELB
+D024;D024;110F 1170 11B3;D024;110F 1170 11B3; # (퀤; 퀤; á„ᅰᆳ; 퀤; á„ᅰᆳ; ) HANGUL SYLLABLE KWELS
+D025;D025;110F 1170 11B4;D025;110F 1170 11B4; # (퀥; 퀥; á„ᅰᆴ; 퀥; á„ᅰᆴ; ) HANGUL SYLLABLE KWELT
+D026;D026;110F 1170 11B5;D026;110F 1170 11B5; # (퀦; 퀦; á„ᅰᆵ; 퀦; á„ᅰᆵ; ) HANGUL SYLLABLE KWELP
+D027;D027;110F 1170 11B6;D027;110F 1170 11B6; # (퀧; 퀧; á„ᅰᆶ; 퀧; á„ᅰᆶ; ) HANGUL SYLLABLE KWELH
+D028;D028;110F 1170 11B7;D028;110F 1170 11B7; # (퀨; 퀨; á„ᅰᆷ; 퀨; á„ᅰᆷ; ) HANGUL SYLLABLE KWEM
+D029;D029;110F 1170 11B8;D029;110F 1170 11B8; # (퀩; 퀩; á„ᅰᆸ; 퀩; á„ᅰᆸ; ) HANGUL SYLLABLE KWEB
+D02A;D02A;110F 1170 11B9;D02A;110F 1170 11B9; # (퀪; 퀪; á„ᅰᆹ; 퀪; á„ᅰᆹ; ) HANGUL SYLLABLE KWEBS
+D02B;D02B;110F 1170 11BA;D02B;110F 1170 11BA; # (퀫; 퀫; á„ᅰᆺ; 퀫; á„ᅰᆺ; ) HANGUL SYLLABLE KWES
+D02C;D02C;110F 1170 11BB;D02C;110F 1170 11BB; # (퀬; 퀬; á„ᅰᆻ; 퀬; á„ᅰᆻ; ) HANGUL SYLLABLE KWESS
+D02D;D02D;110F 1170 11BC;D02D;110F 1170 11BC; # (퀭; 퀭; á„ᅰᆼ; 퀭; á„ᅰᆼ; ) HANGUL SYLLABLE KWENG
+D02E;D02E;110F 1170 11BD;D02E;110F 1170 11BD; # (퀮; 퀮; á„ᅰᆽ; 퀮; á„ᅰᆽ; ) HANGUL SYLLABLE KWEJ
+D02F;D02F;110F 1170 11BE;D02F;110F 1170 11BE; # (퀯; 퀯; á„ᅰᆾ; 퀯; á„ᅰᆾ; ) HANGUL SYLLABLE KWEC
+D030;D030;110F 1170 11BF;D030;110F 1170 11BF; # (퀰; 퀰; á„ᅰᆿ; 퀰; á„ᅰᆿ; ) HANGUL SYLLABLE KWEK
+D031;D031;110F 1170 11C0;D031;110F 1170 11C0; # (퀱; 퀱; á„ᅰᇀ; 퀱; á„ᅰᇀ; ) HANGUL SYLLABLE KWET
+D032;D032;110F 1170 11C1;D032;110F 1170 11C1; # (퀲; 퀲; á„á…°á‡; 퀲; á„á…°á‡; ) HANGUL SYLLABLE KWEP
+D033;D033;110F 1170 11C2;D033;110F 1170 11C2; # (퀳; 퀳; á„ᅰᇂ; 퀳; á„ᅰᇂ; ) HANGUL SYLLABLE KWEH
+D034;D034;110F 1171;D034;110F 1171; # (퀴; 퀴; á„á…±; 퀴; á„á…±; ) HANGUL SYLLABLE KWI
+D035;D035;110F 1171 11A8;D035;110F 1171 11A8; # (퀵; 퀵; á„ᅱᆨ; 퀵; á„ᅱᆨ; ) HANGUL SYLLABLE KWIG
+D036;D036;110F 1171 11A9;D036;110F 1171 11A9; # (퀶; 퀶; á„ᅱᆩ; 퀶; á„ᅱᆩ; ) HANGUL SYLLABLE KWIGG
+D037;D037;110F 1171 11AA;D037;110F 1171 11AA; # (퀷; 퀷; á„ᅱᆪ; 퀷; á„ᅱᆪ; ) HANGUL SYLLABLE KWIGS
+D038;D038;110F 1171 11AB;D038;110F 1171 11AB; # (퀸; 퀸; á„ᅱᆫ; 퀸; á„ᅱᆫ; ) HANGUL SYLLABLE KWIN
+D039;D039;110F 1171 11AC;D039;110F 1171 11AC; # (퀹; 퀹; á„ᅱᆬ; 퀹; á„ᅱᆬ; ) HANGUL SYLLABLE KWINJ
+D03A;D03A;110F 1171 11AD;D03A;110F 1171 11AD; # (퀺; 퀺; á„ᅱᆭ; 퀺; á„ᅱᆭ; ) HANGUL SYLLABLE KWINH
+D03B;D03B;110F 1171 11AE;D03B;110F 1171 11AE; # (퀻; 퀻; á„ᅱᆮ; 퀻; á„ᅱᆮ; ) HANGUL SYLLABLE KWID
+D03C;D03C;110F 1171 11AF;D03C;110F 1171 11AF; # (퀼; 퀼; á„ᅱᆯ; 퀼; á„ᅱᆯ; ) HANGUL SYLLABLE KWIL
+D03D;D03D;110F 1171 11B0;D03D;110F 1171 11B0; # (퀽; 퀽; á„ᅱᆰ; 퀽; á„ᅱᆰ; ) HANGUL SYLLABLE KWILG
+D03E;D03E;110F 1171 11B1;D03E;110F 1171 11B1; # (퀾; 퀾; á„ᅱᆱ; 퀾; á„ᅱᆱ; ) HANGUL SYLLABLE KWILM
+D03F;D03F;110F 1171 11B2;D03F;110F 1171 11B2; # (퀿; 퀿; á„ᅱᆲ; 퀿; á„ᅱᆲ; ) HANGUL SYLLABLE KWILB
+D040;D040;110F 1171 11B3;D040;110F 1171 11B3; # (í€; í€; á„ᅱᆳ; í€; á„ᅱᆳ; ) HANGUL SYLLABLE KWILS
+D041;D041;110F 1171 11B4;D041;110F 1171 11B4; # (í; í; á„ᅱᆴ; í; á„ᅱᆴ; ) HANGUL SYLLABLE KWILT
+D042;D042;110F 1171 11B5;D042;110F 1171 11B5; # (í‚; í‚; á„ᅱᆵ; í‚; á„ᅱᆵ; ) HANGUL SYLLABLE KWILP
+D043;D043;110F 1171 11B6;D043;110F 1171 11B6; # (íƒ; íƒ; á„ᅱᆶ; íƒ; á„ᅱᆶ; ) HANGUL SYLLABLE KWILH
+D044;D044;110F 1171 11B7;D044;110F 1171 11B7; # (í„; í„; á„ᅱᆷ; í„; á„ᅱᆷ; ) HANGUL SYLLABLE KWIM
+D045;D045;110F 1171 11B8;D045;110F 1171 11B8; # (í…; í…; á„ᅱᆸ; í…; á„ᅱᆸ; ) HANGUL SYLLABLE KWIB
+D046;D046;110F 1171 11B9;D046;110F 1171 11B9; # (í†; í†; á„ᅱᆹ; í†; á„ᅱᆹ; ) HANGUL SYLLABLE KWIBS
+D047;D047;110F 1171 11BA;D047;110F 1171 11BA; # (í‡; í‡; á„ᅱᆺ; í‡; á„ᅱᆺ; ) HANGUL SYLLABLE KWIS
+D048;D048;110F 1171 11BB;D048;110F 1171 11BB; # (íˆ; íˆ; á„ᅱᆻ; íˆ; á„ᅱᆻ; ) HANGUL SYLLABLE KWISS
+D049;D049;110F 1171 11BC;D049;110F 1171 11BC; # (í‰; í‰; á„ᅱᆼ; í‰; á„ᅱᆼ; ) HANGUL SYLLABLE KWING
+D04A;D04A;110F 1171 11BD;D04A;110F 1171 11BD; # (íŠ; íŠ; á„ᅱᆽ; íŠ; á„ᅱᆽ; ) HANGUL SYLLABLE KWIJ
+D04B;D04B;110F 1171 11BE;D04B;110F 1171 11BE; # (í‹; í‹; á„ᅱᆾ; í‹; á„ᅱᆾ; ) HANGUL SYLLABLE KWIC
+D04C;D04C;110F 1171 11BF;D04C;110F 1171 11BF; # (íŒ; íŒ; á„ᅱᆿ; íŒ; á„ᅱᆿ; ) HANGUL SYLLABLE KWIK
+D04D;D04D;110F 1171 11C0;D04D;110F 1171 11C0; # (í; í; á„ᅱᇀ; í; á„ᅱᇀ; ) HANGUL SYLLABLE KWIT
+D04E;D04E;110F 1171 11C1;D04E;110F 1171 11C1; # (íŽ; íŽ; á„á…±á‡; íŽ; á„á…±á‡; ) HANGUL SYLLABLE KWIP
+D04F;D04F;110F 1171 11C2;D04F;110F 1171 11C2; # (í; í; á„ᅱᇂ; í; á„ᅱᇂ; ) HANGUL SYLLABLE KWIH
+D050;D050;110F 1172;D050;110F 1172; # (í; í; á„á…²; í; á„á…²; ) HANGUL SYLLABLE KYU
+D051;D051;110F 1172 11A8;D051;110F 1172 11A8; # (í‘; í‘; á„ᅲᆨ; í‘; á„ᅲᆨ; ) HANGUL SYLLABLE KYUG
+D052;D052;110F 1172 11A9;D052;110F 1172 11A9; # (í’; í’; á„ᅲᆩ; í’; á„ᅲᆩ; ) HANGUL SYLLABLE KYUGG
+D053;D053;110F 1172 11AA;D053;110F 1172 11AA; # (í“; í“; á„ᅲᆪ; í“; á„ᅲᆪ; ) HANGUL SYLLABLE KYUGS
+D054;D054;110F 1172 11AB;D054;110F 1172 11AB; # (í”; í”; á„ᅲᆫ; í”; á„ᅲᆫ; ) HANGUL SYLLABLE KYUN
+D055;D055;110F 1172 11AC;D055;110F 1172 11AC; # (í•; í•; á„ᅲᆬ; í•; á„ᅲᆬ; ) HANGUL SYLLABLE KYUNJ
+D056;D056;110F 1172 11AD;D056;110F 1172 11AD; # (í–; í–; á„ᅲᆭ; í–; á„ᅲᆭ; ) HANGUL SYLLABLE KYUNH
+D057;D057;110F 1172 11AE;D057;110F 1172 11AE; # (í—; í—; á„ᅲᆮ; í—; á„ᅲᆮ; ) HANGUL SYLLABLE KYUD
+D058;D058;110F 1172 11AF;D058;110F 1172 11AF; # (í˜; í˜; á„ᅲᆯ; í˜; á„ᅲᆯ; ) HANGUL SYLLABLE KYUL
+D059;D059;110F 1172 11B0;D059;110F 1172 11B0; # (í™; í™; á„ᅲᆰ; í™; á„ᅲᆰ; ) HANGUL SYLLABLE KYULG
+D05A;D05A;110F 1172 11B1;D05A;110F 1172 11B1; # (íš; íš; á„ᅲᆱ; íš; á„ᅲᆱ; ) HANGUL SYLLABLE KYULM
+D05B;D05B;110F 1172 11B2;D05B;110F 1172 11B2; # (í›; í›; á„ᅲᆲ; í›; á„ᅲᆲ; ) HANGUL SYLLABLE KYULB
+D05C;D05C;110F 1172 11B3;D05C;110F 1172 11B3; # (íœ; íœ; á„ᅲᆳ; íœ; á„ᅲᆳ; ) HANGUL SYLLABLE KYULS
+D05D;D05D;110F 1172 11B4;D05D;110F 1172 11B4; # (í; í; á„ᅲᆴ; í; á„ᅲᆴ; ) HANGUL SYLLABLE KYULT
+D05E;D05E;110F 1172 11B5;D05E;110F 1172 11B5; # (íž; íž; á„ᅲᆵ; íž; á„ᅲᆵ; ) HANGUL SYLLABLE KYULP
+D05F;D05F;110F 1172 11B6;D05F;110F 1172 11B6; # (íŸ; íŸ; á„ᅲᆶ; íŸ; á„ᅲᆶ; ) HANGUL SYLLABLE KYULH
+D060;D060;110F 1172 11B7;D060;110F 1172 11B7; # (í ; í ; á„ᅲᆷ; í ; á„ᅲᆷ; ) HANGUL SYLLABLE KYUM
+D061;D061;110F 1172 11B8;D061;110F 1172 11B8; # (í¡; í¡; á„ᅲᆸ; í¡; á„ᅲᆸ; ) HANGUL SYLLABLE KYUB
+D062;D062;110F 1172 11B9;D062;110F 1172 11B9; # (í¢; í¢; á„ᅲᆹ; í¢; á„ᅲᆹ; ) HANGUL SYLLABLE KYUBS
+D063;D063;110F 1172 11BA;D063;110F 1172 11BA; # (í£; í£; á„ᅲᆺ; í£; á„ᅲᆺ; ) HANGUL SYLLABLE KYUS
+D064;D064;110F 1172 11BB;D064;110F 1172 11BB; # (í¤; í¤; á„ᅲᆻ; í¤; á„ᅲᆻ; ) HANGUL SYLLABLE KYUSS
+D065;D065;110F 1172 11BC;D065;110F 1172 11BC; # (í¥; í¥; á„ᅲᆼ; í¥; á„ᅲᆼ; ) HANGUL SYLLABLE KYUNG
+D066;D066;110F 1172 11BD;D066;110F 1172 11BD; # (í¦; í¦; á„ᅲᆽ; í¦; á„ᅲᆽ; ) HANGUL SYLLABLE KYUJ
+D067;D067;110F 1172 11BE;D067;110F 1172 11BE; # (í§; í§; á„ᅲᆾ; í§; á„ᅲᆾ; ) HANGUL SYLLABLE KYUC
+D068;D068;110F 1172 11BF;D068;110F 1172 11BF; # (í¨; í¨; á„ᅲᆿ; í¨; á„ᅲᆿ; ) HANGUL SYLLABLE KYUK
+D069;D069;110F 1172 11C0;D069;110F 1172 11C0; # (í©; í©; á„ᅲᇀ; í©; á„ᅲᇀ; ) HANGUL SYLLABLE KYUT
+D06A;D06A;110F 1172 11C1;D06A;110F 1172 11C1; # (íª; íª; á„á…²á‡; íª; á„á…²á‡; ) HANGUL SYLLABLE KYUP
+D06B;D06B;110F 1172 11C2;D06B;110F 1172 11C2; # (í«; í«; á„ᅲᇂ; í«; á„ᅲᇂ; ) HANGUL SYLLABLE KYUH
+D06C;D06C;110F 1173;D06C;110F 1173; # (í¬; í¬; á„á…³; í¬; á„á…³; ) HANGUL SYLLABLE KEU
+D06D;D06D;110F 1173 11A8;D06D;110F 1173 11A8; # (í­; í­; á„ᅳᆨ; í­; á„ᅳᆨ; ) HANGUL SYLLABLE KEUG
+D06E;D06E;110F 1173 11A9;D06E;110F 1173 11A9; # (í®; í®; á„ᅳᆩ; í®; á„ᅳᆩ; ) HANGUL SYLLABLE KEUGG
+D06F;D06F;110F 1173 11AA;D06F;110F 1173 11AA; # (í¯; í¯; á„ᅳᆪ; í¯; á„ᅳᆪ; ) HANGUL SYLLABLE KEUGS
+D070;D070;110F 1173 11AB;D070;110F 1173 11AB; # (í°; í°; á„ᅳᆫ; í°; á„ᅳᆫ; ) HANGUL SYLLABLE KEUN
+D071;D071;110F 1173 11AC;D071;110F 1173 11AC; # (í±; í±; á„ᅳᆬ; í±; á„ᅳᆬ; ) HANGUL SYLLABLE KEUNJ
+D072;D072;110F 1173 11AD;D072;110F 1173 11AD; # (í²; í²; á„ᅳᆭ; í²; á„ᅳᆭ; ) HANGUL SYLLABLE KEUNH
+D073;D073;110F 1173 11AE;D073;110F 1173 11AE; # (í³; í³; á„ᅳᆮ; í³; á„ᅳᆮ; ) HANGUL SYLLABLE KEUD
+D074;D074;110F 1173 11AF;D074;110F 1173 11AF; # (í´; í´; á„ᅳᆯ; í´; á„ᅳᆯ; ) HANGUL SYLLABLE KEUL
+D075;D075;110F 1173 11B0;D075;110F 1173 11B0; # (íµ; íµ; á„ᅳᆰ; íµ; á„ᅳᆰ; ) HANGUL SYLLABLE KEULG
+D076;D076;110F 1173 11B1;D076;110F 1173 11B1; # (í¶; í¶; á„ᅳᆱ; í¶; á„ᅳᆱ; ) HANGUL SYLLABLE KEULM
+D077;D077;110F 1173 11B2;D077;110F 1173 11B2; # (í·; í·; á„ᅳᆲ; í·; á„ᅳᆲ; ) HANGUL SYLLABLE KEULB
+D078;D078;110F 1173 11B3;D078;110F 1173 11B3; # (í¸; í¸; á„ᅳᆳ; í¸; á„ᅳᆳ; ) HANGUL SYLLABLE KEULS
+D079;D079;110F 1173 11B4;D079;110F 1173 11B4; # (í¹; í¹; á„ᅳᆴ; í¹; á„ᅳᆴ; ) HANGUL SYLLABLE KEULT
+D07A;D07A;110F 1173 11B5;D07A;110F 1173 11B5; # (íº; íº; á„ᅳᆵ; íº; á„ᅳᆵ; ) HANGUL SYLLABLE KEULP
+D07B;D07B;110F 1173 11B6;D07B;110F 1173 11B6; # (í»; í»; á„ᅳᆶ; í»; á„ᅳᆶ; ) HANGUL SYLLABLE KEULH
+D07C;D07C;110F 1173 11B7;D07C;110F 1173 11B7; # (í¼; í¼; á„ᅳᆷ; í¼; á„ᅳᆷ; ) HANGUL SYLLABLE KEUM
+D07D;D07D;110F 1173 11B8;D07D;110F 1173 11B8; # (í½; í½; á„ᅳᆸ; í½; á„ᅳᆸ; ) HANGUL SYLLABLE KEUB
+D07E;D07E;110F 1173 11B9;D07E;110F 1173 11B9; # (í¾; í¾; á„ᅳᆹ; í¾; á„ᅳᆹ; ) HANGUL SYLLABLE KEUBS
+D07F;D07F;110F 1173 11BA;D07F;110F 1173 11BA; # (í¿; í¿; á„ᅳᆺ; í¿; á„ᅳᆺ; ) HANGUL SYLLABLE KEUS
+D080;D080;110F 1173 11BB;D080;110F 1173 11BB; # (í‚€; í‚€; á„ᅳᆻ; í‚€; á„ᅳᆻ; ) HANGUL SYLLABLE KEUSS
+D081;D081;110F 1173 11BC;D081;110F 1173 11BC; # (í‚; í‚; á„ᅳᆼ; í‚; á„ᅳᆼ; ) HANGUL SYLLABLE KEUNG
+D082;D082;110F 1173 11BD;D082;110F 1173 11BD; # (í‚‚; í‚‚; á„ᅳᆽ; í‚‚; á„ᅳᆽ; ) HANGUL SYLLABLE KEUJ
+D083;D083;110F 1173 11BE;D083;110F 1173 11BE; # (킃; 킃; á„ᅳᆾ; 킃; á„ᅳᆾ; ) HANGUL SYLLABLE KEUC
+D084;D084;110F 1173 11BF;D084;110F 1173 11BF; # (í‚„; í‚„; á„ᅳᆿ; í‚„; á„ᅳᆿ; ) HANGUL SYLLABLE KEUK
+D085;D085;110F 1173 11C0;D085;110F 1173 11C0; # (í‚…; í‚…; á„ᅳᇀ; í‚…; á„ᅳᇀ; ) HANGUL SYLLABLE KEUT
+D086;D086;110F 1173 11C1;D086;110F 1173 11C1; # (킆; 킆; á„á…³á‡; 킆; á„á…³á‡; ) HANGUL SYLLABLE KEUP
+D087;D087;110F 1173 11C2;D087;110F 1173 11C2; # (킇; 킇; á„ᅳᇂ; 킇; á„ᅳᇂ; ) HANGUL SYLLABLE KEUH
+D088;D088;110F 1174;D088;110F 1174; # (킈; 킈; á„á…´; 킈; á„á…´; ) HANGUL SYLLABLE KYI
+D089;D089;110F 1174 11A8;D089;110F 1174 11A8; # (킉; 킉; á„ᅴᆨ; 킉; á„ᅴᆨ; ) HANGUL SYLLABLE KYIG
+D08A;D08A;110F 1174 11A9;D08A;110F 1174 11A9; # (í‚Š; í‚Š; á„ᅴᆩ; í‚Š; á„ᅴᆩ; ) HANGUL SYLLABLE KYIGG
+D08B;D08B;110F 1174 11AA;D08B;110F 1174 11AA; # (í‚‹; í‚‹; á„ᅴᆪ; í‚‹; á„ᅴᆪ; ) HANGUL SYLLABLE KYIGS
+D08C;D08C;110F 1174 11AB;D08C;110F 1174 11AB; # (í‚Œ; í‚Œ; á„ᅴᆫ; í‚Œ; á„ᅴᆫ; ) HANGUL SYLLABLE KYIN
+D08D;D08D;110F 1174 11AC;D08D;110F 1174 11AC; # (í‚; í‚; á„ᅴᆬ; í‚; á„ᅴᆬ; ) HANGUL SYLLABLE KYINJ
+D08E;D08E;110F 1174 11AD;D08E;110F 1174 11AD; # (í‚Ž; í‚Ž; á„ᅴᆭ; í‚Ž; á„ᅴᆭ; ) HANGUL SYLLABLE KYINH
+D08F;D08F;110F 1174 11AE;D08F;110F 1174 11AE; # (í‚; í‚; á„ᅴᆮ; í‚; á„ᅴᆮ; ) HANGUL SYLLABLE KYID
+D090;D090;110F 1174 11AF;D090;110F 1174 11AF; # (í‚; í‚; á„ᅴᆯ; í‚; á„ᅴᆯ; ) HANGUL SYLLABLE KYIL
+D091;D091;110F 1174 11B0;D091;110F 1174 11B0; # (í‚‘; í‚‘; á„ᅴᆰ; í‚‘; á„ᅴᆰ; ) HANGUL SYLLABLE KYILG
+D092;D092;110F 1174 11B1;D092;110F 1174 11B1; # (í‚’; í‚’; á„ᅴᆱ; í‚’; á„ᅴᆱ; ) HANGUL SYLLABLE KYILM
+D093;D093;110F 1174 11B2;D093;110F 1174 11B2; # (í‚“; í‚“; á„ᅴᆲ; í‚“; á„ᅴᆲ; ) HANGUL SYLLABLE KYILB
+D094;D094;110F 1174 11B3;D094;110F 1174 11B3; # (í‚”; í‚”; á„ᅴᆳ; í‚”; á„ᅴᆳ; ) HANGUL SYLLABLE KYILS
+D095;D095;110F 1174 11B4;D095;110F 1174 11B4; # (í‚•; í‚•; á„ᅴᆴ; í‚•; á„ᅴᆴ; ) HANGUL SYLLABLE KYILT
+D096;D096;110F 1174 11B5;D096;110F 1174 11B5; # (í‚–; í‚–; á„ᅴᆵ; í‚–; á„ᅴᆵ; ) HANGUL SYLLABLE KYILP
+D097;D097;110F 1174 11B6;D097;110F 1174 11B6; # (í‚—; í‚—; á„ᅴᆶ; í‚—; á„ᅴᆶ; ) HANGUL SYLLABLE KYILH
+D098;D098;110F 1174 11B7;D098;110F 1174 11B7; # (킘; 킘; á„ᅴᆷ; 킘; á„ᅴᆷ; ) HANGUL SYLLABLE KYIM
+D099;D099;110F 1174 11B8;D099;110F 1174 11B8; # (í‚™; í‚™; á„ᅴᆸ; í‚™; á„ᅴᆸ; ) HANGUL SYLLABLE KYIB
+D09A;D09A;110F 1174 11B9;D09A;110F 1174 11B9; # (í‚š; í‚š; á„ᅴᆹ; í‚š; á„ᅴᆹ; ) HANGUL SYLLABLE KYIBS
+D09B;D09B;110F 1174 11BA;D09B;110F 1174 11BA; # (í‚›; í‚›; á„ᅴᆺ; í‚›; á„ᅴᆺ; ) HANGUL SYLLABLE KYIS
+D09C;D09C;110F 1174 11BB;D09C;110F 1174 11BB; # (í‚œ; í‚œ; á„ᅴᆻ; í‚œ; á„ᅴᆻ; ) HANGUL SYLLABLE KYISS
+D09D;D09D;110F 1174 11BC;D09D;110F 1174 11BC; # (í‚; í‚; á„ᅴᆼ; í‚; á„ᅴᆼ; ) HANGUL SYLLABLE KYING
+D09E;D09E;110F 1174 11BD;D09E;110F 1174 11BD; # (í‚ž; í‚ž; á„ᅴᆽ; í‚ž; á„ᅴᆽ; ) HANGUL SYLLABLE KYIJ
+D09F;D09F;110F 1174 11BE;D09F;110F 1174 11BE; # (í‚Ÿ; í‚Ÿ; á„ᅴᆾ; í‚Ÿ; á„ᅴᆾ; ) HANGUL SYLLABLE KYIC
+D0A0;D0A0;110F 1174 11BF;D0A0;110F 1174 11BF; # (í‚ ; í‚ ; á„ᅴᆿ; í‚ ; á„ᅴᆿ; ) HANGUL SYLLABLE KYIK
+D0A1;D0A1;110F 1174 11C0;D0A1;110F 1174 11C0; # (í‚¡; í‚¡; á„ᅴᇀ; í‚¡; á„ᅴᇀ; ) HANGUL SYLLABLE KYIT
+D0A2;D0A2;110F 1174 11C1;D0A2;110F 1174 11C1; # (í‚¢; í‚¢; á„á…´á‡; í‚¢; á„á…´á‡; ) HANGUL SYLLABLE KYIP
+D0A3;D0A3;110F 1174 11C2;D0A3;110F 1174 11C2; # (í‚£; í‚£; á„ᅴᇂ; í‚£; á„ᅴᇂ; ) HANGUL SYLLABLE KYIH
+D0A4;D0A4;110F 1175;D0A4;110F 1175; # (키; 키; á„á…µ; 키; á„á…µ; ) HANGUL SYLLABLE KI
+D0A5;D0A5;110F 1175 11A8;D0A5;110F 1175 11A8; # (í‚¥; í‚¥; á„ᅵᆨ; í‚¥; á„ᅵᆨ; ) HANGUL SYLLABLE KIG
+D0A6;D0A6;110F 1175 11A9;D0A6;110F 1175 11A9; # (킦; 킦; á„ᅵᆩ; 킦; á„ᅵᆩ; ) HANGUL SYLLABLE KIGG
+D0A7;D0A7;110F 1175 11AA;D0A7;110F 1175 11AA; # (킧; 킧; á„ᅵᆪ; 킧; á„ᅵᆪ; ) HANGUL SYLLABLE KIGS
+D0A8;D0A8;110F 1175 11AB;D0A8;110F 1175 11AB; # (킨; 킨; á„ᅵᆫ; 킨; á„ᅵᆫ; ) HANGUL SYLLABLE KIN
+D0A9;D0A9;110F 1175 11AC;D0A9;110F 1175 11AC; # (í‚©; í‚©; á„ᅵᆬ; í‚©; á„ᅵᆬ; ) HANGUL SYLLABLE KINJ
+D0AA;D0AA;110F 1175 11AD;D0AA;110F 1175 11AD; # (킪; 킪; á„ᅵᆭ; 킪; á„ᅵᆭ; ) HANGUL SYLLABLE KINH
+D0AB;D0AB;110F 1175 11AE;D0AB;110F 1175 11AE; # (í‚«; í‚«; á„ᅵᆮ; í‚«; á„ᅵᆮ; ) HANGUL SYLLABLE KID
+D0AC;D0AC;110F 1175 11AF;D0AC;110F 1175 11AF; # (킬; 킬; á„ᅵᆯ; 킬; á„ᅵᆯ; ) HANGUL SYLLABLE KIL
+D0AD;D0AD;110F 1175 11B0;D0AD;110F 1175 11B0; # (í‚­; í‚­; á„ᅵᆰ; í‚­; á„ᅵᆰ; ) HANGUL SYLLABLE KILG
+D0AE;D0AE;110F 1175 11B1;D0AE;110F 1175 11B1; # (í‚®; í‚®; á„ᅵᆱ; í‚®; á„ᅵᆱ; ) HANGUL SYLLABLE KILM
+D0AF;D0AF;110F 1175 11B2;D0AF;110F 1175 11B2; # (킯; 킯; á„ᅵᆲ; 킯; á„ᅵᆲ; ) HANGUL SYLLABLE KILB
+D0B0;D0B0;110F 1175 11B3;D0B0;110F 1175 11B3; # (í‚°; í‚°; á„ᅵᆳ; í‚°; á„ᅵᆳ; ) HANGUL SYLLABLE KILS
+D0B1;D0B1;110F 1175 11B4;D0B1;110F 1175 11B4; # (킱; 킱; á„ᅵᆴ; 킱; á„ᅵᆴ; ) HANGUL SYLLABLE KILT
+D0B2;D0B2;110F 1175 11B5;D0B2;110F 1175 11B5; # (킲; 킲; á„ᅵᆵ; 킲; á„ᅵᆵ; ) HANGUL SYLLABLE KILP
+D0B3;D0B3;110F 1175 11B6;D0B3;110F 1175 11B6; # (킳; 킳; á„ᅵᆶ; 킳; á„ᅵᆶ; ) HANGUL SYLLABLE KILH
+D0B4;D0B4;110F 1175 11B7;D0B4;110F 1175 11B7; # (í‚´; í‚´; á„ᅵᆷ; í‚´; á„ᅵᆷ; ) HANGUL SYLLABLE KIM
+D0B5;D0B5;110F 1175 11B8;D0B5;110F 1175 11B8; # (킵; 킵; á„ᅵᆸ; 킵; á„ᅵᆸ; ) HANGUL SYLLABLE KIB
+D0B6;D0B6;110F 1175 11B9;D0B6;110F 1175 11B9; # (킶; 킶; á„ᅵᆹ; 킶; á„ᅵᆹ; ) HANGUL SYLLABLE KIBS
+D0B7;D0B7;110F 1175 11BA;D0B7;110F 1175 11BA; # (í‚·; í‚·; á„ᅵᆺ; í‚·; á„ᅵᆺ; ) HANGUL SYLLABLE KIS
+D0B8;D0B8;110F 1175 11BB;D0B8;110F 1175 11BB; # (킸; 킸; á„ᅵᆻ; 킸; á„ᅵᆻ; ) HANGUL SYLLABLE KISS
+D0B9;D0B9;110F 1175 11BC;D0B9;110F 1175 11BC; # (킹; 킹; á„ᅵᆼ; 킹; á„ᅵᆼ; ) HANGUL SYLLABLE KING
+D0BA;D0BA;110F 1175 11BD;D0BA;110F 1175 11BD; # (킺; 킺; á„ᅵᆽ; 킺; á„ᅵᆽ; ) HANGUL SYLLABLE KIJ
+D0BB;D0BB;110F 1175 11BE;D0BB;110F 1175 11BE; # (í‚»; í‚»; á„ᅵᆾ; í‚»; á„ᅵᆾ; ) HANGUL SYLLABLE KIC
+D0BC;D0BC;110F 1175 11BF;D0BC;110F 1175 11BF; # (킼; 킼; á„ᅵᆿ; 킼; á„ᅵᆿ; ) HANGUL SYLLABLE KIK
+D0BD;D0BD;110F 1175 11C0;D0BD;110F 1175 11C0; # (킽; 킽; á„ᅵᇀ; 킽; á„ᅵᇀ; ) HANGUL SYLLABLE KIT
+D0BE;D0BE;110F 1175 11C1;D0BE;110F 1175 11C1; # (킾; 킾; á„á…µá‡; 킾; á„á…µá‡; ) HANGUL SYLLABLE KIP
+D0BF;D0BF;110F 1175 11C2;D0BF;110F 1175 11C2; # (í‚¿; í‚¿; á„ᅵᇂ; í‚¿; á„ᅵᇂ; ) HANGUL SYLLABLE KIH
+D0C0;D0C0;1110 1161;D0C0;1110 1161; # (타; 타; á„á…¡; 타; á„á…¡; ) HANGUL SYLLABLE TA
+D0C1;D0C1;1110 1161 11A8;D0C1;1110 1161 11A8; # (íƒ; íƒ; á„ᅡᆨ; íƒ; á„ᅡᆨ; ) HANGUL SYLLABLE TAG
+D0C2;D0C2;1110 1161 11A9;D0C2;1110 1161 11A9; # (탂; 탂; á„ᅡᆩ; 탂; á„ᅡᆩ; ) HANGUL SYLLABLE TAGG
+D0C3;D0C3;1110 1161 11AA;D0C3;1110 1161 11AA; # (탃; 탃; á„ᅡᆪ; 탃; á„ᅡᆪ; ) HANGUL SYLLABLE TAGS
+D0C4;D0C4;1110 1161 11AB;D0C4;1110 1161 11AB; # (탄; 탄; á„ᅡᆫ; 탄; á„ᅡᆫ; ) HANGUL SYLLABLE TAN
+D0C5;D0C5;1110 1161 11AC;D0C5;1110 1161 11AC; # (탅; 탅; á„ᅡᆬ; 탅; á„ᅡᆬ; ) HANGUL SYLLABLE TANJ
+D0C6;D0C6;1110 1161 11AD;D0C6;1110 1161 11AD; # (탆; 탆; á„ᅡᆭ; 탆; á„ᅡᆭ; ) HANGUL SYLLABLE TANH
+D0C7;D0C7;1110 1161 11AE;D0C7;1110 1161 11AE; # (탇; 탇; á„ᅡᆮ; 탇; á„ᅡᆮ; ) HANGUL SYLLABLE TAD
+D0C8;D0C8;1110 1161 11AF;D0C8;1110 1161 11AF; # (탈; 탈; á„ᅡᆯ; 탈; á„ᅡᆯ; ) HANGUL SYLLABLE TAL
+D0C9;D0C9;1110 1161 11B0;D0C9;1110 1161 11B0; # (탉; 탉; á„ᅡᆰ; 탉; á„ᅡᆰ; ) HANGUL SYLLABLE TALG
+D0CA;D0CA;1110 1161 11B1;D0CA;1110 1161 11B1; # (탊; 탊; á„ᅡᆱ; 탊; á„ᅡᆱ; ) HANGUL SYLLABLE TALM
+D0CB;D0CB;1110 1161 11B2;D0CB;1110 1161 11B2; # (탋; 탋; á„ᅡᆲ; 탋; á„ᅡᆲ; ) HANGUL SYLLABLE TALB
+D0CC;D0CC;1110 1161 11B3;D0CC;1110 1161 11B3; # (탌; 탌; á„ᅡᆳ; 탌; á„ᅡᆳ; ) HANGUL SYLLABLE TALS
+D0CD;D0CD;1110 1161 11B4;D0CD;1110 1161 11B4; # (íƒ; íƒ; á„ᅡᆴ; íƒ; á„ᅡᆴ; ) HANGUL SYLLABLE TALT
+D0CE;D0CE;1110 1161 11B5;D0CE;1110 1161 11B5; # (탎; 탎; á„ᅡᆵ; 탎; á„ᅡᆵ; ) HANGUL SYLLABLE TALP
+D0CF;D0CF;1110 1161 11B6;D0CF;1110 1161 11B6; # (íƒ; íƒ; á„ᅡᆶ; íƒ; á„ᅡᆶ; ) HANGUL SYLLABLE TALH
+D0D0;D0D0;1110 1161 11B7;D0D0;1110 1161 11B7; # (íƒ; íƒ; á„ᅡᆷ; íƒ; á„ᅡᆷ; ) HANGUL SYLLABLE TAM
+D0D1;D0D1;1110 1161 11B8;D0D1;1110 1161 11B8; # (탑; 탑; á„ᅡᆸ; 탑; á„ᅡᆸ; ) HANGUL SYLLABLE TAB
+D0D2;D0D2;1110 1161 11B9;D0D2;1110 1161 11B9; # (탒; 탒; á„ᅡᆹ; 탒; á„ᅡᆹ; ) HANGUL SYLLABLE TABS
+D0D3;D0D3;1110 1161 11BA;D0D3;1110 1161 11BA; # (탓; 탓; á„ᅡᆺ; 탓; á„ᅡᆺ; ) HANGUL SYLLABLE TAS
+D0D4;D0D4;1110 1161 11BB;D0D4;1110 1161 11BB; # (탔; 탔; á„ᅡᆻ; 탔; á„ᅡᆻ; ) HANGUL SYLLABLE TASS
+D0D5;D0D5;1110 1161 11BC;D0D5;1110 1161 11BC; # (탕; 탕; á„ᅡᆼ; 탕; á„ᅡᆼ; ) HANGUL SYLLABLE TANG
+D0D6;D0D6;1110 1161 11BD;D0D6;1110 1161 11BD; # (탖; 탖; á„ᅡᆽ; 탖; á„ᅡᆽ; ) HANGUL SYLLABLE TAJ
+D0D7;D0D7;1110 1161 11BE;D0D7;1110 1161 11BE; # (탗; 탗; á„ᅡᆾ; 탗; á„ᅡᆾ; ) HANGUL SYLLABLE TAC
+D0D8;D0D8;1110 1161 11BF;D0D8;1110 1161 11BF; # (탘; 탘; á„ᅡᆿ; 탘; á„ᅡᆿ; ) HANGUL SYLLABLE TAK
+D0D9;D0D9;1110 1161 11C0;D0D9;1110 1161 11C0; # (탙; 탙; á„ᅡᇀ; 탙; á„ᅡᇀ; ) HANGUL SYLLABLE TAT
+D0DA;D0DA;1110 1161 11C1;D0DA;1110 1161 11C1; # (탚; 탚; á„á…¡á‡; 탚; á„á…¡á‡; ) HANGUL SYLLABLE TAP
+D0DB;D0DB;1110 1161 11C2;D0DB;1110 1161 11C2; # (탛; 탛; á„ᅡᇂ; 탛; á„ᅡᇂ; ) HANGUL SYLLABLE TAH
+D0DC;D0DC;1110 1162;D0DC;1110 1162; # (태; 태; á„á…¢; 태; á„á…¢; ) HANGUL SYLLABLE TAE
+D0DD;D0DD;1110 1162 11A8;D0DD;1110 1162 11A8; # (íƒ; íƒ; á„ᅢᆨ; íƒ; á„ᅢᆨ; ) HANGUL SYLLABLE TAEG
+D0DE;D0DE;1110 1162 11A9;D0DE;1110 1162 11A9; # (탞; 탞; á„ᅢᆩ; 탞; á„ᅢᆩ; ) HANGUL SYLLABLE TAEGG
+D0DF;D0DF;1110 1162 11AA;D0DF;1110 1162 11AA; # (탟; 탟; á„ᅢᆪ; 탟; á„ᅢᆪ; ) HANGUL SYLLABLE TAEGS
+D0E0;D0E0;1110 1162 11AB;D0E0;1110 1162 11AB; # (탠; 탠; á„ᅢᆫ; 탠; á„ᅢᆫ; ) HANGUL SYLLABLE TAEN
+D0E1;D0E1;1110 1162 11AC;D0E1;1110 1162 11AC; # (탡; 탡; á„ᅢᆬ; 탡; á„ᅢᆬ; ) HANGUL SYLLABLE TAENJ
+D0E2;D0E2;1110 1162 11AD;D0E2;1110 1162 11AD; # (탢; 탢; á„ᅢᆭ; 탢; á„ᅢᆭ; ) HANGUL SYLLABLE TAENH
+D0E3;D0E3;1110 1162 11AE;D0E3;1110 1162 11AE; # (탣; 탣; á„ᅢᆮ; 탣; á„ᅢᆮ; ) HANGUL SYLLABLE TAED
+D0E4;D0E4;1110 1162 11AF;D0E4;1110 1162 11AF; # (탤; 탤; á„ᅢᆯ; 탤; á„ᅢᆯ; ) HANGUL SYLLABLE TAEL
+D0E5;D0E5;1110 1162 11B0;D0E5;1110 1162 11B0; # (탥; 탥; á„ᅢᆰ; 탥; á„ᅢᆰ; ) HANGUL SYLLABLE TAELG
+D0E6;D0E6;1110 1162 11B1;D0E6;1110 1162 11B1; # (탦; 탦; á„ᅢᆱ; 탦; á„ᅢᆱ; ) HANGUL SYLLABLE TAELM
+D0E7;D0E7;1110 1162 11B2;D0E7;1110 1162 11B2; # (탧; 탧; á„ᅢᆲ; 탧; á„ᅢᆲ; ) HANGUL SYLLABLE TAELB
+D0E8;D0E8;1110 1162 11B3;D0E8;1110 1162 11B3; # (탨; 탨; á„ᅢᆳ; 탨; á„ᅢᆳ; ) HANGUL SYLLABLE TAELS
+D0E9;D0E9;1110 1162 11B4;D0E9;1110 1162 11B4; # (탩; 탩; á„ᅢᆴ; 탩; á„ᅢᆴ; ) HANGUL SYLLABLE TAELT
+D0EA;D0EA;1110 1162 11B5;D0EA;1110 1162 11B5; # (탪; 탪; á„ᅢᆵ; 탪; á„ᅢᆵ; ) HANGUL SYLLABLE TAELP
+D0EB;D0EB;1110 1162 11B6;D0EB;1110 1162 11B6; # (탫; 탫; á„ᅢᆶ; 탫; á„ᅢᆶ; ) HANGUL SYLLABLE TAELH
+D0EC;D0EC;1110 1162 11B7;D0EC;1110 1162 11B7; # (탬; 탬; á„ᅢᆷ; 탬; á„ᅢᆷ; ) HANGUL SYLLABLE TAEM
+D0ED;D0ED;1110 1162 11B8;D0ED;1110 1162 11B8; # (탭; 탭; á„ᅢᆸ; 탭; á„ᅢᆸ; ) HANGUL SYLLABLE TAEB
+D0EE;D0EE;1110 1162 11B9;D0EE;1110 1162 11B9; # (탮; 탮; á„ᅢᆹ; 탮; á„ᅢᆹ; ) HANGUL SYLLABLE TAEBS
+D0EF;D0EF;1110 1162 11BA;D0EF;1110 1162 11BA; # (탯; 탯; á„ᅢᆺ; 탯; á„ᅢᆺ; ) HANGUL SYLLABLE TAES
+D0F0;D0F0;1110 1162 11BB;D0F0;1110 1162 11BB; # (탰; 탰; á„ᅢᆻ; 탰; á„ᅢᆻ; ) HANGUL SYLLABLE TAESS
+D0F1;D0F1;1110 1162 11BC;D0F1;1110 1162 11BC; # (탱; 탱; á„ᅢᆼ; 탱; á„ᅢᆼ; ) HANGUL SYLLABLE TAENG
+D0F2;D0F2;1110 1162 11BD;D0F2;1110 1162 11BD; # (탲; 탲; á„ᅢᆽ; 탲; á„ᅢᆽ; ) HANGUL SYLLABLE TAEJ
+D0F3;D0F3;1110 1162 11BE;D0F3;1110 1162 11BE; # (탳; 탳; á„ᅢᆾ; 탳; á„ᅢᆾ; ) HANGUL SYLLABLE TAEC
+D0F4;D0F4;1110 1162 11BF;D0F4;1110 1162 11BF; # (탴; 탴; á„ᅢᆿ; 탴; á„ᅢᆿ; ) HANGUL SYLLABLE TAEK
+D0F5;D0F5;1110 1162 11C0;D0F5;1110 1162 11C0; # (탵; 탵; á„ᅢᇀ; 탵; á„ᅢᇀ; ) HANGUL SYLLABLE TAET
+D0F6;D0F6;1110 1162 11C1;D0F6;1110 1162 11C1; # (탶; 탶; á„á…¢á‡; 탶; á„á…¢á‡; ) HANGUL SYLLABLE TAEP
+D0F7;D0F7;1110 1162 11C2;D0F7;1110 1162 11C2; # (탷; 탷; á„ᅢᇂ; 탷; á„ᅢᇂ; ) HANGUL SYLLABLE TAEH
+D0F8;D0F8;1110 1163;D0F8;1110 1163; # (탸; 탸; á„á…£; 탸; á„á…£; ) HANGUL SYLLABLE TYA
+D0F9;D0F9;1110 1163 11A8;D0F9;1110 1163 11A8; # (탹; 탹; á„ᅣᆨ; 탹; á„ᅣᆨ; ) HANGUL SYLLABLE TYAG
+D0FA;D0FA;1110 1163 11A9;D0FA;1110 1163 11A9; # (탺; 탺; á„ᅣᆩ; 탺; á„ᅣᆩ; ) HANGUL SYLLABLE TYAGG
+D0FB;D0FB;1110 1163 11AA;D0FB;1110 1163 11AA; # (탻; 탻; á„ᅣᆪ; 탻; á„ᅣᆪ; ) HANGUL SYLLABLE TYAGS
+D0FC;D0FC;1110 1163 11AB;D0FC;1110 1163 11AB; # (탼; 탼; á„ᅣᆫ; 탼; á„ᅣᆫ; ) HANGUL SYLLABLE TYAN
+D0FD;D0FD;1110 1163 11AC;D0FD;1110 1163 11AC; # (탽; 탽; á„ᅣᆬ; 탽; á„ᅣᆬ; ) HANGUL SYLLABLE TYANJ
+D0FE;D0FE;1110 1163 11AD;D0FE;1110 1163 11AD; # (탾; 탾; á„ᅣᆭ; 탾; á„ᅣᆭ; ) HANGUL SYLLABLE TYANH
+D0FF;D0FF;1110 1163 11AE;D0FF;1110 1163 11AE; # (탿; 탿; á„ᅣᆮ; 탿; á„ᅣᆮ; ) HANGUL SYLLABLE TYAD
+D100;D100;1110 1163 11AF;D100;1110 1163 11AF; # (í„€; í„€; á„ᅣᆯ; í„€; á„ᅣᆯ; ) HANGUL SYLLABLE TYAL
+D101;D101;1110 1163 11B0;D101;1110 1163 11B0; # (í„; í„; á„ᅣᆰ; í„; á„ᅣᆰ; ) HANGUL SYLLABLE TYALG
+D102;D102;1110 1163 11B1;D102;1110 1163 11B1; # (í„‚; í„‚; á„ᅣᆱ; í„‚; á„ᅣᆱ; ) HANGUL SYLLABLE TYALM
+D103;D103;1110 1163 11B2;D103;1110 1163 11B2; # (턃; 턃; á„ᅣᆲ; 턃; á„ᅣᆲ; ) HANGUL SYLLABLE TYALB
+D104;D104;1110 1163 11B3;D104;1110 1163 11B3; # (í„„; í„„; á„ᅣᆳ; í„„; á„ᅣᆳ; ) HANGUL SYLLABLE TYALS
+D105;D105;1110 1163 11B4;D105;1110 1163 11B4; # (í„…; í„…; á„ᅣᆴ; í„…; á„ᅣᆴ; ) HANGUL SYLLABLE TYALT
+D106;D106;1110 1163 11B5;D106;1110 1163 11B5; # (턆; 턆; á„ᅣᆵ; 턆; á„ᅣᆵ; ) HANGUL SYLLABLE TYALP
+D107;D107;1110 1163 11B6;D107;1110 1163 11B6; # (턇; 턇; á„ᅣᆶ; 턇; á„ᅣᆶ; ) HANGUL SYLLABLE TYALH
+D108;D108;1110 1163 11B7;D108;1110 1163 11B7; # (턈; 턈; á„ᅣᆷ; 턈; á„ᅣᆷ; ) HANGUL SYLLABLE TYAM
+D109;D109;1110 1163 11B8;D109;1110 1163 11B8; # (턉; 턉; á„ᅣᆸ; 턉; á„ᅣᆸ; ) HANGUL SYLLABLE TYAB
+D10A;D10A;1110 1163 11B9;D10A;1110 1163 11B9; # (í„Š; í„Š; á„ᅣᆹ; í„Š; á„ᅣᆹ; ) HANGUL SYLLABLE TYABS
+D10B;D10B;1110 1163 11BA;D10B;1110 1163 11BA; # (í„‹; í„‹; á„ᅣᆺ; í„‹; á„ᅣᆺ; ) HANGUL SYLLABLE TYAS
+D10C;D10C;1110 1163 11BB;D10C;1110 1163 11BB; # (í„Œ; í„Œ; á„ᅣᆻ; í„Œ; á„ᅣᆻ; ) HANGUL SYLLABLE TYASS
+D10D;D10D;1110 1163 11BC;D10D;1110 1163 11BC; # (í„; í„; á„ᅣᆼ; í„; á„ᅣᆼ; ) HANGUL SYLLABLE TYANG
+D10E;D10E;1110 1163 11BD;D10E;1110 1163 11BD; # (í„Ž; í„Ž; á„ᅣᆽ; í„Ž; á„ᅣᆽ; ) HANGUL SYLLABLE TYAJ
+D10F;D10F;1110 1163 11BE;D10F;1110 1163 11BE; # (í„; í„; á„ᅣᆾ; í„; á„ᅣᆾ; ) HANGUL SYLLABLE TYAC
+D110;D110;1110 1163 11BF;D110;1110 1163 11BF; # (í„; í„; á„ᅣᆿ; í„; á„ᅣᆿ; ) HANGUL SYLLABLE TYAK
+D111;D111;1110 1163 11C0;D111;1110 1163 11C0; # (í„‘; í„‘; á„ᅣᇀ; í„‘; á„ᅣᇀ; ) HANGUL SYLLABLE TYAT
+D112;D112;1110 1163 11C1;D112;1110 1163 11C1; # (í„’; í„’; á„á…£á‡; í„’; á„á…£á‡; ) HANGUL SYLLABLE TYAP
+D113;D113;1110 1163 11C2;D113;1110 1163 11C2; # (í„“; í„“; á„ᅣᇂ; í„“; á„ᅣᇂ; ) HANGUL SYLLABLE TYAH
+D114;D114;1110 1164;D114;1110 1164; # (í„”; í„”; á„á…¤; í„”; á„á…¤; ) HANGUL SYLLABLE TYAE
+D115;D115;1110 1164 11A8;D115;1110 1164 11A8; # (í„•; í„•; á„ᅤᆨ; í„•; á„ᅤᆨ; ) HANGUL SYLLABLE TYAEG
+D116;D116;1110 1164 11A9;D116;1110 1164 11A9; # (í„–; í„–; á„ᅤᆩ; í„–; á„ᅤᆩ; ) HANGUL SYLLABLE TYAEGG
+D117;D117;1110 1164 11AA;D117;1110 1164 11AA; # (í„—; í„—; á„ᅤᆪ; í„—; á„ᅤᆪ; ) HANGUL SYLLABLE TYAEGS
+D118;D118;1110 1164 11AB;D118;1110 1164 11AB; # (턘; 턘; á„ᅤᆫ; 턘; á„ᅤᆫ; ) HANGUL SYLLABLE TYAEN
+D119;D119;1110 1164 11AC;D119;1110 1164 11AC; # (í„™; í„™; á„ᅤᆬ; í„™; á„ᅤᆬ; ) HANGUL SYLLABLE TYAENJ
+D11A;D11A;1110 1164 11AD;D11A;1110 1164 11AD; # (í„š; í„š; á„ᅤᆭ; í„š; á„ᅤᆭ; ) HANGUL SYLLABLE TYAENH
+D11B;D11B;1110 1164 11AE;D11B;1110 1164 11AE; # (í„›; í„›; á„ᅤᆮ; í„›; á„ᅤᆮ; ) HANGUL SYLLABLE TYAED
+D11C;D11C;1110 1164 11AF;D11C;1110 1164 11AF; # (í„œ; í„œ; á„ᅤᆯ; í„œ; á„ᅤᆯ; ) HANGUL SYLLABLE TYAEL
+D11D;D11D;1110 1164 11B0;D11D;1110 1164 11B0; # (í„; í„; á„ᅤᆰ; í„; á„ᅤᆰ; ) HANGUL SYLLABLE TYAELG
+D11E;D11E;1110 1164 11B1;D11E;1110 1164 11B1; # (í„ž; í„ž; á„ᅤᆱ; í„ž; á„ᅤᆱ; ) HANGUL SYLLABLE TYAELM
+D11F;D11F;1110 1164 11B2;D11F;1110 1164 11B2; # (í„Ÿ; í„Ÿ; á„ᅤᆲ; í„Ÿ; á„ᅤᆲ; ) HANGUL SYLLABLE TYAELB
+D120;D120;1110 1164 11B3;D120;1110 1164 11B3; # (í„ ; í„ ; á„ᅤᆳ; í„ ; á„ᅤᆳ; ) HANGUL SYLLABLE TYAELS
+D121;D121;1110 1164 11B4;D121;1110 1164 11B4; # (í„¡; í„¡; á„ᅤᆴ; í„¡; á„ᅤᆴ; ) HANGUL SYLLABLE TYAELT
+D122;D122;1110 1164 11B5;D122;1110 1164 11B5; # (í„¢; í„¢; á„ᅤᆵ; í„¢; á„ᅤᆵ; ) HANGUL SYLLABLE TYAELP
+D123;D123;1110 1164 11B6;D123;1110 1164 11B6; # (í„£; í„£; á„ᅤᆶ; í„£; á„ᅤᆶ; ) HANGUL SYLLABLE TYAELH
+D124;D124;1110 1164 11B7;D124;1110 1164 11B7; # (턤; 턤; á„ᅤᆷ; 턤; á„ᅤᆷ; ) HANGUL SYLLABLE TYAEM
+D125;D125;1110 1164 11B8;D125;1110 1164 11B8; # (í„¥; í„¥; á„ᅤᆸ; í„¥; á„ᅤᆸ; ) HANGUL SYLLABLE TYAEB
+D126;D126;1110 1164 11B9;D126;1110 1164 11B9; # (턦; 턦; á„ᅤᆹ; 턦; á„ᅤᆹ; ) HANGUL SYLLABLE TYAEBS
+D127;D127;1110 1164 11BA;D127;1110 1164 11BA; # (턧; 턧; á„ᅤᆺ; 턧; á„ᅤᆺ; ) HANGUL SYLLABLE TYAES
+D128;D128;1110 1164 11BB;D128;1110 1164 11BB; # (턨; 턨; á„ᅤᆻ; 턨; á„ᅤᆻ; ) HANGUL SYLLABLE TYAESS
+D129;D129;1110 1164 11BC;D129;1110 1164 11BC; # (í„©; í„©; á„ᅤᆼ; í„©; á„ᅤᆼ; ) HANGUL SYLLABLE TYAENG
+D12A;D12A;1110 1164 11BD;D12A;1110 1164 11BD; # (턪; 턪; á„ᅤᆽ; 턪; á„ᅤᆽ; ) HANGUL SYLLABLE TYAEJ
+D12B;D12B;1110 1164 11BE;D12B;1110 1164 11BE; # (í„«; í„«; á„ᅤᆾ; í„«; á„ᅤᆾ; ) HANGUL SYLLABLE TYAEC
+D12C;D12C;1110 1164 11BF;D12C;1110 1164 11BF; # (턬; 턬; á„ᅤᆿ; 턬; á„ᅤᆿ; ) HANGUL SYLLABLE TYAEK
+D12D;D12D;1110 1164 11C0;D12D;1110 1164 11C0; # (í„­; í„­; á„ᅤᇀ; í„­; á„ᅤᇀ; ) HANGUL SYLLABLE TYAET
+D12E;D12E;1110 1164 11C1;D12E;1110 1164 11C1; # (í„®; í„®; á„á…¤á‡; í„®; á„á…¤á‡; ) HANGUL SYLLABLE TYAEP
+D12F;D12F;1110 1164 11C2;D12F;1110 1164 11C2; # (턯; 턯; á„ᅤᇂ; 턯; á„ᅤᇂ; ) HANGUL SYLLABLE TYAEH
+D130;D130;1110 1165;D130;1110 1165; # (í„°; í„°; á„á…¥; í„°; á„á…¥; ) HANGUL SYLLABLE TEO
+D131;D131;1110 1165 11A8;D131;1110 1165 11A8; # (턱; 턱; á„ᅥᆨ; 턱; á„ᅥᆨ; ) HANGUL SYLLABLE TEOG
+D132;D132;1110 1165 11A9;D132;1110 1165 11A9; # (턲; 턲; á„ᅥᆩ; 턲; á„ᅥᆩ; ) HANGUL SYLLABLE TEOGG
+D133;D133;1110 1165 11AA;D133;1110 1165 11AA; # (턳; 턳; á„ᅥᆪ; 턳; á„ᅥᆪ; ) HANGUL SYLLABLE TEOGS
+D134;D134;1110 1165 11AB;D134;1110 1165 11AB; # (í„´; í„´; á„ᅥᆫ; í„´; á„ᅥᆫ; ) HANGUL SYLLABLE TEON
+D135;D135;1110 1165 11AC;D135;1110 1165 11AC; # (턵; 턵; á„ᅥᆬ; 턵; á„ᅥᆬ; ) HANGUL SYLLABLE TEONJ
+D136;D136;1110 1165 11AD;D136;1110 1165 11AD; # (턶; 턶; á„ᅥᆭ; 턶; á„ᅥᆭ; ) HANGUL SYLLABLE TEONH
+D137;D137;1110 1165 11AE;D137;1110 1165 11AE; # (í„·; í„·; á„ᅥᆮ; í„·; á„ᅥᆮ; ) HANGUL SYLLABLE TEOD
+D138;D138;1110 1165 11AF;D138;1110 1165 11AF; # (털; 털; á„ᅥᆯ; 털; á„ᅥᆯ; ) HANGUL SYLLABLE TEOL
+D139;D139;1110 1165 11B0;D139;1110 1165 11B0; # (턹; 턹; á„ᅥᆰ; 턹; á„ᅥᆰ; ) HANGUL SYLLABLE TEOLG
+D13A;D13A;1110 1165 11B1;D13A;1110 1165 11B1; # (턺; 턺; á„ᅥᆱ; 턺; á„ᅥᆱ; ) HANGUL SYLLABLE TEOLM
+D13B;D13B;1110 1165 11B2;D13B;1110 1165 11B2; # (í„»; í„»; á„ᅥᆲ; í„»; á„ᅥᆲ; ) HANGUL SYLLABLE TEOLB
+D13C;D13C;1110 1165 11B3;D13C;1110 1165 11B3; # (턼; 턼; á„ᅥᆳ; 턼; á„ᅥᆳ; ) HANGUL SYLLABLE TEOLS
+D13D;D13D;1110 1165 11B4;D13D;1110 1165 11B4; # (턽; 턽; á„ᅥᆴ; 턽; á„ᅥᆴ; ) HANGUL SYLLABLE TEOLT
+D13E;D13E;1110 1165 11B5;D13E;1110 1165 11B5; # (턾; 턾; á„ᅥᆵ; 턾; á„ᅥᆵ; ) HANGUL SYLLABLE TEOLP
+D13F;D13F;1110 1165 11B6;D13F;1110 1165 11B6; # (í„¿; í„¿; á„ᅥᆶ; í„¿; á„ᅥᆶ; ) HANGUL SYLLABLE TEOLH
+D140;D140;1110 1165 11B7;D140;1110 1165 11B7; # (í…€; í…€; á„ᅥᆷ; í…€; á„ᅥᆷ; ) HANGUL SYLLABLE TEOM
+D141;D141;1110 1165 11B8;D141;1110 1165 11B8; # (í…; í…; á„ᅥᆸ; í…; á„ᅥᆸ; ) HANGUL SYLLABLE TEOB
+D142;D142;1110 1165 11B9;D142;1110 1165 11B9; # (í…‚; í…‚; á„ᅥᆹ; í…‚; á„ᅥᆹ; ) HANGUL SYLLABLE TEOBS
+D143;D143;1110 1165 11BA;D143;1110 1165 11BA; # (í…ƒ; í…ƒ; á„ᅥᆺ; í…ƒ; á„ᅥᆺ; ) HANGUL SYLLABLE TEOS
+D144;D144;1110 1165 11BB;D144;1110 1165 11BB; # (í…„; í…„; á„ᅥᆻ; í…„; á„ᅥᆻ; ) HANGUL SYLLABLE TEOSS
+D145;D145;1110 1165 11BC;D145;1110 1165 11BC; # (í……; í……; á„ᅥᆼ; í……; á„ᅥᆼ; ) HANGUL SYLLABLE TEONG
+D146;D146;1110 1165 11BD;D146;1110 1165 11BD; # (í…†; í…†; á„ᅥᆽ; í…†; á„ᅥᆽ; ) HANGUL SYLLABLE TEOJ
+D147;D147;1110 1165 11BE;D147;1110 1165 11BE; # (í…‡; í…‡; á„ᅥᆾ; í…‡; á„ᅥᆾ; ) HANGUL SYLLABLE TEOC
+D148;D148;1110 1165 11BF;D148;1110 1165 11BF; # (í…ˆ; í…ˆ; á„ᅥᆿ; í…ˆ; á„ᅥᆿ; ) HANGUL SYLLABLE TEOK
+D149;D149;1110 1165 11C0;D149;1110 1165 11C0; # (í…‰; í…‰; á„ᅥᇀ; í…‰; á„ᅥᇀ; ) HANGUL SYLLABLE TEOT
+D14A;D14A;1110 1165 11C1;D14A;1110 1165 11C1; # (í…Š; í…Š; á„á…¥á‡; í…Š; á„á…¥á‡; ) HANGUL SYLLABLE TEOP
+D14B;D14B;1110 1165 11C2;D14B;1110 1165 11C2; # (í…‹; í…‹; á„ᅥᇂ; í…‹; á„ᅥᇂ; ) HANGUL SYLLABLE TEOH
+D14C;D14C;1110 1166;D14C;1110 1166; # (í…Œ; í…Œ; á„á…¦; í…Œ; á„á…¦; ) HANGUL SYLLABLE TE
+D14D;D14D;1110 1166 11A8;D14D;1110 1166 11A8; # (í…; í…; á„ᅦᆨ; í…; á„ᅦᆨ; ) HANGUL SYLLABLE TEG
+D14E;D14E;1110 1166 11A9;D14E;1110 1166 11A9; # (í…Ž; í…Ž; á„ᅦᆩ; í…Ž; á„ᅦᆩ; ) HANGUL SYLLABLE TEGG
+D14F;D14F;1110 1166 11AA;D14F;1110 1166 11AA; # (í…; í…; á„ᅦᆪ; í…; á„ᅦᆪ; ) HANGUL SYLLABLE TEGS
+D150;D150;1110 1166 11AB;D150;1110 1166 11AB; # (í…; í…; á„ᅦᆫ; í…; á„ᅦᆫ; ) HANGUL SYLLABLE TEN
+D151;D151;1110 1166 11AC;D151;1110 1166 11AC; # (í…‘; í…‘; á„ᅦᆬ; í…‘; á„ᅦᆬ; ) HANGUL SYLLABLE TENJ
+D152;D152;1110 1166 11AD;D152;1110 1166 11AD; # (í…’; í…’; á„ᅦᆭ; í…’; á„ᅦᆭ; ) HANGUL SYLLABLE TENH
+D153;D153;1110 1166 11AE;D153;1110 1166 11AE; # (í…“; í…“; á„ᅦᆮ; í…“; á„ᅦᆮ; ) HANGUL SYLLABLE TED
+D154;D154;1110 1166 11AF;D154;1110 1166 11AF; # (í…”; í…”; á„ᅦᆯ; í…”; á„ᅦᆯ; ) HANGUL SYLLABLE TEL
+D155;D155;1110 1166 11B0;D155;1110 1166 11B0; # (í…•; í…•; á„ᅦᆰ; í…•; á„ᅦᆰ; ) HANGUL SYLLABLE TELG
+D156;D156;1110 1166 11B1;D156;1110 1166 11B1; # (í…–; í…–; á„ᅦᆱ; í…–; á„ᅦᆱ; ) HANGUL SYLLABLE TELM
+D157;D157;1110 1166 11B2;D157;1110 1166 11B2; # (í…—; í…—; á„ᅦᆲ; í…—; á„ᅦᆲ; ) HANGUL SYLLABLE TELB
+D158;D158;1110 1166 11B3;D158;1110 1166 11B3; # (í…˜; í…˜; á„ᅦᆳ; í…˜; á„ᅦᆳ; ) HANGUL SYLLABLE TELS
+D159;D159;1110 1166 11B4;D159;1110 1166 11B4; # (í…™; í…™; á„ᅦᆴ; í…™; á„ᅦᆴ; ) HANGUL SYLLABLE TELT
+D15A;D15A;1110 1166 11B5;D15A;1110 1166 11B5; # (í…š; í…š; á„ᅦᆵ; í…š; á„ᅦᆵ; ) HANGUL SYLLABLE TELP
+D15B;D15B;1110 1166 11B6;D15B;1110 1166 11B6; # (í…›; í…›; á„ᅦᆶ; í…›; á„ᅦᆶ; ) HANGUL SYLLABLE TELH
+D15C;D15C;1110 1166 11B7;D15C;1110 1166 11B7; # (í…œ; í…œ; á„ᅦᆷ; í…œ; á„ᅦᆷ; ) HANGUL SYLLABLE TEM
+D15D;D15D;1110 1166 11B8;D15D;1110 1166 11B8; # (í…; í…; á„ᅦᆸ; í…; á„ᅦᆸ; ) HANGUL SYLLABLE TEB
+D15E;D15E;1110 1166 11B9;D15E;1110 1166 11B9; # (í…ž; í…ž; á„ᅦᆹ; í…ž; á„ᅦᆹ; ) HANGUL SYLLABLE TEBS
+D15F;D15F;1110 1166 11BA;D15F;1110 1166 11BA; # (í…Ÿ; í…Ÿ; á„ᅦᆺ; í…Ÿ; á„ᅦᆺ; ) HANGUL SYLLABLE TES
+D160;D160;1110 1166 11BB;D160;1110 1166 11BB; # (í… ; í… ; á„ᅦᆻ; í… ; á„ᅦᆻ; ) HANGUL SYLLABLE TESS
+D161;D161;1110 1166 11BC;D161;1110 1166 11BC; # (í…¡; í…¡; á„ᅦᆼ; í…¡; á„ᅦᆼ; ) HANGUL SYLLABLE TENG
+D162;D162;1110 1166 11BD;D162;1110 1166 11BD; # (í…¢; í…¢; á„ᅦᆽ; í…¢; á„ᅦᆽ; ) HANGUL SYLLABLE TEJ
+D163;D163;1110 1166 11BE;D163;1110 1166 11BE; # (í…£; í…£; á„ᅦᆾ; í…£; á„ᅦᆾ; ) HANGUL SYLLABLE TEC
+D164;D164;1110 1166 11BF;D164;1110 1166 11BF; # (í…¤; í…¤; á„ᅦᆿ; í…¤; á„ᅦᆿ; ) HANGUL SYLLABLE TEK
+D165;D165;1110 1166 11C0;D165;1110 1166 11C0; # (í…¥; í…¥; á„ᅦᇀ; í…¥; á„ᅦᇀ; ) HANGUL SYLLABLE TET
+D166;D166;1110 1166 11C1;D166;1110 1166 11C1; # (í…¦; í…¦; á„á…¦á‡; í…¦; á„á…¦á‡; ) HANGUL SYLLABLE TEP
+D167;D167;1110 1166 11C2;D167;1110 1166 11C2; # (í…§; í…§; á„ᅦᇂ; í…§; á„ᅦᇂ; ) HANGUL SYLLABLE TEH
+D168;D168;1110 1167;D168;1110 1167; # (í…¨; í…¨; á„á…§; í…¨; á„á…§; ) HANGUL SYLLABLE TYEO
+D169;D169;1110 1167 11A8;D169;1110 1167 11A8; # (í…©; í…©; á„ᅧᆨ; í…©; á„ᅧᆨ; ) HANGUL SYLLABLE TYEOG
+D16A;D16A;1110 1167 11A9;D16A;1110 1167 11A9; # (í…ª; í…ª; á„ᅧᆩ; í…ª; á„ᅧᆩ; ) HANGUL SYLLABLE TYEOGG
+D16B;D16B;1110 1167 11AA;D16B;1110 1167 11AA; # (í…«; í…«; á„ᅧᆪ; í…«; á„ᅧᆪ; ) HANGUL SYLLABLE TYEOGS
+D16C;D16C;1110 1167 11AB;D16C;1110 1167 11AB; # (í…¬; í…¬; á„ᅧᆫ; í…¬; á„ᅧᆫ; ) HANGUL SYLLABLE TYEON
+D16D;D16D;1110 1167 11AC;D16D;1110 1167 11AC; # (í…­; í…­; á„ᅧᆬ; í…­; á„ᅧᆬ; ) HANGUL SYLLABLE TYEONJ
+D16E;D16E;1110 1167 11AD;D16E;1110 1167 11AD; # (í…®; í…®; á„ᅧᆭ; í…®; á„ᅧᆭ; ) HANGUL SYLLABLE TYEONH
+D16F;D16F;1110 1167 11AE;D16F;1110 1167 11AE; # (í…¯; í…¯; á„ᅧᆮ; í…¯; á„ᅧᆮ; ) HANGUL SYLLABLE TYEOD
+D170;D170;1110 1167 11AF;D170;1110 1167 11AF; # (í…°; í…°; á„ᅧᆯ; í…°; á„ᅧᆯ; ) HANGUL SYLLABLE TYEOL
+D171;D171;1110 1167 11B0;D171;1110 1167 11B0; # (í…±; í…±; á„ᅧᆰ; í…±; á„ᅧᆰ; ) HANGUL SYLLABLE TYEOLG
+D172;D172;1110 1167 11B1;D172;1110 1167 11B1; # (í…²; í…²; á„ᅧᆱ; í…²; á„ᅧᆱ; ) HANGUL SYLLABLE TYEOLM
+D173;D173;1110 1167 11B2;D173;1110 1167 11B2; # (í…³; í…³; á„ᅧᆲ; í…³; á„ᅧᆲ; ) HANGUL SYLLABLE TYEOLB
+D174;D174;1110 1167 11B3;D174;1110 1167 11B3; # (í…´; í…´; á„ᅧᆳ; í…´; á„ᅧᆳ; ) HANGUL SYLLABLE TYEOLS
+D175;D175;1110 1167 11B4;D175;1110 1167 11B4; # (í…µ; í…µ; á„ᅧᆴ; í…µ; á„ᅧᆴ; ) HANGUL SYLLABLE TYEOLT
+D176;D176;1110 1167 11B5;D176;1110 1167 11B5; # (í…¶; í…¶; á„ᅧᆵ; í…¶; á„ᅧᆵ; ) HANGUL SYLLABLE TYEOLP
+D177;D177;1110 1167 11B6;D177;1110 1167 11B6; # (í…·; í…·; á„ᅧᆶ; í…·; á„ᅧᆶ; ) HANGUL SYLLABLE TYEOLH
+D178;D178;1110 1167 11B7;D178;1110 1167 11B7; # (í…¸; í…¸; á„ᅧᆷ; í…¸; á„ᅧᆷ; ) HANGUL SYLLABLE TYEOM
+D179;D179;1110 1167 11B8;D179;1110 1167 11B8; # (í…¹; í…¹; á„ᅧᆸ; í…¹; á„ᅧᆸ; ) HANGUL SYLLABLE TYEOB
+D17A;D17A;1110 1167 11B9;D17A;1110 1167 11B9; # (í…º; í…º; á„ᅧᆹ; í…º; á„ᅧᆹ; ) HANGUL SYLLABLE TYEOBS
+D17B;D17B;1110 1167 11BA;D17B;1110 1167 11BA; # (í…»; í…»; á„ᅧᆺ; í…»; á„ᅧᆺ; ) HANGUL SYLLABLE TYEOS
+D17C;D17C;1110 1167 11BB;D17C;1110 1167 11BB; # (í…¼; í…¼; á„ᅧᆻ; í…¼; á„ᅧᆻ; ) HANGUL SYLLABLE TYEOSS
+D17D;D17D;1110 1167 11BC;D17D;1110 1167 11BC; # (í…½; í…½; á„ᅧᆼ; í…½; á„ᅧᆼ; ) HANGUL SYLLABLE TYEONG
+D17E;D17E;1110 1167 11BD;D17E;1110 1167 11BD; # (í…¾; í…¾; á„ᅧᆽ; í…¾; á„ᅧᆽ; ) HANGUL SYLLABLE TYEOJ
+D17F;D17F;1110 1167 11BE;D17F;1110 1167 11BE; # (í…¿; í…¿; á„ᅧᆾ; í…¿; á„ᅧᆾ; ) HANGUL SYLLABLE TYEOC
+D180;D180;1110 1167 11BF;D180;1110 1167 11BF; # (톀; 톀; á„ᅧᆿ; 톀; á„ᅧᆿ; ) HANGUL SYLLABLE TYEOK
+D181;D181;1110 1167 11C0;D181;1110 1167 11C0; # (í†; í†; á„ᅧᇀ; í†; á„ᅧᇀ; ) HANGUL SYLLABLE TYEOT
+D182;D182;1110 1167 11C1;D182;1110 1167 11C1; # (톂; 톂; á„á…§á‡; 톂; á„á…§á‡; ) HANGUL SYLLABLE TYEOP
+D183;D183;1110 1167 11C2;D183;1110 1167 11C2; # (톃; 톃; á„ᅧᇂ; 톃; á„ᅧᇂ; ) HANGUL SYLLABLE TYEOH
+D184;D184;1110 1168;D184;1110 1168; # (톄; 톄; á„á…¨; 톄; á„á…¨; ) HANGUL SYLLABLE TYE
+D185;D185;1110 1168 11A8;D185;1110 1168 11A8; # (톅; 톅; á„ᅨᆨ; 톅; á„ᅨᆨ; ) HANGUL SYLLABLE TYEG
+D186;D186;1110 1168 11A9;D186;1110 1168 11A9; # (톆; 톆; á„ᅨᆩ; 톆; á„ᅨᆩ; ) HANGUL SYLLABLE TYEGG
+D187;D187;1110 1168 11AA;D187;1110 1168 11AA; # (톇; 톇; á„ᅨᆪ; 톇; á„ᅨᆪ; ) HANGUL SYLLABLE TYEGS
+D188;D188;1110 1168 11AB;D188;1110 1168 11AB; # (톈; 톈; á„ᅨᆫ; 톈; á„ᅨᆫ; ) HANGUL SYLLABLE TYEN
+D189;D189;1110 1168 11AC;D189;1110 1168 11AC; # (톉; 톉; á„ᅨᆬ; 톉; á„ᅨᆬ; ) HANGUL SYLLABLE TYENJ
+D18A;D18A;1110 1168 11AD;D18A;1110 1168 11AD; # (톊; 톊; á„ᅨᆭ; 톊; á„ᅨᆭ; ) HANGUL SYLLABLE TYENH
+D18B;D18B;1110 1168 11AE;D18B;1110 1168 11AE; # (톋; 톋; á„ᅨᆮ; 톋; á„ᅨᆮ; ) HANGUL SYLLABLE TYED
+D18C;D18C;1110 1168 11AF;D18C;1110 1168 11AF; # (톌; 톌; á„ᅨᆯ; 톌; á„ᅨᆯ; ) HANGUL SYLLABLE TYEL
+D18D;D18D;1110 1168 11B0;D18D;1110 1168 11B0; # (í†; í†; á„ᅨᆰ; í†; á„ᅨᆰ; ) HANGUL SYLLABLE TYELG
+D18E;D18E;1110 1168 11B1;D18E;1110 1168 11B1; # (톎; 톎; á„ᅨᆱ; 톎; á„ᅨᆱ; ) HANGUL SYLLABLE TYELM
+D18F;D18F;1110 1168 11B2;D18F;1110 1168 11B2; # (í†; í†; á„ᅨᆲ; í†; á„ᅨᆲ; ) HANGUL SYLLABLE TYELB
+D190;D190;1110 1168 11B3;D190;1110 1168 11B3; # (í†; í†; á„ᅨᆳ; í†; á„ᅨᆳ; ) HANGUL SYLLABLE TYELS
+D191;D191;1110 1168 11B4;D191;1110 1168 11B4; # (톑; 톑; á„ᅨᆴ; 톑; á„ᅨᆴ; ) HANGUL SYLLABLE TYELT
+D192;D192;1110 1168 11B5;D192;1110 1168 11B5; # (톒; 톒; á„ᅨᆵ; 톒; á„ᅨᆵ; ) HANGUL SYLLABLE TYELP
+D193;D193;1110 1168 11B6;D193;1110 1168 11B6; # (톓; 톓; á„ᅨᆶ; 톓; á„ᅨᆶ; ) HANGUL SYLLABLE TYELH
+D194;D194;1110 1168 11B7;D194;1110 1168 11B7; # (톔; 톔; á„ᅨᆷ; 톔; á„ᅨᆷ; ) HANGUL SYLLABLE TYEM
+D195;D195;1110 1168 11B8;D195;1110 1168 11B8; # (톕; 톕; á„ᅨᆸ; 톕; á„ᅨᆸ; ) HANGUL SYLLABLE TYEB
+D196;D196;1110 1168 11B9;D196;1110 1168 11B9; # (톖; 톖; á„ᅨᆹ; 톖; á„ᅨᆹ; ) HANGUL SYLLABLE TYEBS
+D197;D197;1110 1168 11BA;D197;1110 1168 11BA; # (톗; 톗; á„ᅨᆺ; 톗; á„ᅨᆺ; ) HANGUL SYLLABLE TYES
+D198;D198;1110 1168 11BB;D198;1110 1168 11BB; # (톘; 톘; á„ᅨᆻ; 톘; á„ᅨᆻ; ) HANGUL SYLLABLE TYESS
+D199;D199;1110 1168 11BC;D199;1110 1168 11BC; # (톙; 톙; á„ᅨᆼ; 톙; á„ᅨᆼ; ) HANGUL SYLLABLE TYENG
+D19A;D19A;1110 1168 11BD;D19A;1110 1168 11BD; # (톚; 톚; á„ᅨᆽ; 톚; á„ᅨᆽ; ) HANGUL SYLLABLE TYEJ
+D19B;D19B;1110 1168 11BE;D19B;1110 1168 11BE; # (톛; 톛; á„ᅨᆾ; 톛; á„ᅨᆾ; ) HANGUL SYLLABLE TYEC
+D19C;D19C;1110 1168 11BF;D19C;1110 1168 11BF; # (톜; 톜; á„ᅨᆿ; 톜; á„ᅨᆿ; ) HANGUL SYLLABLE TYEK
+D19D;D19D;1110 1168 11C0;D19D;1110 1168 11C0; # (í†; í†; á„ᅨᇀ; í†; á„ᅨᇀ; ) HANGUL SYLLABLE TYET
+D19E;D19E;1110 1168 11C1;D19E;1110 1168 11C1; # (톞; 톞; á„á…¨á‡; 톞; á„á…¨á‡; ) HANGUL SYLLABLE TYEP
+D19F;D19F;1110 1168 11C2;D19F;1110 1168 11C2; # (톟; 톟; á„ᅨᇂ; 톟; á„ᅨᇂ; ) HANGUL SYLLABLE TYEH
+D1A0;D1A0;1110 1169;D1A0;1110 1169; # (토; 토; á„á…©; 토; á„á…©; ) HANGUL SYLLABLE TO
+D1A1;D1A1;1110 1169 11A8;D1A1;1110 1169 11A8; # (톡; 톡; á„ᅩᆨ; 톡; á„ᅩᆨ; ) HANGUL SYLLABLE TOG
+D1A2;D1A2;1110 1169 11A9;D1A2;1110 1169 11A9; # (톢; 톢; á„ᅩᆩ; 톢; á„ᅩᆩ; ) HANGUL SYLLABLE TOGG
+D1A3;D1A3;1110 1169 11AA;D1A3;1110 1169 11AA; # (톣; 톣; á„ᅩᆪ; 톣; á„ᅩᆪ; ) HANGUL SYLLABLE TOGS
+D1A4;D1A4;1110 1169 11AB;D1A4;1110 1169 11AB; # (톤; 톤; á„ᅩᆫ; 톤; á„ᅩᆫ; ) HANGUL SYLLABLE TON
+D1A5;D1A5;1110 1169 11AC;D1A5;1110 1169 11AC; # (톥; 톥; á„ᅩᆬ; 톥; á„ᅩᆬ; ) HANGUL SYLLABLE TONJ
+D1A6;D1A6;1110 1169 11AD;D1A6;1110 1169 11AD; # (톦; 톦; á„ᅩᆭ; 톦; á„ᅩᆭ; ) HANGUL SYLLABLE TONH
+D1A7;D1A7;1110 1169 11AE;D1A7;1110 1169 11AE; # (톧; 톧; á„ᅩᆮ; 톧; á„ᅩᆮ; ) HANGUL SYLLABLE TOD
+D1A8;D1A8;1110 1169 11AF;D1A8;1110 1169 11AF; # (톨; 톨; á„ᅩᆯ; 톨; á„ᅩᆯ; ) HANGUL SYLLABLE TOL
+D1A9;D1A9;1110 1169 11B0;D1A9;1110 1169 11B0; # (톩; 톩; á„ᅩᆰ; 톩; á„ᅩᆰ; ) HANGUL SYLLABLE TOLG
+D1AA;D1AA;1110 1169 11B1;D1AA;1110 1169 11B1; # (톪; 톪; á„ᅩᆱ; 톪; á„ᅩᆱ; ) HANGUL SYLLABLE TOLM
+D1AB;D1AB;1110 1169 11B2;D1AB;1110 1169 11B2; # (톫; 톫; á„ᅩᆲ; 톫; á„ᅩᆲ; ) HANGUL SYLLABLE TOLB
+D1AC;D1AC;1110 1169 11B3;D1AC;1110 1169 11B3; # (톬; 톬; á„ᅩᆳ; 톬; á„ᅩᆳ; ) HANGUL SYLLABLE TOLS
+D1AD;D1AD;1110 1169 11B4;D1AD;1110 1169 11B4; # (톭; 톭; á„ᅩᆴ; 톭; á„ᅩᆴ; ) HANGUL SYLLABLE TOLT
+D1AE;D1AE;1110 1169 11B5;D1AE;1110 1169 11B5; # (톮; 톮; á„ᅩᆵ; 톮; á„ᅩᆵ; ) HANGUL SYLLABLE TOLP
+D1AF;D1AF;1110 1169 11B6;D1AF;1110 1169 11B6; # (톯; 톯; á„ᅩᆶ; 톯; á„ᅩᆶ; ) HANGUL SYLLABLE TOLH
+D1B0;D1B0;1110 1169 11B7;D1B0;1110 1169 11B7; # (톰; 톰; á„ᅩᆷ; 톰; á„ᅩᆷ; ) HANGUL SYLLABLE TOM
+D1B1;D1B1;1110 1169 11B8;D1B1;1110 1169 11B8; # (톱; 톱; á„ᅩᆸ; 톱; á„ᅩᆸ; ) HANGUL SYLLABLE TOB
+D1B2;D1B2;1110 1169 11B9;D1B2;1110 1169 11B9; # (톲; 톲; á„ᅩᆹ; 톲; á„ᅩᆹ; ) HANGUL SYLLABLE TOBS
+D1B3;D1B3;1110 1169 11BA;D1B3;1110 1169 11BA; # (톳; 톳; á„ᅩᆺ; 톳; á„ᅩᆺ; ) HANGUL SYLLABLE TOS
+D1B4;D1B4;1110 1169 11BB;D1B4;1110 1169 11BB; # (톴; 톴; á„ᅩᆻ; 톴; á„ᅩᆻ; ) HANGUL SYLLABLE TOSS
+D1B5;D1B5;1110 1169 11BC;D1B5;1110 1169 11BC; # (통; 통; á„ᅩᆼ; 통; á„ᅩᆼ; ) HANGUL SYLLABLE TONG
+D1B6;D1B6;1110 1169 11BD;D1B6;1110 1169 11BD; # (톶; 톶; á„ᅩᆽ; 톶; á„ᅩᆽ; ) HANGUL SYLLABLE TOJ
+D1B7;D1B7;1110 1169 11BE;D1B7;1110 1169 11BE; # (톷; 톷; á„ᅩᆾ; 톷; á„ᅩᆾ; ) HANGUL SYLLABLE TOC
+D1B8;D1B8;1110 1169 11BF;D1B8;1110 1169 11BF; # (톸; 톸; á„ᅩᆿ; 톸; á„ᅩᆿ; ) HANGUL SYLLABLE TOK
+D1B9;D1B9;1110 1169 11C0;D1B9;1110 1169 11C0; # (톹; 톹; á„ᅩᇀ; 톹; á„ᅩᇀ; ) HANGUL SYLLABLE TOT
+D1BA;D1BA;1110 1169 11C1;D1BA;1110 1169 11C1; # (톺; 톺; á„á…©á‡; 톺; á„á…©á‡; ) HANGUL SYLLABLE TOP
+D1BB;D1BB;1110 1169 11C2;D1BB;1110 1169 11C2; # (톻; 톻; á„ᅩᇂ; 톻; á„ᅩᇂ; ) HANGUL SYLLABLE TOH
+D1BC;D1BC;1110 116A;D1BC;1110 116A; # (톼; 톼; á„á…ª; 톼; á„á…ª; ) HANGUL SYLLABLE TWA
+D1BD;D1BD;1110 116A 11A8;D1BD;1110 116A 11A8; # (톽; 톽; á„ᅪᆨ; 톽; á„ᅪᆨ; ) HANGUL SYLLABLE TWAG
+D1BE;D1BE;1110 116A 11A9;D1BE;1110 116A 11A9; # (톾; 톾; á„ᅪᆩ; 톾; á„ᅪᆩ; ) HANGUL SYLLABLE TWAGG
+D1BF;D1BF;1110 116A 11AA;D1BF;1110 116A 11AA; # (톿; 톿; á„ᅪᆪ; 톿; á„ᅪᆪ; ) HANGUL SYLLABLE TWAGS
+D1C0;D1C0;1110 116A 11AB;D1C0;1110 116A 11AB; # (퇀; 퇀; á„ᅪᆫ; 퇀; á„ᅪᆫ; ) HANGUL SYLLABLE TWAN
+D1C1;D1C1;1110 116A 11AC;D1C1;1110 116A 11AC; # (í‡; í‡; á„ᅪᆬ; í‡; á„ᅪᆬ; ) HANGUL SYLLABLE TWANJ
+D1C2;D1C2;1110 116A 11AD;D1C2;1110 116A 11AD; # (퇂; 퇂; á„ᅪᆭ; 퇂; á„ᅪᆭ; ) HANGUL SYLLABLE TWANH
+D1C3;D1C3;1110 116A 11AE;D1C3;1110 116A 11AE; # (퇃; 퇃; á„ᅪᆮ; 퇃; á„ᅪᆮ; ) HANGUL SYLLABLE TWAD
+D1C4;D1C4;1110 116A 11AF;D1C4;1110 116A 11AF; # (퇄; 퇄; á„ᅪᆯ; 퇄; á„ᅪᆯ; ) HANGUL SYLLABLE TWAL
+D1C5;D1C5;1110 116A 11B0;D1C5;1110 116A 11B0; # (퇅; 퇅; á„ᅪᆰ; 퇅; á„ᅪᆰ; ) HANGUL SYLLABLE TWALG
+D1C6;D1C6;1110 116A 11B1;D1C6;1110 116A 11B1; # (퇆; 퇆; á„ᅪᆱ; 퇆; á„ᅪᆱ; ) HANGUL SYLLABLE TWALM
+D1C7;D1C7;1110 116A 11B2;D1C7;1110 116A 11B2; # (퇇; 퇇; á„ᅪᆲ; 퇇; á„ᅪᆲ; ) HANGUL SYLLABLE TWALB
+D1C8;D1C8;1110 116A 11B3;D1C8;1110 116A 11B3; # (퇈; 퇈; á„ᅪᆳ; 퇈; á„ᅪᆳ; ) HANGUL SYLLABLE TWALS
+D1C9;D1C9;1110 116A 11B4;D1C9;1110 116A 11B4; # (퇉; 퇉; á„ᅪᆴ; 퇉; á„ᅪᆴ; ) HANGUL SYLLABLE TWALT
+D1CA;D1CA;1110 116A 11B5;D1CA;1110 116A 11B5; # (퇊; 퇊; á„ᅪᆵ; 퇊; á„ᅪᆵ; ) HANGUL SYLLABLE TWALP
+D1CB;D1CB;1110 116A 11B6;D1CB;1110 116A 11B6; # (퇋; 퇋; á„ᅪᆶ; 퇋; á„ᅪᆶ; ) HANGUL SYLLABLE TWALH
+D1CC;D1CC;1110 116A 11B7;D1CC;1110 116A 11B7; # (퇌; 퇌; á„ᅪᆷ; 퇌; á„ᅪᆷ; ) HANGUL SYLLABLE TWAM
+D1CD;D1CD;1110 116A 11B8;D1CD;1110 116A 11B8; # (í‡; í‡; á„ᅪᆸ; í‡; á„ᅪᆸ; ) HANGUL SYLLABLE TWAB
+D1CE;D1CE;1110 116A 11B9;D1CE;1110 116A 11B9; # (퇎; 퇎; á„ᅪᆹ; 퇎; á„ᅪᆹ; ) HANGUL SYLLABLE TWABS
+D1CF;D1CF;1110 116A 11BA;D1CF;1110 116A 11BA; # (í‡; í‡; á„ᅪᆺ; í‡; á„ᅪᆺ; ) HANGUL SYLLABLE TWAS
+D1D0;D1D0;1110 116A 11BB;D1D0;1110 116A 11BB; # (í‡; í‡; á„ᅪᆻ; í‡; á„ᅪᆻ; ) HANGUL SYLLABLE TWASS
+D1D1;D1D1;1110 116A 11BC;D1D1;1110 116A 11BC; # (퇑; 퇑; á„ᅪᆼ; 퇑; á„ᅪᆼ; ) HANGUL SYLLABLE TWANG
+D1D2;D1D2;1110 116A 11BD;D1D2;1110 116A 11BD; # (퇒; 퇒; á„ᅪᆽ; 퇒; á„ᅪᆽ; ) HANGUL SYLLABLE TWAJ
+D1D3;D1D3;1110 116A 11BE;D1D3;1110 116A 11BE; # (퇓; 퇓; á„ᅪᆾ; 퇓; á„ᅪᆾ; ) HANGUL SYLLABLE TWAC
+D1D4;D1D4;1110 116A 11BF;D1D4;1110 116A 11BF; # (퇔; 퇔; á„ᅪᆿ; 퇔; á„ᅪᆿ; ) HANGUL SYLLABLE TWAK
+D1D5;D1D5;1110 116A 11C0;D1D5;1110 116A 11C0; # (퇕; 퇕; á„ᅪᇀ; 퇕; á„ᅪᇀ; ) HANGUL SYLLABLE TWAT
+D1D6;D1D6;1110 116A 11C1;D1D6;1110 116A 11C1; # (퇖; 퇖; á„á…ªá‡; 퇖; á„á…ªá‡; ) HANGUL SYLLABLE TWAP
+D1D7;D1D7;1110 116A 11C2;D1D7;1110 116A 11C2; # (퇗; 퇗; á„ᅪᇂ; 퇗; á„ᅪᇂ; ) HANGUL SYLLABLE TWAH
+D1D8;D1D8;1110 116B;D1D8;1110 116B; # (퇘; 퇘; á„á…«; 퇘; á„á…«; ) HANGUL SYLLABLE TWAE
+D1D9;D1D9;1110 116B 11A8;D1D9;1110 116B 11A8; # (퇙; 퇙; á„ᅫᆨ; 퇙; á„ᅫᆨ; ) HANGUL SYLLABLE TWAEG
+D1DA;D1DA;1110 116B 11A9;D1DA;1110 116B 11A9; # (퇚; 퇚; á„ᅫᆩ; 퇚; á„ᅫᆩ; ) HANGUL SYLLABLE TWAEGG
+D1DB;D1DB;1110 116B 11AA;D1DB;1110 116B 11AA; # (퇛; 퇛; á„ᅫᆪ; 퇛; á„ᅫᆪ; ) HANGUL SYLLABLE TWAEGS
+D1DC;D1DC;1110 116B 11AB;D1DC;1110 116B 11AB; # (퇜; 퇜; á„ᅫᆫ; 퇜; á„ᅫᆫ; ) HANGUL SYLLABLE TWAEN
+D1DD;D1DD;1110 116B 11AC;D1DD;1110 116B 11AC; # (í‡; í‡; á„ᅫᆬ; í‡; á„ᅫᆬ; ) HANGUL SYLLABLE TWAENJ
+D1DE;D1DE;1110 116B 11AD;D1DE;1110 116B 11AD; # (퇞; 퇞; á„ᅫᆭ; 퇞; á„ᅫᆭ; ) HANGUL SYLLABLE TWAENH
+D1DF;D1DF;1110 116B 11AE;D1DF;1110 116B 11AE; # (퇟; 퇟; á„ᅫᆮ; 퇟; á„ᅫᆮ; ) HANGUL SYLLABLE TWAED
+D1E0;D1E0;1110 116B 11AF;D1E0;1110 116B 11AF; # (퇠; 퇠; á„ᅫᆯ; 퇠; á„ᅫᆯ; ) HANGUL SYLLABLE TWAEL
+D1E1;D1E1;1110 116B 11B0;D1E1;1110 116B 11B0; # (퇡; 퇡; á„ᅫᆰ; 퇡; á„ᅫᆰ; ) HANGUL SYLLABLE TWAELG
+D1E2;D1E2;1110 116B 11B1;D1E2;1110 116B 11B1; # (퇢; 퇢; á„ᅫᆱ; 퇢; á„ᅫᆱ; ) HANGUL SYLLABLE TWAELM
+D1E3;D1E3;1110 116B 11B2;D1E3;1110 116B 11B2; # (퇣; 퇣; á„ᅫᆲ; 퇣; á„ᅫᆲ; ) HANGUL SYLLABLE TWAELB
+D1E4;D1E4;1110 116B 11B3;D1E4;1110 116B 11B3; # (퇤; 퇤; á„ᅫᆳ; 퇤; á„ᅫᆳ; ) HANGUL SYLLABLE TWAELS
+D1E5;D1E5;1110 116B 11B4;D1E5;1110 116B 11B4; # (퇥; 퇥; á„ᅫᆴ; 퇥; á„ᅫᆴ; ) HANGUL SYLLABLE TWAELT
+D1E6;D1E6;1110 116B 11B5;D1E6;1110 116B 11B5; # (퇦; 퇦; á„ᅫᆵ; 퇦; á„ᅫᆵ; ) HANGUL SYLLABLE TWAELP
+D1E7;D1E7;1110 116B 11B6;D1E7;1110 116B 11B6; # (퇧; 퇧; á„ᅫᆶ; 퇧; á„ᅫᆶ; ) HANGUL SYLLABLE TWAELH
+D1E8;D1E8;1110 116B 11B7;D1E8;1110 116B 11B7; # (퇨; 퇨; á„ᅫᆷ; 퇨; á„ᅫᆷ; ) HANGUL SYLLABLE TWAEM
+D1E9;D1E9;1110 116B 11B8;D1E9;1110 116B 11B8; # (퇩; 퇩; á„ᅫᆸ; 퇩; á„ᅫᆸ; ) HANGUL SYLLABLE TWAEB
+D1EA;D1EA;1110 116B 11B9;D1EA;1110 116B 11B9; # (퇪; 퇪; á„ᅫᆹ; 퇪; á„ᅫᆹ; ) HANGUL SYLLABLE TWAEBS
+D1EB;D1EB;1110 116B 11BA;D1EB;1110 116B 11BA; # (퇫; 퇫; á„ᅫᆺ; 퇫; á„ᅫᆺ; ) HANGUL SYLLABLE TWAES
+D1EC;D1EC;1110 116B 11BB;D1EC;1110 116B 11BB; # (퇬; 퇬; á„ᅫᆻ; 퇬; á„ᅫᆻ; ) HANGUL SYLLABLE TWAESS
+D1ED;D1ED;1110 116B 11BC;D1ED;1110 116B 11BC; # (퇭; 퇭; á„ᅫᆼ; 퇭; á„ᅫᆼ; ) HANGUL SYLLABLE TWAENG
+D1EE;D1EE;1110 116B 11BD;D1EE;1110 116B 11BD; # (퇮; 퇮; á„ᅫᆽ; 퇮; á„ᅫᆽ; ) HANGUL SYLLABLE TWAEJ
+D1EF;D1EF;1110 116B 11BE;D1EF;1110 116B 11BE; # (퇯; 퇯; á„ᅫᆾ; 퇯; á„ᅫᆾ; ) HANGUL SYLLABLE TWAEC
+D1F0;D1F0;1110 116B 11BF;D1F0;1110 116B 11BF; # (퇰; 퇰; á„ᅫᆿ; 퇰; á„ᅫᆿ; ) HANGUL SYLLABLE TWAEK
+D1F1;D1F1;1110 116B 11C0;D1F1;1110 116B 11C0; # (퇱; 퇱; á„ᅫᇀ; 퇱; á„ᅫᇀ; ) HANGUL SYLLABLE TWAET
+D1F2;D1F2;1110 116B 11C1;D1F2;1110 116B 11C1; # (퇲; 퇲; á„á…«á‡; 퇲; á„á…«á‡; ) HANGUL SYLLABLE TWAEP
+D1F3;D1F3;1110 116B 11C2;D1F3;1110 116B 11C2; # (퇳; 퇳; á„ᅫᇂ; 퇳; á„ᅫᇂ; ) HANGUL SYLLABLE TWAEH
+D1F4;D1F4;1110 116C;D1F4;1110 116C; # (퇴; 퇴; á„á…¬; 퇴; á„á…¬; ) HANGUL SYLLABLE TOE
+D1F5;D1F5;1110 116C 11A8;D1F5;1110 116C 11A8; # (퇵; 퇵; á„ᅬᆨ; 퇵; á„ᅬᆨ; ) HANGUL SYLLABLE TOEG
+D1F6;D1F6;1110 116C 11A9;D1F6;1110 116C 11A9; # (퇶; 퇶; á„ᅬᆩ; 퇶; á„ᅬᆩ; ) HANGUL SYLLABLE TOEGG
+D1F7;D1F7;1110 116C 11AA;D1F7;1110 116C 11AA; # (퇷; 퇷; á„ᅬᆪ; 퇷; á„ᅬᆪ; ) HANGUL SYLLABLE TOEGS
+D1F8;D1F8;1110 116C 11AB;D1F8;1110 116C 11AB; # (퇸; 퇸; á„ᅬᆫ; 퇸; á„ᅬᆫ; ) HANGUL SYLLABLE TOEN
+D1F9;D1F9;1110 116C 11AC;D1F9;1110 116C 11AC; # (퇹; 퇹; á„ᅬᆬ; 퇹; á„ᅬᆬ; ) HANGUL SYLLABLE TOENJ
+D1FA;D1FA;1110 116C 11AD;D1FA;1110 116C 11AD; # (퇺; 퇺; á„ᅬᆭ; 퇺; á„ᅬᆭ; ) HANGUL SYLLABLE TOENH
+D1FB;D1FB;1110 116C 11AE;D1FB;1110 116C 11AE; # (퇻; 퇻; á„ᅬᆮ; 퇻; á„ᅬᆮ; ) HANGUL SYLLABLE TOED
+D1FC;D1FC;1110 116C 11AF;D1FC;1110 116C 11AF; # (퇼; 퇼; á„ᅬᆯ; 퇼; á„ᅬᆯ; ) HANGUL SYLLABLE TOEL
+D1FD;D1FD;1110 116C 11B0;D1FD;1110 116C 11B0; # (퇽; 퇽; á„ᅬᆰ; 퇽; á„ᅬᆰ; ) HANGUL SYLLABLE TOELG
+D1FE;D1FE;1110 116C 11B1;D1FE;1110 116C 11B1; # (퇾; 퇾; á„ᅬᆱ; 퇾; á„ᅬᆱ; ) HANGUL SYLLABLE TOELM
+D1FF;D1FF;1110 116C 11B2;D1FF;1110 116C 11B2; # (퇿; 퇿; á„ᅬᆲ; 퇿; á„ᅬᆲ; ) HANGUL SYLLABLE TOELB
+D200;D200;1110 116C 11B3;D200;1110 116C 11B3; # (툀; 툀; á„ᅬᆳ; 툀; á„ᅬᆳ; ) HANGUL SYLLABLE TOELS
+D201;D201;1110 116C 11B4;D201;1110 116C 11B4; # (íˆ; íˆ; á„ᅬᆴ; íˆ; á„ᅬᆴ; ) HANGUL SYLLABLE TOELT
+D202;D202;1110 116C 11B5;D202;1110 116C 11B5; # (툂; 툂; á„ᅬᆵ; 툂; á„ᅬᆵ; ) HANGUL SYLLABLE TOELP
+D203;D203;1110 116C 11B6;D203;1110 116C 11B6; # (툃; 툃; á„ᅬᆶ; 툃; á„ᅬᆶ; ) HANGUL SYLLABLE TOELH
+D204;D204;1110 116C 11B7;D204;1110 116C 11B7; # (툄; 툄; á„ᅬᆷ; 툄; á„ᅬᆷ; ) HANGUL SYLLABLE TOEM
+D205;D205;1110 116C 11B8;D205;1110 116C 11B8; # (툅; 툅; á„ᅬᆸ; 툅; á„ᅬᆸ; ) HANGUL SYLLABLE TOEB
+D206;D206;1110 116C 11B9;D206;1110 116C 11B9; # (툆; 툆; á„ᅬᆹ; 툆; á„ᅬᆹ; ) HANGUL SYLLABLE TOEBS
+D207;D207;1110 116C 11BA;D207;1110 116C 11BA; # (툇; 툇; á„ᅬᆺ; 툇; á„ᅬᆺ; ) HANGUL SYLLABLE TOES
+D208;D208;1110 116C 11BB;D208;1110 116C 11BB; # (툈; 툈; á„ᅬᆻ; 툈; á„ᅬᆻ; ) HANGUL SYLLABLE TOESS
+D209;D209;1110 116C 11BC;D209;1110 116C 11BC; # (툉; 툉; á„ᅬᆼ; 툉; á„ᅬᆼ; ) HANGUL SYLLABLE TOENG
+D20A;D20A;1110 116C 11BD;D20A;1110 116C 11BD; # (툊; 툊; á„ᅬᆽ; 툊; á„ᅬᆽ; ) HANGUL SYLLABLE TOEJ
+D20B;D20B;1110 116C 11BE;D20B;1110 116C 11BE; # (툋; 툋; á„ᅬᆾ; 툋; á„ᅬᆾ; ) HANGUL SYLLABLE TOEC
+D20C;D20C;1110 116C 11BF;D20C;1110 116C 11BF; # (툌; 툌; á„ᅬᆿ; 툌; á„ᅬᆿ; ) HANGUL SYLLABLE TOEK
+D20D;D20D;1110 116C 11C0;D20D;1110 116C 11C0; # (íˆ; íˆ; á„ᅬᇀ; íˆ; á„ᅬᇀ; ) HANGUL SYLLABLE TOET
+D20E;D20E;1110 116C 11C1;D20E;1110 116C 11C1; # (툎; 툎; á„á…¬á‡; 툎; á„á…¬á‡; ) HANGUL SYLLABLE TOEP
+D20F;D20F;1110 116C 11C2;D20F;1110 116C 11C2; # (íˆ; íˆ; á„ᅬᇂ; íˆ; á„ᅬᇂ; ) HANGUL SYLLABLE TOEH
+D210;D210;1110 116D;D210;1110 116D; # (íˆ; íˆ; á„á…­; íˆ; á„á…­; ) HANGUL SYLLABLE TYO
+D211;D211;1110 116D 11A8;D211;1110 116D 11A8; # (툑; 툑; á„ᅭᆨ; 툑; á„ᅭᆨ; ) HANGUL SYLLABLE TYOG
+D212;D212;1110 116D 11A9;D212;1110 116D 11A9; # (툒; 툒; á„ᅭᆩ; 툒; á„ᅭᆩ; ) HANGUL SYLLABLE TYOGG
+D213;D213;1110 116D 11AA;D213;1110 116D 11AA; # (툓; 툓; á„ᅭᆪ; 툓; á„ᅭᆪ; ) HANGUL SYLLABLE TYOGS
+D214;D214;1110 116D 11AB;D214;1110 116D 11AB; # (툔; 툔; á„ᅭᆫ; 툔; á„ᅭᆫ; ) HANGUL SYLLABLE TYON
+D215;D215;1110 116D 11AC;D215;1110 116D 11AC; # (툕; 툕; á„ᅭᆬ; 툕; á„ᅭᆬ; ) HANGUL SYLLABLE TYONJ
+D216;D216;1110 116D 11AD;D216;1110 116D 11AD; # (툖; 툖; á„ᅭᆭ; 툖; á„ᅭᆭ; ) HANGUL SYLLABLE TYONH
+D217;D217;1110 116D 11AE;D217;1110 116D 11AE; # (툗; 툗; á„ᅭᆮ; 툗; á„ᅭᆮ; ) HANGUL SYLLABLE TYOD
+D218;D218;1110 116D 11AF;D218;1110 116D 11AF; # (툘; 툘; á„ᅭᆯ; 툘; á„ᅭᆯ; ) HANGUL SYLLABLE TYOL
+D219;D219;1110 116D 11B0;D219;1110 116D 11B0; # (툙; 툙; á„ᅭᆰ; 툙; á„ᅭᆰ; ) HANGUL SYLLABLE TYOLG
+D21A;D21A;1110 116D 11B1;D21A;1110 116D 11B1; # (툚; 툚; á„ᅭᆱ; 툚; á„ᅭᆱ; ) HANGUL SYLLABLE TYOLM
+D21B;D21B;1110 116D 11B2;D21B;1110 116D 11B2; # (툛; 툛; á„ᅭᆲ; 툛; á„ᅭᆲ; ) HANGUL SYLLABLE TYOLB
+D21C;D21C;1110 116D 11B3;D21C;1110 116D 11B3; # (툜; 툜; á„ᅭᆳ; 툜; á„ᅭᆳ; ) HANGUL SYLLABLE TYOLS
+D21D;D21D;1110 116D 11B4;D21D;1110 116D 11B4; # (íˆ; íˆ; á„ᅭᆴ; íˆ; á„ᅭᆴ; ) HANGUL SYLLABLE TYOLT
+D21E;D21E;1110 116D 11B5;D21E;1110 116D 11B5; # (툞; 툞; á„ᅭᆵ; 툞; á„ᅭᆵ; ) HANGUL SYLLABLE TYOLP
+D21F;D21F;1110 116D 11B6;D21F;1110 116D 11B6; # (툟; 툟; á„ᅭᆶ; 툟; á„ᅭᆶ; ) HANGUL SYLLABLE TYOLH
+D220;D220;1110 116D 11B7;D220;1110 116D 11B7; # (툠; 툠; á„ᅭᆷ; 툠; á„ᅭᆷ; ) HANGUL SYLLABLE TYOM
+D221;D221;1110 116D 11B8;D221;1110 116D 11B8; # (툡; 툡; á„ᅭᆸ; 툡; á„ᅭᆸ; ) HANGUL SYLLABLE TYOB
+D222;D222;1110 116D 11B9;D222;1110 116D 11B9; # (툢; 툢; á„ᅭᆹ; 툢; á„ᅭᆹ; ) HANGUL SYLLABLE TYOBS
+D223;D223;1110 116D 11BA;D223;1110 116D 11BA; # (툣; 툣; á„ᅭᆺ; 툣; á„ᅭᆺ; ) HANGUL SYLLABLE TYOS
+D224;D224;1110 116D 11BB;D224;1110 116D 11BB; # (툤; 툤; á„ᅭᆻ; 툤; á„ᅭᆻ; ) HANGUL SYLLABLE TYOSS
+D225;D225;1110 116D 11BC;D225;1110 116D 11BC; # (툥; 툥; á„ᅭᆼ; 툥; á„ᅭᆼ; ) HANGUL SYLLABLE TYONG
+D226;D226;1110 116D 11BD;D226;1110 116D 11BD; # (툦; 툦; á„ᅭᆽ; 툦; á„ᅭᆽ; ) HANGUL SYLLABLE TYOJ
+D227;D227;1110 116D 11BE;D227;1110 116D 11BE; # (툧; 툧; á„ᅭᆾ; 툧; á„ᅭᆾ; ) HANGUL SYLLABLE TYOC
+D228;D228;1110 116D 11BF;D228;1110 116D 11BF; # (툨; 툨; á„ᅭᆿ; 툨; á„ᅭᆿ; ) HANGUL SYLLABLE TYOK
+D229;D229;1110 116D 11C0;D229;1110 116D 11C0; # (툩; 툩; á„ᅭᇀ; 툩; á„ᅭᇀ; ) HANGUL SYLLABLE TYOT
+D22A;D22A;1110 116D 11C1;D22A;1110 116D 11C1; # (툪; 툪; á„á…­á‡; 툪; á„á…­á‡; ) HANGUL SYLLABLE TYOP
+D22B;D22B;1110 116D 11C2;D22B;1110 116D 11C2; # (툫; 툫; á„ᅭᇂ; 툫; á„ᅭᇂ; ) HANGUL SYLLABLE TYOH
+D22C;D22C;1110 116E;D22C;1110 116E; # (투; 투; á„á…®; 투; á„á…®; ) HANGUL SYLLABLE TU
+D22D;D22D;1110 116E 11A8;D22D;1110 116E 11A8; # (툭; 툭; á„ᅮᆨ; 툭; á„ᅮᆨ; ) HANGUL SYLLABLE TUG
+D22E;D22E;1110 116E 11A9;D22E;1110 116E 11A9; # (툮; 툮; á„ᅮᆩ; 툮; á„ᅮᆩ; ) HANGUL SYLLABLE TUGG
+D22F;D22F;1110 116E 11AA;D22F;1110 116E 11AA; # (툯; 툯; á„ᅮᆪ; 툯; á„ᅮᆪ; ) HANGUL SYLLABLE TUGS
+D230;D230;1110 116E 11AB;D230;1110 116E 11AB; # (툰; 툰; á„ᅮᆫ; 툰; á„ᅮᆫ; ) HANGUL SYLLABLE TUN
+D231;D231;1110 116E 11AC;D231;1110 116E 11AC; # (툱; 툱; á„ᅮᆬ; 툱; á„ᅮᆬ; ) HANGUL SYLLABLE TUNJ
+D232;D232;1110 116E 11AD;D232;1110 116E 11AD; # (툲; 툲; á„ᅮᆭ; 툲; á„ᅮᆭ; ) HANGUL SYLLABLE TUNH
+D233;D233;1110 116E 11AE;D233;1110 116E 11AE; # (툳; 툳; á„ᅮᆮ; 툳; á„ᅮᆮ; ) HANGUL SYLLABLE TUD
+D234;D234;1110 116E 11AF;D234;1110 116E 11AF; # (툴; 툴; á„ᅮᆯ; 툴; á„ᅮᆯ; ) HANGUL SYLLABLE TUL
+D235;D235;1110 116E 11B0;D235;1110 116E 11B0; # (툵; 툵; á„ᅮᆰ; 툵; á„ᅮᆰ; ) HANGUL SYLLABLE TULG
+D236;D236;1110 116E 11B1;D236;1110 116E 11B1; # (툶; 툶; á„ᅮᆱ; 툶; á„ᅮᆱ; ) HANGUL SYLLABLE TULM
+D237;D237;1110 116E 11B2;D237;1110 116E 11B2; # (툷; 툷; á„ᅮᆲ; 툷; á„ᅮᆲ; ) HANGUL SYLLABLE TULB
+D238;D238;1110 116E 11B3;D238;1110 116E 11B3; # (툸; 툸; á„ᅮᆳ; 툸; á„ᅮᆳ; ) HANGUL SYLLABLE TULS
+D239;D239;1110 116E 11B4;D239;1110 116E 11B4; # (툹; 툹; á„ᅮᆴ; 툹; á„ᅮᆴ; ) HANGUL SYLLABLE TULT
+D23A;D23A;1110 116E 11B5;D23A;1110 116E 11B5; # (툺; 툺; á„ᅮᆵ; 툺; á„ᅮᆵ; ) HANGUL SYLLABLE TULP
+D23B;D23B;1110 116E 11B6;D23B;1110 116E 11B6; # (툻; 툻; á„ᅮᆶ; 툻; á„ᅮᆶ; ) HANGUL SYLLABLE TULH
+D23C;D23C;1110 116E 11B7;D23C;1110 116E 11B7; # (툼; 툼; á„ᅮᆷ; 툼; á„ᅮᆷ; ) HANGUL SYLLABLE TUM
+D23D;D23D;1110 116E 11B8;D23D;1110 116E 11B8; # (툽; 툽; á„ᅮᆸ; 툽; á„ᅮᆸ; ) HANGUL SYLLABLE TUB
+D23E;D23E;1110 116E 11B9;D23E;1110 116E 11B9; # (툾; 툾; á„ᅮᆹ; 툾; á„ᅮᆹ; ) HANGUL SYLLABLE TUBS
+D23F;D23F;1110 116E 11BA;D23F;1110 116E 11BA; # (툿; 툿; á„ᅮᆺ; 툿; á„ᅮᆺ; ) HANGUL SYLLABLE TUS
+D240;D240;1110 116E 11BB;D240;1110 116E 11BB; # (퉀; 퉀; á„ᅮᆻ; 퉀; á„ᅮᆻ; ) HANGUL SYLLABLE TUSS
+D241;D241;1110 116E 11BC;D241;1110 116E 11BC; # (í‰; í‰; á„ᅮᆼ; í‰; á„ᅮᆼ; ) HANGUL SYLLABLE TUNG
+D242;D242;1110 116E 11BD;D242;1110 116E 11BD; # (퉂; 퉂; á„ᅮᆽ; 퉂; á„ᅮᆽ; ) HANGUL SYLLABLE TUJ
+D243;D243;1110 116E 11BE;D243;1110 116E 11BE; # (퉃; 퉃; á„ᅮᆾ; 퉃; á„ᅮᆾ; ) HANGUL SYLLABLE TUC
+D244;D244;1110 116E 11BF;D244;1110 116E 11BF; # (퉄; 퉄; á„ᅮᆿ; 퉄; á„ᅮᆿ; ) HANGUL SYLLABLE TUK
+D245;D245;1110 116E 11C0;D245;1110 116E 11C0; # (퉅; 퉅; á„ᅮᇀ; 퉅; á„ᅮᇀ; ) HANGUL SYLLABLE TUT
+D246;D246;1110 116E 11C1;D246;1110 116E 11C1; # (퉆; 퉆; á„á…®á‡; 퉆; á„á…®á‡; ) HANGUL SYLLABLE TUP
+D247;D247;1110 116E 11C2;D247;1110 116E 11C2; # (퉇; 퉇; á„ᅮᇂ; 퉇; á„ᅮᇂ; ) HANGUL SYLLABLE TUH
+D248;D248;1110 116F;D248;1110 116F; # (퉈; 퉈; á„á…¯; 퉈; á„á…¯; ) HANGUL SYLLABLE TWEO
+D249;D249;1110 116F 11A8;D249;1110 116F 11A8; # (퉉; 퉉; á„ᅯᆨ; 퉉; á„ᅯᆨ; ) HANGUL SYLLABLE TWEOG
+D24A;D24A;1110 116F 11A9;D24A;1110 116F 11A9; # (퉊; 퉊; á„ᅯᆩ; 퉊; á„ᅯᆩ; ) HANGUL SYLLABLE TWEOGG
+D24B;D24B;1110 116F 11AA;D24B;1110 116F 11AA; # (퉋; 퉋; á„ᅯᆪ; 퉋; á„ᅯᆪ; ) HANGUL SYLLABLE TWEOGS
+D24C;D24C;1110 116F 11AB;D24C;1110 116F 11AB; # (퉌; 퉌; á„ᅯᆫ; 퉌; á„ᅯᆫ; ) HANGUL SYLLABLE TWEON
+D24D;D24D;1110 116F 11AC;D24D;1110 116F 11AC; # (í‰; í‰; á„ᅯᆬ; í‰; á„ᅯᆬ; ) HANGUL SYLLABLE TWEONJ
+D24E;D24E;1110 116F 11AD;D24E;1110 116F 11AD; # (퉎; 퉎; á„ᅯᆭ; 퉎; á„ᅯᆭ; ) HANGUL SYLLABLE TWEONH
+D24F;D24F;1110 116F 11AE;D24F;1110 116F 11AE; # (í‰; í‰; á„ᅯᆮ; í‰; á„ᅯᆮ; ) HANGUL SYLLABLE TWEOD
+D250;D250;1110 116F 11AF;D250;1110 116F 11AF; # (í‰; í‰; á„ᅯᆯ; í‰; á„ᅯᆯ; ) HANGUL SYLLABLE TWEOL
+D251;D251;1110 116F 11B0;D251;1110 116F 11B0; # (퉑; 퉑; á„ᅯᆰ; 퉑; á„ᅯᆰ; ) HANGUL SYLLABLE TWEOLG
+D252;D252;1110 116F 11B1;D252;1110 116F 11B1; # (퉒; 퉒; á„ᅯᆱ; 퉒; á„ᅯᆱ; ) HANGUL SYLLABLE TWEOLM
+D253;D253;1110 116F 11B2;D253;1110 116F 11B2; # (퉓; 퉓; á„ᅯᆲ; 퉓; á„ᅯᆲ; ) HANGUL SYLLABLE TWEOLB
+D254;D254;1110 116F 11B3;D254;1110 116F 11B3; # (퉔; 퉔; á„ᅯᆳ; 퉔; á„ᅯᆳ; ) HANGUL SYLLABLE TWEOLS
+D255;D255;1110 116F 11B4;D255;1110 116F 11B4; # (퉕; 퉕; á„ᅯᆴ; 퉕; á„ᅯᆴ; ) HANGUL SYLLABLE TWEOLT
+D256;D256;1110 116F 11B5;D256;1110 116F 11B5; # (퉖; 퉖; á„ᅯᆵ; 퉖; á„ᅯᆵ; ) HANGUL SYLLABLE TWEOLP
+D257;D257;1110 116F 11B6;D257;1110 116F 11B6; # (퉗; 퉗; á„ᅯᆶ; 퉗; á„ᅯᆶ; ) HANGUL SYLLABLE TWEOLH
+D258;D258;1110 116F 11B7;D258;1110 116F 11B7; # (퉘; 퉘; á„ᅯᆷ; 퉘; á„ᅯᆷ; ) HANGUL SYLLABLE TWEOM
+D259;D259;1110 116F 11B8;D259;1110 116F 11B8; # (퉙; 퉙; á„ᅯᆸ; 퉙; á„ᅯᆸ; ) HANGUL SYLLABLE TWEOB
+D25A;D25A;1110 116F 11B9;D25A;1110 116F 11B9; # (퉚; 퉚; á„ᅯᆹ; 퉚; á„ᅯᆹ; ) HANGUL SYLLABLE TWEOBS
+D25B;D25B;1110 116F 11BA;D25B;1110 116F 11BA; # (퉛; 퉛; á„ᅯᆺ; 퉛; á„ᅯᆺ; ) HANGUL SYLLABLE TWEOS
+D25C;D25C;1110 116F 11BB;D25C;1110 116F 11BB; # (퉜; 퉜; á„ᅯᆻ; 퉜; á„ᅯᆻ; ) HANGUL SYLLABLE TWEOSS
+D25D;D25D;1110 116F 11BC;D25D;1110 116F 11BC; # (í‰; í‰; á„ᅯᆼ; í‰; á„ᅯᆼ; ) HANGUL SYLLABLE TWEONG
+D25E;D25E;1110 116F 11BD;D25E;1110 116F 11BD; # (퉞; 퉞; á„ᅯᆽ; 퉞; á„ᅯᆽ; ) HANGUL SYLLABLE TWEOJ
+D25F;D25F;1110 116F 11BE;D25F;1110 116F 11BE; # (퉟; 퉟; á„ᅯᆾ; 퉟; á„ᅯᆾ; ) HANGUL SYLLABLE TWEOC
+D260;D260;1110 116F 11BF;D260;1110 116F 11BF; # (퉠; 퉠; á„ᅯᆿ; 퉠; á„ᅯᆿ; ) HANGUL SYLLABLE TWEOK
+D261;D261;1110 116F 11C0;D261;1110 116F 11C0; # (퉡; 퉡; á„ᅯᇀ; 퉡; á„ᅯᇀ; ) HANGUL SYLLABLE TWEOT
+D262;D262;1110 116F 11C1;D262;1110 116F 11C1; # (퉢; 퉢; á„á…¯á‡; 퉢; á„á…¯á‡; ) HANGUL SYLLABLE TWEOP
+D263;D263;1110 116F 11C2;D263;1110 116F 11C2; # (퉣; 퉣; á„ᅯᇂ; 퉣; á„ᅯᇂ; ) HANGUL SYLLABLE TWEOH
+D264;D264;1110 1170;D264;1110 1170; # (퉤; 퉤; á„á…°; 퉤; á„á…°; ) HANGUL SYLLABLE TWE
+D265;D265;1110 1170 11A8;D265;1110 1170 11A8; # (퉥; 퉥; á„ᅰᆨ; 퉥; á„ᅰᆨ; ) HANGUL SYLLABLE TWEG
+D266;D266;1110 1170 11A9;D266;1110 1170 11A9; # (퉦; 퉦; á„ᅰᆩ; 퉦; á„ᅰᆩ; ) HANGUL SYLLABLE TWEGG
+D267;D267;1110 1170 11AA;D267;1110 1170 11AA; # (퉧; 퉧; á„ᅰᆪ; 퉧; á„ᅰᆪ; ) HANGUL SYLLABLE TWEGS
+D268;D268;1110 1170 11AB;D268;1110 1170 11AB; # (퉨; 퉨; á„ᅰᆫ; 퉨; á„ᅰᆫ; ) HANGUL SYLLABLE TWEN
+D269;D269;1110 1170 11AC;D269;1110 1170 11AC; # (퉩; 퉩; á„ᅰᆬ; 퉩; á„ᅰᆬ; ) HANGUL SYLLABLE TWENJ
+D26A;D26A;1110 1170 11AD;D26A;1110 1170 11AD; # (퉪; 퉪; á„ᅰᆭ; 퉪; á„ᅰᆭ; ) HANGUL SYLLABLE TWENH
+D26B;D26B;1110 1170 11AE;D26B;1110 1170 11AE; # (퉫; 퉫; á„ᅰᆮ; 퉫; á„ᅰᆮ; ) HANGUL SYLLABLE TWED
+D26C;D26C;1110 1170 11AF;D26C;1110 1170 11AF; # (퉬; 퉬; á„ᅰᆯ; 퉬; á„ᅰᆯ; ) HANGUL SYLLABLE TWEL
+D26D;D26D;1110 1170 11B0;D26D;1110 1170 11B0; # (퉭; 퉭; á„ᅰᆰ; 퉭; á„ᅰᆰ; ) HANGUL SYLLABLE TWELG
+D26E;D26E;1110 1170 11B1;D26E;1110 1170 11B1; # (퉮; 퉮; á„ᅰᆱ; 퉮; á„ᅰᆱ; ) HANGUL SYLLABLE TWELM
+D26F;D26F;1110 1170 11B2;D26F;1110 1170 11B2; # (퉯; 퉯; á„ᅰᆲ; 퉯; á„ᅰᆲ; ) HANGUL SYLLABLE TWELB
+D270;D270;1110 1170 11B3;D270;1110 1170 11B3; # (퉰; 퉰; á„ᅰᆳ; 퉰; á„ᅰᆳ; ) HANGUL SYLLABLE TWELS
+D271;D271;1110 1170 11B4;D271;1110 1170 11B4; # (퉱; 퉱; á„ᅰᆴ; 퉱; á„ᅰᆴ; ) HANGUL SYLLABLE TWELT
+D272;D272;1110 1170 11B5;D272;1110 1170 11B5; # (퉲; 퉲; á„ᅰᆵ; 퉲; á„ᅰᆵ; ) HANGUL SYLLABLE TWELP
+D273;D273;1110 1170 11B6;D273;1110 1170 11B6; # (퉳; 퉳; á„ᅰᆶ; 퉳; á„ᅰᆶ; ) HANGUL SYLLABLE TWELH
+D274;D274;1110 1170 11B7;D274;1110 1170 11B7; # (퉴; 퉴; á„ᅰᆷ; 퉴; á„ᅰᆷ; ) HANGUL SYLLABLE TWEM
+D275;D275;1110 1170 11B8;D275;1110 1170 11B8; # (퉵; 퉵; á„ᅰᆸ; 퉵; á„ᅰᆸ; ) HANGUL SYLLABLE TWEB
+D276;D276;1110 1170 11B9;D276;1110 1170 11B9; # (퉶; 퉶; á„ᅰᆹ; 퉶; á„ᅰᆹ; ) HANGUL SYLLABLE TWEBS
+D277;D277;1110 1170 11BA;D277;1110 1170 11BA; # (퉷; 퉷; á„ᅰᆺ; 퉷; á„ᅰᆺ; ) HANGUL SYLLABLE TWES
+D278;D278;1110 1170 11BB;D278;1110 1170 11BB; # (퉸; 퉸; á„ᅰᆻ; 퉸; á„ᅰᆻ; ) HANGUL SYLLABLE TWESS
+D279;D279;1110 1170 11BC;D279;1110 1170 11BC; # (퉹; 퉹; á„ᅰᆼ; 퉹; á„ᅰᆼ; ) HANGUL SYLLABLE TWENG
+D27A;D27A;1110 1170 11BD;D27A;1110 1170 11BD; # (퉺; 퉺; á„ᅰᆽ; 퉺; á„ᅰᆽ; ) HANGUL SYLLABLE TWEJ
+D27B;D27B;1110 1170 11BE;D27B;1110 1170 11BE; # (퉻; 퉻; á„ᅰᆾ; 퉻; á„ᅰᆾ; ) HANGUL SYLLABLE TWEC
+D27C;D27C;1110 1170 11BF;D27C;1110 1170 11BF; # (퉼; 퉼; á„ᅰᆿ; 퉼; á„ᅰᆿ; ) HANGUL SYLLABLE TWEK
+D27D;D27D;1110 1170 11C0;D27D;1110 1170 11C0; # (퉽; 퉽; á„ᅰᇀ; 퉽; á„ᅰᇀ; ) HANGUL SYLLABLE TWET
+D27E;D27E;1110 1170 11C1;D27E;1110 1170 11C1; # (퉾; 퉾; á„á…°á‡; 퉾; á„á…°á‡; ) HANGUL SYLLABLE TWEP
+D27F;D27F;1110 1170 11C2;D27F;1110 1170 11C2; # (퉿; 퉿; á„ᅰᇂ; 퉿; á„ᅰᇂ; ) HANGUL SYLLABLE TWEH
+D280;D280;1110 1171;D280;1110 1171; # (튀; 튀; á„á…±; 튀; á„á…±; ) HANGUL SYLLABLE TWI
+D281;D281;1110 1171 11A8;D281;1110 1171 11A8; # (íŠ; íŠ; á„ᅱᆨ; íŠ; á„ᅱᆨ; ) HANGUL SYLLABLE TWIG
+D282;D282;1110 1171 11A9;D282;1110 1171 11A9; # (튂; 튂; á„ᅱᆩ; 튂; á„ᅱᆩ; ) HANGUL SYLLABLE TWIGG
+D283;D283;1110 1171 11AA;D283;1110 1171 11AA; # (튃; 튃; á„ᅱᆪ; 튃; á„ᅱᆪ; ) HANGUL SYLLABLE TWIGS
+D284;D284;1110 1171 11AB;D284;1110 1171 11AB; # (튄; 튄; á„ᅱᆫ; 튄; á„ᅱᆫ; ) HANGUL SYLLABLE TWIN
+D285;D285;1110 1171 11AC;D285;1110 1171 11AC; # (튅; 튅; á„ᅱᆬ; 튅; á„ᅱᆬ; ) HANGUL SYLLABLE TWINJ
+D286;D286;1110 1171 11AD;D286;1110 1171 11AD; # (튆; 튆; á„ᅱᆭ; 튆; á„ᅱᆭ; ) HANGUL SYLLABLE TWINH
+D287;D287;1110 1171 11AE;D287;1110 1171 11AE; # (튇; 튇; á„ᅱᆮ; 튇; á„ᅱᆮ; ) HANGUL SYLLABLE TWID
+D288;D288;1110 1171 11AF;D288;1110 1171 11AF; # (튈; 튈; á„ᅱᆯ; 튈; á„ᅱᆯ; ) HANGUL SYLLABLE TWIL
+D289;D289;1110 1171 11B0;D289;1110 1171 11B0; # (튉; 튉; á„ᅱᆰ; 튉; á„ᅱᆰ; ) HANGUL SYLLABLE TWILG
+D28A;D28A;1110 1171 11B1;D28A;1110 1171 11B1; # (튊; 튊; á„ᅱᆱ; 튊; á„ᅱᆱ; ) HANGUL SYLLABLE TWILM
+D28B;D28B;1110 1171 11B2;D28B;1110 1171 11B2; # (튋; 튋; á„ᅱᆲ; 튋; á„ᅱᆲ; ) HANGUL SYLLABLE TWILB
+D28C;D28C;1110 1171 11B3;D28C;1110 1171 11B3; # (튌; 튌; á„ᅱᆳ; 튌; á„ᅱᆳ; ) HANGUL SYLLABLE TWILS
+D28D;D28D;1110 1171 11B4;D28D;1110 1171 11B4; # (íŠ; íŠ; á„ᅱᆴ; íŠ; á„ᅱᆴ; ) HANGUL SYLLABLE TWILT
+D28E;D28E;1110 1171 11B5;D28E;1110 1171 11B5; # (튎; 튎; á„ᅱᆵ; 튎; á„ᅱᆵ; ) HANGUL SYLLABLE TWILP
+D28F;D28F;1110 1171 11B6;D28F;1110 1171 11B6; # (íŠ; íŠ; á„ᅱᆶ; íŠ; á„ᅱᆶ; ) HANGUL SYLLABLE TWILH
+D290;D290;1110 1171 11B7;D290;1110 1171 11B7; # (íŠ; íŠ; á„ᅱᆷ; íŠ; á„ᅱᆷ; ) HANGUL SYLLABLE TWIM
+D291;D291;1110 1171 11B8;D291;1110 1171 11B8; # (튑; 튑; á„ᅱᆸ; 튑; á„ᅱᆸ; ) HANGUL SYLLABLE TWIB
+D292;D292;1110 1171 11B9;D292;1110 1171 11B9; # (튒; 튒; á„ᅱᆹ; 튒; á„ᅱᆹ; ) HANGUL SYLLABLE TWIBS
+D293;D293;1110 1171 11BA;D293;1110 1171 11BA; # (튓; 튓; á„ᅱᆺ; 튓; á„ᅱᆺ; ) HANGUL SYLLABLE TWIS
+D294;D294;1110 1171 11BB;D294;1110 1171 11BB; # (튔; 튔; á„ᅱᆻ; 튔; á„ᅱᆻ; ) HANGUL SYLLABLE TWISS
+D295;D295;1110 1171 11BC;D295;1110 1171 11BC; # (튕; 튕; á„ᅱᆼ; 튕; á„ᅱᆼ; ) HANGUL SYLLABLE TWING
+D296;D296;1110 1171 11BD;D296;1110 1171 11BD; # (튖; 튖; á„ᅱᆽ; 튖; á„ᅱᆽ; ) HANGUL SYLLABLE TWIJ
+D297;D297;1110 1171 11BE;D297;1110 1171 11BE; # (튗; 튗; á„ᅱᆾ; 튗; á„ᅱᆾ; ) HANGUL SYLLABLE TWIC
+D298;D298;1110 1171 11BF;D298;1110 1171 11BF; # (튘; 튘; á„ᅱᆿ; 튘; á„ᅱᆿ; ) HANGUL SYLLABLE TWIK
+D299;D299;1110 1171 11C0;D299;1110 1171 11C0; # (튙; 튙; á„ᅱᇀ; 튙; á„ᅱᇀ; ) HANGUL SYLLABLE TWIT
+D29A;D29A;1110 1171 11C1;D29A;1110 1171 11C1; # (튚; 튚; á„á…±á‡; 튚; á„á…±á‡; ) HANGUL SYLLABLE TWIP
+D29B;D29B;1110 1171 11C2;D29B;1110 1171 11C2; # (튛; 튛; á„ᅱᇂ; 튛; á„ᅱᇂ; ) HANGUL SYLLABLE TWIH
+D29C;D29C;1110 1172;D29C;1110 1172; # (튜; 튜; á„á…²; 튜; á„á…²; ) HANGUL SYLLABLE TYU
+D29D;D29D;1110 1172 11A8;D29D;1110 1172 11A8; # (íŠ; íŠ; á„ᅲᆨ; íŠ; á„ᅲᆨ; ) HANGUL SYLLABLE TYUG
+D29E;D29E;1110 1172 11A9;D29E;1110 1172 11A9; # (튞; 튞; á„ᅲᆩ; 튞; á„ᅲᆩ; ) HANGUL SYLLABLE TYUGG
+D29F;D29F;1110 1172 11AA;D29F;1110 1172 11AA; # (튟; 튟; á„ᅲᆪ; 튟; á„ᅲᆪ; ) HANGUL SYLLABLE TYUGS
+D2A0;D2A0;1110 1172 11AB;D2A0;1110 1172 11AB; # (튠; 튠; á„ᅲᆫ; 튠; á„ᅲᆫ; ) HANGUL SYLLABLE TYUN
+D2A1;D2A1;1110 1172 11AC;D2A1;1110 1172 11AC; # (튡; 튡; á„ᅲᆬ; 튡; á„ᅲᆬ; ) HANGUL SYLLABLE TYUNJ
+D2A2;D2A2;1110 1172 11AD;D2A2;1110 1172 11AD; # (튢; 튢; á„ᅲᆭ; 튢; á„ᅲᆭ; ) HANGUL SYLLABLE TYUNH
+D2A3;D2A3;1110 1172 11AE;D2A3;1110 1172 11AE; # (튣; 튣; á„ᅲᆮ; 튣; á„ᅲᆮ; ) HANGUL SYLLABLE TYUD
+D2A4;D2A4;1110 1172 11AF;D2A4;1110 1172 11AF; # (튤; 튤; á„ᅲᆯ; 튤; á„ᅲᆯ; ) HANGUL SYLLABLE TYUL
+D2A5;D2A5;1110 1172 11B0;D2A5;1110 1172 11B0; # (튥; 튥; á„ᅲᆰ; 튥; á„ᅲᆰ; ) HANGUL SYLLABLE TYULG
+D2A6;D2A6;1110 1172 11B1;D2A6;1110 1172 11B1; # (튦; 튦; á„ᅲᆱ; 튦; á„ᅲᆱ; ) HANGUL SYLLABLE TYULM
+D2A7;D2A7;1110 1172 11B2;D2A7;1110 1172 11B2; # (튧; 튧; á„ᅲᆲ; 튧; á„ᅲᆲ; ) HANGUL SYLLABLE TYULB
+D2A8;D2A8;1110 1172 11B3;D2A8;1110 1172 11B3; # (튨; 튨; á„ᅲᆳ; 튨; á„ᅲᆳ; ) HANGUL SYLLABLE TYULS
+D2A9;D2A9;1110 1172 11B4;D2A9;1110 1172 11B4; # (튩; 튩; á„ᅲᆴ; 튩; á„ᅲᆴ; ) HANGUL SYLLABLE TYULT
+D2AA;D2AA;1110 1172 11B5;D2AA;1110 1172 11B5; # (튪; 튪; á„ᅲᆵ; 튪; á„ᅲᆵ; ) HANGUL SYLLABLE TYULP
+D2AB;D2AB;1110 1172 11B6;D2AB;1110 1172 11B6; # (튫; 튫; á„ᅲᆶ; 튫; á„ᅲᆶ; ) HANGUL SYLLABLE TYULH
+D2AC;D2AC;1110 1172 11B7;D2AC;1110 1172 11B7; # (튬; 튬; á„ᅲᆷ; 튬; á„ᅲᆷ; ) HANGUL SYLLABLE TYUM
+D2AD;D2AD;1110 1172 11B8;D2AD;1110 1172 11B8; # (튭; 튭; á„ᅲᆸ; 튭; á„ᅲᆸ; ) HANGUL SYLLABLE TYUB
+D2AE;D2AE;1110 1172 11B9;D2AE;1110 1172 11B9; # (튮; 튮; á„ᅲᆹ; 튮; á„ᅲᆹ; ) HANGUL SYLLABLE TYUBS
+D2AF;D2AF;1110 1172 11BA;D2AF;1110 1172 11BA; # (튯; 튯; á„ᅲᆺ; 튯; á„ᅲᆺ; ) HANGUL SYLLABLE TYUS
+D2B0;D2B0;1110 1172 11BB;D2B0;1110 1172 11BB; # (튰; 튰; á„ᅲᆻ; 튰; á„ᅲᆻ; ) HANGUL SYLLABLE TYUSS
+D2B1;D2B1;1110 1172 11BC;D2B1;1110 1172 11BC; # (튱; 튱; á„ᅲᆼ; 튱; á„ᅲᆼ; ) HANGUL SYLLABLE TYUNG
+D2B2;D2B2;1110 1172 11BD;D2B2;1110 1172 11BD; # (튲; 튲; á„ᅲᆽ; 튲; á„ᅲᆽ; ) HANGUL SYLLABLE TYUJ
+D2B3;D2B3;1110 1172 11BE;D2B3;1110 1172 11BE; # (튳; 튳; á„ᅲᆾ; 튳; á„ᅲᆾ; ) HANGUL SYLLABLE TYUC
+D2B4;D2B4;1110 1172 11BF;D2B4;1110 1172 11BF; # (튴; 튴; á„ᅲᆿ; 튴; á„ᅲᆿ; ) HANGUL SYLLABLE TYUK
+D2B5;D2B5;1110 1172 11C0;D2B5;1110 1172 11C0; # (튵; 튵; á„ᅲᇀ; 튵; á„ᅲᇀ; ) HANGUL SYLLABLE TYUT
+D2B6;D2B6;1110 1172 11C1;D2B6;1110 1172 11C1; # (튶; 튶; á„á…²á‡; 튶; á„á…²á‡; ) HANGUL SYLLABLE TYUP
+D2B7;D2B7;1110 1172 11C2;D2B7;1110 1172 11C2; # (튷; 튷; á„ᅲᇂ; 튷; á„ᅲᇂ; ) HANGUL SYLLABLE TYUH
+D2B8;D2B8;1110 1173;D2B8;1110 1173; # (트; 트; á„á…³; 트; á„á…³; ) HANGUL SYLLABLE TEU
+D2B9;D2B9;1110 1173 11A8;D2B9;1110 1173 11A8; # (특; 특; á„ᅳᆨ; 특; á„ᅳᆨ; ) HANGUL SYLLABLE TEUG
+D2BA;D2BA;1110 1173 11A9;D2BA;1110 1173 11A9; # (튺; 튺; á„ᅳᆩ; 튺; á„ᅳᆩ; ) HANGUL SYLLABLE TEUGG
+D2BB;D2BB;1110 1173 11AA;D2BB;1110 1173 11AA; # (튻; 튻; á„ᅳᆪ; 튻; á„ᅳᆪ; ) HANGUL SYLLABLE TEUGS
+D2BC;D2BC;1110 1173 11AB;D2BC;1110 1173 11AB; # (튼; 튼; á„ᅳᆫ; 튼; á„ᅳᆫ; ) HANGUL SYLLABLE TEUN
+D2BD;D2BD;1110 1173 11AC;D2BD;1110 1173 11AC; # (튽; 튽; á„ᅳᆬ; 튽; á„ᅳᆬ; ) HANGUL SYLLABLE TEUNJ
+D2BE;D2BE;1110 1173 11AD;D2BE;1110 1173 11AD; # (튾; 튾; á„ᅳᆭ; 튾; á„ᅳᆭ; ) HANGUL SYLLABLE TEUNH
+D2BF;D2BF;1110 1173 11AE;D2BF;1110 1173 11AE; # (튿; 튿; á„ᅳᆮ; 튿; á„ᅳᆮ; ) HANGUL SYLLABLE TEUD
+D2C0;D2C0;1110 1173 11AF;D2C0;1110 1173 11AF; # (í‹€; í‹€; á„ᅳᆯ; í‹€; á„ᅳᆯ; ) HANGUL SYLLABLE TEUL
+D2C1;D2C1;1110 1173 11B0;D2C1;1110 1173 11B0; # (í‹; í‹; á„ᅳᆰ; í‹; á„ᅳᆰ; ) HANGUL SYLLABLE TEULG
+D2C2;D2C2;1110 1173 11B1;D2C2;1110 1173 11B1; # (í‹‚; í‹‚; á„ᅳᆱ; í‹‚; á„ᅳᆱ; ) HANGUL SYLLABLE TEULM
+D2C3;D2C3;1110 1173 11B2;D2C3;1110 1173 11B2; # (틃; 틃; á„ᅳᆲ; 틃; á„ᅳᆲ; ) HANGUL SYLLABLE TEULB
+D2C4;D2C4;1110 1173 11B3;D2C4;1110 1173 11B3; # (í‹„; í‹„; á„ᅳᆳ; í‹„; á„ᅳᆳ; ) HANGUL SYLLABLE TEULS
+D2C5;D2C5;1110 1173 11B4;D2C5;1110 1173 11B4; # (í‹…; í‹…; á„ᅳᆴ; í‹…; á„ᅳᆴ; ) HANGUL SYLLABLE TEULT
+D2C6;D2C6;1110 1173 11B5;D2C6;1110 1173 11B5; # (틆; 틆; á„ᅳᆵ; 틆; á„ᅳᆵ; ) HANGUL SYLLABLE TEULP
+D2C7;D2C7;1110 1173 11B6;D2C7;1110 1173 11B6; # (틇; 틇; á„ᅳᆶ; 틇; á„ᅳᆶ; ) HANGUL SYLLABLE TEULH
+D2C8;D2C8;1110 1173 11B7;D2C8;1110 1173 11B7; # (틈; 틈; á„ᅳᆷ; 틈; á„ᅳᆷ; ) HANGUL SYLLABLE TEUM
+D2C9;D2C9;1110 1173 11B8;D2C9;1110 1173 11B8; # (틉; 틉; á„ᅳᆸ; 틉; á„ᅳᆸ; ) HANGUL SYLLABLE TEUB
+D2CA;D2CA;1110 1173 11B9;D2CA;1110 1173 11B9; # (í‹Š; í‹Š; á„ᅳᆹ; í‹Š; á„ᅳᆹ; ) HANGUL SYLLABLE TEUBS
+D2CB;D2CB;1110 1173 11BA;D2CB;1110 1173 11BA; # (í‹‹; í‹‹; á„ᅳᆺ; í‹‹; á„ᅳᆺ; ) HANGUL SYLLABLE TEUS
+D2CC;D2CC;1110 1173 11BB;D2CC;1110 1173 11BB; # (í‹Œ; í‹Œ; á„ᅳᆻ; í‹Œ; á„ᅳᆻ; ) HANGUL SYLLABLE TEUSS
+D2CD;D2CD;1110 1173 11BC;D2CD;1110 1173 11BC; # (í‹; í‹; á„ᅳᆼ; í‹; á„ᅳᆼ; ) HANGUL SYLLABLE TEUNG
+D2CE;D2CE;1110 1173 11BD;D2CE;1110 1173 11BD; # (í‹Ž; í‹Ž; á„ᅳᆽ; í‹Ž; á„ᅳᆽ; ) HANGUL SYLLABLE TEUJ
+D2CF;D2CF;1110 1173 11BE;D2CF;1110 1173 11BE; # (í‹; í‹; á„ᅳᆾ; í‹; á„ᅳᆾ; ) HANGUL SYLLABLE TEUC
+D2D0;D2D0;1110 1173 11BF;D2D0;1110 1173 11BF; # (í‹; í‹; á„ᅳᆿ; í‹; á„ᅳᆿ; ) HANGUL SYLLABLE TEUK
+D2D1;D2D1;1110 1173 11C0;D2D1;1110 1173 11C0; # (í‹‘; í‹‘; á„ᅳᇀ; í‹‘; á„ᅳᇀ; ) HANGUL SYLLABLE TEUT
+D2D2;D2D2;1110 1173 11C1;D2D2;1110 1173 11C1; # (í‹’; í‹’; á„á…³á‡; í‹’; á„á…³á‡; ) HANGUL SYLLABLE TEUP
+D2D3;D2D3;1110 1173 11C2;D2D3;1110 1173 11C2; # (í‹“; í‹“; á„ᅳᇂ; í‹“; á„ᅳᇂ; ) HANGUL SYLLABLE TEUH
+D2D4;D2D4;1110 1174;D2D4;1110 1174; # (í‹”; í‹”; á„á…´; í‹”; á„á…´; ) HANGUL SYLLABLE TYI
+D2D5;D2D5;1110 1174 11A8;D2D5;1110 1174 11A8; # (í‹•; í‹•; á„ᅴᆨ; í‹•; á„ᅴᆨ; ) HANGUL SYLLABLE TYIG
+D2D6;D2D6;1110 1174 11A9;D2D6;1110 1174 11A9; # (í‹–; í‹–; á„ᅴᆩ; í‹–; á„ᅴᆩ; ) HANGUL SYLLABLE TYIGG
+D2D7;D2D7;1110 1174 11AA;D2D7;1110 1174 11AA; # (í‹—; í‹—; á„ᅴᆪ; í‹—; á„ᅴᆪ; ) HANGUL SYLLABLE TYIGS
+D2D8;D2D8;1110 1174 11AB;D2D8;1110 1174 11AB; # (틘; 틘; á„ᅴᆫ; 틘; á„ᅴᆫ; ) HANGUL SYLLABLE TYIN
+D2D9;D2D9;1110 1174 11AC;D2D9;1110 1174 11AC; # (í‹™; í‹™; á„ᅴᆬ; í‹™; á„ᅴᆬ; ) HANGUL SYLLABLE TYINJ
+D2DA;D2DA;1110 1174 11AD;D2DA;1110 1174 11AD; # (í‹š; í‹š; á„ᅴᆭ; í‹š; á„ᅴᆭ; ) HANGUL SYLLABLE TYINH
+D2DB;D2DB;1110 1174 11AE;D2DB;1110 1174 11AE; # (í‹›; í‹›; á„ᅴᆮ; í‹›; á„ᅴᆮ; ) HANGUL SYLLABLE TYID
+D2DC;D2DC;1110 1174 11AF;D2DC;1110 1174 11AF; # (í‹œ; í‹œ; á„ᅴᆯ; í‹œ; á„ᅴᆯ; ) HANGUL SYLLABLE TYIL
+D2DD;D2DD;1110 1174 11B0;D2DD;1110 1174 11B0; # (í‹; í‹; á„ᅴᆰ; í‹; á„ᅴᆰ; ) HANGUL SYLLABLE TYILG
+D2DE;D2DE;1110 1174 11B1;D2DE;1110 1174 11B1; # (í‹ž; í‹ž; á„ᅴᆱ; í‹ž; á„ᅴᆱ; ) HANGUL SYLLABLE TYILM
+D2DF;D2DF;1110 1174 11B2;D2DF;1110 1174 11B2; # (í‹Ÿ; í‹Ÿ; á„ᅴᆲ; í‹Ÿ; á„ᅴᆲ; ) HANGUL SYLLABLE TYILB
+D2E0;D2E0;1110 1174 11B3;D2E0;1110 1174 11B3; # (í‹ ; í‹ ; á„ᅴᆳ; í‹ ; á„ᅴᆳ; ) HANGUL SYLLABLE TYILS
+D2E1;D2E1;1110 1174 11B4;D2E1;1110 1174 11B4; # (í‹¡; í‹¡; á„ᅴᆴ; í‹¡; á„ᅴᆴ; ) HANGUL SYLLABLE TYILT
+D2E2;D2E2;1110 1174 11B5;D2E2;1110 1174 11B5; # (í‹¢; í‹¢; á„ᅴᆵ; í‹¢; á„ᅴᆵ; ) HANGUL SYLLABLE TYILP
+D2E3;D2E3;1110 1174 11B6;D2E3;1110 1174 11B6; # (í‹£; í‹£; á„ᅴᆶ; í‹£; á„ᅴᆶ; ) HANGUL SYLLABLE TYILH
+D2E4;D2E4;1110 1174 11B7;D2E4;1110 1174 11B7; # (틤; 틤; á„ᅴᆷ; 틤; á„ᅴᆷ; ) HANGUL SYLLABLE TYIM
+D2E5;D2E5;1110 1174 11B8;D2E5;1110 1174 11B8; # (í‹¥; í‹¥; á„ᅴᆸ; í‹¥; á„ᅴᆸ; ) HANGUL SYLLABLE TYIB
+D2E6;D2E6;1110 1174 11B9;D2E6;1110 1174 11B9; # (틦; 틦; á„ᅴᆹ; 틦; á„ᅴᆹ; ) HANGUL SYLLABLE TYIBS
+D2E7;D2E7;1110 1174 11BA;D2E7;1110 1174 11BA; # (틧; 틧; á„ᅴᆺ; 틧; á„ᅴᆺ; ) HANGUL SYLLABLE TYIS
+D2E8;D2E8;1110 1174 11BB;D2E8;1110 1174 11BB; # (틨; 틨; á„ᅴᆻ; 틨; á„ᅴᆻ; ) HANGUL SYLLABLE TYISS
+D2E9;D2E9;1110 1174 11BC;D2E9;1110 1174 11BC; # (í‹©; í‹©; á„ᅴᆼ; í‹©; á„ᅴᆼ; ) HANGUL SYLLABLE TYING
+D2EA;D2EA;1110 1174 11BD;D2EA;1110 1174 11BD; # (틪; 틪; á„ᅴᆽ; 틪; á„ᅴᆽ; ) HANGUL SYLLABLE TYIJ
+D2EB;D2EB;1110 1174 11BE;D2EB;1110 1174 11BE; # (í‹«; í‹«; á„ᅴᆾ; í‹«; á„ᅴᆾ; ) HANGUL SYLLABLE TYIC
+D2EC;D2EC;1110 1174 11BF;D2EC;1110 1174 11BF; # (틬; 틬; á„ᅴᆿ; 틬; á„ᅴᆿ; ) HANGUL SYLLABLE TYIK
+D2ED;D2ED;1110 1174 11C0;D2ED;1110 1174 11C0; # (í‹­; í‹­; á„ᅴᇀ; í‹­; á„ᅴᇀ; ) HANGUL SYLLABLE TYIT
+D2EE;D2EE;1110 1174 11C1;D2EE;1110 1174 11C1; # (í‹®; í‹®; á„á…´á‡; í‹®; á„á…´á‡; ) HANGUL SYLLABLE TYIP
+D2EF;D2EF;1110 1174 11C2;D2EF;1110 1174 11C2; # (틯; 틯; á„ᅴᇂ; 틯; á„ᅴᇂ; ) HANGUL SYLLABLE TYIH
+D2F0;D2F0;1110 1175;D2F0;1110 1175; # (í‹°; í‹°; á„á…µ; í‹°; á„á…µ; ) HANGUL SYLLABLE TI
+D2F1;D2F1;1110 1175 11A8;D2F1;1110 1175 11A8; # (틱; 틱; á„ᅵᆨ; 틱; á„ᅵᆨ; ) HANGUL SYLLABLE TIG
+D2F2;D2F2;1110 1175 11A9;D2F2;1110 1175 11A9; # (틲; 틲; á„ᅵᆩ; 틲; á„ᅵᆩ; ) HANGUL SYLLABLE TIGG
+D2F3;D2F3;1110 1175 11AA;D2F3;1110 1175 11AA; # (틳; 틳; á„ᅵᆪ; 틳; á„ᅵᆪ; ) HANGUL SYLLABLE TIGS
+D2F4;D2F4;1110 1175 11AB;D2F4;1110 1175 11AB; # (í‹´; í‹´; á„ᅵᆫ; í‹´; á„ᅵᆫ; ) HANGUL SYLLABLE TIN
+D2F5;D2F5;1110 1175 11AC;D2F5;1110 1175 11AC; # (틵; 틵; á„ᅵᆬ; 틵; á„ᅵᆬ; ) HANGUL SYLLABLE TINJ
+D2F6;D2F6;1110 1175 11AD;D2F6;1110 1175 11AD; # (틶; 틶; á„ᅵᆭ; 틶; á„ᅵᆭ; ) HANGUL SYLLABLE TINH
+D2F7;D2F7;1110 1175 11AE;D2F7;1110 1175 11AE; # (í‹·; í‹·; á„ᅵᆮ; í‹·; á„ᅵᆮ; ) HANGUL SYLLABLE TID
+D2F8;D2F8;1110 1175 11AF;D2F8;1110 1175 11AF; # (틸; 틸; á„ᅵᆯ; 틸; á„ᅵᆯ; ) HANGUL SYLLABLE TIL
+D2F9;D2F9;1110 1175 11B0;D2F9;1110 1175 11B0; # (틹; 틹; á„ᅵᆰ; 틹; á„ᅵᆰ; ) HANGUL SYLLABLE TILG
+D2FA;D2FA;1110 1175 11B1;D2FA;1110 1175 11B1; # (틺; 틺; á„ᅵᆱ; 틺; á„ᅵᆱ; ) HANGUL SYLLABLE TILM
+D2FB;D2FB;1110 1175 11B2;D2FB;1110 1175 11B2; # (í‹»; í‹»; á„ᅵᆲ; í‹»; á„ᅵᆲ; ) HANGUL SYLLABLE TILB
+D2FC;D2FC;1110 1175 11B3;D2FC;1110 1175 11B3; # (틼; 틼; á„ᅵᆳ; 틼; á„ᅵᆳ; ) HANGUL SYLLABLE TILS
+D2FD;D2FD;1110 1175 11B4;D2FD;1110 1175 11B4; # (틽; 틽; á„ᅵᆴ; 틽; á„ᅵᆴ; ) HANGUL SYLLABLE TILT
+D2FE;D2FE;1110 1175 11B5;D2FE;1110 1175 11B5; # (틾; 틾; á„ᅵᆵ; 틾; á„ᅵᆵ; ) HANGUL SYLLABLE TILP
+D2FF;D2FF;1110 1175 11B6;D2FF;1110 1175 11B6; # (í‹¿; í‹¿; á„ᅵᆶ; í‹¿; á„ᅵᆶ; ) HANGUL SYLLABLE TILH
+D300;D300;1110 1175 11B7;D300;1110 1175 11B7; # (팀; 팀; á„ᅵᆷ; 팀; á„ᅵᆷ; ) HANGUL SYLLABLE TIM
+D301;D301;1110 1175 11B8;D301;1110 1175 11B8; # (íŒ; íŒ; á„ᅵᆸ; íŒ; á„ᅵᆸ; ) HANGUL SYLLABLE TIB
+D302;D302;1110 1175 11B9;D302;1110 1175 11B9; # (팂; 팂; á„ᅵᆹ; 팂; á„ᅵᆹ; ) HANGUL SYLLABLE TIBS
+D303;D303;1110 1175 11BA;D303;1110 1175 11BA; # (팃; 팃; á„ᅵᆺ; 팃; á„ᅵᆺ; ) HANGUL SYLLABLE TIS
+D304;D304;1110 1175 11BB;D304;1110 1175 11BB; # (팄; 팄; á„ᅵᆻ; 팄; á„ᅵᆻ; ) HANGUL SYLLABLE TISS
+D305;D305;1110 1175 11BC;D305;1110 1175 11BC; # (팅; 팅; á„ᅵᆼ; 팅; á„ᅵᆼ; ) HANGUL SYLLABLE TING
+D306;D306;1110 1175 11BD;D306;1110 1175 11BD; # (팆; 팆; á„ᅵᆽ; 팆; á„ᅵᆽ; ) HANGUL SYLLABLE TIJ
+D307;D307;1110 1175 11BE;D307;1110 1175 11BE; # (팇; 팇; á„ᅵᆾ; 팇; á„ᅵᆾ; ) HANGUL SYLLABLE TIC
+D308;D308;1110 1175 11BF;D308;1110 1175 11BF; # (팈; 팈; á„ᅵᆿ; 팈; á„ᅵᆿ; ) HANGUL SYLLABLE TIK
+D309;D309;1110 1175 11C0;D309;1110 1175 11C0; # (팉; 팉; á„ᅵᇀ; 팉; á„ᅵᇀ; ) HANGUL SYLLABLE TIT
+D30A;D30A;1110 1175 11C1;D30A;1110 1175 11C1; # (팊; 팊; á„á…µá‡; 팊; á„á…µá‡; ) HANGUL SYLLABLE TIP
+D30B;D30B;1110 1175 11C2;D30B;1110 1175 11C2; # (팋; 팋; á„ᅵᇂ; 팋; á„ᅵᇂ; ) HANGUL SYLLABLE TIH
+D30C;D30C;1111 1161;D30C;1111 1161; # (파; 파; 파; 파; 파; ) HANGUL SYLLABLE PA
+D30D;D30D;1111 1161 11A8;D30D;1111 1161 11A8; # (íŒ; íŒ; 팍; íŒ; 팍; ) HANGUL SYLLABLE PAG
+D30E;D30E;1111 1161 11A9;D30E;1111 1161 11A9; # (팎; 팎; 팎; 팎; 팎; ) HANGUL SYLLABLE PAGG
+D30F;D30F;1111 1161 11AA;D30F;1111 1161 11AA; # (íŒ; íŒ; 팏; íŒ; 팏; ) HANGUL SYLLABLE PAGS
+D310;D310;1111 1161 11AB;D310;1111 1161 11AB; # (íŒ; íŒ; 판; íŒ; 판; ) HANGUL SYLLABLE PAN
+D311;D311;1111 1161 11AC;D311;1111 1161 11AC; # (팑; 팑; 팑; 팑; 팑; ) HANGUL SYLLABLE PANJ
+D312;D312;1111 1161 11AD;D312;1111 1161 11AD; # (팒; 팒; 팒; 팒; 팒; ) HANGUL SYLLABLE PANH
+D313;D313;1111 1161 11AE;D313;1111 1161 11AE; # (팓; 팓; 팓; 팓; 팓; ) HANGUL SYLLABLE PAD
+D314;D314;1111 1161 11AF;D314;1111 1161 11AF; # (팔; 팔; 팔; 팔; 팔; ) HANGUL SYLLABLE PAL
+D315;D315;1111 1161 11B0;D315;1111 1161 11B0; # (팕; 팕; 팕; 팕; 팕; ) HANGUL SYLLABLE PALG
+D316;D316;1111 1161 11B1;D316;1111 1161 11B1; # (팖; 팖; 팖; 팖; 팖; ) HANGUL SYLLABLE PALM
+D317;D317;1111 1161 11B2;D317;1111 1161 11B2; # (팗; 팗; 팗; 팗; 팗; ) HANGUL SYLLABLE PALB
+D318;D318;1111 1161 11B3;D318;1111 1161 11B3; # (팘; 팘; 팘; 팘; 팘; ) HANGUL SYLLABLE PALS
+D319;D319;1111 1161 11B4;D319;1111 1161 11B4; # (팙; 팙; 팙; 팙; 팙; ) HANGUL SYLLABLE PALT
+D31A;D31A;1111 1161 11B5;D31A;1111 1161 11B5; # (팚; 팚; 팚; 팚; 팚; ) HANGUL SYLLABLE PALP
+D31B;D31B;1111 1161 11B6;D31B;1111 1161 11B6; # (팛; 팛; 팛; 팛; 팛; ) HANGUL SYLLABLE PALH
+D31C;D31C;1111 1161 11B7;D31C;1111 1161 11B7; # (팜; 팜; 팜; 팜; 팜; ) HANGUL SYLLABLE PAM
+D31D;D31D;1111 1161 11B8;D31D;1111 1161 11B8; # (íŒ; íŒ; 팝; íŒ; 팝; ) HANGUL SYLLABLE PAB
+D31E;D31E;1111 1161 11B9;D31E;1111 1161 11B9; # (팞; 팞; 팞; 팞; 팞; ) HANGUL SYLLABLE PABS
+D31F;D31F;1111 1161 11BA;D31F;1111 1161 11BA; # (팟; 팟; 팟; 팟; 팟; ) HANGUL SYLLABLE PAS
+D320;D320;1111 1161 11BB;D320;1111 1161 11BB; # (팠; 팠; 팠; 팠; 팠; ) HANGUL SYLLABLE PASS
+D321;D321;1111 1161 11BC;D321;1111 1161 11BC; # (팡; 팡; 팡; 팡; 팡; ) HANGUL SYLLABLE PANG
+D322;D322;1111 1161 11BD;D322;1111 1161 11BD; # (팢; 팢; 팢; 팢; 팢; ) HANGUL SYLLABLE PAJ
+D323;D323;1111 1161 11BE;D323;1111 1161 11BE; # (팣; 팣; 팣; 팣; 팣; ) HANGUL SYLLABLE PAC
+D324;D324;1111 1161 11BF;D324;1111 1161 11BF; # (팤; 팤; 팤; 팤; 팤; ) HANGUL SYLLABLE PAK
+D325;D325;1111 1161 11C0;D325;1111 1161 11C0; # (팥; 팥; 팥; 팥; 팥; ) HANGUL SYLLABLE PAT
+D326;D326;1111 1161 11C1;D326;1111 1161 11C1; # (팦; 팦; á„‘á…¡á‡; 팦; á„‘á…¡á‡; ) HANGUL SYLLABLE PAP
+D327;D327;1111 1161 11C2;D327;1111 1161 11C2; # (팧; 팧; 팧; 팧; 팧; ) HANGUL SYLLABLE PAH
+D328;D328;1111 1162;D328;1111 1162; # (패; 패; 패; 패; 패; ) HANGUL SYLLABLE PAE
+D329;D329;1111 1162 11A8;D329;1111 1162 11A8; # (팩; 팩; 팩; 팩; 팩; ) HANGUL SYLLABLE PAEG
+D32A;D32A;1111 1162 11A9;D32A;1111 1162 11A9; # (팪; 팪; 팪; 팪; 팪; ) HANGUL SYLLABLE PAEGG
+D32B;D32B;1111 1162 11AA;D32B;1111 1162 11AA; # (팫; 팫; 팫; 팫; 팫; ) HANGUL SYLLABLE PAEGS
+D32C;D32C;1111 1162 11AB;D32C;1111 1162 11AB; # (팬; 팬; 팬; 팬; 팬; ) HANGUL SYLLABLE PAEN
+D32D;D32D;1111 1162 11AC;D32D;1111 1162 11AC; # (팭; 팭; 팭; 팭; 팭; ) HANGUL SYLLABLE PAENJ
+D32E;D32E;1111 1162 11AD;D32E;1111 1162 11AD; # (팮; 팮; 팮; 팮; 팮; ) HANGUL SYLLABLE PAENH
+D32F;D32F;1111 1162 11AE;D32F;1111 1162 11AE; # (팯; 팯; 팯; 팯; 팯; ) HANGUL SYLLABLE PAED
+D330;D330;1111 1162 11AF;D330;1111 1162 11AF; # (팰; 팰; 팰; 팰; 팰; ) HANGUL SYLLABLE PAEL
+D331;D331;1111 1162 11B0;D331;1111 1162 11B0; # (팱; 팱; 팱; 팱; 팱; ) HANGUL SYLLABLE PAELG
+D332;D332;1111 1162 11B1;D332;1111 1162 11B1; # (팲; 팲; 팲; 팲; 팲; ) HANGUL SYLLABLE PAELM
+D333;D333;1111 1162 11B2;D333;1111 1162 11B2; # (팳; 팳; 팳; 팳; 팳; ) HANGUL SYLLABLE PAELB
+D334;D334;1111 1162 11B3;D334;1111 1162 11B3; # (팴; 팴; 팴; 팴; 팴; ) HANGUL SYLLABLE PAELS
+D335;D335;1111 1162 11B4;D335;1111 1162 11B4; # (팵; 팵; 팵; 팵; 팵; ) HANGUL SYLLABLE PAELT
+D336;D336;1111 1162 11B5;D336;1111 1162 11B5; # (팶; 팶; 팶; 팶; 팶; ) HANGUL SYLLABLE PAELP
+D337;D337;1111 1162 11B6;D337;1111 1162 11B6; # (팷; 팷; 팷; 팷; 팷; ) HANGUL SYLLABLE PAELH
+D338;D338;1111 1162 11B7;D338;1111 1162 11B7; # (팸; 팸; 팸; 팸; 팸; ) HANGUL SYLLABLE PAEM
+D339;D339;1111 1162 11B8;D339;1111 1162 11B8; # (팹; 팹; 팹; 팹; 팹; ) HANGUL SYLLABLE PAEB
+D33A;D33A;1111 1162 11B9;D33A;1111 1162 11B9; # (팺; 팺; 팺; 팺; 팺; ) HANGUL SYLLABLE PAEBS
+D33B;D33B;1111 1162 11BA;D33B;1111 1162 11BA; # (팻; 팻; 팻; 팻; 팻; ) HANGUL SYLLABLE PAES
+D33C;D33C;1111 1162 11BB;D33C;1111 1162 11BB; # (팼; 팼; 팼; 팼; 팼; ) HANGUL SYLLABLE PAESS
+D33D;D33D;1111 1162 11BC;D33D;1111 1162 11BC; # (팽; 팽; 팽; 팽; 팽; ) HANGUL SYLLABLE PAENG
+D33E;D33E;1111 1162 11BD;D33E;1111 1162 11BD; # (팾; 팾; 팾; 팾; 팾; ) HANGUL SYLLABLE PAEJ
+D33F;D33F;1111 1162 11BE;D33F;1111 1162 11BE; # (팿; 팿; 팿; 팿; 팿; ) HANGUL SYLLABLE PAEC
+D340;D340;1111 1162 11BF;D340;1111 1162 11BF; # (í€; í€; 퍀; í€; 퍀; ) HANGUL SYLLABLE PAEK
+D341;D341;1111 1162 11C0;D341;1111 1162 11C0; # (í; í; 퍁; í; 퍁; ) HANGUL SYLLABLE PAET
+D342;D342;1111 1162 11C1;D342;1111 1162 11C1; # (í‚; í‚; á„‘á…¢á‡; í‚; á„‘á…¢á‡; ) HANGUL SYLLABLE PAEP
+D343;D343;1111 1162 11C2;D343;1111 1162 11C2; # (íƒ; íƒ; 퍃; íƒ; 퍃; ) HANGUL SYLLABLE PAEH
+D344;D344;1111 1163;D344;1111 1163; # (í„; í„; á„‘á…£; í„; á„‘á…£; ) HANGUL SYLLABLE PYA
+D345;D345;1111 1163 11A8;D345;1111 1163 11A8; # (í…; í…; 퍅; í…; 퍅; ) HANGUL SYLLABLE PYAG
+D346;D346;1111 1163 11A9;D346;1111 1163 11A9; # (í†; í†; 퍆; í†; 퍆; ) HANGUL SYLLABLE PYAGG
+D347;D347;1111 1163 11AA;D347;1111 1163 11AA; # (í‡; í‡; 퍇; í‡; 퍇; ) HANGUL SYLLABLE PYAGS
+D348;D348;1111 1163 11AB;D348;1111 1163 11AB; # (íˆ; íˆ; 퍈; íˆ; 퍈; ) HANGUL SYLLABLE PYAN
+D349;D349;1111 1163 11AC;D349;1111 1163 11AC; # (í‰; í‰; 퍉; í‰; 퍉; ) HANGUL SYLLABLE PYANJ
+D34A;D34A;1111 1163 11AD;D34A;1111 1163 11AD; # (íŠ; íŠ; 퍊; íŠ; 퍊; ) HANGUL SYLLABLE PYANH
+D34B;D34B;1111 1163 11AE;D34B;1111 1163 11AE; # (í‹; í‹; 퍋; í‹; 퍋; ) HANGUL SYLLABLE PYAD
+D34C;D34C;1111 1163 11AF;D34C;1111 1163 11AF; # (íŒ; íŒ; 퍌; íŒ; 퍌; ) HANGUL SYLLABLE PYAL
+D34D;D34D;1111 1163 11B0;D34D;1111 1163 11B0; # (í; í; 퍍; í; 퍍; ) HANGUL SYLLABLE PYALG
+D34E;D34E;1111 1163 11B1;D34E;1111 1163 11B1; # (íŽ; íŽ; 퍎; íŽ; 퍎; ) HANGUL SYLLABLE PYALM
+D34F;D34F;1111 1163 11B2;D34F;1111 1163 11B2; # (í; í; 퍏; í; 퍏; ) HANGUL SYLLABLE PYALB
+D350;D350;1111 1163 11B3;D350;1111 1163 11B3; # (í; í; 퍐; í; 퍐; ) HANGUL SYLLABLE PYALS
+D351;D351;1111 1163 11B4;D351;1111 1163 11B4; # (í‘; í‘; 퍑; í‘; 퍑; ) HANGUL SYLLABLE PYALT
+D352;D352;1111 1163 11B5;D352;1111 1163 11B5; # (í’; í’; 퍒; í’; 퍒; ) HANGUL SYLLABLE PYALP
+D353;D353;1111 1163 11B6;D353;1111 1163 11B6; # (í“; í“; 퍓; í“; 퍓; ) HANGUL SYLLABLE PYALH
+D354;D354;1111 1163 11B7;D354;1111 1163 11B7; # (í”; í”; 퍔; í”; 퍔; ) HANGUL SYLLABLE PYAM
+D355;D355;1111 1163 11B8;D355;1111 1163 11B8; # (í•; í•; 퍕; í•; 퍕; ) HANGUL SYLLABLE PYAB
+D356;D356;1111 1163 11B9;D356;1111 1163 11B9; # (í–; í–; 퍖; í–; 퍖; ) HANGUL SYLLABLE PYABS
+D357;D357;1111 1163 11BA;D357;1111 1163 11BA; # (í—; í—; 퍗; í—; 퍗; ) HANGUL SYLLABLE PYAS
+D358;D358;1111 1163 11BB;D358;1111 1163 11BB; # (í˜; í˜; 퍘; í˜; 퍘; ) HANGUL SYLLABLE PYASS
+D359;D359;1111 1163 11BC;D359;1111 1163 11BC; # (í™; í™; 퍙; í™; 퍙; ) HANGUL SYLLABLE PYANG
+D35A;D35A;1111 1163 11BD;D35A;1111 1163 11BD; # (íš; íš; 퍚; íš; 퍚; ) HANGUL SYLLABLE PYAJ
+D35B;D35B;1111 1163 11BE;D35B;1111 1163 11BE; # (í›; í›; 퍛; í›; 퍛; ) HANGUL SYLLABLE PYAC
+D35C;D35C;1111 1163 11BF;D35C;1111 1163 11BF; # (íœ; íœ; 퍜; íœ; 퍜; ) HANGUL SYLLABLE PYAK
+D35D;D35D;1111 1163 11C0;D35D;1111 1163 11C0; # (í; í; 퍝; í; 퍝; ) HANGUL SYLLABLE PYAT
+D35E;D35E;1111 1163 11C1;D35E;1111 1163 11C1; # (íž; íž; á„‘á…£á‡; íž; á„‘á…£á‡; ) HANGUL SYLLABLE PYAP
+D35F;D35F;1111 1163 11C2;D35F;1111 1163 11C2; # (íŸ; íŸ; 퍟; íŸ; 퍟; ) HANGUL SYLLABLE PYAH
+D360;D360;1111 1164;D360;1111 1164; # (í ; í ; á„‘á…¤; í ; á„‘á…¤; ) HANGUL SYLLABLE PYAE
+D361;D361;1111 1164 11A8;D361;1111 1164 11A8; # (í¡; í¡; 퍡; í¡; 퍡; ) HANGUL SYLLABLE PYAEG
+D362;D362;1111 1164 11A9;D362;1111 1164 11A9; # (í¢; í¢; 퍢; í¢; 퍢; ) HANGUL SYLLABLE PYAEGG
+D363;D363;1111 1164 11AA;D363;1111 1164 11AA; # (í£; í£; 퍣; í£; 퍣; ) HANGUL SYLLABLE PYAEGS
+D364;D364;1111 1164 11AB;D364;1111 1164 11AB; # (í¤; í¤; 퍤; í¤; 퍤; ) HANGUL SYLLABLE PYAEN
+D365;D365;1111 1164 11AC;D365;1111 1164 11AC; # (í¥; í¥; 퍥; í¥; 퍥; ) HANGUL SYLLABLE PYAENJ
+D366;D366;1111 1164 11AD;D366;1111 1164 11AD; # (í¦; í¦; 퍦; í¦; 퍦; ) HANGUL SYLLABLE PYAENH
+D367;D367;1111 1164 11AE;D367;1111 1164 11AE; # (í§; í§; 퍧; í§; 퍧; ) HANGUL SYLLABLE PYAED
+D368;D368;1111 1164 11AF;D368;1111 1164 11AF; # (í¨; í¨; 퍨; í¨; 퍨; ) HANGUL SYLLABLE PYAEL
+D369;D369;1111 1164 11B0;D369;1111 1164 11B0; # (í©; í©; 퍩; í©; 퍩; ) HANGUL SYLLABLE PYAELG
+D36A;D36A;1111 1164 11B1;D36A;1111 1164 11B1; # (íª; íª; 퍪; íª; 퍪; ) HANGUL SYLLABLE PYAELM
+D36B;D36B;1111 1164 11B2;D36B;1111 1164 11B2; # (í«; í«; 퍫; í«; 퍫; ) HANGUL SYLLABLE PYAELB
+D36C;D36C;1111 1164 11B3;D36C;1111 1164 11B3; # (í¬; í¬; 퍬; í¬; 퍬; ) HANGUL SYLLABLE PYAELS
+D36D;D36D;1111 1164 11B4;D36D;1111 1164 11B4; # (í­; í­; 퍭; í­; 퍭; ) HANGUL SYLLABLE PYAELT
+D36E;D36E;1111 1164 11B5;D36E;1111 1164 11B5; # (í®; í®; 퍮; í®; 퍮; ) HANGUL SYLLABLE PYAELP
+D36F;D36F;1111 1164 11B6;D36F;1111 1164 11B6; # (í¯; í¯; 퍯; í¯; 퍯; ) HANGUL SYLLABLE PYAELH
+D370;D370;1111 1164 11B7;D370;1111 1164 11B7; # (í°; í°; 퍰; í°; 퍰; ) HANGUL SYLLABLE PYAEM
+D371;D371;1111 1164 11B8;D371;1111 1164 11B8; # (í±; í±; 퍱; í±; 퍱; ) HANGUL SYLLABLE PYAEB
+D372;D372;1111 1164 11B9;D372;1111 1164 11B9; # (í²; í²; 퍲; í²; 퍲; ) HANGUL SYLLABLE PYAEBS
+D373;D373;1111 1164 11BA;D373;1111 1164 11BA; # (í³; í³; 퍳; í³; 퍳; ) HANGUL SYLLABLE PYAES
+D374;D374;1111 1164 11BB;D374;1111 1164 11BB; # (í´; í´; 퍴; í´; 퍴; ) HANGUL SYLLABLE PYAESS
+D375;D375;1111 1164 11BC;D375;1111 1164 11BC; # (íµ; íµ; 퍵; íµ; 퍵; ) HANGUL SYLLABLE PYAENG
+D376;D376;1111 1164 11BD;D376;1111 1164 11BD; # (í¶; í¶; 퍶; í¶; 퍶; ) HANGUL SYLLABLE PYAEJ
+D377;D377;1111 1164 11BE;D377;1111 1164 11BE; # (í·; í·; 퍷; í·; 퍷; ) HANGUL SYLLABLE PYAEC
+D378;D378;1111 1164 11BF;D378;1111 1164 11BF; # (í¸; í¸; 퍸; í¸; 퍸; ) HANGUL SYLLABLE PYAEK
+D379;D379;1111 1164 11C0;D379;1111 1164 11C0; # (í¹; í¹; 퍹; í¹; 퍹; ) HANGUL SYLLABLE PYAET
+D37A;D37A;1111 1164 11C1;D37A;1111 1164 11C1; # (íº; íº; á„‘á…¤á‡; íº; á„‘á…¤á‡; ) HANGUL SYLLABLE PYAEP
+D37B;D37B;1111 1164 11C2;D37B;1111 1164 11C2; # (í»; í»; 퍻; í»; 퍻; ) HANGUL SYLLABLE PYAEH
+D37C;D37C;1111 1165;D37C;1111 1165; # (í¼; í¼; á„‘á…¥; í¼; á„‘á…¥; ) HANGUL SYLLABLE PEO
+D37D;D37D;1111 1165 11A8;D37D;1111 1165 11A8; # (í½; í½; 퍽; í½; 퍽; ) HANGUL SYLLABLE PEOG
+D37E;D37E;1111 1165 11A9;D37E;1111 1165 11A9; # (í¾; í¾; 퍾; í¾; 퍾; ) HANGUL SYLLABLE PEOGG
+D37F;D37F;1111 1165 11AA;D37F;1111 1165 11AA; # (í¿; í¿; 퍿; í¿; 퍿; ) HANGUL SYLLABLE PEOGS
+D380;D380;1111 1165 11AB;D380;1111 1165 11AB; # (펀; 펀; 펀; 펀; 펀; ) HANGUL SYLLABLE PEON
+D381;D381;1111 1165 11AC;D381;1111 1165 11AC; # (íŽ; íŽ; 펁; íŽ; 펁; ) HANGUL SYLLABLE PEONJ
+D382;D382;1111 1165 11AD;D382;1111 1165 11AD; # (펂; 펂; 펂; 펂; 펂; ) HANGUL SYLLABLE PEONH
+D383;D383;1111 1165 11AE;D383;1111 1165 11AE; # (펃; 펃; 펃; 펃; 펃; ) HANGUL SYLLABLE PEOD
+D384;D384;1111 1165 11AF;D384;1111 1165 11AF; # (펄; 펄; 펄; 펄; 펄; ) HANGUL SYLLABLE PEOL
+D385;D385;1111 1165 11B0;D385;1111 1165 11B0; # (펅; 펅; 펅; 펅; 펅; ) HANGUL SYLLABLE PEOLG
+D386;D386;1111 1165 11B1;D386;1111 1165 11B1; # (펆; 펆; 펆; 펆; 펆; ) HANGUL SYLLABLE PEOLM
+D387;D387;1111 1165 11B2;D387;1111 1165 11B2; # (펇; 펇; 펇; 펇; 펇; ) HANGUL SYLLABLE PEOLB
+D388;D388;1111 1165 11B3;D388;1111 1165 11B3; # (펈; 펈; 펈; 펈; 펈; ) HANGUL SYLLABLE PEOLS
+D389;D389;1111 1165 11B4;D389;1111 1165 11B4; # (펉; 펉; 펉; 펉; 펉; ) HANGUL SYLLABLE PEOLT
+D38A;D38A;1111 1165 11B5;D38A;1111 1165 11B5; # (펊; 펊; 펊; 펊; 펊; ) HANGUL SYLLABLE PEOLP
+D38B;D38B;1111 1165 11B6;D38B;1111 1165 11B6; # (펋; 펋; 펋; 펋; 펋; ) HANGUL SYLLABLE PEOLH
+D38C;D38C;1111 1165 11B7;D38C;1111 1165 11B7; # (펌; 펌; 펌; 펌; 펌; ) HANGUL SYLLABLE PEOM
+D38D;D38D;1111 1165 11B8;D38D;1111 1165 11B8; # (íŽ; íŽ; 펍; íŽ; 펍; ) HANGUL SYLLABLE PEOB
+D38E;D38E;1111 1165 11B9;D38E;1111 1165 11B9; # (펎; 펎; 펎; 펎; 펎; ) HANGUL SYLLABLE PEOBS
+D38F;D38F;1111 1165 11BA;D38F;1111 1165 11BA; # (íŽ; íŽ; 펏; íŽ; 펏; ) HANGUL SYLLABLE PEOS
+D390;D390;1111 1165 11BB;D390;1111 1165 11BB; # (íŽ; íŽ; 펐; íŽ; 펐; ) HANGUL SYLLABLE PEOSS
+D391;D391;1111 1165 11BC;D391;1111 1165 11BC; # (펑; 펑; 펑; 펑; 펑; ) HANGUL SYLLABLE PEONG
+D392;D392;1111 1165 11BD;D392;1111 1165 11BD; # (펒; 펒; 펒; 펒; 펒; ) HANGUL SYLLABLE PEOJ
+D393;D393;1111 1165 11BE;D393;1111 1165 11BE; # (펓; 펓; 펓; 펓; 펓; ) HANGUL SYLLABLE PEOC
+D394;D394;1111 1165 11BF;D394;1111 1165 11BF; # (펔; 펔; 펔; 펔; 펔; ) HANGUL SYLLABLE PEOK
+D395;D395;1111 1165 11C0;D395;1111 1165 11C0; # (펕; 펕; 펕; 펕; 펕; ) HANGUL SYLLABLE PEOT
+D396;D396;1111 1165 11C1;D396;1111 1165 11C1; # (펖; 펖; á„‘á…¥á‡; 펖; á„‘á…¥á‡; ) HANGUL SYLLABLE PEOP
+D397;D397;1111 1165 11C2;D397;1111 1165 11C2; # (펗; 펗; 펗; 펗; 펗; ) HANGUL SYLLABLE PEOH
+D398;D398;1111 1166;D398;1111 1166; # (페; 페; 페; 페; 페; ) HANGUL SYLLABLE PE
+D399;D399;1111 1166 11A8;D399;1111 1166 11A8; # (펙; 펙; 펙; 펙; 펙; ) HANGUL SYLLABLE PEG
+D39A;D39A;1111 1166 11A9;D39A;1111 1166 11A9; # (펚; 펚; 펚; 펚; 펚; ) HANGUL SYLLABLE PEGG
+D39B;D39B;1111 1166 11AA;D39B;1111 1166 11AA; # (펛; 펛; 펛; 펛; 펛; ) HANGUL SYLLABLE PEGS
+D39C;D39C;1111 1166 11AB;D39C;1111 1166 11AB; # (펜; 펜; 펜; 펜; 펜; ) HANGUL SYLLABLE PEN
+D39D;D39D;1111 1166 11AC;D39D;1111 1166 11AC; # (íŽ; íŽ; 펝; íŽ; 펝; ) HANGUL SYLLABLE PENJ
+D39E;D39E;1111 1166 11AD;D39E;1111 1166 11AD; # (펞; 펞; 펞; 펞; 펞; ) HANGUL SYLLABLE PENH
+D39F;D39F;1111 1166 11AE;D39F;1111 1166 11AE; # (펟; 펟; 펟; 펟; 펟; ) HANGUL SYLLABLE PED
+D3A0;D3A0;1111 1166 11AF;D3A0;1111 1166 11AF; # (펠; 펠; 펠; 펠; 펠; ) HANGUL SYLLABLE PEL
+D3A1;D3A1;1111 1166 11B0;D3A1;1111 1166 11B0; # (펡; 펡; 펡; 펡; 펡; ) HANGUL SYLLABLE PELG
+D3A2;D3A2;1111 1166 11B1;D3A2;1111 1166 11B1; # (펢; 펢; 펢; 펢; 펢; ) HANGUL SYLLABLE PELM
+D3A3;D3A3;1111 1166 11B2;D3A3;1111 1166 11B2; # (펣; 펣; 펣; 펣; 펣; ) HANGUL SYLLABLE PELB
+D3A4;D3A4;1111 1166 11B3;D3A4;1111 1166 11B3; # (펤; 펤; 펤; 펤; 펤; ) HANGUL SYLLABLE PELS
+D3A5;D3A5;1111 1166 11B4;D3A5;1111 1166 11B4; # (펥; 펥; 펥; 펥; 펥; ) HANGUL SYLLABLE PELT
+D3A6;D3A6;1111 1166 11B5;D3A6;1111 1166 11B5; # (펦; 펦; 펦; 펦; 펦; ) HANGUL SYLLABLE PELP
+D3A7;D3A7;1111 1166 11B6;D3A7;1111 1166 11B6; # (펧; 펧; 펧; 펧; 펧; ) HANGUL SYLLABLE PELH
+D3A8;D3A8;1111 1166 11B7;D3A8;1111 1166 11B7; # (펨; 펨; 펨; 펨; 펨; ) HANGUL SYLLABLE PEM
+D3A9;D3A9;1111 1166 11B8;D3A9;1111 1166 11B8; # (펩; 펩; 펩; 펩; 펩; ) HANGUL SYLLABLE PEB
+D3AA;D3AA;1111 1166 11B9;D3AA;1111 1166 11B9; # (펪; 펪; 펪; 펪; 펪; ) HANGUL SYLLABLE PEBS
+D3AB;D3AB;1111 1166 11BA;D3AB;1111 1166 11BA; # (펫; 펫; 펫; 펫; 펫; ) HANGUL SYLLABLE PES
+D3AC;D3AC;1111 1166 11BB;D3AC;1111 1166 11BB; # (펬; 펬; 펬; 펬; 펬; ) HANGUL SYLLABLE PESS
+D3AD;D3AD;1111 1166 11BC;D3AD;1111 1166 11BC; # (펭; 펭; 펭; 펭; 펭; ) HANGUL SYLLABLE PENG
+D3AE;D3AE;1111 1166 11BD;D3AE;1111 1166 11BD; # (펮; 펮; 펮; 펮; 펮; ) HANGUL SYLLABLE PEJ
+D3AF;D3AF;1111 1166 11BE;D3AF;1111 1166 11BE; # (펯; 펯; 펯; 펯; 펯; ) HANGUL SYLLABLE PEC
+D3B0;D3B0;1111 1166 11BF;D3B0;1111 1166 11BF; # (펰; 펰; 펰; 펰; 펰; ) HANGUL SYLLABLE PEK
+D3B1;D3B1;1111 1166 11C0;D3B1;1111 1166 11C0; # (펱; 펱; 펱; 펱; 펱; ) HANGUL SYLLABLE PET
+D3B2;D3B2;1111 1166 11C1;D3B2;1111 1166 11C1; # (펲; 펲; á„‘á…¦á‡; 펲; á„‘á…¦á‡; ) HANGUL SYLLABLE PEP
+D3B3;D3B3;1111 1166 11C2;D3B3;1111 1166 11C2; # (펳; 펳; 펳; 펳; 펳; ) HANGUL SYLLABLE PEH
+D3B4;D3B4;1111 1167;D3B4;1111 1167; # (펴; 펴; 펴; 펴; 펴; ) HANGUL SYLLABLE PYEO
+D3B5;D3B5;1111 1167 11A8;D3B5;1111 1167 11A8; # (펵; 펵; 펵; 펵; 펵; ) HANGUL SYLLABLE PYEOG
+D3B6;D3B6;1111 1167 11A9;D3B6;1111 1167 11A9; # (펶; 펶; 펶; 펶; 펶; ) HANGUL SYLLABLE PYEOGG
+D3B7;D3B7;1111 1167 11AA;D3B7;1111 1167 11AA; # (펷; 펷; 펷; 펷; 펷; ) HANGUL SYLLABLE PYEOGS
+D3B8;D3B8;1111 1167 11AB;D3B8;1111 1167 11AB; # (편; 편; 편; 편; 편; ) HANGUL SYLLABLE PYEON
+D3B9;D3B9;1111 1167 11AC;D3B9;1111 1167 11AC; # (펹; 펹; 펹; 펹; 펹; ) HANGUL SYLLABLE PYEONJ
+D3BA;D3BA;1111 1167 11AD;D3BA;1111 1167 11AD; # (펺; 펺; 펺; 펺; 펺; ) HANGUL SYLLABLE PYEONH
+D3BB;D3BB;1111 1167 11AE;D3BB;1111 1167 11AE; # (펻; 펻; 펻; 펻; 펻; ) HANGUL SYLLABLE PYEOD
+D3BC;D3BC;1111 1167 11AF;D3BC;1111 1167 11AF; # (펼; 펼; 펼; 펼; 펼; ) HANGUL SYLLABLE PYEOL
+D3BD;D3BD;1111 1167 11B0;D3BD;1111 1167 11B0; # (펽; 펽; 펽; 펽; 펽; ) HANGUL SYLLABLE PYEOLG
+D3BE;D3BE;1111 1167 11B1;D3BE;1111 1167 11B1; # (펾; 펾; 펾; 펾; 펾; ) HANGUL SYLLABLE PYEOLM
+D3BF;D3BF;1111 1167 11B2;D3BF;1111 1167 11B2; # (펿; 펿; 펿; 펿; 펿; ) HANGUL SYLLABLE PYEOLB
+D3C0;D3C0;1111 1167 11B3;D3C0;1111 1167 11B3; # (í€; í€; 폀; í€; 폀; ) HANGUL SYLLABLE PYEOLS
+D3C1;D3C1;1111 1167 11B4;D3C1;1111 1167 11B4; # (í; í; 폁; í; 폁; ) HANGUL SYLLABLE PYEOLT
+D3C2;D3C2;1111 1167 11B5;D3C2;1111 1167 11B5; # (í‚; í‚; 폂; í‚; 폂; ) HANGUL SYLLABLE PYEOLP
+D3C3;D3C3;1111 1167 11B6;D3C3;1111 1167 11B6; # (íƒ; íƒ; 폃; íƒ; 폃; ) HANGUL SYLLABLE PYEOLH
+D3C4;D3C4;1111 1167 11B7;D3C4;1111 1167 11B7; # (í„; í„; 폄; í„; 폄; ) HANGUL SYLLABLE PYEOM
+D3C5;D3C5;1111 1167 11B8;D3C5;1111 1167 11B8; # (í…; í…; 폅; í…; 폅; ) HANGUL SYLLABLE PYEOB
+D3C6;D3C6;1111 1167 11B9;D3C6;1111 1167 11B9; # (í†; í†; 폆; í†; 폆; ) HANGUL SYLLABLE PYEOBS
+D3C7;D3C7;1111 1167 11BA;D3C7;1111 1167 11BA; # (í‡; í‡; 폇; í‡; 폇; ) HANGUL SYLLABLE PYEOS
+D3C8;D3C8;1111 1167 11BB;D3C8;1111 1167 11BB; # (íˆ; íˆ; 폈; íˆ; 폈; ) HANGUL SYLLABLE PYEOSS
+D3C9;D3C9;1111 1167 11BC;D3C9;1111 1167 11BC; # (í‰; í‰; 평; í‰; 평; ) HANGUL SYLLABLE PYEONG
+D3CA;D3CA;1111 1167 11BD;D3CA;1111 1167 11BD; # (íŠ; íŠ; 폊; íŠ; 폊; ) HANGUL SYLLABLE PYEOJ
+D3CB;D3CB;1111 1167 11BE;D3CB;1111 1167 11BE; # (í‹; í‹; 폋; í‹; 폋; ) HANGUL SYLLABLE PYEOC
+D3CC;D3CC;1111 1167 11BF;D3CC;1111 1167 11BF; # (íŒ; íŒ; 폌; íŒ; 폌; ) HANGUL SYLLABLE PYEOK
+D3CD;D3CD;1111 1167 11C0;D3CD;1111 1167 11C0; # (í; í; 폍; í; 폍; ) HANGUL SYLLABLE PYEOT
+D3CE;D3CE;1111 1167 11C1;D3CE;1111 1167 11C1; # (íŽ; íŽ; á„‘á…§á‡; íŽ; á„‘á…§á‡; ) HANGUL SYLLABLE PYEOP
+D3CF;D3CF;1111 1167 11C2;D3CF;1111 1167 11C2; # (í; í; 폏; í; 폏; ) HANGUL SYLLABLE PYEOH
+D3D0;D3D0;1111 1168;D3D0;1111 1168; # (í; í; á„‘á…¨; í; á„‘á…¨; ) HANGUL SYLLABLE PYE
+D3D1;D3D1;1111 1168 11A8;D3D1;1111 1168 11A8; # (í‘; í‘; 폑; í‘; 폑; ) HANGUL SYLLABLE PYEG
+D3D2;D3D2;1111 1168 11A9;D3D2;1111 1168 11A9; # (í’; í’; 폒; í’; 폒; ) HANGUL SYLLABLE PYEGG
+D3D3;D3D3;1111 1168 11AA;D3D3;1111 1168 11AA; # (í“; í“; 폓; í“; 폓; ) HANGUL SYLLABLE PYEGS
+D3D4;D3D4;1111 1168 11AB;D3D4;1111 1168 11AB; # (í”; í”; 폔; í”; 폔; ) HANGUL SYLLABLE PYEN
+D3D5;D3D5;1111 1168 11AC;D3D5;1111 1168 11AC; # (í•; í•; 폕; í•; 폕; ) HANGUL SYLLABLE PYENJ
+D3D6;D3D6;1111 1168 11AD;D3D6;1111 1168 11AD; # (í–; í–; 폖; í–; 폖; ) HANGUL SYLLABLE PYENH
+D3D7;D3D7;1111 1168 11AE;D3D7;1111 1168 11AE; # (í—; í—; 폗; í—; 폗; ) HANGUL SYLLABLE PYED
+D3D8;D3D8;1111 1168 11AF;D3D8;1111 1168 11AF; # (í˜; í˜; 폘; í˜; 폘; ) HANGUL SYLLABLE PYEL
+D3D9;D3D9;1111 1168 11B0;D3D9;1111 1168 11B0; # (í™; í™; 폙; í™; 폙; ) HANGUL SYLLABLE PYELG
+D3DA;D3DA;1111 1168 11B1;D3DA;1111 1168 11B1; # (íš; íš; 폚; íš; 폚; ) HANGUL SYLLABLE PYELM
+D3DB;D3DB;1111 1168 11B2;D3DB;1111 1168 11B2; # (í›; í›; 폛; í›; 폛; ) HANGUL SYLLABLE PYELB
+D3DC;D3DC;1111 1168 11B3;D3DC;1111 1168 11B3; # (íœ; íœ; 폜; íœ; 폜; ) HANGUL SYLLABLE PYELS
+D3DD;D3DD;1111 1168 11B4;D3DD;1111 1168 11B4; # (í; í; 폝; í; 폝; ) HANGUL SYLLABLE PYELT
+D3DE;D3DE;1111 1168 11B5;D3DE;1111 1168 11B5; # (íž; íž; 폞; íž; 폞; ) HANGUL SYLLABLE PYELP
+D3DF;D3DF;1111 1168 11B6;D3DF;1111 1168 11B6; # (íŸ; íŸ; 폟; íŸ; 폟; ) HANGUL SYLLABLE PYELH
+D3E0;D3E0;1111 1168 11B7;D3E0;1111 1168 11B7; # (í ; í ; 폠; í ; 폠; ) HANGUL SYLLABLE PYEM
+D3E1;D3E1;1111 1168 11B8;D3E1;1111 1168 11B8; # (í¡; í¡; 폡; í¡; 폡; ) HANGUL SYLLABLE PYEB
+D3E2;D3E2;1111 1168 11B9;D3E2;1111 1168 11B9; # (í¢; í¢; 폢; í¢; 폢; ) HANGUL SYLLABLE PYEBS
+D3E3;D3E3;1111 1168 11BA;D3E3;1111 1168 11BA; # (í£; í£; 폣; í£; 폣; ) HANGUL SYLLABLE PYES
+D3E4;D3E4;1111 1168 11BB;D3E4;1111 1168 11BB; # (í¤; í¤; 폤; í¤; 폤; ) HANGUL SYLLABLE PYESS
+D3E5;D3E5;1111 1168 11BC;D3E5;1111 1168 11BC; # (í¥; í¥; 폥; í¥; 폥; ) HANGUL SYLLABLE PYENG
+D3E6;D3E6;1111 1168 11BD;D3E6;1111 1168 11BD; # (í¦; í¦; 폦; í¦; 폦; ) HANGUL SYLLABLE PYEJ
+D3E7;D3E7;1111 1168 11BE;D3E7;1111 1168 11BE; # (í§; í§; 폧; í§; 폧; ) HANGUL SYLLABLE PYEC
+D3E8;D3E8;1111 1168 11BF;D3E8;1111 1168 11BF; # (í¨; í¨; 폨; í¨; 폨; ) HANGUL SYLLABLE PYEK
+D3E9;D3E9;1111 1168 11C0;D3E9;1111 1168 11C0; # (í©; í©; 폩; í©; 폩; ) HANGUL SYLLABLE PYET
+D3EA;D3EA;1111 1168 11C1;D3EA;1111 1168 11C1; # (íª; íª; á„‘á…¨á‡; íª; á„‘á…¨á‡; ) HANGUL SYLLABLE PYEP
+D3EB;D3EB;1111 1168 11C2;D3EB;1111 1168 11C2; # (í«; í«; 폫; í«; 폫; ) HANGUL SYLLABLE PYEH
+D3EC;D3EC;1111 1169;D3EC;1111 1169; # (í¬; í¬; á„‘á…©; í¬; á„‘á…©; ) HANGUL SYLLABLE PO
+D3ED;D3ED;1111 1169 11A8;D3ED;1111 1169 11A8; # (í­; í­; 폭; í­; 폭; ) HANGUL SYLLABLE POG
+D3EE;D3EE;1111 1169 11A9;D3EE;1111 1169 11A9; # (í®; í®; 폮; í®; 폮; ) HANGUL SYLLABLE POGG
+D3EF;D3EF;1111 1169 11AA;D3EF;1111 1169 11AA; # (í¯; í¯; 폯; í¯; 폯; ) HANGUL SYLLABLE POGS
+D3F0;D3F0;1111 1169 11AB;D3F0;1111 1169 11AB; # (í°; í°; 폰; í°; 폰; ) HANGUL SYLLABLE PON
+D3F1;D3F1;1111 1169 11AC;D3F1;1111 1169 11AC; # (í±; í±; 폱; í±; 폱; ) HANGUL SYLLABLE PONJ
+D3F2;D3F2;1111 1169 11AD;D3F2;1111 1169 11AD; # (í²; í²; 폲; í²; 폲; ) HANGUL SYLLABLE PONH
+D3F3;D3F3;1111 1169 11AE;D3F3;1111 1169 11AE; # (í³; í³; 폳; í³; 폳; ) HANGUL SYLLABLE POD
+D3F4;D3F4;1111 1169 11AF;D3F4;1111 1169 11AF; # (í´; í´; 폴; í´; 폴; ) HANGUL SYLLABLE POL
+D3F5;D3F5;1111 1169 11B0;D3F5;1111 1169 11B0; # (íµ; íµ; 폵; íµ; 폵; ) HANGUL SYLLABLE POLG
+D3F6;D3F6;1111 1169 11B1;D3F6;1111 1169 11B1; # (í¶; í¶; 폶; í¶; 폶; ) HANGUL SYLLABLE POLM
+D3F7;D3F7;1111 1169 11B2;D3F7;1111 1169 11B2; # (í·; í·; 폷; í·; 폷; ) HANGUL SYLLABLE POLB
+D3F8;D3F8;1111 1169 11B3;D3F8;1111 1169 11B3; # (í¸; í¸; 폸; í¸; 폸; ) HANGUL SYLLABLE POLS
+D3F9;D3F9;1111 1169 11B4;D3F9;1111 1169 11B4; # (í¹; í¹; 폹; í¹; 폹; ) HANGUL SYLLABLE POLT
+D3FA;D3FA;1111 1169 11B5;D3FA;1111 1169 11B5; # (íº; íº; 폺; íº; 폺; ) HANGUL SYLLABLE POLP
+D3FB;D3FB;1111 1169 11B6;D3FB;1111 1169 11B6; # (í»; í»; 폻; í»; 폻; ) HANGUL SYLLABLE POLH
+D3FC;D3FC;1111 1169 11B7;D3FC;1111 1169 11B7; # (í¼; í¼; 폼; í¼; 폼; ) HANGUL SYLLABLE POM
+D3FD;D3FD;1111 1169 11B8;D3FD;1111 1169 11B8; # (í½; í½; 폽; í½; 폽; ) HANGUL SYLLABLE POB
+D3FE;D3FE;1111 1169 11B9;D3FE;1111 1169 11B9; # (í¾; í¾; 폾; í¾; 폾; ) HANGUL SYLLABLE POBS
+D3FF;D3FF;1111 1169 11BA;D3FF;1111 1169 11BA; # (í¿; í¿; 폿; í¿; 폿; ) HANGUL SYLLABLE POS
+D400;D400;1111 1169 11BB;D400;1111 1169 11BB; # (í€; í€; 퐀; í€; 퐀; ) HANGUL SYLLABLE POSS
+D401;D401;1111 1169 11BC;D401;1111 1169 11BC; # (í; í; 퐁; í; 퐁; ) HANGUL SYLLABLE PONG
+D402;D402;1111 1169 11BD;D402;1111 1169 11BD; # (í‚; í‚; 퐂; í‚; 퐂; ) HANGUL SYLLABLE POJ
+D403;D403;1111 1169 11BE;D403;1111 1169 11BE; # (íƒ; íƒ; 퐃; íƒ; 퐃; ) HANGUL SYLLABLE POC
+D404;D404;1111 1169 11BF;D404;1111 1169 11BF; # (í„; í„; 퐄; í„; 퐄; ) HANGUL SYLLABLE POK
+D405;D405;1111 1169 11C0;D405;1111 1169 11C0; # (í…; í…; 퐅; í…; 퐅; ) HANGUL SYLLABLE POT
+D406;D406;1111 1169 11C1;D406;1111 1169 11C1; # (í†; í†; á„‘á…©á‡; í†; á„‘á…©á‡; ) HANGUL SYLLABLE POP
+D407;D407;1111 1169 11C2;D407;1111 1169 11C2; # (í‡; í‡; 퐇; í‡; 퐇; ) HANGUL SYLLABLE POH
+D408;D408;1111 116A;D408;1111 116A; # (íˆ; íˆ; á„‘á…ª; íˆ; á„‘á…ª; ) HANGUL SYLLABLE PWA
+D409;D409;1111 116A 11A8;D409;1111 116A 11A8; # (í‰; í‰; 퐉; í‰; 퐉; ) HANGUL SYLLABLE PWAG
+D40A;D40A;1111 116A 11A9;D40A;1111 116A 11A9; # (íŠ; íŠ; 퐊; íŠ; 퐊; ) HANGUL SYLLABLE PWAGG
+D40B;D40B;1111 116A 11AA;D40B;1111 116A 11AA; # (í‹; í‹; 퐋; í‹; 퐋; ) HANGUL SYLLABLE PWAGS
+D40C;D40C;1111 116A 11AB;D40C;1111 116A 11AB; # (íŒ; íŒ; 퐌; íŒ; 퐌; ) HANGUL SYLLABLE PWAN
+D40D;D40D;1111 116A 11AC;D40D;1111 116A 11AC; # (í; í; 퐍; í; 퐍; ) HANGUL SYLLABLE PWANJ
+D40E;D40E;1111 116A 11AD;D40E;1111 116A 11AD; # (íŽ; íŽ; 퐎; íŽ; 퐎; ) HANGUL SYLLABLE PWANH
+D40F;D40F;1111 116A 11AE;D40F;1111 116A 11AE; # (í; í; 퐏; í; 퐏; ) HANGUL SYLLABLE PWAD
+D410;D410;1111 116A 11AF;D410;1111 116A 11AF; # (í; í; 퐐; í; 퐐; ) HANGUL SYLLABLE PWAL
+D411;D411;1111 116A 11B0;D411;1111 116A 11B0; # (í‘; í‘; 퐑; í‘; 퐑; ) HANGUL SYLLABLE PWALG
+D412;D412;1111 116A 11B1;D412;1111 116A 11B1; # (í’; í’; 퐒; í’; 퐒; ) HANGUL SYLLABLE PWALM
+D413;D413;1111 116A 11B2;D413;1111 116A 11B2; # (í“; í“; 퐓; í“; 퐓; ) HANGUL SYLLABLE PWALB
+D414;D414;1111 116A 11B3;D414;1111 116A 11B3; # (í”; í”; 퐔; í”; 퐔; ) HANGUL SYLLABLE PWALS
+D415;D415;1111 116A 11B4;D415;1111 116A 11B4; # (í•; í•; 퐕; í•; 퐕; ) HANGUL SYLLABLE PWALT
+D416;D416;1111 116A 11B5;D416;1111 116A 11B5; # (í–; í–; 퐖; í–; 퐖; ) HANGUL SYLLABLE PWALP
+D417;D417;1111 116A 11B6;D417;1111 116A 11B6; # (í—; í—; 퐗; í—; 퐗; ) HANGUL SYLLABLE PWALH
+D418;D418;1111 116A 11B7;D418;1111 116A 11B7; # (í˜; í˜; 퐘; í˜; 퐘; ) HANGUL SYLLABLE PWAM
+D419;D419;1111 116A 11B8;D419;1111 116A 11B8; # (í™; í™; 퐙; í™; 퐙; ) HANGUL SYLLABLE PWAB
+D41A;D41A;1111 116A 11B9;D41A;1111 116A 11B9; # (íš; íš; 퐚; íš; 퐚; ) HANGUL SYLLABLE PWABS
+D41B;D41B;1111 116A 11BA;D41B;1111 116A 11BA; # (í›; í›; 퐛; í›; 퐛; ) HANGUL SYLLABLE PWAS
+D41C;D41C;1111 116A 11BB;D41C;1111 116A 11BB; # (íœ; íœ; 퐜; íœ; 퐜; ) HANGUL SYLLABLE PWASS
+D41D;D41D;1111 116A 11BC;D41D;1111 116A 11BC; # (í; í; 퐝; í; 퐝; ) HANGUL SYLLABLE PWANG
+D41E;D41E;1111 116A 11BD;D41E;1111 116A 11BD; # (íž; íž; 퐞; íž; 퐞; ) HANGUL SYLLABLE PWAJ
+D41F;D41F;1111 116A 11BE;D41F;1111 116A 11BE; # (íŸ; íŸ; 퐟; íŸ; 퐟; ) HANGUL SYLLABLE PWAC
+D420;D420;1111 116A 11BF;D420;1111 116A 11BF; # (í ; í ; 퐠; í ; 퐠; ) HANGUL SYLLABLE PWAK
+D421;D421;1111 116A 11C0;D421;1111 116A 11C0; # (í¡; í¡; 퐡; í¡; 퐡; ) HANGUL SYLLABLE PWAT
+D422;D422;1111 116A 11C1;D422;1111 116A 11C1; # (í¢; í¢; á„‘á…ªá‡; í¢; á„‘á…ªá‡; ) HANGUL SYLLABLE PWAP
+D423;D423;1111 116A 11C2;D423;1111 116A 11C2; # (í£; í£; 퐣; í£; 퐣; ) HANGUL SYLLABLE PWAH
+D424;D424;1111 116B;D424;1111 116B; # (í¤; í¤; á„‘á…«; í¤; á„‘á…«; ) HANGUL SYLLABLE PWAE
+D425;D425;1111 116B 11A8;D425;1111 116B 11A8; # (í¥; í¥; 퐥; í¥; 퐥; ) HANGUL SYLLABLE PWAEG
+D426;D426;1111 116B 11A9;D426;1111 116B 11A9; # (í¦; í¦; 퐦; í¦; 퐦; ) HANGUL SYLLABLE PWAEGG
+D427;D427;1111 116B 11AA;D427;1111 116B 11AA; # (í§; í§; 퐧; í§; 퐧; ) HANGUL SYLLABLE PWAEGS
+D428;D428;1111 116B 11AB;D428;1111 116B 11AB; # (í¨; í¨; 퐨; í¨; 퐨; ) HANGUL SYLLABLE PWAEN
+D429;D429;1111 116B 11AC;D429;1111 116B 11AC; # (í©; í©; 퐩; í©; 퐩; ) HANGUL SYLLABLE PWAENJ
+D42A;D42A;1111 116B 11AD;D42A;1111 116B 11AD; # (íª; íª; 퐪; íª; 퐪; ) HANGUL SYLLABLE PWAENH
+D42B;D42B;1111 116B 11AE;D42B;1111 116B 11AE; # (í«; í«; 퐫; í«; 퐫; ) HANGUL SYLLABLE PWAED
+D42C;D42C;1111 116B 11AF;D42C;1111 116B 11AF; # (í¬; í¬; 퐬; í¬; 퐬; ) HANGUL SYLLABLE PWAEL
+D42D;D42D;1111 116B 11B0;D42D;1111 116B 11B0; # (í­; í­; 퐭; í­; 퐭; ) HANGUL SYLLABLE PWAELG
+D42E;D42E;1111 116B 11B1;D42E;1111 116B 11B1; # (í®; í®; 퐮; í®; 퐮; ) HANGUL SYLLABLE PWAELM
+D42F;D42F;1111 116B 11B2;D42F;1111 116B 11B2; # (í¯; í¯; 퐯; í¯; 퐯; ) HANGUL SYLLABLE PWAELB
+D430;D430;1111 116B 11B3;D430;1111 116B 11B3; # (í°; í°; 퐰; í°; 퐰; ) HANGUL SYLLABLE PWAELS
+D431;D431;1111 116B 11B4;D431;1111 116B 11B4; # (í±; í±; 퐱; í±; 퐱; ) HANGUL SYLLABLE PWAELT
+D432;D432;1111 116B 11B5;D432;1111 116B 11B5; # (í²; í²; 퐲; í²; 퐲; ) HANGUL SYLLABLE PWAELP
+D433;D433;1111 116B 11B6;D433;1111 116B 11B6; # (í³; í³; 퐳; í³; 퐳; ) HANGUL SYLLABLE PWAELH
+D434;D434;1111 116B 11B7;D434;1111 116B 11B7; # (í´; í´; 퐴; í´; 퐴; ) HANGUL SYLLABLE PWAEM
+D435;D435;1111 116B 11B8;D435;1111 116B 11B8; # (íµ; íµ; 퐵; íµ; 퐵; ) HANGUL SYLLABLE PWAEB
+D436;D436;1111 116B 11B9;D436;1111 116B 11B9; # (í¶; í¶; 퐶; í¶; 퐶; ) HANGUL SYLLABLE PWAEBS
+D437;D437;1111 116B 11BA;D437;1111 116B 11BA; # (í·; í·; 퐷; í·; 퐷; ) HANGUL SYLLABLE PWAES
+D438;D438;1111 116B 11BB;D438;1111 116B 11BB; # (í¸; í¸; 퐸; í¸; 퐸; ) HANGUL SYLLABLE PWAESS
+D439;D439;1111 116B 11BC;D439;1111 116B 11BC; # (í¹; í¹; 퐹; í¹; 퐹; ) HANGUL SYLLABLE PWAENG
+D43A;D43A;1111 116B 11BD;D43A;1111 116B 11BD; # (íº; íº; 퐺; íº; 퐺; ) HANGUL SYLLABLE PWAEJ
+D43B;D43B;1111 116B 11BE;D43B;1111 116B 11BE; # (í»; í»; 퐻; í»; 퐻; ) HANGUL SYLLABLE PWAEC
+D43C;D43C;1111 116B 11BF;D43C;1111 116B 11BF; # (í¼; í¼; 퐼; í¼; 퐼; ) HANGUL SYLLABLE PWAEK
+D43D;D43D;1111 116B 11C0;D43D;1111 116B 11C0; # (í½; í½; 퐽; í½; 퐽; ) HANGUL SYLLABLE PWAET
+D43E;D43E;1111 116B 11C1;D43E;1111 116B 11C1; # (í¾; í¾; á„‘á…«á‡; í¾; á„‘á…«á‡; ) HANGUL SYLLABLE PWAEP
+D43F;D43F;1111 116B 11C2;D43F;1111 116B 11C2; # (í¿; í¿; 퐿; í¿; 퐿; ) HANGUL SYLLABLE PWAEH
+D440;D440;1111 116C;D440;1111 116C; # (í‘€; í‘€; á„‘á…¬; í‘€; á„‘á…¬; ) HANGUL SYLLABLE POE
+D441;D441;1111 116C 11A8;D441;1111 116C 11A8; # (í‘; í‘; 푁; í‘; 푁; ) HANGUL SYLLABLE POEG
+D442;D442;1111 116C 11A9;D442;1111 116C 11A9; # (푂; 푂; 푂; 푂; 푂; ) HANGUL SYLLABLE POEGG
+D443;D443;1111 116C 11AA;D443;1111 116C 11AA; # (푃; 푃; 푃; 푃; 푃; ) HANGUL SYLLABLE POEGS
+D444;D444;1111 116C 11AB;D444;1111 116C 11AB; # (푄; 푄; 푄; 푄; 푄; ) HANGUL SYLLABLE POEN
+D445;D445;1111 116C 11AC;D445;1111 116C 11AC; # (푅; 푅; 푅; 푅; 푅; ) HANGUL SYLLABLE POENJ
+D446;D446;1111 116C 11AD;D446;1111 116C 11AD; # (푆; 푆; 푆; 푆; 푆; ) HANGUL SYLLABLE POENH
+D447;D447;1111 116C 11AE;D447;1111 116C 11AE; # (푇; 푇; 푇; 푇; 푇; ) HANGUL SYLLABLE POED
+D448;D448;1111 116C 11AF;D448;1111 116C 11AF; # (푈; 푈; 푈; 푈; 푈; ) HANGUL SYLLABLE POEL
+D449;D449;1111 116C 11B0;D449;1111 116C 11B0; # (푉; 푉; 푉; 푉; 푉; ) HANGUL SYLLABLE POELG
+D44A;D44A;1111 116C 11B1;D44A;1111 116C 11B1; # (푊; 푊; 푊; 푊; 푊; ) HANGUL SYLLABLE POELM
+D44B;D44B;1111 116C 11B2;D44B;1111 116C 11B2; # (푋; 푋; 푋; 푋; 푋; ) HANGUL SYLLABLE POELB
+D44C;D44C;1111 116C 11B3;D44C;1111 116C 11B3; # (푌; 푌; 푌; 푌; 푌; ) HANGUL SYLLABLE POELS
+D44D;D44D;1111 116C 11B4;D44D;1111 116C 11B4; # (í‘; í‘; 푍; í‘; 푍; ) HANGUL SYLLABLE POELT
+D44E;D44E;1111 116C 11B5;D44E;1111 116C 11B5; # (푎; 푎; 푎; 푎; 푎; ) HANGUL SYLLABLE POELP
+D44F;D44F;1111 116C 11B6;D44F;1111 116C 11B6; # (í‘; í‘; 푏; í‘; 푏; ) HANGUL SYLLABLE POELH
+D450;D450;1111 116C 11B7;D450;1111 116C 11B7; # (í‘; í‘; 푐; í‘; 푐; ) HANGUL SYLLABLE POEM
+D451;D451;1111 116C 11B8;D451;1111 116C 11B8; # (푑; 푑; 푑; 푑; 푑; ) HANGUL SYLLABLE POEB
+D452;D452;1111 116C 11B9;D452;1111 116C 11B9; # (푒; 푒; 푒; 푒; 푒; ) HANGUL SYLLABLE POEBS
+D453;D453;1111 116C 11BA;D453;1111 116C 11BA; # (푓; 푓; 푓; 푓; 푓; ) HANGUL SYLLABLE POES
+D454;D454;1111 116C 11BB;D454;1111 116C 11BB; # (푔; 푔; 푔; 푔; 푔; ) HANGUL SYLLABLE POESS
+D455;D455;1111 116C 11BC;D455;1111 116C 11BC; # (푕; 푕; 푕; 푕; 푕; ) HANGUL SYLLABLE POENG
+D456;D456;1111 116C 11BD;D456;1111 116C 11BD; # (푖; 푖; 푖; 푖; 푖; ) HANGUL SYLLABLE POEJ
+D457;D457;1111 116C 11BE;D457;1111 116C 11BE; # (푗; 푗; 푗; 푗; 푗; ) HANGUL SYLLABLE POEC
+D458;D458;1111 116C 11BF;D458;1111 116C 11BF; # (푘; 푘; 푘; 푘; 푘; ) HANGUL SYLLABLE POEK
+D459;D459;1111 116C 11C0;D459;1111 116C 11C0; # (푙; 푙; 푙; 푙; 푙; ) HANGUL SYLLABLE POET
+D45A;D45A;1111 116C 11C1;D45A;1111 116C 11C1; # (í‘š; í‘š; á„‘á…¬á‡; í‘š; á„‘á…¬á‡; ) HANGUL SYLLABLE POEP
+D45B;D45B;1111 116C 11C2;D45B;1111 116C 11C2; # (푛; 푛; 푛; 푛; 푛; ) HANGUL SYLLABLE POEH
+D45C;D45C;1111 116D;D45C;1111 116D; # (표; 표; 표; 표; 표; ) HANGUL SYLLABLE PYO
+D45D;D45D;1111 116D 11A8;D45D;1111 116D 11A8; # (í‘; í‘; 푝; í‘; 푝; ) HANGUL SYLLABLE PYOG
+D45E;D45E;1111 116D 11A9;D45E;1111 116D 11A9; # (푞; 푞; 푞; 푞; 푞; ) HANGUL SYLLABLE PYOGG
+D45F;D45F;1111 116D 11AA;D45F;1111 116D 11AA; # (푟; 푟; 푟; 푟; 푟; ) HANGUL SYLLABLE PYOGS
+D460;D460;1111 116D 11AB;D460;1111 116D 11AB; # (푠; 푠; 푠; 푠; 푠; ) HANGUL SYLLABLE PYON
+D461;D461;1111 116D 11AC;D461;1111 116D 11AC; # (푡; 푡; 푡; 푡; 푡; ) HANGUL SYLLABLE PYONJ
+D462;D462;1111 116D 11AD;D462;1111 116D 11AD; # (푢; 푢; 푢; 푢; 푢; ) HANGUL SYLLABLE PYONH
+D463;D463;1111 116D 11AE;D463;1111 116D 11AE; # (푣; 푣; 푣; 푣; 푣; ) HANGUL SYLLABLE PYOD
+D464;D464;1111 116D 11AF;D464;1111 116D 11AF; # (푤; 푤; 푤; 푤; 푤; ) HANGUL SYLLABLE PYOL
+D465;D465;1111 116D 11B0;D465;1111 116D 11B0; # (푥; 푥; 푥; 푥; 푥; ) HANGUL SYLLABLE PYOLG
+D466;D466;1111 116D 11B1;D466;1111 116D 11B1; # (푦; 푦; 푦; 푦; 푦; ) HANGUL SYLLABLE PYOLM
+D467;D467;1111 116D 11B2;D467;1111 116D 11B2; # (푧; 푧; 푧; 푧; 푧; ) HANGUL SYLLABLE PYOLB
+D468;D468;1111 116D 11B3;D468;1111 116D 11B3; # (푨; 푨; 푨; 푨; 푨; ) HANGUL SYLLABLE PYOLS
+D469;D469;1111 116D 11B4;D469;1111 116D 11B4; # (푩; 푩; 푩; 푩; 푩; ) HANGUL SYLLABLE PYOLT
+D46A;D46A;1111 116D 11B5;D46A;1111 116D 11B5; # (푪; 푪; 푪; 푪; 푪; ) HANGUL SYLLABLE PYOLP
+D46B;D46B;1111 116D 11B6;D46B;1111 116D 11B6; # (푫; 푫; 푫; 푫; 푫; ) HANGUL SYLLABLE PYOLH
+D46C;D46C;1111 116D 11B7;D46C;1111 116D 11B7; # (푬; 푬; 푬; 푬; 푬; ) HANGUL SYLLABLE PYOM
+D46D;D46D;1111 116D 11B8;D46D;1111 116D 11B8; # (푭; 푭; 푭; 푭; 푭; ) HANGUL SYLLABLE PYOB
+D46E;D46E;1111 116D 11B9;D46E;1111 116D 11B9; # (푮; 푮; 푮; 푮; 푮; ) HANGUL SYLLABLE PYOBS
+D46F;D46F;1111 116D 11BA;D46F;1111 116D 11BA; # (푯; 푯; 푯; 푯; 푯; ) HANGUL SYLLABLE PYOS
+D470;D470;1111 116D 11BB;D470;1111 116D 11BB; # (푰; 푰; 푰; 푰; 푰; ) HANGUL SYLLABLE PYOSS
+D471;D471;1111 116D 11BC;D471;1111 116D 11BC; # (푱; 푱; 푱; 푱; 푱; ) HANGUL SYLLABLE PYONG
+D472;D472;1111 116D 11BD;D472;1111 116D 11BD; # (푲; 푲; 푲; 푲; 푲; ) HANGUL SYLLABLE PYOJ
+D473;D473;1111 116D 11BE;D473;1111 116D 11BE; # (푳; 푳; 푳; 푳; 푳; ) HANGUL SYLLABLE PYOC
+D474;D474;1111 116D 11BF;D474;1111 116D 11BF; # (푴; 푴; 푴; 푴; 푴; ) HANGUL SYLLABLE PYOK
+D475;D475;1111 116D 11C0;D475;1111 116D 11C0; # (푵; 푵; 푵; 푵; 푵; ) HANGUL SYLLABLE PYOT
+D476;D476;1111 116D 11C1;D476;1111 116D 11C1; # (푶; 푶; á„‘á…­á‡; 푶; á„‘á…­á‡; ) HANGUL SYLLABLE PYOP
+D477;D477;1111 116D 11C2;D477;1111 116D 11C2; # (푷; 푷; 푷; 푷; 푷; ) HANGUL SYLLABLE PYOH
+D478;D478;1111 116E;D478;1111 116E; # (푸; 푸; 푸; 푸; 푸; ) HANGUL SYLLABLE PU
+D479;D479;1111 116E 11A8;D479;1111 116E 11A8; # (푹; 푹; 푹; 푹; 푹; ) HANGUL SYLLABLE PUG
+D47A;D47A;1111 116E 11A9;D47A;1111 116E 11A9; # (푺; 푺; 푺; 푺; 푺; ) HANGUL SYLLABLE PUGG
+D47B;D47B;1111 116E 11AA;D47B;1111 116E 11AA; # (푻; 푻; 푻; 푻; 푻; ) HANGUL SYLLABLE PUGS
+D47C;D47C;1111 116E 11AB;D47C;1111 116E 11AB; # (푼; 푼; 푼; 푼; 푼; ) HANGUL SYLLABLE PUN
+D47D;D47D;1111 116E 11AC;D47D;1111 116E 11AC; # (푽; 푽; 푽; 푽; 푽; ) HANGUL SYLLABLE PUNJ
+D47E;D47E;1111 116E 11AD;D47E;1111 116E 11AD; # (푾; 푾; 푾; 푾; 푾; ) HANGUL SYLLABLE PUNH
+D47F;D47F;1111 116E 11AE;D47F;1111 116E 11AE; # (푿; 푿; 푿; 푿; 푿; ) HANGUL SYLLABLE PUD
+D480;D480;1111 116E 11AF;D480;1111 116E 11AF; # (풀; 풀; 풀; 풀; 풀; ) HANGUL SYLLABLE PUL
+D481;D481;1111 116E 11B0;D481;1111 116E 11B0; # (í’; í’; 풁; í’; 풁; ) HANGUL SYLLABLE PULG
+D482;D482;1111 116E 11B1;D482;1111 116E 11B1; # (풂; 풂; 풂; 풂; 풂; ) HANGUL SYLLABLE PULM
+D483;D483;1111 116E 11B2;D483;1111 116E 11B2; # (풃; 풃; 풃; 풃; 풃; ) HANGUL SYLLABLE PULB
+D484;D484;1111 116E 11B3;D484;1111 116E 11B3; # (풄; 풄; 풄; 풄; 풄; ) HANGUL SYLLABLE PULS
+D485;D485;1111 116E 11B4;D485;1111 116E 11B4; # (풅; 풅; 풅; 풅; 풅; ) HANGUL SYLLABLE PULT
+D486;D486;1111 116E 11B5;D486;1111 116E 11B5; # (풆; 풆; 풆; 풆; 풆; ) HANGUL SYLLABLE PULP
+D487;D487;1111 116E 11B6;D487;1111 116E 11B6; # (풇; 풇; 풇; 풇; 풇; ) HANGUL SYLLABLE PULH
+D488;D488;1111 116E 11B7;D488;1111 116E 11B7; # (품; 품; 품; 품; 품; ) HANGUL SYLLABLE PUM
+D489;D489;1111 116E 11B8;D489;1111 116E 11B8; # (풉; 풉; 풉; 풉; 풉; ) HANGUL SYLLABLE PUB
+D48A;D48A;1111 116E 11B9;D48A;1111 116E 11B9; # (풊; 풊; 풊; 풊; 풊; ) HANGUL SYLLABLE PUBS
+D48B;D48B;1111 116E 11BA;D48B;1111 116E 11BA; # (풋; 풋; 풋; 풋; 풋; ) HANGUL SYLLABLE PUS
+D48C;D48C;1111 116E 11BB;D48C;1111 116E 11BB; # (풌; 풌; 풌; 풌; 풌; ) HANGUL SYLLABLE PUSS
+D48D;D48D;1111 116E 11BC;D48D;1111 116E 11BC; # (í’; í’; 풍; í’; 풍; ) HANGUL SYLLABLE PUNG
+D48E;D48E;1111 116E 11BD;D48E;1111 116E 11BD; # (풎; 풎; 풎; 풎; 풎; ) HANGUL SYLLABLE PUJ
+D48F;D48F;1111 116E 11BE;D48F;1111 116E 11BE; # (í’; í’; 풏; í’; 풏; ) HANGUL SYLLABLE PUC
+D490;D490;1111 116E 11BF;D490;1111 116E 11BF; # (í’; í’; 풐; í’; 풐; ) HANGUL SYLLABLE PUK
+D491;D491;1111 116E 11C0;D491;1111 116E 11C0; # (풑; 풑; 풑; 풑; 풑; ) HANGUL SYLLABLE PUT
+D492;D492;1111 116E 11C1;D492;1111 116E 11C1; # (í’’; í’’; á„‘á…®á‡; í’’; á„‘á…®á‡; ) HANGUL SYLLABLE PUP
+D493;D493;1111 116E 11C2;D493;1111 116E 11C2; # (풓; 풓; 풓; 풓; 풓; ) HANGUL SYLLABLE PUH
+D494;D494;1111 116F;D494;1111 116F; # (í’”; í’”; á„‘á…¯; í’”; á„‘á…¯; ) HANGUL SYLLABLE PWEO
+D495;D495;1111 116F 11A8;D495;1111 116F 11A8; # (풕; 풕; 풕; 풕; 풕; ) HANGUL SYLLABLE PWEOG
+D496;D496;1111 116F 11A9;D496;1111 116F 11A9; # (풖; 풖; 풖; 풖; 풖; ) HANGUL SYLLABLE PWEOGG
+D497;D497;1111 116F 11AA;D497;1111 116F 11AA; # (풗; 풗; 풗; 풗; 풗; ) HANGUL SYLLABLE PWEOGS
+D498;D498;1111 116F 11AB;D498;1111 116F 11AB; # (풘; 풘; 풘; 풘; 풘; ) HANGUL SYLLABLE PWEON
+D499;D499;1111 116F 11AC;D499;1111 116F 11AC; # (풙; 풙; 풙; 풙; 풙; ) HANGUL SYLLABLE PWEONJ
+D49A;D49A;1111 116F 11AD;D49A;1111 116F 11AD; # (풚; 풚; 풚; 풚; 풚; ) HANGUL SYLLABLE PWEONH
+D49B;D49B;1111 116F 11AE;D49B;1111 116F 11AE; # (풛; 풛; 풛; 풛; 풛; ) HANGUL SYLLABLE PWEOD
+D49C;D49C;1111 116F 11AF;D49C;1111 116F 11AF; # (풜; 풜; 풜; 풜; 풜; ) HANGUL SYLLABLE PWEOL
+D49D;D49D;1111 116F 11B0;D49D;1111 116F 11B0; # (í’; í’; 풝; í’; 풝; ) HANGUL SYLLABLE PWEOLG
+D49E;D49E;1111 116F 11B1;D49E;1111 116F 11B1; # (풞; 풞; 풞; 풞; 풞; ) HANGUL SYLLABLE PWEOLM
+D49F;D49F;1111 116F 11B2;D49F;1111 116F 11B2; # (풟; 풟; 풟; 풟; 풟; ) HANGUL SYLLABLE PWEOLB
+D4A0;D4A0;1111 116F 11B3;D4A0;1111 116F 11B3; # (풠; 풠; 풠; 풠; 풠; ) HANGUL SYLLABLE PWEOLS
+D4A1;D4A1;1111 116F 11B4;D4A1;1111 116F 11B4; # (풡; 풡; 풡; 풡; 풡; ) HANGUL SYLLABLE PWEOLT
+D4A2;D4A2;1111 116F 11B5;D4A2;1111 116F 11B5; # (풢; 풢; 풢; 풢; 풢; ) HANGUL SYLLABLE PWEOLP
+D4A3;D4A3;1111 116F 11B6;D4A3;1111 116F 11B6; # (풣; 풣; 풣; 풣; 풣; ) HANGUL SYLLABLE PWEOLH
+D4A4;D4A4;1111 116F 11B7;D4A4;1111 116F 11B7; # (풤; 풤; 풤; 풤; 풤; ) HANGUL SYLLABLE PWEOM
+D4A5;D4A5;1111 116F 11B8;D4A5;1111 116F 11B8; # (풥; 풥; 풥; 풥; 풥; ) HANGUL SYLLABLE PWEOB
+D4A6;D4A6;1111 116F 11B9;D4A6;1111 116F 11B9; # (풦; 풦; 풦; 풦; 풦; ) HANGUL SYLLABLE PWEOBS
+D4A7;D4A7;1111 116F 11BA;D4A7;1111 116F 11BA; # (풧; 풧; 풧; 풧; 풧; ) HANGUL SYLLABLE PWEOS
+D4A8;D4A8;1111 116F 11BB;D4A8;1111 116F 11BB; # (풨; 풨; 풨; 풨; 풨; ) HANGUL SYLLABLE PWEOSS
+D4A9;D4A9;1111 116F 11BC;D4A9;1111 116F 11BC; # (풩; 풩; 풩; 풩; 풩; ) HANGUL SYLLABLE PWEONG
+D4AA;D4AA;1111 116F 11BD;D4AA;1111 116F 11BD; # (풪; 풪; 풪; 풪; 풪; ) HANGUL SYLLABLE PWEOJ
+D4AB;D4AB;1111 116F 11BE;D4AB;1111 116F 11BE; # (풫; 풫; 풫; 풫; 풫; ) HANGUL SYLLABLE PWEOC
+D4AC;D4AC;1111 116F 11BF;D4AC;1111 116F 11BF; # (풬; 풬; 풬; 풬; 풬; ) HANGUL SYLLABLE PWEOK
+D4AD;D4AD;1111 116F 11C0;D4AD;1111 116F 11C0; # (풭; 풭; 풭; 풭; 풭; ) HANGUL SYLLABLE PWEOT
+D4AE;D4AE;1111 116F 11C1;D4AE;1111 116F 11C1; # (í’®; í’®; á„‘á…¯á‡; í’®; á„‘á…¯á‡; ) HANGUL SYLLABLE PWEOP
+D4AF;D4AF;1111 116F 11C2;D4AF;1111 116F 11C2; # (풯; 풯; 풯; 풯; 풯; ) HANGUL SYLLABLE PWEOH
+D4B0;D4B0;1111 1170;D4B0;1111 1170; # (í’°; í’°; á„‘á…°; í’°; á„‘á…°; ) HANGUL SYLLABLE PWE
+D4B1;D4B1;1111 1170 11A8;D4B1;1111 1170 11A8; # (풱; 풱; 풱; 풱; 풱; ) HANGUL SYLLABLE PWEG
+D4B2;D4B2;1111 1170 11A9;D4B2;1111 1170 11A9; # (풲; 풲; 풲; 풲; 풲; ) HANGUL SYLLABLE PWEGG
+D4B3;D4B3;1111 1170 11AA;D4B3;1111 1170 11AA; # (풳; 풳; 풳; 풳; 풳; ) HANGUL SYLLABLE PWEGS
+D4B4;D4B4;1111 1170 11AB;D4B4;1111 1170 11AB; # (풴; 풴; 풴; 풴; 풴; ) HANGUL SYLLABLE PWEN
+D4B5;D4B5;1111 1170 11AC;D4B5;1111 1170 11AC; # (풵; 풵; 풵; 풵; 풵; ) HANGUL SYLLABLE PWENJ
+D4B6;D4B6;1111 1170 11AD;D4B6;1111 1170 11AD; # (풶; 풶; 풶; 풶; 풶; ) HANGUL SYLLABLE PWENH
+D4B7;D4B7;1111 1170 11AE;D4B7;1111 1170 11AE; # (풷; 풷; 풷; 풷; 풷; ) HANGUL SYLLABLE PWED
+D4B8;D4B8;1111 1170 11AF;D4B8;1111 1170 11AF; # (풸; 풸; 풸; 풸; 풸; ) HANGUL SYLLABLE PWEL
+D4B9;D4B9;1111 1170 11B0;D4B9;1111 1170 11B0; # (풹; 풹; 풹; 풹; 풹; ) HANGUL SYLLABLE PWELG
+D4BA;D4BA;1111 1170 11B1;D4BA;1111 1170 11B1; # (풺; 풺; 풺; 풺; 풺; ) HANGUL SYLLABLE PWELM
+D4BB;D4BB;1111 1170 11B2;D4BB;1111 1170 11B2; # (풻; 풻; 풻; 풻; 풻; ) HANGUL SYLLABLE PWELB
+D4BC;D4BC;1111 1170 11B3;D4BC;1111 1170 11B3; # (풼; 풼; 풼; 풼; 풼; ) HANGUL SYLLABLE PWELS
+D4BD;D4BD;1111 1170 11B4;D4BD;1111 1170 11B4; # (풽; 풽; 풽; 풽; 풽; ) HANGUL SYLLABLE PWELT
+D4BE;D4BE;1111 1170 11B5;D4BE;1111 1170 11B5; # (풾; 풾; 풾; 풾; 풾; ) HANGUL SYLLABLE PWELP
+D4BF;D4BF;1111 1170 11B6;D4BF;1111 1170 11B6; # (풿; 풿; 풿; 풿; 풿; ) HANGUL SYLLABLE PWELH
+D4C0;D4C0;1111 1170 11B7;D4C0;1111 1170 11B7; # (퓀; 퓀; 퓀; 퓀; 퓀; ) HANGUL SYLLABLE PWEM
+D4C1;D4C1;1111 1170 11B8;D4C1;1111 1170 11B8; # (í“; í“; 퓁; í“; 퓁; ) HANGUL SYLLABLE PWEB
+D4C2;D4C2;1111 1170 11B9;D4C2;1111 1170 11B9; # (퓂; 퓂; 퓂; 퓂; 퓂; ) HANGUL SYLLABLE PWEBS
+D4C3;D4C3;1111 1170 11BA;D4C3;1111 1170 11BA; # (퓃; 퓃; 퓃; 퓃; 퓃; ) HANGUL SYLLABLE PWES
+D4C4;D4C4;1111 1170 11BB;D4C4;1111 1170 11BB; # (퓄; 퓄; 퓄; 퓄; 퓄; ) HANGUL SYLLABLE PWESS
+D4C5;D4C5;1111 1170 11BC;D4C5;1111 1170 11BC; # (퓅; 퓅; 퓅; 퓅; 퓅; ) HANGUL SYLLABLE PWENG
+D4C6;D4C6;1111 1170 11BD;D4C6;1111 1170 11BD; # (퓆; 퓆; 퓆; 퓆; 퓆; ) HANGUL SYLLABLE PWEJ
+D4C7;D4C7;1111 1170 11BE;D4C7;1111 1170 11BE; # (퓇; 퓇; 퓇; 퓇; 퓇; ) HANGUL SYLLABLE PWEC
+D4C8;D4C8;1111 1170 11BF;D4C8;1111 1170 11BF; # (퓈; 퓈; 퓈; 퓈; 퓈; ) HANGUL SYLLABLE PWEK
+D4C9;D4C9;1111 1170 11C0;D4C9;1111 1170 11C0; # (퓉; 퓉; 퓉; 퓉; 퓉; ) HANGUL SYLLABLE PWET
+D4CA;D4CA;1111 1170 11C1;D4CA;1111 1170 11C1; # (í“Š; í“Š; á„‘á…°á‡; í“Š; á„‘á…°á‡; ) HANGUL SYLLABLE PWEP
+D4CB;D4CB;1111 1170 11C2;D4CB;1111 1170 11C2; # (퓋; 퓋; 퓋; 퓋; 퓋; ) HANGUL SYLLABLE PWEH
+D4CC;D4CC;1111 1171;D4CC;1111 1171; # (퓌; 퓌; 퓌; 퓌; 퓌; ) HANGUL SYLLABLE PWI
+D4CD;D4CD;1111 1171 11A8;D4CD;1111 1171 11A8; # (í“; í“; 퓍; í“; 퓍; ) HANGUL SYLLABLE PWIG
+D4CE;D4CE;1111 1171 11A9;D4CE;1111 1171 11A9; # (퓎; 퓎; 퓎; 퓎; 퓎; ) HANGUL SYLLABLE PWIGG
+D4CF;D4CF;1111 1171 11AA;D4CF;1111 1171 11AA; # (í“; í“; 퓏; í“; 퓏; ) HANGUL SYLLABLE PWIGS
+D4D0;D4D0;1111 1171 11AB;D4D0;1111 1171 11AB; # (í“; í“; 퓐; í“; 퓐; ) HANGUL SYLLABLE PWIN
+D4D1;D4D1;1111 1171 11AC;D4D1;1111 1171 11AC; # (퓑; 퓑; 퓑; 퓑; 퓑; ) HANGUL SYLLABLE PWINJ
+D4D2;D4D2;1111 1171 11AD;D4D2;1111 1171 11AD; # (퓒; 퓒; 퓒; 퓒; 퓒; ) HANGUL SYLLABLE PWINH
+D4D3;D4D3;1111 1171 11AE;D4D3;1111 1171 11AE; # (퓓; 퓓; 퓓; 퓓; 퓓; ) HANGUL SYLLABLE PWID
+D4D4;D4D4;1111 1171 11AF;D4D4;1111 1171 11AF; # (퓔; 퓔; 퓔; 퓔; 퓔; ) HANGUL SYLLABLE PWIL
+D4D5;D4D5;1111 1171 11B0;D4D5;1111 1171 11B0; # (퓕; 퓕; 퓕; 퓕; 퓕; ) HANGUL SYLLABLE PWILG
+D4D6;D4D6;1111 1171 11B1;D4D6;1111 1171 11B1; # (퓖; 퓖; 퓖; 퓖; 퓖; ) HANGUL SYLLABLE PWILM
+D4D7;D4D7;1111 1171 11B2;D4D7;1111 1171 11B2; # (퓗; 퓗; 퓗; 퓗; 퓗; ) HANGUL SYLLABLE PWILB
+D4D8;D4D8;1111 1171 11B3;D4D8;1111 1171 11B3; # (퓘; 퓘; 퓘; 퓘; 퓘; ) HANGUL SYLLABLE PWILS
+D4D9;D4D9;1111 1171 11B4;D4D9;1111 1171 11B4; # (퓙; 퓙; 퓙; 퓙; 퓙; ) HANGUL SYLLABLE PWILT
+D4DA;D4DA;1111 1171 11B5;D4DA;1111 1171 11B5; # (퓚; 퓚; 퓚; 퓚; 퓚; ) HANGUL SYLLABLE PWILP
+D4DB;D4DB;1111 1171 11B6;D4DB;1111 1171 11B6; # (퓛; 퓛; 퓛; 퓛; 퓛; ) HANGUL SYLLABLE PWILH
+D4DC;D4DC;1111 1171 11B7;D4DC;1111 1171 11B7; # (퓜; 퓜; 퓜; 퓜; 퓜; ) HANGUL SYLLABLE PWIM
+D4DD;D4DD;1111 1171 11B8;D4DD;1111 1171 11B8; # (í“; í“; 퓝; í“; 퓝; ) HANGUL SYLLABLE PWIB
+D4DE;D4DE;1111 1171 11B9;D4DE;1111 1171 11B9; # (퓞; 퓞; 퓞; 퓞; 퓞; ) HANGUL SYLLABLE PWIBS
+D4DF;D4DF;1111 1171 11BA;D4DF;1111 1171 11BA; # (퓟; 퓟; 퓟; 퓟; 퓟; ) HANGUL SYLLABLE PWIS
+D4E0;D4E0;1111 1171 11BB;D4E0;1111 1171 11BB; # (퓠; 퓠; 퓠; 퓠; 퓠; ) HANGUL SYLLABLE PWISS
+D4E1;D4E1;1111 1171 11BC;D4E1;1111 1171 11BC; # (퓡; 퓡; 퓡; 퓡; 퓡; ) HANGUL SYLLABLE PWING
+D4E2;D4E2;1111 1171 11BD;D4E2;1111 1171 11BD; # (퓢; 퓢; 퓢; 퓢; 퓢; ) HANGUL SYLLABLE PWIJ
+D4E3;D4E3;1111 1171 11BE;D4E3;1111 1171 11BE; # (퓣; 퓣; 퓣; 퓣; 퓣; ) HANGUL SYLLABLE PWIC
+D4E4;D4E4;1111 1171 11BF;D4E4;1111 1171 11BF; # (퓤; 퓤; 퓤; 퓤; 퓤; ) HANGUL SYLLABLE PWIK
+D4E5;D4E5;1111 1171 11C0;D4E5;1111 1171 11C0; # (퓥; 퓥; 퓥; 퓥; 퓥; ) HANGUL SYLLABLE PWIT
+D4E6;D4E6;1111 1171 11C1;D4E6;1111 1171 11C1; # (퓦; 퓦; á„‘á…±á‡; 퓦; á„‘á…±á‡; ) HANGUL SYLLABLE PWIP
+D4E7;D4E7;1111 1171 11C2;D4E7;1111 1171 11C2; # (퓧; 퓧; 퓧; 퓧; 퓧; ) HANGUL SYLLABLE PWIH
+D4E8;D4E8;1111 1172;D4E8;1111 1172; # (퓨; 퓨; 퓨; 퓨; 퓨; ) HANGUL SYLLABLE PYU
+D4E9;D4E9;1111 1172 11A8;D4E9;1111 1172 11A8; # (퓩; 퓩; 퓩; 퓩; 퓩; ) HANGUL SYLLABLE PYUG
+D4EA;D4EA;1111 1172 11A9;D4EA;1111 1172 11A9; # (퓪; 퓪; 퓪; 퓪; 퓪; ) HANGUL SYLLABLE PYUGG
+D4EB;D4EB;1111 1172 11AA;D4EB;1111 1172 11AA; # (퓫; 퓫; 퓫; 퓫; 퓫; ) HANGUL SYLLABLE PYUGS
+D4EC;D4EC;1111 1172 11AB;D4EC;1111 1172 11AB; # (퓬; 퓬; 퓬; 퓬; 퓬; ) HANGUL SYLLABLE PYUN
+D4ED;D4ED;1111 1172 11AC;D4ED;1111 1172 11AC; # (퓭; 퓭; 퓭; 퓭; 퓭; ) HANGUL SYLLABLE PYUNJ
+D4EE;D4EE;1111 1172 11AD;D4EE;1111 1172 11AD; # (퓮; 퓮; 퓮; 퓮; 퓮; ) HANGUL SYLLABLE PYUNH
+D4EF;D4EF;1111 1172 11AE;D4EF;1111 1172 11AE; # (퓯; 퓯; 퓯; 퓯; 퓯; ) HANGUL SYLLABLE PYUD
+D4F0;D4F0;1111 1172 11AF;D4F0;1111 1172 11AF; # (퓰; 퓰; 퓰; 퓰; 퓰; ) HANGUL SYLLABLE PYUL
+D4F1;D4F1;1111 1172 11B0;D4F1;1111 1172 11B0; # (퓱; 퓱; 퓱; 퓱; 퓱; ) HANGUL SYLLABLE PYULG
+D4F2;D4F2;1111 1172 11B1;D4F2;1111 1172 11B1; # (퓲; 퓲; 퓲; 퓲; 퓲; ) HANGUL SYLLABLE PYULM
+D4F3;D4F3;1111 1172 11B2;D4F3;1111 1172 11B2; # (퓳; 퓳; 퓳; 퓳; 퓳; ) HANGUL SYLLABLE PYULB
+D4F4;D4F4;1111 1172 11B3;D4F4;1111 1172 11B3; # (퓴; 퓴; 퓴; 퓴; 퓴; ) HANGUL SYLLABLE PYULS
+D4F5;D4F5;1111 1172 11B4;D4F5;1111 1172 11B4; # (퓵; 퓵; 퓵; 퓵; 퓵; ) HANGUL SYLLABLE PYULT
+D4F6;D4F6;1111 1172 11B5;D4F6;1111 1172 11B5; # (퓶; 퓶; 퓶; 퓶; 퓶; ) HANGUL SYLLABLE PYULP
+D4F7;D4F7;1111 1172 11B6;D4F7;1111 1172 11B6; # (퓷; 퓷; 퓷; 퓷; 퓷; ) HANGUL SYLLABLE PYULH
+D4F8;D4F8;1111 1172 11B7;D4F8;1111 1172 11B7; # (퓸; 퓸; 퓸; 퓸; 퓸; ) HANGUL SYLLABLE PYUM
+D4F9;D4F9;1111 1172 11B8;D4F9;1111 1172 11B8; # (퓹; 퓹; 퓹; 퓹; 퓹; ) HANGUL SYLLABLE PYUB
+D4FA;D4FA;1111 1172 11B9;D4FA;1111 1172 11B9; # (퓺; 퓺; 퓺; 퓺; 퓺; ) HANGUL SYLLABLE PYUBS
+D4FB;D4FB;1111 1172 11BA;D4FB;1111 1172 11BA; # (퓻; 퓻; 퓻; 퓻; 퓻; ) HANGUL SYLLABLE PYUS
+D4FC;D4FC;1111 1172 11BB;D4FC;1111 1172 11BB; # (퓼; 퓼; 퓼; 퓼; 퓼; ) HANGUL SYLLABLE PYUSS
+D4FD;D4FD;1111 1172 11BC;D4FD;1111 1172 11BC; # (퓽; 퓽; 퓽; 퓽; 퓽; ) HANGUL SYLLABLE PYUNG
+D4FE;D4FE;1111 1172 11BD;D4FE;1111 1172 11BD; # (퓾; 퓾; 퓾; 퓾; 퓾; ) HANGUL SYLLABLE PYUJ
+D4FF;D4FF;1111 1172 11BE;D4FF;1111 1172 11BE; # (퓿; 퓿; 퓿; 퓿; 퓿; ) HANGUL SYLLABLE PYUC
+D500;D500;1111 1172 11BF;D500;1111 1172 11BF; # (픀; 픀; 픀; 픀; 픀; ) HANGUL SYLLABLE PYUK
+D501;D501;1111 1172 11C0;D501;1111 1172 11C0; # (í”; í”; 픁; í”; 픁; ) HANGUL SYLLABLE PYUT
+D502;D502;1111 1172 11C1;D502;1111 1172 11C1; # (픂; 픂; á„‘á…²á‡; 픂; á„‘á…²á‡; ) HANGUL SYLLABLE PYUP
+D503;D503;1111 1172 11C2;D503;1111 1172 11C2; # (픃; 픃; 픃; 픃; 픃; ) HANGUL SYLLABLE PYUH
+D504;D504;1111 1173;D504;1111 1173; # (프; 프; 프; 프; 프; ) HANGUL SYLLABLE PEU
+D505;D505;1111 1173 11A8;D505;1111 1173 11A8; # (픅; 픅; 픅; 픅; 픅; ) HANGUL SYLLABLE PEUG
+D506;D506;1111 1173 11A9;D506;1111 1173 11A9; # (픆; 픆; 픆; 픆; 픆; ) HANGUL SYLLABLE PEUGG
+D507;D507;1111 1173 11AA;D507;1111 1173 11AA; # (픇; 픇; 픇; 픇; 픇; ) HANGUL SYLLABLE PEUGS
+D508;D508;1111 1173 11AB;D508;1111 1173 11AB; # (픈; 픈; 픈; 픈; 픈; ) HANGUL SYLLABLE PEUN
+D509;D509;1111 1173 11AC;D509;1111 1173 11AC; # (픉; 픉; 픉; 픉; 픉; ) HANGUL SYLLABLE PEUNJ
+D50A;D50A;1111 1173 11AD;D50A;1111 1173 11AD; # (픊; 픊; 픊; 픊; 픊; ) HANGUL SYLLABLE PEUNH
+D50B;D50B;1111 1173 11AE;D50B;1111 1173 11AE; # (픋; 픋; 픋; 픋; 픋; ) HANGUL SYLLABLE PEUD
+D50C;D50C;1111 1173 11AF;D50C;1111 1173 11AF; # (플; 플; 플; 플; 플; ) HANGUL SYLLABLE PEUL
+D50D;D50D;1111 1173 11B0;D50D;1111 1173 11B0; # (í”; í”; 픍; í”; 픍; ) HANGUL SYLLABLE PEULG
+D50E;D50E;1111 1173 11B1;D50E;1111 1173 11B1; # (픎; 픎; 픎; 픎; 픎; ) HANGUL SYLLABLE PEULM
+D50F;D50F;1111 1173 11B2;D50F;1111 1173 11B2; # (í”; í”; 픏; í”; 픏; ) HANGUL SYLLABLE PEULB
+D510;D510;1111 1173 11B3;D510;1111 1173 11B3; # (í”; í”; 픐; í”; 픐; ) HANGUL SYLLABLE PEULS
+D511;D511;1111 1173 11B4;D511;1111 1173 11B4; # (픑; 픑; 픑; 픑; 픑; ) HANGUL SYLLABLE PEULT
+D512;D512;1111 1173 11B5;D512;1111 1173 11B5; # (픒; 픒; 픒; 픒; 픒; ) HANGUL SYLLABLE PEULP
+D513;D513;1111 1173 11B6;D513;1111 1173 11B6; # (픓; 픓; 픓; 픓; 픓; ) HANGUL SYLLABLE PEULH
+D514;D514;1111 1173 11B7;D514;1111 1173 11B7; # (픔; 픔; 픔; 픔; 픔; ) HANGUL SYLLABLE PEUM
+D515;D515;1111 1173 11B8;D515;1111 1173 11B8; # (픕; 픕; 픕; 픕; 픕; ) HANGUL SYLLABLE PEUB
+D516;D516;1111 1173 11B9;D516;1111 1173 11B9; # (픖; 픖; 픖; 픖; 픖; ) HANGUL SYLLABLE PEUBS
+D517;D517;1111 1173 11BA;D517;1111 1173 11BA; # (픗; 픗; 픗; 픗; 픗; ) HANGUL SYLLABLE PEUS
+D518;D518;1111 1173 11BB;D518;1111 1173 11BB; # (픘; 픘; 픘; 픘; 픘; ) HANGUL SYLLABLE PEUSS
+D519;D519;1111 1173 11BC;D519;1111 1173 11BC; # (픙; 픙; 픙; 픙; 픙; ) HANGUL SYLLABLE PEUNG
+D51A;D51A;1111 1173 11BD;D51A;1111 1173 11BD; # (픚; 픚; 픚; 픚; 픚; ) HANGUL SYLLABLE PEUJ
+D51B;D51B;1111 1173 11BE;D51B;1111 1173 11BE; # (픛; 픛; 픛; 픛; 픛; ) HANGUL SYLLABLE PEUC
+D51C;D51C;1111 1173 11BF;D51C;1111 1173 11BF; # (픜; 픜; 픜; 픜; 픜; ) HANGUL SYLLABLE PEUK
+D51D;D51D;1111 1173 11C0;D51D;1111 1173 11C0; # (í”; í”; 픝; í”; 픝; ) HANGUL SYLLABLE PEUT
+D51E;D51E;1111 1173 11C1;D51E;1111 1173 11C1; # (픞; 픞; á„‘á…³á‡; 픞; á„‘á…³á‡; ) HANGUL SYLLABLE PEUP
+D51F;D51F;1111 1173 11C2;D51F;1111 1173 11C2; # (픟; 픟; 픟; 픟; 픟; ) HANGUL SYLLABLE PEUH
+D520;D520;1111 1174;D520;1111 1174; # (í” ; í” ; á„‘á…´; í” ; á„‘á…´; ) HANGUL SYLLABLE PYI
+D521;D521;1111 1174 11A8;D521;1111 1174 11A8; # (픡; 픡; 픡; 픡; 픡; ) HANGUL SYLLABLE PYIG
+D522;D522;1111 1174 11A9;D522;1111 1174 11A9; # (픢; 픢; 픢; 픢; 픢; ) HANGUL SYLLABLE PYIGG
+D523;D523;1111 1174 11AA;D523;1111 1174 11AA; # (픣; 픣; 픣; 픣; 픣; ) HANGUL SYLLABLE PYIGS
+D524;D524;1111 1174 11AB;D524;1111 1174 11AB; # (픤; 픤; 픤; 픤; 픤; ) HANGUL SYLLABLE PYIN
+D525;D525;1111 1174 11AC;D525;1111 1174 11AC; # (픥; 픥; 픥; 픥; 픥; ) HANGUL SYLLABLE PYINJ
+D526;D526;1111 1174 11AD;D526;1111 1174 11AD; # (픦; 픦; 픦; 픦; 픦; ) HANGUL SYLLABLE PYINH
+D527;D527;1111 1174 11AE;D527;1111 1174 11AE; # (픧; 픧; 픧; 픧; 픧; ) HANGUL SYLLABLE PYID
+D528;D528;1111 1174 11AF;D528;1111 1174 11AF; # (픨; 픨; 픨; 픨; 픨; ) HANGUL SYLLABLE PYIL
+D529;D529;1111 1174 11B0;D529;1111 1174 11B0; # (픩; 픩; 픩; 픩; 픩; ) HANGUL SYLLABLE PYILG
+D52A;D52A;1111 1174 11B1;D52A;1111 1174 11B1; # (픪; 픪; 픪; 픪; 픪; ) HANGUL SYLLABLE PYILM
+D52B;D52B;1111 1174 11B2;D52B;1111 1174 11B2; # (픫; 픫; 픫; 픫; 픫; ) HANGUL SYLLABLE PYILB
+D52C;D52C;1111 1174 11B3;D52C;1111 1174 11B3; # (픬; 픬; 픬; 픬; 픬; ) HANGUL SYLLABLE PYILS
+D52D;D52D;1111 1174 11B4;D52D;1111 1174 11B4; # (픭; 픭; 픭; 픭; 픭; ) HANGUL SYLLABLE PYILT
+D52E;D52E;1111 1174 11B5;D52E;1111 1174 11B5; # (픮; 픮; 픮; 픮; 픮; ) HANGUL SYLLABLE PYILP
+D52F;D52F;1111 1174 11B6;D52F;1111 1174 11B6; # (픯; 픯; 픯; 픯; 픯; ) HANGUL SYLLABLE PYILH
+D530;D530;1111 1174 11B7;D530;1111 1174 11B7; # (픰; 픰; 픰; 픰; 픰; ) HANGUL SYLLABLE PYIM
+D531;D531;1111 1174 11B8;D531;1111 1174 11B8; # (픱; 픱; 픱; 픱; 픱; ) HANGUL SYLLABLE PYIB
+D532;D532;1111 1174 11B9;D532;1111 1174 11B9; # (픲; 픲; 픲; 픲; 픲; ) HANGUL SYLLABLE PYIBS
+D533;D533;1111 1174 11BA;D533;1111 1174 11BA; # (픳; 픳; 픳; 픳; 픳; ) HANGUL SYLLABLE PYIS
+D534;D534;1111 1174 11BB;D534;1111 1174 11BB; # (픴; 픴; 픴; 픴; 픴; ) HANGUL SYLLABLE PYISS
+D535;D535;1111 1174 11BC;D535;1111 1174 11BC; # (픵; 픵; 픵; 픵; 픵; ) HANGUL SYLLABLE PYING
+D536;D536;1111 1174 11BD;D536;1111 1174 11BD; # (픶; 픶; 픶; 픶; 픶; ) HANGUL SYLLABLE PYIJ
+D537;D537;1111 1174 11BE;D537;1111 1174 11BE; # (픷; 픷; 픷; 픷; 픷; ) HANGUL SYLLABLE PYIC
+D538;D538;1111 1174 11BF;D538;1111 1174 11BF; # (픸; 픸; 픸; 픸; 픸; ) HANGUL SYLLABLE PYIK
+D539;D539;1111 1174 11C0;D539;1111 1174 11C0; # (픹; 픹; 픹; 픹; 픹; ) HANGUL SYLLABLE PYIT
+D53A;D53A;1111 1174 11C1;D53A;1111 1174 11C1; # (픺; 픺; á„‘á…´á‡; 픺; á„‘á…´á‡; ) HANGUL SYLLABLE PYIP
+D53B;D53B;1111 1174 11C2;D53B;1111 1174 11C2; # (픻; 픻; 픻; 픻; 픻; ) HANGUL SYLLABLE PYIH
+D53C;D53C;1111 1175;D53C;1111 1175; # (피; 피; 피; 피; 피; ) HANGUL SYLLABLE PI
+D53D;D53D;1111 1175 11A8;D53D;1111 1175 11A8; # (픽; 픽; 픽; 픽; 픽; ) HANGUL SYLLABLE PIG
+D53E;D53E;1111 1175 11A9;D53E;1111 1175 11A9; # (픾; 픾; 픾; 픾; 픾; ) HANGUL SYLLABLE PIGG
+D53F;D53F;1111 1175 11AA;D53F;1111 1175 11AA; # (픿; 픿; 픿; 픿; 픿; ) HANGUL SYLLABLE PIGS
+D540;D540;1111 1175 11AB;D540;1111 1175 11AB; # (핀; 핀; 핀; 핀; 핀; ) HANGUL SYLLABLE PIN
+D541;D541;1111 1175 11AC;D541;1111 1175 11AC; # (í•; í•; 핁; í•; 핁; ) HANGUL SYLLABLE PINJ
+D542;D542;1111 1175 11AD;D542;1111 1175 11AD; # (핂; 핂; 핂; 핂; 핂; ) HANGUL SYLLABLE PINH
+D543;D543;1111 1175 11AE;D543;1111 1175 11AE; # (핃; 핃; 핃; 핃; 핃; ) HANGUL SYLLABLE PID
+D544;D544;1111 1175 11AF;D544;1111 1175 11AF; # (필; 필; 필; 필; 필; ) HANGUL SYLLABLE PIL
+D545;D545;1111 1175 11B0;D545;1111 1175 11B0; # (핅; 핅; 핅; 핅; 핅; ) HANGUL SYLLABLE PILG
+D546;D546;1111 1175 11B1;D546;1111 1175 11B1; # (핆; 핆; 핆; 핆; 핆; ) HANGUL SYLLABLE PILM
+D547;D547;1111 1175 11B2;D547;1111 1175 11B2; # (핇; 핇; 핇; 핇; 핇; ) HANGUL SYLLABLE PILB
+D548;D548;1111 1175 11B3;D548;1111 1175 11B3; # (핈; 핈; 핈; 핈; 핈; ) HANGUL SYLLABLE PILS
+D549;D549;1111 1175 11B4;D549;1111 1175 11B4; # (핉; 핉; 핉; 핉; 핉; ) HANGUL SYLLABLE PILT
+D54A;D54A;1111 1175 11B5;D54A;1111 1175 11B5; # (핊; 핊; 핊; 핊; 핊; ) HANGUL SYLLABLE PILP
+D54B;D54B;1111 1175 11B6;D54B;1111 1175 11B6; # (핋; 핋; 핋; 핋; 핋; ) HANGUL SYLLABLE PILH
+D54C;D54C;1111 1175 11B7;D54C;1111 1175 11B7; # (핌; 핌; 핌; 핌; 핌; ) HANGUL SYLLABLE PIM
+D54D;D54D;1111 1175 11B8;D54D;1111 1175 11B8; # (í•; í•; 핍; í•; 핍; ) HANGUL SYLLABLE PIB
+D54E;D54E;1111 1175 11B9;D54E;1111 1175 11B9; # (핎; 핎; 핎; 핎; 핎; ) HANGUL SYLLABLE PIBS
+D54F;D54F;1111 1175 11BA;D54F;1111 1175 11BA; # (í•; í•; 핏; í•; 핏; ) HANGUL SYLLABLE PIS
+D550;D550;1111 1175 11BB;D550;1111 1175 11BB; # (í•; í•; 핐; í•; 핐; ) HANGUL SYLLABLE PISS
+D551;D551;1111 1175 11BC;D551;1111 1175 11BC; # (핑; 핑; 핑; 핑; 핑; ) HANGUL SYLLABLE PING
+D552;D552;1111 1175 11BD;D552;1111 1175 11BD; # (핒; 핒; 핒; 핒; 핒; ) HANGUL SYLLABLE PIJ
+D553;D553;1111 1175 11BE;D553;1111 1175 11BE; # (핓; 핓; 핓; 핓; 핓; ) HANGUL SYLLABLE PIC
+D554;D554;1111 1175 11BF;D554;1111 1175 11BF; # (핔; 핔; 핔; 핔; 핔; ) HANGUL SYLLABLE PIK
+D555;D555;1111 1175 11C0;D555;1111 1175 11C0; # (핕; 핕; 핕; 핕; 핕; ) HANGUL SYLLABLE PIT
+D556;D556;1111 1175 11C1;D556;1111 1175 11C1; # (í•–; í•–; á„‘á…µá‡; í•–; á„‘á…µá‡; ) HANGUL SYLLABLE PIP
+D557;D557;1111 1175 11C2;D557;1111 1175 11C2; # (핗; 핗; 핗; 핗; 핗; ) HANGUL SYLLABLE PIH
+D558;D558;1112 1161;D558;1112 1161; # (하; 하; 하; 하; 하; ) HANGUL SYLLABLE HA
+D559;D559;1112 1161 11A8;D559;1112 1161 11A8; # (학; 학; 학; 학; 학; ) HANGUL SYLLABLE HAG
+D55A;D55A;1112 1161 11A9;D55A;1112 1161 11A9; # (핚; 핚; 핚; 핚; 핚; ) HANGUL SYLLABLE HAGG
+D55B;D55B;1112 1161 11AA;D55B;1112 1161 11AA; # (핛; 핛; 핛; 핛; 핛; ) HANGUL SYLLABLE HAGS
+D55C;D55C;1112 1161 11AB;D55C;1112 1161 11AB; # (한; 한; 한; 한; 한; ) HANGUL SYLLABLE HAN
+D55D;D55D;1112 1161 11AC;D55D;1112 1161 11AC; # (í•; í•; 핝; í•; 핝; ) HANGUL SYLLABLE HANJ
+D55E;D55E;1112 1161 11AD;D55E;1112 1161 11AD; # (핞; 핞; 핞; 핞; 핞; ) HANGUL SYLLABLE HANH
+D55F;D55F;1112 1161 11AE;D55F;1112 1161 11AE; # (핟; 핟; 핟; 핟; 핟; ) HANGUL SYLLABLE HAD
+D560;D560;1112 1161 11AF;D560;1112 1161 11AF; # (할; 할; 할; 할; 할; ) HANGUL SYLLABLE HAL
+D561;D561;1112 1161 11B0;D561;1112 1161 11B0; # (핡; 핡; 핡; 핡; 핡; ) HANGUL SYLLABLE HALG
+D562;D562;1112 1161 11B1;D562;1112 1161 11B1; # (핢; 핢; 핢; 핢; 핢; ) HANGUL SYLLABLE HALM
+D563;D563;1112 1161 11B2;D563;1112 1161 11B2; # (핣; 핣; 핣; 핣; 핣; ) HANGUL SYLLABLE HALB
+D564;D564;1112 1161 11B3;D564;1112 1161 11B3; # (핤; 핤; 핤; 핤; 핤; ) HANGUL SYLLABLE HALS
+D565;D565;1112 1161 11B4;D565;1112 1161 11B4; # (핥; 핥; 핥; 핥; 핥; ) HANGUL SYLLABLE HALT
+D566;D566;1112 1161 11B5;D566;1112 1161 11B5; # (핦; 핦; 핦; 핦; 핦; ) HANGUL SYLLABLE HALP
+D567;D567;1112 1161 11B6;D567;1112 1161 11B6; # (핧; 핧; 핧; 핧; 핧; ) HANGUL SYLLABLE HALH
+D568;D568;1112 1161 11B7;D568;1112 1161 11B7; # (함; 함; 함; 함; 함; ) HANGUL SYLLABLE HAM
+D569;D569;1112 1161 11B8;D569;1112 1161 11B8; # (합; 합; 합; 합; 합; ) HANGUL SYLLABLE HAB
+D56A;D56A;1112 1161 11B9;D56A;1112 1161 11B9; # (핪; 핪; 핪; 핪; 핪; ) HANGUL SYLLABLE HABS
+D56B;D56B;1112 1161 11BA;D56B;1112 1161 11BA; # (핫; 핫; 핫; 핫; 핫; ) HANGUL SYLLABLE HAS
+D56C;D56C;1112 1161 11BB;D56C;1112 1161 11BB; # (핬; 핬; 핬; 핬; 핬; ) HANGUL SYLLABLE HASS
+D56D;D56D;1112 1161 11BC;D56D;1112 1161 11BC; # (항; 항; 항; 항; 항; ) HANGUL SYLLABLE HANG
+D56E;D56E;1112 1161 11BD;D56E;1112 1161 11BD; # (핮; 핮; 핮; 핮; 핮; ) HANGUL SYLLABLE HAJ
+D56F;D56F;1112 1161 11BE;D56F;1112 1161 11BE; # (핯; 핯; 핯; 핯; 핯; ) HANGUL SYLLABLE HAC
+D570;D570;1112 1161 11BF;D570;1112 1161 11BF; # (핰; 핰; 핰; 핰; 핰; ) HANGUL SYLLABLE HAK
+D571;D571;1112 1161 11C0;D571;1112 1161 11C0; # (핱; 핱; 핱; 핱; 핱; ) HANGUL SYLLABLE HAT
+D572;D572;1112 1161 11C1;D572;1112 1161 11C1; # (핲; 핲; á„’á…¡á‡; 핲; á„’á…¡á‡; ) HANGUL SYLLABLE HAP
+D573;D573;1112 1161 11C2;D573;1112 1161 11C2; # (핳; 핳; 핳; 핳; 핳; ) HANGUL SYLLABLE HAH
+D574;D574;1112 1162;D574;1112 1162; # (í•´; í•´; á„’á…¢; í•´; á„’á…¢; ) HANGUL SYLLABLE HAE
+D575;D575;1112 1162 11A8;D575;1112 1162 11A8; # (핵; 핵; 핵; 핵; 핵; ) HANGUL SYLLABLE HAEG
+D576;D576;1112 1162 11A9;D576;1112 1162 11A9; # (핶; 핶; 핶; 핶; 핶; ) HANGUL SYLLABLE HAEGG
+D577;D577;1112 1162 11AA;D577;1112 1162 11AA; # (핷; 핷; 핷; 핷; 핷; ) HANGUL SYLLABLE HAEGS
+D578;D578;1112 1162 11AB;D578;1112 1162 11AB; # (핸; 핸; 핸; 핸; 핸; ) HANGUL SYLLABLE HAEN
+D579;D579;1112 1162 11AC;D579;1112 1162 11AC; # (핹; 핹; 핹; 핹; 핹; ) HANGUL SYLLABLE HAENJ
+D57A;D57A;1112 1162 11AD;D57A;1112 1162 11AD; # (핺; 핺; 핺; 핺; 핺; ) HANGUL SYLLABLE HAENH
+D57B;D57B;1112 1162 11AE;D57B;1112 1162 11AE; # (핻; 핻; 핻; 핻; 핻; ) HANGUL SYLLABLE HAED
+D57C;D57C;1112 1162 11AF;D57C;1112 1162 11AF; # (핼; 핼; 핼; 핼; 핼; ) HANGUL SYLLABLE HAEL
+D57D;D57D;1112 1162 11B0;D57D;1112 1162 11B0; # (핽; 핽; 핽; 핽; 핽; ) HANGUL SYLLABLE HAELG
+D57E;D57E;1112 1162 11B1;D57E;1112 1162 11B1; # (핾; 핾; 핾; 핾; 핾; ) HANGUL SYLLABLE HAELM
+D57F;D57F;1112 1162 11B2;D57F;1112 1162 11B2; # (핿; 핿; 핿; 핿; 핿; ) HANGUL SYLLABLE HAELB
+D580;D580;1112 1162 11B3;D580;1112 1162 11B3; # (햀; 햀; 햀; 햀; 햀; ) HANGUL SYLLABLE HAELS
+D581;D581;1112 1162 11B4;D581;1112 1162 11B4; # (í–; í–; 햁; í–; 햁; ) HANGUL SYLLABLE HAELT
+D582;D582;1112 1162 11B5;D582;1112 1162 11B5; # (햂; 햂; 햂; 햂; 햂; ) HANGUL SYLLABLE HAELP
+D583;D583;1112 1162 11B6;D583;1112 1162 11B6; # (햃; 햃; 햃; 햃; 햃; ) HANGUL SYLLABLE HAELH
+D584;D584;1112 1162 11B7;D584;1112 1162 11B7; # (햄; 햄; 햄; 햄; 햄; ) HANGUL SYLLABLE HAEM
+D585;D585;1112 1162 11B8;D585;1112 1162 11B8; # (햅; 햅; 햅; 햅; 햅; ) HANGUL SYLLABLE HAEB
+D586;D586;1112 1162 11B9;D586;1112 1162 11B9; # (햆; 햆; 햆; 햆; 햆; ) HANGUL SYLLABLE HAEBS
+D587;D587;1112 1162 11BA;D587;1112 1162 11BA; # (햇; 햇; 햇; 햇; 햇; ) HANGUL SYLLABLE HAES
+D588;D588;1112 1162 11BB;D588;1112 1162 11BB; # (했; 했; 했; 했; 했; ) HANGUL SYLLABLE HAESS
+D589;D589;1112 1162 11BC;D589;1112 1162 11BC; # (행; 행; 행; 행; 행; ) HANGUL SYLLABLE HAENG
+D58A;D58A;1112 1162 11BD;D58A;1112 1162 11BD; # (햊; 햊; 햊; 햊; 햊; ) HANGUL SYLLABLE HAEJ
+D58B;D58B;1112 1162 11BE;D58B;1112 1162 11BE; # (햋; 햋; 햋; 햋; 햋; ) HANGUL SYLLABLE HAEC
+D58C;D58C;1112 1162 11BF;D58C;1112 1162 11BF; # (햌; 햌; 햌; 햌; 햌; ) HANGUL SYLLABLE HAEK
+D58D;D58D;1112 1162 11C0;D58D;1112 1162 11C0; # (í–; í–; 햍; í–; 햍; ) HANGUL SYLLABLE HAET
+D58E;D58E;1112 1162 11C1;D58E;1112 1162 11C1; # (í–Ž; í–Ž; á„’á…¢á‡; í–Ž; á„’á…¢á‡; ) HANGUL SYLLABLE HAEP
+D58F;D58F;1112 1162 11C2;D58F;1112 1162 11C2; # (í–; í–; 햏; í–; 햏; ) HANGUL SYLLABLE HAEH
+D590;D590;1112 1163;D590;1112 1163; # (í–; í–; á„’á…£; í–; á„’á…£; ) HANGUL SYLLABLE HYA
+D591;D591;1112 1163 11A8;D591;1112 1163 11A8; # (햑; 햑; 햑; 햑; 햑; ) HANGUL SYLLABLE HYAG
+D592;D592;1112 1163 11A9;D592;1112 1163 11A9; # (햒; 햒; 햒; 햒; 햒; ) HANGUL SYLLABLE HYAGG
+D593;D593;1112 1163 11AA;D593;1112 1163 11AA; # (햓; 햓; 햓; 햓; 햓; ) HANGUL SYLLABLE HYAGS
+D594;D594;1112 1163 11AB;D594;1112 1163 11AB; # (햔; 햔; 햔; 햔; 햔; ) HANGUL SYLLABLE HYAN
+D595;D595;1112 1163 11AC;D595;1112 1163 11AC; # (햕; 햕; 햕; 햕; 햕; ) HANGUL SYLLABLE HYANJ
+D596;D596;1112 1163 11AD;D596;1112 1163 11AD; # (햖; 햖; 햖; 햖; 햖; ) HANGUL SYLLABLE HYANH
+D597;D597;1112 1163 11AE;D597;1112 1163 11AE; # (햗; 햗; 햗; 햗; 햗; ) HANGUL SYLLABLE HYAD
+D598;D598;1112 1163 11AF;D598;1112 1163 11AF; # (햘; 햘; 햘; 햘; 햘; ) HANGUL SYLLABLE HYAL
+D599;D599;1112 1163 11B0;D599;1112 1163 11B0; # (햙; 햙; 햙; 햙; 햙; ) HANGUL SYLLABLE HYALG
+D59A;D59A;1112 1163 11B1;D59A;1112 1163 11B1; # (햚; 햚; 햚; 햚; 햚; ) HANGUL SYLLABLE HYALM
+D59B;D59B;1112 1163 11B2;D59B;1112 1163 11B2; # (햛; 햛; 햛; 햛; 햛; ) HANGUL SYLLABLE HYALB
+D59C;D59C;1112 1163 11B3;D59C;1112 1163 11B3; # (햜; 햜; 햜; 햜; 햜; ) HANGUL SYLLABLE HYALS
+D59D;D59D;1112 1163 11B4;D59D;1112 1163 11B4; # (í–; í–; 햝; í–; 햝; ) HANGUL SYLLABLE HYALT
+D59E;D59E;1112 1163 11B5;D59E;1112 1163 11B5; # (햞; 햞; 햞; 햞; 햞; ) HANGUL SYLLABLE HYALP
+D59F;D59F;1112 1163 11B6;D59F;1112 1163 11B6; # (햟; 햟; 햟; 햟; 햟; ) HANGUL SYLLABLE HYALH
+D5A0;D5A0;1112 1163 11B7;D5A0;1112 1163 11B7; # (햠; 햠; 햠; 햠; 햠; ) HANGUL SYLLABLE HYAM
+D5A1;D5A1;1112 1163 11B8;D5A1;1112 1163 11B8; # (햡; 햡; 햡; 햡; 햡; ) HANGUL SYLLABLE HYAB
+D5A2;D5A2;1112 1163 11B9;D5A2;1112 1163 11B9; # (햢; 햢; 햢; 햢; 햢; ) HANGUL SYLLABLE HYABS
+D5A3;D5A3;1112 1163 11BA;D5A3;1112 1163 11BA; # (햣; 햣; 햣; 햣; 햣; ) HANGUL SYLLABLE HYAS
+D5A4;D5A4;1112 1163 11BB;D5A4;1112 1163 11BB; # (햤; 햤; 햤; 햤; 햤; ) HANGUL SYLLABLE HYASS
+D5A5;D5A5;1112 1163 11BC;D5A5;1112 1163 11BC; # (향; 향; 향; 향; 향; ) HANGUL SYLLABLE HYANG
+D5A6;D5A6;1112 1163 11BD;D5A6;1112 1163 11BD; # (햦; 햦; 햦; 햦; 햦; ) HANGUL SYLLABLE HYAJ
+D5A7;D5A7;1112 1163 11BE;D5A7;1112 1163 11BE; # (햧; 햧; 햧; 햧; 햧; ) HANGUL SYLLABLE HYAC
+D5A8;D5A8;1112 1163 11BF;D5A8;1112 1163 11BF; # (햨; 햨; 햨; 햨; 햨; ) HANGUL SYLLABLE HYAK
+D5A9;D5A9;1112 1163 11C0;D5A9;1112 1163 11C0; # (햩; 햩; 햩; 햩; 햩; ) HANGUL SYLLABLE HYAT
+D5AA;D5AA;1112 1163 11C1;D5AA;1112 1163 11C1; # (í–ª; í–ª; á„’á…£á‡; í–ª; á„’á…£á‡; ) HANGUL SYLLABLE HYAP
+D5AB;D5AB;1112 1163 11C2;D5AB;1112 1163 11C2; # (햫; 햫; 햫; 햫; 햫; ) HANGUL SYLLABLE HYAH
+D5AC;D5AC;1112 1164;D5AC;1112 1164; # (í–¬; í–¬; á„’á…¤; í–¬; á„’á…¤; ) HANGUL SYLLABLE HYAE
+D5AD;D5AD;1112 1164 11A8;D5AD;1112 1164 11A8; # (햭; 햭; 햭; 햭; 햭; ) HANGUL SYLLABLE HYAEG
+D5AE;D5AE;1112 1164 11A9;D5AE;1112 1164 11A9; # (햮; 햮; 햮; 햮; 햮; ) HANGUL SYLLABLE HYAEGG
+D5AF;D5AF;1112 1164 11AA;D5AF;1112 1164 11AA; # (햯; 햯; 햯; 햯; 햯; ) HANGUL SYLLABLE HYAEGS
+D5B0;D5B0;1112 1164 11AB;D5B0;1112 1164 11AB; # (햰; 햰; 햰; 햰; 햰; ) HANGUL SYLLABLE HYAEN
+D5B1;D5B1;1112 1164 11AC;D5B1;1112 1164 11AC; # (햱; 햱; 햱; 햱; 햱; ) HANGUL SYLLABLE HYAENJ
+D5B2;D5B2;1112 1164 11AD;D5B2;1112 1164 11AD; # (햲; 햲; 햲; 햲; 햲; ) HANGUL SYLLABLE HYAENH
+D5B3;D5B3;1112 1164 11AE;D5B3;1112 1164 11AE; # (햳; 햳; 햳; 햳; 햳; ) HANGUL SYLLABLE HYAED
+D5B4;D5B4;1112 1164 11AF;D5B4;1112 1164 11AF; # (햴; 햴; 햴; 햴; 햴; ) HANGUL SYLLABLE HYAEL
+D5B5;D5B5;1112 1164 11B0;D5B5;1112 1164 11B0; # (햵; 햵; 햵; 햵; 햵; ) HANGUL SYLLABLE HYAELG
+D5B6;D5B6;1112 1164 11B1;D5B6;1112 1164 11B1; # (햶; 햶; 햶; 햶; 햶; ) HANGUL SYLLABLE HYAELM
+D5B7;D5B7;1112 1164 11B2;D5B7;1112 1164 11B2; # (햷; 햷; 햷; 햷; 햷; ) HANGUL SYLLABLE HYAELB
+D5B8;D5B8;1112 1164 11B3;D5B8;1112 1164 11B3; # (햸; 햸; 햸; 햸; 햸; ) HANGUL SYLLABLE HYAELS
+D5B9;D5B9;1112 1164 11B4;D5B9;1112 1164 11B4; # (햹; 햹; 햹; 햹; 햹; ) HANGUL SYLLABLE HYAELT
+D5BA;D5BA;1112 1164 11B5;D5BA;1112 1164 11B5; # (햺; 햺; 햺; 햺; 햺; ) HANGUL SYLLABLE HYAELP
+D5BB;D5BB;1112 1164 11B6;D5BB;1112 1164 11B6; # (햻; 햻; 햻; 햻; 햻; ) HANGUL SYLLABLE HYAELH
+D5BC;D5BC;1112 1164 11B7;D5BC;1112 1164 11B7; # (햼; 햼; 햼; 햼; 햼; ) HANGUL SYLLABLE HYAEM
+D5BD;D5BD;1112 1164 11B8;D5BD;1112 1164 11B8; # (햽; 햽; 햽; 햽; 햽; ) HANGUL SYLLABLE HYAEB
+D5BE;D5BE;1112 1164 11B9;D5BE;1112 1164 11B9; # (햾; 햾; 햾; 햾; 햾; ) HANGUL SYLLABLE HYAEBS
+D5BF;D5BF;1112 1164 11BA;D5BF;1112 1164 11BA; # (햿; 햿; 햿; 햿; 햿; ) HANGUL SYLLABLE HYAES
+D5C0;D5C0;1112 1164 11BB;D5C0;1112 1164 11BB; # (헀; 헀; 헀; 헀; 헀; ) HANGUL SYLLABLE HYAESS
+D5C1;D5C1;1112 1164 11BC;D5C1;1112 1164 11BC; # (í—; í—; 헁; í—; 헁; ) HANGUL SYLLABLE HYAENG
+D5C2;D5C2;1112 1164 11BD;D5C2;1112 1164 11BD; # (헂; 헂; 헂; 헂; 헂; ) HANGUL SYLLABLE HYAEJ
+D5C3;D5C3;1112 1164 11BE;D5C3;1112 1164 11BE; # (헃; 헃; 헃; 헃; 헃; ) HANGUL SYLLABLE HYAEC
+D5C4;D5C4;1112 1164 11BF;D5C4;1112 1164 11BF; # (헄; 헄; 헄; 헄; 헄; ) HANGUL SYLLABLE HYAEK
+D5C5;D5C5;1112 1164 11C0;D5C5;1112 1164 11C0; # (헅; 헅; 헅; 헅; 헅; ) HANGUL SYLLABLE HYAET
+D5C6;D5C6;1112 1164 11C1;D5C6;1112 1164 11C1; # (í—†; í—†; á„’á…¤á‡; í—†; á„’á…¤á‡; ) HANGUL SYLLABLE HYAEP
+D5C7;D5C7;1112 1164 11C2;D5C7;1112 1164 11C2; # (헇; 헇; 헇; 헇; 헇; ) HANGUL SYLLABLE HYAEH
+D5C8;D5C8;1112 1165;D5C8;1112 1165; # (í—ˆ; í—ˆ; á„’á…¥; í—ˆ; á„’á…¥; ) HANGUL SYLLABLE HEO
+D5C9;D5C9;1112 1165 11A8;D5C9;1112 1165 11A8; # (헉; 헉; 헉; 헉; 헉; ) HANGUL SYLLABLE HEOG
+D5CA;D5CA;1112 1165 11A9;D5CA;1112 1165 11A9; # (헊; 헊; 헊; 헊; 헊; ) HANGUL SYLLABLE HEOGG
+D5CB;D5CB;1112 1165 11AA;D5CB;1112 1165 11AA; # (헋; 헋; 헋; 헋; 헋; ) HANGUL SYLLABLE HEOGS
+D5CC;D5CC;1112 1165 11AB;D5CC;1112 1165 11AB; # (헌; 헌; 헌; 헌; 헌; ) HANGUL SYLLABLE HEON
+D5CD;D5CD;1112 1165 11AC;D5CD;1112 1165 11AC; # (í—; í—; 헍; í—; 헍; ) HANGUL SYLLABLE HEONJ
+D5CE;D5CE;1112 1165 11AD;D5CE;1112 1165 11AD; # (헎; 헎; 헎; 헎; 헎; ) HANGUL SYLLABLE HEONH
+D5CF;D5CF;1112 1165 11AE;D5CF;1112 1165 11AE; # (í—; í—; 헏; í—; 헏; ) HANGUL SYLLABLE HEOD
+D5D0;D5D0;1112 1165 11AF;D5D0;1112 1165 11AF; # (í—; í—; 헐; í—; 헐; ) HANGUL SYLLABLE HEOL
+D5D1;D5D1;1112 1165 11B0;D5D1;1112 1165 11B0; # (헑; 헑; 헑; 헑; 헑; ) HANGUL SYLLABLE HEOLG
+D5D2;D5D2;1112 1165 11B1;D5D2;1112 1165 11B1; # (헒; 헒; 헒; 헒; 헒; ) HANGUL SYLLABLE HEOLM
+D5D3;D5D3;1112 1165 11B2;D5D3;1112 1165 11B2; # (헓; 헓; 헓; 헓; 헓; ) HANGUL SYLLABLE HEOLB
+D5D4;D5D4;1112 1165 11B3;D5D4;1112 1165 11B3; # (헔; 헔; 헔; 헔; 헔; ) HANGUL SYLLABLE HEOLS
+D5D5;D5D5;1112 1165 11B4;D5D5;1112 1165 11B4; # (헕; 헕; 헕; 헕; 헕; ) HANGUL SYLLABLE HEOLT
+D5D6;D5D6;1112 1165 11B5;D5D6;1112 1165 11B5; # (헖; 헖; 헖; 헖; 헖; ) HANGUL SYLLABLE HEOLP
+D5D7;D5D7;1112 1165 11B6;D5D7;1112 1165 11B6; # (헗; 헗; 헗; 헗; 헗; ) HANGUL SYLLABLE HEOLH
+D5D8;D5D8;1112 1165 11B7;D5D8;1112 1165 11B7; # (험; 험; 험; 험; 험; ) HANGUL SYLLABLE HEOM
+D5D9;D5D9;1112 1165 11B8;D5D9;1112 1165 11B8; # (헙; 헙; 헙; 헙; 헙; ) HANGUL SYLLABLE HEOB
+D5DA;D5DA;1112 1165 11B9;D5DA;1112 1165 11B9; # (헚; 헚; 헚; 헚; 헚; ) HANGUL SYLLABLE HEOBS
+D5DB;D5DB;1112 1165 11BA;D5DB;1112 1165 11BA; # (헛; 헛; 헛; 헛; 헛; ) HANGUL SYLLABLE HEOS
+D5DC;D5DC;1112 1165 11BB;D5DC;1112 1165 11BB; # (헜; 헜; 헜; 헜; 헜; ) HANGUL SYLLABLE HEOSS
+D5DD;D5DD;1112 1165 11BC;D5DD;1112 1165 11BC; # (í—; í—; 헝; í—; 헝; ) HANGUL SYLLABLE HEONG
+D5DE;D5DE;1112 1165 11BD;D5DE;1112 1165 11BD; # (헞; 헞; 헞; 헞; 헞; ) HANGUL SYLLABLE HEOJ
+D5DF;D5DF;1112 1165 11BE;D5DF;1112 1165 11BE; # (헟; 헟; 헟; 헟; 헟; ) HANGUL SYLLABLE HEOC
+D5E0;D5E0;1112 1165 11BF;D5E0;1112 1165 11BF; # (헠; 헠; 헠; 헠; 헠; ) HANGUL SYLLABLE HEOK
+D5E1;D5E1;1112 1165 11C0;D5E1;1112 1165 11C0; # (헡; 헡; 헡; 헡; 헡; ) HANGUL SYLLABLE HEOT
+D5E2;D5E2;1112 1165 11C1;D5E2;1112 1165 11C1; # (í—¢; í—¢; á„’á…¥á‡; í—¢; á„’á…¥á‡; ) HANGUL SYLLABLE HEOP
+D5E3;D5E3;1112 1165 11C2;D5E3;1112 1165 11C2; # (헣; 헣; 헣; 헣; 헣; ) HANGUL SYLLABLE HEOH
+D5E4;D5E4;1112 1166;D5E4;1112 1166; # (í—¤; í—¤; á„’á…¦; í—¤; á„’á…¦; ) HANGUL SYLLABLE HE
+D5E5;D5E5;1112 1166 11A8;D5E5;1112 1166 11A8; # (헥; 헥; 헥; 헥; 헥; ) HANGUL SYLLABLE HEG
+D5E6;D5E6;1112 1166 11A9;D5E6;1112 1166 11A9; # (헦; 헦; 헦; 헦; 헦; ) HANGUL SYLLABLE HEGG
+D5E7;D5E7;1112 1166 11AA;D5E7;1112 1166 11AA; # (헧; 헧; 헧; 헧; 헧; ) HANGUL SYLLABLE HEGS
+D5E8;D5E8;1112 1166 11AB;D5E8;1112 1166 11AB; # (헨; 헨; 헨; 헨; 헨; ) HANGUL SYLLABLE HEN
+D5E9;D5E9;1112 1166 11AC;D5E9;1112 1166 11AC; # (헩; 헩; 헩; 헩; 헩; ) HANGUL SYLLABLE HENJ
+D5EA;D5EA;1112 1166 11AD;D5EA;1112 1166 11AD; # (헪; 헪; 헪; 헪; 헪; ) HANGUL SYLLABLE HENH
+D5EB;D5EB;1112 1166 11AE;D5EB;1112 1166 11AE; # (헫; 헫; 헫; 헫; 헫; ) HANGUL SYLLABLE HED
+D5EC;D5EC;1112 1166 11AF;D5EC;1112 1166 11AF; # (헬; 헬; 헬; 헬; 헬; ) HANGUL SYLLABLE HEL
+D5ED;D5ED;1112 1166 11B0;D5ED;1112 1166 11B0; # (헭; 헭; 헭; 헭; 헭; ) HANGUL SYLLABLE HELG
+D5EE;D5EE;1112 1166 11B1;D5EE;1112 1166 11B1; # (헮; 헮; 헮; 헮; 헮; ) HANGUL SYLLABLE HELM
+D5EF;D5EF;1112 1166 11B2;D5EF;1112 1166 11B2; # (헯; 헯; 헯; 헯; 헯; ) HANGUL SYLLABLE HELB
+D5F0;D5F0;1112 1166 11B3;D5F0;1112 1166 11B3; # (헰; 헰; 헰; 헰; 헰; ) HANGUL SYLLABLE HELS
+D5F1;D5F1;1112 1166 11B4;D5F1;1112 1166 11B4; # (헱; 헱; 헱; 헱; 헱; ) HANGUL SYLLABLE HELT
+D5F2;D5F2;1112 1166 11B5;D5F2;1112 1166 11B5; # (헲; 헲; 헲; 헲; 헲; ) HANGUL SYLLABLE HELP
+D5F3;D5F3;1112 1166 11B6;D5F3;1112 1166 11B6; # (헳; 헳; 헳; 헳; 헳; ) HANGUL SYLLABLE HELH
+D5F4;D5F4;1112 1166 11B7;D5F4;1112 1166 11B7; # (헴; 헴; 헴; 헴; 헴; ) HANGUL SYLLABLE HEM
+D5F5;D5F5;1112 1166 11B8;D5F5;1112 1166 11B8; # (헵; 헵; 헵; 헵; 헵; ) HANGUL SYLLABLE HEB
+D5F6;D5F6;1112 1166 11B9;D5F6;1112 1166 11B9; # (헶; 헶; 헶; 헶; 헶; ) HANGUL SYLLABLE HEBS
+D5F7;D5F7;1112 1166 11BA;D5F7;1112 1166 11BA; # (헷; 헷; 헷; 헷; 헷; ) HANGUL SYLLABLE HES
+D5F8;D5F8;1112 1166 11BB;D5F8;1112 1166 11BB; # (헸; 헸; 헸; 헸; 헸; ) HANGUL SYLLABLE HESS
+D5F9;D5F9;1112 1166 11BC;D5F9;1112 1166 11BC; # (헹; 헹; 헹; 헹; 헹; ) HANGUL SYLLABLE HENG
+D5FA;D5FA;1112 1166 11BD;D5FA;1112 1166 11BD; # (헺; 헺; 헺; 헺; 헺; ) HANGUL SYLLABLE HEJ
+D5FB;D5FB;1112 1166 11BE;D5FB;1112 1166 11BE; # (헻; 헻; 헻; 헻; 헻; ) HANGUL SYLLABLE HEC
+D5FC;D5FC;1112 1166 11BF;D5FC;1112 1166 11BF; # (헼; 헼; 헼; 헼; 헼; ) HANGUL SYLLABLE HEK
+D5FD;D5FD;1112 1166 11C0;D5FD;1112 1166 11C0; # (헽; 헽; 헽; 헽; 헽; ) HANGUL SYLLABLE HET
+D5FE;D5FE;1112 1166 11C1;D5FE;1112 1166 11C1; # (í—¾; í—¾; á„’á…¦á‡; í—¾; á„’á…¦á‡; ) HANGUL SYLLABLE HEP
+D5FF;D5FF;1112 1166 11C2;D5FF;1112 1166 11C2; # (헿; 헿; 헿; 헿; 헿; ) HANGUL SYLLABLE HEH
+D600;D600;1112 1167;D600;1112 1167; # (혀; 혀; 혀; 혀; 혀; ) HANGUL SYLLABLE HYEO
+D601;D601;1112 1167 11A8;D601;1112 1167 11A8; # (í˜; í˜; 혁; í˜; 혁; ) HANGUL SYLLABLE HYEOG
+D602;D602;1112 1167 11A9;D602;1112 1167 11A9; # (혂; 혂; 혂; 혂; 혂; ) HANGUL SYLLABLE HYEOGG
+D603;D603;1112 1167 11AA;D603;1112 1167 11AA; # (혃; 혃; 혃; 혃; 혃; ) HANGUL SYLLABLE HYEOGS
+D604;D604;1112 1167 11AB;D604;1112 1167 11AB; # (현; 현; 현; 현; 현; ) HANGUL SYLLABLE HYEON
+D605;D605;1112 1167 11AC;D605;1112 1167 11AC; # (혅; 혅; 혅; 혅; 혅; ) HANGUL SYLLABLE HYEONJ
+D606;D606;1112 1167 11AD;D606;1112 1167 11AD; # (혆; 혆; 혆; 혆; 혆; ) HANGUL SYLLABLE HYEONH
+D607;D607;1112 1167 11AE;D607;1112 1167 11AE; # (혇; 혇; 혇; 혇; 혇; ) HANGUL SYLLABLE HYEOD
+D608;D608;1112 1167 11AF;D608;1112 1167 11AF; # (혈; 혈; 혈; 혈; 혈; ) HANGUL SYLLABLE HYEOL
+D609;D609;1112 1167 11B0;D609;1112 1167 11B0; # (혉; 혉; 혉; 혉; 혉; ) HANGUL SYLLABLE HYEOLG
+D60A;D60A;1112 1167 11B1;D60A;1112 1167 11B1; # (혊; 혊; 혊; 혊; 혊; ) HANGUL SYLLABLE HYEOLM
+D60B;D60B;1112 1167 11B2;D60B;1112 1167 11B2; # (혋; 혋; 혋; 혋; 혋; ) HANGUL SYLLABLE HYEOLB
+D60C;D60C;1112 1167 11B3;D60C;1112 1167 11B3; # (혌; 혌; 혌; 혌; 혌; ) HANGUL SYLLABLE HYEOLS
+D60D;D60D;1112 1167 11B4;D60D;1112 1167 11B4; # (í˜; í˜; 혍; í˜; 혍; ) HANGUL SYLLABLE HYEOLT
+D60E;D60E;1112 1167 11B5;D60E;1112 1167 11B5; # (혎; 혎; 혎; 혎; 혎; ) HANGUL SYLLABLE HYEOLP
+D60F;D60F;1112 1167 11B6;D60F;1112 1167 11B6; # (í˜; í˜; 혏; í˜; 혏; ) HANGUL SYLLABLE HYEOLH
+D610;D610;1112 1167 11B7;D610;1112 1167 11B7; # (í˜; í˜; 혐; í˜; 혐; ) HANGUL SYLLABLE HYEOM
+D611;D611;1112 1167 11B8;D611;1112 1167 11B8; # (협; 협; 협; 협; 협; ) HANGUL SYLLABLE HYEOB
+D612;D612;1112 1167 11B9;D612;1112 1167 11B9; # (혒; 혒; 혒; 혒; 혒; ) HANGUL SYLLABLE HYEOBS
+D613;D613;1112 1167 11BA;D613;1112 1167 11BA; # (혓; 혓; 혓; 혓; 혓; ) HANGUL SYLLABLE HYEOS
+D614;D614;1112 1167 11BB;D614;1112 1167 11BB; # (혔; 혔; 혔; 혔; 혔; ) HANGUL SYLLABLE HYEOSS
+D615;D615;1112 1167 11BC;D615;1112 1167 11BC; # (형; 형; 형; 형; 형; ) HANGUL SYLLABLE HYEONG
+D616;D616;1112 1167 11BD;D616;1112 1167 11BD; # (혖; 혖; 혖; 혖; 혖; ) HANGUL SYLLABLE HYEOJ
+D617;D617;1112 1167 11BE;D617;1112 1167 11BE; # (혗; 혗; 혗; 혗; 혗; ) HANGUL SYLLABLE HYEOC
+D618;D618;1112 1167 11BF;D618;1112 1167 11BF; # (혘; 혘; 혘; 혘; 혘; ) HANGUL SYLLABLE HYEOK
+D619;D619;1112 1167 11C0;D619;1112 1167 11C0; # (혙; 혙; 혙; 혙; 혙; ) HANGUL SYLLABLE HYEOT
+D61A;D61A;1112 1167 11C1;D61A;1112 1167 11C1; # (혚; 혚; á„’á…§á‡; 혚; á„’á…§á‡; ) HANGUL SYLLABLE HYEOP
+D61B;D61B;1112 1167 11C2;D61B;1112 1167 11C2; # (혛; 혛; 혛; 혛; 혛; ) HANGUL SYLLABLE HYEOH
+D61C;D61C;1112 1168;D61C;1112 1168; # (혜; 혜; 혜; 혜; 혜; ) HANGUL SYLLABLE HYE
+D61D;D61D;1112 1168 11A8;D61D;1112 1168 11A8; # (í˜; í˜; 혝; í˜; 혝; ) HANGUL SYLLABLE HYEG
+D61E;D61E;1112 1168 11A9;D61E;1112 1168 11A9; # (혞; 혞; 혞; 혞; 혞; ) HANGUL SYLLABLE HYEGG
+D61F;D61F;1112 1168 11AA;D61F;1112 1168 11AA; # (혟; 혟; 혟; 혟; 혟; ) HANGUL SYLLABLE HYEGS
+D620;D620;1112 1168 11AB;D620;1112 1168 11AB; # (혠; 혠; 혠; 혠; 혠; ) HANGUL SYLLABLE HYEN
+D621;D621;1112 1168 11AC;D621;1112 1168 11AC; # (혡; 혡; 혡; 혡; 혡; ) HANGUL SYLLABLE HYENJ
+D622;D622;1112 1168 11AD;D622;1112 1168 11AD; # (혢; 혢; 혢; 혢; 혢; ) HANGUL SYLLABLE HYENH
+D623;D623;1112 1168 11AE;D623;1112 1168 11AE; # (혣; 혣; 혣; 혣; 혣; ) HANGUL SYLLABLE HYED
+D624;D624;1112 1168 11AF;D624;1112 1168 11AF; # (혤; 혤; 혤; 혤; 혤; ) HANGUL SYLLABLE HYEL
+D625;D625;1112 1168 11B0;D625;1112 1168 11B0; # (혥; 혥; 혥; 혥; 혥; ) HANGUL SYLLABLE HYELG
+D626;D626;1112 1168 11B1;D626;1112 1168 11B1; # (혦; 혦; 혦; 혦; 혦; ) HANGUL SYLLABLE HYELM
+D627;D627;1112 1168 11B2;D627;1112 1168 11B2; # (혧; 혧; 혧; 혧; 혧; ) HANGUL SYLLABLE HYELB
+D628;D628;1112 1168 11B3;D628;1112 1168 11B3; # (혨; 혨; 혨; 혨; 혨; ) HANGUL SYLLABLE HYELS
+D629;D629;1112 1168 11B4;D629;1112 1168 11B4; # (혩; 혩; 혩; 혩; 혩; ) HANGUL SYLLABLE HYELT
+D62A;D62A;1112 1168 11B5;D62A;1112 1168 11B5; # (혪; 혪; 혪; 혪; 혪; ) HANGUL SYLLABLE HYELP
+D62B;D62B;1112 1168 11B6;D62B;1112 1168 11B6; # (혫; 혫; 혫; 혫; 혫; ) HANGUL SYLLABLE HYELH
+D62C;D62C;1112 1168 11B7;D62C;1112 1168 11B7; # (혬; 혬; 혬; 혬; 혬; ) HANGUL SYLLABLE HYEM
+D62D;D62D;1112 1168 11B8;D62D;1112 1168 11B8; # (혭; 혭; 혭; 혭; 혭; ) HANGUL SYLLABLE HYEB
+D62E;D62E;1112 1168 11B9;D62E;1112 1168 11B9; # (혮; 혮; 혮; 혮; 혮; ) HANGUL SYLLABLE HYEBS
+D62F;D62F;1112 1168 11BA;D62F;1112 1168 11BA; # (혯; 혯; 혯; 혯; 혯; ) HANGUL SYLLABLE HYES
+D630;D630;1112 1168 11BB;D630;1112 1168 11BB; # (혰; 혰; 혰; 혰; 혰; ) HANGUL SYLLABLE HYESS
+D631;D631;1112 1168 11BC;D631;1112 1168 11BC; # (혱; 혱; 혱; 혱; 혱; ) HANGUL SYLLABLE HYENG
+D632;D632;1112 1168 11BD;D632;1112 1168 11BD; # (혲; 혲; 혲; 혲; 혲; ) HANGUL SYLLABLE HYEJ
+D633;D633;1112 1168 11BE;D633;1112 1168 11BE; # (혳; 혳; 혳; 혳; 혳; ) HANGUL SYLLABLE HYEC
+D634;D634;1112 1168 11BF;D634;1112 1168 11BF; # (혴; 혴; 혴; 혴; 혴; ) HANGUL SYLLABLE HYEK
+D635;D635;1112 1168 11C0;D635;1112 1168 11C0; # (혵; 혵; 혵; 혵; 혵; ) HANGUL SYLLABLE HYET
+D636;D636;1112 1168 11C1;D636;1112 1168 11C1; # (혶; 혶; á„’á…¨á‡; 혶; á„’á…¨á‡; ) HANGUL SYLLABLE HYEP
+D637;D637;1112 1168 11C2;D637;1112 1168 11C2; # (혷; 혷; 혷; 혷; 혷; ) HANGUL SYLLABLE HYEH
+D638;D638;1112 1169;D638;1112 1169; # (호; 호; 호; 호; 호; ) HANGUL SYLLABLE HO
+D639;D639;1112 1169 11A8;D639;1112 1169 11A8; # (혹; 혹; 혹; 혹; 혹; ) HANGUL SYLLABLE HOG
+D63A;D63A;1112 1169 11A9;D63A;1112 1169 11A9; # (혺; 혺; 혺; 혺; 혺; ) HANGUL SYLLABLE HOGG
+D63B;D63B;1112 1169 11AA;D63B;1112 1169 11AA; # (혻; 혻; 혻; 혻; 혻; ) HANGUL SYLLABLE HOGS
+D63C;D63C;1112 1169 11AB;D63C;1112 1169 11AB; # (혼; 혼; 혼; 혼; 혼; ) HANGUL SYLLABLE HON
+D63D;D63D;1112 1169 11AC;D63D;1112 1169 11AC; # (혽; 혽; 혽; 혽; 혽; ) HANGUL SYLLABLE HONJ
+D63E;D63E;1112 1169 11AD;D63E;1112 1169 11AD; # (혾; 혾; 혾; 혾; 혾; ) HANGUL SYLLABLE HONH
+D63F;D63F;1112 1169 11AE;D63F;1112 1169 11AE; # (혿; 혿; 혿; 혿; 혿; ) HANGUL SYLLABLE HOD
+D640;D640;1112 1169 11AF;D640;1112 1169 11AF; # (홀; 홀; 홀; 홀; 홀; ) HANGUL SYLLABLE HOL
+D641;D641;1112 1169 11B0;D641;1112 1169 11B0; # (í™; í™; 홁; í™; 홁; ) HANGUL SYLLABLE HOLG
+D642;D642;1112 1169 11B1;D642;1112 1169 11B1; # (홂; 홂; 홂; 홂; 홂; ) HANGUL SYLLABLE HOLM
+D643;D643;1112 1169 11B2;D643;1112 1169 11B2; # (홃; 홃; 홃; 홃; 홃; ) HANGUL SYLLABLE HOLB
+D644;D644;1112 1169 11B3;D644;1112 1169 11B3; # (홄; 홄; 홄; 홄; 홄; ) HANGUL SYLLABLE HOLS
+D645;D645;1112 1169 11B4;D645;1112 1169 11B4; # (홅; 홅; 홅; 홅; 홅; ) HANGUL SYLLABLE HOLT
+D646;D646;1112 1169 11B5;D646;1112 1169 11B5; # (홆; 홆; 홆; 홆; 홆; ) HANGUL SYLLABLE HOLP
+D647;D647;1112 1169 11B6;D647;1112 1169 11B6; # (홇; 홇; 홇; 홇; 홇; ) HANGUL SYLLABLE HOLH
+D648;D648;1112 1169 11B7;D648;1112 1169 11B7; # (홈; 홈; 홈; 홈; 홈; ) HANGUL SYLLABLE HOM
+D649;D649;1112 1169 11B8;D649;1112 1169 11B8; # (홉; 홉; 홉; 홉; 홉; ) HANGUL SYLLABLE HOB
+D64A;D64A;1112 1169 11B9;D64A;1112 1169 11B9; # (홊; 홊; 홊; 홊; 홊; ) HANGUL SYLLABLE HOBS
+D64B;D64B;1112 1169 11BA;D64B;1112 1169 11BA; # (홋; 홋; 홋; 홋; 홋; ) HANGUL SYLLABLE HOS
+D64C;D64C;1112 1169 11BB;D64C;1112 1169 11BB; # (홌; 홌; 홌; 홌; 홌; ) HANGUL SYLLABLE HOSS
+D64D;D64D;1112 1169 11BC;D64D;1112 1169 11BC; # (í™; í™; 홍; í™; 홍; ) HANGUL SYLLABLE HONG
+D64E;D64E;1112 1169 11BD;D64E;1112 1169 11BD; # (홎; 홎; 홎; 홎; 홎; ) HANGUL SYLLABLE HOJ
+D64F;D64F;1112 1169 11BE;D64F;1112 1169 11BE; # (í™; í™; 홏; í™; 홏; ) HANGUL SYLLABLE HOC
+D650;D650;1112 1169 11BF;D650;1112 1169 11BF; # (í™; í™; 홐; í™; 홐; ) HANGUL SYLLABLE HOK
+D651;D651;1112 1169 11C0;D651;1112 1169 11C0; # (홑; 홑; 홑; 홑; 홑; ) HANGUL SYLLABLE HOT
+D652;D652;1112 1169 11C1;D652;1112 1169 11C1; # (í™’; í™’; á„’á…©á‡; í™’; á„’á…©á‡; ) HANGUL SYLLABLE HOP
+D653;D653;1112 1169 11C2;D653;1112 1169 11C2; # (홓; 홓; 홓; 홓; 홓; ) HANGUL SYLLABLE HOH
+D654;D654;1112 116A;D654;1112 116A; # (í™”; í™”; á„’á…ª; í™”; á„’á…ª; ) HANGUL SYLLABLE HWA
+D655;D655;1112 116A 11A8;D655;1112 116A 11A8; # (확; 확; 확; 확; 확; ) HANGUL SYLLABLE HWAG
+D656;D656;1112 116A 11A9;D656;1112 116A 11A9; # (홖; 홖; 홖; 홖; 홖; ) HANGUL SYLLABLE HWAGG
+D657;D657;1112 116A 11AA;D657;1112 116A 11AA; # (홗; 홗; 홗; 홗; 홗; ) HANGUL SYLLABLE HWAGS
+D658;D658;1112 116A 11AB;D658;1112 116A 11AB; # (환; 환; 환; 환; 환; ) HANGUL SYLLABLE HWAN
+D659;D659;1112 116A 11AC;D659;1112 116A 11AC; # (홙; 홙; 홙; 홙; 홙; ) HANGUL SYLLABLE HWANJ
+D65A;D65A;1112 116A 11AD;D65A;1112 116A 11AD; # (홚; 홚; 홚; 홚; 홚; ) HANGUL SYLLABLE HWANH
+D65B;D65B;1112 116A 11AE;D65B;1112 116A 11AE; # (홛; 홛; 홛; 홛; 홛; ) HANGUL SYLLABLE HWAD
+D65C;D65C;1112 116A 11AF;D65C;1112 116A 11AF; # (활; 활; 활; 활; 활; ) HANGUL SYLLABLE HWAL
+D65D;D65D;1112 116A 11B0;D65D;1112 116A 11B0; # (í™; í™; 홝; í™; 홝; ) HANGUL SYLLABLE HWALG
+D65E;D65E;1112 116A 11B1;D65E;1112 116A 11B1; # (홞; 홞; 홞; 홞; 홞; ) HANGUL SYLLABLE HWALM
+D65F;D65F;1112 116A 11B2;D65F;1112 116A 11B2; # (홟; 홟; 홟; 홟; 홟; ) HANGUL SYLLABLE HWALB
+D660;D660;1112 116A 11B3;D660;1112 116A 11B3; # (홠; 홠; 홠; 홠; 홠; ) HANGUL SYLLABLE HWALS
+D661;D661;1112 116A 11B4;D661;1112 116A 11B4; # (홡; 홡; 홡; 홡; 홡; ) HANGUL SYLLABLE HWALT
+D662;D662;1112 116A 11B5;D662;1112 116A 11B5; # (홢; 홢; 홢; 홢; 홢; ) HANGUL SYLLABLE HWALP
+D663;D663;1112 116A 11B6;D663;1112 116A 11B6; # (홣; 홣; 홣; 홣; 홣; ) HANGUL SYLLABLE HWALH
+D664;D664;1112 116A 11B7;D664;1112 116A 11B7; # (홤; 홤; 홤; 홤; 홤; ) HANGUL SYLLABLE HWAM
+D665;D665;1112 116A 11B8;D665;1112 116A 11B8; # (홥; 홥; 홥; 홥; 홥; ) HANGUL SYLLABLE HWAB
+D666;D666;1112 116A 11B9;D666;1112 116A 11B9; # (홦; 홦; 홦; 홦; 홦; ) HANGUL SYLLABLE HWABS
+D667;D667;1112 116A 11BA;D667;1112 116A 11BA; # (홧; 홧; 홧; 홧; 홧; ) HANGUL SYLLABLE HWAS
+D668;D668;1112 116A 11BB;D668;1112 116A 11BB; # (홨; 홨; 홨; 홨; 홨; ) HANGUL SYLLABLE HWASS
+D669;D669;1112 116A 11BC;D669;1112 116A 11BC; # (황; 황; 황; 황; 황; ) HANGUL SYLLABLE HWANG
+D66A;D66A;1112 116A 11BD;D66A;1112 116A 11BD; # (홪; 홪; 홪; 홪; 홪; ) HANGUL SYLLABLE HWAJ
+D66B;D66B;1112 116A 11BE;D66B;1112 116A 11BE; # (홫; 홫; 홫; 홫; 홫; ) HANGUL SYLLABLE HWAC
+D66C;D66C;1112 116A 11BF;D66C;1112 116A 11BF; # (홬; 홬; 홬; 홬; 홬; ) HANGUL SYLLABLE HWAK
+D66D;D66D;1112 116A 11C0;D66D;1112 116A 11C0; # (홭; 홭; 홭; 홭; 홭; ) HANGUL SYLLABLE HWAT
+D66E;D66E;1112 116A 11C1;D66E;1112 116A 11C1; # (í™®; í™®; á„’á…ªá‡; í™®; á„’á…ªá‡; ) HANGUL SYLLABLE HWAP
+D66F;D66F;1112 116A 11C2;D66F;1112 116A 11C2; # (홯; 홯; 홯; 홯; 홯; ) HANGUL SYLLABLE HWAH
+D670;D670;1112 116B;D670;1112 116B; # (í™°; í™°; á„’á…«; í™°; á„’á…«; ) HANGUL SYLLABLE HWAE
+D671;D671;1112 116B 11A8;D671;1112 116B 11A8; # (홱; 홱; 홱; 홱; 홱; ) HANGUL SYLLABLE HWAEG
+D672;D672;1112 116B 11A9;D672;1112 116B 11A9; # (홲; 홲; 홲; 홲; 홲; ) HANGUL SYLLABLE HWAEGG
+D673;D673;1112 116B 11AA;D673;1112 116B 11AA; # (홳; 홳; 홳; 홳; 홳; ) HANGUL SYLLABLE HWAEGS
+D674;D674;1112 116B 11AB;D674;1112 116B 11AB; # (홴; 홴; 홴; 홴; 홴; ) HANGUL SYLLABLE HWAEN
+D675;D675;1112 116B 11AC;D675;1112 116B 11AC; # (홵; 홵; 홵; 홵; 홵; ) HANGUL SYLLABLE HWAENJ
+D676;D676;1112 116B 11AD;D676;1112 116B 11AD; # (홶; 홶; 홶; 홶; 홶; ) HANGUL SYLLABLE HWAENH
+D677;D677;1112 116B 11AE;D677;1112 116B 11AE; # (홷; 홷; 홷; 홷; 홷; ) HANGUL SYLLABLE HWAED
+D678;D678;1112 116B 11AF;D678;1112 116B 11AF; # (홸; 홸; 홸; 홸; 홸; ) HANGUL SYLLABLE HWAEL
+D679;D679;1112 116B 11B0;D679;1112 116B 11B0; # (홹; 홹; 홹; 홹; 홹; ) HANGUL SYLLABLE HWAELG
+D67A;D67A;1112 116B 11B1;D67A;1112 116B 11B1; # (홺; 홺; 홺; 홺; 홺; ) HANGUL SYLLABLE HWAELM
+D67B;D67B;1112 116B 11B2;D67B;1112 116B 11B2; # (홻; 홻; 홻; 홻; 홻; ) HANGUL SYLLABLE HWAELB
+D67C;D67C;1112 116B 11B3;D67C;1112 116B 11B3; # (홼; 홼; 홼; 홼; 홼; ) HANGUL SYLLABLE HWAELS
+D67D;D67D;1112 116B 11B4;D67D;1112 116B 11B4; # (홽; 홽; 홽; 홽; 홽; ) HANGUL SYLLABLE HWAELT
+D67E;D67E;1112 116B 11B5;D67E;1112 116B 11B5; # (홾; 홾; 홾; 홾; 홾; ) HANGUL SYLLABLE HWAELP
+D67F;D67F;1112 116B 11B6;D67F;1112 116B 11B6; # (홿; 홿; 홿; 홿; 홿; ) HANGUL SYLLABLE HWAELH
+D680;D680;1112 116B 11B7;D680;1112 116B 11B7; # (횀; 횀; 횀; 횀; 횀; ) HANGUL SYLLABLE HWAEM
+D681;D681;1112 116B 11B8;D681;1112 116B 11B8; # (íš; íš; 횁; íš; 횁; ) HANGUL SYLLABLE HWAEB
+D682;D682;1112 116B 11B9;D682;1112 116B 11B9; # (횂; 횂; 횂; 횂; 횂; ) HANGUL SYLLABLE HWAEBS
+D683;D683;1112 116B 11BA;D683;1112 116B 11BA; # (횃; 횃; 횃; 횃; 횃; ) HANGUL SYLLABLE HWAES
+D684;D684;1112 116B 11BB;D684;1112 116B 11BB; # (횄; 횄; 횄; 횄; 횄; ) HANGUL SYLLABLE HWAESS
+D685;D685;1112 116B 11BC;D685;1112 116B 11BC; # (횅; 횅; 횅; 횅; 횅; ) HANGUL SYLLABLE HWAENG
+D686;D686;1112 116B 11BD;D686;1112 116B 11BD; # (횆; 횆; 횆; 횆; 횆; ) HANGUL SYLLABLE HWAEJ
+D687;D687;1112 116B 11BE;D687;1112 116B 11BE; # (횇; 횇; 횇; 횇; 횇; ) HANGUL SYLLABLE HWAEC
+D688;D688;1112 116B 11BF;D688;1112 116B 11BF; # (횈; 횈; 횈; 횈; 횈; ) HANGUL SYLLABLE HWAEK
+D689;D689;1112 116B 11C0;D689;1112 116B 11C0; # (횉; 횉; 횉; 횉; 횉; ) HANGUL SYLLABLE HWAET
+D68A;D68A;1112 116B 11C1;D68A;1112 116B 11C1; # (횊; 횊; á„’á…«á‡; 횊; á„’á…«á‡; ) HANGUL SYLLABLE HWAEP
+D68B;D68B;1112 116B 11C2;D68B;1112 116B 11C2; # (횋; 횋; 횋; 횋; 횋; ) HANGUL SYLLABLE HWAEH
+D68C;D68C;1112 116C;D68C;1112 116C; # (회; 회; 회; 회; 회; ) HANGUL SYLLABLE HOE
+D68D;D68D;1112 116C 11A8;D68D;1112 116C 11A8; # (íš; íš; 획; íš; 획; ) HANGUL SYLLABLE HOEG
+D68E;D68E;1112 116C 11A9;D68E;1112 116C 11A9; # (횎; 횎; 횎; 횎; 횎; ) HANGUL SYLLABLE HOEGG
+D68F;D68F;1112 116C 11AA;D68F;1112 116C 11AA; # (íš; íš; 횏; íš; 횏; ) HANGUL SYLLABLE HOEGS
+D690;D690;1112 116C 11AB;D690;1112 116C 11AB; # (íš; íš; 횐; íš; 횐; ) HANGUL SYLLABLE HOEN
+D691;D691;1112 116C 11AC;D691;1112 116C 11AC; # (횑; 횑; 횑; 횑; 횑; ) HANGUL SYLLABLE HOENJ
+D692;D692;1112 116C 11AD;D692;1112 116C 11AD; # (횒; 횒; 횒; 횒; 횒; ) HANGUL SYLLABLE HOENH
+D693;D693;1112 116C 11AE;D693;1112 116C 11AE; # (횓; 횓; 횓; 횓; 횓; ) HANGUL SYLLABLE HOED
+D694;D694;1112 116C 11AF;D694;1112 116C 11AF; # (횔; 횔; 횔; 횔; 횔; ) HANGUL SYLLABLE HOEL
+D695;D695;1112 116C 11B0;D695;1112 116C 11B0; # (횕; 횕; 횕; 횕; 횕; ) HANGUL SYLLABLE HOELG
+D696;D696;1112 116C 11B1;D696;1112 116C 11B1; # (횖; 횖; 횖; 횖; 횖; ) HANGUL SYLLABLE HOELM
+D697;D697;1112 116C 11B2;D697;1112 116C 11B2; # (횗; 횗; 횗; 횗; 횗; ) HANGUL SYLLABLE HOELB
+D698;D698;1112 116C 11B3;D698;1112 116C 11B3; # (횘; 횘; 횘; 횘; 횘; ) HANGUL SYLLABLE HOELS
+D699;D699;1112 116C 11B4;D699;1112 116C 11B4; # (횙; 횙; 횙; 횙; 횙; ) HANGUL SYLLABLE HOELT
+D69A;D69A;1112 116C 11B5;D69A;1112 116C 11B5; # (횚; 횚; 횚; 횚; 횚; ) HANGUL SYLLABLE HOELP
+D69B;D69B;1112 116C 11B6;D69B;1112 116C 11B6; # (횛; 횛; 횛; 횛; 횛; ) HANGUL SYLLABLE HOELH
+D69C;D69C;1112 116C 11B7;D69C;1112 116C 11B7; # (횜; 횜; 횜; 횜; 횜; ) HANGUL SYLLABLE HOEM
+D69D;D69D;1112 116C 11B8;D69D;1112 116C 11B8; # (íš; íš; 횝; íš; 횝; ) HANGUL SYLLABLE HOEB
+D69E;D69E;1112 116C 11B9;D69E;1112 116C 11B9; # (횞; 횞; 횞; 횞; 횞; ) HANGUL SYLLABLE HOEBS
+D69F;D69F;1112 116C 11BA;D69F;1112 116C 11BA; # (횟; 횟; 횟; 횟; 횟; ) HANGUL SYLLABLE HOES
+D6A0;D6A0;1112 116C 11BB;D6A0;1112 116C 11BB; # (횠; 횠; 횠; 횠; 횠; ) HANGUL SYLLABLE HOESS
+D6A1;D6A1;1112 116C 11BC;D6A1;1112 116C 11BC; # (횡; 횡; 횡; 횡; 횡; ) HANGUL SYLLABLE HOENG
+D6A2;D6A2;1112 116C 11BD;D6A2;1112 116C 11BD; # (횢; 횢; 횢; 횢; 횢; ) HANGUL SYLLABLE HOEJ
+D6A3;D6A3;1112 116C 11BE;D6A3;1112 116C 11BE; # (횣; 횣; 횣; 횣; 횣; ) HANGUL SYLLABLE HOEC
+D6A4;D6A4;1112 116C 11BF;D6A4;1112 116C 11BF; # (횤; 횤; 횤; 횤; 횤; ) HANGUL SYLLABLE HOEK
+D6A5;D6A5;1112 116C 11C0;D6A5;1112 116C 11C0; # (횥; 횥; 횥; 횥; 횥; ) HANGUL SYLLABLE HOET
+D6A6;D6A6;1112 116C 11C1;D6A6;1112 116C 11C1; # (횦; 횦; á„’á…¬á‡; 횦; á„’á…¬á‡; ) HANGUL SYLLABLE HOEP
+D6A7;D6A7;1112 116C 11C2;D6A7;1112 116C 11C2; # (횧; 횧; 횧; 횧; 횧; ) HANGUL SYLLABLE HOEH
+D6A8;D6A8;1112 116D;D6A8;1112 116D; # (효; 효; 효; 효; 효; ) HANGUL SYLLABLE HYO
+D6A9;D6A9;1112 116D 11A8;D6A9;1112 116D 11A8; # (횩; 횩; 횩; 횩; 횩; ) HANGUL SYLLABLE HYOG
+D6AA;D6AA;1112 116D 11A9;D6AA;1112 116D 11A9; # (횪; 횪; 횪; 횪; 횪; ) HANGUL SYLLABLE HYOGG
+D6AB;D6AB;1112 116D 11AA;D6AB;1112 116D 11AA; # (횫; 횫; 횫; 횫; 횫; ) HANGUL SYLLABLE HYOGS
+D6AC;D6AC;1112 116D 11AB;D6AC;1112 116D 11AB; # (횬; 횬; 횬; 횬; 횬; ) HANGUL SYLLABLE HYON
+D6AD;D6AD;1112 116D 11AC;D6AD;1112 116D 11AC; # (횭; 횭; 횭; 횭; 횭; ) HANGUL SYLLABLE HYONJ
+D6AE;D6AE;1112 116D 11AD;D6AE;1112 116D 11AD; # (횮; 횮; 횮; 횮; 횮; ) HANGUL SYLLABLE HYONH
+D6AF;D6AF;1112 116D 11AE;D6AF;1112 116D 11AE; # (횯; 횯; 횯; 횯; 횯; ) HANGUL SYLLABLE HYOD
+D6B0;D6B0;1112 116D 11AF;D6B0;1112 116D 11AF; # (횰; 횰; 횰; 횰; 횰; ) HANGUL SYLLABLE HYOL
+D6B1;D6B1;1112 116D 11B0;D6B1;1112 116D 11B0; # (횱; 횱; 횱; 횱; 횱; ) HANGUL SYLLABLE HYOLG
+D6B2;D6B2;1112 116D 11B1;D6B2;1112 116D 11B1; # (횲; 횲; 횲; 횲; 횲; ) HANGUL SYLLABLE HYOLM
+D6B3;D6B3;1112 116D 11B2;D6B3;1112 116D 11B2; # (횳; 횳; 횳; 횳; 횳; ) HANGUL SYLLABLE HYOLB
+D6B4;D6B4;1112 116D 11B3;D6B4;1112 116D 11B3; # (횴; 횴; 횴; 횴; 횴; ) HANGUL SYLLABLE HYOLS
+D6B5;D6B5;1112 116D 11B4;D6B5;1112 116D 11B4; # (횵; 횵; 횵; 횵; 횵; ) HANGUL SYLLABLE HYOLT
+D6B6;D6B6;1112 116D 11B5;D6B6;1112 116D 11B5; # (횶; 횶; 횶; 횶; 횶; ) HANGUL SYLLABLE HYOLP
+D6B7;D6B7;1112 116D 11B6;D6B7;1112 116D 11B6; # (횷; 횷; 횷; 횷; 횷; ) HANGUL SYLLABLE HYOLH
+D6B8;D6B8;1112 116D 11B7;D6B8;1112 116D 11B7; # (횸; 횸; 횸; 횸; 횸; ) HANGUL SYLLABLE HYOM
+D6B9;D6B9;1112 116D 11B8;D6B9;1112 116D 11B8; # (횹; 횹; 횹; 횹; 횹; ) HANGUL SYLLABLE HYOB
+D6BA;D6BA;1112 116D 11B9;D6BA;1112 116D 11B9; # (횺; 횺; 횺; 횺; 횺; ) HANGUL SYLLABLE HYOBS
+D6BB;D6BB;1112 116D 11BA;D6BB;1112 116D 11BA; # (횻; 횻; 횻; 횻; 횻; ) HANGUL SYLLABLE HYOS
+D6BC;D6BC;1112 116D 11BB;D6BC;1112 116D 11BB; # (횼; 횼; 횼; 횼; 횼; ) HANGUL SYLLABLE HYOSS
+D6BD;D6BD;1112 116D 11BC;D6BD;1112 116D 11BC; # (횽; 횽; 횽; 횽; 횽; ) HANGUL SYLLABLE HYONG
+D6BE;D6BE;1112 116D 11BD;D6BE;1112 116D 11BD; # (횾; 횾; 횾; 횾; 횾; ) HANGUL SYLLABLE HYOJ
+D6BF;D6BF;1112 116D 11BE;D6BF;1112 116D 11BE; # (횿; 횿; 횿; 횿; 횿; ) HANGUL SYLLABLE HYOC
+D6C0;D6C0;1112 116D 11BF;D6C0;1112 116D 11BF; # (훀; 훀; 훀; 훀; 훀; ) HANGUL SYLLABLE HYOK
+D6C1;D6C1;1112 116D 11C0;D6C1;1112 116D 11C0; # (í›; í›; 훁; í›; 훁; ) HANGUL SYLLABLE HYOT
+D6C2;D6C2;1112 116D 11C1;D6C2;1112 116D 11C1; # (훂; 훂; á„’á…­á‡; 훂; á„’á…­á‡; ) HANGUL SYLLABLE HYOP
+D6C3;D6C3;1112 116D 11C2;D6C3;1112 116D 11C2; # (훃; 훃; 훃; 훃; 훃; ) HANGUL SYLLABLE HYOH
+D6C4;D6C4;1112 116E;D6C4;1112 116E; # (후; 후; 후; 후; 후; ) HANGUL SYLLABLE HU
+D6C5;D6C5;1112 116E 11A8;D6C5;1112 116E 11A8; # (훅; 훅; 훅; 훅; 훅; ) HANGUL SYLLABLE HUG
+D6C6;D6C6;1112 116E 11A9;D6C6;1112 116E 11A9; # (훆; 훆; 훆; 훆; 훆; ) HANGUL SYLLABLE HUGG
+D6C7;D6C7;1112 116E 11AA;D6C7;1112 116E 11AA; # (훇; 훇; 훇; 훇; 훇; ) HANGUL SYLLABLE HUGS
+D6C8;D6C8;1112 116E 11AB;D6C8;1112 116E 11AB; # (훈; 훈; 훈; 훈; 훈; ) HANGUL SYLLABLE HUN
+D6C9;D6C9;1112 116E 11AC;D6C9;1112 116E 11AC; # (훉; 훉; 훉; 훉; 훉; ) HANGUL SYLLABLE HUNJ
+D6CA;D6CA;1112 116E 11AD;D6CA;1112 116E 11AD; # (훊; 훊; 훊; 훊; 훊; ) HANGUL SYLLABLE HUNH
+D6CB;D6CB;1112 116E 11AE;D6CB;1112 116E 11AE; # (훋; 훋; 훋; 훋; 훋; ) HANGUL SYLLABLE HUD
+D6CC;D6CC;1112 116E 11AF;D6CC;1112 116E 11AF; # (훌; 훌; 훌; 훌; 훌; ) HANGUL SYLLABLE HUL
+D6CD;D6CD;1112 116E 11B0;D6CD;1112 116E 11B0; # (í›; í›; 훍; í›; 훍; ) HANGUL SYLLABLE HULG
+D6CE;D6CE;1112 116E 11B1;D6CE;1112 116E 11B1; # (훎; 훎; 훎; 훎; 훎; ) HANGUL SYLLABLE HULM
+D6CF;D6CF;1112 116E 11B2;D6CF;1112 116E 11B2; # (í›; í›; 훏; í›; 훏; ) HANGUL SYLLABLE HULB
+D6D0;D6D0;1112 116E 11B3;D6D0;1112 116E 11B3; # (í›; í›; 훐; í›; 훐; ) HANGUL SYLLABLE HULS
+D6D1;D6D1;1112 116E 11B4;D6D1;1112 116E 11B4; # (훑; 훑; 훑; 훑; 훑; ) HANGUL SYLLABLE HULT
+D6D2;D6D2;1112 116E 11B5;D6D2;1112 116E 11B5; # (훒; 훒; 훒; 훒; 훒; ) HANGUL SYLLABLE HULP
+D6D3;D6D3;1112 116E 11B6;D6D3;1112 116E 11B6; # (훓; 훓; 훓; 훓; 훓; ) HANGUL SYLLABLE HULH
+D6D4;D6D4;1112 116E 11B7;D6D4;1112 116E 11B7; # (훔; 훔; 훔; 훔; 훔; ) HANGUL SYLLABLE HUM
+D6D5;D6D5;1112 116E 11B8;D6D5;1112 116E 11B8; # (훕; 훕; 훕; 훕; 훕; ) HANGUL SYLLABLE HUB
+D6D6;D6D6;1112 116E 11B9;D6D6;1112 116E 11B9; # (훖; 훖; 훖; 훖; 훖; ) HANGUL SYLLABLE HUBS
+D6D7;D6D7;1112 116E 11BA;D6D7;1112 116E 11BA; # (훗; 훗; 훗; 훗; 훗; ) HANGUL SYLLABLE HUS
+D6D8;D6D8;1112 116E 11BB;D6D8;1112 116E 11BB; # (훘; 훘; 훘; 훘; 훘; ) HANGUL SYLLABLE HUSS
+D6D9;D6D9;1112 116E 11BC;D6D9;1112 116E 11BC; # (훙; 훙; 훙; 훙; 훙; ) HANGUL SYLLABLE HUNG
+D6DA;D6DA;1112 116E 11BD;D6DA;1112 116E 11BD; # (훚; 훚; 훚; 훚; 훚; ) HANGUL SYLLABLE HUJ
+D6DB;D6DB;1112 116E 11BE;D6DB;1112 116E 11BE; # (훛; 훛; 훛; 훛; 훛; ) HANGUL SYLLABLE HUC
+D6DC;D6DC;1112 116E 11BF;D6DC;1112 116E 11BF; # (훜; 훜; 훜; 훜; 훜; ) HANGUL SYLLABLE HUK
+D6DD;D6DD;1112 116E 11C0;D6DD;1112 116E 11C0; # (í›; í›; 훝; í›; 훝; ) HANGUL SYLLABLE HUT
+D6DE;D6DE;1112 116E 11C1;D6DE;1112 116E 11C1; # (훞; 훞; á„’á…®á‡; 훞; á„’á…®á‡; ) HANGUL SYLLABLE HUP
+D6DF;D6DF;1112 116E 11C2;D6DF;1112 116E 11C2; # (훟; 훟; 훟; 훟; 훟; ) HANGUL SYLLABLE HUH
+D6E0;D6E0;1112 116F;D6E0;1112 116F; # (í› ; í› ; á„’á…¯; í› ; á„’á…¯; ) HANGUL SYLLABLE HWEO
+D6E1;D6E1;1112 116F 11A8;D6E1;1112 116F 11A8; # (훡; 훡; 훡; 훡; 훡; ) HANGUL SYLLABLE HWEOG
+D6E2;D6E2;1112 116F 11A9;D6E2;1112 116F 11A9; # (훢; 훢; 훢; 훢; 훢; ) HANGUL SYLLABLE HWEOGG
+D6E3;D6E3;1112 116F 11AA;D6E3;1112 116F 11AA; # (훣; 훣; 훣; 훣; 훣; ) HANGUL SYLLABLE HWEOGS
+D6E4;D6E4;1112 116F 11AB;D6E4;1112 116F 11AB; # (훤; 훤; 훤; 훤; 훤; ) HANGUL SYLLABLE HWEON
+D6E5;D6E5;1112 116F 11AC;D6E5;1112 116F 11AC; # (훥; 훥; 훥; 훥; 훥; ) HANGUL SYLLABLE HWEONJ
+D6E6;D6E6;1112 116F 11AD;D6E6;1112 116F 11AD; # (훦; 훦; 훦; 훦; 훦; ) HANGUL SYLLABLE HWEONH
+D6E7;D6E7;1112 116F 11AE;D6E7;1112 116F 11AE; # (훧; 훧; 훧; 훧; 훧; ) HANGUL SYLLABLE HWEOD
+D6E8;D6E8;1112 116F 11AF;D6E8;1112 116F 11AF; # (훨; 훨; 훨; 훨; 훨; ) HANGUL SYLLABLE HWEOL
+D6E9;D6E9;1112 116F 11B0;D6E9;1112 116F 11B0; # (훩; 훩; 훩; 훩; 훩; ) HANGUL SYLLABLE HWEOLG
+D6EA;D6EA;1112 116F 11B1;D6EA;1112 116F 11B1; # (훪; 훪; 훪; 훪; 훪; ) HANGUL SYLLABLE HWEOLM
+D6EB;D6EB;1112 116F 11B2;D6EB;1112 116F 11B2; # (훫; 훫; 훫; 훫; 훫; ) HANGUL SYLLABLE HWEOLB
+D6EC;D6EC;1112 116F 11B3;D6EC;1112 116F 11B3; # (훬; 훬; 훬; 훬; 훬; ) HANGUL SYLLABLE HWEOLS
+D6ED;D6ED;1112 116F 11B4;D6ED;1112 116F 11B4; # (훭; 훭; 훭; 훭; 훭; ) HANGUL SYLLABLE HWEOLT
+D6EE;D6EE;1112 116F 11B5;D6EE;1112 116F 11B5; # (훮; 훮; 훮; 훮; 훮; ) HANGUL SYLLABLE HWEOLP
+D6EF;D6EF;1112 116F 11B6;D6EF;1112 116F 11B6; # (훯; 훯; 훯; 훯; 훯; ) HANGUL SYLLABLE HWEOLH
+D6F0;D6F0;1112 116F 11B7;D6F0;1112 116F 11B7; # (훰; 훰; 훰; 훰; 훰; ) HANGUL SYLLABLE HWEOM
+D6F1;D6F1;1112 116F 11B8;D6F1;1112 116F 11B8; # (훱; 훱; 훱; 훱; 훱; ) HANGUL SYLLABLE HWEOB
+D6F2;D6F2;1112 116F 11B9;D6F2;1112 116F 11B9; # (훲; 훲; 훲; 훲; 훲; ) HANGUL SYLLABLE HWEOBS
+D6F3;D6F3;1112 116F 11BA;D6F3;1112 116F 11BA; # (훳; 훳; 훳; 훳; 훳; ) HANGUL SYLLABLE HWEOS
+D6F4;D6F4;1112 116F 11BB;D6F4;1112 116F 11BB; # (훴; 훴; 훴; 훴; 훴; ) HANGUL SYLLABLE HWEOSS
+D6F5;D6F5;1112 116F 11BC;D6F5;1112 116F 11BC; # (훵; 훵; 훵; 훵; 훵; ) HANGUL SYLLABLE HWEONG
+D6F6;D6F6;1112 116F 11BD;D6F6;1112 116F 11BD; # (훶; 훶; 훶; 훶; 훶; ) HANGUL SYLLABLE HWEOJ
+D6F7;D6F7;1112 116F 11BE;D6F7;1112 116F 11BE; # (훷; 훷; 훷; 훷; 훷; ) HANGUL SYLLABLE HWEOC
+D6F8;D6F8;1112 116F 11BF;D6F8;1112 116F 11BF; # (훸; 훸; 훸; 훸; 훸; ) HANGUL SYLLABLE HWEOK
+D6F9;D6F9;1112 116F 11C0;D6F9;1112 116F 11C0; # (훹; 훹; 훹; 훹; 훹; ) HANGUL SYLLABLE HWEOT
+D6FA;D6FA;1112 116F 11C1;D6FA;1112 116F 11C1; # (훺; 훺; á„’á…¯á‡; 훺; á„’á…¯á‡; ) HANGUL SYLLABLE HWEOP
+D6FB;D6FB;1112 116F 11C2;D6FB;1112 116F 11C2; # (훻; 훻; 훻; 훻; 훻; ) HANGUL SYLLABLE HWEOH
+D6FC;D6FC;1112 1170;D6FC;1112 1170; # (훼; 훼; 훼; 훼; 훼; ) HANGUL SYLLABLE HWE
+D6FD;D6FD;1112 1170 11A8;D6FD;1112 1170 11A8; # (훽; 훽; 훽; 훽; 훽; ) HANGUL SYLLABLE HWEG
+D6FE;D6FE;1112 1170 11A9;D6FE;1112 1170 11A9; # (훾; 훾; 훾; 훾; 훾; ) HANGUL SYLLABLE HWEGG
+D6FF;D6FF;1112 1170 11AA;D6FF;1112 1170 11AA; # (훿; 훿; 훿; 훿; 훿; ) HANGUL SYLLABLE HWEGS
+D700;D700;1112 1170 11AB;D700;1112 1170 11AB; # (휀; 휀; 휀; 휀; 휀; ) HANGUL SYLLABLE HWEN
+D701;D701;1112 1170 11AC;D701;1112 1170 11AC; # (íœ; íœ; 휁; íœ; 휁; ) HANGUL SYLLABLE HWENJ
+D702;D702;1112 1170 11AD;D702;1112 1170 11AD; # (휂; 휂; 휂; 휂; 휂; ) HANGUL SYLLABLE HWENH
+D703;D703;1112 1170 11AE;D703;1112 1170 11AE; # (휃; 휃; 휃; 휃; 휃; ) HANGUL SYLLABLE HWED
+D704;D704;1112 1170 11AF;D704;1112 1170 11AF; # (휄; 휄; 휄; 휄; 휄; ) HANGUL SYLLABLE HWEL
+D705;D705;1112 1170 11B0;D705;1112 1170 11B0; # (휅; 휅; 휅; 휅; 휅; ) HANGUL SYLLABLE HWELG
+D706;D706;1112 1170 11B1;D706;1112 1170 11B1; # (휆; 휆; 휆; 휆; 휆; ) HANGUL SYLLABLE HWELM
+D707;D707;1112 1170 11B2;D707;1112 1170 11B2; # (휇; 휇; 휇; 휇; 휇; ) HANGUL SYLLABLE HWELB
+D708;D708;1112 1170 11B3;D708;1112 1170 11B3; # (휈; 휈; 휈; 휈; 휈; ) HANGUL SYLLABLE HWELS
+D709;D709;1112 1170 11B4;D709;1112 1170 11B4; # (휉; 휉; 휉; 휉; 휉; ) HANGUL SYLLABLE HWELT
+D70A;D70A;1112 1170 11B5;D70A;1112 1170 11B5; # (휊; 휊; 휊; 휊; 휊; ) HANGUL SYLLABLE HWELP
+D70B;D70B;1112 1170 11B6;D70B;1112 1170 11B6; # (휋; 휋; 휋; 휋; 휋; ) HANGUL SYLLABLE HWELH
+D70C;D70C;1112 1170 11B7;D70C;1112 1170 11B7; # (휌; 휌; 휌; 휌; 휌; ) HANGUL SYLLABLE HWEM
+D70D;D70D;1112 1170 11B8;D70D;1112 1170 11B8; # (íœ; íœ; 휍; íœ; 휍; ) HANGUL SYLLABLE HWEB
+D70E;D70E;1112 1170 11B9;D70E;1112 1170 11B9; # (휎; 휎; 휎; 휎; 휎; ) HANGUL SYLLABLE HWEBS
+D70F;D70F;1112 1170 11BA;D70F;1112 1170 11BA; # (íœ; íœ; 휏; íœ; 휏; ) HANGUL SYLLABLE HWES
+D710;D710;1112 1170 11BB;D710;1112 1170 11BB; # (íœ; íœ; 휐; íœ; 휐; ) HANGUL SYLLABLE HWESS
+D711;D711;1112 1170 11BC;D711;1112 1170 11BC; # (휑; 휑; 휑; 휑; 휑; ) HANGUL SYLLABLE HWENG
+D712;D712;1112 1170 11BD;D712;1112 1170 11BD; # (휒; 휒; 휒; 휒; 휒; ) HANGUL SYLLABLE HWEJ
+D713;D713;1112 1170 11BE;D713;1112 1170 11BE; # (휓; 휓; 휓; 휓; 휓; ) HANGUL SYLLABLE HWEC
+D714;D714;1112 1170 11BF;D714;1112 1170 11BF; # (휔; 휔; 휔; 휔; 휔; ) HANGUL SYLLABLE HWEK
+D715;D715;1112 1170 11C0;D715;1112 1170 11C0; # (휕; 휕; 휕; 휕; 휕; ) HANGUL SYLLABLE HWET
+D716;D716;1112 1170 11C1;D716;1112 1170 11C1; # (휖; 휖; á„’á…°á‡; 휖; á„’á…°á‡; ) HANGUL SYLLABLE HWEP
+D717;D717;1112 1170 11C2;D717;1112 1170 11C2; # (휗; 휗; 휗; 휗; 휗; ) HANGUL SYLLABLE HWEH
+D718;D718;1112 1171;D718;1112 1171; # (휘; 휘; 휘; 휘; 휘; ) HANGUL SYLLABLE HWI
+D719;D719;1112 1171 11A8;D719;1112 1171 11A8; # (휙; 휙; 휙; 휙; 휙; ) HANGUL SYLLABLE HWIG
+D71A;D71A;1112 1171 11A9;D71A;1112 1171 11A9; # (휚; 휚; 휚; 휚; 휚; ) HANGUL SYLLABLE HWIGG
+D71B;D71B;1112 1171 11AA;D71B;1112 1171 11AA; # (휛; 휛; 휛; 휛; 휛; ) HANGUL SYLLABLE HWIGS
+D71C;D71C;1112 1171 11AB;D71C;1112 1171 11AB; # (휜; 휜; 휜; 휜; 휜; ) HANGUL SYLLABLE HWIN
+D71D;D71D;1112 1171 11AC;D71D;1112 1171 11AC; # (íœ; íœ; 휝; íœ; 휝; ) HANGUL SYLLABLE HWINJ
+D71E;D71E;1112 1171 11AD;D71E;1112 1171 11AD; # (휞; 휞; 휞; 휞; 휞; ) HANGUL SYLLABLE HWINH
+D71F;D71F;1112 1171 11AE;D71F;1112 1171 11AE; # (휟; 휟; 휟; 휟; 휟; ) HANGUL SYLLABLE HWID
+D720;D720;1112 1171 11AF;D720;1112 1171 11AF; # (휠; 휠; 휠; 휠; 휠; ) HANGUL SYLLABLE HWIL
+D721;D721;1112 1171 11B0;D721;1112 1171 11B0; # (휡; 휡; 휡; 휡; 휡; ) HANGUL SYLLABLE HWILG
+D722;D722;1112 1171 11B1;D722;1112 1171 11B1; # (휢; 휢; 휢; 휢; 휢; ) HANGUL SYLLABLE HWILM
+D723;D723;1112 1171 11B2;D723;1112 1171 11B2; # (휣; 휣; 휣; 휣; 휣; ) HANGUL SYLLABLE HWILB
+D724;D724;1112 1171 11B3;D724;1112 1171 11B3; # (휤; 휤; 휤; 휤; 휤; ) HANGUL SYLLABLE HWILS
+D725;D725;1112 1171 11B4;D725;1112 1171 11B4; # (휥; 휥; 휥; 휥; 휥; ) HANGUL SYLLABLE HWILT
+D726;D726;1112 1171 11B5;D726;1112 1171 11B5; # (휦; 휦; 휦; 휦; 휦; ) HANGUL SYLLABLE HWILP
+D727;D727;1112 1171 11B6;D727;1112 1171 11B6; # (휧; 휧; 휧; 휧; 휧; ) HANGUL SYLLABLE HWILH
+D728;D728;1112 1171 11B7;D728;1112 1171 11B7; # (휨; 휨; 휨; 휨; 휨; ) HANGUL SYLLABLE HWIM
+D729;D729;1112 1171 11B8;D729;1112 1171 11B8; # (휩; 휩; 휩; 휩; 휩; ) HANGUL SYLLABLE HWIB
+D72A;D72A;1112 1171 11B9;D72A;1112 1171 11B9; # (휪; 휪; 휪; 휪; 휪; ) HANGUL SYLLABLE HWIBS
+D72B;D72B;1112 1171 11BA;D72B;1112 1171 11BA; # (휫; 휫; 휫; 휫; 휫; ) HANGUL SYLLABLE HWIS
+D72C;D72C;1112 1171 11BB;D72C;1112 1171 11BB; # (휬; 휬; 휬; 휬; 휬; ) HANGUL SYLLABLE HWISS
+D72D;D72D;1112 1171 11BC;D72D;1112 1171 11BC; # (휭; 휭; 휭; 휭; 휭; ) HANGUL SYLLABLE HWING
+D72E;D72E;1112 1171 11BD;D72E;1112 1171 11BD; # (휮; 휮; 휮; 휮; 휮; ) HANGUL SYLLABLE HWIJ
+D72F;D72F;1112 1171 11BE;D72F;1112 1171 11BE; # (휯; 휯; 휯; 휯; 휯; ) HANGUL SYLLABLE HWIC
+D730;D730;1112 1171 11BF;D730;1112 1171 11BF; # (휰; 휰; 휰; 휰; 휰; ) HANGUL SYLLABLE HWIK
+D731;D731;1112 1171 11C0;D731;1112 1171 11C0; # (휱; 휱; 휱; 휱; 휱; ) HANGUL SYLLABLE HWIT
+D732;D732;1112 1171 11C1;D732;1112 1171 11C1; # (휲; 휲; á„’á…±á‡; 휲; á„’á…±á‡; ) HANGUL SYLLABLE HWIP
+D733;D733;1112 1171 11C2;D733;1112 1171 11C2; # (휳; 휳; 휳; 휳; 휳; ) HANGUL SYLLABLE HWIH
+D734;D734;1112 1172;D734;1112 1172; # (휴; 휴; 휴; 휴; 휴; ) HANGUL SYLLABLE HYU
+D735;D735;1112 1172 11A8;D735;1112 1172 11A8; # (휵; 휵; 휵; 휵; 휵; ) HANGUL SYLLABLE HYUG
+D736;D736;1112 1172 11A9;D736;1112 1172 11A9; # (휶; 휶; 휶; 휶; 휶; ) HANGUL SYLLABLE HYUGG
+D737;D737;1112 1172 11AA;D737;1112 1172 11AA; # (휷; 휷; 휷; 휷; 휷; ) HANGUL SYLLABLE HYUGS
+D738;D738;1112 1172 11AB;D738;1112 1172 11AB; # (휸; 휸; 휸; 휸; 휸; ) HANGUL SYLLABLE HYUN
+D739;D739;1112 1172 11AC;D739;1112 1172 11AC; # (휹; 휹; 휹; 휹; 휹; ) HANGUL SYLLABLE HYUNJ
+D73A;D73A;1112 1172 11AD;D73A;1112 1172 11AD; # (휺; 휺; 휺; 휺; 휺; ) HANGUL SYLLABLE HYUNH
+D73B;D73B;1112 1172 11AE;D73B;1112 1172 11AE; # (휻; 휻; 휻; 휻; 휻; ) HANGUL SYLLABLE HYUD
+D73C;D73C;1112 1172 11AF;D73C;1112 1172 11AF; # (휼; 휼; 휼; 휼; 휼; ) HANGUL SYLLABLE HYUL
+D73D;D73D;1112 1172 11B0;D73D;1112 1172 11B0; # (휽; 휽; 휽; 휽; 휽; ) HANGUL SYLLABLE HYULG
+D73E;D73E;1112 1172 11B1;D73E;1112 1172 11B1; # (휾; 휾; 휾; 휾; 휾; ) HANGUL SYLLABLE HYULM
+D73F;D73F;1112 1172 11B2;D73F;1112 1172 11B2; # (휿; 휿; 휿; 휿; 휿; ) HANGUL SYLLABLE HYULB
+D740;D740;1112 1172 11B3;D740;1112 1172 11B3; # (í€; í€; 흀; í€; 흀; ) HANGUL SYLLABLE HYULS
+D741;D741;1112 1172 11B4;D741;1112 1172 11B4; # (í; í; 흁; í; 흁; ) HANGUL SYLLABLE HYULT
+D742;D742;1112 1172 11B5;D742;1112 1172 11B5; # (í‚; í‚; 흂; í‚; 흂; ) HANGUL SYLLABLE HYULP
+D743;D743;1112 1172 11B6;D743;1112 1172 11B6; # (íƒ; íƒ; 흃; íƒ; 흃; ) HANGUL SYLLABLE HYULH
+D744;D744;1112 1172 11B7;D744;1112 1172 11B7; # (í„; í„; 흄; í„; 흄; ) HANGUL SYLLABLE HYUM
+D745;D745;1112 1172 11B8;D745;1112 1172 11B8; # (í…; í…; 흅; í…; 흅; ) HANGUL SYLLABLE HYUB
+D746;D746;1112 1172 11B9;D746;1112 1172 11B9; # (í†; í†; 흆; í†; 흆; ) HANGUL SYLLABLE HYUBS
+D747;D747;1112 1172 11BA;D747;1112 1172 11BA; # (í‡; í‡; 흇; í‡; 흇; ) HANGUL SYLLABLE HYUS
+D748;D748;1112 1172 11BB;D748;1112 1172 11BB; # (íˆ; íˆ; 흈; íˆ; 흈; ) HANGUL SYLLABLE HYUSS
+D749;D749;1112 1172 11BC;D749;1112 1172 11BC; # (í‰; í‰; 흉; í‰; 흉; ) HANGUL SYLLABLE HYUNG
+D74A;D74A;1112 1172 11BD;D74A;1112 1172 11BD; # (íŠ; íŠ; 흊; íŠ; 흊; ) HANGUL SYLLABLE HYUJ
+D74B;D74B;1112 1172 11BE;D74B;1112 1172 11BE; # (í‹; í‹; 흋; í‹; 흋; ) HANGUL SYLLABLE HYUC
+D74C;D74C;1112 1172 11BF;D74C;1112 1172 11BF; # (íŒ; íŒ; 흌; íŒ; 흌; ) HANGUL SYLLABLE HYUK
+D74D;D74D;1112 1172 11C0;D74D;1112 1172 11C0; # (í; í; 흍; í; 흍; ) HANGUL SYLLABLE HYUT
+D74E;D74E;1112 1172 11C1;D74E;1112 1172 11C1; # (íŽ; íŽ; á„’á…²á‡; íŽ; á„’á…²á‡; ) HANGUL SYLLABLE HYUP
+D74F;D74F;1112 1172 11C2;D74F;1112 1172 11C2; # (í; í; 흏; í; 흏; ) HANGUL SYLLABLE HYUH
+D750;D750;1112 1173;D750;1112 1173; # (í; í; á„’á…³; í; á„’á…³; ) HANGUL SYLLABLE HEU
+D751;D751;1112 1173 11A8;D751;1112 1173 11A8; # (í‘; í‘; 흑; í‘; 흑; ) HANGUL SYLLABLE HEUG
+D752;D752;1112 1173 11A9;D752;1112 1173 11A9; # (í’; í’; 흒; í’; 흒; ) HANGUL SYLLABLE HEUGG
+D753;D753;1112 1173 11AA;D753;1112 1173 11AA; # (í“; í“; 흓; í“; 흓; ) HANGUL SYLLABLE HEUGS
+D754;D754;1112 1173 11AB;D754;1112 1173 11AB; # (í”; í”; 흔; í”; 흔; ) HANGUL SYLLABLE HEUN
+D755;D755;1112 1173 11AC;D755;1112 1173 11AC; # (í•; í•; 흕; í•; 흕; ) HANGUL SYLLABLE HEUNJ
+D756;D756;1112 1173 11AD;D756;1112 1173 11AD; # (í–; í–; 흖; í–; 흖; ) HANGUL SYLLABLE HEUNH
+D757;D757;1112 1173 11AE;D757;1112 1173 11AE; # (í—; í—; 흗; í—; 흗; ) HANGUL SYLLABLE HEUD
+D758;D758;1112 1173 11AF;D758;1112 1173 11AF; # (í˜; í˜; 흘; í˜; 흘; ) HANGUL SYLLABLE HEUL
+D759;D759;1112 1173 11B0;D759;1112 1173 11B0; # (í™; í™; 흙; í™; 흙; ) HANGUL SYLLABLE HEULG
+D75A;D75A;1112 1173 11B1;D75A;1112 1173 11B1; # (íš; íš; 흚; íš; 흚; ) HANGUL SYLLABLE HEULM
+D75B;D75B;1112 1173 11B2;D75B;1112 1173 11B2; # (í›; í›; 흛; í›; 흛; ) HANGUL SYLLABLE HEULB
+D75C;D75C;1112 1173 11B3;D75C;1112 1173 11B3; # (íœ; íœ; 흜; íœ; 흜; ) HANGUL SYLLABLE HEULS
+D75D;D75D;1112 1173 11B4;D75D;1112 1173 11B4; # (í; í; 흝; í; 흝; ) HANGUL SYLLABLE HEULT
+D75E;D75E;1112 1173 11B5;D75E;1112 1173 11B5; # (íž; íž; 흞; íž; 흞; ) HANGUL SYLLABLE HEULP
+D75F;D75F;1112 1173 11B6;D75F;1112 1173 11B6; # (íŸ; íŸ; 흟; íŸ; 흟; ) HANGUL SYLLABLE HEULH
+D760;D760;1112 1173 11B7;D760;1112 1173 11B7; # (í ; í ; 흠; í ; 흠; ) HANGUL SYLLABLE HEUM
+D761;D761;1112 1173 11B8;D761;1112 1173 11B8; # (í¡; í¡; 흡; í¡; 흡; ) HANGUL SYLLABLE HEUB
+D762;D762;1112 1173 11B9;D762;1112 1173 11B9; # (í¢; í¢; 흢; í¢; 흢; ) HANGUL SYLLABLE HEUBS
+D763;D763;1112 1173 11BA;D763;1112 1173 11BA; # (í£; í£; 흣; í£; 흣; ) HANGUL SYLLABLE HEUS
+D764;D764;1112 1173 11BB;D764;1112 1173 11BB; # (í¤; í¤; 흤; í¤; 흤; ) HANGUL SYLLABLE HEUSS
+D765;D765;1112 1173 11BC;D765;1112 1173 11BC; # (í¥; í¥; 흥; í¥; 흥; ) HANGUL SYLLABLE HEUNG
+D766;D766;1112 1173 11BD;D766;1112 1173 11BD; # (í¦; í¦; 흦; í¦; 흦; ) HANGUL SYLLABLE HEUJ
+D767;D767;1112 1173 11BE;D767;1112 1173 11BE; # (í§; í§; 흧; í§; 흧; ) HANGUL SYLLABLE HEUC
+D768;D768;1112 1173 11BF;D768;1112 1173 11BF; # (í¨; í¨; 흨; í¨; 흨; ) HANGUL SYLLABLE HEUK
+D769;D769;1112 1173 11C0;D769;1112 1173 11C0; # (í©; í©; 흩; í©; 흩; ) HANGUL SYLLABLE HEUT
+D76A;D76A;1112 1173 11C1;D76A;1112 1173 11C1; # (íª; íª; á„’á…³á‡; íª; á„’á…³á‡; ) HANGUL SYLLABLE HEUP
+D76B;D76B;1112 1173 11C2;D76B;1112 1173 11C2; # (í«; í«; 흫; í«; 흫; ) HANGUL SYLLABLE HEUH
+D76C;D76C;1112 1174;D76C;1112 1174; # (í¬; í¬; á„’á…´; í¬; á„’á…´; ) HANGUL SYLLABLE HYI
+D76D;D76D;1112 1174 11A8;D76D;1112 1174 11A8; # (í­; í­; 흭; í­; 흭; ) HANGUL SYLLABLE HYIG
+D76E;D76E;1112 1174 11A9;D76E;1112 1174 11A9; # (í®; í®; 흮; í®; 흮; ) HANGUL SYLLABLE HYIGG
+D76F;D76F;1112 1174 11AA;D76F;1112 1174 11AA; # (í¯; í¯; 흯; í¯; 흯; ) HANGUL SYLLABLE HYIGS
+D770;D770;1112 1174 11AB;D770;1112 1174 11AB; # (í°; í°; 흰; í°; 흰; ) HANGUL SYLLABLE HYIN
+D771;D771;1112 1174 11AC;D771;1112 1174 11AC; # (í±; í±; 흱; í±; 흱; ) HANGUL SYLLABLE HYINJ
+D772;D772;1112 1174 11AD;D772;1112 1174 11AD; # (í²; í²; 흲; í²; 흲; ) HANGUL SYLLABLE HYINH
+D773;D773;1112 1174 11AE;D773;1112 1174 11AE; # (í³; í³; 흳; í³; 흳; ) HANGUL SYLLABLE HYID
+D774;D774;1112 1174 11AF;D774;1112 1174 11AF; # (í´; í´; 흴; í´; 흴; ) HANGUL SYLLABLE HYIL
+D775;D775;1112 1174 11B0;D775;1112 1174 11B0; # (íµ; íµ; 흵; íµ; 흵; ) HANGUL SYLLABLE HYILG
+D776;D776;1112 1174 11B1;D776;1112 1174 11B1; # (í¶; í¶; 흶; í¶; 흶; ) HANGUL SYLLABLE HYILM
+D777;D777;1112 1174 11B2;D777;1112 1174 11B2; # (í·; í·; 흷; í·; 흷; ) HANGUL SYLLABLE HYILB
+D778;D778;1112 1174 11B3;D778;1112 1174 11B3; # (í¸; í¸; 흸; í¸; 흸; ) HANGUL SYLLABLE HYILS
+D779;D779;1112 1174 11B4;D779;1112 1174 11B4; # (í¹; í¹; 흹; í¹; 흹; ) HANGUL SYLLABLE HYILT
+D77A;D77A;1112 1174 11B5;D77A;1112 1174 11B5; # (íº; íº; 흺; íº; 흺; ) HANGUL SYLLABLE HYILP
+D77B;D77B;1112 1174 11B6;D77B;1112 1174 11B6; # (í»; í»; 흻; í»; 흻; ) HANGUL SYLLABLE HYILH
+D77C;D77C;1112 1174 11B7;D77C;1112 1174 11B7; # (í¼; í¼; 흼; í¼; 흼; ) HANGUL SYLLABLE HYIM
+D77D;D77D;1112 1174 11B8;D77D;1112 1174 11B8; # (í½; í½; 흽; í½; 흽; ) HANGUL SYLLABLE HYIB
+D77E;D77E;1112 1174 11B9;D77E;1112 1174 11B9; # (í¾; í¾; 흾; í¾; 흾; ) HANGUL SYLLABLE HYIBS
+D77F;D77F;1112 1174 11BA;D77F;1112 1174 11BA; # (í¿; í¿; 흿; í¿; 흿; ) HANGUL SYLLABLE HYIS
+D780;D780;1112 1174 11BB;D780;1112 1174 11BB; # (힀; 힀; 힀; 힀; 힀; ) HANGUL SYLLABLE HYISS
+D781;D781;1112 1174 11BC;D781;1112 1174 11BC; # (íž; íž; 힁; íž; 힁; ) HANGUL SYLLABLE HYING
+D782;D782;1112 1174 11BD;D782;1112 1174 11BD; # (힂; 힂; 힂; 힂; 힂; ) HANGUL SYLLABLE HYIJ
+D783;D783;1112 1174 11BE;D783;1112 1174 11BE; # (힃; 힃; 힃; 힃; 힃; ) HANGUL SYLLABLE HYIC
+D784;D784;1112 1174 11BF;D784;1112 1174 11BF; # (힄; 힄; 힄; 힄; 힄; ) HANGUL SYLLABLE HYIK
+D785;D785;1112 1174 11C0;D785;1112 1174 11C0; # (힅; 힅; 힅; 힅; 힅; ) HANGUL SYLLABLE HYIT
+D786;D786;1112 1174 11C1;D786;1112 1174 11C1; # (힆; 힆; á„’á…´á‡; 힆; á„’á…´á‡; ) HANGUL SYLLABLE HYIP
+D787;D787;1112 1174 11C2;D787;1112 1174 11C2; # (힇; 힇; 힇; 힇; 힇; ) HANGUL SYLLABLE HYIH
+D788;D788;1112 1175;D788;1112 1175; # (히; 히; 히; 히; 히; ) HANGUL SYLLABLE HI
+D789;D789;1112 1175 11A8;D789;1112 1175 11A8; # (힉; 힉; 힉; 힉; 힉; ) HANGUL SYLLABLE HIG
+D78A;D78A;1112 1175 11A9;D78A;1112 1175 11A9; # (힊; 힊; 힊; 힊; 힊; ) HANGUL SYLLABLE HIGG
+D78B;D78B;1112 1175 11AA;D78B;1112 1175 11AA; # (힋; 힋; 힋; 힋; 힋; ) HANGUL SYLLABLE HIGS
+D78C;D78C;1112 1175 11AB;D78C;1112 1175 11AB; # (힌; 힌; 힌; 힌; 힌; ) HANGUL SYLLABLE HIN
+D78D;D78D;1112 1175 11AC;D78D;1112 1175 11AC; # (íž; íž; 힍; íž; 힍; ) HANGUL SYLLABLE HINJ
+D78E;D78E;1112 1175 11AD;D78E;1112 1175 11AD; # (힎; 힎; 힎; 힎; 힎; ) HANGUL SYLLABLE HINH
+D78F;D78F;1112 1175 11AE;D78F;1112 1175 11AE; # (íž; íž; 힏; íž; 힏; ) HANGUL SYLLABLE HID
+D790;D790;1112 1175 11AF;D790;1112 1175 11AF; # (íž; íž; 힐; íž; 힐; ) HANGUL SYLLABLE HIL
+D791;D791;1112 1175 11B0;D791;1112 1175 11B0; # (힑; 힑; 힑; 힑; 힑; ) HANGUL SYLLABLE HILG
+D792;D792;1112 1175 11B1;D792;1112 1175 11B1; # (힒; 힒; 힒; 힒; 힒; ) HANGUL SYLLABLE HILM
+D793;D793;1112 1175 11B2;D793;1112 1175 11B2; # (힓; 힓; 힓; 힓; 힓; ) HANGUL SYLLABLE HILB
+D794;D794;1112 1175 11B3;D794;1112 1175 11B3; # (힔; 힔; 힔; 힔; 힔; ) HANGUL SYLLABLE HILS
+D795;D795;1112 1175 11B4;D795;1112 1175 11B4; # (힕; 힕; 힕; 힕; 힕; ) HANGUL SYLLABLE HILT
+D796;D796;1112 1175 11B5;D796;1112 1175 11B5; # (힖; 힖; 힖; 힖; 힖; ) HANGUL SYLLABLE HILP
+D797;D797;1112 1175 11B6;D797;1112 1175 11B6; # (힗; 힗; 힗; 힗; 힗; ) HANGUL SYLLABLE HILH
+D798;D798;1112 1175 11B7;D798;1112 1175 11B7; # (힘; 힘; 힘; 힘; 힘; ) HANGUL SYLLABLE HIM
+D799;D799;1112 1175 11B8;D799;1112 1175 11B8; # (힙; 힙; 힙; 힙; 힙; ) HANGUL SYLLABLE HIB
+D79A;D79A;1112 1175 11B9;D79A;1112 1175 11B9; # (힚; 힚; 힚; 힚; 힚; ) HANGUL SYLLABLE HIBS
+D79B;D79B;1112 1175 11BA;D79B;1112 1175 11BA; # (힛; 힛; 힛; 힛; 힛; ) HANGUL SYLLABLE HIS
+D79C;D79C;1112 1175 11BB;D79C;1112 1175 11BB; # (힜; 힜; 힜; 힜; 힜; ) HANGUL SYLLABLE HISS
+D79D;D79D;1112 1175 11BC;D79D;1112 1175 11BC; # (íž; íž; 힝; íž; 힝; ) HANGUL SYLLABLE HING
+D79E;D79E;1112 1175 11BD;D79E;1112 1175 11BD; # (힞; 힞; 힞; 힞; 힞; ) HANGUL SYLLABLE HIJ
+D79F;D79F;1112 1175 11BE;D79F;1112 1175 11BE; # (힟; 힟; 힟; 힟; 힟; ) HANGUL SYLLABLE HIC
+D7A0;D7A0;1112 1175 11BF;D7A0;1112 1175 11BF; # (힠; 힠; 힠; 힠; 힠; ) HANGUL SYLLABLE HIK
+D7A1;D7A1;1112 1175 11C0;D7A1;1112 1175 11C0; # (힡; 힡; 힡; 힡; 힡; ) HANGUL SYLLABLE HIT
+D7A2;D7A2;1112 1175 11C1;D7A2;1112 1175 11C1; # (힢; 힢; á„’á…µá‡; 힢; á„’á…µá‡; ) HANGUL SYLLABLE HIP
+D7A3;D7A3;1112 1175 11C2;D7A3;1112 1175 11C2; # (힣; 힣; 힣; 힣; 힣; ) HANGUL SYLLABLE HIH
+F900;8C48;8C48;8C48;8C48; # (豈; 豈; 豈; 豈; 豈; ) CJK COMPATIBILITY IDEOGRAPH-F900
+F901;66F4;66F4;66F4;66F4; # (ï¤; æ›´; æ›´; æ›´; æ›´; ) CJK COMPATIBILITY IDEOGRAPH-F901
+F902;8ECA;8ECA;8ECA;8ECA; # (車; 車; 車; 車; 車; ) CJK COMPATIBILITY IDEOGRAPH-F902
+F903;8CC8;8CC8;8CC8;8CC8; # (賈; 賈; 賈; 賈; 賈; ) CJK COMPATIBILITY IDEOGRAPH-F903
+F904;6ED1;6ED1;6ED1;6ED1; # (滑; 滑; 滑; 滑; 滑; ) CJK COMPATIBILITY IDEOGRAPH-F904
+F905;4E32;4E32;4E32;4E32; # (串; 串; 串; 串; 串; ) CJK COMPATIBILITY IDEOGRAPH-F905
+F906;53E5;53E5;53E5;53E5; # (句; å¥; å¥; å¥; å¥; ) CJK COMPATIBILITY IDEOGRAPH-F906
+F907;9F9C;9F9C;9F9C;9F9C; # (龜; 龜; 龜; 龜; 龜; ) CJK COMPATIBILITY IDEOGRAPH-F907
+F908;9F9C;9F9C;9F9C;9F9C; # (龜; 龜; 龜; 龜; 龜; ) CJK COMPATIBILITY IDEOGRAPH-F908
+F909;5951;5951;5951;5951; # (契; 契; 契; 契; 契; ) CJK COMPATIBILITY IDEOGRAPH-F909
+F90A;91D1;91D1;91D1;91D1; # (金; 金; 金; 金; 金; ) CJK COMPATIBILITY IDEOGRAPH-F90A
+F90B;5587;5587;5587;5587; # (喇; 喇; 喇; 喇; 喇; ) CJK COMPATIBILITY IDEOGRAPH-F90B
+F90C;5948;5948;5948;5948; # (奈; 奈; 奈; 奈; 奈; ) CJK COMPATIBILITY IDEOGRAPH-F90C
+F90D;61F6;61F6;61F6;61F6; # (ï¤; 懶; 懶; 懶; 懶; ) CJK COMPATIBILITY IDEOGRAPH-F90D
+F90E;7669;7669;7669;7669; # (癩; 癩; 癩; 癩; 癩; ) CJK COMPATIBILITY IDEOGRAPH-F90E
+F90F;7F85;7F85;7F85;7F85; # (ï¤; ç¾…; ç¾…; ç¾…; ç¾…; ) CJK COMPATIBILITY IDEOGRAPH-F90F
+F910;863F;863F;863F;863F; # (ï¤; 蘿; 蘿; 蘿; 蘿; ) CJK COMPATIBILITY IDEOGRAPH-F910
+F911;87BA;87BA;87BA;87BA; # (螺; 螺; 螺; 螺; 螺; ) CJK COMPATIBILITY IDEOGRAPH-F911
+F912;88F8;88F8;88F8;88F8; # (裸; 裸; 裸; 裸; 裸; ) CJK COMPATIBILITY IDEOGRAPH-F912
+F913;908F;908F;908F;908F; # (邏; é‚; é‚; é‚; é‚; ) CJK COMPATIBILITY IDEOGRAPH-F913
+F914;6A02;6A02;6A02;6A02; # (樂; 樂; 樂; 樂; 樂; ) CJK COMPATIBILITY IDEOGRAPH-F914
+F915;6D1B;6D1B;6D1B;6D1B; # (洛; 洛; 洛; 洛; 洛; ) CJK COMPATIBILITY IDEOGRAPH-F915
+F916;70D9;70D9;70D9;70D9; # (烙; 烙; 烙; 烙; 烙; ) CJK COMPATIBILITY IDEOGRAPH-F916
+F917;73DE;73DE;73DE;73DE; # (珞; çž; çž; çž; çž; ) CJK COMPATIBILITY IDEOGRAPH-F917
+F918;843D;843D;843D;843D; # (落; è½; è½; è½; è½; ) CJK COMPATIBILITY IDEOGRAPH-F918
+F919;916A;916A;916A;916A; # (酪; 酪; 酪; 酪; 酪; ) CJK COMPATIBILITY IDEOGRAPH-F919
+F91A;99F1;99F1;99F1;99F1; # (駱; 駱; 駱; 駱; 駱; ) CJK COMPATIBILITY IDEOGRAPH-F91A
+F91B;4E82;4E82;4E82;4E82; # (亂; 亂; 亂; 亂; 亂; ) CJK COMPATIBILITY IDEOGRAPH-F91B
+F91C;5375;5375;5375;5375; # (卵; åµ; åµ; åµ; åµ; ) CJK COMPATIBILITY IDEOGRAPH-F91C
+F91D;6B04;6B04;6B04;6B04; # (ï¤; 欄; 欄; 欄; 欄; ) CJK COMPATIBILITY IDEOGRAPH-F91D
+F91E;721B;721B;721B;721B; # (爛; 爛; 爛; 爛; 爛; ) CJK COMPATIBILITY IDEOGRAPH-F91E
+F91F;862D;862D;862D;862D; # (蘭; 蘭; 蘭; 蘭; 蘭; ) CJK COMPATIBILITY IDEOGRAPH-F91F
+F920;9E1E;9E1E;9E1E;9E1E; # (鸞; 鸞; 鸞; 鸞; 鸞; ) CJK COMPATIBILITY IDEOGRAPH-F920
+F921;5D50;5D50;5D50;5D50; # (嵐; åµ; åµ; åµ; åµ; ) CJK COMPATIBILITY IDEOGRAPH-F921
+F922;6FEB;6FEB;6FEB;6FEB; # (濫; 濫; 濫; 濫; 濫; ) CJK COMPATIBILITY IDEOGRAPH-F922
+F923;85CD;85CD;85CD;85CD; # (藍; è—; è—; è—; è—; ) CJK COMPATIBILITY IDEOGRAPH-F923
+F924;8964;8964;8964;8964; # (襤; 襤; 襤; 襤; 襤; ) CJK COMPATIBILITY IDEOGRAPH-F924
+F925;62C9;62C9;62C9;62C9; # (拉; 拉; 拉; 拉; 拉; ) CJK COMPATIBILITY IDEOGRAPH-F925
+F926;81D8;81D8;81D8;81D8; # (臘; 臘; 臘; 臘; 臘; ) CJK COMPATIBILITY IDEOGRAPH-F926
+F927;881F;881F;881F;881F; # (蠟; 蠟; 蠟; 蠟; 蠟; ) CJK COMPATIBILITY IDEOGRAPH-F927
+F928;5ECA;5ECA;5ECA;5ECA; # (廊; 廊; 廊; 廊; 廊; ) CJK COMPATIBILITY IDEOGRAPH-F928
+F929;6717;6717;6717;6717; # (朗; 朗; 朗; 朗; 朗; ) CJK COMPATIBILITY IDEOGRAPH-F929
+F92A;6D6A;6D6A;6D6A;6D6A; # (浪; 浪; 浪; 浪; 浪; ) CJK COMPATIBILITY IDEOGRAPH-F92A
+F92B;72FC;72FC;72FC;72FC; # (狼; 狼; 狼; 狼; 狼; ) CJK COMPATIBILITY IDEOGRAPH-F92B
+F92C;90CE;90CE;90CE;90CE; # (郎; 郎; 郎; 郎; 郎; ) CJK COMPATIBILITY IDEOGRAPH-F92C
+F92D;4F86;4F86;4F86;4F86; # (來; 來; 來; 來; 來; ) CJK COMPATIBILITY IDEOGRAPH-F92D
+F92E;51B7;51B7;51B7;51B7; # (冷; 冷; 冷; 冷; 冷; ) CJK COMPATIBILITY IDEOGRAPH-F92E
+F92F;52DE;52DE;52DE;52DE; # (勞; 勞; 勞; 勞; 勞; ) CJK COMPATIBILITY IDEOGRAPH-F92F
+F930;64C4;64C4;64C4;64C4; # (擄; 擄; 擄; 擄; 擄; ) CJK COMPATIBILITY IDEOGRAPH-F930
+F931;6AD3;6AD3;6AD3;6AD3; # (櫓; 櫓; 櫓; 櫓; 櫓; ) CJK COMPATIBILITY IDEOGRAPH-F931
+F932;7210;7210;7210;7210; # (爐; çˆ; çˆ; çˆ; çˆ; ) CJK COMPATIBILITY IDEOGRAPH-F932
+F933;76E7;76E7;76E7;76E7; # (盧; 盧; 盧; 盧; 盧; ) CJK COMPATIBILITY IDEOGRAPH-F933
+F934;8001;8001;8001;8001; # (老; è€; è€; è€; è€; ) CJK COMPATIBILITY IDEOGRAPH-F934
+F935;8606;8606;8606;8606; # (蘆; 蘆; 蘆; 蘆; 蘆; ) CJK COMPATIBILITY IDEOGRAPH-F935
+F936;865C;865C;865C;865C; # (虜; 虜; 虜; 虜; 虜; ) CJK COMPATIBILITY IDEOGRAPH-F936
+F937;8DEF;8DEF;8DEF;8DEF; # (路; 路; 路; 路; 路; ) CJK COMPATIBILITY IDEOGRAPH-F937
+F938;9732;9732;9732;9732; # (露; 露; 露; 露; 露; ) CJK COMPATIBILITY IDEOGRAPH-F938
+F939;9B6F;9B6F;9B6F;9B6F; # (魯; 魯; 魯; 魯; 魯; ) CJK COMPATIBILITY IDEOGRAPH-F939
+F93A;9DFA;9DFA;9DFA;9DFA; # (鷺; 鷺; 鷺; 鷺; 鷺; ) CJK COMPATIBILITY IDEOGRAPH-F93A
+F93B;788C;788C;788C;788C; # (碌; 碌; 碌; 碌; 碌; ) CJK COMPATIBILITY IDEOGRAPH-F93B
+F93C;797F;797F;797F;797F; # (祿; 祿; 祿; 祿; 祿; ) CJK COMPATIBILITY IDEOGRAPH-F93C
+F93D;7DA0;7DA0;7DA0;7DA0; # (綠; 綠; 綠; 綠; 綠; ) CJK COMPATIBILITY IDEOGRAPH-F93D
+F93E;83C9;83C9;83C9;83C9; # (菉; è‰; è‰; è‰; è‰; ) CJK COMPATIBILITY IDEOGRAPH-F93E
+F93F;9304;9304;9304;9304; # (錄; 錄; 錄; 錄; 錄; ) CJK COMPATIBILITY IDEOGRAPH-F93F
+F940;9E7F;9E7F;9E7F;9E7F; # (鹿; 鹿; 鹿; 鹿; 鹿; ) CJK COMPATIBILITY IDEOGRAPH-F940
+F941;8AD6;8AD6;8AD6;8AD6; # (ï¥; è«–; è«–; è«–; è«–; ) CJK COMPATIBILITY IDEOGRAPH-F941
+F942;58DF;58DF;58DF;58DF; # (壟; 壟; 壟; 壟; 壟; ) CJK COMPATIBILITY IDEOGRAPH-F942
+F943;5F04;5F04;5F04;5F04; # (弄; 弄; 弄; 弄; 弄; ) CJK COMPATIBILITY IDEOGRAPH-F943
+F944;7C60;7C60;7C60;7C60; # (籠; 籠; 籠; 籠; 籠; ) CJK COMPATIBILITY IDEOGRAPH-F944
+F945;807E;807E;807E;807E; # (聾; è¾; è¾; è¾; è¾; ) CJK COMPATIBILITY IDEOGRAPH-F945
+F946;7262;7262;7262;7262; # (牢; 牢; 牢; 牢; 牢; ) CJK COMPATIBILITY IDEOGRAPH-F946
+F947;78CA;78CA;78CA;78CA; # (磊; 磊; 磊; 磊; 磊; ) CJK COMPATIBILITY IDEOGRAPH-F947
+F948;8CC2;8CC2;8CC2;8CC2; # (賂; 賂; 賂; 賂; 賂; ) CJK COMPATIBILITY IDEOGRAPH-F948
+F949;96F7;96F7;96F7;96F7; # (雷; 雷; 雷; 雷; 雷; ) CJK COMPATIBILITY IDEOGRAPH-F949
+F94A;58D8;58D8;58D8;58D8; # (壘; 壘; 壘; 壘; 壘; ) CJK COMPATIBILITY IDEOGRAPH-F94A
+F94B;5C62;5C62;5C62;5C62; # (屢; 屢; 屢; 屢; 屢; ) CJK COMPATIBILITY IDEOGRAPH-F94B
+F94C;6A13;6A13;6A13;6A13; # (樓; 樓; 樓; 樓; 樓; ) CJK COMPATIBILITY IDEOGRAPH-F94C
+F94D;6DDA;6DDA;6DDA;6DDA; # (ï¥; æ·š; æ·š; æ·š; æ·š; ) CJK COMPATIBILITY IDEOGRAPH-F94D
+F94E;6F0F;6F0F;6F0F;6F0F; # (漏; æ¼; æ¼; æ¼; æ¼; ) CJK COMPATIBILITY IDEOGRAPH-F94E
+F94F;7D2F;7D2F;7D2F;7D2F; # (ï¥; ç´¯; ç´¯; ç´¯; ç´¯; ) CJK COMPATIBILITY IDEOGRAPH-F94F
+F950;7E37;7E37;7E37;7E37; # (ï¥; 縷; 縷; 縷; 縷; ) CJK COMPATIBILITY IDEOGRAPH-F950
+F951;964B;964B;964B;964B; # (陋; 陋; 陋; 陋; 陋; ) CJK COMPATIBILITY IDEOGRAPH-F951
+F952;52D2;52D2;52D2;52D2; # (勒; 勒; 勒; 勒; 勒; ) CJK COMPATIBILITY IDEOGRAPH-F952
+F953;808B;808B;808B;808B; # (肋; 肋; 肋; 肋; 肋; ) CJK COMPATIBILITY IDEOGRAPH-F953
+F954;51DC;51DC;51DC;51DC; # (凜; 凜; 凜; 凜; 凜; ) CJK COMPATIBILITY IDEOGRAPH-F954
+F955;51CC;51CC;51CC;51CC; # (凌; 凌; 凌; 凌; 凌; ) CJK COMPATIBILITY IDEOGRAPH-F955
+F956;7A1C;7A1C;7A1C;7A1C; # (稜; 稜; 稜; 稜; 稜; ) CJK COMPATIBILITY IDEOGRAPH-F956
+F957;7DBE;7DBE;7DBE;7DBE; # (綾; 綾; 綾; 綾; 綾; ) CJK COMPATIBILITY IDEOGRAPH-F957
+F958;83F1;83F1;83F1;83F1; # (菱; è±; è±; è±; è±; ) CJK COMPATIBILITY IDEOGRAPH-F958
+F959;9675;9675;9675;9675; # (陵; 陵; 陵; 陵; 陵; ) CJK COMPATIBILITY IDEOGRAPH-F959
+F95A;8B80;8B80;8B80;8B80; # (讀; 讀; 讀; 讀; 讀; ) CJK COMPATIBILITY IDEOGRAPH-F95A
+F95B;62CF;62CF;62CF;62CF; # (拏; æ‹; æ‹; æ‹; æ‹; ) CJK COMPATIBILITY IDEOGRAPH-F95B
+F95C;6A02;6A02;6A02;6A02; # (樂; 樂; 樂; 樂; 樂; ) CJK COMPATIBILITY IDEOGRAPH-F95C
+F95D;8AFE;8AFE;8AFE;8AFE; # (ï¥; 諾; 諾; 諾; 諾; ) CJK COMPATIBILITY IDEOGRAPH-F95D
+F95E;4E39;4E39;4E39;4E39; # (丹; 丹; 丹; 丹; 丹; ) CJK COMPATIBILITY IDEOGRAPH-F95E
+F95F;5BE7;5BE7;5BE7;5BE7; # (寧; 寧; 寧; 寧; 寧; ) CJK COMPATIBILITY IDEOGRAPH-F95F
+F960;6012;6012;6012;6012; # (怒; 怒; 怒; 怒; 怒; ) CJK COMPATIBILITY IDEOGRAPH-F960
+F961;7387;7387;7387;7387; # (率; 率; 率; 率; 率; ) CJK COMPATIBILITY IDEOGRAPH-F961
+F962;7570;7570;7570;7570; # (異; 異; 異; 異; 異; ) CJK COMPATIBILITY IDEOGRAPH-F962
+F963;5317;5317;5317;5317; # (北; 北; 北; 北; 北; ) CJK COMPATIBILITY IDEOGRAPH-F963
+F964;78FB;78FB;78FB;78FB; # (磻; 磻; 磻; 磻; 磻; ) CJK COMPATIBILITY IDEOGRAPH-F964
+F965;4FBF;4FBF;4FBF;4FBF; # (便; 便; 便; 便; 便; ) CJK COMPATIBILITY IDEOGRAPH-F965
+F966;5FA9;5FA9;5FA9;5FA9; # (復; 復; 復; 復; 復; ) CJK COMPATIBILITY IDEOGRAPH-F966
+F967;4E0D;4E0D;4E0D;4E0D; # (不; ä¸; ä¸; ä¸; ä¸; ) CJK COMPATIBILITY IDEOGRAPH-F967
+F968;6CCC;6CCC;6CCC;6CCC; # (泌; 泌; 泌; 泌; 泌; ) CJK COMPATIBILITY IDEOGRAPH-F968
+F969;6578;6578;6578;6578; # (數; 數; 數; 數; 數; ) CJK COMPATIBILITY IDEOGRAPH-F969
+F96A;7D22;7D22;7D22;7D22; # (索; 索; 索; 索; 索; ) CJK COMPATIBILITY IDEOGRAPH-F96A
+F96B;53C3;53C3;53C3;53C3; # (參; åƒ; åƒ; åƒ; åƒ; ) CJK COMPATIBILITY IDEOGRAPH-F96B
+F96C;585E;585E;585E;585E; # (塞; 塞; 塞; 塞; 塞; ) CJK COMPATIBILITY IDEOGRAPH-F96C
+F96D;7701;7701;7701;7701; # (省; çœ; çœ; çœ; çœ; ) CJK COMPATIBILITY IDEOGRAPH-F96D
+F96E;8449;8449;8449;8449; # (葉; 葉; 葉; 葉; 葉; ) CJK COMPATIBILITY IDEOGRAPH-F96E
+F96F;8AAA;8AAA;8AAA;8AAA; # (說; 說; 說; 說; 說; ) CJK COMPATIBILITY IDEOGRAPH-F96F
+F970;6BBA;6BBA;6BBA;6BBA; # (殺; 殺; 殺; 殺; 殺; ) CJK COMPATIBILITY IDEOGRAPH-F970
+F971;8FB0;8FB0;8FB0;8FB0; # (辰; 辰; 辰; 辰; 辰; ) CJK COMPATIBILITY IDEOGRAPH-F971
+F972;6C88;6C88;6C88;6C88; # (沈; 沈; 沈; 沈; 沈; ) CJK COMPATIBILITY IDEOGRAPH-F972
+F973;62FE;62FE;62FE;62FE; # (拾; 拾; 拾; 拾; 拾; ) CJK COMPATIBILITY IDEOGRAPH-F973
+F974;82E5;82E5;82E5;82E5; # (若; 若; 若; 若; 若; ) CJK COMPATIBILITY IDEOGRAPH-F974
+F975;63A0;63A0;63A0;63A0; # (掠; 掠; 掠; 掠; 掠; ) CJK COMPATIBILITY IDEOGRAPH-F975
+F976;7565;7565;7565;7565; # (略; 略; 略; 略; 略; ) CJK COMPATIBILITY IDEOGRAPH-F976
+F977;4EAE;4EAE;4EAE;4EAE; # (亮; 亮; 亮; 亮; 亮; ) CJK COMPATIBILITY IDEOGRAPH-F977
+F978;5169;5169;5169;5169; # (兩; 兩; 兩; 兩; 兩; ) CJK COMPATIBILITY IDEOGRAPH-F978
+F979;51C9;51C9;51C9;51C9; # (凉; 凉; 凉; 凉; 凉; ) CJK COMPATIBILITY IDEOGRAPH-F979
+F97A;6881;6881;6881;6881; # (梁; æ¢; æ¢; æ¢; æ¢; ) CJK COMPATIBILITY IDEOGRAPH-F97A
+F97B;7CE7;7CE7;7CE7;7CE7; # (糧; 糧; 糧; 糧; 糧; ) CJK COMPATIBILITY IDEOGRAPH-F97B
+F97C;826F;826F;826F;826F; # (良; 良; 良; 良; 良; ) CJK COMPATIBILITY IDEOGRAPH-F97C
+F97D;8AD2;8AD2;8AD2;8AD2; # (諒; 諒; 諒; 諒; 諒; ) CJK COMPATIBILITY IDEOGRAPH-F97D
+F97E;91CF;91CF;91CF;91CF; # (量; é‡; é‡; é‡; é‡; ) CJK COMPATIBILITY IDEOGRAPH-F97E
+F97F;52F5;52F5;52F5;52F5; # (勵; 勵; 勵; 勵; 勵; ) CJK COMPATIBILITY IDEOGRAPH-F97F
+F980;5442;5442;5442;5442; # (呂; 呂; 呂; 呂; 呂; ) CJK COMPATIBILITY IDEOGRAPH-F980
+F981;5973;5973;5973;5973; # (ï¦; 女; 女; 女; 女; ) CJK COMPATIBILITY IDEOGRAPH-F981
+F982;5EEC;5EEC;5EEC;5EEC; # (廬; 廬; 廬; 廬; 廬; ) CJK COMPATIBILITY IDEOGRAPH-F982
+F983;65C5;65C5;65C5;65C5; # (旅; 旅; 旅; 旅; 旅; ) CJK COMPATIBILITY IDEOGRAPH-F983
+F984;6FFE;6FFE;6FFE;6FFE; # (濾; 濾; 濾; 濾; 濾; ) CJK COMPATIBILITY IDEOGRAPH-F984
+F985;792A;792A;792A;792A; # (礪; 礪; 礪; 礪; 礪; ) CJK COMPATIBILITY IDEOGRAPH-F985
+F986;95AD;95AD;95AD;95AD; # (閭; 閭; 閭; 閭; 閭; ) CJK COMPATIBILITY IDEOGRAPH-F986
+F987;9A6A;9A6A;9A6A;9A6A; # (驪; 驪; 驪; 驪; 驪; ) CJK COMPATIBILITY IDEOGRAPH-F987
+F988;9E97;9E97;9E97;9E97; # (麗; 麗; 麗; 麗; 麗; ) CJK COMPATIBILITY IDEOGRAPH-F988
+F989;9ECE;9ECE;9ECE;9ECE; # (黎; 黎; 黎; 黎; 黎; ) CJK COMPATIBILITY IDEOGRAPH-F989
+F98A;529B;529B;529B;529B; # (力; 力; 力; 力; 力; ) CJK COMPATIBILITY IDEOGRAPH-F98A
+F98B;66C6;66C6;66C6;66C6; # (曆; 曆; 曆; 曆; 曆; ) CJK COMPATIBILITY IDEOGRAPH-F98B
+F98C;6B77;6B77;6B77;6B77; # (歷; 歷; 歷; 歷; 歷; ) CJK COMPATIBILITY IDEOGRAPH-F98C
+F98D;8F62;8F62;8F62;8F62; # (ï¦; è½¢; è½¢; è½¢; è½¢; ) CJK COMPATIBILITY IDEOGRAPH-F98D
+F98E;5E74;5E74;5E74;5E74; # (年; 年; 年; 年; 年; ) CJK COMPATIBILITY IDEOGRAPH-F98E
+F98F;6190;6190;6190;6190; # (ï¦; æ†; æ†; æ†; æ†; ) CJK COMPATIBILITY IDEOGRAPH-F98F
+F990;6200;6200;6200;6200; # (ï¦; 戀; 戀; 戀; 戀; ) CJK COMPATIBILITY IDEOGRAPH-F990
+F991;649A;649A;649A;649A; # (撚; 撚; 撚; 撚; 撚; ) CJK COMPATIBILITY IDEOGRAPH-F991
+F992;6F23;6F23;6F23;6F23; # (漣; 漣; 漣; 漣; 漣; ) CJK COMPATIBILITY IDEOGRAPH-F992
+F993;7149;7149;7149;7149; # (煉; 煉; 煉; 煉; 煉; ) CJK COMPATIBILITY IDEOGRAPH-F993
+F994;7489;7489;7489;7489; # (璉; 璉; 璉; 璉; 璉; ) CJK COMPATIBILITY IDEOGRAPH-F994
+F995;79CA;79CA;79CA;79CA; # (秊; 秊; 秊; 秊; 秊; ) CJK COMPATIBILITY IDEOGRAPH-F995
+F996;7DF4;7DF4;7DF4;7DF4; # (練; 練; 練; 練; 練; ) CJK COMPATIBILITY IDEOGRAPH-F996
+F997;806F;806F;806F;806F; # (聯; è¯; è¯; è¯; è¯; ) CJK COMPATIBILITY IDEOGRAPH-F997
+F998;8F26;8F26;8F26;8F26; # (輦; 輦; 輦; 輦; 輦; ) CJK COMPATIBILITY IDEOGRAPH-F998
+F999;84EE;84EE;84EE;84EE; # (蓮; 蓮; 蓮; 蓮; 蓮; ) CJK COMPATIBILITY IDEOGRAPH-F999
+F99A;9023;9023;9023;9023; # (連; 連; 連; 連; 連; ) CJK COMPATIBILITY IDEOGRAPH-F99A
+F99B;934A;934A;934A;934A; # (鍊; éŠ; éŠ; éŠ; éŠ; ) CJK COMPATIBILITY IDEOGRAPH-F99B
+F99C;5217;5217;5217;5217; # (列; 列; 列; 列; 列; ) CJK COMPATIBILITY IDEOGRAPH-F99C
+F99D;52A3;52A3;52A3;52A3; # (ï¦; 劣; 劣; 劣; 劣; ) CJK COMPATIBILITY IDEOGRAPH-F99D
+F99E;54BD;54BD;54BD;54BD; # (咽; 咽; 咽; 咽; 咽; ) CJK COMPATIBILITY IDEOGRAPH-F99E
+F99F;70C8;70C8;70C8;70C8; # (烈; 烈; 烈; 烈; 烈; ) CJK COMPATIBILITY IDEOGRAPH-F99F
+F9A0;88C2;88C2;88C2;88C2; # (裂; 裂; 裂; 裂; 裂; ) CJK COMPATIBILITY IDEOGRAPH-F9A0
+F9A1;8AAA;8AAA;8AAA;8AAA; # (說; 說; 說; 說; 說; ) CJK COMPATIBILITY IDEOGRAPH-F9A1
+F9A2;5EC9;5EC9;5EC9;5EC9; # (廉; 廉; 廉; 廉; 廉; ) CJK COMPATIBILITY IDEOGRAPH-F9A2
+F9A3;5FF5;5FF5;5FF5;5FF5; # (念; 念; 念; 念; 念; ) CJK COMPATIBILITY IDEOGRAPH-F9A3
+F9A4;637B;637B;637B;637B; # (捻; æ»; æ»; æ»; æ»; ) CJK COMPATIBILITY IDEOGRAPH-F9A4
+F9A5;6BAE;6BAE;6BAE;6BAE; # (殮; 殮; 殮; 殮; 殮; ) CJK COMPATIBILITY IDEOGRAPH-F9A5
+F9A6;7C3E;7C3E;7C3E;7C3E; # (簾; 簾; 簾; 簾; 簾; ) CJK COMPATIBILITY IDEOGRAPH-F9A6
+F9A7;7375;7375;7375;7375; # (獵; çµ; çµ; çµ; çµ; ) CJK COMPATIBILITY IDEOGRAPH-F9A7
+F9A8;4EE4;4EE4;4EE4;4EE4; # (令; 令; 令; 令; 令; ) CJK COMPATIBILITY IDEOGRAPH-F9A8
+F9A9;56F9;56F9;56F9;56F9; # (囹; 囹; 囹; 囹; 囹; ) CJK COMPATIBILITY IDEOGRAPH-F9A9
+F9AA;5BE7;5BE7;5BE7;5BE7; # (寧; 寧; 寧; 寧; 寧; ) CJK COMPATIBILITY IDEOGRAPH-F9AA
+F9AB;5DBA;5DBA;5DBA;5DBA; # (嶺; 嶺; 嶺; 嶺; 嶺; ) CJK COMPATIBILITY IDEOGRAPH-F9AB
+F9AC;601C;601C;601C;601C; # (怜; 怜; 怜; 怜; 怜; ) CJK COMPATIBILITY IDEOGRAPH-F9AC
+F9AD;73B2;73B2;73B2;73B2; # (玲; 玲; 玲; 玲; 玲; ) CJK COMPATIBILITY IDEOGRAPH-F9AD
+F9AE;7469;7469;7469;7469; # (瑩; 瑩; 瑩; 瑩; 瑩; ) CJK COMPATIBILITY IDEOGRAPH-F9AE
+F9AF;7F9A;7F9A;7F9A;7F9A; # (羚; 羚; 羚; 羚; 羚; ) CJK COMPATIBILITY IDEOGRAPH-F9AF
+F9B0;8046;8046;8046;8046; # (聆; è†; è†; è†; è†; ) CJK COMPATIBILITY IDEOGRAPH-F9B0
+F9B1;9234;9234;9234;9234; # (鈴; 鈴; 鈴; 鈴; 鈴; ) CJK COMPATIBILITY IDEOGRAPH-F9B1
+F9B2;96F6;96F6;96F6;96F6; # (零; 零; 零; 零; 零; ) CJK COMPATIBILITY IDEOGRAPH-F9B2
+F9B3;9748;9748;9748;9748; # (靈; éˆ; éˆ; éˆ; éˆ; ) CJK COMPATIBILITY IDEOGRAPH-F9B3
+F9B4;9818;9818;9818;9818; # (領; 領; 領; 領; 領; ) CJK COMPATIBILITY IDEOGRAPH-F9B4
+F9B5;4F8B;4F8B;4F8B;4F8B; # (例; 例; 例; 例; 例; ) CJK COMPATIBILITY IDEOGRAPH-F9B5
+F9B6;79AE;79AE;79AE;79AE; # (禮; 禮; 禮; 禮; 禮; ) CJK COMPATIBILITY IDEOGRAPH-F9B6
+F9B7;91B4;91B4;91B4;91B4; # (醴; 醴; 醴; 醴; 醴; ) CJK COMPATIBILITY IDEOGRAPH-F9B7
+F9B8;96B8;96B8;96B8;96B8; # (隸; 隸; 隸; 隸; 隸; ) CJK COMPATIBILITY IDEOGRAPH-F9B8
+F9B9;60E1;60E1;60E1;60E1; # (惡; 惡; 惡; 惡; 惡; ) CJK COMPATIBILITY IDEOGRAPH-F9B9
+F9BA;4E86;4E86;4E86;4E86; # (了; 了; 了; 了; 了; ) CJK COMPATIBILITY IDEOGRAPH-F9BA
+F9BB;50DA;50DA;50DA;50DA; # (僚; 僚; 僚; 僚; 僚; ) CJK COMPATIBILITY IDEOGRAPH-F9BB
+F9BC;5BEE;5BEE;5BEE;5BEE; # (寮; 寮; 寮; 寮; 寮; ) CJK COMPATIBILITY IDEOGRAPH-F9BC
+F9BD;5C3F;5C3F;5C3F;5C3F; # (尿; 尿; 尿; 尿; 尿; ) CJK COMPATIBILITY IDEOGRAPH-F9BD
+F9BE;6599;6599;6599;6599; # (料; 料; 料; 料; 料; ) CJK COMPATIBILITY IDEOGRAPH-F9BE
+F9BF;6A02;6A02;6A02;6A02; # (樂; 樂; 樂; 樂; 樂; ) CJK COMPATIBILITY IDEOGRAPH-F9BF
+F9C0;71CE;71CE;71CE;71CE; # (燎; 燎; 燎; 燎; 燎; ) CJK COMPATIBILITY IDEOGRAPH-F9C0
+F9C1;7642;7642;7642;7642; # (ï§; 療; 療; 療; 療; ) CJK COMPATIBILITY IDEOGRAPH-F9C1
+F9C2;84FC;84FC;84FC;84FC; # (蓼; 蓼; 蓼; 蓼; 蓼; ) CJK COMPATIBILITY IDEOGRAPH-F9C2
+F9C3;907C;907C;907C;907C; # (遼; é¼; é¼; é¼; é¼; ) CJK COMPATIBILITY IDEOGRAPH-F9C3
+F9C4;9F8D;9F8D;9F8D;9F8D; # (龍; é¾; é¾; é¾; é¾; ) CJK COMPATIBILITY IDEOGRAPH-F9C4
+F9C5;6688;6688;6688;6688; # (暈; 暈; 暈; 暈; 暈; ) CJK COMPATIBILITY IDEOGRAPH-F9C5
+F9C6;962E;962E;962E;962E; # (阮; 阮; 阮; 阮; 阮; ) CJK COMPATIBILITY IDEOGRAPH-F9C6
+F9C7;5289;5289;5289;5289; # (劉; 劉; 劉; 劉; 劉; ) CJK COMPATIBILITY IDEOGRAPH-F9C7
+F9C8;677B;677B;677B;677B; # (杻; æ»; æ»; æ»; æ»; ) CJK COMPATIBILITY IDEOGRAPH-F9C8
+F9C9;67F3;67F3;67F3;67F3; # (柳; 柳; 柳; 柳; 柳; ) CJK COMPATIBILITY IDEOGRAPH-F9C9
+F9CA;6D41;6D41;6D41;6D41; # (流; æµ; æµ; æµ; æµ; ) CJK COMPATIBILITY IDEOGRAPH-F9CA
+F9CB;6E9C;6E9C;6E9C;6E9C; # (溜; 溜; 溜; 溜; 溜; ) CJK COMPATIBILITY IDEOGRAPH-F9CB
+F9CC;7409;7409;7409;7409; # (琉; ç‰; ç‰; ç‰; ç‰; ) CJK COMPATIBILITY IDEOGRAPH-F9CC
+F9CD;7559;7559;7559;7559; # (ï§; ç•™; ç•™; ç•™; ç•™; ) CJK COMPATIBILITY IDEOGRAPH-F9CD
+F9CE;786B;786B;786B;786B; # (硫; 硫; 硫; 硫; 硫; ) CJK COMPATIBILITY IDEOGRAPH-F9CE
+F9CF;7D10;7D10;7D10;7D10; # (ï§; ç´; ç´; ç´; ç´; ) CJK COMPATIBILITY IDEOGRAPH-F9CF
+F9D0;985E;985E;985E;985E; # (ï§; é¡ž; é¡ž; é¡ž; é¡ž; ) CJK COMPATIBILITY IDEOGRAPH-F9D0
+F9D1;516D;516D;516D;516D; # (六; 六; 六; 六; 六; ) CJK COMPATIBILITY IDEOGRAPH-F9D1
+F9D2;622E;622E;622E;622E; # (戮; 戮; 戮; 戮; 戮; ) CJK COMPATIBILITY IDEOGRAPH-F9D2
+F9D3;9678;9678;9678;9678; # (陸; 陸; 陸; 陸; 陸; ) CJK COMPATIBILITY IDEOGRAPH-F9D3
+F9D4;502B;502B;502B;502B; # (倫; 倫; 倫; 倫; 倫; ) CJK COMPATIBILITY IDEOGRAPH-F9D4
+F9D5;5D19;5D19;5D19;5D19; # (崙; 崙; 崙; 崙; 崙; ) CJK COMPATIBILITY IDEOGRAPH-F9D5
+F9D6;6DEA;6DEA;6DEA;6DEA; # (淪; 淪; 淪; 淪; 淪; ) CJK COMPATIBILITY IDEOGRAPH-F9D6
+F9D7;8F2A;8F2A;8F2A;8F2A; # (輪; 輪; 輪; 輪; 輪; ) CJK COMPATIBILITY IDEOGRAPH-F9D7
+F9D8;5F8B;5F8B;5F8B;5F8B; # (律; 律; 律; 律; 律; ) CJK COMPATIBILITY IDEOGRAPH-F9D8
+F9D9;6144;6144;6144;6144; # (慄; 慄; 慄; 慄; 慄; ) CJK COMPATIBILITY IDEOGRAPH-F9D9
+F9DA;6817;6817;6817;6817; # (栗; 栗; 栗; 栗; 栗; ) CJK COMPATIBILITY IDEOGRAPH-F9DA
+F9DB;7387;7387;7387;7387; # (率; 率; 率; 率; 率; ) CJK COMPATIBILITY IDEOGRAPH-F9DB
+F9DC;9686;9686;9686;9686; # (隆; 隆; 隆; 隆; 隆; ) CJK COMPATIBILITY IDEOGRAPH-F9DC
+F9DD;5229;5229;5229;5229; # (ï§; 利; 利; 利; 利; ) CJK COMPATIBILITY IDEOGRAPH-F9DD
+F9DE;540F;540F;540F;540F; # (吏; å; å; å; å; ) CJK COMPATIBILITY IDEOGRAPH-F9DE
+F9DF;5C65;5C65;5C65;5C65; # (履; 履; 履; 履; 履; ) CJK COMPATIBILITY IDEOGRAPH-F9DF
+F9E0;6613;6613;6613;6613; # (易; 易; 易; 易; 易; ) CJK COMPATIBILITY IDEOGRAPH-F9E0
+F9E1;674E;674E;674E;674E; # (李; æŽ; æŽ; æŽ; æŽ; ) CJK COMPATIBILITY IDEOGRAPH-F9E1
+F9E2;68A8;68A8;68A8;68A8; # (梨; 梨; 梨; 梨; 梨; ) CJK COMPATIBILITY IDEOGRAPH-F9E2
+F9E3;6CE5;6CE5;6CE5;6CE5; # (泥; 泥; 泥; 泥; 泥; ) CJK COMPATIBILITY IDEOGRAPH-F9E3
+F9E4;7406;7406;7406;7406; # (理; ç†; ç†; ç†; ç†; ) CJK COMPATIBILITY IDEOGRAPH-F9E4
+F9E5;75E2;75E2;75E2;75E2; # (痢; 痢; 痢; 痢; 痢; ) CJK COMPATIBILITY IDEOGRAPH-F9E5
+F9E6;7F79;7F79;7F79;7F79; # (罹; 罹; 罹; 罹; 罹; ) CJK COMPATIBILITY IDEOGRAPH-F9E6
+F9E7;88CF;88CF;88CF;88CF; # (裏; è£; è£; è£; è£; ) CJK COMPATIBILITY IDEOGRAPH-F9E7
+F9E8;88E1;88E1;88E1;88E1; # (裡; 裡; 裡; 裡; 裡; ) CJK COMPATIBILITY IDEOGRAPH-F9E8
+F9E9;91CC;91CC;91CC;91CC; # (里; 里; 里; 里; 里; ) CJK COMPATIBILITY IDEOGRAPH-F9E9
+F9EA;96E2;96E2;96E2;96E2; # (離; 離; 離; 離; 離; ) CJK COMPATIBILITY IDEOGRAPH-F9EA
+F9EB;533F;533F;533F;533F; # (匿; 匿; 匿; 匿; 匿; ) CJK COMPATIBILITY IDEOGRAPH-F9EB
+F9EC;6EBA;6EBA;6EBA;6EBA; # (溺; 溺; 溺; 溺; 溺; ) CJK COMPATIBILITY IDEOGRAPH-F9EC
+F9ED;541D;541D;541D;541D; # (吝; å; å; å; å; ) CJK COMPATIBILITY IDEOGRAPH-F9ED
+F9EE;71D0;71D0;71D0;71D0; # (燐; ç‡; ç‡; ç‡; ç‡; ) CJK COMPATIBILITY IDEOGRAPH-F9EE
+F9EF;7498;7498;7498;7498; # (璘; 璘; 璘; 璘; 璘; ) CJK COMPATIBILITY IDEOGRAPH-F9EF
+F9F0;85FA;85FA;85FA;85FA; # (藺; 藺; 藺; 藺; 藺; ) CJK COMPATIBILITY IDEOGRAPH-F9F0
+F9F1;96A3;96A3;96A3;96A3; # (隣; 隣; 隣; 隣; 隣; ) CJK COMPATIBILITY IDEOGRAPH-F9F1
+F9F2;9C57;9C57;9C57;9C57; # (鱗; 鱗; 鱗; 鱗; 鱗; ) CJK COMPATIBILITY IDEOGRAPH-F9F2
+F9F3;9E9F;9E9F;9E9F;9E9F; # (麟; 麟; 麟; 麟; 麟; ) CJK COMPATIBILITY IDEOGRAPH-F9F3
+F9F4;6797;6797;6797;6797; # (林; 林; 林; 林; 林; ) CJK COMPATIBILITY IDEOGRAPH-F9F4
+F9F5;6DCB;6DCB;6DCB;6DCB; # (淋; 淋; 淋; 淋; 淋; ) CJK COMPATIBILITY IDEOGRAPH-F9F5
+F9F6;81E8;81E8;81E8;81E8; # (臨; 臨; 臨; 臨; 臨; ) CJK COMPATIBILITY IDEOGRAPH-F9F6
+F9F7;7ACB;7ACB;7ACB;7ACB; # (立; 立; 立; 立; 立; ) CJK COMPATIBILITY IDEOGRAPH-F9F7
+F9F8;7B20;7B20;7B20;7B20; # (笠; 笠; 笠; 笠; 笠; ) CJK COMPATIBILITY IDEOGRAPH-F9F8
+F9F9;7C92;7C92;7C92;7C92; # (粒; 粒; 粒; 粒; 粒; ) CJK COMPATIBILITY IDEOGRAPH-F9F9
+F9FA;72C0;72C0;72C0;72C0; # (狀; 狀; 狀; 狀; 狀; ) CJK COMPATIBILITY IDEOGRAPH-F9FA
+F9FB;7099;7099;7099;7099; # (炙; 炙; 炙; 炙; 炙; ) CJK COMPATIBILITY IDEOGRAPH-F9FB
+F9FC;8B58;8B58;8B58;8B58; # (識; 識; 識; 識; 識; ) CJK COMPATIBILITY IDEOGRAPH-F9FC
+F9FD;4EC0;4EC0;4EC0;4EC0; # (什; 什; 什; 什; 什; ) CJK COMPATIBILITY IDEOGRAPH-F9FD
+F9FE;8336;8336;8336;8336; # (茶; 茶; 茶; 茶; 茶; ) CJK COMPATIBILITY IDEOGRAPH-F9FE
+F9FF;523A;523A;523A;523A; # (刺; 刺; 刺; 刺; 刺; ) CJK COMPATIBILITY IDEOGRAPH-F9FF
+FA00;5207;5207;5207;5207; # (切; 切; 切; 切; 切; ) CJK COMPATIBILITY IDEOGRAPH-FA00
+FA01;5EA6;5EA6;5EA6;5EA6; # (ï¨; 度; 度; 度; 度; ) CJK COMPATIBILITY IDEOGRAPH-FA01
+FA02;62D3;62D3;62D3;62D3; # (拓; 拓; 拓; 拓; 拓; ) CJK COMPATIBILITY IDEOGRAPH-FA02
+FA03;7CD6;7CD6;7CD6;7CD6; # (糖; 糖; 糖; 糖; 糖; ) CJK COMPATIBILITY IDEOGRAPH-FA03
+FA04;5B85;5B85;5B85;5B85; # (宅; 宅; 宅; 宅; 宅; ) CJK COMPATIBILITY IDEOGRAPH-FA04
+FA05;6D1E;6D1E;6D1E;6D1E; # (洞; 洞; 洞; 洞; 洞; ) CJK COMPATIBILITY IDEOGRAPH-FA05
+FA06;66B4;66B4;66B4;66B4; # (暴; 暴; 暴; 暴; 暴; ) CJK COMPATIBILITY IDEOGRAPH-FA06
+FA07;8F3B;8F3B;8F3B;8F3B; # (輻; 輻; 輻; 輻; 輻; ) CJK COMPATIBILITY IDEOGRAPH-FA07
+FA08;884C;884C;884C;884C; # (行; 行; 行; 行; 行; ) CJK COMPATIBILITY IDEOGRAPH-FA08
+FA09;964D;964D;964D;964D; # (降; é™; é™; é™; é™; ) CJK COMPATIBILITY IDEOGRAPH-FA09
+FA0A;898B;898B;898B;898B; # (見; 見; 見; 見; 見; ) CJK COMPATIBILITY IDEOGRAPH-FA0A
+FA0B;5ED3;5ED3;5ED3;5ED3; # (廓; 廓; 廓; 廓; 廓; ) CJK COMPATIBILITY IDEOGRAPH-FA0B
+FA0C;5140;5140;5140;5140; # (兀; 兀; 兀; 兀; 兀; ) CJK COMPATIBILITY IDEOGRAPH-FA0C
+FA0D;55C0;55C0;55C0;55C0; # (ï¨; å—€; å—€; å—€; å—€; ) CJK COMPATIBILITY IDEOGRAPH-FA0D
+FA10;585A;585A;585A;585A; # (ï¨; å¡š; å¡š; å¡š; å¡š; ) CJK COMPATIBILITY IDEOGRAPH-FA10
+FA12;6674;6674;6674;6674; # (晴; 晴; 晴; 晴; 晴; ) CJK COMPATIBILITY IDEOGRAPH-FA12
+FA15;51DE;51DE;51DE;51DE; # (凞; 凞; 凞; 凞; 凞; ) CJK COMPATIBILITY IDEOGRAPH-FA15
+FA16;732A;732A;732A;732A; # (猪; 猪; 猪; 猪; 猪; ) CJK COMPATIBILITY IDEOGRAPH-FA16
+FA17;76CA;76CA;76CA;76CA; # (益; 益; 益; 益; 益; ) CJK COMPATIBILITY IDEOGRAPH-FA17
+FA18;793C;793C;793C;793C; # (礼; 礼; 礼; 礼; 礼; ) CJK COMPATIBILITY IDEOGRAPH-FA18
+FA19;795E;795E;795E;795E; # (神; 神; 神; 神; 神; ) CJK COMPATIBILITY IDEOGRAPH-FA19
+FA1A;7965;7965;7965;7965; # (祥; 祥; 祥; 祥; 祥; ) CJK COMPATIBILITY IDEOGRAPH-FA1A
+FA1B;798F;798F;798F;798F; # (福; ç¦; ç¦; ç¦; ç¦; ) CJK COMPATIBILITY IDEOGRAPH-FA1B
+FA1C;9756;9756;9756;9756; # (靖; é–; é–; é–; é–; ) CJK COMPATIBILITY IDEOGRAPH-FA1C
+FA1D;7CBE;7CBE;7CBE;7CBE; # (ï¨; ç²¾; ç²¾; ç²¾; ç²¾; ) CJK COMPATIBILITY IDEOGRAPH-FA1D
+FA1E;7FBD;7FBD;7FBD;7FBD; # (羽; 羽; 羽; 羽; 羽; ) CJK COMPATIBILITY IDEOGRAPH-FA1E
+FA20;8612;8612;8612;8612; # (蘒; 蘒; 蘒; 蘒; 蘒; ) CJK COMPATIBILITY IDEOGRAPH-FA20
+FA22;8AF8;8AF8;8AF8;8AF8; # (諸; 諸; 諸; 諸; 諸; ) CJK COMPATIBILITY IDEOGRAPH-FA22
+FA25;9038;9038;9038;9038; # (逸; 逸; 逸; 逸; 逸; ) CJK COMPATIBILITY IDEOGRAPH-FA25
+FA26;90FD;90FD;90FD;90FD; # (都; 都; 都; 都; 都; ) CJK COMPATIBILITY IDEOGRAPH-FA26
+FA2A;98EF;98EF;98EF;98EF; # (飯; 飯; 飯; 飯; 飯; ) CJK COMPATIBILITY IDEOGRAPH-FA2A
+FA2B;98FC;98FC;98FC;98FC; # (飼; 飼; 飼; 飼; 飼; ) CJK COMPATIBILITY IDEOGRAPH-FA2B
+FA2C;9928;9928;9928;9928; # (館; 館; 館; 館; 館; ) CJK COMPATIBILITY IDEOGRAPH-FA2C
+FA2D;9DB4;9DB4;9DB4;9DB4; # (鶴; 鶴; 鶴; 鶴; 鶴; ) CJK COMPATIBILITY IDEOGRAPH-FA2D
+FA2E;90DE;90DE;90DE;90DE; # (郞; 郞; 郞; 郞; 郞; ) CJK COMPATIBILITY IDEOGRAPH-FA2E
+FA2F;96B7;96B7;96B7;96B7; # (隷; 隷; 隷; 隷; 隷; ) CJK COMPATIBILITY IDEOGRAPH-FA2F
+FA30;4FAE;4FAE;4FAE;4FAE; # (侮; 侮; 侮; 侮; 侮; ) CJK COMPATIBILITY IDEOGRAPH-FA30
+FA31;50E7;50E7;50E7;50E7; # (僧; 僧; 僧; 僧; 僧; ) CJK COMPATIBILITY IDEOGRAPH-FA31
+FA32;514D;514D;514D;514D; # (免; å…; å…; å…; å…; ) CJK COMPATIBILITY IDEOGRAPH-FA32
+FA33;52C9;52C9;52C9;52C9; # (勉; 勉; 勉; 勉; 勉; ) CJK COMPATIBILITY IDEOGRAPH-FA33
+FA34;52E4;52E4;52E4;52E4; # (勤; 勤; 勤; 勤; 勤; ) CJK COMPATIBILITY IDEOGRAPH-FA34
+FA35;5351;5351;5351;5351; # (卑; å‘; å‘; å‘; å‘; ) CJK COMPATIBILITY IDEOGRAPH-FA35
+FA36;559D;559D;559D;559D; # (喝; å–; å–; å–; å–; ) CJK COMPATIBILITY IDEOGRAPH-FA36
+FA37;5606;5606;5606;5606; # (嘆; 嘆; 嘆; 嘆; 嘆; ) CJK COMPATIBILITY IDEOGRAPH-FA37
+FA38;5668;5668;5668;5668; # (器; 器; 器; 器; 器; ) CJK COMPATIBILITY IDEOGRAPH-FA38
+FA39;5840;5840;5840;5840; # (塀; 塀; 塀; 塀; 塀; ) CJK COMPATIBILITY IDEOGRAPH-FA39
+FA3A;58A8;58A8;58A8;58A8; # (墨; 墨; 墨; 墨; 墨; ) CJK COMPATIBILITY IDEOGRAPH-FA3A
+FA3B;5C64;5C64;5C64;5C64; # (層; 層; 層; 層; 層; ) CJK COMPATIBILITY IDEOGRAPH-FA3B
+FA3C;5C6E;5C6E;5C6E;5C6E; # (屮; 屮; 屮; 屮; 屮; ) CJK COMPATIBILITY IDEOGRAPH-FA3C
+FA3D;6094;6094;6094;6094; # (悔; 悔; 悔; 悔; 悔; ) CJK COMPATIBILITY IDEOGRAPH-FA3D
+FA3E;6168;6168;6168;6168; # (慨; 慨; 慨; 慨; 慨; ) CJK COMPATIBILITY IDEOGRAPH-FA3E
+FA3F;618E;618E;618E;618E; # (憎; 憎; 憎; 憎; 憎; ) CJK COMPATIBILITY IDEOGRAPH-FA3F
+FA40;61F2;61F2;61F2;61F2; # (懲; 懲; 懲; 懲; 懲; ) CJK COMPATIBILITY IDEOGRAPH-FA40
+FA41;654F;654F;654F;654F; # (ï©; æ•; æ•; æ•; æ•; ) CJK COMPATIBILITY IDEOGRAPH-FA41
+FA42;65E2;65E2;65E2;65E2; # (ï©‚; æ—¢; æ—¢; æ—¢; æ—¢; ) CJK COMPATIBILITY IDEOGRAPH-FA42
+FA43;6691;6691;6691;6691; # (暑; 暑; 暑; 暑; 暑; ) CJK COMPATIBILITY IDEOGRAPH-FA43
+FA44;6885;6885;6885;6885; # (梅; 梅; 梅; 梅; 梅; ) CJK COMPATIBILITY IDEOGRAPH-FA44
+FA45;6D77;6D77;6D77;6D77; # (ï©…; æµ·; æµ·; æµ·; æµ·; ) CJK COMPATIBILITY IDEOGRAPH-FA45
+FA46;6E1A;6E1A;6E1A;6E1A; # (渚; 渚; 渚; 渚; 渚; ) CJK COMPATIBILITY IDEOGRAPH-FA46
+FA47;6F22;6F22;6F22;6F22; # (漢; 漢; 漢; 漢; 漢; ) CJK COMPATIBILITY IDEOGRAPH-FA47
+FA48;716E;716E;716E;716E; # (煮; 煮; 煮; 煮; 煮; ) CJK COMPATIBILITY IDEOGRAPH-FA48
+FA49;722B;722B;722B;722B; # (爫; 爫; 爫; 爫; 爫; ) CJK COMPATIBILITY IDEOGRAPH-FA49
+FA4A;7422;7422;7422;7422; # (ï©Š; ç¢; ç¢; ç¢; ç¢; ) CJK COMPATIBILITY IDEOGRAPH-FA4A
+FA4B;7891;7891;7891;7891; # (碑; 碑; 碑; 碑; 碑; ) CJK COMPATIBILITY IDEOGRAPH-FA4B
+FA4C;793E;793E;793E;793E; # (社; 社; 社; 社; 社; ) CJK COMPATIBILITY IDEOGRAPH-FA4C
+FA4D;7949;7949;7949;7949; # (ï©; 祉; 祉; 祉; 祉; ) CJK COMPATIBILITY IDEOGRAPH-FA4D
+FA4E;7948;7948;7948;7948; # (祈; 祈; 祈; 祈; 祈; ) CJK COMPATIBILITY IDEOGRAPH-FA4E
+FA4F;7950;7950;7950;7950; # (ï©; ç¥; ç¥; ç¥; ç¥; ) CJK COMPATIBILITY IDEOGRAPH-FA4F
+FA50;7956;7956;7956;7956; # (ï©; 祖; 祖; 祖; 祖; ) CJK COMPATIBILITY IDEOGRAPH-FA50
+FA51;795D;795D;795D;795D; # (ï©‘; ç¥; ç¥; ç¥; ç¥; ) CJK COMPATIBILITY IDEOGRAPH-FA51
+FA52;798D;798D;798D;798D; # (ï©’; ç¦; ç¦; ç¦; ç¦; ) CJK COMPATIBILITY IDEOGRAPH-FA52
+FA53;798E;798E;798E;798E; # (禎; 禎; 禎; 禎; 禎; ) CJK COMPATIBILITY IDEOGRAPH-FA53
+FA54;7A40;7A40;7A40;7A40; # (ï©”; ç©€; ç©€; ç©€; ç©€; ) CJK COMPATIBILITY IDEOGRAPH-FA54
+FA55;7A81;7A81;7A81;7A81; # (ï©•; çª; çª; çª; çª; ) CJK COMPATIBILITY IDEOGRAPH-FA55
+FA56;7BC0;7BC0;7BC0;7BC0; # (節; 節; 節; 節; 節; ) CJK COMPATIBILITY IDEOGRAPH-FA56
+FA57;7DF4;7DF4;7DF4;7DF4; # (ï©—; ç·´; ç·´; ç·´; ç·´; ) CJK COMPATIBILITY IDEOGRAPH-FA57
+FA58;7E09;7E09;7E09;7E09; # (縉; 縉; 縉; 縉; 縉; ) CJK COMPATIBILITY IDEOGRAPH-FA58
+FA59;7E41;7E41;7E41;7E41; # (ï©™; ç¹; ç¹; ç¹; ç¹; ) CJK COMPATIBILITY IDEOGRAPH-FA59
+FA5A;7F72;7F72;7F72;7F72; # (ï©š; ç½²; ç½²; ç½²; ç½²; ) CJK COMPATIBILITY IDEOGRAPH-FA5A
+FA5B;8005;8005;8005;8005; # (者; 者; 者; 者; 者; ) CJK COMPATIBILITY IDEOGRAPH-FA5B
+FA5C;81ED;81ED;81ED;81ED; # (臭; 臭; 臭; 臭; 臭; ) CJK COMPATIBILITY IDEOGRAPH-FA5C
+FA5D;8279;8279;8279;8279; # (ï©; 艹; 艹; 艹; 艹; ) CJK COMPATIBILITY IDEOGRAPH-FA5D
+FA5E;8279;8279;8279;8279; # (艹; 艹; 艹; 艹; 艹; ) CJK COMPATIBILITY IDEOGRAPH-FA5E
+FA5F;8457;8457;8457;8457; # (ï©Ÿ; è‘—; è‘—; è‘—; è‘—; ) CJK COMPATIBILITY IDEOGRAPH-FA5F
+FA60;8910;8910;8910;8910; # (ï© ; è¤; è¤; è¤; è¤; ) CJK COMPATIBILITY IDEOGRAPH-FA60
+FA61;8996;8996;8996;8996; # (視; 視; 視; 視; 視; ) CJK COMPATIBILITY IDEOGRAPH-FA61
+FA62;8B01;8B01;8B01;8B01; # (ï©¢; è¬; è¬; è¬; è¬; ) CJK COMPATIBILITY IDEOGRAPH-FA62
+FA63;8B39;8B39;8B39;8B39; # (謹; 謹; 謹; 謹; 謹; ) CJK COMPATIBILITY IDEOGRAPH-FA63
+FA64;8CD3;8CD3;8CD3;8CD3; # (賓; 賓; 賓; 賓; 賓; ) CJK COMPATIBILITY IDEOGRAPH-FA64
+FA65;8D08;8D08;8D08;8D08; # (ï©¥; è´ˆ; è´ˆ; è´ˆ; è´ˆ; ) CJK COMPATIBILITY IDEOGRAPH-FA65
+FA66;8FB6;8FB6;8FB6;8FB6; # (辶; 辶; 辶; 辶; 辶; ) CJK COMPATIBILITY IDEOGRAPH-FA66
+FA67;9038;9038;9038;9038; # (逸; 逸; 逸; 逸; 逸; ) CJK COMPATIBILITY IDEOGRAPH-FA67
+FA68;96E3;96E3;96E3;96E3; # (難; 難; 難; 難; 難; ) CJK COMPATIBILITY IDEOGRAPH-FA68
+FA69;97FF;97FF;97FF;97FF; # (響; 響; 響; 響; 響; ) CJK COMPATIBILITY IDEOGRAPH-FA69
+FA6A;983B;983B;983B;983B; # (頻; 頻; 頻; 頻; 頻; ) CJK COMPATIBILITY IDEOGRAPH-FA6A
+FA6B;6075;6075;6075;6075; # (ï©«; æµ; æµ; æµ; æµ; ) CJK COMPATIBILITY IDEOGRAPH-FA6B
+FA6C;242EE;242EE;242EE;242EE; # (𤋮; 𤋮; 𤋮; 𤋮; 𤋮; ) CJK COMPATIBILITY IDEOGRAPH-FA6C
+FA6D;8218;8218;8218;8218; # (舘; 舘; 舘; 舘; 舘; ) CJK COMPATIBILITY IDEOGRAPH-FA6D
+FA70;4E26;4E26;4E26;4E26; # (並; 並; 並; 並; 並; ) CJK COMPATIBILITY IDEOGRAPH-FA70
+FA71;51B5;51B5;51B5;51B5; # (况; 况; 况; 况; 况; ) CJK COMPATIBILITY IDEOGRAPH-FA71
+FA72;5168;5168;5168;5168; # (全; 全; 全; 全; 全; ) CJK COMPATIBILITY IDEOGRAPH-FA72
+FA73;4F80;4F80;4F80;4F80; # (侀; 侀; 侀; 侀; 侀; ) CJK COMPATIBILITY IDEOGRAPH-FA73
+FA74;5145;5145;5145;5145; # (ï©´; å……; å……; å……; å……; ) CJK COMPATIBILITY IDEOGRAPH-FA74
+FA75;5180;5180;5180;5180; # (冀; 冀; 冀; 冀; 冀; ) CJK COMPATIBILITY IDEOGRAPH-FA75
+FA76;52C7;52C7;52C7;52C7; # (勇; 勇; 勇; 勇; 勇; ) CJK COMPATIBILITY IDEOGRAPH-FA76
+FA77;52FA;52FA;52FA;52FA; # (勺; 勺; 勺; 勺; 勺; ) CJK COMPATIBILITY IDEOGRAPH-FA77
+FA78;559D;559D;559D;559D; # (喝; å–; å–; å–; å–; ) CJK COMPATIBILITY IDEOGRAPH-FA78
+FA79;5555;5555;5555;5555; # (啕; 啕; 啕; 啕; 啕; ) CJK COMPATIBILITY IDEOGRAPH-FA79
+FA7A;5599;5599;5599;5599; # (喙; 喙; 喙; 喙; 喙; ) CJK COMPATIBILITY IDEOGRAPH-FA7A
+FA7B;55E2;55E2;55E2;55E2; # (ï©»; å—¢; å—¢; å—¢; å—¢; ) CJK COMPATIBILITY IDEOGRAPH-FA7B
+FA7C;585A;585A;585A;585A; # (塚; 塚; 塚; 塚; 塚; ) CJK COMPATIBILITY IDEOGRAPH-FA7C
+FA7D;58B3;58B3;58B3;58B3; # (墳; 墳; 墳; 墳; 墳; ) CJK COMPATIBILITY IDEOGRAPH-FA7D
+FA7E;5944;5944;5944;5944; # (奄; 奄; 奄; 奄; 奄; ) CJK COMPATIBILITY IDEOGRAPH-FA7E
+FA7F;5954;5954;5954;5954; # (奔; 奔; 奔; 奔; 奔; ) CJK COMPATIBILITY IDEOGRAPH-FA7F
+FA80;5A62;5A62;5A62;5A62; # (婢; 婢; 婢; 婢; 婢; ) CJK COMPATIBILITY IDEOGRAPH-FA80
+FA81;5B28;5B28;5B28;5B28; # (ïª; 嬨; 嬨; 嬨; 嬨; ) CJK COMPATIBILITY IDEOGRAPH-FA81
+FA82;5ED2;5ED2;5ED2;5ED2; # (廒; 廒; 廒; 廒; 廒; ) CJK COMPATIBILITY IDEOGRAPH-FA82
+FA83;5ED9;5ED9;5ED9;5ED9; # (廙; 廙; 廙; 廙; 廙; ) CJK COMPATIBILITY IDEOGRAPH-FA83
+FA84;5F69;5F69;5F69;5F69; # (彩; 彩; 彩; 彩; 彩; ) CJK COMPATIBILITY IDEOGRAPH-FA84
+FA85;5FAD;5FAD;5FAD;5FAD; # (徭; 徭; 徭; 徭; 徭; ) CJK COMPATIBILITY IDEOGRAPH-FA85
+FA86;60D8;60D8;60D8;60D8; # (惘; 惘; 惘; 惘; 惘; ) CJK COMPATIBILITY IDEOGRAPH-FA86
+FA87;614E;614E;614E;614E; # (慎; 慎; 慎; 慎; 慎; ) CJK COMPATIBILITY IDEOGRAPH-FA87
+FA88;6108;6108;6108;6108; # (愈; 愈; 愈; 愈; 愈; ) CJK COMPATIBILITY IDEOGRAPH-FA88
+FA89;618E;618E;618E;618E; # (憎; 憎; 憎; 憎; 憎; ) CJK COMPATIBILITY IDEOGRAPH-FA89
+FA8A;6160;6160;6160;6160; # (慠; 慠; 慠; 慠; 慠; ) CJK COMPATIBILITY IDEOGRAPH-FA8A
+FA8B;61F2;61F2;61F2;61F2; # (懲; 懲; 懲; 懲; 懲; ) CJK COMPATIBILITY IDEOGRAPH-FA8B
+FA8C;6234;6234;6234;6234; # (戴; 戴; 戴; 戴; 戴; ) CJK COMPATIBILITY IDEOGRAPH-FA8C
+FA8D;63C4;63C4;63C4;63C4; # (ïª; æ„; æ„; æ„; æ„; ) CJK COMPATIBILITY IDEOGRAPH-FA8D
+FA8E;641C;641C;641C;641C; # (搜; æœ; æœ; æœ; æœ; ) CJK COMPATIBILITY IDEOGRAPH-FA8E
+FA8F;6452;6452;6452;6452; # (ïª; æ‘’; æ‘’; æ‘’; æ‘’; ) CJK COMPATIBILITY IDEOGRAPH-FA8F
+FA90;6556;6556;6556;6556; # (ïª; æ•–; æ•–; æ•–; æ•–; ) CJK COMPATIBILITY IDEOGRAPH-FA90
+FA91;6674;6674;6674;6674; # (晴; 晴; 晴; 晴; 晴; ) CJK COMPATIBILITY IDEOGRAPH-FA91
+FA92;6717;6717;6717;6717; # (朗; 朗; 朗; 朗; 朗; ) CJK COMPATIBILITY IDEOGRAPH-FA92
+FA93;671B;671B;671B;671B; # (望; 望; 望; 望; 望; ) CJK COMPATIBILITY IDEOGRAPH-FA93
+FA94;6756;6756;6756;6756; # (杖; æ–; æ–; æ–; æ–; ) CJK COMPATIBILITY IDEOGRAPH-FA94
+FA95;6B79;6B79;6B79;6B79; # (歹; 歹; 歹; 歹; 歹; ) CJK COMPATIBILITY IDEOGRAPH-FA95
+FA96;6BBA;6BBA;6BBA;6BBA; # (殺; 殺; 殺; 殺; 殺; ) CJK COMPATIBILITY IDEOGRAPH-FA96
+FA97;6D41;6D41;6D41;6D41; # (流; æµ; æµ; æµ; æµ; ) CJK COMPATIBILITY IDEOGRAPH-FA97
+FA98;6EDB;6EDB;6EDB;6EDB; # (滛; 滛; 滛; 滛; 滛; ) CJK COMPATIBILITY IDEOGRAPH-FA98
+FA99;6ECB;6ECB;6ECB;6ECB; # (滋; 滋; 滋; 滋; 滋; ) CJK COMPATIBILITY IDEOGRAPH-FA99
+FA9A;6F22;6F22;6F22;6F22; # (漢; 漢; 漢; 漢; 漢; ) CJK COMPATIBILITY IDEOGRAPH-FA9A
+FA9B;701E;701E;701E;701E; # (瀞; 瀞; 瀞; 瀞; 瀞; ) CJK COMPATIBILITY IDEOGRAPH-FA9B
+FA9C;716E;716E;716E;716E; # (煮; 煮; 煮; 煮; 煮; ) CJK COMPATIBILITY IDEOGRAPH-FA9C
+FA9D;77A7;77A7;77A7;77A7; # (ïª; 瞧; 瞧; 瞧; 瞧; ) CJK COMPATIBILITY IDEOGRAPH-FA9D
+FA9E;7235;7235;7235;7235; # (爵; 爵; 爵; 爵; 爵; ) CJK COMPATIBILITY IDEOGRAPH-FA9E
+FA9F;72AF;72AF;72AF;72AF; # (犯; 犯; 犯; 犯; 犯; ) CJK COMPATIBILITY IDEOGRAPH-FA9F
+FAA0;732A;732A;732A;732A; # (猪; 猪; 猪; 猪; 猪; ) CJK COMPATIBILITY IDEOGRAPH-FAA0
+FAA1;7471;7471;7471;7471; # (瑱; 瑱; 瑱; 瑱; 瑱; ) CJK COMPATIBILITY IDEOGRAPH-FAA1
+FAA2;7506;7506;7506;7506; # (甆; 甆; 甆; 甆; 甆; ) CJK COMPATIBILITY IDEOGRAPH-FAA2
+FAA3;753B;753B;753B;753B; # (画; 画; 画; 画; 画; ) CJK COMPATIBILITY IDEOGRAPH-FAA3
+FAA4;761D;761D;761D;761D; # (瘝; ç˜; ç˜; ç˜; ç˜; ) CJK COMPATIBILITY IDEOGRAPH-FAA4
+FAA5;761F;761F;761F;761F; # (瘟; 瘟; 瘟; 瘟; 瘟; ) CJK COMPATIBILITY IDEOGRAPH-FAA5
+FAA6;76CA;76CA;76CA;76CA; # (益; 益; 益; 益; 益; ) CJK COMPATIBILITY IDEOGRAPH-FAA6
+FAA7;76DB;76DB;76DB;76DB; # (盛; 盛; 盛; 盛; 盛; ) CJK COMPATIBILITY IDEOGRAPH-FAA7
+FAA8;76F4;76F4;76F4;76F4; # (直; 直; 直; 直; 直; ) CJK COMPATIBILITY IDEOGRAPH-FAA8
+FAA9;774A;774A;774A;774A; # (睊; çŠ; çŠ; çŠ; çŠ; ) CJK COMPATIBILITY IDEOGRAPH-FAA9
+FAAA;7740;7740;7740;7740; # (着; ç€; ç€; ç€; ç€; ) CJK COMPATIBILITY IDEOGRAPH-FAAA
+FAAB;78CC;78CC;78CC;78CC; # (磌; 磌; 磌; 磌; 磌; ) CJK COMPATIBILITY IDEOGRAPH-FAAB
+FAAC;7AB1;7AB1;7AB1;7AB1; # (窱; 窱; 窱; 窱; 窱; ) CJK COMPATIBILITY IDEOGRAPH-FAAC
+FAAD;7BC0;7BC0;7BC0;7BC0; # (節; 節; 節; 節; 節; ) CJK COMPATIBILITY IDEOGRAPH-FAAD
+FAAE;7C7B;7C7B;7C7B;7C7B; # (类; 类; 类; 类; 类; ) CJK COMPATIBILITY IDEOGRAPH-FAAE
+FAAF;7D5B;7D5B;7D5B;7D5B; # (絛; 絛; 絛; 絛; 絛; ) CJK COMPATIBILITY IDEOGRAPH-FAAF
+FAB0;7DF4;7DF4;7DF4;7DF4; # (練; 練; 練; 練; 練; ) CJK COMPATIBILITY IDEOGRAPH-FAB0
+FAB1;7F3E;7F3E;7F3E;7F3E; # (缾; 缾; 缾; 缾; 缾; ) CJK COMPATIBILITY IDEOGRAPH-FAB1
+FAB2;8005;8005;8005;8005; # (者; 者; 者; 者; 者; ) CJK COMPATIBILITY IDEOGRAPH-FAB2
+FAB3;8352;8352;8352;8352; # (荒; è’; è’; è’; è’; ) CJK COMPATIBILITY IDEOGRAPH-FAB3
+FAB4;83EF;83EF;83EF;83EF; # (華; è¯; è¯; è¯; è¯; ) CJK COMPATIBILITY IDEOGRAPH-FAB4
+FAB5;8779;8779;8779;8779; # (蝹; è¹; è¹; è¹; è¹; ) CJK COMPATIBILITY IDEOGRAPH-FAB5
+FAB6;8941;8941;8941;8941; # (襁; è¥; è¥; è¥; è¥; ) CJK COMPATIBILITY IDEOGRAPH-FAB6
+FAB7;8986;8986;8986;8986; # (覆; 覆; 覆; 覆; 覆; ) CJK COMPATIBILITY IDEOGRAPH-FAB7
+FAB8;8996;8996;8996;8996; # (視; 視; 視; 視; 視; ) CJK COMPATIBILITY IDEOGRAPH-FAB8
+FAB9;8ABF;8ABF;8ABF;8ABF; # (調; 調; 調; 調; 調; ) CJK COMPATIBILITY IDEOGRAPH-FAB9
+FABA;8AF8;8AF8;8AF8;8AF8; # (諸; 諸; 諸; 諸; 諸; ) CJK COMPATIBILITY IDEOGRAPH-FABA
+FABB;8ACB;8ACB;8ACB;8ACB; # (請; 請; 請; 請; 請; ) CJK COMPATIBILITY IDEOGRAPH-FABB
+FABC;8B01;8B01;8B01;8B01; # (謁; è¬; è¬; è¬; è¬; ) CJK COMPATIBILITY IDEOGRAPH-FABC
+FABD;8AFE;8AFE;8AFE;8AFE; # (諾; 諾; 諾; 諾; 諾; ) CJK COMPATIBILITY IDEOGRAPH-FABD
+FABE;8AED;8AED;8AED;8AED; # (諭; 諭; 諭; 諭; 諭; ) CJK COMPATIBILITY IDEOGRAPH-FABE
+FABF;8B39;8B39;8B39;8B39; # (謹; 謹; 謹; 謹; 謹; ) CJK COMPATIBILITY IDEOGRAPH-FABF
+FAC0;8B8A;8B8A;8B8A;8B8A; # (變; 變; 變; 變; 變; ) CJK COMPATIBILITY IDEOGRAPH-FAC0
+FAC1;8D08;8D08;8D08;8D08; # (ï«; è´ˆ; è´ˆ; è´ˆ; è´ˆ; ) CJK COMPATIBILITY IDEOGRAPH-FAC1
+FAC2;8F38;8F38;8F38;8F38; # (輸; 輸; 輸; 輸; 輸; ) CJK COMPATIBILITY IDEOGRAPH-FAC2
+FAC3;9072;9072;9072;9072; # (遲; é²; é²; é²; é²; ) CJK COMPATIBILITY IDEOGRAPH-FAC3
+FAC4;9199;9199;9199;9199; # (醙; 醙; 醙; 醙; 醙; ) CJK COMPATIBILITY IDEOGRAPH-FAC4
+FAC5;9276;9276;9276;9276; # (鉶; 鉶; 鉶; 鉶; 鉶; ) CJK COMPATIBILITY IDEOGRAPH-FAC5
+FAC6;967C;967C;967C;967C; # (陼; 陼; 陼; 陼; 陼; ) CJK COMPATIBILITY IDEOGRAPH-FAC6
+FAC7;96E3;96E3;96E3;96E3; # (難; 難; 難; 難; 難; ) CJK COMPATIBILITY IDEOGRAPH-FAC7
+FAC8;9756;9756;9756;9756; # (靖; é–; é–; é–; é–; ) CJK COMPATIBILITY IDEOGRAPH-FAC8
+FAC9;97DB;97DB;97DB;97DB; # (韛; 韛; 韛; 韛; 韛; ) CJK COMPATIBILITY IDEOGRAPH-FAC9
+FACA;97FF;97FF;97FF;97FF; # (響; 響; 響; 響; 響; ) CJK COMPATIBILITY IDEOGRAPH-FACA
+FACB;980B;980B;980B;980B; # (ï«‹; é ‹; é ‹; é ‹; é ‹; ) CJK COMPATIBILITY IDEOGRAPH-FACB
+FACC;983B;983B;983B;983B; # (頻; 頻; 頻; 頻; 頻; ) CJK COMPATIBILITY IDEOGRAPH-FACC
+FACD;9B12;9B12;9B12;9B12; # (ï«; 鬒; 鬒; 鬒; 鬒; ) CJK COMPATIBILITY IDEOGRAPH-FACD
+FACE;9F9C;9F9C;9F9C;9F9C; # (龜; 龜; 龜; 龜; 龜; ) CJK COMPATIBILITY IDEOGRAPH-FACE
+FACF;2284A;2284A;2284A;2284A; # (ï«; 𢡊; 𢡊; 𢡊; 𢡊; ) CJK COMPATIBILITY IDEOGRAPH-FACF
+FAD0;22844;22844;22844;22844; # (ï«; 𢡄; 𢡄; 𢡄; 𢡄; ) CJK COMPATIBILITY IDEOGRAPH-FAD0
+FAD1;233D5;233D5;233D5;233D5; # (ï«‘; ð£•; ð£•; ð£•; ð£•; ) CJK COMPATIBILITY IDEOGRAPH-FAD1
+FAD2;3B9D;3B9D;3B9D;3B9D; # (ï«’; ã®; ã®; ã®; ã®; ) CJK COMPATIBILITY IDEOGRAPH-FAD2
+FAD3;4018;4018;4018;4018; # (䀘; 䀘; 䀘; 䀘; 䀘; ) CJK COMPATIBILITY IDEOGRAPH-FAD3
+FAD4;4039;4039;4039;4039; # (䀹; 䀹; 䀹; 䀹; 䀹; ) CJK COMPATIBILITY IDEOGRAPH-FAD4
+FAD5;25249;25249;25249;25249; # (𥉉; 𥉉; 𥉉; 𥉉; 𥉉; ) CJK COMPATIBILITY IDEOGRAPH-FAD5
+FAD6;25CD0;25CD0;25CD0;25CD0; # (ï«–; ð¥³; ð¥³; ð¥³; ð¥³; ) CJK COMPATIBILITY IDEOGRAPH-FAD6
+FAD7;27ED3;27ED3;27ED3;27ED3; # (𧻓; 𧻓; 𧻓; 𧻓; 𧻓; ) CJK COMPATIBILITY IDEOGRAPH-FAD7
+FAD8;9F43;9F43;9F43;9F43; # (齃; 齃; 齃; 齃; 齃; ) CJK COMPATIBILITY IDEOGRAPH-FAD8
+FAD9;9F8E;9F8E;9F8E;9F8E; # (龎; 龎; 龎; 龎; 龎; ) CJK COMPATIBILITY IDEOGRAPH-FAD9
+FB00;FB00;FB00;0066 0066;0066 0066; # (ff; ff; ff; ff; ff; ) LATIN SMALL LIGATURE FF
+FB01;FB01;FB01;0066 0069;0066 0069; # (ï¬; ï¬; ï¬; fi; fi; ) LATIN SMALL LIGATURE FI
+FB02;FB02;FB02;0066 006C;0066 006C; # (fl; fl; fl; fl; fl; ) LATIN SMALL LIGATURE FL
+FB03;FB03;FB03;0066 0066 0069;0066 0066 0069; # (ffi; ffi; ffi; ffi; ffi; ) LATIN SMALL LIGATURE FFI
+FB04;FB04;FB04;0066 0066 006C;0066 0066 006C; # (ffl; ffl; ffl; ffl; ffl; ) LATIN SMALL LIGATURE FFL
+FB05;FB05;FB05;0073 0074;0073 0074; # (ſt; ſt; ſt; st; st; ) LATIN SMALL LIGATURE LONG S T
+FB06;FB06;FB06;0073 0074;0073 0074; # (st; st; st; st; st; ) LATIN SMALL LIGATURE ST
+FB13;FB13;FB13;0574 0576;0574 0576; # (ﬓ; ﬓ; ﬓ; մն; մն; ) ARMENIAN SMALL LIGATURE MEN NOW
+FB14;FB14;FB14;0574 0565;0574 0565; # (ﬔ; ﬔ; ﬔ; մե; մե; ) ARMENIAN SMALL LIGATURE MEN ECH
+FB15;FB15;FB15;0574 056B;0574 056B; # (ﬕ; ﬕ; ﬕ; մի; մի; ) ARMENIAN SMALL LIGATURE MEN INI
+FB16;FB16;FB16;057E 0576;057E 0576; # (ﬖ; ﬖ; ﬖ; վն; վն; ) ARMENIAN SMALL LIGATURE VEW NOW
+FB17;FB17;FB17;0574 056D;0574 056D; # (ﬗ; ﬗ; ﬗ; մխ; մխ; ) ARMENIAN SMALL LIGATURE MEN XEH
+FB1D;05D9 05B4;05D9 05B4;05D9 05B4;05D9 05B4; # (ï¬; י◌ִ; י◌ִ; י◌ִ; י◌ִ; ) HEBREW LETTER YOD WITH HIRIQ
+FB1F;05F2 05B7;05F2 05B7;05F2 05B7;05F2 05B7; # (ײַ; ײ◌ַ; ײ◌ַ; ײ◌ַ; ײ◌ַ; ) HEBREW LIGATURE YIDDISH YOD YOD PATAH
+FB20;FB20;FB20;05E2;05E2; # (ﬠ; ﬠ; ﬠ; ע; ע; ) HEBREW LETTER ALTERNATIVE AYIN
+FB21;FB21;FB21;05D0;05D0; # (ﬡ; ﬡ; ﬡ; ×; ×; ) HEBREW LETTER WIDE ALEF
+FB22;FB22;FB22;05D3;05D3; # (ﬢ; ﬢ; ﬢ; ד; ד; ) HEBREW LETTER WIDE DALET
+FB23;FB23;FB23;05D4;05D4; # (ﬣ; ﬣ; ﬣ; ה; ה; ) HEBREW LETTER WIDE HE
+FB24;FB24;FB24;05DB;05DB; # (ﬤ; ﬤ; ﬤ; כ; כ; ) HEBREW LETTER WIDE KAF
+FB25;FB25;FB25;05DC;05DC; # (ﬥ; ﬥ; ﬥ; ל; ל; ) HEBREW LETTER WIDE LAMED
+FB26;FB26;FB26;05DD;05DD; # (ﬦ; ﬦ; ﬦ; ×; ×; ) HEBREW LETTER WIDE FINAL MEM
+FB27;FB27;FB27;05E8;05E8; # (ﬧ; ﬧ; ﬧ; ר; ר; ) HEBREW LETTER WIDE RESH
+FB28;FB28;FB28;05EA;05EA; # (ﬨ; ﬨ; ﬨ; ת; ת; ) HEBREW LETTER WIDE TAV
+FB29;FB29;FB29;002B;002B; # (﬩; ﬩; ﬩; +; +; ) HEBREW LETTER ALTERNATIVE PLUS SIGN
+FB2A;05E9 05C1;05E9 05C1;05E9 05C1;05E9 05C1; # (שׁ; ש◌×; ש◌×; ש◌×; ש◌×; ) HEBREW LETTER SHIN WITH SHIN DOT
+FB2B;05E9 05C2;05E9 05C2;05E9 05C2;05E9 05C2; # (שׂ; ש◌ׂ; ש◌ׂ; ש◌ׂ; ש◌ׂ; ) HEBREW LETTER SHIN WITH SIN DOT
+FB2C;05E9 05BC 05C1;05E9 05BC 05C1;05E9 05BC 05C1;05E9 05BC 05C1; # (שּׁ; ש◌ּ◌×; ש◌ּ◌×; ש◌ּ◌×; ש◌ּ◌×; ) HEBREW LETTER SHIN WITH DAGESH AND SHIN DOT
+FB2D;05E9 05BC 05C2;05E9 05BC 05C2;05E9 05BC 05C2;05E9 05BC 05C2; # (שּׂ; ש◌ּ◌ׂ; ש◌ּ◌ׂ; ש◌ּ◌ׂ; ש◌ּ◌ׂ; ) HEBREW LETTER SHIN WITH DAGESH AND SIN DOT
+FB2E;05D0 05B7;05D0 05B7;05D0 05B7;05D0 05B7; # (אַ; ×◌ַ; ×◌ַ; ×◌ַ; ×◌ַ; ) HEBREW LETTER ALEF WITH PATAH
+FB2F;05D0 05B8;05D0 05B8;05D0 05B8;05D0 05B8; # (אָ; ×◌ָ; ×◌ָ; ×◌ָ; ×◌ָ; ) HEBREW LETTER ALEF WITH QAMATS
+FB30;05D0 05BC;05D0 05BC;05D0 05BC;05D0 05BC; # (אּ; ×◌ּ; ×◌ּ; ×◌ּ; ×◌ּ; ) HEBREW LETTER ALEF WITH MAPIQ
+FB31;05D1 05BC;05D1 05BC;05D1 05BC;05D1 05BC; # (בּ; ב◌ּ; ב◌ּ; ב◌ּ; ב◌ּ; ) HEBREW LETTER BET WITH DAGESH
+FB32;05D2 05BC;05D2 05BC;05D2 05BC;05D2 05BC; # (גּ; ג◌ּ; ג◌ּ; ג◌ּ; ג◌ּ; ) HEBREW LETTER GIMEL WITH DAGESH
+FB33;05D3 05BC;05D3 05BC;05D3 05BC;05D3 05BC; # (דּ; ד◌ּ; ד◌ּ; ד◌ּ; ד◌ּ; ) HEBREW LETTER DALET WITH DAGESH
+FB34;05D4 05BC;05D4 05BC;05D4 05BC;05D4 05BC; # (הּ; ה◌ּ; ה◌ּ; ה◌ּ; ה◌ּ; ) HEBREW LETTER HE WITH MAPIQ
+FB35;05D5 05BC;05D5 05BC;05D5 05BC;05D5 05BC; # (וּ; ו◌ּ; ו◌ּ; ו◌ּ; ו◌ּ; ) HEBREW LETTER VAV WITH DAGESH
+FB36;05D6 05BC;05D6 05BC;05D6 05BC;05D6 05BC; # (זּ; ז◌ּ; ז◌ּ; ז◌ּ; ז◌ּ; ) HEBREW LETTER ZAYIN WITH DAGESH
+FB38;05D8 05BC;05D8 05BC;05D8 05BC;05D8 05BC; # (טּ; ט◌ּ; ט◌ּ; ט◌ּ; ט◌ּ; ) HEBREW LETTER TET WITH DAGESH
+FB39;05D9 05BC;05D9 05BC;05D9 05BC;05D9 05BC; # (יּ; י◌ּ; י◌ּ; י◌ּ; י◌ּ; ) HEBREW LETTER YOD WITH DAGESH
+FB3A;05DA 05BC;05DA 05BC;05DA 05BC;05DA 05BC; # (ךּ; ך◌ּ; ך◌ּ; ך◌ּ; ך◌ּ; ) HEBREW LETTER FINAL KAF WITH DAGESH
+FB3B;05DB 05BC;05DB 05BC;05DB 05BC;05DB 05BC; # (כּ; כ◌ּ; כ◌ּ; כ◌ּ; כ◌ּ; ) HEBREW LETTER KAF WITH DAGESH
+FB3C;05DC 05BC;05DC 05BC;05DC 05BC;05DC 05BC; # (לּ; ל◌ּ; ל◌ּ; ל◌ּ; ל◌ּ; ) HEBREW LETTER LAMED WITH DAGESH
+FB3E;05DE 05BC;05DE 05BC;05DE 05BC;05DE 05BC; # (מּ; מ◌ּ; מ◌ּ; מ◌ּ; מ◌ּ; ) HEBREW LETTER MEM WITH DAGESH
+FB40;05E0 05BC;05E0 05BC;05E0 05BC;05E0 05BC; # (נּ; נ◌ּ; נ◌ּ; נ◌ּ; נ◌ּ; ) HEBREW LETTER NUN WITH DAGESH
+FB41;05E1 05BC;05E1 05BC;05E1 05BC;05E1 05BC; # (ï­; ס◌ּ; ס◌ּ; ס◌ּ; ס◌ּ; ) HEBREW LETTER SAMEKH WITH DAGESH
+FB43;05E3 05BC;05E3 05BC;05E3 05BC;05E3 05BC; # (ףּ; ף◌ּ; ף◌ּ; ף◌ּ; ף◌ּ; ) HEBREW LETTER FINAL PE WITH DAGESH
+FB44;05E4 05BC;05E4 05BC;05E4 05BC;05E4 05BC; # (פּ; פ◌ּ; פ◌ּ; פ◌ּ; פ◌ּ; ) HEBREW LETTER PE WITH DAGESH
+FB46;05E6 05BC;05E6 05BC;05E6 05BC;05E6 05BC; # (צּ; צ◌ּ; צ◌ּ; צ◌ּ; צ◌ּ; ) HEBREW LETTER TSADI WITH DAGESH
+FB47;05E7 05BC;05E7 05BC;05E7 05BC;05E7 05BC; # (קּ; ק◌ּ; ק◌ּ; ק◌ּ; ק◌ּ; ) HEBREW LETTER QOF WITH DAGESH
+FB48;05E8 05BC;05E8 05BC;05E8 05BC;05E8 05BC; # (רּ; ר◌ּ; ר◌ּ; ר◌ּ; ר◌ּ; ) HEBREW LETTER RESH WITH DAGESH
+FB49;05E9 05BC;05E9 05BC;05E9 05BC;05E9 05BC; # (שּ; ש◌ּ; ש◌ּ; ש◌ּ; ש◌ּ; ) HEBREW LETTER SHIN WITH DAGESH
+FB4A;05EA 05BC;05EA 05BC;05EA 05BC;05EA 05BC; # (תּ; ת◌ּ; ת◌ּ; ת◌ּ; ת◌ּ; ) HEBREW LETTER TAV WITH DAGESH
+FB4B;05D5 05B9;05D5 05B9;05D5 05B9;05D5 05B9; # (וֹ; ו◌ֹ; ו◌ֹ; ו◌ֹ; ו◌ֹ; ) HEBREW LETTER VAV WITH HOLAM
+FB4C;05D1 05BF;05D1 05BF;05D1 05BF;05D1 05BF; # (בֿ; ב◌ֿ; ב◌ֿ; ב◌ֿ; ב◌ֿ; ) HEBREW LETTER BET WITH RAFE
+FB4D;05DB 05BF;05DB 05BF;05DB 05BF;05DB 05BF; # (ï­; כ◌ֿ; כ◌ֿ; כ◌ֿ; כ◌ֿ; ) HEBREW LETTER KAF WITH RAFE
+FB4E;05E4 05BF;05E4 05BF;05E4 05BF;05E4 05BF; # (פֿ; פ◌ֿ; פ◌ֿ; פ◌ֿ; פ◌ֿ; ) HEBREW LETTER PE WITH RAFE
+FB4F;FB4F;FB4F;05D0 05DC;05D0 05DC; # (ï­; ï­; ï­; ×ל; ×ל; ) HEBREW LIGATURE ALEF LAMED
+FB50;FB50;FB50;0671;0671; # (ï­; ï­; ï­; Ù±; Ù±; ) ARABIC LETTER ALEF WASLA ISOLATED FORM
+FB51;FB51;FB51;0671;0671; # (ï­‘; ï­‘; ï­‘; Ù±; Ù±; ) ARABIC LETTER ALEF WASLA FINAL FORM
+FB52;FB52;FB52;067B;067B; # (ï­’; ï­’; ï­’; Ù»; Ù»; ) ARABIC LETTER BEEH ISOLATED FORM
+FB53;FB53;FB53;067B;067B; # (ï­“; ï­“; ï­“; Ù»; Ù»; ) ARABIC LETTER BEEH FINAL FORM
+FB54;FB54;FB54;067B;067B; # (ï­”; ï­”; ï­”; Ù»; Ù»; ) ARABIC LETTER BEEH INITIAL FORM
+FB55;FB55;FB55;067B;067B; # (ï­•; ï­•; ï­•; Ù»; Ù»; ) ARABIC LETTER BEEH MEDIAL FORM
+FB56;FB56;FB56;067E;067E; # (ï­–; ï­–; ï­–; Ù¾; Ù¾; ) ARABIC LETTER PEH ISOLATED FORM
+FB57;FB57;FB57;067E;067E; # (ï­—; ï­—; ï­—; Ù¾; Ù¾; ) ARABIC LETTER PEH FINAL FORM
+FB58;FB58;FB58;067E;067E; # (ï­˜; ï­˜; ï­˜; Ù¾; Ù¾; ) ARABIC LETTER PEH INITIAL FORM
+FB59;FB59;FB59;067E;067E; # (ï­™; ï­™; ï­™; Ù¾; Ù¾; ) ARABIC LETTER PEH MEDIAL FORM
+FB5A;FB5A;FB5A;0680;0680; # (ï­š; ï­š; ï­š; Ú€; Ú€; ) ARABIC LETTER BEHEH ISOLATED FORM
+FB5B;FB5B;FB5B;0680;0680; # (ï­›; ï­›; ï­›; Ú€; Ú€; ) ARABIC LETTER BEHEH FINAL FORM
+FB5C;FB5C;FB5C;0680;0680; # (ﭜ; ﭜ; ﭜ; ڀ; ڀ; ) ARABIC LETTER BEHEH INITIAL FORM
+FB5D;FB5D;FB5D;0680;0680; # (ï­; ï­; ï­; Ú€; Ú€; ) ARABIC LETTER BEHEH MEDIAL FORM
+FB5E;FB5E;FB5E;067A;067A; # (ï­ž; ï­ž; ï­ž; Ùº; Ùº; ) ARABIC LETTER TTEHEH ISOLATED FORM
+FB5F;FB5F;FB5F;067A;067A; # (ï­Ÿ; ï­Ÿ; ï­Ÿ; Ùº; Ùº; ) ARABIC LETTER TTEHEH FINAL FORM
+FB60;FB60;FB60;067A;067A; # (ï­ ; ï­ ; ï­ ; Ùº; Ùº; ) ARABIC LETTER TTEHEH INITIAL FORM
+FB61;FB61;FB61;067A;067A; # (ï­¡; ï­¡; ï­¡; Ùº; Ùº; ) ARABIC LETTER TTEHEH MEDIAL FORM
+FB62;FB62;FB62;067F;067F; # (ï­¢; ï­¢; ï­¢; Ù¿; Ù¿; ) ARABIC LETTER TEHEH ISOLATED FORM
+FB63;FB63;FB63;067F;067F; # (ï­£; ï­£; ï­£; Ù¿; Ù¿; ) ARABIC LETTER TEHEH FINAL FORM
+FB64;FB64;FB64;067F;067F; # (ï­¤; ï­¤; ï­¤; Ù¿; Ù¿; ) ARABIC LETTER TEHEH INITIAL FORM
+FB65;FB65;FB65;067F;067F; # (ï­¥; ï­¥; ï­¥; Ù¿; Ù¿; ) ARABIC LETTER TEHEH MEDIAL FORM
+FB66;FB66;FB66;0679;0679; # (ï­¦; ï­¦; ï­¦; Ù¹; Ù¹; ) ARABIC LETTER TTEH ISOLATED FORM
+FB67;FB67;FB67;0679;0679; # (ï­§; ï­§; ï­§; Ù¹; Ù¹; ) ARABIC LETTER TTEH FINAL FORM
+FB68;FB68;FB68;0679;0679; # (ï­¨; ï­¨; ï­¨; Ù¹; Ù¹; ) ARABIC LETTER TTEH INITIAL FORM
+FB69;FB69;FB69;0679;0679; # (ï­©; ï­©; ï­©; Ù¹; Ù¹; ) ARABIC LETTER TTEH MEDIAL FORM
+FB6A;FB6A;FB6A;06A4;06A4; # (ï­ª; ï­ª; ï­ª; Ú¤; Ú¤; ) ARABIC LETTER VEH ISOLATED FORM
+FB6B;FB6B;FB6B;06A4;06A4; # (ï­«; ï­«; ï­«; Ú¤; Ú¤; ) ARABIC LETTER VEH FINAL FORM
+FB6C;FB6C;FB6C;06A4;06A4; # (ï­¬; ï­¬; ï­¬; Ú¤; Ú¤; ) ARABIC LETTER VEH INITIAL FORM
+FB6D;FB6D;FB6D;06A4;06A4; # (ï­­; ï­­; ï­­; Ú¤; Ú¤; ) ARABIC LETTER VEH MEDIAL FORM
+FB6E;FB6E;FB6E;06A6;06A6; # (ï­®; ï­®; ï­®; Ú¦; Ú¦; ) ARABIC LETTER PEHEH ISOLATED FORM
+FB6F;FB6F;FB6F;06A6;06A6; # (ï­¯; ï­¯; ï­¯; Ú¦; Ú¦; ) ARABIC LETTER PEHEH FINAL FORM
+FB70;FB70;FB70;06A6;06A6; # (ï­°; ï­°; ï­°; Ú¦; Ú¦; ) ARABIC LETTER PEHEH INITIAL FORM
+FB71;FB71;FB71;06A6;06A6; # (ï­±; ï­±; ï­±; Ú¦; Ú¦; ) ARABIC LETTER PEHEH MEDIAL FORM
+FB72;FB72;FB72;0684;0684; # (ï­²; ï­²; ï­²; Ú„; Ú„; ) ARABIC LETTER DYEH ISOLATED FORM
+FB73;FB73;FB73;0684;0684; # (ï­³; ï­³; ï­³; Ú„; Ú„; ) ARABIC LETTER DYEH FINAL FORM
+FB74;FB74;FB74;0684;0684; # (ï­´; ï­´; ï­´; Ú„; Ú„; ) ARABIC LETTER DYEH INITIAL FORM
+FB75;FB75;FB75;0684;0684; # (ï­µ; ï­µ; ï­µ; Ú„; Ú„; ) ARABIC LETTER DYEH MEDIAL FORM
+FB76;FB76;FB76;0683;0683; # (ï­¶; ï­¶; ï­¶; Úƒ; Úƒ; ) ARABIC LETTER NYEH ISOLATED FORM
+FB77;FB77;FB77;0683;0683; # (ï­·; ï­·; ï­·; Úƒ; Úƒ; ) ARABIC LETTER NYEH FINAL FORM
+FB78;FB78;FB78;0683;0683; # (ï­¸; ï­¸; ï­¸; Úƒ; Úƒ; ) ARABIC LETTER NYEH INITIAL FORM
+FB79;FB79;FB79;0683;0683; # (ï­¹; ï­¹; ï­¹; Úƒ; Úƒ; ) ARABIC LETTER NYEH MEDIAL FORM
+FB7A;FB7A;FB7A;0686;0686; # (ï­º; ï­º; ï­º; Ú†; Ú†; ) ARABIC LETTER TCHEH ISOLATED FORM
+FB7B;FB7B;FB7B;0686;0686; # (ï­»; ï­»; ï­»; Ú†; Ú†; ) ARABIC LETTER TCHEH FINAL FORM
+FB7C;FB7C;FB7C;0686;0686; # (ï­¼; ï­¼; ï­¼; Ú†; Ú†; ) ARABIC LETTER TCHEH INITIAL FORM
+FB7D;FB7D;FB7D;0686;0686; # (ï­½; ï­½; ï­½; Ú†; Ú†; ) ARABIC LETTER TCHEH MEDIAL FORM
+FB7E;FB7E;FB7E;0687;0687; # (ï­¾; ï­¾; ï­¾; Ú‡; Ú‡; ) ARABIC LETTER TCHEHEH ISOLATED FORM
+FB7F;FB7F;FB7F;0687;0687; # (ï­¿; ï­¿; ï­¿; Ú‡; Ú‡; ) ARABIC LETTER TCHEHEH FINAL FORM
+FB80;FB80;FB80;0687;0687; # (ﮀ; ﮀ; ﮀ; ڇ; ڇ; ) ARABIC LETTER TCHEHEH INITIAL FORM
+FB81;FB81;FB81;0687;0687; # (ï®; ï®; ï®; Ú‡; Ú‡; ) ARABIC LETTER TCHEHEH MEDIAL FORM
+FB82;FB82;FB82;068D;068D; # (ﮂ; ﮂ; ﮂ; Ú; Ú; ) ARABIC LETTER DDAHAL ISOLATED FORM
+FB83;FB83;FB83;068D;068D; # (ﮃ; ﮃ; ﮃ; Ú; Ú; ) ARABIC LETTER DDAHAL FINAL FORM
+FB84;FB84;FB84;068C;068C; # (ﮄ; ﮄ; ﮄ; ڌ; ڌ; ) ARABIC LETTER DAHAL ISOLATED FORM
+FB85;FB85;FB85;068C;068C; # (ﮅ; ﮅ; ﮅ; ڌ; ڌ; ) ARABIC LETTER DAHAL FINAL FORM
+FB86;FB86;FB86;068E;068E; # (ﮆ; ﮆ; ﮆ; ڎ; ڎ; ) ARABIC LETTER DUL ISOLATED FORM
+FB87;FB87;FB87;068E;068E; # (ﮇ; ﮇ; ﮇ; ڎ; ڎ; ) ARABIC LETTER DUL FINAL FORM
+FB88;FB88;FB88;0688;0688; # (ﮈ; ﮈ; ﮈ; ڈ; ڈ; ) ARABIC LETTER DDAL ISOLATED FORM
+FB89;FB89;FB89;0688;0688; # (ﮉ; ﮉ; ﮉ; ڈ; ڈ; ) ARABIC LETTER DDAL FINAL FORM
+FB8A;FB8A;FB8A;0698;0698; # (ﮊ; ﮊ; ﮊ; ژ; ژ; ) ARABIC LETTER JEH ISOLATED FORM
+FB8B;FB8B;FB8B;0698;0698; # (ﮋ; ﮋ; ﮋ; ژ; ژ; ) ARABIC LETTER JEH FINAL FORM
+FB8C;FB8C;FB8C;0691;0691; # (ﮌ; ﮌ; ﮌ; ڑ; ڑ; ) ARABIC LETTER RREH ISOLATED FORM
+FB8D;FB8D;FB8D;0691;0691; # (ï®; ï®; ï®; Ú‘; Ú‘; ) ARABIC LETTER RREH FINAL FORM
+FB8E;FB8E;FB8E;06A9;06A9; # (ﮎ; ﮎ; ﮎ; ک; ک; ) ARABIC LETTER KEHEH ISOLATED FORM
+FB8F;FB8F;FB8F;06A9;06A9; # (ï®; ï®; ï®; Ú©; Ú©; ) ARABIC LETTER KEHEH FINAL FORM
+FB90;FB90;FB90;06A9;06A9; # (ï®; ï®; ï®; Ú©; Ú©; ) ARABIC LETTER KEHEH INITIAL FORM
+FB91;FB91;FB91;06A9;06A9; # (ﮑ; ﮑ; ﮑ; ک; ک; ) ARABIC LETTER KEHEH MEDIAL FORM
+FB92;FB92;FB92;06AF;06AF; # (ï®’; ï®’; ï®’; Ú¯; Ú¯; ) ARABIC LETTER GAF ISOLATED FORM
+FB93;FB93;FB93;06AF;06AF; # (ﮓ; ﮓ; ﮓ; گ; گ; ) ARABIC LETTER GAF FINAL FORM
+FB94;FB94;FB94;06AF;06AF; # (ï®”; ï®”; ï®”; Ú¯; Ú¯; ) ARABIC LETTER GAF INITIAL FORM
+FB95;FB95;FB95;06AF;06AF; # (ﮕ; ﮕ; ﮕ; گ; گ; ) ARABIC LETTER GAF MEDIAL FORM
+FB96;FB96;FB96;06B3;06B3; # (ï®–; ï®–; ï®–; Ú³; Ú³; ) ARABIC LETTER GUEH ISOLATED FORM
+FB97;FB97;FB97;06B3;06B3; # (ï®—; ï®—; ï®—; Ú³; Ú³; ) ARABIC LETTER GUEH FINAL FORM
+FB98;FB98;FB98;06B3;06B3; # (ﮘ; ﮘ; ﮘ; ڳ; ڳ; ) ARABIC LETTER GUEH INITIAL FORM
+FB99;FB99;FB99;06B3;06B3; # (ï®™; ï®™; ï®™; Ú³; Ú³; ) ARABIC LETTER GUEH MEDIAL FORM
+FB9A;FB9A;FB9A;06B1;06B1; # (ﮚ; ﮚ; ﮚ; ڱ; ڱ; ) ARABIC LETTER NGOEH ISOLATED FORM
+FB9B;FB9B;FB9B;06B1;06B1; # (ï®›; ï®›; ï®›; Ú±; Ú±; ) ARABIC LETTER NGOEH FINAL FORM
+FB9C;FB9C;FB9C;06B1;06B1; # (ﮜ; ﮜ; ﮜ; ڱ; ڱ; ) ARABIC LETTER NGOEH INITIAL FORM
+FB9D;FB9D;FB9D;06B1;06B1; # (ï®; ï®; ï®; Ú±; Ú±; ) ARABIC LETTER NGOEH MEDIAL FORM
+FB9E;FB9E;FB9E;06BA;06BA; # (ﮞ; ﮞ; ﮞ; ں; ں; ) ARABIC LETTER NOON GHUNNA ISOLATED FORM
+FB9F;FB9F;FB9F;06BA;06BA; # (ﮟ; ﮟ; ﮟ; ں; ں; ) ARABIC LETTER NOON GHUNNA FINAL FORM
+FBA0;FBA0;FBA0;06BB;06BB; # (ï® ; ï® ; ï® ; Ú»; Ú»; ) ARABIC LETTER RNOON ISOLATED FORM
+FBA1;FBA1;FBA1;06BB;06BB; # (ﮡ; ﮡ; ﮡ; ڻ; ڻ; ) ARABIC LETTER RNOON FINAL FORM
+FBA2;FBA2;FBA2;06BB;06BB; # (ﮢ; ﮢ; ﮢ; ڻ; ڻ; ) ARABIC LETTER RNOON INITIAL FORM
+FBA3;FBA3;FBA3;06BB;06BB; # (ﮣ; ﮣ; ﮣ; ڻ; ڻ; ) ARABIC LETTER RNOON MEDIAL FORM
+FBA4;FBA4;FBA4;06C0;06D5 0654; # (ﮤ; ﮤ; ﮤ; ۀ; ە◌ٔ; ) ARABIC LETTER HEH WITH YEH ABOVE ISOLATED FORM
+FBA5;FBA5;FBA5;06C0;06D5 0654; # (ﮥ; ﮥ; ﮥ; ۀ; ە◌ٔ; ) ARABIC LETTER HEH WITH YEH ABOVE FINAL FORM
+FBA6;FBA6;FBA6;06C1;06C1; # (ﮦ; ﮦ; ﮦ; Û; Û; ) ARABIC LETTER HEH GOAL ISOLATED FORM
+FBA7;FBA7;FBA7;06C1;06C1; # (ﮧ; ﮧ; ﮧ; Û; Û; ) ARABIC LETTER HEH GOAL FINAL FORM
+FBA8;FBA8;FBA8;06C1;06C1; # (ﮨ; ﮨ; ﮨ; Û; Û; ) ARABIC LETTER HEH GOAL INITIAL FORM
+FBA9;FBA9;FBA9;06C1;06C1; # (ﮩ; ﮩ; ﮩ; Û; Û; ) ARABIC LETTER HEH GOAL MEDIAL FORM
+FBAA;FBAA;FBAA;06BE;06BE; # (ﮪ; ﮪ; ﮪ; ھ; ھ; ) ARABIC LETTER HEH DOACHASHMEE ISOLATED FORM
+FBAB;FBAB;FBAB;06BE;06BE; # (ﮫ; ﮫ; ﮫ; ھ; ھ; ) ARABIC LETTER HEH DOACHASHMEE FINAL FORM
+FBAC;FBAC;FBAC;06BE;06BE; # (ﮬ; ﮬ; ﮬ; ھ; ھ; ) ARABIC LETTER HEH DOACHASHMEE INITIAL FORM
+FBAD;FBAD;FBAD;06BE;06BE; # (ï®­; ï®­; ï®­; Ú¾; Ú¾; ) ARABIC LETTER HEH DOACHASHMEE MEDIAL FORM
+FBAE;FBAE;FBAE;06D2;06D2; # (ï®®; ï®®; ï®®; Û’; Û’; ) ARABIC LETTER YEH BARREE ISOLATED FORM
+FBAF;FBAF;FBAF;06D2;06D2; # (ﮯ; ﮯ; ﮯ; ے; ے; ) ARABIC LETTER YEH BARREE FINAL FORM
+FBB0;FBB0;FBB0;06D3;06D2 0654; # (ﮰ; ﮰ; ﮰ; ۓ; ے◌ٔ; ) ARABIC LETTER YEH BARREE WITH HAMZA ABOVE ISOLATED FORM
+FBB1;FBB1;FBB1;06D3;06D2 0654; # (ﮱ; ﮱ; ﮱ; ۓ; ے◌ٔ; ) ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM
+FBD3;FBD3;FBD3;06AD;06AD; # (ﯓ; ﯓ; ﯓ; ڭ; ڭ; ) ARABIC LETTER NG ISOLATED FORM
+FBD4;FBD4;FBD4;06AD;06AD; # (ﯔ; ﯔ; ﯔ; ڭ; ڭ; ) ARABIC LETTER NG FINAL FORM
+FBD5;FBD5;FBD5;06AD;06AD; # (ﯕ; ﯕ; ﯕ; ڭ; ڭ; ) ARABIC LETTER NG INITIAL FORM
+FBD6;FBD6;FBD6;06AD;06AD; # (ﯖ; ﯖ; ﯖ; ڭ; ڭ; ) ARABIC LETTER NG MEDIAL FORM
+FBD7;FBD7;FBD7;06C7;06C7; # (ﯗ; ﯗ; ﯗ; ۇ; ۇ; ) ARABIC LETTER U ISOLATED FORM
+FBD8;FBD8;FBD8;06C7;06C7; # (ﯘ; ﯘ; ﯘ; ۇ; ۇ; ) ARABIC LETTER U FINAL FORM
+FBD9;FBD9;FBD9;06C6;06C6; # (ﯙ; ﯙ; ﯙ; ۆ; ۆ; ) ARABIC LETTER OE ISOLATED FORM
+FBDA;FBDA;FBDA;06C6;06C6; # (ﯚ; ﯚ; ﯚ; ۆ; ۆ; ) ARABIC LETTER OE FINAL FORM
+FBDB;FBDB;FBDB;06C8;06C8; # (ﯛ; ﯛ; ﯛ; ۈ; ۈ; ) ARABIC LETTER YU ISOLATED FORM
+FBDC;FBDC;FBDC;06C8;06C8; # (ﯜ; ﯜ; ﯜ; ۈ; ۈ; ) ARABIC LETTER YU FINAL FORM
+FBDD;FBDD;FBDD;06C7 0674;06C7 0674; # (ï¯; ï¯; ï¯; Û‡Ù´; Û‡Ù´; ) ARABIC LETTER U WITH HAMZA ABOVE ISOLATED FORM
+FBDE;FBDE;FBDE;06CB;06CB; # (ﯞ; ﯞ; ﯞ; ۋ; ۋ; ) ARABIC LETTER VE ISOLATED FORM
+FBDF;FBDF;FBDF;06CB;06CB; # (ﯟ; ﯟ; ﯟ; ۋ; ۋ; ) ARABIC LETTER VE FINAL FORM
+FBE0;FBE0;FBE0;06C5;06C5; # (ﯠ; ﯠ; ﯠ; ۅ; ۅ; ) ARABIC LETTER KIRGHIZ OE ISOLATED FORM
+FBE1;FBE1;FBE1;06C5;06C5; # (ﯡ; ﯡ; ﯡ; ۅ; ۅ; ) ARABIC LETTER KIRGHIZ OE FINAL FORM
+FBE2;FBE2;FBE2;06C9;06C9; # (ﯢ; ﯢ; ﯢ; ۉ; ۉ; ) ARABIC LETTER KIRGHIZ YU ISOLATED FORM
+FBE3;FBE3;FBE3;06C9;06C9; # (ﯣ; ﯣ; ﯣ; ۉ; ۉ; ) ARABIC LETTER KIRGHIZ YU FINAL FORM
+FBE4;FBE4;FBE4;06D0;06D0; # (ﯤ; ﯤ; ﯤ; Û; Û; ) ARABIC LETTER E ISOLATED FORM
+FBE5;FBE5;FBE5;06D0;06D0; # (ﯥ; ﯥ; ﯥ; Û; Û; ) ARABIC LETTER E FINAL FORM
+FBE6;FBE6;FBE6;06D0;06D0; # (ﯦ; ﯦ; ﯦ; Û; Û; ) ARABIC LETTER E INITIAL FORM
+FBE7;FBE7;FBE7;06D0;06D0; # (ﯧ; ﯧ; ﯧ; Û; Û; ) ARABIC LETTER E MEDIAL FORM
+FBE8;FBE8;FBE8;0649;0649; # (ﯨ; ﯨ; ﯨ; ى; ى; ) ARABIC LETTER UIGHUR KAZAKH KIRGHIZ ALEF MAKSURA INITIAL FORM
+FBE9;FBE9;FBE9;0649;0649; # (ﯩ; ﯩ; ﯩ; ى; ى; ) ARABIC LETTER UIGHUR KAZAKH KIRGHIZ ALEF MAKSURA MEDIAL FORM
+FBEA;FBEA;FBEA;0626 0627;064A 0654 0627; # (ﯪ; ﯪ; ﯪ; ئا; ي◌ٔا; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF ISOLATED FORM
+FBEB;FBEB;FBEB;0626 0627;064A 0654 0627; # (ﯫ; ﯫ; ﯫ; ئا; ي◌ٔا; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF FINAL FORM
+FBEC;FBEC;FBEC;0626 06D5;064A 0654 06D5; # (ﯬ; ﯬ; ﯬ; ئە; ي◌ٔە; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH AE ISOLATED FORM
+FBED;FBED;FBED;0626 06D5;064A 0654 06D5; # (ﯭ; ﯭ; ﯭ; ئە; ي◌ٔە; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH AE FINAL FORM
+FBEE;FBEE;FBEE;0626 0648;064A 0654 0648; # (ﯮ; ﯮ; ﯮ; ئو; ي◌ٔو; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH WAW ISOLATED FORM
+FBEF;FBEF;FBEF;0626 0648;064A 0654 0648; # (ﯯ; ﯯ; ﯯ; ئو; ي◌ٔو; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH WAW FINAL FORM
+FBF0;FBF0;FBF0;0626 06C7;064A 0654 06C7; # (ﯰ; ﯰ; ﯰ; ئۇ; ي◌ٔۇ; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH U ISOLATED FORM
+FBF1;FBF1;FBF1;0626 06C7;064A 0654 06C7; # (ﯱ; ﯱ; ﯱ; ئۇ; ي◌ٔۇ; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH U FINAL FORM
+FBF2;FBF2;FBF2;0626 06C6;064A 0654 06C6; # (ﯲ; ﯲ; ﯲ; ئۆ; ي◌ٔۆ; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH OE ISOLATED FORM
+FBF3;FBF3;FBF3;0626 06C6;064A 0654 06C6; # (ﯳ; ﯳ; ﯳ; ئۆ; ي◌ٔۆ; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH OE FINAL FORM
+FBF4;FBF4;FBF4;0626 06C8;064A 0654 06C8; # (ﯴ; ﯴ; ﯴ; ئۈ; ي◌ٔۈ; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YU ISOLATED FORM
+FBF5;FBF5;FBF5;0626 06C8;064A 0654 06C8; # (ﯵ; ﯵ; ﯵ; ئۈ; ي◌ٔۈ; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YU FINAL FORM
+FBF6;FBF6;FBF6;0626 06D0;064A 0654 06D0; # (ﯶ; ﯶ; ﯶ; ئÛ; ي◌ٔÛ; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E ISOLATED FORM
+FBF7;FBF7;FBF7;0626 06D0;064A 0654 06D0; # (ﯷ; ﯷ; ﯷ; ئÛ; ي◌ٔÛ; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E FINAL FORM
+FBF8;FBF8;FBF8;0626 06D0;064A 0654 06D0; # (ﯸ; ﯸ; ﯸ; ئÛ; ي◌ٔÛ; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E INITIAL FORM
+FBF9;FBF9;FBF9;0626 0649;064A 0654 0649; # (ﯹ; ﯹ; ﯹ; ئى; ي◌ٔى; ) ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA ISOLATED FORM
+FBFA;FBFA;FBFA;0626 0649;064A 0654 0649; # (ﯺ; ﯺ; ﯺ; ئى; ي◌ٔى; ) ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA FINAL FORM
+FBFB;FBFB;FBFB;0626 0649;064A 0654 0649; # (ﯻ; ﯻ; ﯻ; ئى; ي◌ٔى; ) ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA INITIAL FORM
+FBFC;FBFC;FBFC;06CC;06CC; # (ﯼ; ﯼ; ﯼ; ی; ی; ) ARABIC LETTER FARSI YEH ISOLATED FORM
+FBFD;FBFD;FBFD;06CC;06CC; # (ﯽ; ﯽ; ﯽ; ی; ی; ) ARABIC LETTER FARSI YEH FINAL FORM
+FBFE;FBFE;FBFE;06CC;06CC; # (ﯾ; ﯾ; ﯾ; ی; ی; ) ARABIC LETTER FARSI YEH INITIAL FORM
+FBFF;FBFF;FBFF;06CC;06CC; # (ﯿ; ﯿ; ﯿ; ی; ی; ) ARABIC LETTER FARSI YEH MEDIAL FORM
+FC00;FC00;FC00;0626 062C;064A 0654 062C; # (ﰀ; ﰀ; ﰀ; ئج; ي◌ٔج; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH JEEM ISOLATED FORM
+FC01;FC01;FC01;0626 062D;064A 0654 062D; # (ï°; ï°; ï°; ئح; ي◌ٔح; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HAH ISOLATED FORM
+FC02;FC02;FC02;0626 0645;064A 0654 0645; # (ﰂ; ﰂ; ﰂ; ئم; ي◌ٔم; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM ISOLATED FORM
+FC03;FC03;FC03;0626 0649;064A 0654 0649; # (ﰃ; ﰃ; ﰃ; ئى; ي◌ٔى; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF MAKSURA ISOLATED FORM
+FC04;FC04;FC04;0626 064A;064A 0654 064A; # (ﰄ; ﰄ; ﰄ; ئي; ي◌ٔي; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YEH ISOLATED FORM
+FC05;FC05;FC05;0628 062C;0628 062C; # (ﰅ; ﰅ; ﰅ; بج; بج; ) ARABIC LIGATURE BEH WITH JEEM ISOLATED FORM
+FC06;FC06;FC06;0628 062D;0628 062D; # (ﰆ; ﰆ; ﰆ; بح; بح; ) ARABIC LIGATURE BEH WITH HAH ISOLATED FORM
+FC07;FC07;FC07;0628 062E;0628 062E; # (ﰇ; ﰇ; ﰇ; بخ; بخ; ) ARABIC LIGATURE BEH WITH KHAH ISOLATED FORM
+FC08;FC08;FC08;0628 0645;0628 0645; # (ﰈ; ﰈ; ﰈ; بم; بم; ) ARABIC LIGATURE BEH WITH MEEM ISOLATED FORM
+FC09;FC09;FC09;0628 0649;0628 0649; # (ﰉ; ﰉ; ﰉ; بى; بى; ) ARABIC LIGATURE BEH WITH ALEF MAKSURA ISOLATED FORM
+FC0A;FC0A;FC0A;0628 064A;0628 064A; # (ﰊ; ﰊ; ﰊ; بي; بي; ) ARABIC LIGATURE BEH WITH YEH ISOLATED FORM
+FC0B;FC0B;FC0B;062A 062C;062A 062C; # (ﰋ; ﰋ; ﰋ; تج; تج; ) ARABIC LIGATURE TEH WITH JEEM ISOLATED FORM
+FC0C;FC0C;FC0C;062A 062D;062A 062D; # (ﰌ; ﰌ; ﰌ; تح; تح; ) ARABIC LIGATURE TEH WITH HAH ISOLATED FORM
+FC0D;FC0D;FC0D;062A 062E;062A 062E; # (ï°; ï°; ï°; تخ; تخ; ) ARABIC LIGATURE TEH WITH KHAH ISOLATED FORM
+FC0E;FC0E;FC0E;062A 0645;062A 0645; # (ﰎ; ﰎ; ﰎ; تم; تم; ) ARABIC LIGATURE TEH WITH MEEM ISOLATED FORM
+FC0F;FC0F;FC0F;062A 0649;062A 0649; # (ï°; ï°; ï°; تى; تى; ) ARABIC LIGATURE TEH WITH ALEF MAKSURA ISOLATED FORM
+FC10;FC10;FC10;062A 064A;062A 064A; # (ï°; ï°; ï°; تي; تي; ) ARABIC LIGATURE TEH WITH YEH ISOLATED FORM
+FC11;FC11;FC11;062B 062C;062B 062C; # (ﰑ; ﰑ; ﰑ; ثج; ثج; ) ARABIC LIGATURE THEH WITH JEEM ISOLATED FORM
+FC12;FC12;FC12;062B 0645;062B 0645; # (ﰒ; ﰒ; ﰒ; ثم; ثم; ) ARABIC LIGATURE THEH WITH MEEM ISOLATED FORM
+FC13;FC13;FC13;062B 0649;062B 0649; # (ﰓ; ﰓ; ﰓ; ثى; ثى; ) ARABIC LIGATURE THEH WITH ALEF MAKSURA ISOLATED FORM
+FC14;FC14;FC14;062B 064A;062B 064A; # (ﰔ; ﰔ; ﰔ; ثي; ثي; ) ARABIC LIGATURE THEH WITH YEH ISOLATED FORM
+FC15;FC15;FC15;062C 062D;062C 062D; # (ﰕ; ﰕ; ﰕ; جح; جح; ) ARABIC LIGATURE JEEM WITH HAH ISOLATED FORM
+FC16;FC16;FC16;062C 0645;062C 0645; # (ﰖ; ﰖ; ﰖ; جم; جم; ) ARABIC LIGATURE JEEM WITH MEEM ISOLATED FORM
+FC17;FC17;FC17;062D 062C;062D 062C; # (ﰗ; ﰗ; ﰗ; حج; حج; ) ARABIC LIGATURE HAH WITH JEEM ISOLATED FORM
+FC18;FC18;FC18;062D 0645;062D 0645; # (ﰘ; ﰘ; ﰘ; حم; حم; ) ARABIC LIGATURE HAH WITH MEEM ISOLATED FORM
+FC19;FC19;FC19;062E 062C;062E 062C; # (ﰙ; ﰙ; ﰙ; خج; خج; ) ARABIC LIGATURE KHAH WITH JEEM ISOLATED FORM
+FC1A;FC1A;FC1A;062E 062D;062E 062D; # (ﰚ; ﰚ; ﰚ; خح; خح; ) ARABIC LIGATURE KHAH WITH HAH ISOLATED FORM
+FC1B;FC1B;FC1B;062E 0645;062E 0645; # (ﰛ; ﰛ; ﰛ; خم; خم; ) ARABIC LIGATURE KHAH WITH MEEM ISOLATED FORM
+FC1C;FC1C;FC1C;0633 062C;0633 062C; # (ﰜ; ﰜ; ﰜ; سج; سج; ) ARABIC LIGATURE SEEN WITH JEEM ISOLATED FORM
+FC1D;FC1D;FC1D;0633 062D;0633 062D; # (ï°; ï°; ï°; سح; سح; ) ARABIC LIGATURE SEEN WITH HAH ISOLATED FORM
+FC1E;FC1E;FC1E;0633 062E;0633 062E; # (ﰞ; ﰞ; ﰞ; سخ; سخ; ) ARABIC LIGATURE SEEN WITH KHAH ISOLATED FORM
+FC1F;FC1F;FC1F;0633 0645;0633 0645; # (ﰟ; ﰟ; ﰟ; سم; سم; ) ARABIC LIGATURE SEEN WITH MEEM ISOLATED FORM
+FC20;FC20;FC20;0635 062D;0635 062D; # (ﰠ; ﰠ; ﰠ; صح; صح; ) ARABIC LIGATURE SAD WITH HAH ISOLATED FORM
+FC21;FC21;FC21;0635 0645;0635 0645; # (ﰡ; ﰡ; ﰡ; صم; صم; ) ARABIC LIGATURE SAD WITH MEEM ISOLATED FORM
+FC22;FC22;FC22;0636 062C;0636 062C; # (ﰢ; ﰢ; ﰢ; ضج; ضج; ) ARABIC LIGATURE DAD WITH JEEM ISOLATED FORM
+FC23;FC23;FC23;0636 062D;0636 062D; # (ﰣ; ﰣ; ﰣ; ضح; ضح; ) ARABIC LIGATURE DAD WITH HAH ISOLATED FORM
+FC24;FC24;FC24;0636 062E;0636 062E; # (ﰤ; ﰤ; ﰤ; ضخ; ضخ; ) ARABIC LIGATURE DAD WITH KHAH ISOLATED FORM
+FC25;FC25;FC25;0636 0645;0636 0645; # (ﰥ; ﰥ; ﰥ; ضم; ضم; ) ARABIC LIGATURE DAD WITH MEEM ISOLATED FORM
+FC26;FC26;FC26;0637 062D;0637 062D; # (ﰦ; ﰦ; ﰦ; طح; طح; ) ARABIC LIGATURE TAH WITH HAH ISOLATED FORM
+FC27;FC27;FC27;0637 0645;0637 0645; # (ﰧ; ﰧ; ﰧ; طم; طم; ) ARABIC LIGATURE TAH WITH MEEM ISOLATED FORM
+FC28;FC28;FC28;0638 0645;0638 0645; # (ﰨ; ﰨ; ﰨ; ظم; ظم; ) ARABIC LIGATURE ZAH WITH MEEM ISOLATED FORM
+FC29;FC29;FC29;0639 062C;0639 062C; # (ﰩ; ﰩ; ﰩ; عج; عج; ) ARABIC LIGATURE AIN WITH JEEM ISOLATED FORM
+FC2A;FC2A;FC2A;0639 0645;0639 0645; # (ﰪ; ﰪ; ﰪ; عم; عم; ) ARABIC LIGATURE AIN WITH MEEM ISOLATED FORM
+FC2B;FC2B;FC2B;063A 062C;063A 062C; # (ﰫ; ﰫ; ﰫ; غج; غج; ) ARABIC LIGATURE GHAIN WITH JEEM ISOLATED FORM
+FC2C;FC2C;FC2C;063A 0645;063A 0645; # (ﰬ; ﰬ; ﰬ; غم; غم; ) ARABIC LIGATURE GHAIN WITH MEEM ISOLATED FORM
+FC2D;FC2D;FC2D;0641 062C;0641 062C; # (ï°­; ï°­; ï°­; Ùج; Ùج; ) ARABIC LIGATURE FEH WITH JEEM ISOLATED FORM
+FC2E;FC2E;FC2E;0641 062D;0641 062D; # (ï°®; ï°®; ï°®; ÙØ­; ÙØ­; ) ARABIC LIGATURE FEH WITH HAH ISOLATED FORM
+FC2F;FC2F;FC2F;0641 062E;0641 062E; # (ï°¯; ï°¯; ï°¯; ÙØ®; ÙØ®; ) ARABIC LIGATURE FEH WITH KHAH ISOLATED FORM
+FC30;FC30;FC30;0641 0645;0641 0645; # (ï°°; ï°°; ï°°; ÙÙ…; ÙÙ…; ) ARABIC LIGATURE FEH WITH MEEM ISOLATED FORM
+FC31;FC31;FC31;0641 0649;0641 0649; # (ï°±; ï°±; ï°±; ÙÙ‰; ÙÙ‰; ) ARABIC LIGATURE FEH WITH ALEF MAKSURA ISOLATED FORM
+FC32;FC32;FC32;0641 064A;0641 064A; # (ï°²; ï°²; ï°²; ÙÙŠ; ÙÙŠ; ) ARABIC LIGATURE FEH WITH YEH ISOLATED FORM
+FC33;FC33;FC33;0642 062D;0642 062D; # (ﰳ; ﰳ; ﰳ; قح; قح; ) ARABIC LIGATURE QAF WITH HAH ISOLATED FORM
+FC34;FC34;FC34;0642 0645;0642 0645; # (ﰴ; ﰴ; ﰴ; قم; قم; ) ARABIC LIGATURE QAF WITH MEEM ISOLATED FORM
+FC35;FC35;FC35;0642 0649;0642 0649; # (ﰵ; ﰵ; ﰵ; قى; قى; ) ARABIC LIGATURE QAF WITH ALEF MAKSURA ISOLATED FORM
+FC36;FC36;FC36;0642 064A;0642 064A; # (ﰶ; ﰶ; ﰶ; قي; قي; ) ARABIC LIGATURE QAF WITH YEH ISOLATED FORM
+FC37;FC37;FC37;0643 0627;0643 0627; # (ﰷ; ﰷ; ﰷ; كا; كا; ) ARABIC LIGATURE KAF WITH ALEF ISOLATED FORM
+FC38;FC38;FC38;0643 062C;0643 062C; # (ﰸ; ﰸ; ﰸ; كج; كج; ) ARABIC LIGATURE KAF WITH JEEM ISOLATED FORM
+FC39;FC39;FC39;0643 062D;0643 062D; # (ﰹ; ﰹ; ﰹ; كح; كح; ) ARABIC LIGATURE KAF WITH HAH ISOLATED FORM
+FC3A;FC3A;FC3A;0643 062E;0643 062E; # (ﰺ; ﰺ; ﰺ; كخ; كخ; ) ARABIC LIGATURE KAF WITH KHAH ISOLATED FORM
+FC3B;FC3B;FC3B;0643 0644;0643 0644; # (ﰻ; ﰻ; ﰻ; كل; كل; ) ARABIC LIGATURE KAF WITH LAM ISOLATED FORM
+FC3C;FC3C;FC3C;0643 0645;0643 0645; # (ﰼ; ﰼ; ﰼ; كم; كم; ) ARABIC LIGATURE KAF WITH MEEM ISOLATED FORM
+FC3D;FC3D;FC3D;0643 0649;0643 0649; # (ﰽ; ﰽ; ﰽ; كى; كى; ) ARABIC LIGATURE KAF WITH ALEF MAKSURA ISOLATED FORM
+FC3E;FC3E;FC3E;0643 064A;0643 064A; # (ﰾ; ﰾ; ﰾ; كي; كي; ) ARABIC LIGATURE KAF WITH YEH ISOLATED FORM
+FC3F;FC3F;FC3F;0644 062C;0644 062C; # (ﰿ; ﰿ; ﰿ; لج; لج; ) ARABIC LIGATURE LAM WITH JEEM ISOLATED FORM
+FC40;FC40;FC40;0644 062D;0644 062D; # (ﱀ; ﱀ; ﱀ; لح; لح; ) ARABIC LIGATURE LAM WITH HAH ISOLATED FORM
+FC41;FC41;FC41;0644 062E;0644 062E; # (ï±; ï±; ï±; لخ; لخ; ) ARABIC LIGATURE LAM WITH KHAH ISOLATED FORM
+FC42;FC42;FC42;0644 0645;0644 0645; # (ﱂ; ﱂ; ﱂ; لم; لم; ) ARABIC LIGATURE LAM WITH MEEM ISOLATED FORM
+FC43;FC43;FC43;0644 0649;0644 0649; # (ﱃ; ﱃ; ﱃ; لى; لى; ) ARABIC LIGATURE LAM WITH ALEF MAKSURA ISOLATED FORM
+FC44;FC44;FC44;0644 064A;0644 064A; # (ﱄ; ﱄ; ﱄ; لي; لي; ) ARABIC LIGATURE LAM WITH YEH ISOLATED FORM
+FC45;FC45;FC45;0645 062C;0645 062C; # (ﱅ; ﱅ; ﱅ; مج; مج; ) ARABIC LIGATURE MEEM WITH JEEM ISOLATED FORM
+FC46;FC46;FC46;0645 062D;0645 062D; # (ﱆ; ﱆ; ﱆ; مح; مح; ) ARABIC LIGATURE MEEM WITH HAH ISOLATED FORM
+FC47;FC47;FC47;0645 062E;0645 062E; # (ﱇ; ﱇ; ﱇ; مخ; مخ; ) ARABIC LIGATURE MEEM WITH KHAH ISOLATED FORM
+FC48;FC48;FC48;0645 0645;0645 0645; # (ﱈ; ﱈ; ﱈ; مم; مم; ) ARABIC LIGATURE MEEM WITH MEEM ISOLATED FORM
+FC49;FC49;FC49;0645 0649;0645 0649; # (ﱉ; ﱉ; ﱉ; مى; مى; ) ARABIC LIGATURE MEEM WITH ALEF MAKSURA ISOLATED FORM
+FC4A;FC4A;FC4A;0645 064A;0645 064A; # (ﱊ; ﱊ; ﱊ; مي; مي; ) ARABIC LIGATURE MEEM WITH YEH ISOLATED FORM
+FC4B;FC4B;FC4B;0646 062C;0646 062C; # (ﱋ; ﱋ; ﱋ; نج; نج; ) ARABIC LIGATURE NOON WITH JEEM ISOLATED FORM
+FC4C;FC4C;FC4C;0646 062D;0646 062D; # (ﱌ; ﱌ; ﱌ; نح; نح; ) ARABIC LIGATURE NOON WITH HAH ISOLATED FORM
+FC4D;FC4D;FC4D;0646 062E;0646 062E; # (ï±; ï±; ï±; نخ; نخ; ) ARABIC LIGATURE NOON WITH KHAH ISOLATED FORM
+FC4E;FC4E;FC4E;0646 0645;0646 0645; # (ﱎ; ﱎ; ﱎ; نم; نم; ) ARABIC LIGATURE NOON WITH MEEM ISOLATED FORM
+FC4F;FC4F;FC4F;0646 0649;0646 0649; # (ï±; ï±; ï±; نى; نى; ) ARABIC LIGATURE NOON WITH ALEF MAKSURA ISOLATED FORM
+FC50;FC50;FC50;0646 064A;0646 064A; # (ï±; ï±; ï±; ني; ني; ) ARABIC LIGATURE NOON WITH YEH ISOLATED FORM
+FC51;FC51;FC51;0647 062C;0647 062C; # (ﱑ; ﱑ; ﱑ; هج; هج; ) ARABIC LIGATURE HEH WITH JEEM ISOLATED FORM
+FC52;FC52;FC52;0647 0645;0647 0645; # (ﱒ; ﱒ; ﱒ; هم; هم; ) ARABIC LIGATURE HEH WITH MEEM ISOLATED FORM
+FC53;FC53;FC53;0647 0649;0647 0649; # (ﱓ; ﱓ; ﱓ; هى; هى; ) ARABIC LIGATURE HEH WITH ALEF MAKSURA ISOLATED FORM
+FC54;FC54;FC54;0647 064A;0647 064A; # (ﱔ; ﱔ; ﱔ; هي; هي; ) ARABIC LIGATURE HEH WITH YEH ISOLATED FORM
+FC55;FC55;FC55;064A 062C;064A 062C; # (ﱕ; ﱕ; ﱕ; يج; يج; ) ARABIC LIGATURE YEH WITH JEEM ISOLATED FORM
+FC56;FC56;FC56;064A 062D;064A 062D; # (ﱖ; ﱖ; ﱖ; يح; يح; ) ARABIC LIGATURE YEH WITH HAH ISOLATED FORM
+FC57;FC57;FC57;064A 062E;064A 062E; # (ﱗ; ﱗ; ﱗ; يخ; يخ; ) ARABIC LIGATURE YEH WITH KHAH ISOLATED FORM
+FC58;FC58;FC58;064A 0645;064A 0645; # (ﱘ; ﱘ; ﱘ; يم; يم; ) ARABIC LIGATURE YEH WITH MEEM ISOLATED FORM
+FC59;FC59;FC59;064A 0649;064A 0649; # (ﱙ; ﱙ; ﱙ; يى; يى; ) ARABIC LIGATURE YEH WITH ALEF MAKSURA ISOLATED FORM
+FC5A;FC5A;FC5A;064A 064A;064A 064A; # (ﱚ; ﱚ; ﱚ; يي; يي; ) ARABIC LIGATURE YEH WITH YEH ISOLATED FORM
+FC5B;FC5B;FC5B;0630 0670;0630 0670; # (ﱛ; ﱛ; ﱛ; ذ◌ٰ; ذ◌ٰ; ) ARABIC LIGATURE THAL WITH SUPERSCRIPT ALEF ISOLATED FORM
+FC5C;FC5C;FC5C;0631 0670;0631 0670; # (ﱜ; ﱜ; ﱜ; ر◌ٰ; ر◌ٰ; ) ARABIC LIGATURE REH WITH SUPERSCRIPT ALEF ISOLATED FORM
+FC5D;FC5D;FC5D;0649 0670;0649 0670; # (ï±; ï±; ï±; ى◌ٰ; ى◌ٰ; ) ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF ISOLATED FORM
+FC5E;FC5E;FC5E;0020 064C 0651;0020 064C 0651; # (ﱞ; ﱞ; ﱞ; ◌ٌ◌ّ; ◌ٌ◌ّ; ) ARABIC LIGATURE SHADDA WITH DAMMATAN ISOLATED FORM
+FC5F;FC5F;FC5F;0020 064D 0651;0020 064D 0651; # (ﱟ; ﱟ; ﱟ; â—ŒÙ◌ّ; â—ŒÙ◌ّ; ) ARABIC LIGATURE SHADDA WITH KASRATAN ISOLATED FORM
+FC60;FC60;FC60;0020 064E 0651;0020 064E 0651; # (ﱠ; ﱠ; ﱠ; ◌َ◌ّ; ◌َ◌ّ; ) ARABIC LIGATURE SHADDA WITH FATHA ISOLATED FORM
+FC61;FC61;FC61;0020 064F 0651;0020 064F 0651; # (ﱡ; ﱡ; ﱡ; â—ŒÙ◌ّ; â—ŒÙ◌ّ; ) ARABIC LIGATURE SHADDA WITH DAMMA ISOLATED FORM
+FC62;FC62;FC62;0020 0650 0651;0020 0650 0651; # (ï±¢; ï±¢; ï±¢; â—ŒÙ◌ّ; â—ŒÙ◌ّ; ) ARABIC LIGATURE SHADDA WITH KASRA ISOLATED FORM
+FC63;FC63;FC63;0020 0651 0670;0020 0651 0670; # (ﱣ; ﱣ; ﱣ; ◌ّ◌ٰ; ◌ّ◌ٰ; ) ARABIC LIGATURE SHADDA WITH SUPERSCRIPT ALEF ISOLATED FORM
+FC64;FC64;FC64;0626 0631;064A 0654 0631; # (ﱤ; ﱤ; ﱤ; ئر; ي◌ٔر; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH REH FINAL FORM
+FC65;FC65;FC65;0626 0632;064A 0654 0632; # (ﱥ; ﱥ; ﱥ; ئز; ي◌ٔز; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ZAIN FINAL FORM
+FC66;FC66;FC66;0626 0645;064A 0654 0645; # (ﱦ; ﱦ; ﱦ; ئم; ي◌ٔم; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM FINAL FORM
+FC67;FC67;FC67;0626 0646;064A 0654 0646; # (ﱧ; ﱧ; ﱧ; ئن; ي◌ٔن; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH NOON FINAL FORM
+FC68;FC68;FC68;0626 0649;064A 0654 0649; # (ﱨ; ﱨ; ﱨ; ئى; ي◌ٔى; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF MAKSURA FINAL FORM
+FC69;FC69;FC69;0626 064A;064A 0654 064A; # (ﱩ; ﱩ; ﱩ; ئي; ي◌ٔي; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YEH FINAL FORM
+FC6A;FC6A;FC6A;0628 0631;0628 0631; # (ﱪ; ﱪ; ﱪ; بر; بر; ) ARABIC LIGATURE BEH WITH REH FINAL FORM
+FC6B;FC6B;FC6B;0628 0632;0628 0632; # (ﱫ; ﱫ; ﱫ; بز; بز; ) ARABIC LIGATURE BEH WITH ZAIN FINAL FORM
+FC6C;FC6C;FC6C;0628 0645;0628 0645; # (ﱬ; ﱬ; ﱬ; بم; بم; ) ARABIC LIGATURE BEH WITH MEEM FINAL FORM
+FC6D;FC6D;FC6D;0628 0646;0628 0646; # (ﱭ; ﱭ; ﱭ; بن; بن; ) ARABIC LIGATURE BEH WITH NOON FINAL FORM
+FC6E;FC6E;FC6E;0628 0649;0628 0649; # (ﱮ; ﱮ; ﱮ; بى; بى; ) ARABIC LIGATURE BEH WITH ALEF MAKSURA FINAL FORM
+FC6F;FC6F;FC6F;0628 064A;0628 064A; # (ﱯ; ﱯ; ﱯ; بي; بي; ) ARABIC LIGATURE BEH WITH YEH FINAL FORM
+FC70;FC70;FC70;062A 0631;062A 0631; # (ﱰ; ﱰ; ﱰ; تر; تر; ) ARABIC LIGATURE TEH WITH REH FINAL FORM
+FC71;FC71;FC71;062A 0632;062A 0632; # (ﱱ; ﱱ; ﱱ; تز; تز; ) ARABIC LIGATURE TEH WITH ZAIN FINAL FORM
+FC72;FC72;FC72;062A 0645;062A 0645; # (ﱲ; ﱲ; ﱲ; تم; تم; ) ARABIC LIGATURE TEH WITH MEEM FINAL FORM
+FC73;FC73;FC73;062A 0646;062A 0646; # (ﱳ; ﱳ; ﱳ; تن; تن; ) ARABIC LIGATURE TEH WITH NOON FINAL FORM
+FC74;FC74;FC74;062A 0649;062A 0649; # (ﱴ; ﱴ; ﱴ; تى; تى; ) ARABIC LIGATURE TEH WITH ALEF MAKSURA FINAL FORM
+FC75;FC75;FC75;062A 064A;062A 064A; # (ﱵ; ﱵ; ﱵ; تي; تي; ) ARABIC LIGATURE TEH WITH YEH FINAL FORM
+FC76;FC76;FC76;062B 0631;062B 0631; # (ﱶ; ﱶ; ﱶ; ثر; ثر; ) ARABIC LIGATURE THEH WITH REH FINAL FORM
+FC77;FC77;FC77;062B 0632;062B 0632; # (ﱷ; ﱷ; ﱷ; ثز; ثز; ) ARABIC LIGATURE THEH WITH ZAIN FINAL FORM
+FC78;FC78;FC78;062B 0645;062B 0645; # (ﱸ; ﱸ; ﱸ; ثم; ثم; ) ARABIC LIGATURE THEH WITH MEEM FINAL FORM
+FC79;FC79;FC79;062B 0646;062B 0646; # (ﱹ; ﱹ; ﱹ; ثن; ثن; ) ARABIC LIGATURE THEH WITH NOON FINAL FORM
+FC7A;FC7A;FC7A;062B 0649;062B 0649; # (ﱺ; ﱺ; ﱺ; ثى; ثى; ) ARABIC LIGATURE THEH WITH ALEF MAKSURA FINAL FORM
+FC7B;FC7B;FC7B;062B 064A;062B 064A; # (ﱻ; ﱻ; ﱻ; ثي; ثي; ) ARABIC LIGATURE THEH WITH YEH FINAL FORM
+FC7C;FC7C;FC7C;0641 0649;0641 0649; # (ï±¼; ï±¼; ï±¼; ÙÙ‰; ÙÙ‰; ) ARABIC LIGATURE FEH WITH ALEF MAKSURA FINAL FORM
+FC7D;FC7D;FC7D;0641 064A;0641 064A; # (ï±½; ï±½; ï±½; ÙÙŠ; ÙÙŠ; ) ARABIC LIGATURE FEH WITH YEH FINAL FORM
+FC7E;FC7E;FC7E;0642 0649;0642 0649; # (ﱾ; ﱾ; ﱾ; قى; قى; ) ARABIC LIGATURE QAF WITH ALEF MAKSURA FINAL FORM
+FC7F;FC7F;FC7F;0642 064A;0642 064A; # (ﱿ; ﱿ; ﱿ; قي; قي; ) ARABIC LIGATURE QAF WITH YEH FINAL FORM
+FC80;FC80;FC80;0643 0627;0643 0627; # (ﲀ; ﲀ; ﲀ; كا; كا; ) ARABIC LIGATURE KAF WITH ALEF FINAL FORM
+FC81;FC81;FC81;0643 0644;0643 0644; # (ï²; ï²; ï²; كل; كل; ) ARABIC LIGATURE KAF WITH LAM FINAL FORM
+FC82;FC82;FC82;0643 0645;0643 0645; # (ﲂ; ﲂ; ﲂ; كم; كم; ) ARABIC LIGATURE KAF WITH MEEM FINAL FORM
+FC83;FC83;FC83;0643 0649;0643 0649; # (ﲃ; ﲃ; ﲃ; كى; كى; ) ARABIC LIGATURE KAF WITH ALEF MAKSURA FINAL FORM
+FC84;FC84;FC84;0643 064A;0643 064A; # (ﲄ; ﲄ; ﲄ; كي; كي; ) ARABIC LIGATURE KAF WITH YEH FINAL FORM
+FC85;FC85;FC85;0644 0645;0644 0645; # (ﲅ; ﲅ; ﲅ; لم; لم; ) ARABIC LIGATURE LAM WITH MEEM FINAL FORM
+FC86;FC86;FC86;0644 0649;0644 0649; # (ﲆ; ﲆ; ﲆ; لى; لى; ) ARABIC LIGATURE LAM WITH ALEF MAKSURA FINAL FORM
+FC87;FC87;FC87;0644 064A;0644 064A; # (ﲇ; ﲇ; ﲇ; لي; لي; ) ARABIC LIGATURE LAM WITH YEH FINAL FORM
+FC88;FC88;FC88;0645 0627;0645 0627; # (ﲈ; ﲈ; ﲈ; ما; ما; ) ARABIC LIGATURE MEEM WITH ALEF FINAL FORM
+FC89;FC89;FC89;0645 0645;0645 0645; # (ﲉ; ﲉ; ﲉ; مم; مم; ) ARABIC LIGATURE MEEM WITH MEEM FINAL FORM
+FC8A;FC8A;FC8A;0646 0631;0646 0631; # (ﲊ; ﲊ; ﲊ; نر; نر; ) ARABIC LIGATURE NOON WITH REH FINAL FORM
+FC8B;FC8B;FC8B;0646 0632;0646 0632; # (ﲋ; ﲋ; ﲋ; نز; نز; ) ARABIC LIGATURE NOON WITH ZAIN FINAL FORM
+FC8C;FC8C;FC8C;0646 0645;0646 0645; # (ﲌ; ﲌ; ﲌ; نم; نم; ) ARABIC LIGATURE NOON WITH MEEM FINAL FORM
+FC8D;FC8D;FC8D;0646 0646;0646 0646; # (ï²; ï²; ï²; نن; نن; ) ARABIC LIGATURE NOON WITH NOON FINAL FORM
+FC8E;FC8E;FC8E;0646 0649;0646 0649; # (ﲎ; ﲎ; ﲎ; نى; نى; ) ARABIC LIGATURE NOON WITH ALEF MAKSURA FINAL FORM
+FC8F;FC8F;FC8F;0646 064A;0646 064A; # (ï²; ï²; ï²; ني; ني; ) ARABIC LIGATURE NOON WITH YEH FINAL FORM
+FC90;FC90;FC90;0649 0670;0649 0670; # (ï²; ï²; ï²; ى◌ٰ; ى◌ٰ; ) ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF FINAL FORM
+FC91;FC91;FC91;064A 0631;064A 0631; # (ﲑ; ﲑ; ﲑ; ير; ير; ) ARABIC LIGATURE YEH WITH REH FINAL FORM
+FC92;FC92;FC92;064A 0632;064A 0632; # (ﲒ; ﲒ; ﲒ; يز; يز; ) ARABIC LIGATURE YEH WITH ZAIN FINAL FORM
+FC93;FC93;FC93;064A 0645;064A 0645; # (ﲓ; ﲓ; ﲓ; يم; يم; ) ARABIC LIGATURE YEH WITH MEEM FINAL FORM
+FC94;FC94;FC94;064A 0646;064A 0646; # (ﲔ; ﲔ; ﲔ; ين; ين; ) ARABIC LIGATURE YEH WITH NOON FINAL FORM
+FC95;FC95;FC95;064A 0649;064A 0649; # (ﲕ; ﲕ; ﲕ; يى; يى; ) ARABIC LIGATURE YEH WITH ALEF MAKSURA FINAL FORM
+FC96;FC96;FC96;064A 064A;064A 064A; # (ﲖ; ﲖ; ﲖ; يي; يي; ) ARABIC LIGATURE YEH WITH YEH FINAL FORM
+FC97;FC97;FC97;0626 062C;064A 0654 062C; # (ﲗ; ﲗ; ﲗ; ئج; ي◌ٔج; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH JEEM INITIAL FORM
+FC98;FC98;FC98;0626 062D;064A 0654 062D; # (ﲘ; ﲘ; ﲘ; ئح; ي◌ٔح; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HAH INITIAL FORM
+FC99;FC99;FC99;0626 062E;064A 0654 062E; # (ﲙ; ﲙ; ﲙ; ئخ; ي◌ٔخ; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH KHAH INITIAL FORM
+FC9A;FC9A;FC9A;0626 0645;064A 0654 0645; # (ﲚ; ﲚ; ﲚ; ئم; ي◌ٔم; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM INITIAL FORM
+FC9B;FC9B;FC9B;0626 0647;064A 0654 0647; # (ﲛ; ﲛ; ﲛ; ئه; ي◌ٔه; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HEH INITIAL FORM
+FC9C;FC9C;FC9C;0628 062C;0628 062C; # (ﲜ; ﲜ; ﲜ; بج; بج; ) ARABIC LIGATURE BEH WITH JEEM INITIAL FORM
+FC9D;FC9D;FC9D;0628 062D;0628 062D; # (ï²; ï²; ï²; بح; بح; ) ARABIC LIGATURE BEH WITH HAH INITIAL FORM
+FC9E;FC9E;FC9E;0628 062E;0628 062E; # (ﲞ; ﲞ; ﲞ; بخ; بخ; ) ARABIC LIGATURE BEH WITH KHAH INITIAL FORM
+FC9F;FC9F;FC9F;0628 0645;0628 0645; # (ﲟ; ﲟ; ﲟ; بم; بم; ) ARABIC LIGATURE BEH WITH MEEM INITIAL FORM
+FCA0;FCA0;FCA0;0628 0647;0628 0647; # (ﲠ; ﲠ; ﲠ; به; به; ) ARABIC LIGATURE BEH WITH HEH INITIAL FORM
+FCA1;FCA1;FCA1;062A 062C;062A 062C; # (ﲡ; ﲡ; ﲡ; تج; تج; ) ARABIC LIGATURE TEH WITH JEEM INITIAL FORM
+FCA2;FCA2;FCA2;062A 062D;062A 062D; # (ﲢ; ﲢ; ﲢ; تح; تح; ) ARABIC LIGATURE TEH WITH HAH INITIAL FORM
+FCA3;FCA3;FCA3;062A 062E;062A 062E; # (ﲣ; ﲣ; ﲣ; تخ; تخ; ) ARABIC LIGATURE TEH WITH KHAH INITIAL FORM
+FCA4;FCA4;FCA4;062A 0645;062A 0645; # (ﲤ; ﲤ; ﲤ; تم; تم; ) ARABIC LIGATURE TEH WITH MEEM INITIAL FORM
+FCA5;FCA5;FCA5;062A 0647;062A 0647; # (ﲥ; ﲥ; ﲥ; ته; ته; ) ARABIC LIGATURE TEH WITH HEH INITIAL FORM
+FCA6;FCA6;FCA6;062B 0645;062B 0645; # (ﲦ; ﲦ; ﲦ; ثم; ثم; ) ARABIC LIGATURE THEH WITH MEEM INITIAL FORM
+FCA7;FCA7;FCA7;062C 062D;062C 062D; # (ﲧ; ﲧ; ﲧ; جح; جح; ) ARABIC LIGATURE JEEM WITH HAH INITIAL FORM
+FCA8;FCA8;FCA8;062C 0645;062C 0645; # (ﲨ; ﲨ; ﲨ; جم; جم; ) ARABIC LIGATURE JEEM WITH MEEM INITIAL FORM
+FCA9;FCA9;FCA9;062D 062C;062D 062C; # (ﲩ; ﲩ; ﲩ; حج; حج; ) ARABIC LIGATURE HAH WITH JEEM INITIAL FORM
+FCAA;FCAA;FCAA;062D 0645;062D 0645; # (ﲪ; ﲪ; ﲪ; حم; حم; ) ARABIC LIGATURE HAH WITH MEEM INITIAL FORM
+FCAB;FCAB;FCAB;062E 062C;062E 062C; # (ﲫ; ﲫ; ﲫ; خج; خج; ) ARABIC LIGATURE KHAH WITH JEEM INITIAL FORM
+FCAC;FCAC;FCAC;062E 0645;062E 0645; # (ﲬ; ﲬ; ﲬ; خم; خم; ) ARABIC LIGATURE KHAH WITH MEEM INITIAL FORM
+FCAD;FCAD;FCAD;0633 062C;0633 062C; # (ﲭ; ﲭ; ﲭ; سج; سج; ) ARABIC LIGATURE SEEN WITH JEEM INITIAL FORM
+FCAE;FCAE;FCAE;0633 062D;0633 062D; # (ﲮ; ﲮ; ﲮ; سح; سح; ) ARABIC LIGATURE SEEN WITH HAH INITIAL FORM
+FCAF;FCAF;FCAF;0633 062E;0633 062E; # (ﲯ; ﲯ; ﲯ; سخ; سخ; ) ARABIC LIGATURE SEEN WITH KHAH INITIAL FORM
+FCB0;FCB0;FCB0;0633 0645;0633 0645; # (ﲰ; ﲰ; ﲰ; سم; سم; ) ARABIC LIGATURE SEEN WITH MEEM INITIAL FORM
+FCB1;FCB1;FCB1;0635 062D;0635 062D; # (ﲱ; ﲱ; ﲱ; صح; صح; ) ARABIC LIGATURE SAD WITH HAH INITIAL FORM
+FCB2;FCB2;FCB2;0635 062E;0635 062E; # (ﲲ; ﲲ; ﲲ; صخ; صخ; ) ARABIC LIGATURE SAD WITH KHAH INITIAL FORM
+FCB3;FCB3;FCB3;0635 0645;0635 0645; # (ﲳ; ﲳ; ﲳ; صم; صم; ) ARABIC LIGATURE SAD WITH MEEM INITIAL FORM
+FCB4;FCB4;FCB4;0636 062C;0636 062C; # (ﲴ; ﲴ; ﲴ; ضج; ضج; ) ARABIC LIGATURE DAD WITH JEEM INITIAL FORM
+FCB5;FCB5;FCB5;0636 062D;0636 062D; # (ﲵ; ﲵ; ﲵ; ضح; ضح; ) ARABIC LIGATURE DAD WITH HAH INITIAL FORM
+FCB6;FCB6;FCB6;0636 062E;0636 062E; # (ﲶ; ﲶ; ﲶ; ضخ; ضخ; ) ARABIC LIGATURE DAD WITH KHAH INITIAL FORM
+FCB7;FCB7;FCB7;0636 0645;0636 0645; # (ﲷ; ﲷ; ﲷ; ضم; ضم; ) ARABIC LIGATURE DAD WITH MEEM INITIAL FORM
+FCB8;FCB8;FCB8;0637 062D;0637 062D; # (ﲸ; ﲸ; ﲸ; طح; طح; ) ARABIC LIGATURE TAH WITH HAH INITIAL FORM
+FCB9;FCB9;FCB9;0638 0645;0638 0645; # (ﲹ; ﲹ; ﲹ; ظم; ظم; ) ARABIC LIGATURE ZAH WITH MEEM INITIAL FORM
+FCBA;FCBA;FCBA;0639 062C;0639 062C; # (ﲺ; ﲺ; ﲺ; عج; عج; ) ARABIC LIGATURE AIN WITH JEEM INITIAL FORM
+FCBB;FCBB;FCBB;0639 0645;0639 0645; # (ﲻ; ﲻ; ﲻ; عم; عم; ) ARABIC LIGATURE AIN WITH MEEM INITIAL FORM
+FCBC;FCBC;FCBC;063A 062C;063A 062C; # (ﲼ; ﲼ; ﲼ; غج; غج; ) ARABIC LIGATURE GHAIN WITH JEEM INITIAL FORM
+FCBD;FCBD;FCBD;063A 0645;063A 0645; # (ﲽ; ﲽ; ﲽ; غم; غم; ) ARABIC LIGATURE GHAIN WITH MEEM INITIAL FORM
+FCBE;FCBE;FCBE;0641 062C;0641 062C; # (ï²¾; ï²¾; ï²¾; Ùج; Ùج; ) ARABIC LIGATURE FEH WITH JEEM INITIAL FORM
+FCBF;FCBF;FCBF;0641 062D;0641 062D; # (ﲿ; ﲿ; ﲿ; ÙØ­; ÙØ­; ) ARABIC LIGATURE FEH WITH HAH INITIAL FORM
+FCC0;FCC0;FCC0;0641 062E;0641 062E; # (ï³€; ï³€; ï³€; ÙØ®; ÙØ®; ) ARABIC LIGATURE FEH WITH KHAH INITIAL FORM
+FCC1;FCC1;FCC1;0641 0645;0641 0645; # (ï³; ï³; ï³; ÙÙ…; ÙÙ…; ) ARABIC LIGATURE FEH WITH MEEM INITIAL FORM
+FCC2;FCC2;FCC2;0642 062D;0642 062D; # (ﳂ; ﳂ; ﳂ; قح; قح; ) ARABIC LIGATURE QAF WITH HAH INITIAL FORM
+FCC3;FCC3;FCC3;0642 0645;0642 0645; # (ﳃ; ﳃ; ﳃ; قم; قم; ) ARABIC LIGATURE QAF WITH MEEM INITIAL FORM
+FCC4;FCC4;FCC4;0643 062C;0643 062C; # (ﳄ; ﳄ; ﳄ; كج; كج; ) ARABIC LIGATURE KAF WITH JEEM INITIAL FORM
+FCC5;FCC5;FCC5;0643 062D;0643 062D; # (ﳅ; ﳅ; ﳅ; كح; كح; ) ARABIC LIGATURE KAF WITH HAH INITIAL FORM
+FCC6;FCC6;FCC6;0643 062E;0643 062E; # (ﳆ; ﳆ; ﳆ; كخ; كخ; ) ARABIC LIGATURE KAF WITH KHAH INITIAL FORM
+FCC7;FCC7;FCC7;0643 0644;0643 0644; # (ﳇ; ﳇ; ﳇ; كل; كل; ) ARABIC LIGATURE KAF WITH LAM INITIAL FORM
+FCC8;FCC8;FCC8;0643 0645;0643 0645; # (ﳈ; ﳈ; ﳈ; كم; كم; ) ARABIC LIGATURE KAF WITH MEEM INITIAL FORM
+FCC9;FCC9;FCC9;0644 062C;0644 062C; # (ﳉ; ﳉ; ﳉ; لج; لج; ) ARABIC LIGATURE LAM WITH JEEM INITIAL FORM
+FCCA;FCCA;FCCA;0644 062D;0644 062D; # (ﳊ; ﳊ; ﳊ; لح; لح; ) ARABIC LIGATURE LAM WITH HAH INITIAL FORM
+FCCB;FCCB;FCCB;0644 062E;0644 062E; # (ﳋ; ﳋ; ﳋ; لخ; لخ; ) ARABIC LIGATURE LAM WITH KHAH INITIAL FORM
+FCCC;FCCC;FCCC;0644 0645;0644 0645; # (ﳌ; ﳌ; ﳌ; لم; لم; ) ARABIC LIGATURE LAM WITH MEEM INITIAL FORM
+FCCD;FCCD;FCCD;0644 0647;0644 0647; # (ï³; ï³; ï³; له; له; ) ARABIC LIGATURE LAM WITH HEH INITIAL FORM
+FCCE;FCCE;FCCE;0645 062C;0645 062C; # (ﳎ; ﳎ; ﳎ; مج; مج; ) ARABIC LIGATURE MEEM WITH JEEM INITIAL FORM
+FCCF;FCCF;FCCF;0645 062D;0645 062D; # (ï³; ï³; ï³; مح; مح; ) ARABIC LIGATURE MEEM WITH HAH INITIAL FORM
+FCD0;FCD0;FCD0;0645 062E;0645 062E; # (ï³; ï³; ï³; مخ; مخ; ) ARABIC LIGATURE MEEM WITH KHAH INITIAL FORM
+FCD1;FCD1;FCD1;0645 0645;0645 0645; # (ﳑ; ﳑ; ﳑ; مم; مم; ) ARABIC LIGATURE MEEM WITH MEEM INITIAL FORM
+FCD2;FCD2;FCD2;0646 062C;0646 062C; # (ﳒ; ﳒ; ﳒ; نج; نج; ) ARABIC LIGATURE NOON WITH JEEM INITIAL FORM
+FCD3;FCD3;FCD3;0646 062D;0646 062D; # (ﳓ; ﳓ; ﳓ; نح; نح; ) ARABIC LIGATURE NOON WITH HAH INITIAL FORM
+FCD4;FCD4;FCD4;0646 062E;0646 062E; # (ﳔ; ﳔ; ﳔ; نخ; نخ; ) ARABIC LIGATURE NOON WITH KHAH INITIAL FORM
+FCD5;FCD5;FCD5;0646 0645;0646 0645; # (ﳕ; ﳕ; ﳕ; نم; نم; ) ARABIC LIGATURE NOON WITH MEEM INITIAL FORM
+FCD6;FCD6;FCD6;0646 0647;0646 0647; # (ﳖ; ﳖ; ﳖ; نه; نه; ) ARABIC LIGATURE NOON WITH HEH INITIAL FORM
+FCD7;FCD7;FCD7;0647 062C;0647 062C; # (ﳗ; ﳗ; ﳗ; هج; هج; ) ARABIC LIGATURE HEH WITH JEEM INITIAL FORM
+FCD8;FCD8;FCD8;0647 0645;0647 0645; # (ﳘ; ﳘ; ﳘ; هم; هم; ) ARABIC LIGATURE HEH WITH MEEM INITIAL FORM
+FCD9;FCD9;FCD9;0647 0670;0647 0670; # (ﳙ; ﳙ; ﳙ; ه◌ٰ; ه◌ٰ; ) ARABIC LIGATURE HEH WITH SUPERSCRIPT ALEF INITIAL FORM
+FCDA;FCDA;FCDA;064A 062C;064A 062C; # (ﳚ; ﳚ; ﳚ; يج; يج; ) ARABIC LIGATURE YEH WITH JEEM INITIAL FORM
+FCDB;FCDB;FCDB;064A 062D;064A 062D; # (ﳛ; ﳛ; ﳛ; يح; يح; ) ARABIC LIGATURE YEH WITH HAH INITIAL FORM
+FCDC;FCDC;FCDC;064A 062E;064A 062E; # (ﳜ; ﳜ; ﳜ; يخ; يخ; ) ARABIC LIGATURE YEH WITH KHAH INITIAL FORM
+FCDD;FCDD;FCDD;064A 0645;064A 0645; # (ï³; ï³; ï³; يم; يم; ) ARABIC LIGATURE YEH WITH MEEM INITIAL FORM
+FCDE;FCDE;FCDE;064A 0647;064A 0647; # (ﳞ; ﳞ; ﳞ; يه; يه; ) ARABIC LIGATURE YEH WITH HEH INITIAL FORM
+FCDF;FCDF;FCDF;0626 0645;064A 0654 0645; # (ﳟ; ﳟ; ﳟ; ئم; ي◌ٔم; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM MEDIAL FORM
+FCE0;FCE0;FCE0;0626 0647;064A 0654 0647; # (ﳠ; ﳠ; ﳠ; ئه; ي◌ٔه; ) ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HEH MEDIAL FORM
+FCE1;FCE1;FCE1;0628 0645;0628 0645; # (ﳡ; ﳡ; ﳡ; بم; بم; ) ARABIC LIGATURE BEH WITH MEEM MEDIAL FORM
+FCE2;FCE2;FCE2;0628 0647;0628 0647; # (ﳢ; ﳢ; ﳢ; به; به; ) ARABIC LIGATURE BEH WITH HEH MEDIAL FORM
+FCE3;FCE3;FCE3;062A 0645;062A 0645; # (ﳣ; ﳣ; ﳣ; تم; تم; ) ARABIC LIGATURE TEH WITH MEEM MEDIAL FORM
+FCE4;FCE4;FCE4;062A 0647;062A 0647; # (ﳤ; ﳤ; ﳤ; ته; ته; ) ARABIC LIGATURE TEH WITH HEH MEDIAL FORM
+FCE5;FCE5;FCE5;062B 0645;062B 0645; # (ﳥ; ﳥ; ﳥ; ثم; ثم; ) ARABIC LIGATURE THEH WITH MEEM MEDIAL FORM
+FCE6;FCE6;FCE6;062B 0647;062B 0647; # (ﳦ; ﳦ; ﳦ; ثه; ثه; ) ARABIC LIGATURE THEH WITH HEH MEDIAL FORM
+FCE7;FCE7;FCE7;0633 0645;0633 0645; # (ﳧ; ﳧ; ﳧ; سم; سم; ) ARABIC LIGATURE SEEN WITH MEEM MEDIAL FORM
+FCE8;FCE8;FCE8;0633 0647;0633 0647; # (ﳨ; ﳨ; ﳨ; سه; سه; ) ARABIC LIGATURE SEEN WITH HEH MEDIAL FORM
+FCE9;FCE9;FCE9;0634 0645;0634 0645; # (ﳩ; ﳩ; ﳩ; شم; شم; ) ARABIC LIGATURE SHEEN WITH MEEM MEDIAL FORM
+FCEA;FCEA;FCEA;0634 0647;0634 0647; # (ﳪ; ﳪ; ﳪ; شه; شه; ) ARABIC LIGATURE SHEEN WITH HEH MEDIAL FORM
+FCEB;FCEB;FCEB;0643 0644;0643 0644; # (ﳫ; ﳫ; ﳫ; كل; كل; ) ARABIC LIGATURE KAF WITH LAM MEDIAL FORM
+FCEC;FCEC;FCEC;0643 0645;0643 0645; # (ﳬ; ﳬ; ﳬ; كم; كم; ) ARABIC LIGATURE KAF WITH MEEM MEDIAL FORM
+FCED;FCED;FCED;0644 0645;0644 0645; # (ﳭ; ﳭ; ﳭ; لم; لم; ) ARABIC LIGATURE LAM WITH MEEM MEDIAL FORM
+FCEE;FCEE;FCEE;0646 0645;0646 0645; # (ﳮ; ﳮ; ﳮ; نم; نم; ) ARABIC LIGATURE NOON WITH MEEM MEDIAL FORM
+FCEF;FCEF;FCEF;0646 0647;0646 0647; # (ﳯ; ﳯ; ﳯ; نه; نه; ) ARABIC LIGATURE NOON WITH HEH MEDIAL FORM
+FCF0;FCF0;FCF0;064A 0645;064A 0645; # (ﳰ; ﳰ; ﳰ; يم; يم; ) ARABIC LIGATURE YEH WITH MEEM MEDIAL FORM
+FCF1;FCF1;FCF1;064A 0647;064A 0647; # (ﳱ; ﳱ; ﳱ; يه; يه; ) ARABIC LIGATURE YEH WITH HEH MEDIAL FORM
+FCF2;FCF2;FCF2;0640 064E 0651;0640 064E 0651; # (ﳲ; ﳲ; ﳲ; ـ◌َ◌ّ; ـ◌َ◌ّ; ) ARABIC LIGATURE SHADDA WITH FATHA MEDIAL FORM
+FCF3;FCF3;FCF3;0640 064F 0651;0640 064F 0651; # (ï³³; ï³³; ï³³; ـ◌Ù◌ّ; ـ◌Ù◌ّ; ) ARABIC LIGATURE SHADDA WITH DAMMA MEDIAL FORM
+FCF4;FCF4;FCF4;0640 0650 0651;0640 0650 0651; # (ï³´; ï³´; ï³´; ـ◌Ù◌ّ; ـ◌Ù◌ّ; ) ARABIC LIGATURE SHADDA WITH KASRA MEDIAL FORM
+FCF5;FCF5;FCF5;0637 0649;0637 0649; # (ﳵ; ﳵ; ﳵ; طى; طى; ) ARABIC LIGATURE TAH WITH ALEF MAKSURA ISOLATED FORM
+FCF6;FCF6;FCF6;0637 064A;0637 064A; # (ﳶ; ﳶ; ﳶ; طي; طي; ) ARABIC LIGATURE TAH WITH YEH ISOLATED FORM
+FCF7;FCF7;FCF7;0639 0649;0639 0649; # (ﳷ; ﳷ; ﳷ; عى; عى; ) ARABIC LIGATURE AIN WITH ALEF MAKSURA ISOLATED FORM
+FCF8;FCF8;FCF8;0639 064A;0639 064A; # (ﳸ; ﳸ; ﳸ; عي; عي; ) ARABIC LIGATURE AIN WITH YEH ISOLATED FORM
+FCF9;FCF9;FCF9;063A 0649;063A 0649; # (ﳹ; ﳹ; ﳹ; غى; غى; ) ARABIC LIGATURE GHAIN WITH ALEF MAKSURA ISOLATED FORM
+FCFA;FCFA;FCFA;063A 064A;063A 064A; # (ﳺ; ﳺ; ﳺ; غي; غي; ) ARABIC LIGATURE GHAIN WITH YEH ISOLATED FORM
+FCFB;FCFB;FCFB;0633 0649;0633 0649; # (ﳻ; ﳻ; ﳻ; سى; سى; ) ARABIC LIGATURE SEEN WITH ALEF MAKSURA ISOLATED FORM
+FCFC;FCFC;FCFC;0633 064A;0633 064A; # (ﳼ; ﳼ; ﳼ; سي; سي; ) ARABIC LIGATURE SEEN WITH YEH ISOLATED FORM
+FCFD;FCFD;FCFD;0634 0649;0634 0649; # (ﳽ; ﳽ; ﳽ; شى; شى; ) ARABIC LIGATURE SHEEN WITH ALEF MAKSURA ISOLATED FORM
+FCFE;FCFE;FCFE;0634 064A;0634 064A; # (ﳾ; ﳾ; ﳾ; شي; شي; ) ARABIC LIGATURE SHEEN WITH YEH ISOLATED FORM
+FCFF;FCFF;FCFF;062D 0649;062D 0649; # (ﳿ; ﳿ; ﳿ; حى; حى; ) ARABIC LIGATURE HAH WITH ALEF MAKSURA ISOLATED FORM
+FD00;FD00;FD00;062D 064A;062D 064A; # (ﴀ; ﴀ; ﴀ; حي; حي; ) ARABIC LIGATURE HAH WITH YEH ISOLATED FORM
+FD01;FD01;FD01;062C 0649;062C 0649; # (ï´; ï´; ï´; جى; جى; ) ARABIC LIGATURE JEEM WITH ALEF MAKSURA ISOLATED FORM
+FD02;FD02;FD02;062C 064A;062C 064A; # (ﴂ; ﴂ; ﴂ; جي; جي; ) ARABIC LIGATURE JEEM WITH YEH ISOLATED FORM
+FD03;FD03;FD03;062E 0649;062E 0649; # (ﴃ; ﴃ; ﴃ; خى; خى; ) ARABIC LIGATURE KHAH WITH ALEF MAKSURA ISOLATED FORM
+FD04;FD04;FD04;062E 064A;062E 064A; # (ﴄ; ﴄ; ﴄ; خي; خي; ) ARABIC LIGATURE KHAH WITH YEH ISOLATED FORM
+FD05;FD05;FD05;0635 0649;0635 0649; # (ﴅ; ﴅ; ﴅ; صى; صى; ) ARABIC LIGATURE SAD WITH ALEF MAKSURA ISOLATED FORM
+FD06;FD06;FD06;0635 064A;0635 064A; # (ﴆ; ﴆ; ﴆ; صي; صي; ) ARABIC LIGATURE SAD WITH YEH ISOLATED FORM
+FD07;FD07;FD07;0636 0649;0636 0649; # (ﴇ; ﴇ; ﴇ; ضى; ضى; ) ARABIC LIGATURE DAD WITH ALEF MAKSURA ISOLATED FORM
+FD08;FD08;FD08;0636 064A;0636 064A; # (ﴈ; ﴈ; ﴈ; ضي; ضي; ) ARABIC LIGATURE DAD WITH YEH ISOLATED FORM
+FD09;FD09;FD09;0634 062C;0634 062C; # (ﴉ; ﴉ; ﴉ; شج; شج; ) ARABIC LIGATURE SHEEN WITH JEEM ISOLATED FORM
+FD0A;FD0A;FD0A;0634 062D;0634 062D; # (ﴊ; ﴊ; ﴊ; شح; شح; ) ARABIC LIGATURE SHEEN WITH HAH ISOLATED FORM
+FD0B;FD0B;FD0B;0634 062E;0634 062E; # (ﴋ; ﴋ; ﴋ; شخ; شخ; ) ARABIC LIGATURE SHEEN WITH KHAH ISOLATED FORM
+FD0C;FD0C;FD0C;0634 0645;0634 0645; # (ﴌ; ﴌ; ﴌ; شم; شم; ) ARABIC LIGATURE SHEEN WITH MEEM ISOLATED FORM
+FD0D;FD0D;FD0D;0634 0631;0634 0631; # (ï´; ï´; ï´; شر; شر; ) ARABIC LIGATURE SHEEN WITH REH ISOLATED FORM
+FD0E;FD0E;FD0E;0633 0631;0633 0631; # (ﴎ; ﴎ; ﴎ; سر; سر; ) ARABIC LIGATURE SEEN WITH REH ISOLATED FORM
+FD0F;FD0F;FD0F;0635 0631;0635 0631; # (ï´; ï´; ï´; صر; صر; ) ARABIC LIGATURE SAD WITH REH ISOLATED FORM
+FD10;FD10;FD10;0636 0631;0636 0631; # (ï´; ï´; ï´; ضر; ضر; ) ARABIC LIGATURE DAD WITH REH ISOLATED FORM
+FD11;FD11;FD11;0637 0649;0637 0649; # (ﴑ; ﴑ; ﴑ; طى; طى; ) ARABIC LIGATURE TAH WITH ALEF MAKSURA FINAL FORM
+FD12;FD12;FD12;0637 064A;0637 064A; # (ﴒ; ﴒ; ﴒ; طي; طي; ) ARABIC LIGATURE TAH WITH YEH FINAL FORM
+FD13;FD13;FD13;0639 0649;0639 0649; # (ﴓ; ﴓ; ﴓ; عى; عى; ) ARABIC LIGATURE AIN WITH ALEF MAKSURA FINAL FORM
+FD14;FD14;FD14;0639 064A;0639 064A; # (ﴔ; ﴔ; ﴔ; عي; عي; ) ARABIC LIGATURE AIN WITH YEH FINAL FORM
+FD15;FD15;FD15;063A 0649;063A 0649; # (ﴕ; ﴕ; ﴕ; غى; غى; ) ARABIC LIGATURE GHAIN WITH ALEF MAKSURA FINAL FORM
+FD16;FD16;FD16;063A 064A;063A 064A; # (ﴖ; ﴖ; ﴖ; غي; غي; ) ARABIC LIGATURE GHAIN WITH YEH FINAL FORM
+FD17;FD17;FD17;0633 0649;0633 0649; # (ﴗ; ﴗ; ﴗ; سى; سى; ) ARABIC LIGATURE SEEN WITH ALEF MAKSURA FINAL FORM
+FD18;FD18;FD18;0633 064A;0633 064A; # (ﴘ; ﴘ; ﴘ; سي; سي; ) ARABIC LIGATURE SEEN WITH YEH FINAL FORM
+FD19;FD19;FD19;0634 0649;0634 0649; # (ﴙ; ﴙ; ﴙ; شى; شى; ) ARABIC LIGATURE SHEEN WITH ALEF MAKSURA FINAL FORM
+FD1A;FD1A;FD1A;0634 064A;0634 064A; # (ﴚ; ﴚ; ﴚ; شي; شي; ) ARABIC LIGATURE SHEEN WITH YEH FINAL FORM
+FD1B;FD1B;FD1B;062D 0649;062D 0649; # (ﴛ; ﴛ; ﴛ; حى; حى; ) ARABIC LIGATURE HAH WITH ALEF MAKSURA FINAL FORM
+FD1C;FD1C;FD1C;062D 064A;062D 064A; # (ﴜ; ﴜ; ﴜ; حي; حي; ) ARABIC LIGATURE HAH WITH YEH FINAL FORM
+FD1D;FD1D;FD1D;062C 0649;062C 0649; # (ï´; ï´; ï´; جى; جى; ) ARABIC LIGATURE JEEM WITH ALEF MAKSURA FINAL FORM
+FD1E;FD1E;FD1E;062C 064A;062C 064A; # (ﴞ; ﴞ; ﴞ; جي; جي; ) ARABIC LIGATURE JEEM WITH YEH FINAL FORM
+FD1F;FD1F;FD1F;062E 0649;062E 0649; # (ﴟ; ﴟ; ﴟ; خى; خى; ) ARABIC LIGATURE KHAH WITH ALEF MAKSURA FINAL FORM
+FD20;FD20;FD20;062E 064A;062E 064A; # (ﴠ; ﴠ; ﴠ; خي; خي; ) ARABIC LIGATURE KHAH WITH YEH FINAL FORM
+FD21;FD21;FD21;0635 0649;0635 0649; # (ﴡ; ﴡ; ﴡ; صى; صى; ) ARABIC LIGATURE SAD WITH ALEF MAKSURA FINAL FORM
+FD22;FD22;FD22;0635 064A;0635 064A; # (ﴢ; ﴢ; ﴢ; صي; صي; ) ARABIC LIGATURE SAD WITH YEH FINAL FORM
+FD23;FD23;FD23;0636 0649;0636 0649; # (ﴣ; ﴣ; ﴣ; ضى; ضى; ) ARABIC LIGATURE DAD WITH ALEF MAKSURA FINAL FORM
+FD24;FD24;FD24;0636 064A;0636 064A; # (ﴤ; ﴤ; ﴤ; ضي; ضي; ) ARABIC LIGATURE DAD WITH YEH FINAL FORM
+FD25;FD25;FD25;0634 062C;0634 062C; # (ﴥ; ﴥ; ﴥ; شج; شج; ) ARABIC LIGATURE SHEEN WITH JEEM FINAL FORM
+FD26;FD26;FD26;0634 062D;0634 062D; # (ﴦ; ﴦ; ﴦ; شح; شح; ) ARABIC LIGATURE SHEEN WITH HAH FINAL FORM
+FD27;FD27;FD27;0634 062E;0634 062E; # (ﴧ; ﴧ; ﴧ; شخ; شخ; ) ARABIC LIGATURE SHEEN WITH KHAH FINAL FORM
+FD28;FD28;FD28;0634 0645;0634 0645; # (ﴨ; ﴨ; ﴨ; شم; شم; ) ARABIC LIGATURE SHEEN WITH MEEM FINAL FORM
+FD29;FD29;FD29;0634 0631;0634 0631; # (ﴩ; ﴩ; ﴩ; شر; شر; ) ARABIC LIGATURE SHEEN WITH REH FINAL FORM
+FD2A;FD2A;FD2A;0633 0631;0633 0631; # (ﴪ; ﴪ; ﴪ; سر; سر; ) ARABIC LIGATURE SEEN WITH REH FINAL FORM
+FD2B;FD2B;FD2B;0635 0631;0635 0631; # (ﴫ; ﴫ; ﴫ; صر; صر; ) ARABIC LIGATURE SAD WITH REH FINAL FORM
+FD2C;FD2C;FD2C;0636 0631;0636 0631; # (ﴬ; ﴬ; ﴬ; ضر; ضر; ) ARABIC LIGATURE DAD WITH REH FINAL FORM
+FD2D;FD2D;FD2D;0634 062C;0634 062C; # (ﴭ; ﴭ; ﴭ; شج; شج; ) ARABIC LIGATURE SHEEN WITH JEEM INITIAL FORM
+FD2E;FD2E;FD2E;0634 062D;0634 062D; # (ﴮ; ﴮ; ﴮ; شح; شح; ) ARABIC LIGATURE SHEEN WITH HAH INITIAL FORM
+FD2F;FD2F;FD2F;0634 062E;0634 062E; # (ﴯ; ﴯ; ﴯ; شخ; شخ; ) ARABIC LIGATURE SHEEN WITH KHAH INITIAL FORM
+FD30;FD30;FD30;0634 0645;0634 0645; # (ﴰ; ﴰ; ﴰ; شم; شم; ) ARABIC LIGATURE SHEEN WITH MEEM INITIAL FORM
+FD31;FD31;FD31;0633 0647;0633 0647; # (ﴱ; ﴱ; ﴱ; سه; سه; ) ARABIC LIGATURE SEEN WITH HEH INITIAL FORM
+FD32;FD32;FD32;0634 0647;0634 0647; # (ﴲ; ﴲ; ﴲ; شه; شه; ) ARABIC LIGATURE SHEEN WITH HEH INITIAL FORM
+FD33;FD33;FD33;0637 0645;0637 0645; # (ﴳ; ﴳ; ﴳ; طم; طم; ) ARABIC LIGATURE TAH WITH MEEM INITIAL FORM
+FD34;FD34;FD34;0633 062C;0633 062C; # (ﴴ; ﴴ; ﴴ; سج; سج; ) ARABIC LIGATURE SEEN WITH JEEM MEDIAL FORM
+FD35;FD35;FD35;0633 062D;0633 062D; # (ﴵ; ﴵ; ﴵ; سح; سح; ) ARABIC LIGATURE SEEN WITH HAH MEDIAL FORM
+FD36;FD36;FD36;0633 062E;0633 062E; # (ﴶ; ﴶ; ﴶ; سخ; سخ; ) ARABIC LIGATURE SEEN WITH KHAH MEDIAL FORM
+FD37;FD37;FD37;0634 062C;0634 062C; # (ﴷ; ﴷ; ﴷ; شج; شج; ) ARABIC LIGATURE SHEEN WITH JEEM MEDIAL FORM
+FD38;FD38;FD38;0634 062D;0634 062D; # (ﴸ; ﴸ; ﴸ; شح; شح; ) ARABIC LIGATURE SHEEN WITH HAH MEDIAL FORM
+FD39;FD39;FD39;0634 062E;0634 062E; # (ﴹ; ﴹ; ﴹ; شخ; شخ; ) ARABIC LIGATURE SHEEN WITH KHAH MEDIAL FORM
+FD3A;FD3A;FD3A;0637 0645;0637 0645; # (ﴺ; ﴺ; ﴺ; طم; طم; ) ARABIC LIGATURE TAH WITH MEEM MEDIAL FORM
+FD3B;FD3B;FD3B;0638 0645;0638 0645; # (ﴻ; ﴻ; ﴻ; ظم; ظم; ) ARABIC LIGATURE ZAH WITH MEEM MEDIAL FORM
+FD3C;FD3C;FD3C;0627 064B;0627 064B; # (ﴼ; ﴼ; ﴼ; ا◌ً; ا◌ً; ) ARABIC LIGATURE ALEF WITH FATHATAN FINAL FORM
+FD3D;FD3D;FD3D;0627 064B;0627 064B; # (ﴽ; ﴽ; ﴽ; ا◌ً; ا◌ً; ) ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM
+FD50;FD50;FD50;062A 062C 0645;062A 062C 0645; # (ïµ; ïµ; ïµ; تجم; تجم; ) ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM
+FD51;FD51;FD51;062A 062D 062C;062A 062D 062C; # (ﵑ; ﵑ; ﵑ; تحج; تحج; ) ARABIC LIGATURE TEH WITH HAH WITH JEEM FINAL FORM
+FD52;FD52;FD52;062A 062D 062C;062A 062D 062C; # (ﵒ; ﵒ; ﵒ; تحج; تحج; ) ARABIC LIGATURE TEH WITH HAH WITH JEEM INITIAL FORM
+FD53;FD53;FD53;062A 062D 0645;062A 062D 0645; # (ﵓ; ﵓ; ﵓ; تحم; تحم; ) ARABIC LIGATURE TEH WITH HAH WITH MEEM INITIAL FORM
+FD54;FD54;FD54;062A 062E 0645;062A 062E 0645; # (ﵔ; ﵔ; ﵔ; تخم; تخم; ) ARABIC LIGATURE TEH WITH KHAH WITH MEEM INITIAL FORM
+FD55;FD55;FD55;062A 0645 062C;062A 0645 062C; # (ﵕ; ﵕ; ﵕ; تمج; تمج; ) ARABIC LIGATURE TEH WITH MEEM WITH JEEM INITIAL FORM
+FD56;FD56;FD56;062A 0645 062D;062A 0645 062D; # (ﵖ; ﵖ; ﵖ; تمح; تمح; ) ARABIC LIGATURE TEH WITH MEEM WITH HAH INITIAL FORM
+FD57;FD57;FD57;062A 0645 062E;062A 0645 062E; # (ﵗ; ﵗ; ﵗ; تمخ; تمخ; ) ARABIC LIGATURE TEH WITH MEEM WITH KHAH INITIAL FORM
+FD58;FD58;FD58;062C 0645 062D;062C 0645 062D; # (ﵘ; ﵘ; ﵘ; جمح; جمح; ) ARABIC LIGATURE JEEM WITH MEEM WITH HAH FINAL FORM
+FD59;FD59;FD59;062C 0645 062D;062C 0645 062D; # (ﵙ; ﵙ; ﵙ; جمح; جمح; ) ARABIC LIGATURE JEEM WITH MEEM WITH HAH INITIAL FORM
+FD5A;FD5A;FD5A;062D 0645 064A;062D 0645 064A; # (ﵚ; ﵚ; ﵚ; حمي; حمي; ) ARABIC LIGATURE HAH WITH MEEM WITH YEH FINAL FORM
+FD5B;FD5B;FD5B;062D 0645 0649;062D 0645 0649; # (ﵛ; ﵛ; ﵛ; حمى; حمى; ) ARABIC LIGATURE HAH WITH MEEM WITH ALEF MAKSURA FINAL FORM
+FD5C;FD5C;FD5C;0633 062D 062C;0633 062D 062C; # (ﵜ; ﵜ; ﵜ; سحج; سحج; ) ARABIC LIGATURE SEEN WITH HAH WITH JEEM INITIAL FORM
+FD5D;FD5D;FD5D;0633 062C 062D;0633 062C 062D; # (ïµ; ïµ; ïµ; سجح; سجح; ) ARABIC LIGATURE SEEN WITH JEEM WITH HAH INITIAL FORM
+FD5E;FD5E;FD5E;0633 062C 0649;0633 062C 0649; # (ﵞ; ﵞ; ﵞ; سجى; سجى; ) ARABIC LIGATURE SEEN WITH JEEM WITH ALEF MAKSURA FINAL FORM
+FD5F;FD5F;FD5F;0633 0645 062D;0633 0645 062D; # (ﵟ; ﵟ; ﵟ; سمح; سمح; ) ARABIC LIGATURE SEEN WITH MEEM WITH HAH FINAL FORM
+FD60;FD60;FD60;0633 0645 062D;0633 0645 062D; # (ﵠ; ﵠ; ﵠ; سمح; سمح; ) ARABIC LIGATURE SEEN WITH MEEM WITH HAH INITIAL FORM
+FD61;FD61;FD61;0633 0645 062C;0633 0645 062C; # (ﵡ; ﵡ; ﵡ; سمج; سمج; ) ARABIC LIGATURE SEEN WITH MEEM WITH JEEM INITIAL FORM
+FD62;FD62;FD62;0633 0645 0645;0633 0645 0645; # (ﵢ; ﵢ; ﵢ; سمم; سمم; ) ARABIC LIGATURE SEEN WITH MEEM WITH MEEM FINAL FORM
+FD63;FD63;FD63;0633 0645 0645;0633 0645 0645; # (ﵣ; ﵣ; ﵣ; سمم; سمم; ) ARABIC LIGATURE SEEN WITH MEEM WITH MEEM INITIAL FORM
+FD64;FD64;FD64;0635 062D 062D;0635 062D 062D; # (ﵤ; ﵤ; ﵤ; صحح; صحح; ) ARABIC LIGATURE SAD WITH HAH WITH HAH FINAL FORM
+FD65;FD65;FD65;0635 062D 062D;0635 062D 062D; # (ﵥ; ﵥ; ﵥ; صحح; صحح; ) ARABIC LIGATURE SAD WITH HAH WITH HAH INITIAL FORM
+FD66;FD66;FD66;0635 0645 0645;0635 0645 0645; # (ﵦ; ﵦ; ﵦ; صمم; صمم; ) ARABIC LIGATURE SAD WITH MEEM WITH MEEM FINAL FORM
+FD67;FD67;FD67;0634 062D 0645;0634 062D 0645; # (ﵧ; ﵧ; ﵧ; شحم; شحم; ) ARABIC LIGATURE SHEEN WITH HAH WITH MEEM FINAL FORM
+FD68;FD68;FD68;0634 062D 0645;0634 062D 0645; # (ﵨ; ﵨ; ﵨ; شحم; شحم; ) ARABIC LIGATURE SHEEN WITH HAH WITH MEEM INITIAL FORM
+FD69;FD69;FD69;0634 062C 064A;0634 062C 064A; # (ﵩ; ﵩ; ﵩ; شجي; شجي; ) ARABIC LIGATURE SHEEN WITH JEEM WITH YEH FINAL FORM
+FD6A;FD6A;FD6A;0634 0645 062E;0634 0645 062E; # (ﵪ; ﵪ; ﵪ; شمخ; شمخ; ) ARABIC LIGATURE SHEEN WITH MEEM WITH KHAH FINAL FORM
+FD6B;FD6B;FD6B;0634 0645 062E;0634 0645 062E; # (ﵫ; ﵫ; ﵫ; شمخ; شمخ; ) ARABIC LIGATURE SHEEN WITH MEEM WITH KHAH INITIAL FORM
+FD6C;FD6C;FD6C;0634 0645 0645;0634 0645 0645; # (ﵬ; ﵬ; ﵬ; شمم; شمم; ) ARABIC LIGATURE SHEEN WITH MEEM WITH MEEM FINAL FORM
+FD6D;FD6D;FD6D;0634 0645 0645;0634 0645 0645; # (ﵭ; ﵭ; ﵭ; شمم; شمم; ) ARABIC LIGATURE SHEEN WITH MEEM WITH MEEM INITIAL FORM
+FD6E;FD6E;FD6E;0636 062D 0649;0636 062D 0649; # (ﵮ; ﵮ; ﵮ; ضحى; ضحى; ) ARABIC LIGATURE DAD WITH HAH WITH ALEF MAKSURA FINAL FORM
+FD6F;FD6F;FD6F;0636 062E 0645;0636 062E 0645; # (ﵯ; ﵯ; ﵯ; ضخم; ضخم; ) ARABIC LIGATURE DAD WITH KHAH WITH MEEM FINAL FORM
+FD70;FD70;FD70;0636 062E 0645;0636 062E 0645; # (ﵰ; ﵰ; ﵰ; ضخم; ضخم; ) ARABIC LIGATURE DAD WITH KHAH WITH MEEM INITIAL FORM
+FD71;FD71;FD71;0637 0645 062D;0637 0645 062D; # (ﵱ; ﵱ; ﵱ; طمح; طمح; ) ARABIC LIGATURE TAH WITH MEEM WITH HAH FINAL FORM
+FD72;FD72;FD72;0637 0645 062D;0637 0645 062D; # (ﵲ; ﵲ; ﵲ; طمح; طمح; ) ARABIC LIGATURE TAH WITH MEEM WITH HAH INITIAL FORM
+FD73;FD73;FD73;0637 0645 0645;0637 0645 0645; # (ﵳ; ﵳ; ﵳ; طمم; طمم; ) ARABIC LIGATURE TAH WITH MEEM WITH MEEM INITIAL FORM
+FD74;FD74;FD74;0637 0645 064A;0637 0645 064A; # (ﵴ; ﵴ; ﵴ; طمي; طمي; ) ARABIC LIGATURE TAH WITH MEEM WITH YEH FINAL FORM
+FD75;FD75;FD75;0639 062C 0645;0639 062C 0645; # (ﵵ; ﵵ; ﵵ; عجم; عجم; ) ARABIC LIGATURE AIN WITH JEEM WITH MEEM FINAL FORM
+FD76;FD76;FD76;0639 0645 0645;0639 0645 0645; # (ﵶ; ﵶ; ﵶ; عمم; عمم; ) ARABIC LIGATURE AIN WITH MEEM WITH MEEM FINAL FORM
+FD77;FD77;FD77;0639 0645 0645;0639 0645 0645; # (ﵷ; ﵷ; ﵷ; عمم; عمم; ) ARABIC LIGATURE AIN WITH MEEM WITH MEEM INITIAL FORM
+FD78;FD78;FD78;0639 0645 0649;0639 0645 0649; # (ﵸ; ﵸ; ﵸ; عمى; عمى; ) ARABIC LIGATURE AIN WITH MEEM WITH ALEF MAKSURA FINAL FORM
+FD79;FD79;FD79;063A 0645 0645;063A 0645 0645; # (ﵹ; ﵹ; ﵹ; غمم; غمم; ) ARABIC LIGATURE GHAIN WITH MEEM WITH MEEM FINAL FORM
+FD7A;FD7A;FD7A;063A 0645 064A;063A 0645 064A; # (ﵺ; ﵺ; ﵺ; غمي; غمي; ) ARABIC LIGATURE GHAIN WITH MEEM WITH YEH FINAL FORM
+FD7B;FD7B;FD7B;063A 0645 0649;063A 0645 0649; # (ﵻ; ﵻ; ﵻ; غمى; غمى; ) ARABIC LIGATURE GHAIN WITH MEEM WITH ALEF MAKSURA FINAL FORM
+FD7C;FD7C;FD7C;0641 062E 0645;0641 062E 0645; # (ïµ¼; ïµ¼; ïµ¼; Ùخم; Ùخم; ) ARABIC LIGATURE FEH WITH KHAH WITH MEEM FINAL FORM
+FD7D;FD7D;FD7D;0641 062E 0645;0641 062E 0645; # (ïµ½; ïµ½; ïµ½; Ùخم; Ùخم; ) ARABIC LIGATURE FEH WITH KHAH WITH MEEM INITIAL FORM
+FD7E;FD7E;FD7E;0642 0645 062D;0642 0645 062D; # (ﵾ; ﵾ; ﵾ; قمح; قمح; ) ARABIC LIGATURE QAF WITH MEEM WITH HAH FINAL FORM
+FD7F;FD7F;FD7F;0642 0645 0645;0642 0645 0645; # (ﵿ; ﵿ; ﵿ; قمم; قمم; ) ARABIC LIGATURE QAF WITH MEEM WITH MEEM FINAL FORM
+FD80;FD80;FD80;0644 062D 0645;0644 062D 0645; # (ﶀ; ﶀ; ﶀ; لحم; لحم; ) ARABIC LIGATURE LAM WITH HAH WITH MEEM FINAL FORM
+FD81;FD81;FD81;0644 062D 064A;0644 062D 064A; # (ï¶; ï¶; ï¶; لحي; لحي; ) ARABIC LIGATURE LAM WITH HAH WITH YEH FINAL FORM
+FD82;FD82;FD82;0644 062D 0649;0644 062D 0649; # (ﶂ; ﶂ; ﶂ; لحى; لحى; ) ARABIC LIGATURE LAM WITH HAH WITH ALEF MAKSURA FINAL FORM
+FD83;FD83;FD83;0644 062C 062C;0644 062C 062C; # (ﶃ; ﶃ; ﶃ; لجج; لجج; ) ARABIC LIGATURE LAM WITH JEEM WITH JEEM INITIAL FORM
+FD84;FD84;FD84;0644 062C 062C;0644 062C 062C; # (ﶄ; ﶄ; ﶄ; لجج; لجج; ) ARABIC LIGATURE LAM WITH JEEM WITH JEEM FINAL FORM
+FD85;FD85;FD85;0644 062E 0645;0644 062E 0645; # (ﶅ; ﶅ; ﶅ; لخم; لخم; ) ARABIC LIGATURE LAM WITH KHAH WITH MEEM FINAL FORM
+FD86;FD86;FD86;0644 062E 0645;0644 062E 0645; # (ﶆ; ﶆ; ﶆ; لخم; لخم; ) ARABIC LIGATURE LAM WITH KHAH WITH MEEM INITIAL FORM
+FD87;FD87;FD87;0644 0645 062D;0644 0645 062D; # (ﶇ; ﶇ; ﶇ; لمح; لمح; ) ARABIC LIGATURE LAM WITH MEEM WITH HAH FINAL FORM
+FD88;FD88;FD88;0644 0645 062D;0644 0645 062D; # (ﶈ; ﶈ; ﶈ; لمح; لمح; ) ARABIC LIGATURE LAM WITH MEEM WITH HAH INITIAL FORM
+FD89;FD89;FD89;0645 062D 062C;0645 062D 062C; # (ﶉ; ﶉ; ﶉ; محج; محج; ) ARABIC LIGATURE MEEM WITH HAH WITH JEEM INITIAL FORM
+FD8A;FD8A;FD8A;0645 062D 0645;0645 062D 0645; # (ﶊ; ﶊ; ﶊ; محم; محم; ) ARABIC LIGATURE MEEM WITH HAH WITH MEEM INITIAL FORM
+FD8B;FD8B;FD8B;0645 062D 064A;0645 062D 064A; # (ﶋ; ﶋ; ﶋ; محي; محي; ) ARABIC LIGATURE MEEM WITH HAH WITH YEH FINAL FORM
+FD8C;FD8C;FD8C;0645 062C 062D;0645 062C 062D; # (ﶌ; ﶌ; ﶌ; مجح; مجح; ) ARABIC LIGATURE MEEM WITH JEEM WITH HAH INITIAL FORM
+FD8D;FD8D;FD8D;0645 062C 0645;0645 062C 0645; # (ï¶; ï¶; ï¶; مجم; مجم; ) ARABIC LIGATURE MEEM WITH JEEM WITH MEEM INITIAL FORM
+FD8E;FD8E;FD8E;0645 062E 062C;0645 062E 062C; # (ﶎ; ﶎ; ﶎ; مخج; مخج; ) ARABIC LIGATURE MEEM WITH KHAH WITH JEEM INITIAL FORM
+FD8F;FD8F;FD8F;0645 062E 0645;0645 062E 0645; # (ï¶; ï¶; ï¶; مخم; مخم; ) ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM
+FD92;FD92;FD92;0645 062C 062E;0645 062C 062E; # (ﶒ; ﶒ; ﶒ; مجخ; مجخ; ) ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM
+FD93;FD93;FD93;0647 0645 062C;0647 0645 062C; # (ﶓ; ﶓ; ﶓ; همج; همج; ) ARABIC LIGATURE HEH WITH MEEM WITH JEEM INITIAL FORM
+FD94;FD94;FD94;0647 0645 0645;0647 0645 0645; # (ﶔ; ﶔ; ﶔ; همم; همم; ) ARABIC LIGATURE HEH WITH MEEM WITH MEEM INITIAL FORM
+FD95;FD95;FD95;0646 062D 0645;0646 062D 0645; # (ﶕ; ﶕ; ﶕ; نحم; نحم; ) ARABIC LIGATURE NOON WITH HAH WITH MEEM INITIAL FORM
+FD96;FD96;FD96;0646 062D 0649;0646 062D 0649; # (ﶖ; ﶖ; ﶖ; نحى; نحى; ) ARABIC LIGATURE NOON WITH HAH WITH ALEF MAKSURA FINAL FORM
+FD97;FD97;FD97;0646 062C 0645;0646 062C 0645; # (ﶗ; ﶗ; ﶗ; نجم; نجم; ) ARABIC LIGATURE NOON WITH JEEM WITH MEEM FINAL FORM
+FD98;FD98;FD98;0646 062C 0645;0646 062C 0645; # (ﶘ; ﶘ; ﶘ; نجم; نجم; ) ARABIC LIGATURE NOON WITH JEEM WITH MEEM INITIAL FORM
+FD99;FD99;FD99;0646 062C 0649;0646 062C 0649; # (ﶙ; ﶙ; ﶙ; نجى; نجى; ) ARABIC LIGATURE NOON WITH JEEM WITH ALEF MAKSURA FINAL FORM
+FD9A;FD9A;FD9A;0646 0645 064A;0646 0645 064A; # (ﶚ; ﶚ; ﶚ; نمي; نمي; ) ARABIC LIGATURE NOON WITH MEEM WITH YEH FINAL FORM
+FD9B;FD9B;FD9B;0646 0645 0649;0646 0645 0649; # (ﶛ; ﶛ; ﶛ; نمى; نمى; ) ARABIC LIGATURE NOON WITH MEEM WITH ALEF MAKSURA FINAL FORM
+FD9C;FD9C;FD9C;064A 0645 0645;064A 0645 0645; # (ﶜ; ﶜ; ﶜ; يمم; يمم; ) ARABIC LIGATURE YEH WITH MEEM WITH MEEM FINAL FORM
+FD9D;FD9D;FD9D;064A 0645 0645;064A 0645 0645; # (ï¶; ï¶; ï¶; يمم; يمم; ) ARABIC LIGATURE YEH WITH MEEM WITH MEEM INITIAL FORM
+FD9E;FD9E;FD9E;0628 062E 064A;0628 062E 064A; # (ﶞ; ﶞ; ﶞ; بخي; بخي; ) ARABIC LIGATURE BEH WITH KHAH WITH YEH FINAL FORM
+FD9F;FD9F;FD9F;062A 062C 064A;062A 062C 064A; # (ﶟ; ﶟ; ﶟ; تجي; تجي; ) ARABIC LIGATURE TEH WITH JEEM WITH YEH FINAL FORM
+FDA0;FDA0;FDA0;062A 062C 0649;062A 062C 0649; # (ﶠ; ﶠ; ﶠ; تجى; تجى; ) ARABIC LIGATURE TEH WITH JEEM WITH ALEF MAKSURA FINAL FORM
+FDA1;FDA1;FDA1;062A 062E 064A;062A 062E 064A; # (ﶡ; ﶡ; ﶡ; تخي; تخي; ) ARABIC LIGATURE TEH WITH KHAH WITH YEH FINAL FORM
+FDA2;FDA2;FDA2;062A 062E 0649;062A 062E 0649; # (ﶢ; ﶢ; ﶢ; تخى; تخى; ) ARABIC LIGATURE TEH WITH KHAH WITH ALEF MAKSURA FINAL FORM
+FDA3;FDA3;FDA3;062A 0645 064A;062A 0645 064A; # (ﶣ; ﶣ; ﶣ; تمي; تمي; ) ARABIC LIGATURE TEH WITH MEEM WITH YEH FINAL FORM
+FDA4;FDA4;FDA4;062A 0645 0649;062A 0645 0649; # (ﶤ; ﶤ; ﶤ; تمى; تمى; ) ARABIC LIGATURE TEH WITH MEEM WITH ALEF MAKSURA FINAL FORM
+FDA5;FDA5;FDA5;062C 0645 064A;062C 0645 064A; # (ﶥ; ﶥ; ﶥ; جمي; جمي; ) ARABIC LIGATURE JEEM WITH MEEM WITH YEH FINAL FORM
+FDA6;FDA6;FDA6;062C 062D 0649;062C 062D 0649; # (ﶦ; ﶦ; ﶦ; جحى; جحى; ) ARABIC LIGATURE JEEM WITH HAH WITH ALEF MAKSURA FINAL FORM
+FDA7;FDA7;FDA7;062C 0645 0649;062C 0645 0649; # (ﶧ; ﶧ; ﶧ; جمى; جمى; ) ARABIC LIGATURE JEEM WITH MEEM WITH ALEF MAKSURA FINAL FORM
+FDA8;FDA8;FDA8;0633 062E 0649;0633 062E 0649; # (ﶨ; ﶨ; ﶨ; سخى; سخى; ) ARABIC LIGATURE SEEN WITH KHAH WITH ALEF MAKSURA FINAL FORM
+FDA9;FDA9;FDA9;0635 062D 064A;0635 062D 064A; # (ﶩ; ﶩ; ﶩ; صحي; صحي; ) ARABIC LIGATURE SAD WITH HAH WITH YEH FINAL FORM
+FDAA;FDAA;FDAA;0634 062D 064A;0634 062D 064A; # (ﶪ; ﶪ; ﶪ; شحي; شحي; ) ARABIC LIGATURE SHEEN WITH HAH WITH YEH FINAL FORM
+FDAB;FDAB;FDAB;0636 062D 064A;0636 062D 064A; # (ﶫ; ﶫ; ﶫ; ضحي; ضحي; ) ARABIC LIGATURE DAD WITH HAH WITH YEH FINAL FORM
+FDAC;FDAC;FDAC;0644 062C 064A;0644 062C 064A; # (ﶬ; ﶬ; ﶬ; لجي; لجي; ) ARABIC LIGATURE LAM WITH JEEM WITH YEH FINAL FORM
+FDAD;FDAD;FDAD;0644 0645 064A;0644 0645 064A; # (ﶭ; ﶭ; ﶭ; لمي; لمي; ) ARABIC LIGATURE LAM WITH MEEM WITH YEH FINAL FORM
+FDAE;FDAE;FDAE;064A 062D 064A;064A 062D 064A; # (ﶮ; ﶮ; ﶮ; يحي; يحي; ) ARABIC LIGATURE YEH WITH HAH WITH YEH FINAL FORM
+FDAF;FDAF;FDAF;064A 062C 064A;064A 062C 064A; # (ﶯ; ﶯ; ﶯ; يجي; يجي; ) ARABIC LIGATURE YEH WITH JEEM WITH YEH FINAL FORM
+FDB0;FDB0;FDB0;064A 0645 064A;064A 0645 064A; # (ﶰ; ﶰ; ﶰ; يمي; يمي; ) ARABIC LIGATURE YEH WITH MEEM WITH YEH FINAL FORM
+FDB1;FDB1;FDB1;0645 0645 064A;0645 0645 064A; # (ﶱ; ﶱ; ﶱ; ممي; ممي; ) ARABIC LIGATURE MEEM WITH MEEM WITH YEH FINAL FORM
+FDB2;FDB2;FDB2;0642 0645 064A;0642 0645 064A; # (ﶲ; ﶲ; ﶲ; قمي; قمي; ) ARABIC LIGATURE QAF WITH MEEM WITH YEH FINAL FORM
+FDB3;FDB3;FDB3;0646 062D 064A;0646 062D 064A; # (ﶳ; ﶳ; ﶳ; نحي; نحي; ) ARABIC LIGATURE NOON WITH HAH WITH YEH FINAL FORM
+FDB4;FDB4;FDB4;0642 0645 062D;0642 0645 062D; # (ﶴ; ﶴ; ﶴ; قمح; قمح; ) ARABIC LIGATURE QAF WITH MEEM WITH HAH INITIAL FORM
+FDB5;FDB5;FDB5;0644 062D 0645;0644 062D 0645; # (ﶵ; ﶵ; ﶵ; لحم; لحم; ) ARABIC LIGATURE LAM WITH HAH WITH MEEM INITIAL FORM
+FDB6;FDB6;FDB6;0639 0645 064A;0639 0645 064A; # (ﶶ; ﶶ; ﶶ; عمي; عمي; ) ARABIC LIGATURE AIN WITH MEEM WITH YEH FINAL FORM
+FDB7;FDB7;FDB7;0643 0645 064A;0643 0645 064A; # (ﶷ; ﶷ; ﶷ; كمي; كمي; ) ARABIC LIGATURE KAF WITH MEEM WITH YEH FINAL FORM
+FDB8;FDB8;FDB8;0646 062C 062D;0646 062C 062D; # (ﶸ; ﶸ; ﶸ; نجح; نجح; ) ARABIC LIGATURE NOON WITH JEEM WITH HAH INITIAL FORM
+FDB9;FDB9;FDB9;0645 062E 064A;0645 062E 064A; # (ﶹ; ﶹ; ﶹ; مخي; مخي; ) ARABIC LIGATURE MEEM WITH KHAH WITH YEH FINAL FORM
+FDBA;FDBA;FDBA;0644 062C 0645;0644 062C 0645; # (ﶺ; ﶺ; ﶺ; لجم; لجم; ) ARABIC LIGATURE LAM WITH JEEM WITH MEEM INITIAL FORM
+FDBB;FDBB;FDBB;0643 0645 0645;0643 0645 0645; # (ﶻ; ﶻ; ﶻ; كمم; كمم; ) ARABIC LIGATURE KAF WITH MEEM WITH MEEM FINAL FORM
+FDBC;FDBC;FDBC;0644 062C 0645;0644 062C 0645; # (ﶼ; ﶼ; ﶼ; لجم; لجم; ) ARABIC LIGATURE LAM WITH JEEM WITH MEEM FINAL FORM
+FDBD;FDBD;FDBD;0646 062C 062D;0646 062C 062D; # (ﶽ; ﶽ; ﶽ; نجح; نجح; ) ARABIC LIGATURE NOON WITH JEEM WITH HAH FINAL FORM
+FDBE;FDBE;FDBE;062C 062D 064A;062C 062D 064A; # (ﶾ; ﶾ; ﶾ; جحي; جحي; ) ARABIC LIGATURE JEEM WITH HAH WITH YEH FINAL FORM
+FDBF;FDBF;FDBF;062D 062C 064A;062D 062C 064A; # (ﶿ; ﶿ; ﶿ; حجي; حجي; ) ARABIC LIGATURE HAH WITH JEEM WITH YEH FINAL FORM
+FDC0;FDC0;FDC0;0645 062C 064A;0645 062C 064A; # (ﷀ; ﷀ; ﷀ; مجي; مجي; ) ARABIC LIGATURE MEEM WITH JEEM WITH YEH FINAL FORM
+FDC1;FDC1;FDC1;0641 0645 064A;0641 0645 064A; # (ï·; ï·; ï·; Ùمي; Ùمي; ) ARABIC LIGATURE FEH WITH MEEM WITH YEH FINAL FORM
+FDC2;FDC2;FDC2;0628 062D 064A;0628 062D 064A; # (ﷂ; ﷂ; ﷂ; بحي; بحي; ) ARABIC LIGATURE BEH WITH HAH WITH YEH FINAL FORM
+FDC3;FDC3;FDC3;0643 0645 0645;0643 0645 0645; # (ﷃ; ﷃ; ﷃ; كمم; كمم; ) ARABIC LIGATURE KAF WITH MEEM WITH MEEM INITIAL FORM
+FDC4;FDC4;FDC4;0639 062C 0645;0639 062C 0645; # (ﷄ; ﷄ; ﷄ; عجم; عجم; ) ARABIC LIGATURE AIN WITH JEEM WITH MEEM INITIAL FORM
+FDC5;FDC5;FDC5;0635 0645 0645;0635 0645 0645; # (ﷅ; ﷅ; ﷅ; صمم; صمم; ) ARABIC LIGATURE SAD WITH MEEM WITH MEEM INITIAL FORM
+FDC6;FDC6;FDC6;0633 062E 064A;0633 062E 064A; # (ﷆ; ﷆ; ﷆ; سخي; سخي; ) ARABIC LIGATURE SEEN WITH KHAH WITH YEH FINAL FORM
+FDC7;FDC7;FDC7;0646 062C 064A;0646 062C 064A; # (ﷇ; ﷇ; ﷇ; نجي; نجي; ) ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM
+FDF0;FDF0;FDF0;0635 0644 06D2;0635 0644 06D2; # (ﷰ; ﷰ; ﷰ; صلے; صلے; ) ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM
+FDF1;FDF1;FDF1;0642 0644 06D2;0642 0644 06D2; # (ﷱ; ﷱ; ﷱ; قلے; قلے; ) ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN ISOLATED FORM
+FDF2;FDF2;FDF2;0627 0644 0644 0647;0627 0644 0644 0647; # (ﷲ; ﷲ; ﷲ; الله; الله; ) ARABIC LIGATURE ALLAH ISOLATED FORM
+FDF3;FDF3;FDF3;0627 0643 0628 0631;0627 0643 0628 0631; # (ﷳ; ﷳ; ﷳ; اكبر; اكبر; ) ARABIC LIGATURE AKBAR ISOLATED FORM
+FDF4;FDF4;FDF4;0645 062D 0645 062F;0645 062D 0645 062F; # (ﷴ; ﷴ; ﷴ; محمد; محمد; ) ARABIC LIGATURE MOHAMMAD ISOLATED FORM
+FDF5;FDF5;FDF5;0635 0644 0639 0645;0635 0644 0639 0645; # (ﷵ; ﷵ; ﷵ; صلعم; صلعم; ) ARABIC LIGATURE SALAM ISOLATED FORM
+FDF6;FDF6;FDF6;0631 0633 0648 0644;0631 0633 0648 0644; # (ﷶ; ﷶ; ﷶ; رسول; رسول; ) ARABIC LIGATURE RASOUL ISOLATED FORM
+FDF7;FDF7;FDF7;0639 0644 064A 0647;0639 0644 064A 0647; # (ﷷ; ﷷ; ﷷ; عليه; عليه; ) ARABIC LIGATURE ALAYHE ISOLATED FORM
+FDF8;FDF8;FDF8;0648 0633 0644 0645;0648 0633 0644 0645; # (ﷸ; ﷸ; ﷸ; وسلم; وسلم; ) ARABIC LIGATURE WASALLAM ISOLATED FORM
+FDF9;FDF9;FDF9;0635 0644 0649;0635 0644 0649; # (ﷹ; ﷹ; ﷹ; صلى; صلى; ) ARABIC LIGATURE SALLA ISOLATED FORM
+FDFA;FDFA;FDFA;0635 0644 0649 0020 0627 0644 0644 0647 0020 0639 0644 064A 0647 0020 0648 0633 0644 0645;0635 0644 0649 0020 0627 0644 0644 0647 0020 0639 0644 064A 0647 0020 0648 0633 0644 0645; # (ﷺ; ﷺ; ﷺ; صلى الله عليه وسلم; صلى الله عليه وسلم; ) ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM
+FDFB;FDFB;FDFB;062C 0644 0020 062C 0644 0627 0644 0647;062C 0644 0020 062C 0644 0627 0644 0647; # (ﷻ; ﷻ; ﷻ; جل جلاله; جل جلاله; ) ARABIC LIGATURE JALLAJALALOUHOU
+FDFC;FDFC;FDFC;0631 06CC 0627 0644;0631 06CC 0627 0644; # (﷼; ﷼; ﷼; ریال; ریال; ) RIAL SIGN
+FE10;FE10;FE10;002C;002C; # (ï¸; ï¸; ï¸; ,; ,; ) PRESENTATION FORM FOR VERTICAL COMMA
+FE11;FE11;FE11;3001;3001; # (︑; ︑; ︑; ã€; ã€; ) PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC COMMA
+FE12;FE12;FE12;3002;3002; # (︒; ︒; ︒; 。; 。; ) PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC FULL STOP
+FE13;FE13;FE13;003A;003A; # (︓; ︓; ︓; :; :; ) PRESENTATION FORM FOR VERTICAL COLON
+FE14;FE14;FE14;003B;003B; # (︔; ︔; ︔; ;; ;; ) PRESENTATION FORM FOR VERTICAL SEMICOLON
+FE15;FE15;FE15;0021;0021; # (︕; ︕; ︕; !; !; ) PRESENTATION FORM FOR VERTICAL EXCLAMATION MARK
+FE16;FE16;FE16;003F;003F; # (︖; ︖; ︖; ?; ?; ) PRESENTATION FORM FOR VERTICAL QUESTION MARK
+FE17;FE17;FE17;3016;3016; # (︗; ︗; ︗; 〖; 〖; ) PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET
+FE18;FE18;FE18;3017;3017; # (︘; ︘; ︘; 〗; 〗; ) PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET
+FE19;FE19;FE19;002E 002E 002E;002E 002E 002E; # (︙; ︙; ︙; ...; ...; ) PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS
+FE30;FE30;FE30;002E 002E;002E 002E; # (︰; ︰; ︰; ..; ..; ) PRESENTATION FORM FOR VERTICAL TWO DOT LEADER
+FE31;FE31;FE31;2014;2014; # (︱; ︱; ︱; —; —; ) PRESENTATION FORM FOR VERTICAL EM DASH
+FE32;FE32;FE32;2013;2013; # (︲; ︲; ︲; –; –; ) PRESENTATION FORM FOR VERTICAL EN DASH
+FE33;FE33;FE33;005F;005F; # (︳; ︳; ︳; _; _; ) PRESENTATION FORM FOR VERTICAL LOW LINE
+FE34;FE34;FE34;005F;005F; # (︴; ︴; ︴; _; _; ) PRESENTATION FORM FOR VERTICAL WAVY LOW LINE
+FE35;FE35;FE35;0028;0028; # (︵; ︵; ︵; (; (; ) PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS
+FE36;FE36;FE36;0029;0029; # (︶; ︶; ︶; ); ); ) PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS
+FE37;FE37;FE37;007B;007B; # (︷; ︷; ︷; {; {; ) PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET
+FE38;FE38;FE38;007D;007D; # (︸; ︸; ︸; }; }; ) PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET
+FE39;FE39;FE39;3014;3014; # (︹; ︹; ︹; 〔; 〔; ) PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET
+FE3A;FE3A;FE3A;3015;3015; # (︺; ︺; ︺; 〕; 〕; ) PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET
+FE3B;FE3B;FE3B;3010;3010; # (︻; ︻; ︻; ã€; ã€; ) PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET
+FE3C;FE3C;FE3C;3011;3011; # (︼; ︼; ︼; 】; 】; ) PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET
+FE3D;FE3D;FE3D;300A;300A; # (︽; ︽; ︽; 《; 《; ) PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET
+FE3E;FE3E;FE3E;300B;300B; # (︾; ︾; ︾; 》; 》; ) PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET
+FE3F;FE3F;FE3F;3008;3008; # (︿; ︿; ︿; 〈; 〈; ) PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET
+FE40;FE40;FE40;3009;3009; # (﹀; ﹀; ﹀; 〉; 〉; ) PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET
+FE41;FE41;FE41;300C;300C; # (ï¹; ï¹; ï¹; 「; 「; ) PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET
+FE42;FE42;FE42;300D;300D; # (﹂; ﹂; ﹂; ã€; ã€; ) PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET
+FE43;FE43;FE43;300E;300E; # (﹃; ﹃; ﹃; 『; 『; ) PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET
+FE44;FE44;FE44;300F;300F; # (﹄; ﹄; ﹄; ã€; ã€; ) PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET
+FE47;FE47;FE47;005B;005B; # (﹇; ﹇; ﹇; [; [; ) PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET
+FE48;FE48;FE48;005D;005D; # (﹈; ﹈; ﹈; ]; ]; ) PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET
+FE49;FE49;FE49;0020 0305;0020 0305; # (﹉; ﹉; ﹉; ◌̅; ◌̅; ) DASHED OVERLINE
+FE4A;FE4A;FE4A;0020 0305;0020 0305; # (﹊; ﹊; ﹊; ◌̅; ◌̅; ) CENTRELINE OVERLINE
+FE4B;FE4B;FE4B;0020 0305;0020 0305; # (﹋; ﹋; ﹋; ◌̅; ◌̅; ) WAVY OVERLINE
+FE4C;FE4C;FE4C;0020 0305;0020 0305; # (﹌; ﹌; ﹌; ◌̅; ◌̅; ) DOUBLE WAVY OVERLINE
+FE4D;FE4D;FE4D;005F;005F; # (ï¹; ï¹; ï¹; _; _; ) DASHED LOW LINE
+FE4E;FE4E;FE4E;005F;005F; # (﹎; ﹎; ﹎; _; _; ) CENTRELINE LOW LINE
+FE4F;FE4F;FE4F;005F;005F; # (ï¹; ï¹; ï¹; _; _; ) WAVY LOW LINE
+FE50;FE50;FE50;002C;002C; # (ï¹; ï¹; ï¹; ,; ,; ) SMALL COMMA
+FE51;FE51;FE51;3001;3001; # (﹑; ﹑; ﹑; ã€; ã€; ) SMALL IDEOGRAPHIC COMMA
+FE52;FE52;FE52;002E;002E; # (ï¹’; ï¹’; ï¹’; .; .; ) SMALL FULL STOP
+FE54;FE54;FE54;003B;003B; # (ï¹”; ï¹”; ï¹”; ;; ;; ) SMALL SEMICOLON
+FE55;FE55;FE55;003A;003A; # (﹕; ﹕; ﹕; :; :; ) SMALL COLON
+FE56;FE56;FE56;003F;003F; # (ï¹–; ï¹–; ï¹–; ?; ?; ) SMALL QUESTION MARK
+FE57;FE57;FE57;0021;0021; # (ï¹—; ï¹—; ï¹—; !; !; ) SMALL EXCLAMATION MARK
+FE58;FE58;FE58;2014;2014; # (﹘; ﹘; ﹘; —; —; ) SMALL EM DASH
+FE59;FE59;FE59;0028;0028; # (ï¹™; ï¹™; ï¹™; (; (; ) SMALL LEFT PARENTHESIS
+FE5A;FE5A;FE5A;0029;0029; # (﹚; ﹚; ﹚; ); ); ) SMALL RIGHT PARENTHESIS
+FE5B;FE5B;FE5B;007B;007B; # (ï¹›; ï¹›; ï¹›; {; {; ) SMALL LEFT CURLY BRACKET
+FE5C;FE5C;FE5C;007D;007D; # (﹜; ﹜; ﹜; }; }; ) SMALL RIGHT CURLY BRACKET
+FE5D;FE5D;FE5D;3014;3014; # (ï¹; ï¹; ï¹; 〔; 〔; ) SMALL LEFT TORTOISE SHELL BRACKET
+FE5E;FE5E;FE5E;3015;3015; # (﹞; ﹞; ﹞; 〕; 〕; ) SMALL RIGHT TORTOISE SHELL BRACKET
+FE5F;FE5F;FE5F;0023;0023; # (﹟; ﹟; ﹟; #; #; ) SMALL NUMBER SIGN
+FE60;FE60;FE60;0026;0026; # (ï¹ ; ï¹ ; ï¹ ; &; &; ) SMALL AMPERSAND
+FE61;FE61;FE61;002A;002A; # (﹡; ﹡; ﹡; *; *; ) SMALL ASTERISK
+FE62;FE62;FE62;002B;002B; # (ï¹¢; ï¹¢; ï¹¢; +; +; ) SMALL PLUS SIGN
+FE63;FE63;FE63;002D;002D; # (ï¹£; ï¹£; ï¹£; -; -; ) SMALL HYPHEN-MINUS
+FE64;FE64;FE64;003C;003C; # (﹤; ﹤; ﹤; <; <; ) SMALL LESS-THAN SIGN
+FE65;FE65;FE65;003E;003E; # (ï¹¥; ï¹¥; ï¹¥; >; >; ) SMALL GREATER-THAN SIGN
+FE66;FE66;FE66;003D;003D; # (﹦; ﹦; ﹦; =; =; ) SMALL EQUALS SIGN
+FE68;FE68;FE68;005C;005C; # (﹨; ﹨; ﹨; \; \; ) SMALL REVERSE SOLIDUS
+FE69;FE69;FE69;0024;0024; # (﹩; ﹩; ﹩; $; $; ) SMALL DOLLAR SIGN
+FE6A;FE6A;FE6A;0025;0025; # (﹪; ﹪; ﹪; %; %; ) SMALL PERCENT SIGN
+FE6B;FE6B;FE6B;0040;0040; # (﹫; ﹫; ﹫; @; @; ) SMALL COMMERCIAL AT
+FE70;FE70;FE70;0020 064B;0020 064B; # (ﹰ; ﹰ; ﹰ; ◌ً; ◌ً; ) ARABIC FATHATAN ISOLATED FORM
+FE71;FE71;FE71;0640 064B;0640 064B; # (ﹱ; ﹱ; ﹱ; ـ◌ً; ـ◌ً; ) ARABIC TATWEEL WITH FATHATAN ABOVE
+FE72;FE72;FE72;0020 064C;0020 064C; # (ﹲ; ﹲ; ﹲ; ◌ٌ; ◌ٌ; ) ARABIC DAMMATAN ISOLATED FORM
+FE74;FE74;FE74;0020 064D;0020 064D; # (ï¹´; ï¹´; ï¹´; â—ŒÙ; â—ŒÙ; ) ARABIC KASRATAN ISOLATED FORM
+FE76;FE76;FE76;0020 064E;0020 064E; # (ﹶ; ﹶ; ﹶ; ◌َ; ◌َ; ) ARABIC FATHA ISOLATED FORM
+FE77;FE77;FE77;0640 064E;0640 064E; # (ﹷ; ﹷ; ﹷ; ـ◌َ; ـ◌َ; ) ARABIC FATHA MEDIAL FORM
+FE78;FE78;FE78;0020 064F;0020 064F; # (ﹸ; ﹸ; ﹸ; â—ŒÙ; â—ŒÙ; ) ARABIC DAMMA ISOLATED FORM
+FE79;FE79;FE79;0640 064F;0640 064F; # (ï¹¹; ï¹¹; ï¹¹; ـ◌Ù; ـ◌Ù; ) ARABIC DAMMA MEDIAL FORM
+FE7A;FE7A;FE7A;0020 0650;0020 0650; # (ﹺ; ﹺ; ﹺ; â—ŒÙ; â—ŒÙ; ) ARABIC KASRA ISOLATED FORM
+FE7B;FE7B;FE7B;0640 0650;0640 0650; # (ï¹»; ï¹»; ï¹»; ـ◌Ù; ـ◌Ù; ) ARABIC KASRA MEDIAL FORM
+FE7C;FE7C;FE7C;0020 0651;0020 0651; # (ﹼ; ﹼ; ﹼ; ◌ّ; ◌ّ; ) ARABIC SHADDA ISOLATED FORM
+FE7D;FE7D;FE7D;0640 0651;0640 0651; # (ﹽ; ﹽ; ﹽ; ـ◌ّ; ـ◌ّ; ) ARABIC SHADDA MEDIAL FORM
+FE7E;FE7E;FE7E;0020 0652;0020 0652; # (ﹾ; ﹾ; ﹾ; ◌ْ; ◌ْ; ) ARABIC SUKUN ISOLATED FORM
+FE7F;FE7F;FE7F;0640 0652;0640 0652; # (ﹿ; ﹿ; ﹿ; ـ◌ْ; ـ◌ْ; ) ARABIC SUKUN MEDIAL FORM
+FE80;FE80;FE80;0621;0621; # (ﺀ; ﺀ; ﺀ; ء; ء; ) ARABIC LETTER HAMZA ISOLATED FORM
+FE81;FE81;FE81;0622;0627 0653; # (ïº; ïº; ïº; Ø¢; ا◌ٓ; ) ARABIC LETTER ALEF WITH MADDA ABOVE ISOLATED FORM
+FE82;FE82;FE82;0622;0627 0653; # (ﺂ; ﺂ; ﺂ; آ; ا◌ٓ; ) ARABIC LETTER ALEF WITH MADDA ABOVE FINAL FORM
+FE83;FE83;FE83;0623;0627 0654; # (ﺃ; ﺃ; ﺃ; أ; ا◌ٔ; ) ARABIC LETTER ALEF WITH HAMZA ABOVE ISOLATED FORM
+FE84;FE84;FE84;0623;0627 0654; # (ﺄ; ﺄ; ﺄ; أ; ا◌ٔ; ) ARABIC LETTER ALEF WITH HAMZA ABOVE FINAL FORM
+FE85;FE85;FE85;0624;0648 0654; # (ﺅ; ﺅ; ﺅ; ؤ; و◌ٔ; ) ARABIC LETTER WAW WITH HAMZA ABOVE ISOLATED FORM
+FE86;FE86;FE86;0624;0648 0654; # (ﺆ; ﺆ; ﺆ; ؤ; و◌ٔ; ) ARABIC LETTER WAW WITH HAMZA ABOVE FINAL FORM
+FE87;FE87;FE87;0625;0627 0655; # (ﺇ; ﺇ; ﺇ; إ; ا◌ٕ; ) ARABIC LETTER ALEF WITH HAMZA BELOW ISOLATED FORM
+FE88;FE88;FE88;0625;0627 0655; # (ﺈ; ﺈ; ﺈ; إ; ا◌ٕ; ) ARABIC LETTER ALEF WITH HAMZA BELOW FINAL FORM
+FE89;FE89;FE89;0626;064A 0654; # (ﺉ; ﺉ; ﺉ; ئ; ي◌ٔ; ) ARABIC LETTER YEH WITH HAMZA ABOVE ISOLATED FORM
+FE8A;FE8A;FE8A;0626;064A 0654; # (ﺊ; ﺊ; ﺊ; ئ; ي◌ٔ; ) ARABIC LETTER YEH WITH HAMZA ABOVE FINAL FORM
+FE8B;FE8B;FE8B;0626;064A 0654; # (ﺋ; ﺋ; ﺋ; ئ; ي◌ٔ; ) ARABIC LETTER YEH WITH HAMZA ABOVE INITIAL FORM
+FE8C;FE8C;FE8C;0626;064A 0654; # (ﺌ; ﺌ; ﺌ; ئ; ي◌ٔ; ) ARABIC LETTER YEH WITH HAMZA ABOVE MEDIAL FORM
+FE8D;FE8D;FE8D;0627;0627; # (ïº; ïº; ïº; ا; ا; ) ARABIC LETTER ALEF ISOLATED FORM
+FE8E;FE8E;FE8E;0627;0627; # (ﺎ; ﺎ; ﺎ; ا; ا; ) ARABIC LETTER ALEF FINAL FORM
+FE8F;FE8F;FE8F;0628;0628; # (ïº; ïº; ïº; ب; ب; ) ARABIC LETTER BEH ISOLATED FORM
+FE90;FE90;FE90;0628;0628; # (ïº; ïº; ïº; ب; ب; ) ARABIC LETTER BEH FINAL FORM
+FE91;FE91;FE91;0628;0628; # (ﺑ; ﺑ; ﺑ; ب; ب; ) ARABIC LETTER BEH INITIAL FORM
+FE92;FE92;FE92;0628;0628; # (ﺒ; ﺒ; ﺒ; ب; ب; ) ARABIC LETTER BEH MEDIAL FORM
+FE93;FE93;FE93;0629;0629; # (ﺓ; ﺓ; ﺓ; ة; ة; ) ARABIC LETTER TEH MARBUTA ISOLATED FORM
+FE94;FE94;FE94;0629;0629; # (ﺔ; ﺔ; ﺔ; ة; ة; ) ARABIC LETTER TEH MARBUTA FINAL FORM
+FE95;FE95;FE95;062A;062A; # (ﺕ; ﺕ; ﺕ; ت; ت; ) ARABIC LETTER TEH ISOLATED FORM
+FE96;FE96;FE96;062A;062A; # (ﺖ; ﺖ; ﺖ; ت; ت; ) ARABIC LETTER TEH FINAL FORM
+FE97;FE97;FE97;062A;062A; # (ﺗ; ﺗ; ﺗ; ت; ت; ) ARABIC LETTER TEH INITIAL FORM
+FE98;FE98;FE98;062A;062A; # (ﺘ; ﺘ; ﺘ; ت; ت; ) ARABIC LETTER TEH MEDIAL FORM
+FE99;FE99;FE99;062B;062B; # (ﺙ; ﺙ; ﺙ; ث; ث; ) ARABIC LETTER THEH ISOLATED FORM
+FE9A;FE9A;FE9A;062B;062B; # (ﺚ; ﺚ; ﺚ; ث; ث; ) ARABIC LETTER THEH FINAL FORM
+FE9B;FE9B;FE9B;062B;062B; # (ﺛ; ﺛ; ﺛ; ث; ث; ) ARABIC LETTER THEH INITIAL FORM
+FE9C;FE9C;FE9C;062B;062B; # (ﺜ; ﺜ; ﺜ; ث; ث; ) ARABIC LETTER THEH MEDIAL FORM
+FE9D;FE9D;FE9D;062C;062C; # (ïº; ïº; ïº; ج; ج; ) ARABIC LETTER JEEM ISOLATED FORM
+FE9E;FE9E;FE9E;062C;062C; # (ﺞ; ﺞ; ﺞ; ج; ج; ) ARABIC LETTER JEEM FINAL FORM
+FE9F;FE9F;FE9F;062C;062C; # (ﺟ; ﺟ; ﺟ; ج; ج; ) ARABIC LETTER JEEM INITIAL FORM
+FEA0;FEA0;FEA0;062C;062C; # (ﺠ; ﺠ; ﺠ; ج; ج; ) ARABIC LETTER JEEM MEDIAL FORM
+FEA1;FEA1;FEA1;062D;062D; # (ﺡ; ﺡ; ﺡ; ح; ح; ) ARABIC LETTER HAH ISOLATED FORM
+FEA2;FEA2;FEA2;062D;062D; # (ﺢ; ﺢ; ﺢ; ح; ح; ) ARABIC LETTER HAH FINAL FORM
+FEA3;FEA3;FEA3;062D;062D; # (ﺣ; ﺣ; ﺣ; ح; ح; ) ARABIC LETTER HAH INITIAL FORM
+FEA4;FEA4;FEA4;062D;062D; # (ﺤ; ﺤ; ﺤ; ح; ح; ) ARABIC LETTER HAH MEDIAL FORM
+FEA5;FEA5;FEA5;062E;062E; # (ﺥ; ﺥ; ﺥ; خ; خ; ) ARABIC LETTER KHAH ISOLATED FORM
+FEA6;FEA6;FEA6;062E;062E; # (ﺦ; ﺦ; ﺦ; خ; خ; ) ARABIC LETTER KHAH FINAL FORM
+FEA7;FEA7;FEA7;062E;062E; # (ﺧ; ﺧ; ﺧ; خ; خ; ) ARABIC LETTER KHAH INITIAL FORM
+FEA8;FEA8;FEA8;062E;062E; # (ﺨ; ﺨ; ﺨ; خ; خ; ) ARABIC LETTER KHAH MEDIAL FORM
+FEA9;FEA9;FEA9;062F;062F; # (ﺩ; ﺩ; ﺩ; د; د; ) ARABIC LETTER DAL ISOLATED FORM
+FEAA;FEAA;FEAA;062F;062F; # (ﺪ; ﺪ; ﺪ; د; د; ) ARABIC LETTER DAL FINAL FORM
+FEAB;FEAB;FEAB;0630;0630; # (ﺫ; ﺫ; ﺫ; ذ; ذ; ) ARABIC LETTER THAL ISOLATED FORM
+FEAC;FEAC;FEAC;0630;0630; # (ﺬ; ﺬ; ﺬ; ذ; ذ; ) ARABIC LETTER THAL FINAL FORM
+FEAD;FEAD;FEAD;0631;0631; # (ﺭ; ﺭ; ﺭ; ر; ر; ) ARABIC LETTER REH ISOLATED FORM
+FEAE;FEAE;FEAE;0631;0631; # (ﺮ; ﺮ; ﺮ; ر; ر; ) ARABIC LETTER REH FINAL FORM
+FEAF;FEAF;FEAF;0632;0632; # (ﺯ; ﺯ; ﺯ; ز; ز; ) ARABIC LETTER ZAIN ISOLATED FORM
+FEB0;FEB0;FEB0;0632;0632; # (ﺰ; ﺰ; ﺰ; ز; ز; ) ARABIC LETTER ZAIN FINAL FORM
+FEB1;FEB1;FEB1;0633;0633; # (ﺱ; ﺱ; ﺱ; س; س; ) ARABIC LETTER SEEN ISOLATED FORM
+FEB2;FEB2;FEB2;0633;0633; # (ﺲ; ﺲ; ﺲ; س; س; ) ARABIC LETTER SEEN FINAL FORM
+FEB3;FEB3;FEB3;0633;0633; # (ﺳ; ﺳ; ﺳ; س; س; ) ARABIC LETTER SEEN INITIAL FORM
+FEB4;FEB4;FEB4;0633;0633; # (ﺴ; ﺴ; ﺴ; س; س; ) ARABIC LETTER SEEN MEDIAL FORM
+FEB5;FEB5;FEB5;0634;0634; # (ﺵ; ﺵ; ﺵ; ش; ش; ) ARABIC LETTER SHEEN ISOLATED FORM
+FEB6;FEB6;FEB6;0634;0634; # (ﺶ; ﺶ; ﺶ; ش; ش; ) ARABIC LETTER SHEEN FINAL FORM
+FEB7;FEB7;FEB7;0634;0634; # (ﺷ; ﺷ; ﺷ; ش; ش; ) ARABIC LETTER SHEEN INITIAL FORM
+FEB8;FEB8;FEB8;0634;0634; # (ﺸ; ﺸ; ﺸ; ش; ش; ) ARABIC LETTER SHEEN MEDIAL FORM
+FEB9;FEB9;FEB9;0635;0635; # (ﺹ; ﺹ; ﺹ; ص; ص; ) ARABIC LETTER SAD ISOLATED FORM
+FEBA;FEBA;FEBA;0635;0635; # (ﺺ; ﺺ; ﺺ; ص; ص; ) ARABIC LETTER SAD FINAL FORM
+FEBB;FEBB;FEBB;0635;0635; # (ﺻ; ﺻ; ﺻ; ص; ص; ) ARABIC LETTER SAD INITIAL FORM
+FEBC;FEBC;FEBC;0635;0635; # (ﺼ; ﺼ; ﺼ; ص; ص; ) ARABIC LETTER SAD MEDIAL FORM
+FEBD;FEBD;FEBD;0636;0636; # (ﺽ; ﺽ; ﺽ; ض; ض; ) ARABIC LETTER DAD ISOLATED FORM
+FEBE;FEBE;FEBE;0636;0636; # (ﺾ; ﺾ; ﺾ; ض; ض; ) ARABIC LETTER DAD FINAL FORM
+FEBF;FEBF;FEBF;0636;0636; # (ﺿ; ﺿ; ﺿ; ض; ض; ) ARABIC LETTER DAD INITIAL FORM
+FEC0;FEC0;FEC0;0636;0636; # (ﻀ; ﻀ; ﻀ; ض; ض; ) ARABIC LETTER DAD MEDIAL FORM
+FEC1;FEC1;FEC1;0637;0637; # (ï»; ï»; ï»; Ø·; Ø·; ) ARABIC LETTER TAH ISOLATED FORM
+FEC2;FEC2;FEC2;0637;0637; # (ﻂ; ﻂ; ﻂ; ط; ط; ) ARABIC LETTER TAH FINAL FORM
+FEC3;FEC3;FEC3;0637;0637; # (ﻃ; ﻃ; ﻃ; ط; ط; ) ARABIC LETTER TAH INITIAL FORM
+FEC4;FEC4;FEC4;0637;0637; # (ﻄ; ﻄ; ﻄ; ط; ط; ) ARABIC LETTER TAH MEDIAL FORM
+FEC5;FEC5;FEC5;0638;0638; # (ﻅ; ﻅ; ﻅ; ظ; ظ; ) ARABIC LETTER ZAH ISOLATED FORM
+FEC6;FEC6;FEC6;0638;0638; # (ﻆ; ﻆ; ﻆ; ظ; ظ; ) ARABIC LETTER ZAH FINAL FORM
+FEC7;FEC7;FEC7;0638;0638; # (ﻇ; ﻇ; ﻇ; ظ; ظ; ) ARABIC LETTER ZAH INITIAL FORM
+FEC8;FEC8;FEC8;0638;0638; # (ﻈ; ﻈ; ﻈ; ظ; ظ; ) ARABIC LETTER ZAH MEDIAL FORM
+FEC9;FEC9;FEC9;0639;0639; # (ﻉ; ﻉ; ﻉ; ع; ع; ) ARABIC LETTER AIN ISOLATED FORM
+FECA;FECA;FECA;0639;0639; # (ﻊ; ﻊ; ﻊ; ع; ع; ) ARABIC LETTER AIN FINAL FORM
+FECB;FECB;FECB;0639;0639; # (ﻋ; ﻋ; ﻋ; ع; ع; ) ARABIC LETTER AIN INITIAL FORM
+FECC;FECC;FECC;0639;0639; # (ﻌ; ﻌ; ﻌ; ع; ع; ) ARABIC LETTER AIN MEDIAL FORM
+FECD;FECD;FECD;063A;063A; # (ï»; ï»; ï»; غ; غ; ) ARABIC LETTER GHAIN ISOLATED FORM
+FECE;FECE;FECE;063A;063A; # (ﻎ; ﻎ; ﻎ; غ; غ; ) ARABIC LETTER GHAIN FINAL FORM
+FECF;FECF;FECF;063A;063A; # (ï»; ï»; ï»; غ; غ; ) ARABIC LETTER GHAIN INITIAL FORM
+FED0;FED0;FED0;063A;063A; # (ï»; ï»; ï»; غ; غ; ) ARABIC LETTER GHAIN MEDIAL FORM
+FED1;FED1;FED1;0641;0641; # (ﻑ; ﻑ; ﻑ; Ù; Ù; ) ARABIC LETTER FEH ISOLATED FORM
+FED2;FED2;FED2;0641;0641; # (ï»’; ï»’; ï»’; Ù; Ù; ) ARABIC LETTER FEH FINAL FORM
+FED3;FED3;FED3;0641;0641; # (ﻓ; ﻓ; ﻓ; Ù; Ù; ) ARABIC LETTER FEH INITIAL FORM
+FED4;FED4;FED4;0641;0641; # (ï»”; ï»”; ï»”; Ù; Ù; ) ARABIC LETTER FEH MEDIAL FORM
+FED5;FED5;FED5;0642;0642; # (ﻕ; ﻕ; ﻕ; ق; ق; ) ARABIC LETTER QAF ISOLATED FORM
+FED6;FED6;FED6;0642;0642; # (ï»–; ï»–; ï»–; Ù‚; Ù‚; ) ARABIC LETTER QAF FINAL FORM
+FED7;FED7;FED7;0642;0642; # (ï»—; ï»—; ï»—; Ù‚; Ù‚; ) ARABIC LETTER QAF INITIAL FORM
+FED8;FED8;FED8;0642;0642; # (ﻘ; ﻘ; ﻘ; ق; ق; ) ARABIC LETTER QAF MEDIAL FORM
+FED9;FED9;FED9;0643;0643; # (ï»™; ï»™; ï»™; Ùƒ; Ùƒ; ) ARABIC LETTER KAF ISOLATED FORM
+FEDA;FEDA;FEDA;0643;0643; # (ﻚ; ﻚ; ﻚ; ك; ك; ) ARABIC LETTER KAF FINAL FORM
+FEDB;FEDB;FEDB;0643;0643; # (ï»›; ï»›; ï»›; Ùƒ; Ùƒ; ) ARABIC LETTER KAF INITIAL FORM
+FEDC;FEDC;FEDC;0643;0643; # (ﻜ; ﻜ; ﻜ; ك; ك; ) ARABIC LETTER KAF MEDIAL FORM
+FEDD;FEDD;FEDD;0644;0644; # (ï»; ï»; ï»; Ù„; Ù„; ) ARABIC LETTER LAM ISOLATED FORM
+FEDE;FEDE;FEDE;0644;0644; # (ﻞ; ﻞ; ﻞ; ل; ل; ) ARABIC LETTER LAM FINAL FORM
+FEDF;FEDF;FEDF;0644;0644; # (ﻟ; ﻟ; ﻟ; ل; ل; ) ARABIC LETTER LAM INITIAL FORM
+FEE0;FEE0;FEE0;0644;0644; # (ï» ; ï» ; ï» ; Ù„; Ù„; ) ARABIC LETTER LAM MEDIAL FORM
+FEE1;FEE1;FEE1;0645;0645; # (ﻡ; ﻡ; ﻡ; م; م; ) ARABIC LETTER MEEM ISOLATED FORM
+FEE2;FEE2;FEE2;0645;0645; # (ﻢ; ﻢ; ﻢ; م; م; ) ARABIC LETTER MEEM FINAL FORM
+FEE3;FEE3;FEE3;0645;0645; # (ﻣ; ﻣ; ﻣ; م; م; ) ARABIC LETTER MEEM INITIAL FORM
+FEE4;FEE4;FEE4;0645;0645; # (ﻤ; ﻤ; ﻤ; م; م; ) ARABIC LETTER MEEM MEDIAL FORM
+FEE5;FEE5;FEE5;0646;0646; # (ﻥ; ﻥ; ﻥ; ن; ن; ) ARABIC LETTER NOON ISOLATED FORM
+FEE6;FEE6;FEE6;0646;0646; # (ﻦ; ﻦ; ﻦ; ن; ن; ) ARABIC LETTER NOON FINAL FORM
+FEE7;FEE7;FEE7;0646;0646; # (ﻧ; ﻧ; ﻧ; ن; ن; ) ARABIC LETTER NOON INITIAL FORM
+FEE8;FEE8;FEE8;0646;0646; # (ﻨ; ﻨ; ﻨ; ن; ن; ) ARABIC LETTER NOON MEDIAL FORM
+FEE9;FEE9;FEE9;0647;0647; # (ﻩ; ﻩ; ﻩ; ه; ه; ) ARABIC LETTER HEH ISOLATED FORM
+FEEA;FEEA;FEEA;0647;0647; # (ﻪ; ﻪ; ﻪ; ه; ه; ) ARABIC LETTER HEH FINAL FORM
+FEEB;FEEB;FEEB;0647;0647; # (ﻫ; ﻫ; ﻫ; ه; ه; ) ARABIC LETTER HEH INITIAL FORM
+FEEC;FEEC;FEEC;0647;0647; # (ﻬ; ﻬ; ﻬ; ه; ه; ) ARABIC LETTER HEH MEDIAL FORM
+FEED;FEED;FEED;0648;0648; # (ï»­; ï»­; ï»­; Ùˆ; Ùˆ; ) ARABIC LETTER WAW ISOLATED FORM
+FEEE;FEEE;FEEE;0648;0648; # (ï»®; ï»®; ï»®; Ùˆ; Ùˆ; ) ARABIC LETTER WAW FINAL FORM
+FEEF;FEEF;FEEF;0649;0649; # (ﻯ; ﻯ; ﻯ; ى; ى; ) ARABIC LETTER ALEF MAKSURA ISOLATED FORM
+FEF0;FEF0;FEF0;0649;0649; # (ï»°; ï»°; ï»°; Ù‰; Ù‰; ) ARABIC LETTER ALEF MAKSURA FINAL FORM
+FEF1;FEF1;FEF1;064A;064A; # (ï»±; ï»±; ï»±; ÙŠ; ÙŠ; ) ARABIC LETTER YEH ISOLATED FORM
+FEF2;FEF2;FEF2;064A;064A; # (ﻲ; ﻲ; ﻲ; ي; ي; ) ARABIC LETTER YEH FINAL FORM
+FEF3;FEF3;FEF3;064A;064A; # (ﻳ; ﻳ; ﻳ; ي; ي; ) ARABIC LETTER YEH INITIAL FORM
+FEF4;FEF4;FEF4;064A;064A; # (ï»´; ï»´; ï»´; ÙŠ; ÙŠ; ) ARABIC LETTER YEH MEDIAL FORM
+FEF5;FEF5;FEF5;0644 0622;0644 0627 0653; # (ﻵ; ﻵ; ﻵ; لآ; لا◌ٓ; ) ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM
+FEF6;FEF6;FEF6;0644 0622;0644 0627 0653; # (ﻶ; ﻶ; ﻶ; لآ; لا◌ٓ; ) ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM
+FEF7;FEF7;FEF7;0644 0623;0644 0627 0654; # (ﻷ; ﻷ; ﻷ; لأ; لا◌ٔ; ) ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM
+FEF8;FEF8;FEF8;0644 0623;0644 0627 0654; # (ﻸ; ﻸ; ﻸ; لأ; لا◌ٔ; ) ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM
+FEF9;FEF9;FEF9;0644 0625;0644 0627 0655; # (ﻹ; ﻹ; ﻹ; لإ; لا◌ٕ; ) ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM
+FEFA;FEFA;FEFA;0644 0625;0644 0627 0655; # (ﻺ; ﻺ; ﻺ; لإ; لا◌ٕ; ) ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW FINAL FORM
+FEFB;FEFB;FEFB;0644 0627;0644 0627; # (ﻻ; ﻻ; ﻻ; لا; لا; ) ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
+FEFC;FEFC;FEFC;0644 0627;0644 0627; # (ﻼ; ﻼ; ﻼ; لا; لا; ) ARABIC LIGATURE LAM WITH ALEF FINAL FORM
+FF01;FF01;FF01;0021;0021; # (ï¼; ï¼; ï¼; !; !; ) FULLWIDTH EXCLAMATION MARK
+FF02;FF02;FF02;0022;0022; # ("; "; "; "; "; ) FULLWIDTH QUOTATION MARK
+FF03;FF03;FF03;0023;0023; # (#; #; #; #; #; ) FULLWIDTH NUMBER SIGN
+FF04;FF04;FF04;0024;0024; # ($; $; $; $; $; ) FULLWIDTH DOLLAR SIGN
+FF05;FF05;FF05;0025;0025; # (ï¼…; ï¼…; ï¼…; %; %; ) FULLWIDTH PERCENT SIGN
+FF06;FF06;FF06;0026;0026; # (&; &; &; &; &; ) FULLWIDTH AMPERSAND
+FF07;FF07;FF07;0027;0027; # ('; '; '; '; '; ) FULLWIDTH APOSTROPHE
+FF08;FF08;FF08;0028;0028; # ((; (; (; (; (; ) FULLWIDTH LEFT PARENTHESIS
+FF09;FF09;FF09;0029;0029; # (); ); ); ); ); ) FULLWIDTH RIGHT PARENTHESIS
+FF0A;FF0A;FF0A;002A;002A; # (*; *; *; *; *; ) FULLWIDTH ASTERISK
+FF0B;FF0B;FF0B;002B;002B; # (+; +; +; +; +; ) FULLWIDTH PLUS SIGN
+FF0C;FF0C;FF0C;002C;002C; # (,; ,; ,; ,; ,; ) FULLWIDTH COMMA
+FF0D;FF0D;FF0D;002D;002D; # (ï¼; ï¼; ï¼; -; -; ) FULLWIDTH HYPHEN-MINUS
+FF0E;FF0E;FF0E;002E;002E; # (.; .; .; .; .; ) FULLWIDTH FULL STOP
+FF0F;FF0F;FF0F;002F;002F; # (ï¼; ï¼; ï¼; /; /; ) FULLWIDTH SOLIDUS
+FF10;FF10;FF10;0030;0030; # (ï¼; ï¼; ï¼; 0; 0; ) FULLWIDTH DIGIT ZERO
+FF11;FF11;FF11;0031;0031; # (1; 1; 1; 1; 1; ) FULLWIDTH DIGIT ONE
+FF12;FF12;FF12;0032;0032; # (ï¼’; ï¼’; ï¼’; 2; 2; ) FULLWIDTH DIGIT TWO
+FF13;FF13;FF13;0033;0033; # (3; 3; 3; 3; 3; ) FULLWIDTH DIGIT THREE
+FF14;FF14;FF14;0034;0034; # (ï¼”; ï¼”; ï¼”; 4; 4; ) FULLWIDTH DIGIT FOUR
+FF15;FF15;FF15;0035;0035; # (5; 5; 5; 5; 5; ) FULLWIDTH DIGIT FIVE
+FF16;FF16;FF16;0036;0036; # (ï¼–; ï¼–; ï¼–; 6; 6; ) FULLWIDTH DIGIT SIX
+FF17;FF17;FF17;0037;0037; # (ï¼—; ï¼—; ï¼—; 7; 7; ) FULLWIDTH DIGIT SEVEN
+FF18;FF18;FF18;0038;0038; # (8; 8; 8; 8; 8; ) FULLWIDTH DIGIT EIGHT
+FF19;FF19;FF19;0039;0039; # (ï¼™; ï¼™; ï¼™; 9; 9; ) FULLWIDTH DIGIT NINE
+FF1A;FF1A;FF1A;003A;003A; # (:; :; :; :; :; ) FULLWIDTH COLON
+FF1B;FF1B;FF1B;003B;003B; # (ï¼›; ï¼›; ï¼›; ;; ;; ) FULLWIDTH SEMICOLON
+FF1C;FF1C;FF1C;003C;003C; # (<; <; <; <; <; ) FULLWIDTH LESS-THAN SIGN
+FF1D;FF1D;FF1D;003D;003D; # (ï¼; ï¼; ï¼; =; =; ) FULLWIDTH EQUALS SIGN
+FF1E;FF1E;FF1E;003E;003E; # (>; >; >; >; >; ) FULLWIDTH GREATER-THAN SIGN
+FF1F;FF1F;FF1F;003F;003F; # (?; ?; ?; ?; ?; ) FULLWIDTH QUESTION MARK
+FF20;FF20;FF20;0040;0040; # (ï¼ ; ï¼ ; ï¼ ; @; @; ) FULLWIDTH COMMERCIAL AT
+FF21;FF21;FF21;0041;0041; # (A; A; A; A; A; ) FULLWIDTH LATIN CAPITAL LETTER A
+FF22;FF22;FF22;0042;0042; # (ï¼¢; ï¼¢; ï¼¢; B; B; ) FULLWIDTH LATIN CAPITAL LETTER B
+FF23;FF23;FF23;0043;0043; # (ï¼£; ï¼£; ï¼£; C; C; ) FULLWIDTH LATIN CAPITAL LETTER C
+FF24;FF24;FF24;0044;0044; # (D; D; D; D; D; ) FULLWIDTH LATIN CAPITAL LETTER D
+FF25;FF25;FF25;0045;0045; # (ï¼¥; ï¼¥; ï¼¥; E; E; ) FULLWIDTH LATIN CAPITAL LETTER E
+FF26;FF26;FF26;0046;0046; # (F; F; F; F; F; ) FULLWIDTH LATIN CAPITAL LETTER F
+FF27;FF27;FF27;0047;0047; # (G; G; G; G; G; ) FULLWIDTH LATIN CAPITAL LETTER G
+FF28;FF28;FF28;0048;0048; # (H; H; H; H; H; ) FULLWIDTH LATIN CAPITAL LETTER H
+FF29;FF29;FF29;0049;0049; # (I; I; I; I; I; ) FULLWIDTH LATIN CAPITAL LETTER I
+FF2A;FF2A;FF2A;004A;004A; # (J; J; J; J; J; ) FULLWIDTH LATIN CAPITAL LETTER J
+FF2B;FF2B;FF2B;004B;004B; # (K; K; K; K; K; ) FULLWIDTH LATIN CAPITAL LETTER K
+FF2C;FF2C;FF2C;004C;004C; # (L; L; L; L; L; ) FULLWIDTH LATIN CAPITAL LETTER L
+FF2D;FF2D;FF2D;004D;004D; # (ï¼­; ï¼­; ï¼­; M; M; ) FULLWIDTH LATIN CAPITAL LETTER M
+FF2E;FF2E;FF2E;004E;004E; # (ï¼®; ï¼®; ï¼®; N; N; ) FULLWIDTH LATIN CAPITAL LETTER N
+FF2F;FF2F;FF2F;004F;004F; # (O; O; O; O; O; ) FULLWIDTH LATIN CAPITAL LETTER O
+FF30;FF30;FF30;0050;0050; # (ï¼°; ï¼°; ï¼°; P; P; ) FULLWIDTH LATIN CAPITAL LETTER P
+FF31;FF31;FF31;0051;0051; # (ï¼±; ï¼±; ï¼±; Q; Q; ) FULLWIDTH LATIN CAPITAL LETTER Q
+FF32;FF32;FF32;0052;0052; # (ï¼²; ï¼²; ï¼²; R; R; ) FULLWIDTH LATIN CAPITAL LETTER R
+FF33;FF33;FF33;0053;0053; # (ï¼³; ï¼³; ï¼³; S; S; ) FULLWIDTH LATIN CAPITAL LETTER S
+FF34;FF34;FF34;0054;0054; # (ï¼´; ï¼´; ï¼´; T; T; ) FULLWIDTH LATIN CAPITAL LETTER T
+FF35;FF35;FF35;0055;0055; # (ï¼µ; ï¼µ; ï¼µ; U; U; ) FULLWIDTH LATIN CAPITAL LETTER U
+FF36;FF36;FF36;0056;0056; # (V; V; V; V; V; ) FULLWIDTH LATIN CAPITAL LETTER V
+FF37;FF37;FF37;0057;0057; # (ï¼·; ï¼·; ï¼·; W; W; ) FULLWIDTH LATIN CAPITAL LETTER W
+FF38;FF38;FF38;0058;0058; # (X; X; X; X; X; ) FULLWIDTH LATIN CAPITAL LETTER X
+FF39;FF39;FF39;0059;0059; # (ï¼¹; ï¼¹; ï¼¹; Y; Y; ) FULLWIDTH LATIN CAPITAL LETTER Y
+FF3A;FF3A;FF3A;005A;005A; # (Z; Z; Z; Z; Z; ) FULLWIDTH LATIN CAPITAL LETTER Z
+FF3B;FF3B;FF3B;005B;005B; # (ï¼»; ï¼»; ï¼»; [; [; ) FULLWIDTH LEFT SQUARE BRACKET
+FF3C;FF3C;FF3C;005C;005C; # (ï¼¼; ï¼¼; ï¼¼; \; \; ) FULLWIDTH REVERSE SOLIDUS
+FF3D;FF3D;FF3D;005D;005D; # (ï¼½; ï¼½; ï¼½; ]; ]; ) FULLWIDTH RIGHT SQUARE BRACKET
+FF3E;FF3E;FF3E;005E;005E; # (ï¼¾; ï¼¾; ï¼¾; ^; ^; ) FULLWIDTH CIRCUMFLEX ACCENT
+FF3F;FF3F;FF3F;005F;005F; # (_; _; _; _; _; ) FULLWIDTH LOW LINE
+FF40;FF40;FF40;0060;0060; # (ï½€; ï½€; ï½€; `; `; ) FULLWIDTH GRAVE ACCENT
+FF41;FF41;FF41;0061;0061; # (ï½; ï½; ï½; a; a; ) FULLWIDTH LATIN SMALL LETTER A
+FF42;FF42;FF42;0062;0062; # (b; b; b; b; b; ) FULLWIDTH LATIN SMALL LETTER B
+FF43;FF43;FF43;0063;0063; # (c; c; c; c; c; ) FULLWIDTH LATIN SMALL LETTER C
+FF44;FF44;FF44;0064;0064; # (d; d; d; d; d; ) FULLWIDTH LATIN SMALL LETTER D
+FF45;FF45;FF45;0065;0065; # (ï½…; ï½…; ï½…; e; e; ) FULLWIDTH LATIN SMALL LETTER E
+FF46;FF46;FF46;0066;0066; # (f; f; f; f; f; ) FULLWIDTH LATIN SMALL LETTER F
+FF47;FF47;FF47;0067;0067; # (g; g; g; g; g; ) FULLWIDTH LATIN SMALL LETTER G
+FF48;FF48;FF48;0068;0068; # (h; h; h; h; h; ) FULLWIDTH LATIN SMALL LETTER H
+FF49;FF49;FF49;0069;0069; # (i; i; i; i; i; ) FULLWIDTH LATIN SMALL LETTER I
+FF4A;FF4A;FF4A;006A;006A; # (j; j; j; j; j; ) FULLWIDTH LATIN SMALL LETTER J
+FF4B;FF4B;FF4B;006B;006B; # (k; k; k; k; k; ) FULLWIDTH LATIN SMALL LETTER K
+FF4C;FF4C;FF4C;006C;006C; # (l; l; l; l; l; ) FULLWIDTH LATIN SMALL LETTER L
+FF4D;FF4D;FF4D;006D;006D; # (ï½; ï½; ï½; m; m; ) FULLWIDTH LATIN SMALL LETTER M
+FF4E;FF4E;FF4E;006E;006E; # (n; n; n; n; n; ) FULLWIDTH LATIN SMALL LETTER N
+FF4F;FF4F;FF4F;006F;006F; # (ï½; ï½; ï½; o; o; ) FULLWIDTH LATIN SMALL LETTER O
+FF50;FF50;FF50;0070;0070; # (ï½; ï½; ï½; p; p; ) FULLWIDTH LATIN SMALL LETTER P
+FF51;FF51;FF51;0071;0071; # (q; q; q; q; q; ) FULLWIDTH LATIN SMALL LETTER Q
+FF52;FF52;FF52;0072;0072; # (ï½’; ï½’; ï½’; r; r; ) FULLWIDTH LATIN SMALL LETTER R
+FF53;FF53;FF53;0073;0073; # (s; s; s; s; s; ) FULLWIDTH LATIN SMALL LETTER S
+FF54;FF54;FF54;0074;0074; # (ï½”; ï½”; ï½”; t; t; ) FULLWIDTH LATIN SMALL LETTER T
+FF55;FF55;FF55;0075;0075; # (u; u; u; u; u; ) FULLWIDTH LATIN SMALL LETTER U
+FF56;FF56;FF56;0076;0076; # (ï½–; ï½–; ï½–; v; v; ) FULLWIDTH LATIN SMALL LETTER V
+FF57;FF57;FF57;0077;0077; # (ï½—; ï½—; ï½—; w; w; ) FULLWIDTH LATIN SMALL LETTER W
+FF58;FF58;FF58;0078;0078; # (x; x; x; x; x; ) FULLWIDTH LATIN SMALL LETTER X
+FF59;FF59;FF59;0079;0079; # (ï½™; ï½™; ï½™; y; y; ) FULLWIDTH LATIN SMALL LETTER Y
+FF5A;FF5A;FF5A;007A;007A; # (z; z; z; z; z; ) FULLWIDTH LATIN SMALL LETTER Z
+FF5B;FF5B;FF5B;007B;007B; # (ï½›; ï½›; ï½›; {; {; ) FULLWIDTH LEFT CURLY BRACKET
+FF5C;FF5C;FF5C;007C;007C; # (|; |; |; |; |; ) FULLWIDTH VERTICAL LINE
+FF5D;FF5D;FF5D;007D;007D; # (ï½; ï½; ï½; }; }; ) FULLWIDTH RIGHT CURLY BRACKET
+FF5E;FF5E;FF5E;007E;007E; # (~; ~; ~; ~; ~; ) FULLWIDTH TILDE
+FF5F;FF5F;FF5F;2985;2985; # (⦅; ⦅; ⦅; ⦅; ⦅; ) FULLWIDTH LEFT WHITE PARENTHESIS
+FF60;FF60;FF60;2986;2986; # (⦆; ⦆; ⦆; ⦆; ⦆; ) FULLWIDTH RIGHT WHITE PARENTHESIS
+FF61;FF61;FF61;3002;3002; # (。; 。; 。; 。; 。; ) HALFWIDTH IDEOGRAPHIC FULL STOP
+FF62;FF62;FF62;300C;300C; # (「; 「; 「; 「; 「; ) HALFWIDTH LEFT CORNER BRACKET
+FF63;FF63;FF63;300D;300D; # (ï½£; ï½£; ï½£; ã€; ã€; ) HALFWIDTH RIGHT CORNER BRACKET
+FF64;FF64;FF64;3001;3001; # (、; 、; 、; ã€; ã€; ) HALFWIDTH IDEOGRAPHIC COMMA
+FF65;FF65;FF65;30FB;30FB; # (・; ・; ・; ・; ・; ) HALFWIDTH KATAKANA MIDDLE DOT
+FF66;FF66;FF66;30F2;30F2; # (ヲ; ヲ; ヲ; ヲ; ヲ; ) HALFWIDTH KATAKANA LETTER WO
+FF67;FF67;FF67;30A1;30A1; # (ァ; ァ; ァ; ァ; ァ; ) HALFWIDTH KATAKANA LETTER SMALL A
+FF68;FF68;FF68;30A3;30A3; # (ィ; ィ; ィ; ィ; ィ; ) HALFWIDTH KATAKANA LETTER SMALL I
+FF69;FF69;FF69;30A5;30A5; # (ゥ; ゥ; ゥ; ゥ; ゥ; ) HALFWIDTH KATAKANA LETTER SMALL U
+FF6A;FF6A;FF6A;30A7;30A7; # (ェ; ェ; ェ; ェ; ェ; ) HALFWIDTH KATAKANA LETTER SMALL E
+FF6B;FF6B;FF6B;30A9;30A9; # (ォ; ォ; ォ; ォ; ォ; ) HALFWIDTH KATAKANA LETTER SMALL O
+FF6C;FF6C;FF6C;30E3;30E3; # (ャ; ャ; ャ; ャ; ャ; ) HALFWIDTH KATAKANA LETTER SMALL YA
+FF6D;FF6D;FF6D;30E5;30E5; # (ュ; ュ; ュ; ュ; ュ; ) HALFWIDTH KATAKANA LETTER SMALL YU
+FF6E;FF6E;FF6E;30E7;30E7; # (ョ; ョ; ョ; ョ; ョ; ) HALFWIDTH KATAKANA LETTER SMALL YO
+FF6F;FF6F;FF6F;30C3;30C3; # (ッ; ッ; ッ; ッ; ッ; ) HALFWIDTH KATAKANA LETTER SMALL TU
+FF70;FF70;FF70;30FC;30FC; # (ー; ー; ー; ー; ー; ) HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
+FF71;FF71;FF71;30A2;30A2; # (ï½±; ï½±; ï½±; ã‚¢; ã‚¢; ) HALFWIDTH KATAKANA LETTER A
+FF72;FF72;FF72;30A4;30A4; # (イ; イ; イ; イ; イ; ) HALFWIDTH KATAKANA LETTER I
+FF73;FF73;FF73;30A6;30A6; # (ウ; ウ; ウ; ウ; ウ; ) HALFWIDTH KATAKANA LETTER U
+FF74;FF74;FF74;30A8;30A8; # (エ; エ; エ; エ; エ; ) HALFWIDTH KATAKANA LETTER E
+FF75;FF75;FF75;30AA;30AA; # (オ; オ; オ; オ; オ; ) HALFWIDTH KATAKANA LETTER O
+FF76;FF76;FF76;30AB;30AB; # (カ; カ; カ; カ; カ; ) HALFWIDTH KATAKANA LETTER KA
+FF77;FF77;FF77;30AD;30AD; # (ï½·; ï½·; ï½·; ã‚­; ã‚­; ) HALFWIDTH KATAKANA LETTER KI
+FF78;FF78;FF78;30AF;30AF; # (ク; ク; ク; ク; ク; ) HALFWIDTH KATAKANA LETTER KU
+FF79;FF79;FF79;30B1;30B1; # (ケ; ケ; ケ; ケ; ケ; ) HALFWIDTH KATAKANA LETTER KE
+FF7A;FF7A;FF7A;30B3;30B3; # (コ; コ; コ; コ; コ; ) HALFWIDTH KATAKANA LETTER KO
+FF7B;FF7B;FF7B;30B5;30B5; # (サ; サ; サ; サ; サ; ) HALFWIDTH KATAKANA LETTER SA
+FF7C;FF7C;FF7C;30B7;30B7; # (ï½¼; ï½¼; ï½¼; ã‚·; ã‚·; ) HALFWIDTH KATAKANA LETTER SI
+FF7D;FF7D;FF7D;30B9;30B9; # (ス; ス; ス; ス; ス; ) HALFWIDTH KATAKANA LETTER SU
+FF7E;FF7E;FF7E;30BB;30BB; # (ï½¾; ï½¾; ï½¾; ã‚»; ã‚»; ) HALFWIDTH KATAKANA LETTER SE
+FF7F;FF7F;FF7F;30BD;30BD; # (ソ; ソ; ソ; ソ; ソ; ) HALFWIDTH KATAKANA LETTER SO
+FF80;FF80;FF80;30BF;30BF; # (ï¾€; ï¾€; ï¾€; ã‚¿; ã‚¿; ) HALFWIDTH KATAKANA LETTER TA
+FF81;FF81;FF81;30C1;30C1; # (ï¾; ï¾; ï¾; ãƒ; ãƒ; ) HALFWIDTH KATAKANA LETTER TI
+FF82;FF82;FF82;30C4;30C4; # (ツ; ツ; ツ; ツ; ツ; ) HALFWIDTH KATAKANA LETTER TU
+FF83;FF83;FF83;30C6;30C6; # (テ; テ; テ; テ; テ; ) HALFWIDTH KATAKANA LETTER TE
+FF84;FF84;FF84;30C8;30C8; # (ト; ト; ト; ト; ト; ) HALFWIDTH KATAKANA LETTER TO
+FF85;FF85;FF85;30CA;30CA; # (ナ; ナ; ナ; ナ; ナ; ) HALFWIDTH KATAKANA LETTER NA
+FF86;FF86;FF86;30CB;30CB; # (ニ; ニ; ニ; ニ; ニ; ) HALFWIDTH KATAKANA LETTER NI
+FF87;FF87;FF87;30CC;30CC; # (ヌ; ヌ; ヌ; ヌ; ヌ; ) HALFWIDTH KATAKANA LETTER NU
+FF88;FF88;FF88;30CD;30CD; # (ネ; ネ; ネ; ãƒ; ãƒ; ) HALFWIDTH KATAKANA LETTER NE
+FF89;FF89;FF89;30CE;30CE; # (ノ; ノ; ノ; ノ; ノ; ) HALFWIDTH KATAKANA LETTER NO
+FF8A;FF8A;FF8A;30CF;30CF; # (ハ; ハ; ハ; ãƒ; ãƒ; ) HALFWIDTH KATAKANA LETTER HA
+FF8B;FF8B;FF8B;30D2;30D2; # (ヒ; ヒ; ヒ; ヒ; ヒ; ) HALFWIDTH KATAKANA LETTER HI
+FF8C;FF8C;FF8C;30D5;30D5; # (フ; フ; フ; フ; フ; ) HALFWIDTH KATAKANA LETTER HU
+FF8D;FF8D;FF8D;30D8;30D8; # (ï¾; ï¾; ï¾; ヘ; ヘ; ) HALFWIDTH KATAKANA LETTER HE
+FF8E;FF8E;FF8E;30DB;30DB; # (ホ; ホ; ホ; ホ; ホ; ) HALFWIDTH KATAKANA LETTER HO
+FF8F;FF8F;FF8F;30DE;30DE; # (ï¾; ï¾; ï¾; マ; マ; ) HALFWIDTH KATAKANA LETTER MA
+FF90;FF90;FF90;30DF;30DF; # (ï¾; ï¾; ï¾; ミ; ミ; ) HALFWIDTH KATAKANA LETTER MI
+FF91;FF91;FF91;30E0;30E0; # (ム; ム; ム; ム; ム; ) HALFWIDTH KATAKANA LETTER MU
+FF92;FF92;FF92;30E1;30E1; # (メ; メ; メ; メ; メ; ) HALFWIDTH KATAKANA LETTER ME
+FF93;FF93;FF93;30E2;30E2; # (モ; モ; モ; モ; モ; ) HALFWIDTH KATAKANA LETTER MO
+FF94;FF94;FF94;30E4;30E4; # (ヤ; ヤ; ヤ; ヤ; ヤ; ) HALFWIDTH KATAKANA LETTER YA
+FF95;FF95;FF95;30E6;30E6; # (ユ; ユ; ユ; ユ; ユ; ) HALFWIDTH KATAKANA LETTER YU
+FF96;FF96;FF96;30E8;30E8; # (ヨ; ヨ; ヨ; ヨ; ヨ; ) HALFWIDTH KATAKANA LETTER YO
+FF97;FF97;FF97;30E9;30E9; # (ラ; ラ; ラ; ラ; ラ; ) HALFWIDTH KATAKANA LETTER RA
+FF98;FF98;FF98;30EA;30EA; # (リ; リ; リ; リ; リ; ) HALFWIDTH KATAKANA LETTER RI
+FF99;FF99;FF99;30EB;30EB; # (ル; ル; ル; ル; ル; ) HALFWIDTH KATAKANA LETTER RU
+FF9A;FF9A;FF9A;30EC;30EC; # (レ; レ; レ; レ; レ; ) HALFWIDTH KATAKANA LETTER RE
+FF9B;FF9B;FF9B;30ED;30ED; # (ロ; ロ; ロ; ロ; ロ; ) HALFWIDTH KATAKANA LETTER RO
+FF9C;FF9C;FF9C;30EF;30EF; # (ワ; ワ; ワ; ワ; ワ; ) HALFWIDTH KATAKANA LETTER WA
+FF9D;FF9D;FF9D;30F3;30F3; # (ï¾; ï¾; ï¾; ン; ン; ) HALFWIDTH KATAKANA LETTER N
+FF9E;FF9E;FF9E;3099;3099; # (゙; ゙; ゙; ◌゙; ◌゙; ) HALFWIDTH KATAKANA VOICED SOUND MARK
+FF9F;FF9F;FF9F;309A;309A; # (゚; ゚; ゚; ◌゚; ◌゚; ) HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+FFA0;FFA0;FFA0;1160;1160; # (ï¾ ; ï¾ ; ï¾ ; á… ; á… ; ) HALFWIDTH HANGUL FILLER
+FFA1;FFA1;FFA1;1100;1100; # (ᄀ; ᄀ; ᄀ; ᄀ; ᄀ; ) HALFWIDTH HANGUL LETTER KIYEOK
+FFA2;FFA2;FFA2;1101;1101; # (ï¾¢; ï¾¢; ï¾¢; á„; á„; ) HALFWIDTH HANGUL LETTER SSANGKIYEOK
+FFA3;FFA3;FFA3;11AA;11AA; # (ᆪ; ᆪ; ᆪ; ᆪ; ᆪ; ) HALFWIDTH HANGUL LETTER KIYEOK-SIOS
+FFA4;FFA4;FFA4;1102;1102; # (ᄂ; ᄂ; ᄂ; ᄂ; ᄂ; ) HALFWIDTH HANGUL LETTER NIEUN
+FFA5;FFA5;FFA5;11AC;11AC; # (ᆬ; ᆬ; ᆬ; ᆬ; ᆬ; ) HALFWIDTH HANGUL LETTER NIEUN-CIEUC
+FFA6;FFA6;FFA6;11AD;11AD; # (ᆭ; ᆭ; ᆭ; ᆭ; ᆭ; ) HALFWIDTH HANGUL LETTER NIEUN-HIEUH
+FFA7;FFA7;FFA7;1103;1103; # (ᄃ; ᄃ; ᄃ; ᄃ; ᄃ; ) HALFWIDTH HANGUL LETTER TIKEUT
+FFA8;FFA8;FFA8;1104;1104; # (ᄄ; ᄄ; ᄄ; ᄄ; ᄄ; ) HALFWIDTH HANGUL LETTER SSANGTIKEUT
+FFA9;FFA9;FFA9;1105;1105; # (ᄅ; ᄅ; ᄅ; ᄅ; ᄅ; ) HALFWIDTH HANGUL LETTER RIEUL
+FFAA;FFAA;FFAA;11B0;11B0; # (ᆰ; ᆰ; ᆰ; ᆰ; ᆰ; ) HALFWIDTH HANGUL LETTER RIEUL-KIYEOK
+FFAB;FFAB;FFAB;11B1;11B1; # (ᆱ; ᆱ; ᆱ; ᆱ; ᆱ; ) HALFWIDTH HANGUL LETTER RIEUL-MIEUM
+FFAC;FFAC;FFAC;11B2;11B2; # (ᆲ; ᆲ; ᆲ; ᆲ; ᆲ; ) HALFWIDTH HANGUL LETTER RIEUL-PIEUP
+FFAD;FFAD;FFAD;11B3;11B3; # (ᆳ; ᆳ; ᆳ; ᆳ; ᆳ; ) HALFWIDTH HANGUL LETTER RIEUL-SIOS
+FFAE;FFAE;FFAE;11B4;11B4; # (ᆴ; ᆴ; ᆴ; ᆴ; ᆴ; ) HALFWIDTH HANGUL LETTER RIEUL-THIEUTH
+FFAF;FFAF;FFAF;11B5;11B5; # (ᆵ; ᆵ; ᆵ; ᆵ; ᆵ; ) HALFWIDTH HANGUL LETTER RIEUL-PHIEUPH
+FFB0;FFB0;FFB0;111A;111A; # (ï¾°; ï¾°; ï¾°; á„š; á„š; ) HALFWIDTH HANGUL LETTER RIEUL-HIEUH
+FFB1;FFB1;FFB1;1106;1106; # (ᄆ; ᄆ; ᄆ; ᄆ; ᄆ; ) HALFWIDTH HANGUL LETTER MIEUM
+FFB2;FFB2;FFB2;1107;1107; # (ᄇ; ᄇ; ᄇ; ᄇ; ᄇ; ) HALFWIDTH HANGUL LETTER PIEUP
+FFB3;FFB3;FFB3;1108;1108; # (ᄈ; ᄈ; ᄈ; ᄈ; ᄈ; ) HALFWIDTH HANGUL LETTER SSANGPIEUP
+FFB4;FFB4;FFB4;1121;1121; # (ï¾´; ï¾´; ï¾´; á„¡; á„¡; ) HALFWIDTH HANGUL LETTER PIEUP-SIOS
+FFB5;FFB5;FFB5;1109;1109; # (ᄉ; ᄉ; ᄉ; ᄉ; ᄉ; ) HALFWIDTH HANGUL LETTER SIOS
+FFB6;FFB6;FFB6;110A;110A; # (ᄊ; ᄊ; ᄊ; ᄊ; ᄊ; ) HALFWIDTH HANGUL LETTER SSANGSIOS
+FFB7;FFB7;FFB7;110B;110B; # (ï¾·; ï¾·; ï¾·; á„‹; á„‹; ) HALFWIDTH HANGUL LETTER IEUNG
+FFB8;FFB8;FFB8;110C;110C; # (ᄌ; ᄌ; ᄌ; ᄌ; ᄌ; ) HALFWIDTH HANGUL LETTER CIEUC
+FFB9;FFB9;FFB9;110D;110D; # (ï¾¹; ï¾¹; ï¾¹; á„; á„; ) HALFWIDTH HANGUL LETTER SSANGCIEUC
+FFBA;FFBA;FFBA;110E;110E; # (ᄎ; ᄎ; ᄎ; ᄎ; ᄎ; ) HALFWIDTH HANGUL LETTER CHIEUCH
+FFBB;FFBB;FFBB;110F;110F; # (ï¾»; ï¾»; ï¾»; á„; á„; ) HALFWIDTH HANGUL LETTER KHIEUKH
+FFBC;FFBC;FFBC;1110;1110; # (ï¾¼; ï¾¼; ï¾¼; á„; á„; ) HALFWIDTH HANGUL LETTER THIEUTH
+FFBD;FFBD;FFBD;1111;1111; # (ï¾½; ï¾½; ï¾½; á„‘; á„‘; ) HALFWIDTH HANGUL LETTER PHIEUPH
+FFBE;FFBE;FFBE;1112;1112; # (ï¾¾; ï¾¾; ï¾¾; á„’; á„’; ) HALFWIDTH HANGUL LETTER HIEUH
+FFC2;FFC2;FFC2;1161;1161; # (ï¿‚; ï¿‚; ï¿‚; á…¡; á…¡; ) HALFWIDTH HANGUL LETTER A
+FFC3;FFC3;FFC3;1162;1162; # (ᅢ; ᅢ; ᅢ; ᅢ; ᅢ; ) HALFWIDTH HANGUL LETTER AE
+FFC4;FFC4;FFC4;1163;1163; # (ï¿„; ï¿„; ï¿„; á…£; á…£; ) HALFWIDTH HANGUL LETTER YA
+FFC5;FFC5;FFC5;1164;1164; # (ï¿…; ï¿…; ï¿…; á…¤; á…¤; ) HALFWIDTH HANGUL LETTER YAE
+FFC6;FFC6;FFC6;1165;1165; # (ᅥ; ᅥ; ᅥ; ᅥ; ᅥ; ) HALFWIDTH HANGUL LETTER EO
+FFC7;FFC7;FFC7;1166;1166; # (ᅦ; ᅦ; ᅦ; ᅦ; ᅦ; ) HALFWIDTH HANGUL LETTER E
+FFCA;FFCA;FFCA;1167;1167; # (ï¿Š; ï¿Š; ï¿Š; á…§; á…§; ) HALFWIDTH HANGUL LETTER YEO
+FFCB;FFCB;FFCB;1168;1168; # (ï¿‹; ï¿‹; ï¿‹; á…¨; á…¨; ) HALFWIDTH HANGUL LETTER YE
+FFCC;FFCC;FFCC;1169;1169; # (ᅩ; ᅩ; ᅩ; ᅩ; ᅩ; ) HALFWIDTH HANGUL LETTER O
+FFCD;FFCD;FFCD;116A;116A; # (ï¿; ï¿; ï¿; á…ª; á…ª; ) HALFWIDTH HANGUL LETTER WA
+FFCE;FFCE;FFCE;116B;116B; # (ï¿Ž; ï¿Ž; ï¿Ž; á…«; á…«; ) HALFWIDTH HANGUL LETTER WAE
+FFCF;FFCF;FFCF;116C;116C; # (ï¿; ï¿; ï¿; á…¬; á…¬; ) HALFWIDTH HANGUL LETTER OE
+FFD2;FFD2;FFD2;116D;116D; # (ï¿’; ï¿’; ï¿’; á…­; á…­; ) HALFWIDTH HANGUL LETTER YO
+FFD3;FFD3;FFD3;116E;116E; # (ï¿“; ï¿“; ï¿“; á…®; á…®; ) HALFWIDTH HANGUL LETTER U
+FFD4;FFD4;FFD4;116F;116F; # (ï¿”; ï¿”; ï¿”; á…¯; á…¯; ) HALFWIDTH HANGUL LETTER WEO
+FFD5;FFD5;FFD5;1170;1170; # (ï¿•; ï¿•; ï¿•; á…°; á…°; ) HALFWIDTH HANGUL LETTER WE
+FFD6;FFD6;FFD6;1171;1171; # (ï¿–; ï¿–; ï¿–; á…±; á…±; ) HALFWIDTH HANGUL LETTER WI
+FFD7;FFD7;FFD7;1172;1172; # (ï¿—; ï¿—; ï¿—; á…²; á…²; ) HALFWIDTH HANGUL LETTER YU
+FFDA;FFDA;FFDA;1173;1173; # (ï¿š; ï¿š; ï¿š; á…³; á…³; ) HALFWIDTH HANGUL LETTER EU
+FFDB;FFDB;FFDB;1174;1174; # (ï¿›; ï¿›; ï¿›; á…´; á…´; ) HALFWIDTH HANGUL LETTER YI
+FFDC;FFDC;FFDC;1175;1175; # (ᅵ; ᅵ; ᅵ; ᅵ; ᅵ; ) HALFWIDTH HANGUL LETTER I
+FFE0;FFE0;FFE0;00A2;00A2; # (¢; ¢; ¢; ¢; ¢; ) FULLWIDTH CENT SIGN
+FFE1;FFE1;FFE1;00A3;00A3; # (£; £; £; £; £; ) FULLWIDTH POUND SIGN
+FFE2;FFE2;FFE2;00AC;00AC; # (¬; ¬; ¬; ¬; ¬; ) FULLWIDTH NOT SIGN
+FFE3;FFE3;FFE3;0020 0304;0020 0304; # ( ̄;  ̄;  ̄; ◌̄; ◌̄; ) FULLWIDTH MACRON
+FFE4;FFE4;FFE4;00A6;00A6; # (¦; ¦; ¦; ¦; ¦; ) FULLWIDTH BROKEN BAR
+FFE5;FFE5;FFE5;00A5;00A5; # (¥; ¥; ¥; ¥; ¥; ) FULLWIDTH YEN SIGN
+FFE6;FFE6;FFE6;20A9;20A9; # (₩; ₩; ₩; ₩; ₩; ) FULLWIDTH WON SIGN
+FFE8;FFE8;FFE8;2502;2502; # (│; │; │; │; │; ) HALFWIDTH FORMS LIGHT VERTICAL
+FFE9;FFE9;FFE9;2190;2190; # (ï¿©; ï¿©; ï¿©; â†; â†; ) HALFWIDTH LEFTWARDS ARROW
+FFEA;FFEA;FFEA;2191;2191; # (↑; ↑; ↑; ↑; ↑; ) HALFWIDTH UPWARDS ARROW
+FFEB;FFEB;FFEB;2192;2192; # (→; →; →; →; →; ) HALFWIDTH RIGHTWARDS ARROW
+FFEC;FFEC;FFEC;2193;2193; # (↓; ↓; ↓; ↓; ↓; ) HALFWIDTH DOWNWARDS ARROW
+FFED;FFED;FFED;25A0;25A0; # (ï¿­; ï¿­; ï¿­; â– ; â– ; ) HALFWIDTH BLACK SQUARE
+FFEE;FFEE;FFEE;25CB;25CB; # (ï¿®; ï¿®; ï¿®; â—‹; â—‹; ) HALFWIDTH WHITE CIRCLE
+10781;10781;10781;02D0;02D0; # (ðž; ðž; ðž; Ë; Ë; ) MODIFIER LETTER SUPERSCRIPT TRIANGULAR COLON
+10782;10782;10782;02D1;02D1; # (ðž‚; ðž‚; ðž‚; Ë‘; Ë‘; ) MODIFIER LETTER SUPERSCRIPT HALF TRIANGULAR COLON
+10783;10783;10783;00E6;00E6; # (ðžƒ; ðžƒ; ðžƒ; æ; æ; ) MODIFIER LETTER SMALL AE
+10784;10784;10784;0299;0299; # (ðž„; ðž„; ðž„; Ê™; Ê™; ) MODIFIER LETTER SMALL CAPITAL B
+10785;10785;10785;0253;0253; # (ðž…; ðž…; ðž…; É“; É“; ) MODIFIER LETTER SMALL B WITH HOOK
+10787;10787;10787;02A3;02A3; # (ðž‡; ðž‡; ðž‡; Ê£; Ê£; ) MODIFIER LETTER SMALL DZ DIGRAPH
+10788;10788;10788;AB66;AB66; # (ðžˆ; ðžˆ; ðžˆ; ê­¦; ê­¦; ) MODIFIER LETTER SMALL DZ DIGRAPH WITH RETROFLEX HOOK
+10789;10789;10789;02A5;02A5; # (ðž‰; ðž‰; ðž‰; Ê¥; Ê¥; ) MODIFIER LETTER SMALL DZ DIGRAPH WITH CURL
+1078A;1078A;1078A;02A4;02A4; # (ðžŠ; ðžŠ; ðžŠ; ʤ; ʤ; ) MODIFIER LETTER SMALL DEZH DIGRAPH
+1078B;1078B;1078B;0256;0256; # (ðž‹; ðž‹; ðž‹; É–; É–; ) MODIFIER LETTER SMALL D WITH TAIL
+1078C;1078C;1078C;0257;0257; # (ðžŒ; ðžŒ; ðžŒ; É—; É—; ) MODIFIER LETTER SMALL D WITH HOOK
+1078D;1078D;1078D;1D91;1D91; # (ðž; ðž; ðž; ᶑ; ᶑ; ) MODIFIER LETTER SMALL D WITH HOOK AND TAIL
+1078E;1078E;1078E;0258;0258; # (ðžŽ; ðžŽ; ðžŽ; ɘ; ɘ; ) MODIFIER LETTER SMALL REVERSED E
+1078F;1078F;1078F;025E;025E; # (ðž; ðž; ðž; Éž; Éž; ) MODIFIER LETTER SMALL CLOSED REVERSED OPEN E
+10790;10790;10790;02A9;02A9; # (ðž; ðž; ðž; Ê©; Ê©; ) MODIFIER LETTER SMALL FENG DIGRAPH
+10791;10791;10791;0264;0264; # (ðž‘; ðž‘; ðž‘; ɤ; ɤ; ) MODIFIER LETTER SMALL RAMS HORN
+10792;10792;10792;0262;0262; # (ðž’; ðž’; ðž’; É¢; É¢; ) MODIFIER LETTER SMALL CAPITAL G
+10793;10793;10793;0260;0260; # (ðž“; ðž“; ðž“; É ; É ; ) MODIFIER LETTER SMALL G WITH HOOK
+10794;10794;10794;029B;029B; # (ðž”; ðž”; ðž”; Ê›; Ê›; ) MODIFIER LETTER SMALL CAPITAL G WITH HOOK
+10795;10795;10795;0127;0127; # (ðž•; ðž•; ðž•; ħ; ħ; ) MODIFIER LETTER SMALL H WITH STROKE
+10796;10796;10796;029C;029C; # (ðž–; ðž–; ðž–; Êœ; Êœ; ) MODIFIER LETTER SMALL CAPITAL H
+10797;10797;10797;0267;0267; # (ðž—; ðž—; ðž—; ɧ; ɧ; ) MODIFIER LETTER SMALL HENG WITH HOOK
+10798;10798;10798;0284;0284; # (ðž˜; ðž˜; ðž˜; Ê„; Ê„; ) MODIFIER LETTER SMALL DOTLESS J WITH STROKE AND HOOK
+10799;10799;10799;02AA;02AA; # (ðž™; ðž™; ðž™; ʪ; ʪ; ) MODIFIER LETTER SMALL LS DIGRAPH
+1079A;1079A;1079A;02AB;02AB; # (ðžš; ðžš; ðžš; Ê«; Ê«; ) MODIFIER LETTER SMALL LZ DIGRAPH
+1079B;1079B;1079B;026C;026C; # (ðž›; ðž›; ðž›; ɬ; ɬ; ) MODIFIER LETTER SMALL L WITH BELT
+1079C;1079C;1079C;1DF04;1DF04; # (ðžœ; ðžœ; ðžœ; ð¼„; ð¼„; ) MODIFIER LETTER SMALL CAPITAL L WITH BELT
+1079D;1079D;1079D;A78E;A78E; # (ðž; ðž; ðž; ꞎ; ꞎ; ) MODIFIER LETTER SMALL L WITH RETROFLEX HOOK AND BELT
+1079E;1079E;1079E;026E;026E; # (ðžž; ðžž; ðžž; É®; É®; ) MODIFIER LETTER SMALL LEZH
+1079F;1079F;1079F;1DF05;1DF05; # (ðžŸ; ðžŸ; ðžŸ; ð¼…; ð¼…; ) MODIFIER LETTER SMALL LEZH WITH RETROFLEX HOOK
+107A0;107A0;107A0;028E;028E; # (ðž ; ðž ; ðž ; ÊŽ; ÊŽ; ) MODIFIER LETTER SMALL TURNED Y
+107A1;107A1;107A1;1DF06;1DF06; # (ðž¡; ðž¡; ðž¡; ð¼†; ð¼†; ) MODIFIER LETTER SMALL TURNED Y WITH BELT
+107A2;107A2;107A2;00F8;00F8; # (ðž¢; ðž¢; ðž¢; ø; ø; ) MODIFIER LETTER SMALL O WITH STROKE
+107A3;107A3;107A3;0276;0276; # (ðž£; ðž£; ðž£; ɶ; ɶ; ) MODIFIER LETTER SMALL CAPITAL OE
+107A4;107A4;107A4;0277;0277; # (ðž¤; ðž¤; ðž¤; É·; É·; ) MODIFIER LETTER SMALL CLOSED OMEGA
+107A5;107A5;107A5;0071;0071; # (ðž¥; ðž¥; ðž¥; q; q; ) MODIFIER LETTER SMALL Q
+107A6;107A6;107A6;027A;027A; # (ðž¦; ðž¦; ðž¦; ɺ; ɺ; ) MODIFIER LETTER SMALL TURNED R WITH LONG LEG
+107A7;107A7;107A7;1DF08;1DF08; # (ðž§; ðž§; ðž§; ð¼ˆ; ð¼ˆ; ) MODIFIER LETTER SMALL TURNED R WITH LONG LEG AND RETROFLEX HOOK
+107A8;107A8;107A8;027D;027D; # (ðž¨; ðž¨; ðž¨; ɽ; ɽ; ) MODIFIER LETTER SMALL R WITH TAIL
+107A9;107A9;107A9;027E;027E; # (ðž©; ðž©; ðž©; ɾ; ɾ; ) MODIFIER LETTER SMALL R WITH FISHHOOK
+107AA;107AA;107AA;0280;0280; # (ðžª; ðžª; ðžª; Ê€; Ê€; ) MODIFIER LETTER SMALL CAPITAL R
+107AB;107AB;107AB;02A8;02A8; # (ðž«; ðž«; ðž«; ʨ; ʨ; ) MODIFIER LETTER SMALL TC DIGRAPH WITH CURL
+107AC;107AC;107AC;02A6;02A6; # (ðž¬; ðž¬; ðž¬; ʦ; ʦ; ) MODIFIER LETTER SMALL TS DIGRAPH
+107AD;107AD;107AD;AB67;AB67; # (ðž­; ðž­; ðž­; ê­§; ê­§; ) MODIFIER LETTER SMALL TS DIGRAPH WITH RETROFLEX HOOK
+107AE;107AE;107AE;02A7;02A7; # (ðž®; ðž®; ðž®; ʧ; ʧ; ) MODIFIER LETTER SMALL TESH DIGRAPH
+107AF;107AF;107AF;0288;0288; # (ðž¯; ðž¯; ðž¯; ʈ; ʈ; ) MODIFIER LETTER SMALL T WITH RETROFLEX HOOK
+107B0;107B0;107B0;2C71;2C71; # (ðž°; ðž°; ðž°; â±±; â±±; ) MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2;107B2;107B2;028F;028F; # (ðž²; ðž²; ðž²; Ê; Ê; ) MODIFIER LETTER SMALL CAPITAL Y
+107B3;107B3;107B3;02A1;02A1; # (ðž³; ðž³; ðž³; Ê¡; Ê¡; ) MODIFIER LETTER GLOTTAL STOP WITH STROKE
+107B4;107B4;107B4;02A2;02A2; # (ðž´; ðž´; ðž´; Ê¢; Ê¢; ) MODIFIER LETTER REVERSED GLOTTAL STOP WITH STROKE
+107B5;107B5;107B5;0298;0298; # (ðžµ; ðžµ; ðžµ; ʘ; ʘ; ) MODIFIER LETTER BILABIAL CLICK
+107B6;107B6;107B6;01C0;01C0; # (ðž¶; ðž¶; ðž¶; Ç€; Ç€; ) MODIFIER LETTER DENTAL CLICK
+107B7;107B7;107B7;01C1;01C1; # (ðž·; ðž·; ðž·; Ç; Ç; ) MODIFIER LETTER LATERAL CLICK
+107B8;107B8;107B8;01C2;01C2; # (ðž¸; ðž¸; ðž¸; Ç‚; Ç‚; ) MODIFIER LETTER ALVEOLAR CLICK
+107B9;107B9;107B9;1DF0A;1DF0A; # (ðž¹; ðž¹; ðž¹; ð¼Š; ð¼Š; ) MODIFIER LETTER RETROFLEX CLICK WITH RETROFLEX HOOK
+107BA;107BA;107BA;1DF1E;1DF1E; # (ðžº; ðžº; ðžº; ð¼ž; ð¼ž; ) MODIFIER LETTER SMALL S WITH CURL
+1109A;1109A;11099 110BA;1109A;11099 110BA; # (𑂚; 𑂚; 𑂙◌𑂺; 𑂚; 𑂙◌𑂺; ) KAITHI LETTER DDDHA
+1109C;1109C;1109B 110BA;1109C;1109B 110BA; # (𑂜; 𑂜; 𑂛◌𑂺; 𑂜; 𑂛◌𑂺; ) KAITHI LETTER RHA
+110AB;110AB;110A5 110BA;110AB;110A5 110BA; # (𑂫; 𑂫; 𑂥◌𑂺; 𑂫; 𑂥◌𑂺; ) KAITHI LETTER VA
+1112E;1112E;11131 11127;1112E;11131 11127; # (◌𑄮; ◌𑄮; ◌𑄱◌𑄧; ◌𑄮; ◌𑄱◌𑄧; ) CHAKMA VOWEL SIGN O
+1112F;1112F;11132 11127;1112F;11132 11127; # (◌𑄯; ◌𑄯; ◌𑄲◌𑄧; ◌𑄯; ◌𑄲◌𑄧; ) CHAKMA VOWEL SIGN AU
+1134B;1134B;11347 1133E;1134B;11347 1133E; # (ð‘‹; ð‘‹; ð‘‡ð‘Œ¾; ð‘‹; ð‘‡ð‘Œ¾; ) GRANTHA VOWEL SIGN OO
+1134C;1134C;11347 11357;1134C;11347 11357; # (ð‘Œ; ð‘Œ; ð‘‡ð‘—; ð‘Œ; ð‘‡ð‘—; ) GRANTHA VOWEL SIGN AU
+114BB;114BB;114B9 114BA;114BB;114B9 114BA; # (𑒻; 𑒻; 𑒹◌𑒺; 𑒻; 𑒹◌𑒺; ) TIRHUTA VOWEL SIGN AI
+114BC;114BC;114B9 114B0;114BC;114B9 114B0; # (𑒼; 𑒼; 𑒼; 𑒼; 𑒼; ) TIRHUTA VOWEL SIGN O
+114BE;114BE;114B9 114BD;114BE;114B9 114BD; # (𑒾; 𑒾; 𑒾; 𑒾; 𑒾; ) TIRHUTA VOWEL SIGN AU
+115BA;115BA;115B8 115AF;115BA;115B8 115AF; # (𑖺; 𑖺; 𑖺; 𑖺; 𑖺; ) SIDDHAM VOWEL SIGN O
+115BB;115BB;115B9 115AF;115BB;115B9 115AF; # (𑖻; 𑖻; 𑖻; 𑖻; 𑖻; ) SIDDHAM VOWEL SIGN AU
+11938;11938;11935 11930;11938;11935 11930; # (𑤸; 𑤸; 𑤸; 𑤸; 𑤸; ) DIVES AKURU VOWEL SIGN O
+1D15E;1D157 1D165;1D157 1D165;1D157 1D165;1D157 1D165; # (ð…ž; ð…—ð…¥; ð…—ð…¥; ð…—ð…¥; ð…—ð…¥; ) MUSICAL SYMBOL HALF NOTE
+1D15F;1D158 1D165;1D158 1D165;1D158 1D165;1D158 1D165; # (ð…Ÿ; ð…˜ð…¥; ð…˜ð…¥; ð…˜ð…¥; ð…˜ð…¥; ) MUSICAL SYMBOL QUARTER NOTE
+1D160;1D158 1D165 1D16E;1D158 1D165 1D16E;1D158 1D165 1D16E;1D158 1D165 1D16E; # (ð… ; ð…˜ð…¥ð…®; ð…˜ð…¥ð…®; ð…˜ð…¥ð…®; ð…˜ð…¥ð…®; ) MUSICAL SYMBOL EIGHTH NOTE
+1D161;1D158 1D165 1D16F;1D158 1D165 1D16F;1D158 1D165 1D16F;1D158 1D165 1D16F; # (ð…¡; ð…˜ð…¥ð…¯; ð…˜ð…¥ð…¯; ð…˜ð…¥ð…¯; ð…˜ð…¥ð…¯; ) MUSICAL SYMBOL SIXTEENTH NOTE
+1D162;1D158 1D165 1D170;1D158 1D165 1D170;1D158 1D165 1D170;1D158 1D165 1D170; # (ð…¢; ð…˜ð…¥ð…°; ð…˜ð…¥ð…°; ð…˜ð…¥ð…°; ð…˜ð…¥ð…°; ) MUSICAL SYMBOL THIRTY-SECOND NOTE
+1D163;1D158 1D165 1D171;1D158 1D165 1D171;1D158 1D165 1D171;1D158 1D165 1D171; # (ð…£; ð…˜ð…¥ð…±; ð…˜ð…¥ð…±; ð…˜ð…¥ð…±; ð…˜ð…¥ð…±; ) MUSICAL SYMBOL SIXTY-FOURTH NOTE
+1D164;1D158 1D165 1D172;1D158 1D165 1D172;1D158 1D165 1D172;1D158 1D165 1D172; # (ð…¤; ð…˜ð…¥ð…²; ð…˜ð…¥ð…²; ð…˜ð…¥ð…²; ð…˜ð…¥ð…²; ) MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH NOTE
+1D1BB;1D1B9 1D165;1D1B9 1D165;1D1B9 1D165;1D1B9 1D165; # (ð†»; ð†¹ð…¥; ð†¹ð…¥; ð†¹ð…¥; ð†¹ð…¥; ) MUSICAL SYMBOL MINIMA
+1D1BC;1D1BA 1D165;1D1BA 1D165;1D1BA 1D165;1D1BA 1D165; # (ð†¼; ð†ºð…¥; ð†ºð…¥; ð†ºð…¥; ð†ºð…¥; ) MUSICAL SYMBOL MINIMA BLACK
+1D1BD;1D1B9 1D165 1D16E;1D1B9 1D165 1D16E;1D1B9 1D165 1D16E;1D1B9 1D165 1D16E; # (ð†½; ð†¹ð…¥ð…®; ð†¹ð…¥ð…®; ð†¹ð…¥ð…®; ð†¹ð…¥ð…®; ) MUSICAL SYMBOL SEMIMINIMA WHITE
+1D1BE;1D1BA 1D165 1D16E;1D1BA 1D165 1D16E;1D1BA 1D165 1D16E;1D1BA 1D165 1D16E; # (ð†¾; ð†ºð…¥ð…®; ð†ºð…¥ð…®; ð†ºð…¥ð…®; ð†ºð…¥ð…®; ) MUSICAL SYMBOL SEMIMINIMA BLACK
+1D1BF;1D1B9 1D165 1D16F;1D1B9 1D165 1D16F;1D1B9 1D165 1D16F;1D1B9 1D165 1D16F; # (ð†¿; ð†¹ð…¥ð…¯; ð†¹ð…¥ð…¯; ð†¹ð…¥ð…¯; ð†¹ð…¥ð…¯; ) MUSICAL SYMBOL FUSA WHITE
+1D1C0;1D1BA 1D165 1D16F;1D1BA 1D165 1D16F;1D1BA 1D165 1D16F;1D1BA 1D165 1D16F; # (ð‡€; ð†ºð…¥ð…¯; ð†ºð…¥ð…¯; ð†ºð…¥ð…¯; ð†ºð…¥ð…¯; ) MUSICAL SYMBOL FUSA BLACK
+1D400;1D400;1D400;0041;0041; # (ð€; ð€; ð€; A; A; ) MATHEMATICAL BOLD CAPITAL A
+1D401;1D401;1D401;0042;0042; # (ð; ð; ð; B; B; ) MATHEMATICAL BOLD CAPITAL B
+1D402;1D402;1D402;0043;0043; # (ð‚; ð‚; ð‚; C; C; ) MATHEMATICAL BOLD CAPITAL C
+1D403;1D403;1D403;0044;0044; # (ðƒ; ðƒ; ðƒ; D; D; ) MATHEMATICAL BOLD CAPITAL D
+1D404;1D404;1D404;0045;0045; # (ð„; ð„; ð„; E; E; ) MATHEMATICAL BOLD CAPITAL E
+1D405;1D405;1D405;0046;0046; # (ð…; ð…; ð…; F; F; ) MATHEMATICAL BOLD CAPITAL F
+1D406;1D406;1D406;0047;0047; # (ð†; ð†; ð†; G; G; ) MATHEMATICAL BOLD CAPITAL G
+1D407;1D407;1D407;0048;0048; # (ð‡; ð‡; ð‡; H; H; ) MATHEMATICAL BOLD CAPITAL H
+1D408;1D408;1D408;0049;0049; # (ðˆ; ðˆ; ðˆ; I; I; ) MATHEMATICAL BOLD CAPITAL I
+1D409;1D409;1D409;004A;004A; # (ð‰; ð‰; ð‰; J; J; ) MATHEMATICAL BOLD CAPITAL J
+1D40A;1D40A;1D40A;004B;004B; # (ðŠ; ðŠ; ðŠ; K; K; ) MATHEMATICAL BOLD CAPITAL K
+1D40B;1D40B;1D40B;004C;004C; # (ð‹; ð‹; ð‹; L; L; ) MATHEMATICAL BOLD CAPITAL L
+1D40C;1D40C;1D40C;004D;004D; # (ðŒ; ðŒ; ðŒ; M; M; ) MATHEMATICAL BOLD CAPITAL M
+1D40D;1D40D;1D40D;004E;004E; # (ð; ð; ð; N; N; ) MATHEMATICAL BOLD CAPITAL N
+1D40E;1D40E;1D40E;004F;004F; # (ðŽ; ðŽ; ðŽ; O; O; ) MATHEMATICAL BOLD CAPITAL O
+1D40F;1D40F;1D40F;0050;0050; # (ð; ð; ð; P; P; ) MATHEMATICAL BOLD CAPITAL P
+1D410;1D410;1D410;0051;0051; # (ð; ð; ð; Q; Q; ) MATHEMATICAL BOLD CAPITAL Q
+1D411;1D411;1D411;0052;0052; # (ð‘; ð‘; ð‘; R; R; ) MATHEMATICAL BOLD CAPITAL R
+1D412;1D412;1D412;0053;0053; # (ð’; ð’; ð’; S; S; ) MATHEMATICAL BOLD CAPITAL S
+1D413;1D413;1D413;0054;0054; # (ð“; ð“; ð“; T; T; ) MATHEMATICAL BOLD CAPITAL T
+1D414;1D414;1D414;0055;0055; # (ð”; ð”; ð”; U; U; ) MATHEMATICAL BOLD CAPITAL U
+1D415;1D415;1D415;0056;0056; # (ð•; ð•; ð•; V; V; ) MATHEMATICAL BOLD CAPITAL V
+1D416;1D416;1D416;0057;0057; # (ð–; ð–; ð–; W; W; ) MATHEMATICAL BOLD CAPITAL W
+1D417;1D417;1D417;0058;0058; # (ð—; ð—; ð—; X; X; ) MATHEMATICAL BOLD CAPITAL X
+1D418;1D418;1D418;0059;0059; # (ð˜; ð˜; ð˜; Y; Y; ) MATHEMATICAL BOLD CAPITAL Y
+1D419;1D419;1D419;005A;005A; # (ð™; ð™; ð™; Z; Z; ) MATHEMATICAL BOLD CAPITAL Z
+1D41A;1D41A;1D41A;0061;0061; # (ðš; ðš; ðš; a; a; ) MATHEMATICAL BOLD SMALL A
+1D41B;1D41B;1D41B;0062;0062; # (ð›; ð›; ð›; b; b; ) MATHEMATICAL BOLD SMALL B
+1D41C;1D41C;1D41C;0063;0063; # (ðœ; ðœ; ðœ; c; c; ) MATHEMATICAL BOLD SMALL C
+1D41D;1D41D;1D41D;0064;0064; # (ð; ð; ð; d; d; ) MATHEMATICAL BOLD SMALL D
+1D41E;1D41E;1D41E;0065;0065; # (ðž; ðž; ðž; e; e; ) MATHEMATICAL BOLD SMALL E
+1D41F;1D41F;1D41F;0066;0066; # (ðŸ; ðŸ; ðŸ; f; f; ) MATHEMATICAL BOLD SMALL F
+1D420;1D420;1D420;0067;0067; # (ð ; ð ; ð ; g; g; ) MATHEMATICAL BOLD SMALL G
+1D421;1D421;1D421;0068;0068; # (ð¡; ð¡; ð¡; h; h; ) MATHEMATICAL BOLD SMALL H
+1D422;1D422;1D422;0069;0069; # (ð¢; ð¢; ð¢; i; i; ) MATHEMATICAL BOLD SMALL I
+1D423;1D423;1D423;006A;006A; # (ð£; ð£; ð£; j; j; ) MATHEMATICAL BOLD SMALL J
+1D424;1D424;1D424;006B;006B; # (ð¤; ð¤; ð¤; k; k; ) MATHEMATICAL BOLD SMALL K
+1D425;1D425;1D425;006C;006C; # (ð¥; ð¥; ð¥; l; l; ) MATHEMATICAL BOLD SMALL L
+1D426;1D426;1D426;006D;006D; # (ð¦; ð¦; ð¦; m; m; ) MATHEMATICAL BOLD SMALL M
+1D427;1D427;1D427;006E;006E; # (ð§; ð§; ð§; n; n; ) MATHEMATICAL BOLD SMALL N
+1D428;1D428;1D428;006F;006F; # (ð¨; ð¨; ð¨; o; o; ) MATHEMATICAL BOLD SMALL O
+1D429;1D429;1D429;0070;0070; # (ð©; ð©; ð©; p; p; ) MATHEMATICAL BOLD SMALL P
+1D42A;1D42A;1D42A;0071;0071; # (ðª; ðª; ðª; q; q; ) MATHEMATICAL BOLD SMALL Q
+1D42B;1D42B;1D42B;0072;0072; # (ð«; ð«; ð«; r; r; ) MATHEMATICAL BOLD SMALL R
+1D42C;1D42C;1D42C;0073;0073; # (ð¬; ð¬; ð¬; s; s; ) MATHEMATICAL BOLD SMALL S
+1D42D;1D42D;1D42D;0074;0074; # (ð­; ð­; ð­; t; t; ) MATHEMATICAL BOLD SMALL T
+1D42E;1D42E;1D42E;0075;0075; # (ð®; ð®; ð®; u; u; ) MATHEMATICAL BOLD SMALL U
+1D42F;1D42F;1D42F;0076;0076; # (ð¯; ð¯; ð¯; v; v; ) MATHEMATICAL BOLD SMALL V
+1D430;1D430;1D430;0077;0077; # (ð°; ð°; ð°; w; w; ) MATHEMATICAL BOLD SMALL W
+1D431;1D431;1D431;0078;0078; # (ð±; ð±; ð±; x; x; ) MATHEMATICAL BOLD SMALL X
+1D432;1D432;1D432;0079;0079; # (ð²; ð²; ð²; y; y; ) MATHEMATICAL BOLD SMALL Y
+1D433;1D433;1D433;007A;007A; # (ð³; ð³; ð³; z; z; ) MATHEMATICAL BOLD SMALL Z
+1D434;1D434;1D434;0041;0041; # (ð´; ð´; ð´; A; A; ) MATHEMATICAL ITALIC CAPITAL A
+1D435;1D435;1D435;0042;0042; # (ðµ; ðµ; ðµ; B; B; ) MATHEMATICAL ITALIC CAPITAL B
+1D436;1D436;1D436;0043;0043; # (ð¶; ð¶; ð¶; C; C; ) MATHEMATICAL ITALIC CAPITAL C
+1D437;1D437;1D437;0044;0044; # (ð·; ð·; ð·; D; D; ) MATHEMATICAL ITALIC CAPITAL D
+1D438;1D438;1D438;0045;0045; # (ð¸; ð¸; ð¸; E; E; ) MATHEMATICAL ITALIC CAPITAL E
+1D439;1D439;1D439;0046;0046; # (ð¹; ð¹; ð¹; F; F; ) MATHEMATICAL ITALIC CAPITAL F
+1D43A;1D43A;1D43A;0047;0047; # (ðº; ðº; ðº; G; G; ) MATHEMATICAL ITALIC CAPITAL G
+1D43B;1D43B;1D43B;0048;0048; # (ð»; ð»; ð»; H; H; ) MATHEMATICAL ITALIC CAPITAL H
+1D43C;1D43C;1D43C;0049;0049; # (ð¼; ð¼; ð¼; I; I; ) MATHEMATICAL ITALIC CAPITAL I
+1D43D;1D43D;1D43D;004A;004A; # (ð½; ð½; ð½; J; J; ) MATHEMATICAL ITALIC CAPITAL J
+1D43E;1D43E;1D43E;004B;004B; # (ð¾; ð¾; ð¾; K; K; ) MATHEMATICAL ITALIC CAPITAL K
+1D43F;1D43F;1D43F;004C;004C; # (ð¿; ð¿; ð¿; L; L; ) MATHEMATICAL ITALIC CAPITAL L
+1D440;1D440;1D440;004D;004D; # (ð‘€; ð‘€; ð‘€; M; M; ) MATHEMATICAL ITALIC CAPITAL M
+1D441;1D441;1D441;004E;004E; # (ð‘; ð‘; ð‘; N; N; ) MATHEMATICAL ITALIC CAPITAL N
+1D442;1D442;1D442;004F;004F; # (ð‘‚; ð‘‚; ð‘‚; O; O; ) MATHEMATICAL ITALIC CAPITAL O
+1D443;1D443;1D443;0050;0050; # (ð‘ƒ; ð‘ƒ; ð‘ƒ; P; P; ) MATHEMATICAL ITALIC CAPITAL P
+1D444;1D444;1D444;0051;0051; # (ð‘„; ð‘„; ð‘„; Q; Q; ) MATHEMATICAL ITALIC CAPITAL Q
+1D445;1D445;1D445;0052;0052; # (ð‘…; ð‘…; ð‘…; R; R; ) MATHEMATICAL ITALIC CAPITAL R
+1D446;1D446;1D446;0053;0053; # (ð‘†; ð‘†; ð‘†; S; S; ) MATHEMATICAL ITALIC CAPITAL S
+1D447;1D447;1D447;0054;0054; # (ð‘‡; ð‘‡; ð‘‡; T; T; ) MATHEMATICAL ITALIC CAPITAL T
+1D448;1D448;1D448;0055;0055; # (ð‘ˆ; ð‘ˆ; ð‘ˆ; U; U; ) MATHEMATICAL ITALIC CAPITAL U
+1D449;1D449;1D449;0056;0056; # (ð‘‰; ð‘‰; ð‘‰; V; V; ) MATHEMATICAL ITALIC CAPITAL V
+1D44A;1D44A;1D44A;0057;0057; # (ð‘Š; ð‘Š; ð‘Š; W; W; ) MATHEMATICAL ITALIC CAPITAL W
+1D44B;1D44B;1D44B;0058;0058; # (ð‘‹; ð‘‹; ð‘‹; X; X; ) MATHEMATICAL ITALIC CAPITAL X
+1D44C;1D44C;1D44C;0059;0059; # (ð‘Œ; ð‘Œ; ð‘Œ; Y; Y; ) MATHEMATICAL ITALIC CAPITAL Y
+1D44D;1D44D;1D44D;005A;005A; # (ð‘; ð‘; ð‘; Z; Z; ) MATHEMATICAL ITALIC CAPITAL Z
+1D44E;1D44E;1D44E;0061;0061; # (ð‘Ž; ð‘Ž; ð‘Ž; a; a; ) MATHEMATICAL ITALIC SMALL A
+1D44F;1D44F;1D44F;0062;0062; # (ð‘; ð‘; ð‘; b; b; ) MATHEMATICAL ITALIC SMALL B
+1D450;1D450;1D450;0063;0063; # (ð‘; ð‘; ð‘; c; c; ) MATHEMATICAL ITALIC SMALL C
+1D451;1D451;1D451;0064;0064; # (ð‘‘; ð‘‘; ð‘‘; d; d; ) MATHEMATICAL ITALIC SMALL D
+1D452;1D452;1D452;0065;0065; # (ð‘’; ð‘’; ð‘’; e; e; ) MATHEMATICAL ITALIC SMALL E
+1D453;1D453;1D453;0066;0066; # (ð‘“; ð‘“; ð‘“; f; f; ) MATHEMATICAL ITALIC SMALL F
+1D454;1D454;1D454;0067;0067; # (ð‘”; ð‘”; ð‘”; g; g; ) MATHEMATICAL ITALIC SMALL G
+1D456;1D456;1D456;0069;0069; # (ð‘–; ð‘–; ð‘–; i; i; ) MATHEMATICAL ITALIC SMALL I
+1D457;1D457;1D457;006A;006A; # (ð‘—; ð‘—; ð‘—; j; j; ) MATHEMATICAL ITALIC SMALL J
+1D458;1D458;1D458;006B;006B; # (ð‘˜; ð‘˜; ð‘˜; k; k; ) MATHEMATICAL ITALIC SMALL K
+1D459;1D459;1D459;006C;006C; # (ð‘™; ð‘™; ð‘™; l; l; ) MATHEMATICAL ITALIC SMALL L
+1D45A;1D45A;1D45A;006D;006D; # (ð‘š; ð‘š; ð‘š; m; m; ) MATHEMATICAL ITALIC SMALL M
+1D45B;1D45B;1D45B;006E;006E; # (ð‘›; ð‘›; ð‘›; n; n; ) MATHEMATICAL ITALIC SMALL N
+1D45C;1D45C;1D45C;006F;006F; # (ð‘œ; ð‘œ; ð‘œ; o; o; ) MATHEMATICAL ITALIC SMALL O
+1D45D;1D45D;1D45D;0070;0070; # (ð‘; ð‘; ð‘; p; p; ) MATHEMATICAL ITALIC SMALL P
+1D45E;1D45E;1D45E;0071;0071; # (ð‘ž; ð‘ž; ð‘ž; q; q; ) MATHEMATICAL ITALIC SMALL Q
+1D45F;1D45F;1D45F;0072;0072; # (ð‘Ÿ; ð‘Ÿ; ð‘Ÿ; r; r; ) MATHEMATICAL ITALIC SMALL R
+1D460;1D460;1D460;0073;0073; # (ð‘ ; ð‘ ; ð‘ ; s; s; ) MATHEMATICAL ITALIC SMALL S
+1D461;1D461;1D461;0074;0074; # (ð‘¡; ð‘¡; ð‘¡; t; t; ) MATHEMATICAL ITALIC SMALL T
+1D462;1D462;1D462;0075;0075; # (ð‘¢; ð‘¢; ð‘¢; u; u; ) MATHEMATICAL ITALIC SMALL U
+1D463;1D463;1D463;0076;0076; # (ð‘£; ð‘£; ð‘£; v; v; ) MATHEMATICAL ITALIC SMALL V
+1D464;1D464;1D464;0077;0077; # (ð‘¤; ð‘¤; ð‘¤; w; w; ) MATHEMATICAL ITALIC SMALL W
+1D465;1D465;1D465;0078;0078; # (ð‘¥; ð‘¥; ð‘¥; x; x; ) MATHEMATICAL ITALIC SMALL X
+1D466;1D466;1D466;0079;0079; # (ð‘¦; ð‘¦; ð‘¦; y; y; ) MATHEMATICAL ITALIC SMALL Y
+1D467;1D467;1D467;007A;007A; # (ð‘§; ð‘§; ð‘§; z; z; ) MATHEMATICAL ITALIC SMALL Z
+1D468;1D468;1D468;0041;0041; # (ð‘¨; ð‘¨; ð‘¨; A; A; ) MATHEMATICAL BOLD ITALIC CAPITAL A
+1D469;1D469;1D469;0042;0042; # (ð‘©; ð‘©; ð‘©; B; B; ) MATHEMATICAL BOLD ITALIC CAPITAL B
+1D46A;1D46A;1D46A;0043;0043; # (ð‘ª; ð‘ª; ð‘ª; C; C; ) MATHEMATICAL BOLD ITALIC CAPITAL C
+1D46B;1D46B;1D46B;0044;0044; # (ð‘«; ð‘«; ð‘«; D; D; ) MATHEMATICAL BOLD ITALIC CAPITAL D
+1D46C;1D46C;1D46C;0045;0045; # (ð‘¬; ð‘¬; ð‘¬; E; E; ) MATHEMATICAL BOLD ITALIC CAPITAL E
+1D46D;1D46D;1D46D;0046;0046; # (ð‘­; ð‘­; ð‘­; F; F; ) MATHEMATICAL BOLD ITALIC CAPITAL F
+1D46E;1D46E;1D46E;0047;0047; # (ð‘®; ð‘®; ð‘®; G; G; ) MATHEMATICAL BOLD ITALIC CAPITAL G
+1D46F;1D46F;1D46F;0048;0048; # (ð‘¯; ð‘¯; ð‘¯; H; H; ) MATHEMATICAL BOLD ITALIC CAPITAL H
+1D470;1D470;1D470;0049;0049; # (ð‘°; ð‘°; ð‘°; I; I; ) MATHEMATICAL BOLD ITALIC CAPITAL I
+1D471;1D471;1D471;004A;004A; # (ð‘±; ð‘±; ð‘±; J; J; ) MATHEMATICAL BOLD ITALIC CAPITAL J
+1D472;1D472;1D472;004B;004B; # (ð‘²; ð‘²; ð‘²; K; K; ) MATHEMATICAL BOLD ITALIC CAPITAL K
+1D473;1D473;1D473;004C;004C; # (ð‘³; ð‘³; ð‘³; L; L; ) MATHEMATICAL BOLD ITALIC CAPITAL L
+1D474;1D474;1D474;004D;004D; # (ð‘´; ð‘´; ð‘´; M; M; ) MATHEMATICAL BOLD ITALIC CAPITAL M
+1D475;1D475;1D475;004E;004E; # (ð‘µ; ð‘µ; ð‘µ; N; N; ) MATHEMATICAL BOLD ITALIC CAPITAL N
+1D476;1D476;1D476;004F;004F; # (ð‘¶; ð‘¶; ð‘¶; O; O; ) MATHEMATICAL BOLD ITALIC CAPITAL O
+1D477;1D477;1D477;0050;0050; # (ð‘·; ð‘·; ð‘·; P; P; ) MATHEMATICAL BOLD ITALIC CAPITAL P
+1D478;1D478;1D478;0051;0051; # (ð‘¸; ð‘¸; ð‘¸; Q; Q; ) MATHEMATICAL BOLD ITALIC CAPITAL Q
+1D479;1D479;1D479;0052;0052; # (ð‘¹; ð‘¹; ð‘¹; R; R; ) MATHEMATICAL BOLD ITALIC CAPITAL R
+1D47A;1D47A;1D47A;0053;0053; # (ð‘º; ð‘º; ð‘º; S; S; ) MATHEMATICAL BOLD ITALIC CAPITAL S
+1D47B;1D47B;1D47B;0054;0054; # (ð‘»; ð‘»; ð‘»; T; T; ) MATHEMATICAL BOLD ITALIC CAPITAL T
+1D47C;1D47C;1D47C;0055;0055; # (ð‘¼; ð‘¼; ð‘¼; U; U; ) MATHEMATICAL BOLD ITALIC CAPITAL U
+1D47D;1D47D;1D47D;0056;0056; # (ð‘½; ð‘½; ð‘½; V; V; ) MATHEMATICAL BOLD ITALIC CAPITAL V
+1D47E;1D47E;1D47E;0057;0057; # (ð‘¾; ð‘¾; ð‘¾; W; W; ) MATHEMATICAL BOLD ITALIC CAPITAL W
+1D47F;1D47F;1D47F;0058;0058; # (ð‘¿; ð‘¿; ð‘¿; X; X; ) MATHEMATICAL BOLD ITALIC CAPITAL X
+1D480;1D480;1D480;0059;0059; # (ð’€; ð’€; ð’€; Y; Y; ) MATHEMATICAL BOLD ITALIC CAPITAL Y
+1D481;1D481;1D481;005A;005A; # (ð’; ð’; ð’; Z; Z; ) MATHEMATICAL BOLD ITALIC CAPITAL Z
+1D482;1D482;1D482;0061;0061; # (ð’‚; ð’‚; ð’‚; a; a; ) MATHEMATICAL BOLD ITALIC SMALL A
+1D483;1D483;1D483;0062;0062; # (ð’ƒ; ð’ƒ; ð’ƒ; b; b; ) MATHEMATICAL BOLD ITALIC SMALL B
+1D484;1D484;1D484;0063;0063; # (ð’„; ð’„; ð’„; c; c; ) MATHEMATICAL BOLD ITALIC SMALL C
+1D485;1D485;1D485;0064;0064; # (ð’…; ð’…; ð’…; d; d; ) MATHEMATICAL BOLD ITALIC SMALL D
+1D486;1D486;1D486;0065;0065; # (ð’†; ð’†; ð’†; e; e; ) MATHEMATICAL BOLD ITALIC SMALL E
+1D487;1D487;1D487;0066;0066; # (ð’‡; ð’‡; ð’‡; f; f; ) MATHEMATICAL BOLD ITALIC SMALL F
+1D488;1D488;1D488;0067;0067; # (ð’ˆ; ð’ˆ; ð’ˆ; g; g; ) MATHEMATICAL BOLD ITALIC SMALL G
+1D489;1D489;1D489;0068;0068; # (ð’‰; ð’‰; ð’‰; h; h; ) MATHEMATICAL BOLD ITALIC SMALL H
+1D48A;1D48A;1D48A;0069;0069; # (ð’Š; ð’Š; ð’Š; i; i; ) MATHEMATICAL BOLD ITALIC SMALL I
+1D48B;1D48B;1D48B;006A;006A; # (ð’‹; ð’‹; ð’‹; j; j; ) MATHEMATICAL BOLD ITALIC SMALL J
+1D48C;1D48C;1D48C;006B;006B; # (ð’Œ; ð’Œ; ð’Œ; k; k; ) MATHEMATICAL BOLD ITALIC SMALL K
+1D48D;1D48D;1D48D;006C;006C; # (ð’; ð’; ð’; l; l; ) MATHEMATICAL BOLD ITALIC SMALL L
+1D48E;1D48E;1D48E;006D;006D; # (ð’Ž; ð’Ž; ð’Ž; m; m; ) MATHEMATICAL BOLD ITALIC SMALL M
+1D48F;1D48F;1D48F;006E;006E; # (ð’; ð’; ð’; n; n; ) MATHEMATICAL BOLD ITALIC SMALL N
+1D490;1D490;1D490;006F;006F; # (ð’; ð’; ð’; o; o; ) MATHEMATICAL BOLD ITALIC SMALL O
+1D491;1D491;1D491;0070;0070; # (ð’‘; ð’‘; ð’‘; p; p; ) MATHEMATICAL BOLD ITALIC SMALL P
+1D492;1D492;1D492;0071;0071; # (ð’’; ð’’; ð’’; q; q; ) MATHEMATICAL BOLD ITALIC SMALL Q
+1D493;1D493;1D493;0072;0072; # (ð’“; ð’“; ð’“; r; r; ) MATHEMATICAL BOLD ITALIC SMALL R
+1D494;1D494;1D494;0073;0073; # (ð’”; ð’”; ð’”; s; s; ) MATHEMATICAL BOLD ITALIC SMALL S
+1D495;1D495;1D495;0074;0074; # (ð’•; ð’•; ð’•; t; t; ) MATHEMATICAL BOLD ITALIC SMALL T
+1D496;1D496;1D496;0075;0075; # (ð’–; ð’–; ð’–; u; u; ) MATHEMATICAL BOLD ITALIC SMALL U
+1D497;1D497;1D497;0076;0076; # (ð’—; ð’—; ð’—; v; v; ) MATHEMATICAL BOLD ITALIC SMALL V
+1D498;1D498;1D498;0077;0077; # (ð’˜; ð’˜; ð’˜; w; w; ) MATHEMATICAL BOLD ITALIC SMALL W
+1D499;1D499;1D499;0078;0078; # (ð’™; ð’™; ð’™; x; x; ) MATHEMATICAL BOLD ITALIC SMALL X
+1D49A;1D49A;1D49A;0079;0079; # (ð’š; ð’š; ð’š; y; y; ) MATHEMATICAL BOLD ITALIC SMALL Y
+1D49B;1D49B;1D49B;007A;007A; # (ð’›; ð’›; ð’›; z; z; ) MATHEMATICAL BOLD ITALIC SMALL Z
+1D49C;1D49C;1D49C;0041;0041; # (ð’œ; ð’œ; ð’œ; A; A; ) MATHEMATICAL SCRIPT CAPITAL A
+1D49E;1D49E;1D49E;0043;0043; # (ð’ž; ð’ž; ð’ž; C; C; ) MATHEMATICAL SCRIPT CAPITAL C
+1D49F;1D49F;1D49F;0044;0044; # (ð’Ÿ; ð’Ÿ; ð’Ÿ; D; D; ) MATHEMATICAL SCRIPT CAPITAL D
+1D4A2;1D4A2;1D4A2;0047;0047; # (ð’¢; ð’¢; ð’¢; G; G; ) MATHEMATICAL SCRIPT CAPITAL G
+1D4A5;1D4A5;1D4A5;004A;004A; # (ð’¥; ð’¥; ð’¥; J; J; ) MATHEMATICAL SCRIPT CAPITAL J
+1D4A6;1D4A6;1D4A6;004B;004B; # (ð’¦; ð’¦; ð’¦; K; K; ) MATHEMATICAL SCRIPT CAPITAL K
+1D4A9;1D4A9;1D4A9;004E;004E; # (ð’©; ð’©; ð’©; N; N; ) MATHEMATICAL SCRIPT CAPITAL N
+1D4AA;1D4AA;1D4AA;004F;004F; # (ð’ª; ð’ª; ð’ª; O; O; ) MATHEMATICAL SCRIPT CAPITAL O
+1D4AB;1D4AB;1D4AB;0050;0050; # (ð’«; ð’«; ð’«; P; P; ) MATHEMATICAL SCRIPT CAPITAL P
+1D4AC;1D4AC;1D4AC;0051;0051; # (ð’¬; ð’¬; ð’¬; Q; Q; ) MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE;1D4AE;1D4AE;0053;0053; # (ð’®; ð’®; ð’®; S; S; ) MATHEMATICAL SCRIPT CAPITAL S
+1D4AF;1D4AF;1D4AF;0054;0054; # (ð’¯; ð’¯; ð’¯; T; T; ) MATHEMATICAL SCRIPT CAPITAL T
+1D4B0;1D4B0;1D4B0;0055;0055; # (ð’°; ð’°; ð’°; U; U; ) MATHEMATICAL SCRIPT CAPITAL U
+1D4B1;1D4B1;1D4B1;0056;0056; # (ð’±; ð’±; ð’±; V; V; ) MATHEMATICAL SCRIPT CAPITAL V
+1D4B2;1D4B2;1D4B2;0057;0057; # (ð’²; ð’²; ð’²; W; W; ) MATHEMATICAL SCRIPT CAPITAL W
+1D4B3;1D4B3;1D4B3;0058;0058; # (ð’³; ð’³; ð’³; X; X; ) MATHEMATICAL SCRIPT CAPITAL X
+1D4B4;1D4B4;1D4B4;0059;0059; # (ð’´; ð’´; ð’´; Y; Y; ) MATHEMATICAL SCRIPT CAPITAL Y
+1D4B5;1D4B5;1D4B5;005A;005A; # (ð’µ; ð’µ; ð’µ; Z; Z; ) MATHEMATICAL SCRIPT CAPITAL Z
+1D4B6;1D4B6;1D4B6;0061;0061; # (ð’¶; ð’¶; ð’¶; a; a; ) MATHEMATICAL SCRIPT SMALL A
+1D4B7;1D4B7;1D4B7;0062;0062; # (ð’·; ð’·; ð’·; b; b; ) MATHEMATICAL SCRIPT SMALL B
+1D4B8;1D4B8;1D4B8;0063;0063; # (ð’¸; ð’¸; ð’¸; c; c; ) MATHEMATICAL SCRIPT SMALL C
+1D4B9;1D4B9;1D4B9;0064;0064; # (ð’¹; ð’¹; ð’¹; d; d; ) MATHEMATICAL SCRIPT SMALL D
+1D4BB;1D4BB;1D4BB;0066;0066; # (ð’»; ð’»; ð’»; f; f; ) MATHEMATICAL SCRIPT SMALL F
+1D4BD;1D4BD;1D4BD;0068;0068; # (ð’½; ð’½; ð’½; h; h; ) MATHEMATICAL SCRIPT SMALL H
+1D4BE;1D4BE;1D4BE;0069;0069; # (ð’¾; ð’¾; ð’¾; i; i; ) MATHEMATICAL SCRIPT SMALL I
+1D4BF;1D4BF;1D4BF;006A;006A; # (ð’¿; ð’¿; ð’¿; j; j; ) MATHEMATICAL SCRIPT SMALL J
+1D4C0;1D4C0;1D4C0;006B;006B; # (ð“€; ð“€; ð“€; k; k; ) MATHEMATICAL SCRIPT SMALL K
+1D4C1;1D4C1;1D4C1;006C;006C; # (ð“; ð“; ð“; l; l; ) MATHEMATICAL SCRIPT SMALL L
+1D4C2;1D4C2;1D4C2;006D;006D; # (ð“‚; ð“‚; ð“‚; m; m; ) MATHEMATICAL SCRIPT SMALL M
+1D4C3;1D4C3;1D4C3;006E;006E; # (ð“ƒ; ð“ƒ; ð“ƒ; n; n; ) MATHEMATICAL SCRIPT SMALL N
+1D4C5;1D4C5;1D4C5;0070;0070; # (ð“…; ð“…; ð“…; p; p; ) MATHEMATICAL SCRIPT SMALL P
+1D4C6;1D4C6;1D4C6;0071;0071; # (ð“†; ð“†; ð“†; q; q; ) MATHEMATICAL SCRIPT SMALL Q
+1D4C7;1D4C7;1D4C7;0072;0072; # (ð“‡; ð“‡; ð“‡; r; r; ) MATHEMATICAL SCRIPT SMALL R
+1D4C8;1D4C8;1D4C8;0073;0073; # (ð“ˆ; ð“ˆ; ð“ˆ; s; s; ) MATHEMATICAL SCRIPT SMALL S
+1D4C9;1D4C9;1D4C9;0074;0074; # (ð“‰; ð“‰; ð“‰; t; t; ) MATHEMATICAL SCRIPT SMALL T
+1D4CA;1D4CA;1D4CA;0075;0075; # (ð“Š; ð“Š; ð“Š; u; u; ) MATHEMATICAL SCRIPT SMALL U
+1D4CB;1D4CB;1D4CB;0076;0076; # (ð“‹; ð“‹; ð“‹; v; v; ) MATHEMATICAL SCRIPT SMALL V
+1D4CC;1D4CC;1D4CC;0077;0077; # (ð“Œ; ð“Œ; ð“Œ; w; w; ) MATHEMATICAL SCRIPT SMALL W
+1D4CD;1D4CD;1D4CD;0078;0078; # (ð“; ð“; ð“; x; x; ) MATHEMATICAL SCRIPT SMALL X
+1D4CE;1D4CE;1D4CE;0079;0079; # (ð“Ž; ð“Ž; ð“Ž; y; y; ) MATHEMATICAL SCRIPT SMALL Y
+1D4CF;1D4CF;1D4CF;007A;007A; # (ð“; ð“; ð“; z; z; ) MATHEMATICAL SCRIPT SMALL Z
+1D4D0;1D4D0;1D4D0;0041;0041; # (ð“; ð“; ð“; A; A; ) MATHEMATICAL BOLD SCRIPT CAPITAL A
+1D4D1;1D4D1;1D4D1;0042;0042; # (ð“‘; ð“‘; ð“‘; B; B; ) MATHEMATICAL BOLD SCRIPT CAPITAL B
+1D4D2;1D4D2;1D4D2;0043;0043; # (ð“’; ð“’; ð“’; C; C; ) MATHEMATICAL BOLD SCRIPT CAPITAL C
+1D4D3;1D4D3;1D4D3;0044;0044; # (ð““; ð““; ð““; D; D; ) MATHEMATICAL BOLD SCRIPT CAPITAL D
+1D4D4;1D4D4;1D4D4;0045;0045; # (ð“”; ð“”; ð“”; E; E; ) MATHEMATICAL BOLD SCRIPT CAPITAL E
+1D4D5;1D4D5;1D4D5;0046;0046; # (ð“•; ð“•; ð“•; F; F; ) MATHEMATICAL BOLD SCRIPT CAPITAL F
+1D4D6;1D4D6;1D4D6;0047;0047; # (ð“–; ð“–; ð“–; G; G; ) MATHEMATICAL BOLD SCRIPT CAPITAL G
+1D4D7;1D4D7;1D4D7;0048;0048; # (ð“—; ð“—; ð“—; H; H; ) MATHEMATICAL BOLD SCRIPT CAPITAL H
+1D4D8;1D4D8;1D4D8;0049;0049; # (ð“˜; ð“˜; ð“˜; I; I; ) MATHEMATICAL BOLD SCRIPT CAPITAL I
+1D4D9;1D4D9;1D4D9;004A;004A; # (ð“™; ð“™; ð“™; J; J; ) MATHEMATICAL BOLD SCRIPT CAPITAL J
+1D4DA;1D4DA;1D4DA;004B;004B; # (ð“š; ð“š; ð“š; K; K; ) MATHEMATICAL BOLD SCRIPT CAPITAL K
+1D4DB;1D4DB;1D4DB;004C;004C; # (ð“›; ð“›; ð“›; L; L; ) MATHEMATICAL BOLD SCRIPT CAPITAL L
+1D4DC;1D4DC;1D4DC;004D;004D; # (ð“œ; ð“œ; ð“œ; M; M; ) MATHEMATICAL BOLD SCRIPT CAPITAL M
+1D4DD;1D4DD;1D4DD;004E;004E; # (ð“; ð“; ð“; N; N; ) MATHEMATICAL BOLD SCRIPT CAPITAL N
+1D4DE;1D4DE;1D4DE;004F;004F; # (ð“ž; ð“ž; ð“ž; O; O; ) MATHEMATICAL BOLD SCRIPT CAPITAL O
+1D4DF;1D4DF;1D4DF;0050;0050; # (ð“Ÿ; ð“Ÿ; ð“Ÿ; P; P; ) MATHEMATICAL BOLD SCRIPT CAPITAL P
+1D4E0;1D4E0;1D4E0;0051;0051; # (ð“ ; ð“ ; ð“ ; Q; Q; ) MATHEMATICAL BOLD SCRIPT CAPITAL Q
+1D4E1;1D4E1;1D4E1;0052;0052; # (ð“¡; ð“¡; ð“¡; R; R; ) MATHEMATICAL BOLD SCRIPT CAPITAL R
+1D4E2;1D4E2;1D4E2;0053;0053; # (ð“¢; ð“¢; ð“¢; S; S; ) MATHEMATICAL BOLD SCRIPT CAPITAL S
+1D4E3;1D4E3;1D4E3;0054;0054; # (ð“£; ð“£; ð“£; T; T; ) MATHEMATICAL BOLD SCRIPT CAPITAL T
+1D4E4;1D4E4;1D4E4;0055;0055; # (ð“¤; ð“¤; ð“¤; U; U; ) MATHEMATICAL BOLD SCRIPT CAPITAL U
+1D4E5;1D4E5;1D4E5;0056;0056; # (ð“¥; ð“¥; ð“¥; V; V; ) MATHEMATICAL BOLD SCRIPT CAPITAL V
+1D4E6;1D4E6;1D4E6;0057;0057; # (ð“¦; ð“¦; ð“¦; W; W; ) MATHEMATICAL BOLD SCRIPT CAPITAL W
+1D4E7;1D4E7;1D4E7;0058;0058; # (ð“§; ð“§; ð“§; X; X; ) MATHEMATICAL BOLD SCRIPT CAPITAL X
+1D4E8;1D4E8;1D4E8;0059;0059; # (ð“¨; ð“¨; ð“¨; Y; Y; ) MATHEMATICAL BOLD SCRIPT CAPITAL Y
+1D4E9;1D4E9;1D4E9;005A;005A; # (ð“©; ð“©; ð“©; Z; Z; ) MATHEMATICAL BOLD SCRIPT CAPITAL Z
+1D4EA;1D4EA;1D4EA;0061;0061; # (ð“ª; ð“ª; ð“ª; a; a; ) MATHEMATICAL BOLD SCRIPT SMALL A
+1D4EB;1D4EB;1D4EB;0062;0062; # (ð“«; ð“«; ð“«; b; b; ) MATHEMATICAL BOLD SCRIPT SMALL B
+1D4EC;1D4EC;1D4EC;0063;0063; # (ð“¬; ð“¬; ð“¬; c; c; ) MATHEMATICAL BOLD SCRIPT SMALL C
+1D4ED;1D4ED;1D4ED;0064;0064; # (ð“­; ð“­; ð“­; d; d; ) MATHEMATICAL BOLD SCRIPT SMALL D
+1D4EE;1D4EE;1D4EE;0065;0065; # (ð“®; ð“®; ð“®; e; e; ) MATHEMATICAL BOLD SCRIPT SMALL E
+1D4EF;1D4EF;1D4EF;0066;0066; # (ð“¯; ð“¯; ð“¯; f; f; ) MATHEMATICAL BOLD SCRIPT SMALL F
+1D4F0;1D4F0;1D4F0;0067;0067; # (ð“°; ð“°; ð“°; g; g; ) MATHEMATICAL BOLD SCRIPT SMALL G
+1D4F1;1D4F1;1D4F1;0068;0068; # (ð“±; ð“±; ð“±; h; h; ) MATHEMATICAL BOLD SCRIPT SMALL H
+1D4F2;1D4F2;1D4F2;0069;0069; # (ð“²; ð“²; ð“²; i; i; ) MATHEMATICAL BOLD SCRIPT SMALL I
+1D4F3;1D4F3;1D4F3;006A;006A; # (ð“³; ð“³; ð“³; j; j; ) MATHEMATICAL BOLD SCRIPT SMALL J
+1D4F4;1D4F4;1D4F4;006B;006B; # (ð“´; ð“´; ð“´; k; k; ) MATHEMATICAL BOLD SCRIPT SMALL K
+1D4F5;1D4F5;1D4F5;006C;006C; # (ð“µ; ð“µ; ð“µ; l; l; ) MATHEMATICAL BOLD SCRIPT SMALL L
+1D4F6;1D4F6;1D4F6;006D;006D; # (ð“¶; ð“¶; ð“¶; m; m; ) MATHEMATICAL BOLD SCRIPT SMALL M
+1D4F7;1D4F7;1D4F7;006E;006E; # (ð“·; ð“·; ð“·; n; n; ) MATHEMATICAL BOLD SCRIPT SMALL N
+1D4F8;1D4F8;1D4F8;006F;006F; # (ð“¸; ð“¸; ð“¸; o; o; ) MATHEMATICAL BOLD SCRIPT SMALL O
+1D4F9;1D4F9;1D4F9;0070;0070; # (ð“¹; ð“¹; ð“¹; p; p; ) MATHEMATICAL BOLD SCRIPT SMALL P
+1D4FA;1D4FA;1D4FA;0071;0071; # (ð“º; ð“º; ð“º; q; q; ) MATHEMATICAL BOLD SCRIPT SMALL Q
+1D4FB;1D4FB;1D4FB;0072;0072; # (ð“»; ð“»; ð“»; r; r; ) MATHEMATICAL BOLD SCRIPT SMALL R
+1D4FC;1D4FC;1D4FC;0073;0073; # (ð“¼; ð“¼; ð“¼; s; s; ) MATHEMATICAL BOLD SCRIPT SMALL S
+1D4FD;1D4FD;1D4FD;0074;0074; # (ð“½; ð“½; ð“½; t; t; ) MATHEMATICAL BOLD SCRIPT SMALL T
+1D4FE;1D4FE;1D4FE;0075;0075; # (ð“¾; ð“¾; ð“¾; u; u; ) MATHEMATICAL BOLD SCRIPT SMALL U
+1D4FF;1D4FF;1D4FF;0076;0076; # (ð“¿; ð“¿; ð“¿; v; v; ) MATHEMATICAL BOLD SCRIPT SMALL V
+1D500;1D500;1D500;0077;0077; # (ð”€; ð”€; ð”€; w; w; ) MATHEMATICAL BOLD SCRIPT SMALL W
+1D501;1D501;1D501;0078;0078; # (ð”; ð”; ð”; x; x; ) MATHEMATICAL BOLD SCRIPT SMALL X
+1D502;1D502;1D502;0079;0079; # (ð”‚; ð”‚; ð”‚; y; y; ) MATHEMATICAL BOLD SCRIPT SMALL Y
+1D503;1D503;1D503;007A;007A; # (ð”ƒ; ð”ƒ; ð”ƒ; z; z; ) MATHEMATICAL BOLD SCRIPT SMALL Z
+1D504;1D504;1D504;0041;0041; # (ð”„; ð”„; ð”„; A; A; ) MATHEMATICAL FRAKTUR CAPITAL A
+1D505;1D505;1D505;0042;0042; # (ð”…; ð”…; ð”…; B; B; ) MATHEMATICAL FRAKTUR CAPITAL B
+1D507;1D507;1D507;0044;0044; # (ð”‡; ð”‡; ð”‡; D; D; ) MATHEMATICAL FRAKTUR CAPITAL D
+1D508;1D508;1D508;0045;0045; # (ð”ˆ; ð”ˆ; ð”ˆ; E; E; ) MATHEMATICAL FRAKTUR CAPITAL E
+1D509;1D509;1D509;0046;0046; # (ð”‰; ð”‰; ð”‰; F; F; ) MATHEMATICAL FRAKTUR CAPITAL F
+1D50A;1D50A;1D50A;0047;0047; # (ð”Š; ð”Š; ð”Š; G; G; ) MATHEMATICAL FRAKTUR CAPITAL G
+1D50D;1D50D;1D50D;004A;004A; # (ð”; ð”; ð”; J; J; ) MATHEMATICAL FRAKTUR CAPITAL J
+1D50E;1D50E;1D50E;004B;004B; # (ð”Ž; ð”Ž; ð”Ž; K; K; ) MATHEMATICAL FRAKTUR CAPITAL K
+1D50F;1D50F;1D50F;004C;004C; # (ð”; ð”; ð”; L; L; ) MATHEMATICAL FRAKTUR CAPITAL L
+1D510;1D510;1D510;004D;004D; # (ð”; ð”; ð”; M; M; ) MATHEMATICAL FRAKTUR CAPITAL M
+1D511;1D511;1D511;004E;004E; # (ð”‘; ð”‘; ð”‘; N; N; ) MATHEMATICAL FRAKTUR CAPITAL N
+1D512;1D512;1D512;004F;004F; # (ð”’; ð”’; ð”’; O; O; ) MATHEMATICAL FRAKTUR CAPITAL O
+1D513;1D513;1D513;0050;0050; # (ð”“; ð”“; ð”“; P; P; ) MATHEMATICAL FRAKTUR CAPITAL P
+1D514;1D514;1D514;0051;0051; # (ð””; ð””; ð””; Q; Q; ) MATHEMATICAL FRAKTUR CAPITAL Q
+1D516;1D516;1D516;0053;0053; # (ð”–; ð”–; ð”–; S; S; ) MATHEMATICAL FRAKTUR CAPITAL S
+1D517;1D517;1D517;0054;0054; # (ð”—; ð”—; ð”—; T; T; ) MATHEMATICAL FRAKTUR CAPITAL T
+1D518;1D518;1D518;0055;0055; # (ð”˜; ð”˜; ð”˜; U; U; ) MATHEMATICAL FRAKTUR CAPITAL U
+1D519;1D519;1D519;0056;0056; # (ð”™; ð”™; ð”™; V; V; ) MATHEMATICAL FRAKTUR CAPITAL V
+1D51A;1D51A;1D51A;0057;0057; # (ð”š; ð”š; ð”š; W; W; ) MATHEMATICAL FRAKTUR CAPITAL W
+1D51B;1D51B;1D51B;0058;0058; # (ð”›; ð”›; ð”›; X; X; ) MATHEMATICAL FRAKTUR CAPITAL X
+1D51C;1D51C;1D51C;0059;0059; # (ð”œ; ð”œ; ð”œ; Y; Y; ) MATHEMATICAL FRAKTUR CAPITAL Y
+1D51E;1D51E;1D51E;0061;0061; # (ð”ž; ð”ž; ð”ž; a; a; ) MATHEMATICAL FRAKTUR SMALL A
+1D51F;1D51F;1D51F;0062;0062; # (ð”Ÿ; ð”Ÿ; ð”Ÿ; b; b; ) MATHEMATICAL FRAKTUR SMALL B
+1D520;1D520;1D520;0063;0063; # (ð” ; ð” ; ð” ; c; c; ) MATHEMATICAL FRAKTUR SMALL C
+1D521;1D521;1D521;0064;0064; # (ð”¡; ð”¡; ð”¡; d; d; ) MATHEMATICAL FRAKTUR SMALL D
+1D522;1D522;1D522;0065;0065; # (ð”¢; ð”¢; ð”¢; e; e; ) MATHEMATICAL FRAKTUR SMALL E
+1D523;1D523;1D523;0066;0066; # (ð”£; ð”£; ð”£; f; f; ) MATHEMATICAL FRAKTUR SMALL F
+1D524;1D524;1D524;0067;0067; # (ð”¤; ð”¤; ð”¤; g; g; ) MATHEMATICAL FRAKTUR SMALL G
+1D525;1D525;1D525;0068;0068; # (ð”¥; ð”¥; ð”¥; h; h; ) MATHEMATICAL FRAKTUR SMALL H
+1D526;1D526;1D526;0069;0069; # (ð”¦; ð”¦; ð”¦; i; i; ) MATHEMATICAL FRAKTUR SMALL I
+1D527;1D527;1D527;006A;006A; # (ð”§; ð”§; ð”§; j; j; ) MATHEMATICAL FRAKTUR SMALL J
+1D528;1D528;1D528;006B;006B; # (ð”¨; ð”¨; ð”¨; k; k; ) MATHEMATICAL FRAKTUR SMALL K
+1D529;1D529;1D529;006C;006C; # (ð”©; ð”©; ð”©; l; l; ) MATHEMATICAL FRAKTUR SMALL L
+1D52A;1D52A;1D52A;006D;006D; # (ð”ª; ð”ª; ð”ª; m; m; ) MATHEMATICAL FRAKTUR SMALL M
+1D52B;1D52B;1D52B;006E;006E; # (ð”«; ð”«; ð”«; n; n; ) MATHEMATICAL FRAKTUR SMALL N
+1D52C;1D52C;1D52C;006F;006F; # (ð”¬; ð”¬; ð”¬; o; o; ) MATHEMATICAL FRAKTUR SMALL O
+1D52D;1D52D;1D52D;0070;0070; # (ð”­; ð”­; ð”­; p; p; ) MATHEMATICAL FRAKTUR SMALL P
+1D52E;1D52E;1D52E;0071;0071; # (ð”®; ð”®; ð”®; q; q; ) MATHEMATICAL FRAKTUR SMALL Q
+1D52F;1D52F;1D52F;0072;0072; # (ð”¯; ð”¯; ð”¯; r; r; ) MATHEMATICAL FRAKTUR SMALL R
+1D530;1D530;1D530;0073;0073; # (ð”°; ð”°; ð”°; s; s; ) MATHEMATICAL FRAKTUR SMALL S
+1D531;1D531;1D531;0074;0074; # (ð”±; ð”±; ð”±; t; t; ) MATHEMATICAL FRAKTUR SMALL T
+1D532;1D532;1D532;0075;0075; # (ð”²; ð”²; ð”²; u; u; ) MATHEMATICAL FRAKTUR SMALL U
+1D533;1D533;1D533;0076;0076; # (ð”³; ð”³; ð”³; v; v; ) MATHEMATICAL FRAKTUR SMALL V
+1D534;1D534;1D534;0077;0077; # (ð”´; ð”´; ð”´; w; w; ) MATHEMATICAL FRAKTUR SMALL W
+1D535;1D535;1D535;0078;0078; # (ð”µ; ð”µ; ð”µ; x; x; ) MATHEMATICAL FRAKTUR SMALL X
+1D536;1D536;1D536;0079;0079; # (ð”¶; ð”¶; ð”¶; y; y; ) MATHEMATICAL FRAKTUR SMALL Y
+1D537;1D537;1D537;007A;007A; # (ð”·; ð”·; ð”·; z; z; ) MATHEMATICAL FRAKTUR SMALL Z
+1D538;1D538;1D538;0041;0041; # (ð”¸; ð”¸; ð”¸; A; A; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL A
+1D539;1D539;1D539;0042;0042; # (ð”¹; ð”¹; ð”¹; B; B; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B;1D53B;1D53B;0044;0044; # (ð”»; ð”»; ð”»; D; D; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL D
+1D53C;1D53C;1D53C;0045;0045; # (ð”¼; ð”¼; ð”¼; E; E; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL E
+1D53D;1D53D;1D53D;0046;0046; # (ð”½; ð”½; ð”½; F; F; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL F
+1D53E;1D53E;1D53E;0047;0047; # (ð”¾; ð”¾; ð”¾; G; G; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540;1D540;1D540;0049;0049; # (ð•€; ð•€; ð•€; I; I; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL I
+1D541;1D541;1D541;004A;004A; # (ð•; ð•; ð•; J; J; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL J
+1D542;1D542;1D542;004B;004B; # (ð•‚; ð•‚; ð•‚; K; K; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL K
+1D543;1D543;1D543;004C;004C; # (ð•ƒ; ð•ƒ; ð•ƒ; L; L; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL L
+1D544;1D544;1D544;004D;004D; # (ð•„; ð•„; ð•„; M; M; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546;1D546;1D546;004F;004F; # (ð•†; ð•†; ð•†; O; O; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A;1D54A;1D54A;0053;0053; # (ð•Š; ð•Š; ð•Š; S; S; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL S
+1D54B;1D54B;1D54B;0054;0054; # (ð•‹; ð•‹; ð•‹; T; T; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL T
+1D54C;1D54C;1D54C;0055;0055; # (ð•Œ; ð•Œ; ð•Œ; U; U; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL U
+1D54D;1D54D;1D54D;0056;0056; # (ð•; ð•; ð•; V; V; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL V
+1D54E;1D54E;1D54E;0057;0057; # (ð•Ž; ð•Ž; ð•Ž; W; W; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL W
+1D54F;1D54F;1D54F;0058;0058; # (ð•; ð•; ð•; X; X; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL X
+1D550;1D550;1D550;0059;0059; # (ð•; ð•; ð•; Y; Y; ) MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D552;1D552;1D552;0061;0061; # (ð•’; ð•’; ð•’; a; a; ) MATHEMATICAL DOUBLE-STRUCK SMALL A
+1D553;1D553;1D553;0062;0062; # (ð•“; ð•“; ð•“; b; b; ) MATHEMATICAL DOUBLE-STRUCK SMALL B
+1D554;1D554;1D554;0063;0063; # (ð•”; ð•”; ð•”; c; c; ) MATHEMATICAL DOUBLE-STRUCK SMALL C
+1D555;1D555;1D555;0064;0064; # (ð••; ð••; ð••; d; d; ) MATHEMATICAL DOUBLE-STRUCK SMALL D
+1D556;1D556;1D556;0065;0065; # (ð•–; ð•–; ð•–; e; e; ) MATHEMATICAL DOUBLE-STRUCK SMALL E
+1D557;1D557;1D557;0066;0066; # (ð•—; ð•—; ð•—; f; f; ) MATHEMATICAL DOUBLE-STRUCK SMALL F
+1D558;1D558;1D558;0067;0067; # (ð•˜; ð•˜; ð•˜; g; g; ) MATHEMATICAL DOUBLE-STRUCK SMALL G
+1D559;1D559;1D559;0068;0068; # (ð•™; ð•™; ð•™; h; h; ) MATHEMATICAL DOUBLE-STRUCK SMALL H
+1D55A;1D55A;1D55A;0069;0069; # (ð•š; ð•š; ð•š; i; i; ) MATHEMATICAL DOUBLE-STRUCK SMALL I
+1D55B;1D55B;1D55B;006A;006A; # (ð•›; ð•›; ð•›; j; j; ) MATHEMATICAL DOUBLE-STRUCK SMALL J
+1D55C;1D55C;1D55C;006B;006B; # (ð•œ; ð•œ; ð•œ; k; k; ) MATHEMATICAL DOUBLE-STRUCK SMALL K
+1D55D;1D55D;1D55D;006C;006C; # (ð•; ð•; ð•; l; l; ) MATHEMATICAL DOUBLE-STRUCK SMALL L
+1D55E;1D55E;1D55E;006D;006D; # (ð•ž; ð•ž; ð•ž; m; m; ) MATHEMATICAL DOUBLE-STRUCK SMALL M
+1D55F;1D55F;1D55F;006E;006E; # (ð•Ÿ; ð•Ÿ; ð•Ÿ; n; n; ) MATHEMATICAL DOUBLE-STRUCK SMALL N
+1D560;1D560;1D560;006F;006F; # (ð• ; ð• ; ð• ; o; o; ) MATHEMATICAL DOUBLE-STRUCK SMALL O
+1D561;1D561;1D561;0070;0070; # (ð•¡; ð•¡; ð•¡; p; p; ) MATHEMATICAL DOUBLE-STRUCK SMALL P
+1D562;1D562;1D562;0071;0071; # (ð•¢; ð•¢; ð•¢; q; q; ) MATHEMATICAL DOUBLE-STRUCK SMALL Q
+1D563;1D563;1D563;0072;0072; # (ð•£; ð•£; ð•£; r; r; ) MATHEMATICAL DOUBLE-STRUCK SMALL R
+1D564;1D564;1D564;0073;0073; # (ð•¤; ð•¤; ð•¤; s; s; ) MATHEMATICAL DOUBLE-STRUCK SMALL S
+1D565;1D565;1D565;0074;0074; # (ð•¥; ð•¥; ð•¥; t; t; ) MATHEMATICAL DOUBLE-STRUCK SMALL T
+1D566;1D566;1D566;0075;0075; # (ð•¦; ð•¦; ð•¦; u; u; ) MATHEMATICAL DOUBLE-STRUCK SMALL U
+1D567;1D567;1D567;0076;0076; # (ð•§; ð•§; ð•§; v; v; ) MATHEMATICAL DOUBLE-STRUCK SMALL V
+1D568;1D568;1D568;0077;0077; # (ð•¨; ð•¨; ð•¨; w; w; ) MATHEMATICAL DOUBLE-STRUCK SMALL W
+1D569;1D569;1D569;0078;0078; # (ð•©; ð•©; ð•©; x; x; ) MATHEMATICAL DOUBLE-STRUCK SMALL X
+1D56A;1D56A;1D56A;0079;0079; # (ð•ª; ð•ª; ð•ª; y; y; ) MATHEMATICAL DOUBLE-STRUCK SMALL Y
+1D56B;1D56B;1D56B;007A;007A; # (ð•«; ð•«; ð•«; z; z; ) MATHEMATICAL DOUBLE-STRUCK SMALL Z
+1D56C;1D56C;1D56C;0041;0041; # (ð•¬; ð•¬; ð•¬; A; A; ) MATHEMATICAL BOLD FRAKTUR CAPITAL A
+1D56D;1D56D;1D56D;0042;0042; # (ð•­; ð•­; ð•­; B; B; ) MATHEMATICAL BOLD FRAKTUR CAPITAL B
+1D56E;1D56E;1D56E;0043;0043; # (ð•®; ð•®; ð•®; C; C; ) MATHEMATICAL BOLD FRAKTUR CAPITAL C
+1D56F;1D56F;1D56F;0044;0044; # (ð•¯; ð•¯; ð•¯; D; D; ) MATHEMATICAL BOLD FRAKTUR CAPITAL D
+1D570;1D570;1D570;0045;0045; # (ð•°; ð•°; ð•°; E; E; ) MATHEMATICAL BOLD FRAKTUR CAPITAL E
+1D571;1D571;1D571;0046;0046; # (ð•±; ð•±; ð•±; F; F; ) MATHEMATICAL BOLD FRAKTUR CAPITAL F
+1D572;1D572;1D572;0047;0047; # (ð•²; ð•²; ð•²; G; G; ) MATHEMATICAL BOLD FRAKTUR CAPITAL G
+1D573;1D573;1D573;0048;0048; # (ð•³; ð•³; ð•³; H; H; ) MATHEMATICAL BOLD FRAKTUR CAPITAL H
+1D574;1D574;1D574;0049;0049; # (ð•´; ð•´; ð•´; I; I; ) MATHEMATICAL BOLD FRAKTUR CAPITAL I
+1D575;1D575;1D575;004A;004A; # (ð•µ; ð•µ; ð•µ; J; J; ) MATHEMATICAL BOLD FRAKTUR CAPITAL J
+1D576;1D576;1D576;004B;004B; # (ð•¶; ð•¶; ð•¶; K; K; ) MATHEMATICAL BOLD FRAKTUR CAPITAL K
+1D577;1D577;1D577;004C;004C; # (ð•·; ð•·; ð•·; L; L; ) MATHEMATICAL BOLD FRAKTUR CAPITAL L
+1D578;1D578;1D578;004D;004D; # (ð•¸; ð•¸; ð•¸; M; M; ) MATHEMATICAL BOLD FRAKTUR CAPITAL M
+1D579;1D579;1D579;004E;004E; # (ð•¹; ð•¹; ð•¹; N; N; ) MATHEMATICAL BOLD FRAKTUR CAPITAL N
+1D57A;1D57A;1D57A;004F;004F; # (ð•º; ð•º; ð•º; O; O; ) MATHEMATICAL BOLD FRAKTUR CAPITAL O
+1D57B;1D57B;1D57B;0050;0050; # (ð•»; ð•»; ð•»; P; P; ) MATHEMATICAL BOLD FRAKTUR CAPITAL P
+1D57C;1D57C;1D57C;0051;0051; # (ð•¼; ð•¼; ð•¼; Q; Q; ) MATHEMATICAL BOLD FRAKTUR CAPITAL Q
+1D57D;1D57D;1D57D;0052;0052; # (ð•½; ð•½; ð•½; R; R; ) MATHEMATICAL BOLD FRAKTUR CAPITAL R
+1D57E;1D57E;1D57E;0053;0053; # (ð•¾; ð•¾; ð•¾; S; S; ) MATHEMATICAL BOLD FRAKTUR CAPITAL S
+1D57F;1D57F;1D57F;0054;0054; # (ð•¿; ð•¿; ð•¿; T; T; ) MATHEMATICAL BOLD FRAKTUR CAPITAL T
+1D580;1D580;1D580;0055;0055; # (ð–€; ð–€; ð–€; U; U; ) MATHEMATICAL BOLD FRAKTUR CAPITAL U
+1D581;1D581;1D581;0056;0056; # (ð–; ð–; ð–; V; V; ) MATHEMATICAL BOLD FRAKTUR CAPITAL V
+1D582;1D582;1D582;0057;0057; # (ð–‚; ð–‚; ð–‚; W; W; ) MATHEMATICAL BOLD FRAKTUR CAPITAL W
+1D583;1D583;1D583;0058;0058; # (ð–ƒ; ð–ƒ; ð–ƒ; X; X; ) MATHEMATICAL BOLD FRAKTUR CAPITAL X
+1D584;1D584;1D584;0059;0059; # (ð–„; ð–„; ð–„; Y; Y; ) MATHEMATICAL BOLD FRAKTUR CAPITAL Y
+1D585;1D585;1D585;005A;005A; # (ð–…; ð–…; ð–…; Z; Z; ) MATHEMATICAL BOLD FRAKTUR CAPITAL Z
+1D586;1D586;1D586;0061;0061; # (ð–†; ð–†; ð–†; a; a; ) MATHEMATICAL BOLD FRAKTUR SMALL A
+1D587;1D587;1D587;0062;0062; # (ð–‡; ð–‡; ð–‡; b; b; ) MATHEMATICAL BOLD FRAKTUR SMALL B
+1D588;1D588;1D588;0063;0063; # (ð–ˆ; ð–ˆ; ð–ˆ; c; c; ) MATHEMATICAL BOLD FRAKTUR SMALL C
+1D589;1D589;1D589;0064;0064; # (ð–‰; ð–‰; ð–‰; d; d; ) MATHEMATICAL BOLD FRAKTUR SMALL D
+1D58A;1D58A;1D58A;0065;0065; # (ð–Š; ð–Š; ð–Š; e; e; ) MATHEMATICAL BOLD FRAKTUR SMALL E
+1D58B;1D58B;1D58B;0066;0066; # (ð–‹; ð–‹; ð–‹; f; f; ) MATHEMATICAL BOLD FRAKTUR SMALL F
+1D58C;1D58C;1D58C;0067;0067; # (ð–Œ; ð–Œ; ð–Œ; g; g; ) MATHEMATICAL BOLD FRAKTUR SMALL G
+1D58D;1D58D;1D58D;0068;0068; # (ð–; ð–; ð–; h; h; ) MATHEMATICAL BOLD FRAKTUR SMALL H
+1D58E;1D58E;1D58E;0069;0069; # (ð–Ž; ð–Ž; ð–Ž; i; i; ) MATHEMATICAL BOLD FRAKTUR SMALL I
+1D58F;1D58F;1D58F;006A;006A; # (ð–; ð–; ð–; j; j; ) MATHEMATICAL BOLD FRAKTUR SMALL J
+1D590;1D590;1D590;006B;006B; # (ð–; ð–; ð–; k; k; ) MATHEMATICAL BOLD FRAKTUR SMALL K
+1D591;1D591;1D591;006C;006C; # (ð–‘; ð–‘; ð–‘; l; l; ) MATHEMATICAL BOLD FRAKTUR SMALL L
+1D592;1D592;1D592;006D;006D; # (ð–’; ð–’; ð–’; m; m; ) MATHEMATICAL BOLD FRAKTUR SMALL M
+1D593;1D593;1D593;006E;006E; # (ð–“; ð–“; ð–“; n; n; ) MATHEMATICAL BOLD FRAKTUR SMALL N
+1D594;1D594;1D594;006F;006F; # (ð–”; ð–”; ð–”; o; o; ) MATHEMATICAL BOLD FRAKTUR SMALL O
+1D595;1D595;1D595;0070;0070; # (ð–•; ð–•; ð–•; p; p; ) MATHEMATICAL BOLD FRAKTUR SMALL P
+1D596;1D596;1D596;0071;0071; # (ð––; ð––; ð––; q; q; ) MATHEMATICAL BOLD FRAKTUR SMALL Q
+1D597;1D597;1D597;0072;0072; # (ð–—; ð–—; ð–—; r; r; ) MATHEMATICAL BOLD FRAKTUR SMALL R
+1D598;1D598;1D598;0073;0073; # (ð–˜; ð–˜; ð–˜; s; s; ) MATHEMATICAL BOLD FRAKTUR SMALL S
+1D599;1D599;1D599;0074;0074; # (ð–™; ð–™; ð–™; t; t; ) MATHEMATICAL BOLD FRAKTUR SMALL T
+1D59A;1D59A;1D59A;0075;0075; # (ð–š; ð–š; ð–š; u; u; ) MATHEMATICAL BOLD FRAKTUR SMALL U
+1D59B;1D59B;1D59B;0076;0076; # (ð–›; ð–›; ð–›; v; v; ) MATHEMATICAL BOLD FRAKTUR SMALL V
+1D59C;1D59C;1D59C;0077;0077; # (ð–œ; ð–œ; ð–œ; w; w; ) MATHEMATICAL BOLD FRAKTUR SMALL W
+1D59D;1D59D;1D59D;0078;0078; # (ð–; ð–; ð–; x; x; ) MATHEMATICAL BOLD FRAKTUR SMALL X
+1D59E;1D59E;1D59E;0079;0079; # (ð–ž; ð–ž; ð–ž; y; y; ) MATHEMATICAL BOLD FRAKTUR SMALL Y
+1D59F;1D59F;1D59F;007A;007A; # (ð–Ÿ; ð–Ÿ; ð–Ÿ; z; z; ) MATHEMATICAL BOLD FRAKTUR SMALL Z
+1D5A0;1D5A0;1D5A0;0041;0041; # (ð– ; ð– ; ð– ; A; A; ) MATHEMATICAL SANS-SERIF CAPITAL A
+1D5A1;1D5A1;1D5A1;0042;0042; # (ð–¡; ð–¡; ð–¡; B; B; ) MATHEMATICAL SANS-SERIF CAPITAL B
+1D5A2;1D5A2;1D5A2;0043;0043; # (ð–¢; ð–¢; ð–¢; C; C; ) MATHEMATICAL SANS-SERIF CAPITAL C
+1D5A3;1D5A3;1D5A3;0044;0044; # (ð–£; ð–£; ð–£; D; D; ) MATHEMATICAL SANS-SERIF CAPITAL D
+1D5A4;1D5A4;1D5A4;0045;0045; # (ð–¤; ð–¤; ð–¤; E; E; ) MATHEMATICAL SANS-SERIF CAPITAL E
+1D5A5;1D5A5;1D5A5;0046;0046; # (ð–¥; ð–¥; ð–¥; F; F; ) MATHEMATICAL SANS-SERIF CAPITAL F
+1D5A6;1D5A6;1D5A6;0047;0047; # (ð–¦; ð–¦; ð–¦; G; G; ) MATHEMATICAL SANS-SERIF CAPITAL G
+1D5A7;1D5A7;1D5A7;0048;0048; # (ð–§; ð–§; ð–§; H; H; ) MATHEMATICAL SANS-SERIF CAPITAL H
+1D5A8;1D5A8;1D5A8;0049;0049; # (ð–¨; ð–¨; ð–¨; I; I; ) MATHEMATICAL SANS-SERIF CAPITAL I
+1D5A9;1D5A9;1D5A9;004A;004A; # (ð–©; ð–©; ð–©; J; J; ) MATHEMATICAL SANS-SERIF CAPITAL J
+1D5AA;1D5AA;1D5AA;004B;004B; # (ð–ª; ð–ª; ð–ª; K; K; ) MATHEMATICAL SANS-SERIF CAPITAL K
+1D5AB;1D5AB;1D5AB;004C;004C; # (ð–«; ð–«; ð–«; L; L; ) MATHEMATICAL SANS-SERIF CAPITAL L
+1D5AC;1D5AC;1D5AC;004D;004D; # (ð–¬; ð–¬; ð–¬; M; M; ) MATHEMATICAL SANS-SERIF CAPITAL M
+1D5AD;1D5AD;1D5AD;004E;004E; # (ð–­; ð–­; ð–­; N; N; ) MATHEMATICAL SANS-SERIF CAPITAL N
+1D5AE;1D5AE;1D5AE;004F;004F; # (ð–®; ð–®; ð–®; O; O; ) MATHEMATICAL SANS-SERIF CAPITAL O
+1D5AF;1D5AF;1D5AF;0050;0050; # (ð–¯; ð–¯; ð–¯; P; P; ) MATHEMATICAL SANS-SERIF CAPITAL P
+1D5B0;1D5B0;1D5B0;0051;0051; # (ð–°; ð–°; ð–°; Q; Q; ) MATHEMATICAL SANS-SERIF CAPITAL Q
+1D5B1;1D5B1;1D5B1;0052;0052; # (ð–±; ð–±; ð–±; R; R; ) MATHEMATICAL SANS-SERIF CAPITAL R
+1D5B2;1D5B2;1D5B2;0053;0053; # (ð–²; ð–²; ð–²; S; S; ) MATHEMATICAL SANS-SERIF CAPITAL S
+1D5B3;1D5B3;1D5B3;0054;0054; # (ð–³; ð–³; ð–³; T; T; ) MATHEMATICAL SANS-SERIF CAPITAL T
+1D5B4;1D5B4;1D5B4;0055;0055; # (ð–´; ð–´; ð–´; U; U; ) MATHEMATICAL SANS-SERIF CAPITAL U
+1D5B5;1D5B5;1D5B5;0056;0056; # (ð–µ; ð–µ; ð–µ; V; V; ) MATHEMATICAL SANS-SERIF CAPITAL V
+1D5B6;1D5B6;1D5B6;0057;0057; # (ð–¶; ð–¶; ð–¶; W; W; ) MATHEMATICAL SANS-SERIF CAPITAL W
+1D5B7;1D5B7;1D5B7;0058;0058; # (ð–·; ð–·; ð–·; X; X; ) MATHEMATICAL SANS-SERIF CAPITAL X
+1D5B8;1D5B8;1D5B8;0059;0059; # (ð–¸; ð–¸; ð–¸; Y; Y; ) MATHEMATICAL SANS-SERIF CAPITAL Y
+1D5B9;1D5B9;1D5B9;005A;005A; # (ð–¹; ð–¹; ð–¹; Z; Z; ) MATHEMATICAL SANS-SERIF CAPITAL Z
+1D5BA;1D5BA;1D5BA;0061;0061; # (ð–º; ð–º; ð–º; a; a; ) MATHEMATICAL SANS-SERIF SMALL A
+1D5BB;1D5BB;1D5BB;0062;0062; # (ð–»; ð–»; ð–»; b; b; ) MATHEMATICAL SANS-SERIF SMALL B
+1D5BC;1D5BC;1D5BC;0063;0063; # (ð–¼; ð–¼; ð–¼; c; c; ) MATHEMATICAL SANS-SERIF SMALL C
+1D5BD;1D5BD;1D5BD;0064;0064; # (ð–½; ð–½; ð–½; d; d; ) MATHEMATICAL SANS-SERIF SMALL D
+1D5BE;1D5BE;1D5BE;0065;0065; # (ð–¾; ð–¾; ð–¾; e; e; ) MATHEMATICAL SANS-SERIF SMALL E
+1D5BF;1D5BF;1D5BF;0066;0066; # (ð–¿; ð–¿; ð–¿; f; f; ) MATHEMATICAL SANS-SERIF SMALL F
+1D5C0;1D5C0;1D5C0;0067;0067; # (ð—€; ð—€; ð—€; g; g; ) MATHEMATICAL SANS-SERIF SMALL G
+1D5C1;1D5C1;1D5C1;0068;0068; # (ð—; ð—; ð—; h; h; ) MATHEMATICAL SANS-SERIF SMALL H
+1D5C2;1D5C2;1D5C2;0069;0069; # (ð—‚; ð—‚; ð—‚; i; i; ) MATHEMATICAL SANS-SERIF SMALL I
+1D5C3;1D5C3;1D5C3;006A;006A; # (ð—ƒ; ð—ƒ; ð—ƒ; j; j; ) MATHEMATICAL SANS-SERIF SMALL J
+1D5C4;1D5C4;1D5C4;006B;006B; # (ð—„; ð—„; ð—„; k; k; ) MATHEMATICAL SANS-SERIF SMALL K
+1D5C5;1D5C5;1D5C5;006C;006C; # (ð—…; ð—…; ð—…; l; l; ) MATHEMATICAL SANS-SERIF SMALL L
+1D5C6;1D5C6;1D5C6;006D;006D; # (ð—†; ð—†; ð—†; m; m; ) MATHEMATICAL SANS-SERIF SMALL M
+1D5C7;1D5C7;1D5C7;006E;006E; # (ð—‡; ð—‡; ð—‡; n; n; ) MATHEMATICAL SANS-SERIF SMALL N
+1D5C8;1D5C8;1D5C8;006F;006F; # (ð—ˆ; ð—ˆ; ð—ˆ; o; o; ) MATHEMATICAL SANS-SERIF SMALL O
+1D5C9;1D5C9;1D5C9;0070;0070; # (ð—‰; ð—‰; ð—‰; p; p; ) MATHEMATICAL SANS-SERIF SMALL P
+1D5CA;1D5CA;1D5CA;0071;0071; # (ð—Š; ð—Š; ð—Š; q; q; ) MATHEMATICAL SANS-SERIF SMALL Q
+1D5CB;1D5CB;1D5CB;0072;0072; # (ð—‹; ð—‹; ð—‹; r; r; ) MATHEMATICAL SANS-SERIF SMALL R
+1D5CC;1D5CC;1D5CC;0073;0073; # (ð—Œ; ð—Œ; ð—Œ; s; s; ) MATHEMATICAL SANS-SERIF SMALL S
+1D5CD;1D5CD;1D5CD;0074;0074; # (ð—; ð—; ð—; t; t; ) MATHEMATICAL SANS-SERIF SMALL T
+1D5CE;1D5CE;1D5CE;0075;0075; # (ð—Ž; ð—Ž; ð—Ž; u; u; ) MATHEMATICAL SANS-SERIF SMALL U
+1D5CF;1D5CF;1D5CF;0076;0076; # (ð—; ð—; ð—; v; v; ) MATHEMATICAL SANS-SERIF SMALL V
+1D5D0;1D5D0;1D5D0;0077;0077; # (ð—; ð—; ð—; w; w; ) MATHEMATICAL SANS-SERIF SMALL W
+1D5D1;1D5D1;1D5D1;0078;0078; # (ð—‘; ð—‘; ð—‘; x; x; ) MATHEMATICAL SANS-SERIF SMALL X
+1D5D2;1D5D2;1D5D2;0079;0079; # (ð—’; ð—’; ð—’; y; y; ) MATHEMATICAL SANS-SERIF SMALL Y
+1D5D3;1D5D3;1D5D3;007A;007A; # (ð—“; ð—“; ð—“; z; z; ) MATHEMATICAL SANS-SERIF SMALL Z
+1D5D4;1D5D4;1D5D4;0041;0041; # (ð—”; ð—”; ð—”; A; A; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL A
+1D5D5;1D5D5;1D5D5;0042;0042; # (ð—•; ð—•; ð—•; B; B; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL B
+1D5D6;1D5D6;1D5D6;0043;0043; # (ð—–; ð—–; ð—–; C; C; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL C
+1D5D7;1D5D7;1D5D7;0044;0044; # (ð——; ð——; ð——; D; D; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL D
+1D5D8;1D5D8;1D5D8;0045;0045; # (ð—˜; ð—˜; ð—˜; E; E; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL E
+1D5D9;1D5D9;1D5D9;0046;0046; # (ð—™; ð—™; ð—™; F; F; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL F
+1D5DA;1D5DA;1D5DA;0047;0047; # (ð—š; ð—š; ð—š; G; G; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL G
+1D5DB;1D5DB;1D5DB;0048;0048; # (ð—›; ð—›; ð—›; H; H; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL H
+1D5DC;1D5DC;1D5DC;0049;0049; # (ð—œ; ð—œ; ð—œ; I; I; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL I
+1D5DD;1D5DD;1D5DD;004A;004A; # (ð—; ð—; ð—; J; J; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL J
+1D5DE;1D5DE;1D5DE;004B;004B; # (ð—ž; ð—ž; ð—ž; K; K; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL K
+1D5DF;1D5DF;1D5DF;004C;004C; # (ð—Ÿ; ð—Ÿ; ð—Ÿ; L; L; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL L
+1D5E0;1D5E0;1D5E0;004D;004D; # (ð— ; ð— ; ð— ; M; M; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL M
+1D5E1;1D5E1;1D5E1;004E;004E; # (ð—¡; ð—¡; ð—¡; N; N; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL N
+1D5E2;1D5E2;1D5E2;004F;004F; # (ð—¢; ð—¢; ð—¢; O; O; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL O
+1D5E3;1D5E3;1D5E3;0050;0050; # (ð—£; ð—£; ð—£; P; P; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL P
+1D5E4;1D5E4;1D5E4;0051;0051; # (ð—¤; ð—¤; ð—¤; Q; Q; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL Q
+1D5E5;1D5E5;1D5E5;0052;0052; # (ð—¥; ð—¥; ð—¥; R; R; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL R
+1D5E6;1D5E6;1D5E6;0053;0053; # (ð—¦; ð—¦; ð—¦; S; S; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL S
+1D5E7;1D5E7;1D5E7;0054;0054; # (ð—§; ð—§; ð—§; T; T; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL T
+1D5E8;1D5E8;1D5E8;0055;0055; # (ð—¨; ð—¨; ð—¨; U; U; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL U
+1D5E9;1D5E9;1D5E9;0056;0056; # (ð—©; ð—©; ð—©; V; V; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL V
+1D5EA;1D5EA;1D5EA;0057;0057; # (ð—ª; ð—ª; ð—ª; W; W; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL W
+1D5EB;1D5EB;1D5EB;0058;0058; # (ð—«; ð—«; ð—«; X; X; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL X
+1D5EC;1D5EC;1D5EC;0059;0059; # (ð—¬; ð—¬; ð—¬; Y; Y; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL Y
+1D5ED;1D5ED;1D5ED;005A;005A; # (ð—­; ð—­; ð—­; Z; Z; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL Z
+1D5EE;1D5EE;1D5EE;0061;0061; # (ð—®; ð—®; ð—®; a; a; ) MATHEMATICAL SANS-SERIF BOLD SMALL A
+1D5EF;1D5EF;1D5EF;0062;0062; # (ð—¯; ð—¯; ð—¯; b; b; ) MATHEMATICAL SANS-SERIF BOLD SMALL B
+1D5F0;1D5F0;1D5F0;0063;0063; # (ð—°; ð—°; ð—°; c; c; ) MATHEMATICAL SANS-SERIF BOLD SMALL C
+1D5F1;1D5F1;1D5F1;0064;0064; # (ð—±; ð—±; ð—±; d; d; ) MATHEMATICAL SANS-SERIF BOLD SMALL D
+1D5F2;1D5F2;1D5F2;0065;0065; # (ð—²; ð—²; ð—²; e; e; ) MATHEMATICAL SANS-SERIF BOLD SMALL E
+1D5F3;1D5F3;1D5F3;0066;0066; # (ð—³; ð—³; ð—³; f; f; ) MATHEMATICAL SANS-SERIF BOLD SMALL F
+1D5F4;1D5F4;1D5F4;0067;0067; # (ð—´; ð—´; ð—´; g; g; ) MATHEMATICAL SANS-SERIF BOLD SMALL G
+1D5F5;1D5F5;1D5F5;0068;0068; # (ð—µ; ð—µ; ð—µ; h; h; ) MATHEMATICAL SANS-SERIF BOLD SMALL H
+1D5F6;1D5F6;1D5F6;0069;0069; # (ð—¶; ð—¶; ð—¶; i; i; ) MATHEMATICAL SANS-SERIF BOLD SMALL I
+1D5F7;1D5F7;1D5F7;006A;006A; # (ð—·; ð—·; ð—·; j; j; ) MATHEMATICAL SANS-SERIF BOLD SMALL J
+1D5F8;1D5F8;1D5F8;006B;006B; # (ð—¸; ð—¸; ð—¸; k; k; ) MATHEMATICAL SANS-SERIF BOLD SMALL K
+1D5F9;1D5F9;1D5F9;006C;006C; # (ð—¹; ð—¹; ð—¹; l; l; ) MATHEMATICAL SANS-SERIF BOLD SMALL L
+1D5FA;1D5FA;1D5FA;006D;006D; # (ð—º; ð—º; ð—º; m; m; ) MATHEMATICAL SANS-SERIF BOLD SMALL M
+1D5FB;1D5FB;1D5FB;006E;006E; # (ð—»; ð—»; ð—»; n; n; ) MATHEMATICAL SANS-SERIF BOLD SMALL N
+1D5FC;1D5FC;1D5FC;006F;006F; # (ð—¼; ð—¼; ð—¼; o; o; ) MATHEMATICAL SANS-SERIF BOLD SMALL O
+1D5FD;1D5FD;1D5FD;0070;0070; # (ð—½; ð—½; ð—½; p; p; ) MATHEMATICAL SANS-SERIF BOLD SMALL P
+1D5FE;1D5FE;1D5FE;0071;0071; # (ð—¾; ð—¾; ð—¾; q; q; ) MATHEMATICAL SANS-SERIF BOLD SMALL Q
+1D5FF;1D5FF;1D5FF;0072;0072; # (ð—¿; ð—¿; ð—¿; r; r; ) MATHEMATICAL SANS-SERIF BOLD SMALL R
+1D600;1D600;1D600;0073;0073; # (ð˜€; ð˜€; ð˜€; s; s; ) MATHEMATICAL SANS-SERIF BOLD SMALL S
+1D601;1D601;1D601;0074;0074; # (ð˜; ð˜; ð˜; t; t; ) MATHEMATICAL SANS-SERIF BOLD SMALL T
+1D602;1D602;1D602;0075;0075; # (ð˜‚; ð˜‚; ð˜‚; u; u; ) MATHEMATICAL SANS-SERIF BOLD SMALL U
+1D603;1D603;1D603;0076;0076; # (ð˜ƒ; ð˜ƒ; ð˜ƒ; v; v; ) MATHEMATICAL SANS-SERIF BOLD SMALL V
+1D604;1D604;1D604;0077;0077; # (ð˜„; ð˜„; ð˜„; w; w; ) MATHEMATICAL SANS-SERIF BOLD SMALL W
+1D605;1D605;1D605;0078;0078; # (ð˜…; ð˜…; ð˜…; x; x; ) MATHEMATICAL SANS-SERIF BOLD SMALL X
+1D606;1D606;1D606;0079;0079; # (ð˜†; ð˜†; ð˜†; y; y; ) MATHEMATICAL SANS-SERIF BOLD SMALL Y
+1D607;1D607;1D607;007A;007A; # (ð˜‡; ð˜‡; ð˜‡; z; z; ) MATHEMATICAL SANS-SERIF BOLD SMALL Z
+1D608;1D608;1D608;0041;0041; # (ð˜ˆ; ð˜ˆ; ð˜ˆ; A; A; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL A
+1D609;1D609;1D609;0042;0042; # (ð˜‰; ð˜‰; ð˜‰; B; B; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL B
+1D60A;1D60A;1D60A;0043;0043; # (ð˜Š; ð˜Š; ð˜Š; C; C; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL C
+1D60B;1D60B;1D60B;0044;0044; # (ð˜‹; ð˜‹; ð˜‹; D; D; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL D
+1D60C;1D60C;1D60C;0045;0045; # (ð˜Œ; ð˜Œ; ð˜Œ; E; E; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL E
+1D60D;1D60D;1D60D;0046;0046; # (ð˜; ð˜; ð˜; F; F; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL F
+1D60E;1D60E;1D60E;0047;0047; # (ð˜Ž; ð˜Ž; ð˜Ž; G; G; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL G
+1D60F;1D60F;1D60F;0048;0048; # (ð˜; ð˜; ð˜; H; H; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL H
+1D610;1D610;1D610;0049;0049; # (ð˜; ð˜; ð˜; I; I; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL I
+1D611;1D611;1D611;004A;004A; # (ð˜‘; ð˜‘; ð˜‘; J; J; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL J
+1D612;1D612;1D612;004B;004B; # (ð˜’; ð˜’; ð˜’; K; K; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL K
+1D613;1D613;1D613;004C;004C; # (ð˜“; ð˜“; ð˜“; L; L; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL L
+1D614;1D614;1D614;004D;004D; # (ð˜”; ð˜”; ð˜”; M; M; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL M
+1D615;1D615;1D615;004E;004E; # (ð˜•; ð˜•; ð˜•; N; N; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL N
+1D616;1D616;1D616;004F;004F; # (ð˜–; ð˜–; ð˜–; O; O; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL O
+1D617;1D617;1D617;0050;0050; # (ð˜—; ð˜—; ð˜—; P; P; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL P
+1D618;1D618;1D618;0051;0051; # (ð˜˜; ð˜˜; ð˜˜; Q; Q; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL Q
+1D619;1D619;1D619;0052;0052; # (ð˜™; ð˜™; ð˜™; R; R; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL R
+1D61A;1D61A;1D61A;0053;0053; # (ð˜š; ð˜š; ð˜š; S; S; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL S
+1D61B;1D61B;1D61B;0054;0054; # (ð˜›; ð˜›; ð˜›; T; T; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL T
+1D61C;1D61C;1D61C;0055;0055; # (ð˜œ; ð˜œ; ð˜œ; U; U; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL U
+1D61D;1D61D;1D61D;0056;0056; # (ð˜; ð˜; ð˜; V; V; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL V
+1D61E;1D61E;1D61E;0057;0057; # (ð˜ž; ð˜ž; ð˜ž; W; W; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL W
+1D61F;1D61F;1D61F;0058;0058; # (ð˜Ÿ; ð˜Ÿ; ð˜Ÿ; X; X; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL X
+1D620;1D620;1D620;0059;0059; # (ð˜ ; ð˜ ; ð˜ ; Y; Y; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL Y
+1D621;1D621;1D621;005A;005A; # (ð˜¡; ð˜¡; ð˜¡; Z; Z; ) MATHEMATICAL SANS-SERIF ITALIC CAPITAL Z
+1D622;1D622;1D622;0061;0061; # (ð˜¢; ð˜¢; ð˜¢; a; a; ) MATHEMATICAL SANS-SERIF ITALIC SMALL A
+1D623;1D623;1D623;0062;0062; # (ð˜£; ð˜£; ð˜£; b; b; ) MATHEMATICAL SANS-SERIF ITALIC SMALL B
+1D624;1D624;1D624;0063;0063; # (ð˜¤; ð˜¤; ð˜¤; c; c; ) MATHEMATICAL SANS-SERIF ITALIC SMALL C
+1D625;1D625;1D625;0064;0064; # (ð˜¥; ð˜¥; ð˜¥; d; d; ) MATHEMATICAL SANS-SERIF ITALIC SMALL D
+1D626;1D626;1D626;0065;0065; # (ð˜¦; ð˜¦; ð˜¦; e; e; ) MATHEMATICAL SANS-SERIF ITALIC SMALL E
+1D627;1D627;1D627;0066;0066; # (ð˜§; ð˜§; ð˜§; f; f; ) MATHEMATICAL SANS-SERIF ITALIC SMALL F
+1D628;1D628;1D628;0067;0067; # (ð˜¨; ð˜¨; ð˜¨; g; g; ) MATHEMATICAL SANS-SERIF ITALIC SMALL G
+1D629;1D629;1D629;0068;0068; # (ð˜©; ð˜©; ð˜©; h; h; ) MATHEMATICAL SANS-SERIF ITALIC SMALL H
+1D62A;1D62A;1D62A;0069;0069; # (ð˜ª; ð˜ª; ð˜ª; i; i; ) MATHEMATICAL SANS-SERIF ITALIC SMALL I
+1D62B;1D62B;1D62B;006A;006A; # (ð˜«; ð˜«; ð˜«; j; j; ) MATHEMATICAL SANS-SERIF ITALIC SMALL J
+1D62C;1D62C;1D62C;006B;006B; # (ð˜¬; ð˜¬; ð˜¬; k; k; ) MATHEMATICAL SANS-SERIF ITALIC SMALL K
+1D62D;1D62D;1D62D;006C;006C; # (ð˜­; ð˜­; ð˜­; l; l; ) MATHEMATICAL SANS-SERIF ITALIC SMALL L
+1D62E;1D62E;1D62E;006D;006D; # (ð˜®; ð˜®; ð˜®; m; m; ) MATHEMATICAL SANS-SERIF ITALIC SMALL M
+1D62F;1D62F;1D62F;006E;006E; # (ð˜¯; ð˜¯; ð˜¯; n; n; ) MATHEMATICAL SANS-SERIF ITALIC SMALL N
+1D630;1D630;1D630;006F;006F; # (ð˜°; ð˜°; ð˜°; o; o; ) MATHEMATICAL SANS-SERIF ITALIC SMALL O
+1D631;1D631;1D631;0070;0070; # (ð˜±; ð˜±; ð˜±; p; p; ) MATHEMATICAL SANS-SERIF ITALIC SMALL P
+1D632;1D632;1D632;0071;0071; # (ð˜²; ð˜²; ð˜²; q; q; ) MATHEMATICAL SANS-SERIF ITALIC SMALL Q
+1D633;1D633;1D633;0072;0072; # (ð˜³; ð˜³; ð˜³; r; r; ) MATHEMATICAL SANS-SERIF ITALIC SMALL R
+1D634;1D634;1D634;0073;0073; # (ð˜´; ð˜´; ð˜´; s; s; ) MATHEMATICAL SANS-SERIF ITALIC SMALL S
+1D635;1D635;1D635;0074;0074; # (ð˜µ; ð˜µ; ð˜µ; t; t; ) MATHEMATICAL SANS-SERIF ITALIC SMALL T
+1D636;1D636;1D636;0075;0075; # (ð˜¶; ð˜¶; ð˜¶; u; u; ) MATHEMATICAL SANS-SERIF ITALIC SMALL U
+1D637;1D637;1D637;0076;0076; # (ð˜·; ð˜·; ð˜·; v; v; ) MATHEMATICAL SANS-SERIF ITALIC SMALL V
+1D638;1D638;1D638;0077;0077; # (ð˜¸; ð˜¸; ð˜¸; w; w; ) MATHEMATICAL SANS-SERIF ITALIC SMALL W
+1D639;1D639;1D639;0078;0078; # (ð˜¹; ð˜¹; ð˜¹; x; x; ) MATHEMATICAL SANS-SERIF ITALIC SMALL X
+1D63A;1D63A;1D63A;0079;0079; # (ð˜º; ð˜º; ð˜º; y; y; ) MATHEMATICAL SANS-SERIF ITALIC SMALL Y
+1D63B;1D63B;1D63B;007A;007A; # (ð˜»; ð˜»; ð˜»; z; z; ) MATHEMATICAL SANS-SERIF ITALIC SMALL Z
+1D63C;1D63C;1D63C;0041;0041; # (ð˜¼; ð˜¼; ð˜¼; A; A; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL A
+1D63D;1D63D;1D63D;0042;0042; # (ð˜½; ð˜½; ð˜½; B; B; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL B
+1D63E;1D63E;1D63E;0043;0043; # (ð˜¾; ð˜¾; ð˜¾; C; C; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL C
+1D63F;1D63F;1D63F;0044;0044; # (ð˜¿; ð˜¿; ð˜¿; D; D; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL D
+1D640;1D640;1D640;0045;0045; # (ð™€; ð™€; ð™€; E; E; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL E
+1D641;1D641;1D641;0046;0046; # (ð™; ð™; ð™; F; F; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL F
+1D642;1D642;1D642;0047;0047; # (ð™‚; ð™‚; ð™‚; G; G; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL G
+1D643;1D643;1D643;0048;0048; # (ð™ƒ; ð™ƒ; ð™ƒ; H; H; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL H
+1D644;1D644;1D644;0049;0049; # (ð™„; ð™„; ð™„; I; I; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL I
+1D645;1D645;1D645;004A;004A; # (ð™…; ð™…; ð™…; J; J; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL J
+1D646;1D646;1D646;004B;004B; # (ð™†; ð™†; ð™†; K; K; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL K
+1D647;1D647;1D647;004C;004C; # (ð™‡; ð™‡; ð™‡; L; L; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL L
+1D648;1D648;1D648;004D;004D; # (ð™ˆ; ð™ˆ; ð™ˆ; M; M; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL M
+1D649;1D649;1D649;004E;004E; # (ð™‰; ð™‰; ð™‰; N; N; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL N
+1D64A;1D64A;1D64A;004F;004F; # (ð™Š; ð™Š; ð™Š; O; O; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL O
+1D64B;1D64B;1D64B;0050;0050; # (ð™‹; ð™‹; ð™‹; P; P; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL P
+1D64C;1D64C;1D64C;0051;0051; # (ð™Œ; ð™Œ; ð™Œ; Q; Q; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Q
+1D64D;1D64D;1D64D;0052;0052; # (ð™; ð™; ð™; R; R; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL R
+1D64E;1D64E;1D64E;0053;0053; # (ð™Ž; ð™Ž; ð™Ž; S; S; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL S
+1D64F;1D64F;1D64F;0054;0054; # (ð™; ð™; ð™; T; T; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL T
+1D650;1D650;1D650;0055;0055; # (ð™; ð™; ð™; U; U; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL U
+1D651;1D651;1D651;0056;0056; # (ð™‘; ð™‘; ð™‘; V; V; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL V
+1D652;1D652;1D652;0057;0057; # (ð™’; ð™’; ð™’; W; W; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL W
+1D653;1D653;1D653;0058;0058; # (ð™“; ð™“; ð™“; X; X; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL X
+1D654;1D654;1D654;0059;0059; # (ð™”; ð™”; ð™”; Y; Y; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Y
+1D655;1D655;1D655;005A;005A; # (ð™•; ð™•; ð™•; Z; Z; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Z
+1D656;1D656;1D656;0061;0061; # (ð™–; ð™–; ð™–; a; a; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL A
+1D657;1D657;1D657;0062;0062; # (ð™—; ð™—; ð™—; b; b; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL B
+1D658;1D658;1D658;0063;0063; # (ð™˜; ð™˜; ð™˜; c; c; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL C
+1D659;1D659;1D659;0064;0064; # (ð™™; ð™™; ð™™; d; d; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL D
+1D65A;1D65A;1D65A;0065;0065; # (ð™š; ð™š; ð™š; e; e; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL E
+1D65B;1D65B;1D65B;0066;0066; # (ð™›; ð™›; ð™›; f; f; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL F
+1D65C;1D65C;1D65C;0067;0067; # (ð™œ; ð™œ; ð™œ; g; g; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL G
+1D65D;1D65D;1D65D;0068;0068; # (ð™; ð™; ð™; h; h; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL H
+1D65E;1D65E;1D65E;0069;0069; # (ð™ž; ð™ž; ð™ž; i; i; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL I
+1D65F;1D65F;1D65F;006A;006A; # (ð™Ÿ; ð™Ÿ; ð™Ÿ; j; j; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL J
+1D660;1D660;1D660;006B;006B; # (ð™ ; ð™ ; ð™ ; k; k; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL K
+1D661;1D661;1D661;006C;006C; # (ð™¡; ð™¡; ð™¡; l; l; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL L
+1D662;1D662;1D662;006D;006D; # (ð™¢; ð™¢; ð™¢; m; m; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL M
+1D663;1D663;1D663;006E;006E; # (ð™£; ð™£; ð™£; n; n; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL N
+1D664;1D664;1D664;006F;006F; # (ð™¤; ð™¤; ð™¤; o; o; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL O
+1D665;1D665;1D665;0070;0070; # (ð™¥; ð™¥; ð™¥; p; p; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL P
+1D666;1D666;1D666;0071;0071; # (ð™¦; ð™¦; ð™¦; q; q; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Q
+1D667;1D667;1D667;0072;0072; # (ð™§; ð™§; ð™§; r; r; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL R
+1D668;1D668;1D668;0073;0073; # (ð™¨; ð™¨; ð™¨; s; s; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL S
+1D669;1D669;1D669;0074;0074; # (ð™©; ð™©; ð™©; t; t; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL T
+1D66A;1D66A;1D66A;0075;0075; # (ð™ª; ð™ª; ð™ª; u; u; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL U
+1D66B;1D66B;1D66B;0076;0076; # (ð™«; ð™«; ð™«; v; v; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL V
+1D66C;1D66C;1D66C;0077;0077; # (ð™¬; ð™¬; ð™¬; w; w; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL W
+1D66D;1D66D;1D66D;0078;0078; # (ð™­; ð™­; ð™­; x; x; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL X
+1D66E;1D66E;1D66E;0079;0079; # (ð™®; ð™®; ð™®; y; y; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Y
+1D66F;1D66F;1D66F;007A;007A; # (ð™¯; ð™¯; ð™¯; z; z; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Z
+1D670;1D670;1D670;0041;0041; # (ð™°; ð™°; ð™°; A; A; ) MATHEMATICAL MONOSPACE CAPITAL A
+1D671;1D671;1D671;0042;0042; # (ð™±; ð™±; ð™±; B; B; ) MATHEMATICAL MONOSPACE CAPITAL B
+1D672;1D672;1D672;0043;0043; # (ð™²; ð™²; ð™²; C; C; ) MATHEMATICAL MONOSPACE CAPITAL C
+1D673;1D673;1D673;0044;0044; # (ð™³; ð™³; ð™³; D; D; ) MATHEMATICAL MONOSPACE CAPITAL D
+1D674;1D674;1D674;0045;0045; # (ð™´; ð™´; ð™´; E; E; ) MATHEMATICAL MONOSPACE CAPITAL E
+1D675;1D675;1D675;0046;0046; # (ð™µ; ð™µ; ð™µ; F; F; ) MATHEMATICAL MONOSPACE CAPITAL F
+1D676;1D676;1D676;0047;0047; # (ð™¶; ð™¶; ð™¶; G; G; ) MATHEMATICAL MONOSPACE CAPITAL G
+1D677;1D677;1D677;0048;0048; # (ð™·; ð™·; ð™·; H; H; ) MATHEMATICAL MONOSPACE CAPITAL H
+1D678;1D678;1D678;0049;0049; # (ð™¸; ð™¸; ð™¸; I; I; ) MATHEMATICAL MONOSPACE CAPITAL I
+1D679;1D679;1D679;004A;004A; # (ð™¹; ð™¹; ð™¹; J; J; ) MATHEMATICAL MONOSPACE CAPITAL J
+1D67A;1D67A;1D67A;004B;004B; # (ð™º; ð™º; ð™º; K; K; ) MATHEMATICAL MONOSPACE CAPITAL K
+1D67B;1D67B;1D67B;004C;004C; # (ð™»; ð™»; ð™»; L; L; ) MATHEMATICAL MONOSPACE CAPITAL L
+1D67C;1D67C;1D67C;004D;004D; # (ð™¼; ð™¼; ð™¼; M; M; ) MATHEMATICAL MONOSPACE CAPITAL M
+1D67D;1D67D;1D67D;004E;004E; # (ð™½; ð™½; ð™½; N; N; ) MATHEMATICAL MONOSPACE CAPITAL N
+1D67E;1D67E;1D67E;004F;004F; # (ð™¾; ð™¾; ð™¾; O; O; ) MATHEMATICAL MONOSPACE CAPITAL O
+1D67F;1D67F;1D67F;0050;0050; # (ð™¿; ð™¿; ð™¿; P; P; ) MATHEMATICAL MONOSPACE CAPITAL P
+1D680;1D680;1D680;0051;0051; # (ðš€; ðš€; ðš€; Q; Q; ) MATHEMATICAL MONOSPACE CAPITAL Q
+1D681;1D681;1D681;0052;0052; # (ðš; ðš; ðš; R; R; ) MATHEMATICAL MONOSPACE CAPITAL R
+1D682;1D682;1D682;0053;0053; # (ðš‚; ðš‚; ðš‚; S; S; ) MATHEMATICAL MONOSPACE CAPITAL S
+1D683;1D683;1D683;0054;0054; # (ðšƒ; ðšƒ; ðšƒ; T; T; ) MATHEMATICAL MONOSPACE CAPITAL T
+1D684;1D684;1D684;0055;0055; # (ðš„; ðš„; ðš„; U; U; ) MATHEMATICAL MONOSPACE CAPITAL U
+1D685;1D685;1D685;0056;0056; # (ðš…; ðš…; ðš…; V; V; ) MATHEMATICAL MONOSPACE CAPITAL V
+1D686;1D686;1D686;0057;0057; # (ðš†; ðš†; ðš†; W; W; ) MATHEMATICAL MONOSPACE CAPITAL W
+1D687;1D687;1D687;0058;0058; # (ðš‡; ðš‡; ðš‡; X; X; ) MATHEMATICAL MONOSPACE CAPITAL X
+1D688;1D688;1D688;0059;0059; # (ðšˆ; ðšˆ; ðšˆ; Y; Y; ) MATHEMATICAL MONOSPACE CAPITAL Y
+1D689;1D689;1D689;005A;005A; # (ðš‰; ðš‰; ðš‰; Z; Z; ) MATHEMATICAL MONOSPACE CAPITAL Z
+1D68A;1D68A;1D68A;0061;0061; # (ðšŠ; ðšŠ; ðšŠ; a; a; ) MATHEMATICAL MONOSPACE SMALL A
+1D68B;1D68B;1D68B;0062;0062; # (ðš‹; ðš‹; ðš‹; b; b; ) MATHEMATICAL MONOSPACE SMALL B
+1D68C;1D68C;1D68C;0063;0063; # (ðšŒ; ðšŒ; ðšŒ; c; c; ) MATHEMATICAL MONOSPACE SMALL C
+1D68D;1D68D;1D68D;0064;0064; # (ðš; ðš; ðš; d; d; ) MATHEMATICAL MONOSPACE SMALL D
+1D68E;1D68E;1D68E;0065;0065; # (ðšŽ; ðšŽ; ðšŽ; e; e; ) MATHEMATICAL MONOSPACE SMALL E
+1D68F;1D68F;1D68F;0066;0066; # (ðš; ðš; ðš; f; f; ) MATHEMATICAL MONOSPACE SMALL F
+1D690;1D690;1D690;0067;0067; # (ðš; ðš; ðš; g; g; ) MATHEMATICAL MONOSPACE SMALL G
+1D691;1D691;1D691;0068;0068; # (ðš‘; ðš‘; ðš‘; h; h; ) MATHEMATICAL MONOSPACE SMALL H
+1D692;1D692;1D692;0069;0069; # (ðš’; ðš’; ðš’; i; i; ) MATHEMATICAL MONOSPACE SMALL I
+1D693;1D693;1D693;006A;006A; # (ðš“; ðš“; ðš“; j; j; ) MATHEMATICAL MONOSPACE SMALL J
+1D694;1D694;1D694;006B;006B; # (ðš”; ðš”; ðš”; k; k; ) MATHEMATICAL MONOSPACE SMALL K
+1D695;1D695;1D695;006C;006C; # (ðš•; ðš•; ðš•; l; l; ) MATHEMATICAL MONOSPACE SMALL L
+1D696;1D696;1D696;006D;006D; # (ðš–; ðš–; ðš–; m; m; ) MATHEMATICAL MONOSPACE SMALL M
+1D697;1D697;1D697;006E;006E; # (ðš—; ðš—; ðš—; n; n; ) MATHEMATICAL MONOSPACE SMALL N
+1D698;1D698;1D698;006F;006F; # (ðš˜; ðš˜; ðš˜; o; o; ) MATHEMATICAL MONOSPACE SMALL O
+1D699;1D699;1D699;0070;0070; # (ðš™; ðš™; ðš™; p; p; ) MATHEMATICAL MONOSPACE SMALL P
+1D69A;1D69A;1D69A;0071;0071; # (ðšš; ðšš; ðšš; q; q; ) MATHEMATICAL MONOSPACE SMALL Q
+1D69B;1D69B;1D69B;0072;0072; # (ðš›; ðš›; ðš›; r; r; ) MATHEMATICAL MONOSPACE SMALL R
+1D69C;1D69C;1D69C;0073;0073; # (ðšœ; ðšœ; ðšœ; s; s; ) MATHEMATICAL MONOSPACE SMALL S
+1D69D;1D69D;1D69D;0074;0074; # (ðš; ðš; ðš; t; t; ) MATHEMATICAL MONOSPACE SMALL T
+1D69E;1D69E;1D69E;0075;0075; # (ðšž; ðšž; ðšž; u; u; ) MATHEMATICAL MONOSPACE SMALL U
+1D69F;1D69F;1D69F;0076;0076; # (ðšŸ; ðšŸ; ðšŸ; v; v; ) MATHEMATICAL MONOSPACE SMALL V
+1D6A0;1D6A0;1D6A0;0077;0077; # (ðš ; ðš ; ðš ; w; w; ) MATHEMATICAL MONOSPACE SMALL W
+1D6A1;1D6A1;1D6A1;0078;0078; # (ðš¡; ðš¡; ðš¡; x; x; ) MATHEMATICAL MONOSPACE SMALL X
+1D6A2;1D6A2;1D6A2;0079;0079; # (ðš¢; ðš¢; ðš¢; y; y; ) MATHEMATICAL MONOSPACE SMALL Y
+1D6A3;1D6A3;1D6A3;007A;007A; # (ðš£; ðš£; ðš£; z; z; ) MATHEMATICAL MONOSPACE SMALL Z
+1D6A4;1D6A4;1D6A4;0131;0131; # (ðš¤; ðš¤; ðš¤; ı; ı; ) MATHEMATICAL ITALIC SMALL DOTLESS I
+1D6A5;1D6A5;1D6A5;0237;0237; # (ðš¥; ðš¥; ðš¥; È·; È·; ) MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6A8;1D6A8;1D6A8;0391;0391; # (ðš¨; ðš¨; ðš¨; Α; Α; ) MATHEMATICAL BOLD CAPITAL ALPHA
+1D6A9;1D6A9;1D6A9;0392;0392; # (ðš©; ðš©; ðš©; Î’; Î’; ) MATHEMATICAL BOLD CAPITAL BETA
+1D6AA;1D6AA;1D6AA;0393;0393; # (ðšª; ðšª; ðšª; Γ; Γ; ) MATHEMATICAL BOLD CAPITAL GAMMA
+1D6AB;1D6AB;1D6AB;0394;0394; # (ðš«; ðš«; ðš«; Δ; Δ; ) MATHEMATICAL BOLD CAPITAL DELTA
+1D6AC;1D6AC;1D6AC;0395;0395; # (ðš¬; ðš¬; ðš¬; Ε; Ε; ) MATHEMATICAL BOLD CAPITAL EPSILON
+1D6AD;1D6AD;1D6AD;0396;0396; # (ðš­; ðš­; ðš­; Ζ; Ζ; ) MATHEMATICAL BOLD CAPITAL ZETA
+1D6AE;1D6AE;1D6AE;0397;0397; # (ðš®; ðš®; ðš®; Η; Η; ) MATHEMATICAL BOLD CAPITAL ETA
+1D6AF;1D6AF;1D6AF;0398;0398; # (ðš¯; ðš¯; ðš¯; Θ; Θ; ) MATHEMATICAL BOLD CAPITAL THETA
+1D6B0;1D6B0;1D6B0;0399;0399; # (ðš°; ðš°; ðš°; Ι; Ι; ) MATHEMATICAL BOLD CAPITAL IOTA
+1D6B1;1D6B1;1D6B1;039A;039A; # (ðš±; ðš±; ðš±; Κ; Κ; ) MATHEMATICAL BOLD CAPITAL KAPPA
+1D6B2;1D6B2;1D6B2;039B;039B; # (ðš²; ðš²; ðš²; Λ; Λ; ) MATHEMATICAL BOLD CAPITAL LAMDA
+1D6B3;1D6B3;1D6B3;039C;039C; # (ðš³; ðš³; ðš³; Îœ; Îœ; ) MATHEMATICAL BOLD CAPITAL MU
+1D6B4;1D6B4;1D6B4;039D;039D; # (ðš´; ðš´; ðš´; Î; Î; ) MATHEMATICAL BOLD CAPITAL NU
+1D6B5;1D6B5;1D6B5;039E;039E; # (ðšµ; ðšµ; ðšµ; Ξ; Ξ; ) MATHEMATICAL BOLD CAPITAL XI
+1D6B6;1D6B6;1D6B6;039F;039F; # (ðš¶; ðš¶; ðš¶; Ο; Ο; ) MATHEMATICAL BOLD CAPITAL OMICRON
+1D6B7;1D6B7;1D6B7;03A0;03A0; # (ðš·; ðš·; ðš·; Π; Π; ) MATHEMATICAL BOLD CAPITAL PI
+1D6B8;1D6B8;1D6B8;03A1;03A1; # (ðš¸; ðš¸; ðš¸; Ρ; Ρ; ) MATHEMATICAL BOLD CAPITAL RHO
+1D6B9;1D6B9;1D6B9;0398;0398; # (ðš¹; ðš¹; ðš¹; Θ; Θ; ) MATHEMATICAL BOLD CAPITAL THETA SYMBOL
+1D6BA;1D6BA;1D6BA;03A3;03A3; # (ðšº; ðšº; ðšº; Σ; Σ; ) MATHEMATICAL BOLD CAPITAL SIGMA
+1D6BB;1D6BB;1D6BB;03A4;03A4; # (ðš»; ðš»; ðš»; Τ; Τ; ) MATHEMATICAL BOLD CAPITAL TAU
+1D6BC;1D6BC;1D6BC;03A5;03A5; # (ðš¼; ðš¼; ðš¼; Î¥; Î¥; ) MATHEMATICAL BOLD CAPITAL UPSILON
+1D6BD;1D6BD;1D6BD;03A6;03A6; # (ðš½; ðš½; ðš½; Φ; Φ; ) MATHEMATICAL BOLD CAPITAL PHI
+1D6BE;1D6BE;1D6BE;03A7;03A7; # (ðš¾; ðš¾; ðš¾; Χ; Χ; ) MATHEMATICAL BOLD CAPITAL CHI
+1D6BF;1D6BF;1D6BF;03A8;03A8; # (ðš¿; ðš¿; ðš¿; Ψ; Ψ; ) MATHEMATICAL BOLD CAPITAL PSI
+1D6C0;1D6C0;1D6C0;03A9;03A9; # (ð›€; ð›€; ð›€; Ω; Ω; ) MATHEMATICAL BOLD CAPITAL OMEGA
+1D6C1;1D6C1;1D6C1;2207;2207; # (ð›; ð›; ð›; ∇; ∇; ) MATHEMATICAL BOLD NABLA
+1D6C2;1D6C2;1D6C2;03B1;03B1; # (ð›‚; ð›‚; ð›‚; α; α; ) MATHEMATICAL BOLD SMALL ALPHA
+1D6C3;1D6C3;1D6C3;03B2;03B2; # (ð›ƒ; ð›ƒ; ð›ƒ; β; β; ) MATHEMATICAL BOLD SMALL BETA
+1D6C4;1D6C4;1D6C4;03B3;03B3; # (ð›„; ð›„; ð›„; γ; γ; ) MATHEMATICAL BOLD SMALL GAMMA
+1D6C5;1D6C5;1D6C5;03B4;03B4; # (ð›…; ð›…; ð›…; δ; δ; ) MATHEMATICAL BOLD SMALL DELTA
+1D6C6;1D6C6;1D6C6;03B5;03B5; # (ð›†; ð›†; ð›†; ε; ε; ) MATHEMATICAL BOLD SMALL EPSILON
+1D6C7;1D6C7;1D6C7;03B6;03B6; # (ð›‡; ð›‡; ð›‡; ζ; ζ; ) MATHEMATICAL BOLD SMALL ZETA
+1D6C8;1D6C8;1D6C8;03B7;03B7; # (ð›ˆ; ð›ˆ; ð›ˆ; η; η; ) MATHEMATICAL BOLD SMALL ETA
+1D6C9;1D6C9;1D6C9;03B8;03B8; # (ð›‰; ð›‰; ð›‰; θ; θ; ) MATHEMATICAL BOLD SMALL THETA
+1D6CA;1D6CA;1D6CA;03B9;03B9; # (ð›Š; ð›Š; ð›Š; ι; ι; ) MATHEMATICAL BOLD SMALL IOTA
+1D6CB;1D6CB;1D6CB;03BA;03BA; # (ð›‹; ð›‹; ð›‹; κ; κ; ) MATHEMATICAL BOLD SMALL KAPPA
+1D6CC;1D6CC;1D6CC;03BB;03BB; # (ð›Œ; ð›Œ; ð›Œ; λ; λ; ) MATHEMATICAL BOLD SMALL LAMDA
+1D6CD;1D6CD;1D6CD;03BC;03BC; # (ð›; ð›; ð›; μ; μ; ) MATHEMATICAL BOLD SMALL MU
+1D6CE;1D6CE;1D6CE;03BD;03BD; # (ð›Ž; ð›Ž; ð›Ž; ν; ν; ) MATHEMATICAL BOLD SMALL NU
+1D6CF;1D6CF;1D6CF;03BE;03BE; # (ð›; ð›; ð›; ξ; ξ; ) MATHEMATICAL BOLD SMALL XI
+1D6D0;1D6D0;1D6D0;03BF;03BF; # (ð›; ð›; ð›; ο; ο; ) MATHEMATICAL BOLD SMALL OMICRON
+1D6D1;1D6D1;1D6D1;03C0;03C0; # (ð›‘; ð›‘; ð›‘; Ï€; Ï€; ) MATHEMATICAL BOLD SMALL PI
+1D6D2;1D6D2;1D6D2;03C1;03C1; # (ð›’; ð›’; ð›’; Ï; Ï; ) MATHEMATICAL BOLD SMALL RHO
+1D6D3;1D6D3;1D6D3;03C2;03C2; # (ð›“; ð›“; ð›“; Ï‚; Ï‚; ) MATHEMATICAL BOLD SMALL FINAL SIGMA
+1D6D4;1D6D4;1D6D4;03C3;03C3; # (ð›”; ð›”; ð›”; σ; σ; ) MATHEMATICAL BOLD SMALL SIGMA
+1D6D5;1D6D5;1D6D5;03C4;03C4; # (ð›•; ð›•; ð›•; Ï„; Ï„; ) MATHEMATICAL BOLD SMALL TAU
+1D6D6;1D6D6;1D6D6;03C5;03C5; # (ð›–; ð›–; ð›–; Ï…; Ï…; ) MATHEMATICAL BOLD SMALL UPSILON
+1D6D7;1D6D7;1D6D7;03C6;03C6; # (ð›—; ð›—; ð›—; φ; φ; ) MATHEMATICAL BOLD SMALL PHI
+1D6D8;1D6D8;1D6D8;03C7;03C7; # (ð›˜; ð›˜; ð›˜; χ; χ; ) MATHEMATICAL BOLD SMALL CHI
+1D6D9;1D6D9;1D6D9;03C8;03C8; # (ð›™; ð›™; ð›™; ψ; ψ; ) MATHEMATICAL BOLD SMALL PSI
+1D6DA;1D6DA;1D6DA;03C9;03C9; # (ð›š; ð›š; ð›š; ω; ω; ) MATHEMATICAL BOLD SMALL OMEGA
+1D6DB;1D6DB;1D6DB;2202;2202; # (ð››; ð››; ð››; ∂; ∂; ) MATHEMATICAL BOLD PARTIAL DIFFERENTIAL
+1D6DC;1D6DC;1D6DC;03B5;03B5; # (ð›œ; ð›œ; ð›œ; ε; ε; ) MATHEMATICAL BOLD EPSILON SYMBOL
+1D6DD;1D6DD;1D6DD;03B8;03B8; # (ð›; ð›; ð›; θ; θ; ) MATHEMATICAL BOLD THETA SYMBOL
+1D6DE;1D6DE;1D6DE;03BA;03BA; # (ð›ž; ð›ž; ð›ž; κ; κ; ) MATHEMATICAL BOLD KAPPA SYMBOL
+1D6DF;1D6DF;1D6DF;03C6;03C6; # (ð›Ÿ; ð›Ÿ; ð›Ÿ; φ; φ; ) MATHEMATICAL BOLD PHI SYMBOL
+1D6E0;1D6E0;1D6E0;03C1;03C1; # (ð› ; ð› ; ð› ; Ï; Ï; ) MATHEMATICAL BOLD RHO SYMBOL
+1D6E1;1D6E1;1D6E1;03C0;03C0; # (ð›¡; ð›¡; ð›¡; Ï€; Ï€; ) MATHEMATICAL BOLD PI SYMBOL
+1D6E2;1D6E2;1D6E2;0391;0391; # (ð›¢; ð›¢; ð›¢; Α; Α; ) MATHEMATICAL ITALIC CAPITAL ALPHA
+1D6E3;1D6E3;1D6E3;0392;0392; # (ð›£; ð›£; ð›£; Î’; Î’; ) MATHEMATICAL ITALIC CAPITAL BETA
+1D6E4;1D6E4;1D6E4;0393;0393; # (ð›¤; ð›¤; ð›¤; Γ; Γ; ) MATHEMATICAL ITALIC CAPITAL GAMMA
+1D6E5;1D6E5;1D6E5;0394;0394; # (ð›¥; ð›¥; ð›¥; Δ; Δ; ) MATHEMATICAL ITALIC CAPITAL DELTA
+1D6E6;1D6E6;1D6E6;0395;0395; # (ð›¦; ð›¦; ð›¦; Ε; Ε; ) MATHEMATICAL ITALIC CAPITAL EPSILON
+1D6E7;1D6E7;1D6E7;0396;0396; # (ð›§; ð›§; ð›§; Ζ; Ζ; ) MATHEMATICAL ITALIC CAPITAL ZETA
+1D6E8;1D6E8;1D6E8;0397;0397; # (ð›¨; ð›¨; ð›¨; Η; Η; ) MATHEMATICAL ITALIC CAPITAL ETA
+1D6E9;1D6E9;1D6E9;0398;0398; # (ð›©; ð›©; ð›©; Θ; Θ; ) MATHEMATICAL ITALIC CAPITAL THETA
+1D6EA;1D6EA;1D6EA;0399;0399; # (ð›ª; ð›ª; ð›ª; Ι; Ι; ) MATHEMATICAL ITALIC CAPITAL IOTA
+1D6EB;1D6EB;1D6EB;039A;039A; # (ð›«; ð›«; ð›«; Κ; Κ; ) MATHEMATICAL ITALIC CAPITAL KAPPA
+1D6EC;1D6EC;1D6EC;039B;039B; # (ð›¬; ð›¬; ð›¬; Λ; Λ; ) MATHEMATICAL ITALIC CAPITAL LAMDA
+1D6ED;1D6ED;1D6ED;039C;039C; # (ð›­; ð›­; ð›­; Îœ; Îœ; ) MATHEMATICAL ITALIC CAPITAL MU
+1D6EE;1D6EE;1D6EE;039D;039D; # (ð›®; ð›®; ð›®; Î; Î; ) MATHEMATICAL ITALIC CAPITAL NU
+1D6EF;1D6EF;1D6EF;039E;039E; # (ð›¯; ð›¯; ð›¯; Ξ; Ξ; ) MATHEMATICAL ITALIC CAPITAL XI
+1D6F0;1D6F0;1D6F0;039F;039F; # (ð›°; ð›°; ð›°; Ο; Ο; ) MATHEMATICAL ITALIC CAPITAL OMICRON
+1D6F1;1D6F1;1D6F1;03A0;03A0; # (ð›±; ð›±; ð›±; Π; Π; ) MATHEMATICAL ITALIC CAPITAL PI
+1D6F2;1D6F2;1D6F2;03A1;03A1; # (ð›²; ð›²; ð›²; Ρ; Ρ; ) MATHEMATICAL ITALIC CAPITAL RHO
+1D6F3;1D6F3;1D6F3;0398;0398; # (ð›³; ð›³; ð›³; Θ; Θ; ) MATHEMATICAL ITALIC CAPITAL THETA SYMBOL
+1D6F4;1D6F4;1D6F4;03A3;03A3; # (ð›´; ð›´; ð›´; Σ; Σ; ) MATHEMATICAL ITALIC CAPITAL SIGMA
+1D6F5;1D6F5;1D6F5;03A4;03A4; # (ð›µ; ð›µ; ð›µ; Τ; Τ; ) MATHEMATICAL ITALIC CAPITAL TAU
+1D6F6;1D6F6;1D6F6;03A5;03A5; # (ð›¶; ð›¶; ð›¶; Î¥; Î¥; ) MATHEMATICAL ITALIC CAPITAL UPSILON
+1D6F7;1D6F7;1D6F7;03A6;03A6; # (ð›·; ð›·; ð›·; Φ; Φ; ) MATHEMATICAL ITALIC CAPITAL PHI
+1D6F8;1D6F8;1D6F8;03A7;03A7; # (ð›¸; ð›¸; ð›¸; Χ; Χ; ) MATHEMATICAL ITALIC CAPITAL CHI
+1D6F9;1D6F9;1D6F9;03A8;03A8; # (ð›¹; ð›¹; ð›¹; Ψ; Ψ; ) MATHEMATICAL ITALIC CAPITAL PSI
+1D6FA;1D6FA;1D6FA;03A9;03A9; # (ð›º; ð›º; ð›º; Ω; Ω; ) MATHEMATICAL ITALIC CAPITAL OMEGA
+1D6FB;1D6FB;1D6FB;2207;2207; # (ð›»; ð›»; ð›»; ∇; ∇; ) MATHEMATICAL ITALIC NABLA
+1D6FC;1D6FC;1D6FC;03B1;03B1; # (ð›¼; ð›¼; ð›¼; α; α; ) MATHEMATICAL ITALIC SMALL ALPHA
+1D6FD;1D6FD;1D6FD;03B2;03B2; # (ð›½; ð›½; ð›½; β; β; ) MATHEMATICAL ITALIC SMALL BETA
+1D6FE;1D6FE;1D6FE;03B3;03B3; # (ð›¾; ð›¾; ð›¾; γ; γ; ) MATHEMATICAL ITALIC SMALL GAMMA
+1D6FF;1D6FF;1D6FF;03B4;03B4; # (ð›¿; ð›¿; ð›¿; δ; δ; ) MATHEMATICAL ITALIC SMALL DELTA
+1D700;1D700;1D700;03B5;03B5; # (ðœ€; ðœ€; ðœ€; ε; ε; ) MATHEMATICAL ITALIC SMALL EPSILON
+1D701;1D701;1D701;03B6;03B6; # (ðœ; ðœ; ðœ; ζ; ζ; ) MATHEMATICAL ITALIC SMALL ZETA
+1D702;1D702;1D702;03B7;03B7; # (ðœ‚; ðœ‚; ðœ‚; η; η; ) MATHEMATICAL ITALIC SMALL ETA
+1D703;1D703;1D703;03B8;03B8; # (ðœƒ; ðœƒ; ðœƒ; θ; θ; ) MATHEMATICAL ITALIC SMALL THETA
+1D704;1D704;1D704;03B9;03B9; # (ðœ„; ðœ„; ðœ„; ι; ι; ) MATHEMATICAL ITALIC SMALL IOTA
+1D705;1D705;1D705;03BA;03BA; # (ðœ…; ðœ…; ðœ…; κ; κ; ) MATHEMATICAL ITALIC SMALL KAPPA
+1D706;1D706;1D706;03BB;03BB; # (ðœ†; ðœ†; ðœ†; λ; λ; ) MATHEMATICAL ITALIC SMALL LAMDA
+1D707;1D707;1D707;03BC;03BC; # (ðœ‡; ðœ‡; ðœ‡; μ; μ; ) MATHEMATICAL ITALIC SMALL MU
+1D708;1D708;1D708;03BD;03BD; # (ðœˆ; ðœˆ; ðœˆ; ν; ν; ) MATHEMATICAL ITALIC SMALL NU
+1D709;1D709;1D709;03BE;03BE; # (ðœ‰; ðœ‰; ðœ‰; ξ; ξ; ) MATHEMATICAL ITALIC SMALL XI
+1D70A;1D70A;1D70A;03BF;03BF; # (ðœŠ; ðœŠ; ðœŠ; ο; ο; ) MATHEMATICAL ITALIC SMALL OMICRON
+1D70B;1D70B;1D70B;03C0;03C0; # (ðœ‹; ðœ‹; ðœ‹; Ï€; Ï€; ) MATHEMATICAL ITALIC SMALL PI
+1D70C;1D70C;1D70C;03C1;03C1; # (ðœŒ; ðœŒ; ðœŒ; Ï; Ï; ) MATHEMATICAL ITALIC SMALL RHO
+1D70D;1D70D;1D70D;03C2;03C2; # (ðœ; ðœ; ðœ; Ï‚; Ï‚; ) MATHEMATICAL ITALIC SMALL FINAL SIGMA
+1D70E;1D70E;1D70E;03C3;03C3; # (ðœŽ; ðœŽ; ðœŽ; σ; σ; ) MATHEMATICAL ITALIC SMALL SIGMA
+1D70F;1D70F;1D70F;03C4;03C4; # (ðœ; ðœ; ðœ; Ï„; Ï„; ) MATHEMATICAL ITALIC SMALL TAU
+1D710;1D710;1D710;03C5;03C5; # (ðœ; ðœ; ðœ; Ï…; Ï…; ) MATHEMATICAL ITALIC SMALL UPSILON
+1D711;1D711;1D711;03C6;03C6; # (ðœ‘; ðœ‘; ðœ‘; φ; φ; ) MATHEMATICAL ITALIC SMALL PHI
+1D712;1D712;1D712;03C7;03C7; # (ðœ’; ðœ’; ðœ’; χ; χ; ) MATHEMATICAL ITALIC SMALL CHI
+1D713;1D713;1D713;03C8;03C8; # (ðœ“; ðœ“; ðœ“; ψ; ψ; ) MATHEMATICAL ITALIC SMALL PSI
+1D714;1D714;1D714;03C9;03C9; # (ðœ”; ðœ”; ðœ”; ω; ω; ) MATHEMATICAL ITALIC SMALL OMEGA
+1D715;1D715;1D715;2202;2202; # (ðœ•; ðœ•; ðœ•; ∂; ∂; ) MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL
+1D716;1D716;1D716;03B5;03B5; # (ðœ–; ðœ–; ðœ–; ε; ε; ) MATHEMATICAL ITALIC EPSILON SYMBOL
+1D717;1D717;1D717;03B8;03B8; # (ðœ—; ðœ—; ðœ—; θ; θ; ) MATHEMATICAL ITALIC THETA SYMBOL
+1D718;1D718;1D718;03BA;03BA; # (ðœ˜; ðœ˜; ðœ˜; κ; κ; ) MATHEMATICAL ITALIC KAPPA SYMBOL
+1D719;1D719;1D719;03C6;03C6; # (ðœ™; ðœ™; ðœ™; φ; φ; ) MATHEMATICAL ITALIC PHI SYMBOL
+1D71A;1D71A;1D71A;03C1;03C1; # (ðœš; ðœš; ðœš; Ï; Ï; ) MATHEMATICAL ITALIC RHO SYMBOL
+1D71B;1D71B;1D71B;03C0;03C0; # (ðœ›; ðœ›; ðœ›; Ï€; Ï€; ) MATHEMATICAL ITALIC PI SYMBOL
+1D71C;1D71C;1D71C;0391;0391; # (ðœœ; ðœœ; ðœœ; Α; Α; ) MATHEMATICAL BOLD ITALIC CAPITAL ALPHA
+1D71D;1D71D;1D71D;0392;0392; # (ðœ; ðœ; ðœ; Î’; Î’; ) MATHEMATICAL BOLD ITALIC CAPITAL BETA
+1D71E;1D71E;1D71E;0393;0393; # (ðœž; ðœž; ðœž; Γ; Γ; ) MATHEMATICAL BOLD ITALIC CAPITAL GAMMA
+1D71F;1D71F;1D71F;0394;0394; # (ðœŸ; ðœŸ; ðœŸ; Δ; Δ; ) MATHEMATICAL BOLD ITALIC CAPITAL DELTA
+1D720;1D720;1D720;0395;0395; # (ðœ ; ðœ ; ðœ ; Ε; Ε; ) MATHEMATICAL BOLD ITALIC CAPITAL EPSILON
+1D721;1D721;1D721;0396;0396; # (ðœ¡; ðœ¡; ðœ¡; Ζ; Ζ; ) MATHEMATICAL BOLD ITALIC CAPITAL ZETA
+1D722;1D722;1D722;0397;0397; # (ðœ¢; ðœ¢; ðœ¢; Η; Η; ) MATHEMATICAL BOLD ITALIC CAPITAL ETA
+1D723;1D723;1D723;0398;0398; # (ðœ£; ðœ£; ðœ£; Θ; Θ; ) MATHEMATICAL BOLD ITALIC CAPITAL THETA
+1D724;1D724;1D724;0399;0399; # (ðœ¤; ðœ¤; ðœ¤; Ι; Ι; ) MATHEMATICAL BOLD ITALIC CAPITAL IOTA
+1D725;1D725;1D725;039A;039A; # (ðœ¥; ðœ¥; ðœ¥; Κ; Κ; ) MATHEMATICAL BOLD ITALIC CAPITAL KAPPA
+1D726;1D726;1D726;039B;039B; # (ðœ¦; ðœ¦; ðœ¦; Λ; Λ; ) MATHEMATICAL BOLD ITALIC CAPITAL LAMDA
+1D727;1D727;1D727;039C;039C; # (ðœ§; ðœ§; ðœ§; Îœ; Îœ; ) MATHEMATICAL BOLD ITALIC CAPITAL MU
+1D728;1D728;1D728;039D;039D; # (ðœ¨; ðœ¨; ðœ¨; Î; Î; ) MATHEMATICAL BOLD ITALIC CAPITAL NU
+1D729;1D729;1D729;039E;039E; # (ðœ©; ðœ©; ðœ©; Ξ; Ξ; ) MATHEMATICAL BOLD ITALIC CAPITAL XI
+1D72A;1D72A;1D72A;039F;039F; # (ðœª; ðœª; ðœª; Ο; Ο; ) MATHEMATICAL BOLD ITALIC CAPITAL OMICRON
+1D72B;1D72B;1D72B;03A0;03A0; # (ðœ«; ðœ«; ðœ«; Π; Π; ) MATHEMATICAL BOLD ITALIC CAPITAL PI
+1D72C;1D72C;1D72C;03A1;03A1; # (ðœ¬; ðœ¬; ðœ¬; Ρ; Ρ; ) MATHEMATICAL BOLD ITALIC CAPITAL RHO
+1D72D;1D72D;1D72D;0398;0398; # (ðœ­; ðœ­; ðœ­; Θ; Θ; ) MATHEMATICAL BOLD ITALIC CAPITAL THETA SYMBOL
+1D72E;1D72E;1D72E;03A3;03A3; # (ðœ®; ðœ®; ðœ®; Σ; Σ; ) MATHEMATICAL BOLD ITALIC CAPITAL SIGMA
+1D72F;1D72F;1D72F;03A4;03A4; # (ðœ¯; ðœ¯; ðœ¯; Τ; Τ; ) MATHEMATICAL BOLD ITALIC CAPITAL TAU
+1D730;1D730;1D730;03A5;03A5; # (ðœ°; ðœ°; ðœ°; Î¥; Î¥; ) MATHEMATICAL BOLD ITALIC CAPITAL UPSILON
+1D731;1D731;1D731;03A6;03A6; # (ðœ±; ðœ±; ðœ±; Φ; Φ; ) MATHEMATICAL BOLD ITALIC CAPITAL PHI
+1D732;1D732;1D732;03A7;03A7; # (ðœ²; ðœ²; ðœ²; Χ; Χ; ) MATHEMATICAL BOLD ITALIC CAPITAL CHI
+1D733;1D733;1D733;03A8;03A8; # (ðœ³; ðœ³; ðœ³; Ψ; Ψ; ) MATHEMATICAL BOLD ITALIC CAPITAL PSI
+1D734;1D734;1D734;03A9;03A9; # (ðœ´; ðœ´; ðœ´; Ω; Ω; ) MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D735;1D735;1D735;2207;2207; # (ðœµ; ðœµ; ðœµ; ∇; ∇; ) MATHEMATICAL BOLD ITALIC NABLA
+1D736;1D736;1D736;03B1;03B1; # (ðœ¶; ðœ¶; ðœ¶; α; α; ) MATHEMATICAL BOLD ITALIC SMALL ALPHA
+1D737;1D737;1D737;03B2;03B2; # (ðœ·; ðœ·; ðœ·; β; β; ) MATHEMATICAL BOLD ITALIC SMALL BETA
+1D738;1D738;1D738;03B3;03B3; # (ðœ¸; ðœ¸; ðœ¸; γ; γ; ) MATHEMATICAL BOLD ITALIC SMALL GAMMA
+1D739;1D739;1D739;03B4;03B4; # (ðœ¹; ðœ¹; ðœ¹; δ; δ; ) MATHEMATICAL BOLD ITALIC SMALL DELTA
+1D73A;1D73A;1D73A;03B5;03B5; # (ðœº; ðœº; ðœº; ε; ε; ) MATHEMATICAL BOLD ITALIC SMALL EPSILON
+1D73B;1D73B;1D73B;03B6;03B6; # (ðœ»; ðœ»; ðœ»; ζ; ζ; ) MATHEMATICAL BOLD ITALIC SMALL ZETA
+1D73C;1D73C;1D73C;03B7;03B7; # (ðœ¼; ðœ¼; ðœ¼; η; η; ) MATHEMATICAL BOLD ITALIC SMALL ETA
+1D73D;1D73D;1D73D;03B8;03B8; # (ðœ½; ðœ½; ðœ½; θ; θ; ) MATHEMATICAL BOLD ITALIC SMALL THETA
+1D73E;1D73E;1D73E;03B9;03B9; # (ðœ¾; ðœ¾; ðœ¾; ι; ι; ) MATHEMATICAL BOLD ITALIC SMALL IOTA
+1D73F;1D73F;1D73F;03BA;03BA; # (ðœ¿; ðœ¿; ðœ¿; κ; κ; ) MATHEMATICAL BOLD ITALIC SMALL KAPPA
+1D740;1D740;1D740;03BB;03BB; # (ð€; ð€; ð€; λ; λ; ) MATHEMATICAL BOLD ITALIC SMALL LAMDA
+1D741;1D741;1D741;03BC;03BC; # (ð; ð; ð; μ; μ; ) MATHEMATICAL BOLD ITALIC SMALL MU
+1D742;1D742;1D742;03BD;03BD; # (ð‚; ð‚; ð‚; ν; ν; ) MATHEMATICAL BOLD ITALIC SMALL NU
+1D743;1D743;1D743;03BE;03BE; # (ðƒ; ðƒ; ðƒ; ξ; ξ; ) MATHEMATICAL BOLD ITALIC SMALL XI
+1D744;1D744;1D744;03BF;03BF; # (ð„; ð„; ð„; ο; ο; ) MATHEMATICAL BOLD ITALIC SMALL OMICRON
+1D745;1D745;1D745;03C0;03C0; # (ð…; ð…; ð…; Ï€; Ï€; ) MATHEMATICAL BOLD ITALIC SMALL PI
+1D746;1D746;1D746;03C1;03C1; # (ð†; ð†; ð†; Ï; Ï; ) MATHEMATICAL BOLD ITALIC SMALL RHO
+1D747;1D747;1D747;03C2;03C2; # (ð‡; ð‡; ð‡; Ï‚; Ï‚; ) MATHEMATICAL BOLD ITALIC SMALL FINAL SIGMA
+1D748;1D748;1D748;03C3;03C3; # (ðˆ; ðˆ; ðˆ; σ; σ; ) MATHEMATICAL BOLD ITALIC SMALL SIGMA
+1D749;1D749;1D749;03C4;03C4; # (ð‰; ð‰; ð‰; Ï„; Ï„; ) MATHEMATICAL BOLD ITALIC SMALL TAU
+1D74A;1D74A;1D74A;03C5;03C5; # (ðŠ; ðŠ; ðŠ; Ï…; Ï…; ) MATHEMATICAL BOLD ITALIC SMALL UPSILON
+1D74B;1D74B;1D74B;03C6;03C6; # (ð‹; ð‹; ð‹; φ; φ; ) MATHEMATICAL BOLD ITALIC SMALL PHI
+1D74C;1D74C;1D74C;03C7;03C7; # (ðŒ; ðŒ; ðŒ; χ; χ; ) MATHEMATICAL BOLD ITALIC SMALL CHI
+1D74D;1D74D;1D74D;03C8;03C8; # (ð; ð; ð; ψ; ψ; ) MATHEMATICAL BOLD ITALIC SMALL PSI
+1D74E;1D74E;1D74E;03C9;03C9; # (ðŽ; ðŽ; ðŽ; ω; ω; ) MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D74F;1D74F;1D74F;2202;2202; # (ð; ð; ð; ∂; ∂; ) MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL
+1D750;1D750;1D750;03B5;03B5; # (ð; ð; ð; ε; ε; ) MATHEMATICAL BOLD ITALIC EPSILON SYMBOL
+1D751;1D751;1D751;03B8;03B8; # (ð‘; ð‘; ð‘; θ; θ; ) MATHEMATICAL BOLD ITALIC THETA SYMBOL
+1D752;1D752;1D752;03BA;03BA; # (ð’; ð’; ð’; κ; κ; ) MATHEMATICAL BOLD ITALIC KAPPA SYMBOL
+1D753;1D753;1D753;03C6;03C6; # (ð“; ð“; ð“; φ; φ; ) MATHEMATICAL BOLD ITALIC PHI SYMBOL
+1D754;1D754;1D754;03C1;03C1; # (ð”; ð”; ð”; Ï; Ï; ) MATHEMATICAL BOLD ITALIC RHO SYMBOL
+1D755;1D755;1D755;03C0;03C0; # (ð•; ð•; ð•; Ï€; Ï€; ) MATHEMATICAL BOLD ITALIC PI SYMBOL
+1D756;1D756;1D756;0391;0391; # (ð–; ð–; ð–; Α; Α; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL ALPHA
+1D757;1D757;1D757;0392;0392; # (ð—; ð—; ð—; Î’; Î’; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL BETA
+1D758;1D758;1D758;0393;0393; # (ð˜; ð˜; ð˜; Γ; Γ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL GAMMA
+1D759;1D759;1D759;0394;0394; # (ð™; ð™; ð™; Δ; Δ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL DELTA
+1D75A;1D75A;1D75A;0395;0395; # (ðš; ðš; ðš; Ε; Ε; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL EPSILON
+1D75B;1D75B;1D75B;0396;0396; # (ð›; ð›; ð›; Ζ; Ζ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL ZETA
+1D75C;1D75C;1D75C;0397;0397; # (ðœ; ðœ; ðœ; Η; Η; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL ETA
+1D75D;1D75D;1D75D;0398;0398; # (ð; ð; ð; Θ; Θ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL THETA
+1D75E;1D75E;1D75E;0399;0399; # (ðž; ðž; ðž; Ι; Ι; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL IOTA
+1D75F;1D75F;1D75F;039A;039A; # (ðŸ; ðŸ; ðŸ; Κ; Κ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL KAPPA
+1D760;1D760;1D760;039B;039B; # (ð ; ð ; ð ; Λ; Λ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL LAMDA
+1D761;1D761;1D761;039C;039C; # (ð¡; ð¡; ð¡; Îœ; Îœ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL MU
+1D762;1D762;1D762;039D;039D; # (ð¢; ð¢; ð¢; Î; Î; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL NU
+1D763;1D763;1D763;039E;039E; # (ð£; ð£; ð£; Ξ; Ξ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL XI
+1D764;1D764;1D764;039F;039F; # (ð¤; ð¤; ð¤; Ο; Ο; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL OMICRON
+1D765;1D765;1D765;03A0;03A0; # (ð¥; ð¥; ð¥; Π; Π; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL PI
+1D766;1D766;1D766;03A1;03A1; # (ð¦; ð¦; ð¦; Ρ; Ρ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL RHO
+1D767;1D767;1D767;0398;0398; # (ð§; ð§; ð§; Θ; Θ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL THETA SYMBOL
+1D768;1D768;1D768;03A3;03A3; # (ð¨; ð¨; ð¨; Σ; Σ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL SIGMA
+1D769;1D769;1D769;03A4;03A4; # (ð©; ð©; ð©; Τ; Τ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL TAU
+1D76A;1D76A;1D76A;03A5;03A5; # (ðª; ðª; ðª; Î¥; Î¥; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL UPSILON
+1D76B;1D76B;1D76B;03A6;03A6; # (ð«; ð«; ð«; Φ; Φ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL PHI
+1D76C;1D76C;1D76C;03A7;03A7; # (ð¬; ð¬; ð¬; Χ; Χ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL CHI
+1D76D;1D76D;1D76D;03A8;03A8; # (ð­; ð­; ð­; Ψ; Ψ; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL PSI
+1D76E;1D76E;1D76E;03A9;03A9; # (ð®; ð®; ð®; Ω; Ω; ) MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D76F;1D76F;1D76F;2207;2207; # (ð¯; ð¯; ð¯; ∇; ∇; ) MATHEMATICAL SANS-SERIF BOLD NABLA
+1D770;1D770;1D770;03B1;03B1; # (ð°; ð°; ð°; α; α; ) MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA
+1D771;1D771;1D771;03B2;03B2; # (ð±; ð±; ð±; β; β; ) MATHEMATICAL SANS-SERIF BOLD SMALL BETA
+1D772;1D772;1D772;03B3;03B3; # (ð²; ð²; ð²; γ; γ; ) MATHEMATICAL SANS-SERIF BOLD SMALL GAMMA
+1D773;1D773;1D773;03B4;03B4; # (ð³; ð³; ð³; δ; δ; ) MATHEMATICAL SANS-SERIF BOLD SMALL DELTA
+1D774;1D774;1D774;03B5;03B5; # (ð´; ð´; ð´; ε; ε; ) MATHEMATICAL SANS-SERIF BOLD SMALL EPSILON
+1D775;1D775;1D775;03B6;03B6; # (ðµ; ðµ; ðµ; ζ; ζ; ) MATHEMATICAL SANS-SERIF BOLD SMALL ZETA
+1D776;1D776;1D776;03B7;03B7; # (ð¶; ð¶; ð¶; η; η; ) MATHEMATICAL SANS-SERIF BOLD SMALL ETA
+1D777;1D777;1D777;03B8;03B8; # (ð·; ð·; ð·; θ; θ; ) MATHEMATICAL SANS-SERIF BOLD SMALL THETA
+1D778;1D778;1D778;03B9;03B9; # (ð¸; ð¸; ð¸; ι; ι; ) MATHEMATICAL SANS-SERIF BOLD SMALL IOTA
+1D779;1D779;1D779;03BA;03BA; # (ð¹; ð¹; ð¹; κ; κ; ) MATHEMATICAL SANS-SERIF BOLD SMALL KAPPA
+1D77A;1D77A;1D77A;03BB;03BB; # (ðº; ðº; ðº; λ; λ; ) MATHEMATICAL SANS-SERIF BOLD SMALL LAMDA
+1D77B;1D77B;1D77B;03BC;03BC; # (ð»; ð»; ð»; μ; μ; ) MATHEMATICAL SANS-SERIF BOLD SMALL MU
+1D77C;1D77C;1D77C;03BD;03BD; # (ð¼; ð¼; ð¼; ν; ν; ) MATHEMATICAL SANS-SERIF BOLD SMALL NU
+1D77D;1D77D;1D77D;03BE;03BE; # (ð½; ð½; ð½; ξ; ξ; ) MATHEMATICAL SANS-SERIF BOLD SMALL XI
+1D77E;1D77E;1D77E;03BF;03BF; # (ð¾; ð¾; ð¾; ο; ο; ) MATHEMATICAL SANS-SERIF BOLD SMALL OMICRON
+1D77F;1D77F;1D77F;03C0;03C0; # (ð¿; ð¿; ð¿; Ï€; Ï€; ) MATHEMATICAL SANS-SERIF BOLD SMALL PI
+1D780;1D780;1D780;03C1;03C1; # (ðž€; ðž€; ðž€; Ï; Ï; ) MATHEMATICAL SANS-SERIF BOLD SMALL RHO
+1D781;1D781;1D781;03C2;03C2; # (ðž; ðž; ðž; Ï‚; Ï‚; ) MATHEMATICAL SANS-SERIF BOLD SMALL FINAL SIGMA
+1D782;1D782;1D782;03C3;03C3; # (ðž‚; ðž‚; ðž‚; σ; σ; ) MATHEMATICAL SANS-SERIF BOLD SMALL SIGMA
+1D783;1D783;1D783;03C4;03C4; # (ðžƒ; ðžƒ; ðžƒ; Ï„; Ï„; ) MATHEMATICAL SANS-SERIF BOLD SMALL TAU
+1D784;1D784;1D784;03C5;03C5; # (ðž„; ðž„; ðž„; Ï…; Ï…; ) MATHEMATICAL SANS-SERIF BOLD SMALL UPSILON
+1D785;1D785;1D785;03C6;03C6; # (ðž…; ðž…; ðž…; φ; φ; ) MATHEMATICAL SANS-SERIF BOLD SMALL PHI
+1D786;1D786;1D786;03C7;03C7; # (ðž†; ðž†; ðž†; χ; χ; ) MATHEMATICAL SANS-SERIF BOLD SMALL CHI
+1D787;1D787;1D787;03C8;03C8; # (ðž‡; ðž‡; ðž‡; ψ; ψ; ) MATHEMATICAL SANS-SERIF BOLD SMALL PSI
+1D788;1D788;1D788;03C9;03C9; # (ðžˆ; ðžˆ; ðžˆ; ω; ω; ) MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D789;1D789;1D789;2202;2202; # (ðž‰; ðž‰; ðž‰; ∂; ∂; ) MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL
+1D78A;1D78A;1D78A;03B5;03B5; # (ðžŠ; ðžŠ; ðžŠ; ε; ε; ) MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL
+1D78B;1D78B;1D78B;03B8;03B8; # (ðž‹; ðž‹; ðž‹; θ; θ; ) MATHEMATICAL SANS-SERIF BOLD THETA SYMBOL
+1D78C;1D78C;1D78C;03BA;03BA; # (ðžŒ; ðžŒ; ðžŒ; κ; κ; ) MATHEMATICAL SANS-SERIF BOLD KAPPA SYMBOL
+1D78D;1D78D;1D78D;03C6;03C6; # (ðž; ðž; ðž; φ; φ; ) MATHEMATICAL SANS-SERIF BOLD PHI SYMBOL
+1D78E;1D78E;1D78E;03C1;03C1; # (ðžŽ; ðžŽ; ðžŽ; Ï; Ï; ) MATHEMATICAL SANS-SERIF BOLD RHO SYMBOL
+1D78F;1D78F;1D78F;03C0;03C0; # (ðž; ðž; ðž; Ï€; Ï€; ) MATHEMATICAL SANS-SERIF BOLD PI SYMBOL
+1D790;1D790;1D790;0391;0391; # (ðž; ðž; ðž; Α; Α; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ALPHA
+1D791;1D791;1D791;0392;0392; # (ðž‘; ðž‘; ðž‘; Î’; Î’; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL BETA
+1D792;1D792;1D792;0393;0393; # (ðž’; ðž’; ðž’; Γ; Γ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL GAMMA
+1D793;1D793;1D793;0394;0394; # (ðž“; ðž“; ðž“; Δ; Δ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL DELTA
+1D794;1D794;1D794;0395;0395; # (ðž”; ðž”; ðž”; Ε; Ε; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL EPSILON
+1D795;1D795;1D795;0396;0396; # (ðž•; ðž•; ðž•; Ζ; Ζ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ZETA
+1D796;1D796;1D796;0397;0397; # (ðž–; ðž–; ðž–; Η; Η; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ETA
+1D797;1D797;1D797;0398;0398; # (ðž—; ðž—; ðž—; Θ; Θ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL THETA
+1D798;1D798;1D798;0399;0399; # (ðž˜; ðž˜; ðž˜; Ι; Ι; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL IOTA
+1D799;1D799;1D799;039A;039A; # (ðž™; ðž™; ðž™; Κ; Κ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL KAPPA
+1D79A;1D79A;1D79A;039B;039B; # (ðžš; ðžš; ðžš; Λ; Λ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL LAMDA
+1D79B;1D79B;1D79B;039C;039C; # (ðž›; ðž›; ðž›; Îœ; Îœ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL MU
+1D79C;1D79C;1D79C;039D;039D; # (ðžœ; ðžœ; ðžœ; Î; Î; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL NU
+1D79D;1D79D;1D79D;039E;039E; # (ðž; ðž; ðž; Ξ; Ξ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL XI
+1D79E;1D79E;1D79E;039F;039F; # (ðžž; ðžž; ðžž; Ο; Ο; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMICRON
+1D79F;1D79F;1D79F;03A0;03A0; # (ðžŸ; ðžŸ; ðžŸ; Π; Π; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PI
+1D7A0;1D7A0;1D7A0;03A1;03A1; # (ðž ; ðž ; ðž ; Ρ; Ρ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL RHO
+1D7A1;1D7A1;1D7A1;0398;0398; # (ðž¡; ðž¡; ðž¡; Θ; Θ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL THETA SYMBOL
+1D7A2;1D7A2;1D7A2;03A3;03A3; # (ðž¢; ðž¢; ðž¢; Σ; Σ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL SIGMA
+1D7A3;1D7A3;1D7A3;03A4;03A4; # (ðž£; ðž£; ðž£; Τ; Τ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL TAU
+1D7A4;1D7A4;1D7A4;03A5;03A5; # (ðž¤; ðž¤; ðž¤; Î¥; Î¥; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL UPSILON
+1D7A5;1D7A5;1D7A5;03A6;03A6; # (ðž¥; ðž¥; ðž¥; Φ; Φ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PHI
+1D7A6;1D7A6;1D7A6;03A7;03A7; # (ðž¦; ðž¦; ðž¦; Χ; Χ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL CHI
+1D7A7;1D7A7;1D7A7;03A8;03A8; # (ðž§; ðž§; ðž§; Ψ; Ψ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PSI
+1D7A8;1D7A8;1D7A8;03A9;03A9; # (ðž¨; ðž¨; ðž¨; Ω; Ω; ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7A9;1D7A9;1D7A9;2207;2207; # (ðž©; ðž©; ðž©; ∇; ∇; ) MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA
+1D7AA;1D7AA;1D7AA;03B1;03B1; # (ðžª; ðžª; ðžª; α; α; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA
+1D7AB;1D7AB;1D7AB;03B2;03B2; # (ðž«; ðž«; ðž«; β; β; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL BETA
+1D7AC;1D7AC;1D7AC;03B3;03B3; # (ðž¬; ðž¬; ðž¬; γ; γ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL GAMMA
+1D7AD;1D7AD;1D7AD;03B4;03B4; # (ðž­; ðž­; ðž­; δ; δ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL DELTA
+1D7AE;1D7AE;1D7AE;03B5;03B5; # (ðž®; ðž®; ðž®; ε; ε; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL EPSILON
+1D7AF;1D7AF;1D7AF;03B6;03B6; # (ðž¯; ðž¯; ðž¯; ζ; ζ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ZETA
+1D7B0;1D7B0;1D7B0;03B7;03B7; # (ðž°; ðž°; ðž°; η; η; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ETA
+1D7B1;1D7B1;1D7B1;03B8;03B8; # (ðž±; ðž±; ðž±; θ; θ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL THETA
+1D7B2;1D7B2;1D7B2;03B9;03B9; # (ðž²; ðž²; ðž²; ι; ι; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL IOTA
+1D7B3;1D7B3;1D7B3;03BA;03BA; # (ðž³; ðž³; ðž³; κ; κ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL KAPPA
+1D7B4;1D7B4;1D7B4;03BB;03BB; # (ðž´; ðž´; ðž´; λ; λ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL LAMDA
+1D7B5;1D7B5;1D7B5;03BC;03BC; # (ðžµ; ðžµ; ðžµ; μ; μ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL MU
+1D7B6;1D7B6;1D7B6;03BD;03BD; # (ðž¶; ðž¶; ðž¶; ν; ν; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL NU
+1D7B7;1D7B7;1D7B7;03BE;03BE; # (ðž·; ðž·; ðž·; ξ; ξ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL XI
+1D7B8;1D7B8;1D7B8;03BF;03BF; # (ðž¸; ðž¸; ðž¸; ο; ο; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMICRON
+1D7B9;1D7B9;1D7B9;03C0;03C0; # (ðž¹; ðž¹; ðž¹; Ï€; Ï€; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PI
+1D7BA;1D7BA;1D7BA;03C1;03C1; # (ðžº; ðžº; ðžº; Ï; Ï; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL RHO
+1D7BB;1D7BB;1D7BB;03C2;03C2; # (ðž»; ðž»; ðž»; Ï‚; Ï‚; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL FINAL SIGMA
+1D7BC;1D7BC;1D7BC;03C3;03C3; # (ðž¼; ðž¼; ðž¼; σ; σ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL SIGMA
+1D7BD;1D7BD;1D7BD;03C4;03C4; # (ðž½; ðž½; ðž½; Ï„; Ï„; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL TAU
+1D7BE;1D7BE;1D7BE;03C5;03C5; # (ðž¾; ðž¾; ðž¾; Ï…; Ï…; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL UPSILON
+1D7BF;1D7BF;1D7BF;03C6;03C6; # (ðž¿; ðž¿; ðž¿; φ; φ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PHI
+1D7C0;1D7C0;1D7C0;03C7;03C7; # (ðŸ€; ðŸ€; ðŸ€; χ; χ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL CHI
+1D7C1;1D7C1;1D7C1;03C8;03C8; # (ðŸ; ðŸ; ðŸ; ψ; ψ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PSI
+1D7C2;1D7C2;1D7C2;03C9;03C9; # (ðŸ‚; ðŸ‚; ðŸ‚; ω; ω; ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C3;1D7C3;1D7C3;2202;2202; # (ðŸƒ; ðŸƒ; ðŸƒ; ∂; ∂; ) MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL
+1D7C4;1D7C4;1D7C4;03B5;03B5; # (ðŸ„; ðŸ„; ðŸ„; ε; ε; ) MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL
+1D7C5;1D7C5;1D7C5;03B8;03B8; # (ðŸ…; ðŸ…; ðŸ…; θ; θ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC THETA SYMBOL
+1D7C6;1D7C6;1D7C6;03BA;03BA; # (ðŸ†; ðŸ†; ðŸ†; κ; κ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC KAPPA SYMBOL
+1D7C7;1D7C7;1D7C7;03C6;03C6; # (ðŸ‡; ðŸ‡; ðŸ‡; φ; φ; ) MATHEMATICAL SANS-SERIF BOLD ITALIC PHI SYMBOL
+1D7C8;1D7C8;1D7C8;03C1;03C1; # (ðŸˆ; ðŸˆ; ðŸˆ; Ï; Ï; ) MATHEMATICAL SANS-SERIF BOLD ITALIC RHO SYMBOL
+1D7C9;1D7C9;1D7C9;03C0;03C0; # (ðŸ‰; ðŸ‰; ðŸ‰; Ï€; Ï€; ) MATHEMATICAL SANS-SERIF BOLD ITALIC PI SYMBOL
+1D7CA;1D7CA;1D7CA;03DC;03DC; # (ðŸŠ; ðŸŠ; ðŸŠ; Ïœ; Ïœ; ) MATHEMATICAL BOLD CAPITAL DIGAMMA
+1D7CB;1D7CB;1D7CB;03DD;03DD; # (ðŸ‹; ðŸ‹; ðŸ‹; Ï; Ï; ) MATHEMATICAL BOLD SMALL DIGAMMA
+1D7CE;1D7CE;1D7CE;0030;0030; # (ðŸŽ; ðŸŽ; ðŸŽ; 0; 0; ) MATHEMATICAL BOLD DIGIT ZERO
+1D7CF;1D7CF;1D7CF;0031;0031; # (ðŸ; ðŸ; ðŸ; 1; 1; ) MATHEMATICAL BOLD DIGIT ONE
+1D7D0;1D7D0;1D7D0;0032;0032; # (ðŸ; ðŸ; ðŸ; 2; 2; ) MATHEMATICAL BOLD DIGIT TWO
+1D7D1;1D7D1;1D7D1;0033;0033; # (ðŸ‘; ðŸ‘; ðŸ‘; 3; 3; ) MATHEMATICAL BOLD DIGIT THREE
+1D7D2;1D7D2;1D7D2;0034;0034; # (ðŸ’; ðŸ’; ðŸ’; 4; 4; ) MATHEMATICAL BOLD DIGIT FOUR
+1D7D3;1D7D3;1D7D3;0035;0035; # (ðŸ“; ðŸ“; ðŸ“; 5; 5; ) MATHEMATICAL BOLD DIGIT FIVE
+1D7D4;1D7D4;1D7D4;0036;0036; # (ðŸ”; ðŸ”; ðŸ”; 6; 6; ) MATHEMATICAL BOLD DIGIT SIX
+1D7D5;1D7D5;1D7D5;0037;0037; # (ðŸ•; ðŸ•; ðŸ•; 7; 7; ) MATHEMATICAL BOLD DIGIT SEVEN
+1D7D6;1D7D6;1D7D6;0038;0038; # (ðŸ–; ðŸ–; ðŸ–; 8; 8; ) MATHEMATICAL BOLD DIGIT EIGHT
+1D7D7;1D7D7;1D7D7;0039;0039; # (ðŸ—; ðŸ—; ðŸ—; 9; 9; ) MATHEMATICAL BOLD DIGIT NINE
+1D7D8;1D7D8;1D7D8;0030;0030; # (ðŸ˜; ðŸ˜; ðŸ˜; 0; 0; ) MATHEMATICAL DOUBLE-STRUCK DIGIT ZERO
+1D7D9;1D7D9;1D7D9;0031;0031; # (ðŸ™; ðŸ™; ðŸ™; 1; 1; ) MATHEMATICAL DOUBLE-STRUCK DIGIT ONE
+1D7DA;1D7DA;1D7DA;0032;0032; # (ðŸš; ðŸš; ðŸš; 2; 2; ) MATHEMATICAL DOUBLE-STRUCK DIGIT TWO
+1D7DB;1D7DB;1D7DB;0033;0033; # (ðŸ›; ðŸ›; ðŸ›; 3; 3; ) MATHEMATICAL DOUBLE-STRUCK DIGIT THREE
+1D7DC;1D7DC;1D7DC;0034;0034; # (ðŸœ; ðŸœ; ðŸœ; 4; 4; ) MATHEMATICAL DOUBLE-STRUCK DIGIT FOUR
+1D7DD;1D7DD;1D7DD;0035;0035; # (ðŸ; ðŸ; ðŸ; 5; 5; ) MATHEMATICAL DOUBLE-STRUCK DIGIT FIVE
+1D7DE;1D7DE;1D7DE;0036;0036; # (ðŸž; ðŸž; ðŸž; 6; 6; ) MATHEMATICAL DOUBLE-STRUCK DIGIT SIX
+1D7DF;1D7DF;1D7DF;0037;0037; # (ðŸŸ; ðŸŸ; ðŸŸ; 7; 7; ) MATHEMATICAL DOUBLE-STRUCK DIGIT SEVEN
+1D7E0;1D7E0;1D7E0;0038;0038; # (ðŸ ; ðŸ ; ðŸ ; 8; 8; ) MATHEMATICAL DOUBLE-STRUCK DIGIT EIGHT
+1D7E1;1D7E1;1D7E1;0039;0039; # (ðŸ¡; ðŸ¡; ðŸ¡; 9; 9; ) MATHEMATICAL DOUBLE-STRUCK DIGIT NINE
+1D7E2;1D7E2;1D7E2;0030;0030; # (ðŸ¢; ðŸ¢; ðŸ¢; 0; 0; ) MATHEMATICAL SANS-SERIF DIGIT ZERO
+1D7E3;1D7E3;1D7E3;0031;0031; # (ðŸ£; ðŸ£; ðŸ£; 1; 1; ) MATHEMATICAL SANS-SERIF DIGIT ONE
+1D7E4;1D7E4;1D7E4;0032;0032; # (ðŸ¤; ðŸ¤; ðŸ¤; 2; 2; ) MATHEMATICAL SANS-SERIF DIGIT TWO
+1D7E5;1D7E5;1D7E5;0033;0033; # (ðŸ¥; ðŸ¥; ðŸ¥; 3; 3; ) MATHEMATICAL SANS-SERIF DIGIT THREE
+1D7E6;1D7E6;1D7E6;0034;0034; # (ðŸ¦; ðŸ¦; ðŸ¦; 4; 4; ) MATHEMATICAL SANS-SERIF DIGIT FOUR
+1D7E7;1D7E7;1D7E7;0035;0035; # (ðŸ§; ðŸ§; ðŸ§; 5; 5; ) MATHEMATICAL SANS-SERIF DIGIT FIVE
+1D7E8;1D7E8;1D7E8;0036;0036; # (ðŸ¨; ðŸ¨; ðŸ¨; 6; 6; ) MATHEMATICAL SANS-SERIF DIGIT SIX
+1D7E9;1D7E9;1D7E9;0037;0037; # (ðŸ©; ðŸ©; ðŸ©; 7; 7; ) MATHEMATICAL SANS-SERIF DIGIT SEVEN
+1D7EA;1D7EA;1D7EA;0038;0038; # (ðŸª; ðŸª; ðŸª; 8; 8; ) MATHEMATICAL SANS-SERIF DIGIT EIGHT
+1D7EB;1D7EB;1D7EB;0039;0039; # (ðŸ«; ðŸ«; ðŸ«; 9; 9; ) MATHEMATICAL SANS-SERIF DIGIT NINE
+1D7EC;1D7EC;1D7EC;0030;0030; # (ðŸ¬; ðŸ¬; ðŸ¬; 0; 0; ) MATHEMATICAL SANS-SERIF BOLD DIGIT ZERO
+1D7ED;1D7ED;1D7ED;0031;0031; # (ðŸ­; ðŸ­; ðŸ­; 1; 1; ) MATHEMATICAL SANS-SERIF BOLD DIGIT ONE
+1D7EE;1D7EE;1D7EE;0032;0032; # (ðŸ®; ðŸ®; ðŸ®; 2; 2; ) MATHEMATICAL SANS-SERIF BOLD DIGIT TWO
+1D7EF;1D7EF;1D7EF;0033;0033; # (ðŸ¯; ðŸ¯; ðŸ¯; 3; 3; ) MATHEMATICAL SANS-SERIF BOLD DIGIT THREE
+1D7F0;1D7F0;1D7F0;0034;0034; # (ðŸ°; ðŸ°; ðŸ°; 4; 4; ) MATHEMATICAL SANS-SERIF BOLD DIGIT FOUR
+1D7F1;1D7F1;1D7F1;0035;0035; # (ðŸ±; ðŸ±; ðŸ±; 5; 5; ) MATHEMATICAL SANS-SERIF BOLD DIGIT FIVE
+1D7F2;1D7F2;1D7F2;0036;0036; # (ðŸ²; ðŸ²; ðŸ²; 6; 6; ) MATHEMATICAL SANS-SERIF BOLD DIGIT SIX
+1D7F3;1D7F3;1D7F3;0037;0037; # (ðŸ³; ðŸ³; ðŸ³; 7; 7; ) MATHEMATICAL SANS-SERIF BOLD DIGIT SEVEN
+1D7F4;1D7F4;1D7F4;0038;0038; # (ðŸ´; ðŸ´; ðŸ´; 8; 8; ) MATHEMATICAL SANS-SERIF BOLD DIGIT EIGHT
+1D7F5;1D7F5;1D7F5;0039;0039; # (ðŸµ; ðŸµ; ðŸµ; 9; 9; ) MATHEMATICAL SANS-SERIF BOLD DIGIT NINE
+1D7F6;1D7F6;1D7F6;0030;0030; # (ðŸ¶; ðŸ¶; ðŸ¶; 0; 0; ) MATHEMATICAL MONOSPACE DIGIT ZERO
+1D7F7;1D7F7;1D7F7;0031;0031; # (ðŸ·; ðŸ·; ðŸ·; 1; 1; ) MATHEMATICAL MONOSPACE DIGIT ONE
+1D7F8;1D7F8;1D7F8;0032;0032; # (ðŸ¸; ðŸ¸; ðŸ¸; 2; 2; ) MATHEMATICAL MONOSPACE DIGIT TWO
+1D7F9;1D7F9;1D7F9;0033;0033; # (ðŸ¹; ðŸ¹; ðŸ¹; 3; 3; ) MATHEMATICAL MONOSPACE DIGIT THREE
+1D7FA;1D7FA;1D7FA;0034;0034; # (ðŸº; ðŸº; ðŸº; 4; 4; ) MATHEMATICAL MONOSPACE DIGIT FOUR
+1D7FB;1D7FB;1D7FB;0035;0035; # (ðŸ»; ðŸ»; ðŸ»; 5; 5; ) MATHEMATICAL MONOSPACE DIGIT FIVE
+1D7FC;1D7FC;1D7FC;0036;0036; # (ðŸ¼; ðŸ¼; ðŸ¼; 6; 6; ) MATHEMATICAL MONOSPACE DIGIT SIX
+1D7FD;1D7FD;1D7FD;0037;0037; # (ðŸ½; ðŸ½; ðŸ½; 7; 7; ) MATHEMATICAL MONOSPACE DIGIT SEVEN
+1D7FE;1D7FE;1D7FE;0038;0038; # (ðŸ¾; ðŸ¾; ðŸ¾; 8; 8; ) MATHEMATICAL MONOSPACE DIGIT EIGHT
+1D7FF;1D7FF;1D7FF;0039;0039; # (ðŸ¿; ðŸ¿; ðŸ¿; 9; 9; ) MATHEMATICAL MONOSPACE DIGIT NINE
+1EE00;1EE00;1EE00;0627;0627; # (𞸀; 𞸀; 𞸀; ا; ا; ) ARABIC MATHEMATICAL ALEF
+1EE01;1EE01;1EE01;0628;0628; # (ðž¸; ðž¸; ðž¸; ب; ب; ) ARABIC MATHEMATICAL BEH
+1EE02;1EE02;1EE02;062C;062C; # (𞸂; 𞸂; 𞸂; ج; ج; ) ARABIC MATHEMATICAL JEEM
+1EE03;1EE03;1EE03;062F;062F; # (𞸃; 𞸃; 𞸃; د; د; ) ARABIC MATHEMATICAL DAL
+1EE05;1EE05;1EE05;0648;0648; # (𞸅; 𞸅; 𞸅; و; و; ) ARABIC MATHEMATICAL WAW
+1EE06;1EE06;1EE06;0632;0632; # (𞸆; 𞸆; 𞸆; ز; ز; ) ARABIC MATHEMATICAL ZAIN
+1EE07;1EE07;1EE07;062D;062D; # (𞸇; 𞸇; 𞸇; ح; ح; ) ARABIC MATHEMATICAL HAH
+1EE08;1EE08;1EE08;0637;0637; # (𞸈; 𞸈; 𞸈; ط; ط; ) ARABIC MATHEMATICAL TAH
+1EE09;1EE09;1EE09;064A;064A; # (𞸉; 𞸉; 𞸉; ي; ي; ) ARABIC MATHEMATICAL YEH
+1EE0A;1EE0A;1EE0A;0643;0643; # (𞸊; 𞸊; 𞸊; ك; ك; ) ARABIC MATHEMATICAL KAF
+1EE0B;1EE0B;1EE0B;0644;0644; # (𞸋; 𞸋; 𞸋; ل; ل; ) ARABIC MATHEMATICAL LAM
+1EE0C;1EE0C;1EE0C;0645;0645; # (𞸌; 𞸌; 𞸌; م; م; ) ARABIC MATHEMATICAL MEEM
+1EE0D;1EE0D;1EE0D;0646;0646; # (ðž¸; ðž¸; ðž¸; Ù†; Ù†; ) ARABIC MATHEMATICAL NOON
+1EE0E;1EE0E;1EE0E;0633;0633; # (𞸎; 𞸎; 𞸎; س; س; ) ARABIC MATHEMATICAL SEEN
+1EE0F;1EE0F;1EE0F;0639;0639; # (ðž¸; ðž¸; ðž¸; ع; ع; ) ARABIC MATHEMATICAL AIN
+1EE10;1EE10;1EE10;0641;0641; # (ðž¸; ðž¸; ðž¸; Ù; Ù; ) ARABIC MATHEMATICAL FEH
+1EE11;1EE11;1EE11;0635;0635; # (𞸑; 𞸑; 𞸑; ص; ص; ) ARABIC MATHEMATICAL SAD
+1EE12;1EE12;1EE12;0642;0642; # (𞸒; 𞸒; 𞸒; ق; ق; ) ARABIC MATHEMATICAL QAF
+1EE13;1EE13;1EE13;0631;0631; # (𞸓; 𞸓; 𞸓; ر; ر; ) ARABIC MATHEMATICAL REH
+1EE14;1EE14;1EE14;0634;0634; # (𞸔; 𞸔; 𞸔; ش; ش; ) ARABIC MATHEMATICAL SHEEN
+1EE15;1EE15;1EE15;062A;062A; # (𞸕; 𞸕; 𞸕; ت; ت; ) ARABIC MATHEMATICAL TEH
+1EE16;1EE16;1EE16;062B;062B; # (𞸖; 𞸖; 𞸖; ث; ث; ) ARABIC MATHEMATICAL THEH
+1EE17;1EE17;1EE17;062E;062E; # (𞸗; 𞸗; 𞸗; خ; خ; ) ARABIC MATHEMATICAL KHAH
+1EE18;1EE18;1EE18;0630;0630; # (𞸘; 𞸘; 𞸘; ذ; ذ; ) ARABIC MATHEMATICAL THAL
+1EE19;1EE19;1EE19;0636;0636; # (𞸙; 𞸙; 𞸙; ض; ض; ) ARABIC MATHEMATICAL DAD
+1EE1A;1EE1A;1EE1A;0638;0638; # (𞸚; 𞸚; 𞸚; ظ; ظ; ) ARABIC MATHEMATICAL ZAH
+1EE1B;1EE1B;1EE1B;063A;063A; # (𞸛; 𞸛; 𞸛; غ; غ; ) ARABIC MATHEMATICAL GHAIN
+1EE1C;1EE1C;1EE1C;066E;066E; # (𞸜; 𞸜; 𞸜; ٮ; ٮ; ) ARABIC MATHEMATICAL DOTLESS BEH
+1EE1D;1EE1D;1EE1D;06BA;06BA; # (ðž¸; ðž¸; ðž¸; Úº; Úº; ) ARABIC MATHEMATICAL DOTLESS NOON
+1EE1E;1EE1E;1EE1E;06A1;06A1; # (𞸞; 𞸞; 𞸞; ڡ; ڡ; ) ARABIC MATHEMATICAL DOTLESS FEH
+1EE1F;1EE1F;1EE1F;066F;066F; # (𞸟; 𞸟; 𞸟; ٯ; ٯ; ) ARABIC MATHEMATICAL DOTLESS QAF
+1EE21;1EE21;1EE21;0628;0628; # (𞸡; 𞸡; 𞸡; ب; ب; ) ARABIC MATHEMATICAL INITIAL BEH
+1EE22;1EE22;1EE22;062C;062C; # (𞸢; 𞸢; 𞸢; ج; ج; ) ARABIC MATHEMATICAL INITIAL JEEM
+1EE24;1EE24;1EE24;0647;0647; # (𞸤; 𞸤; 𞸤; ه; ه; ) ARABIC MATHEMATICAL INITIAL HEH
+1EE27;1EE27;1EE27;062D;062D; # (𞸧; 𞸧; 𞸧; ح; ح; ) ARABIC MATHEMATICAL INITIAL HAH
+1EE29;1EE29;1EE29;064A;064A; # (𞸩; 𞸩; 𞸩; ي; ي; ) ARABIC MATHEMATICAL INITIAL YEH
+1EE2A;1EE2A;1EE2A;0643;0643; # (𞸪; 𞸪; 𞸪; ك; ك; ) ARABIC MATHEMATICAL INITIAL KAF
+1EE2B;1EE2B;1EE2B;0644;0644; # (𞸫; 𞸫; 𞸫; ل; ل; ) ARABIC MATHEMATICAL INITIAL LAM
+1EE2C;1EE2C;1EE2C;0645;0645; # (𞸬; 𞸬; 𞸬; م; م; ) ARABIC MATHEMATICAL INITIAL MEEM
+1EE2D;1EE2D;1EE2D;0646;0646; # (𞸭; 𞸭; 𞸭; ن; ن; ) ARABIC MATHEMATICAL INITIAL NOON
+1EE2E;1EE2E;1EE2E;0633;0633; # (𞸮; 𞸮; 𞸮; س; س; ) ARABIC MATHEMATICAL INITIAL SEEN
+1EE2F;1EE2F;1EE2F;0639;0639; # (𞸯; 𞸯; 𞸯; ع; ع; ) ARABIC MATHEMATICAL INITIAL AIN
+1EE30;1EE30;1EE30;0641;0641; # (𞸰; 𞸰; 𞸰; Ù; Ù; ) ARABIC MATHEMATICAL INITIAL FEH
+1EE31;1EE31;1EE31;0635;0635; # (𞸱; 𞸱; 𞸱; ص; ص; ) ARABIC MATHEMATICAL INITIAL SAD
+1EE32;1EE32;1EE32;0642;0642; # (𞸲; 𞸲; 𞸲; ق; ق; ) ARABIC MATHEMATICAL INITIAL QAF
+1EE34;1EE34;1EE34;0634;0634; # (𞸴; 𞸴; 𞸴; ش; ش; ) ARABIC MATHEMATICAL INITIAL SHEEN
+1EE35;1EE35;1EE35;062A;062A; # (𞸵; 𞸵; 𞸵; ت; ت; ) ARABIC MATHEMATICAL INITIAL TEH
+1EE36;1EE36;1EE36;062B;062B; # (𞸶; 𞸶; 𞸶; ث; ث; ) ARABIC MATHEMATICAL INITIAL THEH
+1EE37;1EE37;1EE37;062E;062E; # (𞸷; 𞸷; 𞸷; خ; خ; ) ARABIC MATHEMATICAL INITIAL KHAH
+1EE39;1EE39;1EE39;0636;0636; # (𞸹; 𞸹; 𞸹; ض; ض; ) ARABIC MATHEMATICAL INITIAL DAD
+1EE3B;1EE3B;1EE3B;063A;063A; # (𞸻; 𞸻; 𞸻; غ; غ; ) ARABIC MATHEMATICAL INITIAL GHAIN
+1EE42;1EE42;1EE42;062C;062C; # (𞹂; 𞹂; 𞹂; ج; ج; ) ARABIC MATHEMATICAL TAILED JEEM
+1EE47;1EE47;1EE47;062D;062D; # (𞹇; 𞹇; 𞹇; ح; ح; ) ARABIC MATHEMATICAL TAILED HAH
+1EE49;1EE49;1EE49;064A;064A; # (𞹉; 𞹉; 𞹉; ي; ي; ) ARABIC MATHEMATICAL TAILED YEH
+1EE4B;1EE4B;1EE4B;0644;0644; # (𞹋; 𞹋; 𞹋; ل; ل; ) ARABIC MATHEMATICAL TAILED LAM
+1EE4D;1EE4D;1EE4D;0646;0646; # (ðž¹; ðž¹; ðž¹; Ù†; Ù†; ) ARABIC MATHEMATICAL TAILED NOON
+1EE4E;1EE4E;1EE4E;0633;0633; # (𞹎; 𞹎; 𞹎; س; س; ) ARABIC MATHEMATICAL TAILED SEEN
+1EE4F;1EE4F;1EE4F;0639;0639; # (ðž¹; ðž¹; ðž¹; ع; ع; ) ARABIC MATHEMATICAL TAILED AIN
+1EE51;1EE51;1EE51;0635;0635; # (𞹑; 𞹑; 𞹑; ص; ص; ) ARABIC MATHEMATICAL TAILED SAD
+1EE52;1EE52;1EE52;0642;0642; # (ðž¹’; ðž¹’; ðž¹’; Ù‚; Ù‚; ) ARABIC MATHEMATICAL TAILED QAF
+1EE54;1EE54;1EE54;0634;0634; # (ðž¹”; ðž¹”; ðž¹”; Ø´; Ø´; ) ARABIC MATHEMATICAL TAILED SHEEN
+1EE57;1EE57;1EE57;062E;062E; # (ðž¹—; ðž¹—; ðž¹—; Ø®; Ø®; ) ARABIC MATHEMATICAL TAILED KHAH
+1EE59;1EE59;1EE59;0636;0636; # (𞹙; 𞹙; 𞹙; ض; ض; ) ARABIC MATHEMATICAL TAILED DAD
+1EE5B;1EE5B;1EE5B;063A;063A; # (𞹛; 𞹛; 𞹛; غ; غ; ) ARABIC MATHEMATICAL TAILED GHAIN
+1EE5D;1EE5D;1EE5D;06BA;06BA; # (ðž¹; ðž¹; ðž¹; Úº; Úº; ) ARABIC MATHEMATICAL TAILED DOTLESS NOON
+1EE5F;1EE5F;1EE5F;066F;066F; # (𞹟; 𞹟; 𞹟; ٯ; ٯ; ) ARABIC MATHEMATICAL TAILED DOTLESS QAF
+1EE61;1EE61;1EE61;0628;0628; # (𞹡; 𞹡; 𞹡; ب; ب; ) ARABIC MATHEMATICAL STRETCHED BEH
+1EE62;1EE62;1EE62;062C;062C; # (𞹢; 𞹢; 𞹢; ج; ج; ) ARABIC MATHEMATICAL STRETCHED JEEM
+1EE64;1EE64;1EE64;0647;0647; # (𞹤; 𞹤; 𞹤; ه; ه; ) ARABIC MATHEMATICAL STRETCHED HEH
+1EE67;1EE67;1EE67;062D;062D; # (𞹧; 𞹧; 𞹧; ح; ح; ) ARABIC MATHEMATICAL STRETCHED HAH
+1EE68;1EE68;1EE68;0637;0637; # (𞹨; 𞹨; 𞹨; ط; ط; ) ARABIC MATHEMATICAL STRETCHED TAH
+1EE69;1EE69;1EE69;064A;064A; # (𞹩; 𞹩; 𞹩; ي; ي; ) ARABIC MATHEMATICAL STRETCHED YEH
+1EE6A;1EE6A;1EE6A;0643;0643; # (𞹪; 𞹪; 𞹪; ك; ك; ) ARABIC MATHEMATICAL STRETCHED KAF
+1EE6C;1EE6C;1EE6C;0645;0645; # (𞹬; 𞹬; 𞹬; م; م; ) ARABIC MATHEMATICAL STRETCHED MEEM
+1EE6D;1EE6D;1EE6D;0646;0646; # (ðž¹­; ðž¹­; ðž¹­; Ù†; Ù†; ) ARABIC MATHEMATICAL STRETCHED NOON
+1EE6E;1EE6E;1EE6E;0633;0633; # (𞹮; 𞹮; 𞹮; س; س; ) ARABIC MATHEMATICAL STRETCHED SEEN
+1EE6F;1EE6F;1EE6F;0639;0639; # (𞹯; 𞹯; 𞹯; ع; ع; ) ARABIC MATHEMATICAL STRETCHED AIN
+1EE70;1EE70;1EE70;0641;0641; # (ðž¹°; ðž¹°; ðž¹°; Ù; Ù; ) ARABIC MATHEMATICAL STRETCHED FEH
+1EE71;1EE71;1EE71;0635;0635; # (𞹱; 𞹱; 𞹱; ص; ص; ) ARABIC MATHEMATICAL STRETCHED SAD
+1EE72;1EE72;1EE72;0642;0642; # (ðž¹²; ðž¹²; ðž¹²; Ù‚; Ù‚; ) ARABIC MATHEMATICAL STRETCHED QAF
+1EE74;1EE74;1EE74;0634;0634; # (ðž¹´; ðž¹´; ðž¹´; Ø´; Ø´; ) ARABIC MATHEMATICAL STRETCHED SHEEN
+1EE75;1EE75;1EE75;062A;062A; # (𞹵; 𞹵; 𞹵; ت; ت; ) ARABIC MATHEMATICAL STRETCHED TEH
+1EE76;1EE76;1EE76;062B;062B; # (𞹶; 𞹶; 𞹶; ث; ث; ) ARABIC MATHEMATICAL STRETCHED THEH
+1EE77;1EE77;1EE77;062E;062E; # (ðž¹·; ðž¹·; ðž¹·; Ø®; Ø®; ) ARABIC MATHEMATICAL STRETCHED KHAH
+1EE79;1EE79;1EE79;0636;0636; # (𞹹; 𞹹; 𞹹; ض; ض; ) ARABIC MATHEMATICAL STRETCHED DAD
+1EE7A;1EE7A;1EE7A;0638;0638; # (𞹺; 𞹺; 𞹺; ظ; ظ; ) ARABIC MATHEMATICAL STRETCHED ZAH
+1EE7B;1EE7B;1EE7B;063A;063A; # (𞹻; 𞹻; 𞹻; غ; غ; ) ARABIC MATHEMATICAL STRETCHED GHAIN
+1EE7C;1EE7C;1EE7C;066E;066E; # (ðž¹¼; ðž¹¼; ðž¹¼; Ù®; Ù®; ) ARABIC MATHEMATICAL STRETCHED DOTLESS BEH
+1EE7E;1EE7E;1EE7E;06A1;06A1; # (ðž¹¾; ðž¹¾; ðž¹¾; Ú¡; Ú¡; ) ARABIC MATHEMATICAL STRETCHED DOTLESS FEH
+1EE80;1EE80;1EE80;0627;0627; # (𞺀; 𞺀; 𞺀; ا; ا; ) ARABIC MATHEMATICAL LOOPED ALEF
+1EE81;1EE81;1EE81;0628;0628; # (ðžº; ðžº; ðžº; ب; ب; ) ARABIC MATHEMATICAL LOOPED BEH
+1EE82;1EE82;1EE82;062C;062C; # (𞺂; 𞺂; 𞺂; ج; ج; ) ARABIC MATHEMATICAL LOOPED JEEM
+1EE83;1EE83;1EE83;062F;062F; # (𞺃; 𞺃; 𞺃; د; د; ) ARABIC MATHEMATICAL LOOPED DAL
+1EE84;1EE84;1EE84;0647;0647; # (𞺄; 𞺄; 𞺄; ه; ه; ) ARABIC MATHEMATICAL LOOPED HEH
+1EE85;1EE85;1EE85;0648;0648; # (𞺅; 𞺅; 𞺅; و; و; ) ARABIC MATHEMATICAL LOOPED WAW
+1EE86;1EE86;1EE86;0632;0632; # (𞺆; 𞺆; 𞺆; ز; ز; ) ARABIC MATHEMATICAL LOOPED ZAIN
+1EE87;1EE87;1EE87;062D;062D; # (𞺇; 𞺇; 𞺇; ح; ح; ) ARABIC MATHEMATICAL LOOPED HAH
+1EE88;1EE88;1EE88;0637;0637; # (𞺈; 𞺈; 𞺈; ط; ط; ) ARABIC MATHEMATICAL LOOPED TAH
+1EE89;1EE89;1EE89;064A;064A; # (𞺉; 𞺉; 𞺉; ي; ي; ) ARABIC MATHEMATICAL LOOPED YEH
+1EE8B;1EE8B;1EE8B;0644;0644; # (𞺋; 𞺋; 𞺋; ل; ل; ) ARABIC MATHEMATICAL LOOPED LAM
+1EE8C;1EE8C;1EE8C;0645;0645; # (𞺌; 𞺌; 𞺌; م; م; ) ARABIC MATHEMATICAL LOOPED MEEM
+1EE8D;1EE8D;1EE8D;0646;0646; # (ðžº; ðžº; ðžº; Ù†; Ù†; ) ARABIC MATHEMATICAL LOOPED NOON
+1EE8E;1EE8E;1EE8E;0633;0633; # (𞺎; 𞺎; 𞺎; س; س; ) ARABIC MATHEMATICAL LOOPED SEEN
+1EE8F;1EE8F;1EE8F;0639;0639; # (ðžº; ðžº; ðžº; ع; ع; ) ARABIC MATHEMATICAL LOOPED AIN
+1EE90;1EE90;1EE90;0641;0641; # (ðžº; ðžº; ðžº; Ù; Ù; ) ARABIC MATHEMATICAL LOOPED FEH
+1EE91;1EE91;1EE91;0635;0635; # (𞺑; 𞺑; 𞺑; ص; ص; ) ARABIC MATHEMATICAL LOOPED SAD
+1EE92;1EE92;1EE92;0642;0642; # (𞺒; 𞺒; 𞺒; ق; ق; ) ARABIC MATHEMATICAL LOOPED QAF
+1EE93;1EE93;1EE93;0631;0631; # (𞺓; 𞺓; 𞺓; ر; ر; ) ARABIC MATHEMATICAL LOOPED REH
+1EE94;1EE94;1EE94;0634;0634; # (𞺔; 𞺔; 𞺔; ش; ش; ) ARABIC MATHEMATICAL LOOPED SHEEN
+1EE95;1EE95;1EE95;062A;062A; # (𞺕; 𞺕; 𞺕; ت; ت; ) ARABIC MATHEMATICAL LOOPED TEH
+1EE96;1EE96;1EE96;062B;062B; # (𞺖; 𞺖; 𞺖; ث; ث; ) ARABIC MATHEMATICAL LOOPED THEH
+1EE97;1EE97;1EE97;062E;062E; # (𞺗; 𞺗; 𞺗; خ; خ; ) ARABIC MATHEMATICAL LOOPED KHAH
+1EE98;1EE98;1EE98;0630;0630; # (𞺘; 𞺘; 𞺘; ذ; ذ; ) ARABIC MATHEMATICAL LOOPED THAL
+1EE99;1EE99;1EE99;0636;0636; # (𞺙; 𞺙; 𞺙; ض; ض; ) ARABIC MATHEMATICAL LOOPED DAD
+1EE9A;1EE9A;1EE9A;0638;0638; # (𞺚; 𞺚; 𞺚; ظ; ظ; ) ARABIC MATHEMATICAL LOOPED ZAH
+1EE9B;1EE9B;1EE9B;063A;063A; # (𞺛; 𞺛; 𞺛; غ; غ; ) ARABIC MATHEMATICAL LOOPED GHAIN
+1EEA1;1EEA1;1EEA1;0628;0628; # (𞺡; 𞺡; 𞺡; ب; ب; ) ARABIC MATHEMATICAL DOUBLE-STRUCK BEH
+1EEA2;1EEA2;1EEA2;062C;062C; # (𞺢; 𞺢; 𞺢; ج; ج; ) ARABIC MATHEMATICAL DOUBLE-STRUCK JEEM
+1EEA3;1EEA3;1EEA3;062F;062F; # (𞺣; 𞺣; 𞺣; د; د; ) ARABIC MATHEMATICAL DOUBLE-STRUCK DAL
+1EEA5;1EEA5;1EEA5;0648;0648; # (𞺥; 𞺥; 𞺥; و; و; ) ARABIC MATHEMATICAL DOUBLE-STRUCK WAW
+1EEA6;1EEA6;1EEA6;0632;0632; # (𞺦; 𞺦; 𞺦; ز; ز; ) ARABIC MATHEMATICAL DOUBLE-STRUCK ZAIN
+1EEA7;1EEA7;1EEA7;062D;062D; # (𞺧; 𞺧; 𞺧; ح; ح; ) ARABIC MATHEMATICAL DOUBLE-STRUCK HAH
+1EEA8;1EEA8;1EEA8;0637;0637; # (𞺨; 𞺨; 𞺨; ط; ط; ) ARABIC MATHEMATICAL DOUBLE-STRUCK TAH
+1EEA9;1EEA9;1EEA9;064A;064A; # (𞺩; 𞺩; 𞺩; ي; ي; ) ARABIC MATHEMATICAL DOUBLE-STRUCK YEH
+1EEAB;1EEAB;1EEAB;0644;0644; # (𞺫; 𞺫; 𞺫; ل; ل; ) ARABIC MATHEMATICAL DOUBLE-STRUCK LAM
+1EEAC;1EEAC;1EEAC;0645;0645; # (𞺬; 𞺬; 𞺬; م; م; ) ARABIC MATHEMATICAL DOUBLE-STRUCK MEEM
+1EEAD;1EEAD;1EEAD;0646;0646; # (𞺭; 𞺭; 𞺭; ن; ن; ) ARABIC MATHEMATICAL DOUBLE-STRUCK NOON
+1EEAE;1EEAE;1EEAE;0633;0633; # (𞺮; 𞺮; 𞺮; س; س; ) ARABIC MATHEMATICAL DOUBLE-STRUCK SEEN
+1EEAF;1EEAF;1EEAF;0639;0639; # (𞺯; 𞺯; 𞺯; ع; ع; ) ARABIC MATHEMATICAL DOUBLE-STRUCK AIN
+1EEB0;1EEB0;1EEB0;0641;0641; # (𞺰; 𞺰; 𞺰; Ù; Ù; ) ARABIC MATHEMATICAL DOUBLE-STRUCK FEH
+1EEB1;1EEB1;1EEB1;0635;0635; # (𞺱; 𞺱; 𞺱; ص; ص; ) ARABIC MATHEMATICAL DOUBLE-STRUCK SAD
+1EEB2;1EEB2;1EEB2;0642;0642; # (𞺲; 𞺲; 𞺲; ق; ق; ) ARABIC MATHEMATICAL DOUBLE-STRUCK QAF
+1EEB3;1EEB3;1EEB3;0631;0631; # (𞺳; 𞺳; 𞺳; ر; ر; ) ARABIC MATHEMATICAL DOUBLE-STRUCK REH
+1EEB4;1EEB4;1EEB4;0634;0634; # (𞺴; 𞺴; 𞺴; ش; ش; ) ARABIC MATHEMATICAL DOUBLE-STRUCK SHEEN
+1EEB5;1EEB5;1EEB5;062A;062A; # (𞺵; 𞺵; 𞺵; ت; ت; ) ARABIC MATHEMATICAL DOUBLE-STRUCK TEH
+1EEB6;1EEB6;1EEB6;062B;062B; # (𞺶; 𞺶; 𞺶; ث; ث; ) ARABIC MATHEMATICAL DOUBLE-STRUCK THEH
+1EEB7;1EEB7;1EEB7;062E;062E; # (𞺷; 𞺷; 𞺷; خ; خ; ) ARABIC MATHEMATICAL DOUBLE-STRUCK KHAH
+1EEB8;1EEB8;1EEB8;0630;0630; # (𞺸; 𞺸; 𞺸; ذ; ذ; ) ARABIC MATHEMATICAL DOUBLE-STRUCK THAL
+1EEB9;1EEB9;1EEB9;0636;0636; # (𞺹; 𞺹; 𞺹; ض; ض; ) ARABIC MATHEMATICAL DOUBLE-STRUCK DAD
+1EEBA;1EEBA;1EEBA;0638;0638; # (𞺺; 𞺺; 𞺺; ظ; ظ; ) ARABIC MATHEMATICAL DOUBLE-STRUCK ZAH
+1EEBB;1EEBB;1EEBB;063A;063A; # (𞺻; 𞺻; 𞺻; غ; غ; ) ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN
+1F100;1F100;1F100;0030 002E;0030 002E; # (🄀; 🄀; 🄀; 0.; 0.; ) DIGIT ZERO FULL STOP
+1F101;1F101;1F101;0030 002C;0030 002C; # (ðŸ„; ðŸ„; ðŸ„; 0,; 0,; ) DIGIT ZERO COMMA
+1F102;1F102;1F102;0031 002C;0031 002C; # (🄂; 🄂; 🄂; 1,; 1,; ) DIGIT ONE COMMA
+1F103;1F103;1F103;0032 002C;0032 002C; # (🄃; 🄃; 🄃; 2,; 2,; ) DIGIT TWO COMMA
+1F104;1F104;1F104;0033 002C;0033 002C; # (🄄; 🄄; 🄄; 3,; 3,; ) DIGIT THREE COMMA
+1F105;1F105;1F105;0034 002C;0034 002C; # (🄅; 🄅; 🄅; 4,; 4,; ) DIGIT FOUR COMMA
+1F106;1F106;1F106;0035 002C;0035 002C; # (🄆; 🄆; 🄆; 5,; 5,; ) DIGIT FIVE COMMA
+1F107;1F107;1F107;0036 002C;0036 002C; # (🄇; 🄇; 🄇; 6,; 6,; ) DIGIT SIX COMMA
+1F108;1F108;1F108;0037 002C;0037 002C; # (🄈; 🄈; 🄈; 7,; 7,; ) DIGIT SEVEN COMMA
+1F109;1F109;1F109;0038 002C;0038 002C; # (🄉; 🄉; 🄉; 8,; 8,; ) DIGIT EIGHT COMMA
+1F10A;1F10A;1F10A;0039 002C;0039 002C; # (🄊; 🄊; 🄊; 9,; 9,; ) DIGIT NINE COMMA
+1F110;1F110;1F110;0028 0041 0029;0028 0041 0029; # (ðŸ„; ðŸ„; ðŸ„; (A); (A); ) PARENTHESIZED LATIN CAPITAL LETTER A
+1F111;1F111;1F111;0028 0042 0029;0028 0042 0029; # (🄑; 🄑; 🄑; (B); (B); ) PARENTHESIZED LATIN CAPITAL LETTER B
+1F112;1F112;1F112;0028 0043 0029;0028 0043 0029; # (🄒; 🄒; 🄒; (C); (C); ) PARENTHESIZED LATIN CAPITAL LETTER C
+1F113;1F113;1F113;0028 0044 0029;0028 0044 0029; # (🄓; 🄓; 🄓; (D); (D); ) PARENTHESIZED LATIN CAPITAL LETTER D
+1F114;1F114;1F114;0028 0045 0029;0028 0045 0029; # (🄔; 🄔; 🄔; (E); (E); ) PARENTHESIZED LATIN CAPITAL LETTER E
+1F115;1F115;1F115;0028 0046 0029;0028 0046 0029; # (🄕; 🄕; 🄕; (F); (F); ) PARENTHESIZED LATIN CAPITAL LETTER F
+1F116;1F116;1F116;0028 0047 0029;0028 0047 0029; # (🄖; 🄖; 🄖; (G); (G); ) PARENTHESIZED LATIN CAPITAL LETTER G
+1F117;1F117;1F117;0028 0048 0029;0028 0048 0029; # (🄗; 🄗; 🄗; (H); (H); ) PARENTHESIZED LATIN CAPITAL LETTER H
+1F118;1F118;1F118;0028 0049 0029;0028 0049 0029; # (🄘; 🄘; 🄘; (I); (I); ) PARENTHESIZED LATIN CAPITAL LETTER I
+1F119;1F119;1F119;0028 004A 0029;0028 004A 0029; # (🄙; 🄙; 🄙; (J); (J); ) PARENTHESIZED LATIN CAPITAL LETTER J
+1F11A;1F11A;1F11A;0028 004B 0029;0028 004B 0029; # (🄚; 🄚; 🄚; (K); (K); ) PARENTHESIZED LATIN CAPITAL LETTER K
+1F11B;1F11B;1F11B;0028 004C 0029;0028 004C 0029; # (🄛; 🄛; 🄛; (L); (L); ) PARENTHESIZED LATIN CAPITAL LETTER L
+1F11C;1F11C;1F11C;0028 004D 0029;0028 004D 0029; # (🄜; 🄜; 🄜; (M); (M); ) PARENTHESIZED LATIN CAPITAL LETTER M
+1F11D;1F11D;1F11D;0028 004E 0029;0028 004E 0029; # (ðŸ„; ðŸ„; ðŸ„; (N); (N); ) PARENTHESIZED LATIN CAPITAL LETTER N
+1F11E;1F11E;1F11E;0028 004F 0029;0028 004F 0029; # (🄞; 🄞; 🄞; (O); (O); ) PARENTHESIZED LATIN CAPITAL LETTER O
+1F11F;1F11F;1F11F;0028 0050 0029;0028 0050 0029; # (🄟; 🄟; 🄟; (P); (P); ) PARENTHESIZED LATIN CAPITAL LETTER P
+1F120;1F120;1F120;0028 0051 0029;0028 0051 0029; # (🄠; 🄠; 🄠; (Q); (Q); ) PARENTHESIZED LATIN CAPITAL LETTER Q
+1F121;1F121;1F121;0028 0052 0029;0028 0052 0029; # (🄡; 🄡; 🄡; (R); (R); ) PARENTHESIZED LATIN CAPITAL LETTER R
+1F122;1F122;1F122;0028 0053 0029;0028 0053 0029; # (🄢; 🄢; 🄢; (S); (S); ) PARENTHESIZED LATIN CAPITAL LETTER S
+1F123;1F123;1F123;0028 0054 0029;0028 0054 0029; # (🄣; 🄣; 🄣; (T); (T); ) PARENTHESIZED LATIN CAPITAL LETTER T
+1F124;1F124;1F124;0028 0055 0029;0028 0055 0029; # (🄤; 🄤; 🄤; (U); (U); ) PARENTHESIZED LATIN CAPITAL LETTER U
+1F125;1F125;1F125;0028 0056 0029;0028 0056 0029; # (🄥; 🄥; 🄥; (V); (V); ) PARENTHESIZED LATIN CAPITAL LETTER V
+1F126;1F126;1F126;0028 0057 0029;0028 0057 0029; # (🄦; 🄦; 🄦; (W); (W); ) PARENTHESIZED LATIN CAPITAL LETTER W
+1F127;1F127;1F127;0028 0058 0029;0028 0058 0029; # (🄧; 🄧; 🄧; (X); (X); ) PARENTHESIZED LATIN CAPITAL LETTER X
+1F128;1F128;1F128;0028 0059 0029;0028 0059 0029; # (🄨; 🄨; 🄨; (Y); (Y); ) PARENTHESIZED LATIN CAPITAL LETTER Y
+1F129;1F129;1F129;0028 005A 0029;0028 005A 0029; # (🄩; 🄩; 🄩; (Z); (Z); ) PARENTHESIZED LATIN CAPITAL LETTER Z
+1F12A;1F12A;1F12A;3014 0053 3015;3014 0053 3015; # (🄪; 🄪; 🄪; 〔S〕; 〔S〕; ) TORTOISE SHELL BRACKETED LATIN CAPITAL LETTER S
+1F12B;1F12B;1F12B;0043;0043; # (🄫; 🄫; 🄫; C; C; ) CIRCLED ITALIC LATIN CAPITAL LETTER C
+1F12C;1F12C;1F12C;0052;0052; # (🄬; 🄬; 🄬; R; R; ) CIRCLED ITALIC LATIN CAPITAL LETTER R
+1F12D;1F12D;1F12D;0043 0044;0043 0044; # (🄭; 🄭; 🄭; CD; CD; ) CIRCLED CD
+1F12E;1F12E;1F12E;0057 005A;0057 005A; # (🄮; 🄮; 🄮; WZ; WZ; ) CIRCLED WZ
+1F130;1F130;1F130;0041;0041; # (🄰; 🄰; 🄰; A; A; ) SQUARED LATIN CAPITAL LETTER A
+1F131;1F131;1F131;0042;0042; # (🄱; 🄱; 🄱; B; B; ) SQUARED LATIN CAPITAL LETTER B
+1F132;1F132;1F132;0043;0043; # (🄲; 🄲; 🄲; C; C; ) SQUARED LATIN CAPITAL LETTER C
+1F133;1F133;1F133;0044;0044; # (🄳; 🄳; 🄳; D; D; ) SQUARED LATIN CAPITAL LETTER D
+1F134;1F134;1F134;0045;0045; # (🄴; 🄴; 🄴; E; E; ) SQUARED LATIN CAPITAL LETTER E
+1F135;1F135;1F135;0046;0046; # (🄵; 🄵; 🄵; F; F; ) SQUARED LATIN CAPITAL LETTER F
+1F136;1F136;1F136;0047;0047; # (🄶; 🄶; 🄶; G; G; ) SQUARED LATIN CAPITAL LETTER G
+1F137;1F137;1F137;0048;0048; # (🄷; 🄷; 🄷; H; H; ) SQUARED LATIN CAPITAL LETTER H
+1F138;1F138;1F138;0049;0049; # (🄸; 🄸; 🄸; I; I; ) SQUARED LATIN CAPITAL LETTER I
+1F139;1F139;1F139;004A;004A; # (🄹; 🄹; 🄹; J; J; ) SQUARED LATIN CAPITAL LETTER J
+1F13A;1F13A;1F13A;004B;004B; # (🄺; 🄺; 🄺; K; K; ) SQUARED LATIN CAPITAL LETTER K
+1F13B;1F13B;1F13B;004C;004C; # (🄻; 🄻; 🄻; L; L; ) SQUARED LATIN CAPITAL LETTER L
+1F13C;1F13C;1F13C;004D;004D; # (🄼; 🄼; 🄼; M; M; ) SQUARED LATIN CAPITAL LETTER M
+1F13D;1F13D;1F13D;004E;004E; # (🄽; 🄽; 🄽; N; N; ) SQUARED LATIN CAPITAL LETTER N
+1F13E;1F13E;1F13E;004F;004F; # (🄾; 🄾; 🄾; O; O; ) SQUARED LATIN CAPITAL LETTER O
+1F13F;1F13F;1F13F;0050;0050; # (🄿; 🄿; 🄿; P; P; ) SQUARED LATIN CAPITAL LETTER P
+1F140;1F140;1F140;0051;0051; # (🅀; 🅀; 🅀; Q; Q; ) SQUARED LATIN CAPITAL LETTER Q
+1F141;1F141;1F141;0052;0052; # (ðŸ…; ðŸ…; ðŸ…; R; R; ) SQUARED LATIN CAPITAL LETTER R
+1F142;1F142;1F142;0053;0053; # (🅂; 🅂; 🅂; S; S; ) SQUARED LATIN CAPITAL LETTER S
+1F143;1F143;1F143;0054;0054; # (🅃; 🅃; 🅃; T; T; ) SQUARED LATIN CAPITAL LETTER T
+1F144;1F144;1F144;0055;0055; # (🅄; 🅄; 🅄; U; U; ) SQUARED LATIN CAPITAL LETTER U
+1F145;1F145;1F145;0056;0056; # (🅅; 🅅; 🅅; V; V; ) SQUARED LATIN CAPITAL LETTER V
+1F146;1F146;1F146;0057;0057; # (🅆; 🅆; 🅆; W; W; ) SQUARED LATIN CAPITAL LETTER W
+1F147;1F147;1F147;0058;0058; # (🅇; 🅇; 🅇; X; X; ) SQUARED LATIN CAPITAL LETTER X
+1F148;1F148;1F148;0059;0059; # (🅈; 🅈; 🅈; Y; Y; ) SQUARED LATIN CAPITAL LETTER Y
+1F149;1F149;1F149;005A;005A; # (🅉; 🅉; 🅉; Z; Z; ) SQUARED LATIN CAPITAL LETTER Z
+1F14A;1F14A;1F14A;0048 0056;0048 0056; # (🅊; 🅊; 🅊; HV; HV; ) SQUARED HV
+1F14B;1F14B;1F14B;004D 0056;004D 0056; # (🅋; 🅋; 🅋; MV; MV; ) SQUARED MV
+1F14C;1F14C;1F14C;0053 0044;0053 0044; # (🅌; 🅌; 🅌; SD; SD; ) SQUARED SD
+1F14D;1F14D;1F14D;0053 0053;0053 0053; # (ðŸ…; ðŸ…; ðŸ…; SS; SS; ) SQUARED SS
+1F14E;1F14E;1F14E;0050 0050 0056;0050 0050 0056; # (🅎; 🅎; 🅎; PPV; PPV; ) SQUARED PPV
+1F14F;1F14F;1F14F;0057 0043;0057 0043; # (ðŸ…; ðŸ…; ðŸ…; WC; WC; ) SQUARED WC
+1F16A;1F16A;1F16A;004D 0043;004D 0043; # (🅪; 🅪; 🅪; MC; MC; ) RAISED MC SIGN
+1F16B;1F16B;1F16B;004D 0044;004D 0044; # (🅫; 🅫; 🅫; MD; MD; ) RAISED MD SIGN
+1F16C;1F16C;1F16C;004D 0052;004D 0052; # (🅬; 🅬; 🅬; MR; MR; ) RAISED MR SIGN
+1F190;1F190;1F190;0044 004A;0044 004A; # (ðŸ†; ðŸ†; ðŸ†; DJ; DJ; ) SQUARE DJ
+1F200;1F200;1F200;307B 304B;307B 304B; # (🈀; 🈀; 🈀; ã»ã‹; ã»ã‹; ) SQUARE HIRAGANA HOKA
+1F201;1F201;1F201;30B3 30B3;30B3 30B3; # (ðŸˆ; ðŸˆ; ðŸˆ; ココ; ココ; ) SQUARED KATAKANA KOKO
+1F202;1F202;1F202;30B5;30B5; # (🈂; 🈂; 🈂; サ; サ; ) SQUARED KATAKANA SA
+1F210;1F210;1F210;624B;624B; # (ðŸˆ; ðŸˆ; ðŸˆ; 手; 手; ) SQUARED CJK UNIFIED IDEOGRAPH-624B
+1F211;1F211;1F211;5B57;5B57; # (🈑; 🈑; 🈑; 字; 字; ) SQUARED CJK UNIFIED IDEOGRAPH-5B57
+1F212;1F212;1F212;53CC;53CC; # (🈒; 🈒; 🈒; åŒ; åŒ; ) SQUARED CJK UNIFIED IDEOGRAPH-53CC
+1F213;1F213;1F213;30C7;30C6 3099; # (🈓; 🈓; 🈓; デ; テ◌゙; ) SQUARED KATAKANA DE
+1F214;1F214;1F214;4E8C;4E8C; # (🈔; 🈔; 🈔; 二; 二; ) SQUARED CJK UNIFIED IDEOGRAPH-4E8C
+1F215;1F215;1F215;591A;591A; # (🈕; 🈕; 🈕; 多; 多; ) SQUARED CJK UNIFIED IDEOGRAPH-591A
+1F216;1F216;1F216;89E3;89E3; # (🈖; 🈖; 🈖; 解; 解; ) SQUARED CJK UNIFIED IDEOGRAPH-89E3
+1F217;1F217;1F217;5929;5929; # (🈗; 🈗; 🈗; 天; 天; ) SQUARED CJK UNIFIED IDEOGRAPH-5929
+1F218;1F218;1F218;4EA4;4EA4; # (🈘; 🈘; 🈘; 交; 交; ) SQUARED CJK UNIFIED IDEOGRAPH-4EA4
+1F219;1F219;1F219;6620;6620; # (🈙; 🈙; 🈙; 映; 映; ) SQUARED CJK UNIFIED IDEOGRAPH-6620
+1F21A;1F21A;1F21A;7121;7121; # (🈚; 🈚; 🈚; 無; 無; ) SQUARED CJK UNIFIED IDEOGRAPH-7121
+1F21B;1F21B;1F21B;6599;6599; # (🈛; 🈛; 🈛; 料; 料; ) SQUARED CJK UNIFIED IDEOGRAPH-6599
+1F21C;1F21C;1F21C;524D;524D; # (🈜; 🈜; 🈜; å‰; å‰; ) SQUARED CJK UNIFIED IDEOGRAPH-524D
+1F21D;1F21D;1F21D;5F8C;5F8C; # (ðŸˆ; ðŸˆ; ðŸˆ; 後; 後; ) SQUARED CJK UNIFIED IDEOGRAPH-5F8C
+1F21E;1F21E;1F21E;518D;518D; # (🈞; 🈞; 🈞; å†; å†; ) SQUARED CJK UNIFIED IDEOGRAPH-518D
+1F21F;1F21F;1F21F;65B0;65B0; # (🈟; 🈟; 🈟; 新; 新; ) SQUARED CJK UNIFIED IDEOGRAPH-65B0
+1F220;1F220;1F220;521D;521D; # (🈠; 🈠; 🈠; åˆ; åˆ; ) SQUARED CJK UNIFIED IDEOGRAPH-521D
+1F221;1F221;1F221;7D42;7D42; # (🈡; 🈡; 🈡; 終; 終; ) SQUARED CJK UNIFIED IDEOGRAPH-7D42
+1F222;1F222;1F222;751F;751F; # (🈢; 🈢; 🈢; 生; 生; ) SQUARED CJK UNIFIED IDEOGRAPH-751F
+1F223;1F223;1F223;8CA9;8CA9; # (🈣; 🈣; 🈣; 販; 販; ) SQUARED CJK UNIFIED IDEOGRAPH-8CA9
+1F224;1F224;1F224;58F0;58F0; # (🈤; 🈤; 🈤; 声; 声; ) SQUARED CJK UNIFIED IDEOGRAPH-58F0
+1F225;1F225;1F225;5439;5439; # (🈥; 🈥; 🈥; å¹; å¹; ) SQUARED CJK UNIFIED IDEOGRAPH-5439
+1F226;1F226;1F226;6F14;6F14; # (🈦; 🈦; 🈦; 演; 演; ) SQUARED CJK UNIFIED IDEOGRAPH-6F14
+1F227;1F227;1F227;6295;6295; # (🈧; 🈧; 🈧; 投; 投; ) SQUARED CJK UNIFIED IDEOGRAPH-6295
+1F228;1F228;1F228;6355;6355; # (🈨; 🈨; 🈨; æ•; æ•; ) SQUARED CJK UNIFIED IDEOGRAPH-6355
+1F229;1F229;1F229;4E00;4E00; # (🈩; 🈩; 🈩; 一; 一; ) SQUARED CJK UNIFIED IDEOGRAPH-4E00
+1F22A;1F22A;1F22A;4E09;4E09; # (🈪; 🈪; 🈪; 三; 三; ) SQUARED CJK UNIFIED IDEOGRAPH-4E09
+1F22B;1F22B;1F22B;904A;904A; # (🈫; 🈫; 🈫; éŠ; éŠ; ) SQUARED CJK UNIFIED IDEOGRAPH-904A
+1F22C;1F22C;1F22C;5DE6;5DE6; # (🈬; 🈬; 🈬; 左; 左; ) SQUARED CJK UNIFIED IDEOGRAPH-5DE6
+1F22D;1F22D;1F22D;4E2D;4E2D; # (🈭; 🈭; 🈭; 中; 中; ) SQUARED CJK UNIFIED IDEOGRAPH-4E2D
+1F22E;1F22E;1F22E;53F3;53F3; # (🈮; 🈮; 🈮; å³; å³; ) SQUARED CJK UNIFIED IDEOGRAPH-53F3
+1F22F;1F22F;1F22F;6307;6307; # (🈯; 🈯; 🈯; 指; 指; ) SQUARED CJK UNIFIED IDEOGRAPH-6307
+1F230;1F230;1F230;8D70;8D70; # (🈰; 🈰; 🈰; 走; 走; ) SQUARED CJK UNIFIED IDEOGRAPH-8D70
+1F231;1F231;1F231;6253;6253; # (🈱; 🈱; 🈱; 打; 打; ) SQUARED CJK UNIFIED IDEOGRAPH-6253
+1F232;1F232;1F232;7981;7981; # (🈲; 🈲; 🈲; ç¦; ç¦; ) SQUARED CJK UNIFIED IDEOGRAPH-7981
+1F233;1F233;1F233;7A7A;7A7A; # (🈳; 🈳; 🈳; 空; 空; ) SQUARED CJK UNIFIED IDEOGRAPH-7A7A
+1F234;1F234;1F234;5408;5408; # (🈴; 🈴; 🈴; åˆ; åˆ; ) SQUARED CJK UNIFIED IDEOGRAPH-5408
+1F235;1F235;1F235;6E80;6E80; # (🈵; 🈵; 🈵; 満; 満; ) SQUARED CJK UNIFIED IDEOGRAPH-6E80
+1F236;1F236;1F236;6709;6709; # (🈶; 🈶; 🈶; 有; 有; ) SQUARED CJK UNIFIED IDEOGRAPH-6709
+1F237;1F237;1F237;6708;6708; # (🈷; 🈷; 🈷; 月; 月; ) SQUARED CJK UNIFIED IDEOGRAPH-6708
+1F238;1F238;1F238;7533;7533; # (🈸; 🈸; 🈸; 申; 申; ) SQUARED CJK UNIFIED IDEOGRAPH-7533
+1F239;1F239;1F239;5272;5272; # (🈹; 🈹; 🈹; 割; 割; ) SQUARED CJK UNIFIED IDEOGRAPH-5272
+1F23A;1F23A;1F23A;55B6;55B6; # (🈺; 🈺; 🈺; 営; 営; ) SQUARED CJK UNIFIED IDEOGRAPH-55B6
+1F23B;1F23B;1F23B;914D;914D; # (🈻; 🈻; 🈻; é…; é…; ) SQUARED CJK UNIFIED IDEOGRAPH-914D
+1F240;1F240;1F240;3014 672C 3015;3014 672C 3015; # (🉀; 🉀; 🉀; 〔本〕; 〔本〕; ) TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C
+1F241;1F241;1F241;3014 4E09 3015;3014 4E09 3015; # (ðŸ‰; ðŸ‰; ðŸ‰; 〔三〕; 〔三〕; ) TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-4E09
+1F242;1F242;1F242;3014 4E8C 3015;3014 4E8C 3015; # (🉂; 🉂; 🉂; 〔二〕; 〔二〕; ) TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-4E8C
+1F243;1F243;1F243;3014 5B89 3015;3014 5B89 3015; # (🉃; 🉃; 🉃; 〔安〕; 〔安〕; ) TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-5B89
+1F244;1F244;1F244;3014 70B9 3015;3014 70B9 3015; # (🉄; 🉄; 🉄; 〔点〕; 〔点〕; ) TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-70B9
+1F245;1F245;1F245;3014 6253 3015;3014 6253 3015; # (🉅; 🉅; 🉅; 〔打〕; 〔打〕; ) TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6253
+1F246;1F246;1F246;3014 76D7 3015;3014 76D7 3015; # (🉆; 🉆; 🉆; 〔盗〕; 〔盗〕; ) TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-76D7
+1F247;1F247;1F247;3014 52DD 3015;3014 52DD 3015; # (🉇; 🉇; 🉇; 〔å‹ã€•; 〔å‹ã€•; ) TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-52DD
+1F248;1F248;1F248;3014 6557 3015;3014 6557 3015; # (🉈; 🉈; 🉈; 〔敗〕; 〔敗〕; ) TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557
+1F250;1F250;1F250;5F97;5F97; # (ðŸ‰; ðŸ‰; ðŸ‰; å¾—; å¾—; ) CIRCLED IDEOGRAPH ADVANTAGE
+1F251;1F251;1F251;53EF;53EF; # (🉑; 🉑; 🉑; å¯; å¯; ) CIRCLED IDEOGRAPH ACCEPT
+1FBF0;1FBF0;1FBF0;0030;0030; # (🯰; 🯰; 🯰; 0; 0; ) SEGMENTED DIGIT ZERO
+1FBF1;1FBF1;1FBF1;0031;0031; # (🯱; 🯱; 🯱; 1; 1; ) SEGMENTED DIGIT ONE
+1FBF2;1FBF2;1FBF2;0032;0032; # (🯲; 🯲; 🯲; 2; 2; ) SEGMENTED DIGIT TWO
+1FBF3;1FBF3;1FBF3;0033;0033; # (🯳; 🯳; 🯳; 3; 3; ) SEGMENTED DIGIT THREE
+1FBF4;1FBF4;1FBF4;0034;0034; # (🯴; 🯴; 🯴; 4; 4; ) SEGMENTED DIGIT FOUR
+1FBF5;1FBF5;1FBF5;0035;0035; # (🯵; 🯵; 🯵; 5; 5; ) SEGMENTED DIGIT FIVE
+1FBF6;1FBF6;1FBF6;0036;0036; # (🯶; 🯶; 🯶; 6; 6; ) SEGMENTED DIGIT SIX
+1FBF7;1FBF7;1FBF7;0037;0037; # (🯷; 🯷; 🯷; 7; 7; ) SEGMENTED DIGIT SEVEN
+1FBF8;1FBF8;1FBF8;0038;0038; # (🯸; 🯸; 🯸; 8; 8; ) SEGMENTED DIGIT EIGHT
+1FBF9;1FBF9;1FBF9;0039;0039; # (🯹; 🯹; 🯹; 9; 9; ) SEGMENTED DIGIT NINE
+2F800;4E3D;4E3D;4E3D;4E3D; # (丽; 丽; 丽; 丽; 丽; ) CJK COMPATIBILITY IDEOGRAPH-2F800
+2F801;4E38;4E38;4E38;4E38; # (ð¯ ; 丸; 丸; 丸; 丸; ) CJK COMPATIBILITY IDEOGRAPH-2F801
+2F802;4E41;4E41;4E41;4E41; # (乁; ä¹; ä¹; ä¹; ä¹; ) CJK COMPATIBILITY IDEOGRAPH-2F802
+2F803;20122;20122;20122;20122; # (𠄢; 𠄢; 𠄢; 𠄢; 𠄢; ) CJK COMPATIBILITY IDEOGRAPH-2F803
+2F804;4F60;4F60;4F60;4F60; # (你; 你; 你; 你; 你; ) CJK COMPATIBILITY IDEOGRAPH-2F804
+2F805;4FAE;4FAE;4FAE;4FAE; # (侮; 侮; 侮; 侮; 侮; ) CJK COMPATIBILITY IDEOGRAPH-2F805
+2F806;4FBB;4FBB;4FBB;4FBB; # (侻; 侻; 侻; 侻; 侻; ) CJK COMPATIBILITY IDEOGRAPH-2F806
+2F807;5002;5002;5002;5002; # (倂; 倂; 倂; 倂; 倂; ) CJK COMPATIBILITY IDEOGRAPH-2F807
+2F808;507A;507A;507A;507A; # (偺; åº; åº; åº; åº; ) CJK COMPATIBILITY IDEOGRAPH-2F808
+2F809;5099;5099;5099;5099; # (備; 備; 備; 備; 備; ) CJK COMPATIBILITY IDEOGRAPH-2F809
+2F80A;50E7;50E7;50E7;50E7; # (僧; 僧; 僧; 僧; 僧; ) CJK COMPATIBILITY IDEOGRAPH-2F80A
+2F80B;50CF;50CF;50CF;50CF; # (像; åƒ; åƒ; åƒ; åƒ; ) CJK COMPATIBILITY IDEOGRAPH-2F80B
+2F80C;349E;349E;349E;349E; # (㒞; 㒞; 㒞; 㒞; 㒞; ) CJK COMPATIBILITY IDEOGRAPH-2F80C
+2F80D;2063A;2063A;2063A;2063A; # (ð¯ ; 𠘺; 𠘺; 𠘺; 𠘺; ) CJK COMPATIBILITY IDEOGRAPH-2F80D
+2F80E;514D;514D;514D;514D; # (免; å…; å…; å…; å…; ) CJK COMPATIBILITY IDEOGRAPH-2F80E
+2F80F;5154;5154;5154;5154; # (ð¯ ; å…”; å…”; å…”; å…”; ) CJK COMPATIBILITY IDEOGRAPH-2F80F
+2F810;5164;5164;5164;5164; # (ð¯ ; å…¤; å…¤; å…¤; å…¤; ) CJK COMPATIBILITY IDEOGRAPH-2F810
+2F811;5177;5177;5177;5177; # (具; 具; 具; 具; 具; ) CJK COMPATIBILITY IDEOGRAPH-2F811
+2F812;2051C;2051C;2051C;2051C; # (𠔜; 𠔜; 𠔜; 𠔜; 𠔜; ) CJK COMPATIBILITY IDEOGRAPH-2F812
+2F813;34B9;34B9;34B9;34B9; # (㒹; 㒹; 㒹; 㒹; 㒹; ) CJK COMPATIBILITY IDEOGRAPH-2F813
+2F814;5167;5167;5167;5167; # (內; 內; 內; 內; 內; ) CJK COMPATIBILITY IDEOGRAPH-2F814
+2F815;518D;518D;518D;518D; # (再; å†; å†; å†; å†; ) CJK COMPATIBILITY IDEOGRAPH-2F815
+2F816;2054B;2054B;2054B;2054B; # (𠕋; 𠕋; 𠕋; 𠕋; 𠕋; ) CJK COMPATIBILITY IDEOGRAPH-2F816
+2F817;5197;5197;5197;5197; # (冗; 冗; 冗; 冗; 冗; ) CJK COMPATIBILITY IDEOGRAPH-2F817
+2F818;51A4;51A4;51A4;51A4; # (冤; 冤; 冤; 冤; 冤; ) CJK COMPATIBILITY IDEOGRAPH-2F818
+2F819;4ECC;4ECC;4ECC;4ECC; # (仌; 仌; 仌; 仌; 仌; ) CJK COMPATIBILITY IDEOGRAPH-2F819
+2F81A;51AC;51AC;51AC;51AC; # (冬; 冬; 冬; 冬; 冬; ) CJK COMPATIBILITY IDEOGRAPH-2F81A
+2F81B;51B5;51B5;51B5;51B5; # (况; 况; 况; 况; 况; ) CJK COMPATIBILITY IDEOGRAPH-2F81B
+2F81C;291DF;291DF;291DF;291DF; # (𩇟; 𩇟; 𩇟; 𩇟; 𩇟; ) CJK COMPATIBILITY IDEOGRAPH-2F81C
+2F81D;51F5;51F5;51F5;51F5; # (ð¯ ; 凵; 凵; 凵; 凵; ) CJK COMPATIBILITY IDEOGRAPH-2F81D
+2F81E;5203;5203;5203;5203; # (刃; 刃; 刃; 刃; 刃; ) CJK COMPATIBILITY IDEOGRAPH-2F81E
+2F81F;34DF;34DF;34DF;34DF; # (㓟; 㓟; 㓟; 㓟; 㓟; ) CJK COMPATIBILITY IDEOGRAPH-2F81F
+2F820;523B;523B;523B;523B; # (刻; 刻; 刻; 刻; 刻; ) CJK COMPATIBILITY IDEOGRAPH-2F820
+2F821;5246;5246;5246;5246; # (剆; 剆; 剆; 剆; 剆; ) CJK COMPATIBILITY IDEOGRAPH-2F821
+2F822;5272;5272;5272;5272; # (割; 割; 割; 割; 割; ) CJK COMPATIBILITY IDEOGRAPH-2F822
+2F823;5277;5277;5277;5277; # (剷; 剷; 剷; 剷; 剷; ) CJK COMPATIBILITY IDEOGRAPH-2F823
+2F824;3515;3515;3515;3515; # (㔕; 㔕; 㔕; 㔕; 㔕; ) CJK COMPATIBILITY IDEOGRAPH-2F824
+2F825;52C7;52C7;52C7;52C7; # (勇; 勇; 勇; 勇; 勇; ) CJK COMPATIBILITY IDEOGRAPH-2F825
+2F826;52C9;52C9;52C9;52C9; # (勉; 勉; 勉; 勉; 勉; ) CJK COMPATIBILITY IDEOGRAPH-2F826
+2F827;52E4;52E4;52E4;52E4; # (勤; 勤; 勤; 勤; 勤; ) CJK COMPATIBILITY IDEOGRAPH-2F827
+2F828;52FA;52FA;52FA;52FA; # (勺; 勺; 勺; 勺; 勺; ) CJK COMPATIBILITY IDEOGRAPH-2F828
+2F829;5305;5305;5305;5305; # (包; 包; 包; 包; 包; ) CJK COMPATIBILITY IDEOGRAPH-2F829
+2F82A;5306;5306;5306;5306; # (匆; 匆; 匆; 匆; 匆; ) CJK COMPATIBILITY IDEOGRAPH-2F82A
+2F82B;5317;5317;5317;5317; # (北; 北; 北; 北; 北; ) CJK COMPATIBILITY IDEOGRAPH-2F82B
+2F82C;5349;5349;5349;5349; # (卉; å‰; å‰; å‰; å‰; ) CJK COMPATIBILITY IDEOGRAPH-2F82C
+2F82D;5351;5351;5351;5351; # (卑; å‘; å‘; å‘; å‘; ) CJK COMPATIBILITY IDEOGRAPH-2F82D
+2F82E;535A;535A;535A;535A; # (博; åš; åš; åš; åš; ) CJK COMPATIBILITY IDEOGRAPH-2F82E
+2F82F;5373;5373;5373;5373; # (即; å³; å³; å³; å³; ) CJK COMPATIBILITY IDEOGRAPH-2F82F
+2F830;537D;537D;537D;537D; # (卽; å½; å½; å½; å½; ) CJK COMPATIBILITY IDEOGRAPH-2F830
+2F831;537F;537F;537F;537F; # (卿; å¿; å¿; å¿; å¿; ) CJK COMPATIBILITY IDEOGRAPH-2F831
+2F832;537F;537F;537F;537F; # (卿; å¿; å¿; å¿; å¿; ) CJK COMPATIBILITY IDEOGRAPH-2F832
+2F833;537F;537F;537F;537F; # (卿; å¿; å¿; å¿; å¿; ) CJK COMPATIBILITY IDEOGRAPH-2F833
+2F834;20A2C;20A2C;20A2C;20A2C; # (𠨬; 𠨬; 𠨬; 𠨬; 𠨬; ) CJK COMPATIBILITY IDEOGRAPH-2F834
+2F835;7070;7070;7070;7070; # (灰; ç°; ç°; ç°; ç°; ) CJK COMPATIBILITY IDEOGRAPH-2F835
+2F836;53CA;53CA;53CA;53CA; # (及; åŠ; åŠ; åŠ; åŠ; ) CJK COMPATIBILITY IDEOGRAPH-2F836
+2F837;53DF;53DF;53DF;53DF; # (叟; åŸ; åŸ; åŸ; åŸ; ) CJK COMPATIBILITY IDEOGRAPH-2F837
+2F838;20B63;20B63;20B63;20B63; # (𠭣; 𠭣; 𠭣; 𠭣; 𠭣; ) CJK COMPATIBILITY IDEOGRAPH-2F838
+2F839;53EB;53EB;53EB;53EB; # (叫; å«; å«; å«; å«; ) CJK COMPATIBILITY IDEOGRAPH-2F839
+2F83A;53F1;53F1;53F1;53F1; # (叱; å±; å±; å±; å±; ) CJK COMPATIBILITY IDEOGRAPH-2F83A
+2F83B;5406;5406;5406;5406; # (吆; å†; å†; å†; å†; ) CJK COMPATIBILITY IDEOGRAPH-2F83B
+2F83C;549E;549E;549E;549E; # (咞; 咞; 咞; 咞; 咞; ) CJK COMPATIBILITY IDEOGRAPH-2F83C
+2F83D;5438;5438;5438;5438; # (吸; å¸; å¸; å¸; å¸; ) CJK COMPATIBILITY IDEOGRAPH-2F83D
+2F83E;5448;5448;5448;5448; # (呈; 呈; 呈; 呈; 呈; ) CJK COMPATIBILITY IDEOGRAPH-2F83E
+2F83F;5468;5468;5468;5468; # (周; 周; 周; 周; 周; ) CJK COMPATIBILITY IDEOGRAPH-2F83F
+2F840;54A2;54A2;54A2;54A2; # (咢; 咢; 咢; 咢; 咢; ) CJK COMPATIBILITY IDEOGRAPH-2F840
+2F841;54F6;54F6;54F6;54F6; # (ð¯¡; 哶; 哶; 哶; 哶; ) CJK COMPATIBILITY IDEOGRAPH-2F841
+2F842;5510;5510;5510;5510; # (唐; å”; å”; å”; å”; ) CJK COMPATIBILITY IDEOGRAPH-2F842
+2F843;5553;5553;5553;5553; # (啓; 啓; 啓; 啓; 啓; ) CJK COMPATIBILITY IDEOGRAPH-2F843
+2F844;5563;5563;5563;5563; # (啣; 啣; 啣; 啣; 啣; ) CJK COMPATIBILITY IDEOGRAPH-2F844
+2F845;5584;5584;5584;5584; # (善; 善; 善; 善; 善; ) CJK COMPATIBILITY IDEOGRAPH-2F845
+2F846;5584;5584;5584;5584; # (善; 善; 善; 善; 善; ) CJK COMPATIBILITY IDEOGRAPH-2F846
+2F847;5599;5599;5599;5599; # (喙; 喙; 喙; 喙; 喙; ) CJK COMPATIBILITY IDEOGRAPH-2F847
+2F848;55AB;55AB;55AB;55AB; # (喫; 喫; 喫; 喫; 喫; ) CJK COMPATIBILITY IDEOGRAPH-2F848
+2F849;55B3;55B3;55B3;55B3; # (喳; 喳; 喳; 喳; 喳; ) CJK COMPATIBILITY IDEOGRAPH-2F849
+2F84A;55C2;55C2;55C2;55C2; # (嗂; 嗂; 嗂; 嗂; 嗂; ) CJK COMPATIBILITY IDEOGRAPH-2F84A
+2F84B;5716;5716;5716;5716; # (圖; 圖; 圖; 圖; 圖; ) CJK COMPATIBILITY IDEOGRAPH-2F84B
+2F84C;5606;5606;5606;5606; # (嘆; 嘆; 嘆; 嘆; 嘆; ) CJK COMPATIBILITY IDEOGRAPH-2F84C
+2F84D;5717;5717;5717;5717; # (ð¯¡; 圗; 圗; 圗; 圗; ) CJK COMPATIBILITY IDEOGRAPH-2F84D
+2F84E;5651;5651;5651;5651; # (噑; 噑; 噑; 噑; 噑; ) CJK COMPATIBILITY IDEOGRAPH-2F84E
+2F84F;5674;5674;5674;5674; # (ð¯¡; å™´; å™´; å™´; å™´; ) CJK COMPATIBILITY IDEOGRAPH-2F84F
+2F850;5207;5207;5207;5207; # (ð¯¡; 切; 切; 切; 切; ) CJK COMPATIBILITY IDEOGRAPH-2F850
+2F851;58EE;58EE;58EE;58EE; # (壮; 壮; 壮; 壮; 壮; ) CJK COMPATIBILITY IDEOGRAPH-2F851
+2F852;57CE;57CE;57CE;57CE; # (城; 城; 城; 城; 城; ) CJK COMPATIBILITY IDEOGRAPH-2F852
+2F853;57F4;57F4;57F4;57F4; # (埴; 埴; 埴; 埴; 埴; ) CJK COMPATIBILITY IDEOGRAPH-2F853
+2F854;580D;580D;580D;580D; # (堍; å ; å ; å ; å ; ) CJK COMPATIBILITY IDEOGRAPH-2F854
+2F855;578B;578B;578B;578B; # (型; 型; 型; 型; 型; ) CJK COMPATIBILITY IDEOGRAPH-2F855
+2F856;5832;5832;5832;5832; # (堲; 堲; 堲; 堲; 堲; ) CJK COMPATIBILITY IDEOGRAPH-2F856
+2F857;5831;5831;5831;5831; # (報; 報; 報; 報; 報; ) CJK COMPATIBILITY IDEOGRAPH-2F857
+2F858;58AC;58AC;58AC;58AC; # (墬; 墬; 墬; 墬; 墬; ) CJK COMPATIBILITY IDEOGRAPH-2F858
+2F859;214E4;214E4;214E4;214E4; # (𡓤; 𡓤; 𡓤; 𡓤; 𡓤; ) CJK COMPATIBILITY IDEOGRAPH-2F859
+2F85A;58F2;58F2;58F2;58F2; # (売; 売; 売; 売; 売; ) CJK COMPATIBILITY IDEOGRAPH-2F85A
+2F85B;58F7;58F7;58F7;58F7; # (壷; 壷; 壷; 壷; 壷; ) CJK COMPATIBILITY IDEOGRAPH-2F85B
+2F85C;5906;5906;5906;5906; # (夆; 夆; 夆; 夆; 夆; ) CJK COMPATIBILITY IDEOGRAPH-2F85C
+2F85D;591A;591A;591A;591A; # (ð¯¡; 多; 多; 多; 多; ) CJK COMPATIBILITY IDEOGRAPH-2F85D
+2F85E;5922;5922;5922;5922; # (夢; 夢; 夢; 夢; 夢; ) CJK COMPATIBILITY IDEOGRAPH-2F85E
+2F85F;5962;5962;5962;5962; # (奢; 奢; 奢; 奢; 奢; ) CJK COMPATIBILITY IDEOGRAPH-2F85F
+2F860;216A8;216A8;216A8;216A8; # (𡚨; 𡚨; 𡚨; 𡚨; 𡚨; ) CJK COMPATIBILITY IDEOGRAPH-2F860
+2F861;216EA;216EA;216EA;216EA; # (𡛪; 𡛪; 𡛪; 𡛪; 𡛪; ) CJK COMPATIBILITY IDEOGRAPH-2F861
+2F862;59EC;59EC;59EC;59EC; # (姬; 姬; 姬; 姬; 姬; ) CJK COMPATIBILITY IDEOGRAPH-2F862
+2F863;5A1B;5A1B;5A1B;5A1B; # (娛; 娛; 娛; 娛; 娛; ) CJK COMPATIBILITY IDEOGRAPH-2F863
+2F864;5A27;5A27;5A27;5A27; # (娧; 娧; 娧; 娧; 娧; ) CJK COMPATIBILITY IDEOGRAPH-2F864
+2F865;59D8;59D8;59D8;59D8; # (姘; 姘; 姘; 姘; 姘; ) CJK COMPATIBILITY IDEOGRAPH-2F865
+2F866;5A66;5A66;5A66;5A66; # (婦; 婦; 婦; 婦; 婦; ) CJK COMPATIBILITY IDEOGRAPH-2F866
+2F867;36EE;36EE;36EE;36EE; # (㛮; 㛮; 㛮; 㛮; 㛮; ) CJK COMPATIBILITY IDEOGRAPH-2F867
+2F868;36FC;36FC;36FC;36FC; # (㛼; 㛼; 㛼; 㛼; 㛼; ) CJK COMPATIBILITY IDEOGRAPH-2F868
+2F869;5B08;5B08;5B08;5B08; # (嬈; 嬈; 嬈; 嬈; 嬈; ) CJK COMPATIBILITY IDEOGRAPH-2F869
+2F86A;5B3E;5B3E;5B3E;5B3E; # (嬾; 嬾; 嬾; 嬾; 嬾; ) CJK COMPATIBILITY IDEOGRAPH-2F86A
+2F86B;5B3E;5B3E;5B3E;5B3E; # (嬾; 嬾; 嬾; 嬾; 嬾; ) CJK COMPATIBILITY IDEOGRAPH-2F86B
+2F86C;219C8;219C8;219C8;219C8; # (𡧈; 𡧈; 𡧈; 𡧈; 𡧈; ) CJK COMPATIBILITY IDEOGRAPH-2F86C
+2F86D;5BC3;5BC3;5BC3;5BC3; # (寃; 寃; 寃; 寃; 寃; ) CJK COMPATIBILITY IDEOGRAPH-2F86D
+2F86E;5BD8;5BD8;5BD8;5BD8; # (寘; 寘; 寘; 寘; 寘; ) CJK COMPATIBILITY IDEOGRAPH-2F86E
+2F86F;5BE7;5BE7;5BE7;5BE7; # (寧; 寧; 寧; 寧; 寧; ) CJK COMPATIBILITY IDEOGRAPH-2F86F
+2F870;5BF3;5BF3;5BF3;5BF3; # (寳; 寳; 寳; 寳; 寳; ) CJK COMPATIBILITY IDEOGRAPH-2F870
+2F871;21B18;21B18;21B18;21B18; # (𡬘; 𡬘; 𡬘; 𡬘; 𡬘; ) CJK COMPATIBILITY IDEOGRAPH-2F871
+2F872;5BFF;5BFF;5BFF;5BFF; # (寿; 寿; 寿; 寿; 寿; ) CJK COMPATIBILITY IDEOGRAPH-2F872
+2F873;5C06;5C06;5C06;5C06; # (将; 将; 将; 将; 将; ) CJK COMPATIBILITY IDEOGRAPH-2F873
+2F874;5F53;5F53;5F53;5F53; # (当; 当; 当; 当; 当; ) CJK COMPATIBILITY IDEOGRAPH-2F874
+2F875;5C22;5C22;5C22;5C22; # (尢; 尢; 尢; 尢; 尢; ) CJK COMPATIBILITY IDEOGRAPH-2F875
+2F876;3781;3781;3781;3781; # (㞁; ãž; ãž; ãž; ãž; ) CJK COMPATIBILITY IDEOGRAPH-2F876
+2F877;5C60;5C60;5C60;5C60; # (屠; 屠; 屠; 屠; 屠; ) CJK COMPATIBILITY IDEOGRAPH-2F877
+2F878;5C6E;5C6E;5C6E;5C6E; # (屮; 屮; 屮; 屮; 屮; ) CJK COMPATIBILITY IDEOGRAPH-2F878
+2F879;5CC0;5CC0;5CC0;5CC0; # (峀; 峀; 峀; 峀; 峀; ) CJK COMPATIBILITY IDEOGRAPH-2F879
+2F87A;5C8D;5C8D;5C8D;5C8D; # (岍; å²; å²; å²; å²; ) CJK COMPATIBILITY IDEOGRAPH-2F87A
+2F87B;21DE4;21DE4;21DE4;21DE4; # (𡷤; 𡷤; 𡷤; 𡷤; 𡷤; ) CJK COMPATIBILITY IDEOGRAPH-2F87B
+2F87C;5D43;5D43;5D43;5D43; # (嵃; 嵃; 嵃; 嵃; 嵃; ) CJK COMPATIBILITY IDEOGRAPH-2F87C
+2F87D;21DE6;21DE6;21DE6;21DE6; # (𡷦; 𡷦; 𡷦; 𡷦; 𡷦; ) CJK COMPATIBILITY IDEOGRAPH-2F87D
+2F87E;5D6E;5D6E;5D6E;5D6E; # (嵮; 嵮; 嵮; 嵮; 嵮; ) CJK COMPATIBILITY IDEOGRAPH-2F87E
+2F87F;5D6B;5D6B;5D6B;5D6B; # (嵫; 嵫; 嵫; 嵫; 嵫; ) CJK COMPATIBILITY IDEOGRAPH-2F87F
+2F880;5D7C;5D7C;5D7C;5D7C; # (嵼; 嵼; 嵼; 嵼; 嵼; ) CJK COMPATIBILITY IDEOGRAPH-2F880
+2F881;5DE1;5DE1;5DE1;5DE1; # (ð¯¢; å·¡; å·¡; å·¡; å·¡; ) CJK COMPATIBILITY IDEOGRAPH-2F881
+2F882;5DE2;5DE2;5DE2;5DE2; # (巢; 巢; 巢; 巢; 巢; ) CJK COMPATIBILITY IDEOGRAPH-2F882
+2F883;382F;382F;382F;382F; # (㠯; 㠯; 㠯; 㠯; 㠯; ) CJK COMPATIBILITY IDEOGRAPH-2F883
+2F884;5DFD;5DFD;5DFD;5DFD; # (巽; 巽; 巽; 巽; 巽; ) CJK COMPATIBILITY IDEOGRAPH-2F884
+2F885;5E28;5E28;5E28;5E28; # (帨; 帨; 帨; 帨; 帨; ) CJK COMPATIBILITY IDEOGRAPH-2F885
+2F886;5E3D;5E3D;5E3D;5E3D; # (帽; 帽; 帽; 帽; 帽; ) CJK COMPATIBILITY IDEOGRAPH-2F886
+2F887;5E69;5E69;5E69;5E69; # (幩; 幩; 幩; 幩; 幩; ) CJK COMPATIBILITY IDEOGRAPH-2F887
+2F888;3862;3862;3862;3862; # (㡢; 㡢; 㡢; 㡢; 㡢; ) CJK COMPATIBILITY IDEOGRAPH-2F888
+2F889;22183;22183;22183;22183; # (𢆃; 𢆃; 𢆃; 𢆃; 𢆃; ) CJK COMPATIBILITY IDEOGRAPH-2F889
+2F88A;387C;387C;387C;387C; # (㡼; 㡼; 㡼; 㡼; 㡼; ) CJK COMPATIBILITY IDEOGRAPH-2F88A
+2F88B;5EB0;5EB0;5EB0;5EB0; # (庰; 庰; 庰; 庰; 庰; ) CJK COMPATIBILITY IDEOGRAPH-2F88B
+2F88C;5EB3;5EB3;5EB3;5EB3; # (庳; 庳; 庳; 庳; 庳; ) CJK COMPATIBILITY IDEOGRAPH-2F88C
+2F88D;5EB6;5EB6;5EB6;5EB6; # (ð¯¢; 庶; 庶; 庶; 庶; ) CJK COMPATIBILITY IDEOGRAPH-2F88D
+2F88E;5ECA;5ECA;5ECA;5ECA; # (廊; 廊; 廊; 廊; 廊; ) CJK COMPATIBILITY IDEOGRAPH-2F88E
+2F88F;2A392;2A392;2A392;2A392; # (ð¯¢; 𪎒; 𪎒; 𪎒; 𪎒; ) CJK COMPATIBILITY IDEOGRAPH-2F88F
+2F890;5EFE;5EFE;5EFE;5EFE; # (ð¯¢; 廾; 廾; 廾; 廾; ) CJK COMPATIBILITY IDEOGRAPH-2F890
+2F891;22331;22331;22331;22331; # (𢌱; 𢌱; 𢌱; 𢌱; 𢌱; ) CJK COMPATIBILITY IDEOGRAPH-2F891
+2F892;22331;22331;22331;22331; # (𢌱; 𢌱; 𢌱; 𢌱; 𢌱; ) CJK COMPATIBILITY IDEOGRAPH-2F892
+2F893;8201;8201;8201;8201; # (舁; èˆ; èˆ; èˆ; èˆ; ) CJK COMPATIBILITY IDEOGRAPH-2F893
+2F894;5F22;5F22;5F22;5F22; # (弢; 弢; 弢; 弢; 弢; ) CJK COMPATIBILITY IDEOGRAPH-2F894
+2F895;5F22;5F22;5F22;5F22; # (弢; 弢; 弢; 弢; 弢; ) CJK COMPATIBILITY IDEOGRAPH-2F895
+2F896;38C7;38C7;38C7;38C7; # (㣇; 㣇; 㣇; 㣇; 㣇; ) CJK COMPATIBILITY IDEOGRAPH-2F896
+2F897;232B8;232B8;232B8;232B8; # (𣊸; 𣊸; 𣊸; 𣊸; 𣊸; ) CJK COMPATIBILITY IDEOGRAPH-2F897
+2F898;261DA;261DA;261DA;261DA; # (𦇚; 𦇚; 𦇚; 𦇚; 𦇚; ) CJK COMPATIBILITY IDEOGRAPH-2F898
+2F899;5F62;5F62;5F62;5F62; # (形; 形; 形; 形; 形; ) CJK COMPATIBILITY IDEOGRAPH-2F899
+2F89A;5F6B;5F6B;5F6B;5F6B; # (彫; 彫; 彫; 彫; 彫; ) CJK COMPATIBILITY IDEOGRAPH-2F89A
+2F89B;38E3;38E3;38E3;38E3; # (㣣; 㣣; 㣣; 㣣; 㣣; ) CJK COMPATIBILITY IDEOGRAPH-2F89B
+2F89C;5F9A;5F9A;5F9A;5F9A; # (徚; 徚; 徚; 徚; 徚; ) CJK COMPATIBILITY IDEOGRAPH-2F89C
+2F89D;5FCD;5FCD;5FCD;5FCD; # (ð¯¢; å¿; å¿; å¿; å¿; ) CJK COMPATIBILITY IDEOGRAPH-2F89D
+2F89E;5FD7;5FD7;5FD7;5FD7; # (志; 志; 志; 志; 志; ) CJK COMPATIBILITY IDEOGRAPH-2F89E
+2F89F;5FF9;5FF9;5FF9;5FF9; # (忹; 忹; 忹; 忹; 忹; ) CJK COMPATIBILITY IDEOGRAPH-2F89F
+2F8A0;6081;6081;6081;6081; # (悁; æ‚; æ‚; æ‚; æ‚; ) CJK COMPATIBILITY IDEOGRAPH-2F8A0
+2F8A1;393A;393A;393A;393A; # (㤺; 㤺; 㤺; 㤺; 㤺; ) CJK COMPATIBILITY IDEOGRAPH-2F8A1
+2F8A2;391C;391C;391C;391C; # (㤜; 㤜; 㤜; 㤜; 㤜; ) CJK COMPATIBILITY IDEOGRAPH-2F8A2
+2F8A3;6094;6094;6094;6094; # (悔; 悔; 悔; 悔; 悔; ) CJK COMPATIBILITY IDEOGRAPH-2F8A3
+2F8A4;226D4;226D4;226D4;226D4; # (𢛔; 𢛔; 𢛔; 𢛔; 𢛔; ) CJK COMPATIBILITY IDEOGRAPH-2F8A4
+2F8A5;60C7;60C7;60C7;60C7; # (惇; 惇; 惇; 惇; 惇; ) CJK COMPATIBILITY IDEOGRAPH-2F8A5
+2F8A6;6148;6148;6148;6148; # (慈; 慈; 慈; 慈; 慈; ) CJK COMPATIBILITY IDEOGRAPH-2F8A6
+2F8A7;614C;614C;614C;614C; # (慌; 慌; 慌; 慌; 慌; ) CJK COMPATIBILITY IDEOGRAPH-2F8A7
+2F8A8;614E;614E;614E;614E; # (慎; 慎; 慎; 慎; 慎; ) CJK COMPATIBILITY IDEOGRAPH-2F8A8
+2F8A9;614C;614C;614C;614C; # (慌; 慌; 慌; 慌; 慌; ) CJK COMPATIBILITY IDEOGRAPH-2F8A9
+2F8AA;617A;617A;617A;617A; # (慺; 慺; 慺; 慺; 慺; ) CJK COMPATIBILITY IDEOGRAPH-2F8AA
+2F8AB;618E;618E;618E;618E; # (憎; 憎; 憎; 憎; 憎; ) CJK COMPATIBILITY IDEOGRAPH-2F8AB
+2F8AC;61B2;61B2;61B2;61B2; # (憲; 憲; 憲; 憲; 憲; ) CJK COMPATIBILITY IDEOGRAPH-2F8AC
+2F8AD;61A4;61A4;61A4;61A4; # (憤; 憤; 憤; 憤; 憤; ) CJK COMPATIBILITY IDEOGRAPH-2F8AD
+2F8AE;61AF;61AF;61AF;61AF; # (憯; 憯; 憯; 憯; 憯; ) CJK COMPATIBILITY IDEOGRAPH-2F8AE
+2F8AF;61DE;61DE;61DE;61DE; # (懞; 懞; 懞; 懞; 懞; ) CJK COMPATIBILITY IDEOGRAPH-2F8AF
+2F8B0;61F2;61F2;61F2;61F2; # (懲; 懲; 懲; 懲; 懲; ) CJK COMPATIBILITY IDEOGRAPH-2F8B0
+2F8B1;61F6;61F6;61F6;61F6; # (懶; 懶; 懶; 懶; 懶; ) CJK COMPATIBILITY IDEOGRAPH-2F8B1
+2F8B2;6210;6210;6210;6210; # (成; æˆ; æˆ; æˆ; æˆ; ) CJK COMPATIBILITY IDEOGRAPH-2F8B2
+2F8B3;621B;621B;621B;621B; # (戛; 戛; 戛; 戛; 戛; ) CJK COMPATIBILITY IDEOGRAPH-2F8B3
+2F8B4;625D;625D;625D;625D; # (扝; æ‰; æ‰; æ‰; æ‰; ) CJK COMPATIBILITY IDEOGRAPH-2F8B4
+2F8B5;62B1;62B1;62B1;62B1; # (抱; 抱; 抱; 抱; 抱; ) CJK COMPATIBILITY IDEOGRAPH-2F8B5
+2F8B6;62D4;62D4;62D4;62D4; # (拔; 拔; 拔; 拔; 拔; ) CJK COMPATIBILITY IDEOGRAPH-2F8B6
+2F8B7;6350;6350;6350;6350; # (捐; æ; æ; æ; æ; ) CJK COMPATIBILITY IDEOGRAPH-2F8B7
+2F8B8;22B0C;22B0C;22B0C;22B0C; # (𢬌; 𢬌; 𢬌; 𢬌; 𢬌; ) CJK COMPATIBILITY IDEOGRAPH-2F8B8
+2F8B9;633D;633D;633D;633D; # (挽; 挽; 挽; 挽; 挽; ) CJK COMPATIBILITY IDEOGRAPH-2F8B9
+2F8BA;62FC;62FC;62FC;62FC; # (拼; 拼; 拼; 拼; 拼; ) CJK COMPATIBILITY IDEOGRAPH-2F8BA
+2F8BB;6368;6368;6368;6368; # (捨; æ¨; æ¨; æ¨; æ¨; ) CJK COMPATIBILITY IDEOGRAPH-2F8BB
+2F8BC;6383;6383;6383;6383; # (掃; 掃; 掃; 掃; 掃; ) CJK COMPATIBILITY IDEOGRAPH-2F8BC
+2F8BD;63E4;63E4;63E4;63E4; # (揤; æ¤; æ¤; æ¤; æ¤; ) CJK COMPATIBILITY IDEOGRAPH-2F8BD
+2F8BE;22BF1;22BF1;22BF1;22BF1; # (𢯱; 𢯱; 𢯱; 𢯱; 𢯱; ) CJK COMPATIBILITY IDEOGRAPH-2F8BE
+2F8BF;6422;6422;6422;6422; # (搢; æ¢; æ¢; æ¢; æ¢; ) CJK COMPATIBILITY IDEOGRAPH-2F8BF
+2F8C0;63C5;63C5;63C5;63C5; # (揅; æ…; æ…; æ…; æ…; ) CJK COMPATIBILITY IDEOGRAPH-2F8C0
+2F8C1;63A9;63A9;63A9;63A9; # (ð¯£; 掩; 掩; 掩; 掩; ) CJK COMPATIBILITY IDEOGRAPH-2F8C1
+2F8C2;3A2E;3A2E;3A2E;3A2E; # (㨮; 㨮; 㨮; 㨮; 㨮; ) CJK COMPATIBILITY IDEOGRAPH-2F8C2
+2F8C3;6469;6469;6469;6469; # (摩; 摩; 摩; 摩; 摩; ) CJK COMPATIBILITY IDEOGRAPH-2F8C3
+2F8C4;647E;647E;647E;647E; # (摾; 摾; 摾; 摾; 摾; ) CJK COMPATIBILITY IDEOGRAPH-2F8C4
+2F8C5;649D;649D;649D;649D; # (撝; æ’; æ’; æ’; æ’; ) CJK COMPATIBILITY IDEOGRAPH-2F8C5
+2F8C6;6477;6477;6477;6477; # (摷; 摷; 摷; 摷; 摷; ) CJK COMPATIBILITY IDEOGRAPH-2F8C6
+2F8C7;3A6C;3A6C;3A6C;3A6C; # (㩬; 㩬; 㩬; 㩬; 㩬; ) CJK COMPATIBILITY IDEOGRAPH-2F8C7
+2F8C8;654F;654F;654F;654F; # (敏; æ•; æ•; æ•; æ•; ) CJK COMPATIBILITY IDEOGRAPH-2F8C8
+2F8C9;656C;656C;656C;656C; # (敬; 敬; 敬; 敬; 敬; ) CJK COMPATIBILITY IDEOGRAPH-2F8C9
+2F8CA;2300A;2300A;2300A;2300A; # (𣀊; 𣀊; 𣀊; 𣀊; 𣀊; ) CJK COMPATIBILITY IDEOGRAPH-2F8CA
+2F8CB;65E3;65E3;65E3;65E3; # (旣; 旣; 旣; 旣; 旣; ) CJK COMPATIBILITY IDEOGRAPH-2F8CB
+2F8CC;66F8;66F8;66F8;66F8; # (書; 書; 書; 書; 書; ) CJK COMPATIBILITY IDEOGRAPH-2F8CC
+2F8CD;6649;6649;6649;6649; # (ð¯£; 晉; 晉; 晉; 晉; ) CJK COMPATIBILITY IDEOGRAPH-2F8CD
+2F8CE;3B19;3B19;3B19;3B19; # (㬙; 㬙; 㬙; 㬙; 㬙; ) CJK COMPATIBILITY IDEOGRAPH-2F8CE
+2F8CF;6691;6691;6691;6691; # (ð¯£; æš‘; æš‘; æš‘; æš‘; ) CJK COMPATIBILITY IDEOGRAPH-2F8CF
+2F8D0;3B08;3B08;3B08;3B08; # (ð¯£; 㬈; 㬈; 㬈; 㬈; ) CJK COMPATIBILITY IDEOGRAPH-2F8D0
+2F8D1;3AE4;3AE4;3AE4;3AE4; # (㫤; 㫤; 㫤; 㫤; 㫤; ) CJK COMPATIBILITY IDEOGRAPH-2F8D1
+2F8D2;5192;5192;5192;5192; # (冒; 冒; 冒; 冒; 冒; ) CJK COMPATIBILITY IDEOGRAPH-2F8D2
+2F8D3;5195;5195;5195;5195; # (冕; 冕; 冕; 冕; 冕; ) CJK COMPATIBILITY IDEOGRAPH-2F8D3
+2F8D4;6700;6700;6700;6700; # (最; 最; 最; 最; 最; ) CJK COMPATIBILITY IDEOGRAPH-2F8D4
+2F8D5;669C;669C;669C;669C; # (暜; 暜; 暜; 暜; 暜; ) CJK COMPATIBILITY IDEOGRAPH-2F8D5
+2F8D6;80AD;80AD;80AD;80AD; # (肭; 肭; 肭; 肭; 肭; ) CJK COMPATIBILITY IDEOGRAPH-2F8D6
+2F8D7;43D9;43D9;43D9;43D9; # (䏙; ä™; ä™; ä™; ä™; ) CJK COMPATIBILITY IDEOGRAPH-2F8D7
+2F8D8;6717;6717;6717;6717; # (朗; 朗; 朗; 朗; 朗; ) CJK COMPATIBILITY IDEOGRAPH-2F8D8
+2F8D9;671B;671B;671B;671B; # (望; 望; 望; 望; 望; ) CJK COMPATIBILITY IDEOGRAPH-2F8D9
+2F8DA;6721;6721;6721;6721; # (朡; 朡; 朡; 朡; 朡; ) CJK COMPATIBILITY IDEOGRAPH-2F8DA
+2F8DB;675E;675E;675E;675E; # (杞; æž; æž; æž; æž; ) CJK COMPATIBILITY IDEOGRAPH-2F8DB
+2F8DC;6753;6753;6753;6753; # (杓; æ“; æ“; æ“; æ“; ) CJK COMPATIBILITY IDEOGRAPH-2F8DC
+2F8DD;233C3;233C3;233C3;233C3; # (ð¯£; ð£ƒ; ð£ƒ; ð£ƒ; ð£ƒ; ) CJK COMPATIBILITY IDEOGRAPH-2F8DD
+2F8DE;3B49;3B49;3B49;3B49; # (㭉; 㭉; 㭉; 㭉; 㭉; ) CJK COMPATIBILITY IDEOGRAPH-2F8DE
+2F8DF;67FA;67FA;67FA;67FA; # (柺; 柺; 柺; 柺; 柺; ) CJK COMPATIBILITY IDEOGRAPH-2F8DF
+2F8E0;6785;6785;6785;6785; # (枅; 枅; 枅; 枅; 枅; ) CJK COMPATIBILITY IDEOGRAPH-2F8E0
+2F8E1;6852;6852;6852;6852; # (桒; 桒; 桒; 桒; 桒; ) CJK COMPATIBILITY IDEOGRAPH-2F8E1
+2F8E2;6885;6885;6885;6885; # (梅; 梅; 梅; 梅; 梅; ) CJK COMPATIBILITY IDEOGRAPH-2F8E2
+2F8E3;2346D;2346D;2346D;2346D; # (𣑭; 𣑭; 𣑭; 𣑭; 𣑭; ) CJK COMPATIBILITY IDEOGRAPH-2F8E3
+2F8E4;688E;688E;688E;688E; # (梎; 梎; 梎; 梎; 梎; ) CJK COMPATIBILITY IDEOGRAPH-2F8E4
+2F8E5;681F;681F;681F;681F; # (栟; 栟; 栟; 栟; 栟; ) CJK COMPATIBILITY IDEOGRAPH-2F8E5
+2F8E6;6914;6914;6914;6914; # (椔; 椔; 椔; 椔; 椔; ) CJK COMPATIBILITY IDEOGRAPH-2F8E6
+2F8E7;3B9D;3B9D;3B9D;3B9D; # (㮝; ã®; ã®; ã®; ã®; ) CJK COMPATIBILITY IDEOGRAPH-2F8E7
+2F8E8;6942;6942;6942;6942; # (楂; 楂; 楂; 楂; 楂; ) CJK COMPATIBILITY IDEOGRAPH-2F8E8
+2F8E9;69A3;69A3;69A3;69A3; # (榣; 榣; 榣; 榣; 榣; ) CJK COMPATIBILITY IDEOGRAPH-2F8E9
+2F8EA;69EA;69EA;69EA;69EA; # (槪; 槪; 槪; 槪; 槪; ) CJK COMPATIBILITY IDEOGRAPH-2F8EA
+2F8EB;6AA8;6AA8;6AA8;6AA8; # (檨; 檨; 檨; 檨; 檨; ) CJK COMPATIBILITY IDEOGRAPH-2F8EB
+2F8EC;236A3;236A3;236A3;236A3; # (𣚣; 𣚣; 𣚣; 𣚣; 𣚣; ) CJK COMPATIBILITY IDEOGRAPH-2F8EC
+2F8ED;6ADB;6ADB;6ADB;6ADB; # (櫛; 櫛; 櫛; 櫛; 櫛; ) CJK COMPATIBILITY IDEOGRAPH-2F8ED
+2F8EE;3C18;3C18;3C18;3C18; # (㰘; 㰘; 㰘; 㰘; 㰘; ) CJK COMPATIBILITY IDEOGRAPH-2F8EE
+2F8EF;6B21;6B21;6B21;6B21; # (次; 次; 次; 次; 次; ) CJK COMPATIBILITY IDEOGRAPH-2F8EF
+2F8F0;238A7;238A7;238A7;238A7; # (𣢧; 𣢧; 𣢧; 𣢧; 𣢧; ) CJK COMPATIBILITY IDEOGRAPH-2F8F0
+2F8F1;6B54;6B54;6B54;6B54; # (歔; 歔; 歔; 歔; 歔; ) CJK COMPATIBILITY IDEOGRAPH-2F8F1
+2F8F2;3C4E;3C4E;3C4E;3C4E; # (㱎; 㱎; 㱎; 㱎; 㱎; ) CJK COMPATIBILITY IDEOGRAPH-2F8F2
+2F8F3;6B72;6B72;6B72;6B72; # (歲; 歲; 歲; 歲; 歲; ) CJK COMPATIBILITY IDEOGRAPH-2F8F3
+2F8F4;6B9F;6B9F;6B9F;6B9F; # (殟; 殟; 殟; 殟; 殟; ) CJK COMPATIBILITY IDEOGRAPH-2F8F4
+2F8F5;6BBA;6BBA;6BBA;6BBA; # (殺; 殺; 殺; 殺; 殺; ) CJK COMPATIBILITY IDEOGRAPH-2F8F5
+2F8F6;6BBB;6BBB;6BBB;6BBB; # (殻; 殻; 殻; 殻; 殻; ) CJK COMPATIBILITY IDEOGRAPH-2F8F6
+2F8F7;23A8D;23A8D;23A8D;23A8D; # (𣪍; ð£ª; ð£ª; ð£ª; ð£ª; ) CJK COMPATIBILITY IDEOGRAPH-2F8F7
+2F8F8;21D0B;21D0B;21D0B;21D0B; # (𡴋; 𡴋; 𡴋; 𡴋; 𡴋; ) CJK COMPATIBILITY IDEOGRAPH-2F8F8
+2F8F9;23AFA;23AFA;23AFA;23AFA; # (𣫺; 𣫺; 𣫺; 𣫺; 𣫺; ) CJK COMPATIBILITY IDEOGRAPH-2F8F9
+2F8FA;6C4E;6C4E;6C4E;6C4E; # (汎; 汎; 汎; 汎; 汎; ) CJK COMPATIBILITY IDEOGRAPH-2F8FA
+2F8FB;23CBC;23CBC;23CBC;23CBC; # (𣲼; 𣲼; 𣲼; 𣲼; 𣲼; ) CJK COMPATIBILITY IDEOGRAPH-2F8FB
+2F8FC;6CBF;6CBF;6CBF;6CBF; # (沿; 沿; 沿; 沿; 沿; ) CJK COMPATIBILITY IDEOGRAPH-2F8FC
+2F8FD;6CCD;6CCD;6CCD;6CCD; # (泍; æ³; æ³; æ³; æ³; ) CJK COMPATIBILITY IDEOGRAPH-2F8FD
+2F8FE;6C67;6C67;6C67;6C67; # (汧; 汧; 汧; 汧; 汧; ) CJK COMPATIBILITY IDEOGRAPH-2F8FE
+2F8FF;6D16;6D16;6D16;6D16; # (洖; 洖; 洖; 洖; 洖; ) CJK COMPATIBILITY IDEOGRAPH-2F8FF
+2F900;6D3E;6D3E;6D3E;6D3E; # (派; 派; 派; 派; 派; ) CJK COMPATIBILITY IDEOGRAPH-2F900
+2F901;6D77;6D77;6D77;6D77; # (ð¯¤; æµ·; æµ·; æµ·; æµ·; ) CJK COMPATIBILITY IDEOGRAPH-2F901
+2F902;6D41;6D41;6D41;6D41; # (流; æµ; æµ; æµ; æµ; ) CJK COMPATIBILITY IDEOGRAPH-2F902
+2F903;6D69;6D69;6D69;6D69; # (浩; 浩; 浩; 浩; 浩; ) CJK COMPATIBILITY IDEOGRAPH-2F903
+2F904;6D78;6D78;6D78;6D78; # (浸; 浸; 浸; 浸; 浸; ) CJK COMPATIBILITY IDEOGRAPH-2F904
+2F905;6D85;6D85;6D85;6D85; # (涅; 涅; 涅; 涅; 涅; ) CJK COMPATIBILITY IDEOGRAPH-2F905
+2F906;23D1E;23D1E;23D1E;23D1E; # (𣴞; 𣴞; 𣴞; 𣴞; 𣴞; ) CJK COMPATIBILITY IDEOGRAPH-2F906
+2F907;6D34;6D34;6D34;6D34; # (洴; 洴; 洴; 洴; 洴; ) CJK COMPATIBILITY IDEOGRAPH-2F907
+2F908;6E2F;6E2F;6E2F;6E2F; # (港; 港; 港; 港; 港; ) CJK COMPATIBILITY IDEOGRAPH-2F908
+2F909;6E6E;6E6E;6E6E;6E6E; # (湮; 湮; 湮; 湮; 湮; ) CJK COMPATIBILITY IDEOGRAPH-2F909
+2F90A;3D33;3D33;3D33;3D33; # (㴳; 㴳; 㴳; 㴳; 㴳; ) CJK COMPATIBILITY IDEOGRAPH-2F90A
+2F90B;6ECB;6ECB;6ECB;6ECB; # (滋; 滋; 滋; 滋; 滋; ) CJK COMPATIBILITY IDEOGRAPH-2F90B
+2F90C;6EC7;6EC7;6EC7;6EC7; # (滇; 滇; 滇; 滇; 滇; ) CJK COMPATIBILITY IDEOGRAPH-2F90C
+2F90D;23ED1;23ED1;23ED1;23ED1; # (ð¯¤; 𣻑; 𣻑; 𣻑; 𣻑; ) CJK COMPATIBILITY IDEOGRAPH-2F90D
+2F90E;6DF9;6DF9;6DF9;6DF9; # (淹; 淹; 淹; 淹; 淹; ) CJK COMPATIBILITY IDEOGRAPH-2F90E
+2F90F;6F6E;6F6E;6F6E;6F6E; # (ð¯¤; æ½®; æ½®; æ½®; æ½®; ) CJK COMPATIBILITY IDEOGRAPH-2F90F
+2F910;23F5E;23F5E;23F5E;23F5E; # (ð¯¤; 𣽞; 𣽞; 𣽞; 𣽞; ) CJK COMPATIBILITY IDEOGRAPH-2F910
+2F911;23F8E;23F8E;23F8E;23F8E; # (𣾎; 𣾎; 𣾎; 𣾎; 𣾎; ) CJK COMPATIBILITY IDEOGRAPH-2F911
+2F912;6FC6;6FC6;6FC6;6FC6; # (濆; 濆; 濆; 濆; 濆; ) CJK COMPATIBILITY IDEOGRAPH-2F912
+2F913;7039;7039;7039;7039; # (瀹; 瀹; 瀹; 瀹; 瀹; ) CJK COMPATIBILITY IDEOGRAPH-2F913
+2F914;701E;701E;701E;701E; # (瀞; 瀞; 瀞; 瀞; 瀞; ) CJK COMPATIBILITY IDEOGRAPH-2F914
+2F915;701B;701B;701B;701B; # (瀛; 瀛; 瀛; 瀛; 瀛; ) CJK COMPATIBILITY IDEOGRAPH-2F915
+2F916;3D96;3D96;3D96;3D96; # (㶖; 㶖; 㶖; 㶖; 㶖; ) CJK COMPATIBILITY IDEOGRAPH-2F916
+2F917;704A;704A;704A;704A; # (灊; çŠ; çŠ; çŠ; çŠ; ) CJK COMPATIBILITY IDEOGRAPH-2F917
+2F918;707D;707D;707D;707D; # (災; ç½; ç½; ç½; ç½; ) CJK COMPATIBILITY IDEOGRAPH-2F918
+2F919;7077;7077;7077;7077; # (灷; ç·; ç·; ç·; ç·; ) CJK COMPATIBILITY IDEOGRAPH-2F919
+2F91A;70AD;70AD;70AD;70AD; # (炭; 炭; 炭; 炭; 炭; ) CJK COMPATIBILITY IDEOGRAPH-2F91A
+2F91B;20525;20525;20525;20525; # (𠔥; 𠔥; 𠔥; 𠔥; 𠔥; ) CJK COMPATIBILITY IDEOGRAPH-2F91B
+2F91C;7145;7145;7145;7145; # (煅; 煅; 煅; 煅; 煅; ) CJK COMPATIBILITY IDEOGRAPH-2F91C
+2F91D;24263;24263;24263;24263; # (ð¯¤; 𤉣; 𤉣; 𤉣; 𤉣; ) CJK COMPATIBILITY IDEOGRAPH-2F91D
+2F91E;719C;719C;719C;719C; # (熜; 熜; 熜; 熜; 熜; ) CJK COMPATIBILITY IDEOGRAPH-2F91E
+2F91F;243AB;243AB;243AB;243AB; # (𤎫; 𤎫; 𤎫; 𤎫; 𤎫; ) CJK COMPATIBILITY IDEOGRAPH-2F91F
+2F920;7228;7228;7228;7228; # (爨; 爨; 爨; 爨; 爨; ) CJK COMPATIBILITY IDEOGRAPH-2F920
+2F921;7235;7235;7235;7235; # (爵; 爵; 爵; 爵; 爵; ) CJK COMPATIBILITY IDEOGRAPH-2F921
+2F922;7250;7250;7250;7250; # (牐; ç‰; ç‰; ç‰; ç‰; ) CJK COMPATIBILITY IDEOGRAPH-2F922
+2F923;24608;24608;24608;24608; # (𤘈; 𤘈; 𤘈; 𤘈; 𤘈; ) CJK COMPATIBILITY IDEOGRAPH-2F923
+2F924;7280;7280;7280;7280; # (犀; 犀; 犀; 犀; 犀; ) CJK COMPATIBILITY IDEOGRAPH-2F924
+2F925;7295;7295;7295;7295; # (犕; 犕; 犕; 犕; 犕; ) CJK COMPATIBILITY IDEOGRAPH-2F925
+2F926;24735;24735;24735;24735; # (𤜵; 𤜵; 𤜵; 𤜵; 𤜵; ) CJK COMPATIBILITY IDEOGRAPH-2F926
+2F927;24814;24814;24814;24814; # (𤠔; 𤠔; 𤠔; 𤠔; 𤠔; ) CJK COMPATIBILITY IDEOGRAPH-2F927
+2F928;737A;737A;737A;737A; # (獺; çº; çº; çº; çº; ) CJK COMPATIBILITY IDEOGRAPH-2F928
+2F929;738B;738B;738B;738B; # (王; 王; 王; 王; 王; ) CJK COMPATIBILITY IDEOGRAPH-2F929
+2F92A;3EAC;3EAC;3EAC;3EAC; # (㺬; 㺬; 㺬; 㺬; 㺬; ) CJK COMPATIBILITY IDEOGRAPH-2F92A
+2F92B;73A5;73A5;73A5;73A5; # (玥; 玥; 玥; 玥; 玥; ) CJK COMPATIBILITY IDEOGRAPH-2F92B
+2F92C;3EB8;3EB8;3EB8;3EB8; # (㺸; 㺸; 㺸; 㺸; 㺸; ) CJK COMPATIBILITY IDEOGRAPH-2F92C
+2F92D;3EB8;3EB8;3EB8;3EB8; # (㺸; 㺸; 㺸; 㺸; 㺸; ) CJK COMPATIBILITY IDEOGRAPH-2F92D
+2F92E;7447;7447;7447;7447; # (瑇; 瑇; 瑇; 瑇; 瑇; ) CJK COMPATIBILITY IDEOGRAPH-2F92E
+2F92F;745C;745C;745C;745C; # (瑜; 瑜; 瑜; 瑜; 瑜; ) CJK COMPATIBILITY IDEOGRAPH-2F92F
+2F930;7471;7471;7471;7471; # (瑱; 瑱; 瑱; 瑱; 瑱; ) CJK COMPATIBILITY IDEOGRAPH-2F930
+2F931;7485;7485;7485;7485; # (璅; 璅; 璅; 璅; 璅; ) CJK COMPATIBILITY IDEOGRAPH-2F931
+2F932;74CA;74CA;74CA;74CA; # (瓊; 瓊; 瓊; 瓊; 瓊; ) CJK COMPATIBILITY IDEOGRAPH-2F932
+2F933;3F1B;3F1B;3F1B;3F1B; # (㼛; 㼛; 㼛; 㼛; 㼛; ) CJK COMPATIBILITY IDEOGRAPH-2F933
+2F934;7524;7524;7524;7524; # (甤; 甤; 甤; 甤; 甤; ) CJK COMPATIBILITY IDEOGRAPH-2F934
+2F935;24C36;24C36;24C36;24C36; # (𤰶; 𤰶; 𤰶; 𤰶; 𤰶; ) CJK COMPATIBILITY IDEOGRAPH-2F935
+2F936;753E;753E;753E;753E; # (甾; 甾; 甾; 甾; 甾; ) CJK COMPATIBILITY IDEOGRAPH-2F936
+2F937;24C92;24C92;24C92;24C92; # (𤲒; 𤲒; 𤲒; 𤲒; 𤲒; ) CJK COMPATIBILITY IDEOGRAPH-2F937
+2F938;7570;7570;7570;7570; # (異; 異; 異; 異; 異; ) CJK COMPATIBILITY IDEOGRAPH-2F938
+2F939;2219F;2219F;2219F;2219F; # (𢆟; 𢆟; 𢆟; 𢆟; 𢆟; ) CJK COMPATIBILITY IDEOGRAPH-2F939
+2F93A;7610;7610;7610;7610; # (瘐; ç˜; ç˜; ç˜; ç˜; ) CJK COMPATIBILITY IDEOGRAPH-2F93A
+2F93B;24FA1;24FA1;24FA1;24FA1; # (𤾡; 𤾡; 𤾡; 𤾡; 𤾡; ) CJK COMPATIBILITY IDEOGRAPH-2F93B
+2F93C;24FB8;24FB8;24FB8;24FB8; # (𤾸; 𤾸; 𤾸; 𤾸; 𤾸; ) CJK COMPATIBILITY IDEOGRAPH-2F93C
+2F93D;25044;25044;25044;25044; # (𥁄; ð¥„; ð¥„; ð¥„; ð¥„; ) CJK COMPATIBILITY IDEOGRAPH-2F93D
+2F93E;3FFC;3FFC;3FFC;3FFC; # (㿼; 㿼; 㿼; 㿼; 㿼; ) CJK COMPATIBILITY IDEOGRAPH-2F93E
+2F93F;4008;4008;4008;4008; # (䀈; 䀈; 䀈; 䀈; 䀈; ) CJK COMPATIBILITY IDEOGRAPH-2F93F
+2F940;76F4;76F4;76F4;76F4; # (直; 直; 直; 直; 直; ) CJK COMPATIBILITY IDEOGRAPH-2F940
+2F941;250F3;250F3;250F3;250F3; # (ð¯¥; 𥃳; 𥃳; 𥃳; 𥃳; ) CJK COMPATIBILITY IDEOGRAPH-2F941
+2F942;250F2;250F2;250F2;250F2; # (𥃲; 𥃲; 𥃲; 𥃲; 𥃲; ) CJK COMPATIBILITY IDEOGRAPH-2F942
+2F943;25119;25119;25119;25119; # (𥄙; 𥄙; 𥄙; 𥄙; 𥄙; ) CJK COMPATIBILITY IDEOGRAPH-2F943
+2F944;25133;25133;25133;25133; # (𥄳; 𥄳; 𥄳; 𥄳; 𥄳; ) CJK COMPATIBILITY IDEOGRAPH-2F944
+2F945;771E;771E;771E;771E; # (眞; 眞; 眞; 眞; 眞; ) CJK COMPATIBILITY IDEOGRAPH-2F945
+2F946;771F;771F;771F;771F; # (真; 真; 真; 真; 真; ) CJK COMPATIBILITY IDEOGRAPH-2F946
+2F947;771F;771F;771F;771F; # (真; 真; 真; 真; 真; ) CJK COMPATIBILITY IDEOGRAPH-2F947
+2F948;774A;774A;774A;774A; # (睊; çŠ; çŠ; çŠ; çŠ; ) CJK COMPATIBILITY IDEOGRAPH-2F948
+2F949;4039;4039;4039;4039; # (䀹; 䀹; 䀹; 䀹; 䀹; ) CJK COMPATIBILITY IDEOGRAPH-2F949
+2F94A;778B;778B;778B;778B; # (瞋; 瞋; 瞋; 瞋; 瞋; ) CJK COMPATIBILITY IDEOGRAPH-2F94A
+2F94B;4046;4046;4046;4046; # (䁆; ä†; ä†; ä†; ä†; ) CJK COMPATIBILITY IDEOGRAPH-2F94B
+2F94C;4096;4096;4096;4096; # (䂖; 䂖; 䂖; 䂖; 䂖; ) CJK COMPATIBILITY IDEOGRAPH-2F94C
+2F94D;2541D;2541D;2541D;2541D; # (ð¯¥; ð¥; ð¥; ð¥; ð¥; ) CJK COMPATIBILITY IDEOGRAPH-2F94D
+2F94E;784E;784E;784E;784E; # (硎; 硎; 硎; 硎; 硎; ) CJK COMPATIBILITY IDEOGRAPH-2F94E
+2F94F;788C;788C;788C;788C; # (ð¯¥; 碌; 碌; 碌; 碌; ) CJK COMPATIBILITY IDEOGRAPH-2F94F
+2F950;78CC;78CC;78CC;78CC; # (ð¯¥; 磌; 磌; 磌; 磌; ) CJK COMPATIBILITY IDEOGRAPH-2F950
+2F951;40E3;40E3;40E3;40E3; # (䃣; 䃣; 䃣; 䃣; 䃣; ) CJK COMPATIBILITY IDEOGRAPH-2F951
+2F952;25626;25626;25626;25626; # (𥘦; 𥘦; 𥘦; 𥘦; 𥘦; ) CJK COMPATIBILITY IDEOGRAPH-2F952
+2F953;7956;7956;7956;7956; # (祖; 祖; 祖; 祖; 祖; ) CJK COMPATIBILITY IDEOGRAPH-2F953
+2F954;2569A;2569A;2569A;2569A; # (𥚚; 𥚚; 𥚚; 𥚚; 𥚚; ) CJK COMPATIBILITY IDEOGRAPH-2F954
+2F955;256C5;256C5;256C5;256C5; # (𥛅; 𥛅; 𥛅; 𥛅; 𥛅; ) CJK COMPATIBILITY IDEOGRAPH-2F955
+2F956;798F;798F;798F;798F; # (福; ç¦; ç¦; ç¦; ç¦; ) CJK COMPATIBILITY IDEOGRAPH-2F956
+2F957;79EB;79EB;79EB;79EB; # (秫; 秫; 秫; 秫; 秫; ) CJK COMPATIBILITY IDEOGRAPH-2F957
+2F958;412F;412F;412F;412F; # (䄯; 䄯; 䄯; 䄯; 䄯; ) CJK COMPATIBILITY IDEOGRAPH-2F958
+2F959;7A40;7A40;7A40;7A40; # (穀; 穀; 穀; 穀; 穀; ) CJK COMPATIBILITY IDEOGRAPH-2F959
+2F95A;7A4A;7A4A;7A4A;7A4A; # (穊; 穊; 穊; 穊; 穊; ) CJK COMPATIBILITY IDEOGRAPH-2F95A
+2F95B;7A4F;7A4F;7A4F;7A4F; # (穏; ç©; ç©; ç©; ç©; ) CJK COMPATIBILITY IDEOGRAPH-2F95B
+2F95C;2597C;2597C;2597C;2597C; # (𥥼; 𥥼; 𥥼; 𥥼; 𥥼; ) CJK COMPATIBILITY IDEOGRAPH-2F95C
+2F95D;25AA7;25AA7;25AA7;25AA7; # (ð¯¥; 𥪧; 𥪧; 𥪧; 𥪧; ) CJK COMPATIBILITY IDEOGRAPH-2F95D
+2F95E;25AA7;25AA7;25AA7;25AA7; # (𥪧; 𥪧; 𥪧; 𥪧; 𥪧; ) CJK COMPATIBILITY IDEOGRAPH-2F95E
+2F95F;7AEE;7AEE;7AEE;7AEE; # (竮; 竮; 竮; 竮; 竮; ) CJK COMPATIBILITY IDEOGRAPH-2F95F
+2F960;4202;4202;4202;4202; # (䈂; 䈂; 䈂; 䈂; 䈂; ) CJK COMPATIBILITY IDEOGRAPH-2F960
+2F961;25BAB;25BAB;25BAB;25BAB; # (𥮫; 𥮫; 𥮫; 𥮫; 𥮫; ) CJK COMPATIBILITY IDEOGRAPH-2F961
+2F962;7BC6;7BC6;7BC6;7BC6; # (篆; 篆; 篆; 篆; 篆; ) CJK COMPATIBILITY IDEOGRAPH-2F962
+2F963;7BC9;7BC9;7BC9;7BC9; # (築; 築; 築; 築; 築; ) CJK COMPATIBILITY IDEOGRAPH-2F963
+2F964;4227;4227;4227;4227; # (䈧; 䈧; 䈧; 䈧; 䈧; ) CJK COMPATIBILITY IDEOGRAPH-2F964
+2F965;25C80;25C80;25C80;25C80; # (𥲀; 𥲀; 𥲀; 𥲀; 𥲀; ) CJK COMPATIBILITY IDEOGRAPH-2F965
+2F966;7CD2;7CD2;7CD2;7CD2; # (糒; 糒; 糒; 糒; 糒; ) CJK COMPATIBILITY IDEOGRAPH-2F966
+2F967;42A0;42A0;42A0;42A0; # (䊠; 䊠; 䊠; 䊠; 䊠; ) CJK COMPATIBILITY IDEOGRAPH-2F967
+2F968;7CE8;7CE8;7CE8;7CE8; # (糨; 糨; 糨; 糨; 糨; ) CJK COMPATIBILITY IDEOGRAPH-2F968
+2F969;7CE3;7CE3;7CE3;7CE3; # (糣; 糣; 糣; 糣; 糣; ) CJK COMPATIBILITY IDEOGRAPH-2F969
+2F96A;7D00;7D00;7D00;7D00; # (紀; 紀; 紀; 紀; 紀; ) CJK COMPATIBILITY IDEOGRAPH-2F96A
+2F96B;25F86;25F86;25F86;25F86; # (𥾆; 𥾆; 𥾆; 𥾆; 𥾆; ) CJK COMPATIBILITY IDEOGRAPH-2F96B
+2F96C;7D63;7D63;7D63;7D63; # (絣; 絣; 絣; 絣; 絣; ) CJK COMPATIBILITY IDEOGRAPH-2F96C
+2F96D;4301;4301;4301;4301; # (䌁; äŒ; äŒ; äŒ; äŒ; ) CJK COMPATIBILITY IDEOGRAPH-2F96D
+2F96E;7DC7;7DC7;7DC7;7DC7; # (緇; 緇; 緇; 緇; 緇; ) CJK COMPATIBILITY IDEOGRAPH-2F96E
+2F96F;7E02;7E02;7E02;7E02; # (縂; 縂; 縂; 縂; 縂; ) CJK COMPATIBILITY IDEOGRAPH-2F96F
+2F970;7E45;7E45;7E45;7E45; # (繅; 繅; 繅; 繅; 繅; ) CJK COMPATIBILITY IDEOGRAPH-2F970
+2F971;4334;4334;4334;4334; # (䌴; 䌴; 䌴; 䌴; 䌴; ) CJK COMPATIBILITY IDEOGRAPH-2F971
+2F972;26228;26228;26228;26228; # (𦈨; 𦈨; 𦈨; 𦈨; 𦈨; ) CJK COMPATIBILITY IDEOGRAPH-2F972
+2F973;26247;26247;26247;26247; # (𦉇; 𦉇; 𦉇; 𦉇; 𦉇; ) CJK COMPATIBILITY IDEOGRAPH-2F973
+2F974;4359;4359;4359;4359; # (䍙; ä™; ä™; ä™; ä™; ) CJK COMPATIBILITY IDEOGRAPH-2F974
+2F975;262D9;262D9;262D9;262D9; # (𦋙; 𦋙; 𦋙; 𦋙; 𦋙; ) CJK COMPATIBILITY IDEOGRAPH-2F975
+2F976;7F7A;7F7A;7F7A;7F7A; # (罺; 罺; 罺; 罺; 罺; ) CJK COMPATIBILITY IDEOGRAPH-2F976
+2F977;2633E;2633E;2633E;2633E; # (𦌾; 𦌾; 𦌾; 𦌾; 𦌾; ) CJK COMPATIBILITY IDEOGRAPH-2F977
+2F978;7F95;7F95;7F95;7F95; # (羕; 羕; 羕; 羕; 羕; ) CJK COMPATIBILITY IDEOGRAPH-2F978
+2F979;7FFA;7FFA;7FFA;7FFA; # (翺; 翺; 翺; 翺; 翺; ) CJK COMPATIBILITY IDEOGRAPH-2F979
+2F97A;8005;8005;8005;8005; # (者; 者; 者; 者; 者; ) CJK COMPATIBILITY IDEOGRAPH-2F97A
+2F97B;264DA;264DA;264DA;264DA; # (𦓚; 𦓚; 𦓚; 𦓚; 𦓚; ) CJK COMPATIBILITY IDEOGRAPH-2F97B
+2F97C;26523;26523;26523;26523; # (𦔣; 𦔣; 𦔣; 𦔣; 𦔣; ) CJK COMPATIBILITY IDEOGRAPH-2F97C
+2F97D;8060;8060;8060;8060; # (聠; è ; è ; è ; è ; ) CJK COMPATIBILITY IDEOGRAPH-2F97D
+2F97E;265A8;265A8;265A8;265A8; # (𦖨; 𦖨; 𦖨; 𦖨; 𦖨; ) CJK COMPATIBILITY IDEOGRAPH-2F97E
+2F97F;8070;8070;8070;8070; # (聰; è°; è°; è°; è°; ) CJK COMPATIBILITY IDEOGRAPH-2F97F
+2F980;2335F;2335F;2335F;2335F; # (𣍟; ð£Ÿ; ð£Ÿ; ð£Ÿ; ð£Ÿ; ) CJK COMPATIBILITY IDEOGRAPH-2F980
+2F981;43D5;43D5;43D5;43D5; # (ð¯¦; ä•; ä•; ä•; ä•; ) CJK COMPATIBILITY IDEOGRAPH-2F981
+2F982;80B2;80B2;80B2;80B2; # (育; 育; 育; 育; 育; ) CJK COMPATIBILITY IDEOGRAPH-2F982
+2F983;8103;8103;8103;8103; # (脃; 脃; 脃; 脃; 脃; ) CJK COMPATIBILITY IDEOGRAPH-2F983
+2F984;440B;440B;440B;440B; # (䐋; ä‹; ä‹; ä‹; ä‹; ) CJK COMPATIBILITY IDEOGRAPH-2F984
+2F985;813E;813E;813E;813E; # (脾; 脾; 脾; 脾; 脾; ) CJK COMPATIBILITY IDEOGRAPH-2F985
+2F986;5AB5;5AB5;5AB5;5AB5; # (媵; 媵; 媵; 媵; 媵; ) CJK COMPATIBILITY IDEOGRAPH-2F986
+2F987;267A7;267A7;267A7;267A7; # (𦞧; 𦞧; 𦞧; 𦞧; 𦞧; ) CJK COMPATIBILITY IDEOGRAPH-2F987
+2F988;267B5;267B5;267B5;267B5; # (𦞵; 𦞵; 𦞵; 𦞵; 𦞵; ) CJK COMPATIBILITY IDEOGRAPH-2F988
+2F989;23393;23393;23393;23393; # (𣎓; 𣎓; 𣎓; 𣎓; 𣎓; ) CJK COMPATIBILITY IDEOGRAPH-2F989
+2F98A;2339C;2339C;2339C;2339C; # (𣎜; 𣎜; 𣎜; 𣎜; 𣎜; ) CJK COMPATIBILITY IDEOGRAPH-2F98A
+2F98B;8201;8201;8201;8201; # (舁; èˆ; èˆ; èˆ; èˆ; ) CJK COMPATIBILITY IDEOGRAPH-2F98B
+2F98C;8204;8204;8204;8204; # (舄; 舄; 舄; 舄; 舄; ) CJK COMPATIBILITY IDEOGRAPH-2F98C
+2F98D;8F9E;8F9E;8F9E;8F9E; # (ð¯¦; 辞; 辞; 辞; 辞; ) CJK COMPATIBILITY IDEOGRAPH-2F98D
+2F98E;446B;446B;446B;446B; # (䑫; 䑫; 䑫; 䑫; 䑫; ) CJK COMPATIBILITY IDEOGRAPH-2F98E
+2F98F;8291;8291;8291;8291; # (ð¯¦; 芑; 芑; 芑; 芑; ) CJK COMPATIBILITY IDEOGRAPH-2F98F
+2F990;828B;828B;828B;828B; # (ð¯¦; 芋; 芋; 芋; 芋; ) CJK COMPATIBILITY IDEOGRAPH-2F990
+2F991;829D;829D;829D;829D; # (芝; èŠ; èŠ; èŠ; èŠ; ) CJK COMPATIBILITY IDEOGRAPH-2F991
+2F992;52B3;52B3;52B3;52B3; # (劳; 劳; 劳; 劳; 劳; ) CJK COMPATIBILITY IDEOGRAPH-2F992
+2F993;82B1;82B1;82B1;82B1; # (花; 花; 花; 花; 花; ) CJK COMPATIBILITY IDEOGRAPH-2F993
+2F994;82B3;82B3;82B3;82B3; # (芳; 芳; 芳; 芳; 芳; ) CJK COMPATIBILITY IDEOGRAPH-2F994
+2F995;82BD;82BD;82BD;82BD; # (芽; 芽; 芽; 芽; 芽; ) CJK COMPATIBILITY IDEOGRAPH-2F995
+2F996;82E6;82E6;82E6;82E6; # (苦; 苦; 苦; 苦; 苦; ) CJK COMPATIBILITY IDEOGRAPH-2F996
+2F997;26B3C;26B3C;26B3C;26B3C; # (𦬼; 𦬼; 𦬼; 𦬼; 𦬼; ) CJK COMPATIBILITY IDEOGRAPH-2F997
+2F998;82E5;82E5;82E5;82E5; # (若; 若; 若; 若; 若; ) CJK COMPATIBILITY IDEOGRAPH-2F998
+2F999;831D;831D;831D;831D; # (茝; èŒ; èŒ; èŒ; èŒ; ) CJK COMPATIBILITY IDEOGRAPH-2F999
+2F99A;8363;8363;8363;8363; # (荣; è£; è£; è£; è£; ) CJK COMPATIBILITY IDEOGRAPH-2F99A
+2F99B;83AD;83AD;83AD;83AD; # (莭; 莭; 莭; 莭; 莭; ) CJK COMPATIBILITY IDEOGRAPH-2F99B
+2F99C;8323;8323;8323;8323; # (茣; 茣; 茣; 茣; 茣; ) CJK COMPATIBILITY IDEOGRAPH-2F99C
+2F99D;83BD;83BD;83BD;83BD; # (ð¯¦; 莽; 莽; 莽; 莽; ) CJK COMPATIBILITY IDEOGRAPH-2F99D
+2F99E;83E7;83E7;83E7;83E7; # (菧; è§; è§; è§; è§; ) CJK COMPATIBILITY IDEOGRAPH-2F99E
+2F99F;8457;8457;8457;8457; # (著; 著; 著; 著; 著; ) CJK COMPATIBILITY IDEOGRAPH-2F99F
+2F9A0;8353;8353;8353;8353; # (荓; è“; è“; è“; è“; ) CJK COMPATIBILITY IDEOGRAPH-2F9A0
+2F9A1;83CA;83CA;83CA;83CA; # (菊; èŠ; èŠ; èŠ; èŠ; ) CJK COMPATIBILITY IDEOGRAPH-2F9A1
+2F9A2;83CC;83CC;83CC;83CC; # (菌; èŒ; èŒ; èŒ; èŒ; ) CJK COMPATIBILITY IDEOGRAPH-2F9A2
+2F9A3;83DC;83DC;83DC;83DC; # (菜; èœ; èœ; èœ; èœ; ) CJK COMPATIBILITY IDEOGRAPH-2F9A3
+2F9A4;26C36;26C36;26C36;26C36; # (𦰶; 𦰶; 𦰶; 𦰶; 𦰶; ) CJK COMPATIBILITY IDEOGRAPH-2F9A4
+2F9A5;26D6B;26D6B;26D6B;26D6B; # (𦵫; 𦵫; 𦵫; 𦵫; 𦵫; ) CJK COMPATIBILITY IDEOGRAPH-2F9A5
+2F9A6;26CD5;26CD5;26CD5;26CD5; # (𦳕; 𦳕; 𦳕; 𦳕; 𦳕; ) CJK COMPATIBILITY IDEOGRAPH-2F9A6
+2F9A7;452B;452B;452B;452B; # (䔫; 䔫; 䔫; 䔫; 䔫; ) CJK COMPATIBILITY IDEOGRAPH-2F9A7
+2F9A8;84F1;84F1;84F1;84F1; # (蓱; 蓱; 蓱; 蓱; 蓱; ) CJK COMPATIBILITY IDEOGRAPH-2F9A8
+2F9A9;84F3;84F3;84F3;84F3; # (蓳; 蓳; 蓳; 蓳; 蓳; ) CJK COMPATIBILITY IDEOGRAPH-2F9A9
+2F9AA;8516;8516;8516;8516; # (蔖; 蔖; 蔖; 蔖; 蔖; ) CJK COMPATIBILITY IDEOGRAPH-2F9AA
+2F9AB;273CA;273CA;273CA;273CA; # (𧏊; ð§Š; ð§Š; ð§Š; ð§Š; ) CJK COMPATIBILITY IDEOGRAPH-2F9AB
+2F9AC;8564;8564;8564;8564; # (蕤; 蕤; 蕤; 蕤; 蕤; ) CJK COMPATIBILITY IDEOGRAPH-2F9AC
+2F9AD;26F2C;26F2C;26F2C;26F2C; # (𦼬; 𦼬; 𦼬; 𦼬; 𦼬; ) CJK COMPATIBILITY IDEOGRAPH-2F9AD
+2F9AE;455D;455D;455D;455D; # (䕝; ä•; ä•; ä•; ä•; ) CJK COMPATIBILITY IDEOGRAPH-2F9AE
+2F9AF;4561;4561;4561;4561; # (䕡; 䕡; 䕡; 䕡; 䕡; ) CJK COMPATIBILITY IDEOGRAPH-2F9AF
+2F9B0;26FB1;26FB1;26FB1;26FB1; # (𦾱; 𦾱; 𦾱; 𦾱; 𦾱; ) CJK COMPATIBILITY IDEOGRAPH-2F9B0
+2F9B1;270D2;270D2;270D2;270D2; # (𧃒; 𧃒; 𧃒; 𧃒; 𧃒; ) CJK COMPATIBILITY IDEOGRAPH-2F9B1
+2F9B2;456B;456B;456B;456B; # (䕫; 䕫; 䕫; 䕫; 䕫; ) CJK COMPATIBILITY IDEOGRAPH-2F9B2
+2F9B3;8650;8650;8650;8650; # (虐; è™; è™; è™; è™; ) CJK COMPATIBILITY IDEOGRAPH-2F9B3
+2F9B4;865C;865C;865C;865C; # (虜; 虜; 虜; 虜; 虜; ) CJK COMPATIBILITY IDEOGRAPH-2F9B4
+2F9B5;8667;8667;8667;8667; # (虧; 虧; 虧; 虧; 虧; ) CJK COMPATIBILITY IDEOGRAPH-2F9B5
+2F9B6;8669;8669;8669;8669; # (虩; 虩; 虩; 虩; 虩; ) CJK COMPATIBILITY IDEOGRAPH-2F9B6
+2F9B7;86A9;86A9;86A9;86A9; # (蚩; 蚩; 蚩; 蚩; 蚩; ) CJK COMPATIBILITY IDEOGRAPH-2F9B7
+2F9B8;8688;8688;8688;8688; # (蚈; 蚈; 蚈; 蚈; 蚈; ) CJK COMPATIBILITY IDEOGRAPH-2F9B8
+2F9B9;870E;870E;870E;870E; # (蜎; 蜎; 蜎; 蜎; 蜎; ) CJK COMPATIBILITY IDEOGRAPH-2F9B9
+2F9BA;86E2;86E2;86E2;86E2; # (蛢; 蛢; 蛢; 蛢; 蛢; ) CJK COMPATIBILITY IDEOGRAPH-2F9BA
+2F9BB;8779;8779;8779;8779; # (蝹; è¹; è¹; è¹; è¹; ) CJK COMPATIBILITY IDEOGRAPH-2F9BB
+2F9BC;8728;8728;8728;8728; # (蜨; 蜨; 蜨; 蜨; 蜨; ) CJK COMPATIBILITY IDEOGRAPH-2F9BC
+2F9BD;876B;876B;876B;876B; # (蝫; è«; è«; è«; è«; ) CJK COMPATIBILITY IDEOGRAPH-2F9BD
+2F9BE;8786;8786;8786;8786; # (螆; 螆; 螆; 螆; 螆; ) CJK COMPATIBILITY IDEOGRAPH-2F9BE
+2F9BF;45D7;45D7;45D7;45D7; # (䗗; 䗗; 䗗; 䗗; 䗗; ) CJK COMPATIBILITY IDEOGRAPH-2F9BF
+2F9C0;87E1;87E1;87E1;87E1; # (蟡; 蟡; 蟡; 蟡; 蟡; ) CJK COMPATIBILITY IDEOGRAPH-2F9C0
+2F9C1;8801;8801;8801;8801; # (ð¯§; è ; è ; è ; è ; ) CJK COMPATIBILITY IDEOGRAPH-2F9C1
+2F9C2;45F9;45F9;45F9;45F9; # (䗹; 䗹; 䗹; 䗹; 䗹; ) CJK COMPATIBILITY IDEOGRAPH-2F9C2
+2F9C3;8860;8860;8860;8860; # (衠; 衠; 衠; 衠; 衠; ) CJK COMPATIBILITY IDEOGRAPH-2F9C3
+2F9C4;8863;8863;8863;8863; # (衣; 衣; 衣; 衣; 衣; ) CJK COMPATIBILITY IDEOGRAPH-2F9C4
+2F9C5;27667;27667;27667;27667; # (𧙧; 𧙧; 𧙧; 𧙧; 𧙧; ) CJK COMPATIBILITY IDEOGRAPH-2F9C5
+2F9C6;88D7;88D7;88D7;88D7; # (裗; 裗; 裗; 裗; 裗; ) CJK COMPATIBILITY IDEOGRAPH-2F9C6
+2F9C7;88DE;88DE;88DE;88DE; # (裞; 裞; 裞; 裞; 裞; ) CJK COMPATIBILITY IDEOGRAPH-2F9C7
+2F9C8;4635;4635;4635;4635; # (䘵; 䘵; 䘵; 䘵; 䘵; ) CJK COMPATIBILITY IDEOGRAPH-2F9C8
+2F9C9;88FA;88FA;88FA;88FA; # (裺; 裺; 裺; 裺; 裺; ) CJK COMPATIBILITY IDEOGRAPH-2F9C9
+2F9CA;34BB;34BB;34BB;34BB; # (㒻; 㒻; 㒻; 㒻; 㒻; ) CJK COMPATIBILITY IDEOGRAPH-2F9CA
+2F9CB;278AE;278AE;278AE;278AE; # (𧢮; 𧢮; 𧢮; 𧢮; 𧢮; ) CJK COMPATIBILITY IDEOGRAPH-2F9CB
+2F9CC;27966;27966;27966;27966; # (𧥦; 𧥦; 𧥦; 𧥦; 𧥦; ) CJK COMPATIBILITY IDEOGRAPH-2F9CC
+2F9CD;46BE;46BE;46BE;46BE; # (ð¯§; äš¾; äš¾; äš¾; äš¾; ) CJK COMPATIBILITY IDEOGRAPH-2F9CD
+2F9CE;46C7;46C7;46C7;46C7; # (䛇; 䛇; 䛇; 䛇; 䛇; ) CJK COMPATIBILITY IDEOGRAPH-2F9CE
+2F9CF;8AA0;8AA0;8AA0;8AA0; # (ð¯§; 誠; 誠; 誠; 誠; ) CJK COMPATIBILITY IDEOGRAPH-2F9CF
+2F9D0;8AED;8AED;8AED;8AED; # (ð¯§; è«­; è«­; è«­; è«­; ) CJK COMPATIBILITY IDEOGRAPH-2F9D0
+2F9D1;8B8A;8B8A;8B8A;8B8A; # (變; 變; 變; 變; 變; ) CJK COMPATIBILITY IDEOGRAPH-2F9D1
+2F9D2;8C55;8C55;8C55;8C55; # (豕; 豕; 豕; 豕; 豕; ) CJK COMPATIBILITY IDEOGRAPH-2F9D2
+2F9D3;27CA8;27CA8;27CA8;27CA8; # (𧲨; 𧲨; 𧲨; 𧲨; 𧲨; ) CJK COMPATIBILITY IDEOGRAPH-2F9D3
+2F9D4;8CAB;8CAB;8CAB;8CAB; # (貫; 貫; 貫; 貫; 貫; ) CJK COMPATIBILITY IDEOGRAPH-2F9D4
+2F9D5;8CC1;8CC1;8CC1;8CC1; # (賁; è³; è³; è³; è³; ) CJK COMPATIBILITY IDEOGRAPH-2F9D5
+2F9D6;8D1B;8D1B;8D1B;8D1B; # (贛; 贛; 贛; 贛; 贛; ) CJK COMPATIBILITY IDEOGRAPH-2F9D6
+2F9D7;8D77;8D77;8D77;8D77; # (起; 起; 起; 起; 起; ) CJK COMPATIBILITY IDEOGRAPH-2F9D7
+2F9D8;27F2F;27F2F;27F2F;27F2F; # (𧼯; 𧼯; 𧼯; 𧼯; 𧼯; ) CJK COMPATIBILITY IDEOGRAPH-2F9D8
+2F9D9;20804;20804;20804;20804; # (𠠄; 𠠄; 𠠄; 𠠄; 𠠄; ) CJK COMPATIBILITY IDEOGRAPH-2F9D9
+2F9DA;8DCB;8DCB;8DCB;8DCB; # (跋; 跋; 跋; 跋; 跋; ) CJK COMPATIBILITY IDEOGRAPH-2F9DA
+2F9DB;8DBC;8DBC;8DBC;8DBC; # (趼; 趼; 趼; 趼; 趼; ) CJK COMPATIBILITY IDEOGRAPH-2F9DB
+2F9DC;8DF0;8DF0;8DF0;8DF0; # (跰; 跰; 跰; 跰; 跰; ) CJK COMPATIBILITY IDEOGRAPH-2F9DC
+2F9DD;208DE;208DE;208DE;208DE; # (ð¯§; 𠣞; 𠣞; 𠣞; 𠣞; ) CJK COMPATIBILITY IDEOGRAPH-2F9DD
+2F9DE;8ED4;8ED4;8ED4;8ED4; # (軔; 軔; 軔; 軔; 軔; ) CJK COMPATIBILITY IDEOGRAPH-2F9DE
+2F9DF;8F38;8F38;8F38;8F38; # (輸; 輸; 輸; 輸; 輸; ) CJK COMPATIBILITY IDEOGRAPH-2F9DF
+2F9E0;285D2;285D2;285D2;285D2; # (𨗒; 𨗒; 𨗒; 𨗒; 𨗒; ) CJK COMPATIBILITY IDEOGRAPH-2F9E0
+2F9E1;285ED;285ED;285ED;285ED; # (𨗭; 𨗭; 𨗭; 𨗭; 𨗭; ) CJK COMPATIBILITY IDEOGRAPH-2F9E1
+2F9E2;9094;9094;9094;9094; # (邔; 邔; 邔; 邔; 邔; ) CJK COMPATIBILITY IDEOGRAPH-2F9E2
+2F9E3;90F1;90F1;90F1;90F1; # (郱; 郱; 郱; 郱; 郱; ) CJK COMPATIBILITY IDEOGRAPH-2F9E3
+2F9E4;9111;9111;9111;9111; # (鄑; 鄑; 鄑; 鄑; 鄑; ) CJK COMPATIBILITY IDEOGRAPH-2F9E4
+2F9E5;2872E;2872E;2872E;2872E; # (𨜮; 𨜮; 𨜮; 𨜮; 𨜮; ) CJK COMPATIBILITY IDEOGRAPH-2F9E5
+2F9E6;911B;911B;911B;911B; # (鄛; 鄛; 鄛; 鄛; 鄛; ) CJK COMPATIBILITY IDEOGRAPH-2F9E6
+2F9E7;9238;9238;9238;9238; # (鈸; 鈸; 鈸; 鈸; 鈸; ) CJK COMPATIBILITY IDEOGRAPH-2F9E7
+2F9E8;92D7;92D7;92D7;92D7; # (鋗; 鋗; 鋗; 鋗; 鋗; ) CJK COMPATIBILITY IDEOGRAPH-2F9E8
+2F9E9;92D8;92D8;92D8;92D8; # (鋘; 鋘; 鋘; 鋘; 鋘; ) CJK COMPATIBILITY IDEOGRAPH-2F9E9
+2F9EA;927C;927C;927C;927C; # (鉼; 鉼; 鉼; 鉼; 鉼; ) CJK COMPATIBILITY IDEOGRAPH-2F9EA
+2F9EB;93F9;93F9;93F9;93F9; # (鏹; é¹; é¹; é¹; é¹; ) CJK COMPATIBILITY IDEOGRAPH-2F9EB
+2F9EC;9415;9415;9415;9415; # (鐕; é•; é•; é•; é•; ) CJK COMPATIBILITY IDEOGRAPH-2F9EC
+2F9ED;28BFA;28BFA;28BFA;28BFA; # (𨯺; 𨯺; 𨯺; 𨯺; 𨯺; ) CJK COMPATIBILITY IDEOGRAPH-2F9ED
+2F9EE;958B;958B;958B;958B; # (開; 開; 開; 開; 開; ) CJK COMPATIBILITY IDEOGRAPH-2F9EE
+2F9EF;4995;4995;4995;4995; # (䦕; 䦕; 䦕; 䦕; 䦕; ) CJK COMPATIBILITY IDEOGRAPH-2F9EF
+2F9F0;95B7;95B7;95B7;95B7; # (閷; 閷; 閷; 閷; 閷; ) CJK COMPATIBILITY IDEOGRAPH-2F9F0
+2F9F1;28D77;28D77;28D77;28D77; # (𨵷; 𨵷; 𨵷; 𨵷; 𨵷; ) CJK COMPATIBILITY IDEOGRAPH-2F9F1
+2F9F2;49E6;49E6;49E6;49E6; # (䧦; 䧦; 䧦; 䧦; 䧦; ) CJK COMPATIBILITY IDEOGRAPH-2F9F2
+2F9F3;96C3;96C3;96C3;96C3; # (雃; 雃; 雃; 雃; 雃; ) CJK COMPATIBILITY IDEOGRAPH-2F9F3
+2F9F4;5DB2;5DB2;5DB2;5DB2; # (嶲; 嶲; 嶲; 嶲; 嶲; ) CJK COMPATIBILITY IDEOGRAPH-2F9F4
+2F9F5;9723;9723;9723;9723; # (霣; 霣; 霣; 霣; 霣; ) CJK COMPATIBILITY IDEOGRAPH-2F9F5
+2F9F6;29145;29145;29145;29145; # (𩅅; 𩅅; 𩅅; 𩅅; 𩅅; ) CJK COMPATIBILITY IDEOGRAPH-2F9F6
+2F9F7;2921A;2921A;2921A;2921A; # (𩈚; 𩈚; 𩈚; 𩈚; 𩈚; ) CJK COMPATIBILITY IDEOGRAPH-2F9F7
+2F9F8;4A6E;4A6E;4A6E;4A6E; # (䩮; 䩮; 䩮; 䩮; 䩮; ) CJK COMPATIBILITY IDEOGRAPH-2F9F8
+2F9F9;4A76;4A76;4A76;4A76; # (䩶; 䩶; 䩶; 䩶; 䩶; ) CJK COMPATIBILITY IDEOGRAPH-2F9F9
+2F9FA;97E0;97E0;97E0;97E0; # (韠; 韠; 韠; 韠; 韠; ) CJK COMPATIBILITY IDEOGRAPH-2F9FA
+2F9FB;2940A;2940A;2940A;2940A; # (𩐊; ð©Š; ð©Š; ð©Š; ð©Š; ) CJK COMPATIBILITY IDEOGRAPH-2F9FB
+2F9FC;4AB2;4AB2;4AB2;4AB2; # (䪲; 䪲; 䪲; 䪲; 䪲; ) CJK COMPATIBILITY IDEOGRAPH-2F9FC
+2F9FD;29496;29496;29496;29496; # (𩒖; 𩒖; 𩒖; 𩒖; 𩒖; ) CJK COMPATIBILITY IDEOGRAPH-2F9FD
+2F9FE;980B;980B;980B;980B; # (頋; 頋; 頋; 頋; 頋; ) CJK COMPATIBILITY IDEOGRAPH-2F9FE
+2F9FF;980B;980B;980B;980B; # (頋; 頋; 頋; 頋; 頋; ) CJK COMPATIBILITY IDEOGRAPH-2F9FF
+2FA00;9829;9829;9829;9829; # (頩; 頩; 頩; 頩; 頩; ) CJK COMPATIBILITY IDEOGRAPH-2FA00
+2FA01;295B6;295B6;295B6;295B6; # (ð¯¨; ð©–¶; ð©–¶; ð©–¶; ð©–¶; ) CJK COMPATIBILITY IDEOGRAPH-2FA01
+2FA02;98E2;98E2;98E2;98E2; # (飢; 飢; 飢; 飢; 飢; ) CJK COMPATIBILITY IDEOGRAPH-2FA02
+2FA03;4B33;4B33;4B33;4B33; # (䬳; 䬳; 䬳; 䬳; 䬳; ) CJK COMPATIBILITY IDEOGRAPH-2FA03
+2FA04;9929;9929;9929;9929; # (餩; 餩; 餩; 餩; 餩; ) CJK COMPATIBILITY IDEOGRAPH-2FA04
+2FA05;99A7;99A7;99A7;99A7; # (馧; 馧; 馧; 馧; 馧; ) CJK COMPATIBILITY IDEOGRAPH-2FA05
+2FA06;99C2;99C2;99C2;99C2; # (駂; 駂; 駂; 駂; 駂; ) CJK COMPATIBILITY IDEOGRAPH-2FA06
+2FA07;99FE;99FE;99FE;99FE; # (駾; 駾; 駾; 駾; 駾; ) CJK COMPATIBILITY IDEOGRAPH-2FA07
+2FA08;4BCE;4BCE;4BCE;4BCE; # (䯎; 䯎; 䯎; 䯎; 䯎; ) CJK COMPATIBILITY IDEOGRAPH-2FA08
+2FA09;29B30;29B30;29B30;29B30; # (𩬰; 𩬰; 𩬰; 𩬰; 𩬰; ) CJK COMPATIBILITY IDEOGRAPH-2FA09
+2FA0A;9B12;9B12;9B12;9B12; # (鬒; 鬒; 鬒; 鬒; 鬒; ) CJK COMPATIBILITY IDEOGRAPH-2FA0A
+2FA0B;9C40;9C40;9C40;9C40; # (鱀; 鱀; 鱀; 鱀; 鱀; ) CJK COMPATIBILITY IDEOGRAPH-2FA0B
+2FA0C;9CFD;9CFD;9CFD;9CFD; # (鳽; 鳽; 鳽; 鳽; 鳽; ) CJK COMPATIBILITY IDEOGRAPH-2FA0C
+2FA0D;4CCE;4CCE;4CCE;4CCE; # (ð¯¨; 䳎; 䳎; 䳎; 䳎; ) CJK COMPATIBILITY IDEOGRAPH-2FA0D
+2FA0E;4CED;4CED;4CED;4CED; # (䳭; 䳭; 䳭; 䳭; 䳭; ) CJK COMPATIBILITY IDEOGRAPH-2FA0E
+2FA0F;9D67;9D67;9D67;9D67; # (ð¯¨; 鵧; 鵧; 鵧; 鵧; ) CJK COMPATIBILITY IDEOGRAPH-2FA0F
+2FA10;2A0CE;2A0CE;2A0CE;2A0CE; # (ð¯¨; 𪃎; 𪃎; 𪃎; 𪃎; ) CJK COMPATIBILITY IDEOGRAPH-2FA10
+2FA11;4CF8;4CF8;4CF8;4CF8; # (䳸; 䳸; 䳸; 䳸; 䳸; ) CJK COMPATIBILITY IDEOGRAPH-2FA11
+2FA12;2A105;2A105;2A105;2A105; # (𪄅; 𪄅; 𪄅; 𪄅; 𪄅; ) CJK COMPATIBILITY IDEOGRAPH-2FA12
+2FA13;2A20E;2A20E;2A20E;2A20E; # (𪈎; 𪈎; 𪈎; 𪈎; 𪈎; ) CJK COMPATIBILITY IDEOGRAPH-2FA13
+2FA14;2A291;2A291;2A291;2A291; # (𪊑; 𪊑; 𪊑; 𪊑; 𪊑; ) CJK COMPATIBILITY IDEOGRAPH-2FA14
+2FA15;9EBB;9EBB;9EBB;9EBB; # (麻; 麻; 麻; 麻; 麻; ) CJK COMPATIBILITY IDEOGRAPH-2FA15
+2FA16;4D56;4D56;4D56;4D56; # (䵖; 䵖; 䵖; 䵖; 䵖; ) CJK COMPATIBILITY IDEOGRAPH-2FA16
+2FA17;9EF9;9EF9;9EF9;9EF9; # (黹; 黹; 黹; 黹; 黹; ) CJK COMPATIBILITY IDEOGRAPH-2FA17
+2FA18;9EFE;9EFE;9EFE;9EFE; # (黾; 黾; 黾; 黾; 黾; ) CJK COMPATIBILITY IDEOGRAPH-2FA18
+2FA19;9F05;9F05;9F05;9F05; # (鼅; 鼅; 鼅; 鼅; 鼅; ) CJK COMPATIBILITY IDEOGRAPH-2FA19
+2FA1A;9F0F;9F0F;9F0F;9F0F; # (鼏; é¼; é¼; é¼; é¼; ) CJK COMPATIBILITY IDEOGRAPH-2FA1A
+2FA1B;9F16;9F16;9F16;9F16; # (鼖; 鼖; 鼖; 鼖; 鼖; ) CJK COMPATIBILITY IDEOGRAPH-2FA1B
+2FA1C;9F3B;9F3B;9F3B;9F3B; # (鼻; 鼻; 鼻; 鼻; 鼻; ) CJK COMPATIBILITY IDEOGRAPH-2FA1C
+2FA1D;2A600;2A600;2A600;2A600; # (ð¯¨; 𪘀; 𪘀; 𪘀; 𪘀; ) CJK COMPATIBILITY IDEOGRAPH-2FA1D
+#
+@Part2 # Canonical Order Test
+#
+0061 0315 0300 05AE 0300 0062;00E0 05AE 0300 0315 0062;0061 05AE 0300 0300 0315 0062;00E0 05AE 0300 0315 0062;0061 05AE 0300 0300 0315 0062; # (a◌̕◌̀◌֮◌̀b; à◌֮◌̀◌̕b; a◌֮◌̀◌̀◌̕b; à◌֮◌̀◌̕b; a◌֮◌̀◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRAVE ACCENT, LATIN SMALL LETTER B
+0061 0300 0315 0300 05AE 0062;00E0 05AE 0300 0315 0062;0061 05AE 0300 0300 0315 0062;00E0 05AE 0300 0315 0062;0061 05AE 0300 0300 0315 0062; # (a◌̀◌̕◌̀◌֮b; à◌֮◌̀◌̕b; a◌֮◌̀◌̀◌̕b; à◌֮◌̀◌̕b; a◌֮◌̀◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GRAVE ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0301 0062;00E0 05AE 0301 0315 0062;0061 05AE 0300 0301 0315 0062;00E0 05AE 0301 0315 0062;0061 05AE 0300 0301 0315 0062; # (a◌̕◌̀◌֮◌Ìb; à◌֮◌Ì◌̕b; a◌֮◌̀◌Ì◌̕b; à◌֮◌Ì◌̕b; a◌֮◌̀◌Ì◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING ACUTE ACCENT, LATIN SMALL LETTER B
+0061 0301 0315 0300 05AE 0062;00E1 05AE 0300 0315 0062;0061 05AE 0301 0300 0315 0062;00E1 05AE 0300 0315 0062;0061 05AE 0301 0300 0315 0062; # (aâ—ŒÌ◌̕◌̀◌֮b; á◌֮◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; á◌֮◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING ACUTE ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0302 0062;00E0 05AE 0302 0315 0062;0061 05AE 0300 0302 0315 0062;00E0 05AE 0302 0315 0062;0061 05AE 0300 0302 0315 0062; # (a◌̕◌̀◌֮◌̂b; à◌֮◌̂◌̕b; a◌֮◌̀◌̂◌̕b; à◌֮◌̂◌̕b; a◌֮◌̀◌̂◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CIRCUMFLEX ACCENT, LATIN SMALL LETTER B
+0061 0302 0315 0300 05AE 0062;1EA7 05AE 0315 0062;0061 05AE 0302 0300 0315 0062;1EA7 05AE 0315 0062;0061 05AE 0302 0300 0315 0062; # (a◌̂◌̕◌̀◌֮b; ầ◌֮◌̕b; a◌֮◌̂◌̀◌̕b; ầ◌֮◌̕b; a◌֮◌̂◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CIRCUMFLEX ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0303 0062;00E0 05AE 0303 0315 0062;0061 05AE 0300 0303 0315 0062;00E0 05AE 0303 0315 0062;0061 05AE 0300 0303 0315 0062; # (a◌̕◌̀◌֮◌̃b; à◌֮◌̃◌̕b; a◌֮◌̀◌̃◌̕b; à◌֮◌̃◌̕b; a◌֮◌̀◌̃◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING TILDE, LATIN SMALL LETTER B
+0061 0303 0315 0300 05AE 0062;00E3 05AE 0300 0315 0062;0061 05AE 0303 0300 0315 0062;00E3 05AE 0300 0315 0062;0061 05AE 0303 0300 0315 0062; # (a◌̃◌̕◌̀◌֮b; ã◌֮◌̀◌̕b; a◌֮◌̃◌̀◌̕b; ã◌֮◌̀◌̕b; a◌֮◌̃◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING TILDE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0304 0062;00E0 05AE 0304 0315 0062;0061 05AE 0300 0304 0315 0062;00E0 05AE 0304 0315 0062;0061 05AE 0300 0304 0315 0062; # (a◌̕◌̀◌֮◌̄b; à◌֮◌̄◌̕b; a◌֮◌̀◌̄◌̕b; à◌֮◌̄◌̕b; a◌֮◌̀◌̄◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING MACRON, LATIN SMALL LETTER B
+0061 0304 0315 0300 05AE 0062;0101 05AE 0300 0315 0062;0061 05AE 0304 0300 0315 0062;0101 05AE 0300 0315 0062;0061 05AE 0304 0300 0315 0062; # (a◌̄◌̕◌̀◌֮b; Ä◌֮◌̀◌̕b; a◌֮◌̄◌̀◌̕b; Ä◌֮◌̀◌̕b; a◌֮◌̄◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING MACRON, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0305 0062;00E0 05AE 0305 0315 0062;0061 05AE 0300 0305 0315 0062;00E0 05AE 0305 0315 0062;0061 05AE 0300 0305 0315 0062; # (a◌̕◌̀◌֮◌̅b; à◌֮◌̅◌̕b; a◌֮◌̀◌̅◌̕b; à◌֮◌̅◌̕b; a◌֮◌̀◌̅◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING OVERLINE, LATIN SMALL LETTER B
+0061 0305 0315 0300 05AE 0062;0061 05AE 0305 0300 0315 0062;0061 05AE 0305 0300 0315 0062;0061 05AE 0305 0300 0315 0062;0061 05AE 0305 0300 0315 0062; # (a◌̅◌̕◌̀◌֮b; a◌֮◌̅◌̀◌̕b; a◌֮◌̅◌̀◌̕b; a◌֮◌̅◌̀◌̕b; a◌֮◌̅◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING OVERLINE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0306 0062;00E0 05AE 0306 0315 0062;0061 05AE 0300 0306 0315 0062;00E0 05AE 0306 0315 0062;0061 05AE 0300 0306 0315 0062; # (a◌̕◌̀◌֮◌̆b; à◌֮◌̆◌̕b; a◌֮◌̀◌̆◌̕b; à◌֮◌̆◌̕b; a◌֮◌̀◌̆◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING BREVE, LATIN SMALL LETTER B
+0061 0306 0315 0300 05AE 0062;1EB1 05AE 0315 0062;0061 05AE 0306 0300 0315 0062;1EB1 05AE 0315 0062;0061 05AE 0306 0300 0315 0062; # (a◌̆◌̕◌̀◌֮b; ằ◌֮◌̕b; a◌֮◌̆◌̀◌̕b; ằ◌֮◌̕b; a◌֮◌̆◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING BREVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0307 0062;00E0 05AE 0307 0315 0062;0061 05AE 0300 0307 0315 0062;00E0 05AE 0307 0315 0062;0061 05AE 0300 0307 0315 0062; # (a◌̕◌̀◌֮◌̇b; à◌֮◌̇◌̕b; a◌֮◌̀◌̇◌̕b; à◌֮◌̇◌̕b; a◌֮◌̀◌̇◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOT ABOVE, LATIN SMALL LETTER B
+0061 0307 0315 0300 05AE 0062;0227 05AE 0300 0315 0062;0061 05AE 0307 0300 0315 0062;0227 05AE 0300 0315 0062;0061 05AE 0307 0300 0315 0062; # (a◌̇◌̕◌̀◌֮b; ȧ◌֮◌̀◌̕b; a◌֮◌̇◌̀◌̕b; ȧ◌֮◌̀◌̕b; a◌֮◌̇◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOT ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0308 0062;00E0 05AE 0308 0315 0062;0061 05AE 0300 0308 0315 0062;00E0 05AE 0308 0315 0062;0061 05AE 0300 0308 0315 0062; # (a◌̕◌̀◌֮◌̈b; à◌֮◌̈◌̕b; a◌֮◌̀◌̈◌̕b; à◌֮◌̈◌̕b; a◌֮◌̀◌̈◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DIAERESIS, LATIN SMALL LETTER B
+0061 0308 0315 0300 05AE 0062;00E4 05AE 0300 0315 0062;0061 05AE 0308 0300 0315 0062;00E4 05AE 0300 0315 0062;0061 05AE 0308 0300 0315 0062; # (a◌̈◌̕◌̀◌֮b; ä◌֮◌̀◌̕b; a◌֮◌̈◌̀◌̕b; ä◌֮◌̀◌̕b; a◌֮◌̈◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DIAERESIS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0309 0062;00E0 05AE 0309 0315 0062;0061 05AE 0300 0309 0315 0062;00E0 05AE 0309 0315 0062;0061 05AE 0300 0309 0315 0062; # (a◌̕◌̀◌֮◌̉b; à◌֮◌̉◌̕b; a◌֮◌̀◌̉◌̕b; à◌֮◌̉◌̕b; a◌֮◌̀◌̉◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING HOOK ABOVE, LATIN SMALL LETTER B
+0061 0309 0315 0300 05AE 0062;1EA3 05AE 0300 0315 0062;0061 05AE 0309 0300 0315 0062;1EA3 05AE 0300 0315 0062;0061 05AE 0309 0300 0315 0062; # (a◌̉◌̕◌̀◌֮b; ả◌֮◌̀◌̕b; a◌֮◌̉◌̀◌̕b; ả◌֮◌̀◌̕b; a◌֮◌̉◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING HOOK ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 030A 0062;00E0 05AE 030A 0315 0062;0061 05AE 0300 030A 0315 0062;00E0 05AE 030A 0315 0062;0061 05AE 0300 030A 0315 0062; # (a◌̕◌̀◌֮◌̊b; à◌֮◌̊◌̕b; a◌֮◌̀◌̊◌̕b; à◌֮◌̊◌̕b; a◌֮◌̀◌̊◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING RING ABOVE, LATIN SMALL LETTER B
+0061 030A 0315 0300 05AE 0062;00E5 05AE 0300 0315 0062;0061 05AE 030A 0300 0315 0062;00E5 05AE 0300 0315 0062;0061 05AE 030A 0300 0315 0062; # (a◌̊◌̕◌̀◌֮b; å◌֮◌̀◌̕b; a◌֮◌̊◌̀◌̕b; å◌֮◌̀◌̕b; a◌֮◌̊◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING RING ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 030B 0062;00E0 05AE 030B 0315 0062;0061 05AE 0300 030B 0315 0062;00E0 05AE 030B 0315 0062;0061 05AE 0300 030B 0315 0062; # (a◌̕◌̀◌֮◌̋b; à◌֮◌̋◌̕b; a◌֮◌̀◌̋◌̕b; à◌֮◌̋◌̕b; a◌֮◌̀◌̋◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOUBLE ACUTE ACCENT, LATIN SMALL LETTER B
+0061 030B 0315 0300 05AE 0062;0061 05AE 030B 0300 0315 0062;0061 05AE 030B 0300 0315 0062;0061 05AE 030B 0300 0315 0062;0061 05AE 030B 0300 0315 0062; # (a◌̋◌̕◌̀◌֮b; a◌֮◌̋◌̀◌̕b; a◌֮◌̋◌̀◌̕b; a◌֮◌̋◌̀◌̕b; a◌֮◌̋◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOUBLE ACUTE ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 030C 0062;00E0 05AE 030C 0315 0062;0061 05AE 0300 030C 0315 0062;00E0 05AE 030C 0315 0062;0061 05AE 0300 030C 0315 0062; # (a◌̕◌̀◌֮◌̌b; à◌֮◌̌◌̕b; a◌֮◌̀◌̌◌̕b; à◌֮◌̌◌̕b; a◌֮◌̀◌̌◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CARON, LATIN SMALL LETTER B
+0061 030C 0315 0300 05AE 0062;01CE 05AE 0300 0315 0062;0061 05AE 030C 0300 0315 0062;01CE 05AE 0300 0315 0062;0061 05AE 030C 0300 0315 0062; # (a◌̌◌̕◌̀◌֮b; ǎ◌֮◌̀◌̕b; a◌֮◌̌◌̀◌̕b; ǎ◌֮◌̀◌̕b; a◌֮◌̌◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CARON, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 030D 0062;00E0 05AE 030D 0315 0062;0061 05AE 0300 030D 0315 0062;00E0 05AE 030D 0315 0062;0061 05AE 0300 030D 0315 0062; # (a◌̕◌̀◌֮◌Ìb; à◌֮◌Ì◌̕b; a◌֮◌̀◌Ì◌̕b; à◌֮◌Ì◌̕b; a◌֮◌̀◌Ì◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING VERTICAL LINE ABOVE, LATIN SMALL LETTER B
+0061 030D 0315 0300 05AE 0062;0061 05AE 030D 0300 0315 0062;0061 05AE 030D 0300 0315 0062;0061 05AE 030D 0300 0315 0062;0061 05AE 030D 0300 0315 0062; # (aâ—ŒÌ◌̕◌̀◌֮b; a◌֮◌Ì◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING VERTICAL LINE ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 030E 0062;00E0 05AE 030E 0315 0062;0061 05AE 0300 030E 0315 0062;00E0 05AE 030E 0315 0062;0061 05AE 0300 030E 0315 0062; # (a◌̕◌̀◌֮◌̎b; à◌֮◌̎◌̕b; a◌֮◌̀◌̎◌̕b; à◌֮◌̎◌̕b; a◌֮◌̀◌̎◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOUBLE VERTICAL LINE ABOVE, LATIN SMALL LETTER B
+0061 030E 0315 0300 05AE 0062;0061 05AE 030E 0300 0315 0062;0061 05AE 030E 0300 0315 0062;0061 05AE 030E 0300 0315 0062;0061 05AE 030E 0300 0315 0062; # (a◌̎◌̕◌̀◌֮b; a◌֮◌̎◌̀◌̕b; a◌֮◌̎◌̀◌̕b; a◌֮◌̎◌̀◌̕b; a◌֮◌̎◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOUBLE VERTICAL LINE ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 030F 0062;00E0 05AE 030F 0315 0062;0061 05AE 0300 030F 0315 0062;00E0 05AE 030F 0315 0062;0061 05AE 0300 030F 0315 0062; # (a◌̕◌̀◌֮◌Ìb; à◌֮◌Ì◌̕b; a◌֮◌̀◌Ì◌̕b; à◌֮◌Ì◌̕b; a◌֮◌̀◌Ì◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOUBLE GRAVE ACCENT, LATIN SMALL LETTER B
+0061 030F 0315 0300 05AE 0062;0201 05AE 0300 0315 0062;0061 05AE 030F 0300 0315 0062;0201 05AE 0300 0315 0062;0061 05AE 030F 0300 0315 0062; # (aâ—ŒÌ◌̕◌̀◌֮b; È◌֮◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; È◌֮◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOUBLE GRAVE ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0310 0062;00E0 05AE 0310 0315 0062;0061 05AE 0300 0310 0315 0062;00E0 05AE 0310 0315 0062;0061 05AE 0300 0310 0315 0062; # (a◌̕◌̀◌֮◌Ìb; à◌֮◌Ì◌̕b; a◌֮◌̀◌Ì◌̕b; à◌֮◌Ì◌̕b; a◌֮◌̀◌Ì◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CANDRABINDU, LATIN SMALL LETTER B
+0061 0310 0315 0300 05AE 0062;0061 05AE 0310 0300 0315 0062;0061 05AE 0310 0300 0315 0062;0061 05AE 0310 0300 0315 0062;0061 05AE 0310 0300 0315 0062; # (aâ—ŒÌ◌̕◌̀◌֮b; a◌֮◌Ì◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CANDRABINDU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0311 0062;00E0 05AE 0311 0315 0062;0061 05AE 0300 0311 0315 0062;00E0 05AE 0311 0315 0062;0061 05AE 0300 0311 0315 0062; # (a◌̕◌̀◌֮◌̑b; à◌֮◌̑◌̕b; a◌֮◌̀◌̑◌̕b; à◌֮◌̑◌̕b; a◌֮◌̀◌̑◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING INVERTED BREVE, LATIN SMALL LETTER B
+0061 0311 0315 0300 05AE 0062;0203 05AE 0300 0315 0062;0061 05AE 0311 0300 0315 0062;0203 05AE 0300 0315 0062;0061 05AE 0311 0300 0315 0062; # (a◌̑◌̕◌̀◌֮b; ȃ◌֮◌̀◌̕b; a◌֮◌̑◌̀◌̕b; ȃ◌֮◌̀◌̕b; a◌֮◌̑◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING INVERTED BREVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0312 0062;00E0 05AE 0312 0315 0062;0061 05AE 0300 0312 0315 0062;00E0 05AE 0312 0315 0062;0061 05AE 0300 0312 0315 0062; # (a◌̕◌̀◌֮◌̒b; à◌֮◌̒◌̕b; a◌֮◌̀◌̒◌̕b; à◌֮◌̒◌̕b; a◌֮◌̀◌̒◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING TURNED COMMA ABOVE, LATIN SMALL LETTER B
+0061 0312 0315 0300 05AE 0062;0061 05AE 0312 0300 0315 0062;0061 05AE 0312 0300 0315 0062;0061 05AE 0312 0300 0315 0062;0061 05AE 0312 0300 0315 0062; # (a◌̒◌̕◌̀◌֮b; a◌֮◌̒◌̀◌̕b; a◌֮◌̒◌̀◌̕b; a◌֮◌̒◌̀◌̕b; a◌֮◌̒◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING TURNED COMMA ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0313 0062;00E0 05AE 0313 0315 0062;0061 05AE 0300 0313 0315 0062;00E0 05AE 0313 0315 0062;0061 05AE 0300 0313 0315 0062; # (a◌̕◌̀◌֮◌̓b; à◌֮◌̓◌̕b; a◌֮◌̀◌̓◌̕b; à◌֮◌̓◌̕b; a◌֮◌̀◌̓◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING COMMA ABOVE, LATIN SMALL LETTER B
+0061 0313 0315 0300 05AE 0062;0061 05AE 0313 0300 0315 0062;0061 05AE 0313 0300 0315 0062;0061 05AE 0313 0300 0315 0062;0061 05AE 0313 0300 0315 0062; # (a◌̓◌̕◌̀◌֮b; a◌֮◌̓◌̀◌̕b; a◌֮◌̓◌̀◌̕b; a◌֮◌̓◌̀◌̕b; a◌֮◌̓◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0314 0062;00E0 05AE 0314 0315 0062;0061 05AE 0300 0314 0315 0062;00E0 05AE 0314 0315 0062;0061 05AE 0300 0314 0315 0062; # (a◌̕◌̀◌֮◌̔b; à◌֮◌̔◌̕b; a◌֮◌̀◌̔◌̕b; à◌֮◌̔◌̕b; a◌֮◌̀◌̔◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING REVERSED COMMA ABOVE, LATIN SMALL LETTER B
+0061 0314 0315 0300 05AE 0062;0061 05AE 0314 0300 0315 0062;0061 05AE 0314 0300 0315 0062;0061 05AE 0314 0300 0315 0062;0061 05AE 0314 0300 0315 0062; # (a◌̔◌̕◌̀◌֮b; a◌֮◌̔◌̀◌̕b; a◌֮◌̔◌̀◌̕b; a◌֮◌̔◌̀◌̕b; a◌֮◌̔◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING REVERSED COMMA ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 035C 0315 0300 0315 0062;00E0 0315 0315 035C 0062;0061 0300 0315 0315 035C 0062;00E0 0315 0315 035C 0062;0061 0300 0315 0315 035C 0062; # (a◌͜◌̕◌̀◌̕b; à◌̕◌̕◌͜b; a◌̀◌̕◌̕◌͜b; à◌̕◌̕◌͜b; a◌̀◌̕◌̕◌͜b; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, COMBINING COMMA ABOVE RIGHT, LATIN SMALL LETTER B
+0061 0315 035C 0315 0300 0062;00E0 0315 0315 035C 0062;0061 0300 0315 0315 035C 0062;00E0 0315 0315 035C 0062;0061 0300 0315 0315 035C 0062; # (a◌̕◌͜◌̕◌̀b; à◌̕◌̕◌͜b; a◌̀◌̕◌̕◌͜b; à◌̕◌̕◌͜b; a◌̀◌̕◌̕◌͜b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0316 0062;0061 1DFA 0316 0316 059A 0062;0061 1DFA 0316 0316 059A 0062;0061 1DFA 0316 0316 059A 0062;0061 1DFA 0316 0316 059A 0062; # (a◌֚◌̖◌᷺◌̖b; a◌᷺◌̖◌̖◌֚b; a◌᷺◌̖◌̖◌֚b; a◌᷺◌̖◌̖◌֚b; a◌᷺◌̖◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING GRAVE ACCENT BELOW, LATIN SMALL LETTER B
+0061 0316 059A 0316 1DFA 0062;0061 1DFA 0316 0316 059A 0062;0061 1DFA 0316 0316 059A 0062;0061 1DFA 0316 0316 059A 0062;0061 1DFA 0316 0316 059A 0062; # (a◌̖◌֚◌̖◌᷺b; a◌᷺◌̖◌̖◌֚b; a◌᷺◌̖◌̖◌֚b; a◌᷺◌̖◌̖◌֚b; a◌᷺◌̖◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING GRAVE ACCENT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0317 0062;0061 1DFA 0316 0317 059A 0062;0061 1DFA 0316 0317 059A 0062;0061 1DFA 0316 0317 059A 0062;0061 1DFA 0316 0317 059A 0062; # (a◌֚◌̖◌᷺◌̗b; a◌᷺◌̖◌̗◌֚b; a◌᷺◌̖◌̗◌֚b; a◌᷺◌̖◌̗◌֚b; a◌᷺◌̖◌̗◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING ACUTE ACCENT BELOW, LATIN SMALL LETTER B
+0061 0317 059A 0316 1DFA 0062;0061 1DFA 0317 0316 059A 0062;0061 1DFA 0317 0316 059A 0062;0061 1DFA 0317 0316 059A 0062;0061 1DFA 0317 0316 059A 0062; # (a◌̗◌֚◌̖◌᷺b; a◌᷺◌̗◌̖◌֚b; a◌᷺◌̗◌̖◌֚b; a◌᷺◌̗◌̖◌֚b; a◌᷺◌̗◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING ACUTE ACCENT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0318 0062;0061 1DFA 0316 0318 059A 0062;0061 1DFA 0316 0318 059A 0062;0061 1DFA 0316 0318 059A 0062;0061 1DFA 0316 0318 059A 0062; # (a◌֚◌̖◌᷺◌̘b; a◌᷺◌̖◌̘◌֚b; a◌᷺◌̖◌̘◌֚b; a◌᷺◌̖◌̘◌֚b; a◌᷺◌̖◌̘◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LEFT TACK BELOW, LATIN SMALL LETTER B
+0061 0318 059A 0316 1DFA 0062;0061 1DFA 0318 0316 059A 0062;0061 1DFA 0318 0316 059A 0062;0061 1DFA 0318 0316 059A 0062;0061 1DFA 0318 0316 059A 0062; # (a◌̘◌֚◌̖◌᷺b; a◌᷺◌̘◌̖◌֚b; a◌᷺◌̘◌̖◌֚b; a◌᷺◌̘◌̖◌֚b; a◌᷺◌̘◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LEFT TACK BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0319 0062;0061 1DFA 0316 0319 059A 0062;0061 1DFA 0316 0319 059A 0062;0061 1DFA 0316 0319 059A 0062;0061 1DFA 0316 0319 059A 0062; # (a◌֚◌̖◌᷺◌̙b; a◌᷺◌̖◌̙◌֚b; a◌᷺◌̖◌̙◌֚b; a◌᷺◌̖◌̙◌֚b; a◌᷺◌̖◌̙◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING RIGHT TACK BELOW, LATIN SMALL LETTER B
+0061 0319 059A 0316 1DFA 0062;0061 1DFA 0319 0316 059A 0062;0061 1DFA 0319 0316 059A 0062;0061 1DFA 0319 0316 059A 0062;0061 1DFA 0319 0316 059A 0062; # (a◌̙◌֚◌̖◌᷺b; a◌᷺◌̙◌̖◌֚b; a◌᷺◌̙◌̖◌֚b; a◌᷺◌̙◌̖◌֚b; a◌᷺◌̙◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING RIGHT TACK BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 035C 0315 0300 031A 0062;00E0 0315 031A 035C 0062;0061 0300 0315 031A 035C 0062;00E0 0315 031A 035C 0062;0061 0300 0315 031A 035C 0062; # (a◌͜◌̕◌̀◌̚b; à◌̕◌̚◌͜b; a◌̀◌̕◌̚◌͜b; à◌̕◌̚◌͜b; a◌̀◌̕◌̚◌͜b; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, COMBINING LEFT ANGLE ABOVE, LATIN SMALL LETTER B
+0061 031A 035C 0315 0300 0062;00E0 031A 0315 035C 0062;0061 0300 031A 0315 035C 0062;00E0 031A 0315 035C 0062;0061 0300 031A 0315 035C 0062; # (a◌̚◌͜◌̕◌̀b; à◌̚◌̕◌͜b; a◌̀◌̚◌̕◌͜b; à◌̚◌̕◌͜b; a◌̀◌̚◌̕◌͜b; ) LATIN SMALL LETTER A, COMBINING LEFT ANGLE ABOVE, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, LATIN SMALL LETTER B
+0061 1DFA 031B 1DCE 031B 0062;0061 1DCE 031B 031B 1DFA 0062;0061 1DCE 031B 031B 1DFA 0062;0061 1DCE 031B 031B 1DFA 0062;0061 1DCE 031B 031B 1DFA 0062; # (a◌᷺◌̛◌᷎◌̛b; a◌᷎◌̛◌̛◌᷺b; a◌᷎◌̛◌̛◌᷺b; a◌᷎◌̛◌̛◌᷺b; a◌᷎◌̛◌̛◌᷺b; ) LATIN SMALL LETTER A, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, COMBINING HORN, LATIN SMALL LETTER B
+0061 031B 1DFA 031B 1DCE 0062;0061 1DCE 031B 031B 1DFA 0062;0061 1DCE 031B 031B 1DFA 0062;0061 1DCE 031B 031B 1DFA 0062;0061 1DCE 031B 031B 1DFA 0062; # (a◌̛◌᷺◌̛◌᷎b; a◌᷎◌̛◌̛◌᷺b; a◌᷎◌̛◌̛◌᷺b; a◌᷎◌̛◌̛◌᷺b; a◌᷎◌̛◌̛◌᷺b; ) LATIN SMALL LETTER A, COMBINING HORN, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 031C 0062;0061 1DFA 0316 031C 059A 0062;0061 1DFA 0316 031C 059A 0062;0061 1DFA 0316 031C 059A 0062;0061 1DFA 0316 031C 059A 0062; # (a◌֚◌̖◌᷺◌̜b; a◌᷺◌̖◌̜◌֚b; a◌᷺◌̖◌̜◌֚b; a◌᷺◌̖◌̜◌֚b; a◌᷺◌̖◌̜◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LEFT HALF RING BELOW, LATIN SMALL LETTER B
+0061 031C 059A 0316 1DFA 0062;0061 1DFA 031C 0316 059A 0062;0061 1DFA 031C 0316 059A 0062;0061 1DFA 031C 0316 059A 0062;0061 1DFA 031C 0316 059A 0062; # (a◌̜◌֚◌̖◌᷺b; a◌᷺◌̜◌̖◌֚b; a◌᷺◌̜◌̖◌֚b; a◌᷺◌̜◌̖◌֚b; a◌᷺◌̜◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LEFT HALF RING BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 031D 0062;0061 1DFA 0316 031D 059A 0062;0061 1DFA 0316 031D 059A 0062;0061 1DFA 0316 031D 059A 0062;0061 1DFA 0316 031D 059A 0062; # (a◌֚◌̖◌᷺◌Ìb; a◌᷺◌̖◌Ì◌֚b; a◌᷺◌̖◌Ì◌֚b; a◌᷺◌̖◌Ì◌֚b; a◌᷺◌̖◌Ì◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING UP TACK BELOW, LATIN SMALL LETTER B
+0061 031D 059A 0316 1DFA 0062;0061 1DFA 031D 0316 059A 0062;0061 1DFA 031D 0316 059A 0062;0061 1DFA 031D 0316 059A 0062;0061 1DFA 031D 0316 059A 0062; # (aâ—ŒÌ◌֚◌̖◌᷺b; a◌᷺◌Ì◌̖◌֚b; a◌᷺◌Ì◌̖◌֚b; a◌᷺◌Ì◌̖◌֚b; a◌᷺◌Ì◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING UP TACK BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 031E 0062;0061 1DFA 0316 031E 059A 0062;0061 1DFA 0316 031E 059A 0062;0061 1DFA 0316 031E 059A 0062;0061 1DFA 0316 031E 059A 0062; # (a◌֚◌̖◌᷺◌̞b; a◌᷺◌̖◌̞◌֚b; a◌᷺◌̖◌̞◌֚b; a◌᷺◌̖◌̞◌֚b; a◌᷺◌̖◌̞◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING DOWN TACK BELOW, LATIN SMALL LETTER B
+0061 031E 059A 0316 1DFA 0062;0061 1DFA 031E 0316 059A 0062;0061 1DFA 031E 0316 059A 0062;0061 1DFA 031E 0316 059A 0062;0061 1DFA 031E 0316 059A 0062; # (a◌̞◌֚◌̖◌᷺b; a◌᷺◌̞◌̖◌֚b; a◌᷺◌̞◌̖◌֚b; a◌᷺◌̞◌̖◌֚b; a◌᷺◌̞◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING DOWN TACK BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 031F 0062;0061 1DFA 0316 031F 059A 0062;0061 1DFA 0316 031F 059A 0062;0061 1DFA 0316 031F 059A 0062;0061 1DFA 0316 031F 059A 0062; # (a◌֚◌̖◌᷺◌̟b; a◌᷺◌̖◌̟◌֚b; a◌᷺◌̖◌̟◌֚b; a◌᷺◌̖◌̟◌֚b; a◌᷺◌̖◌̟◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING PLUS SIGN BELOW, LATIN SMALL LETTER B
+0061 031F 059A 0316 1DFA 0062;0061 1DFA 031F 0316 059A 0062;0061 1DFA 031F 0316 059A 0062;0061 1DFA 031F 0316 059A 0062;0061 1DFA 031F 0316 059A 0062; # (a◌̟◌֚◌̖◌᷺b; a◌᷺◌̟◌̖◌֚b; a◌᷺◌̟◌̖◌֚b; a◌᷺◌̟◌̖◌֚b; a◌᷺◌̟◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING PLUS SIGN BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0320 0062;0061 1DFA 0316 0320 059A 0062;0061 1DFA 0316 0320 059A 0062;0061 1DFA 0316 0320 059A 0062;0061 1DFA 0316 0320 059A 0062; # (a◌֚◌̖◌᷺◌̠b; a◌᷺◌̖◌̠◌֚b; a◌᷺◌̖◌̠◌֚b; a◌᷺◌̖◌̠◌֚b; a◌᷺◌̖◌̠◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING MINUS SIGN BELOW, LATIN SMALL LETTER B
+0061 0320 059A 0316 1DFA 0062;0061 1DFA 0320 0316 059A 0062;0061 1DFA 0320 0316 059A 0062;0061 1DFA 0320 0316 059A 0062;0061 1DFA 0320 0316 059A 0062; # (a◌̠◌֚◌̖◌᷺b; a◌᷺◌̠◌̖◌֚b; a◌᷺◌̠◌̖◌֚b; a◌᷺◌̠◌̖◌֚b; a◌᷺◌̠◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING MINUS SIGN BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 1DCE 0321 0F74 0321 0062;0061 0F74 0321 0321 1DCE 0062;0061 0F74 0321 0321 1DCE 0062;0061 0F74 0321 0321 1DCE 0062;0061 0F74 0321 0321 1DCE 0062; # (a◌᷎◌̡◌ུ◌̡b; a◌ུ◌̡◌̡◌᷎b; a◌ུ◌̡◌̡◌᷎b; a◌ུ◌̡◌̡◌᷎b; a◌ུ◌̡◌̡◌᷎b; ) LATIN SMALL LETTER A, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, COMBINING PALATALIZED HOOK BELOW, LATIN SMALL LETTER B
+0061 0321 1DCE 0321 0F74 0062;0061 0F74 0321 0321 1DCE 0062;0061 0F74 0321 0321 1DCE 0062;0061 0F74 0321 0321 1DCE 0062;0061 0F74 0321 0321 1DCE 0062; # (a◌̡◌᷎◌̡◌ུb; a◌ུ◌̡◌̡◌᷎b; a◌ུ◌̡◌̡◌᷎b; a◌ུ◌̡◌̡◌᷎b; a◌ུ◌̡◌̡◌᷎b; ) LATIN SMALL LETTER A, COMBINING PALATALIZED HOOK BELOW, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, LATIN SMALL LETTER B
+0061 1DCE 0321 0F74 0322 0062;0061 0F74 0321 0322 1DCE 0062;0061 0F74 0321 0322 1DCE 0062;0061 0F74 0321 0322 1DCE 0062;0061 0F74 0321 0322 1DCE 0062; # (a◌᷎◌̡◌ུ◌̢b; a◌ུ◌̡◌̢◌᷎b; a◌ུ◌̡◌̢◌᷎b; a◌ུ◌̡◌̢◌᷎b; a◌ུ◌̡◌̢◌᷎b; ) LATIN SMALL LETTER A, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, COMBINING RETROFLEX HOOK BELOW, LATIN SMALL LETTER B
+0061 0322 1DCE 0321 0F74 0062;0061 0F74 0322 0321 1DCE 0062;0061 0F74 0322 0321 1DCE 0062;0061 0F74 0322 0321 1DCE 0062;0061 0F74 0322 0321 1DCE 0062; # (a◌̢◌᷎◌̡◌ུb; a◌ུ◌̢◌̡◌᷎b; a◌ུ◌̢◌̡◌᷎b; a◌ུ◌̢◌̡◌᷎b; a◌ུ◌̢◌̡◌᷎b; ) LATIN SMALL LETTER A, COMBINING RETROFLEX HOOK BELOW, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0323 0062;0061 1DFA 0316 0323 059A 0062;0061 1DFA 0316 0323 059A 0062;0061 1DFA 0316 0323 059A 0062;0061 1DFA 0316 0323 059A 0062; # (a◌֚◌̖◌᷺◌̣b; a◌᷺◌̖◌̣◌֚b; a◌᷺◌̖◌̣◌֚b; a◌᷺◌̖◌̣◌֚b; a◌᷺◌̖◌̣◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING DOT BELOW, LATIN SMALL LETTER B
+0061 0323 059A 0316 1DFA 0062;1EA1 1DFA 0316 059A 0062;0061 1DFA 0323 0316 059A 0062;1EA1 1DFA 0316 059A 0062;0061 1DFA 0323 0316 059A 0062; # (a◌̣◌֚◌̖◌᷺b; ạ◌᷺◌̖◌֚b; a◌᷺◌̣◌̖◌֚b; ạ◌᷺◌̖◌֚b; a◌᷺◌̣◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING DOT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0324 0062;0061 1DFA 0316 0324 059A 0062;0061 1DFA 0316 0324 059A 0062;0061 1DFA 0316 0324 059A 0062;0061 1DFA 0316 0324 059A 0062; # (a◌֚◌̖◌᷺◌̤b; a◌᷺◌̖◌̤◌֚b; a◌᷺◌̖◌̤◌֚b; a◌᷺◌̖◌̤◌֚b; a◌᷺◌̖◌̤◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING DIAERESIS BELOW, LATIN SMALL LETTER B
+0061 0324 059A 0316 1DFA 0062;0061 1DFA 0324 0316 059A 0062;0061 1DFA 0324 0316 059A 0062;0061 1DFA 0324 0316 059A 0062;0061 1DFA 0324 0316 059A 0062; # (a◌̤◌֚◌̖◌᷺b; a◌᷺◌̤◌̖◌֚b; a◌᷺◌̤◌̖◌֚b; a◌᷺◌̤◌̖◌֚b; a◌᷺◌̤◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING DIAERESIS BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0325 0062;0061 1DFA 0316 0325 059A 0062;0061 1DFA 0316 0325 059A 0062;0061 1DFA 0316 0325 059A 0062;0061 1DFA 0316 0325 059A 0062; # (a◌֚◌̖◌᷺◌̥b; a◌᷺◌̖◌̥◌֚b; a◌᷺◌̖◌̥◌֚b; a◌᷺◌̖◌̥◌֚b; a◌᷺◌̖◌̥◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING RING BELOW, LATIN SMALL LETTER B
+0061 0325 059A 0316 1DFA 0062;1E01 1DFA 0316 059A 0062;0061 1DFA 0325 0316 059A 0062;1E01 1DFA 0316 059A 0062;0061 1DFA 0325 0316 059A 0062; # (a◌̥◌֚◌̖◌᷺b; á¸â—Œá·ºâ—ŒÌ–◌֚b; a◌᷺◌̥◌̖◌֚b; á¸â—Œá·ºâ—ŒÌ–◌֚b; a◌᷺◌̥◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING RING BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0326 0062;0061 1DFA 0316 0326 059A 0062;0061 1DFA 0316 0326 059A 0062;0061 1DFA 0316 0326 059A 0062;0061 1DFA 0316 0326 059A 0062; # (a◌֚◌̖◌᷺◌̦b; a◌᷺◌̖◌̦◌֚b; a◌᷺◌̖◌̦◌֚b; a◌᷺◌̖◌̦◌֚b; a◌᷺◌̖◌̦◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING COMMA BELOW, LATIN SMALL LETTER B
+0061 0326 059A 0316 1DFA 0062;0061 1DFA 0326 0316 059A 0062;0061 1DFA 0326 0316 059A 0062;0061 1DFA 0326 0316 059A 0062;0061 1DFA 0326 0316 059A 0062; # (a◌̦◌֚◌̖◌᷺b; a◌᷺◌̦◌̖◌֚b; a◌᷺◌̦◌̖◌֚b; a◌᷺◌̦◌̖◌֚b; a◌᷺◌̦◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING COMMA BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 1DCE 0321 0F74 0327 0062;0061 0F74 0321 0327 1DCE 0062;0061 0F74 0321 0327 1DCE 0062;0061 0F74 0321 0327 1DCE 0062;0061 0F74 0321 0327 1DCE 0062; # (a◌᷎◌̡◌ུ◌̧b; a◌ུ◌̡◌̧◌᷎b; a◌ུ◌̡◌̧◌᷎b; a◌ུ◌̡◌̧◌᷎b; a◌ུ◌̡◌̧◌᷎b; ) LATIN SMALL LETTER A, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, COMBINING CEDILLA, LATIN SMALL LETTER B
+0061 0327 1DCE 0321 0F74 0062;0061 0F74 0327 0321 1DCE 0062;0061 0F74 0327 0321 1DCE 0062;0061 0F74 0327 0321 1DCE 0062;0061 0F74 0327 0321 1DCE 0062; # (a◌̧◌᷎◌̡◌ུb; a◌ུ◌̧◌̡◌᷎b; a◌ུ◌̧◌̡◌᷎b; a◌ུ◌̧◌̡◌᷎b; a◌ུ◌̧◌̡◌᷎b; ) LATIN SMALL LETTER A, COMBINING CEDILLA, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, LATIN SMALL LETTER B
+0061 1DCE 0321 0F74 0328 0062;0061 0F74 0321 0328 1DCE 0062;0061 0F74 0321 0328 1DCE 0062;0061 0F74 0321 0328 1DCE 0062;0061 0F74 0321 0328 1DCE 0062; # (a◌᷎◌̡◌ུ◌̨b; a◌ུ◌̡◌̨◌᷎b; a◌ུ◌̡◌̨◌᷎b; a◌ུ◌̡◌̨◌᷎b; a◌ུ◌̡◌̨◌᷎b; ) LATIN SMALL LETTER A, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, COMBINING OGONEK, LATIN SMALL LETTER B
+0061 0328 1DCE 0321 0F74 0062;0105 0F74 0321 1DCE 0062;0061 0F74 0328 0321 1DCE 0062;0105 0F74 0321 1DCE 0062;0061 0F74 0328 0321 1DCE 0062; # (a◌̨◌᷎◌̡◌ུb; ą◌ུ◌̡◌᷎b; a◌ུ◌̨◌̡◌᷎b; ą◌ུ◌̡◌᷎b; a◌ུ◌̨◌̡◌᷎b; ) LATIN SMALL LETTER A, COMBINING OGONEK, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0329 0062;0061 1DFA 0316 0329 059A 0062;0061 1DFA 0316 0329 059A 0062;0061 1DFA 0316 0329 059A 0062;0061 1DFA 0316 0329 059A 0062; # (a◌֚◌̖◌᷺◌̩b; a◌᷺◌̖◌̩◌֚b; a◌᷺◌̖◌̩◌֚b; a◌᷺◌̖◌̩◌֚b; a◌᷺◌̖◌̩◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING VERTICAL LINE BELOW, LATIN SMALL LETTER B
+0061 0329 059A 0316 1DFA 0062;0061 1DFA 0329 0316 059A 0062;0061 1DFA 0329 0316 059A 0062;0061 1DFA 0329 0316 059A 0062;0061 1DFA 0329 0316 059A 0062; # (a◌̩◌֚◌̖◌᷺b; a◌᷺◌̩◌̖◌֚b; a◌᷺◌̩◌̖◌֚b; a◌᷺◌̩◌̖◌֚b; a◌᷺◌̩◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING VERTICAL LINE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 032A 0062;0061 1DFA 0316 032A 059A 0062;0061 1DFA 0316 032A 059A 0062;0061 1DFA 0316 032A 059A 0062;0061 1DFA 0316 032A 059A 0062; # (a◌֚◌̖◌᷺◌̪b; a◌᷺◌̖◌̪◌֚b; a◌᷺◌̖◌̪◌֚b; a◌᷺◌̖◌̪◌֚b; a◌᷺◌̖◌̪◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING BRIDGE BELOW, LATIN SMALL LETTER B
+0061 032A 059A 0316 1DFA 0062;0061 1DFA 032A 0316 059A 0062;0061 1DFA 032A 0316 059A 0062;0061 1DFA 032A 0316 059A 0062;0061 1DFA 032A 0316 059A 0062; # (a◌̪◌֚◌̖◌᷺b; a◌᷺◌̪◌̖◌֚b; a◌᷺◌̪◌̖◌֚b; a◌᷺◌̪◌̖◌֚b; a◌᷺◌̪◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING BRIDGE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 032B 0062;0061 1DFA 0316 032B 059A 0062;0061 1DFA 0316 032B 059A 0062;0061 1DFA 0316 032B 059A 0062;0061 1DFA 0316 032B 059A 0062; # (a◌֚◌̖◌᷺◌̫b; a◌᷺◌̖◌̫◌֚b; a◌᷺◌̖◌̫◌֚b; a◌᷺◌̖◌̫◌֚b; a◌᷺◌̖◌̫◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING INVERTED DOUBLE ARCH BELOW, LATIN SMALL LETTER B
+0061 032B 059A 0316 1DFA 0062;0061 1DFA 032B 0316 059A 0062;0061 1DFA 032B 0316 059A 0062;0061 1DFA 032B 0316 059A 0062;0061 1DFA 032B 0316 059A 0062; # (a◌̫◌֚◌̖◌᷺b; a◌᷺◌̫◌̖◌֚b; a◌᷺◌̫◌̖◌֚b; a◌᷺◌̫◌̖◌֚b; a◌᷺◌̫◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING INVERTED DOUBLE ARCH BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 032C 0062;0061 1DFA 0316 032C 059A 0062;0061 1DFA 0316 032C 059A 0062;0061 1DFA 0316 032C 059A 0062;0061 1DFA 0316 032C 059A 0062; # (a◌֚◌̖◌᷺◌̬b; a◌᷺◌̖◌̬◌֚b; a◌᷺◌̖◌̬◌֚b; a◌᷺◌̖◌̬◌֚b; a◌᷺◌̖◌̬◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING CARON BELOW, LATIN SMALL LETTER B
+0061 032C 059A 0316 1DFA 0062;0061 1DFA 032C 0316 059A 0062;0061 1DFA 032C 0316 059A 0062;0061 1DFA 032C 0316 059A 0062;0061 1DFA 032C 0316 059A 0062; # (a◌̬◌֚◌̖◌᷺b; a◌᷺◌̬◌̖◌֚b; a◌᷺◌̬◌̖◌֚b; a◌᷺◌̬◌̖◌֚b; a◌᷺◌̬◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING CARON BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 032D 0062;0061 1DFA 0316 032D 059A 0062;0061 1DFA 0316 032D 059A 0062;0061 1DFA 0316 032D 059A 0062;0061 1DFA 0316 032D 059A 0062; # (a◌֚◌̖◌᷺◌̭b; a◌᷺◌̖◌̭◌֚b; a◌᷺◌̖◌̭◌֚b; a◌᷺◌̖◌̭◌֚b; a◌᷺◌̖◌̭◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING CIRCUMFLEX ACCENT BELOW, LATIN SMALL LETTER B
+0061 032D 059A 0316 1DFA 0062;0061 1DFA 032D 0316 059A 0062;0061 1DFA 032D 0316 059A 0062;0061 1DFA 032D 0316 059A 0062;0061 1DFA 032D 0316 059A 0062; # (a◌̭◌֚◌̖◌᷺b; a◌᷺◌̭◌̖◌֚b; a◌᷺◌̭◌̖◌֚b; a◌᷺◌̭◌̖◌֚b; a◌᷺◌̭◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING CIRCUMFLEX ACCENT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 032E 0062;0061 1DFA 0316 032E 059A 0062;0061 1DFA 0316 032E 059A 0062;0061 1DFA 0316 032E 059A 0062;0061 1DFA 0316 032E 059A 0062; # (a◌֚◌̖◌᷺◌̮b; a◌᷺◌̖◌̮◌֚b; a◌᷺◌̖◌̮◌֚b; a◌᷺◌̖◌̮◌֚b; a◌᷺◌̖◌̮◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING BREVE BELOW, LATIN SMALL LETTER B
+0061 032E 059A 0316 1DFA 0062;0061 1DFA 032E 0316 059A 0062;0061 1DFA 032E 0316 059A 0062;0061 1DFA 032E 0316 059A 0062;0061 1DFA 032E 0316 059A 0062; # (a◌̮◌֚◌̖◌᷺b; a◌᷺◌̮◌̖◌֚b; a◌᷺◌̮◌̖◌֚b; a◌᷺◌̮◌̖◌֚b; a◌᷺◌̮◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING BREVE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 032F 0062;0061 1DFA 0316 032F 059A 0062;0061 1DFA 0316 032F 059A 0062;0061 1DFA 0316 032F 059A 0062;0061 1DFA 0316 032F 059A 0062; # (a◌֚◌̖◌᷺◌̯b; a◌᷺◌̖◌̯◌֚b; a◌᷺◌̖◌̯◌֚b; a◌᷺◌̖◌̯◌֚b; a◌᷺◌̖◌̯◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING INVERTED BREVE BELOW, LATIN SMALL LETTER B
+0061 032F 059A 0316 1DFA 0062;0061 1DFA 032F 0316 059A 0062;0061 1DFA 032F 0316 059A 0062;0061 1DFA 032F 0316 059A 0062;0061 1DFA 032F 0316 059A 0062; # (a◌̯◌֚◌̖◌᷺b; a◌᷺◌̯◌̖◌֚b; a◌᷺◌̯◌̖◌֚b; a◌᷺◌̯◌̖◌֚b; a◌᷺◌̯◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING INVERTED BREVE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0330 0062;0061 1DFA 0316 0330 059A 0062;0061 1DFA 0316 0330 059A 0062;0061 1DFA 0316 0330 059A 0062;0061 1DFA 0316 0330 059A 0062; # (a◌֚◌̖◌᷺◌̰b; a◌᷺◌̖◌̰◌֚b; a◌᷺◌̖◌̰◌֚b; a◌᷺◌̖◌̰◌֚b; a◌᷺◌̖◌̰◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING TILDE BELOW, LATIN SMALL LETTER B
+0061 0330 059A 0316 1DFA 0062;0061 1DFA 0330 0316 059A 0062;0061 1DFA 0330 0316 059A 0062;0061 1DFA 0330 0316 059A 0062;0061 1DFA 0330 0316 059A 0062; # (a◌̰◌֚◌̖◌᷺b; a◌᷺◌̰◌̖◌֚b; a◌᷺◌̰◌̖◌֚b; a◌᷺◌̰◌̖◌֚b; a◌᷺◌̰◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING TILDE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0331 0062;0061 1DFA 0316 0331 059A 0062;0061 1DFA 0316 0331 059A 0062;0061 1DFA 0316 0331 059A 0062;0061 1DFA 0316 0331 059A 0062; # (a◌֚◌̖◌᷺◌̱b; a◌᷺◌̖◌̱◌֚b; a◌᷺◌̖◌̱◌֚b; a◌᷺◌̖◌̱◌֚b; a◌᷺◌̖◌̱◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING MACRON BELOW, LATIN SMALL LETTER B
+0061 0331 059A 0316 1DFA 0062;0061 1DFA 0331 0316 059A 0062;0061 1DFA 0331 0316 059A 0062;0061 1DFA 0331 0316 059A 0062;0061 1DFA 0331 0316 059A 0062; # (a◌̱◌֚◌̖◌᷺b; a◌᷺◌̱◌̖◌֚b; a◌᷺◌̱◌̖◌֚b; a◌᷺◌̱◌̖◌֚b; a◌᷺◌̱◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING MACRON BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0332 0062;0061 1DFA 0316 0332 059A 0062;0061 1DFA 0316 0332 059A 0062;0061 1DFA 0316 0332 059A 0062;0061 1DFA 0316 0332 059A 0062; # (a◌֚◌̖◌᷺◌̲b; a◌᷺◌̖◌̲◌֚b; a◌᷺◌̖◌̲◌֚b; a◌᷺◌̖◌̲◌֚b; a◌᷺◌̖◌̲◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LOW LINE, LATIN SMALL LETTER B
+0061 0332 059A 0316 1DFA 0062;0061 1DFA 0332 0316 059A 0062;0061 1DFA 0332 0316 059A 0062;0061 1DFA 0332 0316 059A 0062;0061 1DFA 0332 0316 059A 0062; # (a◌̲◌֚◌̖◌᷺b; a◌᷺◌̲◌̖◌֚b; a◌᷺◌̲◌̖◌֚b; a◌᷺◌̲◌̖◌֚b; a◌᷺◌̲◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LOW LINE, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0333 0062;0061 1DFA 0316 0333 059A 0062;0061 1DFA 0316 0333 059A 0062;0061 1DFA 0316 0333 059A 0062;0061 1DFA 0316 0333 059A 0062; # (a◌֚◌̖◌᷺◌̳b; a◌᷺◌̖◌̳◌֚b; a◌᷺◌̖◌̳◌֚b; a◌᷺◌̖◌̳◌֚b; a◌᷺◌̖◌̳◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING DOUBLE LOW LINE, LATIN SMALL LETTER B
+0061 0333 059A 0316 1DFA 0062;0061 1DFA 0333 0316 059A 0062;0061 1DFA 0333 0316 059A 0062;0061 1DFA 0333 0316 059A 0062;0061 1DFA 0333 0316 059A 0062; # (a◌̳◌֚◌̖◌᷺b; a◌᷺◌̳◌̖◌֚b; a◌᷺◌̳◌̖◌֚b; a◌᷺◌̳◌̖◌֚b; a◌᷺◌̳◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING DOUBLE LOW LINE, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 16FF0 0334 0334 0062;0061 0334 0334 16FF0 0062;0061 0334 0334 16FF0 0062;0061 0334 0334 16FF0 0062;0061 0334 0334 16FF0 0062; # (a𖿰◌̴◌̴b; a◌̴◌̴𖿰b; a◌̴◌̴𖿰b; a◌̴◌̴𖿰b; a◌̴◌̴𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 0334 16FF0 0334 0062;0061 0334 0334 16FF0 0062;0061 0334 0334 16FF0 0062;0061 0334 0334 16FF0 0062;0061 0334 0334 16FF0 0062; # (a◌̴𖿰◌̴b; a◌̴◌̴𖿰b; a◌̴◌̴𖿰b; a◌̴◌̴𖿰b; a◌̴◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING TILDE OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 0335 0062;0061 0334 0335 16FF0 0062;0061 0334 0335 16FF0 0062;0061 0334 0335 16FF0 0062;0061 0334 0335 16FF0 0062; # (a𖿰◌̴◌̵b; a◌̴◌̵𖿰b; a◌̴◌̵𖿰b; a◌̴◌̵𖿰b; a◌̴◌̵𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING SHORT STROKE OVERLAY, LATIN SMALL LETTER B
+0061 0335 16FF0 0334 0062;0061 0335 0334 16FF0 0062;0061 0335 0334 16FF0 0062;0061 0335 0334 16FF0 0062;0061 0335 0334 16FF0 0062; # (a◌̵𖿰◌̴b; a◌̵◌̴𖿰b; a◌̵◌̴𖿰b; a◌̵◌̴𖿰b; a◌̵◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING SHORT STROKE OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 0336 0062;0061 0334 0336 16FF0 0062;0061 0334 0336 16FF0 0062;0061 0334 0336 16FF0 0062;0061 0334 0336 16FF0 0062; # (a𖿰◌̴◌̶b; a◌̴◌̶𖿰b; a◌̴◌̶𖿰b; a◌̴◌̶𖿰b; a◌̴◌̶𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING LONG STROKE OVERLAY, LATIN SMALL LETTER B
+0061 0336 16FF0 0334 0062;0061 0336 0334 16FF0 0062;0061 0336 0334 16FF0 0062;0061 0336 0334 16FF0 0062;0061 0336 0334 16FF0 0062; # (a◌̶𖿰◌̴b; a◌̶◌̴𖿰b; a◌̶◌̴𖿰b; a◌̶◌̴𖿰b; a◌̶◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING LONG STROKE OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 0337 0062;0061 0334 0337 16FF0 0062;0061 0334 0337 16FF0 0062;0061 0334 0337 16FF0 0062;0061 0334 0337 16FF0 0062; # (a𖿰◌̴◌̷b; a◌̴◌̷𖿰b; a◌̴◌̷𖿰b; a◌̴◌̷𖿰b; a◌̴◌̷𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING SHORT SOLIDUS OVERLAY, LATIN SMALL LETTER B
+0061 0337 16FF0 0334 0062;0061 0337 0334 16FF0 0062;0061 0337 0334 16FF0 0062;0061 0337 0334 16FF0 0062;0061 0337 0334 16FF0 0062; # (a◌̷𖿰◌̴b; a◌̷◌̴𖿰b; a◌̷◌̴𖿰b; a◌̷◌̴𖿰b; a◌̷◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING SHORT SOLIDUS OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 0338 0062;0061 0334 0338 16FF0 0062;0061 0334 0338 16FF0 0062;0061 0334 0338 16FF0 0062;0061 0334 0338 16FF0 0062; # (a𖿰◌̴◌̸b; a◌̴◌̸𖿰b; a◌̴◌̸𖿰b; a◌̴◌̸𖿰b; a◌̴◌̸𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING LONG SOLIDUS OVERLAY, LATIN SMALL LETTER B
+0061 0338 16FF0 0334 0062;0061 0338 0334 16FF0 0062;0061 0338 0334 16FF0 0062;0061 0338 0334 16FF0 0062;0061 0338 0334 16FF0 0062; # (a◌̸𖿰◌̴b; a◌̸◌̴𖿰b; a◌̸◌̴𖿰b; a◌̸◌̴𖿰b; a◌̸◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING LONG SOLIDUS OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0339 0062;0061 1DFA 0316 0339 059A 0062;0061 1DFA 0316 0339 059A 0062;0061 1DFA 0316 0339 059A 0062;0061 1DFA 0316 0339 059A 0062; # (a◌֚◌̖◌᷺◌̹b; a◌᷺◌̖◌̹◌֚b; a◌᷺◌̖◌̹◌֚b; a◌᷺◌̖◌̹◌֚b; a◌᷺◌̖◌̹◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING RIGHT HALF RING BELOW, LATIN SMALL LETTER B
+0061 0339 059A 0316 1DFA 0062;0061 1DFA 0339 0316 059A 0062;0061 1DFA 0339 0316 059A 0062;0061 1DFA 0339 0316 059A 0062;0061 1DFA 0339 0316 059A 0062; # (a◌̹◌֚◌̖◌᷺b; a◌᷺◌̹◌̖◌֚b; a◌᷺◌̹◌̖◌֚b; a◌᷺◌̹◌̖◌֚b; a◌᷺◌̹◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING RIGHT HALF RING BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 033A 0062;0061 1DFA 0316 033A 059A 0062;0061 1DFA 0316 033A 059A 0062;0061 1DFA 0316 033A 059A 0062;0061 1DFA 0316 033A 059A 0062; # (a◌֚◌̖◌᷺◌̺b; a◌᷺◌̖◌̺◌֚b; a◌᷺◌̖◌̺◌֚b; a◌᷺◌̖◌̺◌֚b; a◌᷺◌̖◌̺◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING INVERTED BRIDGE BELOW, LATIN SMALL LETTER B
+0061 033A 059A 0316 1DFA 0062;0061 1DFA 033A 0316 059A 0062;0061 1DFA 033A 0316 059A 0062;0061 1DFA 033A 0316 059A 0062;0061 1DFA 033A 0316 059A 0062; # (a◌̺◌֚◌̖◌᷺b; a◌᷺◌̺◌̖◌֚b; a◌᷺◌̺◌̖◌֚b; a◌᷺◌̺◌̖◌֚b; a◌᷺◌̺◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING INVERTED BRIDGE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 033B 0062;0061 1DFA 0316 033B 059A 0062;0061 1DFA 0316 033B 059A 0062;0061 1DFA 0316 033B 059A 0062;0061 1DFA 0316 033B 059A 0062; # (a◌֚◌̖◌᷺◌̻b; a◌᷺◌̖◌̻◌֚b; a◌᷺◌̖◌̻◌֚b; a◌᷺◌̖◌̻◌֚b; a◌᷺◌̖◌̻◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING SQUARE BELOW, LATIN SMALL LETTER B
+0061 033B 059A 0316 1DFA 0062;0061 1DFA 033B 0316 059A 0062;0061 1DFA 033B 0316 059A 0062;0061 1DFA 033B 0316 059A 0062;0061 1DFA 033B 0316 059A 0062; # (a◌̻◌֚◌̖◌᷺b; a◌᷺◌̻◌̖◌֚b; a◌᷺◌̻◌̖◌֚b; a◌᷺◌̻◌̖◌֚b; a◌᷺◌̻◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING SQUARE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 033C 0062;0061 1DFA 0316 033C 059A 0062;0061 1DFA 0316 033C 059A 0062;0061 1DFA 0316 033C 059A 0062;0061 1DFA 0316 033C 059A 0062; # (a◌֚◌̖◌᷺◌̼b; a◌᷺◌̖◌̼◌֚b; a◌᷺◌̖◌̼◌֚b; a◌᷺◌̖◌̼◌֚b; a◌᷺◌̖◌̼◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING SEAGULL BELOW, LATIN SMALL LETTER B
+0061 033C 059A 0316 1DFA 0062;0061 1DFA 033C 0316 059A 0062;0061 1DFA 033C 0316 059A 0062;0061 1DFA 033C 0316 059A 0062;0061 1DFA 033C 0316 059A 0062; # (a◌̼◌֚◌̖◌᷺b; a◌᷺◌̼◌̖◌֚b; a◌᷺◌̼◌̖◌֚b; a◌᷺◌̼◌̖◌֚b; a◌᷺◌̼◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING SEAGULL BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 033D 0062;00E0 05AE 033D 0315 0062;0061 05AE 0300 033D 0315 0062;00E0 05AE 033D 0315 0062;0061 05AE 0300 033D 0315 0062; # (a◌̕◌̀◌֮◌̽b; à◌֮◌̽◌̕b; a◌֮◌̀◌̽◌̕b; à◌֮◌̽◌̕b; a◌֮◌̀◌̽◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING X ABOVE, LATIN SMALL LETTER B
+0061 033D 0315 0300 05AE 0062;0061 05AE 033D 0300 0315 0062;0061 05AE 033D 0300 0315 0062;0061 05AE 033D 0300 0315 0062;0061 05AE 033D 0300 0315 0062; # (a◌̽◌̕◌̀◌֮b; a◌֮◌̽◌̀◌̕b; a◌֮◌̽◌̀◌̕b; a◌֮◌̽◌̀◌̕b; a◌֮◌̽◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING X ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 033E 0062;00E0 05AE 033E 0315 0062;0061 05AE 0300 033E 0315 0062;00E0 05AE 033E 0315 0062;0061 05AE 0300 033E 0315 0062; # (a◌̕◌̀◌֮◌̾b; à◌֮◌̾◌̕b; a◌֮◌̀◌̾◌̕b; à◌֮◌̾◌̕b; a◌֮◌̀◌̾◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING VERTICAL TILDE, LATIN SMALL LETTER B
+0061 033E 0315 0300 05AE 0062;0061 05AE 033E 0300 0315 0062;0061 05AE 033E 0300 0315 0062;0061 05AE 033E 0300 0315 0062;0061 05AE 033E 0300 0315 0062; # (a◌̾◌̕◌̀◌֮b; a◌֮◌̾◌̀◌̕b; a◌֮◌̾◌̀◌̕b; a◌֮◌̾◌̀◌̕b; a◌֮◌̾◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING VERTICAL TILDE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 033F 0062;00E0 05AE 033F 0315 0062;0061 05AE 0300 033F 0315 0062;00E0 05AE 033F 0315 0062;0061 05AE 0300 033F 0315 0062; # (a◌̕◌̀◌֮◌̿b; à◌֮◌̿◌̕b; a◌֮◌̀◌̿◌̕b; à◌֮◌̿◌̕b; a◌֮◌̀◌̿◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOUBLE OVERLINE, LATIN SMALL LETTER B
+0061 033F 0315 0300 05AE 0062;0061 05AE 033F 0300 0315 0062;0061 05AE 033F 0300 0315 0062;0061 05AE 033F 0300 0315 0062;0061 05AE 033F 0300 0315 0062; # (a◌̿◌̕◌̀◌֮b; a◌֮◌̿◌̀◌̕b; a◌֮◌̿◌̀◌̕b; a◌֮◌̿◌̀◌̕b; a◌֮◌̿◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOUBLE OVERLINE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0340 0062;00E0 05AE 0300 0315 0062;0061 05AE 0300 0300 0315 0062;00E0 05AE 0300 0315 0062;0061 05AE 0300 0300 0315 0062; # (a◌̕◌̀◌֮◌̀b; à◌֮◌̀◌̕b; a◌֮◌̀◌̀◌̕b; à◌֮◌̀◌̕b; a◌֮◌̀◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRAVE TONE MARK, LATIN SMALL LETTER B
+0061 0340 0315 0300 05AE 0062;00E0 05AE 0300 0315 0062;0061 05AE 0300 0300 0315 0062;00E0 05AE 0300 0315 0062;0061 05AE 0300 0300 0315 0062; # (a◌̀◌̕◌̀◌֮b; à◌֮◌̀◌̕b; a◌֮◌̀◌̀◌̕b; à◌֮◌̀◌̕b; a◌֮◌̀◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GRAVE TONE MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0341 0062;00E0 05AE 0301 0315 0062;0061 05AE 0300 0301 0315 0062;00E0 05AE 0301 0315 0062;0061 05AE 0300 0301 0315 0062; # (a◌̕◌̀◌֮◌Íb; à◌֮◌Ì◌̕b; a◌֮◌̀◌Ì◌̕b; à◌֮◌Ì◌̕b; a◌֮◌̀◌Ì◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING ACUTE TONE MARK, LATIN SMALL LETTER B
+0061 0341 0315 0300 05AE 0062;00E1 05AE 0300 0315 0062;0061 05AE 0301 0300 0315 0062;00E1 05AE 0300 0315 0062;0061 05AE 0301 0300 0315 0062; # (aâ—ŒÍ◌̕◌̀◌֮b; á◌֮◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; á◌֮◌̀◌̕b; a◌֮◌Ì◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING ACUTE TONE MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0342 0062;00E0 05AE 0342 0315 0062;0061 05AE 0300 0342 0315 0062;00E0 05AE 0342 0315 0062;0061 05AE 0300 0342 0315 0062; # (a◌̕◌̀◌֮◌͂b; à◌֮◌͂◌̕b; a◌֮◌̀◌͂◌̕b; à◌֮◌͂◌̕b; a◌֮◌̀◌͂◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GREEK PERISPOMENI, LATIN SMALL LETTER B
+0061 0342 0315 0300 05AE 0062;0061 05AE 0342 0300 0315 0062;0061 05AE 0342 0300 0315 0062;0061 05AE 0342 0300 0315 0062;0061 05AE 0342 0300 0315 0062; # (a◌͂◌̕◌̀◌֮b; a◌֮◌͂◌̀◌̕b; a◌֮◌͂◌̀◌̕b; a◌֮◌͂◌̀◌̕b; a◌֮◌͂◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GREEK PERISPOMENI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0343 0062;00E0 05AE 0313 0315 0062;0061 05AE 0300 0313 0315 0062;00E0 05AE 0313 0315 0062;0061 05AE 0300 0313 0315 0062; # (a◌̕◌̀◌֮◌̓b; à◌֮◌̓◌̕b; a◌֮◌̀◌̓◌̕b; à◌֮◌̓◌̕b; a◌֮◌̀◌̓◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GREEK KORONIS, LATIN SMALL LETTER B
+0061 0343 0315 0300 05AE 0062;0061 05AE 0313 0300 0315 0062;0061 05AE 0313 0300 0315 0062;0061 05AE 0313 0300 0315 0062;0061 05AE 0313 0300 0315 0062; # (a◌̓◌̕◌̀◌֮b; a◌֮◌̓◌̀◌̕b; a◌֮◌̓◌̀◌̕b; a◌֮◌̓◌̀◌̕b; a◌֮◌̓◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GREEK KORONIS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0344 0062;00E0 05AE 0308 0301 0315 0062;0061 05AE 0300 0308 0301 0315 0062;00E0 05AE 0308 0301 0315 0062;0061 05AE 0300 0308 0301 0315 0062; # (a◌̕◌̀◌֮◌̈́b; à◌֮◌̈◌Ì◌̕b; a◌֮◌̀◌̈◌Ì◌̕b; à◌֮◌̈◌Ì◌̕b; a◌֮◌̀◌̈◌Ì◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GREEK DIALYTIKA TONOS, LATIN SMALL LETTER B
+0061 0344 0315 0300 05AE 0062;00E4 05AE 0301 0300 0315 0062;0061 05AE 0308 0301 0300 0315 0062;00E4 05AE 0301 0300 0315 0062;0061 05AE 0308 0301 0300 0315 0062; # (a◌̈́◌̕◌̀◌֮b; ä◌֮◌Ì◌̀◌̕b; a◌֮◌̈◌Ì◌̀◌̕b; ä◌֮◌Ì◌̀◌̕b; a◌֮◌̈◌Ì◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GREEK DIALYTIKA TONOS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0345 035D 0345 0062;0061 035D 0345 0345 0062;0061 035D 0345 0345 0062;0061 035D 0345 0345 0062;0061 035D 0345 0345 0062; # (a◌ͅ◌Í◌ͅb; aâ—ŒÍ◌ͅ◌ͅb; aâ—ŒÍ◌ͅ◌ͅb; aâ—ŒÍ◌ͅ◌ͅb; aâ—ŒÍ◌ͅ◌ͅb; ) LATIN SMALL LETTER A, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING GREEK YPOGEGRAMMENI, LATIN SMALL LETTER B
+0061 0345 0345 035D 0062;0061 035D 0345 0345 0062;0061 035D 0345 0345 0062;0061 035D 0345 0345 0062;0061 035D 0345 0345 0062; # (a◌ͅ◌ͅ◌Íb; aâ—ŒÍ◌ͅ◌ͅb; aâ—ŒÍ◌ͅ◌ͅb; aâ—ŒÍ◌ͅ◌ͅb; aâ—ŒÍ◌ͅ◌ͅb; ) LATIN SMALL LETTER A, COMBINING GREEK YPOGEGRAMMENI, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0346 0062;00E0 05AE 0346 0315 0062;0061 05AE 0300 0346 0315 0062;00E0 05AE 0346 0315 0062;0061 05AE 0300 0346 0315 0062; # (a◌̕◌̀◌֮◌͆b; à◌֮◌͆◌̕b; a◌֮◌̀◌͆◌̕b; à◌֮◌͆◌̕b; a◌֮◌̀◌͆◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING BRIDGE ABOVE, LATIN SMALL LETTER B
+0061 0346 0315 0300 05AE 0062;0061 05AE 0346 0300 0315 0062;0061 05AE 0346 0300 0315 0062;0061 05AE 0346 0300 0315 0062;0061 05AE 0346 0300 0315 0062; # (a◌͆◌̕◌̀◌֮b; a◌֮◌͆◌̀◌̕b; a◌֮◌͆◌̀◌̕b; a◌֮◌͆◌̀◌̕b; a◌֮◌͆◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING BRIDGE ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0347 0062;0061 1DFA 0316 0347 059A 0062;0061 1DFA 0316 0347 059A 0062;0061 1DFA 0316 0347 059A 0062;0061 1DFA 0316 0347 059A 0062; # (a◌֚◌̖◌᷺◌͇b; a◌᷺◌̖◌͇◌֚b; a◌᷺◌̖◌͇◌֚b; a◌᷺◌̖◌͇◌֚b; a◌᷺◌̖◌͇◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING EQUALS SIGN BELOW, LATIN SMALL LETTER B
+0061 0347 059A 0316 1DFA 0062;0061 1DFA 0347 0316 059A 0062;0061 1DFA 0347 0316 059A 0062;0061 1DFA 0347 0316 059A 0062;0061 1DFA 0347 0316 059A 0062; # (a◌͇◌֚◌̖◌᷺b; a◌᷺◌͇◌̖◌֚b; a◌᷺◌͇◌̖◌֚b; a◌᷺◌͇◌̖◌֚b; a◌᷺◌͇◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING EQUALS SIGN BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0348 0062;0061 1DFA 0316 0348 059A 0062;0061 1DFA 0316 0348 059A 0062;0061 1DFA 0316 0348 059A 0062;0061 1DFA 0316 0348 059A 0062; # (a◌֚◌̖◌᷺◌͈b; a◌᷺◌̖◌͈◌֚b; a◌᷺◌̖◌͈◌֚b; a◌᷺◌̖◌͈◌֚b; a◌᷺◌̖◌͈◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING DOUBLE VERTICAL LINE BELOW, LATIN SMALL LETTER B
+0061 0348 059A 0316 1DFA 0062;0061 1DFA 0348 0316 059A 0062;0061 1DFA 0348 0316 059A 0062;0061 1DFA 0348 0316 059A 0062;0061 1DFA 0348 0316 059A 0062; # (a◌͈◌֚◌̖◌᷺b; a◌᷺◌͈◌̖◌֚b; a◌᷺◌͈◌̖◌֚b; a◌᷺◌͈◌̖◌֚b; a◌᷺◌͈◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING DOUBLE VERTICAL LINE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0349 0062;0061 1DFA 0316 0349 059A 0062;0061 1DFA 0316 0349 059A 0062;0061 1DFA 0316 0349 059A 0062;0061 1DFA 0316 0349 059A 0062; # (a◌֚◌̖◌᷺◌͉b; a◌᷺◌̖◌͉◌֚b; a◌᷺◌̖◌͉◌֚b; a◌᷺◌̖◌͉◌֚b; a◌᷺◌̖◌͉◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LEFT ANGLE BELOW, LATIN SMALL LETTER B
+0061 0349 059A 0316 1DFA 0062;0061 1DFA 0349 0316 059A 0062;0061 1DFA 0349 0316 059A 0062;0061 1DFA 0349 0316 059A 0062;0061 1DFA 0349 0316 059A 0062; # (a◌͉◌֚◌̖◌᷺b; a◌᷺◌͉◌̖◌֚b; a◌᷺◌͉◌̖◌֚b; a◌᷺◌͉◌̖◌֚b; a◌᷺◌͉◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LEFT ANGLE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 034A 0062;00E0 05AE 034A 0315 0062;0061 05AE 0300 034A 0315 0062;00E0 05AE 034A 0315 0062;0061 05AE 0300 034A 0315 0062; # (a◌̕◌̀◌֮◌͊b; à◌֮◌͊◌̕b; a◌֮◌̀◌͊◌̕b; à◌֮◌͊◌̕b; a◌֮◌̀◌͊◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING NOT TILDE ABOVE, LATIN SMALL LETTER B
+0061 034A 0315 0300 05AE 0062;0061 05AE 034A 0300 0315 0062;0061 05AE 034A 0300 0315 0062;0061 05AE 034A 0300 0315 0062;0061 05AE 034A 0300 0315 0062; # (a◌͊◌̕◌̀◌֮b; a◌֮◌͊◌̀◌̕b; a◌֮◌͊◌̀◌̕b; a◌֮◌͊◌̀◌̕b; a◌֮◌͊◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING NOT TILDE ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 034B 0062;00E0 05AE 034B 0315 0062;0061 05AE 0300 034B 0315 0062;00E0 05AE 034B 0315 0062;0061 05AE 0300 034B 0315 0062; # (a◌̕◌̀◌֮◌͋b; à◌֮◌͋◌̕b; a◌֮◌̀◌͋◌̕b; à◌֮◌͋◌̕b; a◌֮◌̀◌͋◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING HOMOTHETIC ABOVE, LATIN SMALL LETTER B
+0061 034B 0315 0300 05AE 0062;0061 05AE 034B 0300 0315 0062;0061 05AE 034B 0300 0315 0062;0061 05AE 034B 0300 0315 0062;0061 05AE 034B 0300 0315 0062; # (a◌͋◌̕◌̀◌֮b; a◌֮◌͋◌̀◌̕b; a◌֮◌͋◌̀◌̕b; a◌֮◌͋◌̀◌̕b; a◌֮◌͋◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING HOMOTHETIC ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 034C 0062;00E0 05AE 034C 0315 0062;0061 05AE 0300 034C 0315 0062;00E0 05AE 034C 0315 0062;0061 05AE 0300 034C 0315 0062; # (a◌̕◌̀◌֮◌͌b; à◌֮◌͌◌̕b; a◌֮◌̀◌͌◌̕b; à◌֮◌͌◌̕b; a◌֮◌̀◌͌◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING ALMOST EQUAL TO ABOVE, LATIN SMALL LETTER B
+0061 034C 0315 0300 05AE 0062;0061 05AE 034C 0300 0315 0062;0061 05AE 034C 0300 0315 0062;0061 05AE 034C 0300 0315 0062;0061 05AE 034C 0300 0315 0062; # (a◌͌◌̕◌̀◌֮b; a◌֮◌͌◌̀◌̕b; a◌֮◌͌◌̀◌̕b; a◌֮◌͌◌̀◌̕b; a◌֮◌͌◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING ALMOST EQUAL TO ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 034D 0062;0061 1DFA 0316 034D 059A 0062;0061 1DFA 0316 034D 059A 0062;0061 1DFA 0316 034D 059A 0062;0061 1DFA 0316 034D 059A 0062; # (a◌֚◌̖◌᷺◌Íb; a◌᷺◌̖◌Í◌֚b; a◌᷺◌̖◌Í◌֚b; a◌᷺◌̖◌Í◌֚b; a◌᷺◌̖◌Í◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LEFT RIGHT ARROW BELOW, LATIN SMALL LETTER B
+0061 034D 059A 0316 1DFA 0062;0061 1DFA 034D 0316 059A 0062;0061 1DFA 034D 0316 059A 0062;0061 1DFA 034D 0316 059A 0062;0061 1DFA 034D 0316 059A 0062; # (aâ—ŒÍ◌֚◌̖◌᷺b; a◌᷺◌Í◌̖◌֚b; a◌᷺◌Í◌̖◌֚b; a◌᷺◌Í◌̖◌֚b; a◌᷺◌Í◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LEFT RIGHT ARROW BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 034E 0062;0061 1DFA 0316 034E 059A 0062;0061 1DFA 0316 034E 059A 0062;0061 1DFA 0316 034E 059A 0062;0061 1DFA 0316 034E 059A 0062; # (a◌֚◌̖◌᷺◌͎b; a◌᷺◌̖◌͎◌֚b; a◌᷺◌̖◌͎◌֚b; a◌᷺◌̖◌͎◌֚b; a◌᷺◌̖◌͎◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING UPWARDS ARROW BELOW, LATIN SMALL LETTER B
+0061 034E 059A 0316 1DFA 0062;0061 1DFA 034E 0316 059A 0062;0061 1DFA 034E 0316 059A 0062;0061 1DFA 034E 0316 059A 0062;0061 1DFA 034E 0316 059A 0062; # (a◌͎◌֚◌̖◌᷺b; a◌᷺◌͎◌̖◌֚b; a◌᷺◌͎◌̖◌֚b; a◌᷺◌͎◌̖◌֚b; a◌᷺◌͎◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING UPWARDS ARROW BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0350 0062;00E0 05AE 0350 0315 0062;0061 05AE 0300 0350 0315 0062;00E0 05AE 0350 0315 0062;0061 05AE 0300 0350 0315 0062; # (a◌̕◌̀◌֮◌Íb; à◌֮◌Í◌̕b; a◌֮◌̀◌Í◌̕b; à◌֮◌Í◌̕b; a◌֮◌̀◌Í◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING RIGHT ARROWHEAD ABOVE, LATIN SMALL LETTER B
+0061 0350 0315 0300 05AE 0062;0061 05AE 0350 0300 0315 0062;0061 05AE 0350 0300 0315 0062;0061 05AE 0350 0300 0315 0062;0061 05AE 0350 0300 0315 0062; # (aâ—ŒÍ◌̕◌̀◌֮b; a◌֮◌Í◌̀◌̕b; a◌֮◌Í◌̀◌̕b; a◌֮◌Í◌̀◌̕b; a◌֮◌Í◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING RIGHT ARROWHEAD ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0351 0062;00E0 05AE 0351 0315 0062;0061 05AE 0300 0351 0315 0062;00E0 05AE 0351 0315 0062;0061 05AE 0300 0351 0315 0062; # (a◌̕◌̀◌֮◌͑b; à◌֮◌͑◌̕b; a◌֮◌̀◌͑◌̕b; à◌֮◌͑◌̕b; a◌֮◌̀◌͑◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LEFT HALF RING ABOVE, LATIN SMALL LETTER B
+0061 0351 0315 0300 05AE 0062;0061 05AE 0351 0300 0315 0062;0061 05AE 0351 0300 0315 0062;0061 05AE 0351 0300 0315 0062;0061 05AE 0351 0300 0315 0062; # (a◌͑◌̕◌̀◌֮b; a◌֮◌͑◌̀◌̕b; a◌֮◌͑◌̀◌̕b; a◌֮◌͑◌̀◌̕b; a◌֮◌͑◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LEFT HALF RING ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0352 0062;00E0 05AE 0352 0315 0062;0061 05AE 0300 0352 0315 0062;00E0 05AE 0352 0315 0062;0061 05AE 0300 0352 0315 0062; # (a◌̕◌̀◌֮◌͒b; à◌֮◌͒◌̕b; a◌֮◌̀◌͒◌̕b; à◌֮◌͒◌̕b; a◌֮◌̀◌͒◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING FERMATA, LATIN SMALL LETTER B
+0061 0352 0315 0300 05AE 0062;0061 05AE 0352 0300 0315 0062;0061 05AE 0352 0300 0315 0062;0061 05AE 0352 0300 0315 0062;0061 05AE 0352 0300 0315 0062; # (a◌͒◌̕◌̀◌֮b; a◌֮◌͒◌̀◌̕b; a◌֮◌͒◌̀◌̕b; a◌֮◌͒◌̀◌̕b; a◌֮◌͒◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING FERMATA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0353 0062;0061 1DFA 0316 0353 059A 0062;0061 1DFA 0316 0353 059A 0062;0061 1DFA 0316 0353 059A 0062;0061 1DFA 0316 0353 059A 0062; # (a◌֚◌̖◌᷺◌͓b; a◌᷺◌̖◌͓◌֚b; a◌᷺◌̖◌͓◌֚b; a◌᷺◌̖◌͓◌֚b; a◌᷺◌̖◌͓◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING X BELOW, LATIN SMALL LETTER B
+0061 0353 059A 0316 1DFA 0062;0061 1DFA 0353 0316 059A 0062;0061 1DFA 0353 0316 059A 0062;0061 1DFA 0353 0316 059A 0062;0061 1DFA 0353 0316 059A 0062; # (a◌͓◌֚◌̖◌᷺b; a◌᷺◌͓◌̖◌֚b; a◌᷺◌͓◌̖◌֚b; a◌᷺◌͓◌̖◌֚b; a◌᷺◌͓◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING X BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0354 0062;0061 1DFA 0316 0354 059A 0062;0061 1DFA 0316 0354 059A 0062;0061 1DFA 0316 0354 059A 0062;0061 1DFA 0316 0354 059A 0062; # (a◌֚◌̖◌᷺◌͔b; a◌᷺◌̖◌͔◌֚b; a◌᷺◌̖◌͔◌֚b; a◌᷺◌̖◌͔◌֚b; a◌᷺◌̖◌͔◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LEFT ARROWHEAD BELOW, LATIN SMALL LETTER B
+0061 0354 059A 0316 1DFA 0062;0061 1DFA 0354 0316 059A 0062;0061 1DFA 0354 0316 059A 0062;0061 1DFA 0354 0316 059A 0062;0061 1DFA 0354 0316 059A 0062; # (a◌͔◌֚◌̖◌᷺b; a◌᷺◌͔◌̖◌֚b; a◌᷺◌͔◌̖◌֚b; a◌᷺◌͔◌̖◌֚b; a◌᷺◌͔◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LEFT ARROWHEAD BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0355 0062;0061 1DFA 0316 0355 059A 0062;0061 1DFA 0316 0355 059A 0062;0061 1DFA 0316 0355 059A 0062;0061 1DFA 0316 0355 059A 0062; # (a◌֚◌̖◌᷺◌͕b; a◌᷺◌̖◌͕◌֚b; a◌᷺◌̖◌͕◌֚b; a◌᷺◌̖◌͕◌֚b; a◌᷺◌̖◌͕◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING RIGHT ARROWHEAD BELOW, LATIN SMALL LETTER B
+0061 0355 059A 0316 1DFA 0062;0061 1DFA 0355 0316 059A 0062;0061 1DFA 0355 0316 059A 0062;0061 1DFA 0355 0316 059A 0062;0061 1DFA 0355 0316 059A 0062; # (a◌͕◌֚◌̖◌᷺b; a◌᷺◌͕◌̖◌֚b; a◌᷺◌͕◌̖◌֚b; a◌᷺◌͕◌̖◌֚b; a◌᷺◌͕◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING RIGHT ARROWHEAD BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0356 0062;0061 1DFA 0316 0356 059A 0062;0061 1DFA 0316 0356 059A 0062;0061 1DFA 0316 0356 059A 0062;0061 1DFA 0316 0356 059A 0062; # (a◌֚◌̖◌᷺◌͖b; a◌᷺◌̖◌͖◌֚b; a◌᷺◌̖◌͖◌֚b; a◌᷺◌̖◌͖◌֚b; a◌᷺◌̖◌͖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING RIGHT ARROWHEAD AND UP ARROWHEAD BELOW, LATIN SMALL LETTER B
+0061 0356 059A 0316 1DFA 0062;0061 1DFA 0356 0316 059A 0062;0061 1DFA 0356 0316 059A 0062;0061 1DFA 0356 0316 059A 0062;0061 1DFA 0356 0316 059A 0062; # (a◌͖◌֚◌̖◌᷺b; a◌᷺◌͖◌̖◌֚b; a◌᷺◌͖◌̖◌֚b; a◌᷺◌͖◌̖◌֚b; a◌᷺◌͖◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING RIGHT ARROWHEAD AND UP ARROWHEAD BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0357 0062;00E0 05AE 0357 0315 0062;0061 05AE 0300 0357 0315 0062;00E0 05AE 0357 0315 0062;0061 05AE 0300 0357 0315 0062; # (a◌̕◌̀◌֮◌͗b; à◌֮◌͗◌̕b; a◌֮◌̀◌͗◌̕b; à◌֮◌͗◌̕b; a◌֮◌̀◌͗◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING RIGHT HALF RING ABOVE, LATIN SMALL LETTER B
+0061 0357 0315 0300 05AE 0062;0061 05AE 0357 0300 0315 0062;0061 05AE 0357 0300 0315 0062;0061 05AE 0357 0300 0315 0062;0061 05AE 0357 0300 0315 0062; # (a◌͗◌̕◌̀◌֮b; a◌֮◌͗◌̀◌̕b; a◌֮◌͗◌̀◌̕b; a◌֮◌͗◌̀◌̕b; a◌֮◌͗◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING RIGHT HALF RING ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 035C 0315 0300 0358 0062;00E0 0315 0358 035C 0062;0061 0300 0315 0358 035C 0062;00E0 0315 0358 035C 0062;0061 0300 0315 0358 035C 0062; # (a◌͜◌̕◌̀◌͘b; à◌̕◌͘◌͜b; a◌̀◌̕◌͘◌͜b; à◌̕◌͘◌͜b; a◌̀◌̕◌͘◌͜b; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, COMBINING DOT ABOVE RIGHT, LATIN SMALL LETTER B
+0061 0358 035C 0315 0300 0062;00E0 0358 0315 035C 0062;0061 0300 0358 0315 035C 0062;00E0 0358 0315 035C 0062;0061 0300 0358 0315 035C 0062; # (a◌͘◌͜◌̕◌̀b; à◌͘◌̕◌͜b; a◌̀◌͘◌̕◌͜b; à◌͘◌̕◌͜b; a◌̀◌͘◌̕◌͜b; ) LATIN SMALL LETTER A, COMBINING DOT ABOVE RIGHT, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0359 0062;0061 1DFA 0316 0359 059A 0062;0061 1DFA 0316 0359 059A 0062;0061 1DFA 0316 0359 059A 0062;0061 1DFA 0316 0359 059A 0062; # (a◌֚◌̖◌᷺◌͙b; a◌᷺◌̖◌͙◌֚b; a◌᷺◌̖◌͙◌֚b; a◌᷺◌̖◌͙◌֚b; a◌᷺◌̖◌͙◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING ASTERISK BELOW, LATIN SMALL LETTER B
+0061 0359 059A 0316 1DFA 0062;0061 1DFA 0359 0316 059A 0062;0061 1DFA 0359 0316 059A 0062;0061 1DFA 0359 0316 059A 0062;0061 1DFA 0359 0316 059A 0062; # (a◌͙◌֚◌̖◌᷺b; a◌᷺◌͙◌̖◌֚b; a◌᷺◌͙◌̖◌֚b; a◌᷺◌͙◌̖◌֚b; a◌᷺◌͙◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING ASTERISK BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 035A 0062;0061 1DFA 0316 035A 059A 0062;0061 1DFA 0316 035A 059A 0062;0061 1DFA 0316 035A 059A 0062;0061 1DFA 0316 035A 059A 0062; # (a◌֚◌̖◌᷺◌͚b; a◌᷺◌̖◌͚◌֚b; a◌᷺◌̖◌͚◌֚b; a◌᷺◌̖◌͚◌֚b; a◌᷺◌̖◌͚◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING DOUBLE RING BELOW, LATIN SMALL LETTER B
+0061 035A 059A 0316 1DFA 0062;0061 1DFA 035A 0316 059A 0062;0061 1DFA 035A 0316 059A 0062;0061 1DFA 035A 0316 059A 0062;0061 1DFA 035A 0316 059A 0062; # (a◌͚◌֚◌̖◌᷺b; a◌᷺◌͚◌̖◌֚b; a◌᷺◌͚◌̖◌֚b; a◌᷺◌͚◌̖◌֚b; a◌᷺◌͚◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING DOUBLE RING BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 035B 0062;00E0 05AE 035B 0315 0062;0061 05AE 0300 035B 0315 0062;00E0 05AE 035B 0315 0062;0061 05AE 0300 035B 0315 0062; # (a◌̕◌̀◌֮◌͛b; à◌֮◌͛◌̕b; a◌֮◌̀◌͛◌̕b; à◌֮◌͛◌̕b; a◌֮◌̀◌͛◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING ZIGZAG ABOVE, LATIN SMALL LETTER B
+0061 035B 0315 0300 05AE 0062;0061 05AE 035B 0300 0315 0062;0061 05AE 035B 0300 0315 0062;0061 05AE 035B 0300 0315 0062;0061 05AE 035B 0300 0315 0062; # (a◌͛◌̕◌̀◌֮b; a◌֮◌͛◌̀◌̕b; a◌֮◌͛◌̀◌̕b; a◌֮◌͛◌̀◌̕b; a◌֮◌͛◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING ZIGZAG ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 035D 035C 0315 035C 0062;0061 0315 035C 035C 035D 0062;0061 0315 035C 035C 035D 0062;0061 0315 035C 035C 035D 0062;0061 0315 035C 035C 035D 0062; # (aâ—ŒÍ◌͜◌̕◌͜b; a◌̕◌͜◌͜◌Íb; a◌̕◌͜◌͜◌Íb; a◌̕◌͜◌͜◌Íb; a◌̕◌͜◌͜◌Íb; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING DOUBLE BREVE BELOW, LATIN SMALL LETTER B
+0061 035C 035D 035C 0315 0062;0061 0315 035C 035C 035D 0062;0061 0315 035C 035C 035D 0062;0061 0315 035C 035C 035D 0062;0061 0315 035C 035C 035D 0062; # (a◌͜◌Í◌͜◌̕b; a◌̕◌͜◌͜◌Íb; a◌̕◌͜◌͜◌Íb; a◌̕◌͜◌͜◌Íb; a◌̕◌͜◌͜◌Íb; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE BELOW, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, LATIN SMALL LETTER B
+0061 0345 035D 035C 035D 0062;0061 035C 035D 035D 0345 0062;0061 035C 035D 035D 0345 0062;0061 035C 035D 035D 0345 0062;0061 035C 035D 035D 0345 0062; # (a◌ͅ◌Í◌͜◌Íb; a◌͜◌Íâ—ŒÍ◌ͅb; a◌͜◌Íâ—ŒÍ◌ͅb; a◌͜◌Íâ—ŒÍ◌ͅb; a◌͜◌Íâ—ŒÍ◌ͅb; ) LATIN SMALL LETTER A, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING DOUBLE BREVE, LATIN SMALL LETTER B
+0061 035D 0345 035D 035C 0062;0061 035C 035D 035D 0345 0062;0061 035C 035D 035D 0345 0062;0061 035C 035D 035D 0345 0062;0061 035C 035D 035D 0345 0062; # (aâ—ŒÍ◌ͅ◌Í◌͜b; a◌͜◌Íâ—ŒÍ◌ͅb; a◌͜◌Íâ—ŒÍ◌ͅb; a◌͜◌Íâ—ŒÍ◌ͅb; a◌͜◌Íâ—ŒÍ◌ͅb; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, LATIN SMALL LETTER B
+0061 0345 035D 035C 035E 0062;0061 035C 035D 035E 0345 0062;0061 035C 035D 035E 0345 0062;0061 035C 035D 035E 0345 0062;0061 035C 035D 035E 0345 0062; # (a◌ͅ◌Í◌͜◌͞b; a◌͜◌Í◌͞◌ͅb; a◌͜◌Í◌͞◌ͅb; a◌͜◌Í◌͞◌ͅb; a◌͜◌Í◌͞◌ͅb; ) LATIN SMALL LETTER A, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING DOUBLE MACRON, LATIN SMALL LETTER B
+0061 035E 0345 035D 035C 0062;0061 035C 035E 035D 0345 0062;0061 035C 035E 035D 0345 0062;0061 035C 035E 035D 0345 0062;0061 035C 035E 035D 0345 0062; # (a◌͞◌ͅ◌Í◌͜b; a◌͜◌͞◌Í◌ͅb; a◌͜◌͞◌Í◌ͅb; a◌͜◌͞◌Í◌ͅb; a◌͜◌͞◌Í◌ͅb; ) LATIN SMALL LETTER A, COMBINING DOUBLE MACRON, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, LATIN SMALL LETTER B
+0061 035D 035C 0315 035F 0062;0061 0315 035C 035F 035D 0062;0061 0315 035C 035F 035D 0062;0061 0315 035C 035F 035D 0062;0061 0315 035C 035F 035D 0062; # (aâ—ŒÍ◌͜◌̕◌͟b; a◌̕◌͜◌͟◌Íb; a◌̕◌͜◌͟◌Íb; a◌̕◌͜◌͟◌Íb; a◌̕◌͜◌͟◌Íb; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING DOUBLE MACRON BELOW, LATIN SMALL LETTER B
+0061 035F 035D 035C 0315 0062;0061 0315 035F 035C 035D 0062;0061 0315 035F 035C 035D 0062;0061 0315 035F 035C 035D 0062;0061 0315 035F 035C 035D 0062; # (a◌͟◌Í◌͜◌̕b; a◌̕◌͟◌͜◌Íb; a◌̕◌͟◌͜◌Íb; a◌̕◌͟◌͜◌Íb; a◌̕◌͟◌͜◌Íb; ) LATIN SMALL LETTER A, COMBINING DOUBLE MACRON BELOW, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, LATIN SMALL LETTER B
+0061 0345 035D 035C 0360 0062;0061 035C 035D 0360 0345 0062;0061 035C 035D 0360 0345 0062;0061 035C 035D 0360 0345 0062;0061 035C 035D 0360 0345 0062; # (a◌ͅ◌Í◌͜◌͠b; a◌͜◌Í◌͠◌ͅb; a◌͜◌Í◌͠◌ͅb; a◌͜◌Í◌͠◌ͅb; a◌͜◌Í◌͠◌ͅb; ) LATIN SMALL LETTER A, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING DOUBLE TILDE, LATIN SMALL LETTER B
+0061 0360 0345 035D 035C 0062;0061 035C 0360 035D 0345 0062;0061 035C 0360 035D 0345 0062;0061 035C 0360 035D 0345 0062;0061 035C 0360 035D 0345 0062; # (a◌͠◌ͅ◌Í◌͜b; a◌͜◌͠◌Í◌ͅb; a◌͜◌͠◌Í◌ͅb; a◌͜◌͠◌Í◌ͅb; a◌͜◌͠◌Í◌ͅb; ) LATIN SMALL LETTER A, COMBINING DOUBLE TILDE, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, LATIN SMALL LETTER B
+0061 0345 035D 035C 0361 0062;0061 035C 035D 0361 0345 0062;0061 035C 035D 0361 0345 0062;0061 035C 035D 0361 0345 0062;0061 035C 035D 0361 0345 0062; # (a◌ͅ◌Í◌͜◌͡b; a◌͜◌Í◌͡◌ͅb; a◌͜◌Í◌͡◌ͅb; a◌͜◌Í◌͡◌ͅb; a◌͜◌Í◌͡◌ͅb; ) LATIN SMALL LETTER A, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING DOUBLE INVERTED BREVE, LATIN SMALL LETTER B
+0061 0361 0345 035D 035C 0062;0061 035C 0361 035D 0345 0062;0061 035C 0361 035D 0345 0062;0061 035C 0361 035D 0345 0062;0061 035C 0361 035D 0345 0062; # (a◌͡◌ͅ◌Í◌͜b; a◌͜◌͡◌Í◌ͅb; a◌͜◌͡◌Í◌ͅb; a◌͜◌͡◌Í◌ͅb; a◌͜◌͡◌Í◌ͅb; ) LATIN SMALL LETTER A, COMBINING DOUBLE INVERTED BREVE, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, LATIN SMALL LETTER B
+0061 035D 035C 0315 0362 0062;0061 0315 035C 0362 035D 0062;0061 0315 035C 0362 035D 0062;0061 0315 035C 0362 035D 0062;0061 0315 035C 0362 035D 0062; # (aâ—ŒÍ◌͜◌̕◌͢b; a◌̕◌͜◌͢◌Íb; a◌̕◌͜◌͢◌Íb; a◌̕◌͜◌͢◌Íb; a◌̕◌͜◌͢◌Íb; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING DOUBLE RIGHTWARDS ARROW BELOW, LATIN SMALL LETTER B
+0061 0362 035D 035C 0315 0062;0061 0315 0362 035C 035D 0062;0061 0315 0362 035C 035D 0062;0061 0315 0362 035C 035D 0062;0061 0315 0362 035C 035D 0062; # (a◌͢◌Í◌͜◌̕b; a◌̕◌͢◌͜◌Íb; a◌̕◌͢◌͜◌Íb; a◌̕◌͢◌͜◌Íb; a◌̕◌͢◌͜◌Íb; ) LATIN SMALL LETTER A, COMBINING DOUBLE RIGHTWARDS ARROW BELOW, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0363 0062;00E0 05AE 0363 0315 0062;0061 05AE 0300 0363 0315 0062;00E0 05AE 0363 0315 0062;0061 05AE 0300 0363 0315 0062; # (a◌̕◌̀◌֮◌ͣb; à◌֮◌ͣ◌̕b; a◌֮◌̀◌ͣ◌̕b; à◌֮◌ͣ◌̕b; a◌֮◌̀◌ͣ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER A, LATIN SMALL LETTER B
+0061 0363 0315 0300 05AE 0062;0061 05AE 0363 0300 0315 0062;0061 05AE 0363 0300 0315 0062;0061 05AE 0363 0300 0315 0062;0061 05AE 0363 0300 0315 0062; # (a◌ͣ◌̕◌̀◌֮b; a◌֮◌ͣ◌̀◌̕b; a◌֮◌ͣ◌̀◌̕b; a◌֮◌ͣ◌̀◌̕b; a◌֮◌ͣ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0364 0062;00E0 05AE 0364 0315 0062;0061 05AE 0300 0364 0315 0062;00E0 05AE 0364 0315 0062;0061 05AE 0300 0364 0315 0062; # (a◌̕◌̀◌֮◌ͤb; à◌֮◌ͤ◌̕b; a◌֮◌̀◌ͤ◌̕b; à◌֮◌ͤ◌̕b; a◌֮◌̀◌ͤ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER E, LATIN SMALL LETTER B
+0061 0364 0315 0300 05AE 0062;0061 05AE 0364 0300 0315 0062;0061 05AE 0364 0300 0315 0062;0061 05AE 0364 0300 0315 0062;0061 05AE 0364 0300 0315 0062; # (a◌ͤ◌̕◌̀◌֮b; a◌֮◌ͤ◌̀◌̕b; a◌֮◌ͤ◌̀◌̕b; a◌֮◌ͤ◌̀◌̕b; a◌֮◌ͤ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER E, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0365 0062;00E0 05AE 0365 0315 0062;0061 05AE 0300 0365 0315 0062;00E0 05AE 0365 0315 0062;0061 05AE 0300 0365 0315 0062; # (a◌̕◌̀◌֮◌ͥb; à◌֮◌ͥ◌̕b; a◌֮◌̀◌ͥ◌̕b; à◌֮◌ͥ◌̕b; a◌֮◌̀◌ͥ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER I, LATIN SMALL LETTER B
+0061 0365 0315 0300 05AE 0062;0061 05AE 0365 0300 0315 0062;0061 05AE 0365 0300 0315 0062;0061 05AE 0365 0300 0315 0062;0061 05AE 0365 0300 0315 0062; # (a◌ͥ◌̕◌̀◌֮b; a◌֮◌ͥ◌̀◌̕b; a◌֮◌ͥ◌̀◌̕b; a◌֮◌ͥ◌̀◌̕b; a◌֮◌ͥ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER I, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0366 0062;00E0 05AE 0366 0315 0062;0061 05AE 0300 0366 0315 0062;00E0 05AE 0366 0315 0062;0061 05AE 0300 0366 0315 0062; # (a◌̕◌̀◌֮◌ͦb; à◌֮◌ͦ◌̕b; a◌֮◌̀◌ͦ◌̕b; à◌֮◌ͦ◌̕b; a◌֮◌̀◌ͦ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER O, LATIN SMALL LETTER B
+0061 0366 0315 0300 05AE 0062;0061 05AE 0366 0300 0315 0062;0061 05AE 0366 0300 0315 0062;0061 05AE 0366 0300 0315 0062;0061 05AE 0366 0300 0315 0062; # (a◌ͦ◌̕◌̀◌֮b; a◌֮◌ͦ◌̀◌̕b; a◌֮◌ͦ◌̀◌̕b; a◌֮◌ͦ◌̀◌̕b; a◌֮◌ͦ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER O, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0367 0062;00E0 05AE 0367 0315 0062;0061 05AE 0300 0367 0315 0062;00E0 05AE 0367 0315 0062;0061 05AE 0300 0367 0315 0062; # (a◌̕◌̀◌֮◌ͧb; à◌֮◌ͧ◌̕b; a◌֮◌̀◌ͧ◌̕b; à◌֮◌ͧ◌̕b; a◌֮◌̀◌ͧ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER U, LATIN SMALL LETTER B
+0061 0367 0315 0300 05AE 0062;0061 05AE 0367 0300 0315 0062;0061 05AE 0367 0300 0315 0062;0061 05AE 0367 0300 0315 0062;0061 05AE 0367 0300 0315 0062; # (a◌ͧ◌̕◌̀◌֮b; a◌֮◌ͧ◌̀◌̕b; a◌֮◌ͧ◌̀◌̕b; a◌֮◌ͧ◌̀◌̕b; a◌֮◌ͧ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER U, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0368 0062;00E0 05AE 0368 0315 0062;0061 05AE 0300 0368 0315 0062;00E0 05AE 0368 0315 0062;0061 05AE 0300 0368 0315 0062; # (a◌̕◌̀◌֮◌ͨb; à◌֮◌ͨ◌̕b; a◌֮◌̀◌ͨ◌̕b; à◌֮◌ͨ◌̕b; a◌֮◌̀◌ͨ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER C, LATIN SMALL LETTER B
+0061 0368 0315 0300 05AE 0062;0061 05AE 0368 0300 0315 0062;0061 05AE 0368 0300 0315 0062;0061 05AE 0368 0300 0315 0062;0061 05AE 0368 0300 0315 0062; # (a◌ͨ◌̕◌̀◌֮b; a◌֮◌ͨ◌̀◌̕b; a◌֮◌ͨ◌̀◌̕b; a◌֮◌ͨ◌̀◌̕b; a◌֮◌ͨ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER C, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0369 0062;00E0 05AE 0369 0315 0062;0061 05AE 0300 0369 0315 0062;00E0 05AE 0369 0315 0062;0061 05AE 0300 0369 0315 0062; # (a◌̕◌̀◌֮◌ͩb; à◌֮◌ͩ◌̕b; a◌֮◌̀◌ͩ◌̕b; à◌֮◌ͩ◌̕b; a◌֮◌̀◌ͩ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER D, LATIN SMALL LETTER B
+0061 0369 0315 0300 05AE 0062;0061 05AE 0369 0300 0315 0062;0061 05AE 0369 0300 0315 0062;0061 05AE 0369 0300 0315 0062;0061 05AE 0369 0300 0315 0062; # (a◌ͩ◌̕◌̀◌֮b; a◌֮◌ͩ◌̀◌̕b; a◌֮◌ͩ◌̀◌̕b; a◌֮◌ͩ◌̀◌̕b; a◌֮◌ͩ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER D, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 036A 0062;00E0 05AE 036A 0315 0062;0061 05AE 0300 036A 0315 0062;00E0 05AE 036A 0315 0062;0061 05AE 0300 036A 0315 0062; # (a◌̕◌̀◌֮◌ͪb; à◌֮◌ͪ◌̕b; a◌֮◌̀◌ͪ◌̕b; à◌֮◌ͪ◌̕b; a◌֮◌̀◌ͪ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER H, LATIN SMALL LETTER B
+0061 036A 0315 0300 05AE 0062;0061 05AE 036A 0300 0315 0062;0061 05AE 036A 0300 0315 0062;0061 05AE 036A 0300 0315 0062;0061 05AE 036A 0300 0315 0062; # (a◌ͪ◌̕◌̀◌֮b; a◌֮◌ͪ◌̀◌̕b; a◌֮◌ͪ◌̀◌̕b; a◌֮◌ͪ◌̀◌̕b; a◌֮◌ͪ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER H, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 036B 0062;00E0 05AE 036B 0315 0062;0061 05AE 0300 036B 0315 0062;00E0 05AE 036B 0315 0062;0061 05AE 0300 036B 0315 0062; # (a◌̕◌̀◌֮◌ͫb; à◌֮◌ͫ◌̕b; a◌֮◌̀◌ͫ◌̕b; à◌֮◌ͫ◌̕b; a◌֮◌̀◌ͫ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER M, LATIN SMALL LETTER B
+0061 036B 0315 0300 05AE 0062;0061 05AE 036B 0300 0315 0062;0061 05AE 036B 0300 0315 0062;0061 05AE 036B 0300 0315 0062;0061 05AE 036B 0300 0315 0062; # (a◌ͫ◌̕◌̀◌֮b; a◌֮◌ͫ◌̀◌̕b; a◌֮◌ͫ◌̀◌̕b; a◌֮◌ͫ◌̀◌̕b; a◌֮◌ͫ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER M, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 036C 0062;00E0 05AE 036C 0315 0062;0061 05AE 0300 036C 0315 0062;00E0 05AE 036C 0315 0062;0061 05AE 0300 036C 0315 0062; # (a◌̕◌̀◌֮◌ͬb; à◌֮◌ͬ◌̕b; a◌֮◌̀◌ͬ◌̕b; à◌֮◌ͬ◌̕b; a◌֮◌̀◌ͬ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER R, LATIN SMALL LETTER B
+0061 036C 0315 0300 05AE 0062;0061 05AE 036C 0300 0315 0062;0061 05AE 036C 0300 0315 0062;0061 05AE 036C 0300 0315 0062;0061 05AE 036C 0300 0315 0062; # (a◌ͬ◌̕◌̀◌֮b; a◌֮◌ͬ◌̀◌̕b; a◌֮◌ͬ◌̀◌̕b; a◌֮◌ͬ◌̀◌̕b; a◌֮◌ͬ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER R, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 036D 0062;00E0 05AE 036D 0315 0062;0061 05AE 0300 036D 0315 0062;00E0 05AE 036D 0315 0062;0061 05AE 0300 036D 0315 0062; # (a◌̕◌̀◌֮◌ͭb; à◌֮◌ͭ◌̕b; a◌֮◌̀◌ͭ◌̕b; à◌֮◌ͭ◌̕b; a◌֮◌̀◌ͭ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER T, LATIN SMALL LETTER B
+0061 036D 0315 0300 05AE 0062;0061 05AE 036D 0300 0315 0062;0061 05AE 036D 0300 0315 0062;0061 05AE 036D 0300 0315 0062;0061 05AE 036D 0300 0315 0062; # (a◌ͭ◌̕◌̀◌֮b; a◌֮◌ͭ◌̀◌̕b; a◌֮◌ͭ◌̀◌̕b; a◌֮◌ͭ◌̀◌̕b; a◌֮◌ͭ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER T, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 036E 0062;00E0 05AE 036E 0315 0062;0061 05AE 0300 036E 0315 0062;00E0 05AE 036E 0315 0062;0061 05AE 0300 036E 0315 0062; # (a◌̕◌̀◌֮◌ͮb; à◌֮◌ͮ◌̕b; a◌֮◌̀◌ͮ◌̕b; à◌֮◌ͮ◌̕b; a◌֮◌̀◌ͮ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER V, LATIN SMALL LETTER B
+0061 036E 0315 0300 05AE 0062;0061 05AE 036E 0300 0315 0062;0061 05AE 036E 0300 0315 0062;0061 05AE 036E 0300 0315 0062;0061 05AE 036E 0300 0315 0062; # (a◌ͮ◌̕◌̀◌֮b; a◌֮◌ͮ◌̀◌̕b; a◌֮◌ͮ◌̀◌̕b; a◌֮◌ͮ◌̀◌̕b; a◌֮◌ͮ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER V, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 036F 0062;00E0 05AE 036F 0315 0062;0061 05AE 0300 036F 0315 0062;00E0 05AE 036F 0315 0062;0061 05AE 0300 036F 0315 0062; # (a◌̕◌̀◌֮◌ͯb; à◌֮◌ͯ◌̕b; a◌֮◌̀◌ͯ◌̕b; à◌֮◌ͯ◌̕b; a◌֮◌̀◌ͯ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER X, LATIN SMALL LETTER B
+0061 036F 0315 0300 05AE 0062;0061 05AE 036F 0300 0315 0062;0061 05AE 036F 0300 0315 0062;0061 05AE 036F 0300 0315 0062;0061 05AE 036F 0300 0315 0062; # (a◌ͯ◌̕◌̀◌֮b; a◌֮◌ͯ◌̀◌̕b; a◌֮◌ͯ◌̀◌̕b; a◌֮◌ͯ◌̀◌̕b; a◌֮◌ͯ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER X, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0483 0062;00E0 05AE 0483 0315 0062;0061 05AE 0300 0483 0315 0062;00E0 05AE 0483 0315 0062;0061 05AE 0300 0483 0315 0062; # (a◌̕◌̀◌֮◌҃b; à◌֮◌҃◌̕b; a◌֮◌̀◌҃◌̕b; à◌֮◌҃◌̕b; a◌֮◌̀◌҃◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC TITLO, LATIN SMALL LETTER B
+0061 0483 0315 0300 05AE 0062;0061 05AE 0483 0300 0315 0062;0061 05AE 0483 0300 0315 0062;0061 05AE 0483 0300 0315 0062;0061 05AE 0483 0300 0315 0062; # (a◌҃◌̕◌̀◌֮b; a◌֮◌҃◌̀◌̕b; a◌֮◌҃◌̀◌̕b; a◌֮◌҃◌̀◌̕b; a◌֮◌҃◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC TITLO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0484 0062;00E0 05AE 0484 0315 0062;0061 05AE 0300 0484 0315 0062;00E0 05AE 0484 0315 0062;0061 05AE 0300 0484 0315 0062; # (a◌̕◌̀◌֮◌҄b; à◌֮◌҄◌̕b; a◌֮◌̀◌҄◌̕b; à◌֮◌҄◌̕b; a◌֮◌̀◌҄◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC PALATALIZATION, LATIN SMALL LETTER B
+0061 0484 0315 0300 05AE 0062;0061 05AE 0484 0300 0315 0062;0061 05AE 0484 0300 0315 0062;0061 05AE 0484 0300 0315 0062;0061 05AE 0484 0300 0315 0062; # (a◌҄◌̕◌̀◌֮b; a◌֮◌҄◌̀◌̕b; a◌֮◌҄◌̀◌̕b; a◌֮◌҄◌̀◌̕b; a◌֮◌҄◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC PALATALIZATION, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0485 0062;00E0 05AE 0485 0315 0062;0061 05AE 0300 0485 0315 0062;00E0 05AE 0485 0315 0062;0061 05AE 0300 0485 0315 0062; # (a◌̕◌̀◌֮◌҅b; à◌֮◌҅◌̕b; a◌֮◌̀◌҅◌̕b; à◌֮◌҅◌̕b; a◌֮◌̀◌҅◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC DASIA PNEUMATA, LATIN SMALL LETTER B
+0061 0485 0315 0300 05AE 0062;0061 05AE 0485 0300 0315 0062;0061 05AE 0485 0300 0315 0062;0061 05AE 0485 0300 0315 0062;0061 05AE 0485 0300 0315 0062; # (a◌҅◌̕◌̀◌֮b; a◌֮◌҅◌̀◌̕b; a◌֮◌҅◌̀◌̕b; a◌֮◌҅◌̀◌̕b; a◌֮◌҅◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC DASIA PNEUMATA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0486 0062;00E0 05AE 0486 0315 0062;0061 05AE 0300 0486 0315 0062;00E0 05AE 0486 0315 0062;0061 05AE 0300 0486 0315 0062; # (a◌̕◌̀◌֮◌҆b; à◌֮◌҆◌̕b; a◌֮◌̀◌҆◌̕b; à◌֮◌҆◌̕b; a◌֮◌̀◌҆◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC PSILI PNEUMATA, LATIN SMALL LETTER B
+0061 0486 0315 0300 05AE 0062;0061 05AE 0486 0300 0315 0062;0061 05AE 0486 0300 0315 0062;0061 05AE 0486 0300 0315 0062;0061 05AE 0486 0300 0315 0062; # (a◌҆◌̕◌̀◌֮b; a◌֮◌҆◌̀◌̕b; a◌֮◌҆◌̀◌̕b; a◌֮◌҆◌̀◌̕b; a◌֮◌҆◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC PSILI PNEUMATA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0487 0062;00E0 05AE 0487 0315 0062;0061 05AE 0300 0487 0315 0062;00E0 05AE 0487 0315 0062;0061 05AE 0300 0487 0315 0062; # (a◌̕◌̀◌֮◌҇b; à◌֮◌҇◌̕b; a◌֮◌̀◌҇◌̕b; à◌֮◌҇◌̕b; a◌֮◌̀◌҇◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC POKRYTIE, LATIN SMALL LETTER B
+0061 0487 0315 0300 05AE 0062;0061 05AE 0487 0300 0315 0062;0061 05AE 0487 0300 0315 0062;0061 05AE 0487 0300 0315 0062;0061 05AE 0487 0300 0315 0062; # (a◌҇◌̕◌̀◌֮b; a◌֮◌҇◌̀◌̕b; a◌֮◌҇◌̀◌̕b; a◌֮◌҇◌̀◌̕b; a◌֮◌҇◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC POKRYTIE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0591 0062;0061 1DFA 0316 0591 059A 0062;0061 1DFA 0316 0591 059A 0062;0061 1DFA 0316 0591 059A 0062;0061 1DFA 0316 0591 059A 0062; # (a◌֚◌̖◌᷺◌֑b; a◌᷺◌̖◌֑◌֚b; a◌᷺◌̖◌֑◌֚b; a◌᷺◌̖◌֑◌֚b; a◌᷺◌̖◌֑◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW ACCENT ETNAHTA, LATIN SMALL LETTER B
+0061 0591 059A 0316 1DFA 0062;0061 1DFA 0591 0316 059A 0062;0061 1DFA 0591 0316 059A 0062;0061 1DFA 0591 0316 059A 0062;0061 1DFA 0591 0316 059A 0062; # (a◌֑◌֚◌̖◌᷺b; a◌᷺◌֑◌̖◌֚b; a◌᷺◌֑◌̖◌֚b; a◌᷺◌֑◌̖◌֚b; a◌᷺◌֑◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT ETNAHTA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0592 0062;00E0 05AE 0592 0315 0062;0061 05AE 0300 0592 0315 0062;00E0 05AE 0592 0315 0062;0061 05AE 0300 0592 0315 0062; # (a◌̕◌̀◌֮◌֒b; à◌֮◌֒◌̕b; a◌֮◌̀◌֒◌̕b; à◌֮◌֒◌̕b; a◌֮◌̀◌֒◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT SEGOL, LATIN SMALL LETTER B
+0061 0592 0315 0300 05AE 0062;0061 05AE 0592 0300 0315 0062;0061 05AE 0592 0300 0315 0062;0061 05AE 0592 0300 0315 0062;0061 05AE 0592 0300 0315 0062; # (a◌֒◌̕◌̀◌֮b; a◌֮◌֒◌̀◌̕b; a◌֮◌֒◌̀◌̕b; a◌֮◌֒◌̀◌̕b; a◌֮◌֒◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT SEGOL, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0593 0062;00E0 05AE 0593 0315 0062;0061 05AE 0300 0593 0315 0062;00E0 05AE 0593 0315 0062;0061 05AE 0300 0593 0315 0062; # (a◌̕◌̀◌֮◌֓b; à◌֮◌֓◌̕b; a◌֮◌̀◌֓◌̕b; à◌֮◌֓◌̕b; a◌֮◌̀◌֓◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT SHALSHELET, LATIN SMALL LETTER B
+0061 0593 0315 0300 05AE 0062;0061 05AE 0593 0300 0315 0062;0061 05AE 0593 0300 0315 0062;0061 05AE 0593 0300 0315 0062;0061 05AE 0593 0300 0315 0062; # (a◌֓◌̕◌̀◌֮b; a◌֮◌֓◌̀◌̕b; a◌֮◌֓◌̀◌̕b; a◌֮◌֓◌̀◌̕b; a◌֮◌֓◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT SHALSHELET, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0594 0062;00E0 05AE 0594 0315 0062;0061 05AE 0300 0594 0315 0062;00E0 05AE 0594 0315 0062;0061 05AE 0300 0594 0315 0062; # (a◌̕◌̀◌֮◌֔b; à◌֮◌֔◌̕b; a◌֮◌̀◌֔◌̕b; à◌֮◌֔◌̕b; a◌֮◌̀◌֔◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT ZAQEF QATAN, LATIN SMALL LETTER B
+0061 0594 0315 0300 05AE 0062;0061 05AE 0594 0300 0315 0062;0061 05AE 0594 0300 0315 0062;0061 05AE 0594 0300 0315 0062;0061 05AE 0594 0300 0315 0062; # (a◌֔◌̕◌̀◌֮b; a◌֮◌֔◌̀◌̕b; a◌֮◌֔◌̀◌̕b; a◌֮◌֔◌̀◌̕b; a◌֮◌֔◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT ZAQEF QATAN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0595 0062;00E0 05AE 0595 0315 0062;0061 05AE 0300 0595 0315 0062;00E0 05AE 0595 0315 0062;0061 05AE 0300 0595 0315 0062; # (a◌̕◌̀◌֮◌֕b; à◌֮◌֕◌̕b; a◌֮◌̀◌֕◌̕b; à◌֮◌֕◌̕b; a◌֮◌̀◌֕◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT ZAQEF GADOL, LATIN SMALL LETTER B
+0061 0595 0315 0300 05AE 0062;0061 05AE 0595 0300 0315 0062;0061 05AE 0595 0300 0315 0062;0061 05AE 0595 0300 0315 0062;0061 05AE 0595 0300 0315 0062; # (a◌֕◌̕◌̀◌֮b; a◌֮◌֕◌̀◌̕b; a◌֮◌֕◌̀◌̕b; a◌֮◌֕◌̀◌̕b; a◌֮◌֕◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT ZAQEF GADOL, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0596 0062;0061 1DFA 0316 0596 059A 0062;0061 1DFA 0316 0596 059A 0062;0061 1DFA 0316 0596 059A 0062;0061 1DFA 0316 0596 059A 0062; # (a◌֚◌̖◌᷺◌֖b; a◌᷺◌̖◌֖◌֚b; a◌᷺◌̖◌֖◌֚b; a◌᷺◌̖◌֖◌֚b; a◌᷺◌̖◌֖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW ACCENT TIPEHA, LATIN SMALL LETTER B
+0061 0596 059A 0316 1DFA 0062;0061 1DFA 0596 0316 059A 0062;0061 1DFA 0596 0316 059A 0062;0061 1DFA 0596 0316 059A 0062;0061 1DFA 0596 0316 059A 0062; # (a◌֖◌֚◌̖◌᷺b; a◌᷺◌֖◌̖◌֚b; a◌᷺◌֖◌̖◌֚b; a◌᷺◌֖◌̖◌֚b; a◌᷺◌֖◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT TIPEHA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0597 0062;00E0 05AE 0597 0315 0062;0061 05AE 0300 0597 0315 0062;00E0 05AE 0597 0315 0062;0061 05AE 0300 0597 0315 0062; # (a◌̕◌̀◌֮◌֗b; à◌֮◌֗◌̕b; a◌֮◌̀◌֗◌̕b; à◌֮◌֗◌̕b; a◌֮◌̀◌֗◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT REVIA, LATIN SMALL LETTER B
+0061 0597 0315 0300 05AE 0062;0061 05AE 0597 0300 0315 0062;0061 05AE 0597 0300 0315 0062;0061 05AE 0597 0300 0315 0062;0061 05AE 0597 0300 0315 0062; # (a◌֗◌̕◌̀◌֮b; a◌֮◌֗◌̀◌̕b; a◌֮◌֗◌̀◌̕b; a◌֮◌֗◌̀◌̕b; a◌֮◌֗◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT REVIA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0598 0062;00E0 05AE 0598 0315 0062;0061 05AE 0300 0598 0315 0062;00E0 05AE 0598 0315 0062;0061 05AE 0300 0598 0315 0062; # (a◌̕◌̀◌֮◌֘b; à◌֮◌֘◌̕b; a◌֮◌̀◌֘◌̕b; à◌֮◌֘◌̕b; a◌֮◌̀◌֘◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT ZARQA, LATIN SMALL LETTER B
+0061 0598 0315 0300 05AE 0062;0061 05AE 0598 0300 0315 0062;0061 05AE 0598 0300 0315 0062;0061 05AE 0598 0300 0315 0062;0061 05AE 0598 0300 0315 0062; # (a◌֘◌̕◌̀◌֮b; a◌֮◌֘◌̀◌̕b; a◌֮◌֘◌̀◌̕b; a◌֮◌֘◌̀◌̕b; a◌֮◌֘◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT ZARQA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0599 0062;00E0 05AE 0599 0315 0062;0061 05AE 0300 0599 0315 0062;00E0 05AE 0599 0315 0062;0061 05AE 0300 0599 0315 0062; # (a◌̕◌̀◌֮◌֙b; à◌֮◌֙◌̕b; a◌֮◌̀◌֙◌̕b; à◌֮◌֙◌̕b; a◌֮◌̀◌֙◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT PASHTA, LATIN SMALL LETTER B
+0061 0599 0315 0300 05AE 0062;0061 05AE 0599 0300 0315 0062;0061 05AE 0599 0300 0315 0062;0061 05AE 0599 0300 0315 0062;0061 05AE 0599 0300 0315 0062; # (a◌֙◌̕◌̀◌֮b; a◌֮◌֙◌̀◌̕b; a◌֮◌֙◌̀◌̕b; a◌֮◌֙◌̀◌̕b; a◌֮◌֙◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT PASHTA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 302E 059A 0316 059A 0062;0061 0316 059A 059A 302E 0062;0061 0316 059A 059A 302E 0062;0061 0316 059A 059A 302E 0062;0061 0316 059A 059A 302E 0062; # (a〮◌֚◌̖◌֚b; a◌̖◌֚◌֚〮b; a◌̖◌֚◌֚〮b; a◌̖◌֚◌֚〮b; a◌̖◌֚◌֚〮b; ) LATIN SMALL LETTER A, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, HEBREW ACCENT YETIV, LATIN SMALL LETTER B
+0061 059A 302E 059A 0316 0062;0061 0316 059A 059A 302E 0062;0061 0316 059A 059A 302E 0062;0061 0316 059A 059A 302E 0062;0061 0316 059A 059A 302E 0062; # (a◌֚〮◌֚◌̖b; a◌̖◌֚◌֚〮b; a◌̖◌֚◌֚〮b; a◌̖◌֚◌֚〮b; a◌̖◌֚◌֚〮b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 059B 0062;0061 1DFA 0316 059B 059A 0062;0061 1DFA 0316 059B 059A 0062;0061 1DFA 0316 059B 059A 0062;0061 1DFA 0316 059B 059A 0062; # (a◌֚◌̖◌᷺◌֛b; a◌᷺◌̖◌֛◌֚b; a◌᷺◌̖◌֛◌֚b; a◌᷺◌̖◌֛◌֚b; a◌᷺◌̖◌֛◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW ACCENT TEVIR, LATIN SMALL LETTER B
+0061 059B 059A 0316 1DFA 0062;0061 1DFA 059B 0316 059A 0062;0061 1DFA 059B 0316 059A 0062;0061 1DFA 059B 0316 059A 0062;0061 1DFA 059B 0316 059A 0062; # (a◌֛◌֚◌̖◌᷺b; a◌᷺◌֛◌̖◌֚b; a◌᷺◌֛◌̖◌֚b; a◌᷺◌֛◌̖◌֚b; a◌᷺◌֛◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT TEVIR, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 059C 0062;00E0 05AE 059C 0315 0062;0061 05AE 0300 059C 0315 0062;00E0 05AE 059C 0315 0062;0061 05AE 0300 059C 0315 0062; # (a◌̕◌̀◌֮◌֜b; à◌֮◌֜◌̕b; a◌֮◌̀◌֜◌̕b; à◌֮◌֜◌̕b; a◌֮◌̀◌֜◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT GERESH, LATIN SMALL LETTER B
+0061 059C 0315 0300 05AE 0062;0061 05AE 059C 0300 0315 0062;0061 05AE 059C 0300 0315 0062;0061 05AE 059C 0300 0315 0062;0061 05AE 059C 0300 0315 0062; # (a◌֜◌̕◌̀◌֮b; a◌֮◌֜◌̀◌̕b; a◌֮◌֜◌̀◌̕b; a◌֮◌֜◌̀◌̕b; a◌֮◌֜◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT GERESH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 059D 0062;00E0 05AE 059D 0315 0062;0061 05AE 0300 059D 0315 0062;00E0 05AE 059D 0315 0062;0061 05AE 0300 059D 0315 0062; # (a◌̕◌̀◌֮◌Öb; à◌֮◌Ö◌̕b; a◌֮◌̀◌Ö◌̕b; à◌֮◌Ö◌̕b; a◌֮◌̀◌Ö◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT GERESH MUQDAM, LATIN SMALL LETTER B
+0061 059D 0315 0300 05AE 0062;0061 05AE 059D 0300 0315 0062;0061 05AE 059D 0300 0315 0062;0061 05AE 059D 0300 0315 0062;0061 05AE 059D 0300 0315 0062; # (aâ—ŒÖ◌̕◌̀◌֮b; a◌֮◌Ö◌̀◌̕b; a◌֮◌Ö◌̀◌̕b; a◌֮◌Ö◌̀◌̕b; a◌֮◌Ö◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT GERESH MUQDAM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 059E 0062;00E0 05AE 059E 0315 0062;0061 05AE 0300 059E 0315 0062;00E0 05AE 059E 0315 0062;0061 05AE 0300 059E 0315 0062; # (a◌̕◌̀◌֮◌֞b; à◌֮◌֞◌̕b; a◌֮◌̀◌֞◌̕b; à◌֮◌֞◌̕b; a◌֮◌̀◌֞◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT GERSHAYIM, LATIN SMALL LETTER B
+0061 059E 0315 0300 05AE 0062;0061 05AE 059E 0300 0315 0062;0061 05AE 059E 0300 0315 0062;0061 05AE 059E 0300 0315 0062;0061 05AE 059E 0300 0315 0062; # (a◌֞◌̕◌̀◌֮b; a◌֮◌֞◌̀◌̕b; a◌֮◌֞◌̀◌̕b; a◌֮◌֞◌̀◌̕b; a◌֮◌֞◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT GERSHAYIM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 059F 0062;00E0 05AE 059F 0315 0062;0061 05AE 0300 059F 0315 0062;00E0 05AE 059F 0315 0062;0061 05AE 0300 059F 0315 0062; # (a◌̕◌̀◌֮◌֟b; à◌֮◌֟◌̕b; a◌֮◌̀◌֟◌̕b; à◌֮◌֟◌̕b; a◌֮◌̀◌֟◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT QARNEY PARA, LATIN SMALL LETTER B
+0061 059F 0315 0300 05AE 0062;0061 05AE 059F 0300 0315 0062;0061 05AE 059F 0300 0315 0062;0061 05AE 059F 0300 0315 0062;0061 05AE 059F 0300 0315 0062; # (a◌֟◌̕◌̀◌֮b; a◌֮◌֟◌̀◌̕b; a◌֮◌֟◌̀◌̕b; a◌֮◌֟◌̀◌̕b; a◌֮◌֟◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT QARNEY PARA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 05A0 0062;00E0 05AE 05A0 0315 0062;0061 05AE 0300 05A0 0315 0062;00E0 05AE 05A0 0315 0062;0061 05AE 0300 05A0 0315 0062; # (a◌̕◌̀◌֮◌֠b; à◌֮◌֠◌̕b; a◌֮◌̀◌֠◌̕b; à◌֮◌֠◌̕b; a◌֮◌̀◌֠◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT TELISHA GEDOLA, LATIN SMALL LETTER B
+0061 05A0 0315 0300 05AE 0062;0061 05AE 05A0 0300 0315 0062;0061 05AE 05A0 0300 0315 0062;0061 05AE 05A0 0300 0315 0062;0061 05AE 05A0 0300 0315 0062; # (a◌֠◌̕◌̀◌֮b; a◌֮◌֠◌̀◌̕b; a◌֮◌֠◌̀◌̕b; a◌֮◌֠◌̀◌̕b; a◌֮◌֠◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT TELISHA GEDOLA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 05A1 0062;00E0 05AE 05A1 0315 0062;0061 05AE 0300 05A1 0315 0062;00E0 05AE 05A1 0315 0062;0061 05AE 0300 05A1 0315 0062; # (a◌̕◌̀◌֮◌֡b; à◌֮◌֡◌̕b; a◌֮◌̀◌֡◌̕b; à◌֮◌֡◌̕b; a◌֮◌̀◌֡◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT PAZER, LATIN SMALL LETTER B
+0061 05A1 0315 0300 05AE 0062;0061 05AE 05A1 0300 0315 0062;0061 05AE 05A1 0300 0315 0062;0061 05AE 05A1 0300 0315 0062;0061 05AE 05A1 0300 0315 0062; # (a◌֡◌̕◌̀◌֮b; a◌֮◌֡◌̀◌̕b; a◌֮◌֡◌̀◌̕b; a◌֮◌֡◌̀◌̕b; a◌֮◌֡◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT PAZER, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 05A2 0062;0061 1DFA 0316 05A2 059A 0062;0061 1DFA 0316 05A2 059A 0062;0061 1DFA 0316 05A2 059A 0062;0061 1DFA 0316 05A2 059A 0062; # (a◌֚◌̖◌᷺◌֢b; a◌᷺◌̖◌֢◌֚b; a◌᷺◌̖◌֢◌֚b; a◌᷺◌̖◌֢◌֚b; a◌᷺◌̖◌֢◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW ACCENT ATNAH HAFUKH, LATIN SMALL LETTER B
+0061 05A2 059A 0316 1DFA 0062;0061 1DFA 05A2 0316 059A 0062;0061 1DFA 05A2 0316 059A 0062;0061 1DFA 05A2 0316 059A 0062;0061 1DFA 05A2 0316 059A 0062; # (a◌֢◌֚◌̖◌᷺b; a◌᷺◌֢◌̖◌֚b; a◌᷺◌֢◌̖◌֚b; a◌᷺◌֢◌̖◌֚b; a◌᷺◌֢◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT ATNAH HAFUKH, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 05A3 0062;0061 1DFA 0316 05A3 059A 0062;0061 1DFA 0316 05A3 059A 0062;0061 1DFA 0316 05A3 059A 0062;0061 1DFA 0316 05A3 059A 0062; # (a◌֚◌̖◌᷺◌֣b; a◌᷺◌̖◌֣◌֚b; a◌᷺◌̖◌֣◌֚b; a◌᷺◌̖◌֣◌֚b; a◌᷺◌̖◌֣◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW ACCENT MUNAH, LATIN SMALL LETTER B
+0061 05A3 059A 0316 1DFA 0062;0061 1DFA 05A3 0316 059A 0062;0061 1DFA 05A3 0316 059A 0062;0061 1DFA 05A3 0316 059A 0062;0061 1DFA 05A3 0316 059A 0062; # (a◌֣◌֚◌̖◌᷺b; a◌᷺◌֣◌̖◌֚b; a◌᷺◌֣◌̖◌֚b; a◌᷺◌֣◌̖◌֚b; a◌᷺◌֣◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT MUNAH, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 05A4 0062;0061 1DFA 0316 05A4 059A 0062;0061 1DFA 0316 05A4 059A 0062;0061 1DFA 0316 05A4 059A 0062;0061 1DFA 0316 05A4 059A 0062; # (a◌֚◌̖◌᷺◌֤b; a◌᷺◌̖◌֤◌֚b; a◌᷺◌̖◌֤◌֚b; a◌᷺◌̖◌֤◌֚b; a◌᷺◌̖◌֤◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW ACCENT MAHAPAKH, LATIN SMALL LETTER B
+0061 05A4 059A 0316 1DFA 0062;0061 1DFA 05A4 0316 059A 0062;0061 1DFA 05A4 0316 059A 0062;0061 1DFA 05A4 0316 059A 0062;0061 1DFA 05A4 0316 059A 0062; # (a◌֤◌֚◌̖◌᷺b; a◌᷺◌֤◌̖◌֚b; a◌᷺◌֤◌̖◌֚b; a◌᷺◌֤◌̖◌֚b; a◌᷺◌֤◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT MAHAPAKH, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 05A5 0062;0061 1DFA 0316 05A5 059A 0062;0061 1DFA 0316 05A5 059A 0062;0061 1DFA 0316 05A5 059A 0062;0061 1DFA 0316 05A5 059A 0062; # (a◌֚◌̖◌᷺◌֥b; a◌᷺◌̖◌֥◌֚b; a◌᷺◌̖◌֥◌֚b; a◌᷺◌̖◌֥◌֚b; a◌᷺◌̖◌֥◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW ACCENT MERKHA, LATIN SMALL LETTER B
+0061 05A5 059A 0316 1DFA 0062;0061 1DFA 05A5 0316 059A 0062;0061 1DFA 05A5 0316 059A 0062;0061 1DFA 05A5 0316 059A 0062;0061 1DFA 05A5 0316 059A 0062; # (a◌֥◌֚◌̖◌᷺b; a◌᷺◌֥◌̖◌֚b; a◌᷺◌֥◌̖◌֚b; a◌᷺◌֥◌̖◌֚b; a◌᷺◌֥◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT MERKHA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 05A6 0062;0061 1DFA 0316 05A6 059A 0062;0061 1DFA 0316 05A6 059A 0062;0061 1DFA 0316 05A6 059A 0062;0061 1DFA 0316 05A6 059A 0062; # (a◌֚◌̖◌᷺◌֦b; a◌᷺◌̖◌֦◌֚b; a◌᷺◌̖◌֦◌֚b; a◌᷺◌̖◌֦◌֚b; a◌᷺◌̖◌֦◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW ACCENT MERKHA KEFULA, LATIN SMALL LETTER B
+0061 05A6 059A 0316 1DFA 0062;0061 1DFA 05A6 0316 059A 0062;0061 1DFA 05A6 0316 059A 0062;0061 1DFA 05A6 0316 059A 0062;0061 1DFA 05A6 0316 059A 0062; # (a◌֦◌֚◌̖◌᷺b; a◌᷺◌֦◌̖◌֚b; a◌᷺◌֦◌̖◌֚b; a◌᷺◌֦◌̖◌֚b; a◌᷺◌֦◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT MERKHA KEFULA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 05A7 0062;0061 1DFA 0316 05A7 059A 0062;0061 1DFA 0316 05A7 059A 0062;0061 1DFA 0316 05A7 059A 0062;0061 1DFA 0316 05A7 059A 0062; # (a◌֚◌̖◌᷺◌֧b; a◌᷺◌̖◌֧◌֚b; a◌᷺◌̖◌֧◌֚b; a◌᷺◌̖◌֧◌֚b; a◌᷺◌̖◌֧◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW ACCENT DARGA, LATIN SMALL LETTER B
+0061 05A7 059A 0316 1DFA 0062;0061 1DFA 05A7 0316 059A 0062;0061 1DFA 05A7 0316 059A 0062;0061 1DFA 05A7 0316 059A 0062;0061 1DFA 05A7 0316 059A 0062; # (a◌֧◌֚◌̖◌᷺b; a◌᷺◌֧◌̖◌֚b; a◌᷺◌֧◌̖◌֚b; a◌᷺◌֧◌̖◌֚b; a◌᷺◌֧◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT DARGA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 05A8 0062;00E0 05AE 05A8 0315 0062;0061 05AE 0300 05A8 0315 0062;00E0 05AE 05A8 0315 0062;0061 05AE 0300 05A8 0315 0062; # (a◌̕◌̀◌֮◌֨b; à◌֮◌֨◌̕b; a◌֮◌̀◌֨◌̕b; à◌֮◌֨◌̕b; a◌֮◌̀◌֨◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT QADMA, LATIN SMALL LETTER B
+0061 05A8 0315 0300 05AE 0062;0061 05AE 05A8 0300 0315 0062;0061 05AE 05A8 0300 0315 0062;0061 05AE 05A8 0300 0315 0062;0061 05AE 05A8 0300 0315 0062; # (a◌֨◌̕◌̀◌֮b; a◌֮◌֨◌̀◌̕b; a◌֮◌֨◌̀◌̕b; a◌֮◌֨◌̀◌̕b; a◌֮◌֨◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT QADMA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 05A9 0062;00E0 05AE 05A9 0315 0062;0061 05AE 0300 05A9 0315 0062;00E0 05AE 05A9 0315 0062;0061 05AE 0300 05A9 0315 0062; # (a◌̕◌̀◌֮◌֩b; à◌֮◌֩◌̕b; a◌֮◌̀◌֩◌̕b; à◌֮◌֩◌̕b; a◌֮◌̀◌֩◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT TELISHA QETANA, LATIN SMALL LETTER B
+0061 05A9 0315 0300 05AE 0062;0061 05AE 05A9 0300 0315 0062;0061 05AE 05A9 0300 0315 0062;0061 05AE 05A9 0300 0315 0062;0061 05AE 05A9 0300 0315 0062; # (a◌֩◌̕◌̀◌֮b; a◌֮◌֩◌̀◌̕b; a◌֮◌֩◌̀◌̕b; a◌֮◌֩◌̀◌̕b; a◌֮◌֩◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT TELISHA QETANA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 05AA 0062;0061 1DFA 0316 05AA 059A 0062;0061 1DFA 0316 05AA 059A 0062;0061 1DFA 0316 05AA 059A 0062;0061 1DFA 0316 05AA 059A 0062; # (a◌֚◌̖◌᷺◌֪b; a◌᷺◌̖◌֪◌֚b; a◌᷺◌̖◌֪◌֚b; a◌᷺◌̖◌֪◌֚b; a◌᷺◌̖◌֪◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW ACCENT YERAH BEN YOMO, LATIN SMALL LETTER B
+0061 05AA 059A 0316 1DFA 0062;0061 1DFA 05AA 0316 059A 0062;0061 1DFA 05AA 0316 059A 0062;0061 1DFA 05AA 0316 059A 0062;0061 1DFA 05AA 0316 059A 0062; # (a◌֪◌֚◌̖◌᷺b; a◌᷺◌֪◌̖◌֚b; a◌᷺◌֪◌̖◌֚b; a◌᷺◌֪◌̖◌֚b; a◌᷺◌֪◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YERAH BEN YOMO, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 05AB 0062;00E0 05AE 05AB 0315 0062;0061 05AE 0300 05AB 0315 0062;00E0 05AE 05AB 0315 0062;0061 05AE 0300 05AB 0315 0062; # (a◌̕◌̀◌֮◌֫b; à◌֮◌֫◌̕b; a◌֮◌̀◌֫◌̕b; à◌֮◌֫◌̕b; a◌֮◌̀◌֫◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT OLE, LATIN SMALL LETTER B
+0061 05AB 0315 0300 05AE 0062;0061 05AE 05AB 0300 0315 0062;0061 05AE 05AB 0300 0315 0062;0061 05AE 05AB 0300 0315 0062;0061 05AE 05AB 0300 0315 0062; # (a◌֫◌̕◌̀◌֮b; a◌֮◌֫◌̀◌̕b; a◌֮◌֫◌̀◌̕b; a◌֮◌֫◌̀◌̕b; a◌֮◌֫◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT OLE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 05AC 0062;00E0 05AE 05AC 0315 0062;0061 05AE 0300 05AC 0315 0062;00E0 05AE 05AC 0315 0062;0061 05AE 0300 05AC 0315 0062; # (a◌̕◌̀◌֮◌֬b; à◌֮◌֬◌̕b; a◌֮◌̀◌֬◌̕b; à◌֮◌֬◌̕b; a◌֮◌̀◌֬◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW ACCENT ILUY, LATIN SMALL LETTER B
+0061 05AC 0315 0300 05AE 0062;0061 05AE 05AC 0300 0315 0062;0061 05AE 05AC 0300 0315 0062;0061 05AE 05AC 0300 0315 0062;0061 05AE 05AC 0300 0315 0062; # (a◌֬◌̕◌̀◌֮b; a◌֮◌֬◌̀◌̕b; a◌֮◌֬◌̀◌̕b; a◌֮◌֬◌̀◌̕b; a◌֮◌֬◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW ACCENT ILUY, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 302E 059A 0316 05AD 0062;0061 0316 059A 05AD 302E 0062;0061 0316 059A 05AD 302E 0062;0061 0316 059A 05AD 302E 0062;0061 0316 059A 05AD 302E 0062; # (a〮◌֚◌̖◌֭b; a◌̖◌֚◌֭〮b; a◌̖◌֚◌֭〮b; a◌̖◌֚◌֭〮b; a◌̖◌֚◌֭〮b; ) LATIN SMALL LETTER A, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, HEBREW ACCENT DEHI, LATIN SMALL LETTER B
+0061 05AD 302E 059A 0316 0062;0061 0316 05AD 059A 302E 0062;0061 0316 05AD 059A 302E 0062;0061 0316 05AD 059A 302E 0062;0061 0316 05AD 059A 302E 0062; # (a◌֭〮◌֚◌̖b; a◌̖◌֭◌֚〮b; a◌̖◌֭◌֚〮b; a◌̖◌֭◌֚〮b; a◌̖◌֭◌֚〮b; ) LATIN SMALL LETTER A, HEBREW ACCENT DEHI, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, LATIN SMALL LETTER B
+0061 0300 05AE 1D16D 05AE 0062;00E0 1D16D 05AE 05AE 0062;0061 1D16D 05AE 05AE 0300 0062;00E0 1D16D 05AE 05AE 0062;0061 1D16D 05AE 05AE 0300 0062; # (a◌̀◌֮ð…­â—ŒÖ®b; àð…­â—ŒÖ®â—ŒÖ®b; að…­â—ŒÖ®â—ŒÖ®â—ŒÌ€b; àð…­â—ŒÖ®â—ŒÖ®b; að…­â—ŒÖ®â—ŒÖ®â—ŒÌ€b; ) LATIN SMALL LETTER A, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05AE 0300 05AE 1D16D 0062;00E0 1D16D 05AE 05AE 0062;0061 1D16D 05AE 05AE 0300 0062;00E0 1D16D 05AE 05AE 0062;0061 1D16D 05AE 05AE 0300 0062; # (a◌֮◌̀◌֮ð…­b; àð…­â—ŒÖ®â—ŒÖ®b; að…­â—ŒÖ®â—ŒÖ®â—ŒÌ€b; àð…­â—ŒÖ®â—ŒÖ®b; að…­â—ŒÖ®â—ŒÖ®â—ŒÌ€b; ) LATIN SMALL LETTER A, HEBREW ACCENT ZINOR, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 05AF 0062;00E0 05AE 05AF 0315 0062;0061 05AE 0300 05AF 0315 0062;00E0 05AE 05AF 0315 0062;0061 05AE 0300 05AF 0315 0062; # (a◌̕◌̀◌֮◌֯b; à◌֮◌֯◌̕b; a◌֮◌̀◌֯◌̕b; à◌֮◌֯◌̕b; a◌֮◌̀◌֯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW MARK MASORA CIRCLE, LATIN SMALL LETTER B
+0061 05AF 0315 0300 05AE 0062;0061 05AE 05AF 0300 0315 0062;0061 05AE 05AF 0300 0315 0062;0061 05AE 05AF 0300 0315 0062;0061 05AE 05AF 0300 0315 0062; # (a◌֯◌̕◌̀◌֮b; a◌֮◌֯◌̀◌̕b; a◌֮◌֯◌̀◌̕b; a◌֮◌֯◌̀◌̕b; a◌֮◌֯◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW MARK MASORA CIRCLE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05B1 05B0 094D 05B0 0062;0061 094D 05B0 05B0 05B1 0062;0061 094D 05B0 05B0 05B1 0062;0061 094D 05B0 05B0 05B1 0062;0061 094D 05B0 05B0 05B1 0062; # (a◌ֱ◌ְ◌à¥â—ŒÖ°b; aâ—Œà¥â—ŒÖ°â—ŒÖ°â—ŒÖ±b; aâ—Œà¥â—ŒÖ°â—ŒÖ°â—ŒÖ±b; aâ—Œà¥â—ŒÖ°â—ŒÖ°â—ŒÖ±b; aâ—Œà¥â—ŒÖ°â—ŒÖ°â—ŒÖ±b; ) LATIN SMALL LETTER A, HEBREW POINT HATAF SEGOL, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, HEBREW POINT SHEVA, LATIN SMALL LETTER B
+0061 05B0 05B1 05B0 094D 0062;0061 094D 05B0 05B0 05B1 0062;0061 094D 05B0 05B0 05B1 0062;0061 094D 05B0 05B0 05B1 0062;0061 094D 05B0 05B0 05B1 0062; # (a◌ְ◌ֱ◌ְ◌à¥b; aâ—Œà¥â—ŒÖ°â—ŒÖ°â—ŒÖ±b; aâ—Œà¥â—ŒÖ°â—ŒÖ°â—ŒÖ±b; aâ—Œà¥â—ŒÖ°â—ŒÖ°â—ŒÖ±b; aâ—Œà¥â—ŒÖ°â—ŒÖ°â—ŒÖ±b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, HEBREW POINT HATAF SEGOL, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 05B2 05B1 05B0 05B1 0062;0061 05B0 05B1 05B1 05B2 0062;0061 05B0 05B1 05B1 05B2 0062;0061 05B0 05B1 05B1 05B2 0062;0061 05B0 05B1 05B1 05B2 0062; # (a◌ֲ◌ֱ◌ְ◌ֱb; a◌ְ◌ֱ◌ֱ◌ֲb; a◌ְ◌ֱ◌ֱ◌ֲb; a◌ְ◌ֱ◌ֱ◌ֲb; a◌ְ◌ֱ◌ֱ◌ֲb; ) LATIN SMALL LETTER A, HEBREW POINT HATAF PATAH, HEBREW POINT HATAF SEGOL, HEBREW POINT SHEVA, HEBREW POINT HATAF SEGOL, LATIN SMALL LETTER B
+0061 05B1 05B2 05B1 05B0 0062;0061 05B0 05B1 05B1 05B2 0062;0061 05B0 05B1 05B1 05B2 0062;0061 05B0 05B1 05B1 05B2 0062;0061 05B0 05B1 05B1 05B2 0062; # (a◌ֱ◌ֲ◌ֱ◌ְb; a◌ְ◌ֱ◌ֱ◌ֲb; a◌ְ◌ֱ◌ֱ◌ֲb; a◌ְ◌ֱ◌ֱ◌ֲb; a◌ְ◌ֱ◌ֱ◌ֲb; ) LATIN SMALL LETTER A, HEBREW POINT HATAF SEGOL, HEBREW POINT HATAF PATAH, HEBREW POINT HATAF SEGOL, HEBREW POINT SHEVA, LATIN SMALL LETTER B
+0061 05B3 05B2 05B1 05B2 0062;0061 05B1 05B2 05B2 05B3 0062;0061 05B1 05B2 05B2 05B3 0062;0061 05B1 05B2 05B2 05B3 0062;0061 05B1 05B2 05B2 05B3 0062; # (a◌ֳ◌ֲ◌ֱ◌ֲb; a◌ֱ◌ֲ◌ֲ◌ֳb; a◌ֱ◌ֲ◌ֲ◌ֳb; a◌ֱ◌ֲ◌ֲ◌ֳb; a◌ֱ◌ֲ◌ֲ◌ֳb; ) LATIN SMALL LETTER A, HEBREW POINT HATAF QAMATS, HEBREW POINT HATAF PATAH, HEBREW POINT HATAF SEGOL, HEBREW POINT HATAF PATAH, LATIN SMALL LETTER B
+0061 05B2 05B3 05B2 05B1 0062;0061 05B1 05B2 05B2 05B3 0062;0061 05B1 05B2 05B2 05B3 0062;0061 05B1 05B2 05B2 05B3 0062;0061 05B1 05B2 05B2 05B3 0062; # (a◌ֲ◌ֳ◌ֲ◌ֱb; a◌ֱ◌ֲ◌ֲ◌ֳb; a◌ֱ◌ֲ◌ֲ◌ֳb; a◌ֱ◌ֲ◌ֲ◌ֳb; a◌ֱ◌ֲ◌ֲ◌ֳb; ) LATIN SMALL LETTER A, HEBREW POINT HATAF PATAH, HEBREW POINT HATAF QAMATS, HEBREW POINT HATAF PATAH, HEBREW POINT HATAF SEGOL, LATIN SMALL LETTER B
+0061 05B4 05B3 05B2 05B3 0062;0061 05B2 05B3 05B3 05B4 0062;0061 05B2 05B3 05B3 05B4 0062;0061 05B2 05B3 05B3 05B4 0062;0061 05B2 05B3 05B3 05B4 0062; # (a◌ִ◌ֳ◌ֲ◌ֳb; a◌ֲ◌ֳ◌ֳ◌ִb; a◌ֲ◌ֳ◌ֳ◌ִb; a◌ֲ◌ֳ◌ֳ◌ִb; a◌ֲ◌ֳ◌ֳ◌ִb; ) LATIN SMALL LETTER A, HEBREW POINT HIRIQ, HEBREW POINT HATAF QAMATS, HEBREW POINT HATAF PATAH, HEBREW POINT HATAF QAMATS, LATIN SMALL LETTER B
+0061 05B3 05B4 05B3 05B2 0062;0061 05B2 05B3 05B3 05B4 0062;0061 05B2 05B3 05B3 05B4 0062;0061 05B2 05B3 05B3 05B4 0062;0061 05B2 05B3 05B3 05B4 0062; # (a◌ֳ◌ִ◌ֳ◌ֲb; a◌ֲ◌ֳ◌ֳ◌ִb; a◌ֲ◌ֳ◌ֳ◌ִb; a◌ֲ◌ֳ◌ֳ◌ִb; a◌ֲ◌ֳ◌ֳ◌ִb; ) LATIN SMALL LETTER A, HEBREW POINT HATAF QAMATS, HEBREW POINT HIRIQ, HEBREW POINT HATAF QAMATS, HEBREW POINT HATAF PATAH, LATIN SMALL LETTER B
+0061 05B5 05B4 05B3 05B4 0062;0061 05B3 05B4 05B4 05B5 0062;0061 05B3 05B4 05B4 05B5 0062;0061 05B3 05B4 05B4 05B5 0062;0061 05B3 05B4 05B4 05B5 0062; # (a◌ֵ◌ִ◌ֳ◌ִb; a◌ֳ◌ִ◌ִ◌ֵb; a◌ֳ◌ִ◌ִ◌ֵb; a◌ֳ◌ִ◌ִ◌ֵb; a◌ֳ◌ִ◌ִ◌ֵb; ) LATIN SMALL LETTER A, HEBREW POINT TSERE, HEBREW POINT HIRIQ, HEBREW POINT HATAF QAMATS, HEBREW POINT HIRIQ, LATIN SMALL LETTER B
+0061 05B4 05B5 05B4 05B3 0062;0061 05B3 05B4 05B4 05B5 0062;0061 05B3 05B4 05B4 05B5 0062;0061 05B3 05B4 05B4 05B5 0062;0061 05B3 05B4 05B4 05B5 0062; # (a◌ִ◌ֵ◌ִ◌ֳb; a◌ֳ◌ִ◌ִ◌ֵb; a◌ֳ◌ִ◌ִ◌ֵb; a◌ֳ◌ִ◌ִ◌ֵb; a◌ֳ◌ִ◌ִ◌ֵb; ) LATIN SMALL LETTER A, HEBREW POINT HIRIQ, HEBREW POINT TSERE, HEBREW POINT HIRIQ, HEBREW POINT HATAF QAMATS, LATIN SMALL LETTER B
+0061 05B6 05B5 05B4 05B5 0062;0061 05B4 05B5 05B5 05B6 0062;0061 05B4 05B5 05B5 05B6 0062;0061 05B4 05B5 05B5 05B6 0062;0061 05B4 05B5 05B5 05B6 0062; # (a◌ֶ◌ֵ◌ִ◌ֵb; a◌ִ◌ֵ◌ֵ◌ֶb; a◌ִ◌ֵ◌ֵ◌ֶb; a◌ִ◌ֵ◌ֵ◌ֶb; a◌ִ◌ֵ◌ֵ◌ֶb; ) LATIN SMALL LETTER A, HEBREW POINT SEGOL, HEBREW POINT TSERE, HEBREW POINT HIRIQ, HEBREW POINT TSERE, LATIN SMALL LETTER B
+0061 05B5 05B6 05B5 05B4 0062;0061 05B4 05B5 05B5 05B6 0062;0061 05B4 05B5 05B5 05B6 0062;0061 05B4 05B5 05B5 05B6 0062;0061 05B4 05B5 05B5 05B6 0062; # (a◌ֵ◌ֶ◌ֵ◌ִb; a◌ִ◌ֵ◌ֵ◌ֶb; a◌ִ◌ֵ◌ֵ◌ֶb; a◌ִ◌ֵ◌ֵ◌ֶb; a◌ִ◌ֵ◌ֵ◌ֶb; ) LATIN SMALL LETTER A, HEBREW POINT TSERE, HEBREW POINT SEGOL, HEBREW POINT TSERE, HEBREW POINT HIRIQ, LATIN SMALL LETTER B
+0061 05B7 05B6 05B5 05B6 0062;0061 05B5 05B6 05B6 05B7 0062;0061 05B5 05B6 05B6 05B7 0062;0061 05B5 05B6 05B6 05B7 0062;0061 05B5 05B6 05B6 05B7 0062; # (a◌ַ◌ֶ◌ֵ◌ֶb; a◌ֵ◌ֶ◌ֶ◌ַb; a◌ֵ◌ֶ◌ֶ◌ַb; a◌ֵ◌ֶ◌ֶ◌ַb; a◌ֵ◌ֶ◌ֶ◌ַb; ) LATIN SMALL LETTER A, HEBREW POINT PATAH, HEBREW POINT SEGOL, HEBREW POINT TSERE, HEBREW POINT SEGOL, LATIN SMALL LETTER B
+0061 05B6 05B7 05B6 05B5 0062;0061 05B5 05B6 05B6 05B7 0062;0061 05B5 05B6 05B6 05B7 0062;0061 05B5 05B6 05B6 05B7 0062;0061 05B5 05B6 05B6 05B7 0062; # (a◌ֶ◌ַ◌ֶ◌ֵb; a◌ֵ◌ֶ◌ֶ◌ַb; a◌ֵ◌ֶ◌ֶ◌ַb; a◌ֵ◌ֶ◌ֶ◌ַb; a◌ֵ◌ֶ◌ֶ◌ַb; ) LATIN SMALL LETTER A, HEBREW POINT SEGOL, HEBREW POINT PATAH, HEBREW POINT SEGOL, HEBREW POINT TSERE, LATIN SMALL LETTER B
+0061 05B8 05B7 05B6 05B7 0062;0061 05B6 05B7 05B7 05B8 0062;0061 05B6 05B7 05B7 05B8 0062;0061 05B6 05B7 05B7 05B8 0062;0061 05B6 05B7 05B7 05B8 0062; # (a◌ָ◌ַ◌ֶ◌ַb; a◌ֶ◌ַ◌ַ◌ָb; a◌ֶ◌ַ◌ַ◌ָb; a◌ֶ◌ַ◌ַ◌ָb; a◌ֶ◌ַ◌ַ◌ָb; ) LATIN SMALL LETTER A, HEBREW POINT QAMATS, HEBREW POINT PATAH, HEBREW POINT SEGOL, HEBREW POINT PATAH, LATIN SMALL LETTER B
+0061 05B7 05B8 05B7 05B6 0062;0061 05B6 05B7 05B7 05B8 0062;0061 05B6 05B7 05B7 05B8 0062;0061 05B6 05B7 05B7 05B8 0062;0061 05B6 05B7 05B7 05B8 0062; # (a◌ַ◌ָ◌ַ◌ֶb; a◌ֶ◌ַ◌ַ◌ָb; a◌ֶ◌ַ◌ַ◌ָb; a◌ֶ◌ַ◌ַ◌ָb; a◌ֶ◌ַ◌ַ◌ָb; ) LATIN SMALL LETTER A, HEBREW POINT PATAH, HEBREW POINT QAMATS, HEBREW POINT PATAH, HEBREW POINT SEGOL, LATIN SMALL LETTER B
+0061 05B9 05B8 05B7 05B8 0062;0061 05B7 05B8 05B8 05B9 0062;0061 05B7 05B8 05B8 05B9 0062;0061 05B7 05B8 05B8 05B9 0062;0061 05B7 05B8 05B8 05B9 0062; # (a◌ֹ◌ָ◌ַ◌ָb; a◌ַ◌ָ◌ָ◌ֹb; a◌ַ◌ָ◌ָ◌ֹb; a◌ַ◌ָ◌ָ◌ֹb; a◌ַ◌ָ◌ָ◌ֹb; ) LATIN SMALL LETTER A, HEBREW POINT HOLAM, HEBREW POINT QAMATS, HEBREW POINT PATAH, HEBREW POINT QAMATS, LATIN SMALL LETTER B
+0061 05B8 05B9 05B8 05B7 0062;0061 05B7 05B8 05B8 05B9 0062;0061 05B7 05B8 05B8 05B9 0062;0061 05B7 05B8 05B8 05B9 0062;0061 05B7 05B8 05B8 05B9 0062; # (a◌ָ◌ֹ◌ָ◌ַb; a◌ַ◌ָ◌ָ◌ֹb; a◌ַ◌ָ◌ָ◌ֹb; a◌ַ◌ָ◌ָ◌ֹb; a◌ַ◌ָ◌ָ◌ֹb; ) LATIN SMALL LETTER A, HEBREW POINT QAMATS, HEBREW POINT HOLAM, HEBREW POINT QAMATS, HEBREW POINT PATAH, LATIN SMALL LETTER B
+0061 05BB 05B9 05B8 05B9 0062;0061 05B8 05B9 05B9 05BB 0062;0061 05B8 05B9 05B9 05BB 0062;0061 05B8 05B9 05B9 05BB 0062;0061 05B8 05B9 05B9 05BB 0062; # (a◌ֻ◌ֹ◌ָ◌ֹb; a◌ָ◌ֹ◌ֹ◌ֻb; a◌ָ◌ֹ◌ֹ◌ֻb; a◌ָ◌ֹ◌ֹ◌ֻb; a◌ָ◌ֹ◌ֹ◌ֻb; ) LATIN SMALL LETTER A, HEBREW POINT QUBUTS, HEBREW POINT HOLAM, HEBREW POINT QAMATS, HEBREW POINT HOLAM, LATIN SMALL LETTER B
+0061 05B9 05BB 05B9 05B8 0062;0061 05B8 05B9 05B9 05BB 0062;0061 05B8 05B9 05B9 05BB 0062;0061 05B8 05B9 05B9 05BB 0062;0061 05B8 05B9 05B9 05BB 0062; # (a◌ֹ◌ֻ◌ֹ◌ָb; a◌ָ◌ֹ◌ֹ◌ֻb; a◌ָ◌ֹ◌ֹ◌ֻb; a◌ָ◌ֹ◌ֹ◌ֻb; a◌ָ◌ֹ◌ֹ◌ֻb; ) LATIN SMALL LETTER A, HEBREW POINT HOLAM, HEBREW POINT QUBUTS, HEBREW POINT HOLAM, HEBREW POINT QAMATS, LATIN SMALL LETTER B
+0061 05BB 05B9 05B8 05BA 0062;0061 05B8 05B9 05BA 05BB 0062;0061 05B8 05B9 05BA 05BB 0062;0061 05B8 05B9 05BA 05BB 0062;0061 05B8 05B9 05BA 05BB 0062; # (a◌ֻ◌ֹ◌ָ◌ֺb; a◌ָ◌ֹ◌ֺ◌ֻb; a◌ָ◌ֹ◌ֺ◌ֻb; a◌ָ◌ֹ◌ֺ◌ֻb; a◌ָ◌ֹ◌ֺ◌ֻb; ) LATIN SMALL LETTER A, HEBREW POINT QUBUTS, HEBREW POINT HOLAM, HEBREW POINT QAMATS, HEBREW POINT HOLAM HASER FOR VAV, LATIN SMALL LETTER B
+0061 05BA 05BB 05B9 05B8 0062;0061 05B8 05BA 05B9 05BB 0062;0061 05B8 05BA 05B9 05BB 0062;0061 05B8 05BA 05B9 05BB 0062;0061 05B8 05BA 05B9 05BB 0062; # (a◌ֺ◌ֻ◌ֹ◌ָb; a◌ָ◌ֺ◌ֹ◌ֻb; a◌ָ◌ֺ◌ֹ◌ֻb; a◌ָ◌ֺ◌ֹ◌ֻb; a◌ָ◌ֺ◌ֹ◌ֻb; ) LATIN SMALL LETTER A, HEBREW POINT HOLAM HASER FOR VAV, HEBREW POINT QUBUTS, HEBREW POINT HOLAM, HEBREW POINT QAMATS, LATIN SMALL LETTER B
+0061 05BC 05BB 05B9 05BB 0062;0061 05B9 05BB 05BB 05BC 0062;0061 05B9 05BB 05BB 05BC 0062;0061 05B9 05BB 05BB 05BC 0062;0061 05B9 05BB 05BB 05BC 0062; # (a◌ּ◌ֻ◌ֹ◌ֻb; a◌ֹ◌ֻ◌ֻ◌ּb; a◌ֹ◌ֻ◌ֻ◌ּb; a◌ֹ◌ֻ◌ֻ◌ּb; a◌ֹ◌ֻ◌ֻ◌ּb; ) LATIN SMALL LETTER A, HEBREW POINT DAGESH OR MAPIQ, HEBREW POINT QUBUTS, HEBREW POINT HOLAM, HEBREW POINT QUBUTS, LATIN SMALL LETTER B
+0061 05BB 05BC 05BB 05B9 0062;0061 05B9 05BB 05BB 05BC 0062;0061 05B9 05BB 05BB 05BC 0062;0061 05B9 05BB 05BB 05BC 0062;0061 05B9 05BB 05BB 05BC 0062; # (a◌ֻ◌ּ◌ֻ◌ֹb; a◌ֹ◌ֻ◌ֻ◌ּb; a◌ֹ◌ֻ◌ֻ◌ּb; a◌ֹ◌ֻ◌ֻ◌ּb; a◌ֹ◌ֻ◌ֻ◌ּb; ) LATIN SMALL LETTER A, HEBREW POINT QUBUTS, HEBREW POINT DAGESH OR MAPIQ, HEBREW POINT QUBUTS, HEBREW POINT HOLAM, LATIN SMALL LETTER B
+0061 05BD 05BC 05BB 05BC 0062;0061 05BB 05BC 05BC 05BD 0062;0061 05BB 05BC 05BC 05BD 0062;0061 05BB 05BC 05BC 05BD 0062;0061 05BB 05BC 05BC 05BD 0062; # (a◌ֽ◌ּ◌ֻ◌ּb; a◌ֻ◌ּ◌ּ◌ֽb; a◌ֻ◌ּ◌ּ◌ֽb; a◌ֻ◌ּ◌ּ◌ֽb; a◌ֻ◌ּ◌ּ◌ֽb; ) LATIN SMALL LETTER A, HEBREW POINT METEG, HEBREW POINT DAGESH OR MAPIQ, HEBREW POINT QUBUTS, HEBREW POINT DAGESH OR MAPIQ, LATIN SMALL LETTER B
+0061 05BC 05BD 05BC 05BB 0062;0061 05BB 05BC 05BC 05BD 0062;0061 05BB 05BC 05BC 05BD 0062;0061 05BB 05BC 05BC 05BD 0062;0061 05BB 05BC 05BC 05BD 0062; # (a◌ּ◌ֽ◌ּ◌ֻb; a◌ֻ◌ּ◌ּ◌ֽb; a◌ֻ◌ּ◌ּ◌ֽb; a◌ֻ◌ּ◌ּ◌ֽb; a◌ֻ◌ּ◌ּ◌ֽb; ) LATIN SMALL LETTER A, HEBREW POINT DAGESH OR MAPIQ, HEBREW POINT METEG, HEBREW POINT DAGESH OR MAPIQ, HEBREW POINT QUBUTS, LATIN SMALL LETTER B
+0061 05BF 05BD 05BC 05BD 0062;0061 05BC 05BD 05BD 05BF 0062;0061 05BC 05BD 05BD 05BF 0062;0061 05BC 05BD 05BD 05BF 0062;0061 05BC 05BD 05BD 05BF 0062; # (a◌ֿ◌ֽ◌ּ◌ֽb; a◌ּ◌ֽ◌ֽ◌ֿb; a◌ּ◌ֽ◌ֽ◌ֿb; a◌ּ◌ֽ◌ֽ◌ֿb; a◌ּ◌ֽ◌ֽ◌ֿb; ) LATIN SMALL LETTER A, HEBREW POINT RAFE, HEBREW POINT METEG, HEBREW POINT DAGESH OR MAPIQ, HEBREW POINT METEG, LATIN SMALL LETTER B
+0061 05BD 05BF 05BD 05BC 0062;0061 05BC 05BD 05BD 05BF 0062;0061 05BC 05BD 05BD 05BF 0062;0061 05BC 05BD 05BD 05BF 0062;0061 05BC 05BD 05BD 05BF 0062; # (a◌ֽ◌ֿ◌ֽ◌ּb; a◌ּ◌ֽ◌ֽ◌ֿb; a◌ּ◌ֽ◌ֽ◌ֿb; a◌ּ◌ֽ◌ֽ◌ֿb; a◌ּ◌ֽ◌ֽ◌ֿb; ) LATIN SMALL LETTER A, HEBREW POINT METEG, HEBREW POINT RAFE, HEBREW POINT METEG, HEBREW POINT DAGESH OR MAPIQ, LATIN SMALL LETTER B
+0061 05C1 05BF 05BD 05BF 0062;0061 05BD 05BF 05BF 05C1 0062;0061 05BD 05BF 05BF 05C1 0062;0061 05BD 05BF 05BF 05C1 0062;0061 05BD 05BF 05BF 05C1 0062; # (aâ—Œ×◌ֿ◌ֽ◌ֿb; a◌ֽ◌ֿ◌ֿ◌×b; a◌ֽ◌ֿ◌ֿ◌×b; a◌ֽ◌ֿ◌ֿ◌×b; a◌ֽ◌ֿ◌ֿ◌×b; ) LATIN SMALL LETTER A, HEBREW POINT SHIN DOT, HEBREW POINT RAFE, HEBREW POINT METEG, HEBREW POINT RAFE, LATIN SMALL LETTER B
+0061 05BF 05C1 05BF 05BD 0062;0061 05BD 05BF 05BF 05C1 0062;0061 05BD 05BF 05BF 05C1 0062;0061 05BD 05BF 05BF 05C1 0062;0061 05BD 05BF 05BF 05C1 0062; # (a◌ֿ◌×◌ֿ◌ֽb; a◌ֽ◌ֿ◌ֿ◌×b; a◌ֽ◌ֿ◌ֿ◌×b; a◌ֽ◌ֿ◌ֿ◌×b; a◌ֽ◌ֿ◌ֿ◌×b; ) LATIN SMALL LETTER A, HEBREW POINT RAFE, HEBREW POINT SHIN DOT, HEBREW POINT RAFE, HEBREW POINT METEG, LATIN SMALL LETTER B
+0061 05C2 05C1 05BF 05C1 0062;0061 05BF 05C1 05C1 05C2 0062;0061 05BF 05C1 05C1 05C2 0062;0061 05BF 05C1 05C1 05C2 0062;0061 05BF 05C1 05C1 05C2 0062; # (a◌ׂ◌×◌ֿ◌×b; a◌ֿ◌×â—Œ×◌ׂb; a◌ֿ◌×â—Œ×◌ׂb; a◌ֿ◌×â—Œ×◌ׂb; a◌ֿ◌×â—Œ×◌ׂb; ) LATIN SMALL LETTER A, HEBREW POINT SIN DOT, HEBREW POINT SHIN DOT, HEBREW POINT RAFE, HEBREW POINT SHIN DOT, LATIN SMALL LETTER B
+0061 05C1 05C2 05C1 05BF 0062;0061 05BF 05C1 05C1 05C2 0062;0061 05BF 05C1 05C1 05C2 0062;0061 05BF 05C1 05C1 05C2 0062;0061 05BF 05C1 05C1 05C2 0062; # (aâ—Œ×◌ׂ◌×◌ֿb; a◌ֿ◌×â—Œ×◌ׂb; a◌ֿ◌×â—Œ×◌ׂb; a◌ֿ◌×â—Œ×◌ׂb; a◌ֿ◌×â—Œ×◌ׂb; ) LATIN SMALL LETTER A, HEBREW POINT SHIN DOT, HEBREW POINT SIN DOT, HEBREW POINT SHIN DOT, HEBREW POINT RAFE, LATIN SMALL LETTER B
+0061 FB1E 05C2 05C1 05C2 0062;0061 05C1 05C2 05C2 FB1E 0062;0061 05C1 05C2 05C2 FB1E 0062;0061 05C1 05C2 05C2 FB1E 0062;0061 05C1 05C2 05C2 FB1E 0062; # (a◌ﬞ◌ׂ◌×◌ׂb; aâ—Œ×◌ׂ◌ׂ◌ﬞb; aâ—Œ×◌ׂ◌ׂ◌ﬞb; aâ—Œ×◌ׂ◌ׂ◌ﬞb; aâ—Œ×◌ׂ◌ׂ◌ﬞb; ) LATIN SMALL LETTER A, HEBREW POINT JUDEO-SPANISH VARIKA, HEBREW POINT SIN DOT, HEBREW POINT SHIN DOT, HEBREW POINT SIN DOT, LATIN SMALL LETTER B
+0061 05C2 FB1E 05C2 05C1 0062;0061 05C1 05C2 05C2 FB1E 0062;0061 05C1 05C2 05C2 FB1E 0062;0061 05C1 05C2 05C2 FB1E 0062;0061 05C1 05C2 05C2 FB1E 0062; # (a◌ׂ◌ﬞ◌ׂ◌×b; aâ—Œ×◌ׂ◌ׂ◌ﬞb; aâ—Œ×◌ׂ◌ׂ◌ﬞb; aâ—Œ×◌ׂ◌ׂ◌ﬞb; aâ—Œ×◌ׂ◌ׂ◌ﬞb; ) LATIN SMALL LETTER A, HEBREW POINT SIN DOT, HEBREW POINT JUDEO-SPANISH VARIKA, HEBREW POINT SIN DOT, HEBREW POINT SHIN DOT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 05C4 0062;00E0 05AE 05C4 0315 0062;0061 05AE 0300 05C4 0315 0062;00E0 05AE 05C4 0315 0062;0061 05AE 0300 05C4 0315 0062; # (a◌̕◌̀◌֮◌ׄb; à◌֮◌ׄ◌̕b; a◌֮◌̀◌ׄ◌̕b; à◌֮◌ׄ◌̕b; a◌֮◌̀◌ׄ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HEBREW MARK UPPER DOT, LATIN SMALL LETTER B
+0061 05C4 0315 0300 05AE 0062;0061 05AE 05C4 0300 0315 0062;0061 05AE 05C4 0300 0315 0062;0061 05AE 05C4 0300 0315 0062;0061 05AE 05C4 0300 0315 0062; # (a◌ׄ◌̕◌̀◌֮b; a◌֮◌ׄ◌̀◌̕b; a◌֮◌ׄ◌̀◌̕b; a◌֮◌ׄ◌̀◌̕b; a◌֮◌ׄ◌̀◌̕b; ) LATIN SMALL LETTER A, HEBREW MARK UPPER DOT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 05C5 0062;0061 1DFA 0316 05C5 059A 0062;0061 1DFA 0316 05C5 059A 0062;0061 1DFA 0316 05C5 059A 0062;0061 1DFA 0316 05C5 059A 0062; # (a◌֚◌̖◌᷺◌ׅb; a◌᷺◌̖◌ׅ◌֚b; a◌᷺◌̖◌ׅ◌֚b; a◌᷺◌̖◌ׅ◌֚b; a◌᷺◌̖◌ׅ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, HEBREW MARK LOWER DOT, LATIN SMALL LETTER B
+0061 05C5 059A 0316 1DFA 0062;0061 1DFA 05C5 0316 059A 0062;0061 1DFA 05C5 0316 059A 0062;0061 1DFA 05C5 0316 059A 0062;0061 1DFA 05C5 0316 059A 0062; # (a◌ׅ◌֚◌̖◌᷺b; a◌᷺◌ׅ◌̖◌֚b; a◌᷺◌ׅ◌̖◌֚b; a◌᷺◌ׅ◌̖◌֚b; a◌᷺◌ׅ◌̖◌֚b; ) LATIN SMALL LETTER A, HEBREW MARK LOWER DOT, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 05B9 05B8 05B7 05C7 0062;0061 05B7 05B8 05C7 05B9 0062;0061 05B7 05B8 05C7 05B9 0062;0061 05B7 05B8 05C7 05B9 0062;0061 05B7 05B8 05C7 05B9 0062; # (a◌ֹ◌ָ◌ַ◌ׇb; a◌ַ◌ָ◌ׇ◌ֹb; a◌ַ◌ָ◌ׇ◌ֹb; a◌ַ◌ָ◌ׇ◌ֹb; a◌ַ◌ָ◌ׇ◌ֹb; ) LATIN SMALL LETTER A, HEBREW POINT HOLAM, HEBREW POINT QAMATS, HEBREW POINT PATAH, HEBREW POINT QAMATS QATAN, LATIN SMALL LETTER B
+0061 05C7 05B9 05B8 05B7 0062;0061 05B7 05C7 05B8 05B9 0062;0061 05B7 05C7 05B8 05B9 0062;0061 05B7 05C7 05B8 05B9 0062;0061 05B7 05C7 05B8 05B9 0062; # (a◌ׇ◌ֹ◌ָ◌ַb; a◌ַ◌ׇ◌ָ◌ֹb; a◌ַ◌ׇ◌ָ◌ֹb; a◌ַ◌ׇ◌ָ◌ֹb; a◌ַ◌ׇ◌ָ◌ֹb; ) LATIN SMALL LETTER A, HEBREW POINT QAMATS QATAN, HEBREW POINT HOLAM, HEBREW POINT QAMATS, HEBREW POINT PATAH, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0610 0062;00E0 05AE 0610 0315 0062;0061 05AE 0300 0610 0315 0062;00E0 05AE 0610 0315 0062;0061 05AE 0300 0610 0315 0062; # (a◌̕◌̀◌֮◌Øb; à◌֮◌Ø◌̕b; a◌֮◌̀◌Ø◌̕b; à◌֮◌Ø◌̕b; a◌֮◌̀◌Ø◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM, LATIN SMALL LETTER B
+0061 0610 0315 0300 05AE 0062;0061 05AE 0610 0300 0315 0062;0061 05AE 0610 0300 0315 0062;0061 05AE 0610 0300 0315 0062;0061 05AE 0610 0300 0315 0062; # (aâ—ŒØ◌̕◌̀◌֮b; a◌֮◌Ø◌̀◌̕b; a◌֮◌Ø◌̀◌̕b; a◌֮◌Ø◌̀◌̕b; a◌֮◌Ø◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0611 0062;00E0 05AE 0611 0315 0062;0061 05AE 0300 0611 0315 0062;00E0 05AE 0611 0315 0062;0061 05AE 0300 0611 0315 0062; # (a◌̕◌̀◌֮◌ؑb; à◌֮◌ؑ◌̕b; a◌֮◌̀◌ؑ◌̕b; à◌֮◌ؑ◌̕b; a◌֮◌̀◌ؑ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SIGN ALAYHE ASSALLAM, LATIN SMALL LETTER B
+0061 0611 0315 0300 05AE 0062;0061 05AE 0611 0300 0315 0062;0061 05AE 0611 0300 0315 0062;0061 05AE 0611 0300 0315 0062;0061 05AE 0611 0300 0315 0062; # (a◌ؑ◌̕◌̀◌֮b; a◌֮◌ؑ◌̀◌̕b; a◌֮◌ؑ◌̀◌̕b; a◌֮◌ؑ◌̀◌̕b; a◌֮◌ؑ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SIGN ALAYHE ASSALLAM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0612 0062;00E0 05AE 0612 0315 0062;0061 05AE 0300 0612 0315 0062;00E0 05AE 0612 0315 0062;0061 05AE 0300 0612 0315 0062; # (a◌̕◌̀◌֮◌ؒb; à◌֮◌ؒ◌̕b; a◌֮◌̀◌ؒ◌̕b; à◌֮◌ؒ◌̕b; a◌֮◌̀◌ؒ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SIGN RAHMATULLAH ALAYHE, LATIN SMALL LETTER B
+0061 0612 0315 0300 05AE 0062;0061 05AE 0612 0300 0315 0062;0061 05AE 0612 0300 0315 0062;0061 05AE 0612 0300 0315 0062;0061 05AE 0612 0300 0315 0062; # (a◌ؒ◌̕◌̀◌֮b; a◌֮◌ؒ◌̀◌̕b; a◌֮◌ؒ◌̀◌̕b; a◌֮◌ؒ◌̀◌̕b; a◌֮◌ؒ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SIGN RAHMATULLAH ALAYHE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0613 0062;00E0 05AE 0613 0315 0062;0061 05AE 0300 0613 0315 0062;00E0 05AE 0613 0315 0062;0061 05AE 0300 0613 0315 0062; # (a◌̕◌̀◌֮◌ؓb; à◌֮◌ؓ◌̕b; a◌֮◌̀◌ؓ◌̕b; à◌֮◌ؓ◌̕b; a◌֮◌̀◌ؓ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SIGN RADI ALLAHOU ANHU, LATIN SMALL LETTER B
+0061 0613 0315 0300 05AE 0062;0061 05AE 0613 0300 0315 0062;0061 05AE 0613 0300 0315 0062;0061 05AE 0613 0300 0315 0062;0061 05AE 0613 0300 0315 0062; # (a◌ؓ◌̕◌̀◌֮b; a◌֮◌ؓ◌̀◌̕b; a◌֮◌ؓ◌̀◌̕b; a◌֮◌ؓ◌̀◌̕b; a◌֮◌ؓ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SIGN RADI ALLAHOU ANHU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0614 0062;00E0 05AE 0614 0315 0062;0061 05AE 0300 0614 0315 0062;00E0 05AE 0614 0315 0062;0061 05AE 0300 0614 0315 0062; # (a◌̕◌̀◌֮◌ؔb; à◌֮◌ؔ◌̕b; a◌֮◌̀◌ؔ◌̕b; à◌֮◌ؔ◌̕b; a◌֮◌̀◌ؔ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SIGN TAKHALLUS, LATIN SMALL LETTER B
+0061 0614 0315 0300 05AE 0062;0061 05AE 0614 0300 0315 0062;0061 05AE 0614 0300 0315 0062;0061 05AE 0614 0300 0315 0062;0061 05AE 0614 0300 0315 0062; # (a◌ؔ◌̕◌̀◌֮b; a◌֮◌ؔ◌̀◌̕b; a◌֮◌ؔ◌̀◌̕b; a◌֮◌ؔ◌̀◌̕b; a◌֮◌ؔ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SIGN TAKHALLUS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0615 0062;00E0 05AE 0615 0315 0062;0061 05AE 0300 0615 0315 0062;00E0 05AE 0615 0315 0062;0061 05AE 0300 0615 0315 0062; # (a◌̕◌̀◌֮◌ؕb; à◌֮◌ؕ◌̕b; a◌֮◌̀◌ؕ◌̕b; à◌֮◌ؕ◌̕b; a◌֮◌̀◌ؕ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH TAH, LATIN SMALL LETTER B
+0061 0615 0315 0300 05AE 0062;0061 05AE 0615 0300 0315 0062;0061 05AE 0615 0300 0315 0062;0061 05AE 0615 0300 0315 0062;0061 05AE 0615 0300 0315 0062; # (a◌ؕ◌̕◌̀◌֮b; a◌֮◌ؕ◌̀◌̕b; a◌֮◌ؕ◌̀◌̕b; a◌֮◌ؕ◌̀◌̕b; a◌֮◌ؕ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH TAH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0616 0062;00E0 05AE 0616 0315 0062;0061 05AE 0300 0616 0315 0062;00E0 05AE 0616 0315 0062;0061 05AE 0300 0616 0315 0062; # (a◌̕◌̀◌֮◌ؖb; à◌֮◌ؖ◌̕b; a◌֮◌̀◌ؖ◌̕b; à◌֮◌ؖ◌̕b; a◌֮◌̀◌ؖ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH LIGATURE ALEF WITH LAM WITH YEH, LATIN SMALL LETTER B
+0061 0616 0315 0300 05AE 0062;0061 05AE 0616 0300 0315 0062;0061 05AE 0616 0300 0315 0062;0061 05AE 0616 0300 0315 0062;0061 05AE 0616 0300 0315 0062; # (a◌ؖ◌̕◌̀◌֮b; a◌֮◌ؖ◌̀◌̕b; a◌֮◌ؖ◌̀◌̕b; a◌֮◌ؖ◌̀◌̕b; a◌֮◌ؖ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH LIGATURE ALEF WITH LAM WITH YEH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0617 0062;00E0 05AE 0617 0315 0062;0061 05AE 0300 0617 0315 0062;00E0 05AE 0617 0315 0062;0061 05AE 0300 0617 0315 0062; # (a◌̕◌̀◌֮◌ؗb; à◌֮◌ؗ◌̕b; a◌֮◌̀◌ؗ◌̕b; à◌֮◌ؗ◌̕b; a◌֮◌̀◌ؗ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH ZAIN, LATIN SMALL LETTER B
+0061 0617 0315 0300 05AE 0062;0061 05AE 0617 0300 0315 0062;0061 05AE 0617 0300 0315 0062;0061 05AE 0617 0300 0315 0062;0061 05AE 0617 0300 0315 0062; # (a◌ؗ◌̕◌̀◌֮b; a◌֮◌ؗ◌̀◌̕b; a◌֮◌ؗ◌̀◌̕b; a◌֮◌ؗ◌̀◌̕b; a◌֮◌ؗ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH ZAIN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0619 0618 064D 0618 0062;0061 064D 0618 0618 0619 0062;0061 064D 0618 0618 0619 0062;0061 064D 0618 0618 0619 0062;0061 064D 0618 0618 0619 0062; # (a◌ؙ◌ؘ◌Ù◌ؘb; aâ—ŒÙ◌ؘ◌ؘ◌ؙb; aâ—ŒÙ◌ؘ◌ؘ◌ؙb; aâ—ŒÙ◌ؘ◌ؘ◌ؙb; aâ—ŒÙ◌ؘ◌ؘ◌ؙb; ) LATIN SMALL LETTER A, ARABIC SMALL DAMMA, ARABIC SMALL FATHA, ARABIC KASRATAN, ARABIC SMALL FATHA, LATIN SMALL LETTER B
+0061 0618 0619 0618 064D 0062;0061 064D 0618 0618 0619 0062;0061 064D 0618 0618 0619 0062;0061 064D 0618 0618 0619 0062;0061 064D 0618 0618 0619 0062; # (a◌ؘ◌ؙ◌ؘ◌Ùb; aâ—ŒÙ◌ؘ◌ؘ◌ؙb; aâ—ŒÙ◌ؘ◌ؘ◌ؙb; aâ—ŒÙ◌ؘ◌ؘ◌ؙb; aâ—ŒÙ◌ؘ◌ؘ◌ؙb; ) LATIN SMALL LETTER A, ARABIC SMALL FATHA, ARABIC SMALL DAMMA, ARABIC SMALL FATHA, ARABIC KASRATAN, LATIN SMALL LETTER B
+0061 061A 0619 0618 0619 0062;0061 0618 0619 0619 061A 0062;0061 0618 0619 0619 061A 0062;0061 0618 0619 0619 061A 0062;0061 0618 0619 0619 061A 0062; # (a◌ؚ◌ؙ◌ؘ◌ؙb; a◌ؘ◌ؙ◌ؙ◌ؚb; a◌ؘ◌ؙ◌ؙ◌ؚb; a◌ؘ◌ؙ◌ؙ◌ؚb; a◌ؘ◌ؙ◌ؙ◌ؚb; ) LATIN SMALL LETTER A, ARABIC SMALL KASRA, ARABIC SMALL DAMMA, ARABIC SMALL FATHA, ARABIC SMALL DAMMA, LATIN SMALL LETTER B
+0061 0619 061A 0619 0618 0062;0061 0618 0619 0619 061A 0062;0061 0618 0619 0619 061A 0062;0061 0618 0619 0619 061A 0062;0061 0618 0619 0619 061A 0062; # (a◌ؙ◌ؚ◌ؙ◌ؘb; a◌ؘ◌ؙ◌ؙ◌ؚb; a◌ؘ◌ؙ◌ؙ◌ؚb; a◌ؘ◌ؙ◌ؙ◌ؚb; a◌ؘ◌ؙ◌ؙ◌ؚb; ) LATIN SMALL LETTER A, ARABIC SMALL DAMMA, ARABIC SMALL KASRA, ARABIC SMALL DAMMA, ARABIC SMALL FATHA, LATIN SMALL LETTER B
+0061 0651 061A 0619 061A 0062;0061 0619 061A 061A 0651 0062;0061 0619 061A 061A 0651 0062;0061 0619 061A 061A 0651 0062;0061 0619 061A 061A 0651 0062; # (a◌ّ◌ؚ◌ؙ◌ؚb; a◌ؙ◌ؚ◌ؚ◌ّb; a◌ؙ◌ؚ◌ؚ◌ّb; a◌ؙ◌ؚ◌ؚ◌ّb; a◌ؙ◌ؚ◌ؚ◌ّb; ) LATIN SMALL LETTER A, ARABIC SHADDA, ARABIC SMALL KASRA, ARABIC SMALL DAMMA, ARABIC SMALL KASRA, LATIN SMALL LETTER B
+0061 061A 0651 061A 0619 0062;0061 0619 061A 061A 0651 0062;0061 0619 061A 061A 0651 0062;0061 0619 061A 061A 0651 0062;0061 0619 061A 061A 0651 0062; # (a◌ؚ◌ّ◌ؚ◌ؙb; a◌ؙ◌ؚ◌ؚ◌ّb; a◌ؙ◌ؚ◌ؚ◌ّb; a◌ؙ◌ؚ◌ؚ◌ّb; a◌ؙ◌ؚ◌ؚ◌ّb; ) LATIN SMALL LETTER A, ARABIC SMALL KASRA, ARABIC SHADDA, ARABIC SMALL KASRA, ARABIC SMALL DAMMA, LATIN SMALL LETTER B
+0061 064C 064B FB1E 064B 0062;0061 FB1E 064B 064B 064C 0062;0061 FB1E 064B 064B 064C 0062;0061 FB1E 064B 064B 064C 0062;0061 FB1E 064B 064B 064C 0062; # (a◌ٌ◌ً◌ﬞ◌ًb; a◌ﬞ◌ً◌ً◌ٌb; a◌ﬞ◌ً◌ً◌ٌb; a◌ﬞ◌ً◌ً◌ٌb; a◌ﬞ◌ً◌ً◌ٌb; ) LATIN SMALL LETTER A, ARABIC DAMMATAN, ARABIC FATHATAN, HEBREW POINT JUDEO-SPANISH VARIKA, ARABIC FATHATAN, LATIN SMALL LETTER B
+0061 064B 064C 064B FB1E 0062;0061 FB1E 064B 064B 064C 0062;0061 FB1E 064B 064B 064C 0062;0061 FB1E 064B 064B 064C 0062;0061 FB1E 064B 064B 064C 0062; # (a◌ً◌ٌ◌ً◌ﬞb; a◌ﬞ◌ً◌ً◌ٌb; a◌ﬞ◌ً◌ً◌ٌb; a◌ﬞ◌ً◌ً◌ٌb; a◌ﬞ◌ً◌ً◌ٌb; ) LATIN SMALL LETTER A, ARABIC FATHATAN, ARABIC DAMMATAN, ARABIC FATHATAN, HEBREW POINT JUDEO-SPANISH VARIKA, LATIN SMALL LETTER B
+0061 064D 064C 064B 064C 0062;0061 064B 064C 064C 064D 0062;0061 064B 064C 064C 064D 0062;0061 064B 064C 064C 064D 0062;0061 064B 064C 064C 064D 0062; # (aâ—ŒÙ◌ٌ◌ً◌ٌb; a◌ً◌ٌ◌ٌ◌Ùb; a◌ً◌ٌ◌ٌ◌Ùb; a◌ً◌ٌ◌ٌ◌Ùb; a◌ً◌ٌ◌ٌ◌Ùb; ) LATIN SMALL LETTER A, ARABIC KASRATAN, ARABIC DAMMATAN, ARABIC FATHATAN, ARABIC DAMMATAN, LATIN SMALL LETTER B
+0061 064C 064D 064C 064B 0062;0061 064B 064C 064C 064D 0062;0061 064B 064C 064C 064D 0062;0061 064B 064C 064C 064D 0062;0061 064B 064C 064C 064D 0062; # (a◌ٌ◌Ù◌ٌ◌ًb; a◌ً◌ٌ◌ٌ◌Ùb; a◌ً◌ٌ◌ٌ◌Ùb; a◌ً◌ٌ◌ٌ◌Ùb; a◌ً◌ٌ◌ٌ◌Ùb; ) LATIN SMALL LETTER A, ARABIC DAMMATAN, ARABIC KASRATAN, ARABIC DAMMATAN, ARABIC FATHATAN, LATIN SMALL LETTER B
+0061 0618 064D 064C 064D 0062;0061 064C 064D 064D 0618 0062;0061 064C 064D 064D 0618 0062;0061 064C 064D 064D 0618 0062;0061 064C 064D 064D 0618 0062; # (a◌ؘ◌Ù◌ٌ◌Ùb; a◌ٌ◌Ùâ—ŒÙ◌ؘb; a◌ٌ◌Ùâ—ŒÙ◌ؘb; a◌ٌ◌Ùâ—ŒÙ◌ؘb; a◌ٌ◌Ùâ—ŒÙ◌ؘb; ) LATIN SMALL LETTER A, ARABIC SMALL FATHA, ARABIC KASRATAN, ARABIC DAMMATAN, ARABIC KASRATAN, LATIN SMALL LETTER B
+0061 064D 0618 064D 064C 0062;0061 064C 064D 064D 0618 0062;0061 064C 064D 064D 0618 0062;0061 064C 064D 064D 0618 0062;0061 064C 064D 064D 0618 0062; # (aâ—ŒÙ◌ؘ◌Ù◌ٌb; a◌ٌ◌Ùâ—ŒÙ◌ؘb; a◌ٌ◌Ùâ—ŒÙ◌ؘb; a◌ٌ◌Ùâ—ŒÙ◌ؘb; a◌ٌ◌Ùâ—ŒÙ◌ؘb; ) LATIN SMALL LETTER A, ARABIC KASRATAN, ARABIC SMALL FATHA, ARABIC KASRATAN, ARABIC DAMMATAN, LATIN SMALL LETTER B
+0061 0619 0618 064D 064E 0062;0061 064D 0618 064E 0619 0062;0061 064D 0618 064E 0619 0062;0061 064D 0618 064E 0619 0062;0061 064D 0618 064E 0619 0062; # (a◌ؙ◌ؘ◌Ù◌َb; aâ—ŒÙ◌ؘ◌َ◌ؙb; aâ—ŒÙ◌ؘ◌َ◌ؙb; aâ—ŒÙ◌ؘ◌َ◌ؙb; aâ—ŒÙ◌ؘ◌َ◌ؙb; ) LATIN SMALL LETTER A, ARABIC SMALL DAMMA, ARABIC SMALL FATHA, ARABIC KASRATAN, ARABIC FATHA, LATIN SMALL LETTER B
+0061 064E 0619 0618 064D 0062;0061 064D 064E 0618 0619 0062;0061 064D 064E 0618 0619 0062;0061 064D 064E 0618 0619 0062;0061 064D 064E 0618 0619 0062; # (a◌َ◌ؙ◌ؘ◌Ùb; aâ—ŒÙ◌َ◌ؘ◌ؙb; aâ—ŒÙ◌َ◌ؘ◌ؙb; aâ—ŒÙ◌َ◌ؘ◌ؙb; aâ—ŒÙ◌َ◌ؘ◌ؙb; ) LATIN SMALL LETTER A, ARABIC FATHA, ARABIC SMALL DAMMA, ARABIC SMALL FATHA, ARABIC KASRATAN, LATIN SMALL LETTER B
+0061 061A 0619 0618 064F 0062;0061 0618 0619 064F 061A 0062;0061 0618 0619 064F 061A 0062;0061 0618 0619 064F 061A 0062;0061 0618 0619 064F 061A 0062; # (a◌ؚ◌ؙ◌ؘ◌Ùb; a◌ؘ◌ؙ◌Ù◌ؚb; a◌ؘ◌ؙ◌Ù◌ؚb; a◌ؘ◌ؙ◌Ù◌ؚb; a◌ؘ◌ؙ◌Ù◌ؚb; ) LATIN SMALL LETTER A, ARABIC SMALL KASRA, ARABIC SMALL DAMMA, ARABIC SMALL FATHA, ARABIC DAMMA, LATIN SMALL LETTER B
+0061 064F 061A 0619 0618 0062;0061 0618 064F 0619 061A 0062;0061 0618 064F 0619 061A 0062;0061 0618 064F 0619 061A 0062;0061 0618 064F 0619 061A 0062; # (aâ—ŒÙ◌ؚ◌ؙ◌ؘb; a◌ؘ◌Ù◌ؙ◌ؚb; a◌ؘ◌Ù◌ؙ◌ؚb; a◌ؘ◌Ù◌ؙ◌ؚb; a◌ؘ◌Ù◌ؙ◌ؚb; ) LATIN SMALL LETTER A, ARABIC DAMMA, ARABIC SMALL KASRA, ARABIC SMALL DAMMA, ARABIC SMALL FATHA, LATIN SMALL LETTER B
+0061 0651 061A 0619 0650 0062;0061 0619 061A 0650 0651 0062;0061 0619 061A 0650 0651 0062;0061 0619 061A 0650 0651 0062;0061 0619 061A 0650 0651 0062; # (a◌ّ◌ؚ◌ؙ◌Ùb; a◌ؙ◌ؚ◌Ù◌ّb; a◌ؙ◌ؚ◌Ù◌ّb; a◌ؙ◌ؚ◌Ù◌ّb; a◌ؙ◌ؚ◌Ù◌ّb; ) LATIN SMALL LETTER A, ARABIC SHADDA, ARABIC SMALL KASRA, ARABIC SMALL DAMMA, ARABIC KASRA, LATIN SMALL LETTER B
+0061 0650 0651 061A 0619 0062;0061 0619 0650 061A 0651 0062;0061 0619 0650 061A 0651 0062;0061 0619 0650 061A 0651 0062;0061 0619 0650 061A 0651 0062; # (aâ—ŒÙ◌ّ◌ؚ◌ؙb; a◌ؙ◌Ù◌ؚ◌ّb; a◌ؙ◌Ù◌ؚ◌ّb; a◌ؙ◌Ù◌ؚ◌ّb; a◌ؙ◌Ù◌ؚ◌ّb; ) LATIN SMALL LETTER A, ARABIC KASRA, ARABIC SHADDA, ARABIC SMALL KASRA, ARABIC SMALL DAMMA, LATIN SMALL LETTER B
+0061 0652 0651 061A 0651 0062;0061 061A 0651 0651 0652 0062;0061 061A 0651 0651 0652 0062;0061 061A 0651 0651 0652 0062;0061 061A 0651 0651 0652 0062; # (a◌ْ◌ّ◌ؚ◌ّb; a◌ؚ◌ّ◌ّ◌ْb; a◌ؚ◌ّ◌ّ◌ْb; a◌ؚ◌ّ◌ّ◌ْb; a◌ؚ◌ّ◌ّ◌ْb; ) LATIN SMALL LETTER A, ARABIC SUKUN, ARABIC SHADDA, ARABIC SMALL KASRA, ARABIC SHADDA, LATIN SMALL LETTER B
+0061 0651 0652 0651 061A 0062;0061 061A 0651 0651 0652 0062;0061 061A 0651 0651 0652 0062;0061 061A 0651 0651 0652 0062;0061 061A 0651 0651 0652 0062; # (a◌ّ◌ْ◌ّ◌ؚb; a◌ؚ◌ّ◌ّ◌ْb; a◌ؚ◌ّ◌ّ◌ْb; a◌ؚ◌ّ◌ّ◌ْb; a◌ؚ◌ّ◌ّ◌ْb; ) LATIN SMALL LETTER A, ARABIC SHADDA, ARABIC SUKUN, ARABIC SHADDA, ARABIC SMALL KASRA, LATIN SMALL LETTER B
+0061 0670 0652 0651 0652 0062;0061 0651 0652 0652 0670 0062;0061 0651 0652 0652 0670 0062;0061 0651 0652 0652 0670 0062;0061 0651 0652 0652 0670 0062; # (a◌ٰ◌ْ◌ّ◌ْb; a◌ّ◌ْ◌ْ◌ٰb; a◌ّ◌ْ◌ْ◌ٰb; a◌ّ◌ْ◌ْ◌ٰb; a◌ّ◌ْ◌ْ◌ٰb; ) LATIN SMALL LETTER A, ARABIC LETTER SUPERSCRIPT ALEF, ARABIC SUKUN, ARABIC SHADDA, ARABIC SUKUN, LATIN SMALL LETTER B
+0061 0652 0670 0652 0651 0062;0061 0651 0652 0652 0670 0062;0061 0651 0652 0652 0670 0062;0061 0651 0652 0652 0670 0062;0061 0651 0652 0652 0670 0062; # (a◌ْ◌ٰ◌ْ◌ّb; a◌ّ◌ْ◌ْ◌ٰb; a◌ّ◌ْ◌ْ◌ٰb; a◌ّ◌ْ◌ْ◌ٰb; a◌ّ◌ْ◌ْ◌ٰb; ) LATIN SMALL LETTER A, ARABIC SUKUN, ARABIC LETTER SUPERSCRIPT ALEF, ARABIC SUKUN, ARABIC SHADDA, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0653 0062;00E0 05AE 0653 0315 0062;0061 05AE 0300 0653 0315 0062;00E0 05AE 0653 0315 0062;0061 05AE 0300 0653 0315 0062; # (a◌̕◌̀◌֮◌ٓb; à◌֮◌ٓ◌̕b; a◌֮◌̀◌ٓ◌̕b; à◌֮◌ٓ◌̕b; a◌֮◌̀◌ٓ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC MADDAH ABOVE, LATIN SMALL LETTER B
+0061 0653 0315 0300 05AE 0062;0061 05AE 0653 0300 0315 0062;0061 05AE 0653 0300 0315 0062;0061 05AE 0653 0300 0315 0062;0061 05AE 0653 0300 0315 0062; # (a◌ٓ◌̕◌̀◌֮b; a◌֮◌ٓ◌̀◌̕b; a◌֮◌ٓ◌̀◌̕b; a◌֮◌ٓ◌̀◌̕b; a◌֮◌ٓ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC MADDAH ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0654 0062;00E0 05AE 0654 0315 0062;0061 05AE 0300 0654 0315 0062;00E0 05AE 0654 0315 0062;0061 05AE 0300 0654 0315 0062; # (a◌̕◌̀◌֮◌ٔb; à◌֮◌ٔ◌̕b; a◌֮◌̀◌ٔ◌̕b; à◌֮◌ٔ◌̕b; a◌֮◌̀◌ٔ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC HAMZA ABOVE, LATIN SMALL LETTER B
+0061 0654 0315 0300 05AE 0062;0061 05AE 0654 0300 0315 0062;0061 05AE 0654 0300 0315 0062;0061 05AE 0654 0300 0315 0062;0061 05AE 0654 0300 0315 0062; # (a◌ٔ◌̕◌̀◌֮b; a◌֮◌ٔ◌̀◌̕b; a◌֮◌ٔ◌̀◌̕b; a◌֮◌ٔ◌̀◌̕b; a◌֮◌ٔ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC HAMZA ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0655 0062;0061 1DFA 0316 0655 059A 0062;0061 1DFA 0316 0655 059A 0062;0061 1DFA 0316 0655 059A 0062;0061 1DFA 0316 0655 059A 0062; # (a◌֚◌̖◌᷺◌ٕb; a◌᷺◌̖◌ٕ◌֚b; a◌᷺◌̖◌ٕ◌֚b; a◌᷺◌̖◌ٕ◌֚b; a◌᷺◌̖◌ٕ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC HAMZA BELOW, LATIN SMALL LETTER B
+0061 0655 059A 0316 1DFA 0062;0061 1DFA 0655 0316 059A 0062;0061 1DFA 0655 0316 059A 0062;0061 1DFA 0655 0316 059A 0062;0061 1DFA 0655 0316 059A 0062; # (a◌ٕ◌֚◌̖◌᷺b; a◌᷺◌ٕ◌̖◌֚b; a◌᷺◌ٕ◌̖◌֚b; a◌᷺◌ٕ◌̖◌֚b; a◌᷺◌ٕ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC HAMZA BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0656 0062;0061 1DFA 0316 0656 059A 0062;0061 1DFA 0316 0656 059A 0062;0061 1DFA 0316 0656 059A 0062;0061 1DFA 0316 0656 059A 0062; # (a◌֚◌̖◌᷺◌ٖb; a◌᷺◌̖◌ٖ◌֚b; a◌᷺◌̖◌ٖ◌֚b; a◌᷺◌̖◌ٖ◌֚b; a◌᷺◌̖◌ٖ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SUBSCRIPT ALEF, LATIN SMALL LETTER B
+0061 0656 059A 0316 1DFA 0062;0061 1DFA 0656 0316 059A 0062;0061 1DFA 0656 0316 059A 0062;0061 1DFA 0656 0316 059A 0062;0061 1DFA 0656 0316 059A 0062; # (a◌ٖ◌֚◌̖◌᷺b; a◌᷺◌ٖ◌̖◌֚b; a◌᷺◌ٖ◌̖◌֚b; a◌᷺◌ٖ◌̖◌֚b; a◌᷺◌ٖ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC SUBSCRIPT ALEF, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0657 0062;00E0 05AE 0657 0315 0062;0061 05AE 0300 0657 0315 0062;00E0 05AE 0657 0315 0062;0061 05AE 0300 0657 0315 0062; # (a◌̕◌̀◌֮◌ٗb; à◌֮◌ٗ◌̕b; a◌֮◌̀◌ٗ◌̕b; à◌֮◌ٗ◌̕b; a◌֮◌̀◌ٗ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC INVERTED DAMMA, LATIN SMALL LETTER B
+0061 0657 0315 0300 05AE 0062;0061 05AE 0657 0300 0315 0062;0061 05AE 0657 0300 0315 0062;0061 05AE 0657 0300 0315 0062;0061 05AE 0657 0300 0315 0062; # (a◌ٗ◌̕◌̀◌֮b; a◌֮◌ٗ◌̀◌̕b; a◌֮◌ٗ◌̀◌̕b; a◌֮◌ٗ◌̀◌̕b; a◌֮◌ٗ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC INVERTED DAMMA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0658 0062;00E0 05AE 0658 0315 0062;0061 05AE 0300 0658 0315 0062;00E0 05AE 0658 0315 0062;0061 05AE 0300 0658 0315 0062; # (a◌̕◌̀◌֮◌٘b; à◌֮◌٘◌̕b; a◌֮◌̀◌٘◌̕b; à◌֮◌٘◌̕b; a◌֮◌̀◌٘◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC MARK NOON GHUNNA, LATIN SMALL LETTER B
+0061 0658 0315 0300 05AE 0062;0061 05AE 0658 0300 0315 0062;0061 05AE 0658 0300 0315 0062;0061 05AE 0658 0300 0315 0062;0061 05AE 0658 0300 0315 0062; # (a◌٘◌̕◌̀◌֮b; a◌֮◌٘◌̀◌̕b; a◌֮◌٘◌̀◌̕b; a◌֮◌٘◌̀◌̕b; a◌֮◌٘◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC MARK NOON GHUNNA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0659 0062;00E0 05AE 0659 0315 0062;0061 05AE 0300 0659 0315 0062;00E0 05AE 0659 0315 0062;0061 05AE 0300 0659 0315 0062; # (a◌̕◌̀◌֮◌ٙb; à◌֮◌ٙ◌̕b; a◌֮◌̀◌ٙ◌̕b; à◌֮◌ٙ◌̕b; a◌֮◌̀◌ٙ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC ZWARAKAY, LATIN SMALL LETTER B
+0061 0659 0315 0300 05AE 0062;0061 05AE 0659 0300 0315 0062;0061 05AE 0659 0300 0315 0062;0061 05AE 0659 0300 0315 0062;0061 05AE 0659 0300 0315 0062; # (a◌ٙ◌̕◌̀◌֮b; a◌֮◌ٙ◌̀◌̕b; a◌֮◌ٙ◌̀◌̕b; a◌֮◌ٙ◌̀◌̕b; a◌֮◌ٙ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC ZWARAKAY, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 065A 0062;00E0 05AE 065A 0315 0062;0061 05AE 0300 065A 0315 0062;00E0 05AE 065A 0315 0062;0061 05AE 0300 065A 0315 0062; # (a◌̕◌̀◌֮◌ٚb; à◌֮◌ٚ◌̕b; a◌֮◌̀◌ٚ◌̕b; à◌֮◌ٚ◌̕b; a◌֮◌̀◌ٚ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC VOWEL SIGN SMALL V ABOVE, LATIN SMALL LETTER B
+0061 065A 0315 0300 05AE 0062;0061 05AE 065A 0300 0315 0062;0061 05AE 065A 0300 0315 0062;0061 05AE 065A 0300 0315 0062;0061 05AE 065A 0300 0315 0062; # (a◌ٚ◌̕◌̀◌֮b; a◌֮◌ٚ◌̀◌̕b; a◌֮◌ٚ◌̀◌̕b; a◌֮◌ٚ◌̀◌̕b; a◌֮◌ٚ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC VOWEL SIGN SMALL V ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 065B 0062;00E0 05AE 065B 0315 0062;0061 05AE 0300 065B 0315 0062;00E0 05AE 065B 0315 0062;0061 05AE 0300 065B 0315 0062; # (a◌̕◌̀◌֮◌ٛb; à◌֮◌ٛ◌̕b; a◌֮◌̀◌ٛ◌̕b; à◌֮◌ٛ◌̕b; a◌֮◌̀◌ٛ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC VOWEL SIGN INVERTED SMALL V ABOVE, LATIN SMALL LETTER B
+0061 065B 0315 0300 05AE 0062;0061 05AE 065B 0300 0315 0062;0061 05AE 065B 0300 0315 0062;0061 05AE 065B 0300 0315 0062;0061 05AE 065B 0300 0315 0062; # (a◌ٛ◌̕◌̀◌֮b; a◌֮◌ٛ◌̀◌̕b; a◌֮◌ٛ◌̀◌̕b; a◌֮◌ٛ◌̀◌̕b; a◌֮◌ٛ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC VOWEL SIGN INVERTED SMALL V ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 065C 0062;0061 1DFA 0316 065C 059A 0062;0061 1DFA 0316 065C 059A 0062;0061 1DFA 0316 065C 059A 0062;0061 1DFA 0316 065C 059A 0062; # (a◌֚◌̖◌᷺◌ٜb; a◌᷺◌̖◌ٜ◌֚b; a◌᷺◌̖◌ٜ◌֚b; a◌᷺◌̖◌ٜ◌֚b; a◌᷺◌̖◌ٜ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC VOWEL SIGN DOT BELOW, LATIN SMALL LETTER B
+0061 065C 059A 0316 1DFA 0062;0061 1DFA 065C 0316 059A 0062;0061 1DFA 065C 0316 059A 0062;0061 1DFA 065C 0316 059A 0062;0061 1DFA 065C 0316 059A 0062; # (a◌ٜ◌֚◌̖◌᷺b; a◌᷺◌ٜ◌̖◌֚b; a◌᷺◌ٜ◌̖◌֚b; a◌᷺◌ٜ◌̖◌֚b; a◌᷺◌ٜ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC VOWEL SIGN DOT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 065D 0062;00E0 05AE 065D 0315 0062;0061 05AE 0300 065D 0315 0062;00E0 05AE 065D 0315 0062;0061 05AE 0300 065D 0315 0062; # (a◌̕◌̀◌֮◌Ùb; à◌֮◌Ù◌̕b; a◌֮◌̀◌Ù◌̕b; à◌֮◌Ù◌̕b; a◌֮◌̀◌Ù◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC REVERSED DAMMA, LATIN SMALL LETTER B
+0061 065D 0315 0300 05AE 0062;0061 05AE 065D 0300 0315 0062;0061 05AE 065D 0300 0315 0062;0061 05AE 065D 0300 0315 0062;0061 05AE 065D 0300 0315 0062; # (aâ—ŒÙ◌̕◌̀◌֮b; a◌֮◌Ù◌̀◌̕b; a◌֮◌Ù◌̀◌̕b; a◌֮◌Ù◌̀◌̕b; a◌֮◌Ù◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC REVERSED DAMMA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 065E 0062;00E0 05AE 065E 0315 0062;0061 05AE 0300 065E 0315 0062;00E0 05AE 065E 0315 0062;0061 05AE 0300 065E 0315 0062; # (a◌̕◌̀◌֮◌ٞb; à◌֮◌ٞ◌̕b; a◌֮◌̀◌ٞ◌̕b; à◌֮◌ٞ◌̕b; a◌֮◌̀◌ٞ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC FATHA WITH TWO DOTS, LATIN SMALL LETTER B
+0061 065E 0315 0300 05AE 0062;0061 05AE 065E 0300 0315 0062;0061 05AE 065E 0300 0315 0062;0061 05AE 065E 0300 0315 0062;0061 05AE 065E 0300 0315 0062; # (a◌ٞ◌̕◌̀◌֮b; a◌֮◌ٞ◌̀◌̕b; a◌֮◌ٞ◌̀◌̕b; a◌֮◌ٞ◌̀◌̕b; a◌֮◌ٞ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC FATHA WITH TWO DOTS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 065F 0062;0061 1DFA 0316 065F 059A 0062;0061 1DFA 0316 065F 059A 0062;0061 1DFA 0316 065F 059A 0062;0061 1DFA 0316 065F 059A 0062; # (a◌֚◌̖◌᷺◌ٟb; a◌᷺◌̖◌ٟ◌֚b; a◌᷺◌̖◌ٟ◌֚b; a◌᷺◌̖◌ٟ◌֚b; a◌᷺◌̖◌ٟ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC WAVY HAMZA BELOW, LATIN SMALL LETTER B
+0061 065F 059A 0316 1DFA 0062;0061 1DFA 065F 0316 059A 0062;0061 1DFA 065F 0316 059A 0062;0061 1DFA 065F 0316 059A 0062;0061 1DFA 065F 0316 059A 0062; # (a◌ٟ◌֚◌̖◌᷺b; a◌᷺◌ٟ◌̖◌֚b; a◌᷺◌ٟ◌̖◌֚b; a◌᷺◌ٟ◌̖◌֚b; a◌᷺◌ٟ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC WAVY HAMZA BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0711 0670 0652 0670 0062;0061 0652 0670 0670 0711 0062;0061 0652 0670 0670 0711 0062;0061 0652 0670 0670 0711 0062;0061 0652 0670 0670 0711 0062; # (a◌ܑ◌ٰ◌ْ◌ٰb; a◌ْ◌ٰ◌ٰ◌ܑb; a◌ْ◌ٰ◌ٰ◌ܑb; a◌ْ◌ٰ◌ٰ◌ܑb; a◌ْ◌ٰ◌ٰ◌ܑb; ) LATIN SMALL LETTER A, SYRIAC LETTER SUPERSCRIPT ALAPH, ARABIC LETTER SUPERSCRIPT ALEF, ARABIC SUKUN, ARABIC LETTER SUPERSCRIPT ALEF, LATIN SMALL LETTER B
+0061 0670 0711 0670 0652 0062;0061 0652 0670 0670 0711 0062;0061 0652 0670 0670 0711 0062;0061 0652 0670 0670 0711 0062;0061 0652 0670 0670 0711 0062; # (a◌ٰ◌ܑ◌ٰ◌ْb; a◌ْ◌ٰ◌ٰ◌ܑb; a◌ْ◌ٰ◌ٰ◌ܑb; a◌ْ◌ٰ◌ٰ◌ܑb; a◌ْ◌ٰ◌ٰ◌ܑb; ) LATIN SMALL LETTER A, ARABIC LETTER SUPERSCRIPT ALEF, SYRIAC LETTER SUPERSCRIPT ALAPH, ARABIC LETTER SUPERSCRIPT ALEF, ARABIC SUKUN, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06D6 0062;00E0 05AE 06D6 0315 0062;0061 05AE 0300 06D6 0315 0062;00E0 05AE 06D6 0315 0062;0061 05AE 0300 06D6 0315 0062; # (a◌̕◌̀◌֮◌ۖb; à◌֮◌ۖ◌̕b; a◌֮◌̀◌ۖ◌̕b; à◌֮◌ۖ◌̕b; a◌֮◌̀◌ۖ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA, LATIN SMALL LETTER B
+0061 06D6 0315 0300 05AE 0062;0061 05AE 06D6 0300 0315 0062;0061 05AE 06D6 0300 0315 0062;0061 05AE 06D6 0300 0315 0062;0061 05AE 06D6 0300 0315 0062; # (a◌ۖ◌̕◌̀◌֮b; a◌֮◌ۖ◌̀◌̕b; a◌֮◌ۖ◌̀◌̕b; a◌֮◌ۖ◌̀◌̕b; a◌֮◌ۖ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06D7 0062;00E0 05AE 06D7 0315 0062;0061 05AE 0300 06D7 0315 0062;00E0 05AE 06D7 0315 0062;0061 05AE 0300 06D7 0315 0062; # (a◌̕◌̀◌֮◌ۗb; à◌֮◌ۗ◌̕b; a◌֮◌̀◌ۗ◌̕b; à◌֮◌ۗ◌̕b; a◌֮◌̀◌ۗ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH LIGATURE QAF WITH LAM WITH ALEF MAKSURA, LATIN SMALL LETTER B
+0061 06D7 0315 0300 05AE 0062;0061 05AE 06D7 0300 0315 0062;0061 05AE 06D7 0300 0315 0062;0061 05AE 06D7 0300 0315 0062;0061 05AE 06D7 0300 0315 0062; # (a◌ۗ◌̕◌̀◌֮b; a◌֮◌ۗ◌̀◌̕b; a◌֮◌ۗ◌̀◌̕b; a◌֮◌ۗ◌̀◌̕b; a◌֮◌ۗ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH LIGATURE QAF WITH LAM WITH ALEF MAKSURA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06D8 0062;00E0 05AE 06D8 0315 0062;0061 05AE 0300 06D8 0315 0062;00E0 05AE 06D8 0315 0062;0061 05AE 0300 06D8 0315 0062; # (a◌̕◌̀◌֮◌ۘb; à◌֮◌ۘ◌̕b; a◌֮◌̀◌ۘ◌̕b; à◌֮◌ۘ◌̕b; a◌֮◌̀◌ۘ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH MEEM INITIAL FORM, LATIN SMALL LETTER B
+0061 06D8 0315 0300 05AE 0062;0061 05AE 06D8 0300 0315 0062;0061 05AE 06D8 0300 0315 0062;0061 05AE 06D8 0300 0315 0062;0061 05AE 06D8 0300 0315 0062; # (a◌ۘ◌̕◌̀◌֮b; a◌֮◌ۘ◌̀◌̕b; a◌֮◌ۘ◌̀◌̕b; a◌֮◌ۘ◌̀◌̕b; a◌֮◌ۘ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH MEEM INITIAL FORM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06D9 0062;00E0 05AE 06D9 0315 0062;0061 05AE 0300 06D9 0315 0062;00E0 05AE 06D9 0315 0062;0061 05AE 0300 06D9 0315 0062; # (a◌̕◌̀◌֮◌ۙb; à◌֮◌ۙ◌̕b; a◌֮◌̀◌ۙ◌̕b; à◌֮◌ۙ◌̕b; a◌֮◌̀◌ۙ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH LAM ALEF, LATIN SMALL LETTER B
+0061 06D9 0315 0300 05AE 0062;0061 05AE 06D9 0300 0315 0062;0061 05AE 06D9 0300 0315 0062;0061 05AE 06D9 0300 0315 0062;0061 05AE 06D9 0300 0315 0062; # (a◌ۙ◌̕◌̀◌֮b; a◌֮◌ۙ◌̀◌̕b; a◌֮◌ۙ◌̀◌̕b; a◌֮◌ۙ◌̀◌̕b; a◌֮◌ۙ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH LAM ALEF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06DA 0062;00E0 05AE 06DA 0315 0062;0061 05AE 0300 06DA 0315 0062;00E0 05AE 06DA 0315 0062;0061 05AE 0300 06DA 0315 0062; # (a◌̕◌̀◌֮◌ۚb; à◌֮◌ۚ◌̕b; a◌֮◌̀◌ۚ◌̕b; à◌֮◌ۚ◌̕b; a◌֮◌̀◌ۚ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH JEEM, LATIN SMALL LETTER B
+0061 06DA 0315 0300 05AE 0062;0061 05AE 06DA 0300 0315 0062;0061 05AE 06DA 0300 0315 0062;0061 05AE 06DA 0300 0315 0062;0061 05AE 06DA 0300 0315 0062; # (a◌ۚ◌̕◌̀◌֮b; a◌֮◌ۚ◌̀◌̕b; a◌֮◌ۚ◌̀◌̕b; a◌֮◌ۚ◌̀◌̕b; a◌֮◌ۚ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH JEEM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06DB 0062;00E0 05AE 06DB 0315 0062;0061 05AE 0300 06DB 0315 0062;00E0 05AE 06DB 0315 0062;0061 05AE 0300 06DB 0315 0062; # (a◌̕◌̀◌֮◌ۛb; à◌֮◌ۛ◌̕b; a◌֮◌̀◌ۛ◌̕b; à◌֮◌ۛ◌̕b; a◌֮◌̀◌ۛ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH THREE DOTS, LATIN SMALL LETTER B
+0061 06DB 0315 0300 05AE 0062;0061 05AE 06DB 0300 0315 0062;0061 05AE 06DB 0300 0315 0062;0061 05AE 06DB 0300 0315 0062;0061 05AE 06DB 0300 0315 0062; # (a◌ۛ◌̕◌̀◌֮b; a◌֮◌ۛ◌̀◌̕b; a◌֮◌ۛ◌̀◌̕b; a◌֮◌ۛ◌̀◌̕b; a◌֮◌ۛ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH THREE DOTS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06DC 0062;00E0 05AE 06DC 0315 0062;0061 05AE 0300 06DC 0315 0062;00E0 05AE 06DC 0315 0062;0061 05AE 0300 06DC 0315 0062; # (a◌̕◌̀◌֮◌ۜb; à◌֮◌ۜ◌̕b; a◌֮◌̀◌ۜ◌̕b; à◌֮◌ۜ◌̕b; a◌֮◌̀◌ۜ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH SEEN, LATIN SMALL LETTER B
+0061 06DC 0315 0300 05AE 0062;0061 05AE 06DC 0300 0315 0062;0061 05AE 06DC 0300 0315 0062;0061 05AE 06DC 0300 0315 0062;0061 05AE 06DC 0300 0315 0062; # (a◌ۜ◌̕◌̀◌֮b; a◌֮◌ۜ◌̀◌̕b; a◌֮◌ۜ◌̀◌̕b; a◌֮◌ۜ◌̀◌̕b; a◌֮◌ۜ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH SEEN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06DF 0062;00E0 05AE 06DF 0315 0062;0061 05AE 0300 06DF 0315 0062;00E0 05AE 06DF 0315 0062;0061 05AE 0300 06DF 0315 0062; # (a◌̕◌̀◌֮◌۟b; à◌֮◌۟◌̕b; a◌֮◌̀◌۟◌̕b; à◌֮◌۟◌̕b; a◌֮◌̀◌۟◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH ROUNDED ZERO, LATIN SMALL LETTER B
+0061 06DF 0315 0300 05AE 0062;0061 05AE 06DF 0300 0315 0062;0061 05AE 06DF 0300 0315 0062;0061 05AE 06DF 0300 0315 0062;0061 05AE 06DF 0300 0315 0062; # (a◌۟◌̕◌̀◌֮b; a◌֮◌۟◌̀◌̕b; a◌֮◌۟◌̀◌̕b; a◌֮◌۟◌̀◌̕b; a◌֮◌۟◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH ROUNDED ZERO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06E0 0062;00E0 05AE 06E0 0315 0062;0061 05AE 0300 06E0 0315 0062;00E0 05AE 06E0 0315 0062;0061 05AE 0300 06E0 0315 0062; # (a◌̕◌̀◌֮◌۠b; à◌֮◌۠◌̕b; a◌֮◌̀◌۠◌̕b; à◌֮◌۠◌̕b; a◌֮◌̀◌۠◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH UPRIGHT RECTANGULAR ZERO, LATIN SMALL LETTER B
+0061 06E0 0315 0300 05AE 0062;0061 05AE 06E0 0300 0315 0062;0061 05AE 06E0 0300 0315 0062;0061 05AE 06E0 0300 0315 0062;0061 05AE 06E0 0300 0315 0062; # (a◌۠◌̕◌̀◌֮b; a◌֮◌۠◌̀◌̕b; a◌֮◌۠◌̀◌̕b; a◌֮◌۠◌̀◌̕b; a◌֮◌۠◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH UPRIGHT RECTANGULAR ZERO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06E1 0062;00E0 05AE 06E1 0315 0062;0061 05AE 0300 06E1 0315 0062;00E0 05AE 06E1 0315 0062;0061 05AE 0300 06E1 0315 0062; # (a◌̕◌̀◌֮◌ۡb; à◌֮◌ۡ◌̕b; a◌֮◌̀◌ۡ◌̕b; à◌֮◌ۡ◌̕b; a◌֮◌̀◌ۡ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH DOTLESS HEAD OF KHAH, LATIN SMALL LETTER B
+0061 06E1 0315 0300 05AE 0062;0061 05AE 06E1 0300 0315 0062;0061 05AE 06E1 0300 0315 0062;0061 05AE 06E1 0300 0315 0062;0061 05AE 06E1 0300 0315 0062; # (a◌ۡ◌̕◌̀◌֮b; a◌֮◌ۡ◌̀◌̕b; a◌֮◌ۡ◌̀◌̕b; a◌֮◌ۡ◌̀◌̕b; a◌֮◌ۡ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH DOTLESS HEAD OF KHAH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06E2 0062;00E0 05AE 06E2 0315 0062;0061 05AE 0300 06E2 0315 0062;00E0 05AE 06E2 0315 0062;0061 05AE 0300 06E2 0315 0062; # (a◌̕◌̀◌֮◌ۢb; à◌֮◌ۢ◌̕b; a◌֮◌̀◌ۢ◌̕b; à◌֮◌ۢ◌̕b; a◌֮◌̀◌ۢ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH MEEM ISOLATED FORM, LATIN SMALL LETTER B
+0061 06E2 0315 0300 05AE 0062;0061 05AE 06E2 0300 0315 0062;0061 05AE 06E2 0300 0315 0062;0061 05AE 06E2 0300 0315 0062;0061 05AE 06E2 0300 0315 0062; # (a◌ۢ◌̕◌̀◌֮b; a◌֮◌ۢ◌̀◌̕b; a◌֮◌ۢ◌̀◌̕b; a◌֮◌ۢ◌̀◌̕b; a◌֮◌ۢ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH MEEM ISOLATED FORM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 06E3 0062;0061 1DFA 0316 06E3 059A 0062;0061 1DFA 0316 06E3 059A 0062;0061 1DFA 0316 06E3 059A 0062;0061 1DFA 0316 06E3 059A 0062; # (a◌֚◌̖◌᷺◌ۣb; a◌᷺◌̖◌ۣ◌֚b; a◌᷺◌̖◌ۣ◌֚b; a◌᷺◌̖◌ۣ◌֚b; a◌᷺◌̖◌ۣ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SMALL LOW SEEN, LATIN SMALL LETTER B
+0061 06E3 059A 0316 1DFA 0062;0061 1DFA 06E3 0316 059A 0062;0061 1DFA 06E3 0316 059A 0062;0061 1DFA 06E3 0316 059A 0062;0061 1DFA 06E3 0316 059A 0062; # (a◌ۣ◌֚◌̖◌᷺b; a◌᷺◌ۣ◌̖◌֚b; a◌᷺◌ۣ◌̖◌֚b; a◌᷺◌ۣ◌̖◌֚b; a◌᷺◌ۣ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC SMALL LOW SEEN, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06E4 0062;00E0 05AE 06E4 0315 0062;0061 05AE 0300 06E4 0315 0062;00E0 05AE 06E4 0315 0062;0061 05AE 0300 06E4 0315 0062; # (a◌̕◌̀◌֮◌ۤb; à◌֮◌ۤ◌̕b; a◌֮◌̀◌ۤ◌̕b; à◌֮◌ۤ◌̕b; a◌֮◌̀◌ۤ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH MADDA, LATIN SMALL LETTER B
+0061 06E4 0315 0300 05AE 0062;0061 05AE 06E4 0300 0315 0062;0061 05AE 06E4 0300 0315 0062;0061 05AE 06E4 0300 0315 0062;0061 05AE 06E4 0300 0315 0062; # (a◌ۤ◌̕◌̀◌֮b; a◌֮◌ۤ◌̀◌̕b; a◌֮◌ۤ◌̀◌̕b; a◌֮◌ۤ◌̀◌̕b; a◌֮◌ۤ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH MADDA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06E7 0062;00E0 05AE 06E7 0315 0062;0061 05AE 0300 06E7 0315 0062;00E0 05AE 06E7 0315 0062;0061 05AE 0300 06E7 0315 0062; # (a◌̕◌̀◌֮◌ۧb; à◌֮◌ۧ◌̕b; a◌֮◌̀◌ۧ◌̕b; à◌֮◌ۧ◌̕b; a◌֮◌̀◌ۧ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH YEH, LATIN SMALL LETTER B
+0061 06E7 0315 0300 05AE 0062;0061 05AE 06E7 0300 0315 0062;0061 05AE 06E7 0300 0315 0062;0061 05AE 06E7 0300 0315 0062;0061 05AE 06E7 0300 0315 0062; # (a◌ۧ◌̕◌̀◌֮b; a◌֮◌ۧ◌̀◌̕b; a◌֮◌ۧ◌̀◌̕b; a◌֮◌ۧ◌̀◌̕b; a◌֮◌ۧ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH YEH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06E8 0062;00E0 05AE 06E8 0315 0062;0061 05AE 0300 06E8 0315 0062;00E0 05AE 06E8 0315 0062;0061 05AE 0300 06E8 0315 0062; # (a◌̕◌̀◌֮◌ۨb; à◌֮◌ۨ◌̕b; a◌֮◌̀◌ۨ◌̕b; à◌֮◌ۨ◌̕b; a◌֮◌̀◌ۨ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH NOON, LATIN SMALL LETTER B
+0061 06E8 0315 0300 05AE 0062;0061 05AE 06E8 0300 0315 0062;0061 05AE 06E8 0300 0315 0062;0061 05AE 06E8 0300 0315 0062;0061 05AE 06E8 0300 0315 0062; # (a◌ۨ◌̕◌̀◌֮b; a◌֮◌ۨ◌̀◌̕b; a◌֮◌ۨ◌̀◌̕b; a◌֮◌ۨ◌̀◌̕b; a◌֮◌ۨ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH NOON, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 06EA 0062;0061 1DFA 0316 06EA 059A 0062;0061 1DFA 0316 06EA 059A 0062;0061 1DFA 0316 06EA 059A 0062;0061 1DFA 0316 06EA 059A 0062; # (a◌֚◌̖◌᷺◌۪b; a◌᷺◌̖◌۪◌֚b; a◌᷺◌̖◌۪◌֚b; a◌᷺◌̖◌۪◌֚b; a◌᷺◌̖◌۪◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC EMPTY CENTRE LOW STOP, LATIN SMALL LETTER B
+0061 06EA 059A 0316 1DFA 0062;0061 1DFA 06EA 0316 059A 0062;0061 1DFA 06EA 0316 059A 0062;0061 1DFA 06EA 0316 059A 0062;0061 1DFA 06EA 0316 059A 0062; # (a◌۪◌֚◌̖◌᷺b; a◌᷺◌۪◌̖◌֚b; a◌᷺◌۪◌̖◌֚b; a◌᷺◌۪◌̖◌֚b; a◌᷺◌۪◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC EMPTY CENTRE LOW STOP, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06EB 0062;00E0 05AE 06EB 0315 0062;0061 05AE 0300 06EB 0315 0062;00E0 05AE 06EB 0315 0062;0061 05AE 0300 06EB 0315 0062; # (a◌̕◌̀◌֮◌۫b; à◌֮◌۫◌̕b; a◌֮◌̀◌۫◌̕b; à◌֮◌۫◌̕b; a◌֮◌̀◌۫◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC EMPTY CENTRE HIGH STOP, LATIN SMALL LETTER B
+0061 06EB 0315 0300 05AE 0062;0061 05AE 06EB 0300 0315 0062;0061 05AE 06EB 0300 0315 0062;0061 05AE 06EB 0300 0315 0062;0061 05AE 06EB 0300 0315 0062; # (a◌۫◌̕◌̀◌֮b; a◌֮◌۫◌̀◌̕b; a◌֮◌۫◌̀◌̕b; a◌֮◌۫◌̀◌̕b; a◌֮◌۫◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC EMPTY CENTRE HIGH STOP, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 06EC 0062;00E0 05AE 06EC 0315 0062;0061 05AE 0300 06EC 0315 0062;00E0 05AE 06EC 0315 0062;0061 05AE 0300 06EC 0315 0062; # (a◌̕◌̀◌֮◌۬b; à◌֮◌۬◌̕b; a◌֮◌̀◌۬◌̕b; à◌֮◌۬◌̕b; a◌֮◌̀◌۬◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC ROUNDED HIGH STOP WITH FILLED CENTRE, LATIN SMALL LETTER B
+0061 06EC 0315 0300 05AE 0062;0061 05AE 06EC 0300 0315 0062;0061 05AE 06EC 0300 0315 0062;0061 05AE 06EC 0300 0315 0062;0061 05AE 06EC 0300 0315 0062; # (a◌۬◌̕◌̀◌֮b; a◌֮◌۬◌̀◌̕b; a◌֮◌۬◌̀◌̕b; a◌֮◌۬◌̀◌̕b; a◌֮◌۬◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC ROUNDED HIGH STOP WITH FILLED CENTRE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 06ED 0062;0061 1DFA 0316 06ED 059A 0062;0061 1DFA 0316 06ED 059A 0062;0061 1DFA 0316 06ED 059A 0062;0061 1DFA 0316 06ED 059A 0062; # (a◌֚◌̖◌᷺◌ۭb; a◌᷺◌̖◌ۭ◌֚b; a◌᷺◌̖◌ۭ◌֚b; a◌᷺◌̖◌ۭ◌֚b; a◌᷺◌̖◌ۭ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SMALL LOW MEEM, LATIN SMALL LETTER B
+0061 06ED 059A 0316 1DFA 0062;0061 1DFA 06ED 0316 059A 0062;0061 1DFA 06ED 0316 059A 0062;0061 1DFA 06ED 0316 059A 0062;0061 1DFA 06ED 0316 059A 0062; # (a◌ۭ◌֚◌̖◌᷺b; a◌᷺◌ۭ◌̖◌֚b; a◌᷺◌ۭ◌̖◌֚b; a◌᷺◌ۭ◌̖◌֚b; a◌᷺◌ۭ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC SMALL LOW MEEM, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0C55 0711 0670 0711 0062;0061 0670 0711 0711 0C55 0062;0061 0670 0711 0711 0C55 0062;0061 0670 0711 0711 0C55 0062;0061 0670 0711 0711 0C55 0062; # (a◌ౕ◌ܑ◌ٰ◌ܑb; a◌ٰ◌ܑ◌ܑ◌ౕb; a◌ٰ◌ܑ◌ܑ◌ౕb; a◌ٰ◌ܑ◌ܑ◌ౕb; a◌ٰ◌ܑ◌ܑ◌ౕb; ) LATIN SMALL LETTER A, TELUGU LENGTH MARK, SYRIAC LETTER SUPERSCRIPT ALAPH, ARABIC LETTER SUPERSCRIPT ALEF, SYRIAC LETTER SUPERSCRIPT ALAPH, LATIN SMALL LETTER B
+0061 0711 0C55 0711 0670 0062;0061 0670 0711 0711 0C55 0062;0061 0670 0711 0711 0C55 0062;0061 0670 0711 0711 0C55 0062;0061 0670 0711 0711 0C55 0062; # (a◌ܑ◌ౕ◌ܑ◌ٰb; a◌ٰ◌ܑ◌ܑ◌ౕb; a◌ٰ◌ܑ◌ܑ◌ౕb; a◌ٰ◌ܑ◌ܑ◌ౕb; a◌ٰ◌ܑ◌ܑ◌ౕb; ) LATIN SMALL LETTER A, SYRIAC LETTER SUPERSCRIPT ALAPH, TELUGU LENGTH MARK, SYRIAC LETTER SUPERSCRIPT ALAPH, ARABIC LETTER SUPERSCRIPT ALEF, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0730 0062;00E0 05AE 0730 0315 0062;0061 05AE 0300 0730 0315 0062;00E0 05AE 0730 0315 0062;0061 05AE 0300 0730 0315 0062; # (a◌̕◌̀◌֮◌ܰb; à◌֮◌ܰ◌̕b; a◌֮◌̀◌ܰ◌̕b; à◌֮◌ܰ◌̕b; a◌֮◌̀◌ܰ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC PTHAHA ABOVE, LATIN SMALL LETTER B
+0061 0730 0315 0300 05AE 0062;0061 05AE 0730 0300 0315 0062;0061 05AE 0730 0300 0315 0062;0061 05AE 0730 0300 0315 0062;0061 05AE 0730 0300 0315 0062; # (a◌ܰ◌̕◌̀◌֮b; a◌֮◌ܰ◌̀◌̕b; a◌֮◌ܰ◌̀◌̕b; a◌֮◌ܰ◌̀◌̕b; a◌֮◌ܰ◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC PTHAHA ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0731 0062;0061 1DFA 0316 0731 059A 0062;0061 1DFA 0316 0731 059A 0062;0061 1DFA 0316 0731 059A 0062;0061 1DFA 0316 0731 059A 0062; # (a◌֚◌̖◌᷺◌ܱb; a◌᷺◌̖◌ܱ◌֚b; a◌᷺◌̖◌ܱ◌֚b; a◌᷺◌̖◌ܱ◌֚b; a◌᷺◌̖◌ܱ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC PTHAHA BELOW, LATIN SMALL LETTER B
+0061 0731 059A 0316 1DFA 0062;0061 1DFA 0731 0316 059A 0062;0061 1DFA 0731 0316 059A 0062;0061 1DFA 0731 0316 059A 0062;0061 1DFA 0731 0316 059A 0062; # (a◌ܱ◌֚◌̖◌᷺b; a◌᷺◌ܱ◌̖◌֚b; a◌᷺◌ܱ◌̖◌֚b; a◌᷺◌ܱ◌̖◌֚b; a◌᷺◌ܱ◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC PTHAHA BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0732 0062;00E0 05AE 0732 0315 0062;0061 05AE 0300 0732 0315 0062;00E0 05AE 0732 0315 0062;0061 05AE 0300 0732 0315 0062; # (a◌̕◌̀◌֮◌ܲb; à◌֮◌ܲ◌̕b; a◌֮◌̀◌ܲ◌̕b; à◌֮◌ܲ◌̕b; a◌֮◌̀◌ܲ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC PTHAHA DOTTED, LATIN SMALL LETTER B
+0061 0732 0315 0300 05AE 0062;0061 05AE 0732 0300 0315 0062;0061 05AE 0732 0300 0315 0062;0061 05AE 0732 0300 0315 0062;0061 05AE 0732 0300 0315 0062; # (a◌ܲ◌̕◌̀◌֮b; a◌֮◌ܲ◌̀◌̕b; a◌֮◌ܲ◌̀◌̕b; a◌֮◌ܲ◌̀◌̕b; a◌֮◌ܲ◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC PTHAHA DOTTED, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0733 0062;00E0 05AE 0733 0315 0062;0061 05AE 0300 0733 0315 0062;00E0 05AE 0733 0315 0062;0061 05AE 0300 0733 0315 0062; # (a◌̕◌̀◌֮◌ܳb; à◌֮◌ܳ◌̕b; a◌֮◌̀◌ܳ◌̕b; à◌֮◌ܳ◌̕b; a◌֮◌̀◌ܳ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC ZQAPHA ABOVE, LATIN SMALL LETTER B
+0061 0733 0315 0300 05AE 0062;0061 05AE 0733 0300 0315 0062;0061 05AE 0733 0300 0315 0062;0061 05AE 0733 0300 0315 0062;0061 05AE 0733 0300 0315 0062; # (a◌ܳ◌̕◌̀◌֮b; a◌֮◌ܳ◌̀◌̕b; a◌֮◌ܳ◌̀◌̕b; a◌֮◌ܳ◌̀◌̕b; a◌֮◌ܳ◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC ZQAPHA ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0734 0062;0061 1DFA 0316 0734 059A 0062;0061 1DFA 0316 0734 059A 0062;0061 1DFA 0316 0734 059A 0062;0061 1DFA 0316 0734 059A 0062; # (a◌֚◌̖◌᷺◌ܴb; a◌᷺◌̖◌ܴ◌֚b; a◌᷺◌̖◌ܴ◌֚b; a◌᷺◌̖◌ܴ◌֚b; a◌᷺◌̖◌ܴ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC ZQAPHA BELOW, LATIN SMALL LETTER B
+0061 0734 059A 0316 1DFA 0062;0061 1DFA 0734 0316 059A 0062;0061 1DFA 0734 0316 059A 0062;0061 1DFA 0734 0316 059A 0062;0061 1DFA 0734 0316 059A 0062; # (a◌ܴ◌֚◌̖◌᷺b; a◌᷺◌ܴ◌̖◌֚b; a◌᷺◌ܴ◌̖◌֚b; a◌᷺◌ܴ◌̖◌֚b; a◌᷺◌ܴ◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC ZQAPHA BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0735 0062;00E0 05AE 0735 0315 0062;0061 05AE 0300 0735 0315 0062;00E0 05AE 0735 0315 0062;0061 05AE 0300 0735 0315 0062; # (a◌̕◌̀◌֮◌ܵb; à◌֮◌ܵ◌̕b; a◌֮◌̀◌ܵ◌̕b; à◌֮◌ܵ◌̕b; a◌֮◌̀◌ܵ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC ZQAPHA DOTTED, LATIN SMALL LETTER B
+0061 0735 0315 0300 05AE 0062;0061 05AE 0735 0300 0315 0062;0061 05AE 0735 0300 0315 0062;0061 05AE 0735 0300 0315 0062;0061 05AE 0735 0300 0315 0062; # (a◌ܵ◌̕◌̀◌֮b; a◌֮◌ܵ◌̀◌̕b; a◌֮◌ܵ◌̀◌̕b; a◌֮◌ܵ◌̀◌̕b; a◌֮◌ܵ◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC ZQAPHA DOTTED, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0736 0062;00E0 05AE 0736 0315 0062;0061 05AE 0300 0736 0315 0062;00E0 05AE 0736 0315 0062;0061 05AE 0300 0736 0315 0062; # (a◌̕◌̀◌֮◌ܶb; à◌֮◌ܶ◌̕b; a◌֮◌̀◌ܶ◌̕b; à◌֮◌ܶ◌̕b; a◌֮◌̀◌ܶ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC RBASA ABOVE, LATIN SMALL LETTER B
+0061 0736 0315 0300 05AE 0062;0061 05AE 0736 0300 0315 0062;0061 05AE 0736 0300 0315 0062;0061 05AE 0736 0300 0315 0062;0061 05AE 0736 0300 0315 0062; # (a◌ܶ◌̕◌̀◌֮b; a◌֮◌ܶ◌̀◌̕b; a◌֮◌ܶ◌̀◌̕b; a◌֮◌ܶ◌̀◌̕b; a◌֮◌ܶ◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC RBASA ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0737 0062;0061 1DFA 0316 0737 059A 0062;0061 1DFA 0316 0737 059A 0062;0061 1DFA 0316 0737 059A 0062;0061 1DFA 0316 0737 059A 0062; # (a◌֚◌̖◌᷺◌ܷb; a◌᷺◌̖◌ܷ◌֚b; a◌᷺◌̖◌ܷ◌֚b; a◌᷺◌̖◌ܷ◌֚b; a◌᷺◌̖◌ܷ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC RBASA BELOW, LATIN SMALL LETTER B
+0061 0737 059A 0316 1DFA 0062;0061 1DFA 0737 0316 059A 0062;0061 1DFA 0737 0316 059A 0062;0061 1DFA 0737 0316 059A 0062;0061 1DFA 0737 0316 059A 0062; # (a◌ܷ◌֚◌̖◌᷺b; a◌᷺◌ܷ◌̖◌֚b; a◌᷺◌ܷ◌̖◌֚b; a◌᷺◌ܷ◌̖◌֚b; a◌᷺◌ܷ◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC RBASA BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0738 0062;0061 1DFA 0316 0738 059A 0062;0061 1DFA 0316 0738 059A 0062;0061 1DFA 0316 0738 059A 0062;0061 1DFA 0316 0738 059A 0062; # (a◌֚◌̖◌᷺◌ܸb; a◌᷺◌̖◌ܸ◌֚b; a◌᷺◌̖◌ܸ◌֚b; a◌᷺◌̖◌ܸ◌֚b; a◌᷺◌̖◌ܸ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC DOTTED ZLAMA HORIZONTAL, LATIN SMALL LETTER B
+0061 0738 059A 0316 1DFA 0062;0061 1DFA 0738 0316 059A 0062;0061 1DFA 0738 0316 059A 0062;0061 1DFA 0738 0316 059A 0062;0061 1DFA 0738 0316 059A 0062; # (a◌ܸ◌֚◌̖◌᷺b; a◌᷺◌ܸ◌̖◌֚b; a◌᷺◌ܸ◌̖◌֚b; a◌᷺◌ܸ◌̖◌֚b; a◌᷺◌ܸ◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC DOTTED ZLAMA HORIZONTAL, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0739 0062;0061 1DFA 0316 0739 059A 0062;0061 1DFA 0316 0739 059A 0062;0061 1DFA 0316 0739 059A 0062;0061 1DFA 0316 0739 059A 0062; # (a◌֚◌̖◌᷺◌ܹb; a◌᷺◌̖◌ܹ◌֚b; a◌᷺◌̖◌ܹ◌֚b; a◌᷺◌̖◌ܹ◌֚b; a◌᷺◌̖◌ܹ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC DOTTED ZLAMA ANGULAR, LATIN SMALL LETTER B
+0061 0739 059A 0316 1DFA 0062;0061 1DFA 0739 0316 059A 0062;0061 1DFA 0739 0316 059A 0062;0061 1DFA 0739 0316 059A 0062;0061 1DFA 0739 0316 059A 0062; # (a◌ܹ◌֚◌̖◌᷺b; a◌᷺◌ܹ◌̖◌֚b; a◌᷺◌ܹ◌̖◌֚b; a◌᷺◌ܹ◌̖◌֚b; a◌᷺◌ܹ◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC DOTTED ZLAMA ANGULAR, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 073A 0062;00E0 05AE 073A 0315 0062;0061 05AE 0300 073A 0315 0062;00E0 05AE 073A 0315 0062;0061 05AE 0300 073A 0315 0062; # (a◌̕◌̀◌֮◌ܺb; à◌֮◌ܺ◌̕b; a◌֮◌̀◌ܺ◌̕b; à◌֮◌ܺ◌̕b; a◌֮◌̀◌ܺ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC HBASA ABOVE, LATIN SMALL LETTER B
+0061 073A 0315 0300 05AE 0062;0061 05AE 073A 0300 0315 0062;0061 05AE 073A 0300 0315 0062;0061 05AE 073A 0300 0315 0062;0061 05AE 073A 0300 0315 0062; # (a◌ܺ◌̕◌̀◌֮b; a◌֮◌ܺ◌̀◌̕b; a◌֮◌ܺ◌̀◌̕b; a◌֮◌ܺ◌̀◌̕b; a◌֮◌ܺ◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC HBASA ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 073B 0062;0061 1DFA 0316 073B 059A 0062;0061 1DFA 0316 073B 059A 0062;0061 1DFA 0316 073B 059A 0062;0061 1DFA 0316 073B 059A 0062; # (a◌֚◌̖◌᷺◌ܻb; a◌᷺◌̖◌ܻ◌֚b; a◌᷺◌̖◌ܻ◌֚b; a◌᷺◌̖◌ܻ◌֚b; a◌᷺◌̖◌ܻ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC HBASA BELOW, LATIN SMALL LETTER B
+0061 073B 059A 0316 1DFA 0062;0061 1DFA 073B 0316 059A 0062;0061 1DFA 073B 0316 059A 0062;0061 1DFA 073B 0316 059A 0062;0061 1DFA 073B 0316 059A 0062; # (a◌ܻ◌֚◌̖◌᷺b; a◌᷺◌ܻ◌̖◌֚b; a◌᷺◌ܻ◌̖◌֚b; a◌᷺◌ܻ◌̖◌֚b; a◌᷺◌ܻ◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC HBASA BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 073C 0062;0061 1DFA 0316 073C 059A 0062;0061 1DFA 0316 073C 059A 0062;0061 1DFA 0316 073C 059A 0062;0061 1DFA 0316 073C 059A 0062; # (a◌֚◌̖◌᷺◌ܼb; a◌᷺◌̖◌ܼ◌֚b; a◌᷺◌̖◌ܼ◌֚b; a◌᷺◌̖◌ܼ◌֚b; a◌᷺◌̖◌ܼ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC HBASA-ESASA DOTTED, LATIN SMALL LETTER B
+0061 073C 059A 0316 1DFA 0062;0061 1DFA 073C 0316 059A 0062;0061 1DFA 073C 0316 059A 0062;0061 1DFA 073C 0316 059A 0062;0061 1DFA 073C 0316 059A 0062; # (a◌ܼ◌֚◌̖◌᷺b; a◌᷺◌ܼ◌̖◌֚b; a◌᷺◌ܼ◌̖◌֚b; a◌᷺◌ܼ◌̖◌֚b; a◌᷺◌ܼ◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC HBASA-ESASA DOTTED, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 073D 0062;00E0 05AE 073D 0315 0062;0061 05AE 0300 073D 0315 0062;00E0 05AE 073D 0315 0062;0061 05AE 0300 073D 0315 0062; # (a◌̕◌̀◌֮◌ܽb; à◌֮◌ܽ◌̕b; a◌֮◌̀◌ܽ◌̕b; à◌֮◌ܽ◌̕b; a◌֮◌̀◌ܽ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC ESASA ABOVE, LATIN SMALL LETTER B
+0061 073D 0315 0300 05AE 0062;0061 05AE 073D 0300 0315 0062;0061 05AE 073D 0300 0315 0062;0061 05AE 073D 0300 0315 0062;0061 05AE 073D 0300 0315 0062; # (a◌ܽ◌̕◌̀◌֮b; a◌֮◌ܽ◌̀◌̕b; a◌֮◌ܽ◌̀◌̕b; a◌֮◌ܽ◌̀◌̕b; a◌֮◌ܽ◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC ESASA ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 073E 0062;0061 1DFA 0316 073E 059A 0062;0061 1DFA 0316 073E 059A 0062;0061 1DFA 0316 073E 059A 0062;0061 1DFA 0316 073E 059A 0062; # (a◌֚◌̖◌᷺◌ܾb; a◌᷺◌̖◌ܾ◌֚b; a◌᷺◌̖◌ܾ◌֚b; a◌᷺◌̖◌ܾ◌֚b; a◌᷺◌̖◌ܾ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC ESASA BELOW, LATIN SMALL LETTER B
+0061 073E 059A 0316 1DFA 0062;0061 1DFA 073E 0316 059A 0062;0061 1DFA 073E 0316 059A 0062;0061 1DFA 073E 0316 059A 0062;0061 1DFA 073E 0316 059A 0062; # (a◌ܾ◌֚◌̖◌᷺b; a◌᷺◌ܾ◌̖◌֚b; a◌᷺◌ܾ◌̖◌֚b; a◌᷺◌ܾ◌̖◌֚b; a◌᷺◌ܾ◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC ESASA BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 073F 0062;00E0 05AE 073F 0315 0062;0061 05AE 0300 073F 0315 0062;00E0 05AE 073F 0315 0062;0061 05AE 0300 073F 0315 0062; # (a◌̕◌̀◌֮◌ܿb; à◌֮◌ܿ◌̕b; a◌֮◌̀◌ܿ◌̕b; à◌֮◌ܿ◌̕b; a◌֮◌̀◌ܿ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC RWAHA, LATIN SMALL LETTER B
+0061 073F 0315 0300 05AE 0062;0061 05AE 073F 0300 0315 0062;0061 05AE 073F 0300 0315 0062;0061 05AE 073F 0300 0315 0062;0061 05AE 073F 0300 0315 0062; # (a◌ܿ◌̕◌̀◌֮b; a◌֮◌ܿ◌̀◌̕b; a◌֮◌ܿ◌̀◌̕b; a◌֮◌ܿ◌̀◌̕b; a◌֮◌ܿ◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC RWAHA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0740 0062;00E0 05AE 0740 0315 0062;0061 05AE 0300 0740 0315 0062;00E0 05AE 0740 0315 0062;0061 05AE 0300 0740 0315 0062; # (a◌̕◌̀◌֮◌݀b; à◌֮◌݀◌̕b; a◌֮◌̀◌݀◌̕b; à◌֮◌݀◌̕b; a◌֮◌̀◌݀◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC FEMININE DOT, LATIN SMALL LETTER B
+0061 0740 0315 0300 05AE 0062;0061 05AE 0740 0300 0315 0062;0061 05AE 0740 0300 0315 0062;0061 05AE 0740 0300 0315 0062;0061 05AE 0740 0300 0315 0062; # (a◌݀◌̕◌̀◌֮b; a◌֮◌݀◌̀◌̕b; a◌֮◌݀◌̀◌̕b; a◌֮◌݀◌̀◌̕b; a◌֮◌݀◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC FEMININE DOT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0741 0062;00E0 05AE 0741 0315 0062;0061 05AE 0300 0741 0315 0062;00E0 05AE 0741 0315 0062;0061 05AE 0300 0741 0315 0062; # (a◌̕◌̀◌֮◌Ýb; à◌֮◌Ý◌̕b; a◌֮◌̀◌Ý◌̕b; à◌֮◌Ý◌̕b; a◌֮◌̀◌Ý◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC QUSHSHAYA, LATIN SMALL LETTER B
+0061 0741 0315 0300 05AE 0062;0061 05AE 0741 0300 0315 0062;0061 05AE 0741 0300 0315 0062;0061 05AE 0741 0300 0315 0062;0061 05AE 0741 0300 0315 0062; # (aâ—ŒÝ◌̕◌̀◌֮b; a◌֮◌Ý◌̀◌̕b; a◌֮◌Ý◌̀◌̕b; a◌֮◌Ý◌̀◌̕b; a◌֮◌Ý◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC QUSHSHAYA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0742 0062;0061 1DFA 0316 0742 059A 0062;0061 1DFA 0316 0742 059A 0062;0061 1DFA 0316 0742 059A 0062;0061 1DFA 0316 0742 059A 0062; # (a◌֚◌̖◌᷺◌݂b; a◌᷺◌̖◌݂◌֚b; a◌᷺◌̖◌݂◌֚b; a◌᷺◌̖◌݂◌֚b; a◌᷺◌̖◌݂◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC RUKKAKHA, LATIN SMALL LETTER B
+0061 0742 059A 0316 1DFA 0062;0061 1DFA 0742 0316 059A 0062;0061 1DFA 0742 0316 059A 0062;0061 1DFA 0742 0316 059A 0062;0061 1DFA 0742 0316 059A 0062; # (a◌݂◌֚◌̖◌᷺b; a◌᷺◌݂◌̖◌֚b; a◌᷺◌݂◌̖◌֚b; a◌᷺◌݂◌̖◌֚b; a◌᷺◌݂◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC RUKKAKHA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0743 0062;00E0 05AE 0743 0315 0062;0061 05AE 0300 0743 0315 0062;00E0 05AE 0743 0315 0062;0061 05AE 0300 0743 0315 0062; # (a◌̕◌̀◌֮◌݃b; à◌֮◌݃◌̕b; a◌֮◌̀◌݃◌̕b; à◌֮◌݃◌̕b; a◌֮◌̀◌݃◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC TWO VERTICAL DOTS ABOVE, LATIN SMALL LETTER B
+0061 0743 0315 0300 05AE 0062;0061 05AE 0743 0300 0315 0062;0061 05AE 0743 0300 0315 0062;0061 05AE 0743 0300 0315 0062;0061 05AE 0743 0300 0315 0062; # (a◌݃◌̕◌̀◌֮b; a◌֮◌݃◌̀◌̕b; a◌֮◌݃◌̀◌̕b; a◌֮◌݃◌̀◌̕b; a◌֮◌݃◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC TWO VERTICAL DOTS ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0744 0062;0061 1DFA 0316 0744 059A 0062;0061 1DFA 0316 0744 059A 0062;0061 1DFA 0316 0744 059A 0062;0061 1DFA 0316 0744 059A 0062; # (a◌֚◌̖◌᷺◌݄b; a◌᷺◌̖◌݄◌֚b; a◌᷺◌̖◌݄◌֚b; a◌᷺◌̖◌݄◌֚b; a◌᷺◌̖◌݄◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC TWO VERTICAL DOTS BELOW, LATIN SMALL LETTER B
+0061 0744 059A 0316 1DFA 0062;0061 1DFA 0744 0316 059A 0062;0061 1DFA 0744 0316 059A 0062;0061 1DFA 0744 0316 059A 0062;0061 1DFA 0744 0316 059A 0062; # (a◌݄◌֚◌̖◌᷺b; a◌᷺◌݄◌̖◌֚b; a◌᷺◌݄◌̖◌֚b; a◌᷺◌݄◌̖◌֚b; a◌᷺◌݄◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC TWO VERTICAL DOTS BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0745 0062;00E0 05AE 0745 0315 0062;0061 05AE 0300 0745 0315 0062;00E0 05AE 0745 0315 0062;0061 05AE 0300 0745 0315 0062; # (a◌̕◌̀◌֮◌݅b; à◌֮◌݅◌̕b; a◌֮◌̀◌݅◌̕b; à◌֮◌݅◌̕b; a◌֮◌̀◌݅◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC THREE DOTS ABOVE, LATIN SMALL LETTER B
+0061 0745 0315 0300 05AE 0062;0061 05AE 0745 0300 0315 0062;0061 05AE 0745 0300 0315 0062;0061 05AE 0745 0300 0315 0062;0061 05AE 0745 0300 0315 0062; # (a◌݅◌̕◌̀◌֮b; a◌֮◌݅◌̀◌̕b; a◌֮◌݅◌̀◌̕b; a◌֮◌݅◌̀◌̕b; a◌֮◌݅◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC THREE DOTS ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0746 0062;0061 1DFA 0316 0746 059A 0062;0061 1DFA 0316 0746 059A 0062;0061 1DFA 0316 0746 059A 0062;0061 1DFA 0316 0746 059A 0062; # (a◌֚◌̖◌᷺◌݆b; a◌᷺◌̖◌݆◌֚b; a◌᷺◌̖◌݆◌֚b; a◌᷺◌̖◌݆◌֚b; a◌᷺◌̖◌݆◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC THREE DOTS BELOW, LATIN SMALL LETTER B
+0061 0746 059A 0316 1DFA 0062;0061 1DFA 0746 0316 059A 0062;0061 1DFA 0746 0316 059A 0062;0061 1DFA 0746 0316 059A 0062;0061 1DFA 0746 0316 059A 0062; # (a◌݆◌֚◌̖◌᷺b; a◌᷺◌݆◌̖◌֚b; a◌᷺◌݆◌̖◌֚b; a◌᷺◌݆◌̖◌֚b; a◌᷺◌݆◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC THREE DOTS BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0747 0062;00E0 05AE 0747 0315 0062;0061 05AE 0300 0747 0315 0062;00E0 05AE 0747 0315 0062;0061 05AE 0300 0747 0315 0062; # (a◌̕◌̀◌֮◌݇b; à◌֮◌݇◌̕b; a◌֮◌̀◌݇◌̕b; à◌֮◌݇◌̕b; a◌֮◌̀◌݇◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC OBLIQUE LINE ABOVE, LATIN SMALL LETTER B
+0061 0747 0315 0300 05AE 0062;0061 05AE 0747 0300 0315 0062;0061 05AE 0747 0300 0315 0062;0061 05AE 0747 0300 0315 0062;0061 05AE 0747 0300 0315 0062; # (a◌݇◌̕◌̀◌֮b; a◌֮◌݇◌̀◌̕b; a◌֮◌݇◌̀◌̕b; a◌֮◌݇◌̀◌̕b; a◌֮◌݇◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC OBLIQUE LINE ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0748 0062;0061 1DFA 0316 0748 059A 0062;0061 1DFA 0316 0748 059A 0062;0061 1DFA 0316 0748 059A 0062;0061 1DFA 0316 0748 059A 0062; # (a◌֚◌̖◌᷺◌݈b; a◌᷺◌̖◌݈◌֚b; a◌᷺◌̖◌݈◌֚b; a◌᷺◌̖◌݈◌֚b; a◌᷺◌̖◌݈◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SYRIAC OBLIQUE LINE BELOW, LATIN SMALL LETTER B
+0061 0748 059A 0316 1DFA 0062;0061 1DFA 0748 0316 059A 0062;0061 1DFA 0748 0316 059A 0062;0061 1DFA 0748 0316 059A 0062;0061 1DFA 0748 0316 059A 0062; # (a◌݈◌֚◌̖◌᷺b; a◌᷺◌݈◌̖◌֚b; a◌᷺◌݈◌̖◌֚b; a◌᷺◌݈◌̖◌֚b; a◌᷺◌݈◌̖◌֚b; ) LATIN SMALL LETTER A, SYRIAC OBLIQUE LINE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0749 0062;00E0 05AE 0749 0315 0062;0061 05AE 0300 0749 0315 0062;00E0 05AE 0749 0315 0062;0061 05AE 0300 0749 0315 0062; # (a◌̕◌̀◌֮◌݉b; à◌֮◌݉◌̕b; a◌֮◌̀◌݉◌̕b; à◌֮◌݉◌̕b; a◌֮◌̀◌݉◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC MUSIC, LATIN SMALL LETTER B
+0061 0749 0315 0300 05AE 0062;0061 05AE 0749 0300 0315 0062;0061 05AE 0749 0300 0315 0062;0061 05AE 0749 0300 0315 0062;0061 05AE 0749 0300 0315 0062; # (a◌݉◌̕◌̀◌֮b; a◌֮◌݉◌̀◌̕b; a◌֮◌݉◌̀◌̕b; a◌֮◌݉◌̀◌̕b; a◌֮◌݉◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC MUSIC, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 074A 0062;00E0 05AE 074A 0315 0062;0061 05AE 0300 074A 0315 0062;00E0 05AE 074A 0315 0062;0061 05AE 0300 074A 0315 0062; # (a◌̕◌̀◌֮◌݊b; à◌֮◌݊◌̕b; a◌֮◌̀◌݊◌̕b; à◌֮◌݊◌̕b; a◌֮◌̀◌݊◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SYRIAC BARREKH, LATIN SMALL LETTER B
+0061 074A 0315 0300 05AE 0062;0061 05AE 074A 0300 0315 0062;0061 05AE 074A 0300 0315 0062;0061 05AE 074A 0300 0315 0062;0061 05AE 074A 0300 0315 0062; # (a◌݊◌̕◌̀◌֮b; a◌֮◌݊◌̀◌̕b; a◌֮◌݊◌̀◌̕b; a◌֮◌݊◌̀◌̕b; a◌֮◌݊◌̀◌̕b; ) LATIN SMALL LETTER A, SYRIAC BARREKH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 07EB 0062;00E0 05AE 07EB 0315 0062;0061 05AE 0300 07EB 0315 0062;00E0 05AE 07EB 0315 0062;0061 05AE 0300 07EB 0315 0062; # (a◌̕◌̀◌֮◌߫b; à◌֮◌߫◌̕b; a◌֮◌̀◌߫◌̕b; à◌֮◌߫◌̕b; a◌֮◌̀◌߫◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NKO COMBINING SHORT HIGH TONE, LATIN SMALL LETTER B
+0061 07EB 0315 0300 05AE 0062;0061 05AE 07EB 0300 0315 0062;0061 05AE 07EB 0300 0315 0062;0061 05AE 07EB 0300 0315 0062;0061 05AE 07EB 0300 0315 0062; # (a◌߫◌̕◌̀◌֮b; a◌֮◌߫◌̀◌̕b; a◌֮◌߫◌̀◌̕b; a◌֮◌߫◌̀◌̕b; a◌֮◌߫◌̀◌̕b; ) LATIN SMALL LETTER A, NKO COMBINING SHORT HIGH TONE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 07EC 0062;00E0 05AE 07EC 0315 0062;0061 05AE 0300 07EC 0315 0062;00E0 05AE 07EC 0315 0062;0061 05AE 0300 07EC 0315 0062; # (a◌̕◌̀◌֮◌߬b; à◌֮◌߬◌̕b; a◌֮◌̀◌߬◌̕b; à◌֮◌߬◌̕b; a◌֮◌̀◌߬◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NKO COMBINING SHORT LOW TONE, LATIN SMALL LETTER B
+0061 07EC 0315 0300 05AE 0062;0061 05AE 07EC 0300 0315 0062;0061 05AE 07EC 0300 0315 0062;0061 05AE 07EC 0300 0315 0062;0061 05AE 07EC 0300 0315 0062; # (a◌߬◌̕◌̀◌֮b; a◌֮◌߬◌̀◌̕b; a◌֮◌߬◌̀◌̕b; a◌֮◌߬◌̀◌̕b; a◌֮◌߬◌̀◌̕b; ) LATIN SMALL LETTER A, NKO COMBINING SHORT LOW TONE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 07ED 0062;00E0 05AE 07ED 0315 0062;0061 05AE 0300 07ED 0315 0062;00E0 05AE 07ED 0315 0062;0061 05AE 0300 07ED 0315 0062; # (a◌̕◌̀◌֮◌߭b; à◌֮◌߭◌̕b; a◌֮◌̀◌߭◌̕b; à◌֮◌߭◌̕b; a◌֮◌̀◌߭◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NKO COMBINING SHORT RISING TONE, LATIN SMALL LETTER B
+0061 07ED 0315 0300 05AE 0062;0061 05AE 07ED 0300 0315 0062;0061 05AE 07ED 0300 0315 0062;0061 05AE 07ED 0300 0315 0062;0061 05AE 07ED 0300 0315 0062; # (a◌߭◌̕◌̀◌֮b; a◌֮◌߭◌̀◌̕b; a◌֮◌߭◌̀◌̕b; a◌֮◌߭◌̀◌̕b; a◌֮◌߭◌̀◌̕b; ) LATIN SMALL LETTER A, NKO COMBINING SHORT RISING TONE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 07EE 0062;00E0 05AE 07EE 0315 0062;0061 05AE 0300 07EE 0315 0062;00E0 05AE 07EE 0315 0062;0061 05AE 0300 07EE 0315 0062; # (a◌̕◌̀◌֮◌߮b; à◌֮◌߮◌̕b; a◌֮◌̀◌߮◌̕b; à◌֮◌߮◌̕b; a◌֮◌̀◌߮◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NKO COMBINING LONG DESCENDING TONE, LATIN SMALL LETTER B
+0061 07EE 0315 0300 05AE 0062;0061 05AE 07EE 0300 0315 0062;0061 05AE 07EE 0300 0315 0062;0061 05AE 07EE 0300 0315 0062;0061 05AE 07EE 0300 0315 0062; # (a◌߮◌̕◌̀◌֮b; a◌֮◌߮◌̀◌̕b; a◌֮◌߮◌̀◌̕b; a◌֮◌߮◌̀◌̕b; a◌֮◌߮◌̀◌̕b; ) LATIN SMALL LETTER A, NKO COMBINING LONG DESCENDING TONE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 07EF 0062;00E0 05AE 07EF 0315 0062;0061 05AE 0300 07EF 0315 0062;00E0 05AE 07EF 0315 0062;0061 05AE 0300 07EF 0315 0062; # (a◌̕◌̀◌֮◌߯b; à◌֮◌߯◌̕b; a◌֮◌̀◌߯◌̕b; à◌֮◌߯◌̕b; a◌֮◌̀◌߯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NKO COMBINING LONG HIGH TONE, LATIN SMALL LETTER B
+0061 07EF 0315 0300 05AE 0062;0061 05AE 07EF 0300 0315 0062;0061 05AE 07EF 0300 0315 0062;0061 05AE 07EF 0300 0315 0062;0061 05AE 07EF 0300 0315 0062; # (a◌߯◌̕◌̀◌֮b; a◌֮◌߯◌̀◌̕b; a◌֮◌߯◌̀◌̕b; a◌֮◌߯◌̀◌̕b; a◌֮◌߯◌̀◌̕b; ) LATIN SMALL LETTER A, NKO COMBINING LONG HIGH TONE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 07F0 0062;00E0 05AE 07F0 0315 0062;0061 05AE 0300 07F0 0315 0062;00E0 05AE 07F0 0315 0062;0061 05AE 0300 07F0 0315 0062; # (a◌̕◌̀◌֮◌߰b; à◌֮◌߰◌̕b; a◌֮◌̀◌߰◌̕b; à◌֮◌߰◌̕b; a◌֮◌̀◌߰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NKO COMBINING LONG LOW TONE, LATIN SMALL LETTER B
+0061 07F0 0315 0300 05AE 0062;0061 05AE 07F0 0300 0315 0062;0061 05AE 07F0 0300 0315 0062;0061 05AE 07F0 0300 0315 0062;0061 05AE 07F0 0300 0315 0062; # (a◌߰◌̕◌̀◌֮b; a◌֮◌߰◌̀◌̕b; a◌֮◌߰◌̀◌̕b; a◌֮◌߰◌̀◌̕b; a◌֮◌߰◌̀◌̕b; ) LATIN SMALL LETTER A, NKO COMBINING LONG LOW TONE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 07F1 0062;00E0 05AE 07F1 0315 0062;0061 05AE 0300 07F1 0315 0062;00E0 05AE 07F1 0315 0062;0061 05AE 0300 07F1 0315 0062; # (a◌̕◌̀◌֮◌߱b; à◌֮◌߱◌̕b; a◌֮◌̀◌߱◌̕b; à◌֮◌߱◌̕b; a◌֮◌̀◌߱◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NKO COMBINING LONG RISING TONE, LATIN SMALL LETTER B
+0061 07F1 0315 0300 05AE 0062;0061 05AE 07F1 0300 0315 0062;0061 05AE 07F1 0300 0315 0062;0061 05AE 07F1 0300 0315 0062;0061 05AE 07F1 0300 0315 0062; # (a◌߱◌̕◌̀◌֮b; a◌֮◌߱◌̀◌̕b; a◌֮◌߱◌̀◌̕b; a◌֮◌߱◌̀◌̕b; a◌֮◌߱◌̀◌̕b; ) LATIN SMALL LETTER A, NKO COMBINING LONG RISING TONE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 07F2 0062;0061 1DFA 0316 07F2 059A 0062;0061 1DFA 0316 07F2 059A 0062;0061 1DFA 0316 07F2 059A 0062;0061 1DFA 0316 07F2 059A 0062; # (a◌֚◌̖◌᷺◌߲b; a◌᷺◌̖◌߲◌֚b; a◌᷺◌̖◌߲◌֚b; a◌᷺◌̖◌߲◌֚b; a◌᷺◌̖◌߲◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, NKO COMBINING NASALIZATION MARK, LATIN SMALL LETTER B
+0061 07F2 059A 0316 1DFA 0062;0061 1DFA 07F2 0316 059A 0062;0061 1DFA 07F2 0316 059A 0062;0061 1DFA 07F2 0316 059A 0062;0061 1DFA 07F2 0316 059A 0062; # (a◌߲◌֚◌̖◌᷺b; a◌᷺◌߲◌̖◌֚b; a◌᷺◌߲◌̖◌֚b; a◌᷺◌߲◌̖◌֚b; a◌᷺◌߲◌̖◌֚b; ) LATIN SMALL LETTER A, NKO COMBINING NASALIZATION MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 07F3 0062;00E0 05AE 07F3 0315 0062;0061 05AE 0300 07F3 0315 0062;00E0 05AE 07F3 0315 0062;0061 05AE 0300 07F3 0315 0062; # (a◌̕◌̀◌֮◌߳b; à◌֮◌߳◌̕b; a◌֮◌̀◌߳◌̕b; à◌֮◌߳◌̕b; a◌֮◌̀◌߳◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NKO COMBINING DOUBLE DOT ABOVE, LATIN SMALL LETTER B
+0061 07F3 0315 0300 05AE 0062;0061 05AE 07F3 0300 0315 0062;0061 05AE 07F3 0300 0315 0062;0061 05AE 07F3 0300 0315 0062;0061 05AE 07F3 0300 0315 0062; # (a◌߳◌̕◌̀◌֮b; a◌֮◌߳◌̀◌̕b; a◌֮◌߳◌̀◌̕b; a◌֮◌߳◌̀◌̕b; a◌֮◌߳◌̀◌̕b; ) LATIN SMALL LETTER A, NKO COMBINING DOUBLE DOT ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 07FD 0062;0061 1DFA 0316 07FD 059A 0062;0061 1DFA 0316 07FD 059A 0062;0061 1DFA 0316 07FD 059A 0062;0061 1DFA 0316 07FD 059A 0062; # (a◌֚◌̖◌᷺◌߽b; a◌᷺◌̖◌߽◌֚b; a◌᷺◌̖◌߽◌֚b; a◌᷺◌̖◌߽◌֚b; a◌᷺◌̖◌߽◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, NKO DANTAYALAN, LATIN SMALL LETTER B
+0061 07FD 059A 0316 1DFA 0062;0061 1DFA 07FD 0316 059A 0062;0061 1DFA 07FD 0316 059A 0062;0061 1DFA 07FD 0316 059A 0062;0061 1DFA 07FD 0316 059A 0062; # (a◌߽◌֚◌̖◌᷺b; a◌᷺◌߽◌̖◌֚b; a◌᷺◌߽◌̖◌֚b; a◌᷺◌߽◌̖◌֚b; a◌᷺◌߽◌̖◌֚b; ) LATIN SMALL LETTER A, NKO DANTAYALAN, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0816 0062;00E0 05AE 0816 0315 0062;0061 05AE 0300 0816 0315 0062;00E0 05AE 0816 0315 0062;0061 05AE 0300 0816 0315 0062; # (a◌̕◌̀◌֮◌ࠖb; à◌֮◌ࠖ◌̕b; a◌֮◌̀◌ࠖ◌̕b; à◌֮◌ࠖ◌̕b; a◌֮◌̀◌ࠖ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN MARK IN, LATIN SMALL LETTER B
+0061 0816 0315 0300 05AE 0062;0061 05AE 0816 0300 0315 0062;0061 05AE 0816 0300 0315 0062;0061 05AE 0816 0300 0315 0062;0061 05AE 0816 0300 0315 0062; # (a◌ࠖ◌̕◌̀◌֮b; a◌֮◌ࠖ◌̀◌̕b; a◌֮◌ࠖ◌̀◌̕b; a◌֮◌ࠖ◌̀◌̕b; a◌֮◌ࠖ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN MARK IN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0817 0062;00E0 05AE 0817 0315 0062;0061 05AE 0300 0817 0315 0062;00E0 05AE 0817 0315 0062;0061 05AE 0300 0817 0315 0062; # (a◌̕◌̀◌֮◌ࠗb; à◌֮◌ࠗ◌̕b; a◌֮◌̀◌ࠗ◌̕b; à◌֮◌ࠗ◌̕b; a◌֮◌̀◌ࠗ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN MARK IN-ALAF, LATIN SMALL LETTER B
+0061 0817 0315 0300 05AE 0062;0061 05AE 0817 0300 0315 0062;0061 05AE 0817 0300 0315 0062;0061 05AE 0817 0300 0315 0062;0061 05AE 0817 0300 0315 0062; # (a◌ࠗ◌̕◌̀◌֮b; a◌֮◌ࠗ◌̀◌̕b; a◌֮◌ࠗ◌̀◌̕b; a◌֮◌ࠗ◌̀◌̕b; a◌֮◌ࠗ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN MARK IN-ALAF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0818 0062;00E0 05AE 0818 0315 0062;0061 05AE 0300 0818 0315 0062;00E0 05AE 0818 0315 0062;0061 05AE 0300 0818 0315 0062; # (a◌̕◌̀◌֮◌࠘b; à◌֮◌࠘◌̕b; a◌֮◌̀◌࠘◌̕b; à◌֮◌࠘◌̕b; a◌֮◌̀◌࠘◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN MARK OCCLUSION, LATIN SMALL LETTER B
+0061 0818 0315 0300 05AE 0062;0061 05AE 0818 0300 0315 0062;0061 05AE 0818 0300 0315 0062;0061 05AE 0818 0300 0315 0062;0061 05AE 0818 0300 0315 0062; # (a◌࠘◌̕◌̀◌֮b; a◌֮◌࠘◌̀◌̕b; a◌֮◌࠘◌̀◌̕b; a◌֮◌࠘◌̀◌̕b; a◌֮◌࠘◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN MARK OCCLUSION, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0819 0062;00E0 05AE 0819 0315 0062;0061 05AE 0300 0819 0315 0062;00E0 05AE 0819 0315 0062;0061 05AE 0300 0819 0315 0062; # (a◌̕◌̀◌֮◌࠙b; à◌֮◌࠙◌̕b; a◌֮◌̀◌࠙◌̕b; à◌֮◌࠙◌̕b; a◌֮◌̀◌࠙◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN MARK DAGESH, LATIN SMALL LETTER B
+0061 0819 0315 0300 05AE 0062;0061 05AE 0819 0300 0315 0062;0061 05AE 0819 0300 0315 0062;0061 05AE 0819 0300 0315 0062;0061 05AE 0819 0300 0315 0062; # (a◌࠙◌̕◌̀◌֮b; a◌֮◌࠙◌̀◌̕b; a◌֮◌࠙◌̀◌̕b; a◌֮◌࠙◌̀◌̕b; a◌֮◌࠙◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN MARK DAGESH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 081B 0062;00E0 05AE 081B 0315 0062;0061 05AE 0300 081B 0315 0062;00E0 05AE 081B 0315 0062;0061 05AE 0300 081B 0315 0062; # (a◌̕◌̀◌֮◌ࠛb; à◌֮◌ࠛ◌̕b; a◌֮◌̀◌ࠛ◌̕b; à◌֮◌ࠛ◌̕b; a◌֮◌̀◌ࠛ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN MARK EPENTHETIC YUT, LATIN SMALL LETTER B
+0061 081B 0315 0300 05AE 0062;0061 05AE 081B 0300 0315 0062;0061 05AE 081B 0300 0315 0062;0061 05AE 081B 0300 0315 0062;0061 05AE 081B 0300 0315 0062; # (a◌ࠛ◌̕◌̀◌֮b; a◌֮◌ࠛ◌̀◌̕b; a◌֮◌ࠛ◌̀◌̕b; a◌֮◌ࠛ◌̀◌̕b; a◌֮◌ࠛ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN MARK EPENTHETIC YUT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 081C 0062;00E0 05AE 081C 0315 0062;0061 05AE 0300 081C 0315 0062;00E0 05AE 081C 0315 0062;0061 05AE 0300 081C 0315 0062; # (a◌̕◌̀◌֮◌ࠜb; à◌֮◌ࠜ◌̕b; a◌֮◌̀◌ࠜ◌̕b; à◌֮◌ࠜ◌̕b; a◌֮◌̀◌ࠜ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN LONG E, LATIN SMALL LETTER B
+0061 081C 0315 0300 05AE 0062;0061 05AE 081C 0300 0315 0062;0061 05AE 081C 0300 0315 0062;0061 05AE 081C 0300 0315 0062;0061 05AE 081C 0300 0315 0062; # (a◌ࠜ◌̕◌̀◌֮b; a◌֮◌ࠜ◌̀◌̕b; a◌֮◌ࠜ◌̀◌̕b; a◌֮◌ࠜ◌̀◌̕b; a◌֮◌ࠜ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN LONG E, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 081D 0062;00E0 05AE 081D 0315 0062;0061 05AE 0300 081D 0315 0062;00E0 05AE 081D 0315 0062;0061 05AE 0300 081D 0315 0062; # (a◌̕◌̀◌֮◌à b; à◌֮◌à â—ŒÌ•b; a◌֮◌̀◌à â—ŒÌ•b; à◌֮◌à â—ŒÌ•b; a◌֮◌̀◌à â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN E, LATIN SMALL LETTER B
+0061 081D 0315 0300 05AE 0062;0061 05AE 081D 0300 0315 0062;0061 05AE 081D 0300 0315 0062;0061 05AE 081D 0300 0315 0062;0061 05AE 081D 0300 0315 0062; # (aâ—Œà â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌à â—ŒÌ€â—ŒÌ•b; a◌֮◌à â—ŒÌ€â—ŒÌ•b; a◌֮◌à â—ŒÌ€â—ŒÌ•b; a◌֮◌à â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN E, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 081E 0062;00E0 05AE 081E 0315 0062;0061 05AE 0300 081E 0315 0062;00E0 05AE 081E 0315 0062;0061 05AE 0300 081E 0315 0062; # (a◌̕◌̀◌֮◌ࠞb; à◌֮◌ࠞ◌̕b; a◌֮◌̀◌ࠞ◌̕b; à◌֮◌ࠞ◌̕b; a◌֮◌̀◌ࠞ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN OVERLONG AA, LATIN SMALL LETTER B
+0061 081E 0315 0300 05AE 0062;0061 05AE 081E 0300 0315 0062;0061 05AE 081E 0300 0315 0062;0061 05AE 081E 0300 0315 0062;0061 05AE 081E 0300 0315 0062; # (a◌ࠞ◌̕◌̀◌֮b; a◌֮◌ࠞ◌̀◌̕b; a◌֮◌ࠞ◌̀◌̕b; a◌֮◌ࠞ◌̀◌̕b; a◌֮◌ࠞ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN OVERLONG AA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 081F 0062;00E0 05AE 081F 0315 0062;0061 05AE 0300 081F 0315 0062;00E0 05AE 081F 0315 0062;0061 05AE 0300 081F 0315 0062; # (a◌̕◌̀◌֮◌ࠟb; à◌֮◌ࠟ◌̕b; a◌֮◌̀◌ࠟ◌̕b; à◌֮◌ࠟ◌̕b; a◌֮◌̀◌ࠟ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN LONG AA, LATIN SMALL LETTER B
+0061 081F 0315 0300 05AE 0062;0061 05AE 081F 0300 0315 0062;0061 05AE 081F 0300 0315 0062;0061 05AE 081F 0300 0315 0062;0061 05AE 081F 0300 0315 0062; # (a◌ࠟ◌̕◌̀◌֮b; a◌֮◌ࠟ◌̀◌̕b; a◌֮◌ࠟ◌̀◌̕b; a◌֮◌ࠟ◌̀◌̕b; a◌֮◌ࠟ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN LONG AA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0820 0062;00E0 05AE 0820 0315 0062;0061 05AE 0300 0820 0315 0062;00E0 05AE 0820 0315 0062;0061 05AE 0300 0820 0315 0062; # (a◌̕◌̀◌֮◌ࠠb; à◌֮◌ࠠ◌̕b; a◌֮◌̀◌ࠠ◌̕b; à◌֮◌ࠠ◌̕b; a◌֮◌̀◌ࠠ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN AA, LATIN SMALL LETTER B
+0061 0820 0315 0300 05AE 0062;0061 05AE 0820 0300 0315 0062;0061 05AE 0820 0300 0315 0062;0061 05AE 0820 0300 0315 0062;0061 05AE 0820 0300 0315 0062; # (a◌ࠠ◌̕◌̀◌֮b; a◌֮◌ࠠ◌̀◌̕b; a◌֮◌ࠠ◌̀◌̕b; a◌֮◌ࠠ◌̀◌̕b; a◌֮◌ࠠ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN AA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0821 0062;00E0 05AE 0821 0315 0062;0061 05AE 0300 0821 0315 0062;00E0 05AE 0821 0315 0062;0061 05AE 0300 0821 0315 0062; # (a◌̕◌̀◌֮◌ࠡb; à◌֮◌ࠡ◌̕b; a◌֮◌̀◌ࠡ◌̕b; à◌֮◌ࠡ◌̕b; a◌֮◌̀◌ࠡ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN OVERLONG A, LATIN SMALL LETTER B
+0061 0821 0315 0300 05AE 0062;0061 05AE 0821 0300 0315 0062;0061 05AE 0821 0300 0315 0062;0061 05AE 0821 0300 0315 0062;0061 05AE 0821 0300 0315 0062; # (a◌ࠡ◌̕◌̀◌֮b; a◌֮◌ࠡ◌̀◌̕b; a◌֮◌ࠡ◌̀◌̕b; a◌֮◌ࠡ◌̀◌̕b; a◌֮◌ࠡ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN OVERLONG A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0822 0062;00E0 05AE 0822 0315 0062;0061 05AE 0300 0822 0315 0062;00E0 05AE 0822 0315 0062;0061 05AE 0300 0822 0315 0062; # (a◌̕◌̀◌֮◌ࠢb; à◌֮◌ࠢ◌̕b; a◌֮◌̀◌ࠢ◌̕b; à◌֮◌ࠢ◌̕b; a◌֮◌̀◌ࠢ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN LONG A, LATIN SMALL LETTER B
+0061 0822 0315 0300 05AE 0062;0061 05AE 0822 0300 0315 0062;0061 05AE 0822 0300 0315 0062;0061 05AE 0822 0300 0315 0062;0061 05AE 0822 0300 0315 0062; # (a◌ࠢ◌̕◌̀◌֮b; a◌֮◌ࠢ◌̀◌̕b; a◌֮◌ࠢ◌̀◌̕b; a◌֮◌ࠢ◌̀◌̕b; a◌֮◌ࠢ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN LONG A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0823 0062;00E0 05AE 0823 0315 0062;0061 05AE 0300 0823 0315 0062;00E0 05AE 0823 0315 0062;0061 05AE 0300 0823 0315 0062; # (a◌̕◌̀◌֮◌ࠣb; à◌֮◌ࠣ◌̕b; a◌֮◌̀◌ࠣ◌̕b; à◌֮◌ࠣ◌̕b; a◌֮◌̀◌ࠣ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN A, LATIN SMALL LETTER B
+0061 0823 0315 0300 05AE 0062;0061 05AE 0823 0300 0315 0062;0061 05AE 0823 0300 0315 0062;0061 05AE 0823 0300 0315 0062;0061 05AE 0823 0300 0315 0062; # (a◌ࠣ◌̕◌̀◌֮b; a◌֮◌ࠣ◌̀◌̕b; a◌֮◌ࠣ◌̀◌̕b; a◌֮◌ࠣ◌̀◌̕b; a◌֮◌ࠣ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0825 0062;00E0 05AE 0825 0315 0062;0061 05AE 0300 0825 0315 0062;00E0 05AE 0825 0315 0062;0061 05AE 0300 0825 0315 0062; # (a◌̕◌̀◌֮◌ࠥb; à◌֮◌ࠥ◌̕b; a◌֮◌̀◌ࠥ◌̕b; à◌֮◌ࠥ◌̕b; a◌֮◌̀◌ࠥ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN SHORT A, LATIN SMALL LETTER B
+0061 0825 0315 0300 05AE 0062;0061 05AE 0825 0300 0315 0062;0061 05AE 0825 0300 0315 0062;0061 05AE 0825 0300 0315 0062;0061 05AE 0825 0300 0315 0062; # (a◌ࠥ◌̕◌̀◌֮b; a◌֮◌ࠥ◌̀◌̕b; a◌֮◌ࠥ◌̀◌̕b; a◌֮◌ࠥ◌̀◌̕b; a◌֮◌ࠥ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN SHORT A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0826 0062;00E0 05AE 0826 0315 0062;0061 05AE 0300 0826 0315 0062;00E0 05AE 0826 0315 0062;0061 05AE 0300 0826 0315 0062; # (a◌̕◌̀◌֮◌ࠦb; à◌֮◌ࠦ◌̕b; a◌֮◌̀◌ࠦ◌̕b; à◌֮◌ࠦ◌̕b; a◌֮◌̀◌ࠦ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN LONG U, LATIN SMALL LETTER B
+0061 0826 0315 0300 05AE 0062;0061 05AE 0826 0300 0315 0062;0061 05AE 0826 0300 0315 0062;0061 05AE 0826 0300 0315 0062;0061 05AE 0826 0300 0315 0062; # (a◌ࠦ◌̕◌̀◌֮b; a◌֮◌ࠦ◌̀◌̕b; a◌֮◌ࠦ◌̀◌̕b; a◌֮◌ࠦ◌̀◌̕b; a◌֮◌ࠦ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN LONG U, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0827 0062;00E0 05AE 0827 0315 0062;0061 05AE 0300 0827 0315 0062;00E0 05AE 0827 0315 0062;0061 05AE 0300 0827 0315 0062; # (a◌̕◌̀◌֮◌ࠧb; à◌֮◌ࠧ◌̕b; a◌֮◌̀◌ࠧ◌̕b; à◌֮◌ࠧ◌̕b; a◌֮◌̀◌ࠧ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN U, LATIN SMALL LETTER B
+0061 0827 0315 0300 05AE 0062;0061 05AE 0827 0300 0315 0062;0061 05AE 0827 0300 0315 0062;0061 05AE 0827 0300 0315 0062;0061 05AE 0827 0300 0315 0062; # (a◌ࠧ◌̕◌̀◌֮b; a◌֮◌ࠧ◌̀◌̕b; a◌֮◌ࠧ◌̀◌̕b; a◌֮◌ࠧ◌̀◌̕b; a◌֮◌ࠧ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN U, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0829 0062;00E0 05AE 0829 0315 0062;0061 05AE 0300 0829 0315 0062;00E0 05AE 0829 0315 0062;0061 05AE 0300 0829 0315 0062; # (a◌̕◌̀◌֮◌ࠩb; à◌֮◌ࠩ◌̕b; a◌֮◌̀◌ࠩ◌̕b; à◌֮◌ࠩ◌̕b; a◌֮◌̀◌ࠩ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN LONG I, LATIN SMALL LETTER B
+0061 0829 0315 0300 05AE 0062;0061 05AE 0829 0300 0315 0062;0061 05AE 0829 0300 0315 0062;0061 05AE 0829 0300 0315 0062;0061 05AE 0829 0300 0315 0062; # (a◌ࠩ◌̕◌̀◌֮b; a◌֮◌ࠩ◌̀◌̕b; a◌֮◌ࠩ◌̀◌̕b; a◌֮◌ࠩ◌̀◌̕b; a◌֮◌ࠩ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN LONG I, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 082A 0062;00E0 05AE 082A 0315 0062;0061 05AE 0300 082A 0315 0062;00E0 05AE 082A 0315 0062;0061 05AE 0300 082A 0315 0062; # (a◌̕◌̀◌֮◌ࠪb; à◌֮◌ࠪ◌̕b; a◌֮◌̀◌ࠪ◌̕b; à◌֮◌ࠪ◌̕b; a◌֮◌̀◌ࠪ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN I, LATIN SMALL LETTER B
+0061 082A 0315 0300 05AE 0062;0061 05AE 082A 0300 0315 0062;0061 05AE 082A 0300 0315 0062;0061 05AE 082A 0300 0315 0062;0061 05AE 082A 0300 0315 0062; # (a◌ࠪ◌̕◌̀◌֮b; a◌֮◌ࠪ◌̀◌̕b; a◌֮◌ࠪ◌̀◌̕b; a◌֮◌ࠪ◌̀◌̕b; a◌֮◌ࠪ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN I, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 082B 0062;00E0 05AE 082B 0315 0062;0061 05AE 0300 082B 0315 0062;00E0 05AE 082B 0315 0062;0061 05AE 0300 082B 0315 0062; # (a◌̕◌̀◌֮◌ࠫb; à◌֮◌ࠫ◌̕b; a◌֮◌̀◌ࠫ◌̕b; à◌֮◌ࠫ◌̕b; a◌֮◌̀◌ࠫ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN O, LATIN SMALL LETTER B
+0061 082B 0315 0300 05AE 0062;0061 05AE 082B 0300 0315 0062;0061 05AE 082B 0300 0315 0062;0061 05AE 082B 0300 0315 0062;0061 05AE 082B 0300 0315 0062; # (a◌ࠫ◌̕◌̀◌֮b; a◌֮◌ࠫ◌̀◌̕b; a◌֮◌ࠫ◌̀◌̕b; a◌֮◌ࠫ◌̀◌̕b; a◌֮◌ࠫ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN O, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 082C 0062;00E0 05AE 082C 0315 0062;0061 05AE 0300 082C 0315 0062;00E0 05AE 082C 0315 0062;0061 05AE 0300 082C 0315 0062; # (a◌̕◌̀◌֮◌ࠬb; à◌֮◌ࠬ◌̕b; a◌֮◌̀◌ࠬ◌̕b; à◌֮◌ࠬ◌̕b; a◌֮◌̀◌ࠬ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN VOWEL SIGN SUKUN, LATIN SMALL LETTER B
+0061 082C 0315 0300 05AE 0062;0061 05AE 082C 0300 0315 0062;0061 05AE 082C 0300 0315 0062;0061 05AE 082C 0300 0315 0062;0061 05AE 082C 0300 0315 0062; # (a◌ࠬ◌̕◌̀◌֮b; a◌֮◌ࠬ◌̀◌̕b; a◌֮◌ࠬ◌̀◌̕b; a◌֮◌ࠬ◌̀◌̕b; a◌֮◌ࠬ◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN VOWEL SIGN SUKUN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 082D 0062;00E0 05AE 082D 0315 0062;0061 05AE 0300 082D 0315 0062;00E0 05AE 082D 0315 0062;0061 05AE 0300 082D 0315 0062; # (a◌̕◌̀◌֮◌࠭b; à◌֮◌࠭◌̕b; a◌֮◌̀◌࠭◌̕b; à◌֮◌࠭◌̕b; a◌֮◌̀◌࠭◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SAMARITAN MARK NEQUDAA, LATIN SMALL LETTER B
+0061 082D 0315 0300 05AE 0062;0061 05AE 082D 0300 0315 0062;0061 05AE 082D 0300 0315 0062;0061 05AE 082D 0300 0315 0062;0061 05AE 082D 0300 0315 0062; # (a◌࠭◌̕◌̀◌֮b; a◌֮◌࠭◌̀◌̕b; a◌֮◌࠭◌̀◌̕b; a◌֮◌࠭◌̀◌̕b; a◌֮◌࠭◌̀◌̕b; ) LATIN SMALL LETTER A, SAMARITAN MARK NEQUDAA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0859 0062;0061 1DFA 0316 0859 059A 0062;0061 1DFA 0316 0859 059A 0062;0061 1DFA 0316 0859 059A 0062;0061 1DFA 0316 0859 059A 0062; # (a◌֚◌̖◌᷺◌࡙b; a◌᷺◌̖◌࡙◌֚b; a◌᷺◌̖◌࡙◌֚b; a◌᷺◌̖◌࡙◌֚b; a◌᷺◌̖◌࡙◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MANDAIC AFFRICATION MARK, LATIN SMALL LETTER B
+0061 0859 059A 0316 1DFA 0062;0061 1DFA 0859 0316 059A 0062;0061 1DFA 0859 0316 059A 0062;0061 1DFA 0859 0316 059A 0062;0061 1DFA 0859 0316 059A 0062; # (a◌࡙◌֚◌̖◌᷺b; a◌᷺◌࡙◌̖◌֚b; a◌᷺◌࡙◌̖◌֚b; a◌᷺◌࡙◌̖◌֚b; a◌᷺◌࡙◌̖◌֚b; ) LATIN SMALL LETTER A, MANDAIC AFFRICATION MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 085A 0062;0061 1DFA 0316 085A 059A 0062;0061 1DFA 0316 085A 059A 0062;0061 1DFA 0316 085A 059A 0062;0061 1DFA 0316 085A 059A 0062; # (a◌֚◌̖◌᷺◌࡚b; a◌᷺◌̖◌࡚◌֚b; a◌᷺◌̖◌࡚◌֚b; a◌᷺◌̖◌࡚◌֚b; a◌᷺◌̖◌࡚◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MANDAIC VOCALIZATION MARK, LATIN SMALL LETTER B
+0061 085A 059A 0316 1DFA 0062;0061 1DFA 085A 0316 059A 0062;0061 1DFA 085A 0316 059A 0062;0061 1DFA 085A 0316 059A 0062;0061 1DFA 085A 0316 059A 0062; # (a◌࡚◌֚◌̖◌᷺b; a◌᷺◌࡚◌̖◌֚b; a◌᷺◌࡚◌̖◌֚b; a◌᷺◌࡚◌̖◌֚b; a◌᷺◌࡚◌̖◌֚b; ) LATIN SMALL LETTER A, MANDAIC VOCALIZATION MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 085B 0062;0061 1DFA 0316 085B 059A 0062;0061 1DFA 0316 085B 059A 0062;0061 1DFA 0316 085B 059A 0062;0061 1DFA 0316 085B 059A 0062; # (a◌֚◌̖◌᷺◌࡛b; a◌᷺◌̖◌࡛◌֚b; a◌᷺◌̖◌࡛◌֚b; a◌᷺◌̖◌࡛◌֚b; a◌᷺◌̖◌࡛◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MANDAIC GEMINATION MARK, LATIN SMALL LETTER B
+0061 085B 059A 0316 1DFA 0062;0061 1DFA 085B 0316 059A 0062;0061 1DFA 085B 0316 059A 0062;0061 1DFA 085B 0316 059A 0062;0061 1DFA 085B 0316 059A 0062; # (a◌࡛◌֚◌̖◌᷺b; a◌᷺◌࡛◌̖◌֚b; a◌᷺◌࡛◌̖◌֚b; a◌᷺◌࡛◌̖◌֚b; a◌᷺◌࡛◌̖◌֚b; ) LATIN SMALL LETTER A, MANDAIC GEMINATION MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0898 0062;00E0 05AE 0898 0315 0062;0061 05AE 0300 0898 0315 0062;00E0 05AE 0898 0315 0062;0061 05AE 0300 0898 0315 0062; # (a◌̕◌̀◌֮◌࢘b; à◌֮◌࢘◌̕b; a◌֮◌̀◌࢘◌̕b; à◌֮◌࢘◌̕b; a◌֮◌̀◌࢘◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH WORD AL-JUZ, LATIN SMALL LETTER B
+0061 0898 0315 0300 05AE 0062;0061 05AE 0898 0300 0315 0062;0061 05AE 0898 0300 0315 0062;0061 05AE 0898 0300 0315 0062;0061 05AE 0898 0300 0315 0062; # (a◌࢘◌̕◌̀◌֮b; a◌֮◌࢘◌̀◌̕b; a◌֮◌࢘◌̀◌̕b; a◌֮◌࢘◌̀◌̕b; a◌֮◌࢘◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH WORD AL-JUZ, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0899 0062;0061 1DFA 0316 0899 059A 0062;0061 1DFA 0316 0899 059A 0062;0061 1DFA 0316 0899 059A 0062;0061 1DFA 0316 0899 059A 0062; # (a◌֚◌̖◌᷺◌࢙b; a◌᷺◌̖◌࢙◌֚b; a◌᷺◌̖◌࢙◌֚b; a◌᷺◌̖◌࢙◌֚b; a◌᷺◌̖◌࢙◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SMALL LOW WORD ISHMAAM, LATIN SMALL LETTER B
+0061 0899 059A 0316 1DFA 0062;0061 1DFA 0899 0316 059A 0062;0061 1DFA 0899 0316 059A 0062;0061 1DFA 0899 0316 059A 0062;0061 1DFA 0899 0316 059A 0062; # (a◌࢙◌֚◌̖◌᷺b; a◌᷺◌࢙◌̖◌֚b; a◌᷺◌࢙◌̖◌֚b; a◌᷺◌࢙◌̖◌֚b; a◌᷺◌࢙◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC SMALL LOW WORD ISHMAAM, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 089A 0062;0061 1DFA 0316 089A 059A 0062;0061 1DFA 0316 089A 059A 0062;0061 1DFA 0316 089A 059A 0062;0061 1DFA 0316 089A 059A 0062; # (a◌֚◌̖◌᷺◌࢚b; a◌᷺◌̖◌࢚◌֚b; a◌᷺◌̖◌࢚◌֚b; a◌᷺◌̖◌࢚◌֚b; a◌᷺◌̖◌࢚◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SMALL LOW WORD IMAALA, LATIN SMALL LETTER B
+0061 089A 059A 0316 1DFA 0062;0061 1DFA 089A 0316 059A 0062;0061 1DFA 089A 0316 059A 0062;0061 1DFA 089A 0316 059A 0062;0061 1DFA 089A 0316 059A 0062; # (a◌࢚◌֚◌̖◌᷺b; a◌᷺◌࢚◌̖◌֚b; a◌᷺◌࢚◌̖◌֚b; a◌᷺◌࢚◌̖◌֚b; a◌᷺◌࢚◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC SMALL LOW WORD IMAALA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 089B 0062;0061 1DFA 0316 089B 059A 0062;0061 1DFA 0316 089B 059A 0062;0061 1DFA 0316 089B 059A 0062;0061 1DFA 0316 089B 059A 0062; # (a◌֚◌̖◌᷺◌࢛b; a◌᷺◌̖◌࢛◌֚b; a◌᷺◌̖◌࢛◌֚b; a◌᷺◌̖◌࢛◌֚b; a◌᷺◌̖◌࢛◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SMALL LOW WORD TASHEEL, LATIN SMALL LETTER B
+0061 089B 059A 0316 1DFA 0062;0061 1DFA 089B 0316 059A 0062;0061 1DFA 089B 0316 059A 0062;0061 1DFA 089B 0316 059A 0062;0061 1DFA 089B 0316 059A 0062; # (a◌࢛◌֚◌̖◌᷺b; a◌᷺◌࢛◌̖◌֚b; a◌᷺◌࢛◌̖◌֚b; a◌᷺◌࢛◌̖◌֚b; a◌᷺◌࢛◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC SMALL LOW WORD TASHEEL, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 089C 0062;00E0 05AE 089C 0315 0062;0061 05AE 0300 089C 0315 0062;00E0 05AE 089C 0315 0062;0061 05AE 0300 089C 0315 0062; # (a◌̕◌̀◌֮◌࢜b; à◌֮◌࢜◌̕b; a◌֮◌̀◌࢜◌̕b; à◌֮◌࢜◌̕b; a◌֮◌̀◌࢜◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC MADDA WAAJIB, LATIN SMALL LETTER B
+0061 089C 0315 0300 05AE 0062;0061 05AE 089C 0300 0315 0062;0061 05AE 089C 0300 0315 0062;0061 05AE 089C 0300 0315 0062;0061 05AE 089C 0300 0315 0062; # (a◌࢜◌̕◌̀◌֮b; a◌֮◌࢜◌̀◌̕b; a◌֮◌࢜◌̀◌̕b; a◌֮◌࢜◌̀◌̕b; a◌֮◌࢜◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC MADDA WAAJIB, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 089D 0062;00E0 05AE 089D 0315 0062;0061 05AE 0300 089D 0315 0062;00E0 05AE 089D 0315 0062;0061 05AE 0300 089D 0315 0062; # (a◌̕◌̀◌֮◌à¢b; à◌֮◌à¢â—ŒÌ•b; a◌֮◌̀◌à¢â—ŒÌ•b; à◌֮◌à¢â—ŒÌ•b; a◌֮◌̀◌à¢â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SUPERSCRIPT ALEF MOKHASSAS, LATIN SMALL LETTER B
+0061 089D 0315 0300 05AE 0062;0061 05AE 089D 0300 0315 0062;0061 05AE 089D 0300 0315 0062;0061 05AE 089D 0300 0315 0062;0061 05AE 089D 0300 0315 0062; # (aâ—Œà¢â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌à¢â—ŒÌ€â—ŒÌ•b; a◌֮◌à¢â—ŒÌ€â—ŒÌ•b; a◌֮◌à¢â—ŒÌ€â—ŒÌ•b; a◌֮◌à¢â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, ARABIC SUPERSCRIPT ALEF MOKHASSAS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 089E 0062;00E0 05AE 089E 0315 0062;0061 05AE 0300 089E 0315 0062;00E0 05AE 089E 0315 0062;0061 05AE 0300 089E 0315 0062; # (a◌̕◌̀◌֮◌࢞b; à◌֮◌࢞◌̕b; a◌֮◌̀◌࢞◌̕b; à◌֮◌࢞◌̕b; a◌֮◌̀◌࢞◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC DOUBLED MADDA, LATIN SMALL LETTER B
+0061 089E 0315 0300 05AE 0062;0061 05AE 089E 0300 0315 0062;0061 05AE 089E 0300 0315 0062;0061 05AE 089E 0300 0315 0062;0061 05AE 089E 0300 0315 0062; # (a◌࢞◌̕◌̀◌֮b; a◌֮◌࢞◌̀◌̕b; a◌֮◌࢞◌̀◌̕b; a◌֮◌࢞◌̀◌̕b; a◌֮◌࢞◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC DOUBLED MADDA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 089F 0062;00E0 05AE 089F 0315 0062;0061 05AE 0300 089F 0315 0062;00E0 05AE 089F 0315 0062;0061 05AE 0300 089F 0315 0062; # (a◌̕◌̀◌֮◌࢟b; à◌֮◌࢟◌̕b; a◌֮◌̀◌࢟◌̕b; à◌֮◌࢟◌̕b; a◌֮◌̀◌࢟◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC HALF MADDA OVER MADDA, LATIN SMALL LETTER B
+0061 089F 0315 0300 05AE 0062;0061 05AE 089F 0300 0315 0062;0061 05AE 089F 0300 0315 0062;0061 05AE 089F 0300 0315 0062;0061 05AE 089F 0300 0315 0062; # (a◌࢟◌̕◌̀◌֮b; a◌֮◌࢟◌̀◌̕b; a◌֮◌࢟◌̀◌̕b; a◌֮◌࢟◌̀◌̕b; a◌֮◌࢟◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC HALF MADDA OVER MADDA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08CA 0062;00E0 05AE 08CA 0315 0062;0061 05AE 0300 08CA 0315 0062;00E0 05AE 08CA 0315 0062;0061 05AE 0300 08CA 0315 0062; # (a◌̕◌̀◌֮◌࣊b; à◌֮◌࣊◌̕b; a◌֮◌̀◌࣊◌̕b; à◌֮◌࣊◌̕b; a◌֮◌̀◌࣊◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH FARSI YEH, LATIN SMALL LETTER B
+0061 08CA 0315 0300 05AE 0062;0061 05AE 08CA 0300 0315 0062;0061 05AE 08CA 0300 0315 0062;0061 05AE 08CA 0300 0315 0062;0061 05AE 08CA 0300 0315 0062; # (a◌࣊◌̕◌̀◌֮b; a◌֮◌࣊◌̀◌̕b; a◌֮◌࣊◌̀◌̕b; a◌֮◌࣊◌̀◌̕b; a◌֮◌࣊◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH FARSI YEH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08CB 0062;00E0 05AE 08CB 0315 0062;0061 05AE 0300 08CB 0315 0062;00E0 05AE 08CB 0315 0062;0061 05AE 0300 08CB 0315 0062; # (a◌̕◌̀◌֮◌࣋b; à◌֮◌࣋◌̕b; a◌֮◌̀◌࣋◌̕b; à◌֮◌࣋◌̕b; a◌֮◌̀◌࣋◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH YEH BARREE WITH TWO DOTS BELOW, LATIN SMALL LETTER B
+0061 08CB 0315 0300 05AE 0062;0061 05AE 08CB 0300 0315 0062;0061 05AE 08CB 0300 0315 0062;0061 05AE 08CB 0300 0315 0062;0061 05AE 08CB 0300 0315 0062; # (a◌࣋◌̕◌̀◌֮b; a◌֮◌࣋◌̀◌̕b; a◌֮◌࣋◌̀◌̕b; a◌֮◌࣋◌̀◌̕b; a◌֮◌࣋◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH YEH BARREE WITH TWO DOTS BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08CC 0062;00E0 05AE 08CC 0315 0062;0061 05AE 0300 08CC 0315 0062;00E0 05AE 08CC 0315 0062;0061 05AE 0300 08CC 0315 0062; # (a◌̕◌̀◌֮◌࣌b; à◌֮◌࣌◌̕b; a◌֮◌̀◌࣌◌̕b; à◌֮◌࣌◌̕b; a◌֮◌̀◌࣌◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH WORD SAH, LATIN SMALL LETTER B
+0061 08CC 0315 0300 05AE 0062;0061 05AE 08CC 0300 0315 0062;0061 05AE 08CC 0300 0315 0062;0061 05AE 08CC 0300 0315 0062;0061 05AE 08CC 0300 0315 0062; # (a◌࣌◌̕◌̀◌֮b; a◌֮◌࣌◌̀◌̕b; a◌֮◌࣌◌̀◌̕b; a◌֮◌࣌◌̀◌̕b; a◌֮◌࣌◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH WORD SAH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08CD 0062;00E0 05AE 08CD 0315 0062;0061 05AE 0300 08CD 0315 0062;00E0 05AE 08CD 0315 0062;0061 05AE 0300 08CD 0315 0062; # (a◌̕◌̀◌֮◌à£b; à◌֮◌à£â—ŒÌ•b; a◌֮◌̀◌à£â—ŒÌ•b; à◌֮◌à£â—ŒÌ•b; a◌֮◌̀◌à£â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH ZAH, LATIN SMALL LETTER B
+0061 08CD 0315 0300 05AE 0062;0061 05AE 08CD 0300 0315 0062;0061 05AE 08CD 0300 0315 0062;0061 05AE 08CD 0300 0315 0062;0061 05AE 08CD 0300 0315 0062; # (aâ—Œà£â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌à£â—ŒÌ€â—ŒÌ•b; a◌֮◌à£â—ŒÌ€â—ŒÌ•b; a◌֮◌à£â—ŒÌ€â—ŒÌ•b; a◌֮◌à£â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH ZAH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08CE 0062;00E0 05AE 08CE 0315 0062;0061 05AE 0300 08CE 0315 0062;00E0 05AE 08CE 0315 0062;0061 05AE 0300 08CE 0315 0062; # (a◌̕◌̀◌֮◌࣎b; à◌֮◌࣎◌̕b; a◌֮◌̀◌࣎◌̕b; à◌֮◌࣎◌̕b; a◌֮◌̀◌࣎◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC LARGE ROUND DOT ABOVE, LATIN SMALL LETTER B
+0061 08CE 0315 0300 05AE 0062;0061 05AE 08CE 0300 0315 0062;0061 05AE 08CE 0300 0315 0062;0061 05AE 08CE 0300 0315 0062;0061 05AE 08CE 0300 0315 0062; # (a◌࣎◌̕◌̀◌֮b; a◌֮◌࣎◌̀◌̕b; a◌֮◌࣎◌̀◌̕b; a◌֮◌࣎◌̀◌̕b; a◌֮◌࣎◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC LARGE ROUND DOT ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08CF 0062;0061 1DFA 0316 08CF 059A 0062;0061 1DFA 0316 08CF 059A 0062;0061 1DFA 0316 08CF 059A 0062;0061 1DFA 0316 08CF 059A 0062; # (a◌֚◌̖◌᷺◌à£b; a◌᷺◌̖◌à£â—ŒÖšb; a◌᷺◌̖◌à£â—ŒÖšb; a◌᷺◌̖◌à£â—ŒÖšb; a◌᷺◌̖◌à£â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC LARGE ROUND DOT BELOW, LATIN SMALL LETTER B
+0061 08CF 059A 0316 1DFA 0062;0061 1DFA 08CF 0316 059A 0062;0061 1DFA 08CF 0316 059A 0062;0061 1DFA 08CF 0316 059A 0062;0061 1DFA 08CF 0316 059A 0062; # (aâ—Œà£â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌à£â—ŒÌ–◌֚b; a◌᷺◌à£â—ŒÌ–◌֚b; a◌᷺◌à£â—ŒÌ–◌֚b; a◌᷺◌à£â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, ARABIC LARGE ROUND DOT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08D0 0062;0061 1DFA 0316 08D0 059A 0062;0061 1DFA 0316 08D0 059A 0062;0061 1DFA 0316 08D0 059A 0062;0061 1DFA 0316 08D0 059A 0062; # (a◌֚◌̖◌᷺◌à£b; a◌᷺◌̖◌à£â—ŒÖšb; a◌᷺◌̖◌à£â—ŒÖšb; a◌᷺◌̖◌à£â—ŒÖšb; a◌᷺◌̖◌à£â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SUKUN BELOW, LATIN SMALL LETTER B
+0061 08D0 059A 0316 1DFA 0062;0061 1DFA 08D0 0316 059A 0062;0061 1DFA 08D0 0316 059A 0062;0061 1DFA 08D0 0316 059A 0062;0061 1DFA 08D0 0316 059A 0062; # (aâ—Œà£â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌à£â—ŒÌ–◌֚b; a◌᷺◌à£â—ŒÌ–◌֚b; a◌᷺◌à£â—ŒÌ–◌֚b; a◌᷺◌à£â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, ARABIC SUKUN BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08D1 0062;0061 1DFA 0316 08D1 059A 0062;0061 1DFA 0316 08D1 059A 0062;0061 1DFA 0316 08D1 059A 0062;0061 1DFA 0316 08D1 059A 0062; # (a◌֚◌̖◌᷺◌࣑b; a◌᷺◌̖◌࣑◌֚b; a◌᷺◌̖◌࣑◌֚b; a◌᷺◌̖◌࣑◌֚b; a◌᷺◌̖◌࣑◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC LARGE CIRCLE BELOW, LATIN SMALL LETTER B
+0061 08D1 059A 0316 1DFA 0062;0061 1DFA 08D1 0316 059A 0062;0061 1DFA 08D1 0316 059A 0062;0061 1DFA 08D1 0316 059A 0062;0061 1DFA 08D1 0316 059A 0062; # (a◌࣑◌֚◌̖◌᷺b; a◌᷺◌࣑◌̖◌֚b; a◌᷺◌࣑◌̖◌֚b; a◌᷺◌࣑◌̖◌֚b; a◌᷺◌࣑◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC LARGE CIRCLE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08D2 0062;0061 1DFA 0316 08D2 059A 0062;0061 1DFA 0316 08D2 059A 0062;0061 1DFA 0316 08D2 059A 0062;0061 1DFA 0316 08D2 059A 0062; # (a◌֚◌̖◌᷺◌࣒b; a◌᷺◌̖◌࣒◌֚b; a◌᷺◌̖◌࣒◌֚b; a◌᷺◌̖◌࣒◌֚b; a◌᷺◌̖◌࣒◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC LARGE ROUND DOT INSIDE CIRCLE BELOW, LATIN SMALL LETTER B
+0061 08D2 059A 0316 1DFA 0062;0061 1DFA 08D2 0316 059A 0062;0061 1DFA 08D2 0316 059A 0062;0061 1DFA 08D2 0316 059A 0062;0061 1DFA 08D2 0316 059A 0062; # (a◌࣒◌֚◌̖◌᷺b; a◌᷺◌࣒◌̖◌֚b; a◌᷺◌࣒◌̖◌֚b; a◌᷺◌࣒◌̖◌֚b; a◌᷺◌࣒◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC LARGE ROUND DOT INSIDE CIRCLE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08D3 0062;0061 1DFA 0316 08D3 059A 0062;0061 1DFA 0316 08D3 059A 0062;0061 1DFA 0316 08D3 059A 0062;0061 1DFA 0316 08D3 059A 0062; # (a◌֚◌̖◌᷺◌࣓b; a◌᷺◌̖◌࣓◌֚b; a◌᷺◌̖◌࣓◌֚b; a◌᷺◌̖◌࣓◌֚b; a◌᷺◌̖◌࣓◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SMALL LOW WAW, LATIN SMALL LETTER B
+0061 08D3 059A 0316 1DFA 0062;0061 1DFA 08D3 0316 059A 0062;0061 1DFA 08D3 0316 059A 0062;0061 1DFA 08D3 0316 059A 0062;0061 1DFA 08D3 0316 059A 0062; # (a◌࣓◌֚◌̖◌᷺b; a◌᷺◌࣓◌̖◌֚b; a◌᷺◌࣓◌̖◌֚b; a◌᷺◌࣓◌̖◌֚b; a◌᷺◌࣓◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC SMALL LOW WAW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08D4 0062;00E0 05AE 08D4 0315 0062;0061 05AE 0300 08D4 0315 0062;00E0 05AE 08D4 0315 0062;0061 05AE 0300 08D4 0315 0062; # (a◌̕◌̀◌֮◌ࣔb; à◌֮◌ࣔ◌̕b; a◌֮◌̀◌ࣔ◌̕b; à◌֮◌ࣔ◌̕b; a◌֮◌̀◌ࣔ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH WORD AR-RUB, LATIN SMALL LETTER B
+0061 08D4 0315 0300 05AE 0062;0061 05AE 08D4 0300 0315 0062;0061 05AE 08D4 0300 0315 0062;0061 05AE 08D4 0300 0315 0062;0061 05AE 08D4 0300 0315 0062; # (a◌ࣔ◌̕◌̀◌֮b; a◌֮◌ࣔ◌̀◌̕b; a◌֮◌ࣔ◌̀◌̕b; a◌֮◌ࣔ◌̀◌̕b; a◌֮◌ࣔ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH WORD AR-RUB, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08D5 0062;00E0 05AE 08D5 0315 0062;0061 05AE 0300 08D5 0315 0062;00E0 05AE 08D5 0315 0062;0061 05AE 0300 08D5 0315 0062; # (a◌̕◌̀◌֮◌ࣕb; à◌֮◌ࣕ◌̕b; a◌֮◌̀◌ࣕ◌̕b; à◌֮◌ࣕ◌̕b; a◌֮◌̀◌ࣕ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH SAD, LATIN SMALL LETTER B
+0061 08D5 0315 0300 05AE 0062;0061 05AE 08D5 0300 0315 0062;0061 05AE 08D5 0300 0315 0062;0061 05AE 08D5 0300 0315 0062;0061 05AE 08D5 0300 0315 0062; # (a◌ࣕ◌̕◌̀◌֮b; a◌֮◌ࣕ◌̀◌̕b; a◌֮◌ࣕ◌̀◌̕b; a◌֮◌ࣕ◌̀◌̕b; a◌֮◌ࣕ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH SAD, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08D6 0062;00E0 05AE 08D6 0315 0062;0061 05AE 0300 08D6 0315 0062;00E0 05AE 08D6 0315 0062;0061 05AE 0300 08D6 0315 0062; # (a◌̕◌̀◌֮◌ࣖb; à◌֮◌ࣖ◌̕b; a◌֮◌̀◌ࣖ◌̕b; à◌֮◌ࣖ◌̕b; a◌֮◌̀◌ࣖ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH AIN, LATIN SMALL LETTER B
+0061 08D6 0315 0300 05AE 0062;0061 05AE 08D6 0300 0315 0062;0061 05AE 08D6 0300 0315 0062;0061 05AE 08D6 0300 0315 0062;0061 05AE 08D6 0300 0315 0062; # (a◌ࣖ◌̕◌̀◌֮b; a◌֮◌ࣖ◌̀◌̕b; a◌֮◌ࣖ◌̀◌̕b; a◌֮◌ࣖ◌̀◌̕b; a◌֮◌ࣖ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH AIN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08D7 0062;00E0 05AE 08D7 0315 0062;0061 05AE 0300 08D7 0315 0062;00E0 05AE 08D7 0315 0062;0061 05AE 0300 08D7 0315 0062; # (a◌̕◌̀◌֮◌ࣗb; à◌֮◌ࣗ◌̕b; a◌֮◌̀◌ࣗ◌̕b; à◌֮◌ࣗ◌̕b; a◌֮◌̀◌ࣗ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH QAF, LATIN SMALL LETTER B
+0061 08D7 0315 0300 05AE 0062;0061 05AE 08D7 0300 0315 0062;0061 05AE 08D7 0300 0315 0062;0061 05AE 08D7 0300 0315 0062;0061 05AE 08D7 0300 0315 0062; # (a◌ࣗ◌̕◌̀◌֮b; a◌֮◌ࣗ◌̀◌̕b; a◌֮◌ࣗ◌̀◌̕b; a◌֮◌ࣗ◌̀◌̕b; a◌֮◌ࣗ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH QAF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08D8 0062;00E0 05AE 08D8 0315 0062;0061 05AE 0300 08D8 0315 0062;00E0 05AE 08D8 0315 0062;0061 05AE 0300 08D8 0315 0062; # (a◌̕◌̀◌֮◌ࣘb; à◌֮◌ࣘ◌̕b; a◌֮◌̀◌ࣘ◌̕b; à◌֮◌ࣘ◌̕b; a◌֮◌̀◌ࣘ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH NOON WITH KASRA, LATIN SMALL LETTER B
+0061 08D8 0315 0300 05AE 0062;0061 05AE 08D8 0300 0315 0062;0061 05AE 08D8 0300 0315 0062;0061 05AE 08D8 0300 0315 0062;0061 05AE 08D8 0300 0315 0062; # (a◌ࣘ◌̕◌̀◌֮b; a◌֮◌ࣘ◌̀◌̕b; a◌֮◌ࣘ◌̀◌̕b; a◌֮◌ࣘ◌̀◌̕b; a◌֮◌ࣘ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH NOON WITH KASRA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08D9 0062;00E0 05AE 08D9 0315 0062;0061 05AE 0300 08D9 0315 0062;00E0 05AE 08D9 0315 0062;0061 05AE 0300 08D9 0315 0062; # (a◌̕◌̀◌֮◌ࣙb; à◌֮◌ࣙ◌̕b; a◌֮◌̀◌ࣙ◌̕b; à◌֮◌ࣙ◌̕b; a◌֮◌̀◌ࣙ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL LOW NOON WITH KASRA, LATIN SMALL LETTER B
+0061 08D9 0315 0300 05AE 0062;0061 05AE 08D9 0300 0315 0062;0061 05AE 08D9 0300 0315 0062;0061 05AE 08D9 0300 0315 0062;0061 05AE 08D9 0300 0315 0062; # (a◌ࣙ◌̕◌̀◌֮b; a◌֮◌ࣙ◌̀◌̕b; a◌֮◌ࣙ◌̀◌̕b; a◌֮◌ࣙ◌̀◌̕b; a◌֮◌ࣙ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL LOW NOON WITH KASRA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08DA 0062;00E0 05AE 08DA 0315 0062;0061 05AE 0300 08DA 0315 0062;00E0 05AE 08DA 0315 0062;0061 05AE 0300 08DA 0315 0062; # (a◌̕◌̀◌֮◌ࣚb; à◌֮◌ࣚ◌̕b; a◌֮◌̀◌ࣚ◌̕b; à◌֮◌ࣚ◌̕b; a◌֮◌̀◌ࣚ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH WORD ATH-THALATHA, LATIN SMALL LETTER B
+0061 08DA 0315 0300 05AE 0062;0061 05AE 08DA 0300 0315 0062;0061 05AE 08DA 0300 0315 0062;0061 05AE 08DA 0300 0315 0062;0061 05AE 08DA 0300 0315 0062; # (a◌ࣚ◌̕◌̀◌֮b; a◌֮◌ࣚ◌̀◌̕b; a◌֮◌ࣚ◌̀◌̕b; a◌֮◌ࣚ◌̀◌̕b; a◌֮◌ࣚ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH WORD ATH-THALATHA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08DB 0062;00E0 05AE 08DB 0315 0062;0061 05AE 0300 08DB 0315 0062;00E0 05AE 08DB 0315 0062;0061 05AE 0300 08DB 0315 0062; # (a◌̕◌̀◌֮◌ࣛb; à◌֮◌ࣛ◌̕b; a◌֮◌̀◌ࣛ◌̕b; à◌֮◌ࣛ◌̕b; a◌֮◌̀◌ࣛ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH WORD AS-SAJDA, LATIN SMALL LETTER B
+0061 08DB 0315 0300 05AE 0062;0061 05AE 08DB 0300 0315 0062;0061 05AE 08DB 0300 0315 0062;0061 05AE 08DB 0300 0315 0062;0061 05AE 08DB 0300 0315 0062; # (a◌ࣛ◌̕◌̀◌֮b; a◌֮◌ࣛ◌̀◌̕b; a◌֮◌ࣛ◌̀◌̕b; a◌֮◌ࣛ◌̀◌̕b; a◌֮◌ࣛ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH WORD AS-SAJDA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08DC 0062;00E0 05AE 08DC 0315 0062;0061 05AE 0300 08DC 0315 0062;00E0 05AE 08DC 0315 0062;0061 05AE 0300 08DC 0315 0062; # (a◌̕◌̀◌֮◌ࣜb; à◌֮◌ࣜ◌̕b; a◌֮◌̀◌ࣜ◌̕b; à◌֮◌ࣜ◌̕b; a◌֮◌̀◌ࣜ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH WORD AN-NISF, LATIN SMALL LETTER B
+0061 08DC 0315 0300 05AE 0062;0061 05AE 08DC 0300 0315 0062;0061 05AE 08DC 0300 0315 0062;0061 05AE 08DC 0300 0315 0062;0061 05AE 08DC 0300 0315 0062; # (a◌ࣜ◌̕◌̀◌֮b; a◌֮◌ࣜ◌̀◌̕b; a◌֮◌ࣜ◌̀◌̕b; a◌֮◌ࣜ◌̀◌̕b; a◌֮◌ࣜ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH WORD AN-NISF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08DD 0062;00E0 05AE 08DD 0315 0062;0061 05AE 0300 08DD 0315 0062;00E0 05AE 08DD 0315 0062;0061 05AE 0300 08DD 0315 0062; # (a◌̕◌̀◌֮◌à£b; à◌֮◌à£â—ŒÌ•b; a◌֮◌̀◌à£â—ŒÌ•b; à◌֮◌à£â—ŒÌ•b; a◌֮◌̀◌à£â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH WORD SAKTA, LATIN SMALL LETTER B
+0061 08DD 0315 0300 05AE 0062;0061 05AE 08DD 0300 0315 0062;0061 05AE 08DD 0300 0315 0062;0061 05AE 08DD 0300 0315 0062;0061 05AE 08DD 0300 0315 0062; # (aâ—Œà£â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌à£â—ŒÌ€â—ŒÌ•b; a◌֮◌à£â—ŒÌ€â—ŒÌ•b; a◌֮◌à£â—ŒÌ€â—ŒÌ•b; a◌֮◌à£â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH WORD SAKTA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08DE 0062;00E0 05AE 08DE 0315 0062;0061 05AE 0300 08DE 0315 0062;00E0 05AE 08DE 0315 0062;0061 05AE 0300 08DE 0315 0062; # (a◌̕◌̀◌֮◌ࣞb; à◌֮◌ࣞ◌̕b; a◌֮◌̀◌ࣞ◌̕b; à◌֮◌ࣞ◌̕b; a◌֮◌̀◌ࣞ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH WORD QIF, LATIN SMALL LETTER B
+0061 08DE 0315 0300 05AE 0062;0061 05AE 08DE 0300 0315 0062;0061 05AE 08DE 0300 0315 0062;0061 05AE 08DE 0300 0315 0062;0061 05AE 08DE 0300 0315 0062; # (a◌ࣞ◌̕◌̀◌֮b; a◌֮◌ࣞ◌̀◌̕b; a◌֮◌ࣞ◌̀◌̕b; a◌֮◌ࣞ◌̀◌̕b; a◌֮◌ࣞ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH WORD QIF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08DF 0062;00E0 05AE 08DF 0315 0062;0061 05AE 0300 08DF 0315 0062;00E0 05AE 08DF 0315 0062;0061 05AE 0300 08DF 0315 0062; # (a◌̕◌̀◌֮◌ࣟb; à◌֮◌ࣟ◌̕b; a◌֮◌̀◌ࣟ◌̕b; à◌֮◌ࣟ◌̕b; a◌֮◌̀◌ࣟ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH WORD WAQFA, LATIN SMALL LETTER B
+0061 08DF 0315 0300 05AE 0062;0061 05AE 08DF 0300 0315 0062;0061 05AE 08DF 0300 0315 0062;0061 05AE 08DF 0300 0315 0062;0061 05AE 08DF 0300 0315 0062; # (a◌ࣟ◌̕◌̀◌֮b; a◌֮◌ࣟ◌̀◌̕b; a◌֮◌ࣟ◌̀◌̕b; a◌֮◌ࣟ◌̀◌̕b; a◌֮◌ࣟ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH WORD WAQFA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08E0 0062;00E0 05AE 08E0 0315 0062;0061 05AE 0300 08E0 0315 0062;00E0 05AE 08E0 0315 0062;0061 05AE 0300 08E0 0315 0062; # (a◌̕◌̀◌֮◌࣠b; à◌֮◌࣠◌̕b; a◌֮◌̀◌࣠◌̕b; à◌֮◌࣠◌̕b; a◌֮◌̀◌࣠◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH FOOTNOTE MARKER, LATIN SMALL LETTER B
+0061 08E0 0315 0300 05AE 0062;0061 05AE 08E0 0300 0315 0062;0061 05AE 08E0 0300 0315 0062;0061 05AE 08E0 0300 0315 0062;0061 05AE 08E0 0300 0315 0062; # (a◌࣠◌̕◌̀◌֮b; a◌֮◌࣠◌̀◌̕b; a◌֮◌࣠◌̀◌̕b; a◌֮◌࣠◌̀◌̕b; a◌֮◌࣠◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH FOOTNOTE MARKER, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08E1 0062;00E0 05AE 08E1 0315 0062;0061 05AE 0300 08E1 0315 0062;00E0 05AE 08E1 0315 0062;0061 05AE 0300 08E1 0315 0062; # (a◌̕◌̀◌֮◌࣡b; à◌֮◌࣡◌̕b; a◌֮◌̀◌࣡◌̕b; à◌֮◌࣡◌̕b; a◌֮◌̀◌࣡◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH SIGN SAFHA, LATIN SMALL LETTER B
+0061 08E1 0315 0300 05AE 0062;0061 05AE 08E1 0300 0315 0062;0061 05AE 08E1 0300 0315 0062;0061 05AE 08E1 0300 0315 0062;0061 05AE 08E1 0300 0315 0062; # (a◌࣡◌̕◌̀◌֮b; a◌֮◌࣡◌̀◌̕b; a◌֮◌࣡◌̀◌̕b; a◌֮◌࣡◌̀◌̕b; a◌֮◌࣡◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH SIGN SAFHA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08E3 0062;0061 1DFA 0316 08E3 059A 0062;0061 1DFA 0316 08E3 059A 0062;0061 1DFA 0316 08E3 059A 0062;0061 1DFA 0316 08E3 059A 0062; # (a◌֚◌̖◌᷺◌ࣣb; a◌᷺◌̖◌ࣣ◌֚b; a◌᷺◌̖◌ࣣ◌֚b; a◌᷺◌̖◌ࣣ◌֚b; a◌᷺◌̖◌ࣣ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC TURNED DAMMA BELOW, LATIN SMALL LETTER B
+0061 08E3 059A 0316 1DFA 0062;0061 1DFA 08E3 0316 059A 0062;0061 1DFA 08E3 0316 059A 0062;0061 1DFA 08E3 0316 059A 0062;0061 1DFA 08E3 0316 059A 0062; # (a◌ࣣ◌֚◌̖◌᷺b; a◌᷺◌ࣣ◌̖◌֚b; a◌᷺◌ࣣ◌̖◌֚b; a◌᷺◌ࣣ◌̖◌֚b; a◌᷺◌ࣣ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC TURNED DAMMA BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08E4 0062;00E0 05AE 08E4 0315 0062;0061 05AE 0300 08E4 0315 0062;00E0 05AE 08E4 0315 0062;0061 05AE 0300 08E4 0315 0062; # (a◌̕◌̀◌֮◌ࣤb; à◌֮◌ࣤ◌̕b; a◌֮◌̀◌ࣤ◌̕b; à◌֮◌ࣤ◌̕b; a◌֮◌̀◌ࣤ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC CURLY FATHA, LATIN SMALL LETTER B
+0061 08E4 0315 0300 05AE 0062;0061 05AE 08E4 0300 0315 0062;0061 05AE 08E4 0300 0315 0062;0061 05AE 08E4 0300 0315 0062;0061 05AE 08E4 0300 0315 0062; # (a◌ࣤ◌̕◌̀◌֮b; a◌֮◌ࣤ◌̀◌̕b; a◌֮◌ࣤ◌̀◌̕b; a◌֮◌ࣤ◌̀◌̕b; a◌֮◌ࣤ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC CURLY FATHA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08E5 0062;00E0 05AE 08E5 0315 0062;0061 05AE 0300 08E5 0315 0062;00E0 05AE 08E5 0315 0062;0061 05AE 0300 08E5 0315 0062; # (a◌̕◌̀◌֮◌ࣥb; à◌֮◌ࣥ◌̕b; a◌֮◌̀◌ࣥ◌̕b; à◌֮◌ࣥ◌̕b; a◌֮◌̀◌ࣥ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC CURLY DAMMA, LATIN SMALL LETTER B
+0061 08E5 0315 0300 05AE 0062;0061 05AE 08E5 0300 0315 0062;0061 05AE 08E5 0300 0315 0062;0061 05AE 08E5 0300 0315 0062;0061 05AE 08E5 0300 0315 0062; # (a◌ࣥ◌̕◌̀◌֮b; a◌֮◌ࣥ◌̀◌̕b; a◌֮◌ࣥ◌̀◌̕b; a◌֮◌ࣥ◌̀◌̕b; a◌֮◌ࣥ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC CURLY DAMMA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08E6 0062;0061 1DFA 0316 08E6 059A 0062;0061 1DFA 0316 08E6 059A 0062;0061 1DFA 0316 08E6 059A 0062;0061 1DFA 0316 08E6 059A 0062; # (a◌֚◌̖◌᷺◌ࣦb; a◌᷺◌̖◌ࣦ◌֚b; a◌᷺◌̖◌ࣦ◌֚b; a◌᷺◌̖◌ࣦ◌֚b; a◌᷺◌̖◌ࣦ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC CURLY KASRA, LATIN SMALL LETTER B
+0061 08E6 059A 0316 1DFA 0062;0061 1DFA 08E6 0316 059A 0062;0061 1DFA 08E6 0316 059A 0062;0061 1DFA 08E6 0316 059A 0062;0061 1DFA 08E6 0316 059A 0062; # (a◌ࣦ◌֚◌̖◌᷺b; a◌᷺◌ࣦ◌̖◌֚b; a◌᷺◌ࣦ◌̖◌֚b; a◌᷺◌ࣦ◌̖◌֚b; a◌᷺◌ࣦ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC CURLY KASRA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08E7 0062;00E0 05AE 08E7 0315 0062;0061 05AE 0300 08E7 0315 0062;00E0 05AE 08E7 0315 0062;0061 05AE 0300 08E7 0315 0062; # (a◌̕◌̀◌֮◌ࣧb; à◌֮◌ࣧ◌̕b; a◌֮◌̀◌ࣧ◌̕b; à◌֮◌ࣧ◌̕b; a◌֮◌̀◌ࣧ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC CURLY FATHATAN, LATIN SMALL LETTER B
+0061 08E7 0315 0300 05AE 0062;0061 05AE 08E7 0300 0315 0062;0061 05AE 08E7 0300 0315 0062;0061 05AE 08E7 0300 0315 0062;0061 05AE 08E7 0300 0315 0062; # (a◌ࣧ◌̕◌̀◌֮b; a◌֮◌ࣧ◌̀◌̕b; a◌֮◌ࣧ◌̀◌̕b; a◌֮◌ࣧ◌̀◌̕b; a◌֮◌ࣧ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC CURLY FATHATAN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08E8 0062;00E0 05AE 08E8 0315 0062;0061 05AE 0300 08E8 0315 0062;00E0 05AE 08E8 0315 0062;0061 05AE 0300 08E8 0315 0062; # (a◌̕◌̀◌֮◌ࣨb; à◌֮◌ࣨ◌̕b; a◌֮◌̀◌ࣨ◌̕b; à◌֮◌ࣨ◌̕b; a◌֮◌̀◌ࣨ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC CURLY DAMMATAN, LATIN SMALL LETTER B
+0061 08E8 0315 0300 05AE 0062;0061 05AE 08E8 0300 0315 0062;0061 05AE 08E8 0300 0315 0062;0061 05AE 08E8 0300 0315 0062;0061 05AE 08E8 0300 0315 0062; # (a◌ࣨ◌̕◌̀◌֮b; a◌֮◌ࣨ◌̀◌̕b; a◌֮◌ࣨ◌̀◌̕b; a◌֮◌ࣨ◌̀◌̕b; a◌֮◌ࣨ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC CURLY DAMMATAN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08E9 0062;0061 1DFA 0316 08E9 059A 0062;0061 1DFA 0316 08E9 059A 0062;0061 1DFA 0316 08E9 059A 0062;0061 1DFA 0316 08E9 059A 0062; # (a◌֚◌̖◌᷺◌ࣩb; a◌᷺◌̖◌ࣩ◌֚b; a◌᷺◌̖◌ࣩ◌֚b; a◌᷺◌̖◌ࣩ◌֚b; a◌᷺◌̖◌ࣩ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC CURLY KASRATAN, LATIN SMALL LETTER B
+0061 08E9 059A 0316 1DFA 0062;0061 1DFA 08E9 0316 059A 0062;0061 1DFA 08E9 0316 059A 0062;0061 1DFA 08E9 0316 059A 0062;0061 1DFA 08E9 0316 059A 0062; # (a◌ࣩ◌֚◌̖◌᷺b; a◌᷺◌ࣩ◌̖◌֚b; a◌᷺◌ࣩ◌̖◌֚b; a◌᷺◌ࣩ◌̖◌֚b; a◌᷺◌ࣩ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC CURLY KASRATAN, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08EA 0062;00E0 05AE 08EA 0315 0062;0061 05AE 0300 08EA 0315 0062;00E0 05AE 08EA 0315 0062;0061 05AE 0300 08EA 0315 0062; # (a◌̕◌̀◌֮◌࣪b; à◌֮◌࣪◌̕b; a◌֮◌̀◌࣪◌̕b; à◌֮◌࣪◌̕b; a◌֮◌̀◌࣪◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC TONE ONE DOT ABOVE, LATIN SMALL LETTER B
+0061 08EA 0315 0300 05AE 0062;0061 05AE 08EA 0300 0315 0062;0061 05AE 08EA 0300 0315 0062;0061 05AE 08EA 0300 0315 0062;0061 05AE 08EA 0300 0315 0062; # (a◌࣪◌̕◌̀◌֮b; a◌֮◌࣪◌̀◌̕b; a◌֮◌࣪◌̀◌̕b; a◌֮◌࣪◌̀◌̕b; a◌֮◌࣪◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC TONE ONE DOT ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08EB 0062;00E0 05AE 08EB 0315 0062;0061 05AE 0300 08EB 0315 0062;00E0 05AE 08EB 0315 0062;0061 05AE 0300 08EB 0315 0062; # (a◌̕◌̀◌֮◌࣫b; à◌֮◌࣫◌̕b; a◌֮◌̀◌࣫◌̕b; à◌֮◌࣫◌̕b; a◌֮◌̀◌࣫◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC TONE TWO DOTS ABOVE, LATIN SMALL LETTER B
+0061 08EB 0315 0300 05AE 0062;0061 05AE 08EB 0300 0315 0062;0061 05AE 08EB 0300 0315 0062;0061 05AE 08EB 0300 0315 0062;0061 05AE 08EB 0300 0315 0062; # (a◌࣫◌̕◌̀◌֮b; a◌֮◌࣫◌̀◌̕b; a◌֮◌࣫◌̀◌̕b; a◌֮◌࣫◌̀◌̕b; a◌֮◌࣫◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC TONE TWO DOTS ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08EC 0062;00E0 05AE 08EC 0315 0062;0061 05AE 0300 08EC 0315 0062;00E0 05AE 08EC 0315 0062;0061 05AE 0300 08EC 0315 0062; # (a◌̕◌̀◌֮◌࣬b; à◌֮◌࣬◌̕b; a◌֮◌̀◌࣬◌̕b; à◌֮◌࣬◌̕b; a◌֮◌̀◌࣬◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC TONE LOOP ABOVE, LATIN SMALL LETTER B
+0061 08EC 0315 0300 05AE 0062;0061 05AE 08EC 0300 0315 0062;0061 05AE 08EC 0300 0315 0062;0061 05AE 08EC 0300 0315 0062;0061 05AE 08EC 0300 0315 0062; # (a◌࣬◌̕◌̀◌֮b; a◌֮◌࣬◌̀◌̕b; a◌֮◌࣬◌̀◌̕b; a◌֮◌࣬◌̀◌̕b; a◌֮◌࣬◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC TONE LOOP ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08ED 0062;0061 1DFA 0316 08ED 059A 0062;0061 1DFA 0316 08ED 059A 0062;0061 1DFA 0316 08ED 059A 0062;0061 1DFA 0316 08ED 059A 0062; # (a◌֚◌̖◌᷺◌࣭b; a◌᷺◌̖◌࣭◌֚b; a◌᷺◌̖◌࣭◌֚b; a◌᷺◌̖◌࣭◌֚b; a◌᷺◌̖◌࣭◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC TONE ONE DOT BELOW, LATIN SMALL LETTER B
+0061 08ED 059A 0316 1DFA 0062;0061 1DFA 08ED 0316 059A 0062;0061 1DFA 08ED 0316 059A 0062;0061 1DFA 08ED 0316 059A 0062;0061 1DFA 08ED 0316 059A 0062; # (a◌࣭◌֚◌̖◌᷺b; a◌᷺◌࣭◌̖◌֚b; a◌᷺◌࣭◌̖◌֚b; a◌᷺◌࣭◌̖◌֚b; a◌᷺◌࣭◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC TONE ONE DOT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08EE 0062;0061 1DFA 0316 08EE 059A 0062;0061 1DFA 0316 08EE 059A 0062;0061 1DFA 0316 08EE 059A 0062;0061 1DFA 0316 08EE 059A 0062; # (a◌֚◌̖◌᷺◌࣮b; a◌᷺◌̖◌࣮◌֚b; a◌᷺◌̖◌࣮◌֚b; a◌᷺◌̖◌࣮◌֚b; a◌᷺◌̖◌࣮◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC TONE TWO DOTS BELOW, LATIN SMALL LETTER B
+0061 08EE 059A 0316 1DFA 0062;0061 1DFA 08EE 0316 059A 0062;0061 1DFA 08EE 0316 059A 0062;0061 1DFA 08EE 0316 059A 0062;0061 1DFA 08EE 0316 059A 0062; # (a◌࣮◌֚◌̖◌᷺b; a◌᷺◌࣮◌̖◌֚b; a◌᷺◌࣮◌̖◌֚b; a◌᷺◌࣮◌̖◌֚b; a◌᷺◌࣮◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC TONE TWO DOTS BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08EF 0062;0061 1DFA 0316 08EF 059A 0062;0061 1DFA 0316 08EF 059A 0062;0061 1DFA 0316 08EF 059A 0062;0061 1DFA 0316 08EF 059A 0062; # (a◌֚◌̖◌᷺◌࣯b; a◌᷺◌̖◌࣯◌֚b; a◌᷺◌̖◌࣯◌֚b; a◌᷺◌̖◌࣯◌֚b; a◌᷺◌̖◌࣯◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC TONE LOOP BELOW, LATIN SMALL LETTER B
+0061 08EF 059A 0316 1DFA 0062;0061 1DFA 08EF 0316 059A 0062;0061 1DFA 08EF 0316 059A 0062;0061 1DFA 08EF 0316 059A 0062;0061 1DFA 08EF 0316 059A 0062; # (a◌࣯◌֚◌̖◌᷺b; a◌᷺◌࣯◌̖◌֚b; a◌᷺◌࣯◌̖◌֚b; a◌᷺◌࣯◌̖◌֚b; a◌᷺◌࣯◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC TONE LOOP BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 064C 064B FB1E 08F0 0062;0061 FB1E 064B 08F0 064C 0062;0061 FB1E 064B 08F0 064C 0062;0061 FB1E 064B 08F0 064C 0062;0061 FB1E 064B 08F0 064C 0062; # (a◌ٌ◌ً◌ﬞ◌ࣰb; a◌ﬞ◌ً◌ࣰ◌ٌb; a◌ﬞ◌ً◌ࣰ◌ٌb; a◌ﬞ◌ً◌ࣰ◌ٌb; a◌ﬞ◌ً◌ࣰ◌ٌb; ) LATIN SMALL LETTER A, ARABIC DAMMATAN, ARABIC FATHATAN, HEBREW POINT JUDEO-SPANISH VARIKA, ARABIC OPEN FATHATAN, LATIN SMALL LETTER B
+0061 08F0 064C 064B FB1E 0062;0061 FB1E 08F0 064B 064C 0062;0061 FB1E 08F0 064B 064C 0062;0061 FB1E 08F0 064B 064C 0062;0061 FB1E 08F0 064B 064C 0062; # (a◌ࣰ◌ٌ◌ً◌ﬞb; a◌ﬞ◌ࣰ◌ً◌ٌb; a◌ﬞ◌ࣰ◌ً◌ٌb; a◌ﬞ◌ࣰ◌ً◌ٌb; a◌ﬞ◌ࣰ◌ً◌ٌb; ) LATIN SMALL LETTER A, ARABIC OPEN FATHATAN, ARABIC DAMMATAN, ARABIC FATHATAN, HEBREW POINT JUDEO-SPANISH VARIKA, LATIN SMALL LETTER B
+0061 064D 064C 064B 08F1 0062;0061 064B 064C 08F1 064D 0062;0061 064B 064C 08F1 064D 0062;0061 064B 064C 08F1 064D 0062;0061 064B 064C 08F1 064D 0062; # (aâ—ŒÙ◌ٌ◌ً◌ࣱb; a◌ً◌ٌ◌ࣱ◌Ùb; a◌ً◌ٌ◌ࣱ◌Ùb; a◌ً◌ٌ◌ࣱ◌Ùb; a◌ً◌ٌ◌ࣱ◌Ùb; ) LATIN SMALL LETTER A, ARABIC KASRATAN, ARABIC DAMMATAN, ARABIC FATHATAN, ARABIC OPEN DAMMATAN, LATIN SMALL LETTER B
+0061 08F1 064D 064C 064B 0062;0061 064B 08F1 064C 064D 0062;0061 064B 08F1 064C 064D 0062;0061 064B 08F1 064C 064D 0062;0061 064B 08F1 064C 064D 0062; # (a◌ࣱ◌Ù◌ٌ◌ًb; a◌ً◌ࣱ◌ٌ◌Ùb; a◌ً◌ࣱ◌ٌ◌Ùb; a◌ً◌ࣱ◌ٌ◌Ùb; a◌ً◌ࣱ◌ٌ◌Ùb; ) LATIN SMALL LETTER A, ARABIC OPEN DAMMATAN, ARABIC KASRATAN, ARABIC DAMMATAN, ARABIC FATHATAN, LATIN SMALL LETTER B
+0061 0618 064D 064C 08F2 0062;0061 064C 064D 08F2 0618 0062;0061 064C 064D 08F2 0618 0062;0061 064C 064D 08F2 0618 0062;0061 064C 064D 08F2 0618 0062; # (a◌ؘ◌Ù◌ٌ◌ࣲb; a◌ٌ◌Ù◌ࣲ◌ؘb; a◌ٌ◌Ù◌ࣲ◌ؘb; a◌ٌ◌Ù◌ࣲ◌ؘb; a◌ٌ◌Ù◌ࣲ◌ؘb; ) LATIN SMALL LETTER A, ARABIC SMALL FATHA, ARABIC KASRATAN, ARABIC DAMMATAN, ARABIC OPEN KASRATAN, LATIN SMALL LETTER B
+0061 08F2 0618 064D 064C 0062;0061 064C 08F2 064D 0618 0062;0061 064C 08F2 064D 0618 0062;0061 064C 08F2 064D 0618 0062;0061 064C 08F2 064D 0618 0062; # (a◌ࣲ◌ؘ◌Ù◌ٌb; a◌ٌ◌ࣲ◌Ù◌ؘb; a◌ٌ◌ࣲ◌Ù◌ؘb; a◌ٌ◌ࣲ◌Ù◌ؘb; a◌ٌ◌ࣲ◌Ù◌ؘb; ) LATIN SMALL LETTER A, ARABIC OPEN KASRATAN, ARABIC SMALL FATHA, ARABIC KASRATAN, ARABIC DAMMATAN, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08F3 0062;00E0 05AE 08F3 0315 0062;0061 05AE 0300 08F3 0315 0062;00E0 05AE 08F3 0315 0062;0061 05AE 0300 08F3 0315 0062; # (a◌̕◌̀◌֮◌ࣳb; à◌֮◌ࣳ◌̕b; a◌֮◌̀◌ࣳ◌̕b; à◌֮◌ࣳ◌̕b; a◌֮◌̀◌ࣳ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC SMALL HIGH WAW, LATIN SMALL LETTER B
+0061 08F3 0315 0300 05AE 0062;0061 05AE 08F3 0300 0315 0062;0061 05AE 08F3 0300 0315 0062;0061 05AE 08F3 0300 0315 0062;0061 05AE 08F3 0300 0315 0062; # (a◌ࣳ◌̕◌̀◌֮b; a◌֮◌ࣳ◌̀◌̕b; a◌֮◌ࣳ◌̀◌̕b; a◌֮◌ࣳ◌̀◌̕b; a◌֮◌ࣳ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC SMALL HIGH WAW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08F4 0062;00E0 05AE 08F4 0315 0062;0061 05AE 0300 08F4 0315 0062;00E0 05AE 08F4 0315 0062;0061 05AE 0300 08F4 0315 0062; # (a◌̕◌̀◌֮◌ࣴb; à◌֮◌ࣴ◌̕b; a◌֮◌̀◌ࣴ◌̕b; à◌֮◌ࣴ◌̕b; a◌֮◌̀◌ࣴ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC FATHA WITH RING, LATIN SMALL LETTER B
+0061 08F4 0315 0300 05AE 0062;0061 05AE 08F4 0300 0315 0062;0061 05AE 08F4 0300 0315 0062;0061 05AE 08F4 0300 0315 0062;0061 05AE 08F4 0300 0315 0062; # (a◌ࣴ◌̕◌̀◌֮b; a◌֮◌ࣴ◌̀◌̕b; a◌֮◌ࣴ◌̀◌̕b; a◌֮◌ࣴ◌̀◌̕b; a◌֮◌ࣴ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC FATHA WITH RING, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08F5 0062;00E0 05AE 08F5 0315 0062;0061 05AE 0300 08F5 0315 0062;00E0 05AE 08F5 0315 0062;0061 05AE 0300 08F5 0315 0062; # (a◌̕◌̀◌֮◌ࣵb; à◌֮◌ࣵ◌̕b; a◌֮◌̀◌ࣵ◌̕b; à◌֮◌ࣵ◌̕b; a◌֮◌̀◌ࣵ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC FATHA WITH DOT ABOVE, LATIN SMALL LETTER B
+0061 08F5 0315 0300 05AE 0062;0061 05AE 08F5 0300 0315 0062;0061 05AE 08F5 0300 0315 0062;0061 05AE 08F5 0300 0315 0062;0061 05AE 08F5 0300 0315 0062; # (a◌ࣵ◌̕◌̀◌֮b; a◌֮◌ࣵ◌̀◌̕b; a◌֮◌ࣵ◌̀◌̕b; a◌֮◌ࣵ◌̀◌̕b; a◌֮◌ࣵ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC FATHA WITH DOT ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08F6 0062;0061 1DFA 0316 08F6 059A 0062;0061 1DFA 0316 08F6 059A 0062;0061 1DFA 0316 08F6 059A 0062;0061 1DFA 0316 08F6 059A 0062; # (a◌֚◌̖◌᷺◌ࣶb; a◌᷺◌̖◌ࣶ◌֚b; a◌᷺◌̖◌ࣶ◌֚b; a◌᷺◌̖◌ࣶ◌֚b; a◌᷺◌̖◌ࣶ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC KASRA WITH DOT BELOW, LATIN SMALL LETTER B
+0061 08F6 059A 0316 1DFA 0062;0061 1DFA 08F6 0316 059A 0062;0061 1DFA 08F6 0316 059A 0062;0061 1DFA 08F6 0316 059A 0062;0061 1DFA 08F6 0316 059A 0062; # (a◌ࣶ◌֚◌̖◌᷺b; a◌᷺◌ࣶ◌̖◌֚b; a◌᷺◌ࣶ◌̖◌֚b; a◌᷺◌ࣶ◌̖◌֚b; a◌᷺◌ࣶ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC KASRA WITH DOT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08F7 0062;00E0 05AE 08F7 0315 0062;0061 05AE 0300 08F7 0315 0062;00E0 05AE 08F7 0315 0062;0061 05AE 0300 08F7 0315 0062; # (a◌̕◌̀◌֮◌ࣷb; à◌֮◌ࣷ◌̕b; a◌֮◌̀◌ࣷ◌̕b; à◌֮◌ࣷ◌̕b; a◌֮◌̀◌ࣷ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC LEFT ARROWHEAD ABOVE, LATIN SMALL LETTER B
+0061 08F7 0315 0300 05AE 0062;0061 05AE 08F7 0300 0315 0062;0061 05AE 08F7 0300 0315 0062;0061 05AE 08F7 0300 0315 0062;0061 05AE 08F7 0300 0315 0062; # (a◌ࣷ◌̕◌̀◌֮b; a◌֮◌ࣷ◌̀◌̕b; a◌֮◌ࣷ◌̀◌̕b; a◌֮◌ࣷ◌̀◌̕b; a◌֮◌ࣷ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC LEFT ARROWHEAD ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08F8 0062;00E0 05AE 08F8 0315 0062;0061 05AE 0300 08F8 0315 0062;00E0 05AE 08F8 0315 0062;0061 05AE 0300 08F8 0315 0062; # (a◌̕◌̀◌֮◌ࣸb; à◌֮◌ࣸ◌̕b; a◌֮◌̀◌ࣸ◌̕b; à◌֮◌ࣸ◌̕b; a◌֮◌̀◌ࣸ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC RIGHT ARROWHEAD ABOVE, LATIN SMALL LETTER B
+0061 08F8 0315 0300 05AE 0062;0061 05AE 08F8 0300 0315 0062;0061 05AE 08F8 0300 0315 0062;0061 05AE 08F8 0300 0315 0062;0061 05AE 08F8 0300 0315 0062; # (a◌ࣸ◌̕◌̀◌֮b; a◌֮◌ࣸ◌̀◌̕b; a◌֮◌ࣸ◌̀◌̕b; a◌֮◌ࣸ◌̀◌̕b; a◌֮◌ࣸ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC RIGHT ARROWHEAD ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08F9 0062;0061 1DFA 0316 08F9 059A 0062;0061 1DFA 0316 08F9 059A 0062;0061 1DFA 0316 08F9 059A 0062;0061 1DFA 0316 08F9 059A 0062; # (a◌֚◌̖◌᷺◌ࣹb; a◌᷺◌̖◌ࣹ◌֚b; a◌᷺◌̖◌ࣹ◌֚b; a◌᷺◌̖◌ࣹ◌֚b; a◌᷺◌̖◌ࣹ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC LEFT ARROWHEAD BELOW, LATIN SMALL LETTER B
+0061 08F9 059A 0316 1DFA 0062;0061 1DFA 08F9 0316 059A 0062;0061 1DFA 08F9 0316 059A 0062;0061 1DFA 08F9 0316 059A 0062;0061 1DFA 08F9 0316 059A 0062; # (a◌ࣹ◌֚◌̖◌᷺b; a◌᷺◌ࣹ◌̖◌֚b; a◌᷺◌ࣹ◌̖◌֚b; a◌᷺◌ࣹ◌̖◌֚b; a◌᷺◌ࣹ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC LEFT ARROWHEAD BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 08FA 0062;0061 1DFA 0316 08FA 059A 0062;0061 1DFA 0316 08FA 059A 0062;0061 1DFA 0316 08FA 059A 0062;0061 1DFA 0316 08FA 059A 0062; # (a◌֚◌̖◌᷺◌ࣺb; a◌᷺◌̖◌ࣺ◌֚b; a◌᷺◌̖◌ࣺ◌֚b; a◌᷺◌̖◌ࣺ◌֚b; a◌᷺◌̖◌ࣺ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC RIGHT ARROWHEAD BELOW, LATIN SMALL LETTER B
+0061 08FA 059A 0316 1DFA 0062;0061 1DFA 08FA 0316 059A 0062;0061 1DFA 08FA 0316 059A 0062;0061 1DFA 08FA 0316 059A 0062;0061 1DFA 08FA 0316 059A 0062; # (a◌ࣺ◌֚◌̖◌᷺b; a◌᷺◌ࣺ◌̖◌֚b; a◌᷺◌ࣺ◌̖◌֚b; a◌᷺◌ࣺ◌̖◌֚b; a◌᷺◌ࣺ◌̖◌֚b; ) LATIN SMALL LETTER A, ARABIC RIGHT ARROWHEAD BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08FB 0062;00E0 05AE 08FB 0315 0062;0061 05AE 0300 08FB 0315 0062;00E0 05AE 08FB 0315 0062;0061 05AE 0300 08FB 0315 0062; # (a◌̕◌̀◌֮◌ࣻb; à◌֮◌ࣻ◌̕b; a◌֮◌̀◌ࣻ◌̕b; à◌֮◌ࣻ◌̕b; a◌֮◌̀◌ࣻ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC DOUBLE RIGHT ARROWHEAD ABOVE, LATIN SMALL LETTER B
+0061 08FB 0315 0300 05AE 0062;0061 05AE 08FB 0300 0315 0062;0061 05AE 08FB 0300 0315 0062;0061 05AE 08FB 0300 0315 0062;0061 05AE 08FB 0300 0315 0062; # (a◌ࣻ◌̕◌̀◌֮b; a◌֮◌ࣻ◌̀◌̕b; a◌֮◌ࣻ◌̀◌̕b; a◌֮◌ࣻ◌̀◌̕b; a◌֮◌ࣻ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC DOUBLE RIGHT ARROWHEAD ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08FC 0062;00E0 05AE 08FC 0315 0062;0061 05AE 0300 08FC 0315 0062;00E0 05AE 08FC 0315 0062;0061 05AE 0300 08FC 0315 0062; # (a◌̕◌̀◌֮◌ࣼb; à◌֮◌ࣼ◌̕b; a◌֮◌̀◌ࣼ◌̕b; à◌֮◌ࣼ◌̕b; a◌֮◌̀◌ࣼ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC DOUBLE RIGHT ARROWHEAD ABOVE WITH DOT, LATIN SMALL LETTER B
+0061 08FC 0315 0300 05AE 0062;0061 05AE 08FC 0300 0315 0062;0061 05AE 08FC 0300 0315 0062;0061 05AE 08FC 0300 0315 0062;0061 05AE 08FC 0300 0315 0062; # (a◌ࣼ◌̕◌̀◌֮b; a◌֮◌ࣼ◌̀◌̕b; a◌֮◌ࣼ◌̀◌̕b; a◌֮◌ࣼ◌̀◌̕b; a◌֮◌ࣼ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC DOUBLE RIGHT ARROWHEAD ABOVE WITH DOT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08FD 0062;00E0 05AE 08FD 0315 0062;0061 05AE 0300 08FD 0315 0062;00E0 05AE 08FD 0315 0062;0061 05AE 0300 08FD 0315 0062; # (a◌̕◌̀◌֮◌ࣽb; à◌֮◌ࣽ◌̕b; a◌֮◌̀◌ࣽ◌̕b; à◌֮◌ࣽ◌̕b; a◌֮◌̀◌ࣽ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC RIGHT ARROWHEAD ABOVE WITH DOT, LATIN SMALL LETTER B
+0061 08FD 0315 0300 05AE 0062;0061 05AE 08FD 0300 0315 0062;0061 05AE 08FD 0300 0315 0062;0061 05AE 08FD 0300 0315 0062;0061 05AE 08FD 0300 0315 0062; # (a◌ࣽ◌̕◌̀◌֮b; a◌֮◌ࣽ◌̀◌̕b; a◌֮◌ࣽ◌̀◌̕b; a◌֮◌ࣽ◌̀◌̕b; a◌֮◌ࣽ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC RIGHT ARROWHEAD ABOVE WITH DOT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08FE 0062;00E0 05AE 08FE 0315 0062;0061 05AE 0300 08FE 0315 0062;00E0 05AE 08FE 0315 0062;0061 05AE 0300 08FE 0315 0062; # (a◌̕◌̀◌֮◌ࣾb; à◌֮◌ࣾ◌̕b; a◌֮◌̀◌ࣾ◌̕b; à◌֮◌ࣾ◌̕b; a◌֮◌̀◌ࣾ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC DAMMA WITH DOT, LATIN SMALL LETTER B
+0061 08FE 0315 0300 05AE 0062;0061 05AE 08FE 0300 0315 0062;0061 05AE 08FE 0300 0315 0062;0061 05AE 08FE 0300 0315 0062;0061 05AE 08FE 0300 0315 0062; # (a◌ࣾ◌̕◌̀◌֮b; a◌֮◌ࣾ◌̀◌̕b; a◌֮◌ࣾ◌̀◌̕b; a◌֮◌ࣾ◌̀◌̕b; a◌֮◌ࣾ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC DAMMA WITH DOT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 08FF 0062;00E0 05AE 08FF 0315 0062;0061 05AE 0300 08FF 0315 0062;00E0 05AE 08FF 0315 0062;0061 05AE 0300 08FF 0315 0062; # (a◌̕◌̀◌֮◌ࣿb; à◌֮◌ࣿ◌̕b; a◌֮◌̀◌ࣿ◌̕b; à◌֮◌ࣿ◌̕b; a◌֮◌̀◌ࣿ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ARABIC MARK SIDEWAYS NOON GHUNNA, LATIN SMALL LETTER B
+0061 08FF 0315 0300 05AE 0062;0061 05AE 08FF 0300 0315 0062;0061 05AE 08FF 0300 0315 0062;0061 05AE 08FF 0300 0315 0062;0061 05AE 08FF 0300 0315 0062; # (a◌ࣿ◌̕◌̀◌֮b; a◌֮◌ࣿ◌̀◌̕b; a◌֮◌ࣿ◌̀◌̕b; a◌֮◌ࣿ◌̀◌̕b; a◌֮◌ࣿ◌̀◌̕b; ) LATIN SMALL LETTER A, ARABIC MARK SIDEWAYS NOON GHUNNA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 093C 0062;0061 16FF0 093C 093C 3099 0062;0061 16FF0 093C 093C 3099 0062;0061 16FF0 093C 093C 3099 0062;0061 16FF0 093C 093C 3099 0062; # (a◌゙◌𖿰़◌़b; a𖿰◌़◌़◌゙b; a𖿰◌़◌़◌゙b; a𖿰◌़◌़◌゙b; a𖿰◌़◌़◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, DEVANAGARI SIGN NUKTA, LATIN SMALL LETTER B
+0061 093C 3099 093C 16FF0 0062;0061 16FF0 093C 093C 3099 0062;0061 16FF0 093C 093C 3099 0062;0061 16FF0 093C 093C 3099 0062;0061 16FF0 093C 093C 3099 0062; # (a◌़◌゙◌𖿰़b; a𖿰◌़◌़◌゙b; a𖿰◌़◌़◌゙b; a𖿰◌़◌़◌゙b; a𖿰◌़◌़◌゙b; ) LATIN SMALL LETTER A, DEVANAGARI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 094D 0062;0061 3099 094D 094D 05B0 0062;0061 3099 094D 094D 05B0 0062;0061 3099 094D 094D 05B0 0062;0061 3099 094D 094D 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà¥b; a◌゙◌à¥â—Œà¥â—ŒÖ°b; a◌゙◌à¥â—Œà¥â—ŒÖ°b; a◌゙◌à¥â—Œà¥â—ŒÖ°b; a◌゙◌à¥â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 094D 05B0 094D 3099 0062;0061 3099 094D 094D 05B0 0062;0061 3099 094D 094D 05B0 0062;0061 3099 094D 094D 05B0 0062;0061 3099 094D 094D 05B0 0062; # (aâ—Œà¥â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌à¥â—Œà¥â—ŒÖ°b; a◌゙◌à¥â—Œà¥â—ŒÖ°b; a◌゙◌à¥â—Œà¥â—ŒÖ°b; a◌゙◌à¥â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, DEVANAGARI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0951 0062;00E0 05AE 0951 0315 0062;0061 05AE 0300 0951 0315 0062;00E0 05AE 0951 0315 0062;0061 05AE 0300 0951 0315 0062; # (a◌̕◌̀◌֮◌॑b; à◌֮◌॑◌̕b; a◌֮◌̀◌॑◌̕b; à◌֮◌॑◌̕b; a◌֮◌̀◌॑◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, DEVANAGARI STRESS SIGN UDATTA, LATIN SMALL LETTER B
+0061 0951 0315 0300 05AE 0062;0061 05AE 0951 0300 0315 0062;0061 05AE 0951 0300 0315 0062;0061 05AE 0951 0300 0315 0062;0061 05AE 0951 0300 0315 0062; # (a◌॑◌̕◌̀◌֮b; a◌֮◌॑◌̀◌̕b; a◌֮◌॑◌̀◌̕b; a◌֮◌॑◌̀◌̕b; a◌֮◌॑◌̀◌̕b; ) LATIN SMALL LETTER A, DEVANAGARI STRESS SIGN UDATTA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0952 0062;0061 1DFA 0316 0952 059A 0062;0061 1DFA 0316 0952 059A 0062;0061 1DFA 0316 0952 059A 0062;0061 1DFA 0316 0952 059A 0062; # (a◌֚◌̖◌᷺◌॒b; a◌᷺◌̖◌॒◌֚b; a◌᷺◌̖◌॒◌֚b; a◌᷺◌̖◌॒◌֚b; a◌᷺◌̖◌॒◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, DEVANAGARI STRESS SIGN ANUDATTA, LATIN SMALL LETTER B
+0061 0952 059A 0316 1DFA 0062;0061 1DFA 0952 0316 059A 0062;0061 1DFA 0952 0316 059A 0062;0061 1DFA 0952 0316 059A 0062;0061 1DFA 0952 0316 059A 0062; # (a◌॒◌֚◌̖◌᷺b; a◌᷺◌॒◌̖◌֚b; a◌᷺◌॒◌̖◌֚b; a◌᷺◌॒◌̖◌֚b; a◌᷺◌॒◌̖◌֚b; ) LATIN SMALL LETTER A, DEVANAGARI STRESS SIGN ANUDATTA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0953 0062;00E0 05AE 0953 0315 0062;0061 05AE 0300 0953 0315 0062;00E0 05AE 0953 0315 0062;0061 05AE 0300 0953 0315 0062; # (a◌̕◌̀◌֮◌॓b; à◌֮◌॓◌̕b; a◌֮◌̀◌॓◌̕b; à◌֮◌॓◌̕b; a◌֮◌̀◌॓◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, DEVANAGARI GRAVE ACCENT, LATIN SMALL LETTER B
+0061 0953 0315 0300 05AE 0062;0061 05AE 0953 0300 0315 0062;0061 05AE 0953 0300 0315 0062;0061 05AE 0953 0300 0315 0062;0061 05AE 0953 0300 0315 0062; # (a◌॓◌̕◌̀◌֮b; a◌֮◌॓◌̀◌̕b; a◌֮◌॓◌̀◌̕b; a◌֮◌॓◌̀◌̕b; a◌֮◌॓◌̀◌̕b; ) LATIN SMALL LETTER A, DEVANAGARI GRAVE ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0954 0062;00E0 05AE 0954 0315 0062;0061 05AE 0300 0954 0315 0062;00E0 05AE 0954 0315 0062;0061 05AE 0300 0954 0315 0062; # (a◌̕◌̀◌֮◌॔b; à◌֮◌॔◌̕b; a◌֮◌̀◌॔◌̕b; à◌֮◌॔◌̕b; a◌֮◌̀◌॔◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, DEVANAGARI ACUTE ACCENT, LATIN SMALL LETTER B
+0061 0954 0315 0300 05AE 0062;0061 05AE 0954 0300 0315 0062;0061 05AE 0954 0300 0315 0062;0061 05AE 0954 0300 0315 0062;0061 05AE 0954 0300 0315 0062; # (a◌॔◌̕◌̀◌֮b; a◌֮◌॔◌̀◌̕b; a◌֮◌॔◌̀◌̕b; a◌֮◌॔◌̀◌̕b; a◌֮◌॔◌̀◌̕b; ) LATIN SMALL LETTER A, DEVANAGARI ACUTE ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 09BC 0062;0061 16FF0 093C 09BC 3099 0062;0061 16FF0 093C 09BC 3099 0062;0061 16FF0 093C 09BC 3099 0062;0061 16FF0 093C 09BC 3099 0062; # (a◌゙◌𖿰़◌়b; a𖿰◌़◌়◌゙b; a𖿰◌़◌়◌゙b; a𖿰◌़◌়◌゙b; a𖿰◌़◌়◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, BENGALI SIGN NUKTA, LATIN SMALL LETTER B
+0061 09BC 3099 093C 16FF0 0062;0061 16FF0 09BC 093C 3099 0062;0061 16FF0 09BC 093C 3099 0062;0061 16FF0 09BC 093C 3099 0062;0061 16FF0 09BC 093C 3099 0062; # (a◌়◌゙◌𖿰़b; a𖿰◌়◌़◌゙b; a𖿰◌়◌़◌゙b; a𖿰◌়◌़◌゙b; a𖿰◌়◌़◌゙b; ) LATIN SMALL LETTER A, BENGALI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 09CD 0062;0061 3099 094D 09CD 05B0 0062;0061 3099 094D 09CD 05B0 0062;0061 3099 094D 09CD 05B0 0062;0061 3099 094D 09CD 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà§b; a◌゙◌à¥â—Œà§â—ŒÖ°b; a◌゙◌à¥â—Œà§â—ŒÖ°b; a◌゙◌à¥â—Œà§â—ŒÖ°b; a◌゙◌à¥â—Œà§â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, BENGALI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 09CD 05B0 094D 3099 0062;0061 3099 09CD 094D 05B0 0062;0061 3099 09CD 094D 05B0 0062;0061 3099 09CD 094D 05B0 0062;0061 3099 09CD 094D 05B0 0062; # (aâ—Œà§â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌à§â—Œà¥â—ŒÖ°b; a◌゙◌à§â—Œà¥â—ŒÖ°b; a◌゙◌à§â—Œà¥â—ŒÖ°b; a◌゙◌à§â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, BENGALI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE 09FE 0062;00E0 05AE 09FE 0315 0062;0061 05AE 0300 09FE 0315 0062;00E0 05AE 09FE 0315 0062;0061 05AE 0300 09FE 0315 0062; # (a◌̕◌̀◌֮◌৾b; à◌֮◌৾◌̕b; a◌֮◌̀◌৾◌̕b; à◌֮◌৾◌̕b; a◌֮◌̀◌৾◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BENGALI SANDHI MARK, LATIN SMALL LETTER B
+0061 09FE 0315 0300 05AE 0062;0061 05AE 09FE 0300 0315 0062;0061 05AE 09FE 0300 0315 0062;0061 05AE 09FE 0300 0315 0062;0061 05AE 09FE 0300 0315 0062; # (a◌৾◌̕◌̀◌֮b; a◌֮◌৾◌̀◌̕b; a◌֮◌৾◌̀◌̕b; a◌֮◌৾◌̀◌̕b; a◌֮◌৾◌̀◌̕b; ) LATIN SMALL LETTER A, BENGALI SANDHI MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 0A3C 0062;0061 16FF0 093C 0A3C 3099 0062;0061 16FF0 093C 0A3C 3099 0062;0061 16FF0 093C 0A3C 3099 0062;0061 16FF0 093C 0A3C 3099 0062; # (a◌゙◌𖿰़◌਼b; a𖿰◌़◌਼◌゙b; a𖿰◌़◌਼◌゙b; a𖿰◌़◌਼◌゙b; a𖿰◌़◌਼◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, GURMUKHI SIGN NUKTA, LATIN SMALL LETTER B
+0061 0A3C 3099 093C 16FF0 0062;0061 16FF0 0A3C 093C 3099 0062;0061 16FF0 0A3C 093C 3099 0062;0061 16FF0 0A3C 093C 3099 0062;0061 16FF0 0A3C 093C 3099 0062; # (a◌਼◌゙◌𖿰़b; a𖿰◌਼◌़◌゙b; a𖿰◌਼◌़◌゙b; a𖿰◌਼◌़◌゙b; a𖿰◌਼◌़◌゙b; ) LATIN SMALL LETTER A, GURMUKHI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0A4D 0062;0061 3099 094D 0A4D 05B0 0062;0061 3099 094D 0A4D 05B0 0062;0061 3099 094D 0A4D 05B0 0062;0061 3099 094D 0A4D 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà©b; a◌゙◌à¥â—Œà©â—ŒÖ°b; a◌゙◌à¥â—Œà©â—ŒÖ°b; a◌゙◌à¥â—Œà©â—ŒÖ°b; a◌゙◌à¥â—Œà©â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, GURMUKHI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 0A4D 05B0 094D 3099 0062;0061 3099 0A4D 094D 05B0 0062;0061 3099 0A4D 094D 05B0 0062;0061 3099 0A4D 094D 05B0 0062;0061 3099 0A4D 094D 05B0 0062; # (aâ—Œà©â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌à©â—Œà¥â—ŒÖ°b; a◌゙◌à©â—Œà¥â—ŒÖ°b; a◌゙◌à©â—Œà¥â—ŒÖ°b; a◌゙◌à©â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, GURMUKHI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 0ABC 0062;0061 16FF0 093C 0ABC 3099 0062;0061 16FF0 093C 0ABC 3099 0062;0061 16FF0 093C 0ABC 3099 0062;0061 16FF0 093C 0ABC 3099 0062; # (a◌゙◌𖿰़◌઼b; a𖿰◌़◌઼◌゙b; a𖿰◌़◌઼◌゙b; a𖿰◌़◌઼◌゙b; a𖿰◌़◌઼◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, GUJARATI SIGN NUKTA, LATIN SMALL LETTER B
+0061 0ABC 3099 093C 16FF0 0062;0061 16FF0 0ABC 093C 3099 0062;0061 16FF0 0ABC 093C 3099 0062;0061 16FF0 0ABC 093C 3099 0062;0061 16FF0 0ABC 093C 3099 0062; # (a◌઼◌゙◌𖿰़b; a𖿰◌઼◌़◌゙b; a𖿰◌઼◌़◌゙b; a𖿰◌઼◌़◌゙b; a𖿰◌઼◌़◌゙b; ) LATIN SMALL LETTER A, GUJARATI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0ACD 0062;0061 3099 094D 0ACD 05B0 0062;0061 3099 094D 0ACD 05B0 0062;0061 3099 094D 0ACD 05B0 0062;0061 3099 094D 0ACD 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà«b; a◌゙◌à¥â—Œà«â—ŒÖ°b; a◌゙◌à¥â—Œà«â—ŒÖ°b; a◌゙◌à¥â—Œà«â—ŒÖ°b; a◌゙◌à¥â—Œà«â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, GUJARATI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 0ACD 05B0 094D 3099 0062;0061 3099 0ACD 094D 05B0 0062;0061 3099 0ACD 094D 05B0 0062;0061 3099 0ACD 094D 05B0 0062;0061 3099 0ACD 094D 05B0 0062; # (aâ—Œà«â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌à«â—Œà¥â—ŒÖ°b; a◌゙◌à«â—Œà¥â—ŒÖ°b; a◌゙◌à«â—Œà¥â—ŒÖ°b; a◌゙◌à«â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, GUJARATI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 0B3C 0062;0061 16FF0 093C 0B3C 3099 0062;0061 16FF0 093C 0B3C 3099 0062;0061 16FF0 093C 0B3C 3099 0062;0061 16FF0 093C 0B3C 3099 0062; # (a◌゙◌𖿰़◌଼b; a𖿰◌़◌଼◌゙b; a𖿰◌़◌଼◌゙b; a𖿰◌़◌଼◌゙b; a𖿰◌़◌଼◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, ORIYA SIGN NUKTA, LATIN SMALL LETTER B
+0061 0B3C 3099 093C 16FF0 0062;0061 16FF0 0B3C 093C 3099 0062;0061 16FF0 0B3C 093C 3099 0062;0061 16FF0 0B3C 093C 3099 0062;0061 16FF0 0B3C 093C 3099 0062; # (a◌଼◌゙◌𖿰़b; a𖿰◌଼◌़◌゙b; a𖿰◌଼◌़◌゙b; a𖿰◌଼◌़◌゙b; a𖿰◌଼◌़◌゙b; ) LATIN SMALL LETTER A, ORIYA SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0B4D 0062;0061 3099 094D 0B4D 05B0 0062;0061 3099 094D 0B4D 05B0 0062;0061 3099 094D 0B4D 05B0 0062;0061 3099 094D 0B4D 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà­b; a◌゙◌à¥â—Œà­â—ŒÖ°b; a◌゙◌à¥â—Œà­â—ŒÖ°b; a◌゙◌à¥â—Œà­â—ŒÖ°b; a◌゙◌à¥â—Œà­â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, ORIYA SIGN VIRAMA, LATIN SMALL LETTER B
+0061 0B4D 05B0 094D 3099 0062;0061 3099 0B4D 094D 05B0 0062;0061 3099 0B4D 094D 05B0 0062;0061 3099 0B4D 094D 05B0 0062;0061 3099 0B4D 094D 05B0 0062; # (aâ—Œà­â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌à­â—Œà¥â—ŒÖ°b; a◌゙◌à­â—Œà¥â—ŒÖ°b; a◌゙◌à­â—Œà¥â—ŒÖ°b; a◌゙◌à­â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, ORIYA SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0BCD 0062;0061 3099 094D 0BCD 05B0 0062;0061 3099 094D 0BCD 05B0 0062;0061 3099 094D 0BCD 05B0 0062;0061 3099 094D 0BCD 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà¯b; a◌゙◌à¥â—Œà¯â—ŒÖ°b; a◌゙◌à¥â—Œà¯â—ŒÖ°b; a◌゙◌à¥â—Œà¯â—ŒÖ°b; a◌゙◌à¥â—Œà¯â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, TAMIL SIGN VIRAMA, LATIN SMALL LETTER B
+0061 0BCD 05B0 094D 3099 0062;0061 3099 0BCD 094D 05B0 0062;0061 3099 0BCD 094D 05B0 0062;0061 3099 0BCD 094D 05B0 0062;0061 3099 0BCD 094D 05B0 0062; # (aâ—Œà¯â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌à¯â—Œà¥â—ŒÖ°b; a◌゙◌à¯â—Œà¥â—ŒÖ°b; a◌゙◌à¯â—Œà¥â—ŒÖ°b; a◌゙◌à¯â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, TAMIL SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 0C3C 0062;0061 16FF0 093C 0C3C 3099 0062;0061 16FF0 093C 0C3C 3099 0062;0061 16FF0 093C 0C3C 3099 0062;0061 16FF0 093C 0C3C 3099 0062; # (a◌゙◌𖿰़◌఼b; a𖿰◌़◌఼◌゙b; a𖿰◌़◌఼◌゙b; a𖿰◌़◌఼◌゙b; a𖿰◌़◌఼◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, TELUGU SIGN NUKTA, LATIN SMALL LETTER B
+0061 0C3C 3099 093C 16FF0 0062;0061 16FF0 0C3C 093C 3099 0062;0061 16FF0 0C3C 093C 3099 0062;0061 16FF0 0C3C 093C 3099 0062;0061 16FF0 0C3C 093C 3099 0062; # (a◌఼◌゙◌𖿰़b; a𖿰◌఼◌़◌゙b; a𖿰◌఼◌़◌゙b; a𖿰◌఼◌़◌゙b; a𖿰◌఼◌़◌゙b; ) LATIN SMALL LETTER A, TELUGU SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0C4D 0062;0061 3099 094D 0C4D 05B0 0062;0061 3099 094D 0C4D 05B0 0062;0061 3099 094D 0C4D 05B0 0062;0061 3099 094D 0C4D 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà±b; a◌゙◌à¥â—Œà±â—ŒÖ°b; a◌゙◌à¥â—Œà±â—ŒÖ°b; a◌゙◌à¥â—Œà±â—ŒÖ°b; a◌゙◌à¥â—Œà±â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, TELUGU SIGN VIRAMA, LATIN SMALL LETTER B
+0061 0C4D 05B0 094D 3099 0062;0061 3099 0C4D 094D 05B0 0062;0061 3099 0C4D 094D 05B0 0062;0061 3099 0C4D 094D 05B0 0062;0061 3099 0C4D 094D 05B0 0062; # (aâ—Œà±â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌à±â—Œà¥â—ŒÖ°b; a◌゙◌à±â—Œà¥â—ŒÖ°b; a◌゙◌à±â—Œà¥â—ŒÖ°b; a◌゙◌à±â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, TELUGU SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0C56 0C55 0711 0C55 0062;0061 0711 0C55 0C55 0C56 0062;0061 0711 0C55 0C55 0C56 0062;0061 0711 0C55 0C55 0C56 0062;0061 0711 0C55 0C55 0C56 0062; # (a◌ౖ◌ౕ◌ܑ◌ౕb; a◌ܑ◌ౕ◌ౕ◌ౖb; a◌ܑ◌ౕ◌ౕ◌ౖb; a◌ܑ◌ౕ◌ౕ◌ౖb; a◌ܑ◌ౕ◌ౕ◌ౖb; ) LATIN SMALL LETTER A, TELUGU AI LENGTH MARK, TELUGU LENGTH MARK, SYRIAC LETTER SUPERSCRIPT ALAPH, TELUGU LENGTH MARK, LATIN SMALL LETTER B
+0061 0C55 0C56 0C55 0711 0062;0061 0711 0C55 0C55 0C56 0062;0061 0711 0C55 0C55 0C56 0062;0061 0711 0C55 0C55 0C56 0062;0061 0711 0C55 0C55 0C56 0062; # (a◌ౕ◌ౖ◌ౕ◌ܑb; a◌ܑ◌ౕ◌ౕ◌ౖb; a◌ܑ◌ౕ◌ౕ◌ౖb; a◌ܑ◌ౕ◌ౕ◌ౖb; a◌ܑ◌ౕ◌ౕ◌ౖb; ) LATIN SMALL LETTER A, TELUGU LENGTH MARK, TELUGU AI LENGTH MARK, TELUGU LENGTH MARK, SYRIAC LETTER SUPERSCRIPT ALAPH, LATIN SMALL LETTER B
+0061 0E38 0C56 0C55 0C56 0062;0061 0C55 0C56 0C56 0E38 0062;0061 0C55 0C56 0C56 0E38 0062;0061 0C55 0C56 0C56 0E38 0062;0061 0C55 0C56 0C56 0E38 0062; # (a◌ุ◌ౖ◌ౕ◌ౖb; a◌ౕ◌ౖ◌ౖ◌ุb; a◌ౕ◌ౖ◌ౖ◌ุb; a◌ౕ◌ౖ◌ౖ◌ุb; a◌ౕ◌ౖ◌ౖ◌ุb; ) LATIN SMALL LETTER A, THAI CHARACTER SARA U, TELUGU AI LENGTH MARK, TELUGU LENGTH MARK, TELUGU AI LENGTH MARK, LATIN SMALL LETTER B
+0061 0C56 0E38 0C56 0C55 0062;0061 0C55 0C56 0C56 0E38 0062;0061 0C55 0C56 0C56 0E38 0062;0061 0C55 0C56 0C56 0E38 0062;0061 0C55 0C56 0C56 0E38 0062; # (a◌ౖ◌ุ◌ౖ◌ౕb; a◌ౕ◌ౖ◌ౖ◌ุb; a◌ౕ◌ౖ◌ౖ◌ุb; a◌ౕ◌ౖ◌ౖ◌ุb; a◌ౕ◌ౖ◌ౖ◌ุb; ) LATIN SMALL LETTER A, TELUGU AI LENGTH MARK, THAI CHARACTER SARA U, TELUGU AI LENGTH MARK, TELUGU LENGTH MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 0CBC 0062;0061 16FF0 093C 0CBC 3099 0062;0061 16FF0 093C 0CBC 3099 0062;0061 16FF0 093C 0CBC 3099 0062;0061 16FF0 093C 0CBC 3099 0062; # (a◌゙◌𖿰़◌಼b; a𖿰◌़◌಼◌゙b; a𖿰◌़◌಼◌゙b; a𖿰◌़◌಼◌゙b; a𖿰◌़◌಼◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, KANNADA SIGN NUKTA, LATIN SMALL LETTER B
+0061 0CBC 3099 093C 16FF0 0062;0061 16FF0 0CBC 093C 3099 0062;0061 16FF0 0CBC 093C 3099 0062;0061 16FF0 0CBC 093C 3099 0062;0061 16FF0 0CBC 093C 3099 0062; # (a◌಼◌゙◌𖿰़b; a𖿰◌಼◌़◌゙b; a𖿰◌಼◌़◌゙b; a𖿰◌಼◌़◌゙b; a𖿰◌಼◌़◌゙b; ) LATIN SMALL LETTER A, KANNADA SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0CCD 0062;0061 3099 094D 0CCD 05B0 0062;0061 3099 094D 0CCD 05B0 0062;0061 3099 094D 0CCD 05B0 0062;0061 3099 094D 0CCD 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà³b; a◌゙◌à¥â—Œà³â—ŒÖ°b; a◌゙◌à¥â—Œà³â—ŒÖ°b; a◌゙◌à¥â—Œà³â—ŒÖ°b; a◌゙◌à¥â—Œà³â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, KANNADA SIGN VIRAMA, LATIN SMALL LETTER B
+0061 0CCD 05B0 094D 3099 0062;0061 3099 0CCD 094D 05B0 0062;0061 3099 0CCD 094D 05B0 0062;0061 3099 0CCD 094D 05B0 0062;0061 3099 0CCD 094D 05B0 0062; # (aâ—Œà³â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌à³â—Œà¥â—ŒÖ°b; a◌゙◌à³â—Œà¥â—ŒÖ°b; a◌゙◌à³â—Œà¥â—ŒÖ°b; a◌゙◌à³â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, KANNADA SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0D3B 0062;0061 3099 094D 0D3B 05B0 0062;0061 3099 094D 0D3B 05B0 0062;0061 3099 094D 0D3B 05B0 0062;0061 3099 094D 0D3B 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà´»b; a◌゙◌à¥â—Œà´»â—ŒÖ°b; a◌゙◌à¥â—Œà´»â—ŒÖ°b; a◌゙◌à¥â—Œà´»â—ŒÖ°b; a◌゙◌à¥â—Œà´»â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, MALAYALAM SIGN VERTICAL BAR VIRAMA, LATIN SMALL LETTER B
+0061 0D3B 05B0 094D 3099 0062;0061 3099 0D3B 094D 05B0 0062;0061 3099 0D3B 094D 05B0 0062;0061 3099 0D3B 094D 05B0 0062;0061 3099 0D3B 094D 05B0 0062; # (a◌഻◌ְ◌à¥â—Œã‚™b; a◌゙◌഻◌à¥â—ŒÖ°b; a◌゙◌഻◌à¥â—ŒÖ°b; a◌゙◌഻◌à¥â—ŒÖ°b; a◌゙◌഻◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MALAYALAM SIGN VERTICAL BAR VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0D3C 0062;0061 3099 094D 0D3C 05B0 0062;0061 3099 094D 0D3C 05B0 0062;0061 3099 094D 0D3C 05B0 0062;0061 3099 094D 0D3C 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà´¼b; a◌゙◌à¥â—Œà´¼â—ŒÖ°b; a◌゙◌à¥â—Œà´¼â—ŒÖ°b; a◌゙◌à¥â—Œà´¼â—ŒÖ°b; a◌゙◌à¥â—Œà´¼â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, MALAYALAM SIGN CIRCULAR VIRAMA, LATIN SMALL LETTER B
+0061 0D3C 05B0 094D 3099 0062;0061 3099 0D3C 094D 05B0 0062;0061 3099 0D3C 094D 05B0 0062;0061 3099 0D3C 094D 05B0 0062;0061 3099 0D3C 094D 05B0 0062; # (a◌഼◌ְ◌à¥â—Œã‚™b; a◌゙◌഼◌à¥â—ŒÖ°b; a◌゙◌഼◌à¥â—ŒÖ°b; a◌゙◌഼◌à¥â—ŒÖ°b; a◌゙◌഼◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MALAYALAM SIGN CIRCULAR VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0D4D 0062;0061 3099 094D 0D4D 05B0 0062;0061 3099 094D 0D4D 05B0 0062;0061 3099 094D 0D4D 05B0 0062;0061 3099 094D 0D4D 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œàµb; a◌゙◌à¥â—Œàµâ—ŒÖ°b; a◌゙◌à¥â—Œàµâ—ŒÖ°b; a◌゙◌à¥â—Œàµâ—ŒÖ°b; a◌゙◌à¥â—Œàµâ—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, MALAYALAM SIGN VIRAMA, LATIN SMALL LETTER B
+0061 0D4D 05B0 094D 3099 0062;0061 3099 0D4D 094D 05B0 0062;0061 3099 0D4D 094D 05B0 0062;0061 3099 0D4D 094D 05B0 0062;0061 3099 0D4D 094D 05B0 0062; # (aâ—Œàµâ—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌àµâ—Œà¥â—ŒÖ°b; a◌゙◌àµâ—Œà¥â—ŒÖ°b; a◌゙◌àµâ—Œà¥â—ŒÖ°b; a◌゙◌àµâ—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MALAYALAM SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0DCA 0062;0061 3099 094D 0DCA 05B0 0062;0061 3099 094D 0DCA 05B0 0062;0061 3099 094D 0DCA 05B0 0062;0061 3099 094D 0DCA 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà·Šb; a◌゙◌à¥â—Œà·Šâ—ŒÖ°b; a◌゙◌à¥â—Œà·Šâ—ŒÖ°b; a◌゙◌à¥â—Œà·Šâ—ŒÖ°b; a◌゙◌à¥â—Œà·Šâ—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, SINHALA SIGN AL-LAKUNA, LATIN SMALL LETTER B
+0061 0DCA 05B0 094D 3099 0062;0061 3099 0DCA 094D 05B0 0062;0061 3099 0DCA 094D 05B0 0062;0061 3099 0DCA 094D 05B0 0062;0061 3099 0DCA 094D 05B0 0062; # (a◌්◌ְ◌à¥â—Œã‚™b; a◌゙◌්◌à¥â—ŒÖ°b; a◌゙◌්◌à¥â—ŒÖ°b; a◌゙◌්◌à¥â—ŒÖ°b; a◌゙◌්◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, SINHALA SIGN AL-LAKUNA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0E48 0E38 0C56 0E38 0062;0061 0C56 0E38 0E38 0E48 0062;0061 0C56 0E38 0E38 0E48 0062;0061 0C56 0E38 0E38 0E48 0062;0061 0C56 0E38 0E38 0E48 0062; # (a◌่◌ุ◌ౖ◌ุb; a◌ౖ◌ุ◌ุ◌่b; a◌ౖ◌ุ◌ุ◌่b; a◌ౖ◌ุ◌ุ◌่b; a◌ౖ◌ุ◌ุ◌่b; ) LATIN SMALL LETTER A, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, TELUGU AI LENGTH MARK, THAI CHARACTER SARA U, LATIN SMALL LETTER B
+0061 0E38 0E48 0E38 0C56 0062;0061 0C56 0E38 0E38 0E48 0062;0061 0C56 0E38 0E38 0E48 0062;0061 0C56 0E38 0E38 0E48 0062;0061 0C56 0E38 0E38 0E48 0062; # (a◌ุ◌่◌ุ◌ౖb; a◌ౖ◌ุ◌ุ◌่b; a◌ౖ◌ุ◌ุ◌่b; a◌ౖ◌ุ◌ุ◌่b; a◌ౖ◌ุ◌ุ◌่b; ) LATIN SMALL LETTER A, THAI CHARACTER SARA U, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, TELUGU AI LENGTH MARK, LATIN SMALL LETTER B
+0061 0E48 0E38 0C56 0E39 0062;0061 0C56 0E38 0E39 0E48 0062;0061 0C56 0E38 0E39 0E48 0062;0061 0C56 0E38 0E39 0E48 0062;0061 0C56 0E38 0E39 0E48 0062; # (a◌่◌ุ◌ౖ◌ูb; a◌ౖ◌ุ◌ู◌่b; a◌ౖ◌ุ◌ู◌่b; a◌ౖ◌ุ◌ู◌่b; a◌ౖ◌ุ◌ู◌่b; ) LATIN SMALL LETTER A, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, TELUGU AI LENGTH MARK, THAI CHARACTER SARA UU, LATIN SMALL LETTER B
+0061 0E39 0E48 0E38 0C56 0062;0061 0C56 0E39 0E38 0E48 0062;0061 0C56 0E39 0E38 0E48 0062;0061 0C56 0E39 0E38 0E48 0062;0061 0C56 0E39 0E38 0E48 0062; # (a◌ู◌่◌ุ◌ౖb; a◌ౖ◌ู◌ุ◌่b; a◌ౖ◌ู◌ุ◌่b; a◌ౖ◌ู◌ุ◌่b; a◌ౖ◌ู◌ุ◌่b; ) LATIN SMALL LETTER A, THAI CHARACTER SARA UU, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, TELUGU AI LENGTH MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0E3A 0062;0061 3099 094D 0E3A 05B0 0062;0061 3099 094D 0E3A 05B0 0062;0061 3099 094D 0E3A 05B0 0062;0061 3099 094D 0E3A 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà¸ºb; a◌゙◌à¥â—Œà¸ºâ—ŒÖ°b; a◌゙◌à¥â—Œà¸ºâ—ŒÖ°b; a◌゙◌à¥â—Œà¸ºâ—ŒÖ°b; a◌゙◌à¥â—Œà¸ºâ—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, THAI CHARACTER PHINTHU, LATIN SMALL LETTER B
+0061 0E3A 05B0 094D 3099 0062;0061 3099 0E3A 094D 05B0 0062;0061 3099 0E3A 094D 05B0 0062;0061 3099 0E3A 094D 05B0 0062;0061 3099 0E3A 094D 05B0 0062; # (a◌ฺ◌ְ◌à¥â—Œã‚™b; a◌゙◌ฺ◌à¥â—ŒÖ°b; a◌゙◌ฺ◌à¥â—ŒÖ°b; a◌゙◌ฺ◌à¥â—ŒÖ°b; a◌゙◌ฺ◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, THAI CHARACTER PHINTHU, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0EB8 0E48 0E38 0E48 0062;0061 0E38 0E48 0E48 0EB8 0062;0061 0E38 0E48 0E48 0EB8 0062;0061 0E38 0E48 0E48 0EB8 0062;0061 0E38 0E48 0E48 0EB8 0062; # (a◌ຸ◌่◌ุ◌่b; a◌ุ◌่◌่◌ຸb; a◌ุ◌่◌่◌ຸb; a◌ุ◌่◌่◌ຸb; a◌ุ◌่◌่◌ຸb; ) LATIN SMALL LETTER A, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, THAI CHARACTER MAI EK, LATIN SMALL LETTER B
+0061 0E48 0EB8 0E48 0E38 0062;0061 0E38 0E48 0E48 0EB8 0062;0061 0E38 0E48 0E48 0EB8 0062;0061 0E38 0E48 0E48 0EB8 0062;0061 0E38 0E48 0E48 0EB8 0062; # (a◌่◌ຸ◌่◌ุb; a◌ุ◌่◌่◌ຸb; a◌ุ◌่◌่◌ຸb; a◌ุ◌่◌่◌ຸb; a◌ุ◌่◌่◌ຸb; ) LATIN SMALL LETTER A, THAI CHARACTER MAI EK, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, LATIN SMALL LETTER B
+0061 0EB8 0E48 0E38 0E49 0062;0061 0E38 0E48 0E49 0EB8 0062;0061 0E38 0E48 0E49 0EB8 0062;0061 0E38 0E48 0E49 0EB8 0062;0061 0E38 0E48 0E49 0EB8 0062; # (a◌ຸ◌่◌ุ◌้b; a◌ุ◌่◌้◌ຸb; a◌ุ◌่◌้◌ຸb; a◌ุ◌่◌้◌ຸb; a◌ุ◌่◌้◌ຸb; ) LATIN SMALL LETTER A, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, THAI CHARACTER MAI THO, LATIN SMALL LETTER B
+0061 0E49 0EB8 0E48 0E38 0062;0061 0E38 0E49 0E48 0EB8 0062;0061 0E38 0E49 0E48 0EB8 0062;0061 0E38 0E49 0E48 0EB8 0062;0061 0E38 0E49 0E48 0EB8 0062; # (a◌้◌ຸ◌่◌ุb; a◌ุ◌้◌่◌ຸb; a◌ุ◌้◌่◌ຸb; a◌ุ◌้◌่◌ຸb; a◌ุ◌้◌่◌ຸb; ) LATIN SMALL LETTER A, THAI CHARACTER MAI THO, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, LATIN SMALL LETTER B
+0061 0EB8 0E48 0E38 0E4A 0062;0061 0E38 0E48 0E4A 0EB8 0062;0061 0E38 0E48 0E4A 0EB8 0062;0061 0E38 0E48 0E4A 0EB8 0062;0061 0E38 0E48 0E4A 0EB8 0062; # (a◌ຸ◌่◌ุ◌๊b; a◌ุ◌่◌๊◌ຸb; a◌ุ◌่◌๊◌ຸb; a◌ุ◌่◌๊◌ຸb; a◌ุ◌่◌๊◌ຸb; ) LATIN SMALL LETTER A, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, THAI CHARACTER MAI TRI, LATIN SMALL LETTER B
+0061 0E4A 0EB8 0E48 0E38 0062;0061 0E38 0E4A 0E48 0EB8 0062;0061 0E38 0E4A 0E48 0EB8 0062;0061 0E38 0E4A 0E48 0EB8 0062;0061 0E38 0E4A 0E48 0EB8 0062; # (a◌๊◌ຸ◌่◌ุb; a◌ุ◌๊◌่◌ຸb; a◌ุ◌๊◌่◌ຸb; a◌ุ◌๊◌่◌ຸb; a◌ุ◌๊◌่◌ຸb; ) LATIN SMALL LETTER A, THAI CHARACTER MAI TRI, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, LATIN SMALL LETTER B
+0061 0EB8 0E48 0E38 0E4B 0062;0061 0E38 0E48 0E4B 0EB8 0062;0061 0E38 0E48 0E4B 0EB8 0062;0061 0E38 0E48 0E4B 0EB8 0062;0061 0E38 0E48 0E4B 0EB8 0062; # (a◌ຸ◌่◌ุ◌๋b; a◌ุ◌่◌๋◌ຸb; a◌ุ◌่◌๋◌ຸb; a◌ุ◌่◌๋◌ຸb; a◌ุ◌่◌๋◌ຸb; ) LATIN SMALL LETTER A, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, THAI CHARACTER MAI CHATTAWA, LATIN SMALL LETTER B
+0061 0E4B 0EB8 0E48 0E38 0062;0061 0E38 0E4B 0E48 0EB8 0062;0061 0E38 0E4B 0E48 0EB8 0062;0061 0E38 0E4B 0E48 0EB8 0062;0061 0E38 0E4B 0E48 0EB8 0062; # (a◌๋◌ຸ◌่◌ุb; a◌ุ◌๋◌่◌ຸb; a◌ุ◌๋◌่◌ຸb; a◌ุ◌๋◌่◌ຸb; a◌ุ◌๋◌่◌ຸb; ) LATIN SMALL LETTER A, THAI CHARACTER MAI CHATTAWA, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, THAI CHARACTER SARA U, LATIN SMALL LETTER B
+0061 0EC8 0EB8 0E48 0EB8 0062;0061 0E48 0EB8 0EB8 0EC8 0062;0061 0E48 0EB8 0EB8 0EC8 0062;0061 0E48 0EB8 0EB8 0EC8 0062;0061 0E48 0EB8 0EB8 0EC8 0062; # (a◌່◌ຸ◌่◌ຸb; a◌่◌ຸ◌ຸ◌່b; a◌่◌ຸ◌ຸ◌່b; a◌่◌ຸ◌ຸ◌່b; a◌่◌ຸ◌ຸ◌່b; ) LATIN SMALL LETTER A, LAO TONE MAI EK, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, LAO VOWEL SIGN U, LATIN SMALL LETTER B
+0061 0EB8 0EC8 0EB8 0E48 0062;0061 0E48 0EB8 0EB8 0EC8 0062;0061 0E48 0EB8 0EB8 0EC8 0062;0061 0E48 0EB8 0EB8 0EC8 0062;0061 0E48 0EB8 0EB8 0EC8 0062; # (a◌ຸ◌່◌ຸ◌่b; a◌่◌ຸ◌ຸ◌່b; a◌่◌ຸ◌ຸ◌່b; a◌่◌ຸ◌ຸ◌່b; a◌่◌ຸ◌ຸ◌່b; ) LATIN SMALL LETTER A, LAO VOWEL SIGN U, LAO TONE MAI EK, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, LATIN SMALL LETTER B
+0061 0EC8 0EB8 0E48 0EB9 0062;0061 0E48 0EB8 0EB9 0EC8 0062;0061 0E48 0EB8 0EB9 0EC8 0062;0061 0E48 0EB8 0EB9 0EC8 0062;0061 0E48 0EB8 0EB9 0EC8 0062; # (a◌່◌ຸ◌่◌ູb; a◌่◌ຸ◌ູ◌່b; a◌่◌ຸ◌ູ◌່b; a◌่◌ຸ◌ູ◌່b; a◌่◌ຸ◌ູ◌່b; ) LATIN SMALL LETTER A, LAO TONE MAI EK, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, LAO VOWEL SIGN UU, LATIN SMALL LETTER B
+0061 0EB9 0EC8 0EB8 0E48 0062;0061 0E48 0EB9 0EB8 0EC8 0062;0061 0E48 0EB9 0EB8 0EC8 0062;0061 0E48 0EB9 0EB8 0EC8 0062;0061 0E48 0EB9 0EB8 0EC8 0062; # (a◌ູ◌່◌ຸ◌่b; a◌่◌ູ◌ຸ◌່b; a◌่◌ູ◌ຸ◌່b; a◌่◌ູ◌ຸ◌່b; a◌่◌ູ◌ຸ◌່b; ) LATIN SMALL LETTER A, LAO VOWEL SIGN UU, LAO TONE MAI EK, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0EBA 0062;0061 3099 094D 0EBA 05B0 0062;0061 3099 094D 0EBA 05B0 0062;0061 3099 094D 0EBA 05B0 0062;0061 3099 094D 0EBA 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œàººb; a◌゙◌à¥â—Œàººâ—ŒÖ°b; a◌゙◌à¥â—Œàººâ—ŒÖ°b; a◌゙◌à¥â—Œàººâ—ŒÖ°b; a◌゙◌à¥â—Œàººâ—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LAO SIGN PALI VIRAMA, LATIN SMALL LETTER B
+0061 0EBA 05B0 094D 3099 0062;0061 3099 0EBA 094D 05B0 0062;0061 3099 0EBA 094D 05B0 0062;0061 3099 0EBA 094D 05B0 0062;0061 3099 0EBA 094D 05B0 0062; # (a◌຺◌ְ◌à¥â—Œã‚™b; a◌゙◌຺◌à¥â—ŒÖ°b; a◌゙◌຺◌à¥â—ŒÖ°b; a◌゙◌຺◌à¥â—ŒÖ°b; a◌゙◌຺◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, LAO SIGN PALI VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0F71 0EC8 0EB8 0EC8 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062; # (a◌ཱ◌່◌ຸ◌່b; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LAO TONE MAI EK, LATIN SMALL LETTER B
+0061 0EC8 0F71 0EC8 0EB8 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062; # (a◌່◌ཱ◌່◌ຸb; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; ) LATIN SMALL LETTER A, LAO TONE MAI EK, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LATIN SMALL LETTER B
+0061 0F71 0EC8 0EB8 0EC9 0062;0061 0EB8 0EC8 0EC9 0F71 0062;0061 0EB8 0EC8 0EC9 0F71 0062;0061 0EB8 0EC8 0EC9 0F71 0062;0061 0EB8 0EC8 0EC9 0F71 0062; # (a◌ཱ◌່◌ຸ◌້b; a◌ຸ◌່◌້◌ཱb; a◌ຸ◌່◌້◌ཱb; a◌ຸ◌່◌້◌ཱb; a◌ຸ◌່◌້◌ཱb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LAO TONE MAI THO, LATIN SMALL LETTER B
+0061 0EC9 0F71 0EC8 0EB8 0062;0061 0EB8 0EC9 0EC8 0F71 0062;0061 0EB8 0EC9 0EC8 0F71 0062;0061 0EB8 0EC9 0EC8 0F71 0062;0061 0EB8 0EC9 0EC8 0F71 0062; # (a◌້◌ཱ◌່◌ຸb; a◌ຸ◌້◌່◌ཱb; a◌ຸ◌້◌່◌ཱb; a◌ຸ◌້◌່◌ཱb; a◌ຸ◌້◌່◌ཱb; ) LATIN SMALL LETTER A, LAO TONE MAI THO, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LATIN SMALL LETTER B
+0061 0F71 0EC8 0EB8 0ECA 0062;0061 0EB8 0EC8 0ECA 0F71 0062;0061 0EB8 0EC8 0ECA 0F71 0062;0061 0EB8 0EC8 0ECA 0F71 0062;0061 0EB8 0EC8 0ECA 0F71 0062; # (a◌ཱ◌່◌ຸ◌໊b; a◌ຸ◌່◌໊◌ཱb; a◌ຸ◌່◌໊◌ཱb; a◌ຸ◌່◌໊◌ཱb; a◌ຸ◌່◌໊◌ཱb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LAO TONE MAI TI, LATIN SMALL LETTER B
+0061 0ECA 0F71 0EC8 0EB8 0062;0061 0EB8 0ECA 0EC8 0F71 0062;0061 0EB8 0ECA 0EC8 0F71 0062;0061 0EB8 0ECA 0EC8 0F71 0062;0061 0EB8 0ECA 0EC8 0F71 0062; # (a◌໊◌ཱ◌່◌ຸb; a◌ຸ◌໊◌່◌ཱb; a◌ຸ◌໊◌່◌ཱb; a◌ຸ◌໊◌່◌ཱb; a◌ຸ◌໊◌່◌ཱb; ) LATIN SMALL LETTER A, LAO TONE MAI TI, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LATIN SMALL LETTER B
+0061 0F71 0EC8 0EB8 0ECB 0062;0061 0EB8 0EC8 0ECB 0F71 0062;0061 0EB8 0EC8 0ECB 0F71 0062;0061 0EB8 0EC8 0ECB 0F71 0062;0061 0EB8 0EC8 0ECB 0F71 0062; # (a◌ཱ◌່◌ຸ◌໋b; a◌ຸ◌່◌໋◌ཱb; a◌ຸ◌່◌໋◌ཱb; a◌ຸ◌່◌໋◌ཱb; a◌ຸ◌່◌໋◌ཱb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LAO TONE MAI CATAWA, LATIN SMALL LETTER B
+0061 0ECB 0F71 0EC8 0EB8 0062;0061 0EB8 0ECB 0EC8 0F71 0062;0061 0EB8 0ECB 0EC8 0F71 0062;0061 0EB8 0ECB 0EC8 0F71 0062;0061 0EB8 0ECB 0EC8 0F71 0062; # (a◌໋◌ཱ◌່◌ຸb; a◌ຸ◌໋◌່◌ཱb; a◌ຸ◌໋◌່◌ཱb; a◌ຸ◌໋◌່◌ཱb; a◌ຸ◌໋◌່◌ཱb; ) LATIN SMALL LETTER A, LAO TONE MAI CATAWA, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0F18 0062;0061 1DFA 0316 0F18 059A 0062;0061 1DFA 0316 0F18 059A 0062;0061 1DFA 0316 0F18 059A 0062;0061 1DFA 0316 0F18 059A 0062; # (a◌֚◌̖◌᷺◌༘b; a◌᷺◌̖◌༘◌֚b; a◌᷺◌̖◌༘◌֚b; a◌᷺◌̖◌༘◌֚b; a◌᷺◌̖◌༘◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, TIBETAN ASTROLOGICAL SIGN -KHYUD PA, LATIN SMALL LETTER B
+0061 0F18 059A 0316 1DFA 0062;0061 1DFA 0F18 0316 059A 0062;0061 1DFA 0F18 0316 059A 0062;0061 1DFA 0F18 0316 059A 0062;0061 1DFA 0F18 0316 059A 0062; # (a◌༘◌֚◌̖◌᷺b; a◌᷺◌༘◌̖◌֚b; a◌᷺◌༘◌̖◌֚b; a◌᷺◌༘◌̖◌֚b; a◌᷺◌༘◌̖◌֚b; ) LATIN SMALL LETTER A, TIBETAN ASTROLOGICAL SIGN -KHYUD PA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0F19 0062;0061 1DFA 0316 0F19 059A 0062;0061 1DFA 0316 0F19 059A 0062;0061 1DFA 0316 0F19 059A 0062;0061 1DFA 0316 0F19 059A 0062; # (a◌֚◌̖◌᷺◌༙b; a◌᷺◌̖◌༙◌֚b; a◌᷺◌̖◌༙◌֚b; a◌᷺◌̖◌༙◌֚b; a◌᷺◌̖◌༙◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS, LATIN SMALL LETTER B
+0061 0F19 059A 0316 1DFA 0062;0061 1DFA 0F19 0316 059A 0062;0061 1DFA 0F19 0316 059A 0062;0061 1DFA 0F19 0316 059A 0062;0061 1DFA 0F19 0316 059A 0062; # (a◌༙◌֚◌̖◌᷺b; a◌᷺◌༙◌̖◌֚b; a◌᷺◌༙◌̖◌֚b; a◌᷺◌༙◌̖◌֚b; a◌᷺◌༙◌̖◌֚b; ) LATIN SMALL LETTER A, TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0F35 0062;0061 1DFA 0316 0F35 059A 0062;0061 1DFA 0316 0F35 059A 0062;0061 1DFA 0316 0F35 059A 0062;0061 1DFA 0316 0F35 059A 0062; # (a◌֚◌̖◌᷺◌༵b; a◌᷺◌̖◌༵◌֚b; a◌᷺◌̖◌༵◌֚b; a◌᷺◌̖◌༵◌֚b; a◌᷺◌̖◌༵◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, TIBETAN MARK NGAS BZUNG NYI ZLA, LATIN SMALL LETTER B
+0061 0F35 059A 0316 1DFA 0062;0061 1DFA 0F35 0316 059A 0062;0061 1DFA 0F35 0316 059A 0062;0061 1DFA 0F35 0316 059A 0062;0061 1DFA 0F35 0316 059A 0062; # (a◌༵◌֚◌̖◌᷺b; a◌᷺◌༵◌̖◌֚b; a◌᷺◌༵◌̖◌֚b; a◌᷺◌༵◌̖◌֚b; a◌᷺◌༵◌̖◌֚b; ) LATIN SMALL LETTER A, TIBETAN MARK NGAS BZUNG NYI ZLA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0F37 0062;0061 1DFA 0316 0F37 059A 0062;0061 1DFA 0316 0F37 059A 0062;0061 1DFA 0316 0F37 059A 0062;0061 1DFA 0316 0F37 059A 0062; # (a◌֚◌̖◌᷺◌༷b; a◌᷺◌̖◌༷◌֚b; a◌᷺◌̖◌༷◌֚b; a◌᷺◌̖◌༷◌֚b; a◌᷺◌̖◌༷◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, TIBETAN MARK NGAS BZUNG SGOR RTAGS, LATIN SMALL LETTER B
+0061 0F37 059A 0316 1DFA 0062;0061 1DFA 0F37 0316 059A 0062;0061 1DFA 0F37 0316 059A 0062;0061 1DFA 0F37 0316 059A 0062;0061 1DFA 0F37 0316 059A 0062; # (a◌༷◌֚◌̖◌᷺b; a◌᷺◌༷◌̖◌֚b; a◌᷺◌༷◌̖◌֚b; a◌᷺◌༷◌̖◌֚b; a◌᷺◌༷◌̖◌֚b; ) LATIN SMALL LETTER A, TIBETAN MARK NGAS BZUNG SGOR RTAGS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 1DFA 031B 1DCE 0F39 0062;0061 1DCE 031B 0F39 1DFA 0062;0061 1DCE 031B 0F39 1DFA 0062;0061 1DCE 031B 0F39 1DFA 0062;0061 1DCE 031B 0F39 1DFA 0062; # (a◌᷺◌̛◌᷎◌༹b; a◌᷎◌̛◌༹◌᷺b; a◌᷎◌̛◌༹◌᷺b; a◌᷎◌̛◌༹◌᷺b; a◌᷎◌̛◌༹◌᷺b; ) LATIN SMALL LETTER A, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, TIBETAN MARK TSA -PHRU, LATIN SMALL LETTER B
+0061 0F39 1DFA 031B 1DCE 0062;0061 1DCE 0F39 031B 1DFA 0062;0061 1DCE 0F39 031B 1DFA 0062;0061 1DCE 0F39 031B 1DFA 0062;0061 1DCE 0F39 031B 1DFA 0062; # (a◌༹◌᷺◌̛◌᷎b; a◌᷎◌༹◌̛◌᷺b; a◌᷎◌༹◌̛◌᷺b; a◌᷎◌༹◌̛◌᷺b; a◌᷎◌༹◌̛◌᷺b; ) LATIN SMALL LETTER A, TIBETAN MARK TSA -PHRU, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, LATIN SMALL LETTER B
+0061 0F72 0F71 0EC8 0F71 0062;0061 0EC8 0F71 0F71 0F72 0062;0061 0EC8 0F71 0F71 0F72 0062;0061 0EC8 0F71 0F71 0F72 0062;0061 0EC8 0F71 0F71 0F72 0062; # (a◌ི◌ཱ◌່◌ཱb; a◌່◌ཱ◌ཱ◌ིb; a◌່◌ཱ◌ཱ◌ིb; a◌່◌ཱ◌ཱ◌ིb; a◌່◌ཱ◌ཱ◌ིb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, TIBETAN VOWEL SIGN AA, LATIN SMALL LETTER B
+0061 0F71 0F72 0F71 0EC8 0062;0061 0EC8 0F71 0F71 0F72 0062;0061 0EC8 0F71 0F71 0F72 0062;0061 0EC8 0F71 0F71 0F72 0062;0061 0EC8 0F71 0F71 0F72 0062; # (a◌ཱ◌ི◌ཱ◌່b; a◌່◌ཱ◌ཱ◌ིb; a◌່◌ཱ◌ཱ◌ིb; a◌່◌ཱ◌ཱ◌ིb; a◌່◌ཱ◌ཱ◌ིb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN AA, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LATIN SMALL LETTER B
+0061 0F74 0F72 0F71 0F72 0062;0061 0F71 0F72 0F72 0F74 0062;0061 0F71 0F72 0F72 0F74 0062;0061 0F71 0F72 0F72 0F74 0062;0061 0F71 0F72 0F72 0F74 0062; # (a◌ུ◌ི◌ཱ◌ིb; a◌ཱ◌ི◌ི◌ུb; a◌ཱ◌ི◌ི◌ུb; a◌ཱ◌ི◌ི◌ུb; a◌ཱ◌ི◌ི◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, TIBETAN VOWEL SIGN I, LATIN SMALL LETTER B
+0061 0F72 0F74 0F72 0F71 0062;0061 0F71 0F72 0F72 0F74 0062;0061 0F71 0F72 0F72 0F74 0062;0061 0F71 0F72 0F72 0F74 0062;0061 0F71 0F72 0F72 0F74 0062; # (a◌ི◌ུ◌ི◌ཱb; a◌ཱ◌ི◌ི◌ུb; a◌ཱ◌ི◌ི◌ུb; a◌ཱ◌ི◌ི◌ུb; a◌ཱ◌ི◌ི◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, LATIN SMALL LETTER B
+0061 0321 0F74 0F72 0F74 0062;0061 0F72 0F74 0F74 0321 0062;0061 0F72 0F74 0F74 0321 0062;0061 0F72 0F74 0F74 0321 0062;0061 0F72 0F74 0F74 0321 0062; # (a◌̡◌ུ◌ི◌ུb; a◌ི◌ུ◌ུ◌̡b; a◌ི◌ུ◌ུ◌̡b; a◌ི◌ུ◌ུ◌̡b; a◌ི◌ུ◌ུ◌̡b; ) LATIN SMALL LETTER A, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN U, LATIN SMALL LETTER B
+0061 0F74 0321 0F74 0F72 0062;0061 0F72 0F74 0F74 0321 0062;0061 0F72 0F74 0F74 0321 0062;0061 0F72 0F74 0F74 0321 0062;0061 0F72 0F74 0F74 0321 0062; # (a◌ུ◌̡◌ུ◌ིb; a◌ི◌ུ◌ུ◌̡b; a◌ི◌ུ◌ུ◌̡b; a◌ི◌ུ◌ུ◌̡b; a◌ི◌ུ◌ུ◌̡b; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN U, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, LATIN SMALL LETTER B
+0061 0F74 0F72 0F71 0F7A 0062;0061 0F71 0F72 0F7A 0F74 0062;0061 0F71 0F72 0F7A 0F74 0062;0061 0F71 0F72 0F7A 0F74 0062;0061 0F71 0F72 0F7A 0F74 0062; # (a◌ུ◌ི◌ཱ◌ེb; a◌ཱ◌ི◌ེ◌ུb; a◌ཱ◌ི◌ེ◌ུb; a◌ཱ◌ི◌ེ◌ུb; a◌ཱ◌ི◌ེ◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, TIBETAN VOWEL SIGN E, LATIN SMALL LETTER B
+0061 0F7A 0F74 0F72 0F71 0062;0061 0F71 0F7A 0F72 0F74 0062;0061 0F71 0F7A 0F72 0F74 0062;0061 0F71 0F7A 0F72 0F74 0062;0061 0F71 0F7A 0F72 0F74 0062; # (a◌ེ◌ུ◌ི◌ཱb; a◌ཱ◌ེ◌ི◌ུb; a◌ཱ◌ེ◌ི◌ུb; a◌ཱ◌ེ◌ི◌ུb; a◌ཱ◌ེ◌ི◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN E, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, LATIN SMALL LETTER B
+0061 0F74 0F72 0F71 0F7B 0062;0061 0F71 0F72 0F7B 0F74 0062;0061 0F71 0F72 0F7B 0F74 0062;0061 0F71 0F72 0F7B 0F74 0062;0061 0F71 0F72 0F7B 0F74 0062; # (a◌ུ◌ི◌ཱ◌ཻb; a◌ཱ◌ི◌ཻ◌ུb; a◌ཱ◌ི◌ཻ◌ུb; a◌ཱ◌ི◌ཻ◌ུb; a◌ཱ◌ི◌ཻ◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, TIBETAN VOWEL SIGN EE, LATIN SMALL LETTER B
+0061 0F7B 0F74 0F72 0F71 0062;0061 0F71 0F7B 0F72 0F74 0062;0061 0F71 0F7B 0F72 0F74 0062;0061 0F71 0F7B 0F72 0F74 0062;0061 0F71 0F7B 0F72 0F74 0062; # (a◌ཻ◌ུ◌ི◌ཱb; a◌ཱ◌ཻ◌ི◌ུb; a◌ཱ◌ཻ◌ི◌ུb; a◌ཱ◌ཻ◌ི◌ུb; a◌ཱ◌ཻ◌ི◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN EE, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, LATIN SMALL LETTER B
+0061 0F74 0F72 0F71 0F7C 0062;0061 0F71 0F72 0F7C 0F74 0062;0061 0F71 0F72 0F7C 0F74 0062;0061 0F71 0F72 0F7C 0F74 0062;0061 0F71 0F72 0F7C 0F74 0062; # (a◌ུ◌ི◌ཱ◌ོb; a◌ཱ◌ི◌ོ◌ུb; a◌ཱ◌ི◌ོ◌ུb; a◌ཱ◌ི◌ོ◌ུb; a◌ཱ◌ི◌ོ◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, TIBETAN VOWEL SIGN O, LATIN SMALL LETTER B
+0061 0F7C 0F74 0F72 0F71 0062;0061 0F71 0F7C 0F72 0F74 0062;0061 0F71 0F7C 0F72 0F74 0062;0061 0F71 0F7C 0F72 0F74 0062;0061 0F71 0F7C 0F72 0F74 0062; # (a◌ོ◌ུ◌ི◌ཱb; a◌ཱ◌ོ◌ི◌ུb; a◌ཱ◌ོ◌ི◌ུb; a◌ཱ◌ོ◌ི◌ུb; a◌ཱ◌ོ◌ི◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN O, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, LATIN SMALL LETTER B
+0061 0F74 0F72 0F71 0F7D 0062;0061 0F71 0F72 0F7D 0F74 0062;0061 0F71 0F72 0F7D 0F74 0062;0061 0F71 0F72 0F7D 0F74 0062;0061 0F71 0F72 0F7D 0F74 0062; # (a◌ུ◌ི◌ཱ◌ཽb; a◌ཱ◌ི◌ཽ◌ུb; a◌ཱ◌ི◌ཽ◌ུb; a◌ཱ◌ི◌ཽ◌ུb; a◌ཱ◌ི◌ཽ◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, TIBETAN VOWEL SIGN OO, LATIN SMALL LETTER B
+0061 0F7D 0F74 0F72 0F71 0062;0061 0F71 0F7D 0F72 0F74 0062;0061 0F71 0F7D 0F72 0F74 0062;0061 0F71 0F7D 0F72 0F74 0062;0061 0F71 0F7D 0F72 0F74 0062; # (a◌ཽ◌ུ◌ི◌ཱb; a◌ཱ◌ཽ◌ི◌ུb; a◌ཱ◌ཽ◌ི◌ུb; a◌ཱ◌ཽ◌ི◌ུb; a◌ཱ◌ཽ◌ི◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN OO, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, LATIN SMALL LETTER B
+0061 0F74 0F72 0F71 0F80 0062;0061 0F71 0F72 0F80 0F74 0062;0061 0F71 0F72 0F80 0F74 0062;0061 0F71 0F72 0F80 0F74 0062;0061 0F71 0F72 0F80 0F74 0062; # (a◌ུ◌ི◌ཱ◌ྀb; a◌ཱ◌ི◌ྀ◌ུb; a◌ཱ◌ི◌ྀ◌ུb; a◌ཱ◌ི◌ྀ◌ུb; a◌ཱ◌ི◌ྀ◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, TIBETAN VOWEL SIGN REVERSED I, LATIN SMALL LETTER B
+0061 0F80 0F74 0F72 0F71 0062;0061 0F71 0F80 0F72 0F74 0062;0061 0F71 0F80 0F72 0F74 0062;0061 0F71 0F80 0F72 0F74 0062;0061 0F71 0F80 0F72 0F74 0062; # (a◌ྀ◌ུ◌ི◌ཱb; a◌ཱ◌ྀ◌ི◌ུb; a◌ཱ◌ྀ◌ི◌ུb; a◌ཱ◌ྀ◌ི◌ུb; a◌ཱ◌ྀ◌ི◌ུb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN REVERSED I, TIBETAN VOWEL SIGN U, TIBETAN VOWEL SIGN I, TIBETAN VOWEL SIGN AA, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0F82 0062;00E0 05AE 0F82 0315 0062;0061 05AE 0300 0F82 0315 0062;00E0 05AE 0F82 0315 0062;0061 05AE 0300 0F82 0315 0062; # (a◌̕◌̀◌֮◌ྂb; à◌֮◌ྂ◌̕b; a◌֮◌̀◌ྂ◌̕b; à◌֮◌ྂ◌̕b; a◌֮◌̀◌ྂ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TIBETAN SIGN NYI ZLA NAA DA, LATIN SMALL LETTER B
+0061 0F82 0315 0300 05AE 0062;0061 05AE 0F82 0300 0315 0062;0061 05AE 0F82 0300 0315 0062;0061 05AE 0F82 0300 0315 0062;0061 05AE 0F82 0300 0315 0062; # (a◌ྂ◌̕◌̀◌֮b; a◌֮◌ྂ◌̀◌̕b; a◌֮◌ྂ◌̀◌̕b; a◌֮◌ྂ◌̀◌̕b; a◌֮◌ྂ◌̀◌̕b; ) LATIN SMALL LETTER A, TIBETAN SIGN NYI ZLA NAA DA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0F83 0062;00E0 05AE 0F83 0315 0062;0061 05AE 0300 0F83 0315 0062;00E0 05AE 0F83 0315 0062;0061 05AE 0300 0F83 0315 0062; # (a◌̕◌̀◌֮◌ྃb; à◌֮◌ྃ◌̕b; a◌֮◌̀◌ྃ◌̕b; à◌֮◌ྃ◌̕b; a◌֮◌̀◌ྃ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TIBETAN SIGN SNA LDAN, LATIN SMALL LETTER B
+0061 0F83 0315 0300 05AE 0062;0061 05AE 0F83 0300 0315 0062;0061 05AE 0F83 0300 0315 0062;0061 05AE 0F83 0300 0315 0062;0061 05AE 0F83 0300 0315 0062; # (a◌ྃ◌̕◌̀◌֮b; a◌֮◌ྃ◌̀◌̕b; a◌֮◌ྃ◌̀◌̕b; a◌֮◌ྃ◌̀◌̕b; a◌֮◌ྃ◌̀◌̕b; ) LATIN SMALL LETTER A, TIBETAN SIGN SNA LDAN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0F84 0062;0061 3099 094D 0F84 05B0 0062;0061 3099 094D 0F84 05B0 0062;0061 3099 094D 0F84 05B0 0062;0061 3099 094D 0F84 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œà¾„b; a◌゙◌à¥â—Œà¾„◌ְb; a◌゙◌à¥â—Œà¾„◌ְb; a◌゙◌à¥â—Œà¾„◌ְb; a◌゙◌à¥â—Œà¾„◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, TIBETAN MARK HALANTA, LATIN SMALL LETTER B
+0061 0F84 05B0 094D 3099 0062;0061 3099 0F84 094D 05B0 0062;0061 3099 0F84 094D 05B0 0062;0061 3099 0F84 094D 05B0 0062;0061 3099 0F84 094D 05B0 0062; # (a◌྄◌ְ◌à¥â—Œã‚™b; a◌゙◌྄◌à¥â—ŒÖ°b; a◌゙◌྄◌à¥â—ŒÖ°b; a◌゙◌྄◌à¥â—ŒÖ°b; a◌゙◌྄◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, TIBETAN MARK HALANTA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0F86 0062;00E0 05AE 0F86 0315 0062;0061 05AE 0300 0F86 0315 0062;00E0 05AE 0F86 0315 0062;0061 05AE 0300 0F86 0315 0062; # (a◌̕◌̀◌֮◌྆b; à◌֮◌྆◌̕b; a◌֮◌̀◌྆◌̕b; à◌֮◌྆◌̕b; a◌֮◌̀◌྆◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TIBETAN SIGN LCI RTAGS, LATIN SMALL LETTER B
+0061 0F86 0315 0300 05AE 0062;0061 05AE 0F86 0300 0315 0062;0061 05AE 0F86 0300 0315 0062;0061 05AE 0F86 0300 0315 0062;0061 05AE 0F86 0300 0315 0062; # (a◌྆◌̕◌̀◌֮b; a◌֮◌྆◌̀◌̕b; a◌֮◌྆◌̀◌̕b; a◌֮◌྆◌̀◌̕b; a◌֮◌྆◌̀◌̕b; ) LATIN SMALL LETTER A, TIBETAN SIGN LCI RTAGS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 0F87 0062;00E0 05AE 0F87 0315 0062;0061 05AE 0300 0F87 0315 0062;00E0 05AE 0F87 0315 0062;0061 05AE 0300 0F87 0315 0062; # (a◌̕◌̀◌֮◌྇b; à◌֮◌྇◌̕b; a◌֮◌̀◌྇◌̕b; à◌֮◌྇◌̕b; a◌֮◌̀◌྇◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TIBETAN SIGN YANG RTAGS, LATIN SMALL LETTER B
+0061 0F87 0315 0300 05AE 0062;0061 05AE 0F87 0300 0315 0062;0061 05AE 0F87 0300 0315 0062;0061 05AE 0F87 0300 0315 0062;0061 05AE 0F87 0300 0315 0062; # (a◌྇◌̕◌̀◌֮b; a◌֮◌྇◌̀◌̕b; a◌֮◌྇◌̀◌̕b; a◌֮◌྇◌̀◌̕b; a◌֮◌྇◌̀◌̕b; ) LATIN SMALL LETTER A, TIBETAN SIGN YANG RTAGS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 0FC6 0062;0061 1DFA 0316 0FC6 059A 0062;0061 1DFA 0316 0FC6 059A 0062;0061 1DFA 0316 0FC6 059A 0062;0061 1DFA 0316 0FC6 059A 0062; # (a◌֚◌̖◌᷺◌࿆b; a◌᷺◌̖◌࿆◌֚b; a◌᷺◌̖◌࿆◌֚b; a◌᷺◌̖◌࿆◌֚b; a◌᷺◌̖◌࿆◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, TIBETAN SYMBOL PADMA GDAN, LATIN SMALL LETTER B
+0061 0FC6 059A 0316 1DFA 0062;0061 1DFA 0FC6 0316 059A 0062;0061 1DFA 0FC6 0316 059A 0062;0061 1DFA 0FC6 0316 059A 0062;0061 1DFA 0FC6 0316 059A 0062; # (a◌࿆◌֚◌̖◌᷺b; a◌᷺◌࿆◌̖◌֚b; a◌᷺◌࿆◌̖◌֚b; a◌᷺◌࿆◌̖◌֚b; a◌᷺◌࿆◌̖◌֚b; ) LATIN SMALL LETTER A, TIBETAN SYMBOL PADMA GDAN, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 1037 0062;0061 16FF0 093C 1037 3099 0062;0061 16FF0 093C 1037 3099 0062;0061 16FF0 093C 1037 3099 0062;0061 16FF0 093C 1037 3099 0062; # (a◌゙◌𖿰़◌့b; a𖿰◌़◌့◌゙b; a𖿰◌़◌့◌゙b; a𖿰◌़◌့◌゙b; a𖿰◌़◌့◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, MYANMAR SIGN DOT BELOW, LATIN SMALL LETTER B
+0061 1037 3099 093C 16FF0 0062;0061 16FF0 1037 093C 3099 0062;0061 16FF0 1037 093C 3099 0062;0061 16FF0 1037 093C 3099 0062;0061 16FF0 1037 093C 3099 0062; # (a◌့◌゙◌𖿰़b; a𖿰◌့◌़◌゙b; a𖿰◌့◌़◌゙b; a𖿰◌့◌़◌゙b; a𖿰◌့◌़◌゙b; ) LATIN SMALL LETTER A, MYANMAR SIGN DOT BELOW, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1039 0062;0061 3099 094D 1039 05B0 0062;0061 3099 094D 1039 05B0 0062;0061 3099 094D 1039 05B0 0062;0061 3099 094D 1039 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œá€¹b; a◌゙◌à¥â—Œá€¹â—ŒÖ°b; a◌゙◌à¥â—Œá€¹â—ŒÖ°b; a◌゙◌à¥â—Œá€¹â—ŒÖ°b; a◌゙◌à¥â—Œá€¹â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, MYANMAR SIGN VIRAMA, LATIN SMALL LETTER B
+0061 1039 05B0 094D 3099 0062;0061 3099 1039 094D 05B0 0062;0061 3099 1039 094D 05B0 0062;0061 3099 1039 094D 05B0 0062;0061 3099 1039 094D 05B0 0062; # (a◌္◌ְ◌à¥â—Œã‚™b; a◌゙◌္◌à¥â—ŒÖ°b; a◌゙◌္◌à¥â—ŒÖ°b; a◌゙◌္◌à¥â—ŒÖ°b; a◌゙◌္◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MYANMAR SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 103A 0062;0061 3099 094D 103A 05B0 0062;0061 3099 094D 103A 05B0 0062;0061 3099 094D 103A 05B0 0062;0061 3099 094D 103A 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œá€ºb; a◌゙◌à¥â—Œá€ºâ—ŒÖ°b; a◌゙◌à¥â—Œá€ºâ—ŒÖ°b; a◌゙◌à¥â—Œá€ºâ—ŒÖ°b; a◌゙◌à¥â—Œá€ºâ—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, MYANMAR SIGN ASAT, LATIN SMALL LETTER B
+0061 103A 05B0 094D 3099 0062;0061 3099 103A 094D 05B0 0062;0061 3099 103A 094D 05B0 0062;0061 3099 103A 094D 05B0 0062;0061 3099 103A 094D 05B0 0062; # (a◌်◌ְ◌à¥â—Œã‚™b; a◌゙◌်◌à¥â—ŒÖ°b; a◌゙◌်◌à¥â—ŒÖ°b; a◌゙◌်◌à¥â—ŒÖ°b; a◌゙◌်◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MYANMAR SIGN ASAT, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 108D 0062;0061 1DFA 0316 108D 059A 0062;0061 1DFA 0316 108D 059A 0062;0061 1DFA 0316 108D 059A 0062;0061 1DFA 0316 108D 059A 0062; # (a◌֚◌̖◌᷺◌á‚b; a◌᷺◌̖◌á‚◌֚b; a◌᷺◌̖◌á‚◌֚b; a◌᷺◌̖◌á‚◌֚b; a◌᷺◌̖◌á‚◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE, LATIN SMALL LETTER B
+0061 108D 059A 0316 1DFA 0062;0061 1DFA 108D 0316 059A 0062;0061 1DFA 108D 0316 059A 0062;0061 1DFA 108D 0316 059A 0062;0061 1DFA 108D 0316 059A 0062; # (aâ—Œá‚◌֚◌̖◌᷺b; a◌᷺◌á‚◌̖◌֚b; a◌᷺◌á‚◌̖◌֚b; a◌᷺◌á‚◌̖◌֚b; a◌᷺◌á‚◌̖◌֚b; ) LATIN SMALL LETTER A, MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 135D 0062;00E0 05AE 135D 0315 0062;0061 05AE 0300 135D 0315 0062;00E0 05AE 135D 0315 0062;0061 05AE 0300 135D 0315 0062; # (a◌̕◌̀◌֮◌áb; à◌֮◌á◌̕b; a◌֮◌̀◌á◌̕b; à◌֮◌á◌̕b; a◌֮◌̀◌á◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK, LATIN SMALL LETTER B
+0061 135D 0315 0300 05AE 0062;0061 05AE 135D 0300 0315 0062;0061 05AE 135D 0300 0315 0062;0061 05AE 135D 0300 0315 0062;0061 05AE 135D 0300 0315 0062; # (aâ—Œá◌̕◌̀◌֮b; a◌֮◌á◌̀◌̕b; a◌֮◌á◌̀◌̕b; a◌֮◌á◌̀◌̕b; a◌֮◌á◌̀◌̕b; ) LATIN SMALL LETTER A, ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 135E 0062;00E0 05AE 135E 0315 0062;0061 05AE 0300 135E 0315 0062;00E0 05AE 135E 0315 0062;0061 05AE 0300 135E 0315 0062; # (a◌̕◌̀◌֮◌ážb; à◌֮◌ážâ—ŒÌ•b; a◌֮◌̀◌ážâ—ŒÌ•b; à◌֮◌ážâ—ŒÌ•b; a◌֮◌̀◌ážâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ETHIOPIC COMBINING VOWEL LENGTH MARK, LATIN SMALL LETTER B
+0061 135E 0315 0300 05AE 0062;0061 05AE 135E 0300 0315 0062;0061 05AE 135E 0300 0315 0062;0061 05AE 135E 0300 0315 0062;0061 05AE 135E 0300 0315 0062; # (aâ—Œážâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ážâ—ŒÌ€â—ŒÌ•b; a◌֮◌ážâ—ŒÌ€â—ŒÌ•b; a◌֮◌ážâ—ŒÌ€â—ŒÌ•b; a◌֮◌ážâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, ETHIOPIC COMBINING VOWEL LENGTH MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 135F 0062;00E0 05AE 135F 0315 0062;0061 05AE 0300 135F 0315 0062;00E0 05AE 135F 0315 0062;0061 05AE 0300 135F 0315 0062; # (a◌̕◌̀◌֮◌áŸb; à◌֮◌áŸâ—ŒÌ•b; a◌֮◌̀◌áŸâ—ŒÌ•b; à◌֮◌áŸâ—ŒÌ•b; a◌֮◌̀◌áŸâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ETHIOPIC COMBINING GEMINATION MARK, LATIN SMALL LETTER B
+0061 135F 0315 0300 05AE 0062;0061 05AE 135F 0300 0315 0062;0061 05AE 135F 0300 0315 0062;0061 05AE 135F 0300 0315 0062;0061 05AE 135F 0300 0315 0062; # (aâ—ŒáŸâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌áŸâ—ŒÌ€â—ŒÌ•b; a◌֮◌áŸâ—ŒÌ€â—ŒÌ•b; a◌֮◌áŸâ—ŒÌ€â—ŒÌ•b; a◌֮◌áŸâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, ETHIOPIC COMBINING GEMINATION MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1714 0062;0061 3099 094D 1714 05B0 0062;0061 3099 094D 1714 05B0 0062;0061 3099 094D 1714 05B0 0062;0061 3099 094D 1714 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œáœ”b; a◌゙◌à¥â—Œáœ”◌ְb; a◌゙◌à¥â—Œáœ”◌ְb; a◌゙◌à¥â—Œáœ”◌ְb; a◌゙◌à¥â—Œáœ”◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, TAGALOG SIGN VIRAMA, LATIN SMALL LETTER B
+0061 1714 05B0 094D 3099 0062;0061 3099 1714 094D 05B0 0062;0061 3099 1714 094D 05B0 0062;0061 3099 1714 094D 05B0 0062;0061 3099 1714 094D 05B0 0062; # (a◌᜔◌ְ◌à¥â—Œã‚™b; a◌゙◌᜔◌à¥â—ŒÖ°b; a◌゙◌᜔◌à¥â—ŒÖ°b; a◌゙◌᜔◌à¥â—ŒÖ°b; a◌゙◌᜔◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, TAGALOG SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1715 0062;0061 3099 094D 1715 05B0 0062;0061 3099 094D 1715 05B0 0062;0061 3099 094D 1715 05B0 0062;0061 3099 094D 1715 05B0 0062; # (a◌ְ◌à¥â—Œã‚™áœ•b; a◌゙◌à¥áœ•â—ŒÖ°b; a◌゙◌à¥áœ•â—ŒÖ°b; a◌゙◌à¥áœ•â—ŒÖ°b; a◌゙◌à¥áœ•â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, TAGALOG SIGN PAMUDPOD, LATIN SMALL LETTER B
+0061 1715 05B0 094D 3099 0062;0061 3099 1715 094D 05B0 0062;0061 3099 1715 094D 05B0 0062;0061 3099 1715 094D 05B0 0062;0061 3099 1715 094D 05B0 0062; # (a᜕◌ְ◌à¥â—Œã‚™b; a◌゙᜕◌à¥â—ŒÖ°b; a◌゙᜕◌à¥â—ŒÖ°b; a◌゙᜕◌à¥â—ŒÖ°b; a◌゙᜕◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, TAGALOG SIGN PAMUDPOD, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1734 0062;0061 3099 094D 1734 05B0 0062;0061 3099 094D 1734 05B0 0062;0061 3099 094D 1734 05B0 0062;0061 3099 094D 1734 05B0 0062; # (a◌ְ◌à¥â—Œã‚™áœ´b; a◌゙◌à¥áœ´â—ŒÖ°b; a◌゙◌à¥áœ´â—ŒÖ°b; a◌゙◌à¥áœ´â—ŒÖ°b; a◌゙◌à¥áœ´â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, HANUNOO SIGN PAMUDPOD, LATIN SMALL LETTER B
+0061 1734 05B0 094D 3099 0062;0061 3099 1734 094D 05B0 0062;0061 3099 1734 094D 05B0 0062;0061 3099 1734 094D 05B0 0062;0061 3099 1734 094D 05B0 0062; # (a᜴◌ְ◌à¥â—Œã‚™b; a◌゙᜴◌à¥â—ŒÖ°b; a◌゙᜴◌à¥â—ŒÖ°b; a◌゙᜴◌à¥â—ŒÖ°b; a◌゙᜴◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, HANUNOO SIGN PAMUDPOD, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 17D2 0062;0061 3099 094D 17D2 05B0 0062;0061 3099 094D 17D2 05B0 0062;0061 3099 094D 17D2 05B0 0062;0061 3099 094D 17D2 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—ŒáŸ’b; a◌゙◌à¥â—ŒáŸ’◌ְb; a◌゙◌à¥â—ŒáŸ’◌ְb; a◌゙◌à¥â—ŒáŸ’◌ְb; a◌゙◌à¥â—ŒáŸ’◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, KHMER SIGN COENG, LATIN SMALL LETTER B
+0061 17D2 05B0 094D 3099 0062;0061 3099 17D2 094D 05B0 0062;0061 3099 17D2 094D 05B0 0062;0061 3099 17D2 094D 05B0 0062;0061 3099 17D2 094D 05B0 0062; # (a◌្◌ְ◌à¥â—Œã‚™b; a◌゙◌្◌à¥â—ŒÖ°b; a◌゙◌្◌à¥â—ŒÖ°b; a◌゙◌្◌à¥â—ŒÖ°b; a◌゙◌្◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, KHMER SIGN COENG, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE 17DD 0062;00E0 05AE 17DD 0315 0062;0061 05AE 0300 17DD 0315 0062;00E0 05AE 17DD 0315 0062;0061 05AE 0300 17DD 0315 0062; # (a◌̕◌̀◌֮◌áŸb; à◌֮◌áŸâ—ŒÌ•b; a◌֮◌̀◌áŸâ—ŒÌ•b; à◌֮◌áŸâ—ŒÌ•b; a◌֮◌̀◌áŸâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, KHMER SIGN ATTHACAN, LATIN SMALL LETTER B
+0061 17DD 0315 0300 05AE 0062;0061 05AE 17DD 0300 0315 0062;0061 05AE 17DD 0300 0315 0062;0061 05AE 17DD 0300 0315 0062;0061 05AE 17DD 0300 0315 0062; # (aâ—ŒáŸâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌áŸâ—ŒÌ€â—ŒÌ•b; a◌֮◌áŸâ—ŒÌ€â—ŒÌ•b; a◌֮◌áŸâ—ŒÌ€â—ŒÌ•b; a◌֮◌áŸâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, KHMER SIGN ATTHACAN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0300 05AE 1D16D 18A9 0062;00E0 1D16D 05AE 18A9 0062;0061 1D16D 05AE 18A9 0300 0062;00E0 1D16D 05AE 18A9 0062;0061 1D16D 05AE 18A9 0300 0062; # (a◌̀◌֮ð…­â—Œá¢©b; àð…­â—ŒÖ®â—Œá¢©b; að…­â—ŒÖ®â—Œá¢©â—ŒÌ€b; àð…­â—ŒÖ®â—Œá¢©b; að…­â—ŒÖ®â—Œá¢©â—ŒÌ€b; ) LATIN SMALL LETTER A, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, MONGOLIAN LETTER ALI GALI DAGALGA, LATIN SMALL LETTER B
+0061 18A9 0300 05AE 1D16D 0062;00E0 1D16D 18A9 05AE 0062;0061 1D16D 18A9 05AE 0300 0062;00E0 1D16D 18A9 05AE 0062;0061 1D16D 18A9 05AE 0300 0062; # (a◌ᢩ◌̀◌֮ð…­b; àð…­â—Œá¢©â—ŒÖ®b; að…­â—Œá¢©â—ŒÖ®â—ŒÌ€b; àð…­â—Œá¢©â—ŒÖ®b; að…­â—Œá¢©â—ŒÖ®â—ŒÌ€b; ) LATIN SMALL LETTER A, MONGOLIAN LETTER ALI GALI DAGALGA, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, LATIN SMALL LETTER B
+0061 302E 059A 0316 1939 0062;0061 0316 059A 1939 302E 0062;0061 0316 059A 1939 302E 0062;0061 0316 059A 1939 302E 0062;0061 0316 059A 1939 302E 0062; # (a〮◌֚◌̖◌᤹b; a◌̖◌֚◌᤹〮b; a◌̖◌֚◌᤹〮b; a◌̖◌֚◌᤹〮b; a◌̖◌֚◌᤹〮b; ) LATIN SMALL LETTER A, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, LIMBU SIGN MUKPHRENG, LATIN SMALL LETTER B
+0061 1939 302E 059A 0316 0062;0061 0316 1939 059A 302E 0062;0061 0316 1939 059A 302E 0062;0061 0316 1939 059A 302E 0062;0061 0316 1939 059A 302E 0062; # (a◌᤹〮◌֚◌̖b; a◌̖◌᤹◌֚〮b; a◌̖◌᤹◌֚〮b; a◌̖◌᤹◌֚〮b; a◌̖◌᤹◌֚〮b; ) LATIN SMALL LETTER A, LIMBU SIGN MUKPHRENG, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, LATIN SMALL LETTER B
+0061 0315 0300 05AE 193A 0062;00E0 05AE 193A 0315 0062;0061 05AE 0300 193A 0315 0062;00E0 05AE 193A 0315 0062;0061 05AE 0300 193A 0315 0062; # (a◌̕◌̀◌֮◌᤺b; à◌֮◌᤺◌̕b; a◌֮◌̀◌᤺◌̕b; à◌֮◌᤺◌̕b; a◌֮◌̀◌᤺◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LIMBU SIGN KEMPHRENG, LATIN SMALL LETTER B
+0061 193A 0315 0300 05AE 0062;0061 05AE 193A 0300 0315 0062;0061 05AE 193A 0300 0315 0062;0061 05AE 193A 0300 0315 0062;0061 05AE 193A 0300 0315 0062; # (a◌᤺◌̕◌̀◌֮b; a◌֮◌᤺◌̀◌̕b; a◌֮◌᤺◌̀◌̕b; a◌֮◌᤺◌̀◌̕b; a◌֮◌᤺◌̀◌̕b; ) LATIN SMALL LETTER A, LIMBU SIGN KEMPHRENG, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 193B 0062;0061 1DFA 0316 193B 059A 0062;0061 1DFA 0316 193B 059A 0062;0061 1DFA 0316 193B 059A 0062;0061 1DFA 0316 193B 059A 0062; # (a◌֚◌̖◌᷺◌᤻b; a◌᷺◌̖◌᤻◌֚b; a◌᷺◌̖◌᤻◌֚b; a◌᷺◌̖◌᤻◌֚b; a◌᷺◌̖◌᤻◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LIMBU SIGN SA-I, LATIN SMALL LETTER B
+0061 193B 059A 0316 1DFA 0062;0061 1DFA 193B 0316 059A 0062;0061 1DFA 193B 0316 059A 0062;0061 1DFA 193B 0316 059A 0062;0061 1DFA 193B 0316 059A 0062; # (a◌᤻◌֚◌̖◌᷺b; a◌᷺◌᤻◌̖◌֚b; a◌᷺◌᤻◌̖◌֚b; a◌᷺◌᤻◌̖◌֚b; a◌᷺◌᤻◌̖◌֚b; ) LATIN SMALL LETTER A, LIMBU SIGN SA-I, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1A17 0062;00E0 05AE 1A17 0315 0062;0061 05AE 0300 1A17 0315 0062;00E0 05AE 1A17 0315 0062;0061 05AE 0300 1A17 0315 0062; # (a◌̕◌̀◌֮◌ᨗb; à◌֮◌ᨗ◌̕b; a◌֮◌̀◌ᨗ◌̕b; à◌֮◌ᨗ◌̕b; a◌֮◌̀◌ᨗ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BUGINESE VOWEL SIGN I, LATIN SMALL LETTER B
+0061 1A17 0315 0300 05AE 0062;0061 05AE 1A17 0300 0315 0062;0061 05AE 1A17 0300 0315 0062;0061 05AE 1A17 0300 0315 0062;0061 05AE 1A17 0300 0315 0062; # (a◌ᨗ◌̕◌̀◌֮b; a◌֮◌ᨗ◌̀◌̕b; a◌֮◌ᨗ◌̀◌̕b; a◌֮◌ᨗ◌̀◌̕b; a◌֮◌ᨗ◌̀◌̕b; ) LATIN SMALL LETTER A, BUGINESE VOWEL SIGN I, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1A18 0062;0061 1DFA 0316 1A18 059A 0062;0061 1DFA 0316 1A18 059A 0062;0061 1DFA 0316 1A18 059A 0062;0061 1DFA 0316 1A18 059A 0062; # (a◌֚◌̖◌᷺◌ᨘb; a◌᷺◌̖◌ᨘ◌֚b; a◌᷺◌̖◌ᨘ◌֚b; a◌᷺◌̖◌ᨘ◌֚b; a◌᷺◌̖◌ᨘ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, BUGINESE VOWEL SIGN U, LATIN SMALL LETTER B
+0061 1A18 059A 0316 1DFA 0062;0061 1DFA 1A18 0316 059A 0062;0061 1DFA 1A18 0316 059A 0062;0061 1DFA 1A18 0316 059A 0062;0061 1DFA 1A18 0316 059A 0062; # (a◌ᨘ◌֚◌̖◌᷺b; a◌᷺◌ᨘ◌̖◌֚b; a◌᷺◌ᨘ◌̖◌֚b; a◌᷺◌ᨘ◌̖◌֚b; a◌᷺◌ᨘ◌̖◌֚b; ) LATIN SMALL LETTER A, BUGINESE VOWEL SIGN U, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1A60 0062;0061 3099 094D 1A60 05B0 0062;0061 3099 094D 1A60 05B0 0062;0061 3099 094D 1A60 05B0 0062;0061 3099 094D 1A60 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œá© b; a◌゙◌à¥â—Œá© â—ŒÖ°b; a◌゙◌à¥â—Œá© â—ŒÖ°b; a◌゙◌à¥â—Œá© â—ŒÖ°b; a◌゙◌à¥â—Œá© â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, TAI THAM SIGN SAKOT, LATIN SMALL LETTER B
+0061 1A60 05B0 094D 3099 0062;0061 3099 1A60 094D 05B0 0062;0061 3099 1A60 094D 05B0 0062;0061 3099 1A60 094D 05B0 0062;0061 3099 1A60 094D 05B0 0062; # (a◌᩠◌ְ◌à¥â—Œã‚™b; a◌゙◌᩠◌à¥â—ŒÖ°b; a◌゙◌᩠◌à¥â—ŒÖ°b; a◌゙◌᩠◌à¥â—ŒÖ°b; a◌゙◌᩠◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, TAI THAM SIGN SAKOT, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1A75 0062;00E0 05AE 1A75 0315 0062;0061 05AE 0300 1A75 0315 0062;00E0 05AE 1A75 0315 0062;0061 05AE 0300 1A75 0315 0062; # (a◌̕◌̀◌֮◌᩵b; à◌֮◌᩵◌̕b; a◌֮◌̀◌᩵◌̕b; à◌֮◌᩵◌̕b; a◌֮◌̀◌᩵◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI THAM SIGN TONE-1, LATIN SMALL LETTER B
+0061 1A75 0315 0300 05AE 0062;0061 05AE 1A75 0300 0315 0062;0061 05AE 1A75 0300 0315 0062;0061 05AE 1A75 0300 0315 0062;0061 05AE 1A75 0300 0315 0062; # (a◌᩵◌̕◌̀◌֮b; a◌֮◌᩵◌̀◌̕b; a◌֮◌᩵◌̀◌̕b; a◌֮◌᩵◌̀◌̕b; a◌֮◌᩵◌̀◌̕b; ) LATIN SMALL LETTER A, TAI THAM SIGN TONE-1, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1A76 0062;00E0 05AE 1A76 0315 0062;0061 05AE 0300 1A76 0315 0062;00E0 05AE 1A76 0315 0062;0061 05AE 0300 1A76 0315 0062; # (a◌̕◌̀◌֮◌᩶b; à◌֮◌᩶◌̕b; a◌֮◌̀◌᩶◌̕b; à◌֮◌᩶◌̕b; a◌֮◌̀◌᩶◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI THAM SIGN TONE-2, LATIN SMALL LETTER B
+0061 1A76 0315 0300 05AE 0062;0061 05AE 1A76 0300 0315 0062;0061 05AE 1A76 0300 0315 0062;0061 05AE 1A76 0300 0315 0062;0061 05AE 1A76 0300 0315 0062; # (a◌᩶◌̕◌̀◌֮b; a◌֮◌᩶◌̀◌̕b; a◌֮◌᩶◌̀◌̕b; a◌֮◌᩶◌̀◌̕b; a◌֮◌᩶◌̀◌̕b; ) LATIN SMALL LETTER A, TAI THAM SIGN TONE-2, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1A77 0062;00E0 05AE 1A77 0315 0062;0061 05AE 0300 1A77 0315 0062;00E0 05AE 1A77 0315 0062;0061 05AE 0300 1A77 0315 0062; # (a◌̕◌̀◌֮◌᩷b; à◌֮◌᩷◌̕b; a◌֮◌̀◌᩷◌̕b; à◌֮◌᩷◌̕b; a◌֮◌̀◌᩷◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI THAM SIGN KHUEN TONE-3, LATIN SMALL LETTER B
+0061 1A77 0315 0300 05AE 0062;0061 05AE 1A77 0300 0315 0062;0061 05AE 1A77 0300 0315 0062;0061 05AE 1A77 0300 0315 0062;0061 05AE 1A77 0300 0315 0062; # (a◌᩷◌̕◌̀◌֮b; a◌֮◌᩷◌̀◌̕b; a◌֮◌᩷◌̀◌̕b; a◌֮◌᩷◌̀◌̕b; a◌֮◌᩷◌̀◌̕b; ) LATIN SMALL LETTER A, TAI THAM SIGN KHUEN TONE-3, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1A78 0062;00E0 05AE 1A78 0315 0062;0061 05AE 0300 1A78 0315 0062;00E0 05AE 1A78 0315 0062;0061 05AE 0300 1A78 0315 0062; # (a◌̕◌̀◌֮◌᩸b; à◌֮◌᩸◌̕b; a◌֮◌̀◌᩸◌̕b; à◌֮◌᩸◌̕b; a◌֮◌̀◌᩸◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI THAM SIGN KHUEN TONE-4, LATIN SMALL LETTER B
+0061 1A78 0315 0300 05AE 0062;0061 05AE 1A78 0300 0315 0062;0061 05AE 1A78 0300 0315 0062;0061 05AE 1A78 0300 0315 0062;0061 05AE 1A78 0300 0315 0062; # (a◌᩸◌̕◌̀◌֮b; a◌֮◌᩸◌̀◌̕b; a◌֮◌᩸◌̀◌̕b; a◌֮◌᩸◌̀◌̕b; a◌֮◌᩸◌̀◌̕b; ) LATIN SMALL LETTER A, TAI THAM SIGN KHUEN TONE-4, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1A79 0062;00E0 05AE 1A79 0315 0062;0061 05AE 0300 1A79 0315 0062;00E0 05AE 1A79 0315 0062;0061 05AE 0300 1A79 0315 0062; # (a◌̕◌̀◌֮◌᩹b; à◌֮◌᩹◌̕b; a◌֮◌̀◌᩹◌̕b; à◌֮◌᩹◌̕b; a◌֮◌̀◌᩹◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI THAM SIGN KHUEN TONE-5, LATIN SMALL LETTER B
+0061 1A79 0315 0300 05AE 0062;0061 05AE 1A79 0300 0315 0062;0061 05AE 1A79 0300 0315 0062;0061 05AE 1A79 0300 0315 0062;0061 05AE 1A79 0300 0315 0062; # (a◌᩹◌̕◌̀◌֮b; a◌֮◌᩹◌̀◌̕b; a◌֮◌᩹◌̀◌̕b; a◌֮◌᩹◌̀◌̕b; a◌֮◌᩹◌̀◌̕b; ) LATIN SMALL LETTER A, TAI THAM SIGN KHUEN TONE-5, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1A7A 0062;00E0 05AE 1A7A 0315 0062;0061 05AE 0300 1A7A 0315 0062;00E0 05AE 1A7A 0315 0062;0061 05AE 0300 1A7A 0315 0062; # (a◌̕◌̀◌֮◌᩺b; à◌֮◌᩺◌̕b; a◌֮◌̀◌᩺◌̕b; à◌֮◌᩺◌̕b; a◌֮◌̀◌᩺◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI THAM SIGN RA HAAM, LATIN SMALL LETTER B
+0061 1A7A 0315 0300 05AE 0062;0061 05AE 1A7A 0300 0315 0062;0061 05AE 1A7A 0300 0315 0062;0061 05AE 1A7A 0300 0315 0062;0061 05AE 1A7A 0300 0315 0062; # (a◌᩺◌̕◌̀◌֮b; a◌֮◌᩺◌̀◌̕b; a◌֮◌᩺◌̀◌̕b; a◌֮◌᩺◌̀◌̕b; a◌֮◌᩺◌̀◌̕b; ) LATIN SMALL LETTER A, TAI THAM SIGN RA HAAM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1A7B 0062;00E0 05AE 1A7B 0315 0062;0061 05AE 0300 1A7B 0315 0062;00E0 05AE 1A7B 0315 0062;0061 05AE 0300 1A7B 0315 0062; # (a◌̕◌̀◌֮◌᩻b; à◌֮◌᩻◌̕b; a◌֮◌̀◌᩻◌̕b; à◌֮◌᩻◌̕b; a◌֮◌̀◌᩻◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI THAM SIGN MAI SAM, LATIN SMALL LETTER B
+0061 1A7B 0315 0300 05AE 0062;0061 05AE 1A7B 0300 0315 0062;0061 05AE 1A7B 0300 0315 0062;0061 05AE 1A7B 0300 0315 0062;0061 05AE 1A7B 0300 0315 0062; # (a◌᩻◌̕◌̀◌֮b; a◌֮◌᩻◌̀◌̕b; a◌֮◌᩻◌̀◌̕b; a◌֮◌᩻◌̀◌̕b; a◌֮◌᩻◌̀◌̕b; ) LATIN SMALL LETTER A, TAI THAM SIGN MAI SAM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1A7C 0062;00E0 05AE 1A7C 0315 0062;0061 05AE 0300 1A7C 0315 0062;00E0 05AE 1A7C 0315 0062;0061 05AE 0300 1A7C 0315 0062; # (a◌̕◌̀◌֮◌᩼b; à◌֮◌᩼◌̕b; a◌֮◌̀◌᩼◌̕b; à◌֮◌᩼◌̕b; a◌֮◌̀◌᩼◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI THAM SIGN KHUEN-LUE KARAN, LATIN SMALL LETTER B
+0061 1A7C 0315 0300 05AE 0062;0061 05AE 1A7C 0300 0315 0062;0061 05AE 1A7C 0300 0315 0062;0061 05AE 1A7C 0300 0315 0062;0061 05AE 1A7C 0300 0315 0062; # (a◌᩼◌̕◌̀◌֮b; a◌֮◌᩼◌̀◌̕b; a◌֮◌᩼◌̀◌̕b; a◌֮◌᩼◌̀◌̕b; a◌֮◌᩼◌̀◌̕b; ) LATIN SMALL LETTER A, TAI THAM SIGN KHUEN-LUE KARAN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1A7F 0062;0061 1DFA 0316 1A7F 059A 0062;0061 1DFA 0316 1A7F 059A 0062;0061 1DFA 0316 1A7F 059A 0062;0061 1DFA 0316 1A7F 059A 0062; # (a◌֚◌̖◌᷺◌᩿b; a◌᷺◌̖◌᩿◌֚b; a◌᷺◌̖◌᩿◌֚b; a◌᷺◌̖◌᩿◌֚b; a◌᷺◌̖◌᩿◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, TAI THAM COMBINING CRYPTOGRAMMIC DOT, LATIN SMALL LETTER B
+0061 1A7F 059A 0316 1DFA 0062;0061 1DFA 1A7F 0316 059A 0062;0061 1DFA 1A7F 0316 059A 0062;0061 1DFA 1A7F 0316 059A 0062;0061 1DFA 1A7F 0316 059A 0062; # (a◌᩿◌֚◌̖◌᷺b; a◌᷺◌᩿◌̖◌֚b; a◌᷺◌᩿◌̖◌֚b; a◌᷺◌᩿◌̖◌֚b; a◌᷺◌᩿◌̖◌֚b; ) LATIN SMALL LETTER A, TAI THAM COMBINING CRYPTOGRAMMIC DOT, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AB0 0062;00E0 05AE 1AB0 0315 0062;0061 05AE 0300 1AB0 0315 0062;00E0 05AE 1AB0 0315 0062;0061 05AE 0300 1AB0 0315 0062; # (a◌̕◌̀◌֮◌᪰b; à◌֮◌᪰◌̕b; a◌֮◌̀◌᪰◌̕b; à◌֮◌᪰◌̕b; a◌֮◌̀◌᪰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOUBLED CIRCUMFLEX ACCENT, LATIN SMALL LETTER B
+0061 1AB0 0315 0300 05AE 0062;0061 05AE 1AB0 0300 0315 0062;0061 05AE 1AB0 0300 0315 0062;0061 05AE 1AB0 0300 0315 0062;0061 05AE 1AB0 0300 0315 0062; # (a◌᪰◌̕◌̀◌֮b; a◌֮◌᪰◌̀◌̕b; a◌֮◌᪰◌̀◌̕b; a◌֮◌᪰◌̀◌̕b; a◌֮◌᪰◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOUBLED CIRCUMFLEX ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AB1 0062;00E0 05AE 1AB1 0315 0062;0061 05AE 0300 1AB1 0315 0062;00E0 05AE 1AB1 0315 0062;0061 05AE 0300 1AB1 0315 0062; # (a◌̕◌̀◌֮◌᪱b; à◌֮◌᪱◌̕b; a◌֮◌̀◌᪱◌̕b; à◌֮◌᪱◌̕b; a◌֮◌̀◌᪱◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DIAERESIS-RING, LATIN SMALL LETTER B
+0061 1AB1 0315 0300 05AE 0062;0061 05AE 1AB1 0300 0315 0062;0061 05AE 1AB1 0300 0315 0062;0061 05AE 1AB1 0300 0315 0062;0061 05AE 1AB1 0300 0315 0062; # (a◌᪱◌̕◌̀◌֮b; a◌֮◌᪱◌̀◌̕b; a◌֮◌᪱◌̀◌̕b; a◌֮◌᪱◌̀◌̕b; a◌֮◌᪱◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DIAERESIS-RING, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AB2 0062;00E0 05AE 1AB2 0315 0062;0061 05AE 0300 1AB2 0315 0062;00E0 05AE 1AB2 0315 0062;0061 05AE 0300 1AB2 0315 0062; # (a◌̕◌̀◌֮◌᪲b; à◌֮◌᪲◌̕b; a◌֮◌̀◌᪲◌̕b; à◌֮◌᪲◌̕b; a◌֮◌̀◌᪲◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING INFINITY, LATIN SMALL LETTER B
+0061 1AB2 0315 0300 05AE 0062;0061 05AE 1AB2 0300 0315 0062;0061 05AE 1AB2 0300 0315 0062;0061 05AE 1AB2 0300 0315 0062;0061 05AE 1AB2 0300 0315 0062; # (a◌᪲◌̕◌̀◌֮b; a◌֮◌᪲◌̀◌̕b; a◌֮◌᪲◌̀◌̕b; a◌֮◌᪲◌̀◌̕b; a◌֮◌᪲◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING INFINITY, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AB3 0062;00E0 05AE 1AB3 0315 0062;0061 05AE 0300 1AB3 0315 0062;00E0 05AE 1AB3 0315 0062;0061 05AE 0300 1AB3 0315 0062; # (a◌̕◌̀◌֮◌᪳b; à◌֮◌᪳◌̕b; a◌֮◌̀◌᪳◌̕b; à◌֮◌᪳◌̕b; a◌֮◌̀◌᪳◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOWNWARDS ARROW, LATIN SMALL LETTER B
+0061 1AB3 0315 0300 05AE 0062;0061 05AE 1AB3 0300 0315 0062;0061 05AE 1AB3 0300 0315 0062;0061 05AE 1AB3 0300 0315 0062;0061 05AE 1AB3 0300 0315 0062; # (a◌᪳◌̕◌̀◌֮b; a◌֮◌᪳◌̀◌̕b; a◌֮◌᪳◌̀◌̕b; a◌֮◌᪳◌̀◌̕b; a◌֮◌᪳◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOWNWARDS ARROW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AB4 0062;00E0 05AE 1AB4 0315 0062;0061 05AE 0300 1AB4 0315 0062;00E0 05AE 1AB4 0315 0062;0061 05AE 0300 1AB4 0315 0062; # (a◌̕◌̀◌֮◌᪴b; à◌֮◌᪴◌̕b; a◌֮◌̀◌᪴◌̕b; à◌֮◌᪴◌̕b; a◌֮◌̀◌᪴◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING TRIPLE DOT, LATIN SMALL LETTER B
+0061 1AB4 0315 0300 05AE 0062;0061 05AE 1AB4 0300 0315 0062;0061 05AE 1AB4 0300 0315 0062;0061 05AE 1AB4 0300 0315 0062;0061 05AE 1AB4 0300 0315 0062; # (a◌᪴◌̕◌̀◌֮b; a◌֮◌᪴◌̀◌̕b; a◌֮◌᪴◌̀◌̕b; a◌֮◌᪴◌̀◌̕b; a◌֮◌᪴◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING TRIPLE DOT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1AB5 0062;0061 1DFA 0316 1AB5 059A 0062;0061 1DFA 0316 1AB5 059A 0062;0061 1DFA 0316 1AB5 059A 0062;0061 1DFA 0316 1AB5 059A 0062; # (a◌֚◌̖◌᷺◌᪵b; a◌᷺◌̖◌᪵◌֚b; a◌᷺◌̖◌᪵◌֚b; a◌᷺◌̖◌᪵◌֚b; a◌᷺◌̖◌᪵◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING X-X BELOW, LATIN SMALL LETTER B
+0061 1AB5 059A 0316 1DFA 0062;0061 1DFA 1AB5 0316 059A 0062;0061 1DFA 1AB5 0316 059A 0062;0061 1DFA 1AB5 0316 059A 0062;0061 1DFA 1AB5 0316 059A 0062; # (a◌᪵◌֚◌̖◌᷺b; a◌᷺◌᪵◌̖◌֚b; a◌᷺◌᪵◌̖◌֚b; a◌᷺◌᪵◌̖◌֚b; a◌᷺◌᪵◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING X-X BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1AB6 0062;0061 1DFA 0316 1AB6 059A 0062;0061 1DFA 0316 1AB6 059A 0062;0061 1DFA 0316 1AB6 059A 0062;0061 1DFA 0316 1AB6 059A 0062; # (a◌֚◌̖◌᷺◌᪶b; a◌᷺◌̖◌᪶◌֚b; a◌᷺◌̖◌᪶◌֚b; a◌᷺◌̖◌᪶◌֚b; a◌᷺◌̖◌᪶◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING WIGGLY LINE BELOW, LATIN SMALL LETTER B
+0061 1AB6 059A 0316 1DFA 0062;0061 1DFA 1AB6 0316 059A 0062;0061 1DFA 1AB6 0316 059A 0062;0061 1DFA 1AB6 0316 059A 0062;0061 1DFA 1AB6 0316 059A 0062; # (a◌᪶◌֚◌̖◌᷺b; a◌᷺◌᪶◌̖◌֚b; a◌᷺◌᪶◌̖◌֚b; a◌᷺◌᪶◌̖◌֚b; a◌᷺◌᪶◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING WIGGLY LINE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1AB7 0062;0061 1DFA 0316 1AB7 059A 0062;0061 1DFA 0316 1AB7 059A 0062;0061 1DFA 0316 1AB7 059A 0062;0061 1DFA 0316 1AB7 059A 0062; # (a◌֚◌̖◌᷺◌᪷b; a◌᷺◌̖◌᪷◌֚b; a◌᷺◌̖◌᪷◌֚b; a◌᷺◌̖◌᪷◌֚b; a◌᷺◌̖◌᪷◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING OPEN MARK BELOW, LATIN SMALL LETTER B
+0061 1AB7 059A 0316 1DFA 0062;0061 1DFA 1AB7 0316 059A 0062;0061 1DFA 1AB7 0316 059A 0062;0061 1DFA 1AB7 0316 059A 0062;0061 1DFA 1AB7 0316 059A 0062; # (a◌᪷◌֚◌̖◌᷺b; a◌᷺◌᪷◌̖◌֚b; a◌᷺◌᪷◌̖◌֚b; a◌᷺◌᪷◌̖◌֚b; a◌᷺◌᪷◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING OPEN MARK BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1AB8 0062;0061 1DFA 0316 1AB8 059A 0062;0061 1DFA 0316 1AB8 059A 0062;0061 1DFA 0316 1AB8 059A 0062;0061 1DFA 0316 1AB8 059A 0062; # (a◌֚◌̖◌᷺◌᪸b; a◌᷺◌̖◌᪸◌֚b; a◌᷺◌̖◌᪸◌֚b; a◌᷺◌̖◌᪸◌֚b; a◌᷺◌̖◌᪸◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING DOUBLE OPEN MARK BELOW, LATIN SMALL LETTER B
+0061 1AB8 059A 0316 1DFA 0062;0061 1DFA 1AB8 0316 059A 0062;0061 1DFA 1AB8 0316 059A 0062;0061 1DFA 1AB8 0316 059A 0062;0061 1DFA 1AB8 0316 059A 0062; # (a◌᪸◌֚◌̖◌᷺b; a◌᷺◌᪸◌̖◌֚b; a◌᷺◌᪸◌̖◌֚b; a◌᷺◌᪸◌̖◌֚b; a◌᷺◌᪸◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING DOUBLE OPEN MARK BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1AB9 0062;0061 1DFA 0316 1AB9 059A 0062;0061 1DFA 0316 1AB9 059A 0062;0061 1DFA 0316 1AB9 059A 0062;0061 1DFA 0316 1AB9 059A 0062; # (a◌֚◌̖◌᷺◌᪹b; a◌᷺◌̖◌᪹◌֚b; a◌᷺◌̖◌᪹◌֚b; a◌᷺◌̖◌᪹◌֚b; a◌᷺◌̖◌᪹◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LIGHT CENTRALIZATION STROKE BELOW, LATIN SMALL LETTER B
+0061 1AB9 059A 0316 1DFA 0062;0061 1DFA 1AB9 0316 059A 0062;0061 1DFA 1AB9 0316 059A 0062;0061 1DFA 1AB9 0316 059A 0062;0061 1DFA 1AB9 0316 059A 0062; # (a◌᪹◌֚◌̖◌᷺b; a◌᷺◌᪹◌̖◌֚b; a◌᷺◌᪹◌̖◌֚b; a◌᷺◌᪹◌̖◌֚b; a◌᷺◌᪹◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LIGHT CENTRALIZATION STROKE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1ABA 0062;0061 1DFA 0316 1ABA 059A 0062;0061 1DFA 0316 1ABA 059A 0062;0061 1DFA 0316 1ABA 059A 0062;0061 1DFA 0316 1ABA 059A 0062; # (a◌֚◌̖◌᷺◌᪺b; a◌᷺◌̖◌᪺◌֚b; a◌᷺◌̖◌᪺◌֚b; a◌᷺◌̖◌᪺◌֚b; a◌᷺◌̖◌᪺◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING STRONG CENTRALIZATION STROKE BELOW, LATIN SMALL LETTER B
+0061 1ABA 059A 0316 1DFA 0062;0061 1DFA 1ABA 0316 059A 0062;0061 1DFA 1ABA 0316 059A 0062;0061 1DFA 1ABA 0316 059A 0062;0061 1DFA 1ABA 0316 059A 0062; # (a◌᪺◌֚◌̖◌᷺b; a◌᷺◌᪺◌̖◌֚b; a◌᷺◌᪺◌̖◌֚b; a◌᷺◌᪺◌̖◌֚b; a◌᷺◌᪺◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING STRONG CENTRALIZATION STROKE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1ABB 0062;00E0 05AE 1ABB 0315 0062;0061 05AE 0300 1ABB 0315 0062;00E0 05AE 1ABB 0315 0062;0061 05AE 0300 1ABB 0315 0062; # (a◌̕◌̀◌֮◌᪻b; à◌֮◌᪻◌̕b; a◌֮◌̀◌᪻◌̕b; à◌֮◌᪻◌̕b; a◌֮◌̀◌᪻◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING PARENTHESES ABOVE, LATIN SMALL LETTER B
+0061 1ABB 0315 0300 05AE 0062;0061 05AE 1ABB 0300 0315 0062;0061 05AE 1ABB 0300 0315 0062;0061 05AE 1ABB 0300 0315 0062;0061 05AE 1ABB 0300 0315 0062; # (a◌᪻◌̕◌̀◌֮b; a◌֮◌᪻◌̀◌̕b; a◌֮◌᪻◌̀◌̕b; a◌֮◌᪻◌̀◌̕b; a◌֮◌᪻◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING PARENTHESES ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1ABC 0062;00E0 05AE 1ABC 0315 0062;0061 05AE 0300 1ABC 0315 0062;00E0 05AE 1ABC 0315 0062;0061 05AE 0300 1ABC 0315 0062; # (a◌̕◌̀◌֮◌᪼b; à◌֮◌᪼◌̕b; a◌֮◌̀◌᪼◌̕b; à◌֮◌᪼◌̕b; a◌֮◌̀◌᪼◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOUBLE PARENTHESES ABOVE, LATIN SMALL LETTER B
+0061 1ABC 0315 0300 05AE 0062;0061 05AE 1ABC 0300 0315 0062;0061 05AE 1ABC 0300 0315 0062;0061 05AE 1ABC 0300 0315 0062;0061 05AE 1ABC 0300 0315 0062; # (a◌᪼◌̕◌̀◌֮b; a◌֮◌᪼◌̀◌̕b; a◌֮◌᪼◌̀◌̕b; a◌֮◌᪼◌̀◌̕b; a◌֮◌᪼◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOUBLE PARENTHESES ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1ABD 0062;0061 1DFA 0316 1ABD 059A 0062;0061 1DFA 0316 1ABD 059A 0062;0061 1DFA 0316 1ABD 059A 0062;0061 1DFA 0316 1ABD 059A 0062; # (a◌֚◌̖◌᷺◌᪽b; a◌᷺◌̖◌᪽◌֚b; a◌᷺◌̖◌᪽◌֚b; a◌᷺◌̖◌᪽◌֚b; a◌᷺◌̖◌᪽◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING PARENTHESES BELOW, LATIN SMALL LETTER B
+0061 1ABD 059A 0316 1DFA 0062;0061 1DFA 1ABD 0316 059A 0062;0061 1DFA 1ABD 0316 059A 0062;0061 1DFA 1ABD 0316 059A 0062;0061 1DFA 1ABD 0316 059A 0062; # (a◌᪽◌֚◌̖◌᷺b; a◌᷺◌᪽◌̖◌֚b; a◌᷺◌᪽◌̖◌֚b; a◌᷺◌᪽◌̖◌֚b; a◌᷺◌᪽◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING PARENTHESES BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1ABF 0062;0061 1DFA 0316 1ABF 059A 0062;0061 1DFA 0316 1ABF 059A 0062;0061 1DFA 0316 1ABF 059A 0062;0061 1DFA 0316 1ABF 059A 0062; # (a◌֚◌̖◌᷺◌ᪿb; a◌᷺◌̖◌ᪿ◌֚b; a◌᷺◌̖◌ᪿ◌֚b; a◌᷺◌̖◌ᪿ◌֚b; a◌᷺◌̖◌ᪿ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LATIN SMALL LETTER W BELOW, LATIN SMALL LETTER B
+0061 1ABF 059A 0316 1DFA 0062;0061 1DFA 1ABF 0316 059A 0062;0061 1DFA 1ABF 0316 059A 0062;0061 1DFA 1ABF 0316 059A 0062;0061 1DFA 1ABF 0316 059A 0062; # (a◌ᪿ◌֚◌̖◌᷺b; a◌᷺◌ᪿ◌̖◌֚b; a◌᷺◌ᪿ◌̖◌֚b; a◌᷺◌ᪿ◌̖◌֚b; a◌᷺◌ᪿ◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER W BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1AC0 0062;0061 1DFA 0316 1AC0 059A 0062;0061 1DFA 0316 1AC0 059A 0062;0061 1DFA 0316 1AC0 059A 0062;0061 1DFA 0316 1AC0 059A 0062; # (a◌֚◌̖◌᷺◌ᫀb; a◌᷺◌̖◌ᫀ◌֚b; a◌᷺◌̖◌ᫀ◌֚b; a◌᷺◌̖◌ᫀ◌֚b; a◌᷺◌̖◌ᫀ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LATIN SMALL LETTER TURNED W BELOW, LATIN SMALL LETTER B
+0061 1AC0 059A 0316 1DFA 0062;0061 1DFA 1AC0 0316 059A 0062;0061 1DFA 1AC0 0316 059A 0062;0061 1DFA 1AC0 0316 059A 0062;0061 1DFA 1AC0 0316 059A 0062; # (a◌ᫀ◌֚◌̖◌᷺b; a◌᷺◌ᫀ◌̖◌֚b; a◌᷺◌ᫀ◌̖◌֚b; a◌᷺◌ᫀ◌̖◌֚b; a◌᷺◌ᫀ◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER TURNED W BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AC1 0062;00E0 05AE 1AC1 0315 0062;0061 05AE 0300 1AC1 0315 0062;00E0 05AE 1AC1 0315 0062;0061 05AE 0300 1AC1 0315 0062; # (a◌̕◌̀◌֮◌á«b; à◌֮◌á«â—ŒÌ•b; a◌֮◌̀◌á«â—ŒÌ•b; à◌֮◌á«â—ŒÌ•b; a◌֮◌̀◌á«â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LEFT PARENTHESIS ABOVE LEFT, LATIN SMALL LETTER B
+0061 1AC1 0315 0300 05AE 0062;0061 05AE 1AC1 0300 0315 0062;0061 05AE 1AC1 0300 0315 0062;0061 05AE 1AC1 0300 0315 0062;0061 05AE 1AC1 0300 0315 0062; # (aâ—Œá«â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌á«â—ŒÌ€â—ŒÌ•b; a◌֮◌á«â—ŒÌ€â—ŒÌ•b; a◌֮◌á«â—ŒÌ€â—ŒÌ•b; a◌֮◌á«â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING LEFT PARENTHESIS ABOVE LEFT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AC2 0062;00E0 05AE 1AC2 0315 0062;0061 05AE 0300 1AC2 0315 0062;00E0 05AE 1AC2 0315 0062;0061 05AE 0300 1AC2 0315 0062; # (a◌̕◌̀◌֮◌᫂b; à◌֮◌᫂◌̕b; a◌֮◌̀◌᫂◌̕b; à◌֮◌᫂◌̕b; a◌֮◌̀◌᫂◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING RIGHT PARENTHESIS ABOVE RIGHT, LATIN SMALL LETTER B
+0061 1AC2 0315 0300 05AE 0062;0061 05AE 1AC2 0300 0315 0062;0061 05AE 1AC2 0300 0315 0062;0061 05AE 1AC2 0300 0315 0062;0061 05AE 1AC2 0300 0315 0062; # (a◌᫂◌̕◌̀◌֮b; a◌֮◌᫂◌̀◌̕b; a◌֮◌᫂◌̀◌̕b; a◌֮◌᫂◌̀◌̕b; a◌֮◌᫂◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING RIGHT PARENTHESIS ABOVE RIGHT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1AC3 0062;0061 1DFA 0316 1AC3 059A 0062;0061 1DFA 0316 1AC3 059A 0062;0061 1DFA 0316 1AC3 059A 0062;0061 1DFA 0316 1AC3 059A 0062; # (a◌֚◌̖◌᷺◌᫃b; a◌᷺◌̖◌᫃◌֚b; a◌᷺◌̖◌᫃◌֚b; a◌᷺◌̖◌᫃◌֚b; a◌᷺◌̖◌᫃◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LEFT PARENTHESIS BELOW LEFT, LATIN SMALL LETTER B
+0061 1AC3 059A 0316 1DFA 0062;0061 1DFA 1AC3 0316 059A 0062;0061 1DFA 1AC3 0316 059A 0062;0061 1DFA 1AC3 0316 059A 0062;0061 1DFA 1AC3 0316 059A 0062; # (a◌᫃◌֚◌̖◌᷺b; a◌᷺◌᫃◌̖◌֚b; a◌᷺◌᫃◌̖◌֚b; a◌᷺◌᫃◌̖◌֚b; a◌᷺◌᫃◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LEFT PARENTHESIS BELOW LEFT, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1AC4 0062;0061 1DFA 0316 1AC4 059A 0062;0061 1DFA 0316 1AC4 059A 0062;0061 1DFA 0316 1AC4 059A 0062;0061 1DFA 0316 1AC4 059A 0062; # (a◌֚◌̖◌᷺◌᫄b; a◌᷺◌̖◌᫄◌֚b; a◌᷺◌̖◌᫄◌֚b; a◌᷺◌̖◌᫄◌֚b; a◌᷺◌̖◌᫄◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING RIGHT PARENTHESIS BELOW RIGHT, LATIN SMALL LETTER B
+0061 1AC4 059A 0316 1DFA 0062;0061 1DFA 1AC4 0316 059A 0062;0061 1DFA 1AC4 0316 059A 0062;0061 1DFA 1AC4 0316 059A 0062;0061 1DFA 1AC4 0316 059A 0062; # (a◌᫄◌֚◌̖◌᷺b; a◌᷺◌᫄◌̖◌֚b; a◌᷺◌᫄◌̖◌֚b; a◌᷺◌᫄◌̖◌֚b; a◌᷺◌᫄◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING RIGHT PARENTHESIS BELOW RIGHT, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AC5 0062;00E0 05AE 1AC5 0315 0062;0061 05AE 0300 1AC5 0315 0062;00E0 05AE 1AC5 0315 0062;0061 05AE 0300 1AC5 0315 0062; # (a◌̕◌̀◌֮◌᫅b; à◌֮◌᫅◌̕b; a◌֮◌̀◌᫅◌̕b; à◌֮◌᫅◌̕b; a◌֮◌̀◌᫅◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING SQUARE BRACKETS ABOVE, LATIN SMALL LETTER B
+0061 1AC5 0315 0300 05AE 0062;0061 05AE 1AC5 0300 0315 0062;0061 05AE 1AC5 0300 0315 0062;0061 05AE 1AC5 0300 0315 0062;0061 05AE 1AC5 0300 0315 0062; # (a◌᫅◌̕◌̀◌֮b; a◌֮◌᫅◌̀◌̕b; a◌֮◌᫅◌̀◌̕b; a◌֮◌᫅◌̀◌̕b; a◌֮◌᫅◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING SQUARE BRACKETS ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AC6 0062;00E0 05AE 1AC6 0315 0062;0061 05AE 0300 1AC6 0315 0062;00E0 05AE 1AC6 0315 0062;0061 05AE 0300 1AC6 0315 0062; # (a◌̕◌̀◌֮◌᫆b; à◌֮◌᫆◌̕b; a◌֮◌̀◌᫆◌̕b; à◌֮◌᫆◌̕b; a◌֮◌̀◌᫆◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING NUMBER SIGN ABOVE, LATIN SMALL LETTER B
+0061 1AC6 0315 0300 05AE 0062;0061 05AE 1AC6 0300 0315 0062;0061 05AE 1AC6 0300 0315 0062;0061 05AE 1AC6 0300 0315 0062;0061 05AE 1AC6 0300 0315 0062; # (a◌᫆◌̕◌̀◌֮b; a◌֮◌᫆◌̀◌̕b; a◌֮◌᫆◌̀◌̕b; a◌֮◌᫆◌̀◌̕b; a◌֮◌᫆◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING NUMBER SIGN ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AC7 0062;00E0 05AE 1AC7 0315 0062;0061 05AE 0300 1AC7 0315 0062;00E0 05AE 1AC7 0315 0062;0061 05AE 0300 1AC7 0315 0062; # (a◌̕◌̀◌֮◌᫇b; à◌֮◌᫇◌̕b; a◌֮◌̀◌᫇◌̕b; à◌֮◌᫇◌̕b; a◌֮◌̀◌᫇◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING INVERTED DOUBLE ARCH ABOVE, LATIN SMALL LETTER B
+0061 1AC7 0315 0300 05AE 0062;0061 05AE 1AC7 0300 0315 0062;0061 05AE 1AC7 0300 0315 0062;0061 05AE 1AC7 0300 0315 0062;0061 05AE 1AC7 0300 0315 0062; # (a◌᫇◌̕◌̀◌֮b; a◌֮◌᫇◌̀◌̕b; a◌֮◌᫇◌̀◌̕b; a◌֮◌᫇◌̀◌̕b; a◌֮◌᫇◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING INVERTED DOUBLE ARCH ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AC8 0062;00E0 05AE 1AC8 0315 0062;0061 05AE 0300 1AC8 0315 0062;00E0 05AE 1AC8 0315 0062;0061 05AE 0300 1AC8 0315 0062; # (a◌̕◌̀◌֮◌᫈b; à◌֮◌᫈◌̕b; a◌֮◌̀◌᫈◌̕b; à◌֮◌᫈◌̕b; a◌֮◌̀◌᫈◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING PLUS SIGN ABOVE, LATIN SMALL LETTER B
+0061 1AC8 0315 0300 05AE 0062;0061 05AE 1AC8 0300 0315 0062;0061 05AE 1AC8 0300 0315 0062;0061 05AE 1AC8 0300 0315 0062;0061 05AE 1AC8 0300 0315 0062; # (a◌᫈◌̕◌̀◌֮b; a◌֮◌᫈◌̀◌̕b; a◌֮◌᫈◌̀◌̕b; a◌֮◌᫈◌̀◌̕b; a◌֮◌᫈◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING PLUS SIGN ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1AC9 0062;00E0 05AE 1AC9 0315 0062;0061 05AE 0300 1AC9 0315 0062;00E0 05AE 1AC9 0315 0062;0061 05AE 0300 1AC9 0315 0062; # (a◌̕◌̀◌֮◌᫉b; à◌֮◌᫉◌̕b; a◌֮◌̀◌᫉◌̕b; à◌֮◌᫉◌̕b; a◌֮◌̀◌᫉◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOUBLE PLUS SIGN ABOVE, LATIN SMALL LETTER B
+0061 1AC9 0315 0300 05AE 0062;0061 05AE 1AC9 0300 0315 0062;0061 05AE 1AC9 0300 0315 0062;0061 05AE 1AC9 0300 0315 0062;0061 05AE 1AC9 0300 0315 0062; # (a◌᫉◌̕◌̀◌֮b; a◌֮◌᫉◌̀◌̕b; a◌֮◌᫉◌̀◌̕b; a◌֮◌᫉◌̀◌̕b; a◌֮◌᫉◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOUBLE PLUS SIGN ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1ACA 0062;0061 1DFA 0316 1ACA 059A 0062;0061 1DFA 0316 1ACA 059A 0062;0061 1DFA 0316 1ACA 059A 0062;0061 1DFA 0316 1ACA 059A 0062; # (a◌֚◌̖◌᷺◌᫊b; a◌᷺◌̖◌᫊◌֚b; a◌᷺◌̖◌᫊◌֚b; a◌᷺◌̖◌᫊◌֚b; a◌᷺◌̖◌᫊◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING DOUBLE PLUS SIGN BELOW, LATIN SMALL LETTER B
+0061 1ACA 059A 0316 1DFA 0062;0061 1DFA 1ACA 0316 059A 0062;0061 1DFA 1ACA 0316 059A 0062;0061 1DFA 1ACA 0316 059A 0062;0061 1DFA 1ACA 0316 059A 0062; # (a◌᫊◌֚◌̖◌᷺b; a◌᷺◌᫊◌̖◌֚b; a◌᷺◌᫊◌̖◌֚b; a◌᷺◌᫊◌̖◌֚b; a◌᷺◌᫊◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING DOUBLE PLUS SIGN BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1ACB 0062;00E0 05AE 1ACB 0315 0062;0061 05AE 0300 1ACB 0315 0062;00E0 05AE 1ACB 0315 0062;0061 05AE 0300 1ACB 0315 0062; # (a◌̕◌̀◌֮◌᫋b; à◌֮◌᫋◌̕b; a◌֮◌̀◌᫋◌̕b; à◌֮◌᫋◌̕b; a◌֮◌̀◌᫋◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING TRIPLE ACUTE ACCENT, LATIN SMALL LETTER B
+0061 1ACB 0315 0300 05AE 0062;0061 05AE 1ACB 0300 0315 0062;0061 05AE 1ACB 0300 0315 0062;0061 05AE 1ACB 0300 0315 0062;0061 05AE 1ACB 0300 0315 0062; # (a◌᫋◌̕◌̀◌֮b; a◌֮◌᫋◌̀◌̕b; a◌֮◌᫋◌̀◌̕b; a◌֮◌᫋◌̀◌̕b; a◌֮◌᫋◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING TRIPLE ACUTE ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1ACC 0062;00E0 05AE 1ACC 0315 0062;0061 05AE 0300 1ACC 0315 0062;00E0 05AE 1ACC 0315 0062;0061 05AE 0300 1ACC 0315 0062; # (a◌̕◌̀◌֮◌ᫌb; à◌֮◌ᫌ◌̕b; a◌֮◌̀◌ᫌ◌̕b; à◌֮◌ᫌ◌̕b; a◌֮◌̀◌ᫌ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER INSULAR G, LATIN SMALL LETTER B
+0061 1ACC 0315 0300 05AE 0062;0061 05AE 1ACC 0300 0315 0062;0061 05AE 1ACC 0300 0315 0062;0061 05AE 1ACC 0300 0315 0062;0061 05AE 1ACC 0300 0315 0062; # (a◌ᫌ◌̕◌̀◌֮b; a◌֮◌ᫌ◌̀◌̕b; a◌֮◌ᫌ◌̀◌̕b; a◌֮◌ᫌ◌̀◌̕b; a◌֮◌ᫌ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER INSULAR G, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1ACD 0062;00E0 05AE 1ACD 0315 0062;0061 05AE 0300 1ACD 0315 0062;00E0 05AE 1ACD 0315 0062;0061 05AE 0300 1ACD 0315 0062; # (a◌̕◌̀◌֮◌á«b; à◌֮◌á«â—ŒÌ•b; a◌֮◌̀◌á«â—ŒÌ•b; à◌֮◌á«â—ŒÌ•b; a◌֮◌̀◌á«â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER INSULAR R, LATIN SMALL LETTER B
+0061 1ACD 0315 0300 05AE 0062;0061 05AE 1ACD 0300 0315 0062;0061 05AE 1ACD 0300 0315 0062;0061 05AE 1ACD 0300 0315 0062;0061 05AE 1ACD 0300 0315 0062; # (aâ—Œá«â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌á«â—ŒÌ€â—ŒÌ•b; a◌֮◌á«â—ŒÌ€â—ŒÌ•b; a◌֮◌á«â—ŒÌ€â—ŒÌ•b; a◌֮◌á«â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER INSULAR R, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1ACE 0062;00E0 05AE 1ACE 0315 0062;0061 05AE 0300 1ACE 0315 0062;00E0 05AE 1ACE 0315 0062;0061 05AE 0300 1ACE 0315 0062; # (a◌̕◌̀◌֮◌ᫎb; à◌֮◌ᫎ◌̕b; a◌֮◌̀◌ᫎ◌̕b; à◌֮◌ᫎ◌̕b; a◌֮◌̀◌ᫎ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER INSULAR T, LATIN SMALL LETTER B
+0061 1ACE 0315 0300 05AE 0062;0061 05AE 1ACE 0300 0315 0062;0061 05AE 1ACE 0300 0315 0062;0061 05AE 1ACE 0300 0315 0062;0061 05AE 1ACE 0300 0315 0062; # (a◌ᫎ◌̕◌̀◌֮b; a◌֮◌ᫎ◌̀◌̕b; a◌֮◌ᫎ◌̀◌̕b; a◌֮◌ᫎ◌̀◌̕b; a◌֮◌ᫎ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER INSULAR T, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 1B34 0062;0061 16FF0 093C 1B34 3099 0062;0061 16FF0 093C 1B34 3099 0062;0061 16FF0 093C 1B34 3099 0062;0061 16FF0 093C 1B34 3099 0062; # (a◌゙◌𖿰़◌᬴b; a𖿰◌़◌᬴◌゙b; a𖿰◌़◌᬴◌゙b; a𖿰◌़◌᬴◌゙b; a𖿰◌़◌᬴◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, BALINESE SIGN REREKAN, LATIN SMALL LETTER B
+0061 1B34 3099 093C 16FF0 0062;0061 16FF0 1B34 093C 3099 0062;0061 16FF0 1B34 093C 3099 0062;0061 16FF0 1B34 093C 3099 0062;0061 16FF0 1B34 093C 3099 0062; # (a◌᬴◌゙◌𖿰़b; a𖿰◌᬴◌़◌゙b; a𖿰◌᬴◌़◌゙b; a𖿰◌᬴◌़◌゙b; a𖿰◌᬴◌़◌゙b; ) LATIN SMALL LETTER A, BALINESE SIGN REREKAN, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1B44 0062;0061 3099 094D 1B44 05B0 0062;0061 3099 094D 1B44 05B0 0062;0061 3099 094D 1B44 05B0 0062;0061 3099 094D 1B44 05B0 0062; # (a◌ְ◌à¥â—Œã‚™á­„b; a◌゙◌à¥á­„◌ְb; a◌゙◌à¥á­„◌ְb; a◌゙◌à¥á­„◌ְb; a◌゙◌à¥á­„◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, BALINESE ADEG ADEG, LATIN SMALL LETTER B
+0061 1B44 05B0 094D 3099 0062;0061 3099 1B44 094D 05B0 0062;0061 3099 1B44 094D 05B0 0062;0061 3099 1B44 094D 05B0 0062;0061 3099 1B44 094D 05B0 0062; # (a᭄◌ְ◌à¥â—Œã‚™b; a◌゙᭄◌à¥â—ŒÖ°b; a◌゙᭄◌à¥â—ŒÖ°b; a◌゙᭄◌à¥â—ŒÖ°b; a◌゙᭄◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, BALINESE ADEG ADEG, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1B6B 0062;00E0 05AE 1B6B 0315 0062;0061 05AE 0300 1B6B 0315 0062;00E0 05AE 1B6B 0315 0062;0061 05AE 0300 1B6B 0315 0062; # (a◌̕◌̀◌֮◌᭫b; à◌֮◌᭫◌̕b; a◌֮◌̀◌᭫◌̕b; à◌֮◌᭫◌̕b; a◌֮◌̀◌᭫◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BALINESE MUSICAL SYMBOL COMBINING TEGEH, LATIN SMALL LETTER B
+0061 1B6B 0315 0300 05AE 0062;0061 05AE 1B6B 0300 0315 0062;0061 05AE 1B6B 0300 0315 0062;0061 05AE 1B6B 0300 0315 0062;0061 05AE 1B6B 0300 0315 0062; # (a◌᭫◌̕◌̀◌֮b; a◌֮◌᭫◌̀◌̕b; a◌֮◌᭫◌̀◌̕b; a◌֮◌᭫◌̀◌̕b; a◌֮◌᭫◌̀◌̕b; ) LATIN SMALL LETTER A, BALINESE MUSICAL SYMBOL COMBINING TEGEH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1B6C 0062;0061 1DFA 0316 1B6C 059A 0062;0061 1DFA 0316 1B6C 059A 0062;0061 1DFA 0316 1B6C 059A 0062;0061 1DFA 0316 1B6C 059A 0062; # (a◌֚◌̖◌᷺◌᭬b; a◌᷺◌̖◌᭬◌֚b; a◌᷺◌̖◌᭬◌֚b; a◌᷺◌̖◌᭬◌֚b; a◌᷺◌̖◌᭬◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, BALINESE MUSICAL SYMBOL COMBINING ENDEP, LATIN SMALL LETTER B
+0061 1B6C 059A 0316 1DFA 0062;0061 1DFA 1B6C 0316 059A 0062;0061 1DFA 1B6C 0316 059A 0062;0061 1DFA 1B6C 0316 059A 0062;0061 1DFA 1B6C 0316 059A 0062; # (a◌᭬◌֚◌̖◌᷺b; a◌᷺◌᭬◌̖◌֚b; a◌᷺◌᭬◌̖◌֚b; a◌᷺◌᭬◌̖◌֚b; a◌᷺◌᭬◌̖◌֚b; ) LATIN SMALL LETTER A, BALINESE MUSICAL SYMBOL COMBINING ENDEP, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1B6D 0062;00E0 05AE 1B6D 0315 0062;0061 05AE 0300 1B6D 0315 0062;00E0 05AE 1B6D 0315 0062;0061 05AE 0300 1B6D 0315 0062; # (a◌̕◌̀◌֮◌᭭b; à◌֮◌᭭◌̕b; a◌֮◌̀◌᭭◌̕b; à◌֮◌᭭◌̕b; a◌֮◌̀◌᭭◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BALINESE MUSICAL SYMBOL COMBINING KEMPUL, LATIN SMALL LETTER B
+0061 1B6D 0315 0300 05AE 0062;0061 05AE 1B6D 0300 0315 0062;0061 05AE 1B6D 0300 0315 0062;0061 05AE 1B6D 0300 0315 0062;0061 05AE 1B6D 0300 0315 0062; # (a◌᭭◌̕◌̀◌֮b; a◌֮◌᭭◌̀◌̕b; a◌֮◌᭭◌̀◌̕b; a◌֮◌᭭◌̀◌̕b; a◌֮◌᭭◌̀◌̕b; ) LATIN SMALL LETTER A, BALINESE MUSICAL SYMBOL COMBINING KEMPUL, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1B6E 0062;00E0 05AE 1B6E 0315 0062;0061 05AE 0300 1B6E 0315 0062;00E0 05AE 1B6E 0315 0062;0061 05AE 0300 1B6E 0315 0062; # (a◌̕◌̀◌֮◌᭮b; à◌֮◌᭮◌̕b; a◌֮◌̀◌᭮◌̕b; à◌֮◌᭮◌̕b; a◌֮◌̀◌᭮◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BALINESE MUSICAL SYMBOL COMBINING KEMPLI, LATIN SMALL LETTER B
+0061 1B6E 0315 0300 05AE 0062;0061 05AE 1B6E 0300 0315 0062;0061 05AE 1B6E 0300 0315 0062;0061 05AE 1B6E 0300 0315 0062;0061 05AE 1B6E 0300 0315 0062; # (a◌᭮◌̕◌̀◌֮b; a◌֮◌᭮◌̀◌̕b; a◌֮◌᭮◌̀◌̕b; a◌֮◌᭮◌̀◌̕b; a◌֮◌᭮◌̀◌̕b; ) LATIN SMALL LETTER A, BALINESE MUSICAL SYMBOL COMBINING KEMPLI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1B6F 0062;00E0 05AE 1B6F 0315 0062;0061 05AE 0300 1B6F 0315 0062;00E0 05AE 1B6F 0315 0062;0061 05AE 0300 1B6F 0315 0062; # (a◌̕◌̀◌֮◌᭯b; à◌֮◌᭯◌̕b; a◌֮◌̀◌᭯◌̕b; à◌֮◌᭯◌̕b; a◌֮◌̀◌᭯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BALINESE MUSICAL SYMBOL COMBINING JEGOGAN, LATIN SMALL LETTER B
+0061 1B6F 0315 0300 05AE 0062;0061 05AE 1B6F 0300 0315 0062;0061 05AE 1B6F 0300 0315 0062;0061 05AE 1B6F 0300 0315 0062;0061 05AE 1B6F 0300 0315 0062; # (a◌᭯◌̕◌̀◌֮b; a◌֮◌᭯◌̀◌̕b; a◌֮◌᭯◌̀◌̕b; a◌֮◌᭯◌̀◌̕b; a◌֮◌᭯◌̀◌̕b; ) LATIN SMALL LETTER A, BALINESE MUSICAL SYMBOL COMBINING JEGOGAN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1B70 0062;00E0 05AE 1B70 0315 0062;0061 05AE 0300 1B70 0315 0062;00E0 05AE 1B70 0315 0062;0061 05AE 0300 1B70 0315 0062; # (a◌̕◌̀◌֮◌᭰b; à◌֮◌᭰◌̕b; a◌֮◌̀◌᭰◌̕b; à◌֮◌᭰◌̕b; a◌֮◌̀◌᭰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BALINESE MUSICAL SYMBOL COMBINING KEMPUL WITH JEGOGAN, LATIN SMALL LETTER B
+0061 1B70 0315 0300 05AE 0062;0061 05AE 1B70 0300 0315 0062;0061 05AE 1B70 0300 0315 0062;0061 05AE 1B70 0300 0315 0062;0061 05AE 1B70 0300 0315 0062; # (a◌᭰◌̕◌̀◌֮b; a◌֮◌᭰◌̀◌̕b; a◌֮◌᭰◌̀◌̕b; a◌֮◌᭰◌̀◌̕b; a◌֮◌᭰◌̀◌̕b; ) LATIN SMALL LETTER A, BALINESE MUSICAL SYMBOL COMBINING KEMPUL WITH JEGOGAN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1B71 0062;00E0 05AE 1B71 0315 0062;0061 05AE 0300 1B71 0315 0062;00E0 05AE 1B71 0315 0062;0061 05AE 0300 1B71 0315 0062; # (a◌̕◌̀◌֮◌᭱b; à◌֮◌᭱◌̕b; a◌֮◌̀◌᭱◌̕b; à◌֮◌᭱◌̕b; a◌֮◌̀◌᭱◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BALINESE MUSICAL SYMBOL COMBINING KEMPLI WITH JEGOGAN, LATIN SMALL LETTER B
+0061 1B71 0315 0300 05AE 0062;0061 05AE 1B71 0300 0315 0062;0061 05AE 1B71 0300 0315 0062;0061 05AE 1B71 0300 0315 0062;0061 05AE 1B71 0300 0315 0062; # (a◌᭱◌̕◌̀◌֮b; a◌֮◌᭱◌̀◌̕b; a◌֮◌᭱◌̀◌̕b; a◌֮◌᭱◌̀◌̕b; a◌֮◌᭱◌̀◌̕b; ) LATIN SMALL LETTER A, BALINESE MUSICAL SYMBOL COMBINING KEMPLI WITH JEGOGAN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1B72 0062;00E0 05AE 1B72 0315 0062;0061 05AE 0300 1B72 0315 0062;00E0 05AE 1B72 0315 0062;0061 05AE 0300 1B72 0315 0062; # (a◌̕◌̀◌֮◌᭲b; à◌֮◌᭲◌̕b; a◌֮◌̀◌᭲◌̕b; à◌֮◌᭲◌̕b; a◌֮◌̀◌᭲◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BALINESE MUSICAL SYMBOL COMBINING BENDE, LATIN SMALL LETTER B
+0061 1B72 0315 0300 05AE 0062;0061 05AE 1B72 0300 0315 0062;0061 05AE 1B72 0300 0315 0062;0061 05AE 1B72 0300 0315 0062;0061 05AE 1B72 0300 0315 0062; # (a◌᭲◌̕◌̀◌֮b; a◌֮◌᭲◌̀◌̕b; a◌֮◌᭲◌̀◌̕b; a◌֮◌᭲◌̀◌̕b; a◌֮◌᭲◌̀◌̕b; ) LATIN SMALL LETTER A, BALINESE MUSICAL SYMBOL COMBINING BENDE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1B73 0062;00E0 05AE 1B73 0315 0062;0061 05AE 0300 1B73 0315 0062;00E0 05AE 1B73 0315 0062;0061 05AE 0300 1B73 0315 0062; # (a◌̕◌̀◌֮◌᭳b; à◌֮◌᭳◌̕b; a◌֮◌̀◌᭳◌̕b; à◌֮◌᭳◌̕b; a◌֮◌̀◌᭳◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BALINESE MUSICAL SYMBOL COMBINING GONG, LATIN SMALL LETTER B
+0061 1B73 0315 0300 05AE 0062;0061 05AE 1B73 0300 0315 0062;0061 05AE 1B73 0300 0315 0062;0061 05AE 1B73 0300 0315 0062;0061 05AE 1B73 0300 0315 0062; # (a◌᭳◌̕◌̀◌֮b; a◌֮◌᭳◌̀◌̕b; a◌֮◌᭳◌̀◌̕b; a◌֮◌᭳◌̀◌̕b; a◌֮◌᭳◌̀◌̕b; ) LATIN SMALL LETTER A, BALINESE MUSICAL SYMBOL COMBINING GONG, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1BAA 0062;0061 3099 094D 1BAA 05B0 0062;0061 3099 094D 1BAA 05B0 0062;0061 3099 094D 1BAA 05B0 0062;0061 3099 094D 1BAA 05B0 0062; # (a◌ְ◌à¥â—Œã‚™á®ªb; a◌゙◌à¥á®ªâ—ŒÖ°b; a◌゙◌à¥á®ªâ—ŒÖ°b; a◌゙◌à¥á®ªâ—ŒÖ°b; a◌゙◌à¥á®ªâ—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, SUNDANESE SIGN PAMAAEH, LATIN SMALL LETTER B
+0061 1BAA 05B0 094D 3099 0062;0061 3099 1BAA 094D 05B0 0062;0061 3099 1BAA 094D 05B0 0062;0061 3099 1BAA 094D 05B0 0062;0061 3099 1BAA 094D 05B0 0062; # (a᮪◌ְ◌à¥â—Œã‚™b; a◌゙᮪◌à¥â—ŒÖ°b; a◌゙᮪◌à¥â—ŒÖ°b; a◌゙᮪◌à¥â—ŒÖ°b; a◌゙᮪◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, SUNDANESE SIGN PAMAAEH, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1BAB 0062;0061 3099 094D 1BAB 05B0 0062;0061 3099 094D 1BAB 05B0 0062;0061 3099 094D 1BAB 05B0 0062;0061 3099 094D 1BAB 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œá®«b; a◌゙◌à¥â—Œá®«â—ŒÖ°b; a◌゙◌à¥â—Œá®«â—ŒÖ°b; a◌゙◌à¥â—Œá®«â—ŒÖ°b; a◌゙◌à¥â—Œá®«â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, SUNDANESE SIGN VIRAMA, LATIN SMALL LETTER B
+0061 1BAB 05B0 094D 3099 0062;0061 3099 1BAB 094D 05B0 0062;0061 3099 1BAB 094D 05B0 0062;0061 3099 1BAB 094D 05B0 0062;0061 3099 1BAB 094D 05B0 0062; # (a◌᮫◌ְ◌à¥â—Œã‚™b; a◌゙◌᮫◌à¥â—ŒÖ°b; a◌゙◌᮫◌à¥â—ŒÖ°b; a◌゙◌᮫◌à¥â—ŒÖ°b; a◌゙◌᮫◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, SUNDANESE SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 1BE6 0062;0061 16FF0 093C 1BE6 3099 0062;0061 16FF0 093C 1BE6 3099 0062;0061 16FF0 093C 1BE6 3099 0062;0061 16FF0 093C 1BE6 3099 0062; # (a◌゙◌𖿰़◌᯦b; a𖿰◌़◌᯦◌゙b; a𖿰◌़◌᯦◌゙b; a𖿰◌़◌᯦◌゙b; a𖿰◌़◌᯦◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, BATAK SIGN TOMPI, LATIN SMALL LETTER B
+0061 1BE6 3099 093C 16FF0 0062;0061 16FF0 1BE6 093C 3099 0062;0061 16FF0 1BE6 093C 3099 0062;0061 16FF0 1BE6 093C 3099 0062;0061 16FF0 1BE6 093C 3099 0062; # (a◌᯦◌゙◌𖿰़b; a𖿰◌᯦◌़◌゙b; a𖿰◌᯦◌़◌゙b; a𖿰◌᯦◌़◌゙b; a𖿰◌᯦◌़◌゙b; ) LATIN SMALL LETTER A, BATAK SIGN TOMPI, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1BF2 0062;0061 3099 094D 1BF2 05B0 0062;0061 3099 094D 1BF2 05B0 0062;0061 3099 094D 1BF2 05B0 0062;0061 3099 094D 1BF2 05B0 0062; # (a◌ְ◌à¥â—Œã‚™á¯²b; a◌゙◌à¥á¯²â—ŒÖ°b; a◌゙◌à¥á¯²â—ŒÖ°b; a◌゙◌à¥á¯²â—ŒÖ°b; a◌゙◌à¥á¯²â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, BATAK PANGOLAT, LATIN SMALL LETTER B
+0061 1BF2 05B0 094D 3099 0062;0061 3099 1BF2 094D 05B0 0062;0061 3099 1BF2 094D 05B0 0062;0061 3099 1BF2 094D 05B0 0062;0061 3099 1BF2 094D 05B0 0062; # (a᯲◌ְ◌à¥â—Œã‚™b; a◌゙᯲◌à¥â—ŒÖ°b; a◌゙᯲◌à¥â—ŒÖ°b; a◌゙᯲◌à¥â—ŒÖ°b; a◌゙᯲◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, BATAK PANGOLAT, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1BF3 0062;0061 3099 094D 1BF3 05B0 0062;0061 3099 094D 1BF3 05B0 0062;0061 3099 094D 1BF3 05B0 0062;0061 3099 094D 1BF3 05B0 0062; # (a◌ְ◌à¥â—Œã‚™á¯³b; a◌゙◌à¥á¯³â—ŒÖ°b; a◌゙◌à¥á¯³â—ŒÖ°b; a◌゙◌à¥á¯³â—ŒÖ°b; a◌゙◌à¥á¯³â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, BATAK PANONGONAN, LATIN SMALL LETTER B
+0061 1BF3 05B0 094D 3099 0062;0061 3099 1BF3 094D 05B0 0062;0061 3099 1BF3 094D 05B0 0062;0061 3099 1BF3 094D 05B0 0062;0061 3099 1BF3 094D 05B0 0062; # (a᯳◌ְ◌à¥â—Œã‚™b; a◌゙᯳◌à¥â—ŒÖ°b; a◌゙᯳◌à¥â—ŒÖ°b; a◌゙᯳◌à¥â—ŒÖ°b; a◌゙᯳◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, BATAK PANONGONAN, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 1C37 0062;0061 16FF0 093C 1C37 3099 0062;0061 16FF0 093C 1C37 3099 0062;0061 16FF0 093C 1C37 3099 0062;0061 16FF0 093C 1C37 3099 0062; # (a◌゙◌𖿰़◌᰷b; a𖿰◌़◌᰷◌゙b; a𖿰◌़◌᰷◌゙b; a𖿰◌़◌᰷◌゙b; a𖿰◌़◌᰷◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LEPCHA SIGN NUKTA, LATIN SMALL LETTER B
+0061 1C37 3099 093C 16FF0 0062;0061 16FF0 1C37 093C 3099 0062;0061 16FF0 1C37 093C 3099 0062;0061 16FF0 1C37 093C 3099 0062;0061 16FF0 1C37 093C 3099 0062; # (a◌᰷◌゙◌𖿰़b; a𖿰◌᰷◌़◌゙b; a𖿰◌᰷◌़◌゙b; a𖿰◌᰷◌़◌゙b; a𖿰◌᰷◌़◌゙b; ) LATIN SMALL LETTER A, LEPCHA SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1CD0 0062;00E0 05AE 1CD0 0315 0062;0061 05AE 0300 1CD0 0315 0062;00E0 05AE 1CD0 0315 0062;0061 05AE 0300 1CD0 0315 0062; # (a◌̕◌̀◌֮◌á³b; à◌֮◌á³â—ŒÌ•b; a◌֮◌̀◌á³â—ŒÌ•b; à◌֮◌á³â—ŒÌ•b; a◌֮◌̀◌á³â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, VEDIC TONE KARSHANA, LATIN SMALL LETTER B
+0061 1CD0 0315 0300 05AE 0062;0061 05AE 1CD0 0300 0315 0062;0061 05AE 1CD0 0300 0315 0062;0061 05AE 1CD0 0300 0315 0062;0061 05AE 1CD0 0300 0315 0062; # (aâ—Œá³â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌á³â—ŒÌ€â—ŒÌ•b; a◌֮◌á³â—ŒÌ€â—ŒÌ•b; a◌֮◌á³â—ŒÌ€â—ŒÌ•b; a◌֮◌á³â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, VEDIC TONE KARSHANA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1CD1 0062;00E0 05AE 1CD1 0315 0062;0061 05AE 0300 1CD1 0315 0062;00E0 05AE 1CD1 0315 0062;0061 05AE 0300 1CD1 0315 0062; # (a◌̕◌̀◌֮◌᳑b; à◌֮◌᳑◌̕b; a◌֮◌̀◌᳑◌̕b; à◌֮◌᳑◌̕b; a◌֮◌̀◌᳑◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, VEDIC TONE SHARA, LATIN SMALL LETTER B
+0061 1CD1 0315 0300 05AE 0062;0061 05AE 1CD1 0300 0315 0062;0061 05AE 1CD1 0300 0315 0062;0061 05AE 1CD1 0300 0315 0062;0061 05AE 1CD1 0300 0315 0062; # (a◌᳑◌̕◌̀◌֮b; a◌֮◌᳑◌̀◌̕b; a◌֮◌᳑◌̀◌̕b; a◌֮◌᳑◌̀◌̕b; a◌֮◌᳑◌̀◌̕b; ) LATIN SMALL LETTER A, VEDIC TONE SHARA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1CD2 0062;00E0 05AE 1CD2 0315 0062;0061 05AE 0300 1CD2 0315 0062;00E0 05AE 1CD2 0315 0062;0061 05AE 0300 1CD2 0315 0062; # (a◌̕◌̀◌֮◌᳒b; à◌֮◌᳒◌̕b; a◌֮◌̀◌᳒◌̕b; à◌֮◌᳒◌̕b; a◌֮◌̀◌᳒◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, VEDIC TONE PRENKHA, LATIN SMALL LETTER B
+0061 1CD2 0315 0300 05AE 0062;0061 05AE 1CD2 0300 0315 0062;0061 05AE 1CD2 0300 0315 0062;0061 05AE 1CD2 0300 0315 0062;0061 05AE 1CD2 0300 0315 0062; # (a◌᳒◌̕◌̀◌֮b; a◌֮◌᳒◌̀◌̕b; a◌֮◌᳒◌̀◌̕b; a◌֮◌᳒◌̀◌̕b; a◌֮◌᳒◌̀◌̕b; ) LATIN SMALL LETTER A, VEDIC TONE PRENKHA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 16FF0 0334 1CD4 0062;0061 0334 1CD4 16FF0 0062;0061 0334 1CD4 16FF0 0062;0061 0334 1CD4 16FF0 0062;0061 0334 1CD4 16FF0 0062; # (a𖿰◌̴◌᳔b; a◌̴◌᳔𖿰b; a◌̴◌᳔𖿰b; a◌̴◌᳔𖿰b; a◌̴◌᳔𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, VEDIC SIGN YAJURVEDIC MIDLINE SVARITA, LATIN SMALL LETTER B
+0061 1CD4 16FF0 0334 0062;0061 1CD4 0334 16FF0 0062;0061 1CD4 0334 16FF0 0062;0061 1CD4 0334 16FF0 0062;0061 1CD4 0334 16FF0 0062; # (a◌᳔𖿰◌̴b; a◌᳔◌̴𖿰b; a◌᳔◌̴𖿰b; a◌᳔◌̴𖿰b; a◌᳔◌̴𖿰b; ) LATIN SMALL LETTER A, VEDIC SIGN YAJURVEDIC MIDLINE SVARITA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1CD5 0062;0061 1DFA 0316 1CD5 059A 0062;0061 1DFA 0316 1CD5 059A 0062;0061 1DFA 0316 1CD5 059A 0062;0061 1DFA 0316 1CD5 059A 0062; # (a◌֚◌̖◌᷺◌᳕b; a◌᷺◌̖◌᳕◌֚b; a◌᷺◌̖◌᳕◌֚b; a◌᷺◌̖◌᳕◌֚b; a◌᷺◌̖◌᳕◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, VEDIC TONE YAJURVEDIC AGGRAVATED INDEPENDENT SVARITA, LATIN SMALL LETTER B
+0061 1CD5 059A 0316 1DFA 0062;0061 1DFA 1CD5 0316 059A 0062;0061 1DFA 1CD5 0316 059A 0062;0061 1DFA 1CD5 0316 059A 0062;0061 1DFA 1CD5 0316 059A 0062; # (a◌᳕◌֚◌̖◌᷺b; a◌᷺◌᳕◌̖◌֚b; a◌᷺◌᳕◌̖◌֚b; a◌᷺◌᳕◌̖◌֚b; a◌᷺◌᳕◌̖◌֚b; ) LATIN SMALL LETTER A, VEDIC TONE YAJURVEDIC AGGRAVATED INDEPENDENT SVARITA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1CD6 0062;0061 1DFA 0316 1CD6 059A 0062;0061 1DFA 0316 1CD6 059A 0062;0061 1DFA 0316 1CD6 059A 0062;0061 1DFA 0316 1CD6 059A 0062; # (a◌֚◌̖◌᷺◌᳖b; a◌᷺◌̖◌᳖◌֚b; a◌᷺◌̖◌᳖◌֚b; a◌᷺◌̖◌᳖◌֚b; a◌᷺◌̖◌᳖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, VEDIC TONE YAJURVEDIC INDEPENDENT SVARITA, LATIN SMALL LETTER B
+0061 1CD6 059A 0316 1DFA 0062;0061 1DFA 1CD6 0316 059A 0062;0061 1DFA 1CD6 0316 059A 0062;0061 1DFA 1CD6 0316 059A 0062;0061 1DFA 1CD6 0316 059A 0062; # (a◌᳖◌֚◌̖◌᷺b; a◌᷺◌᳖◌̖◌֚b; a◌᷺◌᳖◌̖◌֚b; a◌᷺◌᳖◌̖◌֚b; a◌᷺◌᳖◌̖◌֚b; ) LATIN SMALL LETTER A, VEDIC TONE YAJURVEDIC INDEPENDENT SVARITA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1CD7 0062;0061 1DFA 0316 1CD7 059A 0062;0061 1DFA 0316 1CD7 059A 0062;0061 1DFA 0316 1CD7 059A 0062;0061 1DFA 0316 1CD7 059A 0062; # (a◌֚◌̖◌᷺◌᳗b; a◌᷺◌̖◌᳗◌֚b; a◌᷺◌̖◌᳗◌֚b; a◌᷺◌̖◌᳗◌֚b; a◌᷺◌̖◌᳗◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA, LATIN SMALL LETTER B
+0061 1CD7 059A 0316 1DFA 0062;0061 1DFA 1CD7 0316 059A 0062;0061 1DFA 1CD7 0316 059A 0062;0061 1DFA 1CD7 0316 059A 0062;0061 1DFA 1CD7 0316 059A 0062; # (a◌᳗◌֚◌̖◌᷺b; a◌᷺◌᳗◌̖◌֚b; a◌᷺◌᳗◌̖◌֚b; a◌᷺◌᳗◌̖◌֚b; a◌᷺◌᳗◌̖◌֚b; ) LATIN SMALL LETTER A, VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1CD8 0062;0061 1DFA 0316 1CD8 059A 0062;0061 1DFA 0316 1CD8 059A 0062;0061 1DFA 0316 1CD8 059A 0062;0061 1DFA 0316 1CD8 059A 0062; # (a◌֚◌̖◌᷺◌᳘b; a◌᷺◌̖◌᳘◌֚b; a◌᷺◌̖◌᳘◌֚b; a◌᷺◌̖◌᳘◌֚b; a◌᷺◌̖◌᳘◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, VEDIC TONE CANDRA BELOW, LATIN SMALL LETTER B
+0061 1CD8 059A 0316 1DFA 0062;0061 1DFA 1CD8 0316 059A 0062;0061 1DFA 1CD8 0316 059A 0062;0061 1DFA 1CD8 0316 059A 0062;0061 1DFA 1CD8 0316 059A 0062; # (a◌᳘◌֚◌̖◌᷺b; a◌᷺◌᳘◌̖◌֚b; a◌᷺◌᳘◌̖◌֚b; a◌᷺◌᳘◌̖◌֚b; a◌᷺◌᳘◌̖◌֚b; ) LATIN SMALL LETTER A, VEDIC TONE CANDRA BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1CD9 0062;0061 1DFA 0316 1CD9 059A 0062;0061 1DFA 0316 1CD9 059A 0062;0061 1DFA 0316 1CD9 059A 0062;0061 1DFA 0316 1CD9 059A 0062; # (a◌֚◌̖◌᷺◌᳙b; a◌᷺◌̖◌᳙◌֚b; a◌᷺◌̖◌᳙◌֚b; a◌᷺◌̖◌᳙◌֚b; a◌᷺◌̖◌᳙◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA SCHROEDER, LATIN SMALL LETTER B
+0061 1CD9 059A 0316 1DFA 0062;0061 1DFA 1CD9 0316 059A 0062;0061 1DFA 1CD9 0316 059A 0062;0061 1DFA 1CD9 0316 059A 0062;0061 1DFA 1CD9 0316 059A 0062; # (a◌᳙◌֚◌̖◌᷺b; a◌᷺◌᳙◌̖◌֚b; a◌᷺◌᳙◌̖◌֚b; a◌᷺◌᳙◌̖◌֚b; a◌᷺◌᳙◌̖◌֚b; ) LATIN SMALL LETTER A, VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA SCHROEDER, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1CDA 0062;00E0 05AE 1CDA 0315 0062;0061 05AE 0300 1CDA 0315 0062;00E0 05AE 1CDA 0315 0062;0061 05AE 0300 1CDA 0315 0062; # (a◌̕◌̀◌֮◌᳚b; à◌֮◌᳚◌̕b; a◌֮◌̀◌᳚◌̕b; à◌֮◌᳚◌̕b; a◌֮◌̀◌᳚◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, VEDIC TONE DOUBLE SVARITA, LATIN SMALL LETTER B
+0061 1CDA 0315 0300 05AE 0062;0061 05AE 1CDA 0300 0315 0062;0061 05AE 1CDA 0300 0315 0062;0061 05AE 1CDA 0300 0315 0062;0061 05AE 1CDA 0300 0315 0062; # (a◌᳚◌̕◌̀◌֮b; a◌֮◌᳚◌̀◌̕b; a◌֮◌᳚◌̀◌̕b; a◌֮◌᳚◌̀◌̕b; a◌֮◌᳚◌̀◌̕b; ) LATIN SMALL LETTER A, VEDIC TONE DOUBLE SVARITA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1CDB 0062;00E0 05AE 1CDB 0315 0062;0061 05AE 0300 1CDB 0315 0062;00E0 05AE 1CDB 0315 0062;0061 05AE 0300 1CDB 0315 0062; # (a◌̕◌̀◌֮◌᳛b; à◌֮◌᳛◌̕b; a◌֮◌̀◌᳛◌̕b; à◌֮◌᳛◌̕b; a◌֮◌̀◌᳛◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, VEDIC TONE TRIPLE SVARITA, LATIN SMALL LETTER B
+0061 1CDB 0315 0300 05AE 0062;0061 05AE 1CDB 0300 0315 0062;0061 05AE 1CDB 0300 0315 0062;0061 05AE 1CDB 0300 0315 0062;0061 05AE 1CDB 0300 0315 0062; # (a◌᳛◌̕◌̀◌֮b; a◌֮◌᳛◌̀◌̕b; a◌֮◌᳛◌̀◌̕b; a◌֮◌᳛◌̀◌̕b; a◌֮◌᳛◌̀◌̕b; ) LATIN SMALL LETTER A, VEDIC TONE TRIPLE SVARITA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1CDC 0062;0061 1DFA 0316 1CDC 059A 0062;0061 1DFA 0316 1CDC 059A 0062;0061 1DFA 0316 1CDC 059A 0062;0061 1DFA 0316 1CDC 059A 0062; # (a◌֚◌̖◌᷺◌᳜b; a◌᷺◌̖◌᳜◌֚b; a◌᷺◌̖◌᳜◌֚b; a◌᷺◌̖◌᳜◌֚b; a◌᷺◌̖◌᳜◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, VEDIC TONE KATHAKA ANUDATTA, LATIN SMALL LETTER B
+0061 1CDC 059A 0316 1DFA 0062;0061 1DFA 1CDC 0316 059A 0062;0061 1DFA 1CDC 0316 059A 0062;0061 1DFA 1CDC 0316 059A 0062;0061 1DFA 1CDC 0316 059A 0062; # (a◌᳜◌֚◌̖◌᷺b; a◌᷺◌᳜◌̖◌֚b; a◌᷺◌᳜◌̖◌֚b; a◌᷺◌᳜◌̖◌֚b; a◌᷺◌᳜◌̖◌֚b; ) LATIN SMALL LETTER A, VEDIC TONE KATHAKA ANUDATTA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1CDD 0062;0061 1DFA 0316 1CDD 059A 0062;0061 1DFA 0316 1CDD 059A 0062;0061 1DFA 0316 1CDD 059A 0062;0061 1DFA 0316 1CDD 059A 0062; # (a◌֚◌̖◌᷺◌á³b; a◌᷺◌̖◌á³â—ŒÖšb; a◌᷺◌̖◌á³â—ŒÖšb; a◌᷺◌̖◌á³â—ŒÖšb; a◌᷺◌̖◌á³â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, VEDIC TONE DOT BELOW, LATIN SMALL LETTER B
+0061 1CDD 059A 0316 1DFA 0062;0061 1DFA 1CDD 0316 059A 0062;0061 1DFA 1CDD 0316 059A 0062;0061 1DFA 1CDD 0316 059A 0062;0061 1DFA 1CDD 0316 059A 0062; # (aâ—Œá³â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌á³â—ŒÌ–◌֚b; a◌᷺◌á³â—ŒÌ–◌֚b; a◌᷺◌á³â—ŒÌ–◌֚b; a◌᷺◌á³â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, VEDIC TONE DOT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1CDE 0062;0061 1DFA 0316 1CDE 059A 0062;0061 1DFA 0316 1CDE 059A 0062;0061 1DFA 0316 1CDE 059A 0062;0061 1DFA 0316 1CDE 059A 0062; # (a◌֚◌̖◌᷺◌᳞b; a◌᷺◌̖◌᳞◌֚b; a◌᷺◌̖◌᳞◌֚b; a◌᷺◌̖◌᳞◌֚b; a◌᷺◌̖◌᳞◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, VEDIC TONE TWO DOTS BELOW, LATIN SMALL LETTER B
+0061 1CDE 059A 0316 1DFA 0062;0061 1DFA 1CDE 0316 059A 0062;0061 1DFA 1CDE 0316 059A 0062;0061 1DFA 1CDE 0316 059A 0062;0061 1DFA 1CDE 0316 059A 0062; # (a◌᳞◌֚◌̖◌᷺b; a◌᷺◌᳞◌̖◌֚b; a◌᷺◌᳞◌̖◌֚b; a◌᷺◌᳞◌̖◌֚b; a◌᷺◌᳞◌̖◌֚b; ) LATIN SMALL LETTER A, VEDIC TONE TWO DOTS BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1CDF 0062;0061 1DFA 0316 1CDF 059A 0062;0061 1DFA 0316 1CDF 059A 0062;0061 1DFA 0316 1CDF 059A 0062;0061 1DFA 0316 1CDF 059A 0062; # (a◌֚◌̖◌᷺◌᳟b; a◌᷺◌̖◌᳟◌֚b; a◌᷺◌̖◌᳟◌֚b; a◌᷺◌̖◌᳟◌֚b; a◌᷺◌̖◌᳟◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, VEDIC TONE THREE DOTS BELOW, LATIN SMALL LETTER B
+0061 1CDF 059A 0316 1DFA 0062;0061 1DFA 1CDF 0316 059A 0062;0061 1DFA 1CDF 0316 059A 0062;0061 1DFA 1CDF 0316 059A 0062;0061 1DFA 1CDF 0316 059A 0062; # (a◌᳟◌֚◌̖◌᷺b; a◌᷺◌᳟◌̖◌֚b; a◌᷺◌᳟◌̖◌֚b; a◌᷺◌᳟◌̖◌֚b; a◌᷺◌᳟◌̖◌֚b; ) LATIN SMALL LETTER A, VEDIC TONE THREE DOTS BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1CE0 0062;00E0 05AE 1CE0 0315 0062;0061 05AE 0300 1CE0 0315 0062;00E0 05AE 1CE0 0315 0062;0061 05AE 0300 1CE0 0315 0062; # (a◌̕◌̀◌֮◌᳠b; à◌֮◌᳠◌̕b; a◌֮◌̀◌᳠◌̕b; à◌֮◌᳠◌̕b; a◌֮◌̀◌᳠◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA, LATIN SMALL LETTER B
+0061 1CE0 0315 0300 05AE 0062;0061 05AE 1CE0 0300 0315 0062;0061 05AE 1CE0 0300 0315 0062;0061 05AE 1CE0 0300 0315 0062;0061 05AE 1CE0 0300 0315 0062; # (a◌᳠◌̕◌̀◌֮b; a◌֮◌᳠◌̀◌̕b; a◌֮◌᳠◌̀◌̕b; a◌֮◌᳠◌̀◌̕b; a◌֮◌᳠◌̀◌̕b; ) LATIN SMALL LETTER A, VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 16FF0 0334 1CE2 0062;0061 0334 1CE2 16FF0 0062;0061 0334 1CE2 16FF0 0062;0061 0334 1CE2 16FF0 0062;0061 0334 1CE2 16FF0 0062; # (a𖿰◌̴◌᳢b; a◌̴◌᳢𖿰b; a◌̴◌᳢𖿰b; a◌̴◌᳢𖿰b; a◌̴◌᳢𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, VEDIC SIGN VISARGA SVARITA, LATIN SMALL LETTER B
+0061 1CE2 16FF0 0334 0062;0061 1CE2 0334 16FF0 0062;0061 1CE2 0334 16FF0 0062;0061 1CE2 0334 16FF0 0062;0061 1CE2 0334 16FF0 0062; # (a◌᳢𖿰◌̴b; a◌᳢◌̴𖿰b; a◌᳢◌̴𖿰b; a◌᳢◌̴𖿰b; a◌᳢◌̴𖿰b; ) LATIN SMALL LETTER A, VEDIC SIGN VISARGA SVARITA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 1CE3 0062;0061 0334 1CE3 16FF0 0062;0061 0334 1CE3 16FF0 0062;0061 0334 1CE3 16FF0 0062;0061 0334 1CE3 16FF0 0062; # (a𖿰◌̴◌᳣b; a◌̴◌᳣𖿰b; a◌̴◌᳣𖿰b; a◌̴◌᳣𖿰b; a◌̴◌᳣𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, VEDIC SIGN VISARGA UDATTA, LATIN SMALL LETTER B
+0061 1CE3 16FF0 0334 0062;0061 1CE3 0334 16FF0 0062;0061 1CE3 0334 16FF0 0062;0061 1CE3 0334 16FF0 0062;0061 1CE3 0334 16FF0 0062; # (a◌᳣𖿰◌̴b; a◌᳣◌̴𖿰b; a◌᳣◌̴𖿰b; a◌᳣◌̴𖿰b; a◌᳣◌̴𖿰b; ) LATIN SMALL LETTER A, VEDIC SIGN VISARGA UDATTA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 1CE4 0062;0061 0334 1CE4 16FF0 0062;0061 0334 1CE4 16FF0 0062;0061 0334 1CE4 16FF0 0062;0061 0334 1CE4 16FF0 0062; # (a𖿰◌̴◌᳤b; a◌̴◌᳤𖿰b; a◌̴◌᳤𖿰b; a◌̴◌᳤𖿰b; a◌̴◌᳤𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, VEDIC SIGN REVERSED VISARGA UDATTA, LATIN SMALL LETTER B
+0061 1CE4 16FF0 0334 0062;0061 1CE4 0334 16FF0 0062;0061 1CE4 0334 16FF0 0062;0061 1CE4 0334 16FF0 0062;0061 1CE4 0334 16FF0 0062; # (a◌᳤𖿰◌̴b; a◌᳤◌̴𖿰b; a◌᳤◌̴𖿰b; a◌᳤◌̴𖿰b; a◌᳤◌̴𖿰b; ) LATIN SMALL LETTER A, VEDIC SIGN REVERSED VISARGA UDATTA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 1CE5 0062;0061 0334 1CE5 16FF0 0062;0061 0334 1CE5 16FF0 0062;0061 0334 1CE5 16FF0 0062;0061 0334 1CE5 16FF0 0062; # (a𖿰◌̴◌᳥b; a◌̴◌᳥𖿰b; a◌̴◌᳥𖿰b; a◌̴◌᳥𖿰b; a◌̴◌᳥𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, VEDIC SIGN VISARGA ANUDATTA, LATIN SMALL LETTER B
+0061 1CE5 16FF0 0334 0062;0061 1CE5 0334 16FF0 0062;0061 1CE5 0334 16FF0 0062;0061 1CE5 0334 16FF0 0062;0061 1CE5 0334 16FF0 0062; # (a◌᳥𖿰◌̴b; a◌᳥◌̴𖿰b; a◌᳥◌̴𖿰b; a◌᳥◌̴𖿰b; a◌᳥◌̴𖿰b; ) LATIN SMALL LETTER A, VEDIC SIGN VISARGA ANUDATTA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 1CE6 0062;0061 0334 1CE6 16FF0 0062;0061 0334 1CE6 16FF0 0062;0061 0334 1CE6 16FF0 0062;0061 0334 1CE6 16FF0 0062; # (a𖿰◌̴◌᳦b; a◌̴◌᳦𖿰b; a◌̴◌᳦𖿰b; a◌̴◌᳦𖿰b; a◌̴◌᳦𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, VEDIC SIGN REVERSED VISARGA ANUDATTA, LATIN SMALL LETTER B
+0061 1CE6 16FF0 0334 0062;0061 1CE6 0334 16FF0 0062;0061 1CE6 0334 16FF0 0062;0061 1CE6 0334 16FF0 0062;0061 1CE6 0334 16FF0 0062; # (a◌᳦𖿰◌̴b; a◌᳦◌̴𖿰b; a◌᳦◌̴𖿰b; a◌᳦◌̴𖿰b; a◌᳦◌̴𖿰b; ) LATIN SMALL LETTER A, VEDIC SIGN REVERSED VISARGA ANUDATTA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 1CE7 0062;0061 0334 1CE7 16FF0 0062;0061 0334 1CE7 16FF0 0062;0061 0334 1CE7 16FF0 0062;0061 0334 1CE7 16FF0 0062; # (a𖿰◌̴◌᳧b; a◌̴◌᳧𖿰b; a◌̴◌᳧𖿰b; a◌̴◌᳧𖿰b; a◌̴◌᳧𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, VEDIC SIGN VISARGA UDATTA WITH TAIL, LATIN SMALL LETTER B
+0061 1CE7 16FF0 0334 0062;0061 1CE7 0334 16FF0 0062;0061 1CE7 0334 16FF0 0062;0061 1CE7 0334 16FF0 0062;0061 1CE7 0334 16FF0 0062; # (a◌᳧𖿰◌̴b; a◌᳧◌̴𖿰b; a◌᳧◌̴𖿰b; a◌᳧◌̴𖿰b; a◌᳧◌̴𖿰b; ) LATIN SMALL LETTER A, VEDIC SIGN VISARGA UDATTA WITH TAIL, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 1CE8 0062;0061 0334 1CE8 16FF0 0062;0061 0334 1CE8 16FF0 0062;0061 0334 1CE8 16FF0 0062;0061 0334 1CE8 16FF0 0062; # (a𖿰◌̴◌᳨b; a◌̴◌᳨𖿰b; a◌̴◌᳨𖿰b; a◌̴◌᳨𖿰b; a◌̴◌᳨𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, VEDIC SIGN VISARGA ANUDATTA WITH TAIL, LATIN SMALL LETTER B
+0061 1CE8 16FF0 0334 0062;0061 1CE8 0334 16FF0 0062;0061 1CE8 0334 16FF0 0062;0061 1CE8 0334 16FF0 0062;0061 1CE8 0334 16FF0 0062; # (a◌᳨𖿰◌̴b; a◌᳨◌̴𖿰b; a◌᳨◌̴𖿰b; a◌᳨◌̴𖿰b; a◌᳨◌̴𖿰b; ) LATIN SMALL LETTER A, VEDIC SIGN VISARGA ANUDATTA WITH TAIL, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1CED 0062;0061 1DFA 0316 1CED 059A 0062;0061 1DFA 0316 1CED 059A 0062;0061 1DFA 0316 1CED 059A 0062;0061 1DFA 0316 1CED 059A 0062; # (a◌֚◌̖◌᷺◌᳭b; a◌᷺◌̖◌᳭◌֚b; a◌᷺◌̖◌᳭◌֚b; a◌᷺◌̖◌᳭◌֚b; a◌᷺◌̖◌᳭◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, VEDIC SIGN TIRYAK, LATIN SMALL LETTER B
+0061 1CED 059A 0316 1DFA 0062;0061 1DFA 1CED 0316 059A 0062;0061 1DFA 1CED 0316 059A 0062;0061 1DFA 1CED 0316 059A 0062;0061 1DFA 1CED 0316 059A 0062; # (a◌᳭◌֚◌̖◌᷺b; a◌᷺◌᳭◌̖◌֚b; a◌᷺◌᳭◌̖◌֚b; a◌᷺◌᳭◌̖◌֚b; a◌᷺◌᳭◌̖◌֚b; ) LATIN SMALL LETTER A, VEDIC SIGN TIRYAK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1CF4 0062;00E0 05AE 1CF4 0315 0062;0061 05AE 0300 1CF4 0315 0062;00E0 05AE 1CF4 0315 0062;0061 05AE 0300 1CF4 0315 0062; # (a◌̕◌̀◌֮◌᳴b; à◌֮◌᳴◌̕b; a◌֮◌̀◌᳴◌̕b; à◌֮◌᳴◌̕b; a◌֮◌̀◌᳴◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, VEDIC TONE CANDRA ABOVE, LATIN SMALL LETTER B
+0061 1CF4 0315 0300 05AE 0062;0061 05AE 1CF4 0300 0315 0062;0061 05AE 1CF4 0300 0315 0062;0061 05AE 1CF4 0300 0315 0062;0061 05AE 1CF4 0300 0315 0062; # (a◌᳴◌̕◌̀◌֮b; a◌֮◌᳴◌̀◌̕b; a◌֮◌᳴◌̀◌̕b; a◌֮◌᳴◌̀◌̕b; a◌֮◌᳴◌̀◌̕b; ) LATIN SMALL LETTER A, VEDIC TONE CANDRA ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1CF8 0062;00E0 05AE 1CF8 0315 0062;0061 05AE 0300 1CF8 0315 0062;00E0 05AE 1CF8 0315 0062;0061 05AE 0300 1CF8 0315 0062; # (a◌̕◌̀◌֮◌᳸b; à◌֮◌᳸◌̕b; a◌֮◌̀◌᳸◌̕b; à◌֮◌᳸◌̕b; a◌֮◌̀◌᳸◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, VEDIC TONE RING ABOVE, LATIN SMALL LETTER B
+0061 1CF8 0315 0300 05AE 0062;0061 05AE 1CF8 0300 0315 0062;0061 05AE 1CF8 0300 0315 0062;0061 05AE 1CF8 0300 0315 0062;0061 05AE 1CF8 0300 0315 0062; # (a◌᳸◌̕◌̀◌֮b; a◌֮◌᳸◌̀◌̕b; a◌֮◌᳸◌̀◌̕b; a◌֮◌᳸◌̀◌̕b; a◌֮◌᳸◌̀◌̕b; ) LATIN SMALL LETTER A, VEDIC TONE RING ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1CF9 0062;00E0 05AE 1CF9 0315 0062;0061 05AE 0300 1CF9 0315 0062;00E0 05AE 1CF9 0315 0062;0061 05AE 0300 1CF9 0315 0062; # (a◌̕◌̀◌֮◌᳹b; à◌֮◌᳹◌̕b; a◌֮◌̀◌᳹◌̕b; à◌֮◌᳹◌̕b; a◌֮◌̀◌᳹◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, VEDIC TONE DOUBLE RING ABOVE, LATIN SMALL LETTER B
+0061 1CF9 0315 0300 05AE 0062;0061 05AE 1CF9 0300 0315 0062;0061 05AE 1CF9 0300 0315 0062;0061 05AE 1CF9 0300 0315 0062;0061 05AE 1CF9 0300 0315 0062; # (a◌᳹◌̕◌̀◌֮b; a◌֮◌᳹◌̀◌̕b; a◌֮◌᳹◌̀◌̕b; a◌֮◌᳹◌̀◌̕b; a◌֮◌᳹◌̀◌̕b; ) LATIN SMALL LETTER A, VEDIC TONE DOUBLE RING ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DC0 0062;00E0 05AE 1DC0 0315 0062;0061 05AE 0300 1DC0 0315 0062;00E0 05AE 1DC0 0315 0062;0061 05AE 0300 1DC0 0315 0062; # (a◌̕◌̀◌֮◌᷀b; à◌֮◌᷀◌̕b; a◌֮◌̀◌᷀◌̕b; à◌֮◌᷀◌̕b; a◌֮◌̀◌᷀◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOTTED GRAVE ACCENT, LATIN SMALL LETTER B
+0061 1DC0 0315 0300 05AE 0062;0061 05AE 1DC0 0300 0315 0062;0061 05AE 1DC0 0300 0315 0062;0061 05AE 1DC0 0300 0315 0062;0061 05AE 1DC0 0300 0315 0062; # (a◌᷀◌̕◌̀◌֮b; a◌֮◌᷀◌̀◌̕b; a◌֮◌᷀◌̀◌̕b; a◌֮◌᷀◌̀◌̕b; a◌֮◌᷀◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOTTED GRAVE ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DC1 0062;00E0 05AE 1DC1 0315 0062;0061 05AE 0300 1DC1 0315 0062;00E0 05AE 1DC1 0315 0062;0061 05AE 0300 1DC1 0315 0062; # (a◌̕◌̀◌֮◌á·b; à◌֮◌á·â—ŒÌ•b; a◌֮◌̀◌á·â—ŒÌ•b; à◌֮◌á·â—ŒÌ•b; a◌֮◌̀◌á·â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOTTED ACUTE ACCENT, LATIN SMALL LETTER B
+0061 1DC1 0315 0300 05AE 0062;0061 05AE 1DC1 0300 0315 0062;0061 05AE 1DC1 0300 0315 0062;0061 05AE 1DC1 0300 0315 0062;0061 05AE 1DC1 0300 0315 0062; # (aâ—Œá·â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌á·â—ŒÌ€â—ŒÌ•b; a◌֮◌á·â—ŒÌ€â—ŒÌ•b; a◌֮◌á·â—ŒÌ€â—ŒÌ•b; a◌֮◌á·â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING DOTTED ACUTE ACCENT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1DC2 0062;0061 1DFA 0316 1DC2 059A 0062;0061 1DFA 0316 1DC2 059A 0062;0061 1DFA 0316 1DC2 059A 0062;0061 1DFA 0316 1DC2 059A 0062; # (a◌֚◌̖◌᷺◌᷂b; a◌᷺◌̖◌᷂◌֚b; a◌᷺◌̖◌᷂◌֚b; a◌᷺◌̖◌᷂◌֚b; a◌᷺◌̖◌᷂◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING SNAKE BELOW, LATIN SMALL LETTER B
+0061 1DC2 059A 0316 1DFA 0062;0061 1DFA 1DC2 0316 059A 0062;0061 1DFA 1DC2 0316 059A 0062;0061 1DFA 1DC2 0316 059A 0062;0061 1DFA 1DC2 0316 059A 0062; # (a◌᷂◌֚◌̖◌᷺b; a◌᷺◌᷂◌̖◌֚b; a◌᷺◌᷂◌̖◌֚b; a◌᷺◌᷂◌̖◌֚b; a◌᷺◌᷂◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING SNAKE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DC3 0062;00E0 05AE 1DC3 0315 0062;0061 05AE 0300 1DC3 0315 0062;00E0 05AE 1DC3 0315 0062;0061 05AE 0300 1DC3 0315 0062; # (a◌̕◌̀◌֮◌᷃b; à◌֮◌᷃◌̕b; a◌֮◌̀◌᷃◌̕b; à◌֮◌᷃◌̕b; a◌֮◌̀◌᷃◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING SUSPENSION MARK, LATIN SMALL LETTER B
+0061 1DC3 0315 0300 05AE 0062;0061 05AE 1DC3 0300 0315 0062;0061 05AE 1DC3 0300 0315 0062;0061 05AE 1DC3 0300 0315 0062;0061 05AE 1DC3 0300 0315 0062; # (a◌᷃◌̕◌̀◌֮b; a◌֮◌᷃◌̀◌̕b; a◌֮◌᷃◌̀◌̕b; a◌֮◌᷃◌̀◌̕b; a◌֮◌᷃◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING SUSPENSION MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DC4 0062;00E0 05AE 1DC4 0315 0062;0061 05AE 0300 1DC4 0315 0062;00E0 05AE 1DC4 0315 0062;0061 05AE 0300 1DC4 0315 0062; # (a◌̕◌̀◌֮◌᷄b; à◌֮◌᷄◌̕b; a◌֮◌̀◌᷄◌̕b; à◌֮◌᷄◌̕b; a◌֮◌̀◌᷄◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING MACRON-ACUTE, LATIN SMALL LETTER B
+0061 1DC4 0315 0300 05AE 0062;0061 05AE 1DC4 0300 0315 0062;0061 05AE 1DC4 0300 0315 0062;0061 05AE 1DC4 0300 0315 0062;0061 05AE 1DC4 0300 0315 0062; # (a◌᷄◌̕◌̀◌֮b; a◌֮◌᷄◌̀◌̕b; a◌֮◌᷄◌̀◌̕b; a◌֮◌᷄◌̀◌̕b; a◌֮◌᷄◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING MACRON-ACUTE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DC5 0062;00E0 05AE 1DC5 0315 0062;0061 05AE 0300 1DC5 0315 0062;00E0 05AE 1DC5 0315 0062;0061 05AE 0300 1DC5 0315 0062; # (a◌̕◌̀◌֮◌᷅b; à◌֮◌᷅◌̕b; a◌֮◌̀◌᷅◌̕b; à◌֮◌᷅◌̕b; a◌֮◌̀◌᷅◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRAVE-MACRON, LATIN SMALL LETTER B
+0061 1DC5 0315 0300 05AE 0062;0061 05AE 1DC5 0300 0315 0062;0061 05AE 1DC5 0300 0315 0062;0061 05AE 1DC5 0300 0315 0062;0061 05AE 1DC5 0300 0315 0062; # (a◌᷅◌̕◌̀◌֮b; a◌֮◌᷅◌̀◌̕b; a◌֮◌᷅◌̀◌̕b; a◌֮◌᷅◌̀◌̕b; a◌֮◌᷅◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GRAVE-MACRON, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DC6 0062;00E0 05AE 1DC6 0315 0062;0061 05AE 0300 1DC6 0315 0062;00E0 05AE 1DC6 0315 0062;0061 05AE 0300 1DC6 0315 0062; # (a◌̕◌̀◌֮◌᷆b; à◌֮◌᷆◌̕b; a◌֮◌̀◌᷆◌̕b; à◌֮◌᷆◌̕b; a◌֮◌̀◌᷆◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING MACRON-GRAVE, LATIN SMALL LETTER B
+0061 1DC6 0315 0300 05AE 0062;0061 05AE 1DC6 0300 0315 0062;0061 05AE 1DC6 0300 0315 0062;0061 05AE 1DC6 0300 0315 0062;0061 05AE 1DC6 0300 0315 0062; # (a◌᷆◌̕◌̀◌֮b; a◌֮◌᷆◌̀◌̕b; a◌֮◌᷆◌̀◌̕b; a◌֮◌᷆◌̀◌̕b; a◌֮◌᷆◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING MACRON-GRAVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DC7 0062;00E0 05AE 1DC7 0315 0062;0061 05AE 0300 1DC7 0315 0062;00E0 05AE 1DC7 0315 0062;0061 05AE 0300 1DC7 0315 0062; # (a◌̕◌̀◌֮◌᷇b; à◌֮◌᷇◌̕b; a◌֮◌̀◌᷇◌̕b; à◌֮◌᷇◌̕b; a◌֮◌̀◌᷇◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING ACUTE-MACRON, LATIN SMALL LETTER B
+0061 1DC7 0315 0300 05AE 0062;0061 05AE 1DC7 0300 0315 0062;0061 05AE 1DC7 0300 0315 0062;0061 05AE 1DC7 0300 0315 0062;0061 05AE 1DC7 0300 0315 0062; # (a◌᷇◌̕◌̀◌֮b; a◌֮◌᷇◌̀◌̕b; a◌֮◌᷇◌̀◌̕b; a◌֮◌᷇◌̀◌̕b; a◌֮◌᷇◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING ACUTE-MACRON, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DC8 0062;00E0 05AE 1DC8 0315 0062;0061 05AE 0300 1DC8 0315 0062;00E0 05AE 1DC8 0315 0062;0061 05AE 0300 1DC8 0315 0062; # (a◌̕◌̀◌֮◌᷈b; à◌֮◌᷈◌̕b; a◌֮◌̀◌᷈◌̕b; à◌֮◌᷈◌̕b; a◌֮◌̀◌᷈◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRAVE-ACUTE-GRAVE, LATIN SMALL LETTER B
+0061 1DC8 0315 0300 05AE 0062;0061 05AE 1DC8 0300 0315 0062;0061 05AE 1DC8 0300 0315 0062;0061 05AE 1DC8 0300 0315 0062;0061 05AE 1DC8 0300 0315 0062; # (a◌᷈◌̕◌̀◌֮b; a◌֮◌᷈◌̀◌̕b; a◌֮◌᷈◌̀◌̕b; a◌֮◌᷈◌̀◌̕b; a◌֮◌᷈◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GRAVE-ACUTE-GRAVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DC9 0062;00E0 05AE 1DC9 0315 0062;0061 05AE 0300 1DC9 0315 0062;00E0 05AE 1DC9 0315 0062;0061 05AE 0300 1DC9 0315 0062; # (a◌̕◌̀◌֮◌᷉b; à◌֮◌᷉◌̕b; a◌֮◌̀◌᷉◌̕b; à◌֮◌᷉◌̕b; a◌֮◌̀◌᷉◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING ACUTE-GRAVE-ACUTE, LATIN SMALL LETTER B
+0061 1DC9 0315 0300 05AE 0062;0061 05AE 1DC9 0300 0315 0062;0061 05AE 1DC9 0300 0315 0062;0061 05AE 1DC9 0300 0315 0062;0061 05AE 1DC9 0300 0315 0062; # (a◌᷉◌̕◌̀◌֮b; a◌֮◌᷉◌̀◌̕b; a◌֮◌᷉◌̀◌̕b; a◌֮◌᷉◌̀◌̕b; a◌֮◌᷉◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING ACUTE-GRAVE-ACUTE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1DCA 0062;0061 1DFA 0316 1DCA 059A 0062;0061 1DFA 0316 1DCA 059A 0062;0061 1DFA 0316 1DCA 059A 0062;0061 1DFA 0316 1DCA 059A 0062; # (a◌֚◌̖◌᷺◌᷊b; a◌᷺◌̖◌᷊◌֚b; a◌᷺◌̖◌᷊◌֚b; a◌᷺◌̖◌᷊◌֚b; a◌᷺◌̖◌᷊◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LATIN SMALL LETTER R BELOW, LATIN SMALL LETTER B
+0061 1DCA 059A 0316 1DFA 0062;0061 1DFA 1DCA 0316 059A 0062;0061 1DFA 1DCA 0316 059A 0062;0061 1DFA 1DCA 0316 059A 0062;0061 1DFA 1DCA 0316 059A 0062; # (a◌᷊◌֚◌̖◌᷺b; a◌᷺◌᷊◌̖◌֚b; a◌᷺◌᷊◌̖◌֚b; a◌᷺◌᷊◌̖◌֚b; a◌᷺◌᷊◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER R BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DCB 0062;00E0 05AE 1DCB 0315 0062;0061 05AE 0300 1DCB 0315 0062;00E0 05AE 1DCB 0315 0062;0061 05AE 0300 1DCB 0315 0062; # (a◌̕◌̀◌֮◌᷋b; à◌֮◌᷋◌̕b; a◌֮◌̀◌᷋◌̕b; à◌֮◌᷋◌̕b; a◌֮◌̀◌᷋◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING BREVE-MACRON, LATIN SMALL LETTER B
+0061 1DCB 0315 0300 05AE 0062;0061 05AE 1DCB 0300 0315 0062;0061 05AE 1DCB 0300 0315 0062;0061 05AE 1DCB 0300 0315 0062;0061 05AE 1DCB 0300 0315 0062; # (a◌᷋◌̕◌̀◌֮b; a◌֮◌᷋◌̀◌̕b; a◌֮◌᷋◌̀◌̕b; a◌֮◌᷋◌̀◌̕b; a◌֮◌᷋◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING BREVE-MACRON, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DCC 0062;00E0 05AE 1DCC 0315 0062;0061 05AE 0300 1DCC 0315 0062;00E0 05AE 1DCC 0315 0062;0061 05AE 0300 1DCC 0315 0062; # (a◌̕◌̀◌֮◌᷌b; à◌֮◌᷌◌̕b; a◌֮◌̀◌᷌◌̕b; à◌֮◌᷌◌̕b; a◌֮◌̀◌᷌◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING MACRON-BREVE, LATIN SMALL LETTER B
+0061 1DCC 0315 0300 05AE 0062;0061 05AE 1DCC 0300 0315 0062;0061 05AE 1DCC 0300 0315 0062;0061 05AE 1DCC 0300 0315 0062;0061 05AE 1DCC 0300 0315 0062; # (a◌᷌◌̕◌̀◌֮b; a◌֮◌᷌◌̀◌̕b; a◌֮◌᷌◌̀◌̕b; a◌֮◌᷌◌̀◌̕b; a◌֮◌᷌◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING MACRON-BREVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0345 035D 035C 1DCD 0062;0061 035C 035D 1DCD 0345 0062;0061 035C 035D 1DCD 0345 0062;0061 035C 035D 1DCD 0345 0062;0061 035C 035D 1DCD 0345 0062; # (a◌ͅ◌Í◌͜◌á·b; a◌͜◌Íâ—Œá·â—ŒÍ…b; a◌͜◌Íâ—Œá·â—ŒÍ…b; a◌͜◌Íâ—Œá·â—ŒÍ…b; a◌͜◌Íâ—Œá·â—ŒÍ…b; ) LATIN SMALL LETTER A, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING DOUBLE CIRCUMFLEX ABOVE, LATIN SMALL LETTER B
+0061 1DCD 0345 035D 035C 0062;0061 035C 1DCD 035D 0345 0062;0061 035C 1DCD 035D 0345 0062;0061 035C 1DCD 035D 0345 0062;0061 035C 1DCD 035D 0345 0062; # (aâ—Œá·â—ŒÍ…â—ŒÍ◌͜b; a◌͜◌á·â—ŒÍ◌ͅb; a◌͜◌á·â—ŒÍ◌ͅb; a◌͜◌á·â—ŒÍ◌ͅb; a◌͜◌á·â—ŒÍ◌ͅb; ) LATIN SMALL LETTER A, COMBINING DOUBLE CIRCUMFLEX ABOVE, COMBINING GREEK YPOGEGRAMMENI, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, LATIN SMALL LETTER B
+0061 031B 1DCE 0321 1DCE 0062;0061 0321 1DCE 1DCE 031B 0062;0061 0321 1DCE 1DCE 031B 0062;0061 0321 1DCE 1DCE 031B 0062;0061 0321 1DCE 1DCE 031B 0062; # (a◌̛◌᷎◌̡◌᷎b; a◌̡◌᷎◌᷎◌̛b; a◌̡◌᷎◌᷎◌̛b; a◌̡◌᷎◌᷎◌̛b; a◌̡◌᷎◌᷎◌̛b; ) LATIN SMALL LETTER A, COMBINING HORN, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, COMBINING OGONEK ABOVE, LATIN SMALL LETTER B
+0061 1DCE 031B 1DCE 0321 0062;0061 0321 1DCE 1DCE 031B 0062;0061 0321 1DCE 1DCE 031B 0062;0061 0321 1DCE 1DCE 031B 0062;0061 0321 1DCE 1DCE 031B 0062; # (a◌᷎◌̛◌᷎◌̡b; a◌̡◌᷎◌᷎◌̛b; a◌̡◌᷎◌᷎◌̛b; a◌̡◌᷎◌᷎◌̛b; a◌̡◌᷎◌᷎◌̛b; ) LATIN SMALL LETTER A, COMBINING OGONEK ABOVE, COMBINING HORN, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1DCF 0062;0061 1DFA 0316 1DCF 059A 0062;0061 1DFA 0316 1DCF 059A 0062;0061 1DFA 0316 1DCF 059A 0062;0061 1DFA 0316 1DCF 059A 0062; # (a◌֚◌̖◌᷺◌á·b; a◌᷺◌̖◌á·â—ŒÖšb; a◌᷺◌̖◌á·â—ŒÖšb; a◌᷺◌̖◌á·â—ŒÖšb; a◌᷺◌̖◌á·â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING ZIGZAG BELOW, LATIN SMALL LETTER B
+0061 1DCF 059A 0316 1DFA 0062;0061 1DFA 1DCF 0316 059A 0062;0061 1DFA 1DCF 0316 059A 0062;0061 1DFA 1DCF 0316 059A 0062;0061 1DFA 1DCF 0316 059A 0062; # (aâ—Œá·â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌á·â—ŒÌ–◌֚b; a◌᷺◌á·â—ŒÌ–◌֚b; a◌᷺◌á·â—ŒÌ–◌֚b; a◌᷺◌á·â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, COMBINING ZIGZAG BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 1DCE 0321 0F74 1DD0 0062;0061 0F74 0321 1DD0 1DCE 0062;0061 0F74 0321 1DD0 1DCE 0062;0061 0F74 0321 1DD0 1DCE 0062;0061 0F74 0321 1DD0 1DCE 0062; # (a◌᷎◌̡◌ུ◌á·b; a◌ུ◌̡◌á·â—Œá·Žb; a◌ུ◌̡◌á·â—Œá·Žb; a◌ུ◌̡◌á·â—Œá·Žb; a◌ུ◌̡◌á·â—Œá·Žb; ) LATIN SMALL LETTER A, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, COMBINING IS BELOW, LATIN SMALL LETTER B
+0061 1DD0 1DCE 0321 0F74 0062;0061 0F74 1DD0 0321 1DCE 0062;0061 0F74 1DD0 0321 1DCE 0062;0061 0F74 1DD0 0321 1DCE 0062;0061 0F74 1DD0 0321 1DCE 0062; # (aâ—Œá·â—Œá·Žâ—ŒÌ¡â—Œà½´b; a◌ུ◌á·â—ŒÌ¡â—Œá·Žb; a◌ུ◌á·â—ŒÌ¡â—Œá·Žb; a◌ུ◌á·â—ŒÌ¡â—Œá·Žb; a◌ུ◌á·â—ŒÌ¡â—Œá·Žb; ) LATIN SMALL LETTER A, COMBINING IS BELOW, COMBINING OGONEK ABOVE, COMBINING PALATALIZED HOOK BELOW, TIBETAN VOWEL SIGN U, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DD1 0062;00E0 05AE 1DD1 0315 0062;0061 05AE 0300 1DD1 0315 0062;00E0 05AE 1DD1 0315 0062;0061 05AE 0300 1DD1 0315 0062; # (a◌̕◌̀◌֮◌᷑b; à◌֮◌᷑◌̕b; a◌֮◌̀◌᷑◌̕b; à◌֮◌᷑◌̕b; a◌֮◌̀◌᷑◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING UR ABOVE, LATIN SMALL LETTER B
+0061 1DD1 0315 0300 05AE 0062;0061 05AE 1DD1 0300 0315 0062;0061 05AE 1DD1 0300 0315 0062;0061 05AE 1DD1 0300 0315 0062;0061 05AE 1DD1 0300 0315 0062; # (a◌᷑◌̕◌̀◌֮b; a◌֮◌᷑◌̀◌̕b; a◌֮◌᷑◌̀◌̕b; a◌֮◌᷑◌̀◌̕b; a◌֮◌᷑◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING UR ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DD2 0062;00E0 05AE 1DD2 0315 0062;0061 05AE 0300 1DD2 0315 0062;00E0 05AE 1DD2 0315 0062;0061 05AE 0300 1DD2 0315 0062; # (a◌̕◌̀◌֮◌᷒b; à◌֮◌᷒◌̕b; a◌֮◌̀◌᷒◌̕b; à◌֮◌᷒◌̕b; a◌֮◌̀◌᷒◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING US ABOVE, LATIN SMALL LETTER B
+0061 1DD2 0315 0300 05AE 0062;0061 05AE 1DD2 0300 0315 0062;0061 05AE 1DD2 0300 0315 0062;0061 05AE 1DD2 0300 0315 0062;0061 05AE 1DD2 0300 0315 0062; # (a◌᷒◌̕◌̀◌֮b; a◌֮◌᷒◌̀◌̕b; a◌֮◌᷒◌̀◌̕b; a◌֮◌᷒◌̀◌̕b; a◌֮◌᷒◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING US ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DD3 0062;00E0 05AE 1DD3 0315 0062;0061 05AE 0300 1DD3 0315 0062;00E0 05AE 1DD3 0315 0062;0061 05AE 0300 1DD3 0315 0062; # (a◌̕◌̀◌֮◌ᷓb; à◌֮◌ᷓ◌̕b; a◌֮◌̀◌ᷓ◌̕b; à◌֮◌ᷓ◌̕b; a◌֮◌̀◌ᷓ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER FLATTENED OPEN A ABOVE, LATIN SMALL LETTER B
+0061 1DD3 0315 0300 05AE 0062;0061 05AE 1DD3 0300 0315 0062;0061 05AE 1DD3 0300 0315 0062;0061 05AE 1DD3 0300 0315 0062;0061 05AE 1DD3 0300 0315 0062; # (a◌ᷓ◌̕◌̀◌֮b; a◌֮◌ᷓ◌̀◌̕b; a◌֮◌ᷓ◌̀◌̕b; a◌֮◌ᷓ◌̀◌̕b; a◌֮◌ᷓ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER FLATTENED OPEN A ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DD4 0062;00E0 05AE 1DD4 0315 0062;0061 05AE 0300 1DD4 0315 0062;00E0 05AE 1DD4 0315 0062;0061 05AE 0300 1DD4 0315 0062; # (a◌̕◌̀◌֮◌ᷔb; à◌֮◌ᷔ◌̕b; a◌֮◌̀◌ᷔ◌̕b; à◌֮◌ᷔ◌̕b; a◌֮◌̀◌ᷔ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER AE, LATIN SMALL LETTER B
+0061 1DD4 0315 0300 05AE 0062;0061 05AE 1DD4 0300 0315 0062;0061 05AE 1DD4 0300 0315 0062;0061 05AE 1DD4 0300 0315 0062;0061 05AE 1DD4 0300 0315 0062; # (a◌ᷔ◌̕◌̀◌֮b; a◌֮◌ᷔ◌̀◌̕b; a◌֮◌ᷔ◌̀◌̕b; a◌֮◌ᷔ◌̀◌̕b; a◌֮◌ᷔ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER AE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DD5 0062;00E0 05AE 1DD5 0315 0062;0061 05AE 0300 1DD5 0315 0062;00E0 05AE 1DD5 0315 0062;0061 05AE 0300 1DD5 0315 0062; # (a◌̕◌̀◌֮◌ᷕb; à◌֮◌ᷕ◌̕b; a◌֮◌̀◌ᷕ◌̕b; à◌֮◌ᷕ◌̕b; a◌֮◌̀◌ᷕ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER AO, LATIN SMALL LETTER B
+0061 1DD5 0315 0300 05AE 0062;0061 05AE 1DD5 0300 0315 0062;0061 05AE 1DD5 0300 0315 0062;0061 05AE 1DD5 0300 0315 0062;0061 05AE 1DD5 0300 0315 0062; # (a◌ᷕ◌̕◌̀◌֮b; a◌֮◌ᷕ◌̀◌̕b; a◌֮◌ᷕ◌̀◌̕b; a◌֮◌ᷕ◌̀◌̕b; a◌֮◌ᷕ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER AO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DD6 0062;00E0 05AE 1DD6 0315 0062;0061 05AE 0300 1DD6 0315 0062;00E0 05AE 1DD6 0315 0062;0061 05AE 0300 1DD6 0315 0062; # (a◌̕◌̀◌֮◌ᷖb; à◌֮◌ᷖ◌̕b; a◌֮◌̀◌ᷖ◌̕b; à◌֮◌ᷖ◌̕b; a◌֮◌̀◌ᷖ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER AV, LATIN SMALL LETTER B
+0061 1DD6 0315 0300 05AE 0062;0061 05AE 1DD6 0300 0315 0062;0061 05AE 1DD6 0300 0315 0062;0061 05AE 1DD6 0300 0315 0062;0061 05AE 1DD6 0300 0315 0062; # (a◌ᷖ◌̕◌̀◌֮b; a◌֮◌ᷖ◌̀◌̕b; a◌֮◌ᷖ◌̀◌̕b; a◌֮◌ᷖ◌̀◌̕b; a◌֮◌ᷖ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER AV, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DD7 0062;00E0 05AE 1DD7 0315 0062;0061 05AE 0300 1DD7 0315 0062;00E0 05AE 1DD7 0315 0062;0061 05AE 0300 1DD7 0315 0062; # (a◌̕◌̀◌֮◌ᷗb; à◌֮◌ᷗ◌̕b; a◌֮◌̀◌ᷗ◌̕b; à◌֮◌ᷗ◌̕b; a◌֮◌̀◌ᷗ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER C CEDILLA, LATIN SMALL LETTER B
+0061 1DD7 0315 0300 05AE 0062;0061 05AE 1DD7 0300 0315 0062;0061 05AE 1DD7 0300 0315 0062;0061 05AE 1DD7 0300 0315 0062;0061 05AE 1DD7 0300 0315 0062; # (a◌ᷗ◌̕◌̀◌֮b; a◌֮◌ᷗ◌̀◌̕b; a◌֮◌ᷗ◌̀◌̕b; a◌֮◌ᷗ◌̀◌̕b; a◌֮◌ᷗ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER C CEDILLA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DD8 0062;00E0 05AE 1DD8 0315 0062;0061 05AE 0300 1DD8 0315 0062;00E0 05AE 1DD8 0315 0062;0061 05AE 0300 1DD8 0315 0062; # (a◌̕◌̀◌֮◌ᷘb; à◌֮◌ᷘ◌̕b; a◌֮◌̀◌ᷘ◌̕b; à◌֮◌ᷘ◌̕b; a◌֮◌̀◌ᷘ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER INSULAR D, LATIN SMALL LETTER B
+0061 1DD8 0315 0300 05AE 0062;0061 05AE 1DD8 0300 0315 0062;0061 05AE 1DD8 0300 0315 0062;0061 05AE 1DD8 0300 0315 0062;0061 05AE 1DD8 0300 0315 0062; # (a◌ᷘ◌̕◌̀◌֮b; a◌֮◌ᷘ◌̀◌̕b; a◌֮◌ᷘ◌̀◌̕b; a◌֮◌ᷘ◌̀◌̕b; a◌֮◌ᷘ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER INSULAR D, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DD9 0062;00E0 05AE 1DD9 0315 0062;0061 05AE 0300 1DD9 0315 0062;00E0 05AE 1DD9 0315 0062;0061 05AE 0300 1DD9 0315 0062; # (a◌̕◌̀◌֮◌ᷙb; à◌֮◌ᷙ◌̕b; a◌֮◌̀◌ᷙ◌̕b; à◌֮◌ᷙ◌̕b; a◌֮◌̀◌ᷙ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER ETH, LATIN SMALL LETTER B
+0061 1DD9 0315 0300 05AE 0062;0061 05AE 1DD9 0300 0315 0062;0061 05AE 1DD9 0300 0315 0062;0061 05AE 1DD9 0300 0315 0062;0061 05AE 1DD9 0300 0315 0062; # (a◌ᷙ◌̕◌̀◌֮b; a◌֮◌ᷙ◌̀◌̕b; a◌֮◌ᷙ◌̀◌̕b; a◌֮◌ᷙ◌̀◌̕b; a◌֮◌ᷙ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER ETH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DDA 0062;00E0 05AE 1DDA 0315 0062;0061 05AE 0300 1DDA 0315 0062;00E0 05AE 1DDA 0315 0062;0061 05AE 0300 1DDA 0315 0062; # (a◌̕◌̀◌֮◌ᷚb; à◌֮◌ᷚ◌̕b; a◌֮◌̀◌ᷚ◌̕b; à◌֮◌ᷚ◌̕b; a◌֮◌̀◌ᷚ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER G, LATIN SMALL LETTER B
+0061 1DDA 0315 0300 05AE 0062;0061 05AE 1DDA 0300 0315 0062;0061 05AE 1DDA 0300 0315 0062;0061 05AE 1DDA 0300 0315 0062;0061 05AE 1DDA 0300 0315 0062; # (a◌ᷚ◌̕◌̀◌֮b; a◌֮◌ᷚ◌̀◌̕b; a◌֮◌ᷚ◌̀◌̕b; a◌֮◌ᷚ◌̀◌̕b; a◌֮◌ᷚ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER G, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DDB 0062;00E0 05AE 1DDB 0315 0062;0061 05AE 0300 1DDB 0315 0062;00E0 05AE 1DDB 0315 0062;0061 05AE 0300 1DDB 0315 0062; # (a◌̕◌̀◌֮◌ᷛb; à◌֮◌ᷛ◌̕b; a◌֮◌̀◌ᷛ◌̕b; à◌֮◌ᷛ◌̕b; a◌֮◌̀◌ᷛ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN LETTER SMALL CAPITAL G, LATIN SMALL LETTER B
+0061 1DDB 0315 0300 05AE 0062;0061 05AE 1DDB 0300 0315 0062;0061 05AE 1DDB 0300 0315 0062;0061 05AE 1DDB 0300 0315 0062;0061 05AE 1DDB 0300 0315 0062; # (a◌ᷛ◌̕◌̀◌֮b; a◌֮◌ᷛ◌̀◌̕b; a◌֮◌ᷛ◌̀◌̕b; a◌֮◌ᷛ◌̀◌̕b; a◌֮◌ᷛ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN LETTER SMALL CAPITAL G, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DDC 0062;00E0 05AE 1DDC 0315 0062;0061 05AE 0300 1DDC 0315 0062;00E0 05AE 1DDC 0315 0062;0061 05AE 0300 1DDC 0315 0062; # (a◌̕◌̀◌֮◌ᷜb; à◌֮◌ᷜ◌̕b; a◌֮◌̀◌ᷜ◌̕b; à◌֮◌ᷜ◌̕b; a◌֮◌̀◌ᷜ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER K, LATIN SMALL LETTER B
+0061 1DDC 0315 0300 05AE 0062;0061 05AE 1DDC 0300 0315 0062;0061 05AE 1DDC 0300 0315 0062;0061 05AE 1DDC 0300 0315 0062;0061 05AE 1DDC 0300 0315 0062; # (a◌ᷜ◌̕◌̀◌֮b; a◌֮◌ᷜ◌̀◌̕b; a◌֮◌ᷜ◌̀◌̕b; a◌֮◌ᷜ◌̀◌̕b; a◌֮◌ᷜ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER K, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DDD 0062;00E0 05AE 1DDD 0315 0062;0061 05AE 0300 1DDD 0315 0062;00E0 05AE 1DDD 0315 0062;0061 05AE 0300 1DDD 0315 0062; # (a◌̕◌̀◌֮◌á·b; à◌֮◌á·â—ŒÌ•b; a◌֮◌̀◌á·â—ŒÌ•b; à◌֮◌á·â—ŒÌ•b; a◌֮◌̀◌á·â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER L, LATIN SMALL LETTER B
+0061 1DDD 0315 0300 05AE 0062;0061 05AE 1DDD 0300 0315 0062;0061 05AE 1DDD 0300 0315 0062;0061 05AE 1DDD 0300 0315 0062;0061 05AE 1DDD 0300 0315 0062; # (aâ—Œá·â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌á·â—ŒÌ€â—ŒÌ•b; a◌֮◌á·â—ŒÌ€â—ŒÌ•b; a◌֮◌á·â—ŒÌ€â—ŒÌ•b; a◌֮◌á·â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER L, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DDE 0062;00E0 05AE 1DDE 0315 0062;0061 05AE 0300 1DDE 0315 0062;00E0 05AE 1DDE 0315 0062;0061 05AE 0300 1DDE 0315 0062; # (a◌̕◌̀◌֮◌ᷞb; à◌֮◌ᷞ◌̕b; a◌֮◌̀◌ᷞ◌̕b; à◌֮◌ᷞ◌̕b; a◌֮◌̀◌ᷞ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN LETTER SMALL CAPITAL L, LATIN SMALL LETTER B
+0061 1DDE 0315 0300 05AE 0062;0061 05AE 1DDE 0300 0315 0062;0061 05AE 1DDE 0300 0315 0062;0061 05AE 1DDE 0300 0315 0062;0061 05AE 1DDE 0300 0315 0062; # (a◌ᷞ◌̕◌̀◌֮b; a◌֮◌ᷞ◌̀◌̕b; a◌֮◌ᷞ◌̀◌̕b; a◌֮◌ᷞ◌̀◌̕b; a◌֮◌ᷞ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN LETTER SMALL CAPITAL L, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DDF 0062;00E0 05AE 1DDF 0315 0062;0061 05AE 0300 1DDF 0315 0062;00E0 05AE 1DDF 0315 0062;0061 05AE 0300 1DDF 0315 0062; # (a◌̕◌̀◌֮◌ᷟb; à◌֮◌ᷟ◌̕b; a◌֮◌̀◌ᷟ◌̕b; à◌֮◌ᷟ◌̕b; a◌֮◌̀◌ᷟ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN LETTER SMALL CAPITAL M, LATIN SMALL LETTER B
+0061 1DDF 0315 0300 05AE 0062;0061 05AE 1DDF 0300 0315 0062;0061 05AE 1DDF 0300 0315 0062;0061 05AE 1DDF 0300 0315 0062;0061 05AE 1DDF 0300 0315 0062; # (a◌ᷟ◌̕◌̀◌֮b; a◌֮◌ᷟ◌̀◌̕b; a◌֮◌ᷟ◌̀◌̕b; a◌֮◌ᷟ◌̀◌̕b; a◌֮◌ᷟ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN LETTER SMALL CAPITAL M, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DE0 0062;00E0 05AE 1DE0 0315 0062;0061 05AE 0300 1DE0 0315 0062;00E0 05AE 1DE0 0315 0062;0061 05AE 0300 1DE0 0315 0062; # (a◌̕◌̀◌֮◌ᷠb; à◌֮◌ᷠ◌̕b; a◌֮◌̀◌ᷠ◌̕b; à◌֮◌ᷠ◌̕b; a◌֮◌̀◌ᷠ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER N, LATIN SMALL LETTER B
+0061 1DE0 0315 0300 05AE 0062;0061 05AE 1DE0 0300 0315 0062;0061 05AE 1DE0 0300 0315 0062;0061 05AE 1DE0 0300 0315 0062;0061 05AE 1DE0 0300 0315 0062; # (a◌ᷠ◌̕◌̀◌֮b; a◌֮◌ᷠ◌̀◌̕b; a◌֮◌ᷠ◌̀◌̕b; a◌֮◌ᷠ◌̀◌̕b; a◌֮◌ᷠ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER N, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DE1 0062;00E0 05AE 1DE1 0315 0062;0061 05AE 0300 1DE1 0315 0062;00E0 05AE 1DE1 0315 0062;0061 05AE 0300 1DE1 0315 0062; # (a◌̕◌̀◌֮◌ᷡb; à◌֮◌ᷡ◌̕b; a◌֮◌̀◌ᷡ◌̕b; à◌֮◌ᷡ◌̕b; a◌֮◌̀◌ᷡ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN LETTER SMALL CAPITAL N, LATIN SMALL LETTER B
+0061 1DE1 0315 0300 05AE 0062;0061 05AE 1DE1 0300 0315 0062;0061 05AE 1DE1 0300 0315 0062;0061 05AE 1DE1 0300 0315 0062;0061 05AE 1DE1 0300 0315 0062; # (a◌ᷡ◌̕◌̀◌֮b; a◌֮◌ᷡ◌̀◌̕b; a◌֮◌ᷡ◌̀◌̕b; a◌֮◌ᷡ◌̀◌̕b; a◌֮◌ᷡ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN LETTER SMALL CAPITAL N, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DE2 0062;00E0 05AE 1DE2 0315 0062;0061 05AE 0300 1DE2 0315 0062;00E0 05AE 1DE2 0315 0062;0061 05AE 0300 1DE2 0315 0062; # (a◌̕◌̀◌֮◌ᷢb; à◌֮◌ᷢ◌̕b; a◌֮◌̀◌ᷢ◌̕b; à◌֮◌ᷢ◌̕b; a◌֮◌̀◌ᷢ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN LETTER SMALL CAPITAL R, LATIN SMALL LETTER B
+0061 1DE2 0315 0300 05AE 0062;0061 05AE 1DE2 0300 0315 0062;0061 05AE 1DE2 0300 0315 0062;0061 05AE 1DE2 0300 0315 0062;0061 05AE 1DE2 0300 0315 0062; # (a◌ᷢ◌̕◌̀◌֮b; a◌֮◌ᷢ◌̀◌̕b; a◌֮◌ᷢ◌̀◌̕b; a◌֮◌ᷢ◌̀◌̕b; a◌֮◌ᷢ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN LETTER SMALL CAPITAL R, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DE3 0062;00E0 05AE 1DE3 0315 0062;0061 05AE 0300 1DE3 0315 0062;00E0 05AE 1DE3 0315 0062;0061 05AE 0300 1DE3 0315 0062; # (a◌̕◌̀◌֮◌ᷣb; à◌֮◌ᷣ◌̕b; a◌֮◌̀◌ᷣ◌̕b; à◌֮◌ᷣ◌̕b; a◌֮◌̀◌ᷣ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER R ROTUNDA, LATIN SMALL LETTER B
+0061 1DE3 0315 0300 05AE 0062;0061 05AE 1DE3 0300 0315 0062;0061 05AE 1DE3 0300 0315 0062;0061 05AE 1DE3 0300 0315 0062;0061 05AE 1DE3 0300 0315 0062; # (a◌ᷣ◌̕◌̀◌֮b; a◌֮◌ᷣ◌̀◌̕b; a◌֮◌ᷣ◌̀◌̕b; a◌֮◌ᷣ◌̀◌̕b; a◌֮◌ᷣ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER R ROTUNDA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DE4 0062;00E0 05AE 1DE4 0315 0062;0061 05AE 0300 1DE4 0315 0062;00E0 05AE 1DE4 0315 0062;0061 05AE 0300 1DE4 0315 0062; # (a◌̕◌̀◌֮◌ᷤb; à◌֮◌ᷤ◌̕b; a◌֮◌̀◌ᷤ◌̕b; à◌֮◌ᷤ◌̕b; a◌֮◌̀◌ᷤ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER S, LATIN SMALL LETTER B
+0061 1DE4 0315 0300 05AE 0062;0061 05AE 1DE4 0300 0315 0062;0061 05AE 1DE4 0300 0315 0062;0061 05AE 1DE4 0300 0315 0062;0061 05AE 1DE4 0300 0315 0062; # (a◌ᷤ◌̕◌̀◌֮b; a◌֮◌ᷤ◌̀◌̕b; a◌֮◌ᷤ◌̀◌̕b; a◌֮◌ᷤ◌̀◌̕b; a◌֮◌ᷤ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER S, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DE5 0062;00E0 05AE 1DE5 0315 0062;0061 05AE 0300 1DE5 0315 0062;00E0 05AE 1DE5 0315 0062;0061 05AE 0300 1DE5 0315 0062; # (a◌̕◌̀◌֮◌ᷥb; à◌֮◌ᷥ◌̕b; a◌֮◌̀◌ᷥ◌̕b; à◌֮◌ᷥ◌̕b; a◌֮◌̀◌ᷥ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER LONG S, LATIN SMALL LETTER B
+0061 1DE5 0315 0300 05AE 0062;0061 05AE 1DE5 0300 0315 0062;0061 05AE 1DE5 0300 0315 0062;0061 05AE 1DE5 0300 0315 0062;0061 05AE 1DE5 0300 0315 0062; # (a◌ᷥ◌̕◌̀◌֮b; a◌֮◌ᷥ◌̀◌̕b; a◌֮◌ᷥ◌̀◌̕b; a◌֮◌ᷥ◌̀◌̕b; a◌֮◌ᷥ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER LONG S, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DE6 0062;00E0 05AE 1DE6 0315 0062;0061 05AE 0300 1DE6 0315 0062;00E0 05AE 1DE6 0315 0062;0061 05AE 0300 1DE6 0315 0062; # (a◌̕◌̀◌֮◌ᷦb; à◌֮◌ᷦ◌̕b; a◌֮◌̀◌ᷦ◌̕b; à◌֮◌ᷦ◌̕b; a◌֮◌̀◌ᷦ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER Z, LATIN SMALL LETTER B
+0061 1DE6 0315 0300 05AE 0062;0061 05AE 1DE6 0300 0315 0062;0061 05AE 1DE6 0300 0315 0062;0061 05AE 1DE6 0300 0315 0062;0061 05AE 1DE6 0300 0315 0062; # (a◌ᷦ◌̕◌̀◌֮b; a◌֮◌ᷦ◌̀◌̕b; a◌֮◌ᷦ◌̀◌̕b; a◌֮◌ᷦ◌̀◌̕b; a◌֮◌ᷦ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER Z, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DE7 0062;00E0 05AE 1DE7 0315 0062;0061 05AE 0300 1DE7 0315 0062;00E0 05AE 1DE7 0315 0062;0061 05AE 0300 1DE7 0315 0062; # (a◌̕◌̀◌֮◌ᷧb; à◌֮◌ᷧ◌̕b; a◌֮◌̀◌ᷧ◌̕b; à◌֮◌ᷧ◌̕b; a◌֮◌̀◌ᷧ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER ALPHA, LATIN SMALL LETTER B
+0061 1DE7 0315 0300 05AE 0062;0061 05AE 1DE7 0300 0315 0062;0061 05AE 1DE7 0300 0315 0062;0061 05AE 1DE7 0300 0315 0062;0061 05AE 1DE7 0300 0315 0062; # (a◌ᷧ◌̕◌̀◌֮b; a◌֮◌ᷧ◌̀◌̕b; a◌֮◌ᷧ◌̀◌̕b; a◌֮◌ᷧ◌̀◌̕b; a◌֮◌ᷧ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER ALPHA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DE8 0062;00E0 05AE 1DE8 0315 0062;0061 05AE 0300 1DE8 0315 0062;00E0 05AE 1DE8 0315 0062;0061 05AE 0300 1DE8 0315 0062; # (a◌̕◌̀◌֮◌ᷨb; à◌֮◌ᷨ◌̕b; a◌֮◌̀◌ᷨ◌̕b; à◌֮◌ᷨ◌̕b; a◌֮◌̀◌ᷨ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER B, LATIN SMALL LETTER B
+0061 1DE8 0315 0300 05AE 0062;0061 05AE 1DE8 0300 0315 0062;0061 05AE 1DE8 0300 0315 0062;0061 05AE 1DE8 0300 0315 0062;0061 05AE 1DE8 0300 0315 0062; # (a◌ᷨ◌̕◌̀◌֮b; a◌֮◌ᷨ◌̀◌̕b; a◌֮◌ᷨ◌̀◌̕b; a◌֮◌ᷨ◌̀◌̕b; a◌֮◌ᷨ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER B, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DE9 0062;00E0 05AE 1DE9 0315 0062;0061 05AE 0300 1DE9 0315 0062;00E0 05AE 1DE9 0315 0062;0061 05AE 0300 1DE9 0315 0062; # (a◌̕◌̀◌֮◌ᷩb; à◌֮◌ᷩ◌̕b; a◌֮◌̀◌ᷩ◌̕b; à◌֮◌ᷩ◌̕b; a◌֮◌̀◌ᷩ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER BETA, LATIN SMALL LETTER B
+0061 1DE9 0315 0300 05AE 0062;0061 05AE 1DE9 0300 0315 0062;0061 05AE 1DE9 0300 0315 0062;0061 05AE 1DE9 0300 0315 0062;0061 05AE 1DE9 0300 0315 0062; # (a◌ᷩ◌̕◌̀◌֮b; a◌֮◌ᷩ◌̀◌̕b; a◌֮◌ᷩ◌̀◌̕b; a◌֮◌ᷩ◌̀◌̕b; a◌֮◌ᷩ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER BETA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DEA 0062;00E0 05AE 1DEA 0315 0062;0061 05AE 0300 1DEA 0315 0062;00E0 05AE 1DEA 0315 0062;0061 05AE 0300 1DEA 0315 0062; # (a◌̕◌̀◌֮◌ᷪb; à◌֮◌ᷪ◌̕b; a◌֮◌̀◌ᷪ◌̕b; à◌֮◌ᷪ◌̕b; a◌֮◌̀◌ᷪ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER SCHWA, LATIN SMALL LETTER B
+0061 1DEA 0315 0300 05AE 0062;0061 05AE 1DEA 0300 0315 0062;0061 05AE 1DEA 0300 0315 0062;0061 05AE 1DEA 0300 0315 0062;0061 05AE 1DEA 0300 0315 0062; # (a◌ᷪ◌̕◌̀◌֮b; a◌֮◌ᷪ◌̀◌̕b; a◌֮◌ᷪ◌̀◌̕b; a◌֮◌ᷪ◌̀◌̕b; a◌֮◌ᷪ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER SCHWA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DEB 0062;00E0 05AE 1DEB 0315 0062;0061 05AE 0300 1DEB 0315 0062;00E0 05AE 1DEB 0315 0062;0061 05AE 0300 1DEB 0315 0062; # (a◌̕◌̀◌֮◌ᷫb; à◌֮◌ᷫ◌̕b; a◌֮◌̀◌ᷫ◌̕b; à◌֮◌ᷫ◌̕b; a◌֮◌̀◌ᷫ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER F, LATIN SMALL LETTER B
+0061 1DEB 0315 0300 05AE 0062;0061 05AE 1DEB 0300 0315 0062;0061 05AE 1DEB 0300 0315 0062;0061 05AE 1DEB 0300 0315 0062;0061 05AE 1DEB 0300 0315 0062; # (a◌ᷫ◌̕◌̀◌֮b; a◌֮◌ᷫ◌̀◌̕b; a◌֮◌ᷫ◌̀◌̕b; a◌֮◌ᷫ◌̀◌̕b; a◌֮◌ᷫ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER F, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DEC 0062;00E0 05AE 1DEC 0315 0062;0061 05AE 0300 1DEC 0315 0062;00E0 05AE 1DEC 0315 0062;0061 05AE 0300 1DEC 0315 0062; # (a◌̕◌̀◌֮◌ᷬb; à◌֮◌ᷬ◌̕b; a◌֮◌̀◌ᷬ◌̕b; à◌֮◌ᷬ◌̕b; a◌֮◌̀◌ᷬ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER L WITH DOUBLE MIDDLE TILDE, LATIN SMALL LETTER B
+0061 1DEC 0315 0300 05AE 0062;0061 05AE 1DEC 0300 0315 0062;0061 05AE 1DEC 0300 0315 0062;0061 05AE 1DEC 0300 0315 0062;0061 05AE 1DEC 0300 0315 0062; # (a◌ᷬ◌̕◌̀◌֮b; a◌֮◌ᷬ◌̀◌̕b; a◌֮◌ᷬ◌̀◌̕b; a◌֮◌ᷬ◌̀◌̕b; a◌֮◌ᷬ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER L WITH DOUBLE MIDDLE TILDE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DED 0062;00E0 05AE 1DED 0315 0062;0061 05AE 0300 1DED 0315 0062;00E0 05AE 1DED 0315 0062;0061 05AE 0300 1DED 0315 0062; # (a◌̕◌̀◌֮◌ᷭb; à◌֮◌ᷭ◌̕b; a◌֮◌̀◌ᷭ◌̕b; à◌֮◌ᷭ◌̕b; a◌֮◌̀◌ᷭ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER O WITH LIGHT CENTRALIZATION STROKE, LATIN SMALL LETTER B
+0061 1DED 0315 0300 05AE 0062;0061 05AE 1DED 0300 0315 0062;0061 05AE 1DED 0300 0315 0062;0061 05AE 1DED 0300 0315 0062;0061 05AE 1DED 0300 0315 0062; # (a◌ᷭ◌̕◌̀◌֮b; a◌֮◌ᷭ◌̀◌̕b; a◌֮◌ᷭ◌̀◌̕b; a◌֮◌ᷭ◌̀◌̕b; a◌֮◌ᷭ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER O WITH LIGHT CENTRALIZATION STROKE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DEE 0062;00E0 05AE 1DEE 0315 0062;0061 05AE 0300 1DEE 0315 0062;00E0 05AE 1DEE 0315 0062;0061 05AE 0300 1DEE 0315 0062; # (a◌̕◌̀◌֮◌ᷮb; à◌֮◌ᷮ◌̕b; a◌֮◌̀◌ᷮ◌̕b; à◌֮◌ᷮ◌̕b; a◌֮◌̀◌ᷮ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER P, LATIN SMALL LETTER B
+0061 1DEE 0315 0300 05AE 0062;0061 05AE 1DEE 0300 0315 0062;0061 05AE 1DEE 0300 0315 0062;0061 05AE 1DEE 0300 0315 0062;0061 05AE 1DEE 0300 0315 0062; # (a◌ᷮ◌̕◌̀◌֮b; a◌֮◌ᷮ◌̀◌̕b; a◌֮◌ᷮ◌̀◌̕b; a◌֮◌ᷮ◌̀◌̕b; a◌֮◌ᷮ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER P, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DEF 0062;00E0 05AE 1DEF 0315 0062;0061 05AE 0300 1DEF 0315 0062;00E0 05AE 1DEF 0315 0062;0061 05AE 0300 1DEF 0315 0062; # (a◌̕◌̀◌֮◌ᷯb; à◌֮◌ᷯ◌̕b; a◌֮◌̀◌ᷯ◌̕b; à◌֮◌ᷯ◌̕b; a◌֮◌̀◌ᷯ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER ESH, LATIN SMALL LETTER B
+0061 1DEF 0315 0300 05AE 0062;0061 05AE 1DEF 0300 0315 0062;0061 05AE 1DEF 0300 0315 0062;0061 05AE 1DEF 0300 0315 0062;0061 05AE 1DEF 0300 0315 0062; # (a◌ᷯ◌̕◌̀◌֮b; a◌֮◌ᷯ◌̀◌̕b; a◌֮◌ᷯ◌̀◌̕b; a◌֮◌ᷯ◌̀◌̕b; a◌֮◌ᷯ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER ESH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DF0 0062;00E0 05AE 1DF0 0315 0062;0061 05AE 0300 1DF0 0315 0062;00E0 05AE 1DF0 0315 0062;0061 05AE 0300 1DF0 0315 0062; # (a◌̕◌̀◌֮◌ᷰb; à◌֮◌ᷰ◌̕b; a◌֮◌̀◌ᷰ◌̕b; à◌֮◌ᷰ◌̕b; a◌֮◌̀◌ᷰ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER U WITH LIGHT CENTRALIZATION STROKE, LATIN SMALL LETTER B
+0061 1DF0 0315 0300 05AE 0062;0061 05AE 1DF0 0300 0315 0062;0061 05AE 1DF0 0300 0315 0062;0061 05AE 1DF0 0300 0315 0062;0061 05AE 1DF0 0300 0315 0062; # (a◌ᷰ◌̕◌̀◌֮b; a◌֮◌ᷰ◌̀◌̕b; a◌֮◌ᷰ◌̀◌̕b; a◌֮◌ᷰ◌̀◌̕b; a◌֮◌ᷰ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER U WITH LIGHT CENTRALIZATION STROKE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DF1 0062;00E0 05AE 1DF1 0315 0062;0061 05AE 0300 1DF1 0315 0062;00E0 05AE 1DF1 0315 0062;0061 05AE 0300 1DF1 0315 0062; # (a◌̕◌̀◌֮◌ᷱb; à◌֮◌ᷱ◌̕b; a◌֮◌̀◌ᷱ◌̕b; à◌֮◌ᷱ◌̕b; a◌֮◌̀◌ᷱ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER W, LATIN SMALL LETTER B
+0061 1DF1 0315 0300 05AE 0062;0061 05AE 1DF1 0300 0315 0062;0061 05AE 1DF1 0300 0315 0062;0061 05AE 1DF1 0300 0315 0062;0061 05AE 1DF1 0300 0315 0062; # (a◌ᷱ◌̕◌̀◌֮b; a◌֮◌ᷱ◌̀◌̕b; a◌֮◌ᷱ◌̀◌̕b; a◌֮◌ᷱ◌̀◌̕b; a◌֮◌ᷱ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER W, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DF2 0062;00E0 05AE 1DF2 0315 0062;0061 05AE 0300 1DF2 0315 0062;00E0 05AE 1DF2 0315 0062;0061 05AE 0300 1DF2 0315 0062; # (a◌̕◌̀◌֮◌ᷲb; à◌֮◌ᷲ◌̕b; a◌֮◌̀◌ᷲ◌̕b; à◌֮◌ᷲ◌̕b; a◌֮◌̀◌ᷲ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER A WITH DIAERESIS, LATIN SMALL LETTER B
+0061 1DF2 0315 0300 05AE 0062;0061 05AE 1DF2 0300 0315 0062;0061 05AE 1DF2 0300 0315 0062;0061 05AE 1DF2 0300 0315 0062;0061 05AE 1DF2 0300 0315 0062; # (a◌ᷲ◌̕◌̀◌֮b; a◌֮◌ᷲ◌̀◌̕b; a◌֮◌ᷲ◌̀◌̕b; a◌֮◌ᷲ◌̀◌̕b; a◌֮◌ᷲ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER A WITH DIAERESIS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DF3 0062;00E0 05AE 1DF3 0315 0062;0061 05AE 0300 1DF3 0315 0062;00E0 05AE 1DF3 0315 0062;0061 05AE 0300 1DF3 0315 0062; # (a◌̕◌̀◌֮◌ᷳb; à◌֮◌ᷳ◌̕b; a◌֮◌̀◌ᷳ◌̕b; à◌֮◌ᷳ◌̕b; a◌֮◌̀◌ᷳ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER O WITH DIAERESIS, LATIN SMALL LETTER B
+0061 1DF3 0315 0300 05AE 0062;0061 05AE 1DF3 0300 0315 0062;0061 05AE 1DF3 0300 0315 0062;0061 05AE 1DF3 0300 0315 0062;0061 05AE 1DF3 0300 0315 0062; # (a◌ᷳ◌̕◌̀◌֮b; a◌֮◌ᷳ◌̀◌̕b; a◌֮◌ᷳ◌̀◌̕b; a◌֮◌ᷳ◌̀◌̕b; a◌֮◌ᷳ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER O WITH DIAERESIS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DF4 0062;00E0 05AE 1DF4 0315 0062;0061 05AE 0300 1DF4 0315 0062;00E0 05AE 1DF4 0315 0062;0061 05AE 0300 1DF4 0315 0062; # (a◌̕◌̀◌֮◌ᷴb; à◌֮◌ᷴ◌̕b; a◌֮◌̀◌ᷴ◌̕b; à◌֮◌ᷴ◌̕b; a◌֮◌̀◌ᷴ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LATIN SMALL LETTER U WITH DIAERESIS, LATIN SMALL LETTER B
+0061 1DF4 0315 0300 05AE 0062;0061 05AE 1DF4 0300 0315 0062;0061 05AE 1DF4 0300 0315 0062;0061 05AE 1DF4 0300 0315 0062;0061 05AE 1DF4 0300 0315 0062; # (a◌ᷴ◌̕◌̀◌֮b; a◌֮◌ᷴ◌̀◌̕b; a◌֮◌ᷴ◌̀◌̕b; a◌֮◌ᷴ◌̀◌̕b; a◌֮◌ᷴ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LATIN SMALL LETTER U WITH DIAERESIS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DF5 0062;00E0 05AE 1DF5 0315 0062;0061 05AE 0300 1DF5 0315 0062;00E0 05AE 1DF5 0315 0062;0061 05AE 0300 1DF5 0315 0062; # (a◌̕◌̀◌֮◌᷵b; à◌֮◌᷵◌̕b; a◌֮◌̀◌᷵◌̕b; à◌֮◌᷵◌̕b; a◌֮◌̀◌᷵◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING UP TACK ABOVE, LATIN SMALL LETTER B
+0061 1DF5 0315 0300 05AE 0062;0061 05AE 1DF5 0300 0315 0062;0061 05AE 1DF5 0300 0315 0062;0061 05AE 1DF5 0300 0315 0062;0061 05AE 1DF5 0300 0315 0062; # (a◌᷵◌̕◌̀◌֮b; a◌֮◌᷵◌̀◌̕b; a◌֮◌᷵◌̀◌̕b; a◌֮◌᷵◌̀◌̕b; a◌֮◌᷵◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING UP TACK ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 035C 0315 0300 1DF6 0062;00E0 0315 1DF6 035C 0062;0061 0300 0315 1DF6 035C 0062;00E0 0315 1DF6 035C 0062;0061 0300 0315 1DF6 035C 0062; # (a◌͜◌̕◌̀◌᷶b; à◌̕◌᷶◌͜b; a◌̀◌̕◌᷶◌͜b; à◌̕◌᷶◌͜b; a◌̀◌̕◌᷶◌͜b; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, COMBINING KAVYKA ABOVE RIGHT, LATIN SMALL LETTER B
+0061 1DF6 035C 0315 0300 0062;00E0 1DF6 0315 035C 0062;0061 0300 1DF6 0315 035C 0062;00E0 1DF6 0315 035C 0062;0061 0300 1DF6 0315 035C 0062; # (a◌᷶◌͜◌̕◌̀b; à◌᷶◌̕◌͜b; a◌̀◌᷶◌̕◌͜b; à◌᷶◌̕◌͜b; a◌̀◌᷶◌̕◌͜b; ) LATIN SMALL LETTER A, COMBINING KAVYKA ABOVE RIGHT, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, LATIN SMALL LETTER B
+0061 0300 05AE 1D16D 1DF7 0062;00E0 1D16D 05AE 1DF7 0062;0061 1D16D 05AE 1DF7 0300 0062;00E0 1D16D 05AE 1DF7 0062;0061 1D16D 05AE 1DF7 0300 0062; # (a◌̀◌֮ð…­â—Œá··b; àð…­â—ŒÖ®â—Œá··b; að…­â—ŒÖ®â—Œá··â—ŒÌ€b; àð…­â—ŒÖ®â—Œá··b; að…­â—ŒÖ®â—Œá··â—ŒÌ€b; ) LATIN SMALL LETTER A, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, COMBINING KAVYKA ABOVE LEFT, LATIN SMALL LETTER B
+0061 1DF7 0300 05AE 1D16D 0062;00E0 1D16D 1DF7 05AE 0062;0061 1D16D 1DF7 05AE 0300 0062;00E0 1D16D 1DF7 05AE 0062;0061 1D16D 1DF7 05AE 0300 0062; # (a◌᷷◌̀◌֮ð…­b; àð…­â—Œá··â—ŒÖ®b; að…­â—Œá··â—ŒÖ®â—ŒÌ€b; àð…­â—Œá··â—ŒÖ®b; að…­â—Œá··â—ŒÖ®â—ŒÌ€b; ) LATIN SMALL LETTER A, COMBINING KAVYKA ABOVE LEFT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, LATIN SMALL LETTER B
+0061 0300 05AE 1D16D 1DF8 0062;00E0 1D16D 05AE 1DF8 0062;0061 1D16D 05AE 1DF8 0300 0062;00E0 1D16D 05AE 1DF8 0062;0061 1D16D 05AE 1DF8 0300 0062; # (a◌̀◌֮ð…­â—Œá·¸b; àð…­â—ŒÖ®â—Œá·¸b; að…­â—ŒÖ®â—Œá·¸â—ŒÌ€b; àð…­â—ŒÖ®â—Œá·¸b; að…­â—ŒÖ®â—Œá·¸â—ŒÌ€b; ) LATIN SMALL LETTER A, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, COMBINING DOT ABOVE LEFT, LATIN SMALL LETTER B
+0061 1DF8 0300 05AE 1D16D 0062;00E0 1D16D 1DF8 05AE 0062;0061 1D16D 1DF8 05AE 0300 0062;00E0 1D16D 1DF8 05AE 0062;0061 1D16D 1DF8 05AE 0300 0062; # (a◌᷸◌̀◌֮ð…­b; àð…­â—Œá·¸â—ŒÖ®b; að…­â—Œá·¸â—ŒÖ®â—ŒÌ€b; àð…­â—Œá·¸â—ŒÖ®b; að…­â—Œá·¸â—ŒÖ®â—ŒÌ€b; ) LATIN SMALL LETTER A, COMBINING DOT ABOVE LEFT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1DF9 0062;0061 1DFA 0316 1DF9 059A 0062;0061 1DFA 0316 1DF9 059A 0062;0061 1DFA 0316 1DF9 059A 0062;0061 1DFA 0316 1DF9 059A 0062; # (a◌֚◌̖◌᷺◌᷹b; a◌᷺◌̖◌᷹◌֚b; a◌᷺◌̖◌᷹◌֚b; a◌᷺◌̖◌᷹◌֚b; a◌᷺◌̖◌᷹◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING WIDE INVERTED BRIDGE BELOW, LATIN SMALL LETTER B
+0061 1DF9 059A 0316 1DFA 0062;0061 1DFA 1DF9 0316 059A 0062;0061 1DFA 1DF9 0316 059A 0062;0061 1DFA 1DF9 0316 059A 0062;0061 1DFA 1DF9 0316 059A 0062; # (a◌᷹◌֚◌̖◌᷺b; a◌᷺◌᷹◌̖◌֚b; a◌᷺◌᷹◌̖◌֚b; a◌᷺◌᷹◌̖◌֚b; a◌᷺◌᷹◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING WIDE INVERTED BRIDGE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0316 1DFA 031B 1DFA 0062;0061 031B 1DFA 1DFA 0316 0062;0061 031B 1DFA 1DFA 0316 0062;0061 031B 1DFA 1DFA 0316 0062;0061 031B 1DFA 1DFA 0316 0062; # (a◌̖◌᷺◌̛◌᷺b; a◌̛◌᷺◌᷺◌̖b; a◌̛◌᷺◌᷺◌̖b; a◌̛◌᷺◌᷺◌̖b; a◌̛◌᷺◌᷺◌̖b; ) LATIN SMALL LETTER A, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 1DFA 0316 1DFA 031B 0062;0061 031B 1DFA 1DFA 0316 0062;0061 031B 1DFA 1DFA 0316 0062;0061 031B 1DFA 1DFA 0316 0062;0061 031B 1DFA 1DFA 0316 0062; # (a◌᷺◌̖◌᷺◌̛b; a◌̛◌᷺◌᷺◌̖b; a◌̛◌᷺◌᷺◌̖b; a◌̛◌᷺◌᷺◌̖b; a◌̛◌᷺◌᷺◌̖b; ) LATIN SMALL LETTER A, COMBINING DOT BELOW LEFT, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING HORN, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DFB 0062;00E0 05AE 1DFB 0315 0062;0061 05AE 0300 1DFB 0315 0062;00E0 05AE 1DFB 0315 0062;0061 05AE 0300 1DFB 0315 0062; # (a◌̕◌̀◌֮◌᷻b; à◌֮◌᷻◌̕b; a◌֮◌̀◌᷻◌̕b; à◌֮◌᷻◌̕b; a◌֮◌̀◌᷻◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DELETION MARK, LATIN SMALL LETTER B
+0061 1DFB 0315 0300 05AE 0062;0061 05AE 1DFB 0300 0315 0062;0061 05AE 1DFB 0300 0315 0062;0061 05AE 1DFB 0300 0315 0062;0061 05AE 1DFB 0300 0315 0062; # (a◌᷻◌̕◌̀◌֮b; a◌֮◌᷻◌̀◌̕b; a◌֮◌᷻◌̀◌̕b; a◌֮◌᷻◌̀◌̕b; a◌֮◌᷻◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DELETION MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 035D 035C 0315 1DFC 0062;0061 0315 035C 1DFC 035D 0062;0061 0315 035C 1DFC 035D 0062;0061 0315 035C 1DFC 035D 0062;0061 0315 035C 1DFC 035D 0062; # (aâ—ŒÍ◌͜◌̕◌᷼b; a◌̕◌͜◌᷼◌Íb; a◌̕◌͜◌᷼◌Íb; a◌̕◌͜◌᷼◌Íb; a◌̕◌͜◌᷼◌Íb; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING DOUBLE INVERTED BREVE BELOW, LATIN SMALL LETTER B
+0061 1DFC 035D 035C 0315 0062;0061 0315 1DFC 035C 035D 0062;0061 0315 1DFC 035C 035D 0062;0061 0315 1DFC 035C 035D 0062;0061 0315 1DFC 035C 035D 0062; # (a◌᷼◌Í◌͜◌̕b; a◌̕◌᷼◌͜◌Íb; a◌̕◌᷼◌͜◌Íb; a◌̕◌᷼◌͜◌Íb; a◌̕◌᷼◌͜◌Íb; ) LATIN SMALL LETTER A, COMBINING DOUBLE INVERTED BREVE BELOW, COMBINING DOUBLE BREVE, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1DFD 0062;0061 1DFA 0316 1DFD 059A 0062;0061 1DFA 0316 1DFD 059A 0062;0061 1DFA 0316 1DFD 059A 0062;0061 1DFA 0316 1DFD 059A 0062; # (a◌֚◌̖◌᷺◌᷽b; a◌᷺◌̖◌᷽◌֚b; a◌᷺◌̖◌᷽◌֚b; a◌᷺◌̖◌᷽◌֚b; a◌᷺◌̖◌᷽◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING ALMOST EQUAL TO BELOW, LATIN SMALL LETTER B
+0061 1DFD 059A 0316 1DFA 0062;0061 1DFA 1DFD 0316 059A 0062;0061 1DFA 1DFD 0316 059A 0062;0061 1DFA 1DFD 0316 059A 0062;0061 1DFA 1DFD 0316 059A 0062; # (a◌᷽◌֚◌̖◌᷺b; a◌᷺◌᷽◌̖◌֚b; a◌᷺◌᷽◌̖◌֚b; a◌᷺◌᷽◌̖◌֚b; a◌᷺◌᷽◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING ALMOST EQUAL TO BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1DFE 0062;00E0 05AE 1DFE 0315 0062;0061 05AE 0300 1DFE 0315 0062;00E0 05AE 1DFE 0315 0062;0061 05AE 0300 1DFE 0315 0062; # (a◌̕◌̀◌֮◌᷾b; à◌֮◌᷾◌̕b; a◌֮◌̀◌᷾◌̕b; à◌֮◌᷾◌̕b; a◌֮◌̀◌᷾◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LEFT ARROWHEAD ABOVE, LATIN SMALL LETTER B
+0061 1DFE 0315 0300 05AE 0062;0061 05AE 1DFE 0300 0315 0062;0061 05AE 1DFE 0300 0315 0062;0061 05AE 1DFE 0300 0315 0062;0061 05AE 1DFE 0300 0315 0062; # (a◌᷾◌̕◌̀◌֮b; a◌֮◌᷾◌̀◌̕b; a◌֮◌᷾◌̀◌̕b; a◌֮◌᷾◌̀◌̕b; a◌֮◌᷾◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LEFT ARROWHEAD ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1DFF 0062;0061 1DFA 0316 1DFF 059A 0062;0061 1DFA 0316 1DFF 059A 0062;0061 1DFA 0316 1DFF 059A 0062;0061 1DFA 0316 1DFF 059A 0062; # (a◌֚◌̖◌᷺◌᷿b; a◌᷺◌̖◌᷿◌֚b; a◌᷺◌̖◌᷿◌֚b; a◌᷺◌̖◌᷿◌֚b; a◌᷺◌̖◌᷿◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW, LATIN SMALL LETTER B
+0061 1DFF 059A 0316 1DFA 0062;0061 1DFA 1DFF 0316 059A 0062;0061 1DFA 1DFF 0316 059A 0062;0061 1DFA 1DFF 0316 059A 0062;0061 1DFA 1DFF 0316 059A 0062; # (a◌᷿◌֚◌̖◌᷺b; a◌᷺◌᷿◌̖◌֚b; a◌᷺◌᷿◌̖◌֚b; a◌᷺◌᷿◌̖◌֚b; a◌᷺◌᷿◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20D0 0062;00E0 05AE 20D0 0315 0062;0061 05AE 0300 20D0 0315 0062;00E0 05AE 20D0 0315 0062;0061 05AE 0300 20D0 0315 0062; # (a◌̕◌̀◌֮◌âƒb; à◌֮◌âƒâ—ŒÌ•b; a◌֮◌̀◌âƒâ—ŒÌ•b; à◌֮◌âƒâ—ŒÌ•b; a◌֮◌̀◌âƒâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LEFT HARPOON ABOVE, LATIN SMALL LETTER B
+0061 20D0 0315 0300 05AE 0062;0061 05AE 20D0 0300 0315 0062;0061 05AE 20D0 0300 0315 0062;0061 05AE 20D0 0300 0315 0062;0061 05AE 20D0 0300 0315 0062; # (aâ—Œâƒâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌âƒâ—ŒÌ€â—ŒÌ•b; a◌֮◌âƒâ—ŒÌ€â—ŒÌ•b; a◌֮◌âƒâ—ŒÌ€â—ŒÌ•b; a◌֮◌âƒâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING LEFT HARPOON ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20D1 0062;00E0 05AE 20D1 0315 0062;0061 05AE 0300 20D1 0315 0062;00E0 05AE 20D1 0315 0062;0061 05AE 0300 20D1 0315 0062; # (a◌̕◌̀◌֮◌⃑b; à◌֮◌⃑◌̕b; a◌֮◌̀◌⃑◌̕b; à◌֮◌⃑◌̕b; a◌֮◌̀◌⃑◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING RIGHT HARPOON ABOVE, LATIN SMALL LETTER B
+0061 20D1 0315 0300 05AE 0062;0061 05AE 20D1 0300 0315 0062;0061 05AE 20D1 0300 0315 0062;0061 05AE 20D1 0300 0315 0062;0061 05AE 20D1 0300 0315 0062; # (a◌⃑◌̕◌̀◌֮b; a◌֮◌⃑◌̀◌̕b; a◌֮◌⃑◌̀◌̕b; a◌֮◌⃑◌̀◌̕b; a◌֮◌⃑◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING RIGHT HARPOON ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 16FF0 0334 20D2 0062;0061 0334 20D2 16FF0 0062;0061 0334 20D2 16FF0 0062;0061 0334 20D2 16FF0 0062;0061 0334 20D2 16FF0 0062; # (a𖿰◌̴◌⃒b; a◌̴◌⃒𖿰b; a◌̴◌⃒𖿰b; a◌̴◌⃒𖿰b; a◌̴◌⃒𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING LONG VERTICAL LINE OVERLAY, LATIN SMALL LETTER B
+0061 20D2 16FF0 0334 0062;0061 20D2 0334 16FF0 0062;0061 20D2 0334 16FF0 0062;0061 20D2 0334 16FF0 0062;0061 20D2 0334 16FF0 0062; # (a◌⃒𖿰◌̴b; a◌⃒◌̴𖿰b; a◌⃒◌̴𖿰b; a◌⃒◌̴𖿰b; a◌⃒◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING LONG VERTICAL LINE OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 20D3 0062;0061 0334 20D3 16FF0 0062;0061 0334 20D3 16FF0 0062;0061 0334 20D3 16FF0 0062;0061 0334 20D3 16FF0 0062; # (a𖿰◌̴◌⃓b; a◌̴◌⃓𖿰b; a◌̴◌⃓𖿰b; a◌̴◌⃓𖿰b; a◌̴◌⃓𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING SHORT VERTICAL LINE OVERLAY, LATIN SMALL LETTER B
+0061 20D3 16FF0 0334 0062;0061 20D3 0334 16FF0 0062;0061 20D3 0334 16FF0 0062;0061 20D3 0334 16FF0 0062;0061 20D3 0334 16FF0 0062; # (a◌⃓𖿰◌̴b; a◌⃓◌̴𖿰b; a◌⃓◌̴𖿰b; a◌⃓◌̴𖿰b; a◌⃓◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING SHORT VERTICAL LINE OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20D4 0062;00E0 05AE 20D4 0315 0062;0061 05AE 0300 20D4 0315 0062;00E0 05AE 20D4 0315 0062;0061 05AE 0300 20D4 0315 0062; # (a◌̕◌̀◌֮◌⃔b; à◌֮◌⃔◌̕b; a◌֮◌̀◌⃔◌̕b; à◌֮◌⃔◌̕b; a◌֮◌̀◌⃔◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING ANTICLOCKWISE ARROW ABOVE, LATIN SMALL LETTER B
+0061 20D4 0315 0300 05AE 0062;0061 05AE 20D4 0300 0315 0062;0061 05AE 20D4 0300 0315 0062;0061 05AE 20D4 0300 0315 0062;0061 05AE 20D4 0300 0315 0062; # (a◌⃔◌̕◌̀◌֮b; a◌֮◌⃔◌̀◌̕b; a◌֮◌⃔◌̀◌̕b; a◌֮◌⃔◌̀◌̕b; a◌֮◌⃔◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING ANTICLOCKWISE ARROW ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20D5 0062;00E0 05AE 20D5 0315 0062;0061 05AE 0300 20D5 0315 0062;00E0 05AE 20D5 0315 0062;0061 05AE 0300 20D5 0315 0062; # (a◌̕◌̀◌֮◌⃕b; à◌֮◌⃕◌̕b; a◌֮◌̀◌⃕◌̕b; à◌֮◌⃕◌̕b; a◌֮◌̀◌⃕◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CLOCKWISE ARROW ABOVE, LATIN SMALL LETTER B
+0061 20D5 0315 0300 05AE 0062;0061 05AE 20D5 0300 0315 0062;0061 05AE 20D5 0300 0315 0062;0061 05AE 20D5 0300 0315 0062;0061 05AE 20D5 0300 0315 0062; # (a◌⃕◌̕◌̀◌֮b; a◌֮◌⃕◌̀◌̕b; a◌֮◌⃕◌̀◌̕b; a◌֮◌⃕◌̀◌̕b; a◌֮◌⃕◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CLOCKWISE ARROW ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20D6 0062;00E0 05AE 20D6 0315 0062;0061 05AE 0300 20D6 0315 0062;00E0 05AE 20D6 0315 0062;0061 05AE 0300 20D6 0315 0062; # (a◌̕◌̀◌֮◌⃖b; à◌֮◌⃖◌̕b; a◌֮◌̀◌⃖◌̕b; à◌֮◌⃖◌̕b; a◌֮◌̀◌⃖◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LEFT ARROW ABOVE, LATIN SMALL LETTER B
+0061 20D6 0315 0300 05AE 0062;0061 05AE 20D6 0300 0315 0062;0061 05AE 20D6 0300 0315 0062;0061 05AE 20D6 0300 0315 0062;0061 05AE 20D6 0300 0315 0062; # (a◌⃖◌̕◌̀◌֮b; a◌֮◌⃖◌̀◌̕b; a◌֮◌⃖◌̀◌̕b; a◌֮◌⃖◌̀◌̕b; a◌֮◌⃖◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LEFT ARROW ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20D7 0062;00E0 05AE 20D7 0315 0062;0061 05AE 0300 20D7 0315 0062;00E0 05AE 20D7 0315 0062;0061 05AE 0300 20D7 0315 0062; # (a◌̕◌̀◌֮◌⃗b; à◌֮◌⃗◌̕b; a◌֮◌̀◌⃗◌̕b; à◌֮◌⃗◌̕b; a◌֮◌̀◌⃗◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING RIGHT ARROW ABOVE, LATIN SMALL LETTER B
+0061 20D7 0315 0300 05AE 0062;0061 05AE 20D7 0300 0315 0062;0061 05AE 20D7 0300 0315 0062;0061 05AE 20D7 0300 0315 0062;0061 05AE 20D7 0300 0315 0062; # (a◌⃗◌̕◌̀◌֮b; a◌֮◌⃗◌̀◌̕b; a◌֮◌⃗◌̀◌̕b; a◌֮◌⃗◌̀◌̕b; a◌֮◌⃗◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING RIGHT ARROW ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 16FF0 0334 20D8 0062;0061 0334 20D8 16FF0 0062;0061 0334 20D8 16FF0 0062;0061 0334 20D8 16FF0 0062;0061 0334 20D8 16FF0 0062; # (a𖿰◌̴◌⃘b; a◌̴◌⃘𖿰b; a◌̴◌⃘𖿰b; a◌̴◌⃘𖿰b; a◌̴◌⃘𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING RING OVERLAY, LATIN SMALL LETTER B
+0061 20D8 16FF0 0334 0062;0061 20D8 0334 16FF0 0062;0061 20D8 0334 16FF0 0062;0061 20D8 0334 16FF0 0062;0061 20D8 0334 16FF0 0062; # (a◌⃘𖿰◌̴b; a◌⃘◌̴𖿰b; a◌⃘◌̴𖿰b; a◌⃘◌̴𖿰b; a◌⃘◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING RING OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 20D9 0062;0061 0334 20D9 16FF0 0062;0061 0334 20D9 16FF0 0062;0061 0334 20D9 16FF0 0062;0061 0334 20D9 16FF0 0062; # (a𖿰◌̴◌⃙b; a◌̴◌⃙𖿰b; a◌̴◌⃙𖿰b; a◌̴◌⃙𖿰b; a◌̴◌⃙𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING CLOCKWISE RING OVERLAY, LATIN SMALL LETTER B
+0061 20D9 16FF0 0334 0062;0061 20D9 0334 16FF0 0062;0061 20D9 0334 16FF0 0062;0061 20D9 0334 16FF0 0062;0061 20D9 0334 16FF0 0062; # (a◌⃙𖿰◌̴b; a◌⃙◌̴𖿰b; a◌⃙◌̴𖿰b; a◌⃙◌̴𖿰b; a◌⃙◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING CLOCKWISE RING OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 20DA 0062;0061 0334 20DA 16FF0 0062;0061 0334 20DA 16FF0 0062;0061 0334 20DA 16FF0 0062;0061 0334 20DA 16FF0 0062; # (a𖿰◌̴◌⃚b; a◌̴◌⃚𖿰b; a◌̴◌⃚𖿰b; a◌̴◌⃚𖿰b; a◌̴◌⃚𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING ANTICLOCKWISE RING OVERLAY, LATIN SMALL LETTER B
+0061 20DA 16FF0 0334 0062;0061 20DA 0334 16FF0 0062;0061 20DA 0334 16FF0 0062;0061 20DA 0334 16FF0 0062;0061 20DA 0334 16FF0 0062; # (a◌⃚𖿰◌̴b; a◌⃚◌̴𖿰b; a◌⃚◌̴𖿰b; a◌⃚◌̴𖿰b; a◌⃚◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING ANTICLOCKWISE RING OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20DB 0062;00E0 05AE 20DB 0315 0062;0061 05AE 0300 20DB 0315 0062;00E0 05AE 20DB 0315 0062;0061 05AE 0300 20DB 0315 0062; # (a◌̕◌̀◌֮◌⃛b; à◌֮◌⃛◌̕b; a◌֮◌̀◌⃛◌̕b; à◌֮◌⃛◌̕b; a◌֮◌̀◌⃛◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING THREE DOTS ABOVE, LATIN SMALL LETTER B
+0061 20DB 0315 0300 05AE 0062;0061 05AE 20DB 0300 0315 0062;0061 05AE 20DB 0300 0315 0062;0061 05AE 20DB 0300 0315 0062;0061 05AE 20DB 0300 0315 0062; # (a◌⃛◌̕◌̀◌֮b; a◌֮◌⃛◌̀◌̕b; a◌֮◌⃛◌̀◌̕b; a◌֮◌⃛◌̀◌̕b; a◌֮◌⃛◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING THREE DOTS ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20DC 0062;00E0 05AE 20DC 0315 0062;0061 05AE 0300 20DC 0315 0062;00E0 05AE 20DC 0315 0062;0061 05AE 0300 20DC 0315 0062; # (a◌̕◌̀◌֮◌⃜b; à◌֮◌⃜◌̕b; a◌֮◌̀◌⃜◌̕b; à◌֮◌⃜◌̕b; a◌֮◌̀◌⃜◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING FOUR DOTS ABOVE, LATIN SMALL LETTER B
+0061 20DC 0315 0300 05AE 0062;0061 05AE 20DC 0300 0315 0062;0061 05AE 20DC 0300 0315 0062;0061 05AE 20DC 0300 0315 0062;0061 05AE 20DC 0300 0315 0062; # (a◌⃜◌̕◌̀◌֮b; a◌֮◌⃜◌̀◌̕b; a◌֮◌⃜◌̀◌̕b; a◌֮◌⃜◌̀◌̕b; a◌֮◌⃜◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING FOUR DOTS ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20E1 0062;00E0 05AE 20E1 0315 0062;0061 05AE 0300 20E1 0315 0062;00E0 05AE 20E1 0315 0062;0061 05AE 0300 20E1 0315 0062; # (a◌̕◌̀◌֮◌⃡b; à◌֮◌⃡◌̕b; a◌֮◌̀◌⃡◌̕b; à◌֮◌⃡◌̕b; a◌֮◌̀◌⃡◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LEFT RIGHT ARROW ABOVE, LATIN SMALL LETTER B
+0061 20E1 0315 0300 05AE 0062;0061 05AE 20E1 0300 0315 0062;0061 05AE 20E1 0300 0315 0062;0061 05AE 20E1 0300 0315 0062;0061 05AE 20E1 0300 0315 0062; # (a◌⃡◌̕◌̀◌֮b; a◌֮◌⃡◌̀◌̕b; a◌֮◌⃡◌̀◌̕b; a◌֮◌⃡◌̀◌̕b; a◌֮◌⃡◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LEFT RIGHT ARROW ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 16FF0 0334 20E5 0062;0061 0334 20E5 16FF0 0062;0061 0334 20E5 16FF0 0062;0061 0334 20E5 16FF0 0062;0061 0334 20E5 16FF0 0062; # (a𖿰◌̴◌⃥b; a◌̴◌⃥𖿰b; a◌̴◌⃥𖿰b; a◌̴◌⃥𖿰b; a◌̴◌⃥𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING REVERSE SOLIDUS OVERLAY, LATIN SMALL LETTER B
+0061 20E5 16FF0 0334 0062;0061 20E5 0334 16FF0 0062;0061 20E5 0334 16FF0 0062;0061 20E5 0334 16FF0 0062;0061 20E5 0334 16FF0 0062; # (a◌⃥𖿰◌̴b; a◌⃥◌̴𖿰b; a◌⃥◌̴𖿰b; a◌⃥◌̴𖿰b; a◌⃥◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING REVERSE SOLIDUS OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 20E6 0062;0061 0334 20E6 16FF0 0062;0061 0334 20E6 16FF0 0062;0061 0334 20E6 16FF0 0062;0061 0334 20E6 16FF0 0062; # (a𖿰◌̴◌⃦b; a◌̴◌⃦𖿰b; a◌̴◌⃦𖿰b; a◌̴◌⃦𖿰b; a◌̴◌⃦𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING DOUBLE VERTICAL STROKE OVERLAY, LATIN SMALL LETTER B
+0061 20E6 16FF0 0334 0062;0061 20E6 0334 16FF0 0062;0061 20E6 0334 16FF0 0062;0061 20E6 0334 16FF0 0062;0061 20E6 0334 16FF0 0062; # (a◌⃦𖿰◌̴b; a◌⃦◌̴𖿰b; a◌⃦◌̴𖿰b; a◌⃦◌̴𖿰b; a◌⃦◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING DOUBLE VERTICAL STROKE OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20E7 0062;00E0 05AE 20E7 0315 0062;0061 05AE 0300 20E7 0315 0062;00E0 05AE 20E7 0315 0062;0061 05AE 0300 20E7 0315 0062; # (a◌̕◌̀◌֮◌⃧b; à◌֮◌⃧◌̕b; a◌֮◌̀◌⃧◌̕b; à◌֮◌⃧◌̕b; a◌֮◌̀◌⃧◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING ANNUITY SYMBOL, LATIN SMALL LETTER B
+0061 20E7 0315 0300 05AE 0062;0061 05AE 20E7 0300 0315 0062;0061 05AE 20E7 0300 0315 0062;0061 05AE 20E7 0300 0315 0062;0061 05AE 20E7 0300 0315 0062; # (a◌⃧◌̕◌̀◌֮b; a◌֮◌⃧◌̀◌̕b; a◌֮◌⃧◌̀◌̕b; a◌֮◌⃧◌̀◌̕b; a◌֮◌⃧◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING ANNUITY SYMBOL, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 20E8 0062;0061 1DFA 0316 20E8 059A 0062;0061 1DFA 0316 20E8 059A 0062;0061 1DFA 0316 20E8 059A 0062;0061 1DFA 0316 20E8 059A 0062; # (a◌֚◌̖◌᷺◌⃨b; a◌᷺◌̖◌⃨◌֚b; a◌᷺◌̖◌⃨◌֚b; a◌᷺◌̖◌⃨◌֚b; a◌᷺◌̖◌⃨◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING TRIPLE UNDERDOT, LATIN SMALL LETTER B
+0061 20E8 059A 0316 1DFA 0062;0061 1DFA 20E8 0316 059A 0062;0061 1DFA 20E8 0316 059A 0062;0061 1DFA 20E8 0316 059A 0062;0061 1DFA 20E8 0316 059A 0062; # (a◌⃨◌֚◌̖◌᷺b; a◌᷺◌⃨◌̖◌֚b; a◌᷺◌⃨◌̖◌֚b; a◌᷺◌⃨◌̖◌֚b; a◌᷺◌⃨◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING TRIPLE UNDERDOT, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20E9 0062;00E0 05AE 20E9 0315 0062;0061 05AE 0300 20E9 0315 0062;00E0 05AE 20E9 0315 0062;0061 05AE 0300 20E9 0315 0062; # (a◌̕◌̀◌֮◌⃩b; à◌֮◌⃩◌̕b; a◌֮◌̀◌⃩◌̕b; à◌֮◌⃩◌̕b; a◌֮◌̀◌⃩◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING WIDE BRIDGE ABOVE, LATIN SMALL LETTER B
+0061 20E9 0315 0300 05AE 0062;0061 05AE 20E9 0300 0315 0062;0061 05AE 20E9 0300 0315 0062;0061 05AE 20E9 0300 0315 0062;0061 05AE 20E9 0300 0315 0062; # (a◌⃩◌̕◌̀◌֮b; a◌֮◌⃩◌̀◌̕b; a◌֮◌⃩◌̀◌̕b; a◌֮◌⃩◌̀◌̕b; a◌֮◌⃩◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING WIDE BRIDGE ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 16FF0 0334 20EA 0062;0061 0334 20EA 16FF0 0062;0061 0334 20EA 16FF0 0062;0061 0334 20EA 16FF0 0062;0061 0334 20EA 16FF0 0062; # (a𖿰◌̴◌⃪b; a◌̴◌⃪𖿰b; a◌̴◌⃪𖿰b; a◌̴◌⃪𖿰b; a◌̴◌⃪𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING LEFTWARDS ARROW OVERLAY, LATIN SMALL LETTER B
+0061 20EA 16FF0 0334 0062;0061 20EA 0334 16FF0 0062;0061 20EA 0334 16FF0 0062;0061 20EA 0334 16FF0 0062;0061 20EA 0334 16FF0 0062; # (a◌⃪𖿰◌̴b; a◌⃪◌̴𖿰b; a◌⃪◌̴𖿰b; a◌⃪◌̴𖿰b; a◌⃪◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING LEFTWARDS ARROW OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 20EB 0062;0061 0334 20EB 16FF0 0062;0061 0334 20EB 16FF0 0062;0061 0334 20EB 16FF0 0062;0061 0334 20EB 16FF0 0062; # (a𖿰◌̴◌⃫b; a◌̴◌⃫𖿰b; a◌̴◌⃫𖿰b; a◌̴◌⃫𖿰b; a◌̴◌⃫𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, COMBINING LONG DOUBLE SOLIDUS OVERLAY, LATIN SMALL LETTER B
+0061 20EB 16FF0 0334 0062;0061 20EB 0334 16FF0 0062;0061 20EB 0334 16FF0 0062;0061 20EB 0334 16FF0 0062;0061 20EB 0334 16FF0 0062; # (a◌⃫𖿰◌̴b; a◌⃫◌̴𖿰b; a◌⃫◌̴𖿰b; a◌⃫◌̴𖿰b; a◌⃫◌̴𖿰b; ) LATIN SMALL LETTER A, COMBINING LONG DOUBLE SOLIDUS OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 20EC 0062;0061 1DFA 0316 20EC 059A 0062;0061 1DFA 0316 20EC 059A 0062;0061 1DFA 0316 20EC 059A 0062;0061 1DFA 0316 20EC 059A 0062; # (a◌֚◌̖◌᷺◌⃬b; a◌᷺◌̖◌⃬◌֚b; a◌᷺◌̖◌⃬◌֚b; a◌᷺◌̖◌⃬◌֚b; a◌᷺◌̖◌⃬◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING RIGHTWARDS HARPOON WITH BARB DOWNWARDS, LATIN SMALL LETTER B
+0061 20EC 059A 0316 1DFA 0062;0061 1DFA 20EC 0316 059A 0062;0061 1DFA 20EC 0316 059A 0062;0061 1DFA 20EC 0316 059A 0062;0061 1DFA 20EC 0316 059A 0062; # (a◌⃬◌֚◌̖◌᷺b; a◌᷺◌⃬◌̖◌֚b; a◌᷺◌⃬◌̖◌֚b; a◌᷺◌⃬◌̖◌֚b; a◌᷺◌⃬◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING RIGHTWARDS HARPOON WITH BARB DOWNWARDS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 20ED 0062;0061 1DFA 0316 20ED 059A 0062;0061 1DFA 0316 20ED 059A 0062;0061 1DFA 0316 20ED 059A 0062;0061 1DFA 0316 20ED 059A 0062; # (a◌֚◌̖◌᷺◌⃭b; a◌᷺◌̖◌⃭◌֚b; a◌᷺◌̖◌⃭◌֚b; a◌᷺◌̖◌⃭◌֚b; a◌᷺◌̖◌⃭◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LEFTWARDS HARPOON WITH BARB DOWNWARDS, LATIN SMALL LETTER B
+0061 20ED 059A 0316 1DFA 0062;0061 1DFA 20ED 0316 059A 0062;0061 1DFA 20ED 0316 059A 0062;0061 1DFA 20ED 0316 059A 0062;0061 1DFA 20ED 0316 059A 0062; # (a◌⃭◌֚◌̖◌᷺b; a◌᷺◌⃭◌̖◌֚b; a◌᷺◌⃭◌̖◌֚b; a◌᷺◌⃭◌̖◌֚b; a◌᷺◌⃭◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LEFTWARDS HARPOON WITH BARB DOWNWARDS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 20EE 0062;0061 1DFA 0316 20EE 059A 0062;0061 1DFA 0316 20EE 059A 0062;0061 1DFA 0316 20EE 059A 0062;0061 1DFA 0316 20EE 059A 0062; # (a◌֚◌̖◌᷺◌⃮b; a◌᷺◌̖◌⃮◌֚b; a◌᷺◌̖◌⃮◌֚b; a◌᷺◌̖◌⃮◌֚b; a◌᷺◌̖◌⃮◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LEFT ARROW BELOW, LATIN SMALL LETTER B
+0061 20EE 059A 0316 1DFA 0062;0061 1DFA 20EE 0316 059A 0062;0061 1DFA 20EE 0316 059A 0062;0061 1DFA 20EE 0316 059A 0062;0061 1DFA 20EE 0316 059A 0062; # (a◌⃮◌֚◌̖◌᷺b; a◌᷺◌⃮◌̖◌֚b; a◌᷺◌⃮◌̖◌֚b; a◌᷺◌⃮◌̖◌֚b; a◌᷺◌⃮◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LEFT ARROW BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 20EF 0062;0061 1DFA 0316 20EF 059A 0062;0061 1DFA 0316 20EF 059A 0062;0061 1DFA 0316 20EF 059A 0062;0061 1DFA 0316 20EF 059A 0062; # (a◌֚◌̖◌᷺◌⃯b; a◌᷺◌̖◌⃯◌֚b; a◌᷺◌̖◌⃯◌֚b; a◌᷺◌̖◌⃯◌֚b; a◌᷺◌̖◌⃯◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING RIGHT ARROW BELOW, LATIN SMALL LETTER B
+0061 20EF 059A 0316 1DFA 0062;0061 1DFA 20EF 0316 059A 0062;0061 1DFA 20EF 0316 059A 0062;0061 1DFA 20EF 0316 059A 0062;0061 1DFA 20EF 0316 059A 0062; # (a◌⃯◌֚◌̖◌᷺b; a◌᷺◌⃯◌̖◌֚b; a◌᷺◌⃯◌̖◌֚b; a◌᷺◌⃯◌̖◌֚b; a◌᷺◌⃯◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING RIGHT ARROW BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 20F0 0062;00E0 05AE 20F0 0315 0062;0061 05AE 0300 20F0 0315 0062;00E0 05AE 20F0 0315 0062;0061 05AE 0300 20F0 0315 0062; # (a◌̕◌̀◌֮◌⃰b; à◌֮◌⃰◌̕b; a◌֮◌̀◌⃰◌̕b; à◌֮◌⃰◌̕b; a◌֮◌̀◌⃰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING ASTERISK ABOVE, LATIN SMALL LETTER B
+0061 20F0 0315 0300 05AE 0062;0061 05AE 20F0 0300 0315 0062;0061 05AE 20F0 0300 0315 0062;0061 05AE 20F0 0300 0315 0062;0061 05AE 20F0 0300 0315 0062; # (a◌⃰◌̕◌̀◌֮b; a◌֮◌⃰◌̀◌̕b; a◌֮◌⃰◌̀◌̕b; a◌֮◌⃰◌̀◌̕b; a◌֮◌⃰◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING ASTERISK ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2CEF 0062;00E0 05AE 2CEF 0315 0062;0061 05AE 0300 2CEF 0315 0062;00E0 05AE 2CEF 0315 0062;0061 05AE 0300 2CEF 0315 0062; # (a◌̕◌̀◌֮◌⳯b; à◌֮◌⳯◌̕b; a◌֮◌̀◌⳯◌̕b; à◌֮◌⳯◌̕b; a◌֮◌̀◌⳯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COPTIC COMBINING NI ABOVE, LATIN SMALL LETTER B
+0061 2CEF 0315 0300 05AE 0062;0061 05AE 2CEF 0300 0315 0062;0061 05AE 2CEF 0300 0315 0062;0061 05AE 2CEF 0300 0315 0062;0061 05AE 2CEF 0300 0315 0062; # (a◌⳯◌̕◌̀◌֮b; a◌֮◌⳯◌̀◌̕b; a◌֮◌⳯◌̀◌̕b; a◌֮◌⳯◌̀◌̕b; a◌֮◌⳯◌̀◌̕b; ) LATIN SMALL LETTER A, COPTIC COMBINING NI ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2CF0 0062;00E0 05AE 2CF0 0315 0062;0061 05AE 0300 2CF0 0315 0062;00E0 05AE 2CF0 0315 0062;0061 05AE 0300 2CF0 0315 0062; # (a◌̕◌̀◌֮◌⳰b; à◌֮◌⳰◌̕b; a◌֮◌̀◌⳰◌̕b; à◌֮◌⳰◌̕b; a◌֮◌̀◌⳰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COPTIC COMBINING SPIRITUS ASPER, LATIN SMALL LETTER B
+0061 2CF0 0315 0300 05AE 0062;0061 05AE 2CF0 0300 0315 0062;0061 05AE 2CF0 0300 0315 0062;0061 05AE 2CF0 0300 0315 0062;0061 05AE 2CF0 0300 0315 0062; # (a◌⳰◌̕◌̀◌֮b; a◌֮◌⳰◌̀◌̕b; a◌֮◌⳰◌̀◌̕b; a◌֮◌⳰◌̀◌̕b; a◌֮◌⳰◌̀◌̕b; ) LATIN SMALL LETTER A, COPTIC COMBINING SPIRITUS ASPER, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2CF1 0062;00E0 05AE 2CF1 0315 0062;0061 05AE 0300 2CF1 0315 0062;00E0 05AE 2CF1 0315 0062;0061 05AE 0300 2CF1 0315 0062; # (a◌̕◌̀◌֮◌⳱b; à◌֮◌⳱◌̕b; a◌֮◌̀◌⳱◌̕b; à◌֮◌⳱◌̕b; a◌֮◌̀◌⳱◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COPTIC COMBINING SPIRITUS LENIS, LATIN SMALL LETTER B
+0061 2CF1 0315 0300 05AE 0062;0061 05AE 2CF1 0300 0315 0062;0061 05AE 2CF1 0300 0315 0062;0061 05AE 2CF1 0300 0315 0062;0061 05AE 2CF1 0300 0315 0062; # (a◌⳱◌̕◌̀◌֮b; a◌֮◌⳱◌̀◌̕b; a◌֮◌⳱◌̀◌̕b; a◌֮◌⳱◌̀◌̕b; a◌֮◌⳱◌̀◌̕b; ) LATIN SMALL LETTER A, COPTIC COMBINING SPIRITUS LENIS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05B0 094D 3099 2D7F 0062;0061 3099 094D 2D7F 05B0 0062;0061 3099 094D 2D7F 05B0 0062;0061 3099 094D 2D7F 05B0 0062;0061 3099 094D 2D7F 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œâµ¿b; a◌゙◌à¥â—Œâµ¿â—ŒÖ°b; a◌゙◌à¥â—Œâµ¿â—ŒÖ°b; a◌゙◌à¥â—Œâµ¿â—ŒÖ°b; a◌゙◌à¥â—Œâµ¿â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, TIFINAGH CONSONANT JOINER, LATIN SMALL LETTER B
+0061 2D7F 05B0 094D 3099 0062;0061 3099 2D7F 094D 05B0 0062;0061 3099 2D7F 094D 05B0 0062;0061 3099 2D7F 094D 05B0 0062;0061 3099 2D7F 094D 05B0 0062; # (a◌⵿◌ְ◌à¥â—Œã‚™b; a◌゙◌⵿◌à¥â—ŒÖ°b; a◌゙◌⵿◌à¥â—ŒÖ°b; a◌゙◌⵿◌à¥â—ŒÖ°b; a◌゙◌⵿◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, TIFINAGH CONSONANT JOINER, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DE0 0062;00E0 05AE 2DE0 0315 0062;0061 05AE 0300 2DE0 0315 0062;00E0 05AE 2DE0 0315 0062;0061 05AE 0300 2DE0 0315 0062; # (a◌̕◌̀◌֮◌ⷠb; à◌֮◌ⷠ◌̕b; a◌֮◌̀◌ⷠ◌̕b; à◌֮◌ⷠ◌̕b; a◌֮◌̀◌ⷠ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER BE, LATIN SMALL LETTER B
+0061 2DE0 0315 0300 05AE 0062;0061 05AE 2DE0 0300 0315 0062;0061 05AE 2DE0 0300 0315 0062;0061 05AE 2DE0 0300 0315 0062;0061 05AE 2DE0 0300 0315 0062; # (a◌ⷠ◌̕◌̀◌֮b; a◌֮◌ⷠ◌̀◌̕b; a◌֮◌ⷠ◌̀◌̕b; a◌֮◌ⷠ◌̀◌̕b; a◌֮◌ⷠ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER BE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DE1 0062;00E0 05AE 2DE1 0315 0062;0061 05AE 0300 2DE1 0315 0062;00E0 05AE 2DE1 0315 0062;0061 05AE 0300 2DE1 0315 0062; # (a◌̕◌̀◌֮◌ⷡb; à◌֮◌ⷡ◌̕b; a◌֮◌̀◌ⷡ◌̕b; à◌֮◌ⷡ◌̕b; a◌֮◌̀◌ⷡ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER VE, LATIN SMALL LETTER B
+0061 2DE1 0315 0300 05AE 0062;0061 05AE 2DE1 0300 0315 0062;0061 05AE 2DE1 0300 0315 0062;0061 05AE 2DE1 0300 0315 0062;0061 05AE 2DE1 0300 0315 0062; # (a◌ⷡ◌̕◌̀◌֮b; a◌֮◌ⷡ◌̀◌̕b; a◌֮◌ⷡ◌̀◌̕b; a◌֮◌ⷡ◌̀◌̕b; a◌֮◌ⷡ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER VE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DE2 0062;00E0 05AE 2DE2 0315 0062;0061 05AE 0300 2DE2 0315 0062;00E0 05AE 2DE2 0315 0062;0061 05AE 0300 2DE2 0315 0062; # (a◌̕◌̀◌֮◌ⷢb; à◌֮◌ⷢ◌̕b; a◌֮◌̀◌ⷢ◌̕b; à◌֮◌ⷢ◌̕b; a◌֮◌̀◌ⷢ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER GHE, LATIN SMALL LETTER B
+0061 2DE2 0315 0300 05AE 0062;0061 05AE 2DE2 0300 0315 0062;0061 05AE 2DE2 0300 0315 0062;0061 05AE 2DE2 0300 0315 0062;0061 05AE 2DE2 0300 0315 0062; # (a◌ⷢ◌̕◌̀◌֮b; a◌֮◌ⷢ◌̀◌̕b; a◌֮◌ⷢ◌̀◌̕b; a◌֮◌ⷢ◌̀◌̕b; a◌֮◌ⷢ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER GHE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DE3 0062;00E0 05AE 2DE3 0315 0062;0061 05AE 0300 2DE3 0315 0062;00E0 05AE 2DE3 0315 0062;0061 05AE 0300 2DE3 0315 0062; # (a◌̕◌̀◌֮◌ⷣb; à◌֮◌ⷣ◌̕b; a◌֮◌̀◌ⷣ◌̕b; à◌֮◌ⷣ◌̕b; a◌֮◌̀◌ⷣ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER DE, LATIN SMALL LETTER B
+0061 2DE3 0315 0300 05AE 0062;0061 05AE 2DE3 0300 0315 0062;0061 05AE 2DE3 0300 0315 0062;0061 05AE 2DE3 0300 0315 0062;0061 05AE 2DE3 0300 0315 0062; # (a◌ⷣ◌̕◌̀◌֮b; a◌֮◌ⷣ◌̀◌̕b; a◌֮◌ⷣ◌̀◌̕b; a◌֮◌ⷣ◌̀◌̕b; a◌֮◌ⷣ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER DE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DE4 0062;00E0 05AE 2DE4 0315 0062;0061 05AE 0300 2DE4 0315 0062;00E0 05AE 2DE4 0315 0062;0061 05AE 0300 2DE4 0315 0062; # (a◌̕◌̀◌֮◌ⷤb; à◌֮◌ⷤ◌̕b; a◌֮◌̀◌ⷤ◌̕b; à◌֮◌ⷤ◌̕b; a◌֮◌̀◌ⷤ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER ZHE, LATIN SMALL LETTER B
+0061 2DE4 0315 0300 05AE 0062;0061 05AE 2DE4 0300 0315 0062;0061 05AE 2DE4 0300 0315 0062;0061 05AE 2DE4 0300 0315 0062;0061 05AE 2DE4 0300 0315 0062; # (a◌ⷤ◌̕◌̀◌֮b; a◌֮◌ⷤ◌̀◌̕b; a◌֮◌ⷤ◌̀◌̕b; a◌֮◌ⷤ◌̀◌̕b; a◌֮◌ⷤ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER ZHE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DE5 0062;00E0 05AE 2DE5 0315 0062;0061 05AE 0300 2DE5 0315 0062;00E0 05AE 2DE5 0315 0062;0061 05AE 0300 2DE5 0315 0062; # (a◌̕◌̀◌֮◌ⷥb; à◌֮◌ⷥ◌̕b; a◌֮◌̀◌ⷥ◌̕b; à◌֮◌ⷥ◌̕b; a◌֮◌̀◌ⷥ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER ZE, LATIN SMALL LETTER B
+0061 2DE5 0315 0300 05AE 0062;0061 05AE 2DE5 0300 0315 0062;0061 05AE 2DE5 0300 0315 0062;0061 05AE 2DE5 0300 0315 0062;0061 05AE 2DE5 0300 0315 0062; # (a◌ⷥ◌̕◌̀◌֮b; a◌֮◌ⷥ◌̀◌̕b; a◌֮◌ⷥ◌̀◌̕b; a◌֮◌ⷥ◌̀◌̕b; a◌֮◌ⷥ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER ZE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DE6 0062;00E0 05AE 2DE6 0315 0062;0061 05AE 0300 2DE6 0315 0062;00E0 05AE 2DE6 0315 0062;0061 05AE 0300 2DE6 0315 0062; # (a◌̕◌̀◌֮◌ⷦb; à◌֮◌ⷦ◌̕b; a◌֮◌̀◌ⷦ◌̕b; à◌֮◌ⷦ◌̕b; a◌֮◌̀◌ⷦ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER KA, LATIN SMALL LETTER B
+0061 2DE6 0315 0300 05AE 0062;0061 05AE 2DE6 0300 0315 0062;0061 05AE 2DE6 0300 0315 0062;0061 05AE 2DE6 0300 0315 0062;0061 05AE 2DE6 0300 0315 0062; # (a◌ⷦ◌̕◌̀◌֮b; a◌֮◌ⷦ◌̀◌̕b; a◌֮◌ⷦ◌̀◌̕b; a◌֮◌ⷦ◌̀◌̕b; a◌֮◌ⷦ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER KA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DE7 0062;00E0 05AE 2DE7 0315 0062;0061 05AE 0300 2DE7 0315 0062;00E0 05AE 2DE7 0315 0062;0061 05AE 0300 2DE7 0315 0062; # (a◌̕◌̀◌֮◌ⷧb; à◌֮◌ⷧ◌̕b; a◌֮◌̀◌ⷧ◌̕b; à◌֮◌ⷧ◌̕b; a◌֮◌̀◌ⷧ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER EL, LATIN SMALL LETTER B
+0061 2DE7 0315 0300 05AE 0062;0061 05AE 2DE7 0300 0315 0062;0061 05AE 2DE7 0300 0315 0062;0061 05AE 2DE7 0300 0315 0062;0061 05AE 2DE7 0300 0315 0062; # (a◌ⷧ◌̕◌̀◌֮b; a◌֮◌ⷧ◌̀◌̕b; a◌֮◌ⷧ◌̀◌̕b; a◌֮◌ⷧ◌̀◌̕b; a◌֮◌ⷧ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER EL, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DE8 0062;00E0 05AE 2DE8 0315 0062;0061 05AE 0300 2DE8 0315 0062;00E0 05AE 2DE8 0315 0062;0061 05AE 0300 2DE8 0315 0062; # (a◌̕◌̀◌֮◌ⷨb; à◌֮◌ⷨ◌̕b; a◌֮◌̀◌ⷨ◌̕b; à◌֮◌ⷨ◌̕b; a◌֮◌̀◌ⷨ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER EM, LATIN SMALL LETTER B
+0061 2DE8 0315 0300 05AE 0062;0061 05AE 2DE8 0300 0315 0062;0061 05AE 2DE8 0300 0315 0062;0061 05AE 2DE8 0300 0315 0062;0061 05AE 2DE8 0300 0315 0062; # (a◌ⷨ◌̕◌̀◌֮b; a◌֮◌ⷨ◌̀◌̕b; a◌֮◌ⷨ◌̀◌̕b; a◌֮◌ⷨ◌̀◌̕b; a◌֮◌ⷨ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER EM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DE9 0062;00E0 05AE 2DE9 0315 0062;0061 05AE 0300 2DE9 0315 0062;00E0 05AE 2DE9 0315 0062;0061 05AE 0300 2DE9 0315 0062; # (a◌̕◌̀◌֮◌ⷩb; à◌֮◌ⷩ◌̕b; a◌֮◌̀◌ⷩ◌̕b; à◌֮◌ⷩ◌̕b; a◌֮◌̀◌ⷩ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER EN, LATIN SMALL LETTER B
+0061 2DE9 0315 0300 05AE 0062;0061 05AE 2DE9 0300 0315 0062;0061 05AE 2DE9 0300 0315 0062;0061 05AE 2DE9 0300 0315 0062;0061 05AE 2DE9 0300 0315 0062; # (a◌ⷩ◌̕◌̀◌֮b; a◌֮◌ⷩ◌̀◌̕b; a◌֮◌ⷩ◌̀◌̕b; a◌֮◌ⷩ◌̀◌̕b; a◌֮◌ⷩ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER EN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DEA 0062;00E0 05AE 2DEA 0315 0062;0061 05AE 0300 2DEA 0315 0062;00E0 05AE 2DEA 0315 0062;0061 05AE 0300 2DEA 0315 0062; # (a◌̕◌̀◌֮◌ⷪb; à◌֮◌ⷪ◌̕b; a◌֮◌̀◌ⷪ◌̕b; à◌֮◌ⷪ◌̕b; a◌֮◌̀◌ⷪ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER O, LATIN SMALL LETTER B
+0061 2DEA 0315 0300 05AE 0062;0061 05AE 2DEA 0300 0315 0062;0061 05AE 2DEA 0300 0315 0062;0061 05AE 2DEA 0300 0315 0062;0061 05AE 2DEA 0300 0315 0062; # (a◌ⷪ◌̕◌̀◌֮b; a◌֮◌ⷪ◌̀◌̕b; a◌֮◌ⷪ◌̀◌̕b; a◌֮◌ⷪ◌̀◌̕b; a◌֮◌ⷪ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER O, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DEB 0062;00E0 05AE 2DEB 0315 0062;0061 05AE 0300 2DEB 0315 0062;00E0 05AE 2DEB 0315 0062;0061 05AE 0300 2DEB 0315 0062; # (a◌̕◌̀◌֮◌ⷫb; à◌֮◌ⷫ◌̕b; a◌֮◌̀◌ⷫ◌̕b; à◌֮◌ⷫ◌̕b; a◌֮◌̀◌ⷫ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER PE, LATIN SMALL LETTER B
+0061 2DEB 0315 0300 05AE 0062;0061 05AE 2DEB 0300 0315 0062;0061 05AE 2DEB 0300 0315 0062;0061 05AE 2DEB 0300 0315 0062;0061 05AE 2DEB 0300 0315 0062; # (a◌ⷫ◌̕◌̀◌֮b; a◌֮◌ⷫ◌̀◌̕b; a◌֮◌ⷫ◌̀◌̕b; a◌֮◌ⷫ◌̀◌̕b; a◌֮◌ⷫ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER PE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DEC 0062;00E0 05AE 2DEC 0315 0062;0061 05AE 0300 2DEC 0315 0062;00E0 05AE 2DEC 0315 0062;0061 05AE 0300 2DEC 0315 0062; # (a◌̕◌̀◌֮◌ⷬb; à◌֮◌ⷬ◌̕b; a◌֮◌̀◌ⷬ◌̕b; à◌֮◌ⷬ◌̕b; a◌֮◌̀◌ⷬ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER ER, LATIN SMALL LETTER B
+0061 2DEC 0315 0300 05AE 0062;0061 05AE 2DEC 0300 0315 0062;0061 05AE 2DEC 0300 0315 0062;0061 05AE 2DEC 0300 0315 0062;0061 05AE 2DEC 0300 0315 0062; # (a◌ⷬ◌̕◌̀◌֮b; a◌֮◌ⷬ◌̀◌̕b; a◌֮◌ⷬ◌̀◌̕b; a◌֮◌ⷬ◌̀◌̕b; a◌֮◌ⷬ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER ER, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DED 0062;00E0 05AE 2DED 0315 0062;0061 05AE 0300 2DED 0315 0062;00E0 05AE 2DED 0315 0062;0061 05AE 0300 2DED 0315 0062; # (a◌̕◌̀◌֮◌ⷭb; à◌֮◌ⷭ◌̕b; a◌֮◌̀◌ⷭ◌̕b; à◌֮◌ⷭ◌̕b; a◌֮◌̀◌ⷭ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER ES, LATIN SMALL LETTER B
+0061 2DED 0315 0300 05AE 0062;0061 05AE 2DED 0300 0315 0062;0061 05AE 2DED 0300 0315 0062;0061 05AE 2DED 0300 0315 0062;0061 05AE 2DED 0300 0315 0062; # (a◌ⷭ◌̕◌̀◌֮b; a◌֮◌ⷭ◌̀◌̕b; a◌֮◌ⷭ◌̀◌̕b; a◌֮◌ⷭ◌̀◌̕b; a◌֮◌ⷭ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER ES, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DEE 0062;00E0 05AE 2DEE 0315 0062;0061 05AE 0300 2DEE 0315 0062;00E0 05AE 2DEE 0315 0062;0061 05AE 0300 2DEE 0315 0062; # (a◌̕◌̀◌֮◌ⷮb; à◌֮◌ⷮ◌̕b; a◌֮◌̀◌ⷮ◌̕b; à◌֮◌ⷮ◌̕b; a◌֮◌̀◌ⷮ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER TE, LATIN SMALL LETTER B
+0061 2DEE 0315 0300 05AE 0062;0061 05AE 2DEE 0300 0315 0062;0061 05AE 2DEE 0300 0315 0062;0061 05AE 2DEE 0300 0315 0062;0061 05AE 2DEE 0300 0315 0062; # (a◌ⷮ◌̕◌̀◌֮b; a◌֮◌ⷮ◌̀◌̕b; a◌֮◌ⷮ◌̀◌̕b; a◌֮◌ⷮ◌̀◌̕b; a◌֮◌ⷮ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER TE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DEF 0062;00E0 05AE 2DEF 0315 0062;0061 05AE 0300 2DEF 0315 0062;00E0 05AE 2DEF 0315 0062;0061 05AE 0300 2DEF 0315 0062; # (a◌̕◌̀◌֮◌ⷯb; à◌֮◌ⷯ◌̕b; a◌֮◌̀◌ⷯ◌̕b; à◌֮◌ⷯ◌̕b; a◌֮◌̀◌ⷯ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER HA, LATIN SMALL LETTER B
+0061 2DEF 0315 0300 05AE 0062;0061 05AE 2DEF 0300 0315 0062;0061 05AE 2DEF 0300 0315 0062;0061 05AE 2DEF 0300 0315 0062;0061 05AE 2DEF 0300 0315 0062; # (a◌ⷯ◌̕◌̀◌֮b; a◌֮◌ⷯ◌̀◌̕b; a◌֮◌ⷯ◌̀◌̕b; a◌֮◌ⷯ◌̀◌̕b; a◌֮◌ⷯ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER HA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DF0 0062;00E0 05AE 2DF0 0315 0062;0061 05AE 0300 2DF0 0315 0062;00E0 05AE 2DF0 0315 0062;0061 05AE 0300 2DF0 0315 0062; # (a◌̕◌̀◌֮◌ⷰb; à◌֮◌ⷰ◌̕b; a◌֮◌̀◌ⷰ◌̕b; à◌֮◌ⷰ◌̕b; a◌֮◌̀◌ⷰ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER TSE, LATIN SMALL LETTER B
+0061 2DF0 0315 0300 05AE 0062;0061 05AE 2DF0 0300 0315 0062;0061 05AE 2DF0 0300 0315 0062;0061 05AE 2DF0 0300 0315 0062;0061 05AE 2DF0 0300 0315 0062; # (a◌ⷰ◌̕◌̀◌֮b; a◌֮◌ⷰ◌̀◌̕b; a◌֮◌ⷰ◌̀◌̕b; a◌֮◌ⷰ◌̀◌̕b; a◌֮◌ⷰ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER TSE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DF1 0062;00E0 05AE 2DF1 0315 0062;0061 05AE 0300 2DF1 0315 0062;00E0 05AE 2DF1 0315 0062;0061 05AE 0300 2DF1 0315 0062; # (a◌̕◌̀◌֮◌ⷱb; à◌֮◌ⷱ◌̕b; a◌֮◌̀◌ⷱ◌̕b; à◌֮◌ⷱ◌̕b; a◌֮◌̀◌ⷱ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER CHE, LATIN SMALL LETTER B
+0061 2DF1 0315 0300 05AE 0062;0061 05AE 2DF1 0300 0315 0062;0061 05AE 2DF1 0300 0315 0062;0061 05AE 2DF1 0300 0315 0062;0061 05AE 2DF1 0300 0315 0062; # (a◌ⷱ◌̕◌̀◌֮b; a◌֮◌ⷱ◌̀◌̕b; a◌֮◌ⷱ◌̀◌̕b; a◌֮◌ⷱ◌̀◌̕b; a◌֮◌ⷱ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER CHE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DF2 0062;00E0 05AE 2DF2 0315 0062;0061 05AE 0300 2DF2 0315 0062;00E0 05AE 2DF2 0315 0062;0061 05AE 0300 2DF2 0315 0062; # (a◌̕◌̀◌֮◌ⷲb; à◌֮◌ⷲ◌̕b; a◌֮◌̀◌ⷲ◌̕b; à◌֮◌ⷲ◌̕b; a◌֮◌̀◌ⷲ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER SHA, LATIN SMALL LETTER B
+0061 2DF2 0315 0300 05AE 0062;0061 05AE 2DF2 0300 0315 0062;0061 05AE 2DF2 0300 0315 0062;0061 05AE 2DF2 0300 0315 0062;0061 05AE 2DF2 0300 0315 0062; # (a◌ⷲ◌̕◌̀◌֮b; a◌֮◌ⷲ◌̀◌̕b; a◌֮◌ⷲ◌̀◌̕b; a◌֮◌ⷲ◌̀◌̕b; a◌֮◌ⷲ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER SHA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DF3 0062;00E0 05AE 2DF3 0315 0062;0061 05AE 0300 2DF3 0315 0062;00E0 05AE 2DF3 0315 0062;0061 05AE 0300 2DF3 0315 0062; # (a◌̕◌̀◌֮◌ⷳb; à◌֮◌ⷳ◌̕b; a◌֮◌̀◌ⷳ◌̕b; à◌֮◌ⷳ◌̕b; a◌֮◌̀◌ⷳ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER SHCHA, LATIN SMALL LETTER B
+0061 2DF3 0315 0300 05AE 0062;0061 05AE 2DF3 0300 0315 0062;0061 05AE 2DF3 0300 0315 0062;0061 05AE 2DF3 0300 0315 0062;0061 05AE 2DF3 0300 0315 0062; # (a◌ⷳ◌̕◌̀◌֮b; a◌֮◌ⷳ◌̀◌̕b; a◌֮◌ⷳ◌̀◌̕b; a◌֮◌ⷳ◌̀◌̕b; a◌֮◌ⷳ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER SHCHA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DF4 0062;00E0 05AE 2DF4 0315 0062;0061 05AE 0300 2DF4 0315 0062;00E0 05AE 2DF4 0315 0062;0061 05AE 0300 2DF4 0315 0062; # (a◌̕◌̀◌֮◌ⷴb; à◌֮◌ⷴ◌̕b; a◌֮◌̀◌ⷴ◌̕b; à◌֮◌ⷴ◌̕b; a◌֮◌̀◌ⷴ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER FITA, LATIN SMALL LETTER B
+0061 2DF4 0315 0300 05AE 0062;0061 05AE 2DF4 0300 0315 0062;0061 05AE 2DF4 0300 0315 0062;0061 05AE 2DF4 0300 0315 0062;0061 05AE 2DF4 0300 0315 0062; # (a◌ⷴ◌̕◌̀◌֮b; a◌֮◌ⷴ◌̀◌̕b; a◌֮◌ⷴ◌̀◌̕b; a◌֮◌ⷴ◌̀◌̕b; a◌֮◌ⷴ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER FITA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DF5 0062;00E0 05AE 2DF5 0315 0062;0061 05AE 0300 2DF5 0315 0062;00E0 05AE 2DF5 0315 0062;0061 05AE 0300 2DF5 0315 0062; # (a◌̕◌̀◌֮◌ⷵb; à◌֮◌ⷵ◌̕b; a◌֮◌̀◌ⷵ◌̕b; à◌֮◌ⷵ◌̕b; a◌֮◌̀◌ⷵ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER ES-TE, LATIN SMALL LETTER B
+0061 2DF5 0315 0300 05AE 0062;0061 05AE 2DF5 0300 0315 0062;0061 05AE 2DF5 0300 0315 0062;0061 05AE 2DF5 0300 0315 0062;0061 05AE 2DF5 0300 0315 0062; # (a◌ⷵ◌̕◌̀◌֮b; a◌֮◌ⷵ◌̀◌̕b; a◌֮◌ⷵ◌̀◌̕b; a◌֮◌ⷵ◌̀◌̕b; a◌֮◌ⷵ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER ES-TE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DF6 0062;00E0 05AE 2DF6 0315 0062;0061 05AE 0300 2DF6 0315 0062;00E0 05AE 2DF6 0315 0062;0061 05AE 0300 2DF6 0315 0062; # (a◌̕◌̀◌֮◌ⷶb; à◌֮◌ⷶ◌̕b; a◌֮◌̀◌ⷶ◌̕b; à◌֮◌ⷶ◌̕b; a◌֮◌̀◌ⷶ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER A, LATIN SMALL LETTER B
+0061 2DF6 0315 0300 05AE 0062;0061 05AE 2DF6 0300 0315 0062;0061 05AE 2DF6 0300 0315 0062;0061 05AE 2DF6 0300 0315 0062;0061 05AE 2DF6 0300 0315 0062; # (a◌ⷶ◌̕◌̀◌֮b; a◌֮◌ⷶ◌̀◌̕b; a◌֮◌ⷶ◌̀◌̕b; a◌֮◌ⷶ◌̀◌̕b; a◌֮◌ⷶ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DF7 0062;00E0 05AE 2DF7 0315 0062;0061 05AE 0300 2DF7 0315 0062;00E0 05AE 2DF7 0315 0062;0061 05AE 0300 2DF7 0315 0062; # (a◌̕◌̀◌֮◌ⷷb; à◌֮◌ⷷ◌̕b; a◌֮◌̀◌ⷷ◌̕b; à◌֮◌ⷷ◌̕b; a◌֮◌̀◌ⷷ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER IE, LATIN SMALL LETTER B
+0061 2DF7 0315 0300 05AE 0062;0061 05AE 2DF7 0300 0315 0062;0061 05AE 2DF7 0300 0315 0062;0061 05AE 2DF7 0300 0315 0062;0061 05AE 2DF7 0300 0315 0062; # (a◌ⷷ◌̕◌̀◌֮b; a◌֮◌ⷷ◌̀◌̕b; a◌֮◌ⷷ◌̀◌̕b; a◌֮◌ⷷ◌̀◌̕b; a◌֮◌ⷷ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER IE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DF8 0062;00E0 05AE 2DF8 0315 0062;0061 05AE 0300 2DF8 0315 0062;00E0 05AE 2DF8 0315 0062;0061 05AE 0300 2DF8 0315 0062; # (a◌̕◌̀◌֮◌ⷸb; à◌֮◌ⷸ◌̕b; a◌֮◌̀◌ⷸ◌̕b; à◌֮◌ⷸ◌̕b; a◌֮◌̀◌ⷸ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER DJERV, LATIN SMALL LETTER B
+0061 2DF8 0315 0300 05AE 0062;0061 05AE 2DF8 0300 0315 0062;0061 05AE 2DF8 0300 0315 0062;0061 05AE 2DF8 0300 0315 0062;0061 05AE 2DF8 0300 0315 0062; # (a◌ⷸ◌̕◌̀◌֮b; a◌֮◌ⷸ◌̀◌̕b; a◌֮◌ⷸ◌̀◌̕b; a◌֮◌ⷸ◌̀◌̕b; a◌֮◌ⷸ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER DJERV, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DF9 0062;00E0 05AE 2DF9 0315 0062;0061 05AE 0300 2DF9 0315 0062;00E0 05AE 2DF9 0315 0062;0061 05AE 0300 2DF9 0315 0062; # (a◌̕◌̀◌֮◌ⷹb; à◌֮◌ⷹ◌̕b; a◌֮◌̀◌ⷹ◌̕b; à◌֮◌ⷹ◌̕b; a◌֮◌̀◌ⷹ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER MONOGRAPH UK, LATIN SMALL LETTER B
+0061 2DF9 0315 0300 05AE 0062;0061 05AE 2DF9 0300 0315 0062;0061 05AE 2DF9 0300 0315 0062;0061 05AE 2DF9 0300 0315 0062;0061 05AE 2DF9 0300 0315 0062; # (a◌ⷹ◌̕◌̀◌֮b; a◌֮◌ⷹ◌̀◌̕b; a◌֮◌ⷹ◌̀◌̕b; a◌֮◌ⷹ◌̀◌̕b; a◌֮◌ⷹ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER MONOGRAPH UK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DFA 0062;00E0 05AE 2DFA 0315 0062;0061 05AE 0300 2DFA 0315 0062;00E0 05AE 2DFA 0315 0062;0061 05AE 0300 2DFA 0315 0062; # (a◌̕◌̀◌֮◌ⷺb; à◌֮◌ⷺ◌̕b; a◌֮◌̀◌ⷺ◌̕b; à◌֮◌ⷺ◌̕b; a◌֮◌̀◌ⷺ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER YAT, LATIN SMALL LETTER B
+0061 2DFA 0315 0300 05AE 0062;0061 05AE 2DFA 0300 0315 0062;0061 05AE 2DFA 0300 0315 0062;0061 05AE 2DFA 0300 0315 0062;0061 05AE 2DFA 0300 0315 0062; # (a◌ⷺ◌̕◌̀◌֮b; a◌֮◌ⷺ◌̀◌̕b; a◌֮◌ⷺ◌̀◌̕b; a◌֮◌ⷺ◌̀◌̕b; a◌֮◌ⷺ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER YAT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DFB 0062;00E0 05AE 2DFB 0315 0062;0061 05AE 0300 2DFB 0315 0062;00E0 05AE 2DFB 0315 0062;0061 05AE 0300 2DFB 0315 0062; # (a◌̕◌̀◌֮◌ⷻb; à◌֮◌ⷻ◌̕b; a◌֮◌̀◌ⷻ◌̕b; à◌֮◌ⷻ◌̕b; a◌֮◌̀◌ⷻ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER YU, LATIN SMALL LETTER B
+0061 2DFB 0315 0300 05AE 0062;0061 05AE 2DFB 0300 0315 0062;0061 05AE 2DFB 0300 0315 0062;0061 05AE 2DFB 0300 0315 0062;0061 05AE 2DFB 0300 0315 0062; # (a◌ⷻ◌̕◌̀◌֮b; a◌֮◌ⷻ◌̀◌̕b; a◌֮◌ⷻ◌̀◌̕b; a◌֮◌ⷻ◌̀◌̕b; a◌֮◌ⷻ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER YU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DFC 0062;00E0 05AE 2DFC 0315 0062;0061 05AE 0300 2DFC 0315 0062;00E0 05AE 2DFC 0315 0062;0061 05AE 0300 2DFC 0315 0062; # (a◌̕◌̀◌֮◌ⷼb; à◌֮◌ⷼ◌̕b; a◌֮◌̀◌ⷼ◌̕b; à◌֮◌ⷼ◌̕b; a◌֮◌̀◌ⷼ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER IOTIFIED A, LATIN SMALL LETTER B
+0061 2DFC 0315 0300 05AE 0062;0061 05AE 2DFC 0300 0315 0062;0061 05AE 2DFC 0300 0315 0062;0061 05AE 2DFC 0300 0315 0062;0061 05AE 2DFC 0300 0315 0062; # (a◌ⷼ◌̕◌̀◌֮b; a◌֮◌ⷼ◌̀◌̕b; a◌֮◌ⷼ◌̀◌̕b; a◌֮◌ⷼ◌̀◌̕b; a◌֮◌ⷼ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER IOTIFIED A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DFD 0062;00E0 05AE 2DFD 0315 0062;0061 05AE 0300 2DFD 0315 0062;00E0 05AE 2DFD 0315 0062;0061 05AE 0300 2DFD 0315 0062; # (a◌̕◌̀◌֮◌ⷽb; à◌֮◌ⷽ◌̕b; a◌֮◌̀◌ⷽ◌̕b; à◌֮◌ⷽ◌̕b; a◌֮◌̀◌ⷽ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER LITTLE YUS, LATIN SMALL LETTER B
+0061 2DFD 0315 0300 05AE 0062;0061 05AE 2DFD 0300 0315 0062;0061 05AE 2DFD 0300 0315 0062;0061 05AE 2DFD 0300 0315 0062;0061 05AE 2DFD 0300 0315 0062; # (a◌ⷽ◌̕◌̀◌֮b; a◌֮◌ⷽ◌̀◌̕b; a◌֮◌ⷽ◌̀◌̕b; a◌֮◌ⷽ◌̀◌̕b; a◌֮◌ⷽ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER LITTLE YUS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DFE 0062;00E0 05AE 2DFE 0315 0062;0061 05AE 0300 2DFE 0315 0062;00E0 05AE 2DFE 0315 0062;0061 05AE 0300 2DFE 0315 0062; # (a◌̕◌̀◌֮◌ⷾb; à◌֮◌ⷾ◌̕b; a◌֮◌̀◌ⷾ◌̕b; à◌֮◌ⷾ◌̕b; a◌֮◌̀◌ⷾ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER BIG YUS, LATIN SMALL LETTER B
+0061 2DFE 0315 0300 05AE 0062;0061 05AE 2DFE 0300 0315 0062;0061 05AE 2DFE 0300 0315 0062;0061 05AE 2DFE 0300 0315 0062;0061 05AE 2DFE 0300 0315 0062; # (a◌ⷾ◌̕◌̀◌֮b; a◌֮◌ⷾ◌̀◌̕b; a◌֮◌ⷾ◌̀◌̕b; a◌֮◌ⷾ◌̀◌̕b; a◌֮◌ⷾ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER BIG YUS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 2DFF 0062;00E0 05AE 2DFF 0315 0062;0061 05AE 0300 2DFF 0315 0062;00E0 05AE 2DFF 0315 0062;0061 05AE 0300 2DFF 0315 0062; # (a◌̕◌̀◌֮◌ⷿb; à◌֮◌ⷿ◌̕b; a◌֮◌̀◌ⷿ◌̕b; à◌֮◌ⷿ◌̕b; a◌֮◌̀◌ⷿ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER IOTIFIED BIG YUS, LATIN SMALL LETTER B
+0061 2DFF 0315 0300 05AE 0062;0061 05AE 2DFF 0300 0315 0062;0061 05AE 2DFF 0300 0315 0062;0061 05AE 2DFF 0300 0315 0062;0061 05AE 2DFF 0300 0315 0062; # (a◌ⷿ◌̕◌̀◌֮b; a◌֮◌ⷿ◌̀◌̕b; a◌֮◌ⷿ◌̀◌̕b; a◌֮◌ⷿ◌̀◌̕b; a◌֮◌ⷿ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER IOTIFIED BIG YUS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0316 1DFA 031B 302A 0062;0061 031B 1DFA 302A 0316 0062;0061 031B 1DFA 302A 0316 0062;0061 031B 1DFA 302A 0316 0062;0061 031B 1DFA 302A 0316 0062; # (a◌̖◌᷺◌̛◌〪b; a◌̛◌᷺◌〪◌̖b; a◌̛◌᷺◌〪◌̖b; a◌̛◌᷺◌〪◌̖b; a◌̛◌᷺◌〪◌̖b; ) LATIN SMALL LETTER A, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING HORN, IDEOGRAPHIC LEVEL TONE MARK, LATIN SMALL LETTER B
+0061 302A 0316 1DFA 031B 0062;0061 031B 302A 1DFA 0316 0062;0061 031B 302A 1DFA 0316 0062;0061 031B 302A 1DFA 0316 0062;0061 031B 302A 1DFA 0316 0062; # (a◌〪◌̖◌᷺◌̛b; a◌̛◌〪◌᷺◌̖b; a◌̛◌〪◌᷺◌̖b; a◌̛◌〪◌᷺◌̖b; a◌̛◌〪◌᷺◌̖b; ) LATIN SMALL LETTER A, IDEOGRAPHIC LEVEL TONE MARK, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING HORN, LATIN SMALL LETTER B
+0061 0300 05AE 1D16D 302B 0062;00E0 1D16D 05AE 302B 0062;0061 1D16D 05AE 302B 0300 0062;00E0 1D16D 05AE 302B 0062;0061 1D16D 05AE 302B 0300 0062; # (a◌̀◌֮ð…­â—Œã€«b; àð…­â—ŒÖ®â—Œã€«b; að…­â—ŒÖ®â—Œã€«â—ŒÌ€b; àð…­â—ŒÖ®â—Œã€«b; að…­â—ŒÖ®â—Œã€«â—ŒÌ€b; ) LATIN SMALL LETTER A, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, IDEOGRAPHIC RISING TONE MARK, LATIN SMALL LETTER B
+0061 302B 0300 05AE 1D16D 0062;00E0 1D16D 302B 05AE 0062;0061 1D16D 302B 05AE 0300 0062;00E0 1D16D 302B 05AE 0062;0061 1D16D 302B 05AE 0300 0062; # (a◌〫◌̀◌֮ð…­b; àð…­â—Œã€«â—ŒÖ®b; að…­â—Œã€«â—ŒÖ®â—ŒÌ€b; àð…­â—Œã€«â—ŒÖ®b; að…­â—Œã€«â—ŒÖ®â—ŒÌ€b; ) LATIN SMALL LETTER A, IDEOGRAPHIC RISING TONE MARK, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, LATIN SMALL LETTER B
+0061 035C 0315 0300 302C 0062;00E0 0315 302C 035C 0062;0061 0300 0315 302C 035C 0062;00E0 0315 302C 035C 0062;0061 0300 0315 302C 035C 0062; # (a◌͜◌̕◌̀◌〬b; à◌̕◌〬◌͜b; a◌̀◌̕◌〬◌͜b; à◌̕◌〬◌͜b; a◌̀◌̕◌〬◌͜b; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, IDEOGRAPHIC DEPARTING TONE MARK, LATIN SMALL LETTER B
+0061 302C 035C 0315 0300 0062;00E0 302C 0315 035C 0062;0061 0300 302C 0315 035C 0062;00E0 302C 0315 035C 0062;0061 0300 302C 0315 035C 0062; # (a◌〬◌͜◌̕◌̀b; à◌〬◌̕◌͜b; a◌̀◌〬◌̕◌͜b; à◌〬◌̕◌͜b; a◌̀◌〬◌̕◌͜b; ) LATIN SMALL LETTER A, IDEOGRAPHIC DEPARTING TONE MARK, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, LATIN SMALL LETTER B
+0061 302E 059A 0316 302D 0062;0061 0316 059A 302D 302E 0062;0061 0316 059A 302D 302E 0062;0061 0316 059A 302D 302E 0062;0061 0316 059A 302D 302E 0062; # (a〮◌֚◌̖◌〭b; a◌̖◌֚◌〭〮b; a◌̖◌֚◌〭〮b; a◌̖◌֚◌〭〮b; a◌̖◌֚◌〭〮b; ) LATIN SMALL LETTER A, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, IDEOGRAPHIC ENTERING TONE MARK, LATIN SMALL LETTER B
+0061 302D 302E 059A 0316 0062;0061 0316 302D 059A 302E 0062;0061 0316 302D 059A 302E 0062;0061 0316 302D 059A 302E 0062;0061 0316 302D 059A 302E 0062; # (a◌〭〮◌֚◌̖b; a◌̖◌〭◌֚〮b; a◌̖◌〭◌֚〮b; a◌̖◌〭◌֚〮b; a◌̖◌〭◌֚〮b; ) LATIN SMALL LETTER A, IDEOGRAPHIC ENTERING TONE MARK, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, LATIN SMALL LETTER B
+0061 1D16D 302E 059A 302E 0062;0061 059A 302E 302E 1D16D 0062;0061 059A 302E 302E 1D16D 0062;0061 059A 302E 302E 1D16D 0062;0061 059A 302E 302E 1D16D 0062; # (að…­ã€®â—ŒÖšã€®b; a◌֚〮〮ð…­b; a◌֚〮〮ð…­b; a◌֚〮〮ð…­b; a◌֚〮〮ð…­b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, HANGUL SINGLE DOT TONE MARK, LATIN SMALL LETTER B
+0061 302E 1D16D 302E 059A 0062;0061 059A 302E 302E 1D16D 0062;0061 059A 302E 302E 1D16D 0062;0061 059A 302E 302E 1D16D 0062;0061 059A 302E 302E 1D16D 0062; # (a〮ð…­ã€®â—ŒÖšb; a◌֚〮〮ð…­b; a◌֚〮〮ð…­b; a◌֚〮〮ð…­b; a◌֚〮〮ð…­b; ) LATIN SMALL LETTER A, HANGUL SINGLE DOT TONE MARK, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, LATIN SMALL LETTER B
+0061 1D16D 302E 059A 302F 0062;0061 059A 302E 302F 1D16D 0062;0061 059A 302E 302F 1D16D 0062;0061 059A 302E 302F 1D16D 0062;0061 059A 302E 302F 1D16D 0062; # (að…­ã€®â—ŒÖšã€¯b; a◌֚〮〯ð…­b; a◌֚〮〯ð…­b; a◌֚〮〯ð…­b; a◌֚〮〯ð…­b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, HANGUL DOUBLE DOT TONE MARK, LATIN SMALL LETTER B
+0061 302F 1D16D 302E 059A 0062;0061 059A 302F 302E 1D16D 0062;0061 059A 302F 302E 1D16D 0062;0061 059A 302F 302E 1D16D 0062;0061 059A 302F 302E 1D16D 0062; # (a〯ð…­ã€®â—ŒÖšb; a◌֚〯〮ð…­b; a◌֚〯〮ð…­b; a◌֚〯〮ð…­b; a◌֚〯〮ð…­b; ) LATIN SMALL LETTER A, HANGUL DOUBLE DOT TONE MARK, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, HANGUL SINGLE DOT TONE MARK, HEBREW ACCENT YETIV, LATIN SMALL LETTER B
+0061 094D 3099 093C 3099 0062;0061 093C 3099 3099 094D 0062;0061 093C 3099 3099 094D 0062;0061 093C 3099 3099 094D 0062;0061 093C 3099 3099 094D 0062; # (aâ—Œà¥â—Œã‚™â—Œà¤¼â—Œã‚™b; a◌़◌゙◌゙◌à¥b; a◌़◌゙◌゙◌à¥b; a◌़◌゙◌゙◌à¥b; a◌़◌゙◌゙◌à¥b; ) LATIN SMALL LETTER A, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 094D 3099 093C 0062;0061 093C 3099 3099 094D 0062;0061 093C 3099 3099 094D 0062;0061 093C 3099 3099 094D 0062;0061 093C 3099 3099 094D 0062; # (a◌゙◌à¥â—Œã‚™â—Œà¤¼b; a◌़◌゙◌゙◌à¥b; a◌़◌゙◌゙◌à¥b; a◌़◌゙◌゙◌à¥b; a◌़◌゙◌゙◌à¥b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, LATIN SMALL LETTER B
+0061 094D 3099 093C 309A 0062;0061 093C 3099 309A 094D 0062;0061 093C 3099 309A 094D 0062;0061 093C 3099 309A 094D 0062;0061 093C 3099 309A 094D 0062; # (aâ—Œà¥â—Œã‚™â—Œà¤¼â—Œã‚šb; a◌़◌゙◌゚◌à¥b; a◌़◌゙◌゚◌à¥b; a◌़◌゙◌゚◌à¥b; a◌़◌゙◌゚◌à¥b; ) LATIN SMALL LETTER A, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 309A 094D 3099 093C 0062;0061 093C 309A 3099 094D 0062;0061 093C 309A 3099 094D 0062;0061 093C 309A 3099 094D 0062;0061 093C 309A 3099 094D 0062; # (a◌゚◌à¥â—Œã‚™â—Œà¤¼b; a◌़◌゚◌゙◌à¥b; a◌़◌゚◌゙◌à¥b; a◌़◌゚◌゙◌à¥b; a◌़◌゚◌゙◌à¥b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, LATIN SMALL LETTER B
+0061 0315 0300 05AE A66F 0062;00E0 05AE A66F 0315 0062;0061 05AE 0300 A66F 0315 0062;00E0 05AE A66F 0315 0062;0061 05AE 0300 A66F 0315 0062; # (a◌̕◌̀◌֮◌꙯b; à◌֮◌꙯◌̕b; a◌֮◌̀◌꙯◌̕b; à◌֮◌꙯◌̕b; a◌֮◌̀◌꙯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC VZMET, LATIN SMALL LETTER B
+0061 A66F 0315 0300 05AE 0062;0061 05AE A66F 0300 0315 0062;0061 05AE A66F 0300 0315 0062;0061 05AE A66F 0300 0315 0062;0061 05AE A66F 0300 0315 0062; # (a◌꙯◌̕◌̀◌֮b; a◌֮◌꙯◌̀◌̕b; a◌֮◌꙯◌̀◌̕b; a◌֮◌꙯◌̀◌̕b; a◌֮◌꙯◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC VZMET, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A674 0062;00E0 05AE A674 0315 0062;0061 05AE 0300 A674 0315 0062;00E0 05AE A674 0315 0062;0061 05AE 0300 A674 0315 0062; # (a◌̕◌̀◌֮◌ꙴb; à◌֮◌ꙴ◌̕b; a◌֮◌̀◌ꙴ◌̕b; à◌֮◌ꙴ◌̕b; a◌֮◌̀◌ꙴ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER UKRAINIAN IE, LATIN SMALL LETTER B
+0061 A674 0315 0300 05AE 0062;0061 05AE A674 0300 0315 0062;0061 05AE A674 0300 0315 0062;0061 05AE A674 0300 0315 0062;0061 05AE A674 0300 0315 0062; # (a◌ꙴ◌̕◌̀◌֮b; a◌֮◌ꙴ◌̀◌̕b; a◌֮◌ꙴ◌̀◌̕b; a◌֮◌ꙴ◌̀◌̕b; a◌֮◌ꙴ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER UKRAINIAN IE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A675 0062;00E0 05AE A675 0315 0062;0061 05AE 0300 A675 0315 0062;00E0 05AE A675 0315 0062;0061 05AE 0300 A675 0315 0062; # (a◌̕◌̀◌֮◌ꙵb; à◌֮◌ꙵ◌̕b; a◌֮◌̀◌ꙵ◌̕b; à◌֮◌ꙵ◌̕b; a◌֮◌̀◌ꙵ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER I, LATIN SMALL LETTER B
+0061 A675 0315 0300 05AE 0062;0061 05AE A675 0300 0315 0062;0061 05AE A675 0300 0315 0062;0061 05AE A675 0300 0315 0062;0061 05AE A675 0300 0315 0062; # (a◌ꙵ◌̕◌̀◌֮b; a◌֮◌ꙵ◌̀◌̕b; a◌֮◌ꙵ◌̀◌̕b; a◌֮◌ꙵ◌̀◌̕b; a◌֮◌ꙵ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER I, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A676 0062;00E0 05AE A676 0315 0062;0061 05AE 0300 A676 0315 0062;00E0 05AE A676 0315 0062;0061 05AE 0300 A676 0315 0062; # (a◌̕◌̀◌֮◌ꙶb; à◌֮◌ꙶ◌̕b; a◌֮◌̀◌ꙶ◌̕b; à◌֮◌ꙶ◌̕b; a◌֮◌̀◌ꙶ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER YI, LATIN SMALL LETTER B
+0061 A676 0315 0300 05AE 0062;0061 05AE A676 0300 0315 0062;0061 05AE A676 0300 0315 0062;0061 05AE A676 0300 0315 0062;0061 05AE A676 0300 0315 0062; # (a◌ꙶ◌̕◌̀◌֮b; a◌֮◌ꙶ◌̀◌̕b; a◌֮◌ꙶ◌̀◌̕b; a◌֮◌ꙶ◌̀◌̕b; a◌֮◌ꙶ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER YI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A677 0062;00E0 05AE A677 0315 0062;0061 05AE 0300 A677 0315 0062;00E0 05AE A677 0315 0062;0061 05AE 0300 A677 0315 0062; # (a◌̕◌̀◌֮◌ꙷb; à◌֮◌ꙷ◌̕b; a◌֮◌̀◌ꙷ◌̕b; à◌֮◌ꙷ◌̕b; a◌֮◌̀◌ꙷ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER U, LATIN SMALL LETTER B
+0061 A677 0315 0300 05AE 0062;0061 05AE A677 0300 0315 0062;0061 05AE A677 0300 0315 0062;0061 05AE A677 0300 0315 0062;0061 05AE A677 0300 0315 0062; # (a◌ꙷ◌̕◌̀◌֮b; a◌֮◌ꙷ◌̀◌̕b; a◌֮◌ꙷ◌̀◌̕b; a◌֮◌ꙷ◌̀◌̕b; a◌֮◌ꙷ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER U, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A678 0062;00E0 05AE A678 0315 0062;0061 05AE 0300 A678 0315 0062;00E0 05AE A678 0315 0062;0061 05AE 0300 A678 0315 0062; # (a◌̕◌̀◌֮◌ꙸb; à◌֮◌ꙸ◌̕b; a◌֮◌̀◌ꙸ◌̕b; à◌֮◌ꙸ◌̕b; a◌֮◌̀◌ꙸ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER HARD SIGN, LATIN SMALL LETTER B
+0061 A678 0315 0300 05AE 0062;0061 05AE A678 0300 0315 0062;0061 05AE A678 0300 0315 0062;0061 05AE A678 0300 0315 0062;0061 05AE A678 0300 0315 0062; # (a◌ꙸ◌̕◌̀◌֮b; a◌֮◌ꙸ◌̀◌̕b; a◌֮◌ꙸ◌̀◌̕b; a◌֮◌ꙸ◌̀◌̕b; a◌֮◌ꙸ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER HARD SIGN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A679 0062;00E0 05AE A679 0315 0062;0061 05AE 0300 A679 0315 0062;00E0 05AE A679 0315 0062;0061 05AE 0300 A679 0315 0062; # (a◌̕◌̀◌֮◌ꙹb; à◌֮◌ꙹ◌̕b; a◌֮◌̀◌ꙹ◌̕b; à◌֮◌ꙹ◌̕b; a◌֮◌̀◌ꙹ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER YERU, LATIN SMALL LETTER B
+0061 A679 0315 0300 05AE 0062;0061 05AE A679 0300 0315 0062;0061 05AE A679 0300 0315 0062;0061 05AE A679 0300 0315 0062;0061 05AE A679 0300 0315 0062; # (a◌ꙹ◌̕◌̀◌֮b; a◌֮◌ꙹ◌̀◌̕b; a◌֮◌ꙹ◌̀◌̕b; a◌֮◌ꙹ◌̀◌̕b; a◌֮◌ꙹ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER YERU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A67A 0062;00E0 05AE A67A 0315 0062;0061 05AE 0300 A67A 0315 0062;00E0 05AE A67A 0315 0062;0061 05AE 0300 A67A 0315 0062; # (a◌̕◌̀◌֮◌ꙺb; à◌֮◌ꙺ◌̕b; a◌֮◌̀◌ꙺ◌̕b; à◌֮◌ꙺ◌̕b; a◌֮◌̀◌ꙺ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER SOFT SIGN, LATIN SMALL LETTER B
+0061 A67A 0315 0300 05AE 0062;0061 05AE A67A 0300 0315 0062;0061 05AE A67A 0300 0315 0062;0061 05AE A67A 0300 0315 0062;0061 05AE A67A 0300 0315 0062; # (a◌ꙺ◌̕◌̀◌֮b; a◌֮◌ꙺ◌̀◌̕b; a◌֮◌ꙺ◌̀◌̕b; a◌֮◌ꙺ◌̀◌̕b; a◌֮◌ꙺ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER SOFT SIGN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A67B 0062;00E0 05AE A67B 0315 0062;0061 05AE 0300 A67B 0315 0062;00E0 05AE A67B 0315 0062;0061 05AE 0300 A67B 0315 0062; # (a◌̕◌̀◌֮◌ꙻb; à◌֮◌ꙻ◌̕b; a◌֮◌̀◌ꙻ◌̕b; à◌֮◌ꙻ◌̕b; a◌֮◌̀◌ꙻ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER OMEGA, LATIN SMALL LETTER B
+0061 A67B 0315 0300 05AE 0062;0061 05AE A67B 0300 0315 0062;0061 05AE A67B 0300 0315 0062;0061 05AE A67B 0300 0315 0062;0061 05AE A67B 0300 0315 0062; # (a◌ꙻ◌̕◌̀◌֮b; a◌֮◌ꙻ◌̀◌̕b; a◌֮◌ꙻ◌̀◌̕b; a◌֮◌ꙻ◌̀◌̕b; a◌֮◌ꙻ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER OMEGA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A67C 0062;00E0 05AE A67C 0315 0062;0061 05AE 0300 A67C 0315 0062;00E0 05AE A67C 0315 0062;0061 05AE 0300 A67C 0315 0062; # (a◌̕◌̀◌֮◌꙼b; à◌֮◌꙼◌̕b; a◌֮◌̀◌꙼◌̕b; à◌֮◌꙼◌̕b; a◌֮◌̀◌꙼◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC KAVYKA, LATIN SMALL LETTER B
+0061 A67C 0315 0300 05AE 0062;0061 05AE A67C 0300 0315 0062;0061 05AE A67C 0300 0315 0062;0061 05AE A67C 0300 0315 0062;0061 05AE A67C 0300 0315 0062; # (a◌꙼◌̕◌̀◌֮b; a◌֮◌꙼◌̀◌̕b; a◌֮◌꙼◌̀◌̕b; a◌֮◌꙼◌̀◌̕b; a◌֮◌꙼◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC KAVYKA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A67D 0062;00E0 05AE A67D 0315 0062;0061 05AE 0300 A67D 0315 0062;00E0 05AE A67D 0315 0062;0061 05AE 0300 A67D 0315 0062; # (a◌̕◌̀◌֮◌꙽b; à◌֮◌꙽◌̕b; a◌֮◌̀◌꙽◌̕b; à◌֮◌꙽◌̕b; a◌֮◌̀◌꙽◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC PAYEROK, LATIN SMALL LETTER B
+0061 A67D 0315 0300 05AE 0062;0061 05AE A67D 0300 0315 0062;0061 05AE A67D 0300 0315 0062;0061 05AE A67D 0300 0315 0062;0061 05AE A67D 0300 0315 0062; # (a◌꙽◌̕◌̀◌֮b; a◌֮◌꙽◌̀◌̕b; a◌֮◌꙽◌̀◌̕b; a◌֮◌꙽◌̀◌̕b; a◌֮◌꙽◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC PAYEROK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A69E 0062;00E0 05AE A69E 0315 0062;0061 05AE 0300 A69E 0315 0062;00E0 05AE A69E 0315 0062;0061 05AE 0300 A69E 0315 0062; # (a◌̕◌̀◌֮◌ꚞb; à◌֮◌ꚞ◌̕b; a◌֮◌̀◌ꚞ◌̕b; à◌֮◌ꚞ◌̕b; a◌֮◌̀◌ꚞ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER EF, LATIN SMALL LETTER B
+0061 A69E 0315 0300 05AE 0062;0061 05AE A69E 0300 0315 0062;0061 05AE A69E 0300 0315 0062;0061 05AE A69E 0300 0315 0062;0061 05AE A69E 0300 0315 0062; # (a◌ꚞ◌̕◌̀◌֮b; a◌֮◌ꚞ◌̀◌̕b; a◌֮◌ꚞ◌̀◌̕b; a◌֮◌ꚞ◌̀◌̕b; a◌֮◌ꚞ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER EF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A69F 0062;00E0 05AE A69F 0315 0062;0061 05AE 0300 A69F 0315 0062;00E0 05AE A69F 0315 0062;0061 05AE 0300 A69F 0315 0062; # (a◌̕◌̀◌֮◌ꚟb; à◌֮◌ꚟ◌̕b; a◌֮◌̀◌ꚟ◌̕b; à◌֮◌ꚟ◌̕b; a◌֮◌̀◌ꚟ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC LETTER IOTIFIED E, LATIN SMALL LETTER B
+0061 A69F 0315 0300 05AE 0062;0061 05AE A69F 0300 0315 0062;0061 05AE A69F 0300 0315 0062;0061 05AE A69F 0300 0315 0062;0061 05AE A69F 0300 0315 0062; # (a◌ꚟ◌̕◌̀◌֮b; a◌֮◌ꚟ◌̀◌̕b; a◌֮◌ꚟ◌̀◌̕b; a◌֮◌ꚟ◌̀◌̕b; a◌֮◌ꚟ◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC LETTER IOTIFIED E, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A6F0 0062;00E0 05AE A6F0 0315 0062;0061 05AE 0300 A6F0 0315 0062;00E0 05AE A6F0 0315 0062;0061 05AE 0300 A6F0 0315 0062; # (a◌̕◌̀◌֮◌꛰b; à◌֮◌꛰◌̕b; a◌֮◌̀◌꛰◌̕b; à◌֮◌꛰◌̕b; a◌֮◌̀◌꛰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BAMUM COMBINING MARK KOQNDON, LATIN SMALL LETTER B
+0061 A6F0 0315 0300 05AE 0062;0061 05AE A6F0 0300 0315 0062;0061 05AE A6F0 0300 0315 0062;0061 05AE A6F0 0300 0315 0062;0061 05AE A6F0 0300 0315 0062; # (a◌꛰◌̕◌̀◌֮b; a◌֮◌꛰◌̀◌̕b; a◌֮◌꛰◌̀◌̕b; a◌֮◌꛰◌̀◌̕b; a◌֮◌꛰◌̀◌̕b; ) LATIN SMALL LETTER A, BAMUM COMBINING MARK KOQNDON, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A6F1 0062;00E0 05AE A6F1 0315 0062;0061 05AE 0300 A6F1 0315 0062;00E0 05AE A6F1 0315 0062;0061 05AE 0300 A6F1 0315 0062; # (a◌̕◌̀◌֮◌꛱b; à◌֮◌꛱◌̕b; a◌֮◌̀◌꛱◌̕b; à◌֮◌꛱◌̕b; a◌֮◌̀◌꛱◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, BAMUM COMBINING MARK TUKWENTIS, LATIN SMALL LETTER B
+0061 A6F1 0315 0300 05AE 0062;0061 05AE A6F1 0300 0315 0062;0061 05AE A6F1 0300 0315 0062;0061 05AE A6F1 0300 0315 0062;0061 05AE A6F1 0300 0315 0062; # (a◌꛱◌̕◌̀◌֮b; a◌֮◌꛱◌̀◌̕b; a◌֮◌꛱◌̀◌̕b; a◌֮◌꛱◌̀◌̕b; a◌֮◌꛱◌̀◌̕b; ) LATIN SMALL LETTER A, BAMUM COMBINING MARK TUKWENTIS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05B0 094D 3099 A806 0062;0061 3099 094D A806 05B0 0062;0061 3099 094D A806 05B0 0062;0061 3099 094D A806 05B0 0062;0061 3099 094D A806 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œê †b; a◌゙◌à¥â—Œê †â—ŒÖ°b; a◌゙◌à¥â—Œê †â—ŒÖ°b; a◌゙◌à¥â—Œê †â—ŒÖ°b; a◌゙◌à¥â—Œê †â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, SYLOTI NAGRI SIGN HASANTA, LATIN SMALL LETTER B
+0061 A806 05B0 094D 3099 0062;0061 3099 A806 094D 05B0 0062;0061 3099 A806 094D 05B0 0062;0061 3099 A806 094D 05B0 0062;0061 3099 A806 094D 05B0 0062; # (a◌꠆◌ְ◌à¥â—Œã‚™b; a◌゙◌꠆◌à¥â—ŒÖ°b; a◌゙◌꠆◌à¥â—ŒÖ°b; a◌゙◌꠆◌à¥â—ŒÖ°b; a◌゙◌꠆◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, SYLOTI NAGRI SIGN HASANTA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 A82C 0062;0061 3099 094D A82C 05B0 0062;0061 3099 094D A82C 05B0 0062;0061 3099 094D A82C 05B0 0062;0061 3099 094D A82C 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œê ¬b; a◌゙◌à¥â—Œê ¬â—ŒÖ°b; a◌゙◌à¥â—Œê ¬â—ŒÖ°b; a◌゙◌à¥â—Œê ¬â—ŒÖ°b; a◌゙◌à¥â—Œê ¬â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, SYLOTI NAGRI SIGN ALTERNATE HASANTA, LATIN SMALL LETTER B
+0061 A82C 05B0 094D 3099 0062;0061 3099 A82C 094D 05B0 0062;0061 3099 A82C 094D 05B0 0062;0061 3099 A82C 094D 05B0 0062;0061 3099 A82C 094D 05B0 0062; # (a◌꠬◌ְ◌à¥â—Œã‚™b; a◌゙◌꠬◌à¥â—ŒÖ°b; a◌゙◌꠬◌à¥â—ŒÖ°b; a◌゙◌꠬◌à¥â—ŒÖ°b; a◌゙◌꠬◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, SYLOTI NAGRI SIGN ALTERNATE HASANTA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 A8C4 0062;0061 3099 094D A8C4 05B0 0062;0061 3099 094D A8C4 05B0 0062;0061 3099 094D A8C4 05B0 0062;0061 3099 094D A8C4 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œê£„b; a◌゙◌à¥â—Œê£„◌ְb; a◌゙◌à¥â—Œê£„◌ְb; a◌゙◌à¥â—Œê£„◌ְb; a◌゙◌à¥â—Œê£„◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, SAURASHTRA SIGN VIRAMA, LATIN SMALL LETTER B
+0061 A8C4 05B0 094D 3099 0062;0061 3099 A8C4 094D 05B0 0062;0061 3099 A8C4 094D 05B0 0062;0061 3099 A8C4 094D 05B0 0062;0061 3099 A8C4 094D 05B0 0062; # (a◌꣄◌ְ◌à¥â—Œã‚™b; a◌゙◌꣄◌à¥â—ŒÖ°b; a◌゙◌꣄◌à¥â—ŒÖ°b; a◌゙◌꣄◌à¥â—ŒÖ°b; a◌゙◌꣄◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, SAURASHTRA SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8E0 0062;00E0 05AE A8E0 0315 0062;0061 05AE 0300 A8E0 0315 0062;00E0 05AE A8E0 0315 0062;0061 05AE 0300 A8E0 0315 0062; # (a◌̕◌̀◌֮◌꣠b; à◌֮◌꣠◌̕b; a◌֮◌̀◌꣠◌̕b; à◌֮◌꣠◌̕b; a◌֮◌̀◌꣠◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI DIGIT ZERO, LATIN SMALL LETTER B
+0061 A8E0 0315 0300 05AE 0062;0061 05AE A8E0 0300 0315 0062;0061 05AE A8E0 0300 0315 0062;0061 05AE A8E0 0300 0315 0062;0061 05AE A8E0 0300 0315 0062; # (a◌꣠◌̕◌̀◌֮b; a◌֮◌꣠◌̀◌̕b; a◌֮◌꣠◌̀◌̕b; a◌֮◌꣠◌̀◌̕b; a◌֮◌꣠◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI DIGIT ZERO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8E1 0062;00E0 05AE A8E1 0315 0062;0061 05AE 0300 A8E1 0315 0062;00E0 05AE A8E1 0315 0062;0061 05AE 0300 A8E1 0315 0062; # (a◌̕◌̀◌֮◌꣡b; à◌֮◌꣡◌̕b; a◌֮◌̀◌꣡◌̕b; à◌֮◌꣡◌̕b; a◌֮◌̀◌꣡◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI DIGIT ONE, LATIN SMALL LETTER B
+0061 A8E1 0315 0300 05AE 0062;0061 05AE A8E1 0300 0315 0062;0061 05AE A8E1 0300 0315 0062;0061 05AE A8E1 0300 0315 0062;0061 05AE A8E1 0300 0315 0062; # (a◌꣡◌̕◌̀◌֮b; a◌֮◌꣡◌̀◌̕b; a◌֮◌꣡◌̀◌̕b; a◌֮◌꣡◌̀◌̕b; a◌֮◌꣡◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI DIGIT ONE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8E2 0062;00E0 05AE A8E2 0315 0062;0061 05AE 0300 A8E2 0315 0062;00E0 05AE A8E2 0315 0062;0061 05AE 0300 A8E2 0315 0062; # (a◌̕◌̀◌֮◌꣢b; à◌֮◌꣢◌̕b; a◌֮◌̀◌꣢◌̕b; à◌֮◌꣢◌̕b; a◌֮◌̀◌꣢◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI DIGIT TWO, LATIN SMALL LETTER B
+0061 A8E2 0315 0300 05AE 0062;0061 05AE A8E2 0300 0315 0062;0061 05AE A8E2 0300 0315 0062;0061 05AE A8E2 0300 0315 0062;0061 05AE A8E2 0300 0315 0062; # (a◌꣢◌̕◌̀◌֮b; a◌֮◌꣢◌̀◌̕b; a◌֮◌꣢◌̀◌̕b; a◌֮◌꣢◌̀◌̕b; a◌֮◌꣢◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI DIGIT TWO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8E3 0062;00E0 05AE A8E3 0315 0062;0061 05AE 0300 A8E3 0315 0062;00E0 05AE A8E3 0315 0062;0061 05AE 0300 A8E3 0315 0062; # (a◌̕◌̀◌֮◌꣣b; à◌֮◌꣣◌̕b; a◌֮◌̀◌꣣◌̕b; à◌֮◌꣣◌̕b; a◌֮◌̀◌꣣◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI DIGIT THREE, LATIN SMALL LETTER B
+0061 A8E3 0315 0300 05AE 0062;0061 05AE A8E3 0300 0315 0062;0061 05AE A8E3 0300 0315 0062;0061 05AE A8E3 0300 0315 0062;0061 05AE A8E3 0300 0315 0062; # (a◌꣣◌̕◌̀◌֮b; a◌֮◌꣣◌̀◌̕b; a◌֮◌꣣◌̀◌̕b; a◌֮◌꣣◌̀◌̕b; a◌֮◌꣣◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI DIGIT THREE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8E4 0062;00E0 05AE A8E4 0315 0062;0061 05AE 0300 A8E4 0315 0062;00E0 05AE A8E4 0315 0062;0061 05AE 0300 A8E4 0315 0062; # (a◌̕◌̀◌֮◌꣤b; à◌֮◌꣤◌̕b; a◌֮◌̀◌꣤◌̕b; à◌֮◌꣤◌̕b; a◌֮◌̀◌꣤◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI DIGIT FOUR, LATIN SMALL LETTER B
+0061 A8E4 0315 0300 05AE 0062;0061 05AE A8E4 0300 0315 0062;0061 05AE A8E4 0300 0315 0062;0061 05AE A8E4 0300 0315 0062;0061 05AE A8E4 0300 0315 0062; # (a◌꣤◌̕◌̀◌֮b; a◌֮◌꣤◌̀◌̕b; a◌֮◌꣤◌̀◌̕b; a◌֮◌꣤◌̀◌̕b; a◌֮◌꣤◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI DIGIT FOUR, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8E5 0062;00E0 05AE A8E5 0315 0062;0061 05AE 0300 A8E5 0315 0062;00E0 05AE A8E5 0315 0062;0061 05AE 0300 A8E5 0315 0062; # (a◌̕◌̀◌֮◌꣥b; à◌֮◌꣥◌̕b; a◌֮◌̀◌꣥◌̕b; à◌֮◌꣥◌̕b; a◌֮◌̀◌꣥◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI DIGIT FIVE, LATIN SMALL LETTER B
+0061 A8E5 0315 0300 05AE 0062;0061 05AE A8E5 0300 0315 0062;0061 05AE A8E5 0300 0315 0062;0061 05AE A8E5 0300 0315 0062;0061 05AE A8E5 0300 0315 0062; # (a◌꣥◌̕◌̀◌֮b; a◌֮◌꣥◌̀◌̕b; a◌֮◌꣥◌̀◌̕b; a◌֮◌꣥◌̀◌̕b; a◌֮◌꣥◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI DIGIT FIVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8E6 0062;00E0 05AE A8E6 0315 0062;0061 05AE 0300 A8E6 0315 0062;00E0 05AE A8E6 0315 0062;0061 05AE 0300 A8E6 0315 0062; # (a◌̕◌̀◌֮◌꣦b; à◌֮◌꣦◌̕b; a◌֮◌̀◌꣦◌̕b; à◌֮◌꣦◌̕b; a◌֮◌̀◌꣦◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI DIGIT SIX, LATIN SMALL LETTER B
+0061 A8E6 0315 0300 05AE 0062;0061 05AE A8E6 0300 0315 0062;0061 05AE A8E6 0300 0315 0062;0061 05AE A8E6 0300 0315 0062;0061 05AE A8E6 0300 0315 0062; # (a◌꣦◌̕◌̀◌֮b; a◌֮◌꣦◌̀◌̕b; a◌֮◌꣦◌̀◌̕b; a◌֮◌꣦◌̀◌̕b; a◌֮◌꣦◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI DIGIT SIX, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8E7 0062;00E0 05AE A8E7 0315 0062;0061 05AE 0300 A8E7 0315 0062;00E0 05AE A8E7 0315 0062;0061 05AE 0300 A8E7 0315 0062; # (a◌̕◌̀◌֮◌꣧b; à◌֮◌꣧◌̕b; a◌֮◌̀◌꣧◌̕b; à◌֮◌꣧◌̕b; a◌֮◌̀◌꣧◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI DIGIT SEVEN, LATIN SMALL LETTER B
+0061 A8E7 0315 0300 05AE 0062;0061 05AE A8E7 0300 0315 0062;0061 05AE A8E7 0300 0315 0062;0061 05AE A8E7 0300 0315 0062;0061 05AE A8E7 0300 0315 0062; # (a◌꣧◌̕◌̀◌֮b; a◌֮◌꣧◌̀◌̕b; a◌֮◌꣧◌̀◌̕b; a◌֮◌꣧◌̀◌̕b; a◌֮◌꣧◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI DIGIT SEVEN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8E8 0062;00E0 05AE A8E8 0315 0062;0061 05AE 0300 A8E8 0315 0062;00E0 05AE A8E8 0315 0062;0061 05AE 0300 A8E8 0315 0062; # (a◌̕◌̀◌֮◌꣨b; à◌֮◌꣨◌̕b; a◌֮◌̀◌꣨◌̕b; à◌֮◌꣨◌̕b; a◌֮◌̀◌꣨◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI DIGIT EIGHT, LATIN SMALL LETTER B
+0061 A8E8 0315 0300 05AE 0062;0061 05AE A8E8 0300 0315 0062;0061 05AE A8E8 0300 0315 0062;0061 05AE A8E8 0300 0315 0062;0061 05AE A8E8 0300 0315 0062; # (a◌꣨◌̕◌̀◌֮b; a◌֮◌꣨◌̀◌̕b; a◌֮◌꣨◌̀◌̕b; a◌֮◌꣨◌̀◌̕b; a◌֮◌꣨◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI DIGIT EIGHT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8E9 0062;00E0 05AE A8E9 0315 0062;0061 05AE 0300 A8E9 0315 0062;00E0 05AE A8E9 0315 0062;0061 05AE 0300 A8E9 0315 0062; # (a◌̕◌̀◌֮◌꣩b; à◌֮◌꣩◌̕b; a◌֮◌̀◌꣩◌̕b; à◌֮◌꣩◌̕b; a◌֮◌̀◌꣩◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI DIGIT NINE, LATIN SMALL LETTER B
+0061 A8E9 0315 0300 05AE 0062;0061 05AE A8E9 0300 0315 0062;0061 05AE A8E9 0300 0315 0062;0061 05AE A8E9 0300 0315 0062;0061 05AE A8E9 0300 0315 0062; # (a◌꣩◌̕◌̀◌֮b; a◌֮◌꣩◌̀◌̕b; a◌֮◌꣩◌̀◌̕b; a◌֮◌꣩◌̀◌̕b; a◌֮◌꣩◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI DIGIT NINE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8EA 0062;00E0 05AE A8EA 0315 0062;0061 05AE 0300 A8EA 0315 0062;00E0 05AE A8EA 0315 0062;0061 05AE 0300 A8EA 0315 0062; # (a◌̕◌̀◌֮◌꣪b; à◌֮◌꣪◌̕b; a◌֮◌̀◌꣪◌̕b; à◌֮◌꣪◌̕b; a◌֮◌̀◌꣪◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI LETTER A, LATIN SMALL LETTER B
+0061 A8EA 0315 0300 05AE 0062;0061 05AE A8EA 0300 0315 0062;0061 05AE A8EA 0300 0315 0062;0061 05AE A8EA 0300 0315 0062;0061 05AE A8EA 0300 0315 0062; # (a◌꣪◌̕◌̀◌֮b; a◌֮◌꣪◌̀◌̕b; a◌֮◌꣪◌̀◌̕b; a◌֮◌꣪◌̀◌̕b; a◌֮◌꣪◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8EB 0062;00E0 05AE A8EB 0315 0062;0061 05AE 0300 A8EB 0315 0062;00E0 05AE A8EB 0315 0062;0061 05AE 0300 A8EB 0315 0062; # (a◌̕◌̀◌֮◌꣫b; à◌֮◌꣫◌̕b; a◌֮◌̀◌꣫◌̕b; à◌֮◌꣫◌̕b; a◌֮◌̀◌꣫◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI LETTER U, LATIN SMALL LETTER B
+0061 A8EB 0315 0300 05AE 0062;0061 05AE A8EB 0300 0315 0062;0061 05AE A8EB 0300 0315 0062;0061 05AE A8EB 0300 0315 0062;0061 05AE A8EB 0300 0315 0062; # (a◌꣫◌̕◌̀◌֮b; a◌֮◌꣫◌̀◌̕b; a◌֮◌꣫◌̀◌̕b; a◌֮◌꣫◌̀◌̕b; a◌֮◌꣫◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI LETTER U, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8EC 0062;00E0 05AE A8EC 0315 0062;0061 05AE 0300 A8EC 0315 0062;00E0 05AE A8EC 0315 0062;0061 05AE 0300 A8EC 0315 0062; # (a◌̕◌̀◌֮◌꣬b; à◌֮◌꣬◌̕b; a◌֮◌̀◌꣬◌̕b; à◌֮◌꣬◌̕b; a◌֮◌̀◌꣬◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI LETTER KA, LATIN SMALL LETTER B
+0061 A8EC 0315 0300 05AE 0062;0061 05AE A8EC 0300 0315 0062;0061 05AE A8EC 0300 0315 0062;0061 05AE A8EC 0300 0315 0062;0061 05AE A8EC 0300 0315 0062; # (a◌꣬◌̕◌̀◌֮b; a◌֮◌꣬◌̀◌̕b; a◌֮◌꣬◌̀◌̕b; a◌֮◌꣬◌̀◌̕b; a◌֮◌꣬◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI LETTER KA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8ED 0062;00E0 05AE A8ED 0315 0062;0061 05AE 0300 A8ED 0315 0062;00E0 05AE A8ED 0315 0062;0061 05AE 0300 A8ED 0315 0062; # (a◌̕◌̀◌֮◌꣭b; à◌֮◌꣭◌̕b; a◌֮◌̀◌꣭◌̕b; à◌֮◌꣭◌̕b; a◌֮◌̀◌꣭◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI LETTER NA, LATIN SMALL LETTER B
+0061 A8ED 0315 0300 05AE 0062;0061 05AE A8ED 0300 0315 0062;0061 05AE A8ED 0300 0315 0062;0061 05AE A8ED 0300 0315 0062;0061 05AE A8ED 0300 0315 0062; # (a◌꣭◌̕◌̀◌֮b; a◌֮◌꣭◌̀◌̕b; a◌֮◌꣭◌̀◌̕b; a◌֮◌꣭◌̀◌̕b; a◌֮◌꣭◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI LETTER NA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8EE 0062;00E0 05AE A8EE 0315 0062;0061 05AE 0300 A8EE 0315 0062;00E0 05AE A8EE 0315 0062;0061 05AE 0300 A8EE 0315 0062; # (a◌̕◌̀◌֮◌꣮b; à◌֮◌꣮◌̕b; a◌֮◌̀◌꣮◌̕b; à◌֮◌꣮◌̕b; a◌֮◌̀◌꣮◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI LETTER PA, LATIN SMALL LETTER B
+0061 A8EE 0315 0300 05AE 0062;0061 05AE A8EE 0300 0315 0062;0061 05AE A8EE 0300 0315 0062;0061 05AE A8EE 0300 0315 0062;0061 05AE A8EE 0300 0315 0062; # (a◌꣮◌̕◌̀◌֮b; a◌֮◌꣮◌̀◌̕b; a◌֮◌꣮◌̀◌̕b; a◌֮◌꣮◌̀◌̕b; a◌֮◌꣮◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI LETTER PA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8EF 0062;00E0 05AE A8EF 0315 0062;0061 05AE 0300 A8EF 0315 0062;00E0 05AE A8EF 0315 0062;0061 05AE 0300 A8EF 0315 0062; # (a◌̕◌̀◌֮◌꣯b; à◌֮◌꣯◌̕b; a◌֮◌̀◌꣯◌̕b; à◌֮◌꣯◌̕b; a◌֮◌̀◌꣯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI LETTER RA, LATIN SMALL LETTER B
+0061 A8EF 0315 0300 05AE 0062;0061 05AE A8EF 0300 0315 0062;0061 05AE A8EF 0300 0315 0062;0061 05AE A8EF 0300 0315 0062;0061 05AE A8EF 0300 0315 0062; # (a◌꣯◌̕◌̀◌֮b; a◌֮◌꣯◌̀◌̕b; a◌֮◌꣯◌̀◌̕b; a◌֮◌꣯◌̀◌̕b; a◌֮◌꣯◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI LETTER RA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8F0 0062;00E0 05AE A8F0 0315 0062;0061 05AE 0300 A8F0 0315 0062;00E0 05AE A8F0 0315 0062;0061 05AE 0300 A8F0 0315 0062; # (a◌̕◌̀◌֮◌꣰b; à◌֮◌꣰◌̕b; a◌֮◌̀◌꣰◌̕b; à◌֮◌꣰◌̕b; a◌֮◌̀◌꣰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI LETTER VI, LATIN SMALL LETTER B
+0061 A8F0 0315 0300 05AE 0062;0061 05AE A8F0 0300 0315 0062;0061 05AE A8F0 0300 0315 0062;0061 05AE A8F0 0300 0315 0062;0061 05AE A8F0 0300 0315 0062; # (a◌꣰◌̕◌̀◌֮b; a◌֮◌꣰◌̀◌̕b; a◌֮◌꣰◌̀◌̕b; a◌֮◌꣰◌̀◌̕b; a◌֮◌꣰◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI LETTER VI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE A8F1 0062;00E0 05AE A8F1 0315 0062;0061 05AE 0300 A8F1 0315 0062;00E0 05AE A8F1 0315 0062;0061 05AE 0300 A8F1 0315 0062; # (a◌̕◌̀◌֮◌꣱b; à◌֮◌꣱◌̕b; a◌֮◌̀◌꣱◌̕b; à◌֮◌꣱◌̕b; a◌֮◌̀◌꣱◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DEVANAGARI SIGN AVAGRAHA, LATIN SMALL LETTER B
+0061 A8F1 0315 0300 05AE 0062;0061 05AE A8F1 0300 0315 0062;0061 05AE A8F1 0300 0315 0062;0061 05AE A8F1 0300 0315 0062;0061 05AE A8F1 0300 0315 0062; # (a◌꣱◌̕◌̀◌֮b; a◌֮◌꣱◌̀◌̕b; a◌֮◌꣱◌̀◌̕b; a◌֮◌꣱◌̀◌̕b; a◌֮◌꣱◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DEVANAGARI SIGN AVAGRAHA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA A92B 0062;0061 1DFA 0316 A92B 059A 0062;0061 1DFA 0316 A92B 059A 0062;0061 1DFA 0316 A92B 059A 0062;0061 1DFA 0316 A92B 059A 0062; # (a◌֚◌̖◌᷺◌꤫b; a◌᷺◌̖◌꤫◌֚b; a◌᷺◌̖◌꤫◌֚b; a◌᷺◌̖◌꤫◌֚b; a◌᷺◌̖◌꤫◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, KAYAH LI TONE PLOPHU, LATIN SMALL LETTER B
+0061 A92B 059A 0316 1DFA 0062;0061 1DFA A92B 0316 059A 0062;0061 1DFA A92B 0316 059A 0062;0061 1DFA A92B 0316 059A 0062;0061 1DFA A92B 0316 059A 0062; # (a◌꤫◌֚◌̖◌᷺b; a◌᷺◌꤫◌̖◌֚b; a◌᷺◌꤫◌̖◌֚b; a◌᷺◌꤫◌̖◌֚b; a◌᷺◌꤫◌̖◌֚b; ) LATIN SMALL LETTER A, KAYAH LI TONE PLOPHU, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA A92C 0062;0061 1DFA 0316 A92C 059A 0062;0061 1DFA 0316 A92C 059A 0062;0061 1DFA 0316 A92C 059A 0062;0061 1DFA 0316 A92C 059A 0062; # (a◌֚◌̖◌᷺◌꤬b; a◌᷺◌̖◌꤬◌֚b; a◌᷺◌̖◌꤬◌֚b; a◌᷺◌̖◌꤬◌֚b; a◌᷺◌̖◌꤬◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, KAYAH LI TONE CALYA, LATIN SMALL LETTER B
+0061 A92C 059A 0316 1DFA 0062;0061 1DFA A92C 0316 059A 0062;0061 1DFA A92C 0316 059A 0062;0061 1DFA A92C 0316 059A 0062;0061 1DFA A92C 0316 059A 0062; # (a◌꤬◌֚◌̖◌᷺b; a◌᷺◌꤬◌̖◌֚b; a◌᷺◌꤬◌̖◌֚b; a◌᷺◌꤬◌̖◌֚b; a◌᷺◌꤬◌̖◌֚b; ) LATIN SMALL LETTER A, KAYAH LI TONE CALYA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA A92D 0062;0061 1DFA 0316 A92D 059A 0062;0061 1DFA 0316 A92D 059A 0062;0061 1DFA 0316 A92D 059A 0062;0061 1DFA 0316 A92D 059A 0062; # (a◌֚◌̖◌᷺◌꤭b; a◌᷺◌̖◌꤭◌֚b; a◌᷺◌̖◌꤭◌֚b; a◌᷺◌̖◌꤭◌֚b; a◌᷺◌̖◌꤭◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, KAYAH LI TONE CALYA PLOPHU, LATIN SMALL LETTER B
+0061 A92D 059A 0316 1DFA 0062;0061 1DFA A92D 0316 059A 0062;0061 1DFA A92D 0316 059A 0062;0061 1DFA A92D 0316 059A 0062;0061 1DFA A92D 0316 059A 0062; # (a◌꤭◌֚◌̖◌᷺b; a◌᷺◌꤭◌̖◌֚b; a◌᷺◌꤭◌̖◌֚b; a◌᷺◌꤭◌̖◌֚b; a◌᷺◌꤭◌̖◌֚b; ) LATIN SMALL LETTER A, KAYAH LI TONE CALYA PLOPHU, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 05B0 094D 3099 A953 0062;0061 3099 094D A953 05B0 0062;0061 3099 094D A953 05B0 0062;0061 3099 094D A953 05B0 0062;0061 3099 094D A953 05B0 0062; # (a◌ְ◌à¥â—Œã‚™ê¥“b; a◌゙◌à¥ê¥“◌ְb; a◌゙◌à¥ê¥“◌ְb; a◌゙◌à¥ê¥“◌ְb; a◌゙◌à¥ê¥“◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, REJANG VIRAMA, LATIN SMALL LETTER B
+0061 A953 05B0 094D 3099 0062;0061 3099 A953 094D 05B0 0062;0061 3099 A953 094D 05B0 0062;0061 3099 A953 094D 05B0 0062;0061 3099 A953 094D 05B0 0062; # (a꥓◌ְ◌à¥â—Œã‚™b; a◌゙꥓◌à¥â—ŒÖ°b; a◌゙꥓◌à¥â—ŒÖ°b; a◌゙꥓◌à¥â—ŒÖ°b; a◌゙꥓◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, REJANG VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 A9B3 0062;0061 16FF0 093C A9B3 3099 0062;0061 16FF0 093C A9B3 3099 0062;0061 16FF0 093C A9B3 3099 0062;0061 16FF0 093C A9B3 3099 0062; # (a◌゙◌𖿰़◌꦳b; a𖿰◌़◌꦳◌゙b; a𖿰◌़◌꦳◌゙b; a𖿰◌़◌꦳◌゙b; a𖿰◌़◌꦳◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, JAVANESE SIGN CECAK TELU, LATIN SMALL LETTER B
+0061 A9B3 3099 093C 16FF0 0062;0061 16FF0 A9B3 093C 3099 0062;0061 16FF0 A9B3 093C 3099 0062;0061 16FF0 A9B3 093C 3099 0062;0061 16FF0 A9B3 093C 3099 0062; # (a◌꦳◌゙◌𖿰़b; a𖿰◌꦳◌़◌゙b; a𖿰◌꦳◌़◌゙b; a𖿰◌꦳◌़◌゙b; a𖿰◌꦳◌़◌゙b; ) LATIN SMALL LETTER A, JAVANESE SIGN CECAK TELU, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 A9C0 0062;0061 3099 094D A9C0 05B0 0062;0061 3099 094D A9C0 05B0 0062;0061 3099 094D A9C0 05B0 0062;0061 3099 094D A9C0 05B0 0062; # (a◌ְ◌à¥â—Œã‚™ê§€b; a◌゙◌à¥ê§€â—ŒÖ°b; a◌゙◌à¥ê§€â—ŒÖ°b; a◌゙◌à¥ê§€â—ŒÖ°b; a◌゙◌à¥ê§€â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, JAVANESE PANGKON, LATIN SMALL LETTER B
+0061 A9C0 05B0 094D 3099 0062;0061 3099 A9C0 094D 05B0 0062;0061 3099 A9C0 094D 05B0 0062;0061 3099 A9C0 094D 05B0 0062;0061 3099 A9C0 094D 05B0 0062; # (a꧀◌ְ◌à¥â—Œã‚™b; a◌゙꧀◌à¥â—ŒÖ°b; a◌゙꧀◌à¥â—ŒÖ°b; a◌゙꧀◌à¥â—ŒÖ°b; a◌゙꧀◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, JAVANESE PANGKON, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE AAB0 0062;00E0 05AE AAB0 0315 0062;0061 05AE 0300 AAB0 0315 0062;00E0 05AE AAB0 0315 0062;0061 05AE 0300 AAB0 0315 0062; # (a◌̕◌̀◌֮◌ꪰb; à◌֮◌ꪰ◌̕b; a◌֮◌̀◌ꪰ◌̕b; à◌֮◌ꪰ◌̕b; a◌֮◌̀◌ꪰ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI VIET MAI KANG, LATIN SMALL LETTER B
+0061 AAB0 0315 0300 05AE 0062;0061 05AE AAB0 0300 0315 0062;0061 05AE AAB0 0300 0315 0062;0061 05AE AAB0 0300 0315 0062;0061 05AE AAB0 0300 0315 0062; # (a◌ꪰ◌̕◌̀◌֮b; a◌֮◌ꪰ◌̀◌̕b; a◌֮◌ꪰ◌̀◌̕b; a◌֮◌ꪰ◌̀◌̕b; a◌֮◌ꪰ◌̀◌̕b; ) LATIN SMALL LETTER A, TAI VIET MAI KANG, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE AAB2 0062;00E0 05AE AAB2 0315 0062;0061 05AE 0300 AAB2 0315 0062;00E0 05AE AAB2 0315 0062;0061 05AE 0300 AAB2 0315 0062; # (a◌̕◌̀◌֮◌ꪲb; à◌֮◌ꪲ◌̕b; a◌֮◌̀◌ꪲ◌̕b; à◌֮◌ꪲ◌̕b; a◌֮◌̀◌ꪲ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI VIET VOWEL I, LATIN SMALL LETTER B
+0061 AAB2 0315 0300 05AE 0062;0061 05AE AAB2 0300 0315 0062;0061 05AE AAB2 0300 0315 0062;0061 05AE AAB2 0300 0315 0062;0061 05AE AAB2 0300 0315 0062; # (a◌ꪲ◌̕◌̀◌֮b; a◌֮◌ꪲ◌̀◌̕b; a◌֮◌ꪲ◌̀◌̕b; a◌֮◌ꪲ◌̀◌̕b; a◌֮◌ꪲ◌̀◌̕b; ) LATIN SMALL LETTER A, TAI VIET VOWEL I, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE AAB3 0062;00E0 05AE AAB3 0315 0062;0061 05AE 0300 AAB3 0315 0062;00E0 05AE AAB3 0315 0062;0061 05AE 0300 AAB3 0315 0062; # (a◌̕◌̀◌֮◌ꪳb; à◌֮◌ꪳ◌̕b; a◌֮◌̀◌ꪳ◌̕b; à◌֮◌ꪳ◌̕b; a◌֮◌̀◌ꪳ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI VIET VOWEL UE, LATIN SMALL LETTER B
+0061 AAB3 0315 0300 05AE 0062;0061 05AE AAB3 0300 0315 0062;0061 05AE AAB3 0300 0315 0062;0061 05AE AAB3 0300 0315 0062;0061 05AE AAB3 0300 0315 0062; # (a◌ꪳ◌̕◌̀◌֮b; a◌֮◌ꪳ◌̀◌̕b; a◌֮◌ꪳ◌̀◌̕b; a◌֮◌ꪳ◌̀◌̕b; a◌֮◌ꪳ◌̀◌̕b; ) LATIN SMALL LETTER A, TAI VIET VOWEL UE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA AAB4 0062;0061 1DFA 0316 AAB4 059A 0062;0061 1DFA 0316 AAB4 059A 0062;0061 1DFA 0316 AAB4 059A 0062;0061 1DFA 0316 AAB4 059A 0062; # (a◌֚◌̖◌᷺◌ꪴb; a◌᷺◌̖◌ꪴ◌֚b; a◌᷺◌̖◌ꪴ◌֚b; a◌᷺◌̖◌ꪴ◌֚b; a◌᷺◌̖◌ꪴ◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, TAI VIET VOWEL U, LATIN SMALL LETTER B
+0061 AAB4 059A 0316 1DFA 0062;0061 1DFA AAB4 0316 059A 0062;0061 1DFA AAB4 0316 059A 0062;0061 1DFA AAB4 0316 059A 0062;0061 1DFA AAB4 0316 059A 0062; # (a◌ꪴ◌֚◌̖◌᷺b; a◌᷺◌ꪴ◌̖◌֚b; a◌᷺◌ꪴ◌̖◌֚b; a◌᷺◌ꪴ◌̖◌֚b; a◌᷺◌ꪴ◌̖◌֚b; ) LATIN SMALL LETTER A, TAI VIET VOWEL U, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE AAB7 0062;00E0 05AE AAB7 0315 0062;0061 05AE 0300 AAB7 0315 0062;00E0 05AE AAB7 0315 0062;0061 05AE 0300 AAB7 0315 0062; # (a◌̕◌̀◌֮◌ꪷb; à◌֮◌ꪷ◌̕b; a◌֮◌̀◌ꪷ◌̕b; à◌֮◌ꪷ◌̕b; a◌֮◌̀◌ꪷ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI VIET MAI KHIT, LATIN SMALL LETTER B
+0061 AAB7 0315 0300 05AE 0062;0061 05AE AAB7 0300 0315 0062;0061 05AE AAB7 0300 0315 0062;0061 05AE AAB7 0300 0315 0062;0061 05AE AAB7 0300 0315 0062; # (a◌ꪷ◌̕◌̀◌֮b; a◌֮◌ꪷ◌̀◌̕b; a◌֮◌ꪷ◌̀◌̕b; a◌֮◌ꪷ◌̀◌̕b; a◌֮◌ꪷ◌̀◌̕b; ) LATIN SMALL LETTER A, TAI VIET MAI KHIT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE AAB8 0062;00E0 05AE AAB8 0315 0062;0061 05AE 0300 AAB8 0315 0062;00E0 05AE AAB8 0315 0062;0061 05AE 0300 AAB8 0315 0062; # (a◌̕◌̀◌֮◌ꪸb; à◌֮◌ꪸ◌̕b; a◌֮◌̀◌ꪸ◌̕b; à◌֮◌ꪸ◌̕b; a◌֮◌̀◌ꪸ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI VIET VOWEL IA, LATIN SMALL LETTER B
+0061 AAB8 0315 0300 05AE 0062;0061 05AE AAB8 0300 0315 0062;0061 05AE AAB8 0300 0315 0062;0061 05AE AAB8 0300 0315 0062;0061 05AE AAB8 0300 0315 0062; # (a◌ꪸ◌̕◌̀◌֮b; a◌֮◌ꪸ◌̀◌̕b; a◌֮◌ꪸ◌̀◌̕b; a◌֮◌ꪸ◌̀◌̕b; a◌֮◌ꪸ◌̀◌̕b; ) LATIN SMALL LETTER A, TAI VIET VOWEL IA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE AABE 0062;00E0 05AE AABE 0315 0062;0061 05AE 0300 AABE 0315 0062;00E0 05AE AABE 0315 0062;0061 05AE 0300 AABE 0315 0062; # (a◌̕◌̀◌֮◌ꪾb; à◌֮◌ꪾ◌̕b; a◌֮◌̀◌ꪾ◌̕b; à◌֮◌ꪾ◌̕b; a◌֮◌̀◌ꪾ◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI VIET VOWEL AM, LATIN SMALL LETTER B
+0061 AABE 0315 0300 05AE 0062;0061 05AE AABE 0300 0315 0062;0061 05AE AABE 0300 0315 0062;0061 05AE AABE 0300 0315 0062;0061 05AE AABE 0300 0315 0062; # (a◌ꪾ◌̕◌̀◌֮b; a◌֮◌ꪾ◌̀◌̕b; a◌֮◌ꪾ◌̀◌̕b; a◌֮◌ꪾ◌̀◌̕b; a◌֮◌ꪾ◌̀◌̕b; ) LATIN SMALL LETTER A, TAI VIET VOWEL AM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE AABF 0062;00E0 05AE AABF 0315 0062;0061 05AE 0300 AABF 0315 0062;00E0 05AE AABF 0315 0062;0061 05AE 0300 AABF 0315 0062; # (a◌̕◌̀◌֮◌꪿b; à◌֮◌꪿◌̕b; a◌֮◌̀◌꪿◌̕b; à◌֮◌꪿◌̕b; a◌֮◌̀◌꪿◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI VIET TONE MAI EK, LATIN SMALL LETTER B
+0061 AABF 0315 0300 05AE 0062;0061 05AE AABF 0300 0315 0062;0061 05AE AABF 0300 0315 0062;0061 05AE AABF 0300 0315 0062;0061 05AE AABF 0300 0315 0062; # (a◌꪿◌̕◌̀◌֮b; a◌֮◌꪿◌̀◌̕b; a◌֮◌꪿◌̀◌̕b; a◌֮◌꪿◌̀◌̕b; a◌֮◌꪿◌̀◌̕b; ) LATIN SMALL LETTER A, TAI VIET TONE MAI EK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE AAC1 0062;00E0 05AE AAC1 0315 0062;0061 05AE 0300 AAC1 0315 0062;00E0 05AE AAC1 0315 0062;0061 05AE 0300 AAC1 0315 0062; # (a◌̕◌̀◌֮◌ê«b; à◌֮◌ê«â—ŒÌ•b; a◌֮◌̀◌ê«â—ŒÌ•b; à◌֮◌ê«â—ŒÌ•b; a◌֮◌̀◌ê«â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TAI VIET TONE MAI THO, LATIN SMALL LETTER B
+0061 AAC1 0315 0300 05AE 0062;0061 05AE AAC1 0300 0315 0062;0061 05AE AAC1 0300 0315 0062;0061 05AE AAC1 0300 0315 0062;0061 05AE AAC1 0300 0315 0062; # (aâ—Œê«â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ê«â—ŒÌ€â—ŒÌ•b; a◌֮◌ê«â—ŒÌ€â—ŒÌ•b; a◌֮◌ê«â—ŒÌ€â—ŒÌ•b; a◌֮◌ê«â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, TAI VIET TONE MAI THO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05B0 094D 3099 AAF6 0062;0061 3099 094D AAF6 05B0 0062;0061 3099 094D AAF6 05B0 0062;0061 3099 094D AAF6 05B0 0062;0061 3099 094D AAF6 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œê«¶b; a◌゙◌à¥â—Œê«¶â—ŒÖ°b; a◌゙◌à¥â—Œê«¶â—ŒÖ°b; a◌゙◌à¥â—Œê«¶â—ŒÖ°b; a◌゙◌à¥â—Œê«¶â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, MEETEI MAYEK VIRAMA, LATIN SMALL LETTER B
+0061 AAF6 05B0 094D 3099 0062;0061 3099 AAF6 094D 05B0 0062;0061 3099 AAF6 094D 05B0 0062;0061 3099 AAF6 094D 05B0 0062;0061 3099 AAF6 094D 05B0 0062; # (a◌꫶◌ְ◌à¥â—Œã‚™b; a◌゙◌꫶◌à¥â—ŒÖ°b; a◌゙◌꫶◌à¥â—ŒÖ°b; a◌゙◌꫶◌à¥â—ŒÖ°b; a◌゙◌꫶◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MEETEI MAYEK VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 ABED 0062;0061 3099 094D ABED 05B0 0062;0061 3099 094D ABED 05B0 0062;0061 3099 094D ABED 05B0 0062;0061 3099 094D ABED 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œê¯­b; a◌゙◌à¥â—Œê¯­â—ŒÖ°b; a◌゙◌à¥â—Œê¯­â—ŒÖ°b; a◌゙◌à¥â—Œê¯­â—ŒÖ°b; a◌゙◌à¥â—Œê¯­â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, MEETEI MAYEK APUN IYEK, LATIN SMALL LETTER B
+0061 ABED 05B0 094D 3099 0062;0061 3099 ABED 094D 05B0 0062;0061 3099 ABED 094D 05B0 0062;0061 3099 ABED 094D 05B0 0062;0061 3099 ABED 094D 05B0 0062; # (a◌꯭◌ְ◌à¥â—Œã‚™b; a◌゙◌꯭◌à¥â—ŒÖ°b; a◌゙◌꯭◌à¥â—ŒÖ°b; a◌゙◌꯭◌à¥â—ŒÖ°b; a◌゙◌꯭◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MEETEI MAYEK APUN IYEK, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 064B FB1E 05C2 FB1E 0062;0061 05C2 FB1E FB1E 064B 0062;0061 05C2 FB1E FB1E 064B 0062;0061 05C2 FB1E FB1E 064B 0062;0061 05C2 FB1E FB1E 064B 0062; # (a◌ً◌ﬞ◌ׂ◌ﬞb; a◌ׂ◌ﬞ◌ﬞ◌ًb; a◌ׂ◌ﬞ◌ﬞ◌ًb; a◌ׂ◌ﬞ◌ﬞ◌ًb; a◌ׂ◌ﬞ◌ﬞ◌ًb; ) LATIN SMALL LETTER A, ARABIC FATHATAN, HEBREW POINT JUDEO-SPANISH VARIKA, HEBREW POINT SIN DOT, HEBREW POINT JUDEO-SPANISH VARIKA, LATIN SMALL LETTER B
+0061 FB1E 064B FB1E 05C2 0062;0061 05C2 FB1E FB1E 064B 0062;0061 05C2 FB1E FB1E 064B 0062;0061 05C2 FB1E FB1E 064B 0062;0061 05C2 FB1E FB1E 064B 0062; # (a◌ﬞ◌ً◌ﬞ◌ׂb; a◌ׂ◌ﬞ◌ﬞ◌ًb; a◌ׂ◌ﬞ◌ﬞ◌ًb; a◌ׂ◌ﬞ◌ﬞ◌ًb; a◌ׂ◌ﬞ◌ﬞ◌ًb; ) LATIN SMALL LETTER A, HEBREW POINT JUDEO-SPANISH VARIKA, ARABIC FATHATAN, HEBREW POINT JUDEO-SPANISH VARIKA, HEBREW POINT SIN DOT, LATIN SMALL LETTER B
+0061 0315 0300 05AE FE20 0062;00E0 05AE FE20 0315 0062;0061 05AE 0300 FE20 0315 0062;00E0 05AE FE20 0315 0062;0061 05AE 0300 FE20 0315 0062; # (a◌̕◌̀◌֮◌︠b; à◌֮◌︠◌̕b; a◌֮◌̀◌︠◌̕b; à◌֮◌︠◌̕b; a◌֮◌̀◌︠◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LIGATURE LEFT HALF, LATIN SMALL LETTER B
+0061 FE20 0315 0300 05AE 0062;0061 05AE FE20 0300 0315 0062;0061 05AE FE20 0300 0315 0062;0061 05AE FE20 0300 0315 0062;0061 05AE FE20 0300 0315 0062; # (a◌︠◌̕◌̀◌֮b; a◌֮◌︠◌̀◌̕b; a◌֮◌︠◌̀◌̕b; a◌֮◌︠◌̀◌̕b; a◌֮◌︠◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LIGATURE LEFT HALF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE FE21 0062;00E0 05AE FE21 0315 0062;0061 05AE 0300 FE21 0315 0062;00E0 05AE FE21 0315 0062;0061 05AE 0300 FE21 0315 0062; # (a◌̕◌̀◌֮◌︡b; à◌֮◌︡◌̕b; a◌֮◌̀◌︡◌̕b; à◌֮◌︡◌̕b; a◌֮◌̀◌︡◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING LIGATURE RIGHT HALF, LATIN SMALL LETTER B
+0061 FE21 0315 0300 05AE 0062;0061 05AE FE21 0300 0315 0062;0061 05AE FE21 0300 0315 0062;0061 05AE FE21 0300 0315 0062;0061 05AE FE21 0300 0315 0062; # (a◌︡◌̕◌̀◌֮b; a◌֮◌︡◌̀◌̕b; a◌֮◌︡◌̀◌̕b; a◌֮◌︡◌̀◌̕b; a◌֮◌︡◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING LIGATURE RIGHT HALF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE FE22 0062;00E0 05AE FE22 0315 0062;0061 05AE 0300 FE22 0315 0062;00E0 05AE FE22 0315 0062;0061 05AE 0300 FE22 0315 0062; # (a◌̕◌̀◌֮◌︢b; à◌֮◌︢◌̕b; a◌֮◌̀◌︢◌̕b; à◌֮◌︢◌̕b; a◌֮◌̀◌︢◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOUBLE TILDE LEFT HALF, LATIN SMALL LETTER B
+0061 FE22 0315 0300 05AE 0062;0061 05AE FE22 0300 0315 0062;0061 05AE FE22 0300 0315 0062;0061 05AE FE22 0300 0315 0062;0061 05AE FE22 0300 0315 0062; # (a◌︢◌̕◌̀◌֮b; a◌֮◌︢◌̀◌̕b; a◌֮◌︢◌̀◌̕b; a◌֮◌︢◌̀◌̕b; a◌֮◌︢◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOUBLE TILDE LEFT HALF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE FE23 0062;00E0 05AE FE23 0315 0062;0061 05AE 0300 FE23 0315 0062;00E0 05AE FE23 0315 0062;0061 05AE 0300 FE23 0315 0062; # (a◌̕◌̀◌֮◌︣b; à◌֮◌︣◌̕b; a◌֮◌̀◌︣◌̕b; à◌֮◌︣◌̕b; a◌֮◌̀◌︣◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING DOUBLE TILDE RIGHT HALF, LATIN SMALL LETTER B
+0061 FE23 0315 0300 05AE 0062;0061 05AE FE23 0300 0315 0062;0061 05AE FE23 0300 0315 0062;0061 05AE FE23 0300 0315 0062;0061 05AE FE23 0300 0315 0062; # (a◌︣◌̕◌̀◌֮b; a◌֮◌︣◌̀◌̕b; a◌֮◌︣◌̀◌̕b; a◌֮◌︣◌̀◌̕b; a◌֮◌︣◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING DOUBLE TILDE RIGHT HALF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE FE24 0062;00E0 05AE FE24 0315 0062;0061 05AE 0300 FE24 0315 0062;00E0 05AE FE24 0315 0062;0061 05AE 0300 FE24 0315 0062; # (a◌̕◌̀◌֮◌︤b; à◌֮◌︤◌̕b; a◌֮◌̀◌︤◌̕b; à◌֮◌︤◌̕b; a◌֮◌̀◌︤◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING MACRON LEFT HALF, LATIN SMALL LETTER B
+0061 FE24 0315 0300 05AE 0062;0061 05AE FE24 0300 0315 0062;0061 05AE FE24 0300 0315 0062;0061 05AE FE24 0300 0315 0062;0061 05AE FE24 0300 0315 0062; # (a◌︤◌̕◌̀◌֮b; a◌֮◌︤◌̀◌̕b; a◌֮◌︤◌̀◌̕b; a◌֮◌︤◌̀◌̕b; a◌֮◌︤◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING MACRON LEFT HALF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE FE25 0062;00E0 05AE FE25 0315 0062;0061 05AE 0300 FE25 0315 0062;00E0 05AE FE25 0315 0062;0061 05AE 0300 FE25 0315 0062; # (a◌̕◌̀◌֮◌︥b; à◌֮◌︥◌̕b; a◌֮◌̀◌︥◌̕b; à◌֮◌︥◌̕b; a◌֮◌̀◌︥◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING MACRON RIGHT HALF, LATIN SMALL LETTER B
+0061 FE25 0315 0300 05AE 0062;0061 05AE FE25 0300 0315 0062;0061 05AE FE25 0300 0315 0062;0061 05AE FE25 0300 0315 0062;0061 05AE FE25 0300 0315 0062; # (a◌︥◌̕◌̀◌֮b; a◌֮◌︥◌̀◌̕b; a◌֮◌︥◌̀◌̕b; a◌֮◌︥◌̀◌̕b; a◌֮◌︥◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING MACRON RIGHT HALF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE FE26 0062;00E0 05AE FE26 0315 0062;0061 05AE 0300 FE26 0315 0062;00E0 05AE FE26 0315 0062;0061 05AE 0300 FE26 0315 0062; # (a◌̕◌̀◌֮◌︦b; à◌֮◌︦◌̕b; a◌֮◌̀◌︦◌̕b; à◌֮◌︦◌̕b; a◌֮◌̀◌︦◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CONJOINING MACRON, LATIN SMALL LETTER B
+0061 FE26 0315 0300 05AE 0062;0061 05AE FE26 0300 0315 0062;0061 05AE FE26 0300 0315 0062;0061 05AE FE26 0300 0315 0062;0061 05AE FE26 0300 0315 0062; # (a◌︦◌̕◌̀◌֮b; a◌֮◌︦◌̀◌̕b; a◌֮◌︦◌̀◌̕b; a◌֮◌︦◌̀◌̕b; a◌֮◌︦◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CONJOINING MACRON, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA FE27 0062;0061 1DFA 0316 FE27 059A 0062;0061 1DFA 0316 FE27 059A 0062;0061 1DFA 0316 FE27 059A 0062;0061 1DFA 0316 FE27 059A 0062; # (a◌֚◌̖◌᷺◌︧b; a◌᷺◌̖◌︧◌֚b; a◌᷺◌̖◌︧◌֚b; a◌᷺◌̖◌︧◌֚b; a◌᷺◌̖◌︧◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LIGATURE LEFT HALF BELOW, LATIN SMALL LETTER B
+0061 FE27 059A 0316 1DFA 0062;0061 1DFA FE27 0316 059A 0062;0061 1DFA FE27 0316 059A 0062;0061 1DFA FE27 0316 059A 0062;0061 1DFA FE27 0316 059A 0062; # (a◌︧◌֚◌̖◌᷺b; a◌᷺◌︧◌̖◌֚b; a◌᷺◌︧◌̖◌֚b; a◌᷺◌︧◌̖◌֚b; a◌᷺◌︧◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LIGATURE LEFT HALF BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA FE28 0062;0061 1DFA 0316 FE28 059A 0062;0061 1DFA 0316 FE28 059A 0062;0061 1DFA 0316 FE28 059A 0062;0061 1DFA 0316 FE28 059A 0062; # (a◌֚◌̖◌᷺◌︨b; a◌᷺◌̖◌︨◌֚b; a◌᷺◌̖◌︨◌֚b; a◌᷺◌̖◌︨◌֚b; a◌᷺◌̖◌︨◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING LIGATURE RIGHT HALF BELOW, LATIN SMALL LETTER B
+0061 FE28 059A 0316 1DFA 0062;0061 1DFA FE28 0316 059A 0062;0061 1DFA FE28 0316 059A 0062;0061 1DFA FE28 0316 059A 0062;0061 1DFA FE28 0316 059A 0062; # (a◌︨◌֚◌̖◌᷺b; a◌᷺◌︨◌̖◌֚b; a◌᷺◌︨◌̖◌֚b; a◌᷺◌︨◌̖◌֚b; a◌᷺◌︨◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING LIGATURE RIGHT HALF BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA FE29 0062;0061 1DFA 0316 FE29 059A 0062;0061 1DFA 0316 FE29 059A 0062;0061 1DFA 0316 FE29 059A 0062;0061 1DFA 0316 FE29 059A 0062; # (a◌֚◌̖◌᷺◌︩b; a◌᷺◌̖◌︩◌֚b; a◌᷺◌̖◌︩◌֚b; a◌᷺◌̖◌︩◌֚b; a◌᷺◌̖◌︩◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING TILDE LEFT HALF BELOW, LATIN SMALL LETTER B
+0061 FE29 059A 0316 1DFA 0062;0061 1DFA FE29 0316 059A 0062;0061 1DFA FE29 0316 059A 0062;0061 1DFA FE29 0316 059A 0062;0061 1DFA FE29 0316 059A 0062; # (a◌︩◌֚◌̖◌᷺b; a◌᷺◌︩◌̖◌֚b; a◌᷺◌︩◌̖◌֚b; a◌᷺◌︩◌̖◌֚b; a◌᷺◌︩◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING TILDE LEFT HALF BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA FE2A 0062;0061 1DFA 0316 FE2A 059A 0062;0061 1DFA 0316 FE2A 059A 0062;0061 1DFA 0316 FE2A 059A 0062;0061 1DFA 0316 FE2A 059A 0062; # (a◌֚◌̖◌᷺◌︪b; a◌᷺◌̖◌︪◌֚b; a◌᷺◌̖◌︪◌֚b; a◌᷺◌̖◌︪◌֚b; a◌᷺◌̖◌︪◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING TILDE RIGHT HALF BELOW, LATIN SMALL LETTER B
+0061 FE2A 059A 0316 1DFA 0062;0061 1DFA FE2A 0316 059A 0062;0061 1DFA FE2A 0316 059A 0062;0061 1DFA FE2A 0316 059A 0062;0061 1DFA FE2A 0316 059A 0062; # (a◌︪◌֚◌̖◌᷺b; a◌᷺◌︪◌̖◌֚b; a◌᷺◌︪◌̖◌֚b; a◌᷺◌︪◌̖◌֚b; a◌᷺◌︪◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING TILDE RIGHT HALF BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA FE2B 0062;0061 1DFA 0316 FE2B 059A 0062;0061 1DFA 0316 FE2B 059A 0062;0061 1DFA 0316 FE2B 059A 0062;0061 1DFA 0316 FE2B 059A 0062; # (a◌֚◌̖◌᷺◌︫b; a◌᷺◌̖◌︫◌֚b; a◌᷺◌̖◌︫◌֚b; a◌᷺◌̖◌︫◌֚b; a◌᷺◌̖◌︫◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING MACRON LEFT HALF BELOW, LATIN SMALL LETTER B
+0061 FE2B 059A 0316 1DFA 0062;0061 1DFA FE2B 0316 059A 0062;0061 1DFA FE2B 0316 059A 0062;0061 1DFA FE2B 0316 059A 0062;0061 1DFA FE2B 0316 059A 0062; # (a◌︫◌֚◌̖◌᷺b; a◌᷺◌︫◌̖◌֚b; a◌᷺◌︫◌̖◌֚b; a◌᷺◌︫◌̖◌֚b; a◌᷺◌︫◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING MACRON LEFT HALF BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA FE2C 0062;0061 1DFA 0316 FE2C 059A 0062;0061 1DFA 0316 FE2C 059A 0062;0061 1DFA 0316 FE2C 059A 0062;0061 1DFA 0316 FE2C 059A 0062; # (a◌֚◌̖◌᷺◌︬b; a◌᷺◌̖◌︬◌֚b; a◌᷺◌̖◌︬◌֚b; a◌᷺◌̖◌︬◌֚b; a◌᷺◌̖◌︬◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING MACRON RIGHT HALF BELOW, LATIN SMALL LETTER B
+0061 FE2C 059A 0316 1DFA 0062;0061 1DFA FE2C 0316 059A 0062;0061 1DFA FE2C 0316 059A 0062;0061 1DFA FE2C 0316 059A 0062;0061 1DFA FE2C 0316 059A 0062; # (a◌︬◌֚◌̖◌᷺b; a◌᷺◌︬◌̖◌֚b; a◌᷺◌︬◌̖◌֚b; a◌᷺◌︬◌̖◌֚b; a◌᷺◌︬◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING MACRON RIGHT HALF BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA FE2D 0062;0061 1DFA 0316 FE2D 059A 0062;0061 1DFA 0316 FE2D 059A 0062;0061 1DFA 0316 FE2D 059A 0062;0061 1DFA 0316 FE2D 059A 0062; # (a◌֚◌̖◌᷺◌︭b; a◌᷺◌̖◌︭◌֚b; a◌᷺◌̖◌︭◌֚b; a◌᷺◌̖◌︭◌֚b; a◌᷺◌̖◌︭◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COMBINING CONJOINING MACRON BELOW, LATIN SMALL LETTER B
+0061 FE2D 059A 0316 1DFA 0062;0061 1DFA FE2D 0316 059A 0062;0061 1DFA FE2D 0316 059A 0062;0061 1DFA FE2D 0316 059A 0062;0061 1DFA FE2D 0316 059A 0062; # (a◌︭◌֚◌̖◌᷺b; a◌᷺◌︭◌̖◌֚b; a◌᷺◌︭◌̖◌֚b; a◌᷺◌︭◌̖◌֚b; a◌᷺◌︭◌̖◌֚b; ) LATIN SMALL LETTER A, COMBINING CONJOINING MACRON BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE FE2E 0062;00E0 05AE FE2E 0315 0062;0061 05AE 0300 FE2E 0315 0062;00E0 05AE FE2E 0315 0062;0061 05AE 0300 FE2E 0315 0062; # (a◌̕◌̀◌֮◌︮b; à◌֮◌︮◌̕b; a◌֮◌̀◌︮◌̕b; à◌֮◌︮◌̕b; a◌֮◌̀◌︮◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC TITLO LEFT HALF, LATIN SMALL LETTER B
+0061 FE2E 0315 0300 05AE 0062;0061 05AE FE2E 0300 0315 0062;0061 05AE FE2E 0300 0315 0062;0061 05AE FE2E 0300 0315 0062;0061 05AE FE2E 0300 0315 0062; # (a◌︮◌̕◌̀◌֮b; a◌֮◌︮◌̀◌̕b; a◌֮◌︮◌̀◌̕b; a◌֮◌︮◌̀◌̕b; a◌֮◌︮◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC TITLO LEFT HALF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE FE2F 0062;00E0 05AE FE2F 0315 0062;0061 05AE 0300 FE2F 0315 0062;00E0 05AE FE2F 0315 0062;0061 05AE 0300 FE2F 0315 0062; # (a◌̕◌̀◌֮◌︯b; à◌֮◌︯◌̕b; a◌֮◌̀◌︯◌̕b; à◌֮◌︯◌̕b; a◌֮◌̀◌︯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC TITLO RIGHT HALF, LATIN SMALL LETTER B
+0061 FE2F 0315 0300 05AE 0062;0061 05AE FE2F 0300 0315 0062;0061 05AE FE2F 0300 0315 0062;0061 05AE FE2F 0300 0315 0062;0061 05AE FE2F 0300 0315 0062; # (a◌︯◌̕◌̀◌֮b; a◌֮◌︯◌̀◌̕b; a◌֮◌︯◌̀◌̕b; a◌֮◌︯◌̀◌̕b; a◌֮◌︯◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC TITLO RIGHT HALF, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 101FD 0062;0061 1DFA 0316 101FD 059A 0062;0061 1DFA 0316 101FD 059A 0062;0061 1DFA 0316 101FD 059A 0062;0061 1DFA 0316 101FD 059A 0062; # (a◌֚◌̖◌᷺◌ð‡½b; a◌᷺◌̖◌ð‡½â—ŒÖšb; a◌᷺◌̖◌ð‡½â—ŒÖšb; a◌᷺◌̖◌ð‡½â—ŒÖšb; a◌᷺◌̖◌ð‡½â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE, LATIN SMALL LETTER B
+0061 101FD 059A 0316 1DFA 0062;0061 1DFA 101FD 0316 059A 0062;0061 1DFA 101FD 0316 059A 0062;0061 1DFA 101FD 0316 059A 0062;0061 1DFA 101FD 0316 059A 0062; # (aâ—Œð‡½â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð‡½â—ŒÌ–◌֚b; a◌᷺◌ð‡½â—ŒÌ–◌֚b; a◌᷺◌ð‡½â—ŒÌ–◌֚b; a◌᷺◌ð‡½â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 102E0 0062;0061 1DFA 0316 102E0 059A 0062;0061 1DFA 0316 102E0 059A 0062;0061 1DFA 0316 102E0 059A 0062;0061 1DFA 0316 102E0 059A 0062; # (a◌֚◌̖◌᷺◌ð‹ b; a◌᷺◌̖◌ð‹ â—ŒÖšb; a◌᷺◌̖◌ð‹ â—ŒÖšb; a◌᷺◌̖◌ð‹ â—ŒÖšb; a◌᷺◌̖◌ð‹ â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, COPTIC EPACT THOUSANDS MARK, LATIN SMALL LETTER B
+0061 102E0 059A 0316 1DFA 0062;0061 1DFA 102E0 0316 059A 0062;0061 1DFA 102E0 0316 059A 0062;0061 1DFA 102E0 0316 059A 0062;0061 1DFA 102E0 0316 059A 0062; # (aâ—Œð‹ â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð‹ â—ŒÌ–◌֚b; a◌᷺◌ð‹ â—ŒÌ–◌֚b; a◌᷺◌ð‹ â—ŒÌ–◌֚b; a◌᷺◌ð‹ â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, COPTIC EPACT THOUSANDS MARK, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10376 0062;00E0 05AE 10376 0315 0062;0061 05AE 0300 10376 0315 0062;00E0 05AE 10376 0315 0062;0061 05AE 0300 10376 0315 0062; # (a◌̕◌̀◌֮◌ð¶b; à◌֮◌ð¶â—ŒÌ•b; a◌֮◌̀◌ð¶â—ŒÌ•b; à◌֮◌ð¶â—ŒÌ•b; a◌֮◌̀◌ð¶â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING OLD PERMIC LETTER AN, LATIN SMALL LETTER B
+0061 10376 0315 0300 05AE 0062;0061 05AE 10376 0300 0315 0062;0061 05AE 10376 0300 0315 0062;0061 05AE 10376 0300 0315 0062;0061 05AE 10376 0300 0315 0062; # (aâ—Œð¶â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð¶â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¶â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¶â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¶â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING OLD PERMIC LETTER AN, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10377 0062;00E0 05AE 10377 0315 0062;0061 05AE 0300 10377 0315 0062;00E0 05AE 10377 0315 0062;0061 05AE 0300 10377 0315 0062; # (a◌̕◌̀◌֮◌ð·b; à◌֮◌ð·â—ŒÌ•b; a◌֮◌̀◌ð·â—ŒÌ•b; à◌֮◌ð·â—ŒÌ•b; a◌֮◌̀◌ð·â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING OLD PERMIC LETTER DOI, LATIN SMALL LETTER B
+0061 10377 0315 0300 05AE 0062;0061 05AE 10377 0300 0315 0062;0061 05AE 10377 0300 0315 0062;0061 05AE 10377 0300 0315 0062;0061 05AE 10377 0300 0315 0062; # (aâ—Œð·â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð·â—ŒÌ€â—ŒÌ•b; a◌֮◌ð·â—ŒÌ€â—ŒÌ•b; a◌֮◌ð·â—ŒÌ€â—ŒÌ•b; a◌֮◌ð·â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING OLD PERMIC LETTER DOI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10378 0062;00E0 05AE 10378 0315 0062;0061 05AE 0300 10378 0315 0062;00E0 05AE 10378 0315 0062;0061 05AE 0300 10378 0315 0062; # (a◌̕◌̀◌֮◌ð¸b; à◌֮◌ð¸â—ŒÌ•b; a◌֮◌̀◌ð¸â—ŒÌ•b; à◌֮◌ð¸â—ŒÌ•b; a◌֮◌̀◌ð¸â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING OLD PERMIC LETTER ZATA, LATIN SMALL LETTER B
+0061 10378 0315 0300 05AE 0062;0061 05AE 10378 0300 0315 0062;0061 05AE 10378 0300 0315 0062;0061 05AE 10378 0300 0315 0062;0061 05AE 10378 0300 0315 0062; # (aâ—Œð¸â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð¸â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¸â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¸â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¸â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING OLD PERMIC LETTER ZATA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10379 0062;00E0 05AE 10379 0315 0062;0061 05AE 0300 10379 0315 0062;00E0 05AE 10379 0315 0062;0061 05AE 0300 10379 0315 0062; # (a◌̕◌̀◌֮◌ð¹b; à◌֮◌ð¹â—ŒÌ•b; a◌֮◌̀◌ð¹â—ŒÌ•b; à◌֮◌ð¹â—ŒÌ•b; a◌֮◌̀◌ð¹â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING OLD PERMIC LETTER NENOE, LATIN SMALL LETTER B
+0061 10379 0315 0300 05AE 0062;0061 05AE 10379 0300 0315 0062;0061 05AE 10379 0300 0315 0062;0061 05AE 10379 0300 0315 0062;0061 05AE 10379 0300 0315 0062; # (aâ—Œð¹â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð¹â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¹â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¹â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¹â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING OLD PERMIC LETTER NENOE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1037A 0062;00E0 05AE 1037A 0315 0062;0061 05AE 0300 1037A 0315 0062;00E0 05AE 1037A 0315 0062;0061 05AE 0300 1037A 0315 0062; # (a◌̕◌̀◌֮◌ðºb; à◌֮◌ðºâ—ŒÌ•b; a◌֮◌̀◌ðºâ—ŒÌ•b; à◌֮◌ðºâ—ŒÌ•b; a◌֮◌̀◌ðºâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING OLD PERMIC LETTER SII, LATIN SMALL LETTER B
+0061 1037A 0315 0300 05AE 0062;0061 05AE 1037A 0300 0315 0062;0061 05AE 1037A 0300 0315 0062;0061 05AE 1037A 0300 0315 0062;0061 05AE 1037A 0300 0315 0062; # (aâ—Œðºâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ðºâ—ŒÌ€â—ŒÌ•b; a◌֮◌ðºâ—ŒÌ€â—ŒÌ•b; a◌֮◌ðºâ—ŒÌ€â—ŒÌ•b; a◌֮◌ðºâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING OLD PERMIC LETTER SII, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10A0D 0062;0061 1DFA 0316 10A0D 059A 0062;0061 1DFA 0316 10A0D 059A 0062;0061 1DFA 0316 10A0D 059A 0062;0061 1DFA 0316 10A0D 059A 0062; # (a◌֚◌̖◌᷺◌ð¨b; a◌᷺◌̖◌ð¨â—ŒÖšb; a◌᷺◌̖◌ð¨â—ŒÖšb; a◌᷺◌̖◌ð¨â—ŒÖšb; a◌᷺◌̖◌ð¨â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, KHAROSHTHI SIGN DOUBLE RING BELOW, LATIN SMALL LETTER B
+0061 10A0D 059A 0316 1DFA 0062;0061 1DFA 10A0D 0316 059A 0062;0061 1DFA 10A0D 0316 059A 0062;0061 1DFA 10A0D 0316 059A 0062;0061 1DFA 10A0D 0316 059A 0062; # (aâ—Œð¨â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð¨â—ŒÌ–◌֚b; a◌᷺◌ð¨â—ŒÌ–◌֚b; a◌᷺◌ð¨â—ŒÌ–◌֚b; a◌᷺◌ð¨â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, KHAROSHTHI SIGN DOUBLE RING BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10A0F 0062;00E0 05AE 10A0F 0315 0062;0061 05AE 0300 10A0F 0315 0062;00E0 05AE 10A0F 0315 0062;0061 05AE 0300 10A0F 0315 0062; # (a◌̕◌̀◌֮◌ð¨b; à◌֮◌ð¨â—ŒÌ•b; a◌֮◌̀◌ð¨â—ŒÌ•b; à◌֮◌ð¨â—ŒÌ•b; a◌֮◌̀◌ð¨â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, KHAROSHTHI SIGN VISARGA, LATIN SMALL LETTER B
+0061 10A0F 0315 0300 05AE 0062;0061 05AE 10A0F 0300 0315 0062;0061 05AE 10A0F 0300 0315 0062;0061 05AE 10A0F 0300 0315 0062;0061 05AE 10A0F 0300 0315 0062; # (aâ—Œð¨â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð¨â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¨â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¨â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¨â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, KHAROSHTHI SIGN VISARGA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10A38 0062;00E0 05AE 10A38 0315 0062;0061 05AE 0300 10A38 0315 0062;00E0 05AE 10A38 0315 0062;0061 05AE 0300 10A38 0315 0062; # (a◌̕◌̀◌֮◌ð¨¸b; à◌֮◌ð¨¸â—ŒÌ•b; a◌֮◌̀◌ð¨¸â—ŒÌ•b; à◌֮◌ð¨¸â—ŒÌ•b; a◌֮◌̀◌ð¨¸â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, KHAROSHTHI SIGN BAR ABOVE, LATIN SMALL LETTER B
+0061 10A38 0315 0300 05AE 0062;0061 05AE 10A38 0300 0315 0062;0061 05AE 10A38 0300 0315 0062;0061 05AE 10A38 0300 0315 0062;0061 05AE 10A38 0300 0315 0062; # (aâ—Œð¨¸â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð¨¸â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¨¸â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¨¸â—ŒÌ€â—ŒÌ•b; a◌֮◌ð¨¸â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, KHAROSHTHI SIGN BAR ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 16FF0 0334 10A39 0062;0061 0334 10A39 16FF0 0062;0061 0334 10A39 16FF0 0062;0061 0334 10A39 16FF0 0062;0061 0334 10A39 16FF0 0062; # (a𖿰◌̴◌ð¨¹b; a◌̴◌ð¨¹ð–¿°b; a◌̴◌ð¨¹ð–¿°b; a◌̴◌ð¨¹ð–¿°b; a◌̴◌ð¨¹ð–¿°b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, KHAROSHTHI SIGN CAUDA, LATIN SMALL LETTER B
+0061 10A39 16FF0 0334 0062;0061 10A39 0334 16FF0 0062;0061 10A39 0334 16FF0 0062;0061 10A39 0334 16FF0 0062;0061 10A39 0334 16FF0 0062; # (aâ—Œð¨¹ð–¿°â—ŒÌ´b; aâ—Œð¨¹â—ŒÌ´ð–¿°b; aâ—Œð¨¹â—ŒÌ´ð–¿°b; aâ—Œð¨¹â—ŒÌ´ð–¿°b; aâ—Œð¨¹â—ŒÌ´ð–¿°b; ) LATIN SMALL LETTER A, KHAROSHTHI SIGN CAUDA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10A3A 0062;0061 1DFA 0316 10A3A 059A 0062;0061 1DFA 0316 10A3A 059A 0062;0061 1DFA 0316 10A3A 059A 0062;0061 1DFA 0316 10A3A 059A 0062; # (a◌֚◌̖◌᷺◌ð¨ºb; a◌᷺◌̖◌ð¨ºâ—ŒÖšb; a◌᷺◌̖◌ð¨ºâ—ŒÖšb; a◌᷺◌̖◌ð¨ºâ—ŒÖšb; a◌᷺◌̖◌ð¨ºâ—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, KHAROSHTHI SIGN DOT BELOW, LATIN SMALL LETTER B
+0061 10A3A 059A 0316 1DFA 0062;0061 1DFA 10A3A 0316 059A 0062;0061 1DFA 10A3A 0316 059A 0062;0061 1DFA 10A3A 0316 059A 0062;0061 1DFA 10A3A 0316 059A 0062; # (aâ—Œð¨ºâ—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð¨ºâ—ŒÌ–◌֚b; a◌᷺◌ð¨ºâ—ŒÌ–◌֚b; a◌᷺◌ð¨ºâ—ŒÌ–◌֚b; a◌᷺◌ð¨ºâ—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, KHAROSHTHI SIGN DOT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 05B0 094D 3099 10A3F 0062;0061 3099 094D 10A3F 05B0 0062;0061 3099 094D 10A3F 05B0 0062;0061 3099 094D 10A3F 05B0 0062;0061 3099 094D 10A3F 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð¨¿b; a◌゙◌à¥â—Œð¨¿â—ŒÖ°b; a◌゙◌à¥â—Œð¨¿â—ŒÖ°b; a◌゙◌à¥â—Œð¨¿â—ŒÖ°b; a◌゙◌à¥â—Œð¨¿â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, KHAROSHTHI VIRAMA, LATIN SMALL LETTER B
+0061 10A3F 05B0 094D 3099 0062;0061 3099 10A3F 094D 05B0 0062;0061 3099 10A3F 094D 05B0 0062;0061 3099 10A3F 094D 05B0 0062;0061 3099 10A3F 094D 05B0 0062; # (aâ—Œð¨¿â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌ð¨¿â—Œà¥â—ŒÖ°b; a◌゙◌ð¨¿â—Œà¥â—ŒÖ°b; a◌゙◌ð¨¿â—Œà¥â—ŒÖ°b; a◌゙◌ð¨¿â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, KHAROSHTHI VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10AE5 0062;00E0 05AE 10AE5 0315 0062;0061 05AE 0300 10AE5 0315 0062;00E0 05AE 10AE5 0315 0062;0061 05AE 0300 10AE5 0315 0062; # (a◌̕◌̀◌֮◌ð«¥b; à◌֮◌ð«¥â—ŒÌ•b; a◌֮◌̀◌ð«¥â—ŒÌ•b; à◌֮◌ð«¥â—ŒÌ•b; a◌֮◌̀◌ð«¥â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MANICHAEAN ABBREVIATION MARK ABOVE, LATIN SMALL LETTER B
+0061 10AE5 0315 0300 05AE 0062;0061 05AE 10AE5 0300 0315 0062;0061 05AE 10AE5 0300 0315 0062;0061 05AE 10AE5 0300 0315 0062;0061 05AE 10AE5 0300 0315 0062; # (aâ—Œð«¥â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð«¥â—ŒÌ€â—ŒÌ•b; a◌֮◌ð«¥â—ŒÌ€â—ŒÌ•b; a◌֮◌ð«¥â—ŒÌ€â—ŒÌ•b; a◌֮◌ð«¥â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, MANICHAEAN ABBREVIATION MARK ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10AE6 0062;0061 1DFA 0316 10AE6 059A 0062;0061 1DFA 0316 10AE6 059A 0062;0061 1DFA 0316 10AE6 059A 0062;0061 1DFA 0316 10AE6 059A 0062; # (a◌֚◌̖◌᷺◌ð«¦b; a◌᷺◌̖◌ð«¦â—ŒÖšb; a◌᷺◌̖◌ð«¦â—ŒÖšb; a◌᷺◌̖◌ð«¦â—ŒÖšb; a◌᷺◌̖◌ð«¦â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MANICHAEAN ABBREVIATION MARK BELOW, LATIN SMALL LETTER B
+0061 10AE6 059A 0316 1DFA 0062;0061 1DFA 10AE6 0316 059A 0062;0061 1DFA 10AE6 0316 059A 0062;0061 1DFA 10AE6 0316 059A 0062;0061 1DFA 10AE6 0316 059A 0062; # (aâ—Œð«¦â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð«¦â—ŒÌ–◌֚b; a◌᷺◌ð«¦â—ŒÌ–◌֚b; a◌᷺◌ð«¦â—ŒÌ–◌֚b; a◌᷺◌ð«¦â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MANICHAEAN ABBREVIATION MARK BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10D24 0062;00E0 05AE 10D24 0315 0062;0061 05AE 0300 10D24 0315 0062;00E0 05AE 10D24 0315 0062;0061 05AE 0300 10D24 0315 0062; # (a◌̕◌̀◌֮◌ð´¤b; à◌֮◌ð´¤â—ŒÌ•b; a◌֮◌̀◌ð´¤â—ŒÌ•b; à◌֮◌ð´¤â—ŒÌ•b; a◌֮◌̀◌ð´¤â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HANIFI ROHINGYA SIGN HARBAHAY, LATIN SMALL LETTER B
+0061 10D24 0315 0300 05AE 0062;0061 05AE 10D24 0300 0315 0062;0061 05AE 10D24 0300 0315 0062;0061 05AE 10D24 0300 0315 0062;0061 05AE 10D24 0300 0315 0062; # (aâ—Œð´¤â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð´¤â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´¤â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´¤â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´¤â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, HANIFI ROHINGYA SIGN HARBAHAY, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10D25 0062;00E0 05AE 10D25 0315 0062;0061 05AE 0300 10D25 0315 0062;00E0 05AE 10D25 0315 0062;0061 05AE 0300 10D25 0315 0062; # (a◌̕◌̀◌֮◌ð´¥b; à◌֮◌ð´¥â—ŒÌ•b; a◌֮◌̀◌ð´¥â—ŒÌ•b; à◌֮◌ð´¥â—ŒÌ•b; a◌֮◌̀◌ð´¥â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HANIFI ROHINGYA SIGN TAHALA, LATIN SMALL LETTER B
+0061 10D25 0315 0300 05AE 0062;0061 05AE 10D25 0300 0315 0062;0061 05AE 10D25 0300 0315 0062;0061 05AE 10D25 0300 0315 0062;0061 05AE 10D25 0300 0315 0062; # (aâ—Œð´¥â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð´¥â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´¥â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´¥â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´¥â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, HANIFI ROHINGYA SIGN TAHALA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10D26 0062;00E0 05AE 10D26 0315 0062;0061 05AE 0300 10D26 0315 0062;00E0 05AE 10D26 0315 0062;0061 05AE 0300 10D26 0315 0062; # (a◌̕◌̀◌֮◌ð´¦b; à◌֮◌ð´¦â—ŒÌ•b; a◌֮◌̀◌ð´¦â—ŒÌ•b; à◌֮◌ð´¦â—ŒÌ•b; a◌֮◌̀◌ð´¦â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HANIFI ROHINGYA SIGN TANA, LATIN SMALL LETTER B
+0061 10D26 0315 0300 05AE 0062;0061 05AE 10D26 0300 0315 0062;0061 05AE 10D26 0300 0315 0062;0061 05AE 10D26 0300 0315 0062;0061 05AE 10D26 0300 0315 0062; # (aâ—Œð´¦â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð´¦â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´¦â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´¦â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´¦â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, HANIFI ROHINGYA SIGN TANA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10D27 0062;00E0 05AE 10D27 0315 0062;0061 05AE 0300 10D27 0315 0062;00E0 05AE 10D27 0315 0062;0061 05AE 0300 10D27 0315 0062; # (a◌̕◌̀◌֮◌ð´§b; à◌֮◌ð´§â—ŒÌ•b; a◌֮◌̀◌ð´§â—ŒÌ•b; à◌֮◌ð´§â—ŒÌ•b; a◌֮◌̀◌ð´§â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, HANIFI ROHINGYA SIGN TASSI, LATIN SMALL LETTER B
+0061 10D27 0315 0300 05AE 0062;0061 05AE 10D27 0300 0315 0062;0061 05AE 10D27 0300 0315 0062;0061 05AE 10D27 0300 0315 0062;0061 05AE 10D27 0300 0315 0062; # (aâ—Œð´§â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð´§â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´§â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´§â—ŒÌ€â—ŒÌ•b; a◌֮◌ð´§â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, HANIFI ROHINGYA SIGN TASSI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10EAB 0062;00E0 05AE 10EAB 0315 0062;0061 05AE 0300 10EAB 0315 0062;00E0 05AE 10EAB 0315 0062;0061 05AE 0300 10EAB 0315 0062; # (a◌̕◌̀◌֮◌ðº«b; à◌֮◌ðº«â—ŒÌ•b; a◌֮◌̀◌ðº«â—ŒÌ•b; à◌֮◌ðº«â—ŒÌ•b; a◌֮◌̀◌ðº«â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, YEZIDI COMBINING HAMZA MARK, LATIN SMALL LETTER B
+0061 10EAB 0315 0300 05AE 0062;0061 05AE 10EAB 0300 0315 0062;0061 05AE 10EAB 0300 0315 0062;0061 05AE 10EAB 0300 0315 0062;0061 05AE 10EAB 0300 0315 0062; # (aâ—Œðº«â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ðº«â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº«â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº«â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº«â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, YEZIDI COMBINING HAMZA MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10EAC 0062;00E0 05AE 10EAC 0315 0062;0061 05AE 0300 10EAC 0315 0062;00E0 05AE 10EAC 0315 0062;0061 05AE 0300 10EAC 0315 0062; # (a◌̕◌̀◌֮◌ðº¬b; à◌֮◌ðº¬â—ŒÌ•b; a◌֮◌̀◌ðº¬â—ŒÌ•b; à◌֮◌ðº¬â—ŒÌ•b; a◌֮◌̀◌ðº¬â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, YEZIDI COMBINING MADDA MARK, LATIN SMALL LETTER B
+0061 10EAC 0315 0300 05AE 0062;0061 05AE 10EAC 0300 0315 0062;0061 05AE 10EAC 0300 0315 0062;0061 05AE 10EAC 0300 0315 0062;0061 05AE 10EAC 0300 0315 0062; # (aâ—Œðº¬â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ðº¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº¬â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, YEZIDI COMBINING MADDA MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10F46 0062;0061 1DFA 0316 10F46 059A 0062;0061 1DFA 0316 10F46 059A 0062;0061 1DFA 0316 10F46 059A 0062;0061 1DFA 0316 10F46 059A 0062; # (a◌֚◌̖◌᷺◌ð½†b; a◌᷺◌̖◌ð½†â—ŒÖšb; a◌᷺◌̖◌ð½†â—ŒÖšb; a◌᷺◌̖◌ð½†â—ŒÖšb; a◌᷺◌̖◌ð½†â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SOGDIAN COMBINING DOT BELOW, LATIN SMALL LETTER B
+0061 10F46 059A 0316 1DFA 0062;0061 1DFA 10F46 0316 059A 0062;0061 1DFA 10F46 0316 059A 0062;0061 1DFA 10F46 0316 059A 0062;0061 1DFA 10F46 0316 059A 0062; # (aâ—Œð½†â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð½†â—ŒÌ–◌֚b; a◌᷺◌ð½†â—ŒÌ–◌֚b; a◌᷺◌ð½†â—ŒÌ–◌֚b; a◌᷺◌ð½†â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING DOT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10F47 0062;0061 1DFA 0316 10F47 059A 0062;0061 1DFA 0316 10F47 059A 0062;0061 1DFA 0316 10F47 059A 0062;0061 1DFA 0316 10F47 059A 0062; # (a◌֚◌̖◌᷺◌ð½‡b; a◌᷺◌̖◌ð½‡â—ŒÖšb; a◌᷺◌̖◌ð½‡â—ŒÖšb; a◌᷺◌̖◌ð½‡â—ŒÖšb; a◌᷺◌̖◌ð½‡â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SOGDIAN COMBINING TWO DOTS BELOW, LATIN SMALL LETTER B
+0061 10F47 059A 0316 1DFA 0062;0061 1DFA 10F47 0316 059A 0062;0061 1DFA 10F47 0316 059A 0062;0061 1DFA 10F47 0316 059A 0062;0061 1DFA 10F47 0316 059A 0062; # (aâ—Œð½‡â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð½‡â—ŒÌ–◌֚b; a◌᷺◌ð½‡â—ŒÌ–◌֚b; a◌᷺◌ð½‡â—ŒÌ–◌֚b; a◌᷺◌ð½‡â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING TWO DOTS BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10F48 0062;00E0 05AE 10F48 0315 0062;0061 05AE 0300 10F48 0315 0062;00E0 05AE 10F48 0315 0062;0061 05AE 0300 10F48 0315 0062; # (a◌̕◌̀◌֮◌ð½ˆb; à◌֮◌ð½ˆâ—ŒÌ•b; a◌֮◌̀◌ð½ˆâ—ŒÌ•b; à◌֮◌ð½ˆâ—ŒÌ•b; a◌֮◌̀◌ð½ˆâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SOGDIAN COMBINING DOT ABOVE, LATIN SMALL LETTER B
+0061 10F48 0315 0300 05AE 0062;0061 05AE 10F48 0300 0315 0062;0061 05AE 10F48 0300 0315 0062;0061 05AE 10F48 0300 0315 0062;0061 05AE 10F48 0300 0315 0062; # (aâ—Œð½ˆâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð½ˆâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð½ˆâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð½ˆâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð½ˆâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING DOT ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10F49 0062;00E0 05AE 10F49 0315 0062;0061 05AE 0300 10F49 0315 0062;00E0 05AE 10F49 0315 0062;0061 05AE 0300 10F49 0315 0062; # (a◌̕◌̀◌֮◌ð½‰b; à◌֮◌ð½‰â—ŒÌ•b; a◌֮◌̀◌ð½‰â—ŒÌ•b; à◌֮◌ð½‰â—ŒÌ•b; a◌֮◌̀◌ð½‰â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SOGDIAN COMBINING TWO DOTS ABOVE, LATIN SMALL LETTER B
+0061 10F49 0315 0300 05AE 0062;0061 05AE 10F49 0300 0315 0062;0061 05AE 10F49 0300 0315 0062;0061 05AE 10F49 0300 0315 0062;0061 05AE 10F49 0300 0315 0062; # (aâ—Œð½‰â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð½‰â—ŒÌ€â—ŒÌ•b; a◌֮◌ð½‰â—ŒÌ€â—ŒÌ•b; a◌֮◌ð½‰â—ŒÌ€â—ŒÌ•b; a◌֮◌ð½‰â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING TWO DOTS ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10F4A 0062;00E0 05AE 10F4A 0315 0062;0061 05AE 0300 10F4A 0315 0062;00E0 05AE 10F4A 0315 0062;0061 05AE 0300 10F4A 0315 0062; # (a◌̕◌̀◌֮◌ð½Šb; à◌֮◌ð½Šâ—ŒÌ•b; a◌֮◌̀◌ð½Šâ—ŒÌ•b; à◌֮◌ð½Šâ—ŒÌ•b; a◌֮◌̀◌ð½Šâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SOGDIAN COMBINING CURVE ABOVE, LATIN SMALL LETTER B
+0061 10F4A 0315 0300 05AE 0062;0061 05AE 10F4A 0300 0315 0062;0061 05AE 10F4A 0300 0315 0062;0061 05AE 10F4A 0300 0315 0062;0061 05AE 10F4A 0300 0315 0062; # (aâ—Œð½Šâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð½Šâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð½Šâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð½Šâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð½Šâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING CURVE ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10F4B 0062;0061 1DFA 0316 10F4B 059A 0062;0061 1DFA 0316 10F4B 059A 0062;0061 1DFA 0316 10F4B 059A 0062;0061 1DFA 0316 10F4B 059A 0062; # (a◌֚◌̖◌᷺◌ð½‹b; a◌᷺◌̖◌ð½‹â—ŒÖšb; a◌᷺◌̖◌ð½‹â—ŒÖšb; a◌᷺◌̖◌ð½‹â—ŒÖšb; a◌᷺◌̖◌ð½‹â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SOGDIAN COMBINING CURVE BELOW, LATIN SMALL LETTER B
+0061 10F4B 059A 0316 1DFA 0062;0061 1DFA 10F4B 0316 059A 0062;0061 1DFA 10F4B 0316 059A 0062;0061 1DFA 10F4B 0316 059A 0062;0061 1DFA 10F4B 0316 059A 0062; # (aâ—Œð½‹â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð½‹â—ŒÌ–◌֚b; a◌᷺◌ð½‹â—ŒÌ–◌֚b; a◌᷺◌ð½‹â—ŒÌ–◌֚b; a◌᷺◌ð½‹â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING CURVE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10F4C 0062;00E0 05AE 10F4C 0315 0062;0061 05AE 0300 10F4C 0315 0062;00E0 05AE 10F4C 0315 0062;0061 05AE 0300 10F4C 0315 0062; # (a◌̕◌̀◌֮◌ð½Œb; à◌֮◌ð½Œâ—ŒÌ•b; a◌֮◌̀◌ð½Œâ—ŒÌ•b; à◌֮◌ð½Œâ—ŒÌ•b; a◌֮◌̀◌ð½Œâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, SOGDIAN COMBINING HOOK ABOVE, LATIN SMALL LETTER B
+0061 10F4C 0315 0300 05AE 0062;0061 05AE 10F4C 0300 0315 0062;0061 05AE 10F4C 0300 0315 0062;0061 05AE 10F4C 0300 0315 0062;0061 05AE 10F4C 0300 0315 0062; # (aâ—Œð½Œâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð½Œâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð½Œâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð½Œâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð½Œâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING HOOK ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10F4D 0062;0061 1DFA 0316 10F4D 059A 0062;0061 1DFA 0316 10F4D 059A 0062;0061 1DFA 0316 10F4D 059A 0062;0061 1DFA 0316 10F4D 059A 0062; # (a◌֚◌̖◌᷺◌ð½b; a◌᷺◌̖◌ð½â—ŒÖšb; a◌᷺◌̖◌ð½â—ŒÖšb; a◌᷺◌̖◌ð½â—ŒÖšb; a◌᷺◌̖◌ð½â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SOGDIAN COMBINING HOOK BELOW, LATIN SMALL LETTER B
+0061 10F4D 059A 0316 1DFA 0062;0061 1DFA 10F4D 0316 059A 0062;0061 1DFA 10F4D 0316 059A 0062;0061 1DFA 10F4D 0316 059A 0062;0061 1DFA 10F4D 0316 059A 0062; # (aâ—Œð½â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð½â—ŒÌ–◌֚b; a◌᷺◌ð½â—ŒÌ–◌֚b; a◌᷺◌ð½â—ŒÌ–◌֚b; a◌᷺◌ð½â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING HOOK BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10F4E 0062;0061 1DFA 0316 10F4E 059A 0062;0061 1DFA 0316 10F4E 059A 0062;0061 1DFA 0316 10F4E 059A 0062;0061 1DFA 0316 10F4E 059A 0062; # (a◌֚◌̖◌᷺◌ð½Žb; a◌᷺◌̖◌ð½Žâ—ŒÖšb; a◌᷺◌̖◌ð½Žâ—ŒÖšb; a◌᷺◌̖◌ð½Žâ—ŒÖšb; a◌᷺◌̖◌ð½Žâ—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SOGDIAN COMBINING LONG HOOK BELOW, LATIN SMALL LETTER B
+0061 10F4E 059A 0316 1DFA 0062;0061 1DFA 10F4E 0316 059A 0062;0061 1DFA 10F4E 0316 059A 0062;0061 1DFA 10F4E 0316 059A 0062;0061 1DFA 10F4E 0316 059A 0062; # (aâ—Œð½Žâ—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð½Žâ—ŒÌ–◌֚b; a◌᷺◌ð½Žâ—ŒÌ–◌֚b; a◌᷺◌ð½Žâ—ŒÌ–◌֚b; a◌᷺◌ð½Žâ—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING LONG HOOK BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10F4F 0062;0061 1DFA 0316 10F4F 059A 0062;0061 1DFA 0316 10F4F 059A 0062;0061 1DFA 0316 10F4F 059A 0062;0061 1DFA 0316 10F4F 059A 0062; # (a◌֚◌̖◌᷺◌ð½b; a◌᷺◌̖◌ð½â—ŒÖšb; a◌᷺◌̖◌ð½â—ŒÖšb; a◌᷺◌̖◌ð½â—ŒÖšb; a◌᷺◌̖◌ð½â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SOGDIAN COMBINING RESH BELOW, LATIN SMALL LETTER B
+0061 10F4F 059A 0316 1DFA 0062;0061 1DFA 10F4F 0316 059A 0062;0061 1DFA 10F4F 0316 059A 0062;0061 1DFA 10F4F 0316 059A 0062;0061 1DFA 10F4F 0316 059A 0062; # (aâ—Œð½â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð½â—ŒÌ–◌֚b; a◌᷺◌ð½â—ŒÌ–◌֚b; a◌᷺◌ð½â—ŒÌ–◌֚b; a◌᷺◌ð½â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING RESH BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10F50 0062;0061 1DFA 0316 10F50 059A 0062;0061 1DFA 0316 10F50 059A 0062;0061 1DFA 0316 10F50 059A 0062;0061 1DFA 0316 10F50 059A 0062; # (a◌֚◌̖◌᷺◌ð½b; a◌᷺◌̖◌ð½â—ŒÖšb; a◌᷺◌̖◌ð½â—ŒÖšb; a◌᷺◌̖◌ð½â—ŒÖšb; a◌᷺◌̖◌ð½â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SOGDIAN COMBINING STROKE BELOW, LATIN SMALL LETTER B
+0061 10F50 059A 0316 1DFA 0062;0061 1DFA 10F50 0316 059A 0062;0061 1DFA 10F50 0316 059A 0062;0061 1DFA 10F50 0316 059A 0062;0061 1DFA 10F50 0316 059A 0062; # (aâ—Œð½â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð½â—ŒÌ–◌֚b; a◌᷺◌ð½â—ŒÌ–◌֚b; a◌᷺◌ð½â—ŒÌ–◌֚b; a◌᷺◌ð½â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING STROKE BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10F82 0062;00E0 05AE 10F82 0315 0062;0061 05AE 0300 10F82 0315 0062;00E0 05AE 10F82 0315 0062;0061 05AE 0300 10F82 0315 0062; # (a◌̕◌̀◌֮◌ð¾‚b; à◌֮◌ð¾‚◌̕b; a◌֮◌̀◌ð¾‚◌̕b; à◌֮◌ð¾‚◌̕b; a◌֮◌̀◌ð¾‚◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, OLD UYGHUR COMBINING DOT ABOVE, LATIN SMALL LETTER B
+0061 10F82 0315 0300 05AE 0062;0061 05AE 10F82 0300 0315 0062;0061 05AE 10F82 0300 0315 0062;0061 05AE 10F82 0300 0315 0062;0061 05AE 10F82 0300 0315 0062; # (aâ—Œð¾‚◌̕◌̀◌֮b; a◌֮◌ð¾‚◌̀◌̕b; a◌֮◌ð¾‚◌̀◌̕b; a◌֮◌ð¾‚◌̀◌̕b; a◌֮◌ð¾‚◌̀◌̕b; ) LATIN SMALL LETTER A, OLD UYGHUR COMBINING DOT ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10F83 0062;0061 1DFA 0316 10F83 059A 0062;0061 1DFA 0316 10F83 059A 0062;0061 1DFA 0316 10F83 059A 0062;0061 1DFA 0316 10F83 059A 0062; # (a◌֚◌̖◌᷺◌ð¾ƒb; a◌᷺◌̖◌ð¾ƒâ—ŒÖšb; a◌᷺◌̖◌ð¾ƒâ—ŒÖšb; a◌᷺◌̖◌ð¾ƒâ—ŒÖšb; a◌᷺◌̖◌ð¾ƒâ—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, OLD UYGHUR COMBINING DOT BELOW, LATIN SMALL LETTER B
+0061 10F83 059A 0316 1DFA 0062;0061 1DFA 10F83 0316 059A 0062;0061 1DFA 10F83 0316 059A 0062;0061 1DFA 10F83 0316 059A 0062;0061 1DFA 10F83 0316 059A 0062; # (aâ—Œð¾ƒâ—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð¾ƒâ—ŒÌ–◌֚b; a◌᷺◌ð¾ƒâ—ŒÌ–◌֚b; a◌᷺◌ð¾ƒâ—ŒÌ–◌֚b; a◌᷺◌ð¾ƒâ—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, OLD UYGHUR COMBINING DOT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 10F84 0062;00E0 05AE 10F84 0315 0062;0061 05AE 0300 10F84 0315 0062;00E0 05AE 10F84 0315 0062;0061 05AE 0300 10F84 0315 0062; # (a◌̕◌̀◌֮◌ð¾„b; à◌֮◌ð¾„◌̕b; a◌֮◌̀◌ð¾„◌̕b; à◌֮◌ð¾„◌̕b; a◌֮◌̀◌ð¾„◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, OLD UYGHUR COMBINING TWO DOTS ABOVE, LATIN SMALL LETTER B
+0061 10F84 0315 0300 05AE 0062;0061 05AE 10F84 0300 0315 0062;0061 05AE 10F84 0300 0315 0062;0061 05AE 10F84 0300 0315 0062;0061 05AE 10F84 0300 0315 0062; # (aâ—Œð¾„◌̕◌̀◌֮b; a◌֮◌ð¾„◌̀◌̕b; a◌֮◌ð¾„◌̀◌̕b; a◌֮◌ð¾„◌̀◌̕b; a◌֮◌ð¾„◌̀◌̕b; ) LATIN SMALL LETTER A, OLD UYGHUR COMBINING TWO DOTS ABOVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10F85 0062;0061 1DFA 0316 10F85 059A 0062;0061 1DFA 0316 10F85 059A 0062;0061 1DFA 0316 10F85 059A 0062;0061 1DFA 0316 10F85 059A 0062; # (a◌֚◌̖◌᷺◌ð¾…b; a◌᷺◌̖◌ð¾…◌֚b; a◌᷺◌̖◌ð¾…◌֚b; a◌᷺◌̖◌ð¾…◌֚b; a◌᷺◌̖◌ð¾…◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, OLD UYGHUR COMBINING TWO DOTS BELOW, LATIN SMALL LETTER B
+0061 10F85 059A 0316 1DFA 0062;0061 1DFA 10F85 0316 059A 0062;0061 1DFA 10F85 0316 059A 0062;0061 1DFA 10F85 0316 059A 0062;0061 1DFA 10F85 0316 059A 0062; # (aâ—Œð¾…◌֚◌̖◌᷺b; a◌᷺◌ð¾…◌̖◌֚b; a◌᷺◌ð¾…◌̖◌֚b; a◌᷺◌ð¾…◌̖◌֚b; a◌᷺◌ð¾…◌̖◌֚b; ) LATIN SMALL LETTER A, OLD UYGHUR COMBINING TWO DOTS BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11046 0062;0061 3099 094D 11046 05B0 0062;0061 3099 094D 11046 05B0 0062;0061 3099 094D 11046 05B0 0062;0061 3099 094D 11046 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘†b; a◌゙◌à¥â—Œð‘†â—ŒÖ°b; a◌゙◌à¥â—Œð‘†â—ŒÖ°b; a◌゙◌à¥â—Œð‘†â—ŒÖ°b; a◌゙◌à¥â—Œð‘†â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, BRAHMI VIRAMA, LATIN SMALL LETTER B
+0061 11046 05B0 094D 3099 0062;0061 3099 11046 094D 05B0 0062;0061 3099 11046 094D 05B0 0062;0061 3099 11046 094D 05B0 0062;0061 3099 11046 094D 05B0 0062; # (aâ—Œð‘†â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌ð‘†â—Œà¥â—ŒÖ°b; a◌゙◌ð‘†â—Œà¥â—ŒÖ°b; a◌゙◌ð‘†â—Œà¥â—ŒÖ°b; a◌゙◌ð‘†â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, BRAHMI VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11070 0062;0061 3099 094D 11070 05B0 0062;0061 3099 094D 11070 05B0 0062;0061 3099 094D 11070 05B0 0062;0061 3099 094D 11070 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘°b; a◌゙◌à¥â—Œð‘°â—ŒÖ°b; a◌゙◌à¥â—Œð‘°â—ŒÖ°b; a◌゙◌à¥â—Œð‘°â—ŒÖ°b; a◌゙◌à¥â—Œð‘°â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, BRAHMI SIGN OLD TAMIL VIRAMA, LATIN SMALL LETTER B
+0061 11070 05B0 094D 3099 0062;0061 3099 11070 094D 05B0 0062;0061 3099 11070 094D 05B0 0062;0061 3099 11070 094D 05B0 0062;0061 3099 11070 094D 05B0 0062; # (aâ—Œð‘°â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌ð‘°â—Œà¥â—ŒÖ°b; a◌゙◌ð‘°â—Œà¥â—ŒÖ°b; a◌゙◌ð‘°â—Œà¥â—ŒÖ°b; a◌゙◌ð‘°â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, BRAHMI SIGN OLD TAMIL VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1107F 0062;0061 3099 094D 1107F 05B0 0062;0061 3099 094D 1107F 05B0 0062;0061 3099 094D 1107F 05B0 0062;0061 3099 094D 1107F 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘¿b; a◌゙◌à¥â—Œð‘¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘¿â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, BRAHMI NUMBER JOINER, LATIN SMALL LETTER B
+0061 1107F 05B0 094D 3099 0062;0061 3099 1107F 094D 05B0 0062;0061 3099 1107F 094D 05B0 0062;0061 3099 1107F 094D 05B0 0062;0061 3099 1107F 094D 05B0 0062; # (aâ—Œð‘¿â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙◌ð‘¿â—Œà¥â—ŒÖ°b; a◌゙◌ð‘¿â—Œà¥â—ŒÖ°b; a◌゙◌ð‘¿â—Œà¥â—ŒÖ°b; a◌゙◌ð‘¿â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, BRAHMI NUMBER JOINER, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 110B9 0062;0061 3099 094D 110B9 05B0 0062;0061 3099 094D 110B9 05B0 0062;0061 3099 094D 110B9 05B0 0062;0061 3099 094D 110B9 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘‚¹b; a◌゙◌à¥â—Œð‘‚¹â—ŒÖ°b; a◌゙◌à¥â—Œð‘‚¹â—ŒÖ°b; a◌゙◌à¥â—Œð‘‚¹â—ŒÖ°b; a◌゙◌à¥â—Œð‘‚¹â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, KAITHI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 110B9 05B0 094D 3099 0062;0061 3099 110B9 094D 05B0 0062;0061 3099 110B9 094D 05B0 0062;0061 3099 110B9 094D 05B0 0062;0061 3099 110B9 094D 05B0 0062; # (a◌𑂹◌ְ◌à¥â—Œã‚™b; a◌゙◌𑂹◌à¥â—ŒÖ°b; a◌゙◌𑂹◌à¥â—ŒÖ°b; a◌゙◌𑂹◌à¥â—ŒÖ°b; a◌゙◌𑂹◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, KAITHI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 110BA 0062;0061 16FF0 093C 110BA 3099 0062;0061 16FF0 093C 110BA 3099 0062;0061 16FF0 093C 110BA 3099 0062;0061 16FF0 093C 110BA 3099 0062; # (a◌゙◌𖿰़◌𑂺b; a𖿰◌़◌𑂺◌゙b; a𖿰◌़◌𑂺◌゙b; a𖿰◌़◌𑂺◌゙b; a𖿰◌़◌𑂺◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, KAITHI SIGN NUKTA, LATIN SMALL LETTER B
+0061 110BA 3099 093C 16FF0 0062;0061 16FF0 110BA 093C 3099 0062;0061 16FF0 110BA 093C 3099 0062;0061 16FF0 110BA 093C 3099 0062;0061 16FF0 110BA 093C 3099 0062; # (a◌𑂺◌゙◌𖿰़b; a𖿰◌𑂺◌़◌゙b; a𖿰◌𑂺◌़◌゙b; a𖿰◌𑂺◌़◌゙b; a𖿰◌𑂺◌़◌゙b; ) LATIN SMALL LETTER A, KAITHI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11100 0062;00E0 05AE 11100 0315 0062;0061 05AE 0300 11100 0315 0062;00E0 05AE 11100 0315 0062;0061 05AE 0300 11100 0315 0062; # (a◌̕◌̀◌֮◌𑄀b; à◌֮◌𑄀◌̕b; a◌֮◌̀◌𑄀◌̕b; à◌֮◌𑄀◌̕b; a◌֮◌̀◌𑄀◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, CHAKMA SIGN CANDRABINDU, LATIN SMALL LETTER B
+0061 11100 0315 0300 05AE 0062;0061 05AE 11100 0300 0315 0062;0061 05AE 11100 0300 0315 0062;0061 05AE 11100 0300 0315 0062;0061 05AE 11100 0300 0315 0062; # (a◌𑄀◌̕◌̀◌֮b; a◌֮◌𑄀◌̀◌̕b; a◌֮◌𑄀◌̀◌̕b; a◌֮◌𑄀◌̀◌̕b; a◌֮◌𑄀◌̀◌̕b; ) LATIN SMALL LETTER A, CHAKMA SIGN CANDRABINDU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11101 0062;00E0 05AE 11101 0315 0062;0061 05AE 0300 11101 0315 0062;00E0 05AE 11101 0315 0062;0061 05AE 0300 11101 0315 0062; # (a◌̕◌̀◌֮◌ð‘„b; à◌֮◌ð‘„◌̕b; a◌֮◌̀◌ð‘„◌̕b; à◌֮◌ð‘„◌̕b; a◌֮◌̀◌ð‘„◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, CHAKMA SIGN ANUSVARA, LATIN SMALL LETTER B
+0061 11101 0315 0300 05AE 0062;0061 05AE 11101 0300 0315 0062;0061 05AE 11101 0300 0315 0062;0061 05AE 11101 0300 0315 0062;0061 05AE 11101 0300 0315 0062; # (aâ—Œð‘„◌̕◌̀◌֮b; a◌֮◌ð‘„◌̀◌̕b; a◌֮◌ð‘„◌̀◌̕b; a◌֮◌ð‘„◌̀◌̕b; a◌֮◌ð‘„◌̀◌̕b; ) LATIN SMALL LETTER A, CHAKMA SIGN ANUSVARA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11102 0062;00E0 05AE 11102 0315 0062;0061 05AE 0300 11102 0315 0062;00E0 05AE 11102 0315 0062;0061 05AE 0300 11102 0315 0062; # (a◌̕◌̀◌֮◌𑄂b; à◌֮◌𑄂◌̕b; a◌֮◌̀◌𑄂◌̕b; à◌֮◌𑄂◌̕b; a◌֮◌̀◌𑄂◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, CHAKMA SIGN VISARGA, LATIN SMALL LETTER B
+0061 11102 0315 0300 05AE 0062;0061 05AE 11102 0300 0315 0062;0061 05AE 11102 0300 0315 0062;0061 05AE 11102 0300 0315 0062;0061 05AE 11102 0300 0315 0062; # (a◌𑄂◌̕◌̀◌֮b; a◌֮◌𑄂◌̀◌̕b; a◌֮◌𑄂◌̀◌̕b; a◌֮◌𑄂◌̀◌̕b; a◌֮◌𑄂◌̀◌̕b; ) LATIN SMALL LETTER A, CHAKMA SIGN VISARGA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11133 0062;0061 3099 094D 11133 05B0 0062;0061 3099 094D 11133 05B0 0062;0061 3099 094D 11133 05B0 0062;0061 3099 094D 11133 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘„³b; a◌゙◌à¥â—Œð‘„³â—ŒÖ°b; a◌゙◌à¥â—Œð‘„³â—ŒÖ°b; a◌゙◌à¥â—Œð‘„³â—ŒÖ°b; a◌゙◌à¥â—Œð‘„³â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, CHAKMA VIRAMA, LATIN SMALL LETTER B
+0061 11133 05B0 094D 3099 0062;0061 3099 11133 094D 05B0 0062;0061 3099 11133 094D 05B0 0062;0061 3099 11133 094D 05B0 0062;0061 3099 11133 094D 05B0 0062; # (a◌𑄳◌ְ◌à¥â—Œã‚™b; a◌゙◌𑄳◌à¥â—ŒÖ°b; a◌゙◌𑄳◌à¥â—ŒÖ°b; a◌゙◌𑄳◌à¥â—ŒÖ°b; a◌゙◌𑄳◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, CHAKMA VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11134 0062;0061 3099 094D 11134 05B0 0062;0061 3099 094D 11134 05B0 0062;0061 3099 094D 11134 05B0 0062;0061 3099 094D 11134 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘„´b; a◌゙◌à¥â—Œð‘„´â—ŒÖ°b; a◌゙◌à¥â—Œð‘„´â—ŒÖ°b; a◌゙◌à¥â—Œð‘„´â—ŒÖ°b; a◌゙◌à¥â—Œð‘„´â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, CHAKMA MAAYYAA, LATIN SMALL LETTER B
+0061 11134 05B0 094D 3099 0062;0061 3099 11134 094D 05B0 0062;0061 3099 11134 094D 05B0 0062;0061 3099 11134 094D 05B0 0062;0061 3099 11134 094D 05B0 0062; # (a◌𑄴◌ְ◌à¥â—Œã‚™b; a◌゙◌𑄴◌à¥â—ŒÖ°b; a◌゙◌𑄴◌à¥â—ŒÖ°b; a◌゙◌𑄴◌à¥â—ŒÖ°b; a◌゙◌𑄴◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, CHAKMA MAAYYAA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 11173 0062;0061 16FF0 093C 11173 3099 0062;0061 16FF0 093C 11173 3099 0062;0061 16FF0 093C 11173 3099 0062;0061 16FF0 093C 11173 3099 0062; # (a◌゙◌𖿰़◌𑅳b; a𖿰◌़◌𑅳◌゙b; a𖿰◌़◌𑅳◌゙b; a𖿰◌़◌𑅳◌゙b; a𖿰◌़◌𑅳◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, MAHAJANI SIGN NUKTA, LATIN SMALL LETTER B
+0061 11173 3099 093C 16FF0 0062;0061 16FF0 11173 093C 3099 0062;0061 16FF0 11173 093C 3099 0062;0061 16FF0 11173 093C 3099 0062;0061 16FF0 11173 093C 3099 0062; # (a◌𑅳◌゙◌𖿰़b; a𖿰◌𑅳◌़◌゙b; a𖿰◌𑅳◌़◌゙b; a𖿰◌𑅳◌़◌゙b; a𖿰◌𑅳◌़◌゙b; ) LATIN SMALL LETTER A, MAHAJANI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 111C0 0062;0061 3099 094D 111C0 05B0 0062;0061 3099 094D 111C0 05B0 0062;0061 3099 094D 111C0 05B0 0062;0061 3099 094D 111C0 05B0 0062; # (a◌ְ◌à¥â—Œã‚™ð‘‡€b; a◌゙◌à¥ð‘‡€â—ŒÖ°b; a◌゙◌à¥ð‘‡€â—ŒÖ°b; a◌゙◌à¥ð‘‡€â—ŒÖ°b; a◌゙◌à¥ð‘‡€â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, SHARADA SIGN VIRAMA, LATIN SMALL LETTER B
+0061 111C0 05B0 094D 3099 0062;0061 3099 111C0 094D 05B0 0062;0061 3099 111C0 094D 05B0 0062;0061 3099 111C0 094D 05B0 0062;0061 3099 111C0 094D 05B0 0062; # (a𑇀◌ְ◌à¥â—Œã‚™b; a◌゙𑇀◌à¥â—ŒÖ°b; a◌゙𑇀◌à¥â—ŒÖ°b; a◌゙𑇀◌à¥â—ŒÖ°b; a◌゙𑇀◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, SHARADA SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 111CA 0062;0061 16FF0 093C 111CA 3099 0062;0061 16FF0 093C 111CA 3099 0062;0061 16FF0 093C 111CA 3099 0062;0061 16FF0 093C 111CA 3099 0062; # (a◌゙◌𖿰़◌𑇊b; a𖿰◌़◌𑇊◌゙b; a𖿰◌़◌𑇊◌゙b; a𖿰◌़◌𑇊◌゙b; a𖿰◌़◌𑇊◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, SHARADA SIGN NUKTA, LATIN SMALL LETTER B
+0061 111CA 3099 093C 16FF0 0062;0061 16FF0 111CA 093C 3099 0062;0061 16FF0 111CA 093C 3099 0062;0061 16FF0 111CA 093C 3099 0062;0061 16FF0 111CA 093C 3099 0062; # (a◌𑇊◌゙◌𖿰़b; a𖿰◌𑇊◌़◌゙b; a𖿰◌𑇊◌़◌゙b; a𖿰◌𑇊◌़◌゙b; a𖿰◌𑇊◌़◌゙b; ) LATIN SMALL LETTER A, SHARADA SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11235 0062;0061 3099 094D 11235 05B0 0062;0061 3099 094D 11235 05B0 0062;0061 3099 094D 11235 05B0 0062;0061 3099 094D 11235 05B0 0062; # (a◌ְ◌à¥â—Œã‚™ð‘ˆµb; a◌゙◌à¥ð‘ˆµâ—ŒÖ°b; a◌゙◌à¥ð‘ˆµâ—ŒÖ°b; a◌゙◌à¥ð‘ˆµâ—ŒÖ°b; a◌゙◌à¥ð‘ˆµâ—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, KHOJKI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 11235 05B0 094D 3099 0062;0061 3099 11235 094D 05B0 0062;0061 3099 11235 094D 05B0 0062;0061 3099 11235 094D 05B0 0062;0061 3099 11235 094D 05B0 0062; # (a𑈵◌ְ◌à¥â—Œã‚™b; a◌゙𑈵◌à¥â—ŒÖ°b; a◌゙𑈵◌à¥â—ŒÖ°b; a◌゙𑈵◌à¥â—ŒÖ°b; a◌゙𑈵◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, KHOJKI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 11236 0062;0061 16FF0 093C 11236 3099 0062;0061 16FF0 093C 11236 3099 0062;0061 16FF0 093C 11236 3099 0062;0061 16FF0 093C 11236 3099 0062; # (a◌゙◌𖿰़◌𑈶b; a𖿰◌़◌𑈶◌゙b; a𖿰◌़◌𑈶◌゙b; a𖿰◌़◌𑈶◌゙b; a𖿰◌़◌𑈶◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, KHOJKI SIGN NUKTA, LATIN SMALL LETTER B
+0061 11236 3099 093C 16FF0 0062;0061 16FF0 11236 093C 3099 0062;0061 16FF0 11236 093C 3099 0062;0061 16FF0 11236 093C 3099 0062;0061 16FF0 11236 093C 3099 0062; # (a◌𑈶◌゙◌𖿰़b; a𖿰◌𑈶◌़◌゙b; a𖿰◌𑈶◌़◌゙b; a𖿰◌𑈶◌़◌゙b; a𖿰◌𑈶◌़◌゙b; ) LATIN SMALL LETTER A, KHOJKI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 112E9 0062;0061 16FF0 093C 112E9 3099 0062;0061 16FF0 093C 112E9 3099 0062;0061 16FF0 093C 112E9 3099 0062;0061 16FF0 093C 112E9 3099 0062; # (a◌゙◌𖿰़◌𑋩b; a𖿰◌़◌𑋩◌゙b; a𖿰◌़◌𑋩◌゙b; a𖿰◌़◌𑋩◌゙b; a𖿰◌़◌𑋩◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, KHUDAWADI SIGN NUKTA, LATIN SMALL LETTER B
+0061 112E9 3099 093C 16FF0 0062;0061 16FF0 112E9 093C 3099 0062;0061 16FF0 112E9 093C 3099 0062;0061 16FF0 112E9 093C 3099 0062;0061 16FF0 112E9 093C 3099 0062; # (a◌𑋩◌゙◌𖿰़b; a𖿰◌𑋩◌़◌゙b; a𖿰◌𑋩◌़◌゙b; a𖿰◌𑋩◌़◌゙b; a𖿰◌𑋩◌़◌゙b; ) LATIN SMALL LETTER A, KHUDAWADI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 112EA 0062;0061 3099 094D 112EA 05B0 0062;0061 3099 094D 112EA 05B0 0062;0061 3099 094D 112EA 05B0 0062;0061 3099 094D 112EA 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘‹ªb; a◌゙◌à¥â—Œð‘‹ªâ—ŒÖ°b; a◌゙◌à¥â—Œð‘‹ªâ—ŒÖ°b; a◌゙◌à¥â—Œð‘‹ªâ—ŒÖ°b; a◌゙◌à¥â—Œð‘‹ªâ—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, KHUDAWADI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 112EA 05B0 094D 3099 0062;0061 3099 112EA 094D 05B0 0062;0061 3099 112EA 094D 05B0 0062;0061 3099 112EA 094D 05B0 0062;0061 3099 112EA 094D 05B0 0062; # (a◌𑋪◌ְ◌à¥â—Œã‚™b; a◌゙◌𑋪◌à¥â—ŒÖ°b; a◌゙◌𑋪◌à¥â—ŒÖ°b; a◌゙◌𑋪◌à¥â—ŒÖ°b; a◌゙◌𑋪◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, KHUDAWADI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 1133B 0062;0061 16FF0 093C 1133B 3099 0062;0061 16FF0 093C 1133B 3099 0062;0061 16FF0 093C 1133B 3099 0062;0061 16FF0 093C 1133B 3099 0062; # (a◌゙◌𖿰़◌𑌻b; a𖿰◌़◌𑌻◌゙b; a𖿰◌़◌𑌻◌゙b; a𖿰◌़◌𑌻◌゙b; a𖿰◌़◌𑌻◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING BINDU BELOW, LATIN SMALL LETTER B
+0061 1133B 3099 093C 16FF0 0062;0061 16FF0 1133B 093C 3099 0062;0061 16FF0 1133B 093C 3099 0062;0061 16FF0 1133B 093C 3099 0062;0061 16FF0 1133B 093C 3099 0062; # (a◌𑌻◌゙◌𖿰़b; a𖿰◌𑌻◌़◌゙b; a𖿰◌𑌻◌़◌゙b; a𖿰◌𑌻◌़◌゙b; a𖿰◌𑌻◌़◌゙b; ) LATIN SMALL LETTER A, COMBINING BINDU BELOW, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 1133C 0062;0061 16FF0 093C 1133C 3099 0062;0061 16FF0 093C 1133C 3099 0062;0061 16FF0 093C 1133C 3099 0062;0061 16FF0 093C 1133C 3099 0062; # (a◌゙◌𖿰़◌𑌼b; a𖿰◌़◌𑌼◌゙b; a𖿰◌़◌𑌼◌゙b; a𖿰◌़◌𑌼◌゙b; a𖿰◌़◌𑌼◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, GRANTHA SIGN NUKTA, LATIN SMALL LETTER B
+0061 1133C 3099 093C 16FF0 0062;0061 16FF0 1133C 093C 3099 0062;0061 16FF0 1133C 093C 3099 0062;0061 16FF0 1133C 093C 3099 0062;0061 16FF0 1133C 093C 3099 0062; # (a◌𑌼◌゙◌𖿰़b; a𖿰◌𑌼◌़◌゙b; a𖿰◌𑌼◌़◌゙b; a𖿰◌𑌼◌़◌゙b; a𖿰◌𑌼◌़◌゙b; ) LATIN SMALL LETTER A, GRANTHA SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1134D 0062;0061 3099 094D 1134D 05B0 0062;0061 3099 094D 1134D 05B0 0062;0061 3099 094D 1134D 05B0 0062;0061 3099 094D 1134D 05B0 0062; # (a◌ְ◌à¥â—Œã‚™ð‘b; a◌゙◌à¥ð‘◌ְb; a◌゙◌à¥ð‘◌ְb; a◌゙◌à¥ð‘◌ְb; a◌゙◌à¥ð‘◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, GRANTHA SIGN VIRAMA, LATIN SMALL LETTER B
+0061 1134D 05B0 094D 3099 0062;0061 3099 1134D 094D 05B0 0062;0061 3099 1134D 094D 05B0 0062;0061 3099 1134D 094D 05B0 0062;0061 3099 1134D 094D 05B0 0062; # (að‘◌ְ◌à¥â—Œã‚™b; a◌゙ð‘â—Œà¥â—ŒÖ°b; a◌゙ð‘â—Œà¥â—ŒÖ°b; a◌゙ð‘â—Œà¥â—ŒÖ°b; a◌゙ð‘â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, GRANTHA SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11366 0062;00E0 05AE 11366 0315 0062;0061 05AE 0300 11366 0315 0062;00E0 05AE 11366 0315 0062;0061 05AE 0300 11366 0315 0062; # (a◌̕◌̀◌֮◌ð‘¦b; à◌֮◌ð‘¦â—ŒÌ•b; a◌֮◌̀◌ð‘¦â—ŒÌ•b; à◌֮◌ð‘¦â—ŒÌ•b; a◌֮◌̀◌ð‘¦â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA DIGIT ZERO, LATIN SMALL LETTER B
+0061 11366 0315 0300 05AE 0062;0061 05AE 11366 0300 0315 0062;0061 05AE 11366 0300 0315 0062;0061 05AE 11366 0300 0315 0062;0061 05AE 11366 0300 0315 0062; # (aâ—Œð‘¦â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘¦â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘¦â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘¦â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘¦â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA DIGIT ZERO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11367 0062;00E0 05AE 11367 0315 0062;0061 05AE 0300 11367 0315 0062;00E0 05AE 11367 0315 0062;0061 05AE 0300 11367 0315 0062; # (a◌̕◌̀◌֮◌ð‘§b; à◌֮◌ð‘§â—ŒÌ•b; a◌֮◌̀◌ð‘§â—ŒÌ•b; à◌֮◌ð‘§â—ŒÌ•b; a◌֮◌̀◌ð‘§â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA DIGIT ONE, LATIN SMALL LETTER B
+0061 11367 0315 0300 05AE 0062;0061 05AE 11367 0300 0315 0062;0061 05AE 11367 0300 0315 0062;0061 05AE 11367 0300 0315 0062;0061 05AE 11367 0300 0315 0062; # (aâ—Œð‘§â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘§â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘§â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘§â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘§â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA DIGIT ONE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11368 0062;00E0 05AE 11368 0315 0062;0061 05AE 0300 11368 0315 0062;00E0 05AE 11368 0315 0062;0061 05AE 0300 11368 0315 0062; # (a◌̕◌̀◌֮◌ð‘¨b; à◌֮◌ð‘¨â—ŒÌ•b; a◌֮◌̀◌ð‘¨â—ŒÌ•b; à◌֮◌ð‘¨â—ŒÌ•b; a◌֮◌̀◌ð‘¨â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA DIGIT TWO, LATIN SMALL LETTER B
+0061 11368 0315 0300 05AE 0062;0061 05AE 11368 0300 0315 0062;0061 05AE 11368 0300 0315 0062;0061 05AE 11368 0300 0315 0062;0061 05AE 11368 0300 0315 0062; # (aâ—Œð‘¨â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘¨â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘¨â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘¨â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘¨â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA DIGIT TWO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11369 0062;00E0 05AE 11369 0315 0062;0061 05AE 0300 11369 0315 0062;00E0 05AE 11369 0315 0062;0061 05AE 0300 11369 0315 0062; # (a◌̕◌̀◌֮◌ð‘©b; à◌֮◌ð‘©â—ŒÌ•b; a◌֮◌̀◌ð‘©â—ŒÌ•b; à◌֮◌ð‘©â—ŒÌ•b; a◌֮◌̀◌ð‘©â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA DIGIT THREE, LATIN SMALL LETTER B
+0061 11369 0315 0300 05AE 0062;0061 05AE 11369 0300 0315 0062;0061 05AE 11369 0300 0315 0062;0061 05AE 11369 0300 0315 0062;0061 05AE 11369 0300 0315 0062; # (aâ—Œð‘©â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘©â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘©â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘©â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘©â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA DIGIT THREE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1136A 0062;00E0 05AE 1136A 0315 0062;0061 05AE 0300 1136A 0315 0062;00E0 05AE 1136A 0315 0062;0061 05AE 0300 1136A 0315 0062; # (a◌̕◌̀◌֮◌ð‘ªb; à◌֮◌ð‘ªâ—ŒÌ•b; a◌֮◌̀◌ð‘ªâ—ŒÌ•b; à◌֮◌ð‘ªâ—ŒÌ•b; a◌֮◌̀◌ð‘ªâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA DIGIT FOUR, LATIN SMALL LETTER B
+0061 1136A 0315 0300 05AE 0062;0061 05AE 1136A 0300 0315 0062;0061 05AE 1136A 0300 0315 0062;0061 05AE 1136A 0300 0315 0062;0061 05AE 1136A 0300 0315 0062; # (aâ—Œð‘ªâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘ªâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘ªâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘ªâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘ªâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA DIGIT FOUR, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1136B 0062;00E0 05AE 1136B 0315 0062;0061 05AE 0300 1136B 0315 0062;00E0 05AE 1136B 0315 0062;0061 05AE 0300 1136B 0315 0062; # (a◌̕◌̀◌֮◌ð‘«b; à◌֮◌ð‘«â—ŒÌ•b; a◌֮◌̀◌ð‘«â—ŒÌ•b; à◌֮◌ð‘«â—ŒÌ•b; a◌֮◌̀◌ð‘«â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA DIGIT FIVE, LATIN SMALL LETTER B
+0061 1136B 0315 0300 05AE 0062;0061 05AE 1136B 0300 0315 0062;0061 05AE 1136B 0300 0315 0062;0061 05AE 1136B 0300 0315 0062;0061 05AE 1136B 0300 0315 0062; # (aâ—Œð‘«â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘«â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘«â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘«â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘«â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA DIGIT FIVE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1136C 0062;00E0 05AE 1136C 0315 0062;0061 05AE 0300 1136C 0315 0062;00E0 05AE 1136C 0315 0062;0061 05AE 0300 1136C 0315 0062; # (a◌̕◌̀◌֮◌ð‘¬b; à◌֮◌ð‘¬â—ŒÌ•b; a◌֮◌̀◌ð‘¬â—ŒÌ•b; à◌֮◌ð‘¬â—ŒÌ•b; a◌֮◌̀◌ð‘¬â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA DIGIT SIX, LATIN SMALL LETTER B
+0061 1136C 0315 0300 05AE 0062;0061 05AE 1136C 0300 0315 0062;0061 05AE 1136C 0300 0315 0062;0061 05AE 1136C 0300 0315 0062;0061 05AE 1136C 0300 0315 0062; # (aâ—Œð‘¬â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘¬â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA DIGIT SIX, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11370 0062;00E0 05AE 11370 0315 0062;0061 05AE 0300 11370 0315 0062;00E0 05AE 11370 0315 0062;0061 05AE 0300 11370 0315 0062; # (a◌̕◌̀◌֮◌ð‘°b; à◌֮◌ð‘°â—ŒÌ•b; a◌֮◌̀◌ð‘°â—ŒÌ•b; à◌֮◌ð‘°â—ŒÌ•b; a◌֮◌̀◌ð‘°â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA LETTER A, LATIN SMALL LETTER B
+0061 11370 0315 0300 05AE 0062;0061 05AE 11370 0300 0315 0062;0061 05AE 11370 0300 0315 0062;0061 05AE 11370 0300 0315 0062;0061 05AE 11370 0300 0315 0062; # (aâ—Œð‘°â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘°â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘°â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘°â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘°â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11371 0062;00E0 05AE 11371 0315 0062;0061 05AE 0300 11371 0315 0062;00E0 05AE 11371 0315 0062;0061 05AE 0300 11371 0315 0062; # (a◌̕◌̀◌֮◌ð‘±b; à◌֮◌ð‘±â—ŒÌ•b; a◌֮◌̀◌ð‘±â—ŒÌ•b; à◌֮◌ð‘±â—ŒÌ•b; a◌֮◌̀◌ð‘±â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA LETTER KA, LATIN SMALL LETTER B
+0061 11371 0315 0300 05AE 0062;0061 05AE 11371 0300 0315 0062;0061 05AE 11371 0300 0315 0062;0061 05AE 11371 0300 0315 0062;0061 05AE 11371 0300 0315 0062; # (aâ—Œð‘±â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘±â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘±â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘±â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘±â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA LETTER KA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11372 0062;00E0 05AE 11372 0315 0062;0061 05AE 0300 11372 0315 0062;00E0 05AE 11372 0315 0062;0061 05AE 0300 11372 0315 0062; # (a◌̕◌̀◌֮◌ð‘²b; à◌֮◌ð‘²â—ŒÌ•b; a◌֮◌̀◌ð‘²â—ŒÌ•b; à◌֮◌ð‘²â—ŒÌ•b; a◌֮◌̀◌ð‘²â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA LETTER NA, LATIN SMALL LETTER B
+0061 11372 0315 0300 05AE 0062;0061 05AE 11372 0300 0315 0062;0061 05AE 11372 0300 0315 0062;0061 05AE 11372 0300 0315 0062;0061 05AE 11372 0300 0315 0062; # (aâ—Œð‘²â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘²â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘²â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘²â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘²â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA LETTER NA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11373 0062;00E0 05AE 11373 0315 0062;0061 05AE 0300 11373 0315 0062;00E0 05AE 11373 0315 0062;0061 05AE 0300 11373 0315 0062; # (a◌̕◌̀◌֮◌ð‘³b; à◌֮◌ð‘³â—ŒÌ•b; a◌֮◌̀◌ð‘³â—ŒÌ•b; à◌֮◌ð‘³â—ŒÌ•b; a◌֮◌̀◌ð‘³â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA LETTER VI, LATIN SMALL LETTER B
+0061 11373 0315 0300 05AE 0062;0061 05AE 11373 0300 0315 0062;0061 05AE 11373 0300 0315 0062;0061 05AE 11373 0300 0315 0062;0061 05AE 11373 0300 0315 0062; # (aâ—Œð‘³â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘³â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘³â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘³â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘³â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA LETTER VI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 11374 0062;00E0 05AE 11374 0315 0062;0061 05AE 0300 11374 0315 0062;00E0 05AE 11374 0315 0062;0061 05AE 0300 11374 0315 0062; # (a◌̕◌̀◌֮◌ð‘´b; à◌֮◌ð‘´â—ŒÌ•b; a◌֮◌̀◌ð‘´â—ŒÌ•b; à◌֮◌ð‘´â—ŒÌ•b; a◌֮◌̀◌ð‘´â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GRANTHA LETTER PA, LATIN SMALL LETTER B
+0061 11374 0315 0300 05AE 0062;0061 05AE 11374 0300 0315 0062;0061 05AE 11374 0300 0315 0062;0061 05AE 11374 0300 0315 0062;0061 05AE 11374 0300 0315 0062; # (aâ—Œð‘´â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‘´â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘´â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘´â—ŒÌ€â—ŒÌ•b; a◌֮◌ð‘´â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GRANTHA LETTER PA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11442 0062;0061 3099 094D 11442 05B0 0062;0061 3099 094D 11442 05B0 0062;0061 3099 094D 11442 05B0 0062;0061 3099 094D 11442 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘‘‚b; a◌゙◌à¥â—Œð‘‘‚◌ְb; a◌゙◌à¥â—Œð‘‘‚◌ְb; a◌゙◌à¥â—Œð‘‘‚◌ְb; a◌゙◌à¥â—Œð‘‘‚◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, NEWA SIGN VIRAMA, LATIN SMALL LETTER B
+0061 11442 05B0 094D 3099 0062;0061 3099 11442 094D 05B0 0062;0061 3099 11442 094D 05B0 0062;0061 3099 11442 094D 05B0 0062;0061 3099 11442 094D 05B0 0062; # (a◌𑑂◌ְ◌à¥â—Œã‚™b; a◌゙◌𑑂◌à¥â—ŒÖ°b; a◌゙◌𑑂◌à¥â—ŒÖ°b; a◌゙◌𑑂◌à¥â—ŒÖ°b; a◌゙◌𑑂◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, NEWA SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 11446 0062;0061 16FF0 093C 11446 3099 0062;0061 16FF0 093C 11446 3099 0062;0061 16FF0 093C 11446 3099 0062;0061 16FF0 093C 11446 3099 0062; # (a◌゙◌𖿰़◌𑑆b; a𖿰◌़◌𑑆◌゙b; a𖿰◌़◌𑑆◌゙b; a𖿰◌़◌𑑆◌゙b; a𖿰◌़◌𑑆◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, NEWA SIGN NUKTA, LATIN SMALL LETTER B
+0061 11446 3099 093C 16FF0 0062;0061 16FF0 11446 093C 3099 0062;0061 16FF0 11446 093C 3099 0062;0061 16FF0 11446 093C 3099 0062;0061 16FF0 11446 093C 3099 0062; # (a◌𑑆◌゙◌𖿰़b; a𖿰◌𑑆◌़◌゙b; a𖿰◌𑑆◌़◌゙b; a𖿰◌𑑆◌़◌゙b; a𖿰◌𑑆◌़◌゙b; ) LATIN SMALL LETTER A, NEWA SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1145E 0062;00E0 05AE 1145E 0315 0062;0061 05AE 0300 1145E 0315 0062;00E0 05AE 1145E 0315 0062;0061 05AE 0300 1145E 0315 0062; # (a◌̕◌̀◌֮◌𑑞b; à◌֮◌𑑞◌̕b; a◌֮◌̀◌𑑞◌̕b; à◌֮◌𑑞◌̕b; a◌֮◌̀◌𑑞◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NEWA SANDHI MARK, LATIN SMALL LETTER B
+0061 1145E 0315 0300 05AE 0062;0061 05AE 1145E 0300 0315 0062;0061 05AE 1145E 0300 0315 0062;0061 05AE 1145E 0300 0315 0062;0061 05AE 1145E 0300 0315 0062; # (a◌𑑞◌̕◌̀◌֮b; a◌֮◌𑑞◌̀◌̕b; a◌֮◌𑑞◌̀◌̕b; a◌֮◌𑑞◌̀◌̕b; a◌֮◌𑑞◌̀◌̕b; ) LATIN SMALL LETTER A, NEWA SANDHI MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 05B0 094D 3099 114C2 0062;0061 3099 094D 114C2 05B0 0062;0061 3099 094D 114C2 05B0 0062;0061 3099 094D 114C2 05B0 0062;0061 3099 094D 114C2 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘“‚b; a◌゙◌à¥â—Œð‘“‚◌ְb; a◌゙◌à¥â—Œð‘“‚◌ְb; a◌゙◌à¥â—Œð‘“‚◌ְb; a◌゙◌à¥â—Œð‘“‚◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, TIRHUTA SIGN VIRAMA, LATIN SMALL LETTER B
+0061 114C2 05B0 094D 3099 0062;0061 3099 114C2 094D 05B0 0062;0061 3099 114C2 094D 05B0 0062;0061 3099 114C2 094D 05B0 0062;0061 3099 114C2 094D 05B0 0062; # (a◌𑓂◌ְ◌à¥â—Œã‚™b; a◌゙◌𑓂◌à¥â—ŒÖ°b; a◌゙◌𑓂◌à¥â—ŒÖ°b; a◌゙◌𑓂◌à¥â—ŒÖ°b; a◌゙◌𑓂◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, TIRHUTA SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 114C3 0062;0061 16FF0 093C 114C3 3099 0062;0061 16FF0 093C 114C3 3099 0062;0061 16FF0 093C 114C3 3099 0062;0061 16FF0 093C 114C3 3099 0062; # (a◌゙◌𖿰़◌𑓃b; a𖿰◌़◌𑓃◌゙b; a𖿰◌़◌𑓃◌゙b; a𖿰◌़◌𑓃◌゙b; a𖿰◌़◌𑓃◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, TIRHUTA SIGN NUKTA, LATIN SMALL LETTER B
+0061 114C3 3099 093C 16FF0 0062;0061 16FF0 114C3 093C 3099 0062;0061 16FF0 114C3 093C 3099 0062;0061 16FF0 114C3 093C 3099 0062;0061 16FF0 114C3 093C 3099 0062; # (a◌𑓃◌゙◌𖿰़b; a𖿰◌𑓃◌़◌゙b; a𖿰◌𑓃◌़◌゙b; a𖿰◌𑓃◌़◌゙b; a𖿰◌𑓃◌़◌゙b; ) LATIN SMALL LETTER A, TIRHUTA SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 115BF 0062;0061 3099 094D 115BF 05B0 0062;0061 3099 094D 115BF 05B0 0062;0061 3099 094D 115BF 05B0 0062;0061 3099 094D 115BF 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘–¿b; a◌゙◌à¥â—Œð‘–¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘–¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘–¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘–¿â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, SIDDHAM SIGN VIRAMA, LATIN SMALL LETTER B
+0061 115BF 05B0 094D 3099 0062;0061 3099 115BF 094D 05B0 0062;0061 3099 115BF 094D 05B0 0062;0061 3099 115BF 094D 05B0 0062;0061 3099 115BF 094D 05B0 0062; # (a◌𑖿◌ְ◌à¥â—Œã‚™b; a◌゙◌𑖿◌à¥â—ŒÖ°b; a◌゙◌𑖿◌à¥â—ŒÖ°b; a◌゙◌𑖿◌à¥â—ŒÖ°b; a◌゙◌𑖿◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, SIDDHAM SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 115C0 0062;0061 16FF0 093C 115C0 3099 0062;0061 16FF0 093C 115C0 3099 0062;0061 16FF0 093C 115C0 3099 0062;0061 16FF0 093C 115C0 3099 0062; # (a◌゙◌𖿰़◌𑗀b; a𖿰◌़◌𑗀◌゙b; a𖿰◌़◌𑗀◌゙b; a𖿰◌़◌𑗀◌゙b; a𖿰◌़◌𑗀◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, SIDDHAM SIGN NUKTA, LATIN SMALL LETTER B
+0061 115C0 3099 093C 16FF0 0062;0061 16FF0 115C0 093C 3099 0062;0061 16FF0 115C0 093C 3099 0062;0061 16FF0 115C0 093C 3099 0062;0061 16FF0 115C0 093C 3099 0062; # (a◌𑗀◌゙◌𖿰़b; a𖿰◌𑗀◌़◌゙b; a𖿰◌𑗀◌़◌゙b; a𖿰◌𑗀◌़◌゙b; a𖿰◌𑗀◌़◌゙b; ) LATIN SMALL LETTER A, SIDDHAM SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1163F 0062;0061 3099 094D 1163F 05B0 0062;0061 3099 094D 1163F 05B0 0062;0061 3099 094D 1163F 05B0 0062;0061 3099 094D 1163F 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘˜¿b; a◌゙◌à¥â—Œð‘˜¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘˜¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘˜¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘˜¿â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, MODI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 1163F 05B0 094D 3099 0062;0061 3099 1163F 094D 05B0 0062;0061 3099 1163F 094D 05B0 0062;0061 3099 1163F 094D 05B0 0062;0061 3099 1163F 094D 05B0 0062; # (a◌𑘿◌ְ◌à¥â—Œã‚™b; a◌゙◌𑘿◌à¥â—ŒÖ°b; a◌゙◌𑘿◌à¥â—ŒÖ°b; a◌゙◌𑘿◌à¥â—ŒÖ°b; a◌゙◌𑘿◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MODI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 116B6 0062;0061 3099 094D 116B6 05B0 0062;0061 3099 094D 116B6 05B0 0062;0061 3099 094D 116B6 05B0 0062;0061 3099 094D 116B6 05B0 0062; # (a◌ְ◌à¥â—Œã‚™ð‘š¶b; a◌゙◌à¥ð‘š¶â—ŒÖ°b; a◌゙◌à¥ð‘š¶â—ŒÖ°b; a◌゙◌à¥ð‘š¶â—ŒÖ°b; a◌゙◌à¥ð‘š¶â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, TAKRI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 116B6 05B0 094D 3099 0062;0061 3099 116B6 094D 05B0 0062;0061 3099 116B6 094D 05B0 0062;0061 3099 116B6 094D 05B0 0062;0061 3099 116B6 094D 05B0 0062; # (a𑚶◌ְ◌à¥â—Œã‚™b; a◌゙𑚶◌à¥â—ŒÖ°b; a◌゙𑚶◌à¥â—ŒÖ°b; a◌゙𑚶◌à¥â—ŒÖ°b; a◌゙𑚶◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, TAKRI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 116B7 0062;0061 16FF0 093C 116B7 3099 0062;0061 16FF0 093C 116B7 3099 0062;0061 16FF0 093C 116B7 3099 0062;0061 16FF0 093C 116B7 3099 0062; # (a◌゙◌𖿰़◌𑚷b; a𖿰◌़◌𑚷◌゙b; a𖿰◌़◌𑚷◌゙b; a𖿰◌़◌𑚷◌゙b; a𖿰◌़◌𑚷◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, TAKRI SIGN NUKTA, LATIN SMALL LETTER B
+0061 116B7 3099 093C 16FF0 0062;0061 16FF0 116B7 093C 3099 0062;0061 16FF0 116B7 093C 3099 0062;0061 16FF0 116B7 093C 3099 0062;0061 16FF0 116B7 093C 3099 0062; # (a◌𑚷◌゙◌𖿰़b; a𖿰◌𑚷◌़◌゙b; a𖿰◌𑚷◌़◌゙b; a𖿰◌𑚷◌़◌゙b; a𖿰◌𑚷◌़◌゙b; ) LATIN SMALL LETTER A, TAKRI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1172B 0062;0061 3099 094D 1172B 05B0 0062;0061 3099 094D 1172B 05B0 0062;0061 3099 094D 1172B 05B0 0062;0061 3099 094D 1172B 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘œ«b; a◌゙◌à¥â—Œð‘œ«â—ŒÖ°b; a◌゙◌à¥â—Œð‘œ«â—ŒÖ°b; a◌゙◌à¥â—Œð‘œ«â—ŒÖ°b; a◌゙◌à¥â—Œð‘œ«â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, AHOM SIGN KILLER, LATIN SMALL LETTER B
+0061 1172B 05B0 094D 3099 0062;0061 3099 1172B 094D 05B0 0062;0061 3099 1172B 094D 05B0 0062;0061 3099 1172B 094D 05B0 0062;0061 3099 1172B 094D 05B0 0062; # (a◌𑜫◌ְ◌à¥â—Œã‚™b; a◌゙◌𑜫◌à¥â—ŒÖ°b; a◌゙◌𑜫◌à¥â—ŒÖ°b; a◌゙◌𑜫◌à¥â—ŒÖ°b; a◌゙◌𑜫◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, AHOM SIGN KILLER, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11839 0062;0061 3099 094D 11839 05B0 0062;0061 3099 094D 11839 05B0 0062;0061 3099 094D 11839 05B0 0062;0061 3099 094D 11839 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘ ¹b; a◌゙◌à¥â—Œð‘ ¹â—ŒÖ°b; a◌゙◌à¥â—Œð‘ ¹â—ŒÖ°b; a◌゙◌à¥â—Œð‘ ¹â—ŒÖ°b; a◌゙◌à¥â—Œð‘ ¹â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DOGRA SIGN VIRAMA, LATIN SMALL LETTER B
+0061 11839 05B0 094D 3099 0062;0061 3099 11839 094D 05B0 0062;0061 3099 11839 094D 05B0 0062;0061 3099 11839 094D 05B0 0062;0061 3099 11839 094D 05B0 0062; # (a◌𑠹◌ְ◌à¥â—Œã‚™b; a◌゙◌𑠹◌à¥â—ŒÖ°b; a◌゙◌𑠹◌à¥â—ŒÖ°b; a◌゙◌𑠹◌à¥â—ŒÖ°b; a◌゙◌𑠹◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, DOGRA SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 1183A 0062;0061 16FF0 093C 1183A 3099 0062;0061 16FF0 093C 1183A 3099 0062;0061 16FF0 093C 1183A 3099 0062;0061 16FF0 093C 1183A 3099 0062; # (a◌゙◌𖿰़◌𑠺b; a𖿰◌़◌𑠺◌゙b; a𖿰◌़◌𑠺◌゙b; a𖿰◌़◌𑠺◌゙b; a𖿰◌़◌𑠺◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, DOGRA SIGN NUKTA, LATIN SMALL LETTER B
+0061 1183A 3099 093C 16FF0 0062;0061 16FF0 1183A 093C 3099 0062;0061 16FF0 1183A 093C 3099 0062;0061 16FF0 1183A 093C 3099 0062;0061 16FF0 1183A 093C 3099 0062; # (a◌𑠺◌゙◌𖿰़b; a𖿰◌𑠺◌़◌゙b; a𖿰◌𑠺◌़◌゙b; a𖿰◌𑠺◌़◌゙b; a𖿰◌𑠺◌़◌゙b; ) LATIN SMALL LETTER A, DOGRA SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1193D 0062;0061 3099 094D 1193D 05B0 0062;0061 3099 094D 1193D 05B0 0062;0061 3099 094D 1193D 05B0 0062;0061 3099 094D 1193D 05B0 0062; # (a◌ְ◌à¥â—Œã‚™ð‘¤½b; a◌゙◌à¥ð‘¤½â—ŒÖ°b; a◌゙◌à¥ð‘¤½â—ŒÖ°b; a◌゙◌à¥ð‘¤½â—ŒÖ°b; a◌゙◌à¥ð‘¤½â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DIVES AKURU SIGN HALANTA, LATIN SMALL LETTER B
+0061 1193D 05B0 094D 3099 0062;0061 3099 1193D 094D 05B0 0062;0061 3099 1193D 094D 05B0 0062;0061 3099 1193D 094D 05B0 0062;0061 3099 1193D 094D 05B0 0062; # (a𑤽◌ְ◌à¥â—Œã‚™b; a◌゙𑤽◌à¥â—ŒÖ°b; a◌゙𑤽◌à¥â—ŒÖ°b; a◌゙𑤽◌à¥â—ŒÖ°b; a◌゙𑤽◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, DIVES AKURU SIGN HALANTA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 1193E 0062;0061 3099 094D 1193E 05B0 0062;0061 3099 094D 1193E 05B0 0062;0061 3099 094D 1193E 05B0 0062;0061 3099 094D 1193E 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘¤¾b; a◌゙◌à¥â—Œð‘¤¾â—ŒÖ°b; a◌゙◌à¥â—Œð‘¤¾â—ŒÖ°b; a◌゙◌à¥â—Œð‘¤¾â—ŒÖ°b; a◌゙◌à¥â—Œð‘¤¾â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DIVES AKURU VIRAMA, LATIN SMALL LETTER B
+0061 1193E 05B0 094D 3099 0062;0061 3099 1193E 094D 05B0 0062;0061 3099 1193E 094D 05B0 0062;0061 3099 1193E 094D 05B0 0062;0061 3099 1193E 094D 05B0 0062; # (a◌𑤾◌ְ◌à¥â—Œã‚™b; a◌゙◌𑤾◌à¥â—ŒÖ°b; a◌゙◌𑤾◌à¥â—ŒÖ°b; a◌゙◌𑤾◌à¥â—ŒÖ°b; a◌゙◌𑤾◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, DIVES AKURU VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 11943 0062;0061 16FF0 093C 11943 3099 0062;0061 16FF0 093C 11943 3099 0062;0061 16FF0 093C 11943 3099 0062;0061 16FF0 093C 11943 3099 0062; # (a◌゙◌𖿰़◌𑥃b; a𖿰◌़◌𑥃◌゙b; a𖿰◌़◌𑥃◌゙b; a𖿰◌़◌𑥃◌゙b; a𖿰◌़◌𑥃◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, DIVES AKURU SIGN NUKTA, LATIN SMALL LETTER B
+0061 11943 3099 093C 16FF0 0062;0061 16FF0 11943 093C 3099 0062;0061 16FF0 11943 093C 3099 0062;0061 16FF0 11943 093C 3099 0062;0061 16FF0 11943 093C 3099 0062; # (a◌𑥃◌゙◌𖿰़b; a𖿰◌𑥃◌़◌゙b; a𖿰◌𑥃◌़◌゙b; a𖿰◌𑥃◌़◌゙b; a𖿰◌𑥃◌़◌゙b; ) LATIN SMALL LETTER A, DIVES AKURU SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 119E0 0062;0061 3099 094D 119E0 05B0 0062;0061 3099 094D 119E0 05B0 0062;0061 3099 094D 119E0 05B0 0062;0061 3099 094D 119E0 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘§ b; a◌゙◌à¥â—Œð‘§ â—ŒÖ°b; a◌゙◌à¥â—Œð‘§ â—ŒÖ°b; a◌゙◌à¥â—Œð‘§ â—ŒÖ°b; a◌゙◌à¥â—Œð‘§ â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, NANDINAGARI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 119E0 05B0 094D 3099 0062;0061 3099 119E0 094D 05B0 0062;0061 3099 119E0 094D 05B0 0062;0061 3099 119E0 094D 05B0 0062;0061 3099 119E0 094D 05B0 0062; # (a◌𑧠◌ְ◌à¥â—Œã‚™b; a◌゙◌𑧠◌à¥â—ŒÖ°b; a◌゙◌𑧠◌à¥â—ŒÖ°b; a◌゙◌𑧠◌à¥â—ŒÖ°b; a◌゙◌𑧠◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, NANDINAGARI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11A34 0062;0061 3099 094D 11A34 05B0 0062;0061 3099 094D 11A34 05B0 0062;0061 3099 094D 11A34 05B0 0062;0061 3099 094D 11A34 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘¨´b; a◌゙◌à¥â—Œð‘¨´â—ŒÖ°b; a◌゙◌à¥â—Œð‘¨´â—ŒÖ°b; a◌゙◌à¥â—Œð‘¨´â—ŒÖ°b; a◌゙◌à¥â—Œð‘¨´â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, ZANABAZAR SQUARE SIGN VIRAMA, LATIN SMALL LETTER B
+0061 11A34 05B0 094D 3099 0062;0061 3099 11A34 094D 05B0 0062;0061 3099 11A34 094D 05B0 0062;0061 3099 11A34 094D 05B0 0062;0061 3099 11A34 094D 05B0 0062; # (a◌𑨴◌ְ◌à¥â—Œã‚™b; a◌゙◌𑨴◌à¥â—ŒÖ°b; a◌゙◌𑨴◌à¥â—ŒÖ°b; a◌゙◌𑨴◌à¥â—ŒÖ°b; a◌゙◌𑨴◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, ZANABAZAR SQUARE SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11A47 0062;0061 3099 094D 11A47 05B0 0062;0061 3099 094D 11A47 05B0 0062;0061 3099 094D 11A47 05B0 0062;0061 3099 094D 11A47 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘©‡b; a◌゙◌à¥â—Œð‘©‡â—ŒÖ°b; a◌゙◌à¥â—Œð‘©‡â—ŒÖ°b; a◌゙◌à¥â—Œð‘©‡â—ŒÖ°b; a◌゙◌à¥â—Œð‘©‡â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, ZANABAZAR SQUARE SUBJOINER, LATIN SMALL LETTER B
+0061 11A47 05B0 094D 3099 0062;0061 3099 11A47 094D 05B0 0062;0061 3099 11A47 094D 05B0 0062;0061 3099 11A47 094D 05B0 0062;0061 3099 11A47 094D 05B0 0062; # (a◌𑩇◌ְ◌à¥â—Œã‚™b; a◌゙◌𑩇◌à¥â—ŒÖ°b; a◌゙◌𑩇◌à¥â—ŒÖ°b; a◌゙◌𑩇◌à¥â—ŒÖ°b; a◌゙◌𑩇◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, ZANABAZAR SQUARE SUBJOINER, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11A99 0062;0061 3099 094D 11A99 05B0 0062;0061 3099 094D 11A99 05B0 0062;0061 3099 094D 11A99 05B0 0062;0061 3099 094D 11A99 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘ª™b; a◌゙◌à¥â—Œð‘ª™â—ŒÖ°b; a◌゙◌à¥â—Œð‘ª™â—ŒÖ°b; a◌゙◌à¥â—Œð‘ª™â—ŒÖ°b; a◌゙◌à¥â—Œð‘ª™â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, SOYOMBO SUBJOINER, LATIN SMALL LETTER B
+0061 11A99 05B0 094D 3099 0062;0061 3099 11A99 094D 05B0 0062;0061 3099 11A99 094D 05B0 0062;0061 3099 11A99 094D 05B0 0062;0061 3099 11A99 094D 05B0 0062; # (a◌𑪙◌ְ◌à¥â—Œã‚™b; a◌゙◌𑪙◌à¥â—ŒÖ°b; a◌゙◌𑪙◌à¥â—ŒÖ°b; a◌゙◌𑪙◌à¥â—ŒÖ°b; a◌゙◌𑪙◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, SOYOMBO SUBJOINER, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11C3F 0062;0061 3099 094D 11C3F 05B0 0062;0061 3099 094D 11C3F 05B0 0062;0061 3099 094D 11C3F 05B0 0062;0061 3099 094D 11C3F 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘°¿b; a◌゙◌à¥â—Œð‘°¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘°¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘°¿â—ŒÖ°b; a◌゙◌à¥â—Œð‘°¿â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, BHAIKSUKI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 11C3F 05B0 094D 3099 0062;0061 3099 11C3F 094D 05B0 0062;0061 3099 11C3F 094D 05B0 0062;0061 3099 11C3F 094D 05B0 0062;0061 3099 11C3F 094D 05B0 0062; # (a◌𑰿◌ְ◌à¥â—Œã‚™b; a◌゙◌𑰿◌à¥â—ŒÖ°b; a◌゙◌𑰿◌à¥â—ŒÖ°b; a◌゙◌𑰿◌à¥â—ŒÖ°b; a◌゙◌𑰿◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, BHAIKSUKI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 11D42 0062;0061 16FF0 093C 11D42 3099 0062;0061 16FF0 093C 11D42 3099 0062;0061 16FF0 093C 11D42 3099 0062;0061 16FF0 093C 11D42 3099 0062; # (a◌゙◌𖿰़◌𑵂b; a𖿰◌़◌𑵂◌゙b; a𖿰◌़◌𑵂◌゙b; a𖿰◌़◌𑵂◌゙b; a𖿰◌़◌𑵂◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, MASARAM GONDI SIGN NUKTA, LATIN SMALL LETTER B
+0061 11D42 3099 093C 16FF0 0062;0061 16FF0 11D42 093C 3099 0062;0061 16FF0 11D42 093C 3099 0062;0061 16FF0 11D42 093C 3099 0062;0061 16FF0 11D42 093C 3099 0062; # (a◌𑵂◌゙◌𖿰़b; a𖿰◌𑵂◌़◌゙b; a𖿰◌𑵂◌़◌゙b; a𖿰◌𑵂◌़◌゙b; a𖿰◌𑵂◌़◌゙b; ) LATIN SMALL LETTER A, MASARAM GONDI SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11D44 0062;0061 3099 094D 11D44 05B0 0062;0061 3099 094D 11D44 05B0 0062;0061 3099 094D 11D44 05B0 0062;0061 3099 094D 11D44 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘µ„b; a◌゙◌à¥â—Œð‘µ„◌ְb; a◌゙◌à¥â—Œð‘µ„◌ְb; a◌゙◌à¥â—Œð‘µ„◌ְb; a◌゙◌à¥â—Œð‘µ„◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, MASARAM GONDI SIGN HALANTA, LATIN SMALL LETTER B
+0061 11D44 05B0 094D 3099 0062;0061 3099 11D44 094D 05B0 0062;0061 3099 11D44 094D 05B0 0062;0061 3099 11D44 094D 05B0 0062;0061 3099 11D44 094D 05B0 0062; # (a◌𑵄◌ְ◌à¥â—Œã‚™b; a◌゙◌𑵄◌à¥â—ŒÖ°b; a◌゙◌𑵄◌à¥â—ŒÖ°b; a◌゙◌𑵄◌à¥â—ŒÖ°b; a◌゙◌𑵄◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MASARAM GONDI SIGN HALANTA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11D45 0062;0061 3099 094D 11D45 05B0 0062;0061 3099 094D 11D45 05B0 0062;0061 3099 094D 11D45 05B0 0062;0061 3099 094D 11D45 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘µ…b; a◌゙◌à¥â—Œð‘µ…◌ְb; a◌゙◌à¥â—Œð‘µ…◌ְb; a◌゙◌à¥â—Œð‘µ…◌ְb; a◌゙◌à¥â—Œð‘µ…◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, MASARAM GONDI VIRAMA, LATIN SMALL LETTER B
+0061 11D45 05B0 094D 3099 0062;0061 3099 11D45 094D 05B0 0062;0061 3099 11D45 094D 05B0 0062;0061 3099 11D45 094D 05B0 0062;0061 3099 11D45 094D 05B0 0062; # (a◌𑵅◌ְ◌à¥â—Œã‚™b; a◌゙◌𑵅◌à¥â—ŒÖ°b; a◌゙◌𑵅◌à¥â—ŒÖ°b; a◌゙◌𑵅◌à¥â—ŒÖ°b; a◌゙◌𑵅◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MASARAM GONDI VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11D97 0062;0061 3099 094D 11D97 05B0 0062;0061 3099 094D 11D97 05B0 0062;0061 3099 094D 11D97 05B0 0062;0061 3099 094D 11D97 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘¶—b; a◌゙◌à¥â—Œð‘¶—◌ְb; a◌゙◌à¥â—Œð‘¶—◌ְb; a◌゙◌à¥â—Œð‘¶—◌ְb; a◌゙◌à¥â—Œð‘¶—◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, GUNJALA GONDI VIRAMA, LATIN SMALL LETTER B
+0061 11D97 05B0 094D 3099 0062;0061 3099 11D97 094D 05B0 0062;0061 3099 11D97 094D 05B0 0062;0061 3099 11D97 094D 05B0 0062;0061 3099 11D97 094D 05B0 0062; # (a◌𑶗◌ְ◌à¥â—Œã‚™b; a◌゙◌𑶗◌à¥â—ŒÖ°b; a◌゙◌𑶗◌à¥â—ŒÖ°b; a◌゙◌𑶗◌à¥â—ŒÖ°b; a◌゙◌𑶗◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, GUNJALA GONDI VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 16FF0 0334 16AF0 0062;0061 0334 16AF0 16FF0 0062;0061 0334 16AF0 16FF0 0062;0061 0334 16AF0 16FF0 0062;0061 0334 16AF0 16FF0 0062; # (a𖿰◌̴◌𖫰b; a◌̴◌𖫰𖿰b; a◌̴◌𖫰𖿰b; a◌̴◌𖫰𖿰b; a◌̴◌𖫰𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, BASSA VAH COMBINING HIGH TONE, LATIN SMALL LETTER B
+0061 16AF0 16FF0 0334 0062;0061 16AF0 0334 16FF0 0062;0061 16AF0 0334 16FF0 0062;0061 16AF0 0334 16FF0 0062;0061 16AF0 0334 16FF0 0062; # (a◌𖫰𖿰◌̴b; a◌𖫰◌̴𖿰b; a◌𖫰◌̴𖿰b; a◌𖫰◌̴𖿰b; a◌𖫰◌̴𖿰b; ) LATIN SMALL LETTER A, BASSA VAH COMBINING HIGH TONE, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 16AF1 0062;0061 0334 16AF1 16FF0 0062;0061 0334 16AF1 16FF0 0062;0061 0334 16AF1 16FF0 0062;0061 0334 16AF1 16FF0 0062; # (a𖿰◌̴◌𖫱b; a◌̴◌𖫱𖿰b; a◌̴◌𖫱𖿰b; a◌̴◌𖫱𖿰b; a◌̴◌𖫱𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, BASSA VAH COMBINING LOW TONE, LATIN SMALL LETTER B
+0061 16AF1 16FF0 0334 0062;0061 16AF1 0334 16FF0 0062;0061 16AF1 0334 16FF0 0062;0061 16AF1 0334 16FF0 0062;0061 16AF1 0334 16FF0 0062; # (a◌𖫱𖿰◌̴b; a◌𖫱◌̴𖿰b; a◌𖫱◌̴𖿰b; a◌𖫱◌̴𖿰b; a◌𖫱◌̴𖿰b; ) LATIN SMALL LETTER A, BASSA VAH COMBINING LOW TONE, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 16AF2 0062;0061 0334 16AF2 16FF0 0062;0061 0334 16AF2 16FF0 0062;0061 0334 16AF2 16FF0 0062;0061 0334 16AF2 16FF0 0062; # (a𖿰◌̴◌𖫲b; a◌̴◌𖫲𖿰b; a◌̴◌𖫲𖿰b; a◌̴◌𖫲𖿰b; a◌̴◌𖫲𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, BASSA VAH COMBINING MID TONE, LATIN SMALL LETTER B
+0061 16AF2 16FF0 0334 0062;0061 16AF2 0334 16FF0 0062;0061 16AF2 0334 16FF0 0062;0061 16AF2 0334 16FF0 0062;0061 16AF2 0334 16FF0 0062; # (a◌𖫲𖿰◌̴b; a◌𖫲◌̴𖿰b; a◌𖫲◌̴𖿰b; a◌𖫲◌̴𖿰b; a◌𖫲◌̴𖿰b; ) LATIN SMALL LETTER A, BASSA VAH COMBINING MID TONE, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 16AF3 0062;0061 0334 16AF3 16FF0 0062;0061 0334 16AF3 16FF0 0062;0061 0334 16AF3 16FF0 0062;0061 0334 16AF3 16FF0 0062; # (a𖿰◌̴◌𖫳b; a◌̴◌𖫳𖿰b; a◌̴◌𖫳𖿰b; a◌̴◌𖫳𖿰b; a◌̴◌𖫳𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, BASSA VAH COMBINING LOW-MID TONE, LATIN SMALL LETTER B
+0061 16AF3 16FF0 0334 0062;0061 16AF3 0334 16FF0 0062;0061 16AF3 0334 16FF0 0062;0061 16AF3 0334 16FF0 0062;0061 16AF3 0334 16FF0 0062; # (a◌𖫳𖿰◌̴b; a◌𖫳◌̴𖿰b; a◌𖫳◌̴𖿰b; a◌𖫳◌̴𖿰b; a◌𖫳◌̴𖿰b; ) LATIN SMALL LETTER A, BASSA VAH COMBINING LOW-MID TONE, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 16AF4 0062;0061 0334 16AF4 16FF0 0062;0061 0334 16AF4 16FF0 0062;0061 0334 16AF4 16FF0 0062;0061 0334 16AF4 16FF0 0062; # (a𖿰◌̴◌𖫴b; a◌̴◌𖫴𖿰b; a◌̴◌𖫴𖿰b; a◌̴◌𖫴𖿰b; a◌̴◌𖫴𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, BASSA VAH COMBINING HIGH-LOW TONE, LATIN SMALL LETTER B
+0061 16AF4 16FF0 0334 0062;0061 16AF4 0334 16FF0 0062;0061 16AF4 0334 16FF0 0062;0061 16AF4 0334 16FF0 0062;0061 16AF4 0334 16FF0 0062; # (a◌𖫴𖿰◌̴b; a◌𖫴◌̴𖿰b; a◌𖫴◌̴𖿰b; a◌𖫴◌̴𖿰b; a◌𖫴◌̴𖿰b; ) LATIN SMALL LETTER A, BASSA VAH COMBINING HIGH-LOW TONE, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 0315 0300 05AE 16B30 0062;00E0 05AE 16B30 0315 0062;0061 05AE 0300 16B30 0315 0062;00E0 05AE 16B30 0315 0062;0061 05AE 0300 16B30 0315 0062; # (a◌̕◌̀◌֮◌𖬰b; à◌֮◌𖬰◌̕b; a◌֮◌̀◌𖬰◌̕b; à◌֮◌𖬰◌̕b; a◌֮◌̀◌𖬰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, PAHAWH HMONG MARK CIM TUB, LATIN SMALL LETTER B
+0061 16B30 0315 0300 05AE 0062;0061 05AE 16B30 0300 0315 0062;0061 05AE 16B30 0300 0315 0062;0061 05AE 16B30 0300 0315 0062;0061 05AE 16B30 0300 0315 0062; # (a◌𖬰◌̕◌̀◌֮b; a◌֮◌𖬰◌̀◌̕b; a◌֮◌𖬰◌̀◌̕b; a◌֮◌𖬰◌̀◌̕b; a◌֮◌𖬰◌̀◌̕b; ) LATIN SMALL LETTER A, PAHAWH HMONG MARK CIM TUB, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 16B31 0062;00E0 05AE 16B31 0315 0062;0061 05AE 0300 16B31 0315 0062;00E0 05AE 16B31 0315 0062;0061 05AE 0300 16B31 0315 0062; # (a◌̕◌̀◌֮◌𖬱b; à◌֮◌𖬱◌̕b; a◌֮◌̀◌𖬱◌̕b; à◌֮◌𖬱◌̕b; a◌֮◌̀◌𖬱◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, PAHAWH HMONG MARK CIM SO, LATIN SMALL LETTER B
+0061 16B31 0315 0300 05AE 0062;0061 05AE 16B31 0300 0315 0062;0061 05AE 16B31 0300 0315 0062;0061 05AE 16B31 0300 0315 0062;0061 05AE 16B31 0300 0315 0062; # (a◌𖬱◌̕◌̀◌֮b; a◌֮◌𖬱◌̀◌̕b; a◌֮◌𖬱◌̀◌̕b; a◌֮◌𖬱◌̀◌̕b; a◌֮◌𖬱◌̀◌̕b; ) LATIN SMALL LETTER A, PAHAWH HMONG MARK CIM SO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 16B32 0062;00E0 05AE 16B32 0315 0062;0061 05AE 0300 16B32 0315 0062;00E0 05AE 16B32 0315 0062;0061 05AE 0300 16B32 0315 0062; # (a◌̕◌̀◌֮◌𖬲b; à◌֮◌𖬲◌̕b; a◌֮◌̀◌𖬲◌̕b; à◌֮◌𖬲◌̕b; a◌֮◌̀◌𖬲◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, PAHAWH HMONG MARK CIM KES, LATIN SMALL LETTER B
+0061 16B32 0315 0300 05AE 0062;0061 05AE 16B32 0300 0315 0062;0061 05AE 16B32 0300 0315 0062;0061 05AE 16B32 0300 0315 0062;0061 05AE 16B32 0300 0315 0062; # (a◌𖬲◌̕◌̀◌֮b; a◌֮◌𖬲◌̀◌̕b; a◌֮◌𖬲◌̀◌̕b; a◌֮◌𖬲◌̀◌̕b; a◌֮◌𖬲◌̀◌̕b; ) LATIN SMALL LETTER A, PAHAWH HMONG MARK CIM KES, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 16B33 0062;00E0 05AE 16B33 0315 0062;0061 05AE 0300 16B33 0315 0062;00E0 05AE 16B33 0315 0062;0061 05AE 0300 16B33 0315 0062; # (a◌̕◌̀◌֮◌𖬳b; à◌֮◌𖬳◌̕b; a◌֮◌̀◌𖬳◌̕b; à◌֮◌𖬳◌̕b; a◌֮◌̀◌𖬳◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, PAHAWH HMONG MARK CIM KHAV, LATIN SMALL LETTER B
+0061 16B33 0315 0300 05AE 0062;0061 05AE 16B33 0300 0315 0062;0061 05AE 16B33 0300 0315 0062;0061 05AE 16B33 0300 0315 0062;0061 05AE 16B33 0300 0315 0062; # (a◌𖬳◌̕◌̀◌֮b; a◌֮◌𖬳◌̀◌̕b; a◌֮◌𖬳◌̀◌̕b; a◌֮◌𖬳◌̀◌̕b; a◌֮◌𖬳◌̀◌̕b; ) LATIN SMALL LETTER A, PAHAWH HMONG MARK CIM KHAV, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 16B34 0062;00E0 05AE 16B34 0315 0062;0061 05AE 0300 16B34 0315 0062;00E0 05AE 16B34 0315 0062;0061 05AE 0300 16B34 0315 0062; # (a◌̕◌̀◌֮◌𖬴b; à◌֮◌𖬴◌̕b; a◌֮◌̀◌𖬴◌̕b; à◌֮◌𖬴◌̕b; a◌֮◌̀◌𖬴◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, PAHAWH HMONG MARK CIM SUAM, LATIN SMALL LETTER B
+0061 16B34 0315 0300 05AE 0062;0061 05AE 16B34 0300 0315 0062;0061 05AE 16B34 0300 0315 0062;0061 05AE 16B34 0300 0315 0062;0061 05AE 16B34 0300 0315 0062; # (a◌𖬴◌̕◌̀◌֮b; a◌֮◌𖬴◌̀◌̕b; a◌֮◌𖬴◌̀◌̕b; a◌֮◌𖬴◌̀◌̕b; a◌֮◌𖬴◌̀◌̕b; ) LATIN SMALL LETTER A, PAHAWH HMONG MARK CIM SUAM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 16B35 0062;00E0 05AE 16B35 0315 0062;0061 05AE 0300 16B35 0315 0062;00E0 05AE 16B35 0315 0062;0061 05AE 0300 16B35 0315 0062; # (a◌̕◌̀◌֮◌𖬵b; à◌֮◌𖬵◌̕b; a◌֮◌̀◌𖬵◌̕b; à◌֮◌𖬵◌̕b; a◌֮◌̀◌𖬵◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, PAHAWH HMONG MARK CIM HOM, LATIN SMALL LETTER B
+0061 16B35 0315 0300 05AE 0062;0061 05AE 16B35 0300 0315 0062;0061 05AE 16B35 0300 0315 0062;0061 05AE 16B35 0300 0315 0062;0061 05AE 16B35 0300 0315 0062; # (a◌𖬵◌̕◌̀◌֮b; a◌֮◌𖬵◌̀◌̕b; a◌֮◌𖬵◌̀◌̕b; a◌֮◌𖬵◌̀◌̕b; a◌֮◌𖬵◌̀◌̕b; ) LATIN SMALL LETTER A, PAHAWH HMONG MARK CIM HOM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 16B36 0062;00E0 05AE 16B36 0315 0062;0061 05AE 0300 16B36 0315 0062;00E0 05AE 16B36 0315 0062;0061 05AE 0300 16B36 0315 0062; # (a◌̕◌̀◌֮◌𖬶b; à◌֮◌𖬶◌̕b; a◌֮◌̀◌𖬶◌̕b; à◌֮◌𖬶◌̕b; a◌֮◌̀◌𖬶◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, PAHAWH HMONG MARK CIM TAUM, LATIN SMALL LETTER B
+0061 16B36 0315 0300 05AE 0062;0061 05AE 16B36 0300 0315 0062;0061 05AE 16B36 0300 0315 0062;0061 05AE 16B36 0300 0315 0062;0061 05AE 16B36 0300 0315 0062; # (a◌𖬶◌̕◌̀◌֮b; a◌֮◌𖬶◌̀◌̕b; a◌֮◌𖬶◌̀◌̕b; a◌֮◌𖬶◌̀◌̕b; a◌֮◌𖬶◌̀◌̕b; ) LATIN SMALL LETTER A, PAHAWH HMONG MARK CIM TAUM, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 093C 16FF0 0334 16FF0 0062;0061 0334 16FF0 16FF0 093C 0062;0061 0334 16FF0 16FF0 093C 0062;0061 0334 16FF0 16FF0 093C 0062;0061 0334 16FF0 16FF0 093C 0062; # (a◌𖿰़◌̴𖿰b; a◌̴𖿰𖿰◌़b; a◌̴𖿰𖿰◌़b; a◌̴𖿰𖿰◌़b; a◌̴𖿰𖿰◌़b; ) LATIN SMALL LETTER A, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+0061 16FF0 093C 16FF0 0334 0062;0061 0334 16FF0 16FF0 093C 0062;0061 0334 16FF0 16FF0 093C 0062;0061 0334 16FF0 16FF0 093C 0062;0061 0334 16FF0 16FF0 093C 0062; # (a𖿰◌𖿰़◌̴b; a◌̴𖿰𖿰◌़b; a◌̴𖿰𖿰◌़b; a◌̴𖿰𖿰◌़b; a◌̴𖿰𖿰◌़b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 093C 16FF0 0334 16FF1 0062;0061 0334 16FF0 16FF1 093C 0062;0061 0334 16FF0 16FF1 093C 0062;0061 0334 16FF0 16FF1 093C 0062;0061 0334 16FF0 16FF1 093C 0062; # (a◌𖿰़◌̴𖿱b; a◌̴𖿰𖿱◌़b; a◌̴𖿰𖿱◌़b; a◌̴𖿰𖿱◌़b; a◌̴𖿰𖿱◌़b; ) LATIN SMALL LETTER A, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, VIETNAMESE ALTERNATE READING MARK NHAY, LATIN SMALL LETTER B
+0061 16FF1 093C 16FF0 0334 0062;0061 0334 16FF1 16FF0 093C 0062;0061 0334 16FF1 16FF0 093C 0062;0061 0334 16FF1 16FF0 093C 0062;0061 0334 16FF1 16FF0 093C 0062; # (a𖿱◌𖿰़◌̴b; a◌̴𖿱𖿰◌़b; a◌̴𖿱𖿰◌़b; a◌̴𖿱𖿰◌़b; a◌̴𖿱𖿰◌़b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK NHAY, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 1BC9E 0062;0061 0334 1BC9E 16FF0 0062;0061 0334 1BC9E 16FF0 0062;0061 0334 1BC9E 16FF0 0062;0061 0334 1BC9E 16FF0 0062; # (a𖿰◌̴◌𛲞b; a◌̴◌𛲞𖿰b; a◌̴◌𛲞𖿰b; a◌̴◌𛲞𖿰b; a◌̴◌𛲞𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, DUPLOYAN DOUBLE MARK, LATIN SMALL LETTER B
+0061 1BC9E 16FF0 0334 0062;0061 1BC9E 0334 16FF0 0062;0061 1BC9E 0334 16FF0 0062;0061 1BC9E 0334 16FF0 0062;0061 1BC9E 0334 16FF0 0062; # (a◌𛲞𖿰◌̴b; a◌𛲞◌̴𖿰b; a◌𛲞◌̴𖿰b; a◌𛲞◌̴𖿰b; a◌𛲞◌̴𖿰b; ) LATIN SMALL LETTER A, DUPLOYAN DOUBLE MARK, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 1DFA 031B 1DCE 1D165 0062;0061 1DCE 031B 1D165 1DFA 0062;0061 1DCE 031B 1D165 1DFA 0062;0061 1DCE 031B 1D165 1DFA 0062;0061 1DCE 031B 1D165 1DFA 0062; # (a◌᷺◌̛◌᷎ð…¥b; a◌᷎◌̛ð…¥â—Œá·ºb; a◌᷎◌̛ð…¥â—Œá·ºb; a◌᷎◌̛ð…¥â—Œá·ºb; a◌᷎◌̛ð…¥â—Œá·ºb; ) LATIN SMALL LETTER A, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, MUSICAL SYMBOL COMBINING STEM, LATIN SMALL LETTER B
+0061 1D165 1DFA 031B 1DCE 0062;0061 1DCE 1D165 031B 1DFA 0062;0061 1DCE 1D165 031B 1DFA 0062;0061 1DCE 1D165 031B 1DFA 0062;0061 1DCE 1D165 031B 1DFA 0062; # (að…¥â—Œá·ºâ—ŒÌ›â—Œá·Žb; a◌᷎ð…¥â—ŒÌ›â—Œá·ºb; a◌᷎ð…¥â—ŒÌ›â—Œá·ºb; a◌᷎ð…¥â—ŒÌ›â—Œá·ºb; a◌᷎ð…¥â—ŒÌ›â—Œá·ºb; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING STEM, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, LATIN SMALL LETTER B
+0061 1DFA 031B 1DCE 1D166 0062;0061 1DCE 031B 1D166 1DFA 0062;0061 1DCE 031B 1D166 1DFA 0062;0061 1DCE 031B 1D166 1DFA 0062;0061 1DCE 031B 1D166 1DFA 0062; # (a◌᷺◌̛◌᷎ð…¦b; a◌᷎◌̛ð…¦â—Œá·ºb; a◌᷎◌̛ð…¦â—Œá·ºb; a◌᷎◌̛ð…¦â—Œá·ºb; a◌᷎◌̛ð…¦â—Œá·ºb; ) LATIN SMALL LETTER A, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, MUSICAL SYMBOL COMBINING SPRECHGESANG STEM, LATIN SMALL LETTER B
+0061 1D166 1DFA 031B 1DCE 0062;0061 1DCE 1D166 031B 1DFA 0062;0061 1DCE 1D166 031B 1DFA 0062;0061 1DCE 1D166 031B 1DFA 0062;0061 1DCE 1D166 031B 1DFA 0062; # (að…¦â—Œá·ºâ—ŒÌ›â—Œá·Žb; a◌᷎ð…¦â—ŒÌ›â—Œá·ºb; a◌᷎ð…¦â—ŒÌ›â—Œá·ºb; a◌᷎ð…¦â—ŒÌ›â—Œá·ºb; a◌᷎ð…¦â—ŒÌ›â—Œá·ºb; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING SPRECHGESANG STEM, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, LATIN SMALL LETTER B
+0061 16FF0 0334 1D167 0062;0061 0334 1D167 16FF0 0062;0061 0334 1D167 16FF0 0062;0061 0334 1D167 16FF0 0062;0061 0334 1D167 16FF0 0062; # (a𖿰◌̴◌ð…§b; a◌̴◌ð…§ð–¿°b; a◌̴◌ð…§ð–¿°b; a◌̴◌ð…§ð–¿°b; a◌̴◌ð…§ð–¿°b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, MUSICAL SYMBOL COMBINING TREMOLO-1, LATIN SMALL LETTER B
+0061 1D167 16FF0 0334 0062;0061 1D167 0334 16FF0 0062;0061 1D167 0334 16FF0 0062;0061 1D167 0334 16FF0 0062;0061 1D167 0334 16FF0 0062; # (aâ—Œð…§ð–¿°â—ŒÌ´b; aâ—Œð…§â—ŒÌ´ð–¿°b; aâ—Œð…§â—ŒÌ´ð–¿°b; aâ—Œð…§â—ŒÌ´ð–¿°b; aâ—Œð…§â—ŒÌ´ð–¿°b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING TREMOLO-1, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 1D168 0062;0061 0334 1D168 16FF0 0062;0061 0334 1D168 16FF0 0062;0061 0334 1D168 16FF0 0062;0061 0334 1D168 16FF0 0062; # (a𖿰◌̴◌ð…¨b; a◌̴◌ð…¨ð–¿°b; a◌̴◌ð…¨ð–¿°b; a◌̴◌ð…¨ð–¿°b; a◌̴◌ð…¨ð–¿°b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, MUSICAL SYMBOL COMBINING TREMOLO-2, LATIN SMALL LETTER B
+0061 1D168 16FF0 0334 0062;0061 1D168 0334 16FF0 0062;0061 1D168 0334 16FF0 0062;0061 1D168 0334 16FF0 0062;0061 1D168 0334 16FF0 0062; # (aâ—Œð…¨ð–¿°â—ŒÌ´b; aâ—Œð…¨â—ŒÌ´ð–¿°b; aâ—Œð…¨â—ŒÌ´ð–¿°b; aâ—Œð…¨â—ŒÌ´ð–¿°b; aâ—Œð…¨â—ŒÌ´ð–¿°b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING TREMOLO-2, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 16FF0 0334 1D169 0062;0061 0334 1D169 16FF0 0062;0061 0334 1D169 16FF0 0062;0061 0334 1D169 16FF0 0062;0061 0334 1D169 16FF0 0062; # (a𖿰◌̴◌ð…©b; a◌̴◌ð…©ð–¿°b; a◌̴◌ð…©ð–¿°b; a◌̴◌ð…©ð–¿°b; a◌̴◌ð…©ð–¿°b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, MUSICAL SYMBOL COMBINING TREMOLO-3, LATIN SMALL LETTER B
+0061 1D169 16FF0 0334 0062;0061 1D169 0334 16FF0 0062;0061 1D169 0334 16FF0 0062;0061 1D169 0334 16FF0 0062;0061 1D169 0334 16FF0 0062; # (aâ—Œð…©ð–¿°â—ŒÌ´b; aâ—Œð…©â—ŒÌ´ð–¿°b; aâ—Œð…©â—ŒÌ´ð–¿°b; aâ—Œð…©â—ŒÌ´ð–¿°b; aâ—Œð…©â—ŒÌ´ð–¿°b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING TREMOLO-3, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 05AE 1D16D 302E 1D16D 0062;0061 302E 1D16D 1D16D 05AE 0062;0061 302E 1D16D 1D16D 05AE 0062;0061 302E 1D16D 1D16D 05AE 0062;0061 302E 1D16D 1D16D 05AE 0062; # (a◌֮ð…­ã€®ð…­b; a〮ð…­ð…­â—ŒÖ®b; a〮ð…­ð…­â—ŒÖ®b; a〮ð…­ð…­â—ŒÖ®b; a〮ð…­ð…­â—ŒÖ®b; ) LATIN SMALL LETTER A, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, HANGUL SINGLE DOT TONE MARK, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, LATIN SMALL LETTER B
+0061 1D16D 05AE 1D16D 302E 0062;0061 302E 1D16D 1D16D 05AE 0062;0061 302E 1D16D 1D16D 05AE 0062;0061 302E 1D16D 1D16D 05AE 0062;0061 302E 1D16D 1D16D 05AE 0062; # (að…­â—ŒÖ®ð…­ã€®b; a〮ð…­ð…­â—ŒÖ®b; a〮ð…­ð…­â—ŒÖ®b; a〮ð…­ð…­â—ŒÖ®b; a〮ð…­ð…­â—ŒÖ®b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING AUGMENTATION DOT, HANGUL SINGLE DOT TONE MARK, LATIN SMALL LETTER B
+0061 1DFA 031B 1DCE 1D16E 0062;0061 1DCE 031B 1D16E 1DFA 0062;0061 1DCE 031B 1D16E 1DFA 0062;0061 1DCE 031B 1D16E 1DFA 0062;0061 1DCE 031B 1D16E 1DFA 0062; # (a◌᷺◌̛◌᷎ð…®b; a◌᷎◌̛ð…®â—Œá·ºb; a◌᷎◌̛ð…®â—Œá·ºb; a◌᷎◌̛ð…®â—Œá·ºb; a◌᷎◌̛ð…®â—Œá·ºb; ) LATIN SMALL LETTER A, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, MUSICAL SYMBOL COMBINING FLAG-1, LATIN SMALL LETTER B
+0061 1D16E 1DFA 031B 1DCE 0062;0061 1DCE 1D16E 031B 1DFA 0062;0061 1DCE 1D16E 031B 1DFA 0062;0061 1DCE 1D16E 031B 1DFA 0062;0061 1DCE 1D16E 031B 1DFA 0062; # (að…®â—Œá·ºâ—ŒÌ›â—Œá·Žb; a◌᷎ð…®â—ŒÌ›â—Œá·ºb; a◌᷎ð…®â—ŒÌ›â—Œá·ºb; a◌᷎ð…®â—ŒÌ›â—Œá·ºb; a◌᷎ð…®â—ŒÌ›â—Œá·ºb; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING FLAG-1, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, LATIN SMALL LETTER B
+0061 1DFA 031B 1DCE 1D16F 0062;0061 1DCE 031B 1D16F 1DFA 0062;0061 1DCE 031B 1D16F 1DFA 0062;0061 1DCE 031B 1D16F 1DFA 0062;0061 1DCE 031B 1D16F 1DFA 0062; # (a◌᷺◌̛◌᷎ð…¯b; a◌᷎◌̛ð…¯â—Œá·ºb; a◌᷎◌̛ð…¯â—Œá·ºb; a◌᷎◌̛ð…¯â—Œá·ºb; a◌᷎◌̛ð…¯â—Œá·ºb; ) LATIN SMALL LETTER A, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, MUSICAL SYMBOL COMBINING FLAG-2, LATIN SMALL LETTER B
+0061 1D16F 1DFA 031B 1DCE 0062;0061 1DCE 1D16F 031B 1DFA 0062;0061 1DCE 1D16F 031B 1DFA 0062;0061 1DCE 1D16F 031B 1DFA 0062;0061 1DCE 1D16F 031B 1DFA 0062; # (að…¯â—Œá·ºâ—ŒÌ›â—Œá·Žb; a◌᷎ð…¯â—ŒÌ›â—Œá·ºb; a◌᷎ð…¯â—ŒÌ›â—Œá·ºb; a◌᷎ð…¯â—ŒÌ›â—Œá·ºb; a◌᷎ð…¯â—ŒÌ›â—Œá·ºb; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING FLAG-2, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, LATIN SMALL LETTER B
+0061 1DFA 031B 1DCE 1D170 0062;0061 1DCE 031B 1D170 1DFA 0062;0061 1DCE 031B 1D170 1DFA 0062;0061 1DCE 031B 1D170 1DFA 0062;0061 1DCE 031B 1D170 1DFA 0062; # (a◌᷺◌̛◌᷎ð…°b; a◌᷎◌̛ð…°â—Œá·ºb; a◌᷎◌̛ð…°â—Œá·ºb; a◌᷎◌̛ð…°â—Œá·ºb; a◌᷎◌̛ð…°â—Œá·ºb; ) LATIN SMALL LETTER A, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, MUSICAL SYMBOL COMBINING FLAG-3, LATIN SMALL LETTER B
+0061 1D170 1DFA 031B 1DCE 0062;0061 1DCE 1D170 031B 1DFA 0062;0061 1DCE 1D170 031B 1DFA 0062;0061 1DCE 1D170 031B 1DFA 0062;0061 1DCE 1D170 031B 1DFA 0062; # (að…°â—Œá·ºâ—ŒÌ›â—Œá·Žb; a◌᷎ð…°â—ŒÌ›â—Œá·ºb; a◌᷎ð…°â—ŒÌ›â—Œá·ºb; a◌᷎ð…°â—ŒÌ›â—Œá·ºb; a◌᷎ð…°â—ŒÌ›â—Œá·ºb; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING FLAG-3, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, LATIN SMALL LETTER B
+0061 1DFA 031B 1DCE 1D171 0062;0061 1DCE 031B 1D171 1DFA 0062;0061 1DCE 031B 1D171 1DFA 0062;0061 1DCE 031B 1D171 1DFA 0062;0061 1DCE 031B 1D171 1DFA 0062; # (a◌᷺◌̛◌᷎ð…±b; a◌᷎◌̛ð…±â—Œá·ºb; a◌᷎◌̛ð…±â—Œá·ºb; a◌᷎◌̛ð…±â—Œá·ºb; a◌᷎◌̛ð…±â—Œá·ºb; ) LATIN SMALL LETTER A, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, MUSICAL SYMBOL COMBINING FLAG-4, LATIN SMALL LETTER B
+0061 1D171 1DFA 031B 1DCE 0062;0061 1DCE 1D171 031B 1DFA 0062;0061 1DCE 1D171 031B 1DFA 0062;0061 1DCE 1D171 031B 1DFA 0062;0061 1DCE 1D171 031B 1DFA 0062; # (að…±â—Œá·ºâ—ŒÌ›â—Œá·Žb; a◌᷎ð…±â—ŒÌ›â—Œá·ºb; a◌᷎ð…±â—ŒÌ›â—Œá·ºb; a◌᷎ð…±â—ŒÌ›â—Œá·ºb; a◌᷎ð…±â—ŒÌ›â—Œá·ºb; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING FLAG-4, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, LATIN SMALL LETTER B
+0061 1DFA 031B 1DCE 1D172 0062;0061 1DCE 031B 1D172 1DFA 0062;0061 1DCE 031B 1D172 1DFA 0062;0061 1DCE 031B 1D172 1DFA 0062;0061 1DCE 031B 1D172 1DFA 0062; # (a◌᷺◌̛◌᷎ð…²b; a◌᷎◌̛ð…²â—Œá·ºb; a◌᷎◌̛ð…²â—Œá·ºb; a◌᷎◌̛ð…²â—Œá·ºb; a◌᷎◌̛ð…²â—Œá·ºb; ) LATIN SMALL LETTER A, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, MUSICAL SYMBOL COMBINING FLAG-5, LATIN SMALL LETTER B
+0061 1D172 1DFA 031B 1DCE 0062;0061 1DCE 1D172 031B 1DFA 0062;0061 1DCE 1D172 031B 1DFA 0062;0061 1DCE 1D172 031B 1DFA 0062;0061 1DCE 1D172 031B 1DFA 0062; # (að…²â—Œá·ºâ—ŒÌ›â—Œá·Žb; a◌᷎ð…²â—ŒÌ›â—Œá·ºb; a◌᷎ð…²â—ŒÌ›â—Œá·ºb; a◌᷎ð…²â—ŒÌ›â—Œá·ºb; a◌᷎ð…²â—ŒÌ›â—Œá·ºb; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING FLAG-5, COMBINING DOT BELOW LEFT, COMBINING HORN, COMBINING OGONEK ABOVE, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1D17B 0062;0061 1DFA 0316 1D17B 059A 0062;0061 1DFA 0316 1D17B 059A 0062;0061 1DFA 0316 1D17B 059A 0062;0061 1DFA 0316 1D17B 059A 0062; # (a◌֚◌̖◌᷺◌ð…»b; a◌᷺◌̖◌ð…»â—ŒÖšb; a◌᷺◌̖◌ð…»â—ŒÖšb; a◌᷺◌̖◌ð…»â—ŒÖšb; a◌᷺◌̖◌ð…»â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MUSICAL SYMBOL COMBINING ACCENT, LATIN SMALL LETTER B
+0061 1D17B 059A 0316 1DFA 0062;0061 1DFA 1D17B 0316 059A 0062;0061 1DFA 1D17B 0316 059A 0062;0061 1DFA 1D17B 0316 059A 0062;0061 1DFA 1D17B 0316 059A 0062; # (aâ—Œð…»â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð…»â—ŒÌ–◌֚b; a◌᷺◌ð…»â—ŒÌ–◌֚b; a◌᷺◌ð…»â—ŒÌ–◌֚b; a◌᷺◌ð…»â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING ACCENT, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1D17C 0062;0061 1DFA 0316 1D17C 059A 0062;0061 1DFA 0316 1D17C 059A 0062;0061 1DFA 0316 1D17C 059A 0062;0061 1DFA 0316 1D17C 059A 0062; # (a◌֚◌̖◌᷺◌ð…¼b; a◌᷺◌̖◌ð…¼â—ŒÖšb; a◌᷺◌̖◌ð…¼â—ŒÖšb; a◌᷺◌̖◌ð…¼â—ŒÖšb; a◌᷺◌̖◌ð…¼â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MUSICAL SYMBOL COMBINING STACCATO, LATIN SMALL LETTER B
+0061 1D17C 059A 0316 1DFA 0062;0061 1DFA 1D17C 0316 059A 0062;0061 1DFA 1D17C 0316 059A 0062;0061 1DFA 1D17C 0316 059A 0062;0061 1DFA 1D17C 0316 059A 0062; # (aâ—Œð…¼â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð…¼â—ŒÌ–◌֚b; a◌᷺◌ð…¼â—ŒÌ–◌֚b; a◌᷺◌ð…¼â—ŒÌ–◌֚b; a◌᷺◌ð…¼â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING STACCATO, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1D17D 0062;0061 1DFA 0316 1D17D 059A 0062;0061 1DFA 0316 1D17D 059A 0062;0061 1DFA 0316 1D17D 059A 0062;0061 1DFA 0316 1D17D 059A 0062; # (a◌֚◌̖◌᷺◌ð…½b; a◌᷺◌̖◌ð…½â—ŒÖšb; a◌᷺◌̖◌ð…½â—ŒÖšb; a◌᷺◌̖◌ð…½â—ŒÖšb; a◌᷺◌̖◌ð…½â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MUSICAL SYMBOL COMBINING TENUTO, LATIN SMALL LETTER B
+0061 1D17D 059A 0316 1DFA 0062;0061 1DFA 1D17D 0316 059A 0062;0061 1DFA 1D17D 0316 059A 0062;0061 1DFA 1D17D 0316 059A 0062;0061 1DFA 1D17D 0316 059A 0062; # (aâ—Œð…½â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð…½â—ŒÌ–◌֚b; a◌᷺◌ð…½â—ŒÌ–◌֚b; a◌᷺◌ð…½â—ŒÌ–◌֚b; a◌᷺◌ð…½â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING TENUTO, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1D17E 0062;0061 1DFA 0316 1D17E 059A 0062;0061 1DFA 0316 1D17E 059A 0062;0061 1DFA 0316 1D17E 059A 0062;0061 1DFA 0316 1D17E 059A 0062; # (a◌֚◌̖◌᷺◌ð…¾b; a◌᷺◌̖◌ð…¾â—ŒÖšb; a◌᷺◌̖◌ð…¾â—ŒÖšb; a◌᷺◌̖◌ð…¾â—ŒÖšb; a◌᷺◌̖◌ð…¾â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MUSICAL SYMBOL COMBINING STACCATISSIMO, LATIN SMALL LETTER B
+0061 1D17E 059A 0316 1DFA 0062;0061 1DFA 1D17E 0316 059A 0062;0061 1DFA 1D17E 0316 059A 0062;0061 1DFA 1D17E 0316 059A 0062;0061 1DFA 1D17E 0316 059A 0062; # (aâ—Œð…¾â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð…¾â—ŒÌ–◌֚b; a◌᷺◌ð…¾â—ŒÌ–◌֚b; a◌᷺◌ð…¾â—ŒÌ–◌֚b; a◌᷺◌ð…¾â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING STACCATISSIMO, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1D17F 0062;0061 1DFA 0316 1D17F 059A 0062;0061 1DFA 0316 1D17F 059A 0062;0061 1DFA 0316 1D17F 059A 0062;0061 1DFA 0316 1D17F 059A 0062; # (a◌֚◌̖◌᷺◌ð…¿b; a◌᷺◌̖◌ð…¿â—ŒÖšb; a◌᷺◌̖◌ð…¿â—ŒÖšb; a◌᷺◌̖◌ð…¿â—ŒÖšb; a◌᷺◌̖◌ð…¿â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MUSICAL SYMBOL COMBINING MARCATO, LATIN SMALL LETTER B
+0061 1D17F 059A 0316 1DFA 0062;0061 1DFA 1D17F 0316 059A 0062;0061 1DFA 1D17F 0316 059A 0062;0061 1DFA 1D17F 0316 059A 0062;0061 1DFA 1D17F 0316 059A 0062; # (aâ—Œð…¿â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð…¿â—ŒÌ–◌֚b; a◌᷺◌ð…¿â—ŒÌ–◌֚b; a◌᷺◌ð…¿â—ŒÌ–◌֚b; a◌᷺◌ð…¿â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING MARCATO, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1D180 0062;0061 1DFA 0316 1D180 059A 0062;0061 1DFA 0316 1D180 059A 0062;0061 1DFA 0316 1D180 059A 0062;0061 1DFA 0316 1D180 059A 0062; # (a◌֚◌̖◌᷺◌ð†€b; a◌᷺◌̖◌ð†€â—ŒÖšb; a◌᷺◌̖◌ð†€â—ŒÖšb; a◌᷺◌̖◌ð†€â—ŒÖšb; a◌᷺◌̖◌ð†€â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MUSICAL SYMBOL COMBINING MARCATO-STACCATO, LATIN SMALL LETTER B
+0061 1D180 059A 0316 1DFA 0062;0061 1DFA 1D180 0316 059A 0062;0061 1DFA 1D180 0316 059A 0062;0061 1DFA 1D180 0316 059A 0062;0061 1DFA 1D180 0316 059A 0062; # (aâ—Œð†€â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð†€â—ŒÌ–◌֚b; a◌᷺◌ð†€â—ŒÌ–◌֚b; a◌᷺◌ð†€â—ŒÌ–◌֚b; a◌᷺◌ð†€â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING MARCATO-STACCATO, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1D181 0062;0061 1DFA 0316 1D181 059A 0062;0061 1DFA 0316 1D181 059A 0062;0061 1DFA 0316 1D181 059A 0062;0061 1DFA 0316 1D181 059A 0062; # (a◌֚◌̖◌᷺◌ð†b; a◌᷺◌̖◌ð†â—ŒÖšb; a◌᷺◌̖◌ð†â—ŒÖšb; a◌᷺◌̖◌ð†â—ŒÖšb; a◌᷺◌̖◌ð†â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MUSICAL SYMBOL COMBINING ACCENT-STACCATO, LATIN SMALL LETTER B
+0061 1D181 059A 0316 1DFA 0062;0061 1DFA 1D181 0316 059A 0062;0061 1DFA 1D181 0316 059A 0062;0061 1DFA 1D181 0316 059A 0062;0061 1DFA 1D181 0316 059A 0062; # (aâ—Œð†â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð†â—ŒÌ–◌֚b; a◌᷺◌ð†â—ŒÌ–◌֚b; a◌᷺◌ð†â—ŒÌ–◌֚b; a◌᷺◌ð†â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING ACCENT-STACCATO, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1D182 0062;0061 1DFA 0316 1D182 059A 0062;0061 1DFA 0316 1D182 059A 0062;0061 1DFA 0316 1D182 059A 0062;0061 1DFA 0316 1D182 059A 0062; # (a◌֚◌̖◌᷺◌ð†‚b; a◌᷺◌̖◌ð†‚◌֚b; a◌᷺◌̖◌ð†‚◌֚b; a◌᷺◌̖◌ð†‚◌֚b; a◌᷺◌̖◌ð†‚◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MUSICAL SYMBOL COMBINING LOURE, LATIN SMALL LETTER B
+0061 1D182 059A 0316 1DFA 0062;0061 1DFA 1D182 0316 059A 0062;0061 1DFA 1D182 0316 059A 0062;0061 1DFA 1D182 0316 059A 0062;0061 1DFA 1D182 0316 059A 0062; # (aâ—Œð†‚◌֚◌̖◌᷺b; a◌᷺◌ð†‚◌̖◌֚b; a◌᷺◌ð†‚◌̖◌֚b; a◌᷺◌ð†‚◌̖◌֚b; a◌᷺◌ð†‚◌̖◌֚b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING LOURE, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D185 0062;00E0 05AE 1D185 0315 0062;0061 05AE 0300 1D185 0315 0062;00E0 05AE 1D185 0315 0062;0061 05AE 0300 1D185 0315 0062; # (a◌̕◌̀◌֮◌ð†…b; à◌֮◌ð†…◌̕b; a◌֮◌̀◌ð†…◌̕b; à◌֮◌ð†…◌̕b; a◌֮◌̀◌ð†…◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING DOIT, LATIN SMALL LETTER B
+0061 1D185 0315 0300 05AE 0062;0061 05AE 1D185 0300 0315 0062;0061 05AE 1D185 0300 0315 0062;0061 05AE 1D185 0300 0315 0062;0061 05AE 1D185 0300 0315 0062; # (aâ—Œð†…◌̕◌̀◌֮b; a◌֮◌ð†…◌̀◌̕b; a◌֮◌ð†…◌̀◌̕b; a◌֮◌ð†…◌̀◌̕b; a◌֮◌ð†…◌̀◌̕b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING DOIT, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D186 0062;00E0 05AE 1D186 0315 0062;0061 05AE 0300 1D186 0315 0062;00E0 05AE 1D186 0315 0062;0061 05AE 0300 1D186 0315 0062; # (a◌̕◌̀◌֮◌ð††b; à◌֮◌ð††â—ŒÌ•b; a◌֮◌̀◌ð††â—ŒÌ•b; à◌֮◌ð††â—ŒÌ•b; a◌֮◌̀◌ð††â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING RIP, LATIN SMALL LETTER B
+0061 1D186 0315 0300 05AE 0062;0061 05AE 1D186 0300 0315 0062;0061 05AE 1D186 0300 0315 0062;0061 05AE 1D186 0300 0315 0062;0061 05AE 1D186 0300 0315 0062; # (aâ—Œð††â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð††â—ŒÌ€â—ŒÌ•b; a◌֮◌ð††â—ŒÌ€â—ŒÌ•b; a◌֮◌ð††â—ŒÌ€â—ŒÌ•b; a◌֮◌ð††â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING RIP, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D187 0062;00E0 05AE 1D187 0315 0062;0061 05AE 0300 1D187 0315 0062;00E0 05AE 1D187 0315 0062;0061 05AE 0300 1D187 0315 0062; # (a◌̕◌̀◌֮◌ð†‡b; à◌֮◌ð†‡â—ŒÌ•b; a◌֮◌̀◌ð†‡â—ŒÌ•b; à◌֮◌ð†‡â—ŒÌ•b; a◌֮◌̀◌ð†‡â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING FLIP, LATIN SMALL LETTER B
+0061 1D187 0315 0300 05AE 0062;0061 05AE 1D187 0300 0315 0062;0061 05AE 1D187 0300 0315 0062;0061 05AE 1D187 0300 0315 0062;0061 05AE 1D187 0300 0315 0062; # (aâ—Œð†‡â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð†‡â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†‡â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†‡â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†‡â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING FLIP, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D188 0062;00E0 05AE 1D188 0315 0062;0061 05AE 0300 1D188 0315 0062;00E0 05AE 1D188 0315 0062;0061 05AE 0300 1D188 0315 0062; # (a◌̕◌̀◌֮◌ð†ˆb; à◌֮◌ð†ˆâ—ŒÌ•b; a◌֮◌̀◌ð†ˆâ—ŒÌ•b; à◌֮◌ð†ˆâ—ŒÌ•b; a◌֮◌̀◌ð†ˆâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING SMEAR, LATIN SMALL LETTER B
+0061 1D188 0315 0300 05AE 0062;0061 05AE 1D188 0300 0315 0062;0061 05AE 1D188 0300 0315 0062;0061 05AE 1D188 0300 0315 0062;0061 05AE 1D188 0300 0315 0062; # (aâ—Œð†ˆâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð†ˆâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð†ˆâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð†ˆâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð†ˆâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING SMEAR, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D189 0062;00E0 05AE 1D189 0315 0062;0061 05AE 0300 1D189 0315 0062;00E0 05AE 1D189 0315 0062;0061 05AE 0300 1D189 0315 0062; # (a◌̕◌̀◌֮◌ð†‰b; à◌֮◌ð†‰â—ŒÌ•b; a◌֮◌̀◌ð†‰â—ŒÌ•b; à◌֮◌ð†‰â—ŒÌ•b; a◌֮◌̀◌ð†‰â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING BEND, LATIN SMALL LETTER B
+0061 1D189 0315 0300 05AE 0062;0061 05AE 1D189 0300 0315 0062;0061 05AE 1D189 0300 0315 0062;0061 05AE 1D189 0300 0315 0062;0061 05AE 1D189 0300 0315 0062; # (aâ—Œð†‰â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð†‰â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†‰â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†‰â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†‰â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING BEND, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1D18A 0062;0061 1DFA 0316 1D18A 059A 0062;0061 1DFA 0316 1D18A 059A 0062;0061 1DFA 0316 1D18A 059A 0062;0061 1DFA 0316 1D18A 059A 0062; # (a◌֚◌̖◌᷺◌ð†Šb; a◌᷺◌̖◌ð†Šâ—ŒÖšb; a◌᷺◌̖◌ð†Šâ—ŒÖšb; a◌᷺◌̖◌ð†Šâ—ŒÖšb; a◌᷺◌̖◌ð†Šâ—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MUSICAL SYMBOL COMBINING DOUBLE TONGUE, LATIN SMALL LETTER B
+0061 1D18A 059A 0316 1DFA 0062;0061 1DFA 1D18A 0316 059A 0062;0061 1DFA 1D18A 0316 059A 0062;0061 1DFA 1D18A 0316 059A 0062;0061 1DFA 1D18A 0316 059A 0062; # (aâ—Œð†Šâ—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð†Šâ—ŒÌ–◌֚b; a◌᷺◌ð†Šâ—ŒÌ–◌֚b; a◌᷺◌ð†Šâ—ŒÌ–◌֚b; a◌᷺◌ð†Šâ—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING DOUBLE TONGUE, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1D18B 0062;0061 1DFA 0316 1D18B 059A 0062;0061 1DFA 0316 1D18B 059A 0062;0061 1DFA 0316 1D18B 059A 0062;0061 1DFA 0316 1D18B 059A 0062; # (a◌֚◌̖◌᷺◌ð†‹b; a◌᷺◌̖◌ð†‹â—ŒÖšb; a◌᷺◌̖◌ð†‹â—ŒÖšb; a◌᷺◌̖◌ð†‹â—ŒÖšb; a◌᷺◌̖◌ð†‹â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MUSICAL SYMBOL COMBINING TRIPLE TONGUE, LATIN SMALL LETTER B
+0061 1D18B 059A 0316 1DFA 0062;0061 1DFA 1D18B 0316 059A 0062;0061 1DFA 1D18B 0316 059A 0062;0061 1DFA 1D18B 0316 059A 0062;0061 1DFA 1D18B 0316 059A 0062; # (aâ—Œð†‹â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð†‹â—ŒÌ–◌֚b; a◌᷺◌ð†‹â—ŒÌ–◌֚b; a◌᷺◌ð†‹â—ŒÌ–◌֚b; a◌᷺◌ð†‹â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING TRIPLE TONGUE, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D1AA 0062;00E0 05AE 1D1AA 0315 0062;0061 05AE 0300 1D1AA 0315 0062;00E0 05AE 1D1AA 0315 0062;0061 05AE 0300 1D1AA 0315 0062; # (a◌̕◌̀◌֮◌ð†ªb; à◌֮◌ð†ªâ—ŒÌ•b; a◌֮◌̀◌ð†ªâ—ŒÌ•b; à◌֮◌ð†ªâ—ŒÌ•b; a◌֮◌̀◌ð†ªâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING DOWN BOW, LATIN SMALL LETTER B
+0061 1D1AA 0315 0300 05AE 0062;0061 05AE 1D1AA 0300 0315 0062;0061 05AE 1D1AA 0300 0315 0062;0061 05AE 1D1AA 0300 0315 0062;0061 05AE 1D1AA 0300 0315 0062; # (aâ—Œð†ªâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð†ªâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð†ªâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð†ªâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð†ªâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING DOWN BOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D1AB 0062;00E0 05AE 1D1AB 0315 0062;0061 05AE 0300 1D1AB 0315 0062;00E0 05AE 1D1AB 0315 0062;0061 05AE 0300 1D1AB 0315 0062; # (a◌̕◌̀◌֮◌ð†«b; à◌֮◌ð†«â—ŒÌ•b; a◌֮◌̀◌ð†«â—ŒÌ•b; à◌֮◌ð†«â—ŒÌ•b; a◌֮◌̀◌ð†«â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING UP BOW, LATIN SMALL LETTER B
+0061 1D1AB 0315 0300 05AE 0062;0061 05AE 1D1AB 0300 0315 0062;0061 05AE 1D1AB 0300 0315 0062;0061 05AE 1D1AB 0300 0315 0062;0061 05AE 1D1AB 0300 0315 0062; # (aâ—Œð†«â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð†«â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†«â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†«â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†«â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING UP BOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D1AC 0062;00E0 05AE 1D1AC 0315 0062;0061 05AE 0300 1D1AC 0315 0062;00E0 05AE 1D1AC 0315 0062;0061 05AE 0300 1D1AC 0315 0062; # (a◌̕◌̀◌֮◌ð†¬b; à◌֮◌ð†¬â—ŒÌ•b; a◌֮◌̀◌ð†¬â—ŒÌ•b; à◌֮◌ð†¬â—ŒÌ•b; a◌֮◌̀◌ð†¬â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING HARMONIC, LATIN SMALL LETTER B
+0061 1D1AC 0315 0300 05AE 0062;0061 05AE 1D1AC 0300 0315 0062;0061 05AE 1D1AC 0300 0315 0062;0061 05AE 1D1AC 0300 0315 0062;0061 05AE 1D1AC 0300 0315 0062; # (aâ—Œð†¬â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð†¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†¬â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING HARMONIC, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D1AD 0062;00E0 05AE 1D1AD 0315 0062;0061 05AE 0300 1D1AD 0315 0062;00E0 05AE 1D1AD 0315 0062;0061 05AE 0300 1D1AD 0315 0062; # (a◌̕◌̀◌֮◌ð†­b; à◌֮◌ð†­â—ŒÌ•b; a◌֮◌̀◌ð†­â—ŒÌ•b; à◌֮◌ð†­â—ŒÌ•b; a◌֮◌̀◌ð†­â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, MUSICAL SYMBOL COMBINING SNAP PIZZICATO, LATIN SMALL LETTER B
+0061 1D1AD 0315 0300 05AE 0062;0061 05AE 1D1AD 0300 0315 0062;0061 05AE 1D1AD 0300 0315 0062;0061 05AE 1D1AD 0300 0315 0062;0061 05AE 1D1AD 0300 0315 0062; # (aâ—Œð†­â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð†­â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†­â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†­â—ŒÌ€â—ŒÌ•b; a◌֮◌ð†­â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, MUSICAL SYMBOL COMBINING SNAP PIZZICATO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D242 0062;00E0 05AE 1D242 0315 0062;0061 05AE 0300 1D242 0315 0062;00E0 05AE 1D242 0315 0062;0061 05AE 0300 1D242 0315 0062; # (a◌̕◌̀◌֮◌ð‰‚b; à◌֮◌ð‰‚◌̕b; a◌֮◌̀◌ð‰‚◌̕b; à◌֮◌ð‰‚◌̕b; a◌֮◌̀◌ð‰‚◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GREEK MUSICAL TRISEME, LATIN SMALL LETTER B
+0061 1D242 0315 0300 05AE 0062;0061 05AE 1D242 0300 0315 0062;0061 05AE 1D242 0300 0315 0062;0061 05AE 1D242 0300 0315 0062;0061 05AE 1D242 0300 0315 0062; # (aâ—Œð‰‚◌̕◌̀◌֮b; a◌֮◌ð‰‚◌̀◌̕b; a◌֮◌ð‰‚◌̀◌̕b; a◌֮◌ð‰‚◌̀◌̕b; a◌֮◌ð‰‚◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GREEK MUSICAL TRISEME, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D243 0062;00E0 05AE 1D243 0315 0062;0061 05AE 0300 1D243 0315 0062;00E0 05AE 1D243 0315 0062;0061 05AE 0300 1D243 0315 0062; # (a◌̕◌̀◌֮◌ð‰ƒb; à◌֮◌ð‰ƒâ—ŒÌ•b; a◌֮◌̀◌ð‰ƒâ—ŒÌ•b; à◌֮◌ð‰ƒâ—ŒÌ•b; a◌֮◌̀◌ð‰ƒâ—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GREEK MUSICAL TETRASEME, LATIN SMALL LETTER B
+0061 1D243 0315 0300 05AE 0062;0061 05AE 1D243 0300 0315 0062;0061 05AE 1D243 0300 0315 0062;0061 05AE 1D243 0300 0315 0062;0061 05AE 1D243 0300 0315 0062; # (aâ—Œð‰ƒâ—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ð‰ƒâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð‰ƒâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð‰ƒâ—ŒÌ€â—ŒÌ•b; a◌֮◌ð‰ƒâ—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GREEK MUSICAL TETRASEME, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1D244 0062;00E0 05AE 1D244 0315 0062;0061 05AE 0300 1D244 0315 0062;00E0 05AE 1D244 0315 0062;0061 05AE 0300 1D244 0315 0062; # (a◌̕◌̀◌֮◌ð‰„b; à◌֮◌ð‰„◌̕b; a◌֮◌̀◌ð‰„◌̕b; à◌֮◌ð‰„◌̕b; a◌֮◌̀◌ð‰„◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GREEK MUSICAL PENTASEME, LATIN SMALL LETTER B
+0061 1D244 0315 0300 05AE 0062;0061 05AE 1D244 0300 0315 0062;0061 05AE 1D244 0300 0315 0062;0061 05AE 1D244 0300 0315 0062;0061 05AE 1D244 0300 0315 0062; # (aâ—Œð‰„◌̕◌̀◌֮b; a◌֮◌ð‰„◌̀◌̕b; a◌֮◌ð‰„◌̀◌̕b; a◌֮◌ð‰„◌̀◌̕b; a◌֮◌ð‰„◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GREEK MUSICAL PENTASEME, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E000 0062;00E0 05AE 1E000 0315 0062;0061 05AE 0300 1E000 0315 0062;00E0 05AE 1E000 0315 0062;0061 05AE 0300 1E000 0315 0062; # (a◌̕◌̀◌֮◌𞀀b; à◌֮◌𞀀◌̕b; a◌֮◌̀◌𞀀◌̕b; à◌֮◌𞀀◌̕b; a◌֮◌̀◌𞀀◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER AZU, LATIN SMALL LETTER B
+0061 1E000 0315 0300 05AE 0062;0061 05AE 1E000 0300 0315 0062;0061 05AE 1E000 0300 0315 0062;0061 05AE 1E000 0300 0315 0062;0061 05AE 1E000 0300 0315 0062; # (a◌𞀀◌̕◌̀◌֮b; a◌֮◌𞀀◌̀◌̕b; a◌֮◌𞀀◌̀◌̕b; a◌֮◌𞀀◌̀◌̕b; a◌֮◌𞀀◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER AZU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E001 0062;00E0 05AE 1E001 0315 0062;0061 05AE 0300 1E001 0315 0062;00E0 05AE 1E001 0315 0062;0061 05AE 0300 1E001 0315 0062; # (a◌̕◌̀◌֮◌ðž€b; à◌֮◌ðž€â—ŒÌ•b; a◌֮◌̀◌ðž€â—ŒÌ•b; à◌֮◌ðž€â—ŒÌ•b; a◌֮◌̀◌ðž€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER BUKY, LATIN SMALL LETTER B
+0061 1E001 0315 0300 05AE 0062;0061 05AE 1E001 0300 0315 0062;0061 05AE 1E001 0300 0315 0062;0061 05AE 1E001 0300 0315 0062;0061 05AE 1E001 0300 0315 0062; # (aâ—Œðž€â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER BUKY, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E002 0062;00E0 05AE 1E002 0315 0062;0061 05AE 0300 1E002 0315 0062;00E0 05AE 1E002 0315 0062;0061 05AE 0300 1E002 0315 0062; # (a◌̕◌̀◌֮◌𞀂b; à◌֮◌𞀂◌̕b; a◌֮◌̀◌𞀂◌̕b; à◌֮◌𞀂◌̕b; a◌֮◌̀◌𞀂◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER VEDE, LATIN SMALL LETTER B
+0061 1E002 0315 0300 05AE 0062;0061 05AE 1E002 0300 0315 0062;0061 05AE 1E002 0300 0315 0062;0061 05AE 1E002 0300 0315 0062;0061 05AE 1E002 0300 0315 0062; # (a◌𞀂◌̕◌̀◌֮b; a◌֮◌𞀂◌̀◌̕b; a◌֮◌𞀂◌̀◌̕b; a◌֮◌𞀂◌̀◌̕b; a◌֮◌𞀂◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER VEDE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E003 0062;00E0 05AE 1E003 0315 0062;0061 05AE 0300 1E003 0315 0062;00E0 05AE 1E003 0315 0062;0061 05AE 0300 1E003 0315 0062; # (a◌̕◌̀◌֮◌𞀃b; à◌֮◌𞀃◌̕b; a◌֮◌̀◌𞀃◌̕b; à◌֮◌𞀃◌̕b; a◌֮◌̀◌𞀃◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER GLAGOLI, LATIN SMALL LETTER B
+0061 1E003 0315 0300 05AE 0062;0061 05AE 1E003 0300 0315 0062;0061 05AE 1E003 0300 0315 0062;0061 05AE 1E003 0300 0315 0062;0061 05AE 1E003 0300 0315 0062; # (a◌𞀃◌̕◌̀◌֮b; a◌֮◌𞀃◌̀◌̕b; a◌֮◌𞀃◌̀◌̕b; a◌֮◌𞀃◌̀◌̕b; a◌֮◌𞀃◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER GLAGOLI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E004 0062;00E0 05AE 1E004 0315 0062;0061 05AE 0300 1E004 0315 0062;00E0 05AE 1E004 0315 0062;0061 05AE 0300 1E004 0315 0062; # (a◌̕◌̀◌֮◌𞀄b; à◌֮◌𞀄◌̕b; a◌֮◌̀◌𞀄◌̕b; à◌֮◌𞀄◌̕b; a◌֮◌̀◌𞀄◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER DOBRO, LATIN SMALL LETTER B
+0061 1E004 0315 0300 05AE 0062;0061 05AE 1E004 0300 0315 0062;0061 05AE 1E004 0300 0315 0062;0061 05AE 1E004 0300 0315 0062;0061 05AE 1E004 0300 0315 0062; # (a◌𞀄◌̕◌̀◌֮b; a◌֮◌𞀄◌̀◌̕b; a◌֮◌𞀄◌̀◌̕b; a◌֮◌𞀄◌̀◌̕b; a◌֮◌𞀄◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER DOBRO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E005 0062;00E0 05AE 1E005 0315 0062;0061 05AE 0300 1E005 0315 0062;00E0 05AE 1E005 0315 0062;0061 05AE 0300 1E005 0315 0062; # (a◌̕◌̀◌֮◌𞀅b; à◌֮◌𞀅◌̕b; a◌֮◌̀◌𞀅◌̕b; à◌֮◌𞀅◌̕b; a◌֮◌̀◌𞀅◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER YESTU, LATIN SMALL LETTER B
+0061 1E005 0315 0300 05AE 0062;0061 05AE 1E005 0300 0315 0062;0061 05AE 1E005 0300 0315 0062;0061 05AE 1E005 0300 0315 0062;0061 05AE 1E005 0300 0315 0062; # (a◌𞀅◌̕◌̀◌֮b; a◌֮◌𞀅◌̀◌̕b; a◌֮◌𞀅◌̀◌̕b; a◌֮◌𞀅◌̀◌̕b; a◌֮◌𞀅◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER YESTU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E006 0062;00E0 05AE 1E006 0315 0062;0061 05AE 0300 1E006 0315 0062;00E0 05AE 1E006 0315 0062;0061 05AE 0300 1E006 0315 0062; # (a◌̕◌̀◌֮◌𞀆b; à◌֮◌𞀆◌̕b; a◌֮◌̀◌𞀆◌̕b; à◌֮◌𞀆◌̕b; a◌֮◌̀◌𞀆◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER ZHIVETE, LATIN SMALL LETTER B
+0061 1E006 0315 0300 05AE 0062;0061 05AE 1E006 0300 0315 0062;0061 05AE 1E006 0300 0315 0062;0061 05AE 1E006 0300 0315 0062;0061 05AE 1E006 0300 0315 0062; # (a◌𞀆◌̕◌̀◌֮b; a◌֮◌𞀆◌̀◌̕b; a◌֮◌𞀆◌̀◌̕b; a◌֮◌𞀆◌̀◌̕b; a◌֮◌𞀆◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER ZHIVETE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E008 0062;00E0 05AE 1E008 0315 0062;0061 05AE 0300 1E008 0315 0062;00E0 05AE 1E008 0315 0062;0061 05AE 0300 1E008 0315 0062; # (a◌̕◌̀◌֮◌𞀈b; à◌֮◌𞀈◌̕b; a◌֮◌̀◌𞀈◌̕b; à◌֮◌𞀈◌̕b; a◌֮◌̀◌𞀈◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER ZEMLJA, LATIN SMALL LETTER B
+0061 1E008 0315 0300 05AE 0062;0061 05AE 1E008 0300 0315 0062;0061 05AE 1E008 0300 0315 0062;0061 05AE 1E008 0300 0315 0062;0061 05AE 1E008 0300 0315 0062; # (a◌𞀈◌̕◌̀◌֮b; a◌֮◌𞀈◌̀◌̕b; a◌֮◌𞀈◌̀◌̕b; a◌֮◌𞀈◌̀◌̕b; a◌֮◌𞀈◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER ZEMLJA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E009 0062;00E0 05AE 1E009 0315 0062;0061 05AE 0300 1E009 0315 0062;00E0 05AE 1E009 0315 0062;0061 05AE 0300 1E009 0315 0062; # (a◌̕◌̀◌֮◌𞀉b; à◌֮◌𞀉◌̕b; a◌֮◌̀◌𞀉◌̕b; à◌֮◌𞀉◌̕b; a◌֮◌̀◌𞀉◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER IZHE, LATIN SMALL LETTER B
+0061 1E009 0315 0300 05AE 0062;0061 05AE 1E009 0300 0315 0062;0061 05AE 1E009 0300 0315 0062;0061 05AE 1E009 0300 0315 0062;0061 05AE 1E009 0300 0315 0062; # (a◌𞀉◌̕◌̀◌֮b; a◌֮◌𞀉◌̀◌̕b; a◌֮◌𞀉◌̀◌̕b; a◌֮◌𞀉◌̀◌̕b; a◌֮◌𞀉◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER IZHE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E00A 0062;00E0 05AE 1E00A 0315 0062;0061 05AE 0300 1E00A 0315 0062;00E0 05AE 1E00A 0315 0062;0061 05AE 0300 1E00A 0315 0062; # (a◌̕◌̀◌֮◌𞀊b; à◌֮◌𞀊◌̕b; a◌֮◌̀◌𞀊◌̕b; à◌֮◌𞀊◌̕b; a◌֮◌̀◌𞀊◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER INITIAL IZHE, LATIN SMALL LETTER B
+0061 1E00A 0315 0300 05AE 0062;0061 05AE 1E00A 0300 0315 0062;0061 05AE 1E00A 0300 0315 0062;0061 05AE 1E00A 0300 0315 0062;0061 05AE 1E00A 0300 0315 0062; # (a◌𞀊◌̕◌̀◌֮b; a◌֮◌𞀊◌̀◌̕b; a◌֮◌𞀊◌̀◌̕b; a◌֮◌𞀊◌̀◌̕b; a◌֮◌𞀊◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER INITIAL IZHE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E00B 0062;00E0 05AE 1E00B 0315 0062;0061 05AE 0300 1E00B 0315 0062;00E0 05AE 1E00B 0315 0062;0061 05AE 0300 1E00B 0315 0062; # (a◌̕◌̀◌֮◌𞀋b; à◌֮◌𞀋◌̕b; a◌֮◌̀◌𞀋◌̕b; à◌֮◌𞀋◌̕b; a◌֮◌̀◌𞀋◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER I, LATIN SMALL LETTER B
+0061 1E00B 0315 0300 05AE 0062;0061 05AE 1E00B 0300 0315 0062;0061 05AE 1E00B 0300 0315 0062;0061 05AE 1E00B 0300 0315 0062;0061 05AE 1E00B 0300 0315 0062; # (a◌𞀋◌̕◌̀◌֮b; a◌֮◌𞀋◌̀◌̕b; a◌֮◌𞀋◌̀◌̕b; a◌֮◌𞀋◌̀◌̕b; a◌֮◌𞀋◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER I, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E00C 0062;00E0 05AE 1E00C 0315 0062;0061 05AE 0300 1E00C 0315 0062;00E0 05AE 1E00C 0315 0062;0061 05AE 0300 1E00C 0315 0062; # (a◌̕◌̀◌֮◌𞀌b; à◌֮◌𞀌◌̕b; a◌֮◌̀◌𞀌◌̕b; à◌֮◌𞀌◌̕b; a◌֮◌̀◌𞀌◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER DJERVI, LATIN SMALL LETTER B
+0061 1E00C 0315 0300 05AE 0062;0061 05AE 1E00C 0300 0315 0062;0061 05AE 1E00C 0300 0315 0062;0061 05AE 1E00C 0300 0315 0062;0061 05AE 1E00C 0300 0315 0062; # (a◌𞀌◌̕◌̀◌֮b; a◌֮◌𞀌◌̀◌̕b; a◌֮◌𞀌◌̀◌̕b; a◌֮◌𞀌◌̀◌̕b; a◌֮◌𞀌◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER DJERVI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E00D 0062;00E0 05AE 1E00D 0315 0062;0061 05AE 0300 1E00D 0315 0062;00E0 05AE 1E00D 0315 0062;0061 05AE 0300 1E00D 0315 0062; # (a◌̕◌̀◌֮◌ðž€b; à◌֮◌ðž€â—ŒÌ•b; a◌֮◌̀◌ðž€â—ŒÌ•b; à◌֮◌ðž€â—ŒÌ•b; a◌֮◌̀◌ðž€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER KAKO, LATIN SMALL LETTER B
+0061 1E00D 0315 0300 05AE 0062;0061 05AE 1E00D 0300 0315 0062;0061 05AE 1E00D 0300 0315 0062;0061 05AE 1E00D 0300 0315 0062;0061 05AE 1E00D 0300 0315 0062; # (aâ—Œðž€â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER KAKO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E00E 0062;00E0 05AE 1E00E 0315 0062;0061 05AE 0300 1E00E 0315 0062;00E0 05AE 1E00E 0315 0062;0061 05AE 0300 1E00E 0315 0062; # (a◌̕◌̀◌֮◌𞀎b; à◌֮◌𞀎◌̕b; a◌֮◌̀◌𞀎◌̕b; à◌֮◌𞀎◌̕b; a◌֮◌̀◌𞀎◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER LJUDIJE, LATIN SMALL LETTER B
+0061 1E00E 0315 0300 05AE 0062;0061 05AE 1E00E 0300 0315 0062;0061 05AE 1E00E 0300 0315 0062;0061 05AE 1E00E 0300 0315 0062;0061 05AE 1E00E 0300 0315 0062; # (a◌𞀎◌̕◌̀◌֮b; a◌֮◌𞀎◌̀◌̕b; a◌֮◌𞀎◌̀◌̕b; a◌֮◌𞀎◌̀◌̕b; a◌֮◌𞀎◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER LJUDIJE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E00F 0062;00E0 05AE 1E00F 0315 0062;0061 05AE 0300 1E00F 0315 0062;00E0 05AE 1E00F 0315 0062;0061 05AE 0300 1E00F 0315 0062; # (a◌̕◌̀◌֮◌ðž€b; à◌֮◌ðž€â—ŒÌ•b; a◌֮◌̀◌ðž€â—ŒÌ•b; à◌֮◌ðž€â—ŒÌ•b; a◌֮◌̀◌ðž€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER MYSLITE, LATIN SMALL LETTER B
+0061 1E00F 0315 0300 05AE 0062;0061 05AE 1E00F 0300 0315 0062;0061 05AE 1E00F 0300 0315 0062;0061 05AE 1E00F 0300 0315 0062;0061 05AE 1E00F 0300 0315 0062; # (aâ—Œðž€â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER MYSLITE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E010 0062;00E0 05AE 1E010 0315 0062;0061 05AE 0300 1E010 0315 0062;00E0 05AE 1E010 0315 0062;0061 05AE 0300 1E010 0315 0062; # (a◌̕◌̀◌֮◌ðž€b; à◌֮◌ðž€â—ŒÌ•b; a◌֮◌̀◌ðž€â—ŒÌ•b; à◌֮◌ðž€â—ŒÌ•b; a◌֮◌̀◌ðž€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER NASHI, LATIN SMALL LETTER B
+0061 1E010 0315 0300 05AE 0062;0061 05AE 1E010 0300 0315 0062;0061 05AE 1E010 0300 0315 0062;0061 05AE 1E010 0300 0315 0062;0061 05AE 1E010 0300 0315 0062; # (aâ—Œðž€â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER NASHI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E011 0062;00E0 05AE 1E011 0315 0062;0061 05AE 0300 1E011 0315 0062;00E0 05AE 1E011 0315 0062;0061 05AE 0300 1E011 0315 0062; # (a◌̕◌̀◌֮◌𞀑b; à◌֮◌𞀑◌̕b; a◌֮◌̀◌𞀑◌̕b; à◌֮◌𞀑◌̕b; a◌֮◌̀◌𞀑◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER ONU, LATIN SMALL LETTER B
+0061 1E011 0315 0300 05AE 0062;0061 05AE 1E011 0300 0315 0062;0061 05AE 1E011 0300 0315 0062;0061 05AE 1E011 0300 0315 0062;0061 05AE 1E011 0300 0315 0062; # (a◌𞀑◌̕◌̀◌֮b; a◌֮◌𞀑◌̀◌̕b; a◌֮◌𞀑◌̀◌̕b; a◌֮◌𞀑◌̀◌̕b; a◌֮◌𞀑◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER ONU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E012 0062;00E0 05AE 1E012 0315 0062;0061 05AE 0300 1E012 0315 0062;00E0 05AE 1E012 0315 0062;0061 05AE 0300 1E012 0315 0062; # (a◌̕◌̀◌֮◌𞀒b; à◌֮◌𞀒◌̕b; a◌֮◌̀◌𞀒◌̕b; à◌֮◌𞀒◌̕b; a◌֮◌̀◌𞀒◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER POKOJI, LATIN SMALL LETTER B
+0061 1E012 0315 0300 05AE 0062;0061 05AE 1E012 0300 0315 0062;0061 05AE 1E012 0300 0315 0062;0061 05AE 1E012 0300 0315 0062;0061 05AE 1E012 0300 0315 0062; # (a◌𞀒◌̕◌̀◌֮b; a◌֮◌𞀒◌̀◌̕b; a◌֮◌𞀒◌̀◌̕b; a◌֮◌𞀒◌̀◌̕b; a◌֮◌𞀒◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER POKOJI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E013 0062;00E0 05AE 1E013 0315 0062;0061 05AE 0300 1E013 0315 0062;00E0 05AE 1E013 0315 0062;0061 05AE 0300 1E013 0315 0062; # (a◌̕◌̀◌֮◌𞀓b; à◌֮◌𞀓◌̕b; a◌֮◌̀◌𞀓◌̕b; à◌֮◌𞀓◌̕b; a◌֮◌̀◌𞀓◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER RITSI, LATIN SMALL LETTER B
+0061 1E013 0315 0300 05AE 0062;0061 05AE 1E013 0300 0315 0062;0061 05AE 1E013 0300 0315 0062;0061 05AE 1E013 0300 0315 0062;0061 05AE 1E013 0300 0315 0062; # (a◌𞀓◌̕◌̀◌֮b; a◌֮◌𞀓◌̀◌̕b; a◌֮◌𞀓◌̀◌̕b; a◌֮◌𞀓◌̀◌̕b; a◌֮◌𞀓◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER RITSI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E014 0062;00E0 05AE 1E014 0315 0062;0061 05AE 0300 1E014 0315 0062;00E0 05AE 1E014 0315 0062;0061 05AE 0300 1E014 0315 0062; # (a◌̕◌̀◌֮◌𞀔b; à◌֮◌𞀔◌̕b; a◌֮◌̀◌𞀔◌̕b; à◌֮◌𞀔◌̕b; a◌֮◌̀◌𞀔◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER SLOVO, LATIN SMALL LETTER B
+0061 1E014 0315 0300 05AE 0062;0061 05AE 1E014 0300 0315 0062;0061 05AE 1E014 0300 0315 0062;0061 05AE 1E014 0300 0315 0062;0061 05AE 1E014 0300 0315 0062; # (a◌𞀔◌̕◌̀◌֮b; a◌֮◌𞀔◌̀◌̕b; a◌֮◌𞀔◌̀◌̕b; a◌֮◌𞀔◌̀◌̕b; a◌֮◌𞀔◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER SLOVO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E015 0062;00E0 05AE 1E015 0315 0062;0061 05AE 0300 1E015 0315 0062;00E0 05AE 1E015 0315 0062;0061 05AE 0300 1E015 0315 0062; # (a◌̕◌̀◌֮◌𞀕b; à◌֮◌𞀕◌̕b; a◌֮◌̀◌𞀕◌̕b; à◌֮◌𞀕◌̕b; a◌֮◌̀◌𞀕◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER TVRIDO, LATIN SMALL LETTER B
+0061 1E015 0315 0300 05AE 0062;0061 05AE 1E015 0300 0315 0062;0061 05AE 1E015 0300 0315 0062;0061 05AE 1E015 0300 0315 0062;0061 05AE 1E015 0300 0315 0062; # (a◌𞀕◌̕◌̀◌֮b; a◌֮◌𞀕◌̀◌̕b; a◌֮◌𞀕◌̀◌̕b; a◌֮◌𞀕◌̀◌̕b; a◌֮◌𞀕◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER TVRIDO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E016 0062;00E0 05AE 1E016 0315 0062;0061 05AE 0300 1E016 0315 0062;00E0 05AE 1E016 0315 0062;0061 05AE 0300 1E016 0315 0062; # (a◌̕◌̀◌֮◌𞀖b; à◌֮◌𞀖◌̕b; a◌֮◌̀◌𞀖◌̕b; à◌֮◌𞀖◌̕b; a◌֮◌̀◌𞀖◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER UKU, LATIN SMALL LETTER B
+0061 1E016 0315 0300 05AE 0062;0061 05AE 1E016 0300 0315 0062;0061 05AE 1E016 0300 0315 0062;0061 05AE 1E016 0300 0315 0062;0061 05AE 1E016 0300 0315 0062; # (a◌𞀖◌̕◌̀◌֮b; a◌֮◌𞀖◌̀◌̕b; a◌֮◌𞀖◌̀◌̕b; a◌֮◌𞀖◌̀◌̕b; a◌֮◌𞀖◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER UKU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E017 0062;00E0 05AE 1E017 0315 0062;0061 05AE 0300 1E017 0315 0062;00E0 05AE 1E017 0315 0062;0061 05AE 0300 1E017 0315 0062; # (a◌̕◌̀◌֮◌𞀗b; à◌֮◌𞀗◌̕b; a◌֮◌̀◌𞀗◌̕b; à◌֮◌𞀗◌̕b; a◌֮◌̀◌𞀗◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER FRITU, LATIN SMALL LETTER B
+0061 1E017 0315 0300 05AE 0062;0061 05AE 1E017 0300 0315 0062;0061 05AE 1E017 0300 0315 0062;0061 05AE 1E017 0300 0315 0062;0061 05AE 1E017 0300 0315 0062; # (a◌𞀗◌̕◌̀◌֮b; a◌֮◌𞀗◌̀◌̕b; a◌֮◌𞀗◌̀◌̕b; a◌֮◌𞀗◌̀◌̕b; a◌֮◌𞀗◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER FRITU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E018 0062;00E0 05AE 1E018 0315 0062;0061 05AE 0300 1E018 0315 0062;00E0 05AE 1E018 0315 0062;0061 05AE 0300 1E018 0315 0062; # (a◌̕◌̀◌֮◌𞀘b; à◌֮◌𞀘◌̕b; a◌֮◌̀◌𞀘◌̕b; à◌֮◌𞀘◌̕b; a◌֮◌̀◌𞀘◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER HERU, LATIN SMALL LETTER B
+0061 1E018 0315 0300 05AE 0062;0061 05AE 1E018 0300 0315 0062;0061 05AE 1E018 0300 0315 0062;0061 05AE 1E018 0300 0315 0062;0061 05AE 1E018 0300 0315 0062; # (a◌𞀘◌̕◌̀◌֮b; a◌֮◌𞀘◌̀◌̕b; a◌֮◌𞀘◌̀◌̕b; a◌֮◌𞀘◌̀◌̕b; a◌֮◌𞀘◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER HERU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E01B 0062;00E0 05AE 1E01B 0315 0062;0061 05AE 0300 1E01B 0315 0062;00E0 05AE 1E01B 0315 0062;0061 05AE 0300 1E01B 0315 0062; # (a◌̕◌̀◌֮◌𞀛b; à◌֮◌𞀛◌̕b; a◌֮◌̀◌𞀛◌̕b; à◌֮◌𞀛◌̕b; a◌֮◌̀◌𞀛◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER SHTA, LATIN SMALL LETTER B
+0061 1E01B 0315 0300 05AE 0062;0061 05AE 1E01B 0300 0315 0062;0061 05AE 1E01B 0300 0315 0062;0061 05AE 1E01B 0300 0315 0062;0061 05AE 1E01B 0300 0315 0062; # (a◌𞀛◌̕◌̀◌֮b; a◌֮◌𞀛◌̀◌̕b; a◌֮◌𞀛◌̀◌̕b; a◌֮◌𞀛◌̀◌̕b; a◌֮◌𞀛◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER SHTA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E01C 0062;00E0 05AE 1E01C 0315 0062;0061 05AE 0300 1E01C 0315 0062;00E0 05AE 1E01C 0315 0062;0061 05AE 0300 1E01C 0315 0062; # (a◌̕◌̀◌֮◌𞀜b; à◌֮◌𞀜◌̕b; a◌֮◌̀◌𞀜◌̕b; à◌֮◌𞀜◌̕b; a◌֮◌̀◌𞀜◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER TSI, LATIN SMALL LETTER B
+0061 1E01C 0315 0300 05AE 0062;0061 05AE 1E01C 0300 0315 0062;0061 05AE 1E01C 0300 0315 0062;0061 05AE 1E01C 0300 0315 0062;0061 05AE 1E01C 0300 0315 0062; # (a◌𞀜◌̕◌̀◌֮b; a◌֮◌𞀜◌̀◌̕b; a◌֮◌𞀜◌̀◌̕b; a◌֮◌𞀜◌̀◌̕b; a◌֮◌𞀜◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER TSI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E01D 0062;00E0 05AE 1E01D 0315 0062;0061 05AE 0300 1E01D 0315 0062;00E0 05AE 1E01D 0315 0062;0061 05AE 0300 1E01D 0315 0062; # (a◌̕◌̀◌֮◌ðž€b; à◌֮◌ðž€â—ŒÌ•b; a◌֮◌̀◌ðž€â—ŒÌ•b; à◌֮◌ðž€â—ŒÌ•b; a◌֮◌̀◌ðž€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER CHRIVI, LATIN SMALL LETTER B
+0061 1E01D 0315 0300 05AE 0062;0061 05AE 1E01D 0300 0315 0062;0061 05AE 1E01D 0300 0315 0062;0061 05AE 1E01D 0300 0315 0062;0061 05AE 1E01D 0300 0315 0062; # (aâ—Œðž€â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; a◌֮◌ðž€â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER CHRIVI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E01E 0062;00E0 05AE 1E01E 0315 0062;0061 05AE 0300 1E01E 0315 0062;00E0 05AE 1E01E 0315 0062;0061 05AE 0300 1E01E 0315 0062; # (a◌̕◌̀◌֮◌𞀞b; à◌֮◌𞀞◌̕b; a◌֮◌̀◌𞀞◌̕b; à◌֮◌𞀞◌̕b; a◌֮◌̀◌𞀞◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER SHA, LATIN SMALL LETTER B
+0061 1E01E 0315 0300 05AE 0062;0061 05AE 1E01E 0300 0315 0062;0061 05AE 1E01E 0300 0315 0062;0061 05AE 1E01E 0300 0315 0062;0061 05AE 1E01E 0300 0315 0062; # (a◌𞀞◌̕◌̀◌֮b; a◌֮◌𞀞◌̀◌̕b; a◌֮◌𞀞◌̀◌̕b; a◌֮◌𞀞◌̀◌̕b; a◌֮◌𞀞◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER SHA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E01F 0062;00E0 05AE 1E01F 0315 0062;0061 05AE 0300 1E01F 0315 0062;00E0 05AE 1E01F 0315 0062;0061 05AE 0300 1E01F 0315 0062; # (a◌̕◌̀◌֮◌𞀟b; à◌֮◌𞀟◌̕b; a◌֮◌̀◌𞀟◌̕b; à◌֮◌𞀟◌̕b; a◌֮◌̀◌𞀟◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER YERU, LATIN SMALL LETTER B
+0061 1E01F 0315 0300 05AE 0062;0061 05AE 1E01F 0300 0315 0062;0061 05AE 1E01F 0300 0315 0062;0061 05AE 1E01F 0300 0315 0062;0061 05AE 1E01F 0300 0315 0062; # (a◌𞀟◌̕◌̀◌֮b; a◌֮◌𞀟◌̀◌̕b; a◌֮◌𞀟◌̀◌̕b; a◌֮◌𞀟◌̀◌̕b; a◌֮◌𞀟◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER YERU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E020 0062;00E0 05AE 1E020 0315 0062;0061 05AE 0300 1E020 0315 0062;00E0 05AE 1E020 0315 0062;0061 05AE 0300 1E020 0315 0062; # (a◌̕◌̀◌֮◌𞀠b; à◌֮◌𞀠◌̕b; a◌֮◌̀◌𞀠◌̕b; à◌֮◌𞀠◌̕b; a◌֮◌̀◌𞀠◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER YERI, LATIN SMALL LETTER B
+0061 1E020 0315 0300 05AE 0062;0061 05AE 1E020 0300 0315 0062;0061 05AE 1E020 0300 0315 0062;0061 05AE 1E020 0300 0315 0062;0061 05AE 1E020 0300 0315 0062; # (a◌𞀠◌̕◌̀◌֮b; a◌֮◌𞀠◌̀◌̕b; a◌֮◌𞀠◌̀◌̕b; a◌֮◌𞀠◌̀◌̕b; a◌֮◌𞀠◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER YERI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E021 0062;00E0 05AE 1E021 0315 0062;0061 05AE 0300 1E021 0315 0062;00E0 05AE 1E021 0315 0062;0061 05AE 0300 1E021 0315 0062; # (a◌̕◌̀◌֮◌𞀡b; à◌֮◌𞀡◌̕b; a◌֮◌̀◌𞀡◌̕b; à◌֮◌𞀡◌̕b; a◌֮◌̀◌𞀡◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER YATI, LATIN SMALL LETTER B
+0061 1E021 0315 0300 05AE 0062;0061 05AE 1E021 0300 0315 0062;0061 05AE 1E021 0300 0315 0062;0061 05AE 1E021 0300 0315 0062;0061 05AE 1E021 0300 0315 0062; # (a◌𞀡◌̕◌̀◌֮b; a◌֮◌𞀡◌̀◌̕b; a◌֮◌𞀡◌̀◌̕b; a◌֮◌𞀡◌̀◌̕b; a◌֮◌𞀡◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER YATI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E023 0062;00E0 05AE 1E023 0315 0062;0061 05AE 0300 1E023 0315 0062;00E0 05AE 1E023 0315 0062;0061 05AE 0300 1E023 0315 0062; # (a◌̕◌̀◌֮◌𞀣b; à◌֮◌𞀣◌̕b; a◌֮◌̀◌𞀣◌̕b; à◌֮◌𞀣◌̕b; a◌֮◌̀◌𞀣◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER YU, LATIN SMALL LETTER B
+0061 1E023 0315 0300 05AE 0062;0061 05AE 1E023 0300 0315 0062;0061 05AE 1E023 0300 0315 0062;0061 05AE 1E023 0300 0315 0062;0061 05AE 1E023 0300 0315 0062; # (a◌𞀣◌̕◌̀◌֮b; a◌֮◌𞀣◌̀◌̕b; a◌֮◌𞀣◌̀◌̕b; a◌֮◌𞀣◌̀◌̕b; a◌֮◌𞀣◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER YU, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E024 0062;00E0 05AE 1E024 0315 0062;0061 05AE 0300 1E024 0315 0062;00E0 05AE 1E024 0315 0062;0061 05AE 0300 1E024 0315 0062; # (a◌̕◌̀◌֮◌𞀤b; à◌֮◌𞀤◌̕b; a◌֮◌̀◌𞀤◌̕b; à◌֮◌𞀤◌̕b; a◌֮◌̀◌𞀤◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER SMALL YUS, LATIN SMALL LETTER B
+0061 1E024 0315 0300 05AE 0062;0061 05AE 1E024 0300 0315 0062;0061 05AE 1E024 0300 0315 0062;0061 05AE 1E024 0300 0315 0062;0061 05AE 1E024 0300 0315 0062; # (a◌𞀤◌̕◌̀◌֮b; a◌֮◌𞀤◌̀◌̕b; a◌֮◌𞀤◌̀◌̕b; a◌֮◌𞀤◌̀◌̕b; a◌֮◌𞀤◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER SMALL YUS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E026 0062;00E0 05AE 1E026 0315 0062;0061 05AE 0300 1E026 0315 0062;00E0 05AE 1E026 0315 0062;0061 05AE 0300 1E026 0315 0062; # (a◌̕◌̀◌֮◌𞀦b; à◌֮◌𞀦◌̕b; a◌֮◌̀◌𞀦◌̕b; à◌֮◌𞀦◌̕b; a◌֮◌̀◌𞀦◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER YO, LATIN SMALL LETTER B
+0061 1E026 0315 0300 05AE 0062;0061 05AE 1E026 0300 0315 0062;0061 05AE 1E026 0300 0315 0062;0061 05AE 1E026 0300 0315 0062;0061 05AE 1E026 0300 0315 0062; # (a◌𞀦◌̕◌̀◌֮b; a◌֮◌𞀦◌̀◌̕b; a◌֮◌𞀦◌̀◌̕b; a◌֮◌𞀦◌̀◌̕b; a◌֮◌𞀦◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER YO, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E027 0062;00E0 05AE 1E027 0315 0062;0061 05AE 0300 1E027 0315 0062;00E0 05AE 1E027 0315 0062;0061 05AE 0300 1E027 0315 0062; # (a◌̕◌̀◌֮◌𞀧b; à◌֮◌𞀧◌̕b; a◌֮◌̀◌𞀧◌̕b; à◌֮◌𞀧◌̕b; a◌֮◌̀◌𞀧◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER IOTATED SMALL YUS, LATIN SMALL LETTER B
+0061 1E027 0315 0300 05AE 0062;0061 05AE 1E027 0300 0315 0062;0061 05AE 1E027 0300 0315 0062;0061 05AE 1E027 0300 0315 0062;0061 05AE 1E027 0300 0315 0062; # (a◌𞀧◌̕◌̀◌֮b; a◌֮◌𞀧◌̀◌̕b; a◌֮◌𞀧◌̀◌̕b; a◌֮◌𞀧◌̀◌̕b; a◌֮◌𞀧◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER IOTATED SMALL YUS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E028 0062;00E0 05AE 1E028 0315 0062;0061 05AE 0300 1E028 0315 0062;00E0 05AE 1E028 0315 0062;0061 05AE 0300 1E028 0315 0062; # (a◌̕◌̀◌֮◌𞀨b; à◌֮◌𞀨◌̕b; a◌֮◌̀◌𞀨◌̕b; à◌֮◌𞀨◌̕b; a◌֮◌̀◌𞀨◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER BIG YUS, LATIN SMALL LETTER B
+0061 1E028 0315 0300 05AE 0062;0061 05AE 1E028 0300 0315 0062;0061 05AE 1E028 0300 0315 0062;0061 05AE 1E028 0300 0315 0062;0061 05AE 1E028 0300 0315 0062; # (a◌𞀨◌̕◌̀◌֮b; a◌֮◌𞀨◌̀◌̕b; a◌֮◌𞀨◌̀◌̕b; a◌֮◌𞀨◌̀◌̕b; a◌֮◌𞀨◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER BIG YUS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E029 0062;00E0 05AE 1E029 0315 0062;0061 05AE 0300 1E029 0315 0062;00E0 05AE 1E029 0315 0062;0061 05AE 0300 1E029 0315 0062; # (a◌̕◌̀◌֮◌𞀩b; à◌֮◌𞀩◌̕b; a◌֮◌̀◌𞀩◌̕b; à◌֮◌𞀩◌̕b; a◌֮◌̀◌𞀩◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER IOTATED BIG YUS, LATIN SMALL LETTER B
+0061 1E029 0315 0300 05AE 0062;0061 05AE 1E029 0300 0315 0062;0061 05AE 1E029 0300 0315 0062;0061 05AE 1E029 0300 0315 0062;0061 05AE 1E029 0300 0315 0062; # (a◌𞀩◌̕◌̀◌֮b; a◌֮◌𞀩◌̀◌̕b; a◌֮◌𞀩◌̀◌̕b; a◌֮◌𞀩◌̀◌̕b; a◌֮◌𞀩◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER IOTATED BIG YUS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E02A 0062;00E0 05AE 1E02A 0315 0062;0061 05AE 0300 1E02A 0315 0062;00E0 05AE 1E02A 0315 0062;0061 05AE 0300 1E02A 0315 0062; # (a◌̕◌̀◌֮◌𞀪b; à◌֮◌𞀪◌̕b; a◌֮◌̀◌𞀪◌̕b; à◌֮◌𞀪◌̕b; a◌֮◌̀◌𞀪◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER FITA, LATIN SMALL LETTER B
+0061 1E02A 0315 0300 05AE 0062;0061 05AE 1E02A 0300 0315 0062;0061 05AE 1E02A 0300 0315 0062;0061 05AE 1E02A 0300 0315 0062;0061 05AE 1E02A 0300 0315 0062; # (a◌𞀪◌̕◌̀◌֮b; a◌֮◌𞀪◌̀◌̕b; a◌֮◌𞀪◌̀◌̕b; a◌֮◌𞀪◌̀◌̕b; a◌֮◌𞀪◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER FITA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E130 0062;00E0 05AE 1E130 0315 0062;0061 05AE 0300 1E130 0315 0062;00E0 05AE 1E130 0315 0062;0061 05AE 0300 1E130 0315 0062; # (a◌̕◌̀◌֮◌𞄰b; à◌֮◌𞄰◌̕b; a◌֮◌̀◌𞄰◌̕b; à◌֮◌𞄰◌̕b; a◌֮◌̀◌𞄰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-B, LATIN SMALL LETTER B
+0061 1E130 0315 0300 05AE 0062;0061 05AE 1E130 0300 0315 0062;0061 05AE 1E130 0300 0315 0062;0061 05AE 1E130 0300 0315 0062;0061 05AE 1E130 0300 0315 0062; # (a◌𞄰◌̕◌̀◌֮b; a◌֮◌𞄰◌̀◌̕b; a◌֮◌𞄰◌̀◌̕b; a◌֮◌𞄰◌̀◌̕b; a◌֮◌𞄰◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-B, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E131 0062;00E0 05AE 1E131 0315 0062;0061 05AE 0300 1E131 0315 0062;00E0 05AE 1E131 0315 0062;0061 05AE 0300 1E131 0315 0062; # (a◌̕◌̀◌֮◌𞄱b; à◌֮◌𞄱◌̕b; a◌֮◌̀◌𞄱◌̕b; à◌֮◌𞄱◌̕b; a◌֮◌̀◌𞄱◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-M, LATIN SMALL LETTER B
+0061 1E131 0315 0300 05AE 0062;0061 05AE 1E131 0300 0315 0062;0061 05AE 1E131 0300 0315 0062;0061 05AE 1E131 0300 0315 0062;0061 05AE 1E131 0300 0315 0062; # (a◌𞄱◌̕◌̀◌֮b; a◌֮◌𞄱◌̀◌̕b; a◌֮◌𞄱◌̀◌̕b; a◌֮◌𞄱◌̀◌̕b; a◌֮◌𞄱◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-M, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E132 0062;00E0 05AE 1E132 0315 0062;0061 05AE 0300 1E132 0315 0062;00E0 05AE 1E132 0315 0062;0061 05AE 0300 1E132 0315 0062; # (a◌̕◌̀◌֮◌𞄲b; à◌֮◌𞄲◌̕b; a◌֮◌̀◌𞄲◌̕b; à◌֮◌𞄲◌̕b; a◌֮◌̀◌𞄲◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-J, LATIN SMALL LETTER B
+0061 1E132 0315 0300 05AE 0062;0061 05AE 1E132 0300 0315 0062;0061 05AE 1E132 0300 0315 0062;0061 05AE 1E132 0300 0315 0062;0061 05AE 1E132 0300 0315 0062; # (a◌𞄲◌̕◌̀◌֮b; a◌֮◌𞄲◌̀◌̕b; a◌֮◌𞄲◌̀◌̕b; a◌֮◌𞄲◌̀◌̕b; a◌֮◌𞄲◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-J, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E133 0062;00E0 05AE 1E133 0315 0062;0061 05AE 0300 1E133 0315 0062;00E0 05AE 1E133 0315 0062;0061 05AE 0300 1E133 0315 0062; # (a◌̕◌̀◌֮◌𞄳b; à◌֮◌𞄳◌̕b; a◌֮◌̀◌𞄳◌̕b; à◌֮◌𞄳◌̕b; a◌֮◌̀◌𞄳◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-V, LATIN SMALL LETTER B
+0061 1E133 0315 0300 05AE 0062;0061 05AE 1E133 0300 0315 0062;0061 05AE 1E133 0300 0315 0062;0061 05AE 1E133 0300 0315 0062;0061 05AE 1E133 0300 0315 0062; # (a◌𞄳◌̕◌̀◌֮b; a◌֮◌𞄳◌̀◌̕b; a◌֮◌𞄳◌̀◌̕b; a◌֮◌𞄳◌̀◌̕b; a◌֮◌𞄳◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-V, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E134 0062;00E0 05AE 1E134 0315 0062;0061 05AE 0300 1E134 0315 0062;00E0 05AE 1E134 0315 0062;0061 05AE 0300 1E134 0315 0062; # (a◌̕◌̀◌֮◌𞄴b; à◌֮◌𞄴◌̕b; a◌֮◌̀◌𞄴◌̕b; à◌֮◌𞄴◌̕b; a◌֮◌̀◌𞄴◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-S, LATIN SMALL LETTER B
+0061 1E134 0315 0300 05AE 0062;0061 05AE 1E134 0300 0315 0062;0061 05AE 1E134 0300 0315 0062;0061 05AE 1E134 0300 0315 0062;0061 05AE 1E134 0300 0315 0062; # (a◌𞄴◌̕◌̀◌֮b; a◌֮◌𞄴◌̀◌̕b; a◌֮◌𞄴◌̀◌̕b; a◌֮◌𞄴◌̀◌̕b; a◌֮◌𞄴◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-S, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E135 0062;00E0 05AE 1E135 0315 0062;0061 05AE 0300 1E135 0315 0062;00E0 05AE 1E135 0315 0062;0061 05AE 0300 1E135 0315 0062; # (a◌̕◌̀◌֮◌𞄵b; à◌֮◌𞄵◌̕b; a◌֮◌̀◌𞄵◌̕b; à◌֮◌𞄵◌̕b; a◌֮◌̀◌𞄵◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-G, LATIN SMALL LETTER B
+0061 1E135 0315 0300 05AE 0062;0061 05AE 1E135 0300 0315 0062;0061 05AE 1E135 0300 0315 0062;0061 05AE 1E135 0300 0315 0062;0061 05AE 1E135 0300 0315 0062; # (a◌𞄵◌̕◌̀◌֮b; a◌֮◌𞄵◌̀◌̕b; a◌֮◌𞄵◌̀◌̕b; a◌֮◌𞄵◌̀◌̕b; a◌֮◌𞄵◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-G, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E136 0062;00E0 05AE 1E136 0315 0062;0061 05AE 0300 1E136 0315 0062;00E0 05AE 1E136 0315 0062;0061 05AE 0300 1E136 0315 0062; # (a◌̕◌̀◌֮◌𞄶b; à◌֮◌𞄶◌̕b; a◌֮◌̀◌𞄶◌̕b; à◌֮◌𞄶◌̕b; a◌֮◌̀◌𞄶◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-D, LATIN SMALL LETTER B
+0061 1E136 0315 0300 05AE 0062;0061 05AE 1E136 0300 0315 0062;0061 05AE 1E136 0300 0315 0062;0061 05AE 1E136 0300 0315 0062;0061 05AE 1E136 0300 0315 0062; # (a◌𞄶◌̕◌̀◌֮b; a◌֮◌𞄶◌̀◌̕b; a◌֮◌𞄶◌̀◌̕b; a◌֮◌𞄶◌̀◌̕b; a◌֮◌𞄶◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-D, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E2AE 0062;00E0 05AE 1E2AE 0315 0062;0061 05AE 0300 1E2AE 0315 0062;00E0 05AE 1E2AE 0315 0062;0061 05AE 0300 1E2AE 0315 0062; # (a◌̕◌̀◌֮◌𞊮b; à◌֮◌𞊮◌̕b; a◌֮◌̀◌𞊮◌̕b; à◌֮◌𞊮◌̕b; a◌֮◌̀◌𞊮◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, TOTO SIGN RISING TONE, LATIN SMALL LETTER B
+0061 1E2AE 0315 0300 05AE 0062;0061 05AE 1E2AE 0300 0315 0062;0061 05AE 1E2AE 0300 0315 0062;0061 05AE 1E2AE 0300 0315 0062;0061 05AE 1E2AE 0300 0315 0062; # (a◌𞊮◌̕◌̀◌֮b; a◌֮◌𞊮◌̀◌̕b; a◌֮◌𞊮◌̀◌̕b; a◌֮◌𞊮◌̀◌̕b; a◌֮◌𞊮◌̀◌̕b; ) LATIN SMALL LETTER A, TOTO SIGN RISING TONE, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E2EC 0062;00E0 05AE 1E2EC 0315 0062;0061 05AE 0300 1E2EC 0315 0062;00E0 05AE 1E2EC 0315 0062;0061 05AE 0300 1E2EC 0315 0062; # (a◌̕◌̀◌֮◌𞋬b; à◌֮◌𞋬◌̕b; a◌֮◌̀◌𞋬◌̕b; à◌֮◌𞋬◌̕b; a◌֮◌̀◌𞋬◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, WANCHO TONE TUP, LATIN SMALL LETTER B
+0061 1E2EC 0315 0300 05AE 0062;0061 05AE 1E2EC 0300 0315 0062;0061 05AE 1E2EC 0300 0315 0062;0061 05AE 1E2EC 0300 0315 0062;0061 05AE 1E2EC 0300 0315 0062; # (a◌𞋬◌̕◌̀◌֮b; a◌֮◌𞋬◌̀◌̕b; a◌֮◌𞋬◌̀◌̕b; a◌֮◌𞋬◌̀◌̕b; a◌֮◌𞋬◌̀◌̕b; ) LATIN SMALL LETTER A, WANCHO TONE TUP, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E2ED 0062;00E0 05AE 1E2ED 0315 0062;0061 05AE 0300 1E2ED 0315 0062;00E0 05AE 1E2ED 0315 0062;0061 05AE 0300 1E2ED 0315 0062; # (a◌̕◌̀◌֮◌𞋭b; à◌֮◌𞋭◌̕b; a◌֮◌̀◌𞋭◌̕b; à◌֮◌𞋭◌̕b; a◌֮◌̀◌𞋭◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, WANCHO TONE TUPNI, LATIN SMALL LETTER B
+0061 1E2ED 0315 0300 05AE 0062;0061 05AE 1E2ED 0300 0315 0062;0061 05AE 1E2ED 0300 0315 0062;0061 05AE 1E2ED 0300 0315 0062;0061 05AE 1E2ED 0300 0315 0062; # (a◌𞋭◌̕◌̀◌֮b; a◌֮◌𞋭◌̀◌̕b; a◌֮◌𞋭◌̀◌̕b; a◌֮◌𞋭◌̀◌̕b; a◌֮◌𞋭◌̀◌̕b; ) LATIN SMALL LETTER A, WANCHO TONE TUPNI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E2EE 0062;00E0 05AE 1E2EE 0315 0062;0061 05AE 0300 1E2EE 0315 0062;00E0 05AE 1E2EE 0315 0062;0061 05AE 0300 1E2EE 0315 0062; # (a◌̕◌̀◌֮◌𞋮b; à◌֮◌𞋮◌̕b; a◌֮◌̀◌𞋮◌̕b; à◌֮◌𞋮◌̕b; a◌֮◌̀◌𞋮◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, WANCHO TONE KOI, LATIN SMALL LETTER B
+0061 1E2EE 0315 0300 05AE 0062;0061 05AE 1E2EE 0300 0315 0062;0061 05AE 1E2EE 0300 0315 0062;0061 05AE 1E2EE 0300 0315 0062;0061 05AE 1E2EE 0300 0315 0062; # (a◌𞋮◌̕◌̀◌֮b; a◌֮◌𞋮◌̀◌̕b; a◌֮◌𞋮◌̀◌̕b; a◌֮◌𞋮◌̀◌̕b; a◌֮◌𞋮◌̀◌̕b; ) LATIN SMALL LETTER A, WANCHO TONE KOI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E2EF 0062;00E0 05AE 1E2EF 0315 0062;0061 05AE 0300 1E2EF 0315 0062;00E0 05AE 1E2EF 0315 0062;0061 05AE 0300 1E2EF 0315 0062; # (a◌̕◌̀◌֮◌𞋯b; à◌֮◌𞋯◌̕b; a◌֮◌̀◌𞋯◌̕b; à◌֮◌𞋯◌̕b; a◌֮◌̀◌𞋯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, WANCHO TONE KOINI, LATIN SMALL LETTER B
+0061 1E2EF 0315 0300 05AE 0062;0061 05AE 1E2EF 0300 0315 0062;0061 05AE 1E2EF 0300 0315 0062;0061 05AE 1E2EF 0300 0315 0062;0061 05AE 1E2EF 0300 0315 0062; # (a◌𞋯◌̕◌̀◌֮b; a◌֮◌𞋯◌̀◌̕b; a◌֮◌𞋯◌̀◌̕b; a◌֮◌𞋯◌̀◌̕b; a◌֮◌𞋯◌̀◌̕b; ) LATIN SMALL LETTER A, WANCHO TONE KOINI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1E8D0 0062;0061 1DFA 0316 1E8D0 059A 0062;0061 1DFA 0316 1E8D0 059A 0062;0061 1DFA 0316 1E8D0 059A 0062;0061 1DFA 0316 1E8D0 059A 0062; # (a◌֚◌̖◌᷺◌ðž£b; a◌᷺◌̖◌ðž£â—ŒÖšb; a◌᷺◌̖◌ðž£â—ŒÖšb; a◌᷺◌̖◌ðž£â—ŒÖšb; a◌᷺◌̖◌ðž£â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MENDE KIKAKUI COMBINING NUMBER TEENS, LATIN SMALL LETTER B
+0061 1E8D0 059A 0316 1DFA 0062;0061 1DFA 1E8D0 0316 059A 0062;0061 1DFA 1E8D0 0316 059A 0062;0061 1DFA 1E8D0 0316 059A 0062;0061 1DFA 1E8D0 0316 059A 0062; # (aâ—Œðž£â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ðž£â—ŒÌ–◌֚b; a◌᷺◌ðž£â—ŒÌ–◌֚b; a◌᷺◌ðž£â—ŒÌ–◌֚b; a◌᷺◌ðž£â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MENDE KIKAKUI COMBINING NUMBER TEENS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1E8D1 0062;0061 1DFA 0316 1E8D1 059A 0062;0061 1DFA 0316 1E8D1 059A 0062;0061 1DFA 0316 1E8D1 059A 0062;0061 1DFA 0316 1E8D1 059A 0062; # (a◌֚◌̖◌᷺◌𞣑b; a◌᷺◌̖◌𞣑◌֚b; a◌᷺◌̖◌𞣑◌֚b; a◌᷺◌̖◌𞣑◌֚b; a◌᷺◌̖◌𞣑◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MENDE KIKAKUI COMBINING NUMBER TENS, LATIN SMALL LETTER B
+0061 1E8D1 059A 0316 1DFA 0062;0061 1DFA 1E8D1 0316 059A 0062;0061 1DFA 1E8D1 0316 059A 0062;0061 1DFA 1E8D1 0316 059A 0062;0061 1DFA 1E8D1 0316 059A 0062; # (a◌𞣑◌֚◌̖◌᷺b; a◌᷺◌𞣑◌̖◌֚b; a◌᷺◌𞣑◌̖◌֚b; a◌᷺◌𞣑◌̖◌֚b; a◌᷺◌𞣑◌̖◌֚b; ) LATIN SMALL LETTER A, MENDE KIKAKUI COMBINING NUMBER TENS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1E8D2 0062;0061 1DFA 0316 1E8D2 059A 0062;0061 1DFA 0316 1E8D2 059A 0062;0061 1DFA 0316 1E8D2 059A 0062;0061 1DFA 0316 1E8D2 059A 0062; # (a◌֚◌̖◌᷺◌𞣒b; a◌᷺◌̖◌𞣒◌֚b; a◌᷺◌̖◌𞣒◌֚b; a◌᷺◌̖◌𞣒◌֚b; a◌᷺◌̖◌𞣒◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MENDE KIKAKUI COMBINING NUMBER HUNDREDS, LATIN SMALL LETTER B
+0061 1E8D2 059A 0316 1DFA 0062;0061 1DFA 1E8D2 0316 059A 0062;0061 1DFA 1E8D2 0316 059A 0062;0061 1DFA 1E8D2 0316 059A 0062;0061 1DFA 1E8D2 0316 059A 0062; # (a◌𞣒◌֚◌̖◌᷺b; a◌᷺◌𞣒◌̖◌֚b; a◌᷺◌𞣒◌̖◌֚b; a◌᷺◌𞣒◌̖◌֚b; a◌᷺◌𞣒◌̖◌֚b; ) LATIN SMALL LETTER A, MENDE KIKAKUI COMBINING NUMBER HUNDREDS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1E8D3 0062;0061 1DFA 0316 1E8D3 059A 0062;0061 1DFA 0316 1E8D3 059A 0062;0061 1DFA 0316 1E8D3 059A 0062;0061 1DFA 0316 1E8D3 059A 0062; # (a◌֚◌̖◌᷺◌𞣓b; a◌᷺◌̖◌𞣓◌֚b; a◌᷺◌̖◌𞣓◌֚b; a◌᷺◌̖◌𞣓◌֚b; a◌᷺◌̖◌𞣓◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MENDE KIKAKUI COMBINING NUMBER THOUSANDS, LATIN SMALL LETTER B
+0061 1E8D3 059A 0316 1DFA 0062;0061 1DFA 1E8D3 0316 059A 0062;0061 1DFA 1E8D3 0316 059A 0062;0061 1DFA 1E8D3 0316 059A 0062;0061 1DFA 1E8D3 0316 059A 0062; # (a◌𞣓◌֚◌̖◌᷺b; a◌᷺◌𞣓◌̖◌֚b; a◌᷺◌𞣓◌̖◌֚b; a◌᷺◌𞣓◌̖◌֚b; a◌᷺◌𞣓◌̖◌֚b; ) LATIN SMALL LETTER A, MENDE KIKAKUI COMBINING NUMBER THOUSANDS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1E8D4 0062;0061 1DFA 0316 1E8D4 059A 0062;0061 1DFA 0316 1E8D4 059A 0062;0061 1DFA 0316 1E8D4 059A 0062;0061 1DFA 0316 1E8D4 059A 0062; # (a◌֚◌̖◌᷺◌𞣔b; a◌᷺◌̖◌𞣔◌֚b; a◌᷺◌̖◌𞣔◌֚b; a◌᷺◌̖◌𞣔◌֚b; a◌᷺◌̖◌𞣔◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MENDE KIKAKUI COMBINING NUMBER TEN THOUSANDS, LATIN SMALL LETTER B
+0061 1E8D4 059A 0316 1DFA 0062;0061 1DFA 1E8D4 0316 059A 0062;0061 1DFA 1E8D4 0316 059A 0062;0061 1DFA 1E8D4 0316 059A 0062;0061 1DFA 1E8D4 0316 059A 0062; # (a◌𞣔◌֚◌̖◌᷺b; a◌᷺◌𞣔◌̖◌֚b; a◌᷺◌𞣔◌̖◌֚b; a◌᷺◌𞣔◌̖◌֚b; a◌᷺◌𞣔◌̖◌֚b; ) LATIN SMALL LETTER A, MENDE KIKAKUI COMBINING NUMBER TEN THOUSANDS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1E8D5 0062;0061 1DFA 0316 1E8D5 059A 0062;0061 1DFA 0316 1E8D5 059A 0062;0061 1DFA 0316 1E8D5 059A 0062;0061 1DFA 0316 1E8D5 059A 0062; # (a◌֚◌̖◌᷺◌𞣕b; a◌᷺◌̖◌𞣕◌֚b; a◌᷺◌̖◌𞣕◌֚b; a◌᷺◌̖◌𞣕◌֚b; a◌᷺◌̖◌𞣕◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MENDE KIKAKUI COMBINING NUMBER HUNDRED THOUSANDS, LATIN SMALL LETTER B
+0061 1E8D5 059A 0316 1DFA 0062;0061 1DFA 1E8D5 0316 059A 0062;0061 1DFA 1E8D5 0316 059A 0062;0061 1DFA 1E8D5 0316 059A 0062;0061 1DFA 1E8D5 0316 059A 0062; # (a◌𞣕◌֚◌̖◌᷺b; a◌᷺◌𞣕◌̖◌֚b; a◌᷺◌𞣕◌̖◌֚b; a◌᷺◌𞣕◌̖◌֚b; a◌᷺◌𞣕◌̖◌֚b; ) LATIN SMALL LETTER A, MENDE KIKAKUI COMBINING NUMBER HUNDRED THOUSANDS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1E8D6 0062;0061 1DFA 0316 1E8D6 059A 0062;0061 1DFA 0316 1E8D6 059A 0062;0061 1DFA 0316 1E8D6 059A 0062;0061 1DFA 0316 1E8D6 059A 0062; # (a◌֚◌̖◌᷺◌𞣖b; a◌᷺◌̖◌𞣖◌֚b; a◌᷺◌̖◌𞣖◌֚b; a◌᷺◌̖◌𞣖◌֚b; a◌᷺◌̖◌𞣖◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MENDE KIKAKUI COMBINING NUMBER MILLIONS, LATIN SMALL LETTER B
+0061 1E8D6 059A 0316 1DFA 0062;0061 1DFA 1E8D6 0316 059A 0062;0061 1DFA 1E8D6 0316 059A 0062;0061 1DFA 1E8D6 0316 059A 0062;0061 1DFA 1E8D6 0316 059A 0062; # (a◌𞣖◌֚◌̖◌᷺b; a◌᷺◌𞣖◌̖◌֚b; a◌᷺◌𞣖◌̖◌֚b; a◌᷺◌𞣖◌̖◌֚b; a◌᷺◌𞣖◌̖◌֚b; ) LATIN SMALL LETTER A, MENDE KIKAKUI COMBINING NUMBER MILLIONS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E944 0062;00E0 05AE 1E944 0315 0062;0061 05AE 0300 1E944 0315 0062;00E0 05AE 1E944 0315 0062;0061 05AE 0300 1E944 0315 0062; # (a◌̕◌̀◌֮◌𞥄b; à◌֮◌𞥄◌̕b; a◌֮◌̀◌𞥄◌̕b; à◌֮◌𞥄◌̕b; a◌֮◌̀◌𞥄◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ADLAM ALIF LENGTHENER, LATIN SMALL LETTER B
+0061 1E944 0315 0300 05AE 0062;0061 05AE 1E944 0300 0315 0062;0061 05AE 1E944 0300 0315 0062;0061 05AE 1E944 0300 0315 0062;0061 05AE 1E944 0300 0315 0062; # (a◌𞥄◌̕◌̀◌֮b; a◌֮◌𞥄◌̀◌̕b; a◌֮◌𞥄◌̀◌̕b; a◌֮◌𞥄◌̀◌̕b; a◌֮◌𞥄◌̀◌̕b; ) LATIN SMALL LETTER A, ADLAM ALIF LENGTHENER, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E945 0062;00E0 05AE 1E945 0315 0062;0061 05AE 0300 1E945 0315 0062;00E0 05AE 1E945 0315 0062;0061 05AE 0300 1E945 0315 0062; # (a◌̕◌̀◌֮◌𞥅b; à◌֮◌𞥅◌̕b; a◌֮◌̀◌𞥅◌̕b; à◌֮◌𞥅◌̕b; a◌֮◌̀◌𞥅◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ADLAM VOWEL LENGTHENER, LATIN SMALL LETTER B
+0061 1E945 0315 0300 05AE 0062;0061 05AE 1E945 0300 0315 0062;0061 05AE 1E945 0300 0315 0062;0061 05AE 1E945 0300 0315 0062;0061 05AE 1E945 0300 0315 0062; # (a◌𞥅◌̕◌̀◌֮b; a◌֮◌𞥅◌̀◌̕b; a◌֮◌𞥅◌̀◌̕b; a◌֮◌𞥅◌̀◌̕b; a◌֮◌𞥅◌̀◌̕b; ) LATIN SMALL LETTER A, ADLAM VOWEL LENGTHENER, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E946 0062;00E0 05AE 1E946 0315 0062;0061 05AE 0300 1E946 0315 0062;00E0 05AE 1E946 0315 0062;0061 05AE 0300 1E946 0315 0062; # (a◌̕◌̀◌֮◌𞥆b; à◌֮◌𞥆◌̕b; a◌֮◌̀◌𞥆◌̕b; à◌֮◌𞥆◌̕b; a◌֮◌̀◌𞥆◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ADLAM GEMINATION MARK, LATIN SMALL LETTER B
+0061 1E946 0315 0300 05AE 0062;0061 05AE 1E946 0300 0315 0062;0061 05AE 1E946 0300 0315 0062;0061 05AE 1E946 0300 0315 0062;0061 05AE 1E946 0300 0315 0062; # (a◌𞥆◌̕◌̀◌֮b; a◌֮◌𞥆◌̀◌̕b; a◌֮◌𞥆◌̀◌̕b; a◌֮◌𞥆◌̀◌̕b; a◌֮◌𞥆◌̀◌̕b; ) LATIN SMALL LETTER A, ADLAM GEMINATION MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E947 0062;00E0 05AE 1E947 0315 0062;0061 05AE 0300 1E947 0315 0062;00E0 05AE 1E947 0315 0062;0061 05AE 0300 1E947 0315 0062; # (a◌̕◌̀◌֮◌𞥇b; à◌֮◌𞥇◌̕b; a◌֮◌̀◌𞥇◌̕b; à◌֮◌𞥇◌̕b; a◌֮◌̀◌𞥇◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ADLAM HAMZA, LATIN SMALL LETTER B
+0061 1E947 0315 0300 05AE 0062;0061 05AE 1E947 0300 0315 0062;0061 05AE 1E947 0300 0315 0062;0061 05AE 1E947 0300 0315 0062;0061 05AE 1E947 0300 0315 0062; # (a◌𞥇◌̕◌̀◌֮b; a◌֮◌𞥇◌̀◌̕b; a◌֮◌𞥇◌̀◌̕b; a◌֮◌𞥇◌̀◌̕b; a◌֮◌𞥇◌̀◌̕b; ) LATIN SMALL LETTER A, ADLAM HAMZA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E948 0062;00E0 05AE 1E948 0315 0062;0061 05AE 0300 1E948 0315 0062;00E0 05AE 1E948 0315 0062;0061 05AE 0300 1E948 0315 0062; # (a◌̕◌̀◌֮◌𞥈b; à◌֮◌𞥈◌̕b; a◌֮◌̀◌𞥈◌̕b; à◌֮◌𞥈◌̕b; a◌֮◌̀◌𞥈◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ADLAM CONSONANT MODIFIER, LATIN SMALL LETTER B
+0061 1E948 0315 0300 05AE 0062;0061 05AE 1E948 0300 0315 0062;0061 05AE 1E948 0300 0315 0062;0061 05AE 1E948 0300 0315 0062;0061 05AE 1E948 0300 0315 0062; # (a◌𞥈◌̕◌̀◌֮b; a◌֮◌𞥈◌̀◌̕b; a◌֮◌𞥈◌̀◌̕b; a◌֮◌𞥈◌̀◌̕b; a◌֮◌𞥈◌̀◌̕b; ) LATIN SMALL LETTER A, ADLAM CONSONANT MODIFIER, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E949 0062;00E0 05AE 1E949 0315 0062;0061 05AE 0300 1E949 0315 0062;00E0 05AE 1E949 0315 0062;0061 05AE 0300 1E949 0315 0062; # (a◌̕◌̀◌֮◌𞥉b; à◌֮◌𞥉◌̕b; a◌֮◌̀◌𞥉◌̕b; à◌֮◌𞥉◌̕b; a◌֮◌̀◌𞥉◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, ADLAM GEMINATE CONSONANT MODIFIER, LATIN SMALL LETTER B
+0061 1E949 0315 0300 05AE 0062;0061 05AE 1E949 0300 0315 0062;0061 05AE 1E949 0300 0315 0062;0061 05AE 1E949 0300 0315 0062;0061 05AE 1E949 0300 0315 0062; # (a◌𞥉◌̕◌̀◌֮b; a◌֮◌𞥉◌̀◌̕b; a◌֮◌𞥉◌̀◌̕b; a◌֮◌𞥉◌̀◌̕b; a◌֮◌𞥉◌̀◌̕b; ) LATIN SMALL LETTER A, ADLAM GEMINATE CONSONANT MODIFIER, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 3099 093C 16FF0 1E94A 0062;0061 16FF0 093C 1E94A 3099 0062;0061 16FF0 093C 1E94A 3099 0062;0061 16FF0 093C 1E94A 3099 0062;0061 16FF0 093C 1E94A 3099 0062; # (a◌゙◌𖿰़◌𞥊b; a𖿰◌़◌𞥊◌゙b; a𖿰◌़◌𞥊◌゙b; a𖿰◌़◌𞥊◌゙b; a𖿰◌़◌𞥊◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, ADLAM NUKTA, LATIN SMALL LETTER B
+0061 1E94A 3099 093C 16FF0 0062;0061 16FF0 1E94A 093C 3099 0062;0061 16FF0 1E94A 093C 3099 0062;0061 16FF0 1E94A 093C 3099 0062;0061 16FF0 1E94A 093C 3099 0062; # (a◌𞥊◌゙◌𖿰़b; a𖿰◌𞥊◌़◌゙b; a𖿰◌𞥊◌़◌゙b; a𖿰◌𞥊◌़◌゙b; a𖿰◌𞥊◌़◌゙b; ) LATIN SMALL LETTER A, ADLAM NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, VIETNAMESE ALTERNATE READING MARK CA, LATIN SMALL LETTER B
+#
+@Part3 # PRI #29 Test
+#
+09C7 0334 09BE;09C7 0334 09BE;09C7 0334 09BE;09C7 0334 09BE;09C7 0334 09BE; # (ে◌̴া; ে◌̴া; ে◌̴া; ে◌̴া; ে◌̴া; ) BENGALI VOWEL SIGN E, COMBINING TILDE OVERLAY, BENGALI VOWEL SIGN AA
+09C7 0334 09D7;09C7 0334 09D7;09C7 0334 09D7;09C7 0334 09D7;09C7 0334 09D7; # (ে◌̴ৗ; ে◌̴ৗ; ে◌̴ৗ; ে◌̴ৗ; ে◌̴ৗ; ) BENGALI VOWEL SIGN E, COMBINING TILDE OVERLAY, BENGALI AU LENGTH MARK
+0B47 0334 0B3E;0B47 0334 0B3E;0B47 0334 0B3E;0B47 0334 0B3E;0B47 0334 0B3E; # (େ◌̴ା; େ◌̴ା; େ◌̴ା; େ◌̴ା; େ◌̴ା; ) ORIYA VOWEL SIGN E, COMBINING TILDE OVERLAY, ORIYA VOWEL SIGN AA
+0B47 0334 0B56;0B47 0334 0B56;0B47 0334 0B56;0B47 0334 0B56;0B47 0334 0B56; # (େ◌̴◌ୖ; େ◌̴◌ୖ; େ◌̴◌ୖ; େ◌̴◌ୖ; େ◌̴◌ୖ; ) ORIYA VOWEL SIGN E, COMBINING TILDE OVERLAY, ORIYA AI LENGTH MARK
+0B47 0334 0B57;0B47 0334 0B57;0B47 0334 0B57;0B47 0334 0B57;0B47 0334 0B57; # (େ◌̴ୗ; େ◌̴ୗ; େ◌̴ୗ; େ◌̴ୗ; େ◌̴ୗ; ) ORIYA VOWEL SIGN E, COMBINING TILDE OVERLAY, ORIYA AU LENGTH MARK
+0B92 0334 0BD7;0B92 0334 0BD7;0B92 0334 0BD7;0B92 0334 0BD7;0B92 0334 0BD7; # (ஒ◌̴ௗ; ஒ◌̴ௗ; ஒ◌̴ௗ; ஒ◌̴ௗ; ஒ◌̴ௗ; ) TAMIL LETTER O, COMBINING TILDE OVERLAY, TAMIL AU LENGTH MARK
+0BC6 0334 0BBE;0BC6 0334 0BBE;0BC6 0334 0BBE;0BC6 0334 0BBE;0BC6 0334 0BBE; # (ெ◌̴ா; ெ◌̴ா; ெ◌̴ா; ெ◌̴ா; ெ◌̴ா; ) TAMIL VOWEL SIGN E, COMBINING TILDE OVERLAY, TAMIL VOWEL SIGN AA
+0BC6 0334 0BD7;0BC6 0334 0BD7;0BC6 0334 0BD7;0BC6 0334 0BD7;0BC6 0334 0BD7; # (ெ◌̴ௗ; ெ◌̴ௗ; ெ◌̴ௗ; ெ◌̴ௗ; ெ◌̴ௗ; ) TAMIL VOWEL SIGN E, COMBINING TILDE OVERLAY, TAMIL AU LENGTH MARK
+0BC7 0334 0BBE;0BC7 0334 0BBE;0BC7 0334 0BBE;0BC7 0334 0BBE;0BC7 0334 0BBE; # (ே◌̴ா; ே◌̴ா; ே◌̴ா; ே◌̴ா; ே◌̴ா; ) TAMIL VOWEL SIGN EE, COMBINING TILDE OVERLAY, TAMIL VOWEL SIGN AA
+0CBF 0334 0CD5;0CBF 0334 0CD5;0CBF 0334 0CD5;0CBF 0334 0CD5;0CBF 0334 0CD5; # (◌ಿ◌̴ೕ; ◌ಿ◌̴ೕ; ◌ಿ◌̴ೕ; ◌ಿ◌̴ೕ; ◌ಿ◌̴ೕ; ) KANNADA VOWEL SIGN I, COMBINING TILDE OVERLAY, KANNADA LENGTH MARK
+0CC6 0334 0CC2;0CC6 0334 0CC2;0CC6 0334 0CC2;0CC6 0334 0CC2;0CC6 0334 0CC2; # (◌ೆ◌̴ೂ; ◌ೆ◌̴ೂ; ◌ೆ◌̴ೂ; ◌ೆ◌̴ೂ; ◌ೆ◌̴ೂ; ) KANNADA VOWEL SIGN E, COMBINING TILDE OVERLAY, KANNADA VOWEL SIGN UU
+0CC6 0334 0CD5;0CC6 0334 0CD5;0CC6 0334 0CD5;0CC6 0334 0CD5;0CC6 0334 0CD5; # (◌ೆ◌̴ೕ; ◌ೆ◌̴ೕ; ◌ೆ◌̴ೕ; ◌ೆ◌̴ೕ; ◌ೆ◌̴ೕ; ) KANNADA VOWEL SIGN E, COMBINING TILDE OVERLAY, KANNADA LENGTH MARK
+0CC6 0334 0CD6;0CC6 0334 0CD6;0CC6 0334 0CD6;0CC6 0334 0CD6;0CC6 0334 0CD6; # (◌ೆ◌̴ೖ; ◌ೆ◌̴ೖ; ◌ೆ◌̴ೖ; ◌ೆ◌̴ೖ; ◌ೆ◌̴ೖ; ) KANNADA VOWEL SIGN E, COMBINING TILDE OVERLAY, KANNADA AI LENGTH MARK
+0CCA 0334 0CD5;0CCA 0334 0CD5;0CC6 0CC2 0334 0CD5;0CCA 0334 0CD5;0CC6 0CC2 0334 0CD5; # (ೊ◌̴ೕ; ೊ◌̴ೕ; ◌ೊ◌̴ೕ; ೊ◌̴ೕ; ◌ೊ◌̴ೕ; ) KANNADA VOWEL SIGN O, COMBINING TILDE OVERLAY, KANNADA LENGTH MARK
+0D46 0334 0D3E;0D46 0334 0D3E;0D46 0334 0D3E;0D46 0334 0D3E;0D46 0334 0D3E; # (െ◌̴ാ; െ◌̴ാ; െ◌̴ാ; െ◌̴ാ; െ◌̴ാ; ) MALAYALAM VOWEL SIGN E, COMBINING TILDE OVERLAY, MALAYALAM VOWEL SIGN AA
+0D46 0334 0D57;0D46 0334 0D57;0D46 0334 0D57;0D46 0334 0D57;0D46 0334 0D57; # (െ◌̴ൗ; െ◌̴ൗ; െ◌̴ൗ; െ◌̴ൗ; െ◌̴ൗ; ) MALAYALAM VOWEL SIGN E, COMBINING TILDE OVERLAY, MALAYALAM AU LENGTH MARK
+0D47 0334 0D3E;0D47 0334 0D3E;0D47 0334 0D3E;0D47 0334 0D3E;0D47 0334 0D3E; # (േ◌̴ാ; േ◌̴ാ; േ◌̴ാ; േ◌̴ാ; േ◌̴ാ; ) MALAYALAM VOWEL SIGN EE, COMBINING TILDE OVERLAY, MALAYALAM VOWEL SIGN AA
+0DD9 0334 0DCF;0DD9 0334 0DCF;0DD9 0334 0DCF;0DD9 0334 0DCF;0DD9 0334 0DCF; # (ෙ◌̴à·; ෙ◌̴à·; ෙ◌̴à·; ෙ◌̴à·; ෙ◌̴à·; ) SINHALA VOWEL SIGN KOMBUVA, COMBINING TILDE OVERLAY, SINHALA VOWEL SIGN AELA-PILLA
+0DD9 0334 0DDF;0DD9 0334 0DDF;0DD9 0334 0DDF;0DD9 0334 0DDF;0DD9 0334 0DDF; # (ෙ◌̴ෟ; ෙ◌̴ෟ; ෙ◌̴ෟ; ෙ◌̴ෟ; ෙ◌̴ෟ; ) SINHALA VOWEL SIGN KOMBUVA, COMBINING TILDE OVERLAY, SINHALA VOWEL SIGN GAYANUKITTA
+0F40 0334 0FB5;0F40 0334 0FB5;0F40 0334 0FB5;0F40 0334 0FB5;0F40 0334 0FB5; # (ཀ◌̴◌ྵ; ཀ◌̴◌ྵ; ཀ◌̴◌ྵ; ཀ◌̴◌ྵ; ཀ◌̴◌ྵ; ) TIBETAN LETTER KA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER SSA
+0F42 0334 0FB7;0F42 0334 0FB7;0F42 0334 0FB7;0F42 0334 0FB7;0F42 0334 0FB7; # (ག◌̴◌ྷ; ག◌̴◌ྷ; ག◌̴◌ྷ; ག◌̴◌ྷ; ག◌̴◌ྷ; ) TIBETAN LETTER GA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER HA
+0F4C 0334 0FB7;0F4C 0334 0FB7;0F4C 0334 0FB7;0F4C 0334 0FB7;0F4C 0334 0FB7; # (ཌ◌̴◌ྷ; ཌ◌̴◌ྷ; ཌ◌̴◌ྷ; ཌ◌̴◌ྷ; ཌ◌̴◌ྷ; ) TIBETAN LETTER DDA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER HA
+0F51 0334 0FB7;0F51 0334 0FB7;0F51 0334 0FB7;0F51 0334 0FB7;0F51 0334 0FB7; # (ད◌̴◌ྷ; ད◌̴◌ྷ; ད◌̴◌ྷ; ད◌̴◌ྷ; ད◌̴◌ྷ; ) TIBETAN LETTER DA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER HA
+0F56 0334 0FB7;0F56 0334 0FB7;0F56 0334 0FB7;0F56 0334 0FB7;0F56 0334 0FB7; # (བ◌̴◌ྷ; བ◌̴◌ྷ; བ◌̴◌ྷ; བ◌̴◌ྷ; བ◌̴◌ྷ; ) TIBETAN LETTER BA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER HA
+0F5B 0334 0FB7;0F5B 0334 0FB7;0F5B 0334 0FB7;0F5B 0334 0FB7;0F5B 0334 0FB7; # (ཛ◌̴◌ྷ; ཛ◌̴◌ྷ; ཛ◌̴◌ྷ; ཛ◌̴◌ྷ; ཛ◌̴◌ྷ; ) TIBETAN LETTER DZA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER HA
+0F90 0334 0FB5;0F90 0334 0FB5;0F90 0334 0FB5;0F90 0334 0FB5;0F90 0334 0FB5; # (â—Œà¾â—ŒÌ´â—Œà¾µ; â—Œà¾â—ŒÌ´â—Œà¾µ; â—Œà¾â—ŒÌ´â—Œà¾µ; â—Œà¾â—ŒÌ´â—Œà¾µ; â—Œà¾â—ŒÌ´â—Œà¾µ; ) TIBETAN SUBJOINED LETTER KA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER SSA
+0F92 0334 0FB7;0F92 0334 0FB7;0F92 0334 0FB7;0F92 0334 0FB7;0F92 0334 0FB7; # (◌ྒ◌̴◌ྷ; ◌ྒ◌̴◌ྷ; ◌ྒ◌̴◌ྷ; ◌ྒ◌̴◌ྷ; ◌ྒ◌̴◌ྷ; ) TIBETAN SUBJOINED LETTER GA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER HA
+0F9C 0334 0FB7;0F9C 0334 0FB7;0F9C 0334 0FB7;0F9C 0334 0FB7;0F9C 0334 0FB7; # (◌ྜ◌̴◌ྷ; ◌ྜ◌̴◌ྷ; ◌ྜ◌̴◌ྷ; ◌ྜ◌̴◌ྷ; ◌ྜ◌̴◌ྷ; ) TIBETAN SUBJOINED LETTER DDA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER HA
+0FA1 0334 0FB7;0FA1 0334 0FB7;0FA1 0334 0FB7;0FA1 0334 0FB7;0FA1 0334 0FB7; # (◌ྡ◌̴◌ྷ; ◌ྡ◌̴◌ྷ; ◌ྡ◌̴◌ྷ; ◌ྡ◌̴◌ྷ; ◌ྡ◌̴◌ྷ; ) TIBETAN SUBJOINED LETTER DA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER HA
+0FA6 0334 0FB7;0FA6 0334 0FB7;0FA6 0334 0FB7;0FA6 0334 0FB7;0FA6 0334 0FB7; # (◌ྦ◌̴◌ྷ; ◌ྦ◌̴◌ྷ; ◌ྦ◌̴◌ྷ; ◌ྦ◌̴◌ྷ; ◌ྦ◌̴◌ྷ; ) TIBETAN SUBJOINED LETTER BA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER HA
+0FAB 0334 0FB7;0FAB 0334 0FB7;0FAB 0334 0FB7;0FAB 0334 0FB7;0FAB 0334 0FB7; # (◌ྫ◌̴◌ྷ; ◌ྫ◌̴◌ྷ; ◌ྫ◌̴◌ྷ; ◌ྫ◌̴◌ྷ; ◌ྫ◌̴◌ྷ; ) TIBETAN SUBJOINED LETTER DZA, COMBINING TILDE OVERLAY, TIBETAN SUBJOINED LETTER HA
+1025 0334 102E;1025 0334 102E;1025 0334 102E;1025 0334 102E;1025 0334 102E; # (ဥ◌̴◌ီ; ဥ◌̴◌ီ; ဥ◌̴◌ီ; ဥ◌̴◌ီ; ဥ◌̴◌ီ; ) MYANMAR LETTER U, COMBINING TILDE OVERLAY, MYANMAR VOWEL SIGN II
+1100 0334 1161;1100 0334 1161;1100 0334 1161;1100 0334 1161;1100 0334 1161; # (ᄀ◌̴ᅡ; ᄀ◌̴ᅡ; ᄀ◌̴ᅡ; ᄀ◌̴ᅡ; ᄀ◌̴ᅡ; ) HANGUL CHOSEONG KIYEOK, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG A
+1100 0334 116E;1100 0334 116E;1100 0334 116E;1100 0334 116E;1100 0334 116E; # (ᄀ◌̴ᅮ; ᄀ◌̴ᅮ; ᄀ◌̴ᅮ; ᄀ◌̴ᅮ; ᄀ◌̴ᅮ; ) HANGUL CHOSEONG KIYEOK, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG U
+1101 0334 1166;1101 0334 1166;1101 0334 1166;1101 0334 1166;1101 0334 1166; # (á„◌̴ᅦ; á„◌̴ᅦ; á„◌̴ᅦ; á„◌̴ᅦ; á„◌̴ᅦ; ) HANGUL CHOSEONG SSANGKIYEOK, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG E
+1101 0334 1173;1101 0334 1173;1101 0334 1173;1101 0334 1173;1101 0334 1173; # (á„◌̴ᅳ; á„◌̴ᅳ; á„◌̴ᅳ; á„◌̴ᅳ; á„◌̴ᅳ; ) HANGUL CHOSEONG SSANGKIYEOK, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG EU
+1102 0334 116B;1102 0334 116B;1102 0334 116B;1102 0334 116B;1102 0334 116B; # (ᄂ◌̴ᅫ; ᄂ◌̴ᅫ; ᄂ◌̴ᅫ; ᄂ◌̴ᅫ; ᄂ◌̴ᅫ; ) HANGUL CHOSEONG NIEUN, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG WAE
+1103 0334 1163;1103 0334 1163;1103 0334 1163;1103 0334 1163;1103 0334 1163; # (ᄃ◌̴ᅣ; ᄃ◌̴ᅣ; ᄃ◌̴ᅣ; ᄃ◌̴ᅣ; ᄃ◌̴ᅣ; ) HANGUL CHOSEONG TIKEUT, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG YA
+1103 0334 1170;1103 0334 1170;1103 0334 1170;1103 0334 1170;1103 0334 1170; # (ᄃ◌̴ᅰ; ᄃ◌̴ᅰ; ᄃ◌̴ᅰ; ᄃ◌̴ᅰ; ᄃ◌̴ᅰ; ) HANGUL CHOSEONG TIKEUT, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG WE
+1104 0334 1168;1104 0334 1168;1104 0334 1168;1104 0334 1168;1104 0334 1168; # (ᄄ◌̴ᅨ; ᄄ◌̴ᅨ; ᄄ◌̴ᅨ; ᄄ◌̴ᅨ; ᄄ◌̴ᅨ; ) HANGUL CHOSEONG SSANGTIKEUT, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG YE
+1104 0334 1175;1104 0334 1175;1104 0334 1175;1104 0334 1175;1104 0334 1175; # (ᄄ◌̴ᅵ; ᄄ◌̴ᅵ; ᄄ◌̴ᅵ; ᄄ◌̴ᅵ; ᄄ◌̴ᅵ; ) HANGUL CHOSEONG SSANGTIKEUT, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG I
+1105 0334 116D;1105 0334 116D;1105 0334 116D;1105 0334 116D;1105 0334 116D; # (ᄅ◌̴ᅭ; ᄅ◌̴ᅭ; ᄅ◌̴ᅭ; ᄅ◌̴ᅭ; ᄅ◌̴ᅭ; ) HANGUL CHOSEONG RIEUL, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG YO
+1106 0334 1165;1106 0334 1165;1106 0334 1165;1106 0334 1165;1106 0334 1165; # (ᄆ◌̴ᅥ; ᄆ◌̴ᅥ; ᄆ◌̴ᅥ; ᄆ◌̴ᅥ; ᄆ◌̴ᅥ; ) HANGUL CHOSEONG MIEUM, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG EO
+1106 0334 1172;1106 0334 1172;1106 0334 1172;1106 0334 1172;1106 0334 1172; # (ᄆ◌̴ᅲ; ᄆ◌̴ᅲ; ᄆ◌̴ᅲ; ᄆ◌̴ᅲ; ᄆ◌̴ᅲ; ) HANGUL CHOSEONG MIEUM, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG YU
+1107 0334 116A;1107 0334 116A;1107 0334 116A;1107 0334 116A;1107 0334 116A; # (ᄇ◌̴ᅪ; ᄇ◌̴ᅪ; ᄇ◌̴ᅪ; ᄇ◌̴ᅪ; ᄇ◌̴ᅪ; ) HANGUL CHOSEONG PIEUP, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG WA
+1108 0334 1162;1108 0334 1162;1108 0334 1162;1108 0334 1162;1108 0334 1162; # (ᄈ◌̴ᅢ; ᄈ◌̴ᅢ; ᄈ◌̴ᅢ; ᄈ◌̴ᅢ; ᄈ◌̴ᅢ; ) HANGUL CHOSEONG SSANGPIEUP, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG AE
+1108 0334 116F;1108 0334 116F;1108 0334 116F;1108 0334 116F;1108 0334 116F; # (ᄈ◌̴ᅯ; ᄈ◌̴ᅯ; ᄈ◌̴ᅯ; ᄈ◌̴ᅯ; ᄈ◌̴ᅯ; ) HANGUL CHOSEONG SSANGPIEUP, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG WEO
+1109 0334 1167;1109 0334 1167;1109 0334 1167;1109 0334 1167;1109 0334 1167; # (ᄉ◌̴ᅧ; ᄉ◌̴ᅧ; ᄉ◌̴ᅧ; ᄉ◌̴ᅧ; ᄉ◌̴ᅧ; ) HANGUL CHOSEONG SIOS, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG YEO
+1109 0334 1174;1109 0334 1174;1109 0334 1174;1109 0334 1174;1109 0334 1174; # (ᄉ◌̴ᅴ; ᄉ◌̴ᅴ; ᄉ◌̴ᅴ; ᄉ◌̴ᅴ; ᄉ◌̴ᅴ; ) HANGUL CHOSEONG SIOS, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG YI
+110A 0334 116C;110A 0334 116C;110A 0334 116C;110A 0334 116C;110A 0334 116C; # (ᄊ◌̴ᅬ; ᄊ◌̴ᅬ; ᄊ◌̴ᅬ; ᄊ◌̴ᅬ; ᄊ◌̴ᅬ; ) HANGUL CHOSEONG SSANGSIOS, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG OE
+110B 0334 1164;110B 0334 1164;110B 0334 1164;110B 0334 1164;110B 0334 1164; # (ᄋ◌̴ᅤ; ᄋ◌̴ᅤ; ᄋ◌̴ᅤ; ᄋ◌̴ᅤ; ᄋ◌̴ᅤ; ) HANGUL CHOSEONG IEUNG, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG YAE
+110B 0334 1171;110B 0334 1171;110B 0334 1171;110B 0334 1171;110B 0334 1171; # (ᄋ◌̴ᅱ; ᄋ◌̴ᅱ; ᄋ◌̴ᅱ; ᄋ◌̴ᅱ; ᄋ◌̴ᅱ; ) HANGUL CHOSEONG IEUNG, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG WI
+110C 0334 1169;110C 0334 1169;110C 0334 1169;110C 0334 1169;110C 0334 1169; # (ᄌ◌̴ᅩ; ᄌ◌̴ᅩ; ᄌ◌̴ᅩ; ᄌ◌̴ᅩ; ᄌ◌̴ᅩ; ) HANGUL CHOSEONG CIEUC, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG O
+110D 0334 1161;110D 0334 1161;110D 0334 1161;110D 0334 1161;110D 0334 1161; # (á„◌̴ᅡ; á„◌̴ᅡ; á„◌̴ᅡ; á„◌̴ᅡ; á„◌̴ᅡ; ) HANGUL CHOSEONG SSANGCIEUC, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG A
+110D 0334 116E;110D 0334 116E;110D 0334 116E;110D 0334 116E;110D 0334 116E; # (á„◌̴ᅮ; á„◌̴ᅮ; á„◌̴ᅮ; á„◌̴ᅮ; á„◌̴ᅮ; ) HANGUL CHOSEONG SSANGCIEUC, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG U
+110E 0334 1166;110E 0334 1166;110E 0334 1166;110E 0334 1166;110E 0334 1166; # (ᄎ◌̴ᅦ; ᄎ◌̴ᅦ; ᄎ◌̴ᅦ; ᄎ◌̴ᅦ; ᄎ◌̴ᅦ; ) HANGUL CHOSEONG CHIEUCH, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG E
+110E 0334 1173;110E 0334 1173;110E 0334 1173;110E 0334 1173;110E 0334 1173; # (ᄎ◌̴ᅳ; ᄎ◌̴ᅳ; ᄎ◌̴ᅳ; ᄎ◌̴ᅳ; ᄎ◌̴ᅳ; ) HANGUL CHOSEONG CHIEUCH, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG EU
+110F 0334 116B;110F 0334 116B;110F 0334 116B;110F 0334 116B;110F 0334 116B; # (á„◌̴ᅫ; á„◌̴ᅫ; á„◌̴ᅫ; á„◌̴ᅫ; á„◌̴ᅫ; ) HANGUL CHOSEONG KHIEUKH, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG WAE
+1110 0334 1163;1110 0334 1163;1110 0334 1163;1110 0334 1163;1110 0334 1163; # (á„◌̴ᅣ; á„◌̴ᅣ; á„◌̴ᅣ; á„◌̴ᅣ; á„◌̴ᅣ; ) HANGUL CHOSEONG THIEUTH, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG YA
+1110 0334 1170;1110 0334 1170;1110 0334 1170;1110 0334 1170;1110 0334 1170; # (á„◌̴ᅰ; á„◌̴ᅰ; á„◌̴ᅰ; á„◌̴ᅰ; á„◌̴ᅰ; ) HANGUL CHOSEONG THIEUTH, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG WE
+1111 0334 1168;1111 0334 1168;1111 0334 1168;1111 0334 1168;1111 0334 1168; # (ᄑ◌̴ᅨ; ᄑ◌̴ᅨ; ᄑ◌̴ᅨ; ᄑ◌̴ᅨ; ᄑ◌̴ᅨ; ) HANGUL CHOSEONG PHIEUPH, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG YE
+1111 0334 1175;1111 0334 1175;1111 0334 1175;1111 0334 1175;1111 0334 1175; # (ᄑ◌̴ᅵ; ᄑ◌̴ᅵ; ᄑ◌̴ᅵ; ᄑ◌̴ᅵ; ᄑ◌̴ᅵ; ) HANGUL CHOSEONG PHIEUPH, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG I
+1112 0334 116D;1112 0334 116D;1112 0334 116D;1112 0334 116D;1112 0334 116D; # (ᄒ◌̴ᅭ; ᄒ◌̴ᅭ; ᄒ◌̴ᅭ; ᄒ◌̴ᅭ; ᄒ◌̴ᅭ; ) HANGUL CHOSEONG HIEUH, COMBINING TILDE OVERLAY, HANGUL JUNGSEONG YO
+1B05 0334 1B35;1B05 0334 1B35;1B05 0334 1B35;1B05 0334 1B35;1B05 0334 1B35; # (ᬅ◌̴ᬵ; ᬅ◌̴ᬵ; ᬅ◌̴ᬵ; ᬅ◌̴ᬵ; ᬅ◌̴ᬵ; ) BALINESE LETTER AKARA, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+1B07 0334 1B35;1B07 0334 1B35;1B07 0334 1B35;1B07 0334 1B35;1B07 0334 1B35; # (ᬇ◌̴ᬵ; ᬇ◌̴ᬵ; ᬇ◌̴ᬵ; ᬇ◌̴ᬵ; ᬇ◌̴ᬵ; ) BALINESE LETTER IKARA, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+1B09 0334 1B35;1B09 0334 1B35;1B09 0334 1B35;1B09 0334 1B35;1B09 0334 1B35; # (ᬉ◌̴ᬵ; ᬉ◌̴ᬵ; ᬉ◌̴ᬵ; ᬉ◌̴ᬵ; ᬉ◌̴ᬵ; ) BALINESE LETTER UKARA, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+1B0B 0334 1B35;1B0B 0334 1B35;1B0B 0334 1B35;1B0B 0334 1B35;1B0B 0334 1B35; # (ᬋ◌̴ᬵ; ᬋ◌̴ᬵ; ᬋ◌̴ᬵ; ᬋ◌̴ᬵ; ᬋ◌̴ᬵ; ) BALINESE LETTER RA REPA, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+1B0D 0334 1B35;1B0D 0334 1B35;1B0D 0334 1B35;1B0D 0334 1B35;1B0D 0334 1B35; # (á¬â—ŒÌ´á¬µ; á¬â—ŒÌ´á¬µ; á¬â—ŒÌ´á¬µ; á¬â—ŒÌ´á¬µ; á¬â—ŒÌ´á¬µ; ) BALINESE LETTER LA LENGA, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+1B11 0334 1B35;1B11 0334 1B35;1B11 0334 1B35;1B11 0334 1B35;1B11 0334 1B35; # (ᬑ◌̴ᬵ; ᬑ◌̴ᬵ; ᬑ◌̴ᬵ; ᬑ◌̴ᬵ; ᬑ◌̴ᬵ; ) BALINESE LETTER OKARA, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+1B3A 0334 1B35;1B3A 0334 1B35;1B3A 0334 1B35;1B3A 0334 1B35;1B3A 0334 1B35; # (◌ᬺ◌̴ᬵ; ◌ᬺ◌̴ᬵ; ◌ᬺ◌̴ᬵ; ◌ᬺ◌̴ᬵ; ◌ᬺ◌̴ᬵ; ) BALINESE VOWEL SIGN RA REPA, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+1B3C 0334 1B35;1B3C 0334 1B35;1B3C 0334 1B35;1B3C 0334 1B35;1B3C 0334 1B35; # (◌ᬼ◌̴ᬵ; ◌ᬼ◌̴ᬵ; ◌ᬼ◌̴ᬵ; ◌ᬼ◌̴ᬵ; ◌ᬼ◌̴ᬵ; ) BALINESE VOWEL SIGN LA LENGA, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+1B3E 0334 1B35;1B3E 0334 1B35;1B3E 0334 1B35;1B3E 0334 1B35;1B3E 0334 1B35; # (ᬾ◌̴ᬵ; ᬾ◌̴ᬵ; ᬾ◌̴ᬵ; ᬾ◌̴ᬵ; ᬾ◌̴ᬵ; ) BALINESE VOWEL SIGN TALING, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+1B3F 0334 1B35;1B3F 0334 1B35;1B3F 0334 1B35;1B3F 0334 1B35;1B3F 0334 1B35; # (ᬿ◌̴ᬵ; ᬿ◌̴ᬵ; ᬿ◌̴ᬵ; ᬿ◌̴ᬵ; ᬿ◌̴ᬵ; ) BALINESE VOWEL SIGN TALING REPA, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+1B42 0334 1B35;1B42 0334 1B35;1B42 0334 1B35;1B42 0334 1B35;1B42 0334 1B35; # (◌ᭂ◌̴ᬵ; ◌ᭂ◌̴ᬵ; ◌ᭂ◌̴ᬵ; ◌ᭂ◌̴ᬵ; ◌ᭂ◌̴ᬵ; ) BALINESE VOWEL SIGN PEPET, COMBINING TILDE OVERLAY, BALINESE VOWEL SIGN TEDUNG
+AC54 0334 11AE;AC54 0334 11AE;1100 1164 0334 11AE;AC54 0334 11AE;1100 1164 0334 11AE; # (걔◌̴ᆮ; 걔◌̴ᆮ; 걔◌̴ᆮ; 걔◌̴ᆮ; 걔◌̴ᆮ; ) HANGUL SYLLABLE GYAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+ACA8 0334 11B5;ACA8 0334 11B5;1100 1167 0334 11B5;ACA8 0334 11B5;1100 1167 0334 11B5; # (겨◌̴ᆵ; 겨◌̴ᆵ; 겨◌̴ᆵ; 겨◌̴ᆵ; 겨◌̴ᆵ; ) HANGUL SYLLABLE GYEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+ACFC 0334 11BC;ACFC 0334 11BC;1100 116A 0334 11BC;ACFC 0334 11BC;1100 116A 0334 11BC; # (과◌̴ᆼ; 과◌̴ᆼ; 과◌̴ᆼ; 과◌̴ᆼ; 과◌̴ᆼ; ) HANGUL SYLLABLE GWA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+ADC0 0334 11AE;ADC0 0334 11AE;1100 1171 0334 11AE;ADC0 0334 11AE;1100 1171 0334 11AE; # (귀◌̴ᆮ; 귀◌̴ᆮ; 귀◌̴ᆮ; 귀◌̴ᆮ; 귀◌̴ᆮ; ) HANGUL SYLLABLE GWI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+AE14 0334 11B5;AE14 0334 11B5;1100 1174 0334 11B5;AE14 0334 11B5;1100 1174 0334 11B5; # (긔◌̴ᆵ; 긔◌̴ᆵ; 긔◌̴ᆵ; 긔◌̴ᆵ; 긔◌̴ᆵ; ) HANGUL SYLLABLE GYI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+AE68 0334 11BC;AE68 0334 11BC;1101 1162 0334 11BC;AE68 0334 11BC;1101 1162 0334 11BC; # (깨◌̴ᆼ; 깨◌̴ᆼ; á„ᅢ◌̴ᆼ; 깨◌̴ᆼ; á„ᅢ◌̴ᆼ; ) HANGUL SYLLABLE GGAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+AF2C 0334 11AE;AF2C 0334 11AE;1101 1169 0334 11AE;AF2C 0334 11AE;1101 1169 0334 11AE; # (꼬◌̴ᆮ; 꼬◌̴ᆮ; á„ᅩ◌̴ᆮ; 꼬◌̴ᆮ; á„ᅩ◌̴ᆮ; ) HANGUL SYLLABLE GGO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+AF80 0334 11B5;AF80 0334 11B5;1101 116C 0334 11B5;AF80 0334 11B5;1101 116C 0334 11B5; # (꾀◌̴ᆵ; 꾀◌̴ᆵ; á„ᅬ◌̴ᆵ; 꾀◌̴ᆵ; á„ᅬ◌̴ᆵ; ) HANGUL SYLLABLE GGOE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+AFD4 0334 11BC;AFD4 0334 11BC;1101 116F 0334 11BC;AFD4 0334 11BC;1101 116F 0334 11BC; # (꿔◌̴ᆼ; 꿔◌̴ᆼ; á„ᅯ◌̴ᆼ; 꿔◌̴ᆼ; á„ᅯ◌̴ᆼ; ) HANGUL SYLLABLE GGWEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+B098 0334 11AE;B098 0334 11AE;1102 1161 0334 11AE;B098 0334 11AE;1102 1161 0334 11AE; # (나◌̴ᆮ; 나◌̴ᆮ; 나◌̴ᆮ; 나◌̴ᆮ; 나◌̴ᆮ; ) HANGUL SYLLABLE NA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+B0EC 0334 11B5;B0EC 0334 11B5;1102 1164 0334 11B5;B0EC 0334 11B5;1102 1164 0334 11B5; # (냬◌̴ᆵ; 냬◌̴ᆵ; 냬◌̴ᆵ; 냬◌̴ᆵ; 냬◌̴ᆵ; ) HANGUL SYLLABLE NYAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+B140 0334 11BC;B140 0334 11BC;1102 1167 0334 11BC;B140 0334 11BC;1102 1167 0334 11BC; # (녀◌̴ᆼ; 녀◌̴ᆼ; 녀◌̴ᆼ; 녀◌̴ᆼ; 녀◌̴ᆼ; ) HANGUL SYLLABLE NYEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+B204 0334 11AE;B204 0334 11AE;1102 116E 0334 11AE;B204 0334 11AE;1102 116E 0334 11AE; # (누◌̴ᆮ; 누◌̴ᆮ; 누◌̴ᆮ; 누◌̴ᆮ; 누◌̴ᆮ; ) HANGUL SYLLABLE NU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+B258 0334 11B5;B258 0334 11B5;1102 1171 0334 11B5;B258 0334 11B5;1102 1171 0334 11B5; # (뉘◌̴ᆵ; 뉘◌̴ᆵ; 뉘◌̴ᆵ; 뉘◌̴ᆵ; 뉘◌̴ᆵ; ) HANGUL SYLLABLE NWI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+B2AC 0334 11BC;B2AC 0334 11BC;1102 1174 0334 11BC;B2AC 0334 11BC;1102 1174 0334 11BC; # (늬◌̴ᆼ; 늬◌̴ᆼ; 늬◌̴ᆼ; 늬◌̴ᆼ; 늬◌̴ᆼ; ) HANGUL SYLLABLE NYI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+B370 0334 11AE;B370 0334 11AE;1103 1166 0334 11AE;B370 0334 11AE;1103 1166 0334 11AE; # (ë°â—ŒÌ´á†®; ë°â—ŒÌ´á†®; 데◌̴ᆮ; ë°â—ŒÌ´á†®; 데◌̴ᆮ; ) HANGUL SYLLABLE DE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+B3C4 0334 11B5;B3C4 0334 11B5;1103 1169 0334 11B5;B3C4 0334 11B5;1103 1169 0334 11B5; # (ë„◌̴ᆵ; ë„◌̴ᆵ; 도◌̴ᆵ; ë„◌̴ᆵ; 도◌̴ᆵ; ) HANGUL SYLLABLE DO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+B418 0334 11BC;B418 0334 11BC;1103 116C 0334 11BC;B418 0334 11BC;1103 116C 0334 11BC; # (ë˜â—ŒÌ´á†¼; ë˜â—ŒÌ´á†¼; 되◌̴ᆼ; ë˜â—ŒÌ´á†¼; 되◌̴ᆼ; ) HANGUL SYLLABLE DOE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+B4DC 0334 11AE;B4DC 0334 11AE;1103 1173 0334 11AE;B4DC 0334 11AE;1103 1173 0334 11AE; # (드◌̴ᆮ; 드◌̴ᆮ; 드◌̴ᆮ; 드◌̴ᆮ; 드◌̴ᆮ; ) HANGUL SYLLABLE DEU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+B530 0334 11B5;B530 0334 11B5;1104 1161 0334 11B5;B530 0334 11B5;1104 1161 0334 11B5; # (따◌̴ᆵ; 따◌̴ᆵ; 따◌̴ᆵ; 따◌̴ᆵ; 따◌̴ᆵ; ) HANGUL SYLLABLE DDA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+B584 0334 11BC;B584 0334 11BC;1104 1164 0334 11BC;B584 0334 11BC;1104 1164 0334 11BC; # (떄◌̴ᆼ; 떄◌̴ᆼ; 떄◌̴ᆼ; 떄◌̴ᆼ; 떄◌̴ᆼ; ) HANGUL SYLLABLE DDYAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+B648 0334 11AE;B648 0334 11AE;1104 116B 0334 11AE;B648 0334 11AE;1104 116B 0334 11AE; # (뙈◌̴ᆮ; 뙈◌̴ᆮ; 뙈◌̴ᆮ; 뙈◌̴ᆮ; 뙈◌̴ᆮ; ) HANGUL SYLLABLE DDWAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+B69C 0334 11B5;B69C 0334 11B5;1104 116E 0334 11B5;B69C 0334 11B5;1104 116E 0334 11B5; # (뚜◌̴ᆵ; 뚜◌̴ᆵ; 뚜◌̴ᆵ; 뚜◌̴ᆵ; 뚜◌̴ᆵ; ) HANGUL SYLLABLE DDU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+B6F0 0334 11BC;B6F0 0334 11BC;1104 1171 0334 11BC;B6F0 0334 11BC;1104 1171 0334 11BC; # (뛰◌̴ᆼ; 뛰◌̴ᆼ; 뛰◌̴ᆼ; 뛰◌̴ᆼ; 뛰◌̴ᆼ; ) HANGUL SYLLABLE DDWI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+B7B4 0334 11AE;B7B4 0334 11AE;1105 1163 0334 11AE;B7B4 0334 11AE;1105 1163 0334 11AE; # (랴◌̴ᆮ; 랴◌̴ᆮ; 랴◌̴ᆮ; 랴◌̴ᆮ; 랴◌̴ᆮ; ) HANGUL SYLLABLE RYA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+B808 0334 11B5;B808 0334 11B5;1105 1166 0334 11B5;B808 0334 11B5;1105 1166 0334 11B5; # (레◌̴ᆵ; 레◌̴ᆵ; 레◌̴ᆵ; 레◌̴ᆵ; 레◌̴ᆵ; ) HANGUL SYLLABLE RE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+B85C 0334 11BC;B85C 0334 11BC;1105 1169 0334 11BC;B85C 0334 11BC;1105 1169 0334 11BC; # (로◌̴ᆼ; 로◌̴ᆼ; 로◌̴ᆼ; 로◌̴ᆼ; 로◌̴ᆼ; ) HANGUL SYLLABLE RO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+B920 0334 11AE;B920 0334 11AE;1105 1170 0334 11AE;B920 0334 11AE;1105 1170 0334 11AE; # (뤠◌̴ᆮ; 뤠◌̴ᆮ; 뤠◌̴ᆮ; 뤠◌̴ᆮ; 뤠◌̴ᆮ; ) HANGUL SYLLABLE RWE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+B974 0334 11B5;B974 0334 11B5;1105 1173 0334 11B5;B974 0334 11B5;1105 1173 0334 11B5; # (르◌̴ᆵ; 르◌̴ᆵ; 르◌̴ᆵ; 르◌̴ᆵ; 르◌̴ᆵ; ) HANGUL SYLLABLE REU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+B9C8 0334 11BC;B9C8 0334 11BC;1106 1161 0334 11BC;B9C8 0334 11BC;1106 1161 0334 11BC; # (마◌̴ᆼ; 마◌̴ᆼ; 마◌̴ᆼ; 마◌̴ᆼ; 마◌̴ᆼ; ) HANGUL SYLLABLE MA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+BA8C 0334 11AE;BA8C 0334 11AE;1106 1168 0334 11AE;BA8C 0334 11AE;1106 1168 0334 11AE; # (몌◌̴ᆮ; 몌◌̴ᆮ; 몌◌̴ᆮ; 몌◌̴ᆮ; 몌◌̴ᆮ; ) HANGUL SYLLABLE MYE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+BAE0 0334 11B5;BAE0 0334 11B5;1106 116B 0334 11B5;BAE0 0334 11B5;1106 116B 0334 11B5; # (뫠◌̴ᆵ; 뫠◌̴ᆵ; 뫠◌̴ᆵ; 뫠◌̴ᆵ; 뫠◌̴ᆵ; ) HANGUL SYLLABLE MWAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+BB34 0334 11BC;BB34 0334 11BC;1106 116E 0334 11BC;BB34 0334 11BC;1106 116E 0334 11BC; # (무◌̴ᆼ; 무◌̴ᆼ; 무◌̴ᆼ; 무◌̴ᆼ; 무◌̴ᆼ; ) HANGUL SYLLABLE MU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+BBF8 0334 11AE;BBF8 0334 11AE;1106 1175 0334 11AE;BBF8 0334 11AE;1106 1175 0334 11AE; # (미◌̴ᆮ; 미◌̴ᆮ; 미◌̴ᆮ; 미◌̴ᆮ; 미◌̴ᆮ; ) HANGUL SYLLABLE MI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+BC4C 0334 11B5;BC4C 0334 11B5;1107 1163 0334 11B5;BC4C 0334 11B5;1107 1163 0334 11B5; # (뱌◌̴ᆵ; 뱌◌̴ᆵ; 뱌◌̴ᆵ; 뱌◌̴ᆵ; 뱌◌̴ᆵ; ) HANGUL SYLLABLE BYA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+BCA0 0334 11BC;BCA0 0334 11BC;1107 1166 0334 11BC;BCA0 0334 11BC;1107 1166 0334 11BC; # (베◌̴ᆼ; 베◌̴ᆼ; 베◌̴ᆼ; 베◌̴ᆼ; 베◌̴ᆼ; ) HANGUL SYLLABLE BE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+BD64 0334 11AE;BD64 0334 11AE;1107 116D 0334 11AE;BD64 0334 11AE;1107 116D 0334 11AE; # (뵤◌̴ᆮ; 뵤◌̴ᆮ; 뵤◌̴ᆮ; 뵤◌̴ᆮ; 뵤◌̴ᆮ; ) HANGUL SYLLABLE BYO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+BDB8 0334 11B5;BDB8 0334 11B5;1107 1170 0334 11B5;BDB8 0334 11B5;1107 1170 0334 11B5; # (붸◌̴ᆵ; 붸◌̴ᆵ; 붸◌̴ᆵ; 붸◌̴ᆵ; 붸◌̴ᆵ; ) HANGUL SYLLABLE BWE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+BE0C 0334 11BC;BE0C 0334 11BC;1107 1173 0334 11BC;BE0C 0334 11BC;1107 1173 0334 11BC; # (브◌̴ᆼ; 브◌̴ᆼ; 브◌̴ᆼ; 브◌̴ᆼ; 브◌̴ᆼ; ) HANGUL SYLLABLE BEU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+BED0 0334 11AE;BED0 0334 11AE;1108 1165 0334 11AE;BED0 0334 11AE;1108 1165 0334 11AE; # (ë»â—ŒÌ´á†®; ë»â—ŒÌ´á†®; 뻐◌̴ᆮ; ë»â—ŒÌ´á†®; 뻐◌̴ᆮ; ) HANGUL SYLLABLE BBEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+BF24 0334 11B5;BF24 0334 11B5;1108 1168 0334 11B5;BF24 0334 11B5;1108 1168 0334 11B5; # (뼤◌̴ᆵ; 뼤◌̴ᆵ; 뼤◌̴ᆵ; 뼤◌̴ᆵ; 뼤◌̴ᆵ; ) HANGUL SYLLABLE BBYE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+BF78 0334 11BC;BF78 0334 11BC;1108 116B 0334 11BC;BF78 0334 11BC;1108 116B 0334 11BC; # (뽸◌̴ᆼ; 뽸◌̴ᆼ; 뽸◌̴ᆼ; 뽸◌̴ᆼ; 뽸◌̴ᆼ; ) HANGUL SYLLABLE BBWAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+C03C 0334 11AE;C03C 0334 11AE;1108 1172 0334 11AE;C03C 0334 11AE;1108 1172 0334 11AE; # (쀼◌̴ᆮ; 쀼◌̴ᆮ; 쀼◌̴ᆮ; 쀼◌̴ᆮ; 쀼◌̴ᆮ; ) HANGUL SYLLABLE BBYU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+C090 0334 11B5;C090 0334 11B5;1108 1175 0334 11B5;C090 0334 11B5;1108 1175 0334 11B5; # (ì‚◌̴ᆵ; ì‚◌̴ᆵ; 삐◌̴ᆵ; ì‚◌̴ᆵ; 삐◌̴ᆵ; ) HANGUL SYLLABLE BBI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+C0E4 0334 11BC;C0E4 0334 11BC;1109 1163 0334 11BC;C0E4 0334 11BC;1109 1163 0334 11BC; # (샤◌̴ᆼ; 샤◌̴ᆼ; 샤◌̴ᆼ; 샤◌̴ᆼ; 샤◌̴ᆼ; ) HANGUL SYLLABLE SYA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+C1A8 0334 11AE;C1A8 0334 11AE;1109 116A 0334 11AE;C1A8 0334 11AE;1109 116A 0334 11AE; # (솨◌̴ᆮ; 솨◌̴ᆮ; 솨◌̴ᆮ; 솨◌̴ᆮ; 솨◌̴ᆮ; ) HANGUL SYLLABLE SWA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+C1FC 0334 11B5;C1FC 0334 11B5;1109 116D 0334 11B5;C1FC 0334 11B5;1109 116D 0334 11B5; # (쇼◌̴ᆵ; 쇼◌̴ᆵ; 쇼◌̴ᆵ; 쇼◌̴ᆵ; 쇼◌̴ᆵ; ) HANGUL SYLLABLE SYO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+C250 0334 11BC;C250 0334 11BC;1109 1170 0334 11BC;C250 0334 11BC;1109 1170 0334 11BC; # (ì‰â—ŒÌ´á†¼; ì‰â—ŒÌ´á†¼; 쉐◌̴ᆼ; ì‰â—ŒÌ´á†¼; 쉐◌̴ᆼ; ) HANGUL SYLLABLE SWE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+C314 0334 11AE;C314 0334 11AE;110A 1162 0334 11AE;C314 0334 11AE;110A 1162 0334 11AE; # (쌔◌̴ᆮ; 쌔◌̴ᆮ; 쌔◌̴ᆮ; 쌔◌̴ᆮ; 쌔◌̴ᆮ; ) HANGUL SYLLABLE SSAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+C368 0334 11B5;C368 0334 11B5;110A 1165 0334 11B5;C368 0334 11B5;110A 1165 0334 11B5; # (ì¨â—ŒÌ´á†µ; ì¨â—ŒÌ´á†µ; 써◌̴ᆵ; ì¨â—ŒÌ´á†µ; 써◌̴ᆵ; ) HANGUL SYLLABLE SSEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+C3BC 0334 11BC;C3BC 0334 11BC;110A 1168 0334 11BC;C3BC 0334 11BC;110A 1168 0334 11BC; # (쎼◌̴ᆼ; 쎼◌̴ᆼ; 쎼◌̴ᆼ; 쎼◌̴ᆼ; 쎼◌̴ᆼ; ) HANGUL SYLLABLE SSYE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+C480 0334 11AE;C480 0334 11AE;110A 116F 0334 11AE;C480 0334 11AE;110A 116F 0334 11AE; # (쒀◌̴ᆮ; 쒀◌̴ᆮ; 쒀◌̴ᆮ; 쒀◌̴ᆮ; 쒀◌̴ᆮ; ) HANGUL SYLLABLE SSWEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+C4D4 0334 11B5;C4D4 0334 11B5;110A 1172 0334 11B5;C4D4 0334 11B5;110A 1172 0334 11B5; # (쓔◌̴ᆵ; 쓔◌̴ᆵ; 쓔◌̴ᆵ; 쓔◌̴ᆵ; 쓔◌̴ᆵ; ) HANGUL SYLLABLE SSYU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+C528 0334 11BC;C528 0334 11BC;110A 1175 0334 11BC;C528 0334 11BC;110A 1175 0334 11BC; # (씨◌̴ᆼ; 씨◌̴ᆼ; 씨◌̴ᆼ; 씨◌̴ᆼ; 씨◌̴ᆼ; ) HANGUL SYLLABLE SSI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+C5EC 0334 11AE;C5EC 0334 11AE;110B 1167 0334 11AE;C5EC 0334 11AE;110B 1167 0334 11AE; # (여◌̴ᆮ; 여◌̴ᆮ; 여◌̴ᆮ; 여◌̴ᆮ; 여◌̴ᆮ; ) HANGUL SYLLABLE YEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+C640 0334 11B5;C640 0334 11B5;110B 116A 0334 11B5;C640 0334 11B5;110B 116A 0334 11B5; # (와◌̴ᆵ; 와◌̴ᆵ; 와◌̴ᆵ; 와◌̴ᆵ; 와◌̴ᆵ; ) HANGUL SYLLABLE WA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+C694 0334 11BC;C694 0334 11BC;110B 116D 0334 11BC;C694 0334 11BC;110B 116D 0334 11BC; # (요◌̴ᆼ; 요◌̴ᆼ; 요◌̴ᆼ; 요◌̴ᆼ; 요◌̴ᆼ; ) HANGUL SYLLABLE YO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+C758 0334 11AE;C758 0334 11AE;110B 1174 0334 11AE;C758 0334 11AE;110B 1174 0334 11AE; # (ì˜â—ŒÌ´á†®; ì˜â—ŒÌ´á†®; 의◌̴ᆮ; ì˜â—ŒÌ´á†®; 의◌̴ᆮ; ) HANGUL SYLLABLE YI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+C7AC 0334 11B5;C7AC 0334 11B5;110C 1162 0334 11B5;C7AC 0334 11B5;110C 1162 0334 11B5; # (재◌̴ᆵ; 재◌̴ᆵ; 재◌̴ᆵ; 재◌̴ᆵ; 재◌̴ᆵ; ) HANGUL SYLLABLE JAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+C800 0334 11BC;C800 0334 11BC;110C 1165 0334 11BC;C800 0334 11BC;110C 1165 0334 11BC; # (저◌̴ᆼ; 저◌̴ᆼ; 저◌̴ᆼ; 저◌̴ᆼ; 저◌̴ᆼ; ) HANGUL SYLLABLE JEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+C8C4 0334 11AE;C8C4 0334 11AE;110C 116C 0334 11AE;C8C4 0334 11AE;110C 116C 0334 11AE; # (죄◌̴ᆮ; 죄◌̴ᆮ; 죄◌̴ᆮ; 죄◌̴ᆮ; 죄◌̴ᆮ; ) HANGUL SYLLABLE JOE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+C918 0334 11B5;C918 0334 11B5;110C 116F 0334 11B5;C918 0334 11B5;110C 116F 0334 11B5; # (줘◌̴ᆵ; 줘◌̴ᆵ; 줘◌̴ᆵ; 줘◌̴ᆵ; 줘◌̴ᆵ; ) HANGUL SYLLABLE JWEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+C96C 0334 11BC;C96C 0334 11BC;110C 1172 0334 11BC;C96C 0334 11BC;110C 1172 0334 11BC; # (쥬◌̴ᆼ; 쥬◌̴ᆼ; 쥬◌̴ᆼ; 쥬◌̴ᆼ; 쥬◌̴ᆼ; ) HANGUL SYLLABLE JYU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+CA30 0334 11AE;CA30 0334 11AE;110D 1164 0334 11AE;CA30 0334 11AE;110D 1164 0334 11AE; # (쨰◌̴ᆮ; 쨰◌̴ᆮ; á„ᅤ◌̴ᆮ; 쨰◌̴ᆮ; á„ᅤ◌̴ᆮ; ) HANGUL SYLLABLE JJYAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+CA84 0334 11B5;CA84 0334 11B5;110D 1167 0334 11B5;CA84 0334 11B5;110D 1167 0334 11B5; # (쪄◌̴ᆵ; 쪄◌̴ᆵ; á„ᅧ◌̴ᆵ; 쪄◌̴ᆵ; á„ᅧ◌̴ᆵ; ) HANGUL SYLLABLE JJYEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+CAD8 0334 11BC;CAD8 0334 11BC;110D 116A 0334 11BC;CAD8 0334 11BC;110D 116A 0334 11BC; # (쫘◌̴ᆼ; 쫘◌̴ᆼ; á„ᅪ◌̴ᆼ; 쫘◌̴ᆼ; á„ᅪ◌̴ᆼ; ) HANGUL SYLLABLE JJWA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+CB9C 0334 11AE;CB9C 0334 11AE;110D 1171 0334 11AE;CB9C 0334 11AE;110D 1171 0334 11AE; # (쮜◌̴ᆮ; 쮜◌̴ᆮ; á„ᅱ◌̴ᆮ; 쮜◌̴ᆮ; á„ᅱ◌̴ᆮ; ) HANGUL SYLLABLE JJWI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+CBF0 0334 11B5;CBF0 0334 11B5;110D 1174 0334 11B5;CBF0 0334 11B5;110D 1174 0334 11B5; # (쯰◌̴ᆵ; 쯰◌̴ᆵ; á„ᅴ◌̴ᆵ; 쯰◌̴ᆵ; á„ᅴ◌̴ᆵ; ) HANGUL SYLLABLE JJYI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+CC44 0334 11BC;CC44 0334 11BC;110E 1162 0334 11BC;CC44 0334 11BC;110E 1162 0334 11BC; # (채◌̴ᆼ; 채◌̴ᆼ; 채◌̴ᆼ; 채◌̴ᆼ; 채◌̴ᆼ; ) HANGUL SYLLABLE CAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+CD08 0334 11AE;CD08 0334 11AE;110E 1169 0334 11AE;CD08 0334 11AE;110E 1169 0334 11AE; # (초◌̴ᆮ; 초◌̴ᆮ; 초◌̴ᆮ; 초◌̴ᆮ; 초◌̴ᆮ; ) HANGUL SYLLABLE CO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+CD5C 0334 11B5;CD5C 0334 11B5;110E 116C 0334 11B5;CD5C 0334 11B5;110E 116C 0334 11B5; # (최◌̴ᆵ; 최◌̴ᆵ; 최◌̴ᆵ; 최◌̴ᆵ; 최◌̴ᆵ; ) HANGUL SYLLABLE COE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+CDB0 0334 11BC;CDB0 0334 11BC;110E 116F 0334 11BC;CDB0 0334 11BC;110E 116F 0334 11BC; # (춰◌̴ᆼ; 춰◌̴ᆼ; 춰◌̴ᆼ; 춰◌̴ᆼ; 춰◌̴ᆼ; ) HANGUL SYLLABLE CWEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+CE74 0334 11AE;CE74 0334 11AE;110F 1161 0334 11AE;CE74 0334 11AE;110F 1161 0334 11AE; # (카◌̴ᆮ; 카◌̴ᆮ; á„ᅡ◌̴ᆮ; 카◌̴ᆮ; á„ᅡ◌̴ᆮ; ) HANGUL SYLLABLE KA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+CEC8 0334 11B5;CEC8 0334 11B5;110F 1164 0334 11B5;CEC8 0334 11B5;110F 1164 0334 11B5; # (컈◌̴ᆵ; 컈◌̴ᆵ; á„ᅤ◌̴ᆵ; 컈◌̴ᆵ; á„ᅤ◌̴ᆵ; ) HANGUL SYLLABLE KYAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+CF1C 0334 11BC;CF1C 0334 11BC;110F 1167 0334 11BC;CF1C 0334 11BC;110F 1167 0334 11BC; # (켜◌̴ᆼ; 켜◌̴ᆼ; á„ᅧ◌̴ᆼ; 켜◌̴ᆼ; á„ᅧ◌̴ᆼ; ) HANGUL SYLLABLE KYEO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+CFE0 0334 11AE;CFE0 0334 11AE;110F 116E 0334 11AE;CFE0 0334 11AE;110F 116E 0334 11AE; # (쿠◌̴ᆮ; 쿠◌̴ᆮ; á„ᅮ◌̴ᆮ; 쿠◌̴ᆮ; á„ᅮ◌̴ᆮ; ) HANGUL SYLLABLE KU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+D034 0334 11B5;D034 0334 11B5;110F 1171 0334 11B5;D034 0334 11B5;110F 1171 0334 11B5; # (퀴◌̴ᆵ; 퀴◌̴ᆵ; á„ᅱ◌̴ᆵ; 퀴◌̴ᆵ; á„ᅱ◌̴ᆵ; ) HANGUL SYLLABLE KWI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+D088 0334 11BC;D088 0334 11BC;110F 1174 0334 11BC;D088 0334 11BC;110F 1174 0334 11BC; # (킈◌̴ᆼ; 킈◌̴ᆼ; á„ᅴ◌̴ᆼ; 킈◌̴ᆼ; á„ᅴ◌̴ᆼ; ) HANGUL SYLLABLE KYI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+D14C 0334 11AE;D14C 0334 11AE;1110 1166 0334 11AE;D14C 0334 11AE;1110 1166 0334 11AE; # (테◌̴ᆮ; 테◌̴ᆮ; á„ᅦ◌̴ᆮ; 테◌̴ᆮ; á„ᅦ◌̴ᆮ; ) HANGUL SYLLABLE TE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+D1A0 0334 11B5;D1A0 0334 11B5;1110 1169 0334 11B5;D1A0 0334 11B5;1110 1169 0334 11B5; # (토◌̴ᆵ; 토◌̴ᆵ; á„ᅩ◌̴ᆵ; 토◌̴ᆵ; á„ᅩ◌̴ᆵ; ) HANGUL SYLLABLE TO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+D1F4 0334 11BC;D1F4 0334 11BC;1110 116C 0334 11BC;D1F4 0334 11BC;1110 116C 0334 11BC; # (퇴◌̴ᆼ; 퇴◌̴ᆼ; á„ᅬ◌̴ᆼ; 퇴◌̴ᆼ; á„ᅬ◌̴ᆼ; ) HANGUL SYLLABLE TOE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+D2B8 0334 11AE;D2B8 0334 11AE;1110 1173 0334 11AE;D2B8 0334 11AE;1110 1173 0334 11AE; # (트◌̴ᆮ; 트◌̴ᆮ; á„ᅳ◌̴ᆮ; 트◌̴ᆮ; á„ᅳ◌̴ᆮ; ) HANGUL SYLLABLE TEU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+D30C 0334 11B5;D30C 0334 11B5;1111 1161 0334 11B5;D30C 0334 11B5;1111 1161 0334 11B5; # (파◌̴ᆵ; 파◌̴ᆵ; 파◌̴ᆵ; 파◌̴ᆵ; 파◌̴ᆵ; ) HANGUL SYLLABLE PA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+D360 0334 11BC;D360 0334 11BC;1111 1164 0334 11BC;D360 0334 11BC;1111 1164 0334 11BC; # (í â—ŒÌ´á†¼; í â—ŒÌ´á†¼; 퍠◌̴ᆼ; í â—ŒÌ´á†¼; 퍠◌̴ᆼ; ) HANGUL SYLLABLE PYAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+D424 0334 11AE;D424 0334 11AE;1111 116B 0334 11AE;D424 0334 11AE;1111 116B 0334 11AE; # (í¤â—ŒÌ´á†®; í¤â—ŒÌ´á†®; 퐤◌̴ᆮ; í¤â—ŒÌ´á†®; 퐤◌̴ᆮ; ) HANGUL SYLLABLE PWAE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+D478 0334 11B5;D478 0334 11B5;1111 116E 0334 11B5;D478 0334 11B5;1111 116E 0334 11B5; # (푸◌̴ᆵ; 푸◌̴ᆵ; 푸◌̴ᆵ; 푸◌̴ᆵ; 푸◌̴ᆵ; ) HANGUL SYLLABLE PU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+D4CC 0334 11BC;D4CC 0334 11BC;1111 1171 0334 11BC;D4CC 0334 11BC;1111 1171 0334 11BC; # (퓌◌̴ᆼ; 퓌◌̴ᆼ; 퓌◌̴ᆼ; 퓌◌̴ᆼ; 퓌◌̴ᆼ; ) HANGUL SYLLABLE PWI, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+D590 0334 11AE;D590 0334 11AE;1112 1163 0334 11AE;D590 0334 11AE;1112 1163 0334 11AE; # (í–◌̴ᆮ; í–◌̴ᆮ; 햐◌̴ᆮ; í–◌̴ᆮ; 햐◌̴ᆮ; ) HANGUL SYLLABLE HYA, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+D5E4 0334 11B5;D5E4 0334 11B5;1112 1166 0334 11B5;D5E4 0334 11B5;1112 1166 0334 11B5; # (헤◌̴ᆵ; 헤◌̴ᆵ; 헤◌̴ᆵ; 헤◌̴ᆵ; 헤◌̴ᆵ; ) HANGUL SYLLABLE HE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+D638 0334 11BC;D638 0334 11BC;1112 1169 0334 11BC;D638 0334 11BC;1112 1169 0334 11BC; # (호◌̴ᆼ; 호◌̴ᆼ; 호◌̴ᆼ; 호◌̴ᆼ; 호◌̴ᆼ; ) HANGUL SYLLABLE HO, COMBINING TILDE OVERLAY, HANGUL JONGSEONG IEUNG
+D6FC 0334 11AE;D6FC 0334 11AE;1112 1170 0334 11AE;D6FC 0334 11AE;1112 1170 0334 11AE; # (훼◌̴ᆮ; 훼◌̴ᆮ; 훼◌̴ᆮ; 훼◌̴ᆮ; 훼◌̴ᆮ; ) HANGUL SYLLABLE HWE, COMBINING TILDE OVERLAY, HANGUL JONGSEONG TIKEUT
+D750 0334 11B5;D750 0334 11B5;1112 1173 0334 11B5;D750 0334 11B5;1112 1173 0334 11B5; # (í◌̴ᆵ; í◌̴ᆵ; 흐◌̴ᆵ; í◌̴ᆵ; 흐◌̴ᆵ; ) HANGUL SYLLABLE HEU, COMBINING TILDE OVERLAY, HANGUL JONGSEONG RIEUL-PHIEUPH
+11131 0334 11127;11131 0334 11127;11131 0334 11127;11131 0334 11127;11131 0334 11127; # (◌𑄱◌̴◌𑄧; ◌𑄱◌̴◌𑄧; ◌𑄱◌̴◌𑄧; ◌𑄱◌̴◌𑄧; ◌𑄱◌̴◌𑄧; ) CHAKMA O MARK, COMBINING TILDE OVERLAY, CHAKMA VOWEL SIGN A
+11132 0334 11127;11132 0334 11127;11132 0334 11127;11132 0334 11127;11132 0334 11127; # (◌𑄲◌̴◌𑄧; ◌𑄲◌̴◌𑄧; ◌𑄲◌̴◌𑄧; ◌𑄲◌̴◌𑄧; ◌𑄲◌̴◌𑄧; ) CHAKMA AU MARK, COMBINING TILDE OVERLAY, CHAKMA VOWEL SIGN A
+11347 0334 1133E;11347 0334 1133E;11347 0334 1133E;11347 0334 1133E;11347 0334 1133E; # (ð‘‡â—ŒÌ´ð‘Œ¾; ð‘‡â—ŒÌ´ð‘Œ¾; ð‘‡â—ŒÌ´ð‘Œ¾; ð‘‡â—ŒÌ´ð‘Œ¾; ð‘‡â—ŒÌ´ð‘Œ¾; ) GRANTHA VOWEL SIGN EE, COMBINING TILDE OVERLAY, GRANTHA VOWEL SIGN AA
+11347 0334 11357;11347 0334 11357;11347 0334 11357;11347 0334 11357;11347 0334 11357; # (ð‘‡â—ŒÌ´ð‘—; ð‘‡â—ŒÌ´ð‘—; ð‘‡â—ŒÌ´ð‘—; ð‘‡â—ŒÌ´ð‘—; ð‘‡â—ŒÌ´ð‘—; ) GRANTHA VOWEL SIGN EE, COMBINING TILDE OVERLAY, GRANTHA AU LENGTH MARK
+114B9 0334 114B0;114B9 0334 114B0;114B9 0334 114B0;114B9 0334 114B0;114B9 0334 114B0; # (𑒹◌̴𑒰; 𑒹◌̴𑒰; 𑒹◌̴𑒰; 𑒹◌̴𑒰; 𑒹◌̴𑒰; ) TIRHUTA VOWEL SIGN E, COMBINING TILDE OVERLAY, TIRHUTA VOWEL SIGN AA
+114B9 0334 114BA;114B9 0334 114BA;114B9 0334 114BA;114B9 0334 114BA;114B9 0334 114BA; # (𑒹◌̴◌𑒺; 𑒹◌̴◌𑒺; 𑒹◌̴◌𑒺; 𑒹◌̴◌𑒺; 𑒹◌̴◌𑒺; ) TIRHUTA VOWEL SIGN E, COMBINING TILDE OVERLAY, TIRHUTA VOWEL SIGN SHORT E
+114B9 0334 114BD;114B9 0334 114BD;114B9 0334 114BD;114B9 0334 114BD;114B9 0334 114BD; # (𑒹◌̴𑒽; 𑒹◌̴𑒽; 𑒹◌̴𑒽; 𑒹◌̴𑒽; 𑒹◌̴𑒽; ) TIRHUTA VOWEL SIGN E, COMBINING TILDE OVERLAY, TIRHUTA VOWEL SIGN SHORT O
+115B8 0334 115AF;115B8 0334 115AF;115B8 0334 115AF;115B8 0334 115AF;115B8 0334 115AF; # (𑖸◌̴𑖯; 𑖸◌̴𑖯; 𑖸◌̴𑖯; 𑖸◌̴𑖯; 𑖸◌̴𑖯; ) SIDDHAM VOWEL SIGN E, COMBINING TILDE OVERLAY, SIDDHAM VOWEL SIGN AA
+115B9 0334 115AF;115B9 0334 115AF;115B9 0334 115AF;115B9 0334 115AF;115B9 0334 115AF; # (𑖹◌̴𑖯; 𑖹◌̴𑖯; 𑖹◌̴𑖯; 𑖹◌̴𑖯; 𑖹◌̴𑖯; ) SIDDHAM VOWEL SIGN AI, COMBINING TILDE OVERLAY, SIDDHAM VOWEL SIGN AA
+11935 0334 11930;11935 0334 11930;11935 0334 11930;11935 0334 11930;11935 0334 11930; # (𑤵◌̴𑤰; 𑤵◌̴𑤰; 𑤵◌̴𑤰; 𑤵◌̴𑤰; 𑤵◌̴𑤰; ) DIVES AKURU VOWEL SIGN E, COMBINING TILDE OVERLAY, DIVES AKURU VOWEL SIGN AA
+#
+# EOF
diff --git a/erts/emulator/test/code_SUITE.erl b/erts/emulator/test/code_SUITE.erl
index 020065f6d8..a1e63cd36c 100644
--- a/erts/emulator/test/code_SUITE.erl
+++ b/erts/emulator/test/code_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -33,8 +33,9 @@
false_dependency/1,coverage/1,fun_confusion/1,
t_copy_literals/1, t_copy_literals_frags/1,
erl_544/1, max_heap_size/1,
- check_process_code_signal_order/1,
- check_process_code_dirty_exec_proc/1]).
+ check_process_code_signal_order/1,
+ check_process_code_dirty_exec_proc/1,
+ call_fun_before_load/1]).
-define(line_trace, 1).
-include_lib("common_test/include/ct.hrl").
@@ -52,7 +53,7 @@ all() ->
false_dependency,
coverage, fun_confusion, t_copy_literals, t_copy_literals_frags,
erl_544, max_heap_size, check_process_code_signal_order,
- check_process_code_dirty_exec_proc].
+ check_process_code_dirty_exec_proc, call_fun_before_load].
init_per_suite(Config) ->
erts_debug:set_internal_state(available_internal_state, true),
@@ -1184,6 +1185,29 @@ check_process_code_dirty_exec_proc(Config) when is_list(Config) ->
false = is_process_alive(Pid),
ok.
+%% OTP-18016: When loading a module over itself, a race in fun loading made it
+%% possible to call a local fun before the module was fully reloaded.
+call_fun_before_load(Config) ->
+ Priv = proplists:get_value(priv_dir, Config),
+ Data = proplists:get_value(data_dir, Config),
+ Path = code:get_path(),
+ true = code:add_path(Priv),
+ try
+ SrcFile = filename:join(Data, "call_fun_before_load.erl"),
+ ObjFile = filename:join(Priv, "call_fun_before_load.beam"),
+ {ok,Mod,Code} = compile:file(SrcFile, [binary, report]),
+ {module,Mod} = code:load_binary(Mod, ObjFile, Code),
+
+ ok = call_fun_before_load:run(ObjFile, Code),
+
+ code:purge(call_fun_before_load)
+ after
+ code:set_path(Path),
+ code:delete(call_fun_before_load),
+ code:purge(call_fun_before_load)
+ end,
+ ok.
+
%% Utilities.
make_sub_binary(Bin) when is_binary(Bin) ->
diff --git a/erts/emulator/test/code_SUITE_data/call_fun_before_load.erl b/erts/emulator/test/code_SUITE_data/call_fun_before_load.erl
new file mode 100644
index 0000000000..9ede44ebd2
--- /dev/null
+++ b/erts/emulator/test/code_SUITE_data/call_fun_before_load.erl
@@ -0,0 +1,45 @@
+-module(call_fun_before_load).
+-export([run/2]).
+
+run(ObjFile, Code) ->
+ Ref = make_ref(),
+ Self = self(),
+ {Pid, MRef} =
+ spawn_monitor(
+ fun() ->
+ %% If we're called before being loaded, process_info/2 can't
+ %% figure out where we are, reporting that we're in an
+ %% unknown module rather than the current one.
+ Fun0 = fun(Fun) ->
+ {current_function,
+ {?MODULE, _, _}} =
+ process_info(self(), current_function),
+ Fun(Fun)
+ end,
+
+ NumScheds = erlang:system_info(schedulers_online),
+ Workers = [spawn_link(fun() -> Fun0(Fun0) end)
+ || _ <- lists:seq(1, NumScheds)],
+
+ %% Give the workers time to start before reloading ourselves.
+ timer:sleep(100),
+
+ code:load_binary(?MODULE, ObjFile, Code),
+
+ %% Give the workers time to crash before exiting.
+ timer:sleep(100),
+
+ Self ! {Ref, Workers},
+
+ ok
+ end),
+ receive
+ {Ref, Workers} ->
+ erlang:demonitor(MRef, [flush]),
+ [exit(Worker, kill) || Worker <- Workers],
+ ok;
+ {'DOWN', MRef, process, Pid, {{badmatch,_}, _}} ->
+ ct:fail("Ran newly staged but not yet loaded code.", []);
+ {'DOWN', MRef, process, Pid, Reason} ->
+ ct:fail(Reason)
+ end.
diff --git a/erts/emulator/test/decode_packet_SUITE.erl b/erts/emulator/test/decode_packet_SUITE.erl
index 07653646a2..55bc6eb06b 100644
--- a/erts/emulator/test/decode_packet_SUITE.erl
+++ b/erts/emulator/test/decode_packet_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@
-export([all/0, suite/0,groups/0,
init_per_testcase/2,end_per_testcase/2,
- basic/1, packet_size/1, neg/1, http/1, line/1, ssl/1, otp_8536/1,
+ basic/1, ipv6/1, packet_size/1, neg/1, http/1, line/1, ssl/1, otp_8536/1,
otp_9389/1, otp_9389_line/1]).
suite() ->
@@ -35,7 +35,7 @@ suite() ->
all() ->
[basic, packet_size, neg, http, line, ssl, otp_8536,
- otp_9389, otp_9389_line].
+ otp_9389, otp_9389_line, ipv6].
groups() ->
[].
@@ -206,6 +206,23 @@ pack_ssl(Content, Major, Minor, Body) ->
end,
{Res, {ssl_tls,[],C,{Major,Minor}, Data}}.
+ipv6(Config) when is_list(Config) ->
+ %% Test with port
+ Packet = <<"GET http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:4000/echo_components HTTP/1.1\r\nhost: orange\r\n\r\n">>,
+ {ok, {http_request, 'GET', {absoluteURI, http, <<"[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]">>, 4000, <<"/echo_components">>}, {1, 1}}, <<"host: orange\r\n\r\n">>} =
+ erlang:decode_packet(http_bin, Packet, []),
+ %% Test no port
+ Packet2 = <<"GET http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]/1234 HTTP/1.1\r\nhost: orange\r\n\r\n">>,
+ {ok, {http_request, 'GET', {absoluteURI, http, <<"[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]">>, undefined, <<"/1234">>}, {1, 1}}, <<"host: orange\r\n\r\n">>} =
+ erlang:decode_packet(http_bin, Packet2, []),
+ %% Test short ipv6 form
+ Packet3 = <<"GET http://[::1]/1234 HTTP/1.1\r\nhost: orange\r\n\r\n">>,
+ {ok, {http_request, 'GET', {absoluteURI, http, <<"[::1]">>, undefined, <<"/1234">>}, {1, 1}}, <<"host: orange\r\n\r\n">>} =
+ erlang:decode_packet(http_bin, Packet3, []),
+ %% Test missing `]`
+ Packet4 = <<"GET http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210:4000/echo_components HTTP/1.1\r\nhost: orange\r\n\r\n">>,
+ {ok, {http_request, 'GET', {absoluteURI, http, <<"[FEDC">>, undefined, <<"/echo_components">>}, {1, 1}}, <<"host: orange\r\n\r\n">>} =
+ erlang:decode_packet(http_bin, Packet4, []).
packet_size(Config) when is_list(Config) ->
Packet = <<101,22,203,54,175>>,
diff --git a/erts/emulator/test/dirty_nif_SUITE.erl b/erts/emulator/test/dirty_nif_SUITE.erl
index 59d791eb2b..5069dfa5b9 100644
--- a/erts/emulator/test/dirty_nif_SUITE.erl
+++ b/erts/emulator/test/dirty_nif_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -29,13 +29,49 @@
-export([all/0, suite/0,
init_per_suite/1, end_per_suite/1,
init_per_testcase/2, end_per_testcase/2,
+ init_per_group/2, end_per_group/2, groups/0,
dirty_nif/1, dirty_nif_send/1,
dirty_nif_exception/1, call_dirty_nif_exception/1,
dirty_scheduler_exit/1, dirty_call_while_terminated/1,
dirty_heap_access/1, dirty_process_info/1,
dirty_process_register/1, dirty_process_trace/1,
code_purge/1, literal_area/1, dirty_nif_send_traced/1,
- nif_whereis/1, nif_whereis_parallel/1, nif_whereis_proxy/1]).
+ nif_whereis/1, nif_whereis_parallel/1, nif_whereis_proxy/1,
+ set_halt_options_from_nif/1,
+ delay_halt/1,
+ delay_halt_old_code/1,
+ delay_halt_old_and_new_code/1,
+ flush_false/1,
+ on_halt/1,
+ on_halt_old_code/1,
+ on_halt_old_and_new_code/1,
+ sync_halt/1,
+ many_delay_halt/1,
+ many_on_halt/1]).
+
+-export([load_nif/2]).
+
+-nifs([lib_loaded/0,
+ call_dirty_nif/3,
+ send_from_dirty_nif/1,
+ send_wait_from_dirty_nif/1,
+ call_dirty_nif_exception/1,
+ call_dirty_nif_zero_args/0,
+ dirty_call_while_terminated_nif/1,
+ dirty_sleeper/0,
+ dirty_sleeper/1,
+ dirty_heap_access_nif/1,
+ whereis_term/2,
+ whereis_send/3,
+ dirty_terminating_literal_access/2,
+ delay_halt_normal/3,
+ delay_halt_io_bound/3,
+ delay_halt_cpu_bound/3,
+ sync_halt_io_bound/2,
+ sync_halt_cpu_bound/2,
+ set_halt_option_from_nif_normal/1,
+ set_halt_option_from_nif_io_bound/1,
+ set_halt_option_from_nif_cpu_bound/1]).
-define(nif_stub,nif_stub_error(?LINE)).
@@ -55,23 +91,38 @@ all() ->
literal_area,
dirty_nif_send_traced,
nif_whereis,
- nif_whereis_parallel].
+ nif_whereis_parallel,
+ {group, halt_normal},
+ {group, halt_dirty_cpu},
+ {group, halt_dirty_io},
+ {group, halt_misc}].
+
+halt_sched_tests() ->
+ [set_halt_options_from_nif, delay_halt, delay_halt_old_code, delay_halt_old_and_new_code].
+halt_dirty_sched_tests() ->
+ [sync_halt, flush_false].
+
+groups() ->
+ [{halt_normal, [parallel], halt_sched_tests()},
+ {halt_dirty_cpu, [parallel], halt_sched_tests()++halt_dirty_sched_tests()},
+ {halt_dirty_io, [parallel], halt_sched_tests()++halt_dirty_sched_tests()},
+ {halt_misc, [parallel], [on_halt, on_halt_old_code, on_halt_old_and_new_code, many_on_halt, many_delay_halt]}].
+
+init_per_group(Group, Config) ->
+ [{group, Group} | Config].
+
+end_per_group(_, Config) ->
+ proplists:delete(group, Config).
init_per_suite(Config) ->
- case erlang:system_info(dirty_cpu_schedulers) of
- N when N > 0 ->
- case lib_loaded() of
- false ->
- ok = erlang:load_nif(
- filename:join(?config(data_dir, Config),
- "dirty_nif_SUITE"), []);
- true ->
- ok
- end,
- Config;
- _ ->
- {skipped, "No dirty scheduler support"}
- end.
+ case lib_loaded() of
+ false ->
+ ok = erlang:load_nif(filename:join(?config(data_dir, Config),
+ "dirty_nif_SUITE"), []);
+ true ->
+ ok
+ end,
+ Config.
end_per_suite(_Config) ->
ok.
@@ -82,6 +133,9 @@ init_per_testcase(Case, Config) ->
end_per_testcase(_Case, _Config) ->
ok.
+load_nif(NifLib, LibInfo) ->
+ erlang:load_nif(NifLib, LibInfo).
+
dirty_nif(Config) when is_list(Config) ->
Val1 = 42,
Val2 = "Erlang",
@@ -536,6 +590,356 @@ literal_area(Config) when is_list(Config) ->
literal_area_collector_test:check_idle(5000),
{comment, "Waited "++integer_to_list(TMO)++" milliseconds after purge"}.
+set_halt_options_from_nif(Config) when is_list(Config) ->
+ case ?config(group, Config) of
+ halt_normal ->
+ error = set_halt_option_from_nif_normal(set_on_halt_handler),
+ error = set_halt_option_from_nif_normal(delay_halt);
+ halt_dirty_cpu ->
+ error = set_halt_option_from_nif_cpu_bound(set_on_halt_handler),
+ error = set_halt_option_from_nif_cpu_bound(delay_halt);
+ halt_dirty_io ->
+ error = set_halt_option_from_nif_io_bound(set_on_halt_handler),
+ error = set_halt_option_from_nif_io_bound(delay_halt)
+ end,
+ ok.
+
+delay_halt(Config) when is_list(Config) ->
+ delay_halt(Config, new_code).
+
+delay_halt_old_code(Config) when is_list(Config) ->
+ delay_halt(Config, old_code).
+
+delay_halt_old_and_new_code(Config) when is_list(Config) ->
+ delay_halt(Config, old_and_new_code).
+
+delay_halt(Config, Type) ->
+ Suite = filename:join(?config(data_dir, Config), ?MODULE_STRING),
+ Priv = proplists:get_value(priv_dir, Config),
+ TypeSuffix = "_"++atom_to_list(Type),
+ {Fun, FileName} = case ?config(group, Config) of
+ halt_normal ->
+ {fun delay_halt_normal/3, "delay_halt_normal"++TypeSuffix};
+ halt_dirty_io ->
+ {fun delay_halt_io_bound/3, "delay_halt_io_bound"++TypeSuffix};
+ halt_dirty_cpu ->
+ {fun delay_halt_cpu_bound/3, "delay_halt_cpu_bound"++TypeSuffix}
+ end,
+ Tester = self(),
+ NifFileName = filename:join(Priv, FileName),
+ {ok, Peer, Node} = ?CT_PEER(),
+ Mon = erlang:monitor(process, Peer),
+ ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {delay_halt}]),
+ ok = erpc:call(Node, file, set_cwd, [Priv]),
+ Proxy = spawn_link(Node,
+ fun () ->
+ receive
+ {delay_halt, _} = Msg ->
+ unlink(Tester),
+ Tester ! Msg
+ end
+ end),
+ Start = erlang:monotonic_time(millisecond),
+ ok = erpc:cast(Node, fun () -> Fun(Proxy, FileName, 2) end),
+ receive {delay_halt, Pid} when is_pid(Pid), Node == node(Pid) -> ok end,
+ case Type of
+ new_code ->
+ ok;
+ old_code ->
+ true = erpc:call(Node, erlang, delete_module, [dirty_nif_SUITE]);
+ old_and_new_code ->
+ true = erpc:call(Node, erlang, delete_module, [dirty_nif_SUITE]),
+ ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {delay_halt}]),
+ Proxy2 = spawn_link(Node,
+ fun () ->
+ receive
+ {delay_halt, _} = Msg ->
+ unlink(Tester),
+ Tester ! Msg
+ end
+ end),
+ ok = erpc:cast(Node, fun () -> Fun(Proxy2, FileName++"_new_code", 2) end),
+ receive {delay_halt, Pid2} when is_pid(Pid2), Node == node(Pid2) -> ok end
+ end,
+ ok = erpc:cast(Node, erlang, halt, []),
+ ok = wait_until(fun () ->
+ {ok, <<"ok">>} == file:read_file(NifFileName)
+ andalso
+ (Type /= old_and_new_code
+ orelse {ok, <<"ok">>} == file:read_file(NifFileName++"_new_code"))
+ end,
+ 6000),
+ Time = erlang:monotonic_time(millisecond) - Start,
+ ct:log("~s time=~pms", [FileName, Time]),
+ true = Time >= 2000,
+ receive {'DOWN', Mon, process, Peer, _} -> ok end,
+ ok.
+
+flush_false(Config) when is_list(Config) ->
+ Suite = filename:join(?config(data_dir, Config), ?MODULE_STRING),
+ Priv = proplists:get_value(priv_dir, Config),
+ {Fun, FileName} = case ?config(group, Config) of
+ halt_dirty_io ->
+ {fun delay_halt_io_bound/3, "flush_false_io_bound"};
+ halt_dirty_cpu ->
+ {fun delay_halt_cpu_bound/3, "flush_false_cpu_bound"}
+ end,
+ Tester = self(),
+ NifFileName = filename:join(Priv, FileName),
+ OnHaltBaseName = FileName++"_on_halt",
+ OnHaltFileName = filename:join(Priv, OnHaltBaseName),
+ {ok, Peer, Node} = ?CT_PEER(),
+ Mon = erlang:monitor(process, Peer),
+ ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {sync_halt, OnHaltBaseName, 1}]),
+ ok = erpc:call(Node, file, set_cwd, [Priv]),
+ Proxy = spawn_link(Node,
+ fun () ->
+ receive
+ {delay_halt, _} = Msg ->
+ unlink(Tester),
+ Tester ! Msg
+ end
+ end),
+ Start = erlang:monotonic_time(millisecond),
+ ok = erpc:cast(Node, fun () -> Fun(Proxy, FileName, 1) end),
+ receive {delay_halt, Pid} when is_pid(Pid), Node == node(Pid) -> ok end,
+ ok = erpc:cast(Node, erlang, halt, [0, [{flush,false}]]),
+ receive {'DOWN', Mon, process, Peer, _} -> ok end,
+ Time = erlang:monotonic_time(millisecond) - Start,
+ ct:log("~s time=~pms", [FileName, Time]),
+ Wait = 3000-Time,
+ if Wait > 0 -> receive after Wait -> ok end;
+ true -> ok
+ end,
+ {error,enoent} = file:read_file(NifFileName),
+ {error,enoent} = file:read_file(OnHaltFileName),
+ ok.
+
+many_delay_halt(Config) when is_list(Config) ->
+ try
+ many_delay_halt_test(Config)
+ catch
+ throw:{skip, _} = Skip ->
+ Skip
+ end.
+
+many_delay_halt_test(Config) ->
+ Suite = filename:join(?config(data_dir, Config), ?MODULE_STRING),
+ Priv = proplists:get_value(priv_dir, Config),
+ Tester = self(),
+ {ok, Peer, Node} = ?CT_PEER(),
+ Chk = fun () ->
+ case erlang:system_info(schedulers_online) of
+ 1 -> throw({skip, "Too few schedulers online"});
+ _ -> ok
+ end,
+ case erlang:system_info(dirty_cpu_schedulers_online) of
+ 1 -> throw({skip, "Too few dirty cpu schedulers online"});
+ _ -> ok
+ end,
+ case erlang:system_info(dirty_io_schedulers) of
+ 1 -> throw({skip, "Too few dirty io schedulers online"});
+ _ -> ok
+ end
+ end,
+ try
+ erpc:call(Node, Chk)
+ catch
+ throw:{skip, _} = Skip ->
+ peer:stop(Peer),
+ throw(Skip)
+ end,
+ Mon = erlang:monitor(process, Peer),
+ ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {delay_halt}]),
+ ok = erpc:call(Node, file, set_cwd, [Priv]),
+ ProxyFun = fun (Tag) ->
+ fun () ->
+ receive
+ {delay_halt, _} = Msg ->
+ unlink(Tester),
+ Tester ! {Tag, Msg}
+ end
+ end
+ end,
+ [P1, P2, P3, P4, P5] = [spawn_link(Node, ProxyFun(X)) || X <- lists:seq(1, 5)],
+ Start = erlang:monotonic_time(millisecond),
+ ok = erpc:cast(Node, fun () -> delay_halt_io_bound(P1, "many_delay_halt_io2", 2) end),
+ ok = erpc:cast(Node, fun () -> delay_halt_io_bound(P2, "many_delay_halt_io1", 1) end),
+ ok = erpc:cast(Node, fun () -> delay_halt_cpu_bound(P3, "many_delay_halt_cpu1", 1) end),
+ ok = erpc:cast(Node, fun () -> delay_halt_cpu_bound(P4, "many_delay_halt_cpu2", 2) end),
+ _ = [receive
+ {X, {delay_halt, Pid}} when is_pid(Pid), Node == node(Pid) ->
+ ok
+ end || X <- lists:seq(1, 4)],
+ ok = erpc:cast(Node, fun () -> delay_halt_normal(P5, "many_delay_halt_normal", 1) end),
+ receive
+ {5, {delay_halt, Pid}} when is_pid(Pid), Node == node(Pid) ->
+ ok
+ end,
+ ok = erpc:cast(Node, erlang, halt, []),
+ ok = wait_until(fun () ->
+ {ok, <<"ok">>} == file:read_file(filename:join(Priv, "many_delay_halt_io2"))
+ andalso {ok, <<"ok">>} == file:read_file(filename:join(Priv, "many_delay_halt_cpu2"))
+ end,
+ 3000),
+ {ok, <<"ok">>} = file:read_file(filename:join(Priv, "many_delay_halt_cpu1")),
+ {ok, <<"ok">>} = file:read_file(filename:join(Priv, "many_delay_halt_io1")),
+ {ok, <<"ok">>} = file:read_file(filename:join(Priv, "many_delay_halt_normal")),
+ Time = erlang:monotonic_time(millisecond) - Start,
+ ct:log("many_delay_halt time=~pms", [Time]),
+ true = Time >= 2000,
+ receive {'DOWN', Mon, process, Peer, _} -> ok end,
+ ok.
+
+on_halt(Config) when is_list(Config) ->
+ on_halt(Config, new_code).
+
+on_halt_old_code(Config) when is_list(Config) ->
+ on_halt(Config, old_code).
+
+on_halt_old_and_new_code(Config) when is_list(Config) ->
+ on_halt(Config, old_and_new_code).
+
+on_halt(Config, Type) ->
+ Suite = filename:join(?config(data_dir, Config), ?MODULE_STRING),
+ Priv = proplists:get_value(priv_dir, Config),
+ FileName = "on_halt_"++atom_to_list(Type),
+ OnHaltFileName = filename:join(Priv, FileName),
+ {ok, Peer, Node} = ?CT_PEER(),
+ Mon = erlang:monitor(process, Peer),
+ ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {on_halt, FileName, 1}]),
+ case Type of
+ new_code ->
+ ok;
+ old_code ->
+ true = erpc:call(Node, erlang, delete_module, [dirty_nif_SUITE]);
+ old_and_new_code ->
+ true = erpc:call(Node, erlang, delete_module, [dirty_nif_SUITE]),
+ ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {on_halt, FileName++"_new", 1}])
+ end,
+ ok = erpc:call(Node, file, set_cwd, [Priv]),
+ Start = erlang:monotonic_time(millisecond),
+ ok = erpc:cast(Node, erlang, halt, []),
+ ok = wait_until(fun () ->
+ {ok, <<"ok">>} == file:read_file(OnHaltFileName)
+ andalso (Type /= old_and_new_code
+ orelse {ok, <<"ok">>} == file:read_file(OnHaltFileName++"_new"))
+ end,
+ 3000),
+ Time = erlang:monotonic_time(millisecond) - Start,
+ ct:log("~s time=~pms", [FileName, Time]),
+ if Type == old_and_new_code -> true = Time >= 2000;
+ true -> true = Time >= 1000
+ end,
+ receive {'DOWN', Mon, process, Peer, _} -> ok end,
+ ok.
+
+on_halt_module_code_format() ->
+ lists:flatten(["-module(~s).~n",
+ "-export([load/1, lib_loaded/0]).~n",
+ "-nifs([lib_loaded/0]).~n",
+ "load(SoFile) -> erlang:load_nif(SoFile, ?MODULE_STRING).~n",
+ "lib_loaded() -> false.~n"]).
+
+many_on_halt(Config) when is_list(Config) ->
+ DDir = ?config(data_dir, Config),
+ Priv = proplists:get_value(priv_dir, Config),
+ OnHaltModules = ["on_halt_a","on_halt_b","on_halt_c","on_halt_d","on_halt_e","on_halt_f"],
+ DeleteOnHaltModules = ["on_halt_a","on_halt_c","on_halt_d","on_halt_f"],
+ PurgeOnHaltModules = DeleteOnHaltModules -- ["on_halt_d"],
+ ActiveOnHaltModules = OnHaltModules -- PurgeOnHaltModules,
+ lists:foreach(fun (ModStr) ->
+ Code = io_lib:format(on_halt_module_code_format(), [ModStr]),
+ ok = file:write_file(filename:join(DDir, ModStr++".erl"), Code)
+ end,
+ OnHaltModules),
+ {ok, Peer, Node} = ?CT_PEER(),
+ Mon = erlang:monitor(process, Peer),
+ ok = erpc:call(Node,
+ fun () ->
+ ok = file:set_cwd(Priv),
+ lists:foreach(fun (ModStr) ->
+ AbsModStr = filename:join(DDir, ModStr),
+ {ok,Mod,Bin} = compile:file(AbsModStr, [binary]),
+ {module, Mod} = erlang:load_module(Mod, Bin),
+ ok = Mod:load(AbsModStr),
+ true = Mod:lib_loaded()
+ end,
+ OnHaltModules),
+ lists:foreach(fun (ModStr) ->
+ Mod = list_to_atom(ModStr),
+ true = erlang:delete_module(Mod)
+ end, DeleteOnHaltModules),
+ lists:foreach(fun (ModStr) ->
+ Mod = list_to_atom(ModStr),
+ true = erlang:purge_module(Mod)
+ end, PurgeOnHaltModules),
+ ok
+ end),
+ Start = erlang:monotonic_time(millisecond),
+ ok = erpc:cast(Node, erlang, halt, []),
+ ok = wait_until(fun () ->
+ try
+ lists:foreach(fun (ModStr) ->
+ FileName = filename:join(Priv, ModStr),
+ {ok, <<"ok">>} = file:read_file(FileName)
+ end, ActiveOnHaltModules),
+ true
+ catch
+ _:_ ->
+ false
+ end
+ end,
+ 1000*(length(ActiveOnHaltModules)+1)),
+ Time = erlang:monotonic_time(millisecond) - Start,
+ ct:log("many_on_halt time=~pms", [Time]),
+ true = Time >= length(ActiveOnHaltModules)*1000,
+ receive {'DOWN', Mon, process, Peer, _} -> ok end,
+ ok.
+
+sync_halt(Config) when is_list(Config) ->
+ Suite = filename:join(?config(data_dir, Config), ?MODULE_STRING),
+ Priv = proplists:get_value(priv_dir, Config),
+ {Fun, FileName} = case ?config(group, Config) of
+ halt_dirty_io ->
+ {fun sync_halt_io_bound/2, "sync_halt_io_bound"};
+ halt_dirty_cpu ->
+ {fun sync_halt_cpu_bound/2, "sync_halt_cpu_bound"}
+ end,
+ Tester = self(),
+ NifFileName = filename:join(Priv, FileName),
+ OnHaltBaseFileName = FileName++".onhalt",
+ OnHaltFileName = filename:join(Priv, OnHaltBaseFileName),
+ {ok, Peer, Node} = ?CT_PEER(),
+ Mon = erlang:monitor(process, Peer),
+ ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {sync_halt, OnHaltBaseFileName, 1}]),
+ ok = erpc:call(Node, file, set_cwd, [Priv]),
+ Proxy = spawn_link(Node,
+ fun () ->
+ receive
+ {sync_halt, _} = Msg ->
+ unlink(Tester),
+ Tester ! Msg
+ end
+ end),
+ ok = erpc:cast(Node, fun () -> Fun(Proxy, FileName) end),
+ receive {sync_halt, Pid} when is_pid(Pid), Node == node(Pid) -> ok end,
+ Start = erlang:monotonic_time(millisecond),
+ ok = erpc:cast(Node, erlang, halt, []),
+ ok = wait_until(fun () ->
+ {ok, <<"ok">>} == file:read_file(OnHaltFileName)
+ end,
+ 2000),
+ ok = wait_until(fun () ->
+ {ok, <<"ok">>} == file:read_file(NifFileName)
+ end,
+ 4000),
+ Time = erlang:monotonic_time(millisecond) - Start,
+ ct:log("~s time=~pms", [FileName, Time]),
+ true = Time >= 1000,
+ receive {'DOWN', Mon, process, Peer, _} -> ok end,
+ ok.
+
%%
%% Internal...
%%
@@ -774,6 +1178,14 @@ dirty_heap_access_nif(_) -> ?nif_stub.
whereis_term(_Type,_Name) -> ?nif_stub.
whereis_send(_Type,_Name,_Msg) -> ?nif_stub.
dirty_terminating_literal_access(_Me, _Literal) -> ?nif_stub.
+delay_halt_normal(_Pid, _FileName, _Delay) -> ?nif_stub.
+delay_halt_io_bound(_Pid, _FileName, _Delay) -> ?nif_stub.
+delay_halt_cpu_bound(_Pid, _FileName, _Delay) -> ?nif_stub.
+sync_halt_io_bound(_Pid, _FileName) -> ?nif_stub.
+sync_halt_cpu_bound(_Pid, _FileName) -> ?nif_stub.
+set_halt_option_from_nif_normal(_Op) -> ?nif_stub.
+set_halt_option_from_nif_io_bound(_Op) -> ?nif_stub.
+set_halt_option_from_nif_cpu_bound(_Op) -> ?nif_stub.
nif_stub_error(Line) ->
exit({nif_not_loaded,module,?MODULE,line,Line}).
diff --git a/erts/emulator/test/dirty_nif_SUITE_data/Makefile.src b/erts/emulator/test/dirty_nif_SUITE_data/Makefile.src
index 4462afd815..55ca552cb2 100644
--- a/erts/emulator/test/dirty_nif_SUITE_data/Makefile.src
+++ b/erts/emulator/test/dirty_nif_SUITE_data/Makefile.src
@@ -1,5 +1,5 @@
-NIF_LIBS = dirty_nif_SUITE@dll@
+NIF_LIBS = dirty_nif_SUITE@dll@ on_halt_a@dll@ on_halt_b@dll@ on_halt_c@dll@ on_halt_d@dll@ on_halt_e@dll@ on_halt_f@dll@
all: $(NIF_LIBS) echo_drv@dll@
diff --git a/erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c b/erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c
index fb5146278b..94edd2f29c 100644
--- a/erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c
+++ b/erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2009-2020. All Rights Reserved.
+ * Copyright Ericsson AB 2009-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,14 @@
*/
#include <erl_nif.h>
#include <assert.h>
+#include <errno.h>
#ifdef __WIN32__
#include <windows.h>
#else
#include <unistd.h>
#endif
+#include <stdio.h>
+#include <string.h>
/*
* Hack to get around this function missing from the NIF API.
@@ -43,8 +46,128 @@ static ERL_NIF_TERM atom_pid;
static ERL_NIF_TERM atom_port;
static ERL_NIF_TERM atom_send;
+typedef struct {
+ int halting;
+ int on_halt_wait;
+ ErlNifMutex *mtx;
+ ErlNifCond *cnd;
+ char *filename;
+} PrivData;
+
+static PrivData *make_priv_data(void)
+{
+ PrivData *pdata = enif_alloc(sizeof(PrivData));
+ if (!pdata)
+ return NULL;
+ pdata->halting = 0;
+ pdata->on_halt_wait = 0;
+ pdata->mtx = NULL;
+ pdata->cnd = NULL;
+ pdata->filename = NULL;
+ return pdata;
+}
+
+static void unload(ErlNifEnv *env, void *priv_data)
+{
+ if (priv_data) {
+ PrivData *pdata = priv_data;
+ if (pdata->mtx)
+ enif_mutex_destroy(pdata->mtx);
+ if (pdata->cnd)
+ enif_cond_destroy(pdata->cnd);
+ if (pdata->filename)
+ enif_free(pdata->filename);
+ enif_free(pdata);
+ }
+}
+
+static void on_halt(void *priv_data);
+
static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
{
+ int arity;
+ const ERL_NIF_TERM *array;
+ if (enif_get_tuple(env, load_info, &arity, &array)) {
+ char atom_text[32];
+ int err;
+ unsigned filename_len;
+ PrivData *pdata = NULL;
+
+ if (arity < 1 || 3 < arity)
+ return __LINE__;
+ if (!enif_get_atom(env, array[0], &atom_text[0],
+ sizeof(atom_text), ERL_NIF_LATIN1)) {
+ return __LINE__;
+ }
+ pdata = make_priv_data();
+ if (!pdata)
+ return __LINE__;
+ if (strcmp(atom_text, "on_halt") == 0) {
+ if (0 != enif_set_option(env, ERL_NIF_OPT_ON_HALT, on_halt)) {
+ unload(env, pdata);
+ return __LINE__;
+ }
+ }
+ else if (strcmp(atom_text, "delay_halt") == 0) {
+ if (0 != enif_set_option(env, ERL_NIF_OPT_DELAY_HALT)) {
+ unload(env, pdata);
+ return __LINE__;
+ }
+ }
+ else if (strcmp(atom_text, "sync_halt") == 0) {
+ if (0 != enif_set_option(env, ERL_NIF_OPT_ON_HALT, on_halt)) {
+ unload(env, pdata);
+ return __LINE__;
+ }
+ if (0 != enif_set_option(env, ERL_NIF_OPT_DELAY_HALT)) {
+ unload(env, pdata);
+ return __LINE__;
+ }
+ pdata->mtx = enif_mutex_create("sync_halt_dirty_nif_SUITE");
+ pdata->cnd = enif_cond_create("sync_halt_dirty_nif_SUITE");
+ if (!pdata->mtx || !pdata->cnd) {
+ unload(env, pdata);
+ return __LINE__;
+ }
+ }
+ else {
+ unload(env, pdata);
+ return __LINE__;
+ }
+
+ if (arity >= 2) {
+ if (!enif_get_list_length(env, array[1], &filename_len)) {
+ unload(env, pdata);
+ return __LINE__;
+ }
+ if (filename_len > 0) {
+ filename_len++;
+ pdata->filename = enif_alloc(filename_len);
+ if (!pdata->filename) {
+ unload(env, pdata);
+ return __LINE__;
+ }
+ if (filename_len != enif_get_string(env,
+ array[1],
+ pdata->filename,
+ filename_len,
+ ERL_NIF_LATIN1)) {
+ unload(env, pdata);
+ return __LINE__;
+ }
+ }
+ }
+ if (arity == 3) {
+ if (!enif_get_int(env, array[2], &pdata->on_halt_wait)
+ || pdata->on_halt_wait < 0
+ || pdata->on_halt_wait*1000 < 0) {
+ unload(env, pdata);
+ return __LINE__;
+ }
+ }
+ *priv_data = (void *) pdata;
+ }
+
atom_badarg = enif_make_atom(env, "badarg");
atom_error = enif_make_atom(env, "error");
atom_false = enif_make_atom(env,"false");
@@ -57,6 +180,11 @@ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
return 0;
}
+static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
+{
+ return load(env, priv_data, load_info);
+}
+
static ERL_NIF_TERM lib_loaded(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
return enif_make_atom(env, "true");
@@ -468,6 +596,149 @@ static ERL_NIF_TERM dirty_terminating_literal_access(ErlNifEnv* env, int argc, c
return self_term;
}
+static int fn_write_ok(char *filename)
+{
+ FILE *file = fopen(filename, "w");
+ if (!file)
+ return EINVAL;
+ if (1 != fwrite("ok", 2, 1, file))
+ return EINVAL;
+ fclose(file);
+ return 0;
+}
+
+static int efn_write_ok(ErlNifEnv *env, const ERL_NIF_TERM arg)
+{
+ int res;
+ unsigned filename_len;
+ char *filename;
+
+ if (!enif_get_list_length(env, arg, &filename_len) || filename_len < 2) {
+ res = EINVAL;
+ goto done;
+ }
+ filename_len++;
+ filename = enif_alloc(filename_len);
+ if (!filename) {
+ res = ENOMEM;
+ goto done;
+ }
+ if (filename_len != enif_get_string(env,
+ arg,
+ filename,
+ filename_len,
+ ERL_NIF_LATIN1)) {
+ res = EINVAL;
+ goto done;
+ }
+ res = fn_write_ok(filename);
+done:
+ if (filename)
+ enif_free(filename);
+ switch (res) {
+ case 0:
+ return atom_ok;
+ case ENOMEM:
+ return enif_raise_exception(env, enif_make_atom(env, "enomem"));
+ default:
+ return enif_make_badarg(env);
+ }
+}
+
+static ERL_NIF_TERM delay_halt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{
+ ERL_NIF_TERM msg;
+ ErlNifPid receiver, self;
+ int res, secs;
+
+ if (argc != 3)
+ return enif_make_badarg(env);
+ if (!enif_get_int(env, argv[2], &secs))
+ return enif_make_badarg(env);
+ if (secs < 0 || secs*1000 < 0)
+ return enif_make_badarg(env);
+ if (!enif_self(env, &self))
+ return enif_make_badarg(env);
+ if (!enif_get_local_pid(env, argv[0], &receiver))
+ return enif_make_badarg(env);
+ msg = enif_make_tuple2(env, enif_make_atom(env, "delay_halt"), enif_make_pid(env, &self));
+ res = enif_send(env, &receiver, NULL, msg);
+ if (!res)
+ return enif_make_badarg(env);
+
+#ifdef __WIN32__
+ Sleep(secs*1000);
+#else
+ sleep(secs);
+#endif
+ return efn_write_ok(env, argv[1]);
+}
+
+static ERL_NIF_TERM sync_halt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{
+ ERL_NIF_TERM msg;
+ ErlNifPid receiver, self;
+ int res;
+ PrivData *pdata = enif_priv_data(env);
+ if (!pdata)
+ return enif_raise_exception(env, enif_make_atom(env, "missing_priv_data"));
+ if (argc != 2)
+ return enif_make_badarg(env);
+ if (!enif_self(env, &self))
+ return enif_make_badarg(env);
+ if (!enif_get_local_pid(env, argv[0], &receiver))
+ return enif_make_badarg(env);
+ msg = enif_make_tuple2(env, enif_make_atom(env, "sync_halt"), enif_make_pid(env, &self));
+ res = enif_send(env, &receiver, NULL, msg);
+ if (!res)
+ return enif_make_badarg(env);
+ enif_mutex_lock(pdata->mtx);
+ while (!pdata->halting)
+ enif_cond_wait(pdata->cnd, pdata->mtx);
+ enif_mutex_unlock(pdata->mtx);
+ return efn_write_ok(env, argv[1]);
+}
+
+static ERL_NIF_TERM set_halt_option_from_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{
+ unsigned len;
+ char atom_text[32];
+ if (argc != 1)
+ return enif_make_badarg(env);
+ if (!enif_get_atom(env, argv[0], &atom_text[0], sizeof(atom_text), ERL_NIF_LATIN1))
+ return enif_make_badarg(env);
+ if (strcmp(atom_text, "set_on_halt_handler") == 0) {
+ if (0 == enif_set_option(env, ERL_NIF_OPT_ON_HALT, on_halt))
+ return atom_ok;
+ return atom_error;
+ }
+ else if (strcmp(atom_text, "delay_halt") == 0) {
+ if (0 == enif_set_option(env, ERL_NIF_OPT_DELAY_HALT))
+ return atom_ok;
+ return atom_error;
+ }
+ return enif_make_badarg(env);
+}
+
+static void on_halt(void *priv_data)
+{
+ PrivData *pdata = (PrivData *)priv_data;
+ int res;
+#ifdef __WIN32__
+ Sleep(pdata->on_halt_wait*1000);
+#else
+ sleep(pdata->on_halt_wait);
+#endif
+ if (pdata->mtx) {
+ enif_mutex_lock(pdata->mtx);
+ assert(!pdata->halting);
+ pdata->halting = !0;
+ enif_cond_broadcast(pdata->cnd);
+ enif_mutex_unlock(pdata->mtx);
+ }
+ res = fn_write_ok(pdata->filename);
+ assert(res == 0);
+}
static ErlNifFunc nif_funcs[] =
{
@@ -484,6 +755,14 @@ static ErlNifFunc nif_funcs[] =
{"whereis_send", 3, whereis_send, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"whereis_term", 2, whereis_term, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"dirty_terminating_literal_access", 2, dirty_terminating_literal_access, ERL_NIF_DIRTY_JOB_CPU_BOUND},
+ {"delay_halt_normal", 3, delay_halt, 0},
+ {"delay_halt_io_bound", 3, delay_halt, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"delay_halt_cpu_bound", 3, delay_halt, ERL_NIF_DIRTY_JOB_CPU_BOUND},
+ {"sync_halt_io_bound", 2, sync_halt, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"sync_halt_cpu_bound", 2, sync_halt, ERL_NIF_DIRTY_JOB_CPU_BOUND},
+ {"set_halt_option_from_nif_normal", 1, set_halt_option_from_nif, 0},
+ {"set_halt_option_from_nif_io_bound", 1, set_halt_option_from_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"set_halt_option_from_nif_cpu_bound", 1, set_halt_option_from_nif, ERL_NIF_DIRTY_JOB_CPU_BOUND}
};
-ERL_NIF_INIT(dirty_nif_SUITE,nif_funcs,load,NULL,NULL,NULL)
+ERL_NIF_INIT(dirty_nif_SUITE,nif_funcs,load,NULL,upgrade,unload)
diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_a.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_a.c
new file mode 100644
index 0000000000..73d50a2bf1
--- /dev/null
+++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_a.c
@@ -0,0 +1,23 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+#include "on_halt_nif.c"
+
+ERL_NIF_INIT(on_halt_a,nif_funcs,load,NULL,NULL,unload)
diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_b.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_b.c
new file mode 100644
index 0000000000..b9e13a17fa
--- /dev/null
+++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_b.c
@@ -0,0 +1,23 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+#include "on_halt_nif.c"
+
+ERL_NIF_INIT(on_halt_b,nif_funcs,load,NULL,NULL,unload)
diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_c.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_c.c
new file mode 100644
index 0000000000..db875e7f18
--- /dev/null
+++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_c.c
@@ -0,0 +1,23 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+#include "on_halt_nif.c"
+
+ERL_NIF_INIT(on_halt_c,nif_funcs,load,NULL,NULL,unload)
diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_d.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_d.c
new file mode 100644
index 0000000000..e3b64245ce
--- /dev/null
+++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_d.c
@@ -0,0 +1,23 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+#include "on_halt_nif.c"
+
+ERL_NIF_INIT(on_halt_d,nif_funcs,load,NULL,NULL,unload)
diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_e.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_e.c
new file mode 100644
index 0000000000..73357c9b9d
--- /dev/null
+++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_e.c
@@ -0,0 +1,23 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+#include "on_halt_nif.c"
+
+ERL_NIF_INIT(on_halt_e,nif_funcs,load,NULL,NULL,unload)
diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_f.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_f.c
new file mode 100644
index 0000000000..58b4955d4f
--- /dev/null
+++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_f.c
@@ -0,0 +1,23 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+
+#include "on_halt_nif.c"
+
+ERL_NIF_INIT(on_halt_f,nif_funcs,load,NULL,NULL,unload)
diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_nif.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_nif.c
new file mode 100644
index 0000000000..610b80d3f7
--- /dev/null
+++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_nif.c
@@ -0,0 +1,96 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2022. All Rights Reserved.
+ *
+ * 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.
+ *
+ * %CopyrightEnd%
+ */
+#include "erl_nif.h"
+#include <errno.h>
+#include <assert.h>
+#ifdef __WIN32__
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+
+
+static int fn_write_ok(char *filename)
+{
+ FILE *file = fopen(filename, "w");
+ if (!file)
+ return EINVAL;
+ if (1 != fwrite("ok", 2, 1, file))
+ return EINVAL;
+ fclose(file);
+ return 0;
+}
+
+static void on_halt(void *priv_data)
+{
+ int res;
+#ifdef __WIN32__
+ Sleep(1000);
+#else
+ sleep(1);
+#endif
+ assert(priv_data);
+ res = fn_write_ok((char *) priv_data);
+ assert(res == 0);
+}
+
+static void unload(ErlNifEnv *env, void *priv_data)
+{
+ if (priv_data)
+ enif_free(priv_data);
+}
+
+static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ unsigned filename_len;
+ char *filename;
+ if (0 != enif_set_option(env, ERL_NIF_OPT_ON_HALT, on_halt))
+ return __LINE__;
+ if (!enif_get_list_length(env, load_info, &filename_len))
+ return __LINE__;
+ if (filename_len == 0)
+ return __LINE__;
+ filename_len++;
+ filename = enif_alloc(filename_len);
+ if (!filename)
+ return __LINE__;
+ if (filename_len != enif_get_string(env,
+ load_info,
+ filename,
+ filename_len,
+ ERL_NIF_LATIN1)) {
+ enif_free(filename);
+ return __LINE__;
+ }
+ *priv_data = (void *) filename;
+ return 0;
+}
+
+static ERL_NIF_TERM lib_loaded(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ return enif_make_atom(env, "true");
+}
+
+static ErlNifFunc nif_funcs[] =
+{
+ {"lib_loaded", 0, lib_loaded}
+};
diff --git a/erts/emulator/test/distribution_SUITE.erl b/erts/emulator/test/distribution_SUITE.erl
index ceedd26eb8..d577f2a1e5 100644
--- a/erts/emulator/test/distribution_SUITE.erl
+++ b/erts/emulator/test/distribution_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -877,9 +877,12 @@ make_busy(Node, Time) when is_integer(Time) ->
receive after Own -> ok end,
until(fun () ->
case {DCtrl, process_info(Pid, status)} of
- {DPrt, {status, suspended}} when is_port(DPrt) -> true;
- {DPid, {status, waiting}} when is_pid(DPid) -> true;
- _ -> false
+ {DPrt, {status, waiting}} when is_port(DPrt) ->
+ verify_busy(DPrt);
+ {DPid, {status, waiting}} when is_pid(DPid) ->
+ true;
+ _ ->
+ false
end
end),
%% then dist entry
@@ -896,6 +899,28 @@ unmake_busy(Pid) ->
unlink(Pid),
exit(Pid, bang).
+verify_busy(Port) ->
+ Parent = self(),
+ Pid =
+ spawn_link(
+ fun() ->
+ port_command(Port, "Just some data"),
+ Error = {not_busy, Port},
+ exit(Parent, Error),
+ error(Error)
+ end),
+ receive after 30 -> ok end,
+ case process_info(Pid, status) of
+ {status, suspended} ->
+ unlink(Pid),
+ exit(Pid, kill),
+ true;
+ {status, _} = WrongStatus ->
+ unlink(Pid),
+ exit(Pid, WrongStatus),
+ error(WrongStatus)
+ end.
+
do_busy_test(Node, Fun) ->
Busy = make_busy(Node, 1000),
{P, M} = spawn_monitor(Fun),
@@ -2553,7 +2578,19 @@ ensure_dctrl(Node) ->
end.
dctrl_send(DPrt, Data) when is_port(DPrt) ->
- port_command(DPrt, Data);
+ try prim_inet:send(DPrt, Data) of
+ ok ->
+ ok;
+ Result ->
+ io:format("~w/2: ~p~n", [?FUNCTION_NAME, Result]),
+ Result
+ catch
+ Class: Reason: Stacktrace ->
+ io:format(
+ "~w/2: ~p: ~p: ~p ~n",
+ [?FUNCTION_NAME, Class, Reason, Stacktrace]),
+ erlang:raise(Class, Reason, Stacktrace)
+ end;
dctrl_send(DPid, Data) when is_pid(DPid) ->
Ref = make_ref(),
DPid ! {send, self(), Ref, Data},
@@ -3246,7 +3283,7 @@ is_alive_tester(Node) ->
ok
end.
-dyn_node_name_monitor_node(Config) ->
+dyn_node_name_monitor_node(_Config) ->
%% Test that monitor_node() does not fail when erlang:is_alive() return true
%% but we have not yet gotten a name...
Args = ["-setcookie", atom_to_list(erlang:get_cookie()),
@@ -3284,7 +3321,7 @@ dyn_node_name_monitor_node_test(StartOpts, TestNode) ->
ok.
-dyn_node_name_monitor(Config) ->
+dyn_node_name_monitor(_Config) ->
%% Test that monitor() does not fail when erlang:is_alive() return true
%% but we have not yet gotten a name...
Args = ["-setcookie", atom_to_list(erlang:get_cookie()),
@@ -3716,7 +3753,7 @@ id(X) ->
wait_until(Fun) ->
wait_until(Fun, 24*60*60*1000).
-wait_until(Fun, Timeout) when Timeout < 0 ->
+wait_until(_Fun, Timeout) when Timeout < 0 ->
timeout;
wait_until(Fun, Timeout) ->
case catch Fun() of
@@ -3887,8 +3924,8 @@ forever(Fun) ->
Fun(),
forever(Fun).
-abort(Why) ->
- set_internal_state(abort, Why).
+%% abort(Why) ->
+%% set_internal_state(abort, Why).
start_busy_dist_port_tracer() ->
diff --git a/erts/emulator/test/erl_link_SUITE.erl b/erts/emulator/test/erl_link_SUITE.erl
index 0e3829c275..391dfe6bf9 100644
--- a/erts/emulator/test/erl_link_SUITE.erl
+++ b/erts/emulator/test/erl_link_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -671,9 +671,12 @@ make_busy(Node, Time) when is_integer(Time) ->
receive after Own -> ok end,
wait_until(fun () ->
case {DCtrl, process_info(Pid, status)} of
- {DPrt, {status, suspended}} when is_port(DPrt) -> true;
- {DPid, {status, waiting}} when is_pid(DPid) -> true;
- _ -> false
+ {DPrt, {status, waiting}} when is_port(DPrt) ->
+ verify_busy(DPrt);
+ {DPid, {status, waiting}} when is_pid(DPid) ->
+ true;
+ _->
+ false
end
end),
%% then dist entry
@@ -690,6 +693,28 @@ unmake_busy(Pid) ->
unlink(Pid),
exit(Pid, bang).
+verify_busy(Port) ->
+ Parent = self(),
+ Pid =
+ spawn_link(
+ fun() ->
+ port_command(Port, "Just some data"),
+ Error = {not_busy, Port},
+ exit(Parent, Error),
+ error(Error)
+ end),
+ receive after 30 -> ok end,
+ case process_info(Pid, status) of
+ {status, suspended} ->
+ unlink(Pid),
+ exit(Pid, kill),
+ true;
+ {status, _} = WrongStatus ->
+ unlink(Pid),
+ exit(Pid, WrongStatus),
+ error(WrongStatus)
+ end.
+
suspend_on_busy_test(Node, Doing, Fun) ->
Tester = self(),
DoIt = make_ref(),
@@ -1205,7 +1230,19 @@ ensure_dctrl(Node) ->
end.
dctrl_send(DPrt, Data) when is_port(DPrt) ->
- port_command(DPrt, Data);
+ try prim_inet:send(DPrt, Data) of
+ ok ->
+ ok;
+ Result ->
+ io:format("~w/2: ~p~n", [?FUNCTION_NAME, Result]),
+ Result
+ catch
+ Class: Reason: Stacktrace ->
+ io:format(
+ "~w/2: ~p: ~p: ~p ~n",
+ [?FUNCTION_NAME, Class, Reason, Stacktrace]),
+ erlang:raise(Class, Reason, Stacktrace)
+ end;
dctrl_send(DPid, Data) when is_pid(DPid) ->
Ref = make_ref(),
DPid ! {send, self(), Ref, Data},
diff --git a/erts/emulator/test/erts_test_utils.erl b/erts/emulator/test/erts_test_utils.erl
index 85ef8c203f..dd6b0311b1 100644
--- a/erts/emulator/test/erts_test_utils.erl
+++ b/erts/emulator/test/erts_test_utils.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -47,6 +47,8 @@
-define(V4_PORT_EXT, $x).
-define(OLD_MAX_PIDS_PORTS, ((1 bsl 28) - 1)).
+-define(OLD_MAX_PID_NUM, ((1 bsl 15) - 1)).
+-define(OLD_MAX_PID_SER, ((1 bsl 13) - 1)).
uint64_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 64 ->
[(Uint bsr 56) band 16#ff,
@@ -79,27 +81,52 @@ uint8(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 8 ->
uint8(Uint) ->
exit({badarg, uint8, [Uint]}).
-pid_tag(bad_creation) -> ?PID_EXT;
-pid_tag(Creation) when Creation =< 3 -> ?PID_EXT;
-pid_tag(_Creation) -> ?NEW_PID_EXT.
-
-enc_creation(bad_creation) -> uint8(4);
-enc_creation(Creation) when Creation =< 3 -> uint8(Creation);
-enc_creation(Creation) -> uint32_be(Creation).
+pid_tag(_, _, bad_creation) ->
+ ?PID_EXT;
+pid_tag(Num, Ser, Creation) when Num =< ?OLD_MAX_PID_NUM,
+ Ser =< ?OLD_MAX_PID_SER,
+ Creation =< 3 ->
+ ?PID_EXT;
+pid_tag(_Num, _Ser, _Creation) ->
+ ?NEW_PID_EXT.
+
+enc_creation(_, bad_creation) ->
+ uint8(4);
+enc_creation(Num, Creation) when is_integer(Num),
+ Num =< ?OLD_MAX_PIDS_PORTS,
+ Creation =< 3 ->
+ uint8(Creation);
+enc_creation(Nums, Creation) when is_list(Nums),
+ length(Nums) =< 3,
+ Creation =< 3 ->
+ uint8(Creation);
+enc_creation(_Num, Creation) ->
+ uint32_be(Creation).
+
+enc_creation(_, _, bad_creation) ->
+ uint8(4);
+enc_creation(Num, Ser, Creation) when Num =< ?OLD_MAX_PID_NUM,
+ Ser =< ?OLD_MAX_PID_SER,
+ Creation =< 3 ->
+ uint8(Creation);
+enc_creation(_Num, _Ser, Creation) ->
+ uint32_be(Creation).
mk_ext_pid({NodeName, Creation}, Number, Serial) when is_atom(NodeName) ->
mk_ext_pid({atom_to_list(NodeName), Creation}, Number, Serial);
mk_ext_pid({NodeName, Creation}, Number, Serial) ->
case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- pid_tag(Creation),
+ pid_tag(Number, Serial, Creation),
?ATOM_EXT,
uint16_be(length(NodeName)),
NodeName,
uint32_be(Number),
uint32_be(Serial),
- enc_creation(Creation)])) of
+ enc_creation(Number, Serial, Creation)])) of
Pid when is_pid(Pid) ->
Pid;
+ {'EXIT', {badarg, uint32_be, _}} ->
+ exit({badarg, mk_pid, [{NodeName, Creation}, Number, Serial]});
{'EXIT', {badarg, _}} ->
exit({badarg, mk_pid, [{NodeName, Creation}, Number, Serial]});
Other ->
@@ -127,21 +154,23 @@ mk_ext_port({NodeName, Creation}, Number) ->
true -> uint64_be(Number);
false -> uint32_be(Number)
end,
- enc_creation(Creation)])) of
+ enc_creation(Number, Creation)])) of
Port when is_port(Port) ->
Port;
+ {'EXIT', {badarg, Uint, _}} when Uint == uint64_be; Uint == uint32_be ->
+ exit({badarg, mk_port, [{NodeName, Creation}, Number]});
{'EXIT', {badarg, _}} ->
exit({badarg, mk_port, [{NodeName, Creation}, Number]});
Other ->
exit({unexpected_binary_to_term_result, Other})
end.
-ref_tag(bad_creation) -> ?NEW_REFERENCE_EXT;
-ref_tag(Creation) when Creation =< 3 -> ?NEW_REFERENCE_EXT;
-ref_tag(_Creation) -> ?NEWER_REFERENCE_EXT.
+ref_tag(_Nums, bad_creation) -> ?NEW_REFERENCE_EXT;
+ref_tag(Nums, Creation) when length(Nums) =< 3, Creation =< 3 -> ?NEW_REFERENCE_EXT;
+ref_tag(_Nums, _Creation) -> ?NEWER_REFERENCE_EXT.
mk_ext_ref({NodeName, Creation}, Numbers) when is_atom(NodeName),
- is_list(Numbers) ->
+ is_list(Numbers) ->
mk_ext_ref({atom_to_list(NodeName), Creation}, Numbers);
mk_ext_ref({NodeName, Creation}, [Number]) when is_list(NodeName),
Creation =< 3,
@@ -163,12 +192,12 @@ mk_ext_ref({NodeName, Creation}, [Number]) when is_list(NodeName),
mk_ext_ref({NodeName, Creation}, Numbers) when is_list(NodeName),
is_list(Numbers) ->
case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- ref_tag(Creation),
+ ref_tag(Numbers, Creation),
uint16_be(length(Numbers)),
?ATOM_EXT,
uint16_be(length(NodeName)),
NodeName,
- enc_creation(Creation),
+ enc_creation(Numbers, Creation),
lists:map(fun (N) ->
uint32_be(N)
end,
diff --git a/erts/emulator/test/exception_SUITE.erl b/erts/emulator/test/exception_SUITE.erl
index ea76a473f2..9d4d0972c2 100644
--- a/erts/emulator/test/exception_SUITE.erl
+++ b/erts/emulator/test/exception_SUITE.erl
@@ -846,6 +846,8 @@ error_info(_Config) ->
{display, ["test erlang:display/1"], [no_fail]},
{display_string, [{a,b,c}]},
+ {display_string, [standard_out,"test erlang:display/2"]},
+ {display_string, [stdout,{a,b,c}]},
%% Internal undcoumented BIFs.
{dist_ctrl_get_data, 1},
diff --git a/erts/emulator/test/fun_SUITE.erl b/erts/emulator/test/fun_SUITE.erl
index 3fea66eadc..0f8e7af926 100644
--- a/erts/emulator/test/fun_SUITE.erl
+++ b/erts/emulator/test/fun_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -585,7 +585,7 @@ refc_dist(Config) when is_list(Config) ->
process_flag(trap_exit, true),
Pid = spawn_link(Node, fun() -> receive
Fun when is_function(Fun) ->
- 2 = fun_refc(Fun),
+ 3 = fun_refc(Fun),
exit({normal,Fun}) end
end),
F = fun() -> 42 end,
@@ -608,7 +608,7 @@ refc_dist_send(Node, F) ->
Pid = spawn_link(Node, fun() -> receive
{To,Fun} when is_function(Fun) ->
wait_until(fun () ->
- 2 =:= fun_refc(Fun)
+ 3 =:= fun_refc(Fun)
end),
To ! Fun
end
@@ -636,7 +636,7 @@ refc_dist_reg_send(Node, F) ->
Me ! Ref,
receive
{Me,Fun} when is_function(Fun) ->
- 2 = fun_refc(Fun),
+ 3 = fun_refc(Fun),
Me ! Fun
end
end),
diff --git a/erts/emulator/test/hash_SUITE.erl b/erts/emulator/test/hash_SUITE.erl
index 3232607c9d..90b96f2f97 100644
--- a/erts/emulator/test/hash_SUITE.erl
+++ b/erts/emulator/test/hash_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -224,7 +224,7 @@ notify(X) ->
%%
basic_test() ->
685556714 = erlang:phash({a,b,c},16#FFFFFFFF),
- 37442646 = erlang:phash([a,b,c,{1,2,3},c:pid(0,2,3),
+ 37442646 = erlang:phash([a,b,c,{1,2,3},c:pid(0,2,0),
16#77777777777777],16#FFFFFFFF),
ExternalReference = <<131,114,0,3,100,0,13,110,111,110,111,100,101,64,
110,111,104,111,115,116,0,0,0,0,122,0,0,0,0,0,0,0,0>>,
diff --git a/erts/emulator/test/jit_SUITE.erl b/erts/emulator/test/jit_SUITE.erl
index 67de1b23dc..efd44d31ed 100644
--- a/erts/emulator/test/jit_SUITE.erl
+++ b/erts/emulator/test/jit_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2021-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2021-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@
init_per_suite/1, end_per_suite/1,
init_per_group/2, end_per_group/2,
init_per_testcase/2, end_per_testcase/2]).
--export([annotate/1, named_labels/1, symbols/1]).
+-export([annotate/1, jmsingle/1, named_labels/1, symbols/1]).
suite() ->
[{timetrap, {minutes, 4}}].
@@ -33,7 +33,7 @@ groups() ->
[{perf, [symbols, annotate]}].
all() ->
- [{group, perf}, named_labels].
+ [{group, perf}, jmsingle, named_labels].
init_per_suite(Config) ->
case erlang:system_info(emu_flavor) of
@@ -180,6 +180,62 @@ annotate(Config) ->
[Symbol, Anno])
end.
+run_jmsingle_test(Param, ExpectSuccess, ErrorMsg) ->
+ Cmd = "erl +JMsingle " ++ Param ++ " -noshell " ++
+ "-eval \"erlang:display(all_is_well),erlang:halt(0).\"",
+ Result = os:cmd(Cmd),
+ SuccessfulEmulatorStart =
+ case Result of
+ "all_is_well" ++ _ ->
+ true;
+ _ ->
+ Error = "Failed to allocate executable+writable memory",
+ case string:find(Result, Error) of
+ nomatch -> false;
+ _ -> internal_error
+ end
+ end,
+ case SuccessfulEmulatorStart of
+ ExpectSuccess ->
+ ok;
+ _ ->
+ ct:fail(ErrorMsg)
+ end.
+
+jmsingle(Config) ->
+ %% Smoke test to check that the emulator starts with the +JMsingle
+ %% true/false option and fails with a non-boolean, that is, we
+ %% parse the command line correctly.
+ case os:type() of
+ {_, BSD} when BSD =:= netbsd;
+ BSD =:= openbsd ->
+ %% +JMsingle true might not work on these platforms, and dump core
+ %% because the emulator cannot be started.
+ %%
+ %% Set the cwd to a temporary directory that we'll delete when the
+ %% test is done.
+ {ok, Cwd} = file:get_cwd(),
+ TestDir = filename:join(proplists:get_value(priv_dir, Config),
+ "jmsingle"),
+ ok = file:make_dir(TestDir),
+ try
+ ok = file:set_cwd(TestDir),
+ run_jmsingle_test("true", internal_error,
+ "Emulator did not print the correct diagnostic "
+ "(crashed?) with +JMsingle true")
+ after
+ file:set_cwd(Cwd),
+ file:del_dir_r(TestDir)
+ end;
+ {_, _} ->
+ run_jmsingle_test("true", true,
+ "Emulator did not start with +JMsingle true")
+ end,
+ run_jmsingle_test("false", true,
+ "Emulator did not start with +JMsingle false"),
+ run_jmsingle_test("broken", false,
+ "Emulator started with bad +JMsingle parameter").
+
get_tmp_asm_files() ->
{ok, Cwd} = file:get_cwd(),
filelib:wildcard(filename:join(Cwd, "*.asm")).
diff --git a/erts/emulator/test/map_SUITE.erl b/erts/emulator/test/map_SUITE.erl
index 75dfb36cf6..5d7546c1a4 100644
--- a/erts/emulator/test/map_SUITE.erl
+++ b/erts/emulator/test/map_SUITE.erl
@@ -25,7 +25,7 @@
t_update_literals/1, t_update_literals_large/1,
t_match_and_update_literals/1, t_match_and_update_literals_large/1,
t_update_map_expressions/1,
- t_update_assoc/1, t_update_assoc_large/1,
+ t_update_assoc/1, t_update_assoc_large/1, t_update_assoc_sharing/1,
t_update_exact/1, t_update_exact_large/1,
t_guard_bifs/1,
t_guard_sequence/1, t_guard_sequence_large/1,
@@ -88,8 +88,11 @@
t_get_map_elements/1,
y_regs/1,
- %%Bugs
- t_large_unequal_bins_same_hash_bug/1]).
+ %% Bugs
+ t_large_unequal_bins_same_hash_bug/1,
+
+ %% Display
+ t_map_display/1]).
%% Benchmarks
-export([benchmarks/1]).
@@ -117,7 +120,7 @@ groups() ->
t_update_literals, t_update_literals_large,
t_match_and_update_literals, t_match_and_update_literals_large,
t_update_map_expressions,
- t_update_assoc, t_update_assoc_large,
+ t_update_assoc, t_update_assoc_large, t_update_assoc_sharing,
t_update_exact, t_update_exact_large,
t_guard_bifs,
t_guard_sequence, t_guard_sequence_large,
@@ -168,7 +171,10 @@ groups() ->
y_regs,
%% Bugs
- t_large_unequal_bins_same_hash_bug]},
+ t_large_unequal_bins_same_hash_bug,
+
+ %% Display
+ t_map_display]},
{benchmarks, [{repeat,10}], [benchmarks]}].
run_once() ->
@@ -786,6 +792,45 @@ t_map_get(Config) when is_list(Config) ->
(T) when map_get(x, T) =:= 1 -> ok;
(T) -> false = is_map(T)
end),
+
+ %% Test unions of maps with some other type.
+
+ M3 = id(M2),
+ if
+ is_map(M3) -> ok;
+ is_atom(M3) -> ok
+ end,
+ %% M3 is now known to be either a map or an atom
+ 1 = map_get(a, M3),
+
+ M4 = id(M3),
+ if
+ is_map(M4) -> ok;
+ is_atom(M4) -> ok
+ end,
+ %% M4 is now known to be either a map or an atom
+ if
+ map_get(a, M4) =:= 1 -> ok
+ end,
+
+ M5 = id(M4),
+ if
+ is_map(M5) -> ok;
+ is_tuple(M5) -> ok
+ end,
+ %% M5 is now known to be either a map or a tuple
+ 1 = map_get(a, M5),
+
+ M6 = id(M5),
+ if
+ is_map(M6) -> ok;
+ is_tuple(M6) -> ok
+ end,
+ %% M6 is now known to be either a map or a tuple
+ if
+ map_get(a, M6) =:= 1 -> ok
+ end,
+
ok.
t_is_map_key(Config) when is_list(Config) ->
@@ -808,6 +853,9 @@ t_is_map_key(Config) when is_list(Config) ->
true = is_map_key({1,1.0}, M1),
true = is_map_key(<<"k2">>, M1),
+ true = is_map_key(id(a), M1),
+ true = is_map_key(id("hello"), M1),
+
%% error cases
do_badmap(fun(T) ->
{'EXIT',{{badmap,T},[{erlang,is_map_key,_,_}|_]}} =
@@ -1116,6 +1164,51 @@ t_update_assoc_large(Config) when is_list(Config) ->
ok.
+t_update_assoc_sharing(Config) when is_list(Config) ->
+ Complex = id(#{nested=>map}),
+
+ case erlang:system_info(debug_compiled) of
+ true ->
+ %% the maximum size of a flatmap in a debug-compiled
+ %% system is three
+ M0 = id(#{1=>a,2=>b,complex=>Complex}),
+
+ %% all keys & values are the same
+ M1 = M0#{1=>a,complex=>Complex},
+ true = erts_debug:same(M1, M0),
+ M1 = M0,
+
+ %% only keys are the same
+ M2 = M0#{1=>new_value,complex=>Complex#{extra=>key}},
+ true = same_keys(M0, M2);
+ false ->
+ M0 = id(#{1=>a,2=>b,3.0=>c,4=>d,5=>e,complex=>Complex}),
+
+ %% all keys & values are the same
+ M1 = M0#{1=>a,complex=>Complex},
+ true = erts_debug:same(M1, M0),
+ M1 = M0,
+
+ %% only keys are the same
+ M2 = M0#{1=>new_value,complex=>Complex#{extra=>key}},
+ true = same_keys(M0, M2),
+
+ M3 = M0#{2=>new_value},
+ true = same_keys(M0, M3),
+ #{2:=new_value} = M3,
+
+ M4 = M0#{1=>1,2=>2,3.0=>3,4=>4,5=>5,complex=>6},
+ true = same_keys(M0, M4),
+ #{1:=1,2:=2,3.0:=3,4:=4,5:=5,complex:=6} = M4
+ end,
+
+ ok.
+
+same_keys(M0, M1) ->
+ Keys0 = erts_internal:map_to_tuple_keys(M0),
+ Keys1 = erts_internal:map_to_tuple_keys(M1),
+ erts_debug:same(Keys0, Keys1).
+
t_update_exact(Config) when is_list(Config) ->
M0 = id(#{1=>a,2=>b,3.0=>c,4=>d,5=>e}),
@@ -1657,6 +1750,7 @@ t_map_compare(Config) when is_list(Config) ->
repeat(100, fun(_) -> float_int_compare() end, []),
repeat(100, fun(_) -> recursive_compare() end, []),
repeat(10, fun(_) -> atoms_compare() end, []),
+ repeat(10, fun(_) -> atoms_plus_compare() end, []),
ok.
float_int_compare() ->
@@ -1684,6 +1778,31 @@ atoms_compare() ->
lists:seq(1,length(Atoms))),
ok.
+atoms_plus_compare() ->
+ Atoms = [true, false, ok, '', ?MODULE, list_to_atom(id("a new atom"))],
+ Pairs = lists:map(fun(K) -> list_to_tuple([{K,V} || V <- Atoms]) end,
+ Atoms),
+ Small = 17,
+ Big1 = 1 bsl 64,
+ Big2 = erts_debug:copy_shared(Big1),
+ Float1 = float(Big1),
+ Float2 = float(Big2),
+ Others = {Small, -Small, Big1, -Big1, Big2, Float1, Float2,
+ [], {}, #{}, self(), make_ref(),
+ ok, yes, no, lists, maps, seq},
+ RandOther = fun() -> element(rand:uniform(size(Others)), Others) end,
+
+ lists:foreach(
+ fun(Size) ->
+ M = map_gen(Pairs, Size),
+ M1 = M#{RandOther() => RandOther()},
+ M2 = M#{RandOther() => RandOther()},
+ %%io:format("Maps to compare:\nM1 = ~p\nM2 = ~p\n", [M1, M2]),
+ do_cmp(M1, M2)
+ end,
+ lists:seq(1,length(Atoms))),
+ ok.
+
numeric_keys(N) ->
lists:foldl(fun(_,Acc) ->
Int = rand:uniform(N*4) - N*2,
@@ -1783,8 +1902,8 @@ cmp(M1, M2, Exact) ->
cmp_maps(M1, M2, Exact) ->
case {maps:size(M1),maps:size(M2)} of
{S,S} ->
- {K1,V1} = lists:unzip(term_sort(maps:to_list(M1))),
- {K2,V2} = lists:unzip(term_sort(maps:to_list(M2))),
+ {K1,V1} = lists:unzip(cmp_key_sort(maps:to_list(M1))),
+ {K2,V2} = lists:unzip(cmp_key_sort(maps:to_list(M2))),
case cmp(K1, K2, true) of
0 -> cmp(V1, V2, Exact);
@@ -1808,6 +1927,10 @@ cmp_others(T1, T2, _) ->
{false,false} -> 1
end.
+cmp_key_sort(L) ->
+ lists:sort(fun(A,B) -> cmp(A,B,true) =< 0 end,
+ L).
+
map_gen(Pairs, Size) ->
{_,L} = lists:foldl(fun(_, {Keys, Acc}) ->
KI = rand:uniform(size(Keys)),
@@ -2024,12 +2147,7 @@ t_bif_map_merge(Config) when is_list(Config) ->
M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
4 => number, 18446744073709551629 => wat},
-
- #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>,
- 4 := number, 18446744073709551629 := wat} = maps:merge(#{}, M0),
-
- #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>,
- 4 := number, 18446744073709551629 := wat} = maps:merge(M0, #{}),
+ merge_with_empty(M0),
M1 = #{ "hi" => "hello again", float => 3.3, {1,2} => "tuple", 4 => integer },
@@ -2044,6 +2162,7 @@ t_bif_map_merge(Config) when is_list(Config) ->
Is = lists:seq(1,N),
M2 = maps:from_list([{I,I}||I<-Is]),
150000 = maps:size(M2),
+ merge_with_empty(M2),
M3 = maps:from_list([{<<I:32>>,I}||I<-Is]),
150000 = maps:size(M3),
M4 = maps:merge(M2,M3),
@@ -2082,6 +2201,30 @@ t_bif_map_merge(Config) when is_list(Config) ->
M11 = maps:merge(M9,M10),
ok = check_keys_exist(Ks1 ++ Ks2, M11),
+ %% Verify map and/or key tuple reuse
+
+ MS = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>},
+ merge_with_empty(MS),
+ MS_keys = erts_internal:map_to_tuple_keys(MS),
+
+ %% key tuple reuse
+ MS_a = maps:merge(MS, #{int => 4}),
+ true = erts_debug:same(erts_internal:map_to_tuple_keys(MS_a), MS_keys),
+ %% map reuse
+ MS_b = maps:merge(#{int => 4}, MS),
+ true = erts_debug:same(MS_b, MS),
+
+ %% mutated map reuse with literal key tuple
+ MS_c = maps:put(int, 4, maps:remove(int, MS)),
+ false = erts_debug:same(erts_internal:map_to_tuple_keys(MS_c), MS_keys),
+ MS_cc = maps:merge(MS, MS_c),
+ true = erts_debug:same(MS_cc, MS_c),
+ %% not only do we reuse MS_c, it has mutated to use the literal keys of MS
+ true = erts_debug:same(erts_internal:map_to_tuple_keys(MS_c), MS_keys),
+
+ MS_d = maps:merge(MS_c, MS),
+ true = erts_debug:same(MS_d, MS),
+
%% error case
do_badmap(fun(T) ->
{'EXIT',{{badmap,T},[{maps,merge,_,_}|_]}} =
@@ -2097,6 +2240,15 @@ t_bif_map_merge(Config) when is_list(Config) ->
end),
ok.
+merge_with_empty(M0) ->
+ M0_1 = maps:merge(#{}, M0),
+ M0 = M0_1,
+ true = erts_debug:same(M0, M0_1),
+
+ M0_2 = maps:merge(M0, #{}),
+ M0 = M0_2,
+ true = erts_debug:same(M0, M0_2),
+ ok.
t_bif_map_put(Config) when is_list(Config) ->
M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
@@ -2958,6 +3110,10 @@ t_erts_internal_order(_Config) when is_list(_Config) ->
0 = erts_internal:cmp_term(2147483648,2147483648),
1 = erts_internal:cmp_term(2147483648,0),
+ %% Make sure it's not the internal flatmap order
+ %% where low indexed 'true' < 'a'.
+ -1 = erts_internal:cmp_term(a,true),
+
M = #{0 => 0,2147483648 => 0},
true = M =:= binary_to_term(term_to_binary(M)),
true = M =:= binary_to_term(term_to_binary(M, [deterministic])),
@@ -3120,7 +3276,7 @@ t_dets(_Config) ->
t_tracing(_Config) ->
- dbg:stop_clear(),
+ dbg:stop(),
{ok,Tracer} = dbg:tracer(process,{fun trace_collector/2, self()}),
dbg:p(self(),c),
@@ -3173,7 +3329,7 @@ t_tracing(_Config) ->
%% Check to extra messages
timeout = getmsg(Tracer),
- dbg:stop_clear(),
+ dbg:stop(),
ok.
getmsg(_Tracer) ->
@@ -3539,6 +3695,8 @@ t_large_unequal_bins_same_hash_bug(Config) when is_list(Config) ->
io:format("~p ~p~n", [erlang:phash2(Map3), maps:size(Map3)])
end).
+
+
make_map(0) ->
#{};
make_map(Size) ->
@@ -3583,6 +3741,64 @@ total_memory() ->
undefined
end.
+%% This test case checks that maps larger than 32 elements are readable
+%% when displayed.
+t_map_display(Config) when is_list(Config) ->
+ verify_map_term(make_nontrivial_map(33)),
+ verify_map_term(make_nontrivial_map(253)),
+ verify_map_term({a, make_nontrivial_map(77)}),
+ verify_map_term([make_nontrivial_map(42),
+ {a,make_nontrivial_map(99)},
+ make_nontrivial_map(77)]),
+
+ ok.
+
+make_nontrivial_map(N) ->
+ make_nontrivial_map(N, 32).
+
+make_nontrivial_map(N, Effort) ->
+ L = [begin
+ Key = case I rem 64 of
+ 33 when Effort > 16 ->
+ make_nontrivial_map(I, Effort div 2);
+ _ ->
+ I
+ end,
+ Value = case I rem 5 of
+ 0 ->
+ I * I;
+ 1 ->
+ if
+ Effort > 16 ->
+ make_nontrivial_map(33, Effort div 2);
+ true ->
+ make_map(Effort)
+ end;
+ 2 ->
+ lists:seq(0, I rem 16);
+ 3 ->
+ list_to_tuple(lists:seq(0, I rem 16));
+ 4 ->
+ float(I)
+ end,
+ {Key, Value}
+ end || I <- lists:seq(1, N)],
+ maps:from_list(L).
+
+verify_map_term(Term) ->
+ Printed = string:chomp(erts_debug:display(Term)),
+ {ok,Tokens,1} = erl_scan:string(Printed ++ "."),
+ {ok,ParsedTerm} = erl_parse:parse_term(Tokens),
+
+ case ParsedTerm of
+ Term ->
+ ok;
+ Other ->
+ io:format("Expected:\n~p\n", [Term]),
+ io:format("Got:\n~p", [Other]),
+ ct:fail(failed)
+ end.
+
%%%
%%% Benchmarks
%%%
diff --git a/erts/emulator/test/match_spec_SUITE.erl b/erts/emulator/test/match_spec_SUITE.erl
index eadddfe312..43e475b125 100644
--- a/erts/emulator/test/match_spec_SUITE.erl
+++ b/erts/emulator/test/match_spec_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,17 +23,18 @@
-export([all/0, suite/0]).
-export([init_per_testcase/2, end_per_testcase/2]).
-export([test_1/1, test_2/1, test_3/1, test_4a/1, test_4b/1, test_5a/1,
- test_5b/1, caller_and_return_to/1, bad_match_spec_bin/1,
+ test_5b/1, test_6/1, caller_and_return_to/1, bad_match_spec_bin/1,
trace_control_word/1, silent/1, silent_no_ms/1, silent_test/1,
ms_trace2/1, ms_trace3/1, ms_trace_dead/1, boxed_and_small/1,
destructive_in_test_bif/1, guard_exceptions/1,
empty_list/1,
- unary_plus/1, unary_minus/1, moving_labels/1]).
+ unary_plus/1, unary_minus/1, moving_labels/1,
+ guard_bifs/1]).
-export([fpe/1]).
-export([otp_9422/1]).
-export([faulty_seq_trace/1, do_faulty_seq_trace/0]).
-export([maps/1]).
--export([runner/2, loop_runner/3]).
+-export([runner/2, loop_runner/3, fixed_runner/2]).
-export([f1/1, f2/2, f3/2, fn/1, fn/2, fn/3]).
-export([do_boxed_and_small/0]).
-export([f1_test4/1, f2_test4/2, f3_test4/2]).
@@ -48,7 +49,8 @@ suite() ->
{timetrap, {minutes, 1}}].
all() ->
- [test_1, test_2, test_3, test_4a, test_4b, test_5a, test_5b, caller_and_return_to, bad_match_spec_bin,
+ [test_1, test_2, test_3, test_4a, test_4b, test_5a, test_5b, test_6,
+ caller_and_return_to, bad_match_spec_bin,
trace_control_word, silent, silent_no_ms, silent_test, ms_trace2,
ms_trace3, ms_trace_dead, boxed_and_small, destructive_in_test_bif,
guard_exceptions, unary_plus, unary_minus, fpe,
@@ -56,7 +58,8 @@ all() ->
faulty_seq_trace,
empty_list,
otp_9422,
- maps].
+ maps,
+ guard_bifs].
init_per_testcase(_TestCase, Config) ->
Config.
@@ -239,6 +242,86 @@ test_5b(Config) when is_list(Config) ->
]),
ok.
+%% Test current_stacktrace/[0,1]
+test_6(Config) when is_list(Config) ->
+ %% Test non small argument
+ case catch erlang:trace_pattern({?MODULE, f2_test6, '_'},
+ [{'_', [], [{message, {current_stacktrace, a}}]}]) of
+ {'EXIT', {badarg, _}} -> ok;
+ Other1 -> ct:fail({noerror, Other1})
+ end,
+
+ %% Test negative
+ case catch erlang:trace_pattern({?MODULE, f2_test6, '_'},
+ [{'_', [], [{message, {current_stacktrace, -1}}]}]) of
+ {'EXIT', {badarg, _}} -> ok;
+ Other2 -> ct:fail({noerror, Other2})
+ end,
+
+ Fun = fun() -> f5_test6() end,
+ Pat = [{'_', [], [{message, {current_stacktrace}}]}],
+ P = spawn(?MODULE, fixed_runner, [self(), Fun]),
+ erlang:trace(P, true, [call]),
+ erlang:trace_pattern({?MODULE, f2_test6, 1}, Pat, [local]),
+ erlang:trace_pattern({?MODULE, f1_test6, 0}, Pat, [local]),
+ collect(P, [{trace, P, call, {?MODULE, f2_test6, [f1]},
+ [
+ {?MODULE, f3_test6, 0, [{file, "test6.erl"}, {line, 21}]},
+ {?MODULE, f5_test6, 0, [{file, "test6.erl"}, {line, 14}]},
+ {?MODULE, fixed_runner, 2, [{file, "test6.erl"}, {line, 7}]}
+ ]},
+ {trace, P, call, {?MODULE, f1_test6, []},
+ [
+ {?MODULE, f2_test6, 1, [{file, "test6.erl"}, {line, 25}]},
+ {?MODULE, f3_test6, 0, [{file, "test6.erl"}, {line, 21}]},
+ {?MODULE, f5_test6, 0, [{file, "test6.erl"}, {line, 14}]},
+ {?MODULE, fixed_runner, 2, [{file, "test6.erl"}, {line, 7}]}
+ ]}
+ ]),
+
+ Pat2 = [{'_', [], [{message, {current_stacktrace, 3}}]}],
+ P2 = spawn(?MODULE, fixed_runner, [self(), Fun]),
+ erlang:trace(P2, true, [call]),
+ erlang:trace_pattern({?MODULE, f2_test6, 1}, Pat2, [local]),
+ erlang:trace_pattern({?MODULE, f1_test6, 0}, Pat2, [local]),
+ collect(P2, [{trace, P2, call, {?MODULE, f2_test6, [f1]},
+ [
+ {?MODULE, f3_test6, 0, [{file, "test6.erl"}, {line, 21}]},
+ {?MODULE, f5_test6, 0, [{file, "test6.erl"}, {line, 14}]},
+ {?MODULE, fixed_runner, 2, [{file, "test6.erl"}, {line, 7}]}
+ ]},
+ {trace, P2, call, {?MODULE, f1_test6, []},
+ [
+ {?MODULE, f2_test6, 1, [{file, "test6.erl"}, {line, 25}]},
+ {?MODULE, f3_test6, 0, [{file, "test6.erl"}, {line, 21}]},
+ {?MODULE, f5_test6, 0, [{file, "test6.erl"}, {line, 14}]}
+ ]}
+ ]),
+
+ %% Test when erts_backtrace_depth is less than depth
+ OldDepth = erlang:system_flag(backtrace_depth, 2),
+ try
+ P3 = spawn(?MODULE, fixed_runner, [self(), Fun]),
+ erlang:trace(P3, true, [call]),
+ erlang:trace_pattern({?MODULE, f2_test6, 1}, Pat2, [local]),
+ erlang:trace_pattern({?MODULE, f1_test6, 0}, Pat2, [local]),
+ collect(P3, [{trace, P3, call, {?MODULE, f2_test6, [f1]},
+ [
+ {?MODULE, f3_test6, 0, [{file, "test6.erl"}, {line, 21}]},
+ {?MODULE, f5_test6, 0, [{file, "test6.erl"}, {line, 14}]}
+ ]},
+ {trace, P3, call, {?MODULE, f1_test6, []},
+ [
+ {?MODULE, f2_test6, 1, [{file, "test6.erl"}, {line, 25}]},
+ {?MODULE, f3_test6, 0, [{file, "test6.erl"}, {line, 21}]}
+ ]}
+ ])
+ after
+ erlang:system_flag(backtrace_depth, OldDepth)
+ end,
+
+ ok.
+
%% Test that caller and return to work as they should
%% There was a bug where caller would be undefined when return_to was set
%% for r the bif erlang:put().
@@ -1064,7 +1147,36 @@ moving_labels(Config) when is_list(Config) ->
erlang:match_spec_test({true,false}, Ms2, table),
ok.
-
+
+-record(dummy_record, {}).
+
+%% GH-7045: Some guard BIFs were unavailable in match specifications.
+guard_bifs(_Config) ->
+ Matches =
+ [begin
+ BIF = list_to_tuple([F | lists:duplicate(A, '$1')]),
+ erlang:match_spec_test(Data, [{{'$1'}, [BIF], [{{'$1'}}]}], Kind)
+ end
+ || {F, A} <- erlang:module_info(functions),
+ {Data, Kind} <- [{{a}, table}, {[a], trace}],
+ F =/= is_record, %% Has special requirements, checked below.
+ erl_internal:arith_op(F, A) orelse
+ erl_internal:bool_op(F, A) orelse
+ erl_internal:comp_op(F, A) orelse
+ erl_internal:guard_bif(F, A)],
+ [] = [T || {error, _}=T <- Matches],
+
+ IsRecord = {is_record,
+ '$1',
+ dummy_record,
+ record_info(size, dummy_record)},
+ [{ok, _, [], []} =
+ erlang:match_spec_test(Data, [{{'$1'}, [IsRecord], [{{'$1'}}]}], Kind)
+ || {Data, Kind} <- [{{#dummy_record{}}, table},
+ {[#dummy_record{}], trace}]],
+
+ ok.
+
tr(Fun, MFA, Pat, Expected) ->
tr(Fun, MFA, [call], Pat, [global], Expected).
@@ -1240,3 +1352,33 @@ f2_test5(X, _) ->
f1_test5(X) ->
{X}.
+
+-file("test6.erl", 1).
+fixed_runner(Collector, Fun) ->
+ receive
+ {go, Collector} ->
+ go
+ end,
+ Fun(), % Line 7 - This line number should remain stable
+ receive
+ {done, Collector} ->
+ Collector ! {gone, self()}
+ end.
+
+f5_test6() ->
+ f3_test6(), % Line 14 - This line number should remain stable
+ f4_test6().
+
+f4_test6() ->
+ f4.
+
+f3_test6() ->
+ f2_test6(f1), % Line 21 - This line number should remain stable
+ f3.
+
+f2_test6(X) ->
+ X = f1_test6(), % Line 25 - This line number should remain stable
+ f2.
+
+f1_test6() ->
+ f1.
diff --git a/erts/emulator/test/monitor_SUITE.erl b/erts/emulator/test/monitor_SUITE.erl
index e17f8db920..29401c0bf3 100644
--- a/erts/emulator/test/monitor_SUITE.erl
+++ b/erts/emulator/test/monitor_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1148,7 +1148,7 @@ monitor_3_noproc_gh6185_test(AliasTest, TagTest) ->
end,
%% Process of old incarnation...
- Pid = erts_test_utils:mk_ext_pid({NodeName, OldCreation}, 4711, 17),
+ Pid = erts_test_utils:mk_ext_pid({NodeName, OldCreation}, 4711, 0),
{Tag5, TagOpt5} = TagFun(),
M5 = erlang:monitor(process, Pid, AliasOpt ++ TagOpt5),
receive
@@ -1238,7 +1238,7 @@ monitor_3_noproc_gh6185_exit_test(AliasTest, TagTest) ->
{P5, M5} = spawn_monitor(fun () ->
Pid = erts_test_utils:mk_ext_pid({NodeName,
OldCreation},
- 4711, 17),
+ 4711, 0),
erlang:yield(),
_ = erlang:monitor(process, Pid, AliasOpt ++ TagOpt),
exit(bang)
diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl
index 2f28b4a0e9..359646ca5c 100644
--- a/erts/emulator/test/nif_SUITE.erl
+++ b/erts/emulator/test/nif_SUITE.erl
@@ -46,7 +46,10 @@
monitor_process_purge/1,
demonitor_process/1,
monitor_frenzy/1,
- types/1, many_args/1, binaries/1, get_string/1, get_atom/1,
+ types/1, many_args/1, binaries/1,
+ get_string/1, get_string_length/1,
+ get_atom/1, get_atom_length/1,
+ make_new_atoms/1, make_existing_atoms/1,
maps/1,
api_macros/1,
from_array/1, iolist_as_binary/1, resource/1, resource_binary/1,
@@ -93,12 +96,15 @@
many_args_100/100,
clone_bin/1,
make_sub_bin/3,
- string_to_bin/2,
- atom_to_bin/2,
+ string_to_bin/3,
+ string_length/2,
+ atom_to_bin/3,
+ atom_length/2,
macros/1,
tuple_2_list_and_tuple/1,
iolist_2_bin/1,
get_resource_type/1,
+ init_resource_type/2,
alloc_resource/2,
make_resource/1,
get_resource/2,
@@ -110,6 +116,8 @@
check_is_exception/0,
length_test/6,
make_atoms/0,
+ make_new_atom/2,
+ make_existing_atom/2,
make_strings/0,
make_new_resource_binary/1,
send_list_seq/2,
@@ -196,6 +204,9 @@
-define(RT_CREATE,1).
-define(RT_TAKEOVER,2).
+-define(ERL_NIF_LATIN1,1).
+-define(ERL_NIF_UTF8,2).
+
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
@@ -212,7 +223,9 @@ all() ->
t_load_race,
t_call_nif_early,
load_traced_nif,
- binaries, get_string, get_atom, maps, api_macros, from_array,
+ binaries, get_string, get_string_length,
+ get_atom, get_atom_length, make_new_atoms, make_existing_atoms,
+ maps, api_macros, from_array,
iolist_as_binary, resource, resource_binary,
threading, send, send2, send3,
send_threaded, neg, is_checks, get_length, make_atom,
@@ -646,14 +659,14 @@ t_nifs_attrib(Config) when is_list(Config) ->
ok.
-%% Test erlang:load_nif/2 waiting for code_write_permission.
+%% Test erlang:load_nif/2 waiting for code_mod_permission.
t_load_race(Config) ->
Data = proplists:get_value(data_dir, Config),
File = filename:join(Data, "nif_mod"),
{ok,nif_mod,Bin} = compile:file(File, [binary,return_errors]),
{module,nif_mod} = erlang:load_module(nif_mod,Bin),
try
- erts_debug:set_internal_state(code_write_permission, true),
+ erts_debug:set_internal_state(code_mod_permission, true),
Papa = self(),
spawn_link(fun() ->
ok = nif_mod:load_nif_lib(Config, 1),
@@ -662,7 +675,7 @@ t_load_race(Config) ->
timer:sleep(100),
timeout = receive_any(0)
after
- true = erts_debug:set_internal_state(code_write_permission, false)
+ true = erts_debug:set_internal_state(code_mod_permission, false)
end,
"NIF loaded" = receive_any(),
@@ -1621,27 +1634,304 @@ test_make_sub_bin(Bin) ->
%% Test enif_get_string
get_string(Config) when is_list(Config) ->
ensure_lib_loaded(Config, 1),
- {7, <<"hejsan",0,_:3/binary>>} = string_to_bin("hejsan",10),
- {7, <<"hejsan",0,_>>} = string_to_bin("hejsan",8),
- {7, <<"hejsan",0>>} = string_to_bin("hejsan",7),
- {-6, <<"hejsa",0>>} = string_to_bin("hejsan",6),
- {-5, <<"hejs",0>>} = string_to_bin("hejsan",5),
- {-1, <<0>>} = string_to_bin("hejsan",1),
- {0, <<>>} = string_to_bin("hejsan",0),
- {1, <<0>>} = string_to_bin("",1),
- {0, <<>>} = string_to_bin("",0),
+ {7, <<"hejsan", 0, _:3/binary>>} = string_to_bin("hejsan", 10, ?ERL_NIF_LATIN1),
+ {7, <<"hejsan", 0, _>>} = string_to_bin("hejsan", 8, ?ERL_NIF_LATIN1),
+ {7, <<"hejsan", 0>>} = string_to_bin("hejsan", 7, ?ERL_NIF_LATIN1),
+ {-6, <<"hejsa", 0>>} = string_to_bin("hejsan", 6, ?ERL_NIF_LATIN1),
+ {-5, <<"hejs", 0>>} = string_to_bin("hejsan", 5, ?ERL_NIF_LATIN1),
+ {-1, <<0>>} = string_to_bin("hejsan", 1, ?ERL_NIF_LATIN1),
+ {0, <<>>} = string_to_bin("hejsan", 0, ?ERL_NIF_LATIN1),
+ {1, <<0>>} = string_to_bin("", 1, ?ERL_NIF_LATIN1),
+ {0, <<>>} = string_to_bin("", 0, ?ERL_NIF_LATIN1),
+ {6, <<"hallå", 0, _, _>>} = string_to_bin("hallå", 8, ?ERL_NIF_LATIN1),
+ {6, <<"hallå", 0, _>>} = string_to_bin("hallå", 7, ?ERL_NIF_LATIN1),
+ {6, <<"hallå", 0>>} = string_to_bin("hallå", 6, ?ERL_NIF_LATIN1),
+ {-5, <<"hall", 0>>} = string_to_bin("hallå", 5, ?ERL_NIF_LATIN1),
+ {-4, <<"hal", 0>>} = string_to_bin("hallå", 4, ?ERL_NIF_LATIN1),
+ {0, <<0, _, _>>} = string_to_bin("Ω", 3, ?ERL_NIF_LATIN1),
+ {0, <<0, _>>} = string_to_bin("Ω", 2, ?ERL_NIF_LATIN1),
+ {0, <<0>>} = string_to_bin("Ω", 1, ?ERL_NIF_LATIN1),
+ {0, <<>>} = string_to_bin("Ω", 0, ?ERL_NIF_LATIN1),
+ {7, <<"hejsan", 0, _:3/binary>>} = string_to_bin("hejsan", 10, ?ERL_NIF_UTF8),
+ {7, <<"hejsan", 0, _>>} = string_to_bin("hejsan", 8, ?ERL_NIF_UTF8),
+ {7, <<"hejsan", 0>>} = string_to_bin("hejsan", 7, ?ERL_NIF_UTF8),
+ {-6, <<"hejsa", 0>>} = string_to_bin("hejsan", 6, ?ERL_NIF_UTF8),
+ {-5, <<"hejs", 0>>} = string_to_bin("hejsan", 5, ?ERL_NIF_UTF8),
+ {-1, <<0>>} = string_to_bin("hejsan", 1, ?ERL_NIF_UTF8),
+ {0, <<>>} = string_to_bin("hejsan", 0, ?ERL_NIF_UTF8),
+ {1, <<0>>} = string_to_bin("", 1, ?ERL_NIF_UTF8),
+ {0, <<>>} = string_to_bin("", 0, ?ERL_NIF_UTF8),
+ {7, <<"hallå"/utf8, 0, _>>} = string_to_bin("hallå", 8, ?ERL_NIF_UTF8),
+ {7, <<"hallå"/utf8, 0>>} = string_to_bin("hallå", 7, ?ERL_NIF_UTF8),
+ {-5, <<"hall", 0, _>>} = string_to_bin("hallå", 6, ?ERL_NIF_UTF8),
+ {-5, <<"hall", 0>>} = string_to_bin("hallå", 5, ?ERL_NIF_UTF8),
+ {-4, <<"hal", 0>>} = string_to_bin("hallå", 4, ?ERL_NIF_UTF8),
+ {3, <<"Ω"/utf8, 0>>} = string_to_bin("Ω", 3, ?ERL_NIF_UTF8),
+ {-1, <<0, _>>} = string_to_bin("Ω", 2, ?ERL_NIF_UTF8),
+ {-1, <<0>>} = string_to_bin("Ω", 1, ?ERL_NIF_UTF8),
+ {0, <<>>} = string_to_bin("Ω", 0, ?ERL_NIF_UTF8),
+
+ {0, <<_:5/binary>>} = string_to_bin([-10], 5, ?ERL_NIF_LATIN1),
+ {0, <<_:5/binary>>} = string_to_bin([-10], 5, ?ERL_NIF_UTF8),
+ {0, <<_:5/binary>>} = string_to_bin([(-1 bsl 8) + $A], 5, ?ERL_NIF_LATIN1),
+ {0, <<_:5/binary>>} = string_to_bin([(-1 bsl 8) + $A], 5, ?ERL_NIF_UTF8),
+ ok.
+
+%% Test enif_get_string_length
+get_string_length(Config) when is_list(Config) ->
+ ensure_lib_loaded(Config, 1),
+ 0 = string_length("", ?ERL_NIF_LATIN1),
+ 6 = string_length("hejsan", ?ERL_NIF_LATIN1),
+ 5 = string_length("hallå", ?ERL_NIF_LATIN1),
+ false = string_length("Ω", ?ERL_NIF_LATIN1),
+ false = string_length("hejsanΩ", ?ERL_NIF_LATIN1),
+ 0 = string_length("", ?ERL_NIF_UTF8),
+ 6 = string_length("hejsan", ?ERL_NIF_UTF8),
+ 6 = string_length("hallå", ?ERL_NIF_UTF8),
+ 2 = string_length("Ω", ?ERL_NIF_UTF8),
+ 8 = string_length("hejsanΩ", ?ERL_NIF_UTF8),
+
+ false = string_length([-10], ?ERL_NIF_LATIN1),
+ false = string_length([-10], ?ERL_NIF_UTF8),
+ false = string_length([(-1 bsl 8) + $A], ?ERL_NIF_LATIN1),
+ false = string_length([(-1 bsl 8) + $A], ?ERL_NIF_UTF8),
ok.
%% Test enif_get_atom
get_atom(Config) when is_list(Config) ->
ensure_lib_loaded(Config, 1),
- {7, <<"hejsan",0,_:3/binary>>} = atom_to_bin(hejsan,10),
- {7, <<"hejsan",0,_>>} = atom_to_bin(hejsan,8),
- {7, <<"hejsan",0>>} = atom_to_bin(hejsan,7),
- {0, <<_:6/binary>>} = atom_to_bin(hejsan,6),
- {0, <<>>} = atom_to_bin(hejsan,0),
- {1, <<0>>} = atom_to_bin('',1),
- {0, <<>>} = atom_to_bin('',0),
+ Char1ByteLatin1 = <<"a">>,
+ Longest1ByteLatin1AtomText = binary:copy(Char1ByteLatin1, 255),
+ Longest1ByteLatin1Atom = erlang:binary_to_atom(Longest1ByteLatin1AtomText, latin1),
+ Char2ByteLatin1 = <<"Ã¥">>,
+ Longest2ByteLatin1AtomText = binary:copy(Char2ByteLatin1, 255),
+ Longest2ByteLatin1Atom = erlang:binary_to_atom(Longest2ByteLatin1AtomText, latin1),
+ Char2ByteUtf8 = <<"Ã¥"/utf8>>,
+ Longest2ByteUtf8AtomText = binary:copy(Char2ByteUtf8, 255),
+ Longest2ByteUtf8Atom = erlang:binary_to_atom(Longest2ByteUtf8AtomText, utf8),
+ Char3ByteUtf8 = <<"ᛥ"/utf8>>,
+ Longest3ByteUtf8AtomText = binary:copy(Char3ByteUtf8, 255),
+ Longest3ByteUtf8Atom = erlang:binary_to_atom(Longest3ByteUtf8AtomText, utf8),
+ Char4ByteUtf8 = <<"𠜱"/utf8>>,
+ Longest4ByteUtf8AtomText = binary:copy(Char4ByteUtf8, 255),
+ Longest4ByteUtf8Atom = erlang:binary_to_atom(Longest4ByteUtf8AtomText, utf8),
+ {7, <<"hejsan", 0, _:3/binary>>} = atom_to_bin(hejsan, 10, ?ERL_NIF_LATIN1),
+ {7, <<"hejsan", 0, _>>} = atom_to_bin(hejsan, 8, ?ERL_NIF_LATIN1),
+ {7, <<"hejsan", 0>>} = atom_to_bin(hejsan, 7, ?ERL_NIF_LATIN1),
+ {0, <<_:6/binary>>} = atom_to_bin(hejsan, 6, ?ERL_NIF_LATIN1),
+ {0, <<>>} = atom_to_bin(hejsan, 0, ?ERL_NIF_LATIN1),
+ {1, <<0>>} = atom_to_bin('', 1, ?ERL_NIF_LATIN1),
+ {0, <<>>} = atom_to_bin('', 0, ?ERL_NIF_LATIN1),
+ {6, <<"hallå", 0, _>>} = atom_to_bin('hallå', 7, ?ERL_NIF_LATIN1),
+ {6, <<"hallå", 0>>} = atom_to_bin('hallå', 6, ?ERL_NIF_LATIN1),
+ {0, <<_:5/binary>>} = atom_to_bin('hallå', 5, ?ERL_NIF_LATIN1),
+ {0, <<_:3/binary>>} = atom_to_bin('Ω', 3, ?ERL_NIF_LATIN1),
+ {0, <<_:2/binary>>} = atom_to_bin('Ω', 2, ?ERL_NIF_LATIN1),
+ {0, <<_>>} = atom_to_bin('Ω', 1, ?ERL_NIF_LATIN1),
+ {256, <<Longest1ByteLatin1AtomText:255/bytes, 0>>} = atom_to_bin(Longest1ByteLatin1Atom, 256, ?ERL_NIF_LATIN1),
+ {256, <<Longest2ByteLatin1AtomText:255/bytes, 0>>} = atom_to_bin(Longest2ByteLatin1Atom, 256, ?ERL_NIF_LATIN1),
+ {256, <<Longest2ByteLatin1AtomText:255/bytes, 0, _:255/bytes>>} = atom_to_bin(Longest2ByteLatin1Atom, 511, ?ERL_NIF_LATIN1),
+ {256, <<Longest2ByteLatin1AtomText:255/bytes, 0>>} = atom_to_bin(Longest2ByteUtf8Atom, 256, ?ERL_NIF_LATIN1),
+ {256, <<Longest2ByteLatin1AtomText:255/bytes, 0, _:255/bytes>>} = atom_to_bin(Longest2ByteUtf8Atom, 511, ?ERL_NIF_LATIN1),
+ {0, <<_:256/bytes>>} = atom_to_bin(Longest3ByteUtf8Atom, 256, ?ERL_NIF_LATIN1),
+ {0, <<_:766/bytes>>} = atom_to_bin(Longest3ByteUtf8Atom, 766, ?ERL_NIF_LATIN1),
+ {0, <<_:256/bytes>>} = atom_to_bin(Longest4ByteUtf8Atom, 256, ?ERL_NIF_LATIN1),
+ {0, <<_:1021/bytes>>} = atom_to_bin(Longest4ByteUtf8Atom, 1021, ?ERL_NIF_LATIN1),
+ {7, <<"hejsan", 0, _:3/binary>>} = atom_to_bin(hejsan, 10, ?ERL_NIF_UTF8),
+ {7, <<"hejsan", 0, _>>} = atom_to_bin(hejsan, 8, ?ERL_NIF_UTF8),
+ {7, <<"hejsan", 0>>} = atom_to_bin(hejsan, 7, ?ERL_NIF_UTF8),
+ {0, <<_:6/binary>>} = atom_to_bin(hejsan, 6, ?ERL_NIF_UTF8),
+ {0, <<>>} = atom_to_bin(hejsan, 0, ?ERL_NIF_UTF8),
+ {1, <<0>>} = atom_to_bin('', 1, ?ERL_NIF_UTF8),
+ {0, <<>>} = atom_to_bin('', 0, ?ERL_NIF_UTF8),
+ {7, <<"hallå"/utf8, 0>>} = atom_to_bin('hallå', 7, ?ERL_NIF_UTF8),
+ {0, <<_:6/binary>>} = atom_to_bin('hallå', 6, ?ERL_NIF_UTF8),
+ {0, <<_:5/binary>>} = atom_to_bin('hallå', 5, ?ERL_NIF_UTF8),
+ {3, <<"Ω"/utf8, 0>>} = atom_to_bin('Ω', 3, ?ERL_NIF_UTF8),
+ {0, <<_:2/binary>>} = atom_to_bin('Ω', 2, ?ERL_NIF_UTF8),
+ {0, <<_>>} = atom_to_bin('Ω', 1, ?ERL_NIF_UTF8),
+ {256, <<Longest1ByteLatin1AtomText:255/bytes, 0>>} = atom_to_bin(Longest1ByteLatin1Atom, 256, ?ERL_NIF_UTF8),
+ {0, <<_:256/bytes>>} = atom_to_bin(Longest2ByteLatin1Atom, 256, ?ERL_NIF_UTF8),
+ {511, <<Longest2ByteUtf8AtomText:510/bytes, 0>>} = atom_to_bin(Longest2ByteLatin1Atom, 511, ?ERL_NIF_UTF8),
+ {0, <<_:256/bytes>>} = atom_to_bin(Longest2ByteUtf8Atom, 256, ?ERL_NIF_UTF8),
+ {511, <<Longest2ByteUtf8AtomText:510/bytes, 0>>} = atom_to_bin(Longest2ByteUtf8Atom, 511, ?ERL_NIF_UTF8),
+ {0, <<_:256/bytes>>} = atom_to_bin(Longest3ByteUtf8Atom, 256, ?ERL_NIF_UTF8),
+ {766, <<Longest3ByteUtf8AtomText:765/bytes, 0>>} = atom_to_bin(Longest3ByteUtf8Atom, 766, ?ERL_NIF_UTF8),
+ {0, <<_:256/bytes>>} = atom_to_bin(Longest4ByteUtf8Atom, 256, ?ERL_NIF_UTF8),
+ {1021, <<Longest4ByteUtf8AtomText:1020/bytes, 0>>} = atom_to_bin(Longest4ByteUtf8Atom, 1021, ?ERL_NIF_UTF8),
+ ok.
+
+%% Test enif_get_atom_length
+get_atom_length(Config) when is_list(Config) ->
+ ensure_lib_loaded(Config, 1),
+ Char1ByteLatin1 = <<"a">>,
+ Longest1ByteLatin1AtomText = binary:copy(Char1ByteLatin1, 255),
+ Longest1ByteLatin1Atom = erlang:binary_to_atom(Longest1ByteLatin1AtomText, latin1),
+ Char2ByteLatin1 = <<"Ã¥">>,
+ Longest2ByteLatin1AtomText = binary:copy(Char2ByteLatin1, 255),
+ Longest2ByteLatin1Atom = erlang:binary_to_atom(Longest2ByteLatin1AtomText, latin1),
+ Char2ByteUtf8 = <<"Ã¥"/utf8>>,
+ Longest2ByteUtf8AtomText = binary:copy(Char2ByteUtf8, 255),
+ Longest2ByteUtf8Atom = erlang:binary_to_atom(Longest2ByteUtf8AtomText, utf8),
+ Char3ByteUtf8 = <<"ᛥ"/utf8>>,
+ Longest3ByteUtf8AtomText = binary:copy(Char3ByteUtf8, 255),
+ Longest3ByteUtf8Atom = erlang:binary_to_atom(Longest3ByteUtf8AtomText, utf8),
+ Char4ByteUtf8 = <<"𠜱"/utf8>>,
+ Longest4ByteUtf8AtomText = binary:copy(Char4ByteUtf8, 255),
+ Longest4ByteUtf8Atom = erlang:binary_to_atom(Longest4ByteUtf8AtomText, utf8),
+ 0 = atom_length('', ?ERL_NIF_LATIN1),
+ 6 = atom_length('hejsan', ?ERL_NIF_LATIN1),
+ 5 = atom_length('hallå', ?ERL_NIF_LATIN1),
+ false = atom_length('Ω', ?ERL_NIF_LATIN1),
+ false = atom_length('hejsanΩ', ?ERL_NIF_LATIN1),
+ 255 = atom_length(Longest1ByteLatin1Atom, ?ERL_NIF_LATIN1),
+ 255 = atom_length(Longest2ByteLatin1Atom, ?ERL_NIF_LATIN1),
+ 255 = atom_length(Longest2ByteUtf8Atom, ?ERL_NIF_LATIN1),
+ false = atom_length(Longest3ByteUtf8Atom, ?ERL_NIF_LATIN1),
+ false = atom_length(Longest4ByteUtf8Atom, ?ERL_NIF_LATIN1),
+ 0 = atom_length('', ?ERL_NIF_UTF8),
+ 6 = atom_length('hejsan', ?ERL_NIF_UTF8),
+ 6 = atom_length('hallå', ?ERL_NIF_UTF8),
+ 2 = atom_length('Ω', ?ERL_NIF_UTF8),
+ 8 = atom_length('hejsanΩ', ?ERL_NIF_UTF8),
+ 255 = atom_length(Longest1ByteLatin1Atom, ?ERL_NIF_UTF8),
+ 510 = atom_length(Longest2ByteLatin1Atom, ?ERL_NIF_UTF8),
+ 510 = atom_length(Longest2ByteUtf8Atom, ?ERL_NIF_UTF8),
+ 765 = atom_length(Longest3ByteUtf8Atom, ?ERL_NIF_UTF8),
+ 1020 = atom_length(Longest4ByteUtf8Atom, ?ERL_NIF_UTF8),
+ ok.
+
+%% Test enif_make_new_atom_len
+make_new_atoms(Config) when is_list(Config) ->
+ ensure_lib_loaded(Config, 1),
+ Char1ByteAscii = <<"a">>,
+ Longest1ByteAsciiAtomText = binary:copy(Char1ByteAscii, 255),
+ TooLong1ByteAsciiAtomText = binary:copy(Char1ByteAscii, 256),
+ Longest1ByteLatin1Atom = erlang:binary_to_atom(Longest1ByteAsciiAtomText, latin1),
+ Char2ByteLatin1 = <<"Ã¥">>,
+ Longest2ByteLatin1AtomText = binary:copy(Char2ByteLatin1, 255),
+ TooLong2ByteLatin1AtomText = binary:copy(Char2ByteLatin1, 256),
+ Longest2ByteLatin1Atom = erlang:binary_to_atom(Longest2ByteLatin1AtomText, latin1),
+ Char2ByteUtf8 = <<"Ã¥"/utf8>>,
+ Longest2ByteUtf8AtomText = binary:copy(Char2ByteUtf8, 255),
+ TooLong2ByteUtf8AtomText = binary:copy(Char2ByteUtf8, 256),
+ Longest2ByteUtf8Atom = erlang:binary_to_atom(Longest2ByteUtf8AtomText, utf8),
+ Char3ByteUtf8 = <<"ᛥ"/utf8>>,
+ Longest3ByteUtf8AtomText = binary:copy(Char3ByteUtf8, 255),
+ TooLong3ByteUtf8AtomText = binary:copy(Char3ByteUtf8, 256),
+ Longest3ByteUtf8Atom = erlang:binary_to_atom(Longest3ByteUtf8AtomText, utf8),
+ Char4ByteUtf8 = <<"𠜱"/utf8>>,
+ Longest4ByteUtf8AtomText = binary:copy(Char4ByteUtf8, 255),
+ TooLong4ByteUtf8AtomText = binary:copy(Char4ByteUtf8, 256),
+ Longest4ByteUtf8Atom = erlang:binary_to_atom(Longest4ByteUtf8AtomText, utf8),
+ hejsan = make_new_atom(<<"hejsan">>, ?ERL_NIF_LATIN1),
+ 'hallå' = make_new_atom(<<"hallå">>, ?ERL_NIF_LATIN1),
+ 'Ω' = make_new_atom(<<"Ω"/utf8>>, ?ERL_NIF_LATIN1),
+ '' = make_new_atom(<<>>, ?ERL_NIF_LATIN1),
+ Longest1ByteAsciiAtom = make_new_atom(Longest1ByteAsciiAtomText, ?ERL_NIF_LATIN1),
+ 0 = make_new_atom(TooLong1ByteAsciiAtomText, ?ERL_NIF_LATIN1),
+ Longest2ByteLatin1Atom = make_new_atom(Longest2ByteLatin1AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_new_atom(TooLong2ByteLatin1AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_new_atom(Longest2ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_new_atom(TooLong2ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_new_atom(Longest3ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_new_atom(TooLong3ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_new_atom(Longest4ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_new_atom(TooLong4ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ hejsan = make_new_atom(<<"hejsan"/utf8>>, ?ERL_NIF_UTF8),
+ 'hallå' = make_new_atom(<<"hallå"/utf8>>, ?ERL_NIF_UTF8),
+ 'Ω' = make_new_atom(<<"Ω"/utf8>>, ?ERL_NIF_UTF8),
+ '' = make_new_atom(<<>>, ?ERL_NIF_UTF8),
+ Longest1ByteAsciiAtom = make_new_atom(Longest1ByteAsciiAtomText, ?ERL_NIF_UTF8),
+ 0 = make_new_atom(TooLong1ByteAsciiAtomText, ?ERL_NIF_UTF8),
+ 0 = make_new_atom(Longest2ByteLatin1AtomText, ?ERL_NIF_UTF8),
+ 0 = make_new_atom(TooLong2ByteLatin1AtomText, ?ERL_NIF_UTF8),
+ Longest2ByteUtf8Atom = make_new_atom(Longest2ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ 0 = make_new_atom(TooLong2ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ Longest3ByteUtf8Atom = make_new_atom(Longest3ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ 0 = make_new_atom(TooLong3ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ Longest4ByteUtf8Atom = make_new_atom(Longest4ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ 0 = make_new_atom(TooLong4ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ ok.
+
+%% Test enif_make_existing_atom_len
+make_existing_atoms(Config) when is_list(Config) ->
+ ensure_lib_loaded(Config, 1),
+ _Existing = [hejsan, 'hallå', 'Ω', 'Ω', ''],
+ Char1ByteLatin1 = <<"a">>,
+ Char1ByteLatin1NE = <<"u">>,
+ Longest1ByteLatin1AtomText = binary:copy(Char1ByteLatin1, 255),
+ Longest1ByteLatin1AtomTextNE = binary:copy(Char1ByteLatin1NE, 255),
+ TooLong1ByteLatin1AtomText = binary:copy(Char1ByteLatin1, 256),
+ Longest1ByteLatin1Atom = erlang:binary_to_atom(Longest1ByteLatin1AtomText, latin1),
+ Char2ByteLatin1 = <<"Ã¥">>,
+ Char2ByteLatin1NE = <<"ú">>,
+ Longest2ByteLatin1AtomText = binary:copy(Char2ByteLatin1, 255),
+ Longest2ByteLatin1AtomTextNE = binary:copy(Char2ByteLatin1NE, 255),
+ TooLong2ByteLatin1AtomText = binary:copy(Char2ByteLatin1, 256),
+ Longest2ByteLatin1Atom = erlang:binary_to_atom(Longest2ByteLatin1AtomText, latin1),
+ Char2ByteUtf8 = <<"Ã¥"/utf8>>,
+ Char2ByteUtf8NE = <<"ú"/utf8>>,
+ Longest2ByteUtf8AtomText = binary:copy(Char2ByteUtf8, 255),
+ Longest2ByteUtf8AtomTextNE = binary:copy(Char2ByteUtf8NE, 255),
+ TooLong2ByteUtf8AtomText = binary:copy(Char2ByteUtf8, 256),
+ Longest2ByteUtf8Atom = erlang:binary_to_atom(Longest2ByteUtf8AtomText, utf8),
+ Char3ByteUtf8 = <<"ᛥ"/utf8>>,
+ Char3ByteUtf8NE = <<"ᛤ"/utf8>>,
+ Longest3ByteUtf8AtomText = binary:copy(Char3ByteUtf8, 255),
+ Longest3ByteUtf8AtomTextNE = binary:copy(Char3ByteUtf8NE, 255),
+ TooLong3ByteUtf8AtomText = binary:copy(Char3ByteUtf8, 256),
+ Longest3ByteUtf8Atom = erlang:binary_to_atom(Longest3ByteUtf8AtomText, utf8),
+ Char4ByteUtf8 = <<"𠜱"/utf8>>,
+ Char4ByteUtf8NE = <<"ð ´•"/utf8>>,
+ Longest4ByteUtf8AtomText = binary:copy(Char4ByteUtf8, 255),
+ Longest4ByteUtf8AtomTextNE = binary:copy(Char4ByteUtf8NE, 255),
+ TooLong4ByteUtf8AtomText = binary:copy(Char4ByteUtf8, 256),
+ Longest4ByteUtf8Atom = erlang:binary_to_atom(Longest4ByteUtf8AtomText, utf8),
+ hejsan = make_existing_atom(<<"hejsan">>, ?ERL_NIF_LATIN1),
+ 'hallå' = make_existing_atom(<<"hallå">>, ?ERL_NIF_LATIN1),
+ 'Ω' = make_existing_atom(<<"Ω"/utf8>>, ?ERL_NIF_LATIN1),
+ '' = make_existing_atom(<<>>, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(<<"hejsan1234">>, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(<<"hallå1234">>, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(<<"Ω1234"/utf8>>, ?ERL_NIF_LATIN1),
+ Longest1ByteLatin1Atom = make_existing_atom(Longest1ByteLatin1AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(Longest1ByteLatin1AtomTextNE, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(TooLong1ByteLatin1AtomText, ?ERL_NIF_LATIN1),
+ Longest2ByteLatin1Atom = make_existing_atom(Longest2ByteLatin1AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(Longest2ByteLatin1AtomTextNE, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(TooLong2ByteLatin1AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(Longest2ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(Longest2ByteUtf8AtomTextNE, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(TooLong2ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(Longest3ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(Longest3ByteUtf8AtomTextNE, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(TooLong3ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(Longest4ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(Longest4ByteUtf8AtomTextNE, ?ERL_NIF_LATIN1),
+ 0 = make_existing_atom(TooLong4ByteUtf8AtomText, ?ERL_NIF_LATIN1),
+ hejsan = make_existing_atom(<<"hejsan"/utf8>>, ?ERL_NIF_UTF8),
+ 'hallå' = make_existing_atom(<<"hallå"/utf8>>, ?ERL_NIF_UTF8),
+ 'Ω' = make_existing_atom(<<"Ω"/utf8>>, ?ERL_NIF_UTF8),
+ '' = make_existing_atom(<<>>, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(<<"hejsan1234"/utf8>>, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(<<"hallå1234"/utf8>>, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(<<"Ω1234"/utf8>>, ?ERL_NIF_UTF8),
+ Longest1ByteLatin1Atom = make_existing_atom(Longest1ByteLatin1AtomText, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(Longest1ByteLatin1AtomTextNE, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(TooLong1ByteLatin1AtomText, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(Longest2ByteLatin1AtomText, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(Longest2ByteLatin1AtomTextNE, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(TooLong2ByteLatin1AtomText, ?ERL_NIF_UTF8),
+ Longest2ByteUtf8Atom = make_existing_atom(Longest2ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(Longest2ByteUtf8AtomTextNE, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(TooLong2ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ Longest3ByteUtf8Atom = make_existing_atom(Longest3ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(Longest3ByteUtf8AtomTextNE, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(TooLong3ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ Longest4ByteUtf8Atom = make_existing_atom(Longest4ByteUtf8AtomText, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(Longest4ByteUtf8AtomTextNE, ?ERL_NIF_UTF8),
+ 0 = make_existing_atom(TooLong4ByteUtf8AtomText, ?ERL_NIF_UTF8),
ok.
%% Test NIF maps handling.
@@ -1864,6 +2154,12 @@ resource_neg_do(TypeA) ->
ResB= make_new_resource(TypeB, <<"Bobo">>),
{'EXIT',{badarg,_}} = (catch get_resource(TypeA, ResB)),
{'EXIT',{badarg,_}} = (catch get_resource(TypeB, ResA)),
+
+ %% Test init_resource_type fail outside load/upgrade
+ {0, ?RT_CREATE} = init_resource_type("in_vain", ?RT_CREATE),
+ {0, ?RT_TAKEOVER} = init_resource_type("Gold", ?RT_TAKEOVER),
+ {0, ?RT_CREATE bor ?RT_TAKEOVER} =
+ init_resource_type("Gold", ?RT_CREATE bor ?RT_TAKEOVER),
ok.
%% Test enif_make_resource_binary
@@ -3984,12 +4280,15 @@ hash_nif(_Type, _Term, _Salt) -> ?nif_stub.
many_args_100(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_) -> ?nif_stub.
clone_bin(_) -> ?nif_stub.
make_sub_bin(_,_,_) -> ?nif_stub.
-string_to_bin(_,_) -> ?nif_stub.
-atom_to_bin(_,_) -> ?nif_stub.
+string_to_bin(_,_,_) -> ?nif_stub.
+string_length(_,_) -> ?nif_stub.
+atom_to_bin(_,_,_) -> ?nif_stub.
+atom_length(_,_) -> ?nif_stub.
macros(_) -> ?nif_stub.
tuple_2_list_and_tuple(_) -> ?nif_stub.
iolist_2_bin(_) -> ?nif_stub.
get_resource_type(_) -> ?nif_stub.
+init_resource_type(_,_) -> ?nif_stub.
alloc_resource(_,_) -> ?nif_stub.
make_resource(_) -> ?nif_stub.
get_resource(_,_) -> ?nif_stub.
@@ -4001,6 +4300,8 @@ check_is(_,_,_,_,_,_,_,_,_,_,_) -> ?nif_stub.
check_is_exception() -> ?nif_stub.
length_test(_,_,_,_,_,_) -> ?nif_stub.
make_atoms() -> ?nif_stub.
+make_new_atom(_,_) -> ?nif_stub.
+make_existing_atom(_,_) -> ?nif_stub.
make_strings() -> ?nif_stub.
make_new_resource_binary(_) -> ?nif_stub.
send_list_seq(_,_) -> ?nif_stub.
diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
index 7a47c2ea99..5ae97e529d 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
+++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2009-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2009-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -91,6 +91,8 @@ static ERL_NIF_TERM atom_port;
static ERL_NIF_TERM atom_send;
static ERL_NIF_TERM atom_lookup;
static ERL_NIF_TERM atom_badarg;
+static ERL_NIF_TERM atom_latin1;
+static ERL_NIF_TERM atom_utf8;
typedef struct
{
@@ -280,6 +282,8 @@ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
atom_send = enif_make_atom(env, "send");
atom_lookup = enif_make_atom(env, "lookup");
atom_badarg = enif_make_atom(env, "badarg");
+ atom_latin1 = enif_make_atom(env, "latin1");
+ atom_utf8 = enif_make_atom(env, "utf8");
*priv_data = data;
return 0;
@@ -784,28 +788,63 @@ static ERL_NIF_TERM string_to_bin(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
{
ErlNifBinary obin;
unsigned size;
+ int encoding;
int n;
- if (!enif_get_int(env,argv[1],(int*)&size)
- || !enif_alloc_binary(size,&obin)) {
- return enif_make_badarg(env);
+ if (!enif_get_int(env, argv[2], &encoding)
+ || !enif_get_int(env, argv[1], (int *)&size)
+ || !enif_alloc_binary(size, &obin)) {
+ return enif_make_badarg(env);
+ }
+ n = enif_get_string(env, argv[0], (char *)obin.data, size,
+ (ErlNifCharEncoding) encoding);
+ return enif_make_tuple(env, 2, enif_make_int(env, n),
+ enif_make_binary(env, &obin));
+}
+
+static ERL_NIF_TERM string_length(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ unsigned size;
+ int encoding;
+
+ if (!enif_get_int(env, argv[1], &encoding)) {
+ return enif_make_badarg(env);
}
- n = enif_get_string(env, argv[0], (char*)obin.data, size, ERL_NIF_LATIN1);
- return enif_make_tuple(env, 2, enif_make_int(env,n),
- enif_make_binary(env,&obin));
+ if (!enif_get_string_length(env, argv[0], &size,
+ (ErlNifCharEncoding)encoding)) {
+ return atom_false;
+ }
+ return enif_make_uint(env, size);
}
static ERL_NIF_TERM atom_to_bin(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary obin;
unsigned size;
+ int encoding;
int n;
- if (!enif_get_int(env,argv[1],(int*)&size)
- || !enif_alloc_binary(size,&obin)) {
- return enif_make_badarg(env);
+ if (!enif_get_int(env, argv[2], &encoding)
+ || !enif_get_int(env, argv[1], (int *)&size)
+ || !enif_alloc_binary(size,&obin)) {
+ return enif_make_badarg(env);
}
- n = enif_get_atom(env, argv[0], (char*)obin.data, size, ERL_NIF_LATIN1);
- return enif_make_tuple(env, 2, enif_make_int(env,n),
- enif_make_binary(env,&obin));
+ n = enif_get_atom(env, argv[0], (char *)obin.data, size,
+ (ErlNifCharEncoding)encoding);
+ return enif_make_tuple(env, 2, enif_make_int(env, n),
+ enif_make_binary(env, &obin));
+}
+
+static ERL_NIF_TERM atom_length(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ unsigned size;
+ int encoding;
+
+ if (!enif_get_int(env, argv[1], &encoding))
+ return enif_make_badarg(env);
+ if (!enif_get_atom_length(env, argv[0], &size,
+ (ErlNifCharEncoding)encoding)) {
+ return atom_false;
+ }
+ return enif_make_uint(env, size);
}
static ERL_NIF_TERM macros(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
@@ -893,6 +932,27 @@ static ERL_NIF_TERM get_resource_type(ErlNifEnv* env, int argc, const ERL_NIF_TE
return make_pointer(env, data->rt_arr[ix].vp);
}
+static ERL_NIF_TERM init_resource_type(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ char name[20];
+ int flags;
+ ErlNifResourceTypeInit init;
+ ErlNifResourceType* ret_ptr;
+ ErlNifResourceFlags tried;
+
+ if (0 >= enif_get_string(env, argv[0], name, sizeof(name), ERL_NIF_UTF8) ||
+ !enif_get_int(env, argv[1], &flags)) {
+ return enif_make_badarg(env);
+ }
+ /* Should fail as we are not in load/upgrade callback */
+ init.members = 0;
+ ret_ptr = enif_init_resource_type(env, name, &init,
+ (ErlNifResourceFlags)flags, &tried);
+
+ return enif_make_tuple2(env, enif_make_uint64(env, (ErlNifUInt64)ret_ptr),
+ enif_make_int(env, (int)tried));
+}
+
static ERL_NIF_TERM alloc_resource(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary data_bin;
@@ -1143,6 +1203,41 @@ static ERL_NIF_TERM make_atoms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
arr[0],arr[1],arr[2],arr[3],arr[4],arr[5],arr[6]);
}
+static ERL_NIF_TERM make_new_atom(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifBinary name_bin;
+ int encoding;
+ ERL_NIF_TERM atom_term;
+ int ret;
+
+ if (!enif_get_int(env, argv[1], &encoding)
+ || !enif_inspect_binary(env, argv[0], &name_bin)) {
+ return enif_make_badarg(env);
+ }
+ if (!enif_make_new_atom_len(env, (void *)name_bin.data, name_bin.size, &atom_term,
+ (ErlNifCharEncoding)encoding)) {
+ return enif_make_int(env, 0);
+ }
+ return atom_term;
+}
+
+static ERL_NIF_TERM make_existing_atom(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifBinary name_bin;
+ int encoding;
+ ERL_NIF_TERM atom_term;
+
+ if (!enif_get_int(env, argv[1], &encoding)
+ || !enif_inspect_binary(env, argv[0], &name_bin)) {
+ return enif_make_badarg(env);
+ }
+ if (!enif_make_existing_atom_len(env, (void *)name_bin.data, name_bin.size, &atom_term,
+ (ErlNifCharEncoding)encoding)) {
+ return enif_make_int(env,0);
+ }
+ return atom_term;
+}
+
static ERL_NIF_TERM make_strings(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
const char a0string[8] = {'a','0','s','t','r','i','n','g'};
@@ -3716,12 +3811,15 @@ static ErlNifFunc nif_funcs[] =
{"many_args_100", 100, many_args_100},
{"clone_bin", 1, clone_bin},
{"make_sub_bin", 3, make_sub_bin},
- {"string_to_bin", 2, string_to_bin},
- {"atom_to_bin", 2, atom_to_bin},
+ {"string_to_bin", 3, string_to_bin},
+ {"string_length", 2, string_length},
+ {"atom_to_bin", 3, atom_to_bin},
+ {"atom_length", 2, atom_length},
{"macros", 1, macros},
{"tuple_2_list_and_tuple",1,tuple_2_list_and_tuple},
{"iolist_2_bin", 1, iolist_2_bin},
{"get_resource_type", 1, get_resource_type},
+ {"init_resource_type", 2, init_resource_type},
{"alloc_resource", 2, alloc_resource},
{"make_resource", 1, make_resource},
{"get_resource", 2, get_resource},
@@ -3732,6 +3830,8 @@ static ErlNifFunc nif_funcs[] =
{"check_is_exception", 0, check_is_exception},
{"length_test", 6, length_test},
{"make_atoms", 0, make_atoms},
+ {"make_new_atom", 2, make_new_atom},
+ {"make_existing_atom", 2, make_existing_atom},
{"make_strings", 0, make_strings},
{"make_new_resource", 2, make_new_resource},
{"make_new_resource_binary", 1, make_new_resource_binary},
@@ -3812,7 +3912,6 @@ static ErlNifFunc nif_funcs[] =
{"compare_pids_nif", 2, compare_pids_nif},
{"term_type_nif", 1, term_type_nif},
{"msa_find_y_nif", 1, msa_find_y_nif}
-
};
ERL_NIF_INIT(nif_SUITE,nif_funcs,load,NULL,upgrade,unload)
diff --git a/erts/emulator/test/node_container_SUITE.erl b/erts/emulator/test/node_container_SUITE.erl
index 97ee9adcc0..13d111a1ad 100644
--- a/erts/emulator/test/node_container_SUITE.erl
+++ b/erts/emulator/test/node_container_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -87,7 +87,38 @@ end_per_testcase(_Case, Config) when is_list(Config) ->
%%% The test cases -------------------------------------------------------------
%%%
--define(MAX_PIDS_PORTS, ((1 bsl 28) - 1)).
+max_internal_pids_ports() ->
+ case erlang:system_info(wordsize) of
+ 8 -> (1 bsl 60) - 1;
+ 4 -> (1 bsl 28) - 1
+ end.
+
+max_pids_ports() ->
+ (1 bsl 64) - 1.
+
+max_old_pids_ports() ->
+ (1 bsl 28) - 1.
+
+max_internal_pid_num() ->
+ (1 bsl 28) - 1.
+
+max_internal_pid_ser() ->
+ case erlang:system_info(wordsize) of
+ 8 -> (1 bsl 32) - 1;
+ 4 -> 0
+ end.
+
+max_pid_num() ->
+ (1 bsl 32) - 1.
+
+max_pid_ser() ->
+ (1 bsl 32) - 1.
+
+max_old_pid_num() ->
+ (1 bsl 15) - 1.
+
+max_old_pid_ser() ->
+ (1 bsl 13) - 1.
%%
%% Test case: term_to_binary_to_term_eq
@@ -98,17 +129,21 @@ term_to_binary_to_term_eq(Config) when is_list(Config) ->
ThisNode = {node(), erlang:system_info(creation)},
% Get local node containers
LPid = self(),
- LXPid = mk_pid(ThisNode, 32767, 8191),
+ LXPid = mk_pid(ThisNode, 0, max_internal_pid_ser()),
+ LX2Pid = mk_pid(ThisNode, max_internal_pid_num() - 4711, max_internal_pid_ser()),
LPort = hd(erlang:ports()),
LXPort = mk_port(ThisNode, 268435455),
+ LX2Port = mk_port(ThisNode, max_internal_pids_ports() - 4711),
LLRef = make_ref(),
LHLRef = mk_ref(ThisNode, [47, 11]),
LSRef = mk_ref(ThisNode, [4711]),
% Test local nc:s
LPid = binary_to_term(term_to_binary(LPid)),
LXPid = binary_to_term(term_to_binary(LXPid)),
+ LX2Pid = binary_to_term(term_to_binary(LX2Pid)),
LPort = binary_to_term(term_to_binary(LPort)),
LXPort = binary_to_term(term_to_binary(LXPort)),
+ LX2Port = binary_to_term(term_to_binary(LX2Port)),
LLRef = binary_to_term(term_to_binary(LLRef)),
LHLRef = binary_to_term(term_to_binary(LHLRef)),
LSRef = binary_to_term(term_to_binary(LSRef)),
@@ -122,6 +157,7 @@ term_to_binary_to_term_eq(Config) when is_list(Config) ->
ttbtteq_do_remote(RNode) ->
RPid = mk_pid(RNode, 4711, 1),
RXPid = mk_pid(RNode, 32767, 8191),
+ RXPid2 = mk_pid(RNode, max_pid_num(), max_pid_ser()),
RPort = mk_port(RNode, 4711),
RXPort = mk_port(RNode, 268435455),
RXPort2 = case RNode of
@@ -130,15 +166,18 @@ ttbtteq_do_remote(RNode) ->
_ ->
mk_port(RNode, (1 bsl 51) + 4711)
end,
+ RXPort3 = mk_port(RNode, max_pids_ports()),
RLRef = mk_ref(RNode, [4711, 4711, 4711]),
RHLRef = mk_ref(RNode, [4711, 4711]),
RSRef = mk_ref(RNode, [4711]),
% Test remote nc:s
RPid = binary_to_term(term_to_binary(RPid)),
RXPid = binary_to_term(term_to_binary(RXPid)),
+ RXPid2 = binary_to_term(term_to_binary(RXPid2)),
RPort = binary_to_term(term_to_binary(RPort)),
RXPort = binary_to_term(term_to_binary(RXPort)),
RXPort2 = binary_to_term(term_to_binary(RXPort2)),
+ RXPort3 = binary_to_term(term_to_binary(RXPort3)),
RLRef = binary_to_term(term_to_binary(RLRef)),
RHLRef = binary_to_term(term_to_binary(RHLRef)),
RSRef = binary_to_term(term_to_binary(RSRef)),
@@ -161,36 +200,57 @@ round_trip_eq(Config) when is_list(Config) ->
end
end),
SentPid = self(),
- SentXPid = mk_pid(ThisNode, 17471, 8190),
+ SentXPid = mk_pid(ThisNode, 17471, max_internal_pid_ser()),
+ SentXPid2 = mk_pid(ThisNode, max_internal_pid_num(), max_internal_pid_ser()),
+ SentXPid3 = mk_pid({Node, 4711}, 4711, 17),
+ SentXPid4 = mk_pid({Node, 4711}, max_pid_num(), max_pid_ser()),
SentPort = hd(erlang:ports()),
SentXPort = mk_port(ThisNode, 268435451),
SentXPort2 = mk_port({Node, 4711}, (1 bsl 49) + 4711),
+ SentXPort2 = mk_port({Node, 4711}, (1 bsl 49) + 4711),
+ SentXPort3 = mk_port(ThisNode, max_internal_pids_ports()),
+ SentXPort4 = mk_port({Node, 4711}, max_pids_ports()),
SentLRef = make_ref(),
SentHLRef = mk_ref(ThisNode, [4711, 17]),
SentSRef = mk_ref(ThisNode, [4711]),
RPid ! {Self, {SentPid,
SentXPid,
+ SentXPid2,
+ SentXPid3,
+ SentXPid4,
SentPort,
SentXPort,
SentXPort2,
+ SentXPort3,
+ SentXPort4,
SentLRef,
SentHLRef,
SentSRef}},
receive
{RPid, {RecPid,
RecXPid,
+ RecXPid2,
+ RecXPid3,
+ RecXPid4,
RecPort,
RecXPort,
RecXPort2,
+ RecXPort3,
+ RecXPort4,
RecLRef,
RecHLRef,
RecSRef}} ->
stop_node(Peer, Node),
SentPid = RecPid,
SentXPid = RecXPid,
+ SentXPid2 = RecXPid2,
+ SentXPid3 = RecXPid3,
+ SentXPid4 = RecXPid4,
SentPort = RecPort,
SentXPort = RecXPort,
SentXPort2 = RecXPort2,
+ SentXPort3 = RecXPort3,
+ SentXPort4 = RecXPort4,
SentLRef = RecLRef,
SentHLRef = RecHLRef,
SentSRef = RecSRef,
@@ -327,8 +387,8 @@ cmp(Config) when is_list(Config) ->
true = mk_pid({b@b, 2}, 4711, 1) =:= Pid,
%% Test big external pids (> OTP-24)
- MaxPidNum = (1 bsl 15) - 1,
- MaxPidSer = ?MAX_PIDS_PORTS bsr 15,
+ MaxPidNum = max_old_pid_num(),
+ MaxPidSer = max_old_pid_ser(),
true = mk_pid({b@b, 2}, 4711, MaxPidSer) < mk_pid({a@b, 1}, 4710, MaxPidSer+1),
true = mk_pid({b@b, 2}, 4711, MaxPidSer) < mk_pid({a@b, 1}, 4710, (1 bsl 31)),
true = mk_pid({b@b, 2}, 4711, MaxPidSer) < mk_pid({a@b, 1}, 4710, (1 bsl 32)-1),
@@ -738,20 +798,20 @@ pp_wrap(What) ->
io:format("post creations = ~p~n", [PostCre]),
true = is_integer(PostCre),
true = PreCre > PostCre,
- Now = set_next_id(What, ?MAX_PIDS_PORTS div 2),
+ Now = set_next_id(What, max_internal_pids_ports() div 2),
io:format("reset to = ~p~n", [Now]),
true = is_integer(Now),
ok.
set_high_pp_next(What) ->
- set_high_pp_next(What, ?MAX_PIDS_PORTS-1).
+ set_high_pp_next(What, max_internal_pids_ports()-1).
set_high_pp_next(What, N) ->
M = set_next_id(What, N),
true = is_integer(M),
- case {M >= N, M =< ?MAX_PIDS_PORTS} of
+ case {M >= N, M =< max_internal_pids_ports()} of
{true, true} ->
- ?MAX_PIDS_PORTS - M + 1;
+ max_internal_pids_ports() - M + 1;
_ ->
set_high_pp_next(What, N - 100)
end.
@@ -780,22 +840,24 @@ do_pp_creations(port, N) when is_integer(N) ->
bad_nc(Config) when is_list(Config) ->
% Make sure emulator don't crash on bad node containers...
- MaxPidNum = (1 bsl 15) - 1,
- MaxPidSer = ?MAX_PIDS_PORTS bsr 15,
ThisNode = {node(), erlang:system_info(creation)},
{'EXIT', {badarg, mk_pid, _}}
- = (catch mk_pid(ThisNode, MaxPidNum + 1, 17)),
+ = (catch mk_pid(ThisNode, max_internal_pid_num() + 1, 17)),
{'EXIT', {badarg, mk_pid, _}}
- = (catch mk_pid(ThisNode, 4711, MaxPidSer + 1)),
+ = (catch mk_pid(ThisNode, 4711, max_internal_pid_ser() + 1)),
{'EXIT', {badarg, mk_port, _}}
- = (catch mk_port(ThisNode, ?MAX_PIDS_PORTS + 1)),
+ = (catch mk_port(ThisNode, max_internal_pids_ports() + 1)),
{'EXIT', {badarg, mk_ref, _}}
= (catch mk_ref(ThisNode,[(1 bsl 18), 4711, 4711])),
{'EXIT', {badarg, mk_ref, _}}
= (catch mk_ref(ThisNode, [4711, 4711, 4711, 4711, 4711, 4711, 4711])),
RemNode = {x@y, 2},
+ {'EXIT', {badarg, mk_pid, _}}
+ = (catch mk_pid(RemNode, max_pid_num() + 1, 17)),
+ {'EXIT', {badarg, mk_pid, _}}
+ = (catch mk_pid(RemNode, 4711, max_pid_ser() + 1)),
{'EXIT', {badarg, mk_port, _}}
- = (catch mk_port(RemNode, ?MAX_PIDS_PORTS + 1)),
+ = (catch mk_port(RemNode, max_pids_ports() + 1)),
{'EXIT', {badarg, mk_ref, _}}
= (catch mk_ref(RemNode, [(1 bsl 18), 4711, 4711])),
{'EXIT', {badarg, mk_ref, _}}
@@ -808,14 +870,17 @@ bad_nc(Config) when is_list(Config) ->
{'EXIT', {badarg, mk_ref, _}}
= (catch mk_ref(BadNode, [4711, 4711, 17])),
+
%% OTP 24:
- mk_port({x@y, 4}, ?MAX_PIDS_PORTS + 1),
+ mk_port({x@y, 4}, max_old_pids_ports() + 1),
+ mk_port({x@y, 4}, max_pids_ports()),
%% OTP 24: External pids can use 32+32 bits
- mk_pid(RemNode, MaxPidNum + 1, MaxPidSer),
- mk_pid(RemNode, (1 bsl 32)-1, MaxPidSer),
- mk_pid(RemNode, MaxPidNum, MaxPidSer + 1),
- mk_pid(RemNode, MaxPidNum, (1 bsl 32)-1),
+ mk_pid(RemNode, max_old_pid_num() + 1, max_old_pid_ser()),
+ mk_pid(RemNode, (1 bsl 32)-1, max_old_pid_ser()),
+ mk_pid(RemNode, max_old_pid_num(), max_old_pid_ser() + 1),
+ mk_pid(RemNode, max_old_pid_num(), (1 bsl 32)-1),
+ mk_pid(RemNode, max_pid_num(), max_pid_ser()),
ok.
diff --git a/erts/emulator/test/op_SUITE.erl b/erts/emulator/test/op_SUITE.erl
index dd13fde16d..73b556ad66 100644
--- a/erts/emulator/test/op_SUITE.erl
+++ b/erts/emulator/test/op_SUITE.erl
@@ -25,7 +25,7 @@
-export([all/0, suite/0,
bsl_bsr/1,logical/1,t_not/1,relop_simple/1,relop/1,
complex_relop/1,unsafe_fusing/1,
- range_tests/1,typed_relop/1]).
+ range_tests/1,combined_relops/1,typed_relop/1]).
-import(lists, [foldl/3,flatmap/2]).
@@ -35,7 +35,8 @@ suite() ->
all() ->
[bsl_bsr, logical, t_not, relop_simple, relop,
- complex_relop, unsafe_fusing, range_tests, typed_relop].
+ complex_relop, unsafe_fusing, range_tests,
+ combined_relops, typed_relop].
%% Test the bsl and bsr operators.
bsl_bsr(Config) when is_list(Config) ->
@@ -523,7 +524,7 @@ range_tests(_Config) ->
inside = range_big(MinSmall),
inside = range_big(-1 bsl 58),
inside = range_big(0),
- inside = range_barely_small(17.75),
+ inside = range_big(17.75),
inside = range_big(1 bsl 58),
inside = range_big(MaxSmall),
@@ -531,6 +532,39 @@ range_tests(_Config) ->
greater = range_big(1 bsl 64),
greater = range_big(float(1 bsl 64)),
+ inside = int_range_1(id(-100_000)),
+ inside = int_range_1(id(-10)),
+ inside = int_range_1(id(100)),
+ inside = int_range_1(id(100_000)),
+
+ outside = int_range_1(id(atom)),
+ outside = int_range_1(id(-1 bsl 60)),
+ outside = int_range_1(id(-100_001)),
+ outside = int_range_1(id(100_001)),
+ outside = int_range_1(id(1 bsl 60)),
+
+ inside = int_range_2(id(1)),
+ inside = int_range_2(id(42)),
+ inside = int_range_2(id(16#f000_0000)),
+
+ outside = int_range_2(id([a,list])),
+ outside = int_range_2(id(0)),
+ outside = int_range_1(id(-1 bsl 60)),
+ outside = int_range_1(id(1 bsl 60)),
+
+ inside = int_range_3(id(1 bsl 28)),
+ inside = int_range_3(id((1 bsl 28) + 1)),
+ inside = int_range_3(id((1 bsl 33) + 555)),
+ inside = int_range_3(id((1 bsl 58) - 1)),
+ inside = int_range_3(id(1 bsl 58)),
+
+ outside = int_range_3(id({a,tuple})),
+ outside = int_range_3(id(-1 bsl 60)),
+ outside = int_range_3(id(-1000)),
+ outside = int_range_3(id(100)),
+ outside = int_range_3(id((1 bsl 58) + 1)),
+ outside = int_range_3(id(1 bsl 60)),
+
ok.
range(X) ->
@@ -683,6 +717,221 @@ range_big_2(X) when (-1 bsl 59) - 1 =< X, X =< 1 bsl 59 ->
range_big_2(_) ->
outside.
+int_range_1(X) when is_integer(X), -100_000 =< X, X =< 100_000 ->
+ inside;
+int_range_1(_) ->
+ outside.
+
+int_range_2(X) when is_integer(X), 1 =< X, X =< 16#f000_0000 ->
+ inside;
+int_range_2(_) ->
+ outside.
+
+int_range_3(X) when is_integer(X), 1 bsl 28 =< X, X =< 1 bsl 58 ->
+ inside;
+int_range_3(_) ->
+ outside.
+
+combined_relops(_Config) ->
+ other = test_tok_char(-1 bsl 64),
+ other = test_tok_char($A - 1),
+
+ var = test_tok_char($A),
+ var = test_tok_char($B),
+ var = test_tok_char($P),
+ var = test_tok_char($Y),
+ var = test_tok_char($Z),
+
+ other = test_tok_char($Z + 1),
+
+ var = tok_char($_),
+ other = tok_char(float($_)),
+
+ other = test_tok_char(1 bsl 64),
+
+ other = test_tok_char(atom),
+ other = test_tok_char(self()),
+
+ %%
+ b = ge_ge_int_range_1(-200),
+ b = ge_ge_int_range_1(-101),
+
+ a = ge_ge_int_range_1(-100),
+ a = ge_ge_int_range_1(-50),
+ a = ge_ge_int_range_1(-10),
+
+ b = ge_ge_int_range_1(-9),
+ b = ge_ge_int_range_1(-6),
+
+ a = ge_ge_int_range_1(-5),
+
+ b = ge_ge_int_range_1(-4),
+ b = ge_ge_int_range_1(0),
+ b = ge_ge_int_range_1(42),
+
+ %%
+ b = ge_ge_int_range_2(-1 bsl 59),
+
+ a = ge_ge_int_range_2((-1 bsl 59) + 1),
+ a = ge_ge_int_range_2(-1 bsl 58),
+ a = ge_ge_int_range_2(-1000),
+ a = ge_ge_int_range_2(1 bsl 58),
+ a = ge_ge_int_range_2((1 bsl 59) - 10),
+
+ b = ge_ge_int_range_2((1 bsl 59) - 9),
+
+ a = ge_ge_int_range_2((1 bsl 59) - 5),
+
+ b = ge_ge_int_range_2((1 bsl 59) - 4),
+ b = ge_ge_int_range_2((1 bsl 59) - 1),
+
+ %%
+ b = ge_ge_int_range_3(-1 bsl 59),
+
+ b = ge_ge_int_range_3((-1 bsl 59) + 1),
+ b = ge_ge_int_range_3(-1 bsl 58),
+ b = ge_ge_int_range_3(-1000),
+ b = ge_ge_int_range_3(1 bsl 58),
+
+ a = ge_ge_int_range_3((1 bsl 59) - 20),
+ a = ge_ge_int_range_3((1 bsl 59) - 15),
+ a = ge_ge_int_range_3((1 bsl 59) - 10),
+
+ b = ge_ge_int_range_3((1 bsl 59) - 9),
+
+ a = ge_ge_int_range_3((1 bsl 59) - 5),
+
+ b = ge_ge_int_range_3((1 bsl 59) - 4),
+ b = ge_ge_int_range_3((1 bsl 59) - 1),
+
+ %%
+ b = ge_ge_int_range_4(-1 bsl 59),
+
+ a = ge_ge_int_range_4((-1 bsl 59) + 1),
+ a = ge_ge_int_range_4((-1 bsl 59) + 3),
+ a = ge_ge_int_range_4((-1 bsl 59) + 5),
+
+ b = ge_ge_int_range_4((-1 bsl 59) + 6),
+ b = ge_ge_int_range_4((-1 bsl 59) + 9),
+
+ a = ge_ge_int_range_4((-1 bsl 59) + 10),
+
+ b = ge_ge_int_range_4((-1 bsl 59) + 11),
+
+ b = ge_ge_int_range_4(0),
+ b = ge_ge_int_range_4(1000),
+
+ b = ge_ge_int_range_4((1 bsl 59) - 1),
+
+ %% Test a sequence that can't occur in optimized code:
+ %% is_ge Fail Src 10
+ %% is_ge Fail Src 5
+ Module = {?FUNCTION_NAME,[{test,1}],[],
+ [{function, test, 1, 2,
+ [{label,1},
+ {line,[{location,"t.erl",4}]},
+ {func_info,{atom,?FUNCTION_NAME},{atom,test},1},
+ {label,2},
+ {test,is_ge,{f,4},
+ [{tr,{x,0},{t_integer,{0,1000}}},
+ {integer,10}]},
+ {test,is_ge,
+ {f,3},
+ [{tr,{x,0},{t_integer,{0,1000}}},
+ {integer,5}]},
+ {label,3},
+ {move,{atom,a},{x,0}},
+ return,
+ {label,4},
+ {move,{atom,b},{x,0}},
+ return]}],
+ 5},
+
+ {ok,Mod,Code} = compile:forms(Module, [from_asm,time,report]),
+ {module,Mod} = code:load_binary(Mod, Mod, Code),
+
+ b = Mod:test(0),
+ b = Mod:test(5),
+ b = Mod:test(9),
+
+ a = Mod:test(10),
+ a = Mod:test(11),
+ a = Mod:test(1000),
+
+ true = code:delete(Mod),
+ _ = code:purge(Mod),
+
+ ok.
+
+test_tok_char(C) ->
+ Result = tok_char(C),
+ if
+ is_integer(C) ->
+ Result = tok_char(float(C)),
+ Result = tok_char_int(C),
+ if
+ C band 16#FFFF =:= C ->
+ Result = tok_char_int_range(C);
+ true ->
+ Result
+ end;
+ true ->
+ Result
+ end.
+
+%% is_ge + is_lt
+tok_char(C) when $A =< C, C =< $Z ->
+ var;
+tok_char($_) ->
+ var;
+tok_char(_) ->
+ other.
+
+%% is_ge + is_ge
+tok_char_int(C) when $A =< C, C =< $Z ->
+ var;
+tok_char_int($_) ->
+ var;
+tok_char_int(_) ->
+ other.
+
+%% is_ge + is_ge
+tok_char_int_range(C) when $A =< C, C =< $Z ->
+ var;
+tok_char_int_range($_) ->
+ var;
+tok_char_int_range(_) ->
+ other.
+
+%% is_ge + is_ge
+ge_ge_int_range_1(X) when -100 =< X, X =< -10 ->
+ a;
+ge_ge_int_range_1(-5) ->
+ a;
+ge_ge_int_range_1(_) ->
+ b.
+
+ge_ge_int_range_2(X) when (-1 bsl 59) + 1 =< X, X =< (1 bsl 59) - 10 ->
+ a;
+ge_ge_int_range_2((1 bsl 59) - 5) ->
+ a;
+ge_ge_int_range_2(_) ->
+ b.
+
+ge_ge_int_range_3(X) when (1 bsl 59) - 20 =< X, X =< (1 bsl 59) - 10 ->
+ a;
+ge_ge_int_range_3((1 bsl 59) - 5) ->
+ a;
+ge_ge_int_range_3(_) ->
+ b.
+
+ge_ge_int_range_4(X) when (-1 bsl 59) + 1 =< X, X =< (-1 bsl 59) + 5 ->
+ a;
+ge_ge_int_range_4((-1 bsl 59) + 10) ->
+ a;
+ge_ge_int_range_4(_) ->
+ b.
+
%% Tests operators where type hints are significant.
typed_relop(Config) when is_list(Config) ->
_ = [compare_integer_pid(1 bsl N) || N <- lists:seq(1, 64)],
diff --git a/erts/emulator/test/persistent_term_SUITE.erl b/erts/emulator/test/persistent_term_SUITE.erl
index 35216aa78d..39dd2d3428 100644
--- a/erts/emulator/test/persistent_term_SUITE.erl
+++ b/erts/emulator/test/persistent_term_SUITE.erl
@@ -604,27 +604,54 @@ collisions_delete([], _) ->
colliding_keys() ->
%% Collisions found by find_colliding_keys() below
- L = [[77674392,148027],
- [103370644,950908],
- [106444046,870178],
- [22217246,735880],
- [18088843,694607],
- [63426007,612179],
- [117354942,906431],
- [121434305,94282311,816072],
- [118441466,93873772,783366],
- [124338174,1414801,123089],
- [20240282,17113486,923647],
- [126495528,61463488,164994],
- [125341723,5729072,445539],
- [127450932,80442669,348245],
- [123354692,85724182,14241288,180793],
- [99159367,65959274,61680971,289939],
- [107637580,104512101,62639807,181644],
- [139547511,51654420,2062545,151944],
- [88078274,73031465,53388204,428872],
- [141314238,75761379,55699508,861797],
- [88045216,59272943,21030492,180903]],
+ %% ct:timetrap({minutes, 60}),
+ %% ct:pal("Colliding keys = ~p", [find_colliding_keys()]),
+ Collisions =
+ #{
+ %% Collisions for Jenkins96 hashing.
+ 1268203079 => [[77674392,148027],
+ [103370644,950908],
+ [106444046,870178],
+ [22217246,735880],
+ [18088843,694607],
+ [63426007,612179],
+ [117354942,906431],
+ [121434305,94282311,816072],
+ [118441466,93873772,783366],
+ [124338174,1414801,123089],
+ [20240282,17113486,923647],
+ [126495528,61463488,164994],
+ [125341723,5729072,445539],
+ [127450932,80442669,348245],
+ [123354692,85724182,14241288,180793],
+ [99159367,65959274,61680971,289939],
+ [107637580,104512101,62639807,181644],
+ [139547511,51654420,2062545,151944],
+ [88078274,73031465,53388204,428872],
+ [141314238,75761379,55699508,861797],
+ [88045216,59272943,21030492,180903]],
+ %% Collisions for CRC32-C hashing.
+ 1982459178 => [[-4294967296,654663773],
+ [-3758096384,117792861],
+ [-3221225472,1728405597],
+ [-2684354560,1191534685],
+ [-2147483648,2706162303],
+ [-1610612736,2169291391],
+ [-1073741824,3779904127],
+ [-536870912,3243033215],
+ [-3640303523,0],
+ [-4177174435,536870912],
+ [-2566561699,1073741824],
+ [-3103432611,1610612736],
+ [-1588804993,2147483648],
+ [-2125675905,2684354560],
+ [-515063169,3221225472],
+ [-1051934081,3758096384]]
+ },
+
+ Key = internal_hash(2),
+ ct:pal("internal_hash(2) = ~p", [Key]),
+ #{ Key := L } = Collisions,
%% Verify that the keys still collide (this will fail if the
%% internal hash function has been changed).
@@ -649,57 +676,47 @@ internal_hash(Term) ->
erts_debug:get_internal_state({internal_hash,Term}).
%% Use this function to (re)generate the list in colliding_keys/0
+%%
+%% Grab a coffee, it will take a while.
find_colliding_keys() ->
- MaxCollSz = 4,
- OfEachSz = 7,
erts_debug:set_internal_state(available_internal_state, true),
- MaxInserts = 1 bsl 20,
- T = ets:new(x, [set, private]),
- ok = fck_loop_1(T, 1, MaxInserts, MaxCollSz, OfEachSz),
- fck_collect(T, MaxCollSz, OfEachSz, []).
-
-fck_collect(_T, 1, _OfEachSz, Acc) ->
- Acc;
-fck_collect(T, CollSz, OfEachSz, Acc) ->
- {List, _} = ets:select(T,
- [{{'$1','$2'}, [{'==',{length,'$2'},CollSz}], ['$2']}],
- OfEachSz),
- fck_collect(T, CollSz-1, OfEachSz, List ++ Acc).
-
-
-fck_loop_1(T, Key, 0, MaxCollSz, MaxSzLeft) ->
- fck_loop_2(T, Key, MaxCollSz, MaxSzLeft);
-fck_loop_1(T, Key, Inserts, MaxCollSz, MaxSzLeft) ->
- Hash = internal_hash(Key),
- case ets:insert_new(T, {Hash, [Key]}) of
- true ->
- fck_loop_1(T, Key+1, Inserts-1, MaxCollSz, MaxSzLeft);
- false ->
- [{Hash, KeyList}] = ets:lookup(T, Hash),
- true = ets:insert(T, {Hash, [Key | KeyList]}),
- fck_loop_1(T, Key+1, Inserts, MaxCollSz, MaxSzLeft)
- end.
-
-fck_loop_2(_T, _Key, _MaxCollSz, 0) ->
- ok;
-fck_loop_2(T, Key, MaxCollSz, MaxSzLeft0) ->
- Hash = internal_hash(Key),
- case ets:lookup(T, Hash) of
- [] ->
- fck_loop_2(T, Key+1, MaxCollSz, MaxSzLeft0);
- [{Hash, KeyList}] ->
- true = ets:insert(T, {Hash, [Key | KeyList]}),
- MaxSzLeft1 = case length(KeyList)+1 of
- MaxCollSz ->
- MaxSzLeft0 - 1;
- _ ->
- MaxSzLeft0
- end,
- fck_loop_2(T, Key+1, MaxCollSz, MaxSzLeft1)
+ NumScheds = erlang:system_info(schedulers_online),
+ Start = -(1 bsl 32),
+ End = -Start,
+ Range = End - Start,
+ Step = Range div NumScheds,
+ timer:tc(fun() -> fck_spawn(NumScheds, NumScheds, Start, End, Step, []) end).
+
+fck_spawn(0, _NumScheds, _Start, _End, _Step, Refs) ->
+ fck_await(Refs);
+fck_spawn(N, NumScheds, Start, End, Step, Refs) ->
+ Key = Start + (N - 1) * Step,
+ {_, Ref} = spawn_monitor(fun() -> exit(fck_finder(Start, End, Key)) end),
+ fck_spawn(N - 1, NumScheds, Start, End, Step, [Ref | Refs]).
+
+fck_await([Ref | Refs]) ->
+ receive
+ {'DOWN', Ref, _, _, [_Initial]} ->
+ %% Ignore slices where the initial value only collided with itself.
+ fck_await(Refs);
+ {'DOWN', Ref, _, _, Collisions} ->
+ [Collisions | fck_await(Refs)]
+ end;
+fck_await([]) ->
+ [].
+
+fck_finder(Start, End, Key) ->
+ true = Key >= Start, true = Key < End, %Assertion.
+ fck_finder_1(Start, End, internal_hash(Key)).
+
+fck_finder_1(Same, Same, _Target) ->
+ [];
+fck_finder_1(Next, End, Target) ->
+ case internal_hash(Next) =:= Target of
+ true -> [Next | fck_finder_1(Next + 1, End, Target)];
+ false -> fck_finder_1(Next + 1, End, Target)
end.
-
-
%% OTP-17700 Bug skipped refc++ of shared magic reference
shared_magic_ref(_Config) ->
Ref = atomics:new(10, []),
diff --git a/erts/emulator/test/process_SUITE.erl b/erts/emulator/test/process_SUITE.erl
index 080b63c5f6..d28c4d15db 100644
--- a/erts/emulator/test/process_SUITE.erl
+++ b/erts/emulator/test/process_SUITE.erl
@@ -95,10 +95,10 @@
alias_bif/1,
monitor_alias/1,
spawn_monitor_alias/1,
- alias_process_exit/1,
demonitor_aliasmonitor/1,
down_aliasmonitor/1,
- monitor_tag/1]).
+ monitor_tag/1,
+ no_pid_wrap/1]).
-export([prio_server/2, prio_client/2, init/1, handle_event/2]).
@@ -131,7 +131,8 @@ all() ->
{group, otp_7738}, garb_other_running,
{group, system_task},
{group, alias},
- monitor_tag].
+ monitor_tag,
+ no_pid_wrap].
groups() ->
[{t_exit_2, [],
@@ -185,7 +186,7 @@ groups() ->
gc_request_when_gc_disabled, gc_request_blast_when_gc_disabled,
otp_16436, otp_16642]},
{alias, [],
- [alias_bif, monitor_alias, spawn_monitor_alias, alias_process_exit,
+ [alias_bif, monitor_alias, spawn_monitor_alias,
demonitor_aliasmonitor, down_aliasmonitor]}].
init_per_suite(Config) ->
@@ -205,15 +206,9 @@ end_per_suite(Config) ->
catch erts_debug:set_internal_state(available_internal_state, false),
Config.
-init_per_group(alias, Config) ->
- erts_debug:set_internal_state(available_internal_state, true),
- Config;
init_per_group(_GroupName, Config) ->
Config.
-end_per_group(alias, Config) ->
- erts_debug:set_internal_state(available_internal_state, false),
- Config;
end_per_group(_GroupName, Config) ->
Config.
@@ -234,6 +229,7 @@ end_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
erlang:system_flag(max_heap_size,
#{size => 0,
kill => true,
+ include_shared_binaries => false,
error_logger => true}),
erts_test_utils:ept_check_leaked_nodes(Config).
@@ -539,7 +535,8 @@ t_process_info(Config) when is_list(Config) ->
{status, running} = process_info(self(), status),
{min_heap_size, 233} = process_info(self(), min_heap_size),
{min_bin_vheap_size,46422} = process_info(self(), min_bin_vheap_size),
- {max_heap_size, #{ size := 0, kill := true, error_logger := true}} =
+ {max_heap_size, #{ size := 0, kill := true, error_logger := true,
+ include_shared_binaries := false}} =
process_info(self(), max_heap_size),
{current_function,{?MODULE,t_process_info,1}} =
process_info(self(), current_function),
@@ -697,8 +694,9 @@ process_info_other_msg(Config) when is_list(Config) ->
{min_heap_size, 233} = process_info(Pid, min_heap_size),
{min_bin_vheap_size, 46422} = process_info(Pid, min_bin_vheap_size),
- {max_heap_size, #{ size := 0, kill := true, error_logger := true}} =
- process_info(self(), max_heap_size),
+ {max_heap_size, #{ size := 0, kill := true, error_logger := true,
+ include_shared_binaries := false}} =
+ process_info(Pid, max_heap_size),
Pid ! stop,
ok.
@@ -1089,6 +1087,20 @@ check_proc_infos(A, B) ->
GC = lists:keysearch(garbage_collection, 1, A),
GC = lists:keysearch(garbage_collection, 1, B),
+ {value, {garbage_collection, GClist}} = GC,
+
+ %% This is not really documented
+ true = is_integer(gv(minor_gcs, GClist)),
+ true = is_integer(gv(fullsweep_after, GClist)),
+ true = is_integer(gv(min_heap_size, GClist)),
+ #{error_logger := Bool1,
+ include_shared_binaries := Bool2,
+ kill := Bool3,
+ size := MaxHeapSize} = gv(max_heap_size, GClist),
+ true = is_boolean(Bool1),
+ true = is_boolean(Bool2),
+ true = is_boolean(Bool3),
+ true = is_integer(MaxHeapSize),
ok.
@@ -1956,6 +1968,8 @@ process_flag_badarg(Config) when is_list(Config) ->
chk_badarg(fun () -> process_flag(max_heap_size, #{ size => 233,
error_logger => gurka }) end),
chk_badarg(fun () -> process_flag(max_heap_size, #{ size => 233,
+ include_shared_binaries => gurka}) end),
+ chk_badarg(fun () -> process_flag(max_heap_size, #{ size => 233,
kill => true,
error_logger => gurka }) end),
chk_badarg(fun () -> process_flag(max_heap_size, #{ size => 1 bsl 64 }) end),
@@ -2605,63 +2619,72 @@ spawn_opt_max_heap_size(_Config) ->
flush()
end,
+ spawn_opt_max_heap_size_do(fun oom_fun/1),
+
+ io:format("Repeat tests with refc binaries\n",[]),
+
+ spawn_opt_max_heap_size_do(fun oom_bin_fun/1),
+
+ error_logger:delete_report_handler(?MODULE),
+ ok.
+
+spawn_opt_max_heap_size_do(OomFun) ->
+ Max = 2024,
%% Test that numerical limit works
- max_heap_size_test(1024, 1024, true, true),
+ max_heap_size_test(Max, Max, true, true, OomFun),
%% Test that map limit works
- max_heap_size_test(#{ size => 1024 }, 1024, true, true),
+ max_heap_size_test(#{ size => Max }, Max, true, true, OomFun),
%% Test that no kill is sent
- max_heap_size_test(#{ size => 1024, kill => false }, 1024, false, true),
+ max_heap_size_test(#{ size => Max, kill => false }, Max, false, true, OomFun),
%% Test that no error_logger report is sent
- max_heap_size_test(#{ size => 1024, error_logger => false }, 1024, true, false),
+ max_heap_size_test(#{ size => Max, error_logger => false }, Max, true, false, OomFun),
%% Test that system_flag works
- erlang:system_flag(max_heap_size, #{ size => 0, kill => false,
- error_logger => true}),
- max_heap_size_test(#{ size => 1024 }, 1024, false, true),
- max_heap_size_test(#{ size => 1024, kill => true }, 1024, true, true),
+ erlang:system_flag(max_heap_size, OomFun(#{ size => 0, kill => false,
+ error_logger => true})),
+ max_heap_size_test(#{ size => Max }, Max, false, true, OomFun),
+ max_heap_size_test(#{ size => Max, kill => true }, Max, true, true, OomFun),
- erlang:system_flag(max_heap_size, #{ size => 0, kill => true,
- error_logger => false}),
- max_heap_size_test(#{ size => 1024 }, 1024, true, false),
- max_heap_size_test(#{ size => 1024, error_logger => true }, 1024, true, true),
+ erlang:system_flag(max_heap_size, OomFun(#{ size => 0, kill => true,
+ error_logger => false})),
+ max_heap_size_test(#{ size => Max }, Max, true, false, OomFun),
+ max_heap_size_test(#{ size => Max, error_logger => true }, Max, true, true, OomFun),
- erlang:system_flag(max_heap_size, #{ size => 1 bsl 20, kill => true,
- error_logger => true}),
- max_heap_size_test(#{ }, 1 bsl 20, true, true),
+ erlang:system_flag(max_heap_size, OomFun(#{ size => 1 bsl 16, kill => true,
+ error_logger => true})),
+ max_heap_size_test(#{ }, 1 bsl 16, true, true, OomFun),
erlang:system_flag(max_heap_size, #{ size => 0, kill => true,
error_logger => true}),
%% Test that ordinary case works as expected again
- max_heap_size_test(1024, 1024, true, true),
+ max_heap_size_test(Max, Max, true, true, OomFun),
+ ok.
- error_logger:delete_report_handler(?MODULE),
- ok.
+mhs_spawn_opt(Option) when map_get(size, Option) > 0;
+ is_integer(Option) ->
+ [{max_heap_size, Option}];
+mhs_spawn_opt(_) ->
+ [].
-max_heap_size_test(Option, Size, Kill, ErrorLogger)
- when map_size(Option) == 0 ->
- max_heap_size_test([], Size, Kill, ErrorLogger);
-max_heap_size_test(Option, Size, Kill, ErrorLogger)
- when is_map(Option); is_integer(Option) ->
- max_heap_size_test([{max_heap_size, Option}], Size, Kill, ErrorLogger);
-max_heap_size_test(Option, Size, Kill, ErrorLogger) ->
- OomFun = fun () -> oom_fun([]) end,
- Pid = spawn_opt(OomFun, Option),
+max_heap_size_test(Option, Size, Kill, ErrorLogger, OomFun) ->
+ SpOpt = mhs_spawn_opt(OomFun(Option)),
+ Pid = spawn_opt(fun()-> OomFun(run) end, SpOpt),
{max_heap_size, MHSz} = erlang:process_info(Pid, max_heap_size),
- ct:log("Default: ~p~nOption: ~p~nProc: ~p~n",
- [erlang:system_info(max_heap_size), Option, MHSz]),
+ ct:log("Default: ~p~nOption: ~p~nProc: ~p~nSize = ~p~nSpOpt = ~p~n",
+ [erlang:system_info(max_heap_size), Option, MHSz, Size, SpOpt]),
#{ size := Size} = MHSz,
Ref = erlang:monitor(process, Pid),
if Kill ->
receive
- {'DOWN', Ref, process, Pid, killed} ->
- ok
+ {'DOWN', Ref, process, Pid, Reason} ->
+ killed = Reason
end;
true ->
ok
@@ -2692,12 +2715,37 @@ max_heap_size_test(Option, Size, Kill, ErrorLogger) ->
%% Make sure that there are no unexpected messages.
receive_unexpected().
-oom_fun(Acc0) ->
+oom_fun(Max) when is_integer(Max) -> Max;
+oom_fun(Map) when is_map(Map)-> Map;
+oom_fun(run) ->
+ io:format("oom_fun() started\n",[]),
+ oom_run_fun([], 100).
+
+oom_run_fun(Acc0, 0) ->
+ done;
+oom_run_fun(Acc0, N) ->
%% This is tail-recursive since the compiler is smart enough to figure
%% out that a body-recursive variant never returns, and loops forever
%% without keeping the list alive.
timer:sleep(5),
- oom_fun([lists:seq(1, 1000) | Acc0]).
+ oom_run_fun([lists:seq(1, 1000) | Acc0], N-1).
+
+oom_bin_fun(Max) when is_integer(Max) -> oom_bin_fun(#{size => Max});
+oom_bin_fun(Map) when is_map(Map) -> Map#{include_shared_binaries => true};
+oom_bin_fun(run) ->
+ oom_bin_run_fun([], 10).
+
+oom_bin_run_fun(Acc0, 0) ->
+ done;
+oom_bin_run_fun(Acc0, N) ->
+ timer:sleep(5),
+ oom_bin_run_fun([build_refc_bin(160, <<>>) | Acc0], N-1).
+
+build_refc_bin(0, Acc) ->
+ Acc;
+build_refc_bin(N, Acc) ->
+ build_refc_bin(N-1, <<Acc/binary, 0:(1000*8)>>).
+
receive_error_messages(Pid) ->
receive
@@ -4692,25 +4740,11 @@ otp_16642(Config) when is_list(Config) ->
false = is_process_alive(Pid),
ok.
-pid_ref_table_size() ->
- erts_debug:get_internal_state(pid_ref_table_size).
-
-check_pid_ref_table_size(PRTSz) ->
- receive after 500 -> ok end,
- case pid_ref_table_size() of
- PRTSz ->
- ok;
- NewPRTSz ->
- ct:fail({port_ref_table_size_mismatch, PRTSz, NewPRTSz})
- end.
-
alias_bif(Config) when is_list(Config) ->
- PRTSz = pid_ref_table_size(),
alias_bif_test(node()),
{ok, Peer, Node} = ?CT_PEER(),
alias_bif_test(Node),
stop_node(Peer, Node),
- check_pid_ref_table_size(PRTSz),
ok.
alias_bif_test(Node) ->
@@ -4755,12 +4789,10 @@ alias_bif_test(Node) ->
monitor_alias(Config) when is_list(Config) ->
- PRTSz = pid_ref_table_size(),
monitor_alias_test(node()),
{ok, Peer, Node} = ?CT_PEER(),
monitor_alias_test(Node),
stop_node(Peer, Node),
- check_pid_ref_table_size(PRTSz),
ok.
monitor_alias_test(Node) ->
@@ -4844,7 +4876,6 @@ monitor_alias_test(Node) ->
spawn_monitor_alias(Config) when is_list(Config) ->
%% Exit signals with immediate exit reasons are sent
%% in a different manner than compound exit reasons.
- PRTSz = pid_ref_table_size(),
spawn_monitor_alias_test(undefined, node(), spawn_opt, normal),
spawn_monitor_alias_test(undefined, node(), spawn_opt, make_ref()),
spawn_monitor_alias_test(undefined, node(), spawn_request, normal),
@@ -4857,7 +4888,6 @@ spawn_monitor_alias(Config) when is_list(Config) ->
spawn_monitor_alias_test(Peer3, Node3, spawn_request, normal),
{ok, Peer4, Node4} = ?CT_PEER(),
spawn_monitor_alias_test(Peer4, Node4, spawn_request, make_ref()),
- check_pid_ref_table_size(PRTSz),
ok.
spawn_monitor_alias_test(Peer, Node, SpawnType, ExitReason) ->
@@ -4998,28 +5028,6 @@ spawn_monitor_alias_test(Peer, Node, SpawnType, ExitReason) ->
ok
end.
-alias_process_exit(Config) when is_list(Config) ->
- Tester = self(),
- CreatedAliases = make_ref(),
- PRTSz = pid_ref_table_size(),
- P = spawn_link(fun () ->
- A0 = alias([explicit_unalias]),
- A1 = alias([reply]),
- A2 = monitor(process, Tester, [{alias, explicit_unalias}]),
- A3 = monitor(process, Tester, [{alias, demonitor}]),
- A4 = monitor(process, Tester, [{alias, reply_demonitor}]),
- Tester ! CreatedAliases,
- receive after infinity -> ok end,
- some_module:some_function([A0, A1, A2, A3, A4])
- end),
- receive CreatedAliases -> ok end,
- PRTSz = erts_debug:get_internal_state(pid_ref_table_size) - 5,
- unlink(P),
- exit(P, kill),
- false = is_process_alive(P),
- check_pid_ref_table_size(PRTSz),
- ok.
-
demonitor_aliasmonitor(Config) when is_list(Config) ->
{ok, Peer, Node} = ?CT_PEER(),
Fun = fun () ->
@@ -5156,6 +5164,54 @@ monitor_tag_test(Peer, Node, SpawnType, Tag, ExitReason) ->
ok
end.
+no_pid_wrap(Config) when is_list(Config) ->
+ process_flag(priority, high),
+ SOnln = erlang:system_info(schedulers_online),
+ Pid = spawn(fun () -> ok end),
+ exit(Pid, kill),
+ false = is_process_alive(Pid),
+ ChkSpwndPid = fun () ->
+ check_spawned_pid(Pid)
+ end,
+ MPs = maps:from_list(lists:map(fun (_) ->
+ {P, M} = spawn_monitor(ChkSpwndPid),
+ {M, P}
+ end, lists:seq(1, SOnln))),
+ Res = receive
+ {'DOWN', M, process, _, pid_reused} when is_map_key(M, MPs) ->
+ case erlang:system_info(wordsize) of
+ 8 ->
+ ct:fail("Process identifier reused"),
+ error;
+ 4 ->
+ {comment,
+ "Process identifer reused, but this is"
+ ++ "expected since this is a 32-bit system"}
+ end;
+ {'DOWN', _, _, _, _} = Down ->
+ ct:fail({unexpected_down, Down}),
+ error
+ after
+ 3*60*1000 ->
+ ok
+ end,
+ maps:foreach(fun (_, P) ->
+ exit(P, kill)
+ end, MPs),
+ maps:foreach(fun (_, P) ->
+ false = is_process_alive(P)
+ end, MPs),
+ Res.
+
+check_spawned_pid(OldPid) ->
+ Pid = spawn(fun () -> ok end),
+ case OldPid == Pid of
+ false ->
+ check_spawned_pid(OldPid);
+ true ->
+ exit(pid_reused)
+ end.
+
%% Internal functions
recv_msgs(N) ->
diff --git a/erts/emulator/test/sensitive_SUITE.erl b/erts/emulator/test/sensitive_SUITE.erl
index e3e753c6a5..504e70a826 100644
--- a/erts/emulator/test/sensitive_SUITE.erl
+++ b/erts/emulator/test/sensitive_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -293,7 +293,7 @@ running_trace(Config) when is_list(Config) ->
{trace,Self,in,{sensitive_SUITE,running_trace,1}} | Extra] = Messages,
case erlang:system_info(emu_type) of
ET when ET =:= debug; ET =:= asan ->
- %% F_DBG_FORCED_TRAP in erts_try_size_code_write_permission
+ %% F_DBG_FORCED_TRAP in erts_try_seize_code_mod_permission
[{trace,Self,out,{erts_internal,trace,3}},
{trace,Self,in,{erts_internal,trace,3}}] = Extra;
_ ->
diff --git a/erts/emulator/test/small_SUITE.erl b/erts/emulator/test/small_SUITE.erl
index 10dd36a2cf..c3ca540cd5 100644
--- a/erts/emulator/test/small_SUITE.erl
+++ b/erts/emulator/test/small_SUITE.erl
@@ -23,7 +23,7 @@
-export([all/0, suite/0, groups/0]).
-export([edge_cases/1,
- addition/1, subtraction/1, multiplication/1, division/1,
+ addition/1, subtraction/1, negation/1, multiplication/1, division/1,
test_bitwise/1, test_bsl/1,
element/1,
range_optimization/1]).
@@ -40,7 +40,7 @@ all() ->
groups() ->
[{p, [parallel],
[edge_cases,
- addition, subtraction, multiplication, division,
+ addition, subtraction, negation, multiplication, division,
test_bitwise, test_bsl,
element,
range_optimization]}].
@@ -158,45 +158,88 @@ add_gen_pairs() ->
gen_add_function({Name,{A,B}}) ->
APlusOne = abs(A) + 1,
BPlusOne = abs(B) + 1,
- ?Q("'@Name@'(X0, Y0) when is_integer(X0), is_integer(Y0)->
+ ?Q("'@Name@'(integer, X0, Y0) when is_integer(X0), is_integer(Y0)->
X1 = X0 rem _@APlusOne@,
Y1 = Y0 rem _@BPlusOne@,
Res = X0 + Y0,
Res = X1 + Y1,
Res = Y1 + X1,
Res = X0 + Y1,
- Res = X1 + Y0. ").
+ Res = X1 + Y0;
+ '@Name@'(number0, X, Y) when is_number(X), is_number(Y),
+ X < _@APlusOne@, 0 =< Y, Y < _@BPlusOne@ ->
+ Res = X + Y,
+ Res = Y + X;
+ '@Name@'(number0, X, Y) when is_number(X), is_number(Y),
+ X < _@APlusOne@, -_@BPlusOne@ < Y, Y < _@BPlusOne@ ->
+ Res = X + Y,
+ Res = Y + X;
+ '@Name@'(number1, X, Y) when is_number(X), is_number(Y),
+ X > -_@APlusOne@, 0 =< Y, Y < _@BPlusOne@ ->
+ Res = X + Y,
+ Res = Y + X;
+ '@Name@'(number1, X, Y) when is_number(X), is_number(Y),
+ X > -_@APlusOne@, -_@BPlusOne@ < Y, Y < _@BPlusOne@ ->
+ Res = X + Y,
+ Res = Y + X;
+ '@Name@'(number1, X, Y) when X > -_@APlusOne@, Y > -_@BPlusOne@ ->
+ Res = X + Y,
+ Res = Y + X. ").
test_addition([{Name,{A,B}}|T], Mod) ->
+ F = fun Mod:Name/3,
try
Res0 = A + B,
- Res0 = Mod:Name(A, B),
+ Res0 = F(integer, A, B),
+ Res0 = F(number0, A, B),
+ Res0 = F(number1, A, B),
Res1 = -A + B,
- Res1 = Mod:Name(-A, B),
+ Res1 = F(integer, -A, B),
+ Res1 = F(number0, -A, B),
+ Res1 = F(number1, -A, B),
Res2 = A + (-B),
- Res2 = Mod:Name(A, -B),
+ Res2 = F(integer, A, -B),
+ Res2 = F(number0, A, -B),
+ Res2 = F(number1, A, -B),
Res3 = -A + (-B),
- Res3 = Mod:Name(-A, -B)
+ Res3 = F(integer, -A, -B),
+ Res3 = F(number0, -A, -B),
+ Res3 = F(number1, -A, -B),
+
+ AbsB = abs(B),
+ Res4 = A + AbsB,
+ Res4 = F(number0, A, AbsB),
+ Res4 = F(number1, A, AbsB)
catch
C:R:Stk ->
io:format("~p failed. numbers: ~p ~p\n", [Name,A,B]),
erlang:raise(C, R, Stk)
end,
+ bad_arith(F, [a], B),
+ bad_arith(F, aa, B),
+ bad_arith(F, A, [b]),
+ bad_arith(F, A, bb),
+ bad_arith(F, {a,b}, {c,d}),
+
test_addition(T, Mod);
test_addition([], _) ->
ok.
+bad_arith(F, A, B) ->
+ {'EXIT',{badarith,_}} = catch F(number1, A, B),
+ ok.
+
%% Test that the JIT only omits the overflow check when it's safe.
subtraction(_Config) ->
_ = rand:uniform(), %Seed generator
io:format("Seed: ~p", [rand:export_seed()]),
Mod = list_to_atom(lists:concat([?MODULE,"_",?FUNCTION_NAME])),
Pairs = sub_gen_pairs(),
- io:format("~p\n", [Pairs]),
+ %% io:format("~p\n", [Pairs]),
Fs0 = gen_func_names(Pairs, 0),
Fs = [gen_sub_function(F) || F <- Fs0],
Tree = ?Q(["-module('@Mod@').",
@@ -222,38 +265,134 @@ sub_gen_pairs() ->
gen_sub_function({Name,{A,B}}) ->
APlusOne = abs(A) + 1,
BPlusOne = abs(B) + 1,
- ?Q("'@Name@'(X0, Y0) when is_integer(X0), is_integer(Y0)->
+ ?Q("'@Name@'(integer, X0, Y0) when is_integer(X0), is_integer(Y0)->
X1 = X0 rem _@APlusOne@,
Y1 = Y0 rem _@BPlusOne@,
Res = X0 - Y0,
Res = X1 - Y1,
Res = X0 - Y1,
- Res = X1 - Y0. ").
+ Res = X1 - Y0;
+ '@Name@'(number0, X, Y) when is_number(X), is_number(Y),
+ X > -_@APlusOne@, 0 =< Y, Y < _@BPlusOne@ ->
+ X - Y;
+ '@Name@'(number0, X, Y) when is_number(X), is_number(Y),
+ X > -_@APlusOne@, -_@BPlusOne@ < Y, Y < _@BPlusOne@ ->
+ X - Y;
+ '@Name@'(number1, X, Y) when is_number(X), is_number(Y),
+ X > -_@APlusOne@, 0 =< Y, Y < _@BPlusOne@ ->
+ X - Y;
+ '@Name@'(number1, X, Y) when is_number(X), is_number(Y),
+ X > -_@APlusOne@, -_@BPlusOne@ < Y, Y < _@BPlusOne@ ->
+ X - Y;
+ '@Name@'(number1, X, Y) when X > -_@APlusOne@, Y > -_@BPlusOne@ ->
+ X - Y. ").
test_subtraction([{Name,{A,B}}|T], Mod) ->
+ F = fun Mod:Name/3,
try
Res0 = A - B,
- Res0 = Mod:Name(A, B),
+ Res0 = F(integer, A, B),
+ Res0 = F(number0, A, B),
Res1 = -A - B,
- Res1 = Mod:Name(-A, B),
+ Res1 = F(integer, -A, B),
+ Res1 = F(number0, -A, B),
Res2 = A - (-B),
- Res2 = Mod:Name(A, -B),
+ Res2 = F(integer, A, -B),
+ Res2 = F(number0, A, -B),
Res3 = -A - (-B),
- Res3 = Mod:Name(-A, -B)
+ Res3 = F(integer, -A, -B),
+ Res3 = F(number0, -A, -B),
+
+ AbsB = abs(B),
+ Res4 = A - AbsB,
+ Res4 = F(integer, A, AbsB),
+ Res4 = F(number0, A, AbsB)
catch
C:R:Stk ->
io:format("~p failed. numbers: ~p ~p\n", [Name,A,B]),
erlang:raise(C, R, Stk)
end,
+ bad_arith(F, [a], B),
+ bad_arith(F, aa, B),
+ bad_arith(F, A, [b]),
+ bad_arith(F, A, bb),
+ bad_arith(F, {a,b}, {c,d}),
+
test_subtraction(T, Mod);
test_subtraction([], _) ->
ok.
%% Test that the JIT only omits the overflow check when it's safe.
+negation(_Config) ->
+ _ = rand:uniform(), %Seed generator
+ io:format("Seed: ~p", [rand:export_seed()]),
+ Mod = list_to_atom(lists:concat([?MODULE,"_",?FUNCTION_NAME])),
+ Integers = neg_gen_integers(),
+ %% io:format("~p\n", [Pairs]),
+ Fs0 = gen_func_names(Integers, 0),
+ Fs = [gen_neg_function(F) || F <- Fs0],
+ Tree = ?Q(["-module('@Mod@').",
+ "-compile([export_all,nowarn_export_all])."]) ++ Fs,
+ merl:print(Tree),
+ {ok,_Bin} = merl:compile_and_load(Tree, []),
+ test_negation(Fs0, Mod),
+ ok.
+
+neg_gen_integers() ->
+ {MinSmall, MaxSmall} = determine_small_limits(0),
+
+ N = 1000,
+ M = MaxSmall + N div 2,
+ Ns = [M - rand:uniform(N) || _ <- lists:seq(1, 75)],
+
+ lists:seq(MinSmall-2, MinSmall+2) ++ lists:seq(MaxSmall-2, MaxSmall+2) ++ Ns.
+
+gen_neg_function({Name,A}) ->
+ APlusOne = abs(A) + 1,
+ ?Q("'@Name@'(integer0, X0) when is_integer(X0) ->
+ X1 = X0 rem _@APlusOne@,
+ Res = -X0,
+ Res = -X1;
+ '@Name@'(integer1, X) when is_integer(X), X > -_@APlusOne@ ->
+ -X;
+ '@Name@'(integer2, X) when is_integer(X), X > -_@APlusOne@ ->
+ -X;
+ '@Name@'(number, X) when is_number(X), X > -_@APlusOne@ ->
+ -X;
+ '@Name@'(number, X) when is_number(X), X > -_@APlusOne@ ->
+ -X;
+ '@Name@'(number, X) when is_number(X) ->
+ -X. ").
+
+test_negation([{Name,A}|T], Mod) ->
+ F = fun Mod:Name/2,
+ try
+ Res0 = -A,
+ Res0 = F(integer0, A),
+ Res0 = F(integer1, A),
+ Res0 = F(integer2, A),
+ Res0 = F(number, A),
+
+ Res1 = A,
+ Res1 = F(integer0, -A),
+ Res1 = F(integer1, -A),
+ Res1 = F(integer2, -A),
+ Res1 = F(number, -A)
+ catch
+ C:R:Stk ->
+ io:format("~p failed. numbers: ~p\n", [Name,A]),
+ erlang:raise(C, R, Stk)
+ end,
+
+ test_negation(T, Mod);
+test_negation([], _) ->
+ ok.
+
+%% Test that the JIT only omits the overflow check when it's safe.
multiplication(_Config) ->
_ = rand:uniform(), %Seed generator
io:format("Seed: ~p", [rand:export_seed()]),
@@ -295,7 +434,7 @@ gen_mul_function({Name,{A,B}}) ->
BPlusOne = B + 1,
NumBitsA = num_bits(A),
NumBitsB = num_bits(B),
- ?Q("'@Name@'(X0, Y0, More) when is_integer(X0), is_integer(Y0)->
+ ?Q("'@Name@'(X0, Y0, More) when is_integer(X0), is_integer(Y0), is_boolean(More) ->
X1 = X0 rem _@APlusOne@,
Y1 = Y0 rem _@BPlusOne@,
Res = X0 * Y0,
@@ -311,20 +450,36 @@ gen_mul_function({Name,{A,B}}) ->
Res = X2 * Y1;
true ->
Res
- end. ").
+ end;
+ '@Name@'(X, Y, number) when -_@APlusOne@ < X, X < _@APlusOne@, -_@BPlusOne@ < Y, Y < _@BPlusOne@ ->
+ Res = X * Y,
+ Res = Y * X;
+ '@Name@'(X, fixed, number) when -_@APlusOne@ < X, X < _@APlusOne@ ->
+ X * _@B@;
+ '@Name@'(fixed, Y, number) when -_@BPlusOne@ < Y, Y < _@BPlusOne@ ->
+ _@A@ * Y. ").
test_multiplication([{Name,{A,B}}|T], Mod) ->
+ F = fun Mod:Name/3,
try
Res0 = A * B,
%% io:format("~p * ~p = ~p; size = ~p\n",
%% [A,B,Res0,erts_debug:flat_size(Res0)]),
- Res0 = Mod:Name(A, B, true),
- Res0 = Mod:Name(-A, -B, false),
+ Res0 = F(A, B, true),
+ Res0 = F(-A, -B, false),
+ Res0 = F(A, B, number),
+ Res0 = F(fixed, B, number),
+ Res0 = F(A, fixed, number),
+ Res0 = F(-A, -B, number),
Res1 = -(A * B),
- Res1 = Mod:Name(-A, B, false),
- Res1 = Mod:Name(A, -B, false)
+ Res1 = F(-A, B, false),
+ Res1 = F(A, -B, false),
+ Res1 = F(-A, B, number),
+ Res1 = F(A, -B, number),
+ Res1 = F(-A, fixed, number),
+ Res1 = F(fixed, -B, number)
catch
C:R:Stk ->
io:format("~p failed. numbers: ~p ~p\n", [Name,A,B]),
@@ -367,16 +522,22 @@ div_gen_pairs() ->
NumBitsMaxSmall = num_bits(MaxSmall),
%% Generate random pairs of smalls.
- Pairs0 = [{rand:uniform(MaxSmall),rand:uniform(MaxSmall)} ||
+ Pairs0 = [{rand:uniform(MaxSmall) * rand_sign(),
+ rand:uniform(MaxSmall) * rand_sign()} ||
_ <- lists:seq(1, 75)],
Pairs1 = [{rand:uniform(MaxSmall), N} ||
- N <- [-3,-2,-1,1,2,3,5,17,63,64,1111,22222]] ++ Pairs0,
+ N <- [-4,-3,-2,-1,1,2,3,5,17,63,64,1111,22222]] ++ Pairs0,
%% Generate pairs of numbers whose product are bignums.
[{rand:uniform(MaxSmall),1 bsl Pow} ||
Pow <- lists:seq(NumBitsMaxSmall - 4, NumBitsMaxSmall - 1)] ++ Pairs1.
+rand_sign() ->
+ case rand:uniform() < 0.2 of
+ true -> -1;
+ false -> 1
+ end.
gen_div_function({Name,{A,B}}) ->
APlusOne = abs(A) + 1,
@@ -405,6 +566,12 @@ gen_div_function({Name,{A,B}}) ->
R = X rem Y,
Q = X div Y,
{Q, R};
+ '@Name@'(integer3, X, fixed) when is_integer(X), -_@APlusOne@ < X, X < _@APlusOne@ ->
+ Y = _@B@,
+ Q = X div Y,
+ put(prevent_div_rem_fusion, Q),
+ R = X rem Y,
+ {Q, R};
'@Name@'(number0, X, Y) when -_@APlusOne@ < X, X < _@APlusOne@,
-_@BPlusOne@ < Y, Y < _@BPlusOne@ ->
Q = X div Y,
@@ -414,6 +581,22 @@ gen_div_function({Name,{A,B}}) ->
-_@BPlusOne@ < Y, Y < _@BPlusOne@ ->
R = X rem Y,
Q = X div Y,
+ {Q, R};
+ '@Name@'(number2, X, fixed) when -_@APlusOne@ < X, X < _@APlusOne@ ->
+ Y = _@B@,
+ Q = X div Y,
+ R = X rem Y,
+ {Q, R};
+ '@Name@'(number3, X, fixed) when -_@APlusOne@ < X, X < _@APlusOne@ ->
+ Y = _@B@,
+ R = X rem Y,
+ Q = X div Y,
+ {Q, R};
+ '@Name@'(number4, X, fixed) when -_@APlusOne@ < X, X < _@APlusOne@ ->
+ Y = _@B@,
+ Q = X div Y,
+ put(prevent_div_rem_fusion, Q),
+ R = X rem Y,
{Q, R}. ").
test_division([{Name,{A,B}}|T], Mod) ->
@@ -423,8 +606,12 @@ test_division([{Name,{A,B}}|T], Mod) ->
Res0 = F(integer0, A, B),
Res0 = F(integer1, A, fixed),
Res0 = F(integer2, A, fixed),
+ Res0 = F(integer3, A, fixed),
Res0 = F(number0, A, B),
- Res0 = F(number1, A, B)
+ Res0 = F(number1, A, B),
+ Res0 = F(number2, A, fixed),
+ Res0 = F(number3, A, fixed),
+ Res0 = F(number4, A, fixed)
catch
C:R:Stk ->
io:format("~p failed. numbers: ~p ~p\n", [Name,A,B]),
@@ -496,31 +683,59 @@ gen_bitwise_function({Name,{A,B}}) ->
Y1 = Y0 rem _@BPlusOne@,
AndRes = X0 band Y0,
+ AndRes = Y0 band X0,
AndRes = X1 band Y1,
AndRes = Y1 band X1,
AndRes = X0 band Y1,
AndRes = X1 band Y0,
OrRes = X0 bor Y0,
+ OrRes = Y0 bor X0,
OrRes = X1 bor Y1,
OrRes = Y1 bor X1,
OrRes = X0 bor Y1,
OrRes = X1 bor Y0,
XorRes = X0 bxor Y0,
+ XorRes = Y0 bxor X0,
XorRes = X1 bxor Y1,
XorRes = Y1 bxor X1,
XorRes = X0 bxor Y1,
XorRes = X1 bxor Y0,
- {AndRes, OrRes, XorRes}. ").
+ {AndRes, OrRes, XorRes};
+ '@Name@'(X0, fixed) when is_integer(X0) ->
+ X1 = X0 rem _@APlusOne@,
+
+ AndRes = X0 band _@B@,
+ AndRes = _@B@ band X0,
+ AndRes = X1 band _@B@,
+ AndRes = _@B@ band X1,
+
+ OrRes = X0 bor _@B@,
+ OrRes = _@B@ bor X0,
+ OrRes = X1 bor _@B@,
+ OrRes = _@B@ bor X1,
+
+ XorRes = X0 bxor _@B@,
+ XorRes = _@B@ bxor X0,
+ XorRes = X1 bxor _@B@,
+ XorRes = _@B@ bxor X1,
+
+ {AndRes, OrRes, XorRes}.
+ ").
test_bitwise([{Name,{A,B}}|T], Mod) ->
try
test_bitwise_1(A, B, Mod, Name),
test_bitwise_1(-A, B, Mod, Name),
test_bitwise_1(A, -B, Mod, Name),
- test_bitwise_1(-A, -B, Mod, Name)
+ test_bitwise_1(-A, -B, Mod, Name),
+
+ AndRes = A band B,
+ OrRes = A bor B,
+ XorRes = A bxor B,
+ {AndRes, OrRes, XorRes} = Mod:Name(A, fixed)
catch
C:R:Stk ->
io:format("~p failed. numbers: ~p ~p\n", [Name,A,B]),
@@ -564,15 +779,42 @@ bsl_gen_pairs() ->
gen_bsl_function({Name,{N,S}}) ->
Mask = (1 bsl num_bits(N)) - 1,
- ?Q("'@Name@'(N0) ->
+ ?Q("'@Name@'(N0, fixed) ->
+ Res = N0 bsl _@S@,
N = N0 band _@Mask@,
- N bsl _@S@. ").
+ Res = N0 bsl _@S@,
+ Res = N bsl _@S@;
+ '@Name@'(N0, S) when is_integer(S), 0 =< S, S =< _@S@ ->
+ Res = N0 bsl S,
+ N = N0 band _@Mask@,
+ Res = N0 bsl S,
+ Res = N bsl S. ").
test_bsl([{Name,{N,S}}|T], Mod) ->
- Res = N bsl S,
- try Mod:Name(N) of
- Res ->
- ok
+ try
+ Res0 = N bsl S,
+ Res0 = Mod:Name(N, fixed),
+
+ if
+ S >= 0 ->
+ Res1 = N bsl S,
+ Res1 = Mod:Name(N, S),
+
+ N = Mod:Name(N, 0),
+
+ if
+ S >= 2 ->
+ Res2 = N bsl (S - 1),
+ Res2 = Mod:Name(N, S - 1),
+
+ Res3 = N bsl (S - 2),
+ Res3 = Mod:Name(N, S - 2);
+ true ->
+ ok
+ end;
+ S < 0 ->
+ {'EXIT', {function_clause,_}} = catch Mod:Name(N, S)
+ end
catch
C:R:Stk ->
io:format("~p failed. numbers: ~p ~p\n", [Name,N,S]),
diff --git a/erts/emulator/test/statistics_SUITE.erl b/erts/emulator/test/statistics_SUITE.erl
index c230aa9f19..014c0a114e 100644
--- a/erts/emulator/test/statistics_SUITE.erl
+++ b/erts/emulator/test/statistics_SUITE.erl
@@ -382,7 +382,7 @@ run_scheduler_wall_time_test(Type) ->
Pid
end,
StartDirtyHog = fun(Func) ->
- F = fun () ->
+ F = fun() ->
erts_debug:Func(alive_waitexiting,
MeMySelfAndI)
end,
@@ -470,7 +470,7 @@ online_statistics(Stats) ->
DirtyCPUSchedulersOnline = erlang:system_info(dirty_cpu_schedulers_online),
DirtyIOSchedulersOnline = erlang:system_info(dirty_io_schedulers),
SortedStats = lists:sort(Stats),
- ct:pal("Stats: ~p~n", [SortedStats]),
+ ct:log("Stats: ~p~n", [SortedStats]),
SchedulersStats =
lists:sublist(SortedStats, 1, SchedulersOnline),
DirtyCPUSchedulersStats =
diff --git a/erts/emulator/test/trace_call_memory_SUITE.erl b/erts/emulator/test/trace_call_memory_SUITE.erl
new file mode 100644
index 0000000000..59d1873e49
--- /dev/null
+++ b/erts/emulator/test/trace_call_memory_SUITE.erl
@@ -0,0 +1,366 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(trace_call_memory_SUITE).
+-author("maximfca@gmail.com").
+
+
+%% Test server callbacks
+-export([
+ suite/0,
+ all/0,
+ init_per_testcase/2,
+ end_per_testcase/2
+]).
+
+%% Test cases exports
+-export([
+ basic/0, basic/1,
+ late_trace/0, late_trace/1,
+ skip/0, skip/1,
+ message/0, message/1,
+ parallel_map/0, parallel_map/1,
+ trace_all/0, trace_all/1,
+ spawn_memory/0, spawn_memory/1, spawn_memory_internal/1,
+ spawn_memory_lambda/1,
+ conflict_traces/0, conflict_traces/1,
+ big_words/0, big_words/1
+]).
+
+init_per_testcase(_Case, Config) ->
+ Config.
+
+end_per_testcase(basic, _Config) ->
+ erlang:trace_pattern({?MODULE, alloc_2tup, 0}, false, [call_memory]),
+ erlang:trace(self(), false, [call]);
+end_per_testcase(late_trace, _Config) ->
+ erlang:trace_pattern({?MODULE, '_', '_'}, false, [call_memory]);
+end_per_testcase(skip, _Config) ->
+ erlang:trace_pattern({?MODULE, '_', '_'}, false, [call_memory]),
+ erlang:trace(self(), false, [call]);
+end_per_testcase(message, _Config) ->
+ erlang:trace_pattern({?MODULE, receive_message, 0}, false, [call_memory]),
+ erlang:trace(all, false, [call]);
+end_per_testcase(parallel_map, _Config) ->
+ erlang:trace_pattern({?MODULE, '_', '_'}, false, [call_memory]),
+ erlang:trace(all, false, [call]);
+end_per_testcase(trace_all, _Config) ->
+ erlang:trace_pattern({'_', '_', '_'}, false, [call_memory]),
+ erlang:trace(all, false, [call]);
+end_per_testcase(spawn_memory, _Config) ->
+ erlang:trace_pattern({?MODULE, spawn_memory_internal, '_'}, false, [call_memory]),
+ erlang:trace(self(), false, [call]);
+end_per_testcase(spawn_memory_lambda, _Config) ->
+ erlang:trace_pattern({erlang, apply, 2}, false, [call_memory]),
+ erlang:trace(self(), false, [call]);
+end_per_testcase(conflict_traces, _Config) ->
+ erlang:trace_pattern({?MODULE, '_', '_'}, false, [call_memory]),
+ erlang:trace(self(), false, [call]);
+end_per_testcase(big_words, _Config) ->
+ erlang:trace_pattern({?MODULE,alloc_tuples,2}, false, [call_memory]),
+ erlang:trace(self(), false, [call]).
+
+
+suite() ->
+ [{timetrap, {seconds, 60}}].
+
+all() ->
+ [basic, late_trace, skip, message, parallel_map, trace_all, spawn_memory,
+ spawn_memory_lambda, conflict_traces, big_words].
+
+%% allocation functions
+alloc_2tup() ->
+ {self(),self()}. %% a non literal
+
+receive_message() ->
+ receive
+ Msg -> Msg
+ end.
+
+basic() ->
+ [{doc, "Assert that two-tuple allocated 3-word structure on the heap"}].
+
+basic(Config) when is_list(Config) ->
+ Self = self(),
+ Traced = {?MODULE, alloc_2tup, 0},
+ 1 = erlang:trace_pattern(Traced, true, [call_memory]),
+ 1 = erlang:trace(Self, true, [call]),
+ alloc_2tup(),
+ {call_memory, [{Self, 1, 3}]} = erlang:trace_info(Traced, call_memory),
+ alloc_2tup(),
+ {call_memory, [{Self, 2, 6}]} = erlang:trace_info(Traced, call_memory),
+ %% test that GC works correctly
+ erlang:garbage_collect(),
+ alloc_2tup(),
+ {call_memory, [{Self, 3, 9}]} = erlang:trace_info(Traced, call_memory),
+ 1 = erlang:trace(Self, false, [call]),
+ 1 = erlang:trace_pattern(Traced, false, [call_memory]).
+
+late_trace() ->
+ [{doc, "Tests that garbage_collect call done before tracing is enabled works as expected"}].
+
+late_trace(Config) when is_list(Config) ->
+ Control = self(),
+ Pid = spawn_link(
+ fun () ->
+ _ = late_trace_inner(),
+ 1 = erlang:trace_pattern({?MODULE, late_trace_inner, 0}, true, [call_memory]),
+ 1 = erlang:trace(self(), true, [call]),
+ _ = late_trace_inner(),
+ Control ! continue,
+ receive
+ stop ->
+ 1 = erlang:trace_pattern({?MODULE, late_trace_inner, 0}, false, [call_memory])
+ end
+ end),
+ NonLiteral = non_literal_9(),
+ MRef = monitor(process, Pid),
+ Pid ! NonLiteral,
+ Pid ! NonLiteral,
+ receive continue -> ok end,
+ {call_memory, [{Pid, 1, 12}]} = erlang:trace_info({?MODULE, late_trace_inner, 0}, call_memory),
+ Pid ! stop,
+ receive {'DOWN', MRef, process, Pid, _} -> ok end.
+
+late_trace_inner() ->
+ Ref = alloc_2tup(), %% 3 words
+ receive _Msg -> Ref end. %% 9 more words
+
+non_literal_9() ->
+ %% 9 words in total: 6 words for the list, 3 for the tuple
+ erlang:insert_element(2, erlang:insert_element(1, {}, atom), lists:seq(1, 3)).
+
+skip() ->
+ [{doc, "Tests that skipped trace for a function accumulates in an upper level caller"}].
+
+skip(Config) when is_list(Config) ->
+ Self = self(),
+ 1 = erlang:trace_pattern({?MODULE, upper, 0}, true, [call_memory]),
+ 1 = erlang:trace_pattern({?MODULE, lower, 1}, true, [call_memory]),
+ 1 = erlang:trace(Self, true, [call]),
+ upper(),
+ {call_memory, [{Self, 1, 8 * 2}]} = erlang:trace_info({?MODULE, lower, 1}, call_memory),
+ {call_memory, [{Self, 1, 3 + 3 + 6}]} = erlang:trace_info({?MODULE, upper, 0}, call_memory).
+
+upper() ->
+ Ref = alloc_2tup(), %% 3
+ X = middle(Ref), %% 3 in middle, and 8 in lower (but lower has its own accounting)
+ {1, 2, 3, X, Ref}. %% extra 6 words (5 elements and 1 tuple size)
+
+middle(_Ref) ->
+ Ref2 = alloc_2tup(), %% 3 - but accounted in 'upper/0' instead
+ lower(8),
+ Ref2.
+
+lower(Max) ->
+ lists:seq(1, Max).
+
+message() ->
+ [{doc, "Assert that receiving a message results in memory trace"}].
+
+message(Config) when is_list(Config) ->
+ Control = self(),
+ Traced = {?MODULE, receive_message, 0},
+ NonLiteral = non_literal_9(),
+ 1 = erlang:trace_pattern(Traced, true, [call_memory]),
+ 1 = erlang:trace(self(), true, [call, set_on_first_spawn]),
+ Pid = spawn_link(
+ fun Wrap() ->
+ receive
+ pre ->
+ receive_message(),
+ Control ! done,
+ Wrap();
+ stop ->
+ ok
+ end
+ end),
+ 1 = erlang:trace(self(), false, [call, set_on_first_spawn]),
+ %% first, check that sending a (non-matched) message does not result in a negative allocation
+ Pid ! NonLiteral,
+ timer:sleep(500),
+ {call_memory, []} = erlang:trace_info(Traced, call_memory),
+ %% enter the receive_message/0
+ Pid ! pre,
+ %% wait for 'done' response
+ receive done -> ok end,
+ {call_memory, [{Pid, 1, 9}]} = erlang:trace_info(Traced, call_memory),
+ %% once again, just in case, to verify that decrementing "allocated" worked
+ Pid ! pre,
+ Pid ! NonLiteral,
+ receive done -> ok end,
+ {call_memory, [{Pid, 2, 18}]} = erlang:trace_info(Traced, call_memory),
+ 1 = erlang:trace_pattern(Traced, false, [call_memory]).
+
+parallel_map() ->
+ [{doc, "Test memory profiling with spawned processes"}].
+
+parallel_map(Config) when is_list(Config) ->
+ _ = erlang:trace_pattern({?MODULE, '_', '_'}, true, [call_memory]),
+ 1 = erlang:trace(self(), true, [call, set_on_spawn]),
+ Pid = spawn_link(fun do_parallel/0),
+ MRef = monitor(process, Pid),
+ Pid ! pre_stop,
+ Pid ! {stop, 1},
+ receive
+ {'DOWN', MRef, process, Pid, _} ->
+ %% alloc_2tup called 3 times (once per process)
+ {call_memory, [{_, 1, 3}, {_, 1, 3}, {_, 1, 3}]} =
+ erlang:trace_info({?MODULE, alloc_2tup, 0}, call_memory),
+ %% receive_message called 8 times (3 from "Allocs", 3 from "Grand", and 2 from the runner)
+ %% from 7 processes, but only 3*6 Grand processes and 3 words for the runner are on the heap
+ {call_memory, RecvMsg} = erlang:trace_info({?MODULE, receive_message, 0}, call_memory),
+ {7, 8, 21} = collapse_procs(RecvMsg)
+ end.
+
+do_parallel() ->
+ Allocs = [spawn_link(fun() -> alloc_2tup(), receive_message() end) || _ <- lists:seq(1, 3)],
+ Grand = [spawn_link(fun() -> receive_message() end) || _ <- lists:seq(1, 3)],
+ pre_stop = receive_message(),
+ [P ! {atom, <<"1234">>} || P <- Grand], %% 6 words on the heap: 3 for binary, 3 for tuple
+ {stop, 1} = receive_message(),
+ [exit(P, normal) || P <- Allocs].
+
+trace_all() ->
+ [{doc, "Enables memory tracing for all processes, mainly ensuring there are no core dumps"}].
+
+trace_all(Config) when is_list(Config) ->
+ _ = erlang:trace_pattern({'_', '_', '_'}, true, [call_memory]),
+ _ = erlang:trace(all, true, [call]),
+ do_heavy_lifting(),
+ _ = erlang:trace(all, false, [call]),
+ Profile = profile_memory(),
+ %% it'd be better to introduce more checks, but for now see that
+ %% at least some action happened
+ true = is_map_key(application_controller, Profile).
+
+do_heavy_lifting() ->
+ {ok, []} = application:ensure_all_started(kernel).
+
+profile_memory() ->
+ profile_modules(code:all_loaded(), #{}).
+
+profile_modules([], Acc) ->
+ Acc;
+profile_modules([{Module, _} | Tail], Acc) ->
+ Funcs = try Module:module_info(functions)
+ catch error:undef -> [] % ignore homebrewed without module_info
+ end,
+ case profile_functions(Module, Funcs, #{}) of
+ Empty when Empty =:= #{} ->
+ profile_modules(Tail, Acc);
+ NonEmpty ->
+ profile_modules(Tail, Acc#{Module => NonEmpty})
+ end.
+
+profile_functions(_Module, [], Acc) ->
+ Acc;
+profile_functions(Module, [{Fun, Arity} | Tail], Acc) ->
+ case erlang:trace_info({Module, Fun, Arity}, call_memory) of
+ {call_memory, Skip} when Skip =:= []; Skip =:= false ->
+ profile_functions(Module, Tail, Acc);
+ {call_memory, Mem} ->
+ profile_functions(Module, Tail, Acc#{{Fun, Arity} => collapse_procs(Mem)})
+ end.
+
+collapse_procs(Processes) ->
+ lists:foldl(
+ fun ({_Pid, Calls, Words}, {Procs, TotCalls, TotWords}) ->
+ {Procs + 1, TotCalls + Calls, TotWords + Words}
+ end, {0, 0, 0}, Processes).
+
+spawn_memory() ->
+ [{doc, "Tests that when a process is spawned, initial memory correctly attributed to the initial call"}].
+
+spawn_memory(Config) when is_list(Config) ->
+ LostSharing = lists:seq(1, 16),
+ _ = erlang:trace_pattern({?MODULE, spawn_memory_internal, 1}, true, [call_memory]),
+ 1 = erlang:trace(self(), true, [call, set_on_first_spawn]),
+ Pid = erlang:spawn_link(?MODULE, spawn_memory_internal, [LostSharing]),
+ MRef = monitor(process, Pid),
+ receive {'DOWN', MRef, process, Pid, _} -> ok end,
+ 1 = erlang:trace(self(), false, [all]),
+ %% 16-elements list translates into 34-words for spawn
+ {call_memory, [{Pid, 1, 34}]} = erlang:trace_info({?MODULE, spawn_memory_internal, 1}, call_memory).
+
+spawn_memory_lambda(Config) when is_list(Config) ->
+ %% check that tracing with context captured through lambda also works - but reports erlang:apply
+ LostSharing = lists:seq(1, 16),
+ _ = erlang:trace_pattern({erlang, apply, 2}, true, [call_memory]),
+ 1 = erlang:trace(self(), true, [call, set_on_first_spawn]),
+ Pid = erlang:spawn_link(fun () -> spawn_memory_internal(LostSharing) end),
+ MRef = monitor(process, Pid),
+ receive {'DOWN', MRef, process, Pid, _} -> ok end,
+ 1 = erlang:trace(self(), false, [all]),
+ %% 16-elements list translates into 34-words for spawn, and 6 more words for apply itself
+ {call_memory, [{Pid, 1, 40}]} = erlang:trace_info({erlang, apply, 2}, call_memory).
+
+spawn_memory_internal(Array) ->
+ Array.
+
+conflict_traces() ->
+ [{doc, "Verify that call_time and call_memory don't break each other"}].
+
+conflict_traces(Config) when is_list(Config) ->
+ Self = self(),
+ Traced = {?MODULE, alloc_2tup, 0},
+ %% start call_memory trace
+ 1 = erlang:trace_pattern(Traced, true, [call_memory]),
+ 1 = erlang:trace(Self, true, [call]),
+ alloc_2tup(),
+ {call_memory, [{Self, 1, 3}]} = erlang:trace_info(Traced, call_memory),
+ %% start/stop call_time trace
+ 1 = erlang:trace_pattern(Traced, true, [call_time]),
+ alloc_2tup(), %% this goes to both time and memory
+ {call_time, [{Self, 1, _, _}]} = erlang:trace_info(Traced, call_time),
+ 1 = erlang:trace_pattern(Traced, false, [call_time]),
+ %% memory is unaffected
+ alloc_2tup(),
+ {call_memory, [{Self, 3, 9}]} = erlang:trace_info(Traced, call_memory),
+ %%
+ 1 = erlang:trace(Self, false, [call]),
+ 1 = erlang:trace_pattern(Traced, false, [call_memory]).
+
+big_words() ->
+ [{doc, "Test that Words counter can be a bignum on 32-bit"}].
+
+big_words(Config) when is_list(Config) ->
+ Self = self(),
+ Traced = {?MODULE, alloc_tuples, 2},
+ 1 = erlang:trace_pattern(Traced, true, [call_memory]),
+ 1 = erlang:trace(Self, true, [call]),
+ Words = (1 bsl 27),
+ case {erts_debug:size(Words), 8*erlang:system_info(wordsize)} of
+ {2, 32} -> ok;
+ {0, 64} -> ok
+ end,
+ TupleSz = 1023,
+ TupleCnt = Words div (TupleSz + 1),
+ alloc_tuples(TupleCnt, TupleSz),
+ CallCnt = TupleCnt + 1,
+ {call_memory, [{Self, CallCnt, Words}]} = erlang:trace_info(Traced, call_memory),
+ 1 = erlang:trace(Self, false, [call]),
+ 1 = erlang:trace_pattern(Traced, false, [call_memory]).
+
+
+alloc_tuples(0, _) ->
+ ok;
+alloc_tuples(N, TupleSz) ->
+ erlang:make_tuple(TupleSz, []),
+ alloc_tuples(N-1, TupleSz).
diff --git a/erts/emulator/test/trace_call_time_SUITE.erl b/erts/emulator/test/trace_call_time_SUITE.erl
index 2da660c881..26687129bb 100644
--- a/erts/emulator/test/trace_call_time_SUITE.erl
+++ b/erts/emulator/test/trace_call_time_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -71,13 +71,15 @@
bif/1, nif/1]).
init_per_testcase(_Case, Config) ->
- erlang:trace_pattern({'_','_','_'}, false, [local,meta,call_time,call_count]),
+ erlang:trace_pattern({'_','_','_'}, false,
+ [local,meta,call_time,call_count,call_memory]),
erlang:trace_pattern(on_load, false, [local,meta,call_time,call_count]),
timer:now_diff(now(),now()),
Config.
end_per_testcase(_Case, _Config) ->
- erlang:trace_pattern({'_','_','_'}, false, [local,meta,call_time,call_count]),
+ erlang:trace_pattern({'_','_','_'}, false,
+ [local,meta,call_time,call_count,call_memory]),
erlang:trace_pattern(on_load, false, [local,meta,call_time,call_count]),
erlang:trace(all, false, [all]),
ok.
@@ -96,7 +98,7 @@ all() ->
%% Tests basic call time trace
basic(Config) when is_list(Config) ->
P = erlang:trace_pattern({'_','_','_'}, false, [call_time]),
- M = 1000,
+ M = 900,
%%
1 = erlang:trace_pattern({?MODULE,seq, '_'}, true, [call_time]),
2 = erlang:trace_pattern({?MODULE,seq_r,'_'}, true, [call_time]),
@@ -298,12 +300,14 @@ combo(Config) when is_list(Config) ->
2 = erlang:trace_pattern({?MODULE,seq_r,'_'}, true, [call_time]),
2 = erlang:trace_pattern({?MODULE,seq_r,'_'}, MetaMs, [{meta,MetaTracer}]),
2 = erlang:trace_pattern({?MODULE,seq_r,'_'}, true, [call_count]),
+ 2 = erlang:trace_pattern({?MODULE,seq_r,'_'}, true, [call_memory]),
% bifs
2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, [], [local]),
2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, true, [call_time]),
2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, MetaMs, [{meta,MetaTracer}]),
2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, true, [call_count]),
+ 2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, true, [call_memory]),
1 = erlang:trace(Self, true, [{tracer,LocalTracer} | Flags]),
%%
@@ -319,12 +323,14 @@ combo(Config) when is_list(Config) ->
%% check empty trace_info for ?MODULE:seq_r/3
{all,[_|_]=TraceInfo} = erlang:trace_info({?MODULE,seq_r,3}, all),
+ io:format("TraceInfo=~p\n",[TraceInfo]),
{value,{traced,local}} = lists:keysearch(traced, 1, TraceInfo),
{value,{match_spec,[]}} = lists:keysearch(match_spec, 1, TraceInfo),
{value,{meta,MetaTracer}} = lists:keysearch(meta, 1, TraceInfo),
{value,{meta_match_spec,MetaMs}} = lists:keysearch(meta_match_spec, 1, TraceInfo),
{value,{call_count,0}} = lists:keysearch(call_count, 1, TraceInfo),
{value,{call_time,[]}} = lists:keysearch(call_time, 1, TraceInfo),
+ {value,{call_memory,[]}} = lists:keysearch(call_memory, 1, TraceInfo),
%% check empty trace_info for erlang:term_to_binary/1
{all, [_|_] = TraceInfoBif} = erlang:trace_info({erlang, term_to_binary, 1}, all),
@@ -334,6 +340,7 @@ combo(Config) when is_list(Config) ->
{value,{meta_match_spec,MetaMs}} = lists:keysearch(meta_match_spec, 1, TraceInfoBif),
{value,{call_count,0}} = lists:keysearch(call_count, 1, TraceInfoBif),
{value,{call_time,[]}} = lists:keysearch(call_time, 1, TraceInfoBif),
+ {value,{call_memory,[]}} = lists:keysearch(call_memory, 1, TraceInfoBif),
%%
[3,2,1] = seq_r(1, 3, fun(X) -> X+1 end),
@@ -405,11 +412,21 @@ bif(Config) when is_list(Config) ->
Pid = setup(),
{L, Tot1} = execute(Pid, fun() -> with_bif(M) end),
- {call_time,[{Pid,_,S,Us}]} = erlang:trace_info({?MODULE,with_bif,1}, call_time),
- T1 = Tot1 - (S*1000000 + Us),
-
- ok = check_trace_info({erlang, binary_to_term, 1}, [{Pid, M - 1, 0, 0}], T1/2),
- ok = check_trace_info({erlang, term_to_binary, 1}, [{Pid, M - 1, 0, 0}], T1/2),
+ {call_time,[{Pid,_,S,Us}]}=WB = erlang:trace_info({?MODULE,with_bif,1}, call_time),
+ T1 = Tot1 - (S*1000_000 + Us),
+
+ B2T = erlang:trace_info({erlang, binary_to_term, 1}, call_time),
+ T2B = erlang:trace_info({erlang, term_to_binary, 1}, call_time),
+ io:format("with_bif = ~p\n", [WB]),
+ io:format("binary_to_term = ~p\n", [B2T]),
+ io:format("term_to_binary = ~p\n", [T2B]),
+ Sum = us(WB) + us(B2T) + us(T2B),
+ io:format("Sum = ~p us\n", [Sum]),
+ io:format("Tot1 = ~p us Diff = ~p us\n", [Tot1, Tot1-Sum]),
+ ok = check_trace_info_ret({erlang, binary_to_term, 1}, [{Pid, M-1, 0, 0}],
+ T1/2, B2T),
+ ok = check_trace_info_ret({erlang, term_to_binary, 1}, [{Pid, M-1, 0, 0}],
+ T1/2, T2B),
% disable term2binary
@@ -425,6 +442,9 @@ bif(Config) when is_list(Config) ->
Pid ! quit,
ok.
+us({call_time,[{_,_,S,Us}]}) ->
+ S*1000_000 + Us.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Tests tracing of nifs
@@ -705,23 +725,35 @@ seq_r(Start, Stop, Succ, R) ->
seq_r(Succ(Start), Stop, Succ, [Start | R]).
% Check call time tracing data and print mismatches
-check_trace_info(Mfa, [{Pid, ExpectedC,_,_}] = Expect, Time) ->
- {call_time,[{Pid,C,S,Us}]} = erlang:trace_info(Mfa, call_time),
+check_trace_info(Mfa, Expect, Time) ->
+ check_trace_info_ret(Mfa, Expect, Time, erlang:trace_info(Mfa, call_time)).
+
+check_trace_info_ret(Mfa, [{Pid, ExpectedC,_,_}] = Expect, Time, TraceInfo) ->
+ {call_time,[{Pid,Cnt,S,Us}]} = TraceInfo,
+
{Mod, Name, Arity} = Mfa,
IsBuiltin = erlang:is_builtin(Mod, Name, Arity),
- if
+ ok = if
%% Call count on BIFs may exceed number of calls as they often trap to
%% themselves.
- IsBuiltin, C >= ExpectedC, S >= 0, Us >= 0,
- abs(1 - Time/(S*1000000 + Us)) < ?R_ERROR;
- abs(Time - S*1000000 - Us) < ?US_ERROR ->
+ IsBuiltin, Cnt >= ExpectedC ->
ok;
- not IsBuiltin, C =:= ExpectedC, S >= 0, Us >= 0,
- abs(1 - Time/(S*1000000 + Us)) < ?R_ERROR;
- abs(Time - S*1000000 - Us) < ?US_ERROR ->
+ not IsBuiltin, Cnt =:= ExpectedC ->
+ ok;
+ true ->
+ io:format("Expected ~p -> {call_time, ~p}~n"
+ " - got call count ~w~p",
+ [Mfa, Expect, Cnt]),
+ count_error
+ end,
+
+ true = (S >= 0),
+ true = (Us >= 0),
+ Sum = S*1000_000 + Us,
+ if
+ abs(1 - Time/Sum) < ?R_ERROR; abs(Time - Sum) < ?US_ERROR ->
ok;
true ->
- Sum = S*1000000 + Us,
io:format("Expected ~p -> {call_time, ~p (Time ~p us)}~n - got ~w "
"s. ~w us. = ~w us. - ~w -> delta ~w (ratio ~.2f, "
"should be 1.0)~n",
@@ -729,8 +761,8 @@ check_trace_info(Mfa, [{Pid, ExpectedC,_,_}] = Expect, Time) ->
S, Us, Sum, Time, Sum - Time, Time/Sum]),
time_error
end;
-check_trace_info(Mfa, Expect, _) ->
- case erlang:trace_info(Mfa, call_time) of
+check_trace_info_ret(Mfa, Expect, _, TraceInfo) ->
+ case TraceInfo of
{call_time, Expect} ->
ok;
Other ->
@@ -793,7 +825,7 @@ setup() ->
setup(Opts) ->
Pid = spawn_opt(fun() -> loop() end,
- [link, {max_heap_size, 10000}]),
+ [link, {max_heap_size, 12000}]),
1 = erlang:trace(Pid, true, [call|Opts]),
Pid.
diff --git a/erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.S b/erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.S
index dc873fe73f..5774424284 100644
--- a/erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.S
+++ b/erts/emulator/test/tuple_SUITE_data/get_two_tuple_elements.S
@@ -63,7 +63,7 @@
{trim,2,0}.
{line,[{location,"get_two_tuple_elements.erl",5}]}.
{call,2,{f,5}}.
- {'%',{var_info,{x,0},[{type,number}]}}.
+ {'%',{var_info,{x,0},[{type,{t_number,any}}]}}.
{test,is_eq_exact,{f,3},[{x,0},{integer,3}]}.
{move,{atom,ok},{x,0}}.
{deallocate,0}.
diff --git a/erts/emulator/utils/beam_makeops b/erts/emulator/utils/beam_makeops
index da850ed7cb..c7ea032026 100755
--- a/erts/emulator/utils/beam_makeops
+++ b/erts/emulator/utils/beam_makeops
@@ -1141,6 +1141,7 @@ sub emulator_output {
print '#include "erl_map.h"', "\n";
print '#include "big.h"', "\n";
print '#include "erl_bits.h"', "\n";
+ print '#include "erl_binary.h"', "\n";
print '#include "beam_transform_helpers.h"', "\n";
print '#include "erl_global_literals.h"', "\n";
print "\n";
diff --git a/erts/emulator/utils/make_driver_tab b/erts/emulator/utils/make_driver_tab
index 4ed9cb0ce8..0749df1cbe 100755
--- a/erts/emulator/utils/make_driver_tab
+++ b/erts/emulator/utils/make_driver_tab
@@ -107,7 +107,7 @@ foreach (@static_drivers) {
}
print " {NULL}\n};\n";
-print "void erts_init_static_drivers() {\n";
+print "void erts_init_static_drivers(void) {\n";
my $index = 0;
foreach (@static_drivers) {
diff --git a/erts/emulator/zlib/compress.c b/erts/emulator/zlib/compress.c
index e2db404abf..2ad5326c14 100644
--- a/erts/emulator/zlib/compress.c
+++ b/erts/emulator/zlib/compress.c
@@ -19,7 +19,7 @@
memory, Z_BUF_ERROR if there was not enough room in the output buffer,
Z_STREAM_ERROR if the level parameter is invalid.
*/
-int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
+int ZEXPORT compress2(dest, destLen, source, sourceLen, level)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
@@ -65,7 +65,7 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
/* ===========================================================================
*/
-int ZEXPORT compress (dest, destLen, source, sourceLen)
+int ZEXPORT compress(dest, destLen, source, sourceLen)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
@@ -78,7 +78,7 @@ int ZEXPORT compress (dest, destLen, source, sourceLen)
If the default memLevel or windowBits for deflateInit() is changed, then
this function needs to be updated.
*/
-uLong ZEXPORT compressBound (sourceLen)
+uLong ZEXPORT compressBound(sourceLen)
uLong sourceLen;
{
return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
diff --git a/erts/emulator/zlib/crc32.c b/erts/emulator/zlib/crc32.c
index a1bdce5c23..f8357b083f 100644
--- a/erts/emulator/zlib/crc32.c
+++ b/erts/emulator/zlib/crc32.c
@@ -98,13 +98,22 @@
# endif
#endif
+/* If available, use the ARM processor CRC32 instruction. */
+#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) && W == 8
+# define ARMCRC32
+#endif
+
/* Local functions. */
local z_crc_t multmodp OF((z_crc_t a, z_crc_t b));
local z_crc_t x2nmodp OF((z_off64_t n, unsigned k));
-/* If available, use the ARM processor CRC32 instruction. */
-#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) && W == 8
-# define ARMCRC32
+#if defined(W) && (!defined(ARMCRC32) || defined(DYNAMIC_CRC_TABLE))
+ local z_word_t byte_swap OF((z_word_t word));
+#endif
+
+#if defined(W) && !defined(ARMCRC32)
+ local z_crc_t crc_word OF((z_word_t data));
+ local z_word_t crc_word_big OF((z_word_t data));
#endif
#if defined(W) && (!defined(ARMCRC32) || defined(DYNAMIC_CRC_TABLE))
@@ -630,7 +639,7 @@ unsigned long ZEXPORT crc32_z(crc, buf, len)
#endif /* DYNAMIC_CRC_TABLE */
/* Pre-condition the CRC */
- crc ^= 0xffffffff;
+ crc = (~crc) & 0xffffffff;
/* Compute the CRC up to a word boundary. */
while (len && ((z_size_t)buf & 7) != 0) {
@@ -645,8 +654,8 @@ unsigned long ZEXPORT crc32_z(crc, buf, len)
len &= 7;
/* Do three interleaved CRCs to realize the throughput of one crc32x
- instruction per cycle. Each CRC is calcuated on Z_BATCH words. The three
- CRCs are combined into a single CRC after each set of batches. */
+ instruction per cycle. Each CRC is calculated on Z_BATCH words. The
+ three CRCs are combined into a single CRC after each set of batches. */
while (num >= 3 * Z_BATCH) {
crc1 = 0;
crc2 = 0;
@@ -749,7 +758,7 @@ unsigned long ZEXPORT crc32_z(crc, buf, len)
#endif /* DYNAMIC_CRC_TABLE */
/* Pre-condition the CRC */
- crc ^= 0xffffffff;
+ crc = (~crc) & 0xffffffff;
#ifdef W
@@ -1077,7 +1086,7 @@ uLong ZEXPORT crc32_combine64(crc1, crc2, len2)
#ifdef DYNAMIC_CRC_TABLE
once(&made, make_crc_table);
#endif /* DYNAMIC_CRC_TABLE */
- return multmodp(x2nmodp(len2, 3), crc1) ^ crc2;
+ return multmodp(x2nmodp(len2, 3), crc1) ^ (crc2 & 0xffffffff);
}
/* ========================================================================= */
@@ -1086,7 +1095,7 @@ uLong ZEXPORT crc32_combine(crc1, crc2, len2)
uLong crc2;
z_off_t len2;
{
- return crc32_combine64(crc1, crc2, len2);
+ return crc32_combine64(crc1, crc2, (z_off64_t)len2);
}
/* ========================================================================= */
@@ -1103,14 +1112,14 @@ uLong ZEXPORT crc32_combine_gen64(len2)
uLong ZEXPORT crc32_combine_gen(len2)
z_off_t len2;
{
- return crc32_combine_gen64(len2);
+ return crc32_combine_gen64((z_off64_t)len2);
}
/* ========================================================================= */
-uLong crc32_combine_op(crc1, crc2, op)
+uLong ZEXPORT crc32_combine_op(crc1, crc2, op)
uLong crc1;
uLong crc2;
uLong op;
{
- return multmodp(op, crc1) ^ crc2;
+ return multmodp(op, crc1) ^ (crc2 & 0xffffffff);
}
diff --git a/erts/emulator/zlib/deflate.c b/erts/emulator/zlib/deflate.c
index 799fb93cc0..4a689db359 100644
--- a/erts/emulator/zlib/deflate.c
+++ b/erts/emulator/zlib/deflate.c
@@ -52,7 +52,7 @@
#include "deflate.h"
const char deflate_copyright[] =
- " deflate 1.2.12 Copyright 1995-2022 Jean-loup Gailly and Mark Adler ";
+ " deflate 1.2.13 Copyright 1995-2022 Jean-loup Gailly and Mark Adler ";
/*
If you use the zlib library in a product, an acknowledgment is welcome
in the documentation of your product. If for some reason you cannot
@@ -87,13 +87,7 @@ local void lm_init OF((deflate_state *s));
local void putShortMSB OF((deflate_state *s, uInt b));
local void flush_pending OF((z_streamp strm));
local unsigned read_buf OF((z_streamp strm, Bytef *buf, unsigned size));
-#ifdef ASMV
-# pragma message("Assembler code may have bugs -- use at your own risk")
- void match_init OF((void)); /* asm code initialization */
- uInt longest_match OF((deflate_state *s, IPos cur_match));
-#else
local uInt longest_match OF((deflate_state *s, IPos cur_match));
-#endif
#ifdef ZLIB_DEBUG
local void check_match OF((deflate_state *s, IPos start, IPos match,
@@ -160,7 +154,7 @@ local const config configuration_table[10] = {
* characters, so that a running hash key can be computed from the previous
* key instead of complete recalculation each time.
*/
-#define UPDATE_HASH(s,h,c) (h = (((h)<<s->hash_shift) ^ (c)) & s->hash_mask)
+#define UPDATE_HASH(s,h,c) (h = (((h) << s->hash_shift) ^ (c)) & s->hash_mask)
/* ===========================================================================
@@ -191,9 +185,9 @@ local const config configuration_table[10] = {
*/
#define CLEAR_HASH(s) \
do { \
- s->head[s->hash_size-1] = NIL; \
+ s->head[s->hash_size - 1] = NIL; \
zmemzero((Bytef *)s->head, \
- (unsigned)(s->hash_size-1)*sizeof(*s->head)); \
+ (unsigned)(s->hash_size - 1)*sizeof(*s->head)); \
} while (0)
/* ===========================================================================
@@ -285,6 +279,8 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
if (windowBits < 0) { /* suppress zlib wrapper */
wrap = 0;
+ if (windowBits < -15)
+ return Z_STREAM_ERROR;
windowBits = -windowBits;
}
#ifdef GZIP
@@ -314,7 +310,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
s->hash_bits = (uInt)memLevel + 7;
s->hash_size = 1 << s->hash_bits;
s->hash_mask = s->hash_size - 1;
- s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH);
+ s->hash_shift = ((s->hash_bits + MIN_MATCH-1) / MIN_MATCH);
s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte));
s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos));
@@ -340,11 +336,11 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
* sym_buf value to read moves forward three bytes. From that symbol, up to
* 31 bits are written to pending_buf. The closest the written pending_buf
* bits gets to the next sym_buf symbol to read is just before the last
- * code is written. At that time, 31*(n-2) bits have been written, just
- * after 24*(n-2) bits have been consumed from sym_buf. sym_buf starts at
- * 8*n bits into pending_buf. (Note that the symbol buffer fills when n-1
+ * code is written. At that time, 31*(n - 2) bits have been written, just
+ * after 24*(n - 2) bits have been consumed from sym_buf. sym_buf starts at
+ * 8*n bits into pending_buf. (Note that the symbol buffer fills when n - 1
* symbols are written.) The closest the writing gets to what is unread is
- * then n+14 bits. Here n is lit_bufsize, which is 16384 by default, and
+ * then n + 14 bits. Here n is lit_bufsize, which is 16384 by default, and
* can range from 128 to 32768.
*
* Therefore, at a minimum, there are 142 bits of space between what is
@@ -390,7 +386,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
/* =========================================================================
* Check for a valid deflate stream state. Return 0 if ok, 1 if not.
*/
-local int deflateStateCheck (strm)
+local int deflateStateCheck(strm)
z_streamp strm;
{
deflate_state *s;
@@ -413,7 +409,7 @@ local int deflateStateCheck (strm)
}
/* ========================================================================= */
-int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
+int ZEXPORT deflateSetDictionary(strm, dictionary, dictLength)
z_streamp strm;
const Bytef *dictionary;
uInt dictLength;
@@ -482,7 +478,7 @@ int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
}
/* ========================================================================= */
-int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength)
+int ZEXPORT deflateGetDictionary(strm, dictionary, dictLength)
z_streamp strm;
Bytef *dictionary;
uInt *dictLength;
@@ -504,7 +500,7 @@ int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength)
}
/* ========================================================================= */
-int ZEXPORT deflateResetKeep (strm)
+int ZEXPORT deflateResetKeep(strm)
z_streamp strm;
{
deflate_state *s;
@@ -542,7 +538,7 @@ int ZEXPORT deflateResetKeep (strm)
}
/* ========================================================================= */
-int ZEXPORT deflateReset (strm)
+int ZEXPORT deflateReset(strm)
z_streamp strm;
{
int ret;
@@ -554,7 +550,7 @@ int ZEXPORT deflateReset (strm)
}
/* ========================================================================= */
-int ZEXPORT deflateSetHeader (strm, head)
+int ZEXPORT deflateSetHeader(strm, head)
z_streamp strm;
gz_headerp head;
{
@@ -565,7 +561,7 @@ int ZEXPORT deflateSetHeader (strm, head)
}
/* ========================================================================= */
-int ZEXPORT deflatePending (strm, pending, bits)
+int ZEXPORT deflatePending(strm, pending, bits)
unsigned *pending;
int *bits;
z_streamp strm;
@@ -579,7 +575,7 @@ int ZEXPORT deflatePending (strm, pending, bits)
}
/* ========================================================================= */
-int ZEXPORT deflatePrime (strm, bits, value)
+int ZEXPORT deflatePrime(strm, bits, value)
z_streamp strm;
int bits;
int value;
@@ -674,36 +670,50 @@ int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain)
}
/* =========================================================================
- * For the default windowBits of 15 and memLevel of 8, this function returns
- * a close to exact, as well as small, upper bound on the compressed size.
- * They are coded as constants here for a reason--if the #define's are
- * changed, then this function needs to be changed as well. The return
- * value for 15 and 8 only works for those exact settings.
+ * For the default windowBits of 15 and memLevel of 8, this function returns a
+ * close to exact, as well as small, upper bound on the compressed size. This
+ * is an expansion of ~0.03%, plus a small constant.
+ *
+ * For any setting other than those defaults for windowBits and memLevel, one
+ * of two worst case bounds is returned. This is at most an expansion of ~4% or
+ * ~13%, plus a small constant.
*
- * For any setting other than those defaults for windowBits and memLevel,
- * the value returned is a conservative worst case for the maximum expansion
- * resulting from using fixed blocks instead of stored blocks, which deflate
- * can emit on compressed data for some combinations of the parameters.
+ * Both the 0.03% and 4% derive from the overhead of stored blocks. The first
+ * one is for stored blocks of 16383 bytes (memLevel == 8), whereas the second
+ * is for stored blocks of 127 bytes (the worst case memLevel == 1). The
+ * expansion results from five bytes of header for each stored block.
*
- * This function could be more sophisticated to provide closer upper bounds for
- * every combination of windowBits and memLevel. But even the conservative
- * upper bound of about 14% expansion does not seem onerous for output buffer
- * allocation.
+ * The larger expansion of 13% results from a window size less than or equal to
+ * the symbols buffer size (windowBits <= memLevel + 7). In that case some of
+ * the data being compressed may have slid out of the sliding window, impeding
+ * a stored block from being emitted. Then the only choice is a fixed or
+ * dynamic block, where a fixed block limits the maximum expansion to 9 bits
+ * per 8-bit byte, plus 10 bits for every block. The smallest block size for
+ * which this can occur is 255 (memLevel == 2).
+ *
+ * Shifts are used to approximate divisions, for speed.
*/
uLong ZEXPORT deflateBound(strm, sourceLen)
z_streamp strm;
uLong sourceLen;
{
deflate_state *s;
- uLong complen, wraplen;
+ uLong fixedlen, storelen, wraplen;
+
+ /* upper bound for fixed blocks with 9-bit literals and length 255
+ (memLevel == 2, which is the lowest that may not use stored blocks) --
+ ~13% overhead plus a small constant */
+ fixedlen = sourceLen + (sourceLen >> 3) + (sourceLen >> 8) +
+ (sourceLen >> 9) + 4;
- /* conservative upper bound for compressed data */
- complen = sourceLen +
- ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5;
+ /* upper bound for stored blocks with length 127 (memLevel == 1) --
+ ~4% overhead plus a small constant */
+ storelen = sourceLen + (sourceLen >> 5) + (sourceLen >> 7) +
+ (sourceLen >> 11) + 7;
- /* if can't get parameters, return conservative bound plus zlib wrapper */
+ /* if can't get parameters, return larger bound plus a zlib wrapper */
if (deflateStateCheck(strm))
- return complen + 6;
+ return (fixedlen > storelen ? fixedlen : storelen) + 6;
/* compute wrapper length */
s = strm->state;
@@ -740,11 +750,12 @@ uLong ZEXPORT deflateBound(strm, sourceLen)
wraplen = 6;
}
- /* if not default parameters, return conservative bound */
+ /* if not default parameters, return one of the conservative bounds */
if (s->w_bits != 15 || s->hash_bits != 8 + 7)
- return complen + wraplen;
+ return (s->w_bits <= s->hash_bits ? fixedlen : storelen) + wraplen;
- /* default settings: return tight bound for that case */
+ /* default settings: return tight bound for that case -- ~0.03% overhead
+ plus a small constant */
return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
(sourceLen >> 25) + 13 - 6 + wraplen;
}
@@ -754,7 +765,7 @@ uLong ZEXPORT deflateBound(strm, sourceLen)
* IN assertion: the stream state is correct and there is enough room in
* pending_buf.
*/
-local void putShortMSB (s, b)
+local void putShortMSB(s, b)
deflate_state *s;
uInt b;
{
@@ -801,7 +812,7 @@ local void flush_pending(strm)
} while (0)
/* ========================================================================= */
-int ZEXPORT deflate (strm, flush)
+int ZEXPORT deflate(strm, flush)
z_streamp strm;
int flush;
{
@@ -856,7 +867,7 @@ int ZEXPORT deflate (strm, flush)
s->status = BUSY_STATE;
if (s->status == INIT_STATE) {
/* zlib header */
- uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8;
+ uInt header = (Z_DEFLATED + ((s->w_bits - 8) << 4)) << 8;
uInt level_flags;
if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2)
@@ -1116,7 +1127,7 @@ int ZEXPORT deflate (strm, flush)
}
/* ========================================================================= */
-int ZEXPORT deflateEnd (strm)
+int ZEXPORT deflateEnd(strm)
z_streamp strm;
{
int status;
@@ -1142,7 +1153,7 @@ int ZEXPORT deflateEnd (strm)
* To simplify the source, this is not supported for 16-bit MSDOS (which
* doesn't have enough memory anyway to duplicate compression states).
*/
-int ZEXPORT deflateCopy (dest, source)
+int ZEXPORT deflateCopy(dest, source)
z_streamp dest;
z_streamp source;
{
@@ -1231,7 +1242,7 @@ local unsigned read_buf(strm, buf, size)
/* ===========================================================================
* Initialize the "longest match" routines for a new zlib stream
*/
-local void lm_init (s)
+local void lm_init(s)
deflate_state *s;
{
s->window_size = (ulg)2L*s->w_size;
@@ -1252,11 +1263,6 @@ local void lm_init (s)
s->match_length = s->prev_length = MIN_MATCH-1;
s->match_available = 0;
s->ins_h = 0;
-#ifndef FASTEST
-#ifdef ASMV
- match_init(); /* initialize the asm code */
-#endif
-#endif
}
#ifndef FASTEST
@@ -1269,10 +1275,6 @@ local void lm_init (s)
* string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
* OUT assertion: the match length is not greater than s->lookahead.
*/
-#ifndef ASMV
-/* For 80x86 and 680x0, an optimized version will be provided in match.asm or
- * match.S. The code will be functionally equivalent.
- */
local uInt longest_match(s, cur_match)
deflate_state *s;
IPos cur_match; /* current match */
@@ -1297,10 +1299,10 @@ local uInt longest_match(s, cur_match)
*/
register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1;
register ush scan_start = *(ushf*)scan;
- register ush scan_end = *(ushf*)(scan+best_len-1);
+ register ush scan_end = *(ushf*)(scan + best_len - 1);
#else
register Bytef *strend = s->window + s->strstart + MAX_MATCH;
- register Byte scan_end1 = scan[best_len-1];
+ register Byte scan_end1 = scan[best_len - 1];
register Byte scan_end = scan[best_len];
#endif
@@ -1318,7 +1320,8 @@ local uInt longest_match(s, cur_match)
*/
if ((uInt)nice_match > s->lookahead) nice_match = (int)s->lookahead;
- Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+ Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
+ "need lookahead");
do {
Assert(cur_match < s->strstart, "no future");
@@ -1336,43 +1339,44 @@ local uInt longest_match(s, cur_match)
/* This code assumes sizeof(unsigned short) == 2. Do not use
* UNALIGNED_OK if your compiler uses a different size.
*/
- if (*(ushf*)(match+best_len-1) != scan_end ||
+ if (*(ushf*)(match + best_len - 1) != scan_end ||
*(ushf*)match != scan_start) continue;
/* It is not necessary to compare scan[2] and match[2] since they are
* always equal when the other bytes match, given that the hash keys
* are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at
- * strstart+3, +5, ... up to strstart+257. We check for insufficient
+ * strstart + 3, + 5, up to strstart + 257. We check for insufficient
* lookahead only every 4th comparison; the 128th check will be made
- * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is
+ * at strstart + 257. If MAX_MATCH-2 is not a multiple of 8, it is
* necessary to put more guard bytes at the end of the window, or
* to check more often for insufficient lookahead.
*/
Assert(scan[2] == match[2], "scan[2]?");
scan++, match++;
do {
- } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
- *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
- *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
- *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+ } while (*(ushf*)(scan += 2) == *(ushf*)(match += 2) &&
+ *(ushf*)(scan += 2) == *(ushf*)(match += 2) &&
+ *(ushf*)(scan += 2) == *(ushf*)(match += 2) &&
+ *(ushf*)(scan += 2) == *(ushf*)(match += 2) &&
scan < strend);
/* The funny "do {}" generates better code on most compilers */
- /* Here, scan <= window+strstart+257 */
- Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+ /* Here, scan <= window + strstart + 257 */
+ Assert(scan <= s->window + (unsigned)(s->window_size - 1),
+ "wild scan");
if (*scan == *match) scan++;
- len = (MAX_MATCH - 1) - (int)(strend-scan);
+ len = (MAX_MATCH - 1) - (int)(strend - scan);
scan = strend - (MAX_MATCH-1);
#else /* UNALIGNED_OK */
- if (match[best_len] != scan_end ||
- match[best_len-1] != scan_end1 ||
- *match != *scan ||
- *++match != scan[1]) continue;
+ if (match[best_len] != scan_end ||
+ match[best_len - 1] != scan_end1 ||
+ *match != *scan ||
+ *++match != scan[1]) continue;
- /* The check at best_len-1 can be removed because it will be made
+ /* The check at best_len - 1 can be removed because it will be made
* again later. (This heuristic is not always a win.)
* It is not necessary to compare scan[2] and match[2] since they
* are always equal when the other bytes match, given that
@@ -1382,7 +1386,7 @@ local uInt longest_match(s, cur_match)
Assert(*scan == *match, "match[2]?");
/* We check for insufficient lookahead only every 8th comparison;
- * the 256th check will be made at strstart+258.
+ * the 256th check will be made at strstart + 258.
*/
do {
} while (*++scan == *++match && *++scan == *++match &&
@@ -1391,7 +1395,8 @@ local uInt longest_match(s, cur_match)
*++scan == *++match && *++scan == *++match &&
scan < strend);
- Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+ Assert(scan <= s->window + (unsigned)(s->window_size - 1),
+ "wild scan");
len = MAX_MATCH - (int)(strend - scan);
scan = strend - MAX_MATCH;
@@ -1403,9 +1408,9 @@ local uInt longest_match(s, cur_match)
best_len = len;
if (len >= nice_match) break;
#ifdef UNALIGNED_OK
- scan_end = *(ushf*)(scan+best_len-1);
+ scan_end = *(ushf*)(scan + best_len - 1);
#else
- scan_end1 = scan[best_len-1];
+ scan_end1 = scan[best_len - 1];
scan_end = scan[best_len];
#endif
}
@@ -1415,7 +1420,6 @@ local uInt longest_match(s, cur_match)
if ((uInt)best_len <= s->lookahead) return (uInt)best_len;
return s->lookahead;
}
-#endif /* ASMV */
#else /* FASTEST */
@@ -1436,7 +1440,8 @@ local uInt longest_match(s, cur_match)
*/
Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
- Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+ Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
+ "need lookahead");
Assert(cur_match < s->strstart, "no future");
@@ -1446,7 +1451,7 @@ local uInt longest_match(s, cur_match)
*/
if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1;
- /* The check at best_len-1 can be removed because it will be made
+ /* The check at best_len - 1 can be removed because it will be made
* again later. (This heuristic is not always a win.)
* It is not necessary to compare scan[2] and match[2] since they
* are always equal when the other bytes match, given that
@@ -1456,7 +1461,7 @@ local uInt longest_match(s, cur_match)
Assert(*scan == *match, "match[2]?");
/* We check for insufficient lookahead only every 8th comparison;
- * the 256th check will be made at strstart+258.
+ * the 256th check will be made at strstart + 258.
*/
do {
} while (*++scan == *++match && *++scan == *++match &&
@@ -1465,7 +1470,7 @@ local uInt longest_match(s, cur_match)
*++scan == *++match && *++scan == *++match &&
scan < strend);
- Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+ Assert(scan <= s->window + (unsigned)(s->window_size - 1), "wild scan");
len = MAX_MATCH - (int)(strend - scan);
@@ -1501,7 +1506,7 @@ local void check_match(s, start, match, length)
z_error("invalid match");
}
if (z_verbose > 1) {
- fprintf(stderr,"\\[%d,%d]", start-match, length);
+ fprintf(stderr,"\\[%d,%d]", start - match, length);
do { putc(s->window[start++], stderr); } while (--length != 0);
}
}
@@ -1547,9 +1552,9 @@ local void fill_window(s)
/* If the window is almost full and there is insufficient lookahead,
* move the upper half to the lower one to make room in the upper half.
*/
- if (s->strstart >= wsize+MAX_DIST(s)) {
+ if (s->strstart >= wsize + MAX_DIST(s)) {
- zmemcpy(s->window, s->window+wsize, (unsigned)wsize - more);
+ zmemcpy(s->window, s->window + wsize, (unsigned)wsize - more);
s->match_start -= wsize;
s->strstart -= wsize; /* we now have strstart >= MAX_DIST */
s->block_start -= (long) wsize;
@@ -1680,7 +1685,7 @@ local void fill_window(s)
*
* deflate_stored() is written to minimize the number of times an input byte is
* copied. It is most efficient with large input and output buffers, which
- * maximizes the opportunites to have a single copy from next_in to next_out.
+ * maximizes the opportunities to have a single copy from next_in to next_out.
*/
local block_state deflate_stored(s, flush)
deflate_state *s;
@@ -1890,7 +1895,7 @@ local block_state deflate_fast(s, flush)
if (s->lookahead == 0) break; /* flush the current block */
}
- /* Insert the string window[strstart .. strstart+2] in the
+ /* Insert the string window[strstart .. strstart + 2] in the
* dictionary, and set hash_head to the head of the hash chain:
*/
hash_head = NIL;
@@ -1938,7 +1943,7 @@ local block_state deflate_fast(s, flush)
s->strstart += s->match_length;
s->match_length = 0;
s->ins_h = s->window[s->strstart];
- UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]);
+ UPDATE_HASH(s, s->ins_h, s->window[s->strstart + 1]);
#if MIN_MATCH != 3
Call UPDATE_HASH() MIN_MATCH-3 more times
#endif
@@ -1949,7 +1954,7 @@ local block_state deflate_fast(s, flush)
} else {
/* No match, output a literal byte */
Tracevv((stderr,"%c", s->window[s->strstart]));
- _tr_tally_lit (s, s->window[s->strstart], bflush);
+ _tr_tally_lit(s, s->window[s->strstart], bflush);
s->lookahead--;
s->strstart++;
}
@@ -1993,7 +1998,7 @@ local block_state deflate_slow(s, flush)
if (s->lookahead == 0) break; /* flush the current block */
}
- /* Insert the string window[strstart .. strstart+2] in the
+ /* Insert the string window[strstart .. strstart + 2] in the
* dictionary, and set hash_head to the head of the hash chain:
*/
hash_head = NIL;
@@ -2035,17 +2040,17 @@ local block_state deflate_slow(s, flush)
uInt max_insert = s->strstart + s->lookahead - MIN_MATCH;
/* Do not insert strings in hash table beyond this. */
- check_match(s, s->strstart-1, s->prev_match, s->prev_length);
+ check_match(s, s->strstart - 1, s->prev_match, s->prev_length);
- _tr_tally_dist(s, s->strstart -1 - s->prev_match,
+ _tr_tally_dist(s, s->strstart - 1 - s->prev_match,
s->prev_length - MIN_MATCH, bflush);
/* Insert in hash table all strings up to the end of the match.
- * strstart-1 and strstart are already inserted. If there is not
+ * strstart - 1 and strstart are already inserted. If there is not
* enough lookahead, the last two strings are not inserted in
* the hash table.
*/
- s->lookahead -= s->prev_length-1;
+ s->lookahead -= s->prev_length - 1;
s->prev_length -= 2;
do {
if (++s->strstart <= max_insert) {
@@ -2063,8 +2068,8 @@ local block_state deflate_slow(s, flush)
* single literal. If there was a match but the current match
* is longer, truncate the previous match to a single literal.
*/
- Tracevv((stderr,"%c", s->window[s->strstart-1]));
- _tr_tally_lit(s, s->window[s->strstart-1], bflush);
+ Tracevv((stderr,"%c", s->window[s->strstart - 1]));
+ _tr_tally_lit(s, s->window[s->strstart - 1], bflush);
if (bflush) {
FLUSH_BLOCK_ONLY(s, 0);
}
@@ -2082,8 +2087,8 @@ local block_state deflate_slow(s, flush)
}
Assert (flush != Z_NO_FLUSH, "no flush?");
if (s->match_available) {
- Tracevv((stderr,"%c", s->window[s->strstart-1]));
- _tr_tally_lit(s, s->window[s->strstart-1], bflush);
+ Tracevv((stderr,"%c", s->window[s->strstart - 1]));
+ _tr_tally_lit(s, s->window[s->strstart - 1], bflush);
s->match_available = 0;
}
s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1;
@@ -2140,7 +2145,8 @@ local block_state deflate_rle(s, flush)
if (s->match_length > s->lookahead)
s->match_length = s->lookahead;
}
- Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
+ Assert(scan <= s->window + (uInt)(s->window_size - 1),
+ "wild scan");
}
/* Emit match if have run of MIN_MATCH or longer, else emit literal */
@@ -2155,7 +2161,7 @@ local block_state deflate_rle(s, flush)
} else {
/* No match, output a literal byte */
Tracevv((stderr,"%c", s->window[s->strstart]));
- _tr_tally_lit (s, s->window[s->strstart], bflush);
+ _tr_tally_lit(s, s->window[s->strstart], bflush);
s->lookahead--;
s->strstart++;
}
@@ -2195,7 +2201,7 @@ local block_state deflate_huff(s, flush)
/* Output a literal byte */
s->match_length = 0;
Tracevv((stderr,"%c", s->window[s->strstart]));
- _tr_tally_lit (s, s->window[s->strstart], bflush);
+ _tr_tally_lit(s, s->window[s->strstart], bflush);
s->lookahead--;
s->strstart++;
if (bflush) FLUSH_BLOCK(s, 0);
diff --git a/erts/emulator/zlib/deflate.h b/erts/emulator/zlib/deflate.h
index 17c226113b..1a06cd5f25 100644
--- a/erts/emulator/zlib/deflate.h
+++ b/erts/emulator/zlib/deflate.h
@@ -329,8 +329,8 @@ void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf,
# define _tr_tally_dist(s, distance, length, flush) \
{ uch len = (uch)(length); \
ush dist = (ush)(distance); \
- s->sym_buf[s->sym_next++] = dist; \
- s->sym_buf[s->sym_next++] = dist >> 8; \
+ s->sym_buf[s->sym_next++] = (uch)dist; \
+ s->sym_buf[s->sym_next++] = (uch)(dist >> 8); \
s->sym_buf[s->sym_next++] = len; \
dist--; \
s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \
diff --git a/erts/emulator/zlib/inflate.c b/erts/emulator/zlib/inflate.c
index 7be8c63662..8acbef44e9 100644
--- a/erts/emulator/zlib/inflate.c
+++ b/erts/emulator/zlib/inflate.c
@@ -168,6 +168,8 @@ int windowBits;
/* extract wrap request from windowBits parameter */
if (windowBits < 0) {
+ if (windowBits < -15)
+ return Z_STREAM_ERROR;
wrap = 0;
windowBits = -windowBits;
}
@@ -764,8 +766,9 @@ int flush;
if (copy > have) copy = have;
if (copy) {
if (state->head != Z_NULL &&
- state->head->extra != Z_NULL) {
- len = state->head->extra_len - state->length;
+ state->head->extra != Z_NULL &&
+ (len = state->head->extra_len - state->length) <
+ state->head->extra_max) {
zmemcpy(state->head->extra + len, next,
len + copy > state->head->extra_max ?
state->head->extra_max - len : copy);
diff --git a/erts/emulator/zlib/inftrees.c b/erts/emulator/zlib/inftrees.c
index 7783b162e5..57d2793bec 100644
--- a/erts/emulator/zlib/inftrees.c
+++ b/erts/emulator/zlib/inftrees.c
@@ -9,7 +9,7 @@
#define MAXBITS 15
const char inflate_copyright[] =
- " inflate 1.2.12 Copyright 1995-2022 Mark Adler ";
+ " inflate 1.2.13 Copyright 1995-2022 Mark Adler ";
/*
If you use the zlib library in a product, an acknowledgment is welcome
in the documentation of your product. If for some reason you cannot
@@ -62,7 +62,7 @@ unsigned short FAR *work;
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
static const unsigned short lext[31] = { /* Length codes 257..285 extra */
16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
- 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 199, 202};
+ 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 194, 65};
static const unsigned short dbase[32] = { /* Distance codes 0..29 base */
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
@@ -87,7 +87,7 @@ unsigned short FAR *work;
This routine assumes, but does not check, that all of the entries in
lens[] are in the range 0..MAXBITS. The caller must assure this.
- 1..MAXBITS is interpreted as that code length. zero means that
+ 1..MAXBITS is interpreted as that code length. zero means that that
symbol does not occur in this code.
The codes are sorted by computing a count of codes for each length,
diff --git a/erts/emulator/zlib/inftrees.h b/erts/emulator/zlib/inftrees.h
index baa53a0b1a..f53665311c 100644
--- a/erts/emulator/zlib/inftrees.h
+++ b/erts/emulator/zlib/inftrees.h
@@ -38,7 +38,7 @@ typedef struct {
/* Maximum size of the dynamic table. The maximum number of code structures is
1444, which is the sum of 852 for literal/length codes and 592 for distance
codes. These values were found by exhaustive searches using the program
- examples/enough.c found in the zlib distribtution. The arguments to that
+ examples/enough.c found in the zlib distribution. The arguments to that
program are the number of symbols, the initial root table size, and the
maximum bit length of a code. "enough 286 9 15" for literal/length codes
returns returns 852, and "enough 30 6 15" for distance codes returns 592.
diff --git a/erts/emulator/zlib/trees.c b/erts/emulator/zlib/trees.c
index f73fd99c37..5f305c4722 100644
--- a/erts/emulator/zlib/trees.c
+++ b/erts/emulator/zlib/trees.c
@@ -193,7 +193,7 @@ local void send_bits(s, value, length)
s->bits_sent += (ulg)length;
/* If not enough room in bi_buf, use (valid) bits from bi_buf and
- * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid))
+ * (16 - bi_valid) bits from value, leaving (width - (16 - bi_valid))
* unused bits in value.
*/
if (s->bi_valid > (int)Buf_size - length) {
@@ -256,7 +256,7 @@ local void tr_static_init()
length = 0;
for (code = 0; code < LENGTH_CODES-1; code++) {
base_length[code] = length;
- for (n = 0; n < (1<<extra_lbits[code]); n++) {
+ for (n = 0; n < (1 << extra_lbits[code]); n++) {
_length_code[length++] = (uch)code;
}
}
@@ -265,13 +265,13 @@ local void tr_static_init()
* in two different ways: code 284 + 5 bits or code 285, so we
* overwrite length_code[255] to use the best encoding:
*/
- _length_code[length-1] = (uch)code;
+ _length_code[length - 1] = (uch)code;
/* Initialize the mapping dist (0..32K) -> dist code (0..29) */
dist = 0;
for (code = 0 ; code < 16; code++) {
base_dist[code] = dist;
- for (n = 0; n < (1<<extra_dbits[code]); n++) {
+ for (n = 0; n < (1 << extra_dbits[code]); n++) {
_dist_code[dist++] = (uch)code;
}
}
@@ -279,11 +279,11 @@ local void tr_static_init()
dist >>= 7; /* from now on, all distances are divided by 128 */
for ( ; code < D_CODES; code++) {
base_dist[code] = dist << 7;
- for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) {
+ for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
_dist_code[256 + dist++] = (uch)code;
}
}
- Assert (dist == 256, "tr_static_init: 256+dist != 512");
+ Assert (dist == 256, "tr_static_init: 256 + dist != 512");
/* Construct the codes of the static literal tree */
for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0;
@@ -312,7 +312,7 @@ local void tr_static_init()
}
/* ===========================================================================
- * Genererate the file trees.h describing the static trees.
+ * Generate the file trees.h describing the static trees.
*/
#ifdef GEN_TREES_H
# ifndef ZLIB_DEBUG
@@ -321,7 +321,7 @@ local void tr_static_init()
# define SEPARATOR(i, last, width) \
((i) == (last)? "\n};\n\n" : \
- ((i) % (width) == (width)-1 ? ",\n" : ", "))
+ ((i) % (width) == (width) - 1 ? ",\n" : ", "))
void gen_trees_header()
{
@@ -458,7 +458,7 @@ local void pqdownheap(s, tree, k)
while (j <= s->heap_len) {
/* Set j to the smallest of the two sons: */
if (j < s->heap_len &&
- smaller(tree, s->heap[j+1], s->heap[j], s->depth)) {
+ smaller(tree, s->heap[j + 1], s->heap[j], s->depth)) {
j++;
}
/* Exit if v is smaller than both sons */
@@ -507,7 +507,7 @@ local void gen_bitlen(s, desc)
*/
tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */
- for (h = s->heap_max+1; h < HEAP_SIZE; h++) {
+ for (h = s->heap_max + 1; h < HEAP_SIZE; h++) {
n = s->heap[h];
bits = tree[tree[n].Dad].Len + 1;
if (bits > max_length) bits = max_length, overflow++;
@@ -518,7 +518,7 @@ local void gen_bitlen(s, desc)
s->bl_count[bits]++;
xbits = 0;
- if (n >= base) xbits = extra[n-base];
+ if (n >= base) xbits = extra[n - base];
f = tree[n].Freq;
s->opt_len += (ulg)f * (unsigned)(bits + xbits);
if (stree) s->static_len += (ulg)f * (unsigned)(stree[n].Len + xbits);
@@ -530,10 +530,10 @@ local void gen_bitlen(s, desc)
/* Find the first bit length which could increase: */
do {
- bits = max_length-1;
+ bits = max_length - 1;
while (s->bl_count[bits] == 0) bits--;
- s->bl_count[bits]--; /* move one leaf down the tree */
- s->bl_count[bits+1] += 2; /* move one overflow item as its brother */
+ s->bl_count[bits]--; /* move one leaf down the tree */
+ s->bl_count[bits + 1] += 2; /* move one overflow item as its brother */
s->bl_count[max_length]--;
/* The brother of the overflow item also moves one step up,
* but this does not affect bl_count[max_length]
@@ -569,7 +569,7 @@ local void gen_bitlen(s, desc)
* OUT assertion: the field code is set for all tree elements of non
* zero code length.
*/
-local void gen_codes (tree, max_code, bl_count)
+local void gen_codes(tree, max_code, bl_count)
ct_data *tree; /* the tree to decorate */
int max_code; /* largest code with non zero frequency */
ushf *bl_count; /* number of codes at each bit length */
@@ -583,13 +583,13 @@ local void gen_codes (tree, max_code, bl_count)
* without bit reversal.
*/
for (bits = 1; bits <= MAX_BITS; bits++) {
- code = (code + bl_count[bits-1]) << 1;
+ code = (code + bl_count[bits - 1]) << 1;
next_code[bits] = (ush)code;
}
/* Check that the bit counts in bl_count are consistent. The last code
* must be all ones.
*/
- Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+ Assert (code + bl_count[MAX_BITS] - 1 == (1 << MAX_BITS) - 1,
"inconsistent bit counts");
Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
@@ -600,7 +600,7 @@ local void gen_codes (tree, max_code, bl_count)
tree[n].Code = (ush)bi_reverse(next_code[len]++, len);
Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
- n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
+ n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len] - 1));
}
}
@@ -624,7 +624,7 @@ local void build_tree(s, desc)
int node; /* new node being created */
/* Construct the initial heap, with least frequent element in
- * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+ * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n + 1].
* heap[0] is not used.
*/
s->heap_len = 0, s->heap_max = HEAP_SIZE;
@@ -652,7 +652,7 @@ local void build_tree(s, desc)
}
desc->max_code = max_code;
- /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+ /* The elements heap[heap_len/2 + 1 .. heap_len] are leaves of the tree,
* establish sub-heaps of increasing lengths:
*/
for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n);
@@ -700,7 +700,7 @@ local void build_tree(s, desc)
* Scan a literal or distance tree to determine the frequencies of the codes
* in the bit length tree.
*/
-local void scan_tree (s, tree, max_code)
+local void scan_tree(s, tree, max_code)
deflate_state *s;
ct_data *tree; /* the tree to be scanned */
int max_code; /* and its largest code of non zero frequency */
@@ -714,10 +714,10 @@ local void scan_tree (s, tree, max_code)
int min_count = 4; /* min repeat count */
if (nextlen == 0) max_count = 138, min_count = 3;
- tree[max_code+1].Len = (ush)0xffff; /* guard */
+ tree[max_code + 1].Len = (ush)0xffff; /* guard */
for (n = 0; n <= max_code; n++) {
- curlen = nextlen; nextlen = tree[n+1].Len;
+ curlen = nextlen; nextlen = tree[n + 1].Len;
if (++count < max_count && curlen == nextlen) {
continue;
} else if (count < min_count) {
@@ -745,7 +745,7 @@ local void scan_tree (s, tree, max_code)
* Send a literal or distance tree in compressed form, using the codes in
* bl_tree.
*/
-local void send_tree (s, tree, max_code)
+local void send_tree(s, tree, max_code)
deflate_state *s;
ct_data *tree; /* the tree to be scanned */
int max_code; /* and its largest code of non zero frequency */
@@ -758,11 +758,11 @@ local void send_tree (s, tree, max_code)
int max_count = 7; /* max repeat count */
int min_count = 4; /* min repeat count */
- /* tree[max_code+1].Len = -1; */ /* guard already set */
+ /* tree[max_code + 1].Len = -1; */ /* guard already set */
if (nextlen == 0) max_count = 138, min_count = 3;
for (n = 0; n <= max_code; n++) {
- curlen = nextlen; nextlen = tree[n+1].Len;
+ curlen = nextlen; nextlen = tree[n + 1].Len;
if (++count < max_count && curlen == nextlen) {
continue;
} else if (count < min_count) {
@@ -773,13 +773,13 @@ local void send_tree (s, tree, max_code)
send_code(s, curlen, s->bl_tree); count--;
}
Assert(count >= 3 && count <= 6, " 3_6?");
- send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2);
+ send_code(s, REP_3_6, s->bl_tree); send_bits(s, count - 3, 2);
} else if (count <= 10) {
- send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3);
+ send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count - 3, 3);
} else {
- send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7);
+ send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count - 11, 7);
}
count = 0; prevlen = curlen;
if (nextlen == 0) {
@@ -807,8 +807,8 @@ local int build_bl_tree(s)
/* Build the bit length tree: */
build_tree(s, (tree_desc *)(&(s->bl_desc)));
- /* opt_len now includes the length of the tree representations, except
- * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+ /* opt_len now includes the length of the tree representations, except the
+ * lengths of the bit lengths codes and the 5 + 5 + 4 bits for the counts.
*/
/* Determine the number of bit length codes to send. The pkzip format
@@ -819,7 +819,7 @@ local int build_bl_tree(s)
if (s->bl_tree[bl_order[max_blindex]].Len != 0) break;
}
/* Update opt_len to include the bit length tree and counts */
- s->opt_len += 3*((ulg)max_blindex+1) + 5+5+4;
+ s->opt_len += 3*((ulg)max_blindex + 1) + 5 + 5 + 4;
Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
s->opt_len, s->static_len));
@@ -841,19 +841,19 @@ local void send_all_trees(s, lcodes, dcodes, blcodes)
Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
"too many codes");
Tracev((stderr, "\nbl counts: "));
- send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */
- send_bits(s, dcodes-1, 5);
- send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */
+ send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */
+ send_bits(s, dcodes - 1, 5);
+ send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */
for (rank = 0; rank < blcodes; rank++) {
Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
send_bits(s, s->bl_tree[bl_order[rank]].Len, 3);
}
Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));
- send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */
+ send_tree(s, (ct_data *)s->dyn_ltree, lcodes - 1); /* literal tree */
Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));
- send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */
+ send_tree(s, (ct_data *)s->dyn_dtree, dcodes - 1); /* distance tree */
Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
}
@@ -866,7 +866,7 @@ void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
ulg stored_len; /* length of input block */
int last; /* one if this is the last block for a file */
{
- send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */
+ send_bits(s, (STORED_BLOCK<<1) + last, 3); /* send block type */
bi_windup(s); /* align on byte boundary */
put_short(s, (ush)stored_len);
put_short(s, (ush)~stored_len);
@@ -877,7 +877,7 @@ void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L;
s->compressed_len += (stored_len + 4) << 3;
s->bits_sent += 2*16;
- s->bits_sent += stored_len<<3;
+ s->bits_sent += stored_len << 3;
#endif
}
@@ -943,14 +943,17 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
max_blindex = build_bl_tree(s);
/* Determine the best encoding. Compute the block lengths in bytes. */
- opt_lenb = (s->opt_len+3+7)>>3;
- static_lenb = (s->static_len+3+7)>>3;
+ opt_lenb = (s->opt_len + 3 + 7) >> 3;
+ static_lenb = (s->static_len + 3 + 7) >> 3;
Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
s->sym_next / 3));
- if (static_lenb <= opt_lenb) opt_lenb = static_lenb;
+#ifndef FORCE_STATIC
+ if (static_lenb <= opt_lenb || s->strategy == Z_FIXED)
+#endif
+ opt_lenb = static_lenb;
} else {
Assert(buf != (char*)0, "lost buf");
@@ -960,7 +963,7 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
#ifdef FORCE_STORED
if (buf != (char*)0) { /* force stored block */
#else
- if (stored_len+4 <= opt_lenb && buf != (char*)0) {
+ if (stored_len + 4 <= opt_lenb && buf != (char*)0) {
/* 4: two words for the lengths */
#endif
/* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
@@ -971,21 +974,17 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
*/
_tr_stored_block(s, buf, stored_len, last);
-#ifdef FORCE_STATIC
- } else if (static_lenb >= 0) { /* force static trees */
-#else
- } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) {
-#endif
- send_bits(s, (STATIC_TREES<<1)+last, 3);
+ } else if (static_lenb == opt_lenb) {
+ send_bits(s, (STATIC_TREES<<1) + last, 3);
compress_block(s, (const ct_data *)static_ltree,
(const ct_data *)static_dtree);
#ifdef ZLIB_DEBUG
s->compressed_len += 3 + s->static_len;
#endif
} else {
- send_bits(s, (DYN_TREES<<1)+last, 3);
- send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1,
- max_blindex+1);
+ send_bits(s, (DYN_TREES<<1) + last, 3);
+ send_all_trees(s, s->l_desc.max_code + 1, s->d_desc.max_code + 1,
+ max_blindex + 1);
compress_block(s, (const ct_data *)s->dyn_ltree,
(const ct_data *)s->dyn_dtree);
#ifdef ZLIB_DEBUG
@@ -1004,22 +1003,22 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
s->compressed_len += 7; /* align on byte boundary */
#endif
}
- Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
- s->compressed_len-7*last));
+ Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len >> 3,
+ s->compressed_len - 7*last));
}
/* ===========================================================================
* Save the match info and tally the frequency counts. Return true if
* the current block must be flushed.
*/
-int ZLIB_INTERNAL _tr_tally (s, dist, lc)
+int ZLIB_INTERNAL _tr_tally(s, dist, lc)
deflate_state *s;
unsigned dist; /* distance of matched string */
- unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */
+ unsigned lc; /* match length - MIN_MATCH or unmatched char (dist==0) */
{
- s->sym_buf[s->sym_next++] = dist;
- s->sym_buf[s->sym_next++] = dist >> 8;
- s->sym_buf[s->sym_next++] = lc;
+ s->sym_buf[s->sym_next++] = (uch)dist;
+ s->sym_buf[s->sym_next++] = (uch)(dist >> 8);
+ s->sym_buf[s->sym_next++] = (uch)lc;
if (dist == 0) {
/* lc is the unmatched char */
s->dyn_ltree[lc].Freq++;
@@ -1031,7 +1030,7 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc)
(ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
(ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match");
- s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++;
+ s->dyn_ltree[_length_code[lc] + LITERALS + 1].Freq++;
s->dyn_dtree[d_code(dist)].Freq++;
}
return (s->sym_next == s->sym_end);
@@ -1061,7 +1060,7 @@ local void compress_block(s, ltree, dtree)
} else {
/* Here, lc is the match length - MIN_MATCH */
code = _length_code[lc];
- send_code(s, code+LITERALS+1, ltree); /* send the length code */
+ send_code(s, code + LITERALS + 1, ltree); /* send length code */
extra = extra_lbits[code];
if (extra != 0) {
lc -= base_length[code];
@@ -1177,6 +1176,6 @@ local void bi_windup(s)
s->bi_buf = 0;
s->bi_valid = 0;
#ifdef ZLIB_DEBUG
- s->bits_sent = (s->bits_sent+7) & ~7;
+ s->bits_sent = (s->bits_sent + 7) & ~7;
#endif
}
diff --git a/erts/emulator/zlib/uncompr.c b/erts/emulator/zlib/uncompr.c
index f03a1a865e..f9532f46c1 100644
--- a/erts/emulator/zlib/uncompr.c
+++ b/erts/emulator/zlib/uncompr.c
@@ -24,7 +24,7 @@
Z_DATA_ERROR if the input data was corrupted, including if the input data is
an incomplete zlib stream.
*/
-int ZEXPORT uncompress2 (dest, destLen, source, sourceLen)
+int ZEXPORT uncompress2(dest, destLen, source, sourceLen)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
@@ -83,7 +83,7 @@ int ZEXPORT uncompress2 (dest, destLen, source, sourceLen)
err;
}
-int ZEXPORT uncompress (dest, destLen, source, sourceLen)
+int ZEXPORT uncompress(dest, destLen, source, sourceLen)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
diff --git a/erts/emulator/zlib/zconf.h b/erts/emulator/zlib/zconf.h
index 5e1d68a004..bf977d3e70 100644
--- a/erts/emulator/zlib/zconf.h
+++ b/erts/emulator/zlib/zconf.h
@@ -38,6 +38,9 @@
# define crc32 z_crc32
# define crc32_combine z_crc32_combine
# define crc32_combine64 z_crc32_combine64
+# define crc32_combine_gen z_crc32_combine_gen
+# define crc32_combine_gen64 z_crc32_combine_gen64
+# define crc32_combine_op z_crc32_combine_op
# define crc32_z z_crc32_z
# define deflate z_deflate
# define deflateBound z_deflateBound
@@ -349,6 +352,9 @@
# ifdef FAR
# undef FAR
# endif
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
# include <windows.h>
/* No need for _export, use ZLIB.DEF instead. */
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
@@ -467,11 +473,18 @@ typedef uLong FAR uLongf;
# undef _LARGEFILE64_SOURCE
#endif
-#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H)
-# define Z_HAVE_UNISTD_H
+#ifndef Z_HAVE_UNISTD_H
+# ifdef __WATCOMC__
+# define Z_HAVE_UNISTD_H
+# endif
+#endif
+#ifndef Z_HAVE_UNISTD_H
+# if defined(_LARGEFILE64_SOURCE) && !defined(_WIN32)
+# define Z_HAVE_UNISTD_H
+# endif
#endif
#ifndef Z_SOLO
-# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE)
+# if defined(Z_HAVE_UNISTD_H)
# include <unistd.h> /* for SEEK_*, off_t, and _LFS64_LARGEFILE */
# ifdef VMS
# include <unixio.h> /* for off_t */
diff --git a/erts/emulator/zlib/zlib.h b/erts/emulator/zlib/zlib.h
index 4a98e38bf3..953cb5012d 100644
--- a/erts/emulator/zlib/zlib.h
+++ b/erts/emulator/zlib/zlib.h
@@ -1,5 +1,5 @@
/* zlib.h -- interface of the 'zlib' general purpose compression library
- version 1.2.12, March 11th, 2022
+ version 1.2.13, October 13th, 2022
Copyright (C) 1995-2022 Jean-loup Gailly and Mark Adler
@@ -37,11 +37,11 @@
extern "C" {
#endif
-#define ZLIB_VERSION "1.2.12"
-#define ZLIB_VERNUM 0x12c0
+#define ZLIB_VERSION "1.2.13"
+#define ZLIB_VERNUM 0x12d0
#define ZLIB_VER_MAJOR 1
#define ZLIB_VER_MINOR 2
-#define ZLIB_VER_REVISION 12
+#define ZLIB_VER_REVISION 13
#define ZLIB_VER_SUBREVISION 0
/*
@@ -276,7 +276,7 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
== 0), or after each call of deflate(). If deflate returns Z_OK and with
zero avail_out, it must be called again after making room in the output
buffer because there might be more output pending. See deflatePending(),
- which can be used if desired to determine whether or not there is more ouput
+ which can be used if desired to determine whether or not there is more output
in that case.
Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
@@ -660,7 +660,7 @@ ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm,
to dictionary. dictionary must have enough space, where 32768 bytes is
always enough. If deflateGetDictionary() is called with dictionary equal to
Z_NULL, then only the dictionary length is returned, and nothing is copied.
- Similary, if dictLength is Z_NULL, then it is not set.
+ Similarly, if dictLength is Z_NULL, then it is not set.
deflateGetDictionary() may return a length less than the window size, even
when more than the window size in input has been provided. It may return up
@@ -915,7 +915,7 @@ ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm,
to dictionary. dictionary must have enough space, where 32768 bytes is
always enough. If inflateGetDictionary() is called with dictionary equal to
Z_NULL, then only the dictionary length is returned, and nothing is copied.
- Similary, if dictLength is Z_NULL, then it is not set.
+ Similarly, if dictLength is Z_NULL, then it is not set.
inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the
stream state is inconsistent.
@@ -1437,12 +1437,12 @@ ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems,
In the event that the end of file is reached and only a partial item is
available at the end, i.e. the remaining uncompressed data length is not a
- multiple of size, then the final partial item is nevetheless read into buf
+ multiple of size, then the final partial item is nevertheless read into buf
and the end-of-file flag is set. The length of the partial item read is not
provided, but could be inferred from the result of gztell(). This behavior
is the same as the behavior of fread() implementations in common libraries,
but it prevents the direct use of gzfread() to read a concurrently written
- file, reseting and retrying on end-of-file, when size is not 1.
+ file, resetting and retrying on end-of-file, when size is not 1.
*/
ZEXTERN int ZEXPORT gzwrite OF((gzFile file, voidpc buf, unsigned len));
@@ -1913,7 +1913,7 @@ ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp));
ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void));
ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int));
ZEXTERN int ZEXPORT inflateValidate OF((z_streamp, int));
-ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF ((z_streamp));
+ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF((z_streamp));
ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp));
ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp));
#if defined(_WIN32) && !defined(Z_SOLO)
diff --git a/erts/emulator/zlib/zutil.c b/erts/emulator/zlib/zutil.c
index dcab28a0d5..9543ae825e 100644
--- a/erts/emulator/zlib/zutil.c
+++ b/erts/emulator/zlib/zutil.c
@@ -61,9 +61,11 @@ uLong ZEXPORT zlibCompileFlags()
#ifdef ZLIB_DEBUG
flags += 1 << 8;
#endif
+ /*
#if defined(ASMV) || defined(ASMINF)
flags += 1 << 9;
#endif
+ */
#ifdef ZLIB_WINAPI
flags += 1 << 10;
#endif
@@ -119,7 +121,7 @@ uLong ZEXPORT zlibCompileFlags()
# endif
int ZLIB_INTERNAL z_verbose = verbose;
-void ZLIB_INTERNAL z_error (m)
+void ZLIB_INTERNAL z_error(m)
char *m;
{
fprintf(stderr, "%s\n", m);
@@ -214,7 +216,7 @@ local ptr_table table[MAX_PTR];
* a protected system like OS/2. Use Microsoft C instead.
*/
-voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
+voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, unsigned size)
{
voidpf buf;
ulg bsize = (ulg)items*size;
@@ -240,7 +242,7 @@ voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
return buf;
}
-void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
+void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr)
{
int n;
@@ -277,13 +279,13 @@ void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
# define _hfree hfree
#endif
-voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size)
+voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, uInt items, uInt size)
{
(void)opaque;
return _halloc((long)items, size);
}
-void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
+void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr)
{
(void)opaque;
_hfree(ptr);
@@ -302,7 +304,7 @@ extern voidp calloc OF((uInt items, uInt size));
extern void free OF((voidpf ptr));
#endif
-voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
+voidpf ZLIB_INTERNAL zcalloc(opaque, items, size)
voidpf opaque;
unsigned items;
unsigned size;
@@ -312,7 +314,7 @@ voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
(voidpf)calloc(items, size);
}
-void ZLIB_INTERNAL zcfree (opaque, ptr)
+void ZLIB_INTERNAL zcfree(opaque, ptr)
voidpf opaque;
voidpf ptr;
{
diff --git a/erts/emulator/zlib/zutil.h b/erts/emulator/zlib/zutil.h
index d9a20ae1bf..0bc7f4ecd1 100644
--- a/erts/emulator/zlib/zutil.h
+++ b/erts/emulator/zlib/zutil.h
@@ -193,6 +193,7 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
(!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0)
ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
+ ZEXTERN uLong ZEXPORT crc32_combine_gen64 OF((z_off_t));
#endif
/* common defaults */
diff --git a/erts/epmd/src/epmd_srv.c b/erts/epmd/src/epmd_srv.c
index 18a589ac27..7613533bdd 100644
--- a/erts/epmd/src/epmd_srv.c
+++ b/erts/epmd/src/epmd_srv.c
@@ -745,12 +745,7 @@ static unsigned int get_creation(Node* node)
/* buf is actually one byte larger than bsize,
giving place for null termination */
-static void do_request(g, fd, s, buf, bsize)
- EpmdVars *g;
- int fd;
- Connection *s;
- char *buf;
- int bsize;
+static void do_request(EpmdVars *g, int fd, Connection *s, char *buf, int bsize)
{
char wbuf[OUTBUF_SIZE]; /* Buffer for writing */
int i;
diff --git a/erts/etc/common/Makefile.in b/erts/etc/common/Makefile.in
index 86f63dcf41..3c5a5cee64 100644
--- a/erts/etc/common/Makefile.in
+++ b/erts/etc/common/Makefile.in
@@ -168,7 +168,6 @@ INSTALL_PROGS = \
$(BINDIR)/erlsrv.exe \
$(BINDIR)/erl.exe \
$(BINDIR)/erl_log.exe\
- $(BINDIR)/werl.exe \
$(BINDIR)/$(ERLEXEC) \
$(INSTALL_EMBEDDED_PROGS)
@@ -216,6 +215,12 @@ INSTALL_PROGS = \
$(INSTALL_EMBEDDED_PROGS)
endif
+CREATE_DIRS=$(OBJDIR) $(BINDIR)
+
+ifneq ($(strip $(CREATE_DIRS)),)
+_create_dirs := $(shell mkdir -p $(CREATE_DIRS))
+endif
+
.PHONY: etc
etc: $(ENTRY_OBJ) $(INSTALL_PROGS) $(EXTRA_LIBS) $(INSTALL_LIBS) $(TEXTFILES) $(INSTALL_TOP_BIN)
@@ -267,13 +272,12 @@ endif
rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/vxcall.o
rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/erl.o
rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/erl_log.o
- rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/werl.o
rm -f $(TEXTFILES)
rm -f *~ core
#------------------------------------------------------------------------
# Windows specific targets
-# The windows platform is quite different from the others. erl/werl are small C programs
+# The windows platform is quite different from the others. erl are small C programs
# loading a DLL. INI files are used instead of environment variables and the Install
# script is actually a program, also Install has an INI file which tells of emulator
# versions etc.
@@ -287,9 +291,6 @@ $(BINDIR)/$(ERLEXEC): $(OBJDIR)/erlexec.o $(OBJDIR)/win_erlexec.o $(OBJDIR)/init
$(BINDIR)/erl@EXEEXT@: $(OBJDIR)/erl.o $(OBJDIR)/init_file.o $(OBJDIR)/$(ERLRES_OBJ)
$(V_LD) $(LDFLAGS) -o $@ $(OBJDIR)/erl.o $(OBJDIR)/init_file.o $(OBJDIR)/$(ERLRES_OBJ)
-$(BINDIR)/werl@EXEEXT@: $(OBJDIR)/werl.o $(OBJDIR)/init_file.o $(OBJDIR)/$(ERLRES_OBJ)
- $(V_LD) $(LDFLAGS) -o $@ $(OBJDIR)/werl.o $(OBJDIR)/init_file.o $(OBJDIR)/$(ERLRES_OBJ)
-
$(BINDIR)/erl_log@EXEEXT@: $(OBJDIR)/erl_log.o
$(V_LD) $(LDFLAGS) -o $@ $(OBJDIR)/erl_log.o
@@ -370,10 +371,6 @@ $(OBJDIR)/erlsrv_util.o: $(WINETC)/erlsrv/erlsrv_util.c $(ERLSRV_HEADERS) \
$(OBJDIR)/erlsrv_logmess.h $(RC_GENERATED)
$(V_CC) $(CFLAGS) -I$(OBJDIR) $(MT_FLAG) -o $@ -c $<
-$(OBJDIR)/werl.o: $(WINETC)/erl.c $(WINETC)/init_file.h $(RC_GENERATED)
- $(V_CC) $(CFLAGS) -DBUILD_TYPE=\"-$(TYPE)\" -DERL_RUN_SHARED_LIB=1 \
- -DWIN32_WERL -o $@ -c $(WINETC)/erl.c
-
$(OBJDIR)/erl_log.o: $(WINETC)/erl_log.c $(RC_GENERATED)
$(V_CC) $(CFLAGS) -DBUILD_TYPE=\"-$(TYPE)\" -DERL_RUN_SHARED_LIB=1 \
-o $@ -c $(WINETC)/erl_log.c
diff --git a/erts/etc/common/ct_run.c b/erts/etc/common/ct_run.c
index 0e9c2bea83..bea4892ce2 100644
--- a/erts/etc/common/ct_run.c
+++ b/erts/etc/common/ct_run.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2010-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2010-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,7 +77,6 @@ static void* emalloc(size_t size);
static void efree(void *p);
#endif
static char* strsave(char* string);
-static void push_words(char* src);
static int run_erlang(char* name, char** argv);
static char* get_default_emulator(char* progname);
#ifdef __WIN32__
@@ -187,8 +186,7 @@ int main(int argc, char** argv)
eargv_base = (char **) emalloc(eargv_size*sizeof(char*));
eargv = eargv_base;
eargc = 0;
- push_words(emulator);
- free(emulator);
+ PUSH(emulator);
eargc_base = eargc;
eargv = eargv + eargv_size/2;
eargc = 0;
@@ -319,26 +317,6 @@ int main(int argc, char** argv)
return run_erlang(eargv[0], eargv);
}
-static void
-push_words(char* src)
-{
- char sbuf[MAXPATHLEN];
- char* dst;
-
- dst = sbuf;
- while ((*dst++ = *src++) != '\0') {
- if (isspace((int)*src)) {
- *dst = '\0';
- PUSH(strsave(sbuf));
- dst = sbuf;
- do {
- src++;
- } while (isspace((int)*src));
- }
- }
- if (sbuf[0])
- PUSH(strsave(sbuf));
-}
#ifdef __WIN32__
wchar_t *make_commandline(char **argv)
{
diff --git a/erts/etc/common/erlc.c b/erts/etc/common/erlc.c
index 6cded37733..1493c6f6ff 100644
--- a/erts/etc/common/erlc.c
+++ b/erts/etc/common/erlc.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1997-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1997-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -742,14 +742,16 @@ call_compile_server(char** argv)
ei_x_encode_atom(&args, "encoding");
ei_x_encode_atom(&args, get_encoding());
ei_x_encode_atom(&args, "cwd");
- ei_x_encode_string(&args, cwd);
+ ei_x_encode_binary(&args, cwd, strlen(cwd));
ei_x_encode_atom(&args, "env");
encode_env(&args);
ei_x_encode_atom(&args, "command_line");
argc = 0;
while (argv[argc]) {
+ char *arg;
ei_x_encode_list_header(&args, 1);
- ei_x_encode_string(&args, possibly_unquote(argv[argc]));
+ arg = possibly_unquote(argv[argc]);
+ ei_x_encode_binary(&args, arg, strlen(arg));
argc++;
}
ei_x_encode_empty_list(&args); /* End of command_line */
@@ -773,7 +775,6 @@ call_compile_server(char** argv)
/*
* Decode the answer.
*/
-
dec_index = 0;
if (ei_decode_atom(reply.buff, &dec_index, atom) == 0 &&
strcmp(atom, "wrong_config") == 0) {
diff --git a/erts/etc/common/erlexec.c b/erts/etc/common/erlexec.c
index fa951ae770..888df87e35 100644
--- a/erts/etc/common/erlexec.c
+++ b/erts/etc/common/erlexec.c
@@ -39,14 +39,12 @@
#define DIRSEP "\\"
#define PATHSEP ";"
#define NULL_DEVICE "nul"
-#define BINARY_EXT ""
#define DLL_EXT ".dll"
#define EMULATOR_EXECUTABLE "beam.dll"
#else
#define PATHSEP ":"
#define DIRSEP "/"
#define NULL_DEVICE "/dev/null"
-#define BINARY_EXT ""
#define EMULATOR_EXECUTABLE "beam"
#endif
@@ -154,6 +152,7 @@ static char *plush_val_switches[] = {
"max",
"maxk",
"maxel",
+ "maxib",
"mqd",
"",
NULL
@@ -219,7 +218,6 @@ static char* possibly_quote(char* arg);
/*
* Functions from win_erlexec.c
*/
-int start_win_emulator(char* emu, char *startprog,char** argv, int start_detached);
int start_emulator(char* emu, char*start_prog, char** argv, int start_detached);
#endif
@@ -247,7 +245,7 @@ static const char* emu_flavor = DEFAULT_SUFFIX; /* Flavor of emulator (smp, jit
#ifdef __WIN32__
static char *start_emulator_program = NULL; /* For detached mode -
- erl.exe/werl.exe */
+ erl.exe */
static char* key_val_name = ERLANG_VERSION; /* Used by the registry
* access functions.
*/
@@ -257,7 +255,6 @@ static int config_script_cnt = 0;
static int got_start_erl = 0;
static HANDLE this_module_handle;
-static int run_werl;
static WCHAR *utf8_to_utf16(unsigned char *bytes);
static char *utf16_to_utf8(WCHAR *wstr);
static WCHAR *latin1_to_utf16(char *str);
@@ -415,7 +412,7 @@ static void add_boot_config(void)
#define NEXT_ARG_CHECK() NEXT_ARG_CHECK_NAMED(argv[i])
#ifdef __WIN32__
-__declspec(dllexport) int win_erlexec(int argc, char **argv, HANDLE module, int windowed)
+__declspec(dllexport) int win_erlexec(int argc, char **argv, HANDLE module)
#else
int main(int argc, char **argv)
#endif
@@ -436,7 +433,6 @@ int main(int argc, char **argv)
#ifdef __WIN32__
this_module_handle = module;
- run_werl = windowed;
/* if we started this erl just to get a detached emulator,
* the arguments are already prepared for beam, so we skip
* directly to start_emulator */
@@ -454,6 +450,18 @@ int main(int argc, char **argv)
Eargsp[argc] = NULL;
emu = argv[0];
start_emulator_program = strsave(argv[0]);
+ /* We set the stdandard handles to nul in order for prim_tty_nif
+ and erlang:display_string to work without returning ebadf for
+ detached emulators */
+ SetStdHandle(STD_INPUT_HANDLE,
+ CreateFile("nul", GENERIC_READ, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL));
+ SetStdHandle(STD_OUTPUT_HANDLE,
+ CreateFile("nul", GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL));
+ SetStdHandle(STD_ERROR_HANDLE,
+ CreateFile("nul", GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL));
goto skip_arg_massage;
}
free_env_val(s);
@@ -523,7 +531,7 @@ int main(int argc, char **argv)
emu = add_extra_suffixes(emu);
emu_name = strsave(emu);
- erts_snprintf(tmpStr, sizeof(tmpStr), "%s" DIRSEP "%s" BINARY_EXT, bindir, emu);
+ erts_snprintf(tmpStr, sizeof(tmpStr), "%s" DIRSEP "%s", bindir, emu);
emu = strsave(tmpStr);
s = get_env("ESCRIPT_NAME");
@@ -651,13 +659,12 @@ int main(int argc, char **argv)
break;
case 'd':
- if (strcmp(argv[i], "-detached") != 0) {
- add_arg(argv[i]);
- } else {
- start_detached = 1;
- add_args("-noshell", "-noinput", NULL);
- }
- break;
+ add_arg(argv[i]);
+ if (strcmp(argv[i], "-detached") == 0) {
+ start_detached = 1;
+ add_args("-noshell", "-noinput", NULL);
+ }
+ break;
case 'e':
if (strcmp(argv[i], "-extra") == 0) {
@@ -1119,24 +1126,7 @@ int main(int argc, char **argv)
skip_arg_massage:
/*DebugBreak();*/
- if (run_werl) {
- if (start_detached) {
- char *p;
- /* transform werl to erl */
- p = start_emulator_program+strlen(start_emulator_program);
- while (--p >= start_emulator_program && *p != '/' && *p != '\\' &&
- *p != 'W' && *p != 'w')
- ;
- if (p >= start_emulator_program && (*p == 'W' || *p == 'w') &&
- (p[1] == 'E' || p[1] == 'e') && (p[2] == 'R' || p[2] == 'r') &&
- (p[3] == 'L' || p[3] == 'l')) {
- memmove(p,p+1,strlen(p));
- }
- }
- return start_win_emulator(emu, start_emulator_program, Eargsp, start_detached);
- } else {
- return start_emulator(emu, start_emulator_program, Eargsp, start_detached);
- }
+ return start_emulator(emu, start_emulator_program, Eargsp, start_detached);
#else
@@ -1234,7 +1224,7 @@ usage_aux(void)
"[-emu_type TYPE] [-emu_flavor FLAVOR] "
"[-args_file FILENAME] [+A THREADS] [+a SIZE] [+B[c|d|i]] [+c [BOOLEAN]] "
"[+C MODE] [+dcg DECENTRALIZED_COUNTER_GROUPS_LIMIT] [+h HEAP_SIZE_OPTION] "
- "[+J[Pperf] JIT_OPTION] "
+ "[+J[Pperf|Msingle] JIT_OPTION] "
"[+M<SUBSWITCH> <ARGUMENT>] [+P MAX_PROCS] [+Q MAX_PORTS] "
"[+R COMPAT_REL] "
"[+r] [+rg READER_GROUPS_LIMIT] [+s<SUBSWITCH> SCHEDULER_OPTION] "
@@ -1602,6 +1592,14 @@ static void get_parameters(int argc, char** argv)
emu = EMULATOR_EXECUTABLE;
start_emulator_program = strsave(argv[0]);
+ /* in wsl argv[0] is given as "erl.exe", but start_emulator_program should be
+ an absolute path, so we prepend BINDIR to it */
+ if (strcmp(start_emulator_program, "erl.exe") == 0) {
+ erts_snprintf(tmpStr, sizeof(tmpStr), "%s" DIRSEP "%s", bindir,
+ start_emulator_program);
+ start_emulator_program = strsave(tmpStr);
+ }
+
free(ini_filename);
}
diff --git a/erts/etc/common/escript.c b/erts/etc/common/escript.c
index 078937e676..c7418b2ace 100644
--- a/erts/etc/common/escript.c
+++ b/erts/etc/common/escript.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2007-2018. All Rights Reserved.
+ * Copyright Ericsson AB 2007-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -286,7 +286,7 @@ find_prog(char *origpath)
beg = end + 1;
continue;
}
- strncpy(dir, beg, sz);
+ memcpy(dir, beg, sz);
dir[sz] = '\0';
beg = end + 1;
@@ -510,8 +510,8 @@ main(int argc, char** argv)
PUSH(emulator);
PUSH("+B");
- PUSH2("-boot", "no_dot_erlang");
PUSH("-noshell");
+ PUSH2("-boot", "no_dot_erlang");
/*
* Read options from the %%! row in the script and add them as args
diff --git a/erts/etc/common/etc_common.h b/erts/etc/common/etc_common.h
index 289a33b42a..8f6398fa71 100644
--- a/erts/etc/common/etc_common.h
+++ b/erts/etc/common/etc_common.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2017-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2017-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
# include <io.h>
# include <winbase.h>
# include <process.h>
+# include <direct.h> // _getcwd
#endif
#include <errno.h>
diff --git a/erts/etc/common/heart.c b/erts/etc/common/heart.c
index 297e41dca2..f45c18f3e6 100644
--- a/erts/etc/common/heart.c
+++ b/erts/etc/common/heart.c
@@ -354,8 +354,7 @@ int main(int argc, char **argv) {
* message loop
*/
static int
-message_loop(erlin_fd, erlout_fd)
- int erlin_fd, erlout_fd;
+message_loop(int erlin_fd, int erlout_fd)
{
int i;
time_t now, last_received;
@@ -776,8 +775,7 @@ int wait_until_close_write_or_env_tmo(int tmo) {
* Sends an HEART_ACK.
*/
static int
-notify_ack(fd)
- int fd;
+notify_ack(int fd)
{
struct msg m;
@@ -824,9 +822,7 @@ heart_cmd_reply(int fd, char *s)
* FIXME.
*/
static int
-write_message(fd, mp)
- int fd;
- struct msg *mp;
+write_message(int fd, struct msg *mp)
{
int len = ntohs(mp->len);
@@ -853,9 +849,7 @@ write_message(fd, mp)
* message.
*/
static int
-read_message(fd, mp)
- int fd;
- struct msg *mp;
+read_message(int fd, struct msg *mp)
{
int rlen, i;
unsigned char* tmp;
@@ -891,9 +885,7 @@ read_message(fd, mp)
* bytes read (i.e. len) , 0 if eof, or < 0 if error. len must be > 0.
*/
static int
-read_fill(fd, buf, len)
- int fd, len;
- char *buf;
+read_fill(int fd, char *buf, int len)
{
int i, got = 0;
@@ -914,9 +906,7 @@ read_fill(fd, buf, len)
* 0 if eof, or < 0 if error. len > maxlen > 0 must hold.
*/
static int
-read_skip(fd, buf, maxlen, len)
- int fd, maxlen, len;
- char *buf;
+read_skip(int fd, char *buf, int maxlen, int len)
{
int i, got = 0;
char c;
diff --git a/erts/etc/common/inet_gethost.c b/erts/etc/common/inet_gethost.c
index af313e4185..422384c179 100644
--- a/erts/etc/common/inet_gethost.c
+++ b/erts/etc/common/inet_gethost.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1998-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1998-2022. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1754,12 +1754,11 @@ static int worker_loop(void)
/* Decode the request... */
serial = get_serial(req);
if (OP_CONTROL == (op = get_op(req))) {
- CtlType ctl;
if (serial != INVALID_SERIAL) {
DEBUGF(1, ("Worker got invalid serial: %d.", serial));
exit(0);
}
- switch (ctl = get_ctl(req)) {
+ switch (get_ctl(req)) {
case SETOPT_DEBUG_LEVEL:
debug_level = get_debug_level(req);
DEBUGF(debug_level,
diff --git a/erts/etc/unix/etp.py b/erts/etc/unix/etp.py
index 53b8bd173f..ac4ff2a118 100644
--- a/erts/etc/unix/etp.py
+++ b/erts/etc/unix/etp.py
@@ -2,7 +2,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2013-2021. All Rights Reserved.
+# Copyright Ericsson AB 2013-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -686,10 +686,10 @@ def strip_literal_tag(valobj):
return valobj
def init(target):
- names = ['beam_run_process', 'beam_normal_exit', 'beam_exit', 'beam_save_calls',
- 'beam_bif_export_trap', 'beam_export_trampoline', 'beam_continue_exit',
- 'beam_return_to_trace', 'beam_return_trace', 'beam_exception_trace',
- 'beam_return_time_trace']
+ names = ['beam_run_process', 'beam_normal_exit', 'beam_exit', 'beam_save_calls_export',
+ 'beam_save_calls_fun', 'beam_bif_export_trap', 'beam_export_trampoline',
+ 'beam_continue_exit', 'beam_return_to_trace', 'beam_return_trace',
+ 'beam_exception_trace', 'beam_call_trace_return']
for name in names:
code_pointers[global_var(name, target).unsigned] = name
diff --git a/erts/etc/unix/to_erl.c b/erts/etc/unix/to_erl.c
index f9ca5f6373..4de4ac4d64 100644
--- a/erts/etc/unix/to_erl.c
+++ b/erts/etc/unix/to_erl.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1996-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1996-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,13 +82,13 @@
# define STRERROR(x) ""
#endif
-#define noDEBUG
+#define noDEBUG_TOERL
#define PIPE_DIR "/tmp/"
#define PIPE_STUBNAME "erlang.pipe"
#define PIPE_STUBLEN strlen(PIPE_STUBNAME)
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
#define STATUS(s) { fprintf(stderr, (s)); fflush(stderr); }
#else
#define STATUS(s)
@@ -106,7 +106,7 @@ static int protocol_ver = RUN_ERL_LO_VER; /* assume lowest to begin with */
static int write_all(int fd, const char* buf, int len);
static int window_size_seq(char* buf, size_t bufsz);
static int version_handshake(char* buf, int len, int wfd);
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
static void show_terminal_settings(struct termios *);
#endif
@@ -155,7 +155,7 @@ int main(int argc, char **argv)
pipeIx = 2;
}
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
fprintf(stderr, "%s: pid is : %d\n", argv[0], (int)getpid());
#endif
@@ -209,25 +209,25 @@ int main(int argc, char **argv)
}
if ((rfd = open (FIFO1, O_RDONLY|DONT_BLOCK_PLEASE, 0)) < 0) {
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
fprintf(stderr, "Could not open FIFO %s for reading.\n", FIFO1);
#endif
fprintf(stderr, "No running Erlang on pipe %s: %s\n", pipename, strerror(errno));
exit(1);
}
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
fprintf(stderr, "to_erl: %s opened for reading\n", FIFO1);
#endif
if ((wfd = open (FIFO2, O_WRONLY|DONT_BLOCK_PLEASE, 0)) < 0) {
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
fprintf(stderr, "Could not open FIFO %s for writing.\n", FIFO2);
#endif
fprintf(stderr, "No running Erlang on pipe %s: %s\n", pipename, strerror(errno));
close(rfd);
exit(1);
}
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
fprintf(stderr, "to_erl: %s opened for writing\n", FIFO2);
#endif
@@ -245,7 +245,7 @@ int main(int argc, char **argv)
}
tty_smode = tty_rmode;
tty_eof = '\004'; /* Ctrl+D to exit */
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
show_terminal_settings(&tty_rmode);
#endif
tty_smode.c_iflag =
@@ -347,7 +347,7 @@ int main(int argc, char **argv)
tcsetattr(0, TCSADRAIN, &tty_smode);
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
show_terminal_settings(&tty_smode);
#endif
/*
@@ -420,7 +420,7 @@ int main(int argc, char **argv)
}
if (len) {
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
write_all(1, buf, len);
#endif
if (write_all(wfd, buf, len) != len) {
@@ -580,7 +580,7 @@ static int version_handshake(char* buf, int len, int wfd)
}
-#ifdef DEBUG
+#ifdef DEBUG_TOERL
#define S(x) ((x) > 0 ? 1 : 0)
static void show_terminal_settings(struct termios *t)
diff --git a/erts/etc/win32/Install.c b/erts/etc/win32/Install.c
index 1b8f894dc9..7ed338d744 100644
--- a/erts/etc/win32/Install.c
+++ b/erts/etc/win32/Install.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2003-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2003-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
*/
#include <windows.h>
+#include <winerror.h>
#include <stdio.h>
#include <stdlib.h>
#include "init_file.h"
@@ -47,11 +48,12 @@ int wmain(int argc, wchar_t **argv)
InitFile *ini_file;
InitSection *ini_section;
HANDLE module = GetModuleHandle(NULL);
- wchar_t *binaries[] = { L"erl.exe", L"werl.exe", L"erlc.exe", L"erl_call.exe",
+ wchar_t *binaries[] = { L"erl.exe", L"erlc.exe", L"erl_call.exe",
L"dialyzer.exe",
L"typer.exe",
L"escript.exe", L"ct_run.exe", NULL };
wchar_t *scripts[] = { L"start_clean.boot", L"start_sasl.boot", L"no_dot_erlang.boot", NULL };
+ wchar_t *links[][2] = { { L"erl.exe", L"werl.exe" }, NULL };
wchar_t fromname[MAX_PATH];
wchar_t toname[MAX_PATH];
size_t converted;
@@ -175,7 +177,32 @@ int wmain(int argc, wchar_t **argv)
fprintf(stderr,"Continuing installation anyway...\n");
}
}
-
+
+ for (i = 0; links[i][0] != NULL; ++i) {
+ swprintf(toname,MAX_PATH,L"%s\\%s",bin_dir,links[i][1]);
+ if (!CreateSymbolicLinkW(toname,links[i][0],0)) {
+ DWORD err = GetLastError();
+ if (err == ERROR_PRIVILEGE_NOT_HELD) {
+ fprintf(stderr,"Must be administrator to create link, copying %S instead.\n",
+ links[i][0]);
+ swprintf(fromname,MAX_PATH,L"%s\\%s",bin_dir,links[i][0]);
+ if (!CopyFileW(fromname,toname,FALSE)) {
+ fprintf(stderr,"Could not copy file %S to %S\n",
+ fromname,toname);
+ fprintf(stderr,"Continuing installation anyway...\n");
+ }
+ } else {
+ wchar_t buf[256];
+ FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ buf, (sizeof(buf) / sizeof(wchar_t)), NULL);
+ fprintf(stderr,"Could not create links from %S to %S %d: %S\n",
+ fromname, toname, err, buf);
+ fprintf(stderr,"Continuing installation anyway...\n");
+ }
+ }
+ }
+
for (i = 0; scripts[i] != NULL; ++i) {
swprintf(fromname,MAX_PATH,L"%s\\%s",release_dir,scripts[i]);
swprintf(toname,MAX_PATH,L"%s\\%s",bin_dir,scripts[i]);
diff --git a/erts/etc/win32/Makefile b/erts/etc/win32/Makefile
index c6376ebe74..04c3633403 100644
--- a/erts/etc/win32/Makefile
+++ b/erts/etc/win32/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2016. All Rights Reserved.
+# Copyright Ericsson AB 1996-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -39,7 +39,6 @@ ROOTDIR = $(ERL_TOP)/erts
INSTALL_PROGS = \
$(BINDIR)/inet_gethost.exe \
$(BINDIR)/erl.exe \
- $(BINDIR)/werl.exe \
$(BINDIR)/heart.exe \
$(BINDIR)/erlc.exe \
$(BINDIR)/erlsrv.exe \
diff --git a/erts/etc/win32/erl.c b/erts/etc/win32/erl.c
index 99a41b99e5..1b6f89c11c 100644
--- a/erts/etc/win32/erl.c
+++ b/erts/etc/win32/erl.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2003-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2003-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
#include <stdlib.h>
#include "init_file.h"
-typedef int ErlexecFunction(int, char **, HANDLE, int);
+typedef int ErlexecFunction(int, char **, HANDLE);
#define INI_FILENAME L"erl.ini"
#define INI_SECTION "erlang"
@@ -35,18 +35,8 @@ static void error(char* format, ...);
static wchar_t *erlexec_name;
static wchar_t *erlexec_dir;
-#ifdef WIN32_WERL
-#define WERL 1
-int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
- PWSTR szCmdLine, int iCmdShow)
-{
- int argc = __argc;
- wchar_t **argv = __wargv;
-#else
-#define WERL 0
int wmain(int argc, wchar_t **argv)
{
-#endif
HANDLE erlexec_handle; /* Instance */
ErlexecFunction *win_erlexec;
wchar_t *path = malloc(100*sizeof(wchar_t));
@@ -120,7 +110,7 @@ int wmain(int argc, wchar_t **argv)
}
#endif
- return (*win_erlexec)(argc,utf8argv,erlexec_handle,WERL);
+ return (*win_erlexec)(argc,utf8argv,erlexec_handle);
}
@@ -316,7 +306,6 @@ static void get_parameters(void)
free(ini_filename);
}
-
static void error(char* format, ...)
{
char sbuf[2048];
@@ -326,11 +315,6 @@ static void error(char* format, ...)
vsprintf(sbuf, format, ap);
va_end(ap);
-#ifndef WIN32_WERL
- fprintf(stderr, "%s\n", sbuf);
-#else
- MessageBox(NULL, sbuf, "Werl", MB_OK|MB_ICONERROR);
-#endif
+ fprintf(stderr, "%s\n", sbuf);
exit(1);
}
-
diff --git a/erts/etc/win32/nsis/erlang20.nsi b/erts/etc/win32/nsis/erlang20.nsi
index d8d1883e3a..5380a57742 100644
--- a/erts/etc/win32/nsis/erlang20.nsi
+++ b/erts/etc/win32/nsis/erlang20.nsi
@@ -199,7 +199,7 @@ SectionIn 1 RO
CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER"
continue_create:
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Erlang.lnk" \
- "$INSTDIR\bin\werl.exe"
+ "$INSTDIR\bin\erl.exe"
!insertmacro MUI_STARTMENU_WRITE_END
; And once again, the verbosity...
diff --git a/erts/etc/win32/win_erlexec.c b/erts/etc/win32/win_erlexec.c
index 7b21ed3785..21129ddc3c 100644
--- a/erts/etc/win32/win_erlexec.c
+++ b/erts/etc/win32/win_erlexec.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1997-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1997-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,6 @@ static char* win32_errorstr(int error);
static int has_console(void);
static char** fnuttify_argv(char **argv);
static void free_fnuttified(char **v);
-static int windowed = 0;
#ifdef LOAD_BEAM_DYNAMICALLY
typedef int SysGetKeyFunction(int);
@@ -133,103 +132,6 @@ free_env_val(char *value)
free(value);
}
-
-int
-start_win_emulator(char* utf8emu, char *utf8start_prog, char** utf8argv, int start_detached)
-{
- int len;
- int argc = 0;
-
- windowed = 1;
- while (utf8argv[argc] != NULL) {
- ++argc;
- }
-
- if (start_detached) {
- wchar_t *start_prog=NULL;
- int result;
- int i;
- wchar_t **argv;
- close(0);
- close(1);
- close(2);
-
- set_env("ERL_CONSOLE_MODE", "detached");
- set_env(DLL_ENV, utf8emu);
-
- utf8argv[0] = utf8start_prog;
- utf8argv = fnuttify_argv(utf8argv);
-
- len = MultiByteToWideChar(CP_UTF8, 0, utf8start_prog, -1, NULL, 0);
- start_prog = malloc(len*sizeof(wchar_t));
- MultiByteToWideChar(CP_UTF8, 0, utf8start_prog, -1, start_prog, len);
-
- /* Convert utf8argv to multibyte argv */
- argv = malloc((argc+1) * sizeof(wchar_t*));
- for (i=0; i<argc; i++) {
- len = MultiByteToWideChar(CP_UTF8, 0, utf8argv[i], -1, NULL, 0);
- argv[i] = malloc(len*sizeof(wchar_t));
- MultiByteToWideChar(CP_UTF8, 0, utf8argv[i], -1, argv[i], len);
- }
- argv[argc] = NULL;
-
-#ifdef ARGS_HARDDEBUG
- {
- wchar_t tempbuf[2048] = L"";
- wchar_t *sbuf;
- int i;
- sbuf=tempbuf;
- sbuf += swprintf(sbuf, 2048, L"utf16: %s\n", start_prog);
- for (i = 0; i < argc; ++i) {
- sbuf += swprintf(sbuf, 2048, L"|%s|", argv[i]);
- };
- sbuf += swprintf(sbuf, 2048, L"\nutf8: \n");
- for (i = 0; i < argc; ++i) {
- sbuf += swprintf(sbuf, 2048, L"|%S|", utf8argv[i]);
- };
- MessageBoxW(NULL, tempbuf, L"respawn args", MB_OK|MB_ICONERROR);
- }
-#endif
-
- result = _wspawnv(_P_DETACH, start_prog, argv);
- free_fnuttified(utf8argv);
- if (result == -1) {
- error("Failed to execute %S: %s", start_prog, win32_errorstr(_doserrno));
- }
- } else {
- wchar_t *emu=NULL;
-#ifdef LOAD_BEAM_DYNAMICALLY
- HMODULE beam_module = NULL;
- len = MultiByteToWideChar(CP_UTF8, 0, utf8emu, -1, NULL, 0);
- emu = malloc(len*sizeof(wchar_t));
- MultiByteToWideChar(CP_UTF8, 0, utf8emu, -1, emu, len);
-#ifdef ARGS_HARDDEBUG
- {
- char sbuf[2048] = "";
- int i;
- strcat(sbuf,utf8emu);
- strcat(sbuf,":");
- for (i = 0; i < argc; ++i) {
- strcat(sbuf,"|");
- strcat(sbuf, utf8argv[i]);
- strcat(sbuf,"| ");
- }
- MessageBox(NULL, sbuf, "erl_start args", MB_OK|MB_ICONERROR);
- }
-#endif
- beam_module = load_win_beam_dll(emu);
-#endif
- set_env("ERL_CONSOLE_MODE", "window");
-#ifdef LOAD_BEAM_DYNAMICALLY
- (*sys_primitive_init_p)(beam_module);
- (*erl_start_p)(argc,utf8argv);
-#else
- erl_start(argc,utf8argv);
-#endif
- }
- return 0;
-}
-
void __cdecl
do_keep_window(void)
{
@@ -244,8 +146,6 @@ do_keep_window(void)
int
start_emulator(char* utf8emu, char *utf8start_prog, char** utf8argv, int start_detached)
{
- static char console_mode[] = "tty:ccc";
- char* fd_type;
char* title;
int len;
int argc = 0;
@@ -254,8 +154,6 @@ start_emulator(char* utf8emu, char *utf8start_prog, char** utf8argv, int start_d
fprintf(stderr,"utf8emu = %s, start_prog = %s\n", utf8emu, utf8start_prog);
#endif
- fd_type = strchr(console_mode, ':');
- fd_type++;
_flushall();
while (utf8argv[argc] != NULL) {
@@ -312,7 +210,14 @@ start_emulator(char* utf8emu, char *utf8start_prog, char** utf8argv, int start_d
if (result == (HANDLE)-1) {
#ifdef ARGS_HARDDEBUG
- MessageBox(NULL, "_wspawnv failed","Start detached",MB_OK);
+ wchar_t buf[256], buffer[256], path[1024];
+ DWORD err = GetLastError();
+ FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ buf, (sizeof(buf) / sizeof(wchar_t)), NULL);
+ GetEnvironmentVariableW(L"PATH",path,sizeof(path) / sizeof(wchar_t));
+ wsprintfW(buffer,L"_wspawnv failed %s(%d)\nPATH=%s", buf, err, path);
+ MessageBoxW(NULL, buffer , L"Start detached",MB_OK);
#endif
return 1;
}
@@ -339,7 +244,7 @@ start_emulator(char* utf8emu, char *utf8start_prog, char** utf8argv, int start_d
}
#endif
beam_module = load_win_beam_dll(emu);
-#endif
+#endif
/*
* Start the emulator.
@@ -350,8 +255,8 @@ start_emulator(char* utf8emu, char *utf8start_prog, char** utf8argv, int start_d
SetConsoleTitle(title);
}
free_env_val(title);
-
- set_env("ERL_CONSOLE_MODE", console_mode);
+
+ set_env("ERL_CONSOLE_MODE", "windowed");
if (keep_window) {
atexit(do_keep_window);
}
@@ -387,10 +292,8 @@ error(char* format, ...)
vsprintf(sbuf, format, ap);
va_end(ap);
- if (!windowed && has_console()) {
+ if (has_console()) {
fprintf(stderr, "%s\n", sbuf);
- } else {
- MessageBox(NULL, sbuf, "Werl", MB_OK|MB_ICONERROR);
}
exit(1);
}
diff --git a/erts/include/internal/ethread.h b/erts/include/internal/ethread.h
index 460849ef2f..e1aa6d4126 100644
--- a/erts/include/internal/ethread.h
+++ b/erts/include/internal/ethread.h
@@ -497,10 +497,11 @@ typedef struct {
typedef struct {
int detached; /* boolean (default false) */
int suggested_stack_size; /* kilo words (default sys dependent) */
- char *name; /* max 14 char long (default no-name) */
+ char *name; /* max 15 char long (default no-name) */
} ethr_thr_opts;
#define ETHR_THR_OPTS_DEFAULT_INITER {0, -1, NULL}
+#define ETHR_THR_NAME_MAX 15
#if !defined(ETHR_TRY_INLINE_FUNCS) || defined(ETHR_AUX_IMPL__)
# define ETHR_NEED_SPINLOCK_PROTOTYPES__
diff --git a/erts/lib_src/Makefile.in b/erts/lib_src/Makefile.in
index 043ef2dbd0..392d10f493 100644
--- a/erts/lib_src/Makefile.in
+++ b/erts/lib_src/Makefile.in
@@ -94,7 +94,11 @@ CFLAGS += -DERTS_OPCODE_COUNTER_SUPPORT
PRE_LD=
else
override TYPE=opt
+ifeq (@JIT_ENABLED@, yes)
+OMIT_OMIT_FP=yes
+else
OMIT_FP=true
+endif
TYPE_SUFFIX=
PRE_LD=
endif
@@ -311,15 +315,6 @@ include $(YCF_SOURCE_DIR)/main_target.mk
$(OBJ_DIR)/MADE: $(YCF_EXECUTABLE) $(ETHREAD_LIB) $(ERTS_INTERNAL_LIBS)
$(gen_verbose)
-ifeq ($(OMIT_OMIT_FP),yes)
- @echo '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *'
- @echo '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *'
- @echo '* * * *'
- @echo '* * NOTE: Omit frame pointer optimization has been omitted * *'
- @echo '* * * *'
- @echo '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *'
- @echo '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *'
-endif
$(V_at)echo $? > $(OBJ_DIR)/MADE
#
diff --git a/erts/lib_src/pthread/ethread.c b/erts/lib_src/pthread/ethread.c
index b17aa3a17f..da4f1af11d 100644
--- a/erts/lib_src/pthread/ethread.c
+++ b/erts/lib_src/pthread/ethread.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2010-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2010-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -81,7 +81,7 @@ typedef struct {
void *prep_func_res;
size_t stacksize;
char *name;
- char name_buff[32];
+ char name_buff[ETHR_THR_NAME_MAX + 1];
} ethr_thr_wrap_data__;
static void *thr_wrapper(void *vtwd)
@@ -334,21 +334,9 @@ ethr_thr_create(ethr_tid *tid, void * (*func)(void *), void *arg,
twd.stacksize = 0;
if (opts && opts->name) {
- size_t nlen = sizeof(twd.name_buff);
-#ifdef __HAIKU__
- if (nlen > B_OS_NAME_LENGTH)
- nlen = B_OS_NAME_LENGTH;
-#else
- /*
- * Length of 16 is known to work. At least pthread_setname_np()
- * is documented to fail on too long name string, but documentation
- * does not say what the limit is. Do not have the time to dig
- * further into that now...
- */
- if (nlen > 16)
- nlen = 16;
-#endif
- snprintf(twd.name_buff, nlen, "%s", opts->name);
+ if (strlen(opts->name) >= sizeof(twd.name_buff))
+ return EINVAL;
+ strcpy(twd.name_buff, opts->name);
twd.name = twd.name_buff;
} else
twd.name = NULL;
@@ -506,6 +494,8 @@ ethr_getname(ethr_tid tid, char *buf, size_t len)
void
ethr_setname(char *name)
{
+ if (strlen(name) > ETHR_THR_NAME_MAX)
+ return;
#if defined(ETHR_HAVE_PTHREAD_SETNAME_NP_2)
pthread_setname_np(ethr_self(), name);
#elif defined(ETHR_HAVE_PTHREAD_SET_NAME_NP_2)
diff --git a/erts/lib_src/yielding_c_fun/main_target.mk b/erts/lib_src/yielding_c_fun/main_target.mk
index 4c97d4d9cf..0b06ce860c 100644
--- a/erts/lib_src/yielding_c_fun/main_target.mk
+++ b/erts/lib_src/yielding_c_fun/main_target.mk
@@ -26,16 +26,18 @@ YCF_SOURCES = $(sort $(wildcard $(YCF_SOURCE_DIR)/*.c) $(YCF_EXTRA_SOURCES))
YCF_OBJECTS = $(addprefix $(YCF_OBJ_DIR)/,$(notdir $(YCF_SOURCES:.c=.o)))
-YCF_CFLAGS = $(filter-out -Wstrict-prototypes -Wdeclaration-after-statement -Wmissing-prototypes,$(CFLAGS))
+# YCF is a short lived tool leaking memory deliberately. Disable all sanitizers.
+YCF_CFLAGS = $(filter-out -Wstrict-prototypes -Wdeclaration-after-statement -Wmissing-prototypes -fsanitize%,$(CFLAGS))
+YCF_LDFLAGS = $(filter-out -fsanitize%,$(LDFLAGS))
$(YCF_EXECUTABLE): $(YCF_OBJECTS)
- $(V_LD) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_OBJECTS) -o $@
+ $(V_LD) $(YCF_CFLAGS) $(YCF_LDFLAGS) $(YCF_OBJECTS) -o $@
$(YCF_OBJ_DIR)/%.o: $(YCF_SOURCE_DIR)/lib/tiny_regex_c/%.c $(YCF_HEADERS)
- $(V_CC) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@
+ $(V_CC) $(YCF_CFLAGS) $(YCF_LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@
$(YCF_OBJ_DIR)/%.o: $(YCF_SOURCE_DIR)/lib/simple_c_gc/%.c $(YCF_HEADERS)
- $(V_CC) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@
+ $(V_CC) $(YCF_CFLAGS) $(YCF_LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@
$(YCF_OBJ_DIR)/%.o: $(YCF_SOURCE_DIR)/%.c $(YCF_HEADERS)
- $(V_CC) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@
+ $(V_CC) $(YCF_CFLAGS) $(YCF_LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@
diff --git a/erts/prebuild.keep b/erts/prebuild.keep
index a2e6bd485b..8e695ec83a 100644
--- a/erts/prebuild.keep
+++ b/erts/prebuild.keep
@@ -1 +1 @@
-doc/
+doc
diff --git a/erts/preloaded/ebin/atomics.beam b/erts/preloaded/ebin/atomics.beam
index 6ab249d8fc..5b03c3a9f8 100644
--- a/erts/preloaded/ebin/atomics.beam
+++ b/erts/preloaded/ebin/atomics.beam
Binary files differ
diff --git a/erts/preloaded/ebin/counters.beam b/erts/preloaded/ebin/counters.beam
index 2d0580cc89..595bbe5de7 100644
--- a/erts/preloaded/ebin/counters.beam
+++ b/erts/preloaded/ebin/counters.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erl_init.beam b/erts/preloaded/ebin/erl_init.beam
index b0ee881e2f..eaa1c70fe8 100644
--- a/erts/preloaded/ebin/erl_init.beam
+++ b/erts/preloaded/ebin/erl_init.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erl_prim_loader.beam b/erts/preloaded/ebin/erl_prim_loader.beam
index 8612761a9c..2d34404a6b 100644
--- a/erts/preloaded/ebin/erl_prim_loader.beam
+++ b/erts/preloaded/ebin/erl_prim_loader.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erl_tracer.beam b/erts/preloaded/ebin/erl_tracer.beam
index 380f5d8ea9..d10d8c060b 100644
--- a/erts/preloaded/ebin/erl_tracer.beam
+++ b/erts/preloaded/ebin/erl_tracer.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam
index 63d08c4614..54cbf998c0 100644
--- a/erts/preloaded/ebin/erlang.beam
+++ b/erts/preloaded/ebin/erlang.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erts_code_purger.beam b/erts/preloaded/ebin/erts_code_purger.beam
index 7918e37d6a..2937dd1537 100644
--- a/erts/preloaded/ebin/erts_code_purger.beam
+++ b/erts/preloaded/ebin/erts_code_purger.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam b/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam
index b63e4dd6fc..d8a00bfcc1 100644
--- a/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam
+++ b/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erts_internal.beam b/erts/preloaded/ebin/erts_internal.beam
index c2a9d1fbea..96c2f347f1 100644
--- a/erts/preloaded/ebin/erts_internal.beam
+++ b/erts/preloaded/ebin/erts_internal.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erts_literal_area_collector.beam b/erts/preloaded/ebin/erts_literal_area_collector.beam
index 165bfa4622..9f36684c48 100644
--- a/erts/preloaded/ebin/erts_literal_area_collector.beam
+++ b/erts/preloaded/ebin/erts_literal_area_collector.beam
Binary files differ
diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam
index 93c95568ad..d2ea6b7025 100644
--- a/erts/preloaded/ebin/init.beam
+++ b/erts/preloaded/ebin/init.beam
Binary files differ
diff --git a/erts/preloaded/ebin/persistent_term.beam b/erts/preloaded/ebin/persistent_term.beam
index eee6b18bd4..a705183971 100644
--- a/erts/preloaded/ebin/persistent_term.beam
+++ b/erts/preloaded/ebin/persistent_term.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_buffer.beam b/erts/preloaded/ebin/prim_buffer.beam
index 70b331a353..64ea6f6a99 100644
--- a/erts/preloaded/ebin/prim_buffer.beam
+++ b/erts/preloaded/ebin/prim_buffer.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_eval.beam b/erts/preloaded/ebin/prim_eval.beam
index 7253202ea4..f2c7a12a50 100644
--- a/erts/preloaded/ebin/prim_eval.beam
+++ b/erts/preloaded/ebin/prim_eval.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_file.beam b/erts/preloaded/ebin/prim_file.beam
index 4bb0a1b9e7..1bd1624aaa 100644
--- a/erts/preloaded/ebin/prim_file.beam
+++ b/erts/preloaded/ebin/prim_file.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam
index 5f61ab8cba..40416564f5 100644
--- a/erts/preloaded/ebin/prim_inet.beam
+++ b/erts/preloaded/ebin/prim_inet.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_net.beam b/erts/preloaded/ebin/prim_net.beam
index a39a3d1115..2e9b21b19b 100644
--- a/erts/preloaded/ebin/prim_net.beam
+++ b/erts/preloaded/ebin/prim_net.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_socket.beam b/erts/preloaded/ebin/prim_socket.beam
index 7eb287597f..45f5ab71d9 100644
--- a/erts/preloaded/ebin/prim_socket.beam
+++ b/erts/preloaded/ebin/prim_socket.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_zip.beam b/erts/preloaded/ebin/prim_zip.beam
index 8bee02d875..040d1e3559 100644
--- a/erts/preloaded/ebin/prim_zip.beam
+++ b/erts/preloaded/ebin/prim_zip.beam
Binary files differ
diff --git a/erts/preloaded/ebin/socket_registry.beam b/erts/preloaded/ebin/socket_registry.beam
index c8f4e9e983..5e6b662b9f 100644
--- a/erts/preloaded/ebin/socket_registry.beam
+++ b/erts/preloaded/ebin/socket_registry.beam
Binary files differ
diff --git a/erts/preloaded/ebin/zlib.beam b/erts/preloaded/ebin/zlib.beam
index 53f355e465..35d9131ef0 100644
--- a/erts/preloaded/ebin/zlib.beam
+++ b/erts/preloaded/ebin/zlib.beam
Binary files differ
diff --git a/erts/preloaded/src/erl_prim_loader.erl b/erts/preloaded/src/erl_prim_loader.erl
index 20fda619b8..4442d00914 100644
--- a/erts/preloaded/src/erl_prim_loader.erl
+++ b/erts/preloaded/src/erl_prim_loader.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -53,10 +53,10 @@
-export([set_primary_archive/4]).
%% Used by test suites
--export([purge_archive_cache/0]).
+-export([purge_archive_cache/0, get_modules/3]).
-%% Used by init and the code server.
--export([get_modules/2,get_modules/3, is_basename/1]).
+%% Used by init and the code server
+-export([get_modules/2, is_basename/1]).
-include_lib("kernel/include/file.hrl").
diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl
index 41471e51e2..ddb81dfc76 100644
--- a/erts/preloaded/src/erlang.erl
+++ b/erts/preloaded/src/erlang.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -86,6 +86,7 @@
-type boolean() :: true | false.
-type byte() :: 0..255.
-type char() :: 0..16#10FFFF.
+-type dynamic() :: dynamic().
-type float() :: float().
-type function() :: fun().
-type identifier() :: pid() | port() | reference().
@@ -123,7 +124,7 @@
-type timeout() :: 'infinity' | non_neg_integer().
-type tuple() :: tuple().
-export_type([any/0, arity/0, atom/0, binary/0, bitstring/0, bool/0, boolean/0, byte/0,
- char/0, float/0, function/0, identifier/0, integer/0, iodata/0, iolist/0,
+ char/0, dynamic/0, float/0, function/0, identifier/0, integer/0, iodata/0, iolist/0,
list/0, list/1, map/0, maybe_improper_list/0, maybe_improper_list/2, mfa/0,
module/0, neg_integer/0, nil/0, no_return/0, node/0, non_neg_integer/0,
none/0, nonempty_binary/0, nonempty_bitstring/0, nonempty_improper_list/2,
@@ -216,7 +217,7 @@
-export([crc32/2, crc32_combine/3, date/0, decode_packet/3]).
-export([delete_element/2]).
-export([delete_module/1, demonitor/1, demonitor/2, display/1]).
--export([display_nl/0, display_string/1, erase/0, erase/1]).
+-export([display_string/1, display_string/2, erase/0, erase/1]).
-export([error/1, error/2, error/3, exit/1, exit/2, exit_signal/2, external_size/1]).
-export([external_size/2, finish_after_on_load/2, finish_loading/1, float/1]).
-export([float_to_binary/1, float_to_binary/2,
@@ -389,7 +390,7 @@
{meta, module(), term() } |
{meta_match_spec, trace_match_spec() | false | undefined} |
{call_count, non_neg_integer() | boolean() | undefined} |
- {call_time, [{pid(), non_neg_integer(),
+ {call_time | call_memory, [{pid(), non_neg_integer(),
non_neg_integer(), non_neg_integer()}] | boolean() | undefined}.
-type trace_info_flag() ::
@@ -843,15 +844,21 @@ unalias(_Alias) ->
display(_Term) ->
erlang:nif_error(undefined).
-%% display_nl/0
--spec erlang:display_nl() -> true.
-display_nl() ->
- erlang:nif_error(undefined).
-
%% display_string/1
-spec erlang:display_string(P1) -> true when
+ P1 :: string() | binary().
+display_string(String) ->
+ try erlang:display_string(stderr, String)
+ catch error:badarg:ST ->
+ [{erlang, display_string, _, [ErrorInfo]}|_] = ST,
+ erlang:error(badarg, [String], [ErrorInfo])
+ end.
+
+%% display_string/2
+-spec erlang:display_string(Device, P1) -> true when
+ Device :: stdin | stdout | stderr,
P1 :: string().
-display_string(_P1) ->
+display_string(_Stream,_P1) ->
erlang:nif_error(undefined).
%% dt_append_vm_tag_data/1
@@ -968,7 +975,11 @@ external_size(_Term) ->
%% external_size/2
-spec erlang:external_size(Term, Options) -> non_neg_integer() when
Term :: term(),
- Options :: [{minor_version, Version :: non_neg_integer()}].
+ Options :: [compressed |
+ {compressed, Level :: 0..9} |
+ deterministic |
+ {minor_version, Version :: 0..2} |
+ local ].
external_size(_Term, _Options) ->
erlang:nif_error(undefined).
@@ -1204,8 +1215,13 @@ halt() ->
%% halt/1
%% Shadowed by erl_bif_types: erlang:halt/1
--spec halt(Status) -> no_return() when
- Status :: non_neg_integer() | 'abort' | string().
+-spec halt(Status :: non_neg_integer()) ->
+ no_return();
+ (Abort :: abort) ->
+ no_return();
+ (CrashDumpSlogan :: string()) ->
+ no_return().
+
-dialyzer({no_return, halt/1}).
halt(Status) ->
try
@@ -1216,11 +1232,18 @@ halt(Status) ->
%% halt/2
%% Shadowed by erl_bif_types: erlang:halt/2
--spec halt(Status, Options) -> no_return() when
- Status :: non_neg_integer() | 'abort' | string(),
- Options :: [Option],
- Option :: {flush, boolean()}.
-halt(_Status, _Options) ->
+-type halt_options() ::
+ [{flush, boolean()}].
+
+-spec halt(Status :: non_neg_integer(), Options :: halt_options()) ->
+ no_return();
+ (Abort :: abort, Options :: halt_options()) ->
+ no_return();
+ (CrashDumpSlogan :: string(), Options :: halt_options()) ->
+ no_return().
+
+-dialyzer({no_return, halt/2}).
+halt(_, _) ->
erlang:nif_error(undefined).
%% has_prepared_code_on_load/1
@@ -2106,7 +2129,7 @@ trace_delivered(_Tracee) ->
Function :: atom(),
Arity :: arity(),
Item :: flags | tracer | traced | match_spec
- | meta | meta_match_spec | call_count | call_time | all,
+ | meta | meta_match_spec | call_count | call_time | call_memory | all,
Res :: trace_info_return().
trace_info(_PidPortFuncEvent, _Item) ->
erlang:nif_error(undefined).
@@ -2197,8 +2220,9 @@ get_module_info(_Module, _Item) ->
erlang:nif_error(undefined).
%% Shadowed by erl_bif_types: erlang:hd/1
--spec hd(List) -> term() when
- List :: [term(), ...].
+-spec hd(List) -> Head when
+ List :: nonempty_maybe_improper_list(),
+ Head :: term().
hd(_List) ->
erlang:nif_error(undefined).
@@ -2317,26 +2341,15 @@ is_tuple(_Term) ->
| {features_not_allowed, [atom()]}.
load_module(Mod, Code) ->
try
- Allowed =
- case erlang:module_loaded(erl_features) of
- true ->
- erl_features:load_allowed(Code);
- false -> ok
- end,
- case Allowed of
- {not_allowed, NotEnabled} ->
- {error, {features_not_allowed, NotEnabled}};
- ok ->
- case erlang:prepare_loading(Mod, Code) of
- {error,_}=Error ->
- Error;
- Prep when erlang:is_reference(Prep) ->
- case erlang:finish_loading([Prep]) of
- ok ->
- {module,Mod};
- {Error,[Mod]} ->
- {error,Error}
- end
+ case erlang:prepare_loading(Mod, Code) of
+ {error,_}=Error ->
+ Error;
+ Prep when erlang:is_reference(Prep) ->
+ case erlang:finish_loading([Prep]) of
+ ok ->
+ {module,Mod};
+ {Error,[Mod]} ->
+ {error,Error}
end
end
catch
@@ -2760,7 +2773,8 @@ term_to_binary(_Term) ->
Options :: [compressed |
{compressed, Level :: 0..9} |
deterministic |
- {minor_version, Version :: 0..2} ].
+ {minor_version, Version :: 0..2} |
+ local ].
term_to_binary(_Term, _Options) ->
erlang:nif_error(undefined).
@@ -2774,13 +2788,15 @@ term_to_iovec(_Term) ->
Options :: [compressed |
{compressed, Level :: 0..9} |
deterministic |
- {minor_version, Version :: 0..2} ].
+ {minor_version, Version :: 0..2} |
+ local ].
term_to_iovec(_Term, _Options) ->
erlang:nif_error(undefined).
%% Shadowed by erl_bif_types: erlang:tl/1
--spec tl(List) -> term() when
- List :: nonempty_maybe_improper_list().
+-spec tl(List) -> Tail when
+ List :: nonempty_maybe_improper_list(),
+ Tail :: term().
tl(_List) ->
erlang:nif_error(undefined).
@@ -2808,7 +2824,8 @@ trace_pattern(MFA, MatchSpec) ->
meta | {meta, Pid :: pid()} |
{meta, TracerModule :: module(), TracerState :: term()} |
call_count |
- call_time.
+ call_time |
+ call_memory.
-spec erlang:trace_pattern(send, MatchSpec, []) -> non_neg_integer() when
MatchSpec :: (MatchSpecList :: trace_match_spec())
@@ -3073,7 +3090,8 @@ spawn_monitor(M, F, A) ->
%% TODO change size => to := when -type maps support is finalized
| #{ size => non_neg_integer(),
kill => boolean(),
- error_logger => boolean() }.
+ error_logger => boolean(),
+ include_shared_binaries => boolean() }.
-type spawn_opt_option() ::
link
@@ -4030,6 +4048,8 @@ rvrs([X|Xs],Ys) -> rvrs(Xs, [X|Ys]).
Term1 :: term(),
Term2 :: term(),
Minimum :: term().
+%% In Erlang/OTP 26, min/2 is a guard BIF. This implementation is kept
+%% for backward compatibility with code compiled with an earlier version.
min(A, B) when A > B -> B;
min(A, _) -> A.
@@ -4038,6 +4058,8 @@ min(A, _) -> A.
Term1 :: term(),
Term2 :: term(),
Maximum :: term().
+%% In Erlang/OTP 26, max/2 is a guard BIF. This implementation is kept
+%% for backward compatibility with code compiled with an earlier version.
max(A, B) when A < B -> B;
max(A, _) -> A.
diff --git a/erts/preloaded/src/erts.app.src b/erts/preloaded/src/erts.app.src
index 4b0d0ae2d7..8b3d3288a5 100644
--- a/erts/preloaded/src/erts.app.src
+++ b/erts/preloaded/src/erts.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -42,7 +42,7 @@
{registered, []},
{applications, []},
{env, []},
- {runtime_dependencies, ["stdlib-4.1", "kernel-8.5", "sasl-3.3"]}
+ {runtime_dependencies, ["stdlib-4.1", "kernel-@OTP-18344@", "sasl-3.3"]}
]}.
%% vim: ft=erlang
diff --git a/erts/preloaded/src/erts_internal.erl b/erts/preloaded/src/erts_internal.erl
index 2aa8739388..6688bbfe67 100644
--- a/erts/preloaded/src/erts_internal.erl
+++ b/erts/preloaded/src/erts_internal.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@
-export([cmp_term/2]).
-export([map_to_tuple_keys/1, term_type/1, map_hashmap_children/1,
map_next/3]).
+-export([mc_iterator/1, mc_refill/1]).
-export([open_port/2, port_command/3, port_connect/2, port_close/1,
port_control/3, port_call/3, port_info/1, port_info/2]).
@@ -430,7 +431,7 @@ map_hashmap_children(_M) ->
%% return the next assoc in the iterator and a new iterator
-spec map_next(I, M, A) -> {K,V,NI} | list() when
- I :: non_neg_integer(),
+ I :: non_neg_integer() | list(),
M :: map(),
K :: term(),
V :: term(),
@@ -440,6 +441,61 @@ map_hashmap_children(_M) ->
map_next(_I, _M, _A) ->
erlang:nif_error(undefined).
+%% Introduced in Erlang/OTP 26. This function is a helper, called from
+%% code generated by the compiler. It must be kept compatible as long
+%% code calling this helper can still be loaded.
+-spec mc_iterator(MapOrIter) -> NI when
+ MapOrIter :: map() | maps:iterator(),
+ NI :: term().
+
+mc_iterator(Map) when is_map(Map) ->
+ erts_internal:map_next(0, Map, iterator);
+mc_iterator([Path | Map]) ->
+ %% This is probably an iterator.
+ try erts_internal:map_next(Path, Map, iterator) of
+ Iter ->
+ Iter
+ catch
+ error:badarg ->
+ []
+ end;
+mc_iterator(MapIter) ->
+ %% Possible "used" iterator. Must validate it.
+ case is_map_iter(MapIter) of
+ true -> MapIter;
+ false -> []
+ end.
+
+is_map_iter({_, _, Iter}) ->
+ is_map_iter(Iter);
+is_map_iter(Iter) ->
+ case Iter of
+ [Path | Map] ->
+ try erts_internal:map_next(Path, Map, iterator) of
+ _ ->
+ true
+ catch
+ error:badarg ->
+ false
+ end;
+ none -> true;
+ _ -> false
+ end.
+
+%% Introduced in Erlang/OTP 26. This function is a helper, called from
+%% code generated by the compiler. It must be kept compatible as long
+%% code calling this helper can still be loaded.
+-spec mc_refill(IM) -> {K,V,NI} when
+ IM :: nonempty_improper_list(I, M),
+ I :: non_neg_integer(),
+ M :: map(),
+ K :: term(),
+ V :: term(),
+ NI :: term().
+
+mc_refill([Path | Map]) ->
+ erts_internal:map_next(Path, Map, iterator).
+
-spec erts_internal:flush_monitor_messages(Ref, Multi, Res) -> term() when
Ref :: reference(),
Multi :: boolean(),
diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl
index 5ea349511d..7933cfaa34 100644
--- a/erts/preloaded/src/init.erl
+++ b/erts/preloaded/src/init.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -98,6 +98,17 @@
debug(false, _) -> ok;
debug(_, T) -> erlang:display(T).
+debug(false, _, Fun) ->
+ Fun();
+debug(_, T, Fun) ->
+ erlang:display(T),
+ T1 = erlang:monotonic_time(),
+ Val = Fun(),
+ T2 = erlang:monotonic_time(),
+ Time = erlang:convert_time_unit(T2 - T1, native, microsecond),
+ erlang:display({done_in_microseconds, Time}),
+ Val.
+
-spec get_configfd(integer()) -> none | term().
get_configfd(ConfigFdId) ->
request({get_configfd, ConfigFdId}).
@@ -396,8 +407,8 @@ boot_loop(BootPid, State) ->
loop(State#state{status = {started,PS},
subscribed = []});
{'EXIT',BootPid,Reason} ->
- erlang:display({"init terminating in do_boot",Reason}),
- crash("init terminating in do_boot", [Reason]);
+ % erlang:display({"init terminating in do_boot",Reason}),
+ crash("Runtime terminating during boot", [Reason]);
{'EXIT',Pid,Reason} ->
Kernel = State#state.kernel,
terminate(Pid,Kernel,Reason), %% If Pid is a Kernel pid, halt()!
@@ -462,9 +473,9 @@ new_kernelpid({_Name,ignore},BootPid,State) ->
BootPid ! {self(),ignore},
State;
new_kernelpid({Name,What},BootPid,State) ->
- erlang:display({"could not start kernel pid",Name,What}),
+ % erlang:display({"could not start kernel pid",Name,What}),
clear_system(false,BootPid,State),
- crash("could not start kernel pid", [Name, What]).
+ crash("Could not start kernel pid", [Name, What]).
%% Here is the main loop after the system has booted.
@@ -558,7 +569,7 @@ do_handle_msg(Msg,State) ->
true -> logger:info("init got unexpected: ~p", [X],
#{ error_logger=>#{tag=>info_msg}});
false ->
- erlang:display_string("init got unexpected: "),
+ erlang:display_string(stdout, "init got unexpected: "),
erlang:display(X)
end
end
@@ -833,8 +844,9 @@ del(_Item, []) -> [].
terminate(Pid,Kernel,Reason) ->
case kernel_pid(Pid,Kernel) of
{ok,Name} ->
+ %% If you change this time, also change the time in logger_simple_h.erl
sleep(500), %% Flush error printouts!
- erlang:display({"Kernel pid terminated",Name,Reason}),
+ % erlang:display({"Kernel pid terminated",Name,Reason}),
crash("Kernel pid terminated", [Name, Reason]);
_ ->
false
@@ -1012,10 +1024,13 @@ eval_script([{preLoaded,_}|T], #es{}=Es) ->
eval_script(T, Es);
eval_script([{path,Path}|T], #es{path=false,pa=Pa,pz=Pz,
path_choice=PathChoice,
- vars=Vars}=Es) ->
- RealPath0 = make_path(Pa, Pz, Path, Vars),
- RealPath = patch_path(RealPath0, PathChoice),
- erl_prim_loader:set_path(RealPath),
+ vars=Vars,debug=Deb}=Es) ->
+ debug(Deb, {path,Path},
+ fun() ->
+ RealPath0 = make_path(Pa, Pz, Path, Vars),
+ RealPath = patch_path(RealPath0, PathChoice),
+ erl_prim_loader:set_path(RealPath)
+ end),
eval_script(T, Es);
eval_script([{path,_}|T], #es{}=Es) ->
%% Ignore, use the command line -path flag.
@@ -1038,12 +1053,10 @@ eval_script([{primLoad,Mods}|T], #es{init=Init,prim_load=PrimLoad}=Es)
eval_script(T, Es);
eval_script([{kernelProcess,Server,{Mod,Fun,Args}}|T],
#es{init=Init,debug=Deb}=Es) ->
- debug(Deb, {start,Server}),
- start_in_kernel(Server, Mod, Fun, Args, Init),
+ debug(Deb, {start,Server}, fun() -> start_in_kernel(Server, Mod, Fun, Args, Init) end),
eval_script(T, Es);
eval_script([{apply,{Mod,Fun,Args}}=Apply|T], #es{debug=Deb}=Es) ->
- debug(Deb, Apply),
- apply(Mod, Fun, Args),
+ debug(Deb, Apply, fun() -> apply(Mod, Fun, Args) end),
eval_script(T, Es);
eval_script([], #es{}) ->
ok;
@@ -1225,21 +1238,54 @@ start_it([]) ->
ok;
start_it({eval,Bin}) ->
Str = b2s(Bin),
- {ok,Ts,_} = erl_scan:string(Str),
- Ts1 = case reverse(Ts) of
- [{dot,_}|_] -> Ts;
- TsR -> reverse([{dot,erl_anno:new(1)} | TsR])
- end,
- {ok,Expr} = erl_parse:parse_exprs(Ts1),
- {value, _Value, _Bs} = erl_eval:exprs(Expr, erl_eval:new_bindings()),
- ok;
-start_it([_|_]=MFA) ->
- case MFA of
- [M] -> M:start();
- [M,F] -> M:F();
- [M,F|Args] -> M:F(Args) % Args is a list
+ try
+ {ok,Ts,_} = erl_scan:string(Str),
+ Ts1 = case reverse(Ts) of
+ [{dot,_}|_] -> Ts;
+ TsR -> reverse([{dot,erl_anno:new(1)} | TsR])
+ end,
+ {ok,Expr} = erl_parse:parse_exprs(Ts1),
+ {value, _Value, _Bs} = erl_eval:exprs(Expr, erl_eval:new_bindings()),
+ ok
+ catch E:R:ST ->
+ Message = [<<"Error! Failed to eval: ">>, Bin, <<"\r\n\r\n">>],
+ erlang:display_string(binary_to_list(iolist_to_binary(Message))),
+ erlang:raise(E,R,ST)
+ end;
+start_it([M|FA]) ->
+ case code:ensure_loaded(M) of
+ {module, M} ->
+ case FA of
+ [] -> M:start();
+ [F] -> M:F();
+ [F|Args] -> M:F(Args) % Args is a list
+ end;
+
+ {error, Reason} ->
+ Message = [explain_ensure_loaded_error(M, Reason), <<"\r\n\r\n">>],
+ erlang:display_string(binary_to_list(iolist_to_binary(Message))),
+ erlang:error(undef)
end.
+explain_ensure_loaded_error(M, badfile) ->
+ S = [<<"it requires a more recent Erlang/OTP version "
+ "or its .beam file was corrupted.\r\n"
+ "(You are running Erlang/OTP ">>,
+ erlang:system_info(otp_release), <<".)">>],
+ explain_add_head(M, S);
+explain_ensure_loaded_error(M, nofile) ->
+ S = <<"it cannot be found. Make sure that the module name is correct and\r\n",
+ "that its .beam file is in the code path.">>,
+ explain_add_head(M, S);
+explain_ensure_loaded_error(M, Other) ->
+ [<<"Error! Failed to load module '", (atom_to_binary(M))/binary,
+ "'. Reason: ">>,
+ atom_to_binary(Other)].
+
+explain_add_head(M, S) ->
+ [<<"Error! Failed to load module '", (atom_to_binary(M))/binary,
+ "' because ">>, S].
+
%% Load a module.
do_load_module(Mod, BinCode) ->
@@ -1457,7 +1503,7 @@ archive_extension() ->
run_on_load_handlers() ->
Ref = monitor(process, ?ON_LOAD_HANDLER),
- catch ?ON_LOAD_HANDLER ! run_on_load,
+ _ = catch ?ON_LOAD_HANDLER ! {run_on_load, Ref},
receive
{'DOWN',Ref,process,_,noproc} ->
%% There is no on_load handler process,
@@ -1465,12 +1511,13 @@ run_on_load_handlers() ->
%% called and it is not the first time we
%% pass through here.
ok;
- {'DOWN',Ref,process,_,on_load_done} ->
+ {'DOWN',Ref,process,_,Ref} ->
+ %% All on_load handlers have run succesfully
ok;
- {'DOWN',Ref,process,_,Res} ->
+ {'DOWN',Ref,process,_,Reason} ->
%% Failure to run an on_load handler.
%% This is fatal during start-up.
- exit(Res)
+ exit(Reason)
end.
start_on_load_handler_process() ->
@@ -1486,34 +1533,37 @@ on_load_loop(Mods, Debug0) ->
on_load_loop(Mods, Debug);
{loaded,Mod} ->
on_load_loop([Mod|Mods], Debug0);
- run_on_load ->
+ {run_on_load, Ref} ->
run_on_load_handlers(Mods, Debug0),
- exit(on_load_done)
+ exit(Ref)
end.
run_on_load_handlers([M|Ms], Debug) ->
- debug(Debug, {running_on_load_handler,M}),
+ debug(Debug,
+ {running_on_load_handler,M},
+ fun() -> run_on_load_handler(M, Debug) end),
+ run_on_load_handlers(Ms, Debug);
+run_on_load_handlers([], _) -> ok.
+
+run_on_load_handler(M, Debug) ->
Fun = fun() ->
- Res = erlang:call_on_load_function(M),
- exit(Res)
- end,
+ Res = erlang:call_on_load_function(M),
+ exit(Res)
+ end,
{Pid,Ref} = spawn_monitor(Fun),
receive
- {'DOWN',Ref,process,Pid,OnLoadRes} ->
- Keep = OnLoadRes =:= ok,
- erlang:finish_after_on_load(M, Keep),
- case Keep of
- false ->
- Error = {on_load_function_failed,M,OnLoadRes},
- debug(Debug, Error),
- exit(Error);
- true ->
- debug(Debug, {on_load_handler_returned_ok,M}),
- run_on_load_handlers(Ms, Debug)
- end
- end;
-run_on_load_handlers([], _) -> ok.
-
+ {'DOWN',Ref,process,Pid,OnLoadRes} ->
+ Keep = OnLoadRes =:= ok,
+ erlang:finish_after_on_load(M, Keep),
+ case Keep of
+ false ->
+ Error = {on_load_function_failed,M,OnLoadRes},
+ debug(Debug, Error),
+ exit(Error);
+ true ->
+ debug(Debug, {on_load_handler_returned_ok,M})
+ end
+ end.
%% debug profile (light variant of eprof)
debug_profile_start() ->
diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl
index 3538b4fbba..3e9f578b65 100644
--- a/erts/preloaded/src/prim_file.erl
+++ b/erts/preloaded/src/prim_file.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -653,6 +653,8 @@ read_handle_info_1(Fd, TimeType) ->
error:_ -> {error, badarg}
end.
+adjust_times(FileInfo, posix) ->
+ FileInfo;
adjust_times(FileInfo, TimeType) ->
CTime = from_posix_seconds(FileInfo#file_info.ctime, TimeType),
MTime = from_posix_seconds(FileInfo#file_info.mtime, TimeType),
@@ -877,8 +879,6 @@ is_path_translatable(Path) ->
%% We want to use posix time in all prim but erl_prim_loader makes that tricky
%% It is probably needed to redo the whole erl_prim_loader
-from_posix_seconds(Seconds, posix) when is_integer(Seconds) ->
- Seconds;
from_posix_seconds(Seconds, universal) when is_integer(Seconds) ->
erlang:posixtime_to_universaltime(Seconds);
from_posix_seconds(Seconds, local) when is_integer(Seconds) ->
diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl
index c6274f89d6..89ee3f150a 100644
--- a/erts/preloaded/src/prim_inet.erl
+++ b/erts/preloaded/src/prim_inet.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2022. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
+%%
%% 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
@@ -14,7 +14,7 @@
%% 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.
-%%
+%%
%% %CopyrightEnd%
%%
%% The SCTP protocol was added 2006
@@ -564,48 +564,33 @@ peeloff(S, AssocId) ->
%%
send(S, Data, OptList) when is_port(S), is_list(OptList) ->
?DBG_FORMAT("prim_inet:send(~p, _, ~p)~n", [S,OptList]),
- try erlang:port_command(S, Data, OptList) of
- false -> % Port busy and nosuspend option passed
+ Mref = monitor(port, S),
+ MrefBin = term_to_binary(Mref, [local]),
+ MrefBinSize = byte_size(MrefBin),
+ MrefBinSize = MrefBinSize band 16#FFFF,
+ try
+ erlang:port_command(
+ S, [<<MrefBinSize:16,MrefBin/binary>>, Data], OptList)
+ of
+ false -> % Port busy when nosuspend option was passed
?DBG_FORMAT("prim_inet:send() -> {error,busy}~n", []),
- {error,busy};
- true ->
- send_recv_reply(S, undefined)
- catch
- error:_Error ->
+ {error,busy};
+ true ->
+ receive
+ {inet_reply,S,Status,Mref} ->
+ demonitor(Mref, [flush]),
+ Status;
+ {'DOWN',Mref,_,_,_Reason} ->
+ ?DBG_FORMAT(
+ "prim_inet:send_recv_reply(~p, _) 'DOWN' ~p~n",
+ [S,_Reason]),
+ {error,closed}
+ end
+ catch error: _ ->
?DBG_FORMAT("prim_inet:send() -> {error,einval}~n", []),
{error,einval}
end.
-send_recv_reply(S, Mref) ->
- ReplyTimeout =
- case Mref of
- undefined ->
- ?INET_CLOSE_TIMEOUT;
- _ ->
- infinity
- end,
- receive
- {inet_reply,S,Status} ->
- ?DBG_FORMAT(
- "prim_inet:send_recv_reply(~p, _): inet_reply ~p~n",
- [S,Status]),
- case Mref of
- undefined -> ok;
- _ ->
- demonitor(Mref, [flush]),
- ok
- end,
- Status;
- {'DOWN',Mref,_,_,_Reason} when Mref =/= undefined ->
- ?DBG_FORMAT(
- "prim_inet:send_recv_reply(~p, _) 'DOWN' ~p~n",
- [S,_Reason]),
- {error,closed}
- after ReplyTimeout ->
- send_recv_reply(S, monitor(port, S))
- end.
-
-
send(S, Data) ->
send(S, Data, []).
@@ -645,10 +630,19 @@ do_sendto(S, Address, AncOpts, Data) ->
[enc_value(set, addr, Address),
enc_value(set, uint32, AncDataLen), AncData,
Data],
- try erlang:port_command(S, PortCommandData) of
+ Ref = make_ref(),
+ RefBin = term_to_binary(Ref, [local]),
+ RefBinSize = byte_size(RefBin),
+ RefBinSize = RefBinSize band 16#FFFF,
+ try
+ erlang:port_command(
+ S,
+ [<<RefBinSize:16,RefBin/binary>>,
+ PortCommandData])
+ of
true ->
receive
- {inet_reply,S,Reply} ->
+ {inet_reply,S,Reply,Ref} ->
?DBG_FORMAT(
"prim_inet:sendto() -> ~p~n", [Reply]),
Reply
@@ -669,7 +663,6 @@ do_sendto(S, Address, AncOpts, Data) ->
"prim_inet:sendto() -> {error,einval}~n", []),
{error,einval}
end.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
@@ -1513,6 +1506,9 @@ is_sockopt_val(Opt, Val) ->
%% Socket options processing: Encoding option NAMES:
%%
enc_opt(reuseaddr) -> ?INET_OPT_REUSEADDR;
+enc_opt(reuseport) -> ?INET_OPT_REUSEPORT;
+enc_opt(reuseport_lb) -> ?INET_OPT_REUSEPORT_LB;
+enc_opt(exclusiveaddruse) -> ?INET_OPT_EXCLUSIVEADDRUSE;
enc_opt(keepalive) -> ?INET_OPT_KEEPALIVE;
enc_opt(dontroute) -> ?INET_OPT_DONTROUTE;
enc_opt(linger) -> ?INET_OPT_LINGER;
@@ -1581,6 +1577,9 @@ enc_opt(sctp_get_peer_addr_info) -> ?SCTP_OPT_GET_PEER_ADDR_INFO.
%% Decoding option NAMES:
%%
dec_opt(?INET_OPT_REUSEADDR) -> reuseaddr;
+dec_opt(?INET_OPT_REUSEPORT) -> reuseport;
+dec_opt(?INET_OPT_REUSEPORT_LB) -> reuseport_lb;
+dec_opt(?INET_OPT_EXCLUSIVEADDRUSE) -> exclusiveaddruse;
dec_opt(?INET_OPT_KEEPALIVE) -> keepalive;
dec_opt(?INET_OPT_DONTROUTE) -> dontroute;
dec_opt(?INET_OPT_LINGER) -> linger;
@@ -1664,6 +1663,9 @@ type_opt(_, Opt) ->
%% Types of option values, by option name:
%%
type_opt_1(reuseaddr) -> bool;
+type_opt_1(reuseport) -> bool;
+type_opt_1(reuseport_lb) -> bool;
+type_opt_1(exclusiveaddruse) -> bool;
type_opt_1(keepalive) -> bool;
type_opt_1(dontroute) -> bool;
type_opt_1(linger) -> {bool,int};
diff --git a/erts/preloaded/src/prim_socket.erl b/erts/preloaded/src/prim_socket.erl
index 68c950331a..8da47bba82 100644
--- a/erts/preloaded/src/prim_socket.erl
+++ b/erts/preloaded/src/prim_socket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -512,6 +512,10 @@ send(SockRef, Bin, EFlags, SendRef) when is_integer(EFlags) ->
{select, Written} ->
<<_:Written/binary, RestBin/binary>> = Bin,
{select, RestBin, EFlags};
+
+ completion = C ->
+ C;
+
{error, _Reason} = Result ->
Result
end;
@@ -558,11 +562,13 @@ sendto(SockRef, Bin, To, Flags, SendRef) ->
sockaddr ->
{error, {invalid, {Cause, To}}}
end;
+
ok ->
ok;
{ok, Written} ->
<<_:Written/binary, RestBin/binary>> = Bin,
{ok, RestBin};
+
select ->
Cont = {To, ETo, EFlags},
{select, Cont};
@@ -570,6 +576,10 @@ sendto(SockRef, Bin, To, Flags, SendRef) ->
<<_:Written/binary, RestBin/binary>> = Bin,
Cont = {To, ETo, EFlags},
{select, RestBin, Cont};
+
+ completion = C->
+ C;
+
{error, _Reason} = Result ->
Result
end
@@ -621,6 +631,7 @@ sendmsg_result(
sendmsg_result(
SockRef, RestIOV, Cont, SendRef, true,
nif_sendmsg(SockRef, EMsg, EFlags, SendRef, RestIOV));
+
select ->
if
HasWritten ->
@@ -631,6 +642,11 @@ sendmsg_result(
{select, Written} ->
RestIOV = rest_iov(Written, IOV),
{select, RestIOV, Cont};
+
+ %% Either the message was written or not. No half ways...
+ completion = C ->
+ C;
+
{error, Reason} = Error->
if
HasWritten ->
diff --git a/erts/test/Makefile b/erts/test/Makefile
index addf5f89c5..b44f28f3c8 100644
--- a/erts/test/Makefile
+++ b/erts/test/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2021. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -41,7 +41,10 @@ MODULES= \
upgrade_SUITE \
parallel_messages_SUITE
-ERL_FILES= $(MODULES:%=%.erl)
+ERTS_MODULES= erts_test_utils
+
+ERL_FILES= $(MODULES:%=%.erl) \
+ $(ERTS_MODULES:%=$(ERL_TOP)/erts/emulator/test/%.erl)
TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
diff --git a/erts/test/erl_print_SUITE.erl b/erts/test/erl_print_SUITE.erl
index 84e99675a0..2c3cae64c1 100644
--- a/erts/test/erl_print_SUITE.erl
+++ b/erts/test/erl_print_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -96,10 +96,18 @@ erlang_display(Config) when is_list(Config) ->
MyCre = my_cre(),
+ {Ser1, Ser2, LPort} = case erlang:system_info(wordsize) of
+ 4 -> {0, 0, 1 bsl 27};
+ 8 -> {42, 1 bsl 30, 1 bsl 40}
+ end,
+
%% pids
- chk_display(mk_pid_xstr({node(), MyCre}, 4711, 42)),
+ chk_display(mk_pid_xstr({node(), MyCre}, 4711, Ser1)),
+ chk_display(mk_pid_xstr({node(), MyCre}, 1 bsl 27, Ser2)),
chk_display(mk_pid_xstr({node(), oth_cre(MyCre)}, 4711, 42)),
+ chk_display(mk_pid_xstr({node(), oth_cre(MyCre)}, 1 bsl 27, Ser2)),
chk_display(mk_pid_xstr({node(), oth_cre(oth_cre(MyCre))}, 4711, 42)),
+ chk_display(mk_pid_xstr({node(), oth_cre(oth_cre(MyCre))}, 1 bsl 27, Ser2)),
chk_display(mk_pid_xstr({a@b, MyCre}, 4711, 42)),
chk_display(mk_pid_xstr({a@b, oth_cre(MyCre)}, 4711, 42)),
@@ -107,12 +115,18 @@ erlang_display(Config) when is_list(Config) ->
%% ports
chk_display(mk_port_xstr({node(), MyCre}, 4711)),
+ chk_display(mk_port_xstr({node(), MyCre}, LPort)),
chk_display(mk_port_xstr({node(), oth_cre(MyCre)}, 4711)),
+ chk_display(mk_port_xstr({node(), oth_cre(MyCre)}, LPort)),
chk_display(mk_port_xstr({node(), oth_cre(oth_cre(MyCre))}, 4711)),
+ chk_display(mk_port_xstr({node(), oth_cre(oth_cre(MyCre))}, LPort)),
chk_display(mk_port_xstr({c@d, MyCre}, 4711)),
+ chk_display(mk_port_xstr({c@d, MyCre}, LPort)),
chk_display(mk_port_xstr({c@d, oth_cre(MyCre)}, 4711)),
+ chk_display(mk_port_xstr({c@d, oth_cre(MyCre)}, LPort)),
chk_display(mk_port_xstr({c@d, oth_cre(oth_cre(MyCre))}, 4711)),
+ chk_display(mk_port_xstr({c@d, oth_cre(oth_cre(MyCre))}, LPort)),
%% refs
chk_display(mk_ref_xstr({node(), MyCre}, [1,2,3])),
@@ -316,102 +330,14 @@ run_case(Config, TestArgs, Fun) ->
ct:fail({open_port_failed, Error})
end.
+mk_pid(Node, Number, Serial) ->
+ erts_test_utils:mk_ext_pid(Node, Number, Serial).
--define(VERSION_MAGIC, 131).
-
--define(ATOM_EXT, 100).
--define(REFERENCE_EXT, 101).
--define(PORT_EXT, 102).
--define(PID_EXT, 103).
--define(NEW_REFERENCE_EXT, 114).
--define(NEW_PID_EXT, $X).
--define(NEW_PORT_EXT, $Y).
--define(NEWER_REFERENCE_EXT, $Z).
-
-uint32_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 32 ->
- [(Uint bsr 24) band 16#ff,
- (Uint bsr 16) band 16#ff,
- (Uint bsr 8) band 16#ff,
- Uint band 16#ff];
-uint32_be(Uint) ->
- exit({badarg, uint32_be, [Uint]}).
-
-
-uint16_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 16 ->
- [(Uint bsr 8) band 16#ff,
- Uint band 16#ff];
-uint16_be(Uint) ->
- exit({badarg, uint16_be, [Uint]}).
-
-uint8(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 8 ->
- Uint band 16#ff;
-uint8(Uint) ->
- exit({badarg, uint8, [Uint]}).
-
-
-
-mk_pid({NodeName, Creation}, Number, Serial) when is_atom(NodeName) ->
- mk_pid({atom_to_list(NodeName), Creation}, Number, Serial);
-mk_pid({NodeName, Creation}, Number, Serial) ->
- case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- ?NEW_PID_EXT,
- ?ATOM_EXT,
- uint16_be(length(NodeName)),
- NodeName,
- uint32_be(Number),
- uint32_be(Serial),
- uint32_be(Creation)])) of
- Pid when is_pid(Pid) ->
- Pid;
- {'EXIT', {badarg, _}} ->
- exit({badarg, mk_pid, [{NodeName, Creation}, Number, Serial]});
- Other ->
- exit({unexpected_binary_to_term_result, Other})
- end.
-
-mk_port({NodeName, Creation}, Number) when is_atom(NodeName) ->
- mk_port({atom_to_list(NodeName), Creation}, Number);
-mk_port({NodeName, Creation}, Number) ->
- case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- ?NEW_PORT_EXT,
- ?ATOM_EXT,
- uint16_be(length(NodeName)),
- NodeName,
- uint32_be(Number),
- uint32_be(Creation)])) of
- Port when is_port(Port) ->
- Port;
- {'EXIT', {badarg, _}} ->
- exit({badarg, mk_port, [{NodeName, Creation}, Number]});
- Other ->
- exit({unexpected_binary_to_term_result, Other})
- end.
+mk_port(Node, Number) ->
+ erts_test_utils:mk_ext_port(Node, Number).
-mk_ref({NodeName, Creation}, Numbers) when is_atom(NodeName),
- is_integer(Creation),
- is_list(Numbers) ->
- mk_ref({atom_to_list(NodeName), Creation}, Numbers);
-mk_ref({NodeName, Creation}, Numbers) when is_list(NodeName),
- is_integer(Creation),
- is_list(Numbers) ->
- case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- ?NEWER_REFERENCE_EXT,
- uint16_be(length(Numbers)),
- ?ATOM_EXT,
- uint16_be(length(NodeName)),
- NodeName,
- uint32_be(Creation),
- lists:map(fun (N) ->
- uint32_be(N)
- end,
- Numbers)])) of
- Ref when is_reference(Ref) ->
- Ref;
- {'EXIT', {badarg, _}} ->
- exit({badarg, mk_ref, [{NodeName, Creation}, Numbers]});
- Other ->
- exit({unexpected_binary_to_term_result, Other})
- end.
+mk_ref(Node, Numbers) ->
+ erts_test_utils:mk_ext_ref(Node, Numbers).
my_cre() -> erlang:system_info(creation).
diff --git a/erts/test/erlc_SUITE.erl b/erts/test/erlc_SUITE.erl
index 449aedf301..a5ff8355fa 100644
--- a/erts/test/erlc_SUITE.erl
+++ b/erts/test/erlc_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
compile_yecc/1, compile_script/1,
compile_mib/1, good_citizen/1, deep_cwd/1, arg_overflow/1,
make_dep_options/1,
+ unicode_paths/1,
features_erlc_describe/1,
features_erlc_unknown/1,
features_directives/1,
@@ -56,7 +57,8 @@ groups() ->
tests() ->
[compile_erl, compile_yecc, compile_script, compile_mib,
- good_citizen, deep_cwd, arg_overflow, make_dep_options].
+ good_citizen, deep_cwd, arg_overflow, make_dep_options,
+ unicode_paths].
feature_tests() ->
[features_erlc_describe,
@@ -487,6 +489,28 @@ make_dep_options(Config) ->
false = exists(BeamFileName),
ok.
+unicode_paths(Config) ->
+ case {os:type(), file:native_name_encoding()} of
+ {{win32,_}, _} -> {skip, "Unicode paths not supported on windows"};
+ {_,latin1} -> {skip, "Cannot interpret unicode filenames when native_name_encoding is latin1"};
+ _ ->
+ DepRE = ["_OK_"],
+ {SrcDir,OutDir0,Cmd0} = get_cmd(Config),
+ OutDir = filename:join(OutDir0,"😀"),
+ ok = case file:make_dir(OutDir) of
+ {error, eexist} -> ok;
+ ok -> ok;
+ E -> E
+ end,
+ Cmd = Cmd0 ++ " +brief -o "++OutDir,
+ FileName = filename:join([SrcDir, "😀", "erl_test_unicode.erl"]),
+ BeamFileName = filename:join(OutDir, "erl_test_unicode.beam"),
+ run(Config, Cmd, FileName, "", DepRE),
+ true = exists(BeamFileName),
+ file:delete(BeamFileName),
+ file:delete(OutDir),
+ ok
+ end.
%%% Tests related to the features mechanism
%% Support macros and functions
@@ -816,9 +840,9 @@ features_macros(Config) when is_list(Config) ->
true = erpc:call(Node1, erlang, module_loaded, [erl_features]),
- %% We can't load this due to experimental_ftr_1 not being enabled
- %% in the runtime
- {error, {features_not_allowed, [experimental_ftr_1]}} =
+ %% Starting from OTP 26, compile-time features don't need to be
+ %% enabled in the runtime system.
+ {module, f_macros} =
erpc:call(Node1, code, load_file, [f_macros]),
%% Check features enabled during compilation
[approved_ftr_1, approved_ftr_2, experimental_ftr_1] =
@@ -910,8 +934,6 @@ features_all(Config) when is_list(Config) ->
"-disable-feature", "all"]),
%% Check features enabled during compilation
[approved_ftr_2] = erpc:call(Node2, erl_features, used, [foo]),
- {error, {features_not_allowed, [approved_ftr_2]}} =
- erpc:call(Node2, code, load_file, [foo]),
peer:stop(Peer2),
ok.
@@ -953,7 +975,7 @@ features_runtime(Config) when is_list(Config) ->
Approved = [approved_ftr_2,
approved_ftr_1],
- {Compile, _SrcDir, _OutDir} = compile_fun(Config),
+ {_Compile, _SrcDir, _OutDir} = compile_fun(Config),
{Peer0, Node0} = peer([]),
diff --git a/erts/test/erlc_SUITE_data/src/😀/erl_test_unicode.erl b/erts/test/erlc_SUITE_data/src/😀/erl_test_unicode.erl
new file mode 100644
index 0000000000..32a208b7e2
--- /dev/null
+++ b/erts/test/erlc_SUITE_data/src/😀/erl_test_unicode.erl
@@ -0,0 +1,25 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(erl_test_unicode).
+-export(['😀'/1]).
+
+'😀'(0) ->
+ '😀'.
diff --git a/erts/test/ethread_SUITE.erl b/erts/test/ethread_SUITE.erl
index 19f738c572..4c2c1139df 100644
--- a/erts/test/ethread_SUITE.erl
+++ b/erts/test/ethread_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -43,7 +43,8 @@
rwspinlock/1,
rwmutex/1,
atomic/1,
- dw_atomic_massage/1]).
+ dw_atomic_massage/1,
+ thread_name/1]).
-include_lib("common_test/include/ct.hrl").
@@ -65,7 +66,8 @@ all() ->
rwspinlock,
rwmutex,
atomic,
- dw_atomic_massage].
+ dw_atomic_massage,
+ thread_name].
init_per_testcase(Case, Config) ->
case inet:gethostname() of
@@ -158,6 +160,10 @@ atomic(Config) ->
dw_atomic_massage(Config) ->
run_case(Config, "dw_atomic_massage", "").
+%% Tests thread names.
+thread_name(Config) ->
+ run_case(Config, "thread_name", "").
+
%%
%%
%% Auxiliary functions
diff --git a/erts/test/ethread_SUITE_data/ethread_tests.c b/erts/test/ethread_SUITE_data/ethread_tests.c
index 048acd6a95..377aabac24 100644
--- a/erts/test/ethread_SUITE_data/ethread_tests.c
+++ b/erts/test/ethread_SUITE_data/ethread_tests.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2004-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2004-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@
#define PRINT_VA_LIST(FRMT) \
do { \
- if (FRMT && FRMT != '\0') { \
+ if (FRMT && *(FRMT) != '\0') { \
va_list args; \
va_start(args, FRMT); \
vfprintf(stderr, FRMT, args); \
@@ -1757,6 +1757,7 @@ at_dw_thr(void *vval)
break;
}
}
+ return NULL;
}
static void
@@ -1783,6 +1784,120 @@ dw_atomic_massage_test(void)
}
}
+static ethr_mutex thread_name_mutex;
+static ethr_cond thread_name_cond;
+static int thread_name_state;
+
+static void *
+thread_name_thread(void *my_tid)
+{
+ int res;
+
+ ethr_mutex_lock(&thread_name_mutex);
+ thread_name_state = 1;
+ while (thread_name_state == 1) {
+ res = ethr_cond_wait(&thread_name_cond, &thread_name_mutex);
+ ASSERT(res == 0);
+ }
+ ethr_mutex_unlock(&thread_name_mutex);
+ return NULL;
+}
+
+static void
+thread_name(void)
+{
+ static const ethr_thr_opts default_thr_opts = ETHR_THR_OPTS_DEFAULT_INITER;
+ ethr_tid tid;
+ ethr_thr_opts thr_opts;
+ int res;
+ char buf[ETHR_THR_NAME_MAX + 1];
+
+ res = ethr_mutex_init(&thread_name_mutex);
+ ASSERT(res == 0);
+ res = ethr_cond_init(&thread_name_cond);
+ ASSERT(res == 0);
+
+ if (ethr_getname(ethr_self(), buf, sizeof(buf)) == ENOSYS) {
+ skip("thread names are not supported");
+ return;
+ }
+
+ /* create a thread with the minimum name length */
+ thread_name_state = 0;
+
+ memcpy(&thr_opts, &default_thr_opts, sizeof(thr_opts));
+ thr_opts.name = "";
+ res = ethr_thr_create(&tid, thread_name_thread, NULL, &thr_opts);
+ ASSERT(res == 0);
+
+ memset(buf, 0xFF, sizeof(buf));
+ res = ethr_getname(tid, buf, sizeof(buf));
+ ASSERT(res == 0);
+
+ res = strcmp(buf, "");
+ ASSERT(res == 0);
+
+ ethr_mutex_lock(&thread_name_mutex);
+ thread_name_state = 0;
+ ethr_cond_signal(&thread_name_cond);
+ ethr_mutex_unlock(&thread_name_mutex);
+
+ res = ethr_thr_join(tid, NULL);
+ ASSERT(res == 0);
+
+ /* create a thread with a middling name length */
+ thread_name_state = 0;
+
+ memcpy(&thr_opts, &default_thr_opts, sizeof(thr_opts));
+ thr_opts.name = "123456789";
+ res = ethr_thr_create(&tid, thread_name_thread, NULL, &thr_opts);
+ ASSERT(res == 0);
+
+ memset(buf, 0xFF, sizeof(buf));
+ res = ethr_getname(tid, buf, sizeof(buf));
+ ASSERT(res == 0);
+
+ res = strcmp(buf, "123456789");
+ ASSERT(res == 0);
+
+ ethr_mutex_lock(&thread_name_mutex);
+ thread_name_state = 0;
+ ethr_cond_signal(&thread_name_cond);
+ ethr_mutex_unlock(&thread_name_mutex);
+
+ res = ethr_thr_join(tid, NULL);
+ ASSERT(res == 0);
+
+ /* create a thread with the maximum name length */
+ thread_name_state = 0;
+
+ memcpy(&thr_opts, &default_thr_opts, sizeof(thr_opts));
+ thr_opts.name = "123456789012345";
+ res = ethr_thr_create(&tid, thread_name_thread, NULL, &thr_opts);
+ ASSERT(res == 0);
+
+ memset(buf, 0xFF, sizeof(buf));
+ res = ethr_getname(tid, buf, sizeof(buf));
+ ASSERT(res == 0);
+
+ res = strcmp(buf, "123456789012345");
+ ASSERT(res == 0);
+
+ ethr_mutex_lock(&thread_name_mutex);
+ thread_name_state = 0;
+ ethr_cond_signal(&thread_name_cond);
+ ethr_mutex_unlock(&thread_name_mutex);
+
+ res = ethr_thr_join(tid, NULL);
+ ASSERT(res == 0);
+
+ /* create a thread with an over-sized name length */
+ memcpy(&thr_opts, &default_thr_opts, sizeof(thr_opts));
+ thr_opts.name = "1234567890123456";
+ res = ethr_thr_create(&tid, thread_name_thread, NULL, &thr_opts);
+ ASSERT(res == EINVAL);
+}
+
void *
at_thread(void *unused)
{
@@ -1958,6 +2073,8 @@ main(int argc, char *argv[])
atomic_test();
else if (strcmp(testcase, "dw_atomic_massage") == 0)
dw_atomic_massage_test();
+ else if (strcmp(testcase, "thread_name") == 0)
+ thread_name();
else
skip("Test case \"%s\" not implemented yet", testcase);
diff --git a/erts/test/otp_SUITE.erl b/erts/test/otp_SUITE.erl
index 1690406314..a8424ea46f 100644
--- a/erts/test/otp_SUITE.erl
+++ b/erts/test/otp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -218,17 +218,13 @@ call_to_deprecated(Config) when is_list(Config) ->
{comment,integer_to_list(length(DeprecatedCalls))++" calls to deprecated functions"}.
call_to_size_1(Config) when is_list(Config) ->
- %% Applications that do not call erlang:size/1:
- Apps = [asn1,compiler,debugger,kernel,observer,parsetools,
- runtime_tools,stdlib,tools],
+ %% Forbid the use of erlang:size/1 in all applications.
+ Apps = all_otp_applications(Config),
not_recommended_calls(Config, Apps, {erlang,size,1}).
call_to_now_0(Config) when is_list(Config) ->
- %% Applications that do not call erlang:now/1:
- Apps = [asn1,common_test,compiler,debugger,dialyzer,
- kernel,mnesia,observer,parsetools,reltool,
- runtime_tools,sasl,stdlib,syntax_tools,
- tools],
+ %% Forbid the use of erlang:now/1 in all applications except et.
+ Apps = all_otp_applications(Config) -- [et],
not_recommended_calls(Config, Apps, {erlang,now,0}).
not_recommended_calls(Config, Apps0, MFA) ->
@@ -281,9 +277,20 @@ not_recommended_calls(Config, Apps0, MFA) ->
{comment, Mess}
end;
_ ->
- ct:fail({length(CallsToMFA),calls_to_size_1})
+ ct:fail({length(CallsToMFA),calls_to,MFA})
end.
+all_otp_applications(Config) ->
+ Server = proplists:get_value(xref_server, Config),
+ {ok,AllApplications} = xref:q(Server, "A"),
+ OtpAppsMap = get_otp_applications(Config),
+ [App || App <- AllApplications, is_map_key(App, OtpAppsMap)].
+
+get_otp_applications(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ [Current|_] = read_otp_version_table(DataDir),
+ read_version_lines([Current]).
+
is_present_application(Name, Server) ->
Q = io_lib:format("~w : App", [Name]),
case xref:q(Server, lists:flatten(Q)) of
@@ -475,6 +482,189 @@ check_apps_deps([{App, Deps}|AppDeps], IgnoreApps) ->
end
end.
+%%
+%% Test that the runtime dependencies have not become stale.
+%%
+
+%% Path of installed OTP releases.
+-define(OTP_RELEASES, "/usr/local/otp/releases").
+
+%% Wildcard to match all releases from OTP 17.
+-define(OTP_RELEASE_WC, "{sles10_64_17_patched,ubuntu16_64_*_patched}").
+
+test_runtime_dependencies_versions(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+
+ OtpReleases = ?OTP_RELEASES,
+ OtpReleasesWc = filename:join(OtpReleases, ?OTP_RELEASE_WC),
+
+ IgnoreApps = [],
+ IgnoreUndefs = ignore_undefs(),
+
+ FirstVersionForApp = get_first_app_versions(DataDir),
+
+ case {element(1, os:type()) =:= unix,
+ not is_development_build(DataDir),
+ filelib:is_dir(OtpReleases)} of
+ {true, true, true} ->
+ test_runtime_dependencies_versions_rels(
+ IgnoreApps,
+ IgnoreUndefs,
+ FirstVersionForApp,
+ OtpReleasesWc);
+ {false, _, _} ->
+ {skip, "This test only runs on Unix systems"};
+ {_, false, _} ->
+ {skip,
+ "This test case is designed to run in the Erlang/OTP teams "
+ "test system for daily tests. The test case depends on that "
+ "app versions have been set correctly by scripts that "
+ "are executed before creating builds for the daily tests."};
+ {_, _ ,false} ->
+ {skip, "Can not do the tests without a proper releases dir. "
+ "Check that " ++ OtpReleases ++ " is set up correctly."}
+ end.
+
+ignore_undefs() ->
+ Socket = case lists:member(prim_socket, erlang:pre_loaded()) of
+ true ->
+ #{};
+ false ->
+ Ignore = #{{prim_socket,'_','_'} => true,
+ {socket_registry,'_','_'} => true,
+ {prim_net,'_','_'} => true },
+ #{kernel => Ignore, erts => Ignore}
+ end,
+ Socket#{eunit =>
+ %% Intentional call to nonexisting function
+ #{{eunit_test, nonexisting_function, 0} => true},
+ diameter =>
+ %% The following functions are optional dependencies for diameter
+ #{{dbg,ctp,0} => true,
+ {dbg,p,2} => true,
+ {dbg,stop,0} => true,
+ {dbg,trace_port,2} => true,
+ {dbg,tracer,2} => true,
+ {erl_prettypr,format,1} => true,
+ {erl_syntax,form_list,1} => true},
+ common_test =>
+ %% ftp:start/0 has been part of the ftp application from
+ %% the beginning so it is unclear why xref report this
+ %% as undefined
+ #{{ftp,start,0} => true}}.
+
+%% Read the otp_versions.table file and create a mapping from application name
+%% the first version for each application.
+get_first_app_versions(DataDir) ->
+ Lines = read_otp_version_table(DataDir),
+ read_version_lines(Lines).
+
+test_runtime_dependencies_versions_rels(IgnoreApps, IgnoreUndefs,
+ FirstVersionForApp, OtpReleasesWc) ->
+ AppVersionToPath = version_to_path(OtpReleasesWc),
+ Deps = [Dep || {App,_,_}=Dep <- get_deps(FirstVersionForApp),
+ not lists:member(App, IgnoreApps)],
+ case test_deps(Deps, IgnoreUndefs, AppVersionToPath, FirstVersionForApp) of
+ [] ->
+ ok;
+ [_|_]=Undefs ->
+ _ = [print_undefs(Undef) || Undef <- Undefs],
+ ct:fail({length(Undefs),errors})
+ end.
+
+print_undefs({_App,AppPath,Deps,Undefs}) ->
+ App = filename:basename(filename:dirname(AppPath)),
+ io:format("Undefined functions in ~ts:", [App]),
+ io:put_chars([io_lib:format(" ~p:~p/~p\n", [M,F,A]) ||
+ {M,F,A} <- Undefs]),
+ io:format("Dependencies: ~ts\n", [lists:join(" ", Deps)]).
+
+version_to_path(OtpReleasesWc) ->
+ CurrentWc = filename:join([code:lib_dir(), "*-*", "ebin"]),
+ LibWc = filename:join([OtpReleasesWc, "lib", "*-*", "ebin"]),
+ Dirs = lists:sort(filelib:wildcard(CurrentWc) ++ filelib:wildcard(LibWc)),
+ All = [{filename:basename(filename:dirname(Dir)),Dir} || Dir <- Dirs],
+ maps:from_list(All).
+
+get_deps(FirstVersionForApp) ->
+ Paths = [begin
+ Dir = filename:dirname(Path),
+ [App0 | _] = string:split(filename:basename(Dir), "-"),
+ App = list_to_atom(App0),
+ AppFile = filename:join(Path, App0 ++ ".app"),
+ {Path, App, AppFile}
+ end || Path <- code:get_path(),
+ filename:basename(Path) =:= "ebin"],
+ %% Only keep applications included in OTP.
+ Apps = [Triple || {_, App, AppFile}=Triple <- Paths,
+ filelib:is_file(AppFile),
+ is_map_key(App, FirstVersionForApp)],
+ [{App, Path, get_runtime_deps(App, AppFile)} || {Path, App, AppFile} <- Apps].
+
+get_runtime_deps(App, AppFile) ->
+ {ok,[{application, App, Info}]} = file:consult(AppFile),
+ case lists:keyfind(runtime_dependencies, 1, Info) of
+ {runtime_dependencies, RDeps} ->
+ RDeps;
+ false ->
+ []
+ end.
+
+test_deps([{Name,Path,Deps}|Apps], IgnoreUndefs, AppVersionToPath, FirstVersionForApp) ->
+ case test_dep(Name, Path, Deps, AppVersionToPath, FirstVersionForApp, IgnoreUndefs) of
+ ok ->
+ test_deps(Apps, IgnoreUndefs, AppVersionToPath, FirstVersionForApp);
+ {error, Error} ->
+ [Error|test_deps(Apps, IgnoreUndefs, AppVersionToPath, FirstVersionForApp)]
+ end;
+test_deps([], _IgnoreUndefs, _AppVersionToPath, _FirstVersionForApp) ->
+ [].
+
+test_dep(App, AppPath, Deps, AppVersionToPath, FirstVersionForApp, IgnoreUndefs) ->
+ DepPaths = [get_app_path(Dep, AppVersionToPath, FirstVersionForApp, App) ||
+ Dep <- Deps],
+
+ Server = xref_server_test_dep,
+ {ok, _} = xref:start(Server, []),
+ xref:set_default(Server, [{verbose,false},
+ {warnings,false},
+ {builtins,true}]),
+ ok = xref:set_library_path(Server, DepPaths),
+ {ok, _} = xref:add_directory(Server, AppPath),
+ {ok, Undef0} = xref:analyze(Server, undefined_functions),
+ xref:stop(Server),
+
+ %% Filter out undefined functions that we should ignore.
+ Ignore = maps:get(App, IgnoreUndefs, #{}),
+ Undef = [MFA || {M,F,_A}=MFA <- Undef0,
+ not is_map_key(MFA, Ignore),
+ not is_map_key({M,'_','_'}, Ignore),
+ not is_map_key({M,F,'_'}, Ignore)],
+ case Undef of
+ [] ->
+ ok;
+ [_|_] ->
+ {error, {App, AppPath, Deps, Undef}}
+ end.
+
+get_app_path(App, AppVersionToPath, FirstVersionForApp, ReferencedBy) ->
+ case AppVersionToPath of
+ #{App := Path} ->
+ Path;
+ #{} ->
+ [Name0, _Version] = string:split(App, "-"),
+ Name = list_to_existing_atom(Name0),
+ First = map_get(Name, FirstVersionForApp),
+ io:format("WARNING: ~ts referenced by ~ts is too old; using ~ts instead\n",
+ [App, ReferencedBy, First]),
+ map_get(First, AppVersionToPath)
+ end.
+
+is_development_build(DataDir) ->
+ OTPVersionTicketsPath = filename:join(DataDir, "otp_version_tickets"),
+ {ok, FileContentBin} = file:read_file(OTPVersionTicketsPath),
+ string:trim(binary_to_list(FileContentBin), both, "\n ") =:= "DEVELOPMENT".
+
%%%
%%% Common help functions.
%%%
@@ -545,329 +735,24 @@ start_xref_server(Server, Mode) ->
end,
Server.
-get_suite_data_dir_path() ->
- filename:join(filename:dirname(code:which(?MODULE)), "otp_SUITE_data").
-
-get_otp_versions_table_path() ->
- filename:join(get_suite_data_dir_path(), "otp_versions.table").
-
-get_otp_version_tickets_path() ->
- filename:join(get_suite_data_dir_path(), "otp_version_tickets").
-
-%% Return a map that maps from app versions to the OTP versions they
-%% were last released in. The function makes use of the file
-%% "otp_versions.table" and the current code:get_path() to
-%% find apps.
-get_runtime_dep_to_otp_version_map() ->
- %% Find apps in "otp_versions.table"
- VersionsTableFile = get_otp_versions_table_path(),
- VersionsTableBin =
- case file:read_file(VersionsTableFile) of
- {ok, Bin} -> Bin;
- Error -> ct:fail("Could not read the file ~s which is needed to perform the test. "
- "Error: ~p~n",
- [VersionsTableFile, Error])
- end,
- VersionsTableStr = erlang:binary_to_list(VersionsTableBin),
- Lines = lists:reverse(string:tokens(VersionsTableStr, "\n")),
- AddVersionsInString =
- fun(Map, OTPVersion, AppVersionsString0) ->
- AppVersionsString =
- lists:flatten(string:replace(AppVersionsString0, "#", "", all)),
- AppVersions = string:tokens(AppVersionsString, " "),
- lists:foldl(
- fun(AppVersion0, MapSoFar) ->
- case string:trim(AppVersion0) of
- "" ->
- MapSoFar;
- AppVersion1 ->
- maps:put(AppVersion1, OTPVersion, MapSoFar)
- end
- end,
- Map,
- AppVersions)
- end,
- VersionMap0 =
- lists:foldl(
- fun(Line, MapSoFar) ->
- [OTPVersion, AppVersionsString| _] =
- string:tokens(Line, ":"),
- AddVersionsInString(MapSoFar,
- string:trim(OTPVersion),
- string:trim(AppVersionsString))
- end,
- #{},
- Lines),
- %% Find apps in code:get_path()
- lists:foldl(
- fun(Path, MapSoFar) ->
- case filelib:wildcard(filename:join(Path, "*.app")) of
- [AppFile] ->
- {ok,[{application, App, Info}]} = file:consult(AppFile),
- case lists:keyfind(vsn, 1, Info) of
- false ->
- MapSoFar;
- {vsn, VsnStr} ->
- AppVsnStr =
- erlang:atom_to_list(App) ++ "-" ++ VsnStr,
- maps:put(AppVsnStr, {latest, Path}, MapSoFar)
- end;
- _ ->
- MapSoFar
- end
- end,
- VersionMap0,
- code:get_path()).
-
-%% Find runtime dependencies for an app
-get_runtime_deps(App) ->
- AppFile = code:where_is_file(atom_to_list(App) ++ ".app"),
- {ok,[{application, App, Info}]} = file:consult(AppFile),
- case lists:keyfind(runtime_dependencies, 1, Info) of
- {runtime_dependencies, RDeps} ->
- RDeps;
- false ->
- []
- end.
-
-%% Given a release dir find the path to the given dependency
-find_dep_in_rel_dir(Dep, RelDirRoot) ->
- %% The dependencies that we have found are cached to avoid
- %% searching through the file system unnecessary many times
- CacheId = runtime_dep_test_cache,
- DepCache =
- case erlang:get(CacheId) of
- undefined -> #{};
- M-> M
- end,
- case maps:get(Dep, DepCache, none) of
- none ->
- DepPaths = filelib:wildcard(filename:join([RelDirRoot, "lib", "**", Dep, "ebin"])),
- case DepPaths of
- [Path] ->
- erlang:put(CacheId, maps:put(Dep, Path, DepCache)),
- Path;
- _ ->
- ErrorMessage =
- io_lib:format("ERROR: Could not find ~p in ~p (where it is supposed to be)."
- "Found ~p~n", [Dep, RelDirRoot, DepPaths]),
- io:format(lists:flatten(ErrorMessage)),
- ct:fail(ErrorMessage)
-
- end;
- Path ->
- Path
- end.
-
-%% Get the major OTP version part of an OTP version string
-%% Example: OTP-22.0 gives 22
-get_major_version(OtpVersion) ->
- [_,MajorVersion|_] = string:tokens(OtpVersion, "-."),
- MajorVersion.
-
-%% Returns the release directory for the oldest available OTP release
-%% on the current machine
-first_available_otp_rel() ->
- %% At least one version less than the current version should be available
- SholdBeAvailable = erlang:list_to_integer(erlang:system_info(otp_release)) - 1,
- (fun FindOldest(CurrRel, PrevAvailable) ->
- case test_server:is_release_available(erlang:integer_to_list(CurrRel)) of
- false -> PrevAvailable;
- true -> FindOldest(CurrRel-1, erlang:integer_to_list(CurrRel))
- end
- end)(SholdBeAvailable, none).
-
-%% Searches for the oldest available version of Dep
-get_oldest_available_version_of_dep(Dep) ->
- [DepName, _Version] = string:tokens(Dep, "-"),
- FirstAvailableRel = first_available_otp_rel(),
- (fun Find(LookInRel) ->
- case test_server:is_release_available(LookInRel) of
- true ->
- RelRoot = find_rel_root(LookInRel),
- Options0 = filelib:wildcard(filename:join([RelRoot, "lib", "**", "ebin"])),
- Options1 = [Opt ||
- Opt <- Options0,
- string:find(Opt,
- filename:join("lib", DepName ++ "-")) =/= nomatch],
- GetVersionTuple =
- fun(Path) ->
- [AppVerStr] =
- [C || C <- filename:split(Path),
- string:find(C, DepName ++ "-") =/= nomatch],
- [_,VerStr] = string:tokens(AppVerStr, "-"),
- erlang:list_to_tuple([erlang:list_to_integer(X) ||
- X <- string:tokens(VerStr, ".")])
- end,
- Options2 =
- lists:sort(fun(A,B) ->
- GetVersionTuple(A) =< GetVersionTuple(B)
- end,
- Options1),
- case Options2 of
- [Path|_] ->
- Path;
- _ ->
- NextRelToTry =
- erlang:integer_to_list(erlang:list_to_integer(LookInRel)+1),
- Find(NextRelToTry)
- end;
- false ->
- ct:fail({could_not_find_dep_anywhere, DepName})
- end
- end)(FirstAvailableRel).
-
-find_rel_root(Rel) ->
- case test_server:find_release(Rel) of
- not_available ->
- not_available;
- OtpRelErl -> filename:dirname(filename:dirname(OtpRelErl))
- end.
-
-%% Find the absolute paths to RuntimeDeps
-get_paths_to_dependencies(App, RuntimeDeps) ->
- FirstAvailableOTPRel = first_available_otp_rel(),
- FirstAvailableRel = erlang:list_to_integer(FirstAvailableOTPRel),
- DepToOtpVerMap = get_runtime_dep_to_otp_version_map(),
- lists:foldl(
- fun(Dep, SoFar) ->
- case maps:get(Dep, DepToOtpVerMap, false) of
- false ->
- ct:fail(io_lib:format(
- "The dependency ~s for ~p could not be found. "
- "Have you typed a non-existing version?",
- [Dep, App]));
- {latest, Path} ->
- %% The dependency is in Path (one of the paths returned by code:get_paths())
- [Path | SoFar];
- OtpVersionWithDep ->
- OtpMajorVersionWithDep = get_major_version(OtpVersionWithDep),
- OtpMajorVersionWithDepInt = erlang:list_to_integer(OtpMajorVersionWithDep),
- case find_rel_root(OtpMajorVersionWithDep) of
- not_available when FirstAvailableRel > OtpMajorVersionWithDepInt ->
- io:format("Warning: Could not find runtime dependency ~p for ~p. "
- "~p belongs to ~p but ~p is too old to be available on this machine. "
- "Trying to find the oldest available version of ~p...",
- [Dep, App, Dep, OtpVersionWithDep, OtpVersionWithDep, Dep]),
- [get_oldest_available_version_of_dep(Dep) | SoFar];
- RelDirRoot ->
- [find_dep_in_rel_dir(Dep, RelDirRoot) | SoFar]
- end
- end
- end,
- [],
- RuntimeDeps).
-
-test_app_runtime_deps_versions(AppPath, App, IgnoredUndefinedFunctions) ->
- %% Get a list of all runtime dependencies for app
- RuntimeDeps = get_runtime_deps(App),
- %% Get paths to the found runtime dependencies
- DepPaths = get_paths_to_dependencies(App, RuntimeDeps),
- XRefSName = test_app_runtime_deps_versions_server,
- %% Start xref server and do the test
- {ok, _} = xref:start(XRefSName, []),
- ok = xref:set_library_path(XRefSName, DepPaths),
- Dir = filename:join(AppPath, "ebin"),
- {ok, _} = xref:add_directory(XRefSName, Dir),
- {ok, UndefinedFunctions0} = xref:analyze(XRefSName, undefined_functions),
- xref:stop(XRefSName),
- %% Filter out undefined functions that we should ignore
- UndefinedFunctions1 =
- [F || F <- UndefinedFunctions0,
- not maps:get(F, IgnoredUndefinedFunctions, false),
- not maps:get({element(1,F),'_','_'}, IgnoredUndefinedFunctions, false),
- not maps:get({element(1,F),element(2,F),'_'}, IgnoredUndefinedFunctions, false)],
- case UndefinedFunctions1 of
- [] ->
- ok;
- UndefinedFunctions ->
- {error_undefined_functions_in_app, App, UndefinedFunctions}
- end.
-
-test_runtime_dependencies_versions_rels(IgnoreApps, AppsToIgnoredUndefinedFunctions) ->
- %% Do for every application:
- %% 1. Find deps from app file
- %% 2. Find where the deps are installed
- %% 3. Run xref tests on the apps with the specified dependencies
- %% 4. Report error if undefined function
- Apps0 = [{Path, list_to_atom(AppName)}
- || {match, [Path, AppName]}
- <- [re:run(X,"(" ++ code:lib_dir()++"/"++"([^/-]*).*)/ebin",
- [{capture,[1,2],list},unicode]) || X <- code:get_path()]],
- Apps = [{Path, App} ||
- {Path, App} <- Apps0,
- code:where_is_file(atom_to_list(App) ++ ".app") =/= non_existing],
- Res = [test_app_runtime_deps_versions(AppPath,
- App,
- maps:get(App, AppsToIgnoredUndefinedFunctions, #{})) ||
- {AppPath, App} <- Apps, not lists:member(App, IgnoreApps)],
- BadRes = [R || R <- Res, R =/= ok],
- case BadRes =:= [] of
- true -> ok;
- _ ->
- ct:fail(BadRes)
- end.
-
-is_development_build() ->
- {ok, FileContentBin} = file:read_file(get_otp_version_tickets_path()),
- "DEVELOPMENT" =:= string:trim(erlang:binary_to_list(FileContentBin), both, "\n ").
-
-test_runtime_dependencies_versions(_Config) ->
- ReleasesDir = "/usr/local/otp/releases",
- IgnoreApps = [],
- SocketIgnore = case lists:member(prim_socket, erlang:pre_loaded()) of
- true -> #{};
- false ->
- Ignore = #{{prim_socket,'_','_'} => true,
- {socket_registry,'_','_'} => true,
- {prim_net,'_','_'} => true },
- #{ kernel => Ignore, erts => Ignore }
- end,
- AppsToIgnoredUndefinedFunctions =
- #{eunit =>
- %% Intentional call to nonexisting function
- #{{eunit_test, nonexisting_function, 0} => true},
- diameter =>
- %% The following functions are optional dependencies for diameter
- #{{dbg,ctp,0} => true,
- {dbg,p,2} => true,
- {dbg,stop_clear,0} => true,
- {dbg,trace_port,2} => true,
- {dbg,tracer,2} => true,
- {erl_prettypr,format,1} => true,
- {erl_syntax,form_list,1} => true},
- common_test =>
- %% ftp:start/0 has been part of the ftp application from
- %% the beginning so it is unclear why xref report this
- %% as undefined
- #{{ftp,start,0} => true}},
- case {erlang:element(1, os:type()) =:= unix,
- not is_development_build(),
- filelib:is_dir(ReleasesDir),
- filelib:is_file(get_otp_versions_table_path()),
- first_available_otp_rel() =/= none} of
- {true, true, true, true, true} ->
- test_runtime_dependencies_versions_rels(
- IgnoreApps,
- maps:merge(AppsToIgnoredUndefinedFunctions, SocketIgnore));
- {_, _ ,_, false, _} -> {skip,
- "Could not find the file \"otp_versions.table\". "
- "Check that the test has been built correctly. "
- "\"otp_versions.table\" is copied to \"erts/test/otp_SUITE_data\" "
- "by the makefile \"erts/test/Makefile\""};
- {_, false , _, _, _} -> {skip,
- "This test case is designed to run in the Erlang/OTP teams "
- "test system for nightly tests. The test case depend on that "
- "app versions have been set correctly by scripts that "
- "are executed before creating builds for the nightly tests."};
- {_, _ ,false, _, _} -> {skip, "Can not do the tests without a proper releases dir. "
- "Check that " ++ ReleasesDir ++ " is set up correctly."};
- {_, _ , _, _, false} ->
- PrevRelNr = erlang:list_to_integer(erlang:system_info(otp_release)) - 1,
- PrevRelNrStr = erlang:integer_to_list(PrevRelNr),
- {skip,
- "Seems like the releases dir is not set up correctly. "
- "Is release " ++ PrevRelNrStr ++ " installed in the releases dir? "
- "(releases dir = " ++ ReleasesDir ++ ")"};
- {false, _ ,_, _, _} -> {skip, "This test only runs on Unix systems"}
- end.
+read_otp_version_table(DataDir) ->
+ VersionTableFile = filename:join(DataDir, "otp_versions.table"),
+ {ok, Contents} = file:read_file(VersionTableFile),
+ binary:split(Contents, <<"\n">>, [global,trim]).
+
+read_version_lines(Lines) ->
+ read_version_lines(Lines, #{}).
+
+read_version_lines([Line|Lines], Map0) ->
+ [<<"OTP-",_/binary>>, <<":">> | Apps] = binary:split(Line, <<" ">>, [global,trim]),
+ Map = lists:foldl(fun(App, Acc) ->
+ case binary:split(App, <<"-">>) of
+ [Name, _Version] ->
+ Acc#{binary_to_atom(Name) => binary_to_list(App)};
+ [_] ->
+ Acc
+ end
+ end, Map0, Apps),
+ read_version_lines(Lines, Map);
+read_version_lines([], Map) ->
+ Map.
diff --git a/erts/vsn.mk b/erts/vsn.mk
index 44cd2f0475..67df301d2e 100644
--- a/erts/vsn.mk
+++ b/erts/vsn.mk
@@ -18,7 +18,7 @@
# %CopyrightEnd%
#
-VSN = 13.2.1
+VSN = 13.2.2
# Port number 4365 in 4.2
# Port number 4366 in 4.3
diff --git a/lib/Makefile b/lib/Makefile
index 067f00787b..465e1073e9 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2021. All Rights Reserved.
+# Copyright Ericsson AB 1996-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -56,28 +56,11 @@ ifdef BUILD_STATIC_LIBS
SUB_DIRECTORIES = asn1 crypto
else
ifdef BOOTSTRAP
- SUB_DIRECTORIES = \
- kernel stdlib compiler
- else
- ifdef SECONDARY_BOOTSTRAP
- ifdef TINY_BUILD
- SUB_DIRECTORIES = parsetools sasl
- else
- SUB_DIRECTORIES = parsetools asn1/src
- endif
- else
- ifdef TERTIARY_BOOTSTRAP
- SUB_DIRECTORIES = snmp sasl erl_interface jinterface syntax_tools wx public_key
- else
- ifdef DOC_BOOTSTRAP
- SUB_DIRECTORIES = xmerl edoc erl_docgen public_key
- else # Not bootstrap build
- SUB_DIRECTORIES = $(ERTS_APPLICATIONS) \
- $(ERLANG_APPLICATIONS) \
- $(EXTRA_APPLICATIONS)
- endif
- endif
- endif
+ SUB_DIRECTORIES = $(BOOTSTRAP)
+ else # Not bootstrap build
+ SUB_DIRECTORIES = $(ERTS_APPLICATIONS) \
+ $(ERLANG_APPLICATIONS) \
+ $(EXTRA_APPLICATIONS)
endif
endif
diff --git a/lib/asn1/c_src/asn1_erl_nif.c b/lib/asn1/c_src/asn1_erl_nif.c
index 8dc0dfedcf..aad9db5289 100644
--- a/lib/asn1/c_src/asn1_erl_nif.c
+++ b/lib/asn1/c_src/asn1_erl_nif.c
@@ -1167,7 +1167,7 @@ static mem_chunk_t *ber_new_chunk(unsigned int length) {
new->next = NULL;
new->top = enif_alloc(sizeof(char) * length);
if (new->top == NULL) {
- free(new);
+ enif_free(new);
return NULL;
}
new->curr = new->top + length - 1;
diff --git a/lib/asn1/src/asn1_db.erl b/lib/asn1/src/asn1_db.erl
index 7486fa2db9..c7799d0762 100644
--- a/lib/asn1/src/asn1_db.erl
+++ b/lib/asn1/src/asn1_db.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -82,11 +82,11 @@ loop(#state{parent = Parent, monitor = MRef, table = Table,
includes = Includes} = State) ->
receive
{set, Mod, K2, V} ->
- [{_, Modtab}] = ets:lookup(Table, Mod),
+ Modtab = ets:lookup_element(Table, Mod, 2),
ets:insert(Modtab, {K2, V}),
loop(State);
{set, Mod, Kvs} ->
- [{_, Modtab}] = ets:lookup(Table, Mod),
+ Modtab = ets:lookup_element(Table, Mod, 2),
ets:insert(Modtab, Kvs),
loop(State);
{From, {get, Mod, K2}} ->
@@ -105,7 +105,7 @@ loop(#state{parent = Parent, monitor = MRef, table = Table,
end,
loop(State);
{save, OutFile, Mod} ->
- [{_,Mtab}] = ets:lookup(Table, Mod),
+ Mtab = ets:lookup_element(Table, Mod, 2),
TempFile = OutFile ++ ".#temp",
ok = ets:tab2file(Mtab, TempFile),
ok = file:rename(TempFile, OutFile),
@@ -146,10 +146,7 @@ get_table(Table, Mod, Includes) ->
end.
lookup(Tab, K) ->
- case ets:lookup(Tab, K) of
- [] -> undefined;
- [{K,V}] -> V
- end.
+ ets:lookup_element(Tab, K, 2, undefined).
info(EruleMaps) ->
{asn1ct:vsn(),EruleMaps}.
diff --git a/lib/asn1/src/asn1ct.erl b/lib/asn1/src/asn1ct.erl
index de10339c41..3ab83f6262 100644
--- a/lib/asn1/src/asn1ct.erl
+++ b/lib/asn1/src/asn1ct.erl
@@ -20,6 +20,7 @@
%%
%%
-module(asn1ct).
+-feature(maybe_expr, enable).
%% Compile Time functions for ASN.1 (e.g ASN.1 compiler).
@@ -2233,15 +2234,13 @@ maybe_rename_function2(Thing,Name,Suffix)
%% generated_functions_member/4 checks on both Name and Pattern if
%% the element exists in L
generated_functions_member(M,Name,L,Pattern) ->
- case generated_functions_member(M,Name,L) of
- true ->
- L2 = generated_functions_filter(M,Name,L),
- case lists:keysearch(Pattern,3,L2) of
- {value,_} ->
- true;
- _ -> false
- end;
- _ -> false
+ maybe
+ true ?= generated_functions_member(M,Name,L),
+ L2 = generated_functions_filter(M,Name,L),
+ {value,_} ?= lists:keysearch(Pattern,3,L2),
+ true
+ else
+ false -> false
end.
generated_functions_member(_M,Name,[{Name,_,_}|_]) ->
diff --git a/lib/asn1/src/asn1ct_check.erl b/lib/asn1/src/asn1ct_check.erl
index 12db184b4e..8b7144b5f9 100644
--- a/lib/asn1/src/asn1ct_check.erl
+++ b/lib/asn1/src/asn1ct_check.erl
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1855,8 +1855,8 @@ validate_oid(S, OidType, [{'NamedNumber',_Name,Value}|Vrest], Acc)
validate_oid(S, OidType, Vrest, [Value|Acc]);
validate_oid(S, OidType, [#'Externalvaluereference'{}=Id|Vrest], Acc) ->
NeededOidType = case Acc of
- [] -> o_id;
- [_|_] -> rel_oid
+ [] when OidType =:= o_id -> o_id;
+ _ -> rel_oid
end,
try get_oid_value(S, NeededOidType, true, Id) of
Val when is_integer(Val) ->
@@ -2294,9 +2294,8 @@ use_maps(#state{options=Opts}) ->
create_map_value(Components, ListOfVals) ->
Zipped = lists:zip(Components, ListOfVals),
- L = [{Name,V} || {#'ComponentType'{name=Name},V} <- Zipped,
- V =/= asn1_NOVALUE],
- maps:from_list(L).
+ #{Name => V || {#'ComponentType'{name=Name},V} <- Zipped,
+ V =/= asn1_NOVALUE}.
normalize_seq_or_set(SorS, S,
[{#seqtag{val=Cname},V}|Vs],
diff --git a/lib/asn1/src/asn1ct_gen_check.erl b/lib/asn1/src/asn1ct_gen_check.erl
index 4996491fa7..0e8cd557a4 100644
--- a/lib/asn1/src/asn1ct_gen_check.erl
+++ b/lib/asn1/src/asn1ct_gen_check.erl
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2014-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2014-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -167,8 +167,8 @@ do_seq_set(#gen{pack=map}=Gen, Cs0, Default) ->
end, Cs),
case AllLiterals of
true ->
- L = [{Name,Lit} || {Name,{literal,Lit}} <- Cs],
- {literal,maps:from_list(L)};
+ M = #{Name => Lit || {Name,{literal,Lit}} <- Cs},
+ {literal,M};
false ->
Key = {Cs,Default},
DoGen = fun(Fd, Name) ->
diff --git a/lib/asn1/src/asn1ct_gen_jer.erl b/lib/asn1/src/asn1ct_gen_jer.erl
index 7f6152ad80..cfd16ea7ca 100644
--- a/lib/asn1/src/asn1ct_gen_jer.erl
+++ b/lib/asn1/src/asn1ct_gen_jer.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -159,10 +159,9 @@ gen_encode_choice(Erules,TypeName,D) when is_record(D,type) ->
{Rl,El} -> Rl ++ El;
_ -> CompList
end,
- {choice,maps:from_list(
- [{AltName,AltType}||
- {AltName,AltType,_OptOrMand} <-
- gen_enc_comptypes(Erules,TypeName,CompList1,0,0,[])])}.
+ {choice,#{AltName => AltType ||
+ {AltName,AltType,_OptOrMand} <-
+ gen_enc_comptypes(Erules,TypeName,CompList1,0,0,[])}}.
gen_decode_choice(_,_,_) -> ok.
diff --git a/lib/asn1/src/asn1rt_nif.erl b/lib/asn1/src/asn1rt_nif.erl
index e724d60bae..753729c810 100644
--- a/lib/asn1/src/asn1rt_nif.erl
+++ b/lib/asn1/src/asn1rt_nif.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -47,13 +47,15 @@ load_nif() ->
filename:join(
[PrivDir,
"lib",
- LibTypeName ++ "*"])) /= []) orelse
+ LibTypeName ++ "*"]),
+ erl_prim_loader) /= []) orelse
(filelib:wildcard(
filename:join(
[PrivDir,
"lib",
erlang:system_info(system_architecture),
- LibTypeName ++ "*"])) /= []) of
+ LibTypeName ++ "*"]),
+ erl_prim_loader) /= []) of
true -> LibTypeName;
false -> LibBaseName
end
@@ -66,7 +68,9 @@ load_nif() ->
filename:join([PrivDir, "lib",
erlang:system_info(system_architecture)]),
Candidate =
- filelib:wildcard(filename:join([ArchLibDir,LibName ++ "*" ])),
+ filelib:wildcard(
+ filename:join([ArchLibDir,LibName ++ "*" ]),
+ erl_prim_loader),
case Candidate of
[] -> Error1;
_ ->
diff --git a/lib/asn1/src/asn1rtt_ext.erl b/lib/asn1/src/asn1rtt_ext.erl
index f028e33888..7596194144 100644
--- a/lib/asn1/src/asn1rtt_ext.erl
+++ b/lib/asn1/src/asn1rtt_ext.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -127,7 +127,5 @@ transform_to_EXTERNAL1994_maps(V0) ->
_ ->
%% Keep the EXTERNAL 1990 definition to avoid losing
%% information.
- V = [{K,V} || {K,V} <- maps:to_list(V0),
- V =/= asn1_NOVALUE],
- maps:from_list(V)
+ #{K => V || K := V <- V0, V =/= asn1_NOVALUE}
end.
diff --git a/lib/asn1/test/asn1_SUITE_data/ValueTest.asn b/lib/asn1/test/asn1_SUITE_data/ValueTest.asn
index 0474386061..39feddfffd 100644
--- a/lib/asn1/test/asn1_SUITE_data/ValueTest.asn
+++ b/lib/asn1/test/asn1_SUITE_data/ValueTest.asn
@@ -33,6 +33,10 @@ include-roid OBJECT IDENTIFIER ::= {0 rel-oid-1}
include-oid OBJECT IDENTIFIER ::= {integer-first 1}
include-all OBJECT IDENTIFIER ::= {integer-first 1 rel-oid-1 42}
+-- RELATIVE-OID
+rel-oid-2 RELATIVE-OID ::= {rel-oid-1 6}
+rel-oid-3 RELATIVE-OID ::= {rel-oid-1 rel-oid-2 7}
+
--Character strings
numericstring NumericString ::= "01234567"
printablestring PrintableString ::= "PrintableString"
diff --git a/lib/asn1/test/error_SUITE.erl b/lib/asn1/test/error_SUITE.erl
index 6ce77d93fb..aa2b36bbd7 100644
--- a/lib/asn1/test/error_SUITE.erl
+++ b/lib/asn1/test/error_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -478,6 +478,7 @@ rel_oids(Config) ->
"wrong-type-rel-oid-5 RELATIVE-OID ::= object-1.&undef\n"
"oid-bad-first OBJECT IDENTIFIER ::= {legal-roid 3}\n"
+ "roid-bad-first RELATIVE-OID ::= {legal-oid 3}\n"
"END\n">>},
{error,
[
@@ -486,7 +487,8 @@ rel_oids(Config) ->
{structured_error,{M,14},asn1ct_check,{illegal_oid,rel_oid}},
{structured_error,{M,15},asn1ct_check,{illegal_oid,rel_oid}},
{structured_error,{M,16},asn1ct_check,{undefined_field,undef}},
- {structured_error,{M,17},asn1ct_check,{illegal_oid,o_id}}
+ {structured_error,{M,17},asn1ct_check,{illegal_oid,o_id}},
+ {structured_error,{M,18},asn1ct_check,{illegal_oid,rel_oid}}
]
} = run(P, Config),
ok.
diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml
index bc0e60f9a3..0d7095db5d 100644
--- a/lib/common_test/doc/src/ct.xml
+++ b/lib/common_test/doc/src/ct.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2010</year><year>2021</year>
+ <year>2010</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -67,20 +67,20 @@
<datatypes>
<datatype>
- <name>handle() = pid()</name>
+ <name name="handle" />
<desc>
<p>The identity (handle) of a connection.</p>
</desc>
</datatype>
<datatype>
- <name>config_key() = atom()</name>
+ <name name="config_key" />
<desc>
<p>A configuration key which exists in a configuration file</p>
</desc>
</datatype>
<datatype>
- <name>target_name() = atom()</name>
+ <name name="target_name" />
<desc>
<p>A name and association to configuration data introduced
through a require statement, or a call to
@@ -90,8 +90,11 @@
</desc>
</datatype>
<datatype>
- <name>key_or_name() = config_key() | target_name()</name>
- <name>conn_log_options() = [conn_log_option()]</name>
+ <name name="key_or_name" />
+ <desc></desc>
+ </datatype>
+ <datatype>
+ <name name="conn_log_options" />
<desc>
<p>Options that can be given to the <c>cth_conn_log</c> hook,
which is used for logging of NETCONF and Telnet
@@ -103,21 +106,24 @@
</datatype>
<datatype>
- <name>conn_log_option() = {log_type,conn_log_type()} | {hosts,[key_or_name()]}</name>
- <name>conn_log_type() = raw | pretty | html | silent</name>
- <name>conn_log_mod() = ct_netconfc | ct_telnet</name>
+ <name name="conn_log_option" />
+ <desc></desc>
+ </datatype>
+ <datatype>
+ <name name="conn_log_type" />
+ <desc></desc>
+ </datatype>
+ <datatype>
+ <name name="conn_log_mod" />
+ <desc></desc>
</datatype>
</datatypes>
<funcs>
<func>
- <name since="">abort_current_testcase(Reason) -&gt; ok | {error, ErrorReason}</name>
+ <name since="" name="abort_current_testcase" arity="1" />
<fsummary>Aborts the currently executing test case.</fsummary>
- <type>
- <v>Reason = term()</v>
- <v>ErrorReason = no_testcase_running | parallel_group</v>
- </type>
<desc><marker id="abort_current_testcase-1"/>
<p>Aborts the currently executing test case. The user must know with
certainty which test case is currently executing. The function is
@@ -130,14 +136,9 @@
</func>
<func>
- <name since="OTP R14B">add_config(Callback, Config) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R14B" name="add_config" arity="2" />
<fsummary>Loads configuration variables using the specified callback
module and configuration string.</fsummary>
- <type>
- <v>Callback = atom()</v>
- <v>Config = string()</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="add_config-2"/>
<p>Loads configuration variables using the specified callback module and
configuration string. The callback module is to be either loaded or
@@ -149,14 +150,9 @@
</func>
<func>
- <name since="OTP R15B02">break(Comment) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R15B02" name="break" arity="1" />
<fsummary>Cancels any active timetrap and pause the execution of the
current test case until the user calls function continue/0.</fsummary>
- <type>
- <v>Comment = string()</v>
- <v>Reason = {multiple_cases_running, TestCases} | 'enable break with release_shell option'</v>
- <v>TestCases = [atom()]</v>
- </type>
<desc><marker id="break-1"/>
<p>Cancels any active timetrap and pauses the execution of the
current test case until the user calls function <c>continue/0</c>.
@@ -179,14 +175,9 @@
</func>
<func>
- <name since="OTP R15B02">break(TestCase, Comment) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R15B02" name="break" arity="2" />
<fsummary>Works the same way as break/1, only argument TestCase makes it
possible to pause a test case executing in a parallel group.</fsummary>
- <type>
- <v>TestCase = atom()</v>
- <v>Comment = string()</v>
- <v>Reason = 'test case not running' | 'enable break with release_shell option'</v>
- </type>
<desc><marker id="break-2"/>
<p>Works the same way as
<seemfa marker="#break/1"><c>ct:break/1</c></seemfa>, only
@@ -201,11 +192,8 @@
</func>
<func>
- <name since="OTP R15B">capture_get() -&gt; ListOfStrings</name>
+ <name since="OTP R15B" name="capture_get" arity="0" />
<fsummary>Equivalent to capture_get([default]).</fsummary>
- <type>
- <v>ListOfStrings = [string()]</v>
- </type>
<desc><marker id="capture_get-0"/>
<p>Equivalent to
<seemfa marker="#capture_get/1">ct:capture_get([default])</seemfa>.</p>
@@ -213,13 +201,9 @@
</func>
<func>
- <name since="OTP R15B">capture_get(ExclCategories) -&gt; ListOfStrings</name>
+ <name since="OTP R15B" name="capture_get" arity="1" />
<fsummary>Returns and purges the list of text strings buffered during
the latest session of capturing printouts to stdout.</fsummary>
- <type>
- <v>ExclCategories = [atom()]</v>
- <v>ListOfStrings = [string()]</v>
- </type>
<desc><marker id="capture_get-1"/>
<p>Returns and purges the list of text strings buffered during the
latest session of capturing printouts to <c>stdout</c>. Log
@@ -235,7 +219,7 @@
</func>
<func>
- <name since="OTP R15B">capture_start() -&gt; ok</name>
+ <name since="OTP R15B" name="capture_start" arity="0" />
<fsummary>Starts capturing all text strings printed to stdout
during execution of the test case.</fsummary>
<desc><marker id="capture_start-0"/>
@@ -249,7 +233,7 @@
</func>
<func>
- <name since="OTP R15B">capture_stop() -&gt; ok</name>
+ <name since="OTP R15B" name="capture_stop" arity="0" />
<fsummary>Stops capturing text strings (a session started with
capture_start/0).</fsummary>
<desc><marker id="capture_stop-0"/>
@@ -263,12 +247,9 @@
</func>
<func>
- <name since="">comment(Comment) -&gt; ok</name>
+ <name since="" name="comment" arity="1" />
<fsummary>Prints the specified Comment in the comment field in the
table on the test suite result page.</fsummary>
- <type>
- <v>Comment = term()</v>
- </type>
<desc><marker id="comment-1"/>
<p>Prints the specified <c>Comment</c> in the comment field in the
table on the test suite result page.</p>
@@ -280,13 +261,9 @@
</func>
<func>
- <name since="OTP R15B">comment(Format, Args) -&gt; ok</name>
+ <name since="OTP R15B" name="comment" arity="2" />
<fsummary>Prints the formatted string in the comment field in the
table on the test suite result page.</fsummary>
- <type>
- <v>Format = string()</v>
- <v>Args = list()</v>
- </type>
<desc><marker id="comment-2"/>
<p>Prints the formatted string in the comment field in the table
on the test suite result page.</p>
@@ -299,7 +276,7 @@
</func>
<func>
- <name since="OTP R15B02">continue() -&gt; ok</name>
+ <name since="OTP R15B02" name="continue" arity="0" />
<fsummary>This function must be called to continue after a test
case (not executing in a parallel group) has called break/1.</fsummary>
<desc><marker id="continue-0"/>
@@ -310,12 +287,9 @@
</func>
<func>
- <name since="OTP R15B02">continue(TestCase) -&gt; ok</name>
+ <name since="OTP R15B02" name="continue" arity="1" />
<fsummary>This function must be called to continue after a test case
has called break/2.</fsummary>
- <type>
- <v>TestCase = atom()</v>
- </type>
<desc><marker id="continue-1"/>
<p>This function must be called to continue after a test case has
called <seemfa marker="#break/2"><c>ct:break/2</c></seemfa>.
@@ -326,14 +300,9 @@
</func>
<func>
- <name since="">decrypt_config_file(EncryptFileName, TargetFileName) -&gt; ok | {error, Reason}</name>
+ <name since="" name="decrypt_config_file" arity="2" />
<fsummary>Decrypts EncryptFileName, previously generated with
encrypt_config_file/2,3.</fsummary>
- <type>
- <v>EncryptFileName = string()</v>
- <v>TargetFileName = string()</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="decrypt_config_file-2"/>
<p>Decrypts <c>EncryptFileName</c>, previously generated with
<seemfa marker="#encrypt_config_file/2"><c>ct:encrypt_config_file/2,3</c></seemfa>.
@@ -345,15 +314,9 @@
</func>
<func>
- <name since="">decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -&gt; ok | {error, Reason}</name>
+ <name since="" name="decrypt_config_file" arity="3" />
<fsummary>Decrypts EncryptFileName, previously generated with
encrypt_config_file/2,3.</fsummary>
- <type>
- <v>EncryptFileName = string()</v>
- <v>TargetFileName = string()</v>
- <v>KeyOrFile = {key, string()} | {file, string()}</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="decrypt_config_file-3"/>
<p>Decrypts <c>EncryptFileName</c>, previously generated with
<seemfa marker="#encrypt_config_file/2"><c>ct:encrypt_config_file/2,3</c></seemfa>.
@@ -363,14 +326,9 @@
</func>
<func>
- <name since="">encrypt_config_file(SrcFileName, EncryptFileName) -&gt; ok | {error, Reason}</name>
+ <name since="" name="encrypt_config_file" arity="2" />
<fsummary>Encrypts the source configuration file with DES3 and saves the
result in file EncryptFileName.</fsummary>
- <type>
- <v>SrcFileName = string()</v>
- <v>EncryptFileName = string()</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="encrypt_config_file-2"/>
<p>Encrypts the source configuration file with DES3 and saves the result
in file <c>EncryptFileName</c>. The key, a string, must be
@@ -389,15 +347,9 @@
</func>
<func>
- <name since="">encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -&gt; ok | {error, Reason}</name>
+ <name since="" name="encrypt_config_file" arity="3" />
<fsummary>Encrypts the source configuration file with DES3 and saves the
result in the target file EncryptFileName.</fsummary>
- <type>
- <v>SrcFileName = string()</v>
- <v>EncryptFileName = string()</v>
- <v>KeyOrFile = {key, string()} | {file, string()}</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="encrypt_config_file-3"/>
<p>Encrypts the source configuration file with DES3 and saves the result
in the target file <c>EncryptFileName</c>. The encryption key
@@ -415,26 +367,19 @@
</func>
<func>
- <name since="">fail(Reason) -&gt; ok</name>
+ <name since="" name="fail" arity="1" />
<fsummary>Terminates a test case with the specified error
Reason.</fsummary>
- <type>
- <v>Reason = term()</v>
- </type>
<desc><marker id="fail-1"/>
<p>Terminates a test case with the specified error <c>Reason</c>.</p>
</desc>
</func>
<func>
- <name since="OTP R15B">fail(Format, Args) -&gt; ok</name>
+ <name since="OTP R15B" name="fail" arity="2" />
<fsummary>Terminates a test case with an error message specified by
a format string and a list of values (used as arguments to
io_lib:format/2).</fsummary>
- <type>
- <v>Format = string()</v>
- <v>Args = list()</v>
- </type>
<desc><marker id="fail-2"/>
<p>Terminates a test case with an error message specified by a
format string and a list of values (used as arguments to
@@ -443,7 +388,7 @@
</func>
<func>
- <name since="">get_config(Required) -&gt; Value</name>
+ <name since="" name="get_config" arity="1" />
<fsummary>Equivalent to get_config(Required, undefined, []).</fsummary>
<desc><marker id="get_config-1"/>
<p>Equivalent to <seemfa marker="#get_config/3"><c>ct:get_config(Required,
@@ -452,7 +397,7 @@
</func>
<func>
- <name since="">get_config(Required, Default) -&gt; Value</name>
+ <name since="" name="get_config" arity="2" />
<fsummary>Equivalent to get_config(Required, Default, []).</fsummary>
<desc><marker id="get_config-2"/>
<p>Equivalent to <seemfa marker="#get_config/3"><c>ct:get_config(Required,
@@ -461,17 +406,8 @@
</func>
<func>
- <name since="">get_config(Required, Default, Opts) -&gt; ValueOrElement</name>
+ <name since="" name="get_config" arity="3" />
<fsummary>Reads configuration data values.</fsummary>
- <type>
- <v>Required = KeyOrName | {KeyOrName, SubKey} | {KeyOrName, SubKey, SubKey}</v>
- <v>KeyOrName = atom()</v>
- <v>SubKey = atom()</v>
- <v>Default = term()</v>
- <v>Opts = [Opt] | []</v>
- <v>Opt = element | all</v>
- <v>ValueOrElement = term() | Default</v>
- </type>
<desc><marker id="get_config-3"/>
<p>Reads configuration data values.</p>
@@ -527,11 +463,8 @@
</func>
<func>
- <name since="OTP 17.5">get_event_mgr_ref() -&gt; EvMgrRef</name>
+ <name since="OTP 17.5" name="get_event_mgr_ref" arity="0" />
<fsummary>Gets a reference to the <c>Common Test</c> event manager.</fsummary>
- <type>
- <v>EvMgrRef = atom()</v>
- </type>
<desc><marker id="get_event_mgr_ref-0"/>
<p>Gets a reference to the <c>Common Test</c> event manager.
The reference can be used to, for example, add a user-specific
@@ -545,7 +478,7 @@
</func>
<func>
- <name since="OTP 21.0">get_progname() -&gt; string()</name>
+ <name since="OTP 21.0" name="get_progname" arity="0" />
<fsummary>Returns the command used to start this Erlang instance.</fsummary>
<desc><marker id="get_progname-0"/>
<p>Returns the command used to start this Erlang instance.
@@ -555,22 +488,8 @@
</func>
<func>
- <name since="">get_status() -&gt; TestStatus | {error, Reason} | no_tests_running</name>
+ <name since="" name="get_status" arity="0" />
<fsummary>Returns status of ongoing test.</fsummary>
- <type>
- <v>TestStatus = [StatusElem]</v>
- <v>StatusElem = {current, TestCaseInfo} | {successful, Successful} | {failed, Failed} | {skipped, Skipped} | {total, Total}</v>
- <v>TestCaseInfo = {Suite, TestCase} | [{Suite, TestCase}]</v>
- <v>Suite = atom()</v>
- <v>TestCase = atom()</v>
- <v>Successful = integer()</v>
- <v>Failed = integer()</v>
- <v>Skipped = {UserSkipped, AutoSkipped}</v>
- <v>UserSkipped = integer()</v>
- <v>AutoSkipped = integer()</v>
- <v>Total = integer()</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="get_status-0"/>
<p>Returns status of ongoing test. The returned list contains
information about which test case is executing (a list of cases
@@ -581,13 +500,9 @@
</func>
<func>
- <name since="">get_target_name(Handle) -&gt; {ok, TargetName} | {error, Reason}</name>
+ <name since="" name="get_target_name" arity="1" />
<fsummary>Returns the name of the target that the specified connection
belongs to.</fsummary>
- <type>
- <v>Handle = handle()</v>
- <v>TargetName = target_name()</v>
- </type>
<desc><marker id="get_target_name-1"/>
<p>Returns the name of the target that the specified connection
belongs to.</p>
@@ -595,13 +510,9 @@
</func>
<func>
- <name since="OTP 18.0">get_testspec_terms() -&gt; TestSpecTerms | undefined</name>
+ <name since="OTP 18.0" name="get_testspec_terms" arity="0" />
<fsummary>Gets a list of all test specification terms used to
configure and run this test.</fsummary>
- <type>
- <v>TestSpecTerms = [{Tag, Value}]</v>
- <v>Value = [term()]</v>
- </type>
<desc><marker id="get_testspec_terms-0"/>
<p>Gets a list of all test specification terms used to configure
and run this test.</p>
@@ -609,16 +520,9 @@
</func>
<func>
- <name since="OTP 18.0">get_testspec_terms(Tags) -&gt; TestSpecTerms | undefined</name>
+ <name since="OTP 18.0" name="get_testspec_terms" arity="1" />
<fsummary>Reads one or more terms from the test specification used to
configure and run this test.</fsummary>
- <type>
- <v>Tags = [Tag] | Tag</v>
- <v>Tag = atom()</v>
- <v>TestSpecTerms = [{Tag, Value}] | {Tag, Value}</v>
- <v>Value = [{Node, term()}] | [term()]</v>
- <v>Node = atom()</v>
- </type>
<desc><marker id="get_testspec_terms-1"/>
<p>Reads one or more terms from the test specification used to
configure and run this test. <c>Tag</c> is any valid test
@@ -636,14 +540,9 @@
</func>
<func>
- <name since="OTP R15B">get_timetrap_info() -&gt; {Time, {Scaling,ScaleVal}}</name>
+ <name since="OTP R15B" name="get_timetrap_info" arity="0" />
<fsummary>Reads information about the timetrap set for the current
test case.</fsummary>
- <type>
- <v>Time = integer() | infinity</v>
- <v>Scaling = true | false</v>
- <v>ScaleVal = integer()</v>
- </type>
<desc><marker id="get_timetrap_info-0"/>
<p>Reads information about the timetrap set for the current test
case. <c>Scaling</c> indicates if <c>Common Test</c> will attempt
@@ -655,12 +554,8 @@
</func>
<func>
- <name since="OTP 19.1">get_verbosity(Category) -&gt; Level | undefined</name>
+ <name since="OTP 19.1" name="get_verbosity" arity="1" />
<fsummary>Read the verbosity level for a logging category.</fsummary>
- <type>
- <v>Category = default | atom()</v>
- <v>Level = integer()</v>
- </type>
<desc><marker id="get_verbosity-1"/>
<p>This function returns the verbosity level for the specified logging
category. See the <seeguide marker="write_test_chapter#logging">
@@ -670,18 +565,8 @@
</func>
<func>
- <name since="">install(Opts) -&gt; ok | {error, Reason}</name>
+ <name since="" name="install" arity="1" />
<fsummary>Installs configuration files and event handlers.</fsummary>
- <type>
- <v>Opts = [Opt]</v>
- <v>Opt = {config, ConfigFiles} | {event_handler, Modules} | {decrypt, KeyOrFile}</v>
- <v>ConfigFiles = [ConfigFile]</v>
- <v>ConfigFile = string()</v>
- <v>Modules = [atom()]</v>
- <v>KeyOrFile = {key, Key} | {file, KeyFile}</v>
- <v>Key = string()</v>
- <v>KeyFile = string()</v>
- </type>
<desc><marker id="install-1"/>
<p>Installs configuration files and event handlers.</p>
@@ -697,15 +582,9 @@
</func>
<func>
- <name since="">listenv(Telnet) -&gt; [Env]</name>
+ <name since="" name="listenv" arity="1" />
<fsummary>Performs command listenv on the specified Telnet connection
and returns the result as a list of key-value pairs.</fsummary>
- <type>
- <v>Telnet = term()</v>
- <v>Env = {Key, Value}</v>
- <v>Key = string()</v>
- <v>Value = string()</v>
- </type>
<desc><marker id="listenv-1"/>
<p>Performs command <c>listenv</c> on the specified Telnet connection
and returns the result as a list of key-value pairs.</p>
@@ -713,7 +592,7 @@
</func>
<func>
- <name since="">log(Format) -&gt; ok</name>
+ <name since="" name="log" arity="1" />
<fsummary>Equivalent to log(default, 50, Format, [], []).</fsummary>
<desc><marker id="log-1"/>
<p>Equivalent to
@@ -722,13 +601,9 @@
</func>
<func>
- <name since="">log(X1, X2) -&gt; ok</name>
+ <name since="" name="log" arity="2" />
<fsummary>Equivalent to log(Category, Importance, Format,
FormatArgs, []).</fsummary>
- <type>
- <v>X1 = Category | Importance | Format</v>
- <v>X2 = Format | FormatArgs</v>
- </type>
<desc><marker id="log-2"/>
<p>Equivalent to <seemfa marker="#log/5"><c>ct:log(Category,
Importance, Format, FormatArgs, [])</c></seemfa>.</p>
@@ -736,14 +611,9 @@
</func>
<func>
- <name since="">log(X1, X2, X3) -&gt; ok</name>
+ <name since="" name="log" arity="3" />
<fsummary>Equivalent to log(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
- <type>
- <v>X1 = Category | Importance</v>
- <v>X2 = Importance | Format</v>
- <v>X3 = Format | FormatArgs | Opts</v>
- </type>
<desc><marker id="log-3"/>
<p>Equivalent to <seemfa marker="#log/5"><c>ct:log(Category,
Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
@@ -751,15 +621,9 @@
</func>
<func>
- <name since="OTP R15B02">log(X1, X2, X3, X4) -&gt; ok</name>
+ <name since="OTP R15B02" name="log" arity="4" />
<fsummary>Equivalent to log(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
- <type>
- <v>X1 = Category | Importance</v>
- <v>X2 = Importance | Format</v>
- <v>X3 = Format | FormatArgs</v>
- <v>X4 = FormatArgs | Opts</v>
- </type>
<desc><marker id="log-4"/>
<p>Equivalent to <seemfa marker="#log/5"><c>ct:log(Category,
Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
@@ -767,16 +631,8 @@
</func>
<func>
- <name since="OTP 18.3">log(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
+ <name since="OTP 18.3" name="log" arity="5" />
<fsummary>Prints from a test case to the log file.</fsummary>
- <type>
- <v>Category = atom()</v>
- <v>Importance = integer()</v>
- <v>Format = string()</v>
- <v>FormatArgs = list()</v>
- <v>Opts = [Opt]</v>
- <v>Opt = {heading,string()} | no_css | esc_chars</v>
- </type>
<desc><marker id="log-5"/>
<p>Prints from a test case to the log file.</p>
@@ -798,13 +654,10 @@
</func>
<func>
- <name since="OTP R15B01">make_priv_dir() -&gt; ok | {error, Reason}</name>
+ <name since="OTP R15B01" name="make_priv_dir" arity="0" />
<fsummary>If the test has been started with option create_priv_dir
set to manual_per_tc, in order for the test case to use the private
directory, it must first create it by calling this function.</fsummary>
- <type>
- <v>Reason = term()</v>
- </type>
<desc><marker id="make_priv_dir-0"/>
<p>If the test is started with option <c>create_priv_dir</c>
set to <c>manual_per_tc</c>, in order for the test case to use
@@ -814,13 +667,9 @@
</func>
<func>
- <name since="OTP R15B02">notify(Name, Data) -&gt; ok</name>
+ <name since="OTP R15B02" name="notify" arity="2" />
<fsummary>Sends an asynchronous notification of type Name with Data
to the <c>Common Test</c> event manager.</fsummary>
- <type>
- <v>Name = atom()</v>
- <v>Data = term()</v>
- </type>
<desc><marker id="notify-2"/>
<p>Sends an asynchronous notification of type <c>Name</c> with
<c>Data</c>to the Common Test event manager. This can later be
@@ -832,7 +681,7 @@
</func>
<func>
- <name since="">pal(Format) -&gt; ok</name>
+ <name since="" name="pal" arity="1" />
<fsummary>Equivalent to pal(default, 50, Format, [], []).</fsummary>
<desc><marker id="pal-1"/>
<p>Equivalent to
@@ -842,13 +691,9 @@
</func>
<func>
- <name since="">pal(X1, X2) -&gt; ok</name>
+ <name since="" name="pal" arity="2" />
<fsummary>Equivalent to pal(Category, Importance, Format,
FormatArgs, []).</fsummary>
- <type>
- <v>X1 = Category | Importance | Format</v>
- <v>X2 = Format | FormatArgs</v>
- </type>
<desc><marker id="pal-2"/>
<p>Equivalent to <seemfa marker="#pal/5"><c>ct:pal(Category,
Importance, Format, FormatArgs, [])</c></seemfa>.</p>
@@ -856,14 +701,9 @@
</func>
<func>
- <name since="">pal(X1, X2, X3) -&gt; ok</name>
+ <name since="" name="pal" arity="3" />
<fsummary>Equivalent to pal(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
- <type>
- <v>X1 = Category | Importance</v>
- <v>X2 = Importance | Format</v>
- <v>X3 = Format | FormatArgs | Opts</v>
- </type>
<desc><marker id="pal-3"/>
<p>Equivalent to <seemfa marker="#pal/5"><c>ct:pal(Category,
Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
@@ -871,15 +711,9 @@
</func>
<func>
- <name since="OTP R15B02">pal(X1, X2, X3, X4) -&gt; ok</name>
+ <name since="OTP R15B02" name="pal" arity="4" />
<fsummary>Equivalent to pal(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
- <type>
- <v>X1 = Category | Importance</v>
- <v>X2 = Importance | Format</v>
- <v>X3 = Format | FormatArgs</v>
- <v>X4 = FormatArgs | Opts</v>
- </type>
<desc><marker id="pal-4"/>
<p>Equivalent to <seemfa marker="#pal/5"><c>ct:pal(Category,
Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
@@ -887,16 +721,8 @@
</func>
<func>
- <name since="OTP 19.2">pal(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
+ <name since="OTP 19.2" name="pal" arity="5" />
<fsummary>Prints and logs from a test case.</fsummary>
- <type>
- <v>Category = atom()</v>
- <v>Importance = integer()</v>
- <v>Format = string()</v>
- <v>FormatArgs = list()</v>
- <v>Opts = [Opt]</v>
- <v>Opt = {heading,string()} | no_css</v>
- </type>
<desc><marker id="pal-5"/>
<p>Prints and logs from a test case.</p>
@@ -918,14 +744,9 @@
</func>
<func>
- <name since="">parse_table(Data) -&gt; {Heading, Table}</name>
+ <name since="" name="parse_table" arity="1" />
<fsummary>Parses the printout from an SQL table and returns a list of
tuples.</fsummary>
- <type>
- <v>Data = [string()]</v>
- <v>Heading = tuple()</v>
- <v>Table = [tuple()]</v>
- </type>
<desc><marker id="parse_table-1"/>
<p>Parses the printout from an SQL table and returns a list of
tuples.</p>
@@ -940,7 +761,7 @@
</func>
<func>
- <name since="">print(Format) -&gt; ok</name>
+ <name since="" name="print" arity="1" />
<fsummary>Equivalent to print(default, 50, Format, [], []).</fsummary>
<desc><marker id="print-1"/>
<p>Equivalent to <seemfa marker="#print/5"><c>ct:print(default,
@@ -949,13 +770,9 @@
</func>
<func>
- <name since="OTP R15B02">print(X1, X2) -&gt; ok</name>
+ <name since="OTP R15B02" name="print" arity="2" />
<fsummary>Equivalent to print(Category, Importance, Format,
FormatArgs, []).</fsummary>
- <type>
- <v>X1 = Category | Importance | Format</v>
- <v>X2 = Format | FormatArgs</v>
- </type>
<desc><marker id="print-2"/>
<p>Equivalent to <seemfa marker="#print/5"><c>ct:print(Category,
Importance, Format, FormatArgs, [])</c></seemfa>.</p>
@@ -963,14 +780,9 @@
</func>
<func>
- <name since="">print(X1, X2, X3) -&gt; ok</name>
+ <name since="" name="print" arity="3" />
<fsummary>Equivalent to print(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
- <type>
- <v>X1 = Category | Importance</v>
- <v>X2 = Importance | Format</v>
- <v>X3 = Format | FormatArgs | Opts</v>
- </type>
<desc><marker id="print-3"/>
<p>Equivalent to <seemfa marker="#print/5"><c>ct:print(Category,
Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
@@ -978,15 +790,9 @@
</func>
<func>
- <name since="OTP R15B02">print(X1, X2, X3, X4) -&gt; ok</name>
+ <name since="OTP R15B02" name="print" arity="4" />
<fsummary>Equivalent to print(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
- <type>
- <v>X1 = Category | Importance</v>
- <v>X2 = Importance | Format</v>
- <v>X3 = Format | FormatArgs</v>
- <v>X4 = FormatArgs | Opts</v>
- </type>
<desc><marker id="print-4"/>
<p>Equivalent to <seemfa marker="#print/5"><c>ct:print(Category,
Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
@@ -994,16 +800,8 @@
</func>
<func>
- <name since="OTP 19.2">print(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
+ <name since="OTP 19.2" name="print" arity="5" />
<fsummary>Prints from a test case to the console.</fsummary>
- <type>
- <v>Category = atom()</v>
- <v>Importance = integer()</v>
- <v>Format = string()</v>
- <v>FormatArgs = list()</v>
- <v>Opts = [Opt]</v>
- <v>Opt = {heading,string()}</v>
- </type>
<desc><marker id="print-5"/>
<p>Prints from a test case to the console.</p>
@@ -1021,15 +819,9 @@
</func>
<func>
- <name since="OTP R14B">reload_config(Required) -&gt; ValueOrElement | {error, Reason}</name>
+ <name since="OTP R14B" name="reload_config" arity="1" />
<fsummary>Reloads configuration file containing specified configuration
key.</fsummary>
- <type>
- <v>Required = KeyOrName | {KeyOrName, SubKey} | {KeyOrName, SubKey, SubKey}</v>
- <v>KeyOrName = atom()</v>
- <v>SubKey = atom()</v>
- <v>ValueOrElement = term()</v>
- </type>
<desc><marker id="reload_config-1"/>
<p>Reloads configuration file containing specified configuration key.</p>
@@ -1044,15 +836,9 @@
</func>
<func>
- <name since="OTP 20.2">remaining_test_procs() -&gt; {TestProcs,SharedGL,OtherGLs}</name>
+ <name since="OTP 20.2" name="remaining_test_procs" arity="0" />
<fsummary>>This function will return the identity of test- and group
leader processes that are still running at the time of this call.</fsummary>
- <type>
- <v>TestProcs = [{pid(),GL}]</v>
- <v>GL = pid()</v>
- <v>SharedGL = pid()</v>
- <v>OtherGLs = [pid()]</v>
- </type>
<desc><marker id="remaining_test_procs-0"/>
<p>This function will return the identity of test- and group
leader processes that are still running at the time of this call.
@@ -1080,15 +866,10 @@
</func>
<func>
- <name since="OTP R14B">remove_config(Callback, Config) -&gt; ok</name>
+ <name since="OTP R14B" name="remove_config" arity="2" />
<fsummary>Removes configuration variables (together with
their aliases) that were loaded with specified callback module and
configuration string.</fsummary>
- <type>
- <v>Callback = atom()</v>
- <v>Config = string()</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="remove_config-2"/>
<p>Removes configuration variables (together with their aliases)
that were loaded with specified callback module and configuration
@@ -1097,14 +878,8 @@
</func>
<func>
- <name since="">require(Required) -&gt; ok | {error, Reason}</name>
+ <name since="" name="require" arity="1" />
<fsummary>Checks if the required configuration is available.</fsummary>
- <type>
- <v>Required = Key | {Key, SubKeys} | {Key, SubKey, SubKeys}</v>
- <v>Key = atom()</v>
- <v>SubKeys = SubKey | [SubKey]</v>
- <v>SubKey = atom()</v>
- </type>
<desc><marker id="require-1"/>
<p>Checks if the required configuration is available. Arbitrarily
deep tuples can be specified as <c>Required</c>. Only the last
@@ -1151,15 +926,9 @@
</func>
<func>
- <name since="">require(Name, Required) -&gt; ok | {error, Reason}</name>
+ <name since="" name="require" arity="2" />
<fsummary>Checks if the required configuration is available and gives
it a name.</fsummary>
- <type>
- <v>Name = atom()</v>
- <v>Required = Key | {Key, SubKey} | {Key, SubKey, SubKey}</v>
- <v>SubKey = Key</v>
- <v>Key = atom()</v>
- </type>
<desc><marker id="require-2"/>
<p>Checks if the required configuration is available and gives it a
name. The semantics for <c>Required</c> is the same as in
@@ -1210,12 +979,9 @@
</func>
<func>
- <name since="">run(TestDirs) -&gt; Result</name>
+ <name since="" name="run" arity="1" />
<fsummary>Runs all test cases in all suites in the specified
directories.</fsummary>
- <type>
- <v>TestDirs = TestDir | [TestDir]</v>
- </type>
<desc><marker id="run-1"/>
<p>Runs all test cases in all suites in the specified directories.</p>
@@ -1224,7 +990,7 @@
</func>
<func>
- <name since="">run(TestDir, Suite) -&gt; Result</name>
+ <name since="" name="run" arity="2" />
<fsummary>Runs all test cases in the specified suite.</fsummary>
<desc><marker id="run-2"/>
<p>Runs all test cases in the specified suite.</p>
@@ -1234,14 +1000,8 @@
</func>
<func>
- <name since="">run(TestDir, Suite, Cases) -&gt; Result</name>
+ <name since="" name="run" arity="3" />
<fsummary>Runs the specified test cases.</fsummary>
- <type>
- <v>TestDir = string()</v>
- <v>Suite = atom()</v>
- <v>Cases = atom() | [atom()]</v>
- <v>Result = [TestResult] | {error, Reason}</v>
- </type>
<desc><marker id="run-3"/>
<p>Runs the specified test cases.</p>
@@ -1256,59 +1016,9 @@
</func>
<func>
- <name since="">run_test(Opts) -&gt; Result</name>
+ <name since="" name="run_test" arity="1" />
<fsummary>Runs tests as specified by the combination of options in
Opts.</fsummary>
- <type>
- <v>Opts = [OptTuples]</v>
- <v>OptTuples = {dir, TestDirs} | {suite, Suites} | {group, Groups} | {testcase, Cases} | {spec, TestSpecs} | {join_specs, Bool} | {label, Label} | {config, CfgFiles} | {userconfig, UserConfig} | {allow_user_terms, Bool} | {logdir, LogDir} | {silent_connections, Conns} | {stylesheet, CSSFile} | {cover, CoverSpecFile} | {cover_stop, Bool} | {step, StepOpts} | {event_handler, EventHandlers} | {include, InclDirs} | {auto_compile, Bool} | {abort_if_missing_suites, Bool} | {create_priv_dir, CreatePrivDir} | {multiply_timetraps, M} | {scale_timetraps, Bool} | {repeat, N} | {duration, DurTime} | {until, StopTime} | {force_stop, ForceStop} | {decrypt, DecryptKeyOrFile} | {refresh_logs, LogDir} | {logopts, LogOpts} | {verbosity, VLevels} | {basic_html, Bool} | {esc_chars, Bool} | {keep_logs,KeepSpec} | {ct_hooks, CTHs} | {enable_builtin_hooks, Bool} | {release_shell, Bool}</v>
- <v>TestDirs = [string()] | string()</v>
- <v>Suites = [string()] | [atom()] | string() | atom()</v>
- <v>Cases = [atom()] | atom()</v>
- <v>Groups = GroupNameOrPath | [GroupNameOrPath]</v>
- <v>GroupNameOrPath = [atom()] | atom() | all</v>
- <v>TestSpecs = [string()] | string()</v>
- <v>Label = string() | atom()</v>
- <v>CfgFiles = [string()] | string()</v>
- <v>UserConfig = [{CallbackMod, CfgStrings}] | {CallbackMod, CfgStrings}</v>
- <v>CallbackMod = atom()</v>
- <v>CfgStrings = [string()] | string()</v>
- <v>LogDir = string()</v>
- <v>Conns = all | [atom()]</v>
- <v>CSSFile = string()</v>
- <v>CoverSpecFile = string()</v>
- <v>StepOpts = [StepOpt] | []</v>
- <v>StepOpt = config | keep_inactive</v>
- <v>EventHandlers = EH | [EH]</v>
- <v>EH = atom() | {atom(), InitArgs} | {[atom()], InitArgs}</v>
- <v>InitArgs = [term()]</v>
- <v>InclDirs = [string()] | string()</v>
- <v>CreatePrivDir = auto_per_run | auto_per_tc | manual_per_tc</v>
- <v>M = integer()</v>
- <v>N = integer()</v>
- <v>DurTime = string(HHMMSS)</v>
- <v>StopTime = string(YYMoMoDDHHMMSS) | string(HHMMSS)</v>
- <v>ForceStop = skip_rest | Bool</v>
- <v>DecryptKeyOrFile = {key, DecryptKey} | {file, DecryptFile}</v>
- <v>DecryptKey = string()</v>
- <v>DecryptFile = string()</v>
- <v>LogOpts = [LogOpt]</v>
- <v>LogOpt = no_nl | no_src</v>
- <v>VLevels = VLevel | [{Category, VLevel}]</v>
- <v>VLevel = integer()</v>
- <v>Category = atom()</v>
- <v>KeepSpec = all | pos_integer()</v>
- <v>CTHs = [CTHModule | {CTHModule, CTHInitArgs}]</v>
- <v>CTHModule = atom()</v>
- <v>CTHInitArgs = term()</v>
- <v>Result = {Ok, Failed, {UserSkipped, AutoSkipped}} | TestRunnerPid | {error, Reason}</v>
- <v>Ok = integer()</v>
- <v>Failed = integer()</v>
- <v>UserSkipped = integer()</v>
- <v>AutoSkipped = integer()</v>
- <v>TestRunnerPid = pid()</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="run_test-1"/>
<p>Runs tests as specified by the combination of options in
<c>Opts</c>. The options are the same as those used with program
@@ -1328,17 +1038,8 @@
</func>
<func>
- <name since="">run_testspec(TestSpec) -&gt; Result</name>
+ <name since="" name="run_testspec" arity="1" />
<fsummary>Runs a test specified by TestSpec.</fsummary>
- <type>
- <v>TestSpec = [term()]</v>
- <v>Result = {Ok, Failed, {UserSkipped, AutoSkipped}} | {error, Reason}</v>
- <v>Ok = integer()</v>
- <v>Failed = integer()</v>
- <v>UserSkipped = integer()</v>
- <v>AutoSkipped = integer()</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="run_testspec-1"/>
<p>Runs a test specified by <c>TestSpec</c>. The same terms are used
as in test specification files.</p>
@@ -1348,12 +1049,8 @@
</func>
<func>
- <name since="OTP 19.1">set_verbosity(Category, Level) -&gt; ok</name>
+ <name since="OTP 19.1" name="set_verbosity" arity="2" />
<fsummary>Set the verbosity level for a logging category.</fsummary>
- <type>
- <v>Category = default | atom()</v>
- <v>Level = integer()</v>
- </type>
<desc><marker id="set_verbosity-2"/>
<p>Use this function to set, or modify, the verbosity level for a logging
category. See the <seeguide marker="write_test_chapter#logging">
@@ -1363,16 +1060,9 @@
</func>
<func>
- <name since="OTP R14B">sleep(Time) -&gt; ok</name>
+ <name since="OTP R14B" name="sleep" arity="1" />
<fsummary>This function, similar to timer:sleep/1, suspends the
test case for a specified time.</fsummary>
- <type>
- <v>Time = {hours, Hours} | {minutes, Mins} | {seconds, Secs} | Millisecs | infinity</v>
- <v>Hours = integer()</v>
- <v>Mins = integer()</v>
- <v>Secs = integer()</v>
- <v>Millisecs = integer() | float()</v>
- </type>
<desc><marker id="sleep-1"/>
<p>This function, similar to <c>timer:sleep/1</c> in STDLIB,
suspends the test case for a specified time.
@@ -1385,7 +1075,7 @@
</func>
<func>
- <name since="">start_interactive() -&gt; ok</name>
+ <name since="" name="start_interactive" arity="0" />
<fsummary>Starts <c>Common Test</c> in interactive mode.</fsummary>
<desc><marker id="start_interactive-0"/>
<p>Starts <c>Common Test</c> in interactive mode.</p>
@@ -1413,11 +1103,8 @@
</func>
<func>
- <name since="">step(TestDir, Suite, Case) -&gt; Result</name>
+ <name since="" name="step" arity="3" />
<fsummary>Steps through a test case with the debugger.</fsummary>
- <type>
- <v>Case = atom()</v>
- </type>
<desc><marker id="step-3"/>
<p>Steps through a test case with the debugger.</p>
@@ -1426,13 +1113,8 @@
</func>
<func>
- <name since="">step(TestDir, Suite, Case, Opts) -&gt; Result</name>
+ <name since="" name="step" arity="4" />
<fsummary>Steps through a test case with the debugger.</fsummary>
- <type>
- <v>Case = atom()</v>
- <v>Opts = [Opt] | []</v>
- <v>Opt = config | keep_inactive</v>
- </type>
<desc><marker id="step-4"/>
<p>Steps through a test case with the debugger. If option
<c>config</c> has been specified, breakpoints are also set on
@@ -1443,7 +1125,7 @@
</func>
<func>
- <name since="">stop_interactive() -&gt; ok</name>
+ <name since="" name="stop_interactive" arity="0" />
<fsummary>Exits the interactive mode.</fsummary>
<desc><marker id="stop_interactive-0"/>
<p>Exits the interactive mode.</p>
@@ -1455,13 +1137,9 @@
</func>
<func>
- <name since="OTP R15B02">sync_notify(Name, Data) -&gt; ok</name>
+ <name since="OTP R15B02" name="sync_notify" arity="2" />
<fsummary>Sends a synchronous notification of type Name with Data to
the <c>Common Test</c> event manager.</fsummary>
- <type>
- <v>Name = atom()</v>
- <v>Data = term()</v>
- </type>
<desc><marker id="sync_notify-2"/>
<p>Sends a synchronous notification of type <c>Name</c> with
<c>Data</c> to the <c>Common Test</c> event manager. This can later be
@@ -1474,33 +1152,16 @@
</func>
<func>
- <name since="">testcases(TestDir, Suite) -&gt; Testcases | {error, Reason}</name>
+ <name since="" name="testcases" arity="2" />
<fsummary>Returns all test cases in the specified suite.</fsummary>
- <type>
- <v>TestDir = string()</v>
- <v>Suite = atom()</v>
- <v>Testcases = list()</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="testcases-2"/>
<p>Returns all test cases in the specified suite.</p>
</desc>
</func>
<func>
- <name since="OTP R14B">timetrap(Time) -&gt; ok</name>
+ <name since="OTP R14B" name="timetrap" arity="1" />
<fsummary>Sets a new timetrap for the running test case.</fsummary>
- <type>
- <v>Time = {hours, Hours} | {minutes, Mins} | {seconds, Secs} | Millisecs | infinity | Func</v>
- <v>Hours = integer()</v>
- <v>Mins = integer()</v>
- <v>Secs = integer()</v>
- <v>Millisecs = integer()</v>
- <v>Func = {M, F, A} | function()</v>
- <v>M = atom()</v>
- <v>F = atom()</v>
- <v>A = list()</v>
- </type>
<desc><marker id="timetrap-1"/>
<p>Sets a new timetrap for the running test case.</p>
@@ -1512,15 +1173,9 @@
</func>
<func>
- <name since="">userdata(TestDir, Suite) -&gt; SuiteUserData | {error, Reason}</name>
+ <name since="" name="userdata" arity="2" />
<fsummary>Returns any data specified with tag userdata in the list of
tuples returned from Suite:suite/0.</fsummary>
- <type>
- <v>TestDir = string()</v>
- <v>Suite = atom()</v>
- <v>SuiteUserData = [term()]</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="userdata-2"/>
<p>Returns any data specified with tag <c>userdata</c> in the list
of tuples returned from
@@ -1529,17 +1184,9 @@
</func>
<func>
- <name since="">userdata(TestDir, Suite, Case::GroupOrCase) -&gt; TCUserData | {error, Reason}</name>
+ <name since="" name="userdata" arity="3" />
<fsummary>Returns any data specified with tag userdata in the list of
tuples returned from Suite:group(GroupName) or Suite:Case().</fsummary>
- <type>
- <v>TestDir = string()</v>
- <v>Suite = atom()</v>
- <v>GroupOrCase = {group, GroupName} | atom()</v>
- <v>GroupName = atom()</v>
- <v>TCUserData = [term()]</v>
- <v>Reason = term()</v>
- </type>
<desc><marker id="userdata-3"/>
<p>Returns any data specified with tag <c>userdata</c> in the list
of tuples returned from <c>Suite:group(GroupName)</c> or
diff --git a/lib/common_test/doc/src/ct_hooks_chapter.xml b/lib/common_test/doc/src/ct_hooks_chapter.xml
index aab8671f9e..66758e7e4d 100644
--- a/lib/common_test/doc/src/ct_hooks_chapter.xml
+++ b/lib/common_test/doc/src/ct_hooks_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2011</year><year>2021</year>
+ <year>2011</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -99,6 +99,10 @@
<item><c>{ct_hooks,[{my_cth_module,[{debug,true}],500}]}</c></item>
</list>
+ <p>Note that regardless of how you install a CTH, its BEAM file
+ must be available in the code path when Common Test runs.
+ <c>ct_run</c> accepts the <c>-pa</c> command line option.</p>
+
<section>
<title>Overriding CTHs</title>
<p>By default, each installation of a CTH causes a new instance of it
@@ -373,118 +377,93 @@
<section>
<marker id="example"/>
- <title>Example CTH</title>
- <p>The following CTH logs information about a test run into a format
- parseable by <seemfa marker="kernel:file#consult/1">file:consult/1</seemfa>
- (in Kernel):
- </p>
- <code>
- %%% Common Test Example Common Test Hook module.
- -module(example_cth).
-
- %% Callbacks
- -export([id/1]).
- -export([init/2]).
-
- -export([pre_init_per_suite/3]).
- -export([post_init_per_suite/4]).
- -export([pre_end_per_suite/3]).
- -export([post_end_per_suite/4]).
-
- -export([pre_init_per_group/4]).
- -export([post_init_per_group/5]).
- -export([pre_end_per_group/4]).
- -export([post_end_per_group/5]).
-
- -export([pre_init_per_testcase/4]).
- -export([post_init_per_testcase/5]).
- -export([pre_end_per_testcase/4]).
- -export([post_end_per_testcase/5]).
-
- -export([on_tc_fail/4]).
- -export([on_tc_skip/4]).
-
- -export([terminate/1]).
-
- -record(state, { file_handle, total, suite_total, ts, tcs, data }).
-
- %% Return a unique id for this CTH.
- id(Opts) ->
- proplists:get_value(filename, Opts, "/tmp/file.log").
-
- %% Always called before any other callback function. Use this to initiate
- %% any common state.
- init(Id, Opts) ->
- {ok,D} = file:open(Id,[write]),
- {ok, #state{ file_handle = D, total = 0, data = [] }}.
-
- %% Called before init_per_suite is called.
- pre_init_per_suite(Suite,Config,State) ->
- {Config, State#state{ suite_total = 0, tcs = [] }}.
-
- %% Called after init_per_suite.
- post_init_per_suite(Suite,Config,Return,State) ->
- {Return, State}.
-
- %% Called before end_per_suite.
- pre_end_per_suite(Suite,Config,State) ->
- {Config, State}.
-
- %% Called after end_per_suite.
- post_end_per_suite(Suite,Config,Return,State) ->
- Data = {suites, Suite, State#state.suite_total, lists:reverse(State#state.tcs)},
- {Return, State#state{ data = [Data | State#state.data] ,
- total = State#state.total + State#state.suite_total } }.
-
- %% Called before each init_per_group.
- pre_init_per_group(Suite,Group,Config,State) ->
- {Config, State}.
-
- %% Called after each init_per_group.
- post_init_per_group(Suite,Group,Config,Return,State) ->
- {Return, State}.
-
- %% Called before each end_per_group.
- pre_end_per_group(Suite,Group,Config,State) ->
- {Config, State}.
-
- %% Called after each end_per_group.
- post_end_per_group(Suite,Group,Config,Return,State) ->
- {Return, State}.
-
- %% Called before each init_per_testcase.
- pre_init_per_testcase(Suite,TC,Config,State) ->
- {Config, State#state{ ts = now(), total = State#state.suite_total + 1 } }.
-
- %% Called after each init_per_testcase (immediately before the test case).
- post_init_per_testcase(Suite,TC,Config,Return,State) ->
- {Return, State}
-
-%% Called before each end_per_testcase (immediately after the test case).
- pre_end_per_testcase(Suite,TC,Config,State) ->
- {Config, State}.
-
- %% Called after each end_per_testcase.
- post_end_per_testcase(Suite,TC,Config,Return,State) ->
- TCInfo = {testcase, Suite, TC, Return, timer:now_diff(now(), State#state.ts)},
- {Return, State#state{ ts = undefined, tcs = [TCInfo | State#state.tcs] } }.
-
- %% Called after post_init_per_suite, post_end_per_suite, post_init_per_group,
- %% post_end_per_group and post_end_per_testcase if the suite, group or test case failed.
- on_tc_fail(Suite, TC, Reason, State) ->
- State.
-
- %% Called when a test case is skipped by either user action
- %% or due to an init function failing.
- on_tc_skip(Suite, TC, Reason, State) ->
- State.
-
- %% Called when the scope of the CTH is done
- terminate(State) ->
- io:format(State#state.file_handle, "~p.~n",
- [{test_run, State#state.total, State#state.data}]),
- file:close(State#state.file_handle),
- ok.</code>
+ <title>Example CTH</title>
+ <p>The following CTH logs information about a test run into a format
+ parseable by <seemfa marker="kernel:file#consult/1">file:consult/1</seemfa>
+ (in Kernel):
+ </p>
+ <code>
+%%% Common Test Example Common Test Hook module.
+%%%
+%%% To use this hook, on the command line:
+%%% ct_run -suite example_SUITE -pa . -ct_hooks example_cth
+%%%
+%%% Note `-pa .`: the hook beam file must be in the code path when installing.
+-module(example_cth).
+
+%% Mandatory Callbacks
+-export([init/2]).
+
+%% Optional Callbacks
+-export([id/1]).
+
+-export([pre_init_per_suite/3]).
+-export([post_end_per_suite/4]).
+
+-export([pre_init_per_testcase/4]).
+-export([post_end_per_testcase/5]).
+
+-export([on_tc_skip/4]).
+
+-export([terminate/1]).
+
+%% This hook state is threaded through all the callbacks.
+-record(state, {filename, total, suite_total, ts, tcs, data, skipped}).
+%% This example hook prints its results to a file, see terminate/1.
+-record(test_run, {total, skipped, suites}).
+
+%% Return a unique id for this CTH.
+%% Using the filename means the hook can be used with different
+%% log files to separate timing data within the same test run.
+%% See Installing a CTH for more information.
+id(Opts) ->
+ %% the path is relative to the test run directory
+ proplists:get_value(filename, Opts, "example_cth.log").
+
+%% Always called before any other callback function. Use this to initiate
+%% any common state.
+init(Id, _Opts) ->
+ {ok, #state{filename = Id, total = 0, data = []}}.
+
+%% Called before init_per_suite is called.
+pre_init_per_suite(_Suite,Config,State) ->
+ {Config, State#state{suite_total = 0, tcs = []}}.
+
+%% Called after end_per_suite.
+post_end_per_suite(Suite,_Config,Return,State) ->
+ Data = {suites, Suite, State#state.suite_total,
+ lists:reverse(State#state.tcs)},
+ {Return, State#state{data = [Data | State#state.data],
+ total = State#state.total + State#state.suite_total}}.
+
+%% Called before each init_per_testcase.
+pre_init_per_testcase(_Suite,_TC,Config,State) ->
+ Now = erlang:monotonic_time(microsecond),
+ {Config, State#state{ts = Now, suite_total = State#state.suite_total + 1}}.
+
+%% Called after each end_per_testcase.
+post_end_per_testcase(Suite,TC,_Config,Return,State) ->
+ Now = erlang:monotonic_time(microsecond),
+ TCInfo = {testcase, Suite, TC, Return, Now - State#state.ts},
+ {Return, State#state{ts = undefined, tcs = [TCInfo | State#state.tcs]}}.
+
+%% Called when a test case is skipped by either user action
+%% or due to an init function failing.
+on_tc_skip(_Suite, _TC, _Reason, State) ->
+ State#state{skipped = State#state.skipped + 1}.
+
+%% Called when the scope of the CTH is done.
+terminate(State) ->
+ %% use append to avoid data loss if the path is reused
+ {ok, File} = file:open(State#state.filename, [write, append]),
+ io:format(File, "~p.~n", [results(State)]),
+ file:close(File),
+ ok.
+
+results(State) ->
+ #state{skipped = Skipped, data = Data, total = Total} = State,
+ #test_run{total = Total, skipped = Skipped, suites = lists:reverse(Data)}.
+ </code>
</section>
<section>
diff --git a/lib/common_test/doc/src/ct_suite.xml b/lib/common_test/doc/src/ct_suite.xml
index 8740a3ff79..cc926fbcbb 100644
--- a/lib/common_test/doc/src/ct_suite.xml
+++ b/lib/common_test/doc/src/ct_suite.xml
@@ -85,7 +85,9 @@
<fsummary>Returns the list of all test case groups and test cases
in the module.</fsummary>
<type>
- <v><seetype marker="#ct_test_def">ct_test_def()</seetype> = TestCase | {group, GroupName} | {group, GroupName, Properties} | {group, GroupName, Properties, SubGroups}</v>
+ <v><seetype marker="#ct_test_def">ct_test_def()</seetype> = TestCase |
+ {group, GroupName} | {group, GroupName, Properties} | {group, GroupName,
+ Properties, SubGroups} | {testcase, TestCase, TestCaseRepeatType}</v>
<v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
<v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
<v>Properties = [parallel | sequence | Shuffle | {RepeatType, N}] | default</v>
@@ -93,6 +95,7 @@
<v>Shuffle = shuffle | {shuffle, Seed}</v>
<v>Seed = {integer(), integer(), integer()}</v>
<v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>TestCaseRepeatType = [{repeat, N} | {repeat_until_ok, N} | {repeat_until_fail, N}]</v>
<v>N = integer() | forever</v>
<v>Reason = term()</v>
</type>
@@ -135,11 +138,12 @@
<v><seetype marker="#ct_group_def">ct_group_def()</seetype> = {GroupName, Properties, GroupsAndTestCases}</v>
<v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
<v>Properties = [parallel | sequence | Shuffle | {RepeatType, N}]</v>
- <v>GroupsAndTestCases = [Group | {group, GroupName} | TestCase]</v>
+ <v>GroupsAndTestCases = [Group | {group, GroupName} | TestCase | {testcase, TestCase, TestCaseRepeatType}]</v>
<v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
<v>Shuffle = shuffle | {shuffle, Seed}</v>
<v>Seed = {integer(), integer(), integer()}</v>
<v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>TestCaseRepeatType = [{repeat, N} | {repeat_until_ok, N} | {repeat_until_fail, N}]</v>
<v>N = integer() | forever</v>
</type>
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml
index f9416e1bee..ea33b9d8f9 100644
--- a/lib/common_test/doc/src/run_test_chapter.xml
+++ b/lib/common_test/doc/src/run_test_chapter.xml
@@ -763,7 +763,7 @@
finally function <c>end_per_group</c>. Also, if particular
test cases in a group are specified, <c>init_per_group</c>
and <c>end_per_group</c>, for the group in question, are
- called. If a group defined (in <c>Suite:group/0</c>) as
+ called. If a group defined (in <c>Suite:groups/0</c>) as
a subgroup of another group, is specified (or if particular test
cases of a subgroup are), <c>Common Test</c> calls the configuration
functions for the top-level groups and for the subgroup
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index 8f7c7e8177..99571bbdae 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -515,14 +515,14 @@
<c>{group,GroupName,Properties,SubGroups}</c>
Where, <c>SubGroups</c> is a list of tuples, <c>{GroupName,Properties}</c> or
<c>{GroupName,Properties,SubGroups}</c> representing the subgroups.
- Any subgroups defined in <c>group/0</c> for a group, that are not specified
+ Any subgroups defined in <c>groups/0</c> for a group, that are not specified
in the <c>SubGroups</c> list, executes with their predefined
properties.</p>
<p><em>Example:</em></p>
<pre>
- groups() -> {tests1, [], [{tests2, [], [t2a,t2b]},
- {tests3, [], [t31,t3b]}]}.</pre>
+ groups() -> [{tests1, [], [{tests2, [], [t2a,t2b]},
+ {tests3, [], [t31,t3b]}]}].</pre>
<p>To execute group <c>tests1</c> twice with different properties for <c>tests2</c>
each time:</p>
<pre>
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 02c14651d9..012bb5c740 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -66,10 +66,12 @@
%% For ct_gen_conn
-export_type([config_key/0,
target_name/0,
- key_or_name/0]).
+ key_or_name/0,
+ handle/0]).
%% For cth_conn_log
--export_type([conn_log_options/0,
+-export_type([conn_log_option/0,
+ conn_log_options/0,
conn_log_type/0,
conn_log_mod/0]).
@@ -79,6 +81,7 @@
-type config_key() :: atom(). % Config key which exists in a config file
-type target_name() :: atom().% Name associated to a config_key() though 'require'
-type key_or_name() :: config_key() | target_name().
+-type handle() :: pid().
%% Types used when logging connections with the 'cth_conn_log' hook
-type conn_log_options() :: [conn_log_option()].
@@ -89,34 +92,172 @@
%%----------------------------------------------------------------------
+-spec install(Opts) -> ok | {error, Reason}
+ when Opts :: [Opt],
+ Opt :: {config, ConfigFiles} | {event_handler, Modules} | {decrypt, KeyOrFile},
+ ConfigFiles :: [ConfigFile],
+ ConfigFile :: string(),
+ Modules :: [atom()],
+ KeyOrFile :: {key, Key} | {file, KeyFile},
+ Key :: string(),
+ KeyFile :: string(),
+ Reason :: term().
install(Opts) ->
ct_run:install(Opts).
+-spec run(TestDir, Suite, Cases) -> Result
+ when TestDir :: string(),
+ Suite :: atom(),
+ Cases :: atom() | [atom()],
+ Result :: [TestResult] | {error, Reason},
+ TestResult :: term(),
+ Reason :: term().
run(TestDir,Suite,Cases) ->
ct_run:run(TestDir,Suite,Cases).
+-spec run(TestDir, Suite) -> Result
+ when TestDir :: string(),
+ Suite :: atom(),
+ Result :: [TestResult] | {error, Reason},
+ TestResult :: term(),
+ Reason :: term().
run(TestDir,Suite) ->
ct_run:run(TestDir,Suite).
+-spec run(TestDirs) -> Result
+ when TestDirs :: TestDir | [TestDir],
+ TestDir :: string(),
+ Result :: [TestResult] | {error, Reason},
+ TestResult :: term(),
+ Reason :: term().
run(TestDirs) ->
ct_run:run(TestDirs).
+-spec run_test(Opts) -> Result
+ when Opts :: [OptTuples],
+ OptTuples :: {dir, TestDirs}
+ | {suite, Suites}
+ | {group, Groups}
+ | {testcase, Cases}
+ | {spec, TestSpecs}
+ | {join_specs, boolean()}
+ | {label, Label}
+ | {config, CfgFiles}
+ | {userconfig, UserConfig}
+ | {allow_user_terms, boolean()}
+ | {logdir, LogDir}
+ | {silent_connections, Conns}
+ | {stylesheet, CSSFile}
+ | {cover, CoverSpecFile}
+ | {cover_stop, boolean()}
+ | {step, StepOpts}
+ | {event_handler, EventHandlers}
+ | {include, InclDirs}
+ | {auto_compile, boolean()}
+ | {abort_if_missing_suites, boolean()}
+ | {create_priv_dir, CreatePrivDir}
+ | {multiply_timetraps, M}
+ | {scale_timetraps, boolean()}
+ | {repeat, N}
+ | {duration, DurTime}
+ | {until, StopTime}
+ | {force_stop, ForceStop}
+ | {decrypt, DecryptKeyOrFile}
+ | {refresh_logs, LogDir}
+ | {logopts, LogOpts}
+ | {verbosity, VLevels}
+ | {basic_html, boolean()}
+ | {esc_chars, boolean()}
+ | {keep_logs,KeepSpec}
+ | {ct_hooks, CTHs}
+ | {enable_builtin_hooks, boolean()}
+ | {release_shell, boolean()},
+ TestDirs :: [string()] | string(),
+ Suites :: [string()] | [atom()] | string() | atom(),
+ Cases :: [atom()] | atom(),
+ Groups :: GroupNameOrPath | [GroupNameOrPath],
+ GroupNameOrPath :: [atom()] | atom() | all,
+ TestSpecs :: [string()] | string(),
+ Label :: string() | atom(),
+ CfgFiles :: [string()] | string(),
+ UserConfig :: [{CallbackMod, CfgStrings}] | {CallbackMod, CfgStrings},
+ CallbackMod :: atom(),
+ CfgStrings :: [string()] | string(),
+ LogDir :: string(),
+ Conns :: all | [atom()],
+ CSSFile :: string(),
+ CoverSpecFile :: string(),
+ StepOpts :: [StepOpt],
+ StepOpt :: config | keep_inactive,
+ EventHandlers :: EH | [EH],
+ EH :: atom() | {atom(), InitArgs} | {[atom()], InitArgs},
+ InitArgs :: [term()],
+ InclDirs :: [string()] | string(),
+ CreatePrivDir :: auto_per_run | auto_per_tc | manual_per_tc,
+ M :: integer(),
+ N :: integer(),
+ DurTime :: HHMMSS,
+ HHMMSS :: string(),
+ StopTime :: YYMoMoDDHHMMSS | HHMMSS,
+ YYMoMoDDHHMMSS :: string(),
+ ForceStop :: skip_rest | boolean(),
+ DecryptKeyOrFile :: {key, DecryptKey} | {file, DecryptFile},
+ DecryptKey :: string(),
+ DecryptFile :: string(),
+ LogOpts :: [LogOpt],
+ LogOpt :: no_nl | no_src,
+ VLevels :: VLevel | [{Category, VLevel}],
+ VLevel :: integer(),
+ Category :: atom(),
+ KeepSpec :: all | pos_integer(),
+ CTHs :: [CTHModule | {CTHModule, CTHInitArgs}],
+ CTHModule :: atom(),
+ CTHInitArgs :: term(),
+ Result :: {Ok, Failed, {UserSkipped, AutoSkipped}} | TestRunnerPid | {error, Reason},
+ Ok :: integer(),
+ Failed :: integer(),
+ UserSkipped :: integer(),
+ AutoSkipped :: integer(),
+ TestRunnerPid :: pid(),
+ Reason :: term().
run_test(Opts) ->
ct_run:run_test(Opts).
+-spec run_testspec(TestSpec) -> Result
+ when TestSpec :: [term()],
+ Result :: {Ok, Failed, {UserSkipped, AutoSkipped}} | {error, Reason},
+ Ok :: integer(),
+ Failed :: integer(),
+ UserSkipped :: integer(),
+ AutoSkipped :: integer(),
+ Reason :: term().
run_testspec(TestSpec) ->
ct_run:run_testspec(TestSpec).
+-spec step(TestDir, Suite, Case) -> Result
+ when TestDir :: string(),
+ Suite :: atom(),
+ Case :: atom(),
+ Result :: term().
step(TestDir,Suite,Case) ->
ct_run:step(TestDir,Suite,Case).
+-spec step(TestDir, Suite, Case, Opts) -> Result
+ when TestDir :: string(),
+ Suite :: atom(),
+ Case :: atom(),
+ Opts :: [Opt],
+ Opt :: config | keep_inactive,
+ Result :: term().
step(TestDir,Suite,Case,Opts) ->
ct_run:step(TestDir,Suite,Case,Opts).
+-spec start_interactive() -> ok.
start_interactive() ->
_ = ct_util:start(interactive),
ok.
+-spec stop_interactive() -> ok.
stop_interactive() ->
ct_util:stop(normal),
ok.
@@ -125,24 +266,65 @@ stop_interactive() ->
%%% MISC INTERFACE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec require(Required) -> ok | {error, Reason}
+ when Required :: Key | {Key, SubKeys} | {Key, SubKey, SubKeys},
+ Key :: atom(),
+ SubKeys :: SubKey | [SubKey],
+ SubKey :: atom(),
+ Reason :: term().
require(Required) ->
ct_config:require(Required).
+-spec require(Name, Required) -> ok | {error, Reason}
+ when Name :: atom(),
+ Required :: Key | {Key, SubKey} | {Key, SubKey, SubKey},
+ SubKey :: Key,
+ Key :: atom(),
+ Reason :: term().
require(Name,Required) ->
ct_config:require(Name,Required).
+-spec get_config(Required) -> Value
+ when Required :: KeyOrName | {KeyOrName, SubKey} | {KeyOrName, SubKey, SubKey},
+ KeyOrName :: atom(),
+ SubKey :: atom(),
+ Value :: term().
get_config(Required) ->
ct_config:get_config(Required,undefined,[]).
+-spec get_config(Required, Default) -> Value
+ when Required :: KeyOrName | {KeyOrName, SubKey} | {KeyOrName, SubKey, SubKey},
+ KeyOrName :: atom(),
+ SubKey :: atom(),
+ Default :: term(),
+ Value :: term().
get_config(Required,Default) ->
ct_config:get_config(Required,Default,[]).
+-spec get_config(Required, Default, Opts) -> ValueOrElement
+ when Required :: KeyOrName | {KeyOrName, SubKey} | {KeyOrName, SubKey, SubKey},
+ KeyOrName :: atom(),
+ SubKey :: atom(),
+ Default :: term(),
+ Opts :: [Opt],
+ Opt :: element | all,
+ ValueOrElement :: term() | Default.
get_config(Required,Default,Opts) ->
ct_config:get_config(Required,Default,Opts).
+-spec reload_config(Required) -> ValueOrElement | {error, Reason}
+ when Required :: KeyOrName | {KeyOrName, SubKey} | {KeyOrName, SubKey, SubKey},
+ KeyOrName :: atom(),
+ SubKey :: atom(),
+ ValueOrElement :: term(),
+ Reason :: term().
reload_config(Required)->
ct_config:reload_config(Required).
+-spec get_testspec_terms() -> TestSpecTerms | undefined
+ when TestSpecTerms :: [{Tag, Value}],
+ Tag :: atom(),
+ Value :: [term()].
get_testspec_terms() ->
case ct_util:get_testdata(testspec) of
undefined ->
@@ -151,6 +333,12 @@ get_testspec_terms() ->
ct_testspec:testspec_rec2list(CurrSpecRec)
end.
+-spec get_testspec_terms(Tags) -> TestSpecTerms | undefined
+ when Tags :: [Tag] | Tag,
+ Tag :: atom(),
+ TestSpecTerms :: [{Tag, Value}] | {Tag, Value},
+ Value :: [{Node, term()}] | [term()],
+ Node :: atom().
get_testspec_terms(Tags) ->
case ct_util:get_testdata(testspec) of
undefined ->
@@ -171,9 +359,18 @@ escape_chars(Format, Args) ->
{error,Reason}
end.
+-spec log(Format) -> ok
+ when Format :: string().
log(Format) ->
log(default,?STD_IMPORTANCE,Format,[],[]).
+-spec log(X1, X2) -> ok
+ when X1 :: Category | Importance | Format,
+ X2 :: Format | FormatArgs,
+ Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list().
log(X1,X2) ->
{Category,Importance,Format,Args} =
if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]};
@@ -182,6 +379,16 @@ log(X1,X2) ->
end,
log(Category,Importance,Format,Args,[]).
+-spec log(X1, X2, X3) -> ok
+ when X1 :: Category | Importance,
+ X2 :: Importance | Format,
+ X3 :: Format | FormatArgs | Opts,
+ Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list(),
+ Opts :: [Opt],
+ Opt :: {heading,string()} | no_css | esc_chars.
log(X1,X2,X3) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]};
@@ -191,6 +398,17 @@ log(X1,X2,X3) ->
end,
log(Category,Importance,Format,Args,Opts).
+-spec log(X1, X2, X3, X4) -> ok
+ when X1 :: Category | Importance,
+ X2 :: Importance | Format,
+ X3 :: Format | FormatArgs,
+ X4 :: FormatArgs | Opts,
+ Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list(),
+ Opts :: [Opt],
+ Opt :: {heading,string()} | no_css | esc_chars.
log(X1,X2,X3,X4) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]};
@@ -199,12 +417,28 @@ log(X1,X2,X3,X4) ->
end,
log(Category,Importance,Format,Args,Opts).
+-spec log(Category, Importance, Format, FormatArgs, Opts) -> ok
+ when Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list(),
+ Opts :: [Opt],
+ Opt :: {heading,string()} | no_css | esc_chars.
log(Category,Importance,Format,Args,Opts) ->
ct_logs:tc_log(Category,Importance,Format,Args,Opts).
+-spec print(Format) -> ok
+ when Format :: string().
print(Format) ->
print(default,?STD_IMPORTANCE,Format,[],[]).
+-spec print(X1, X2) -> ok
+ when X1 :: Category | Importance | Format,
+ X2 :: Format | FormatArgs,
+ Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list().
print(X1,X2) ->
{Category,Importance,Format,Args} =
if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]};
@@ -213,6 +447,16 @@ print(X1,X2) ->
end,
print(Category,Importance,Format,Args,[]).
+-spec print(X1, X2, X3) -> ok
+ when X1 :: Category | Importance,
+ X2 :: Importance | Format,
+ X3 :: Format | FormatArgs | Opts,
+ Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list(),
+ Opts :: [Opt],
+ Opt :: {heading,string()}.
print(X1,X2,X3) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]};
@@ -222,6 +466,17 @@ print(X1,X2,X3) ->
end,
print(Category,Importance,Format,Args,Opts).
+-spec print(X1, X2, X3, X4) -> ok
+ when X1 :: Category | Importance,
+ X2 :: Importance | Format,
+ X3 :: Format | FormatArgs,
+ X4 :: FormatArgs | Opts,
+ Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list(),
+ Opts :: [Opt],
+ Opt :: {heading,string()}.
print(X1,X2,X3,X4) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]};
@@ -230,12 +485,28 @@ print(X1,X2,X3,X4) ->
end,
print(Category,Importance,Format,Args,Opts).
+-spec print(Category, Importance, Format, FormatArgs, Opts) -> ok
+ when Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list(),
+ Opts :: [Opt],
+ Opt :: {heading,string()}.
print(Category,Importance,Format,Args,Opts) ->
ct_logs:tc_print(Category,Importance,Format,Args,Opts).
+-spec pal(Format) -> ok
+ when Format :: string().
pal(Format) ->
pal(default,?STD_IMPORTANCE,Format,[]).
+-spec pal(X1, X2) -> ok
+ when X1 :: Category | Importance | Format,
+ X2 :: Format | FormatArgs,
+ Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list().
pal(X1,X2) ->
{Category,Importance,Format,Args} =
if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]};
@@ -244,6 +515,15 @@ pal(X1,X2) ->
end,
pal(Category,Importance,Format,Args,[]).
+-spec pal(X1, X2, X3) -> ok
+ when X1 :: Category | Importance,
+ X2 :: Importance | Format,
+ X3 :: Format | FormatArgs | Opt,
+ Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list(),
+ Opt :: {heading,string()} | no_css.
pal(X1,X2,X3) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]};
@@ -253,6 +533,17 @@ pal(X1,X2,X3) ->
end,
pal(Category,Importance,Format,Args,Opts).
+-spec pal(X1, X2, X3, X4) -> ok
+ when X1 :: Category | Importance,
+ X2 :: Importance | Format,
+ X3 :: Format | FormatArgs,
+ X4 :: FormatArgs | Opts,
+ Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list(),
+ Opts :: [Opt],
+ Opt :: {heading,string()} | no_css.
pal(X1,X2,X3,X4) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]};
@@ -261,25 +552,45 @@ pal(X1,X2,X3,X4) ->
end,
pal(Category,Importance,Format,Args,Opts).
+-spec pal(Category, Importance, Format, FormatArgs, Opts) -> ok
+ when Category :: atom() | integer() | string(),
+ Importance :: integer(),
+ Format :: string(),
+ FormatArgs :: list(),
+ Opts :: [Opt],
+ Opt :: {heading,string()} | no_css.
pal(Category,Importance,Format,Args,Opts) ->
ct_logs:tc_pal(Category,Importance,Format,Args,Opts).
+-spec set_verbosity(Category, Level) -> ok
+ when Category :: default | atom(),
+ Level :: integer().
set_verbosity(Category, Level) ->
ct_util:set_verbosity({Category,Level}).
+-spec get_verbosity(Category) -> Level | undefined
+ when Category :: default | atom(),
+ Level :: integer().
get_verbosity(Category) ->
ct_util:get_verbosity(Category).
+-spec capture_start() -> ok.
capture_start() ->
test_server:capture_start().
+-spec capture_stop() -> ok.
capture_stop() ->
test_server:capture_stop().
+-spec capture_get() -> ListOfStrings
+ when ListOfStrings :: [string()].
capture_get() ->
%% remove default log printouts (e.g. ct:log/2 printouts)
capture_get([default]).
+-spec capture_get(ExclCategories) -> ListOfStrings
+ when ExclCategories :: [atom()],
+ ListOfStrings :: [string()].
capture_get([ExclCat | ExclCategories]) ->
Strs = test_server:capture_get(),
CatsStr = [atom_to_list(ExclCat) |
@@ -296,8 +607,9 @@ capture_get([ExclCat | ExclCategories]) ->
capture_get([]) ->
test_server:capture_get().
--spec fail(term()) -> no_return().
+-spec fail(Reason) -> no_return()
+ when Reason :: term().
fail(Reason) ->
try
exit({test_case_failed,Reason})
@@ -310,8 +622,10 @@ fail(Reason) ->
erlang:raise(Class, R, Stk)
end.
--spec fail(io:format(), [term()]) -> no_return().
+-spec fail(Format, Args) -> no_return()
+ when Format :: io:format(),
+ Args :: [term()].
fail(Format, Args) ->
try io_lib:format(Format, Args) of
Str ->
@@ -330,6 +644,8 @@ fail(Format, Args) ->
exit({BadArgs,{?MODULE,fail,[Format,Args]}})
end.
+-spec comment(Comment) -> ok
+ when Comment :: term().
comment(Comment) when is_list(Comment) ->
Formatted =
case (catch io_lib:format("~ts",[Comment])) of
@@ -343,6 +659,9 @@ comment(Comment) ->
Formatted = io_lib:format("~tp",[Comment]),
send_html_comment(lists:flatten(Formatted)).
+-spec comment(Format, Args) -> ok
+ when Format :: string(),
+ Args :: list().
comment(Format, Args) when is_list(Format), is_list(Args) ->
Formatted =
case (catch io_lib:format(Format, Args)) of
@@ -358,14 +677,19 @@ send_html_comment(Comment) ->
ct_util:set_testdata({{comment,group_leader()},Html}),
test_server:comment(Html).
+-spec make_priv_dir() -> ok | {error, Reason}
+ when Reason :: term().
make_priv_dir() ->
test_server:make_priv_dir().
+-spec get_target_name(Handle) -> {ok, TargetName} | {error, Reason}
+ when Handle :: handle(),
+ TargetName :: target_name(),
+ Reason :: term().
get_target_name(Handle) ->
ct_util:get_target_name(Handle).
-spec get_progname() -> string().
-
get_progname() ->
case init:get_argument(progname) of
{ok, [[Prog]]} ->
@@ -374,12 +698,26 @@ get_progname() ->
"no_prog_name"
end.
+-spec parse_table(Data) -> {Heading, Table}
+ when Data :: [string()],
+ Heading :: tuple(),
+ Table :: [tuple()].
parse_table(Data) ->
ct_util:parse_table(Data).
+-spec listenv(Telnet) -> {ok, [Env]}
+ when Telnet :: term(),
+ Env :: {Key, Value},
+ Key :: string(),
+ Value :: string().
listenv(Telnet) ->
ct_util:listenv(Telnet).
+-spec testcases(TestDir, Suite) -> Testcases | {error, Reason}
+ when TestDir :: string(),
+ Suite :: atom(),
+ Testcases :: list(),
+ Reason :: term().
testcases(TestDir, Suite) ->
case make_and_load(TestDir, Suite) of
E = {error,_} ->
@@ -415,6 +753,11 @@ make_and_load(Dir, Suite) ->
end
end.
+-spec userdata(TestDir, Suite) -> SuiteUserData | {error, Reason}
+ when TestDir :: string(),
+ Suite :: atom(),
+ SuiteUserData :: [term()],
+ Reason :: term().
userdata(TestDir, Suite) ->
case make_and_load(TestDir, Suite) of
E = {error,_} ->
@@ -440,6 +783,13 @@ get_userdata(List, _) when is_list(List) ->
get_userdata(_BadTerm, Spec) ->
{error,list_to_atom(Spec ++ " must return a list")}.
+-spec userdata(TestDir, Suite, Case::GroupOrCase) -> TCUserData | {error, Reason}
+ when TestDir :: string(),
+ Suite :: atom(),
+ GroupOrCase :: {group, GroupName} | atom(),
+ GroupName :: atom(),
+ TCUserData :: [term()],
+ Reason :: term().
userdata(TestDir, Suite, {group,GroupName}) ->
case make_and_load(TestDir, Suite) of
E = {error,_} ->
@@ -458,6 +808,19 @@ userdata(TestDir, Suite, Case) when is_atom(Case) ->
get_userdata(Info, atom_to_list(Case)++"/0")
end.
+-spec get_status() -> TestStatus | {error, Reason} | no_tests_running
+ when TestStatus :: [StatusElem],
+ StatusElem :: {current, TestCaseInfo} | {successful, Successful} | {failed, Failed} | {skipped, Skipped} | {total, Total},
+ TestCaseInfo :: {Suite, TestCase} | [{Suite, TestCase}],
+ Suite :: atom(),
+ TestCase :: atom(),
+ Successful :: integer(),
+ Failed :: integer(),
+ Skipped :: {UserSkipped, AutoSkipped},
+ UserSkipped :: integer(),
+ AutoSkipped :: integer(),
+ Total :: integer(),
+ Reason :: term().
get_status() ->
case get_testdata(curr_tc) of
{ok,TestCase} ->
@@ -489,37 +852,87 @@ get_testdata(Key) ->
{ok,Data}
end.
+-spec abort_current_testcase(Reason) -> ok | {error, ErrorReason}
+ when Reason :: term(),
+ ErrorReason :: no_testcase_running | parallel_group.
abort_current_testcase(Reason) ->
test_server_ctrl:abort_current_testcase(Reason).
+-spec get_event_mgr_ref() -> EvMgrRef
+ when EvMgrRef :: atom().
get_event_mgr_ref() ->
?CT_EVMGR_REF.
+-spec encrypt_config_file(SrcFileName, EncryptFileName) -> ok | {error, Reason}
+ when SrcFileName :: string(),
+ EncryptFileName :: string(),
+ Reason :: term().
encrypt_config_file(SrcFileName, EncryptFileName) ->
ct_config:encrypt_config_file(SrcFileName, EncryptFileName).
+-spec encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -> ok | {error, Reason}
+ when SrcFileName :: string(),
+ EncryptFileName :: string(),
+ KeyOrFile :: {key, string()} | {file, string()},
+ Reason :: term().
encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) ->
ct_config:encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile).
+-spec decrypt_config_file(EncryptFileName, TargetFileName) -> ok | {error, Reason}
+ when EncryptFileName :: string(),
+ TargetFileName :: string(),
+ Reason :: term().
decrypt_config_file(EncryptFileName, TargetFileName) ->
ct_config:decrypt_config_file(EncryptFileName, TargetFileName).
+-spec decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -> ok | {error, Reason}
+ when EncryptFileName :: string(),
+ TargetFileName :: string(),
+ KeyOrFile :: {key, string()} | {file, string()},
+ Reason :: term().
decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) ->
ct_config:decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile).
+-spec add_config(Callback, Config) -> ok | {error, Reason}
+ when Callback :: atom(),
+ Config :: string(),
+ Reason :: term().
add_config(Callback, Config)->
ct_config:add_config(Callback, Config).
+-spec remove_config(Callback, Config) -> ok
+ when Callback :: atom(),
+ Config :: string().
remove_config(Callback, Config) ->
ct_config:remove_config(Callback, Config).
+-spec timetrap(Time) -> infinity | pid()
+ when Time :: {hours, Hours} | {minutes, Mins} | {seconds, Secs} | Millisecs | infinity | Func,
+ Hours :: integer(),
+ Mins :: integer(),
+ Secs :: integer(),
+ Millisecs :: integer(),
+ Func :: {M, F, A} | function(),
+ M :: atom(),
+ F :: atom(),
+ A :: list().
timetrap(Time) ->
test_server:timetrap_cancel(),
test_server:timetrap(Time).
+-spec get_timetrap_info() -> {Time, {Scaling,ScaleVal}}
+ when Time :: integer() | infinity,
+ Scaling :: boolean(),
+ ScaleVal :: integer().
get_timetrap_info() ->
test_server:get_timetrap_info().
+-spec sleep(Time) -> ok
+ when Time :: {hours, Hours} | {minutes, Mins} | {seconds, Secs} | Millisecs | infinity,
+ Hours :: integer(),
+ Mins :: integer(),
+ Secs :: integer(),
+ Millisecs :: integer() | float().
sleep({hours,Hs}) ->
sleep(trunc(Hs * 1000 * 60 * 60));
sleep({minutes,Ms}) ->
@@ -529,12 +942,22 @@ sleep({seconds,Ss}) ->
sleep(Time) ->
test_server:adjusted_sleep(Time).
+-spec notify(Name, Data) -> ok
+ when Name :: atom(),
+ Data :: term().
notify(Name,Data) ->
ct_event:notify(Name, Data).
+-spec sync_notify(Name, Data) -> ok
+ when Name :: atom(),
+ Data :: term().
sync_notify(Name,Data) ->
ct_event:sync_notify(Name, Data).
+-spec break(Comment) -> ok | {error, Reason}
+ when Comment :: string(),
+ Reason :: {multiple_cases_running, TestCases} | 'enable break with release_shell option',
+ TestCases :: [atom()].
break(Comment) ->
case {ct_util:get_testdata(starter),
ct_util:get_testdata(release_shell)} of
@@ -557,6 +980,10 @@ break(Comment) ->
end
end.
+-spec break(TestCase, Comment) -> ok | {error, Reason}
+ when TestCase :: atom(),
+ Comment :: string(),
+ Reason :: 'test case not running' | 'enable break with release_shell option'.
break(TestCase, Comment) ->
case {ct_util:get_testdata(starter),
ct_util:get_testdata(release_shell)} of
@@ -583,12 +1010,20 @@ break(TestCase, Comment) ->
end
end.
+-spec continue() -> ok.
continue() ->
test_server:continue().
+-spec continue(TestCase) -> ok
+ when TestCase :: atom().
continue(TestCase) ->
test_server:continue(TestCase).
+-spec remaining_test_procs() -> {TestProcs,SharedGL,OtherGLs}
+ when TestProcs :: [{pid(),GL}],
+ GL :: pid(),
+ SharedGL :: pid(),
+ OtherGLs :: [pid()].
remaining_test_procs() ->
ct_util:remaining_test_procs().
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index 765a90103c..ad01da29f6 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -443,7 +443,7 @@ get_suite_name(Mod, _) ->
%% Check that alias names are not already in use
check_for_clashes(TCInfo, [CurrGrInfo|Path], SuiteInfo) ->
ReqNames = fun(Info) -> [element(2,R) || R <- Info,
- size(R) == 3,
+ tuple_size(R) == 3,
require == element(1,R)]
end,
ExistingNames = lists:flatten([ReqNames(L) || L <- [SuiteInfo|Path]]),
diff --git a/lib/common_test/src/ct_groups.erl b/lib/common_test/src/ct_groups.erl
index ee851d5103..2c4b7a383e 100644
--- a/lib/common_test/src/ct_groups.erl
+++ b/lib/common_test/src/ct_groups.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -191,8 +191,8 @@ find(Mod, GrNames, all, [{testcase,TC,[Prop]} | Gs], Known,
%% Check if test case should be saved
find(Mod, GrNames, TCs, [TC | Gs], Known, Defs, FindAll)
when is_atom(TC) orelse
- ((size(TC) == 3) andalso (element(1,TC) == testcase)) orelse
- ((size(TC) == 2) and (element(1,TC) /= group)) ->
+ ((tuple_size(TC) == 3) andalso (element(1,TC) == testcase)) orelse
+ ((tuple_size(TC) == 2) andalso (element(1,TC) /= group)) ->
Case =
case TC of
_ when is_atom(TC) ->
@@ -333,8 +333,7 @@ modify_tc_list1(GrSpecTs, TSCs) ->
false -> []
end
end;
- (Test) when is_tuple(Test),
- (size(Test) > 2) ->
+ (Test) when tuple_size(Test) > 2 ->
[Test];
(Test={group,_}) ->
[Test];
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index 07f6cc1b8f..77a8ac5bd0 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -1,7 +1,7 @@
%%----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1023,8 +1023,8 @@ handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
SimpleXml = encode_rpc_operation(get,[Filter]),
do_send_rpc(Op, SimpleXml, Timeout, From, State).
-handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
- ssh_connection:adjust_window(CM,Ch,size(Data)),
+handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) when is_binary(Data) ->
+ ssh_connection:adjust_window(CM,Ch,byte_size(Data)),
log(State#state.connection, recv, Data),
handle_data(Data, State);
@@ -1395,8 +1395,8 @@ frame(Bin) ->
chunk(<<>>) ->
[];
-chunk(Bin) ->
- Sz = min(rand:uniform(1024), size(Bin)),
+chunk(Bin) when is_binary(Bin) ->
+ Sz = min(rand:uniform(1024), byte_size(Bin)),
<<B:Sz/binary, Rest/binary>> = Bin,
["\n#", integer_to_list(Sz), $\n, B | chunk(Rest)].
@@ -2081,7 +2081,7 @@ recv(Bin, [Head, Len | Chunks]) -> %% 1.1 chunking
%% 5 characters from the end of the buffered head, since this binary
%% has already been scanned.
recv(Bin, Head) when is_binary(Head) -> %% 1.0 framing
- frame(<<Head/binary, Bin/binary>>, max(0, size(Head) - 5)).
+ frame(<<Head/binary, Bin/binary>>, max(0, byte_size(Head) - 5)).
%% frame/2
%%
@@ -2090,8 +2090,8 @@ recv(Bin, Head) when is_binary(Head) -> %% 1.0 framing
%% is unambiguous: the high-order bit of every byte of a multi-byte
%% UTF character is 1, while the end-of-message sequence is ASCII.
-frame(Bin, Start) ->
- Sz = size(Bin),
+frame(Bin, Start) when is_binary(Bin) ->
+ Sz = byte_size(Bin),
Scope = {Start, Sz - Start},
case binary:match(Bin, pattern(), [{scope, Scope}]) of
{Len, 6} ->
@@ -2150,7 +2150,7 @@ chunk(Bin, [Sz | Chunks] = L, 0) ->
%% ... or a header.
chunk(Bin, Chunks, Len)
- when size(Bin) < 4 ->
+ when byte_size(Bin) < 4 ->
[Bin, 3 = Len | Chunks];
%% End of chunks.
@@ -2189,7 +2189,7 @@ chunk(<<"\n#", Bin:11/binary, _/binary>>, _, _) ->
{error, {"chunk-size too long", Bin}}; %% 32-bits = max 10 digits
chunk(<<"\n#", _/binary>> = Bin, Chunks, _) ->
- [Bin, size(Bin) | Chunks];
+ [Bin, byte_size(Bin) | Chunks];
chunk(Bin, Chunks, 3 = Len) ->
case drop(Bin) of
diff --git a/lib/common_test/src/ct_property_test.erl b/lib/common_test/src/ct_property_test.erl
index b94cc15374..12644dca49 100644
--- a/lib/common_test/src/ct_property_test.erl
+++ b/lib/common_test/src/ct_property_test.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -36,9 +36,6 @@
print_frequency/0
]).
-%%% Mandatory include
--include_lib("common_test/include/ct.hrl").
-
%%%================================================================
%%%
%%% API
diff --git a/lib/common_test/src/ct_release_test.erl b/lib/common_test/src/ct_release_test.erl
index da556e1481..cb7b590e9f 100644
--- a/lib/common_test/src/ct_release_test.erl
+++ b/lib/common_test/src/ct_release_test.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2014-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2014-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -142,7 +142,7 @@
-spec init(Config) -> Result when
Config :: config(),
Result :: config() | SkipOrFail,
- SkipOrFail :: {skip,Reason} | {fail,Reason}.
+ SkipOrFail :: {skip,Reason::term()} | {fail,Reason :: term()}.
%% Initialize ct_release_test.
%%
%% This function can be called from any of the
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 4ce0d562d3..df12121197 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -3276,7 +3276,7 @@ do_trace(Terms) ->
ok.
stop_trace(true) ->
- dbg:stop_clear();
+ dbg:stop();
stop_trace(false) ->
ok.
diff --git a/lib/common_test/src/ct_ssh.erl b/lib/common_test/src/ct_ssh.erl
index 79ab122452..1475b7bfd6 100644
--- a/lib/common_test/src/ct_ssh.erl
+++ b/lib/common_test/src/ct_ssh.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -738,8 +738,8 @@ do_recv_response(SSH, Chn, Data, End, Timeout) ->
debug("CLSD~n~p ~p", [SSH,Chn]),
{ok,Data};
- {ssh_cm, SSH, {data,Chn,_,NewData}} ->
- ssh_connection:adjust_window(SSH, Chn, size(NewData)),
+ {ssh_cm, SSH, {data,Chn,_,NewData}} when is_binary(NewData) ->
+ ssh_connection:adjust_window(SSH, Chn, byte_size(NewData)),
debug("RECVD~n~tp", [binary_to_list(NewData)]),
DataAcc = Data ++ binary_to_list(NewData),
if is_function(End) ->
diff --git a/lib/common_test/src/ct_suite.erl b/lib/common_test/src/ct_suite.erl
index a2d23e15ef..860efe3ae2 100644
--- a/lib/common_test/src/ct_suite.erl
+++ b/lib/common_test/src/ct_suite.erl
@@ -47,9 +47,9 @@
{group, ct_groupname(), ct_group_props_ref()} |
{group, ct_groupname(), ct_group_props_ref(), ct_subgroups_def()}.
-type ct_testcase_ref() :: {testcase, ct_testname(), ct_testcase_repeat_prop()}.
--type ct_testcase_repeat_prop() :: {repeat, ct_test_repeat()} |
+-type ct_testcase_repeat_prop() :: [{repeat, ct_test_repeat()} |
{repeat_until_ok, ct_test_repeat()} |
- {repeat_until_fail, ct_test_repeat()}.
+ {repeat_until_fail, ct_test_repeat()}].
-type ct_info() :: {timetrap, ct_info_timetrap()} |
{require, ct_info_required()} |
{require, Name :: atom(), ct_info_required()} |
diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl
index d9cefc77cd..9b63c0d60b 100644
--- a/lib/common_test/src/ct_testspec.erl
+++ b/lib/common_test/src/ct_testspec.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1079,7 +1079,7 @@ add_tests([],Spec) -> % done
%% have added something of his/her own, which we'll let pass if relaxed
%% mode is enabled.
check_term(Term) when is_tuple(Term) ->
- Size = size(Term),
+ Size = tuple_size(Term),
[Name|_] = tuple_to_list(Term),
Valid = valid_terms(),
case lists:member({Name,Size},Valid) of
@@ -1093,7 +1093,7 @@ check_term(Term) when is_tuple(Term) ->
case get(relaxed) of
true ->
%% warn if name resembles a CT term
- case resembles_ct_term(Name,size(Term)) of
+ case resembles_ct_term(Name,tuple_size(Term)) of
true ->
io:format("~nSuspicious term, "
"please check:~n"
diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl
index 3e711708c0..3816e202a4 100644
--- a/lib/common_test/src/ct_util.erl
+++ b/lib/common_test/src/ct_util.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -786,7 +786,7 @@ listenv(Telnet) ->
%%% Equivalent to ct:parse_table/1
parse_table(Data) ->
{Heading, Rest} = get_headings(Data),
- Lines = parse_row(Rest,[],size(Heading)),
+ Lines = parse_row(Rest,[],tuple_size(Heading)),
{Heading,Lines}.
get_headings(["|" ++ Headings | Rest]) ->
diff --git a/lib/common_test/src/cth_conn_log.erl b/lib/common_test/src/cth_conn_log.erl
index 7b6f03311a..744642c69f 100644
--- a/lib/common_test/src/cth_conn_log.erl
+++ b/lib/common_test/src/cth_conn_log.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@
%%----------------------------------------------------------------------
-module(cth_conn_log).
--include_lib("common_test/include/ct.hrl").
+-include("ct.hrl").
-export([init/2,
pre_init_per_testcase/4,
diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl
index feb607b46e..d6a882e315 100644
--- a/lib/common_test/src/test_server.erl
+++ b/lib/common_test/src/test_server.erl
@@ -2789,7 +2789,8 @@ peer_name(Module, TestCase) ->
peer:random_name(lists:concat([Module, "-", TestCase])).
%% Command line arguments passed
--spec start_peer([string()] | peer:start_options(), atom() | string(), TestCase :: atom() | string()) ->
+-spec start_peer([string()] | peer:start_options() | #{ start_cover => boolean() },
+ atom() | string(), TestCase :: atom() | string()) ->
{ok, gen_statem:server_ref(), node()} | {error, term()}.
start_peer(Args, Module, TestCase) when is_list(Args) ->
start_peer(#{args => Args, name => peer_name(Module, TestCase)}, Module);
@@ -2801,9 +2802,10 @@ start_peer(Opts, Module, TestCase) ->
start_peer(Opts#{name => peer_name(Module, TestCase)}, Module).
%% Release compatibility testing
--spec start_peer([string()] | peer:start_options(), atom() | string(), TestCase :: atom() | string(),
- Release :: string(), OutDir :: file:filename()) ->
- {ok, gen_statem:server_ref(), node()} | {error, term()} | not_available.
+-spec start_peer([string()] | peer:start_options() | #{ start_cover => boolean() },
+ atom() | string(), TestCase :: atom() | string(),
+ Release :: string(), OutDir :: file:filename()) ->
+ {ok, gen_statem:server_ref(), node()} | {error, term()} | not_available.
start_peer(Args, Module, TestCase, Release, OutDir) when is_list(Args) ->
start_peer(#{args => Args}, Module, TestCase, Release, OutDir);
start_peer(Opts, Module, TestCase, Release, OutDir) ->
@@ -2815,8 +2817,8 @@ start_peer(Opts, Module, TestCase, Release, OutDir) ->
%% for old releases. Keep ERL_FLAGS, and ERL_ZFLAGS for sometimes you might need it...
Env = maps:get(env, Opts, []) ++ [{"ERL_AFLAGS", false}],
NewArgs = ["-pa", peer_compile(Erl, code:which(peer), OutDir) | maps:get(args, Opts, [])],
- start_peer(Opts#{exec => Erl, args => NewArgs,
- env => Env}, Module, TestCase)
+ start_peer(Opts#{exec => Erl, args => NewArgs, env => Env,
+ start_cover => false }, Module, TestCase)
end.
%% Internal implementation
@@ -2844,7 +2846,6 @@ start_peer(#{name := Name} = Opts, Module) ->
Shutdown = binary_to_term(term_to_binary({10000, CoverMain})),
case peer:start_link(Opts#{args => FullArgs, shutdown => Shutdown}) of
{ok, Peer, Node} ->
- do_cover_for_node(Node, start),
{ok, Peer, Node};
Other ->
Other
@@ -2861,7 +2862,7 @@ peer_compile(Erl, cover_compiled, OutDir) ->
peer_compile(Erl, ModPath, OutDir) ->
{ok, ModSrc} = filelib:find_source(ModPath),
Erlc = filename:join(filename:dirname(Erl), "erlc"),
- cmd(Erlc, ["-o", OutDir, ModSrc]),
+ cmd(Erlc, ["-o", OutDir, unicode:characters_to_binary(ModSrc)]),
OutDir.
%% This should really be implemented as os:cmd.
diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl
index 538ea0de44..23a6deb6df 100644
--- a/lib/common_test/src/test_server_ctrl.erl
+++ b/lib/common_test/src/test_server_ctrl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -116,7 +116,8 @@
create_priv_dir=auto_per_run, finish=false,
target_info, cover=false, wait_for_node=[],
testcase_callback=undefined, idle_notify=[],
- get_totals=false, random_seed=undefined}).
+ get_totals=false, random_seed=undefined,
+ old_releases=#{}}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% OPERATOR INTERFACE
@@ -917,13 +918,19 @@ handle_call({is_release_available, Release}, _From, State) ->
%%
%% Find the path of the release's erl file if available
-handle_call({find_release, Release}, _From, State) ->
- R =
- case test_server_node:is_release_available(Release) of
- true -> test_server_node:find_release(Release);
- _ -> not_available
- end,
- {reply, R, State}.
+handle_call({find_release, Release}, From, State = #state{ old_releases = OldReleases }) ->
+ case maps:find(Release, OldReleases) of
+ error ->
+ R =
+ case test_server_node:find_release(Release) of
+ none -> not_available;
+ PathToRelease -> PathToRelease
+ end,
+ handle_call({find_release, Release}, From,
+ State#state{ old_releases = OldReleases#{ Release => R }});
+ {ok, R} ->
+ {reply, R, State}
+ end.
%%--------------------------------------------------------------------
set_hosts(Hosts) ->
diff --git a/lib/common_test/src/test_server_node.erl b/lib/common_test/src/test_server_node.erl
index e5400e5e4d..17a5650355 100644
--- a/lib/common_test/src/test_server_node.erl
+++ b/lib/common_test/src/test_server_node.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -485,7 +485,7 @@ find_release(Rel) ->
none ->
case string:take(Rel,"_",true) of
{Rel,[]} ->
- false;
+ none;
{RelNum,_} ->
find_release_path(RelNum)
end;
@@ -504,19 +504,19 @@ find_release_path([Path|T], Rel) ->
false ->
find_release_path(T, Rel);
ErlExec ->
- Pattern = filename:join([Path,"..","releases","*","OTP_VERSION"]),
- case filelib:wildcard(Pattern) of
- [VersionFile] ->
- {ok, VsnBin} = file:read_file(VersionFile),
- [MajorVsn|_] = string:lexemes(VsnBin, "."),
- case unicode:characters_to_list(MajorVsn) of
- Rel ->
- ErlExec;
- _Else ->
- find_release_path(T, Rel)
+ QuotedExec = "\""++ErlExec++"\"",
+ Release = os:cmd(QuotedExec ++ " -noinput -eval 'io:format(\"~ts\", [erlang:system_info(otp_release)])' -s init stop"),
+ case Release =:= Rel of
+ true ->
+ %% Check is the release is a source tree release,
+ %% if so we should not use it.
+ case os:cmd(QuotedExec ++ " -noinput -eval 'io:format(\"~p\",[filelib:is_file(filename:join([code:root_dir(),\"OTP_VERSION\"]))]).' -s init stop") of
+ "true" ->
+ find_release_path(T, Rel);
+ "false" ->
+ ErlExec
end;
- _Else ->
- find_release_path(T, Rel)
+ false -> find_release_path(T, Rel)
end
end;
find_release_path([], _) ->
diff --git a/lib/common_test/test/test_server_SUITE.erl b/lib/common_test/test/test_server_SUITE.erl
index 37c7731404..e1180fa1f5 100644
--- a/lib/common_test/test/test_server_SUITE.erl
+++ b/lib/common_test/test/test_server_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -283,7 +283,7 @@ get_latest_run_dir(Dir) ->
Dir
end.
-l(X) when is_binary(X) -> size(X);
+l(X) when is_binary(X) -> byte_size(X);
l(X) when is_list(X) -> length(X).
get_latest_dir([H|T],Latest) when H>Latest ->
diff --git a/lib/common_test/test_server/.gitignore b/lib/common_test/test_server/.gitignore
new file mode 100644
index 0000000000..7c38aa35ba
--- /dev/null
+++ b/lib/common_test/test_server/.gitignore
@@ -0,0 +1,4 @@
+conf_vars
+config.log
+config.status
+variables
diff --git a/lib/compiler/Makefile b/lib/compiler/Makefile
index a7d896f258..9394129f02 100644
--- a/lib/compiler/Makefile
+++ b/lib/compiler/Makefile
@@ -38,6 +38,7 @@ include $(ERL_TOP)/make/otp_subdir.mk
DIA_PLT_APPS=crypto
+NO_TEST_TARGET:=1 # Avoid warning about ignoring old recipe for target 'test'
include $(ERL_TOP)/make/app_targets.mk
# Enable feature maybe_expr in runtime when running tests
diff --git a/lib/compiler/doc/src/Makefile b/lib/compiler/doc/src/Makefile
index 801e1adfac..57c7bc2dc7 100644
--- a/lib/compiler/doc/src/Makefile
+++ b/lib/compiler/doc/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2021. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ EDOC_REF3_FILES = cerl.xml cerl_trees.xml cerl_clauses.xml
XML_PART_FILES = internal.xml
XML_NOTES_FILES = notes.xml
-XML_INTERNAL_FILES = beam_ssa.xml
+XML_INTERNAL_FILES = beam_ssa.xml ssa_checks.xml
XML_GEN_FILES = $(XML_INTERNAL_FILES:%=$(XMLDIR)/%)
diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml
index 5828fc6a17..d7bfc63c83 100644
--- a/lib/compiler/doc/src/compile.xml
+++ b/lib/compiler/doc/src/compile.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -589,7 +589,17 @@ module.beam: module.erl \
more information.
</p>
</item>
- </taglist>
+
+ <tag><c>{check_ssa, Tag :: atom()}</c></tag>
+ <item>
+ <p>Parse and check assertions on the structure and content
+ of the BEAM SSA code produced by the compiler. The
+ <c>Tag</c> indicates the set of assertions to check and
+ after which compiler pass the check is performed. This
+ option is internal to the compiler and can be changed or
+ removed at any time without prior warning.</p>
+ </item>
+ </taglist>
<p>If warnings are turned on (option <c>report_warnings</c>
described earlier), the following options control what type of
@@ -826,7 +836,23 @@ module.beam: module.erl \
(or contract) for an exported or unexported function is not
given. Use this option to turn on this kind of warning.</p>
</item>
- </taglist>
+
+ <tag><c>nowarn_redefined_builtin_type</c></tag>
+ <item>
+ <p>By default, a warning is emitted when a built-in type
+ is locally redefined. Use this option to turn off this
+ kind of warning.</p>
+ </item>
+
+ <tag><c>{nowarn_redefined_builtin_type, Types}</c></tag>
+ <item>
+ <p>By default, a warning is emitted when a built-in type
+ is locally redefined. Use this option to turn off this
+ kind of warning for the types in <c>Types</c>, where
+ <c>Types</c> is a tuple <c>{TypeName,Arity}</c> or a list
+ of such tuples.</p>
+ </item>
+</taglist>
<p>Other kinds of warnings are <em>opportunistic
warnings</em>. They are generated when the compiler happens to
diff --git a/lib/compiler/doc/src/internal.xml b/lib/compiler/doc/src/internal.xml
index be2cf75356..57b2231218 100644
--- a/lib/compiler/doc/src/internal.xml
+++ b/lib/compiler/doc/src/internal.xml
@@ -4,7 +4,7 @@
<internal xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2018</year><year>2021</year>
+ <year>2018</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -35,5 +35,6 @@
<xi:include href="cerl_trees.xml"/>
<xi:include href="cerl_clauses.xml"/>
<xi:include href="beam_ssa.xml"/>
+ <xi:include href="ssa_checks.xml"/>
</internal>
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index 75396f4c0f..f92505409c 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,21 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 8.2.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed type handling bugs that could cause an internal
+ error in the compiler for correct code.</p>
+ <p>
+ Own Id: OTP-18565 Aux Id: GH-7147 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 8.2.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/compiler/internal_doc/ssa_checks.md b/lib/compiler/internal_doc/ssa_checks.md
new file mode 100644
index 0000000000..f4c68e3939
--- /dev/null
+++ b/lib/compiler/internal_doc/ssa_checks.md
@@ -0,0 +1,124 @@
+BEAM SSA Checks
+===============
+
+While developing optimizations operating on the BEAM SSA it is often
+hard to check that various transforms have the intended effect. For
+example, unless a transform produces crashing code, it is hard to
+detect that the transform is broken. Likewise missing an optimization
+opportunity is also hard to detect.
+
+To simplify the creation of tests on BEAM SSA the compiler has an
+internal mode in which it parses and checks assertions on the
+structure and content of the produced BEAM SSA code. This is a short
+introduction to the syntax and semantics of the SSA checker
+functionality.
+
+Syntax
+------
+
+SSA checks are embedded in the source code as comments starting with
+with one of `%ssa%`, `%%ssa%` or `%%%ssa%`. This is a short
+introduction the syntax, for the full syntax please refer to the
+`ssa_check_when_clause` production in `erl_parse.yrl`.
+
+SSA checks can be placed inside any Erlang function, for example:
+
+ t0() ->
+ %ssa% () when post_ssa_opt ->
+ %ssa% ret(#{}).
+ #{}.
+
+will check that `t0/0` returns the literal `#{}`. If we want to check
+that a function returns its first formal parameter, we can write:
+
+ t1(A, _B) ->
+ %ssa% (X, _) when post_ssa_opt ->
+ %ssa% ret(X).
+ A.
+
+Note how we match the first formal parameter using `X`. The reason for
+having our own formal parameters for the SSA check, is that we don't
+want to introduce new identifiers at the Erlang level to support
+SSA-level checks. Consider if `t1/2` had been defined as `t1([A|As],
+B)` we would have had to introduce a new identifier for the aggregate
+value `[A|As]`.
+
+The full syntax for a SSA check clause is:
+
+ <expected-result>? (<formals>) when <pipeline-location> -> <checks> '.'
+
+where `<expected-result>` can be one of `pass` (the check must
+succeed), `fail` and `xfail` (the check must fail). Omitting
+`<expected-result>` is parsed as an implicit `pass`.
+
+`<formals>` is a comma-separated list of variables.
+
+`<pipeline-location>` specifies when in the compiler pipeline to run
+the checks. For now the only supported value for `<pipeline-location>`
+is `post_ssa_opt` which runs the checks after the `ssa_opt` pass.
+
+`<checks>` is a comma-separated list of matches against the BEAM SSA
+code. For non-flow-control operations the syntax is:
+
+ <variable> = <operation> ( <arguments> ) <annotation>?
+
+where `<operation>` is the `#b_set.op` field from the internal SSA
+representation. BIFs are written as `bif:<atom>`.
+
+`<arguments>` is a comma-separated list of variables or literals.
+
+For flow control operations and labels, the syntax is as follows:
+
+ br(<bool>, <true-label>, <false-label>)
+
+ switch(<value>, <fail-label>, [{<label>,<value>},...])
+
+ ret(<value>)
+
+ label <value>
+
+where `<value>` is a literal or a variable.
+
+A check can also include an assertion on operation annotations. The
+assertion is written as a map-like pattern following the argument
+list, for example:
+
+ t0() ->
+ %ssa% () when post_ssa_opt ->
+ %ssa% _ = call(fun return_int/0) { result_type => {t_integer,{17,17}},
+ %ssa% location => {_,32} },
+ %ssa% _ = call(fun return_tuple/0) {
+ %ssa% result_type => {t_tuple,2,true,#{1 => {t_integer,{1,1}},
+ %ssa% 2 => {t_integer,{2,2}}}}
+ %ssa% }.
+ X = return_int(),
+ Y = return_tuple(),
+ {X, Y}.
+
+Semantics
+---------
+
+When an SSA assertion is matched against the BEAM SSA for a function,
+patterns are applied sequentially. If the current pattern doesn't
+match, the checker tries with the next instruction. If the checker
+reaches the end of the SSA representation without having matched all
+patterns, the check is considered failed otherwise the assertion is
+considered successful. When a pattern is matched against an SSA
+operation, the values of variables already bound are considered and if
+the patterns matches, free variables introduced in the successfully
+matched pattern are bound to the values they have in the matched
+operation.
+
+Compiler integration
+--------------------
+
+The BEAM SSA checker is enabled by the compiler option
+`{check_ssa,post_ssa_opt}`. When enabled, a failed SSA assertion will
+be reported using the same mechanisms as an ordinary compilation
+error.
+
+Limitations
+-----------
+
+* Unbound variables are not allowed in the key-part of map literals nor
+in annotation assertions.
diff --git a/lib/compiler/scripts/smoke b/lib/compiler/scripts/smoke
index ae31c923b8..98e5fd55df 100755
--- a/lib/compiler/scripts/smoke
+++ b/lib/compiler/scripts/smoke
@@ -29,7 +29,7 @@ clone_elixir() ->
true ->
GetHeadSHA1 = "cd elixir && git rev-parse --verify HEAD",
Before = os:cmd(GetHeadSHA1),
- cmd("cd elixir && git pull --ff-only origin master"),
+ cmd("cd elixir && git pull --ff-only origin main"),
case os:cmd(GetHeadSHA1) of
Before ->
ok;
diff --git a/lib/compiler/src/.gitignore b/lib/compiler/src/.gitignore
new file mode 100644
index 0000000000..05d4e67134
--- /dev/null
+++ b/lib/compiler/src/.gitignore
@@ -0,0 +1 @@
+OPCODES-GENERATED
diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile
index d801d6baa0..e0625337b5 100644
--- a/lib/compiler/src/Makefile
+++ b/lib/compiler/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2022. All Rights Reserved.
+# Copyright Ericsson AB 1996-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -60,15 +60,18 @@ MODULES = \
beam_listing \
beam_opcodes \
beam_ssa \
+ beam_ssa_alias \
beam_ssa_bc_size \
beam_ssa_bool \
beam_ssa_bsm \
+ beam_ssa_check \
beam_ssa_codegen \
beam_ssa_dead \
beam_ssa_lint \
beam_ssa_opt \
beam_ssa_pp \
beam_ssa_pre_codegen \
+ beam_ssa_private_append \
beam_ssa_recv \
beam_ssa_share \
beam_ssa_throw \
@@ -154,7 +157,7 @@ docs:
clean:
rm -f $(TARGET_FILES)
- rm -f $(EGEN)/beam_opcodes.erl $(EGEN)/beam_opcodes.hrl
+ rm -f $(EGEN)/beam_opcodes.erl $(EGEN)/beam_opcodes.hrl $(EGEN)/OPCODES-GENERATED
rm -f $(EGEN)/core_parse.erl
rm -f core
@@ -168,8 +171,10 @@ $(APP_TARGET): $(APP_SRC) ../vsn.mk
$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
$(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@
-$(EGEN)/beam_opcodes.erl $(EGEN)/beam_opcodes.hrl: genop.tab
- $(gen_verbose)$(PERL) $(ERL_TOP)/erts/emulator/utils/beam_makeops -compiler -outdir $(EGEN) $<
+$(EGEN)/beam_opcodes.erl $(EGEN)/beam_opcodes.hrl: $(EGEN)/OPCODES-GENERATED
+
+$(EGEN)/OPCODES-GENERATED: genop.tab
+ $(gen_verbose)$(PERL) $(ERL_TOP)/erts/emulator/utils/beam_makeops -compiler -outdir $(EGEN) $< && echo $? >$(EGEN)/OPCODES-GENERATED
$(EBIN)/beam_asm.beam: $(ESRC)/beam_asm.erl $(EGEN)/beam_opcodes.hrl
$(V_ERLC) $(ERL_COMPILE_FLAGS) -DCOMPILER_VSN='"$(VSN)"' -o$(EBIN) $<
@@ -199,25 +204,31 @@ release_docs_spec:
# Dependencies -- alphabetically, please
# ----------------------------------------------------
-$(EBIN)/beam_asm.beam: beam_asm.hrl
+$(EBIN)/beam_a.beam: beam_asm.hrl beam_types.hrl
+$(EBIN)/beam_asm.beam: beam_asm.hrl $(EGEN)/beam_opcodes.hrl beam_types.hrl
$(EBIN)/beam_call_types.beam: beam_types.hrl
$(EBIN)/beam_block.beam: beam_asm.hrl
-$(EBIN)/beam_disasm.beam: $(EGEN)/beam_opcodes.hrl beam_disasm.hrl beam_asm.hrl
+$(EBIN)/beam_dict.beam: beam_types.hrl
+$(EBIN)/beam_disasm.beam: $(EGEN)/beam_opcodes.hrl beam_disasm.hrl \
+ beam_asm.hrl beam_types.hrl
$(EBIN)/beam_jump.beam: beam_asm.hrl
$(EBIN)/beam_kernel_to_ssa.beam: v3_kernel.hrl beam_ssa.hrl
-$(EBIN)/beam_listing.beam: core_parse.hrl v3_kernel.hrl beam_ssa.hrl beam_asm.hrl
+$(EBIN)/beam_listing.beam: core_parse.hrl v3_kernel.hrl beam_ssa.hrl \
+ beam_asm.hrl beam_types.hrl
$(EBIN)/beam_ssa.beam: beam_ssa.hrl
+$(EBIN)/beam_ssa_alias_opt.beam: beam_ssa_opt.hrl beam_types.hrl
$(EBIN)/beam_ssa_bsm.beam: beam_ssa.hrl
$(EBIN)/beam_ssa_bool.beam: beam_ssa.hrl
+$(EBIN)/beam_ssa_check.beam: beam_ssa.hrl beam_types.hrl
$(EBIN)/beam_ssa_codegen.beam: beam_ssa.hrl beam_asm.hrl
$(EBIN)/beam_ssa_dead.beam: beam_ssa.hrl
$(EBIN)/beam_ssa_lint.beam: beam_ssa.hrl
$(EBIN)/beam_ssa_opt.beam: beam_ssa.hrl
-$(EBIN)/beam_ssa_pp.beam: beam_ssa.hrl
+$(EBIN)/beam_ssa_pp.beam: beam_ssa.hrl beam_types.hrl
$(EBIN)/beam_ssa_pre_codegen.beam: beam_ssa.hrl beam_asm.hrl
$(EBIN)/beam_ssa_recv.beam: beam_ssa.hrl
$(EBIN)/beam_ssa_share.beam: beam_ssa.hrl
-$(EBIN)/beam_ssa_throw.beam: beam_ssa.hrl
+$(EBIN)/beam_ssa_throw.beam: beam_ssa.hrl beam_types.hrl
$(EBIN)/beam_ssa_type.beam: beam_ssa.hrl beam_types.hrl
$(EBIN)/beam_trim.beam: beam_asm.hrl
$(EBIN)/beam_types.beam: beam_types.hrl
diff --git a/lib/compiler/src/beam_a.erl b/lib/compiler/src/beam_a.erl
index 97f4089a1b..3e249de7ef 100644
--- a/lib/compiler/src/beam_a.erl
+++ b/lib/compiler/src/beam_a.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,6 +25,8 @@
-export([module/2]).
+-include("beam_asm.hrl").
+
-spec module(beam_asm:module_code(), [compile:option()]) ->
{'ok',beam_utils:module_code()}.
@@ -85,6 +87,24 @@ rename_instrs([I|Is]) ->
[rename_instr(I)|rename_instrs(Is)];
rename_instrs([]) -> [].
+rename_instr({bif,Bif,Fail,[A,B],Dst}=I) ->
+ case Bif of
+ '=<' ->
+ {bif,'>=',Fail,[B,A],Dst};
+ '<' ->
+ case [A,B] of
+ [{integer,N},{tr,_,#t_integer{}}=Src] ->
+ {bif,'>=',Fail,[Src,{integer,N+1}],Dst};
+ [{tr,_,#t_integer{}}=Src,{integer,N}] ->
+ {bif,'>=',Fail,[{integer,N-1},Src],Dst};
+ [_,_] ->
+ I
+ end;
+ '>' ->
+ rename_instr({bif,'<',Fail,[B,A],Dst});
+ _ ->
+ I
+ end;
rename_instr({bs_put_binary=I,F,Sz,U,Fl,Src}) ->
{bs_put,F,{I,U,Fl},[Sz,Src]};
rename_instr({bs_put_float=I,F,Sz,U,Fl,Src}) ->
diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl
index 7317b2409d..d959e21ea1 100644
--- a/lib/compiler/src/beam_asm.erl
+++ b/lib/compiler/src/beam_asm.erl
@@ -26,7 +26,7 @@
-export_type([fail/0,label/0,src/0,module_code/0,function_name/0]).
--import(lists, [map/2,member/2,keymember/3,duplicate/2,splitwith/2]).
+-import(lists, [append/1,duplicate/2,map/2,member/2,keymember/3,splitwith/2]).
-include("beam_opcodes.hrl").
-include("beam_asm.hrl").
@@ -481,6 +481,13 @@ encode_arg({extfunc, M, F, A}, Dict0) ->
encode_arg({list, List}, Dict0) ->
{L, Dict} = encode_list(List, Dict0, []),
{[encode(?tag_z, 1), encode(?tag_u, length(List))|L], Dict};
+encode_arg({commands, List0}, Dict) ->
+ List1 = [begin
+ [H|T] = tuple_to_list(Tuple),
+ [{atom,H}|T]
+ end || Tuple <- List0],
+ List = append(List1),
+ encode_arg({list, List}, Dict);
encode_arg({float, Float}, Dict) when is_float(Float) ->
encode_literal(Float, Dict);
encode_arg({fr,Fr}, Dict) ->
@@ -540,13 +547,13 @@ encode_alloc_list_1([], Dict, Acc) ->
-spec encode(non_neg_integer(), integer()) -> iolist() | integer().
-encode(Tag, N) when N < 0 ->
+encode(Tag, N) when is_integer(N), N < 0 ->
encode1(Tag, negative_to_bytes(N));
-encode(Tag, N) when N < 16 ->
+encode(Tag, N) when is_integer(N), N < 16 ->
(N bsl 4) bor Tag;
-encode(Tag, N) when N < 16#800 ->
+encode(Tag, N) when is_integer(N), N < 16#800 ->
[((N bsr 3) band 2#11100000) bor Tag bor 2#00001000, N band 16#ff];
-encode(Tag, N) ->
+encode(Tag, N) when is_integer(N) ->
encode1(Tag, to_bytes(N)).
encode1(Tag, Bytes) ->
diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl
index 2954d95b7e..b93293763d 100644
--- a/lib/compiler/src/beam_block.erl
+++ b/lib/compiler/src/beam_block.erl
@@ -74,6 +74,10 @@ function({function,Name,Arity,CLabel,Is0}) ->
%%% (Provided that x2 is killed in the code that follows.)
%%%
+swap_opt([{move,Src,Dst},{swap,Dst,Other}|Is]) when Src =/= Other ->
+ swap_opt([{move,Other,Dst},{move,Src,Other}|Is]);
+swap_opt([{move,Src,Dst},{swap,Other,Dst}|Is]) when Src =/= Other ->
+ swap_opt([{move,Other,Dst},{move,Src,Other}|Is]);
swap_opt([{move,Reg1,{x,_}=Temp}=Move1,
{move,Reg2,Reg1}=Move2|Is0]) when Reg1 =/= Temp ->
case swap_opt_end(Is0, Temp, Reg2, []) of
diff --git a/lib/compiler/src/beam_bounds.erl b/lib/compiler/src/beam_bounds.erl
index 9a0507bb38..6ac5141821 100644
--- a/lib/compiler/src/beam_bounds.erl
+++ b/lib/compiler/src/beam_bounds.erl
@@ -26,49 +26,284 @@
%%
%%
-module(beam_bounds).
--export(['+'/2, '-'/2, '*'/2, 'div'/2, 'rem'/2,
- 'band'/2, 'bor'/2, 'bxor'/2, 'bsr'/2, 'bsl'/2,
- relop/3]).
-
--type range() :: {integer(), integer()} | 'any'.
--type range_result() :: range() | 'any'.
+-export([bounds/2, bounds/3, relop/3, infer_relop_types/3,
+ is_masking_redundant/2]).
+-export_type([range/0]).
+
+-type range() :: {integer(), integer()} |
+ {'-inf', integer()} |
+ {integer(), '+inf'} |
+ 'any'.
+-type range_result() :: range() | 'any' | 'none'.
-type relop() :: '<' | '=<' | '>' | '>='.
-type bool_result() :: 'true' | 'false' | 'maybe'.
+-type op() :: atom().
+
+%% Maximum size of integers in bits to keep ranges for.
+-define(NUM_BITS, 128).
+
+-spec bounds(op(), range()) -> range_result().
+
+bounds('bnot', R0) ->
+ case R0 of
+ {A, B} when is_integer(A), is_integer(B), A =/= B ->
+ %% While it's easy to get an exact range, doing so can make certain
+ %% chains of operations slow to converge, e.g.
+ %%
+ %% f(0) -> -1; f(N) -> abs(bnot f(N)).
+ %%
+ %% Where the range increases by 1 every time we pass through,
+ %% making it more or less impossible to reach a fixpoint.
+ %%
+ %% We therefore widen the range a bit quicker to ensure that we
+ %% converge on 'any' within a reasonable time frame, hoping that
+ %% the range will still be tight enough in the cases where we
+ %% don't feed the result into itself.
+ case {abs(A) bsr ?NUM_BITS, abs(B) bsr ?NUM_BITS} of
+ {0, 0} ->
+ Min = min(-B - 1, -(B bsl 1) - 1),
+ Max = max(-A - 1, -(A bsl 1) - 1),
+ normalize({Min, Max});
+ {_, _} ->
+ any
+ end;
+ {A, B} ->
+ R = {inf_add(inf_neg(B), -1), inf_add(inf_neg(A), -1)},
+ normalize(R);
+ _ ->
+ any
+ end;
+bounds(abs, R) ->
+ case R of
+ {A,B} when is_integer(A), is_integer(B) ->
+ Min = 0,
+ Max = max(abs(A), abs(B)),
+ {Min,Max};
+ _ ->
+ {0,'+inf'}
+ end.
--spec '+'(range(), range()) -> range_result().
+-spec bounds(op(), range(), range()) -> range_result().
+
+bounds('+', R1, R2) ->
+ case {R1,R2} of
+ {{A,B}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0,
+ abs(B) bsr ?NUM_BITS =:= 0,
+ abs(C) bsr ?NUM_BITS =:= 0,
+ abs(D) bsr ?NUM_BITS =:= 0 ->
+ normalize({A+C,B+D});
+ {{'-inf',B}, {_C,D}} when abs(B) bsr ?NUM_BITS =:= 0,
+ abs(D) bsr ?NUM_BITS =:= 0 ->
+ normalize({'-inf',B+D});
+ {{_A,B}, {'-inf',D}} when abs(B) bsr ?NUM_BITS =:= 0,
+ abs(D) bsr ?NUM_BITS =:= 0 ->
+ normalize({'-inf',B+D});
+ {{A,'+inf'}, {C,_D}} when abs(A) bsr ?NUM_BITS =:= 0,
+ abs(C) bsr ?NUM_BITS =:= 0 ->
+ normalize({A+C,'+inf'});
+ {{A,_B}, {C,'+inf'}} when abs(A) bsr ?NUM_BITS =:= 0,
+ abs(C) bsr ?NUM_BITS =:= 0 ->
+ normalize({A+C,'+inf'});
+ {_, _} ->
+ any
+ end;
+bounds('-', R1, R2) ->
+ case {R1,R2} of
+ {{A,B}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0,
+ abs(B) bsr ?NUM_BITS =:= 0,
+ abs(C) bsr ?NUM_BITS =:= 0,
+ abs(D) bsr ?NUM_BITS =:= 0 ->
+ normalize({A-D,B-C});
+ {{A,'+inf'}, {_C,D}} when abs(A) bsr ?NUM_BITS =:= 0,
+ abs(D) bsr ?NUM_BITS =:= 0 ->
+ normalize({A-D,'+inf'});
+ {{_A,B}, {C,'+inf'}} when abs(B) bsr ?NUM_BITS =:= 0,
+ abs(C) bsr ?NUM_BITS =:= 0 ->
+ normalize({'-inf',B-C});
+ {{'-inf',B}, {C,_D}} when abs(B) bsr ?NUM_BITS =:= 0,
+ abs(C) bsr ?NUM_BITS =:= 0 ->
+ normalize({'-inf',B-C});
+ {{A,_B}, {'-inf',D}} when abs(A) bsr ?NUM_BITS =:= 0,
+ abs(D) bsr ?NUM_BITS =:= 0 ->
+ normalize({A-D,'+inf'});
+ {_, _} ->
+ any
+ end;
+bounds('*', R1, R2) ->
+ case {R1,R2} of
+ {{A,B}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0,
+ abs(B) bsr ?NUM_BITS =:= 0,
+ abs(C) bsr ?NUM_BITS =:= 0,
+ abs(D) bsr ?NUM_BITS =:= 0 ->
+ All = [X * Y || X <- [A,B], Y <- [C,D]],
+ Min = lists:min(All),
+ Max = lists:max(All),
+ normalize({Min,Max});
+ {{A,'+inf'}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0,
+ abs(C) bsr ?NUM_BITS =:= 0,
+ abs(D) bsr ?NUM_BITS =:= 0,
+ C >= 0 ->
+ {min(A*C, A*D),'+inf'};
+ {{'-inf',B}, {C,D}} when abs(B) bsr ?NUM_BITS =:= 0,
+ abs(C) bsr ?NUM_BITS =:= 0,
+ abs(D) bsr ?NUM_BITS =:= 0,
+ C >= 0 ->
+ {'-inf',max(B*C, B*D)};
+ {{A,B}, {'-inf',_}} when is_integer(A), is_integer(B) ->
+ bounds('*', R2, R1);
+ {{A,B}, {_,'+inf'}} when is_integer(A), is_integer(B) ->
+ bounds('*', R2, R1);
+ {_, _} ->
+ any
+ end;
+bounds('div', R1, R2) ->
+ div_bounds(R1, R2);
+bounds('rem', R1, R2) ->
+ rem_bounds(R1, R2);
+bounds('band', R1, R2) ->
+ case {R1,R2} of
+ {{A,B}, {C,D}} when A bsr ?NUM_BITS =:= 0, A >= 0,
+ C bsr ?NUM_BITS =:= 0, C >= 0,
+ is_integer(B), is_integer(D) ->
+ Min = min_band(A, B, C, D),
+ Max = max_band(A, B, C, D),
+ {Min,Max};
+ {_, {C,D}} when is_integer(C), C >= 0 ->
+ {0,D};
+ {{A,B}, _} when is_integer(A), A >= 0 ->
+ {0,B};
+ {_, _} ->
+ any
+ end;
+bounds('bor', R1, R2) ->
+ case {R1,R2} of
+ {{A,B}, {C,D}} when A bsr ?NUM_BITS =:= 0, A >= 0,
+ C bsr ?NUM_BITS =:= 0, C >= 0,
+ is_integer(B), is_integer(D) ->
+ Min = min_bor(A, B, C, D),
+ Max = max_bor(A, B, C, D),
+ {Min,Max};
+ {_, _} ->
+ any
+ end;
+bounds('bxor', R1, R2) ->
+ case {R1,R2} of
+ {{A,B}, {C,D}} when A bsr ?NUM_BITS =:= 0, A >= 0,
+ C bsr ?NUM_BITS =:= 0, C >= 0,
+ is_integer(B), is_integer(D) ->
+ Max = max_bxor(A, B, C, D),
+ {0,Max};
+ {_, _} ->
+ any
+ end;
+bounds('bsr', R1, R2) ->
+ case {R1,R2} of
+ {{A,B}, {C,D}} when is_integer(C), C >= 0 ->
+ Min = inf_min(inf_bsr(A, C), inf_bsr(A, D)),
+ Max = inf_max(inf_bsr(B, C), inf_bsr(B, D)),
+ normalize({Min,Max});
+ {_, _} ->
+ any
+ end;
+bounds('bsl', R1, R2) ->
+ case {R1,R2} of
+ {{A,B}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0,
+ abs(B) bsr ?NUM_BITS =:= 0 ->
+ Min = inf_min(inf_bsl(A, C), inf_bsl(A, D)),
+ Max = inf_max(inf_bsl(B, C), inf_bsl(B, D)),
+ normalize({Min,Max});
+ {_, _} ->
+ any
+ end;
+bounds(max, R1, R2) ->
+ case {R1,R2} of
+ {{A,B},{C,D}} ->
+ normalize({inf_max(A, C),inf_max(B, D)});
+ {_,_} ->
+ any
+ end;
+bounds(min, R1, R2) ->
+ case {R1,R2} of
+ {{A,B},{C,D}} ->
+ normalize({inf_min(A, C),inf_min(B, D)});
+ {_,_} ->
+ any
+ end.
-'+'({A,B}, {C,D}) when abs(A) bsr 256 =:= 0, abs(B) bsr 256 =:= 0,
- abs(C) bsr 256 =:= 0, abs(D) bsr 256 =:= 0 ->
- verify_range({A+C,B+D});
-'+'(_, _) ->
- any.
+-spec relop(relop(), range(), range()) -> bool_result().
+
+relop('<', {A,B}, {C,D}) ->
+ case {inf_lt(B, C),inf_lt(A, D)} of
+ {Bool,Bool} -> Bool;
+ {_,_} -> 'maybe'
+ end;
+relop('=<', {A,B}, {C,D}) ->
+ case {inf_le(B, C),inf_le(A, D)} of
+ {Bool,Bool} -> Bool;
+ {_,_} -> 'maybe'
+ end;
+relop('>=', {A,B}, {C,D}) ->
+ case {inf_ge(B, C),inf_ge(A, D)} of
+ {Bool,Bool} -> Bool;
+ {_,_} -> 'maybe'
+ end;
+relop('>', {A,B}, {C,D}) ->
+ case {inf_gt(B, C),inf_gt(A, D)} of
+ {Bool,Bool} -> Bool;
+ {_,_} -> 'maybe'
+ end;
+relop(_, _, _) ->
+ 'maybe'.
--spec '-'(range(), range()) -> range_result().
+-spec infer_relop_types(relop(), range(), range()) -> any().
-'-'({A,B}, {C,D}) when abs(A) bsr 256 =:= 0, abs(B) bsr 256 =:= 0,
- abs(C) bsr 256 =:= 0, abs(D) bsr 256 =:= 0 ->
- verify_range({A-D,B-C});
-'-'(_, _) ->
+infer_relop_types(Op, {_,_}=Range1, {_,_}=Range2) ->
+ case relop(Op, Range1, Range2) of
+ 'maybe' -> infer_relop_types_1(Op, Range1, Range2);
+ true -> any;
+ false -> none
+ end;
+infer_relop_types('<', {A,_}=R1, any) ->
+ {R1, normalize({inf_add(A, 1), '+inf'})};
+infer_relop_types('<', any, {_,D}=R2) ->
+ {normalize({'-inf', inf_add(D, -1)}), R2};
+infer_relop_types('=<', {A,_}=R1, any) ->
+ {R1, normalize({A, '+inf'})};
+infer_relop_types('=<', any, {_,D}=R2) ->
+ {normalize({'-inf', D}), R2};
+infer_relop_types('>=', {_,B}=R1, any) ->
+ {R1, normalize({'-inf', B})};
+infer_relop_types('>=', any, {C,_}=R2) ->
+ {normalize({C, '+inf'}), R2};
+infer_relop_types('>', {_,B}=R1, any) ->
+ {R1, normalize({'-inf', inf_add(B, -1)})};
+infer_relop_types('>', any, {C,_}=R2) ->
+ {normalize({inf_add(C, 1), '+inf'}), R2};
+infer_relop_types(_Op, _R1, _R2) ->
any.
--spec '*'(range(), range()) -> range_result().
+-spec is_masking_redundant(range(), integer()) -> boolean().
-'*'({A,B}, {C,D}) when abs(A) bsr 256 =:= 0, abs(B) bsr 256 =:= 0,
- abs(C) bsr 256 =:= 0, abs(D) bsr 256 =:= 0 ->
- All = [X * Y || X <- [A,B], Y <- [C,D]],
- Min = lists:min(All),
- Max = lists:max(All),
- verify_range({Min,Max});
-'*'(_, _) ->
- any.
+is_masking_redundant(_, -1) ->
+ true;
+is_masking_redundant({A,B}, M)
+ when M band (M + 1) =:= 0, %Is M + 1 a power of two?
+ M > 0,
+ is_integer(A), A >= 0,
+ B band M =:= B ->
+ true;
+is_masking_redundant(_, _) ->
+ false.
--spec 'div'(range(), range()) -> range_result().
+%%%
+%%% Internal functions.
+%%%
-'div'({_,_}, {0,0}) ->
+div_bounds({_,_}, {0,0}) ->
%% Division by zero, don't try to do anything clever.
any;
-'div'({A,B}, {C,D}) when is_integer(A), is_integer(B),
- is_integer(C), is_integer(D) ->
+div_bounds({A,B}, {C,D}) when is_integer(A), is_integer(B),
+ is_integer(C), is_integer(D) ->
Denominators = [min(C, D),max(C, D)|
%% Handle zero crossing for the denominator.
if
@@ -82,90 +317,34 @@
Y =/= 0],
Min = lists:min(All),
Max = lists:max(All),
- verify_range({Min,Max});
-'div'(_, _) ->
+ normalize({Min,Max});
+div_bounds({A,'+inf'}, {C,D}) when is_integer(C), C > 0, is_integer(D) ->
+ Min = min(A div C, A div D),
+ Max = '+inf',
+ normalize({Min,Max});
+div_bounds({'-inf',B}, {C,D}) when is_integer(C), C > 0, is_integer(D) ->
+ Min = '-inf',
+ Max = max(B div C, B div D),
+ normalize({Min,Max});
+div_bounds(_, _) ->
any.
--spec 'rem'(range(), range()) -> range_result().
-
-'rem'({A,_}, {C,D}) when C > 0 ->
- Max = D - 1,
+rem_bounds({A,_}, {C,D}) when is_integer(C), is_integer(D), C > 0 ->
+ Max = inf_add(D, -1),
Min = if
+ A =:= '-inf' -> -Max;
A >= 0 -> 0;
true -> -Max
end,
- verify_range({Min,Max});
-'rem'(_, {C,D}) when C =/= 0; D =/= 0 ->
+ normalize({Min,Max});
+rem_bounds(_, {C,D}) when is_integer(C), is_integer(D),
+ C =/= 0 orelse D =/= 0 ->
Max = max(abs(C), abs(D)) - 1,
Min = -Max,
- verify_range({Min,Max});
-'rem'(_, _) ->
+ normalize({Min,Max});
+rem_bounds(_, _) ->
any.
--spec 'band'(range(), range()) -> range_result().
-
-'band'({A,B}, {C,D}) when A >= 0, A bsr 256 =:= 0, C >= 0, C bsr 256 =:= 0 ->
- Min = min_band(A, B, C, D),
- Max = max_band(A, B, C, D),
- {Min,Max};
-'band'(_, {C,D}) when C >= 0 ->
- {0,D};
-'band'({A,B}, _) when A >= 0 ->
- {0,B};
-'band'(_, _) ->
- any.
-
--spec 'bor'(range(), range()) -> range_result().
-
-'bor'({A,B}, {C,D}) when A >= 0, A bsr 256 =:= 0, C >= 0, C bsr 256 =:= 0 ->
- Min = min_bor(A, B, C, D),
- Max = max_bor(A, B, C, D),
- {Min,Max};
-'bor'(_, _) ->
- any.
-
--spec 'bxor'(range(), range()) -> range_result().
-
-'bxor'({A,B}, {C,D}) when A >= 0, A bsr 256 =:= 0, C >= 0, C bsr 256 =:= 0 ->
- Max = max_bxor(A, B, C, D),
- {0,Max};
-'bxor'(_, _) ->
- any.
-
--spec 'bsr'(range(), range()) -> range_result().
-
-'bsr'({A,B}, {C,D}) when C >= 0 ->
- Min = min(A bsr C, A bsr D),
- Max = max(B bsr C, B bsr D),
- {Min,Max};
-'bsr'(_, _) ->
- any.
-
--spec 'bsl'(range(), range()) -> range_result().
-
-'bsl'({A,B}, {C,D}) when abs(B) bsr 128 =:= 0, C >= 0, D < 128 ->
- Min = min(A bsl C, A bsl D),
- Max = max(B bsl C, B bsl D),
- {Min,Max};
-'bsl'(_, _) ->
- any.
-
--spec relop(relop(), range(), range()) -> bool_result().
-
-relop(Op, {A,B}, {C,D}) ->
- case {erlang:Op(B, C),erlang:Op(A, D)} of
- {Bool,Bool} -> Bool;
- {_,_} -> 'maybe'
- end;
-relop(_, _, _) ->
- 'maybe'.
-
-%%%
-%%% Internal functions.
-%%%
-
-verify_range({Min,Max}=T) when Min =< Max -> T.
-
min_band(A, B, C, D) ->
M = 1 bsl (upper_bit(A bor C) + 1),
min_band(A, B, C, D, M).
@@ -299,3 +478,92 @@ upper_bit_1(Val0, N) ->
0 -> N;
Val -> upper_bit_1(Val, N + 1)
end.
+
+infer_relop_types_1('<', {A,B}, {C,D}) ->
+ Left = normalize({A, clamp(inf_add(D, -1), A, B)}),
+ Right = normalize({clamp(inf_add(A, 1), C, D), D}),
+ {Left,Right};
+infer_relop_types_1('=<', {A,B}, {C,D}) ->
+ Left = normalize({A, clamp(D, A, B)}),
+ Right = normalize({clamp(A, C, D), D}),
+ {Left,Right};
+infer_relop_types_1('>=', {A,B}, {C,D}) ->
+ Left = normalize({clamp(C, A, B), B}),
+ Right = normalize({C, clamp(B, C, D)}),
+ {Left,Right};
+infer_relop_types_1('>', {A,B}, {C,D}) ->
+ Left = normalize({clamp(inf_add(C, 1), A, B), B}),
+ Right = normalize({C,clamp(inf_add(B, -1), C, D)}),
+ {Left,Right}.
+
+%%%
+%%% Handling of ranges.
+%%%
+%%% A range can begin with '-inf' OR end with '+inf'.
+%%%
+%%% Atoms are greater than all integers. Therefore, we don't
+%%% need any special handling of '+inf'.
+%%%
+
+normalize({'-inf','-inf'}) ->
+ {'-inf',-1};
+normalize({'-inf','+inf'}) ->
+ any;
+normalize({'+inf','+inf'}) ->
+ {0,'+inf'};
+normalize({Min,Max}=T) ->
+ true = inf_ge(Max, Min),
+ T.
+
+clamp(V, A, B) ->
+ inf_min(inf_max(V, A), B).
+
+inf_min(A, B) when A =:= '-inf'; B =:= '-inf' -> '-inf';
+inf_min(A, B) when A =< B -> A;
+inf_min(A, B) when A > B -> B.
+
+inf_max('-inf', B) -> B;
+inf_max(A, '-inf') -> A;
+inf_max(A, B) when A >= B -> A;
+inf_max(A, B) when A < B -> B.
+
+inf_neg('-inf') -> '+inf';
+inf_neg('+inf') -> '-inf';
+inf_neg(N) -> -N.
+
+inf_add(Int, N) when is_integer(Int) -> Int + N;
+inf_add(Inf, _N) -> Inf.
+
+inf_bsr('-inf', _S) ->
+ '-inf';
+inf_bsr('+inf', _S) ->
+ '+inf';
+inf_bsr(N, S0) when S0 =:= '-inf'; S0 < 0 ->
+ S = inf_neg(S0),
+ if
+ S >= ?NUM_BITS, N < 0 -> '-inf';
+ S >= ?NUM_BITS, N >= 0 -> '+inf';
+ true -> N bsl S
+ end;
+inf_bsr(N, '+inf') ->
+ if
+ N < 0 -> -1;
+ N >= 0 -> 0
+ end;
+inf_bsr(N, S) when S >= 0 ->
+ N bsr S.
+
+inf_bsl(N, S) ->
+ inf_bsr(N, inf_neg(S)).
+
+inf_lt(_, '-inf') -> false;
+inf_lt('-inf', _) -> true;
+inf_lt(A, B) -> A < B.
+
+inf_ge(_, '-inf') -> true;
+inf_ge('-inf', _) -> false;
+inf_ge(A, B) -> A >= B.
+
+inf_le(A, B) -> inf_ge(B, A).
+
+inf_gt(A, B) -> inf_lt(B, A).
diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl
index bd1d672d4a..4324080098 100644
--- a/lib/compiler/src/beam_call_types.erl
+++ b/lib/compiler/src/beam_call_types.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -27,6 +27,17 @@
-export([will_succeed/3, types/3, arith_type/2]).
%%
+%% Define an upper limit for functions that return sizes of data
+%% structures. The chosen value is about half the maxium size of a
+%% smallnum. That means that adding a small constant to it will result
+%% in a smallnum, but still the value is still sufficiently high to
+%% make it impossible to reach in the foreseeable future.
+%%
+-define(SIZE_UPPER_LIMIT, ((1 bsl 58) - 1)).
+
+-define(UNICODE_TYPE, #t_integer{elements={0,16#10FFFF}}).
+
+%%
%% Returns whether a call will succeed or not.
%%
%% Notes:
@@ -42,23 +53,47 @@
-spec will_succeed(Mod, Func, ArgTypes) -> Result when
Mod :: atom(),
Func :: atom(),
- ArgTypes :: [normal_type()],
+ ArgTypes :: [type()],
Result :: 'yes' | 'no' | 'maybe'.
-will_succeed(erlang, Op, [LHS, RHS])
- when Op =:= '+'; Op =:= '-'; Op =:= '*' ->
+will_succeed(erlang, Op, [LHS, RHS]) when Op =:= '+';
+ Op =:= '-';
+ Op =:= '*' ->
succeeds_if_smallish(LHS, RHS);
-will_succeed(erlang, Op, [#t_integer{elements={_,_}},
- #t_integer{elements={Div,_}}])
- when (Op =:= 'div' orelse Op =:= 'rem'), Div > 0 ->
- yes;
-will_succeed(erlang, 'bsr', [#t_integer{elements={_,_}},
- #t_integer{elements={S,_}}])
- when S >= 0 ->
- yes;
-will_succeed(erlang, 'bsl', [#t_integer{}=LHS,#t_integer{elements={S,_}}])
- when S < 64 ->
- succeeds_if_smallish(LHS);
+will_succeed(erlang, Op, [LHS, RHS]=Args) when Op =:= 'div';
+ Op =:= 'rem' ->
+ case {meet(LHS, #t_integer{}), meet(RHS, #t_integer{})} of
+ {#t_integer{elements={_,_}}=LHS,
+ #t_integer{elements={Min,Max}}=RHS}
+ when is_integer(Min), Min > 0;
+ is_integer(Max), Max < -1 ->
+ 'yes';
+ {#t_integer{}, #t_integer{}} ->
+ fails_on_conflict(erlang, Op, Args);
+ {_, _} ->
+ no
+ end;
+will_succeed(erlang, 'bsr'=Op, [LHS, RHS]=Args) ->
+ case {meet(LHS, #t_integer{}), meet(RHS, #t_integer{})} of
+ {#t_integer{elements={_,_}}=LHS,
+ #t_integer{elements={Shift,_}}=RHS}
+ when is_integer(Shift), Shift >= 0 ->
+ 'yes';
+ {#t_integer{}, #t_integer{}} ->
+ fails_on_conflict(erlang, Op, Args);
+ {_, _} ->
+ no
+ end;
+will_succeed(erlang, 'bsl'=Op, [LHS, RHS]=Args) ->
+ case {meet(LHS, #t_integer{}), meet(RHS, #t_integer{})} of
+ {LHS, #t_integer{elements={Shift,_}}=RHS}
+ when is_integer(Shift), Shift < 64 ->
+ succeeds_if_smallish(LHS);
+ {#t_integer{}, #t_integer{}} ->
+ fails_on_conflict(erlang, Op, Args);
+ {_, _} ->
+ no
+ end;
will_succeed(erlang, '++', [LHS, _RHS]) ->
succeeds_if_type(LHS, proper_list());
will_succeed(erlang, '--', [_, _] = Args) ->
@@ -66,54 +101,87 @@ will_succeed(erlang, '--', [_, _] = Args) ->
will_succeed(erlang, BoolOp, [_, _] = Args) when BoolOp =:= 'and';
BoolOp =:= 'or' ->
succeeds_if_types(Args, beam_types:make_boolean());
-will_succeed(erlang, Op, [_, _] = Args) when Op =:= 'band'; Op =:= 'bor'; Op =:= 'bxor' ->
+will_succeed(erlang, Op, [_, _] = Args) when Op =:= 'band';
+ Op =:= 'bor';
+ Op =:= 'bxor' ->
succeeds_if_types(Args, #t_integer{});
will_succeed(erlang, bit_size, [Arg]) ->
succeeds_if_type(Arg, #t_bitstring{});
will_succeed(erlang, byte_size, [Arg]) ->
succeeds_if_type(Arg, #t_bitstring{});
-will_succeed(erlang, element, [Pos, #t_tuple{size=Sz}] = Args) when Sz > 0 ->
- SizeType = #t_integer{elements={1,Sz}},
- case beam_types:meet(Pos, SizeType) of
- Pos ->
- yes;
+will_succeed(erlang, element, [Pos, Tuple]=Args) ->
+ case normalize(Tuple) of
+ #t_tuple{exact=Exact,size=Sz} when Sz >= 1 ->
+ case meet(Pos, #t_integer{elements={1,Sz}}) of
+ Pos -> yes;
+ none when Exact -> no;
+ _ -> fails_on_conflict(erlang, element, Args)
+
+ end;
_ ->
- fails_on_conflict(Args, [#t_integer{}, #t_tuple{}])
+ fails_on_conflict(erlang, element, Args)
end;
will_succeed(erlang, hd, [Arg]) ->
succeeds_if_type(Arg, #t_cons{});
-will_succeed(erlang, is_function, [_,#t_integer{elements={Min,_}}])
- when Min >= 0 ->
- yes;
+will_succeed(erlang, is_function, [_, Arity]=Args) ->
+ case meet(Arity, #t_integer{}) of
+ #t_integer{elements={Min,_}}=Arity when is_integer(Min), Min >= 0 ->
+ yes;
+ #t_integer{} ->
+ fails_on_conflict(erlang, is_function, Args);
+ _ ->
+ no
+ end;
will_succeed(erlang, is_map_key, [_Key, Map]) ->
succeeds_if_type(Map, #t_map{});
will_succeed(erlang, length, [Arg]) ->
succeeds_if_type(Arg, proper_list());
will_succeed(erlang, map_size, [Arg]) ->
succeeds_if_type(Arg, #t_map{});
+will_succeed(erlang, node, [Arg]) ->
+ succeeds_if_type(Arg, identifier);
+will_succeed(erlang, 'and', [_, _]=Args) ->
+ succeeds_if_types(Args, beam_types:make_boolean());
will_succeed(erlang, 'not', [Arg]) ->
succeeds_if_type(Arg, beam_types:make_boolean());
-will_succeed(erlang, setelement, [#t_integer{elements={Min,Max}},
- #t_tuple{exact=Exact,size=Size}, _]) ->
- if
- 1 =< Min, Max =< Size ->
- %% The index is always in range.
- yes;
- Max < 1; Exact, Size < Min ->
- %% The index is always out of range.
+will_succeed(erlang, 'or', [_, _]=Args) ->
+ succeeds_if_types(Args, beam_types:make_boolean());
+will_succeed(erlang, 'xor', [_, _]=Args) ->
+ succeeds_if_types(Args, beam_types:make_boolean());
+will_succeed(erlang, setelement, [Pos, Tuple0, _Value]=Args) ->
+ PosRange = #t_integer{elements={1,?MAX_TUPLE_SIZE}},
+ case {meet(Pos, PosRange), meet(Tuple0, #t_tuple{size=1})} of
+ {none, _} ->
no;
- true ->
- 'maybe'
+ {_, none} ->
+ no;
+ {#t_integer{elements={Min,Max}}=Pos, Tuple} ->
+ MaxTupleSize = max_tuple_size(Tuple),
+ if
+ MaxTupleSize < Min ->
+ %% Index is always out of range.
+ no;
+ Tuple0 =:= Tuple, Max =< MaxTupleSize ->
+ %% We always have a tuple, and the index is always in
+ %% range.
+ yes;
+ true ->
+ %% We may or may not have a tuple, and the index may or may
+ %% not be in range if we do.
+ fails_on_conflict(erlang, setelement, Args)
+ end;
+ {_, _} ->
+ fails_on_conflict(erlang, setelement, Args)
end;
will_succeed(erlang, size, [Arg]) ->
- ArgType = beam_types:join(#t_tuple{}, #t_bitstring{}),
+ ArgType = join(#t_tuple{}, #t_bitstring{}),
succeeds_if_type(Arg, ArgType);
will_succeed(erlang, tuple_size, [Arg]) ->
succeeds_if_type(Arg, #t_tuple{});
will_succeed(erlang, tl, [Arg]) ->
succeeds_if_type(Arg, #t_cons{});
will_succeed(erlang, raise, [Class, _Reason, nil]) ->
- case beam_types:meet(Class, #t_atom{elements=[error,exit,throw]}) of
+ case meet(Class, #t_atom{elements=[error,exit,throw]}) of
Class -> no;
none -> yes;
_ -> 'maybe'
@@ -128,23 +196,36 @@ will_succeed(Mod, Func, Args) ->
true ->
no;
false ->
- %% While we can't infer success for functions outside the
- %% 'erlang' module (see above comment), it's safe to infer
- %% failure when we know they return none or if the
- %% arguments must have certain types.
- case types(Mod, Func, Args) of
- {none, _, _} -> no;
- {_, ArgTypes, _} -> fails_on_conflict(Args, ArgTypes)
- end
+ fails_on_conflict(Mod, Func, Args)
end
end.
-fails_on_conflict([ArgType | Args], [Required | Types]) ->
- case beam_types:meet(ArgType, Required) of
+max_tuple_size(#t_union{tuple_set=[_|_]=Set}=Union) ->
+ Union = meet(Union, #t_tuple{}), %Assertion.
+ Arities = [Arity || {{Arity, _Tag}, _Record} <- Set],
+ lists:max(Arities);
+max_tuple_size(#t_tuple{exact=true,size=Size}) ->
+ Size;
+max_tuple_size(#t_tuple{exact=false}) ->
+ ?MAX_TUPLE_SIZE.
+
+%% While we can't infer success for functions outside the 'erlang'
+%% module, it's safe to infer failure when we know they return `none` or
+%% if the arguments must have certain types.
+%%
+%% Returns: 'maybe' or 'no'
+fails_on_conflict(Mod, Func, Args) ->
+ case types(Mod, Func, Args) of
+ {none, _, _} -> no;
+ {_, ArgTypes, _} -> fails_on_conflict_1(Args, ArgTypes)
+ end.
+
+fails_on_conflict_1([ArgType | Args], [Required | Types]) ->
+ case meet(ArgType, Required) of
none -> no;
- _ -> fails_on_conflict(Args, Types)
+ _ -> fails_on_conflict_1(Args, Types)
end;
-fails_on_conflict([], []) ->
+fails_on_conflict_1([], []) ->
'maybe'.
succeeds_if_types([LHS, RHS], Required) ->
@@ -157,7 +238,7 @@ succeeds_if_types([LHS, RHS], Required) ->
end.
succeeds_if_type(ArgType, Required) ->
- case beam_types:meet(ArgType, Required) of
+ case meet(ArgType, Required) of
ArgType -> yes;
none -> no;
_ -> 'maybe'
@@ -167,7 +248,7 @@ succeeds_if_smallish(#t_integer{elements={Min,Max}})
when abs(Min) bsr 128 =:= 0, abs(Max) bsr 128 =:= 0 ->
yes;
succeeds_if_smallish(ArgType) ->
- case succeeds_if_type(ArgType, number) of
+ case succeeds_if_type(ArgType, #t_number{}) of
yes ->
%% Could potentially fail with a `system_limit` exception.
'maybe';
@@ -185,20 +266,6 @@ succeeds_if_smallish(LHS, RHS) ->
end.
%%
-%% Define an upper limit for functions that return sizes of data
-%% structures. The chosen value is about half the maxium size of a
-%% smallnum. That means that adding a small constant to it will result
-%% in a smallnum, but still the value is still sufficiently high to
-%% make it impossible to reach in the foreseeable future.
-%%
--define(SIZE_UPPER_LIMIT, ((1 bsl 58) - 1)).
-
-%%
-%% The document maximum size of a tuple.
-%%
--define(MAX_TUPLE_SIZE, (1 bsl 24) - 1).
-
-%%
%% Returns the inferred return and argument types for known functions, and
%% whether it's safe to subtract argument types on failure.
%%
@@ -221,7 +288,7 @@ succeeds_if_smallish(LHS, RHS) ->
types(erlang, 'map_size', [_]) ->
sub_safe(#t_integer{elements={0,?SIZE_UPPER_LIMIT}}, [#t_map{}]);
types(erlang, 'tuple_size', [Src]) ->
- Min = case Src of
+ Min = case normalize(meet(Src, #t_tuple{})) of
#t_tuple{size=Sz} -> Sz;
_ -> 0
end,
@@ -259,10 +326,10 @@ types(erlang, 'xor', [_,_]) ->
sub_unsafe(Bool, [Bool, Bool]);
%% Relational operators.
-types(erlang, Op, [#t_integer{elements=Range1},
- #t_integer{elements=Range2}])
+types(erlang, Op, [Arg1, Arg2])
when Op =:= '<'; Op =:= '=<'; Op =:= '>='; Op =:= '>' ->
- case beam_bounds:relop(Op, Range1, Range2) of
+ {R1,R2} = {get_range(Arg1), get_range(Arg2)},
+ case beam_bounds:relop(Op, R1, R2) of
'maybe' ->
sub_unsafe(beam_types:make_boolean(), [any, any]);
Bool when is_boolean(Bool) ->
@@ -281,7 +348,7 @@ types(erlang, is_boolean, [Type]) ->
true ->
sub_unsafe(#t_atom{elements=[true]}, [any]);
false ->
- case beam_types:meet(Type, #t_atom{}) of
+ case meet(Type, #t_atom{}) of
#t_atom{elements=[_|_]=Es} ->
case any(fun is_boolean/1, Es) of
true ->
@@ -297,14 +364,30 @@ types(erlang, is_boolean, [Type]) ->
end;
types(erlang, is_float, [Type]) ->
sub_unsafe_type_test(Type, #t_float{});
-types(erlang, is_function, [Type, #t_integer{elements={Arity,Arity}}])
- when Arity >= 0, Arity =< ?MAX_FUNC_ARGS ->
- RetType =
- case beam_types:meet(Type, #t_fun{arity=Arity}) of
- Type -> #t_atom{elements=[true]};
- none -> #t_atom{elements=[false]};
- _ -> beam_types:make_boolean()
- end,
+types(erlang, is_function, [Type, ArityType]) ->
+ RetType = case meet(ArityType, #t_integer{}) of
+ none ->
+ none;
+ #t_integer{elements={Arity,Arity}}
+ when is_integer(Arity) ->
+ if
+ Arity < 0 ->
+ none;
+ 0 =< Arity, Arity =< ?MAX_FUNC_ARGS ->
+ case meet(Type, #t_fun{arity=Arity}) of
+ Type -> #t_atom{elements=[true]};
+ none -> #t_atom{elements=[false]};
+ _ -> beam_types:make_boolean()
+ end;
+ Arity > ?MAX_FUNC_ARGS ->
+ #t_atom{elements=[false]}
+ end;
+ #t_integer{} ->
+ case meet(Type, #t_fun{}) of
+ none -> #t_atom{elements=[false]};
+ _ -> beam_types:make_boolean()
+ end
+ end,
sub_unsafe(RetType, [any, any]);
types(erlang, is_function, [Type]) ->
sub_unsafe_type_test(Type, #t_fun{});
@@ -315,7 +398,7 @@ types(erlang, is_list, [Type]) ->
types(erlang, is_map, [Type]) ->
sub_unsafe_type_test(Type, #t_map{});
types(erlang, is_number, [Type]) ->
- sub_unsafe_type_test(Type, number);
+ sub_unsafe_type_test(Type, #t_number{});
types(erlang, is_pid, [Type]) ->
sub_unsafe_type_test(Type, pid);
types(erlang, is_port, [Type]) ->
@@ -327,68 +410,113 @@ types(erlang, is_tuple, [Type]) ->
%% Bitwise ops
types(erlang, 'band', [_,_]=Args) ->
- sub_unsafe(erlang_band_type(Args), [#t_integer{}, #t_integer{}]);
+ sub_unsafe(beam_bounds_type('band', #t_integer{}, Args),
+ [#t_integer{}, #t_integer{}]);
types(erlang, 'bor', [_,_]=Args) ->
- sub_unsafe(erlang_bor_type(Args), [#t_integer{}, #t_integer{}]);
-types(erlang, 'bxor', [_,_]) ->
- sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
-types(erlang, 'bsl', [_,_]) ->
- sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+ sub_unsafe(beam_bounds_type('bor', #t_integer{}, Args),
+ [#t_integer{}, #t_integer{}]);
+types(erlang, 'bxor', [_,_]=Args) ->
+ sub_unsafe(beam_bounds_type('bxor', #t_integer{}, Args),
+ [#t_integer{}, #t_integer{}]);
+types(erlang, 'bsl', [_,_]=Args) ->
+ sub_unsafe(beam_bounds_type('bsl', #t_integer{}, Args),
+ [#t_integer{}, #t_integer{}]);
types(erlang, 'bsr', [_,_]=Args) ->
- sub_unsafe(erlang_bsr_type(Args), [#t_integer{}, #t_integer{}]);
-types(erlang, 'bnot', [_]) ->
- sub_unsafe(#t_integer{}, [#t_integer{}]);
+ sub_unsafe(beam_bounds_type('bsr', #t_integer{}, Args),
+ [#t_integer{}, #t_integer{}]);
+types(erlang, 'bnot', [_]=Args) ->
+ sub_unsafe(beam_bounds_type('bnot', #t_integer{}, Args),
+ [#t_integer{}]);
%% Fixed-type arithmetic
types(erlang, 'float', [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(erlang, 'round', [_]) ->
- sub_unsafe(#t_integer{}, [number]);
+ sub_unsafe(#t_integer{}, [#t_number{}]);
types(erlang, 'floor', [_]) ->
- sub_unsafe(#t_integer{}, [number]);
+ sub_unsafe(#t_integer{}, [#t_number{}]);
types(erlang, 'ceil', [_]) ->
- sub_unsafe(#t_integer{}, [number]);
+ sub_unsafe(#t_integer{}, [#t_number{}]);
types(erlang, 'trunc', [_]) ->
- sub_unsafe(#t_integer{}, [number]);
+ sub_unsafe(#t_integer{}, [#t_number{}]);
types(erlang, '/', [_,_]) ->
- sub_unsafe(#t_float{}, [number, number]);
+ sub_unsafe(#t_float{}, [#t_number{}, #t_number{}]);
types(erlang, 'div', [_,_]=Args) ->
- ArgTypes = [#t_integer{}, #t_integer{}],
- sub_unsafe(erlang_div_type(Args), ArgTypes);
+ sub_unsafe(beam_bounds_type('div', #t_integer{}, Args),
+ [#t_integer{}, #t_integer{}]);
types(erlang, 'rem', Args) ->
- ArgTypes = [#t_integer{}, #t_integer{}],
- sub_unsafe(erlang_rem_type(Args), ArgTypes);
+ sub_unsafe(beam_bounds_type('rem', #t_integer{}, Args),
+ [#t_integer{}, #t_integer{}]);
+
+%% Some mixed-type arithmetic.
+types(erlang, Op, [LHS, RHS]) when Op =:= '+'; Op =:= '-' ->
+ case get_range(LHS, RHS, #t_number{}) of
+ {Type, {A,B}, {C,_D}} when is_integer(C), C >= 0 ->
+ R = beam_bounds:bounds(Op, {A,B}, {C,'+inf'}),
+ RetType = case Type of
+ integer -> #t_integer{elements=R};
+ number -> #t_number{elements=R}
+ end,
+ sub_unsafe(RetType, [#t_number{}, #t_number{}]);
+ {Type, {A,_B}, {C,D}} when Op =:= '+', is_integer(A), A >= 0 ->
+ R = beam_bounds:bounds(Op, {A,'+inf'}, {C,D}),
+ RetType = case Type of
+ integer -> #t_integer{elements=R};
+ number -> #t_number{elements=R}
+ end,
+ sub_unsafe(RetType, [#t_number{}, #t_number{}]);
+ _ ->
+ mixed_arith_types([LHS, RHS])
+ end;
-%% Mixed-type arithmetic; '+'/2 and friends are handled in the catch-all
+types(erlang, abs, [Type]) ->
+ case meet(Type, #t_number{}) of
+ #t_float{} ->
+ sub_unsafe(#t_float{}, [#t_float{}]);
+ #t_integer{elements=R} ->
+ RetType = #t_integer{elements=beam_bounds:bounds(abs, R)},
+ sub_unsafe(RetType, [#t_integer{}]);
+ #t_number{elements=R} ->
+ RetType = #t_number{elements=beam_bounds:bounds(abs, R)},
+ sub_unsafe(RetType, [#t_number{}]);
+ _ ->
+ sub_unsafe(#t_number{}, [#t_number{}])
+ end;
+
+%% The rest of the mixed-type arithmetic is handled in the catch-all
%% clause for the 'erlang' module.
-types(erlang, 'abs', [_]=Args) ->
- mixed_arith_types(Args);
%% List operations
types(erlang, '++', [LHS, RHS]) ->
%% `[] ++ RHS` yields RHS, even if RHS is not a list.
ListType = copy_list(LHS, same_length, proper),
- RetType = beam_types:join(ListType, RHS),
+ RetType = join(ListType, RHS),
sub_unsafe(RetType, [proper_list(), any]);
types(erlang, '--', [LHS, _]) ->
RetType = copy_list(LHS, new_length, proper),
sub_unsafe(RetType, [proper_list(), proper_list()]);
+types(erlang, atom_to_list, [_]) ->
+ sub_unsafe(proper_list(?UNICODE_TYPE), [#t_atom{}]);
types(erlang, 'iolist_to_binary', [_]) ->
%% Arg is an iodata(), despite its name.
- ArgType = beam_types:join(#t_list{}, #t_bitstring{size_unit=8}),
+ ArgType = join(#t_list{}, #t_bitstring{size_unit=8}),
sub_unsafe(#t_bitstring{size_unit=8}, [ArgType]);
types(erlang, 'iolist_size', [_]) ->
%% Arg is an iodata(), despite its name. The size is NOT limited
%% by the size of memory.
- ArgType = beam_types:join(#t_list{}, #t_bitstring{size_unit=8}),
- sub_unsafe(#t_integer{}, [ArgType]);
+ ArgType = join(#t_list{}, #t_bitstring{size_unit=8}),
+ sub_unsafe(#t_integer{elements={0,'+inf'}}, [ArgType]);
types(erlang, 'list_to_binary', [_]) ->
%% Arg is an iolist(), despite its name.
sub_unsafe(#t_bitstring{size_unit=8}, [#t_list{}]);
types(erlang, 'list_to_bitstring', [_]) ->
%% As list_to_binary but with bitstrings rather than binaries.
- sub_unsafe(#t_bitstring{}, [proper_list()]);
+ sub_unsafe(#t_bitstring{}, [#t_list{}]);
+types(erlang, list_to_integer, [_]) ->
+ sub_unsafe(#t_integer{}, [proper_cons()]);
+types(erlang, list_to_integer, [_, _]) ->
+ sub_unsafe(#t_integer{}, [proper_cons(), #t_integer{}]);
%% Process operations
types(erlang, alias, []) ->
@@ -439,84 +567,78 @@ types(erlang, 'map_get', [Key, Map]) ->
RetType = erlang_map_get_type(Key, Map),
sub_unsafe(RetType, [any, #t_map{}]);
types(erlang, 'node', [_]) ->
- sub_unsafe(#t_atom{}, [any]);
+ sub_unsafe(#t_atom{}, [identifier]);
types(erlang, 'node', []) ->
sub_unsafe(#t_atom{}, []);
+types(erlang, self, []) ->
+ sub_unsafe(pid, []);
types(erlang, 'size', [_]) ->
- ArgType = beam_types:join(#t_tuple{}, #t_bitstring{}),
+ ArgType = join(#t_tuple{}, #t_bitstring{}),
sub_unsafe(#t_integer{}, [ArgType]);
%% Tuple element ops
-types(erlang, element, [PosType, TupleType]) ->
- Index = case PosType of
- #t_integer{elements={Same,Same}} when is_integer(Same) ->
- Same;
- _ ->
- 0
- end,
-
- RetType = case TupleType of
- #t_tuple{size=Sz,elements=Es} when Index =< Sz,
- Index >= 1 ->
- beam_types:get_tuple_element(Index, Es);
- _ ->
- any
- end,
-
- ArgTypes = [#t_integer{elements={1,?MAX_TUPLE_SIZE}},
- #t_tuple{size=Index}],
-
- sub_unsafe(RetType, ArgTypes);
+types(erlang, element, [Pos, Tuple0]) ->
+ PosRange = #t_integer{elements={1,?MAX_TUPLE_SIZE}},
+ case meet(Pos, PosRange) of
+ #t_integer{elements={Index,Index}} when Index >= 1 ->
+ case normalize(meet(Tuple0, #t_tuple{size=Index})) of
+ #t_tuple{elements=Es}=Tuple ->
+ RetType = beam_types:get_tuple_element(Index, Es),
+ sub_unsafe(RetType, [PosRange, Tuple]);
+ none ->
+ sub_unsafe(none, [PosRange, #t_tuple{}])
+ end;
+ _ ->
+ sub_unsafe(any, [PosRange, #t_tuple{}])
+ end;
types(erlang, setelement, [PosType, TupleType, ArgType]) ->
- RetType = case {PosType,TupleType} of
- {#t_integer{elements={Index,Index}},
- #t_tuple{elements=Es0,size=Size}=T} when Index >= 1 ->
- %% This is an exact index, update the type of said
- %% element or return 'none' if it's known to be out of
- %% bounds.
- Es = beam_types:set_tuple_element(Index, ArgType, Es0),
- case T#t_tuple.exact of
- false ->
- T#t_tuple{size=max(Index, Size),elements=Es};
- true when Index =< Size ->
- T#t_tuple{elements=Es};
- true ->
- none
- end;
- {#t_integer{elements={Min,Max}},
- #t_tuple{elements=Es0,size=Size}=T} when Min >= 1 ->
- %% We know this will land between Min and Max, so kill
- %% the types for those indexes.
- Es = discard_tuple_element_info(Min, Max, Es0),
- case T#t_tuple.exact of
- false ->
- T#t_tuple{elements=Es,size=max(Min, Size)};
- true when Min =< Size ->
- T#t_tuple{elements=Es,size=Size};
- true ->
- none
+ PosRange = #t_integer{elements={1,?MAX_TUPLE_SIZE}},
+ RetType = case meet(PosType, PosRange) of
+ #t_integer{elements={Same,Same}} ->
+ beam_types:update_tuple(TupleType, [{Same, ArgType}]);
+ #t_integer{} ->
+ case normalize(meet(TupleType, #t_tuple{size=1})) of
+ #t_tuple{}=T -> T#t_tuple{elements=#{}};
+ none -> none
end;
- {_,#t_tuple{}=T} ->
- %% Position unknown, so we have to discard all element
- %% information.
- T#t_tuple{elements=#{}};
- {#t_integer{elements={Min,_Max}},_} ->
- #t_tuple{size=Min};
- {_,_} ->
- #t_tuple{}
+ none ->
+ none
end,
- sub_unsafe(RetType, [#t_integer{}, #t_tuple{}, any]);
+ sub_unsafe(RetType, [PosRange, #t_tuple{size=1}, any]);
types(erlang, make_fun, [_,_,Arity0]) ->
- Type = case Arity0 of
+ Type = case meet(Arity0, #t_integer{}) of
#t_integer{elements={Arity,Arity}}
when Arity >= 0, Arity =< ?MAX_FUNC_ARGS ->
#t_fun{arity=Arity};
+ #t_integer{} ->
+ #t_fun{};
_ ->
- #t_fun{}
+ none
end,
sub_unsafe(Type, [#t_atom{}, #t_atom{}, #t_integer{}]);
+types(erlang, Op, [LHS,RHS]) when Op =:= min; Op =:= max ->
+ R1 = get_range(LHS),
+ R2 = get_range(RHS),
+ R = beam_bounds:bounds(Op, R1, R2),
+
+ %% We cannot use mixed_arith_types/1 here as we will return either argument
+ %% unchanged. The result will not be converted to a float if one of the
+ %% arguments is a float.
+ %%
+ %% 1235.0 = 1 + 1234.0
+ %% 1 = erlang:min(1, 1234.0)
+ RetType = case {LHS, RHS} of
+ {#t_integer{}, #t_integer{}} -> #t_integer{elements=R};
+ {#t_integer{}, #t_number{}} -> #t_number{elements=R};
+ {#t_number{}, #t_integer{}} -> #t_number{elements=R};
+ {#t_number{}, #t_number{}} -> #t_number{elements=R};
+ {_, _} -> join(LHS, RHS)
+ end,
+
+ sub_unsafe(RetType, [any, any]);
+
types(erlang, Name, Args) ->
Arity = length(Args),
@@ -546,53 +668,53 @@ types(erlang, Name, Args) ->
%%
types(math, cos, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, cosh, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, sin, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, sinh, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, tan, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, tanh, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, acos, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, acosh, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, asin, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, asinh, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, atan, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, atanh, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, erf, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, erfc, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, exp, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, log, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, log2, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, log10, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, sqrt, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, atan2, [_,_]) ->
- sub_unsafe(#t_float{}, [number, number]);
+ sub_unsafe(#t_float{}, [#t_number{}, #t_number{}]);
types(math, pow, [_,_]) ->
- sub_unsafe(#t_float{}, [number, number]);
+ sub_unsafe(#t_float{}, [#t_number{}, #t_number{}]);
types(math, ceil, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, floor, [_]) ->
- sub_unsafe(#t_float{}, [number]);
+ sub_unsafe(#t_float{}, [#t_number{}]);
types(math, fmod, [_,_]) ->
- sub_unsafe(#t_float{}, [number, number]);
+ sub_unsafe(#t_float{}, [#t_number{}, #t_number{}]);
types(math, pi, []) ->
sub_unsafe(#t_float{}, []);
@@ -620,11 +742,13 @@ types(lists, subtract, [_,_]=Args) ->
%% Functions returning booleans.
types(lists, all, [_,_]) ->
%% This can succeed on improper lists if the fun returns 'false' for an
- %% element before reaching the end.
- sub_unsafe(beam_types:make_boolean(), [#t_fun{arity=1}, #t_list{}]);
+ %% element before reaching the end. It can also return 'true' for the
+ %% empty list, which means we cannot assume the predicate is a fun.
+ sub_unsafe(beam_types:make_boolean(), [any, #t_list{}]);
types(lists, any, [_,_]) ->
- %% Doesn't imply that the argument is a proper list; see lists:all/2
- sub_unsafe(beam_types:make_boolean(), [#t_fun{arity=1}, #t_list{}]);
+ %% Doesn't imply that the argument is a proper list, nor that the fun is
+ %% valid; see lists:all/2
+ sub_unsafe(beam_types:make_boolean(), [any, #t_list{}]);
types(lists, keymember, [_,_,_]) ->
%% Doesn't imply that the argument is a proper list; see lists:all/2
sub_unsafe(beam_types:make_boolean(), [any, #t_integer{}, #t_list{}]);
@@ -643,10 +767,10 @@ types(lists, suffix, [_,_]) ->
%% Simple folds
types(lists, foldl, [Fun, Init, List]) ->
RetType = lists_fold_type(Fun, Init, List),
- sub_unsafe(RetType, [#t_fun{arity=2}, any, proper_list()]);
+ sub_unsafe(RetType, [any, any, proper_list()]);
types(lists, foldr, [Fun, Init, List]) ->
RetType = lists_fold_type(Fun, Init, List),
- sub_unsafe(RetType, [#t_fun{arity=2}, any, proper_list()]);
+ sub_unsafe(RetType, [any, any, proper_list()]);
%% Functions returning plain lists.
types(lists, droplast, [List]) ->
@@ -656,17 +780,17 @@ types(lists, dropwhile, [_Fun, List]) ->
%% If the element is found before the end of the list, we could return an
%% improper list.
RetType = copy_list(List, new_length, maybe_improper),
- sub_unsafe(RetType, [#t_fun{arity=1}, #t_list{}]);
+ sub_unsafe(RetType, [any, #t_list{}]);
types(lists, duplicate, [_Count, Element]) ->
sub_unsafe(proper_list(Element), [#t_integer{}, any]);
types(lists, filter, [_Fun, List]) ->
RetType = copy_list(List, new_length, proper),
- sub_unsafe(RetType, [#t_fun{arity=1}, proper_list()]);
+ sub_unsafe(RetType, [any, proper_list()]);
types(lists, flatten, [_]) ->
sub_unsafe(proper_list(), [proper_list()]);
types(lists, map, [Fun, List]) ->
RetType = lists_map_type(Fun, List),
- sub_unsafe(RetType, [#t_fun{arity=1}, proper_list()]);
+ sub_unsafe(RetType, [any, proper_list()]);
types(lists, reverse, [List]) ->
RetType = copy_list(List, same_length, proper),
sub_unsafe(RetType, [proper_list()]);
@@ -674,9 +798,10 @@ types(lists, sort, [List]) ->
RetType = copy_list(List, same_length, proper),
sub_unsafe(RetType, [proper_list()]);
types(lists, takewhile, [_Fun, List]) ->
- %% Doesn't imply that the argument is a proper list; see lists:all/2
+ %% Doesn't imply that the argument is a proper list, nor that the fun is
+ %% valid; see lists:all/2
RetType = copy_list(List, new_length, proper),
- sub_unsafe(RetType, [#t_fun{arity=1}, #t_list{}]);
+ sub_unsafe(RetType, [any, #t_list{}]);
types(lists, usort, [List]) ->
%% The result is not quite the same length, but a non-empty list will stay
%% non-empty.
@@ -686,41 +811,51 @@ types(lists, zip, [_,_]=Lists) ->
{RetType, ArgType} = lists_zip_types(Lists),
sub_unsafe(RetType, [ArgType, ArgType]);
types(lists, zipwith, [Fun | [_,_]=Lists]) ->
+ %% Doesn't imply that the argument is a fun, as a possible implementation
+ %% could succeed when both lists are empty.
{RetType, ArgType} = lists_zipwith_types(Fun, Lists),
- sub_unsafe(RetType, [#t_fun{arity=2}, ArgType, ArgType]);
+ sub_unsafe(RetType, [any, ArgType, ArgType]);
%% Functions with complex return values.
types(lists, keyfind, [KeyType,PosType,_]) ->
%% Doesn't imply that the argument is a proper list; see lists:all/2
- TupleType = case PosType of
+ TupleType = case meet(PosType, #t_integer{}) of
#t_integer{elements={Index,Index}} when is_integer(Index),
Index >= 1 ->
Es = beam_types:set_tuple_element(Index, KeyType, #{}),
#t_tuple{size=Index,elements=Es};
- _ ->
- #t_tuple{}
+ #t_integer{} ->
+ #t_tuple{};
+ none ->
+ none
end,
- RetType = beam_types:join(TupleType, beam_types:make_atom(false)),
- sub_unsafe(RetType, [any, #t_integer{}, #t_list{}]);
+ RetType = join(TupleType, beam_types:make_atom(false)),
+ sub_unsafe(RetType, [any, any, #t_list{}]);
types(lists, MapFold, [Fun, Init, List])
when MapFold =:= mapfoldl; MapFold =:= mapfoldr ->
RetType = lists_mapfold_type(Fun, Init, List),
- sub_unsafe(RetType, [#t_fun{arity=2}, any, proper_list()]);
+ %% Doesn't imply that the argument is a proper list, nor that the fun is
+ %% valid; see lists:all/2
+ sub_unsafe(RetType, [any, any, proper_list()]);
types(lists, partition, [_Fun, List]) ->
+ %% Doesn't imply that the argument is a proper list, nor that the fun is
+ %% valid; see lists:all/2
ListType = copy_list(List, new_length, proper),
RetType = make_two_tuple(ListType, ListType),
- sub_unsafe(RetType, [#t_fun{arity=1}, proper_list()]);
+ sub_unsafe(RetType, [any, proper_list()]);
types(lists, search, [_,_]) ->
- %% Doesn't imply that the argument is a proper list; see lists:all/2
+ %% Doesn't imply that the argument is a proper list, nor that the fun is
+ %% valid; see lists:all/2
TupleType = make_two_tuple(beam_types:make_atom(value), any),
- RetType = beam_types:join(TupleType, beam_types:make_atom(false)),
- sub_unsafe(RetType, [#t_fun{arity=1}, #t_list{}]);
+ RetType = join(TupleType, beam_types:make_atom(false)),
+ sub_unsafe(RetType, [any, #t_list{}]);
types(lists, splitwith, [_Fun, List]) ->
%% Only the elements in the left list are guaranteed to be visited, so both
- %% the argument and the right list may be improper.
+ %% the argument and the right list may be improper. The fun isn't
+ %% guaranteed to be valid either if the list is empty.
Left = copy_list(List, new_length, proper),
Right = copy_list(List, new_length, maybe_improper),
- sub_unsafe(make_two_tuple(Left, Right), [#t_fun{arity=1}, #t_list{}]);
+ sub_unsafe(make_two_tuple(Left, Right), [any, #t_list{}]);
types(lists, unzip, [List]) ->
RetType = lists_unzip_type(2, List),
sub_unsafe(RetType, [proper_list()]);
@@ -730,12 +865,14 @@ types(lists, unzip, [List]) ->
%%
types(maps, filter, [_Fun, Map]) ->
- %% Conservatively assume that key/value types are unchanged.
- RetType = case Map of
+ %% Conservatively assume that key/value types are unchanged. Note that we
+ %% cannot assume that Fun is a function on success, as a potential
+ %% implementation could short-circuit on the empty map.
+ RetType = case meet(Map, #t_map{}) of
#t_map{}=T -> T;
- _ -> #t_map{}
+ _ -> none
end,
- sub_unsafe(RetType, [#t_fun{arity=2}, #t_map{}]);
+ sub_unsafe(RetType, [any, #t_map{}]);
types(maps, find, [Key, Map]) ->
TupleType = case erlang_map_get_type(Key, Map) of
none ->
@@ -744,36 +881,39 @@ types(maps, find, [Key, Map]) ->
make_two_tuple(beam_types:make_atom(ok), ValueType)
end,
%% error | {ok, Value}
- RetType = beam_types:join(beam_types:make_atom(error), TupleType),
+ RetType = join(beam_types:make_atom(error), TupleType),
sub_unsafe(RetType, [any, #t_map{}]);
types(maps, fold, [Fun, Init, _Map]) ->
- RetType = case Fun of
+ RetType = case meet(Fun, #t_fun{arity=3}) of
#t_fun{type=Type} ->
%% The map is potentially empty, so we have to assume it
%% can return the initial value.
- beam_types:join(Type, Init);
+ join(Type, Init);
_ ->
- any
+ %% A potential implementation could still succeed with a
+ %% non-fun for empty maps.
+ Init
end,
- sub_unsafe(RetType, [#t_fun{arity=3}, any, #t_map{}]);
+ sub_unsafe(RetType, [any, any, #t_map{}]);
types(maps, from_keys, [Keys, Value]) ->
KeyType = erlang_hd_type(Keys),
- RetType = case KeyType of
- none -> #t_map{super_key=none,super_value=none};
- _ -> #t_map{super_key=KeyType,super_value=Value}
- end,
+ ValueType = case KeyType of
+ none -> none;
+ _ -> Value
+ end,
+ RetType = #t_map{super_key=KeyType,super_value=ValueType},
sub_unsafe(RetType, [proper_list(), any]);
types(maps, from_list, [Pairs]) ->
PairType = erlang_hd_type(Pairs),
- RetType = case beam_types:normalize(PairType) of
+ RetType = case normalize(meet(PairType, #t_tuple{exact=true,size=2})) of
#t_tuple{elements=Es} ->
SKey = beam_types:get_tuple_element(1, Es),
SValue = beam_types:get_tuple_element(2, Es),
#t_map{super_key=SKey,super_value=SValue};
- none ->
+ none when PairType =:= none ->
#t_map{super_key=none,super_value=none};
- _ ->
- #t_map{}
+ none when PairType =/= none ->
+ none
end,
sub_unsafe(RetType, [proper_list()]);
types(maps, get, [_Key, _Map]=Args) ->
@@ -781,47 +921,51 @@ types(maps, get, [_Key, _Map]=Args) ->
types(maps, get, [Key, Map, Default]) ->
RetType = case erlang_map_get_type(Key, Map) of
none -> Default;
- ValueType -> beam_types:join(ValueType, Default)
+ ValueType -> join(ValueType, Default)
end,
sub_unsafe(RetType, [any, #t_map{}, any]);
types(maps, keys, [Map]) ->
- RetType = case Map of
+ RetType = case meet(Map, #t_map{}) of
#t_map{super_key=none} -> nil;
#t_map{super_key=SKey} -> proper_list(SKey);
- _ -> proper_list()
+ _ -> none
end,
sub_unsafe(RetType, [#t_map{}]);
-types(maps, map, [Fun, Map]) ->
- RetType = case {Fun, Map} of
- {#t_fun{type=FunRet}, #t_map{super_value=SValue0}} ->
- SValue = beam_types:join(FunRet, SValue0),
+types(maps, map, [Fun, Map0]) ->
+ RetType = case {meet(Fun, #t_fun{arity=2}), meet(Map0, #t_map{})} of
+ {#t_fun{type=FunRet}, #t_map{super_value=SValue0}=Map} ->
+ SValue = join(FunRet, SValue0),
Map#t_map{super_value=SValue};
- _ ->
- #t_map{}
+ {none, #t_map{}} ->
+ %% A potential implementation could still work on empty
+ %% maps even when the fun is broken.
+ #t_map{super_key=none,super_value=none};
+ {_, none} ->
+ none
end,
- sub_unsafe(RetType, [#t_fun{arity=2}, #t_map{}]);
+ sub_unsafe(RetType, [any, #t_map{}]);
types(maps, merge, [A, B]) ->
- RetType = case {A, B} of
+ RetType = case {meet(A, #t_map{}), meet(B, #t_map{})} of
{#t_map{super_key=SKeyA,super_value=SValueA},
#t_map{super_key=SKeyB,super_value=SValueB}} ->
- SKey = beam_types:join(SKeyA, SKeyB),
- SValue = beam_types:join(SValueA, SValueB),
+ SKey = join(SKeyA, SKeyB),
+ SValue = join(SValueA, SValueB),
#t_map{super_key=SKey,super_value=SValue};
_ ->
- #t_map{}
+ none
end,
sub_unsafe(RetType, [#t_map{}, #t_map{}]);
types(maps, new, []) ->
RetType = #t_map{super_key=none,super_value=none},
sub_unsafe(RetType, []);
types(maps, put, [Key, Value, Map]) ->
- RetType = case Map of
+ RetType = case meet(Map, #t_map{}) of
#t_map{super_key=SKey0,super_value=SValue0} ->
- SKey = beam_types:join(Key, SKey0),
- SValue = beam_types:join(Value, SValue0),
+ SKey = join(Key, SKey0),
+ SValue = join(Value, SValue0),
#t_map{super_key=SKey,super_value=SValue};
_ ->
- #t_map{}
+ none
end,
sub_unsafe(RetType, [any, any, #t_map{}]);
types(maps, remove, [Key, Map]) ->
@@ -832,48 +976,49 @@ types(maps, take, [Key, Map]) ->
none ->
none;
ValueType ->
- MapType = beam_types:meet(Map, #t_map{}),
+ MapType = meet(Map, #t_map{}),
make_two_tuple(ValueType, MapType)
end,
%% error | {Value, Map}
- RetType = beam_types:join(beam_types:make_atom(error), TupleType),
+ RetType = join(beam_types:make_atom(error), TupleType),
sub_unsafe(RetType, [any, #t_map{}]);
types(maps, to_list, [Map]) ->
- RetType = case Map of
+ RetType = case meet(Map, #t_map{}) of
#t_map{super_key=SKey,super_value=SValue} ->
proper_list(make_two_tuple(SKey, SValue));
_ ->
- proper_list()
+ none
end,
sub_unsafe(RetType, [#t_map{}]);
-types(maps, update_with, [_Key, Fun, Map]) ->
- RetType = case {Fun, Map} of
- {#t_fun{type=FunRet}, #t_map{super_value=SValue0}} ->
- SValue = beam_types:join(FunRet, SValue0),
+types(maps, update_with, [_Key, Fun, Map0]) ->
+ RetType = case {meet(Fun, #t_fun{arity=1}), meet(Map0, #t_map{})} of
+ {#t_fun{type=FunRet}, #t_map{super_value=SValue0}=Map}
+ when FunRet =/= none ->
+ SValue = join(FunRet, SValue0),
Map#t_map{super_value=SValue};
_ ->
- #t_map{}
+ none
end,
sub_unsafe(RetType, [any, #t_fun{arity=1}, #t_map{}]);
types(maps, values, [Map]) ->
- RetType = case Map of
+ RetType = case meet(Map, #t_map{}) of
#t_map{super_value=none} -> nil;
#t_map{super_value=SValue} -> proper_list(SValue);
- _ -> proper_list()
+ _ -> none
end,
sub_unsafe(RetType, [#t_map{}]);
-types(maps, with, [Keys, Map]) ->
- RetType = case {erlang_hd_type(Keys), Map} of
+types(maps, with, [Keys, Map0]) ->
+ RetType = case {erlang_hd_type(Keys), meet(Map0, #t_map{})} of
{none, _} ->
#t_map{super_key=none,super_value=none};
- {KeysType, #t_map{super_key=SKey0}} ->
+ {KeysType, #t_map{super_key=SKey0}=Map} ->
%% Since we know that the Map will only contain the pairs
%% pointed out by Keys, we can restrict the types to
%% those in the list.
- SKey = beam_types:meet(KeysType, SKey0),
+ SKey = meet(KeysType, SKey0),
Map#t_map{super_key=SKey};
{_, _} ->
- #t_map{}
+ none
end,
sub_unsafe(RetType, [proper_list(), #t_map{}]);
types(maps, without, [Keys, Map]) ->
@@ -887,32 +1032,31 @@ types(_, _, Args) ->
-spec arith_type(Op, ArgTypes) -> RetType when
Op :: beam_ssa:op(),
- ArgTypes :: [normal_type()],
+ ArgTypes :: [type()],
RetType :: type().
-arith_type({bif,'+'}, [#t_integer{elements=Range1},
- #t_integer{elements=Range2}]) ->
- #t_integer{elements=beam_bounds:'+'(Range1, Range2)};
-arith_type({bif,'-'}, [#t_integer{elements=Range1},
- #t_integer{elements=Range2}]) ->
- #t_integer{elements=beam_bounds:'-'(Range1, Range2)};
-arith_type({bif,'*'}, [#t_integer{elements=Range1},
- #t_integer{elements=Range2}]) ->
- #t_integer{elements=beam_bounds:'*'(Range1, Range2)};
-arith_type({bif,'div'}, ArgTypes) ->
- erlang_div_type(ArgTypes);
-arith_type({bif,'rem'}, ArgTypes) ->
- erlang_rem_type(ArgTypes);
-arith_type({bif,'band'}, ArgTypes) ->
- erlang_band_type(ArgTypes);
-arith_type({bif,'bor'}, Args) ->
- erlang_bor_type(Args);
-arith_type({bif,'bxor'}, Args) ->
- erlang_bxor_type(Args);
-arith_type({bif,'bsr'}, Args) ->
- erlang_bsr_type(Args);
-arith_type({bif,'bsl'}, Args) ->
- erlang_bsl_type(Args);
+arith_type({bif,'-'}, [Arg]) ->
+ ArgTypes = [#t_integer{elements={0,0}},Arg],
+ beam_bounds_type('-', #t_number{}, ArgTypes);
+arith_type({bif,'bnot'}, [Arg0]) ->
+ case meet(Arg0, #t_integer{}) of
+ none ->
+ none;
+ #t_integer{elements=R} ->
+ #t_integer{elements=beam_bounds:bounds('bnot', R)}
+ end;
+arith_type({bif,Op}, [_,_]=ArgTypes) when Op =:= '+';
+ Op =:= '-';
+ Op =:= '*' ->
+ beam_bounds_type(Op, #t_number{}, ArgTypes);
+arith_type({bif,Op}, [_,_]=ArgTypes) when Op =:= 'band';
+ Op =:= 'bor';
+ Op =:= 'bsl';
+ Op =:= 'bsr';
+ Op =:= 'bxor';
+ Op =:= 'div';
+ Op =:= 'rem' ->
+ beam_bounds_type(Op, #t_integer{}, ArgTypes);
arith_type(_Op, _Args) ->
any.
@@ -920,107 +1064,107 @@ arith_type(_Op, _Args) ->
%% Function-specific helpers.
%%
-mixed_arith_types([FirstType | _]=Args0) ->
+mixed_arith_types(Args0) ->
+ [FirstType|_] = Args = [meet(A, #t_number{}) || A <- Args0],
RetType = foldl(fun(#t_integer{}, #t_integer{}) -> #t_integer{};
- (#t_integer{}, number) -> number;
+ (#t_integer{}, #t_number{}) -> #t_number{};
(#t_integer{}, #t_float{}) -> #t_float{};
(#t_float{}, #t_integer{}) -> #t_float{};
- (#t_float{}, number) -> #t_float{};
+ (#t_float{}, #t_number{}) -> #t_float{};
(#t_float{}, #t_float{}) -> #t_float{};
- (number, #t_integer{}) -> number;
- (number, #t_float{}) -> #t_float{};
- (number, number) -> number;
- (any, _) -> number;
+ (#t_number{}, #t_integer{}) -> #t_number{};
+ (#t_number{}, #t_float{}) -> #t_float{};
+ (#t_number{}, #t_number{}) -> #t_number{};
(_, _) -> none
- end, FirstType, Args0),
- sub_unsafe(RetType, [number || _ <- Args0]).
+ end, FirstType, Args),
+ sub_unsafe(RetType, [#t_number{} || _ <- Args]).
erlang_hd_type(Src) ->
- case beam_types:meet(Src, #t_cons{}) of
+ case meet(Src, #t_cons{}) of
#t_cons{type=Type} -> Type;
none -> none
end.
erlang_tl_type(Src) ->
- case beam_types:meet(Src, #t_cons{}) of
- #t_cons{terminator=Term}=Cons -> beam_types:join(Cons, Term);
+ case meet(Src, #t_cons{}) of
+ #t_cons{terminator=Term}=Cons -> join(Cons, Term);
none -> none
end.
-erlang_band_type([#t_integer{elements=Range1}, #t_integer{elements=Range2}]) ->
- #t_integer{elements=beam_bounds:'band'(Range1, Range2)};
-erlang_band_type([#t_integer{elements=Range}, _RHS]) ->
- #t_integer{elements=beam_bounds:'band'(Range, any)};
-erlang_band_type([_LHS, #t_integer{elements=Range}]) ->
- #t_integer{elements=beam_bounds:'band'(any, Range)};
-erlang_band_type([_, _]) ->
- #t_integer{}.
-
-erlang_bor_type([#t_integer{elements=Range1}, #t_integer{elements=Range2}]) ->
- #t_integer{elements=beam_bounds:'bor'(Range1, Range2)};
-erlang_bor_type([_, _]) ->
- #t_integer{}.
-
-erlang_bxor_type([#t_integer{elements=Range1}, #t_integer{elements=Range2}]) ->
- #t_integer{elements=beam_bounds:'bxor'(Range1, Range2)};
-erlang_bxor_type([_, _]) ->
- #t_integer{}.
-
-erlang_bsr_type([#t_integer{elements=Range1}, #t_integer{elements=Range2}]) ->
- #t_integer{elements=beam_bounds:'bsr'(Range1, Range2)};
-erlang_bsr_type([_, _]) ->
- #t_integer{}.
-
-erlang_bsl_type([#t_integer{elements=Range1}, #t_integer{elements=Range2}]) ->
- #t_integer{elements=beam_bounds:'bsl'(Range1, Range2)};
-erlang_bsl_type([_, _]) ->
- #t_integer{}.
-
-erlang_div_type(ArgTypes) ->
- case ArgTypes of
- [#t_integer{elements=Range1},#t_integer{elements=Range2}]->
- #t_integer{elements=beam_bounds:'div'(Range1, Range2)};
- _ ->
- #t_integer{}
+beam_bounds_type(Op, Type, [LHS, RHS]) ->
+ case get_range(LHS, RHS, Type) of
+ {_, none, _} ->
+ none;
+ {_, _, none} ->
+ none;
+ {float, _R1, _R2} ->
+ #t_float{};
+ {integer, R1, R2} ->
+ #t_integer{elements=beam_bounds:bounds(Op, R1, R2)};
+ {number, R1, R2} ->
+ #t_number{elements=beam_bounds:bounds(Op, R1, R2)}
+ end;
+beam_bounds_type(Op, Type, [Arg]) ->
+ case beam_types:meet(Arg, Type) of
+ #t_float{elements=R} ->
+ #t_float{elements=beam_bounds:bounds(Op, R)};
+ #t_integer{elements=R} ->
+ #t_integer{elements=beam_bounds:bounds(Op, R)};
+ #t_number{elements=R} ->
+ #t_number{elements=beam_bounds:bounds(Op, R)};
+ none ->
+ none
end.
-erlang_rem_type([LHS0, #t_integer{elements=Range2}]) ->
- Range1 = case LHS0 of
- #t_integer{elements=R1} -> R1;
- _ -> any
- end,
- #t_integer{elements=beam_bounds:'rem'(Range1, Range2)};
-erlang_rem_type(_) ->
- #t_integer{}.
+get_range(LHS, RHS, Type) ->
+ get_range(meet(LHS, Type), meet(RHS, Type)).
+
+get_range(#t_float{}=LHS, #t_float{}=RHS) ->
+ {float, get_range(LHS), get_range(RHS)};
+get_range(#t_integer{}=LHS, #t_integer{}=RHS) ->
+ {integer, get_range(LHS), get_range(RHS)};
+get_range(LHS, RHS) ->
+ {number, get_range(LHS), get_range(RHS)}.
+
+get_range(#t_float{}) -> any;
+get_range(#t_integer{elements=R}) -> R;
+get_range(#t_number{elements=R}) -> R;
+get_range(_) -> none.
erlang_map_get_type(Key, Map) ->
- case Map of
+ case meet(Map, #t_map{}) of
#t_map{super_key=SKey,super_value=SValue} ->
- case beam_types:meet(SKey, Key) of
+ case meet(SKey, Key) of
none -> none;
_ -> SValue
end;
- _ ->
- any
+ none ->
+ none
end.
-lists_fold_type(_Fun, Init, nil) ->
+lists_fold_type(Fun, Init, List) ->
+ lists_fold_type_1(meet(Fun, #t_fun{arity=2}),
+ Init,
+ meet(List, #t_list{})).
+
+lists_fold_type_1(_Fun, Init, nil) ->
Init;
-lists_fold_type(#t_fun{type=Type}, _Init, #t_cons{}) ->
+lists_fold_type_1(#t_fun{type=Type}, _Init, #t_cons{}) ->
%% The list is non-empty so it's safe to ignore Init.
Type;
-lists_fold_type(#t_fun{type=Type}, Init, #t_list{}) ->
+lists_fold_type_1(#t_fun{type=Type}, Init, #t_list{}) ->
%% The list is possibly empty so we have to assume it can return the
%% initial value, whose type can differ significantly from the fun's
%% return value.
- beam_types:join(Type, Init);
-lists_fold_type(_Fun, _Init, _List) ->
+ join(Type, Init);
+lists_fold_type_1(_Fun, _Init, _List) ->
any.
-lists_map_type(#t_fun{type=Type}, Types) ->
- lists_map_type_1(Types, Type);
-lists_map_type(_Fun, Types) ->
- lists_map_type_1(Types, any).
+lists_map_type(Fun, Types) ->
+ case meet(Fun, #t_fun{arity=1}) of
+ #t_fun{type=Type} -> lists_map_type_1(Types, Type);
+ none -> none
+ end.
lists_map_type_1(nil, _ElementType) ->
nil;
@@ -1036,35 +1180,40 @@ lists_map_type_1(_, none) ->
lists_map_type_1(_, ElementType) ->
proper_list(ElementType).
-lists_mapfold_type(#t_fun{type=#t_tuple{size=2,elements=Es}}, Init, List) ->
- ElementType = beam_types:get_tuple_element(1, Es),
- AccType = beam_types:get_tuple_element(2, Es),
- lists_mapfold_type_1(List, ElementType, Init, AccType);
-lists_mapfold_type(#t_fun{type=none}, _Init, #t_cons{}) ->
- %% The list is non-empty and the fun never returns.
- none;
-lists_mapfold_type(#t_fun{type=none}, Init, _List) ->
- %% The fun never returns, so the only way we could return normally is
- %% if the list is empty, in which case we'll return [] and the initial
- %% value.
- make_two_tuple(nil, Init);
-lists_mapfold_type(_Fun, Init, List) ->
- lists_mapfold_type_1(List, any, Init, any).
-
-lists_mapfold_type_1(nil, _ElementType, Init, _AccType) ->
- make_two_tuple(nil, Init);
+lists_mapfold_type(Fun, Init, List) ->
+ case {meet(Fun, #t_fun{type=#t_tuple{size=2}}), meet(List, #t_list{})} of
+ {_, nil} ->
+ make_two_tuple(nil, Init);
+ {#t_fun{type=#t_tuple{elements=Es}}, ListType} ->
+ ElementType = beam_types:get_tuple_element(1, Es),
+ AccType = beam_types:get_tuple_element(2, Es),
+ lists_mapfold_type_1(ListType, ElementType, Init, AccType);
+ {#t_fun{type=none}, #t_list{}} ->
+ %% The fun never returns, so the only way we could return normally
+ %% is if the list is empty, in which case we'll return [] and the
+ %% initial value.
+ make_two_tuple(nil, Init);
+ _ ->
+ none
+ end.
+
lists_mapfold_type_1(#t_cons{}, ElementType, _Init, AccType) ->
%% The list has at least one element, so it's safe to ignore Init.
make_two_tuple(proper_cons(ElementType), AccType);
lists_mapfold_type_1(_, ElementType, Init, AccType0) ->
%% We can only rely on AccType when we know the list is non-empty, so we
%% have to join it with the initial value in case the list is empty.
- AccType = beam_types:join(AccType0, Init),
+ AccType = join(AccType0, Init),
make_two_tuple(proper_list(ElementType), AccType).
lists_unzip_type(Size, List) ->
- Es = lut_make_elements(lut_list_types(Size, List), 1, #{}),
- #t_tuple{size=Size,exact=true,elements=Es}.
+ case meet(List, #t_list{type=#t_tuple{exact=true,size=Size}}) of
+ none ->
+ none;
+ ListType ->
+ Es = lut_make_elements(lut_list_types(Size, ListType), 1, #{}),
+ #t_tuple{size=Size,exact=true,elements=Es}
+ end.
lut_make_elements([Type | Types], Index, Es0) ->
Es = beam_types:set_tuple_element(Index, Type, Es0),
@@ -1072,16 +1221,16 @@ lut_make_elements([Type | Types], Index, Es0) ->
lut_make_elements([], _Index, Es) ->
Es.
-lut_list_types(Size, #t_cons{type=#t_tuple{size=Size,elements=Es}}) ->
+lut_list_types(Size, #t_cons{type=Tuple}) ->
+ #t_tuple{size=Size,elements=Es} = normalize(Tuple),
Types = lut_element_types(1, Size, Es),
[proper_cons(T) || T <- Types];
-lut_list_types(Size, #t_list{type=#t_tuple{size=Size,elements=Es}}) ->
+lut_list_types(Size, #t_list{type=Tuple}) ->
+ #t_tuple{size=Size,elements=Es} = normalize(Tuple),
Types = lut_element_types(1, Size, Es),
[proper_list(T) || T <- Types];
lut_list_types(Size, nil) ->
- lists:duplicate(Size, nil);
-lut_list_types(Size, _) ->
- lists:duplicate(Size, proper_list()).
+ lists:duplicate(Size, nil).
lut_element_types(Index, Max, #{}) when Index > Max ->
[];
@@ -1093,42 +1242,43 @@ lut_element_types(Index, Max, Es) ->
%% length, so if one of them is #t_cons{}, we can infer that all of them are
%% #t_cons{} on success.
-lists_zip_types(Types) ->
- lists_zip_types_1(Types, false, #{}, 1).
+lists_zip_types(Types0) ->
+ Types = [meet(T, #t_list{terminator=nil}) || T <- Types0],
+ lists_zip_types_1(Types, fun proper_list/1, #{}, 1).
-lists_zip_types_1([nil | _], _AnyCons, _Es, _N) ->
+lists_zip_types_1([none | _], _ListFun, _Es, _N) ->
+ %% At least one of the lists is not a proper list
+ {none, nil};
+lists_zip_types_1([nil | _], _ListFun, _Es, _N) ->
%% Early exit; we know the result is [] on success.
{nil, nil};
-lists_zip_types_1([#t_cons{type=Type,terminator=nil} | Lists],
- _AnyCons, Es0, N) ->
+lists_zip_types_1([#t_cons{type=Type} | Lists], _ListFun, Es0, N) ->
Es = beam_types:set_tuple_element(N, Type, Es0),
- lists_zip_types_1(Lists, true, Es, N + 1);
-lists_zip_types_1([#t_list{type=Type,terminator=nil} | Lists],
- AnyCons, Es0, N) ->
+ %% The result will be cons.
+ lists_zip_types_1(Lists, fun proper_cons/1, Es, N + 1);
+lists_zip_types_1([#t_list{type=Type} | Lists], ListFun, Es0, N) ->
Es = beam_types:set_tuple_element(N, Type, Es0),
- lists_zip_types_1(Lists, AnyCons, Es, N + 1);
-lists_zip_types_1([_ | Lists], AnyCons, Es, N) ->
- lists_zip_types_1(Lists, AnyCons, Es, N + 1);
-lists_zip_types_1([], true, Es, N) ->
- %% At least one element was cons, so we know it's non-empty on success.
- ElementType = #t_tuple{exact=true,size=(N - 1),elements=Es},
- RetType = proper_cons(ElementType),
- ArgType = proper_cons(),
- {RetType, ArgType};
-lists_zip_types_1([], false, Es, N) ->
- ElementType = #t_tuple{exact=true,size=(N - 1),elements=Es},
- RetType = proper_list(ElementType),
- ArgType = proper_list(),
+ lists_zip_types_1(Lists, ListFun, Es, N + 1);
+lists_zip_types_1([], ListFun, Es, N) ->
+ ElementType = #t_tuple{exact=true,size=N-1,elements=Es},
+ RetType = ListFun(ElementType),
+ ArgType = ListFun(any),
{RetType, ArgType}.
-lists_zipwith_types(#t_fun{type=Type}, Types) ->
- lists_zipwith_type_1(Types, Type);
-lists_zipwith_types(_Fun, Types) ->
- lists_zipwith_type_1(Types, any).
+lists_zipwith_types(Fun, Types0) ->
+ ElementType = case meet(Fun, #t_fun{}) of
+ #t_fun{type=T} -> T;
+ none -> none
+ end,
+ Types = [meet(T, #t_list{terminator=nil}) || T <- Types0],
+ lists_zipwith_type_1(Types, ElementType).
lists_zipwith_type_1([nil | _], _ElementType) ->
%% Early exit; we know the result is [] on success.
{nil, nil};
+lists_zipwith_type_1([none | _], _ElementType) ->
+ %% Early exit; at least one argument cannot be a proper list.
+ {none, any};
lists_zipwith_type_1([#t_cons{} | _Lists], none) ->
%% Early exit; the list is non-empty and we know the fun never
%% returns.
@@ -1138,7 +1288,7 @@ lists_zipwith_type_1([#t_cons{} | _Lists], ElementType) ->
RetType = proper_cons(ElementType),
ArgType = proper_cons(),
{RetType, ArgType};
-lists_zipwith_type_1([_ | Lists], ElementType) ->
+lists_zipwith_type_1([#t_list{} | Lists], ElementType) ->
lists_zipwith_type_1(Lists, ElementType);
lists_zipwith_type_1([], none) ->
%% Since we know the fun won't return, the only way we could return
@@ -1149,16 +1299,19 @@ lists_zipwith_type_1([], ElementType) ->
ArgType = proper_list(),
{RetType, ArgType}.
-maps_remove_type(Key, #t_map{super_key=SKey0}=Map) ->
- case beam_types:is_singleton_type(Key) of
- true ->
- SKey = beam_types:subtract(SKey0, Key),
- Map#t_map{super_key=SKey};
- false ->
- Map
- end;
-maps_remove_type(_Key, _Map) ->
- #t_map{}.
+maps_remove_type(Key, Map0) ->
+ case meet(Map0, #t_map{}) of
+ #t_map{super_key=SKey0}=Map ->
+ case beam_types:is_singleton_type(Key) of
+ true ->
+ SKey = beam_types:subtract(SKey0, Key),
+ Map#t_map{super_key=SKey};
+ false ->
+ Map
+ end;
+ none ->
+ none
+ end.
%%%
%%% Generic helpers
@@ -1166,7 +1319,7 @@ maps_remove_type(_Key, _Map) ->
sub_unsafe_type_test(ArgType, Required) ->
RetType =
- case beam_types:meet(ArgType, Required) of
+ case meet(ArgType, Required) of
ArgType -> #t_atom{elements=[true]};
none -> #t_atom{elements=[false]};
_ -> beam_types:make_boolean()
@@ -1179,12 +1332,6 @@ sub_unsafe(RetType, ArgTypes) ->
sub_safe(RetType, ArgTypes) ->
{RetType, ArgTypes, true}.
-discard_tuple_element_info(Min, Max, Es) ->
- foldl(fun(El, Acc) when Min =< El, El =< Max ->
- maps:remove(El, Acc);
- (_El, Acc) -> Acc
- end, Es, maps:keys(Es)).
-
proper_cons() ->
#t_cons{terminator=nil}.
@@ -1203,27 +1350,27 @@ proper_list(ElementType) ->
List :: type(),
Length :: same_length | new_length,
Proper :: proper | maybe_improper.
-copy_list(#t_cons{terminator=Term}=T, Length, maybe_improper) ->
- copy_list_1(T, Length, Term);
-copy_list(#t_list{terminator=Term}=T, Length, maybe_improper) ->
- copy_list_1(T, Length, Term);
-copy_list(T, Length, proper) ->
- copy_list_1(T, Length, nil);
-copy_list(T, Length, _Proper) ->
- copy_list_1(T, Length, any).
-
-copy_list_1(#t_cons{}=T, same_length, Terminator) ->
- T#t_cons{terminator=Terminator};
-copy_list_1(#t_cons{type=Type}, new_length, Terminator) ->
- #t_list{type=Type,terminator=Terminator};
-copy_list_1(#t_list{}=T, _Length, Terminator) ->
- T#t_list{terminator=Terminator};
-copy_list_1(nil, _Length, _Terminator) ->
- nil;
-copy_list_1(_, _Length, Terminator) ->
- #t_list{terminator=Terminator}.
+copy_list(List0, Length, Proper) ->
+ case {meet(List0, #t_list{}), Length, Proper} of
+ {#t_cons{type=Type,terminator=Term}, new_length, maybe_improper} ->
+ #t_list{type=Type,terminator=Term};
+ {#t_cons{type=Type}, new_length, proper} ->
+ #t_list{type=Type,terminator=nil};
+ {#t_cons{}=T, _, proper} ->
+ T#t_cons{terminator=nil};
+ {#t_list{}=T, _, proper} ->
+ T#t_list{terminator=nil};
+ {none, _, _} ->
+ none;
+ {List, _, _} ->
+ List
+ end.
make_two_tuple(Type1, Type2) ->
Es0 = beam_types:set_tuple_element(1, Type1, #{}),
Es = beam_types:set_tuple_element(2, Type2, Es0),
#t_tuple{size=2,exact=true,elements=Es}.
+
+normalize(T) -> beam_types:normalize(T).
+join(A, B) -> beam_types:join(A, B).
+meet(A, B) -> beam_types:meet(A, B).
diff --git a/lib/compiler/src/beam_clean.erl b/lib/compiler/src/beam_clean.erl
index 76d86d116c..ef44bbfdf5 100644
--- a/lib/compiler/src/beam_clean.erl
+++ b/lib/compiler/src/beam_clean.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
module({Mod,Exp,Attr,Fs0,_}, Opts) ->
Order = [Lbl || {function,_,_,Lbl,_} <- Fs0],
- All = maps:from_list([{Lbl,Func} || {function,_,_,Lbl,_}=Func <- Fs0]),
+ All = #{Lbl => Func || {function,_,_,Lbl,_}=Func <- Fs0},
WorkList = rootset(Fs0, Exp, Attr),
Used = find_all_used(WorkList, All, sets:from_list(WorkList, [{version, 2}])),
Fs1 = remove_unused(Order, Used, All),
@@ -53,12 +53,8 @@ rootset(Fs, Root0, Attr) ->
%% Remove the unused functions.
-remove_unused([F|Fs], Used, All) ->
- case sets:is_element(F, Used) of
- false -> remove_unused(Fs, Used, All);
- true -> [map_get(F, All)|remove_unused(Fs, Used, All)]
- end;
-remove_unused([], _, _) -> [].
+remove_unused(Fs, Used, All) ->
+ [map_get(F, All) || F <- Fs, sets:is_element(F, Used)].
%% Find all used functions.
diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl
index 6b41c15970..35ba0ba82d 100644
--- a/lib/compiler/src/beam_disasm.erl
+++ b/lib/compiler/src/beam_disasm.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -262,19 +262,29 @@ disasm_literals(<<>>, _) -> [].
%% Disassembles the type table of a BEAM file.
%%-----------------------------------------------------------------------
--spec beam_disasm_types('none' | binary()) -> literals().
+-spec beam_disasm_types('none' | binary()) -> types().
beam_disasm_types(none) ->
none;
-beam_disasm_types(<<?BEAM_TYPES_VERSION:32,Count:32,Table/binary>>) ->
- Res = gb_trees:from_orddict(disasm_types(Table, 0)),
- Count = gb_trees:size(Res), %Assertion.
- Res.
-
-disasm_types(<<Type:18/binary,Rest/binary>>, Index) ->
- [{Index,beam_types:decode_ext(Type)}|disasm_types(Rest, Index+1)];
-disasm_types(<<>>, _) ->
- [].
+beam_disasm_types(<<Version:32,Count:32,Table0/binary>>) ->
+ case beam_types:convert_ext(Version, Table0) of
+ none ->
+ ?exit({beam_disasm_types,{unknown_type_version,Version}});
+ Table ->
+ Res = gb_trees:from_orddict(disasm_types(Table, 0)),
+ Count = gb_trees:size(Res), %Assertion.
+ Res
+ end;
+beam_disasm_types(<<_/binary>>) ->
+ none.
+
+disasm_types(Types0, Index) ->
+ case beam_types:decode_ext(Types0) of
+ done ->
+ [];
+ {Types,Rest} ->
+ [{Index,Types}|disasm_types(Rest, Index+1)]
+ end.
%%-----------------------------------------------------------------------
%% Disassembles the code chunk of a BEAM file:
@@ -412,6 +422,10 @@ disasm_instr(B, Bs, Atoms, Literals, Types) ->
disasm_init_yregs(Bs, Atoms, Literals, Types);
bs_create_bin ->
disasm_bs_create_bin(Bs, Atoms, Literals, Types);
+ bs_match ->
+ disasm_bs_match(Bs, Atoms, Literals, Types);
+ update_record ->
+ disasm_update_record(Bs, Atoms, Literals, Types);
_ ->
try decode_n_args(Arity, Bs, Atoms, Literals, Types) of
{Args, RestBs} ->
@@ -488,6 +502,27 @@ disasm_bs_create_bin(Bs0, Atoms, Literals, Types) ->
{List, RestBs} = decode_n_args(Len, Bs7, Atoms, Literals, Types),
{{bs_create_bin, [{A1,A2,A3,A4,A5,Z,U,List}]}, RestBs}.
+disasm_bs_match(Bs0, Atoms, Literals, Types) ->
+ {A1, Bs1} = decode_arg(Bs0, Atoms, Literals, Types),
+ {A2, Bs2} = decode_arg(Bs1, Atoms, Literals, Types),
+ Bs5 = Bs2,
+ {Z, Bs6} = decode_arg(Bs5, Atoms, Literals, Types),
+ {U, Bs7} = decode_arg(Bs6, Atoms, Literals, Types),
+ {u, Len} = U,
+ {List, RestBs} = decode_n_args(Len, Bs7, Atoms, Literals, Types),
+ {{bs_match, [{A1,A2,Z,U,List}]}, RestBs}.
+
+disasm_update_record(Bs1, Atoms, Literals, Types) ->
+ {Hint, Bs2} = decode_arg(Bs1, Atoms, Literals, Types),
+ {Size, Bs3} = decode_arg(Bs2, Atoms, Literals, Types),
+ {Src, Bs4} = decode_arg(Bs3, Atoms, Literals, Types),
+ {Dst, Bs6} = decode_arg(Bs4, Atoms, Literals, Types),
+ {Z, Bs7} = decode_arg(Bs6, Atoms, Literals, Types),
+ {U, Bs8} = decode_arg(Bs7, Atoms, Literals, Types),
+ {u, Len} = U,
+ {List, RestBs} = decode_n_args(Len, Bs8, Atoms, Literals, Types),
+ {{update_record, [Hint,Size,Src,Dst,{{Z,U,List}}]}, RestBs}.
+
%%-----------------------------------------------------------------------
%% decode_arg([Byte]) -> {Arg, [Byte]}
%%
@@ -731,10 +766,14 @@ resolve_names(Fun, Imports, Str, Lbls, Lambdas, Literals, M) ->
[resolve_inst(Instr, Imports, Str, Lbls, Lambdas, Literals, M) || Instr <- Fun].
%%
-%% New make_fun2/4 instruction added in August 2001 (R8).
-%% We handle it specially here to avoid adding an argument to
+%% Instructions that need to look up an entry in the Lambda table.
+%% We handle these specially here to avoid adding an argument to
%% the clause for every instruction.
%%
+%% - make_fun2/4 (R8, added in August 2001)
+%% - make_fun3/3 (OTP 24)
+%% - call_fun2/3 (OTP 25)
+%%
resolve_inst({make_fun2,Args}, _, _, _, Lambdas, _, M) ->
[OldIndex] = resolve_args(Args),
@@ -747,6 +786,17 @@ resolve_inst({make_fun3,[Fun,Dst,{{z,1},{u,_},Env0}]}, _, _, _, Lambdas, _, M) -
{OldIndex,{F,A,_Lbl,_Index,_NumFree,OldUniq}} =
lists:keyfind(OldIndex, 1, Lambdas),
{make_fun3,{M,F,A},OldIndex,OldUniq,Dst,{list,Env1}};
+resolve_inst({call_fun2,Args}, _, _, _, Lambdas, _, _) ->
+ [Tag0,Arity,Func] = resolve_args(Args),
+ Tag = case Tag0 of
+ Index when is_integer(Index) ->
+ {Tag0,{_F,_A,Label,_Index,_NumFree,_OldUniq}} =
+ lists:keyfind(Tag0, 1, Lambdas),
+ {f,Label};
+ _ ->
+ Tag0
+ end,
+ {call_fun2,Tag,Arity,Func};
resolve_inst(Instr, Imports, Str, Lbls, _Lambdas, _Literals, _M) ->
%% io:format(?MODULE_STRING":resolve_inst ~p.~n", [Instr]),
resolve_inst(Instr, Imports, Str, Lbls).
@@ -943,19 +993,19 @@ resolve_inst({fconv,Args},_,_,_) ->
{fconv,Reg,FR};
resolve_inst({fadd=I,Args},_,_,_) ->
[F,A1,A2,Reg] = resolve_args(Args),
- {arithfbif,I,F,[A1,A2],Reg};
+ {bif,I,F,[A1,A2],Reg};
resolve_inst({fsub=I,Args},_,_,_) ->
[F,A1,A2,Reg] = resolve_args(Args),
- {arithfbif,I,F,[A1,A2],Reg};
+ {bif,I,F,[A1,A2],Reg};
resolve_inst({fmul=I,Args},_,_,_) ->
[F,A1,A2,Reg] = resolve_args(Args),
- {arithfbif,I,F,[A1,A2],Reg};
+ {bif,I,F,[A1,A2],Reg};
resolve_inst({fdiv=I,Args},_,_,_) ->
[F,A1,A2,Reg] = resolve_args(Args),
- {arithfbif,I,F,[A1,A2],Reg};
+ {bif,I,F,[A1,A2],Reg};
resolve_inst({fnegate,Args},_,_,_) ->
[F,Arg,Reg] = resolve_args(Args),
- {arithfbif,fnegate,F,[Arg],Reg};
+ {bif,fnegate,F,[Arg],Reg};
%%
%% Instructions for try expressions added in January 2003 (R10).
@@ -969,8 +1019,7 @@ resolve_inst({try_case,[Reg]},_,_,_) -> % analogous to 'catch_end'
resolve_inst({try_case_end,[Arg]},_,_,_) ->
{try_case_end,resolve_arg(Arg)};
resolve_inst({raise,[_Reg1,_Reg2]=Regs},_,_,_) ->
- {raise,{f,0},Regs,{x,0}}; % do NOT wrap this as a 'bif'
- % as there is no raise/2 bif!
+ {bif,raise,{f,0},Regs,{x,0}};
%%
%% New bit syntax instructions added in February 2004 (R10B).
@@ -1009,15 +1058,15 @@ resolve_inst({is_function2=I,Args0},_,_,_) ->
%%
resolve_inst({bs_start_match2=I,[F,Reg,{u,Live},{u,Max},Ms]},_,_,_) ->
{test,I,F,[Reg,Live,Max,Ms]};
-resolve_inst({bs_get_integer2=I,[Lbl,Ms,{u,Live},Arg2,{u,N},{u,U},Arg5]},_,_,_) ->
- [A2,A5] = resolve_args([Arg2,Arg5]),
- {test,I,Lbl,[Ms, Live,A2,N,decode_field_flags(U),A5]};
-resolve_inst({bs_get_binary2=I,[Lbl,Ms,{u,Live},Arg2,{u,N},{u,U},Arg5]},_,_,_) ->
- [A2,A5] = resolve_args([Arg2,Arg5]),
- {test,I,Lbl,[Ms, Live,A2,N,decode_field_flags(U),A5]};
-resolve_inst({bs_get_float2=I,[Lbl,Ms,{u,Live},Arg2,{u,N},{u,U},Arg5]},_,_,_) ->
- [A2,A5] = resolve_args([Arg2,Arg5]),
- {test,I,Lbl,[Ms, Live,A2,N,decode_field_flags(U),A5]};
+resolve_inst({bs_get_integer2=I,[Fail,Ms,{u,Live},Size0,{u,Unit},{u,Flags},Dst0]},_,_,_) ->
+ [Size,Dst] = resolve_args([Size0,Dst0]),
+ {test,I,Fail,Live,[Ms,Size,Unit,decode_field_flags(Flags)],Dst};
+resolve_inst({bs_get_binary2=I,[Fail,Ms,{u,Live},Size0,{u,Unit},{u,Flags},Dst0]},_,_,_) ->
+ [Size,Dst] = resolve_args([Size0,Dst0]),
+ {test,I,Fail,Live,[Ms,Size,Unit,decode_field_flags(Flags)],Dst};
+resolve_inst({bs_get_float2=I,[Fail,Ms,{u,Live},Size0,{u,Unit},{u,Flags},Dst0]},_,_,_) ->
+ [Size,Dst] = resolve_args([Size0,Dst0]),
+ {test,I,Fail,Live,[Ms,Size,Unit,decode_field_flags(Flags)],Dst};
resolve_inst({bs_skip_bits2=I,[Lbl,Ms,Arg2,{u,N},{u,U}]},_,_,_) ->
A2 = resolve_arg(Arg2),
{test,I,Lbl,[Ms,A2,N,decode_field_flags(U)]};
@@ -1077,7 +1126,7 @@ resolve_inst({bs_match_string=I,[F,Ms,{u,Bits},{u,Off}]},_,Strings,_) ->
Bin;
true -> <<>>
end,
- {test,I,F,[Ms,Bits,String]};
+ {test,I,F,[Ms,Bits,{string,String}]};
resolve_inst({bs_init_writable=I,[]},_,_,_) ->
I;
resolve_inst({bs_append=I,[Lbl,Arg2,{u,W},{u,R},{u,U},Arg6,{u,F},Arg8]},_,_,_) ->
@@ -1192,11 +1241,11 @@ resolve_inst({get_tl,[Src,Dst]},_,_,_) ->
resolve_inst({put_tuple2,[Dst,{{z,1},{u,_},List0}]},_,_,_) ->
List = resolve_args(List0),
{put_tuple2,Dst,{list,List}};
-resolve_inst({bs_start_match3,[Fail,Bin,Live,Dst]},_,_,_) ->
- {bs_start_match3,Fail,Bin,Live,Dst};
-resolve_inst({bs_get_tail,[Src,Dst,Live]},_,_,_) ->
+resolve_inst({bs_start_match3=I,[Fail,Bin,{u,Live},Dst]},_,_,_) ->
+ {test,I,Fail,Live,[Bin],Dst};
+resolve_inst({bs_get_tail,[Src,Dst,{u,Live}]},_,_,_) ->
{bs_get_tail,Src,Dst,Live};
-resolve_inst({bs_get_position,[Src,Dst,Live]},_,_,_) ->
+resolve_inst({bs_get_position,[Src,Dst,{u,Live}]},_,_,_) ->
{bs_get_position,Src,Dst,Live};
resolve_inst({bs_set_position,[Src,Dst]},_,_,_) ->
{bs_set_position,Src,Dst};
@@ -1205,7 +1254,7 @@ resolve_inst({bs_set_position,[Src,Dst]},_,_,_) ->
%% OTP 23.
%%
-resolve_inst({bs_start_match4,[Fail,Live,Src,Dst]},_,_,_) ->
+resolve_inst({bs_start_match4,[Fail,{u,Live},Src,Dst]},_,_,_) ->
{bs_start_match4,Fail,Live,Src,Dst};
resolve_inst({swap,[_,_]=List},_,_,_) ->
[R1,R2] = resolve_args(List),
@@ -1231,16 +1280,30 @@ resolve_inst({recv_marker_use,[Reg]},_,_,_) ->
%% OTP 25.
%%
-resolve_inst({bs_create_bin,Args},_,_,_) ->
- {bs_create_bin,Args};
-resolve_inst({call_fun2,[Tag,{u,Arity},Func]},_,_,_) ->
- {call_fun2,Tag,Arity,Func};
+resolve_inst({bs_create_bin,
+ [{Fail,{u,Heap},{u,Live},{u,Unit},Dst,{z,1},{u,_},List0}]},
+ _, Strings, _) ->
+ List = resolve_bs_create_bin_list(List0, Strings),
+ {bs_create_bin,Fail,Heap,Live,Unit,Dst,{list,List}};
resolve_inst({nif_start,[]},_,_,_) ->
nif_start;
resolve_inst({badrecord,[Arg]},_,_,_) ->
{badrecord,resolve_arg(Arg)};
%%
+%% OTP 26.
+%%
+
+resolve_inst({update_record,
+ [Hint,{u,Size},Src,Dst,{{{z,1},{u,_},List0}}]},_,_,_) ->
+ List = resolve_args(List0),
+ {update_record,Hint,Size,Src,Dst,{list,List}};
+resolve_inst({bs_match,[{Fail,Ctx,{z,1},{u,_},Args}]},_,_,_) ->
+ List = resolve_args(Args),
+ Commands = resolve_bs_match_commands(List),
+ {bs_match,Fail,Ctx,{commands,Commands}};
+
+%%
%% Catches instructions that are not yet handled.
%%
resolve_inst(X,_,_,_) -> ?exit({resolve_inst,X}).
@@ -1268,13 +1331,70 @@ resolve_arg_unsigned({u,N}) when is_integer(N), N >= 0 -> N.
resolve_arg_integer({i,N}) when is_integer(N) -> {integer,N}.
%%-----------------------------------------------------------------------
+%% Resolves the OpList for the bs_create_bin/6 instruction
+%%-----------------------------------------------------------------------
+
+resolve_bs_create_bin_list(
+ [{atom,string}=Type,Seg0,Unit0,Flags,Offset0,Size0|Rest], Strings) ->
+ [Seg,Unit,Offset,{integer,Len}=Size] =
+ resolve_args([Seg0,Unit0,Offset0,Size0]),
+ <<_:Offset/binary,Bin:Len/binary,_/binary>> = Strings,
+ [Type,Seg,Unit,Flags,{string,Bin},Size |
+ resolve_bs_create_bin_list(Rest, Strings)];
+resolve_bs_create_bin_list([Type,Seg0,Unit0,Flags,Val0,Size0|Rest], Strings) ->
+ [Seg,Unit,Val,Size] = resolve_args([Seg0,Unit0,Val0,Size0]),
+ [Type,Seg,Unit,Flags,Val,Size |
+ resolve_bs_create_bin_list(Rest, Strings)];
+resolve_bs_create_bin_list([], _Str) ->
+ [].
+
+%%-----------------------------------------------------------------------
+%% Resolves the Commands list for the bs_match/3 instruction
+%%-----------------------------------------------------------------------
+
+resolve_bs_match_commands([{atom,ensure_at_least},Size,Unit|Rest]) ->
+ [{ensure_at_least,Size,Unit} | resolve_bs_match_commands(Rest)];
+resolve_bs_match_commands([{atom,ensure_exactly},Stride|Rest]) ->
+ [{ensure_exactly,Stride} | resolve_bs_match_commands(Rest)];
+resolve_bs_match_commands([{atom,integer},Live,Flags0,Size,Unit,Dst|Rest]) ->
+ Flags = resolve_bs_match_flags(Flags0),
+ [{integer,Live,Flags,Size,Unit,Dst} |
+ resolve_bs_match_commands(Rest)];
+resolve_bs_match_commands([{atom,binary},Live,Flags0,Size,Unit,Dst|Rest]) ->
+ Flags = resolve_bs_match_flags(Flags0),
+ [{binary,Live,Flags,Size,Unit,Dst} |
+ resolve_bs_match_commands(Rest)];
+resolve_bs_match_commands([{atom,'=:='},nil,Bits,Value|Rest]) ->
+ [{'=:=',nil,Bits,Value} | resolve_bs_match_commands(Rest)];
+resolve_bs_match_commands([{atom,skip},Stride|Rest]) ->
+ [{skip,Stride} | resolve_bs_match_commands(Rest)];
+resolve_bs_match_commands([{atom,get_tail},Live,Src,Dst|Rest]) ->
+ [{get_tail,Live,Src,Dst} | resolve_bs_match_commands(Rest)];
+resolve_bs_match_commands([]) ->
+ [].
+
+resolve_bs_match_flags(nil) -> {literal,[]};
+resolve_bs_match_flags({literal,[_|_]}=Flags) -> Flags.
+
+%%-----------------------------------------------------------------------
%% The purpose of the following is just to add a hook for future changes.
%% Currently, field flags are numbers 1-2-4-8 and only two of these
%% numbers (BSF_LITTLE 2 -- BSF_SIGNED 4) have a semantic significance;
%% others are just hints for speeding up the execution; see "erl_bits.h".
+%% Decodes field flags within bitstrings such as `Var/signed' or
+%% `Var/little'. This is the opposite of `beam_asm:flag_to_bit/1'.
+%% Also see "erl_bits.h".
%%-----------------------------------------------------------------------
-decode_field_flags(FF) ->
+decode_field_flags(0) ->
+ {field_flags,[]};
+decode_field_flags(FieldFlags) when is_integer(FieldFlags) ->
+ FF = lists:filter(
+ fun
+ (little) -> (FieldFlags band 16#02) == 16#02;
+ (signed) -> (FieldFlags band 16#04) == 16#04;
+ (native) -> (FieldFlags band 16#10) == 16#10
+ end, [little, signed, native]),
{field_flags,FF}.
%%-----------------------------------------------------------------------
diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl
index 555af00ccc..9ae5a33d74 100644
--- a/lib/compiler/src/beam_jump.erl
+++ b/lib/compiler/src/beam_jump.erl
@@ -407,30 +407,43 @@ share_1([I|Is], Safe, Dict, Lbls, Seq, Acc) ->
share_1(Is, Safe, Dict, Lbls, [I], Acc)
end.
-unambigous_deallocation([{call_ext,_,_}|Is]) ->
+unambigous_deallocation([{bs_init,_,bs_init_writable,_,_,_}|Is]) ->
%% beam_validator requires that the size of the stack frame is
- %% unambigously known when a function is called.
+ %% unambigously known when certain instructions are used.
%%
- %% That means that we must be careful when sharing function calls.
+ %% That means that we must be careful when sharing them.
%%
%% To ensure that the frame size is unambigous, only allow sharing
%% of calls if the call is followed by instructions that
%% indicates the size of the stack frame.
find_deallocation(Is);
+unambigous_deallocation([{call_ext,_,_}|Is]) ->
+ find_deallocation(Is);
unambigous_deallocation([{call,_,_}|Is]) ->
find_deallocation(Is);
unambigous_deallocation([_|Is]) ->
unambigous_deallocation(Is);
-unambigous_deallocation([]) -> true.
+unambigous_deallocation([]) ->
+ true.
-find_deallocation([{line,_}|Is]) -> find_deallocation(Is);
-find_deallocation([{call,_,_}|Is]) -> find_deallocation(Is);
-find_deallocation([{call_ext,_,_}|Is]) -> find_deallocation(Is);
-find_deallocation([{init_yregs,_}|Is]) -> find_deallocation(Is);
-find_deallocation([{block,_}|Is]) -> find_deallocation(Is);
-find_deallocation([{deallocate,_}|_]) -> true;
-find_deallocation([return]) -> true;
-find_deallocation(_) -> false.
+find_deallocation([{block,_}|Is]) ->
+ find_deallocation(Is);
+find_deallocation([{bs_init,_,bs_init_writable,_,_,_}|Is]) ->
+ find_deallocation(Is);
+find_deallocation([{call,_,_}|Is]) ->
+ find_deallocation(Is);
+find_deallocation([{call_ext,_,_}|Is]) ->
+ find_deallocation(Is);
+find_deallocation([{deallocate,_}|_]) ->
+ true;
+find_deallocation([{init_yregs,_}|Is]) ->
+ find_deallocation(Is);
+find_deallocation([{line,_}|Is]) ->
+ find_deallocation(Is);
+find_deallocation([return]) ->
+ true;
+find_deallocation(_) ->
+ false.
%% If the label has a scope set, assign it to any line instruction
%% in the sequence.
@@ -451,6 +464,7 @@ add_scope([I|Is], Scope) ->
[I|add_scope(Is, Scope)];
add_scope([], _Scope) -> [].
+is_shareable([{badmatch,_}|_]) -> false;
is_shareable([build_stacktrace|_]) -> false;
is_shareable([{case_end,_}|_]) -> false;
is_shareable([{'catch',_,_}|_]) -> false;
@@ -599,20 +613,23 @@ find_fixpoint(OptFun, Is0) ->
opt([{test,is_eq_exact,{f,L},_}|[{jump,{f,L}}|_]=Is], Acc, St) ->
%% The is_eq_exact test is not needed.
opt(Is, Acc, St);
-opt([{test,Test0,{f,L}=Lbl,Ops}=I|[{jump,To}|Is]=Is0], Acc, St) ->
+opt([{test,Test0,{f,L}=Lbl,Ops}=I0|[{jump,To}|Is]=Is0], Acc, St) ->
case is_label_defined(Is, L) of
false ->
+ I = is_lt_to_is_ge(I0),
opt(Is0, [I|Acc], label_used(Lbl, St));
true ->
case invert_test(Test0) of
not_possible ->
+ I = is_lt_to_is_ge(I0),
opt(Is0, [I|Acc], label_used(Lbl, St));
Test ->
%% Invert the test and remove the jump.
opt([{test,Test,To,Ops}|Is], Acc, St)
end
end;
-opt([{test,_,{f,_}=Lbl,_}=I|Is], Acc, St) ->
+opt([{test,_,{f,_}=Lbl,_}=I0|Is], Acc, St) ->
+ I = is_lt_to_is_ge(I0),
opt(Is, [I|Acc], label_used(Lbl, St));
opt([{test,_,{f,_}=Lbl,_,_,_}=I|Is], Acc, St) ->
opt(Is, [I|Acc], label_used(Lbl, St));
@@ -673,6 +690,17 @@ opt([], Acc, #st{replace=Replace0}) when Replace0 =/= #{} ->
opt([], Acc, #st{replace=Replace}) when Replace =:= #{} ->
reverse(Acc).
+is_lt_to_is_ge({test,is_lt,Lbl,Args}=I) ->
+ case Args of
+ [{integer,N},{tr,_,#t_integer{}}=Src] ->
+ {test,is_ge,Lbl,[Src,{integer,N+1}]};
+ [{tr,_,#t_integer{}}=Src,{integer,N}] ->
+ {test,is_ge,Lbl,[{integer,N-1},Src]};
+ [_,_] ->
+ I
+ end;
+is_lt_to_is_ge(I) -> I.
+
prune_redundant_values([_Val,F|Vls], F) ->
prune_redundant_values(Vls, F);
prune_redundant_values([Val,Lbl|Vls], F) ->
@@ -911,6 +939,8 @@ instr_labels({bs_start_match4,Fail,_,_,_}) ->
{f,L} -> [L];
{atom,_} -> []
end;
+instr_labels({bs_match,{f,Fail},_Ctx,_List}) ->
+ [Fail];
instr_labels(_) ->
[].
diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl
index 52a68efbb6..f94ab4ed15 100644
--- a/lib/compiler/src/beam_kernel_to_ssa.erl
+++ b/lib/compiler/src/beam_kernel_to_ssa.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -42,7 +42,8 @@
recv=0 :: label(), %Receive label
ultimate_failure=0 :: label(), %Label for ultimate match failure.
labels=#{} :: #{atom() => label()},
- no_make_fun3=false :: boolean()
+ no_make_fun3=false :: boolean(),
+ checks=[] :: [term()]
}).
%% Internal records.
@@ -74,7 +75,12 @@ function(#k_fdef{anno=Anno0,func=Name,arity=Arity,
{As,St1} = new_ssa_vars(As0, St0),
{Asm,St} = cg_fun(Kb, St1),
Anno1 = line_anno(Anno0),
- Anno = Anno1#{func_info=>{Mod,Name,Arity}},
+ Anno2 = Anno1#{func_info=>{Mod,Name,Arity}},
+ Anno = case St#cg.checks of
+ [] -> Anno2;
+ Checks ->
+ Anno2#{ssa_checks=>Checks}
+ end,
#b_function{anno=Anno,args=As,bs=Asm,cnt=St#cg.lcount}
catch
Class:Error:Stack ->
@@ -157,7 +163,9 @@ cg(#k_goto{label=Label,args=As0}, #cg{labels=Labels}=St) ->
As = ssa_args(As0, St),
Branch = map_get(Label, Labels),
Break = #cg_break{args=As,phi=Branch},
- {[Break],St}.
+ {[Break],St};
+cg(#k_opaque{val={ssa_check_when,_,_,_,_}=Check},St) -> %% Extract here
+ {[],St#cg{checks=[Check|St#cg.checks]}}.
%% match_cg(Matc, [Ret], State) -> {[Ainstr],State}.
%% Generate code for a match.
diff --git a/lib/compiler/src/beam_listing.erl b/lib/compiler/src/beam_listing.erl
index ac21f1a1ab..a3491a3824 100644
--- a/lib/compiler/src/beam_listing.erl
+++ b/lib/compiler/src/beam_listing.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -41,19 +41,18 @@ module(File, #c_module{}=Core) ->
module(File, #k_mdef{}=Kern) ->
%% This is a kernel module.
io:put_chars(File, v3_kernel_pp:format(Kern));
- %%io:put_chars(File, io_lib:format("~p~n", [Kern]));
module(File, #b_module{name=Mod,exports=Exp,attributes=Attr,body=Fs}) ->
io:format(File, "module ~p.\n", [Mod]),
io:format(File, "exports ~p.\n", [Exp]),
- io:format(File, "attributes ~p.\n\n", [Attr]),
+ io:format(File, "attributes ~kp.\n\n", [Attr]),
PP = [beam_ssa_pp:format_function(F) || F <- Fs],
io:put_chars(File, lists:join($\n, PP));
module(Stream, {Mod,Exp,Attr,Code,NumLabels}) ->
%% This is output from v3_codegen.
- io:format(Stream, "{module, ~p}. %% version = ~w\n",
+ io:format(Stream, "{module, ~kp}. %% version = ~w\n",
[Mod, beam_opcodes:format_number()]),
io:format(Stream, "\n{exports, ~p}.\n", [Exp]),
- io:format(Stream, "\n{attributes, ~p}.\n", [Attr]),
+ io:format(Stream, "\n{attributes, ~kp}.\n", [Attr]),
io:format(Stream, "\n{labels, ~p}.\n", [NumLabels]),
Lbl2Fun = foldl(fun({function,Name,Arity,Entry,_}, Map) ->
Map#{ Entry => {Name,Arity} }
@@ -66,7 +65,7 @@ module(Stream, {Mod,Exp,Attr,Code,NumLabels}) ->
end, Code);
module(Stream, [_|_]=Fs) ->
%% Form-based abstract format.
- foreach(fun (F) -> io:format(Stream, "~p.\n", [F]) end, Fs).
+ foreach(fun (F) -> io:format(Stream, "~kp.\n", [F]) end, Fs).
format_asm([{label,L}|Is], Lbl2Fun) ->
[io_lib:format(" {label,~p}.\n", [L])|format_asm(Is, Lbl2Fun)];
@@ -75,7 +74,7 @@ format_asm([I={Call,_,L}|Is], Lbl2Fun) when Call =:= call; Call =:= call_only ->
format_asm([I={call_last,_,L,_}|Is], Lbl2Fun) ->
format_asm_call(L, I, Is, Lbl2Fun);
format_asm([I|Is], Lbl2Fun) ->
- [io_lib:format(" ~p", [I]),".\n"|format_asm(Is, Lbl2Fun)];
+ [io_lib:format(" ~kp", [I]),".\n"|format_asm(Is, Lbl2Fun)];
format_asm([], _) -> [].
format_asm_call({f,L}, I, Is, Lbl2Fun) ->
diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl
index c1d11c89da..448b3b4313 100644
--- a/lib/compiler/src/beam_ssa.erl
+++ b/lib/compiler/src/beam_ssa.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -105,7 +105,8 @@
%% To avoid the collapsing, change the value of SET_LIMIT to 50 in the
%% file erl_types.erl in the dialyzer application.
--type prim_op() :: 'bs_extract' | 'bs_get_tail' | 'bs_init_writable' |
+-type prim_op() :: 'bs_create_bin' |
+ 'bs_extract' | 'bs_ensure' | 'bs_get_tail' | 'bs_init_writable' |
'bs_match' | 'bs_start_match' | 'bs_test_tail' |
'build_stacktrace' |
'call' | 'catch_end' |
@@ -115,20 +116,28 @@
'is_nonempty_list' | 'is_tagged_tuple' |
'kill_try_tag' |
'landingpad' |
- 'make_fun' | 'match_fail' | 'new_try_tag' | 'old_make_fun' |
+ 'make_fun' | 'match_fail' | 'new_try_tag' |
+ 'nif_start' |
+ 'old_make_fun' |
'peek_message' | 'phi' | 'put_list' | 'put_map' | 'put_tuple' |
- 'raw_raise' | 'recv_next' | 'remove_message' | 'resume' |
+ 'raw_raise' |
+ 'recv_marker_bind' |
+ 'recv_marker_clear' |
+ 'recv_marker_reserve' |
+ 'recv_next' | 'remove_message' | 'resume' |
+ 'update_tuple' | 'update_record' |
'wait_timeout'.
-type float_op() :: 'checkerror' | 'clearerror' | 'convert' | 'get' | 'put' |
'+' | '-' | '*' | '/'.
%% Primops only used internally during code generation.
--type cg_prim_op() :: 'bs_get' | 'bs_get_position' | 'bs_match_string' |
+-type cg_prim_op() :: 'bs_checked_get' | 'bs_checked_skip' |
+ 'bs_get' | 'bs_get_position' | 'bs_match_string' |
'bs_restore' | 'bs_save' | 'bs_set_position' | 'bs_skip' |
'copy' | 'match_fail' | 'put_tuple_arity' |
- 'put_tuple_element' | 'put_tuple_elements' |
- 'set_tuple_element' | 'succeeded'.
+ 'set_tuple_element' | 'succeeded' |
+ 'update_record'.
-import(lists, [foldl/3,mapfoldl/3,member/2,reverse/1,sort/1]).
@@ -221,6 +230,8 @@ no_side_effect(#b_set{op=Op}) ->
put_tuple -> true;
raw_raise -> true;
{succeeded,guard} -> true;
+ update_record -> true;
+ update_tuple -> true;
_ -> false
end.
@@ -239,7 +250,7 @@ no_side_effect(#b_set{op=Op}) ->
Count :: label(),
Result :: {block_map(), label()}.
-insert_on_edges(Insertions, Blocks, Count) ->
+insert_on_edges(Insertions, Blocks, Count) when is_map(Blocks) ->
%% Sort insertions to simplify the handling of duplicates.
insert_on_edges_1(sort(Insertions), Blocks, Count).
@@ -329,7 +340,7 @@ is_loop_header(#b_set{op=Op}) ->
Result :: predecessor_map().
predecessors(Blocks) ->
- P0 = [{S,L} || {L,Blk} <- maps:to_list(Blocks),
+ P0 = [{S,L} || L := Blk <- Blocks,
S <- successors(Blk)],
P1 = sofs:relation(P0),
P2 = sofs:rel2fam(P1),
@@ -437,7 +448,7 @@ successors(L, Blocks) ->
Blocks :: block_map(),
Def :: ordsets:ordset(var_name()).
-def(Ls, Blocks) ->
+def(Ls, Blocks) when is_map(Blocks) ->
Blks = [map_get(L, Blocks) || L <- Ls],
def_1(Blks, []).
@@ -448,7 +459,7 @@ def(Ls, Blocks) ->
Def :: ordsets:ordset(var_name()),
Unused :: ordsets:ordset(var_name()).
-def_unused(Ls, Unused, Blocks) ->
+def_unused(Ls, Unused, Blocks) when is_map(Blocks) ->
Blks = [map_get(L, Blocks) || L <- Ls],
Preds = sets:from_list(Ls, [{version, 2}]),
def_unused_1(Blks, Preds, [], Unused).
@@ -470,7 +481,7 @@ def_unused(Ls, Unused, Blocks) ->
Labels :: [label()],
Blocks :: block_map(),
Result :: {dominator_map(), numbering_map()}.
-dominators(Labels, Blocks) ->
+dominators(Labels, Blocks) when is_map(Blocks) ->
Preds = predecessors(Blocks),
dominators_from_predecessors(Labels, Preds).
@@ -478,7 +489,7 @@ dominators(Labels, Blocks) ->
Labels :: [label()],
Preds :: predecessor_map(),
Result :: {dominator_map(), numbering_map()}.
-dominators_from_predecessors(Top0, Preds) ->
+dominators_from_predecessors(Top0, Preds) when is_map(Preds) ->
Df = maps:from_list(number(Top0, 0)),
[{0,[]}|Top] = [{L,map_get(L, Preds)} || L <- Top0],
@@ -492,7 +503,7 @@ dominators_from_predecessors(Top0, Preds) ->
%% and Dominators and Numbering as returned from dominators/1.
-spec common_dominators([label()], dominator_map(), numbering_map()) -> [label()].
-common_dominators(Ls, Dom, Numbering) ->
+common_dominators(Ls, Dom, Numbering) when is_map(Dom) ->
Doms = [map_get(L, Dom) || L <- Ls],
dom_intersection(Doms, Numbering).
@@ -502,7 +513,7 @@ common_dominators(Ls, Dom, Numbering) ->
Acc0 :: any(),
Blocks :: block_map().
-fold_instrs(Fun, Labels, Acc0, Blocks) ->
+fold_instrs(Fun, Labels, Acc0, Blocks) when is_map(Blocks) ->
fold_instrs_1(Labels, Fun, Blocks, Acc0).
%% mapfold_blocks(Fun, [Label], Acc, BlockMap) -> {BlockMap,Acc}.
@@ -515,7 +526,7 @@ fold_instrs(Fun, Labels, Acc0, Blocks) ->
Acc :: any(),
Blocks :: block_map(),
Result :: {block_map(), any()}.
-mapfold_blocks(Fun, Labels, Acc, Blocks) ->
+mapfold_blocks(Fun, Labels, Acc, Blocks) when is_map(Blocks) ->
foldl(fun(Lbl, A) ->
mapfold_blocks_1(Fun, Lbl, A)
end, {Blocks, Acc}, Labels).
@@ -534,7 +545,7 @@ mapfold_blocks_1(Fun, Lbl, {Blocks0, Acc0}) ->
Blocks0 :: block_map(),
Blocks :: block_map().
-mapfold_instrs(Fun, Labels, Acc0, Blocks) ->
+mapfold_instrs(Fun, Labels, Acc0, Blocks) when is_map(Blocks) ->
mapfold_instrs_1(Labels, Fun, Blocks, Acc0).
-spec flatmapfold_instrs(Fun, Labels, Acc0, Blocks0) -> {Blocks,Acc} when
@@ -545,7 +556,7 @@ mapfold_instrs(Fun, Labels, Acc0, Blocks) ->
Blocks0 :: block_map(),
Blocks :: block_map().
-flatmapfold_instrs(Fun, Labels, Acc0, Blocks) ->
+flatmapfold_instrs(Fun, Labels, Acc0, Blocks) when is_map(Blocks) ->
flatmapfold_instrs_1(Labels, Fun, Blocks, Acc0).
-type fold_fun() :: fun((label(), b_blk(), any()) -> any()).
@@ -560,7 +571,7 @@ flatmapfold_instrs(Fun, Labels, Acc0, Blocks) ->
Acc0 :: any(),
Blocks :: #{label():=b_blk()}.
-fold_blocks(Fun, Labels, Acc0, Blocks) ->
+fold_blocks(Fun, Labels, Acc0, Blocks) when is_map(Blocks) ->
fold_blocks_1(Labels, Fun, Blocks, Acc0).
%% linearize(Blocks) -> [{BlockLabel,#b_blk{}}].
@@ -574,7 +585,7 @@ fold_blocks(Fun, Labels, Acc0, Blocks) ->
Blocks :: block_map(),
Linear :: [{label(),b_blk()}].
-linearize(Blocks) ->
+linearize(Blocks) when is_map(Blocks) ->
Seen = sets:new([{version, 2}]),
{Linear0,_} = linearize_1([0], Blocks, Seen, []),
Linear = fix_phis(Linear0, #{}),
@@ -592,7 +603,7 @@ rpo(Blocks) ->
Blocks :: block_map(),
Labels :: [label()].
-rpo(From, Blocks) ->
+rpo(From, Blocks) when is_map(Blocks) ->
Seen = sets:new([{version, 2}]),
{Ls,_} = rpo_1(From, Blocks, Seen, []),
Ls.
@@ -609,7 +620,7 @@ rpo(From, Blocks) ->
Blocks :: block_map(),
Labels :: [label()].
-between(From, To, Preds, Blocks) ->
+between(From, To, Preds, Blocks) when is_map(Preds), is_map(Blocks) ->
%% Gather the predecessors of `To` and then walk forward from `From`,
%% skipping the blocks that don't precede `To`.
%%
@@ -627,7 +638,7 @@ between(From, To, Preds, Blocks) ->
rename_vars(Rename, Labels, Blocks) when is_list(Rename) ->
rename_vars(maps:from_list(Rename), Labels, Blocks);
-rename_vars(Rename, Labels, Blocks) when is_map(Rename)->
+rename_vars(Rename, Labels, Blocks) when is_map(Rename), is_map(Blocks) ->
Preds = sets:from_list(Labels, [{version, 2}]),
F = fun(#b_set{op=phi,args=Args0}=Set) ->
Args = rename_phi_vars(Args0, Preds, Rename),
@@ -658,7 +669,7 @@ rename_vars(Rename, Labels, Blocks) when is_map(Rename)->
Blocks :: block_map(),
Count :: label().
-split_blocks(Ls, P, Blocks, Count) ->
+split_blocks(Ls, P, Blocks, Count) when is_map(Blocks) ->
split_blocks_1(Ls, P, Blocks, Count).
-spec trim_unreachable(SSA0) -> SSA when
@@ -707,7 +718,7 @@ definitions(Labels, Blocks) ->
-spec uses(Labels, Blocks) -> usage_map() when
Labels :: [label()],
Blocks :: block_map().
-uses(Labels, Blocks) ->
+uses(Labels, Blocks) when is_map(Blocks) ->
fold_blocks(fun fold_uses_block/3, Labels, #{}, Blocks).
fold_uses_block(Lbl, #b_blk{is=Is,last=Last}, UseMap0) ->
diff --git a/lib/compiler/src/beam_ssa_alias.erl b/lib/compiler/src/beam_ssa_alias.erl
new file mode 100644
index 0000000000..c16011ebc6
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_alias.erl
@@ -0,0 +1,926 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(beam_ssa_alias).
+
+-export([opt/2]).
+
+-import(lists, [foldl/3, reverse/1, zip/2]).
+
+%% The maximum number of iterations when calculating alias
+%% information.
+-define(MAX_REPETITIONS, 16).
+
+-include("beam_ssa_opt.hrl").
+-include("beam_types.hrl").
+
+%% -define(DEBUG, true).
+
+-ifdef(DEBUG).
+-define(DP(FMT, ARGS), io:format(FMT, ARGS)).
+-define(DP(FMT), io:format(FMT)).
+-else.
+-define(DP(FMT, ARGS), skip).
+-define(DP(FMT), skip).
+-endif.
+
+-type call_args_status_map() :: #{ #b_local{} => ['aliased' | 'unique'] }.
+
+%% Alias analysis state
+-record(aas, {
+ caller :: func_id() | 'undefined',
+ call_args = #{} :: call_args_status_map(),
+ alias_map = #{},
+ func_db :: func_info_db(),
+ kills :: kills_map(),
+ st_map :: st_map(),
+ orig_st_map :: st_map(),
+ repeats = sets:new([{version,2}]) :: sets:set(func_id())
+ }).
+
+%% A code location refering to either the #b_set{} defining a variable
+%% or the terminator of a block.
+-type kill_loc() :: #b_var{} | {terminator, beam_ssa:label()}.
+
+%% Map a code location to the set of variables which die at that
+%% location.
+-type kill_set() :: #{ kill_loc() => sets:set(#b_var{}) }.
+
+-type kills_map() :: #{ func_id() => kill_set() }.
+
+%% Record holding the liveness information for a code location.
+-record(liveness_st, {
+ in = sets:new([{version,2}]) :: sets:set(#b_var{}),
+ out = sets:new([{version,2}]) :: sets:set(#b_var{})
+ }).
+
+%%%
+%%% Optimization pass which calculates the alias status of values and
+%%% uses the results to transform the code.
+%%%
+-spec opt(st_map(), func_info_db()) -> {st_map(), func_info_db()}.
+
+opt(StMap0, FuncDb0) ->
+ %% Ignore functions which are not in the function db (never
+ %% called) or are stubs for nifs.
+ Funs = [ F || F <- maps:keys(StMap0),
+ is_map_key(F, FuncDb0), not is_nif(F, StMap0)],
+ Liveness = liveness(Funs, StMap0),
+ KillsMap = killsets(Liveness, StMap0),
+
+ aa(Funs, KillsMap, StMap0, FuncDb0).
+
+%%%
+%%% Calculate liveness for each function using the standard iterative
+%%% fixpoint method.
+%%%
+
+-spec liveness([func_id()], st_map()) ->
+ [{func_id(), #{func_id() => {beam_ssa:label(), #liveness_st{}}}}].
+
+liveness([F|Funs], StMap) ->
+ Liveness = liveness_fun(F, StMap),
+ [{F,Liveness}|liveness(Funs, StMap)];
+liveness([], _StMap) ->
+ [].
+
+liveness_fun(F, StMap0) ->
+ #opt_st{ssa=SSA} = map_get(F, StMap0),
+ State0 = #{Lbl => #liveness_st{} || {Lbl,_} <- SSA},
+ UseDefCache = liveness_make_cache(SSA),
+ liveness_blks_fixp(reverse(SSA), State0, false, UseDefCache).
+
+liveness_blks_fixp(_SSA, State0, State0, _UseDefCache) ->
+ State0;
+liveness_blks_fixp(SSA, State0, _Old, UseDefCache) ->
+ State = liveness_blks(SSA, State0, UseDefCache),
+ liveness_blks_fixp(SSA, State, State0, UseDefCache).
+
+liveness_blks([{Lbl,Blk}|Blocks], State0, UseDefCache) ->
+ OutOld = get_live_out(Lbl, State0),
+ #{Lbl:={Defs,Uses}} = UseDefCache,
+ In = sets:union(Uses, sets:subtract(OutOld, Defs)),
+ Out = successor_live_ins(Blk, State0),
+ liveness_blks(Blocks, set_block_liveness(Lbl, In, Out, State0),
+ UseDefCache);
+liveness_blks([], State0, _UseDefCache) ->
+ State0.
+
+get_live_in(Lbl, State) ->
+ #liveness_st{in=In} = map_get(Lbl, State),
+ In.
+
+get_live_out(Lbl, State) ->
+ #liveness_st{out=Out} = map_get(Lbl, State),
+ Out.
+
+set_block_liveness(Lbl, In, Out, State) ->
+ L = map_get(Lbl, State),
+ State#{Lbl => L#liveness_st{in=In,out=Out}}.
+
+successor_live_ins(Blk, State) ->
+ foldl(fun(Lbl, Acc) ->
+ sets:union(Acc, get_live_in(Lbl, State))
+ end, sets:new([{version,2}]), beam_ssa:successors(Blk)).
+
+blk_defs(#b_blk{is=Is}) ->
+ foldl(fun(#b_set{dst=Dst}, Acc) ->
+ sets:add_element(Dst, Acc)
+ end, sets:new([{version,2}]), Is).
+
+blk_effective_uses(#b_blk{is=Is,last=Last}) ->
+ %% We can't use beam_ssa:used/1 on the whole block as it considers
+ %% a use after a def a use and that will derail the liveness
+ %% calculation.
+ blk_effective_uses([Last|reverse(Is)], sets:new([{version,2}])).
+
+blk_effective_uses([I|Is], Uses0) ->
+ Uses = case I of
+ #b_set{dst=Dst} ->
+ %% The uses after the def do not count
+ sets:del_element(Dst, Uses0);
+ _ -> % A terminator, no defs
+ Uses0
+ end,
+ LocalUses = sets:from_list(beam_ssa:used(I), [{version,2}]),
+ blk_effective_uses(Is, sets:union(Uses, LocalUses));
+blk_effective_uses([], Uses) ->
+ Uses.
+
+liveness_make_cache(SSA) ->
+ liveness_make_cache(SSA, #{}).
+
+liveness_make_cache([{Lbl,Blk}|Blocks], Cache0) ->
+ Defs = blk_defs(Blk),
+ Uses = blk_effective_uses(Blk),
+ Cache = Cache0#{Lbl=>{Defs,Uses}},
+ liveness_make_cache(Blocks, Cache);
+liveness_make_cache([], Cache) ->
+ Cache.
+
+%%%
+%%% Predicate to check if a function is the stub for a nif.
+%%%
+-spec is_nif(func_id(), st_map()) -> boolean().
+
+is_nif(F, StMap) ->
+ #opt_st{ssa=[{0,#b_blk{is=Is}}|_]} = map_get(F, StMap),
+ case Is of
+ [#b_set{op=nif_start}|_] ->
+ true;
+ _ -> false
+ end.
+
+%%%
+%%% Calculate the killset for all functions in the liveness
+%%% information.
+%%%
+-spec killsets([{func_id(),
+ #{func_id() => {beam_ssa:label(), #liveness_st{}}}}],
+ st_map()) -> kills_map().
+
+killsets(Liveness, StMap) ->
+ #{F => kills_fun(F, StMap, Live) || {F, Live} <- Liveness}.
+
+%%%
+%%% Calculate the killset for a function. The killset allows us to
+%%% look up the variables that die at a code location.
+%%%
+kills_fun(Fun, StMap, Liveness) ->
+ #opt_st{ssa=SSA} = map_get(Fun, StMap),
+ kills_fun1(SSA, #{}, Liveness).
+
+kills_fun1([{Lbl,Blk}|Blocks], KillsMap0, Liveness) ->
+ KillsMap = kills_block(Lbl, Blk, map_get(Lbl, Liveness), KillsMap0),
+ kills_fun1(Blocks, KillsMap, Liveness);
+kills_fun1([], KillsMap, _) ->
+ KillsMap.
+
+kills_block(Lbl, #b_blk{is=Is,last=Last}, #liveness_st{out=Out}, KillsMap0) ->
+ kills_is([Last|reverse(Is)], Out, KillsMap0, Lbl).
+
+kills_is([I|Is], Live0, KillsMap0, Blk) ->
+ {Live, Key} = case I of
+ #b_set{dst=Dst} ->
+ {sets:del_element(Dst, Live0), Dst};
+ _ ->
+ {Live0, {terminator, Blk}}
+ end,
+ Uses = sets:from_list(beam_ssa:used(I), [{version,2}]),
+ RemainingUses = sets:union(Live0, Uses),
+ Killed = sets:subtract(RemainingUses, Live0),
+ KillsMap = KillsMap0#{Key => Killed},
+ kills_is(Is, sets:union(Live, Killed), KillsMap, Blk);
+kills_is([], _, KillsMap, _) ->
+ KillsMap.
+
+%%%
+%%% Perform an alias analysis of the given functions, alias
+%%% information is added as annotations on the SSA code.
+%%%
+%%% Alias analysis is done by an algorithm inspired by Kotzmann and
+%%% Mössenböck's 2005 algorithm for Escape Analysis
+%%% (https://www.usenix.org/events/vee05/full_papers/p111-kotzmann.pdf),
+%%% particularly their escape equivalent sets. But in contrast to
+%%% Kotzmann and Mössenböck, instead of just tracking escaping values
+%%% we track if a value in a variable is unique and/or aliased.
+%%%
+%%% A variable is said to be unique if it currently is the only live
+%%% variable pointing to a particular term on the heap. Literals and
+%%% non-boxed terms are considered unique.
+%%%
+%%% A variable is said to be aliased if it points to a heap term which
+%%% can be reached by other means than the boxed pointer in the
+%%% variable.
+%%%
+%%% The alias analysis is performed by traversing the functions in the
+%%% module and their code. For each operation the uniqueness and alias
+%%% status are updated. The unique/aliased status is maintained in a
+%%% map which maps a variable to a either a status or another
+%%% variable. Thus constructing equivalent sets in the same way a
+%%% Kotzmann and Mössenböck.
+%%%
+%%% When the analysis finishes each instruction is annotated with
+%%% information about which of its arguments are unique or aliased.
+%%%
+-spec aa([func_id()], kills_map(), st_map(), func_info_db()) ->
+ {st_map(), func_info_db()}.
+
+aa(Funs, KillsMap, StMap, FuncDb) ->
+ %% Set up the argument info to make all incoming arguments to
+ %% exported functions aliased and all non-exported functions
+ %% unique.
+ ArgsInfo =
+ foldl(
+ fun(F=#b_local{}, Acc) ->
+ #func_info{exported=E,arg_types=AT} = map_get(F, FuncDb),
+ S = case E of
+ true -> aliased;
+ false -> unique
+ end,
+ Acc#{F=>[S || _ <- AT]}
+ end, #{}, Funs),
+ AAS = #aas{call_args=ArgsInfo,func_db=FuncDb,kills=KillsMap,
+ st_map=StMap, orig_st_map=StMap},
+ aa_fixpoint(Funs, AAS).
+
+%%%
+%%% Alias analysis works on the whole module and uses its own fixpoint
+%%% loop instead of the fixpoint abstraction in beam_ssa_opt. The
+%%% reason for this is three-fold:
+%%%
+%%% * The termination condition is simpler: the alias map hasn't
+%%% changed.
+%%%
+%%% * Adapting the alias analysis to fit into the beam_ssa_opt
+%%% fixpoint framework would require it to be expressed as three
+%%% passes in the style of ssa_opt_type_start,
+%%% ssa_opt_type_continue, and ssa_opt_type_finish in order to
+%%% create and maintain the state which is now kept in #aas{}.
+%%%
+%%% * As the beam_ssa_opt fixpoint framework doesn't provide a way for
+%%% an optimization to be informed that the fixpoint calculation
+%%% didn't converge within the interation limit and it is unsafe to
+%%% do optimizations on incomplete unique/aliased information it is
+%%% much simpler to explicitly handle it locally instead of trying
+%%% to detect incomplete information in a hypothetical
+%%% ssa_opt_alias_finish pass.
+%%%
+aa_fixpoint(Funs, AAS=#aas{func_db=FuncDb}) ->
+ Order = aa_breadth_first(Funs, FuncDb),
+ aa_fixpoint(Order, Order, AAS#aas.alias_map, AAS, ?MAX_REPETITIONS).
+
+aa_fixpoint([F|Fs], Order, OldAliasMap, AAS0=#aas{st_map=StMap}, Limit) ->
+ #b_local{name=#b_literal{val=_N},arity=_A} = F,
+ AAS1 = AAS0#aas{caller=F},
+ ?DP("-= ~p/~p =-~n", [_N, _A]),
+ {OptSt,AAS2} = aa_fun(F, map_get(F, StMap), AAS1),
+ AAS = AAS2#aas{st_map=StMap#{F => OptSt}},
+ aa_fixpoint(Fs, Order, OldAliasMap, AAS, Limit);
+aa_fixpoint([], _Order, OldAliasMap,
+ #aas{alias_map=OldAliasMap,func_db=FuncDb,st_map=StMap}, _) ->
+ ?DP("**** End of iteration ****~n"),
+ {StMap, FuncDb};
+aa_fixpoint([], _, _, #aas{func_db=FuncDb,orig_st_map=StMap}, 0) ->
+ ?DP("**** End of iteration, too many iterations ****~n"),
+ {StMap, FuncDb};
+aa_fixpoint([], Order, _OldAliasMap,
+ AAS=#aas{alias_map=AliasMap,repeats=Repeats}, Limit) ->
+ ?DP("**** Things have changed, starting next iteration ****~n"),
+ %% Following the depth first order, select those in Repeats.
+ NewOrder = [Id || Id <- Order, sets:is_element(Id, Repeats)],
+ aa_fixpoint(NewOrder, Order, AliasMap,
+ AAS#aas{repeats=sets:new([{version,2}])}, Limit - 1).
+
+aa_fun(F, #opt_st{ssa=Linear0,args=Args}=St,
+ AAS0=#aas{alias_map=AliasMap0,func_db=FuncDb,repeats=Repeats0}) ->
+ %% Initially assume all formal parameters are unique for a
+ %% non-exported function, if we have call argument info in the
+ %% AAS, we use it. For an exported function, all arguments are
+ %% assumed to be aliased.
+ ArgsStatus = aa_get_call_args_status(Args, F, AAS0),
+ SS0 = foldl(fun({Var, Status}, Acc) ->
+ aa_new_ssa_var(Var, Status, Acc)
+ end, #{}, ArgsStatus),
+ ?DP("@@ Args: ~p~n", [ArgsStatus]),
+ {Linear1,SS,AAS1} = aa_blocks(Linear0, SS0, AAS0),
+ ?DP("SS:~n~s~n~n", [SS]),
+ AAS = aa_merge_call_args_status(SS, AAS1),
+
+ AliasMap = AliasMap0#{ F => SS },
+ PrevSS = maps:get(F, AliasMap0, #{}),
+ Repeats = case PrevSS =/= SS of
+ true ->
+ %% Alias status has changed, so schedule both
+ %% our callers and callees for renewed analysis.
+ #{ F := #func_info{in=In,out=Out} } = FuncDb,
+ foldl(fun sets:add_element/2,
+ foldl(fun sets:add_element/2, Repeats0, Out), In);
+ false ->
+ Repeats0
+ end,
+ {St#opt_st{ssa=Linear1}, AAS#aas{alias_map=AliasMap,repeats=Repeats}}.
+
+%% Main entry point for the alias analysis
+aa_blocks([{L,#b_blk{is=Is0,last=T0}=Blk}|Bs0], SS0, AAS0) ->
+ {Is,SS1,AAS1} = aa_is(Is0, SS0, [], AAS0),
+ {T,SS2} = aa_terminator(T0, SS1, AAS1),
+ {Bs,SS,AAS} = aa_blocks(Bs0, SS2, AAS1),
+ {[{L,Blk#b_blk{is=Is,last=T}}|Bs],SS,AAS};
+aa_blocks([], SS, AAS) ->
+ {[],SS, AAS}.
+
+aa_is([I=#b_set{dst=Dst,op=Op,args=Args,anno=Anno0}|Is], SS0, Acc, AAS0) ->
+ SS1 = aa_new_ssa_var(Dst, unique, SS0),
+ {SS, AAS} =
+ case Op of
+ %% Instructions changing the alias status.
+ {bif,Bif} ->
+ {aa_bif(Dst, Bif, Args, SS1, AAS0), AAS0};
+ bs_create_bin ->
+ case Args of
+ [#b_literal{val=Flag},_,Arg|_] when
+ Flag =:= private_append ; Flag =:= append ->
+ case aa_all_dies([Arg], Dst, AAS0) of
+ true ->
+ %% Inherit the status of the argument
+ {aa_join(Dst, Arg, SS1), AAS0};
+ false ->
+ %% We alias with the surviving arg
+ {aa_set_aliased([Dst|Args], SS1), AAS0}
+ end;
+ _ ->
+ %% TODO: Too conservative?
+ {aa_set_aliased([Dst|Args], SS1), AAS0}
+ end;
+ bs_extract ->
+ {aa_set_aliased([Dst|Args], SS1), AAS0};
+ bs_get_tail ->
+ {aa_set_aliased([Dst|Args], SS1), AAS0};
+ bs_match ->
+ {aa_set_aliased([Dst|Args], SS1), AAS0};
+ bs_start_match ->
+ [_,Bin] = Args,
+ {aa_set_aliased([Dst,Bin], SS1), AAS0};
+ build_stacktrace ->
+ %% build_stacktrace can potentially alias anything
+ %% live at this point in the code. We handle it by
+ %% aliasing everything known to us. Touching
+ %% variables which are dead is harmless.
+ {aa_alias_all(SS1), AAS0};
+ call ->
+ {aa_call(Dst, Args, Anno0, SS1, AAS0), AAS0};
+ 'catch_end' ->
+ [_Tag,Arg] = Args,
+ {aa_join(Dst, Arg, SS1), AAS0};
+ extract ->
+ [Arg,_] = Args,
+ {aa_join(Dst, Arg, SS1), AAS0};
+ get_hd ->
+ [Arg] = Args,
+ {aa_pair_extraction(Dst, Arg, hd, SS1), AAS0};
+ get_map_element ->
+ [Map,_Key] = Args,
+ {aa_join(Dst, Map, SS1), AAS0};
+ get_tl ->
+ [Arg] = Args,
+ {aa_pair_extraction(Dst, Arg, tl, SS1), AAS0};
+ get_tuple_element ->
+ [Arg,Idx] = Args,
+ {aa_tuple_extraction(Dst, Arg, Idx, SS1), AAS0};
+ landingpad ->
+ {aa_set_aliased(Dst, SS1), AAS0};
+ make_fun ->
+ [Callee|Env] = Args,
+ aa_make_fun(Dst, Callee, Env, SS1, AAS0);
+ old_make_fun ->
+ [Callee|Env] = Args,
+ aa_make_fun(Dst, Callee, Env, SS1, AAS0);
+ peek_message ->
+ {aa_set_aliased(Dst, SS1), AAS0};
+ phi ->
+ {aa_phi(Dst, Args, SS1), AAS0};
+ put_list ->
+ {aa_construct_term(Dst, Args, SS1, AAS0), AAS0};
+ put_map ->
+ {aa_construct_term(Dst, Args, SS1, AAS0), AAS0};
+ put_tuple ->
+ {aa_construct_term(Dst, Args, SS1, AAS0), AAS0};
+ update_tuple ->
+ {aa_construct_term(Dst, Args, SS1, AAS0), AAS0};
+ update_record ->
+ [_Hint,_Size,Src|Updates] = Args,
+ Values = [Src|aa_update_record_get_vars(Updates)],
+ {aa_construct_term(Dst, Values, SS1, AAS0), AAS0};
+
+ %% Instructions which don't change the alias status
+ {float,_} ->
+ {SS1, AAS0};
+ {succeeded,_} ->
+ {SS1, AAS0};
+ bs_init_writable ->
+ {SS1, AAS0};
+ bs_test_tail ->
+ {SS1, AAS0};
+ has_map_field ->
+ {SS1, AAS0};
+ is_nonempty_list ->
+ {SS1, AAS0};
+ is_tagged_tuple ->
+ {SS1, AAS0};
+ kill_try_tag ->
+ {SS1, AAS0};
+ match_fail ->
+ {SS1, AAS0};
+ new_try_tag ->
+ {SS1, AAS0};
+ nif_start ->
+ {SS1, AAS0};
+ raw_raise ->
+ {SS1, AAS0};
+ recv_marker_bind ->
+ {SS1, AAS0};
+ recv_marker_clear ->
+ {SS1, AAS0};
+ recv_marker_reserve ->
+ {SS1, AAS0};
+ recv_next ->
+ {SS1, AAS0};
+ remove_message ->
+ {SS1, AAS0};
+ resume ->
+ {SS1, AAS0};
+ wait_timeout ->
+ {SS1, AAS0};
+ _ ->
+ exit({unknown_instruction, I})
+ end,
+ aa_is(Is, SS, [aa_update_annotation(I, SS1, AAS)|Acc], AAS);
+aa_is([], SS, Acc, AAS) ->
+ {reverse(Acc), SS, AAS}.
+
+aa_terminator(T=#b_br{anno=Anno0}, SS0, AAS) ->
+ Anno = aa_update_annotation(Anno0, SS0, AAS),
+ {T#b_br{anno=Anno}, SS0};
+aa_terminator(T=#b_ret{arg=Arg,anno=Anno0}, SS0, AAS) ->
+ Type = maps:get(result_type, Anno0, any),
+ Status0 = aa_get_status(Arg, SS0),
+ ?DP("Returned ~p:~p:~p~n", [Arg, Status0, Type]),
+ Type2Status0 = maps:get(returns, SS0, #{}),
+ Status = case Type2Status0 of
+ #{ Type := OtherStatus } ->
+ aa_meet(Status0, OtherStatus);
+ #{ } ->
+ Status0
+ end,
+ Type2Status = Type2Status0#{ Type => Status },
+ ?DP("new status map: ~p~n", [Type2Status]),
+ SS = SS0#{ returns => Type2Status},
+ {aa_update_annotation(T, SS, AAS), SS};
+aa_terminator(T=#b_switch{anno=Anno0}, SS0, AAS) ->
+ Anno = aa_update_annotation(Anno0, SS0, AAS),
+ {T#b_switch{anno=Anno}, SS0}.
+
+%% Add a new ssa variable to the alias state and set its status.
+aa_new_ssa_var(Var, Status, State) ->
+ false = maps:get(Var, State, false), % Assertion
+ State#{Var => {status, Status}}.
+
+aa_get_representative(Var, State) ->
+ %% TODO: Consider path compression
+ case State of
+ #{ Var := {status, _} } ->
+ Var;
+ #{ Var := Parent } ->
+ aa_get_representative(Parent, State)
+ end.
+
+aa_get_status(V=#b_var{}, State) ->
+ Repr = aa_get_representative(V, State),
+ #{ Repr := {status, S} } = State,
+ S;
+aa_get_status(#b_literal{}, _State) ->
+ unique.
+
+aa_set_status(V=#b_var{}, Status, State) ->
+ Repr = aa_get_representative(V, State),
+ State#{ Repr => {status, Status} };
+aa_set_status(#b_literal{}, _Status, State) ->
+ State;
+aa_set_status([X|T], Status, State) ->
+ aa_set_status(X, Status, aa_set_status(T, Status, State));
+aa_set_status([], _, State) ->
+ State.
+
+aa_update_annotation(I=#b_set{anno=Anno0,args=Args,op=Op}, SS, AAS) ->
+ {Aliased,Unique} =
+ foldl(fun(#b_var{}=V, {As,Us}) ->
+ case aa_get_status(V, SS) of
+ aliased ->
+ {ordsets:add_element(V, As), Us};
+ unique ->
+ {As, ordsets:add_element(V, Us)}
+ end;
+ (_, A) ->
+ A
+ end, {ordsets:new(),ordsets:new()}, Args),
+ Anno1 = case Aliased of
+ [] -> maps:remove(aliased, Anno0);
+ _ -> Anno0#{aliased => Aliased}
+ end,
+ Anno2 = case Unique of
+ [] -> maps:remove(unique, Anno1);
+ _ -> Anno1#{unique => Unique}
+ end,
+ Anno = case {Op,Args} of
+ {bs_create_bin,[#b_literal{val=append},_,Var|_]} ->
+ %% Alias analysis indicate the alias status of the
+ %% instruction arguments before the instruction is
+ %% executed. For the private-append optimization we
+ %% need to know if the first fragment dies with
+ %% this instruction or not. Adding an annotation
+ %% here, during alias analysis, is more efficient
+ %% than trying to reconstruct information in the
+ %% kill map during the private-append pass.
+ #aas{caller=Caller,kills=KillsMap} = AAS,
+ #b_set{dst=Dst} = I,
+ KillMap = map_get(Caller, KillsMap),
+ Dies = sets:is_element(Var, map_get(Dst, KillMap)),
+ Anno2#{first_fragment_dies => Dies};
+ _ ->
+ Anno2
+ end,
+ I#b_set{anno=Anno};
+aa_update_annotation(I=#b_ret{arg=#b_var{}=V,anno=Anno0}, SS, _AAS) ->
+ Anno = case aa_get_status(V, SS) of
+ aliased ->
+ maps:remove(unique, Anno0#{aliased=>[V]});
+ unique ->
+ maps:remove(aliased, Anno0#{unique=>[V]})
+ end,
+ I#b_ret{anno=Anno};
+aa_update_annotation(I, _SS, _AAS) ->
+ %% For now we don't care about the other terminators.
+ I.
+
+aa_set_aliased(Args, SS) ->
+ aa_set_status(Args, aliased, SS).
+
+aa_alias_all(SS0) ->
+ maps:map(fun(#b_var{}, _) ->
+ {status,aliased};
+ (returns, Types) ->
+ #{ T => aliased || T := _ <- Types};
+ (_, V) ->
+ V
+ end, SS0).
+
+aa_join_ls(VarA, [#b_var{}=VarB|Vars], State) ->
+ aa_join_ls(VarB, Vars, aa_join(VarA, VarB, State));
+aa_join_ls(VarA, [_|Vars], State) ->
+ aa_join_ls(VarA, Vars, State);
+aa_join_ls(_, [], State) ->
+ State.
+
+aa_join(#b_var{}=VarA, #b_var{}=VarB, State) ->
+ ARepr = aa_get_representative(VarA, State),
+ BRepr = aa_get_representative(VarB, State),
+ case {ARepr, BRepr} of
+ {Repr, Repr} ->
+ State;
+ _ ->
+ {status, A} = map_get(ARepr, State),
+ {status, B} = map_get(BRepr, State),
+ State#{ ARepr => {status, aa_meet(A, B)}, BRepr => ARepr }
+ end;
+aa_join(_, _, State) ->
+ State.
+
+aa_meet(#b_var{}=Var, SetStatus, State) ->
+ Repr = aa_get_representative(Var, State),
+ {status, Status} = map_get(Repr, State),
+ State#{ Repr => {status, aa_meet(SetStatus, Status)} };
+aa_meet(#b_literal{}, _SetStatus, State) ->
+ State;
+aa_meet([Var|Vars], [Status|Statuses], State) ->
+ aa_meet(Vars, Statuses, aa_meet(Var, Status, State));
+aa_meet([], [], State) ->
+ State.
+
+aa_meet(StatusA, StatusB) ->
+ case {StatusA, StatusB} of
+ {_,aliased} -> aliased;
+ {aliased, _} -> aliased;
+ {unique, unique} -> unique
+ end.
+
+aa_meet([H|T]) ->
+ aa_meet(H, aa_meet(T));
+aa_meet([]) ->
+ unique.
+
+%%
+%% Type is always less specific or exactly the same as one of the
+%% types in StatusByType, so we need to meet all possible statuses for
+%% the call site.
+%%
+aa_get_status_by_type(Type, StatusByType) ->
+ Statuses = [Status || Candidate := Status <- StatusByType,
+ beam_types:meet(Type, Candidate) =/= none],
+ aa_meet(Statuses).
+
+%% Predicate to check if all variables in `Vars` dies at `Where`.
+-spec aa_all_dies([#b_var{}], kill_loc(), #aas{}) -> boolean().
+aa_all_dies(Vars, Where, #aas{caller=Caller,kills=Kills}) ->
+ KillMap = map_get(Caller, Kills),
+ KillSet = map_get(Where, KillMap),
+ aa_all_dies(Vars, KillSet).
+
+aa_all_dies([#b_literal{}|Vars], KillSet) ->
+ aa_all_dies(Vars, KillSet);
+aa_all_dies([#b_var{}=V|Vars], KillSet) ->
+ case sets:is_element(V, KillSet) of
+ true ->
+ aa_all_dies(Vars, KillSet);
+ false ->
+ false
+ end;
+aa_all_dies([], _) ->
+ true.
+
+aa_alias_if_args_dont_die(Args, Where, SS, AAS) ->
+ case aa_all_dies(Args, Where, AAS) of
+ true ->
+ SS;
+ false ->
+ aa_set_aliased([Where|Args], SS)
+ end.
+
+%% Check that a variable in Args only occurs once, literals are
+%% ignored.
+aa_all_vars_unique(Args) ->
+ aa_all_vars_unique(Args, #{}).
+
+aa_all_vars_unique([#b_literal{}|Args], Seen) ->
+ aa_all_vars_unique(Args, Seen);
+aa_all_vars_unique([#b_var{}=V|Args], Seen) ->
+ case Seen of
+ #{ V := _ } ->
+ false;
+ #{} ->
+ aa_all_vars_unique(Args, Seen#{V => true })
+ end;
+aa_all_vars_unique([], _) ->
+ true.
+
+aa_construct_term(Dst, Values, SS, AAS) ->
+ case aa_all_vars_unique(Values)
+ andalso aa_all_dies(Values, Dst, AAS) of
+ true ->
+ aa_join_ls(Dst, Values, SS);
+ false ->
+ aa_set_aliased([Dst|Values], SS)
+ end.
+
+aa_update_record_get_vars([#b_literal{}, Value|Updates]) ->
+ [Value|aa_update_record_get_vars(Updates)];
+aa_update_record_get_vars([]) ->
+ [].
+
+aa_bif(Dst, element, [_Idx,Tuple], SS, AAS) ->
+ %% If we extract a value and the aggregate dies and wasn't aliased,
+ %% we should not consider this an aliasing operation.
+ aa_alias_if_args_dont_die([Tuple], Dst, SS, AAS);
+aa_bif(Dst, hd, Args, SS, AAS) ->
+ %% If we extract a value and the aggregate dies and wasn't aliased,
+ %% we should not consider this an aliasing operation.
+ aa_alias_if_args_dont_die(Args, Dst, SS, AAS);
+aa_bif(Dst, tl, Args, SS, AAS) ->
+ %% If we extract a value and the aggregate dies and wasn't aliased,
+ %% we should not consider this an aliasing operation.
+ aa_alias_if_args_dont_die(Args, Dst, SS, AAS);
+%% TODO: Ignored for now, as we don't track what's inside maps.
+%% aa_bif(_Dst, map_get, _Args, SS, _AAS) ->
+%% SS;
+aa_bif(Dst, Bif, Args, SS, _AAS) ->
+ Arity = length(Args),
+ case erl_internal:guard_bif(Bif, Arity)
+ orelse erl_internal:bool_op(Bif, Arity)
+ orelse erl_internal:comp_op(Bif, Arity)
+ orelse erl_internal:arith_op(Bif, Arity)
+ orelse erl_internal:new_type_test(Bif, Arity) of
+ true ->
+ SS;
+ false ->
+ %% Assume anything else shares the arguments and returns an
+ %% aliased result.
+ aa_set_aliased([Dst|Args], SS)
+ end.
+
+aa_phi(Dst, Args0, SS) ->
+ Args = [V || {V,_} <- Args0],
+ aa_join_ls(Dst, Args, SS).
+
+aa_call(Dst, [#b_local{}=Callee|Args], Anno, SS0,
+ #aas{alias_map=AliasMap,st_map=StMap}) ->
+ #b_local{name=#b_literal{val=_N},arity=_A} = Callee,
+ ?DP("A Call~n callee: ~p/~p~n args: ~p~n", [_N, _A, Args]),
+ IsNif = is_nif(Callee, StMap),
+ case AliasMap of
+ #{ Callee := CalleeSS } when not IsNif ->
+ ?DP(" The callee is known~n"),
+ #opt_st{args=CalleeArgs} = map_get(Callee, StMap),
+ ?DP(" args in caller: ~p~n",
+ [[{Arg, aa_get_status(Arg, SS0)} || Arg <- Args]]),
+ ArgStates = [ aa_get_status(Arg, CalleeSS) || Arg <- CalleeArgs],
+ ?DP(" callee arg states: ~p~n", [ArgStates]),
+ SS1 = aa_add_call_info(Callee, Args, SS0),
+ SS = aa_meet(Args, ArgStates, SS1),
+ ?DP(" meet: ~p~n",
+ [[{Arg, aa_get_status(Arg, SS1)} || Arg <- Args]]),
+ ReturnStatusByType = maps:get(returns, CalleeSS, #{}),
+ ?DP(" status by type: ~p~n", [ReturnStatusByType]),
+ ReturnedType = case Anno of
+ #{ result_type := ResultType } ->
+ ResultType;
+ #{} ->
+ any
+ end,
+ %% ReturnedType is always less specific or exactly the
+ %% same as one of the types in ReturnStatusByType.
+ ?DP(" returned type: ~s~n",
+ [beam_ssa_pp:format_type(ReturnedType)]),
+ ResultStatus = aa_get_status_by_type(ReturnedType,
+ ReturnStatusByType),
+ ?DP(" result status: ~p~n", [ResultStatus]),
+ aa_set_status(Dst, ResultStatus, SS);
+ _ when IsNif ->
+ %% This is a nif, assume that all arguments will be
+ %% aliased and that the result is aliased.
+ aa_set_aliased([Dst|Args], SS0);
+ #{} ->
+ %% We don't know anything about the function, don't change
+ %% the status of any variables
+ SS0
+ end;
+aa_call(Dst, [_Callee|Args], _Anno, SS, _AAS) ->
+ %% This is either a call to a fun or to an external function,
+ %% assume that all arguments and the result escape.
+ aa_set_aliased([Dst|Args], SS).
+
+%% Add info about the aliasing status of the arguments to the call
+aa_add_call_info(Callee, Args, SS0) ->
+ ArgStats = [aa_get_status(Arg, SS0) || Arg <- Args],
+ NewStats = case SS0 of
+ #{{call_info, Callee} := Stats} ->
+ [aa_meet(A, B) || {A,B} <- zip(Stats, ArgStats)];
+ #{} ->
+ ArgStats
+ end,
+ SS0#{{call_info, Callee} => NewStats}.
+
+%% Incorporate aliasing information derived when analysing the body of
+%% a function into the module-global state.
+aa_merge_call_args_status(SS, AAS=#aas{call_args=Info0}) ->
+ Info =
+ maps:fold(fun({call_info,Callee}, NewArgs, Acc) ->
+ #{ Callee := OldArgs } = Acc,
+ Args = [aa_meet(A, B)
+ || {A,B} <- zip(NewArgs, OldArgs)],
+ Acc#{Callee => Args};
+ (_, _, Acc) ->
+ Acc
+ end, Info0, SS),
+ AAS#aas{call_args=Info}.
+
+aa_get_call_args_status(Args, Callee, #aas{call_args=Info}) ->
+ #{ Callee := Status } = Info,
+ zip(Args, Status).
+
+aa_pair_extraction(Dst, Pair, Element, SS0) ->
+ case SS0 of
+ #{{pair,Pair}:=both} ->
+ %% Both elements have already been extracted
+ aa_set_aliased([Dst,Pair], SS0);
+ #{{pair,Pair}:=Element} ->
+ %% This element has already been extracted
+ aa_set_aliased([Dst,Pair], SS0);
+ #{{pair,Pair}:=_Other} ->
+ %% Both elements have now been extracted
+ aa_join(Dst, Pair, SS0#{{pair,Pair}=>both});
+ _ ->
+ %% Nothing has been extracted from this pair
+ aa_join(Dst, Pair, SS0#{{pair,Pair}=>Element})
+ end.
+
+aa_tuple_extraction(Dst, Tuple, #b_literal{val=I}, SS1) ->
+ case SS1 of
+ #{{tuple_element,Tuple}:=OrdSet0} ->
+ case ordsets:is_element(I, OrdSet0) of
+ true ->
+ aa_set_aliased([Dst,Tuple], SS1);
+ false ->
+ OrdSet = ordsets:add_element(I, OrdSet0),
+ aa_join(Dst, Tuple, SS1#{{tuple_element,Tuple}=>OrdSet})
+ end;
+ _ ->
+ %% There are no aliases yet.
+ aa_join(Dst, Tuple, SS1#{{tuple_element,Tuple}=>[I]})
+ end.
+
+aa_make_fun(Dst, Callee=#b_local{name=#b_literal{}},
+ Env0, SS0,
+ AAS0=#aas{call_args=Info0,repeats=Repeats0}) ->
+ %% When a value is copied into the environment of a fun we assume
+ %% that it has been aliased as there is no obvious way to track
+ %% and ensure that the value is only used once, even if the
+ %% argument dies at this location.
+ %%
+ %% We also assume that all arguments are aliased because someone
+ %% could have stolen the function through tracing and called it
+ %% with unexpected arguments, which may be aliased.
+ SS = aa_set_aliased([Dst|Env0], SS0),
+ #{ Callee := Status0 } = Info0,
+ Status = [aliased || _ <- Status0],
+ #{ Callee := PrevStatus } = Info0,
+ Info = Info0#{ Callee := Status },
+ Repeats = case PrevStatus =/= Status of
+ true ->
+ %% We have new information for the callee, we
+ %% have to revisit it.
+ sets:add_element(Callee, Repeats0);
+ false ->
+ Repeats0
+ end,
+ AAS = AAS0#aas{call_args=Info,repeats=Repeats},
+ {SS, AAS}.
+
+aa_breadth_first(Funs, FuncDb) ->
+ IsExported = fun (F) ->
+ #{ F := #func_info{exported=E} } = FuncDb,
+ E
+ end,
+ Exported = [ F || F <- Funs, IsExported(F)],
+ aa_breadth_first(Exported, [], sets:new([{version,2}]), FuncDb).
+
+aa_breadth_first([F|Work], Next, Seen, FuncDb) ->
+ case sets:is_element(F, Seen) of
+ true ->
+ aa_breadth_first(Work, Next, Seen, FuncDb);
+ false ->
+ case FuncDb of
+ #{ F := #func_info{out=Children} } ->
+ [F|aa_breadth_first(Work, Children ++ Next,
+ sets:add_element(F, Seen), FuncDb)];
+ #{} ->
+ %% Other optimization steps can have determined
+ %% that the function is not called and removed it
+ %% from the funcdb, but it still remains in the
+ %% #func_info{} of the (at the syntax-level)
+ %% caller.
+ aa_breadth_first(Work, Next, Seen, FuncDb)
+ end
+ end;
+aa_breadth_first([], [], _Seen, _FuncDb) ->
+ [];
+aa_breadth_first([], Next, Seen, FuncDb) ->
+ aa_breadth_first(Next, [], Seen, FuncDb).
+
diff --git a/lib/compiler/src/beam_ssa_bc_size.erl b/lib/compiler/src/beam_ssa_bc_size.erl
index c48a7c8cf6..1c4dd58048 100644
--- a/lib/compiler/src/beam_ssa_bc_size.erl
+++ b/lib/compiler/src/beam_ssa_bc_size.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -44,7 +44,7 @@
-spec opt(st_map()) -> st_map().
-opt(StMap) ->
+opt(StMap) when is_map(StMap) ->
opt(maps:keys(StMap), StMap).
opt([Id|Ids], StMap0) ->
@@ -71,7 +71,8 @@ opt_function(Id, StMap) ->
opt_blks([{L,#b_blk{is=Is}=Blk}|Blks], ParamInfo, StMap, AnyChange, Count0, Acc0) ->
case Is of
[#b_set{op=bs_init_writable,dst=Dst}] ->
- Bs = #{st_map => StMap, Dst => {writable,#b_literal{val=0}}},
+ Bs = #{st_map => StMap, Dst => {writable,#b_literal{val=0}},
+ seen => sets:new([{version,2}])},
try opt_writable(Bs, L, Blk, Blks, ParamInfo, Count0, Acc0) of
{Acc,Count} ->
opt_blks(Blks, ParamInfo, StMap, changed, Count, Acc)
@@ -94,7 +95,7 @@ opt_writable(Bs0, L, Blk, Blks, ParamInfo, Count0, Acc0) ->
last=CallLast}}|_]} ->
ensure_not_match_context(Call, ParamInfo),
- ArgTypes = maps:from_list([{Arg,{arg,Arg}} || Arg <- Args]),
+ ArgTypes = #{Arg => {arg,Arg} || Arg <- Args},
Bs = maps:merge(ArgTypes, Bs0),
Result = map_get(Dst, call_size_func(Call, Bs)),
{Expr,Annos} = make_expr_tree(Result),
@@ -153,10 +154,32 @@ call_size_func(#b_set{anno=Anno,op=call,args=[Name|Args],dst=Dst}, Bs) ->
%% and there is no need to analyze it.
Bs#{Dst => any};
true ->
- NewBs = NewBs0#{Name => self, st_map => StMap},
- Map0 = #{0 => NewBs},
- Result = calc_size(Linear, Map0),
- Bs#{Dst => Result}
+ Seen0 = map_get(seen, Bs),
+ case sets:is_element(Name, Seen0) of
+ true ->
+ %% This can happen if there is a call such as:
+ %%
+ %% foo(<< 0 || false >>)
+ %%
+ %% Essentially, this is reduced to:
+ %%
+ %% foo(<<>>)
+ %%
+ %% This sub pass will then try to analyze
+ %% the code in foo/1 and everything it
+ %% calls. To prevent an infinite loop in
+ %% case there is mutual recursion between
+ %% some of the functions called by foo/1,
+ %% give up if function that has already
+ %% been analyzed is called again.
+ throw(not_possible);
+ false ->
+ Seen = sets:add_element(Name, Seen0),
+ NewBs = NewBs0#{Name => self, st_map => StMap, seen => Seen},
+ Map0 = #{0 => NewBs},
+ Result = calc_size(Linear, Map0),
+ Bs#{Dst => Result}
+ end
end;
#{} ->
case Name of
@@ -199,7 +222,7 @@ setup_call_bs([], [], #{}, NewBs) -> NewBs.
calc_size([{L,#b_blk{is=Is,last=Last}}|Blks], Map0) ->
case maps:take(L, Map0) of
- {Bs0,Map1} ->
+ {Bs0,Map1} when is_map(Bs0) ->
Bs1 = calc_size_is(Is, Bs0),
Map2 = update_successors(Last, Bs1, Map1),
case get_ret(Last, Bs1) of
@@ -239,6 +262,8 @@ update_successors(#b_br{bool=Bool,succ=Succ,fail=Fail}, Bs0, Map0) ->
case get_value(Bool, Bs0) of
#b_literal{val=true} ->
update_successor(Succ, Bs0, Map0);
+ #b_literal{val=false} ->
+ update_successor(Fail, Bs0, Map0);
{succeeded,Var} ->
Map = update_successor(Succ, Bs0, Map0),
update_successor(Fail, maps:remove(Var, Bs0), Map);
diff --git a/lib/compiler/src/beam_ssa_bool.erl b/lib/compiler/src/beam_ssa_bool.erl
index fb72a78c29..7633f8c9a1 100644
--- a/lib/compiler/src/beam_ssa_bool.erl
+++ b/lib/compiler/src/beam_ssa_bool.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1198,9 +1198,7 @@ redirect_phi_1(_PhiVtx, _Args, _SuccFail, _G, _St) ->
digraph_bool_def(G) ->
Vs = beam_digraph:vertices(G),
- Ds = [{Dst,Vtx} || {Vtx,#b_set{dst=Dst}} <- Vs],
- maps:from_list(Ds).
-
+ #{Dst => Vtx || {Vtx,#b_set{dst=Dst}} <- Vs}.
%% ensure_disjoint_paths(G, Vertex1, Vertex2) -> ok.
%% Ensure that there is no path from Vertex1 to Vertex2 in
@@ -1378,8 +1376,7 @@ ensure_init(Root, G, G0) ->
%% Build a map of all variables that are set by instructions in
%% the digraph. Variables not included in this map have been
%% defined by code before the code in the digraph.
- Vars = maps:from_list([{Dst,unset} ||
- {_,#b_set{dst=Dst}} <- Vs]),
+ Vars = #{Dst => unset || {_,#b_set{dst=Dst}} <- Vs},
RPO = beam_digraph:reverse_postorder(G, [Root]),
ensure_init_1(RPO, Used, G, #{Root=>Vars}).
@@ -1403,7 +1400,7 @@ ensure_init_instr(Vtx, Used, G, InitMaps0) ->
%% originate from a guard, it is possible that a
%% variable set in the optimized code will be used
%% here.
- case [V || {V,unset} <- maps:to_list(VarMap0)] of
+ case [V || V := unset <- VarMap0] of
[] ->
InitMaps0;
[_|_]=Unset0 ->
diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl
index f84ae7dced..71736b3bc0 100644
--- a/lib/compiler/src/beam_ssa_bsm.erl
+++ b/lib/compiler/src/beam_ssa_bsm.erl
@@ -473,10 +473,27 @@ combine_matches(#b_function{bs=Blocks0,cnt=Counter0}=F, ModInfo) ->
%% so we can reuse the RPO computed for Blocks0.
Blocks2 = beam_ssa:rename_vars(State#cm.renames, RPO, Blocks1),
- {Blocks, Counter} = alias_matched_binaries(Blocks2, Counter0,
- State#cm.match_aliases),
-
- F#b_function{ bs=beam_ssa:trim_unreachable(Blocks),
+ %% Replacing variables with the atom `true` can cause
+ %% branches to phi nodes to be omitted, with the phi nodes
+ %% still referencing the unreachable blocks. Therefore,
+ %% trim now to update the phi nodes.
+ Blocks3 = beam_ssa:trim_unreachable(Blocks2),
+
+ Aliases = State#cm.match_aliases,
+ {Blocks4, Counter} = alias_matched_binaries(Blocks3, Counter0,
+ Aliases),
+ Blocks = if
+ map_size(Aliases) =:= 0 ->
+ %% No need to trim because there were no aliases.
+ Blocks4;
+ true ->
+ %% Play it safe. It is unclear whether
+ %% the call to alias_matched_binaries/3
+ %% could ever make any blocks
+ %% unreachable.
+ beam_ssa:trim_unreachable(Blocks4)
+ end,
+ F#b_function{ bs=Blocks,
cnt=Counter };
false ->
F
diff --git a/lib/compiler/src/beam_ssa_check.erl b/lib/compiler/src/beam_ssa_check.erl
new file mode 100644
index 0000000000..f344286943
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_check.erl
@@ -0,0 +1,378 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+
+-module(beam_ssa_check).
+
+-export([module/2, format_error/1]).
+
+-import(lists, [reverse/1, flatten/1]).
+
+-include("beam_ssa.hrl").
+
+%%-define(DEBUG, true).
+
+-ifdef(DEBUG).
+-define(DP(FMT, ARGS), io:format(FMT, ARGS)).
+-define(DP(FMT), io:format(FMT)).
+-else.
+-define(DP(FMT, ARGS), skip).
+-define(DP(FMT), skip).
+-endif.
+
+-spec module(beam_ssa:b_module(), atom()) -> 'ok' | {'error',list()}.
+
+module(#b_module{body=Body}, Tag) ->
+ Errors = functions(Tag, Body),
+ case Errors of
+ [] ->
+ ok;
+ _ ->
+ {error, reverse(Errors)}
+ end.
+
+functions(Tag, [F|Rest]) ->
+ function(Tag, F) ++ functions(Tag, Rest);
+functions(_Tag, []) ->
+ [].
+
+function(Tag, F) ->
+ run_checks(beam_ssa:get_anno(ssa_checks, F, []), F, Tag).
+
+run_checks([{ssa_check_when,WantedResult,{atom,_,Tag},Args,Exprs}|Checks],
+ F, Tag) ->
+ check_function(Args, Exprs, WantedResult, F) ++ run_checks(Checks, F, Tag);
+run_checks([_|Checks], F, Tag) ->
+ run_checks(Checks, F, Tag);
+run_checks([], _, _) ->
+ [].
+
+check_function(CheckArgs, Exprs, {atom,Loc,pass}, #b_function{args=_Args}=F) ->
+ run_check(CheckArgs, Exprs, Loc, F);
+check_function(CheckArgs, Exprs, {atom,Loc,Key}, #b_function{args=_Args}=F)
+ when Key =:= fail ; Key =:= xfail ->
+ case run_check(CheckArgs, Exprs, Loc, F) of
+ [] ->
+ %% This succeeded but should have failed
+ {File,_} = beam_ssa:get_anno(location, F),
+ [{File,[{Loc,?MODULE,xfail_passed}]}];
+ _ ->
+ []
+ end;
+check_function(_, _, {atom,Loc,Result}, F) ->
+ {File,_} = beam_ssa:get_anno(location, F),
+ [{File,[{Loc,?MODULE,{unknown_result_kind,Result}}]}].
+
+run_check(CheckArgs, Exprs, Loc, #b_function{args=FunArgs}=F) ->
+ init_and_run_check(CheckArgs, FunArgs, #{}, Loc, Exprs, F).
+
+
+%% Create a mapping from each argument in the check pattern to the
+%% actual arguments of the SSA function.
+init_and_run_check([{var,Loc,'_'}|CheckArgs],
+ [#b_var{}|FunArgs],
+ Env, _, Exprs, F) ->
+ init_and_run_check(CheckArgs, FunArgs, Env, Loc, Exprs, F);
+init_and_run_check([{var,Loc,CheckV}|CheckArgs],
+ [V=#b_var{}|FunArgs],
+ Env, _, Exprs, F) ->
+ init_and_run_check(CheckArgs, FunArgs, Env#{CheckV=>V}, Loc, Exprs, F);
+init_and_run_check([{'...',_}], [_|_], Env, _Loc, Exprs, F) ->
+ check_exprs(Exprs, Env, F);
+init_and_run_check([], [], Env, _Loc, Exprs, F) ->
+ check_exprs(Exprs, Env, F);
+init_and_run_check([], _, _Env, Loc, _Exprs, F) ->
+ {File,_} = beam_ssa:get_anno(location, F),
+ [{File,[{Loc,?MODULE,too_few_pattern_args}]}];
+init_and_run_check(_, [], _Env, Loc, _Exprs, F) ->
+ {File,_} = beam_ssa:get_anno(location, F),
+ [{File,[{Loc,?MODULE,too_many_pattern_args}]}].
+
+check_exprs(Exprs, Env, #b_function{bs=Blocks}=F) ->
+ %% A function check executes sequentially on the blocks as if they
+ %% were in a beam ssa listing, so arrange the blocks in RPO and
+ %% insert a label for each new block.
+ Code =
+ lists:foldr(fun(Lbl, Acc) ->
+ #b_blk{is=Is,last=Last} = map_get(Lbl, Blocks),
+ [{label,Lbl}|Is] ++ [Last] ++ Acc
+ end, [], beam_ssa:rpo(Blocks)),
+ ?DP(" Env ~p~n", [Env]),
+ ?DP(" Code~n ~p~n", [Code]),
+ ?DP(" Checks~n ~p~n", [Exprs]),
+ {File,_} = beam_ssa:get_anno(location, F),
+ check_expr_seq(Exprs, Code, Env, never, File).
+
+check_expr_seq([{check_expr,Loc,Args,Anno}|Rest]=Checks,
+ [First|Code], Env0, LastMatchedLoc, File) ->
+ Env = try
+ ?DP("trying:~n pat: ~p~n i: ~p~n", [Args, First]),
+ op_check(Args, Anno, First, Env0)
+ catch
+ throw:no_match ->
+ ?DP("op_check did not match~n"),
+ false;
+ error:_E ->
+ ?DP("op_check failed ~p~n", [_E]),
+ false
+ end,
+ case Env of
+ false ->
+ %% Continue with the next op in the code
+ check_expr_seq(Checks, Code, Env0, LastMatchedLoc, File);
+ Env ->
+ %% The code matched
+ check_expr_seq(Rest, Code, Env, Loc, File)
+ end;
+check_expr_seq([], _Blocks, _Env, _LastMatchedLoc, _File) ->
+ %% Done and all expressions matched.
+ [];
+check_expr_seq([{check_expr,Loc,Args,_}|_], [], Env, LastMatchedLoc, File) ->
+ %% We didn't find a match and we have no more code to look at
+ [{File,[{Loc,?MODULE,{no_match,Args,LastMatchedLoc,Env}}]}].
+
+
+op_check([set,Result,{atom,_,Op}|PArgs], PAnno,
+ #b_set{dst=Dst,args=AArgs,op=Op,anno=AAnno}=_I, Env0) ->
+ ?DP("trying set ~p:~n res: ~p <-> ~p~n args: ~p <-> ~p~n i: ~p~n",
+ [Op, Result, Dst, PArgs, AArgs, _I]),
+ Env = check_annos(PAnno, AAnno, Env0),
+ op_check_call(Op, Result, Dst, PArgs, AArgs, Env);
+op_check([set,Result,{{atom,_,bif},{atom,_,Op}}|PArgs], PAnno,
+ #b_set{dst=Dst,args=AArgs,op={bif,Op},anno=AAnno}=_I, Env0) ->
+ ?DP("trying bif ~p:~n res: ~p <-> ~p~n args: ~p <-> ~p~n i: ~p~n",
+ [Op, Result, Dst, PArgs, AArgs, _I]),
+ Env = check_annos(PAnno, AAnno, Env0),
+ op_check_call(Op, Result, Dst, PArgs, AArgs, Env);
+op_check([none,{atom,_,ret}|PArgs], PAnno,
+ #b_ret{arg=AArg,anno=AAnno}=_I, Env0) ->
+ ?DP("trying return:, arg: ~p <-> ~p~n i: ~p~n",
+ [PArgs, [AArg], _I]),
+ Env = check_annos(PAnno, AAnno, Env0),
+ post_args(PArgs, [AArg], Env);
+op_check([none,{atom,_,br}|PArgs], PAnno,
+ #b_br{bool=ABool,succ=ASucc,fail=AFail,anno=AAnno}=_I, Env0) ->
+ ?DP("trying br: arg: ~p <-> ~p~n i: ~p~n",
+ [PArgs, [ABool,ASucc,AFail], _I]),
+ Env = check_annos(PAnno, AAnno, Env0),
+ post_args(PArgs, [ABool,#b_literal{val=ASucc},#b_literal{val=AFail}], Env);
+op_check([none,{atom,_,switch},PArg,PFail,{list,_,PArgs}], PAnno,
+ #b_switch{arg=AArg,fail=AFail,list=AList,anno=AAnno}=_I, Env0) ->
+ ?DP("trying switch: arg: ~p <-> ~p~n i: ~p~n",
+ [PArgs, [AArg,AFail,AList], _I]),
+ Env1 = env_post(PArg, AArg, env_post(PFail, #b_literal{val=AFail}, Env0)),
+ Env = check_annos(PAnno, AAnno, Env1),
+ post_switch_args(PArgs, AList, Env);
+op_check([label,PLbl], _Anno, {label,ALbl}, Env) when is_integer(ALbl) ->
+ env_post(PLbl, #b_literal{val=ALbl}, Env).
+
+op_check_call(Op, PResult, AResult, PArgs, AArgs, Env0) ->
+ Env = env_post(PResult, AResult, Env0),
+ case Op of
+ phi ->
+ post_phi_args(PArgs, AArgs, Env);
+ _ ->
+ post_args(PArgs, AArgs, Env)
+ end.
+
+post_args([{'...',_}], _, Env) ->
+ Env;
+post_args([PA|PArgs], [AA|AArgs], Env) ->
+ post_args(PArgs, AArgs, env_post(PA, AA, Env));
+post_args([], [], Env) ->
+ Env;
+post_args(Pattern, Args, _Env) ->
+ io:format("Failed to match ~kp <-> ~kp~n", [Pattern, Args]),
+ error({internal_pattern_match_error,post_args}).
+
+post_phi_args([{'...',_}], _, Env) ->
+ Env;
+post_phi_args([{tuple,_,[PVar,PLbl]}|PArgs], [{AVar,ALbl}|AArgs], Env0) ->
+ Env = env_post(PVar, AVar, env_post(PLbl, ALbl, Env0)),
+ post_phi_args(PArgs, AArgs, Env);
+post_phi_args([], [], Env) ->
+ Env.
+
+post_switch_args([{'...',_}], _, Env) ->
+ Env;
+post_switch_args([{tuple,_,[PVal,PLbl]}|PArgs], [{AVal,ALbl}|AArgs], Env0) ->
+ Env = env_post(PVal, AVal, env_post(PLbl, #b_literal{val=ALbl}, Env0)),
+ post_switch_args(PArgs, AArgs, Env);
+post_switch_args([], [], Env) ->
+ Env.
+
+env_post({var,_,PV}, Actual, Env) ->
+ env_post1(PV, Actual, Env);
+env_post({atom,_,Atom}, #b_literal{val=Atom}, Env) ->
+ Env;
+env_post({atom,_,Atom}, Atom, Env) when is_atom(Atom) ->
+ Env;
+env_post({local_fun,{atom,_,N},{integer,_,A}},
+ #b_local{name=#b_literal{val=N},arity=A}, Env) ->
+ Env;
+env_post({external_fun,{atom,_,M},{atom,_,N},{integer,_,A}},
+ #b_remote{mod=#b_literal{val=M},name=#b_literal{val=N},arity=A},
+ Env) ->
+ Env;
+env_post({external_fun,{atom,_,M},{atom,_,N},{integer,_,A}},
+ #b_literal{val=F}, Env) ->
+ {M,N,A} = erlang:fun_info_mfa(F),
+ Env;
+env_post({integer,_,V}, #b_literal{val=V}, Env) ->
+ Env;
+env_post({integer,_,V}, V, Env) when is_integer(V) ->
+ Env;
+env_post({float,_,V}, #b_literal{val=V}, Env) ->
+ Env;
+env_post({float,_,V}, V, Env) when is_float(V) ->
+ Env;
+env_post({float_epsilon,{float,_,V},{float,_,Epsilon}},
+ #b_literal{val=Actual}, Env) ->
+ true = abs(V - Actual) < Epsilon,
+ Env;
+env_post({float_epsilon,{float,_,V},{float,_,Epsilon}}, Actual, Env)
+ when is_float(Actual) ->
+ true = abs(V - Actual) < Epsilon,
+ Env;
+env_post({binary,_,Bits}, #b_literal{val=V}, Env) ->
+ post_bitstring(Bits, V, Env);
+env_post({binary,_,Bits}, Bin, Env) when is_bitstring(Bin)->
+ post_bitstring(Bits, Bin, Env);
+env_post({list,_,Elems}, #b_literal{val=Ls}, Env) ->
+ post_list(Elems, Ls, Env);
+env_post({list,_,Elems}, Ls, Env) when is_list(Ls) ->
+ post_list(Elems, Ls, Env);
+env_post({tuple,_,Es}, #b_literal{val=Ls}, Env) ->
+ post_tuple(Es, tuple_to_list(Ls), Env);
+env_post({tuple,_,Es}, Tuple, Env) when is_tuple(Tuple) ->
+ post_tuple(Es, tuple_to_list(Tuple), Env);
+env_post({map,_,Elems}, #b_literal{val=Map}, Env) when is_map(Map) ->
+ post_map(Elems, Map, Env);
+env_post({map,_,Elems}, Map, Env) when is_map(Map) ->
+ post_map(Elems, Map, Env);
+env_post(_Pattern, _Args, _Env) ->
+ ?DP("Failed to match ~p <-> ~p~n", [_Pattern, _Args]),
+ error({internal_pattern_match_error,env_post}).
+
+env_post1('_', _Actual, Env) ->
+ ?DP("Ignored posting _ => ~p~n", [_Actual]),
+ Env;
+env_post1(PV, Actual, Env) when is_map_key(PV, Env) ->
+ ?DP("Attempting post ~p => ~p in ~p~n", [PV, Actual, Env]),
+ Actual = map_get(PV, Env),
+ Env;
+env_post1(PV, #b_var{}=Actual, Env) ->
+ ?DP("Posting ~p => ~p~n", [PV, Actual]),
+ Env#{PV => Actual};
+env_post1(PV, #b_literal{}=Actual, Env) ->
+ ?DP("Posting ~p => ~p~n", [PV, Actual]),
+ Env#{PV => Actual};
+env_post1(_Pattern, _Actual, _Env) ->
+ ?DP("Failed to match ~p <-> ~p~n", [_Pattern, _Actual]),
+ error({internal_pattern_match_error,env_post1}).
+
+post_bitstring(Bytes, Actual, Env) ->
+ Actual = build_bitstring(Bytes, <<>>),
+ Env.
+
+%% Convert the parsed literal binary to an actual bitstring.
+build_bitstring([{integer,_,V}|Bytes], Acc) ->
+ build_bitstring(Bytes, <<Acc/bits, V:8>>);
+build_bitstring([{{integer,_,V},{integer,_,N}}|Bytes], Acc) ->
+ build_bitstring(Bytes, <<Acc/bits, V:N>>);
+build_bitstring([], Acc) ->
+ Acc.
+
+post_list([{'...',_}], _, Env) ->
+ Env;
+post_list([Elem|Elements], [A|Actual], Env0) ->
+ Env = env_post(Elem, A, Env0),
+ post_list(Elements, Actual, Env);
+post_list([], [], Env) ->
+ Env;
+post_list(Elem, Actual, Env) ->
+ env_post(Elem, Actual, Env).
+
+post_tuple([{'...',_}], _, Env) ->
+ Env;
+post_tuple([Elem|Elements], [A|Actual], Env0) ->
+ Env = env_post(Elem, A, Env0),
+ post_tuple(Elements, Actual, Env);
+post_tuple([], [], Env) ->
+ Env.
+
+post_map([{Key,Val}|Items], Map, Env) ->
+ K = build_map_key(Key),
+ V = build_map_key(Val),
+ #{K := V} = Map,
+
+ post_map(Items, maps:remove(K, Map), Env);
+post_map([], Map, Env) ->
+ 0 = maps:size(Map),
+ Env.
+
+build_map_key({atom,_,A}) ->
+ A;
+build_map_key({local_fun,{atom,_,N},{integer,_,A}}) ->
+ #b_local{name=#b_literal{val=N},arity=A};
+build_map_key({integer,_,V}) ->
+ V;
+build_map_key({float,_,V}) ->
+ V;
+build_map_key({binary,_,Bits}) ->
+ build_bitstring(Bits, <<>>);
+build_map_key({list,_,Elems}) ->
+ build_map_key_list(Elems);
+build_map_key({tuple,_,Elems}) ->
+ list_to_tuple([build_map_key(E) || E <- Elems]);
+build_map_key({map,_,Elems}) ->
+ #{build_map_key(K) => build_map_key(V) || {K,V} <- Elems};
+build_map_key(_Key) ->
+ ?DP("Failed to match ~p~n", [_Key]),
+ error({internal_pattern_match_error,build_map_key}).
+
+build_map_key_list([E|Elems]) ->
+ [build_map_key(E)|build_map_key_list(Elems)];
+build_map_key_list([]) ->
+ [];
+build_map_key_list(E) ->
+ build_map_key(E).
+
+check_annos([{term,{atom,_,Key},PTerm}|Patterns], Actual, Env0) ->
+ ?DP("Checking term anno ~p: ~p~nkeys: ~p~n",
+ [Key, PTerm, maps:keys(Actual)]),
+ #{ Key := ATerm } = Actual,
+ ?DP("~p <-> ~p~n", [PTerm, ATerm]),
+ Env = env_post(PTerm, #b_literal{val=ATerm}, Env0),
+ ?DP("ok~n"),
+ check_annos(Patterns, Actual, Env);
+check_annos([], _, Env) ->
+ Env.
+
+-spec format_error(term()) -> nonempty_string().
+
+format_error(xfail_passed) ->
+ "test which was expected to fail passed";
+format_error({unknown_result_kind,Result}) ->
+ "unknown expected result: " ++ atom_to_list(Result);
+format_error(too_many_pattern_args) ->
+ "pattern has more arguments than the function";
+format_error(too_few_pattern_args) ->
+ "pattern has fewer arguments than the function";
+format_error({no_match,_Args,_LastMatchedLoc,Env}) ->
+ flatten(io_lib:format("no match found for pattern, env: ~p~n", [Env])).
+
diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl
index 9c430d45b6..9f6169829b 100644
--- a/lib/compiler/src/beam_ssa_codegen.erl
+++ b/lib/compiler/src/beam_ssa_codegen.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
-include("beam_ssa.hrl").
-include("beam_asm.hrl").
--import(lists, [foldl/3,keymember/3,keysort/2,map/2,mapfoldl/3,
+-import(lists, [append/1,foldl/3,keymember/3,keysort/2,map/2,mapfoldl/3,
member/2,reverse/1,reverse/2,sort/1,
splitwith/2,takewhile/2]).
@@ -46,8 +46,9 @@
-spec module(beam_ssa:b_module(), [compile:option()]) ->
{'ok',beam_asm:module_code()}.
-module(#b_module{name=Mod,exports=Es,attributes=Attrs,body=Fs}, _Opts) ->
- {Asm,St} = functions(Fs, {atom,Mod}),
+module(#b_module{name=Mod,exports=Es,attributes=Attrs,body=Fs}, Opts) ->
+ NoBsMatch = member(no_bs_match, Opts),
+ {Asm,St} = functions(Fs, NoBsMatch, {atom,Mod}),
{ok,{Mod,Es,Attrs,Asm,St#cg.lcount}}.
-record(need, {h=0 :: non_neg_integer(), % heap words
@@ -77,7 +78,8 @@ module(#b_module{name=Mod,exports=Es,attributes=Attrs,body=Fs}, _Opts) ->
-record(cg_ret, {arg :: cg_value(),
dealloc=none :: 'none' | pos_integer()
}).
--record(cg_switch, {arg :: cg_value(),
+-record(cg_switch, {anno=#{} :: anno(),
+ arg :: cg_value(),
fail :: ssa_label(),
list :: [sw_list_item()]
}).
@@ -107,11 +109,11 @@ module(#b_module{name=Mod,exports=Es,attributes=Attrs,body=Fs}, _Opts) ->
-type ssa_register() :: xreg() | yreg() | freg() | zreg().
-functions(Forms, AtomMod) ->
- mapfoldl(fun (F, St) -> function(F, AtomMod, St) end,
+functions(Forms, NoBsMatch, AtomMod) ->
+ mapfoldl(fun (F, St) -> function(F, NoBsMatch, AtomMod, St) end,
#cg{lcount=1}, Forms).
-function(#b_function{anno=Anno,bs=Blocks}, AtomMod, St0) ->
+function(#b_function{anno=Anno,bs=Blocks}, NoBsMatch, AtomMod, St0) ->
#{func_info:={_,Name,Arity}} = Anno,
try
assert_exception_block(Blocks), %Assertion.
@@ -124,7 +126,7 @@ function(#b_function{anno=Anno,bs=Blocks}, AtomMod, St0) ->
Labels = (St4#cg.labels)#{0=>Entry,?EXCEPTION_BLOCK=>0},
St5 = St4#cg{labels=Labels,used_labels=gb_sets:singleton(Entry),
ultimate_fail=Ult},
- {Body,St} = cg_fun(Blocks, St5#cg{fc_label=Fi}),
+ {Body,St} = cg_fun(Blocks, NoBsMatch, St5#cg{fc_label=Fi}),
Asm = [{label,Fi},line(Anno),
{func_info,AtomMod,{atom,Name},Arity}] ++
add_parameter_annos(Body, Anno) ++
@@ -155,26 +157,28 @@ assert_exception_block(Blocks) ->
add_parameter_annos([{label, _}=Entry | Body], Anno) ->
ParamTypes = maps:get(parameter_info, Anno, #{}),
- Annos = maps:fold(
- fun(K, V, Acc) when is_map_key(K, ParamTypes) ->
- Info = map_get(K, ParamTypes),
- [{'%', {var_info, V, Info}} | Acc];
- (_K, _V, Acc) ->
- Acc
- end, [], maps:get(registers, Anno)),
+ Annos = [begin
+ Info = map_get(K, ParamTypes),
+ {'%', {var_info, V, Info}}
+ end || K := V <- map_get(registers, Anno),
+ is_map_key(K, ParamTypes)],
[Entry | sort(Annos)] ++ Body.
-cg_fun(Blocks, St0) ->
+cg_fun(Blocks, NoBsMatch, St0) ->
Linear0 = linearize(Blocks),
- St = collect_catch_labels(Linear0, St0),
+ St1 = collect_catch_labels(Linear0, St0),
Linear1 = need_heap(Linear0),
- Linear2 = prefer_xregs(Linear1, St),
- Linear3 = liveness(Linear2, St),
- Linear4 = defined(Linear3, St),
- Linear5 = opt_allocate(Linear4, St),
+ Linear2 = prefer_xregs(Linear1, St1),
+ Linear3 = liveness(Linear2, St1),
+ Linear4 = defined(Linear3, St1),
+ Linear5 = opt_allocate(Linear4, St1),
Linear = fix_wait_timeout(Linear5),
- cg_linear(Linear, St).
+ {Asm,St} = cg_linear(Linear, St1),
+ case NoBsMatch of
+ true -> {Asm,St};
+ false -> {bs_translate(Asm),St}
+ end.
%% collect_catch_labels(Linear, St) -> St.
%% Collect all catch labels (labels for blocks that begin
@@ -351,6 +355,8 @@ classify_heap_need({float,Op}, _Args) ->
get -> put_float;
_ -> neutral
end;
+classify_heap_need(update_record, [_Flag, #b_literal{val=Size} |_ ]) ->
+ {put, Size + 1};
classify_heap_need(Name, _Args) ->
classify_heap_need(Name).
@@ -366,6 +372,9 @@ classify_heap_need(Name, _Args) ->
%% Note: Only handle operations in this function that are not handled
%% by classify_heap_need/2.
+classify_heap_need(bs_ensure) -> gc;
+classify_heap_need(bs_checked_get) -> gc;
+classify_heap_need(bs_checked_skip) -> gc;
classify_heap_need(bs_get) -> gc;
classify_heap_need(bs_get_tail) -> gc;
classify_heap_need(bs_init_writable) -> gc;
@@ -474,6 +483,14 @@ prefer_xregs_is([#cg_set{op=call,dst=Dst}=I0|Is], St, Copies, Acc) ->
prefer_xregs_is([#cg_set{op=old_make_fun,dst=Dst}=I0|Is], St, Copies, Acc) ->
I = prefer_xregs_call(I0, Copies, St),
prefer_xregs_is(Is, St, #{Dst=>{x,0}}, [I|Acc]);
+prefer_xregs_is([#cg_set{op=Op}=I|Is], St, Copies0, Acc)
+ when Op =:= bs_checked_get;
+ Op =:= bs_checked_skip;
+ Op =:= bs_checked_get_tail;
+ Op =:= bs_ensure;
+ Op =:= bs_match_string ->
+ Copies = prefer_xregs_prune(I, Copies0, St),
+ prefer_xregs_is(Is, St, Copies, [I|Acc]);
prefer_xregs_is([#cg_set{args=Args0}=I0|Is], St, Copies0, Acc) ->
Args = [do_prefer_xreg(A, Copies0, St) || A <- Args0],
I = I0#cg_set{args=Args},
@@ -496,10 +513,8 @@ prefer_xregs_prune(#cg_set{anno=#{clobbers:=true}}, _, _) ->
#{};
prefer_xregs_prune(#cg_set{dst=Dst}, Copies, St) ->
DstReg = beam_arg(Dst, St),
- F = fun(_, Alias) ->
- beam_arg(Alias, St) =/= DstReg
- end,
- maps:filter(F, Copies).
+ #{V => Alias || V := Alias <- Copies,
+ beam_arg(Alias, St) =/= DstReg}.
%% prefer_xregs_call(Instruction, Copies, St) -> Instruction.
%% Given a 'call' or 'old_make_fun' instruction rewrite the arguments
@@ -529,12 +544,11 @@ do_prefer_xreg(#b_var{}=A, Copies, St) ->
do_prefer_xreg(A, _, _) -> A.
merge_copies(Copies0, Copies1) when map_size(Copies0) =< map_size(Copies1) ->
- maps:filter(fun(K, V) ->
- case Copies1 of
- #{K:=V} -> true;
- #{} -> false
- end
- end, Copies0);
+ #{K => V || K := V <- Copies0,
+ case Copies1 of
+ #{K := V} -> true;
+ #{} -> false
+ end};
merge_copies(Copies0, Copies1) ->
merge_copies(Copies1, Copies0).
@@ -670,6 +684,7 @@ need_live_anno(Op) ->
case Op of
{bif,_} -> true;
bs_create_bin -> true;
+ bs_checked_get -> true;
bs_get -> true;
bs_get_position -> true;
bs_get_tail -> true;
@@ -677,6 +692,7 @@ need_live_anno(Op) ->
bs_skip -> true;
call -> true;
put_map -> true;
+ update_record -> true;
_ -> false
end.
@@ -803,6 +819,7 @@ need_y_init(#cg_set{op=bs_skip,args=[#b_literal{val=Type}|_]}) ->
end;
need_y_init(#cg_set{op=bs_start_match}) -> true;
need_y_init(#cg_set{op=put_map}) -> true;
+need_y_init(#cg_set{op=update_record}) -> true;
need_y_init(#cg_set{}) -> false.
%% opt_allocate([{BlockLabel,Block}], #st{}) -> [BeamInstruction].
@@ -843,8 +860,34 @@ opt_allocate_defs([#cg_set{op=copy,dst=Dst}|Is], Regs) ->
true -> [Dst|opt_allocate_defs(Is, Regs)];
false -> []
end;
+opt_allocate_defs([#cg_set{anno=Anno,op={bif,Bif},args=Args,dst=Dst}|Is], Regs) ->
+ case is_gc_bif(Bif, Args) of
+ false ->
+ ArgTypes = maps:get(arg_types, Anno, #{}),
+ case is_yreg(Dst, Regs) andalso will_bif_succeed(Bif, Args, ArgTypes) of
+ true -> [Dst|opt_allocate_defs(Is, Regs)];
+ false -> []
+ end;
+ true ->
+ []
+ end;
opt_allocate_defs(_, _Regs) -> [].
+will_bif_succeed(Bif, Args, ArgTypes) ->
+ Types = will_bif_succeed_types(Args, ArgTypes, 0),
+ case beam_call_types:will_succeed(erlang, Bif, Types) of
+ yes -> true;
+ _ -> false
+ end.
+
+will_bif_succeed_types([#b_literal{val=Val}|Args], ArgTypes, N) ->
+ Type = beam_types:make_type_from_value(Val),
+ [Type|will_bif_succeed_types(Args, ArgTypes, N + 1)];
+will_bif_succeed_types([#b_var{}|Args], ArgTypes, N) ->
+ Type = maps:get(N, ArgTypes, any),
+ [Type|will_bif_succeed_types(Args, ArgTypes, N + 1)];
+will_bif_succeed_types([], _, _) -> [].
+
opt_alloc_def([{L,#cg_blk{is=Is,last=Last}}|Bs], Ws0, Def0) ->
case gb_sets:is_member(L, Ws0) of
false ->
@@ -986,8 +1029,8 @@ cg_block(Is0, Last, Next, St0) ->
end.
cg_switch(Is0, Last, St0) ->
- #cg_switch{arg=Src0,fail=Fail0,list=List0} = Last,
- Src = beam_arg(Src0, St0),
+ #cg_switch{anno=Anno,arg=Src0,fail=Fail0,list=List0} = Last,
+ Src1 = beam_arg(Src0, St0),
{Fail1,St1} = use_block_label(Fail0, St0),
Fail = ensure_label(Fail1, St1),
{List1,St2} =
@@ -997,13 +1040,14 @@ cg_switch(Is0, Last, St0) ->
end, St1, List0),
{Is1,St} = cg_block(Is0, none, St2),
case reverse(Is1) of
- [{bif,tuple_size,_,[Tuple],{z,_}=Src}|More] ->
+ [{bif,tuple_size,_,[Tuple],{z,_}=Src1}|More] ->
List = map(fun({integer,Arity}) -> Arity;
({f,_}=F) -> F
end, List1),
Is = reverse(More, [{select_tuple_arity,Tuple,Fail,{list,List}}]),
{Is,St};
_ ->
+ [Src] = typed_args([Src0], Anno, St),
SelectVal = {select_val,Src,Fail,{list,List1}},
{Is1 ++ [SelectVal],St}
end.
@@ -1100,7 +1144,13 @@ cg_block([#cg_set{anno=Anno,op={bif,Name},dst=Dst0,args=Args0}=I|T],
Is = Kill++Line++[{gc_bif,Name,{f,0},Live,Args,Dst}|Is0],
{Is,St};
false ->
- Is = [{bif,Name,{f,0},Args,Dst}|Is0],
+ Bif = case {Name,Args} of
+ {'not',[{tr,_,#t_atom{elements=[false,true]}}=Arg]} ->
+ {bif,'=:=',{f,0},[Arg,{atom,false}],Dst};
+ {_,_} ->
+ {bif,Name,{f,0},Args,Dst}
+ end,
+ Is = [Bif|Is0],
{Is,St}
end;
cg_block([#cg_set{op=bs_create_bin,dst=Dst0,args=Args0,anno=Anno}=I,
@@ -1112,13 +1162,23 @@ cg_block([#cg_set{op=bs_create_bin,dst=Dst0,args=Args0,anno=Anno}=I,
Live = get_live(I),
Dst = beam_arg(Dst0, St),
Args = bs_args(Args1),
+ Unit0 = maps:get(unit, Anno, 1),
Unit = case Args of
- [{atom,append},_Seg,U|_] -> U;
- [{atom,private_append},_Seg,U|_] -> U;
- _ -> 1
+ [{atom,append},_Seg,U|_] ->
+ max(U, Unit0);
+ [{atom,private_append},_Seg,U|_] ->
+ max(U, Unit0);
+ _ ->
+ Unit0
end,
+ TypeInfo = case Anno of
+ #{result_type := #t_bitstring{appendable=true}=Type} ->
+ [{'%',{var_info,Dst,[{type,Type}]}}];
+ _ ->
+ []
+ end,
Is = [Line,{bs_create_bin,Fail,Alloc,Live,Unit,Dst,{list,Args}}],
- {Is,St};
+ {Is++TypeInfo,St};
cg_block([#cg_set{op=bs_start_match,
dst=Ctx0,
args=[#b_literal{val=new},Bin0]}=I,
@@ -1128,6 +1188,13 @@ cg_block([#cg_set{op=bs_start_match,
Live = get_live(I),
Is = Pre ++ [{test,bs_start_match3,Fail,Live,[Bin],Dst}],
{Is,St};
+cg_block([#cg_set{op=bs_ensure,args=Ss0},
+ #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
+ %% Temporary instruction that will be incorporated into a bs_match
+ %% instruction by the bs_translate sub pass.
+ [Ctx,{integer,Size},{integer,Unit}] = beam_args(Ss0, St),
+ Is = [{test,bs_ensure,Fail,[Ctx,Size,Unit]}],
+ {Is,St};
cg_block([#cg_set{op=bs_get}=Set,
#cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
{cg_bs_get(Fail, Set, St),St};
@@ -1179,6 +1246,10 @@ cg_block([#cg_set{op={float,convert},dst=Dst0,args=Args0,anno=Anno},
[Src] = typed_args(Args0, Anno, St),
Dst = beam_arg(Dst0, St),
{[line(Anno),{fconv,Src,Dst}], St};
+cg_block([#cg_set{op=bs_skip,args=Args0,anno=Anno}=I,
+ #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
+ Args = typed_args(Args0, Anno, St),
+ {cg_bs_skip(bif_fail(Fail), Args, I),St};
cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=I,
#cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
[Dst|Args] = beam_args([Dst0|Args0], St),
@@ -1294,6 +1365,13 @@ cg_block([#cg_set{op=wait_timeout,dst=Bool,args=Args0}], {Bool,Fail}, St) ->
[{wait_timeout,Fail,Timeout},timeout]
end,
{Is,St};
+cg_block([#cg_set{op=has_map_field,dst=Dst0,args=Args0,anno=Anno}|T], Context, St0) ->
+ [Map,Key] = typed_args(Args0, Anno, St0),
+ Dst = beam_arg(Dst0, St0),
+ I = {bif,is_map_key,{f,0},[Key,Map],Dst},
+ {Is0,St} = cg_block(T, Context, St0),
+ Is = [I|Is0],
+ {Is,St};
cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=Set], none, St) ->
[Dst|Args] = beam_args([Dst0|Args0], St),
Is = cg_instr(Op, Args, Dst, Set),
@@ -1372,6 +1450,12 @@ cg_copy_1([], _St) -> [].
element(1, Val) =:= atom orelse
element(1, Val) =:= literal)).
+bif_to_test(min, Args, Fail, St) ->
+ %% The min/2 and max/2 BIFs can only be rewritten to tests when
+ %% both arguments are known to be booleans.
+ bif_to_test('and', Args, Fail, St);
+bif_to_test(max, Args, Fail, St) ->
+ bif_to_test('or', Args, Fail, St);
bif_to_test('or', [V1,V2], {f,Lbl}=Fail, St0) when Lbl =/= 0 ->
{SuccLabel,St} = new_label(St0),
{[{test,is_eq_exact,{f,SuccLabel},[V1,{atom,false}]},
@@ -1710,6 +1794,17 @@ cg_instr(bs_start_match, [{atom,new}, Src0], Dst, Set) ->
{Src, Pre} = force_reg(Src0, Dst),
Live = get_live(Set),
Pre ++ [{bs_start_match4,{atom,no_fail},Live,Src,Dst}];
+cg_instr(bs_checked_get, [Kind,Ctx,{literal,Flags},{integer,Size},{integer,Unit}], Dst, Set) ->
+ %% Temporary instruction that will be incorporated into a bs_match
+ %% instruction by the bs_translate sub pass.
+ Live = get_live(Set),
+ [{bs_checked_get,Live,Kind,Ctx,field_flags(Flags, Set),Size,Unit,Dst}];
+cg_instr(bs_checked_get, [{atom,binary},Ctx,{literal,_Flags},
+ {atom,all},{integer,Unit}], Dst, Set) ->
+ %% Temporary instruction that will be incorporated into a bs_match
+ %% instruction by the bs_translate sub pass.
+ Live = get_live(Set),
+ [{bs_checked_get_tail,Live,Ctx,Unit,Dst}];
cg_instr(bs_get_tail, [Src], Dst, Set) ->
Live = get_live(Set),
[{bs_get_tail,Src,Dst,Live}];
@@ -1730,6 +1825,13 @@ cg_instr(is_nonempty_list, Ss, Dst, Set) ->
cg_instr(Op, Args, Dst, _Set) ->
cg_instr(Op, Args, Dst).
+cg_instr(bs_checked_skip, [_Type,Ctx,_Flags,{integer,Sz},{integer,U}], {z,_})
+ when is_integer(Sz) ->
+ %% Temporary instruction that will be incorporated into a bs_match
+ %% instruction by the bs_translate sub pass.
+ [{bs_checked_skip,Ctx,Sz*U}];
+cg_instr(bs_checked_skip, [_Type,_Ctx,_Flags,{atom,all},{integer,_U}], {z,_}) ->
+ [];
cg_instr(bs_init_writable, Args, Dst) ->
setup_args(Args) ++ [bs_init_writable|copy({x,0}, Dst)];
cg_instr(bs_set_position, [Ctx,Pos], _Dst) ->
@@ -1748,8 +1850,6 @@ cg_instr(get_tl=Op, [Src], Dst) ->
[{Op,Src,Dst}];
cg_instr(get_tuple_element=Op, [Src,{integer,N}], Dst) ->
[{Op,Src,N,Dst}];
-cg_instr(has_map_field, [Map,Key], Dst) ->
- [{bif,is_map_key,{f,0},[Key,Map],Dst}];
cg_instr(nif_start, [], _Dst) ->
[nif_start];
cg_instr(put_list=Op, [Hd,Tl], Dst) ->
@@ -1769,10 +1869,11 @@ cg_instr(recv_marker_reserve, [], Dst) ->
cg_instr(remove_message, [], _Dst) ->
[remove_message];
cg_instr(resume, [A,B], _Dst) ->
- [{bif,raise,{f,0},[A,B],{x,0}}].
+ [{bif,raise,{f,0},[A,B],{x,0}}];
+cg_instr(update_record, [Hint, {integer,Size}, Src | Ss0], Dst) ->
+ Ss = cg_update_record_list(Ss0, []),
+ [{update_record,Hint,Size,Src,Dst,{list,Ss}}].
-cg_test(bs_skip, Fail, Args, _Dst, I) ->
- cg_bs_skip(Fail, Args, I);
cg_test({float,Op0}, Fail, Args, Dst, #cg_set{anno=Anno}) ->
Op = case Op0 of
'+' -> fadd;
@@ -1787,12 +1888,24 @@ cg_test(peek_message, Fail, [], Dst, _I) ->
cg_test(put_map, Fail, [{atom,exact},SrcMap|Ss], Dst, #cg_set{anno=Anno}=Set) ->
Live = get_live(Set),
[line(Anno),{put_map_exact,Fail,SrcMap,Dst,Live,{list,Ss}}];
+cg_test(set_tuple_element=Op, Fail, Args, Dst, Set) ->
+ {f,0} = Fail, %Assertion.
+ cg_instr(Op, Args, Dst, Set);
cg_test(raw_raise, _Fail, Args, Dst, _I) ->
cg_instr(raw_raise, Args, Dst);
cg_test(resume, _Fail, [_,_]=Args, Dst, _I) ->
cg_instr(resume, Args, Dst).
-cg_bs_get(Fail, #cg_set{dst=Dst0,args=[#b_literal{val=Type}|Ss0]}=Set, St) ->
+cg_update_record_list([{integer, Index}, Value], []) ->
+ [Index, Value];
+cg_update_record_list([{integer, Index}, Value | Updates], Acc) ->
+ cg_update_record_list(Updates, [{Index, Value} | Acc]);
+cg_update_record_list([], Acc) ->
+ append([[Index, Value] || {Index, Value} <- sort(Acc)]).
+
+cg_bs_get(Fail, #cg_set{dst=Dst0,args=Args,anno=Anno}=Set, St) ->
+ [{atom,Type}|Ss0] = typed_args(Args, Anno, St),
+ Dst = beam_arg(Dst0, St),
Op = case Type of
integer -> bs_get_integer2;
float -> bs_get_float2;
@@ -1801,8 +1914,7 @@ cg_bs_get(Fail, #cg_set{dst=Dst0,args=[#b_literal{val=Type}|Ss0]}=Set, St) ->
utf16 -> bs_get_utf16;
utf32 -> bs_get_utf32
end,
- [Dst|Ss1] = beam_args([Dst0|Ss0], St),
- Ss = case Ss1 of
+ Ss = case Ss0 of
[Ctx,{literal,Flags},Size,{integer,Unit}] ->
%% Plain integer/float/binary.
[Ctx,Size,Unit,field_flags(Flags, Set)];
@@ -1966,8 +2078,8 @@ translate_terminator(#b_br{bool=#b_literal{val=true},succ=Succ}) ->
#cg_br{bool=#b_literal{val=true},succ=Succ,fail=Succ};
translate_terminator(#b_br{bool=Bool,succ=Succ,fail=Fail}) ->
#cg_br{bool=Bool,succ=Succ,fail=Fail};
-translate_terminator(#b_switch{arg=Bool,fail=Fail,list=List}) ->
- #cg_switch{arg=Bool,fail=Fail,list=List}.
+translate_terminator(#b_switch{anno=Anno,arg=Bool,fail=Fail,list=List}) ->
+ #cg_switch{anno=Anno,arg=Bool,fail=Fail,list=List}.
translate_phis(L, #cg_br{succ=Target,fail=Target}, Blocks) ->
#b_blk{is=Is} = maps:get(Target, Blocks),
@@ -2113,6 +2225,119 @@ break_up_cycle_1(Dst, [{move,S,D}|Path], Acc) ->
break_up_cycle_1(Dst, Path, [{swap,S,D}|Acc]).
%%%
+%%% Collect and translate binary match instructions, producing a
+%%% bs_match instruction.
+%%%
+
+bs_translate([{bs_get_tail,_,_,_}=I|Is]) ->
+ %% A lone bs_get_tail. There is no advantage to incorporating it into
+ %% a bs_match instruction.
+ [I|bs_translate(Is)];
+bs_translate([I|Is0]) ->
+ case bs_translate_instr(I) of
+ none ->
+ [I|bs_translate(Is0)];
+ {Ctx,Fail0,First} ->
+ {Instrs0,Fail,Is} = bs_translate_collect(Is0, Ctx, Fail0, [First]),
+ Instrs1 = bs_seq_match_fixup(Instrs0),
+ Instrs = bs_eq_fixup(Instrs1),
+ [{bs_match,Fail,Ctx,{commands,Instrs}}|bs_translate(Is)]
+ end;
+bs_translate([]) -> [].
+
+bs_translate_collect([I|Is]=Is0, Ctx, Fail, Acc) ->
+ case bs_translate_instr(I) of
+ {Ctx,Fail,Instr} ->
+ bs_translate_collect(Is, Ctx, Fail, [Instr|Acc]);
+ {Ctx,{f,0},Instr} ->
+ bs_translate_collect(Is, Ctx, Fail, [Instr|Acc]);
+ {_,_,_} ->
+ {bs_translate_fixup(Acc),Fail,Is0};
+ none ->
+ {bs_translate_fixup(Acc),Fail,Is0}
+ end.
+
+bs_translate_fixup([{get_tail,_,_,_}=GT,{test_tail,Bits}|Is0]) ->
+ Is = reverse(Is0),
+ bs_translate_fixup_tail(Is, Bits) ++ [GT];
+bs_translate_fixup([{test_tail,Bits}|Is0]) ->
+ Is = reverse(Is0),
+ bs_translate_fixup_tail(Is, Bits);
+bs_translate_fixup(Is) ->
+ reverse(Is).
+
+%% Fix up matching of multiple binaries in parallel. Example:
+%% f(<<_:8>> = <<X:8>>) -> ...
+bs_seq_match_fixup([{test_tail,Bits},{ensure_exactly,Bits}|Is]) ->
+ [{ensure_exactly,Bits}|bs_seq_match_fixup(Is)];
+bs_seq_match_fixup([{test_tail,Bits0},{ensure_at_least,Bits1,Unit}|Is])
+ when Bits0 >= Bits1, Bits0 rem Unit =:= 0 ->
+ %% The tail test is at least as strict as the ensure_at_least test.
+ [{ensure_exactly,Bits0}|bs_seq_match_fixup(Is)];
+bs_seq_match_fixup([{test_tail,Bits}|Is]) ->
+ [{ensure_exactly,Bits}|bs_seq_match_fixup(Is)];
+bs_seq_match_fixup([I|Is]) ->
+ [I|bs_seq_match_fixup(Is)];
+bs_seq_match_fixup([]) -> [].
+
+bs_eq_fixup([{'=:=',nil,Bits,Value}|Is]) ->
+ EqInstrs = bs_eq_fixup_split(Bits, <<Value:Bits>>),
+ EqInstrs ++ bs_eq_fixup(Is);
+bs_eq_fixup([I|Is]) ->
+ [I|bs_eq_fixup(Is)];
+bs_eq_fixup([]) -> [].
+
+%% In the 32-bit runtime system, each integer to be matched must
+%% fit in a SIGNED 32-bit word. Therefore, we will split the
+%% instruction into multiple instructions each matching at most
+%% 31 bits.
+bs_eq_fixup_split(Bits, Value) when Bits =< 31 ->
+ <<I:Bits>> = Value,
+ [{'=:=',nil,Bits,I}];
+bs_eq_fixup_split(Bits, Value0) ->
+ <<I:31,Value/bits>> = Value0,
+ [{'=:=',nil,31,I} | bs_eq_fixup_split(Bits - 31, Value)].
+
+bs_translate_fixup_tail([{ensure_at_least,Bits0,_}|Is], Bits) ->
+ [{ensure_exactly,Bits0+Bits}|Is];
+bs_translate_fixup_tail([I|Is], Bits) ->
+ [I|bs_translate_fixup_tail(Is, Bits)];
+bs_translate_fixup_tail([], Bits) ->
+ [{ensure_exactly,Bits}].
+
+bs_translate_instr({test,bs_ensure,Fail,[Ctx,Size,Unit]}) ->
+ {Ctx,Fail,{ensure_at_least,Size,Unit}};
+bs_translate_instr({bs_checked_get,Live,{atom,Type},Ctx,{field_flags,Flags0},
+ Size,Unit,Dst}) ->
+ %% Only keep flags that have a meaning for binary matching and are
+ %% distinct from the default value.
+ Flags = [Flag || Flag <- Flags0,
+ case Flag of
+ little -> true;
+ native -> true;
+ big -> false;
+ signed -> true;
+ unsigned -> false;
+ {anno,_} -> false
+ end],
+ {Ctx,{f,0},{Type,Live,{literal,Flags},Size,Unit,Dst}};
+bs_translate_instr({bs_checked_skip,Ctx,Stride}) ->
+ {Ctx,{f,0},{skip,Stride}};
+bs_translate_instr({bs_checked_get_tail,Live,Ctx,Unit,Dst}) ->
+ {Ctx,{f,0},{get_tail,Live,Unit,Dst}};
+bs_translate_instr({bs_get_tail,Ctx,Dst,Live}) ->
+ {Ctx,{f,0},{get_tail,Live,1,Dst}};
+bs_translate_instr({test,bs_test_tail2,Fail,[Ctx,Bits]}) ->
+ {Ctx,Fail,{test_tail,Bits}};
+bs_translate_instr({test,bs_match_string,Fail,[Ctx,Bits,{string,String}]})
+ when bit_size(String) =< 64 ->
+ <<Value:Bits,_/bitstring>> = String,
+ Live = nil,
+ {Ctx,Fail,{'=:=',Live,Bits,Value}};
+bs_translate_instr(_) -> none.
+
+
+%%%
%%% General utility functions.
%%%
@@ -2213,6 +2438,8 @@ local_func_label(Key, #cg{functable=Map}=St0) ->
is_gc_bif(hd, [_]) -> false;
is_gc_bif(tl, [_]) -> false;
is_gc_bif(self, []) -> false;
+is_gc_bif(max, [_,_]) -> false;
+is_gc_bif(min, [_,_]) -> false;
is_gc_bif(node, []) -> false;
is_gc_bif(node, [_]) -> false;
is_gc_bif(element, [_,_]) -> false;
diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl
index 2c0384704e..5294fecb7c 100644
--- a/lib/compiler/src/beam_ssa_dead.erl
+++ b/lib/compiler/src/beam_ssa_dead.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1428,9 +1428,9 @@ used_vars_phis(Is, L, Live0, UsedVars0) ->
case [{P,V} || {#b_var{}=V,P} <- PhiArgs] of
[_|_]=PhiVars ->
PhiLive0 = rel2fam(PhiVars),
- PhiLive = [{{L,P},list_set_union(Vs, Live0)} ||
- {P,Vs} <- PhiLive0],
- maps:merge(UsedVars, maps:from_list(PhiLive));
+ PhiLive = #{{L,P} => list_set_union(Vs, Live0) ||
+ {P,Vs} <- PhiLive0},
+ maps:merge(UsedVars, PhiLive);
[] ->
%% There were only literals in the phi node(s).
UsedVars
diff --git a/lib/compiler/src/beam_ssa_lint.erl b/lib/compiler/src/beam_ssa_lint.erl
index f3cd307f8e..6b6bf87bf6 100644
--- a/lib/compiler/src/beam_ssa_lint.erl
+++ b/lib/compiler/src/beam_ssa_lint.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -108,7 +108,7 @@ validate_variables(#b_function{ args = Args, bs = Blocks }) ->
Blocks :: #{ beam_ssa:label() => beam_ssa:b_blk() }.
vvars_assert_unique(Blocks, Args) ->
BlockIs = [Is || #b_blk{is=Is} <- maps:values(Blocks)],
- Defined0 = maps:from_list([{V,argument} || V <- Args]),
+ Defined0 = #{V => argument || V <- Args},
_ = foldl(fun(Is, Defined) ->
vvars_assert_unique_1(Is, Defined)
end, Defined0, BlockIs),
@@ -128,7 +128,7 @@ vvars_assert_unique_1([], Defined) ->
-spec vvars_phi_nodes(State :: #vvars{}) -> ok.
vvars_phi_nodes(#vvars{ blocks = Blocks }=State) ->
_ = [vvars_phi_nodes_1(Is, Id, State) ||
- {Id, #b_blk{ is = Is }} <- maps:to_list(Blocks)],
+ Id := #b_blk{ is = Is } <- Blocks],
ok.
-spec vvars_phi_nodes_1(Is, Id, State) -> ok when
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
index e10189afd8..13e4114319 100644
--- a/lib/compiler/src/beam_ssa_opt.erl
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -64,7 +64,9 @@ module(Module, Opts) ->
Phases = [{once, Order, prologue_passes(Opts)},
{module, module_passes(Opts)},
{fixpoint, Order, repeated_passes(Opts)},
- {once, Order, epilogue_passes(Opts)}],
+ {once, Order, early_epilogue_passes(Opts)},
+ {module, epilogue_module_passes(Opts)},
+ {once, Order, late_epilogue_passes(Opts)}],
StMap = run_phases(Phases, StMap0, FuncDb),
{ok, finish(Module, StMap)}.
@@ -93,7 +95,7 @@ skip_removed(FuncIds, StMap) ->
fixpoint(_FuncIds, _Order, _Passes, StMap, FuncDb, 0) ->
%% Too many repetitions. Give up and return what we have.
{StMap, FuncDb};
-fixpoint(FuncIds0, Order0, Passes, StMap0, FuncDb0, N) ->
+fixpoint(FuncIds0, Order0, Passes, StMap0, FuncDb0, N) when is_map(StMap0) ->
{StMap, FuncDb} = phase(FuncIds0, Passes, StMap0, FuncDb0),
Repeat = changed(FuncIds0, FuncDb0, FuncDb, StMap0, StMap),
case sets:is_empty(Repeat) of
@@ -243,6 +245,7 @@ prologue_passes(Opts) ->
?PASS(ssa_opt_linearize),
?PASS(ssa_opt_tuple_size),
?PASS(ssa_opt_record),
+ ?PASS(ssa_opt_update_tuple),
?PASS(ssa_opt_cse), % Helps the first type pass.
?PASS(ssa_opt_live)], % ...
passes_1(Ps, Opts).
@@ -275,12 +278,25 @@ repeated_passes(Opts) ->
%clean up phi nodes.
passes_1(Ps, Opts).
-epilogue_passes(Opts) ->
+epilogue_module_passes(Opts) ->
+ Ps0 = [{ssa_opt_alias,
+ fun({StMap, FuncDb}) ->
+ beam_ssa_alias:opt(StMap, FuncDb)
+ end},
+ {ssa_opt_private_append,
+ fun({StMap, FuncDb}) ->
+ beam_ssa_private_append:opt(StMap, FuncDb)
+ end}],
+ passes_1(Ps0, Opts).
+
+early_epilogue_passes(Opts) ->
Ps = [?PASS(ssa_opt_type_finish),
?PASS(ssa_opt_float),
- ?PASS(ssa_opt_sw),
+ ?PASS(ssa_opt_sw)],
+ passes_1(Ps, Opts).
- %% Run live one more time to clean up after the previous
+late_epilogue_passes(Opts) ->
+ Ps = [%% Run live one more time to clean up after the previous
%% epilogue passes.
?PASS(ssa_opt_live),
?PASS(ssa_opt_bsm),
@@ -288,7 +304,9 @@ epilogue_passes(Opts) ->
?PASS(ssa_opt_sink),
?PASS(ssa_opt_blockify),
?PASS(ssa_opt_redundant_br),
+ ?PASS(ssa_opt_bs_ensure),
?PASS(ssa_opt_merge_blocks),
+ ?PASS(ssa_opt_try),
?PASS(ssa_opt_get_tuple_element),
?PASS(ssa_opt_tail_literals),
?PASS(ssa_opt_trim_unreachable),
@@ -299,14 +317,16 @@ epilogue_passes(Opts) ->
passes_1(Ps, Opts0) ->
Negations = [{list_to_atom("no_"++atom_to_list(N)),N} ||
{N,_} <- Ps],
- Opts = proplists:substitute_negations(Negations, Opts0),
+ Expansions = [{no_bs_match,[no_ssa_opt_bs_ensure,no_bs_match]}],
+ Opts = proplists:normalize(Opts0, [{expand,Expansions},
+ {negations,Negations}]),
[case proplists:get_value(Name, Opts, true) of
true ->
P;
false ->
{NoName,Name} = keyfind(Name, 2, Negations),
{NoName,fun(S) -> S end}
- end || {Name,_}=P <- Ps].
+ end || {Name,_}=P <- Ps].
%% Builds a function information map with basic information about incoming and
%% outgoing local calls, as well as whether the function is exported.
@@ -393,7 +413,7 @@ fdb_update(Caller, Callee, FuncDb) ->
%% Functions where module-level optimization is disabled are added last in
%% arbitrary order.
-get_call_order_po(StMap, FuncDb) ->
+get_call_order_po(StMap, FuncDb) when is_map(FuncDb) ->
Order = gco_po(FuncDb),
Order ++ sort([K || K <- maps:keys(StMap), not is_map_key(K, FuncDb)]).
@@ -491,7 +511,7 @@ ssa_opt_split_blocks({#opt_st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) ->
%%% different registers).
%%%
-ssa_opt_coalesce_phis({#opt_st{ssa=Blocks0}=St, FuncDb}) ->
+ssa_opt_coalesce_phis({#opt_st{ssa=Blocks0}=St, FuncDb}) when is_map(Blocks0) ->
Ls = beam_ssa:rpo(Blocks0),
Blocks = c_phis_1(Ls, Blocks0),
{St#opt_st{ssa=Blocks}, FuncDb}.
@@ -876,6 +896,73 @@ is_tagged_tuple_4([_|Is], Bool, TagVar) ->
is_tagged_tuple_4([], _, _) -> no.
%%%
+%%% Replaces setelement/3 with the update_tuple psuedo-instruction, and merges
+%%% multiple such calls into the same instruction.
+%%%
+ssa_opt_update_tuple({#opt_st{ssa=Linear0}=St, FuncDb}) ->
+ {St#opt_st{ssa=update_tuple_opt(Linear0, #{})}, FuncDb}.
+
+update_tuple_opt([{L, #b_blk{is=Is0}=B} | Bs], SetOps0) ->
+ {Is, SetOps} = update_tuple_opt_is(Is0, SetOps0, []),
+ [{L, B#b_blk{is=Is}} | update_tuple_opt(Bs, SetOps)];
+update_tuple_opt([], _SetOps) ->
+ [].
+
+update_tuple_opt_is([#b_set{op=call,
+ dst=Dst,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=setelement}},
+ #b_literal{val=N}=Index,
+ Src,
+ Value]}=I0 | Is],
+ SetOps0, Acc) when is_integer(N), N >= 1 ->
+ SetOps1 = SetOps0#{ Dst => {Src, Index, Value} },
+ SetOps = maps:remove(Value, SetOps1),
+
+ Args = update_tuple_merge(Src, SetOps, [Index, Value],
+ sets:new([{version,2}])),
+ I = I0#b_set{op=update_tuple,dst=Dst,args=Args},
+
+ update_tuple_opt_is(Is, SetOps, [I | Acc]);
+update_tuple_opt_is([#b_set{op=Op}=I | Is], SetOps0, Acc) ->
+ case {Op, beam_ssa:clobbers_xregs(I)} of
+ {_, true} ->
+ %% Merging setelement across stack frames is potentially very
+ %% expensive as the update list needs to be saved on the stack, so
+ %% we discard our state whenever we need one.
+ update_tuple_opt_is(Is, #{}, [I | Acc]);
+ {{succeeded, _}, false} ->
+ %% This is a psuedo-op used to link ourselves with our catch block,
+ %% so it doesn't really count as a use.
+ update_tuple_opt_is(Is, SetOps0, [I | Acc]);
+ {_, false} ->
+ %% It's pointless to merge us with later ops if our result is used
+ %% and needs to be created anyway.
+ SetOps = maps:without(beam_ssa:used(I), SetOps0),
+ update_tuple_opt_is(Is, SetOps, [I | Acc])
+ end;
+update_tuple_opt_is([], SetOps, Acc) ->
+ {reverse(Acc), SetOps}.
+
+update_tuple_merge(Src, SetOps, Updates0, Seen0) ->
+ %% Note that we're merging in reverse order, so Updates0 contains the
+ %% updates made *after* this one.
+ case SetOps of
+ #{ Src := {Ancestor, Index, Value} } ->
+ %% Drop redundant updates, which can happen when when a record is
+ %% updated in several branches and one of them overwrites a
+ %% previous index.
+ Updates = case sets:is_element(Index, Seen0) of
+ false -> [Index, Value | Updates0];
+ true -> Updates0
+ end,
+ Seen = sets:add_element(Index, Seen0),
+ update_tuple_merge(Ancestor, SetOps, Updates, Seen);
+ #{} ->
+ [Src | Updates0]
+ end.
+
+%%%
%%% Common subexpression elimination (CSE).
%%%
%%% Eliminate repeated evaluation of identical expressions. To avoid
@@ -903,7 +990,7 @@ cse_successors([#b_set{op={succeeded,_},args=[Src]},Bif|_], Blk, EsSucc, M0) ->
%% We must remove the substitution for Src from the failure branch.
#b_blk{last=#b_br{succ=Succ,fail=Fail}} = Blk,
M = cse_successors_1([Succ], EsSucc, M0),
- EsFail = maps:filter(fun(_, Val) -> Val =/= Src end, EsSucc),
+ EsFail = #{Var => Val || Var := Val <- EsSucc, Val =/= Src},
cse_successors_1([Fail], EsFail, M);
false ->
%% There can't be any replacement for Src in EsSucc. No need for
@@ -957,6 +1044,30 @@ cse_is([#b_set{op={succeeded,_},dst=Bool,args=[Src]}=I0|Is], Es, Sub0, Acc) ->
Sub = Sub0#{Bool=>#b_literal{val=true}},
cse_is(Is, Es, Sub, Acc)
end;
+cse_is([#b_set{op=put_map,dst=Dst,args=[_Kind,Map|_]}=I0|Is],
+ Es0, Sub0, Acc) ->
+ I1 = sub(I0, Sub0),
+ {ok,ExprKey} = cse_expr(I1),
+ case Es0 of
+ #{ExprKey:=PrevPutMap} ->
+ Sub = Sub0#{Dst=>PrevPutMap},
+ cse_is(Is, Es0, Sub, Acc);
+ #{Map:=PutMap} ->
+ case combine_put_maps(PutMap, I1) of
+ none ->
+ Es1 = Es0#{ExprKey=>Dst},
+ Es = cse_add_inferred_exprs(I1, Es1),
+ cse_is(Is, Es, Sub0, [I1|Acc]);
+ I ->
+ Es1 = Es0#{ExprKey=>Dst},
+ Es = cse_add_inferred_exprs(I1, Es1),
+ cse_is(Is, Es, Sub0, [I|Acc])
+ end;
+ #{} ->
+ Es1 = Es0#{ExprKey=>Dst},
+ Es = cse_add_inferred_exprs(I1, Es1),
+ cse_is(Is, Es, Sub0, [I1|Acc])
+ end;
cse_is([#b_set{dst=Dst}=I0|Is], Es0, Sub0, Acc) ->
I = sub(I0, Sub0),
case beam_ssa:clobbers_xregs(I) of
@@ -1005,8 +1116,9 @@ cse_add_inferred_exprs(#b_set{op={bif,tl},dst=Tl,args=[List]}, Es) ->
Es#{{get_tl,[List]} => Tl};
cse_add_inferred_exprs(#b_set{op={bif,map_get},dst=Value,args=[Key,Map]}, Es) ->
Es#{{get_map_element,[Map,Key]} => Value};
-cse_add_inferred_exprs(#b_set{op=put_map,dst=Map,args=[_,_|Args]}, Es) ->
- cse_add_map_get(Args, Map, Es);
+cse_add_inferred_exprs(#b_set{op=put_map,dst=Map,args=[_,_|Args]}=I, Es0) ->
+ Es = cse_add_map_get(Args, Map, Es0),
+ Es#{Map => I};
cse_add_inferred_exprs(_, Es) -> Es.
cse_add_map_get([Key,Value|T], Map, Es0) ->
@@ -1045,6 +1157,42 @@ cse_suitable(#b_set{anno=Anno,op={bif,Name},args=Args}) ->
erl_internal:bool_op(Name, Arity));
cse_suitable(#b_set{}) -> false.
+combine_put_maps(#b_set{dst=Prev,args=[#b_literal{val=assoc},Map|Args1]},
+ #b_set{args=[#b_literal{val=assoc},Prev|Args2]}=I) ->
+ case are_map_keys_literals(Args1) andalso are_map_keys_literals(Args2) of
+ true ->
+ Args = combine_put_map_args(Args1, Args2),
+ I#b_set{args=[#b_literal{val=assoc},Map|Args]};
+ false ->
+ none
+ end;
+combine_put_maps(#b_set{}, #b_set{}) ->
+ none.
+
+combine_put_map_args(Args1, Args2) ->
+ Keys = sets:from_list(get_map_keys(Args2), [{version,2}]),
+ combine_put_map_args_1(Args1, Args2, Keys).
+
+combine_put_map_args_1([Key,Value|T], Tail, Keys) ->
+ case sets:is_element(Key, Keys) of
+ true ->
+ combine_put_map_args_1(T, Tail, Keys);
+ false ->
+ [Key,Value|combine_put_map_args_1(T, Tail, Keys)]
+ end;
+combine_put_map_args_1([], Tail, _Keys) -> Tail.
+
+get_map_keys([Key,_|T]) ->
+ [Key|get_map_keys(T)];
+get_map_keys([]) -> [].
+
+are_map_keys_literals([#b_literal{},_Value|Args]) ->
+ are_map_keys_literals(Args);
+are_map_keys_literals([#b_var{}|_]) ->
+ false;
+are_map_keys_literals([]) ->
+ true.
+
%%%
%%% Using floating point instructions.
%%%
@@ -1351,9 +1499,9 @@ live_opt_phis(Is, L, Live0, LiveMap0) ->
case [{P,V} || {#b_var{}=V,P} <- PhiArgs] of
[_|_]=PhiVars ->
PhiLive0 = rel2fam(PhiVars),
- PhiLive = [{{L,P},list_set_union(Vs, Live0)} ||
- {P,Vs} <- PhiLive0],
- maps:merge(LiveMap, maps:from_list(PhiLive));
+ PhiLive = #{{L,P} => list_set_union(Vs, Live0) ||
+ {P,Vs} <- PhiLive0},
+ maps:merge(LiveMap, PhiLive);
[] ->
%% There were only literals in the phi node(s).
LiveMap
@@ -1434,7 +1582,14 @@ live_opt_is([], Live, Acc) ->
%%% never throw.
%%%
-ssa_opt_try({#opt_st{ssa=Linear,cnt=Count0}=St, FuncDb}) ->
+ssa_opt_try({#opt_st{ssa=SSA0,cnt=Count0}=St, FuncDb}) ->
+ {Count, SSA} = opt_try(SSA0, Count0),
+ {St#opt_st{ssa=SSA,cnt=Count}, FuncDb}.
+
+opt_try(Blocks, Count0) when is_map(Blocks) ->
+ {Count, Linear} = opt_try(beam_ssa:linearize(Blocks), Count0),
+ {Count, maps:from_list(Linear)};
+opt_try(Linear, Count0) when is_list(Linear) ->
{Count, Shrunk} = shrink_try(Linear, Count0, []),
Reduced = reduce_try(Shrunk, []),
@@ -1442,7 +1597,7 @@ ssa_opt_try({#opt_st{ssa=Linear,cnt=Count0}=St, FuncDb}) ->
EmptySet = sets:new([{version, 2}]),
Trimmed = trim_try(Reduced, EmptySet, EmptySet, []),
- {St#opt_st{ssa=Trimmed,cnt=Count}, FuncDb}.
+ {Count, Trimmed}.
%% Moves all leading/trailing instructions that cannot fail out of try/catch
%% expressions. For example, we can move the tuple constructions `{defg,Arg}`
@@ -1514,13 +1669,19 @@ sink_try_is(Is) ->
sink_try_is_1([#b_set{op=kill_try_tag}=Kill | Is], Acc) ->
[Kill | reverse(Acc, Is)];
sink_try_is_1([I | Is], Acc) ->
- case beam_ssa:no_side_effect(I) of
+ case is_safe_sink_try(I) of
true -> sink_try_is_1(Is, [I | Acc]);
false -> reverse(Acc, [I | Is])
end;
sink_try_is_1([], Acc) ->
reverse(Acc).
+is_safe_sink_try(#b_set{op=Op}=I) ->
+ case Op of
+ bs_extract -> false;
+ _ -> beam_ssa:no_side_effect(I)
+ end.
+
%% Does a strength reduction of try/catch and catch.
%%
%% In try/catch constructs where the expression is restricted
@@ -1702,20 +1863,23 @@ trim_try_is([], _Killed) ->
%%% with bs_test_tail.
%%%
-ssa_opt_bsm({#opt_st{ssa=Linear}=St, FuncDb}) ->
- Extracted0 = bsm_extracted(Linear),
+ssa_opt_bsm({#opt_st{ssa=Linear0}=St, FuncDb}) ->
+ Extracted0 = bsm_extracted(Linear0),
Extracted = sets:from_list(Extracted0, [{version, 2}]),
- {St#opt_st{ssa=bsm_skip(Linear, Extracted)}, FuncDb}.
+ Linear1 = bsm_skip(Linear0, Extracted),
+ Linear = bsm_coalesce_skips(Linear1, #{}),
+ {St#opt_st{ssa=Linear}, FuncDb}.
bsm_skip([{L,#b_blk{is=Is0}=Blk}|Bs0], Extracted) ->
Bs = bsm_skip(Bs0, Extracted),
Is = bsm_skip_is(Is0, Extracted),
- coalesce_skips({L,Blk#b_blk{is=Is}}, Bs);
+ [{L,Blk#b_blk{is=Is}}|Bs];
bsm_skip([], _) -> [].
bsm_skip_is([I0|Is], Extracted) ->
case I0 of
- #b_set{op=bs_match,
+ #b_set{anno=Anno0,
+ op=bs_match,
dst=Ctx,
args=[#b_literal{val=T}=Type,PrevCtx|Args0]}
when T =/= float, T =/= string, T =/= skip ->
@@ -1727,7 +1891,8 @@ bsm_skip_is([I0|Is], Extracted) ->
false ->
%% The value is never extracted.
Args = [#b_literal{val=skip},PrevCtx,Type|Args0],
- I0#b_set{args=Args}
+ Anno = maps:remove(arg_types, Anno0),
+ I0#b_set{anno=Anno,args=Args}
end,
[I|Is];
#b_set{} ->
@@ -1744,22 +1909,31 @@ bsm_extracted([{_,#b_blk{is=Is}}|Bs]) ->
end;
bsm_extracted([]) -> [].
+bsm_coalesce_skips([{L,Blk0}|Bs0], Renames0) ->
+ case coalesce_skips({L,Blk0}, Bs0, Renames0) of
+ not_possible ->
+ [{L,Blk0}|bsm_coalesce_skips(Bs0, Renames0)];
+ {Bs,Renames} ->
+ bsm_coalesce_skips(Bs, Renames)
+ end;
+bsm_coalesce_skips([], _Renames) -> [].
+
coalesce_skips({L,#b_blk{is=[#b_set{op=bs_extract}=Extract|Is0],
- last=Last0}=Blk0}, Bs0) ->
- case coalesce_skips_is(Is0, Last0, Bs0) of
+ last=Last0}=Blk0}, Bs0, Renames0) ->
+ case coalesce_skips_is(Is0, Last0, Bs0, Renames0) of
not_possible ->
- [{L,Blk0}|Bs0];
- {Is,Last,Bs} ->
+ not_possible;
+ {Is,Last,Bs,Renames} ->
Blk = Blk0#b_blk{is=[Extract|Is],last=Last},
- [{L,Blk}|Bs]
+ {[{L,Blk}|Bs],Renames}
end;
-coalesce_skips({L,#b_blk{is=Is0,last=Last0}=Blk0}, Bs0) ->
- case coalesce_skips_is(Is0, Last0, Bs0) of
+coalesce_skips({L,#b_blk{is=Is0,last=Last0}=Blk0}, Bs0, Renames0) ->
+ case coalesce_skips_is(Is0, Last0, Bs0, Renames0) of
not_possible ->
- [{L,Blk0}|Bs0];
- {Is,Last,Bs} ->
+ not_possible;
+ {Is,Last,Bs,Renames} ->
Blk = Blk0#b_blk{is=Is,last=Last},
- [{L,Blk}|Bs]
+ {[{L,Blk}|Bs],Renames}
end.
coalesce_skips_is([#b_set{op=bs_match,
@@ -1767,53 +1941,59 @@ coalesce_skips_is([#b_set{op=bs_match,
Ctx0,Type,Flags,
#b_literal{val=Size0},
#b_literal{val=Unit0}],
- dst=Ctx}=Skip0,
+ dst=PrevCtx}=Skip0,
#b_set{op={succeeded,guard}}],
#b_br{succ=L2,fail=Fail}=Br0,
- Bs0) when is_integer(Size0) ->
+ Bs0,
+ Renames0) when is_integer(Size0) ->
case Bs0 of
[{L2,#b_blk{is=[#b_set{op=bs_match,
dst=SkipDst,
- args=[#b_literal{val=skip},Ctx,_,_,
+ args=[#b_literal{val=skip},PrevCtx,_,_,
#b_literal{val=Size1},
#b_literal{val=Unit1}]},
#b_set{op={succeeded,guard}}=Succeeded],
last=#b_br{fail=Fail}=Br}}|Bs] when is_integer(Size1) ->
+ OldCtx = maps:get(Ctx0, Renames0, Ctx0),
SkipBits = Size0 * Unit0 + Size1 * Unit1,
Skip = Skip0#b_set{dst=SkipDst,
- args=[#b_literal{val=skip},Ctx0,
+ args=[#b_literal{val=skip},OldCtx,
Type,Flags,
#b_literal{val=SkipBits},
#b_literal{val=1}]},
Is = [Skip,Succeeded],
- {Is,Br,Bs};
+ Renames = Renames0#{PrevCtx => Ctx0},
+ {Is,Br,Bs,Renames};
[{L2,#b_blk{is=[#b_set{op=bs_test_tail,
- args=[Ctx,#b_literal{val=TailSkip}]}],
+ args=[PrevCtx,#b_literal{val=TailSkip}]}],
last=#b_br{succ=NextSucc,fail=Fail}}}|Bs] ->
+ OldCtx = maps:get(Ctx0, Renames0, Ctx0),
SkipBits = Size0 * Unit0,
TestTail = Skip0#b_set{op=bs_test_tail,
- args=[Ctx0,#b_literal{val=SkipBits+TailSkip}]},
+ args=[OldCtx,#b_literal{val=SkipBits+TailSkip}]},
Br = Br0#b_br{bool=TestTail#b_set.dst,succ=NextSucc},
Is = [TestTail],
- {Is,Br,Bs};
+ Renames = Renames0#{PrevCtx => Ctx0},
+ {Is,Br,Bs,Renames};
_ ->
not_possible
end;
-coalesce_skips_is(_, _, _) ->
+coalesce_skips_is(_, _, _, _) ->
not_possible.
%%%
%%% Short-cutting binary matching instructions.
%%%
-ssa_opt_bsm_shortcut({#opt_st{ssa=Linear}=St, FuncDb}) ->
- Positions = bsm_positions(Linear, #{}),
+ssa_opt_bsm_shortcut({#opt_st{ssa=Linear0}=St, FuncDb}) ->
+ Positions = bsm_positions(Linear0, #{}),
case map_size(Positions) of
0 ->
%% No binary matching instructions.
{St, FuncDb};
_ ->
- {St#opt_st{ssa=bsm_shortcut(Linear, Positions)}, FuncDb}
+ Linear = bsm_shortcut(Linear0, Positions),
+ ssa_opt_live({St#opt_st{ssa=Linear}, FuncDb})
end.
bsm_positions([{L,#b_blk{is=Is,last=Last}}|Bs], PosMap0) ->
@@ -1854,20 +2034,36 @@ bsm_update_bits([_,_,_,#b_literal{val=Sz},#b_literal{val=U}], Bits)
Bits + Sz*U;
bsm_update_bits(_, Bits) -> Bits.
-bsm_shortcut([{L,#b_blk{is=Is,last=Last0}=Blk}|Bs], PosMap) ->
+bsm_shortcut([{L,#b_blk{is=Is,last=Last0}=Blk}|Bs], PosMap0) ->
case {Is,Last0} of
{[#b_set{op=bs_match,dst=New,args=[_,Old|_]},
#b_set{op={succeeded,guard},dst=Bool,args=[New]}],
#b_br{bool=Bool,fail=Fail}} ->
- case PosMap of
- #{Old:=Bits,Fail:={TailBits,NextFail}} when Bits > TailBits ->
+ case PosMap0 of
+ #{Old := Bits,Fail := {TailBits,NextFail}} when Bits > TailBits ->
Last = Last0#b_br{fail=NextFail},
- [{L,Blk#b_blk{last=Last}}|bsm_shortcut(Bs, PosMap)];
+ [{L,Blk#b_blk{last=Last}}|bsm_shortcut(Bs, PosMap0)];
+ #{} ->
+ [{L,Blk}|bsm_shortcut(Bs, PosMap0)]
+ end;
+ {[#b_set{op=bs_test_tail,dst=Bool,args=[Old,#b_literal{val=TailBits}]}],
+ #b_br{bool=Bool,succ=Succ,fail=Fail}} ->
+ case PosMap0 of
+ #{{bs_test_tail,Old,L} := ActualTailBits} ->
+ Last1 = if
+ TailBits =:= ActualTailBits ->
+ Last0#b_br{fail=Succ};
+ true ->
+ Last0#b_br{succ=Fail}
+ end,
+ Last = beam_ssa:normalize(Last1),
+ [{L,Blk#b_blk{last=Last}}|bsm_shortcut(Bs, PosMap0)];
#{} ->
+ PosMap = PosMap0#{{bs_test_tail,Old,Succ} => TailBits},
[{L,Blk}|bsm_shortcut(Bs, PosMap)]
end;
{_,_} ->
- [{L,Blk}|bsm_shortcut(Bs, PosMap)]
+ [{L,Blk}|bsm_shortcut(Bs, PosMap0)]
end;
bsm_shortcut([], _PosMap) -> [].
@@ -1933,18 +2129,20 @@ opt_create_bin_arg(Type, Unit, Flags, #b_literal{val=Val}, #b_literal{val=Size})
when is_integer(Size), is_integer(Unit) ->
EffectiveSize = Size * Unit,
if
- EffectiveSize > 0 ->
+ EffectiveSize > (1 bsl 24) ->
+ %% Don't bother converting really huge segments as they might fail
+ %% with a `system_limit` exception in runtime. Keeping them as-is
+ %% ensures that the extended error information will be accurate.
+ %%
+ %% We'll also reduce the risk of crashing with an unhelpful "out of
+ %% memory" error message during compilation.
+ not_possible;
+ EffectiveSize > 0, EffectiveSize =< (1 bsl 24) ->
case {Type,opt_create_bin_endian(Flags)} of
{integer,big} when is_integer(Val) ->
if
EffectiveSize < 64 ->
[<<Val:EffectiveSize>>];
- EffectiveSize > 1 bsl 24 ->
- %% The binary construction could fail with a
- %% system_limit. Don't optimize to ensure that
- %% the extended error information will be
- %% accurate.
- not_possible;
true ->
opt_bs_put_split_int(Val, EffectiveSize)
end;
@@ -2322,7 +2520,7 @@ ssa_opt_sink({#opt_st{ssa=Linear}=St, FuncDb}) ->
{do_ssa_opt_sink(Defs, St), FuncDb}
end.
-do_ssa_opt_sink(Defs, #opt_st{ssa=Linear}=St) ->
+do_ssa_opt_sink(Defs, #opt_st{ssa=Linear}=St) when is_map(Defs) ->
%% Find all the blocks that use variables defined by
%% get_tuple_element instructions.
Used = used_blocks(Linear, Defs, []),
@@ -2415,9 +2613,8 @@ filter_deflocs([{Tuple,DefLoc0}|DLs], Preds, Blocks) ->
[{_,{_,First}}|_] = DefLoc0,
Paths = find_paths_to_check(DefLoc0, First),
WillGC0 = ordsets:from_list([FromTo || {{_,_}=FromTo,_} <- Paths]),
- WillGC1 = [{{From,To},will_gc(From, To, Preds, Blocks, true)} ||
- {From,To} <- WillGC0],
- WillGC = maps:from_list(WillGC1),
+ WillGC = #{{From,To} => will_gc(From, To, Preds, Blocks, true) ||
+ {From,To} <- WillGC0},
%% Separate sinks that will force the reference to the tuple to be
%% saved on the stack from sinks that don't force.
@@ -2552,7 +2749,7 @@ gc_update_successors(Blk, GC, WillGC) ->
%% Return an gbset of block labels for the blocks that are not
%% suitable for sinking of get_tuple_element instructions.
-unsuitable(Linear, Blocks, Predecessors) ->
+unsuitable(Linear, Blocks, Predecessors) when is_map(Blocks), is_map(Predecessors) ->
Unsuitable0 = unsuitable_1(Linear),
Unsuitable1 = unsuitable_recv(Linear, Blocks, Predecessors),
gb_sets:from_list(Unsuitable0 ++ Unsuitable1).
@@ -2560,6 +2757,7 @@ unsuitable(Linear, Blocks, Predecessors) ->
unsuitable_1([{L,#b_blk{is=[#b_set{op=Op}=I|_]}}|Bs]) ->
Unsuitable = case Op of
bs_extract -> true;
+ bs_match -> true;
{float,_} -> true;
landingpad -> true;
_ -> beam_ssa:is_loop_header(I)
@@ -2791,6 +2989,7 @@ collect_get_tuple_element(Is, _Src, Acc) ->
ssa_opt_unfold_literals({St,FuncDb}) ->
#opt_st{ssa=Blocks0,args=Args,anno=Anno} = St,
+ true = is_map(Blocks0), %Assertion.
ParamInfo = maps:get(parameter_info, Anno, #{}),
LitMap = collect_arg_literals(Args, ParamInfo, 0, #{}),
case map_size(LitMap) of
@@ -2823,6 +3022,8 @@ collect_arg_literals([V|Vs], Info, X, Acc0) ->
collect_arg_literals([], _Info, _X, Acc) ->
Acc.
+unfold_literals([?EXCEPTION_BLOCK|Ls], LitMap, SafeMap, Blocks) ->
+ unfold_literals(Ls, LitMap, SafeMap,Blocks);
unfold_literals([L|Ls], LitMap, SafeMap0, Blocks0) ->
{Blocks,Safe} =
case map_get(L, SafeMap0) of
@@ -2955,6 +3156,7 @@ unfold_arg(Expr, _LitMap, _X) ->
ssa_opt_tail_literals({St,FuncDb}) ->
#opt_st{cnt=Count0,ssa=Blocks0} = St,
+ true = is_map(Blocks0), %Assertion.
{Count, Blocks} = opt_tail_literals(beam_ssa:rpo(Blocks0), Count0, Blocks0),
{St#opt_st{cnt=Count,ssa=Blocks},FuncDb}.
@@ -3039,7 +3241,7 @@ is_tail_literal(_Is, _Last, _Blocks) ->
%%% ret Var
%%%
-ssa_opt_redundant_br({#opt_st{ssa=Blocks0}=St, FuncDb}) ->
+ssa_opt_redundant_br({#opt_st{ssa=Blocks0}=St, FuncDb}) when is_map(Blocks0) ->
Blocks = redundant_br(beam_ssa:rpo(Blocks0), Blocks0),
{St#opt_st{ssa=Blocks}, FuncDb}.
@@ -3123,6 +3325,156 @@ redundant_br_safe_bool(Is, Bool) ->
end.
%%%
+%%% Add the bs_ensure instruction before a sequence of `bs_match`
+%%% (SSA) instructions, each having a literal size and the
+%%% same failure label.
+%%%
+%%% This is the first part of building the `bs_match` (BEAM)
+%%% instruction that can match multiple segments having the same
+%%% failure label.
+%%%
+
+ssa_opt_bs_ensure({#opt_st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) when is_map(Blocks0) ->
+ RPO = beam_ssa:rpo(Blocks0),
+ Seen = sets:new([{version,2}]),
+ {Blocks,Count} = ssa_opt_bs_ensure(RPO, Seen, Count0, Blocks0),
+ {St#opt_st{ssa=Blocks,cnt=Count}, FuncDb}.
+
+ssa_opt_bs_ensure([L|Ls], Seen0, Count0, Blocks0) ->
+ case sets:is_element(L, Seen0) of
+ true ->
+ %% This block is already covered by a `bs_ensure`
+ %% instruction.
+ ssa_opt_bs_ensure(Ls, Seen0, Count0, Blocks0);
+ false ->
+ case is_bs_match_blk(L, Blocks0) of
+ no ->
+ ssa_opt_bs_ensure(Ls, Seen0, Count0, Blocks0);
+ {yes,Size0,#b_br{succ=Succ,fail=Fail}} ->
+ {Size,Blocks1,Seen} =
+ ssa_opt_bs_ensure_collect(Succ, Fail,
+ Blocks0, Seen0, Size0),
+ Blocks2 = annotate_match(L, Blocks1),
+ {Blocks,Count} = build_bs_ensure_match(L, Size, Count0, Blocks2),
+ ssa_opt_bs_ensure(Ls, Seen, Count, Blocks)
+ end
+ end;
+ssa_opt_bs_ensure([], _Seen, Count, Blocks) ->
+ {Blocks,Count}.
+
+ssa_opt_bs_ensure_collect(L, Fail, Blocks0, Seen0, Acc0) ->
+ case is_bs_match_blk(L, Blocks0) of
+ no ->
+ {Acc0,Blocks0,Seen0};
+ {yes,Size,#b_br{succ=Succ,fail=Fail}} ->
+ case update_size(Size, Acc0) of
+ no ->
+ {Acc0,Blocks0,Seen0};
+ Acc ->
+ Seen = sets:add_element(L, Seen0),
+ Blocks = annotate_match(L, Blocks0),
+ ssa_opt_bs_ensure_collect(Succ, Fail, Blocks, Seen, Acc)
+ end;
+ {yes,_,_} ->
+ {Acc0,Blocks0,Seen0}
+ end.
+
+annotate_match(L, Blocks) ->
+ #b_blk{is=Is0} = Blk0 = map_get(L, Blocks),
+ Is = [case I of
+ #b_set{op=bs_match} ->
+ beam_ssa:add_anno(ensured, true, I);
+ #b_set{} ->
+ I
+ end || I <- Is0],
+ Blk = Blk0#b_blk{is=Is},
+ Blocks#{L := Blk}.
+
+update_size({{PrevCtx,NewCtx},Size,Unit}, {{_,PrevCtx},Sum,Unit0}) ->
+ {{PrevCtx,NewCtx},Sum + Size,max(Unit, Unit0)};
+update_size(_, _) ->
+ no.
+
+is_bs_match_blk(L, Blocks) ->
+ Blk = map_get(L, Blocks),
+ case Blk of
+ #b_blk{is=Is,last=#b_br{bool=#b_var{}}=Last} ->
+ case is_bs_match_is(Is) of
+ no ->
+ no;
+ {yes,CtxSizeUnit} ->
+ {yes,CtxSizeUnit,Last}
+ end;
+ #b_blk{} ->
+ no
+ end.
+
+is_bs_match_is([#b_set{op=bs_match,dst=Dst}=I,
+ #b_set{op={succeeded,guard},args=[Dst]}]) ->
+ case is_viable_match(I) of
+ no ->
+ no;
+ {yes,{Ctx,Size,Unit}} when Size bsr 24 =:= 0 ->
+ %% Only include matches of reasonable size.
+ {yes,{{Ctx,Dst},Size,Unit}};
+ {yes,_} ->
+ %% Too large size.
+ no
+ end;
+is_bs_match_is([_|Is]) ->
+ is_bs_match_is(Is);
+is_bs_match_is([]) -> no.
+
+is_viable_match(#b_set{op=bs_match,args=Args}) ->
+ case Args of
+ [#b_literal{val=binary},Ctx,_,#b_literal{val=all},#b_literal{val=U}]
+ when is_integer(U), 1 =< U, U =< 256 ->
+ {yes,{Ctx,0,U}};
+ [#b_literal{val=binary},Ctx,_,#b_literal{val=Size},#b_literal{val=U}]
+ when is_integer(Size) ->
+ {yes,{Ctx,Size*U,1}};
+ [#b_literal{val=integer},Ctx,_,#b_literal{val=Size},#b_literal{val=U}]
+ when is_integer(Size) ->
+ {yes,{Ctx,Size*U,1}};
+ [#b_literal{val=skip},Ctx,_,_,#b_literal{val=all},#b_literal{val=U}] ->
+ {yes,{Ctx,0,U}};
+ [#b_literal{val=skip},Ctx,_,_,#b_literal{val=Size},#b_literal{val=U}]
+ when is_integer(Size) ->
+ {yes,{Ctx,Size*U,1}};
+ [#b_literal{val=string},Ctx,#b_literal{val=Str}] when bit_size(Str) =< 64 ->
+ {yes,{Ctx,bit_size(Str),1}};
+ _ ->
+ no
+ end.
+
+build_bs_ensure_match(L, {_,Size,Unit}, Count0, Blocks0) ->
+ BsMatchL = Count0,
+ Count1 = Count0 + 1,
+ {NewCtx,Count2} = new_var('@context', Count1),
+ {SuccBool,Count} = new_var('@ssa_bool', Count2),
+
+ BsMatchBlk0 = map_get(L, Blocks0),
+
+ #b_blk{is=MatchIs,last=#b_br{fail=Fail}} = BsMatchBlk0,
+ {Prefix,Suffix0} = splitwith(fun(#b_set{op=Op}) -> Op =/= bs_match end, MatchIs),
+ [BsMatch0|Suffix1] = Suffix0,
+ #b_set{args=[Type,_Ctx|Args]} = BsMatch0,
+ BsMatch = BsMatch0#b_set{args=[Type,NewCtx|Args]},
+ Suffix = [BsMatch|Suffix1],
+ BsMatchBlk = BsMatchBlk0#b_blk{is=Suffix},
+
+ #b_set{args=[_,Ctx|_]} = keyfind(bs_match, #b_set.op, MatchIs),
+ Is = Prefix ++ [#b_set{op=bs_ensure,
+ dst=NewCtx,
+ args=[Ctx,#b_literal{val=Size},#b_literal{val=Unit}]},
+ #b_set{op={succeeded,guard},dst=SuccBool,args=[NewCtx]}],
+ Blk = #b_blk{is=Is,last=#b_br{bool=SuccBool,succ=BsMatchL,fail=Fail}},
+
+ Blocks = Blocks0#{L := Blk, BsMatchL => BsMatchBlk},
+
+ {Blocks,Count}.
+
+%%%
%%% Common utilities.
%%%
diff --git a/lib/compiler/src/beam_ssa_pp.erl b/lib/compiler/src/beam_ssa_pp.erl
index 4e6974385e..b2f682b705 100644
--- a/lib/compiler/src/beam_ssa_pp.erl
+++ b/lib/compiler/src/beam_ssa_pp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
%%
-module(beam_ssa_pp).
--export([format_function/1,format_instr/1,format_var/1]).
+-export([format_function/1,format_instr/1,format_var/1,format_type/1]).
-include("beam_ssa.hrl").
-include("beam_types.hrl").
@@ -48,7 +48,7 @@ format_function(#b_function{anno=Anno0,args=Args,
end,
io_lib:format("%% Counter = ~p\n", [Counter]),
[format_anno(Key, Value) ||
- {Key,Value} <- lists:sort(maps:to_list(Anno))],
+ Key := Value <- maps:iterator(Anno, ordered)],
io_lib:format("function `~p`:`~p`(~ts) {\n",
[M, F, format_args(Args, FuncAnno)]),
[format_live_interval(Var, FuncAnno) || Var <- Args],
@@ -99,17 +99,17 @@ format_anno(parameter_info, Map) when is_map(Map) ->
{V,I} <- Params]]
end;
format_anno(Key, Map) when is_map(Map) ->
- Sorted = lists:sort(maps:to_list(Map)),
+ Sorted = maps:to_list(maps:iterator(Map, ordered)),
[io_lib:format("%% ~s:\n", [Key]),
- [io_lib:format("%% ~w => ~w\n", [K,V]) || {K,V} <- Sorted]];
+ [io_lib:format("%% ~kw => ~kw\n", [K,V]) || {K,V} <- Sorted]];
format_anno(Key, Value) ->
- io_lib:format("%% ~s: ~p\n", [Key,Value]).
+ io_lib:format("%% ~s: ~kp\n", [Key,Value]).
format_param_info([{type, T} | Infos], Break) ->
[format_type(T, Break) |
format_param_info(Infos, Break)];
format_param_info([Info | Infos], Break) ->
- [io_lib:format("~s~p", [Break, Info]) |
+ [io_lib:format("~s~kp", [Break, Info]) |
format_param_info(Infos, Break)];
format_param_info([], _Break) ->
[].
@@ -127,9 +127,9 @@ format_block(L, Blocks, FuncAnno) ->
#b_blk{anno=Anno,is=Is,last=Last} = maps:get(L, Blocks),
[case map_size(Anno) of
0 -> [];
- _ -> io_lib:format("%% ~p\n", [Anno])
+ _ -> io_lib:format("%% ~kp\n", [Anno])
end,
- io_lib:format("~p:", [L]),
+ io_lib:format("~kp:", [L]),
format_instrs(Is, FuncAnno, true),
$\n,
format_terminator(Last, FuncAnno)].
@@ -171,20 +171,31 @@ format_i_number(#{}) -> [].
format_terminator(#b_br{anno=A,bool=#b_literal{val=true},
succ=Same,fail=Same}, _) ->
- io_lib:format(" ~sbr ~ts\n", [format_i_number(A),format_label(Same)]);
+ io_lib:format("~s ~sbr ~ts\n",
+ [format_terminator_anno(A),
+ format_i_number(A),
+ format_label(Same)]);
format_terminator(#b_br{anno=A,bool=Bool,succ=Succ,fail=Fail}, FuncAnno) ->
- io_lib:format(" ~sbr ~ts, ~ts, ~ts\n",
- [format_i_number(A),
+ io_lib:format("~s ~sbr ~ts, ~ts, ~ts\n",
+ [format_terminator_anno(A),
+ format_i_number(A),
format_arg(Bool, FuncAnno),
format_label(Succ),
format_label(Fail)]);
format_terminator(#b_switch{anno=A,arg=Arg,fail=Fail,list=List}, FuncAnno) ->
- io_lib:format(" ~sswitch ~ts, ~ts, ~ts\n",
- [format_i_number(A),format_arg(Arg, FuncAnno),
+ io_lib:format("~s ~sswitch ~ts, ~ts, ~ts\n",
+ [format_terminator_anno(A),
+ format_i_number(A),format_arg(Arg, FuncAnno),
format_label(Fail),
format_switch_list(List, FuncAnno)]);
format_terminator(#b_ret{anno=A,arg=Arg}, FuncAnno) ->
- io_lib:format(" ~sret ~ts\n", [format_i_number(A),format_arg(Arg, FuncAnno)]).
+ io_lib:format("~s ~sret ~ts\n",
+ [format_terminator_anno(A),
+ format_i_number(A),
+ format_arg(Arg, FuncAnno)]).
+
+format_terminator_anno(Anno) ->
+ format_instr_anno(Anno, #{}, []).
format_op({Prefix,Name}) ->
io_lib:format("~p:~p", [Prefix,Name]);
@@ -222,7 +233,7 @@ format_args(Args, FuncAnno) ->
format_arg(#b_var{}=Arg, FuncAnno) ->
format_var(Arg, FuncAnno);
format_arg(#b_literal{val=Val}, _FuncAnno) ->
- io_lib:format("`~p`", [Val]);
+ io_lib:format("`~kp`", [Val]);
format_arg(#b_remote{mod=Mod,name=Name,arity=Arity}, FuncAnno) ->
io_lib:format("(~ts:~ts/~p)",
[format_arg(Mod, FuncAnno),format_arg(Name, FuncAnno),Arity]);
@@ -232,7 +243,7 @@ format_arg({Value,Label}, FuncAnno) when is_integer(Label) ->
io_lib:format("{ ~ts, ~ts }", [format_arg(Value, FuncAnno),
format_label(Label)]);
format_arg(Other, _) ->
- io_lib:format("*** ~p ***", [Other]).
+ io_lib:format("*** ~kp ***", [Other]).
format_switch_list(List, FuncAnno) ->
Ss = [io_lib:format("{ ~ts, ~ts }", [format_arg(Val, FuncAnno),
@@ -269,6 +280,16 @@ format_instr_anno(#{arg_types:=Ts}=Anno0, FuncAnno, Args) ->
[io_lib:format(" %% Argument types:~s~ts\n",
[Break, unicode:characters_to_list(Formatted)]) |
format_instr_anno(Anno, FuncAnno, Args)];
+format_instr_anno(#{aliased:=As}=Anno, FuncAnno, Args) ->
+ Break = "\n %% ",
+ [" %% Aliased:",
+ string:join([[Break, format_var(V)] || V <- As], ", "), "\n",
+ format_instr_anno(maps:remove(aliased, Anno), FuncAnno, Args)];
+format_instr_anno(#{unique:=Us}=Anno, FuncAnno, Args) ->
+ Break = "\n %% ",
+ [" %% Unique:",
+ string:join([[Break, format_var(V)] || V <- Us], ", "), "\n",
+ format_instr_anno(maps:remove(unique, Anno), FuncAnno, Args)];
format_instr_anno(Anno, _FuncAnno, _Args) ->
format_instr_anno_1(Anno).
@@ -277,7 +298,7 @@ format_instr_anno_1(Anno) ->
0 ->
[];
_ ->
- [io_lib:format(" %% Anno: ~p\n", [Anno])]
+ [io_lib:format(" %% Anno: ~kp\n", [Anno])]
end.
format_live_interval(#b_var{}=Dst, #{live_intervals:=Intervals}) ->
@@ -292,6 +313,8 @@ format_live_interval(#b_var{}=Dst, #{live_intervals:=Intervals}) ->
end;
format_live_interval(_, _) -> [].
+-spec format_type(type()) -> iolist().
+
format_type(any) ->
"any()";
format_type(#t_atom{elements=any}) ->
@@ -301,6 +324,8 @@ format_type(#t_atom{elements=Es}) ->
|| E <- ordsets:to_list(Es)], " | ");
format_type(#t_bs_matchable{tail_unit=U}) ->
io_lib:format("bs_matchable(~p)", [U]);
+format_type(#t_bitstring{size_unit=S,appendable=true}) ->
+ io_lib:format("bitstring(~p,appendable)", [S]);
format_type(#t_bitstring{size_unit=S}) ->
io_lib:format("bitstring(~p)", [S]);
format_type(#t_bs_context{tail_unit=U}) ->
@@ -333,6 +358,12 @@ format_type(#t_integer{elements={X,X}}) ->
io_lib:format("~p", [X]);
format_type(#t_integer{elements={Low,High}}) ->
io_lib:format("~p..~p", [Low,High]);
+format_type(#t_number{elements=any}) ->
+ "number()";
+format_type(#t_number{elements={X,X}}) ->
+ io_lib:format("number(~p)", [X]);
+format_type(#t_number{elements={Low,High}}) ->
+ io_lib:format("number(~p, ~p)", [Low,High]);
format_type(#t_list{type=ET,terminator=nil}) ->
["list(", format_type(ET), ")"];
format_type(#t_list{type=ET,terminator=TT}) ->
@@ -347,12 +378,16 @@ format_type(#t_tuple{elements=Es,exact=Ex,size=S}) ->
["{",
string:join(format_tuple_elems(S, Ex, Es, 1), ", "),
"}"];
+format_type(other) ->
+ "other()";
format_type(pid) ->
"pid()";
format_type(port) ->
"pid()";
format_type(reference) ->
"reference()";
+format_type(identifier) ->
+ "identifier()";
format_type(none) ->
"none()";
format_type(#t_union{atom=A,list=L,number=N,tuple_set=Ts,other=O}) ->
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
index bb9aa75797..dc76755aad 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -73,8 +73,8 @@
-import(lists, [all/2,any/2,append/1,duplicate/2,
foldl/3,last/1,member/2,partition/2,
- reverse/1,reverse/2,seq/2,sort/1,
- splitwith/2,usort/1,zip/2]).
+ reverse/1,reverse/2,seq/2,sort/1,sort/2,
+ usort/1,zip/2]).
-spec module(beam_ssa:b_module(), [compile:option()]) ->
{'ok',beam_ssa:b_module()}.
@@ -119,7 +119,7 @@ passes(Opts) ->
?PASS(fix_bs),
?PASS(sanitize),
?PASS(expand_match_fail),
- ?PASS(use_set_tuple_element),
+ ?PASS(expand_update_tuple),
?PASS(place_frames),
?PASS(fix_receives),
@@ -203,20 +203,23 @@ assert_no_ces(_, _, Blocks) -> Blocks.
fix_bs(#st{ssa=Blocks,cnt=Count0}=St) ->
F = fun(#b_set{op=bs_start_match,dst=Dst}, A) ->
%% Mark the root of the match context list.
- [{Dst,{context,Dst}}|A];
+ A#{Dst => {context,Dst}};
+ (#b_set{op=bs_ensure,dst=Dst,args=[ParentCtx|_]}, A) ->
+ %% Link this match context to the previous match context.
+ A#{Dst => ParentCtx};
(#b_set{op=bs_match,dst=Dst,args=[_,ParentCtx|_]}, A) ->
- %% Link this match context the previous match context.
- [{Dst,ParentCtx}|A];
+ %% Link this match context to the previous match context.
+ A#{Dst => ParentCtx};
(_, A) ->
A
end,
RPO = beam_ssa:rpo(Blocks),
- case beam_ssa:fold_instrs(F, RPO, [], Blocks) of
- [] ->
+ CtxChain = beam_ssa:fold_instrs(F, RPO, #{}, Blocks),
+ case map_size(CtxChain) of
+ 0 ->
%% No binary matching in this function.
St;
- [_|_]=M ->
- CtxChain = maps:from_list(M),
+ _ ->
Linear0 = beam_ssa:linearize(Blocks),
%% Insert position instructions where needed.
@@ -347,6 +350,45 @@ bs_restores_is([#b_set{op=bs_start_match,dst=Start}|Is],
FPos = SPos0,
SPos = SPos0#{Start=>Start},
bs_restores_is(Is, CtxChain, SPos, FPos, Rs);
+bs_restores_is([#b_set{op=bs_ensure,dst=NewPos,args=Args}|Is],
+ CtxChain, SPos0, _FPos, Rs0) ->
+ Start = bs_subst_ctx(NewPos, CtxChain),
+ [FromPos,#b_literal{val=Bits}|_] = Args,
+ case SPos0 of
+ #{Start := FromPos} ->
+ %% Same position, no restore needed.
+ SPos = case Bits of
+ 0 -> SPos0;
+ _ -> SPos0#{Start := NewPos}
+ end,
+ FPos = SPos0,
+ bs_restores_is(Is, CtxChain, SPos, FPos, Rs0);
+ #{} ->
+ SPos = SPos0#{Start := NewPos},
+ FPos = SPos0#{Start := FromPos},
+ Rs = Rs0#{NewPos=>{Start,FromPos}},
+ bs_restores_is(Is, CtxChain, SPos, FPos, Rs)
+ end;
+bs_restores_is([#b_set{anno=#{ensured := _},
+ op=bs_match,dst=NewPos,args=Args}|Is],
+ CtxChain, SPos0, _FPos, Rs) ->
+ %% This match instruction will be a part of a `bs_match` BEAM
+ %% instruction, so there will never be a restore to this
+ %% position.
+ Start = bs_subst_ctx(NewPos, CtxChain),
+ case Args of
+ [#b_literal{val=skip},_FromPos,_Type,_Flags,#b_literal{val=all},_] ->
+ %% This instruction will be optimized away. (The unit test
+ %% part of it has been take care of by the preceding
+ %% bs_ensure instruction.) All positions will be
+ %% unchanged.
+ SPos = FPos = SPos0,
+ bs_restores_is(Is, CtxChain, SPos, FPos, Rs);
+ [_,FromPos|_] ->
+ SPos = SPos0#{Start := NewPos},
+ FPos = SPos0#{Start := FromPos},
+ bs_restores_is(Is, CtxChain, SPos, FPos, Rs)
+ end;
bs_restores_is([#b_set{op=bs_match,dst=NewPos,args=Args}=I|Is],
CtxChain, SPos0, _FPos, Rs0) ->
Start = bs_subst_ctx(NewPos, CtxChain),
@@ -483,13 +525,13 @@ bs_restore_args([], Pos, _CtxChain, _Dst, Rs) ->
%% Insert all bs_save and bs_restore instructions.
bs_insert_bsm3(Blocks, Saves, Restores) ->
- bs_insert_1(Blocks, [], Saves, Restores, fun(I) -> I end).
+ bs_insert_1(Blocks, [], Saves, Restores).
-bs_insert_1([{L,#b_blk{is=Is0}=Blk} | Bs], Deferred0, Saves, Restores, XFrm) ->
+bs_insert_1([{L,#b_blk{is=Is0}=Blk} | Bs], Deferred0, Saves, Restores) ->
Is1 = bs_insert_deferred(Is0, Deferred0),
- {Is, Deferred} = bs_insert_is(Is1, Saves, Restores, XFrm, []),
- [{L,Blk#b_blk{is=Is}} | bs_insert_1(Bs, Deferred, Saves, Restores, XFrm)];
-bs_insert_1([], [], _, _, _) ->
+ {Is, Deferred} = bs_insert_is(Is1, Saves, Restores, []),
+ [{L,Blk#b_blk{is=Is}} | bs_insert_1(Bs, Deferred, Saves, Restores)];
+bs_insert_1([], [], _, _) ->
[].
bs_insert_deferred([#b_set{op=bs_extract}=I | Is], Deferred) ->
@@ -497,8 +539,7 @@ bs_insert_deferred([#b_set{op=bs_extract}=I | Is], Deferred) ->
bs_insert_deferred(Is, Deferred) ->
Deferred ++ Is.
-bs_insert_is([#b_set{dst=Dst}=I0|Is], Saves, Restores, XFrm, Acc0) ->
- I = XFrm(I0),
+bs_insert_is([#b_set{dst=Dst}=I|Is], Saves, Restores, Acc0) ->
Pre = case Restores of
#{Dst:=R} -> [R];
#{} -> []
@@ -513,9 +554,9 @@ bs_insert_is([#b_set{dst=Dst}=I0|Is], Saves, Restores, XFrm, Acc0) ->
%% Defer the save sequence to the success block.
{reverse(Acc, Is), Post};
_ ->
- bs_insert_is(Is, Saves, Restores, XFrm, Post ++ Acc)
+ bs_insert_is(Is, Saves, Restores, Post ++ Acc)
end;
-bs_insert_is([], _, _, _, Acc) ->
+bs_insert_is([], _, _, Acc) ->
{reverse(Acc), []}.
%% Translate bs_match instructions to bs_get, bs_match_string,
@@ -534,18 +575,45 @@ bs_instrs([{L,#b_blk{is=Is0}=Blk}|Bs], CtxChain, Acc0) ->
bs_instrs(Bs, CtxChain, [{L,Blk#b_blk{is=Is}}|Acc0])
end;
bs_instrs([], _, Acc) ->
- reverse(Acc).
+ bs_rewrite_skip(Acc).
+
+bs_rewrite_skip([{L,#b_blk{is=Is0,last=Last0}=Blk}|Bs]) ->
+ case bs_rewrite_skip_is(Is0, []) of
+ no ->
+ [{L,Blk}|bs_rewrite_skip(Bs)];
+ {yes,Is} ->
+ #b_br{succ=Succ} = Last0,
+ Last = beam_ssa:normalize(Last0#b_br{fail=Succ}),
+ [{L,Blk#b_blk{is=Is,last=Last}}|bs_rewrite_skip(Bs)]
+ end;
+bs_rewrite_skip([]) ->
+ [].
+
+bs_rewrite_skip_is([#b_set{anno=#{ensured := true},op=bs_skip}=I0,
+ #b_set{op={succeeded,guard}}], Acc) ->
+ I = I0#b_set{op=bs_checked_skip},
+ {yes,reverse(Acc, [I])};
+bs_rewrite_skip_is([I|Is], Acc) ->
+ bs_rewrite_skip_is(Is, [I|Acc]);
+bs_rewrite_skip_is([], _Acc) ->
+ no.
bs_instrs_is([#b_set{op={succeeded,_}}=I|Is], CtxChain, Acc) ->
%% This instruction refers to a specific operation, so we must not
%% substitute the context argument.
bs_instrs_is(Is, CtxChain, [I | Acc]);
-bs_instrs_is([#b_set{op=Op,args=Args0}=I0|Is], CtxChain, Acc) ->
+bs_instrs_is([#b_set{anno=Anno0,op=Op,args=Args0}=I0|Is], CtxChain, Acc) ->
Args = [bs_subst_ctx(A, CtxChain) || A <- Args0],
I1 = I0#b_set{args=Args},
I = case {Op,Args} of
{bs_match,[#b_literal{val=skip},Ctx,Type|As]} ->
- I1#b_set{op=bs_skip,args=[Type,Ctx|As]};
+ Anno = case Anno0 of
+ #{arg_types := #{4 := SizeType}} ->
+ Anno0#{arg_types := #{3 => SizeType}};
+ #{} ->
+ Anno0
+ end,
+ I1#b_set{anno=Anno,op=bs_skip,args=[Type,Ctx|As]};
{bs_match,[#b_literal{val=string},Ctx|As]} ->
I1#b_set{op=bs_match_string,args=[Ctx|As]};
{_,_} ->
@@ -560,10 +628,19 @@ bs_instrs_is([], _, Acc) ->
bs_combine(Dst, Ctx, [{L,#b_blk{is=Is0}=Blk}|Acc]) ->
[#b_set{}=Succeeded,
- #b_set{op=bs_match,args=[Type,_|As]}=BsMatch|Is1] = reverse(Is0),
- Is = reverse(Is1, [BsMatch#b_set{op=bs_get,dst=Dst,args=[Type,Ctx|As]},
- Succeeded#b_set{args=[Dst]}]),
- [{L,Blk#b_blk{is=Is}}|Acc].
+ #b_set{anno=Anno,op=bs_match,args=[Type,_|As]}=BsMatch|Is1] = reverse(Is0),
+ if
+ is_map_key(ensured, Anno) ->
+ Is = reverse(Is1, [BsMatch#b_set{op=bs_checked_get,dst=Dst,
+ args=[Type,Ctx|As]}]),
+ #b_blk{last=#b_br{succ=Succ}=Br0} = Blk,
+ Br = beam_ssa:normalize(Br0#b_br{fail=Succ}),
+ [{L,Blk#b_blk{is=Is,last=Br}}|Acc];
+ true ->
+ Is = reverse(Is1, [BsMatch#b_set{op=bs_get,dst=Dst,args=[Type,Ctx|As]},
+ Succeeded#b_set{args=[Dst]}]),
+ [{L,Blk#b_blk{is=Is}}|Acc]
+ end.
bs_subst_ctx(#b_var{}=Var, CtxChain) ->
case CtxChain of
@@ -589,84 +666,89 @@ bs_subst_ctx(Other, _CtxChain) ->
sanitize(#st{ssa=Blocks0,cnt=Count0}=St) ->
Ls = beam_ssa:rpo(Blocks0),
- {Blocks,Count} = sanitize(Ls, Blocks0, Count0, #{}),
+ {Blocks,Count} = sanitize(Ls, Blocks0, Count0, #{}, #{0 => reachable}),
St#st{ssa=Blocks,cnt=Count}.
-sanitize([L|Ls], Blocks0, Count0, Values0) ->
- #b_blk{is=Is0,last=Last0} = Blk0 = map_get(L, Blocks0),
- case sanitize_is(Is0, Last0, Blocks0, Count0, Values0, false, []) of
- no_change ->
- sanitize(Ls, Blocks0, Count0, Values0);
- {Is,Last,Count,Values} ->
- Blk = Blk0#b_blk{is=Is,last=Last},
- Blocks = Blocks0#{L:=Blk},
- sanitize(Ls, Blocks, Count, Values)
- end;
-sanitize([], Blocks0, Count, Values) ->
- Blocks = if
- map_size(Values) =:= 0 ->
- Blocks0;
- true ->
- RPO = beam_ssa:rpo(Blocks0),
- beam_ssa:rename_vars(Values, RPO, Blocks0)
- end,
+sanitize([L|Ls], InBlocks, Count0, Values0, Blocks0) ->
+ case is_map_key(L, Blocks0) of
+ false ->
+ %% This block will never be reached. Discard it.
+ sanitize(Ls, InBlocks, Count0, Values0, Blocks0);
+ true ->
+ #b_blk{is=Is0,last=Last0} = Blk0 = map_get(L, InBlocks),
+ case sanitize_is(Is0, Last0, InBlocks, Blocks0, Count0, Values0, false, []) of
+ no_change ->
+ Blk = sanitize_last(Blk0, Values0),
+ Blocks1 = Blocks0#{L := Blk},
+ Blocks = sanitize_reachable(Blk, Blocks1),
+ sanitize(Ls, InBlocks, Count0, Values0, Blocks);
+ {Is,Last,Count,Values} ->
+ Blk1 = Blk0#b_blk{is=Is,last=Last},
+ Blk = sanitize_last(Blk1, Values),
+ Blocks1 = Blocks0#{L := Blk},
+ Blocks = sanitize_reachable(Blk, Blocks1),
+ sanitize(Ls, InBlocks, Count, Values, Blocks)
+ end
+ end;
+sanitize([], _InBlocks, Count, _Values, Blocks) ->
+ {Blocks,Count}.
- %% Unreachable blocks can cause problems for the dominator calculations.
- Ls = beam_ssa:rpo(Blocks),
- Reachable = gb_sets:from_list(Ls),
- {case map_size(Blocks) =:= gb_sets:size(Reachable) of
- true -> Blocks;
- false -> remove_unreachable(Ls, Blocks, Reachable, [])
- end, Count}.
+sanitize_reachable(Blk, Blocks) ->
+ foldl(fun(S, A) when is_map_key(S, A) -> A;
+ (S, A) -> A#{S => reachable}
+ end, Blocks, beam_ssa:successors(Blk)).
sanitize_is([#b_set{op=get_map_element,args=Args0}=I0|Is],
- Last, Blocks, Count0, Values, Changed, Acc) ->
+ Last, InBlocks, Blocks, Count0, Values, Changed, Acc) ->
case sanitize_args(Args0, Values) of
[#b_literal{}=Map,Key] ->
%% Bind the literal map to a variable.
{MapVar,Count} = new_var('@ssa_map', Count0),
I = I0#b_set{args=[MapVar,Key]},
Copy = #b_set{op=copy,dst=MapVar,args=[Map]},
- sanitize_is(Is, Last, Blocks, Count, Values, true, [I,Copy|Acc]);
+ sanitize_is(Is, Last, InBlocks, Blocks, Count,
+ Values, true, [I,Copy|Acc]);
[_,_]=Args0 ->
- sanitize_is(Is, Last, Blocks, Count0, Values, Changed, [I0|Acc]);
+ sanitize_is(Is, Last, InBlocks, Blocks, Count0,
+ Values, Changed, [I0|Acc]);
[_,_]=Args ->
I = I0#b_set{args=Args},
- sanitize_is(Is, Last, Blocks, Count0, Values, true, [I|Acc])
+ sanitize_is(Is, Last, InBlocks, Blocks, Count0,
+ Values, true, [I|Acc])
end;
sanitize_is([#b_set{op=call,dst=CallDst}=Call,
#b_set{op={succeeded,body},dst=SuccDst,args=[CallDst]}=Succ],
#b_br{bool=SuccDst,succ=SuccLbl,fail=?EXCEPTION_BLOCK}=Last0,
- Blocks, Count, Values, Changed, Acc) ->
- case Blocks of
- #{ SuccLbl := #b_blk{is=[],last=#b_ret{arg=CallDst}=Last} } ->
+ InBlocks, Blocks, Count, Values, Changed, Acc) ->
+ case InBlocks of
+ #{SuccLbl := #b_blk{is=[],last=#b_ret{arg=CallDst}=Last}} ->
%% Tail call that may fail, translate the terminator to an ordinary
%% return to simplify code generation.
- do_sanitize_is(Call, [], Last, Blocks, Count, Values,
- true, Acc);
+ do_sanitize_is(Call, [], Last, InBlocks, Blocks,
+ Count, Values, true, Acc);
#{} ->
- do_sanitize_is(Call, [Succ], Last0, Blocks, Count, Values,
- Changed, Acc)
+ do_sanitize_is(Call, [Succ], Last0, InBlocks, Blocks,
+ Count, Values, Changed, Acc)
end;
sanitize_is([#b_set{op=Op,dst=Dst}=Fail,
#b_set{op={succeeded,body},args=[Dst]}],
#b_br{fail=?EXCEPTION_BLOCK},
- Blocks, Count, Values, _Changed, Acc)
+ InBlocks, Blocks, Count, Values, _Changed, Acc)
when Op =:= match_fail; Op =:= resume ->
%% Match failure or rethrow without a local handler. Translate the
%% terminator to an ordinary return to simplify code generation.
Last = #b_ret{arg=Dst},
- do_sanitize_is(Fail, [], Last, Blocks, Count, Values, true, Acc);
+ do_sanitize_is(Fail, [], Last, InBlocks, Blocks, Count, Values, true, Acc);
sanitize_is([#b_set{op=match_fail,dst=RaiseDst},
#b_set{op={succeeded,guard},dst=SuccDst,args=[RaiseDst]}],
#b_br{bool=SuccDst}=Last0,
- Blocks, Count, Values, _Changed, Acc) ->
+ InBlocks, Blocks, Count, Values, _Changed, Acc) ->
%% Match failures may be present in guards when optimizations are turned
%% off. They must be treated as if they always fail.
Last = beam_ssa:normalize(Last0#b_br{bool=#b_literal{val=false}}),
- sanitize_is([], Last, Blocks, Count, Values, true, Acc);
+ sanitize_is([], Last, InBlocks, Blocks, Count, Values, true, Acc);
sanitize_is([#b_set{op={succeeded,_Kind},dst=Dst,args=[Arg0]}=I0],
- #b_br{bool=Dst}=Last, _Blocks, Count, Values, _Changed, Acc) ->
+ #b_br{bool=Dst}=Last, _InBlocks, _Blocks, Count, Values, _Changed, Acc) ->
%% We no longer need to distinguish between guard and body checks, so we'll
%% rewrite this as a plain 'succeeded'.
case sanitize_arg(Arg0, Values) of
@@ -678,7 +760,7 @@ sanitize_is([#b_set{op={succeeded,_Kind},dst=Dst,args=[Arg0]}=I0],
{reverse(Acc), Last, Count, Values#{ Dst => Value }}
end;
sanitize_is([#b_set{op={succeeded,Kind},args=[Arg0]} | Is],
- Last, Blocks, Count, Values, _Changed, Acc) ->
+ Last, InBlocks, Blocks, Count, Values, _Changed, Acc) ->
%% We're no longer branching on this instruction and can safely remove it.
[] = Is, #b_br{succ=Same,fail=Same} = Last, %Assertion.
if
@@ -687,24 +769,41 @@ sanitize_is([#b_set{op={succeeded,Kind},args=[Arg0]} | Is],
%% in a try/catch; rewrite the terminator to a return.
body = Kind, %Assertion.
Arg = sanitize_arg(Arg0, Values),
- sanitize_is(Is, #b_ret{arg=Arg}, Blocks, Count, Values, true, Acc);
+ sanitize_is(Is, #b_ret{arg=Arg}, InBlocks, Blocks,
+ Count, Values, true, Acc);
Same =/= ?EXCEPTION_BLOCK ->
%% We either always succeed, or always fail to somewhere other than
%% the exception block.
true = Kind =:= guard orelse Kind =:= body, %Assertion.
- sanitize_is(Is, Last, Blocks, Count, Values, true, Acc)
+ sanitize_is(Is, Last, InBlocks, Blocks, Count, Values, true, Acc)
end;
-sanitize_is([#b_set{op=bs_test_tail}=I], Last, Blocks, Count, Values,
- Changed, Acc) ->
+sanitize_is([#b_set{op=bs_test_tail}=I], Last, InBlocks, Blocks,
+ Count, Values, Changed, Acc) ->
case Last of
#b_br{succ=Same,fail=Same} ->
- sanitize_is([], Last, Blocks, Count, Values, true, Acc);
+ sanitize_is([], Last, InBlocks, Blocks,
+ Count, Values, true, Acc);
_ ->
- do_sanitize_is(I, [], Last, Blocks, Count, Values, Changed, Acc)
+ do_sanitize_is(I, [], Last, InBlocks, Blocks,
+ Count, Values, Changed, Acc)
+ end;
+sanitize_is([#b_set{op=bs_get,args=Args0}=I0|Is], Last, InBlocks, Blocks,
+ Count, Values, Changed, Acc) ->
+ case {Args0,sanitize_args(Args0, Values)} of
+ {[_,_,_,#b_var{},_],[Type,Val,Flags,#b_literal{val=all},Unit]} ->
+ %% The size `all` is used for the size of the final binary
+ %% segment in a pattern. Using `all` explicitly is not allowed,
+ %% so we convert it to an obvious invalid size.
+ Args = [Type,Val,Flags,#b_literal{val=bad_size},Unit],
+ I = I0#b_set{args=Args},
+ sanitize_is(Is, Last, InBlocks, Blocks, Count, Values, true, [I|Acc]);
+ {_,Args} ->
+ I = I0#b_set{args=Args},
+ sanitize_is(Is, Last, InBlocks, Blocks, Count, Values, Changed, [I|Acc])
end;
-sanitize_is([#b_set{}=I|Is], Last, Blocks, Count, Values, Changed, Acc) ->
- do_sanitize_is(I, Is, Last, Blocks, Count, Values, Changed, Acc);
-sanitize_is([], Last, _Blocks, Count, Values, Changed, Acc) ->
+sanitize_is([#b_set{}=I|Is], Last, InBlocks, Blocks, Count, Values, Changed, Acc) ->
+ do_sanitize_is(I, Is, Last, InBlocks, Blocks, Count, Values, Changed, Acc);
+sanitize_is([], Last, _InBlocks, _Blocks, Count, Values, Changed, Acc) ->
case Changed of
true ->
{reverse(Acc), Last, Count, Values};
@@ -713,34 +812,59 @@ sanitize_is([], Last, _Blocks, Count, Values, Changed, Acc) ->
end.
do_sanitize_is(#b_set{op=Op,dst=Dst,args=Args0}=I0,
- Is, Last, Blocks, Count, Values, Changed0, Acc) ->
+ Is, Last, InBlocks, Blocks, Count, Values, Changed0, Acc) ->
Args = sanitize_args(Args0, Values),
- case sanitize_instr(Op, Args, I0) of
+ case sanitize_instr(Op, Args, I0, Blocks) of
{value,Value0} ->
Value = #b_literal{val=Value0},
- sanitize_is(Is, Last, Blocks, Count, Values#{Dst=>Value},
+ sanitize_is(Is, Last, InBlocks, Blocks, Count, Values#{Dst=>Value},
true, Acc);
{ok,I} ->
- sanitize_is(Is, Last, Blocks, Count, Values, true, [I|Acc]);
+ sanitize_is(Is, Last, InBlocks, Blocks, Count, Values, true, [I|Acc]);
ok ->
I = I0#b_set{args=Args},
Changed = Changed0 orelse Args =/= Args0,
- sanitize_is(Is, Last, Blocks, Count, Values, Changed, [I|Acc])
+ sanitize_is(Is, Last, InBlocks, Blocks, Count, Values, Changed, [I|Acc])
+ end.
+
+sanitize_last(#b_blk{last=Last0}=Blk, Values) ->
+ Last = case Last0 of
+ #b_br{bool=#b_literal{}} ->
+ Last0;
+ #b_br{bool=Bool} ->
+ beam_ssa:normalize(Last0#b_br{bool=sanitize_arg(Bool, Values)});
+ #b_ret{arg=Arg} ->
+ Last0#b_ret{arg=sanitize_arg(Arg, Values)};
+ #b_switch{arg=Arg} ->
+ beam_ssa:normalize(Last0#b_switch{arg=sanitize_arg(Arg, Values)})
+ end,
+ if
+ Last =/= Last0 ->
+ Blk#b_blk{last=Last};
+ true ->
+ Blk
end.
sanitize_args(Args, Values) ->
[sanitize_arg(Arg, Values) || Arg <- Args].
+sanitize_arg(#b_remote{mod=Mod0,name=Name0}=Remote, Values) ->
+ Mod = sanitize_arg(Mod0, Values),
+ Name = sanitize_arg(Name0, Values),
+ Remote#b_remote{mod=Mod,name=Name};
+sanitize_arg({#b_var{}=Var,L}, Values) ->
+ {sanitize_arg(Var, Values),L};
sanitize_arg(#b_var{}=Var, Values) ->
case Values of
- #{Var:=New} -> New;
+ #{Var := New} -> New;
#{} -> Var
end;
sanitize_arg(Arg, _Values) ->
Arg.
-
-sanitize_instr(phi, PhiArgs, _I) ->
+sanitize_instr(phi, PhiArgs0, I, Blocks) ->
+ PhiArgs = [{V,L} || {V,L} <- PhiArgs0,
+ is_map_key(L, Blocks)],
case phi_all_same_literal(PhiArgs) of
true ->
%% (Can only happen when some optimizations have been
@@ -756,8 +880,11 @@ sanitize_instr(phi, PhiArgs, _I) ->
[{#b_literal{val=Val},_}|_] = PhiArgs,
{value,Val};
false ->
- ok
+ {ok,I#b_set{args=PhiArgs}}
end;
+sanitize_instr(Op, Args, I, _Blocks) ->
+ sanitize_instr(Op, Args, I).
+
sanitize_instr({bif,Bif}, [#b_literal{val=Lit}], _I) ->
case erl_bifs:is_pure(erlang, Bif, 1) of
false ->
@@ -780,10 +907,11 @@ sanitize_instr({bif,Bif}, [#b_literal{val=Lit1},#b_literal{val=Lit2}], _I) ->
end;
sanitize_instr(bs_match, Args, I) ->
%% Matching of floats are never changed to a bs_skip even when the
- %% value is never used, because the match can always fail (for example,
- %% if it is a NaN).
- [#b_literal{val=float}|_] = Args, %Assertion.
- {ok,I#b_set{op=bs_get}};
+ %% value is never used, because the match can always fail (for
+ %% example, if it is a NaN). Sometimes (for contrived code) the
+ %% optimizing passes fail to do the conversion to bs_skip for
+ %% other data types as well.
+ {ok,I#b_set{op=bs_get,args=Args}};
sanitize_instr(get_hd, [#b_literal{val=[Hd|_]}], _I) ->
{value,Hd};
sanitize_instr(get_tl, [#b_literal{val=[_|Tl]}], _I) ->
@@ -807,27 +935,11 @@ sanitize_instr(is_tagged_tuple, [#b_literal{val=Tuple},
true ->
{value,false}
end;
+sanitize_instr(succeeded, [#b_literal{}], _I) ->
+ {value,true};
sanitize_instr(_, _, _) ->
ok.
-remove_unreachable([L|Ls], Blocks, Reachable, Acc) ->
- #b_blk{is=Is0} = Blk0 = map_get(L, Blocks),
- case split_phis(Is0) of
- {[_|_]=Phis,Rest} ->
- Is = [prune_phi(Phi, Reachable) || Phi <- Phis] ++ Rest,
- Blk = Blk0#b_blk{is=Is},
- remove_unreachable(Ls, Blocks, Reachable, [{L,Blk}|Acc]);
- {[],_} ->
- remove_unreachable(Ls, Blocks, Reachable, [{L,Blk0}|Acc])
- end;
-remove_unreachable([], _Blocks, _, Acc) ->
- maps:from_list(Acc).
-
-prune_phi(#b_set{args=Args0}=Phi, Reachable) ->
- Args = [A || {_,Pred}=A <- Args0,
- gb_sets:is_element(Pred, Reachable)],
- Phi#b_set{args=Args}.
-
phi_all_same_literal([{#b_literal{}=Arg, _From} | Phis]) ->
phi_all_same_literal_1(Phis, Arg);
phi_all_same_literal([_|_]) ->
@@ -966,128 +1078,87 @@ find_fc_errors([#b_function{bs=Blocks}|Fs], Acc0) ->
find_fc_errors([], Acc) ->
Acc.
-
+%%% expand_update_tuple(St0) -> St
%%%
-%%% Introduce the set_tuple_element instructions to make
-%%% multiple-field record updates faster.
+%%% Expands the update_tuple psuedo-instruction into its actual instructions.
%%%
-%%% The expansion of record field updates, when more than one field is
-%%% updated, but not a majority of the fields, will create a sequence of
-%%% calls to `erlang:setelement(Index, Value, Tuple)` where Tuple in the
-%%% first call is the original record tuple, and in the subsequent calls
-%%% Tuple is the result of the previous call. Furthermore, all Index
-%%% values are constant positive integers, and the first call to
-%%% `setelement` will have the greatest index. Thus all the following
-%%% calls do not actually need to test at run-time whether Tuple has type
-%%% tuple, nor that the index is within the tuple bounds.
-%%%
-%%% Since this optimization introduces destructive updates, it used to
-%%% be done as the very last Core Erlang pass before going to
-%%% lower-level code. However, it turns out that this kind of destructive
-%%% updates are awkward also in SSA code and can prevent or complicate
-%%% type analysis and aggressive optimizations.
-%%%
-%%% NOTE: Because there no write barriers in the system, this kind of
-%%% optimization can only be done when we are sure that garbage
-%%% collection will not be triggered between the creation of the tuple
-%%% and the destructive updates - otherwise we might insert pointers
-%%% from an older generation to a newer.
-%%%
-
-use_set_tuple_element(#st{ssa=Blocks0}=St) ->
- Uses = count_uses(Blocks0),
- RPO = reverse(beam_ssa:rpo(Blocks0)),
- Blocks = use_ste_1(RPO, Uses, Blocks0),
- St#st{ssa=Blocks}.
-
-use_ste_1([L|Ls], Uses, Blocks) ->
- #b_blk{is=Is0} = Blk0 = map_get(L, Blocks),
- case use_ste_is(Is0, Uses) of
- Is0 ->
- use_ste_1(Ls, Uses, Blocks);
- Is ->
- Blk = Blk0#b_blk{is=Is},
- use_ste_1(Ls, Uses, Blocks#{L:=Blk})
- end;
-use_ste_1([], _, Blocks) -> Blocks.
-
-%%% Optimize within a single block.
+expand_update_tuple(#st{ssa=Blocks0,cnt=Count0}=St) ->
+ Linear0 = beam_ssa:linearize(Blocks0),
+ {Linear, Count} = expand_update_tuple_1(Linear0, Count0, []),
+ Blocks = maps:from_list(Linear),
+ St#st{ssa=Blocks,cnt=Count}.
-use_ste_is([#b_set{}=I|Is0], Uses) ->
- Is = use_ste_is(Is0, Uses),
- case extract_ste(I) of
- none ->
- [I|Is];
- Extracted ->
- use_ste_call(Extracted, I, Is, Uses)
+expand_update_tuple_1([{L, #b_blk{is=Is0}=B0} | Bs], Count0, Acc0) ->
+ case expand_update_tuple_is(Is0, Count0, []) of
+ {Is, Count} ->
+ expand_update_tuple_1(Bs, Count, [{L, B0#b_blk{is=Is}} | Acc0]);
+ {Is, NextIs, Count1} ->
+ %% There are `set_tuple_element` instructions that we must put into
+ %% a new block to avoid separating the `setelement` instruction from
+ %% its `succeeded` instruction.
+ #b_blk{last=Br} = B0,
+ #b_br{succ=Succ} = Br,
+ NextL = Count1,
+ Count = Count1 + 1,
+ NextBr = #b_br{bool=#b_literal{val=true},succ=Succ,fail=Succ},
+ NextB = #b_blk{is=NextIs,last=NextBr},
+ B = B0#b_blk{is=Is,last=Br#b_br{succ=NextL}},
+ Acc = [{NextL, NextB}, {L, B} | Acc0],
+ expand_update_tuple_1(Bs, Count, Acc)
+ end;
+expand_update_tuple_1([], Count, Acc) ->
+ {Acc, Count}.
+
+expand_update_tuple_is([#b_set{op=update_tuple, args=[Src | Args]}=I0 | Is],
+ Count0, Acc) ->
+ {SetElement, Sets, Count} = expand_update_tuple_list(Args, I0, Src, Count0),
+ case {Sets, Is} of
+ {[_ | _], [#b_set{op=succeeded}]} ->
+ {reverse(Acc, [SetElement | Is]), reverse(Sets), Count};
+ {_, _} ->
+ expand_update_tuple_is(Is, Count, Sets ++ [SetElement | Acc])
end;
-use_ste_is([], _Uses) -> [].
+expand_update_tuple_is([I | Is], Count, Acc) ->
+ expand_update_tuple_is(Is, Count, [I | Acc]);
+expand_update_tuple_is([], Count, Acc) ->
+ {reverse(Acc), Count}.
-use_ste_call({Dst0,Pos0,_Var0,_Val0}, Call1, Is0, Uses) ->
- case get_ste_call(Is0, []) of
- {Prefix,{Dst1,Pos1,Dst0,Val1},Call2,Is}
- when Pos1 > 0, Pos0 > Pos1 ->
- case is_single_use(Dst0, Uses) of
- true ->
- Call = Call1#b_set{dst=Dst1},
- Args = [Val1,Dst1,#b_literal{val=Pos1-1}],
- Dsetel = Call2#b_set{op=set_tuple_element,
- dst=Dst0,
- args=Args},
- [Call|Prefix] ++ [Dsetel|Is];
- false ->
- [Call1|Is0]
- end;
- _ ->
- [Call1|Is0]
- end.
-
-get_ste_call([#b_set{op=get_tuple_element}=I|Is], Acc) ->
- get_ste_call(Is, [I|Acc]);
-get_ste_call([#b_set{op=call}=I|Is], Acc) ->
- case extract_ste(I) of
- none ->
- none;
- Extracted ->
- {reverse(Acc),Extracted,I,Is}
- end;
-get_ste_call(_, _) -> none.
-
-extract_ste(#b_set{op=call,dst=Dst,
- args=[#b_remote{mod=#b_literal{val=M},
- name=#b_literal{val=F}}|Args]}) ->
- case {M,F,Args} of
- {erlang,setelement,[#b_literal{val=Pos},Tuple,Val]} ->
- {Dst,Pos,Tuple,Val};
- {_,_,_} ->
- none
- end;
-extract_ste(#b_set{}) -> none.
-
-%% Count how many times each variable is used.
-
-count_uses(Blocks) ->
- count_uses_blk(maps:values(Blocks), #{}).
-
-count_uses_blk([#b_blk{is=Is,last=Last}|Bs], CountMap0) ->
- F = fun(I, CountMap) ->
- foldl(fun(Var, Acc) ->
- case Acc of
- #{Var:=2} -> Acc;
- #{Var:=C} -> Acc#{Var:=C+1};
- #{} -> Acc#{Var=>1}
- end
- end, CountMap, beam_ssa:used(I))
- end,
- CountMap = F(Last, foldl(F, CountMap0, Is)),
- count_uses_blk(Bs, CountMap);
-count_uses_blk([], CountMap) -> CountMap.
-
-is_single_use(V, Uses) ->
- case Uses of
- #{V:=1} -> true;
- #{} -> false
- end.
+%% Expands an update_tuple list into setelement/3 + set_tuple_element.
+%%
+%% Note that it returns the instructions in reverse order.
+expand_update_tuple_list(Args, I0, Src, Count0) ->
+ [Index, Value | Rest] = sort_update_tuple(Args, []),
+
+ %% set_tuple_element is destructive, so we have to start off with a
+ %% setelement/3 call to give them something to work on.
+ I = I0#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=setelement},
+ arity=3},
+ Index, Src, Value]},
+ {Sets, Count} = expand_update_tuple_list_1(Rest, I#b_set.dst, Count0, []),
+ {I, Sets, Count}.
+
+expand_update_tuple_list_1([], _Src, Count, Acc) ->
+ {Acc, Count};
+expand_update_tuple_list_1([Index0, Value | Updates], Src, Count0, Acc) ->
+ %% Change to the 0-based indexing used by `set_tuple_element`.
+ Index = #b_literal{val=(Index0#b_literal.val - 1)},
+ {Dst, Count} = new_var('@ssa_dummy', Count0),
+ SetOp = #b_set{op=set_tuple_element,
+ dst=Dst,
+ args=[Value, Src, Index]},
+ expand_update_tuple_list_1(Updates, Src, Count, [SetOp | Acc]).
+
+%% Sorts updates so that the highest index comes first, letting us use
+%% set_tuple_element for all subsequent operations as we know their indexes
+%% will be valid.
+sort_update_tuple([_Index, _Value]=Args, []) ->
+ Args;
+sort_update_tuple([#b_literal{}=Index, Value | Updates], Acc) ->
+ sort_update_tuple(Updates, [{Index, Value} | Acc]);
+sort_update_tuple([], Acc) ->
+ append([[Index, Value] || {Index, Value} <- sort(fun erlang:'>='/2, Acc)]).
%%%
%%% Find out where frames should be placed.
@@ -2319,7 +2390,7 @@ reserve_yregs_1(L, #st{ssa=Blocks0,cnt=Count0,res=Res0}=St) ->
reserve_try_tags(L, Blocks) ->
Seen = gb_sets:empty(),
{Res0,_} = reserve_try_tags_1([L], Blocks, Seen, #{}),
- Res1 = [maps:to_list(M) || {_,M} <- maps:to_list(Res0)],
+ Res1 = [maps:to_list(M) || M <- maps:values(Res0)],
Res = [{V,{y,Y}} || {V,Y} <- append(Res1)],
ordsets:from_list(Res).
@@ -2528,6 +2599,8 @@ reserve_zreg([#b_set{op=Op,dst=Dst} | Is], Last, ShortLived, A) ->
end;
reserve_zreg([], _, _, A) -> A.
+use_zreg(bs_checked_skip) -> yes;
+use_zreg(bs_ensure) -> yes;
use_zreg(bs_match_string) -> yes;
use_zreg(bs_set_position) -> yes;
use_zreg(kill_try_tag) -> yes;
@@ -2797,9 +2870,9 @@ reserve_call_args(Args) ->
reserve_call_args(Args, 0, #{}).
reserve_call_args([#b_var{}=Var|As], X, Xs) ->
- reserve_call_args(As, X+1, Xs#{Var=>{x,X}});
+ reserve_call_args(As, X+1, Xs#{Var => {x,X}});
reserve_call_args([#b_literal{}|As], X, Xs) ->
- reserve_call_args(As, X+1, Xs);
+ reserve_call_args(As, X+1, Xs#{{x,X} => hole});
reserve_call_args([], _, Xs) -> Xs.
reserve_xreg(V, Xs, Res) ->
@@ -2825,21 +2898,29 @@ reserve_xreg(V, Xs, Res) ->
%% invoking the garbage collector.
res_xregs_prune(Xs, Used, Res) when map_size(Xs) =/= 0 ->
- %% The number of safe registers is the number of the X registers
- %% used after this point. The actual number of safe registers may
- %% be higher than this number, but this is a conservative safe
- %% estimate.
- NumSafe = foldl(fun(V, N) ->
- case Res of
- #{V:={x,_}} -> N + 1;
- #{V:=_} -> N;
- #{} -> N + 1
+ %% Calculate a conservative estimate for the number of safe
+ %% registers based on the used X register after this point. The
+ %% actual number of safe registers may be higher than this number.
+ NumSafe0 = foldl(fun(V, N) ->
+ %% Count the number of used variables
+ %% allocated to X registers.
+ case Res of
+ #{V := {x,_}} -> N + 1;
+ #{V := _} -> N;
+ #{} -> N + 1
+ end
+ end, 0, Used),
+ NumSafe = foldl(fun(X, N) ->
+ %% Decrement the count if there are holes.
+ case Xs of
+ #{{x,X} := hole} -> N - 1;
+ #{} -> N
end
- end, 0, Used),
+ end, NumSafe0, seq(0, NumSafe0-1)),
%% Remove unsafe registers from the list of potential
%% preferred registers.
- maps:filter(fun(_, {x,X}) -> X < NumSafe end, Xs);
+ #{Var => Reg || Var := {x,X}=Reg <- Xs, X < NumSafe};
res_xregs_prune(Xs, _Used, _Res) -> Xs.
%%%
@@ -2912,8 +2993,8 @@ init_free(Res) ->
#{x:=Xs0} = Free1 = maps:from_list(Free0),
Xs = init_xregs(Xs0),
Free = Free1#{x:=Xs},
- Next = maps:fold(fun(K, V, A) -> [{{next,K},length(V)}|A] end, [], Free),
- maps:merge(Free, maps:from_list(Next)).
+ Next = #{{next,K} => length(V) || K := V <- Free},
+ maps:merge(Free, Next).
init_free_1([{_,{prefer,{x,_}=Reg}}|Res]) ->
[{x,Reg}|init_free_1(Res)];
@@ -3143,9 +3224,6 @@ rel2fam(S0) ->
S = sofs:rel2fam(S1),
sofs:to_external(S).
-split_phis(Is) ->
- splitwith(fun(#b_set{op=Op}) -> Op =:= phi end, Is).
-
is_yreg({y,_}) -> true;
is_yreg({x,_}) -> false;
is_yreg({z,_}) -> false;
diff --git a/lib/compiler/src/beam_ssa_private_append.erl b/lib/compiler/src/beam_ssa_private_append.erl
new file mode 100644
index 0000000000..c295492762
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_private_append.erl
@@ -0,0 +1,652 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+%% When a binary is grown by appending data to it using
+%% `bs_create_bin`, a considerable performance improvement can be
+%% achieved if the append can be done using the destructive
+%% `private_append` instead of `append`. Using `private_append` flavor
+%% of `bs_create_bin` is only possible when the binary being extended
+%% has been created by `bs_writable_binary` or by another
+%% `bs_create_bin` using `private_append`. As `private_append` is
+%% destructive, an additional requirement is that there is only one
+%% live reference to the binary being being extended.
+
+%% This optimization implements a new SSA optimization pass which
+%% finds suitable code sequences which iteratively grow a binaries
+%% starting with `<<>>` and rewrites them to start with an initial
+%% value created by `bs_writable_binary` and use `private_append` for
+%% `bs_create_bin`.
+
+-module(beam_ssa_private_append).
+
+-export([opt/2]).
+
+-import(lists, [foldl/3, foldr/3, keysort/2, map/2, reverse/1]).
+
+-include("beam_ssa_opt.hrl").
+-include("beam_types.hrl").
+
+%% -define(DEBUG, true).
+
+-ifdef(DEBUG).
+-define(DP(FMT, ARGS), io:format(FMT, ARGS)).
+-define(DP(FMT), io:format(FMT)).
+-else.
+-define(DP(FMT, ARGS), skip).
+-define(DP(FMT), skip).
+-endif.
+
+-spec opt(st_map(), func_info_db()) -> {st_map(), func_info_db()}.
+opt(StMap, FuncDb) ->
+ %% Ignore functions which are not in the function db (never
+ %% called) or are stubs for nifs.
+ Funs = [ F || F <- maps:keys(StMap),
+ is_map_key(F, FuncDb), not is_nif(F, StMap)],
+ private_append(Funs, StMap, FuncDb).
+
+private_append(Funs, StMap0, FuncDb) ->
+ Appends = maps:fold(fun(Fun, As, Acc) ->
+ [{Fun,A} || A <- As] ++ Acc
+ end, [], find_appends(Funs, StMap0, #{})),
+ %% We now have to find where we create the binaries in order to
+ %% patch them.
+ Defs = find_defs(Appends, StMap0, FuncDb),
+ StMap = patch_appends(Defs, Appends, StMap0),
+ {StMap, FuncDb}.
+
+find_appends([F|Funs], StMap, Found0) ->
+ #opt_st{ssa=Linear} = map_get(F, StMap),
+ Found = find_appends_blk(Linear, F, Found0),
+ find_appends(Funs, StMap, Found);
+find_appends([], _, Found) ->
+ Found.
+
+find_appends_blk([{_Lbl,#b_blk{is=Is}}|Linear], Fun, Found0) ->
+ Found = find_appends_is(Is, Fun, Found0),
+ find_appends_blk(Linear, Fun, Found);
+find_appends_blk([], _, Found) ->
+ Found.
+
+find_appends_is([#b_set{dst=Dst, op=bs_create_bin,
+ args=[#b_literal{val=append},
+ _,
+ Lit=#b_literal{val= <<>>}|_]}|Is],
+ Fun, Found0) ->
+ %% Special case for when the first fragment is a literal <<>> as
+ %% it won't be annotated as unique nor will it die with the
+ %% instruction.
+ AlreadyFound = maps:get(Fun, Found0, []),
+ Found = Found0#{Fun => [{append,Dst,Lit}|AlreadyFound]},
+ find_appends_is(Is, Fun, Found);
+find_appends_is([#b_set{dst=Dst, op=bs_create_bin,
+ args=[#b_literal{val=append},SegmentInfo,Var|_],
+ anno=#{first_fragment_dies:=Dies}=Anno}|Is],
+ Fun, Found0) ->
+ case Dies andalso is_unique(Var, Anno)
+ andalso is_appendable(Anno, SegmentInfo) of
+ true ->
+ AlreadyFound = maps:get(Fun, Found0, []),
+ Found = Found0#{Fun => [{append,Dst,Var}|AlreadyFound]},
+ find_appends_is(Is, Fun, Found);
+ false ->
+ find_appends_is(Is, Fun, Found0)
+ end;
+find_appends_is([_|Is], Fun, Found) ->
+ find_appends_is(Is, Fun, Found);
+find_appends_is([], _, Found) ->
+ Found.
+
+is_unique(Var, Anno) ->
+ ordsets:is_element(Var, maps:get(unique, Anno, [])).
+
+is_appendable(Anno, #b_literal{val=[SegmentUnit|_]})
+ when is_integer(SegmentUnit) ->
+ case Anno of
+ #{arg_types:=#{2:=#t_bitstring{appendable=true,size_unit=SizeUnit}}} ->
+ SizeUnit rem SegmentUnit == 0;
+ _ ->
+ false
+ end.
+
+-record(def_st,
+ {
+ funcdb,
+ stmap,
+ defsdb = #{},
+ literals = #{},
+ valuesdb = #{}
+ }).
+
+find_defs(As, StMap, FuncDb) ->
+ find_defs_1(As, #def_st{funcdb=FuncDb,stmap=StMap}).
+
+find_defs_1([{Fun,{append,Dst,_Arg}}|Work], DefSt0=#def_st{stmap=StMap}) ->
+ #{Fun:=#opt_st{ssa=SSA,args=Args}} = StMap,
+ {DefsInFun,DefSt} = defs_in_fun(Fun, Args, SSA, DefSt0),
+ ValuesInFun = values_in_fun(Fun, DefSt),
+ ?DP("*** append in: ~p ***~n", [Fun]),
+ track_value_in_fun([{Dst,self}], Fun, Work,
+ DefsInFun, ValuesInFun, DefSt);
+find_defs_1([{Fun,{track_call_argument,Callee,Element,Idx}}|Work],
+ DefSt0=#def_st{stmap=StMap}) ->
+ #{Fun:=#opt_st{ssa=SSA,args=Args}} = StMap,
+ ?DP("*** Tracking ~p of the ~p:th argument in call to ~p"
+ " in the function ~p ***~n", [Element, Idx, Callee, Fun]),
+
+ {DefsInFun,DefSt1} = defs_in_fun(Fun, Args, SSA, DefSt0),
+ ValuesInFun = values_in_fun(Fun, DefSt1),
+ {Vars,DefSt} =
+ get_call_arguments(Callee, Element, Idx, DefsInFun, Fun, DefSt1),
+ ?DP(" Vars to track: ~p~n", [Vars]),
+ track_value_in_fun(Vars, Fun, Work, DefsInFun, ValuesInFun, DefSt);
+find_defs_1([{Fun,{track_result,Element}}|Work],
+ DefSt0=#def_st{stmap=StMap}) ->
+ #{Fun:=#opt_st{ssa=SSA,args=Args}} = StMap,
+ {DefsInFun,DefSt1} = defs_in_fun(Fun, Args, SSA, DefSt0),
+ ValuesInFun = values_in_fun(Fun, DefSt0),
+ ?DP("*** Tracking ~p of the result of ~p ***~n",
+ [Fun, Element]),
+ {Results,DefSt} = get_results(SSA, Element, Fun, DefSt1),
+ ?DP("values to track inside the function: ~p~n", [Results]),
+ track_value_in_fun(Results, Fun, Work, DefsInFun, ValuesInFun, DefSt);
+
+find_defs_1([], DefSt) ->
+ DefSt#def_st.literals.
+
+%% Find all variables which are returned and return them in a worklist
+get_results(SSA, Element, Fun, DefSt) ->
+ get_results(SSA, [], Element, Fun, DefSt).
+
+get_results([{_,#b_blk{last=#b_ret{arg=#b_var{}=V}}}|Rest],
+ Acc, Element, Fun, DefSt) ->
+ get_results(Rest, [{V,Element}|Acc], Element, Fun, DefSt);
+get_results([{Lbl,#b_blk{last=#b_ret{arg=#b_literal{val=Lit}}}}|Rest],
+ Acc, Element, Fun, DefSt0) ->
+ %% As tracking doesn't make any attempt to use type information to
+ %% exclude execution paths not relevant when tracking an
+ %% appendable binary, it can happen that we encounter literals
+ %% which do not match the type of the element. We can safely stop
+ %% the tracking in that case.
+ Continue = case Element of
+ {tuple_element,_,_} ->
+ is_tuple(Lit);
+ self ->
+ is_bitstring(Lit);
+ {hd,_} ->
+ is_list(Lit) andalso (Lit =/= [])
+ end,
+ DefSt = if Continue ->
+ add_literal(Fun, {ret,Lbl,Element}, DefSt0);
+ true ->
+ DefSt0
+ end,
+ get_results(Rest, Acc, Element, Fun, DefSt);
+get_results([_|Rest], Acc, Element, Fun, DefSt) ->
+ get_results(Rest, Acc, Element, Fun, DefSt);
+get_results([], Acc, _, _Fun, DefSt) ->
+ {Acc, DefSt}.
+
+track_value_in_fun([{#b_var{}=V,Element}|Rest], Fun, Work,
+ Defs, ValuesInFun, DefSt0)
+ when is_map_key({V,Element}, ValuesInFun) ->
+ %% We have already explored this value.
+ ?DP("We have already explored ~p of ~p in ~p~n", [Element, V, Fun]),
+ track_value_in_fun(Rest, Fun, Work, Defs, ValuesInFun, DefSt0);
+track_value_in_fun([{#b_var{}=V,Element}|Rest], Fun, Work0, Defs,
+ ValuesInFun0, DefSt0=#def_st{}) ->
+ ?DP("Tracking ~p of ~p in fun ~p~n", [Element, V, Fun]),
+ ValuesInFun = ValuesInFun0#{{V,Element}=>visited},
+ case Defs of
+ #{V:=#b_set{dst=V,op=Op,args=Args}} ->
+ case {Op,Args,Element} of
+ {bs_create_bin,[#b_literal{val=append},_,Arg|_],self} ->
+ track_value_in_fun([{Arg,self}|Rest], Fun, Work0,
+ Defs, ValuesInFun, DefSt0);
+ {bs_create_bin,[#b_literal{val=private_append},_,_|_],self} ->
+ %% If the code has already been rewritten to use
+ %% private_append, tracking the accumulator to
+ %% ensure that it is is writable has already
+ %% been seen to, so no need to track it.
+ track_value_in_fun(Rest, Fun, Work0,
+ Defs, ValuesInFun, DefSt0);
+ {bs_init_writable,_,self} ->
+ %% bs_init_writable creates a writable binary, so
+ %% we are done.
+ track_value_in_fun(Rest, Fun, Work0,
+ Defs, ValuesInFun, DefSt0);
+ {call,[#b_local{}=Callee|_Args],_} ->
+ track_value_into_call(Callee, Element, Fun, Rest, Work0,
+ Defs, ValuesInFun, DefSt0);
+ {call,[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error},
+ arity=1}|_Args],_} ->
+ %% As erlang:error/1 never returns, we shouldn't
+ %% try to track this value.
+ track_value_in_fun(Rest, Fun, Work0,
+ Defs, ValuesInFun, DefSt0);
+ {get_hd,[List],_} ->
+ %% We only handle the case when the tracked value
+ %% is in the head field of a cons. This is due to
+ %% the type analyser always assuming that a cons
+ %% is part of a list and therefore we will never
+ %% be able to safely rewrite an accumulator in the
+ %% tail field of the cons, thus we will never
+ %% have to track it.
+ track_value_in_fun(
+ [{List,{hd,Element}}|Rest], Fun, Work0,
+ Defs, ValuesInFun, DefSt0);
+ {get_tuple_element,[#b_var{}=Tuple,#b_literal{val=Idx}],_} ->
+ track_value_in_fun(
+ [{Tuple,{tuple_element,Idx,Element}}|Rest], Fun, Work0,
+ Defs, ValuesInFun, DefSt0);
+ {phi,_,_} ->
+ {ToExplore,DefSt} = handle_phi(Fun, V, Args,
+ Element, DefSt0),
+ track_value_in_fun(ToExplore ++ Rest, Fun, Work0,
+ Defs, ValuesInFun, DefSt);
+ {put_tuple,_,_} ->
+ track_put_tuple(Args, Element, Rest, Fun, V, Work0,
+ Defs, ValuesInFun, DefSt0);
+ {put_list,_,_} ->
+ track_put_list(Args, Element, Rest, Fun, V, Work0,
+ Defs, ValuesInFun, DefSt0);
+ {_,_,_} ->
+ %% Above we have handled all operations through
+ %% which we are able to track the value to its
+ %% construction. All other operations are from
+ %% execution paths not reachable when the actual
+ %% type (at runtime) is a relevant bitstring.
+ %% Thus we can safely abort the tracking here.
+ track_value_in_fun(Rest, Fun, Work0,
+ Defs, ValuesInFun, DefSt0)
+ end;
+ #{V:={arg,Idx}} ->
+ track_value_into_caller(Element, Idx, Rest, Fun, Work0, Defs,
+ ValuesInFun, DefSt0)
+ end;
+track_value_in_fun([{#b_literal{},_}|Rest], Fun, Work,
+ Defs, ValuesInFun, DefSt) ->
+ track_value_in_fun(Rest, Fun, Work, Defs, ValuesInFun, DefSt);
+track_value_in_fun([], Fun, Work, _Defs, ValuesInFun,
+ DefSt0=#def_st{valuesdb=ValuesDb0}) ->
+ %% We are done with this function. Store the result in the
+ %% valuesdb and continue with the work list.
+ DefSt = DefSt0#def_st{valuesdb=ValuesDb0#{Fun=>ValuesInFun}},
+ find_defs_1(Work, DefSt).
+
+track_value_into_call(Callee, Element, CallerFun, CallerWork, GlobalWork0,
+ CallerDefs, CallerValuesInFun, DefSt0) ->
+ GlobalWork = [{Callee, {track_result, Element}}|GlobalWork0],
+ track_value_in_fun(CallerWork, CallerFun, GlobalWork,
+ CallerDefs, CallerValuesInFun, DefSt0).
+
+track_value_into_caller(Element, ArgIdx,
+ CalledFunWorklist, CalledFun,
+ GlobalWorklist0,
+ CalledFunDefs, CalledFunValues,
+ DefSt0=#def_st{funcdb=FuncDb,stmap=StMap}) ->
+ #func_info{in=Callers} = map_get(CalledFun, FuncDb),
+ ?DP("Track into callers of ~p, tracking arg-idx:~p, ~p~n callers:~p~n",
+ [CalledFun, ArgIdx, Element, Callers]),
+ %% The caller information in func_info does not remove a caller
+ %% when it is inlined into another function (although the new
+ %% caller is added), so we have to filter out functions which lack
+ %% entries in the st_map (as they are dead, they have been removed
+ %% from the stmap).
+ Work = [ {Caller,{track_call_argument,CalledFun,Element,ArgIdx}}
+ || Caller <- Callers, is_map_key(Caller, StMap)],
+ GlobalWorklist = Work ++ GlobalWorklist0,
+ track_value_in_fun(CalledFunWorklist, CalledFun, GlobalWorklist,
+ CalledFunDefs, CalledFunValues, DefSt0).
+
+track_put_tuple(FieldVars, {tuple_element,Idx,Element},
+ Work, Fun, Dst, GlobalWork,
+ Defs, ValuesInFun, DefSt0) ->
+ %% The value we are tracking was constructed by a put tuple and we
+ %% are interested in continuing the tracking of the field
+ case lists:nth(Idx + 1, FieldVars) of
+ ToTrack = #b_var{} ->
+ track_value_in_fun([{ToTrack,Element}|Work], Fun, GlobalWork,
+ Defs, ValuesInFun, DefSt0);
+ #b_literal{val=Lit} ->
+ DefSt = add_literal(Fun, {opargs,Dst,Idx,Lit,Element}, DefSt0),
+ track_value_in_fun(Work, Fun, GlobalWork,
+ Defs, ValuesInFun, DefSt)
+ end.
+
+track_put_list([Hd,_Tl], {hd,Element},
+ Work, Fun, Dst, GlobalWork,
+ Defs, ValuesInFun, DefSt0) ->
+ %% The value we are tracking was constructed by a put list and we
+ %% are interested in continuing the tracking of the field. We only
+ %% handle the case when the tracked value is in the head field of
+ %% a cons. This is due to the type analyser always assuming that a
+ %% cons is part of a list and therefore we will never be able to
+ %% safely rewrite an accumulator in the tail field of the cons,
+ %% thus we will never have to track it.
+ case Hd of
+ #b_var{} ->
+ track_value_in_fun([{Hd,Element}|Work], Fun, GlobalWork,
+ Defs, ValuesInFun, DefSt0);
+ #b_literal{val=Lit} ->
+ DefSt = add_literal(Fun, {opargs,Dst,0,Lit,Element}, DefSt0),
+ track_value_in_fun(Work, Fun, GlobalWork, Defs, ValuesInFun, DefSt)
+ end.
+
+%% Find all calls to Callee and produce a work-list containing all
+%% values which are used as the Idx:th argument.
+get_call_arguments(Callee, Element, Idx, Defs, Fun, DefSt0) ->
+ %% We traverse all defs inside the caller to find the calls.
+ maps:fold(fun(_, #b_set{dst=Dst,op=call,args=[Target|Args]}, {Acc,DefSt})
+ when Callee =:= Target ->
+ {Values,DefSt1} =
+ gca(Args, Element, Idx, Fun, Dst, DefSt),
+ {Values ++ Acc, DefSt1};
+ (_, _, Acc) ->
+ Acc
+ end, {[], DefSt0}, Defs).
+
+gca(Args, Element, Idx, Fun, Dst, DefSt) ->
+ gca(Args, 0, Element, Idx, Fun, Dst, DefSt).
+
+gca([#b_var{}=V|_], I, Element, I, _Fun, _Dst, DefSt) ->
+ %% This is the argument we are tracking.
+ {[{V,Element}], DefSt};
+gca([#b_literal{val=Lit}|_], I, self, I, _Fun, _Dst, DefSt)
+ when not is_bitstring(Lit)->
+ %% As value tracking is done without type information, we can
+ %% follow def chains which don't terminate in a bitstring. This is
+ %% harmless, but we should ignore them and not, later on, try to
+ %% patch them to a bs_writable_binary.
+ {[], DefSt};
+gca([#b_literal{val=Lit}|_], I, Element, I, Fun, Dst, DefSt) ->
+ {[], add_literal(Fun, {opargs,Dst,I+1,Lit,Element}, DefSt)};
+gca([_|Args], I, Element, Idx, Fun, Dst, DefSt) ->
+ gca(Args, I + 1, Element, Idx, Fun, Dst, DefSt).
+
+handle_phi(Fun, Dst, Args, Element, DefSt0) ->
+ foldl(fun({#b_literal{val=Lit},Lbl}, {Acc,DefStAcc0}) ->
+ DefStAcc =
+ add_literal(Fun, {phi,Dst,Lbl,Lit,Element}, DefStAcc0),
+ {Acc, DefStAcc};
+ ({V=#b_var{},_Lbl}, {Acc,DefStAcc}) ->
+ {[{V,Element}|Acc],DefStAcc}
+ end, {[],DefSt0}, Args).
+
+%% Cache calculation of the defs for a function so we only do it once.
+defs_in_fun(Fun, Args, SSA, DefSt=#def_st{defsdb=DefsDb}) ->
+ case DefsDb of
+ #{Fun:=Defs} ->
+ {Defs, DefSt};
+ #{} ->
+ BlockMap = maps:from_list(SSA),
+ Labels = maps:keys(BlockMap),
+ Defs0 = beam_ssa:definitions(Labels, BlockMap),
+ {Defs,_} = foldl(fun(Arg, {Acc,Idx}) ->
+ {Acc#{Arg => {arg,Idx}}, Idx + 1}
+ end, {Defs0,0}, Args),
+ {Defs, DefSt#def_st{defsdb=DefsDb#{Fun=>Defs}}}
+ end.
+
+
+%% Look up what we know about the values in Fun.
+values_in_fun(Fun, #def_st{valuesdb=ValuesDb}) ->
+ maps:get(Fun, ValuesDb, #{}).
+
+add_literal(Fun, LitInfo, DefSt=#def_st{literals=Ls}) ->
+ Old = maps:get(Fun, Ls, []),
+ DefSt#def_st{literals=Ls#{Fun => [LitInfo|Old]}}.
+
+patch_appends(Bins, Appends, StMap0) ->
+ ?DP("Appends:~n~p~n", [Appends]),
+ ?DP("Bins:~n~p~n", [Bins]),
+
+ %% Group by function
+ Patches = foldl(fun({Fun,Append}, Acc) ->
+ Acc#{Fun => [Append|maps:get(Fun, Acc, [])] }
+ end, Bins, Appends),
+ ?DP("Patches:~n~p~n", [Patches]),
+ maps:fold(fun(Fun, Ps, StMapAcc) ->
+ OptSt=#opt_st{ssa=SSA0,cnt=Cnt0} =
+ map_get(Fun, StMapAcc),
+ {SSA,Cnt} = patch_appends_f(SSA0, Cnt0, Ps),
+ StMapAcc#{Fun => OptSt#opt_st{ssa=SSA,cnt=Cnt}}
+ end, StMap0, Patches).
+
+patch_appends_f(SSA0, Cnt0, Patches) ->
+ ?DP("Will patch ~p~n", [Patches]),
+ ?DP("SSA: ~p~n", [SSA0]),
+ %% Group by PD
+ PD = foldl(fun(P, Acc) ->
+ case P of
+ {opargs,Dst,_,_,_} -> ok;
+ {append,Dst,_} -> ok;
+ {phi,Dst,_,_,_} -> ok;
+ {ret,Dst,_} -> ok
+ end,
+ Set = ordsets:add_element(P, maps:get(Dst, Acc, [])),
+ Acc#{Dst => Set}
+ end, #{}, Patches),
+ ?DP("PD: ~p~n", [PD]),
+ patch_appends_f(SSA0, Cnt0, PD, [], []).
+
+patch_appends_f([{Lbl,Blk=#b_blk{is=Is0,last=Last0}}|Rest],
+ Cnt0, PD0, Acc0, BlockAdditions0) ->
+ {Last,Extra,Cnt2,PD} =
+ case PD0 of
+ #{ Lbl := Patches } ->
+ {Last1,Extra0,Cnt1} = patch_appends_ret(Last0, Patches, Cnt0),
+ {Last1, reverse(Extra0), Cnt1, maps:remove(Lbl, PD0)};
+ #{} ->
+ {Last0, [], Cnt0, PD0}
+ end,
+ {Is, Cnt, BlockAdditions} = patch_appends_is(Is0, PD, Cnt2, [], []),
+ Acc = [{Lbl,Blk#b_blk{is=Is ++ Extra, last=Last}}|Acc0],
+ patch_appends_f(Rest, Cnt, PD, Acc, BlockAdditions ++ BlockAdditions0 );
+patch_appends_f([], Cnt, _PD, Acc, BlockAdditions) ->
+ ?DP("BlockAdditions: ~p~n", [BlockAdditions]),
+ Linear = insert_block_additions(Acc, maps:from_list(BlockAdditions), []),
+ ?DP("SSA-result:~n~p~n", [Linear]),
+ {Linear, Cnt}.
+
+patch_appends_is([I0=#b_set{dst=Dst}|Rest], PD0, Cnt0, Acc, BlockAdditions0)
+ when is_map_key(Dst, PD0) ->
+ #{ Dst := Patches } = PD0,
+ PD = maps:remove(Dst, PD0),
+ ExtractOpargs = fun({opargs,D,Idx,Lit,Element}) when Dst =:= D ->
+ {Idx, Lit, Element}
+ end,
+ case Patches of
+ [{opargs,Dst,_,_,_}|_] ->
+ Ps = keysort(1, map(ExtractOpargs, Patches)),
+ {Is, Cnt} = patch_opargs(I0, Ps, Cnt0),
+ patch_appends_is(Rest, PD, Cnt, Is++Acc, BlockAdditions0);
+ [{append,Dst,#b_literal{val= <<>>}=Lit}] ->
+ %% Special case for when the first fragment is a literal
+ %% <<>> and it has to be replaced with a bs_init_writable.
+ #b_set{op=bs_create_bin,dst=Dst,args=Args0}=I0,
+ [#b_literal{val=append},SegInfo,Lit|OtherArgs] = Args0,
+ {V,Cnt} = new_var(Cnt0),
+ Init = #b_set{op=bs_init_writable,dst=V,args=[#b_literal{val=256}]},
+ I = I0#b_set{args=[#b_literal{val=private_append},
+ SegInfo,V|OtherArgs]},
+ patch_appends_is(Rest, PD, Cnt, [I,Init|Acc], BlockAdditions0);
+ [{append,Dst,_}] ->
+ #b_set{op=bs_create_bin,dst=Dst,args=Args0}=I0,
+ [#b_literal{val=append}|OtherArgs] = Args0,
+ I = I0#b_set{args=[#b_literal{val=private_append}|OtherArgs]},
+ patch_appends_is(Rest, PD, Cnt0, [I|Acc], BlockAdditions0);
+ [{phi,Dst,_,_,_}|_] ->
+ {I, Extra, Cnt} = patch_phi(I0, Patches, Cnt0),
+ patch_appends_is(Rest, PD, Cnt, [I|Acc], Extra ++ BlockAdditions0)
+ end;
+patch_appends_is([I|Rest], PD, Cnt, Acc, BlockAdditions) ->
+ patch_appends_is(Rest, PD, Cnt, [I|Acc], BlockAdditions);
+patch_appends_is([], _, Cnt, Acc, BlockAdditions) ->
+ {reverse(Acc), Cnt, BlockAdditions}.
+
+%% The only time when we patch a return is when it returns a
+%% literal.
+patch_appends_ret(Last=#b_ret{arg=#b_literal{val=Lit}}, Patches, Cnt0)
+ when is_list(Lit); is_tuple(Lit) ->
+ Ps = keysort(1, [E || {ret,_,E} <- Patches]),
+ ?DP("patch_appends_ret tuple or list :~n lit: ~p~n patches: ~p~n", [Lit, Ps]),
+ {V,Extra,Cnt} = patch_literal_term(Lit, Ps, Cnt0),
+ {Last#b_ret{arg=V}, Extra, Cnt};
+patch_appends_ret(Last=#b_ret{arg=#b_literal{val=Lit}},
+ [{ret,_,Element}],
+ Cnt0) ->
+ ?DP("patch_appends_ret other:~n lit: ~p~n element: ~p~n", [Lit, Element]),
+ {V,Extra,Cnt} = patch_literal_term(Lit, Element, Cnt0),
+ {Last#b_ret{arg=V}, Extra, Cnt}.
+
+%% Should return the instructions in reversed order
+patch_opargs(I0=#b_set{args=Args}, Patches0, Cnt0) ->
+ ?DP("Patching args in ~p~nArgs: ~p~n Patches: ~p~n",
+ [I0,Args,Patches0]),
+ Patches = merge_arg_patches(Patches0),
+ {PatchedArgs, Is, Cnt} = patch_opargs(Args, Patches, 0, [], [], Cnt0),
+ {[I0#b_set{args=reverse(PatchedArgs)}|Is], Cnt}.
+
+patch_opargs([#b_literal{val=Lit}|Args], [{Idx,Lit,Element}|Patches],
+ Idx, PatchedArgs, Is, Cnt0) ->
+ ?DP("Patching arg idx ~p~n lit: ~p~n elem: ~p~n", [Idx, Lit, Element]),
+ {Arg,Extra,Cnt} = patch_literal_term(Lit, Element, Cnt0),
+ patch_opargs(Args, Patches, Idx + 1, [Arg|PatchedArgs], Extra ++ Is, Cnt);
+patch_opargs([Arg|Args], Patches, Idx, PatchedArgs, Is, Cnt) ->
+ ?DP("Skipping arg idx ~p~n arg: ~p~n patches: ~p~n",
+ [Idx,Arg,Patches]),
+ patch_opargs(Args, Patches, Idx + 1, [Arg|PatchedArgs], Is, Cnt);
+patch_opargs([], [], _, PatchedArgs, Is, Cnt) ->
+ {PatchedArgs, Is, Cnt}.
+
+%% The way find_defs and find_appends work, we can end up with
+%% multiple patches patching different parts of a tuple or pair. We
+%% merge them here.
+merge_arg_patches([{Idx,Lit,P0},{Idx,Lit,P1}|Patches]) ->
+ P = case {P0, P1} of
+ {{tuple_element,I0,E0},{tuple_element,I1,E1}} ->
+ {tuple_elements,[{I0,E0},{I1,E1}]};
+ {{tuple_elements,Es},{tuple_element,I,E}} ->
+ {tuple_elements,[{I,E}|Es]}
+ end,
+ merge_arg_patches([{Idx,Lit,P}|Patches]);
+merge_arg_patches([P|Patches]) ->
+ [P|merge_arg_patches(Patches)];
+merge_arg_patches([]) ->
+ [].
+
+patch_phi(I0=#b_set{op=phi,args=Args0}, Patches, Cnt0) ->
+ L2P = foldl(fun(Phi={phi,_,Lbl,_,_}, Acc) ->
+ Acc#{Lbl => Phi}
+ end, #{}, Patches),
+ {Args, Extra, Cnt} =
+ foldr(fun(Arg0={_, Lbl}, {ArgsAcc, ExtraAcc, CntAcc}) ->
+ case L2P of
+ #{Lbl := {phi,_,Lbl,Lit,Element}} ->
+ {Arg,Extra,Cnt1} =
+ patch_literal_term(Lit, Element, CntAcc),
+ {[{Arg,Lbl}|ArgsAcc],
+ [{Lbl, Extra}|ExtraAcc], Cnt1};
+ _ ->
+ {[Arg0|ArgsAcc], ExtraAcc, CntAcc}
+ end
+ end, {[], [], Cnt0}, Args0),
+ I = I0#b_set{op=phi,args=Args},
+ {I, Extra, Cnt}.
+
+%% Should return the instructions in reversed order
+patch_literal_term(Tuple, {tuple_elements,Elems}, Cnt) ->
+ Es = [{tuple_element,I,E} || {I,E} <- keysort(1, Elems)],
+ patch_literal_tuple(Tuple, Es, Cnt);
+patch_literal_term(Tuple, Elements0, Cnt) when is_tuple(Tuple) ->
+ Elements = if is_list(Elements0) -> Elements0;
+ true -> [Elements0]
+ end,
+ patch_literal_tuple(Tuple, Elements, Cnt);
+patch_literal_term(<<>>, self, Cnt0) ->
+ {V,Cnt} = new_var(Cnt0),
+ I = #b_set{op=bs_init_writable,dst=V,args=[#b_literal{val=256}]},
+ {V, [I], Cnt};
+patch_literal_term(Lit, self, Cnt) ->
+ {#b_literal{val=Lit}, [], Cnt};
+patch_literal_term([H0|T0], {hd,Element}, Cnt0) ->
+ {H,Extra,Cnt1} = patch_literal_term(H0, Element, Cnt0),
+ {T,[],Cnt1} = patch_literal_term(T0, [], Cnt1),
+ {Dst,Cnt} = new_var(Cnt1),
+ I = #b_set{op=put_list,dst=Dst,args=[H,T]},
+ {Dst, [I|Extra], Cnt};
+patch_literal_term(Lit, [], Cnt) ->
+ {#b_literal{val=Lit}, [], Cnt}.
+
+patch_literal_tuple(Tuple, Elements, Cnt) ->
+ ?DP("Will patch literal tuple~n tuple:~p~n elements: ~p~n",
+ [Tuple,Elements]),
+ patch_literal_tuple(erlang:tuple_to_list(Tuple), Elements, [], [], 0, Cnt).
+
+patch_literal_tuple([Lit|LitElements], [{tuple_element,Idx,Element}|Elements],
+ Patched, Extra, Idx, Cnt0) ->
+ ?DP("patch_literal_tuple: idx:~p~n Lit: ~p~n patch: ~p~n",
+ [Idx, Lit, Element]),
+ {V,Exs,Cnt} = patch_literal_term(Lit, Element, Cnt0),
+ patch_literal_tuple(LitElements, Elements, [V|Patched],
+ Exs ++ Extra, Idx + 1, Cnt);
+patch_literal_tuple([Lit|LitElements], Patches, Patched, Extra, Idx, Cnt) ->
+ ?DP("patch_literal_tuple: skipping idx:~p~n Lit: ~p~n patches: ~p~n", [Idx, Lit, Patches]),
+ {T,[],Cnt} = patch_literal_term(Lit, [], Cnt),
+ patch_literal_tuple(LitElements, Patches, [T|Patched],
+ Extra, Idx + 1, Cnt);
+patch_literal_tuple([], [], Patched, Extra, _, Cnt0) ->
+ {V,Cnt} = new_var(Cnt0),
+ I = #b_set{op=put_tuple,dst=V,args=reverse(Patched)},
+ {V, [I|Extra], Cnt}.
+
+%% As beam_ssa_opt:new_var/2, but with a hard-coded base
+new_var(Count) ->
+ {#b_var{name={alias_opt,Count}},Count+1}.
+
+%% Done with an accumulator to reverse the reversed block order from
+%% patch_appends_f/5.
+insert_block_additions([Blk0={L,B=#b_blk{is=Is0}}|RevLinear],
+ Lbl2Addition, Acc) ->
+ Blk = case Lbl2Addition of
+ #{ L := Additions} ->
+ Is = Is0 ++ reverse(Additions),
+ {L,B#b_blk{is=Is}};
+ _ ->
+ Blk0
+ end,
+ insert_block_additions(RevLinear, Lbl2Addition, [Blk|Acc]);
+insert_block_additions([], _, Acc) ->
+ Acc.
+
+%%%
+%%% Predicate to check if a function is the stub for a nif.
+%%%
+-spec is_nif(func_id(), st_map()) -> boolean().
+
+is_nif(F, StMap) ->
+ #opt_st{ssa=[{0,#b_blk{is=Is}}|_]} = map_get(F, StMap),
+ case Is of
+ [#b_set{op=nif_start}|_] ->
+ true;
+ _ -> false
+ end.
diff --git a/lib/compiler/src/beam_ssa_recv.erl b/lib/compiler/src/beam_ssa_recv.erl
index f1d58ffb16..240a0b1a34 100644
--- a/lib/compiler/src/beam_ssa_recv.erl
+++ b/lib/compiler/src/beam_ssa_recv.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -104,7 +104,7 @@
-include("beam_ssa.hrl").
--import(lists, [foldl/3, search/2]).
+-import(lists, [foldl/3, member/2, search/2]).
%% Psuedo-block for representing function returns in the block graph. Body
%% calls add an edge returning _from_ this block.
@@ -118,6 +118,7 @@ format_error(OptInfo) ->
-record(scan, { graph=beam_digraph:new(),
module :: #{ beam_ssa:b_local() => {beam_ssa:block_map(),
+ [beam_ssa:b_var()],
[beam_ssa:b_var()]} },
recv_candidates=#{},
ref_candidates=#{} }).
@@ -134,6 +135,11 @@ module(#b_module{}=Mod0, Opts) ->
%% by walking through the module-wide graph.
{Markers, Uses, Clears} = plan(Scan),
+ %% If we have any markers we must have uses and vice versa. If
+ %% there any any clears, we must have markers.
+ true = (Markers =/= #{}) =:= (Uses =/= #{}), %Assertion.
+ true = (Clears =:= #{}) orelse (Markers =/= #{}), %Assertion.
+
Mod = optimize(Mod0, Markers, Uses, Clears),
Ws = case proplists:get_bool(recv_opt_info, Opts) of
@@ -156,7 +162,8 @@ scan(#b_module{body=Fs}) ->
Rs = maps:from_list(Rs0),
ModMap = foldl(fun(#b_function{bs=Blocks,args=Args}=F, Acc) ->
FuncId = get_func_id(F),
- Acc#{ FuncId => {Blocks, Args} }
+ Rets = scan_rets(Blocks),
+ Acc#{ FuncId => {Blocks, Args, Rets} }
end, #{}, Fs),
foldl(fun(F, Scan0) ->
FuncId = get_func_id(F),
@@ -180,6 +187,14 @@ scan_peek_message([#b_function{bs=Bs}=F | Fs]) ->
scan_peek_message([]) ->
[].
+scan_rets(Blocks) ->
+ Rets = maps:fold(fun(_K, #b_blk{last=#b_ret{arg=#b_var{}=RetVal}}, Acc) ->
+ gb_sets:add_element(RetVal, Acc);
+ (_K, _V, Acc) ->
+ Acc
+ end, gb_sets:new(), Blocks),
+ gb_sets:to_list(Rets).
+
scan_peek_message_bs([{Lbl, Blk} | Bs]) ->
case Blk of
#b_blk{is=[#b_set{op=peek_message}=I | _]} ->
@@ -222,8 +237,9 @@ scan_is([#b_set{op=call,dst=Dst,args=[#b_remote{} | _]}=Call,
#b_blk{last=#b_br{succ=Succ}}, Lbl, Blocks, FuncId, State0) ->
case Blocks of
#{ Succ := #b_blk{is=[],last=#b_ret{arg=Dst}}} ->
- %% External tail call, ignore it altogether.
- State0;
+ %% Tail call, skip adding a successor edge to prevent
+ %% clearing the marker needlessly.
+ si_remote_call(Call, Lbl, Succ, Blocks, FuncId, State0);
#{} ->
State = si_remote_call(Call, Lbl, Succ, Blocks, FuncId, State0),
scan_add_edge({FuncId, Lbl}, {FuncId, Succ}, State)
@@ -233,23 +249,19 @@ scan_is([#b_set{op=call,args=[#b_remote{} | _]}=Call | Is],
%% Remote call that always succeeds.
State = si_remote_call(Call, Lbl, Lbl, Blocks, FuncId, State0),
scan_is(Is, Blk, Lbl, Blocks, FuncId, State);
-scan_is([#b_set{op=call,dst=Dst,args=[#b_local{}=Callee | Args]},
+scan_is([#b_set{op=call,dst=Dst,args=[#b_local{} | _]}=Call,
#b_set{op={succeeded,body},args=[Dst]}],
#b_blk{last=#b_br{succ=Succ}},
Lbl, Blocks, FuncId, State0) ->
case Blocks of
#{ Succ := #b_blk{is=[],last=#b_ret{arg=Dst}}} ->
- %% Local tail call, don't add any edges.
- scan_add_call(tail, Args, Callee, Lbl, FuncId, State0);
+ %% Tail call, skip adding a successor edge to prevent
+ %% clearing the marker needlessly.
+ scan_add_call(Call, Lbl, Succ, FuncId, State0);
#{} ->
- State = scan_add_call(body, Args, Callee, Lbl, FuncId, State0),
+ State = scan_add_call(Call, Lbl, Succ, FuncId, State0),
scan_add_edge({FuncId, Lbl}, {FuncId, Succ}, State)
end;
-scan_is([#b_set{op=call,args=[#b_local{}=Callee | Args]} | Is],
- Blk, Lbl, Blocks, FuncId, State0) ->
- %% Local call that always succeeds.
- State = scan_add_call(body, Args, Callee, Lbl, FuncId, State0),
- scan_is(Is, Blk, Lbl, Blocks, FuncId, State);
scan_is([_I | Is], Blk, Lbl, Blocks, FuncId, State) ->
scan_is(Is, Blk, Lbl, Blocks, FuncId, State);
scan_is([], #b_blk{last=#b_ret{}}, Lbl, _Blocks, FuncId, State) ->
@@ -261,25 +273,23 @@ scan_is([], Blk, Lbl, _Blocks, FuncId, State) ->
%% Adds an edge to the callee, with argument/parameter translation to let us
%% follow specific references.
-scan_add_call(Kind, Args, Callee, Lbl, Caller, #scan{module=ModMap}=State0) ->
- #{ Callee := {_Blocks, Params} } = ModMap,
-
- {Translation, Inverse} = scan_translate_call(Args, Params, #{}, #{}),
+scan_add_call(Call, CallLbl, SuccLbl, Caller, #scan{module=ModMap}=State0) ->
+ #b_set{dst=Dst,args=[#b_local{}=Callee | Args]} = Call,
+ #{ Callee := {_Blocks, Params, Rets} } = ModMap,
- State = scan_add_edge({Caller, Lbl},
+ {CallTranslation, CallInverse} =
+ scan_translate_call(Args, Params, #{}, #{}),
+ State = scan_add_edge({Caller, CallLbl},
{Callee, ?ENTRY_BLOCK},
- {Translation, Inverse},
+ {CallTranslation, CallInverse, Args},
State0),
- case Kind of
- body ->
- scan_add_edge({Callee, ?RETURN_BLOCK},
- {Caller, Lbl},
- {Inverse, Translation},
- State);
- tail ->
- State
- end.
+ {RetTranslation, RetInverse} =
+ scan_translate_return(Rets, Dst, CallTranslation),
+ scan_add_edge({Callee, ?RETURN_BLOCK},
+ {Caller, SuccLbl},
+ {RetTranslation, RetInverse, Params},
+ State).
scan_translate_call([Arg | Args], [Param | Params], ArgToParams, ParamToArgs) ->
scan_translate_call(Args, Params,
@@ -288,6 +298,16 @@ scan_translate_call([Arg | Args], [Param | Params], ArgToParams, ParamToArgs) ->
scan_translate_call([], [], ArgToParams, ParamToArgs) ->
{ArgToParams, ParamToArgs}.
+scan_translate_return(Rets, Dst, CallerToCallee0) ->
+ CallerToCallee = CallerToCallee0#{ Dst => Rets },
+ CalleeToCaller = scan_translate_return_1(Rets, Dst, #{}),
+ {CalleeToCaller, CallerToCallee}.
+
+scan_translate_return_1([Ret | Rets], Dst, CalleeToCaller) ->
+ scan_translate_return_1(Rets, Dst, CalleeToCaller#{ Ret => Dst });
+scan_translate_return_1([], _Dst, CalleeToCaller) ->
+ CalleeToCaller.
+
scan_add_edge(From, To, State) ->
scan_add_edge(From, To, branch, State).
@@ -342,7 +362,7 @@ si_remote_call_1(Dst, [Callee | Args], Lbl, Blocks) ->
none
end,
case MFA of
- {erlang,alias,A} when 0 =< A, A =< 1 ->
+ {erlang,alias,A} when is_integer(A), 0 =< A, A =< 1 ->
{makes_ref, Lbl, Dst};
{erlang,demonitor,2} ->
case Args of
@@ -357,12 +377,12 @@ si_remote_call_1(Dst, [Callee | Args], Lbl, Blocks) ->
end;
{erlang,make_ref,0} ->
{makes_ref, Lbl, Dst};
- {erlang,monitor,A} when 2 =< A, A =< 3 ->
+ {erlang,monitor,A} when is_integer(A), 2 =< A, A =< 3 ->
{makes_ref, Lbl, Dst};
- {erlang,spawn_monitor,A} when 1 =< A, A =< 4 ->
+ {erlang,spawn_monitor,A} when is_integer(A), 1 =< A, A =< 4 ->
RPO = beam_ssa:rpo([Lbl], Blocks),
si_ref_in_tuple(RPO, Blocks, Dst);
- {erlang,spawn_request,A} when 1 =< A, A =< 5 ->
+ {erlang,spawn_request,A} when is_integer(A), 1 =< A, A =< 5 ->
{makes_ref, Lbl, Dst};
_ ->
%% As an aside, spawn_opt/2-5 is trivially supported by handling it
@@ -410,7 +430,7 @@ plan(Scan) ->
Uses = plan_uses(ReceiveCandidates, RefMap0, ModMap),
%% Limit the reference map to the references that are actually used.
- RefMap = intersect_uses(Uses, Graph, RefMap0),
+ RefMap = intersect_uses(Uses, RefMap0, Graph),
%% Reserve and bind markers when we create a reference that we know will be
%% used.
@@ -449,14 +469,19 @@ propagate_references_1([], _G, Acc) ->
pr_successors([{_From, To, branch} | Edges], Ref) ->
[{To, Ref} | pr_successors(Edges, Ref)];
-pr_successors([{{_, FromLbl}, To, {Translation, _Inverse}} | Edges], Ref) ->
+pr_successors([{{_, FromLbl}, To, {Translation, _Inverse, Args}} | Edges],
+ Ref) ->
case Translation of
- #{ Ref := Param } when FromLbl =/= ?RETURN_BLOCK ->
- %% We ignore return edges to avoid leaking markers to functions
- %% that lack them. Consider the following:
+ #{ Ref := #b_var{}=Param } ->
+ %% If we return a function parameter, we must ignore the return
+ %% edge to avoid leaking markers to functions that lack them.
+ %% Consider the following:
%%
- %% t(NotMarker) -> id(NotMarker), receive NotMarker -> ok end.
- %% g() -> id(make_ref()).
+ %% t(NotMarker) ->
+ %% id(NotMarker),
+ %% receive NotMarker -> ok end.
+ %% g() ->
+ %% id(make_ref()).
%% id(I) -> I.
%%
%% Since id/1 receives a potential marker from at least one source,
@@ -465,7 +490,13 @@ pr_successors([{{_, FromLbl}, To, {Translation, _Inverse}} | Edges], Ref) ->
%% marker after the call to id/1, enabling the optimization in the
%% following receive. This would not be dangerous but it's a
%% pessimization we'd rather avoid.
- [{To, Param} | pr_successors(Edges, Ref)];
+ case (FromLbl =/= ?RETURN_BLOCK orelse
+ not member(Ref, Args)) of
+ true ->
+ [{To, Param} | pr_successors(Edges, Ref)];
+ false ->
+ pr_successors(Edges, Ref)
+ end;
#{} ->
pr_successors(Edges, Ref)
end;
@@ -476,7 +507,7 @@ pr_successors([], _Ref) ->
%% references we can use to jumpstart them.
plan_uses(Candidates, RefMap, ModMap) ->
maps:fold(fun(FuncId, Receives, Acc) ->
- #{ FuncId := {Blocks, _Params} } = ModMap,
+ #{ FuncId := {Blocks, _Params, _Rets} } = ModMap,
case plan_uses_1(Receives, FuncId, Blocks, RefMap) of
[_|_]=Uses -> Acc#{ FuncId => Uses };
[] -> Acc
@@ -630,16 +661,16 @@ pu_is_ref_msg_comparison(_, _) ->
%% Takes the map of all references available at a given block, and limits it to
%% those that are actually used in (all clauses of) a succeeding receive.
-intersect_uses(UsageMap, G, RefMap) ->
+intersect_uses(UsageMap, RefMap, Graph) ->
Roots = maps:fold(fun(FuncId, Uses, Acc) ->
[begin
Vertex = {FuncId, Lbl},
{Vertex, Ref}
end || {Lbl, _I, Ref} <- Uses] ++ Acc
end, [], UsageMap),
- intersect_uses_1(Roots, G, RefMap, #{}).
+ intersect_uses_1(Roots, RefMap, Graph, #{}).
-intersect_uses_1([{Vertex, Ref} | Vs], G, RefMap, Acc0) ->
+intersect_uses_1([{Vertex, Ref} | Vs], RefMap, Graph, Acc0) ->
PossibleRefs = maps:get(Vertex, RefMap, sets:new([{version, 2}])),
ActiveRefs0 = maps:get(Vertex, Acc0, sets:new([{version, 2}])),
Acc = case {sets:is_element(Ref, PossibleRefs),
@@ -647,9 +678,10 @@ intersect_uses_1([{Vertex, Ref} | Vs], G, RefMap, Acc0) ->
{true, false} ->
%% This block lies between reference creation and the receive
%% block, add it to the intersection.
- Next = iu_predecessors(beam_digraph:in_edges(G, Vertex), Ref),
+ Edges = beam_digraph:in_edges(Graph, Vertex),
+ Next = iu_predecessors(Edges, Ref),
ActiveRefs = sets:add_element(Ref, ActiveRefs0),
- intersect_uses_1(Next, G, RefMap,
+ intersect_uses_1(Next, RefMap, Graph,
Acc0#{ Vertex => ActiveRefs });
{false, _} ->
%% This block does not succeed the creation of the
@@ -659,19 +691,22 @@ intersect_uses_1([{Vertex, Ref} | Vs], G, RefMap, Acc0) ->
%% We've already handled this block, move on.
Acc0
end,
- intersect_uses_1(Vs, G, RefMap, Acc);
-intersect_uses_1([], _G, _RefMap, Acc) ->
+ intersect_uses_1(Vs, RefMap, Graph, Acc);
+intersect_uses_1([], _RefMap, _Graph, Acc) ->
Acc.
iu_predecessors([{From, _To, branch} | Edges], Ref) ->
[{From, Ref} | iu_predecessors(Edges, Ref)];
-iu_predecessors([{From, _To, {_Translation, Inverse}} | Edges], Ref) ->
+iu_predecessors([{From, _To, {_Translation, Inverse, _Args}} | Edges], Ref) ->
case Inverse of
#{ Ref := #b_var{}=Arg } ->
[{From, Arg} | iu_predecessors(Edges, Ref)];
+ #{ Ref := [_|_]=Rets } ->
+ [{From, Ret} || Ret <- Rets] ++ iu_predecessors(Edges, Ref);
#{} ->
- %% `Ref` is not a function argument (created in first block) or was
- %% passed as a literal on this call, ignore it.
+ %% `Ref` is not a function argument (created in first block), is
+ %% not a return value, or it was passed as a literal on this call.
+ %% Either way we'll ignore it.
iu_predecessors(Edges, Ref)
end;
iu_predecessors([], _Ref) ->
@@ -715,12 +750,13 @@ plan_clears_1([{From, To, branch} | Edges], ActiveRefs, UsageMap) ->
{FuncId, FromLbl} = From,
{FuncId, ToLbl} = To,
- [{FromLbl, ToLbl, Ref} || Ref <- sets:to_list(Refs)]
- ++ plan_clears_1(Edges, ActiveRefs, UsageMap);
-plan_clears_1([{_From, _To, {_, _}} | Edges], ActiveRefs, UsageMap) ->
- %% We don't need to clear references on calls: those we haven't passed will
- %% remain valid after we return, and those we do pass will be cleared by
- %% the callee when necessary.
+ Clears = [{FromLbl, ToLbl, Ref} || Ref <- sets:to_list(Refs)],
+ Clears ++ plan_clears_1(Edges, ActiveRefs, UsageMap);
+plan_clears_1([{_From, _To, {_, _, _}} | Edges],
+ ActiveRefs, UsageMap) ->
+ %% We don't need to clear references on calls and returns: those we haven't
+ %% passed will remain valid after we return, and those we do pass will be
+ %% cleared by the caller/callee when necessary.
plan_clears_1(Edges, ActiveRefs, UsageMap);
plan_clears_1([], _ActiveRefs, _UsageMap) ->
[].
diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl
index bd21655914..13b6038868 100644
--- a/lib/compiler/src/beam_ssa_share.erl
+++ b/lib/compiler/src/beam_ssa_share.erl
@@ -51,7 +51,7 @@ module(#b_module{body=Fs0}=Module, _Opts) ->
Blocks0 :: beam_ssa:block_map(),
Blk :: beam_ssa:b_blk().
-block(#b_blk{is=Is0,last=Last0}=Blk, Blocks) ->
+block(#b_blk{is=Is0,last=Last0}=Blk, Blocks) when is_map(Blocks) ->
case share_terminator(Last0, Blocks) of
none ->
Blk;
@@ -75,7 +75,7 @@ block(#b_blk{is=Is0,last=Last0}=Blk, Blocks) ->
%%% Local functions.
%%%
-function(#b_function{anno=Anno,bs=Blocks0}=F) ->
+function(#b_function{anno=Anno,bs=Blocks0}=F) when is_map(Blocks0) ->
try
PO = reverse(beam_ssa:rpo(Blocks0)),
{Blocks1,Changed} = blocks(PO, Blocks0, false),
diff --git a/lib/compiler/src/beam_ssa_throw.erl b/lib/compiler/src/beam_ssa_throw.erl
index c2dc552119..63a5541d01 100644
--- a/lib/compiler/src/beam_ssa_throw.erl
+++ b/lib/compiler/src/beam_ssa_throw.erl
@@ -442,7 +442,7 @@ ois_is([#b_set{op={bif,is_list},dst=Dst,args=[Src]} | Is], Ts) ->
ois_is([#b_set{op={bif,is_map},dst=Dst,args=[Src]} | Is], Ts) ->
ois_type_test(Src, Dst, #t_map{}, Is, Ts);
ois_is([#b_set{op={bif,is_number},dst=Dst,args=[Src]} | Is], Ts) ->
- ois_type_test(Src, Dst, number, Is, Ts);
+ ois_type_test(Src, Dst, #t_number{}, Is, Ts);
ois_is([#b_set{op={bif,is_tuple},dst=Dst,args=[Src]} | Is], Ts) ->
ois_type_test(Src, Dst, #t_tuple{}, Is, Ts);
ois_is([#b_set{op=is_nonempty_list,dst=Dst,args=[Src]} | Is], Ts) ->
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl
index 77b723caa0..725eb9ee29 100644
--- a/lib/compiler/src/beam_ssa_type.erl
+++ b/lib/compiler/src/beam_ssa_type.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -207,8 +207,8 @@ sig_function_1(Id, StMap, State0, FuncDb) ->
name=#b_literal{val=unknown},
arity=0}]},
- Ds = maps:from_list([{Var, FakeCall#b_set{dst=Var}} ||
- #b_var{}=Var <- Args]),
+ Ds = #{Var => FakeCall#b_set{dst=Var} ||
+ #b_var{}=Var <- Args},
Ls = #{ ?EXCEPTION_BLOCK => {incoming, Ts},
0 => {incoming, Ts} },
@@ -378,11 +378,7 @@ init_sig_st(StMap, FuncDb) ->
wl=wl_defer_list(Roots, wl_new()) }.
init_sig_roots(FuncDb) ->
- maps:fold(fun(Id, #func_info{exported=true}, Acc) ->
- [Id | Acc];
- (_, _, Acc) ->
- Acc
- end, [], FuncDb).
+ [Id || Id := #func_info{exported=true} <- FuncDb].
init_sig_args([Root | Roots], StMap, Acc) ->
#opt_st{args=Args0} = map_get(Root, StMap),
@@ -438,14 +434,14 @@ opt_continue(Linear0, Args, Anno, FuncDb) when FuncDb =/= #{} ->
#{ Id := #func_info{exported=true} } ->
%% We can't infer the parameter types of exported functions, but
%% running the pass again could still help other functions.
- Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]),
+ Ts = #{V => any || #b_var{}=V <- Args},
opt_function(Linear0, Args, Id, Ts, FuncDb)
end;
opt_continue(Linear0, Args, Anno, _FuncDb) ->
%% Module-level optimization is disabled, pass an empty function database
%% so we only perform local optimizations.
Id = get_func_id(Anno),
- Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]),
+ Ts = #{V => any || #b_var{}=V <- Args},
{Linear, _} = opt_function(Linear0, Args, Id, Ts, #{}),
{Linear, #{}}.
@@ -491,8 +487,8 @@ do_opt_function(Linear0, Args, Id, Ts, FuncDb0, MetaCache) ->
name=#b_literal{val=unknown},
arity=0}]},
- Ds = maps:from_list([{Var, FakeCall#b_set{dst=Var}} ||
- #b_var{}=Var <- Args]),
+ Ds = #{Var => FakeCall#b_set{dst=Var} ||
+ #b_var{}=Var <- Args},
Ls = #{ ?EXCEPTION_BLOCK => {incoming, Ts},
0 => {incoming, Ts} },
@@ -533,7 +529,9 @@ opt_bs([{L, #b_blk{is=Is0,last=Last0}=Blk0} | Bs],
SuccTypes = update_success_types(Last1, Ts, Ds, Meta, SuccTypes0),
UsedOnce = Meta#metadata.used_once,
- {Last, Ls1} = update_successors(Last1, Ts, Ds, Ls0, UsedOnce),
+ {Last2, Ls1} = update_successors(Last1, Ts, Ds, Ls0, UsedOnce),
+
+ Last = opt_anno_types(Last2, Ts),
Ls = Ls1#{ L := {outgoing, Ts} }, %Assertion.
@@ -598,7 +596,17 @@ opt_anno_types(#b_set{op=Op,args=Args}=I, Ts) ->
case benefits_from_type_anno(Op, Args) of
true -> opt_anno_types_1(I, Args, Ts, 0, #{});
false -> I
- end.
+ end;
+opt_anno_types(#b_switch{anno=Anno0,arg=Arg}=I, Ts) ->
+ case concrete_type(Arg, Ts) of
+ any ->
+ I;
+ Type ->
+ Anno = Anno0#{arg_types => #{0 => Type}},
+ I#b_switch{anno=Anno}
+ end;
+opt_anno_types(I, _Ts) ->
+ I.
opt_anno_types_1(I, [#b_var{}=Var | Args], Ts, Index, Acc0) ->
case concrete_type(Var, Ts) of
@@ -626,14 +634,12 @@ opt_anno_types_1(#b_set{anno=Anno0}=I, [], _Ts, _Index, Acc) ->
end.
%% Only add type annotations when we know we'll make good use of them.
-benefits_from_type_anno({bif,'=:='}, _Args) ->
+benefits_from_type_anno({bif,_Op}, _Args) ->
true;
-benefits_from_type_anno({bif,'=/='}, _Args) ->
- true;
-benefits_from_type_anno({bif,Op}, Args) ->
- not erl_internal:bool_op(Op, length(Args));
benefits_from_type_anno(bs_create_bin, _Args) ->
true;
+benefits_from_type_anno(bs_match, _Args) ->
+ true;
benefits_from_type_anno(is_tagged_tuple, _Args) ->
true;
benefits_from_type_anno(call, [#b_var{} | _]) ->
@@ -906,8 +912,20 @@ update_anno_types_1([#b_var{}=V|As], Ts, Index, ArgTypes) ->
case beam_types:meet(T0, T1) of
any ->
update_anno_types_1(As, Ts, Index + 1, ArgTypes);
+ none ->
+ %% This instruction will never be reached. This happens when
+ %% compiling code such as the following:
+ %%
+ %% f(X) when is_integer(X), 0 =< X, X < 64 ->
+ %% (X = bnot X) + 1.
+ %%
+ %% The main type optimization sub pass will not find out
+ %% that `(X = bnot X)` will never succeed and that the `+`
+ %% operator is never executed, but this sub pass will.
+ %% This happens very rarely; therefore, don't bother removing
+ %% the unreachable instruction.
+ update_anno_types_1(As, Ts, Index + 1, ArgTypes);
T ->
- true = T =/= none, %Assertion.
update_anno_types_1(As, Ts, Index + 1, ArgTypes#{Index => T})
end;
update_anno_types_1([_|As], Ts, Index, ArgTypes) ->
@@ -936,12 +954,27 @@ simplify_terminator(#b_switch{arg=Arg0,fail=Fail,list=List0}=Sw0,
#b_br{}=Br ->
simplify_terminator(Br, Ts, Ds, Sub)
end;
-simplify_terminator(#b_ret{arg=Arg}=Ret, Ts, Ds, Sub) ->
+simplify_terminator(#b_ret{arg=Arg,anno=Anno0}=Ret0, Ts, Ds, Sub) ->
%% Reducing the result of a call to a literal (fairly common for 'ok')
%% breaks tail call optimization.
- case Ds of
- #{ Arg := #b_set{op=call}} -> Ret;
- #{} -> Ret#b_ret{arg=simplify_arg(Arg, Ts, Sub)}
+ Ret = case Ds of
+ #{ Arg := #b_set{op=call}} -> Ret0;
+ #{} -> Ret0#b_ret{arg=simplify_arg(Arg, Ts, Sub)}
+ end,
+ %% Annotate the terminator with the type it returns, skip the
+ %% annotation if nothing is yet known about the variable. The
+ %% annotation is used by the alias analysis pass.
+ Type = case {Arg, Ts} of
+ {#b_literal{},_} -> concrete_type(Arg, Ts);
+ {_,#{Arg:=_}} -> concrete_type(Arg, Ts);
+ _ -> any
+ end,
+ case Type of
+ any ->
+ Ret;
+ _ ->
+ Anno = Anno0#{ result_type => Type },
+ Ret#b_ret{anno=Anno}
end.
%%
@@ -1012,10 +1045,24 @@ simplify(#b_set{op=bs_match,dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) ->
Ts = update_types(I, Ts0, Ds0),
Ds = Ds0#{ Dst => I },
{I, Ts, Ds};
+simplify(#b_set{op=bs_create_bin=Op,dst=Dst,args=Args0,anno=Anno}=I0,
+ Ts0, Ds0, _Ls, Sub) ->
+ Args = simplify_args(Args0, Ts0, Sub),
+ I1 = I0#b_set{args=Args},
+ #t_bitstring{size_unit=Unit} = T = type(Op, Args, Anno, Ts0, Ds0),
+ I2 = case T of
+ #t_bitstring{appendable=true} ->
+ beam_ssa:add_anno(result_type, T, I1);
+ _ -> I1
+ end,
+ I = beam_ssa:add_anno(unit, Unit, I2),
+ Ts = Ts0#{ Dst => T },
+ Ds = Ds0#{ Dst => I },
+ {I, Ts, Ds};
simplify(#b_set{dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) ->
Args = simplify_args(Args0, Ts0, Sub),
I1 = beam_ssa:normalize(I0#b_set{args=Args}),
- case simplify(I1, Ts0) of
+ case simplify(I1, Ts0, Ds0) of
#b_set{}=I ->
Ts = update_types(I, Ts0, Ds0),
Ds = Ds0#{ Dst => I },
@@ -1026,54 +1073,67 @@ simplify(#b_set{dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) ->
Sub#{ Dst => Var }
end.
-simplify(#b_set{op={bif,'and'},args=Args}=I, Ts) ->
+simplify(#b_set{op={bif,'band'},args=Args}=I, Ts, Ds) ->
+ case normalized_types(Args, Ts) of
+ [#t_integer{elements=R},#t_integer{elements={M,M}}] ->
+ case beam_bounds:is_masking_redundant(R, M) of
+ true ->
+ %% M is mask that will have no effect.
+ hd(Args);
+ false ->
+ eval_bif(I, Ts, Ds)
+ end;
+ [_,_] ->
+ eval_bif(I, Ts, Ds)
+ end;
+simplify(#b_set{op={bif,'and'},args=Args}=I, Ts, Ds) ->
case is_safe_bool_op(Args, Ts) of
true ->
case Args of
[_,#b_literal{val=false}=Res] -> Res;
[Res,#b_literal{val=true}] -> Res;
- _ -> eval_bif(I, Ts)
+ _ -> eval_bif(I, Ts, Ds)
end;
false ->
I
end;
-simplify(#b_set{op={bif,'or'},args=Args}=I, Ts) ->
+simplify(#b_set{op={bif,'or'},args=Args}=I, Ts, Ds) ->
case is_safe_bool_op(Args, Ts) of
true ->
case Args of
[Res,#b_literal{val=false}] -> Res;
[_,#b_literal{val=true}=Res] -> Res;
- _ -> eval_bif(I, Ts)
+ _ -> eval_bif(I, Ts, Ds)
end;
false ->
I
end;
-simplify(#b_set{op={bif,element},args=[#b_literal{val=Index},Tuple]}=I0, Ts) ->
+simplify(#b_set{op={bif,element},args=[#b_literal{val=Index},Tuple]}=I0, Ts, Ds) ->
case normalized_type(Tuple, Ts) of
#t_tuple{size=Size} when is_integer(Index),
1 =< Index,
Index =< Size ->
I = I0#b_set{op=get_tuple_element,
args=[Tuple,#b_literal{val=Index-1}]},
- simplify(I, Ts);
+ simplify(I, Ts, Ds);
_ ->
- eval_bif(I0, Ts)
+ eval_bif(I0, Ts, Ds)
end;
-simplify(#b_set{op={bif,hd},args=[List]}=I, Ts) ->
+simplify(#b_set{op={bif,hd},args=[List]}=I, Ts, Ds) ->
case normalized_type(List, Ts) of
#t_cons{} ->
I#b_set{op=get_hd};
_ ->
- eval_bif(I, Ts)
+ eval_bif(I, Ts, Ds)
end;
-simplify(#b_set{op={bif,tl},args=[List]}=I, Ts) ->
+simplify(#b_set{op={bif,tl},args=[List]}=I, Ts, Ds) ->
case normalized_type(List, Ts) of
#t_cons{} ->
I#b_set{op=get_tl};
_ ->
- eval_bif(I, Ts)
+ eval_bif(I, Ts, Ds)
end;
-simplify(#b_set{op={bif,size},args=[Term]}=I, Ts) ->
+simplify(#b_set{op={bif,size},args=[Term]}=I, Ts, Ds) ->
case normalized_type(Term, Ts) of
#t_tuple{} ->
simplify(I#b_set{op={bif,tuple_size}}, Ts);
@@ -1081,49 +1141,57 @@ simplify(#b_set{op={bif,size},args=[Term]}=I, Ts) ->
%% If the bitstring is a binary (the size in bits is
%% evenly divisibly by 8), byte_size/1 gives
%% the same result as size/1.
- simplify(I#b_set{op={bif,byte_size}}, Ts);
+ simplify(I#b_set{op={bif,byte_size}}, Ts, Ds);
_ ->
- eval_bif(I, Ts)
+ eval_bif(I, Ts, Ds)
end;
-simplify(#b_set{op={bif,tuple_size},args=[Term]}=I, Ts) ->
+simplify(#b_set{op={bif,tuple_size},args=[Term]}=I, Ts, _Ds) ->
case normalized_type(Term, Ts) of
#t_tuple{size=Size,exact=true} ->
#b_literal{val=Size};
_ ->
I
end;
-simplify(#b_set{op={bif,is_map_key},args=[Key,Map]}=I, Ts) ->
+simplify(#b_set{op={bif,is_map_key},args=[Key,Map]}=I, Ts, _Ds) ->
case normalized_type(Map, Ts) of
#t_map{} ->
I#b_set{op=has_map_field,args=[Map,Key]};
_ ->
I
end;
-simplify(#b_set{op={bif,Op0},args=Args}=I, Ts) when Op0 =:= '==';
- Op0 =:= '/=' ->
- Types = normalized_types(Args, Ts),
- EqEq0 = case {beam_types:meet(Types),beam_types:join(Types)} of
- {none,any} -> true;
- {#t_integer{},#t_integer{}} -> true;
- {#t_float{},#t_float{}} -> true;
- {#t_bitstring{},_} -> true;
- {#t_atom{},_} -> true;
- {_,_} -> false
- end,
- EqEq = EqEq0 orelse any_non_numeric_argument(Args, Ts),
+simplify(#b_set{op={bif,Op0},args=[A,B]}=I, Ts, Ds) when Op0 =:= '==';
+ Op0 =:= '/=' ->
+ EqEq = case {number_type(A, Ts),number_type(B, Ts)} of
+ {none,_} ->
+ %% The LHS does not contain any numbers. A strict
+ %% test is always safe.
+ true;
+ {_,none} ->
+ %% The RHS does not contain any numbers. A strict
+ %% test is always safe.
+ true;
+ {#t_integer{},#t_integer{}} ->
+ %% Both side contain integers but no floats.
+ true;
+ {#t_float{},#t_float{}} ->
+ %% Both side contain floats but no integers.
+ true;
+ {_,_} ->
+ false
+ end,
case EqEq of
true ->
Op = case Op0 of
'==' -> '=:=';
'/=' -> '=/='
end,
- simplify(I#b_set{op={bif,Op}}, Ts);
+ simplify(I#b_set{op={bif,Op}}, Ts, Ds);
false ->
- eval_bif(I, Ts)
+ eval_bif(I, Ts, Ds)
end;
-simplify(#b_set{op={bif,'=:='},args=[Same,Same]}, _Ts) ->
+simplify(#b_set{op={bif,'=:='},args=[Same,Same]}, _Ts, _Ds) ->
#b_literal{val=true};
-simplify(#b_set{op={bif,'=:='},args=[LHS,RHS]}=I, Ts) ->
+simplify(#b_set{op={bif,'=:='},args=[LHS,RHS]}=I, Ts, Ds) ->
LType = concrete_type(LHS, Ts),
RType = concrete_type(RHS, Ts),
case beam_types:meet(LType, RType) of
@@ -1145,12 +1213,12 @@ simplify(#b_set{op={bif,'=:='},args=[LHS,RHS]}=I, Ts) ->
%% comparison operator (such as >=) that can be
%% translated to test instruction, this
%% optimization will eliminate one instruction.
- simplify(I#b_set{op={bif,'not'},args=[LHS]}, Ts);
+ simplify(I#b_set{op={bif,'not'},args=[LHS]}, Ts, Ds);
{_,_} ->
- eval_bif(I, Ts)
+ eval_bif(I, Ts, Ds)
end
end;
-simplify(#b_set{op={bif,is_list},args=[Src]}=I0, Ts) ->
+simplify(#b_set{op={bif,is_list},args=[Src]}=I0, Ts, Ds) ->
case concrete_type(Src, Ts) of
#t_union{list=#t_cons{}} ->
I = I0#b_set{op=is_nonempty_list,args=[Src]},
@@ -1161,17 +1229,26 @@ simplify(#b_set{op={bif,is_list},args=[Src]}=I0, Ts) ->
#t_union{list=nil} ->
I0#b_set{op={bif,'=:='},args=[Src,#b_literal{val=[]}]};
_ ->
- eval_bif(I0, Ts)
+ eval_bif(I0, Ts, Ds)
end;
-simplify(#b_set{op={bif,Op},args=Args}=I, Ts) ->
+simplify(#b_set{op={bif,Op},args=[Term]}=I, Ts, _Ds)
+ when Op =:= trunc; Op =:= round; Op =:= ceil; Op =:= floor ->
+ case normalized_type(Term, Ts) of
+ #t_integer{} -> Term;
+ _ -> I
+ end;
+simplify(#b_set{op={bif,Op},args=Args}=I, Ts, Ds) ->
Types = normalized_types(Args, Ts),
case is_float_op(Op, Types) of
false ->
- eval_bif(I, Ts);
+ eval_bif(I, Ts, Ds);
true ->
AnnoArgs = [anno_float_arg(A) || A <- Types],
- eval_bif(beam_ssa:add_anno(float_op, AnnoArgs, I), Ts)
+ eval_bif(beam_ssa:add_anno(float_op, AnnoArgs, I), Ts, Ds)
end;
+simplify(I, Ts, _Ds) ->
+ simplify(I, Ts).
+
simplify(#b_set{op=bs_extract,args=[Ctx]}=I, Ts) ->
case concrete_type(Ctx, Ts) of
#t_bitstring{} ->
@@ -1306,6 +1383,26 @@ simplify(#b_set{op=peek_message,args=[#b_literal{val=Val}]}=I, _Ts) ->
simplify(#b_set{op=recv_marker_clear,args=[#b_literal{}]}, _Ts) ->
%% Not a receive marker: see the 'peek_message' case.
#b_literal{val=none};
+simplify(#b_set{op=update_tuple,args=[Src | Updates]}=I, Ts) ->
+ case {normalized_type(Src, Ts), update_tuple_highest_index(Updates, -1)} of
+ {#t_tuple{exact=true,size=Size}, Highest} when Highest =< Size,
+ Size < 512 ->
+ Args = [#b_literal{val=reuse},
+ #b_literal{val=Size},
+ Src | Updates],
+ simplify(I#b_set{op=update_record,args=Args}, Ts);
+ {_, _} ->
+ I
+ end;
+simplify(#b_set{op=update_record,args=[Hint0, Size, Src | Updates0]}=I, Ts) ->
+ case simplify_update_record(Src, Hint0, Updates0, Ts) of
+ {changed, _, []} ->
+ Src;
+ {changed, Hint, [_|_]=Updates} ->
+ I#b_set{op=update_record,args=[Hint, Size, Src | Updates]};
+ unchanged ->
+ I
+ end;
simplify(I, _Ts) ->
I.
@@ -1345,14 +1442,14 @@ will_succeed_1(#b_set{op=bs_start_match,args=[_, Arg]}, _Src, Ts) ->
end;
will_succeed_1(#b_set{op={bif,Bif},args=BifArgs}, _Src, Ts) ->
- ArgTypes = normalized_types(BifArgs, Ts),
+ ArgTypes = concrete_types(BifArgs, Ts),
beam_call_types:will_succeed(erlang, Bif, ArgTypes);
will_succeed_1(#b_set{op=call,
args=[#b_remote{mod=#b_literal{val=Mod},
name=#b_literal{val=Func}} |
CallArgs]},
_Src, Ts) ->
- ArgTypes = normalized_types(CallArgs, Ts),
+ ArgTypes = concrete_types(CallArgs, Ts),
beam_call_types:will_succeed(Mod, Func, ArgTypes);
will_succeed_1(#b_set{op=get_hd}, _Src, _Ts) ->
@@ -1363,8 +1460,14 @@ will_succeed_1(#b_set{op=has_map_field}, _Src, _Ts) ->
yes;
will_succeed_1(#b_set{op=get_tuple_element}, _Src, _Ts) ->
yes;
-will_succeed_1(#b_set{op=put_tuple}, _Src, _Ts) ->
+will_succeed_1(#b_set{op=update_tuple,args=[Tuple | Updates]}, _Src, Ts) ->
+ TupleType = concrete_type(Tuple, Ts),
+ HighestIndex = update_tuple_highest_index(Updates, -1),
+ Args = [beam_types:make_integer(HighestIndex), TupleType, any],
+ beam_call_types:will_succeed(erlang, setelement, Args);
+will_succeed_1(#b_set{op=update_record}, _Src, _Ts) ->
yes;
+
will_succeed_1(#b_set{op=bs_create_bin}, _Src, _Ts) ->
%% Intentionally don't try to determine whether construction will
%% fail. Construction is unlikely to fail, and if it fails, the
@@ -1401,6 +1504,46 @@ will_succeed_1(#b_set{op=wait_timeout}, _Src, _Ts) ->
will_succeed_1(#b_set{}, _Src, _Ts) ->
'maybe'.
+simplify_update_record(Src, Hint0, Updates, Ts) ->
+ case sur_1(Updates, concrete_type(Src, Ts), Ts, Hint0, []) of
+ {Hint0, []} ->
+ unchanged;
+ {Hint, Skipped} ->
+ {changed, Hint, sur_skip(Updates, Skipped)}
+ end.
+
+sur_1([Index, Arg | Updates], RecordType, Ts, Hint, Skipped) ->
+ FieldType = concrete_type(Arg, Ts),
+ IndexType = concrete_type(Index, Ts),
+ Singleton = beam_types:is_singleton_type(FieldType),
+ case beam_call_types:types(erlang, element, [IndexType, RecordType]) of
+ {FieldType, _, _} when Singleton ->
+ %% We can skip setting fields when we KNOW that they already have
+ %% the value we're trying to set.
+ sur_1(Updates, RecordType, Ts, Hint, Skipped ++ [Index]);
+ {ElementType, _, _} ->
+ case beam_types:meet(FieldType, ElementType) of
+ none ->
+ %% The element at the index can never have the same value
+ %% as the value we're trying to set. Tell the instruction
+ %% not to bother checking whether it's possible to reuse
+ %% the source.
+ sur_1(Updates, RecordType, Ts,
+ #b_literal{val=copy}, Skipped);
+ _ ->
+ sur_1(Updates, RecordType, Ts, Hint, Skipped)
+ end
+ end;
+sur_1([], _RecordType, _Ts, Hint, Skipped) ->
+ {Hint, Skipped}.
+
+sur_skip([Index, _Arg | Updates], [Index | Skipped]) ->
+ sur_skip(Updates, Skipped);
+sur_skip([Index, Arg | Updates], Skipped) ->
+ [Index, Arg | sur_skip(Updates, Skipped)];
+sur_skip([], []) ->
+ [].
+
simplify_is_record(I, #t_tuple{exact=Exact,
size=Size,
elements=Es},
@@ -1489,17 +1632,6 @@ simplify_remote_call(erlang, throw, [Term], Ts, I) ->
beam_ssa:add_anno(thrown_type, Type, I);
simplify_remote_call(erlang, '++', [#b_literal{val=[]},Tl], _Ts, _I) ->
Tl;
-simplify_remote_call(erlang, setelement,
- [#b_literal{val=Pos},
- #b_literal{val=Tuple},
- #b_var{}=Value], _Ts, I)
- when is_integer(Pos), 1 =< Pos, Pos =< tuple_size(Tuple) ->
- %% Position is a literal integer and the shape of the
- %% tuple is known.
- Els0 = [#b_literal{val=El} || El <- tuple_to_list(Tuple)],
- {Bef,[_|Aft]} = split(Pos - 1, Els0),
- Els = Bef ++ [Value|Aft],
- I#b_set{op=put_tuple,args=Els};
simplify_remote_call(Mod, Name, Args, _Ts, I) ->
case erl_bifs:is_pure(Mod, Name, length(Args)) of
true ->
@@ -1533,51 +1665,58 @@ simplify_pure_call(Mod, Name, Args0, I) ->
end
end.
-any_non_numeric_argument([#b_literal{val=Lit}|_], _Ts) ->
- is_non_numeric(Lit);
-any_non_numeric_argument([#b_var{}=V|T], Ts) ->
- is_non_numeric_type(concrete_type(V, Ts)) orelse
- any_non_numeric_argument(T, Ts);
-any_non_numeric_argument([], _Ts) -> false.
-
-is_non_numeric([H|T]) ->
- is_non_numeric(H) andalso is_non_numeric(T);
-is_non_numeric(Tuple) when is_tuple(Tuple) ->
- is_non_numeric_tuple(Tuple, tuple_size(Tuple));
-is_non_numeric(Map) when is_map(Map) ->
- %% Starting from OTP 18, map keys are compared using `=:=`.
- %% Therefore, we only need to check that the values in the map are
- %% non-numeric. (Support for compiling BEAM files for OTP releases
- %% older than OTP 18 has been dropped.)
- is_non_numeric(maps:values(Map));
-is_non_numeric(Num) when is_number(Num) ->
- false;
-is_non_numeric(_) -> true.
-
-is_non_numeric_tuple(Tuple, El) when El >= 1 ->
- is_non_numeric(element(El, Tuple)) andalso
- is_non_numeric_tuple(Tuple, El-1);
-is_non_numeric_tuple(_Tuple, 0) -> true.
-
-is_non_numeric_type(#t_atom{}) -> true;
-is_non_numeric_type(#t_bitstring{}) -> true;
-is_non_numeric_type(#t_cons{type=Type,terminator=Terminator}) ->
- is_non_numeric_type(Type) andalso is_non_numeric_type(Terminator);
-is_non_numeric_type(#t_list{type=Type,terminator=Terminator}) ->
- is_non_numeric_type(Type) andalso is_non_numeric_type(Terminator);
-is_non_numeric_type(#t_map{super_value=Value}) ->
- is_non_numeric_type(Value);
-is_non_numeric_type(nil) -> true;
-is_non_numeric_type(#t_tuple{size=Size,exact=true,elements=Types})
- when map_size(Types) =:= Size ->
- is_non_numeric_tuple_type(Size, Types);
-is_non_numeric_type(_) -> false.
-
-is_non_numeric_tuple_type(0, _Types) ->
- true;
-is_non_numeric_tuple_type(Pos, Types) ->
- is_non_numeric_type(map_get(Pos, Types)) andalso
- is_non_numeric_tuple_type(Pos - 1, Types).
+number_type(V, Ts) ->
+ number_type_1(concrete_type(V, Ts)).
+
+number_type_1(any) ->
+ #t_number{};
+number_type_1(Type) ->
+ N = beam_types:meet(Type, #t_number{}),
+
+ List = case beam_types:meet(Type, #t_list{}) of
+ #t_cons{type=Head,terminator=Tail} ->
+ beam_types:join(number_type_1(Head), number_type_1(Tail));
+ #t_list{type=Head,terminator=Tail} ->
+ beam_types:join(number_type_1(Head), number_type_1(Tail));
+ nil ->
+ none;
+ none ->
+ none
+ end,
+
+ Tup = case beam_types:meet(Type, #t_tuple{}) of
+ #t_tuple{size=Size,exact=true,elements=ElemTypes}
+ when map_size(ElemTypes) =:= Size ->
+ beam_types:join([number_type_1(ET) ||
+ ET <- maps:values(ElemTypes)] ++ [none]);
+ #t_tuple{} ->
+ #t_number{};
+ #t_union{} ->
+ #t_number{};
+ none ->
+ none
+ end,
+
+ Map = case beam_types:meet(Type, #t_map{}) of
+ #t_map{super_value=MapValue} ->
+ %% Starting from OTP 18, map keys are compared using
+ %% `=:=`. Therefore, we only need to use the values
+ %% in the map.
+ MapValue;
+ none ->
+ none
+ end,
+
+ Fun = case beam_types:meet(Type, #t_fun{}) of
+ #t_fun{} ->
+ %% The environment could contain a number. We don't
+ %% keep track of the environment in #t_fun{}.
+ #t_number{};
+ none ->
+ none
+ end,
+
+ beam_types:join([N,List,Tup,Map,Fun]).
make_literal_list(Args) ->
make_literal_list(Args, []).
@@ -1595,7 +1734,7 @@ is_safe_bool_op([LHS, RHS], Ts) ->
beam_types:is_boolean_type(LType) andalso
beam_types:is_boolean_type(RType).
-eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts) ->
+eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts, Ds) ->
Arity = length(Args),
case erl_bifs:is_pure(erlang, Bif, Arity) of
false ->
@@ -1603,7 +1742,7 @@ eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts) ->
true ->
case make_literal_list(Args) of
none ->
- eval_bif_1(I, Bif, concrete_types(Args, Ts));
+ eval_bif_1(I, Bif, Ts, Ds);
LitArgs ->
try apply(erlang, Bif, LitArgs) of
Val -> #b_literal{val=Val}
@@ -1614,8 +1753,8 @@ eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts) ->
end
end.
-eval_bif_1(I, Op, Types) ->
- case Types of
+eval_bif_1(#b_set{args=Args}=I, Op, Ts, Ds) ->
+ case concrete_types(Args, Ts) of
[#t_integer{},#t_integer{elements={0,0}}] when Op =:= 'bor'; Op =:= 'bxor' ->
#b_set{args=[Result,_]} = I,
Result;
@@ -1637,10 +1776,30 @@ eval_bif_1(I, Op, Types) ->
false ->
I
end;
+ [#t_integer{},#t_integer{}] ->
+ reassociate(I, Ts, Ds);
_ ->
I
end.
+reassociate(#b_set{op={bif,OpB},args=[#b_var{}=V0,#b_literal{val=B0}]}=I, _Ts, Ds)
+ when OpB =:= '+'; OpB =:= '-' ->
+ case map_get(V0, Ds) of
+ #b_set{op={bif,OpA},args=[#b_var{}=V,#b_literal{val=A0}]}
+ when OpA =:= '+'; OpA =:= '-' ->
+ A = erlang:OpA(A0),
+ B = erlang:OpB(B0),
+ case A + B of
+ Combined when Combined < 0 ->
+ I#b_set{op={bif,'-'},args=[V,#b_literal{val=-Combined}]};
+ Combined when Combined >= 0 ->
+ I#b_set{op={bif,'+'},args=[V,#b_literal{val=Combined}]}
+ end;
+ #b_set{} ->
+ I
+ end;
+reassociate(I, _Ts, _Ds) -> I.
+
simplify_args(Args, Ts, Sub) ->
[simplify_arg(Arg, Ts, Sub) || Arg <- Args].
@@ -1904,7 +2063,7 @@ update_types(#b_set{op=Op,dst=Dst,anno=Anno,args=Args}, Ts, Ds) ->
Ts#{ Dst => T }.
type({bif,Bif}, Args, _Anno, Ts, _Ds) ->
- ArgTypes = normalized_types(Args, Ts),
+ ArgTypes = concrete_types(Args, Ts),
case beam_call_types:types(erlang, Bif, ArgTypes) of
{any, _, _} ->
case {Bif, Args} of
@@ -1917,11 +2076,25 @@ type({bif,Bif}, Args, _Anno, Ts, _Ds) ->
{RetType, _, _} ->
RetType
end;
-type(bs_create_bin, _Args, _Anno, _Ts, _Ds) ->
- #t_bitstring{};
-type(bs_extract, [Ctx], _Anno, _Ts, Ds) ->
- #b_set{op=bs_match,args=Args} = map_get(Ctx, Ds),
- bs_match_type(Args);
+type(bs_create_bin, Args, _Anno, Ts, _Ds) ->
+ SizeUnit = bs_size_unit(Args, Ts),
+ Appendable = case Args of
+ [#b_literal{val=private_append}|_] ->
+ true;
+ [#b_literal{val=append},_,Var|_] ->
+ case argument_type(Var, Ts) of
+ #t_bitstring{appendable=A} -> A;
+ #t_union{other=#t_bitstring{appendable=A}} -> A;
+ _ -> false
+ end;
+ _ ->
+ false
+ end,
+ #t_bitstring{size_unit=SizeUnit,appendable=Appendable};
+type(bs_extract, [Ctx], _Anno, Ts, Ds) ->
+ #b_set{op=bs_match,
+ args=[#b_literal{val=Type}, _OrigCtx | Args]} = map_get(Ctx, Ds),
+ bs_match_type(Type, Args, Ts);
type(bs_start_match, [_, Src], _Anno, Ts, _Ds) ->
case beam_types:meet(#t_bs_matchable{}, concrete_type(Src, Ts)) of
none ->
@@ -1940,23 +2113,28 @@ type(bs_match, [#b_literal{val=binary}, Ctx, _Flags,
OpType = #t_bs_context{tail_unit=OpUnit},
beam_types:meet(CtxType, OpType);
-type(bs_match, Args, _Anno, Ts, _Ds) ->
- [_, Ctx | _] = Args,
-
- %% Matches advance the current position without testing the tail unit. We
- %% try to retain unit information by taking the GCD of our current unit and
- %% the increments we know the match will advance by.
- #t_bs_context{tail_unit=CtxUnit} = concrete_type(Ctx, Ts),
- OpUnit = bs_match_stride(Args, Ts),
+type(bs_match, Args0, _Anno, Ts, _Ds) ->
+ [#b_literal{val=Type}, Ctx | Args] = Args0, %Assertion.
+ case bs_match_type(Type, Args, Ts) of
+ none ->
+ none;
+ _ ->
+ %% Matches advance the current position without testing the tail
+ %% unit. We try to retain unit information by taking the GCD of our
+ %% current unit and the increments we know the match will advance
+ %% by.
+ #t_bs_context{tail_unit=CtxUnit} = concrete_type(Ctx, Ts),
+ OpUnit = bs_match_stride(Type, Args, Ts),
- #t_bs_context{tail_unit=gcd(OpUnit, CtxUnit)};
+ #t_bs_context{tail_unit=gcd(OpUnit, CtxUnit)}
+ end;
type(bs_get_tail, [Ctx], _Anno, Ts, _Ds) ->
#t_bs_context{tail_unit=Unit} = concrete_type(Ctx, Ts),
#t_bitstring{size_unit=Unit};
type(call, [#b_remote{mod=#b_literal{val=Mod},
name=#b_literal{val=Name}}|Args], _Anno, Ts, _Ds)
when is_atom(Mod), is_atom(Name) ->
- ArgTypes = normalized_types(Args, Ts),
+ ArgTypes = concrete_types(Args, Ts),
{RetType, _, _} = beam_call_types:types(Mod, Name, ArgTypes),
RetType;
type(call, [#b_remote{mod=Mod,name=Name} | _Args], _Anno, Ts, _Ds) ->
@@ -2012,8 +2190,10 @@ type(get_tl, [Src], _Anno, Ts, _Ds) ->
SrcType = #t_cons{} = normalized_type(Src, Ts), %Assertion.
{RetType, _, _} = beam_call_types:types(erlang, tl, [SrcType]),
RetType;
-type(get_map_element, [_, _]=Args0, _Anno, Ts, _Ds) ->
- [#t_map{}=Map, Key] = normalized_types(Args0, Ts), %Assertion.
+type(get_map_element, [Map0, Key0], _Anno, Ts, _Ds) ->
+ Map = concrete_type(Map0, Ts),
+ Key = concrete_type(Key0, Ts),
+ %% Note the reversed argument order!
{RetType, _, _} = beam_call_types:types(erlang, map_get, [Key, Map]),
RetType;
type(get_tuple_element, [Tuple, Offset], _Anno, _Ts, _Ds) ->
@@ -2026,9 +2206,12 @@ type(get_tuple_element, [Tuple, Offset], _Anno, _Ts, _Ds) ->
true = Index =< Size, %Assertion.
beam_types:get_tuple_element(Index, Es)
end;
-type(has_map_field, [_, _]=Args0, _Anno, Ts, _Ds) ->
- [#t_map{}=Map, Key] = normalized_types(Args0, Ts), %Assertion.
+type(has_map_field, [Map0, Key0], _Anno, Ts, _Ds) ->
+ Map = concrete_type(Map0, Ts),
+ Key = concrete_type(Key0, Ts),
+ %% Note the reversed argument order!
{RetType, _, _} = beam_call_types:types(erlang, is_map_key, [Key, Map]),
+ true = none =/= RetType, %Assertion.
RetType;
type(is_nonempty_list, [_], _Anno, _Ts, _Ds) ->
beam_types:make_boolean();
@@ -2071,12 +2254,33 @@ type(raw_raise, [Class, _, _], _Anno, Ts, _Ds) ->
end;
type(resume, [_, _], _Anno, _Ts, _Ds) ->
none;
+type(update_record, [_Hint, _Size, Src | Updates], _Anno, Ts, _Ds) ->
+ update_tuple_type(Updates, Src, Ts);
+type(update_tuple, [Src | Updates], _Anno, Ts, _Ds) ->
+ update_tuple_type(Updates, Src, Ts);
type(wait_timeout, [#b_literal{val=infinity}], _Anno, _Ts, _Ds) ->
%% Waits forever, never reaching the 'after' block.
beam_types:make_atom(false);
+type(bs_init_writable, [_Size], _, _, _) ->
+ beam_types:make_type_from_value(<<>>);
type(_, _, _, _, _) ->
any.
+update_tuple_type([_|_]=Updates0, Src, Ts) ->
+ Updates = update_tuple_type_1(Updates0, Ts),
+ beam_types:update_tuple(concrete_type(Src, Ts), Updates).
+
+update_tuple_type_1([#b_literal{val=Index}, Value | Updates], Ts) ->
+ [{Index, concrete_type(Value, Ts)} | update_tuple_type_1(Updates, Ts)];
+update_tuple_type_1([], _Ts) ->
+ [].
+
+update_tuple_highest_index([#b_literal{val=Index}, _Value | Updates], Acc) ->
+ update_tuple_highest_index(Updates, max(Index, Acc));
+update_tuple_highest_index([], Acc) ->
+ true = Acc >= 1, %Assertion.
+ Acc.
+
join_tuple_elements(Tuple) ->
join_tuple_elements(tuple_size(Tuple), Tuple, none).
@@ -2088,29 +2292,74 @@ join_tuple_elements(I, Tuple, Type0) ->
join_tuple_elements(I - 1, Tuple, Type).
put_map_type(Map, Ss, Ts) ->
- pmt_1(Ss, Ts, normalized_type(Map, Ts)).
+ pmt_1(Ss, Ts, concrete_type(Map, Ts)).
pmt_1([Key0, Value0 | Ss], Ts, Acc0) ->
- Key = normalized_type(Key0, Ts),
- Value = normalized_type(Value0, Ts),
+ Key = concrete_type(Key0, Ts),
+ Value = concrete_type(Value0, Ts),
{Acc, _, _} = beam_call_types:types(maps, put, [Key, Value, Acc0]),
pmt_1(Ss, Ts, Acc);
pmt_1([], _Ts, Acc) ->
Acc.
+bs_size_unit(Args, Ts) ->
+ bs_size_unit(Args, Ts, 0, 256).
+
+bs_size_unit([#b_literal{val=Type},#b_literal{val=[U1|_]},Value,SizeTerm|Args],
+ Ts, FixedSize, Unit) ->
+ case {Type,Value,SizeTerm} of
+ {_,#b_literal{val=Bs},#b_literal{val=all}} when is_bitstring(Bs) ->
+ %% Add element of known size.
+ Size = bit_size(Bs) + FixedSize,
+ bs_size_unit(Args, Ts, Size, Unit);
+ {_,_,#b_literal{val=all}} ->
+ case concrete_type(Value, Ts) of
+ #t_bitstring{size_unit=U2} ->
+ %% Adding a bitstring.
+ %% TODO: Can U1 be anything but 1?
+ U = max(U1, U2),
+ bs_size_unit(Args, Ts, FixedSize, gcd(U, Unit));
+ _ ->
+ %% Adding something which isn't a bitstring
+ bs_size_unit(Args, Ts, FixedSize, gcd(U1, Unit))
+ end;
+ {utf8,_,_} ->
+ %% Add at least 8 bits, maybe more.
+ bs_size_unit(Args, Ts, 8 + FixedSize, gcd(8, Unit));
+ {utf16,_,_} ->
+ %% Add at least 16 bits, maybe more.
+ bs_size_unit(Args, Ts, 16 + FixedSize, gcd(16, Unit));
+ {utf32,_,_} ->
+ %% Compared to utf8 and utf16 this is a fixed size.
+ bs_size_unit(Args, Ts, 32 + FixedSize, Unit);
+ {_,_,_} ->
+ case concrete_type(SizeTerm, Ts) of
+ #t_integer{elements={Size1, Size1}}
+ when is_integer(Size1), is_integer(U1), Size1 >= 0 ->
+ EffectiveSize = Size1 * U1,
+ %% Adding a fixed size element
+ bs_size_unit(Args, Ts, EffectiveSize + FixedSize, Unit);
+ _ when is_integer(U1) ->
+ %% Add element with known unit.
+ bs_size_unit(Args, Ts, FixedSize, gcd(U1, Unit));
+ _ ->
+ %% Add element without known size or unit.
+ bs_size_unit(Args, Ts, FixedSize, gcd(1, Unit))
+ end
+ end;
+bs_size_unit([], _Ts, FixedSize, Unit) ->
+ gcd(FixedSize, Unit).
+
%% We seldom know how far a match operation may advance, but we can often tell
%% which increment it will advance by.
-bs_match_stride([#b_literal{val=Type} | Args], Ts) ->
- bs_match_stride(Type, Args, Ts).
-
-bs_match_stride(_, [_,_,Size,#b_literal{val=Unit}], Ts) ->
+bs_match_stride(_, [_,Size,#b_literal{val=Unit}], Ts) ->
case concrete_type(Size, Ts) of
#t_integer{elements={Sz, Sz}} when is_integer(Sz) ->
Sz * Unit;
_ ->
Unit
end;
-bs_match_stride(string, [_,#b_literal{val=String}], _) ->
+bs_match_stride(string, [#b_literal{val=String}], _) ->
bit_size(String);
bs_match_stride(utf8, _, _) ->
8;
@@ -2123,42 +2372,50 @@ bs_match_stride(_, _, _) ->
-define(UNICODE_MAX, (16#10FFFF)).
-bs_match_type([#b_literal{val=Type}|Args]) ->
- bs_match_type(Type, Args).
-
-bs_match_type(binary, Args) ->
- [_,_,_,#b_literal{val=U}] = Args,
+bs_match_type(binary, Args, _Ts) ->
+ [_,_,#b_literal{val=U}] = Args,
#t_bitstring{size_unit=U};
-bs_match_type(float, _) ->
+bs_match_type(float, _Args, _Ts) ->
#t_float{};
-bs_match_type(integer, Args) ->
- case Args of
- [_,
- #b_literal{val=Flags},
- #b_literal{val=Size},
- #b_literal{val=Unit}] when Size * Unit < 64 ->
- NumBits = Size * Unit,
- Max = (1 bsl NumBits) - 1,
- case member(unsigned, Flags) of
- true ->
- beam_types:make_integer(0, Max);
- false ->
- Min = -(Max + 1),
- beam_types:make_integer(Min, Max)
+bs_match_type(integer, Args, Ts) ->
+ [#b_literal{val=Flags},Size,#b_literal{val=Unit}] = Args,
+ case beam_types:meet(concrete_type(Size, Ts), #t_integer{}) of
+ #t_integer{elements=Bounds} ->
+ case beam_bounds:bounds('*', Bounds, {Unit, Unit}) of
+ {_, MaxBits} when is_integer(MaxBits),
+ MaxBits >= 1,
+ MaxBits =< 64 ->
+ case member(unsigned, Flags) of
+ true ->
+ Max = (1 bsl MaxBits) - 1,
+ beam_types:make_integer(0, Max);
+ false ->
+ Max = (1 bsl (MaxBits - 1)) - 1,
+ Min = -(Max + 1),
+ beam_types:make_integer(Min, Max)
+ end;
+ {_, 0} ->
+ beam_types:make_integer(0);
+ _ ->
+ case member(unsigned, Flags) of
+ true -> #t_integer{elements={0,'+inf'}};
+ false -> #t_integer{}
+ end
end;
- [_|_] ->
- #t_integer{}
- end;
-bs_match_type(skip, _) ->
- any;
-bs_match_type(string, _) ->
- any;
-bs_match_type(utf8, _) ->
+ none ->
+ none
+ end;
+
+bs_match_type(utf8, _Args, _Ts) ->
beam_types:make_integer(0, ?UNICODE_MAX);
-bs_match_type(utf16, _) ->
+bs_match_type(utf16, _Args, _Ts) ->
beam_types:make_integer(0, ?UNICODE_MAX);
-bs_match_type(utf32, _) ->
- beam_types:make_integer(0, ?UNICODE_MAX).
+bs_match_type(utf32, _Args, _Ts) ->
+ beam_types:make_integer(0, ?UNICODE_MAX);
+bs_match_type(string, _Args, _Ts) ->
+ %% Cannot actually be extracted, but we'll return 'any' to signal that the
+ %% associated `bs_match` may succeed.
+ any.
normalized_types(Values, Ts) ->
[normalized_type(Val, Ts) || Val <- Values].
@@ -2212,12 +2469,7 @@ concrete_type(#b_var{}=Var, Ts) ->
%% subtraction for L will be added to FailTypes.
infer_types_br(#b_var{}=V, Ts, IsTempVar, Ds) ->
- #{V:=#b_set{op=Op,args=Args}} = Ds,
-
- {PosTypes, NegTypes} = infer_type(Op, Args, Ts, Ds),
-
- SuccTs0 = meet_types(PosTypes, Ts),
- FailTs0 = subtract_types(NegTypes, Ts),
+ {SuccTs0, FailTs0} = infer_types_br_1(V, Ts, Ds),
case IsTempVar of
true ->
@@ -2234,6 +2486,146 @@ infer_types_br(#b_var{}=V, Ts, IsTempVar, Ds) ->
{SuccTs, FailTs}
end.
+infer_types_br_1(V, Ts, Ds) ->
+ #{V:=#b_set{op=Op0,args=Args}} = Ds,
+
+ case inv_relop(Op0) of
+ none ->
+ %% Not a relational operator.
+ {PosTypes, NegTypes} = infer_type(Op0, Args, Ts, Ds),
+ SuccTs = meet_types(PosTypes, Ts),
+ FailTs = subtract_types(NegTypes, Ts),
+ {SuccTs, FailTs};
+ InvOp ->
+ %% This is an relational operator.
+ {bif,Op} = Op0,
+
+ %% Infer the types for both sides of operator succceding.
+ Types = concrete_types(Args, Ts),
+ TrueTypes0 = infer_relop(Op, Args, Types, Ds),
+ TrueTypes = meet_types(TrueTypes0, Ts),
+
+ %% Infer the types for both sides of operator failing.
+ FalseTypes0 = infer_relop(InvOp, Args, Types, Ds),
+ FalseTypes = meet_types(FalseTypes0, Ts),
+
+ {TrueTypes, FalseTypes}
+ end.
+
+infer_relop('=:=', [LHS,RHS], [LType,RType], Ds) ->
+ EqTypes = infer_eq_type(map_get(LHS, Ds), RType),
+
+ %% As an example, assume that L1 is known to be 'list', and L2 is
+ %% known to be 'cons'. Then if 'L1 =:= L2' evaluates to 'true', it
+ %% can be inferred that L1 is 'cons' (the meet of 'cons' and
+ %% 'list').
+ Type = beam_types:meet(LType, RType),
+ [{LHS,Type},{RHS,Type}] ++ EqTypes;
+infer_relop(Op, Args, Types, _Ds) ->
+ infer_relop(Op, Args, Types).
+
+infer_relop('=/=', [LHS,RHS], [LType,RType]) ->
+ %% We must be careful with types inferred from '=/='.
+ %%
+ %% For example, if we have L =/= [a], we must not subtract 'cons'
+ %% from the type of L. In general, subtraction is only safe if
+ %% the type being subtracted is singled-valued, for example if it
+ %% is [] or the the atom 'true'.
+ %%
+ %% Note that we subtract the left-hand type from the right-hand
+ %% value and vice versa. We must not subtract the meet of the two
+ %% as it may be too specific. See beam_type_SUITE:type_subtraction/1
+ %% for details.
+ [{V,beam_types:subtract(ThisType, OtherType)} ||
+ {V, ThisType, OtherType} <- [{RHS, RType, LType}, {LHS, LType, RType}],
+ beam_types:is_singleton_type(OtherType)];
+infer_relop(Op, [Arg1,Arg2], Types0) ->
+ case infer_relop(Op, Types0) of
+ any ->
+ %% Both operands lacked ranges.
+ [];
+ {NewType1,NewType2} ->
+ [{Arg1,NewType1},{Arg2,NewType2}]
+ end.
+
+infer_relop(Op, [#t_integer{elements=R1},
+ #t_integer{elements=R2}]) ->
+ case beam_bounds:infer_relop_types(Op, R1, R2) of
+ {NewR1,NewR2} ->
+ {#t_integer{elements=NewR1},
+ #t_integer{elements=NewR2}};
+ none ->
+ {none, none};
+ any ->
+ any
+ end;
+infer_relop(Op0, [Type1,Type2]) ->
+ Op = case Op0 of
+ '<' -> '=<';
+ '>' -> '>=';
+ _ -> Op0
+ end,
+ case {infer_get_range(Type1),infer_get_range(Type2)} of
+ {unknown,unknown} ->
+ any;
+ {unknown,_}=R ->
+ infer_relop_any(Op, R);
+ {_,unknown}=R ->
+ infer_relop_any(Op, R);
+ {R1,R2} ->
+ %% Both operands are numeric types.
+ case beam_bounds:infer_relop_types(Op, R1, R2) of
+ {NewR1,NewR2} ->
+ {#t_number{elements=NewR1},
+ #t_number{elements=NewR2}};
+ none ->
+ {none, none};
+ any ->
+ any
+ end
+ end.
+
+infer_relop_any('=<', {unknown,any}) ->
+ %% Since numbers are smaller than any other terms, the LHS must be
+ %% a number if operator '=<' evaluates to true.
+ {#t_number{}, any};
+infer_relop_any('=<', {unknown,{_,Max}}) ->
+ {make_number({'-inf',Max}), any};
+infer_relop_any('>=', {any,unknown}) ->
+ {any, #t_number{}};
+infer_relop_any('>=', {{_,Max},unknown}) ->
+ {any, make_number({'-inf',Max})};
+infer_relop_any('>=', {unknown,{Min,_}}) when is_integer(Min) ->
+ %% LHS can be any term, including number, but we can figure out
+ %% the minimal possible number.
+ N = #t_number{elements={'-inf',Min}},
+ {beam_types:subtract(any, N), any};
+infer_relop_any('=<', {{Min,_},unknown}) when is_integer(Min) ->
+ N = #t_number{elements={'-inf',Min}},
+ {any, beam_types:subtract(any, N)};
+infer_relop_any(_Op, _R) ->
+ any.
+
+make_number({'-inf','+inf'}) ->
+ #t_number{};
+make_number({_,_}=R) ->
+ #t_number{elements=R}.
+
+inv_relop({bif,Op}) -> inv_relop_1(Op);
+inv_relop(_) -> none.
+
+inv_relop_1('<') -> '>=';
+inv_relop_1('=<') -> '>';
+inv_relop_1('>=') -> '<';
+inv_relop_1('>') -> '=<';
+inv_relop_1('=:=') -> '=/=';
+inv_relop_1('=/=') -> '=:=';
+inv_relop_1(_) -> none.
+
+infer_get_range(#t_integer{elements=R}) -> R;
+infer_get_range(#t_number{elements=R}) -> R;
+infer_get_range(_) -> unknown.
+
infer_br_value(_V, _Bool, none) ->
none;
infer_br_value(V, Bool, NewTs) ->
@@ -2247,7 +2639,9 @@ infer_br_value(V, Bool, NewTs) ->
end.
infer_types_switch(V, Lit, Ts0, IsTempVar, Ds) ->
- {PosTypes, _} = infer_type({bif,'=:='}, [V, Lit], Ts0, Ds),
+ Args = [V,Lit],
+ Types = concrete_types(Args, Ts0),
+ PosTypes = infer_relop('=:=', Args, Types, Ds),
Ts = meet_types(PosTypes, Ts0),
case IsTempVar of
true -> ts_remove_var(V, Ts);
@@ -2312,7 +2706,7 @@ infer_type({bif,is_map}, [#b_var{}=Arg], _Ts, _Ds) ->
T = {Arg, #t_map{}},
{[T], [T]};
infer_type({bif,is_number}, [#b_var{}=Arg], _Ts, _Ds) ->
- T = {Arg, number},
+ T = {Arg, #t_number{}},
{[T], [T]};
infer_type({bif,is_pid}, [#b_var{}=Arg], _Ts, _Ds) ->
T = {Arg, pid},
@@ -2326,55 +2720,29 @@ infer_type({bif,is_reference}, [#b_var{}=Arg], _Ts, _Ds) ->
infer_type({bif,is_tuple}, [#b_var{}=Arg], _Ts, _Ds) ->
T = {Arg, #t_tuple{}},
{[T], [T]};
-infer_type({bif,'=:='}, [#b_var{}=LHS,#b_var{}=RHS], Ts, _Ds) ->
- %% As an example, assume that L1 is known to be 'list', and L2 is
- %% known to be 'cons'. Then if 'L1 =:= L2' evaluates to 'true', it can
- %% be inferred that L1 is 'cons' (the meet of 'cons' and 'list').
- LType = concrete_type(LHS, Ts),
- RType = concrete_type(RHS, Ts),
- Type = beam_types:meet(LType, RType),
-
- PosTypes = [{V,Type} || {V, OrigType} <- [{LHS, LType}, {RHS, RType}],
- OrigType =/= Type],
-
- %% We must be careful with types inferred from '=:='.
- %%
- %% If we have seen L =:= [a], we know that L is 'cons' if the
- %% comparison succeeds. However, if the comparison fails, L could
- %% still be 'cons'. Therefore, we must not subtract 'cons' from the
- %% previous type of L.
+infer_type({bif,'and'}, [#b_var{}=LHS,#b_var{}=RHS], Ts, Ds) ->
+ %% When this BIF yields true, we know that both `LHS` and `RHS` are 'true'
+ %% and should infer accordingly, lest we break later optimizations that
+ %% rewrite this BIF to plain control flow.
%%
- %% However, it is safe to subtract a type inferred from '=:=' if
- %% it is single-valued, e.g. if it is [] or the atom 'true'.
- %%
- %% Note that we subtract the left-hand type from the right-hand
- %% value and vice versa. We must not subtract the meet of the two
- %% as it may be too specific. See beam_type_SUITE:type_subtraction/1
- %% for details.
- NegTypes = [T || {_, OtherType}=T <- [{RHS, LType}, {LHS, RType}],
- beam_types:is_singleton_type(OtherType)],
-
- {PosTypes, NegTypes};
-infer_type({bif,'=:='}, [#b_var{}=Src,#b_literal{}=Lit], Ts, Ds) ->
- Def = maps:get(Src, Ds),
- LitType = concrete_type(Lit, Ts),
- PosTypes = [{Src, LitType} | infer_eq_type(Def, LitType)],
-
- %% Subtraction is only safe if LitType is single-valued.
- NegTypes = case beam_types:is_singleton_type(LitType) of
- true -> PosTypes;
- false -> []
- end,
+ %% Note that we can't do anything for the 'false' case as either (or both)
+ %% of the arguments could be false.
+ #{ LHS := #b_set{op=LHSOp,args=LHSArgs},
+ RHS := #b_set{op=RHSOp,args=RHSArgs} } = Ds,
- {PosTypes, NegTypes};
+ LHSTypes = infer_and_type(LHSOp, LHSArgs, Ts, Ds),
+ RHSTypes = infer_and_type(RHSOp, RHSArgs, Ts, Ds),
+
+ True = beam_types:make_atom(true),
+ {[{LHS, True}, {RHS, True}] ++ LHSTypes ++ RHSTypes, []};
infer_type(_Op, _Args, _Ts, _Ds) ->
{[], []}.
infer_success_type({bif,Op}, Args, Ts, _Ds) ->
- ArgTypes = normalized_types(Args, Ts),
+ ArgTypes = concrete_types(Args, Ts),
{_, PosTypes0, CanSubtract} = beam_call_types:types(erlang, Op, ArgTypes),
- PosTypes = [T || {#b_var{},_}=T <- zip(Args, PosTypes0)],
+ PosTypes = zip(Args, PosTypes0),
case CanSubtract of
true -> {PosTypes, PosTypes};
@@ -2415,6 +2783,16 @@ infer_eq_type(#b_set{op=get_tuple_element,
infer_eq_type(_, _) ->
[].
+infer_and_type(Op, Args, Ts, Ds) ->
+ case inv_relop(Op) of
+ none ->
+ {LHSTypes0, _} = infer_type(Op, Args, Ts, Ds),
+ LHSTypes0;
+ _InvOp ->
+ {bif,RelOp} = Op,
+ infer_relop(RelOp, Args, concrete_types(Args, Ts), Ds)
+ end.
+
join_types(Ts, Ts) ->
Ts;
join_types(LHS, RHS) ->
@@ -2448,7 +2826,13 @@ join_types_1([V | Vs], Bigger, Smaller) ->
join_types_1([], _Bigger, Smaller) ->
Smaller.
-meet_types([{V,T0}|Vs], Ts) ->
+meet_types([{#b_literal{}=Lit, T0} | Vs], Ts) ->
+ T1 = concrete_type(Lit, Ts),
+ case beam_types:meet(T0, T1) of
+ none -> none;
+ _ -> meet_types(Vs, Ts)
+ end;
+meet_types([{#b_var{}=V, T0} | Vs], Ts) ->
T1 = concrete_type(V, Ts),
case beam_types:meet(T0, T1) of
none -> none;
@@ -2458,12 +2842,18 @@ meet_types([{V,T0}|Vs], Ts) ->
meet_types([], Ts) ->
Ts.
-subtract_types([{V,T0}|Vs], Ts) ->
+subtract_types([{#b_literal{}=Lit, T0} | Vs], Ts) ->
+ T1 = concrete_type(Lit, Ts),
+ case beam_types:subtract(T0, T1) of
+ none -> none;
+ _ -> subtract_types(Vs, Ts)
+ end;
+subtract_types([{#b_var{}=V, T0}|Vs], Ts) ->
T1 = concrete_type(V, Ts),
case beam_types:subtract(T1, T0) of
none -> none;
T1 -> subtract_types(Vs, Ts);
- T -> subtract_types(Vs, Ts#{ V:= T })
+ T -> subtract_types(Vs, Ts#{V := T})
end;
subtract_types([], Ts) ->
Ts.
diff --git a/lib/compiler/src/beam_trim.erl b/lib/compiler/src/beam_trim.erl
index 4fb141251d..b5ff21bbac 100644
--- a/lib/compiler/src/beam_trim.erl
+++ b/lib/compiler/src/beam_trim.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -212,8 +212,7 @@ is_recipe_viable({_,Trim,Moves}, UsedRegs) ->
expand_recipe({Layout,Trim,Moves}, FrameSize) ->
Is = reverse(Moves, [{trim,Trim,FrameSize-Trim}]),
- Map0 = [{Src,Dst-Trim} || {move,{y,Src},{y,Dst}} <- Moves],
- Map = maps:from_list(Map0),
+ Map = #{Src => Dst - Trim || {move,{y,Src},{y,Dst}} <- Moves},
Remap = {Trim,Map},
case [Y || {kill,Y} <- Layout] of
[] ->
@@ -278,6 +277,12 @@ remap([{make_fun3,F,Index,OldUniq,Dst0,{list,Env0}}|Is], Remap) ->
Dst = remap_arg(Dst0, Remap),
I = {make_fun3,F,Index,OldUniq,Dst,{list,Env}},
[I|remap(Is, Remap)];
+remap([{update_record,Hint,Size,Src0,Dst0,{list,Updates0}}|Is], Remap) ->
+ Updates = remap_args(Updates0, Remap),
+ Src = remap_arg(Src0, Remap),
+ Dst = remap_arg(Dst0, Remap),
+ I = {update_record,Hint,Size,Src,Dst,{list,Updates}},
+ [I|remap(Is, Remap)];
remap([{deallocate,N}|Is], {Trim,_}=Remap) ->
I = {deallocate,N-Trim},
[I|remap(Is, Remap)];
@@ -527,6 +532,12 @@ do_usage([{make_fun3,_,_,_,Dst,{list,Ss}}|Is], Safe, Regs0, Ns0, Acc) ->
Ns = ordsets:union(yregs([Dst]), Ns0),
U = {Regs,Ns},
do_usage(Is, Safe, Regs, Ns, [U|Acc]);
+do_usage([{update_record,_,_,Src,Dst,{list,Ss}}|Is], Safe, Regs0, Ns0, Acc) ->
+ Regs1 = ordsets:del_element(Dst, Regs0),
+ Regs = ordsets:union(Regs1, yregs([Src|Ss])),
+ Ns = ordsets:union(yregs([Dst]), Ns0),
+ U = {Regs,Ns},
+ do_usage(Is, Safe, Regs, Ns, [U|Acc]);
do_usage([{line,_}|Is], Safe, Regs, Ns, Acc) ->
U = {Regs,Ns},
do_usage(Is, Safe, Regs, Ns, [U|Acc]);
diff --git a/lib/compiler/src/beam_types.erl b/lib/compiler/src/beam_types.erl
index b2fef1e7ac..c3bf7c8fae 100644
--- a/lib/compiler/src/beam_types.erl
+++ b/lib/compiler/src/beam_types.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
-define(BEAM_TYPES_INTERNAL, true).
-include("beam_types.hrl").
--import(lists, [foldl/3, reverse/1, usort/1]).
+-import(lists, [foldl/3, mapfoldl/3, reverse/1, usort/1]).
-export([meet/1, meet/2, join/1, join/2, subtract/2]).
@@ -35,7 +35,9 @@
is_singleton_type/1,
normalize/1]).
--export([get_tuple_element/2, set_tuple_element/3]).
+-export([get_tuple_element/2,
+ set_tuple_element/3,
+ update_tuple/2]).
-export([make_type_from_value/1]).
@@ -49,7 +51,7 @@
-export([limit_depth/1]).
--export([decode_ext/1, encode_ext/1]).
+-export([decode_ext/1, encode_ext/1, convert_ext/2]).
%% This is exported to help catch errors in property test generators and is not
%% meant to be used outside of test suites.
@@ -61,7 +63,7 @@
N =:= nil).
-define(IS_NUMBER_TYPE(N),
- N =:= number orelse
+ is_record(N, t_number) orelse
is_record(N, t_float) orelse
is_record(N, t_integer)).
@@ -277,19 +279,17 @@ join_unions(#t_union{atom=AtomA,list=ListA,number=NumberA,
other=lub(OtherA, OtherB)},
shrink_union(Union);
join_unions(#t_union{atom=AtomA}=A, #t_atom{}=B) ->
- A#t_union{atom=lub(AtomA, B)};
+ shrink_union(A#t_union{atom=lub(AtomA, B)});
join_unions(#t_union{list=ListA}=A, B) when ?IS_LIST_TYPE(B) ->
- A#t_union{list=lub(ListA, B)};
+ shrink_union(A#t_union{list=lub(ListA, B)});
join_unions(#t_union{number=NumberA}=A, B) when ?IS_NUMBER_TYPE(B) ->
- A#t_union{number=lub(NumberA, B)};
+ shrink_union(A#t_union{number=lub(NumberA, B)});
join_unions(#t_union{tuple_set=TSetA}=A, #t_tuple{}=B) ->
Set = join_tuple_sets(TSetA, new_tuple_set(B)),
shrink_union(A#t_union{tuple_set=Set});
join_unions(#t_union{other=OtherA}=A, B) ->
- case lub(OtherA, B) of
- any -> any;
- T -> A#t_union{other=T}
- end.
+ T = lub(OtherA, B),
+ shrink_union(A#t_union{other=T}).
join_tuple_sets(A, none) ->
A;
@@ -334,6 +334,47 @@ jts_records([], [{KeyB, B} | RsB], N, Acc) ->
-spec subtract(type(), type()) -> type().
+subtract(any, #t_number{elements={'-inf',Max}}) ->
+ %% We handle this case specially in order to represent the type
+ %% Var in Var =< Integer.
+ #t_union{atom=#t_atom{},
+ list=#t_list{},
+ number=#t_number{elements={Max,'+inf'}},
+ tuple_set=#t_tuple{},
+ other=other};
+subtract(any, nil) ->
+ %%
+ %% We handle this subtraction mainly for correctness. Consider:
+ %%
+ %% foobar(L) when L =/= [], is_list(L), L =/= [], hd(L) -> ok.
+ %%
+ %% The type-based optimizations would rewrite the hd/1 BIF call to
+ %% a get_hd instruction:
+ %%
+ %% foobar(L) when L =/= [], is_list(L), L =/= [], get_hd(L) -> ok.
+ %%
+ %% The beam_ssa_dead pass would later remove the redundant second
+ %% test of `L =/= []`:
+ %%
+ %% foobar(L) when L =/= [], is_list(L), get_hd(L) -> ok.
+ %%
+ %% With that test removed, the type for L in the get_hd instruction
+ %% would be #t_list{} instead of the required #t_cons{} and that
+ %% would trigger an assertion. (If the assertion were to be removed,
+ %% beam_validator would complain instead.)
+ %%
+ %% By letting the type subtraction of `any` by `nil` return a
+ %% union, the optimized code above becomes legal. As an added bonus,
+ %% the is_list/1 guard test can be replaced with the cheaper
+ %% is_nonempty_list instruction:
+ %%
+ %% foobar(L) when L =/= [], is_nonempty_list(L), get_hd(L) -> ok.
+ %%
+ #t_union{atom=#t_atom{},
+ list=#t_cons{},
+ number=#t_number{},
+ tuple_set=#t_tuple{},
+ other=other};
subtract(#t_atom{elements=[_|_]=Set0}, #t_atom{elements=[_|_]=Set1}) ->
case ordsets:subtract(Set0, Set1) of
[] -> none;
@@ -341,8 +382,11 @@ subtract(#t_atom{elements=[_|_]=Set0}, #t_atom{elements=[_|_]=Set1}) ->
end;
subtract(#t_bitstring{size_unit=UnitA}=T, #t_bs_matchable{tail_unit=UnitB}) ->
subtract_matchable(T, UnitA, UnitB);
-subtract(#t_bitstring{size_unit=UnitA}=T, #t_bitstring{size_unit=UnitB}) ->
+subtract(#t_bitstring{appendable=App,size_unit=UnitA}=T,
+ #t_bitstring{appendable=App,size_unit=UnitB}) ->
subtract_matchable(T, UnitA, UnitB);
+subtract(#t_bitstring{}=T, #t_bitstring{}) ->
+ T;
subtract(#t_bs_context{tail_unit=UnitA}=T, #t_bs_matchable{tail_unit=UnitB}) ->
subtract_matchable(T, UnitA, UnitB);
subtract(#t_bs_context{tail_unit=UnitA}=T, #t_bs_context{tail_unit=UnitB}) ->
@@ -358,8 +402,10 @@ subtract(#t_integer{elements={Min, Max}}, #t_integer{elements={N,N}}) ->
Max =:= N ->
#t_integer{elements={Min, Max - 1}}
end;
-subtract(number, #t_float{elements=any}) -> #t_integer{};
-subtract(number, #t_integer{elements=any}) -> #t_float{};
+subtract(#t_number{elements=R}, #t_float{elements=any}) ->
+ integer_from_range(R);
+subtract(#t_number{elements=R}, #t_integer{elements=any}) ->
+ float_from_range(R);
%% A list is essentially `#t_cons{} | nil`, so we're left with nil if we
%% subtract a cons cell that is more general than the one in the list.
@@ -372,6 +418,9 @@ subtract(#t_list{type=TypeA,terminator=TermA}=T,
subtract(#t_list{type=Type,terminator=Term}, nil) ->
#t_cons{type=Type,terminator=Term};
+subtract(identifier, other) ->
+ identifier;
+
subtract(#t_union{atom=Atom}=A, #t_atom{}=B)->
shrink_union(A#t_union{atom=subtract(Atom, B)});
subtract(#t_union{number=Number}=A, B) when ?IS_NUMBER_TYPE(B) ->
@@ -475,7 +524,7 @@ is_boolean_type(_) ->
-spec is_numerical_type(type()) -> boolean().
is_numerical_type(#t_integer{}) -> true;
-is_numerical_type(number) -> true;
+is_numerical_type(#t_number{}) -> true;
is_numerical_type(_) -> false.
-spec set_tuple_element(Index, Type, Elements) -> Elements when
@@ -500,6 +549,58 @@ get_tuple_element(Index, Es) ->
#{} -> any
end.
+%% Helper routine for `update_tuple` / `update_record` instructions, which copy
+%% an existing type and updates a few fields.
+-spec update_tuple(Type, Updates) -> Tuple when
+ Type :: type(),
+ Updates :: [{pos_integer(), type()}, ...],
+ Tuple :: type().
+update_tuple(#t_union{tuple_set=[_|_]=Set0}, [_|_]=Updates) ->
+ case Updates of
+ [{1, _} | _] ->
+ %% The update overwrites the tag, so we can no longer keep any
+ %% records apart. Normalize the set before trying again.
+ update_tuple(normalize_tuple_set(Set0, none), Updates);
+ [_|_] ->
+ case update_tuple_set(Set0, Updates) of
+ [] ->
+ none;
+ [_|_]=Set ->
+ verified_type(shrink_union(#t_union{tuple_set=Set}))
+ end
+ end;
+update_tuple(#t_union{tuple_set=#t_tuple{}=Tuple}, [_|_]=Updates) ->
+ update_tuple(Tuple, Updates);
+update_tuple(#t_tuple{exact=Exact,
+ size=Size,
+ elements=Es0}=Tuple,
+ [_|_]=Updates) ->
+ case update_tuple_1(Updates, Size, Es0) of
+ {MinSize, _Es} when Exact, MinSize > Size ->
+ none;
+ {MinSize, Es} ->
+ verified_normal_type(Tuple#t_tuple{size=MinSize,elements=Es})
+ end;
+update_tuple(Type, [_|_]=Updates) ->
+ case meet(Type, #t_tuple{size=1}) of
+ none -> none;
+ Tuple -> update_tuple(Tuple, Updates)
+ end.
+
+update_tuple_set([{Tag, Record0} | Set], Updates) ->
+ case update_tuple(Record0, Updates) of
+ none -> update_tuple_set(Set, Updates);
+ #t_tuple{}=Record -> [{Tag, Record} | update_tuple_set(Set, Updates)]
+ end;
+update_tuple_set([], _Es) ->
+ [].
+
+update_tuple_1([{Index, Type} | Updates], MinSize, Es0) ->
+ Es = set_tuple_element(Index, Type, Es0),
+ update_tuple_1(Updates, max(Index, MinSize), Es);
+update_tuple_1([], MinSize, Es) ->
+ {MinSize, Es}.
+
-spec normalize(type()) -> normal_type().
normalize(#t_union{atom=Atom,list=List,number=Number,
tuple_set=Tuples,other=Other}) ->
@@ -530,11 +631,11 @@ mtfv_1(A) when is_atom(A) ->
mtfv_1(B) when is_bitstring(B) ->
case bit_size(B) of
0 ->
- %% This is a bit of a hack, but saying that empty binaries have a
- %% unit of 8 helps us get rid of is_binary/1 checks.
- #t_bitstring{size_unit=8};
+ %% See the #t_bitstring{} definition in beam_types.hrl for
+ %% why empty binaries are considered appendable.
+ #t_bitstring{size_unit=256,appendable=true};
Size ->
- #t_bitstring{size_unit=Size}
+ #t_bitstring{size_unit=gcd(Size, 256)}
end;
mtfv_1(F) when is_float(F) ->
make_float(F);
@@ -551,9 +652,9 @@ mtfv_1(L) when is_list(L) ->
mtfv_1(M) when is_map(M) ->
{SKey, SValue} =
maps:fold(fun(Key, Value, {SKey0, SValue0}) ->
- SKey = join(mtfv_1(Key), SKey0),
- SValue = join(mtfv_1(Value), SValue0),
- {SKey, SValue}
+ SKey = join(mtfv_1(Key), SKey0),
+ SValue = join(mtfv_1(Value), SValue0),
+ {SKey, SValue}
end, {none, none}, M),
#t_map{super_key=SKey,super_value=SValue};
mtfv_1(T) when is_tuple(T) ->
@@ -725,11 +826,13 @@ glb(#t_atom{elements=[_|_]}=T, #t_atom{elements=any}) ->
T;
glb(#t_atom{elements=any}, #t_atom{elements=[_|_]}=T) ->
T;
-glb(#t_bitstring{size_unit=U1}, #t_bitstring{size_unit=U2}) ->
- #t_bitstring{size_unit=U1 * U2 div gcd(U1, U2)};
-glb(#t_bitstring{size_unit=UnitA}=T, #t_bs_matchable{tail_unit=UnitB}) ->
+glb(#t_bitstring{size_unit=U1,appendable=A1},
+ #t_bitstring{size_unit=U2,appendable=A2}) ->
+ #t_bitstring{size_unit=U1 * U2 div gcd(U1, U2),appendable=A1 or A2};
+glb(#t_bitstring{size_unit=UnitA,appendable=Appendable}=T,
+ #t_bs_matchable{tail_unit=UnitB}) ->
Unit = UnitA * UnitB div gcd(UnitA, UnitB),
- T#t_bitstring{size_unit=Unit};
+ T#t_bitstring{size_unit=Unit,appendable=Appendable};
glb(#t_bs_context{tail_unit=UnitA}, #t_bs_context{tail_unit=UnitB}) ->
Unit = UnitA * UnitB div gcd(UnitA, UnitB),
#t_bs_context{tail_unit=Unit};
@@ -739,9 +842,10 @@ glb(#t_bs_context{tail_unit=UnitA}=T, #t_bs_matchable{tail_unit=UnitB}) ->
glb(#t_bs_matchable{tail_unit=UnitA}, #t_bs_matchable{tail_unit=UnitB}) ->
Unit = UnitA * UnitB div gcd(UnitA, UnitB),
#t_bs_matchable{tail_unit=Unit};
-glb(#t_bs_matchable{tail_unit=UnitA}, #t_bitstring{size_unit=UnitB}=T) ->
+glb(#t_bs_matchable{tail_unit=UnitA},
+ #t_bitstring{size_unit=UnitB,appendable=Appendable}=T) ->
Unit = UnitA * UnitB div gcd(UnitA, UnitB),
- T#t_bitstring{size_unit=Unit};
+ T#t_bitstring{size_unit=Unit,appendable=Appendable};
glb(#t_bs_matchable{tail_unit=UnitA}, #t_bs_context{tail_unit=UnitB}=T) ->
Unit = UnitA * UnitB div gcd(UnitA, UnitB),
T#t_bs_context{tail_unit=Unit};
@@ -760,15 +864,8 @@ glb(#t_cons{type=TypeA,terminator=TermA},
{_, none} -> none;
{Type, Term} -> #t_cons{type=Type,terminator=Term}
end;
-glb(#t_float{}=T, #t_float{elements=any}) ->
- T;
-glb(#t_float{elements=any}, #t_float{}=T) ->
- T;
-glb(#t_float{elements={MinA,MaxA}}, #t_float{elements={MinB,MaxB}})
- when MinA >= MinB, MinA =< MaxB;
- MinB >= MinA, MinB =< MaxA ->
- true = MinA =< MaxA andalso MinB =< MaxB, %Assertion.
- #t_float{elements={max(MinA, MinB),min(MaxA, MaxB)}};
+glb(#t_float{elements=R1}, #t_float{elements=R2}) ->
+ float_from_range(glb_ranges(R1, R2));
glb(#t_fun{arity=SameArity,target=SameTarget,type=TypeA},
#t_fun{arity=SameArity,target=SameTarget,type=TypeB}=T) ->
T#t_fun{type=meet(TypeA, TypeB)};
@@ -780,19 +877,12 @@ glb(#t_fun{arity=any}=A, #t_fun{arity=ArityB}=B) when ArityB =/= any->
glb(A#t_fun{arity=ArityB}, B);
glb(#t_fun{arity=ArityA}=A, #t_fun{arity=any}=B) when ArityA =/= any ->
glb(A, B#t_fun{arity=ArityA});
-glb(#t_integer{elements={_,_}}=T, #t_integer{elements=any}) ->
- T;
-glb(#t_integer{elements=any}, #t_integer{elements={_,_}}=T) ->
- T;
-glb(#t_integer{elements={MinA,MaxA}}, #t_integer{elements={MinB,MaxB}})
- when MinA >= MinB, MinA =< MaxB;
- MinB >= MinA, MinB =< MaxA ->
- true = MinA =< MaxA andalso MinB =< MaxB, %Assertion.
- #t_integer{elements={max(MinA, MinB),min(MaxA, MaxB)}};
-glb(#t_integer{}=T, number) ->
- T;
-glb(#t_float{}=T, number) ->
- T;
+glb(#t_integer{elements=R1}, #t_integer{elements=R2}) ->
+ integer_from_range(glb_ranges(R1, R2));
+glb(#t_integer{elements=R1}, #t_number{elements=R2}) ->
+ integer_from_range(glb_ranges(R1, R2));
+glb(#t_float{elements=R1}, #t_number{elements=R2}) ->
+ float_from_range(glb_ranges(R1, R2));
glb(#t_list{type=TypeA,terminator=TermA},
#t_list{type=TypeB,terminator=TermB}) ->
%% A list is a union of `[type() | _]` and `[]`, so we're left with
@@ -808,10 +898,12 @@ glb(#t_list{}, nil) ->
nil;
glb(nil, #t_list{}) ->
nil;
-glb(number, #t_integer{}=T) ->
- T;
-glb(number, #t_float{}=T) ->
- T;
+glb(#t_number{elements=R1}, #t_number{elements=R2}) ->
+ number_from_range(glb_ranges(R1, R2));
+glb(#t_number{elements=R1}, #t_integer{elements=R2}) ->
+ integer_from_range(glb_ranges(R1, R2));
+glb(#t_number{elements=R1}, #t_float{elements=R2}) ->
+ float_from_range(glb_ranges(R1, R2));
glb(#t_map{super_key=SKeyA,super_value=SValueA},
#t_map{super_key=SKeyB,super_value=SValueB}) ->
%% Note the use of meet/2; elements don't need to be normal types.
@@ -820,10 +912,46 @@ glb(#t_map{super_key=SKeyA,super_value=SValueA},
#t_map{super_key=SKey,super_value=SValue};
glb(#t_tuple{}=T1, #t_tuple{}=T2) ->
glb_tuples(T1, T2);
+glb(identifier, T) ->
+ case is_identifier(T) of
+ true ->
+ T;
+ false ->
+ case T of
+ other -> identifier;
+ _ -> none
+ end
+ end;
+glb(T, identifier) ->
+ glb(identifier, T);
+glb(other, T) ->
+ case is_other(T) of
+ true -> T;
+ false -> none
+ end;
+glb(T, other) ->
+ glb(other, T);
glb(_, _) ->
%% Inconsistent types. There will be an exception at runtime.
none.
+glb_ranges({MinA,MaxA}, {MinB,MaxB}) ->
+ true = inf_le(MinA, MaxA) andalso inf_le(MinB, MaxB), %Assertion.
+ case (inf_ge(MinA, MinB) andalso inf_le(MinA, MaxB)) orelse
+ (inf_ge(MinB, MinA) andalso inf_le(MinB, MaxA)) of
+ true ->
+ true = inf_le(MinA, MaxA) andalso inf_le(MinB, MaxB), %Assertion.
+ {inf_max(MinA, MinB),inf_min(MaxA, MaxB)};
+ false ->
+ none
+ end;
+glb_ranges({MinA,MaxA}, any) ->
+ {MinA,MaxA};
+glb_ranges(any, {MinB,MaxB}) ->
+ {MinB,MaxB};
+glb_ranges(_, _) ->
+ any.
+
glb_tuples(#t_tuple{size=Sz1,exact=Ex1}, #t_tuple{size=Sz2,exact=Ex2})
when Ex1, Sz1 < Sz2;
Ex2, Sz2 < Sz1 ->
@@ -894,8 +1022,9 @@ lub(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
end;
lub(#t_atom{elements=any}=T, #t_atom{elements=[_|_]}) -> T;
lub(#t_atom{elements=[_|_]}, #t_atom{elements=any}=T) -> T;
-lub(#t_bitstring{size_unit=U1}, #t_bitstring{size_unit=U2}) ->
- #t_bitstring{size_unit=gcd(U1, U2)};
+lub(#t_bitstring{size_unit=U1,appendable=A1},
+ #t_bitstring{size_unit=U2,appendable=A2}) ->
+ #t_bitstring{size_unit=gcd(U1, U2),appendable=A1 and A2};
lub(#t_bitstring{size_unit=U1}, #t_bs_context{tail_unit=U2}) ->
#t_bs_matchable{tail_unit=gcd(U1, U2)};
lub(#t_bitstring{size_unit=UnitA}, #t_bs_matchable{tail_unit=UnitB}) ->
@@ -921,15 +1050,12 @@ lub(#t_cons{type=TypeA,terminator=TermA},
#t_list{type=join(TypeA,TypeB),terminator=join(TermA, TermB)};
lub(#t_cons{type=Type,terminator=Term}, nil) ->
#t_list{type=Type,terminator=Term};
-lub(#t_float{elements={MinA,MaxA}},
- #t_float{elements={MinB,MaxB}}) ->
- #t_float{elements={min(MinA,MinB),max(MaxA,MaxB)}};
-lub(#t_float{}, #t_float{}) ->
- #t_float{};
-lub(#t_float{}, #t_integer{}) ->
- number;
-lub(#t_float{}, number) ->
- number;
+lub(#t_float{elements=R1}, #t_float{elements=R2}) ->
+ float_from_range(lub_ranges(R1, R2));
+lub(#t_float{elements=R1}, #t_integer{elements=R2}) ->
+ number_from_range(lub_ranges(R1, R2));
+lub(#t_float{elements=R1}, #t_number{elements=R2}) ->
+ number_from_range(lub_ranges(R1, R2));
lub(#t_fun{arity=SameArity,target=SameTarget,type=TypeA},
#t_fun{arity=SameArity,target=SameTarget,type=TypeB}) ->
#t_fun{arity=SameArity,target=SameTarget,type=join(TypeA, TypeB)};
@@ -937,15 +1063,12 @@ lub(#t_fun{arity=SameArity,type=TypeA}, #t_fun{arity=SameArity,type=TypeB}) ->
#t_fun{arity=SameArity,type=join(TypeA, TypeB)};
lub(#t_fun{type=TypeA}, #t_fun{type=TypeB}) ->
#t_fun{type=join(TypeA, TypeB)};
-lub(#t_integer{elements={MinA,MaxA}},
- #t_integer{elements={MinB,MaxB}}) ->
- #t_integer{elements={min(MinA,MinB),max(MaxA,MaxB)}};
-lub(#t_integer{}, #t_integer{}) ->
- #t_integer{};
-lub(#t_integer{}, #t_float{}) ->
- number;
-lub(#t_integer{}, number) ->
- number;
+lub(#t_integer{elements=R1}, #t_integer{elements=R2}) ->
+ integer_from_range(lub_ranges(R1, R2));
+lub(#t_integer{elements=R1}, #t_float{elements=R2}) ->
+ number_from_range(lub_ranges(R1, R2));
+lub(#t_integer{elements=R1}, #t_number{elements=R2}) ->
+ number_from_range(lub_ranges(R1, R2));
lub(#t_list{type=TypeA,terminator=TermA},
#t_list{type=TypeB,terminator=TermB}) ->
#t_list{type=join(TypeA, TypeB),terminator=join(TermA, TermB)};
@@ -957,10 +1080,12 @@ lub(nil, #t_list{}=T) ->
T;
lub(#t_list{}=T, nil) ->
T;
-lub(number, #t_integer{}) ->
- number;
-lub(number, #t_float{}) ->
- number;
+lub(#t_number{elements=R1}, #t_number{elements=R2}) ->
+ number_from_range(lub_ranges(R1, R2));
+lub(#t_number{elements=R1}, #t_integer{elements=R2}) ->
+ number_from_range(lub_ranges(R1, R2));
+lub(#t_number{elements=R1}, #t_float{elements=R2}) ->
+ number_from_range(lub_ranges(R1, R2));
lub(#t_map{super_key=SKeyA,super_value=SValueA},
#t_map{super_key=SKeyB,super_value=SValueB}) ->
%% Note the use of join/2; elements don't need to be normal types.
@@ -976,8 +1101,35 @@ lub(#t_tuple{size=SzA,elements=EsA}, #t_tuple{size=SzB,elements=EsB}) ->
Sz = min(SzA, SzB),
Es = lub_tuple_elements(Sz, EsA, EsB),
#t_tuple{size=Sz,elements=Es};
-lub(_T1, _T2) ->
- %%io:format("~p ~p\n", [_T1,_T2]),
+lub(T1, T2) ->
+ %%io:format("~p ~p\n", [T1,T2]),
+ case is_identifier(T1) andalso is_identifier(T2) of
+ true ->
+ identifier;
+ false ->
+ case is_other(T1) andalso is_other(T2) of
+ true -> other;
+ false -> any
+ end
+ end.
+
+is_other(Type) ->
+ AnyMinusOther = #t_union{atom=#t_atom{},
+ list=#t_list{},
+ number=#t_number{},
+ tuple_set=#t_tuple{},
+ other=none},
+ meet(AnyMinusOther, Type) =:= none.
+
+is_identifier(identifier) -> true;
+is_identifier(pid) -> true;
+is_identifier(port) -> true;
+is_identifier(reference) -> true;
+is_identifier(_) -> false.
+
+lub_ranges({MinA,MaxA}, {MinB,MaxB}) ->
+ {inf_min(MinA, MinB), inf_max(MaxA, MaxB)};
+lub_ranges(_, _) ->
any.
lub_bs_matchable(UnitA, UnitB) ->
@@ -985,7 +1137,7 @@ lub_bs_matchable(UnitA, UnitB) ->
lub_tuple_elements(MinSize, EsA, EsB) ->
Es0 = lub_elements(EsA, EsB),
- maps:filter(fun(Index, _Type) -> Index =< MinSize end, Es0).
+ #{Index => Type || Index := Type <- Es0, Index =< MinSize}.
lub_elements(Es1, Es2) ->
Keys = if
@@ -1014,6 +1166,74 @@ gcd(A, B) ->
X -> gcd(B, X)
end.
+%%%
+%%% Handling of ranges.
+%%%
+%%% A range can begin with '-inf' OR end with '+inf'.
+%%%
+%%% Atoms are greater than all integers. Therefore, we don't
+%%% need any special handling of '+inf'.
+%%%
+
+float_from_range(none) ->
+ none;
+float_from_range(any) ->
+ #t_float{};
+float_from_range({Min0,Max0}) ->
+ case {safe_float(Min0),safe_float(Max0)} of
+ {'-inf','+inf'} ->
+ #t_float{};
+ {Min,Max} ->
+ #t_float{elements={Min,Max}}
+ end.
+
+safe_float(N) when is_number(N) ->
+ try
+ float(N)
+ catch
+ error:_ when N < 0 -> '-inf';
+ error:_ when N > 0 -> '+inf'
+ end;
+safe_float('-inf'=NegInf) -> NegInf;
+safe_float('+inf'=PosInf) -> PosInf.
+
+integer_from_range(none) ->
+ none;
+integer_from_range(any) ->
+ #t_integer{};
+integer_from_range({'-inf','+inf'}) ->
+ #t_integer{};
+integer_from_range({'-inf',Max}) ->
+ #t_integer{elements={'-inf',ceil(Max)}};
+integer_from_range({Min,'+inf'}) ->
+ #t_integer{elements={floor(Min),'+inf'}};
+integer_from_range({Min,Max}) ->
+ #t_integer{elements={floor(Min),ceil(Max)}}.
+
+number_from_range(N) ->
+ case integer_from_range(N) of
+ #t_integer{elements=R} ->
+ #t_number{elements=R};
+ none ->
+ none
+ end.
+
+inf_le('-inf', _) -> true;
+inf_le(A, B) -> A =< B.
+
+inf_ge(_, '-inf') -> true;
+inf_ge('-inf', _) -> false;
+inf_ge(A, B) -> A >= B.
+
+inf_min(A, B) when A =:= '-inf'; B =:= '-inf' -> '-inf';
+inf_min(A, B) when A =< B -> A;
+inf_min(A, B) when A > B -> B.
+
+inf_max('-inf', B) -> B;
+inf_max(A, '-inf') -> A;
+inf_max(A, B) when A >= B -> A;
+inf_max(A, B) when A < B -> B.
+
%%
record_key(#t_tuple{exact=true,size=Size,elements=#{ 1 := Tag }}) ->
@@ -1032,8 +1252,6 @@ new_tuple_set(T) ->
%%
-shrink_union(#t_union{other=any}) ->
- any;
shrink_union(#t_union{atom=Atom,list=none,number=none,
tuple_set=none,other=none}) ->
Atom;
@@ -1052,6 +1270,12 @@ shrink_union(#t_union{atom=none,list=none,number=none,
shrink_union(#t_union{atom=none,list=none,number=none,
tuple_set=none,other=Other}) ->
Other;
+shrink_union(#t_union{atom=#t_atom{elements=any},
+ list=#t_list{type=any,terminator=any},
+ number=#t_number{elements=any},
+ tuple_set=#t_tuple{size=0,exact=false},
+ other=other}) ->
+ any;
shrink_union(#t_union{}=T) ->
T.
@@ -1127,6 +1351,12 @@ verified_normal_type(#t_fun{arity=Arity,
T;
verified_normal_type(#t_float{}=T) -> T;
verified_normal_type(#t_integer{elements=any}=T) -> T;
+verified_normal_type(#t_integer{elements={'-inf',Max}}=T)
+ when is_integer(Max) ->
+ T;
+verified_normal_type(#t_integer{elements={Min,'+inf'}}=T)
+ when is_integer(Min) ->
+ T;
verified_normal_type(#t_integer{elements={Min,Max}}=T)
when is_integer(Min), is_integer(Max), Min =< Max ->
T;
@@ -1136,21 +1366,22 @@ verified_normal_type(#t_list{type=Type,terminator=Term}=T) ->
T;
verified_normal_type(#t_map{}=T) -> T;
verified_normal_type(nil=T) -> T;
-verified_normal_type(number=T) -> T;
+verified_normal_type(#t_number{}=T) -> T;
+verified_normal_type(other=T) -> T;
verified_normal_type(pid=T) -> T;
verified_normal_type(port=T) -> T;
verified_normal_type(reference=T) -> T;
+verified_normal_type(identifier=T) -> T;
verified_normal_type(#t_tuple{size=Size,elements=Es}=T) ->
%% All known elements must have a valid index and type (which may be a
%% union). 'any' is prohibited since it's implicit and should never be
%% present in the map, and a 'none' element ought to have reduced the
%% entire tuple to 'none'.
- maps:fold(fun(Index, Element, _) when is_integer(Index),
- 1 =< Index, Index =< Size,
- Index =< ?TUPLE_ELEMENT_LIMIT,
- Element =/= any, Element =/= none ->
- verified_type(Element)
- end, [], Es),
+ _ = [verified_type(Element) ||
+ Index := Element <- Es,
+ is_integer(Index), 1 =< Index, Index =< Size,
+ Index =< ?TUPLE_ELEMENT_LIMIT,
+ Element =/= any, Element =/= none],
T.
%%%
@@ -1178,6 +1409,32 @@ verified_normal_type(#t_tuple{size=Size,elements=Es}=T) ->
-define(BEAM_TYPE_REFERENCE, (1 bsl 11)).
-define(BEAM_TYPE_TUPLE, (1 bsl 12)).
+-define(BEAM_TYPE_HAS_LOWER_BOUND, (1 bsl 13)).
+-define(BEAM_TYPE_HAS_UPPER_BOUND, (1 bsl 14)).
+-define(BEAM_TYPE_HAS_UNIT, (1 bsl 15)).
+
+-define(BEAM_TYPES_VERSION_26, ?BEAM_TYPES_VERSION).
+-define(BEAM_TYPES_VERSION_25, 1).
+
+-spec convert_ext(pos_integer(), binary()) -> binary() | 'none'.
+convert_ext(?BEAM_TYPES_VERSION, Types) ->
+ Types;
+convert_ext(?BEAM_TYPES_VERSION_25, Types0) ->
+ NumberMask = (?BEAM_TYPE_FLOAT bor ?BEAM_TYPE_INTEGER),
+ Types = << case Min =< Max of
+ true ->
+ true = 0 =/= (TypeBits0 band NumberMask), %Assertion.
+ TypeBits = TypeBits0 bor
+ ?BEAM_TYPE_HAS_LOWER_BOUND bor
+ ?BEAM_TYPE_HAS_UPPER_BOUND,
+ <<TypeBits:16,Min:64/signed,Max:64/signed>>;
+ false ->
+ <<TypeBits0:16>>
+ end || <<TypeBits0:16,Min:64/signed,Max:64/signed>> <= Types0 >>,
+ convert_ext(?BEAM_TYPES_VERSION_26, Types);
+convert_ext(_Version, _Types) ->
+ none.
+
ext_type_mapping() ->
[{?BEAM_TYPE_ATOM, #t_atom{}},
{?BEAM_TYPE_BITSTRING, #t_bitstring{}},
@@ -1193,12 +1450,20 @@ ext_type_mapping() ->
{?BEAM_TYPE_REFERENCE, reference},
{?BEAM_TYPE_TUPLE, #t_tuple{}}].
--spec decode_ext(binary()) -> type().
-decode_ext(<<TypeBits:16/big,Min:64,Max:64>>) ->
+-spec decode_ext(binary()) -> {type(),binary()} | 'done'.
+decode_ext(<<TypeBits:16/big,More/binary>>) ->
+ true = TypeBits =/= 0, %Assertion.
Res = foldl(fun({Id, Type}, Acc) ->
decode_ext_bits(TypeBits, Id, Type, Acc)
end, none, ext_type_mapping()),
- {Res,Min,Max}.
+ {[Min,Max,Unit],Extra} = decode_extra(TypeBits, More),
+ R = case {Min,Max} of
+ {'-inf','+inf'} -> any;
+ R0 -> R0
+ end,
+ {decode_fix(Res, R, Unit),Extra};
+decode_ext(<<>>) ->
+ done.
decode_ext_bits(Input, TypeBit, Type, Acc) ->
case Input band TypeBit of
@@ -1206,17 +1471,52 @@ decode_ext_bits(Input, TypeBit, Type, Acc) ->
_ -> join(Type, Acc)
end.
+decode_extra(TypeBits, Extra) ->
+ L = [{?BEAM_TYPE_HAS_LOWER_BOUND, 64, signed, '-inf'},
+ {?BEAM_TYPE_HAS_UPPER_BOUND, 64, signed, '+inf'},
+ {?BEAM_TYPE_HAS_UNIT, 8, unsigned, 1}],
+ mapfoldl(fun({Bit,Size,Spec,Default}, Acc0) ->
+ case {TypeBits band Bit, Spec, Acc0} of
+ {Bit, unsigned, <<Value:Size/unsigned,Acc/binary>>} ->
+ {Value, Acc};
+ {Bit, signed, <<Value:Size/signed,Acc/binary>>} ->
+ {Value, Acc};
+ {0, _Spec, <<_/binary>>} ->
+ {Default,Acc0}
+ end
+ end, Extra, L).
+
+decode_fix(#t_integer{}, Range, _Unit) ->
+ #t_integer{elements=Range};
+decode_fix(#t_number{}, Range, _Unit) ->
+ #t_number{elements=Range};
+decode_fix(#t_bitstring{}, _Range, Unit) ->
+ #t_bitstring{size_unit=Unit+1};
+decode_fix(#t_union{}=Type0, Range, Unit) ->
+ Type1 = case meet(Type0, #t_integer{}) of
+ #t_integer{} ->
+ Type0#t_union{number=#t_integer{elements=Range}};
+ _ ->
+ Type0
+ end,
+ case meet(Type1, #t_bitstring{}) of
+ #t_bitstring{} ->
+ Type1#t_union{other=#t_bitstring{size_unit=Unit}};
+ _ ->
+ Type1
+ end;
+decode_fix(Type, _, _) ->
+ Type.
+
-spec encode_ext(type()) -> binary().
encode_ext(Input) ->
- TypeBits = foldl(fun({Id, Type}, Acc) ->
- encode_ext_bits(Input, Id, Type, Acc)
- end, 0, ext_type_mapping()),
- case get_integer_range(Input) of
- none ->
- <<TypeBits:16/big,0:64,-1:64>>;
- {Min,Max} ->
- <<TypeBits:16/big,Min:64,Max:64>>
- end.
+ TypeBits0 = foldl(fun({Id, Type}, Acc) ->
+ encode_ext_bits(Input, Id, Type, Acc)
+ end, 0, ext_type_mapping()),
+ {TypeBits1,Extra} = encode_extra(Input),
+ TypeBits = TypeBits0 bor TypeBits1,
+ true = TypeBits =/= 0, %Assertion.
+ <<TypeBits:16,Extra/binary>>.
encode_ext_bits(Input, TypeBit, Type, Acc) ->
case meet(Input, Type) of
@@ -1224,21 +1524,49 @@ encode_ext_bits(Input, TypeBit, Type, Acc) ->
_ -> Acc bor TypeBit
end.
-get_integer_range(#t_integer{elements={Min,Max}}) ->
- case is_small(Min) andalso is_small(Max) of
+encode_extra(Input) ->
+ {TypeBits0,Extra0} = encode_range(Input),
+ {TypeBits1,Extra1} = encode_unit(Input),
+ {TypeBits0 bor TypeBits1,<<Extra0/binary,Extra1/binary>>}.
+
+encode_range(#t_integer{elements={Min,Max}}) ->
+ encode_range(Min, Max);
+encode_range(#t_number{elements={Min,Max}}) ->
+ encode_range(Min, Max);
+encode_range(#t_union{number=N}) ->
+ encode_range(N);
+encode_range(_) ->
+ {0,<<>>}.
+
+encode_range(Min, Max) ->
+ case is_small(Min) of
true ->
- {Min,Max};
+ encode_range(Max, ?BEAM_TYPE_HAS_LOWER_BOUND, <<Min:64>>);
false ->
- %% Not an integer with range, or at least one of the
- %% endpoints is a bignum.
- none
- end;
-get_integer_range(#t_union{number=N}) ->
- get_integer_range(N);
-get_integer_range(_) -> none.
+ encode_range(Max, 0, <<>>)
+ end.
+
+encode_range(Max, TypeBits, Extra) ->
+ case is_small(Max) of
+ true ->
+ {TypeBits bor ?BEAM_TYPE_HAS_UPPER_BOUND,
+ <<Extra/binary,Max:64>>};
+ false ->
+ {TypeBits,Extra}
+ end.
+
+encode_unit(#t_bitstring{size_unit=Unit}) ->
+ true = is_integer(Unit) andalso 0 < Unit andalso Unit =< 256, %Assertion.
+ {?BEAM_TYPE_HAS_UNIT,<<(Unit-1):8>>};
+encode_unit(#t_union{other=Other}) ->
+ encode_unit(Other);
+encode_unit(_) ->
+ {0,<<>>}.
%% Test whether the number is a small on a 64-bit machine.
%% (Normally the compiler doesn't know/doesn't care whether something is
%% bignum, but because the type representation is versioned this is safe.)
-is_small(N) ->
- -(1 bsl 59) =< N andalso N =< (1 bsl 59) - 1.
+is_small(N) when is_integer(N), -(1 bsl 59) =< N andalso N =< (1 bsl 59) - 1 ->
+ true;
+is_small(_) ->
+ false.
diff --git a/lib/compiler/src/beam_types.hrl b/lib/compiler/src/beam_types.hrl
index 18f7e29074..a96d8c00cb 100644
--- a/lib/compiler/src/beam_types.hrl
+++ b/lib/compiler/src/beam_types.hrl
@@ -19,7 +19,7 @@
%%
%% Type version, must be bumped whenever the external type format changes.
--define(BEAM_TYPES_VERSION, 1).
+-define(BEAM_TYPES_VERSION, 2).
%% Common term types for passes operating on beam SSA and assembly. Helper
%% functions for wrangling these can be found in beam_types.erl
@@ -29,21 +29,23 @@
%% any Any Erlang term (top element).
%%
%% - #t_atom{} Atom, or a set thereof.
-%% - #t_bs_matchable{} Binary-matchable types.
-%% - #t_bitstring{} Bitstring.
-%% - #t_bs_context{} Match context.
-%% - #t_fun{} Fun.
-%% - #t_map{} Map.
-%% - number Any number.
+%% - #t_number{} Any number.
%% -- #t_float{} Floating point number.
%% -- #t_integer{} Integer.
%% - #t_list{} Any list.
%% -- #t_cons{} Cons (nonempty list).
%% -- nil The empty list.
-%% - pid
-%% - port
-%% - reference
%% - #t_tuple{} Tuple.
+%% - other Other types.
+%% -- #t_fun{} Fun.
+%% -- #t_map{} Map.
+%% -- identifier
+%% -- pid
+%% -- port
+%% -- reference
+%% -- #t_bs_matchable{} Binary-matchable types.
+%% -- #t_bitstring{} Bitstring.
+%% -- #t_bs_context{} Match context.
%%
%% none No type (bottom element).
%%
@@ -81,17 +83,28 @@
%% [1] https://en.wikipedia.org/wiki/Lattice_(order)#General_lattice
-define(ATOM_SET_SIZE, 5).
+
+%% Documented limits
-define(MAX_FUNC_ARGS, 255).
+-define(MAX_TUPLE_SIZE, (1 bsl 24) - 1).
+
+-type float_range() :: 'any' | {'-inf',float()} | {float(),'+inf'}.
-record(t_atom, {elements=any :: 'any' | ordsets:ordset(atom())}).
--record(t_bitstring, {size_unit=1 :: pos_integer()}).
+-record(t_bitstring, {size_unit=1 :: pos_integer(),
+ %% The appendable flag indicates whether the bitstring
+ %% originated as <<>> and has only been appended to by
+ %% `bs_create_bin` with the bitstring as the leftmost
+ %% fragment.
+ appendable=false :: boolean()}).
-record(t_bs_context, {tail_unit=1 :: pos_integer()}).
-record(t_bs_matchable, {tail_unit=1 :: pos_integer()}).
--record(t_float, {elements=any :: 'any' | {float(),float()}}).
+-record(t_float, {elements=any :: float_range()}).
-record(t_fun, {arity=any :: arity() | 'any',
target=any :: {atom(), non_neg_integer()} | 'any',
type=any :: type() }).
--record(t_integer, {elements=any :: 'any' | {integer(),integer()}}).
+-record(t_integer, {elements=any :: 'any' | beam_bounds:range()}).
+-record(t_number, {elements=any :: 'any' | beam_bounds:range()}).
%% `super_key` and `super_value` are the join of all key and value types.
%%
@@ -132,27 +145,36 @@
-define(TUPLE_ELEMENT_LIMIT, 12).
-type tuple_elements() :: #{ Key :: pos_integer() => type() }.
--type normal_type() :: any | none |
- number | #t_float{} | #t_integer{} |
+-type normal_type() :: 'any' | 'none' |
+ #t_number{} | #t_float{} | #t_integer{} |
#t_atom{} |
#t_bitstring{} | #t_bs_context{} | #t_bs_matchable{} |
#t_fun{} |
- #t_list{} | #t_cons{} | nil |
+ #t_list{} | #t_cons{} | 'nil' |
+ 'other' |
#t_map{} |
- pid |
- port |
- reference |
+ 'identifier' |
+ 'pid' |
+ 'port' |
+ 'reference' |
#t_tuple{}.
+-type other_type() :: 'none' | #t_fun{} | #t_map{} |
+ 'pid' | 'port' | 'reference' | 'identifier' |
+ #t_bitstring{} | #t_bs_context{} |
+ #t_bs_matchable{}.
+
-type record_key() :: {Arity :: integer(), Tag :: normal_type() }.
-type record_set() :: ordsets:ordset({record_key(), #t_tuple{}}).
-type tuple_set() :: #t_tuple{} | record_set().
--record(t_union, {atom=none :: none | #t_atom{},
- list=none :: none | #t_list{} | #t_cons{} | nil,
- number=none :: none | number | #t_float{} | #t_integer{},
- tuple_set=none :: none | tuple_set(),
- other=none :: normal_type()}).
+%% The fields in the union must not overlap. In particular, that means
+%% that the type `any` is not allowed in any field.
+-record(t_union, {atom=none :: 'none' | #t_atom{},
+ list=none :: 'none' | #t_list{} | #t_cons{} | nil,
+ number=none :: 'none' | #t_number{} | #t_float{} | #t_integer{},
+ tuple_set=none :: 'none' | tuple_set(),
+ other=none :: 'other' | other_type()}).
-type type() :: #t_union{} | normal_type().
diff --git a/lib/compiler/src/beam_utils.erl b/lib/compiler/src/beam_utils.erl
index 904e1f3e82..71ba58bffc 100644
--- a/lib/compiler/src/beam_utils.erl
+++ b/lib/compiler/src/beam_utils.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -115,6 +115,9 @@ replace_labels_1([{get_map_elements=I,{f,Lbl},Src,List}|Is], Acc, D, Fb) when Lb
replace_labels_1([{bs_start_match4,{f,Lbl},Live,Src,Dst}|Is], Acc, D, Fb) ->
I = {bs_start_match4,{f,label(Lbl, D, Fb)},Live,Src,Dst},
replace_labels_1(Is, [I | Acc], D, Fb);
+replace_labels_1([{bs_match,{f,Lbl},Ctx,List}|Is], Acc, D, Fb) ->
+ I = {bs_match,{f,label(Lbl, D, Fb)},Ctx,List},
+ replace_labels_1(Is, [I | Acc], D, Fb);
replace_labels_1([I|Is], Acc, D, Fb) ->
replace_labels_1(Is, [I|Acc], D, Fb);
replace_labels_1([], Acc, _, _) -> Acc.
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index 413d246d4d..217b7a2c97 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -186,7 +186,7 @@ validate_0([{function, Name, Arity, Entry, Code} | Fs], Module, Level, Ft) ->
%%
%% Note that this may be 0 if there's a frame without saved values,
%% such as on a body-recursive call.
- numy=none :: none | undecided | index(),
+ numy=none :: none | {undecided,index()} | index(),
%% Available heap size.
h=0,
%% Available heap size for funs (aka lambdas).
@@ -427,12 +427,13 @@ vi({jump,{f,Lbl}}, Vst) ->
%% The next instruction is never executed.
branch(Lbl, Vst, fun kill_state/1);
-vi({select_val,Src,{f,Fail},{list,Choices}}, Vst) ->
+vi({select_val,Src0,{f,Fail},{list,Choices}}, Vst) ->
+ Src = unpack_typed_arg(Src0, Vst),
assert_term(Src, Vst),
assert_choices(Choices),
validate_select_val(Fail, Choices, Src, Vst);
vi({select_tuple_arity,Tuple0,{f,Fail},{list,Choices}}, Vst) ->
- Tuple = unpack_typed_arg(Tuple0),
+ Tuple = unpack_typed_arg(Tuple0, Vst),
assert_type(#t_tuple{}, Tuple, Vst),
assert_arities(Choices),
@@ -455,7 +456,7 @@ vi({test,is_function2,{f,Lbl},[Src,{integer,Arity}]}, Vst)
when Arity >= 0, Arity =< ?MAX_FUNC_ARGS ->
type_test(Lbl, #t_fun{arity=Arity}, Src, Vst);
vi({test,is_function2,{f,Lbl},[Src0,_Arity]}, Vst) ->
- Src = unpack_typed_arg(Src0),
+ Src = unpack_typed_arg(Src0, Vst),
assert_term(Src, Vst),
branch(Lbl, Vst,
fun(FailVst) ->
@@ -474,7 +475,7 @@ vi({test,is_integer,{f,Lbl},[Src]}, Vst) ->
vi({test,is_nonempty_list,{f,Lbl},[Src]}, Vst) ->
type_test(Lbl, #t_cons{}, Src, Vst);
vi({test,is_number,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, number, Src, Vst);
+ type_test(Lbl, #t_number{}, Src, Vst);
vi({test,is_list,{f,Lbl},[Src]}, Vst) ->
type_test(Lbl, #t_list{}, Src, Vst);
vi({test,is_map,{f,Lbl},[Src]}, Vst) ->
@@ -482,7 +483,7 @@ vi({test,is_map,{f,Lbl},[Src]}, Vst) ->
vi({test,is_nil,{f,Lbl},[Src0]}, Vst) ->
%% is_nil is an exact check against the 'nil' value, and should not be
%% treated as a simple type test.
- Src = unpack_typed_arg(Src0),
+ Src = unpack_typed_arg(Src0, Vst),
assert_term(Src, Vst),
branch(Lbl, Vst,
fun(FailVst) ->
@@ -502,16 +503,16 @@ vi({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst) when is_integer(Sz) ->
Type = #t_tuple{exact=true,size=Sz},
type_test(Lbl, Type, Tuple, Vst);
vi({test,is_tagged_tuple,{f,Lbl},[Src0,Sz,Atom]}, Vst) ->
- Src = unpack_typed_arg(Src0),
+ Src = unpack_typed_arg(Src0, Vst),
assert_term(Src, Vst),
Es = #{ 1 => get_literal_type(Atom) },
Type = #t_tuple{exact=true,size=Sz,elements=Es},
type_test(Lbl, Type, Src, Vst);
vi({test,is_eq_exact,{f,Lbl},[Src0,Val0]}, Vst) ->
assert_no_exception(Lbl),
- Src = unpack_typed_arg(Src0),
+ Src = unpack_typed_arg(Src0, Vst),
assert_term(Src, Vst),
- Val = unpack_typed_arg(Val0),
+ Val = unpack_typed_arg(Val0, Vst),
assert_term(Val, Vst),
branch(Lbl, Vst,
fun(FailVst) ->
@@ -522,9 +523,9 @@ vi({test,is_eq_exact,{f,Lbl},[Src0,Val0]}, Vst) ->
end);
vi({test,is_ne_exact,{f,Lbl},[Src0,Val0]}, Vst) ->
assert_no_exception(Lbl),
- Src = unpack_typed_arg(Src0),
+ Src = unpack_typed_arg(Src0, Vst),
assert_term(Src, Vst),
- Val = unpack_typed_arg(Val0),
+ Val = unpack_typed_arg(Val0, Vst),
assert_term(Val, Vst),
branch(Lbl, Vst,
fun(FailVst) ->
@@ -629,33 +630,6 @@ vi({put_tuple2,Dst,{list,Elements}}, Vst0) ->
end, {#{}, 1}, Elements),
Type = #t_tuple{exact=true,size=Size,elements=Es},
create_term(Type, put_tuple2, [], Dst, Vst);
-vi({put_tuple,Sz,Dst}, Vst0) when is_integer(Sz) ->
- Vst1 = eat_heap(1, Vst0),
- Vst = create_term(#t_abstract{kind=unfinished_tuple}, put_tuple, [],
- Dst, Vst1),
- #vst{current=St0} = Vst,
- St = St0#st{puts_left={Sz,{Dst,Sz,#{}}}},
- Vst#vst{current=St};
-vi({put,Src}, Vst0) ->
- assert_term(Src, Vst0),
- Vst = eat_heap(1, Vst0),
- #vst{current=St0} = Vst,
- case St0 of
- #st{puts_left=none} ->
- error(not_building_a_tuple);
- #st{puts_left={1,{Dst,Sz,Es0}}} ->
- ElementType = get_term_type(Src, Vst0),
- Es = beam_types:set_tuple_element(Sz, ElementType, Es0),
- St = St0#st{puts_left=none},
- Type = #t_tuple{exact=true,size=Sz,elements=Es},
- create_term(Type, put_tuple, [], Dst, Vst#vst{current=St});
- #st{puts_left={PutsLeft,{Dst,Sz,Es0}}} when is_integer(PutsLeft) ->
- Index = Sz - PutsLeft + 1,
- ElementType = get_term_type(Src, Vst0),
- Es = beam_types:set_tuple_element(Index, ElementType, Es0),
- St = St0#st{puts_left={PutsLeft-1,{Dst,Sz,Es}}},
- Vst#vst{current=St}
- end;
vi({set_tuple_element,Src,Tuple,N}, Vst) ->
%% This instruction never fails, though it may be invalid in some contexts;
%% see validate_mutation/2
@@ -666,9 +640,12 @@ vi({set_tuple_element,Src,Tuple,N}, Vst) ->
%% helpers as we must support overwriting (rather than just widening or
%% narrowing) known elements, and we can't use extract_term either since
%% the source tuple may be aliased.
- #t_tuple{elements=Es0}=Type = normalize(get_term_type(Tuple, Vst)),
- Es = beam_types:set_tuple_element(I, get_term_type(Src, Vst), Es0),
- override_type(Type#t_tuple{elements=Es}, Tuple, Vst);
+ TupleType0 = get_term_type(Tuple, Vst),
+ ArgType = get_term_type(Src, Vst),
+ TupleType = beam_types:update_tuple(TupleType0, [{I, ArgType}]),
+ override_type(TupleType, Tuple, Vst);
+vi({update_record,_Hint,Size,Src,Dst,{list,Ss}}, Vst) ->
+ verify_update_record(Size, Src, Dst, Ss, Vst);
%%
%% Calls
@@ -702,7 +679,7 @@ vi({call_fun2,{f,Lbl},Live,Fun0}, #vst{ft=Ft}=Vst) ->
assert_term(Fun, Vst),
validate_body_call('fun', Live, Vst);
vi({call_fun2,Tag,Live,Fun0}, Vst) ->
- Fun = unpack_typed_arg(Fun0),
+ Fun = unpack_typed_arg(Fun0, Vst),
assert_term(Fun, Vst),
case Tag of
@@ -762,8 +739,8 @@ vi(return, Vst) ->
%%
vi({bif,Op,{f,Fail},Ss0,Dst0}, Vst0) ->
- Ss = [unpack_typed_arg(Arg) || Arg <- Ss0],
- Dst = unpack_typed_arg(Dst0),
+ Ss = [unpack_typed_arg(Arg, Vst0) || Arg <- Ss0],
+ Dst = unpack_typed_arg(Dst0, Vst0),
case is_float_arith_bif(Op, Ss) of
true ->
@@ -774,8 +751,8 @@ vi({bif,Op,{f,Fail},Ss0,Dst0}, Vst0) ->
validate_bif(bif, Op, Fail, Ss, Dst, Vst0, Vst0)
end;
vi({gc_bif,Op,{f,Fail},Live,Ss0,Dst0}, Vst0) ->
- Ss = [unpack_typed_arg(Arg) || Arg <- Ss0],
- Dst = unpack_typed_arg(Dst0),
+ Ss = [unpack_typed_arg(Arg, Vst0) || Arg <- Ss0],
+ Dst = unpack_typed_arg(Dst0, Vst0),
validate_src(Ss, Vst0),
verify_live(Live, Vst0),
@@ -906,7 +883,7 @@ vi(build_stacktrace, Vst0) ->
%%
vi({get_map_elements,{f,Fail},Src0,{list,List}}, Vst) ->
- Src = unpack_typed_arg(Src0),
+ Src = unpack_typed_arg(Src0, Vst),
verify_get_map(Fail, Src, List, Vst);
vi({put_map_assoc=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
verify_put_map(Op, Fail, Src, Dst, Live, List, Vst);
@@ -917,6 +894,20 @@ vi({put_map_exact=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
%% Bit syntax matching
%%
+vi({bs_match,{f,Fail},Ctx0,{commands,List}}, Vst) ->
+ Ctx = unpack_typed_arg(Ctx0, Vst),
+
+ assert_no_exception(Fail),
+ assert_type(#t_bs_context{}, Ctx, Vst),
+ verify_y_init(Vst),
+
+ branch(Fail, Vst,
+ fun(FailVst) ->
+ validate_failed_bs_match(List, Ctx, FailVst)
+ end,
+ fun(SuccVst) ->
+ validate_bs_match(List, Ctx, 1, SuccVst)
+ end);
vi({bs_get_tail,Ctx,Dst,Live}, Vst0) ->
assert_type(#t_bs_context{}, Ctx, Vst0),
verify_live(Live, Vst0),
@@ -934,7 +925,8 @@ vi({test,bs_start_match3,{f,_}=Fail,Live,[Src],Dst}, Vst) ->
vi({test,bs_match_string,{f,Fail},[Ctx,Stride,{string,String}]}, Vst) ->
true = is_bitstring(String), %Assertion.
validate_bs_skip(Fail, Ctx, Stride, Vst);
-vi({test,bs_skip_bits2,{f,Fail},[Ctx,Size,Unit,_Flags]}, Vst) ->
+vi({test,bs_skip_bits2,{f,Fail},[Ctx,Size0,Unit,_Flags]}, Vst) ->
+ Size = unpack_typed_arg(Size0, Vst),
assert_term(Size, Vst),
Stride = case get_concrete_type(Size, Vst) of
@@ -974,29 +966,19 @@ vi({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,Size,Unit,_],Dst}, Vst) ->
Stride = bsm_stride(Size, Unit),
validate_bs_get(Op, Fail, Ctx, Live, Stride, Type, Dst, Vst);
vi({test,bs_get_integer2=Op,{f,Fail},Live,
- [Ctx,{integer,Sz},Unit,{field_flags,Flags}],Dst},Vst) ->
-
- NumBits = Unit * Sz,
- Stride = NumBits,
-
- Type = if
- 0 =< NumBits, NumBits =< 64 ->
- Max = (1 bsl NumBits) - 1,
- case member(unsigned, Flags) of
- true ->
- beam_types:make_integer(0, Max);
- false ->
- Min = -(Max + 1),
- beam_types:make_integer(Min, Max)
- end;
- true ->
- %% Way too large or negative size.
- #t_integer{}
- end,
-
+ [Ctx,{integer,Size},Unit,{field_flags,Flags}],Dst},Vst) ->
+ Type = bs_integer_type({Size, Size}, Unit, Flags),
+ Stride = Unit * Size,
validate_bs_get(Op, Fail, Ctx, Live, Stride, Type, Dst, Vst);
-vi({test,bs_get_integer2=Op,{f,Fail},Live,[Ctx,_Sz,Unit,_Flags],Dst},Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, Unit, #t_integer{}, Dst, Vst);
+vi({test,bs_get_integer2=Op,{f,Fail},Live,[Ctx,Sz0,Unit,{field_flags,Flags}],Dst},Vst) ->
+ Sz = unpack_typed_arg(Sz0, Vst),
+ Type = case meet(get_term_type(Sz, Vst), #t_integer{}) of
+ #t_integer{elements=Bounds} ->
+ bs_integer_type(Bounds, Unit, Flags);
+ none ->
+ none
+ end,
+ validate_bs_get(Op, Fail, Ctx, Live, Unit, Type, Dst, Vst);
vi({test,bs_get_float2=Op,{f,Fail},Live,[Ctx,Size,Unit,_],Dst},Vst) ->
Stride = bsm_stride(Size, Unit),
validate_bs_get(Op, Fail, Ctx, Live, Stride, #t_float{}, Dst, Vst);
@@ -1009,9 +991,30 @@ vi({test,bs_get_utf16=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
vi({test,bs_get_utf32=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
Type = beam_types:make_integer(0, ?UNICODE_MAX),
validate_bs_get(Op, Fail, Ctx, Live, 32, Type, Dst, Vst);
+vi({test,is_lt,{f,Fail},Args0}, Vst) ->
+ Args = [unpack_typed_arg(Arg, Vst) || Arg <- Args0],
+ validate_src(Args, Vst),
+ Types = [get_term_type(Arg, Vst) || Arg <- Args],
+ branch(Fail, Vst,
+ fun(FailVst) ->
+ infer_relop_types('>=', Args, Types, FailVst)
+ end,
+ fun(SuccVst) ->
+ infer_relop_types('<', Args, Types, SuccVst)
+ end);
+vi({test,is_ge,{f,Fail},Args0}, Vst) ->
+ Args = [unpack_typed_arg(Arg, Vst) || Arg <- Args0],
+ validate_src(Args, Vst),
+ Types = [get_term_type(Arg, Vst) || Arg <- Args],
+ branch(Fail, Vst,
+ fun(FailVst) ->
+ infer_relop_types('<', Args, Types, FailVst)
+ end,
+ fun(SuccVst) ->
+ infer_relop_types('>=', Args, Types, SuccVst)
+ end);
vi({test,_Op,{f,Lbl},Ss}, Vst) ->
- %% is_lt, is_gt, et cetera.
- validate_src([unpack_typed_arg(Arg) || Arg <- Ss], Vst),
+ validate_src([unpack_typed_arg(Arg, Vst) || Arg <- Ss], Vst),
branch(Lbl, Vst);
%%
@@ -1044,12 +1047,12 @@ vi({bs_set_position, Ctx, Pos}, Vst0) ->
%% Floating-point instructions (excluding BIFs)
%%
vi({fconv,Src0,{fr,_}=Dst}, Vst) ->
- Src = unpack_typed_arg(Src0),
+ Src = unpack_typed_arg(Src0, Vst),
assert_term(Src, Vst),
branch(?EXCEPTION_LABEL, Vst,
fun(SuccVst0) ->
- SuccVst = update_type(fun meet/2, number, Src, SuccVst0),
+ SuccVst = update_type(fun meet/2, #t_number{}, Src, SuccVst0),
set_freg(Dst, SuccVst)
end);
@@ -1094,7 +1097,7 @@ vi(bs_init_writable=I, Vst) ->
vi({bs_create_bin,{f,Fail},Heap,Live,Unit,Dst,{list,List0}}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
- List = [unpack_typed_arg(Arg) || Arg <- List0],
+ List = [unpack_typed_arg(Arg, Vst0) || Arg <- List0],
verify_create_bin_list(List, Vst0),
Vst = prune_x_regs(Live, Vst0),
branch(Fail, Vst,
@@ -1218,6 +1221,85 @@ vi({bs_put_utf32,{f,Fail},_,Src}, Vst) ->
vi(_, _) ->
error(unknown_instruction).
+infer_relop_types(Op, Args, Types, Vst) ->
+ case infer_relop_types(Op, Types) of
+ [] ->
+ Vst;
+ Infer ->
+ Zipped = zip(Args, Infer),
+ foldl(fun({V,T}, Acc) ->
+ update_type(fun meet/2, T, V, Acc)
+ end, Vst, Zipped)
+ end.
+
+infer_relop_types(Op, [#t_integer{elements=R1},
+ #t_integer{elements=R2}]) ->
+ case beam_bounds:infer_relop_types(Op, R1, R2) of
+ {NewR1,NewR2} ->
+ NewType1 = #t_integer{elements=NewR1},
+ NewType2 = #t_integer{elements=NewR2},
+ [NewType1,NewType2];
+ none ->
+ [none, none];
+ any ->
+ []
+ end;
+infer_relop_types(Op0, [Type1,Type2]) ->
+ Op = case Op0 of
+ '<' -> '=<';
+ '>' -> '>=';
+ _ -> Op0
+ end,
+ case {infer_get_range(Type1),infer_get_range(Type2)} of
+ {none,_}=R ->
+ [infer_relop_any(Op, R, Type1),Type2];
+ {_,none}=R ->
+ [Type1,infer_relop_any(Op, R, Type2)];
+ {R1,R2} ->
+ case beam_bounds:infer_relop_types(Op, R1, R2) of
+ {NewR1,NewR2} ->
+ NewType1 = meet(#t_number{elements=NewR1}, Type1),
+ NewType2 = meet(#t_number{elements=NewR2}, Type2),
+ [NewType1,NewType2];
+ none ->
+ [none, none];
+ any ->
+ []
+ end
+ end;
+infer_relop_types(_, _) ->
+ [].
+
+infer_relop_any('=<', {none,any}, Type) ->
+ N = #t_number{},
+ meet(N, Type);
+infer_relop_any('=<', {none,{_,Max}}, Type) ->
+ N = infer_make_number({'-inf',Max}),
+ meet(N, Type);
+infer_relop_any('>=', {any,none}, Type) ->
+ N = #t_number{},
+ meet(N, Type);
+infer_relop_any('>=', {{_,Max},none}, Type) ->
+ N = infer_make_number({'-inf',Max}),
+ meet(N, Type);
+infer_relop_any('>=', {none,{Min,_}}, Type) when is_integer(Min) ->
+ N = #t_number{elements={'-inf',Min}},
+ meet(subtract(any, N), Type);
+infer_relop_any('=<', {{Min,_},none}, Type) when is_integer(Min) ->
+ N = #t_number{elements={'-inf',Min}},
+ meet(subtract(any, N), Type);
+infer_relop_any(_, _, Type) ->
+ Type.
+
+infer_make_number({'-inf','+inf'}) ->
+ #t_number{};
+infer_make_number({_,_}=R) ->
+ #t_number{elements=R}.
+
+infer_get_range(#t_integer{elements=R}) -> R;
+infer_get_range(#t_number{elements=R}) -> R;
+infer_get_range(_) -> none.
+
validate_var_info([{fun_type, Type} | Info], Reg, Vst0) ->
%% Explicit type information inserted after make_fun2 instructions to mark
%% the return type of the created fun.
@@ -1272,6 +1354,12 @@ validate_body_call(Func, Live,
verify_call_args(Func, Live, Vst),
SuccFun = fun(SuccVst0) ->
+ %% Note that we don't try to infer anything from the
+ %% argument types, as that may cause types to become
+ %% concrete "too early."
+ %%
+ %% See beam_types_SUITE:premature_concretization/1 for
+ %% details.
{RetType, _, _} = call_types(Func, Live, SuccVst0),
true = RetType =/= none, %Assertion.
@@ -1327,7 +1415,7 @@ verify_get_map(Fail, Src, List, Vst0) ->
clobber_map_vals(List, Src, FailVst)
end,
fun(SuccVst) ->
- Keys = extract_map_keys(List),
+ Keys = extract_map_keys(List, SuccVst),
assert_unique_map_keys(Keys),
extract_map_vals(List, Src, SuccVst)
end).
@@ -1342,7 +1430,7 @@ verify_get_map(Fail, Src, List, Vst0) ->
%% We must be careful to preserve the uninitialized status for Y registers
%% that have been allocated but not yet defined.
clobber_map_vals([Key0, Dst | T], Map, Vst0) ->
- Key = unpack_typed_arg(Key0),
+ Key = unpack_typed_arg(Key0, Vst0),
case is_reg_initialized(Dst, Vst0) of
true ->
Vst = extract_term(any, {bif,map_get}, [Key, Map], Dst, Vst0),
@@ -1364,9 +1452,9 @@ is_reg_initialized({y,_}=Reg, #vst{current=#st{ys=Ys}}) ->
end;
is_reg_initialized(V, #vst{}) -> error({not_a_register, V}).
-extract_map_keys([Key,_Val | T]) ->
- [unpack_typed_arg(Key) | extract_map_keys(T)];
-extract_map_keys([]) ->
+extract_map_keys([Key,_Val | T], Vst) ->
+ [unpack_typed_arg(Key, Vst) | extract_map_keys(T, Vst)];
+extract_map_keys([], _Vst) ->
[].
@@ -1380,7 +1468,7 @@ extract_map_vals([Key0, Dst | Vs], Map, Seen0, Vst0, Vsti0) ->
%% The destinations must not overwrite each other.
error(conflicting_destinations);
false ->
- Key = unpack_typed_arg(Key0),
+ Key = unpack_typed_arg(Key0, Vsti0),
assert_term(Key, Vst0),
case bif_types(map_get, [Key, Map], Vst0) of
{none, _, _} ->
@@ -1405,7 +1493,7 @@ verify_put_map(Op, Fail, Src, Dst, Live, List, Vst0) ->
SuccFun = fun(SuccVst0) ->
SuccVst = prune_x_regs(Live, SuccVst0),
- Keys = extract_map_keys(List),
+ Keys = extract_map_keys(List, SuccVst),
assert_unique_map_keys(Keys),
Type = put_map_type(Src, List, Vst),
@@ -1422,17 +1510,48 @@ verify_put_map(Op, Fail, Src, Dst, Live, List, Vst0) ->
end.
put_map_type(Map0, List, Vst) ->
- Map = normalize(get_term_type(Map0, Vst)),
+ Map = get_term_type(Map0, Vst),
pmt_1(List, Vst, Map).
pmt_1([Key0, Value0 | List], Vst, Acc0) ->
- Key = normalize(get_term_type(Key0, Vst)),
- Value = normalize(get_term_type(Value0, Vst)),
+ Key = get_term_type(Key0, Vst),
+ Value = get_term_type(Value0, Vst),
{Acc, _, _} = beam_call_types:types(maps, put, [Key, Value, Acc0]),
pmt_1(List, Vst, Acc);
pmt_1([], _Vst, Acc) ->
Acc.
+verify_update_record(Size, Src, Dst, List, Vst0) ->
+ assert_type(#t_tuple{exact=true,size=Size}, Src, Vst0),
+ verify_y_init(Vst0),
+
+ Vst = eat_heap(Size + 1, Vst0),
+
+ case update_tuple_type(List, Src, Vst) of
+ none -> error(invalid_index);
+ Type -> create_term(Type, update_record, [], Dst, Vst)
+ end.
+
+update_tuple_type([_|_]=Updates0, Src, Vst) ->
+ Filter = #t_tuple{size=update_tuple_highest_index(Updates0, -1)},
+ case meet(get_term_type(Src, Vst), Filter) of
+ none ->
+ none;
+ TupleType ->
+ Updates = update_tuple_type_1(Updates0, Vst),
+ beam_types:update_tuple(TupleType, Updates)
+ end.
+
+update_tuple_type_1([Index, Value | Updates], Vst) ->
+ [{Index, get_term_type(Value, Vst)} | update_tuple_type_1(Updates, Vst)];
+update_tuple_type_1([], _Vst) ->
+ [].
+
+update_tuple_highest_index([Index, _Val | List], Acc) when is_integer(Index) ->
+ update_tuple_highest_index(List, max(Index, Acc));
+update_tuple_highest_index([], Acc) when Acc >= 1 ->
+ Acc.
+
verify_create_bin_list([{atom,string},_Seg,Unit,Flags,Val,Size|Args], Vst) ->
assert_bs_unit({atom,string}, Unit),
assert_term(Flags, Vst),
@@ -1463,7 +1582,7 @@ update_create_bin_list([], Vst) -> Vst.
update_create_bin_type(append) -> #t_bitstring{};
update_create_bin_type(private_append) -> #t_bitstring{};
update_create_bin_type(binary) -> #t_bitstring{};
-update_create_bin_type(float) -> number;
+update_create_bin_type(float) -> #t_number{};
update_create_bin_type(integer) -> #t_integer{};
update_create_bin_type(utf8) -> #t_integer{};
update_create_bin_type(utf16) -> #t_integer{};
@@ -1591,44 +1710,129 @@ validate_bs_start_match({f,Fail}, Live, Src, Dst, Vst) ->
end).
%%
-%% Common code for validating bs_get* instructions.
-%%
-validate_bs_get(Op, Fail, Ctx0, Live, Stride, Type, Dst, Vst) ->
- Ctx = unpack_typed_arg(Ctx0),
+%% Validate the bs_match instruction.
+%%
+
+validate_bs_match([I|Is], Ctx, Unit0, Vst0) ->
+ case I of
+ {ensure_at_least,_Size,Unit} ->
+ Type = #t_bs_context{tail_unit=Unit},
+ Vst1 = update_bs_unit(Ctx, Unit, Vst0),
+ Vst = update_type(fun meet/2, Type, Ctx, Vst1),
+ validate_bs_match(Is, Ctx, Unit, Vst);
+ {ensure_exactly,Stride} ->
+ Vst = advance_bs_context(Ctx, Stride, Vst0),
+ validate_bs_match(Is, Ctx, Unit0, Vst);
+ {'=:=',nil,Bits,Value} when Bits =< 64, is_integer(Value) ->
+ validate_bs_match(Is, Ctx, Unit0, Vst0);
+ {Type0,Live,{literal,Flags},Size,Unit,Dst} when Type0 =:= binary;
+ Type0 =:= integer ->
+ validate_ctx_live(Ctx, Live),
+ verify_live(Live, Vst0),
+ Vst1 = prune_x_regs(Live, Vst0),
+ Type = case Type0 of
+ integer ->
+ true = is_integer(Size), %Assertion.
+ bs_integer_type({Size, Size}, Unit, Flags);
+ binary ->
+ SizeUnit = bsm_size_unit({integer,Size}, Unit),
+ #t_bitstring{size_unit=SizeUnit}
+ end,
+ Vst = extract_term(Type, bs_match, [Ctx], Dst, Vst1, Vst0),
+ validate_bs_match(Is, Ctx, Unit0, Vst);
+ {skip,_Stride} ->
+ validate_bs_match(Is, Ctx, Unit0, Vst0);
+ {get_tail,Live,_,Dst} ->
+ validate_ctx_live(Ctx, Live),
+ verify_live(Live, Vst0),
+ Vst1 = prune_x_regs(Live, Vst0),
+ #t_bs_context{tail_unit=Unit} = get_concrete_type(Ctx, Vst0),
+ Type = #t_bitstring{size_unit=Unit},
+ Vst = extract_term(Type, get_tail, [Ctx], Dst, Vst1, Vst0),
+ %% In rare circumstance, there can be multiple `get_tail` sub commands.
+ validate_bs_match(Is, Ctx, Unit, Vst)
+ end;
+validate_bs_match([], _Ctx, _Unit, Vst) ->
+ Vst.
- assert_no_exception(Fail),
+validate_ctx_live({x,X}=Ctx, Live) when X >= Live ->
+ error({live_does_not_preserve_context,Live,Ctx});
+validate_ctx_live(_, _) ->
+ ok.
- assert_type(#t_bs_context{}, Ctx, Vst),
- verify_live(Live, Vst),
- verify_y_init(Vst),
+validate_failed_bs_match([{ensure_at_least,_Size,Unit}|_], Ctx, Vst) ->
+ Type = #t_bs_context{tail_unit=Unit},
+ update_type(fun subtract/2, Type, Ctx, Vst);
+validate_failed_bs_match([_|Is], Ctx, Vst) ->
+ validate_failed_bs_match(Is, Ctx, Vst);
+validate_failed_bs_match([], _Ctx, Vst) ->
+ Vst.
- branch(Fail, Vst,
- fun(SuccVst0) ->
- SuccVst1 = advance_bs_context(Ctx, Stride, SuccVst0),
- SuccVst = prune_x_regs(Live, SuccVst1),
- extract_term(Type, Op, [Ctx], Dst, SuccVst, SuccVst0)
- end).
+bs_integer_type(Bounds, Unit, Flags) ->
+ case beam_bounds:bounds('*', Bounds, {Unit, Unit}) of
+ {_, MaxBits} when is_integer(MaxBits), MaxBits >= 1, MaxBits =< 64 ->
+ case member(signed, Flags) of
+ true ->
+ Max = (1 bsl (MaxBits - 1)) - 1,
+ Min = -(Max + 1),
+ beam_types:make_integer(Min, Max);
+ false ->
+ Max = (1 bsl MaxBits) - 1,
+ beam_types:make_integer(0, Max)
+ end;
+ {_, 0} ->
+ beam_types:make_integer(0);
+ _ ->
+ case member(signed, Flags) of
+ true -> #t_integer{};
+ false -> #t_integer{elements={0,'+inf'}}
+ end
+ end.
-validate_bs_get_all(Op, Fail, Ctx0, Live, Stride, Type, Dst, Vst) ->
- Ctx = unpack_typed_arg(Ctx0),
+%%
+%% Common code for validating bs_get* instructions.
+%%
+
+validate_bs_get(_Op, Fail, Ctx0, Live, _Stride, none, _Dst, Vst) ->
+ Ctx = unpack_typed_arg(Ctx0, Vst),
+ validate_bs_get_1(
+ Fail, Ctx, Live, Vst,
+ fun(SuccVst) ->
+ kill_state(SuccVst)
+ end);
+validate_bs_get(Op, Fail, Ctx0, Live, Stride, Type, Dst, Vst) ->
+ Ctx = unpack_typed_arg(Ctx0, Vst),
+ validate_bs_get_1(
+ Fail, Ctx, Live, Vst,
+ fun(SuccVst0) ->
+ SuccVst1 = advance_bs_context(Ctx, Stride, SuccVst0),
+ SuccVst = prune_x_regs(Live, SuccVst1),
+ extract_term(Type, Op, [Ctx], Dst, SuccVst, SuccVst0)
+ end).
+validate_bs_get_all(Op, Fail, Ctx0, Live, Stride, Type, Dst, Vst) ->
+ Ctx = unpack_typed_arg(Ctx0, Vst),
+ validate_bs_get_1(
+ Fail, Ctx, Live, Vst,
+ fun(SuccVst0) ->
+ %% This acts as an implicit unit test on the current match
+ %% position, so we'll update the unit in case we rewind here
+ %% later on.
+ SuccVst1 = update_bs_unit(Ctx, Stride, SuccVst0),
+
+ SuccVst2 = advance_bs_context(Ctx, Stride, SuccVst1),
+ SuccVst = prune_x_regs(Live, SuccVst2),
+ extract_term(Type, Op, [Ctx], Dst, SuccVst, SuccVst0)
+ end).
+
+validate_bs_get_1(Fail, Ctx, Live, Vst, SuccFun) ->
assert_no_exception(Fail),
assert_type(#t_bs_context{}, Ctx, Vst),
verify_live(Live, Vst),
verify_y_init(Vst),
- branch(Fail, Vst,
- fun(SuccVst0) ->
- %% This acts as an implicit unit test on the current match
- %% position, so we'll update the unit in case we rewind here
- %% later on.
- SuccVst1 = update_bs_unit(Ctx, Stride, SuccVst0),
-
- SuccVst2 = advance_bs_context(Ctx, Stride, SuccVst1),
- SuccVst = prune_x_regs(Live, SuccVst2),
- extract_term(Type, Op, [Ctx], Dst, SuccVst, SuccVst0)
- end).
+ branch(Fail, Vst, SuccFun).
%%
%% Common code for validating bs_skip* instructions.
@@ -1711,7 +1915,7 @@ invalidate_current_ms_position(Ctx, #vst{current=St0}=Vst) ->
%% Common code for is_$type instructions.
%%
type_test(Fail, Type, Reg0, Vst) ->
- Reg = unpack_typed_arg(Reg0),
+ Reg = unpack_typed_arg(Reg0, Vst),
assert_term(Reg, Vst),
assert_no_exception(Fail),
@@ -1869,7 +2073,7 @@ init_stack(Tag, Y, Vst) ->
init_stack(Tag, Y - 1, create_tag(Tag, allocate, [], {y,Y}, Vst)).
trim_stack(From, To, Top, #st{ys=Ys0}=St) when From =:= Top ->
- Ys = maps:filter(fun({y,Y}, _) -> Y < To end, Ys0),
+ Ys = #{Reg => Val || {y,Y}=Reg := Val <- Ys0, Y < To},
St#st{numy=To,ys=Ys};
trim_stack(From, To, Top, St0) ->
Src = {y, From},
@@ -1924,9 +2128,7 @@ prune_x_regs(Live, #vst{current=St0}=Vst) when is_integer(Live) ->
({y,_}) ->
true
end, Fragile0),
- Xs = maps:filter(fun({x,X}, _) ->
- X < Live
- end, Xs0),
+ Xs = #{Reg => Val || {x,X}=Reg := Val <- Xs0, X < Live},
St = St0#st{fragile=Fragile,xs=Xs},
Vst#vst{current=St}.
@@ -2078,16 +2280,47 @@ infer_types(CompareOp, LHS, {Kind,_}=RHS, Vst) when Kind =:= x; Kind =:= y ->
infer_types(CompareOp, LHS, RHS, #vst{current=#st{vs=Vs}}=Vst0) ->
case Vs of
#{ LHS := LEntry, RHS := REntry } ->
- Vst = infer_types_1(LEntry, RHS, CompareOp, Vst0),
- infer_types_1(REntry, LHS, CompareOp, Vst);
+ Vst = infer_types_1(LEntry, canonical_value(RHS, Vst0),
+ CompareOp, Vst0),
+ infer_types_1(REntry, canonical_value(LHS, Vst),
+ CompareOp, Vst);
#{ LHS := LEntry } ->
- infer_types_1(LEntry, RHS, CompareOp, Vst0);
+ infer_types_1(LEntry, canonical_value(RHS, Vst0), CompareOp, Vst0);
#{ RHS := REntry } ->
- infer_types_1(REntry, LHS, CompareOp, Vst0);
+ infer_types_1(REntry, canonical_value(LHS, Vst0), CompareOp, Vst0);
#{} ->
Vst0
end.
+infer_types_1(#value{op={bif,'and'},args=[LHS,RHS]}, Val, Op, Vst0) ->
+ case Val of
+ {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
+ Vst = update_eq_types(LHS, {atom,true}, Vst0),
+ update_eq_types(RHS, {atom,true}, Vst);
+ _ ->
+ %% As either of the arguments could be 'false', we can't infer
+ %% anything useful from that result.
+ Vst0
+ end;
+infer_types_1(#value{op={bif,'or'},args=[LHS,RHS]}, Val, Op, Vst0) ->
+ case Val of
+ {atom, Bool} when Op =:= eq_exact, not Bool; Op =:= ne_exact, Bool ->
+ Vst = update_eq_types(LHS, {atom,false}, Vst0),
+ update_eq_types(RHS, {atom,false}, Vst);
+ _ ->
+ %% As either of the arguments could be 'true', we can't infer
+ %% anything useful from that result.
+ Vst0
+ end;
+infer_types_1(#value{op={bif,'not'},args=[Arg]}, Val, Op, Vst0) ->
+ case Val of
+ {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
+ update_eq_types(Arg, {atom,false}, Vst0);
+ {atom, Bool} when Op =:= eq_exact, not Bool; Op =:= ne_exact, Bool ->
+ update_eq_types(Arg, {atom,true}, Vst0);
+ _ ->
+ Vst0
+ end;
infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}, Val, Op, Vst) ->
case Val of
{atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
@@ -2106,6 +2339,18 @@ infer_types_1(#value{op={bif,'=/='},args=[LHS,RHS]}, Val, Op, Vst) ->
_ ->
Vst
end;
+infer_types_1(#value{op={bif,RelOp},args=[_,_]=Args}, Val, Op, Vst)
+ when RelOp =:= '<'; RelOp =:= '=<'; RelOp =:= '>='; RelOp =:= '>' ->
+ case Val of
+ {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
+ Types = [get_term_type(Arg, Vst) || Arg <- Args],
+ infer_relop_types(RelOp, Args, Types, Vst);
+ {atom, Bool} when Op =:= ne_exact, Bool; Op =:= eq_exact, not Bool ->
+ Types = [get_term_type(Arg, Vst) || Arg <- Args],
+ infer_relop_types(invert_relop(RelOp), Args, Types, Vst);
+ _ ->
+ Vst
+ end;
infer_types_1(#value{op={bif,is_atom},args=[Src]}, Val, Op, Vst) ->
infer_type_test_bif(#t_atom{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_boolean},args=[Src]}, Val, Op, Vst) ->
@@ -2139,7 +2384,7 @@ infer_types_1(#value{op={bif,is_list},args=[Src]}, Val, Op, Vst) ->
infer_types_1(#value{op={bif,is_map},args=[Src]}, Val, Op, Vst) ->
infer_type_test_bif(#t_map{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_number},args=[Src]}, Val, Op, Vst) ->
- infer_type_test_bif(number, Src, Val, Op, Vst);
+ infer_type_test_bif(#t_number{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_pid},args=[Src]}, Val, Op, Vst) ->
infer_type_test_bif(pid, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_port},args=[Src]}, Val, Op, Vst) ->
@@ -2195,6 +2440,11 @@ infer_type_test_bif(Type, Src, Val, Op, Vst) ->
Vst
end.
+invert_relop('<') -> '>=';
+invert_relop('=<') -> '>';
+invert_relop('>=') -> '<';
+invert_relop('>') -> '=<'.
+
%%%
%%% Keeping track of types.
%%%
@@ -2321,13 +2571,30 @@ update_container_type(Type, Ref, #vst{current=#st{vs=Vs}}=Vst) ->
case Vs of
#{ Ref := #value{op={bif,element},
args=[{integer,Index},Tuple]} } when Index >= 1 ->
- Es = beam_types:set_tuple_element(Index, Type, #{}),
- TupleType = #t_tuple{size=Index,elements=Es},
- update_type(fun meet/2, TupleType, Tuple, Vst);
+ case {Index,Type} of
+ {1,#t_atom{elements=[_,_|_]}} ->
+ %% The first element is one atom out of a set of
+ %% at least two atoms. We must take care to
+ %% construct an atom set.
+ update_type(fun meet_tuple_set/2, Type, Tuple, Vst);
+ {_,_} ->
+ Es = beam_types:set_tuple_element(Index, Type, #{}),
+ TupleType = #t_tuple{size=Index,elements=Es},
+ update_type(fun meet/2, TupleType, Tuple, Vst)
+ end;
#{} ->
Vst
end.
+meet_tuple_set(Type, #t_atom{elements=Atoms}) ->
+ %% Try to create a tuple set out of the known atoms for the first element.
+ #t_tuple{size=Size,exact=Exact} = normalize(meet(Type, #t_tuple{})),
+ Tuples = [#t_tuple{size=Size,exact=Exact,
+ elements=#{1 => #t_atom{elements=[A]}}} ||
+ A <- Atoms],
+ TupleSet = foldl(fun join/2, hd(Tuples), tl(Tuples)),
+ meet(Type, TupleSet).
+
update_eq_types(LHS, RHS, Vst0) ->
LType = get_term_type(LHS, Vst0),
RType = get_term_type(RHS, Vst0),
@@ -2348,7 +2615,7 @@ update_ne_types_1(LHS, RHS, Vst0) ->
%% is a bit trickier since all we know is that the *value* of LHS differs
%% from RHS, so we can't blindly subtract their types.
%%
- %% Consider `number =/= #t_integer{}`; all we know is that LHS isn't equal
+ %% Consider `#number{} =/= #t_integer{}`; all we know is that LHS isn't equal
%% to some *specific integer* of unknown value, and if we were to subtract
%% #t_integer{} we would erroneously infer that the new type is float.
%%
@@ -2361,12 +2628,9 @@ update_ne_types_1(LHS, RHS, Vst0) ->
%% If LHS has a specific value after subtraction we can infer types
%% as if we've made an exact match, which is much stronger than
%% ne_exact.
- LType = get_term_type(LHS, Vst),
- case beam_types:get_singleton_value(LType) of
- {ok, Value} ->
- infer_types(eq_exact, LHS, value_to_literal(Value), Vst);
- error ->
- Vst
+ case canonical_value(LHS, Vst) of
+ LHS -> Vst;
+ Value -> infer_types(eq_exact, LHS, Value, Vst)
end;
false ->
Vst0
@@ -2496,6 +2760,18 @@ value_to_literal(F) when is_float(F) -> {float,F};
value_to_literal(I) when is_integer(I) -> {integer,I};
value_to_literal(Other) -> {literal,Other}.
+canonical_value(Val, Vst) ->
+ Type = get_term_type(Val, Vst),
+ case beam_types:is_singleton_type(Type) of
+ true ->
+ case beam_types:get_singleton_value(Type) of
+ {ok, Res} -> value_to_literal(Res);
+ error -> Val
+ end;
+ false ->
+ Val
+ end.
+
%% These are just wrappers around their equivalents in beam_types, which
%% handle the validator-specific #t_abstract{} type.
%%
@@ -2550,9 +2826,19 @@ validate_src(Ss, Vst) when is_list(Ss) ->
_ = [assert_term(S, Vst) || S <- Ss],
ok.
-unpack_typed_arg(#tr{r=Reg}) ->
+unpack_typed_arg(#tr{r=Reg,t=Type}, Vst) ->
+ %% The validator is not yet clever enough to do proper range analysis like
+ %% the main type pass, so our types will be a bit cruder here, but they
+ %% should at the very least not be in direct conflict.
+ Current = get_movable_term_type(Reg, Vst),
+ case beam_types:meet(Current, Type) of
+ none ->
+ throw({bad_typed_register, Current, Type});
+ _ ->
+ ok
+ end,
Reg;
-unpack_typed_arg(Arg) ->
+unpack_typed_arg(Arg, _Vst) ->
Arg.
%% get_term_type(Src, ValidatorState) -> Type
@@ -2772,7 +3058,7 @@ merge_states_1(StA, StB, Counter0) ->
RecvSt = merge_receive_state(RecvStA, RecvStB),
MsPos = merge_ms_positions(MsPosA, MsPosB, Vs),
Fragile = merge_fragility(FragA, FragB),
- NumY = merge_stk(NumYA, NumYB),
+ NumY = merge_stk(YsA, YsB, NumYA, NumYB),
Ct = merge_ct(CtA, CtB),
St = #st{xs=Xs,ys=Ys,vs=Vs,fragile=Fragile,numy=NumY,
@@ -2910,8 +3196,38 @@ merge_ms_positions_1([], _MsPosA, _MsPosB, _Vs, Acc) ->
merge_receive_state(Same, Same) -> Same;
merge_receive_state(_, _) -> undecided.
-merge_stk(S, S) -> S;
-merge_stk(_, _) -> undecided.
+merge_stk(_, _, S, S) ->
+ S;
+merge_stk(YsA, YsB, StkA, StkB) ->
+ merge_stk_undecided(YsA, YsB, StkA, StkB).
+
+merge_stk_undecided(YsA, YsB, {undecided, StkA}, {undecided, StkB}) ->
+ %% We're merging two branches with different stack sizes. This is only okay
+ %% if we're about to throw an exception, in which case all Y registers must
+ %% be initialized on both paths.
+ ok = merge_stk_verify_init(StkA - 1, YsA),
+ ok = merge_stk_verify_init(StkB - 1, YsB),
+
+ {undecided, min(StkA, StkB)};
+merge_stk_undecided(YsA, YsB, StkA, StkB) when is_integer(StkA) ->
+ merge_stk_undecided(YsA, YsB, {undecided, StkA}, StkB);
+merge_stk_undecided(YsA, YsB, StkA, StkB) when is_integer(StkB) ->
+ merge_stk_undecided(YsA, YsB, StkA, {undecided, StkB});
+merge_stk_undecided(YsA, YsB, none, StkB) ->
+ merge_stk_undecided(YsA, YsB, {undecided, 0}, StkB);
+merge_stk_undecided(YsA, YsB, StkA, none) ->
+ merge_stk_undecided(YsA, YsB, StkA, {undecided, 0}).
+
+merge_stk_verify_init(-1, _Ys) ->
+ ok;
+merge_stk_verify_init(Y, Ys) ->
+ Reg = {y, Y},
+ case Ys of
+ #{ Reg := TagOrVRef } when TagOrVRef =/= uninitialized ->
+ merge_stk_verify_init(Y - 1, Ys);
+ #{} ->
+ error({unsafe_stack, Reg, Ys})
+ end.
merge_ct(S, S) -> S;
merge_ct(Ct0, Ct1) -> merge_ct_1(Ct0, Ct1).
@@ -2930,8 +3246,9 @@ verify_y_init(#vst{current=#st{numy=NumY,ys=Ys}}=Vst) when is_integer(NumY) ->
true = NumY > HighestY, %Assertion.
verify_y_init_1(NumY - 1, Vst),
ok;
-verify_y_init(#vst{current=#st{numy=undecided,ys=Ys}}=Vst) ->
+verify_y_init(#vst{current=#st{numy={undecided,MinSlots},ys=Ys}}=Vst) ->
HighestY = maps:fold(fun({y,Y}, _, Acc) -> max(Y, Acc) end, -1, Ys),
+ true = MinSlots > HighestY, %Assertion.
verify_y_init_1(HighestY, Vst);
verify_y_init(#vst{}) ->
ok.
@@ -2964,7 +3281,7 @@ verify_live_1(X, Vst) when is_integer(X) ->
verify_no_ct(#vst{current=#st{numy=none}}) ->
ok;
-verify_no_ct(#vst{current=#st{numy=undecided}}) ->
+verify_no_ct(#vst{current=#st{numy={undecided,_}}}) ->
error(unknown_size_of_stackframe);
verify_no_ct(#vst{current=St}=Vst) ->
case collect_try_catch_tags(St#st.numy - 1, Vst, []) of
@@ -3107,7 +3424,7 @@ assert_not_fragile(Lit, #vst{}) ->
%%%
bif_types(Op, Ss, Vst) ->
- Args = [normalize(get_term_type(Arg, Vst)) || Arg <- Ss],
+ Args = [get_term_type(Arg, Vst) || Arg <- Ss],
case {Op,Ss} of
{element,[_,{literal,Tuple}]} when tuple_size(Tuple) > 0 ->
case beam_call_types:types(erlang, Op, Args) of
@@ -3118,7 +3435,17 @@ bif_types(Op, Ss, Vst) ->
Other
end;
{_,_} ->
- beam_call_types:types(erlang, Op, Args)
+ Res0 = beam_call_types:types(erlang, Op, Args),
+ {Ret0, ArgTypes, SubSafe} = Res0,
+
+ %% Match the non-converging range analysis done in
+ %% `beam_ssa_type:opt_ranges/1`. This is safe since the validator
+ %% doesn't have to worry about convergence.
+ case beam_call_types:arith_type({bif, Op}, Args) of
+ any -> Res0;
+ Ret0 -> Res0;
+ Ret -> {meet(Ret, Ret0), ArgTypes, SubSafe}
+ end
end.
join_tuple_elements(Tuple) ->
@@ -3134,6 +3461,9 @@ join_tuple_elements(I, Tuple, Type0) ->
call_types({extfunc,M,F,A}, A, Vst) ->
Args = get_call_args(A, Vst),
beam_call_types:types(M, F, Args);
+call_types(bs_init_writable, A, Vst) ->
+ T = beam_types:make_type_from_value(<<>>),
+ {T, get_call_args(A, Vst), false};
call_types(_, A, Vst) ->
{any, get_call_args(A, Vst), false}.
@@ -3146,7 +3476,7 @@ will_bif_succeed(Op, Ss, Vst) ->
true ->
'maybe';
false ->
- Args = [normalize(get_term_type(Arg, Vst)) || Arg <- Ss],
+ Args = [get_term_type(Arg, Vst) || Arg <- Ss],
beam_call_types:will_succeed(erlang, Op, Args)
end.
@@ -3181,7 +3511,7 @@ get_call_args(Arity, Vst) ->
get_call_args_1(Arity, Arity, _) ->
[];
get_call_args_1(N, Arity, Vst) when N < Arity ->
- ArgType = normalize(get_movable_term_type({x,N}, Vst)),
+ ArgType = get_movable_term_type({x,N}, Vst),
[ArgType | get_call_args_1(N + 1, Arity, Vst)].
check_limit({x,X}=Src) when is_integer(X) ->
diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl
index bc28f58712..f824703a8a 100644
--- a/lib/compiler/src/cerl.erl
+++ b/lib/compiler/src/cerl.erl
@@ -156,6 +156,7 @@
-type c_map() :: #c_map{}.
-type c_map_pair() :: #c_map_pair{}.
-type c_module() :: #c_module{}.
+-type c_opaque() :: #c_opaque{}.
-type c_primop() :: #c_primop{}.
-type c_receive() :: #c_receive{}.
-type c_seq() :: #c_seq{}.
@@ -168,7 +169,8 @@
| c_call() | c_case() | c_catch() | c_clause() | c_cons()
| c_fun() | c_let() | c_letrec() | c_literal()
| c_map() | c_map_pair()
- | c_module() | c_primop() | c_receive() | c_seq()
+ | c_module() | c_opaque()
+ | c_primop() | c_receive() | c_seq()
| c_try() | c_tuple() | c_values() | c_var().
-type var_name() :: integer() | atom() | {atom(), integer()}.
@@ -292,7 +294,8 @@ type(#c_seq{}) -> seq;
type(#c_try{}) -> 'try';
type(#c_tuple{}) -> tuple;
type(#c_values{}) -> values;
-type(#c_var{}) -> var.
+type(#c_var{}) -> var;
+type(#c_opaque{}) -> opaque.
%% @spec is_leaf(Node::cerl()) -> boolean()
@@ -1603,7 +1606,7 @@ map_es(#c_literal{anno=As,val=M}) when is_map(M) ->
[ann_c_map_pair(As,
#c_literal{anno=As,val='assoc'},
#c_literal{anno=As,val=K},
- #c_literal{anno=As,val=V}) || {K,V} <- maps:to_list(M)];
+ #c_literal{anno=As,val=V}) || K := V <- M];
map_es(#c_map{es = Es}) ->
Es.
@@ -1647,34 +1650,41 @@ ann_c_map(As, Es) ->
-spec ann_c_map([term()], c_map() | c_literal(), [c_map_pair()]) -> c_map() | c_literal().
-ann_c_map(As,#c_literal{val=M},Es) when is_map(M) ->
- fold_map_pairs(As,Es,M);
-ann_c_map(As,M,Es) ->
- #c_map{arg=M, es=Es, anno=As }.
+ann_c_map(As, #c_literal{val=M0}=Lit, Es) when is_map(M0) ->
+ case update_map_literal(Es, M0) of
+ none ->
+ #c_map{arg=Lit, es=Es, anno=As};
+ M1 ->
+ #c_literal{anno=As, val=M1}
+ end;
+ann_c_map(As, M, Es) ->
+ #c_map{arg=M, es=Es, anno=As}.
-fold_map_pairs(As,[],M) -> #c_literal{anno=As,val=M};
-%% M#{ K => V}
-fold_map_pairs(As,[#c_map_pair{op=#c_literal{val=assoc},key=Ck,val=Cv}=E|Es],M) ->
+update_map_literal([#c_map_pair{op=#c_literal{val=assoc},key=Ck,val=Cv}|Es], M) ->
+ %% M#{K => V}
case is_lit_list([Ck,Cv]) of
true ->
[K,V] = lit_list_vals([Ck,Cv]),
- fold_map_pairs(As,Es,maps:put(K,V,M));
+ update_map_literal(Es, M#{K => V});
false ->
- #c_map{arg=#c_literal{val=M,anno=As}, es=[E|Es], anno=As }
+ none
end;
-%% M#{ K := V}
-fold_map_pairs(As,[#c_map_pair{op=#c_literal{val=exact},key=Ck,val=Cv}=E|Es],M) ->
+update_map_literal([#c_map_pair{op=#c_literal{val=exact},key=Ck,val=Cv}|Es], M) ->
+ %% M#{K := V}
case is_lit_list([Ck,Cv]) of
true ->
[K,V] = lit_list_vals([Ck,Cv]),
- case maps:is_key(K,M) of
- true -> fold_map_pairs(As,Es,maps:put(K,V,M));
+ case is_map_key(K, M) of
+ true ->
+ update_map_literal(Es, M#{K => V});
false ->
- #c_map{arg=#c_literal{val=M,anno=As}, es=[E|Es], anno=As }
+ none
end;
false ->
- #c_map{arg=#c_literal{val=M,anno=As}, es=[E|Es], anno=As }
- end.
+ none
+ end;
+update_map_literal([], M) ->
+ M.
-spec update_c_map(c_map(), cerl(), [cerl()]) -> c_map() | c_literal().
diff --git a/lib/compiler/src/cerl_trees.erl b/lib/compiler/src/cerl_trees.erl
index 383d9b5214..58863b24f9 100644
--- a/lib/compiler/src/cerl_trees.erl
+++ b/lib/compiler/src/cerl_trees.erl
@@ -198,7 +198,9 @@ map_1(F, T) ->
update_c_module(T, map(F, module_name(T)),
map_list(F, module_exports(T)),
map_pairs(F, module_attrs(T)),
- map_pairs(F, module_defs(T)))
+ map_pairs(F, module_defs(T)));
+ opaque ->
+ T
end.
map_list(F, [T | Ts]) ->
@@ -312,7 +314,9 @@ fold_1(F, S, T) ->
fold(F, S, module_name(T)),
module_exports(T)),
module_attrs(T)),
- module_defs(T))
+ module_defs(T));
+ opaque ->
+ S
end.
fold_list(F, S, [T | Ts]) ->
@@ -483,7 +487,9 @@ mapfold(Pre, Post, S00, T0) ->
{Es, S2} = mapfold_list(Pre, Post, S1, module_exports(T)),
{As, S3} = mapfold_pairs(Pre, Post, S2, module_attrs(T)),
{Ds, S4} = mapfold_pairs(Pre, Post, S3, module_defs(T)),
- Post(update_c_module(T, N, Es, As, Ds), S4)
+ Post(update_c_module(T, N, Es, As, Ds), S4);
+ opaque ->
+ Post(T, S0)
end;
skip ->
{T0, S00}
@@ -657,7 +663,9 @@ variables(T, S) ->
ordsets:subtract(Vs1, Vs2);
false ->
ordsets:union(Vs1, Vs2)
- end
+ end;
+ opaque ->
+ []
end.
vars_in_list(Ts, S) ->
@@ -782,7 +790,9 @@ next_free(T, Max) ->
Max2 = next_free(letrec_body(T), Max1),
next_free_in_list(letrec_vars(T), Max2);
module ->
- next_free_in_defs(module_defs(T), Max)
+ next_free_in_defs(module_defs(T), Max);
+ opaque ->
+ Max
end.
next_free_in_list([H | T], Max) ->
@@ -974,7 +984,10 @@ label(T, N, Env) ->
{Ds, N3} = label_defs(module_defs(T), N2, Env1),
{Es, N4} = label_list(module_exports(T), N3, Env1),
{As, N5} = label_ann(T, N4),
- {ann_c_module(As, module_name(T), Es, Ts, Ds), N5}
+ {ann_c_module(As, module_name(T), Es, Ts, Ds), N5};
+ opaque ->
+ %% Not labeled.
+ {T, N}
end.
label_list([T | Ts], N, Env) ->
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 7ef37a6082..9ff09911f9 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -274,7 +274,10 @@ expand_opt(r23, Os) ->
no_recv_opt, no_init_yregs |
expand_opt(r24, Os)]);
expand_opt(r24, Os) ->
- expand_opt(no_type_opt, [no_bs_create_bin, no_ssa_opt_ranges | Os]);
+ expand_opt(no_type_opt, [no_bs_create_bin, no_ssa_opt_ranges |
+ expand_opt(r25, Os)]);
+expand_opt(r25, Os) ->
+ [no_ssa_opt_update_tuple, no_bs_match, no_min_max_bifs | Os];
expand_opt(no_make_fun3, Os) ->
[no_make_fun3, no_fun_opt | Os];
expand_opt({debug_info_key,_}=O, Os) ->
@@ -288,6 +291,8 @@ expand_opt(no_type_opt=O, Os) ->
no_ssa_opt_type_finish | Os];
expand_opt(no_module_opt=O, Os) ->
[O,no_recv_opt | Os];
+expand_opt({check_ssa,Tag}, Os) ->
+ [check_ssa, Tag | Os];
expand_opt(O, Os) -> [O|Os].
-spec format_error(error_description()) -> iolist().
@@ -770,6 +775,16 @@ select_list_passes_1([P|Ps], Opts, Acc) ->
select_list_passes_1([], _, Acc) ->
{not_done,reverse(Acc)}.
+make_ssa_check_pass(PassFlag) ->
+ F = fun (Code, St) ->
+ case beam_ssa_check:module(Code, PassFlag) of
+ ok -> {ok, Code, St};
+ {error, Errors} ->
+ {error, St#compile{errors=St#compile.errors++Errors}}
+ end
+ end,
+ {iff, PassFlag, {PassFlag, F}}.
+
%% The standard passes (almost) always run.
standard_passes() ->
@@ -874,6 +889,7 @@ kernel_passes() ->
{unless,no_bsm_opt,{iff,ssalint,{pass,beam_ssa_lint}}},
{unless,no_ssa_opt,{pass,beam_ssa_opt}},
+ make_ssa_check_pass(post_ssa_opt),
{iff,dssaopt,{listing,"ssaopt"}},
{unless,no_ssa_opt,{iff,ssalint,{pass,beam_ssa_lint}}},
@@ -1026,7 +1042,13 @@ do_parse_module(DefEncoding, #compile{ifile=File,options=Opts,dir=Dir}=St) ->
{location,StartLocation},
{reserved_word_fun, ResWordFun},
{features, Features},
- extra]),
+ extra|
+ case member(check_ssa, Opts) of
+ true ->
+ [{compiler_internal,[ssa_checks]}];
+ false ->
+ []
+ end]),
case R of
%% FIXME Extra should include used features as well
{ok,Forms0,Extra} ->
@@ -1929,9 +1951,8 @@ output_encoding(F, #compile{encoding = Encoding}) ->
diffable(Code0, St) ->
{Mod,Exp,Attr,Fs0,NumLabels} = Code0,
- EntryLabels0 = [{Entry,{Name,Arity}} ||
- {function,Name,Arity,Entry,_} <- Fs0],
- EntryLabels = maps:from_list(EntryLabels0),
+ EntryLabels = #{Entry => {Name,Arity} ||
+ {function,Name,Arity,Entry,_} <- Fs0},
Fs = [diffable_fix_function(F, EntryLabels) || F <- Fs0],
Code = {Mod,Exp,Attr,Fs,NumLabels},
{ok,Code,St}.
@@ -2080,6 +2101,7 @@ pre_load() ->
beam_kernel_to_ssa,
beam_opcodes,
beam_ssa,
+ beam_ssa_alias,
beam_ssa_bc_size,
beam_ssa_bool,
beam_ssa_bsm,
@@ -2087,6 +2109,7 @@ pre_load() ->
beam_ssa_dead,
beam_ssa_opt,
beam_ssa_pre_codegen,
+ beam_ssa_private_append,
beam_ssa_recv,
beam_ssa_share,
beam_ssa_throw,
diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src
index b55d6687a4..206053cab2 100644
--- a/lib/compiler/src/compiler.app.src
+++ b/lib/compiler/src/compiler.app.src
@@ -1,7 +1,7 @@
% This is an -*- erlang -*- file.
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -36,15 +36,18 @@
beam_listing,
beam_opcodes,
beam_ssa,
+ beam_ssa_alias,
beam_ssa_bc_size,
beam_ssa_bool,
beam_ssa_bsm,
+ beam_ssa_check,
beam_ssa_codegen,
beam_ssa_dead,
beam_ssa_lint,
beam_ssa_opt,
beam_ssa_pp,
beam_ssa_pre_codegen,
+ beam_ssa_private_append,
beam_ssa_recv,
beam_ssa_share,
beam_ssa_throw,
@@ -81,5 +84,5 @@
{registered, []},
{applications, [kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["stdlib-4.0","kernel-8.4","erts-13.0",
+ {runtime_dependencies, ["stdlib-@OTP-18414@","kernel-8.4","erts-13.0",
"crypto-5.1"]}]}.
diff --git a/lib/compiler/src/core_parse.hrl b/lib/compiler/src/core_parse.hrl
index 90c796d3d9..868bee98e9 100644
--- a/lib/compiler/src/core_parse.hrl
+++ b/lib/compiler/src/core_parse.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -87,6 +87,8 @@
attrs :: [{cerl:cerl(), cerl:cerl()}],
defs :: [{cerl:cerl(), cerl:cerl()}]}).
+-record(c_opaque, {anno=[] :: list(), val :: any()}).
+
-record(c_primop, {anno=[] :: list(), name :: cerl:cerl(),
args :: [cerl:cerl()]}).
diff --git a/lib/compiler/src/core_pp.erl b/lib/compiler/src/core_pp.erl
index 65ba7cde00..09549f4f80 100644
--- a/lib/compiler/src/core_pp.erl
+++ b/lib/compiler/src/core_pp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -134,11 +134,10 @@ format_1(#c_literal{anno=A,val=Bitstring}, Ctxt) when is_bitstring(Bitstring) ->
Segs = segs_from_bitstring(Bitstring),
format_1(#c_binary{anno=A,segments=Segs}, Ctxt);
format_1(#c_literal{anno=A,val=M},Ctxt) when is_map(M) ->
- Pairs = maps:to_list(M),
Op = #c_literal{val=assoc},
Cpairs = [#c_map_pair{op=Op,
key=#c_literal{val=K},
- val=#c_literal{val=V}} || {K,V} <- Pairs],
+ val=#c_literal{val=V}} || K := V <- M],
format_1(#c_map{anno=A,arg=#c_literal{val=#{}},es=Cpairs},Ctxt);
format_1(#c_literal{val=F},_Ctxt) when is_function(F) ->
{module,M} = erlang:fun_info(F, module),
@@ -359,7 +358,9 @@ format_1(#c_module{name=N,exports=Es,attrs=As,defs=Ds}, Ctxt) ->
format_funcs(Ds, Ctxt),
nl_indent(Ctxt)
| "end"
- ].
+ ];
+format_1(#c_opaque{val=V}, Ctxt) ->
+ ["%% Opaque: ", format_1(#c_literal{val=V}, Ctxt)].
format_funcs(Fs, Ctxt) ->
format_vseq(Fs,
diff --git a/lib/compiler/src/core_scan.erl b/lib/compiler/src/core_scan.erl
index a50a2ffa8d..5064b44d7e 100644
--- a/lib/compiler/src/core_scan.erl
+++ b/lib/compiler/src/core_scan.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -58,6 +58,12 @@
-type error_description() :: term().
-type error_info() :: {erl_anno:location(), module(), error_description()}.
+-define(IS_UNICODE(C),
+ (is_integer(C) andalso
+ (C >= 0 andalso C < 16#D800 orelse
+ C > 16#DFFF andalso C < 16#FFFE orelse
+ C > 16#FFFF andalso C =< 16#10FFFF))).
+
%% string([Char]) ->
%% string([Char], StartPos) ->
%% {ok, [Tok], EndPos} |
@@ -256,6 +262,8 @@ scan(Cs, Pos) ->
%% scan1(Characters, TokenStack, Position)
%% Scan a list of characters into tokens.
+scan1([C|_s], _Toks, _Pos) when not ?IS_UNICODE(C) ->
+ error({badchar,C});
scan1([$\n|Cs], Toks, Pos) -> %Skip newline
scan1(Cs, Toks, Pos+1);
scan1([C|Cs], Toks, Pos) when C >= $\000, C =< $\s -> %Skip control chars
@@ -272,9 +280,9 @@ scan1([C|Cs], Toks, Pos) when C >= $À, C =< $Þ, C /= $× ->
scan_variable(C, Cs, Toks, Pos);
scan1([C|Cs], Toks, Pos) when C >= $0, C =< $9 -> %Numbers
scan_number(C, Cs, Toks, Pos);
-scan1([$-,C|Cs], Toks, Pos) when C >= $0, C =< $9 -> %Signed numbers
+scan1([$-,C|Cs], Toks, Pos) when is_integer(C), C >= $0, C =< $9 -> %Signed numbers
scan_signed_number($-, C, Cs, Toks, Pos);
-scan1([$+,C|Cs], Toks, Pos) when C >= $0, C =< $9 -> %Signed numbers
+scan1([$+,C|Cs], Toks, Pos) when is_integer(C), C >= $0, C =< $9 -> %Signed numbers
scan_signed_number($+, C, Cs, Toks, Pos);
scan1([$_|Cs], Toks, Pos) -> %_ variables
scan_variable($_, Cs, Toks, Pos);
@@ -338,6 +346,8 @@ scan_name([C|Cs], Ncs) ->
scan_name([], Ncs) ->
{Ncs,[]}.
+name_char(C) when not ?IS_UNICODE(C) ->
+ error({badchar,C});
name_char(C) when C >= $a, C =< $z -> true;
name_char(C) when C >= $ß, C =< $ÿ, C /= $÷ -> true;
name_char(C) when C >= $A, C =< $Z -> true;
@@ -374,15 +384,18 @@ scan_char([C|Cs], Pos) ->
{C,Cs,Pos}.
scan_escape([O1,O2,O3|Cs], Pos) when %\<1-3> octal digits
- O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 ->
+ is_integer(O1), O1 >= $0, O1 =< $7,
+ is_integer(O2), O2 >= $0, O2 =< $7,
+ is_integer(O3), O3 >= $0, O3 =< $7 ->
Val = (O1*8 + O2)*8 + O3 - 73*$0,
{Val,Cs,Pos};
scan_escape([O1,O2|Cs], Pos) when
- O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7 ->
+ is_integer(O1), O1 >= $0, O1 =< $7,
+ is_integer(O2), O2 >= $0, O2 =< $7 ->
Val = (O1*8 + O2) - 9*$0,
{Val,Cs,Pos};
scan_escape([O1|Cs], Pos) when
- O1 >= $0, O1 =< $7 ->
+ is_integer(O1), O1 >= $0, O1 =< $7 ->
{O1 - $0,Cs,Pos};
scan_escape([$^,C|Cs], Pos) -> %\^X -> CTL-X
Val = C band 31,
@@ -422,7 +435,8 @@ escape_char(C) -> C.
%% SPos == Start position
%% CPos == Current position
-scan_number(C, Cs0, Toks, Pos) ->
+scan_number(C, Cs0, Toks, Pos) when
+ is_integer(C), C >= $0, C =< $9 ->
{Ncs,Cs,Pos1} = scan_integer(Cs0, [C], Pos),
scan_after_int(Cs, Ncs, Toks, Pos, Pos1).
@@ -430,17 +444,19 @@ scan_signed_number(S, C, Cs0, Toks, Pos) ->
{Ncs,Cs,Pos1} = scan_integer(Cs0, [C,S], Pos),
scan_after_int(Cs, Ncs, Toks, Pos, Pos1).
-scan_integer([C|Cs], Stack, Pos) when C >= $0, C =< $9 ->
+scan_integer([C|Cs], Stack, Pos) when
+ is_integer(C), C >= $0, C =< $9 ->
scan_integer(Cs, [C|Stack], Pos);
scan_integer(Cs, Stack, Pos) ->
{Stack,Cs,Pos}.
-scan_after_int([$.,C|Cs0], Ncs0, Toks, SPos, CPos) when C >= $0, C =< $9 ->
+scan_after_int([$.,C|Cs0], Ncs0, Toks, SPos, CPos) when
+ is_integer(C), C >= $0, C =< $9 ->
{Ncs,Cs,CPos1} = scan_integer(Cs0, [C,$.|Ncs0], CPos),
- scan_after_fraction(Cs, Ncs, Toks, SPos, CPos1);
+ scan_after_fraction(Cs, Ncs, Toks, SPos, CPos1);
scan_after_int([$#|Cs], Ncs, Toks, SPos, CPos) ->
case list_to_integer(reverse(Ncs)) of
- Base when Base >= 2, Base =< 16 ->
+ Base when is_integer(Base), Base >= 2, Base =< 16 ->
scan_based_int(Cs, 0, Base, Toks, SPos, CPos);
Base ->
scan_error({base,Base}, CPos)
@@ -450,15 +466,15 @@ scan_after_int(Cs, Ncs, Toks, SPos, CPos) ->
scan1(Cs, [{integer,SPos,N}|Toks], CPos).
scan_based_int([C|Cs], SoFar, Base, Toks, SPos, CPos) when
- C >= $0, C =< $9, C < Base + $0 ->
+ is_integer(C), C >= $0, C =< $9, C < Base + $0 ->
Next = SoFar * Base + (C - $0),
scan_based_int(Cs, Next, Base, Toks, SPos, CPos);
scan_based_int([C|Cs], SoFar, Base, Toks, SPos, CPos) when
- C >= $a, C =< $f, C < Base + $a - 10 ->
+ is_integer(C), C >= $a, C =< $f, C < Base + $a - 10 ->
Next = SoFar * Base + (C - $a + 10),
scan_based_int(Cs, Next, Base, Toks, SPos, CPos);
scan_based_int([C|Cs], SoFar, Base, Toks, SPos, CPos) when
- C >= $A, C =< $F, C < Base + $A - 10 ->
+ is_integer(C), C >= $A, C =< $F, C < Base + $A - 10 ->
Next = SoFar * Base + (C - $A + 10),
scan_based_int(Cs, Next, Base, Toks, SPos, CPos);
scan_based_int(Cs, SoFar, _, Toks, SPos, CPos) ->
@@ -485,7 +501,8 @@ scan_exponent([$-|Cs], Ncs, Toks, SPos, CPos) ->
scan_exponent(Cs, Ncs, Toks, SPos, CPos) ->
scan_exponent1(Cs, Ncs, Toks, SPos, CPos).
-scan_exponent1([C|Cs0], Ncs0, Toks, SPos, CPos) when C >= $0, C =< $9 ->
+scan_exponent1([C|Cs0], Ncs0, Toks, SPos, CPos) when
+ is_integer(C), C >= $0, C =< $9 ->
{Ncs,Cs,CPos1} = scan_integer(Cs0, [C|Ncs0], CPos),
case catch list_to_float(reverse(Ncs)) of
N when is_float(N) ->
diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab
index e1e36fa4dc..3363f5873f 100755
--- a/lib/compiler/src/genop.tab
+++ b/lib/compiler/src/genop.tab
@@ -670,3 +670,31 @@ BEAM_FORMAT_NUMBER=0
## @spec badrecord Value
## @doc Raises a {badrecord,Value} error exception.
180: badrecord/1
+
+# OTP 26
+
+## @spec update_record Hint Size Src Dst Updates=[Index, Value]
+## @doc Sets Dst to a copy of Src with the update list applied. Hint can be
+## one of:
+##
+## * {atom,copy} - The result will always differ from Src, so
+## don't bother checking if it can be reused.
+## * {atom,reuse} - Reuse Src if a runtime check deduces that it's
+## equal to the result.
+##
+## Note that these are just hints and the implementation is free to
+## ignore them. More hints may be added in the future.
+181: update_record/5
+
+## @spec bs_match Fail Ctx {commands,Commands}
+## @doc Match one or more binary segments of fixed size. Commands
+## can be one of the following:
+##
+## * {ensure_at_least,Stride,Unit}
+## * {ensure_exactly,Stride}
+## * {binary,Live,Flags,Size,Unit,Dst}
+## * {integer,Live,Flags,Size,Unit,Dst}
+## * {skip,Stride}
+## * {get_tail,Live,Unit,Dst}
+## * {'=:=',Live,Size,Value}.
+182: bs_match/3
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index fb23fd7d63..024b2d5f2a 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -93,6 +93,9 @@
-define(ASSERT(E), ignore).
-endif.
+-define(MAX_FUNC_ARGS, 255).
+-define(IS_FUNC_ARITY(A), is_integer(A) andalso 0 =< A andalso A =< ?MAX_FUNC_ARGS).
+
%% Variable value info.
-record(sub, {v=[], %Variable substitutions
s=sets:new([{version, 2}]) :: sets:set(), %Variables in scope
@@ -269,17 +272,8 @@ expr(#c_seq{arg=Arg0,body=B0}=Seq0, Ctxt, Sub) ->
end;
expr(#c_let{}=Let0, Ctxt, Sub) ->
Let = opt_case_in_let(Let0),
- case simplify_let(Let, Sub) of
- impossible ->
- %% The argument for the let is "simple", i.e. has no
- %% complex structures such as let or seq that can be entered.
- ?ASSERT(verify_scope(Let, Sub)),
- opt_fun_call(opt_simple_let(Let, Ctxt, Sub));
- Expr ->
- %% The let body was successfully moved into the let argument.
- %% Now recursively re-process the new expression.
- Expr
- end;
+ ?ASSERT(verify_scope(Let, Sub)),
+ opt_fun_call(opt_let(Let, Ctxt, Sub));
expr(#c_letrec{body=#c_var{}}=Letrec, effect, _Sub) ->
%% This is named fun in an 'effect' context. Warn and ignore.
add_warning(Letrec, {ignored,useless_building}),
@@ -409,7 +403,9 @@ expr(#c_try{anno=A,arg=E0,vars=Vs0,body=B0,evars=Evs0,handler=H0}=Try, _, Sub0)
{Evs1,Sub2} = var_list(Evs0, Sub0),
H1 = body(H0, value, Sub2),
Try#c_try{arg=E1,vars=Vs1,body=B1,evars=Evs1,handler=H1}
- end.
+ end;
+expr(#c_opaque{}=O, effect, _Sub) ->
+ O.
%% If a fun or its application is used as an argument, then it's unsafe to
%% handle it in effect context as the side-effects may rely on its return
@@ -802,8 +798,6 @@ fold_apply(Apply, _, _) -> Apply.
call(#c_call{args=As0}=Call0, #c_literal{val=M}=M0, #c_literal{val=N}=N0, Sub) ->
As1 = expr_list(As0, value, Sub),
case simplify_call(Call0, M, N, As1) of
- #c_literal{}=Lit ->
- Lit;
#c_call{args=As}=Call ->
case get(no_inline_list_funcs) of
true ->
@@ -813,7 +807,11 @@ call(#c_call{args=As0}=Call0, #c_literal{val=M}=M0, #c_literal{val=N}=N0, Sub) -
none -> fold_call(Call, M0, N0, As, Sub);
Core -> expr(Core, Sub)
end
- end
+ end;
+ #c_let{}=Let ->
+ Let;
+ #c_literal{}=Lit ->
+ Lit
end;
call(#c_call{args=As0}=Call, M, N, Sub) ->
As = expr_list(As0, value, Sub),
@@ -823,6 +821,33 @@ call(#c_call{args=As0}=Call, M, N, Sub) ->
%% slightly at the cost of making tracing and stack traces incorrect.
simplify_call(Call, maps, get, [Key, Map]) ->
rewrite_call(Call, erlang, map_get, [Key, Map]);
+simplify_call(#c_call{anno=Anno0}, maps, get, [Key0, Map, Default]) ->
+ Anno = [compiler_generated | Anno0],
+
+ Key = make_var(Anno),
+ Value = make_var(Anno),
+ Fail = make_var(Anno),
+ Raise = #c_primop{name=#c_literal{val=match_fail},
+ args=[#c_tuple{es=[#c_literal{val=badmap},
+ Fail]}]},
+
+ Cs = [#c_clause{anno=Anno,
+ pats=[#c_map{es=[#c_map_pair{op=#c_literal{val=exact},
+ key=Key,
+ val=Value}],
+ is_pat=true}],
+ guard=#c_literal{val=true},
+ body=Value},
+ #c_clause{anno=Anno,
+ pats=[#c_map{es=[],is_pat=true}],
+ guard=#c_literal{val=true},
+ body=Default},
+ #c_clause{anno=Anno,
+ pats=[Fail],
+ guard=#c_literal{val=true},
+ body=Raise}],
+
+ cerl:ann_c_let(Anno, [Key], Key0, #c_case{anno=Anno,arg=Map,clauses=Cs});
simplify_call(Call, maps, is_key, [Key, Map]) ->
rewrite_call(Call, erlang, is_map_key, [Key, Map]);
simplify_call(_Call, maps, new, []) ->
@@ -1864,6 +1889,7 @@ case_opt_data_2(P, TypeSig, Bs0) ->
{[V|Vs],none} ->
{Type,Arity} = TypeSig,
Ann = [compiler_generated],
+ true = ?IS_FUNC_ARITY(Arity),
Vars = make_vars(Ann, Arity),
Data = cerl:ann_make_data(Ann, Type, Vars),
Bs = [{V,Data} | [{Var,V} || Var <- Vs] ++ Bs0],
@@ -1921,7 +1947,7 @@ pat_to_expr(P) ->
pat_to_expr_list(Ps) -> [pat_to_expr(P) || P <- Ps].
-make_vars(A, Max) ->
+make_vars(A, Max) when ?IS_FUNC_ARITY(Max) ->
make_vars(A, 1, Max).
make_vars(A, I, Max) when I =< Max ->
@@ -2183,112 +2209,6 @@ simplify_fun_call(V, Values, #c_fun{vars=Vars,body=FunBody}, CallArgs) ->
throw(impossible)
end.
-%% simplify_let(Let, Sub) -> Expr | impossible
-%% If the argument part of an let contains a complex expression, such
-%% as a let or a sequence, move the original let body into the complex
-%% expression.
-
-simplify_let(#c_let{arg=Arg}=Let, Sub) ->
- move_let_into_expr(Let, Arg, Sub).
-
-move_let_into_expr(#c_let{vars=InnerVs0,body=InnerBody0}=Inner,
- #c_let{vars=OuterVs0,arg=Arg0,body=OuterBody0}=Outer, Sub0) ->
- %%
- %% let <InnerVars> = let <OuterVars> = <Arg>
- %% in <OuterBody>
- %% in <InnerBody>
- %%
- %% ==>
- %%
- %% let <OuterVars> = <Arg>
- %% in let <InnerVars> = <OuterBody>
- %% in <InnerBody>
- %%
- Arg = body(Arg0, Sub0),
- ScopeSub0 = sub_subst_scope(Sub0#sub{t=#{}}),
- {OuterVs,ScopeSub} = var_list(OuterVs0, ScopeSub0),
-
- OuterBody = body(OuterBody0, ScopeSub),
-
- {InnerVs,Sub} = var_list(InnerVs0, Sub0),
- InnerBody = body(InnerBody0, Sub),
- Outer#c_let{vars=OuterVs,arg=Arg,
- body=Inner#c_let{vars=InnerVs,arg=OuterBody,body=InnerBody}};
-move_let_into_expr(#c_let{vars=Lvs0,body=Lbody0}=Let,
- #c_case{arg=Cexpr0,clauses=[Ca0|Cs0]}=Case, Sub0) ->
- case not is_failing_clause(Ca0) andalso
- are_all_failing_clauses(Cs0) of
- true ->
- %% let <Lvars> = case <Case-expr> of
- %% <Cpats> -> <Clause-body>;
- %% <OtherCpats> -> erlang:error(...)
- %% end
- %% in <Let-body>
- %%
- %% ==>
- %%
- %% case <Case-expr> of
- %% <Cpats> ->
- %% let <Lvars> = <Clause-body>
- %% in <Let-body>;
- %% <OtherCpats> -> erlang:error(...)
- %% end
-
- Cexpr = body(Cexpr0, Sub0),
- CaPats0 = Ca0#c_clause.pats,
- G0 = Ca0#c_clause.guard,
- B0 = Ca0#c_clause.body,
- ScopeSub0 = sub_subst_scope(Sub0#sub{t=#{}}),
- try pattern_list(CaPats0, ScopeSub0) of
- {CaPats,ScopeSub} ->
- G = guard(G0, ScopeSub),
-
- B1 = body(B0, ScopeSub),
-
- {Lvs,B2,Sub1} = let_substs(Lvs0, B1, Sub0),
- Sub2 = Sub1#sub{s=sets:union(ScopeSub#sub.s,
- Sub1#sub.s)},
- Lbody = body(Lbody0, Sub2),
- B = Let#c_let{vars=Lvs,
- arg=core_lib:make_values(B2),
- body=Lbody},
-
- Ca = Ca0#c_clause{pats=CaPats,guard=G,body=B},
- Cs = [clause(C, Cexpr, value, Sub0) || C <- Cs0],
- Case#c_case{arg=Cexpr,clauses=[Ca|Cs]}
- catch
- nomatch ->
- %% This is not a defeat. The code will eventually
- %% be optimized to erlang:error(...) by the other
- %% optimizations done in this module.
- impossible
- end;
- false -> impossible
- end;
-move_let_into_expr(#c_let{vars=Lvs0,body=Lbody0}=Let,
- #c_seq{arg=Sarg0,body=Sbody0}=Seq, Sub0) ->
- %%
- %% let <Lvars> = do <Seq-arg>
- %% <Seq-body>
- %% in <Let-body>
- %%
- %% ==>
- %%
- %% do <Seq-arg>
- %% let <Lvars> = <Seq-body>
- %% in <Let-body>
- %%
- Sarg = body(Sarg0, Sub0),
- Sbody1 = body(Sbody0, Sub0),
- {Lvs,Sbody,Sub} = let_substs(Lvs0, Sbody1, Sub0),
- Lbody = body(Lbody0, Sub),
- Seq#c_seq{arg=Sarg,body=Let#c_let{vars=Lvs,arg=core_lib:make_values(Sbody),
- body=Lbody}};
-move_let_into_expr(_Let, _Expr, _Sub) -> impossible.
-
-are_all_failing_clauses(Cs) ->
- all(fun is_failing_clause/1, Cs).
-
is_failing_clause(#c_clause{body=B}) ->
will_fail(B).
@@ -2441,6 +2361,7 @@ delay_build_1(Core0, TypeSig) ->
Core ->
{Type,Arity} = TypeSig,
Ann = [compiler_generated],
+ true = ?IS_FUNC_ARITY(Arity),
Vars = make_vars(Ann, Arity),
Data = cerl:ann_make_data(Ann, Type, Vars),
{yes,Vars,Core,Data}
@@ -2483,42 +2404,41 @@ delay_build_expr_1(Core, _TypeSig) ->
false -> throw(impossible)
end.
-%% opt_simple_let(#c_let{}, Context, Sub) -> CoreTerm
-%% Optimize a let construct that does not contain any lets in
-%% in its argument.
+%% opt_let(#c_let{}, Context, Sub) -> CoreTerm
+%% Optimize a let construct.
-opt_simple_let(Let0, Ctxt, Sub) ->
+opt_let(Let0, Ctxt, Sub) ->
case opt_not_in_let(Let0) of
#c_let{}=Let ->
- opt_simple_let_0(Let, Ctxt, Sub);
+ opt_let_0(Let, Ctxt, Sub);
Expr ->
expr(Expr, Ctxt, Sub)
end.
-opt_simple_let_0(#c_let{arg=Arg0}=Let, Ctxt, Sub) ->
+opt_let_0(#c_let{arg=Arg0}=Let, Ctxt, Sub) ->
Arg = body(Arg0, value, Sub), %This is a body
case will_fail(Arg) of
true -> Arg;
- false -> opt_simple_let_1(Let, Arg, Ctxt, Sub)
+ false -> opt_let_1(Let, Arg, Ctxt, Sub)
end.
-opt_simple_let_1(#c_let{vars=Vs0,body=B0}=Let, Arg0, Ctxt, Sub0) ->
+opt_let_1(#c_let{vars=Vs0,body=B0}=Let, Arg0, Ctxt, Sub0) ->
%% Optimise let and add new substitutions.
{Vs,Args,Sub1} = let_substs(Vs0, Arg0, Sub0),
BodySub = update_let_types(Vs, Args, Sub1),
Sub = Sub1#sub{v=[],s=sets:new([{version, 2}])},
B = body(B0, Ctxt, BodySub),
Arg = core_lib:make_values(Args),
- opt_simple_let_2(Let, Vs, Arg, B, B0, Sub).
+ opt_let_2(Let, Vs, Arg, B, B0, Sub).
-%% opt_simple_let_2(Let0, Vs0, Arg0, Body, PrevBody, Ctxt, Sub) -> Core.
+%% opt_let_2(Let0, Vs0, Arg0, Body, PrevBody, Ctxt, Sub) -> Core.
%% Do final simplifications of the let.
%%
%% Note that the substitutions and scope in Sub have been cleared
%% and should not be used.
-opt_simple_let_2(Let0, Vs0, Arg0, Body, PrevBody, Sub) ->
+opt_let_2(Let0, Vs0, Arg0, Body, PrevBody, Sub) ->
case {Vs0,Arg0,Body} of
{[#c_var{name=V}],Arg1,#c_var{name=V}} ->
%% let <Var> = Arg in <Var> ==> Arg
@@ -2704,17 +2624,11 @@ update_types(_, _, Sub) -> Sub.
%% Kill any entries that references the variable,
%% either in the key or in the value.
-kill_types(V, Tdb) ->
- maps:from_list(kill_types2(V,maps:to_list(Tdb))).
-
-kill_types2(V, [{V,_}|Tdb]) ->
- kill_types2(V, Tdb);
-kill_types2(V, [{_,#c_tuple{}=Tuple}=Entry|Tdb]) ->
- case core_lib:is_var_used(V, Tuple) of
- false -> [Entry|kill_types2(V, Tdb)];
- true -> kill_types2(V, Tdb)
- end;
-kill_types2(_, []) -> [].
+kill_types(Var, Tdb) ->
+ #{Key => Value ||
+ Key := Value <- Tdb,
+ Key =/= Var,
+ not core_lib:is_var_used(Var, Value)}.
%% copy_type(DestVar, SrcVar, Tdb) -> Tdb'
%% If the SrcVar has a type, assign it to DestVar.
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index ff7a48e002..daf1f63585 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -81,13 +81,13 @@
-export([module/2,format_error/1]).
--import(lists, [reverse/1,reverse/2,map/2,member/2,foldl/3,foldr/3,mapfoldl/3,
- splitwith/2,keyfind/3,sort/1,droplast/1,last/1,
+-import(lists, [any/2,reverse/1,reverse/2,map/2,member/2,foldl/3,foldr/3,mapfoldl/3,
+ splitwith/2,keydelete/3,keyfind/3,keymember/3,sort/1,droplast/1,last/1,
duplicate/2]).
-import(ordsets, [add_element/2,del_element/2,is_element/2,
union/1,union/2,intersection/2,subtract/2]).
-import(cerl, [ann_c_cons/3,ann_c_tuple/2,c_tuple/1,
- ann_c_map/3]).
+ ann_c_map/3,cons_hd/1,cons_tl/1]).
-include("core_parse.hrl").
@@ -120,7 +120,8 @@
-record(itry, {anno=#a{},args,vars,body,evars,handler}).
-record(ifilter, {anno=#a{},arg}).
-record(igen, {anno=#a{},acc_pat,acc_guard,
- skip_pat,tail,tail_pat,arg}).
+ skip_pat,tail,tail_pat,arg,
+ refill={nomatch,ignore}}).
-record(isimple, {anno=#a{},term :: cerl:cerl()}).
-type iapply() :: #iapply{}.
@@ -160,7 +161,8 @@
opts=[] :: [compile:option()], %Options.
dialyzer=false :: boolean(), %Help dialyzer or not.
ws=[] :: [warning()], %Warnings.
- file=[{file,""}] %File.
+ file=[{file,""}], %File.
+ load_nif=false :: boolean() %true if calls erlang:load_nif/2
}).
%% XXX: The following type declarations do not belong in this module
@@ -171,12 +173,16 @@
-record(imodule, {name = [],
exports = ordsets:new(),
- nifs = sets:new([{version, 2}]),
+ nifs = none ::
+ 'none' | sets:set(), % Is a set if the attribute is
+ % present in the module.
attrs = [],
defs = [],
file = [],
opts = [],
- ws = []}).
+ ws = [],
+ load_nif=false :: boolean() %true if calls erlang:load_nif/2
+ }).
-spec module([form()], [compile:option()]) ->
{'ok',cerl:c_module(),[warning()]}.
@@ -186,19 +192,28 @@ module(Forms0, Opts) ->
Module = foldl(fun (F, Acc) ->
form(F, Acc, Opts)
end, #imodule{}, Forms),
- #imodule{name=Mod,exports=Exp0,attrs=As0,defs=Kfs0,ws=Ws} = Module,
+ #imodule{name=Mod,exports=Exp0,attrs=As0,
+ defs=Kfs0,ws=Ws,load_nif=LoadNif,nifs=Nifs} = Module,
Exp = case member(export_all, Opts) of
true -> defined_functions(Forms);
false -> Exp0
end,
Cexp = [#c_var{name=FA} || {_,_}=FA <- Exp],
+ Kfs1 = reverse(Kfs0),
+ Kfs = if LoadNif and (Nifs =:= none) ->
+ insert_nif_start(Kfs1);
+ true ->
+ Kfs1
+ end,
As = reverse(As0),
- Kfs = reverse(Kfs0),
+
{ok,#c_module{name=#c_literal{val=Mod},exports=Cexp,attrs=As,defs=Kfs},Ws}.
-form({function,_,_,_,_}=F0, #imodule{defs=Defs}=Module, Opts) ->
- {F,Ws} = function(F0, Module, Opts),
- Module#imodule{defs=[F|Defs],ws=Ws};
+form({function,_,_,_,_}=F0,
+ #imodule{defs=Defs,load_nif=LoadNif0}=Module,
+ Opts) ->
+ {F,Ws,LoadNif} = function(F0, Module, Opts),
+ Module#imodule{defs=[F|Defs],ws=Ws,load_nif=LoadNif or LoadNif0};
form({attribute,_,module,Mod}, Module, _Opts) ->
true = is_atom(Mod),
Module#imodule{name=Mod};
@@ -211,7 +226,13 @@ form({attribute,_,export,Es}, #imodule{exports=Exp0}=Module, _Opts) ->
Exp = ordsets:union(ordsets:from_list(Es), Exp0),
Module#imodule{exports=Exp};
form({attribute,_,nifs,Ns}, #imodule{nifs=Nifs0}=Module, _Opts) ->
- Nifs = sets:union(sets:from_list(Ns, [{version,2}]), Nifs0),
+ Nifs1 = case Nifs0 of
+ none ->
+ sets:new([{version, 2}]);
+ _ ->
+ Nifs0
+ end,
+ Nifs = sets:union(sets:from_list(Ns, [{version,2}]), Nifs1),
Module#imodule{nifs=Nifs};
form({attribute,_,_,_}=F, #imodule{attrs=As}=Module, _Opts) ->
Module#imodule{attrs=[attribute(F)|As]};
@@ -236,7 +257,8 @@ defined_functions(Forms) ->
%% io:format("~w/~w " ++ Format,[Name,Arity]++Terms),
%% ok.
-function({function,_,Name,Arity,Cs0}, Module, Opts) ->
+function({function,_,Name,Arity,Cs0}, Module, Opts)
+ when is_integer(Arity), 0 =< Arity, Arity =< 255 ->
#imodule{file=File, ws=Ws0, nifs=Nifs} = Module,
try
St0 = #core{vcount=0,function={Name,Arity},opts=Opts,
@@ -248,9 +270,9 @@ function({function,_,Name,Arity,Cs0}, Module, Opts) ->
%% ok = function_dump(Name, Arity, "ubody:~n~p~n",[B1]),
{B2,St3} = cbody(B1, Nifs, St2),
%% ok = function_dump(Name, Arity, "cbody:~n~p~n",[B2]),
- {B3,#core{ws=Ws}} = lbody(B2, St3),
+ {B3,#core{ws=Ws,load_nif=LoadNif}} = lbody(B2, St3),
%% ok = function_dump(Name, Arity, "lbody:~n~p~n",[B3]),
- {{#c_var{name={Name,Arity}},B3},Ws}
+ {{#c_var{name={Name,Arity}},B3},Ws,LoadNif}
catch
Class:Error:Stack ->
io:fwrite("Function: ~w/~w\n", [Name,Arity]),
@@ -670,6 +692,9 @@ expr({lc,L,E,Qs0}, St0) ->
lc_tq(L, E, Qs1, #c_literal{anno=lineno_anno(L, St1),val=[]}, St1);
expr({bc,L,E,Qs}, St) ->
bc_tq(L, E, Qs, St);
+expr({mc,L,E,Qs0}, St0) ->
+ {Qs1,St1} = preprocess_quals(L, Qs0, St0),
+ mc_tq(L, E, Qs1, #c_literal{anno=lineno_anno(L, St1),val=[]}, St1);
expr({tuple,L,Es0}, St0) ->
{Es1,Eps,St1} = safe_list(Es0, St0),
A = record_anno(L, St1),
@@ -859,6 +884,9 @@ expr({call,L,{remote,_,M0,F0},As0}, St0) ->
name=#c_literal{val=match_fail},
args=[Tuple]},
{Fail,Aps,St1};
+ {#c_literal{val=erlang},#c_literal{val=load_nif},[_,_]} ->
+ {#icall{anno=#a{anno=Anno},module=M1,name=F1,args=As1},
+ Aps,St1#core{load_nif=true}};
{_,_,_} ->
{#icall{anno=#a{anno=Anno},module=M1,name=F1,args=As1},Aps,St1}
end;
@@ -877,7 +905,7 @@ expr({match,L,P0,E0}, St0) ->
{{sequential_match,_,_,_}=P1,E1} ->
%% Matching of an expression to more than one pattern. Example:
%%
- %% #rec{f=Val} = A = Expr
+ %% #{Key := Value} = #{key := Key} = Expr
{E2,Eps1,St2} = safe(E1, St1),
St3 = St2#core{wanted=St0#core.wanted},
@@ -897,8 +925,8 @@ expr({match,L,P0,E0}, St0) ->
%%
%% begin
%% V = Expr,
- %% A = V,
- %% #rec{f=Val} = V
+ %% #{key := Key} = V,
+ %% #{Key := Value} = V
%% end
Block = blockify(L, P1, Var),
{E3,Eps3,St5} = expr({block,L,Block}, St4),
@@ -951,7 +979,9 @@ expr({op,L,Op,L0,R0}, St0) ->
LineAnno = full_anno(L, St1),
{#icall{anno=#a{anno=LineAnno}, %Must have an #a{}
module=#c_literal{anno=LineAnno,val=erlang},
- name=#c_literal{anno=LineAnno,val=Op},args=As},Aps,St1}.
+ name=#c_literal{anno=LineAnno,val=Op},args=As},Aps,St1};
+expr({ssa_check_when,L,WantedResult,Args,Tag,Clauses}, St) ->
+ {#c_opaque{anno=full_anno(L, St),val={ssa_check_when,WantedResult,Tag,Args,Clauses}}, [], St}.
blockify(L0, {sequential_match,_L1,First,Then}, E) ->
[{single_match,L0,First,E}|blockify(L0, Then, E)];
@@ -1528,7 +1558,7 @@ verify_suitable_fields([]) -> ok.
%% Count the number of bits approximately needed to store Int.
%% (We don't need an exact result for this purpose.)
-count_bits(Int) ->
+count_bits(Int) when is_integer(Int) ->
count_bits_1(abs(Int), 64).
count_bits_1(0, Bits) -> Bits;
@@ -1568,37 +1598,31 @@ fun_tq(Cs0, L, St0, NameInfo) ->
{Fun,[],St4}.
%% lc_tq(Line, Exp, [Qualifier], Mc, State) -> {LetRec,[PreExp],State}.
-%% This TQ from Simon PJ pp 127-138.
+%% This TQ from Simon PJ pp 127-138.
lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
acc_pat=AccPat,acc_guard=AccGuard,
skip_pat=SkipPat,tail=Tail,tail_pat=TailPat,
+ refill={RefillPat,RefillAction},
arg={Pre,Arg}}|Qs], Mc, St0) ->
{Name,St1} = new_fun_name("lc", St0),
LA = lineno_anno(Line, St1),
- LAnno = #a{anno=LA},
F = #c_var{anno=LA,name={Name,1}},
Nc = #iapply{anno=GAnno,op=F,args=[Tail]},
{[FcVar,Var],St2} = new_vars(2, St1),
Fc = bad_generator([FcVar], FcVar, Arg),
- SkipClause = #iclause{anno=#a{anno=[skip_clause,compiler_generated|LA]},
- pats=[SkipPat],guard=[],body=[Nc]},
- TailClause = #iclause{anno=LAnno,pats=[TailPat],guard=[],body=[Mc]},
- {Cs,St4} = case {AccPat,SkipPat} of
- {nomatch,nomatch} ->
- {[TailClause],St2};
- {nomatch,_} ->
- {[SkipClause,TailClause],St2};
- _ ->
- {Lc,Lps,St3} = lc_tq(Line, E, Qs, Nc, St2),
- AccClause = #iclause{anno=LAnno,pats=[AccPat],guard=AccGuard,
- body=Lps ++ [Lc]},
- {[AccClause,SkipClause,TailClause],St3}
- end,
+ SkipClause = make_clause([skip_clause,compiler_generated|LA],
+ SkipPat, [], [], [Nc]),
+ TailClause = make_clause(LA, TailPat, [], [], [Mc]),
+ {Lc,Lps,St3} = lc_tq(Line, E, Qs, Nc, St2),
+ AccClause = make_clause(LA, AccPat, [], AccGuard, Lps ++ [Lc]),
+ RefillClause = make_clause(LA, RefillPat, [], [], [RefillAction,Nc]),
+ Cs0 = [AccClause,SkipClause,TailClause,RefillClause],
+ Cs = [C || C <- Cs0, C =/= nomatch],
Fun = #ifun{anno=GAnno,id=[],vars=[Var],clauses=Cs,fc=Fc},
{#iletrec{anno=GAnno#a{anno=[list_comprehension|GA]},defs=[{{Name,1},Fun}],
body=Pre ++ [#iapply{anno=GAnno,op=F,args=[Arg]}]},
- [],St4};
+ [],St3};
lc_tq(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
filter_tq(Line, E, Filter, Mc, St, Qs, fun lc_tq/5);
lc_tq(Line, E0, [], Mc0, St0) ->
@@ -1633,6 +1657,7 @@ bc_tq(Line, Exp, Qs0, St0) ->
bc_tq1(Line, E, [#igen{anno=GAnno,
acc_pat=AccPat,acc_guard=AccGuard,
skip_pat=SkipPat,tail=Tail,tail_pat=TailPat,
+ refill={RefillPat,RefillAction},
arg={Pre,Arg}}|Qs], Mc, St0) ->
{Name,St1} = new_fun_name("lbc", St0),
LA = lineno_anno(Line, St1),
@@ -1643,30 +1668,23 @@ bc_tq1(Line, E, [#igen{anno=GAnno,
F = #c_var{anno=LA,name={Name,2}},
Nc = #iapply{anno=GAnno,op=F,args=[Tail,AccVar]},
Fc = bad_generator(FcVars, hd(FcVars), Arg),
- SkipClause = #iclause{anno=#a{anno=[compiler_generated,skip_clause|LA]},
- pats=[SkipPat,IgnoreVar],guard=[],body=[Nc]},
- TailClause = #iclause{anno=LAnno,pats=[TailPat,IgnoreVar],guard=[],
- body=[AccVar]},
- {Cs,St} = case {AccPat,SkipPat} of
- {nomatch,nomatch} ->
- {[TailClause],St4};
- {nomatch,_} ->
- {[SkipClause,TailClause],St4};
- {_,_} ->
- {Bc,Bps,St5} = bc_tq1(Line, E, Qs, AccVar, St4),
- Body = Bps ++ [#iset{var=AccVar,arg=Bc},Nc],
- AccClause = #iclause{anno=LAnno,pats=[AccPat,IgnoreVar],
- guard=AccGuard,body=Body},
- {[AccClause,SkipClause,TailClause],St5}
- end,
- Fun = #ifun{anno=LAnno,id=[],vars=Vars,clauses=Cs,fc=Fc},
+ SkipClause = make_clause([compiler_generated,skip_clause|LA],
+ SkipPat, [IgnoreVar], [], [Nc]),
+ TailClause = make_clause(LA, TailPat, [IgnoreVar], [], [AccVar]),
+ {Bc,Bps,St5} = bc_tq1(Line, E, Qs, AccVar, St4),
+ Body = Bps ++ [#iset{var=AccVar,arg=Bc},Nc],
+ AccClause = make_clause(LA, AccPat, [IgnoreVar], AccGuard, Body),
+ RefillClause = make_clause(LA, RefillPat, [AccVar], [], [RefillAction,Nc]),
+ Cs0 = [AccClause,SkipClause,TailClause,RefillClause],
+ Cs = [C || C <- Cs0, C =/= nomatch],
+ Fun = #ifun{anno=GAnno,id=[],vars=Vars,clauses=Cs,fc=Fc},
%% Inlining would disable the size calculation optimization for
%% bs_init_writable.
{#iletrec{anno=LAnno#a{anno=[list_comprehension,no_inline|LA]},
defs=[{{Name,2},Fun}],
body=Pre ++ [#iapply{anno=LAnno,op=F,args=[Arg,Mc]}]},
- [],St};
+ [],St5};
bc_tq1(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
filter_tq(Line, E, Filter, Mc, St, Qs, fun bc_tq1/5);
bc_tq1(_, {bin,Bl,Elements}, [], AccVar, St0) ->
@@ -1703,6 +1721,20 @@ bc_tq_build(Line, Pre0, #c_var{name=AccVar}, Elements0, St0) ->
Anno = Anno0#a{anno=[compiler_generated,single_use|A]},
{set_anno(E, Anno),Pre0++Pre,St}.
+mc_tq(Line, {map_field_assoc,Lf,K,V}, Qs, Mc, St0) ->
+ E = {tuple,Lf,[K,V]},
+ {Lc,Pre0,St1} = lc_tq(Line, E, Qs, Mc, St0),
+ {LcVar,St2} = new_var(St1),
+ Pre = Pre0 ++ [#iset{var=LcVar,arg=Lc}],
+ Call = #icall{module=#c_literal{val=maps},
+ name=#c_literal{val=from_list},
+ args=[LcVar]},
+ {Call,Pre,St2}.
+
+make_clause(_Anno, nomatch, _PatExtra, _Guard, _Body) ->
+ nomatch;
+make_clause(Anno, Pat, PatExtra, Guard, Body) ->
+ #iclause{anno=#a{anno=Anno},pats=[Pat|PatExtra],guard=Guard,body=Body}.
%% filter_tq(Line, Expr, Filter, Mc, State, [Qualifier], TqFun) ->
%% {Case,[PreExpr],State}.
@@ -1779,6 +1811,7 @@ preprocess_quals(_, [], St, Acc) ->
is_generator({generate,_,_,_}) -> true;
is_generator({b_generate,_,_,_}) -> true;
+is_generator({m_generate,_,_,_}) -> true;
is_generator(_) -> false.
%% Retrieve the annotation from an Erlang AST form.
@@ -1787,7 +1820,7 @@ is_generator(_) -> false.
get_qual_anno(Abstract) -> element(2, Abstract).
%%
-%% Generators are abstracted as sextuplets:
+%% Generators are abstracted as a record #igen{}:
%% - acc_pat is the accumulator pattern, e.g. [Pat|Tail] for Pat <- Expr.
%% - acc_guard is the list of guards immediately following the current
%% generator in the qualifier list input.
@@ -1797,6 +1830,8 @@ get_qual_anno(Abstract) -> element(2, Abstract).
%% generator input.
%% - tail_pat is the tail pattern, respectively [] and <<_/bitstring>> for list
%% and bit string generators.
+%% - refill is a pair {RefillPat,RefillAction}, used to refill the iterator
+%% argument (used by map generators).
%% - arg is a pair {Pre,Arg} where Pre is the list of expressions to be
%% inserted before the comprehension function and Arg is the expression
%% that it should be passed.
@@ -1811,22 +1846,13 @@ generator(Line, {generate,Lg,P0,E}, Gs, St0) ->
{Head,St1} = list_gen_pattern(P0, Line, St0),
{[Tail,Skip],St2} = new_vars(2, St1),
{Cg,St3} = lc_guard_tests(Gs, St2),
- {AccPat,SkipPat} = case Head of
- #c_var{} ->
- %% If the generator pattern is a variable, the
- %% pattern from the accumulator clause can be
- %% reused in the skip one. lc_tq and bc_tq1 takes
- %% care of dismissing the latter in that case.
- Cons = ann_c_cons(LA, Head, Tail),
- {Cons,Cons};
- nomatch ->
- %% If it never matches, there is no need for
- %% an accumulator clause.
- {nomatch,ann_c_cons(LA, Skip, Tail)};
- _ ->
- {ann_c_cons(LA, Head, Tail),
- ann_c_cons(LA, Skip, Tail)}
- end,
+ AccPat = case Head of
+ nomatch ->
+ nomatch;
+ _ ->
+ ann_c_cons(LA, Head, Tail)
+ end,
+ SkipPat = ann_c_cons(LA, Skip, Tail),
{Ce,Pre,St4} = safe(E, St3),
Gen = #igen{anno=#a{anno=GA},
acc_pat=AccPat,acc_guard=Cg,skip_pat=SkipPat,
@@ -1859,7 +1885,92 @@ generator(Line, {b_generate,Lg,P,E}, Gs, St0) ->
tail_pat=#c_var{name='_'},
arg={Pre,Ce}},
{Gen,St1}
- end.
+ end;
+generator(Line, {m_generate,Lg,{map_field_exact,_,K0,V0},E}, Gs, St0) ->
+ %% Consider this example:
+ %%
+ %% [{K,V} || K := V <- L].
+ %%
+ %% The following Core Erlang code will be generated:
+ %%
+ %% letrec
+ %% 'lc$^0'/1 =
+ %% fun (Iter0) ->
+ %% case Iter0 of
+ %% <{K,V,NextIter}> when 'true' ->
+ %% let <Tail> =
+ %% apply 'lc$^0'/1(NextIter)
+ %% in [{K,V}|Tail]
+ %% <{_K,_V,NextIter}> when 'true' ->
+ %% %% Skip clause; will be optimized away later
+ %% %% since there are no filters.
+ %% apply 'lc$^0'/1(NextIter)
+ %% <'none'> when 'true' ->
+ %% []
+ %% <Iter> when 'true' ->
+ %% let NextIter =
+ %% call 'erts_internal':'mc_refill'(Iter)
+ %% in apply 'lc$^0'/1(NextIter)
+ %% <Bad> when 'true' ->
+ %% %% Generated by lc_tq/5. Never reached;
+ %% %% will be optimized away.
+ %% call 'erlang':'error'({'bad_generator',Bad})
+ %% end
+ %% in let <Iter> =
+ %% case call 'erts_internal':'mc_iterator'(L) of
+ %% <[]> when 'true' ->
+ %% call 'erlang':'error'
+ %% ({'bad_generator',L})
+ %% <Iter0> when 'true' ->
+ %% Iter0
+ %% end
+ %% in apply 'lc$^0'/1(Iter0)
+ LA = lineno_anno(Line, St0),
+ GA = lineno_anno(Lg, St0),
+ {Pat,St1} = list_gen_pattern({cons,Lg,K0,V0}, Line, St0),
+ {[SkipK,SkipV,IterVar,OuterIterVar,_BadGenVar],St2} = new_vars(5, St1),
+ {Cg,St3} = lc_guard_tests(Gs, St2),
+ {Ce,Pre0,St4} = safe(E, St3),
+ AccPat = case Pat of
+ nomatch ->
+ nomatch;
+ _ ->
+ K = cons_hd(Pat),
+ V = cons_tl(Pat),
+ #c_tuple{es=[K,V,IterVar]}
+ end,
+ SkipPat = #c_tuple{es=[SkipK,SkipV,IterVar]},
+
+ Refill = {SkipK,
+ #iset{var=IterVar,
+ arg=#icall{anno=#a{anno=GA},
+ module=#c_literal{val=erts_internal},
+ name=#c_literal{val=mc_refill},
+ args=[SkipK]}}},
+
+ InitIter = #icall{anno=#a{anno=GA},
+ module=#c_literal{val=erts_internal},
+ name=#c_literal{val=mc_iterator},
+ args=[Ce]},
+
+ BadGenerator = bad_generator([#c_literal{val=[]}], Ce,
+ #c_literal{val=[],anno=GA}),
+ BeforeFc = #iclause{anno=#a{anno=GA},
+ pats=[IterVar],
+ guard=[],
+ body=[IterVar]},
+ Before = #iset{var=OuterIterVar,
+ arg=#icase{args=[InitIter],
+ clauses=[BadGenerator],
+ fc=BeforeFc}},
+
+ Pre = Pre0 ++ [Before],
+ Gen = #igen{anno=#a{anno=GA},
+ acc_pat=AccPat,acc_guard=Cg,skip_pat=SkipPat,
+ tail=IterVar,tail_pat=#c_literal{anno=LA,val=none},
+ refill=Refill,
+ arg={Pre,OuterIterVar}},
+ {Gen,St4}.
append_tail_segment(Segs, St0) ->
{Var,St} = new_var(St0),
@@ -2043,6 +2154,22 @@ pattern({bin,L,Ps}, St0) ->
{Segments,St} = pat_bin(Ps, St0),
{#ibinary{anno=#a{anno=lineno_anno(L, St)},segments=Segments},St};
pattern({match,_,P1,P2}, St) ->
+ %% Handle aliased patterns in a clause. Example:
+ %%
+ %% f({a,b} = {A,B}) -> . . .
+ %%
+ %% The `=` operator does not have any defined order in which the
+ %% two patterns are matched. Therefore, this example can safely be
+ %% rewritten like so:
+ %%
+ %% f({a=A,b=B}) -> . . .
+ %%
+ %% Aliased patterns that are illegal, such as:
+ %%
+ %% f(#{Key := Value} = {key := Key}) -> . . .
+ %%
+ %% have already been rejected by erl_lint.
+ %%
{Cp1,St1} = pattern(P1, St),
{Cp2,St2} = pattern(P2, St1),
{pat_alias(Cp1, Cp2),St2};
@@ -2187,9 +2314,22 @@ pat_alias(P1, #c_var{}=Var) ->
pat_alias(P1, #c_alias{pat=P2}=Alias) ->
Alias#c_alias{pat=pat_alias(P1, P2)};
+pat_alias(#ibinary{segments=[]}=P, #ibinary{segments=[]}) ->
+ P;
+pat_alias(#ibinary{segments=[_|_]=Segs1}=P, #ibinary{segments=[S0|Segs2]}) ->
+ %% Handle aliases of binary patterns in a clause. Example:
+ %% f(<<A:8,B:8>> = <<C:16>>) -> . . .
+ #ibitstr{anno=#a{anno=Anno}=A} = S0,
+ S = S0#ibitstr{anno=A#a{anno=[sequential_match|Anno]}},
+ P#ibinary{segments=Segs1++[S|Segs2]};
+pat_alias(#ibinary{segments=[S0|Segs1]}=P, #ibinary{segments=[]}) ->
+ %% Example: f(<<_:0>> == <>>) -> . . .
+ #ibitstr{anno=#a{anno=Anno}=A} = S0,
+ S = S0#ibitstr{anno=A#a{anno=[sequential_match|Anno]}},
+ P#ibinary{segments=[S|Segs1]};
+
pat_alias(P1, P2) ->
- %% Aliases between binaries are not allowed, so the only
- %% legal patterns that remain are data patterns.
+ %% The only legal patterns that remain are data patterns.
case cerl:is_data(P1) andalso cerl:is_data(P2) of
false -> throw(nomatch);
true -> ok
@@ -2227,19 +2367,19 @@ string_to_conses(Line, Cs, Tail) ->
make_vars(Vs) -> [ #c_var{name=V} || V <- Vs ].
-new_fun_name(#core{function={F,A},fcount=I}=St) ->
+new_fun_name(#core{function={F,A},fcount=I}=St) when is_integer(I) ->
Name = "-" ++ atom_to_list(F) ++ "/" ++ integer_to_list(A)
++ "-fun-" ++ integer_to_list(I) ++ "-",
{list_to_atom(Name),St#core{fcount=I+1}}.
%% new_fun_name(Type, State) -> {FunName,State}.
-new_fun_name(Type, #core{fcount=C}=St) ->
+new_fun_name(Type, #core{fcount=C}=St) when is_integer(C) ->
{list_to_atom(Type ++ "$^" ++ integer_to_list(C)),St#core{fcount=C+1}}.
%% new_var_name(State) -> {VarName,State}.
-new_var_name(#core{vcount=C}=St) ->
+new_var_name(#core{vcount=C}=St) when is_integer(C) ->
{C,St#core{vcount=C + 1}}.
%% new_var(State) -> {{var,Name},State}.
@@ -2436,7 +2576,7 @@ known_bind(#known{}=K, _) -> K.
%% Update the known variables to only the set of variables that
%% should be known when entering the fun.
-known_in_fun(#known{base=[BaseKs|_],ks=Ks0,prev_ks=[PrevKs|_]}=K) ->
+known_in_fun(#known{base=[BaseKs|_],ks=Ks0,prev_ks=[PrevKs|_]}=K, Name) ->
%% Within a group of bodies that see the same bindings, calculate
%% the known variables for a fun. Example:
%%
@@ -2449,9 +2589,20 @@ known_in_fun(#known{base=[BaseKs|_],ks=Ks0,prev_ks=[PrevKs|_]}=K) ->
%%
%% Thus, only `A` is known when entering the fun.
- Ks = union(BaseKs, subtract(Ks0, PrevKs)),
+ Ks1 = union(BaseKs, subtract(Ks0, PrevKs)),
+ Ks = case Name of
+ unnamed -> Ks1;
+ {named,FName} -> union(Ks1, [FName])
+ end,
K#known{base=[],ks=Ks,prev_ks=[]};
-known_in_fun(#known{}=K) -> K.
+known_in_fun(#known{ks=Ks0}=K, Name) ->
+ case Name of
+ unnamed ->
+ K;
+ {named,FName} ->
+ Ks = union(Ks0, [FName]),
+ K#known{ks=Ks}
+ end.
%%%
%%% End of abstract data type for known variables.
@@ -2723,7 +2874,7 @@ uexpr(#ifun{anno=A0,id=Id,vars=As,clauses=Cs0,fc=Fc0,name=Name}=Fun0, Ks0, St0)
{named,FName} -> known_union(Ks0, subtract([FName], Avs))
end,
Ks2 = known_union(Ks1, Avs),
- KnownInFun = known_in_fun(Ks2),
+ KnownInFun = known_in_fun(Ks2, Name),
{Cs3,St3} = ufun_clauses(Cs2, KnownInFun, St2),
{Fc1,St4} = ufun_clause(Fc0, KnownInFun, St3),
Used = subtract(intersection(used_in_any(Cs3), known_get(Ks1)), Avs),
@@ -2779,6 +2930,8 @@ uexpr(#ibinary{anno=A,segments=Ss}, _, St) ->
uexpr(#c_literal{}=Lit, _, St) ->
Anno = get_anno(Lit),
{set_anno(Lit, #a{us=[],anno=Anno}),St};
+uexpr(#c_opaque{}=Opaque, _, St) ->
+ {set_anno(Opaque, #a{us=[],anno=get_anno(Opaque)}),St};
uexpr(Simple, _, St) ->
true = is_simple(Simple), %Sanity check!
Vs = lit_vars(Simple),
@@ -2791,9 +2944,9 @@ uexpr_list(Les0, Ks, St0) ->
%% upattern(Pat, [KnownVar], State) ->
%% {Pat,[GuardTest],[NewVar],[UsedVar],State}.
-upattern(#c_var{name='_'}, _, St0) ->
+upattern(#c_var{anno=Anno,name='_'}, _, St0) ->
{New,St1} = new_var_name(St0),
- {#c_var{name=New},[],[New],[],St1};
+ {#c_var{anno=Anno,name=New},[],[New],[],St1};
upattern(#c_var{name=V}=Var, Ks, St0) ->
case is_element(V, known_get(Ks)) of
true ->
@@ -2996,9 +3149,10 @@ ren_pat(#ibinary{segments=Es0}=P, Ks, {Isub,Osub0}, St0) ->
{Es,_Isub,Osub,St} = ren_pat_bin(Es0, Ks, Isub, Osub0, St0),
{P#ibinary{segments=Es},{Isub,Osub},St};
ren_pat(P, Ks0, {_,_}=Subs0, St0) ->
+ Anno = cerl:get_ann(P),
Es0 = cerl:data_es(P),
{Es,Subs,St} = ren_pats(Es0, Ks0, Subs0, St0),
- {cerl:make_data(cerl:data_type(P), Es),Subs,St}.
+ {cerl:ann_make_data(Anno, cerl:data_type(P), Es),Subs,St}.
ren_pat_bin([#ibitstr{val=Val0,size=Sz0}=E|Es0], Ks, Isub0, Osub0, St0) ->
Sz = ren_get_subst(Sz0, Isub0),
@@ -3042,6 +3196,9 @@ ren_is_subst(_V, []) -> no.
%% from case/receive. In subblocks/clauses the AfterVars of the block
%% are just the exported variables.
+cbody(B0, none, St0) ->
+ {B1,_,_,St1} = cexpr(B0, [], St0),
+ {B1,St1};
cbody(B0, Nifs, St0) ->
{B1,_,_,St1} = cexpr(B0, [], St0),
B2 = case sets:is_element(St1#core.function,Nifs) of
@@ -3255,6 +3412,8 @@ cexpr(#icall{anno=A,module=Mod,name=Name,args=Args}, _As, St0) ->
false ->
{#c_call{anno=Anno,module=Mod,name=Name,args=Args},[],A#a.us,St0}
end;
+cexpr(O=#c_opaque{}, _As, St) ->
+ {O,[],[],St};
cexpr(#iprimop{anno=A,name=Name,args=Args}, _As, St) ->
{#c_primop{anno=A#a.anno,name=Name,args=Args},[],A#a.us,St};
cexpr(#iprotect{anno=A,body=Es}, _As, St0) ->
@@ -3356,6 +3515,7 @@ skip_lowering(#c_call{}, _A) -> skip;
skip_lowering(#c_cons{}, _A) -> skip;
skip_lowering(#c_literal{}, _A) -> skip;
skip_lowering(#c_map{}, _A) -> skip;
+skip_lowering(#c_opaque{}, _A) -> skip;
skip_lowering(#c_primop{}, _A) -> skip;
skip_lowering(#c_tuple{}, _A) -> skip;
skip_lowering(T, A) -> {T, A}.
@@ -3615,14 +3775,26 @@ split_pats([P0|Ps0], St0) ->
split_pats([], _) ->
none.
-split_pat(#c_binary{segments=Segs0}=Bin, St0) ->
+split_pat(#c_binary{anno=Anno0,segments=Segs0}=Bin, St0) ->
Vars = gb_sets:empty(),
case split_bin_segments(Segs0, Vars, St0, []) of
none ->
none;
- {TailVar,Wrap,Bef,Aft,St} ->
- BefBin = Bin#c_binary{segments=Bef},
- {BefBin,{split,[TailVar],Wrap,Bin#c_binary{segments=Aft},nil},St}
+ {size_var,TailVar,Wrap,Bef,Aft,St1} ->
+ {BefBin,Anno,St} = size_var_before_bin(Bin, Bef, St1),
+ {BefBin,{split,[TailVar],Wrap,Bin#c_binary{anno=Anno,segments=Aft},nil},St};
+ {sequential_match,Bef,Aft,St1} ->
+ Anno = keydelete(binary_var, 1, Anno0),
+ {BefBin,St} =
+ case keyfind(binary_var, 1, Anno0) of
+ false ->
+ {BinVar,StInt} = new_var(St1),
+ {#c_alias{var=BinVar,pat=Bin#c_binary{segments=Bef}},StInt};
+ {binary_var,BinVar} ->
+ {Bin#c_binary{anno=Anno,segments=Bef},St1}
+ end,
+ Wrap = fun(Body) -> Body end,
+ {BefBin,{split,[BinVar],Wrap,Bin#c_binary{anno=Anno,segments=Aft},nil},St}
end;
split_pat(#c_map{es=Es}=Map, St) ->
split_map_pat(Es, Map, St, []);
@@ -3642,6 +3814,28 @@ split_pat(Data, St0) ->
Es = cerl:data_es(Data),
split_data(Es, Type, St0, []).
+size_var_before_bin(#c_binary{anno=Anno0,segments=Segments}=Bin0, Bef, St0) ->
+ case any(fun(#c_bitstr{anno=Anno}) ->
+ member(sequential_match, Anno)
+ end, Segments) of
+ true ->
+ case keymember(binary_var, 1, Anno0) of
+ false ->
+ {BinVar,St1} = new_var(St0),
+ Bin = Bin0#c_binary{segments=Bef},
+ P = #c_alias{var=BinVar,pat=Bin},
+ Anno = [{binary_var,BinVar}|Anno0],
+ {P,Anno,St1};
+ true ->
+ Anno = keydelete(binary_var, 1, Anno0),
+ Bin = Bin0#c_binary{anno=Anno,segments=Bef},
+ {Bin,Anno0,St0}
+ end;
+ false ->
+ Bin = Bin0#c_binary{segments=Bef},
+ {Bin,Anno0,St0}
+ end.
+
split_map_pat([#c_map_pair{key=Key,val=Val}=E0|Es], Map0, St0, Acc) ->
case eval_map_key(Key, E0, Es, Map0, St0) of
none ->
@@ -3704,7 +3898,19 @@ split_data([E|Es0], Type, St0, Acc) ->
end;
split_data([], _, _, _) -> none.
-split_bin_segments([#c_bitstr{val=Val,size=Size}=S0|Segs], Vars0, St0, Acc) ->
+split_bin_segments([#c_bitstr{anno=Anno0}=S0|Segs], Vars, St, Acc) ->
+ case member(sequential_match, Anno0) of
+ true ->
+ Anno = Anno0 -- [sequential_match],
+ S = S0#c_bitstr{anno=Anno},
+ {sequential_match,reverse(Acc),[S|Segs],St};
+ false ->
+ split_bin_segments_1(S0, Segs, Vars, St, Acc)
+ end;
+split_bin_segments(_, _, _, _) ->
+ none.
+
+split_bin_segments_1(#c_bitstr{val=Val,size=Size}=S0, Segs, Vars0, St0, Acc) ->
Vars = case Val of
#c_var{name=V} -> gb_sets:add(V, Vars0);
_ -> Vars0
@@ -3721,7 +3927,7 @@ split_bin_segments([#c_bitstr{val=Val,size=Size}=S0|Segs], Vars0, St0, Acc) ->
%% in the same pattern.
{TailVar,Tail,St} = split_tail_seg(S0, Segs, St0),
Wrap = fun(Body) -> Body end,
- {TailVar,Wrap,reverse(Acc, [Tail]),[S0|Segs],St};
+ {size_var,TailVar,Wrap,reverse(Acc, [Tail]),[S0|Segs],St};
false ->
split_bin_segments(Segs, Vars, St0, [S0|Acc])
end;
@@ -3733,10 +3939,8 @@ split_bin_segments([#c_bitstr{val=Val,size=Size}=S0|Segs], Vars0, St0, Acc) ->
{SizeVar,St2} = new_var(St1),
S = S0#c_bitstr{size=SizeVar},
{Wrap,St3} = split_wrap(SizeVar, Size, St2),
- {TailVar,Wrap,reverse(Acc, [Tail]),[S|Segs],St3}
- end;
-split_bin_segments(_, _, _, _) ->
- none.
+ {size_var,TailVar,Wrap,reverse(Acc, [Tail]),[S|Segs],St3}
+ end.
split_tail_seg(#c_bitstr{anno=A}=S, Segs, St0) ->
{TailVar,St} = new_var(St0),
@@ -3890,6 +4094,18 @@ is_simple(_) -> false.
is_simple_list(Es) -> lists:all(fun is_simple/1, Es).
+insert_nif_start([VF={V,F=#c_fun{body=Body}}|Funs]) ->
+ case Body of
+ #c_seq{arg=#c_primop{name=#c_literal{val=nif_start}}} ->
+ [VF|insert_nif_start(Funs)];
+ #c_case{} ->
+ NifStart = #c_primop{name=#c_literal{val=nif_start},args=[]},
+ [{V,F#c_fun{body=#c_seq{arg=NifStart,body=Body}}}
+ |insert_nif_start(Funs)]
+ end;
+insert_nif_start([]) ->
+ [].
+
%%%
%%% Handling of warnings.
%%%
diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl
index febc971427..809924c7f3 100644
--- a/lib/compiler/src/v3_kernel.erl
+++ b/lib/compiler/src/v3_kernel.erl
@@ -119,6 +119,7 @@ copy_anno(Kdst, Ksrc) ->
free=#{}, %Free variables
ws=[] :: [warning()], %Warnings.
no_shared_fun_wrappers=false :: boolean(),
+ no_min_max_bifs=false :: boolean(),
labels=sets:new([{version, 2}])
}).
@@ -130,7 +131,9 @@ module(#c_module{anno=A,name=M,exports=Es,attrs=As,defs=Fs}, Options) ->
Kes = map(fun (#c_var{name={_,_}=Fname}) -> Fname end, Es),
NoSharedFunWrappers = proplists:get_bool(no_shared_fun_wrappers,
Options),
- St0 = #kern{no_shared_fun_wrappers=NoSharedFunWrappers},
+ NoMinMaxBifs = proplists:get_bool(no_min_max_bifs, Options),
+ St0 = #kern{no_shared_fun_wrappers=NoSharedFunWrappers,
+ no_min_max_bifs=NoMinMaxBifs},
{Kfs,St} = mapfoldl(fun function/2, St0, Fs),
{ok,#k_mdef{anno=A,name=M#c_literal.val,exports=Kes,attributes=Kas,
body=Kfs ++ St#kern.funs},sort(St#kern.ws)}.
@@ -323,7 +326,7 @@ expr(#c_call{anno=A,module=M0,name=F0,args=Cargs}, Sub, St0) ->
Ar = length(Cargs),
{[M,F|Kargs],Ap,St1} = atomic_list([M0,F0|Cargs], Sub, St0),
Remote = #k_remote{mod=M,name=F,arity=Ar},
- case call_type(M0, F0, Cargs) of
+ case call_type(M0, F0, Cargs, St1) of
bif ->
{#k_bif{anno=A,op=Remote,args=Kargs},Ap,St1};
call ->
@@ -357,7 +360,9 @@ expr(#c_try{anno=A,arg=Ca,vars=Cvs,body=Cb,evars=Evs,handler=Ch}, Sub0, St0) ->
evars=Kevs,handler=pre_seq(Ph, Kh)},[],St5};
expr(#c_catch{anno=A,body=Cb}, Sub, St0) ->
{Kb,Pb,St1} = body(Cb, Sub, St0),
- {#k_catch{anno=A,body=pre_seq(Pb, Kb)},[],St1}.
+ {#k_catch{anno=A,body=pre_seq(Pb, Kb)},[],St1};
+expr(#c_opaque{anno=A,val=V}, _, St) ->
+ {#k_opaque{anno=A,val=V},[],St}.
%% Implement letrec in the traditional way as a local
%% function for each definition in the letrec.
@@ -543,15 +548,24 @@ map_key_clean(#k_literal{val=V}) -> {lit,V}.
%% call_type(Module, Function, Arity) -> call | bif | error.
%% Classify the call.
-call_type(#c_literal{val=M}, #c_literal{val=F}, As) when is_atom(M), is_atom(F) ->
+call_type(#c_literal{val=M}, #c_literal{val=F}, As, St) when is_atom(M), is_atom(F) ->
case is_remote_bif(M, F, As) of
- false -> call;
- true -> bif
+ false ->
+ call;
+ true ->
+ %% The guard BIFs min/2 and max/2 were introduced in
+ %% Erlang/OTP 26. If we are compiling for an earlier
+ %% version, we must translate them as call instructions.
+ case {M,F,St#kern.no_min_max_bifs} of
+ {erlang,min,true} -> call;
+ {erlang,max,true} -> call;
+ {_,_,_} -> bif
+ end
end;
-call_type(#c_var{}, #c_literal{val=A}, _) when is_atom(A) -> call;
-call_type(#c_literal{val=A}, #c_var{}, _) when is_atom(A) -> call;
-call_type(#c_var{}, #c_var{}, _) -> call;
-call_type(_, _, _) -> error.
+call_type(#c_var{}, #c_literal{val=A}, _, _) when is_atom(A) -> call;
+call_type(#c_literal{val=A}, #c_var{}, _, _) when is_atom(A) -> call;
+call_type(#c_var{}, #c_var{}, _, _) -> call;
+call_type(_, _, _, _) -> error.
%% match_vars(Kexpr, State) -> {[Kvar],[PreKexpr],State}.
%% Force return from body into a list of variables.
@@ -1375,8 +1389,9 @@ select_bin_int([#iclause{pats=[#k_bin_seg{anno=A,type=integer,
true -> throw(not_possible);
false -> ok
end,
- Cs = select_bin_int_1(Cs0, Bits, Fl, Val),
- [{k_bin_int,[C#iclause{pats=[P|Ps]}|Cs]}];
+ Cs1 = [C#iclause{pats=[P|Ps]}|select_bin_int_1(Cs0, Bits, Fl, Val)],
+ Cs = reorder_bin_ints(Cs1),
+ [{k_bin_int,Cs}];
select_bin_int(_) -> throw(not_possible).
select_bin_int_1([#iclause{pats=[#k_bin_seg{anno=A,type=integer,
@@ -1419,6 +1434,44 @@ match_fun(Val) ->
{match,Bs}
end.
+reorder_bin_ints([_]=Cs) ->
+ Cs;
+reorder_bin_ints(Cs0) ->
+ %% It is safe to reorder clauses that matches binaries if the
+ %% first segments for all of them match the same number of bits
+ %% and if the patterns that follow are also safe to re-order.
+ try
+ Cs = sort([{reorder_bin_int_sort_key(C),C} || C <- Cs0]),
+ [C || {_,C} <- Cs]
+ catch
+ throw:not_possible ->
+ Cs0
+ end.
+
+reorder_bin_int_sort_key(#iclause{pats=[Pats|More],guard=#c_literal{val=true}}) ->
+ case all(fun(#k_var{}) -> true;
+ (_) -> false
+ end, More) of
+ true ->
+ %% Only variables. Safe to re-order.
+ ok;
+ false ->
+ %% Not safe to re-order. For example:
+ %% f([<<"prefix">>, <<"action">>]) -> ...
+ %% f([<<"prefix">>, Variable]) -> ...
+ throw(not_possible)
+ end,
+ case Pats of
+ #k_bin_int{val=Val,next=#k_bin_end{}} ->
+ %% Sort before clauses with additional segments. This usually results in
+ %% better code.
+ [Val];
+ #k_bin_int{val=Val} ->
+ [Val,more]
+ end;
+reorder_bin_int_sort_key(#iclause{}) ->
+ throw(not_possible).
+
%% match_value([Var], Con, [Clause], Default, State) -> {SelectExpr,State}.
%% At this point all the clauses have the same constructor, we must
%% now separate them according to value.
@@ -1512,9 +1565,7 @@ group_value(_, Us, Cs) ->
Map = group_values(Cs, #{}),
%% We must sort the grouped values to ensure consistent
%% order from compilation to compilation.
- sort(maps:fold(fun (_, Vcs, Css) ->
- [{Us,reverse(Vcs)}|Css]
- end, [], Map)).
+ sort([{Us,reverse(Vcs)} || _ := Vcs <- Map]).
group_values([C|Cs], Acc) ->
Val = clause_val(C),
@@ -2019,6 +2070,8 @@ uexpr(#k_letrec_goto{anno=A,vars=Vs,first=F0,then=T0}=MatchAlt, Br, St0) ->
{T1,Tu,St2} = ubody(T0, Br, St1),
Used = subtract(union(Fu, Tu), Ns),
{MatchAlt#k_letrec_goto{anno=A,first=F1,then=T1,ret=Rs},Used,St2};
+uexpr(#k_opaque{}=O, _, St) ->
+ {O,[],St};
uexpr(Lit, {break,Rs0}, St0) ->
%% Transform literals to puts here.
%%ok = io:fwrite("uexpr ~w:~p~n", [?LINE,Lit]),
@@ -2161,7 +2214,8 @@ lit_vars(#k_bin_seg{size=Size,seg=S,next=N}) ->
union(lit_vars(Size), union(lit_vars(S), lit_vars(N)));
lit_vars(#k_tuple{es=Es}) ->
lit_list_vars(Es);
-lit_vars(#k_literal{}) -> [].
+lit_vars(#k_literal{}) -> [];
+lit_vars(#k_opaque{}) -> [].
lit_list_vars(Ps) ->
foldl(fun (P, Vs) -> union(lit_vars(P), Vs) end, [], Ps).
diff --git a/lib/compiler/src/v3_kernel.hrl b/lib/compiler/src/v3_kernel.hrl
index 8d3e5dd7e7..5259a73418 100644
--- a/lib/compiler/src/v3_kernel.hrl
+++ b/lib/compiler/src/v3_kernel.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -70,5 +70,7 @@
-record(k_break, {anno=[],args=[]}).
-record(k_return, {anno=[],args=[]}).
+-record(k_opaque, {anno=[],val}).
+
%%k_get_anno(Thing) -> element(2, Thing).
%%k_set_anno(Thing, Anno) -> setelement(2, Thing, Anno).
diff --git a/lib/compiler/src/v3_kernel_pp.erl b/lib/compiler/src/v3_kernel_pp.erl
index d1b075ce0f..fa8c67b6af 100644
--- a/lib/compiler/src/v3_kernel_pp.erl
+++ b/lib/compiler/src/v3_kernel_pp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -139,7 +139,7 @@ format_1(#k_bin_end{}, _Ctxt) -> "#<>#";
format_1(#k_literal{val=A}, _Ctxt) when is_atom(A) ->
core_atom(A);
format_1(#k_literal{val=Term}, _Ctxt) ->
- io_lib:format("~p", [Term]);
+ io_lib:format("~kp", [Term]);
format_1(#k_local{name=N,arity=A}, Ctxt) ->
"local " ++ format_fa_pair({N,A}, Ctxt);
format_1(#k_remote{mod=M,name=N,arity=A}, _Ctxt) ->
@@ -356,6 +356,8 @@ format_1(#ifun{vars=Vs,body=B}, Ctxt) ->
nl_indent(Ctxt1)
| format(B, Ctxt1)
];
+format_1(#k_opaque{val=V}, _Ctxt) ->
+ ["** Opaque: ", io_lib:write(V), " **\n"];
format_1(Type, _Ctxt) ->
["** Unsupported type: ",
io_lib:write(Type)
diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile
index 8e145ae136..6ca1e988da 100644
--- a/lib/compiler/test/Makefile
+++ b/lib/compiler/test/Makefile
@@ -16,6 +16,7 @@ MODULES= \
beam_jump_SUITE \
beam_reorder_SUITE \
beam_ssa_SUITE \
+ beam_ssa_check_SUITE \
beam_type_SUITE \
beam_types_SUITE \
beam_utils_SUITE \
@@ -40,6 +41,7 @@ MODULES= \
map_SUITE \
match_SUITE \
maybe_SUITE \
+ mc_SUITE \
misc_SUITE \
overridden_bif_SUITE \
random_code_SUITE \
@@ -75,6 +77,7 @@ NO_OPT= \
map \
match \
maybe \
+ mc \
misc \
overridden_bif \
receive \
@@ -102,19 +105,27 @@ INLINE= \
map \
match \
maybe \
+ mc \
misc \
overridden_bif \
receive \
record
R23= \
- fun
+ fun \
+ bs_match
R24= \
bs_construct \
bs_utf \
bs_bincomp
+R25= \
+ bs_construct \
+ bs_match \
+ bs_utf \
+ bs_bincomp
+
DIALYZER = bs_match
CORE_MODULES = \
@@ -133,12 +144,16 @@ POST_OPT_MODULES= $(NO_OPT:%=%_post_opt_SUITE)
POST_OPT_ERL_FILES= $(POST_OPT_MODULES:%=%.erl)
NO_CORE_OPT_MODULES= $(NO_OPT:%=%_no_copt_SUITE)
NO_CORE_OPT_ERL_FILES= $(NO_CORE_OPT_MODULES:%=%.erl)
+NO_CORE_SSA_OPT_MODULES= $(NO_OPT:%=%_no_copt_ssa_SUITE)
+NO_CORE_SSA_OPT_ERL_FILES= $(NO_CORE_SSA_OPT_MODULES:%=%.erl)
INLINE_MODULES= $(INLINE:%=%_inline_SUITE)
INLINE_ERL_FILES= $(INLINE_MODULES:%=%.erl)
R23_MODULES= $(R23:%=%_r23_SUITE)
R23_ERL_FILES= $(R23_MODULES:%=%.erl)
R24_MODULES= $(R24:%=%_r24_SUITE)
R24_ERL_FILES= $(R24_MODULES:%=%.erl)
+R25_MODULES= $(R25:%=%_r25_SUITE)
+R25_ERL_FILES= $(R25_MODULES:%=%.erl)
NO_MOD_OPT_MODULES= $(NO_MOD_OPT:%=%_no_module_opt_SUITE)
NO_MOD_OPT_ERL_FILES= $(NO_MOD_OPT_MODULES:%=%.erl)
NO_SSA_OPT_MODULES= $(NO_SSA_OPT:%=%_no_ssa_opt_SUITE)
@@ -179,9 +194,10 @@ EBIN = .
DISABLE_SSA_OPT = +no_bool_opt +no_share_opt +no_bsm_opt +no_fun_opt +no_ssa_opt +no_recv_opt
make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES) \
- $(NO_CORE_OPT_ERL_FILES) $(INLINE_ERL_FILES) $(R23_ERL_FILES) \
+ $(NO_CORE_OPT_ERL_FILES) $(NO_CORE_SSA_OPT_ERL_FILES) \
+ $(INLINE_ERL_FILES) $(R23_ERL_FILES) \
$(NO_MOD_OPT_ERL_FILES) $(NO_TYPE_OPT_ERL_FILES) \
- $(DIALYZER_ERL_FILES) $(R24_ERL_FILES)
+ $(DIALYZER_ERL_FILES) $(R24_ERL_FILES) $(R25_ERL_FILES)
$(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \
> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +no_copt $(DISABLE_SSA_OPT) +no_postopt \
@@ -192,12 +208,16 @@ make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES
-o$(EBIN) $(POST_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +no_copt $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(NO_CORE_OPT_MODULES) >> $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile +no_copt +no_ssa_opt $(ERL_COMPILE_FLAGS) \
+ -o$(EBIN) $(NO_CORE_SSA_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +inline $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(INLINE_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +r23 $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(R23_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +r24 $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(R24_MODULES) >> $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile +r25 $(ERL_COMPILE_FLAGS) \
+ -o$(EBIN) $(R25_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +no_module_opt $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(NO_MOD_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +from_core $(ERL_COMPILE_FLAGS) \
@@ -233,6 +253,9 @@ docs:
%_no_copt_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+%_no_copt_ssa_SUITE.erl: %_SUITE.erl
+ sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+
%_inline_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
@@ -242,6 +265,9 @@ docs:
%_r24_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+%_r25_SUITE.erl: %_SUITE.erl
+ sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+
%_no_module_opt_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
@@ -266,7 +292,9 @@ release_tests_spec: make_emakefile
$(INLINE_ERL_FILES) \
$(R23_ERL_FILES) \
$(R24_ERL_FILES) \
+ $(R25_ERL_FILES) \
$(NO_CORE_OPT_ERL_FILES) \
+ $(NO_CORE_SSA_OPT_ERL_FILES) \
$(NO_MOD_OPT_ERL_FILES) \
$(NO_SSA_OPT_ERL_FILES) \
$(NO_TYPE_OPT_ERL_FILES) \
diff --git a/lib/compiler/test/andor_SUITE.erl b/lib/compiler/test/andor_SUITE.erl
index 250f2c5803..cf7e326f11 100644
--- a/lib/compiler/test/andor_SUITE.erl
+++ b/lib/compiler/test/andor_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -209,8 +209,14 @@ t_and_or(Config) when is_list(Config) ->
{'EXIT',{badarg,_}} = (catch true and Tuple)
end,
+ %% Cover code in beam_ssa_codegen when type optimizations are disabled.
+ {'EXIT',{badarg,_}} = catch bad_and(true),
+
ok.
+bad_and(A) ->
+ (not not ok) and id(A).
+
t_andalso(Config) when is_list(Config) ->
Bs = [true,false],
Ps = [{X,Y} || X <- Bs, Y <- Bs],
@@ -245,6 +251,9 @@ t_andalso(Config) when is_list(Config) ->
%% Cover conversion to right associativity.
true = (is_list(Config) andalso is_list(Bs)) andalso is_list(Ps),
+ %% Cover beam_ssa_dead:will_succeed_vars/4.
+ false = fun(V) -> V == V andalso not (V == V) end(a),
+
ok.
t_orelse(Config) when is_list(Config) ->
diff --git a/lib/compiler/test/beam_block_SUITE.erl b/lib/compiler/test/beam_block_SUITE.erl
index 9ccc8aa36d..f5a816fb9c 100644
--- a/lib/compiler/test/beam_block_SUITE.erl
+++ b/lib/compiler/test/beam_block_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -307,8 +307,16 @@ second_block_pass(_Config) ->
second_1(Fs, TS) ->
[F#{dts=>DTS / TS} || #{dts:=DTS} = F <- Fs].
-coverage(_Config) ->
+coverage(Config) ->
[] = coverage_1(),
+
+ [42,Config] = (coverage_2(Config))({fun id/1}),
+
+ {b,a,badarith} = coverage_3(a, b),
+ ok = coverage_3(0, 1),
+
+ {'EXIT',{badarg,_}} = catch coverage_4(a, b),
+
ok.
coverage_1() ->
@@ -316,6 +324,29 @@ coverage_1() ->
[] -> whatever
end) || 7 <- []].
+coverage_2(Cmds) ->
+ fun({CollecFun}) ->
+ [id(42), CollecFun(Cmds)]
+ end.
+
+coverage_3(Node, Nodes) ->
+ try id(Node) + 1 of
+ _ ->
+ ok
+ catch
+ _:Reason ->
+ coverage_3(Nodes, Node, Reason)
+ end.
+
+coverage_3(A, B, C) ->
+ id({A,B,C}).
+
+coverage_4(A, B) ->
+ do_coverage_4(ok, (ok xor ok)#{ok := ok}, B, ok) =:= A.
+
+do_coverage_4(_, _, _, _) ->
+ ok.
+
%%%
%%% Common functions.
%%%
diff --git a/lib/compiler/test/beam_bounds_SUITE.erl b/lib/compiler/test/beam_bounds_SUITE.erl
index 42e7f2bc27..e10f25c688 100644
--- a/lib/compiler/test/beam_bounds_SUITE.erl
+++ b/lib/compiler/test/beam_bounds_SUITE.erl
@@ -25,7 +25,12 @@
multiplication_bounds/1, division_bounds/1, rem_bounds/1,
band_bounds/1, bor_bounds/1, bxor_bounds/1,
bsr_bounds/1, bsl_bounds/1,
- lt_bounds/1, le_bounds/1, gt_bounds/1, ge_bounds/1]).
+ bnot_bounds/1,
+ lt_bounds/1, le_bounds/1, gt_bounds/1, ge_bounds/1,
+ min_bounds/1, max_bounds/1,
+ abs_bounds/1,
+ infer_lt_gt_bounds/1,
+ redundant_masking/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -42,12 +47,18 @@ groups() ->
band_bounds,
bor_bounds,
bxor_bounds,
+ bnot_bounds,
bsr_bounds,
bsl_bounds,
lt_bounds,
le_bounds,
gt_bounds,
- ge_bounds]}].
+ ge_bounds,
+ min_bounds,
+ max_bounds,
+ abs_bounds,
+ infer_lt_gt_bounds,
+ redundant_masking]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -63,17 +74,59 @@ end_per_group(_GroupName, Config) ->
Config.
addition_bounds(_Config) ->
- test_commutative('+', {-12,12}).
+ test_commutative('+', {-12,12}),
+
+ {'-inf',-15} = beam_bounds:bounds('+', {'-inf',-20}, {2,5}),
+ {'-inf',55} = beam_bounds:bounds('+', {'-inf',50}, {'-inf',5}),
+ {'-inf',110} = beam_bounds:bounds('+', {1,10}, {'-inf',100}),
+ any = beam_bounds:bounds('+', {1,'+inf'}, {'-inf',100}),
+
+ {-8,'+inf'} = beam_bounds:bounds('+', {2,'+inf'}, {-10,20}),
+ {6,'+inf'} = beam_bounds:bounds('+', {1,10}, {5,'+inf'}),
+ {9,'+inf'} = beam_bounds:bounds('+', {2,'+inf'}, {7,'+inf'}),
+
+ ok.
subtraction_bounds(_Config) ->
- test_noncommutative('-', {-12,12}).
+ test_noncommutative('-', {-12,12}),
+
+ {'-inf',18} = beam_bounds:bounds('-', {'-inf',20}, {2,9}),
+ any = beam_bounds:bounds('-', {'-inf',20}, {'-inf',17}),
+ {-99,'+inf'} = beam_bounds:bounds('-', {1,10}, {'-inf',100}),
+ {-93,'+inf'} = beam_bounds:bounds('-', {7,'+inf'}, {'-inf',100}),
+
+ {-18,'+inf'} = beam_bounds:bounds('-', {2,'+inf'}, {-10,20}),
+ {'-inf',6} = beam_bounds:bounds('-', {1,11}, {5,'+inf'}),
+ any = beam_bounds:bounds('-', {2,'+inf'}, {7,'+inf'}),
+
+ ok.
multiplication_bounds(_Config) ->
- test_commutative('*', {-12,12}).
+ test_commutative('*', {-12,12}),
+
+ {'-inf',-40} = beam_bounds:bounds('*', {'-inf',-20}, {2,5}),
+ {'-inf',1000} = beam_bounds:bounds('*', {'-inf',100}, {1,10}),
+ any = beam_bounds:bounds('*', {'-inf',100}, {-10,10}),
+
+ {-100,'+inf'} = beam_bounds:bounds('*', {-10,'+inf'}, {1,10}),
+ {7,'+inf'} = beam_bounds:bounds('*', {7,'+inf'}, {1,10}),
+ any = beam_bounds:bounds('*', {-10,'+inf'}, {-5,5}),
+
+ {'-inf',1000} = beam_bounds:bounds('*', {1,10}, {'-inf',100}),
+ {-100,'+inf'} = beam_bounds:bounds('*', {1,10}, {-10,'+inf'}),
+
+ ok.
division_bounds(_Config) ->
test_noncommutative('div', {-12,12}),
+ {'-inf',-5} = beam_bounds:bounds('div', {'-inf',-20}, {2,4}),
+ {'-inf',50} = beam_bounds:bounds('div', {'-inf',100}, {2,4}),
+
+ {-5,'+inf'} = beam_bounds:bounds('div', {-10,'+inf'}, {2,4}),
+ {2,'+inf'} = beam_bounds:bounds('div', {10,'+inf'}, {2,4}),
+
+ any = beam_bounds:bounds('div', {10,'+inf'}, {0,0}),
{'EXIT', {badarith, _}} = catch division_bounds_1([], ok),
ok.
@@ -88,8 +141,17 @@ division_bounds_1(_, _) ->
rem_bounds(_Config) ->
test_noncommutative('rem', {-12,12}),
- {-7,7} = beam_bounds:'rem'(any, {1,8}),
- {-11,11} = beam_bounds:'rem'(any, {-12,8}),
+ {-7,7} = beam_bounds:bounds('rem', any, {1,8}),
+ {-11,11} = beam_bounds:bounds('rem', any, {-12,8}),
+
+ {-7,7} = beam_bounds:bounds('rem', {'-inf',10}, {1,8}),
+ {0,7} = beam_bounds:bounds('rem', {10,'+inf'}, {1,8}),
+
+ any = beam_bounds:bounds('rem', {1,10}, {'-inf',10}),
+ any = beam_bounds:bounds('rem', {1,10}, {10,'+inf'}),
+
+ any = beam_bounds:bounds('rem', {-10,10}, {'-inf',10}),
+ any = beam_bounds:bounds('rem', {-10,10}, {10,'+inf'}),
ok.
@@ -97,36 +159,99 @@ band_bounds(_Config) ->
test_commutative('band'),
%% Coverage.
- {0,17} = beam_bounds:'band'(any, {7,17}),
- {0,42} = beam_bounds:'band'({0,42}, any),
- any = beam_bounds:'band'({-1,1}, any),
- any = beam_bounds:'band'(any, {-10,0}),
- any = beam_bounds:'band'({-10,0},{-1,10}),
- any = beam_bounds:'band'({-20,-10},{-1,10}),
+ {0,17} = beam_bounds:bounds('band', any, {7,17}),
+ {0,42} = beam_bounds:bounds('band', {0,42}, any),
+ any = beam_bounds:bounds('band', {-1,1}, any),
+ any = beam_bounds:bounds('band', any, {-10,0}),
+ any = beam_bounds:bounds('band', {-10,0}, {-1,10}),
+ any = beam_bounds:bounds('band', {-20,-10}, {-1,10}),
ok.
bor_bounds(_Config) ->
test_commutative('bor'),
- any = beam_bounds:'bor'({-10,0},{-1,10}),
- any = beam_bounds:'bor'({-20,-10},{-1,10}),
+ any = beam_bounds:bounds('bor', {-10,0},{-1,10}),
+ any = beam_bounds:bounds('bor', {-20,-10}, {-1,10}),
ok.
bxor_bounds(_Config) ->
test_commutative('bxor'),
- any = beam_bounds:'bxor'({-10,0},{-1,10}),
- any = beam_bounds:'bxor'({-20,-10},{-1,10}),
+ any = beam_bounds:bounds('bxor', {-10,0}, {-1,10}),
+ any = beam_bounds:bounds('bxor', {-20,-10}, {-1,10}),
ok.
+bnot_bounds(_Config) ->
+ Min = -7,
+ Max = 7,
+ Seq = lists:seq(Min, Max),
+ _ = [bnot_bounds_1({A,B}) ||
+ A <- Seq,
+ B <- lists:nthtail(A-Min, Seq)],
+
+ {-43,'+inf'} = beam_bounds:bounds('bnot', {'-inf',42}),
+ {99,'+inf'} = beam_bounds:bounds('bnot', {'-inf',-100}),
+ {'-inf',-8} = beam_bounds:bounds('bnot', {7,'+inf'}),
+ {'-inf',9} = beam_bounds:bounds('bnot', {-10,'+inf'}),
+
+ -1 = bnot_bounds_2(0),
+
+ ok.
+
+bnot_bounds_1(R) ->
+ {HighestMin,LowestMax} = min_max_unary_op('bnot', R),
+ {Min,Max} = beam_bounds:bounds('bnot', R),
+ if
+ Min =< HighestMin, LowestMax =< Max ->
+ ok;
+ true ->
+ io:format("bnot(~p) evaluates to ~p; should be ~p\n",
+ [R,{Min,Max},{HighestMin,LowestMax}]),
+ ct:fail(bad_min_or_max)
+ end.
+
+%% GH-7145: 'bnot' converged too slowly, effectively hanging the compiler.
+bnot_bounds_2(0) -> -1;
+bnot_bounds_2(N) -> abs(bnot bnot_bounds_2(N)).
+
bsr_bounds(_Config) ->
- test_noncommutative('bsr', {-12,12}, {0,7}).
+ test_noncommutative('bsr', {-12,12}, {0,7}),
+
+ {0,10} = beam_bounds:bounds('bsr', {0,10}, {0,'+inf'}),
+ {0,2} = beam_bounds:bounds('bsr', {0,10}, {2,'+inf'}),
+
+ {-1,10} = beam_bounds:bounds('bsr', {-1,10}, {0,'+inf'}),
+ {-100,900} = beam_bounds:bounds('bsr', {-100,900}, {0,'+inf'}),
+ {-50,450} = beam_bounds:bounds('bsr', {-100,900}, {1,'+inf'}),
+
+ {'-inf',16} = beam_bounds:bounds('bsr', {'-inf',32}, {1,10}),
+ {-5,'+inf'} = beam_bounds:bounds('bsr', {-10,'+inf'}, {1,10}),
+
+ ok.
bsl_bounds(_Config) ->
- test_noncommutative('bsl', {-12,12}, {0,7}).
+ test_noncommutative('bsl', {-12,12}, {-7,7}),
+
+ {2,'+inf'} = beam_bounds:bounds('bsl', {1,10}, {1,10_000}),
+ {0,'+inf'} = beam_bounds:bounds('bsl', {1,10}, {-10,10_000}),
+ any = beam_bounds:bounds('bsl', {-7,10}, {1,10_000}),
+
+ any = beam_bounds:bounds('bsl', {-10,100}, {0,'+inf'}),
+ any = beam_bounds:bounds('bsl', {-10,100}, {1,'+inf'}),
+ any = beam_bounds:bounds('bsl', {-10,100}, {-1,'+inf'}),
+
+ {0,10} = beam_bounds:bounds('bsl', {1,10}, {'-inf',0}),
+ {0,20} = beam_bounds:bounds('bsl', {1,10}, {'-inf',1}),
+ {-7,10} = beam_bounds:bounds('bsl', {-7,10}, {'-inf',0}),
+ {-28,40} = beam_bounds:bounds('bsl', {-7,10}, {'-inf',2}),
+
+ {'-inf',-1} = beam_bounds:bounds('bsl', {-10,-1}, {500,1024}),
+ {0,'+inf'} = beam_bounds:bounds('bsl', {1,10}, {500,1024}),
+
+ ok.
lt_bounds(_Config) ->
test_relop('<').
@@ -140,8 +265,98 @@ gt_bounds(_Config) ->
ge_bounds(_Config) ->
test_relop('>=').
+min_bounds(_Config) ->
+ test_commutative(min, {-12,12}),
+
+ {'-inf',-10} = min_bounds({'-inf',-10}, {1,100}),
+ {'-inf',1} = min_bounds({'-inf',1}, {1,100}),
+ {'-inf',50} = min_bounds({'-inf',50}, {1,100}),
+ {'-inf',100} = min_bounds({'-inf',500}, {1,100}),
+
+ {'-inf',-10} = min_bounds({'-inf',-10}, {1,'+inf'}),
+ {'-inf',1} = min_bounds({'-inf',1}, {1,'+inf'}),
+ {'-inf',700} = min_bounds({'-inf',700}, {1,'+inf'}),
+
+ {1,99} = min_bounds({1,99}, {100,'+inf'}),
+ {1,100} = min_bounds({1,100}, {100,'+inf'}),
+ {100,200} = min_bounds({150,200}, {100,'+inf'}),
+
+ ok.
+
+min_bounds(R1, R2) ->
+ Result = beam_bounds:bounds(min, R1, R2),
+ Result = beam_bounds:bounds(min, R2, R1).
+
+max_bounds(_Config) ->
+ test_commutative(max, {-12,12}),
+
+ {1,100} = max_bounds({'-inf',-10}, {1,100}),
+ {1,100} = max_bounds({'-inf',1}, {1,100}),
+ {1,100} = max_bounds({'-inf',50}, {1,100}),
+ {1,500} = max_bounds({'-inf',500}, {1,100}),
+
+ {1,'+inf'} = max_bounds({'-inf',-10}, {1,'+inf'}),
+ {1,'+inf'} = max_bounds({'-inf',1}, {1,'+inf'}),
+ {1,'+inf'} = max_bounds({'-inf',700}, {1,'+inf'}),
+
+ {100,'+inf'} = max_bounds({1,99}, {100,'+inf'}),
+ {100,'+inf'} = max_bounds({1,100}, {100,'+inf'}),
+ {150,'+inf'} = max_bounds({150,200}, {100,'+inf'}),
+
+ ok.
+
+max_bounds(R1, R2) ->
+ Result = beam_bounds:bounds(max, R1, R2),
+ Result = beam_bounds:bounds(max, R2, R1).
+
+abs_bounds(_Config) ->
+ Min = -7,
+ Max = 7,
+ Seq = lists:seq(Min, Max),
+ _ = [abs_bounds_1({A,B}) ||
+ A <- Seq,
+ B <- lists:nthtail(A-Min, Seq)],
+ ok.
+
+abs_bounds_1(R) ->
+ {HighestMin,LowestMax} = min_max_unary_op('abs', R),
+ {Min,Max} = beam_bounds:bounds(abs, R),
+ if
+ Min =< HighestMin, LowestMax =< Max ->
+ ok;
+ true ->
+ io:format("~p(~p) evaluates to ~p; should be ~p\n",
+ [bif_abs,R,{Min,Max},{HighestMin,LowestMax}]),
+ ct:fail(bad_min_or_max)
+ end.
+
+infer_lt_gt_bounds(_Config) ->
+ {{'-inf',-1}, {'-inf',0}} = infer_lt_gt({'-inf',0}, {'-inf',0}),
+ {{'-inf',1}, {'-inf',2}} = infer_lt_gt({'-inf',1}, {'-inf',2}),
+ {{'-inf',-2}, {'-inf',-1}} = infer_lt_gt({'-inf',1}, {'-inf',-1}),
+ {{'-inf',2}, {1,3}} = infer_lt_gt({'-inf',2}, {1,3}),
+
+ any = infer_lt_gt({'-inf',2}, {3,10}),
+ any = infer_lt_gt({'-inf',2}, {3,'+inf'}),
+
+ {{0,10}, {1,84}} = infer_lt_gt({0,10}, {'-inf',84}),
+ {{0,83}, {1,84}} = infer_lt_gt({0,'+inf'}, {'-inf',84}),
+
+ {{0,'+inf'}, {42, '+inf'}} = infer_lt_gt({0,'+inf'}, {42, '+inf'}),
+ {{100,'+inf'}, {101, '+inf'}} = infer_lt_gt({100,'+inf'}, {42, '+inf'}),
+
+ ok.
+
%%% Utilities
+infer_lt_gt(R1, R2) ->
+ case beam_bounds:infer_relop_types('>', R2, R1) of
+ {Rb,Ra} ->
+ {Ra,Rb} = beam_bounds:infer_relop_types('<', R1, R2);
+ any ->
+ any = beam_bounds:infer_relop_types('<', R1, R2)
+ end.
+
test_commutative(Op) ->
test_commutative(Op, {0,32}).
@@ -157,8 +372,8 @@ test_commutative(Op, {Min,Max}) ->
test_commutative_1(Op, R1, R2) ->
{HighestMin,LowestMax} = min_max_op(Op, R1, R2),
- {Min,Max} = beam_bounds:Op(R1, R2),
- {Min,Max} = beam_bounds:Op(R2, R1),
+ {Min,Max} = beam_bounds:bounds(Op, R1, R2),
+ {Min,Max} = beam_bounds:bounds(Op, R2, R1),
if
Min =< HighestMin, LowestMax =< Max ->
ok;
@@ -167,6 +382,7 @@ test_commutative_1(Op, R1, R2) ->
[Op,R1,R2,{Min,Max},{HighestMin,LowestMax}]),
ct:fail(bad_min_or_max)
end.
+
test_noncommutative(Op, Range) ->
test_noncommutative(Op, Range, Range).
@@ -182,7 +398,7 @@ test_noncommutative(Op, {Min1,Max1}, {Min2,Max2}) ->
test_noncommutative_1(Op, R1, R2) ->
{HighestMin,LowestMax} = min_max_op(Op, R1, R2),
- case beam_bounds:Op(R1, R2) of
+ case beam_bounds:bounds(Op, R1, R2) of
any ->
case {Op,R2} of
{'div',{0,0}} -> ok;
@@ -218,6 +434,20 @@ min_max_op_2(Op, A, C, D, MinMax) when C =< D ->
min_max_op_2(_Op, _, _, _, MinMax) ->
MinMax.
+min_max_unary_op(Op, {A,B}) ->
+ min_max_unary_op_1(Op, A, B, {infinity,-(1 bsl 24)}).
+
+min_max_unary_op_1(Op, A, B, {Min,Max}) when A =< B ->
+ Val = erlang:Op(A),
+ if
+ Min =< Val, Val =< Max ->
+ min_max_unary_op_1(Op, A + 1, B, {Min,Max});
+ true ->
+ min_max_unary_op_1(Op, A + 1, B, {min(Min, Val),max(Max, Val)})
+ end;
+min_max_unary_op_1(_Op, _, _, MinMax) ->
+ MinMax.
+
test_relop(Op) ->
Max = 15,
Seq = lists:seq(0, Max),
@@ -232,13 +462,45 @@ test_relop_1(Op, R1, R2) ->
Bool = rel_op(Op, R1, R2),
case beam_bounds:relop(Op, R1, R2) of
Bool ->
- ok;
+ test_infer_relop(Bool, Op, R1, R2);
Wrong ->
io:format("~p(~p, ~p) evaluates to ~p; should be ~p\n",
[Op,R1,R2,Wrong,Bool]),
ct:fail(bad_bool_result)
end.
+test_infer_relop(true, Op, R1, R2) ->
+ any = beam_bounds:infer_relop_types(Op, R1, R2);
+test_infer_relop(false, Op, R1, R2) ->
+ none = beam_bounds:infer_relop_types(Op, R1, R2);
+test_infer_relop('maybe', Op, {A0,B0}=R1, {C0,D0}=R2) ->
+ {{A,B},{C,D}} = beam_bounds:infer_relop_types(Op, R1, R2),
+ if
+ A =< B, C =< D, A0 =< A, B0 >= B, C0 =< C, D0 >= D ->
+ ok;
+ true ->
+ io:format("~p ~p infers as ~p ~p\n",
+ [R1,R2,{A,B},{C,D}]),
+ ct:fail(ranges_grew)
+ end,
+ _ = [begin
+ case in_range(X, {A,B}) andalso in_range(Y, {C,D}) of
+ true ->
+ ok;
+ false ->
+ io:format("X = ~p; Y = ~p\n", [X,Y]),
+ io:format("~p ~p infers as ~p ~p\n",
+ [R1,R2,{A,B},{C,D}]),
+ ct:fail(bad_inference)
+ end
+ end || X <- lists:seq(A0, B0),
+ Y <- lists:seq(C0, D0),
+ erlang:Op(X, Y)],
+ ok.
+
+in_range(Int, {A,B}) ->
+ A =< Int andalso Int =< B.
+
rel_op(Op, {A,B}, {C,D}) ->
rel_op_1(Op, A, B, C, D, none).
@@ -258,3 +520,37 @@ rel_op_2(Op, A, C, D, BoolResult0) when C =< D ->
rel_op_2(Op, A, C + 1, D, BoolResult);
rel_op_2(_Op, _, _, _, BoolResult) ->
BoolResult.
+
+redundant_masking(_Config) ->
+ Min = -7,
+ Max = 15,
+ Seq = lists:seq(Min, Max),
+ _ = [test_redundant_masking({A,B}, M) ||
+ A <- Seq,
+ B <- lists:nthtail(A-Min, Seq),
+ M <- Seq],
+
+ false = beam_bounds:is_masking_redundant({'-inf',10}, 16#ff),
+ false = beam_bounds:is_masking_redundant({0,'+inf'}, 16#ff),
+ ok.
+
+test_redundant_masking({A,B}=R, M) ->
+ ShouldBe = test_redundant_masking(A, B, M),
+ case beam_bounds:is_masking_redundant(R, M) of
+ ShouldBe ->
+ ok;
+ false when M band (M + 1) =/= 0 ->
+ %% M + 1 is not a power of two.
+ ok;
+ false when A =:= B ->
+ ok;
+ Unexpected ->
+ io:format("beam_bounds:is_masking_redundant(~p, ~p) "
+ "evaluates to ~p; should be ~p\n",
+ [R,M,Unexpected,ShouldBe]),
+ ct:fail(bad_boolean)
+ end.
+
+test_redundant_masking(A, B, M) when A =< B ->
+ A band M =:= A andalso test_redundant_masking(A + 1, B, M);
+test_redundant_masking(_, _, _) -> true.
diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl
index 9a90c1b676..f6aa666990 100644
--- a/lib/compiler/test/beam_except_SUITE.erl
+++ b/lib/compiler/test/beam_except_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
multiple_allocs/1,bs_get_tail/1,coverage/1,
- binary_construction_allocation/1]).
+ binary_construction_allocation/1,unfold_literals/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -34,7 +34,8 @@ groups() ->
[multiple_allocs,
bs_get_tail,
coverage,
- binary_construction_allocation]}].
+ binary_construction_allocation,
+ unfold_literals]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -177,6 +178,19 @@ do_binary_construction_allocation(Req) ->
_ -> ok
end.
+unfold_literals(_Config) ->
+ a = do_unfold_literals(badarg, id({a,b})),
+ {'EXIT',{badarg,_}} = catch do_unfold_literals(badarg, id(a)),
+
+ ok.
+
+do_unfold_literals(_BadArg, T) ->
+ %% The call `erlang:error(badarg)` in ?EXCEPTION_BLOCK would be
+ %% rewritten to `erlang:error(_BadArg)` by
+ %% beam_ssa_opt:unfold_literals/1, which would cause
+ %% beam_ssa_codegen:assert_exception_block/1 to fail.
+ element(1, T).
+
id(I) -> I.
-file("fake.erl", 1).
diff --git a/lib/compiler/test/beam_jump_SUITE.erl b/lib/compiler/test/beam_jump_SUITE.erl
index f90eec9446..65fecd5b7c 100644
--- a/lib/compiler/test/beam_jump_SUITE.erl
+++ b/lib/compiler/test/beam_jump_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
%% %CopyrightEnd%
%%
-module(beam_jump_SUITE).
+-feature(maybe_expr, enable).
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
@@ -81,6 +82,8 @@ ambiguous_catch_try_state(Config) ->
{'EXIT',{{badmatch,0},_}} = (catch ambiguous_catch_try_state_2()),
{'EXIT',{{badmatch,0},_}} = (catch ambiguous_catch_try_state_3()),
+ {'EXIT',{badarg,_}} = catch ambiguous_catch_try_state_4(),
+
ok.
river() -> song.
@@ -228,6 +231,12 @@ ambiguous_catch_try_state_3() ->
end.
+ambiguous_catch_try_state_4() ->
+ 0.0 = try binary_to_float(garbage_collect() orelse ((1.0 = tuple_to_list(ok)) -- ok))
+ after
+ ok
+ end.
+
-record(message2, {id, p1}).
-record(message3, {id, p1, p2}).
@@ -253,6 +262,10 @@ coverage(_Config) ->
error = coverage_3(#{key => <<"child">>}),
error = coverage_3(#{}),
+
+ ok = coverage_4(whatever),
+ -0.5 = coverage_4(any),
+
ok.
coverage_1(Var) ->
@@ -287,6 +300,15 @@ coverage_3(#{key := <<child>>}) when false ->
coverage_3(#{}) ->
error.
+%% Cover beam_jump:value_to_literal/1.
+coverage_4(whatever) ->
+ maybe
+ coverage_4(ok),
+ ok
+ end;
+coverage_4(_) ->
+ (bnot 0) / 2.
+
%% ERIERL-478: The validator failed to validate argument types when calls were
%% shared and the types at the common block turned out wider than the join of
%% each individual call site.
@@ -329,6 +351,9 @@ undecided_allocation(_Config) ->
{'EXIT',_} = catch undecided_allocation_2(id(foobar)),
{'EXIT',_} = catch undecided_allocation_2(id(make_ref())),
+ ok = undecided_allocation_3(id(<<0>>), gurka),
+ {'EXIT', {badarith, _}} = catch undecided_allocation_3(id(<<>>), gurka),
+
ok.
-record(rec, {}).
@@ -381,6 +406,21 @@ undecided_allocation_2(Order) ->
error
end.
+%% GH-6571: bs_init_writable can only be shared when the stack frame size is
+%% known.
+undecided_allocation_3(<<_>>, _) ->
+ ok;
+undecided_allocation_3(X, _) ->
+ case 0 + get_keys() of
+ X ->
+ ok;
+ _ ->
+ (node() orelse garbage_collect()) =:=
+ case <<0 || false>> of
+ #{} ->
+ ok
+ end
+ end.
id(I) ->
I.
diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl
index b98ff3644c..0bb485c7f1 100644
--- a/lib/compiler/test/beam_ssa_SUITE.erl
+++ b/lib/compiler/test/beam_ssa_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
%% %CopyrightEnd%
%%
-module(beam_ssa_SUITE).
+-feature(maybe_expr, enable).
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
@@ -451,6 +452,8 @@ maps(_Config) ->
{jkl,nil,nil} = maps_2(#{jkl => 0}),
error = maps_2(#{}),
+ [] = maps_3(),
+
ok.
maps_1(K) ->
@@ -523,6 +526,15 @@ maps_2b(#{}=Map) ->
end
end.
+%% Cover code in beam_ssa_codegen.
+maps_3() ->
+ [] = case #{} of
+ #{ok := {}} ->
+ ok;
+ _ ->
+ []
+ end -- [].
+
-record(wx_ref, {type=any_type,ref=any_ref}).
cover_ssa_dead(_Config) ->
@@ -887,6 +899,15 @@ grab_bag(_Config) ->
{'EXIT',{{try_clause,[]},[_|_]}} = catch grab_bag_18(),
+ {'EXIT',{{badmatch,[whatever]},[_|_]}} = catch grab_bag_19(),
+
+ {'EXIT',{if_clause,[_|_]}} = catch grab_bag_20(),
+
+ 6 = grab_bag_21(id(64)),
+ {'EXIT',{badarith,_}} = catch grab_bag_21(id(a)),
+
+ false = grab_bag_22(),
+
ok.
grab_bag_1() ->
@@ -1113,6 +1134,67 @@ grab_bag_18() ->
end
end.
+grab_bag_19() ->
+ ([<<bad/utf8>>] =
+ %% beam_ssa_pre_codegen would produce single-valued phi
+ %% nodes, which in turn would cause the constant propagation
+ %% in beam_ssa_codegen:prefer_xregs/2 to produce get_hd and
+ %% get_tl instructions with literal operands.
+ try
+ [whatever]
+ catch
+ _:_ when false ->
+ ok
+ end) ! (some_atom ++ <<>>).
+
+grab_bag_20() ->
+ %% Similarly to grab_bag_19, beam_ssa_pre_codegen would produce
+ %% single-valued phi nodes. The fix for grab_bag_19 would not
+ %% suffice because several phi nodes were involved.
+ {[_ | _] =
+ receive
+ list ->
+ "list";
+ 1 when day ->
+ []
+ after
+ 0 ->
+ if
+ false ->
+ error
+ end
+ end,
+ try
+ ok
+ catch
+ error:_ ->
+ error
+ end}.
+
+%% With the `no_copt` and `no_ssa_opt` options, an internal
+%% consistency error would be reported:
+%%
+%% Internal consistency check failed - please report this bug.
+%% Instruction: {test_heap,2,2}
+%% Error: {{x,0},not_live}:
+grab_bag_21(A) ->
+ _ = id(0),
+ grab_bag_21(ok, A div 10, node(), [-1]).
+
+grab_bag_21(_, D, _, _) ->
+ D.
+
+%% GH-7128: With optimizations disabled, the code would fail to
+%% load with the following message:
+%%
+%% beam/beam_load.c(367): Error loading function
+%% beam_ssa_no_opt_SUITE:grab_bag_22/0: op get_list: Sdd:
+%% bad tag 2 for destination
+grab_bag_22() ->
+ maybe
+ [_ | _] ?= ((true xor true) andalso foo),
+ bar ?= id(42)
+ end.
redundant_br(_Config) ->
{false,{x,y,z}} = redundant_br_1(id({x,y,z})),
diff --git a/lib/compiler/test/beam_ssa_check_SUITE.erl b/lib/compiler/test/beam_ssa_check_SUITE.erl
new file mode 100644
index 0000000000..c1d185cf68
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_check_SUITE.erl
@@ -0,0 +1,138 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(beam_ssa_check_SUITE).
+
+%% Runs tests checking the structure of BEAM SSA code.
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/erl_compile.hrl").
+
+-export([all/0, suite/0, groups/0,
+ init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2,
+
+ alias_checks/1,
+ annotation_checks/1,
+ appendable_checks/1,
+ bs_size_unit_checks/1,
+ private_append_checks/1,
+ ret_annotation_checks/1,
+ sanity_checks/1]).
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group,post_ssa_opt_dynamic},{group,post_ssa_opt_static}].
+
+groups() ->
+ [{post_ssa_opt_static,test_lib:parallel(),
+ [alias_checks,
+ annotation_checks,
+ appendable_checks,
+ private_append_checks,
+ ret_annotation_checks,
+ sanity_checks]},
+ {post_ssa_opt_dynamic,test_lib:parallel(),
+ [bs_size_unit_checks]}].
+
+init_per_suite(Config) ->
+ test_lib:recompile(?MODULE),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(post_ssa_opt_dynamic, Config) ->
+ TargetDir = dynamic_workdir(Config),
+ ct:log("Creating working directory for generated test cases: ~p~n",
+ [TargetDir]),
+ ok = file:make_dir(TargetDir),
+ Config;
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(post_ssa_opt_dynamic, Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ TargetDir = filename:join(PrivDir, "dynamic"),
+ case proplists:get_bool(keep_generated, Config) of
+ false ->
+ ct:log("Removing working directory for generated test cases: ~p~n",
+ [TargetDir]),
+ file:del_dir_r(TargetDir);
+ true ->
+ Config
+ end;
+end_per_group(_GroupName, Config) ->
+ Config.
+
+alias_checks(Config) when is_list(Config) ->
+ run_post_ssa_opt(alias, Config),
+ run_post_ssa_opt(alias_non_convergence, Config),
+ run_post_ssa_opt(alias_chain, Config).
+
+annotation_checks(Config) when is_list(Config) ->
+ run_post_ssa_opt(annotations, Config).
+
+appendable_checks(Config) when is_list(Config) ->
+ run_post_ssa_opt(appendable, Config).
+
+bs_size_unit_checks(Config) when is_list(Config) ->
+ gen_and_run_post_ssa_opt(bs_size_unit_checks, Config).
+
+private_append_checks(Config) when is_list(Config) ->
+ run_post_ssa_opt(private_append, Config).
+
+ret_annotation_checks(Config) when is_list(Config) ->
+ run_post_ssa_opt(ret_annotation, Config).
+
+sanity_checks(Config) when is_list(Config) ->
+ run_post_ssa_opt(sanity_checks, Config).
+
+dynamic_workdir(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ filename:join(PrivDir, "dynamic").
+
+run_post_ssa_opt(Module, Config) ->
+ File = atom_to_list(Module) ++ ".erl",
+
+ DataDir = proplists:get_value(data_dir, Config),
+ Source = filename:join(DataDir, File),
+ run_checks(Source, post_ssa_opt, Config).
+
+run_checks(SourceFile, Pass, _Config) ->
+ Flags = [binary, deterministic, {check_ssa,Pass}, report_errors],
+ case compile:file(SourceFile, Flags) of
+ {ok,_,_} -> ok;
+ error -> ct:fail({unexpected_error, "SSA check failed"})
+ end.
+
+gen_and_run_post_ssa_opt(Base, Config) ->
+ BaseStr = atom_to_list(Base),
+ GenFilenameBase = "gen_" ++ BaseStr,
+ GenModule = list_to_atom(GenFilenameBase),
+ GenFilename = filename:join(proplists:get_value(data_dir, Config),
+ GenFilenameBase ++ ".erl"),
+ ct:log("Compiling generator ~s~n", [GenFilename]),
+ {ok,GenModule,GenCode} = compile:file(GenFilename, [binary]),
+ {module,GenModule} = code:load_binary(GenModule, GenFilename, GenCode),
+ TargetFileName = filename:join(dynamic_workdir(Config), BaseStr ++ ".erl"),
+ ct:log("Generating ~s~n", [TargetFileName]),
+ ok = GenModule:generate(TargetFileName, Config),
+ run_checks(TargetFileName, post_ssa_opt, Config).
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/alias.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/alias.erl
new file mode 100644
index 0000000000..5375298493
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/alias.erl
@@ -0,0 +1,674 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% This module tests that beam_ssa_alias_opt:opt/2 correctly annotates
+%% instructions with information about unique and aliased operands.
+%%
+
+-compile(no_ssa_opt_private_append).
+
+-module(alias).
+
+-export([transformable0/1,
+ transformable1/1,
+ transformable1b/1,
+ transformable2/1,
+ transformable3/1,
+ transformable4/1,
+ transformable5/1,
+ %% transformable6/1,
+ transformable7/1,
+ transformable8/1,
+ transformable9/1,
+ transformable10/1,
+ transformable11/1,
+ transformable12a/1,
+ transformable12b/1,
+ transformable13/1,
+ transformable14/1,
+ transformable15/1,
+ transformable16/1,
+ transformable17/1,
+ transformable18/2,
+ transformable19/1,
+ transformable20/1,
+ transformable21/1,
+ transformable22/1,
+ transformable23/1,
+ transformable24/1,
+ transformable25/1,
+ transformable26/1,
+
+ not_transformable1/2,
+ not_transformable2/1,
+ not_transformable3/1,
+ not_transformable4/1,
+ not_transformable5/1,
+
+ bad_get_status_by_type/0,
+ stacktrace0/0,
+ stacktrace1/0,
+ in_cons/0,
+ make_fun/0,
+ gh6925/0]).
+
+%% Trivial smoke test
+transformable0(L) ->
+%ssa% (A) when post_ssa_opt ->
+%ssa% _ = call(fun transformable0/2, A, _) { aliased => [A] }.
+ transformable0(L, <<>>).
+
+transformable0([H|T], Acc) ->
+%ssa% (_, A) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, A, _, _, _, B, ...) { aliased => [B], unique => [A], first_fragment_dies => true }.
+ transformable0(T, <<Acc/binary, H:8>>);
+transformable0([], Acc) ->
+ Acc.
+
+transformable1(L) ->
+ transformable1(L, start).
+
+transformable1(L, start) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [X], unique => [Arg1], first_fragment_dies => true }.
+ transformable1(L, <<>>);
+transformable1([H|T], Acc) ->
+ transformable1(T, <<Acc/binary, H:8>>);
+transformable1([], Acc) ->
+ Acc.
+
+transformable1b(L) ->
+ transformable1b(L, start).
+
+transformable1b([H|T], X) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% Phi = phi({Arg1, _}, {_, _}, ...),
+%ssa% _ = bs_create_bin(append, _, Phi, _, _, _, X, _) { aliased => [X], unique => [Phi], first_fragment_dies => true }.
+ Acc = case X of
+ start ->
+ <<>>;
+ _ ->
+ X
+ end,
+ N = <<Acc/binary, H:8>>,
+ transformable1b(T, N);
+transformable1b([], Acc) ->
+ Acc.
+
+transformable2(L) ->
+ transformable2(L, <<>>).
+
+transformable2([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [X], unique => [Arg1], first_fragment_dies => true },
+%ssa% _ = call(fun transformable2/2, _, A).
+ case ex:f() of
+ true ->
+ transformable2(T, <<Acc/binary, H:8>>);
+ false ->
+ transformable2(T, <<>>)
+ end;
+transformable2([], Acc) ->
+ Acc.
+
+transformable3(L) ->
+ transformable3(L, <<>>).
+
+transformable3([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [X], unique => [Arg1], first_fragment_dies => true }.
+ R = case ex:f() of
+ true ->
+ <<Acc/binary, H:8>>;
+ false ->
+ <<>>
+ end,
+ transformable3(T, R);
+transformable3([], Acc) ->
+ Acc.
+
+transformable4(L) ->
+ transformable4(L, <<>>).
+
+transformable4([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% Phi = phi({_, _}, {Arg1, _}, ...),
+%ssa% _ = bs_create_bin(append, _, Phi, _, _, _, X, _) { aliased => [X], unique => [Phi], first_fragment_dies => true }.
+ R = case ex:f() of
+ true ->
+ Acc;
+ false ->
+ <<>>
+ end,
+ transformable4(T, <<R/binary, H:8>>);
+transformable4([], Acc) ->
+ Acc.
+
+%% Check that the alias analysis handles local functions.
+transformable5(L) ->
+ transformable5(L, <<>>).
+
+transformable5([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [X], unique => [Arg1], first_fragment_dies => true }.
+ does_not_escape(Acc),
+ transformable5(T, <<Acc/binary, H:8>>);
+transformable5([], Acc) ->
+ Acc.
+
+does_not_escape(_) ->
+ ok.
+
+%% Check that the analysis works when we have an appendable binary in
+%% the head of a cons.
+transformable7(L) ->
+ transformable7(L, [<<>>|0]).
+
+transformable7([H|T], [Acc|N]) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_hd(Arg1),
+%ssa% _ = bs_create_bin(append, _, A, _, _, _, X, _) { aliased => [X], unique => [A], first_fragment_dies => true }.
+ transformable7(T, [<<Acc/binary, H:8>>|N+1]);
+transformable7([], Acc) ->
+ Acc.
+
+%% Check that the analysis works when we have an appendable binary in
+%% just one of the clauses.
+transformable8(L) ->
+ transformable8(L, start).
+
+transformable8(L, start) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [X], unique => [Arg1], first_fragment_dies => true }.
+ transformable8(L, <<>>);
+transformable8([H|T], Acc) ->
+ transformable8b(T, <<Acc/binary, H:8>>);
+transformable8([], Acc) ->
+ Acc.
+
+transformable8b(T, Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...) { unique => [Arg1], first_fragment_dies => true }.
+ transformable8(T, <<Acc/binary, 16#ff:8>>).
+
+%% Check that the analysis works across mutually recursive functions.
+transformable9(L) ->
+ transformable9a(L, <<>>).
+
+transformable9a([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, _, _, _, _, X, _) { aliased => [X], unique => [Arg1], first_fragment_dies => true }.
+ transformable9b(T, <<Acc/binary, 0:8, H:8>>);
+transformable9a([], Acc) ->
+ Acc.
+
+transformable9b([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, _, _, _, _, X, _) { aliased => [X], unique => [Arg1], first_fragment_dies => true }.
+ transformable9a(T, <<Acc/binary, 1:8, H:8>>);
+transformable9b([], Acc) ->
+ Acc.
+
+%% Check that the analysis works for binaries embedded in a literal.
+transformable10(L) ->
+ transformable10(L, {<<>>,0}).
+
+transformable10([H|T], {Acc,N}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% _ = bs_create_bin(append, _, A, _, _, _, X, _) { aliased => [X], unique => [A], first_fragment_dies => true }.
+ transformable10(T, {<<Acc/binary, H:8>>,N+1});
+transformable10([], Acc) ->
+ Acc.
+
+%% Check that the analysis works across clauses
+transformable11(L) ->
+ transformable11(L, <<>>).
+
+transformable11([H|T], Acc) when H =:= 0 ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(append, _, Arg1, ...) { unique => [Arg1], first_fragment_dies => true },
+%ssa% _ = call(fun transformable11/2, _, A),
+%ssa% B = bs_create_bin(append, _, Arg1, ...) { unique => [Arg1], first_fragment_dies => true },
+%ssa% _ = call(fun transformable11/2, _, B).
+ transformable11(T, <<Acc/binary, 0:8>>);
+transformable11([_|T], Acc)->
+ transformable11(T, <<Acc/binary, 1:8>>);
+transformable11([], Acc) ->
+ Acc.
+
+% Broken, type analysis can't handle the list
+transformable12a(L) ->
+ transformable12(L, {<<>>}).
+
+transformable12b(L) ->
+ transformable12(L, [<<>>]).
+
+%% The type analysis can't handle the list yet
+transformable12([H|T], {Acc}) ->
+%ssa% (_, _) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, A, _, _, _, B, _) { aliased => [B, A], first_fragment_dies => true },
+%ssa% _ = bs_create_bin(append, _, C, _, _, _, D, _) { aliased => [D, C], first_fragment_dies => true }.
+ transformable12([H|T], {<<Acc/binary,H:8>>});
+transformable12([H|T], [Acc]) ->
+ transformable12([H|T], [<<Acc/binary,H:8>>]);
+transformable12([], {Acc}) ->
+ Acc;
+transformable12([], [Acc]) ->
+ Acc.
+
+%% Check binaries coming from another function.
+transformable13(L) ->
+ transformable13(L, make_empty_binary()).
+
+transformable13([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [X], unique => [Arg1], first_fragment_dies => true }.
+ transformable13(T, <<Acc/binary, H:8>>);
+transformable13([], Acc) ->
+ Acc.
+
+make_empty_binary() ->
+ <<>>.
+
+%% Check binaries coming from other functions, with various levels of
+%% nesting in the literals and how they are picked apart using
+%% matching.
+transformable14(L) ->
+ {X} = make_wrapped_empty_binary(),
+ transformable14(L, X).
+
+transformable14([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [X], unique => [Arg1], first_fragment_dies => true }.
+ transformable14(T, <<Acc/binary, H:8>>);
+transformable14([], Acc) ->
+ Acc.
+
+make_wrapped_empty_binary() ->
+ {<<>>}.
+
+transformable15(L) ->
+ {X} = make_wrapped_empty_binary(),
+ Y = make_empty_binary(),
+ transformable15(L, X, Y).
+
+transformable15([A,B|T], Acc0, Acc1) ->
+%ssa% (_, Arg1, Arg2) when post_ssa_opt ->
+%ssa% A = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [X], unique => [Arg1], first_fragment_dies => true },
+%ssa% B = bs_create_bin(append, _, Arg2, _, _, _, Y, _) { aliased => [Y], unique => [Arg2], first_fragment_dies => true },
+%ssa% _ = call(fun transformable15/3, _, A, B).
+
+ transformable15(T, <<Acc0/binary, A:8>>, <<Acc1/binary, B:8>>);
+transformable15([], Acc0, Acc1) ->
+ {Acc0,Acc1}.
+
+transformable16(L) ->
+ X = make_wrapped_empty_binary(),
+ Y = make_empty_binary(),
+ transformable16(L, {X, Y}).
+
+transformable16([A,B|T], {{Acc0}, Acc1}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = get_tuple_element(A, 0),
+%ssa% C = bs_create_bin(append, _, B, _, _, _, X, _) { aliased => [X], unique => [B], first_fragment_dies => true },
+%ssa% D = get_tuple_element(Arg1, 1),
+%ssa% E = bs_create_bin(append, _, D, _, _, _, Y, _) { aliased => [Y], unique => [D], first_fragment_dies => true },
+%ssa% F = put_tuple(C),
+%ssa% G = put_tuple(F, E),
+%ssa% _ = call(fun transformable16/2, _, G).
+ transformable16(T, {{<<Acc0/binary, A:8>>}, <<Acc1/binary, B:8>>});
+transformable16([], {{Acc0}, Acc1}) ->
+ {Acc0,Acc1}.
+
+transformable17(L) ->
+ transformable17(L, [0|<<>>]).
+
+transformable17([H|T], [N|Acc]) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tl(Arg1),
+%ssa% B = bs_create_bin(append, _, A, _, _, _, X, _) { aliased => [X], unique => [A], first_fragment_dies => true },
+%ssa% C = put_list(_, B),
+%ssa% _ = call(fun transformable17/2, _, C).
+ transformable17(T, [N+1|<<Acc/binary, H:8>>]);
+transformable17([], Acc) ->
+ Acc.
+
+%% We should use type information to figure out that {<<>>, X} is not
+%% aliased, but as of now we don't have the information at this pass,
+%% nor do we track alias status at the sub-term level.
+transformable18(L, X) when is_integer(X), X < 256 ->
+ transformable18b(L, {<<>>, X}).
+
+transformable18b([H|T], {Acc,X}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(append, _, A, _, _, _, X, _) { aliased => [A], unique => [X], first_fragment_dies => true },
+%ssa% C = put_tuple(B, _),
+%ssa% _ = call(fun transformable18b/2, _, C).
+ transformable18b(T, {<<Acc/binary, (H+X):8>>, X});
+transformable18b([], {Acc,_}) ->
+ Acc.
+
+%% Check that the analysis works when the binary isn't embedded in a
+%% tuple literal.
+transformable19(L) ->
+ X = case ex:foo() of
+ true ->
+ 4711;
+ false ->
+ 17
+ end,
+ transformable19b(L, {<<>>, X}).
+
+transformable19b([H|T], {Acc,X}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+
+%ssa% B = bs_create_bin(append, _, A, _, _, _, X, _) { unique => [X, A], first_fragment_dies => true },
+%ssa% C = put_tuple(B, _),
+%ssa% _ = call(fun transformable19b/2, _, C).
+ transformable19b(T, {<<Acc/binary, (H+X):8>>, X});
+transformable19b([], {Acc,_}) ->
+ Acc.
+
+%% Check that the analysis works when the binary isn't embedded in a
+%% list literal.
+transformable20(L) ->
+ X = case ex:foo() of
+ true ->
+ 4711;
+ false ->
+ 17
+ end,
+ transformable20b(L, [<<>>|X]).
+
+transformable20b([H|T], [Acc|X]) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_hd(Arg1),
+%ssa% B = bs_create_bin(append, _, A, _, _, _, X, _) { unique => [X, A], first_fragment_dies => true },
+%ssa% C = put_list(B, _),
+%ssa% _ = call(fun transformable20b/2, _, C).
+ transformable20b(T, [<<Acc/binary, (H+X):8>>|X+1]);
+transformable20b([], [Acc|_]) ->
+ Acc.
+
+%% Check that the analysis works when the binary is embedded in a
+%% tuple literal returned from another function.
+transformable21(L) ->
+ transformable21(L, make_empty_binary_tuple()).
+
+transformable21([H|T], {AccA,AccB}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(append, _, A, _, _, _, X, _) { aliased => [X], unique => [A], first_fragment_dies => true },
+%ssa% C = get_tuple_element(Arg1, 1),
+%ssa% D = bs_create_bin(append, _, C, _, _, _, _, _) { unique => [C], first_fragment_dies => true },
+%ssa% E = put_tuple(B, D),
+%ssa% _ = call(fun transformable21/2, _, E).
+ transformable21(T, {<<AccA/binary, H:8>>,<<AccB/binary, 17:8>>});
+transformable21([], {AccA, AccB}) ->
+ {AccA, AccB}.
+
+make_empty_binary_tuple() ->
+ {<<>>, <<>>}.
+
+transformable22(L) ->
+ transformable22(L, [<<>>|<<>>]).
+
+transformable22([H|T], [AccA|AccB]) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_hd(Arg1),
+%ssa% B = get_tl(Arg1),
+%ssa% C = bs_create_bin(append, _, A, _, _, _, X, _) { aliased => [X], unique => [A], first_fragment_dies => true },
+%ssa% D = bs_create_bin(append, _, B, _, _, _, _, _) { unique => [B], first_fragment_dies => true },
+%ssa% E = put_list(C, D),
+%ssa% _ = call(fun transformable22/2, _, E).
+ transformable22(T, [<<AccA/binary, H:8>>|<<AccB/binary, 17:8>>]);
+transformable22([], Acc) ->
+ Acc.
+
+%% As transformable21 but with a more complex embedded tuple
+transformable23(L) ->
+ transformable23(L, make_empty_binary_tuple_nested()).
+
+transformable23([H|T], {AccA,{AccB},X}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(append, _, A, _, _, _, X, _) { aliased => [X], unique => [A], first_fragment_dies => true },
+%ssa% C = get_tuple_element(Arg1, 1),
+%ssa% D = get_tuple_element(C, 0),
+%ssa% E = bs_create_bin(append, _, D, _, _, _, _, _) { unique => [D], first_fragment_dies => true },
+%ssa% F = put_tuple(E),
+%ssa% G = put_tuple(B, F, _),
+%ssa% _ = call(fun transformable23/2, _, G).
+ transformable23(T, {<<AccA/binary, H:8>>,{<<AccB/binary, 17:8>>}, X});
+transformable23([], {AccA, AccB, X}) ->
+ {AccA, AccB, X}.
+
+make_empty_binary_tuple_nested() ->
+ {<<>>, {<<>>}, 47}.
+
+transformable24(L) ->
+ transformable24(L, {<<>>, ex:foo()}).
+
+transformable24([H|T], {Acc,X}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(append, _, A, _, _, _, X, _) { aliased => [A], unique => [X], first_fragment_dies => true },
+%ssa% C = put_tuple(B, _),
+%ssa% _ = call(fun transformable24/2, _, C).
+ transformable24(T, {<<Acc/binary, (H+X):8>>, X});
+transformable24([], {Acc,_}) ->
+ Acc.
+
+%% Check that the update of more than one element of a tuple is
+%% handled.
+transformable25(L) ->
+ transformable25(L, {<<>>,<<>>}).
+
+transformable25([H|T], {AccA,AccB}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(append, _, A, _, _, _, X, _) { aliased => [X], unique => [A], first_fragment_dies => true },
+%ssa% C = get_tuple_element(Arg1, 1),
+%ssa% D = bs_create_bin(append, _, C, _) { unique => [C], first_fragment_dies => true },
+%ssa% E = put_tuple(B, D),
+%ssa% _ = call(fun transformable25/2, _, E).
+ transformable25(T, {<<AccA/binary, H:8>>,<<AccB/binary>>});
+transformable25([], Acc) ->
+ Acc.
+
+%% Check that the update of more than two elements of a tuple is
+%% handled (check the that inductive step of
+%% beam_ssa_alias_opt:merge_arg_patches/1 works).
+transformable26(L) ->
+ transformable26(L, {<<>>,<<>>,<<>>}).
+
+transformable26([H|T], {AccA,AccB,AccC}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(append, _, A, _, _, _, X, _) { aliased => [X], unique => [A], first_fragment_dies => true },
+%ssa% C = get_tuple_element(Arg1, 1),
+%ssa% D = bs_create_bin(append, _, C, _, _, _, Y, _) { aliased => [Y], unique => [C], first_fragment_dies => true },
+%ssa% E = get_tuple_element(Arg1, 2),
+%ssa% F = bs_create_bin(append, _, E, _, _, _, Z, _) { aliased => [Z], unique => [E], first_fragment_dies => true },
+%ssa% G = put_tuple(B, D, F),
+%ssa% _ = call(fun transformable26/2, _, G).
+ transformable26(T, {<<AccA/binary, H:8>>,
+ <<AccB/binary, H:8>>,
+ <<AccC/binary, H:8>>});
+transformable26([], Acc) ->
+ Acc.
+
+%%
+%% Check that we detect aliasing
+%%
+
+not_transformable1([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [Arg1, X], first_fragment_dies => true }.
+ not_transformable1(T, <<Acc/binary, H:8>>);
+not_transformable1([], Acc) ->
+ Acc.
+
+not_transformable2(L) ->
+ not_transformable2(L, <<>>).
+
+not_transformable2([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [Arg1, X], first_fragment_dies => true }.
+ ex:escape(Acc),
+ not_transformable2(T, <<Acc/binary, H:8>>);
+not_transformable2([], Acc) ->
+ Acc.
+
+not_transformable3(L) ->
+ not_transformable3(L, <<>>, []).
+
+not_transformable3([H|T], Acc, Ls) ->
+%ssa% (_, Arg1, _) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, X, _) { aliased => [Arg1, X], first_fragment_dies => false }.
+ not_transformable3(T, <<Acc/binary, H:8>>, [Acc|Ls]);
+not_transformable3([], Acc, Ls) ->
+ {Acc, Ls}.
+
+%% We randomly keep multiple references to the binary, so we should
+%% detect aliasing.
+not_transformable4(L) ->
+ not_transformable4(L, [<<>>|[]]).
+
+not_transformable4([H|T], X=[Acc|Ls]) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, X, _, _, _, Y, _) { aliased => [Y, X], first_fragment_dies => true }.
+ Tmp = case ex:f() of
+ true ->
+ [Q|_] = X,
+ Q;
+ false ->
+ ok
+ end,
+ T1 = [Tmp|Ls],
+ not_transformable4(T, [<<Acc/binary, H:8>>|T1]);
+not_transformable4([], Acc) ->
+ Acc.
+
+%% Check that the leak in the external call is detected despite the
+%% mutual recursion.
+not_transformable5(L) ->
+ not_transformable5a(L, <<>>).
+
+not_transformable5a([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, _, _, _, _, X, _) { aliased => [Arg1, X], first_fragment_dies => true }.
+ not_transformable5b(T, <<Acc/binary, 0:8, H:8>>);
+not_transformable5a([], Acc) ->
+ Acc.
+
+not_transformable5b([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, _, _, _, _, _, _, _, X, _) { aliased => [Arg1, X], first_fragment_dies => true }.
+ ex:alias(Acc),
+ not_transformable5a(T, <<Acc/binary, 1:8, H:8>>);
+not_transformable5b([], Acc) ->
+ Acc.
+
+%% Reproducer for a bug in beam_ssa_alias:aa_get_status_by_type/2
+%% where it would return the wrong alias/uniqe status for certain
+%% combinations of returned types.
+bad_get_status_by_type() ->
+%ssa% () when post_ssa_opt ->
+%ssa% A = call(fun bad_get_status_by_type_1/0),
+%ssa% ret(A) { aliased => [A] }.
+ bad_get_status_by_type_1().
+
+bad_get_status_by_type_1() ->
+ case e:foo() of
+ a -> <<0:1>>;
+ b -> X = e:bar(),
+ true = is_binary(X),
+ X
+ end.
+
+stacktrace0() ->
+%ssa% () when post_ssa_opt ->
+%ssa% X = call(fun transformable0/2, ...),
+%ssa% ret(A) { unique => [A] },
+%ssa% ret(A) { aliased => [A] },
+%ssa% ret(_) { result_type => none }.
+ X = transformable0(e:foo(), <<>>),
+ try
+ e:code_that_fails(),
+ X
+ catch
+ _:_:Stacktrace ->
+ e:foo(Stacktrace),
+ X
+ end.
+
+stacktrace1() ->
+%ssa% () when post_ssa_opt ->
+%ssa% X = call(fun transformable0/2, ...),
+%ssa% ret(A) { unique => [A] },
+%ssa% ret('bad'),
+%ssa% ret(_) { result_type => none }.
+ try
+ X = transformable0(e:foo(), <<>>),
+ e:code_that_fails(),
+ X
+ catch
+ _:_:Stacktrace ->
+ e:foo(Stacktrace),
+ bad
+ end.
+
+in_cons() ->
+%ssa% () when post_ssa_opt ->
+%ssa% A = call(fun in_cons_inner/1, ...),
+%ssa% ret(A) { unique => [A] },
+%ssa% ret(_) { result_type => none }.
+ in_cons_inner([x|<<>>]).
+
+in_cons_inner([x|B]) ->
+ [x|<<B/binary,1:8>>].
+
+%% The alias analysis did not consider values copied into the
+%% environment which in turn led to unsafe private appends and
+%% segfaults, GH-6890.
+make_fun() ->
+ make_fun([<<"hello">>], <<>>).
+
+make_fun(List, Indent) ->
+ lists:map(fun (X) -> make_fun1(X, Indent) end, List).
+
+make_fun1(X, Indent) ->
+%ssa% (_, A) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, A, ...) { aliased => [A], first_fragment_dies => true }.
+ make_fun(X, <<Indent/binary," ">>).
+
+
+%% Check that the alias analysis detects that the first fragment to
+%% doesn't die with the second bs_create_bin, GH-6925.
+gh6925() ->
+%ssa% () when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, ...),
+%ssa% B = bs_create_bin(append, ...) { first_fragment_dies => false }.
+ A = << <<"x">> || true >>,
+ B = <<A/binary, "z">>,
+ {A, B}.
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/alias_chain.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/alias_chain.erl
new file mode 100644
index 0000000000..7d46176fb9
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/alias_chain.erl
@@ -0,0 +1,117 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% This module tests that beam_ssa_alias_opt:opt/2 does not annotate
+%% instructions with information about aliased operands when more than
+%% MAX_REPETITIONS are required for the analysis to converge.
+%%
+%% A specially crafted module which is designed to hit the alias
+%% analysis iteration limit if functions are visited naïvely in the
+%% order they occur in the module.
+%%
+-compile(no_ssa_opt_private_append).
+
+-module(alias_chain).
+
+-export([top/1]).
+
+top(L) ->
+%ssa% (A) when post_ssa_opt ->
+%ssa% _ = call(...) { aliased => [A] }.
+ fF(L, <<>>).
+
+fF([H|T], AccA) ->
+ fE(T, <<AccA/binary, H:8>>);
+fF([], Acc) ->
+ Acc.
+
+fE([H|T], AccA) ->
+ fD(T, <<AccA/binary, H:8>>);
+fE([], Acc) ->
+ Acc.
+
+fD([H|T], AccA) ->
+ fC(T, <<AccA/binary, H:8>>);
+fD([], Acc) ->
+ Acc.
+
+fC([H|T], AccA) ->
+ fB(T, <<AccA/binary, H:8>>);
+fC([], Acc) ->
+ Acc.
+
+fB([H|T], AccA) ->
+ fA(T, <<AccA/binary, H:8>>);
+fB([], Acc) ->
+ Acc.
+
+fA([H|T], AccA) ->
+ f9(T, <<AccA/binary, H:8>>);
+fA([], Acc) ->
+ Acc.
+
+f9([H|T], AccA) ->
+ f8(T, <<AccA/binary, H:8>>);
+f9([], Acc) ->
+ Acc.
+
+f8([H|T], AccA) ->
+ f7(T, <<AccA/binary, H:8>>);
+f8([], Acc) ->
+ Acc.
+
+f7([H|T], AccA) ->
+ f6(T, <<AccA/binary, H:8>>);
+f7([], Acc) ->
+ Acc.
+
+f6([H|T], AccA) ->
+ f5(T, <<AccA/binary, H:8>>);
+f6([], Acc) ->
+ Acc.
+
+f5([H|T], AccA) ->
+ f4(T, <<AccA/binary, H:8>>);
+f5([], Acc) ->
+ Acc.
+
+f4([H|T], AccA) ->
+ f3(T, <<AccA/binary, H:8>>);
+f4([], Acc) ->
+ Acc.
+
+f3([H|T], AccA) ->
+ f2(T, <<AccA/binary, H:8>>);
+f3([], Acc) ->
+ Acc.
+
+f2([H|T], AccA) ->
+ f1(T, <<AccA/binary, H:8>>);
+f2([], Acc) ->
+ Acc.
+
+f1([H|T], AccA) ->
+ f0(T, <<AccA/binary, H:8>>);
+f1([], Acc) ->
+ Acc.
+
+f0([H|T], AccA) ->
+ f3(T, <<AccA/binary, H:8>>);
+f0([], Acc) ->
+ Acc.
+
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/alias_non_convergence.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/alias_non_convergence.erl
new file mode 100644
index 0000000000..c127827a77
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/alias_non_convergence.erl
@@ -0,0 +1,40 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% This module tests that beam_ssa_alias_opt:opt/2 does not annotate
+%% instructions with information about aliased operands when more than
+%% MAX_REPETITIONS are required for the analysis to converge.
+%%
+
+-compile(no_ssa_opt_private_append).
+
+-module(alias_non_convergence).
+
+-export([a/1]).
+
+%% a/17 is designed to trigger the iteration limit for alias analysis,
+%% check that no alias information is generated.
+a(X) ->
+%ssa% fail (A) when post_ssa_opt ->
+%ssa% _ = call(...) { aliased => [A] }.
+ a(X, a, b, c, d, e, f, g, h, i, j, k, l, m, o, p, q).
+
+a([X|Xs], A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, _) ->
+ a(Xs, X, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
+a([], A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P) ->
+ {A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P}.
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/annotations.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/annotations.erl
new file mode 100644
index 0000000000..5e938c2588
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/annotations.erl
@@ -0,0 +1,40 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+-module(annotations).
+
+-export([t0/0]).
+
+%% Check annotations, do not add lines before this function without
+%% changing the location annotation.
+t0() ->
+%ssa% () when post_ssa_opt ->
+%ssa% _ = call(fun return_int/0) { result_type => {t_integer,{17,17}},
+%ssa% location => {_,32} },
+%ssa% _ = call(fun return_tuple/0) {
+%ssa% result_type => {t_tuple,2,true,#{1 => {t_integer,{1,1}},
+%ssa% 2 => {t_integer,{2,2}}}}
+%ssa% }.
+ X = return_int(),
+ Y = return_tuple(),
+ {X, Y}.
+
+return_int() ->
+ 17.
+
+return_tuple() ->
+ {1,2}.
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/appendable.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/appendable.erl
new file mode 100644
index 0000000000..7063debd1b
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/appendable.erl
@@ -0,0 +1,162 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+-module(appendable).
+-export([make_empty/0, t0/0, t1/0, t2/0, t3/0, t4/0,
+ t5/0, t6/0, t7/0, t8/1, t9/1, t10/1, t11/1, t12/0]).
+
+%% Check that just returning an empty bitstring is considered
+%% appendable.
+t0() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(<<>>) { result_type => {t_bitstring,_,true} }.
+ A = <<>>,
+ A.
+
+%% Check that appending an unknown bitstring to a literal <<>> results
+%% in an appendable bitstring.
+t1() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,true} }.
+ A = <<>>,
+ B = ex:f(),
+ <<A/binary, B/binary>>.
+
+%% Check that appending an integer to a literal <<>> results in an
+%% appendable bitstring.
+t2() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,true} }.
+ A = <<>>,
+ B = ex:f(),
+ <<A/binary, B:32>>.
+
+%% Check that just returning an empty bitstring is considered
+%% appendable.
+t3() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,true} }.
+ A = make_empty(),
+ A.
+
+%% Check that appending an unknown bitstring to a value known to be
+%% <<>> results in an appendable bitstring.
+t4() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,true} }.
+ A = make_empty(),
+ B = ex:f(),
+ <<A/binary, B/binary>>.
+
+%% Check that appending an integer to a value known results in an
+%% appendable bitstring.
+t5() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,true} }.
+ A = make_empty(),
+ B = ex:f(),
+ <<A/binary, B:32>>.
+
+%% Check that the appendable flag is cleared when we don't append
+%% things to the bitstring accumulator.
+t6() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,false} }.
+ A = <<>>,
+ B = ex:f(),
+ <<B/binary, A/binary>>.
+
+%% Check that the appendable flag is cleared when we don't append
+%% things to the bitstring accumulator.
+t7() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,false} }.
+ A = ex:f(),
+ <<17:8, A/binary>>.
+
+%% More complicated recursive check for setting the appendable flag
+t8(Ls) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,true} }.
+ t8(<<>>, Ls).
+
+t8(Acc, [H|T]) ->
+ t8(<<Acc/binary, H:32>>, T);
+t8(Acc, []) ->
+ Acc.
+
+%% More complicated recursive check for when the appendable flag
+%% should not be set.
+t9(Ls) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,false} }.
+ t9(<<>>, Ls).
+
+t9(Acc, [H|T]) ->
+ t9(<<H:32, Acc/binary>>, T);
+t9(Acc, []) ->
+ Acc.
+
+%% More complicated recursive check for when the appendable flag
+%% should be set.
+t10(Ls) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,true} }.
+ t10(<<>>, Ls).
+
+t10(Acc, [H|T]) ->
+ case H rem 2 of
+ 0 ->
+ t10(<<Acc/binary, H:16>>, T);
+ 1 ->
+ t10(<<Acc/binary, H:32>>, T)
+ end;
+t10(Acc, []) ->
+ Acc.
+
+%% More complicated recursive check for when the appendable flag
+%% should not be set.
+t11(Ls) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_bitstring,_,false} }.
+ t11(<<>>, Ls).
+
+t11(Acc, [H|T]) ->
+ case H rem 2 of
+ 0 ->
+ t11(<<H:32, Acc/binary>>, T);
+ 1 ->
+ t11(<<Acc/binary, H:32>>, T)
+ end;
+t11(Acc, []) ->
+ Acc.
+
+make_empty() ->
+ <<>>.
+
+%% beam_ssa_type:type/5 had problems handling bs_create_bin when the
+%% first fragment was typed as a #t_union{} containing an appendable
+%% bit string. Check that we don't lose append=true.
+t12() ->
+%ssa% () when post_ssa_opt ->
+%ssa% A = call(fun t12_inner/1, ...),
+%ssa% ret(A) { result_type => {t_cons,_,{t_bitstring,8,true}} },
+%ssa% ret(_) { result_type => none }.
+ t12_inner([x|<<>>]).
+
+t12_inner([x|B]) ->
+ [x|<<B/binary,1:8>>].
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/gen_bs_size_unit_checks.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/gen_bs_size_unit_checks.erl
new file mode 100644
index 0000000000..74a872daec
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/gen_bs_size_unit_checks.erl
@@ -0,0 +1,242 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(gen_bs_size_unit_checks).
+
+-export([generate/2]).
+
+-import(lists, [reverse/1, seq/2, uniq/1, unzip/1, zip/2]).
+-import(string, [join/2]).
+
+generate(File, _Config) ->
+ InitialSizes = seq(0, 4) ++ [15, 16, 17, 31, 32, 33, 63, 64, 65,
+ 127, 128, 129, 255, 256, 257],
+ AddedSizes = seq(0, 4) ++ [15, 16 ,17, 31, 32, 33, 63, 64, 65,
+ 127, 128, 129, 255, 256, 257],
+ NoofElements = seq(1, 5),
+
+ _ = rand:seed(exro928ss, {123, 123534, 345345}),
+
+ Cases1 = [{Initial,Increment,Elements,FixedSize} ||
+ Initial <- InitialSizes,
+ Increment <- AddedSizes,
+ Elements <- NoofElements,
+ FixedSize <- [fixed, variable, mixed],
+ Elements =< Increment,
+ Increment > 0],
+ Cases2 = uniq(Cases1),
+ Cases = zip(seq(1, length(Cases2)), Cases2),
+
+ Out = [format_case(["bsu_test_", integer_to_list(Id)],
+ Initial, Increment, Elements, FixedSize)
+ || {Id, {Initial,Increment,Elements,FixedSize}} <- Cases],
+
+ Exports = [["-export([", Export, "]).\n"] || {Export, _} <- Out],
+ Bodies = [Body || {_, Body} <- Out],
+ Module = ["-module(bsu_tests).\n",
+ "\n",
+ Exports,
+ "\n",
+ Bodies
+ ],
+ ok = file:write_file(File, Module).
+
+
+format_case(Name, InitialSize, AddedSize, NoofElements, fixed) ->
+ Expected = gcd(gcd(InitialSize, 256), AddedSize),
+ Es = make_elements(AddedSize, NoofElements, 0),
+ {Vars, Elements} = unzip(Es),
+ Appendable = appendable(InitialSize),
+ Comment = [io_lib:format("%% Adding ~p bits to initial accumulator"
+ " of ~p bits using ~p elements.~n",
+ [AddedSize, InitialSize, NoofElements]),
+ io_lib:format("%% Expected unit size is ~p~n", [Expected])],
+ {[Name, "/1"],
+ body(Comment, Name, Vars, Expected, InitialSize, Elements, Appendable)};
+format_case(Name, InitialSize, _, NoofElements, variable) ->
+ InitialUnit = gcd(InitialSize, 256),
+ {Vars, Elements, Unit} =
+ make_varsize_elements(NoofElements, 0, InitialUnit),
+ Appendable = appendable(InitialSize),
+ Comment = [io_lib:format("%% Adding a variable number of bits to initial"
+ " accumulator of ~p bits using~n%% both fixed size"
+ " and variable sized elements.~n",
+ [InitialSize]),
+ io_lib:format("%% Expected unit size is ~p~n", [Unit])],
+ {[Name, "/1"],
+ body(Comment, Name, Vars, Unit, InitialSize, Elements, Appendable)};
+format_case(Name, InitialSize, AddedSize, NoofElements, mixed) ->
+ InitialUnit = gcd(InitialSize, 256),
+ Es = make_elements(AddedSize, NoofElements, 0),
+ {FixedVars, FixedElements} = unzip(Es),
+ {VaribleVars, VariableElements, Unit1} =
+ make_varsize_elements(NoofElements, NoofElements, InitialUnit),
+ Unit = gcd(gcd(InitialUnit, AddedSize), Unit1),
+ Elements = shuffle(FixedElements ++ VariableElements),
+ Appendable = appendable(InitialSize),
+ Vars = VaribleVars ++ FixedVars,
+ Comment = [io_lib:format("%% Adding a variable number of bits to initial"
+ " accumulator of ~p bits~n%% using ~p elements.~n",
+ [InitialSize, NoofElements]),
+ io_lib:format("%% Expected unit size is ~p~n", [Unit])],
+ {[Name, "/1"],
+ body(Comment, Name, Vars, Unit, InitialSize, Elements, Appendable)}.
+
+body(Comment, Name, Vars, Unit, InitialSize, Elements, Appendable) ->
+ [Comment,
+ Name, "(Xs) ->\n",
+ "%%ssa% (_) when post_ssa_opt ->\n",
+ "%%ssa% _ = call(fun ", Name, "/2, _, A)"
+ " { result_type => {t_bitstring,",
+ integer_to_list(Unit), Appendable, "} }.\n",
+ " ", Name, "(Xs, ", "<<0:", integer_to_list(InitialSize), ">>", ").\n\n",
+ Name, "([_", Vars, "|Xs], Acc) ->\n",
+ " ", Name, "(Xs, ", "<<Acc/bitstring", Elements, ">>", ");\n",
+ Name, "([], Acc) ->\n",
+ " Acc.\n\n"].
+
+make_elements(0, 0, _) ->
+ [];
+make_elements(Size, 1, Id) ->
+ [format_element_of_size(Size, Id)];
+make_elements(Size, NoofElements, Id) when Size =:= NoofElements ->
+ [format_element_of_size(1, Id)|
+ make_elements(Size - 1, NoofElements - 1, Id + 1)];
+make_elements(Size, NoofElements, Id) when Size > NoofElements ->
+ MaxSize = Size - NoofElements,
+ ElemSize = rand:uniform(MaxSize),
+ [format_element_of_size(ElemSize, Id)
+ |make_elements(Size - ElemSize, NoofElements - 1, Id + 1)].
+
+appendable(InitialSize) ->
+ case InitialSize of
+ 0 -> ", true";
+ _ -> ", false"
+ end.
+
+format_element_of_size(Size, Id) ->
+ case rand:uniform(2) of
+ 1 ->
+ format_bitstring_element(Size, Id);
+ 2 ->
+ format_integer_element(Size, Id)
+ end.
+
+format_integer_element(Size, Id) ->
+ case rand:uniform(2) of
+ 1 ->
+ literal_integer_element(Size);
+ 2 ->
+ variable_integer_element(Size, Id)
+ end.
+
+variable_integer_element(Size=32, Id) ->
+ Unit = make_unit(Size),
+ E = case rand:uniform(2) of
+ 1 ->
+ io_lib:format(", V~p:~p~s", [Id, Size, Unit]);
+ 2 ->
+ io_lib:format(", V~p/utf32", [Id])
+ end,
+ V = io_lib:format(", V~p", [Id]),
+ {V, E};
+variable_integer_element(Size, Id) ->
+ Unit = make_unit(Size),
+ E = io_lib:format(", V~p:~p~s", [Id, Size, Unit]),
+ V = io_lib:format(", V~p", [Id]),
+ {V, E}.
+
+literal_integer_element(Size) ->
+ %% We're not doing utf32 here as it is too hard to generate a
+ %% valid code point.
+ V = rand:uniform(1 bsl Size) - 1,
+ Unit = make_unit(Size),
+ E = io_lib:format(", ~p:~p~s", [V, Size, Unit]),
+ {[], E}.
+
+format_bitstring_element(Size, Id) ->
+ case rand:uniform(2) of
+ 1 ->
+ literal_bitstring_element(Size);
+ 2 ->
+ variable_bitstring_element(Size, Id)
+ end.
+
+variable_bitstring_element(Size, Id) ->
+ Unit = make_unit(Size),
+ E = io_lib:format(", V~p:~p/bitstring~s", [Id, Size, Unit]),
+ V = io_lib:format(", V~p", [Id]),
+ {V, E}.
+
+literal_bitstring_element(Size) ->
+ V = rand:uniform(1 bsl Size) - 1,
+ Unit = make_unit(Size),
+ E = io_lib:format(", <<~p:~p>>/bitstring~s", [V, Size, Unit]),
+ {[], E}.
+
+
+make_unit(Size) ->
+ case rand:uniform(1) of
+ 1 ->
+ [];
+ 2 ->
+ io_lib:format("-unit:~p", [Size])
+ end.
+
+make_varsize_elements(NoofElements, Id, Unit0) ->
+ make_varsize_elements(NoofElements, Id, Unit0, [], []).
+
+make_varsize_elements(0, _, Unit, Vars, Elements) ->
+ {reverse(Vars), reverse(Elements), Unit};
+make_varsize_elements(NoofElements, Id, Unit0, Vars, Elements) ->
+ Var = io_lib:format(", V~p", [Id]),
+ Es = #{ 1 => {binary, 8},
+ 2 => {bitstring, 1},
+ 3 => {utf8, 8},
+ 4 => {utf16, 16} },
+
+ {T,U} = map_get(rand:uniform(4), Es),
+ Unit = gcd(U, Unit0),
+ Element = io_lib:format(", V~p/~p", [Id, T]),
+ make_varsize_elements(NoofElements - 1, Id + 1, Unit,
+ [Var|Vars], [Element|Elements]).
+
+gcd(0, Other) -> Other;
+gcd(Other, 0) -> Other;
+gcd(A, B) ->
+ case A rem B of
+ 0 -> B;
+ X -> gcd(B, X)
+ end.
+
+shuffle(Ls) ->
+ %% à la Knuth
+ A = array:from_list(Ls),
+ shuffle(A, array:size(A)).
+
+shuffle(A, 0) ->
+ array:to_list(A);
+shuffle(A0, Size) ->
+ A = swap(rand:uniform(Size) - 1, Size - 1, A0),
+ shuffle(A, Size - 1).
+
+swap(X, Y, A) ->
+ Xv = array:get(X, A),
+ Yv = array:get(Y, A),
+ array:set(X, Yv, array:set(Y, Xv, A)).
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/private_append.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/private_append.erl
new file mode 100644
index 0000000000..c1edc54460
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/private_append.erl
@@ -0,0 +1,1004 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% This module tests that beam_ssa_alias_opt:to_private_append/3
+%% rewrites plain appends in bs_create_bin to private_append when
+%% appropriate.
+%%
+-module(private_append).
+
+-feature(maybe_expr, enable).
+
+-export([transformable0/1,
+ transformable1/1,
+ transformable1b/1,
+ transformable2/1,
+ transformable3/1,
+ transformable4/1,
+ transformable5/1,
+ %% transformable6/1,
+ transformable7/1,
+ transformable8/1,
+ transformable9/1,
+ transformable10/1,
+ transformable11/1,
+ transformable12a/1,
+ transformable12b/1,
+ transformable13/1,
+ transformable14/1,
+ transformable15/1,
+ transformable16/1,
+ transformable18/2,
+ transformable19/1,
+ transformable20/1,
+ transformable21/1,
+ transformable22/1,
+ transformable23/1,
+ transformable24/1,
+ transformable25/1,
+ transformable26/1,
+ transformable27/1,
+ transformable28/1,
+ transformable29/1,
+ transformable30/1,
+ transformable31a/1,
+ transformable31b/1,
+ transformable32/0,
+ transformable32/1,
+ transformable33/0,
+
+ not_transformable1/2,
+ not_transformable2/1,
+ not_transformable3/1,
+ not_transformable4/1,
+ not_transformable5/1,
+ not_transformable6/1,
+ not_transformable7/1,
+ not_transformable8/1,
+ not_transformable9/1,
+ not_transformable10/1,
+ not_transformable11/0,
+ not_transformable12/1,
+ not_transformable13/1,
+ not_transformable14/0,
+ not_transformable15/2,
+
+ id/1,
+
+ bs_create_bin_on_literal/0]).
+
+%% Trivial smoke test
+transformable0(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable0/2, _, A).
+ transformable0(L, <<>>).
+
+transformable0([H|T], Acc) ->
+%ssa% (_, Acc) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(private_append, _, Acc, ...).
+ transformable0(T, <<Acc/binary, H:8>>);
+transformable0([], Acc) ->
+ Acc.
+
+%% Check that the transform works when the binary is produced by a
+%% non-trivial constructions. (transformable{1,2,3,4})
+transformable1(L) ->
+ transformable1(L, start).
+
+transformable1(L, start) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable1/2, _, A),
+%ssa% _ = bs_create_bin(private_append, _, Arg1, ...).
+ transformable1(L, <<>>);
+transformable1([H|T], Acc) ->
+ transformable1(T, <<Acc/binary, H:8>>);
+transformable1([], Acc) ->
+ Acc.
+
+transformable1b(L) ->
+ transformable1b(L, start).
+
+transformable1b([H|T], X) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% Phi = phi({Arg1, _}, {A, _}, ...),
+%ssa% _ = bs_create_bin(private_append, _, Phi, ...).
+ Acc = case X of
+ start ->
+ <<>>;
+ _ ->
+ X
+ end,
+ N = <<Acc/binary, H:8>>,
+ transformable1b(T, N);
+transformable1b([], Acc) ->
+ Acc.
+
+transformable2(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable2/2, _, A).
+ transformable2(L, <<>>).
+
+transformable2([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% _ = call(fun transformable2/2, _, A),
+%ssa% B = bs_init_writable(_),
+%ssa% _ = call(fun transformable2/2, _, B).
+ case ex:f() of
+ true ->
+ transformable2(T, <<Acc/binary, H:8>>);
+ false ->
+ transformable2(T, <<>>)
+ end;
+transformable2([], Acc) ->
+ Acc.
+
+transformable3(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable3/2, _, A).
+ transformable3(L, <<>>).
+
+transformable3([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% B = bs_init_writable(_),
+%ssa% Phi = phi({A, _}, {B, _}, ...),
+%ssa% _ = call(fun transformable3/2, _, Phi).
+ R = case ex:f() of
+ true ->
+ <<Acc/binary, H:8>>;
+ false ->
+ <<>>
+ end,
+ transformable3(T, R);
+transformable3([], Acc) ->
+ Acc.
+
+transformable4(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable4/2, _, A).
+ transformable4(L, <<>>).
+
+transformable4([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% Phi = phi({A, _}, {Arg1, _}, ...),
+%ssa% B = bs_create_bin(private_append, _, Phi, ...),
+%ssa% _ = call(fun transformable4/2, _, B).
+ R = case ex:f() of
+ true ->
+ Acc;
+ false ->
+ <<>>
+ end,
+ transformable4(T, <<R/binary, H:8>>);
+transformable4([], Acc) ->
+ Acc.
+
+%% Check that the alias analysis handles local functions.
+transformable5(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable5/2, _, A).
+ transformable5(L, <<>>).
+
+transformable5([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% _ = call(fun transformable5/2, _, A).
+ does_not_escape(Acc),
+ transformable5(T, <<Acc/binary, H:8>>);
+transformable5([], Acc) ->
+ Acc.
+
+does_not_escape(_) ->
+ ok.
+
+%% Check that the transform works when we have an appendable binary in
+%% the head of a cons.
+transformable7(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = put_list(A, _),
+%ssa% _ = call(fun transformable7/2, _, B).
+ transformable7(L, [<<>>|0]).
+
+transformable7([H|T], [Acc|N]) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_hd(Arg1),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = put_list(B, _),
+%ssa% _ = call(fun transformable7/2, _, C).
+ transformable7(T, [<<Acc/binary, H:8>>|N+1]);
+transformable7([], Acc) ->
+ Acc.
+
+%% Check that the transform works when we have an appendable binary in
+%% just one of the clauses.
+transformable8(L) ->
+ transformable8(L, start).
+
+transformable8(L, start) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% B = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% _ = call(fun transformable8b/2, _, B).
+ transformable8(L, <<>>);
+transformable8([H|T], Acc) ->
+ transformable8b(T, <<Acc/binary, H:8>>);
+transformable8([], Acc) ->
+ Acc.
+
+transformable8b(T, Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% _ = call(fun transformable8/2, _, B).
+ transformable8(T, <<Acc/binary, 16#ff:8>>).
+
+%% Check that the transform works across mutually recursive functions.
+transformable9(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable9a/2, _, A).
+ transformable9a(L, <<>>).
+
+transformable9a([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% _ = call(fun transformable9b/2, _, B).
+ transformable9b(T, <<Acc/binary, 0:8, H:8>>);
+transformable9a([], Acc) ->
+ Acc.
+
+transformable9b([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% _ = call(fun transformable9a/2, _, B).
+ transformable9a(T, <<Acc/binary, 1:8, H:8>>);
+transformable9b([], Acc) ->
+ Acc.
+
+%% Check that the transform works for binaries embedded in a literal.
+transformable10(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = put_tuple(A, ...),
+%ssa% _ = call(fun transformable10/2, _, B).
+ transformable10(L, {<<>>,0}).
+
+transformable10([H|T], {Acc,N}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = put_tuple(B, _),
+%ssa% _ = call(fun transformable10/2, _, C).
+ transformable10(T, {<<Acc/binary, H:8>>,N+1});
+transformable10([], Acc) ->
+ Acc.
+
+%% Check that the transform works across clauses
+transformable11(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable11/2, _, A).
+ transformable11(L, <<>>).
+
+transformable11([H|T], Acc) when H =:= 0 ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% _ = call(fun transformable11/2, _, A),
+%ssa% B = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% _ = call(fun transformable11/2, _, B).
+ transformable11(T, <<Acc/binary, 0:8>>);
+transformable11([_|T], Acc)->
+ transformable11(T, <<Acc/binary, 1:8>>);
+transformable11([], Acc) ->
+ Acc.
+
+% Broken, type analysis can't handle the list
+transformable12a(L) ->
+%ssa% xfail (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = put_tuple(A),
+%ssa% _ = call(fun transformable12/2, _, B).
+ transformable12(L, {<<>>}).
+
+transformable12b(L) ->
+%ssa% xfail (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = put_list(A, '_'),
+%ssa% _ = call(fun transformable12/2, _, B).
+ transformable12(L, [<<>>]).
+
+%% The type analysis can't handle the list yet
+transformable12([H|T], {Acc}) ->
+%ssa% xfail (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_hd(Arg1),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = put_list(B, _),
+%ssa% _ = call(fun transformable12/2, _, C),
+%ssa% D = get_tuple_element(Arg1, 0),
+%ssa% E = bs_create_bin(private_append, _, D, ...),
+%ssa% F = put_tuple('E'),
+%ssa% _ = call(fun transformable12/2, _, F).
+ transformable12([H|T], {<<Acc/binary,H:8>>});
+transformable12([H|T], [Acc]) ->
+ transformable12([H|T], [<<Acc/binary,H:8>>]);
+transformable12([], {Acc}) ->
+ Acc;
+transformable12([], [Acc]) ->
+ Acc.
+
+%% Check binaries coming from another function.
+transformable13(L) ->
+%ssa% (Arg0) when post_ssa_opt ->
+%ssa% A = call(fun make_empty_binary/0),
+%ssa% _ = call(fun transformable13/2, _, A).
+ transformable13(L, make_empty_binary()).
+
+transformable13([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% _ = call(fun transformable13/2, _, A).
+ transformable13(T, <<Acc/binary, H:8>>);
+transformable13([], Acc) ->
+ Acc.
+
+make_empty_binary() ->
+%ssa% () when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% ret(A).
+ <<>>.
+
+%% Check binaries coming from other functions, with various levels of
+%% nesting in the literals and how they are picked apart using
+%% matching.
+transformable14(L) ->
+%ssa% (Arg0) when post_ssa_opt ->
+%ssa% A = call(fun make_wrapped_empty_binary/0),
+%ssa% B = get_tuple_element(A, 0),
+%ssa% _ = call(fun transformable14/2, _, B).
+ {X} = make_wrapped_empty_binary(),
+ transformable14(L, X).
+
+transformable14([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% _ = call(fun transformable14/2, _, A).
+ transformable14(T, <<Acc/binary, H:8>>);
+transformable14([], Acc) ->
+ Acc.
+
+make_wrapped_empty_binary() ->
+%ssa% () when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = put_tuple(A),
+%ssa% ret(B).
+ {<<>>}.
+
+transformable15(L) ->
+%ssa% (Arg0) when post_ssa_opt ->
+%ssa% A = call(fun make_wrapped_empty_binary/0),
+%ssa% B = call(fun make_empty_binary/0),
+%ssa% C = get_tuple_element(A, 0),
+%ssa% _ = call(fun transformable15/3, Arg0, C, B).
+ {X} = make_wrapped_empty_binary(),
+ Y = make_empty_binary(),
+ transformable15(L, X, Y).
+
+transformable15([A,B|T], Acc0, Acc1) ->
+%ssa% (_, Arg1, Arg2) when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, _, Arg1, ...),
+%ssa% B = bs_create_bin(private_append, _, Arg2, ...),
+%ssa% _ = call(fun transformable15/3, _, A, B).
+
+ transformable15(T, <<Acc0/binary, A:8>>, <<Acc1/binary, B:8>>);
+transformable15([], Acc0, Acc1) ->
+ {Acc0,Acc1}.
+
+transformable16(L) ->
+%ssa% (Arg0) when post_ssa_opt ->
+%ssa% A = call(fun make_wrapped_empty_binary/0),
+%ssa% B = call(fun make_empty_binary/0),
+%ssa% C = put_tuple(A, B),
+%ssa% _ = call(fun transformable16/2, Arg0, C).
+ X = make_wrapped_empty_binary(),
+ Y = make_empty_binary(),
+ transformable16(L, {X, Y}).
+
+transformable16([A,B|T], {{Acc0}, Acc1}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = get_tuple_element(A, 0),
+%ssa% C = bs_create_bin(private_append, _, B, ...),
+%ssa% D = get_tuple_element(Arg1, 1),
+%ssa% E = bs_create_bin(private_append, _, D, ...),
+%ssa% F = put_tuple(C),
+%ssa% G = put_tuple(F, E),
+%ssa% _ = call(fun transformable16/2, _, G).
+ transformable16(T, {{<<Acc0/binary, A:8>>}, <<Acc1/binary, B:8>>});
+transformable16([], {{Acc0}, Acc1}) ->
+ {Acc0,Acc1}.
+
+%% We should use type information to figure out that {<<>>, X} is not
+%% aliased, but as of now we don't have the information at this pass,
+%% nor do we track alias status at the sub-term level.
+transformable18(L, X) when is_integer(X), X < 256 ->
+%ssa% xfail (_, _) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = put_tuple(_, A),
+%ssa% _ = call(fun transformable18b/2, _, B).
+ transformable18b(L, {<<>>, X}).
+
+transformable18b([H|T], {Acc,X}) ->
+%ssa% xfail (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = put_tuple(B, _),
+%ssa% _ = call(fun transformable18b/2, _, C).
+ transformable18b(T, {<<Acc/binary, (H+X):8>>, X});
+transformable18b([], {Acc,_}) ->
+ Acc.
+
+%% Check that the conversion works when the binary isn't embedded in a
+%% tuple literal.
+transformable19(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = put_tuple(A, _),
+%ssa% _ = call(fun transformable19b/2, _, B).
+ X = case ex:foo() of
+ true ->
+ 4711;
+ false ->
+ 17
+ end,
+ transformable19b(L, {<<>>, X}).
+
+transformable19b([H|T], {Acc,X}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = put_tuple(B, _),
+%ssa% _ = call(fun transformable19b/2, _, C).
+ transformable19b(T, {<<Acc/binary, (H+X):8>>, X});
+transformable19b([], {Acc,_}) ->
+ Acc.
+
+%% Check that the conversion works when the binary isn't embedded in a
+%% list literal.
+transformable20(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = put_list(A, _),
+%ssa% _ = call(fun transformable20b/2, _, B).
+ X = case ex:foo() of
+ true ->
+ 4711;
+ false ->
+ 17
+ end,
+ transformable20b(L, [<<>>|X]). % XXXX
+
+transformable20b([H|T], [Acc|X]) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_hd(Arg1),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = put_list(B, _),
+%ssa% _ = call(fun transformable20b/2, _, C).
+ transformable20b(T, [<<Acc/binary, (H+X):8>>|X+1]);
+transformable20b([], [Acc|_]) ->
+ Acc.
+
+%% Check that the conversion works when the binary is embedded in a
+%% tuple literal returned from another function.
+transformable21(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = call(fun make_empty_binary_tuple/0),
+%ssa% _ = call(fun transformable21/2, _, A).
+ transformable21(L, make_empty_binary_tuple()).
+
+transformable21([H|T], {AccA,AccB}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = get_tuple_element(Arg1, 1),
+%ssa% D = bs_create_bin(private_append, _, C, ...),
+%ssa% E = put_tuple(B, D),
+%ssa% _ = call(fun transformable21/2, _, E).
+ transformable21(T, {<<AccA/binary, H:8>>,<<AccB/binary, 17:8>>});
+transformable21([], {AccA, AccB}) ->
+ {AccA, AccB}.
+
+make_empty_binary_tuple() ->
+%ssa% () when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = bs_init_writable(_),
+%ssa% C = put_tuple(A, B),
+%ssa% ret(C).
+ {<<>>, <<>>}.
+
+%% Check that the conversion works for the first element of the list.
+%% Type analysis does not understand that the second field of the cons
+%% isn't an improper list. As the private append variant of
+%% bs_create_bin can't deal with non-bitstrings, check that we
+%% conservatively refuse to rewrite the second element of the cons.
+transformable22(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% C = put_list(A, _),
+%ssa% _ = call(fun transformable22/2, _, C).
+ transformable22(L, [<<>>|<<>>]).
+
+transformable22([H|T], [AccA|AccB]) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_hd(Arg1),
+%ssa% B = get_tl(Arg1),
+%ssa% C = bs_create_bin(private_append, _, A, ...),
+%ssa% D = bs_create_bin(append, _, B, ...),
+%ssa% E = put_list(C, D),
+%ssa% _ = call(fun transformable22/2, _, E).
+ transformable22(T, [<<AccA/binary, H:8>>|<<AccB/binary, 17:8>>]);
+transformable22([], Acc) ->
+ Acc.
+
+%% As transformable21 but with a more complex embedded tuple
+transformable23(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = call(fun make_empty_binary_tuple_nested/0),
+%ssa% _ = call(fun transformable23/2, _, A).
+ transformable23(L, make_empty_binary_tuple_nested()).
+
+transformable23([H|T], {AccA,{AccB},X}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = get_tuple_element(Arg1, 1),
+%ssa% D = get_tuple_element(C, 0),
+%ssa% E = bs_create_bin(private_append, _, D, ...),
+%ssa% F = put_tuple(E),
+%ssa% G = put_tuple(B, F, _),
+%ssa% _ = call(fun transformable23/2, _, G).
+ transformable23(T, {<<AccA/binary, H:8>>,{<<AccB/binary, 17:8>>}, X});
+transformable23([], {AccA, AccB, X}) ->
+ {AccA, AccB, X}.
+
+make_empty_binary_tuple_nested() ->
+%ssa% () when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = bs_init_writable(_),
+%ssa% C = put_tuple(B),
+%ssa% D = put_tuple(A, C, _),
+%ssa% ret(D).
+ {<<>>, {<<>>}, 47}.
+
+%% We can't handle this as we do not track alias status at the sub-term level.
+transformable24(L) ->
+%ssa% xfail (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = put_tuple(A, _),
+%ssa% _ = call(fun transformable24/2, _, B).
+ transformable24(L, {<<>>, ex:foo()}).
+
+transformable24([H|T], {Acc,X}) ->
+%ssa% xfail (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = put_tuple(B, _),
+%ssa% _ = call(fun transformable24/2, _, C).
+ transformable24(T, {<<Acc/binary, (H+X):8>>, X});
+transformable24([], {Acc,_}) ->
+ Acc.
+
+%% Check that the update of more than one element of a tuple is
+%% handled.
+transformable25(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = bs_init_writable(_),
+%ssa% C = put_tuple(A, B),
+%ssa% _ = call(fun transformable25/2, _, C).
+ transformable25(L, {<<>>,<<>>}).
+
+transformable25([H|T], {AccA,AccB}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = get_tuple_element(Arg1, 1),
+%ssa% D = bs_create_bin(private_append, _, C, ...),
+%ssa% E = put_tuple(B, D),
+%ssa% _ = call(fun transformable25/2, _, E).
+ transformable25(T, {<<AccA/binary, H:8>>,<<AccB/binary>>});
+transformable25([], Acc) ->
+ Acc.
+
+%% Check that the update of more than two elements of a tuple is
+%% handled (check the inductive step of
+%% beam_ssa_alias_opt:merge_arg_patches/1 works).
+transformable26(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% B = bs_init_writable(_),
+%ssa% C = bs_init_writable(_),
+%ssa% D = put_tuple(A, B, C),
+%ssa% _ = call(fun transformable26/2, _, D).
+ transformable26(L, {<<>>,<<>>,<<>>}).
+
+transformable26([H|T], {AccA,AccB,AccC}) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tuple_element(Arg1, 0),
+%ssa% B = bs_create_bin(private_append, _, A, ...),
+%ssa% C = get_tuple_element(Arg1, 1),
+%ssa% D = bs_create_bin(private_append, _, C, ...),
+%ssa% E = get_tuple_element(Arg1, 2),
+%ssa% F = bs_create_bin(private_append, _, E, ...),
+%ssa% G = put_tuple(B, D, F),
+%ssa% _ = call(fun transformable26/2, _, G).
+ transformable26(T, {<<AccA/binary, H:8>>,
+ <<AccB/binary, H:8>>,
+ <<AccC/binary, H:8>>});
+transformable26([], Acc) ->
+ Acc.
+
+%% Check that we allow the transform when we append a multiple of 8
+%% bits.
+transformable27(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable27/2, _, A).
+ transformable27(L, <<>>).
+
+transformable27([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(private_append, _, Arg1, ...).
+ transformable27(T, <<Acc/binary, H:7, 1:1>>);
+transformable27([], Acc) ->
+ Acc.
+
+transformable28(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable28/2, _, A).
+ transformable28(L, <<>>).
+
+transformable28([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(private_append, _, Arg1, ...).
+ transformable28(T, <<Acc/binary, H:23/bitstring, 1:1>>);
+transformable28([], Acc) ->
+ Acc.
+
+transformable29(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% _ = call(fun transformable29/2, _, A).
+ transformable29(L, <<>>).
+
+transformable29([A,B|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(private_append, _, Arg1, ...).
+ transformable29(T, <<Acc/binary, A:23/bitstring, B/float, 1:9>>);
+transformable29([], Acc) ->
+ Acc.
+
+%% As transformable22, but the accumulator is deconstructed in the
+%% reverse order, tail-head instead of head-tail.
+transformable30(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% C = put_list(A, _),
+%ssa% _ = call(fun transformable30/2, _, C).
+ transformable30(L, [<<>>|<<>>]).
+
+transformable30([H|T], X) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% B = get_tl(Arg1),
+%ssa% A = get_hd(Arg1),
+%ssa% C = bs_create_bin(private_append, _, A, ...),
+%ssa% D = bs_create_bin(append, _, B, ...),
+%ssa% E = put_list(C, D),
+%ssa% _ = call(fun transformable30/2, _, E).
+ AccB = tl(X),
+ AccA = hd(X),
+ transformable30(T, [<<AccA/binary, H:8>>|<<AccB/binary, 17:8>>]);
+transformable30([], Acc) ->
+ Acc.
+
+%% The private-append transform detects that the accumulator can be
+%% transformed when the third argument to transformable31/3 is
+%% 'b'. This test case checks that the transform doesn't try to
+%% rewrite the {a,b} tuple (which lead to a compiler crash).
+transformable31a(L) ->
+ transformable31(L, <<>>, a).
+
+transformable31b(L) ->
+ transformable31(L, <<>>, b).
+
+transformable31([H|T], Acc, a) when is_binary(Acc) ->
+ transformable31(T, <<Acc/binary, H:8>>, a);
+transformable31([_|T], _Acc, b) ->
+ transformable31(T, {a,b}, b);
+transformable31([], Acc, a) when is_binary(Acc) ->
+ Acc;
+transformable31([], Acc, b) when is_tuple(Acc) ->
+ <<>>.
+
+%% Check that we don't crash (Github issue #6847) while attempting to
+%% patch the empty list, but also that the literal <<>> becomes a
+%% bs_init_writable.
+transformable32() ->
+ <<(transformable32(ok))/binary>>.
+
+transformable32(#{}) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% A = bs_init_writable(_),
+%ssa% ret(A).
+ [];
+transformable32(_) ->
+ <<>>.
+
+%% Check that we don't crash (Github issue #6999) while attempting to
+%% patch the empty list, but also that Dest is created with private_append.
+transformable33() ->
+%ssa% () when post_ssa_opt ->
+%ssa% _ = bs_create_bin(private_append, ...).
+ [F01] = [transformable33_inner(<<"0">>) || _ <- [1]],
+ Dest = <<F01/binary>>,
+ Dest.
+
+transformable33_inner(V) ->
+ << <<C>> || <<C:4>> <= V >>.
+
+% Should not be transformed as we can't know the alias status of Acc
+not_transformable1([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...).
+ not_transformable1(T, <<Acc/binary, H:8>>);
+not_transformable1([], Acc) ->
+ Acc.
+
+% Should not be transformed as references to the binary can escape in
+% ex:escape/1.
+not_transformable2(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = call(fun not_transformable2/2, _, <<>>).
+ not_transformable2(L, <<>>).
+
+not_transformable2([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...).
+ ex:escape(Acc),
+ not_transformable2(T, <<Acc/binary, H:8>>);
+not_transformable2([], Acc) ->
+ Acc.
+
+% Should not be transformed as we create and preserve multiple
+% references to the binary.
+not_transformable3(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = call(fun not_transformable3/3, _, <<>>, _).
+ not_transformable3(L, <<>>, []).
+
+not_transformable3([H|T], Acc, Ls) ->
+%ssa% (_, Arg1, _) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...).
+ not_transformable3(T, <<Acc/binary, H:8>>, [Acc|Ls]);
+not_transformable3([], Acc, Ls) ->
+ {Acc, Ls}.
+
+%% We randomly keep multiple references to the binary.
+not_transformable4(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = call(fun not_transformable4/2, _, [<<>>]).
+ not_transformable4(L, [<<>>|[]]).
+
+not_transformable4([H|T], X=[Acc|Ls]) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, ...).
+ Tmp = case ex:f() of
+ true ->
+ [Q|_] = X,
+ Q;
+ false ->
+ ok
+ end,
+ T1 = [Tmp|Ls],
+ not_transformable4(T, [<<Acc/binary, H:8>>|T1]);
+not_transformable4([], Acc) ->
+ Acc.
+
+%% Check that the leak in the external call is detected despite the
+%% mutual recursion.
+not_transformable5(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = call(fun not_transformable5a/2, _, <<>>).
+ not_transformable5a(L, <<>>).
+
+not_transformable5a([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...).
+ not_transformable5b(T, <<Acc/binary, 0:8, H:8>>);
+not_transformable5a([], Acc) ->
+ Acc.
+
+not_transformable5b([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...).
+ ex:alias(Acc),
+ not_transformable5a(T, <<Acc/binary, 1:8, H:8>>);
+not_transformable5b([], Acc) ->
+ Acc.
+
+%% Check that we're not trying to build binaries which are not a
+%% multiple of 8 bits.
+not_transformable6(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = call(fun not_transformable6/2, _, <<>>).
+ not_transformable6(L, <<>>).
+
+not_transformable6([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...).
+ not_transformable6(T, <<Acc/binary, H:1>>);
+not_transformable6([], Acc) ->
+ Acc.
+
+not_transformable7(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = call(fun not_transformable7/2, _, <<>>).
+ not_transformable7(L, <<>>).
+
+not_transformable7([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...).
+ not_transformable7(T, <<Acc/binary, H:H>>);
+not_transformable7([], Acc) ->
+ Acc.
+
+not_transformable8(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = call(fun not_transformable8/2, _, <<>>).
+ not_transformable8(L, <<>>).
+
+not_transformable8([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...).
+ not_transformable8(T, <<Acc/binary, H/float, 15:4>>);
+not_transformable8([], Acc) ->
+ Acc.
+
+not_transformable9(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = call(fun not_transformable9/2, _, <<>>).
+ not_transformable9(L, <<>>).
+
+not_transformable9([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...).
+ not_transformable9(T, <<Acc/binary, H:15/binary, 15:4>>);
+not_transformable9([], Acc) ->
+ Acc.
+
+not_transformable10(L) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = call(fun not_transformable10/2, _, <<>>).
+ not_transformable10(L, <<>>).
+
+not_transformable10([H|T], Acc) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, _, Arg1, ...).
+ not_transformable10(T, <<Acc/binary, H:15/bitstring>>);
+not_transformable10([], Acc) ->
+ Acc.
+
+%% Check that we don't transform when we can't guarantee that the
+%% first fragment to bs_create_bin is a bitstring.
+not_transformable11() ->
+%ssa% fail () when post_ssa_opt ->
+%ssa% _ = bs_init_writable(_).
+ not_transformable11(not_ok).
+
+not_transformable11(X) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, ...).
+ <<(case ok of
+ X -> <<>>;
+ _ -> ok
+ end)/bytes>>.
+
+%% Check that we don't transform when we can't guarantee that the
+%% first fragment to bs_create_bin is a bitstring.
+not_transformable12(A) ->
+%ssa% (_) when post_ssa_opt ->
+%ssa% _ = bs_create_bin(append, ...).
+ <<(case ok of
+ A ->
+ not is_alive();
+ _ ->
+ <<>>
+ end)/bitstring>>.
+
+%% The order of fields in the accumulator is swapped compared to
+%% transformable7. Type analysis does not understand that the second
+%% field of the cons isn't an improper list. As the private append
+%% variant of bs_create_bin can't deal with non-bitstrings, check that
+%% we conservatively refuse to rewrite this case.
+not_transformable13(L) ->
+ not_transformable13(L, [0|<<>>]).
+
+not_transformable13([H|T], [N|Acc]) ->
+%ssa% (_, Arg1) when post_ssa_opt ->
+%ssa% A = get_tl(Arg1),
+%ssa% B = bs_create_bin(append, _, A, ...),
+%ssa% C = put_list(_, B),
+%ssa% _ = call(fun not_transformable13/2, _, C).
+ not_transformable13(T, [N+1|<<Acc/binary, H:8>>]);
+not_transformable13([], Acc) ->
+ Acc.
+
+%% Check that we don't try to transform appends into private_appends
+%% when the first fragment doesn't die with the
+%% bs_create_bin. GH-6925.
+not_transformable14() ->
+%ssa% () when post_ssa_opt ->
+%ssa% A = bs_create_bin(private_append, ...),
+%ssa% B = bs_create_bin(append, ...).
+ A = << <<"x">> || true >>,
+ B = <<A/binary, "z">>,
+ {A, B}.
+
+
+%% Check that we don't crash in cases where tracking a value ends up
+%% in operations through which we can't continue tracking. GH-7011.
+not_transformable15(V, _) when V ->
+%ssa% (_, _) when post_ssa_opt ->
+%ssa% _ = bs_init_writable(_),
+%ssa% B = call(fun not_transformable15/2, _, _),
+%ssa% _ = bs_create_bin(private_append, _, B, ...).
+ << ok || catch <<(not_transformable15(id(ok), ok))/binary>> >>;
+not_transformable15(_, V) ->
+ id(ok) bor V.
+
+id(I) ->
+ I.
+
+%% Check that we don't try to private_append to something created by
+%% bs_create_bin `append`, _, `<<>>`, ...
+bs_create_bin_on_literal() ->
+%ssa% () when post_ssa_opt ->
+%ssa% X = bs_init_writable(_),
+%ssa% Y = bs_create_bin(private_append, _, X, ...),
+%ssa% Z = bs_create_bin(private_append, _, Y, ...),
+%ssa% ret(Z).
+ <<
+ <<
+ (maybe
+ 2147483647 ?= ok
+ else
+ <<_>> ->
+ ok;
+ _ ->
+ <<>>
+ end)/bytes
+ >>/binary
+ >>.
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/ret_annotation.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/ret_annotation.erl
new file mode 100644
index 0000000000..a8e052dc56
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/ret_annotation.erl
@@ -0,0 +1,46 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+-module(ret_annotation).
+
+-export([return_atom/0, return_int/0, return_tuple/0, return_unknown/0]).
+
+%%%
+%%% Check that a type annotation is added to return instructions when
+%%% the type is known.
+%%%
+
+return_atom() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_atom,_} }.
+ foo.
+
+return_int() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_integer,_} }.
+ 17.
+
+return_tuple() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(_) { result_type => {t_tuple,2,true,_} }.
+ {1,2}.
+
+return_unknown() ->
+%ssa% fail () when post_ssa_opt ->
+%ssa% ret(_) { result_type => _ },
+%ssa% label 1.
+ e:f().
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl
new file mode 100644
index 0000000000..47c60fd8d6
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl
@@ -0,0 +1,341 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+
+-module(sanity_checks).
+
+-compile(no_ssa_opt_private_append).
+
+-export([check_fail/0,
+ check_wrong_pass/0,
+ check_xfail/0,
+ check_prefix1/0,
+ check_prefix2/0,
+ check_prefix3/0,
+
+ t0/0, t1/0, t2/0, t3/0, t4/0,
+ t5/0, t6/0, t7/0, t8/0, t9/0,
+ t10/0, t11/0, t12/0, t13/0, t14/0,
+ t15/0, t16/0, t17/0, t18/0, t19/0,
+ t20/0, t21/0, t22/0, t23/0, t24/0,
+ t25/0, t26/0, t27/0, t28/0, t29/0,
+ t30/0, t31/0, t32/1, t33/1, t34/1,
+ t35/1, t36/0, t37/0, t38/0, t39/1,
+ t40/0, t41/0, t42/0, t43/0, t44/0,
+
+ check_env/0]).
+
+%% Check that we do not trigger on the wrong pass
+check_wrong_pass() ->
+%ssa% (_) when this_pass_does_not_exist ->
+%ssa% _ = this_instruction_does_not_exist().
+ ok.
+
+%% Check that failures work
+check_fail() ->
+%ssa% fail (_) when post_ssa_opt ->
+%ssa% _ = this_instruction_does_not_exist().
+ ok.
+
+check_xfail() ->
+%ssa% xfail (_) when post_ssa_opt ->
+%ssa% _ = this_instruction_does_not_exist().
+ ok.
+
+check_prefix1() ->
+%ssa% fail (_) when post_ssa_opt ->
+%ssa% _ = this_instruction_does_not_exist().
+ ok.
+
+check_prefix2() ->
+%%ssa% fail (_) when post_ssa_opt ->
+%ssa% _ = this_instruction_does_not_exist().
+ ok.
+
+check_prefix3() ->
+%%%ssa% fail (_) when post_ssa_opt ->
+%ssa% _ = this_instruction_does_not_exist().
+ ok.
+
+%% Check map literals with any type which can be a key
+t0() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{}).
+ #{}.
+
+t1() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{a=>b}).
+ #{a=>b}.
+
+t2() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{a=>b,c=>d}).
+ #{a=>b,c=>d}.
+
+t3() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{1=>1}).
+ #{1=>1}.
+
+t4() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{3.14=>3.14}).
+ #{3.14=>3.14}.
+
+t5() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{#{}=>1}).
+ #{#{}=>1}.
+
+t6() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{<<17,15>> => 1}).
+ #{<<17,15>> => 1}.
+
+t7() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{{} => 1}).
+ #{{} => 1}.
+
+t8() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{{1,2} => 1}).
+ #{{1,2} => 1}.
+
+t9() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{[] => 1}).
+ #{[] => 1}.
+
+t10() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(#{[1,2] => 1}).
+ #{[1,2] => 1}.
+
+
+%% Check literals
+t11() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(atom).
+ atom.
+
+t12() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(17).
+ 17.
+
+t13() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(3.14).
+ 3.14.
+
+t14() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(3.141(1.0e-3)).
+ 3.1415.
+
+t15() ->
+%ssa% () when post_ssa_opt ->
+%ssa% _ = make_fun(fun t15/0).
+ fun t15/0.
+
+t16() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(fun sanity_checks:t16/0).
+ fun sanity_checks:t16/0.
+
+t17() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret({}).
+ {}.
+
+t18() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret({1}).
+ {1}.
+
+t19() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret({1,2}).
+ {1,2}.
+
+t20() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret({1,2,3}).
+ {1,2,3}.
+
+t21() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret({1,...}).
+ {1,2,3}.
+
+t22() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(<<>>).
+ <<>>.
+
+t23() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(<<1>>).
+ <<1>>.
+
+t24() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(<<1,2>>).
+ <<1,2>>.
+
+t25() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret(<<1,2,3>>).
+ <<1,2,3>>.
+
+t26() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret([]).
+ [].
+
+t27() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret([1]).
+ [1].
+
+t28() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret([1,2]).
+ [1,2].
+
+t29() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret([1,2,3]).
+ [1,2,3].
+
+t30() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret([1,2|3]).
+ [1,2|3].
+
+t31() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret([1,2,...]).
+ [1,2,3].
+
+%% Check that we handle a bif
+t32(X) ->
+%ssa% (X) when post_ssa_opt ->
+%ssa% A = bif:'=='(X, 1).
+ true = X == 1.
+
+%% Check that we handle a br
+t33(X) ->
+%ssa% (X) when post_ssa_opt ->
+%ssa% A = bif:'=='(X, 1),
+%ssa% br(A, 5, 4).
+ true = X == 1.
+
+%% Check that we handle a branch and variable labels
+t34(X) ->
+%ssa% (X) when post_ssa_opt ->
+%ssa% A = bif:'=='(X, 1),
+%ssa% br(A, Succ, Fail),
+%ssa% label Succ,
+%ssa% ret(true),
+%ssa% label Fail,
+%ssa% _ = match_fail(badmatch, ...).
+ true = X == 1.
+
+%% Check that we handle a switch
+t35(X) ->
+%ssa% (X) when post_ssa_opt ->
+%ssa% switch(X, Fail, [{1,One},{2,Two},...]),
+%ssa% label Two,
+%ssa% ret(b),
+%ssa% label One,
+%ssa% ret(a),
+%ssa% label Fail,
+%ssa% _ = match_fail(case_clause, ...).
+ case X of
+ 1 ->
+ a;
+ 2 ->
+ b;
+ 3 ->
+ c
+ end.
+
+%% Check a complex pattern
+t36() ->
+%ssa% () when post_ssa_opt ->
+%ssa% ret({{}, 4,a,{[45|a], 17.0, 3.141(1.0e-3), #{a=>b}}, [1,2,...], ...}).
+ {{}, 4,a,{[45|a], 17.0, 3.14,#{a=>b}}, [1,2,3,4], 3.1415, 5, 7}.
+
+%% Check '...' when used for instruction arguments.
+t37() ->
+%ssa% () when post_ssa_opt ->
+%ssa% _ = call(fun e:f0/4, _, _, ...),
+%ssa% _ = call(fun e:f1/4, ...).
+ e:f0(1, 2, 3, 4),
+ e:f1(1, 2, 3, 4).
+
+%% Check that we fail if we specify too many arguments.
+t38() ->
+%ssa% xfail (_) when post_ssa_opt ->
+%ssa% _ = call(fun e:f0/4, ...).
+ e:f0(1, 2, 3, 4).
+
+%% Check that we fail if we specify too few arguments.
+t39(_) ->
+%ssa% xfail () when post_ssa_opt ->
+%ssa% _ = call(fun e:f0/4, ...).
+ e:f0(1, 2, 3, 4).
+
+
+%% Check ... when used to match part of a tuple.
+t40() ->
+%ssa% () when post_ssa_opt ->
+%ssa% _ = call(fun e:f0/1, {...}).
+ e:f0({1, 2, 3, 4}).
+
+t41() ->
+%ssa% () when post_ssa_opt ->
+%ssa% _ = call(fun e:f0/1, {1, ...}).
+ e:f0({1, 2, 3, 4}).
+
+t42() ->
+%ssa% () when post_ssa_opt ->
+%ssa% _ = call(fun e:f0/1, {1, 2, ...}).
+ e:f0({1, 2, 3, 4}).
+
+t43() ->
+%ssa% fail () when post_ssa_opt ->
+%ssa% _ = call(fun e:f0/1, {1, 2, ...}).
+ e:f0({1}).
+
+t44() ->
+%ssa% () when post_ssa_opt ->
+%ssa% _ = call(fun e:f0/1, {...}).
+ e:f0({}).
+
+%% Ensure bug which trashed the environment after matching a literal
+%% bitstring stays fixed.
+check_env() ->
+%ssa% () when post_ssa_opt ->
+%ssa% X = bs_create_bin(append, _, <<>>, ...),
+%ssa% ret(X).
+ A = <<>>,
+ B = ex:f(),
+ <<A/binary, B/binary>>.
diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl
index e448b5cf41..707c7ca80b 100644
--- a/lib/compiler/test/beam_type_SUITE.erl
+++ b/lib/compiler/test/beam_type_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,12 +22,16 @@
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
integers/1,numbers/1,coverage/1,booleans/1,setelement/1,
- cons/1,tuple/1,record_float/1,binary_float/1,float_compare/1,
+ cons/1,tuple/1,
+ record_float/1,binary_float/1,float_compare/1,float_overflow/1,
arity_checks/1,elixir_binaries/1,find_best/1,
test_size/1,cover_lists_functions/1,list_append/1,bad_binary_unit/1,
none_argument/1,success_type_oscillation/1,type_subtraction/1,
container_subtraction/1,is_list_opt/1,connected_tuple_elements/1,
- switch_fail_inference/1,failures/1]).
+ switch_fail_inference/1,failures/1,
+ cover_maps_functions/1,min_max_mixed_types/1,
+ not_equal/1,infer_relops/1,binary_unit/1,premature_concretization/1,
+ funs/1]).
%% Force id/1 to return 'any'.
-export([id/1]).
@@ -49,6 +53,7 @@ groups() ->
record_float,
binary_float,
float_compare,
+ float_overflow,
arity_checks,
elixir_binaries,
find_best,
@@ -63,7 +68,14 @@ groups() ->
is_list_opt,
connected_tuple_elements,
switch_fail_inference,
- failures
+ failures,
+ cover_maps_functions,
+ min_max_mixed_types,
+ not_equal,
+ infer_relops,
+ binary_unit,
+ premature_concretization,
+ funs
]}].
init_per_suite(Config) ->
@@ -105,6 +117,20 @@ integers(_Config) ->
-693 = do_integers_9(id(7), id(1)),
+ 3 = do_integers_10(1, 2),
+ 10 = do_integers_10(-2, -5),
+
+ {'EXIT',{badarith,_}} = catch do_integers_11(42),
+ {'EXIT',{badarith,_}} = catch do_integers_11({a,b}),
+
+ {'EXIT',{system_limit,_}} = catch do_integers_12(42),
+ {'EXIT',{system_limit,_}} = catch do_integers_12([]),
+
+ {'EXIT',{{badmatch,42},_}} = catch do_integers_13(-43),
+ {'EXIT',{{badmatch,0},_}} = catch do_integers_13(-1),
+ {'EXIT',{{badmatch,-1},_}} = catch do_integers_13(0),
+ {'EXIT',{{badmatch,-18},_}} = catch do_integers_13(17),
+
ok.
do_integers_1(B0) ->
@@ -176,6 +202,47 @@ do_integers_8() ->
do_integers_9(X, Y) ->
X * (-100 bor (Y band 1)).
+do_integers_10(A, B) when is_integer(A), is_integer(B), A < 2, B < 5 ->
+ if
+ A < B -> A + B;
+ true -> A * B
+ end.
+
+do_integers_11(V) ->
+ true - V bsl [].
+
+do_integers_12(X) ->
+ (1 bsl (1 bsl 100)) + X.
+
+%% GH-6427.
+do_integers_13(X) ->
+ try do_integers_13_1(<<X>>) of
+ _ -> error(should_fail)
+ catch
+ C:R:_ ->
+ try do_integers_13_2(X) of
+ _ -> error(should_fail)
+ catch
+ C:R:_ ->
+ try do_integers_13_3(X) of
+ _ -> error(should_fail)
+ catch
+ C:R:Stk ->
+ erlang:raise(C, R, Stk)
+ end
+ end
+ end.
+
+do_integers_13_1(<<X>>) ->
+ <<(X = bnot X)>>.
+
+do_integers_13_2(X) when is_integer(X), -64 < X, X < 64 ->
+ (X = bnot X) + 1.
+
+do_integers_13_3(X) when is_integer(X), -64 < X, X < 64 ->
+ X = bnot X,
+ X + 1.
+
numbers(_Config) ->
Int = id(42),
true = is_integer(Int),
@@ -227,8 +294,16 @@ numbers(_Config) ->
Meet1 = id(0) + -10.0, %Float.
10.0 = abs(Meet1), %Number.
+ %% Cover code in beam_call_types:beam_bounds_type/3.
+ ok = fcmp(0.0, 1.0),
+ error = fcmp(1.0, 0.0),
+
ok.
+fcmp(0.0, 0.0) -> ok;
+fcmp(F1, F2) when (F1 - F2) / F2 < 0.0000001 -> ok;
+fcmp(_, _) -> error.
+
coverage(Config) ->
{'EXIT',{badarith,_}} = (catch id(1) bsl 0.5),
{'EXIT',{badarith,_}} = (catch id(2.0) bsl 2),
@@ -276,6 +351,18 @@ coverage(Config) ->
{'EXIT',{function_clause,_}} = catch coverage_3("a"),
{'EXIT',{function_clause,_}} = catch coverage_3("b"),
+ Number = id(1),
+ if
+ 0 =< Number, Number < 10 ->
+ 0 = coverage_4(-1, Number),
+ 10 = coverage_4(0, Number),
+ 20 = coverage_4(1, Number),
+ 30 = coverage_4(2, Number)
+ end,
+
+ {'EXIT',{badarg,_}} = catch false ++ true,
+ {'EXIT',{badarg,_}} = catch false -- true,
+
ok.
coverage_1() ->
@@ -295,6 +382,9 @@ coverage_2() ->
coverage_3("a" = V) when is_function(V, false) ->
0.
+coverage_4(X, Y) ->
+ 10 * (X + Y).
+
booleans(_Config) ->
{'EXIT',{{case_clause,_},_}} = (catch do_booleans_1(42)),
@@ -326,6 +416,18 @@ booleans(_Config) ->
end,
false = is_boolean(NotBool),
+ {'EXIT',{{case_clause,false},_}} = catch do_booleans_4(42),
+ {'EXIT',{{case_clause,true},_}} = catch do_booleans_4(a),
+ {'EXIT',{{case_clause,true},_}} = catch do_booleans_4(false),
+ {'EXIT',{{badmatch,true},_}} = catch do_booleans_4(true),
+
+ true = do_booleans_5(id(0), id(<<0>>), id(0)),
+ {'EXIT',{function_clause,_}} = catch do_booleans_6(id(0), id(0), id(0)),
+
+ {'EXIT',{{bad_filter,_},_}} = catch do_booleans_7(id(0)),
+ {'EXIT',{function_clause,_}} = catch do_booleans_8(id(0)),
+ {'EXIT',{{try_clause,_},_}} = catch do_booleans_9(id(0)),
+
ok.
do_booleans_1(B) ->
@@ -354,6 +456,58 @@ do_booleans_3(NewContent, IsAnchor) ->
error
end.
+do_booleans_4(X) ->
+ case is_atom(X) of
+ Y when X ->
+ false = Y,
+ 0
+ end.
+
+do_booleans_5(X, <<X>>, X) when true; (0 rem 0) ->
+ (-2147483648 < X) orelse [0 || _ <- X].
+
+do_booleans_6(X, X, (X = [_ | X])) when true; self() ->
+ [0 || _ <- {X}].
+
+%% GH-6603: The boolean optimization pass was clever enough to see that boolean
+%% operations like `xor` never failed when both arguments were booleans, but
+%% neither the type pass nor the validator could see that.
+do_booleans_7(X) ->
+ do_booleans_7_a(
+ try [0 || true xor (ok =/= ((?MODULE:id([]) ++ []) -- []))] of
+ Y ->
+ <<0 || do_booleans_7_a(Y)>>
+ catch
+ [] ->
+ X
+ end
+ ).
+
+do_booleans_7_a(_) ->
+ [].
+
+do_booleans_8([X | Y]) ->
+ try
+ ([ok || _ <- Y] > catch <<ok>>) xor false
+ catch
+ <<Z:X>> ->
+ Z
+ end.
+
+do_booleans_9(X) ->
+ (try
+ (true or garbage_collect()) xor
+ is_tuple(catch (1 / 0))
+ of
+ #{} ->
+ ok
+ catch
+ _ ->
+ ok
+ end) < X.
+
+-record(update_tuple_a, {a,b}).
+-record(update_tuple_b, {a,b,c}).
setelement(_Config) ->
T0 = id({a,42}),
@@ -375,8 +529,41 @@ setelement(_Config) ->
end,
{'EXIT',{badarg,_}} = catch setelement(Index1, {a,b,c}, y),
+ %% Cover some edge cases in beam_call_types:will_succeed/3 and
+ %% beam_call_types:types/3
+ {y} = setelement(1, tuple_or_integer(0), y),
+ {y} = setelement(1, record_or_integer(0), y),
+ {'EXIT',{badarg,_}} = catch setelement(2, tuple_or_integer(id(0)), y),
+ {'EXIT',{badarg,_}} = catch setelement(2, tuple_or_integer(id(1)), y),
+ {'EXIT',{badarg,_}} = catch setelement(2, record_or_integer(id(0)), y),
+ {'EXIT',{badarg,_}} = catch setelement(2, record_or_integer(id(1)), y),
+ {'EXIT',{badarg,_}} = catch setelement(id(2), not_a_tuple, y),
+
+ %% Cover some edge cases in beam_types:update_tuple/2
+ {'EXIT',{badarg,_}} = catch setelement(2, not_a_tuple, y),
+ {'EXIT',{badarg,_}} = catch setelement(not_an_index, {a,b,c}, y),
+ {'EXIT',{badarg,_}} = catch setelement(8, {out_of_range}, y),
+ {y,_,_} = update_tuple_1(#update_tuple_a{}, y),
+ {y,_,_,_} = update_tuple_1(#update_tuple_b{}, y),
+ #update_tuple_a{a=y} = update_tuple_2(#update_tuple_a{}, y),
+ #update_tuple_b{a=y} = update_tuple_2(#update_tuple_b{}, y),
+ {'EXIT',{badarg,_}} = catch update_tuple_3(id(#update_tuple_a{}), y),
+ {'EXIT',{badarg,_}} = catch update_tuple_3(id(#update_tuple_b{}), y),
+ {'EXIT',{badarg,_}} = catch update_tuple_4(id(#update_tuple_a{}), y),
+ #update_tuple_b{c=y} = update_tuple_4(id(#update_tuple_b{}), y),
+
ok.
+record_or_integer(0) ->
+ {tuple};
+record_or_integer(N) when is_integer(N) ->
+ N.
+
+tuple_or_integer(0) ->
+ {id(tuple)};
+tuple_or_integer(N) when is_integer(N) ->
+ N.
+
do_setelement_1(<<N:32>>, Tuple, NewValue) ->
_ = element(N, Tuple),
%% While updating the type for Tuple, beam_ssa_type would do:
@@ -389,6 +576,36 @@ do_setelement_2(<<N:1>>, Tuple, NewValue) ->
two = element(2, Tuple),
setelement(N, Tuple, NewValue).
+update_tuple_1(Tuple, Value0) ->
+ Value = case Tuple of
+ #update_tuple_a{} -> Value0;
+ #update_tuple_b{} -> Value0
+ end,
+ setelement(1, Tuple, Value).
+
+update_tuple_2(Tuple, Value0) ->
+ Value = case Tuple of
+ #update_tuple_a{} -> Value0;
+ #update_tuple_b{} -> Value0
+ end,
+ setelement(2, Tuple, Value).
+
+update_tuple_3(Tuple, Value0) ->
+ Value = case Tuple of
+ #update_tuple_a{} -> Value0;
+ #update_tuple_b{} -> Value0
+ end,
+ setelement(47, Tuple, Value).
+
+update_tuple_4(Tuple, Value0) ->
+ Value = case Tuple of
+ #update_tuple_a{} -> Value0;
+ #update_tuple_b{} -> Value0
+ end,
+ %% #update_tuple_a{} is three elements long, so this should only work for
+ %% #update_tuple_b{}.
+ setelement(4, Tuple, Value).
+
cons(_Config) ->
[did] = cons(assigned, did),
@@ -462,6 +679,14 @@ tuple(_Config) ->
{0,0,-1} = decrement_element(3, Counters10),
{'EXIT',{badarg,_}} = catch decrement_element(4, Counters10),
+ [] = gh_6458(id({true})),
+ {'EXIT',{function_clause,_}} = catch gh_6458(id({false})),
+ {'EXIT',{function_clause,_}} = catch gh_6458(id({42})),
+ {'EXIT',{function_clause,_}} = catch gh_6458(id(a)),
+
+ {'EXIT',{badarg,_}} = catch gh_6927(id({a,b})),
+ {'EXIT',{badarg,_}} = catch gh_6927(id([])),
+
ok.
do_tuple() ->
@@ -481,6 +706,27 @@ decrement_element(Pos, Cs) ->
Ns = element(Pos, Cs),
setelement(Pos, Cs, Ns - 1).
+gh_6458({X}) when X; (X orelse false) ->
+ (X orelse {}),
+ [
+ {X}#{
+ gh_6458() orelse X => []
+ }
+ || _ <- []
+ ].
+
+gh_6458() ->
+ true.
+
+gh_6927(X) ->
+ %% beam_validator would complain because beam_call_types:will_succeed/3
+ %% said `maybe`, but beam_call_types:types/3 returned the type `none`.
+ element(42,
+ case X of
+ {_,_} -> X;
+ _ -> ok
+ end).
+
-record(x, {a}).
record_float(_Config) ->
@@ -529,6 +775,46 @@ do_float_compare(X) ->
_T -> Y > 0
end.
+float_overflow(_Config) ->
+ Res1 = id((1 bsl 1023) * two()),
+ Res1 = float_overflow_1(),
+
+ Res2 = id((-1 bsl 1023) * two()),
+ Res2 = float_overflow_2(),
+
+ {'EXIT',{{bad_filter,[0]},_}} = catch float_overflow_3(),
+
+ ok.
+
+%% GH-7178: There would be an overflow when converting a number range
+%% to a float range.
+float_overflow_1() ->
+ round(
+ try
+ round(float(1 bsl 1023)) * two()
+ catch
+ _:_ ->
+ 0.0
+ end
+ ).
+
+float_overflow_2() ->
+ round(
+ try
+ round(float(-1 bsl 1023)) * two()
+ catch
+ _:_ ->
+ 0.0
+ end
+ ).
+
+two() -> 2.
+
+float_overflow_3() ->
+ [0 || <<>> <= <<>>,
+ [0 || (floor(1.7976931348623157e308) bsl 1) >= (1.0 + map_size(#{}))]
+ ].
+
arity_checks(_Config) ->
%% ERL-549: an unsafe optimization removed a test_arity instruction,
%% causing the following to return 'broken' instead of 'ok'.
@@ -629,8 +915,21 @@ do_test_size(Term) when is_binary(Term) ->
size(Term).
cover_lists_functions(Config) ->
+ foo = lists:foldl(id(fun(_, _) -> foo end), foo, Config),
+ foo = lists:foldl(fun(_, _) -> foo end, foo, Config),
+ {'EXIT',_} = catch lists:foldl(not_a_fun, foo, Config),
+
+ foo = lists:foldr(id(fun(_, _) -> foo end), foo, Config),
+ foo = lists:foldr(fun(_, _) -> foo end, foo, Config),
+ {'EXIT',_} = catch lists:foldr(not_a_fun, foo, Config),
+
{data_dir,_DataDir} = lists:keyfind(data_dir, id(1), Config),
+ {'EXIT',_} = catch lists:keyfind(data_dir, not_a_position, Config),
+ {'EXIT',_} = catch lists:keyfind(data_dir, 1, not_a_list),
+ {'EXIT',_} = catch lists:map(not_a_fun, Config),
+ {'EXIT',_} = catch lists:map(not_a_fun, []),
+ {'EXIT',_} = catch lists:map(fun id/1, not_a_list),
Config = lists:map(id(fun id/1), Config),
case lists:suffix([no|Config], Config) of
@@ -640,6 +939,9 @@ cover_lists_functions(Config) ->
ok
end,
+ [] = lists:zip([], []),
+ {'EXIT',_} = (catch lists:zip(not_list, [b])),
+
Zipper = fun(A, B) -> {A,B} end,
[] = lists:zipwith(Zipper, [], []),
@@ -654,11 +956,17 @@ cover_lists_functions(Config) ->
Zipped),
[{zip_zip,{zip,_}}|_] = DoubleZip,
+ {'EXIT',_} = (catch lists:zipwith(not_a_fun, [a], [b])),
+ {'EXIT',{bad,_}} = (catch lists:zipwith(fun(_A, _B) -> error(bad) end,
+ [a], [b])),
+ {'EXIT',_} = (catch lists:zipwith(fun(_A, _B) -> error(bad) end,
+ not_list, [b])),
{'EXIT',{bad,_}} = (catch lists:zipwith(fun(_A, _B) -> error(bad) end,
lists:duplicate(length(Zipped), zip_zip),
Zipped)),
- [{zip_zip,{zip,_}}|_] = DoubleZip,
+ {'EXIT',_} = catch lists:unzip(not_a_list),
+ {'EXIT',_} = catch lists:unzip([not_a_tuple]),
{[_|_],[_|_]} = lists:unzip(Zipped),
ok.
@@ -933,5 +1241,202 @@ failures_1([] = V1, V2, V3) ->
%% beam_clean to crash.
{V1 - V3, (V1 = V2) - V3}.
+%% Covers various edge cases in beam_call_types:types/3 relating to maps
+cover_maps_functions(_Config) ->
+ {'EXIT',_} = catch maps:filter(fun(_, _) -> true end, not_a_map),
+ {'EXIT',_} = catch maps:filter(not_a_predicate, #{}),
+
+ error = maps:find(key_not_present, #{}),
+
+ {'EXIT',_} = catch maps:fold(fun(_, _, _) -> true end, init, not_a_map),
+ {'EXIT',_} = catch maps:fold(not_a_fun, init, #{}),
+
+ #{} = maps:from_keys([], gurka),
+ #{ hello := gurka } = maps:from_keys([hello], gurka),
+ {'EXIT',_} = catch maps:from_keys(not_a_list, gurka),
+
+ #{} = catch maps:from_list([]),
+ {'EXIT',_} = catch maps:from_list([not_a_tuple]),
+
+ default = maps:get(key_not_present, #{}, default),
+ {'EXIT',_} = catch maps:get(key_not_present, #{}),
+
+ [] = maps:keys(#{}),
+ {'EXIT',_} = catch maps:keys(not_a_map),
+
+ #{ a := ok } = catch maps:map(fun(_, _) -> ok end, #{ a => a }),
+ {'EXIT',_} = catch maps:map(fun(_, _) -> error(crash) end, #{ a => a }),
+ {'EXIT',_} = catch maps:map(not_a_fun, #{}),
+ {'EXIT',_} = catch maps:map(fun(_, _) -> ok end, not_a_map),
+
+ {'EXIT',_} = catch maps:merge(not_a_map, #{}),
+
+ #{} = maps:new(),
+
+ {'EXIT',_} = catch maps:put(key, value, not_a_map),
+
+ #{} = maps:remove(a, #{ a => a }),
+ {'EXIT',_} = catch maps:remove(gurka, not_a_map),
+
+ error = maps:take(key_not_present, #{}),
+ {'EXIT',_} = catch maps:take(key, not_a_map),
+
+ {'EXIT',_} = catch maps:to_list(not_a_map),
+
+ #{ a := ok } = maps:update_with(a, fun(_) -> ok end, #{ a => a }),
+ {'EXIT',_} = catch maps:update_with(a, fun(_) -> error(a) end, #{ a => a }),
+ {'EXIT',_} = catch maps:update_with(key_not_present, fun(_) -> ok end, #{}),
+ {'EXIT',_} = catch maps:update_with(key, not_a_fun, not_a_map),
+
+ [] = maps:values(#{}),
+ {'EXIT',_} = catch maps:values(not_a_map),
+
+ #{} = maps:with([key_not_present], #{}),
+ {'EXIT',_} = catch maps:with(not_a_list, #{}),
+ {'EXIT',_} = catch maps:with([], not_a_map),
+ {'EXIT',_} = catch maps:with([foobar], not_a_map),
+
+ {'EXIT',_} = catch maps:without(not_a_list, #{}),
+ {'EXIT',_} = catch maps:without([], not_a_map),
+
+ ok.
+
+%% The types for erlang:min/2 and erlang:max/2 were wrong, assuming that the
+%% result was a float if either argument was a float.
+min_max_mixed_types(_Config) ->
+ NotFloatA = min(id(12), 100.0),
+ id(NotFloatA * 0.5),
+
+ NotFloatB = max(id(12.0), 100),
+ id(NotFloatB * 0.5),
+
+ %% Cover more of the type analysis code.
+ 1 = id(min(id(0)+1, 42)),
+ -10 = id(min(id(0)+1, -10)),
+ 43 = id(max(3, id(42)+1)),
+ 42 = id(max(-99, id(41)+1)),
+
+ ok.
+
+%% GH-6183. beam_validator had a stronger type analysis for '=/=' and
+%% is_ne_exact than the beam_ssa_type pass. It would figure out that
+%% at the time the comparison 'a /= V' was evaluated, V must be equal
+%% to 'true' and the comparison would therefore always return 'false'.
+%% beam_validator would report that as a type conflict.
+
+not_equal(_Config) ->
+ true = do_not_equal(true),
+ {'EXIT',{function_clause,_}} = catch do_not_equal(false),
+ {'EXIT',{function_clause,_}} = catch do_not_equal(0),
+ {'EXIT',{function_clause,_}} = catch do_not_equal(42),
+ {'EXIT',{function_clause,_}} = catch do_not_equal(self()),
+
+ ok.
+
+do_not_equal(V) when (V / V < (V orelse true)); V; V ->
+ (V = (a /= V)) orelse 0.
+
+
+infer_relops(_Config) ->
+ {'EXIT',{badarith,_}} = catch infer_relops_1(),
+ {'EXIT',{badarith,_}} = catch infer_relops_2(),
+ {'EXIT',{badarith,_}} = catch infer_relops_3(id(0)),
+ infer_relops_4(),
+
+ ok.
+
+%% GH-6568: Type inference for relational operations returned erroneous results
+%% for singletons.
+infer_relops_1() ->
+ <<0 || ((0 rem 0) > [0 || _ <- catch (node())]), _ <- []>>.
+
+infer_relops_2() ->
+ X = self() + 0,
+ [0 || [0 || _ <- date()] =< X, _ <- []].
+
+infer_relops_3(X) ->
+ <<0 || ((+(is_alive())) <
+ [
+ infer_relops_3(infer_relops_3(0))
+ || X
+ ]) andalso ok
+ >>.
+
+infer_relops_4() ->
+ [
+ ok
+ || <<X>> <= <<>>,
+ <<Y:X>> <= <<>>,
+ 0 > Y,
+ [
+ Y
+ || _ <- []
+ ]
+ ].
+
+%% GH-6593: the type pass would correctly determine that the tail unit of
+%% <<0:integer/8,I:integer/8>> was 16, but would fail to see that after
+%% optimizing it to <<"\0",I:integer/8>>
+binary_unit(_Config) ->
+ F = id(binary_unit_1()),
+
+ <<0,1>> = F([1]),
+ <<0,0>> = F([0,1]),
+
+ ok.
+
+binary_unit_1() ->
+ fun Foo(X) ->
+ I = hd([Y || Y <- X, _ <- X, (Foo >= ok)]),
+ <<0, I>>
+ end.
+
+%% ERIERL-918: A call to a local function (in this case `pm_concretization_3`)
+%% forced the extracted type of `Tagged` to be concretized before we checked
+%% `Status`, passing an unknown type to `pm_concretization_4`.
+premature_concretization(_Config) ->
+ ok = pm_concretization_1(id(tagged), id({tagged, foo})),
+ error = pm_concretization_1(id(flurb), id({tagged, foo})),
+ ok.
+
+pm_concretization_1(Frobnitz, Tagged) ->
+ {Status, NewTagged} = pm_concretization_2(Frobnitz, Tagged),
+ pm_concretization_3(NewTagged),
+ case Status of
+ ok -> pm_concretization_4(NewTagged);
+ error -> error
+ end.
+
+pm_concretization_2(tagged, {tagged, _Nonsense}=T) -> {ok, T};
+pm_concretization_2(_, Tagged) -> {error, Tagged}.
+
+pm_concretization_3(_) -> ok.
+pm_concretization_4(_) -> ok.
+
+funs(_Config) ->
+ {'EXIT',{badarg,_}} = catch gh_7179(),
+ false = is_function(id(fun() -> ok end), 1024),
+
+ {'EXIT',{badarg,_}} = catch gh_7197(),
+
+ ok.
+
+%% GH-7179: The beam_ssa_type pass would crash.
+gh_7179() ->
+ << <<0>> || is_function([0 || <<_>> <= <<>>], -1),
+ [] <- [] >>.
+
+%% GH-7197: The beam_ssa_type pass would crash.
+gh_7197() ->
+ [0 || is_function([ok || <<_>> <= <<>>], get_keys()),
+ fun (_) ->
+ ok
+ end].
+
+
+%%%
+%%% Common utilities.
+%%%
+
id(I) ->
I.
diff --git a/lib/compiler/test/beam_types_SUITE.erl b/lib/compiler/test/beam_types_SUITE.erl
index b3325d2b9d..c93c349cd9 100644
--- a/lib/compiler/test/beam_types_SUITE.erl
+++ b/lib/compiler/test/beam_types_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -101,6 +101,16 @@ binary_absorption(Config) when is_list(Config) ->
A = beam_types:meet(A, beam_types:join(A, B)),
A = beam_types:join(A, beam_types:meet(A, B)),
+ %% Tests that the appendable flag behaves as intended.
+ C = #t_bitstring{size_unit=4,appendable=true},
+ D = #t_bitstring{size_unit=6},
+
+ #t_bitstring{size_unit=12,appendable=true} = beam_types:meet(C, D),
+ #t_bitstring{size_unit=2,appendable=false} = beam_types:join(C, D),
+
+ C = beam_types:meet(C, beam_types:join(C, D)),
+ C = beam_types:join(C, beam_types:meet(C, D)),
+
ok.
integer_absorption(Config) when is_list(Config) ->
diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl
index 47970f3021..2092d7401a 100644
--- a/lib/compiler/test/beam_validator_SUITE.erl
+++ b/lib/compiler/test/beam_validator_SUITE.erl
@@ -41,8 +41,9 @@
missing_return_type/1,will_succeed/1,
bs_saved_position_units/1,parent_container/1,
container_performance/1,
- not_equal_inference/1,
- inert_update_type/1]).
+ infer_relops/1,
+ not_equal_inference/1,bad_bin_unit/1,singleton_inference/1,
+ inert_update_type/1,range_inference/1]).
-include_lib("common_test/include/ct.hrl").
@@ -77,9 +78,9 @@ groups() ->
receive_marker,safe_instructions,
missing_return_type,will_succeed,
bs_saved_position_units,parent_container,
- container_performance,
- not_equal_inference,
- inert_update_type]}].
+ container_performance,infer_relops,
+ not_equal_inference,bad_bin_unit,singleton_inference,
+ inert_update_type,range_inference]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -183,9 +184,11 @@ call_without_stack(Config) when is_list(Config) ->
merge_undefined(Config) when is_list(Config) ->
Errors = do_val(merge_undefined, Config),
[{{t,undecided,2},
- {{call_ext,2,{extfunc,debug,filter,2}},
- 22,
- {allocated,undecided}}},
+ {{label,11},
+ 19,
+ {unsafe_stack,{y,1},
+ #{{y,0} := uninitialized,
+ {y,1} := uninitialized}}}},
{{t,uninitialized,2},
{{call_ext,2,{extfunc,io,format,2}},
17,
@@ -324,7 +327,7 @@ state_after_fault_in_catch(Config) when is_list(Config) ->
no_exception_in_catch(Config) when is_list(Config) ->
Errors = do_val(no_exception_in_catch, Config),
[{{no_exception_in_catch,nested_of_1,4},
- {{try_case_end,{x,0}},180,ambiguous_catch_try_state}}] = Errors,
+ {{try_case_end,{x,0}},166,ambiguous_catch_try_state}}] = Errors,
ok.
undef_label(Config) when is_list(Config) ->
@@ -522,13 +525,8 @@ destroy_reg({Tag,N}) ->
bad_tuples(Config) ->
Errors = do_val(bad_tuples, Config),
[{{bad_tuples,heap_overflow,1},
- {{put,{x,0}},9,{heap_overflow,{left,0},{wanted,1}}}},
- {{bad_tuples,long,2},
- {{put,{atom,too_long}},9,not_building_a_tuple}},
- {{bad_tuples,self_referential,1},
- {{put,{x,1}},8,{unfinished_tuple,{x,1}}}},
- {{bad_tuples,short,1},
- {{move,{x,1},{x,0}},8,{unfinished_tuple,{x,1}}}}] = Errors,
+ {{put_tuple2,{x,0},{list,[{atom,ok},{x,0}]}},6,
+ {heap_overflow,{left,2},{wanted,3}}}}] = Errors,
ok.
@@ -555,7 +553,7 @@ receive_stacked(Config) ->
{{test_heap,3,0},11,{fragile_message_reference,{y,_}}}},
{{receive_stacked,f5,0},
{{loop_rec_end,{f,23}},
- 24,
+ 22,
{fragile_message_reference,{y,_}}}},
{{receive_stacked,f6,0},
{{gc_bif,byte_size,{f,29},0,[{y,_}],{x,0}},
@@ -575,7 +573,7 @@ receive_stacked(Config) ->
{fragile_message_reference,{y,_}}}},
{{receive_stacked,m2,0},
{{loop_rec_end,{f,48}},
- 34,
+ 32,
{fragile_message_reference,{y,_}}}}] = Errors,
%% Compile the original source code as a smoke test.
@@ -1039,6 +1037,25 @@ container_performance(Config) ->
_ -> ok
end.
+%% Type inference was half-broken for relational operators, being implemented
+%% for is_lt/is_ge instructions but not the {bif,RelOp} form.
+infer_relops(_Config) ->
+ [lt = infer_relops_1(N) || N <- lists:seq(0,3)],
+ [ge = infer_relops_1(N) || N <- lists:seq(4,7)],
+ ok.
+
+infer_relops_1(N) ->
+ true = N >= 0,
+ Below4 = N < 4,
+ id(N), %% Force Below4 to use the {bif,'<'} form instead of is_lt
+ case Below4 of
+ true -> infer_relops_true(Below4, N);
+ false -> infer_relops_false(Below4, N)
+ end.
+
+infer_relops_true(_, _) -> lt.
+infer_relops_false(_, _) -> ge.
+
%% OTP-18365: A brainfart in inference for '=/=' inverted the results.
not_equal_inference(_Config) ->
{'EXIT', {function_clause, _}} = (catch not_equal_inference_1(id([0]))),
@@ -1047,6 +1064,58 @@ not_equal_inference(_Config) ->
not_equal_inference_1(X) when (X /= []) /= is_port(0 div 0) ->
[X || _ <- []].
+bad_bin_unit(_Config) ->
+ {'EXIT', {function_clause,_}} = catch bad_bin_unit_1(<<1:1>>),
+ [] = bad_bin_unit_2(),
+ ok.
+
+bad_bin_unit_1(<<X:((ok > {<<(true andalso ok)>>}) orelse 1)>>) ->
+ try
+ bad_bin_unit_1_a()
+ after
+ -(X + bad_bin_unit_1_b(not ok)),
+ try
+ ok
+ catch
+ _ ->
+ ok;
+ _ ->
+ ok;
+ _ ->
+ ok;
+ _ ->
+ ok;
+ _ ->
+ ok;
+ _ ->
+ ok
+ end
+ end.
+
+bad_bin_unit_1_a() -> ok.
+bad_bin_unit_1_b(_) -> ok.
+
+bad_bin_unit_2() ->
+ [
+ ok
+ || <<X:(is_number(<<(<<(0 bxor 0)>>)>>) orelse 1)>> <= <<>>,
+ #{X := _} <- ok
+ ].
+
+%% GH-6962: Type inference with singleton types in registers was weaker than
+%% inference on their corresponding literals.
+singleton_inference(Config) ->
+ Mod = ?FUNCTION_NAME,
+
+ Data = proplists:get_value(data_dir, Config),
+ File = filename:join(Data, "singleton_inference.erl"),
+
+ {ok, Mod} = compile:file(File, [no_copt, no_bool_opt, no_ssa_opt]),
+
+ ok = Mod:test(),
+
+ ok.
+
%% GH-6969: A type was made concrete even though that added no additional
%% information.
inert_update_type(_Config) ->
@@ -1060,5 +1129,22 @@ mike([Head | _Rest]) -> joe(Head).
joe({Name, 42}) -> Name;
joe({sys_period, {A, _B}}) -> {41, 42, A}.
+range_inference(_Config) ->
+ ok = range_inference_1(id(<<$a>>)),
+ ok = range_inference_1(id(<<0>>)),
+ ok = range_inference_1(id(<<1114111/utf8>>)),
+
+ ok.
+
+range_inference_1(<<X/utf8>>) ->
+ case 9223372036854775807 - abs(X) of
+ Y when X < Y ->
+ ok;
+ 9223372036854775807 ->
+ ok;
+ -2147483648 ->
+ ok
+ end.
+
id(I) ->
I.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/bad_tuples.S b/lib/compiler/test/beam_validator_SUITE_data/bad_tuples.S
index 7980241c37..81b52757b8 100644
--- a/lib/compiler/test/beam_validator_SUITE_data/bad_tuples.S
+++ b/lib/compiler/test/beam_validator_SUITE_data/bad_tuples.S
@@ -1,69 +1,20 @@
{module, bad_tuples}. %% version = 0
{exports, [{heap_overflow,1},
- {long,2},
{module_info,0},
- {module_info,1},
- {self_referential,1},
- {short,1}]}.
+ {module_info,1}]}.
{attributes, []}.
{labels, 13}.
-
-{function, short, 1, 2}.
- {label,1}.
- {line,[{location,"bad_tuples.erl",4}]}.
- {func_info,{atom,bad_tuples},{atom,short},1}.
- {label,2}.
- {test_heap,3,1}.
- {put_tuple,2,{x,1}}.
- {put,{atom,ok}}.
- {move,{x,1},{x,0}}.
- return.
-
-
-{function, long, 2, 4}.
- {label,3}.
- {line,[{location,"bad_tuples.erl",7}]}.
- {func_info,{atom,bad_tuples},{atom,long},2}.
- {label,4}.
- {test_heap,6,2}.
- {put_tuple,2,{x,2}}.
- {put,{x,0}}.
- {put,{x,1}}.
- {put,{atom,too_long}}.
- {put_tuple,2,{x,0}}.
- {put,{atom,ok}}.
- {put,{x,2}}.
- return.
-
-
{function, heap_overflow, 1, 6}.
{label,5}.
{line,[{location,"bad_tuples.erl",10}]}.
{func_info,{atom,bad_tuples},{atom,heap_overflow},1}.
{label,6}.
- {test_heap,3,1}.
- {put_tuple,2,{x,1}}.
- {put,{atom,ok}}.
- {put,{x,0}}.
- {put,{x,0}}.
- {move,{x,1},{x,0}}.
- return.
-
-
-{function, self_referential, 1, 8}.
- {label,7}.
- {line,[{location,"bad_tuples.erl",13}]}.
- {func_info,{atom,bad_tuples},{atom,self_referential},1}.
- {label,8}.
- {test_heap,3,1}.
- {put_tuple,2,{x,1}}.
- {put,{atom,ok}}.
- {put,{x,1}}.
- {move,{x,1},{x,0}}.
+ {test_heap,2,1}.
+ {put_tuple2,{x,0},{list,[{atom,ok},{x,0}]}}.
return.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/no_exception_in_catch.S b/lib/compiler/test/beam_validator_SUITE_data/no_exception_in_catch.S
index 1a5b417a5f..b6304d00cc 100644
--- a/lib/compiler/test/beam_validator_SUITE_data/no_exception_in_catch.S
+++ b/lib/compiler/test/beam_validator_SUITE_data/no_exception_in_catch.S
@@ -47,9 +47,7 @@
{move,{y,12},{x,0}}.
{call,1,{f,17}}.
{test_heap,3,1}.
- {put_tuple,2,{x,1}}.
- {put,{atom,value1}}.
- {put,{x,0}}.
+ {put_tuple2,{x,1},{list,[{atom,value1},{x,0}]}}.
{move,{x,1},{x,0}}.
{jump,{f,5}}.
{label,3}.
@@ -69,9 +67,7 @@
{move,{y,12},{x,0}}.
{call,1,{f,17}}.
{test_heap,3,1}.
- {put_tuple,2,{x,1}}.
- {put,{atom,caught1}}.
- {put,{x,0}}.
+ {put_tuple2,{x,1},{list,[{atom,caught1},{x,0}]}}.
{move,{x,1},{x,0}}.
{jump,{f,5}}.
{label,4}.
@@ -142,20 +138,14 @@
{label,10}.
{try_end,{y,7}}.
{test_heap,3,1}.
- {put_tuple,2,{x,1}}.
- {put,{atom,value}}.
- {put,{x,0}}.
+ {put_tuple2,{x,1},{list,[{atom,value},{x,0}]}}.
{move,{x,1},{x,0}}.
{jump,{f,12}}.
{label,11}.
{try_case,{y,7}}.
{test_heap,6,3}.
- {put_tuple,2,{x,3}}.
- {put,{x,0}}.
- {put,{x,1}}.
- {put_tuple,2,{x,0}}.
- {put,{atom,caught}}.
- {put,{x,3}}.
+ {put_tuple2,{x,3},{list,[{x,0},{x,1}]}}.
+ {put_tuple2,{x,0},{list,[{atom,caught},{x,3}]}}.
{'%live',1}.
{label,12}.
{try_end,{y,8}}.
@@ -189,11 +179,7 @@
{move,{atom,nested},{x,0}}.
{call_ext,1,{extfunc,erlang,erase,1}}.
{test_heap,5,1}.
- {put_tuple,4,{x,1}}.
- {put,{y,14}}.
- {put,{y,13}}.
- {put,{y,12}}.
- {put,{x,0}}.
+ {put_tuple2,{x,1},{list,[{y,14},{y,13},{y,12},{x,0}]}}.
{move,{x,1},{x,0}}.
{deallocate,15}.
return.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S b/lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S
index a878204d16..d4597f088e 100644
--- a/lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S
+++ b/lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S
@@ -55,16 +55,12 @@
{test,is_nil,{f,9},[{x,0}]}.
{test_heap,3,0}.
remove_message.
- {put_tuple,2,{y,0}}.
- {put,{atom,ok}}.
- {put,{y,1}}.
+ {put_tuple2,{y,0},{list,[{atom,ok},{y,1}]}}.
{move,{integer,42},{x,0}}.
{line,[{location,"receive_stacked.erl",26}]}.
{call,1,{f,52}}.
{test_heap,3,0}.
- {put_tuple,2,{x,0}}.
- {put,{y,0}}.
- {put,{y,1}}.
+ {put_tuple2,{x,0},{list,[{y,0},{y,1}]}}.
{deallocate,2}.
return.
{label,9}.
@@ -86,16 +82,12 @@
{test,is_integer,{f,14},[{y,1}]}.
{test_heap,3,0}.
remove_message.
- {put_tuple,2,{y,0}}.
- {put,{atom,ok}}.
- {put,{y,1}}.
+ {put_tuple2,{y,0},{list,[{atom,ok},{y,1}]}}.
{move,{integer,42},{x,0}}.
{line,[{location,"receive_stacked.erl",34}]}.
{call,1,{f,52}}.
{test_heap,3,0}.
- {put_tuple,2,{x,0}}.
- {put,{y,0}}.
- {put,{y,1}}.
+ {put_tuple2,{x,0},{list,[{y,0},{y,1}]}}.
{deallocate,2}.
return.
{label,14}.
@@ -117,16 +109,12 @@
{test,is_list,{f,19},[{y,1}]}.
{test_heap,3,0}.
remove_message.
- {put_tuple,2,{y,0}}.
- {put,{atom,ok}}.
- {put,{y,1}}.
+ {put_tuple2,{y,0},{list,[{atom,ok},{y,1}]}}.
{move,{integer,42},{x,0}}.
{line,[{location,"receive_stacked.erl",42}]}.
{call,1,{f,52}}.
{test_heap,3,0}.
- {put_tuple,2,{x,0}}.
- {put,{y,0}}.
- {put,{y,1}}.
+ {put_tuple2,{x,0},{list,[{y,0},{y,1}]}}.
{deallocate,2}.
return.
{label,19}.
@@ -153,9 +141,7 @@
{line,[{location,"receive_stacked.erl",50}]}.
{call,1,{f,52}}.
{test_heap,3,0}.
- {put_tuple,2,{x,0}}.
- {put,{y,0}}.
- {put,{y,1}}.
+ {put_tuple2,{x,0},{list,[{y,0},{y,1}]}}.
{deallocate,2}.
return.
{label,24}.
@@ -350,9 +336,7 @@
{line,[{location,"receive_stacked.erl",87}]}.
{call,1,{f,52}}.
{test_heap,3,0}.
- {put_tuple,2,{x,0}}.
- {put,{y,1}}.
- {put,{y,0}}.
+ {put_tuple2,{x,0},{list,[{y,1},{y,0}]}}.
{deallocate,4}.
return.
{label,49}.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/singleton_inference.erl b/lib/compiler/test/beam_validator_SUITE_data/singleton_inference.erl
new file mode 100644
index 0000000000..66f0bb312a
--- /dev/null
+++ b/lib/compiler/test/beam_validator_SUITE_data/singleton_inference.erl
@@ -0,0 +1,7 @@
+-module(singleton_inference).
+-export([test/0]).
+
+test() ->
+ {'EXIT',{{badmatch,true}, _}} =
+ catch [0 || (X = (true or (X = is_port(node()))))],
+ ok.
diff --git a/lib/compiler/test/bif_SUITE.erl b/lib/compiler/test/bif_SUITE.erl
index fc1d9ddfc0..e58db29114 100644
--- a/lib/compiler/test/bif_SUITE.erl
+++ b/lib/compiler/test/bif_SUITE.erl
@@ -25,7 +25,8 @@
init_per_group/2,end_per_group/2,
beam_validator/1,trunc_and_friends/1,cover_safe_and_pure_bifs/1,
cover_trim/1,
- head_tail/1]).
+ head_tail/1,
+ min_max/1]).
suite() ->
[{ct_hooks,[ts_install_cth]}].
@@ -34,12 +35,13 @@ all() ->
[{group,p}].
groups() ->
- [{p,[parallel],
+ [{p,test_lib:parallel(),
[beam_validator,
trunc_and_friends,
cover_safe_and_pure_bifs,
cover_trim,
- head_tail
+ head_tail,
+ min_max
]}].
init_per_suite(Config) ->
@@ -192,5 +194,40 @@ tail_case() ->
X -> {X, ok}
end.
+min_max(_Config) ->
+ False = id(false),
+ True = id(true),
+
+ false = bool_min_false(False, False),
+ false = bool_min_false(False, True),
+ false = bool_min_false(True, False),
+ true = bool_min_true(True, True),
+
+ false = bool_max_false(False, False),
+ true = bool_max_true(False, True),
+ true = bool_max_true(True, False),
+ true = bool_max_true(True, True),
+
+ ok.
+
+%% GH-7170: The following functions would cause a crash in
+%% beam_ssa_codegen.
+
+bool_min_false(A, B) when is_boolean(A), is_boolean(B) ->
+ false = min(A, B).
+
+bool_min_true(A, B) when is_boolean(A), is_boolean(B) ->
+ true = min(A, B).
+
+bool_max_false(A, B) when is_boolean(A), is_boolean(B) ->
+ false = max(A, B).
+
+bool_max_true(A, B) when is_boolean(A), is_boolean(B) ->
+ true = max(A, B).
+
+%%%
+%%% Common utilities.
+%%%
+
id(I) ->
I.
diff --git a/lib/compiler/test/bs_bincomp_SUITE.erl b/lib/compiler/test/bs_bincomp_SUITE.erl
index c3324b64dc..4e7adcb7c5 100644
--- a/lib/compiler/test/bs_bincomp_SUITE.erl
+++ b/lib/compiler/test/bs_bincomp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -24,22 +24,26 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
+ verify_highest_opcode/1,
byte_aligned/1,bit_aligned/1,extended_byte_aligned/1,
extended_bit_aligned/1,mixed/1,filters/1,trim_coverage/1,
nomatch/1,sizes/1,general_expressions/1,
- no_generator/1,zero_pattern/1,multiple_segments/1]).
+ no_generator/1,zero_pattern/1,multiple_segments/1,
+ grab_bag/1]).
-include_lib("common_test/include/ct.hrl").
suite() -> [{ct_hooks,[ts_install_cth]}].
-all() ->
- [byte_aligned, bit_aligned, extended_byte_aligned,
+all() ->
+ [verify_highest_opcode,
+ byte_aligned, bit_aligned, extended_byte_aligned,
extended_bit_aligned, mixed, filters, trim_coverage,
nomatch, sizes, general_expressions,
- no_generator, zero_pattern, multiple_segments].
+ no_generator, zero_pattern, multiple_segments,
+ grab_bag].
-groups() ->
+groups() ->
[].
init_per_suite(Config) ->
@@ -55,6 +59,28 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
+verify_highest_opcode(_Config) ->
+ case ?MODULE of
+ bs_construct_r24_SUITE ->
+ {ok,Beam} = file:read_file(code:which(?MODULE)),
+ case test_lib:highest_opcode(Beam) of
+ Highest when Highest =< 176 ->
+ ok;
+ TooHigh ->
+ ct:fail({too_high_opcode,TooHigh})
+ end;
+ bs_construct_r25_SUITE ->
+ {ok,Beam} = file:read_file(code:which(?MODULE)),
+ case test_lib:highest_opcode(Beam) of
+ Highest when Highest =< 180 ->
+ ok;
+ TooHigh ->
+ ct:fail({too_high_opcode,TooHigh})
+ end;
+ _ ->
+ ok
+ end.
+
byte_aligned(Config) when is_list(Config) ->
cs_init(),
<<"abcdefg">> = cs(<< <<(X+32)>> || <<X>> <= <<"ABCDEFG">> >>),
@@ -149,6 +175,13 @@ mixed(Config) when is_list(Config) ->
<<255>> = over_complex_generator(),
{'EXIT',_} = catch float_segment_size(),
+ <<>> = inconsistent_types_1([]),
+ {'EXIT',{{bad_generator,42},_}} = catch inconsistent_types_1(42),
+ Self = self(),
+ {'EXIT',{{bad_generator,Self},_}} = catch inconsistent_types_1(Self),
+
+ {'EXIT',{{bad_filter,<<>>},_}} = catch inconsistent_types_2(),
+
cs_end().
mixed_nested(L) ->
@@ -220,6 +253,34 @@ float_segment_size() ->
error
end.
+%% GH-6468. Would crash in beam_ssa_bc_size:update_successors/3.
+inconsistent_types_1(X) ->
+ <<
+ X || _ <- X,
+ case is_pid(X) of
+ Y ->
+ (#{
+ ((not Y) andalso
+ <<Y:(Y andalso X)>>) := Y
+ } = X);
+ _ ->
+ false
+ end
+ >>.
+
+%% GH-6468. Same type of crash.
+inconsistent_types_2() ->
+ <<
+ 0 || case id([]) of
+ Y ->
+ <<
+ Y ||
+ _ <- Y,
+ (not ((false = Y) = (Y /= []))), (_ = Y)
+ >>
+ end
+ >>.
+
filters(Config) when is_list(Config) ->
cs_init(),
<<"BDF">> =
@@ -588,6 +649,20 @@ do_multiple_segments_2(Gen) ->
Bin = list_to_binary(List),
List.
+grab_bag(_Config) ->
+ {'EXIT',{function_clause,_}} = catch grab_bag_gh_6553(<<>>),
+ {'EXIT',{function_clause,_}} = catch grab_bag_gh_6553(a),
+ {'EXIT',{{badmatch,<<>>},_}} = catch grab_bag_gh_6553(<<42>>),
+
+ %% Cover a line v3_kernel:get_line/1.
+ _ = catch << ok || <<>> <= ok, ok >>,
+
+ ok.
+
+grab_bag_gh_6553(<<X>>) ->
+ %% Would crash in beam_ssa_pre_codegen.
+ <<X, ((<<_:0>> = <<_>>) = <<>>)>>.
+
cs_init() ->
erts_debug:set_internal_state(available_internal_state, true),
ok.
@@ -603,6 +678,10 @@ cs(Bin) ->
ok;
bs_bincomp_no_ssa_opt_SUITE ->
ok;
+ bs_bincomp_no_copt_SUITE ->
+ ok;
+ bs_bincomp_no_copt_ssa_SUITE ->
+ ok;
bs_bincomp_post_opt_SUITE ->
ok;
_ ->
diff --git a/lib/compiler/test/bs_construct_SUITE.erl b/lib/compiler/test/bs_construct_SUITE.erl
index c681f3c21d..bc6ed43dad 100644
--- a/lib/compiler/test/bs_construct_SUITE.erl
+++ b/lib/compiler/test/bs_construct_SUITE.erl
@@ -31,7 +31,7 @@
two/1,test1/1,fail/1,float_bin/1,in_guard/1,in_catch/1,
nasty_literals/1,coerce_to_float/1,side_effect/1,
opt/1,otp_7556/1,float_arith/1,otp_8054/1,
- strings/1,bad_size/1]).
+ strings/1,bad_size/1,private_append/1]).
-include_lib("common_test/include/ct.hrl").
@@ -47,7 +47,7 @@ groups() ->
[verify_highest_opcode,
two,test1,fail,float_bin,in_guard,in_catch,
nasty_literals,side_effect,opt,otp_7556,float_arith,
- otp_8054,strings,bad_size]}].
+ otp_8054,strings,bad_size,private_append]}].
init_per_suite(Config) ->
@@ -78,7 +78,15 @@ verify_highest_opcode(_Config) ->
Highest when Highest =< 176 ->
ok;
TooHigh ->
- ct:fail({too_high_opcode_for_21,TooHigh})
+ ct:fail({too_high_opcode,TooHigh})
+ end;
+ bs_construct_r25_SUITE ->
+ {ok,Beam} = file:read_file(code:which(?MODULE)),
+ case test_lib:highest_opcode(Beam) of
+ Highest when Highest =< 180 ->
+ ok;
+ TooHigh ->
+ ct:fail({too_high_opcode,TooHigh})
end;
_ ->
ok
@@ -514,6 +522,11 @@ nasty_literals(Config) when is_list(Config) ->
I = 16#7777FFFF7777FFFF7777FFFF7777FFFF7777FFFF7777FFFF,
id(<<I:260>>),
+ %% GH-6643: Excessively large literals could cause the compiler to run out
+ %% of memory.
+ catch id(<<0:16777216/big-integer-unit:1>>),
+ catch id(<<0:(16777216*2)/big-integer-unit:1>>),
+
ok.
-define(COF(Int0),
@@ -741,3 +754,26 @@ bad_binary_size2() ->
bad_binary_size3(Bin) ->
<<Bin:all/binary>>.
+
+private_append(_Config) ->
+ <<"alpha=\"alpha\",beta=\"beta\"">> =
+ private_append_1(#{ <<"alpha">> => <<"alpha">>,
+ <<"beta">> => <<"beta">> }),
+
+ <<>> = private_append_2(false),
+ {'EXIT', _} = catch private_append_2(true),
+
+ ok.
+
+%% GH-7121: Alias analysis would not mark fun arguments as aliased, fooling
+%% the beam_ssa_private_append pass.
+private_append_1(M) when is_map(M) ->
+ maps:fold(fun (K, V, Acc = <<>>) ->
+ <<Acc/binary, K/binary, "=\"", V/binary, "\"">>;
+ (K, V, Acc) ->
+ <<Acc/binary, ",", K/binary, "=\"", V/binary, "\"">>
+ end, <<>>, M).
+
+%% GH-7142: The private append pass crashed on oddly structured code.
+private_append_2(Boolean) ->
+ <<<<(id(Boolean) orelse <<>>)/binary>>/binary>>.
diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl
index b477109f45..1c379b70cf 100644
--- a/lib/compiler/test/bs_match_SUITE.erl
+++ b/lib/compiler/test/bs_match_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -49,7 +49,9 @@
bad_phi_paths/1,many_clauses/1,
combine_empty_segments/1,hangs_forever/1,
bs_saved_position_units/1,empty_matches/1,
- trim_bs_start_match_resume/1]).
+ trim_bs_start_match_resume/1,
+ gh_6410/1,bs_match/1,
+ binary_aliases/1,gh_6923/1]).
-export([coverage_id/1,coverage_external_ignore/2]).
@@ -89,7 +91,9 @@ groups() ->
exceptions_after_match_failure,bad_phi_paths,
many_clauses,combine_empty_segments,hangs_forever,
bs_saved_position_units,empty_matches,
- trim_bs_start_match_resume]}].
+ trim_bs_start_match_resume,
+ gh_6410,bs_match,binary_aliases,
+ gh_6923]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -113,10 +117,10 @@ end_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
verify_highest_opcode(_Config) ->
case ?MODULE of
- bs_match_r21_SUITE ->
+ bs_match_r25_SUITE ->
{ok,Beam} = file:read_file(code:which(?MODULE)),
case test_lib:highest_opcode(Beam) of
- Highest when Highest =< 163 ->
+ Highest when Highest =< 180 ->
ok;
TooHigh ->
ct:fail({too_high_opcode_for_21,TooHigh})
@@ -856,6 +860,12 @@ coverage(Config) when is_list(Config) ->
%% Cover code in beam_ssa_codegen.
ok = coverage_beam_ssa_codegen(<<2>>),
+ %% Cover code in beam_ssa_pre_codegen.
+ {'EXIT',{function_clause,_}} = catch coverage_beam_ssa_pre_codegen(<<>>),
+
+ %% Cover code in beam_ssa_bsm.
+ {'EXIT',{{badarg,<<>>},_}} = catch coverage_beam_ssa_bsm_error(id(<<>>)),
+
ok.
coverage_fold(Fun, Acc, <<H,T/binary>>) ->
@@ -1006,6 +1016,12 @@ coverage_beam_ssa_codegen(Bin) ->
end,
ok.
+coverage_beam_ssa_pre_codegen(<<V0:0, V1:(V0 div V0), _:(V0 bsl V1)/bits>>) ->
+ ok.
+
+coverage_beam_ssa_bsm_error(<<B/bitstring>>) ->
+ B andalso ok.
+
multiple_uses(Config) when is_list(Config) ->
{344,62879,345,<<245,159,1,89>>} = multiple_uses_1(<<1,88,245,159,1,89>>),
true = multiple_uses_2(<<0,0,197,18>>),
@@ -1406,6 +1422,15 @@ bad_size(Config) when is_list(Config) ->
true = bad_size_1(<<0>>),
error = bad_size_1(<<0,1>>),
+ [] = bad_size_2([a]),
+ [] = bad_size_2([<<1,2,3>>]),
+ {'EXIT',{{bad_generator,no_list},_}} = catch bad_size_2(no_list),
+
+ error = bad_size_3(<<>>),
+ error = bad_size_3(<<0>>),
+
+ <<>> = id(<<_V1 || <<_V0/float, _V1:_V0>> <= <<>>>>),
+
ok.
bad_all_size(Bin) ->
@@ -1465,6 +1490,19 @@ bad_size_1(<<0>>) -> true;
bad_size_1(<<0:[]>>) -> false;
bad_size_1(_) -> error.
+-record(rec_bad_size_2, {a}).
+
+bad_size_2(L) ->
+ [
+ ok ||
+ <<_:(bad#rec_bad_size_2.a)/float>> <- L
+ ].
+
+bad_size_3(<<0:((bnot (1 div 0)))>>) ->
+ ok;
+bad_size_3(_) ->
+ error.
+
haystack(Config) when is_list(Config) ->
<<0:10/unit:8>> = haystack_1(<<0:10/unit:8>>),
[<<0:10/unit:8>>,
@@ -2491,143 +2529,95 @@ trim_bs_start_match_resume_1(<<Context/binary>>) ->
_ = id(Context),
Context.
-id(I) -> I.
-
expand_and_squeeze(Config) when is_list(Config) ->
%% UTF8 literals are expanded and then squeezed into integer16
- [
- {test,bs_get_integer2,_,_,[_,{integer,16}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<$á/utf8,_/binary>>"),
- ?Q("<<$é/utf8,_/binary>>")
- ]),
+ ensure_squeezed(16, [?Q("<<$á/utf8,_/binary>>"),
+ ?Q("<<$é/utf8,_/binary>>")]),
%% Sized integers are expanded and then squeezed into integer16
- [
- {test,bs_get_integer2,_,_,[_,{integer,16}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<0:32,_/binary>>"),
- ?Q("<<\"bbbb\",_/binary>>")
- ]),
+ ensure_squeezed(16, [?Q("<<0:32,_/binary>>"),
+ ?Q("<<\"bbbb\",_/binary>>")]),
%% Groups of 8 bits are squeezed into integer16
- [
- {test,bs_get_integer2,_,_,[_,{integer,16}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<\"aaaa\",_/binary>>"),
- ?Q("<<\"bbbb\",_/binary>>")
- ]),
+ ensure_squeezed(16, [?Q("<<\"aaaa\",_/binary>>"),
+ ?Q("<<\"bbbb\",_/binary>>")]),
%% Groups of 8 bits with empty binary are also squeezed
- [
- {test,bs_get_integer2,_,_,[_,{integer,16}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<\"aaaa\",_/binary>>"),
- ?Q("<<\"bbbb\",_/binary>>"),
- ?Q("<<>>")
- ]),
+ ensure_squeezed(16, [?Q("<<\"aaaa\",_/binary>>"),
+ ?Q("<<\"bbbb\",_/binary>>"),
+ ?Q("<<>>")]),
%% Groups of 8 bits with float lookup are not squeezed
- [
- {test,bs_get_integer2,_,_,[_,{integer,8}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<\"aaaa\",_/binary>>"),
- ?Q("<<\"bbbb\",_/binary>>"),
- ?Q("<<_/float>>")
- ]),
+ ensure_squeezed(8, [?Q("<<\"aaaa\",_/binary>>"),
+ ?Q("<<\"bbbb\",_/binary>>"),
+ ?Q("<<_/float>>")]),
%% Groups of diverse bits go with minimum possible
- [
- {test,bs_get_integer2,_,_,[_,{integer,8}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<\"aa\",_/binary>>"),
- ?Q("<<\"bb\",_/binary>>"),
- ?Q("<<\"c\",_/binary>>")
- ]),
+ ensure_squeezed(8, [?Q("<<\"aa\",_/binary>>"),
+ ?Q("<<\"bb\",_/binary>>"),
+ ?Q("<<\"c\",_/binary>>")]),
%% Groups of diverse bits go with minimum possible but are recursive...
- [
- {test,bs_get_integer2,_,_,[_,{integer,8}|_],_}
- | RestDiverse
- ] = binary_match_to_asm([
- ?Q("<<\"aaa\",_/binary>>"),
- ?Q("<<\"abb\",_/binary>>"),
- ?Q("<<\"c\",_/binary>>")
- ]),
-
- %% so we still perform a 16 bits lookup for the remaining
- true = lists:any(fun({test,bs_get_integer2,_,_,[_,{integer,16}|_],_}) -> true;
- (_) -> false end, RestDiverse),
+ [{bs_match,{f,_},_Ctx,
+ {commands,[{ensure_at_least,Size,1},
+ {integer,_Live,_Flags,Size,1,_Dst}]}} | RestDiverse] =
+ binary_match_to_asm([?Q("<<\"aaa\",_/binary>>"),
+ ?Q("<<\"abb\",_/binary>>"),
+ ?Q("<<\"c\",_/binary>>")]),
+
+ %% ... so we still perform a 16 bits lookup for the remaining
+ F = fun({bs_match,{f,_},_,
+ {commands,[{ensure_at_least,16,1},
+ {integer,_Live,_Flags,16,1,_Dst}]}}) ->
+ true;
+ (_) -> false
+ end,
+ true = lists:any(F, RestDiverse),
%% Large match is kept as is if there is a sized match later
- [
- {test,bs_get_integer2,_,_,[_,{integer,64}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<255,255,255,255,255,255,255,255>>"),
- ?Q("<<_:64>>")
- ]),
+ ensure_squeezed(64, [?Q("<<255,255,255,255,255,255,255,255>>"),
+ ?Q("<<_:64>>")]),
%% Large match is kept as is with large matches before and after
- [
- {test,bs_get_integer2,_,_,[_,{integer,32}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<A:32,_:A>>"),
- ?Q("<<0:32>>"),
- ?Q("<<_:32>>")
- ]),
+ ensure_squeezed(32, [?Q("<<A:32,_:A>>"),
+ ?Q("<<0:32>>"),
+ ?Q("<<_:32>>")]),
%% Large match is kept as is with large matches before and after
- [
- {test,bs_get_integer2,_,_,[_,{integer,32}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<A:32,_:A>>"),
- ?Q("<<0,0,0,0>>"),
- ?Q("<<_:32>>")
- ]),
+ ensure_squeezed(32, [?Q("<<A:32,_:A>>"),
+ ?Q("<<0,0,0,0>>"),
+ ?Q("<<_:32>>")]),
%% Large match is kept as is with smaller but still large matches before and after
- [
- {test,bs_get_integer2,_,_,[_,{integer,32}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<A:32, _:A>>"),
- ?Q("<<0:64>>"),
- ?Q("<<_:32>>")
- ]),
+ ensure_squeezed(32, [?Q("<<A:32, _:A>>"),
+ ?Q("<<0:64>>"),
+ ?Q("<<_:32>>")]),
%% There is no squeezing for groups with more than 16 matches
- [
- {test,bs_get_integer2,_,_,[_,{integer,8}|_],_}
- | _
- ] = binary_match_to_asm([
- ?Q("<<\"aa\", _/binary>>"),
- ?Q("<<\"bb\", _/binary>>"),
- ?Q("<<\"cc\", _/binary>>"),
- ?Q("<<\"dd\", _/binary>>"),
- ?Q("<<\"ee\", _/binary>>"),
- ?Q("<<\"ff\", _/binary>>"),
- ?Q("<<\"gg\", _/binary>>"),
- ?Q("<<\"hh\", _/binary>>"),
- ?Q("<<\"ii\", _/binary>>"),
- ?Q("<<\"jj\", _/binary>>"),
- ?Q("<<\"kk\", _/binary>>"),
- ?Q("<<\"ll\", _/binary>>"),
- ?Q("<<\"mm\", _/binary>>"),
- ?Q("<<\"nn\", _/binary>>"),
- ?Q("<<\"oo\", _/binary>>"),
- ?Q("<<\"pp\", _/binary>>")
- ]),
-
- ok.
+ ensure_squeezed(8, [?Q("<<\"aa\", _/binary>>"),
+ ?Q("<<\"bb\", _/binary>>"),
+ ?Q("<<\"cc\", _/binary>>"),
+ ?Q("<<\"dd\", _/binary>>"),
+ ?Q("<<\"ee\", _/binary>>"),
+ ?Q("<<\"ff\", _/binary>>"),
+ ?Q("<<\"gg\", _/binary>>"),
+ ?Q("<<\"hh\", _/binary>>"),
+ ?Q("<<\"ii\", _/binary>>"),
+ ?Q("<<\"jj\", _/binary>>"),
+ ?Q("<<\"kk\", _/binary>>"),
+ ?Q("<<\"ll\", _/binary>>"),
+ ?Q("<<\"mm\", _/binary>>"),
+ ?Q("<<\"nn\", _/binary>>"),
+ ?Q("<<\"oo\", _/binary>>"),
+ ?Q("<<\"pp\", _/binary>>")]),
+
+ ok.
+
+ensure_squeezed(ExpectedSize, Fields) ->
+ [{bs_match,{f,_},_,
+ {commands,[{ensure_at_least,ExpectedSize,1},
+ {integer,_Live,_Flags,ExpectedSize,1,_Dst}]}} | _] =
+ binary_match_to_asm(Fields).
binary_match_to_asm(Matches) ->
Clauses = [
@@ -2676,3 +2666,517 @@ many_clauses(_Config) ->
one_clause(I) ->
?Q(<<"{_@I@,<<L:8,Val:L>>} -> _@I@ + Val">>).
+
+%% GH-6410: Fix crash in beam_ssa_bsm.
+gh_6410(_Config) ->
+ 0 = do_gh_6410(<<42>>),
+ {'EXIT',{{case_clause,<<>>},[_|_]}} = catch do_gh_6410(<<>>),
+ {'EXIT',{{case_clause,a},[_|_]}} = catch do_gh_6410(a),
+ {'EXIT',{badarith,[_|_]}} = catch do_gh_6410([]),
+
+ ok.
+
+do_gh_6410(<<_>>) ->
+ 0;
+do_gh_6410(X) ->
+ +(case X of
+ <<_>> ->
+ X;
+ [] ->
+ X
+ end).
+
+bs_match(_Config) ->
+ <<1,0>> = do_bs_match_1(whatever, <<1,0>>),
+ <<1,1>> = do_bs_match_1(whatever, <<1,1>>),
+ {a,b,c} = do_bs_match_1(whatever, {a,b,c}),
+
+ {'EXIT',{badarg,_}} = catch do_bs_match_gh_6551a(<<>>),
+ false = do_bs_match_gh_6551a(<<42>>),
+
+ {0,0} = do_bs_match_gh_6551b(0),
+ {<<42>>,<<42>>} = do_bs_match_gh_6551b(<<42>>),
+
+ ok = do_bs_match_gh_6613(<<0>>),
+ <<0,0>> = do_bs_match_gh_6613(<<0,0>>),
+
+ <<"abc">> = do_bs_match_gh_6660(id(<<"abc">>)),
+ {'EXIT', {{try_clause,abc},_}} = catch do_bs_match_gh_6660(id(abc)),
+
+ {'EXIT',{{case_clause,_},_}} = catch do_bs_match_gh_6755(id(<<"1000">>)),
+
+ ok.
+
+do_bs_match_1(_, X) ->
+ case X of
+ <<_, 0>> ->
+ id(42);
+ _ ->
+ true
+ end,
+ X.
+
+do_bs_match_gh_6551a(X) ->
+ case X of
+ <<>> ->
+ true -- [];
+ <<_>> ->
+ X
+ end /= X.
+
+
+do_bs_match_gh_6551b(X) ->
+ {X,
+ case X of
+ 0 ->
+ 0;
+ <<_>> ->
+ X
+ end}.
+
+do_bs_match_gh_6613(<<_>>) ->
+ ok;
+do_bs_match_gh_6613(X) ->
+ try
+ try X of
+ #{(not ok) := _} ->
+ ok;
+ <<Z, Z>> ->
+ ok
+ after
+ ok
+ end
+ catch
+ _ ->
+ ok
+ end,
+ X.
+
+do_bs_match_gh_6660(X) ->
+ try X of
+ <<Y/bytes>> ->
+ %% The `bs_extract` instruction was sunk to after the
+ %% `kill_try_tag` instruction, preventing
+ %% beam_ssa_pre_codegen from combining it with the
+ %% preceding bs_match instruction.
+ Y
+ after
+ ok
+ end.
+
+do_bs_match_gh_6755(B) ->
+ C = case B of
+ <<"1000">> -> test;
+ <<"1001">> -> test2
+ end,
+
+ _ = atom_to_list(C),
+
+ case B of
+ <<"b">> -> b
+ end.
+
+%% GH-6348/OTP-18297: Allow aliases for binaries.
+-record(ba_foo, {a,b,c}).
+
+binary_aliases(_Config) ->
+ F1 = fun(<<A:8>> = <<B:8>>) -> {A,B} end,
+ {42,42} = F1(id(<<42>>)),
+ {99,99} = F1(id(<<99>>)),
+
+ F2 = fun(#ba_foo{a = <<X:8>>} = #ba_foo{a = <<Y:8>>}) -> {X,Y} end,
+ {255,255} = F2(id(#ba_foo{a = <<-1>>})),
+ {107,107} = F2(id(#ba_foo{a = <<107>>})),
+
+ F3 = fun(#ba_foo{a = <<X:8>>} = #ba_foo{a = <<Y:4,Z:4>>}) -> {X,Y,Z} end,
+ {255,15,15} = F3(id(#ba_foo{a = <<-1>>})),
+ {16#5c,16#5,16#c} = F3(id(#ba_foo{a = <<16#5c>>})),
+
+ F4 = fun([<<A:8>> = {C,D} = <<B:8>>]) ->
+ {A,B,C,D};
+ (L) ->
+ lists:sum(L)
+ end,
+ 6 = F4(id([1,2,3])),
+
+ F5 = fun(Val) ->
+ <<A:8>> = X = <<B:8>> = Val,
+ {A,B,X}
+ end,
+ {42,42,<<42>>} = F5(id(<<42>>)),
+
+ F6 = fun(X, Y) ->
+ <<A:8>> = <<X:4,Y:4>>,
+ A
+ end,
+ 16#7c = F6(16#7, 16#c),
+ 16#ed = F6(16#e, 16#d),
+
+ F7 = fun(Val) ->
+ (<<A:8>> = X) = (<<B:8>> = <<A:4,B:4>>) = Val,
+ {A,B,X}
+ end,
+ {0,0,<<0>>} = F7(id(<<0>>)),
+ {'EXIT',{{badmatch,<<1>>},_}} = catch F7(<<1>>),
+
+ F8 = fun(Val) ->
+ (<<A:8>> = X) = (Y = <<B:8>>) = Val,
+ {A,B,X,Y}
+ end,
+ {253,253,<<253>>,<<253>>} = F8(id(<<253>>)),
+
+ F9 = fun(Val) ->
+ (Z = <<A:8>> = X) = (Y = <<B:8>> = W) = Val,
+ {A,B,X,Y,Z,W}
+ end,
+ {201,201,<<201>>,<<201>>,<<201>>,<<201>>} = F9(id(<<201>>)),
+
+ F10 = fun(X) ->
+ <<>> = (<<>> = X)
+ end,
+ <<>> = F10(id(<<>>)),
+ {'EXIT',{{badmatch,42},_}} = catch F10(id(42)),
+
+ F11 = fun(Bin) ->
+ <<A:8/bits,B:24/bits>> = <<C:16,D:16>> = <<E:8,F:8,G:8,H:8>> = Bin,
+ {A,B,C,D,E,F,G,H}
+ end,
+ {<<0>>,<<0,0,0>>, 0,0, 0,0,0,0} = F11(id(<<0:32>>)),
+ {<<16#ab>>,<<16#cdef57:24>>, 16#abcd,16#ef57, 16#ab,16#cd,16#ef,16#57} =
+ F11(id(<<16#abcdef57:32>>)),
+
+ F12 = fun(#{key := <<X:8>>} = #{key := <<Y:8>>}) -> {X,Y} end,
+ {255,255} = F12(id(#{key => <<-1>>})),
+ {209,209} = F12(id(#{key => <<209>>})),
+
+ F13 = fun(Bin) ->
+ <<_:8,A:Size>> = <<_:8,B:Size/bits>> = <<Size:8,_/bits>> = Bin,
+ {Size,A,B}
+ end,
+ {0,0,<<>>} = F13(id(<<0>>)),
+ {1,1,<<1:1>>} = F13(id(<<1,1:1>>)),
+ {8,42,<<42>>} = F13(id(<<8,42>>)),
+
+ F14 = fun(Bin) ->
+ [<<_:Y>> | _] = [_ | Y] = id(Bin),
+ ok
+ end,
+ ok = F14([<<>>|0]),
+ ok = F14([<<-1:32>>|32]),
+ {'EXIT',{{badmatch,[<<0:16>>|0]},_}} = catch F14([<<0:16>>|0]),
+ {'EXIT',{{badmatch,[<<0:16>>|atom]},_}} = catch F14([<<0:16>>|atom]),
+
+ F15 = fun(Bin) ->
+ {<<_:Y>>, _} = {_, Y} = id(Bin),
+ Y
+ end,
+ 0 = F15({<<>>, 0}),
+ 32 = F15({<<-1:32>>, 32}),
+ {'EXIT',{{badmatch,{<<0:16>>,0}},_}} = catch F15({<<0:16>>, 0}),
+ {'EXIT',{{badmatch,{<<0:16>>,atom}},_}} = catch F15({<<0:16>>, atom}),
+
+ F16 = fun(Bin) ->
+ [{<<_:Y>>, _}] = [{_, Y}] = id(Bin),
+ Y
+ end,
+ 0 = F16([{<<>>, 0}]),
+ 32 = F16([{<<-1:32>>, 32}]),
+ {'EXIT',{{badmatch,[{<<0:16>>,0}]},_}} = catch F16([{<<0:16>>, 0}]),
+ {'EXIT',{{badmatch,[{<<0:16>>,atom}]},_}} = catch F16([{<<0:16>>, atom}]),
+
+ F17 = fun(#{[] := <<_>>, [] := <<_>>}) -> ok end,
+ ok = F17(id(#{[] => <<42>>})),
+ {'EXIT',{function_clause,_}} = catch F17(id(#{[] => <<>>})),
+ {'EXIT',{function_clause,_}} = catch F17(id(atom)),
+
+ F18 = fun(<<_>> = Bin) ->
+ case Bin of
+ <<_>> -> ok;
+ _ -> error
+ end;
+ (_) -> error
+ end,
+ ok = F18(id(<<42>>)),
+ error = F18(<<>>),
+ error = F18(<<1:1>>),
+ error = F18(atom),
+
+ F19 = fun(B) ->
+ <<42:Sz>> = Sz = <<_>> = B
+ end,
+ {'EXIT',{{badmatch,<<0>>},_}} = catch F19(<<0>>),
+ {'EXIT',{{badmatch,<<>>},_}} = catch F19(<<>>),
+ {'EXIT',{{badmatch,0},_}} = catch F19(0),
+
+ F20 = fun([<<>>] = [<<>>]) -> ok end,
+ ok = F20([<<>>]),
+
+ a = gh_6467(id(0), id(<<0>>), id(0)),
+ {'EXIT',{{badmatch,0},_}} = catch gh_6467(id(0), id(<<0>>), id([])),
+ {'EXIT',{{badmatch,<<7>>},_}} = catch gh_6467(id(<<7>>), id(<<33>>), id([])),
+
+ F21 = fun(<<_:(true andalso 0)>> = <<>>) -> ok;
+ (_) -> error
+ end,
+ ok = F21(<<>>),
+ error = F21(<<42>>),
+ error = F21(42),
+
+ true = gh6415_a(<<42>>, true),
+ error = gh6415_a(<<42>>, false),
+ error = gh6415_a(<<>>, false),
+ error = gh6415_a(<<>>, not_bool),
+ error = gh6415_a(any, true),
+ error = gh6415_a(any, false),
+
+ ok = gh6415_b(true, <<99>>),
+ ok = gh6415_b(false, <<99>>),
+ error = gh6415_b(true, <<>>),
+ error = gh6415_b(false, <<>>),
+
+ ok = gh6415_c(<<10>>, true),
+ error = gh6415_c(<<10>>, false),
+ error = gh6415_c(<<>>, true),
+ error = gh6415_c(42, true),
+
+ gh6415_case_clause(42),
+ gh6415_case_clause(<<42>>),
+ gh6415_case_clause(a),
+
+ error = gh6415_nomatch(<<>>),
+ error = gh6415_nomatch(<<42>>),
+ error = gh6415_nomatch(<<97,98>>),
+ error = gh6415_nomatch(#{0 => <<>>}),
+ error = gh6415_nomatch(#{0 => <<42>>}),
+ error = gh6415_nomatch(#{0 => 42}),
+ error = gh6415_nomatch(#{}),
+ error = gh6415_nomatch({a,tuple}),
+ error = gh6415_nomatch(an_atom),
+
+ 42 = gh6415_match_a(id(<<42>>)),
+ {'EXIT',{function_clause,_}} = catch gh6415_match_a(id(<<>>)),
+ {'EXIT',{function_clause,_}} = catch gh6415_match_a(id(a)),
+
+ {42,<<>>} = gh6415_match_b(id(<<42>>)),
+ {'EXIT',{function_clause,_}} = catch gh6415_match_b(id(<<1,2>>)),
+ {'EXIT',{function_clause,_}} = catch gh6415_match_b(id(<<>>)),
+ {'EXIT',{function_clause,_}} = catch gh6415_match_b(id(a)),
+
+ {'EXIT',{_,_}} = catch gh6415_match_c(),
+
+ ok = gh6415_match_d(id(<<163>>)),
+ {'EXIT',{function_clause,_}} = catch gh6415_match_d(id(<<>>)),
+ {'EXIT',{function_clause,_}} = catch gh6415_match_d(id(a)),
+
+ ok = gh6415_match_e(id(<<163,0>>)),
+ ok = gh6415_match_e(id(<<99,8,42>>)),
+ ok = gh6415_match_e(id(<<99,17,-1:17>>)),
+ {'EXIT',{function_clause,_}} = catch gh6415_match_e(id(<<163>>)),
+ {'EXIT',{function_clause,_}} = catch gh6415_match_e(id(<<>>)),
+ {'EXIT',{function_clause,_}} = catch gh6415_match_e(id(a)),
+
+ 7777 = gh6415_match_f(id(<<17,7777:17>>)),
+ 1234 = gh6415_match_f(id(<<17,1234:17>>)),
+ error = gh6415_match_f(id(<<0>>)),
+ error = gh6415_match_f(id(<<8,42>>)),
+
+ ok.
+
+%% GH-6467. When a matched out value was never used, the bs_match instruction
+%% was not rewritten to a bs_skip instruction, causing an assertion fail in
+%% beam_ssa_pre_codegen.
+gh_6467(X, _, []) ->
+ [0 || _ <- (<<_:Y>> = (Y = ((_ = X) = X)))];
+gh_6467(_, <<Y>>, _) ->
+ a.
+
+gh6415_a(<<X>>, Y) when (<<X>> == false orelse (Y andalso true)); Y ->
+ Y;
+gh6415_a(_, _) ->
+ error.
+
+gh6415_b(X, <<Y>>) when ((Y > X) xor X); not X; X ->
+ ok;
+gh6415_b(_, _) ->
+ error.
+
+gh6415_c(<<X>>, Y) when {(X / 1), (Y andalso true)}; Y ->
+ ok;
+gh6415_c(_, _) ->
+ error.
+
+gh6415_case_clause(X) ->
+ {'EXIT',{{case_clause,0},_}} = catch gh6415_case_clause_a(X),
+ {'EXIT',{{case_clause,0},_}} = catch gh6415_case_clause_b(X),
+ {'EXIT',{{case_clause,0},_}} = catch gh6415_case_clause_c(X),
+ {'EXIT',{{case_clause,0},_}} = catch gh6415_case_clause_d(X),
+ ok.
+
+gh6415_case_clause_a(X) ->
+ case 0 of
+ <<_:(X bor X)>> ->
+ <<_:Y>> = Y = X
+ end,
+ Y.
+
+gh6415_case_clause_b(X) ->
+ case 0 of
+ <<_:(X bor X)>> ->
+ <<_:Y>> = <<Y>> = X
+ end,
+ Y.
+
+gh6415_case_clause_c(X) ->
+ case 0 of
+ <<_:(X bor X)>> ->
+ <<_:Y>> = <<Y>> = X + 1
+ end,
+ Y.
+
+gh6415_case_clause_d(X) ->
+ case 0 of
+ <<_:(X bor X)>> ->
+ <<_:Y>> = <<Y>> = [X]
+ end,
+ Y.
+
+gh6415_nomatch(E0) ->
+ E = id(E0),
+ Res = gh6415_nomatch_a(E),
+ Res = gh6415_nomatch_b(E),
+ Res = gh6415_nomatch_c(E),
+ Res = gh6415_nomatch_d(E),
+ Res = gh6415_nomatch_e(E),
+ Res = gh6415_nomatch_f(E),
+ Res = gh6415_nomatch_g(E),
+ Res = gh6415_nomatch_h(E),
+ Res = gh6415_nomatch_i(E),
+ Res = gh6415_nomatch_j(E),
+ Res = gh6415_nomatch_k(E),
+ Res = gh6415_nomatch_l(E),
+ Res.
+
+gh6415_nomatch_a(#{0 := <<_:0>>, 0 := <<_:8>>}) -> ok;
+gh6415_nomatch_a(_) -> error.
+
+gh6415_nomatch_b(#{0 := <<_:16>>, 0 := <<_:8>>}) -> ok;
+gh6415_nomatch_b(_) -> error.
+
+gh6415_nomatch_c(#{0 := <<>>, 0 := <<_:8>>}) -> ok;
+gh6415_nomatch_c(_) -> error.
+
+gh6415_nomatch_d(#{0 := <<_:8>>, 0 := <<_:0>>}) -> ok;
+gh6415_nomatch_d(_) -> error.
+
+gh6415_nomatch_e(#{0 := <<_:8>>, 0 := <<>>}) -> ok;
+gh6415_nomatch_e(_) -> error.
+
+gh6415_nomatch_f(#{0 := <<_:8>>, 0 := <<_:16>>}) -> ok;
+gh6415_nomatch_f(_) -> error.
+
+gh6415_nomatch_g(#{0 := <<_>> = <<_, 0>>}) -> ok;
+gh6415_nomatch_g(_) -> error.
+
+gh6415_nomatch_h(#{0 := <<X:0>>, 0 := <<X>>}) -> ok;
+gh6415_nomatch_h(_) -> error.
+
+gh6415_nomatch_i(<<_>> = <<_:16,T/binary>>) ->
+ _ = binary_to_list(T),
+ ok;
+gh6415_nomatch_i(_) ->
+ error.
+
+gh6415_nomatch_j(<<_>> = <<X:16,T/binary>>) ->
+ {X,T};
+gh6415_nomatch_j(_) ->
+ error.
+
+gh6415_nomatch_k(#{0 := <<_>>, 0 := <<_, _:(0 div 0)>>}) ->
+ ok;
+gh6415_nomatch_k(_) ->
+ error.
+
+gh6415_nomatch_l(Bin) ->
+ case Bin of
+ <<X:0, _:X/integer>> = <<_:8>> ->
+ ok;
+ _ ->
+ error
+ end.
+
+gh6415_match_a(<<_>> = <<X>>) ->
+ X.
+
+gh6415_match_b(<<_>> = <<X,T/binary>>) ->
+ {X,T}.
+
+gh6415_match_c() ->
+ case catch <<0 || true>> of
+ #{[] := <<_>>, [] := <<X>>} ->
+ X
+ end.
+
+gh6415_match_d(<<_, _:(true andalso 0)>> = <<_>>) ->
+ ok.
+
+gh6415_match_e(<<_, _:(true andalso 0), Size, _:Size>> = <<_, Size, _:Size>>) ->
+ ok.
+
+gh6415_match_f(<<_:(true andalso 0), Size, Var:Size>> =
+ <<Size, _:(true andalso 0), Var:Size>> =
+ <<17,Var:17,_:(true andalso 0)>>) ->
+ Var;
+gh6415_match_f(_) ->
+ error.
+
+gh_6923(_Config) ->
+ Mod = list_to_atom(?MODULE_STRING ++ "_" ++ atom_to_list(?FUNCTION_NAME)),
+
+ %% The second clause of match_route/1 has lower line numbers than
+ %% the first clause.
+ %%
+ %% -module(bs_match_SUITE_gh_6923). %Line 29
+ %% -export([match_route/1]). %Line 29
+ %% match_route([<<"prefix">>, <<"action">>]) -> first; %Line 4
+ %% match_route([<<"prefix">>, _Ignore]) -> second. %Line 2
+ Forms =
+ [{attribute,29,module,Mod},
+ {attribute,29,export,[{match_route,1}]},
+ {function,4,match_route,1,
+ [{clause,4,
+ [{cons,4,
+ {bin,4,[{bin_element,4,{string,4,"prefix"},default,default}]},
+ {cons,4,
+ {bin,4,
+ [{bin_element,4,
+ {string,4,"action"},
+ default,default}]},
+ {nil,4}}}],
+ [],
+ [{atom,4,first}]},
+ {clause,2,
+ [{cons,2,
+ {bin,2,[{bin_element,2,{string,2,"prefix"},default,default}]},
+ {cons,2,{var,2,'_Ignore'},{nil,2}}}],
+ [],
+ [{atom,2,second}]}]}],
+ Opts = test_lib:opt_opts(?MODULE),
+ {ok, Mod, Beam} = compile:forms(Forms, Opts),
+ {module, Mod} = code:load_binary(Mod, "", Beam),
+ first = Mod:match_route([<<"prefix">>, <<"action">>]),
+ second = Mod:match_route([<<"prefix">>, whatever]),
+ _ = code:delete(Mod),
+ _ = code:purge(Mod),
+
+ %% For coverage.
+ first = do_gh_6923([id(<<"abc">>), id(42)]),
+ second = do_gh_6923([id(<<"abc">>), id({a,b,c})]),
+
+ ok.
+
+do_gh_6923([<<"abc">>, A]) when is_integer(A) -> first;
+do_gh_6923([<<"abc">>, A]) when is_tuple(A) -> second.
+
+%%% Utilities.
+
+id(I) -> I.
diff --git a/lib/compiler/test/bs_utf_SUITE.erl b/lib/compiler/test/bs_utf_SUITE.erl
index 10954dd833..beb0f4ef23 100644
--- a/lib/compiler/test/bs_utf_SUITE.erl
+++ b/lib/compiler/test/bs_utf_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,8 +20,9 @@
-module(bs_utf_SUITE).
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
+ verify_highest_opcode/1,
utf8_roundtrip/1,unused_utf_char/1,utf16_roundtrip/1,
utf32_roundtrip/1,guard/1,extreme_tripping/1,
literals/1,coverage/1]).
@@ -30,12 +31,13 @@
suite() -> [{ct_hooks,[ts_install_cth]}].
-all() ->
- [utf8_roundtrip, unused_utf_char, utf16_roundtrip,
+all() ->
+ [verify_highest_opcode,
+ utf8_roundtrip, unused_utf_char, utf16_roundtrip,
utf32_roundtrip, guard, extreme_tripping, literals,
coverage].
-groups() ->
+groups() ->
[].
init_per_suite(Config) ->
@@ -51,6 +53,27 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
+verify_highest_opcode(_Config) ->
+ case ?MODULE of
+ bs_construct_r24_SUITE ->
+ {ok,Beam} = file:read_file(code:which(?MODULE)),
+ case test_lib:highest_opcode(Beam) of
+ Highest when Highest =< 176 ->
+ ok;
+ TooHigh ->
+ ct:fail({too_high_opcode,TooHigh})
+ end;
+ bs_construct_r25_SUITE ->
+ {ok,Beam} = file:read_file(code:which(?MODULE)),
+ case test_lib:highest_opcode(Beam) of
+ Highest when Highest =< 180 ->
+ ok;
+ TooHigh ->
+ ct:fail({too_high_opcode,TooHigh})
+ end;
+ _ ->
+ ok
+ end.
utf8_roundtrip(Config) when is_list(Config) ->
[utf8_roundtrip_1(P) || P <- utf_data()],
diff --git a/lib/compiler/test/compilation_SUITE.erl b/lib/compiler/test/compilation_SUITE.erl
index 81f4e9b35c..f8c61a354c 100644
--- a/lib/compiler/test/compilation_SUITE.erl
+++ b/lib/compiler/test/compilation_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -402,7 +402,7 @@ string_table(Config) when is_list(Config) ->
File = filename:join(DataDir, "string_table.erl"),
{ok,string_table,Beam,[]} = compile:file(File, [return, binary]),
{ok,{string_table,[StringTableChunk]}} = beam_lib:chunks(Beam, ["StrT"]),
- {"StrT", <<"stringtable">>} = StringTableChunk,
+ {"StrT", <<"abcdefghiABCDEFGHI">>} = StringTableChunk,
ok.
otp_8949_a(Config) when is_list(Config) ->
diff --git a/lib/compiler/test/compilation_SUITE_data/infinite_loop.erl b/lib/compiler/test/compilation_SUITE_data/infinite_loop.erl
index 6d9135bb01..8fb9125c77 100644
--- a/lib/compiler/test/compilation_SUITE_data/infinite_loop.erl
+++ b/lib/compiler/test/compilation_SUITE_data/infinite_loop.erl
@@ -20,3 +20,13 @@ foobar() ->
barfoo() ->
barfoo().
+
+%% GH-6474. The compiler would go into an infinite loop.
+
+bc_infinite_loop() ->
+ mutually_recursive(<<0 || false>>).
+
+mutually_recursive(X) ->
+ %% This LC will be implemented as mutually recursive functions.
+ %% Analyzing them would cause an infinite loop.
+ [0 || _ <- [], <<_>> <= X].
diff --git a/lib/compiler/test/compilation_SUITE_data/string_table.erl b/lib/compiler/test/compilation_SUITE_data/string_table.erl
index 1da1d015dd..57d0fc579c 100644
--- a/lib/compiler/test/compilation_SUITE_data/string_table.erl
+++ b/lib/compiler/test/compilation_SUITE_data/string_table.erl
@@ -1,8 +1,8 @@
-module(string_table).
-export([f/1, g/1]).
-f(<<"string">>) -> string;
-f(<<"stringtable">>) -> stringtable.
+f(<<"abcdefghi">>) -> string;
+f(<<"abcdefghiABCDEFGHI">>) -> stringtable.
-g(<<"stringtable">>) -> stringtable;
-g(<<"table">>) -> table.
+g(<<"abcdefghiABCDEFGHI">>) -> stringtable;
+g(<<"ABCDEFGHI">>) -> table.
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index cbe7179e0a..b856f4044b 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -38,7 +38,8 @@
warnings/1, pre_load_check/1, env_compiler_options/1,
bc_options/1, deterministic_include/1, deterministic_paths/1,
compile_attribute/1, message_printing/1, other_options/1,
- transforms/1, erl_compile_api/1, types_pp/1
+ transforms/1, erl_compile_api/1, types_pp/1, bs_init_writable/1,
+ annotations_pp/1
]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -58,7 +59,7 @@ all() ->
env_compiler_options, custom_debug_info, bc_options,
custom_compile_info, deterministic_include, deterministic_paths,
compile_attribute, message_printing, other_options, transforms,
- erl_compile_api, types_pp].
+ erl_compile_api, types_pp, bs_init_writable, annotations_pp].
groups() ->
[].
@@ -1576,7 +1577,7 @@ pre_load_check(Config) ->
try
do_pre_load_check(Config)
after
- dbg:stop_clear()
+ dbg:stop()
end
end.
@@ -1719,7 +1720,8 @@ bc_options(Config) ->
{168, small, [r22]},
{168, small, [no_init_yregs,no_shared_fun_wrappers,
no_ssa_opt_record,no_make_fun3,
- no_ssa_opt_float,no_line_info,no_type_opt]},
+ no_ssa_opt_float,no_line_info,no_type_opt,
+ no_bs_match]},
{169, small, [r23]},
{169, big, [no_init_yregs,no_shared_fun_wrappers,
@@ -2055,9 +2057,9 @@ types_pp(Config) when is_list(Config) ->
"{any(), any(), any(), any(), any()}"},
{make_inexact_tuple, "{any(), any(), any(), ...}"},
{make_union,
- "'foo' | nonempty_list(1..3) | number() |"
- " {'tag0', 1, 2} | {'tag1', 3, 4} | bitstring(24)"},
- {make_bitstring, "bitstring(24)"},
+ "'foo' | nonempty_list(1..3) | number(3, 7) |"
+ " {'tag0', 1, 2} | {'tag1', 3, 4} | bitstring(8)"},
+ {make_bitstring, "bitstring(8)"},
{make_none, "none()"}],
lists:foreach(fun({FunName, Expected}) ->
Actual = map_get(atom_to_list(FunName), ResultTypes),
@@ -2072,26 +2074,84 @@ types_pp(Config) when is_list(Config) ->
ok = file:del_dir_r(TargetDir),
ok.
-%% We assume that a call starts with a "Result type:"-line followed by
-%% a type line, which is followed by an optional annotation before the
-%% actual call.
+%% Parsing for result types. Remember the last seen "Result type"
+%% annotation and apply it to calls when we see them to a call when we
+%% see them.
get_result_types(Lines) ->
- get_result_types(Lines, #{}).
+ get_result_types(Lines, none, #{}).
-get_result_types([" %% Result type:"++_," %% "++TypeLine|Lines], Acc) ->
+get_result_types([" %% Result type:"++_," %% "++TypeLine|Lines], _, Acc) ->
get_result_types(Lines, TypeLine, Acc);
-get_result_types([_|Lines], Acc) ->
- get_result_types(Lines, Acc);
-get_result_types([], Acc) ->
+get_result_types([Line|Lines], TypeLine, Acc0) ->
+ Split = string:split(Line, "="),
+ Acc = case Split of
+ [_, " call" ++ Rest] ->
+ case string:split(Rest, "`", all) of
+ [_,Callee,_] ->
+ Acc0#{ Callee => TypeLine };
+ _ ->
+ Acc0
+ end;
+ _ ->
+ Acc0
+ end,
+ get_result_types(Lines, TypeLine, Acc);
+get_result_types([], _, Acc) ->
Acc.
-get_result_types([" %% Anno: "++_|Lines], TypeLine, Acc) ->
- get_result_types(Lines, TypeLine, Acc);
-get_result_types([CallLine|Lines], TypeLine, Acc) ->
- [_,Callee,_] = string:split(CallLine, "`", all),
- get_result_types(Lines, Acc#{ Callee => TypeLine }).
+%% Check that the beam_ssa_type pass knows about bs_init_writable.
+bs_init_writable(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ InFile = filename:join(DataDir, "bs_init_writable.erl"),
+ OutDir = filename:join(PrivDir, "bs_init_writable"),
+ OutFile = filename:join(OutDir, "bs_init_writable.S"),
+ ok = file:make_dir(OutDir),
+ {ok,bs_init_writable} = compile:file(InFile, ['S',{outdir,OutDir}]),
+ {ok,Listing} = file:read_file(OutFile),
+ Os = [global,multiline,{capture,all_but_first,list}],
+ %% The is_bitstr test should be optimized away.
+ nomatch = re:run(Listing, "({test,is_bitstr,.+})", Os),
+ %% The is_bitstr test should be optimized away.
+ nomatch = re:run(Listing, "({test,is_binary,.+})", Os),
+ ok = file:del_dir_r(OutDir).
+%% Check that an SSA listing contains pretty printed annotations, this
+%% blindly checks that the expected annotation occurs the expected
+%% number of times. Checking that the annotations are correctly placed
+%% and contains the correct information is done in
+%% beam_ssa_check_SUITE.
+annotations_pp(Config) when is_list(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ TargetDir = filename:join(PrivDir, types_pp),
+ File = filename:join(DataDir, "annotations_pp.erl"),
+ Listing = filename:join(TargetDir, "annotations_pp.ssaopt"),
+ ok = file:make_dir(TargetDir),
+
+ {ok,_} = compile:file(File, [dssaopt, {outdir, TargetDir}]),
+ {ok, Data} = file:read_file(Listing),
+ Lines = string:split(binary_to_list(Data), "\n", all),
+
+ ResultTypes = get_annotations(" %% Result type:", Lines),
+ 10 = length(ResultTypes),
+
+ Uniques = get_annotations(" %% Unique:", Lines),
+ 10 = length(Uniques),
+
+ Aliased = get_annotations(" %% Aliased:", Lines),
+ 17 = length(Aliased),
+
+ ok = file:del_dir_r(TargetDir),
+ ok.
+
+get_annotations(Key, [Key," %% "++Anno|Lines]) ->
+ [Anno|get_annotations(Key, Lines)];
+get_annotations(Key, [_|Lines]) ->
+ get_annotations(Key, Lines);
+get_annotations(_, []) ->
+ [].
%%%
%%% Utilities.
diff --git a/lib/compiler/test/compile_SUITE_data/annotations_pp.erl b/lib/compiler/test/compile_SUITE_data/annotations_pp.erl
new file mode 100644
index 0000000000..f1a7c98fe2
--- /dev/null
+++ b/lib/compiler/test/compile_SUITE_data/annotations_pp.erl
@@ -0,0 +1,31 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+-module(annotations_pp).
+
+-export([a/1]).
+
+%% The annotations_pp test will check that these functions and
+%% module_info/[01] have "Result type:" annotations.
+
+a(L) ->
+ b(L, <<>>).
+
+b([H|T], Acc) ->
+ b(T, <<Acc/binary, H:8>>);
+b([], Acc) ->
+ Acc.
diff --git a/lib/compiler/test/compile_SUITE_data/bs_init_writable.erl b/lib/compiler/test/compile_SUITE_data/bs_init_writable.erl
new file mode 100644
index 0000000000..a9e061645f
--- /dev/null
+++ b/lib/compiler/test/compile_SUITE_data/bs_init_writable.erl
@@ -0,0 +1,45 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(bs_init_writable).
+
+-export([do/0]).
+
+do() ->
+ Val = ex:foo(),
+ X = << <<B:1>> || B <- Val >>,
+ should_not_have_bitstring_test(X),
+ Y = << <<B:8>> || B <- Val >>,
+ should_not_have_binary_test(Y).
+
+
+%% If the beam_ssa_type pass does its job,
+%% should_not_have_bitstring_test/1 should not contain a is_bitstr test.
+should_not_have_bitstring_test(X) when is_bitstring(X) ->
+ bitstring;
+should_not_have_bitstring_test(_) ->
+ something_else.
+
+%% If the beam_ssa_type pass does its job,
+%% should_not_have_binary_test/1 should not contain a is_binary test.
+should_not_have_binary_test(X) when is_binary(X) ->
+ binary;
+should_not_have_binary_test(_) ->
+ something_else.
diff --git a/lib/compiler/test/core_SUITE.erl b/lib/compiler/test/core_SUITE.erl
index a17ad9c6ad..783b1669d1 100644
--- a/lib/compiler/test/core_SUITE.erl
+++ b/lib/compiler/test/core_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
cover_v3_kernel_4/1,cover_v3_kernel_5/1,
non_variable_apply/1,name_capture/1,fun_letrec_effect/1,
get_map_element/1,receive_tests/1,
- core_lint/1]).
+ core_lint/1,nif/1,no_nif/1,no_load_nif/1]).
-include_lib("common_test/include/ct.hrl").
@@ -61,7 +61,7 @@ groups() ->
cover_v3_kernel_4,cover_v3_kernel_5,
non_variable_apply,name_capture,fun_letrec_effect,
get_map_element,receive_tests,
- core_lint
+ core_lint,nif,no_nif,no_load_nif
]}].
@@ -170,3 +170,53 @@ core_lint_function(Exports, Attributes, Body) ->
(_) -> true
end, Errors),
error = compile:forms(Mod, [from_core,clint0,report]).
+
+nif(Conf) ->
+ %% Check that only the function in the nif attribute starts with nif_start
+ Funs =
+ nif_compile_to_cerl(Conf, [{d,'WITH_ATTRIBUTE'},{d,'WITH_LOAD_NIF'}]),
+ false = nif_first_instruction_is_nif_start(init, 1, Funs),
+ true = nif_first_instruction_is_nif_start(start, 1, Funs),
+ false = nif_first_instruction_is_nif_start(module_info, 0, Funs),
+ false = nif_first_instruction_is_nif_start(module_info, 1, Funs),
+ ok.
+
+no_nif(Conf) ->
+ %% Check that all functions start with nif_start
+ Funs = nif_compile_to_cerl(Conf, [{d,'WITH_LOAD_NIF'}]),
+ true = nif_first_instruction_is_nif_start(init, 1, Funs),
+ true = nif_first_instruction_is_nif_start(start, 1, Funs),
+ true = nif_first_instruction_is_nif_start(module_info, 0, Funs),
+ true = nif_first_instruction_is_nif_start(module_info, 1, Funs),
+ ok.
+
+no_load_nif(Conf) ->
+ %% Check that no functions start with nif_start
+ Funs = nif_compile_to_cerl(Conf, []),
+ false = nif_first_instruction_is_nif_start(init, 1, Funs),
+ false = nif_first_instruction_is_nif_start(start, 1, Funs),
+ false = nif_first_instruction_is_nif_start(module_info, 0, Funs),
+ false = nif_first_instruction_is_nif_start(module_info, 1, Funs),
+ ok.
+
+nif_compile_to_cerl(Conf, Flags) ->
+ Src = filename:join(proplists:get_value(data_dir, Conf), "nif.erl"),
+ {ok, _, F} = compile:file(Src, [to_core, binary, deterministic]++Flags),
+ Defs = cerl:module_defs(F),
+ [ {cerl:var_name(V),cerl:fun_body(Def)} || {V,Def} <- Defs].
+
+nif_first_instruction_is_nif_start(F, A, [{{F,A},Body}|_]) ->
+ try
+ Primop = cerl:seq_arg(Body),
+ Name = cerl:primop_name(Primop),
+ 0 = cerl:primop_arity(Primop),
+ nif_start = cerl:atom_val(Name),
+ true
+ catch
+ error:_ ->
+ false
+ end;
+nif_first_instruction_is_nif_start(F, A, [_|Rest]) ->
+ nif_first_instruction_is_nif_start(F, A, Rest);
+nif_first_instruction_is_nif_start(_, _, []) ->
+ not_found.
diff --git a/lib/compiler/test/core_SUITE_data/nif.erl b/lib/compiler/test/core_SUITE_data/nif.erl
new file mode 100644
index 0000000000..873e20252b
--- /dev/null
+++ b/lib/compiler/test/core_SUITE_data/nif.erl
@@ -0,0 +1,17 @@
+-module(nif).
+
+-export([init/1, start/1]).
+
+-ifdef(WITH_ATTRIBUTE).
+-nifs([start/1]).
+-endif.
+
+-ifdef(WITH_LOAD_NIF).
+init(File) ->
+ ok = erlang:load_nif(File, 0).
+-else.
+init(_File) ->
+ ok.
+-endif.
+
+start(_) -> erlang:nif_error(not_loaded).
diff --git a/lib/compiler/test/core_alias_SUITE.erl b/lib/compiler/test/core_alias_SUITE.erl
index b79383e882..767bf6fcec 100644
--- a/lib/compiler/test/core_alias_SUITE.erl
+++ b/lib/compiler/test/core_alias_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,7 +21,8 @@
-export([all/0, suite/0, groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2, end_per_group/2,
- tuples/1, cons/1, catastrophic_runtime/1]).
+ tuples/1, cons/1, catastrophic_runtime/1,
+ coverage/1]).
-include_lib("common_test/include/ct.hrl").
@@ -32,7 +33,7 @@ all() ->
groups() ->
[{p,[parallel],
- [tuples, cons, catastrophic_runtime]}].
+ [tuples, cons, catastrophic_runtime, coverage]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -217,3 +218,12 @@ catastrophic_runtime_1(N) ->
Eq = [<<"{{'.',[],[erlang,'=:=']},[],[Value, \"">>, Integer, <<"\"]}">>],
[<<"{{'.',[],[erlang,atom]},[],[">>, Nested, <<",">>, Eq, <<"]}">>].
+coverage(_Config) ->
+ State = id({undefined,undefined}),
+ {State, "Can't detect character encoding due to lack of indata"} =
+ too_deep(State),
+ ok.
+
+too_deep({_,undefined} = State) ->
+ {State, "Can't detect character encoding due to lack of indata"}.
+
diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl
index 8167ab517d..18284b9ebe 100644
--- a/lib/compiler/test/core_fold_SUITE.erl
+++ b/lib/compiler/test/core_fold_SUITE.erl
@@ -18,6 +18,7 @@
%% %CopyrightEnd%
%%
-module(core_fold_SUITE).
+-feature(maybe_expr, enable).
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
@@ -29,7 +30,8 @@
no_no_file/1,configuration/1,supplies/1,
redundant_stack_frame/1,export_from_case/1,
empty_values/1,cover_letrec_effect/1,
- receive_effect/1,map_effect/1]).
+ receive_effect/1,nested_lets/1,
+ map_effect/1]).
-export([foo/0,foo/1,foo/2,foo/3]).
@@ -50,8 +52,8 @@ groups() ->
no_no_file,configuration,supplies,
redundant_stack_frame,export_from_case,
empty_values,cover_letrec_effect,
- receive_effect,map_effect]}].
-
+ receive_effect,nested_lets,
+ map_effect]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -675,6 +677,7 @@ cover_letrec_effect(_Config) ->
end,
_ = catch cover_letrec_effect_1(),
+ _ = catch cover_letrec_effect_2(),
ok.
@@ -692,6 +695,12 @@ cover_letrec_effect_1() ->
end
end.
+cover_letrec_effect_2() ->
+ maybe
+ << ok || ok, _ <- (catch ok)>>,
+ ok
+ end.
+
receive_effect(_Config) ->
self() ! whatever,
{} = do_receive_effect(),
@@ -700,6 +709,154 @@ receive_effect(_Config) ->
do_receive_effect() ->
{} = receive _ -> {} = {} end.
+nested_lets(_Config) ->
+ {'EXIT',{{case_clause,ok},_}} = catch nested_lets_1(<<42>>),
+ {'EXIT',{badarith,_}} = catch nested_lets_2(id(0), id(0)),
+ {'EXIT',{badarith,_}} = catch nested_lets_3(),
+ {'EXIT',{undef,_}} = catch nested_lets_4(),
+ {'EXIT',{{case_clause,_},_}} = catch nested_lets_5(),
+ {'EXIT',{badarith,_}} = catch nested_lets_6(),
+
+ ok.
+
+%% GH-6572: Deeply nested `let` expressions caused `sys_core_fold` to generate
+%% unsafe code that it would attempt to fix up later. Unfortunately it did so
+%% through a limited fixpoint iteration, and would leak said code once the
+%% limit was hit.
+nested_lets_1(<<X>>) ->
+ Y =
+ case ok of
+ X ->
+ true = (ok > (Y = -1)),
+ <<>> =
+ {id(
+ <<
+ (ok - ok),
+ (bnot ok),
+ (nested_lets_1_f() band ok),
+ (nested_lets_1_f()),
+ (not ok),
+ (ok or nested_lets_1_f()),
+ (id(
+ id(
+ <<
+ (id(
+ <<
+ (id(
+ <<0 || _ <- []>>
+ ))
+ >>
+ ) * ok)
+ >>
+ )
+ ))
+ >>
+ )}
+ end.
+
+nested_lets_1_f() ->
+ ok.
+
+%% GH-6612: A variant of GH-6572 that slipped through the initial fix.
+nested_lets_2(X, 0) ->
+ try
+ 0 = {
+ _ = 0 + 0,
+ Z = bnot ok,
+ {(_ = ok), (_ = X)}#{ok => ok},
+ ok +
+ (nested_lets_2_f(
+ ok +
+ nested_lets_2_f(
+ ok +
+ (nested_lets_2_f(
+ (Y =
+ -nested_lets_2_f(
+ #{
+ (ok +
+ (ok +
+ nested_lets_2_f(
+ case
+ try ok of
+ _ ->
+ fun(_) ->
+ ok
+ end
+ after
+ ok
+ end
+ of
+ #{} ->
+ ok
+ end
+ ))) => ok
+ } > ok
+ ))
+ ) + ok)
+ ) >
+ ok
+ ))
+ }
+ of
+ _ ->
+ Z;
+ _ ->
+ Y
+ after
+ ok
+ end.
+
+nested_lets_2_f(_) ->
+ ok.
+
+nested_lets_3() ->
+ try ((true = [X | _]) = (ok * (_ = ok))) of
+ _ ->
+ X
+ after
+ ok
+ end.
+
+nested_lets_4() ->
+ try
+ not (case {(_ = ok), (Y = ?MODULE:undef())} of
+ a ->
+ ok;
+ 0 ->
+ ok
+ end)
+ of
+ _ ->
+ Y
+ after
+ ok
+ end.
+
+%% GH-6633.
+nested_lets_5() ->
+ case self() of
+ [_ | X] ->
+ ok;
+ false ->
+ {ok#{
+ (X = ok) :=
+ ((ok /=
+ maybe
+ ok
+ end) =/= ok)
+ }}
+ end,
+ X.
+
+%% GH-6635.
+nested_lets_6() ->
+ try {not (false orelse (ok#{(1 / 0) := ok})), X = ok} of
+ X ->
+ ok
+ after
+ ok
+ end.
+
map_effect(_Config) ->
{'EXIT',{{badkey,key},_}} = catch map_effect_1(),
@@ -716,4 +873,6 @@ map_effect_2(Map) ->
Map#{key := value},
ok.
+%%% Common utility functions.
+
id(I) -> I.
diff --git a/lib/compiler/test/fun_SUITE.erl b/lib/compiler/test/fun_SUITE.erl
index d9608ef7d1..0d5936824a 100644
--- a/lib/compiler/test/fun_SUITE.erl
+++ b/lib/compiler/test/fun_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
test1/1,overwritten_fun/1,otp_7202/1,bif_fun/1,
- external/1,eep37/1,eep37_dup/1,badarity/1,badfun/1,
+ external/1,eep37/1,badarity/1,badfun/1,
duplicated_fun/1,unused_fun/1,parallel_scopes/1,
coverage/1]).
@@ -39,7 +39,7 @@ all() ->
groups() ->
[{p,[parallel],
[test1,overwritten_fun,otp_7202,bif_fun,external,eep37,
- eep37_dup,badarity,badfun,duplicated_fun,unused_fun,
+ badarity,badfun,duplicated_fun,unused_fun,
parallel_scopes,
coverage]}].
@@ -223,7 +223,14 @@ bad_external_fun() ->
fun V0:V0/V0, %Should fail.
never_reached.
-eep37(Config) when is_list(Config) ->
+%% Named funs.
+eep37(_Config) ->
+ eep37_basic(),
+ eep37_dup(),
+ eep37_gh6515(),
+ ok.
+
+eep37_basic() ->
F = fun Fact(N) when N > 0 -> N * Fact(N - 1); Fact(0) -> 1 end,
Add = fun _(N) -> N + 1 end,
UnusedName = fun _BlackAdder(N) -> N + 42 end,
@@ -232,7 +239,7 @@ eep37(Config) when is_list(Config) ->
50 = UnusedName(8),
ok.
-eep37_dup(Config) when is_list(Config) ->
+eep37_dup() ->
dup1 = (dup1())(),
dup2 = (dup2())(),
ok.
@@ -243,6 +250,39 @@ dup1() ->
dup2() ->
fun _F() -> dup2 end.
+eep37_gh6515() ->
+ {0,F1} = eep37_gh6515_1(),
+ F1 = F1(),
+
+ [0,F2] = eep37_gh6515_2(),
+ 1 = F2(0),
+ 120 = F2(5),
+
+ ok.
+
+eep37_gh6515_1() ->
+ {case [] of
+ #{} ->
+ X = 0;
+ X ->
+ 0
+ end,
+ fun X() ->
+ X
+ end}.
+
+eep37_gh6515_2() ->
+ [case [] of
+ #{} ->
+ Fact = 0;
+ Fact ->
+ 0
+ end,
+ fun Fact(N) when N > 0 ->
+ N * Fact(N - 1);
+ Fact(0) -> 1
+ end].
+
badarity(Config) when is_list(Config) ->
{'EXIT',{{badarity,{_,[]}},_}} = (catch (fun badarity/1)()),
{'EXIT',{{badarity,_},_}} = (catch fun() -> 42 end(0)),
@@ -524,6 +564,12 @@ parallel_scopes_13(A, B) ->
coverage(_Config) ->
ok = coverage_1(),
+
+ [2,3,4] = coverage_2(id([1,2,3])),
+
+ {42,F} = coverage_3(id({[], x})),
+ x = F(),
+
ok.
coverage_1() ->
@@ -534,5 +580,15 @@ coverage_1() ->
("abc") -> party
end,
ok.
+
+coverage_2(List) ->
+ %% Cover a line in beam_ssa_pre_codegen:need_frame_1/2 when the
+ %% no_make_fun3 option is given.
+ lists:map(fun(E) -> E + 1 end, List).
+
+%% Cover a line in beam_block when no_make_fun3 option is given.
+coverage_3({[], A}) ->
+ {id(42), fun() -> A end}.
+
id(I) ->
I.
diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl
index 1a96fa4b6c..f03dcf6225 100644
--- a/lib/compiler/test/guard_SUITE.erl
+++ b/lib/compiler/test/guard_SUITE.erl
@@ -1360,6 +1360,23 @@ rb(_, _, _) -> false.
rel_ops(Config) when is_list(Config) ->
+ TupleUnion = case id(2) of
+ 2 -> {a,id(b)};
+ 3 -> {b,id(b),c}
+ end,
+ Float = float(id(42)),
+ Int = trunc(id(42.0)),
+
+ IntFunFloat = make_fun(Float),
+ IntFunInt = make_fun(Int),
+
+ FloatFun = make_fun(Float, Float),
+ IntFun = make_fun(Int, Int),
+ MixedFun = make_fun(42, 42.0),
+
+ MixedFun14 = make_fun(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13.0, 14.0),
+ IntFun14 = make_fun(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14),
+
?T(=/=, 1, 1.0),
?F(=/=, 2, 2),
?F(=/=, {a}, {a}),
@@ -1368,9 +1385,50 @@ rel_ops(Config) when is_list(Config) ->
?F(/=, 0, 0.0),
?T(/=, 0, 1),
?F(/=, {a}, {a}),
+ ?F(/=, {a,b}, TupleUnion),
+ ?T(/=, {x,y}, TupleUnion),
+ ?T(/=, TupleUnion, {x,y}),
+ ?F(/=, #{key => Int}, #{key => Float}),
+ ?F(/=, #{key => Int}, #{key => Float}),
+ ?F(/=, #{40 => Int}, #{40 => Int}),
+ ?F(/=, #{42 => Float}, #{42 => Int}),
+ ?T(/=, #{100.0 => Float}, #{100 => Float}),
+
+ ?F(/=, FloatFun, FloatFun),
+ ?T(/=, FloatFun, MixedFun14),
+
+ ?T(==, Int, 42.0),
+ ?T(==, Float, 42),
?T(==, 1, 1.0),
+ ?T(==, 1.0, 1),
+ ?F(==, Float, a),
+ ?T(==, Float, Float),
?F(==, a, {}),
+ ?T(==, TupleUnion, {a,b}),
+ ?F(==, {x,y}, TupleUnion),
+ ?F(==, {a,Float}, TupleUnion),
+
+ ?T(==, #{key => Float}, #{key => Int}),
+ ?T(==, #{40 => Int}, #{40 => Int}),
+ ?T(==, #{42 => Int}, #{42 => Float}),
+ ?F(==, #{100 => Float}, #{100.0 => Float}),
+
+ case ?MODULE of
+ guard_inline_SUITE ->
+ %% Inlining will inline the fun environment into the fun bodies,
+ %% creating funs having no enviroment and different bodies.
+ ok;
+ _ ->
+ ?T(==, IntFunInt, IntFunFloat),
+ ?T(==, FloatFun, FloatFun),
+ ?T(==, FloatFun, IntFun),
+ ?T(==, MixedFun, IntFun),
+ ?T(==, MixedFun, FloatFun),
+ ?T(==, IntFun14, MixedFun14),
+ ?T(==, MixedFun14, IntFun14),
+ ?F(==, IntFun14, IntFun)
+ end,
?F(=:=, 1, 1.0),
?T(=:=, 42.0, 42.0),
@@ -1427,6 +1485,17 @@ rel_ops(Config) when is_list(Config) ->
ok.
+make_fun(N) ->
+ fun() -> round(N + 0.5) end.
+
+make_fun(A, B) ->
+ fun() -> {A,B} end.
+
+make_fun(A, B, C, D, E, F, G, H, I, J, K, L, M, N) ->
+ fun() ->
+ {A, B, C, D, E, F, G, H, I, J, K, L, M, N}
+ end.
+
-undef(TestOp).
rel_op_combinations(Config) when is_list(Config) ->
diff --git a/lib/compiler/test/lc_SUITE.erl b/lib/compiler/test/lc_SUITE.erl
index 7d30f1c74f..5de383f928 100644
--- a/lib/compiler/test/lc_SUITE.erl
+++ b/lib/compiler/test/lc_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -124,6 +124,11 @@ basic(Config) when is_list(Config) ->
[{file,"bad_lc.erl"},{line,7}]}|_]}} =
(catch id(bad_generator_bc(a))),
+ {'EXIT',{{bad_generator,a},
+ [{?MODULE,_,_,
+ [{file,"bad_lc.erl"},{line,10}]}|_]}} =
+ (catch id(bad_generator_mc(a))),
+
%% List comprehensions with improper lists.
{'EXIT',{{bad_generator,d},
[{?MODULE,_,_,
@@ -273,3 +278,6 @@ bad_generator(List) -> %Line 2
bad_generator_bc(List) -> %Line 5
<< <<I:4>> || %Line 6
I <- List>>. %Line 7
+bad_generator_mc(List) -> %Line 8
+ #{I => ok || %Line 9
+ I <- List}. %Line 10
diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl
index 4db5b01109..a9b8fab19d 100644
--- a/lib/compiler/test/map_SUITE.erl
+++ b/lib/compiler/test/map_SUITE.erl
@@ -17,6 +17,7 @@
%% %CopyrightEnd%
%%
-module(map_SUITE).
+-feature(maybe_expr, enable).
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2
]).
@@ -87,7 +88,10 @@
%% miscellaneous
t_conflicting_destinations/1,
- t_cse_assoc/1
+ t_cse_assoc/1,
+ shared_key_tuples/1,
+ map_aliases/1,
+ coverage/1
]).
-define(badmap(V, F, Args), {'EXIT', {{badmap,V}, [{maps,F,Args,_}|_]}}).
@@ -161,7 +165,10 @@ all() ->
%% miscellaneous
t_conflicting_destinations,
- t_cse_assoc
+ t_cse_assoc,
+ shared_key_tuples,
+ map_aliases,
+ coverage
].
groups() -> [].
@@ -2546,6 +2553,76 @@ do_cse_assoc(M, V) ->
Assoc
end.
+shared_key_tuples(_Config) ->
+ A = decimal(0),
+ B = decimal(1),
+
+ case ?MODULE of
+ map_inline_SUITE ->
+ %% With inlining, two separate map literals will be created. They
+ %% will not share keys.
+ ok;
+ _ ->
+ %% The two instances should share the key tuple.
+ true = erts_debug:same(erts_internal:map_to_tuple_keys(A),
+ erts_internal:map_to_tuple_keys(B))
+ end,
+ ok.
+
+decimal(Int) ->
+ #{type => decimal, int => Int, exp => 0}.
+
+%% GH-6348/OTP-18297: Extend parallel matching of maps.
+map_aliases(_Config) ->
+ F1 = fun(M) ->
+ #{K := V} = #{k := {a,K}} = M,
+ V
+ end,
+ value = F1(id(#{k => {a,key}, key => value})),
+
+ F2 = fun(#{} = #{}) -> ok end,
+ ok = F2(id(#{})),
+ ok = F2(id(#{key => whatever})),
+
+ F3 = fun(#{a := V} = #{}) -> V end,
+ {a,b,c} = F3(id(#{a => {a,b,c}})),
+
+ F4 = fun(Map) ->
+ [#{Key := Value} | _] = [_ | Key] = id(Map),
+ Value
+ end,
+ bar = F4([#{foo => bar} | foo]),
+
+ F5 = fun(Map) ->
+ {#{Key := Value}, _} = {_, Key} = id(Map),
+ Value
+ end,
+ light = F5({#{frotz => light}, frotz}),
+
+ F6 = fun(E) ->
+ #{Y := _} = (Y = ((_ = X) = E))
+ end,
+ {'EXIT',{{badmatch,0},_}} = catch F6(id(0)),
+ {'EXIT',{{badmatch,#{}},_}} = catch F6(id(#{})),
+ {'EXIT',{{badmatch,#{key := value}},_}} = catch F6(id(#{key => value})),
+
+ ok.
+
+coverage(_Config) ->
+ {'EXIT',{{badmatch,ok},_}} = catch coverage_1(),
+
+ ok.
+
+coverage_1() ->
+ %% Cover beam_block:simplify_get_map_elements/4 when the type
+ %% optimization pass is disabled.
+ try #{ok := _V5, ok := _V4} = (maybe ok end) of
+ #{ok := _V7, _V5 := _V7, ok := _V6, _V4 := _V6} ->
+ ok
+ after
+ ok
+ end.
+
%% aux
rand_terms(0) -> [];
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl
index c10e3158e1..ace761bf0f 100644
--- a/lib/compiler/test/match_SUITE.erl
+++ b/lib/compiler/test/match_SUITE.erl
@@ -27,6 +27,7 @@
coverage/1,grab_bag/1,literal_binary/1,
unary_op/1,eq_types/1,match_after_return/1,match_right_tuple/1,
tuple_size_in_try/1,match_boolean_list/1,
+ heisen_variables/1,
mutable_variables/1]).
-include_lib("common_test/include/ct.hrl").
@@ -45,9 +46,9 @@ groups() ->
grab_bag,literal_binary,unary_op,eq_types,
match_after_return,match_right_tuple,
tuple_size_in_try,match_boolean_list,
+ heisen_variables,
mutable_variables]}].
-
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
Config.
@@ -153,14 +154,19 @@ aliases(Config) when is_list(Config) ->
6 = tup_lit_alias({1,2,3}),
6 = tup_lit_alias_rev({1,2,3}),
- {42,42,42,42} = multiple_aliases_1(42),
- {7,7,7} = multiple_aliases_2(7),
- {{a,b},{a,b},{a,b}} = multiple_aliases_3({a,b}),
+ {1,2,3,4} = list_in_tuple({container, [1,2,3], 4}),
+ {a,b,c,d} = list_in_tuple({container, [a,b,c], d}),
+
+ {13,y,13,17,x,{y,13},17} = tuple_in_tuple({x, {y,13}, 17}),
+ {a,y,a,b,x,{y,a},b} = tuple_in_tuple({x, {y,a}, b}),
+
+ {42,42,42,42} = multiple_aliases_1(id(42)),
+ {7,7,7} = multiple_aliases_2(id(7)),
+ {{a,b},{a,b},{a,b}} = multiple_aliases_3(id({a,b})),
+ {[x,y,z],[x,y,z],[x,y,z]} = multiple_aliases_4(id([x,y,z])),
%% Lists/literals.
- {a,b} = list_alias1([a,b]),
- {a,b} = list_alias2([a,b]),
- {a,b} = list_alias3([a,b]),
+ {a,b} = list_alias(id([a,b])),
%% Multiple matches.
{'EXIT',{{badmatch,home},_}} =
@@ -261,9 +267,20 @@ three_2(A=
C) ->
{A,B,C}.
-tuple_alias({A,B,C}={X,Y,Z}) ->
+tuple_alias(Expr) ->
+ Res = tuple_alias_a(Expr),
+ Res = tuple_alias_b(Expr).
+
+tuple_alias_a({A,B,C} = {X,Y,Z}) ->
+ {A,B,C,X,Y,Z};
+tuple_alias_a({A,B} = {C,D} = {E,F}) ->
+ {A,B,C,D,E,F}.
+
+tuple_alias_b({_,_,_}=Expr) ->
+ {A,B,C} = {X,Y,Z} = Expr,
{A,B,C,X,Y,Z};
-tuple_alias({A,B}={C,D}={E,F}) ->
+tuple_alias_b({_,_}=Expr) ->
+ {A,B} = {C,D} = {E,F} = Expr,
{A,B,C,D,E,F}.
tup_lit_alias({A,B,C}={1,2,3}) ->
@@ -272,22 +289,91 @@ tup_lit_alias({A,B,C}={1,2,3}) ->
tup_lit_alias_rev({1,2,3}={A,B,C}) ->
A+B+C.
-multiple_aliases_1((A=B)=(C=D)) ->
+list_in_tuple(E) ->
+ Res = list_in_tuple_a(E),
+ Res = list_in_tuple_b(E).
+
+list_in_tuple_a({container, [_,_,_] = [A,B,C], D}) ->
+ {A,B,C,D}.
+
+list_in_tuple_b(E) ->
+ {container, [_,_,_] = [A,B,C], D} = E,
+ {A,B,C,D}.
+
+tuple_in_tuple(Expr) ->
+ Res = tuple_in_tuple_a(Expr),
+ Res = tuple_in_tuple_b(Expr).
+
+tuple_in_tuple_a({x, {y,A} = {B,C}, D} = {E, F, G}) ->
+ {A,B,C,D,E,F,G}.
+
+tuple_in_tuple_b(Expr) ->
+ {x, {y,A} = {B,C}, D} = {E, F, G} = Expr,
+ {A,B,C,D,E,F,G}.
+
+multiple_aliases_1(Expr) ->
+ Res = multiple_aliases_1a(Expr),
+ Res = multiple_aliases_1b(Expr).
+
+multiple_aliases_1a((A=B) = (C=D)) ->
{A,B,C,D}.
-multiple_aliases_2((A=B)=(A=C)) ->
+multiple_aliases_1b(Expr) ->
+ (A=B) = (C=D) = Expr,
+ {A,B,C,D}.
+
+multiple_aliases_2((A=B) = (A=C)) ->
+ {A,B,C}.
+
+multiple_aliases_3(Expr) ->
+ Res = multiple_aliases_3a(Expr),
+ Res = multiple_aliases_3b(Expr).
+
+multiple_aliases_3a((A={_,_}=B)={_,_}=C) ->
+ {A,B,C}.
+
+multiple_aliases_3b(Expr) ->
+ (A={_,_}=B) = {_,_} = C = Expr,
+ {A,B,C}.
+
+multiple_aliases_4(Expr) ->
+ Res = multiple_aliases_4a(Expr),
+ Res = multiple_aliases_4b(Expr).
+
+multiple_aliases_4a((A=[_,_,_]=B) = [_,_,_] = C) ->
{A,B,C}.
-multiple_aliases_3((A={_,_}=B)={_,_}=C) ->
+multiple_aliases_4b(Expr) ->
+ (A=[_,_,_]=B) = [_,_,_] = C = Expr,
{A,B,C}.
-list_alias1([a,b]=[X,Y]) ->
+list_alias(Expr) ->
+ Res = list_alias1a(Expr),
+ Res = list_alias1b(Expr),
+ Res = list_alias2a(Expr),
+ Res = list_alias2b(Expr),
+ Res = list_alias3a(Expr),
+ Res = list_alias3b(Expr).
+
+list_alias1a([a,b]=[X,Y]) ->
+ {X,Y}.
+
+list_alias1b(Expr) ->
+ [a,b] = [X,Y] = Expr,
+ {X,Y}.
+
+list_alias2a([X,Y]=[a,b]) ->
+ {X,Y}.
+
+list_alias2b(Expr) ->
+ [X,Y] = [a,b] = Expr,
{X,Y}.
-list_alias2([X,Y]=[a,b]) ->
+list_alias3a([X,b]=[a,Y]) ->
{X,Y}.
-list_alias3([X,b]=[a,Y]) ->
+list_alias3b(Expr) ->
+ [X,b] = [a,Y]= Expr,
{X,Y}.
non_matching_aliases(_Config) ->
@@ -322,6 +408,9 @@ non_matching_aliases(_Config) ->
{'EXIT',{{case_clause,whatever},_}} = (catch pike1(whatever)),
{'EXIT',{{case_clause,whatever},_}} = (catch pike2(whatever)),
+ {'EXIT',{badarith,_}} = catch squid(a),
+ {'EXIT',{{badmatch,43},_}} = catch squid(42),
+
ok.
mixed_aliases(<<X:8>> = x) -> {a,X};
@@ -404,6 +493,11 @@ pike2(X) ->
end,
Var.
+squid(E) ->
+ ([X] = {Y}) = V = E + 1,
+ {V,X + Y}.
+
+
%% OTP-7018.
match_in_call(Config) when is_list(Config) ->
@@ -710,6 +804,7 @@ match_map(Config) when is_list(Config) ->
Map = #{key=>{x,y},ignore=>anything},
#s{map=Map,t={x,y}} = do_match_map(#s{map=Map}),
{a,#{k:={a,b,c}}} = do_match_map_2(#{k=>{a,b,c}}),
+ {'EXIT',{{badmatch,whatever},_}} = catch do_match_map_none(id(whatever)),
ok.
do_match_map(#s{map=#{key:=Val}}=S) ->
@@ -722,6 +817,17 @@ do_match_map_2(Map) ->
Tuple
end.
+do_match_map_none(V) ->
+ %% Cover handling of has_map_fields in beam_validator.
+ #{42 := _} = try
+ {} = {{} = V}
+ catch
+ throw:V ->
+ #{};
+ throw:_ ->
+ V
+ end.
+
map_vars_used(Config) when is_list(Config) ->
{some,value} = do_map_vars_used(a, b, #{{a,b}=>42,v=>{some,value}}),
ok.
@@ -753,6 +859,15 @@ coverage(Config) when is_list(Config) ->
%% Cover beam_ssa_opt.
ok = coverage_6(),
+ %% Cover beam_ssa_dead.
+ a = coverage_7(x, x, id(true)),
+ b = coverage_7(x, 0, id(false)),
+
+ {'EXIT',{{badmatch,{42}},_}} = catch coverage_8(id(42)),
+
+ error = coverage_9(id(1)),
+ true = coverage_9(id(0)),
+
ok.
coverage_1(B, Tag) ->
@@ -797,6 +912,29 @@ coverage_6() ->
error([error,X,V])
end.
+%% Cover beam_ssa_dead:opt_switch_1/3.
+coverage_7(_, _, true) ->
+ a;
+coverage_7(_, 0, false) ->
+ b;
+coverage_7(_, _, true) ->
+ c.
+
+%% Cover beam_ssa_dead:will_succeed_*
+coverage_8(V) ->
+ V =/= (V = {V}).
+
+coverage_9(V) when V == 0 ->
+ -1 /= try ok of
+ _ ->
+ V
+ catch
+ _ ->
+ ok
+ end;
+coverage_9(_) ->
+ error.
+
grab_bag(_Config) ->
[_|T] = id([a,b,c]),
[b,c] = id(T),
@@ -1020,6 +1158,19 @@ match_boolean_list(Config) when is_list(Config) ->
[false | _] -> ok
end.
+
+heisen_variables(_Config) ->
+ {'EXIT',{{badmatch,3},_}} = catch gh_6516_scope1(),
+ {'EXIT',{{badmatch,3},_}} = catch gh_6516_scope2(),
+
+ ok.
+
+gh_6516_scope1() ->
+ {X = 4, X = 3}.
+
+gh_6516_scope2() ->
+ {X = 4, _ = X = 3}.
+
%% GH-6873. Bound variables would be overwritten.
mutable_variables(_Config) ->
{'EXIT',{{badmatch,0},_}} = catch mutable_variables_1(),
@@ -1040,4 +1191,5 @@ mutable_variables_2(Middle, Fun) ->
{tag,V} = Middle = Fun(),
V.
+
id(I) -> I.
diff --git a/lib/compiler/test/mc_SUITE.erl b/lib/compiler/test/mc_SUITE.erl
new file mode 100644
index 0000000000..9da2b44479
--- /dev/null
+++ b/lib/compiler/test/mc_SUITE.erl
@@ -0,0 +1,281 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% Originally based on Per Gustafsson's test suite.
+%%
+
+-module(mc_SUITE).
+
+-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ basic/1,duplicate_keys/1,mixed/1,
+ shadow/1,bad_generators/1]).
+
+-include_lib("common_test/include/ct.hrl").
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group,p}].
+
+groups() ->
+ [{p,test_lib:parallel(),
+ [basic,
+ duplicate_keys,
+ mixed,
+ shadow,
+ bad_generators]}].
+
+init_per_suite(Config) ->
+ test_lib:recompile(?MODULE),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+basic(_Config) ->
+ mc_double(0),
+ mc_double(1),
+ mc_double(2),
+ mc_double(3),
+ mc_double(4),
+ mc_double(5),
+
+ mc_double(17),
+ mc_double(18),
+ mc_double(19),
+
+ mc_double(30),
+ mc_double(31),
+ mc_double(32),
+ mc_double(33),
+ mc_double(34),
+
+ mc_double(63),
+ mc_double(64),
+ mc_double(65),
+
+ mc_double(77),
+ mc_double(127),
+ mc_double(128),
+ mc_double(255),
+ mc_double(333),
+ mc_double(444),
+ mc_double(7777),
+ mc_double(8765),
+
+ %% Patterns that cannot possibly match.
+ #{} = #{K => V || K := [V]={V} <- id(#{a => [b]})},
+ #{} = #{K => V || [K] = 42 := V <- id(#{42 => whatever})},
+
+ %% Filtering.
+ Map = #{{a,1} => {a,b,c}, {b,42} => [1,2,3], c => [4,5,6], d => {x,y}},
+ [c, d] = lists:sort([K || K := _ <- Map, is_atom(K)]),
+ [{a,b,c}, [1,2,3]] = lists:sort([V || {_,_} := V <- Map]),
+ [1] = [H || {_,_} := [H|_] <- Map],
+ [c, {b,42}] = lists:sort([K || K := [_|_] <- Map]),
+
+ %% Filtering using literal patterns.
+ [] = [0 || a := b <- #{}],
+ [] = [0 || a := b <- #{x => y}],
+ [0] = [0 || a := b <- #{a => b}],
+
+ <<>> = << <<0>> || a := b <- #{} >>,
+ <<>> = << <<0>> || a := b <- #{x => y} >>,
+ <<0>> = << <<0>> || a := b <- #{a => b} >>,
+
+ ok.
+
+mc_double(Size) ->
+ Seq = lists:seq(1, Size),
+ Map = #{{key,I} => I || I <- Seq},
+
+ MapDouble = #{K => 2 * V || K := V <- id(Map)},
+ MapDouble = maps:from_list([{{key,I}, 2 * I} || I <- Seq]),
+
+ OddKeys = lists:seq(1, Size, 2),
+ OddKeys = lists:sort([I || {key,I} := I <- Map,
+ I rem 2 =/= 0]),
+
+ OddMap = #{I => [] || {key,I} := I <- Map,
+ I rem 2 =/= 0},
+ OddMap = #{I => [] || {key,I} := I <- Map,
+ id(I) rem 2 =/= 0},
+ OddKeys = lists:sort(maps:keys(OddMap)),
+
+ %% Test that map comprehensions works on iterators.
+ test_iterator(Map, 0),
+ test_iterator(Map, map_size(Map) div 3),
+ test_iterator(Map, map_size(Map) div 2),
+ test_iterator(Map, map_size(Map)),
+
+ ok.
+
+test_iterator(Map, N) ->
+ Iter0 = maps:iterator(Map),
+ {First,Iter} = grab(N, Iter0, []),
+ All = [{K,V} || K := V <- Iter] ++ First,
+ Map = maps:from_list(All),
+ ok.
+
+grab(0, Iter, Acc) ->
+ {Acc,Iter};
+grab(N, Iter0, Acc) ->
+ case maps:next(Iter0) of
+ none ->
+ {Acc,Iter0};
+ {K,V,Iter} ->
+ grab(N - 1, Iter, [{K,V}|Acc])
+ end.
+
+duplicate_keys(_Config) ->
+ #{x := b} = #{V => K || {K,V} <- [{a, x}, {b, x}]},
+
+ #{a := 4, b := 4} =
+ #{K => V || K <- [a,b],
+ <<V>> <= <<1,2,3,4>>},
+ ok.
+
+mixed(_Config) ->
+ Map = id(#{1 => 10, 2 => 5, 3 => 88, 4 => 99, 5 => 36}),
+ Bin = << <<K:8,V:24>> || K := V <- Map >>,
+ Map = maps:from_list([{K,V} || <<K:8,V:24>> <= Bin]),
+
+ Atoms = [list_to_atom([C]) || C <- lists:seq($a, $z)],
+ Integers = lists:seq(1, 64),
+
+ mixed_1(Atoms, Integers),
+ mixed_2(Atoms, Integers),
+ mixed_3(Atoms, Integers),
+
+ sum_of_triangular_numbers(7),
+ sum_of_triangular_numbers(10),
+
+ ok.
+
+mixed_1(Atoms, Integers) ->
+ IntegerMap = #{N => [] || N <- Integers},
+ IntegerKeys = [N || N := [] <- IntegerMap],
+ Integers = lists:sort(IntegerKeys),
+ Combined = [{C,N} || C <- Atoms, N := [] <- IntegerMap],
+ Combined = [{C,N} || C <- Atoms, N := [] <- maps:iterator(IntegerMap)],
+ Combined = [{C,N} || C <- Atoms, N <- IntegerKeys],
+
+ ok.
+
+mixed_2(Atoms, Integers) ->
+ IntegerMap = #{N => [] || N <- Integers},
+ IntegerKeys = [N || N := [] <- IntegerMap],
+ Bin = << <<N:16>> || N := [] <- IntegerMap >>,
+ Integers = lists:sort(IntegerKeys),
+
+ Combined = [{C,N} || N := [] <- IntegerMap, C <- Atoms],
+ Combined = [{C,N} || N := [] <- maps:iterator(IntegerMap), C <- Atoms],
+ Combined = [{C,N} || <<N:16>> <= Bin, C <- Atoms],
+ Combined = [{C,N} || N <- IntegerKeys, C <- Atoms],
+
+ ok.
+
+mixed_3(Atoms, Integers) ->
+ Map = #{K => V || {K,V} <- lists:zip(Atoms, Integers, trim)},
+ Bin = << <<N:16>> || _ := N <- Map >>,
+ {TrimmedAtoms,TrimmedIntegers} = lists:unzip([{K,V} || K := V <- Map]),
+
+ Combined = lists:sort([{K,V} || K := _ <- Map, _ := V <- Map]),
+ Combined = lists:sort([{K,V} || K <- TrimmedAtoms, <<V:16>> <= Bin]),
+ Combined = lists:sort([{K,V} || K <- TrimmedAtoms, V <- TrimmedIntegers]),
+
+ ok.
+
+sum_of_triangular_numbers(N) ->
+ Sum = N * (N + 1) * (N + 2) div 6,
+ Maps = [#{I => I || I <- lists:seq(0, I)} || I <- lists:seq(0, N)],
+ Numbers = [I || M <- Maps, I := I <- M],
+ Numbers = lists:flatten([[I || I := I <- M] || M <- Maps]),
+ Sum = lists:sum([lists:sum([I || I := I <- M]) || M <- Maps]),
+ Sum = lists:sum(Numbers),
+ ok.
+
+shadow(_Config)->
+ Shadowed = nomatch,
+ _ = id(Shadowed), %Eliminate warning.
+ Map = #{Shadowed => Shadowed+1 || Shadowed <- lists:seq(7, 9)},
+ #{7 := 8, 8 := 9, 9 := 10} = id(Map),
+ [8,9] = lists:sort([Shadowed || _ := Shadowed <- id(Map),
+ Shadowed < 10]),
+ ok.
+
+bad_generators(_Config) ->
+ %% Make sure that line numbers point out the generator.
+ case ?MODULE of
+ mc_inline_SUITE ->
+ ok;
+ _ ->
+ {'EXIT',{{bad_generator,a},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,4}]}|_]}} =
+ catch id(bad_generator(a)),
+
+ {'EXIT',{{bad_generator,a},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,7}]}|_]}} =
+ catch id(bad_generator_bc(a)),
+
+ {'EXIT',{{bad_generator,a},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,10}]}|_]}} =
+ catch id(bad_generator_mc(a)),
+
+ BadIterator = [16#ffff|#{}],
+
+ {'EXIT',{{bad_generator,BadIterator},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,4}]}|_]}} =
+ catch id(bad_generator(BadIterator)),
+
+ {'EXIT',{{bad_generator,BadIterator},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,7}]}|_]}} =
+ catch id(bad_generator_bc(BadIterator)),
+
+ {'EXIT',{{bad_generator,BadIterator},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,10}]}|_]}} =
+ catch id(bad_generator_mc(BadIterator))
+ end,
+ ok.
+
+id(I) -> I.
+
+-file("bad_mc.erl", 1).
+bad_generator(Map) -> %Line 2
+ [{K,V} || %Line 3
+ K := V <- Map]. %Line 4
+bad_generator_bc(Map) -> %Line 5
+ << <<K:8,V:24>> || %Line 6
+ K := V <- Map>>. %Line 7
+bad_generator_mc(Map) -> %Line 8
+ #{V => K || %Line 9
+ K := V <- Map}. %Line 10
diff --git a/lib/compiler/test/property_test/beam_types_prop.erl b/lib/compiler/test/property_test/beam_types_prop.erl
index b70de8626b..5477ff3692 100644
--- a/lib/compiler/test/property_test/beam_types_prop.erl
+++ b/lib/compiler/test/property_test/beam_types_prop.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -184,10 +184,11 @@ term_type(Depth) ->
term_types(Depth) ->
nested_generators(Depth) ++
numerical_generators() ++
- [gen_atom(), gen_bs_matchable()].
+ [gen_atom(), gen_bs_matchable(),
+ pid, port, reference, identifier, other].
numerical_generators() ->
- [gen_integer(), gen_float(), number].
+ [gen_integer(), gen_float(), gen_number()].
nested_generators(Depth) when Depth =< 0 ->
[nil];
@@ -216,13 +217,24 @@ gen_atom() ->
gen_bs_matchable() ->
oneof([?LET(Unit, range(1, 16), #t_bs_matchable{tail_unit=Unit}),
?LET(Unit, range(1, 16), #t_bs_context{tail_unit=Unit}),
- ?LET(Unit, range(1, 16), #t_bitstring{size_unit=Unit})]).
+ ?LET({Unit, Appendable}, {range(1, 16), boolean()},
+ #t_bitstring{size_unit=Unit,appendable=Appendable})]).
gen_float() ->
oneof([?LET({A, B}, {integer(), integer()},
begin
- Min = float(min(A,B)),
- Max = float(max(A,B)),
+ Min = float(min(A, B)),
+ Max = float(max(A, B)),
+ #t_float{elements={Min,Max}}
+ end),
+ ?LET({A, B, AExp, BExp},
+ {integer(0, 1_000_000), integer(0, 10_00_000),
+ integer(-300, 300), integer(-300, 300)},
+ begin
+ F1 = A * math:pow(10, AExp),
+ F2 = B * math:pow(10, BExp),
+ Min = min(F1, F2),
+ Max = max(F1, F2),
#t_float{elements={Min,Max}}
end),
#t_float{}]).
@@ -235,6 +247,10 @@ gen_fun(Depth) ->
gen_integer() ->
oneof([?LET({A, B}, {integer(), integer()},
#t_integer{elements={min(A,B), max(A,B)}}),
+ ?LET(Min, integer(),
+ #t_integer{elements={Min, '+inf'}}),
+ ?LET(Max, integer(),
+ #t_integer{elements={'-inf', Max}}),
#t_integer{}]).
gen_list(Depth) ->
@@ -250,6 +266,15 @@ gen_map(Depth) ->
#t_map{super_key=SKey,super_value=SValue}),
[#t_map{}]).
+gen_number() ->
+ oneof([?LET({A, B}, {integer(), integer()},
+ #t_number{elements={min(A,B), max(A,B)}}),
+ ?LET(Min, integer(),
+ #t_number{elements={Min, '+inf'}}),
+ ?LET(Max, integer(),
+ #t_number{elements={'-inf', Max}}),
+ #t_number{}]).
+
gen_tuple(Depth) ->
?SHRINK(oneof([gen_tuple_plain(Depth), gen_tuple_record(Depth)]),
[#t_tuple{}]).
diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl
index 5b52dd8288..deb364df39 100644
--- a/lib/compiler/test/receive_SUITE.erl
+++ b/lib/compiler/test/receive_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -200,7 +200,7 @@ coverage(Config) when is_list(Config) ->
receive_in_called_function() ->
RefA = make_ref(),
- RefB = make_ref(),
+ RefB = returns_reference(),
self() ! hello,
self() ! RefA,
@@ -222,6 +222,9 @@ receive_in_called_function() ->
ok.
+returns_reference() ->
+ make_ref().
+
ricf_1(A, B) ->
%% Both A and B are fed a reference at least once, so both of these loops
%% ought to be optimized.
diff --git a/lib/compiler/test/record_SUITE.erl b/lib/compiler/test/record_SUITE.erl
index cd60525691..a098529380 100644
--- a/lib/compiler/test/record_SUITE.erl
+++ b/lib/compiler/test/record_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -238,6 +238,14 @@ record_test_2(Config) when is_list(Config) ->
begin not is_record(X, foo) or
is_reference(X) end],
+ Map = id(#{a => 1, b => #foo{a=2}, c => 3, d => #bar{d=4},
+ e => 5, f => #foo{a=6}, h => 7}),
+ [#foo{a=2},#foo{a=6}] = lists:sort([X || _ := X <- Map, is_record(X, foo)]),
+ [#bar{d=4}] = [X || _ := X <- Map, is_record(X, bar)],
+ [1,3,5,7,#foo{a=2},#foo{a=6}] =
+ lists:sort([X || _ := X <- Map, not is_record(X, bar)]),
+ [2,6] = lists:sort([A || _ := #foo{a=A} <- Map]),
+
%% Call is_record/2 with illegal arguments.
[] = [X || X <- [], is_record(t, id(X))],
{'EXIT',{badarg,_}} = (catch [X || X <- [1], is_record(t, id(X))]),
@@ -699,6 +707,34 @@ grab_bag(_Config) ->
error = T5(#gb_bar{}),
error = T5(atom),
+ %% With type optimizations disabled, beam_ssa_pre_codegen would insert
+ %% set_tuple_element instructions between the call to setelement/3 and
+ %% its succeeded instruction.
+ T6 = fun(R) ->
+ try
+ %% The succeeded instruction should immediately follow its instruction.
+ %% Not like this:
+ %%
+ %% x0/_212 = call (`erlang`:`setelement`/3), `5`, y0/_87:37, `4`
+ %% z0/@ssa_dummy:34 = set_tuple_element `3`, x0/_212, `3`
+ %% z0/@ssa_dummy:35 = set_tuple_element `2`, x0/_212, `2`
+ %% z0/@ssa_dummy:36 = set_tuple_element `1`, x0/_212, `1`
+ %% z0/@ssa_bool:13 = succeeded x0/_212
+ %% br z0/@ssa_bool:13, ^14, ^4
+ R#foo{a=1,b=2,c=3,d=4}
+ of
+ 42 ->
+ ok
+ catch
+ _:_ ->
+ error
+ end
+ end,
+ error = catch T6(100),
+ error = catch T6([a,b,c]),
+ error = catch T6(#bar{}),
+ {'EXIT',{{try_clause,#foo{}},_}} = catch T6(#foo{}),
+
ok.
%% ERIERL-436; the following code used to be very slow to compile.
diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl
index c4d377e4f1..a6e75f232c 100644
--- a/lib/compiler/test/test_lib.erl
+++ b/lib/compiler/test/test_lib.erl
@@ -92,6 +92,7 @@ opt_opts(Mod) ->
(inline) -> true;
(no_bs_create_bin) -> true;
(no_bsm_opt) -> true;
+ (no_bs_match) -> true;
(no_copt) -> true;
(no_fun_opt) -> true;
(no_init_yregs) -> true;
@@ -119,6 +120,7 @@ get_data_dir(Config) ->
Opts = [{return,list}],
Suffixes = ["_no_opt_SUITE",
"_no_copt_SUITE",
+ "_no_copt_ssa_SUITE",
"_post_opt_SUITE",
"_inline_SUITE",
"_no_module_opt_SUITE",
@@ -138,6 +140,7 @@ is_cloned_mod(Mod) ->
is_cloned_mod_1("_no_opt_SUITE") -> true;
is_cloned_mod_1("_no_copt_SUITE") -> true;
+is_cloned_mod_1("_no_copt_ssa_SUITE") -> true;
is_cloned_mod_1("_no_ssa_opt_SUITE") -> true;
is_cloned_mod_1("_no_type_opt_SUITE") -> true;
is_cloned_mod_1("_post_opt_SUITE") -> true;
diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl
index 5f61485b4c..df45210982 100644
--- a/lib/compiler/test/warnings_SUITE.erl
+++ b/lib/compiler/test/warnings_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2023. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -158,10 +158,15 @@ pattern3(Config) when is_list(Config) ->
f({A,_}) -> {ok,A};
f([_|_]=B) -> {ok,B};
f({urk,nisse}) -> urka_glurka.
+ word(<<\"AND\">>) -> <<\"and\">>;
+ word(<<\"AS\">>) -> <<\"as\">>;
+ word(<<\"A\">>) -> <<\"a\">>;
+ word(<<\"AS\">>) -> <<\"as\">>.
">>,
[nowarn_unused_vars],
{warnings,
- [{{4,13},v3_kernel,{nomatch,{shadow,2}}}]}}],
+ [{{4,13},v3_kernel,{nomatch,{shadow,2}}},
+ {{8,13},v3_kernel,{nomatch,{shadow,6}}}]}}],
[] = run(Config, Ts),
ok.
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index 0c4eae4d45..fa46ea4097 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 8.2.5
+COMPILER_VSN = 8.2.6
diff --git a/lib/crypto/c_src/algorithms.c b/lib/crypto/c_src/algorithms.c
index 53b574af3a..be19286509 100644
--- a/lib/crypto/c_src/algorithms.c
+++ b/lib/crypto/c_src/algorithms.c
@@ -29,7 +29,7 @@
#ifdef HAS_3_0_API
#else
static unsigned int algo_hash_cnt, algo_hash_fips_cnt;
-static ERL_NIF_TERM algo_hash[14]; /* increase when extending the list */
+static ERL_NIF_TERM algo_hash[16]; /* increase when extending the list */
void init_hash_types(ErlNifEnv* env);
#endif
@@ -113,6 +113,12 @@ void init_hash_types(ErlNifEnv* env) {
#ifdef HAVE_SHA3_512
algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_512");
#endif
+#ifdef HAVE_SHAKE128
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "shake128");
+#endif
+#ifdef HAVE_SHAKE256
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "shake256");
+#endif
#ifdef HAVE_BLAKE2
algo_hash[algo_hash_cnt++] = enif_make_atom(env, "blake2b");
algo_hash[algo_hash_cnt++] = enif_make_atom(env, "blake2s");
diff --git a/lib/crypto/c_src/check_openssl.cocci b/lib/crypto/c_src/check_openssl.cocci
index 75d1a6e44b..69259f7638 100644
--- a/lib/crypto/c_src/check_openssl.cocci
+++ b/lib/crypto/c_src/check_openssl.cocci
@@ -85,6 +85,8 @@
// EVP_sha3_256
// EVP_sha3_384
// EVP_sha3_512
+// EVP_shake128
+// EVP_shake256
// EVP_sha512
// OpenSSL_version
// OpenSSL_version_num
@@ -183,7 +185,7 @@ position p;
@openssl_check_1@
expression X;
identifier L;
-identifier FUNC1 =~ "^(EVP_CIPHER_CTX_copy|EVP_CIPHER_CTX_ctrl|EVP_CIPHER_CTX_rand_key|EVP_CIPHER_CTX_reset|EVP_CIPHER_CTX_set_key_length|EVP_CIPHER_CTX_set_padding|EVP_CipherFinal_ex|EVP_CipherInit_ex|EVP_CipherUpdate|EVP_DecryptFinal_ex|EVP_DecryptInit_ex|EVP_DecryptUpdate|EVP_Digest|EVP_DigestFinal|EVP_DigestFinal_ex|EVP_DigestInit|EVP_DigestInit_ex|EVP_DigestSign|EVP_DigestSignInit|EVP_DigestSignUpdate|EVP_DigestSignaFinal|EVP_DigestUpdate|EVP_DigestVerify|EVP_DigestVerifyInit|EVP_EncryptFinal_ex|EVP_EncryptInit_ex|EVP_EncryptUpdate|EVP_MAC_CTX_copy|EVP_MAC_ctrl|EVP_MAC_ctrl_str|EVP_MAC_hex2ctrl|EVP_MAC_init|EVP_MAC_reset|EVP_MAC_str2ctrl|EVP_MAC_update|EVP_MD_CTX_copy|EVP_MD_CTX_copy_ex|EVP_MD_CTX_ctrl|EVP_MD_meth_set_app_datasize|EVP_MD_meth_set_cleanup|EVP_MD_meth_set_copy|EVP_MD_meth_set_ctrl|EVP_MD_meth_set_final|EVP_MD_meth_set_flags|EVP_MD_meth_set_init|EVP_MD_meth_set_input_blocksize|EVP_MD_meth_set_result_size|EVP_MD_meth_set_update|EVP_PKEY_CTX_set_rsa_mgf1_md|EVP_PKEY_CTX_set_rsa_padding|EVP_PKEY_CTX_set_rsa_pss_saltlen|EVP_PKEY_CTX_set_signature|EVP_PKEY_assign|EVP_PKEY_assign_DSA|EVP_PKEY_assign_EC_KEY|EVP_PKEY_assign_RSA|EVP_PKEY_decrypt|EVP_PKEY_decrypt_init|EVP_PKEY_derive|EVP_PKEY_derive_init|EVP_PKEY_derive_set_peer|EVP_PKEY_encrypt|EVP_PKEY_encrypt_init|EVP_PKEY_get1_DH|EVP_PKEY_get_raw_private_key|EVP_PKEY_get_raw_public_key|EVP_PKEY_keygen|EVP_PKEY_keygen_init|EVP_PKEY_set1_DH|EVP_PKEY_sign|EVP_PKEY_sign_init|EVP_PKEY_verify|EVP_PKEY_verify_init|EVP_PKEY_verify_recover|EVP_PKEY_verify_recover_init|EVP_add_mac|RAND_bytes|RAND_priv_bytes)$";
+identifier FUNC1 =~ "^(EVP_CIPHER_CTX_copy|EVP_CIPHER_CTX_ctrl|EVP_CIPHER_CTX_rand_key|EVP_CIPHER_CTX_reset|EVP_CIPHER_CTX_set_key_length|EVP_CIPHER_CTX_set_padding|EVP_CipherFinal_ex|EVP_CipherInit_ex|EVP_CipherUpdate|EVP_DecryptFinal_ex|EVP_DecryptInit_ex|EVP_DecryptUpdate|EVP_Digest|EVP_DigestFinal|EVP_DigestFinal_ex|EVP_DigestFinalXOF|EVP_DigestInit|EVP_DigestInit_ex|EVP_DigestSign|EVP_DigestSignInit|EVP_DigestSignUpdate|EVP_DigestSignaFinal|EVP_DigestUpdate|EVP_DigestVerify|EVP_DigestVerifyInit|EVP_EncryptFinal_ex|EVP_EncryptInit_ex|EVP_EncryptUpdate|EVP_MAC_CTX_copy|EVP_MAC_ctrl|EVP_MAC_ctrl_str|EVP_MAC_hex2ctrl|EVP_MAC_init|EVP_MAC_reset|EVP_MAC_str2ctrl|EVP_MAC_update|EVP_MD_CTX_copy|EVP_MD_CTX_copy_ex|EVP_MD_CTX_ctrl|EVP_MD_meth_set_app_datasize|EVP_MD_meth_set_cleanup|EVP_MD_meth_set_copy|EVP_MD_meth_set_ctrl|EVP_MD_meth_set_final|EVP_MD_meth_set_flags|EVP_MD_meth_set_init|EVP_MD_meth_set_input_blocksize|EVP_MD_meth_set_result_size|EVP_MD_meth_set_update|EVP_PKEY_CTX_set_rsa_mgf1_md|EVP_PKEY_CTX_set_rsa_padding|EVP_PKEY_CTX_set_rsa_pss_saltlen|EVP_PKEY_CTX_set_signature|EVP_PKEY_assign|EVP_PKEY_assign_DSA|EVP_PKEY_assign_EC_KEY|EVP_PKEY_assign_RSA|EVP_PKEY_decrypt|EVP_PKEY_decrypt_init|EVP_PKEY_derive|EVP_PKEY_derive_init|EVP_PKEY_derive_set_peer|EVP_PKEY_encrypt|EVP_PKEY_encrypt_init|EVP_PKEY_get1_DH|EVP_PKEY_get_raw_private_key|EVP_PKEY_get_raw_public_key|EVP_PKEY_keygen|EVP_PKEY_keygen_init|EVP_PKEY_set1_DH|EVP_PKEY_sign|EVP_PKEY_sign_init|EVP_PKEY_verify|EVP_PKEY_verify_init|EVP_PKEY_verify_recover|EVP_PKEY_verify_recover_init|EVP_add_mac|RAND_bytes|RAND_priv_bytes)$";
position p;
@@
@@ -252,7 +254,7 @@ position pnull != openssl_check_null.p;
identifier FUNCNOT =~ "^(BN_add|BN_div|BN_exp|BN_from_montgomery|BN_gcd|BN_generate_prime_ex|BN_mod|BN_mod_add|BN_mod_exp|BN_mod_mul|BN_mod_mul_montgomery|BN_mod_sqr|BN_mod_sub|BN_mul|BN_nnmod|BN_priv_rand|BN_priv_rand_range|BN_pseudo_rand|BN_pseudo_rand_range|BN_rand|BN_rand_range|BN_set_bit|BN_set_word|BN_sqr|BN_sub|BN_to_montgomery|CMAC_Final|CMAC_Init|CMAC_Update|CRYPTO_set_mem_debug|CRYPTO_set_mem_functions|DH_check|DH_check_ex|DH_check_params|DH_check_pub_key_ex|DH_generate_key|DH_generate_parameters_ex|DH_set0_key|DH_set0_pqg|DH_set_length|DSA_set0_key|DSA_set0_pqg|EC_GROUP_check|EC_GROUP_check_discriminant|EC_GROUP_copy|EC_GROUP_get_curve_name|EC_GROUP_get_pentanomial_basis|EC_GROUP_get_trinomial_basis|EC_GROUP_precompute_mult|EC_GROUP_set_generator|EC_GROUP_set_seed|EC_KEY_check_key|EC_KEY_generate_key|EC_KEY_key2buf|EC_KEY_oct2key|EC_KEY_oct2priv|EC_KEY_precompute_mult|EC_KEY_priv2buf|EC_KEY_priv2oct|EC_KEY_set_group|EC_KEY_set_private_key|EC_KEY_set_public_key|EC_KEY_set_public_key_affine_coordinates|EC_KEY_up_ref|EC_POINT_add|EC_POINT_copy|EC_POINT_dbl|EC_POINT_get_Jprojective_coordinates_GFp|EC_POINT_get_affine_coordinates_GF2m|EC_POINT_get_affine_coordinates_GFp|EC_POINT_invert|EC_POINT_make_affine|EC_POINT_mul|EC_POINT_oct2point|EC_POINT_point2oct|EC_POINT_set_Jprojective_coordinates_GFp|EC_POINT_set_affine_coordinates_GF2m|EC_POINT_set_affine_coordinates_GFp|EC_POINT_set_compressed_coordinates_GF2m|EC_POINT_set_compressed_coordinates_GFp|EC_POINT_set_to_infinity|EC_POINTs_make_affine|EC_POINTs_mul|ENGINE_add|ENGINE_ctrl_cmd|ENGINE_ctrl_cmd_string|ENGINE_finish|ENGINE_free|ENGINE_init|ENGINE_register_DH|ENGINE_register_DSA|ENGINE_register_EC|ENGINE_register_RAND|ENGINE_register_RSA|ENGINE_register_all_complete|ENGINE_register_ciphers|ENGINE_register_complete|ENGINE_register_digests|ENGINE_register_pkey_asn1_meths|ENGINE_register_pkey_meths|ENGINE_remove|ENGINE_set_RSA|ENGINE_set_default|ENGINE_set_default_DH|ENGINE_set_default_DSA|ENGINE_set_default_EC|ENGINE_set_default_RAND|ENGINE_set_default_RSA|ENGINE_set_digests|ENGINE_set_id|ENGINE_set_init_function|ENGINE_set_load_privkey_function|ENGINE_set_load_pubkey_function|ENGINE_set_name|ENGINE_up_ref|HMAC_CTX_copy|HMAC_CTX_reset|HMAC_Final|HMAC_Init_ex|HMAC_Update|MD2_Init|MD2_Update|MD2_Final|MD4_Init|MD4_Update|MD4_Final|MD5_Init|MD5_Update|MD5_Final|OPENSSL_init_crypto|OPENSSL_mem_debug_pop|OPENSSL_mem_debug_push|RSA_generate_key_ex|RSA_generate_multi_prime_key|RSA_meth_set_finish|RSA_meth_set_sign|RSA_meth_set_verify|RSA_padding_add_SSLv23|RSA_set0_crt_params|RSA_set0_factors|RSA_set0_key|RSA_set0_multi_prime_params)$";
position pnot != openssl_check_not.p;
-identifier FUNC1 =~ "^(EVP_CIPHER_CTX_copy|EVP_CIPHER_CTX_ctrl|EVP_CIPHER_CTX_rand_key|EVP_CIPHER_CTX_reset|EVP_CIPHER_CTX_set_key_length|EVP_CIPHER_CTX_set_padding|EVP_CipherFinal_ex|EVP_CipherInit_ex|EVP_CipherUpdate|EVP_DecryptFinal_ex|EVP_DecryptInit_ex|EVP_DecryptUpdate|EVP_Digest|EVP_DigestFinal|EVP_DigestFinal_ex|EVP_DigestInit|EVP_DigestInit_ex|EVP_DigestSign|EVP_DigestSignInit|EVP_DigestSignUpdate|EVP_DigestSignaFinal|EVP_DigestUpdate|EVP_DigestVerify|EVP_DigestVerifyInit|EVP_EncryptFinal_ex|EVP_EncryptInit_ex|EVP_EncryptUpdate|EVP_MAC_CTX_copy|EVP_MAC_ctrl|EVP_MAC_ctrl_str|EVP_MAC_hex2ctrl|EVP_MAC_init|EVP_MAC_reset|EVP_MAC_str2ctrl|EVP_MAC_update|EVP_MD_CTX_copy|EVP_MD_CTX_copy_ex|EVP_MD_CTX_ctrl|EVP_MD_meth_set_app_datasize|EVP_MD_meth_set_cleanup|EVP_MD_meth_set_copy|EVP_MD_meth_set_ctrl|EVP_MD_meth_set_final|EVP_MD_meth_set_flags|EVP_MD_meth_set_init|EVP_MD_meth_set_input_blocksize|EVP_MD_meth_set_result_size|EVP_MD_meth_set_update|EVP_PKEY_CTX_set_rsa_mgf1_md|EVP_PKEY_CTX_set_rsa_padding|EVP_PKEY_CTX_set_rsa_pss_saltlen|EVP_PKEY_CTX_set_signature|EVP_PKEY_assign|EVP_PKEY_assign_DSA|EVP_PKEY_assign_EC_KEY|EVP_PKEY_assign_RSA|EVP_PKEY_decrypt|EVP_PKEY_decrypt_init|EVP_PKEY_derive|EVP_PKEY_derive_init|EVP_PKEY_derive_set_peer|EVP_PKEY_encrypt|EVP_PKEY_encrypt_init|EVP_PKEY_get1_DH|EVP_PKEY_get_raw_private_key|EVP_PKEY_get_raw_public_key|EVP_PKEY_keygen|EVP_PKEY_keygen_init|EVP_PKEY_set1_DH|EVP_PKEY_sign|EVP_PKEY_sign_init|EVP_PKEY_verify|EVP_PKEY_verify_init|EVP_PKEY_verify_recover|EVP_PKEY_verify_recover_init|EVP_add_mac|RAND_bytes|RAND_priv_bytes)$";
+identifier FUNC1 =~ "^(EVP_CIPHER_CTX_copy|EVP_CIPHER_CTX_ctrl|EVP_CIPHER_CTX_rand_key|EVP_CIPHER_CTX_reset|EVP_CIPHER_CTX_set_key_length|EVP_CIPHER_CTX_set_padding|EVP_CipherFinal_ex|EVP_CipherInit_ex|EVP_CipherUpdate|EVP_DecryptFinal_ex|EVP_DecryptInit_ex|EVP_DecryptUpdate|EVP_Digest|EVP_DigestFinal|EVP_DigestFinal_ex|EVP_DigestFinalXOF|EVP_DigestInit|EVP_DigestInit_ex|EVP_DigestSign|EVP_DigestSignInit|EVP_DigestSignUpdate|EVP_DigestSignaFinal|EVP_DigestUpdate|EVP_DigestVerify|EVP_DigestVerifyInit|EVP_EncryptFinal_ex|EVP_EncryptInit_ex|EVP_EncryptUpdate|EVP_MAC_CTX_copy|EVP_MAC_ctrl|EVP_MAC_ctrl_str|EVP_MAC_hex2ctrl|EVP_MAC_init|EVP_MAC_reset|EVP_MAC_str2ctrl|EVP_MAC_update|EVP_MD_CTX_copy|EVP_MD_CTX_copy_ex|EVP_MD_CTX_ctrl|EVP_MD_meth_set_app_datasize|EVP_MD_meth_set_cleanup|EVP_MD_meth_set_copy|EVP_MD_meth_set_ctrl|EVP_MD_meth_set_final|EVP_MD_meth_set_flags|EVP_MD_meth_set_init|EVP_MD_meth_set_input_blocksize|EVP_MD_meth_set_result_size|EVP_MD_meth_set_update|EVP_PKEY_CTX_set_rsa_mgf1_md|EVP_PKEY_CTX_set_rsa_padding|EVP_PKEY_CTX_set_rsa_pss_saltlen|EVP_PKEY_CTX_set_signature|EVP_PKEY_assign|EVP_PKEY_assign_DSA|EVP_PKEY_assign_EC_KEY|EVP_PKEY_assign_RSA|EVP_PKEY_decrypt|EVP_PKEY_decrypt_init|EVP_PKEY_derive|EVP_PKEY_derive_init|EVP_PKEY_derive_set_peer|EVP_PKEY_encrypt|EVP_PKEY_encrypt_init|EVP_PKEY_get1_DH|EVP_PKEY_get_raw_private_key|EVP_PKEY_get_raw_public_key|EVP_PKEY_keygen|EVP_PKEY_keygen_init|EVP_PKEY_set1_DH|EVP_PKEY_sign|EVP_PKEY_sign_init|EVP_PKEY_verify|EVP_PKEY_verify_init|EVP_PKEY_verify_recover|EVP_PKEY_verify_recover_init|EVP_add_mac|RAND_bytes|RAND_priv_bytes)$";
position p1 != openssl_check_1.p;
identifier FUNCVOID =~ "^(AES_cfb128_encrypt|AES_cfb8_encrypt|AES_ige_encrypt|BN_GENCB_set|DSA_get0_key|DSA_get0_pqg|EC_GROUP_set_asn1_flag|EC_GROUP_set_point_conversion_form|ENGINE_get_static_state|ENGINE_unregister_DH|ENGINE_unregister_DSA|ENGINE_unregister_EC|ENGINE_unregister_RAND|ENGINE_unregister_RSA|ENGINE_unregister_ciphers|ENGINE_unregister_digests|ENGINE_unregister_pkey_asn1_meths|ENGINE_unregister_pkey_meths|OpenSSL_add_all_ciphers|OpenSSL_add_all_digests|RAND_seed|RC4|RC4_set_key|RSA_get0_crt_params|RSA_get0_factors|RSA_get0_key)$";
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index ef6d0bdc13..12abf8aca2 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -76,6 +76,7 @@ static ErlNifFunc nif_funcs[] = {
{"hash_init_nif", 1, hash_init_nif, 0},
{"hash_update_nif", 2, hash_update_nif, 0},
{"hash_final_nif", 1, hash_final_nif, 0},
+ {"hash_final_xof_nif", 2, hash_final_xof_nif, 0},
{"mac_nif", 4, mac_nif, 0},
{"mac_init_nif", 3, mac_init_nif, 0},
{"mac_update_nif", 2, mac_update_nif, 0},
diff --git a/lib/crypto/c_src/digest.c b/lib/crypto/c_src/digest.c
index d13304de49..775077cb8a 100644
--- a/lib/crypto/c_src/digest.c
+++ b/lib/crypto/c_src/digest.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2010-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2010-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -114,6 +114,22 @@ static struct digest_type_t digest_types[] =
#endif
},
+ {"shake128", "SHAKE-128", 0, 0,
+#ifdef HAVE_SHAKE128
+ {&EVP_shake128, NULL}
+#else
+ {NULL,NULL}
+#endif
+ },
+
+ {"shake256", "SHAKE-256", 0, 0,
+#ifdef HAVE_SHAKE256
+ {&EVP_shake256, NULL}
+#else
+ {NULL,NULL}
+#endif
+ },
+
{"blake2b", "BLAKE2b512", 0, 0,
#ifdef HAVE_BLAKE2
{&EVP_blake2b512,NULL}
diff --git a/lib/crypto/c_src/engine.c b/lib/crypto/c_src/engine.c
index 16de4b8a40..6fb195b813 100644
--- a/lib/crypto/c_src/engine.c
+++ b/lib/crypto/c_src/engine.c
@@ -470,7 +470,11 @@ ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TER
#ifdef HAS_ENGINE_SUPPORT
ASSERT(argc == 0);
+# if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
ENGINE_load_dynamic();
+# else
+ OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_DYNAMIC, NULL);
+# endif
return atom_ok;
#else
return atom_notsup;
diff --git a/lib/crypto/c_src/hash.c b/lib/crypto/c_src/hash.c
index ba454f5062..029dffd44b 100644
--- a/lib/crypto/c_src/hash.c
+++ b/lib/crypto/c_src/hash.c
@@ -508,3 +508,40 @@ ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
#endif /* OPENSSL_VERSION_NUMBER < 1.0 */
+
+#if defined(HAVE_SHAKE128) || defined(HAVE_SHAKE256)
+ERL_NIF_TERM hash_final_xof_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context) */
+ struct evp_md_ctx *ctx;
+ EVP_MD_CTX *new_ctx;
+ ERL_NIF_TERM ret;
+ unsigned char *outp;
+ unsigned int len;
+
+ ASSERT(argc == 2);
+ if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx))
+ return EXCP_BADARG_N(env, 0, "Bad state");
+ if (!enif_get_uint(env, argv[1], &len))
+ return EXCP_BADARG_N(env, 1, "Bad len");
+ ASSERT(0 < len);
+
+ if ((new_ctx = EVP_MD_CTX_new()) == NULL)
+ assign_goto(ret, done, EXCP_ERROR(env, "Low-level call EVP_MD_CTX_new failed"));
+ if (EVP_MD_CTX_copy(new_ctx, ctx->ctx) != 1)
+ assign_goto(ret, done, EXCP_ERROR(env, "Low-level call EVP_MD_CTX_copy failed"));
+ if ((outp = enif_make_new_binary(env, len>>3, &ret)) == NULL)
+ assign_goto(ret, done, EXCP_ERROR(env, "Can't make a new binary"));
+ if (EVP_DigestFinalXOF(new_ctx, outp, len>>3) != 1)
+ assign_goto(ret, done, EXCP_ERROR(env, "Low-level call EVP_DigestFinalXOF failed"));
+
+ done:
+ if (new_ctx)
+ EVP_MD_CTX_free(new_ctx);
+ return ret;
+}
+#else
+ERL_NIF_TERM hash_final_xof_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ return EXCP_NOTSUP(env, "Low-level EVP_DigestFinalXOF function is not supported in this cryptolib");
+}
+#endif /* defined(HAVE_SHAKE128) || defined(HAVE_SHAKE256) */
diff --git a/lib/crypto/c_src/hash.h b/lib/crypto/c_src/hash.h
index df3a43bb4b..5ac6785a33 100644
--- a/lib/crypto/c_src/hash.h
+++ b/lib/crypto/c_src/hash.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2010-2020. All Rights Reserved.
+ * Copyright Ericsson AB 2010-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,5 +30,6 @@ ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM hash_final_xof_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
#endif /* E_HASH_H__ */
diff --git a/lib/crypto/c_src/info.c b/lib/crypto/c_src/info.c
index 3021c3d71b..1458842b98 100644
--- a/lib/crypto/c_src/info.c
+++ b/lib/crypto/c_src/info.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2010-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2010-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,11 @@
#endif
+#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+#define OPENSSL_VERSION SSLEAY_VERSION
+#define OpenSSL_version SSLeay_version
+#endif
+
#ifdef HAVE_DYNAMIC_CRYPTO_LIB
char *crypto_callback_name = CB_NAME;
@@ -111,7 +116,7 @@ ERL_NIF_TERM info_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
enif_make_map_put(env, ret,
enif_make_atom(env, "cryptolib_version_linked"),
- enif_make_string(env, SSLeay_version(SSLEAY_VERSION), ERL_NIF_LATIN1),
+ enif_make_string(env, OpenSSL_version(OPENSSL_VERSION), ERL_NIF_LATIN1),
&ret);
#ifdef HAS_3_0_API
@@ -140,7 +145,7 @@ ERL_NIF_TERM info_lib(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
ASSERT(argc == 0);
name_sz = strlen(libname);
- ver = SSLeay_version(SSLEAY_VERSION);
+ ver = OpenSSL_version(OPENSSL_VERSION);
ver_sz = strlen(ver);
ver_num = OPENSSL_VERSION_NUMBER;
diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h
index 454902f266..e356cf94fb 100644
--- a/lib/crypto/c_src/openssl_config.h
+++ b/lib/crypto/c_src/openssl_config.h
@@ -190,7 +190,14 @@
# ifdef NID_sha3_256
# define HAVE_SHA3_256
# endif
+# ifdef NID_shake128
+# define HAVE_SHAKE128
+# endif
+# ifdef NID_shake256
+# define HAVE_SHAKE256
+# endif
#endif
+
# ifdef NID_sha3_384
# define HAVE_SHA3_384
# endif
diff --git a/lib/crypto/c_src/otp_test_engine.c b/lib/crypto/c_src/otp_test_engine.c
index 1530ad311c..7ead42169b 100644
--- a/lib/crypto/c_src/otp_test_engine.c
+++ b/lib/crypto/c_src/otp_test_engine.c
@@ -101,9 +101,11 @@ static int test_init(ENGINE *e) {
goto err;
#endif /* if defined(FAKE_RSA_IMPL) */
+#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
/* Load all digest and cipher algorithms. Needed for password protected private keys */
OpenSSL_add_all_ciphers();
OpenSSL_add_all_digests();
+#endif
return 111;
diff --git a/lib/crypto/doc/src/algorithm_details.xml b/lib/crypto/doc/src/algorithm_details.xml
index 767fa3cd6f..603f7ebe4f 100644
--- a/lib/crypto/doc/src/algorithm_details.xml
+++ b/lib/crypto/doc/src/algorithm_details.xml
@@ -224,6 +224,8 @@
<row><cell><c>sha3_256</c></cell> <cell>32</cell></row>
<row><cell><c>sha3_384</c></cell> <cell>48</cell></row>
<row><cell><c>sha3_512</c></cell> <cell>64</cell></row>
+ <row><cell><c>shake128</c></cell> <cell>64</cell></row>
+ <row><cell><c>shake256</c></cell> <cell>64</cell></row>
<row><cell><c>blake2b</c></cell> <cell>64</cell></row>
<row><cell><c>blake2s</c></cell> <cell>32</cell></row>
<row><cell><c>md4</c></cell> <cell>16</cell></row>
@@ -258,11 +260,11 @@
<table>
<row><cell><strong>Type</strong></cell>
<cell><strong>Names</strong></cell>
- <cell><strong>Limitated to</strong><br/><strong>OpenSSL versions</strong></cell>
+ <cell><strong>Limited to</strong><br/><strong>OpenSSL versions</strong></cell>
</row>
<row><cell>SHA1</cell><cell>sha</cell><cell></cell></row>
<row><cell>SHA2</cell><cell>sha224, sha256, sha384, sha512</cell><cell></cell></row>
- <row><cell>SHA3</cell><cell>sha3_224, sha3_256, sha3_384, sha3_512</cell><cell>&#8805;1.1.1</cell></row>
+ <row><cell>SHA3</cell><cell>sha3_224, sha3_256, sha3_384, sha3_512, shake128, shake256</cell><cell>&#8805;1.1.1</cell></row>
<row><cell>MD4</cell><cell>md4</cell><cell></cell></row>
<row><cell>MD5</cell><cell>md5</cell><cell></cell></row>
<row><cell>RIPEMD</cell><cell>ripemd160</cell><cell></cell></row>
diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml
index 0d0ba07d8f..3a65824a67 100644
--- a/lib/crypto/doc/src/crypto.xml
+++ b/lib/crypto/doc/src/crypto.xml
@@ -257,6 +257,12 @@
</datatype>
<datatype>
+ <name name="hash_xof_algorithm"/>
+ <desc>
+ </desc>
+ </datatype>
+
+ <datatype>
<name name="hmac_hash_algorithm"/>
<desc>
</desc>
@@ -290,6 +296,7 @@
<name name="sha1"/>
<name name="sha2"/>
<name name="sha3"/>
+ <name name="sha3_xof"/>
<name name="blake2"/>
<desc>
</desc>
@@ -1108,6 +1115,18 @@ end
</func>
<func>
+ <name name="hash_xof" arity="3" since="OTP 26.0"/>
+ <fsummary></fsummary>
+ <desc>
+ <p>Uses the <seeerl marker="#error_3tup">3-tuple style</seeerl> for error handling.</p>
+ <p>Computes a message digest of type <c>Type</c> from <c>Data</c> of <c>Length</c>
+ for the chosen <c>xof_algorithm</c>.</p>
+ <p>May raise exception <c>error:notsup</c> in case the chosen <c>Type</c>
+ is not supported by the underlying libcrypto implementation.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="hash_init" arity="1" since="OTP R15B02"/>
<fsummary></fsummary>
<desc>
diff --git a/lib/crypto/doc/src/new_api.xml b/lib/crypto/doc/src/new_api.xml
index 520c259ebb..d4a71c9e61 100644
--- a/lib/crypto/doc/src/new_api.xml
+++ b/lib/crypto/doc/src/new_api.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2014</year><year>2021</year>
+ <year>2014</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -263,7 +263,7 @@
{&lt;&lt;240,130,38,96,130,241,189,52,3,190,179,213,132,1,72,
192,103,176,90,104,15,71,158>>,
&lt;&lt;131,47,45,91,142,85,9,244,21,141,214,71,31,135,2,155>>}
- 9>
+ 6>
</code>
<p>The <c>[&lt;&lt;"First bytes">>,&lt;&lt;"Second bytes">>]</c> could of course have been one
single binary: <c>&lt;&lt;"First bytesSecond bytes">></c>.
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index 53cdc76830..6abaacad5c 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@
-export([start/0, stop/0, info/0, info_lib/0, info_fips/0, supports/0, enable_fips_mode/1,
version/0, bytes_to_integer/1]).
-export([cipher_info/1, hash_info/1]).
--export([hash/2, hash_init/1, hash_update/2, hash_final/1]).
+-export([hash/2, hash_xof/3, hash_init/1, hash_update/2, hash_final/1, hash_final_xof/2]).
-export([sign/4, sign/5, verify/5, verify/6]).
-export([generate_key/2, generate_key/3, compute_key/4]).
-export([exor/2, strong_rand_bytes/1, mod_pow/3]).
@@ -139,7 +139,7 @@
hash_algorithms/0, pubkey_algorithms/0, cipher_algorithms/0,
mac_algorithms/0, curve_algorithms/0, rsa_opts_algorithms/0,
hash_info/1, hash_nif/2, hash_init_nif/1, hash_update_nif/2,
- hash_final_nif/1, mac_nif/4, mac_init_nif/3, mac_update_nif/2,
+ hash_final_nif/1, hash_final_xof_nif/2, mac_nif/4, mac_init_nif/3, mac_update_nif/2,
mac_final_nif/1, cipher_info_nif/1, ng_crypto_init_nif/4,
ng_crypto_update_nif/2, ng_crypto_update_nif/3, ng_crypto_final_nif/1,
ng_crypto_get_data_nif/1, ng_crypto_one_time_nif/5,
@@ -424,6 +424,7 @@
-type sha1() :: sha .
-type sha2() :: sha224 | sha256 | sha384 | sha512 .
-type sha3() :: sha3_224 | sha3_256 | sha3_384 | sha3_512 .
+-type sha3_xof() :: shake128 | shake256 .
-type blake2() :: blake2b | blake2s .
-type compatibility_only_hash() :: md5 | md4 .
@@ -528,7 +529,7 @@ stop() ->
| {macs, Macs}
| {curves, Curves}
| {rsa_opts, RSAopts},
- Hashs :: [sha1() | sha2() | sha3() | blake2() | ripemd160 | compatibility_only_hash()],
+ Hashs :: [sha1() | sha2() | sha3() | sha3_xof() | blake2() | ripemd160 | compatibility_only_hash()],
Ciphers :: [cipher()],
PKs :: [rsa | dss | ecdsa | dh | ecdh | eddh | ec_gf2m],
Macs :: [hmac | cmac | poly1305],
@@ -558,7 +559,7 @@ supports() ->
| Macs
| Curves
| RSAopts,
- Hashs :: [sha1() | sha2() | sha3() | blake2() | ripemd160 | compatibility_only_hash()],
+ Hashs :: [sha1() | sha2() | sha3() | sha3_xof() | blake2() | ripemd160 | compatibility_only_hash()],
Ciphers :: [cipher()],
PKs :: [rsa | dss | ecdsa | dh | ecdh | eddh | ec_gf2m],
Macs :: [hmac | cmac | poly1305],
@@ -621,7 +622,8 @@ pbkdf2_hmac_nif(_, _, _, _, _) -> ?nif_stub.
%%%
%%%================================================================
--type hash_algorithm() :: sha1() | sha2() | sha3() | blake2() | ripemd160 | compatibility_only_hash() .
+-type hash_algorithm() :: sha1() | sha2() | sha3() | sha3_xof() | blake2() | ripemd160 | compatibility_only_hash() .
+-type hash_xof_algorithm() :: sha3_xof() .
-spec hash_info(Type) -> Result
when Type :: hash_algorithm(),
@@ -640,6 +642,14 @@ hash(Type, Data) ->
MaxBytes = max_bytes(),
hash(Type, Data1, erlang:byte_size(Data1), MaxBytes).
+-spec hash_xof(Type, Data, Length) -> Digest when Type :: hash_xof_algorithm(),
+ Data :: iodata(),
+ Length :: non_neg_integer(),
+ Digest :: binary().
+hash_xof(Type, Data, Length) ->
+ Data1 = iolist_to_binary(Data),
+ hash_xof(Type, Data1, erlang:byte_size(Data1), Length).
+
-opaque hash_state() :: reference().
-spec hash_init(Type) -> State when Type :: hash_algorithm(),
@@ -660,6 +670,12 @@ hash_update(Context, Data) ->
hash_final(Context) ->
?nif_call(hash_final_nif(Context)).
+-spec hash_final_xof(State, Length) -> Digest when State :: hash_state(),
+ Length :: non_neg_integer(),
+ Digest :: binary().
+hash_final_xof(Context, Length) ->
+ notsup_to_error(hash_final_xof_nif(Context, Length)).
+
%%%================================================================
%%%
%%% MACs (Message Authentication Codes)
@@ -669,8 +685,6 @@ hash_final(Context) ->
-type hmac_hash_algorithm() :: sha1() | sha2() | sha3() | compatibility_only_hash().
-type cmac_cipher_algorithm() :: aes_128_cbc | aes_192_cbc | aes_256_cbc | aes_cbc
- | aes_128_cfb128 | aes_192_cfb128 | aes_256_cfb128 | aes_cfb128
- | aes_128_cfb8 | aes_192_cfb8 | aes_256_cfb8 | aes_cfb8
| blowfish_cbc
| des_cbc | des_ede3_cbc
| rc2_cbc
@@ -1266,7 +1280,7 @@ rand_plugin_aes_next(Key, GenWords, F, Count) ->
{V,Cache}.
block_encrypt(Key, Data) ->
- Cipher = case size(Key) of
+ Cipher = case byte_size(Key) of
16 -> aes_128_ecb;
24 -> aes_192_ecb;
32 -> aes_256_ecb;
@@ -2110,13 +2124,15 @@ on_load() ->
filename:join(
[PrivDir,
"lib",
- LibTypeName ++ "*"])) /= []) orelse
+ LibTypeName ++ "*"]),
+ erl_prim_loader) /= []) orelse
(filelib:wildcard(
filename:join(
[PrivDir,
"lib",
erlang:system_info(system_architecture),
- LibTypeName ++ "*"])) /= []) of
+ LibTypeName ++ "*"]),
+ erl_prim_loader) /= []) of
true -> LibTypeName;
false -> LibBaseName
end
@@ -2131,7 +2147,10 @@ on_load() ->
filename:join([PrivDir, "lib",
erlang:system_info(system_architecture)]),
Candidate =
- filelib:wildcard(filename:join([ArchLibDir,LibName ++ "*" ]),erl_prim_loader),
+ filelib:wildcard(
+ filename:join(
+ [ArchLibDir,LibName ++ "*" ]),
+ erl_prim_loader),
case Candidate of
[] -> Error1;
_ ->
@@ -2184,6 +2203,12 @@ hash(Hash, Data, Size, Max) ->
State1 = hash_update(State0, Data, Size, Max),
hash_final(State1).
+hash_xof(Hash, Data, Size, Length) ->
+ Max = max_bytes(),
+ State0 = hash_init(Hash),
+ State1 = hash_update(State0, Data, Size, Max),
+ hash_final_xof(State1, Length).
+
hash_update(State, Data, Size, MaxBytes) when Size =< MaxBytes ->
?nif_call(hash_update_nif(State, Data), {1,2});
hash_update(State0, Data, _, MaxBytes) ->
@@ -2196,6 +2221,7 @@ hash_nif(_Hash, _Data) -> ?nif_stub.
hash_init_nif(_Hash) -> ?nif_stub.
hash_update_nif(_State, _Data) -> ?nif_stub.
hash_final_nif(_State) -> ?nif_stub.
+hash_final_xof_nif(_State, _Length) -> ?nif_stub.
%%%================================================================
@@ -2253,9 +2279,9 @@ srp_pad_length(Width, Length) ->
(Width - Length rem Width) rem Width.
srp_pad_to(Width, Binary) ->
- case srp_pad_length(Width, size(Binary)) of
+ case srp_pad_length(Width, byte_size(Binary)) of
0 -> Binary;
- N -> << 0:(N*8), Binary/binary>>
+ N -> << 0:N/unit:8, Binary/binary>>
end.
srp_host_secret_nif(_Verifier, _B, _U, _A, _Prime) -> ?nif_stub.
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index a2adaf8ed0..0572feaf33 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -1,7 +1,7 @@
%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -82,6 +82,8 @@
generate_compute/1,
hash/0,
hash/1,
+ hash_xof/0,
+ hash_xof/1,
hash_info/0,
hash_info/1,
hmac/0,
@@ -250,6 +252,8 @@ groups() ->
{group, sha3_384},
{group, sha3_512},
{group, sha512},
+ {group, shake128},
+ {group, shake256},
{group, sha},
{group, dh},
@@ -375,6 +379,8 @@ groups() ->
{sha3_256, [], [hash, hmac, hmac_update]},
{sha3_384, [], [hash, hmac, hmac_update]},
{sha3_512, [], [hash, hmac, hmac_update]},
+ {shake128, [], [hash_xof]},
+ {shake256, [], [hash_xof]},
{blake2b, [], [hash, hmac, hmac_update]},
{blake2s, [], [hash, hmac, hmac_update]},
{no_blake2b, [], [no_hash, no_hmac]},
@@ -405,11 +411,11 @@ groups() ->
{ecdh, [], [compute, generate, use_all_ecdh_generate_compute]},
{eddh, [], [compute, generate, use_all_eddh_generate_compute]},
{srp, [], [generate_compute]},
- {des_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]},
+ {des_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
{des_cfb, [], [api_ng, api_ng_one_shot, api_ng_tls]},
- {des_ede3_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac]},
+ {des_ede3_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
{des_ede3_cfb, [], [api_ng, api_ng_one_shot, api_ng_tls]},
- {rc2_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]},
+ {rc2_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
{aes_cfb8, [], []},
{aes_128_cfb8, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_192_cfb8, [], [api_ng, api_ng_one_shot, api_ng_tls]},
@@ -420,7 +426,7 @@ groups() ->
{aes_192_cfb128, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_256_cfb128, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{no_aes_cfb128, [], [no_support]},
- {blowfish_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]},
+ {blowfish_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
{blowfish_ecb, [], [api_ng, api_ng_one_shot]},
{blowfish_cfb64, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{blowfish_ofb64, [], [api_ng, api_ng_one_shot, api_ng_tls]},
@@ -467,15 +473,15 @@ groups() ->
{des_ede3_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{des_ede3_cfb, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_128_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
- {aes_192_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]},
- {aes_256_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac]},
+ {aes_192_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
+ {aes_256_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
{aes_128_ctr, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_192_ctr, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_256_ctr, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_128_ccm, [], [aead_ng, aead_bad_tag]},
{aes_192_ccm, [], [aead_ng, aead_bad_tag]},
{aes_256_ccm, [], [aead_ng, aead_bad_tag]},
- {aes_128_ecb, [], [api_ng, api_ng_one_shot, cmac_update]},
+ {aes_128_ecb, [], [api_ng, api_ng_one_shot]},
{aes_192_ecb, [], [api_ng, api_ng_one_shot]},
{aes_256_ecb, [], [api_ng, api_ng_one_shot]},
{aes_128_gcm, [], [aead_ng, aead_bad_tag]},
@@ -776,6 +782,14 @@ hash(Config) when is_list(Config) ->
hash(Type, Msgs, Digests),
hash(Type, lists:map(fun iolistify/1, Msgs), Digests),
hash_increment(Type, Inc, IncrDigest).
+
+hash_xof() ->
+ [{doc, "Test all different hash_xof functions"}].
+hash_xof(Config) when is_list(Config) ->
+ {Type, MsgsLE, Digests, Lengths} = proplists:get_value(hash_xof, Config),
+ Msgs = lazy_eval(MsgsLE),
+ hash_xof(Type, Msgs, Digests, Lengths).
+
%%--------------------------------------------------------------------
no_hash() ->
[{doc, "Test all disabled hash functions"}].
@@ -1249,7 +1263,7 @@ use_all_ec_sign_verify(_Config) ->
Msg = <<"hello world!">>,
Sups = crypto:supports(),
Curves = proplists:get_value(curves, Sups),
- Hashs = proplists:get_value(hashs, Sups),
+ Hashs = proplists:get_value(hashs, Sups) -- [shake128, shake256],
ct:log("Lib: ~p~nFIPS: ~p~nCurves:~n~p~nHashs: ~p", [crypto:info_lib(),
crypto:info_fips(),
Curves,
@@ -1552,6 +1566,16 @@ hash(Type, [Msg | RestMsg], [Digest| RestDigest]) ->
ct:fail({{crypto, hash, [Type, Msg]}, {expected, Digest}, {got, Other}})
end.
+hash_xof(_, [], [], []) ->
+ ok;
+hash_xof(Type, [Msg | RestMsg], [Digest | RestDigest], [Length | RestLength]) ->
+ case crypto:hash_xof(Type, Msg, Length) of
+ Digest ->
+ hash_xof(Type, RestMsg, RestDigest, RestLength);
+ Other ->
+ ct:fail({{crypto, hash_xof, [Type, Msg, Length]}, {expected, Digest}, {got, Other}})
+ end.
+
hash_increment(Type, Increments, Digest) ->
State = crypto:hash_init(Type),
case hash_increment(State, Increments) of
@@ -2178,6 +2202,12 @@ group_config(sha3_384 = Type, Config) ->
group_config(sha3_512 = Type, Config) ->
{Msgs,Digests} = sha3_test_vectors(Type),
[{hash, {Type, Msgs, Digests}} | Config];
+group_config(shake128 = Type, Config) ->
+ {Msgs,Digests,Lengths} = sha3_shake128_test_vectors(Type),
+ [{hash_xof, {Type, Msgs, Digests, Lengths}} | Config];
+group_config(shake256 = Type, Config) ->
+ {Msgs,Digests,Lengths} = sha3_shake256_test_vectors(Type),
+ [{hash_xof, {Type, Msgs, Digests, Lengths}} | Config];
group_config(blake2b = Type, Config) ->
{Msgs, Digests} = blake2_test_vectors(Type),
[{hash, {Type, Msgs, Digests}} | Config];
@@ -2348,6 +2378,8 @@ do_configure_mac(cmac, Cipher, Config) ->
case Cipher of
aes_128_cbc ->
fun() -> read_rsp(Config, Cipher, ["CMACGenAES128.rsp", "CMACVerAES128.rsp"]) end;
+ aes_192_cbc ->
+ fun() -> read_rsp(Config, Cipher, ["CMACGenAES192.rsp", "CMACVerAES192.rsp"]) end;
aes_256_cbc ->
fun() -> read_rsp(Config, Cipher, ["CMACGenAES256.rsp", "CMACVerAES256.rsp"]) end;
des_ede3_cbc ->
@@ -2574,7 +2606,106 @@ sha3_test_vectors(sha3_512) ->
]
}.
-
+%%% https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing
+sha3_shake128_test_vectors(shake128) ->
+ {[%% SHAKE128 ShortMsg
+ hexstr2bin(""),
+ hexstr2bin("0e"),
+ hexstr2bin("d9e8"),
+ hexstr2bin("4c386d97ace346b2a06faab35663ce8a4c54c295b5b9f6161efafce451ca8f617ab7d5ab88ffe117d6a67cdb0bc5250a3f2556c65f0c09b1d2577ba45cc930a443a33711b175af215a338a8d5e8b918a7176a8fb390e54e5f79f7a236a006a5bf1241b30efecb8b9733f5c32195d1bf22b70419d0c65de9bd7f982c94317456eca610a700a0d05c86bf27b3302e2c92ab53ba815a0b9afbcb88e1afe"),
+ hexstr2bin("5d8f84b2f208b58a68e88ce8efb543a8404f0ec0c9805c760ad359d13faab84d3f8bb1d2a4bb45e72c0ec9245ffda2e572f94e466cffa44b876d5c5ed914d1ff338e06b74ad1e74d1405d23d759356661b7f3b03a7f7c2dc0d2c2dbe3d60822803408d472b752424ea76af1d79a0e7920388dde0c1903e9364b8d6d7b3b75430754b4d6b91cd83d5740866aab34bdbd0f1bd3dc504f1a1d753ba5f938241ce7f52544e0cc2018cc67b6401ce6abdbc8aafc5629bb643730fa3daced8f425787d61069910073ac760c631876fe81d1127034a544820ad3aa51cbf2d904f8cda936c063561a8a0bd0b1f1801777394630fb6f11cb68a588000861283a2dc9d7d2739ff2ae5ed5af5304cc176cd544a39a99064c1cb3b6bcc88a97ad9f6e381e8a3929781861e91f73516d3ee59d3661b5f584b4b717d0fa7a54da03674ac5fa36d3d76412a826c4c8445f7720337119198"),
+ %% SHAKE128 LongMsg
+ hexstr2bin("a6fe00064257aa318b621c5eb311d32bb8004c2fa1a969d205d71762cc5d2e633907992629d1b69d9557ff6d5e8deb454ab00f6e497c89a4fea09e257a6fa2074bd818ceb5981b3e3faefd6e720f2d1edd9c5e4a5c51e5009abf636ed5bca53fe159c8287014a1bd904f5c8a7501625f79ac81eb618f478ce21cae6664acffb30572f059e1ad0fc2912264e8f1ca52af26c8bf78e09d75f3dd9fc734afa8770abe0bd78c90cc2ff448105fb16dd2c5b7edd8611a62e537db9331f5023e16d6ec150cc6e706d7c7fcbfff930c7281831fd5c4aff86ece57ed0db882f59a5fe403105d0592ca38a081fed84922873f538ee774f13b8cc09bd0521db4374aec69f4bae6dcb66455822c0b84c91a3474ffac2ad06f0a4423cd2c6a49d4f0d6242d6a1890937b5d9835a5f0ea5b1d01884d22a6c1718e1f60b3ab5e232947c76ef70b344171083c688093b5f1475377e3069863"),
+ hexstr2bin("c747ee72b00680920ef1f2e16d32567e7d8900b76749832a61a3dc2a8126ec65ce872cc39d9e9c911bb2fcde26f63f2561533d2d8768a82effd04135b1a34bc9193f40cf9948c4ab14edd7a8b86d4e9a1bda84c28a1e4df19e3b2bf00fd7c520cdf7945e4d0be029212beb07cca06c24d4a63e3d19705abc646d032543ca40b015388f3861efd8536cf2a0c30a957f0db43dd467db68b7d77ced2f9af584a5c3178899247cfc94711acd5ea31956709be229ac08790d4c8a0c431918cc0edd9ffb721aeb463bfccb56418d70a2eb281787f230e89a67011d3df82766706fcf8cea7b7a5569b3f9a946d895f24c5a8ec9ca1ec7cee77fbfd73693c34e8aaf62d40c2d222a8a6c71521219def1c0ad89f8d437d883075c6c02209c5496c2d780be880f7832546f4e0fe38cd035a5ec03edccc5cdd0a306f2e4229b4207bfed109186a9e5356cc37628e1bad485a41de3f1db4e0d7b638637c56519f4692afcf9963084d2fa7d70d3e5332a50ab654a114a295c004ceee325ef109ed484aa092515ac27a54a2d7f4b8d0264ae6d8f7a7440dd760e0ded25a3a94cb0491fe81e7b55221ac8ed24f7c86a75f15494cab6f61d20259f840755983a41d9bb048b42e3a0bbe6a1ab0be031c2b558ace8c0057e2719ef1b7ae57aeeeb16a092f93c7e453c9e129a4c8a436edee9211c30166ff5a8f15feab82f9c8c04f48d37c7a3d98972f5261997482766063ad50e3677fc41e6cffa74038cc35acd02291036675b988ae5ab8855ee940382d0fbe9c089b54b2adc4a2226a4f8408d69fb860d310c58cc98284e4e23922c99d21b1597f4a1fbbb3fd65ae72042268e508c0216a8f5b2803625d34a902ac99468befa8361c83ae321268510a8aaf416e062ea17751d8eede62a7f11dd63c3bbbc6b004b7833eee8aac30c67e337926e4007a7d93f8cd55cd1c0ba6ab1460056f3aa8fb9e0e90013013455b57af1e8de9a03a224af4b6d151f6eec1fb4d969ad38030f9342f259ac4f01cac114e0dfad13dc42bf6d614060f7bd7df68f26e608b1ffe8a24557dffbbbb3d2555a0d5d25d51cee7420d635858134d5b8ea3560ae862582ae921f7c476ed1d437f47f4b8c084a100970b333cbe75b5c2d6c0cbde8e0fd457fd05217da90e33a30893e922bd3ef0f4707ff247c4eba7b1cbeb5e7fee7109adca8f998fa48716b08c153184a5f9253f68ac2efdee0da984e8838c438c2e5e78ec21be6f0be04cf8746c5c731d4bb44dea4047c3ac5421d8c719b3edd9a05955ec5c0eb041b23ce75bd9c862759f34ac6fbceb3d7257a709c21e83c3796f60d115af08e7d8757d099d1e13c7516c0cd7837277cff270bfb464127a58cdf42b67228b4b7a529273dbc289bd36434ad35ba0118bff8f37c2454795e63259c70f748d8134ec22a1b7da66ad2323adb3fea5501a8640bb255558c70975d4af6093a5a11163c1db1bbf81f408e413f32005411dedf53808c1ff5b8cfd97b431594e6717cc467fee79017ef52d8e8bd6277dbb540c5749fa8606c9e3d2da55720996355c025b8acf42140dd47b70ffa922ce80bd163afaf2fa321076399c3cf3fa118ce86411771fff6dcb3c7349e3feba8e1d936c0edfa486d7ebae828bdf39c336fa476216d93309854377c567dd4957230e81ae414c61c48ea8176df7b0b5cd7cf9e37cca05bb08e0e320b259501b7123b0023d80e5cd3bbcd22b2c8e65faa15a086724a9a11a760746b1fa9cc0e7a116ce8f9a3c104944f906646eae437ac764b22cea441fd3d97edac5eeb0b4205f62a6a454d1706878a9915da4d5eda8a81363bf4e17f6798bae0bb70f23be78149dc4cafdff7a9d5f751f715fa3d916dbfca988b093b96e351b761fec642431593ec57de604836a61b20d3a0f867a47db66f033c255dd344e5d966b0700a9eea5ac1537bd8f4f521806f73224fc65be1f08e42af3bcc66e3fdae922df779422729c3082d50e22e6fada49d440841fdc2f58d1e8b567297e9219ead1e637976f5855a3e9dcefa29b72a291aae2abfd4474bd65f797b36ae7ed9117e2abdf1817cf8eebc735d242d228064b207bd2a26997ccfcd20b400f2c9ae6fcefa0cc8ace309e74b56024884d836bf2cf8ca693cdf10e22f2908b8acf32720653558e83f5341adb3eec74e7400d4a0522857645e428d764f3b80472423cd8c0b84a93882f326a8bce4b31e46c3e271ea406f03d4ea7df2f3ff2293a282261c888f08a0bcec42412de0cea14248e999bde7172ee5fe67c0e41cf8856e38bc36a3fd7c0837651dded08921bdafb8718093c659cddaa6d447ea3b0a05cf6c174425770e5839873de96b88b35bd73a008e34843cc8fb0c4befcc045efc0f47bdacd7a4c5780b792cc8c7848d6400f7202b67cac80790555420541efe5074f038d87e2da951f56472e5ac26b5be731658bff5400672e1ac77424832f2eb478f102ee5b9c2c93e7095a6e216355378c030b2a78dfdf236373dbd71e740b3cf4ecb18d3f119c930c41f7530fb1509561d316c435ab66dd61e2dfa97e6f2925653f3fc0f7cb53a118998b95f7e5bca5754e256914e37cc0a8ad05f82d7106f22c60f4462049e022ae686d93746117dc18eccd65b128af81aa3ac55f960a0317cd0a857942edd0bc304733f80fc3e0addf27555674db7d84e904b45c62cacc2ac99459d6da4793b240be6520247eb58f3a9673c7ea8c453ed7a3595afe13b09fa486e7eb50e7a7c9422687a85d01f514bda2af168cbbb534188f0af7d2856eeae05ac623313e9b55d71801fd03ee2dc2cae44deed5da3bf90e306f9f8a804590ffa85b1b05ccdcec521b6a9ac3a3ce1e04d3f69d507857e23b2ffdde70a4d32664c83d3f4912eae155a9281e16c3649addea3fce770687a421e61c113f53961fb262e31c759143f94439d0d377bd7fadc89f6feec40549a354fb60f4512d1cf16ae0d31f15c88688c055eb6761028ddb478c2d7ad2152c6436c852e43091c62ff0ca83c3943911143c75de465d22fda7fd08eeeefb968e6b59d5786d5812203fd6987bb68b6382202b22300cd6af6da83bce3ad7db0a1e3d30cb37fc522ec9b5aed3314465c848411ee3770767f85c30a62487b1eaf02a3e5de2181da9581105cbf33b19bde7d1db8a2219e28bae27f5aea45c0995e950f333e29f4db82ea4c4c080ff82fe32bd63ff76af0abc317f4bb7d62e5b764610f0552544091902d3572ef5053ca149798ed1499d0c6412c0c77432850406a6e8ca24229cdc46854b5159d7d293319c8490c2136a543dc27add5631e6afc17c56c533731043bb266b37e391ee13399c6d2e248cad7e83d12c11c05cd9bb2146d996059f24bc0156c16f6ecbbe5923d9a1cb8afdcce7e0cf42f9553bb4c2c086ef911cff60b19396dc8c3dedd9c18ac4b0ed57ef4f1ccf0d6df887272e3309cd176adc2c5ad0fc771b02d360b6e8c8ee18f83de9aaa9c92bfb93c8d7aca87789df77e59e5976a6781b67e1dcec3b88d658908f75761b49ab7a5fbca5967f943da63cabde033f2c0a3c2fcbaad03624fdee3adf0b27cddafc78b0b5199244da590a4da153ee86f7be1bcf1363096537c5485baf13464df79f553b54bc1684d48c8a1f68a851da83f438ca3b8dfe553bdcf577724638182a24be541e05a9abfd18a4266443f3c66b5d01b5b6943562aba58402bafca3b8ecfd129f737eb246cebdc6f716af3ac7185904f6070e3529f1af62b20baeefbcaaa8e081f79fc6d1c3cc81060242ad591568b11fcf647427b85b2fd9c6e5927ffdb8a6800cd16dd966c550d7b9c6b1f8ed0ca4a7523f88c86a5939fe8f29229f50acd33e35cc0dca4248b4e0af73ef22f9c54e0141e7d9a881fa1f94c6f216774de604f104e82d20c0c73bc55602bb63d6231bd21105ed2499ee4f30f0d8f4b44ff4aa6cfcae42797cb143da4fdbbd381c96ac7e420249e90c865767bf3623b2c16931e68f86f430d0a77ba3ef675b1c07a06e8a477566527452b68d64a25a1f6cb44a12201aa8f3670a21c6c74e2b8c51d0d07ab3c7c8ed4be3eb369395de468c56316470d1d71daa69f49769f88515759880b3f981f8360dba73d7ce9946c4badfb641d435152c053762ba49ef1e23dd67edea9b3c6ab93436df0578251a1662a1175edcccd9bb1202da93d9aa5d5403df76731b4f96e349f98b2412b1deb83e5af30fed019b275b4ceaa8bba2a902836fb8e1ec440340d96316d840c7fb26885dc7d9a76be01d4b96018d4270a7dbd68933bba0a2465e4d13e08664eff5edeb3a14407e643ee96d4ecda6108cd02c67cc6564baf2c6982f7ca6b30192557ad17056d8e4930e382af79d87a86f9890d199dc71adae1a9afb1da7e29947a059d5810e177c70e4761f20ffa67c9187958e81e766fb4c98b32a778299be885d1761fca860a876482fb45b6286ff8b13a7091534c92656e78d580ab54cc3cce21ff133c458fb2bafc184e774adb3b4d341dbb442fca1458d95ee57ce705b9de5e01dd6d074d6f991c80b85948f3bc73cf260ef80860e90d14967df1ca8e873cf1452465b28de7874cfa29650affa0ab73553477c09c0d179345f115e5afcd704090912d2f97410ef45049a27847127e8870d2c93dc6a8febc33e8ed20b0b403b5adcccc67b3a6b67a48c2187b133872764093f76926e148f9270b8364d8637cb7627c4a41293e0647ab39cff9aa3a11a58d1c072b04e17fb1eb02de97710263ad7949b0a4403086c34059d0db5541e37d1f18046aa7d02a4b457406a3c1bb4101b8dd057a41a5be56d1c7220a5c756f67bd2df31058d5d231fe09046e11b49d1ae8dd51aa74c43769242ecd6d07fd3dc93103de045f18fecade6adfe2a815ff60d1f49c1c21b678620334daeb9d01d50c4143cc2e04c2367eac3f9fe6d007ef3b3ac8efb890a209ab3481506e68abc099b695c6768ffb03b5322f4d61cfb2f613ec01516609b173aa7fbe37ddae2185d757a0d3c447eaebd736e3ef3dee3d9b50b048cb0ed30b3dc9ae93971fb88bac2dee0dfb0075b8784c8103906175d0675a4c45649837d7ef6f87a7791ece12bd0541834612618c327ddb3635b5ac95e19a889f24ec7d3e1203d4a3e4c138de41c61aabbde2538fa0d00b24e821dab191762efe13f2ed672def5b2ee1f03d16c0d73f2d675993e94aaedb76b08d494fff57040c3a93ce7ac89ae5f1779326e2c0a278b04d5d68c1b88011d496e13f95b4a6d46ea0b56aa16b6c3994ae933b6bcb8e8fdc75d71c3f73ac76e5a00505ab37c964cf4d4107029fcd21be8ed6c4d968034a5be9b0f58d029cd252d8e1e3ba102a7cd54a20eec5eb25631720450954206973d2b1ce3cd0edb6edc2465e20eac82fe6289e386713d8dcc8486bc852037929d0ecd31b3833bd648edef1620662e64269715173b914ed5a18f97748e60efda92a4f597e33dbf90d7f4ee1d6db4bf2cacc586cb82eefb2081bd14dab0e9e345248a34ade73f3291886b91ea3e8cc742fd884f6ee0ccdaf4c9879f4db12dba58cf491af2541a1d5ef6cc8b1af750ef5d8559ef7ff9cd56d8f599974be3aecd8c0f4c08f3ae50d86f9f822a1e4ca39fd2f0b4d78d22630733a24d8d63ecdf9555411daf205a761c39ef46ff6292e74129bc13a7f4ff20549b3c2de2142152ca5b21384e16ecfc215462659b865ebe7be84410d175e477d7864fb545db158ab4f40c268625da45a03595886f08247bb8852516d45e76fc087b396e29375df9edb68e62e80ba6f51153f509ea6678b18f86eba0213d2b0f5fe0a5d2fca75f22ed191467e406d78cfd361d74ae0fcfe25973e43758807534466d016e40a99d982ba22abfeac5e07fbc625b432a829ea0bec2bb66eac6f9fd6456d8aa85bc9dbc41e85dd59cb7664fd75f4b1fd05d695f1809eda9488f0ded326498124c6ed41198321a435a59bd7fe6837945300eb4800d9fd7690237356638eccc30052ed54ab735ec83b43b6a726285426de9f504b6081b1530986e151ac075e17e308b159a275a41012acb01d9c3cb52f43eceee638002339a8b72015d918674675ee229b26125b4562e86f81892cc0772defab61b0dcbaef15c01aea104a4f195f0f26615dd0eb2fd500dd1b1481dfc542af7003f29fdd3ff7a8b51a79237e554970a6f2723abd9611fbe13266c279e22a34261623d0f6eff778dbec3e2d5cc5da1b8b978bd53742bee1620fa677f5978e86b3e06ef86cc8bc3773a784f05230bb17bb98d12bc2fb85bb4afbfd29bdaa16ae01aaf99ea792efed659476f3c65d946c203d6bc3c7880e90803c935f38e369b3f6bbec4a0f1537a399fb39be21150c23a1ce8125acb20068ec1a8fa1ce0c309d7c5d70850c183d60f624f8d9cc20cb4225923ddadb41034b76df1977e7eb12747ad6add50665a82c6656d9cd2d467ad496d2c569bafdc14206d15b6aeb4ab841d76fcac565c2210fd1da0420ae4fc1dedbd6415ab3addf54843a7a0142efc90c7d70efda91f850121df9a25db602476adce6da362c1b088ad860a1c555a2eedb5321469ef2cfdc3c33f8db09ac28b57f814ab7838f0de2eaea8cf63c8a2dcfb841c9f498f00e6e96fb22d6aefa89b226b24902cc434c157fa205a1f04a5713aa2fb22450f47fad265dbebcd307ede766572ce096b0a04a4ee915cb69fee763ba0522fc4f64e842f64213b4013b1cfb47583c7b6b3ed02961a5029c2d0ca666eeab62a41caa12d40e906ae7bffedd97d19dbefbd255a659bfc0153bc6de5cc5500aba453f2cc50c74310b7d95b8fa630d0efd88545de883702096d1bcdad027e80871074154c892320b3e9d0bd139060294404878be5f4e7dafcd85956acc35bc8ed111a989e50d50084b6faab16329b815ce66c6e0d363ed530180dbf51697d785208cff32e225d40f791cf9c14073387af008c62c0c69c8f67ce915e377f43ed3a79fccb98883c80712cc8167978d89acee570108d109167f84e9cc9bb7f4fb62ae7396859fcf33da5ca6c80c311eb392107afeddebebe0d662a887879e4014187d2fe8feefb01e6fa0d35819d7cfbf139e99451423b62ee35e4dce0ef48094df5bfc478c3fcf90ae257bb9f44f48b419dfde5f133f12068ebf012998805b809e92023d9759d4917f35c4710761309a8b1c6fd34407a972bfcfeb218d0b7ae23d68685187036629ca16d90ab9d7307a509d74290e4520795ed4343d565d85c1a326570cc4062d617711e00c7178ef5b52aa16ead8bf222353170306593e2326ba13f6f4b62ad3406e6e02fd990b1645788b7c9d0c3e557986e08103ff76fc1869796e93c636fdcf9875666798594c40aa87d0ca118a6182df77d5bcb0ccb99f989ee6715af45515db6e35d1d62a3a55d0e737f94f6bef47472329b9d50a02c32fcb8714dc2a831a20a096c4bb45878bd3f4dbb32ea9a7cea42da283d4f67e8d17487906b2a12fe65054b2c7af74dceb99f3e47f160b3021e0dd6aa11759d604365de3cf4621008251d2b0621a7de17b0b2f7ae66f5b3348e17ba99b04fab2317552edf5fe729ef50d62b91d64293d4511efc36fa15f5749914f86fa05a3b81abc6ec102445bb2476a4cf581f5409951947aeaa214e0452ed6a12a44811a93c9f4fb847e7fa5e48052d78b15358a9ca57a19150730a5a4ff9ab8bd04e028e946033f61ef1618dba9ac4ca4618dcfef4bad146d275e781dc2c4dd66bf89956397d3837edf737c40ca60f367ee63d6394b13b31989041f58813c584dffe629a8c64b0e03d5fd99bae95514fd43206823ab963e41b58c63fc43ea719b4b5eac61db522a195a1c37f21075f78de92d4d55751dfc0f345c0cb111b670782c59e535dcd21de8f5381d8f46a7e44066f04d76b18f36a98ddee221f2889c0b293f9dcdd3b230972eb9143a94d3223e44d32f2e65d634374878adf2ee483e7dca799cd0c7daab4e8d08e49b00e1faae0a3dbd279bd93ee2ee9434cc0dde9ef5976daae7c131d6379b8b555995bab4b84196bd065a809cd1069940585832049c19848fbac8a1f877dfab2e1d584498867045f876abb3952e18ef993dd62cb89ad72612f0e4125b39e1d0398e5368ed3d1fb4361d945a721eacfe07248fb5011659bcfd311d6a45a32a633c558d0ffd08d74a3fa3205d7ccb4b2d52b58cb3293828ac16277c92ffdd41ca00190e1f0e0d1e6fdab154a4f85d7ce64197d410762acb6026780872d110ae6f4d83df8269747f8bd1ef3a1831fcc8919d736fb23111ca3ef4cccaf20264fab8eb3b071e56667f06ea04d99f6eceb6942ef06683a118aeb04accc8fbdc00ac0fa1e88edd1c0ba849ee993922f6fece58bcb7c0691064fe1dee1f0c73a50d4cdb38327da5058a81baa455638a12b298450f6f854ec42042eda96e9e44a6b2638479c3db60570b92c2d6e15ebd0a139dc4eec1be190672038743618454271878722940b0f55bb864ca4ae432a52648643b235572b21e82c755849fc88d7066130c1354044c59b77e38c907e829ae79751374d2a901547919be34d172d5c8c5bcbcfea5e04d993068751bf28730e9d059b5aae16bedf07f7e2a1a9e8816795dfceab1db4be0624cabea084c17a4207297878d29c16039bb8001ffaf1437c9555afcf59f4eedfe32cefe58bda16c9b8ab7f3e453b2427856a6acabab1d2001d65f5b9299f05cb30c5918d2ef2b70a23e426079064779f6787891e63bacb96807916f50703915caffd5a24087c0987db6bf3612eb87eef8477f197807c8ea48117ba0f7cc31008a35c903683065cc731bb6dc78a97a89c9ee42ac28b6799be71ba2574e57e1063dd6bda3129024f4cd95afbc73e92833b0934ab3bff1c7b48f4e90fa1eb6500d290939ea084f04f8d44b333dca539ad2f45f1d94065fbb1d86d2ccf32f9486fe98f7c64011160ec0cd66c9c7478ed74fde7945b9c2a95cbe14cedea849978cc2d0c8eb0df48d4834030dfac2b043e793b6094a88be76b37f836a4f833467693f1aa331b97a5bbc3dbd694d96ce19d385c439b26bc16fc64919d0a5eab7ad255fbdb01fac6b2872c142a24aac69b9a20c4f2f07c9923c9f0220256b479c11c90903193d4e8f9e70a9dbdf796a49ca5c12a113d00afa844694de942601a93a5c2532031308ad63c0ded048633935f50a7e000e9695c1efc1e59c426080a7d1e69a93982a408f1f6a4769078f82f6e2b238b548e0d4af271adfa15aa02c5d7d70526e00095ffb7b74cbee4185ab54385f2707e8362e8bd1596937026f6d95e700340b6338ceba1ee854a621ce1e17a016354016200b1f98846aa46254ab15b7a128b1e840f494b2cdc9daccf14107c1e149a7fc27d33121a5cc31a4d74ea6945816a9b7a83850dc2c11d26d767eec44c74b83bfd2ef8a17c37626ed80be10262fe63cf9f804b8460c16d62ae63c8dd0d1241d8aaac5f220e750cb68d8631b162d80afd6b9bf929875bf2e2bc8e2b30e05babd8336be31e41842673a66a68f0c5acd4d7572d0a77970f42199a4da26a56df6aad2fe420e0d5e34448eb2ed33afbfb35dffaba1bf92039df89c038bae3e11c02ea08aba5240c10ea88a45a1d0a8631b269bec99a28b39a3fc5b6b5d1381f7018f15638cc5274ab8dc56a62b2e9e4feef172be20170b17ec72ff67b81c15299f165810222f6a001a281b5df1153a891206aca89ee7baa761a5af7c0493a3af840b9219e358b1ec1dd301f35d4d241b71ad70337bda42f0eadc9434a93ed28f96b6ea073608a314a7272fefd69d030cf22ee6e520b848fa705ed6160fe54bd3bf5e89608506e882a16aced9c3cf80657cd03749f34977ced9749caa9f52b683e64d96af371b293ef4e5053a8ea9422df9dd8be45d5574730f660e79bf4cbaa5f3c93a79b40f0e4e86e0fd999ef4f26c509b0940c7a3eaf1f87c560ad89aff43cd1b9d4863aa3ebc41a3dd7e5b77372b6953dae497fc7f517efe99e553052e645e8be6a3aeb362900c75ce712dfcba712c4c25583728db9a883302939655ef118d603e13fcf421d0cea0f8fb7c49224681d013250defa7d4fd64b69b0b52e95142e4cc1fb6332486716a82a3b02818b25025ccd283198b07c7d9e08519c3c52c655db94f423912b9dc1c95f2315e44be819477e7ff6d2e3ccddaa6da27722aaadf142c2b09ce9472f7fd586f68b64d71fc653decebb4397bf7af30219f25c1d496514e3c73b952b8aa57f4a2bbf7dcd4a9e0456aaeb653ca2d9fa7e2e8a532b1735c4609e9c4f393dd70901393e898ed704db8e9b03b253357f333a66aba24495e7c3d1ad1b5200b7892554b59532ac63af3bdef590b57bd5df4fbf38d2b3fa540fa5bf89455802963036bd173fe3967ed1b7d0611ed907027e72854b9c22cccff1ec72a9b6cbcf4870bf92d4bb16ed6730d1fb540ab78559e17e14e564593c68530aa384dc27b75caf427e84736711db211cc854e0bb873a8cb4f6bcc7d1b9cb8782bab111e66b215041f89d154e944946facfca3d6f8d32e4a9a3283326d55823e730a514a24b55185069850d27e51006ee6a4a7d3df7b19dde92be42c31f9cac9a9dd740c5e8664bd079d2c79d94edd57a305c3cc16754fcc64df55320e917862f6ade22dbc2b38f54721cc11a24215720aa55d8745c9ba5f35d53f0eac24735182000275e5aeb374f1ac9a6280368f07440edfce137d41de81950b86b4baa5de74613eab8216bf842bc4fd331c31335e78c8af67289bcb9073a81f63ce5bf551f555fd993a6c436b1df35d4d1e3be9bfcf59371a2d2a42be3095e56ca1104bf12c66a805b6b72672f1c023792d5c9971714bddb1ca338db0e3e4c36bd6e08f07421ba5b89eff52e1ccb3b57df19d2bbd85260bf2a6bd94aeb21e232614d4a9db5efc5d7a994dae00d8557c21140587eb337028a76ad03f00c5ca6c6704f382b5a99238124fa822163d2de02f3b9d9643a5b39c96bd7476ed457006f44e4500dd4b649f4a6918966d23c44a6d106c3e0c0076ce8848b221be5a99599599d20c9a6bfbf8e88fa5f24bb1a020a53b7a68906f422844b39128bc2bcccd148da38f3631ddacb568581ef90f4bcb9d64cffd15189e1194a51a732ce191ca38d3bf6ff7c69424799d3803bf664db41e3274e3e42312f13ec654ea59a370b8c8132e962652a8c7fcd54d87e4de2c42ef4d700445b926310bbf47768a9af2ff8df4f9ea0afc9ffc05b9a093b29e72dcda5ecb467f026337c338fbfd06ee9e074f1f2755eded032d5ef138106fcfa6eca50d8d93dc25506c2ed8796816c6fc38d53cc0be154d5bc1cd241ee4649851d91901bfd2a24a76381348c8a0024a95fbe9f2cd0ccb4330201bfa58f9d922fa0bfee369eb1a9b5b043c30f08c12a4f0b651e9542957f709b2101df035169429ea1c5a91c58c3d89366f36fa138ef996d3d6ca60fc01926ede7332ed82621767de3a0f414a405b88387505ed1fbd740db8403df66d113ce70f0ee458c707d33a4f22a980617d1bde701643d197e83adfcf57e92064ce1cfdaaa7d838ec4bef2da59ad157f28da515481999ae625776937ee377e5c9825b371807b995d87794c5486b2fcce6b622b52648ae8d6a524eb22fab8e4e8473201ec1f62d7875d2e98ca05d6aea2341ba4474692b759621eac34978d7617ee994380d3bdce8aaa6170916372bb69276944c9945b76fce0b734e3412fa289d2edf1065a070ed5f3945c410c3193a1b9cd954fda4fb82fa5911dc57419eabca89282b2feb8e8f7cd59c46f7c7dd7da7fcb0e5084e9eb60354ebf41ccd6bf1ee419713d4e5e0d7eac04416340c1e871cdf4286220a4913cf412a1eea7d0b02ef9f564fbd560ecc8f4264bc2d5a8561648bb7bfb3a1a4a2476bf5e22a1a23e6b7eb8a675106c07654a524c14c78ba126549361823dc566a531975c9426c5ea52c6d5ee3f7bc9dde212fadacf83064a20960e71ada810873dc92d25a9b3b6148386cf84c19424d0bdf2a270598482b1a361af34c415861c6c81f331e9d4fd7d7c232802a1e5e790574dcba559689b981d6bd8236be2b0a6a0108c09bb0706658b1051f036ed9c9d5ca8e8c2259bbb3dfd092bb61bac0aa676d95a35b3d65b6d5ef2efc61717ae482c887c8f749a6c43c0ce1c7549ee0eac6f60e01e6aaf7705563f441cb8f0b731455119459ce1192a172f2ce97d053728e83ba05c261ee3ce1f5a113ca30521d945b7218d250c6c515ea0cf7b3bae1e9c7fbe2d479c812c1808d7c9ce5bc198b09c1f37ee9378b06a15c786a2fc252f87a48ce9993492aa83a13cc04a49b26647b6fdceb27eb29fd4cb07f5093526d46ae51c67a964b0098176fda738f354151f34c758912d468511ece67d168d1fb7c6bfe129f0c365a3a208aee102ff234fa77420580345b0faa1f9e781435a7c441b937f9317c7101365c4a4f030b3bc6061238d6f815e6e8b25809fc42823b903ea1e4c4586f66bd33d4984775ab76be1ec9172e183bdfb90ed0f53982d8f7271a2aedbc3b6c7c55d98e5d1c8e980de5e9a6c5560830ac4b698f4c78fdbd42517e5d3a2603fb1b93d2947f177559660310b02b96f17cb766699ef401dabc84d51240b44dd7a4ab3dc3eee8805376ebe03aef3135fba566d1b604d71c425a267c7dde4178e0f0941c57a27e9f4f780243106cc07d4d352356f9c9db115d218e0e683ccaee46663ff834ba2bbb92e615f5392ebeb410b455bbdaf6ea27529825537a6438f8308f1299023dd51e43488bbf5e3d15236ba863c90092eba84a4753125c5d71dec6f176c66a34660e8b27cc6727a2ab12879bb74814a5c5da6cb78115b52f5fe83216d44913a6c5d4920940b53b646ac98b14b800bd386206e16f15222ee3e95e998692e8a79bdab87f794f73438b388fa09cceb9a451a973d0f779026f7ff68726c71a6ccb1c8d017afc6167e620ffaeeeba613a71fb826b62d8137d1f0e70acfe63d29c292fc6f9ad1cde5acd2aa66c9cf6d94ccb3bd534bf994d5ae6953e154ce2244269df6de63e602e15a0b11512d3d89fb75a1b8a170ec28e636e1e480a36ca772433316a115550b95d06be34e7bae91943c7a28a5c78dc3d5e5cb076d3e4a5320c2d2fa5c21b75e7fda757eb357ab0b0b7bad8bb4b246ec4145b54b0b77ffabfc002e00741b75621bb2c8722423ab5ccdddac6062c3a008a4d1111deff2f528508584c76d5527ccd613d53c1582381d67bd0735346e41af7d026efe8f41b34ecaed87cdbc7146d55b581c5b0bd5d9b345a43e88be24845736743f6b6db6c285c7829771f5f24b31f80a1e4d7b90f8b5042dc2b94488e0866d8102f30ff49b63d30f34528e5abd6958944b81e9661da91fb434658c0dfec38ec34a13fa7db67f79ce0efe0a1d05c1d9a134a6e9a7c0f5e4c1f40be926378516b5854c91a4ac2f285967c2edbbccf2179b6a1ad0f88b221fe1922ad0541341a1d2351b7a1bd5097b2bdf4f9da96b608efd4c5c53bac4c41d9d8f30f62a2a8f9ca3e9ff2845c974cedfe602be7879a3ac415bf9c67116426a505bbcbf1ad5d57395046610b50217afc01c7ab4ab7021fc023961cdadf82382a4783c9fa8bda9d33024feb7ec1768aa86d31614b4d761faba3522cde3d2edf304752f1c563a73180fcc17a24d61ce2b9eaa63532727ea24c40fcb3b953f1837eac302b9a5e4d7de8e5a4f546b89decf7222f0f464da3106ca09b9bb44a528cdd1f255fc0a453e7336501432c8f810f2abad3d7d0076cd1d36b37cb174ada6215483a75805c41d92826df4e1e824eb2195d9b7518c7624d494ad92b2a7a0f6deece765522aea0ba90cd46fd64b98e3f8db18dd166b60bec96e487a335037b15305ae2e7eec3f6f233053e38455dc81d4e5eb4d402fcf21781e34b1b7f0da6c3a29384142bc3cee9d974d0e70a9685b4240d156433c09c34d4ec2d7b51bf17dd0d2266c223d6ad064ac203f3fc8b4c6cd766ba6a432a93ad55aa34abf34156cd39ae90da1c221db34d98e0426065a5ad2e10aacf78635ee8cdc26893adbcebf9eee008455fd2a558ac9e6dbd814b18dff2b4ed791b93854f03dfd11cc7585fcbf460d8e9340cce5b2d7bb44ca6662e6aebf98c5273e19048c1a7782c883c38a912d508b0e2f81e2599d7bdd816de6eb6e1888e41ca76083dcdc264ac399101fd12e79e81b3c01f1eb75c8e471ada612b9c08fb2d667a7595f0d99d85227e3f2eba3fa443ed8959ae51b03183fa828d32e322db0090145db8f2942c9d8b2c7ca4d38521e0d97e533e2d7bae72feff7c31807e37a1906c4a0e9b9a18debcc23fc724611e3fbfc4953415660bfe6e79b69239c316f8aeabc3ffb394692ffc6911ff7779a248e786a7face18520e9117c1bbd79981ed32f951f301292877954979b49278b5e398ea12f6233a8b89b002271622aa7f68637d7a524e025ca0cd89ff4e3e045ff81c185d92db74bc5913627009e19b18518b60c98462c60d91ed522b3543c5a96fb9391035bd6def65025e22fede175490f0c9c3f1051e43323ccb86d169659a30b875b34693ce2b95c51ec75cdfc43c0ddae1f201ffb2076b041d978b78893b2461f3cf2f233e79d1a3cbe68bad4ad7b193d0dd1d172960248b6fe6c937db9231fbdceb573666e001e68d08f310522e6262bd0cab172c57451beafcf46f03aa40d057902693ee680dd48bb4fccf8447098ed00815c562f3b6fab56075e8a9883a268d4504047d19ddb6ee720eb4872c8f603358c1e9743c5f70fa9ab26a74360546a692594fbbfc5ad3ddca918504e5da200b84194d093a29c7de3315bd0cbb8b78c9ff8d3d52186a758145ed0ba278d416831583b8caf6fa458bb205a81a1fb2031e150896e775f6e2026a8a0e0aef6489c09269f15e77bc34299feaf5466a6431ce5b3322251ce76387c2d056e35171c3ed8332c16fc555dadf832b6a37a58848015a74a8b114234038d428aad6a7e50a307046f0a2e9af80d6e532d096d80691de9834f2366594c13e28f8d38bf8a7a7b1e20d46a1eab06d364d66ee0a09c6548e9fdcc65a18212ec146a99aa94c9fe29961ef8b14d042eb26432f8e3c42afd939f7d2edc534b1cee8b7a4c5d9acb50210735486a2a44c40ce50f576d38aab670526802f20c12eeadc02501cc21ed1513da2a400f46db5b557fb206f78eaa2471db64c5313c87fcac5af14690daddb15497547c1b2336792e9cd1841d3687ac2a72fa023454f8cb6ca465a72615ebb975c74a59183fe491fab4d46b48b0f1c1ecc17e3655f613df613508900673b591f9c2826380fd743a4c603109049e470a3b3f87de208037ad1c95508dbc64b9df43418337dffc53d686277df9cb4f15e9b8559d662e93f2b9114db41cb279670604acd3229fbd11fb4a5321ca1753dcf4c69c356adaa1c387c1b6c9b983225b8a907cd8d04aa02d1d0babb2d6733d0a9ff88bd658af2a389db078f7e098eff5f1892a2aad6b5603c04c79a7cb697b3d6fd09cf6bc269da06f5ffcd52e8e42c4bf514d3f367c3f34698f5586f5649ed3c8b1581d01db8cb98142cac80fbdbb56688b4e9ce7f7dc4d8d36b7c700ef086c5f4d45cfc63877e257eb6b66e0b1ca196189b6ba5e58bdadbfe528ee6a83487587e6de7575c7df9967cc47385df4ef8c5eff0e1df62943d3561d11639eabff7dc7309aa4ad04346d4567e6a2d411ac3da53dcd4e4be40f6d044119dd09d36de3b19cf2e3b86816dc6540d23cff72809bd8d87cb1cc1390c92ef39aa158944445cb9e8fa9347de7453be9926d1ca1fd256e5184eef32248765a972c66f758f32279a7b97bbdb026565db49fc9d82a8268da2b7e5c5dbac7030bce478462abfaf8406b18b190a9647e3f4462c99e76a0e4a0e8e19068277cedb6eb64803717dc45b43b260179c4223b451b83e2fc00e91756fc2711af02da94d0811be0c107178949aa59d1c58ad00c23ac6bac6d73bb8f60e7e7669ed770970d11c63cacf42d31753b2cb8f0780e6f4f79829c5f29a8ab533e8ae5c41fdf72b1300bea57898ddccb60371da7162de32cf571396dad20f9a2628a359b3667a6614e45806d11d9f9c0bbd17645efcee640c2bfea0578c4f8e2e67ddc90c5a9b091ef1b753a477206d990b469596f62172f43b744b3170537794cde315148361fd774e65f6b74e6829a91384d5e0f77c45d0b0fcff2f25223fa1813ca39386d4286725ea55d24b80b683fd5c8c06a709c3e7b11f241a54c4cc9aac73dd510efc73389bccdabf156ffe6ff25322ceafcfd583676322baa44208ca750d2a15c1e2c630da95eb45553de7b51af3ff01b7d664eafa5362d7bea3d9780594f1a9478d0a990accf22bb3f35b9c9cc38c676627566591744a3949ef29eb83909cd3490525b952fa563ad698416da04b2fc765c9953c545043a7ee2edf6c1887925c6f5301d9dba67f3a67348071501451ea5bc407b76c6eb6eb5569e343ec033b0c607e8540cd58d5f30dd002edd2a55b99f121961e015609cb25a81db8268bffaac58e98384f99c02c7172ae2c4e86b39acfe729b83a91a61007f17513efa84600458c240789efa8af3b256c75ed40c3b59c1ea7ce40a6f382a24ed39b6ee078d4897f10f6a4e5f54212ee240fd65af2b401cefaa83b1d5ae89fc61f99ccbefdef386f444dc8000fae9ed7be43335ca75da500098e527f410ba561415530c73d19fb64a7541cca5eba88e7235929cf4c7a31bcbcc8ad5212b45d282117c385a1197a672353c9ca4b3078263999e23a48d14c08fb9d752134a96e22a727ac7f8fb95c7f9f91f6883cd6159de94a8625032b9feeae57d2710fb38258bccc143d70bf4ab50457cc8d9a7608531cb63c18e54fd175bd3cebcc0b993ed046502348adf4eb1038ed7360ced6f850b8232d9c56078ba51095e9b990254c942ef90027ac20e898249b12893ebf8e9da6da8138be19758790a9f3c1061d54ac5d10d462d70be855b0a9d29154f87241cf235f75854e362223c2326ad54afd1ebadcc682957c9450c55edaff774b4b441a45efda136e777fb226ce3f6574f0f42ca7ff0e4d3d94904903535b0e7ec2af6e8a5befd041dfa4261eeec51f62d5369db7a78e69148f1d26e0642e835a7e89b538c83c258e179b65f2ba8669de53eaf456e58a50ca2d00423d36fe3da728dad698604a781fc66c59ea5b09acae121a19cae18b6a2717408762f056a42d67abb9f24ba032ba951de84ef9d6519bbbcdf35bc4181ff2f5e5db96bbbb20b3e403468eb1c6bc282cb8fc65bb76be6df60c6c53b6bad7f56ef82823bfb7190346ea65b3b9457c3132447c78ca705dec6ade6bf8def8faf62b7f554f23d1f7e33668feab8ba4dc9c50ca50cf4e4c95e0c04bd994655af0bed4854a631092221c5c86f9bc506b43332d5160b26e93cd109f5b9c6d72682115100fefa6ad8003765f4b3e2f302319bc72e1946952cc6f45b0179dcb2eabf6c287272c62232500b8e8c0b54c5253c500c682d43b237fe2479b86c4f8c1aad58fe2d4e9dd990982ac9eeb475adf83b1c7d23d087209348ede07b0fa3b52ed2742c91f0e6138b2f2182ad6daa3fe3b44d93d1918542b74dbd29c9e25db09fd55a4e30e833427aab6c53389576dd804e6fdb01fec2ab2c631ea03572e210f61c57dd9ac309e8f79387b620aa7019565fffdd3566c3bc5b3f8efe4e3fe558ca1e19552fefde998a5de015f63b0a75ce7f79f4dc681d18dc298aae64b7cbfa74610b3f70ed35b69b81221d19c5ca6551037acc7771132327b79e0b2968fa689d7b9a170e3983e2b3a3ba160bb9248a96b25ebe87205a7db66035cf0cc21545adc4625648536d805b1c04ab5ca3ad3807d2a2659be4678b00c9fe547f6190f41de1e087cfd937220a70be478d03b9001d0af23c163bbeddaf4b3425f1b5e206664a9115f529953a4a3833751d67fc755eb1aabc96a4b4e81a6a93ce143dacff2f64f0ab58c2ec5b82e900ae6e3edc0a356d48619dae0e2ccabe79b757674630d1c78b928b43c0e84cf9a27e2ff6dc2149a467c58bb61f2e48ad727c2bd1426de5f8ce85ed0aa77310e0954eb32e716d03ed7552e15b5c6f1cbdfa7711fa25e8e46f35dffeb6048e773465aa9854305fecaeed845f1c6a61af769e529a50754445a15e1b40a46c9cd87b701c6ff69d5c7c6f5cf42567bda8795dca79356295029215b3b253744420cd1c510e7a942ce52bb5300d04dbca9612da0b6573af19965252b35f97942facbd9d851b689b43ac831703395dd715dd0471dda1c5947791feeee95d8d9283a3328598adbaaa85f50aeb432390e823cfb6cbbda8abc143680020d246c5dde4a6c45ae84fe11078713bc87c465e8d88f0b23e2804a6a3e19afebeeaa5a0f4c729db84107c6c8b7f838e251b0c174599d27f5fa92046baf6ad431fbef4df75bfaef0a79dbdbd6a2fae8a97abff4b9eeb078696bd95fc84d71195a9bbaeb1cf12989c2bdc7e643aed74b976ab9a7bf800e26079d1d04880276a4f035d4dc86f748936877948f17c5d588368966818ff5005857f8269bb3b5c09d7b3306c93d569f37850e0a7d5bce3ec80e408d2b3037ce36e2e5bbcb524e982d81fc165a4af4769ba0eaf15917b15832a5f5f84caa2cc0e92fcaa63c6bb4467c8cf8cc36b4bb15e7417311ca51566cef85116d8d06be1e99c610ad9709d112b1238ff2068392c344f011327913a5c12636fdb5964bd42f6d30209984318c43f4cf294e71bf60cada362c1b0d6486a1003e20980e81553c8ef46031aaedcd236f162eaa20710f70d09a2c952f4de152a527e1dd9db12b8d249a3a3fb2447cf8478306ccbaa186fe4ea5796887d49e1d3c79aa4eece23d6a56ab1589433f820ba79b197ce855adbb7edd7b5cbc04a54aedbae2b1a31c731b4cae59d906e4c5a4b5e8d08452976cf186cc9a3877e2de21e274f0cf6a67b5e72f2b6df5a33d2e0b99f191ab9f6eabe68efa3fc65f7831ea402e3e70e7cceb1827aabba5c152a5877c3ec5b878e352e4bfeed0cc1dcd87ec3271335bc552fdf45bb4aab3082913618658d57484fc49314030b71358e9c670dec4375aaa02d3c4f4d0a2e522cc5ee2dec627a76cc378153a485a63b9d5d8be6cafb2333fe256cf19870b05b443704fad102518d740c8dad5bf2384e898a31e05ba0caf2bfd6c5ff3604ed969942ff31abf36916e7a1fc6381b793d8c3a11d8d16eae961658febaaae3d12ae6c985154e5ac1d8d97967238ce7c3574dc40933f752feb0aafdf5296598fdbfd6ea59fc706930b7bf458637f8a86b9bd53d72789fef65c58cf337f44c083a62dffd92f1a974eaf3c8e7664ba5e8d8cec9ad36f0f3cb4c9c6742ae6388baf1d9a90c1a3c210457579d66eda6bb2e72bbd41c3ce0187019fee1f340be413688eddd1ed825ffa0dfaae6ca05926103a1299c936c93396636b3a20cd74c9e056966233cb4f46cd1eab34a1b9555bfeecddfc919ae507c33098353985a56c8aefa0c226d96f08f1ee03b72628438fe775ea604b7ea5e0961fc369c9cb124e7b9a5a783b96662910f290a06a7bc834626bc46f5740806097875932422c4fe22afd1feed280c92992c2de8adf0247ebe844f874358f090a4fede6394317b2ca117e5da09c371c8d0fe559d0f47a4718f188031966b8b44017d9e6e93d2bef58d794b037f917b4c5d0c07f0d5b324a3e06ab50b98b6bf69497c1d7cca0c75a30e5d6a41f69a136569deacaba73d8c5dda0357deef407549a384c355e1e7887c4b4caa5c17e7225d1cfdad942cebc4e7a56acf724311962a381cf8c07f6bdfc96dce731b897f8e7a9df070740dc7b5896827848f3d32085530cc24874ca162d19b692d47f46b4105153f5f12f2d9458b4cc3ddfd84aab512b21caf70f290fadefea740a802b12145ab73e13881285d3478c54483c18183a9e695014416408d36be0f1506595b188488135dac65bce6cd14b520eb9cc05c61f5be82ca63ba042202291229a1700d08d3a73d37449dc69e4a1cd8fbbc6e2783071f9bcc7e0548eaf27ea0d983f9e71bb5b8e8a1c7d4e7ef4081d67cca93cc574341b210122940d6b79528dda6fd900356cdc6b295672f081e6c81356d383225d2ab3160c52fd32e79f610feb484ef08dd6e3f32576188300d6541f04e634335dce57e823f1eeb395f2ac1045297ec747323bbe8b4c7d9b135f57e73151fe6ce8878033e29fe6f4092b8a4b72a3c6f8d3f75a29d55a30ad1bda3a0876971033afeeb265e2bd8468b3a2820dee47d609d6aeb5c19e1fc02d9cdf80ff4f26383bf67c651a8ecb35a627d039c432862a7ea1b0e512401dbab118f7b348259fc4ca6eb92ebe61597920d86d082ac9cf22dbd7b64ea865d47ecc029b475f946444737b1d65cde4d8cd6d37b1739e2eb35d85db2778d069c48ad2fc32b3b2060445cb9e21a0b593d9b0efe856129adbb339114e7cfa36e66e6d6a78669efc050037476a5e7621e8dcb22d4cb34bad4b5f5412842f29584652a506e99f5f7a1bd8975b423c3c8f7e425fb9e761b54218082cee2ec6ac9113e1b01cc2557bd8b50df49bc8339a5aaeaaf7fa3ad9b5f5ac4e2228a0dbfa9dc9cc2d20b5218a746b10cd6d6eece62004873972d993e43482b1e2368cc779caf2525ab7247bf1f99871806498c0c65c1a6792c82618b68b35b3dbbc3521c0950814e75482dd48c4e7b0cbf5f847a17b5abc724eb2f0a3763fc47d1e5c7355a88594516d62bdf562b34efeb5effa0e0a3006769b02844161fb7454c14524bf61f551078646a69e4f95bc9718f240729554d61e11976c06eb0343e16991be545dd1cb9813912f9a07f97a3393a1c36589139bb766e4106a8df1a3a16bc4b8baf8a57481b9f4a18f18791842ba70eb5d7206c7f0ff6b09f2a72114134c40ac69cedadc32ddc3a4d3fd7f0742f1d230d0cca8fb593faec125e31c35c58fb7f1c74cfc7349f390b20f3395d7b2de683c561564a63ecc21de7abe6d5926aa7d3179ff84ca259fee519737280a20d4855653c8e4493019669a77857f2ec52112d3f8530e5164273efc1284d00f0b78cc2760738264cbabefbd92f4dc070f881b61b8f3f3a9744e0529f7a91866e13f9f17f291d9797ff81f9053790e728453e963323ecf22d468c353cd205b9220ad90a080a05407bb8298cb4f8e8101c1f92f40fab00b6e031c0c11e967342168272379912b8c800cdfcdb6cda1de35370c3f8388d442db9ebc41ecf30a1c852640e9ebba9fdd92e26430fb845f607d51e1acc35337d00bf5de073d70657f4dc99f367f5379d180eac454f1da13d25aa12df958e0376d6fb6f5ce8bc825f6ebe0869bbd024612fa7454cc42556b8e4db355a7ecc67f658730a7fa7080a80dd0098bac2d3d80ec601da9e9557cc30ed6e6ed3ac76c6c42e311492d5420881e9a8332ca386bc6f84255484eb3b6b6ce76ea4914a1487a9ff3d77dd9df3e32b3bc0c0098771364b121a9946ee5a9c367be0c1dd7e2e146d739d19a825a203467b4f2697951daf6917e8932cc53bc55451ceacff704dd4d8335be43b95d7d09a515e4f1ff61190923c98287e9967508f103f4d3f4af82fb1e77fbedd161bb2ee63f422e8d1d53f8e293142839164a3af603c8ce431b88dc2370f91d8d0f4110b5c19873c4ff2262585bbc884d6bc287cebcc51384f1456d1c9154472eaed55bae2d5666198efab1ff93fa2c98ff645be56f78481cc94f2a74898bff48c8be6c19d31a7a0d35438432cc3b89ae59b643c4cc50d08d0c0ca0c5aa1eaa6e0582ce83734334128aa345ac752f23f0032ecbf733fd415822144c8a737adcf0bdb90f955e7f08cc077072c71f138ff0743450f736cc2f00a3ec9a2d862f0b8389c78baa250c7c9c3461881d3aa72242357d6f4f555bde0919fc263952a9baa8d244df5c379a03f599cd121948488f47bf50936a100ae2e3fcd95d3859896eb32fa7870636a6437a80399d45d8862b838a5459c1e777e233b4facba51b3c9c350a65ff4282af9f9db02d861d6bce8b781eff3ef15c21305aead6de0a1841c7c0fb16c48bac049cd1822f791a62a033b7f5c79f72de7571cbf70420d1b11785d96a20636c2373bfb87e95c2c76da998a5973407c0ff031b2971f92086a64c3ad0e019484e894b6ca52e170faedd293ed38bcb04662e4880d5d42c49a3485e7b59e5780fbd3e1a4849e929bc2b41cdaac44c0311ccfcc46763a62428b227acabd6f2a31632c26ec6f9683464e72c0550fa4062cc8feaf5235560673a3db4d560ca9050f849a6976d35f3cd764d0c4eb5e8117071096959492d8b6bfa9080671ec736d8225c6b42afa2445b8cd763286e002792cbd4087f4d5e8fa8e6750ab05a3f5ecf210570ef02b0b274f510b110dde50b60197a38b5a45b05b48a35c46f97a5809c70bf680b0ad3c6906b7386a67a09256f06568acae2b55605526d3de35b9c44d69df4b1052c9e09344c9a4447e6ef1df60f136efd8cf02921eb85e7ca1a8603e4154fdc8bec71f2d704df7a7399095dbb901b65ec079dd770920a527258f5566b531b5b0dcef20c0f40cdb9c804da5d1625e8f004f710e997f284fe7a9b5ca4d2e401acf95880cd337cef6293bef4a4ac5d06add6bfebe80e99605c41563e8791547d52dcfdc0aefa15af05952bcabcd491eeffefd6a15b2ad336ec751e1d774d00f8dd27331439b9a7a7231aefdf1e25612c56aeefb3ab935c733f74577bec6c960ebc76ad0df5f8af424bb35f943115472952924fa561de71afb5dda6dc5f5660197139a0105c3d991adfed0facae3ab07064489af90642df36ffff60a168b11bc38beed0cdd50ef19b6026846addfc499c1c2046111186e1c36e78297d610315776d14a38908d3554e6d0e326158e05f06dab7c5900c0115bf91d675c300a2d9bfb5398ce4603110ae66f2be22a76d7d3b308dbe086e5807960a78ba14a036f506c4cc1162638ec025de726c21321a30b3be0083ccb0e223e128fd8ae4c825e3e74c4ac5859b62f05f441be0ef917cf61ac3bebb5f6c35f71b8dd35ca5f10622cb9c441dc070540db5384da0d03dec59aaf216c95308d9fdcf997893fab34e3e4e35368e956ab894c21b861085a00eb81eb9b7322a0f24a457ec7be535776492c9b7a09c97132702006e53d472505abdd09b0e8a66f7cc875ef74bb4f8b5efdcd89400cc27e225f1eac1455128d736c75b069b367c4b38234f3a7687b7175658f54ad591da601bd0fb84d91b1bad4951289ccbcaec81ede493267ad1e2e42ea39a4c9d9221059f2bbd596d96bd3af907c3be4ecc2a245457e55bc7281ed4df495239d7218e5b166eaa9f9158a018dc47b6fb629ed7bb0209904ae9fedd41fec6977300d494c928164dc7d2d546bf599d7cd4c5c757be8d563e6461ca80b3f387e488a93fb6e83abf0172a09123a6f7757fcad6ea3c974cea4a48bd1fabb35429c3fb4ea77720c4c580209920b9583c6c5a5e145bf8edc7124e586a72d0c8a718a03b5fc0581b5cc624421ae89a5baeee76ff1cb11f1a928aa523d848825c52c123d95afecc30793d7857b46d2919b578503e4e6e01b625a0093ba52522f6eddfa955ded765f89097e6f1d432f82af6a6908c28076348f6eec4f05f7d4510eeb0bb2149de97ec49621a8118be8a07c8d5a2793801963b8ce372e245d56b42cbe0d3dc2a78e473ea386d85f0845128c291f0860ce12d066e1a9da216a1f7f50bec8dd6ff2ade3819676a80da08e0167a781523052a9a4200f09f8030879db0253043b24688e7915d9b9bf253d0a94936d48ae0cf425076fc02133d31eaaf0cbe588ffe9f0783280d217c7aec602f49ae53cc3fb9cd020da5cb7309893fbe532dc1dd0c472b5364ceb5865b123be480476243a0532f018e94b949a5a51b4e3c0a08b56bfd78bac008b4343353c86b95787caba329ece1624f68017223f1d11fb999a3ae10543bc2519a20942f9a26f1d82f4ec8bc8b24314c3424ff7457e986d2f112432e5249042ff3b5fe0425991ddce6b771798e3c7401ba4ece14b1e9f9ecbbabbbb5f784ddfa4d6eb18fff54820d3f705dff7abc930676c93e839464f15d35ba8835c851e85703d58f118bcbb6df95f3be9cb50716d0d63ee15f73ed05ab1bd9392c29529bacb374c7475f7134ca86d"),
+ %% SHAKE128 VariableOut
+ hexstr2bin("84e950051876050dc851fbd99e6247b8"),
+ hexstr2bin("1822b7cc3c4ea4f2440a362b117f808a"),
+ hexstr2bin("2ab3a70f3b01836d8efceb67490c3c38"),
+ hexstr2bin("0a13ad2c7a239b4ba73ea6592ae84ea9")
+ ],
+ [%% SHAKE128 ShortMsg
+ hexstr2bin("7f9c2ba4e88f827d616045507605853e"),
+ hexstr2bin("fa996dafaa208d72287c23bc4ed4bfd5"),
+ hexstr2bin("c7211512340734235bb8d3c4651495aa"),
+ hexstr2bin("ac71d8e087ae133f3da590e1a2b54d48"),
+ hexstr2bin("b4813895ae01b43c9d9ed85a8b03aaf4"),
+ %% SHAKE128 LongMsg
+ hexstr2bin("3109d9472ca436e805c6b3db2251a9bc"),
+ hexstr2bin("0f994e4aa804282cff22ad7d6229ef2c"),
+ %% SHAKE128 VariableOut
+ hexstr2bin("8599bd89f63a848c49ca593ec37a12c6"),
+ hexstr2bin("19e740d7d87bc322edeee86a05eb59b64bb86f90dc7b98f781720b7cac37fdaf293ce6bd047a14fe"),
+ hexstr2bin("ca7ca55bf123aba45287268c4050ab030b1415f4497d5fe8dbc5386ae37d24384a2fd6a715fcad48ff9e810c1d378fa70f1503767e9e338e33697206f863dc8015b4d1e9b8f81ddee22aac59d52055a1b0784a364369cc50f403045a1bdb25b639"),
+ hexstr2bin("5feaf99c15f48851943ff9baa6e5055d8377f0dd347aa4dbece51ad3a6d9ce0c01aee9fe2260b80a4673a909b532adcdd1e421c32d6460535b5fe392a58d2634979a5a104d6c470aa3306c400b061db91c463b2848297bca2bc26d1864ba49d7ff949ebca50fbf79a5e63716dc82b600bd52ca7437ed774d169f6bf02e46487956fba2230f34cd2a0485484d")
+ ],
+ [%% SHAKE128 ShortMsg
+ 128,
+ 128,
+ 128,
+ 128,
+ 128,
+ %% SHAKE128 LongMsg
+ 128,
+ 128,
+ %% SHAKE128 VariableOut
+ 128,
+ 320,
+ 776,
+ 1120
+ ]
+ }.
+
+%%% https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing
+sha3_shake256_test_vectors(shake256) ->
+ {[%% SHAKE256 ShortMsg
+ hexstr2bin(""),
+ hexstr2bin("0f"),
+ hexstr2bin("0dc1"),
+ hexstr2bin("a4d7897eaf5c49979b361c39a67f47e26c2f75e5ffe0645539d4de245138eb8cadaa45aef7fa0c7a732dbbce90c85be2bd4bf6e37dfb4fdebee4d0e0671fc45c3051c6ccb674799bcfda7a431a6e93b3db3e32f30636190a9a2e5620302876e0d4d2f6201353fac4554341df6efb591c6f100f5dc21a2aa176ba592bd7db69e14237bbf2371df6bbb072f9ecb1f714e621c97768d82eea6bf98ebf4a82c005262188ff894a5dd549866f88b00ee82bd99872515d71fac230ccb472c55a60"),
+ hexstr2bin("104fefe89f08d15d36a2233f42a7defa917c5ad2642e06cac56d5cc51ad914ecfb7d984f4199b9cf5fa5a03bf69207b9a353a9681c9cf6437bea0c49d9c3e3db1f3fc76519c70c40cc1dfdd70a9c150943c272cf9eeb861f485f10100c8f4a3e259c6470501932782512225ba64d70b219cf9d5013a21d25d6d65062dcc6b3deb49d58b90d18933f118df70ff42c807ccc851233a34a221eca56b38971ef858475488988794a975d3894633a19c1ae2f05e9b9c0756affd3cfe823ccf29228f60fa7e025bc39a79943325126409460926b057a3fb28a1b098b938872883804fd2bc245d7fd6d29bcda6ca6198f2eff6ea7e03ef78133de8ba65fc8c45a688160719fa1e7646d878ea44c4b5c2e16f48b"),
+ %% SHAKE256 LongMsg
+ hexstr2bin("dc5a100fa16df1583c79722a0d72833d3bf22c109b8889dbd35213c6bfce205813edae3242695cfd9f59b9a1c203c1b72ef1a5423147cb990b5316a85266675894e2644c3f9578cebe451a09e58c53788fe77a9e850943f8a275f830354b0593a762bac55e984db3e0661eca3cb83f67a6fb348e6177f7dee2df40c4322602f094953905681be3954fe44c4c902c8f6bba565a788b38f13411ba76ce0f9f6756a2a2687424c5435a51e62df7a8934b6e141f74c6ccf539e3782d22b5955d3baf1ab2cf7b5c3f74ec2f9447344e937957fd7f0bdfec56d5d25f61cde18c0986e244ecf780d6307e313117256948d4230ebb9ea62bb302cfe80d7dfebabc4a51d7687967ed5b416a139e974c005fff507a96"),
+ hexstr2bin("8488b6b0a5cb3b778d9e6169cddfdd7468127361da571661589a51e95a0ae5c057ccb457cff0dd588fa4298131a7a6b3e201914668f279a06328a206d234a8b1da3f6425c3f913be44d3b58c006eb4615053c62743e41477e3939c68ba51d4a5954990c388cf25d13293cf30421b50a53a70daac74172f0f624207b81948b2c31153fed826585bb8e34d3d150096c2a729c0957c02711290679fab9df8018a34d5c3770da67efd9cdf67c8e442c75290a5abbf40f5dc05217b114fc05e64a4206903c30a0f2852a4ac4b38450e96488834991f9f553f4fea2500bdc8535947ae5679aa76693ea3f9d45f55df6eebebb660f27e422b813914edd4bb5ee0c8bc06238ff4f9bda738c2df0de75b69ae8443b01ea4d07e8c0bc75bf6122a0b994c5158ac683c124d592853bcb56007c75b56ac8559f257470d2af8cc8ad3deb43cb77e37d0e770174e53bad38897511eb818dd8f6550bd9a86664bbfea026101d75f1c9af2a2dfed5e6a9bbc28a84e9be3b1caa58f91b86dc0a2dc38fc5e9b696472ba3c961368b7d3957fde5d3a07272348fc2da05e50a80107349d869094d22a17e08586e5125df3feeda1eff582a8c205f991615e8e3492bdda57513f5426727717dd89bee6c807ca6d6da583c90806114b37973a79f7bbeb56cf2514e44b84b9e6ac6647eade562487aa4889e42d389479890d991adac3ba316cc9b74b2ba807d65ae62728882a32c4c0a0b2d9019fb50ced8a2477c5f451f29507cf91ac26866e4fd106a8afc91cab1875a3b26a859d8bcdd5839aa194d921b4a504bfb8456036f4ef8e71397c0bc5188f07775230747e90b75d8b54ec7947306c00db364fbdb6af07658a108b279829b6842ea0e9616e9ef85a50c8445aeb712468f00d8ac477e9e333fe1a3e97aeb4b1c2e13cf88ce25f6023f3e3e4a395df95e703273f9e000b49a1d241dd848f1f496a970da51e623bb6607c8f137dcde286231e6bb572d97b0256106baf15c1d3462459fd383f769ac4b8f1b31a101ce2c1c448c136698ba3d6fbc082fd73ce3970972ef3f816a7e450d45c37418309470c738a1108ab9dea8c38e86d0fc53eb88e6c1ed82fcc7c81235cc402d0fe1d7930d8eb43745f97b7eeb492142d3497b76a4b319223887ed905f732fe0fabdee0f59a7340c2ae0274c218c55f9fe863a0cada700a1c733354be4dad1246b0d6e422612aee3abf6488d10b8a36ac0829da77c4e22864b5255b15073d26bc358dee81d7193624bb485be336e8819ebc72cd39af8fd54275dc54d3cdea23ba638a64d8a24700c7c93355c041e728bb2ec5e1d74480f7fddbdfa9c046bdef886155b0f06abc356c12afde2fd38f134c83aa7e1b2abc77de11b6c0a41bbd736e58763891d881bb5bbd435ef994e92b98e6a90ccb094b3e2d3ce90347f4f397b470756682939d7cdcf3783de62efc56e97d4bf3b08b3633328700d871776544a1d0dba18d44185f37dcd4e3be46d07d8570179b9256843d341884a9e94f1d607ea130507893fb4bb39fe1b849876e89b5e8966d39123d467d17fddd782223d4930afadd0c274d2a996527254f96dd60c10d4ecf6c686a3553adf1dce6997d892834c3af11a916e331552d90971e25ecf106c92af5ff12f2ff02ea0efb5b35668547122e4fc05e5ec3987b62eac54f138dfc8fe79a813d64c83d6fdb7e115352f26a70c28e152e8397559904103362bfdc16266c2493f375f88609ab3962dd37b6c564d395508d3af5b510dcd11166d7277d18235adf3d0cd6073ee6f2f7ddfc1a8176c92d18846b24e06654f5076443b658ed72060d2211a9e599f472775a8966c62e0c1e151afe6dfd26d343bc62ebd1d02bc3eec5ab6dba4b9f3d818b5dc6e2bb150430f057db1834d5c084d96bb7c00d5c7ddbaedf06515fda2ae6dbed9bd589b21a2a5143ea6daffd858005848fca3607089670c6c5b549d1216b0e12f089578db4d6dfe714b3932589ffe9b75b3973b5cce4b4689230320c8bd6ccd4ad444e2f3cfa434a3f340bd634c92d0b0353eeed319022ce56084214d1f0bf3cba4f961f185f257454363c97eea578896f61cc379f6061fff786a07059818d51fff256e5b2cd2b6e3c0251f01f5d723787aa381b7e1e1a035ff275a88d51de1c6474b70a700c003fdde070692fb7c41f347858223690f4e37494e845d4275ba3d21fe015bce34aef2117c28b877c16495c1154d32f7f98a620b322ce95cdabf02f39f98efd1bb287eff16edd8d740fbbe8be55b7d1b2ad544a994acb3a09770b650b2cfe9a7cdf0e2eb49ce8aa66587fc1c7835485f3dcf6da8c89c42aa467fa5add9fecfdce7a8cc8a97632461b999dcd740535b108cd92b7d50742cd6622c7f8f77ae08dd92a0aa4182145ca5c791bf60d04dfbfba2733cbbcebcc8b16517a25a0e662b265988eaa49c8a99cefa00323d9a7dc15d192defd885eebcc7e9b19cb210bfbd4396b23c28391e4269cb75d3c2117b21d15b89d35838726f864a9aed1c210325b1e4cced410a480cf2d662f282d7bec3d4e3e775bf38f05779122401fcb1d90b0dce399c8362a05fe685483d145fc0fdf43cf7558219d10b1cdafd89d845c9c393bd78b863e4bf91d172a601eb1c2a56abd119d1255dad00fdff87d3335f3de5f03d1acf7702ee693008fcff7b8f4364684acf972d76dccf791a437c9250ade8afb78050113fc53c8ec2cd644a4ba48da60499160ee7f8cd2a95c0e479dca2523ec759d8d71dcc924a2a980edbb3d659019906bc5e3389909f0c70b95e55a3a909e6f7d84d0f7cf0fecadd2db46a514cf76289187febf7a022e8e359b45e3a53bd90fabcc7975edf190e0d6f48c1ade0040ba0b90873db7272b589542058def8a65fd314fb47dab3257eb9321d7c349893feddfc6c6d9d9dfaece31706c7f9fe709e017f3c837923a0976114b6dc8dfd33b6a171303bc67918198130ec3e23db98e6063a2ffcc29716e28595897522846fcd8bb5e850de9a76a5d430552dd5168bd261f439f64f8404daa72329d89ab4a49e0d18f95f5e88832c5c73b6199645c5cda52bef126880d70a364bfc794f048ec2a638d7b9ceb9ca17aa99a383efdfc332405fb3d3b60419dbc38e95d9b7fa6ab5d78427f297a4b7e21f1091ff3a5b20caa3fe1cbcb09459d9df596a6c8e1ce4f211995e9c51dfd9365a56f5de0e3e6ad17085ae2fd098038eda3b0eb419d26a0204d6e00975d1aa6ebcfb63b2e3ffedf11684ecbf9ea5935554b1e742e3b1865ff1cb50dc8c10a805e76c5010541636a4068a3cfe8333a5e7cf3c889b8430b68698e52587fd235e964a384f78c727d164e0f4234f26d8a790ba69672073e676f04ac2e29e51604a8a55ddb64ec59b4abe06613423e1fb04fa969707642b49343787d2fc82217dd87cbacb2924c7f71f0a173fe43f5d116a8ff463edf912bf9ac6e093e7c5d5d294c7b98f1c7d56a1aae3640ec7d3e0c234cb444e8f31d2d00718e59448fbe6e51c05f2a804cafa419ad4ad5c33942563f02716b56fb0e41105c7f7427476ed4bbab1f69476901b5d4b583386dbf283ad293dec4713e8cf09133ea62d1ed9517d1d5635e2f65f323b58e8973d057193d44c8a2561f06bec0ebbf762c28c85abdfa05d20f1e8aaf7822ff058ed6493de9fe67662a4999cc3b87c8ccd91622e6509d051486fd1e5a3b85d29f8f66dcbbc56638d146d840dfe9cb16937182416158249bd2cbb56a1ff545cdfe80675abc59e5058cb4b2431f51207074fd536ef25bf690b4e42cb6242dcab9cb68c309672ee4943e7cc1eab3fbef9f178473f1c1e37bab61db4c4a916b7c5dad506132e5cbc9ae90a4eb7dc36f9e6c230ec912f7771bb682b8fc6d8f38c3834d3f291c5db8fd1b2a361a437936979c41a43d19bca03e77be812368461a07f1523d0311f87ee9a3b3393585fbd740fa184cb74d37e44cb8be40ab83dd9bb0706553d757dea4f40ed4b6248b71da034d51101ff891cead3f29fc7b067c4326c82e878d3736b2852334b4cf9d04c8475c93f85d60f1e3c9d4a8be7b394d99d1c154e30df9283106ab7c3b1e03db2773f0870b5acd80ad0941e38d936475251a23806f3fe5dea8de6f5354f9042082f1d1d451fc8966b3d34d8171abce1f6f67b559320c437d9f7534620548eaf85e28c723d22cdbca7d90bf2b72abc07100214088345a5a309e33c55058f3fb8d45809a1c97d30ad494a96088997b82935fb18cb47d13731d2e80e9c655e28007be611166a7d75fae7f84f841ed045cae147d6186161dd66cd6c4676c03fdc08e5233f6aa0a22227dc68b4ffd762c96eaa8ef87a60662cb461f6708e96a2d0548875e0f8fc99a9b6cf2252c653ddc776ea26ec5bb55c89ad390d194e692589560e84a25beeb2911a08ab9582049fd48638a61e56d0b398a3ab16ad30a8467b9fcfa60ffde78b6ce4416b19cd7a37552f0e932ad66319ce4a40a9b8cfe4743b945470667987928187e1e74be32512eda3e4548e64bcfd8f13824ca8969e3d2dd6256a52394f56938762b6a86d85d0e2b75290b58c7ebd7f61f866190f461fe6d7bafa6fc79f4607656c4fbbe72ceff8cbc1ae3ca808f803e80b8c66a47c8aeb0badb6fbaf07ef51943728f2ca7ade8443ba238051ec2bb10844363df0920caef45616a25a09c106b67bc8e9a2001b09c1b4872d24a8d70b352ae9eb6e271397244c9fe04325599b2ea2a60ce85d241dc8494f9f7acf687701481e1b97cc4e581e09c92214db27837f4446d907d29824c9d0f363ceb08e98fef4593ae7c20b564406090429ab99642ab931eaab00d5b338f408f6bafc87c580d811b5e8d8e91628ea05f30c074d91a3a9ed9e2c786b4db81b06d4aa240291296bde391551561c5efe167b368eb2a09c7ed07026a9c758a9ce539a36a77d907b281520e7cbd0dd38dad10923a2572557b0911d0405887890869593caf75727d8756d09fab8e7ed5908bc297dd2a97477d795d15f2c1d020ecc437dd1a43ddbd40c3c50a8a919a122004e588fa028327f6c5559827df3996707c395052e8030f03519918c0cd4ad4639fe12e38b61c3d0d76602388bbaa35be1bc95c006ccbbaf886b591f64a776184e41e51404c4cd0eb13e57cbd5c0c43fb294ec8ccd81c0849ed7f3dd3110703a95b05b9b9cff92ab7244e6c6dcb4509522c305d5d33e03f1b0b60e40029e4fffb8dfc2d4c440d919a3202bb400e3fcfe9aed6e35c85fea8996ac14d249af4a0a1016c77a1b56f4ebcc46931e02fe0dde836aa2270d65e8be8910841e86d212dc33fbe5d5f2907a94462eb96f235127a784d13e1422fe83033089f88f4a951ed8bd058a82fa9bde95a94f0b12129bcefc17979ea7a9784dea058eceaef566e291f3ba420b83c795828e3e04b1212c685ea454403bf171adf5708592f817d5f2aa708088ca3ea233cd3d6232c70f817811b36ddf5b9b6927981a04c1e53250c354cc0eeb597bba3de86f6211ce1e4992e7ed6aa87117cd4f546a82f800d565d9535eabd643db8e18667c943e30ddc33d338ac14836f71c89bcda9691d57183a908c0edd5f3de8c67c9ed9ef489eebcdb7744084af7a9e57b23f2b62bdd7748b7076b4b66e3e54380754ae8e1539165023581b60d5db0a6784c3b482283871387b1d65b05be8022070b1ce89108867a25f0cc411bb3abde15782ce3125201829faa9a833ede4fc6700dcc3460233e5acb3b9c3002a3dc0ae74a400d77e387047344206a7ebda4af3fcfe152fb2e298566787a194e98f1ab42350d2dcf9278178730e06741e71f2aeaf3e4aabea3c8989c29de534c2d6d3ddabaecda85f9553a5fd3830024e52fe7f60c5ec2170e9bf2b0d24174a7bbbf41056e778a3772cefb71ce60cb94225754d56ba83fb0d9242259143c03ddde584356a235c360b915c9415412302c4916002da4695a089df1b6093236775b0fd8b333089ffc7e16fa2263370fc439a3ec6853491ac78ac7751c35eeb945abcaa12f4f042ebc0c36cca53bd9b1fe58dd5afbeacc24feb3e03ec3539c4cf54c8951f5ff275a8877730da05faec155165fe24958a020761062af5a06bd6a1cb9e65bd5c7d4bbc86c564dd23338965eeea602bf215785a42cd9573eb3d48912704c3f1d3daecb12377b0d895b19a9723e4a4cfbd964dd2f65beb6efef74328d0c357c5ac527f0fc853bbd543b948345a1a7115de55a8f7e578d5efc1e1eeec2ea3c840cf2b2d5cdc232edb1596bac3fa5570073d9ab25078ecfbfc1c427267a96bbf6a25ad1d22c7adb0f3f38e1acc6b5d49acc3ad9265157e04a0f764f621aae5e34c942278e8f0baea24c5eb8dd714faa30c56d571be26f899e03a8da3a197d7dded05d06537c111847673f33d6d5ff42630df5c77e4112061c6cd06a16fda0a545661000d96a17c25f1e5ef5215b3127b1f4788cb5e40bf055998171713d946f18d8efb978df8c1759e0326f458781d4ae174a296b4199c2ca183072d15fe525c9c76062ca42fa91e84b6e249bd17de3a7f50f1ff6f06817fa777200dfeb0f83c8aff6cf464acfcc5abe4697cccf61af400c2bf2f166d34a5725aa8dc5656fe396013731654d882151c7605b30d7e9c206cb1524e31b87e470a16e8c5a35a2dfc259965bbfd1b02b3de55fb1609172b65cc07f483484e270d789128ee6f234d2de179880ae5de464af96796cf472a8e6b27abeaf6e497f79ec3b5b07f411926749d9b252c9687148076961d034f1d8edb85857fd08cfbb6a3f368c7a1a2a47f7760e78348988035fd5cc9d7ddaa5af35dee7dfa39c9e22bc31194b67b192c22e13245ec87712f716ab3e80d82df4785986b7ba25822b41fa72420c6372e19d5581b9da611abfe8f6f84da081d764e0b350367c1561fd95af734f43cfa69d3618424d7fa0dcd5459b3d6890dd4f8d64fdbbf299d5a48baa45a4af863ef53c28e0f54db30a3609895dfb1dc67cfec461733a5a97e8d26bdc5786c14823b734f239eb49190fdeb340c9a1dbb5c37da082ce5c0d0ae17404b84faa530e00951e0eadc4ae16240f68f868d230e36344a8356981584741f1ecdaa88fa34c67f76dfa96e20b4ed970a11cb60cabd3bedc51f5cf4aeae17cd49a9ffc975087cc569b229ff3ef07a28ae22a2ba1bb1b48bd24408a39237e04a48494b661d944a04b2c33235aa7f511b6a2ad4431cd0e835fa5a204ac90517bea3135606fac17864da788cb86e47ba99c6912aa4d7032114c4c96d945c020451e371e85215754d0c683bfe319232fdca4bbf69855433a550b8fab7265214ee298fcac8f275991225e47351481c2761d5801d5c8d9a46be105e53c6abb3251f712f85bf8d221610d6d7874da4160d2fe2ed11ca65b126b17c3f3d61c9871cdd4f4c0c95cb25d28c48c8e1baee87ceb01b1b047ab5a1b9c9bee2215228d7e171eb6dc6125685b7d1a23fe860cd785129a6915f352472e94dfbcf655f71aa5c236e1a1bfdd34aac260dc40407eaf07ee5abd788c35a2d9c845e8f73514c09753a00db7320ed7d98d2b1e09d76ecf73d2b5376e9ec00dabcb70ca9b1ef60fb1400c83936414a25651eb51a1b767c957642c5f5e878be7f302f30a58903568821bbefbbcc29924afa5242fbe7cf42d311732adcef2e3b26e031f00c6253d5b884b49cea9ea06ef43fb67f0a6749e5b8f606aab465d720d6b4087465a3a986942533f84c126e862c1c9195cf04ab0e4d0e436c04b23b149894fbe538b6cb1123ec7f2b6b915a81f9792dabd226bf64993f7199543d22c07368516b0e02811d6ec2e25ccf842d07643ed0ea9c364b90a472da582d7054cb6f18532d6def95527b8da2b4fe51afc27453c37ab49a18397690c4d215b298de948c4954e0e5cd5a5437085a8262a85731a04e82a2bcee08187f782f966cb8a1ebad7b07ec4cf3da01a65c76e31de071a30eb82aef0395a591293e9638047abb7b61306f1b8ccd7d88c4e632a7ec1fb6d3834b9c22d6d4b7e9745d2f82e3a7d54c91b1b4f40d24e8f377edb458362ccec8e84dfdc39f74e6284a1f23fc710796672647fdff38bfc64aa9bb247d62d0628a2607785ac53edc5792ca9a2e1661a7d3063872c57368bd934196ab9fdad31a2223c13270b0f73e7e34a7bba13b7eaf981bcc3dcd27e575d827c7714505fd9d97c9c3568cc76ab7a317feafdc462757bf4b4d839fbdff982043969df74b1594f1ff540a37745eedd025179e67a122474f80016fc49b7ecb747d4a573e14e5a639acbd76f81f9a880c4539afcbb2372650a13226c32b2ab8dbbd2dc9a0c8337c76ba47c4572ee731605fae0a8fe3309dcb9626d324d4cb075deb8518a7cfc8bdb0103719dc0a0d90d25cbcd83b9bd453c647259c70fd286002ef9b6fdb926c12ef96d8a0101bd44c6d22ac415841eaf14c9c79a92e100a69d3b1b7f4c377e6233c34717e7ac9040d369ac66c6d3f3b1e627d6923e67439cdd71921cfe7e700979ed249dea37d647d37ac518202fc8f8312d23c3221917d3216102029cf4b80171e58a9e6119dbd42774197aa9fe0adc763833bfd65cf9e41997cfd2a04092f4ac3aed9be4e93d704fe3fde0dcd535f935e78aead3d9663a1674701f90d8bd5d995bd218fe2c57a6cd5906fd213bd6b8f0999dd692e4e95fa885771bf72373a6b9c6bc981539d6e21031a9222fa869a626ecf0f5ac25ffcf6059698a3bc7725a9e1a118bb2b817acc90c78e250916b4b595008da88031603be5eabdce72b2603a6b485766f1db6815857dad02c8d6812f8415a07af02c8971303d27671c4fb1c84246cfa92e12fea3e92635e2bf2cc97945f78ef89bb93f41ef761f9742b8e1f86f85c5ad698e051fc4a96fec7781533ae08c9f4c083e4953297ded866d54b874f9eb84992ab744301eeea349773ee81b5bbe1d9bbf25704ee63ad95b248bfde4e8625396faf5857f00dd192047c950c4c0744e1049d47cda2e952b3ff0c57aef45d179cc5fb08eb4715e189a708c53dc17967cd02db535a00a762a59586af53e171f3f62e35028767a59e9df79edb80c660bed06f32e624881c51fd2e4e237796636ed810e3d1b6d22b6d516ea5649fbb7bde9d824a9cd7a9c6a52d11c9608473afdcb5067df2f42715d22684c2ecc511193ba6e478d3cdd624d0b7f89ec2f509c5c20f8769f2c9d6f3618fc25dd0ab87d75a7f1e4427819db569203b24d763d933b36a0c3e3d925252a01fb00cbcbd6014bf789843049d70f6e2e861f22a3b628825371d714ff839a1f238c6260d753a8fdf35daebf76bf077b293059d17f626219c093399b665fd6fec8d8d504f5d87774e396ea57c978ca66c928e7c4d2ea49e9c52d71ded65fe2b03606e01fef0d78960f5bc06008ab60185797516164f5e5d8a9670847172ec51cb53f0c198b99b24cbed5b942fcffd2781aa42012fc2c3fcfb66a853df07287298fabdf5a92252d7f392c5c656d1d276d5c78db3e64375eb7325e70272bd431c10a71e34acb58f215c987313123ac67e1633a0a4c62bd4a76666b526f8efbcacb70baa05100db08f8b40ec10fe5559abb791336a6bf660cf69ab7b17ce2905a2e07ad9dd8f755770e42eb93657cc0c9e3e42be6b342dcab1d166d18b6ee3aead418736245796f4841bd43309cb194fc40eca9a2dbb5c42fd521b30c857ca280a4ea3377024ed182776b411e1d4a7939bcdbb6f286dec9a10504b11751526f2786c71665753e222d9a960ae74f0f198a3ef7795f1c879108950891c082394ae462f544b308110a529184c91299ca19f6af8a1b700576c46c6c72788f80f9dbc120bde4e9228e33f7447da42865412485f5dac3aeaa740bd5748d900db45ac9c5a9671e8f2a7f42279bcbc3b01a3de040f9309a9f71480e4c94a001c18424bdf8e33b1c2e8aaae2e77eb54f321f86f52ea3d3c8cdc3bc74d8b4f2f334591e5e63b781034da9d7b941d5827037dee40c58dc0d74c00996e582bccc8dec9c12550d0d80fe75efd06f3c887b4d46be1fcdc48a24c6fea99f0b519576f773b3c649ffce81322dc45cade07fe80fe727fedf7c9661e7110d9f8b4d25385a510802e33fe0cccafd90421b36a311dda2814beb88e90cff819b5e107e555ade0c0190cbd72da9ec6dbb4b6045da3de00035cc98ee6a6d35d4bda340ed0e23aa2e0837a8ce9c60c106ad3f24160db54088a94a8fb800cbb294da37c3a136dde73f84be222d1438704fcfe611af25c224c3468c77ee95709e4859e342a2b6cebcc5ddeee2fb7f7a21d18707cd1a08aab437745cf99610a22079a46d65f9773e524a7a8f9040c46ffe123e6c005fa0b51a6e9c80bdba58aa60ae229dd3ad6077d055c569d405cb5c2c9c5ddb5384d32d7bf024c16a6157d0876f3b3d35c658c05f6f254354517c8cd6f74a1d6d938b58024376e7a894f45bbd455713324d4960271e02e6bb2cbf4622a96ffaf893af80d4f1f8ba215a179529b6dee973491aa04fedc51baf9e7cd1383c3339998d27eb1c8214027abba8c4f44c38859fc2b3c80061d4a8d8b09b764138fda4fa88dbe1007c31865ebf9031458fceaf5fc0fc2061049855e069047d65233701c38cb3aca869b60f736fb524450606315b4cddef9b5d79ec6cd29f2ecb8748199ca297933dd00d6561d200e41c3949d23a6d3e7fd5f4e03327f155d09ceb98b42bd06fdde73a9d50d1f059bbf1b00059a52c0716385233f4b6d00cadffd88f9a1b2023d3cd2ece986bd2a9efecf4918f438f20fdecfedbf5c477ad0b1dd59592129a9bbd18340804d67fd6c9378c663cbda1717c188041fff7ec21331e6a1265b845bfe3efd4b35dbbffca91f700e528ba67e2dae461ccd9f7394e6d291f6edbb729788c7b97376bc15b6b8f57d9781ae3eb5c617928e34d6d661e01667d11bcb44eac01e404fdf6919aa30b7d9e86dbe034c557ff5df02101797836fe0b63d05abd448b51a46bc6d2c4594c91d77734584a07059d895a1e5a7c43551951cddf4e7d2970eafdeaa22de2759bbc0cb2eaf33608b37c33890c217669a6ded8b5b03b9641f8d999e3544afbde4025c6a1c85affb2a9c4e3e5b3fd70d1717627840cc711766d4300bca2d0177e83f68cab9535adeb2e9c21d6da977163fe09c7a3bb9af5af1553b8df55683e3cab54e738182b499d8b2c76c44b96810794be3e78855d4ad43a2e4bb235b9790002695eb01ae5d9af099d6917cca3a8a57dbd166d32b818cc22ff419f096ccfb2ce5d797c630b6809ee1783372666ef25ff2fd6e98952688115ec200c9b28493b5858c002bb44fb9382c14a8c21865d6f633c075453d441da5d1d7263b196ee81e881bcabea389c350fc4487f84acfb6cf10b301558e5acbf41bbbe0b145dc66dc600f4dfb79052d7db480576197fd002f0c5d996602eb01808ee8d6c8c26d9e739fef007ecf426612f7408daa6a8e41aaa918b3e335755cdfbdd66eee09930d88aa339894f0b1ebb5370d914f4ce3f9d6598cc759807a3c762b1d1f9da5dd226021656cfa97e45cd8f65008acb9aea716a0127a359e6a3ec859156f5401bc7bbe780cf3fc1f4a0802b7b05a6f2b4ec8001f521303c9d5c25aba67e4adb7a187d81082d43611639ba6ed971cf33c34db0719c44850ad4d65b1d18d0607323cc561ad7f7424f8c1764a2a8cc6c546f15eb041d17088a321a74240ed5a0d0bdeb2df814b5a454fd42670d0f9ce71e624d1800a1222286602f5a06b01370b9f17ee964e8f0d03812c3a9f7376ba75941fd1fae4fee3bc4061df30b271439f7678b74a67b2e30c372a114aa47c014cdb72be8565002bc0ecbed297d9b606453c61db506f4fa2bbdc5e48f6d682496416ae15a40cbe60b2a57a6e2a2337d5792b73994cc7bd929edb5950187c21e957f02265fb3e92392e0535008c418f57c7fe1e9f93f3a55921ce8ba54417c36794d2803def9d240b8fa67f4822972503ae1ed34cca856f5cf12065e6fe3bff4aa74847ec058adc5e43e3a9938196814f87f8223f54ffa414697b8863ab5b2e191bcbda2eb19a23b8076754712de22cb202249bc5d7c4cc26073a63c9dfe7e8494ec7b744883db29f368bf06f8bfc9fc1992e909ea7774ff856d778780084a651cce68febfe07d17a5ddc1dfd20385304b970b1285879b811e4fd370cb193f0d92282a473976b38dfe9c23d988f37dee1ef957de17f6e9ce91df64fd667a85035cc369579c8fa314666f8cfd02d7dabefa7f7475860a857048e6cf38397343cfe467ce37d2717533451ca45229f7c32847f30bdfbc0c98efa92bbf292d4ba1fa90db00eec1645273036c14e0e39a61b56d1bc3249f4349d11e78dad7284cc80bcae9a88453b2625b082784af1909dcecd4bf454d19f9c1a00e93c84a13bce08af9576e026ea1cc73bc159bc8b48d5b56e4a93962117427e3e145328ad7e4100084eef96ffc7bb94388fd02329a6c067906a045696efea423fe56c0a05a0c81f4d576f2d3e4865054d2a961f5b5ee06dfa06b146c1add60e7cf8e1993867c5bee53b700360e0bba2d03df5f1aeb5d83101cd34c72fc5982b7bbb5e66b427bc5766679cca162511e568468a4a726ffa493e0a09684fcbc331b2a92ce307552e7157683fe1bc8a2a558bc0cd9a74d1e92ef569a99a9024c9a9c84ee7aeb094aea52eb9e3dad284476ba1292a61f480075afe488fee9f653e0c1329191a4e5a9f6b62e92315e6a80bb04f0c4b219ffed7458f4346c4cc0172c139a43bc61f1b732c8a5278336769e99bfbd68a69c5a2fb774ed648cdb49e1a71925cca32c15bec710b924db3d34e63a72e04729b1410d678a37e1069d77f8785b3bf3ed0c3904dd16d253e07c8da0c6a5c329e45f9cceac233a73819b33a6c9614581430a45d29a63597a3bfa671364e0cdd67eae53bbadc65de1a5db6631bfb4a93f4c2c2170297eb46294bfac883dac6615a0a64e1c93fb0bf51b67f6e284dbd1f2b17cb626b0ef4dd391d442477990f6aff2507539854ba9758b47cd01405e23651902ea3c583f8b95dd4013983f15e56d1bb3b159ac3ca02a9c81d1d61ac6095f2dc5a6f948f5d56f1e0b05c3b7c19c15c4d7e8c5305858dcf71feca83b99eaa795cdd3a7a999952afd3e020f0b1f816f94c503c638273953442805c1cb622e06f822578ca2b3328e21e0f1c660b4fd453484eba2ec30ac562aa50367f1a63c44fecd1cf05ccc146333f9b92ee10a795d4b1c86a175fb17af519dd0e3d5ef7e2950d5081dfe6ba371da92448fb3a85b808296be0dfb10e3246218537da0c45b7c9642055ab1194780bfb1793301160c7681f97a2ca5f8797e051f747c7aae3b07771cfaee9d5d685789614061f1ddf507ef4da2489c3a3f84e1b81b2a8a3486909c6cce22e9bf6ebfe1a78160ab847dfb759b1da7085e9f009fee30cdf9dea5e0ad3a6b3ebec7c1806f8d247958930b19baead59110ec00b8e6c69baeee34febf518cb88a43fec962da1a12f42092092a6742b150dc6dca765f4a441592bffaf5306c9d4da1ebee2dcb5631192a0adef623ab992b95696400b28114490d060149f3e102d1ff1799a8e94a6a2f9f83f245972e75696366381e8673f19486917c8442f3cf5c8608309cddfda680a683addf6ad22d536deead9a8329c43c25e9d16ec4abec375d481dd14e77cb526894778c45a0aa31dc882b3b52aa768a14956acf221042aaae5fc60fe5c1c2ee0d3db813d09ed24bd7bd1ac89bfeedc6781dad6f042bb7fc9ed3951e4ae2749cbc07d83db2d68ee88713e30a052e13387c0a041610023477572ef0c524e2c8a49d783fd29450c7511414ecc93941cf2845e3d68bf1a0a26101f741cd1bcaa54dd91a559a1d1363cfa5489eccdf3f08ddfe308b974d0ae703c3b7efffd8a1f5e09875835cf6274ca8623661dd9e31a8a0cd7a8b02cd4aeda5c6f56221036f9fa5010f2fe2c694682474669ec21e1e96b9da473d9418669a8f323902fb443d8be43d72c3f23662a18d0360bb44080eba704e220b14d769d8082e0c37574112a1e57326a555ee9add2936ad222b0409fc0c51905c4cb1588e59e2f469fba5f6c9efccb13fd39544064236d4393fb91844d70a1b99024f3da4cb777b00b016f1eb4c709df733d366031e2b5d6a48d0427a17a24a28ff581f01a41529643e3c0cef428672373f520abc053cd11c0df090861541a5720b75078f2ce385fd6dc14ebd1a37b666bdb737194191b6b657c20850190db5dfed4efc35ad0a1734054ac03d1caaf3eb76adf602195397a2eeeeb5c16b3101d86f8a06db6db2676fcd8268588f08b03032da4e5fd4aaf9479a1b9cbf52d312c357d12d69b501fe178e0e50f4bd78769cb8e64718bbbd660d23f1c733181c6e096ecd7ac8cd226395109980faec23cc97d27ae91516b5655c501a3d0603339f137c24f4defd3b1e4f1bc9fe5365303a89463e3d3f2db97f9cea3b061e5c402d48497ea4948d75b8af7746d4e570c848daddd94a7c47f23a35b9ff2b656a6d9e0f9c8490d3c4f7b81cebb90a9a54137ff35ff418b5ad882190850bb3c277e07cd23fddc281eceaa6fe41caa6595b38663a9436b0d3a8a2b913160aa61cc9c14ef8f83242874b750bbc3f20b209cb608618444819bc7e688c2734a20c5da9399de9902b16ce29c16dc0ae643594edb265f636d15b5a77032135a67bee6c5f70f00ccf6f483dd0f55771553d38e109578d3a337a7f93dd9b00a3353e5331338dcfcb7ca7e0bb873a4e37fe54c2d86776864e03eb8c51f657b2daa99344946db8c28d617ae43295b8dc7a6f50db614e38d6986ecb8a7b0c60be43ca21c87d541b52bb44915e89439688a271db8b6408c507823cc282f965deac455f91e7d431db06bbee9355a8d9fdda17cc9adbc9db44a172347a7bb349f80547328aa9814c2367f62729d2779f145b424837d27810dd2ad21f987d9002d64fb309b5176ced19fcd4b6fbdcdddc049289ad5a795aeeba2c09c5c7907bd7a115f0adba387e5ce54dbe65b07bc5daf40855c2aad655bf002e7feeadbd590f81dc66e640159de0b0e21afaf1b714df0d430f50d0f452c9dab99c62f20b13f39d2e7d7befb4b2c3a244513e6599b33476cd1d1f73d83a566e54c8e83f8391cb1e929d36711fa64be2faccf9b5288a21582ee9f73c8cf3ace4876b4a3c2bbbdf2b912edd6b5e5fc1998cf2f3886d81eddbecd9a0d317671365daed4e900d7976edb298398877c76dee832af3876e81e2269157e1df817cd519d6b6bde331b4d98c7d43126d74c5960a65dec35a35704662a5ca8409d21c3927022e5edf9d52512f7a9830c55c408514bd7529a8ca3862b6ae9a4ff8132e86b7e6befb2a5b3d24fe32fd5a36382283351057ee1933bf7e4b362e0b544c9a05f30bcc5a6c735b9a222cdb5a680f52d695e8ebda567aefe249c4f080c78348fe01f8c2444eeb96fdc3f09bf605e9b8c65bcc9bb407a7aed5d7e14775f7c220b80935068177cdd5930807a3f7b2cf3c9db045cf46d4c0e6a01d139bb38c10097d344b02d9912100a61af85d2419c7c6a1bbdaed5663cf7e8c44e555032f76ee93a9c11c1441edfe7c214b2f47e2dc3e1ca5135ead5fdb5edf74dcdb2b88fab443911fbef9313bb95d44898326b5138c5495788a136eed6ee1d48296f33ea744d514ee4f5209121893ab39c08b38769892b492d94f380c96dc8d5eee25fa82869159b537e5707b03657fe34e3c160c4a1d29e8b42b6640be16c6f84c87639562bf89fcdd7b0f7c621ae80f9501f5dd8b3f1d02816da091ced8a8d916e322e7c1a6cb4cabdb8e201dfb0d46fda47ef2413eb869a9de8d08ae6a5b88b6ffde80898f482b08cf946c1d50170feeeb5852566321a49546d6ac4614068d92a34069a4c228245c1dc96a48612df3d0a02b05b0f6813bac6d23867e1ed2711499efa9f88792bc108ed721877578790b89a114393fffb7551a1b9c3fcfe6bba2a894486f26ae7d8aea6399c3d52029076f6086d785c49ffa1766f86f716bf3a697d74ff0e6a5ee33f918dbe2ca4c8b2c1910637412f87b022aada2f84a88a2872f5fc562119f9773829beece9a2428e3515ab4f72cc1d4d51c98a0c0a3664fad38d5b2afedc301b34f97da6d8dd18792dde680481c4a30dbb4cefcb1c32c82807ae01ebec33ab35926b2a8a9ed7b30a3865bcf4f2ab91a7cbcf8beb2f403c368bc291277da2c35cfd0945f680049ac7c1461a9f49d371720fabdecaa7c139bcdc85895c27cc0107b15e60794106482874fa2171fbfebb12f38c69083a48391176414b087ef1ecbc7bc50a91463f43a2affc320b8d6fe6f113cbab124b47c2eae59056aae7c6fee1a8f794bd041205dcfeb6fcda72b97d92ece425875f425f8fa6ed4cb1f6bdb02db540af8b0f41072dddc4c25d406df78eeb8068c5eaba779646d29dbc304898e74d4aab59cc3ed01bc9a023027aefed3bac1ad55c7232939521caba3808fa21a4cd7028c7cf5df92d3ed3af6896d1b73f8b0e0bb98a2d4bf2d45032a3f534d2ecb25ba8438d30dae8c9d9c80e0895ede124e46c7b971cf935eaed978e999b353598579a5b8a5a0dc4e71cfd5164bf09e6e63888012d8433d4b20d49664a9fb105ec0e7f8e5b3bfa582327cfd11fbf93b76b74def8c1e58455e629c8b9acb4eee429ead84a54493908ea84a01bbe1a03b90113e1a0d8d3534fe3eda5f7147d4746de77352e1a14425db9be2e8fa450e28263a020b7e6b0392927a361bd364b42a4863e0688213f9ecd1d3ebbab86e6b899ffb42cac801e9e584df57c6a76b179839da9dd06ee0da6ed8170e7fdca5dee7631aa65dd19710b73b35e668d5a42447a91352928f5010325931f3404e328fc9da5fd5ce8375a08f1f448a0a445704be01d7a6b4cbadc3c0bae74105f65da7b76c947825c5e2c0f698d3ecb2deccde85a7ee41400c3d1aa16502a0d4bc6e4e39817f36687e2898730eacb36b8a4bdb250520cc3d394a3e5cc04abab788acabd881eab8a1988d518ee995883a301b23258135efb43a1a5779358634b54260e71bd49ba6d7fa5a13cf91571329b985dd0d85f451acf7f58c812b80b9d6d2d2298be25aa7138cd605bf6fbafb3f0bd1326678269b2d572505fb0afeef23204a9aae13f1635c364b50e5e985e9840f4749b6c293744045f1eb6f39c97ea31410620fd02683bae0ff1bc968828401ad2e4b88cb0385eba6a0666522a90f5f976ba8337fc3af35723b8ff4557775bdced05f61fa3e7638637b9d662c654a5d8fa594a48a8e8ed6d447d4ed162d63c05873aecbb90bee2f64cab9f9bfc7d6a7e60450e95750c9a4ef6b113c156cf71335605f4d703495d791d5e1a743955c4582d1018a50d007a7a9ef6f6ab8fbd9bd4bdf9b70857f1438d2e33ef72098833d4d53d20fdbd8f1e8eb1a15fe69a4b0c684378aba61b5135a103df871bf98f4992dd4e901622e2f6e9e362b808f2d2437c6b253b56c9e3946312c369d865d56610c9a6ee84c9b6842a889db79915fdf17fb961fe90e012f031ff62004deae529bd1ee8a4a01e7446af35c4b70d902866e0a2c15ddb0a79d99c0e3df2d30549051641864b1e9da6df065f6a3edcedec445dbbc5e19b8592911fe3d9ba0e218b78aad6026a46c3c88dbe8a10c78d8ba3ea3568e212590dd7e72c04a7be65ec06f4f930ef5acf2d4403463a3ae9a5f609f9063e9878a9efb42c4ed54a8860a1462a70169fc50e36b3a02bd45b0dacb968d14ea17b8cf4c7d1e0f6fa14a0850de369adcba6018a7c9214de0cf3ac18525b038d35edb7c0129b95c2d7988f597e493300d494c8ca6b69b1eb38976d6f3d5b2030607181924c9bcb20f268ad446ef22727c5ee63548fa507c1eba8aa17e6d387e6b59982f1f23ce1afdef792dc7c78a671141569af65cea6f09ac9eaa8eb7edafeb9e83455900989bdf1b7d8981347417b539c54316a60eff5ca5db7a8c7976a06c1c01e43a27c13d9df3bd6edcfe888b4e905bb9129e6635ffee6898de7c7e59d71d38932a62f20820bf626740ec6dcac58c3f83500d9a18dbcf6e8cee88abe015e753da9177d3054ec793f915c8ccdc03769a293aba52b0d68f15df2365a53f0dc27841d72f0b3b105279aec3584ccaef969334366f3506d9788418e4da157ea6f8d051645243b34fa93b0db265969b7033532baef2eb21cdd0e4bf9bb0a3a3cad08291a1a82273f115039e4bb6515770960196090c1d0a98e05c5cafaff6b86091b68af4f85185dfdcf6eaf4152607d608c6feaf8573c724f01578889fb7c44f8496c2c60c77f002463e781648bdb28acfcae9082594625748379a8e488dd081f147c380aa3dbd419d03d01a18bd60733334e27cb429e21b2fe94b297221e3dd20f415889b4bf383276c7b664dcf197b7e2c6e73f9d95057ef5312c1e41b054b58c33e48a7489ab22ea20064af9ae689f6dc1755bd4db3f3ce9e77be67223eb3e2dcc82695ddbbb5f8cde77b05c7ce86e56a648ac82c13d615f20288c3de97fd06683b8c2fb15d74cd13b91219fdda5891fedd7ae21f79ea0b763c87c0f817120c8f1de13b6d88d7f79ffd93b1e511e843c10b8dd3ceeb16fa53ac232518c7d83c78f88d82b844c6bd973b6596267b1e42da752c62e0744228b0dbc5bce5ad38de4c53f8cc3b9a296624fcf0906920e2d872a4bd59fe1956deb64fd34b08d31a3932b78ae19d91e83b6dc69c6756091d4bb11eaa7ecfe43b648d92b79b2841679653abab888732eb849b24d1e2943db5e4be6d599afcdcd7025c2cd774eefe053dd7b7c38ba1841a89d59bf7caccc494ebc21834742c0913004ba39d9e0212de76e5633ab0928f5e9144b576e7a5a7a0a8e4a8a1d60874d8a5848170e00b85d989a53878c0035b9de50ff9b601c8edc3624da40a8637240c3b2c4db703b25fddbd40bde5829bf1c1ce60ef20e0dc9ee4632494d64d622198086e90dcd09ea9d45cde7be9c00358b0925c201a4b8ac3ebf192016df33174655570d1460c776ba5aca6f8ed40e29854792d130d0c7a62bf55bf7237404b245a695a985d82e7ab38f6c1be57cbe1ba93cdfb5062e953864ad7d26f9cfc59395e8e556e89f99473758157559129a8c457d4ed607fabbe3473bef48004ee02bc5b0a89d9323826ca44978f430122dec187d82ccace1a627622544c2ed7dfefb438261d63c00846b98d7e9112117e2bb60b1fdf16b5fde8533fdf03007d97c829dd2a39949b319c46387f8649280439adcbf12fcd9e1df7d4b3a8407f5ede3424325e07f32b7bcf7c2de29671ad7dadadae984476f04338ff64f72c31e27cdbab1e09ac08d9ae886054efe8110ab232bf9e5b4599133552de3b14efa0723866c98c75535b0b916efc95eea5b1a6686eea83815bbf6fbcee792046b05474db7d8361e822752aed2a57d926f4dae96a09364353ad6f6c9fb4d6fcd697d4522dc7e386ab41dd9f8a637906e0fe123b7facabc719643172a84bffb50ccda872f6edf0e306d91bd130c26b0664eae4046eff52f71ba78de99d5cfc35307a583efe96b2604012b827a7ee34bffae1d5ac00a932bba09f0c39478e"),
+ %% SHAKE256 VariableOut
+ hexstr2bin("3e20cf32669fa3fd6e94e519b52a1dba33cd1f3a6947975e9829e4db326d2a18"),
+ hexstr2bin("6ae23f058f0f2264a18cd609acc26dd4dbc00f5c3ee9e13ecaea2bb5a2f0bb6b"),
+ hexstr2bin("e3ef127eadfafaf40408cebb28705df30b68d99dfa1893507ef3062d85461715"),
+ hexstr2bin("afc9ef4e2e46c719120b68a65aa872273d0873fc6ea353859ff6f034443005e6"),
+ hexstr2bin("8d8001e2c096f1b88e7c9224a086efd4797fbf74a8033a2d422a2b6b8f6747e4")
+ ],
+ [%% SHAKE256 ShortMsg
+ hexstr2bin("46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f"),
+ hexstr2bin("aabb07488ff9edd05d6a603b7791b60a16d45093608f1badc0c9cc9a9154f215"),
+ hexstr2bin("8e2df9d379bb034aee064e965f960ebb418a9bb535025fb96427f678cf207877"),
+ hexstr2bin("9510ff5231813a865918badd0011f05915364165492ef17b85929a63e4951589"),
+ hexstr2bin("46293a63c235750d58a24edca5ba637b96cae74325c6c8122c4155c0d15805e6"),
+ %% SHAKE256 LongMsg
+ hexstr2bin("2bac5716803a9cda8f9e84365ab0a681327b5ba34fdedfb1c12e6e807f45284b"),
+ hexstr2bin("51b560fe5c3cc4c9e457e65f15f1b1619d18dbac916ca83a67a4d022301d5229"),
+ %% SHAKE256 VariableOut
+ hexstr2bin("3389aea66244b91428f089"),
+ hexstr2bin("b9b92544fb25cfe4ec6fe437d8da2bbe00f7bdaface3de97b8775a44d753c3adca3f7c6f183cc864"),
+ hexstr2bin("7314002948c057006d4fc21e3e19c258fb5bdd57728fe93c9c6ef265b6d9f559ca73da32c427e135ba0db900d9003b19c9cf116f542a760418b1a435ac75ed5ab4ef151808c3849c3bce11c3cd285dd75e5c9fd0a0b32a89640a68e6e5b270f966f33911cfdffd03488b52b4c7fd1b2219de133e77519c426a63b9d8afac2ccab273ebd23765616b04446d6ac403f46ac0c147eda629eb7583c8bd00dc7c30fcd6711b36f99f80ac"),
+ hexstr2bin("45c65255731e3679b4662f55b02bc5d1c8038a1d778fe91144a5c7d3a286c78c54f52135134a3c6a19a9e6e546de21b2e8a7e280290709f0e482a51bffa95137a381268d10195862818309b2a4954c656d1725c7ad1a29973162832d62afd538cf74e1b70d1775a9f77dc7c7380ea034f5b1869af46c1c26bce29e1980f0de9e55543e7eda19a56453c8b7d58a28ad7a33bc243c7242ffda5409cfd8f8ffd4b350c6d0023f27f93e9eb46a871367706170074d8a2080f0a8b68b8fc6b14b8b4da256e9e64dcb7771640e992eea2334e641"),
+ hexstr2bin("2e975f6a8a14f0704d51b13667d8195c219f71e6345696c49fa4b9d08e9225d3d39393425152c97e71dd24601c11abcfa0f12f53c680bd3ae757b8134a9c10d429615869217fdd5885c4db174985703a6d6de94a667eac3023443a8337ae1bc601b76d7d38ec3c34463105f0d3949d78e562a039e4469548b609395de5a4fd43c46ca9fd6ee29ada5efc07d84d553249450dab4a49c483ded250c9338f85cd937ae66bb436f3b4026e859fda1ca571432f3bfc09e7c03ca4d183b741111ca0483d0edabc03feb23b17ee48e844ba2408d9dcfd0139d2e8c7310125aee801c61ab7900d1efc47c078281766f361c5e6111346235e1dc38325666c")
+ ],
+ [%% SHAKE256 ShortMsg
+ 256,
+ 256,
+ 256,
+ 256,
+ 256,
+ %% SHAKE256 LongMsg
+ 256,
+ 256,
+ %% SHAKE256 VariableOut
+ 88,
+ 320,
+ 1344,
+ 1672,
+ 2000
+ ]
+ }.
%%% http://www.wolfgang-ehrhardt.de/hmac-sha3-testvectors.html
@@ -2804,10 +2935,9 @@ hmac_inc(_) ->
[<<"Sampl">>, <<"e #1">>].
-cmac_key(aes_128_cbc) ->
- hexstr2bin("8eeca0d146fd09ffbbe0d47edcddfcec");
-cmac_key(aes_128_ecb) ->
- hexstr2bin("8eeca0d146fd09ffbbe0d47edcddfcec").
+cmac_key(SubType) ->
+ rand:bytes(
+ maps:get(key_length, crypto:cipher_info(SubType))).
cmac_inc(_) ->
[<<"Sampl">>, <<"e #1">>].
diff --git a/lib/dialyzer/README b/lib/dialyzer/README
index 951a92469b..bbdab21cbf 100644
--- a/lib/dialyzer/README
+++ b/lib/dialyzer/README
@@ -29,10 +29,11 @@ the Erlang/OTP installation for discrepancies.
The complete set of Dialyzer options is:
dialyzer [--help] [--version] [--shell] [--quiet] [--verbose]
- [-pa dir]* [--plt plt] [-Ddefine]* [-I include_dir]*
- [--output_plt file] [-Wwarn]* [--src]
+ [-pa dir]* [--plt plt] [-Ddefine]* [-I include_dir]*
+ [--output_plt file] [-Wwarn]* [--src]
[-c applications] [-r applications] [-o outfile]
[--build_plt] [--add_to_plt] [--remove_from_plt] [--check_plt]
+ [--incremental]
[--plt_info] [--get_warnings]
Use "dialyzer --help" to see an explanation of these options as well as
diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml
index 63a6fb8aca..334cfcb8d7 100644
--- a/lib/dialyzer/doc/src/dialyzer.xml
+++ b/lib/dialyzer/doc/src/dialyzer.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2006</year><year>2022</year>
+ <year>2006</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -86,11 +86,11 @@ dialyzer [--add_to_plt] [--apps applications] [--build_plt]
[--check_plt] [-Ddefine]* [-Dname]* [--dump_callgraph file]
[--error_location flag] [files_or_dirs] [--fullpath]
[--get_warnings] [--gui] [--help] [-I include_dir]*
- [--no_check_plt] [--no_indentation] [-o outfile]
- [--output_plt file] [-pa dir]* [--plt plt] [--plt_info]
- [--plts plt*] [--quiet] [-r dirs] [--raw] [--remove_from_plt]
- [--shell] [--src] [--statistics] [--verbose] [--version]
- [-Wwarn]*</code>
+ [--incremental] [--metrics_file] [--no_check_plt] [--no_indentation]
+ [--no_spec] [-o outfile] [--output_plt file] [-pa dir]* [--plt plt]
+ [--plt_info] [--plts plt*] [--quiet] [-r dirs] [--raw]
+ [--remove_from_plt] [--shell] [--src] [--statistics] [--verbose]
+ [--version] [--warning_apps applications] [-Wwarn]*</code>
<note>
<p>* denotes that multiple occurrences of the option are possible.</p>
@@ -111,6 +111,18 @@ dialyzer [--add_to_plt] [--apps applications] [--build_plt]
</item>
<tag><c>--apps applications</c></tag>
<item>
+ <p> By default, warnings will be reported to all applications given by
+ <c>--apps</c>. However, if <c>--warning_apps</c> is used, only those applications
+ given to <c>--warning_apps</c> will have warnings reported. All applications
+ given by <c>--apps</c>, but not <c>--warning_apps</c>, will be analysed to provide
+ context to the analysis, but warnings will not be reported for them.
+ For example, you may want to include libraries you depend on in the
+ analysis with <c>--apps</c> so discrepancies in their usage can be found,
+ but only include your own code with <c>--warning_apps</c> so that
+ discrepancies are only reported in code that you own.</p>
+ </item>
+ <tag><c>--warning_apps applications</c></tag>
+ <item>
<p>This option is typically used when building or modifying a PLT as
in:</p>
<code type="none">
@@ -195,17 +207,38 @@ dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam</code>
<p>Skip the PLT check when running Dialyzer. This is useful when
working with installed PLTs that never change.</p>
</item>
+ <tag><c>--incremental</c></tag>
+ <item>
+ <p>The analysis starts from an existing incremental PLT, or builds one from
+ scratch if one does not exist, and runs the minimal amount of additional
+ analysis to report all issues in the given set of apps. Notably, incremental
+ PLT files are not compatible with &quot;classic&quot; PLT files, and vice versa.
+ The initial incremental PLT will be updated unless an alternative output
+ incremental PLT is given.</p>
+ </item>
<tag><c>--no_indentation</c></tag>
<item>
<p>Do not insert line breaks in types, contracts, and Erlang
Code when formatting warnings.</p>
</item>
+ <tag><c>--no_spec</c></tag>
+ <item>
+ <p>Ignore functions specs. This is useful for debugging when
+ one suspects that some specs are incorrect.</p>
+ </item>
<tag><c>-o outfile</c> (or
<c>--output outfile</c>)</tag>
<item>
<p>When using Dialyzer from the command line, send the analysis
results to the specified outfile rather than to <c>stdout</c>.</p>
</item>
+ <tag><c>--metrics_file file</c></tag>
+ <item>
+ <p> Write metrics about Dialyzer's incrementality (for example, total number of
+ modules considered, how many modules were changed since the PLT was
+ last updated, how many modules needed to be analyzed) to a file. This
+ can be useful for tracking and debugging Dialyzer's incrementality.</p>
+ </item>
<tag><c>--output_plt file</c></tag>
<item>
<p>Store the PLT at the specified file after building it.</p>
@@ -377,20 +410,18 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<item>
<p>Suppress warnings for unused functions.</p>
</item>
+ <tag><c>-Wno_unknown</c></tag>
+ <item>
+ <p>Suppress warnings about unknown functions and types. The default is to
+ warn about unknown functions and types when setting the exit
+ status. When using Dialyzer from Erlang, warnings about unknown functions
+ and types are returned.</p>
+ </item>
<tag><c>-Wunderspecs</c> (***)</tag>
<item>
<p>Warn about underspecified functions (the specification is strictly
more allowing than the success typing).</p>
</item>
- <tag><c>-Wunknown</c> (***)</tag>
- <item>
- <p>Let warnings about unknown functions and types affect the
- exit status of the command-line version. The default is to ignore
- warnings about unknown functions and types when setting the exit
- status. When using Dialyzer from Erlang, warnings about unknown
- functions and types are returned; the default is not to return
- these warnings.</p>
- </item>
<tag><c>-Wunmatched_returns</c> (***)</tag>
<item>
<p>Include warnings for function calls that ignore a structured return
@@ -476,6 +507,32 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<p>Currently the only option used is the
<seeerl marker="#error_location"><c>error_location</c></seeerl> option.
</p>
+
+ <p><em>Dialyzer configuration file:</em></p>
+
+ <p>Dialyzer's configuration file may also be used to augment the default
+ options and those given directly to the Dialyzer command. It is commonly
+ used to avoid repeating options which would otherwise need to be given
+ explicitly to Dialyzer on every invocation.
+ </p>
+
+ <p>The location of the configuration file can be set via the
+ <c>DIALYZER_CONFIG</c> environment variable, and defaults to
+ within the <c>user_config</c> from <seemfa marker="stdlib:filename#basedir/3">
+ <c>filename:basedir/3</c></seemfa>.
+ </p>
+
+ <p>An example configuration file's contents might be:</p>
+
+ <code type="none">
+ {incremental,
+ {default_apps,[stdlib,kernel,erts]},
+ {default_warning_apps,[stdlib]}
+ }.
+ {warnings, [no_improper_lists]}.
+ {add_pathsa,["/users/samwise/potatoes/ebin"]}.
+ {add_pathsz,["/users/smeagol/fish/ebin"]}.
+ </code>
</section>
<section>
@@ -523,11 +580,6 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
options are also enumerated, see type <seetype marker="#warn_option">
<c>warn_option()</c></seetype>.</p>
- <note>
- <p>Warning option <c>-Wrace_conditions</c> has no effect when
- set in source files.</p>
- </note>
-
<p>Attribute <c>-dialyzer()</c> can also be used for turning on
warnings. For example, if a module has been fixed regarding
unmatched returns, adding the following line can help in assuring
@@ -567,6 +619,12 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<name name="file_location"></name>
</datatype>
<datatype>
+ <name name="filename_opt"></name>
+ </datatype>
+ <datatype>
+ <name name="format_option"></name>
+ </datatype>
+ <datatype>
<name name="warn_option"></name>
<desc>
<p>See section <seeerl
diff --git a/lib/dialyzer/doc/src/dialyzer_chapter.xml b/lib/dialyzer/doc/src/dialyzer_chapter.xml
index 8f0abe3403..b5f87e3ab7 100644
--- a/lib/dialyzer/doc/src/dialyzer_chapter.xml
+++ b/lib/dialyzer/doc/src/dialyzer_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2006</year><year>2021</year>
+ <year>2006</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -227,6 +227,139 @@ dialyzer -I my_includes -DDEBUG -Dvsn=42 -I one_more_dir</code>
</section>
<section>
+ <title>Dialyzer's Model of Analysis</title>
+ <p>Dialyzer operates somewhere between a classical type checker and a more
+ general static-analysis tool: It checks and consumes function specs,
+ yet doesn't require them, and it can find bugs across modules which consider
+ the dataflow of the programs under analysis. This means Dialyzer can find
+ genuine bugs in complex code, and is pragmatic in the face of missing
+ specs or limited information about the codebase, only reporting issues
+ which it can prove have the potential to cause a genuine issue at runtime.
+ This means Dialyzer will sometimes not report every bug, since it cannot
+ always find this proof.
+ </p>
+ <section>
+ <title>How Dialyzer Utilises Function Specifications</title>
+ <p>Dialyzer infers types for all top-level functions in a module. If the module
+ also has a spec given in the source-code, Dialyzer will compare the inferred
+ type to the spec. The comparison checks, for each argument and the return,
+ that the inferred and specified types overlap - which is to say, the types have
+ at least one possible runtime value in common. Notice that Dialyzer does not
+ check that one type contains a subset of values of the other, or that they're
+ precisely equal: This allows Dialyzer to make simplifying assumptions to preserve
+ performance and avoid reporting program flows which could potentially succeed at
+ runtime.
+ </p>
+
+ <p>If the inferred and specified types do not overlap, Dialyzer will warn that
+ the spec is invalid with respect to the implementation. If they do overlap,
+ however, Dialyzer will proceed under the assumption that the correct type for
+ the given function is the intersection of the inferred type and the specified
+ type (the rationale being that the user may know something that Dialyzer itself
+ cannot deduce). One implication of this is that if the user gives a spec for
+ a function which overlaps with Dialyzer's inferred type, but is more restrictive,
+ Dialyzer will trust those restrictions. This may then generate an error elsewhere
+ which follows from the erroneously restricted spec.
+ </p>
+
+ <p><em>Examples:</em></p>
+
+ <p>Non-overlapping argument:</p>
+
+ <code>
+-spec foo(boolean()) -> string().
+%% Dialyzer will infer: foo(integer()) -> string().
+foo(N) ->
+ integer_to_list(N).</code>
+
+ <p>Since the type of the argument in the spec is different from
+ the type that Dialyzer inferred, Dialyzer will generate the
+ following warning:</p>
+
+ <pre>
+some_module.erl:7:2: Invalid type specification for function some_module:foo/1.
+ The success typing is t:foo
+ (integer()) -> string()
+ But the spec is t:foo
+ (boolean()) -> string()
+ They do not overlap in the 1st argument</pre>
+
+ <p>Non-overlapping return:</p>
+
+ <code>
+-spec bar(a | b) -> atom().
+%% Dialyzer will infer: bar(a | b) -> binary().
+bar(a) -> &lt;&lt;"a">>;
+bar(b) -> &lt;&lt;"b">>.</code>
+
+ <p>Since the return value in the spec and the return value inferred
+ by Dialyzer are different, Dialyzer will generate the following
+ warning:</p>
+
+ <pre>
+some_module.erl:11:2: Invalid type specification for function some_module:bar/1.
+ The success typing is t:bar
+ ('a' | 'b') -> &lt;&lt;_:8>>
+ But the spec is t:bar
+ ('a' | 'b') -> atom()
+ The return types do not overlap</pre>
+
+ <p>Overlapping spec and inferred type:</p>
+
+ <code>
+-spec baz(a | b) -> non_neg_integer().
+%% Dialyzer will infer: baz(b | c | d) -> -1 | 0 | 1.
+baz(b) -> -1;
+baz(c) -> 0;
+baz(d) -> 1.</code>
+
+ <p>Dialyzer will "trust" the spec and using the intersection of
+ the spec and inferred type:</p>
+
+<pre>
+baz(b) -> 0 | 1.</pre>
+
+ <p>Notice how the <c>c</c> and <c>d</c> from the argument to <c>baz/1</c>
+ and the <c>-1</c> in the return from the inferred type were
+ dropped once the spec and inferred type were intersected.
+ This could result in warnings being emitted for later functions.</p>
+
+ <p>For example, if <c>baz/1</c> is called like this:</p>
+
+<code>
+call_baz1(A) ->
+ case baz(A) of
+ -1 -> negative;
+ 0 -> zero;
+ 1 -> positive
+ end.</code>
+
+ <p>Dialyzer will generate the following warning:</p>
+
+ <pre>
+some_module.erl:25:9: The pattern
+ -1 can never match the type
+ 0 | 1</pre>
+
+ <p>If <c>baz/1</c> is called like this:</p>
+
+ <code>
+call_baz2() ->
+ baz(a).</code>
+
+ <p>Dialyzer will generate the following warnings:</p>
+
+ <pre>
+some_module.erl:30:1: Function call_baz2/0 has no local return
+some_module.erl:31:9: The call t:baz
+ ('a') will never return since it differs in the 1st argument
+ from the success typing arguments:
+ ('b' | 'c' | 'd')</pre>
+
+ </section>
+ </section>
+
+ <section>
<title>Feedback and Bug Reports</title>
<p>We very much welcome user feedback - even wishlists!
If you notice anything weird, especially if Dialyzer reports
diff --git a/lib/dialyzer/src/Makefile b/lib/dialyzer/src/Makefile
index c934ecdc2b..2f0f1f6b71 100644
--- a/lib/dialyzer/src/Makefile
+++ b/lib/dialyzer/src/Makefile
@@ -62,7 +62,10 @@ MODULES = \
dialyzer_dot \
dialyzer_explanation \
dialyzer_gui_wx \
+ dialyzer_incremental \
dialyzer_options \
+ dialyzer_iplt \
+ dialyzer_cplt \
dialyzer_plt \
dialyzer_succ_typings \
dialyzer_timing \
@@ -118,6 +121,12 @@ $(EBIN)/dialyzer_cl_parse.$(EMULATOR): dialyzer_cl_parse.erl ../vsn.mk
$(EBIN)/dialyzer_plt.$(EMULATOR): dialyzer_plt.erl ../vsn.mk
$(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_plt.erl
+$(EBIN)/dialyzer_cplt.$(EMULATOR): dialyzer_cplt.erl ../vsn.mk
+ $(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_cplt.erl
+
+$(EBIN)/dialyzer_iplt.$(EMULATOR): dialyzer_iplt.erl ../vsn.mk
+ $(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_iplt.erl
+
$(EBIN)/dialyzer_gui_wx.$(EMULATOR): dialyzer_gui_wx.erl ../vsn.mk
$(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_gui_wx.erl
diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src
index f9c7304b9f..311c019a31 100644
--- a/lib/dialyzer/src/dialyzer.app.src
+++ b/lib/dialyzer/src/dialyzer.app.src
@@ -39,8 +39,11 @@
dialyzer_dot,
dialyzer_explanation,
dialyzer_gui_wx,
+ dialyzer_incremental,
dialyzer_options,
dialyzer_plt,
+ dialyzer_cplt,
+ dialyzer_iplt,
dialyzer_succ_typings,
dialyzer_typesig,
dialyzer_utils,
@@ -53,6 +56,6 @@
{registered, []},
{applications, [compiler, kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["wx-2.0","syntax_tools-2.0","stdlib-3.15",
+ {runtime_dependencies, ["wx-2.0","syntax_tools-2.0","stdlib-@OTP-18558@",
"kernel-8.0","erts-12.0",
"compiler-8.0"]}]}.
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl
index 83d3c03e7e..ecfc509e34 100644
--- a/lib/dialyzer/src/dialyzer.erl
+++ b/lib/dialyzer/src/dialyzer.erl
@@ -29,6 +29,8 @@
%%--------------------------------------------------------------------
-export([plain_cl/0,
run/1,
+ run_report_modules_analyzed/1,
+ run_report_modules_changed_and_analyzed/1,
gui/0,
gui/1,
plt_info/1,
@@ -41,6 +43,14 @@
%% Interfaces:
%% - plain_cl/0 : to be used ONLY by the dialyzer C program.
%% - run/1: Erlang interface for a command line-like analysis
+%% - run_report_modules_analyzed/1: Erlang interface for a command line-like
+%% analysis, but also returns the list of modules that
+%% had to be analyzed to compute the result
+%% - run_report_modules_analyzed/1: Erlang interface for a command line-like
+%% analysis, but also returns the list of modules that
+%% had to be analyzed to compute the result, plus the
+%% set of modules that have changed since the PLT was
+%% created (if applicable)
%% - gui/0/1: Erlang interface for the gui.
%% - format_warning/1: Get the string representation of a warning.
%% - format_warning/2: Likewise, but with an option whether
@@ -88,6 +98,7 @@ cl_check_init(#options{analysis_type = AnalType} = Opts) ->
plt_build -> {ok, ?RET_NOTHING_SUSPICIOUS};
plt_add -> {ok, ?RET_NOTHING_SUSPICIOUS};
plt_remove -> {ok, ?RET_NOTHING_SUSPICIOUS};
+ incremental -> {ok, ?RET_NOTHING_SUSPICIOUS};
Other when Other =:= succ_typings; Other =:= plt_check ->
F = fun() ->
NewOpts = Opts#options{analysis_type = plt_check},
@@ -109,16 +120,37 @@ print_plt_info(#options{init_plts = PLTs, output_file = OutputFile}) ->
get_plt_info([PLT|PLTs]) ->
String =
- case dialyzer_plt:included_files(PLT) of
- {ok, Files} ->
- io_lib:format("The PLT ~ts includes the following files:\n~tp\n\n",
- [PLT, Files]);
- {error, read_error} ->
- Msg = io_lib:format("Could not read the PLT file ~tp\n\n", [PLT]),
- throw({dialyzer_error, Msg});
- {error, no_such_file} ->
- Msg = io_lib:format("The PLT file ~tp does not exist\n\n", [PLT]),
- throw({dialyzer_error, Msg})
+ case dialyzer_plt:plt_kind(PLT) of
+ cplt ->
+ case dialyzer_cplt:included_files(PLT) of
+ {ok, Files} ->
+ io_lib:format("The classic PLT ~ts includes the following files:\n~tp\n\n",
+ [PLT, Files]);
+ {error, read_error} ->
+ Msg = io_lib:format("Could not read the classic PLT file ~tp\n\n", [PLT]),
+ throw({dialyzer_error, Msg});
+ {error, no_such_file} ->
+ Msg = io_lib:format("The classic PLT file ~tp does not exist\n\n", [PLT]),
+ throw({dialyzer_error, Msg})
+ end;
+ iplt ->
+ case dialyzer_iplt:included_modules(PLT) of
+ {ok, Modules} ->
+ io_lib:format("The incremental PLT ~ts includes the following modules:\n~tp\n\n",
+ [PLT, Modules]);
+ {error, read_error} ->
+ Msg = io_lib:format("Could not read the incremental PLT file ~tp\n\n", [PLT]),
+ throw({dialyzer_error, Msg});
+ {error, no_such_file} ->
+ Msg = io_lib:format("The incremental PLT file ~tp does not exist\n\n", [PLT]),
+ throw({dialyzer_error, Msg})
+ end;
+ bad_file ->
+ Msg = io_lib:format("Could not read the PLT file ~tp\n\n", [PLT]),
+ throw({dialyzer_error, Msg});
+ no_file ->
+ Msg = io_lib:format("The PLT file ~tp does not exist\n\n", [PLT]),
+ throw({dialyzer_error, Msg})
end,
String ++ get_plt_info(PLTs);
get_plt_info([]) -> "".
@@ -142,10 +174,17 @@ do_print_plt_info(PLTInfo, OutputFile) ->
end.
cl(Opts) ->
- F = fun() ->
- {Ret, _Warnings} = dialyzer_cl:start(Opts),
- Ret
- end,
+ F =
+ fun() ->
+ {Ret, _Warnings} =
+ case Opts#options.analysis_type of
+ incremental ->
+ dialyzer_incremental:start(Opts);
+ _ ->
+ dialyzer_cl:start(Opts)
+ end,
+ Ret
+ end,
doit(F).
-spec run(Options) -> Warnings when
@@ -153,15 +192,41 @@ cl(Opts) ->
Warnings :: [dial_warning()].
run(Opts) ->
+ {Warnings, _ModulesAnalyzed} = run_report_modules_analyzed(Opts),
+ Warnings.
+
+-spec run_report_modules_analyzed(Options) -> {Warnings, ModulesAnalyzed} when
+ Options :: [dial_option()],
+ Warnings :: [dial_warning()],
+ ModulesAnalyzed :: [module()].
+
+-spec run_report_modules_changed_and_analyzed(Options) -> {Warnings, ModulesChanged, ModulesAnalyzed} when
+ Options :: [dial_option()],
+ Warnings :: [dial_warning()],
+ ModulesChanged :: undefined | [module()],
+ ModulesAnalyzed :: [module()].
+
+run_report_modules_analyzed(Opts) ->
+ {Warnings, _ModulesChanged, ModulesAnalyzed} = run_report_modules_changed_and_analyzed(Opts),
+ {Warnings, ModulesAnalyzed}.
+
+run_report_modules_changed_and_analyzed(Opts) ->
try dialyzer_options:build([{report_mode, quiet},
{erlang_mode, true}|Opts]) of
{error, Msg} ->
throw({dialyzer_error, Msg});
OptsRecord ->
ok = check_init(OptsRecord),
- case dialyzer_cl:start(OptsRecord) of
- {?RET_DISCREPANCIES, Warnings} -> Warnings;
- {?RET_NOTHING_SUSPICIOUS, _} -> []
+ AnalysisResult =
+ case OptsRecord#options.analysis_type of
+ incremental ->
+ dialyzer_incremental:start_report_modules_changed_and_analyzed(OptsRecord);
+ _ ->
+ dialyzer_cl:start_report_modules_changed_and_analyzed(OptsRecord)
+ end,
+ case AnalysisResult of
+ {{?RET_DISCREPANCIES, Warnings}, ModulesChanged, ModulesAnalyzed} -> {Warnings, ModulesChanged, ModulesAnalyzed};
+ {{?RET_NOTHING_SUSPICIOUS, _}, ModulesChanged, ModulesAnalyzed} -> {[], ModulesChanged, ModulesAnalyzed}
end
catch
throw:{dialyzer_error, ErrorMsg} ->
@@ -219,15 +284,26 @@ check_gui_options(#options{analysis_type = Mode}) ->
throw({dialyzer_error, Msg}).
-spec plt_info(Plt) ->
- {'ok', Result} | {'error', Reason} when
+ {'ok', ClassicResult | IncrementalResult } | {'error', Reason} when
Plt :: file:filename(),
- Result :: [{'files', [file:filename()]}],
+ ClassicResult :: [{'files', [file:filename()]}],
+ IncrementalResult :: {incremental, [{'modules', [module()]}]},
Reason :: 'not_valid' | 'no_such_file' | 'read_error'.
plt_info(Plt) ->
- case dialyzer_plt:included_files(Plt) of
- {ok, Files} -> {ok, [{files, Files}]};
- Error -> Error
+ case dialyzer_plt:plt_kind(Plt) of
+ cplt ->
+ case dialyzer_cplt:included_files(Plt) of
+ {ok, Files} -> {ok, [{files, Files}]};
+ Error -> Error
+ end;
+ iplt ->
+ case dialyzer_iplt:included_modules(Plt) of
+ {ok, Modules} -> {ok, {incremental, [{modules, Modules}]}};
+ Error -> Error
+ end;
+ bad_file -> {error, not_valid};
+ no_file -> {error, no_such_file}
end.
@@ -339,8 +415,8 @@ message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]},
[M, F, a(Args, I), c(Culprit, I),
t(ExpectedType, I), t(FoundType, I)]);
message_to_string({bin_construction, [Culprit, Size, Seg, Type]}, I, _E) ->
- io_lib:format("Binary construction will fail since the ~s field ~s in"
- " segment ~s has type ~s\n",
+ io_lib:format("Binary construction will fail since the ~ts field ~ts in"
+ " segment ~ts has type ~ts\n",
[Culprit, c(Size, I), c(Seg, I), t(Type, I)]);
message_to_string({call, [M, F, Args, ArgNs, FailReason,
SigArgs, SigRet, Contract]}, I, _E) ->
@@ -440,9 +516,16 @@ message_to_string({contract_range, [Contract, M, F, ArgStrings,
" return for ~tw~ts on position ~s is ~ts\n",
[con(M, F, Contract, I), F, a(ArgStrings, I),
pos(Location, E), t(CRet, I)]);
-message_to_string({invalid_contract, [M, F, A, Sig]}, I, _E) ->
- io_lib:format("Invalid type specification for function ~w:~tw/~w."
- " The success typing is ~ts\n", [M, F, A, sig(Sig, I)]);
+message_to_string({invalid_contract, [M, F, A, none, Contract, Sig]}, I, _E) ->
+ io_lib:format("Invalid type specification for function ~w:~tw/~w.\n"
+ " The success typing is ~ts\n"
+ " But the spec is ~ts\n", [M, F, A, con(M, F, Sig, I), con(M, F, Contract, I)]);
+message_to_string({invalid_contract, [M, F, A, InvalidContractDetails, Contract, Sig]}, I, _E) ->
+ io_lib:format("Invalid type specification for function ~w:~tw/~w.\n"
+ " The success typing is ~ts\n"
+ " But the spec is ~ts\n"
+ "~ts",
+ [M, F, A, con(M, F, Sig, I), con(M, F, Contract, I), format_invalid_contract_details(InvalidContractDetails)]);
message_to_string({contract_with_opaque, [M, F, A, OpaqueType, SigType]},
I, _E) ->
io_lib:format("The specification for ~w:~tw/~w"
@@ -459,7 +542,7 @@ message_to_string({missing_range, [M, F, A, ExtraRanges, ContrRange]}, I, _E) ->
[M, F, A, t(ExtraRanges, I), t(ContrRange, I)]);
message_to_string({overlapping_contract, [M, F, A]}, _I, _E) ->
io_lib:format("Overloaded contract for ~w:~tw/~w has overlapping domains;"
- " such contracts are currently unsupported and are simply ignored\n",
+ " such contracts cannot establish a dependency between the overloaded input and output types\n",
[M, F, A]);
message_to_string({spec_missing_fun, [M, F, A]}, _I, _E) ->
io_lib:format("Contract for function that does not exist: ~w:~tw/~w\n",
@@ -510,19 +593,19 @@ message_to_string({callback_type_mismatch, [B, F, A, ST, CT]}, I, _E) ->
" the callback of the ~w behaviour\n",
[F, A, t("("++ST++")", I), t(CT, I), B]);
message_to_string({callback_arg_type_mismatch, [B, F, A, N, ST, CT]}, I, _E) ->
- io_lib:format("The inferred type for the ~s argument of ~tw/~w (~ts) is"
- " not a supertype of ~ts, which is expected type for this"
+ io_lib:format("The inferred type for the ~s argument of ~tw/~w (~ts)"
+ " has nothing in common with ~ts, which is expected type for this"
" argument in the callback of the ~w behaviour\n",
[ordinal(N), F, A, t(ST, I), t(CT, I), B]);
message_to_string({callback_spec_type_mismatch, [B, F, A, ST, CT]}, I, _E) ->
- io_lib:format("The return type ~ts in the specification of ~tw/~w is not a"
- " subtype of ~ts, which is the expected return type for the"
+ io_lib:format("The return type ~ts in the specification of ~tw/~w has nothing"
+ " in common with ~ts, which is the expected return type for the"
" callback of the ~w behaviour\n",
[t(ST, I), F, A, t(CT, I), B]);
message_to_string({callback_spec_arg_type_mismatch, [B, F, A, N, ST, CT]},
I, _E) ->
- io_lib:format("The specified type for the ~ts argument of ~tw/~w (~ts) is"
- " not a supertype of ~ts, which is expected type for this"
+ io_lib:format("The specified type for the ~ts argument of ~tw/~w (~ts) has"
+ " nothing in common with ~ts, which is expected type for this"
" argument in the callback of the ~w behaviour\n",
[ordinal(N), F, A, t(ST, I), t(CT, I), B]);
message_to_string({callback_missing, [B, F, A]}, _I, _E) ->
@@ -545,6 +628,27 @@ message_to_string({unknown_behaviour, B}, _I, _E) ->
%% Auxiliary functions below
%%-----------------------------------------------------------------------------
+format_invalid_contract_details({InvalidArgIdxs, IsRangeInvalid}) ->
+ ArgOrd = form_position_string(InvalidArgIdxs),
+ ArgDesc =
+ case InvalidArgIdxs of
+ [] -> "";
+ [_] -> io_lib:format("They do not overlap in the ~ts argument", [ArgOrd]);
+ [_|_] -> io_lib:format("They do not overlap in the ~ts arguments", [ArgOrd])
+ end,
+ RangeDesc =
+ case IsRangeInvalid of
+ true -> "return types do not overlap";
+ false -> ""
+ end,
+ case {ArgDesc, RangeDesc} of
+ {"", ""} -> "";
+ {"", [_|_]} -> io_lib:format(" The ~ts\n", [RangeDesc]);
+ {[_|_], ""} -> io_lib:format(" ~ts\n", [ArgDesc]);
+ {[_|_], [_|_]} -> io_lib:format(" ~ts, and the ~ts\n", [ArgDesc, RangeDesc])
+ end.
+
+
call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet,
{IsOverloaded, Contract}, I) ->
PositionString = form_position_string(ArgNs),
@@ -623,10 +727,18 @@ form_position_string(ArgNs) ->
Head ++ " and " ++ ordinal(Last)
end.
-ordinal(1) -> "1st";
-ordinal(2) -> "2nd";
-ordinal(3) -> "3rd";
-ordinal(N) when is_integer(N) -> io_lib:format("~wth", [N]).
+ordinal(N) when is_integer(N),
+ ((N rem 100) =:= 11) orelse
+ ((N rem 100) =:= 12) orelse
+ ((N rem 100) =:= 13) ->
+ io_lib:format("~Bth", [N]);
+ordinal(N) when is_integer(N) ->
+ case min(N rem 10, 4) of
+ 1 -> io_lib:format("~Bst", [N]);
+ 2 -> io_lib:format("~Bnd", [N]);
+ 3 -> io_lib:format("~Brd", [N]);
+ _ -> io_lib:format("~Bth", [N])
+ end.
%% Functions that parse type strings, literal strings, and contract
%% strings. Return strings formatted by erl_pp.
diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl
index 8f96de884e..26a4b0b0a5 100644
--- a/lib/dialyzer/src/dialyzer.hrl
+++ b/lib/dialyzer/src/dialyzer.hrl
@@ -33,29 +33,30 @@
%% Warning classification
%%--------------------------------------------------------------------
--define(WARN_RETURN_NO_RETURN, warn_return_no_exit).
--define(WARN_RETURN_ONLY_EXIT, warn_return_only_exit).
--define(WARN_NOT_CALLED, warn_not_called).
--define(WARN_NON_PROPER_LIST, warn_non_proper_list).
--define(WARN_FUN_APP, warn_fun_app).
--define(WARN_MATCHING, warn_matching).
--define(WARN_OPAQUE, warn_opaque).
--define(WARN_FAILING_CALL, warn_failing_call).
+-define(WARN_BEHAVIOUR, warn_behaviour).
-define(WARN_BIN_CONSTRUCTION, warn_bin_construction).
--define(WARN_CONTRACT_TYPES, warn_contract_types).
--define(WARN_CONTRACT_SYNTAX, warn_contract_syntax).
+-define(WARN_CALLGRAPH, warn_callgraph).
+-define(WARN_CONTRACT_EXTRA_RETURN, warn_contract_extra_return).
+-define(WARN_CONTRACT_MISSING_RETURN, warn_contract_missing_return).
-define(WARN_CONTRACT_NOT_EQUAL, warn_contract_not_equal).
+-define(WARN_CONTRACT_RANGE, warn_contract_range).
-define(WARN_CONTRACT_SUBTYPE, warn_contract_subtype).
--define(WARN_CONTRACT_MISSING_RETURN, warn_contract_missing_return).
-define(WARN_CONTRACT_SUPERTYPE, warn_contract_supertype).
--define(WARN_CONTRACT_EXTRA_RETURN, warn_contract_extra_return).
--define(WARN_CONTRACT_RANGE, warn_contract_range).
--define(WARN_CALLGRAPH, warn_callgraph).
--define(WARN_UNMATCHED_RETURN, warn_umatched_return).
--define(WARN_BEHAVIOUR, warn_behaviour).
+-define(WARN_CONTRACT_SYNTAX, warn_contract_syntax).
+-define(WARN_CONTRACT_TYPES, warn_contract_types).
+-define(WARN_FAILING_CALL, warn_failing_call).
+-define(WARN_FUN_APP, warn_fun_app).
+-define(WARN_MAP_CONSTRUCTION, warn_map_construction).
+-define(WARN_MATCHING, warn_matching).
+-define(WARN_NON_PROPER_LIST, warn_non_proper_list).
+-define(WARN_NOT_CALLED, warn_not_called).
+-define(WARN_OPAQUE, warn_opaque).
+-define(WARN_OVERLAPPING_CONTRACT, warn_overlapping_contract).
+-define(WARN_RETURN_NO_RETURN, warn_return_no_exit).
+-define(WARN_RETURN_ONLY_EXIT, warn_return_only_exit).
-define(WARN_UNDEFINED_CALLBACK, warn_undefined_callbacks).
-define(WARN_UNKNOWN, warn_unknown).
--define(WARN_MAP_CONSTRUCTION, warn_map_construction).
+-define(WARN_UNMATCHED_RETURN, warn_umatched_return).
%%
%% The following type has double role:
@@ -63,14 +64,15 @@
%% 2. It is also the set of tags for warnings that will be returned.
%%
-type dial_warn_tag() :: ?WARN_BEHAVIOUR | ?WARN_BIN_CONSTRUCTION
- | ?WARN_CALLGRAPH | ?WARN_CONTRACT_NOT_EQUAL
+ | ?WARN_CALLGRAPH | ?WARN_CONTRACT_EXTRA_RETURN
+ | ?WARN_CONTRACT_MISSING_RETURN | ?WARN_CONTRACT_NOT_EQUAL
| ?WARN_CONTRACT_RANGE | ?WARN_CONTRACT_SUBTYPE
| ?WARN_CONTRACT_SUPERTYPE | ?WARN_CONTRACT_SYNTAX
| ?WARN_CONTRACT_TYPES | ?WARN_FAILING_CALL
| ?WARN_FUN_APP | ?WARN_MAP_CONSTRUCTION
| ?WARN_MATCHING | ?WARN_NON_PROPER_LIST
| ?WARN_NOT_CALLED | ?WARN_OPAQUE
- | ?WARN_RETURN_NO_RETURN
+ | ?WARN_OVERLAPPING_CONTRACT | ?WARN_RETURN_NO_RETURN
| ?WARN_RETURN_ONLY_EXIT | ?WARN_UNDEFINED_CALLBACK
| ?WARN_UNKNOWN | ?WARN_UNMATCHED_RETURN.
@@ -108,7 +110,7 @@
%%--------------------------------------------------------------------
-type anal_type() :: 'succ_typings' | 'plt_build'.
--type anal_type1() :: anal_type() | 'plt_add' | 'plt_check' | 'plt_remove'.
+-type anal_type1() :: anal_type() | 'plt_add' | 'plt_check' | 'plt_remove' | 'incremental'.
-type contr_constr() :: {'subtype', erl_types:erl_type(), erl_types:erl_type()}.
-type contract_pair() :: {erl_types:erl_type(), [contr_constr()]}.
-type dial_define() :: {atom(), term()}.
@@ -124,6 +126,7 @@
| 'no_return'
| 'no_undefined_callbacks'
| 'no_underspecs'
+ | 'no_unknown'
| 'no_unused'
| 'underspecs'
| 'unknown'
@@ -142,15 +145,23 @@
| {'plts', [FileName :: file:filename()]}
| {'include_dirs', [DirName :: file:filename()]}
| {'output_file', FileName :: file:filename()}
+ | {'metrics_file', FileName :: file:filename()}
+ | {'module_lookup_file', FileName :: file:filename()}
| {'output_plt', FileName :: file:filename()}
| {'check_plt', boolean()}
| {'analysis_type', 'succ_typings' |
'plt_add' |
'plt_build' |
'plt_check' |
- 'plt_remove'}
+ 'plt_remove' |
+ 'incremental'}
| {'warnings', [warn_option()]}
| {'get_warnings', boolean()}
+ | {'use_spec', boolean()}
+ | {'filename_opt', filename_opt()}
+ | {'callgraph_file', file:filename()}
+ | {'mod_deps_file', file:filename()}
+ | {'warning_files_rec', [DirName :: file:filename()]}
| {'error_location', error_location()}.
-type dial_options() :: [dial_option()].
-type filename_opt() :: 'basename' | 'fullpath'.
@@ -172,7 +183,21 @@
-define(ERROR_LOCATION, column).
-type doc_plt() :: 'undefined' | dialyzer_plt:plt().
--record(plt_info, {files :: [dialyzer_plt:file_md5()], mod_deps :: dict:dict()}).
+-record(plt_info, {files :: [dialyzer_cplt:file_md5()],
+ mod_deps = dict:new() :: dialyzer_callgraph:mod_deps()}).
+-record(iplt_info, {files :: [dialyzer_iplt:module_md5()],
+ mod_deps = dict:new() :: dialyzer_callgraph:mod_deps(),
+ warning_map = none :: 'none' | dialyzer_iplt:warning_map(),
+ legal_warnings = none :: none | dial_warn_tags()}).
+
+-record(plt, {info :: ets:tid(), %% {mfa() | integer(), ret_args_types()}
+ types :: ets:tid(), %% {module(), erl_types:type_table()}
+ contracts :: ets:tid(), %% {mfa(), #contract{}}
+ callbacks :: ets:tid(), %% {module(),
+ %% [{mfa(),
+ %% dialyzer_contracts:file_contract()}]
+ exported_types :: ets:tid() %% {module(), sets:set()}
+ }).
-record(analysis, {analysis_pid :: pid() | 'undefined',
type = succ_typings :: anal_type(),
@@ -187,15 +212,18 @@
timing = false :: boolean() | 'debug',
timing_server = none :: dialyzer_timing:timing_server(),
callgraph_file = "" :: file:filename(),
+ mod_deps_file = "" :: file:filename(),
solvers :: [solver()]}).
-record(options, {files = [] :: [file:filename()],
files_rec = [] :: [file:filename()],
+ warning_files = [] :: [file:filename()],
+ warning_files_rec = [] :: [file:filename()],
analysis_type = succ_typings :: anal_type1(),
timing = false :: boolean() | 'debug',
defines = [] :: [dial_define()],
from = byte_code :: start_from(),
- get_warnings = maybe :: boolean() | 'maybe',
+ get_warnings = 'maybe' :: boolean() | 'maybe',
init_plts = [] :: [file:filename()],
include_dirs = [] :: [file:filename()],
output_plt = none :: 'none' | file:filename(),
@@ -208,14 +236,18 @@
filename_opt = basename :: filename_opt(),
indent_opt = ?INDENT_OPT :: iopt(),
callgraph_file = "" :: file:filename(),
+ mod_deps_file = "" :: file:filename(),
check_plt = true :: boolean(),
error_location = ?ERROR_LOCATION :: error_location(),
+ metrics_file = none :: none | file:filename(),
+ module_lookup_file = none :: none | file:filename(),
solvers = [] :: [solver()]}).
-record(contract, {contracts = [] :: [contract_pair()],
args = [] :: [erl_types:erl_type()],
forms = [] :: [{_, _}]}).
+
%%--------------------------------------------------------------------
-define(timing(Server, Msg, Var, Expr),
diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
index ee795a54dc..b5cc6c7392 100644
--- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
+++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
@@ -117,25 +117,16 @@ loop(#server_state{parent = Parent} = State,
analysis_start(Parent, Analysis, LegalWarnings) ->
CServer = dialyzer_codeserver:new(),
Plt = Analysis#analysis.plt,
- State = #analysis_state{codeserver = CServer,
- analysis_type = Analysis#analysis.type,
- defines = Analysis#analysis.defines,
- doc_plt = Analysis#analysis.doc_plt,
- include_dirs = Analysis#analysis.include_dirs,
- plt = Plt,
- parent = Parent,
- legal_warnings = LegalWarnings,
- start_from = Analysis#analysis.start_from,
- use_contracts = Analysis#analysis.use_contracts,
- timing_server = Analysis#analysis.timing_server,
- solvers = Analysis#analysis.solvers
- },
+ Args = {Plt, Analysis, Parent},
+ State = create_analysis_state(Args, LegalWarnings, CServer),
+
Files = ordsets:from_list(Analysis#analysis.files),
{Callgraph, ModCallDeps, Modules, TmpCServer0} = compile_and_store(Files, State),
- %% Remote type postprocessing
- Args = {Plt, Analysis, Parent},
+
+ %% Remote type postprocessing
NewCServer = remote_type_postprocessing(TmpCServer0, Args),
dump_callgraph(Callgraph, State, Analysis),
+
%% Remove all old versions of the files being analyzed
AllNodes = dialyzer_callgraph:all_nodes(Callgraph),
Plt1_a = dialyzer_plt:delete_list(Plt, AllNodes),
@@ -147,6 +138,7 @@ analysis_start(Parent, Analysis, LegalWarnings) ->
State2 = analyze_callgraph(Callgraph, State1),
ModTypeDeps = dict:from_list(maps:to_list(dialyzer_typegraph:module_type_deps(Analysis#analysis.use_contracts, CServer, Modules))),
ModDeps = dialyzer_callgraph:merge_module_deps(ModCallDeps, ModTypeDeps),
+ dump_mod_deps(ModDeps, State, Analysis),
send_mod_deps(Parent, ModDeps),
#analysis_state{plt = Plt2,
doc_plt = DocPlt,
@@ -159,6 +151,21 @@ analysis_start(Parent, Analysis, LegalWarnings) ->
Plt4 = dialyzer_plt:delete_list(Plt3, NonExportsList),
send_analysis_done(Parent, Plt4, DocPlt).
+create_analysis_state({Plt, Analysis, Parent}, LegalWarnings, CServer) ->
+ #analysis_state{ codeserver = CServer,
+ analysis_type = Analysis#analysis.type,
+ defines = Analysis#analysis.defines,
+ doc_plt = Analysis#analysis.doc_plt,
+ include_dirs = Analysis#analysis.include_dirs,
+ plt = Plt,
+ parent = Parent,
+ legal_warnings = LegalWarnings,
+ start_from = Analysis#analysis.start_from,
+ use_contracts = Analysis#analysis.use_contracts,
+ timing_server = Analysis#analysis.timing_server,
+ solvers = Analysis#analysis.solvers }.
+
+
remote_type_postprocessing(TmpCServer, Args) ->
Fun = fun() ->
exit(try remote_type_postproc(TmpCServer, Args) of
@@ -372,7 +379,7 @@ cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent,
CServer, Callgraph, Modules) ->
ModCallDeps = dialyzer_callgraph:module_call_deps(Callgraph),
{Callgraph1, ExtCalls} = dialyzer_callgraph:remove_external(Callgraph),
- ExtCalls1 = [Call || Call = {_From, To} <- ExtCalls,
+ ExtCalls1 = [Call || Call = {_From1, To} <- ExtCalls,
not dialyzer_plt:contains_mfa(InitPlt, To)],
{BadCalls1, RealExtCalls} =
if ExtCalls1 =:= [] -> {[], []};
@@ -380,11 +387,11 @@ cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent,
ModuleSet = sets:from_list(Modules, [{version, 2}]),
PltModuleSet = dialyzer_plt:all_modules(InitPlt),
AllModules = sets:union(ModuleSet, PltModuleSet),
- Pred = fun({_From, {M, _F, _A}}) -> sets:is_element(M, AllModules) end,
+ Pred = fun({_From2, {M, _F, _A}}) -> sets:is_element(M, AllModules) end,
lists:partition(Pred, ExtCalls1)
end,
NonLocalCalls = dialyzer_callgraph:non_local_calls(Callgraph1),
- BadCalls2 = [Call || Call = {_From, To} <- NonLocalCalls,
+ BadCalls2 = [Call || Call = {_From3, To} <- NonLocalCalls,
not dialyzer_codeserver:is_exported(To, CServer)],
case BadCalls1 ++ BadCalls2 of
[] -> ok;
@@ -497,10 +504,11 @@ expand_files(Analysis = #analysis{files = Files, start_from = StartFrom}) ->
case expand_files(Files, Ext, []) of
[] ->
Msg = "No " ++ Ext ++ " files to analyze" ++
- case StartFrom of
- byte_code -> " (no --src specified?)";
- src_code -> ""
- end,
+ case StartFrom of
+ byte_code -> " (no --src specified?)";
+ src_code -> ""
+ end ++
+ "\nConsider setting some default apps in your dialyzer.config file",
exit({error, Msg});
NewFiles ->
Analysis#analysis{files = NewFiles}
@@ -583,7 +591,7 @@ is_ok_fun({_Filename, _Loc, {_M, _F, _A} = MFA}, Codeserver) ->
is_ok_tag(Tag, {_F, _L, MorMFA}, Codeserver) ->
not dialyzer_utils:is_suppressed_tag(MorMFA, Tag, Codeserver).
-
+
send_analysis_done(Parent, Plt, DocPlt) ->
Parent ! {self(), done, Plt, DocPlt},
ok.
@@ -617,8 +625,7 @@ format_bad_calls([{{_, _, _}, {_, module_info, A}}|Left], CodeServer, Acc)
format_bad_calls(Left, CodeServer, Acc);
format_bad_calls([{FromMFA, {M, F, A} = To}|Left], CodeServer, Acc) ->
Msg = {call_to_missing, [M, F, A]},
- {File, Loc} = find_call_file_and_location(FromMFA, To, CodeServer),
- WarningInfo = {File, Loc, FromMFA},
+ WarningInfo = find_call_file_and_location(FromMFA, To, CodeServer),
NewAcc = [{?WARN_CALLGRAPH, WarningInfo, Msg}|Acc],
format_bad_calls(Left, CodeServer, NewAcc);
format_bad_calls([], _CodeServer, Acc) ->
@@ -640,7 +647,7 @@ find_call_file_and_location({Module, _, _} = FromMFA, ToMFA, CodeServer) ->
Ann = cerl:get_ann(SubTree),
File = get_file(CodeServer, Module, Ann),
Location = get_location(SubTree),
- [{File, Location}|Acc];
+ [{File, Location, FromMFA}|Acc];
{erlang, make_fun, 3} ->
[CA1, CA2, CA3] = cerl:call_args(SubTree),
case
@@ -657,7 +664,7 @@ find_call_file_and_location({Module, _, _} = FromMFA, ToMFA, CodeServer) ->
ToMFA ->
Ann = cerl:get_ann(SubTree),
[{get_file(CodeServer, Module, Ann),
- get_location(SubTree)}|Acc];
+ get_location(SubTree), FromMFA}|Acc];
_ ->
Acc
end;
@@ -712,3 +719,26 @@ dump_callgraph(CallGraph, State, #analysis{callgraph_file = File}, _Ext) ->
[File, Reason]),
send_log(State#analysis_state.parent, Msg)
end.
+
+
+dump_mod_deps(_ModDeps, _State, #analysis{mod_deps_file = ""}) -> ok;
+dump_mod_deps(ModDeps, State, #analysis{mod_deps_file = File} = Analysis) ->
+ Extension = filename:extension(File),
+ Start_Msg = io_lib:format("Dumping the full module dependencies graph... ", []),
+ send_log(State#analysis_state.parent, Start_Msg),
+ {T1, _} = statistics(wall_clock),
+ dump_mod_deps(ModDeps, State, Analysis, Extension),
+ {T2, _} = statistics(wall_clock),
+ Finish_Msg = io_lib:format("done in ~2f secs\n", [(T2-T1)/1000]),
+ send_log(State#analysis_state.parent, Finish_Msg),
+ ok.
+
+dump_mod_deps(ModDeps, _State, #analysis{mod_deps_file = File}, ".dot") ->
+ dialyzer_callgraph:mod_deps_to_dot(ModDeps, File);
+dump_mod_deps(ModDeps, _State, #analysis{mod_deps_file = File}, ".ps") ->
+ Args = "-Gratio=compress -Gsize=\"100,100\"",
+ dialyzer_callgraph:mod_deps_to_ps(ModDeps, File, Args);
+dump_mod_deps(_ModDeps, State, #analysis{mod_deps_file = File}, Ext) ->
+ Msg = io_lib:format("Could not write full modules dependencies file ~tp, Reason: Unrecognised file extension '~ts'. Only .dot and .ps are supported\n",
+ [File, Ext]),
+ send_log(State#analysis_state.parent, Msg).
diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl
index d5c8ac0886..d3fbbcb2e1 100644
--- a/lib/dialyzer/src/dialyzer_behaviours.erl
+++ b/lib/dialyzer/src/dialyzer_behaviours.erl
@@ -126,20 +126,18 @@ check_callback(RetArgTypes, CbMFA, Behaviour, Callback,
CbReturnType = dialyzer_contracts:get_contract_return(Callback),
CbArgTypes = dialyzer_contracts:get_contract_args(Callback),
{ReturnType, ArgTypes} = RetArgTypes,
- Acc1 = case erl_types:t_is_subtype(ReturnType, CbReturnType) of
- true ->
- Acc0;
- false ->
- case erl_types:t_is_none(erl_types:t_inf(ReturnType, CbReturnType)) of
- false ->
- Acc0;
- true ->
- [{callback_type_mismatch,
- [Behaviour, Function, Arity,
- erl_types:t_to_string(ReturnType, Records),
- erl_types:t_to_string(CbReturnType, Records)]}|Acc0]
- end
- end,
+ Acc1 =
+ % Allow none() as the return type to be backwards compatible
+ % with logic that allows crashes in callbacks
+ case (not erl_types:t_is_none(ReturnType)) andalso erl_types:t_is_none(erl_types:t_inf(ReturnType, CbReturnType)) of
+ false ->
+ Acc0;
+ true ->
+ [{callback_type_mismatch,
+ [Behaviour, Function, Arity,
+ erl_types:t_to_string(ReturnType, Records),
+ erl_types:t_to_string(CbReturnType, Records)]}|Acc0]
+ end,
Acc2 = case erl_types:any_none(erl_types:t_inf_lists(ArgTypes, CbArgTypes)) of
false -> Acc1;
true ->
@@ -156,10 +154,12 @@ check_callback(RetArgTypes, CbMFA, Behaviour, Callback,
SpecArgTypes =
[erl_types:subst_all_vars_to_any(ArgT0) || ArgT0 <- SpecArgTypes0],
Acc3 =
- case erl_types:t_is_subtype(SpecReturnType, CbReturnType) of
- true ->
- Acc2;
+ % Allow none() as the return type to be backwards compatible
+ % with logic that allows crashes in callbacks
+ case (not erl_types:t_is_none(SpecReturnType)) andalso erl_types:t_is_none(erl_types:t_inf(SpecReturnType, CbReturnType)) of
false ->
+ Acc2;
+ true ->
ExtraType = erl_types:t_subtract(SpecReturnType, CbReturnType),
[{callback_spec_type_mismatch,
[File, Location, Behaviour, Function, Arity,
diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl
index aec4bd7169..792c4d6911 100644
--- a/lib/dialyzer/src/dialyzer_callgraph.erl
+++ b/lib/dialyzer/src/dialyzer_callgraph.erl
@@ -37,16 +37,16 @@
modules/1,
module_call_deps/1,
merge_module_deps/2,
- %% module_postorder/1,
module_postorder_from_funs/2,
new/0,
get_depends_on/2,
- %% get_required_by/2,
in_neighbours/2,
reset_from_funs/2,
scan_core_tree/2,
strip_module_deps/2,
remove_external/1,
+ mod_deps_to_dot/2,
+ mod_deps_to_ps/3,
to_dot/2,
to_ps/3]).
@@ -59,7 +59,6 @@
-type scc() :: [mfa_or_funlbl()].
-type mfa_call() :: {mfa_or_funlbl(), mfa_or_funlbl()}.
-type mfa_calls() :: [mfa_call()].
--type mod_deps() :: dict:dict(module(), [module()]).
%%-----------------------------------------------------------------------------
%% A callgraph is a directed graph where the nodes are functions and a
@@ -91,15 +90,16 @@
self_rec :: ets:tid(),
calls :: ets:tid()}).
+
%% Exported Types
-opaque callgraph() :: #callgraph{}.
-type active_digraph() :: {'d', digraph:graph()}
| {'e',
- Out :: ets:tid(),
- In :: ets:tid(),
- Map :: ets:tid()}.
+ Out :: ets:tid()}.
+
+-type mod_deps() :: dict:dict(module(), [module()]).
%%----------------------------------------------------------------------
@@ -229,8 +229,11 @@ find_non_local_calls([], Set) ->
%% Only considers call dependencies, not type dependencies, which are dealt with elsewhere
-spec get_depends_on(scc() | module(), callgraph()) -> [scc()].
-get_depends_on(SCC, #callgraph{active_digraph = {'e', Out, _In, Maps}}) ->
- lookup_scc(SCC, Out, Maps);
+get_depends_on(SCC, #callgraph{active_digraph = {'e', Out}}) ->
+ case ets_lookup_dict(SCC, Out) of
+ error -> [];
+ {ok, Val} -> Val
+ end;
get_depends_on(SCC, #callgraph{active_digraph = {'d', DG}}) ->
digraph:out_neighbours(DG, SCC).
@@ -241,17 +244,6 @@ get_depends_on(SCC, #callgraph{active_digraph = {'d', DG}}) ->
%% get_required_by(SCC, #callgraph{active_digraph = {'d', DG}}) ->
%% digraph:in_neighbours(DG, SCC).
-lookup_scc(SCC, Table, Maps) ->
- case ets_lookup_dict({'scc', SCC}, Maps) of
- {ok, SCCInt} ->
- case ets_lookup_dict(SCCInt, Table) of
- {ok, Ints} ->
- [ets:lookup_element(Maps, Int, 2) || Int <- Ints];
- error ->
- []
- end;
- error -> []
- end.
%%----------------------------------------------------------------------
%% Handling of modules & SCCs
@@ -322,17 +314,17 @@ strip_module_deps(ModDeps, StripSet) ->
-spec finalize(callgraph()) -> {[scc()], callgraph()}.
finalize(#callgraph{digraph = DG} = CG) ->
- {ActiveDG, Postorder} = condensation(DG),
- {Postorder, CG#callgraph{active_digraph = ActiveDG}}.
+ {ActiveDG, LabelledPostorder} = condensation(DG),
+ {LabelledPostorder, CG#callgraph{active_digraph = ActiveDG}}.
-spec reset_from_funs([mfa_or_funlbl()], callgraph()) -> {[scc()], callgraph()}.
reset_from_funs(Funs, #callgraph{digraph = DG, active_digraph = ADG} = CG) ->
active_digraph_delete(ADG),
SubGraph = digraph_reaching_subgraph(Funs, DG),
- {NewActiveDG, Postorder} = condensation(SubGraph),
+ {NewActiveDG, LabelledPostorder} = condensation(SubGraph),
digraph_delete(SubGraph),
- {Postorder, CG#callgraph{active_digraph = NewActiveDG}}.
+ {LabelledPostorder, CG#callgraph{active_digraph = NewActiveDG}}.
-spec module_postorder_from_funs([mfa_or_funlbl()], callgraph()) ->
{[module()], callgraph()}.
@@ -345,15 +337,15 @@ module_postorder_from_funs(Funs, #callgraph{digraph = DG,
digraph_delete(SubGraph),
{PO, CG#callgraph{active_digraph = Active}}.
+%% We KNOW that `error` is not a valid value in the table.
ets_lookup_dict(Key, Table) ->
- try ets:lookup_element(Table, Key, 2) of
- Val -> {ok, Val}
- catch
- _:_ -> error
+ case ets:lookup_element(Table, Key, 2, error) of
+ error -> error;
+ Val -> {ok, Val}
end.
ets_lookup_set(Key, Table) ->
- ets:lookup(Table, Key) =/= [].
+ ets:member(Table, Key).
%%----------------------------------------------------------------------
%% Core code
@@ -595,10 +587,8 @@ digraph_delete(DG) ->
active_digraph_delete({'d', DG}) ->
digraph:delete(DG);
-active_digraph_delete({'e', Out, In, Maps}) ->
- ets:delete(Out),
- ets:delete(In),
- ets:delete(Maps).
+active_digraph_delete({'e', Out}) ->
+ ets:delete(Out).
digraph_edges(DG) ->
digraph:edges(DG).
@@ -620,6 +610,27 @@ digraph_reaching_subgraph(Funs, DG) ->
%% Utilities for 'dot'
%%=============================================================================
+-spec mod_deps_to_dot(mod_deps(), file:filename()) -> 'ok'.
+
+mod_deps_to_dot(ModDeps, File) ->
+ DepEdges =
+ lists:flatten(
+ [
+ [{Mod, ModuleDependingOnIt} || ModuleDependingOnIt <- ModulesDependingOnIt]
+ || {Mod,ModulesDependingOnIt} <- dict:to_list(ModDeps)
+ ]),
+ dialyzer_dot:translate_list(DepEdges, File, "mod_deps").
+
+-spec mod_deps_to_ps(mod_deps(), file:filename(), string()) -> 'ok'.
+
+mod_deps_to_ps(ModDeps, File, Args) ->
+ %% TODO: As with `to_dot/2`, handle Unicode names.
+ DotFile = filename:rootname(File) ++ ".dot",
+ mod_deps_to_dot(ModDeps, DotFile),
+ Command = io_lib:format("dot -Tps ~ts -o ~ts ~ts", [Args, File, DotFile]),
+ _ = os:cmd(Command),
+ ok.
+
-spec to_dot(callgraph(), file:filename()) -> 'ok'.
to_dot(#callgraph{digraph = DG, esc = Esc} = CG, File) ->
@@ -646,52 +657,32 @@ to_ps(#callgraph{} = CG, File, Args) ->
ok.
condensation(G) ->
- {Pid, Ref} = erlang:spawn_monitor(do_condensation(G, self())),
- receive {'DOWN', Ref, process, Pid, Result} ->
- {SCCInts, OutETS, InETS, MapsETS} = Result,
- NewSCCs = [ets:lookup_element(MapsETS, SCCInt, 2) || SCCInt <- SCCInts],
- {{'e', OutETS, InETS, MapsETS}, NewSCCs}
- end.
-
--spec do_condensation(digraph:graph(), pid()) -> fun(() -> no_return()).
-
-do_condensation(G, Parent) ->
- fun() ->
- [OutETS, InETS, MapsETS] =
- [ets:new(Name,[{read_concurrency, true}]) ||
- Name <- [callgraph_deps_out, callgraph_deps_in, callgraph_scc_map]],
- SCCs = digraph_utils:strong_components(G),
- %% Assign unique numbers to SCCs:
- Ints = lists:seq(1, length(SCCs)),
- IntToSCC = lists:zip(Ints, SCCs),
- IntScc = sofs:relation(IntToSCC, [{int, scc}]),
- %% Create mapping from unique integers to SCCs:
- ets:insert(MapsETS, IntToSCC),
- %% Substitute strong components for vertices in edges using the
- %% unique numbers:
- C2V = sofs:relation([{SC, V} || SC <- SCCs, V <- SC], [{scc, v}]),
- I2V = sofs:relative_product(IntScc, C2V), % [{v, int}]
- Es = sofs:relation(digraph:edges(G), [{v, v}]),
- R1 = sofs:relative_product(I2V, Es),
- R2 = sofs:relative_product(I2V, sofs:converse(R1)),
- R2Strict = sofs:strict_relation(R2),
- %% Create out-neighbours:
- Out = sofs:relation_to_family(sofs:converse(R2Strict)),
- ets:insert(OutETS, sofs:to_external(Out)),
- %% Sort the SCCs topologically:
- DG = sofs:family_to_digraph(Out),
- lists:foreach(fun(I) -> digraph:add_vertex(DG, I) end, Ints),
- SCCInts0 = digraph_utils:topsort(DG),
- digraph:delete(DG),
- %% The out-neighbors of a vertex are the vertices called directly.
- %% The used vertices are to occur *before* the calling vertex:
- SCCInts = lists:reverse(SCCInts0),
- %% Create in-neighbours:
- In = sofs:relation_to_family(R2Strict),
- ets:insert(InETS, sofs:to_external(In)),
- %% Create mapping from SCCs to unique integers:
- ets:insert(MapsETS, lists:zip([{'scc', SCC} || SCC<- SCCs], Ints)),
- lists:foreach(fun(E) -> true = ets:give_away(E, Parent, any)
- end, [OutETS, InETS, MapsETS]),
- exit({SCCInts, OutETS, InETS, MapsETS})
- end.
+ OutETS = ets:new(callgraph_label_deps_out,[{read_concurrency, true}]),
+ SCCs = digraph_utils:strong_components(G),
+ %% Assign unique numbers to SCCs:
+ Ints = lists:seq(1, length(SCCs)),
+ IntToSCC = lists:zip(Ints, SCCs),
+ IntScc = sofs:relation(IntToSCC, [{int, scc}]),
+
+ %% Subsitute strong components for vertices in edges using the
+ %% unique numbers:
+ C2V = sofs:relation([{SC, V} || SC <- SCCs, V <- SC], [{scc, v}]),
+ I2V = sofs:relative_product(IntScc, C2V), % [{v, int}]
+ Es = sofs:relation(digraph:edges(G), [{v, v}]),
+ R1 = sofs:relative_product(I2V, Es),
+ R2 = sofs:relative_product(I2V, sofs:converse(R1)),
+ R2Strict = sofs:strict_relation(R2),
+ %% Create out-neighbours:
+ Out = sofs:relation_to_family(sofs:converse(R2Strict)),
+ ets:insert(OutETS, sofs:to_external(Out)),
+ %% Sort the SCCs topologically:
+ DG = sofs:family_to_digraph(Out),
+ lists:foreach(fun(I) -> digraph:add_vertex(DG, I) end, Ints),
+ SCCInts0 = digraph_utils:topsort(DG),
+ digraph:delete(DG),
+ %% The out-neighbors of a vertex are the vertices called directly.
+ %% The used vertices are to occur *before* the calling vertex:
+ SCCInts = lists:reverse(SCCInts0),
+ IntToSCCMap = maps:from_list(IntToSCC),
+ LabelledPostorder = [{I, maps:get(I, IntToSCCMap)} || I <- SCCInts],
+ {{'e', OutETS}, LabelledPostorder}.
diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl
index 25fdef948e..732873056e 100644
--- a/lib/dialyzer/src/dialyzer_cl.erl
+++ b/lib/dialyzer/src/dialyzer_cl.erl
@@ -23,7 +23,9 @@
-module(dialyzer_cl).
--export([start/1]).
+-export([start/1,
+ start_report_modules_analyzed/1,
+ start_report_modules_changed_and_analyzed/1]).
-include("dialyzer.hrl").
-include_lib("kernel/include/file.hrl"). % needed for #file_info{}
@@ -33,12 +35,8 @@
code_server = none :: 'none'
| dialyzer_codeserver:codeserver(),
erlang_mode = false :: boolean(),
- external_calls = [] :: [{mfa(),
- {file:filename(),
- erl_anno:location()}}],
- external_types = [] :: [{mfa(),
- {file:filename(),
- erl_anno:location()}}],
+ external_calls = [] :: [{mfa(), warning_info()}],
+ external_types = [] :: [{mfa(), warning_info()}],
legal_warnings = ordsets:new() :: [dial_warn_tag()],
mod_deps = dict:new() :: dialyzer_callgraph:mod_deps(),
output = standard_io :: io:device(),
@@ -57,14 +55,26 @@
-spec start(#options{}) -> {dial_ret(), [dial_warning()]}.
-start(#options{analysis_type = AnalysisType} = Options) ->
+start(Opts) ->
+ {{Ret,Warns}, _ModulesAnalyzed} = start_report_modules_analyzed(Opts),
+ {Ret,Warns}.
+
+-spec start_report_modules_analyzed(#options{}) -> {{dial_ret(), [dial_warning()]}, [module()]}.
+
+start_report_modules_analyzed(Opts) ->
+ {{Ret,Warns}, _ModulesChanged, ModulesAnalyzed} = start_report_modules_changed_and_analyzed(Opts),
+ {{Ret,Warns}, ModulesAnalyzed}.
+
+-spec start_report_modules_changed_and_analyzed(#options{}) -> {{dial_ret(), [dial_warning()]}, undefined | [module()], [module()]}.
+
+start_report_modules_changed_and_analyzed(#options{analysis_type = AnalysisType} = Options) ->
process_flag(trap_exit, true),
case AnalysisType of
plt_check -> check_plt(Options);
plt_build -> build_plt(Options);
plt_add -> add_to_plt(Options);
plt_remove -> remove_from_plt(Options);
- succ_typings -> do_analysis(Options)
+ succ_typings -> enrich_with_modules_changed(do_analysis(Options), undefined)
end.
%%--------------------------------------------------------------------
@@ -72,9 +82,9 @@ start(#options{analysis_type = AnalysisType} = Options) ->
build_plt(Opts) ->
Opts1 = init_opts_for_build(Opts),
Files = get_files_from_opts(Opts1),
- Md5 = dialyzer_plt:compute_md5_from_files(Files),
- PltInfo = #plt_info{files = Md5, mod_deps = dict:new()},
- do_analysis(Files, Opts1, dialyzer_plt:new(), PltInfo).
+ Md5 = dialyzer_cplt:compute_md5_from_files(Files),
+ PltInfo = #plt_info{files = Md5},
+ enrich_with_modules_changed(do_analysis(Files, Opts1, dialyzer_plt:new(), PltInfo), undefined).
init_opts_for_build(Opts) ->
case Opts#options.output_plt =:= none of
@@ -95,7 +105,16 @@ init_opts_for_build(Opts) ->
add_to_plt(Opts) ->
Opts1 = init_opts_for_add(Opts),
AddFiles = get_files_from_opts(Opts1),
- plt_common(Opts1, [], AddFiles).
+ case Opts1#options.init_plts of
+ [] -> plt_common(Opts1, [], AddFiles);
+ [_] -> plt_common(Opts1, [], AddFiles);
+ PltFiles ->
+ Plts = [dialyzer_cplt:from_file(F) || F <- PltFiles],
+ % Check merge safety
+ _ = dialyzer_cplt:merge_plts_or_report_conflicts(PltFiles, Plts),
+ _ = [plt_common(Opts#options{init_plts=[Plt]}, [], AddFiles) || Plt <- PltFiles],
+ {{?RET_NOTHING_SUSPICIOUS, []}, [], []}
+ end.
init_opts_for_add(Opts) ->
case Opts#options.output_plt =:= none of
@@ -116,8 +135,6 @@ init_opts_for_add(Opts) ->
end
end.
-%%--------------------------------------------------------------------
-
check_plt(#options{init_plts = []} = Opts) ->
Opts1 = init_opts_for_check(Opts),
report_check(Opts1),
@@ -132,10 +149,12 @@ check_plt_aux([_] = Plt, Opts) ->
plt_common(Opts2, [], []);
check_plt_aux([Plt|Plts], Opts) ->
case check_plt_aux([Plt], Opts) of
- {?RET_NOTHING_SUSPICIOUS, []} -> check_plt_aux(Plts, Opts);
- {?RET_DISCREPANCIES, Warns} ->
- {_RET, MoreWarns} = check_plt_aux(Plts, Opts),
- {?RET_DISCREPANCIES, Warns ++ MoreWarns}
+ {{?RET_NOTHING_SUSPICIOUS, []}, ModulesChanged, ModulesAnalyzed} ->
+ {{Ret, Warns}, MoreModulesChanged, MoreModulesAnalyzed} = check_plt_aux(Plts, Opts),
+ {{Ret, Warns}, ordsets:union(ModulesChanged, MoreModulesChanged), ordsets:union(ModulesAnalyzed, MoreModulesAnalyzed)};
+ {{?RET_DISCREPANCIES, Warns}, ModulesChanged, ModulesAnalyzed} ->
+ {{_RET, MoreWarns}, MoreModulesChanged, MoreModulesAnalyzed} = check_plt_aux(Plts, Opts),
+ {{?RET_DISCREPANCIES, Warns ++ MoreWarns}, ordsets:union(ModulesChanged, MoreModulesChanged), ordsets:union(ModulesAnalyzed, MoreModulesAnalyzed)}
end.
init_opts_for_check(Opts) ->
@@ -145,6 +164,7 @@ init_opts_for_check(Opts) ->
Plt -> Plt
end,
[OutputPlt] = InitPlt,
+
Opts#options{files = [],
files_rec = [],
analysis_type = plt_check,
@@ -198,43 +218,49 @@ plt_common(#options{init_plts = [InitPlt]} = Opts, RemoveFiles, AddFiles) ->
quiet -> ok;
_ -> io:put_chars(" yes\n")
end,
- {?RET_NOTHING_SUSPICIOUS, []};
+ {{?RET_NOTHING_SUSPICIOUS, []}, [], []};
{old_version, Md5} ->
- PltInfo = #plt_info{files = Md5, mod_deps = dict:new()},
+ PltInfo = #plt_info{files = Md5},
Files = [F || {F, _} <- Md5],
- do_analysis(Files, Opts, dialyzer_plt:new(), PltInfo);
+ enrich_with_modules_changed(do_analysis(Files, Opts, dialyzer_plt:new(), PltInfo), undefined);
{differ, Md5, DiffMd5, ModDeps} ->
report_failed_plt_check(Opts, DiffMd5),
- {AnalFiles, RemovedMods, ModDeps1} =
+ {AnalFiles, RemovedMods, ModDeps1} =
expand_dependent_modules(Md5, DiffMd5, ModDeps),
Plt = clean_plt(InitPlt, RemovedMods),
+ ChangedOrRemovedMods = [ChangedOrRemovedMod || {_, ChangedOrRemovedMod} <- DiffMd5],
case AnalFiles =:= [] of
true ->
%% Only removed stuff. Just write the PLT.
- dialyzer_plt:to_file(Opts#options.output_plt, Plt, ModDeps,
- #plt_info{files = Md5, mod_deps = ModDeps}),
- {?RET_NOTHING_SUSPICIOUS, []};
+ dialyzer_cplt:to_file(Opts#options.output_plt, Plt, ModDeps1, #plt_info{files=Md5, mod_deps=ModDeps1}),
+ {{?RET_NOTHING_SUSPICIOUS, []}, ChangedOrRemovedMods, []};
false ->
- do_analysis(AnalFiles, Opts, Plt, #plt_info{files = Md5, mod_deps = ModDeps1})
+ enrich_with_modules_changed(do_analysis(AnalFiles, Opts, Plt, #plt_info{files=Md5, mod_deps=ModDeps1}), ChangedOrRemovedMods)
end;
{error, no_such_file} ->
- Msg = io_lib:format("Could not find the PLT: ~ts\n~s",
+ Msg = io_lib:format("Could not find the PLT: ~ts~n~s",
[InitPlt, default_plt_error_msg()]),
cl_error(Msg);
{error, not_valid} ->
- Msg = io_lib:format("The file: ~ts is not a valid PLT file\n~s",
+ Msg = io_lib:format("The file: ~ts is not a valid PLT file~n~s",
[InitPlt, default_plt_error_msg()]),
cl_error(Msg);
{error, read_error} ->
- Msg = io_lib:format("Could not read the PLT: ~ts\n~s",
+ Msg = io_lib:format("Could not read the PLT: ~ts~n~s",
[InitPlt, default_plt_error_msg()]),
cl_error(Msg);
{error, {no_file_to_remove, F}} ->
- Msg = io_lib:format("Could not remove the file ~ts from the PLT: ~ts\n",
+ Msg = io_lib:format("Could not remove the file ~ts from the PLT: ~ts~n",
[F, InitPlt]),
cl_error(Msg)
end.
+-spec enrich_with_modules_changed({{Ret :: dial_ret(), Warns :: [dial_warning()]}, Analyzed :: [module()]}, Changed :: undefined | [module()]) ->
+ {{dial_ret(), [dial_warning()]}, Changed :: undefined | [module()], Analyzed :: [module()]}.
+
+enrich_with_modules_changed({{Ret,Warns}, Analyzed}, Changed) ->
+ {{Ret,Warns}, Changed, Analyzed}.
+
default_plt_error_msg() ->
"Use the options:\n"
" --build_plt to build a new PLT; or\n"
@@ -252,7 +278,7 @@ default_plt_error_msg() ->
%%--------------------------------------------------------------------
check_plt(#options{init_plts = [Plt]} = Opts, RemoveFiles, AddFiles) ->
- case dialyzer_plt:check_plt(Plt, RemoveFiles, AddFiles) of
+ case dialyzer_cplt:check_plt(Plt, RemoveFiles, AddFiles) of
{old_version, _MD5} = OldVersion ->
report_old_version(Opts),
OldVersion;
@@ -282,7 +308,7 @@ report_old_version(#options{report_mode = ReportMode, init_plts = [InitPlt]}) ->
[InitPlt])
end.
-report_failed_plt_check(#options{analysis_type = AnalType,
+report_failed_plt_check(#options{analysis_type = AnalType,
report_mode = ReportMode}, DiffMd5) ->
case AnalType =:= plt_check of
true ->
@@ -349,10 +375,10 @@ report_md5_diff(List) ->
%%--------------------------------------------------------------------
get_default_init_plt() ->
- [dialyzer_plt:get_default_plt()].
+ [dialyzer_cplt:get_default_cplt_filename()].
get_default_output_plt() ->
- dialyzer_plt:get_default_plt().
+ dialyzer_cplt:get_default_cplt_filename().
%%--------------------------------------------------------------------
@@ -367,11 +393,11 @@ do_analysis(Options) ->
case Options#options.init_plts of
[] -> do_analysis(Files, Options, dialyzer_plt:new(), none);
PltFiles ->
- Plts = [dialyzer_plt:from_file(F) || F <- PltFiles],
- Plt = dialyzer_plt:merge_plts_or_report_conflicts(PltFiles, Plts),
+ Plts = [dialyzer_cplt:from_file(F) || F <- PltFiles],
+ Plt = dialyzer_cplt:merge_plts_or_report_conflicts(PltFiles, Plts),
do_analysis(Files, Options, Plt, none)
end.
-
+
do_analysis(Files, Options, Plt, PltInfo) ->
assert_writable(Options#options.output_plt),
report_analysis_start(Options),
@@ -381,7 +407,8 @@ do_analysis(Files, Options, Plt, PltInfo) ->
output_plt = Options#options.output_plt,
plt_info = PltInfo,
erlang_mode = Options#options.erlang_mode,
- report_mode = Options#options.report_mode},
+ report_mode = Options#options.report_mode
+ },
AnalysisType = convert_analysis_type(Options#options.analysis_type,
Options#options.get_warnings),
InitAnalysis = #analysis{type = AnalysisType,
@@ -393,23 +420,19 @@ do_analysis(Files, Options, Plt, PltInfo) ->
plt = Plt,
use_contracts = Options#options.use_contracts,
callgraph_file = Options#options.callgraph_file,
+ mod_deps_file = Options#options.mod_deps_file,
solvers = Options#options.solvers},
State3 = start_analysis(State2, InitAnalysis),
{T1, _} = statistics(wall_clock),
- Return = cl_loop(State3),
+ RetAndWarns = cl_loop(State3),
{T2, _} = statistics(wall_clock),
report_elapsed_time(T1, T2, Options),
- Return.
-
-convert_analysis_type(plt_check, true) -> succ_typings;
-convert_analysis_type(plt_check, false) -> plt_build;
-convert_analysis_type(plt_add, true) -> succ_typings;
-convert_analysis_type(plt_add, false) -> plt_build;
-convert_analysis_type(plt_build, true) -> succ_typings;
-convert_analysis_type(plt_build, false) -> plt_build;
-convert_analysis_type(plt_remove, true) -> succ_typings;
-convert_analysis_type(plt_remove, false) -> plt_build;
-convert_analysis_type(succ_typings, _) -> succ_typings.
+ {RetAndWarns, lists:usort([path_to_mod(F) || F <- Files])}.
+
+-spec convert_analysis_type(anal_type1(), boolean()) -> succ_typings | plt_build.
+convert_analysis_type(succ_typings, _) -> succ_typings;
+convert_analysis_type(_, true) -> succ_typings;
+convert_analysis_type(_, false) -> plt_build.
%%--------------------------------------------------------------------
@@ -428,14 +451,13 @@ check_if_writable(PltFile) ->
true -> is_writable_file_or_dir(PltFile);
false ->
case filelib:is_dir(PltFile) of
- true -> false;
- false ->
- DirName = filename:dirname(PltFile),
- case filelib:is_dir(DirName) of
+ true -> false;
+ false ->
+ DirName = filename:dirname(PltFile),
+ case filelib:is_dir(DirName) of
false ->
case filelib:ensure_dir(PltFile) of
ok ->
- io:format(" Creating ~ts as it did not exist...~n", [DirName]),
true;
{error, _} ->
false
@@ -458,7 +480,7 @@ is_writable_file_or_dir(PltFile) ->
clean_plt(PltFile, RemovedMods) ->
%% Clean the plt from the removed modules.
- Plt = dialyzer_plt:from_file(PltFile),
+ Plt = dialyzer_cplt:from_file(PltFile),
sets:fold(fun(M, AccPlt) -> dialyzer_plt:delete_module(AccPlt, M) end,
Plt, RemovedMods).
@@ -469,9 +491,9 @@ expand_dependent_modules(Md5, DiffMd5, ModDeps) ->
BigList = sets:to_list(BigSet),
ExpandedSet = expand_dependent_modules_1(BigList, BigSet, ModDeps),
NewModDeps = dialyzer_callgraph:strip_module_deps(ModDeps, BigSet),
- AnalyzeMods = sets:subtract(ExpandedSet, RemovedMods),
+ AnalyzeMods = sets:subtract(ExpandedSet, RemovedMods),
FilterFun = fun(File) ->
- Mod = list_to_atom(filename:basename(File, ".beam")),
+ Mod = path_to_mod(File),
sets:is_element(Mod, AnalyzeMods)
end,
{[F || {F, _} <- Md5, FilterFun(F)], BigSet, NewModDeps}.
@@ -479,12 +501,12 @@ expand_dependent_modules(Md5, DiffMd5, ModDeps) ->
expand_dependent_modules_1([Mod|Mods], Included, ModDeps) ->
case dict:find(Mod, ModDeps) of
{ok, Deps} ->
- NewDeps = sets:subtract(sets:from_list(Deps), Included),
+ NewDeps = sets:subtract(sets:from_list(Deps), Included),
case sets:size(NewDeps) =:= 0 of
true -> expand_dependent_modules_1(Mods, Included, ModDeps);
- false ->
+ false ->
NewIncluded = sets:union(Included, NewDeps),
- expand_dependent_modules_1(sets:to_list(NewDeps) ++ Mods,
+ expand_dependent_modules_1(sets:to_list(NewDeps) ++ Mods,
NewIncluded, ModDeps)
end;
error ->
@@ -493,6 +515,9 @@ expand_dependent_modules_1([Mod|Mods], Included, ModDeps) ->
expand_dependent_modules_1([], Included, _ModDeps) ->
Included.
+path_to_mod(File) ->
+ list_to_atom(filename:basename(File, ".beam")).
+
new_state() ->
#cl_state{}.
@@ -536,7 +561,7 @@ maybe_close_output_file(State) ->
-define(LOG_CACHE_SIZE, 10).
-%%-spec cl_loop(#cl_state{}) ->
+%%-spec cl_loop(#cl_state{}) ->
cl_loop(State) ->
cl_loop(State, []).
@@ -609,14 +634,15 @@ cl_error(State, Msg) ->
maybe_close_output_file(State),
throw({dialyzer_error, lists:flatten(Msg)}).
-return_value(State = #cl_state{code_server = CodeServer,
- erlang_mode = ErlangMode,
- mod_deps = ModDeps,
- output_plt = OutputPlt,
- plt_info = PltInfo,
- error_location = EOpt,
- stored_warnings = StoredWarnings},
- Plt) ->
+return_value(#cl_state{code_server = CodeServer,
+ erlang_mode = ErlangMode,
+ mod_deps = ModDeps,
+ output_plt = OutputPlt,
+ plt_info = PltInfo,
+ error_location = EOpt,
+ stored_warnings = StoredWarnings}=State,
+ Plt) ->
+ UnknownWarnings = unknown_warnings(State),
%% Just for now:
case CodeServer =:= none of
true ->
@@ -628,9 +654,8 @@ return_value(State = #cl_state{code_server = CodeServer,
true ->
dialyzer_plt:delete(Plt);
false ->
- dialyzer_plt:to_file(OutputPlt, Plt, ModDeps, PltInfo)
+ dialyzer_cplt:to_file(OutputPlt, Plt, ModDeps, PltInfo)
end,
- UnknownWarnings = unknown_warnings(State),
RetValue =
case StoredWarnings =:= [] andalso UnknownWarnings =:= [] of
true -> ?RET_NOTHING_SUSPICIOUS;
@@ -638,18 +663,19 @@ return_value(State = #cl_state{code_server = CodeServer,
end,
case ErlangMode of
false ->
- print_warnings(State),
- print_ext_calls(State),
- print_ext_types(State),
- maybe_close_output_file(State),
+ Fns = [ fun print_warnings/1, fun print_ext_calls/1
+ , fun print_ext_types/1, fun maybe_close_output_file/1],
+ lists:foreach(fun (F) -> F(State) end, Fns),
{RetValue, []};
- true ->
- AllWarnings =
- UnknownWarnings ++ process_warnings(StoredWarnings),
- {RetValue, set_warning_id(AllWarnings, EOpt)}
+ true ->
+ ResultingWarnings = process_warnings(StoredWarnings ++ UnknownWarnings),
+ {RetValue, set_warning_id(ResultingWarnings, EOpt)}
end.
-unknown_warnings(State = #cl_state{legal_warnings = LegalWarnings}) ->
+unknown_warnings(State) ->
+ [Warning || {_M, Warning} <- unknown_warnings_by_module(State)].
+
+unknown_warnings_by_module(#cl_state{legal_warnings = LegalWarnings} = State) ->
case ordsets:is_element(?WARN_UNKNOWN, LegalWarnings) of
true ->
lists:sort(unknown_functions(State)) ++
@@ -657,9 +683,14 @@ unknown_warnings(State = #cl_state{legal_warnings = LegalWarnings}) ->
false -> []
end.
-unknown_functions(#cl_state{external_calls = Calls}) ->
- [{?WARN_UNKNOWN, {File, Location, ''},{unknown_function, MFA}} ||
- {MFA, {File, Location}} <- Calls].
+unknown_functions(#cl_state{ external_calls = Calls
+ , code_server = CodeServer}) ->
+ [{Mod, {?WARN_UNKNOWN, WarningInfo, {unknown_function, MFA}}} || { MFA, WarningInfo = {_, _, {Mod, _, _}=WarnMFA}} <- Calls
+ , not dialyzer_codeserver:is_member_meta_info(WarnMFA, CodeServer)].
+
+unknown_types(#cl_state{external_types = Types}) ->
+ [{Mod, {?WARN_UNKNOWN, WarningInfo, {unknown_type, MFA}}} ||
+ {MFA, WarningInfo = {_, _, {Mod, _, _}}} <- Types].
set_warning_id(Warnings, EOpt) ->
lists:map(fun({Tag, {File, Location, _MorMFA}, Msg}) ->
@@ -695,17 +726,13 @@ print_ext_calls(#cl_state{output = Output,
end
end.
-do_print_ext_calls(Output, [{{M,F,A},{File,Location}}|T], Before) ->
+do_print_ext_calls(Output, [{{M,F,A},{File,Location,_FromMFA}}|T], Before) ->
io:format(Output, "~s~tp:~tp/~p (~ts)\n",
[Before,M,F,A,file_pos(File, Location)]),
do_print_ext_calls(Output, T, Before);
do_print_ext_calls(_, [], _) ->
ok.
-unknown_types(#cl_state{external_types = Types}) ->
- [{?WARN_UNKNOWN, {File, Location, ''},{unknown_type, MFA}} ||
- {MFA, {File, Location}} <- Types].
-
print_ext_types(#cl_state{report_mode = quiet}) ->
ok;
print_ext_types(#cl_state{output = Output,
@@ -731,7 +758,7 @@ print_ext_types(#cl_state{output = Output,
end
end.
-do_print_ext_types(Output, [{{M,F,A},{File,Location}}|T], Before) ->
+do_print_ext_types(Output, [{{M,F,A},{File,Location,_}}|T], Before) ->
io:format(Output, "~s~tp:~tp/~p (~ts)\n",
[Before,M,F,A,file_pos(File, Location)]),
do_print_ext_types(Output, T, Before);
@@ -752,8 +779,8 @@ pos(Line) ->
%% Keep one warning per MFA and File. Often too many warnings otherwise.
%%
limit_unknown(Unknowns) ->
- L = [{{MFA, File}, Line} || {MFA, {File, Line}} <- Unknowns],
- [{MFA, {File, Line}} || {{MFA, File}, [Line|_]} <-
+ L = [{{MFA, File}, {FromMFA, Line}} || {MFA, {File, Line, FromMFA}} <- Unknowns],
+ [{MFA, {File, Line, FromMFA}} || {{MFA, File}, [{FromMFA, Line}|_]} <-
dialyzer_utils:family(L)].
%% Keep one warning per MFA. This is how it used to be before Erlang/OTP 24.
%% limit_unknown(Unknowns) ->
@@ -787,10 +814,11 @@ print_warnings(#cl_state{output = Output,
end.
-spec process_warnings([raw_warning()]) -> [raw_warning()].
-
+
process_warnings(Warnings) ->
- Warnings1 = lists:keysort(2, Warnings), %% Sort on file/location (and m/mfa..)
- remove_duplicate_warnings(Warnings1, []).
+ Warnings1 = lists:keysort(3, Warnings), %% First sort on Warning
+ Warnings2 = lists:keysort(2, Warnings1), %% Sort on file/location (and m/mfa..)
+ remove_duplicate_warnings(Warnings2, []).
remove_duplicate_warnings([Duplicate, Duplicate|Left], Acc) ->
remove_duplicate_warnings([Duplicate|Left], Acc);
@@ -813,7 +841,7 @@ add_files(Files, From) ->
add_files(Files, From, Rec) ->
Files1 = [filename:absname(F) || F <- Files],
- Files2 = ordsets:from_list(Files1),
+ Files2 = ordsets:from_list(Files1),
Dirs = ordsets:filter(fun(X) -> filelib:is_dir(X) end, Files2),
Files3 = ordsets:subtract(Files2, Dirs),
Extension = case From of
@@ -840,9 +868,8 @@ add_file_fun(Extension) ->
start_analysis(State, Analysis) ->
Self = self(),
LegalWarnings = State#cl_state.legal_warnings,
- Fun = fun() ->
+ Fun = fun() ->
dialyzer_analysis_callgraph:start(Self, LegalWarnings, Analysis)
end,
BackendPid = spawn_link(Fun),
State#cl_state{backend_pid = BackendPid}.
-
diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl
index e59f4027ac..2babea0073 100644
--- a/lib/dialyzer/src/dialyzer_cl_parse.erl
+++ b/lib/dialyzer/src/dialyzer_cl_parse.erl
@@ -1,5 +1,3 @@
-%% -*- erlang-indent-level: 2 -*-
-%%
%% 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
@@ -14,8 +12,7 @@
-module(dialyzer_cl_parse).
--export([start/0, get_lib_dir/1]).
--export([collect_args/1]). % used also by typer
+-export([start/0]).
-include("dialyzer.hrl").
@@ -27,509 +24,298 @@
| {'gui', #options{}}
| {'error', string()}.
--type deep_string() :: string() | [deep_string()].
-
-%%-----------------------------------------------------------------------
-
-spec start() -> dial_cl_parse_ret().
-
start() ->
- init(),
- Args = init:get_plain_arguments(),
- try
- Ret = cl(Args),
- Ret
- catch
- throw:{dialyzer_cl_parse_error, Msg} -> {error, Msg};
- _:R:S ->
- Msg = io_lib:format("~tp\n~tp\n", [R, S]),
- {error, lists:flatten(Msg)}
- end.
-
-cl(["--add_to_plt"|T]) ->
- put(dialyzer_options_analysis_type, plt_add),
- cl(T);
-cl(["--apps"|T]) ->
- T1 = get_lib_dir(T),
- {Args, T2} = collect_args(T1),
- append_var(dialyzer_options_files_rec, Args),
- cl(T2);
-cl(["--build_plt"|T]) ->
- put(dialyzer_options_analysis_type, plt_build),
- cl(T);
-cl(["--check_plt"|T]) ->
- put(dialyzer_options_analysis_type, plt_check),
- cl(T);
-cl(["-n"|T]) ->
- cl(["--no_check_plt"|T]);
-cl(["--no_check_plt"|T]) ->
- put(dialyzer_options_check_plt, false),
- cl(T);
-cl(["-nn"|T]) ->
- %% Ignored since Erlang/OTP 24.0.
- cl(T);
-cl(["--no_native"|T]) ->
- %% Ignored since Erlang/OTP 24.0.
- cl(T);
-cl(["--no_native_cache"|T]) ->
- %% Ignored since Erlang/OTP 24.0.
- cl(T);
-cl(["--plt_info"|T]) ->
- put(dialyzer_options_analysis_type, plt_info),
- cl(T);
-cl(["--get_warnings"|T]) ->
- put(dialyzer_options_get_warnings, true),
- cl(T);
-cl(["-D"|_]) ->
- cl_error("No defines specified after -D");
-cl(["-D"++Define|T]) ->
- Def = re:split(Define, "=", [{return, list}, unicode]),
- append_defines(Def),
- cl(T);
-cl(["-h"|_]) ->
- help_message();
-cl(["--help"|_]) ->
- help_message();
-cl(["-I"]) ->
- cl_error("no include directory specified after -I");
-cl(["-I", Dir|T]) ->
- append_include(Dir),
- cl(T);
-cl(["-I"++Dir|T]) ->
- append_include(Dir),
- cl(T);
-cl(["--input_list_file"]) ->
- cl_error("No input list file specified");
-cl(["--input_list_file",File|L]) ->
- read_input_list_file(File),
- cl(L);
-cl(["-c"++_|T]) ->
- NewTail = command_line(T),
- cl(NewTail);
-cl(["-r"++_|T0]) ->
- {Args, T} = collect_args(T0),
- append_var(dialyzer_options_files_rec, Args),
- cl(T);
-cl(["--remove_from_plt"|T]) ->
- put(dialyzer_options_analysis_type, plt_remove),
- cl(T);
-cl(["--com"++_|T]) ->
- NewTail = command_line(T),
- cl(NewTail);
-cl(["--output"]) ->
- cl_error("No outfile specified");
-cl(["-o"]) ->
- cl_error("No outfile specified");
-cl(["--output",Output|T]) ->
- put(dialyzer_output, Output),
- cl(T);
-cl(["--output_plt"]) ->
- cl_error("No outfile specified for --output_plt");
-cl(["--output_plt",Output|T]) ->
- put(dialyzer_output_plt, Output),
- cl(T);
-cl(["-o", Output|T]) ->
- put(dialyzer_output, Output),
- cl(T);
-cl(["-o"++Output|T]) ->
- put(dialyzer_output, Output),
- cl(T);
-cl(["--raw"|T]) ->
- put(dialyzer_output_format, raw),
- cl(T);
-cl(["--fullpath"|T]) ->
- put(dialyzer_filename_opt, fullpath),
- cl(T);
-cl(["--no_indentation"|T]) ->
- put(dialyzer_indent_opt, false),
- cl(T);
-cl(["-pa", Path|T]) ->
- case code:add_patha(Path) of
- true -> cl(T);
- {error, _} -> cl_error("Bad directory for -pa: " ++ Path)
- end;
-cl(["--plt"]) ->
- error("No plt specified for --plt");
-cl(["--plt", PLT|T]) ->
- put(dialyzer_init_plts, [PLT]),
- cl(T);
-cl(["--plts"]) ->
- error("No plts specified for --plts");
-cl(["--plts"|T]) ->
- {PLTs, NewT} = get_plts(T, []),
- put(dialyzer_init_plts, PLTs),
- cl(NewT);
-cl(["-q"|T]) ->
- put(dialyzer_options_report_mode, quiet),
- cl(T);
-cl(["--quiet"|T]) ->
- put(dialyzer_options_report_mode, quiet),
- cl(T);
-cl(["--src"|T]) ->
- put(dialyzer_options_from, src_code),
- cl(T);
-cl(["--no_spec"|T]) ->
- put(dialyzer_options_use_contracts, false),
- cl(T);
-cl(["--statistics"|T]) ->
- put(dialyzer_timing, true),
- cl(T);
-cl(["--resources"|T]) ->
- put(dialyzer_options_report_mode, quiet),
- put(dialyzer_timing, debug),
- cl(T);
-cl(["-v"|_]) ->
- io:format("Dialyzer version "++?VSN++"\n"),
- erlang:halt(?RET_NOTHING_SUSPICIOUS);
-cl(["--version"|_]) ->
- io:format("Dialyzer version "++?VSN++"\n"),
- erlang:halt(?RET_NOTHING_SUSPICIOUS);
-cl(["--verbose"|T]) ->
- put(dialyzer_options_report_mode, verbose),
- cl(T);
-cl(["-W"|_]) ->
- cl_error("-W given without warning");
-cl(["-Whelp"|_]) ->
- help_warnings();
-cl(["-W"++Warn|T]) ->
- append_var(dialyzer_warnings, [list_to_atom(Warn)]),
- cl(T);
-cl(["--dump_callgraph"]) ->
- cl_error("No outfile specified for --dump_callgraph");
-cl(["--dump_callgraph", File|T]) ->
- put(dialyzer_callgraph_file, File),
- cl(T);
-cl(["--gui"|T]) ->
- put(dialyzer_options_mode, gui),
- cl(T);
-cl(["--error_location", LineOrColumn|T]) ->
- put(dialyzer_error_location_opt, list_to_atom(LineOrColumn)),
- cl(T);
-cl(["--solver", Solver|T]) -> % not documented
- append_var(dialyzer_solvers, [list_to_atom(Solver)]),
- cl(T);
-cl([H|_] = L) ->
- case filelib:is_file(H) orelse filelib:is_dir(H) of
- true ->
- NewTail = command_line(L),
- cl(NewTail);
- false ->
- cl_error("Unknown option: " ++ H)
- end;
-cl([]) ->
- {RetTag, Opts} =
- case get(dialyzer_options_analysis_type) =:= plt_info of
- true ->
- put(dialyzer_options_analysis_type, plt_check),
- {plt_info, cl_options()};
- false ->
- case get(dialyzer_options_mode) of
- gui -> {gui, common_options()};
- cl ->
- case get(dialyzer_options_analysis_type) =:= plt_check of
- true -> {check_init, cl_options()};
- false -> {cl, cl_options()}
- end
- end
- end,
- case dialyzer_options:build(Opts) of
- {error, Msg} -> cl_error(Msg);
- OptsRecord -> {RetTag, OptsRecord}
- end.
-
-%%-----------------------------------------------------------------------
-
-command_line(T0) ->
- {Args, T} = collect_args(T0),
- append_var(dialyzer_options_files, Args),
- %% if all files specified are ".erl" files, set the 'src' flag automatically
- case lists:all(fun(F) -> filename:extension(F) =:= ".erl" end, Args) of
- true -> put(dialyzer_options_from, src_code);
- false -> ok
- end,
- T.
-
-read_input_list_file(File) ->
- case file:read_file(File) of
- {ok,Bin} ->
- Files = binary:split(Bin, <<"\n">>, [trim_all,global]),
- NewFiles = [binary_to_list(string:trim(F)) || F <- Files],
- append_var(dialyzer_options_files, NewFiles);
- {error,Reason} ->
- cl_error(io_lib:format("Reading of ~s failed: ~s", [File,file:format_error(Reason)]))
- end.
-
--spec cl_error(deep_string()) -> no_return().
-
-cl_error(Str) ->
- Msg = lists:flatten(Str),
- throw({dialyzer_cl_parse_error, Msg}).
-
-init() ->
- %% By not initializing every option, the modified options can be
- %% found. If every option were to be returned by cl_options() and
- %% common_options(), then the environment variables (currently only
- %% ERL_COMPILER_OPTIONS) would be overwritten by default values.
- put(dialyzer_options_mode, cl),
- put(dialyzer_warnings, []),
- ok.
-
-append_defines([Def, Val]) ->
- {ok, Tokens, _} = erl_scan:string(Val++"."),
- {ok, ErlVal} = erl_parse:parse_term(Tokens),
- append_var(dialyzer_options_defines, [{list_to_atom(Def), ErlVal}]);
-append_defines([Def]) ->
- append_var(dialyzer_options_defines, [{list_to_atom(Def), true}]).
-
-append_include(Dir) ->
- append_var(dialyzer_include, [Dir]).
-
-append_var(Var, List) when is_list(List) ->
- case get(Var) of
- undefined ->
- put(Var, List);
- L ->
- put(Var, L ++ List)
- end,
- ok.
-
-%%-----------------------------------------------------------------------
-
--spec collect_args([string()]) -> {[string()], [string()]}.
-
-collect_args(List) ->
- collect_args_1(List, []).
-
-collect_args_1(["-"++_|_] = L, Acc) ->
- {lists:reverse(Acc), L};
-collect_args_1([Arg|T], Acc) ->
- collect_args_1(T, [Arg|Acc]);
-collect_args_1([], Acc) ->
- {lists:reverse(Acc), []}.
+ Args = init:get_plain_arguments(),
+ try argparse:parse(Args, cli(), #{progname => dialyzer}) of
+ {ok, ArgMap, _, _} ->
+ {Command, Opts} = postprocess_side_effects(ArgMap),
+ case dialyzer_options:build(maps:to_list(Opts)) of
+ {error, Msg2} ->
+ {error, Msg2};
+ OptsRecord ->
+ {Command, OptsRecord}
+ end;
+ {error, Error} ->
+ {error, argparse:format_error(Error)}
+ catch
+ throw:{dialyzer_cl_parse_error, Msg} ->
+ {error, Msg};
+ _:R:S ->
+ Msg = io_lib:format("~tp\n~tp\n", [R, S]),
+ {error, lists:flatten(Msg)}
+ end.
%%-----------------------------------------------------------------------
-cl_options() ->
- OptsList = [{files, dialyzer_options_files},
- {files_rec, dialyzer_options_files_rec},
- {output_file, dialyzer_output},
- {output_format, dialyzer_output_format},
- {filename_opt, dialyzer_filename_opt},
- {indent_opt, dialyzer_indent_opt},
- {error_location, dialyzer_error_location_opt},
- {analysis_type, dialyzer_options_analysis_type},
- {get_warnings, dialyzer_options_get_warnings},
- {timing, dialyzer_timing},
- {callgraph_file, dialyzer_callgraph_file}],
- get_options(OptsList) ++ common_options().
-
-common_options() ->
- OptsList = [{defines, dialyzer_options_defines},
- {from, dialyzer_options_from},
- {include_dirs, dialyzer_include},
- {plts, dialyzer_init_plts},
- {output_plt, dialyzer_output_plt},
- {report_mode, dialyzer_options_report_mode},
- {use_spec, dialyzer_options_use_contracts},
- {warnings, dialyzer_warnings},
- {check_plt, dialyzer_options_check_plt},
- {solvers, dialyzer_solvers}],
- get_options(OptsList).
-
-get_options(TagOptionList) ->
- lists:append([get_opt(Tag, Opt) || {Tag, Opt} <- TagOptionList]).
-
-get_opt(Tag, Opt) ->
- case get(Opt) of
- undefined ->
- [];
- V ->
- [{Tag, V}]
- end.
-
-%%-----------------------------------------------------------------------
-
--spec get_lib_dir([string()]) -> [string()].
-
-get_lib_dir(Apps) ->
- get_lib_dir(Apps, []).
-
-get_lib_dir([H|T], Acc) ->
- NewElem =
- case code:lib_dir(list_to_atom(H)) of
- {error, bad_name} -> H;
- LibDir when H =:= "erts" -> % hack for including erts in an un-installed system
- EbinDir = filename:join([LibDir,"ebin"]),
- case file:read_file_info(EbinDir) of
- {error,enoent} ->
- filename:join([LibDir,"preloaded","ebin"]);
- _ ->
- EbinDir
- end;
- LibDir -> filename:join(LibDir,"ebin")
- end,
- get_lib_dir(T, [NewElem|Acc]);
-get_lib_dir([], Acc) ->
- lists:reverse(Acc).
-
-%%-----------------------------------------------------------------------
-
-get_plts(["--"|T], Acc) -> {lists:reverse(Acc), T};
-get_plts(["-"++_Opt = H|T], Acc) -> {lists:reverse(Acc), [H|T]};
-get_plts([H|T], Acc) -> get_plts(T, [H|Acc]);
-get_plts([], Acc) -> {lists:reverse(Acc), []}.
-
-%%-----------------------------------------------------------------------
-
--spec help_warnings() -> no_return().
-
-help_warnings() ->
- S = warning_options_msg(),
- io:put_chars(S),
- erlang:halt(?RET_NOTHING_SUSPICIOUS).
-
--spec help_message() -> no_return().
-
-help_message() ->
- S = "Usage: dialyzer [--add_to_plt] [--apps applications] [--build_plt]
- [--check_plt] [-Ddefine]* [-Dname]* [--dump_callgraph file]
- [--error_location flag] [files_or_dirs] [--fullpath]
- [--get_warnings] [--gui] [--help] [-I include_dir]*
- [--no_check_plt] [--no_indentation] [-o outfile]
- [--output_plt file] [-pa dir]* [--plt plt] [--plt_info]
- [--plts plt*] [--quiet] [-r dirs] [--raw] [--remove_from_plt]
- [--shell] [--src] [--statistics] [--verbose] [--version]
- [-Wwarn]*
-
-Options:
- files_or_dirs (for backwards compatibility also as: -c files_or_dirs)
- Use Dialyzer from the command line to detect defects in the
- specified files or directories containing .erl or .beam files,
- depending on the type of the analysis.
- -r dirs
- Same as the previous but the specified directories are searched
- recursively for subdirectories containing .erl or .beam files in
- them, depending on the type of analysis.
- --input_list_file file
- Specify the name of a file that contains the names of the files
- to be analyzed (one file name per line).
- --apps applications
- Option typically used when building or modifying a plt as in:
- dialyzer --build_plt --apps erts kernel stdlib mnesia ...
- to conveniently refer to library applications corresponding to the
- Erlang/OTP installation. However, the option is general and can also
- be used during analysis in order to refer to Erlang/OTP applications.
- In addition, file or directory names can also be included, as in:
- dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam
- -o outfile (or --output outfile)
- When using Dialyzer from the command line, send the analysis
- results to the specified outfile rather than to stdout.
- --raw
- When using Dialyzer from the command line, output the raw analysis
- results (Erlang terms) instead of the formatted result.
- The raw format is easier to post-process (for instance, to filter
- warnings or to output HTML pages).
- --src
- Override the default, which is to analyze BEAM files, and
- analyze starting from Erlang source code instead.
- -Dname (or -Dname=value)
- When analyzing from source, pass the define to Dialyzer. (**)
- -I include_dir
- When analyzing from source, pass the include_dir to Dialyzer. (**)
- -pa dir
- Include dir in the path for Erlang (useful when analyzing files
- that have '-include_lib()' directives).
- --output_plt file
- Store the plt at the specified file after building it.
- --plt plt
- Use the specified plt as the initial plt (if the plt was built
- during setup the files will be checked for consistency).
- --plts plt*
- Merge the specified plts to create the initial plt -- requires
- that the plts are disjoint (i.e., do not have any module
- appearing in more than one plt).
- The plts are created in the usual way:
- dialyzer --build_plt --output_plt plt_1 files_to_include
- ...
- dialyzer --build_plt --output_plt plt_n files_to_include
- and then can be used in either of the following ways:
- dialyzer files_to_analyze --plts plt_1 ... plt_n
- or:
- dialyzer --plts plt_1 ... plt_n -- files_to_analyze
- (Note the -- delimiter in the second case)
- -Wwarn
- A family of options which selectively turn on/off warnings
- (for help on the names of warnings use dialyzer -Whelp).
- --shell
- Do not disable the Erlang shell while running the GUI.
- --version (or -v)
- Print the Dialyzer version and some more information and exit.
- --help (or -h)
- Print this message and exit.
- --quiet (or -q)
- Make Dialyzer a bit more quiet.
- --verbose
- Make Dialyzer a bit more verbose.
- --statistics
- Prints information about the progress of execution (analysis phases,
- time spent in each and size of the relative input).
- --build_plt
- The analysis starts from an empty plt and creates a new one from the
- files specified with -c and -r. Only works for beam files.
- Use --plt(s) or --output_plt to override the default plt location.
- --add_to_plt
- The plt is extended to also include the files specified with -c and -r.
- Use --plt(s) to specify which plt to start from, and --output_plt to
- specify where to put the plt. Note that the analysis might include
- files from the plt if they depend on the new files.
- This option only works with beam files.
- --remove_from_plt
- The information from the files specified with -c and -r is removed
- from the plt. Note that this may cause a re-analysis of the remaining
- dependent files.
- --check_plt
- Check the plt for consistency and rebuild it if it is not up-to-date.
- Actually, this option is of rare use as it is on by default.
- --no_check_plt (or -n)
- Skip the plt check when running Dialyzer. Useful when working with
- installed plts that never change.
- --plt_info
- Make Dialyzer print information about the plt and then quit. The plt
- can be specified with --plt(s).
- --get_warnings
- Make Dialyzer emit warnings even when manipulating the plt. Warnings
- are only emitted for files that are actually analyzed.
- --dump_callgraph file
- Dump the call graph into the specified file whose format is determined
- by the file name extension. Supported extensions are: raw, dot, and ps.
- If something else is used as file name extension, default format '.raw'
- will be used.
- --error_location column | line
- Use a pair {Line, Column} or an integer Line to pinpoint the location
- of warnings. The default is to use a pair {Line, Column}. When
- formatted, the line and the column are separated by a colon.
- --fullpath
- Display the full path names of files for which warnings are emitted.
- --no_indentation
- Do not indent contracts and success typings. Note that this option has
- no effect when combined with the --raw option.
- --gui
- Use the GUI.
-
+parse_app(AppOrDir) ->
+ case code:lib_dir(list_to_atom(AppOrDir)) of
+ {error, bad_name} -> AppOrDir;
+ LibDir when AppOrDir =:= "erts" -> % hack for including erts in an un-installed system
+ EbinDir = filename:join([LibDir, "ebin"]),
+ case file:read_file_info(EbinDir) of
+ {error, enoent} ->
+ filename:join([LibDir, "preloaded", "ebin"]);
+ _ ->
+ EbinDir
+ end;
+ LibDir -> filename:join(LibDir, "ebin")
+ end.
+
+parse_input_list(File) ->
+ case file:read_file(File) of
+ {ok, Bin} ->
+ Files = binary:split(Bin, <<"\n">>, [trim_all, global]),
+ [binary_to_list(string:trim(F)) || F <- Files];
+ {error, Reason} ->
+ cl_error(io_lib:format("Reading of ~s failed: ~s", [File, file:format_error(Reason)]))
+ end.
+
+parse_define(Arg) ->
+ case re:split(Arg, "=", [{return, list}, unicode]) of
+ [Def, Val] ->
+ {ok, Tokens, _} = erl_scan:string(Val++"."),
+ {ok, ErlVal} = erl_parse:parse_term(Tokens),
+ {list_to_atom(Def), ErlVal};
+ [Def] ->
+ {list_to_atom(Def), true}
+ end.
+
+cli() ->
+ #{
+ arguments => [
+ #{name => files, action => extend, nargs => list, required => false,
+ help => <<"Use Dialyzer from the command line to detect defects in the "
+ "specified files or directories containing .erl or .beam files, "
+ "depending on the type of the analysis.">>},
+ #{name => files, short => $c, long => "-com", action => extend, nargs => list,
+ help => <<"Same as files, specifies files to run the analysis on (left for compatibility)">>},
+ #{name => files_rec, short => $r, action => extend, nargs => list,
+ help => <<"Search the specified directories "
+ "recursively for subdirectories containing .erl or .beam files in "
+ "them, depending on the type of analysis.">>},
+ #{name => files, long => "-input_list_file", type => {custom, fun parse_input_list/1},
+ action => extend,
+ help => <<"Specify the name of a file that contains the names of the files "
+ "to be analyzed (one file name per line).">>},
+ #{name => files_rec, long => "-apps", type => {custom, fun parse_app/1},
+ nargs => list, action => extend,
+ help => <<"Option typically used when building or modifying a plt as in: \n"
+ "dialyzer --build_plt --apps erts kernel stdlib mnesia ... \n"
+ "to conveniently refer to library applications corresponding to the "
+ "Erlang/OTP installation. However, the option is general and can also "
+ "be used during analysis in order to refer to Erlang/OTP applications. "
+ "In addition, file or directory names can also be included, as in: \n"
+ "dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam">>},
+
+ #{name => output_file, short => $o, long => "--output",
+ help => <<"When using Dialyzer from the command line, send the analysis "
+ "results to the specified outfile rather than to stdout.">>},
+ #{name => output_format, long => "-raw", type => boolean, action => {store, raw},
+ help => <<"When using Dialyzer from the command line, output the raw analysis "
+ "results (Erlang terms) instead of the formatted result. "
+ "The raw format is easier to post-process (for instance, to filter "
+ "warnings or to output HTML pages).">>},
+ #{name => from, long => "-src", type => boolean, action => {store, src_code},
+ help => <<"Override the default, which is to analyze BEAM files, and "
+ "analyze starting from Erlang source code instead.">>},
+ #{name => defines, short=>$D, type => {custom, fun parse_define/1}, action => append,
+ help => <<"When analyzing from source, pass the define to Dialyzer. (**)">>},
+ #{name => include_dirs, short=>$I, action => append,
+ help => <<"When analyzing from source, pass the include_dir to Dialyzer. (**)">>},
+ #{name => pa, long => "pa", action => append,
+ help => <<"Include dir in the path for Erlang (useful when analyzing files "
+ "that have '-include_lib()' directives).">>},
+ #{name => output_plt, long => "-output_plt",
+ help => <<"Store the plt at the specified file after building it.">>},
+ #{name => plts, long => "-plt", nargs => 1,
+ help => <<"Use the specified plt as the initial plt (if the plt was built "
+ "during setup the files will be checked for consistency).">>},
+ #{name => plts, long => "-plts", nargs => nonempty_list,
+ help => <<"Merge the specified plts to create the initial plt -- requires "
+ "that the plts are disjoint (i.e., do not have any module "
+ "appearing in more than one plt). "
+ "The plts are created in the usual way: \n"
+ " dialyzer --build_plt --output_plt plt_1 files_to_include "
+ " ... \n"
+ " dialyzer --build_plt --output_plt plt_n files_to_include "
+ "and then can be used in either of the following ways: \n"
+ " dialyzer files_to_analyze --plts plt_1 ... plt_n \n"
+ "or: \n"
+ " dialyzer --plts plt_1 ... plt_n -- files_to_analyze \n"
+ "(Note the -- delimiter in the second case)">>},
+ #{name => warnings, short => $W, action => append, type => {atom, [error_handling,
+ no_behaviours, no_contracts, no_fail_call, no_fun_app, no_improper_lists,
+ no_match, no_missing_calls, no_opaque, no_return, no_undefined_callbacks,
+ no_underspecs, no_unknown, no_unused, underspecs, unknown, unmatched_returns,
+ overspecs, specdiffs, extra_return, no_extra_return, missing_return, no_missing_return]},
+ help => {<<"[-Wwarn]*">>, [<<"A family of options which selectively turn on/off warnings">>]}},
+ #{name => shell, long => "-shell", type => boolean,
+ help => <<"Do not disable the Erlang shell while running the GUI.">>},
+ #{name => version, short => $v, long => "-version", type => boolean,
+ help => <<"Print the Dialyzer version and some more information and exit.">>},
+ #{name => help, short => $h, long => "-help", type => boolean,
+ help => <<"Print this message and exit.">>},
+ #{name => report_mode, short => $q, long => "-quiet", type => boolean, action => {store, quiet},
+ default => normal, help => <<"Make Dialyzer a bit more quiet.">>},
+ #{name => report_mode, long => "-verbose", type => boolean, action => {store, verbose},
+ help => <<"Make Dialyzer a bit more verbose.">>},
+ #{name => timing, long => "-statistics", type => boolean,
+ help => <<"Prints information about the progress of execution (analysis phases, "
+ "time spent in each and size of the relative input).">>},
+ #{name => analysis_type, long => "-build_plt", type => boolean, action => {store, plt_build},
+ help => <<"The analysis starts from an empty plt and creates a new one from the "
+ "files specified with -c and -r. Only works for beam files. "
+ "Use --plt(s) or --output_plt to override the default plt location.">>},
+ #{name => analysis_type, long=> "-add_to_plt", type => boolean, action => {store, plt_add},
+ help => <<"The plt is extended to also include the files specified with -c and -r. "
+ "Use --plt(s) to specify which plt to start from, and --output_plt to "
+ "specify where to put the plt. Note that the analysis might include "
+ "files from the plt if they depend on the new files. "
+ "This option only works with beam files.">>},
+ #{name => analysis_type, long => "-remove_from_plt", type => boolean, action => {store, plt_remove},
+ help => <<"The information from the files specified with -c and -r is removed "
+ "from the plt. Note that this may cause a re-analysis of the remaining "
+ "dependent files.">>},
+ #{name => analysis_type, long => "-check_plt", type => boolean, action => {store, plt_check},
+ help => <<"Check the plt for consistency and rebuild it if it is not up-to-date. "
+ "Actually, this option is of rare use as it is on by default.">>},
+ #{name => check_plt, long => "-no_check_plt", short => $n, type => boolean, action => {store, false},
+ help => <<"Skip the plt check when running Dialyzer. Useful when working with "
+ "installed plts that never change.">>},
+ #{name => analysis_type, long => "-incremental", type => boolean, action => {store, incremental},
+ help => <<"The analysis starts from an existing incremental PLT, or builds one from "
+ "scratch if one doesn't exist, and runs the minimal amount of additional "
+ "analysis to report all issues in the given set of apps. Notably, incremental "
+ "PLT files are not compatible with \"classic\" PLT files, and vice versa. "
+ "The initial incremental PLT will be updated unless an alternative output "
+ "incremental PLT is given.">>},
+ #{name => analysis_type, long => "-plt_info", type => boolean, action => {store, plt_info},
+ help => <<"Make Dialyzer print information about the plt and then quit. The plt "
+ "can be specified with --plt(s).">>},
+ #{name => get_warnings, long => "-get_warnings", type => boolean,
+ help => <<"Make Dialyzer emit warnings even when manipulating the plt. Warnings "
+ "are only emitted for files that are actually analyzed.">>},
+ #{name => callgraph_file, long => "-dump_callgraph",
+ help => <<"Dump the call graph into the specified file whose format is determined "
+ "by the file name extension. Supported extensions are: raw, dot, and ps. "
+ "If something else is used as file name extension, default format '.raw' "
+ "will be used.">>},
+ #{name => mod_deps_file, long => "-dump_full_dependencies_graph",
+ help => <<"Dump the full dependency graph (i.e. dependencies induced by function "
+ "calls, usages of types in specs, behaviour implementations, etc.) into "
+ "the specified file whose format is determined by the file name "
+ "extension. Supported extensions are: dot and ps.">>},
+ #{name => error_location, long => "-error_location", type => {atom, [column, line]},
+ help => <<"Use a pair {Line, Column} or an integer Line to pinpoint the location "
+ "of warnings. The default is to use a pair {Line, Column}. When "
+ "formatted, the line and the column are separated by a colon.">>},
+ #{name => filename_opt, long => "-fullpath", type => boolean, action => {store, fullpath},
+ help => <<"Display the full path names of files for which warnings are emitted.">>},
+ #{name => indent_opt, long => "-no_indentation", type => boolean, action => {store, false},
+ help => <<"Do not indent contracts and success typings. Note that this option has "
+ "no effect when combined with the --raw option.">>},
+ #{name => gui, long => "-gui", type => boolean,
+ help => <<"Use the GUI.">>},
+ #{name => metrics_file, long => "-metrics_file",
+ help => <<"Write metrics about Dialyzer's incrementality (for example, total number of "
+ "modules considered, how many modules were changed since the PLT was "
+ "last updated, how many modules needed to be analyzed) to a file. This "
+ "can be useful for tracking and debugging Dialyzer's incrementality.">>},
+ #{name => warning_files_rec, long => "-warning_apps", type => {custom, fun parse_app/1},
+ nargs => list, action => extend,
+ help => <<"By default, warnings will be reported to all applications given by "
+ "--apps. However, if --warning_apps is used, only those applications "
+ "given to --warning_apps will have warnings reported. All applications "
+ "given by --apps, but not --warning_apps, will be analysed to provide "
+ "context to the analysis, but warnings will not be reported for them. "
+ "For example, you may want to include libraries you depend on in the "
+ "analysis with --apps so discrepancies in their usage can be found, "
+ "but only include your own code with --warning_apps so that "
+ "discrepancies are only reported in code that you own.">>},
+
+ %% Intentionally undocumented options
+ #{name => solvers, long => "-solver", type => {atom, [v1, v2]}, action => append,
+ help => hidden},
+ #{name => timing, long => "-resources", type => boolean, action => {store, debug},
+ help => hidden},
+
+ %% next definition is necessary to ignore '--' left for compatibility reasons
+ #{name => shell, short => $-, type => boolean, help => hidden}
+ ],
+
+ help => [<<"Usage: ">>, usage, <<"\n\nOptions:\n">>,
+ arguments, options, "
Note:
* denotes that multiple occurrences of these options are possible.
** options -D and -I work both from command-line and in the Dialyzer GUI;
the syntax of defines and includes is the same as that used by \"erlc\".
" ++ warning_options_msg() ++ "
+" ++ configuration_file_msg() ++ "
+
The exit status of the command line version is:
0 - No problems were encountered during the analysis and no
warnings were emitted.
1 - Problems were encountered during the analysis.
2 - No problems were encountered, but warnings were emitted.
-",
- io:put_chars(S),
- erlang:halt(?RET_NOTHING_SUSPICIOUS).
+
+"]
+ }.
+
+postprocess_side_effects(ArgMap) when is_map_key(version, ArgMap) ->
+ %% Version handling
+ io:format("Dialyzer version " ++ ?VSN ++ "\n"),
+ erlang:halt(?RET_NOTHING_SUSPICIOUS);
+
+postprocess_side_effects(ArgMap) when is_map_key(help, ArgMap) ->
+ %% Help message
+ io:format(argparse:help(cli(), #{progname => dialyzer})),
+ erlang:halt(?RET_NOTHING_SUSPICIOUS);
+
+postprocess_side_effects(ArgMap) when is_map_key(pa, ArgMap) ->
+ %% Code path side effect
+ [code:add_patha(Path) =/= true andalso cl_error("Bad directory for -pa: " ++ Path) ||
+ Path <- map_get(pa, ArgMap)],
+ postprocess_side_effects(maps:remove(pa, ArgMap));
+
+postprocess_side_effects(ArgMap) when is_map_key(shell, ArgMap) ->
+ %% --shell option is processed by C executable (left here only for help/usage)
+ postprocess_side_effects(maps:remove(shell, ArgMap));
+
+postprocess_side_effects(ArgMap) ->
+ %% if all files specified are ".erl" files, set the 'src' flag automatically
+ %% it is compatibility behaviour, potentially incorrect, because it does not take
+ %% directories (rec_files) into account
+ ArgMap1 =
+ case (is_map_key(files, ArgMap) andalso
+ lists:all(fun(F) -> filename:extension(F) =:= ".erl" end, maps:get(files, ArgMap))) of
+ true ->
+ ArgMap#{from => src_code};
+ false ->
+ ArgMap
+ end,
+
+ %% Run mode (command) is defined by the flag combination
+ case maps:get(analysis_type, ArgMap1, undefined) of
+ plt_info ->
+ %% plt_info is plt_check analysis type
+ {plt_info, ArgMap1#{analysis_type => plt_check}};
+ plt_check ->
+ %% plt_check is a hidden "check_init" command
+ {check_init, ArgMap1};
+ _ when map_get(gui, ArgMap1) ->
+ %% filter out command-line only arguments
+ Allowed = [defines, from, include_dirs, plts, output_plt, report_mode,
+ use_spec, warnings, check_plt, solvers],
+ {gui, maps:with(Allowed, ArgMap1)};
+ _ ->
+ {cl, ArgMap1}
+ end.
+
+cl_error(Str) ->
+ Msg = lists:flatten(Str),
+ throw({dialyzer_cl_parse_error, Msg}).
warning_options_msg() ->
"Warning options:
@@ -557,6 +343,11 @@ warning_options_msg() ->
-Wno_undefined_callbacks
Suppress warnings about behaviours that have no -callback attributes for
their callbacks.
+ -Wno_unknown
+ Suppress warnings about unknown functions and types. The default is to
+ warn about unknown functions and types when setting the exit
+ status. When using Dialyzer from Erlang, warnings about unknown functions
+ and types are returned.
-Wunmatched_returns ***
Include warnings for function calls which ignore a structured return
value or do not match against one of many possible return value(s).
@@ -571,13 +362,8 @@ warning_options_msg() ->
-Wmissing_return ***
Warn about functions that return values that are not part
of the specification.
- -Wunknown ***
- Let warnings about unknown functions and types affect the
- exit status of the command line version. The default is to ignore
- warnings about unknown functions and types when setting the exit
- status. When using the Dialyzer from Erlang, warnings about unknown
- functions and types are returned; the default is not to return
- such warnings.
+ -Woverlapping_contract ***
+ Warn about overloaded functions whose specification include types that overlap.
The following options are also available but their use is not recommended:
(they are mostly for Dialyzer developers and internal debugging)
@@ -599,3 +385,29 @@ They are primarily intended to be used with the -dialyzer attribute:
-Wno_missing_return
Suppress warnings about functions that return values that are not part of the specification.
".
+
+configuration_file_msg() ->
+ "Configuration file:
+ Dialyzer's configuration file may also be used to augment the default
+ options and those given directly to the Dialyzer command. It is commonly
+ used to avoid repeating options which would otherwise need to be given
+ explicitly to Dialyzer on every invocation.
+
+ The location of the configuration file can be set via the
+ DIALYZER_CONFIG environment variable, and defaults to
+ within the user_config location given by filename:basedir/3.
+
+ On your system, the location is currently configured as:
+ " ++ dialyzer_options:get_default_config_filename() ++
+ "
+
+ An example configuration file's contents might be:
+
+ {incremental,
+ {default_apps,[stdlib,kernel,erts]},
+ {default_warning_apps,[stdlib]}
+ }.
+ {warnings, [no_improper_lists]}.
+ {add_pathsa,[\"/users/samwise/potatoes/ebin\"]}.
+ {add_pathsz,[\"/users/smeagol/fish/ebin\"]}.
+".
diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl
index 9b8a165dd0..7c0abd6451 100644
--- a/lib/dialyzer/src/dialyzer_codeserver.erl
+++ b/lib/dialyzer/src/dialyzer_codeserver.erl
@@ -22,40 +22,41 @@
-module(dialyzer_codeserver).
-export([delete/1,
- store_temp_contracts/4,
+ store_temp_contracts/4,
give_away/2,
- finalize_contracts/1,
+ finalize_contracts/1,
finalize_exported_types/2,
- finalize_records/1,
- get_contracts/1,
- get_callbacks/1,
+ finalize_records/1,
+ get_contracts/1,
+ get_callbacks/1,
get_exported_types_table/1,
extract_exported_types/1,
- get_exports/1,
- get_records_table/1,
+ get_exports/1,
+ get_records_table/1,
extract_records/1,
- get_next_core_label/1,
+ get_next_core_label/1,
get_temp_contracts/2,
all_temp_modules/1,
store_contracts/4,
get_temp_exported_types/1,
get_temp_records_table/1,
- lookup_temp_mod_records/2,
- insert/3,
- insert_exports/2,
+ lookup_temp_mod_records/2,
+ insert/3,
+ insert_exports/2,
insert_temp_exported_types/2,
insert_fun_meta_info/2,
- is_exported/2,
- lookup_mod_code/2,
- lookup_mfa_code/2,
- lookup_mfa_var_label/2,
- lookup_mod_records/2,
- lookup_mod_contracts/2,
- lookup_mfa_contract/2,
+ is_exported/2,
+ is_member_meta_info/2,
+ lookup_mod_code/2,
+ lookup_mfa_code/2,
+ lookup_mfa_var_label/2,
+ lookup_mod_records/2,
+ lookup_mod_contracts/2,
+ lookup_mfa_contract/2,
lookup_meta_info/2,
- new/0,
- set_next_core_label/2,
- store_temp_records/3,
+ new/0,
+ set_next_core_label/2,
+ store_temp_records/3,
translate_fake_file/3]).
-export_type([codeserver/0, fun_meta_info/0, contracts/0]).
@@ -96,11 +97,11 @@
%%--------------------------------------------------------------------
+%% We KNOW that `error` is not a valid value in the table.
ets_dict_find(Key, Table) ->
- try ets:lookup_element(Table, Key, 2) of
- Val -> {ok, Val}
- catch
- _:_ -> error
+ case ets:lookup_element(Table, Key, 2, error) of
+ error -> error;
+ Val -> {ok, Val}
end.
ets_map_store(Key, Element, Table) ->
@@ -112,7 +113,7 @@ ets_dict_to_dict(Table) ->
ets:foldl(Fold, dict:new(), Table).
ets_set_is_element(Key, Table) ->
- ets:lookup(Table, Key) =/= [].
+ ets:member(Table, Key).
ets_set_insert_set(Set, Table) ->
ets_set_insert_list(sets:to_list(Set), Table).
@@ -266,10 +267,7 @@ set_next_core_label(NCL, CS) ->
-spec lookup_mod_records(atom(), codeserver()) -> types().
lookup_mod_records(Mod, #codeserver{records = RecDict}) when is_atom(Mod) ->
- case ets_dict_find(Mod, RecDict) of
- error -> maps:new();
- {ok, Map} -> Map
- end.
+ ets:lookup_element(RecDict, Mod, 2, #{}).
-spec get_records_table(codeserver()) -> map_ets().
@@ -298,10 +296,7 @@ get_temp_records_table(#codeserver{temp_records = TempRecDict}) ->
-spec lookup_temp_mod_records(module(), codeserver()) -> types().
lookup_temp_mod_records(Mod, #codeserver{temp_records = TempRecDict}) ->
- case ets_dict_find(Mod, TempRecDict) of
- error -> maps:new();
- {ok, Map} -> Map
- end.
+ ets:lookup_element(TempRecDict, Mod, 2, #{}).
-spec finalize_records(codeserver()) -> codeserver().
@@ -320,14 +315,8 @@ finalize_records(#codeserver{temp_records = TmpRecords,
lookup_mod_contracts(Mod, #codeserver{contracts = ContDict})
when is_atom(Mod) ->
- case ets_dict_find(Mod, ContDict) of
- error -> maps:new();
- {ok, Keys} ->
- maps:from_list([get_file_contract(Key, ContDict)|| Key <- Keys])
- end.
-
-get_file_contract(Key, ContDict) ->
- {Key, ets:lookup_element(ContDict, Key, 2)}.
+ Keys = ets:lookup_element(ContDict, Mod, 2, []),
+ #{Key => ets:lookup_element(ContDict, Key, 2) || Key <- Keys}.
-spec lookup_mfa_contract(mfa(), codeserver()) ->
'error' | {'ok', dialyzer_contracts:file_contract()}.
@@ -341,6 +330,11 @@ lookup_mfa_contract(MFA, #codeserver{contracts = ContDict}) ->
lookup_meta_info(MorMFA, #codeserver{fun_meta_info = FunMetaInfo}) ->
ets_dict_find(MorMFA, FunMetaInfo).
+-spec is_member_meta_info(module() | mfa(), codeserver()) -> boolean().
+
+is_member_meta_info(MorMFA, #codeserver{fun_meta_info = FunMetaInfo}) ->
+ ets_set_is_element(MorMFA, FunMetaInfo).
+
-spec get_contracts(codeserver()) ->
dict:dict(mfa(), dialyzer_contracts:file_contract()).
diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl
index 067e7956f3..9623a6d6ff 100644
--- a/lib/dialyzer/src/dialyzer_contracts.erl
+++ b/lib/dialyzer/src/dialyzer_contracts.erl
@@ -185,7 +185,7 @@ process_contract_remote_types_module(ModuleName, CodeServer) ->
RecordTable = dialyzer_codeserver:get_records_table(CodeServer),
ExpTypes = dialyzer_codeserver:get_exported_types_table(CodeServer),
ContractFun =
- fun({MFA, {File, TmpContract, Xtra}}, C0) ->
+ fun({{_,_,_} = MFA, {File, TmpContract, Xtra}}, C0) ->
#tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract,
{NewCs, C2} = lists:mapfoldl(fun(CFun, C1) ->
CFun(ExpTypes, RecordTable, C1)
@@ -269,6 +269,7 @@ check_contracts(Contracts, Callgraph, FunTypes, ModOpaques) ->
'ok'
| {'error',
'invalid_contract'
+ | {'invalid_contract', {InvalidArgIdxs :: [pos_integer()], IsReturnTypeInvalid :: boolean()}}
| {'opaque_mismatch', erl_types:erl_type()}
| {'overlapping_contract', [module() | atom() | byte()]}
| string()}
@@ -299,12 +300,11 @@ check_contract(#contract{contracts = Contracts}, SuccType, Opaques) ->
ok ->
InfList = [{Contract, erl_types:t_inf(Contract, SuccType, Opaques)}
|| Contract <- Contracts2],
- case check_contract_inf_list(InfList, SuccType, Opaques) of
- {error, _} = Invalid -> Invalid;
+ case check_contract_inf_list(InfList, SuccType, Opaques) of
+ {error, _} = Invalid -> Invalid;
ok ->
case check_extraneous(Contracts2, SuccType, Opaques) of
- {error, invalid_contract} = Err ->
- Err;
+ {error, {invalid_contract, _}} = Err -> Err;
{error, {extra_range, _, _}} = Err ->
MissingError = check_missing(Contracts2, SuccType, Opaques),
{range_warnings, [Err | MissingError]};
@@ -320,6 +320,25 @@ check_contract(#contract{contracts = Contracts}, SuccType, Opaques) ->
throw:{error, _} = Error -> Error
end.
+locate_invalid_elems(InfList) ->
+ case InfList of
+ [{Contract, Inf}] ->
+ ArgComparisons = lists:zip(erl_types:t_fun_args(Contract),
+ erl_types:t_fun_args(Inf)),
+ ProblematicArgs =
+ [erl_types:t_is_none(Succ) andalso (not erl_types:t_is_none(Cont))
+ || {Cont,Succ} <- ArgComparisons],
+ ProblematicRange =
+ erl_types:t_is_none(erl_types:t_fun_range(Inf))
+ andalso (not erl_types:t_is_none(erl_types:t_fun_range(Contract))),
+ ProblematicArgIdxs = [Idx ||
+ {Idx, IsProblematic} <-
+ lists:enumerate(ProblematicArgs), IsProblematic],
+ {error, {invalid_contract, {ProblematicArgIdxs, ProblematicRange}}};
+ _ ->
+ {error, invalid_contract}
+ end.
+
check_domains([_]) -> ok;
check_domains([Dom|Doms]) ->
Fun = fun(D) ->
@@ -330,16 +349,19 @@ check_domains([Dom|Doms]) ->
false -> error
end.
+
%% Allow a contract if one of the overloaded contracts is possible.
%% We used to be more strict, e.g., all overloaded contracts had to be
%% possible.
check_contract_inf_list(List, SuccType, Opaques) ->
case check_contract_inf_list(List, SuccType, Opaques, []) of
ok -> ok;
- {error, []} -> {error, invalid_contract};
+ {error, []} ->
+ locate_invalid_elems(List);
{error, [{SigRange, ContrRange}|_]} ->
case erl_types:t_find_opaque_mismatch(SigRange, ContrRange, Opaques) of
- error -> {error, invalid_contract};
+ error ->
+ locate_invalid_elems(List);
{ok, _T1, T2} -> {error, {opaque_mismatch, T2}}
end
end.
@@ -383,13 +405,12 @@ check_extraneous_1(Contract, SuccType, Opaques) ->
case [CR || CR <- CRngs,
erl_types:t_is_none(erl_types:t_inf(CR, STRng, Opaques))] of
[] ->
- case bad_extraneous_list(CRng, STRng)
- orelse bad_extraneous_map(CRng, STRng)
- of
- true -> {error, invalid_contract};
- false -> ok
+ case bad_extraneous_list(CRng, STRng) orelse bad_extraneous_map(CRng, STRng) of
+ true -> {error, {invalid_contract, {[],true}}};
+ false -> ok
end;
- CRs -> {error, {extra_range, erl_types:t_sup(CRs), STRng}}
+ CRs ->
+ {error, {extra_range, erl_types:t_sup(CRs), STRng}}
end.
bad_extraneous_list(CRng, STRng) ->
@@ -819,7 +840,9 @@ get_invalid_contract_warnings_funs([{MFA, {FileLocation, Contract, _Xtra}}|Left]
NewAcc =
case check_contract(Contract, Sig, Opaques) of
{error, invalid_contract} ->
- [invalid_contract_warning(MFA, WarningInfo, Sig, RecDict)|Acc];
+ [invalid_contract_warning(MFA, WarningInfo, none, Contract, Sig, RecDict)|Acc];
+ {error, {invalid_contract, {_ProblematicArgIdxs, _IsRangeProblematic} = ProblemDetails}} ->
+ [invalid_contract_warning(MFA, WarningInfo, ProblemDetails, Contract, Sig, RecDict)|Acc];
{error, {opaque_mismatch, T2}} ->
W = contract_opaque_warning(MFA, WarningInfo, T2, Sig, RecDict),
[W|Acc];
@@ -864,7 +887,7 @@ get_invalid_contract_warnings_funs([{MFA, {FileLocation, Contract, _Xtra}}|Left]
BifSig = erl_types:t_fun(BifArgs, BifRet),
case check_contract(Contract, BifSig, Opaques) of
{error, _} ->
- [invalid_contract_warning(MFA, WarningInfo, BifSig, RecDict)
+ [invalid_contract_warning(MFA, WarningInfo, none, Contract, BifSig, RecDict)
|Acc];
{range_warnings, _} ->
picky_contract_check(CSig, BifSig, MFA, WarningInfo,
@@ -883,9 +906,10 @@ get_invalid_contract_warnings_funs([{MFA, {FileLocation, Contract, _Xtra}}|Left]
get_invalid_contract_warnings_funs([], _Plt, _RecDict, _Opaques, Acc) ->
Acc.
-invalid_contract_warning({M, F, A}, WarningInfo, SuccType, RecDict) ->
- SuccTypeStr = dialyzer_utils:format_sig(SuccType, RecDict),
- {?WARN_CONTRACT_TYPES, WarningInfo, {invalid_contract, [M, F, A, SuccTypeStr]}}.
+invalid_contract_warning({M, F, A}, WarningInfo, ProblemDetails, Contract, SuccType, RecDict) ->
+ SuccTypeStr = lists:flatten(dialyzer_utils:format_sig(SuccType, RecDict)),
+ ContractTypeStr = contract_to_string(Contract),
+ {?WARN_CONTRACT_TYPES, WarningInfo, {invalid_contract, [M, F, A, ProblemDetails, ContractTypeStr, SuccTypeStr]}}.
contract_opaque_warning({M, F, A}, WarningInfo, OpType, SuccType, RecDict) ->
OpaqueStr = erl_types:t_to_string(OpType),
@@ -894,7 +918,7 @@ contract_opaque_warning({M, F, A}, WarningInfo, OpType, SuccType, RecDict) ->
{contract_with_opaque, [M, F, A, OpaqueStr, SuccTypeStr]}}.
overlapping_contract_warning({M, F, A}, WarningInfo) ->
- {?WARN_CONTRACT_TYPES, WarningInfo, {overlapping_contract, [M, F, A]}}.
+ {?WARN_OVERLAPPING_CONTRACT, WarningInfo, {overlapping_contract, [M, F, A]}}.
extra_range_warning({M, F, A}, WarningInfo, ExtraRanges, STRange) ->
ERangesStr = erl_types:t_to_string(ExtraRanges),
diff --git a/lib/dialyzer/src/dialyzer_coordinator.erl b/lib/dialyzer/src/dialyzer_coordinator.erl
index 2ca3acc4cb..085c4e938c 100644
--- a/lib/dialyzer/src/dialyzer_coordinator.erl
+++ b/lib/dialyzer/src/dialyzer_coordinator.erl
@@ -27,6 +27,9 @@
%%% Exports for the typesig and dataflow analysis workers
-export([wait_for_success_typings/2]).
+%% Exports to handle SCC labels
+-export([get_job_label/2, get_job_input/2]).
+
%%% Exports for the compilation workers
-export([get_next_label/2]).
@@ -36,9 +39,9 @@
-type collector() :: pid().
-type regulator() :: pid().
--type scc_to_pid() :: ets:tid() | 'none'.
+-type job_labels_to_pid() :: ets:tid() | 'none'.
--opaque coordinator() :: {collector(), regulator(), scc_to_pid()}.
+-opaque coordinator() :: {collector(), regulator(), job_labels_to_pid()}.
-type timing() :: dialyzer_timing:timing_server().
-type scc() :: [mfa_or_funlbl()].
@@ -46,7 +49,7 @@
'contract_remote_types' | 'record_remote_types'.
-type compile_job() :: file:filename().
--type typesig_job() :: scc().
+-type typesig_job() :: {integer(),scc()}.
-type dataflow_job() :: module().
-type warnings_job() :: module().
-type contract_remote_types_job() :: module().
@@ -98,7 +101,7 @@
job_fun :: fun(),
init_data :: init_data(),
regulator :: regulator(),
- scc_to_pid :: scc_to_pid()
+ job_labels_to_pid :: job_labels_to_pid()
}).
-include("dialyzer.hrl").
@@ -128,49 +131,26 @@ parallel_job(Mode, Jobs, InitData, Timing) ->
%%--------------------------------------------------------------------
%% API functions for workers (dialyzer_worker).
--spec request_activation(coordinator()) -> ok.
-
-request_activation({_Collector, Regulator, _SCCtoPid}) ->
- Regulator ! {req, self()},
- wait_activation().
-
--spec job_done(job(), job_result(), coordinator()) -> ok.
-
-job_done(Job, Result, {Collector, Regulator, _SCCtoPid}) ->
- Regulator ! done,
- Collector ! {done, Job, Result},
- ok.
-
--spec get_next_label(integer(), coordinator()) -> integer().
-
-%% For the 'compile' worker.
-get_next_label(EstimatedSize, {Collector, _Regulator, _SCCtoPid}) ->
- Collector ! {next_label_request, EstimatedSize, self()},
- receive
- {next_label_reply, NextLabel} -> NextLabel
- end.
-
--spec wait_for_success_typings([scc() | module()], coordinator()) ->
+-spec wait_for_success_typings([job_label()], coordinator()) ->
'ok'.
%% Helper for 'sigtype' and 'dataflow' workers.
-wait_for_success_typings(SCCs, {_Collector, _Regulator, SCCtoPid}) ->
- F = fun(SCC) ->
- %% The SCCs that SCC depends on have always been started.
- try ets:lookup_element(SCCtoPid, SCC, 2) of
+wait_for_success_typings(Labels, {_Collector, _Regulator, JobLabelsToPid}) ->
+ F = fun(JobLabel) ->
+ %% The jobs that job depends on have always been started.
+ case ets:lookup_element(JobLabelsToPid, JobLabel, 2, ok) of
Pid when is_pid(Pid) ->
Ref = erlang:monitor(process, Pid),
receive
{'DOWN', Ref, process, Pid, _Info} ->
ok
- end
- catch
- _:_ ->
+ end;
+ ok ->
%% Already finished.
ok
end
end,
- lists:foreach(F, SCCs).
+ lists:foreach(F, Labels).
%%--------------------------------------------------------------------
@@ -179,18 +159,16 @@ wait_for_success_typings(SCCs, {_Collector, _Regulator, SCCtoPid}) ->
spawn_jobs(Mode, Jobs, InitData, Timing) ->
Collector = self(),
Regulator = spawn_regulator(),
-
- SCCtoPid =
+ JobLabelsToPid =
if
Mode =:= 'typesig'; Mode =:= 'dataflow' ->
- ets:new(scc_to_pid, [{read_concurrency, true}]);
+ ets:new(job_labels_to_pid, [{read_concurrency, true}]);
true ->
none
end,
+ Coordinator = {Collector, Regulator, JobLabelsToPid},
- Coordinator = {Collector, Regulator, SCCtoPid},
-
- JobFun = job_fun(SCCtoPid, Mode, InitData, Coordinator),
+ JobFun = job_fun(JobLabelsToPid, Mode, InitData, Coordinator),
%% Limit the number of processes we start in order to save memory.
MaxNumberOfInitJobs = 20 * dialyzer_utils:parallelism(),
@@ -212,7 +190,8 @@ spawn_jobs(Mode, Jobs, InitData, Timing) ->
#state{mode = Mode, active = JobCount, result = InitResult,
next_label = 0, job_fun = JobFun, jobs = RestJobs,
- init_data = InitData, regulator = Regulator, scc_to_pid = SCCtoPid}.
+ init_data = InitData, regulator = Regulator,
+ job_labels_to_pid = JobLabelsToPid}.
launch_jobs(Jobs, _JobFun, 0) ->
Jobs;
@@ -227,17 +206,18 @@ job_fun(none, Mode, InitData, Coordinator) ->
_ = dialyzer_worker:launch(Mode, Job, InitData, Coordinator),
ok
end;
-job_fun(SCCtoPid, Mode, InitData, Coordinator) ->
+job_fun(JobLabelsToPid, Mode, InitData, Coordinator) ->
fun(Job) ->
+ JobLabel = get_job_label(Mode, Job),
Pid = dialyzer_worker:launch(Mode, Job, InitData, Coordinator),
- true = ets:insert(SCCtoPid, {Job, Pid}),
+ true = ets:insert(JobLabelsToPid, {JobLabel, Pid}),
ok
end.
collect_result(#state{mode = Mode, active = Active, result = Result,
next_label = NextLabel, init_data = InitData,
jobs = JobsLeft, job_fun = JobFun,
- regulator = Regulator, scc_to_pid = SCCtoPid} = State) ->
+ regulator = Regulator, job_labels_to_pid = JobLabelsToPID} = State) ->
receive
{next_label_request, Estimation, Pid} ->
Pid ! {next_label_reply, NextLabel},
@@ -253,15 +233,15 @@ collect_result(#state{mode = Mode, active = Active, result = Result,
{NewResult, NextLabel};
_ ->
if
- SCCtoPid =:= none -> ok;
- true -> ets:delete(SCCtoPid)
+ JobLabelsToPID =:= none -> ok;
+ true -> ets:delete(JobLabelsToPID)
end,
NewResult
end;
N ->
if
- SCCtoPid =:= none -> ok;
- true -> true = ets:delete(SCCtoPid, Job)
+ JobLabelsToPID =:= none -> ok;
+ true -> true = ets:delete(JobLabelsToPID, get_job_label(Mode, Job))
end,
NewJobsLeft =
case JobsLeft of
@@ -288,6 +268,44 @@ update_result(Mode, InitData, Job, Data, Result) ->
Data ++ Result
end.
+
+-type job_label() :: integer() | module().
+
+-type job_input() :: scc() | module().
+
+-spec get_job_label(mode(), job()) -> job_label().
+
+get_job_label(typesig, {Label, _Input}) -> Label;
+get_job_label(dataflow, Job) -> Job;
+get_job_label(contract_remote_types, Job) -> Job;
+get_job_label(record_remote_types, Job) -> Job;
+get_job_label(warnings, Job) -> Job;
+get_job_label(compile, Job) -> Job.
+
+-spec get_job_input(mode(), job()) -> job_input().
+
+get_job_input(typesig, {_Label, Input}) -> Input;
+get_job_input(dataflow, Job) -> Job;
+get_job_input(contract_remote_types, Job) -> Job;
+get_job_input(record_remote_types, Job) -> Job;
+get_job_input(warnings, Job) -> Job;
+get_job_input(compile, Job) -> Job.
+
+-spec job_done(job(), job_result(), coordinator()) -> ok.
+
+job_done(Job, Result, {Collector, Regulator, _JobLabelsToPID}) ->
+ Regulator ! done,
+ Collector ! {done, Job, Result},
+ ok.
+
+-spec get_next_label(integer(), coordinator()) -> integer().
+
+get_next_label(EstimatedSize, {Collector, _Regulator, _JobLabelsToPID}) ->
+ Collector ! {next_label_request, EstimatedSize, self()},
+ receive
+ {next_label_reply, NextLabel} -> NextLabel
+ end.
+
%%--------------------------------------------------------------------
%% The regulator server
%%
@@ -304,6 +322,12 @@ wait_activation() ->
activate_pid(Pid) ->
Pid ! activate.
+-spec request_activation(coordinator()) -> ok.
+
+request_activation({_Collector, Regulator, _JobLabelsToPID}) ->
+ Regulator ! {req, self()},
+ wait_activation().
+
spawn_regulator() ->
InitTickets = dialyzer_utils:parallelism(),
spawn_link(fun() -> regulator_loop(InitTickets, queue:new()) end).
diff --git a/lib/dialyzer/src/dialyzer_cplt.erl b/lib/dialyzer/src/dialyzer_cplt.erl
new file mode 100644
index 0000000000..dcd200adb5
--- /dev/null
+++ b/lib/dialyzer/src/dialyzer_cplt.erl
@@ -0,0 +1,545 @@
+%% -*- erlang-indent-level: 2 -*-
+%%
+%% 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.
+
+%%%-------------------------------------------------------------------
+%%% File : dialyzer_cplt.erl
+%%% Author : Tobias Lindahl <tobiasl@it.uu.se>
+%%% Description : Interface to display information in the persistent
+%%% lookup table files.
+%%% This file handles persistent of "classic" PLT
+%%% files (rather than incremental ones). It intentionally
+%%% duplicates dialyzer_iplt.erl, since they are
+%%% expected to diverge, and for this file to potentially
+%%% be deprecated in favour of the incremental one at
+%%% some point in the future.
+%%%
+%%% Created : 23 Jul 2004 by Tobias Lindahl <tobiasl@it.uu.se>
+%%%-------------------------------------------------------------------
+-module(dialyzer_cplt).
+
+-export([check_plt/3,
+ compute_md5_from_files/1,
+ included_files/1,
+ from_file/1,
+ get_default_cplt_filename/0,
+ merge_plts_or_report_conflicts/2,
+ plt_and_info_from_file/1,
+ to_file/4,
+ is_cplt/1
+ ]).
+
+%% Debug utilities
+-export([pp_non_returning/0, pp_mod/1]).
+
+-export_type([file_md5/0]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+
+%%----------------------------------------------------------------------
+
+-type deep_string() :: string() | [deep_string()].
+
+%%----------------------------------------------------------------------
+
+
+-include("dialyzer.hrl").
+
+-type file_md5() :: {file:filename(), binary()}.
+
+-record(file_plt, {version = "" :: string(),
+ file_md5_list = [] :: [file_md5()],
+ info = dict:new() :: dict:dict(),
+ contracts = dict:new() :: dict:dict(),
+ callbacks = dict:new() :: dict:dict(),
+ types = dict:new() :: dict:dict(),
+ exported_types = sets:new() :: sets:set(),
+ mod_deps :: dialyzer_callgraph:mod_deps(),
+ implementation_md5 = [] :: [file_md5()]}).
+
+%%----------------------------------------------------------------------
+
+-spec get_default_cplt_filename() -> file:filename().
+
+get_default_cplt_filename() ->
+ case os:getenv("DIALYZER_PLT") of
+ false ->
+ CacheDir = filename:basedir(user_cache, "erlang"),
+ filename:join(CacheDir, ".dialyzer_plt");
+ UserSpecPlt -> UserSpecPlt
+ end.
+
+-spec plt_and_info_from_file(file:filename()) -> {dialyzer_plt:plt(), #plt_info{}}.
+
+plt_and_info_from_file(FileName) ->
+ from_file(FileName, true).
+
+-spec from_file(file:filename()) -> dialyzer_plt:plt().
+
+from_file(FileName) ->
+ from_file(FileName, false).
+
+from_file(FileName, ReturnInfo) ->
+ Plt = dialyzer_plt:new(),
+ Fun = fun() -> from_file1(Plt, FileName, ReturnInfo) end,
+ case subproc(Fun) of
+ {ok, Return} ->
+ Return;
+ {error, Msg} ->
+ dialyzer_plt:delete(Plt),
+ plt_error(Msg)
+ end.
+
+from_file1(Plt, FileName, ReturnInfo) ->
+ case get_record_from_file(FileName) of
+ {ok, Rec} ->
+ case check_version(Rec) of
+ error ->
+ Msg = io_lib:format("Old PLT file ~ts\n", [FileName]),
+ {error, Msg};
+ ok ->
+ #file_plt{info = FileInfo,
+ contracts = FileContracts,
+ callbacks = FileCallbacks,
+ types = FileTypes,
+ exported_types = FileExpTypes} = Rec,
+ Types = [{Mod, maps:from_list(dict:to_list(Types))} ||
+ {Mod, Types} <- dict:to_list(FileTypes)],
+ CallbacksList = dict:to_list(FileCallbacks),
+ CallbacksByModule =
+ [{M, [Cb || {{M1,_,_},_} = Cb <- CallbacksList, M1 =:= M]} ||
+ M <- lists:usort([M || {{M,_,_},_} <- CallbacksList])],
+ #plt{info = ETSInfo,
+ types = ETSTypes,
+ contracts = ETSContracts,
+ callbacks = ETSCallbacks,
+ exported_types = ETSExpTypes} = Plt,
+ [true, true, true] =
+ [ets:insert(ETS, Data) ||
+ {ETS, Data} <- [{ETSInfo, dict:to_list(FileInfo)},
+ {ETSTypes, Types},
+ {ETSContracts, dict:to_list(FileContracts)}]],
+ true = ets:insert(ETSCallbacks, CallbacksByModule),
+ true = ets:insert(ETSExpTypes, [{ET} ||
+ ET <- sets:to_list(FileExpTypes)]),
+ case ReturnInfo of
+ false -> {ok, Plt};
+ true ->
+ PltInfo = #plt_info{files = Rec#file_plt.file_md5_list,
+ mod_deps = Rec#file_plt.mod_deps},
+ {ok, {Plt, PltInfo}}
+ end
+ end;
+ {error, Reason} ->
+ Msg = io_lib:format("Could not read PLT file ~ts: ~p\n",
+ [FileName, Reason]),
+ {error, Msg}
+ end.
+
+-type err_rsn() :: 'not_valid' | 'no_such_file' | 'read_error'.
+
+-spec included_files(file:filename()) -> {'ok', [file:filename()]}
+ | {'error', err_rsn()}.
+
+included_files(FileName) ->
+ Fun = fun() -> included_files1(FileName) end,
+ subproc(Fun).
+
+included_files1(FileName) ->
+ case get_record_from_file(FileName) of
+ {ok, #file_plt{file_md5_list = Md5}} ->
+ {ok, [File || {File, _} <- Md5]};
+ {error, _What} = Error ->
+ Error
+ end.
+
+check_version(#file_plt{version = ?VSN, implementation_md5 = ImplMd5}) ->
+ case compute_new_md5(ImplMd5, [], []) of
+ ok -> ok;
+ {differ, _, _} -> error;
+ {error, _} -> error
+ end;
+check_version(#file_plt{}) -> error.
+
+get_record_from_file(FileName) ->
+ case file:read_file(FileName) of
+ {ok, Bin} ->
+ try binary_to_term(Bin) of
+ #file_plt{} = FilePLT -> {ok, FilePLT};
+ _ -> {error, not_valid}
+ catch
+ _:_ -> {error, not_valid}
+ end;
+ {error, enoent} ->
+ {error, no_such_file};
+ {error, _} ->
+ {error, read_error}
+ end.
+
+-spec is_cplt(file:filename()) -> boolean().
+is_cplt(FileName) ->
+ case get_record_from_file(FileName) of
+ {ok, _} -> true;
+ {error, _} -> false
+ end.
+
+-spec merge_disj_plts([#plt{}]) -> #plt{}.
+
+%% One of the PLTs of the list is augmented with the contents of the
+%% other PLTs, and returned. The other PLTs are deleted.
+%%
+%% The keys are compared when checking for disjointness. Sometimes the
+%% key is a module(), sometimes an mfa(). It boils down to checking if
+%% any module occurs more than once.
+merge_disj_plts(List) ->
+ {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList} =
+ group_fields(List),
+ #plt{info = table_disj_merge(InfoList),
+ types = table_disj_merge(TypesList),
+ exported_types = sets_disj_merge(ExpTypesList),
+ contracts = table_disj_merge(ContractsList),
+ callbacks = table_disj_merge(CallbacksList)
+ }.
+
+group_fields(List) ->
+ InfoList = [Info || #plt{info = Info} <- List],
+ TypesList = [Types || #plt{types = Types} <- List],
+ ExpTypesList = [ExpTypes || #plt{exported_types = ExpTypes} <- List],
+ ContractsList = [Contracts || #plt{contracts = Contracts} <- List],
+ CallbacksList = [Callbacks || #plt{callbacks = Callbacks} <- List],
+ {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList}.
+
+-spec merge_plts_or_report_conflicts([file:filename()], [#plt{}]) -> #plt{}.
+
+merge_plts_or_report_conflicts(PltFiles, Plts) ->
+ try
+ merge_disj_plts(Plts)
+ catch throw:{dialyzer_error, not_disjoint_plts} ->
+ IncFiles = lists:append([begin {ok, Fs} = included_files(F), Fs end
+ || F <- PltFiles]),
+ ConfFiles = find_duplicates(IncFiles),
+ Msg = io_lib:format("Could not merge PLTs since they are not disjoint\n"
+ "The following files are included in more than one "
+ "PLTs:\n~tp\n", [ConfFiles]),
+ plt_error(Msg)
+ end.
+
+find_duplicates(List) ->
+ ModList = [filename:basename(E) || E <- List],
+ SortedList = lists:usort(ModList),
+ lists:usort(ModList -- SortedList).
+
+-spec to_file(file:filename(), #plt{}, dialyzer_callgraph:mod_deps(), #plt_info{}) -> 'ok'.
+
+%% Write the PLT to file, and delete the PLT.
+to_file(FileName, Plt, ModDeps, MD5_OldModDeps) ->
+ Fun = fun() -> to_file1(FileName, Plt, ModDeps, MD5_OldModDeps) end,
+ Return = subproc(Fun),
+ dialyzer_plt:delete(Plt),
+ case Return of
+ ok -> ok;
+ {error, Msg} -> plt_error(Msg)
+ end.
+
+to_file1(FileName,
+ #plt{info = ETSInfo, types = ETSTypes, contracts = ETSContracts,
+ callbacks = ETSCallbacks, exported_types = ETSExpTypes},
+ ModDeps, #plt_info{files = MD5, mod_deps = OldModDeps}) ->
+ NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) ->
+ ordsets:union(OldVal, NewVal)
+ end,
+ OldModDeps, ModDeps),
+ ImplMd5 = compute_implementation_md5(),
+ CallbacksList =
+ [Cb ||
+ {_M, Cbs} <- dialyzer_utils:ets_tab2list(ETSCallbacks),
+ Cb <- Cbs],
+ Callbacks = dict:from_list(CallbacksList),
+ Info = dict:from_list(dialyzer_utils:ets_tab2list(ETSInfo)),
+ Types = dialyzer_utils:ets_tab2list(ETSTypes),
+ Contracts = dict:from_list(dialyzer_utils:ets_tab2list(ETSContracts)),
+ ExpTypes = sets:from_list([E || {E} <- dialyzer_utils:ets_tab2list(ETSExpTypes)]),
+ FileTypes = dict:from_list([{Mod, dict:from_list(maps:to_list(MTypes))} ||
+ {Mod, MTypes} <- Types]),
+ Record = #file_plt{version = ?VSN,
+ file_md5_list = MD5,
+ info = Info,
+ contracts = Contracts,
+ callbacks = Callbacks,
+ types = FileTypes,
+ exported_types = ExpTypes,
+ mod_deps = NewModDeps,
+ implementation_md5 = ImplMd5},
+ Bin = term_to_binary(Record, [compressed]),
+ case file:write_file(FileName, Bin) of
+ ok -> ok;
+ {error, Reason} ->
+ Msg = io_lib:format("Could not write PLT file ~ts: ~w\n",
+ [FileName, Reason]),
+ {error, Msg}
+ end.
+
+-type md5_diff() :: [{'differ', atom()} | {'removed', atom()}].
+-type check_error() :: err_rsn() | {'no_file_to_remove', file:filename()}.
+
+-spec check_plt(file:filename(), [file:filename()], [file:filename()]) ->
+ 'ok'
+ | {'error', check_error()}
+ | {'differ', [file_md5()], md5_diff(), dialyzer_callgraph:mod_deps()}
+ | {'old_version', [file_md5()]}.
+
+check_plt(FileName, RemoveFiles, AddFiles) ->
+ Fun = fun() -> check_plt1(FileName, RemoveFiles, AddFiles) end,
+ subproc(Fun).
+
+check_plt1(FileName, RemoveFiles, AddFiles) ->
+ case get_record_from_file(FileName) of
+ {ok, #file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} ->
+ case check_version(Rec) of
+ ok ->
+ case compute_new_md5(Md5, RemoveFiles, AddFiles) of
+ ok -> ok;
+ {differ, NewMd5, DiffMd5} -> {differ, NewMd5, DiffMd5, ModDeps};
+ {error, _What} = Err -> Err
+ end;
+ error ->
+ case compute_new_md5(Md5, RemoveFiles, AddFiles) of
+ ok -> {old_version, Md5};
+ {differ, NewMd5, _DiffMd5} -> {old_version, NewMd5};
+ {error, _What} = Err -> Err
+ end
+ end;
+ Error -> Error
+ end.
+
+compute_new_md5(Md5, [], []) ->
+ compute_new_md5_1(Md5, [], []);
+compute_new_md5(Md5, RemoveFiles0, AddFiles0) ->
+ %% Assume that files are first removed and then added. Files that
+ %% are both removed and added will be checked for consistency in the
+ %% normal way. If they have moved, we assume that they differ.
+ RemoveFiles = RemoveFiles0 -- AddFiles0,
+ AddFiles = AddFiles0 -- RemoveFiles0,
+ InitDiffList = init_diff_list(RemoveFiles, AddFiles),
+ case init_md5_list(Md5, RemoveFiles, AddFiles) of
+ {ok, NewMd5} -> compute_new_md5_1(NewMd5, [], InitDiffList);
+ {error, _What} = Error -> Error
+ end.
+
+compute_new_md5_1([{File, Md5} = Entry|Entries], NewList, Diff) ->
+ case compute_md5_from_file(File) of
+ Md5 -> compute_new_md5_1(Entries, [Entry|NewList], Diff);
+ NewMd5 ->
+ ModName = beam_file_to_module(File),
+ compute_new_md5_1(Entries, [{File, NewMd5}|NewList], [{differ, ModName}|Diff])
+ end;
+compute_new_md5_1([], _NewList, []) ->
+ ok;
+compute_new_md5_1([], NewList, Diff) ->
+ {differ, lists:keysort(1, NewList), Diff}.
+
+-spec compute_implementation_md5() -> [file_md5()].
+
+compute_implementation_md5() ->
+ Dir = code:lib_dir(dialyzer),
+ Files1 = ["erl_bif_types.beam", "erl_types.beam"],
+ Files2 = [filename:join([Dir, "ebin", F]) || F <- Files1],
+ compute_md5_from_files(Files2).
+
+-spec compute_md5_from_files([file:filename()]) -> [file_md5()].
+
+compute_md5_from_files(Files) ->
+ lists:keysort(1, [{F, compute_md5_from_file(F)} || F <- Files]).
+
+compute_md5_from_file(File) ->
+ case beam_lib:all_chunks(File) of
+ {ok, _, Chunks} ->
+ %% We cannot use beam_lib:md5 because it does not consider
+ %% the debug_info chunk, where typespecs are likely stored.
+ %% So we consider almost all chunks except the useless ones.
+ Filtered = [[ID, Chunk] || {ID, Chunk} <- Chunks, ID =/= "CInf", ID =/= "Docs"],
+ erlang:md5(lists:sort(Filtered));
+ {error, beam_lib, {file_error, _, enoent}} ->
+ Msg = io_lib:format("File not found: ~ts\n", [File]),
+ plt_error(Msg);
+ {error, beam_lib, _} ->
+ Msg = io_lib:format("Could not compute MD5 for .beam: ~ts\n", [File]),
+ plt_error(Msg)
+ end.
+
+init_diff_list(RemoveFiles, AddFiles) ->
+ RemoveSet0 = sets:from_list([beam_file_to_module(F) || F <- RemoveFiles]),
+ AddSet0 = sets:from_list([beam_file_to_module(F) || F <- AddFiles]),
+ DiffSet = sets:intersection(AddSet0, RemoveSet0),
+ RemoveSet = sets:subtract(RemoveSet0, DiffSet),
+ %% Added files and diff files will appear as diff files from the md5 check.
+ [{removed, F} || F <- sets:to_list(RemoveSet)].
+
+init_md5_list(Md5, RemoveFiles, AddFiles) ->
+ Files = [{remove, F} || F <- RemoveFiles] ++ [{add, F} || F <- AddFiles],
+ DiffFiles = lists:keysort(2, Files),
+ Md5Sorted = lists:keysort(1, Md5),
+ init_md5_list_1(Md5Sorted, DiffFiles, []).
+
+init_md5_list_1([{File, _Md5}|Md5Left], [{remove, File}|DiffLeft], Acc) ->
+ init_md5_list_1(Md5Left, DiffLeft, Acc);
+init_md5_list_1([{File, _Md5} = Entry|Md5Left], [{add, File}|DiffLeft], Acc) ->
+ init_md5_list_1(Md5Left, DiffLeft, [Entry|Acc]);
+init_md5_list_1([{File1, _Md5} = Entry|Md5Left] = Md5List,
+ [{Tag, File2}|DiffLeft] = DiffList, Acc) ->
+ case File1 < File2 of
+ true -> init_md5_list_1(Md5Left, DiffList, [Entry|Acc]);
+ false ->
+ %% Just an assert.
+ true = File1 > File2,
+ case Tag of
+ add -> init_md5_list_1(Md5List, DiffLeft, [{File2, <<>>}|Acc]);
+ remove -> {error, {no_file_to_remove, File2}}
+ end
+ end;
+init_md5_list_1([], DiffList, Acc) ->
+ AddFiles = [{F, <<>>} || {add, F} <- DiffList],
+ {ok, lists:reverse(Acc, AddFiles)};
+init_md5_list_1(Md5List, [], Acc) ->
+ {ok, lists:reverse(Acc, Md5List)}.
+
+
+subproc(Fun) ->
+ F = fun() ->
+ exit(try Fun()
+ catch throw:T ->
+ {thrown, T}
+ end)
+ end,
+ {Pid, Ref} = erlang:spawn_monitor(F),
+ receive {'DOWN', Ref, process, Pid, Return} ->
+ case Return of
+ {thrown, T} -> throw(T);
+ _ -> Return
+ end
+ end.
+
+
+beam_file_to_module(Filename) ->
+ list_to_atom(filename:basename(Filename, ".beam")).
+
+
+-spec plt_error(deep_string()) -> no_return().
+
+plt_error(Msg) ->
+ throw({dialyzer_error, lists:flatten(Msg)}).
+
+table_disj_merge([H|T]) ->
+ table_disj_merge(T, H).
+
+table_disj_merge([], Acc) ->
+ Acc;
+table_disj_merge([Plt|Plts], Acc) ->
+ case table_is_disjoint(Plt, Acc) of
+ true ->
+ NewAcc = merge_tables(Plt, Acc),
+ table_disj_merge(Plts, NewAcc);
+ false -> throw({dialyzer_error, not_disjoint_plts})
+ end.
+
+sets_disj_merge([H|T]) ->
+ sets_disj_merge(T, H).
+
+sets_disj_merge([], Acc) ->
+ Acc;
+sets_disj_merge([Plt|Plts], Acc) ->
+ case table_is_disjoint(Plt, Acc) of
+ true ->
+ NewAcc = merge_tables(Plt, Acc),
+ sets_disj_merge(Plts, NewAcc);
+ false -> throw({dialyzer_error, not_disjoint_plts})
+ end.
+
+table_is_disjoint(T1, T2) ->
+ tab_is_disj(ets:first(T1), T1, T2).
+
+tab_is_disj('$end_of_table', _T1, _T2) ->
+ true;
+tab_is_disj(K1, T1, T2) ->
+ case ets:member(T2, K1) of
+ false ->
+ tab_is_disj(ets:next(T1, K1), T1, T2);
+ true ->
+ false
+ end.
+
+merge_tables(T1, T2) ->
+ tab_merge(ets:first(T1), T1, T2).
+
+tab_merge('$end_of_table', T1, T2) ->
+ case ets:first(T1) of % no safe_fixtable()...
+ '$end_of_table' ->
+ true = ets:delete(T1),
+ T2;
+ Key ->
+ tab_merge(Key, T1, T2)
+ end;
+tab_merge(K1, T1, T2) ->
+ Vs = ets:lookup(T1, K1),
+ NextK1 = ets:next(T1, K1),
+ true = ets:delete(T1, K1),
+ true = ets:insert(T2, Vs),
+ tab_merge(NextK1, T1, T2).
+
+
+%%---------------------------------------------------------------------------
+%% Debug utilities.
+
+-spec pp_non_returning() -> 'ok'.
+
+pp_non_returning() ->
+ PltFile = get_default_cplt_filename(),
+ Plt = from_file(PltFile),
+ List = ets:tab2list(Plt#plt.info),
+ Unit = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List,
+ erl_types:t_is_unit(Ret)],
+ None = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List,
+ erl_types:t_is_none(Ret)],
+ io:format("=========================================\n"),
+ io:format("= Loops =\n"),
+ io:format("=========================================\n\n"),
+ lists:foreach(fun({{M, F, _}, Type}) ->
+ io:format("~w:~tw~ts.\n",
+ [M, F, dialyzer_utils:format_sig(Type)])
+ end, lists:sort(Unit)),
+ io:format("\n"),
+ io:format("=========================================\n"),
+ io:format("= Errors =\n"),
+ io:format("=========================================\n\n"),
+ lists:foreach(fun({{M, F, _}, Type}) ->
+ io:format("~w:~w~s.\n",
+ [M, F, dialyzer_utils:format_sig(Type)])
+ end, lists:sort(None)),
+ dialyzer_plt:delete(Plt).
+
+-spec pp_mod(atom()) -> 'ok'.
+
+pp_mod(Mod) when is_atom(Mod) ->
+ PltFile = get_default_cplt_filename(),
+ Plt = from_file(PltFile),
+ case dialyzer_plt:lookup_module(Plt, Mod) of
+ {value, List} ->
+ lists:foreach(fun({{_, F, _}, Ret, Args}) ->
+ T = erl_types:t_fun(Args, Ret),
+ S = dialyzer_utils:format_sig(T),
+ io:format("-spec ~tw~ts.\n", [F, S])
+ end, lists:sort(List));
+ none ->
+ io:format("dialyzer: Found no module named '~s' in the PLT\n", [Mod])
+ end,
+ dialyzer_plt:delete(Plt).
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index ece162382f..7e0a75f062 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -1557,8 +1557,9 @@ bind_tuple(Pat, Type, Map, State, Opaques, Rev) ->
true ->
Any = t_any(),
[_Head|AnyTail] = [Any || _ <- Es],
- UntypedRecord = t_tuple([Tag|AnyTail]),
- case state__lookup_record(cerl:atom_val(Tag), length(Tags), State) of
+ TagAtomVal = cerl:atom_val(Tag),
+ UntypedRecord = t_tuple([t_atom(TagAtomVal)|AnyTail]),
+ case state__lookup_record(TagAtomVal, length(Tags), State) of
error ->
{false, UntypedRecord};
{ok, Record, _FieldNames} ->
@@ -1601,20 +1602,23 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) ->
UnitVal = cerl:concrete(cerl:bitstr_unit(Seg)),
Size = cerl:bitstr_size(Seg),
case bitstr_bitsize_type(Size) of
- all ->
- binary = SegType, [] = Segs, %% just an assert
+ {literal, all} ->
+ binary = SegType, [] = Segs, %Assertion.
T = t_inf(t_bitstr(UnitVal, 0), BinType),
{Map1, [Type]} = do_bind_pat_vars([Val], [T], Map,
State, false, []),
Type1 = remove_local_opaque_types(Type, State#state.opaques),
bind_bin_segs(Segs, t_bitstr(0, 0), [Type1|Acc], Map1, State);
- utf -> % XXX: can possibly be strengthened
- true = lists:member(SegType, [utf8, utf16, utf32]),
+ SizeType when SegType =:= utf8; SegType =:= utf16; SegType =:= utf32 ->
+ {literal, undefined} = SizeType, %Assertion.
{Map1, [_]} = do_bind_pat_vars([Val], [t_integer()],
Map, State, false, []),
Type = t_binary(),
bind_bin_segs(Segs, BinType, [Type|Acc], Map1, State);
- any ->
+ {literal, N} when not is_integer(N); N < 0 ->
+ %% Bogus literal size, fails in runtime.
+ bind_error([Seg], BinType, t_none(), bind);
+ _ ->
{Map1, [SizeType]} = do_bind_pat_vars([Size], [t_non_neg_integer()],
Map, State, false, []),
Opaques = State#state.opaques,
@@ -1667,14 +1671,8 @@ bind_bin_segs([], _BinType, Acc, Map, _State) ->
bitstr_bitsize_type(Size) ->
case cerl:is_literal(Size) of
- true ->
- case cerl:concrete(Size) of
- all -> all;
- undefined -> utf;
- _ -> any
- end;
- false ->
- any
+ true -> {literal, cerl:concrete(Size)};
+ false -> variable
end.
%% Return the infimum (meet) of ExpectedType and Type if it describes a
@@ -1800,16 +1798,17 @@ bind_guard(Guard, Map, Env, Eval, State) ->
{Map, Type};
var ->
?debug("Looking for var(~w)...", [cerl_trees:get_label(Guard)]),
- case maps:find(get_label(Guard), Env) of
- error ->
+ GuardLabel = get_label(Guard),
+ case Env of
+ #{GuardLabel := Tree} ->
+ ?debug("Found it\n", []),
+ {Map1, Type} = bind_guard(Tree, Map, Env, Eval, State),
+ {enter_type(Guard, Type, Map1), Type};
+ #{} ->
?debug("Did not find it\n", []),
Type = lookup_type(Guard, Map),
Inf = guard_eval_inf(Eval, Type),
- {enter_type(Guard, Inf, Map), Inf};
- {ok, Tree} ->
- ?debug("Found it\n", []),
- {Map1, Type} = bind_guard(Tree, Map, Env, Eval, State),
- {enter_type(Guard, Type, Map1), Type}
+ {enter_type(Guard, Inf, Map), Inf}
end;
call ->
handle_guard_call(Guard, Map, Env, Eval, State)
@@ -2709,19 +2708,19 @@ enter_type(Key, Val, MS) ->
false ->
#map{map = Map, subst = Subst} = MS,
KeyLabel = get_label(Key),
- case maps:find(KeyLabel, Subst) of
- {ok, NewKey} ->
+ case Subst of
+ #{KeyLabel := NewKey} ->
?debug("Binding ~p to ~p\n", [KeyLabel, NewKey]),
enter_type(NewKey, Val, MS);
- error ->
+ #{} ->
?debug("Entering ~p :: ~ts\n", [KeyLabel, t_to_string(Val)]),
- case maps:find(KeyLabel, Map) of
- {ok, Value} ->
+ case Map of
+ #{KeyLabel := Value} ->
case erl_types:t_is_equal(Val, Value) of
true -> MS;
false -> store_map(KeyLabel, Val, MS)
end;
- error -> store_map(KeyLabel, Val, MS)
+ #{} -> store_map(KeyLabel, Val, MS)
end
end
end
@@ -2730,7 +2729,7 @@ enter_type(Key, Val, MS) ->
store_map(Key, Val, #map{map = Map, ref = undefined} = MapRec) ->
MapRec#map{map = maps:put(Key, Val, Map)};
store_map(Key, Val, #map{map = Map, modified = Mod} = MapRec) ->
- MapRec#map{map = maps:put(Key, Val, Map), modified = [Key | Mod]}.
+ MapRec#map{map = Map#{Key => Val}, modified = [Key | Mod]}.
enter_subst(Key, Val0, #map{subst = Subst} = MS) ->
KeyLabel = get_label(Key),
@@ -2743,10 +2742,10 @@ enter_subst(Key, Val0, #map{subst = Subst} = MS) ->
false -> MS;
true ->
ValLabel = get_label(Val),
- case maps:find(ValLabel, Subst) of
- {ok, NewVal} ->
+ case Subst of
+ #{ValLabel := NewVal} ->
enter_subst(Key, NewVal, MS);
- error ->
+ #{} ->
if KeyLabel =:= ValLabel -> MS;
true ->
?debug("Subst: storing ~p = ~p\n", [KeyLabel, ValLabel]),
@@ -2759,7 +2758,7 @@ enter_subst(Key, Val0, #map{subst = Subst} = MS) ->
store_subst(Key, Val, #map{subst = S, ref = undefined} = Map) ->
Map#map{subst = maps:put(Key, Val, S)};
store_subst(Key, Val, #map{subst = S, modified = Mod} = Map) ->
- Map#map{subst = maps:put(Key, Val, S), modified = [Key | Mod]}.
+ Map#map{subst = S#{Key => Val}, modified = [Key | Mod]}.
lookup_type(Key, #map{map = Map, subst = Subst}) ->
lookup(Key, Map, Subst, t_none()).
@@ -2769,13 +2768,14 @@ lookup(Key, Map, Subst, AnyNone) ->
true -> literal_type(Key);
false ->
Label = get_label(Key),
- case maps:find(Label, Subst) of
- {ok, NewKey} -> lookup(NewKey, Map, Subst, AnyNone);
- error ->
- case maps:find(Label, Map) of
- {ok, Val} -> Val;
- error -> AnyNone
- end
+ case Subst of
+ #{Label := NewKey} ->
+ lookup(NewKey, Map, Subst, AnyNone);
+ #{} ->
+ case Map of
+ #{Label := Val} -> Val;
+ #{} -> AnyNone
+ end
end
end.
diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl
index 3b04b56f80..13d0a65dbb 100644
--- a/lib/dialyzer/src/dialyzer_gui_wx.erl
+++ b/lib/dialyzer/src/dialyzer_gui_wx.erl
@@ -69,7 +69,7 @@
rawWarnings :: list(),
backend_pid :: pid() | 'undefined',
expl_pid :: pid() | 'undefined'}).
-
+
%%------------------------------------------------------------------------
-spec start(#options{}) -> ?RET_NOTHING_SUSPICIOUS.
@@ -84,7 +84,7 @@ create_window(Wx, #options{init_plts = InitPltFiles} = DialyzerOptions) ->
{ok, Host} = inet:gethostname(),
%%---------- initializing frame ---------
- Frame = wxFrame:new(Wx, -1, "Dialyzer " ++ ?VSN ++ " @ " ++ Host),
+ Frame = wxFrame:new(Wx, -1, "Dialyzer " ++ ?VSN ++ " @ " ++ Host),
wxFrame:connect(Frame, close_window),
FileMenu = createFileMenu(),
WarningsMenu = createWarningsMenu(),
@@ -119,7 +119,7 @@ create_window(Wx, #options{init_plts = InitPltFiles} = DialyzerOptions) ->
{style, ?wxTE_MULTILINE
bor ?wxTE_READONLY bor ?wxHSCROLL}]),
DefaultPath = code:root_dir(),
-
+
FilePicker = wxFilePickerCtrl:new(Frame, ?FilePicker,
[{path, DefaultPath},
{message, "Choose File to Analyse"},
@@ -184,7 +184,7 @@ create_window(Wx, #options{init_plts = InitPltFiles} = DialyzerOptions) ->
WarnButtons = wxBoxSizer:new(?wxHORIZONTAL),
RunButtons = wxBoxSizer:new(?wxHORIZONTAL),
Buttons = wxFlexGridSizer:new(3),
-
+
_ = wxSizer:add(ChooseButtons, DeleteButton, ?BorderOpt),
_ = wxSizer:add(ChooseButtons, DeleteAllButton, ?BorderOpt),
_ = wxSizer:add(ChooseItem, Lab1, Center),
@@ -232,7 +232,7 @@ create_window(Wx, #options{init_plts = InitPltFiles} = DialyzerOptions) ->
wxWindow:setSizer(Frame, All),
wxWindow:setSizeHints(Frame, {1150,600}),
wxWindow:show(Frame),
-
+
Warnings = [{?WARN_RETURN_NO_RETURN, ?menuID_WARN_NO_RETURN_FUN},
{?WARN_RETURN_ONLY_EXIT, ?menuID_WARN_ERROR_HANDLING_FUN},
{?WARN_NOT_CALLED, ?menuID_WARN_UNUSED_FUN},
@@ -256,22 +256,22 @@ create_window(Wx, #options{init_plts = InitPltFiles} = DialyzerOptions) ->
case InitPltFiles of
[] -> dialyzer_plt:new();
_ ->
- Plts = [dialyzer_plt:from_file(F) || F <- InitPltFiles],
- dialyzer_plt:merge_plts_or_report_conflicts(InitPltFiles, Plts)
+ Plts = [dialyzer_cplt:from_file(F) || F <- InitPltFiles],
+ dialyzer_cplt:merge_plts_or_report_conflicts(InitPltFiles, Plts)
end,
-
+
#gui_state{add = AddButton,
add_dir = AddDirButton,
add_rec = AddRecButton,
- chosen_box = ChosenBox,
- clear_chosen = DeleteAllButton,
- clear_log = ClearLogButton,
+ chosen_box = ChosenBox,
+ clear_chosen = DeleteAllButton,
+ clear_log = ClearLogButton,
explain_warn = ExplainWarnButton,
- clear_warn = ClearWarningsButton,
- del_file = DeleteButton,
+ clear_warn = ClearWarningsButton,
+ del_file = DeleteButton,
doc_plt = dialyzer_plt:new(),
dir_entry = DirPicker,
- file_box = FilePicker,
+ file_box = FilePicker,
files_to_analyze = ordsets:new(),
gui = Wx,
init_plt = InitPlt,
@@ -281,7 +281,7 @@ create_window(Wx, #options{init_plts = InitPltFiles} = DialyzerOptions) ->
options = DialyzerOptions,
run = RunButton,
stop = StopButton,
- frame = Frame,
+ frame = Frame,
warnings_box = WarningsBox,
wantedWarnings = Warnings,
rawWarnings = []}.
@@ -318,7 +318,7 @@ createWarningsMenu() ->
WarningsMenu.
addCheckedItem(Menu, ItemId, Str) ->
- _ = wxMenu:appendCheckItem(Menu, ItemId, Str),
+ _ = wxMenu:appendCheckItem(Menu, ItemId, Str),
wxMenu:check(Menu, ItemId, true).
createPltMenu() ->
@@ -359,57 +359,57 @@ createHelpMenu() ->
gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt,
log = Log, frame = Frame,
warnings_box = WarningsBox} = State) ->
- receive
+ receive
#wx{event = #wxClose{}} ->
%% io:format("~p Closing window ~n", [self()]),
ok = wxFrame:setStatusText(Frame, "Closing...",[]),
wxWindow:destroy(Frame),
?RET_NOTHING_SUSPICIOUS;
%% ----- Menu -----
- #wx{id = ?menuID_FILE_SAVE_LOG, obj = Frame,
+ #wx{id = ?menuID_FILE_SAVE_LOG, obj = Frame,
event = #wxCommand{type = command_menu_selected}} ->
save_file(State, log),
gui_loop(State);
- #wx{id=?menuID_FILE_SAVE_WARNINGS, obj=Frame,
+ #wx{id=?menuID_FILE_SAVE_WARNINGS, obj=Frame,
event=#wxCommand{type=command_menu_selected}} ->
save_file(State, warnings),
gui_loop(State);
- #wx{id=?menuID_FILE_QUIT, obj=Frame,
+ #wx{id=?menuID_FILE_QUIT, obj=Frame,
event=#wxCommand{type=command_menu_selected}} ->
case maybe_quit(State) of
true -> ?RET_NOTHING_SUSPICIOUS;
false -> gui_loop(State)
end;
- #wx{id=?menuID_PLT_SHOW_CONTENTS, obj=Frame,
+ #wx{id=?menuID_PLT_SHOW_CONTENTS, obj=Frame,
event=#wxCommand{type=command_menu_selected}} ->
show_doc_plt(State),
gui_loop(State);
- #wx{id=?menuID_PLT_SEARCH_CONTENTS, obj=Frame,
+ #wx{id=?menuID_PLT_SEARCH_CONTENTS, obj=Frame,
event=#wxCommand{type=command_menu_selected}} ->
case dialyzer_plt:get_specs(DocPlt) of
"" -> error_sms(State, "No analysis has been made yet!\n");
_ -> search_doc_plt(State)
end,
gui_loop(State);
- #wx{id=?menuID_OPTIONS_INCLUDE_DIR, obj=Frame,
+ #wx{id=?menuID_OPTIONS_INCLUDE_DIR, obj=Frame,
event=#wxCommand{type=command_menu_selected}} ->
NewOptions = include_dialog(State),
NewState = State#gui_state{options = NewOptions},
gui_loop(NewState);
- #wx{id=?menuID_OPTIONS_MACRO, obj=Frame,
+ #wx{id=?menuID_OPTIONS_MACRO, obj=Frame,
event=#wxCommand{type=command_menu_selected}} ->
NewOptions = macro_dialog(State),
NewState = State#gui_state{options = NewOptions},
gui_loop(NewState);
- #wx{id=?menuID_HELP_MANUAL, obj=Frame,
+ #wx{id=?menuID_HELP_MANUAL, obj=Frame,
event=#wxCommand{type=command_menu_selected}} ->
handle_help(State, "Dialyzer Manual", "manual.txt"),
gui_loop(State);
- #wx{id=?menuID_HELP_WARNING_OPTIONS, obj=Frame,
+ #wx{id=?menuID_HELP_WARNING_OPTIONS, obj=Frame,
event=#wxCommand{type=command_menu_selected}} ->
handle_help(State, "Dialyzer Warnings", "warnings.txt"),
gui_loop(State);
- #wx{id=?menuID_HELP_ABOUT, obj=Frame,
+ #wx{id=?menuID_HELP_ABOUT, obj=Frame,
event=#wxCommand{type=command_menu_selected}} ->
Message = " This is DIALYZER version " ++ ?VSN ++ " \n"++
"DIALYZER is a DIscrepancy AnaLYZer for ERlang programs.\n\n"++
@@ -494,8 +494,8 @@ gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt,
gui_loop(NewState);
{BackendPid, cserver, CServer, Plt} ->
Self = self(),
- Fun =
- fun() ->
+ Fun =
+ fun() ->
dialyzer_explanation:expl_loop(Self, CServer, Plt)
end,
ExplanationPid = spawn_link(Fun),
@@ -527,7 +527,7 @@ maybe_quit(#gui_state{frame = Frame} = State) ->
%% ------------ Yes/No Question ------------
dialog(#gui_state{frame = Frame}, Message, Title) ->
MessageWin = wxMessageDialog:new(Frame, Message, [{caption, Title},{style, ?wxYES_NO bor ?wxICON_QUESTION bor ?wxNO_DEFAULT}]),
- case wxDialog:showModal(MessageWin) of
+ case wxDialog:showModal(MessageWin) of
?wxID_YES ->
true;
?wxID_NO ->
@@ -535,7 +535,7 @@ dialog(#gui_state{frame = Frame}, Message, Title) ->
?wxID_CANCEL ->
false
end.
-
+
search_doc_plt(#gui_state{gui = Wx} = State) ->
Dialog = wxFrame:new(Wx, ?SearchPltDialog, "Search the PLT",[{size,{400,100}},{style, ?wxSTAY_ON_TOP}]),
Size = {size,{120,30}},
@@ -591,8 +591,8 @@ search_plt_loop(State= #gui_state{doc_plt = DocPlt, frame = Frame}, Win, ModText
M = format_search(wxTextCtrl:getValue(ModText)),
F = format_search(wxTextCtrl:getValue(FunText)),
A = format_search(wxTextCtrl:getValue(ArText)),
-
- if
+
+ if
(M =:= '_') orelse (F =:= '_') orelse (A =:= '_') ->
error_sms(State, "Please give:\n Module (atom)\n Function (atom)\n Arity (integer)\n"),
search_plt_loop(State, Win, ModText, FunText, ArText, Search, Cancel);
@@ -606,15 +606,15 @@ search_plt_loop(State= #gui_state{doc_plt = DocPlt, frame = Frame}, Win, ModText
free_editor(State, "Content of PLT", NonEmptyString)
end
end
- end.
+ end.
format_search([]) ->
'_';
format_search(String) ->
try list_to_integer(String)
catch error:_ -> list_to_atom(String)
- end.
-
+ end.
+
show_doc_plt(#gui_state{doc_plt = DocPLT} = State) ->
case dialyzer_plt:get_specs(DocPLT) of
"" -> error_sms(State, "No analysis has been made yet!\n");
@@ -648,8 +648,8 @@ free_editor(#gui_state{gui = Wx, frame = Frame}, Title, Contents0) ->
Width0 = LongestLine * 7 + 60,
Width = if Width0 > 800 -> 800; true -> Width0 end,
Size = {size,{Width, Height}},
- Win = wxFrame:new(Wx, ?Message, Title, [{size,{Width+4, Height+50}}]),
-
+ Win = wxFrame:new(Wx, ?Message, Title, [{size,{Width+4, Height+50}}]),
+
Editor = wxTextCtrl:new(Win, ?Message_Info,
[Size,
{style, ?wxTE_MULTILINE
@@ -659,7 +659,7 @@ free_editor(#gui_state{gui = Wx, frame = Frame}, Title, Contents0) ->
Ok = wxButton:new(Win, ?Message_Ok, [{label, "OK"}]),
wxButton:connect(Ok, command_button_clicked),
Layout = wxBoxSizer:new(?wxVERTICAL),
-
+
_ = wxSizer:add(Layout, Editor, ?BorderOpt),
Flag = ?wxALIGN_CENTER bor ?wxBOTTOM bor ?wxALL,
_ = wxSizer:add(Layout, Ok, [{flag, Flag}, ?Border]),
@@ -686,7 +686,7 @@ handle_add_files(#gui_state{chosen_box = ChosenBox, file_box = FileBox,
File ->
NewFile = ordsets:new(),
NewFile1 = ordsets:add_element(File,NewFile),
- Ext =
+ Ext =
case wxRadioBox:getSelection(Mode) of
0 -> ".beam";
1-> ".erl"
@@ -699,7 +699,7 @@ handle_add_dir(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox,
case wxDirPickerCtrl:getPath(DirBox) of
"" ->
State;
- Dir ->
+ Dir ->
NewDir = ordsets:new(),
NewDir1 = ordsets:add_element(Dir,NewDir),
Ext = case wxRadioBox:getSelection(Mode) of
@@ -708,13 +708,13 @@ handle_add_dir(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox,
end,
State#gui_state{files_to_analyze = add_files(filter_mods(NewDir1,Ext), FileList, ChosenBox, Ext)}
end.
-
+
handle_add_rec(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox,
files_to_analyze = FileList, mode = Mode} = State) ->
case wxDirPickerCtrl:getPath(DirBox) of
"" ->
State;
- Dir ->
+ Dir ->
NewDir = ordsets:new(),
NewDir1 = ordsets:add_element(Dir,NewDir),
TargetDirs = ordsets:union(NewDir1, all_subdirs(NewDir1)),
@@ -736,7 +736,7 @@ handle_file_delete(#gui_state{chosen_box = ChosenBox,
handle_file_delete_all(#gui_state{chosen_box = ChosenBox} = State) ->
wxListBox:clear(ChosenBox),
State#gui_state{files_to_analyze = ordsets:new()}.
-
+
add_files(File, FileList, ChosenBox, Ext) ->
Set = filter_mods(FileList, Ext),
Files = ordsets:union(File, Set),
@@ -747,7 +747,7 @@ add_files(File, FileList, ChosenBox, Ext) ->
filter_mods(Mods, Extension) ->
Fun = fun(X) ->
filename:extension(X) =:= Extension
- orelse
+ orelse
(filelib:is_dir(X) andalso
contains_files(X, Extension))
end,
@@ -781,7 +781,7 @@ start_analysis(State) ->
Msg = "You must choose one or more files or dirs\n"
"before starting the analysis!",
error_sms(State, Msg),
- config_gui_stop(State),
+ config_gui_stop(State),
State;
{ok, Files} ->
Msg = "\n========== Starting Analysis ==========\n\n",
@@ -825,8 +825,8 @@ run_analysis(State, Analysis) ->
Self = self(),
NewAnalysis = Analysis#analysis{doc_plt = dialyzer_plt:new()},
LegalWarnings = find_legal_warnings(State),
- Fun =
- fun() ->
+ Fun =
+ fun() ->
dialyzer_analysis_callgraph:start(Self, LegalWarnings, NewAnalysis)
end,
BackendPid = spawn_link(Fun),
@@ -834,13 +834,13 @@ run_analysis(State, Analysis) ->
find_legal_warnings(#gui_state{menu = #menu{warnings = MenuWarnings},
wantedWarnings = Warnings }) ->
- ordsets:from_list([Tag || {Tag, MenuItem} <- Warnings,
+ ordsets:from_list([Tag || {Tag, MenuItem} <- Warnings,
wxMenu:isChecked(MenuWarnings, MenuItem)]).
update_editor(Editor, Msg) ->
wxTextCtrl:appendText(Editor,Msg).
-config_gui_stop(State) ->
+config_gui_stop(State) ->
wxWindow:disable(State#gui_state.stop),
wxWindow:enable(State#gui_state.run),
wxWindow:enable(State#gui_state.del_file),
@@ -904,7 +904,7 @@ save_file(#gui_state{frame = Frame, warnings_box = WBox, log = Log} = State, Typ
_ -> error_sms(State, "Could not write to file:\n")
end
end.
-
+
include_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) ->
Size = {size,{300,480}},
Dialog = wxFrame:new(Wx, ?IncludeDir, "Include Directories",[Size]),
@@ -913,11 +913,11 @@ include_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) ->
DirPicker = wxDirPickerCtrl:new(Dialog, ?InclPicker,
[{path, DefaultPath},
{message, "Choose Directory to Include"},
- {style,?wxDIRP_DIR_MUST_EXIST bor ?wxDIRP_USE_TEXTCTRL}]),
+ {style,?wxDIRP_DIR_MUST_EXIST bor ?wxDIRP_USE_TEXTCTRL}]),
Box = wxListBox:new(Dialog, ?InclBox,
[{size, {200,300}},
{style, ?wxLB_EXTENDED bor ?wxLB_HSCROLL
- bor ?wxLB_NEEDED_SB}]),
+ bor ?wxLB_NEEDED_SB}]),
AddButton = wxButton:new(Dialog, ?InclAdd, [{label, "Add"}]),
DeleteButton = wxButton:new(Dialog, ?InclDel, [{label, "Delete"}]),
DeleteAllButton = wxButton:new(Dialog, ?InclDelAll, [{label, "Delete All"}]),
@@ -954,7 +954,7 @@ include_loop(Options, Win, Box, DirPicker, Frame) ->
receive
#wx{id = ?InclCancel,
event = #wxCommand{type = command_button_clicked}} ->
- wxWindow:destroy(Win),
+ wxWindow:destroy(Win),
Options;
#wx{id = ?IncludeDir, event = #wxClose{type = close_window}} ->
wxWindow:destroy(Win),
@@ -994,8 +994,8 @@ include_loop(Options, Win, Box, DirPicker, Frame) ->
wxListBox:clear(Box),
NewOptions = Options#options{include_dirs = []},
include_loop(NewOptions, Win, Box, DirPicker, Frame)
- end.
-
+ end.
+
macro_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) ->
Size = {size,{300,480}},
Size1 = {size,{120,30}},
@@ -1020,9 +1020,9 @@ macro_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) ->
wxButton:connect(Ok, command_button_clicked),
wxButton:connect(Cancel, command_button_clicked),
- Macros = [io_lib:format("~p = ~p", [X, Y])
+ Macros = [io_lib:format("~p = ~p", [X, Y])
|| {X,Y} <- Options#options.defines],
-
+
wxListBox:set(Box, Macros),
Layout = wxBoxSizer:new(?wxVERTICAL),
Item = wxBoxSizer:new(?wxHORIZONTAL),
@@ -1056,7 +1056,7 @@ macro_loop(Options, Win, Box, MacroText, TermText, Frame) ->
receive
#wx{id = ?MacroCancel,
event = #wxCommand{type = command_button_clicked}} ->
- wxWindow:destroy(Win),
+ wxWindow:destroy(Win),
Options;
#wx{id = ?MacroDir, event = #wxClose{type = close_window}} ->
wxWindow:destroy(Win),
@@ -1071,12 +1071,12 @@ macro_loop(Options, Win, Box, MacroText, TermText, Frame) ->
#wx{id = ?MacroAdd,
event = #wxCommand{type = command_button_clicked}} ->
Defines = Options#options.defines,
- NewDefines =
+ NewDefines =
case wxTextCtrl:getValue(MacroText) of
"" -> Defines;
Macro ->
case wxTextCtrl:getValue(TermText) of
- "" ->
+ "" ->
orddict:store(list_to_atom(Macro), true, Defines);
String ->
orddict:store(list_to_atom(Macro), String, Defines)
@@ -1092,7 +1092,7 @@ macro_loop(Options, Win, Box, MacroText, TermText, Frame) ->
case wxListBox:getSelections(Box) of
{0, _} -> Options;
{_, List} ->
- Fun =
+ Fun =
fun(X) ->
Val = wxControlWithItems:getString(Box,X),
[MacroName|_] = re:split(Val, " ", [{return, list}, unicode]),
@@ -1113,19 +1113,19 @@ macro_loop(Options, Win, Box, MacroText, TermText, Frame) ->
wxListBox:clear(Box),
NewOptions = Options#options{defines = []},
macro_loop(NewOptions, Win, Box, MacroText, TermText, Frame)
- end.
+ end.
handle_help(State, Title, Txt) ->
FileName = filename:join([code:lib_dir(dialyzer), "doc", Txt]),
case file:open(FileName, [read]) of
{error, Reason} ->
- error_sms(State,
+ error_sms(State,
io_lib:format("Could not find doc/~ts file!\n\n ~tp",
[Txt, Reason]));
{ok, _Handle} ->
case file:read_file(FileName) of
{error, Reason} ->
- error_sms(State,
+ error_sms(State,
io_lib:format("Could not read doc/~ts file!\n\n ~tp",
[Txt, Reason]));
{ok, Binary} ->
@@ -1143,7 +1143,7 @@ add_warnings(#gui_state{warnings_box = WarnBox,
W <- NewRawWarns],
wxListBox:set(WarnBox, WarnList),
State#gui_state{rawWarnings = NewRawWarns}.
-
+
handle_explanation(#gui_state{rawWarnings = RawWarns,
warnings_box = WarnBox,
expl_pid = ExplPid} = State) ->
@@ -1173,13 +1173,13 @@ explanation_loop(#gui_state{expl_pid = ExplPid} = State) ->
show_explanation(#gui_state{gui = Wx} = State, Explanation) ->
case Explanation of
none ->
- output_sms(State, ?DIALYZER_MESSAGE_TITLE,
+ output_sms(State, ?DIALYZER_MESSAGE_TITLE,
"There is not any explanation for this error!\n", info);
Expl ->
ExplString = format_explanation(Expl),
Size = {size,{700, 300}},
- Win = wxFrame:new(Wx, ?ExplWin, "Dialyzer Explanation", [{size,{740, 350}}]),
-
+ Win = wxFrame:new(Wx, ?ExplWin, "Dialyzer Explanation", [{size,{740, 350}}]),
+
Editor = wxTextCtrl:new(Win, ?ExplText,
[Size,
{style, ?wxTE_MULTILINE
@@ -1201,11 +1201,11 @@ show_explanation(#gui_state{gui = Wx} = State, Explanation) ->
NewState = State#gui_state{explanation_box = Editor},
show_explanation_loop(NewState, Win, Explanation)
end.
-
+
show_explanation_loop(#gui_state{frame = Frame, expl_pid = ExplPid} = State, Win, Explanation) ->
receive
- {ExplPid, none, _} ->
- output_sms(State, ?DIALYZER_MESSAGE_TITLE,
+ {ExplPid, none, _} ->
+ output_sms(State, ?DIALYZER_MESSAGE_TITLE,
"There is not any other explanation for this error!\n", info),
show_explanation_loop(State, Win, Explanation);
{ExplPid, further, NewExplanation} ->
diff --git a/lib/dialyzer/src/dialyzer_incremental.erl b/lib/dialyzer/src/dialyzer_incremental.erl
new file mode 100644
index 0000000000..02fbde8ec2
--- /dev/null
+++ b/lib/dialyzer/src/dialyzer_incremental.erl
@@ -0,0 +1,817 @@
+%%
+%% 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.
+
+-module(dialyzer_incremental).
+
+-export([start/1, start_report_modules_analyzed/1, start_report_modules_changed_and_analyzed/1]).
+
+-include("dialyzer.hrl").
+-include_lib("kernel/include/file.hrl"). % needed for #file_info{}
+
+-record(incremental_state,
+ {backend_pid :: pid() | 'undefined',
+ code_server = none :: 'none' | dialyzer_codeserver:codeserver(),
+ erlang_mode = false :: boolean(),
+ external_calls = [] :: [{mfa(), warning_info()}],
+ external_types = [] :: [{mfa(), warning_info()}],
+ legal_warnings = ordsets:new() :: [dial_warn_tag()],
+ mod_deps = dict:new() :: dialyzer_callgraph:mod_deps(),
+ output = standard_io :: io:device(),
+ output_format = formatted :: format(),
+ filename_opt = basename :: filename_opt(),
+ error_location = ?ERROR_LOCATION :: error_location(),
+ indent_opt = ?INDENT_OPT :: iopt(),
+ output_plt = none :: file:filename(),
+ plt_info = none :: 'none' | #iplt_info{},
+ report_mode = normal :: rep_mode(),
+ return_status = ?RET_NOTHING_SUSPICIOUS :: dial_ret(),
+ warning_modules = [] :: [module()],
+ stored_warnings = [] :: [raw_warning()]
+ }).
+
+-type incrementality_reason() ::
+ no_stored_warnings_in_plt
+ | plt_built_with_different_version
+ | new_plt_file
+ | warnings_changed
+ | {incremental_changes, NumModulesChangedOrRemoved :: non_neg_integer()}.
+
+-record(incrementality_metrics,
+ {total_modules :: non_neg_integer(),
+ analysed_modules :: non_neg_integer(),
+ reason :: incrementality_reason()
+ }).
+
+%%--------------------------------------------------------------------
+
+-spec start(#options{}) -> {dial_ret(), [dial_warning()]}.
+
+start(Opts) ->
+ {{Ret,Warns}, _ModulesAnalyzed} = start_report_modules_analyzed(Opts),
+ {Ret,Warns}.
+
+-spec start_report_modules_analyzed(#options{}) ->
+ {{dial_ret(), [dial_warning()]}, [module()]}.
+
+start_report_modules_analyzed(#options{analysis_type = incremental} = Options) ->
+ {{Ret, Warn}, _Changed, Analyzed} =
+ start_report_modules_changed_and_analyzed(Options),
+ {{Ret, Warn}, Analyzed}.
+
+-spec start_report_modules_changed_and_analyzed(#options{}) ->
+ {{dial_ret(), [dial_warning()]},
+ Changed :: undefined | [module()],
+ Analyzed :: [module()]}.
+
+start_report_modules_changed_and_analyzed( #options{analysis_type = incremental} = Options) ->
+ Opts1 = init_opts_for_incremental(Options),
+ assert_metrics_file_valid(Opts1),
+ #options{init_plts = [InitPlt], legal_warnings = LegalWarnings} = Opts1,
+ Files = get_files_from_opts(Opts1),
+ case dialyzer_iplt:check_incremental_plt(InitPlt, Opts1, Files) of
+ {ok, #iplt_info{files = Md5, warning_map = none}, ModuleToPathLookup} ->
+ report_no_stored_warnings(Opts1, Md5),
+ PltInfo = #iplt_info{files = Md5, legal_warnings = LegalWarnings},
+ write_module_to_path_lookup(Opts1, ModuleToPathLookup),
+ enrich_with_modules_changed(do_analysis(maps:values(ModuleToPathLookup), Opts1, dialyzer_plt:new(), PltInfo), []);
+ {ok, #iplt_info{warning_map=WarningMap, files = Md5}, ModuleToPathLookup} ->
+ report_stored_warnings_no_changes(Opts1, Md5),
+ write_module_to_path_lookup(Opts1, ModuleToPathLookup),
+ enrich_with_modules_changed(return_existing_errors(Opts1, WarningMap), []);
+ {old_version, Md5, ModuleToPathLookup} ->
+ report_different_plt_version(Opts1, Md5),
+ PltInfo = #iplt_info{files = Md5, legal_warnings = LegalWarnings},
+ write_module_to_path_lookup(Opts1, ModuleToPathLookup),
+ enrich_with_modules_changed(do_analysis(maps:values(ModuleToPathLookup), Opts1, dialyzer_plt:new(), PltInfo), undefined);
+ {new_file, Md5, ModuleToPathLookup} ->
+ report_new_plt_file(Opts1, InitPlt, Md5),
+ PltInfo = #iplt_info{files = Md5, legal_warnings = LegalWarnings},
+ write_module_to_path_lookup(Opts1, ModuleToPathLookup),
+ enrich_with_modules_changed(do_analysis(maps:values(ModuleToPathLookup), Opts1, dialyzer_plt:new(), PltInfo), undefined);
+ {differ, Md5, _DiffMd5, _ModDeps, none, ModuleToPathLookup} ->
+ report_no_stored_warnings(Opts1, Md5),
+ PltInfo = #iplt_info{files = Md5, legal_warnings = LegalWarnings},
+ AllFiles = maps:values(ModuleToPathLookup),
+ write_module_to_path_lookup(Opts1, ModuleToPathLookup),
+ enrich_with_modules_changed(do_analysis(AllFiles, Opts1, dialyzer_plt:new(), PltInfo), undefined);
+ {differ, Md5, DiffMd5, ModDeps, WarningMap, ModuleToPathLookup} ->
+ report_incremental_analysis_needed(Opts1, DiffMd5),
+ {AnalFiles, ModsToRemove, ModDepsInRemainingPlt} =
+ expand_dependent_modules(Md5, DiffMd5, ModDeps, ModuleToPathLookup),
+ WarningsInRemainingPlt =
+ sets:fold(fun(Mod, Acc) -> maps:remove(Mod, Acc) end,
+ WarningMap, ModsToRemove),
+ Plt = clean_plt(InitPlt, ModsToRemove),
+
+ PltInfo = #iplt_info{files = Md5,
+ mod_deps = ModDepsInRemainingPlt,
+ warning_map = WarningsInRemainingPlt,
+ legal_warnings = LegalWarnings},
+ ChangedOrRemovedMods = [ChangedOrRemovedMod || {_, ChangedOrRemovedMod} <- DiffMd5],
+ case AnalFiles =:= [] of
+ true ->
+ %% Only removed stuff that's unused. Just write the PLT.
+ report_stored_warnings_only_safe_removals(Opts1, Md5, DiffMd5),
+ dialyzer_iplt:to_file(Opts1#options.output_plt, Plt, ModDepsInRemainingPlt, PltInfo),
+ write_module_to_path_lookup(Opts1, ModuleToPathLookup),
+ enrich_with_modules_changed(return_existing_errors(Opts1, WarningsInRemainingPlt), ChangedOrRemovedMods);
+ false ->
+ report_degree_of_incrementality(Opts1, Md5, DiffMd5, AnalFiles),
+ write_module_to_path_lookup(Opts1, ModuleToPathLookup),
+ enrich_with_modules_changed(do_analysis(AnalFiles, Opts1, Plt, PltInfo), ChangedOrRemovedMods)
+ end;
+ {legal_warnings_changed, Md5, ModuleToPathLookup} ->
+ report_change_in_legal_warnings(Opts1, Md5),
+ PltInfo = #iplt_info{files = Md5, legal_warnings = LegalWarnings},
+ AllFiles = maps:values(ModuleToPathLookup),
+ write_module_to_path_lookup(Opts1, ModuleToPathLookup),
+ enrich_with_modules_changed(do_analysis(AllFiles, Opts1, dialyzer_plt:new(), PltInfo), undefined);
+ {error, not_valid} ->
+ Msg = io_lib:format("The file: ~ts is not a valid PLT file\n~s",
+ [InitPlt, default_plt_error_msg()]),
+ cl_error(Msg);
+ {error, read_error} ->
+ Msg = io_lib:format("Could not read the PLT: ~ts\n~s",
+ [InitPlt, default_plt_error_msg()]),
+ cl_error(Msg)
+ end.
+
+-spec enrich_with_modules_changed({{Ret :: dial_ret(), Warns :: [dial_warning()]}, Analyzed :: [module()]}, Changed :: undefined | [module()]) ->
+ {{dial_ret(), [dial_warning()]}, Changed :: undefined | [module()], Analyzed :: [module()]}.
+
+enrich_with_modules_changed({{Ret,Warns}, Analyzed}, Changed) ->
+ {{Ret,Warns}, Changed, Analyzed}.
+
+default_plt_error_msg() ->
+ "Remove the broken PLT file or point to the correct location.\n".
+
+init_opts_for_incremental(Opts) ->
+ InitPlt =
+ case Opts#options.init_plts of
+ []-> dialyzer_iplt:get_default_iplt_filename();
+ [Plt] -> Plt;
+ Plts ->
+ Msg =
+ io_lib:format("Incremental mode does not support multiple PLT files (~ts)\n",
+ [format_plts(Plts)]),
+ cl_error(Msg)
+ end,
+ OutputPlt =
+ case Opts#options.output_plt of
+ none -> InitPlt;
+ ExplicitlySetOutputPlt -> ExplicitlySetOutputPlt
+ end,
+ Opts#options{
+ analysis_type = incremental,
+ defines = [],
+ from = byte_code,
+ init_plts = [InitPlt],
+ include_dirs = [],
+ output_plt = OutputPlt,
+ use_contracts = true,
+ get_warnings = true
+ }.
+
+assert_metrics_file_valid(#options{metrics_file = none}) ->
+ ok;
+
+assert_metrics_file_valid(#options{metrics_file = MetricsFile}) ->
+ case check_if_writable(MetricsFile) of
+ true -> ok;
+ false ->
+ Msg = io_lib:format(" The metrics file ~ts is not writable", [MetricsFile]),
+ cl_error(Msg)
+ end.
+
+write_metrics_file(#options{metrics_file = none}, _Format, _Args) ->
+ ok;
+
+write_metrics_file(#options{metrics_file = MetricsFile}, Format, Args) ->
+ case file:write_file(MetricsFile, io_lib:fwrite(Format, Args)) of
+ ok -> ok;
+ {error, Reason} ->
+ Msg = io_lib:format("Could not write metrics file ~ts: ~w\n",
+ [MetricsFile, Reason]),
+ throw({dialyzer_error, Msg})
+ end.
+
+write_metrics_file(
+ Opts,
+ #incrementality_metrics{
+ total_modules = NumTotalModules,
+ analysed_modules = NumAnalysedModules,
+ reason = Reason}
+ ) ->
+
+ ReasonDescription =
+ case Reason of
+ no_stored_warnings_in_plt -> "no_stored_warnings_in_plt";
+ plt_built_with_different_version -> "plt_built_with_different_version";
+ new_plt_file -> "new_plt_file";
+ warnings_changed -> "warnings_changed";
+ {incremental_changes, NumChangedModules} ->
+ io_lib:format("incremental_changes\nchanged_or_removed_modules: ~B", [NumChangedModules])
+ end,
+
+ write_metrics_file(
+ Opts,
+ "total_modules: ~B\nanalysed_modules: ~B\nreason: ~s\n",
+ [NumTotalModules, NumAnalysedModules, ReasonDescription]).
+
+write_module_to_path_lookup(#options{module_lookup_file = none}, _ModuleToPathLookup) ->
+ ok;
+
+write_module_to_path_lookup(#options{module_lookup_file = LookupFile}, ModuleToPathLookup) ->
+ Output = [io_lib:fwrite("~ts, ~ts\n", [atom_to_list(ModuleName), ModulePath]) ||
+ ModuleName := ModulePath <- ModuleToPathLookup],
+ case file:write_file(LookupFile, Output) of
+ ok -> ok;
+ {error, Reason} ->
+ Msg = io_lib:format("Could not write module lookup file ~ts: ~w\n",
+ [LookupFile, Reason]),
+ throw({dialyzer_error, Msg})
+ end.
+
+
+report_new_plt_file(#options{report_mode = ReportMode} = Opts, InitPlt, Md5) ->
+ NumTotalModules = length(Md5),
+ Metrics =
+ #incrementality_metrics{
+ total_modules = NumTotalModules,
+ analysed_modules = NumTotalModules,
+ reason = new_plt_file
+ },
+ case ReportMode of
+ quiet -> ok;
+ normal -> ok;
+ verbose -> io:format("PLT does not yet exist at ~s, so an analysis must be run for ~w modules to populate it\n", [InitPlt, NumTotalModules])
+ end,
+ write_metrics_file(Opts, Metrics).
+
+report_different_plt_version(#options{report_mode = ReportMode} = Opts, Md5) ->
+ NumTotalModules = length(Md5),
+ Metrics =
+ #incrementality_metrics{
+ total_modules = NumTotalModules,
+ analysed_modules = NumTotalModules,
+ reason = plt_built_with_different_version
+ },
+ case ReportMode of
+ quiet -> ok;
+ normal -> ok;
+ verbose -> io:format("PLT is for a different Dialyzer version, so an analysis must be run for ~w modules to rebuild it\n", [NumTotalModules])
+ end,
+ write_metrics_file(Opts, Metrics).
+
+report_stored_warnings_no_changes(#options{report_mode = ReportMode} = Opts, Md5) ->
+ NumTotalModules = length(Md5),
+ Metrics =
+ #incrementality_metrics{
+ total_modules = NumTotalModules,
+ analysed_modules = 0,
+ reason = {incremental_changes, 0}
+ },
+ case ReportMode of
+ quiet -> ok;
+ normal -> ok;
+ verbose -> io:format("PLT has fully cached the request, so no additional analysis is needed\n", [])
+ end,
+ write_metrics_file(Opts, Metrics).
+
+report_stored_warnings_only_safe_removals(#options{report_mode = ReportMode} = Opts, Md5, Removed) ->
+ NumTotalModules = length(Md5),
+ NumRemovedModuled = length(Removed),
+ Metrics =
+ #incrementality_metrics{
+ total_modules = NumTotalModules,
+ analysed_modules = 0,
+ reason = {incremental_changes, NumRemovedModuled}
+ },
+ case ReportMode of
+ quiet -> ok;
+ normal -> ok;
+ verbose -> io:format("PLT has fully cached the request because nothing depended on the file removed, so no additional analysis is needed\n", [])
+ end,
+ write_metrics_file(Opts, Metrics).
+
+report_no_stored_warnings(#options{report_mode = ReportMode} = Opts, Md5) ->
+ NumTotalModules = length(Md5),
+ Metrics =
+ #incrementality_metrics{
+ total_modules = NumTotalModules,
+ analysed_modules = NumTotalModules,
+ reason = no_stored_warnings_in_plt
+ },
+ case ReportMode of
+ quiet -> ok;
+ normal -> ok;
+ verbose ->
+ io:format(
+ "PLT does not contain cached warnings, so an analysis must be run for ~w modules to rebuild it\n",
+ [length(Md5)]
+ )
+ end,
+ write_metrics_file(Opts, Metrics).
+
+report_incremental_analysis_needed(#options{report_mode = ReportMode}, DiffMd5) ->
+ case ReportMode of
+ quiet -> ok;
+ normal -> io:format("There have been changes to analyze\n", []);
+ verbose -> report_md5_diff(DiffMd5)
+ end.
+
+report_degree_of_incrementality(#options{report_mode = ReportMode} = Opts, Md5, ChangedOrRemovedFiles, FilesThatNeedAnalysis) ->
+ NumTotalModules = length(Md5),
+ NumChangedOrRemovedModules = length(ChangedOrRemovedFiles),
+ NumAnalysedModules = length(FilesThatNeedAnalysis),
+ Metrics =
+ #incrementality_metrics{
+ total_modules = NumTotalModules,
+ analysed_modules = NumAnalysedModules,
+ reason = {incremental_changes, NumChangedOrRemovedModules}
+ },
+ ReportFun = fun () ->
+ io:format(
+ " Of the ~B files being tracked, ~B have been changed or removed, "
+ "resulting in ~B requiring analysis because they depend on those changes\n",
+ [NumTotalModules, NumChangedOrRemovedModules, NumAnalysedModules])
+ end,
+ case ReportMode of
+ quiet -> ok;
+ normal -> ReportFun();
+ verbose ->
+ ReportFun(),
+ io:format(" Modules which will be analysed: ~p\n", [[path_to_mod(P) || P <- FilesThatNeedAnalysis]])
+ end,
+ write_metrics_file(Opts, Metrics).
+
+report_change_in_legal_warnings(#options{report_mode = ReportMode} = Opts, Md5) ->
+ NumTotalModules = length(Md5),
+ Metrics =
+ #incrementality_metrics{
+ total_modules = NumTotalModules,
+ analysed_modules = NumTotalModules,
+ reason = warnings_changed
+ },
+ ReportFun = fun () ->
+ io:format(
+ "PLT was built for a different set of enabled warnings, so an analysis must be run for ~w modules to rebuild it\n",
+ [NumTotalModules]
+ )
+ end,
+ case ReportMode of
+ quiet -> ok;
+ normal -> ReportFun();
+ verbose -> ReportFun()
+ end,
+ write_metrics_file(Opts, Metrics).
+
+report_analysis_start(#options{report_mode = quiet}) -> ok;
+report_analysis_start(_) ->
+ io:format("Proceeding with incremental analysis...").
+
+report_elapsed_time(T1, T2, #options{report_mode = ReportMode}) ->
+ case ReportMode of
+ quiet -> ok;
+ _ ->
+ ElapsedTime = T2 - T1,
+ Mins = ElapsedTime div 60000,
+ Secs = (ElapsedTime rem 60000) / 1000,
+ io:format(" done in ~wm~.2fs\n", [Mins, Secs])
+ end.
+
+report_md5_diff(List) ->
+ io:format(" The PLT information is not up to date:\n", []),
+ case [Mod || {removed, Mod} <- List] of
+ [] -> ok;
+ RemovedMods -> io:format(" Removed modules: ~p\n", [RemovedMods])
+ end,
+ case [Mod || {differ, Mod} <- List] of
+ [] -> ok;
+ ChangedMods -> io:format(" Changed modules: ~p\n", [ChangedMods])
+ end.
+%%--------------------------------------------------------------------
+
+
+format_plts([Plt]) -> Plt;
+format_plts([Plt|Plts]) ->
+ Plt ++ ", " ++ format_plts(Plts).
+
+%%--------------------------------------------------------------------
+
+do_analysis(Files, Options, Plt, PltInfo) ->
+ assert_writable(Options#options.output_plt),
+ report_analysis_start(Options),
+ State1 = init_output(Options),
+ State2 = State1#incremental_state{
+ legal_warnings = Options#options.legal_warnings,
+ output_plt = Options#options.output_plt,
+ plt_info = PltInfo,
+ erlang_mode = Options#options.erlang_mode,
+ report_mode = Options#options.report_mode,
+ warning_modules = get_warning_modules_from_opts(Options)
+ },
+ InitAnalysis = #analysis{
+ type = succ_typings,
+ defines = Options#options.defines,
+ include_dirs = Options#options.include_dirs,
+ files = Files,
+ start_from = Options#options.from,
+ timing = Options#options.timing,
+ plt = Plt,
+ use_contracts = Options#options.use_contracts,
+ callgraph_file = Options#options.callgraph_file,
+ mod_deps_file = Options#options.mod_deps_file,
+ solvers = Options#options.solvers
+ },
+ State3 = start_analysis(State2, InitAnalysis),
+ {T1, _} = statistics(wall_clock),
+ RetAndWarns = cl_loop(State3),
+ {T2, _} = statistics(wall_clock),
+ report_elapsed_time(T1, T2, Options),
+ {RetAndWarns, lists:usort([path_to_mod(F) || F <- Files])}.
+
+%%--------------------------------------------------------------------
+
+
+assert_writable(PltFile) ->
+ case check_if_writable(PltFile) of
+ true -> ok;
+ false ->
+ Msg = io_lib:format(" The PLT file ~ts is not writable", [PltFile]),
+ cl_error(Msg)
+ end.
+
+check_if_writable(PltFile) ->
+ case filelib:is_regular(PltFile) of
+ true -> is_writable_file_or_dir(PltFile);
+ false ->
+ case filelib:is_dir(PltFile) of
+ true ->
+ false;
+ false ->
+ DirName = filename:dirname(PltFile),
+ case filelib:is_dir(DirName) of
+ false ->
+ case filelib:ensure_dir(PltFile) of
+ ok ->
+ true;
+ {error, _} ->
+ false
+ end;
+ true ->
+ is_writable_file_or_dir(DirName)
+ end
+ end
+ end.
+
+is_writable_file_or_dir(File) ->
+ case file:read_file_info(File) of
+ {ok, #file_info{access = A}} ->
+ (A =:= write) orelse (A =:= read_write);
+ {error, _} ->
+ false
+ end.
+
+%%--------------------------------------------------------------------
+
+clean_plt(PltFile, RemovedMods) ->
+ %% Clean the plt from the removed modules.
+ Plt = dialyzer_iplt:from_file(PltFile),
+ sets:fold(fun(M, AccPlt) -> dialyzer_plt:delete_module(AccPlt, M) end,
+ Plt, RemovedMods).
+
+expand_dependent_modules(_Md5, DiffMd5, ModDeps, ModuleToPathLookup) ->
+ ChangedMods = sets:from_list([M || {differ, M} <- DiffMd5]),
+ RemovedMods = sets:from_list([M || {removed, M} <- DiffMd5]),
+ BigSet = sets:union(ChangedMods, RemovedMods),
+ BigList = sets:to_list(BigSet),
+ ExpandedSet = expand_dependent_modules_1(BigList, BigSet, ModDeps),
+ NewModDeps = dialyzer_callgraph:strip_module_deps(ModDeps, BigSet),
+ AnalyzeMods = sets:subtract(ExpandedSet, RemovedMods),
+ FilterFun = fun(File) ->
+ Mod = path_to_mod(File),
+ sets:is_element(Mod, AnalyzeMods)
+ end,
+ {[F || F <- maps:values(ModuleToPathLookup), FilterFun(F)], ExpandedSet, NewModDeps}.
+
+expand_dependent_modules_1([Mod|Mods], Included, ModDeps) ->
+ case dict:find(Mod, ModDeps) of
+ {ok, Deps} ->
+ NewDeps = sets:subtract(sets:from_list(Deps), Included),
+ case sets:size(NewDeps) of
+ 0 -> expand_dependent_modules_1(Mods, Included, ModDeps);
+ _ ->
+ NewIncluded = sets:union(Included, NewDeps),
+ expand_dependent_modules_1(sets:to_list(NewDeps) ++ Mods,
+ NewIncluded, ModDeps)
+ end;
+ error ->
+ expand_dependent_modules_1(Mods, Included, ModDeps)
+ end;
+expand_dependent_modules_1([], Included, _ModDeps) ->
+ Included.
+
+path_to_mod(File) ->
+ list_to_atom(filename:basename(File, ".beam")).
+
+init_output(#options{output_file = OutFile,
+ output_plt = OutPlt,
+ output_format = OutFormat,
+ filename_opt = FOpt,
+ indent_opt = IOpt,
+ error_location = EOpt} = Opts) ->
+ State = #incremental_state{output_format = OutFormat,
+ output_plt = OutPlt,
+ filename_opt = FOpt,
+ indent_opt = IOpt,
+ error_location = EOpt,
+ warning_modules = get_warning_modules_from_opts(Opts)},
+ case OutFile =:= none of
+ true ->
+ State;
+ false ->
+ case file:open(OutFile, [write]) of
+ {ok, File} ->
+ %% Warnings and errors can include Unicode characters.
+ ok = io:setopts(File, [{encoding, unicode}]),
+ State#incremental_state{output = File};
+ {error, Reason} ->
+ Msg = io_lib:format("Could not open output file ~tp, Reason: ~p\n",
+ [OutFile, Reason]),
+ cl_error(State, lists:flatten(Msg))
+ end
+ end.
+
+-spec maybe_close_output_file(#incremental_state{}, boolean()) -> 'ok'.
+
+maybe_close_output_file(State, OutputPltInUse) ->
+ case State#incremental_state.output of
+ standard_io -> ok;
+ File when OutputPltInUse -> ok = file:close(File);
+ _File -> ok
+ end.
+
+%% ----------------------------------------------------------------
+%%
+%% Main Loop
+%%
+
+-define(LOG_CACHE_SIZE, 10).
+
+%%-spec cl_loop(#incremental_state{}) ->
+cl_loop(State) ->
+ cl_loop(State, []).
+
+cl_loop(State, LogCache) ->
+ BackendPid = State#incremental_state.backend_pid,
+ receive
+ {BackendPid, log, LogMsg} ->
+ cl_loop(State, lists:sublist([LogMsg|LogCache], ?LOG_CACHE_SIZE));
+ {BackendPid, warnings, Warnings} ->
+ NewState = store_warnings(State, Warnings),
+ cl_loop(NewState, LogCache);
+ {BackendPid, cserver, CodeServer, _Plt} -> % Plt is ignored
+ NewState = State#incremental_state{code_server = CodeServer},
+ cl_loop(NewState, LogCache);
+ {BackendPid, done, NewPlt, _NewDocPlt} ->
+ return_value(State, NewPlt);
+ {BackendPid, ext_calls, ExtCalls} ->
+ cl_loop(State#incremental_state{external_calls = ExtCalls}, LogCache);
+ {BackendPid, ext_types, ExtTypes} ->
+ cl_loop(State#incremental_state{external_types = ExtTypes}, LogCache);
+ {BackendPid, mod_deps, ModDeps} ->
+ NewState = State#incremental_state{mod_deps = ModDeps},
+ cl_loop(NewState, LogCache);
+ {'EXIT', BackendPid, {error, Reason}} ->
+ Msg = failed_anal_msg(Reason, LogCache),
+ cl_error(State, Msg);
+ {'EXIT', BackendPid, Reason} when Reason =/= 'normal' ->
+ Msg = failed_anal_msg(io_lib:format("~p", [Reason]), LogCache),
+ cl_error(State, Msg);
+ _Other ->
+ cl_loop(State, LogCache)
+ end.
+
+-spec failed_anal_msg(string(), [_]) -> nonempty_string().
+
+failed_anal_msg(Reason, LogCache) ->
+ Msg = "Analysis failed with error:\n" ++ lists:flatten(Reason) ++ "\n",
+ case LogCache =:= [] of
+ true -> Msg;
+ false ->
+ Msg ++ "Last messages in the log cache:\n " ++ format_log_cache(LogCache)
+ end.
+
+%%
+%% formats the log cache (treating it as a string) for pretty-printing
+%%
+format_log_cache(LogCache) ->
+ Str = lists:append(lists:reverse(LogCache)),
+ lists:join("\n ", string:lexemes(Str, "\n")).
+
+-spec store_warnings(#incremental_state{}, [raw_warning()]) -> #incremental_state{}.
+
+store_warnings(#incremental_state{stored_warnings = StoredWarnings} = St, Warnings) ->
+ St#incremental_state{stored_warnings = StoredWarnings ++ Warnings}.
+
+-spec cl_error(string()) -> no_return().
+
+cl_error(Msg) ->
+ throw({dialyzer_error, lists:flatten(Msg)}).
+
+-spec cl_error(#incremental_state{}, string()) -> no_return().
+
+cl_error(State, Msg) ->
+ case State#incremental_state.output of
+ standard_io -> ok;
+ Outfile -> io:format(Outfile, "\n~ts\n", [Msg])
+ end,
+ maybe_close_output_file(State, true),
+ throw({dialyzer_error, lists:flatten(Msg)}).
+
+return_value(
+ State =
+ #incremental_state{
+ code_server = CodeServer,
+ mod_deps = ModDeps,
+ output_plt = OutputPlt,
+ plt_info = PltInfo,
+ stored_warnings = NewPLTWarnings
+ },
+ Plt) ->
+ %% Just for now:
+ case CodeServer =:= none of
+ true ->
+ ok;
+ false ->
+ dialyzer_codeserver:delete(CodeServer)
+ end,
+ OldPltWarnings = PltInfo#iplt_info.warning_map,
+ PLTUnknownWarnings = unknown_warnings_by_module(State),
+ PLTWarningMap = dialyzer_iplt:merge_warnings(NewPLTWarnings, PLTUnknownWarnings, OldPltWarnings),
+ PLTWarningList =
+ [Warn || Mod <- maps:keys(PLTWarningMap), Warn <- maps:get(Mod, PLTWarningMap, [])],
+ NewState = State#incremental_state{stored_warnings=PLTWarningList},
+ % Write warnings for all modules to the PLT, even if we're not reporting
+ % them now, so subsequent runs can read them from the PLT
+ dialyzer_iplt:to_file(OutputPlt, Plt, ModDeps, PltInfo#iplt_info{warning_map=PLTWarningMap}),
+ handle_return_and_print(NewState, PLTWarningMap, true).
+
+handle_return_and_print(State, AllWarningsMap, OutputPltInUse) ->
+ WarningModules = State#incremental_state.warning_modules,
+ WarningsToReport =
+ case WarningModules =:= [] of
+ true ->
+ [Warn || Mod <- maps:keys(AllWarningsMap), Warn <- maps:get(Mod, AllWarningsMap, [])];
+ false ->
+ [Warn || Mod <- WarningModules, Warn <- maps:get(Mod, AllWarningsMap, [])]
+ end,
+ RetValue =
+ case WarningsToReport =:= [] of
+ true -> ?RET_NOTHING_SUSPICIOUS;
+ false -> ?RET_DISCREPANCIES
+ end,
+ case State#incremental_state.erlang_mode of
+ false ->
+ #incremental_state{
+ output = Output,
+ output_format = Format,
+ filename_opt = FOpt,
+ indent_opt = IOpt,
+ error_location = EOpt} = State,
+ print_warnings(WarningsToReport, Output, Format, FOpt, IOpt, EOpt),
+ maybe_close_output_file(State, OutputPltInUse),
+ {RetValue, []};
+ true ->
+ {RetValue, set_warning_id(process_warnings(WarningsToReport),
+ State#incremental_state.error_location)}
+ end.
+
+return_existing_errors(Opts, PltWarnings) ->
+ State = init_output(Opts),
+ State1 = State#incremental_state{erlang_mode = Opts#options.erlang_mode},
+ State2 = State1#incremental_state{warning_modules = get_warning_modules_from_opts(Opts)},
+ ModulesAnalyzed = [], % No modules analyzed - we read straight from the cache
+ {handle_return_and_print(State2, PltWarnings, false), ModulesAnalyzed}.
+
+unknown_warnings_by_module(#incremental_state{legal_warnings = LegalWarnings, external_calls=Calls, external_types=Types}) ->
+ case ordsets:is_element(?WARN_UNKNOWN, LegalWarnings) of
+ true ->
+ unknown_functions(Calls) ++ unknown_types(Types);
+ false -> []
+ end.
+
+unknown_functions(Calls) ->
+ [{Mod, {?WARN_UNKNOWN, WarningInfo, {unknown_function, MFA}}} || {MFA, WarningInfo = {_, _, {Mod, _, _}}} <- Calls].
+
+unknown_types(Types) ->
+ [{Mod, {?WARN_UNKNOWN, WarningInfo, {unknown_type, MFA}}} ||
+ {MFA, WarningInfo = {_, _, {Mod, _, _}}} <- Types].
+
+set_warning_id(Warnings, EOpt) ->
+ lists:map(fun({Tag, {File, Location, _MorMFA}, Msg}) ->
+ {Tag, {File, set_location(Location, EOpt)}, Msg}
+ end, Warnings).
+
+set_location({Line, _}, line) ->
+ Line;
+set_location(Location, _EOpt) ->
+ Location.
+
+print_warnings([], _, _, _, _, _) ->
+ ok;
+print_warnings(Warnings, Output, Format, FOpt, IOpt, EOpt) ->
+ PrWarnings = process_warnings(Warnings),
+ case PrWarnings of
+ [] -> ok;
+ [_|_] ->
+ PrWarningsId = set_warning_id(PrWarnings, EOpt),
+ S = case Format of
+ formatted ->
+ Opts = [{filename_opt, FOpt},
+ {indent_opt, IOpt},
+ {error_location, EOpt}],
+ [dialyzer:format_warning(W, Opts) || W <- PrWarningsId];
+ raw ->
+ [io_lib:format("~tp. \n", [W]) ||
+ W <- set_warning_id(PrWarningsId, EOpt)]
+ end,
+ io:format(Output, "\n~ts", [S])
+ end.
+
+-spec process_warnings([raw_warning()]) -> [raw_warning()].
+
+process_warnings(Warnings) ->
+ Warnings1 = lists:keysort(3, Warnings), %% First sort on Warning
+ Warnings2 = lists:keysort(2, Warnings1), %% Sort on file/line (and m/mfa..)
+ remove_duplicate_warnings(Warnings2, []).
+
+remove_duplicate_warnings([Duplicate, Duplicate|Left], Acc) ->
+ remove_duplicate_warnings([Duplicate|Left], Acc);
+remove_duplicate_warnings([NotDuplicate|Left], Acc) ->
+ remove_duplicate_warnings(Left, [NotDuplicate|Acc]);
+remove_duplicate_warnings([], Acc) ->
+ lists:reverse(Acc).
+
+get_files_from_opts(Options) ->
+ Files1 = add_files(Options#options.files),
+ Files2 = add_files_rec(Options#options.files_rec),
+ ordsets:union(Files1, Files2).
+
+get_warning_modules_from_opts(Options) ->
+ Files1 = add_files(Options#options.warning_files),
+ Files2 = add_files_rec(Options#options.warning_files_rec),
+ [path_to_mod(File) || File <- ordsets:union(Files1, Files2)].
+
+add_files_rec(Files) ->
+ add_files(Files, true).
+
+add_files(Files) ->
+ add_files(Files, false).
+
+add_files(Files, Rec) ->
+ Files1 = [filename:absname(F) || F <- Files],
+ Files2 = ordsets:from_list(Files1),
+ Dirs = ordsets:filter(fun(X) -> filelib:is_dir(X) end, Files2),
+ Files3 = ordsets:subtract(Files2, Dirs),
+ Extension = ".beam",
+ Fun = add_file_fun(Extension),
+ lists:foldl(
+ fun(Dir, Acc) ->
+ filelib:fold_files(Dir, Extension, Rec, Fun, Acc)
+ end,
+ Files3,
+ Dirs
+ ).
+
+add_file_fun(Extension) ->
+ fun(File, AccFiles) ->
+ case filename:extension(File) =:= Extension of
+ true ->
+ AbsName = filename:absname(File),
+ ordsets:add_element(AbsName, AccFiles);
+ false -> AccFiles
+ end
+ end.
+
+-spec start_analysis(#incremental_state{}, #analysis{}) -> #incremental_state{}.
+
+start_analysis(State, Analysis) ->
+ Self = self(),
+ LegalWarnings = State#incremental_state.legal_warnings,
+ Fun = fun() ->
+ dialyzer_analysis_callgraph:start(Self, LegalWarnings, Analysis)
+ end,
+ BackendPid = spawn_link(Fun),
+ State#incremental_state{backend_pid = BackendPid}.
diff --git a/lib/dialyzer/src/dialyzer_iplt.erl b/lib/dialyzer/src/dialyzer_iplt.erl
new file mode 100644
index 0000000000..c48e9be48f
--- /dev/null
+++ b/lib/dialyzer/src/dialyzer_iplt.erl
@@ -0,0 +1,594 @@
+%% -*- erlang-indent-level: 2 -*-
+%%
+%% 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.
+
+%%%-------------------------------------------------------------------
+%%% File : dialyzer_iplt.erl
+%%% Description : Interface to display information in the incremental
+%%% persistent lookup tables stored in files.
+%%% Incremental PLTs index files by module name, rather
+%%% than absolute path, for portability, and track warnings
+%%% generated per module, in order to cache them for later
+%%% analyses.
+%%%-------------------------------------------------------------------
+-module(dialyzer_iplt).
+
+-export([check_incremental_plt/3,
+ included_modules/1,
+ from_file/1,
+ get_default_iplt_filename/0,
+ merge_warnings/3,
+ plt_and_info_from_file/1,
+ to_file/4,
+ is_iplt/1,
+ to_file_custom_vsn/6 % Used for testing certain kinds of backwards compatibility
+ ]).
+
+%% Debug utilities
+-export([pp_non_returning/0, pp_mod/1]).
+
+-export_type([module_md5/0, warning_map/0]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+
+%%----------------------------------------------------------------------
+
+-type deep_string() :: string() | [deep_string()].
+
+%%----------------------------------------------------------------------
+
+-record(incremental_data, {mod_deps :: dialyzer_callgraph:mod_deps(),
+ warning_map = none :: warning_map(),
+ legal_warnings = none :: none | dial_warn_tags()}).
+
+-include("dialyzer.hrl").
+
+-type warning_map() :: none | #{module() := [raw_warning()]}.
+-type module_md5() :: {module(), binary()}.
+-type module_file_path_lookup() :: #{module() => file:filename()}.
+
+-record(ifile_plt,
+ {version = "" :: string(),
+ module_md5_list = [] :: [module_md5()],
+ info = term_to_binary(#{}) :: binary(), %% encoded map()
+ contracts = term_to_binary(#{}) :: binary(), %% encoded map()
+ callbacks = term_to_binary(#{}) :: binary(), %% encoded map()
+ types = term_to_binary(#{}) :: binary(), %% encoded map()
+ exported_types = term_to_binary(#{}) :: binary(), %% encoded sets:set()
+ incremental_data = term_to_binary(#incremental_data{}) :: #incremental_data{} | binary(), %% encoded #incremental_data{}
+ implementation_md5 = [] :: [module_md5()]}).
+
+%%----------------------------------------------------------------------
+
+
+-spec get_default_iplt_filename() -> file:filename().
+
+get_default_iplt_filename() ->
+ case os:getenv("DIALYZER_IPLT") of
+ false ->
+ CacheDir = filename:basedir(user_cache, "erlang"),
+ filename:join(CacheDir, ".dialyzer_iplt");
+ UserSpecPlt -> UserSpecPlt
+ end.
+
+-spec plt_and_info_from_file(file:filename()) -> {dialyzer_plt:plt(), #iplt_info{}}.
+
+plt_and_info_from_file(FileName) ->
+ from_file(FileName, true).
+
+-spec from_file(file:filename()) -> dialyzer_plt:plt().
+
+from_file(FileName) ->
+ from_file(FileName, false).
+
+from_file(FileName, ReturnInfo) ->
+ Plt = dialyzer_plt:new(),
+ Fun = fun() -> from_file1(Plt, FileName, ReturnInfo) end,
+ case subproc(Fun) of
+ {ok, Return} ->
+ Return;
+ {error, Msg} ->
+ dialyzer_plt:delete(Plt),
+ plt_error(Msg)
+ end.
+
+from_file1(Plt, FileName, ReturnInfo) ->
+ case get_record_from_file(FileName) of
+ {ok, Rec} ->
+ case check_version(Rec) of
+ error ->
+ Msg = io_lib:format("Old IPLT file ~ts\n", [FileName]),
+ {error, Msg};
+ ok ->
+ #ifile_plt{info = CompressedInfo,
+ contracts = CompressedContracts,
+ callbacks = CompressedCallbacks,
+ types = CompressedTypes,
+ exported_types = CompressedExpTypes} = Rec,
+ FileInfo = binary_to_term(CompressedInfo),
+ FileContracts = binary_to_term(CompressedContracts),
+ FileCallbacks = binary_to_term(CompressedCallbacks),
+ FileTypes = binary_to_term(CompressedTypes),
+ FileExpTypes = binary_to_term(CompressedExpTypes),
+ CallbacksList = maps:to_list(FileCallbacks),
+ CallbacksByModule =
+ [{M, [Cb || {{M1,_,_},_} = Cb <- CallbacksList, M1 =:= M]} ||
+ M <- lists:usort([M || {{M,_,_},_} <- CallbacksList])],
+ #plt{info = ETSInfo,
+ types = ETSTypes,
+ contracts = ETSContracts,
+ callbacks = ETSCallbacks,
+ exported_types = ETSExpTypes} = Plt,
+ [true, true, true] =
+ [ets:insert(ETS, Data) ||
+ {ETS, Data} <- [{ETSInfo, maps:to_list(FileInfo)},
+ {ETSTypes, FileTypes},
+ {ETSContracts, maps:to_list(FileContracts)}]],
+ true = ets:insert(ETSCallbacks, CallbacksByModule),
+ true = ets:insert(ETSExpTypes, [{ET} ||
+ ET <- sets:to_list(FileExpTypes)]),
+ case ReturnInfo of
+ false -> {ok, Plt};
+ true ->
+ IncrementalData = get_incremental_data(Rec),
+ PltInfo =
+ #iplt_info{files = Rec#ifile_plt.module_md5_list,
+ mod_deps = IncrementalData#incremental_data.mod_deps,
+ warning_map = IncrementalData#incremental_data.warning_map,
+ legal_warnings = IncrementalData#incremental_data.legal_warnings},
+ {ok, {Plt, PltInfo}}
+ end
+ end;
+ {error, Reason} ->
+ Msg = io_lib:format("Could not read IPLT file ~ts: ~p\n",
+ [FileName, Reason]),
+ {error, Msg}
+ end.
+
+-spec get_incremental_data(#ifile_plt{}) -> #incremental_data{}.
+get_incremental_data(#ifile_plt{incremental_data = Data}) ->
+ case Data of
+ CompressedData when is_binary(CompressedData) ->
+ binary_to_term(CompressedData);
+ UncompressedData = #incremental_data{} -> % To support older PLTs that didn't have this field compressed
+ UncompressedData
+ end.
+
+-type err_rsn() :: 'not_valid' | 'no_such_file' | 'read_error'.
+
+-spec included_modules(file:filename()) -> {'ok', [module()]}
+ | {'error', err_rsn()}.
+
+included_modules(FileName) ->
+ Fun = fun() -> included_modules1(FileName) end,
+ subproc(Fun).
+
+included_modules1(FileName) ->
+ case get_record_from_file(FileName) of
+ {ok, #ifile_plt{module_md5_list = Md5}} ->
+ {ok, [ModuleName || {ModuleName, _} <- Md5]};
+ {error, _What} = Error ->
+ Error
+ end.
+
+check_version(#ifile_plt{version = ?VSN, implementation_md5 = ImplMd5}) ->
+ case compute_new_md5(ImplMd5, [], [], implementation_module_paths()) of
+ ok -> ok;
+ {differ, _, _} -> error;
+ {error, _} -> error
+ end;
+check_version(#ifile_plt{}) -> error.
+
+get_record_from_file(FileName) ->
+ case file:read_file(FileName) of
+ {ok, Bin} ->
+ try binary_to_term(Bin) of
+ #ifile_plt{} = FilePLT -> {ok, FilePLT};
+ _ -> {error, not_valid}
+ catch
+ _:_ -> {error, not_valid}
+ end;
+ {error, enoent} ->
+ {error, no_such_file};
+ {error, _} ->
+ {error, read_error}
+ end.
+
+-spec is_iplt(file:filename()) -> boolean().
+is_iplt(FileName) ->
+ case get_record_from_file(FileName) of
+ {ok, _} -> true;
+ {error, _} -> false
+ end.
+
+-spec to_file(file:filename(), dialyzer_plt:plt(), dialyzer_callgraph:mod_deps(), #iplt_info{}) -> 'ok'.
+
+%% Write the PLT to file, and deletes the PLT.
+to_file(FileName, Plt, ModDeps, PLTInfo) ->
+ Fun = fun() -> to_file1(FileName, Plt, ModDeps, PLTInfo) end,
+ Return = subproc(Fun),
+ dialyzer_plt:delete(Plt),
+ case Return of
+ ok -> ok;
+ {error, Msg} -> plt_error(Msg)
+ end.
+
+to_file1(FileName, Plt, ModDeps, PltInfo) ->
+ to_file_custom_vsn(FileName, Plt, ModDeps, PltInfo, none, none).
+
+-spec to_file_custom_vsn(
+ file:filename(),
+ dialyzer_plt:plt(),
+ dialyzer_callgraph:mod_deps(),
+ #iplt_info{},
+ none | string(),
+ none | [module_md5()]) -> 'ok'.
+
+to_file_custom_vsn(FileName, Plt, ModDeps, PltInfo, none, ImplMd5) ->
+ to_file_custom_vsn(FileName, Plt, ModDeps, PltInfo, ?VSN, ImplMd5);
+
+to_file_custom_vsn(FileName, Plt, ModDeps, PltInfo, Vsn, none) ->
+ to_file_custom_vsn(FileName, Plt, ModDeps, PltInfo, Vsn, compute_implementation_md5());
+
+to_file_custom_vsn(
+ FileName,
+ #plt{info = ETSInfo, types = ETSTypes, contracts = ETSContracts,
+ callbacks = ETSCallbacks, exported_types = ETSExpTypes},
+ NewModDeps,
+ #iplt_info{files = MD5, mod_deps = OldModDeps, warning_map=NewWarningMap, legal_warnings=LegalWarnings},
+ Vsn,
+ ImplMd5) ->
+ CombinedModDeps = dict:merge(fun(_Key, OldVal, NewVal) ->
+ ordsets:union(OldVal, NewVal)
+ end,
+ OldModDeps, NewModDeps),
+ IncrementalData =
+ #incremental_data{mod_deps=CombinedModDeps, warning_map=NewWarningMap, legal_warnings=LegalWarnings},
+ Callbacks =
+ #{K => V ||
+ {_M, Cbs} <- dialyzer_utils:ets_tab2list(ETSCallbacks),
+ {K, V} <- Cbs},
+ Info = maps:from_list(dialyzer_utils:ets_tab2list(ETSInfo)),
+ Types = dialyzer_utils:ets_tab2list(ETSTypes),
+ Contracts = maps:from_list(dialyzer_utils:ets_tab2list(ETSContracts)),
+ ExpTypes = sets:from_list([E || {E} <- dialyzer_utils:ets_tab2list(ETSExpTypes)], [{version, 2}]),
+ Record = #ifile_plt{version = Vsn,
+ module_md5_list = MD5,
+ info = term_to_binary(Info, [{compressed,9}]),
+ contracts = term_to_binary(Contracts, [{compressed,9}]),
+ callbacks = term_to_binary(Callbacks, [{compressed,9}]),
+ types = term_to_binary(Types, [{compressed,9}]),
+ exported_types = term_to_binary(ExpTypes, [{compressed,9}]),
+ incremental_data = term_to_binary(IncrementalData, [{compressed,9}]),
+ implementation_md5 = ImplMd5},
+ Bin = term_to_binary(Record),
+ case file:write_file(FileName, Bin) of
+ ok -> ok;
+ {error, Reason} ->
+ Msg = io_lib:format("Could not write IPLT file ~ts: ~w\n",
+ [FileName, Reason]),
+ {error, Msg}
+ end.
+
+-spec merge_warnings(none | [raw_warning], [{module(), raw_warning()}], none | warning_map()) -> none | warning_map().
+merge_warnings(none, _, OldWarningMap) -> OldWarningMap;
+merge_warnings(NewWarnings, UnknownWarnings, none) ->
+ convert_to_warning_map(NewWarnings, UnknownWarnings);
+merge_warnings(NewWarnings, UnknownWarnings, OldWarningMap) ->
+ maps:merge(convert_to_warning_map(NewWarnings, UnknownWarnings), OldWarningMap).
+
+convert_to_warning_map(WarningList, UnknownWarnings) ->
+ Temp = lists:foldl(
+ fun({_, {_, _, MorMFA}, _} = Warn, Acc) ->
+ Update = fun(Old) -> [Warn|Old] end,
+ maps:update_with(get_module(MorMFA), Update, [Warn], Acc)
+ end,
+ #{},
+ WarningList),
+ lists:foldl(fun({M, Warn}, Acc) ->
+ Update = fun(Old) -> [Warn|Old] end,
+ maps:update_with(M, Update, [Warn], Acc)
+ end,
+ Temp,
+ UnknownWarnings).
+
+get_module({M,_F,_A}) -> M;
+get_module(M) -> M.
+
+-type md5_diff() :: [{'differ', atom()} | {'removed', atom()}].
+-type check_error() :: err_rsn() | {'no_file_to_remove', file:filename()}.
+
+-spec check_incremental_plt(file:filename(), #options{}, [file:filename()]) ->
+ {'ok', #iplt_info{}, module_file_path_lookup()} |
+ {'old_version', [module_md5()], module_file_path_lookup()} |
+ {'new_file', [module_md5()], module_file_path_lookup()} |
+ {'differ', [module_md5()], md5_diff(), dialyzer_callgraph:mod_deps(), warning_map(), module_file_path_lookup()} |
+ {'legal_warnings_changed', [module_md5()], module_file_path_lookup()} |
+ {'error', check_error()}.
+check_incremental_plt(FileName, Opts, PltFiles) ->
+ Fun = fun() -> check_incremental_plt1(FileName, Opts, PltFiles) end,
+ subproc(Fun).
+
+check_incremental_plt1(FileName, Opts, PltFiles) ->
+ PltModulePathLookup = #{beam_file_to_module(PltFile) => PltFile || PltFile <- PltFiles},
+ case get_record_from_file(FileName) of
+ {ok, #ifile_plt{module_md5_list = Md5} = Rec} ->
+ {RemoveModules, AddModules} = find_files_to_remove_and_add(Md5, maps:keys(PltModulePathLookup)),
+ IncrementalData = get_incremental_data(Rec),
+ PltLegalWarnings = IncrementalData#incremental_data.legal_warnings,
+ LegalWarnings = Opts#options.legal_warnings,
+ LegalWarningsMatch = PltLegalWarnings /= none andalso lists:usort(PltLegalWarnings) =:= lists:usort(LegalWarnings),
+ case check_version_and_compute_md5(Rec, RemoveModules, AddModules, PltModulePathLookup) of
+ ok when not LegalWarningsMatch ->
+ {legal_warnings_changed, Md5, PltModulePathLookup};
+ {differ, NewMd5, _, _, _} when not LegalWarningsMatch ->
+ {legal_warnings_changed, NewMd5, PltModulePathLookup};
+ ok ->
+ {ok, #iplt_info{files = Md5,
+ mod_deps = IncrementalData#incremental_data.mod_deps,
+ warning_map = IncrementalData#incremental_data.warning_map,
+ legal_warnings = IncrementalData#incremental_data.legal_warnings},
+ PltModulePathLookup};
+ {old_version, Md5} ->
+ {old_version, Md5};
+ {differ, NewMd5, DiffMd5, ModDeps, _} ->
+ {differ, NewMd5, DiffMd5, ModDeps, IncrementalData#incremental_data.warning_map, PltModulePathLookup};
+ {old_version, NewMd5} ->
+ {old_version, NewMd5, PltModulePathLookup};
+ {error, Error} ->
+ {error, Error}
+ end;
+ {error, no_such_file} ->
+ {new_file, compute_md5_from_files(PltModulePathLookup), PltModulePathLookup};
+ Error -> Error
+ end.
+
+find_files_to_remove_and_add(Md5, PltModules) ->
+ OldPltFiles = gb_sets:from_list([Name || {Name,_Md5Bin} <- Md5]),
+ NewPltFiles = gb_sets:from_list(PltModules),
+ {gb_sets:to_list(gb_sets:subtract(OldPltFiles, NewPltFiles)),
+ gb_sets:to_list(gb_sets:subtract(NewPltFiles, OldPltFiles))}.
+
+check_version_and_compute_md5(Rec, RemoveFiles, AddFiles, ModuleToPathLookup) ->
+ Md5 = Rec#ifile_plt.module_md5_list,
+ case check_version(Rec) of
+ ok ->
+ case compute_new_md5(Md5, RemoveFiles, AddFiles, ModuleToPathLookup) of
+ ok -> ok;
+ {differ, NewMd5, DiffMd5} ->
+ IncrementalData = get_incremental_data(Rec),
+ {differ,
+ NewMd5,
+ DiffMd5,
+ IncrementalData#incremental_data.mod_deps,
+ IncrementalData#incremental_data.warning_map};
+ {error, _What} = Err -> Err
+ end;
+ error ->
+ case compute_new_md5(Md5, RemoveFiles, AddFiles, ModuleToPathLookup) of
+ ok -> {old_version, Md5};
+ {differ, NewMd5, _DiffMd5} -> {old_version, NewMd5};
+ {error, _What} = Err -> Err
+ end
+ end.
+
+compute_new_md5(Md5, [], [], ModuleToPathLookup) ->
+ compute_new_md5_1(Md5, [], ModuleToPathLookup);
+compute_new_md5(Md5, RemoveFiles0, AddFiles0, ModuleToPathLookup) ->
+ %% Assume that files are first removed and then added. Files that
+ %% are both removed and added will be checked for consistency in the
+ %% normal way.
+ RemoveFiles = RemoveFiles0 -- AddFiles0,
+ AddFiles = AddFiles0 -- RemoveFiles0,
+ InitDiffList = init_diff_list(RemoveFiles, AddFiles),
+ case init_md5_list(Md5, RemoveFiles, AddFiles) of
+ {ok, NewMd5} -> compute_new_md5_1(NewMd5, InitDiffList, ModuleToPathLookup);
+ {error, _What} = Error -> Error
+ end.
+
+compute_new_md5_1(Entries, InitDiffs, ModuleToPathLookup) ->
+ Modules = [Module || {Module, _Md5} <- Entries],
+ ExistingHashes = [Md5 || {_Module, Md5} <- Entries],
+ Files = [maps:get(Module, ModuleToPathLookup) || Module <- Modules],
+ NewHashes = dialyzer_utils:p_map(fun compute_md5_from_file/1, Files),
+ Diffs =
+ lists:zipwith3(
+ fun (Module, BeforeHash, AfterHash) ->
+ case BeforeHash of
+ AfterHash ->
+ none;
+ _ ->
+ {differ, Module}
+ end
+ end,
+ Modules,
+ ExistingHashes,
+ NewHashes),
+ Diffs1 = InitDiffs ++ lists:filter(fun ({differ,_}) -> true; (none) -> false end, Diffs),
+ case Diffs1 of
+ [] ->
+ ok;
+ _ ->
+ ModuleHashes = lists:zip(Modules, NewHashes),
+ {differ, lists:keysort(1, ModuleHashes), Diffs1}
+ end.
+
+-spec implementation_module_paths() -> module_file_path_lookup().
+implementation_module_paths() ->
+ Dir = code:lib_dir(dialyzer),
+ Files1 = ["erl_bif_types.beam", "erl_types.beam"],
+ Files2 = [filename:join([Dir, "ebin", F]) || F <- Files1],
+ #{beam_file_to_module(File) => File || File <- Files2}.
+
+-spec compute_implementation_md5() -> [module_md5()].
+
+compute_implementation_md5() ->
+ Modules = implementation_module_paths(),
+ compute_md5_from_files(Modules).
+
+-spec compute_md5_from_files(module_file_path_lookup()) -> [module_md5()].
+
+compute_md5_from_files(ModuleToPathLookup) ->
+ {Modules,Files} = lists:unzip(maps:to_list(ModuleToPathLookup)),
+ Hashes = dialyzer_utils:p_map(fun compute_md5_from_file/1, Files),
+ lists:keysort(1, lists:zip(Modules, Hashes)).
+
+compute_md5_from_file(File) ->
+ case beam_lib:chunks(File, [debug_info]) of
+ {ok, {ModuleName, [{debug_info, {debug_info_v1, Backend, Data}}]}} ->
+ %% We cannot use beam_lib:md5 because it includes
+ %% non-portable or otherwise irrelvant data that would
+ %% cause the PLT to be invalidated needlessly too often
+ case Backend:debug_info(erlang_v1, ModuleName, Data, []) of
+ {ok, Code} ->
+ StabilisedCode = lists:filtermap(fun (Form) -> make_stable(ModuleName, Form) end, Code),
+ StabilisedCodeBin = erlang:term_to_binary(StabilisedCode),
+ erlang:md5(StabilisedCodeBin);
+ {error, Reason} ->
+ Msg = io_lib:format("Could not compute MD5 for .beam (debug_info error) - did you forget to set the debug_info compilation option? ~ts ~tw\n", [File, Reason]),
+ throw({dialyzer_error, Msg})
+ end;
+ {ok, {_, [{debug_info, no_debug_info}]}} ->
+ Msg = io_lib:format("Could not compute MD5 for .beam (debug_info missing): ~ts\n", [File]),
+ throw({dialyzer_error, Msg});
+ {error, beam_lib, {file_error, _, enoent}} ->
+ Msg = io_lib:format("File not found: ~ts\n", [File]),
+ plt_error(Msg);
+ {error, beam_lib, _} ->
+ Msg = io_lib:format("Could not compute MD5 for .beam: ~ts\n", [File]),
+ plt_error(Msg)
+ end.
+
+%% Absolute paths in -file attributes make beam file hashes brittle, since the
+%% same beam built elsewhere will contain a different absolute path, despite
+%% being semantically identical.
+%%
+%% Here, we replace the full path with just the basename. This is very similar
+%% to the effect of the +deterministic option, by by doing this rewriting here,
+%% we gain some of those determinism benefits even when the build is not run
+%% with +deterministic.
+make_stable(_, {attribute, Anno, file, {SrcFilePath, Line}}) ->
+ {true, {attribute, Anno, file, {filename:basename(SrcFilePath), Line}}};
+
+make_stable(_, Attr) ->
+ {true, Attr}.
+
+init_diff_list(RemoveFiles, AddFiles) ->
+ RemoveSet0 = sets:from_list([beam_file_to_module(F) || F <- RemoveFiles]),
+ AddSet0 = sets:from_list([beam_file_to_module(F) || F <- AddFiles]),
+ DiffSet = sets:intersection(AddSet0, RemoveSet0),
+ RemoveSet = sets:subtract(RemoveSet0, DiffSet),
+ %% Added files and diff files will appear as diff files from the md5 check.
+ [{removed, F} || F <- sets:to_list(RemoveSet)].
+
+init_md5_list(Md5, RemoveFiles, AddFiles) ->
+ Mods = [{remove, beam_file_to_module(F)} || F <- RemoveFiles] ++ [{add, beam_file_to_module(F)} || F <- AddFiles],
+ DiffMods = lists:keysort(2, Mods),
+ Md5Sorted = lists:keysort(1, Md5),
+ init_md5_list_1(Md5Sorted, DiffMods, []).
+
+init_md5_list_1([{Mod, _Md5}|Md5Left], [{remove, Mod}|DiffLeft], Acc) ->
+ init_md5_list_1(Md5Left, DiffLeft, Acc);
+init_md5_list_1([{Mod, _Md5} = Entry|Md5Left], [{add, Mod}|DiffLeft], Acc) ->
+ init_md5_list_1(Md5Left, DiffLeft, [Entry|Acc]);
+init_md5_list_1([{Mod1, _Md5} = Entry|Md5Left] = Md5List,
+ [{Tag, Mod2}|DiffLeft] = DiffList, Acc) ->
+ case Mod1 < Mod2 of
+ true -> init_md5_list_1(Md5Left, DiffList, [Entry|Acc]);
+ false ->
+ %% Just an assert.
+ true = Mod1 > Mod2,
+ case Tag of
+ add -> init_md5_list_1(Md5List, DiffLeft, [{Mod2, <<>>}|Acc]);
+ remove -> {error, {no_file_to_remove, Mod2}}
+ end
+ end;
+init_md5_list_1([], DiffList, Acc) ->
+ AddMods = [{M, <<>>} || {add, M} <- DiffList],
+ {ok, lists:reverse(Acc, AddMods)};
+init_md5_list_1(Md5List, [], Acc) ->
+ {ok, lists:reverse(Acc, Md5List)}.
+
+
+subproc(Fun) ->
+ F = fun() ->
+ exit(try Fun()
+ catch throw:T ->
+ {thrown, T}
+ end)
+ end,
+ {Pid, Ref} = erlang:spawn_monitor(F),
+ receive {'DOWN', Ref, process, Pid, Return} ->
+ case Return of
+ {thrown, T} -> throw(T);
+ _ -> Return
+ end
+ end.
+
+
+beam_file_to_module(Filename) ->
+ list_to_atom(filename:basename(Filename, ".beam")).
+
+
+-spec plt_error(deep_string()) -> no_return().
+
+plt_error(Msg) ->
+ throw({dialyzer_error, lists:flatten(Msg)}).
+
+
+%%---------------------------------------------------------------------------
+%% Debug utilities.
+
+-spec pp_non_returning() -> 'ok'.
+
+pp_non_returning() ->
+ PltFile = get_default_iplt_filename(),
+ Plt = from_file(PltFile),
+ List = ets:tab2list(Plt#plt.info),
+ Unit = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List,
+ erl_types:t_is_unit(Ret)],
+ None = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List,
+ erl_types:t_is_none(Ret)],
+ io:format("=========================================\n"),
+ io:format("= Loops =\n"),
+ io:format("=========================================\n\n"),
+ lists:foreach(fun({{M, F, _}, Type}) ->
+ io:format("~w:~tw~ts.\n",
+ [M, F, dialyzer_utils:format_sig(Type)])
+ end, lists:sort(Unit)),
+ io:format("\n"),
+ io:format("=========================================\n"),
+ io:format("= Errors =\n"),
+ io:format("=========================================\n\n"),
+ lists:foreach(fun({{M, F, _}, Type}) ->
+ io:format("~w:~w~s.\n",
+ [M, F, dialyzer_utils:format_sig(Type)])
+ end, lists:sort(None)),
+ dialyzer_plt:delete(Plt).
+
+-spec pp_mod(atom()) -> 'ok'.
+
+pp_mod(Mod) when is_atom(Mod) ->
+ PltFile = get_default_iplt_filename(),
+ Plt = from_file(PltFile),
+ case dialyzer_plt:lookup_module(Plt, Mod) of
+ {value, List} ->
+ lists:foreach(fun({{_, F, _}, Ret, Args}) ->
+ T = erl_types:t_fun(Args, Ret),
+ S = dialyzer_utils:format_sig(T),
+ io:format("-spec ~tw~ts.\n", [F, S])
+ end, lists:sort(List));
+ none ->
+ io:format("dialyzer: Found no module named '~s' in the IPLT\n", [Mod])
+ end,
+ dialyzer_plt:delete(Plt).
diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl
index 5a56e0b0cb..de35f5f204 100644
--- a/lib/dialyzer/src/dialyzer_options.erl
+++ b/lib/dialyzer/src/dialyzer_options.erl
@@ -18,7 +18,7 @@
-module(dialyzer_options).
--export([build/1, build_warnings/2]).
+-export([build/1, build_warnings/2, get_default_config_filename/0]).
-include("dialyzer.hrl").
@@ -32,26 +32,28 @@
build(Opts) ->
DefaultWarns = [?WARN_RETURN_NO_RETURN,
- ?WARN_NOT_CALLED,
- ?WARN_NON_PROPER_LIST,
- ?WARN_FUN_APP,
- ?WARN_MATCHING,
- ?WARN_OPAQUE,
- ?WARN_CALLGRAPH,
- ?WARN_FAILING_CALL,
- ?WARN_BIN_CONSTRUCTION,
- ?WARN_MAP_CONSTRUCTION,
- ?WARN_CONTRACT_RANGE,
- ?WARN_CONTRACT_TYPES,
- ?WARN_CONTRACT_SYNTAX,
- ?WARN_BEHAVIOUR,
- ?WARN_UNDEFINED_CALLBACK],
+ ?WARN_NOT_CALLED,
+ ?WARN_NON_PROPER_LIST,
+ ?WARN_FUN_APP,
+ ?WARN_MATCHING,
+ ?WARN_OPAQUE,
+ ?WARN_CALLGRAPH,
+ ?WARN_FAILING_CALL,
+ ?WARN_BIN_CONSTRUCTION,
+ ?WARN_MAP_CONSTRUCTION,
+ ?WARN_CONTRACT_RANGE,
+ ?WARN_CONTRACT_TYPES,
+ ?WARN_CONTRACT_SYNTAX,
+ ?WARN_BEHAVIOUR,
+ ?WARN_UNDEFINED_CALLBACK,
+ ?WARN_UNKNOWN],
DefaultWarns1 = ordsets:from_list(DefaultWarns),
- InitPlt = dialyzer_plt:get_default_plt(),
- DefaultOpts = #options{},
- DefaultOpts1 = DefaultOpts#options{legal_warnings = DefaultWarns1,
- init_plts = [InitPlt]},
try
+ WarningsFromConfig = proplists:get_value(warnings, get_config(), []),
+ update_path_from_config(),
+ DefaultWarns2 = build_warnings(WarningsFromConfig, DefaultWarns1),
+ DefaultOpts = #options{},
+ DefaultOpts1 = DefaultOpts#options{legal_warnings = DefaultWarns2},
Opts1 = preprocess_opts(Opts),
Env = env_default_opts(),
ErrLoc = proplists:get_value(error_location, Env, ?ERROR_LOCATION),
@@ -62,6 +64,31 @@ build(Opts) ->
throw:{dialyzer_options_error, Msg} -> {error, Msg}
end.
+update_path_from_config() ->
+ Config = get_config(),
+ PAs = proplists:get_value(add_pathsa, Config, []),
+ PZs = proplists:get_value(add_pathsz, Config, []),
+ case is_list(PAs) of
+ true -> ok;
+ false -> bad_option("Bad list of paths in config", {add_pathsa, PAs})
+ end,
+ case is_list(PZs) of
+ true -> ok;
+ false -> bad_option("Bad list of paths in config", {add_pathsz, PZs})
+ end,
+ %% Add paths one-by-one so that we can report issues
+ %% if any path is invalid
+ %% (code:add_pathsa/1 and code:add_pathsz/1 always return ok)
+ [ case code:add_patha(PA) of
+ true -> ok;
+ {error, _} -> bad_option("Failed to add path from config", {add_patha, PA})
+ end || PA <- PAs ],
+ [ case code:add_pathz(PZ) of
+ true -> ok;
+ {error, _} -> bad_option("Failed to add path from config", {add_pathz, PZ})
+ end || PZ <- PZs ],
+ ok.
+
preprocess_opts([]) -> [];
preprocess_opts([{init_plt, File}|Opts]) ->
[{plts, [File]}|preprocess_opts(Opts)];
@@ -69,14 +96,45 @@ preprocess_opts([Opt|Opts]) ->
[Opt|preprocess_opts(Opts)].
postprocess_opts(Opts = #options{}) ->
- check_file_existence(Opts),
- Opts1 = check_output_plt(Opts),
- adapt_get_warnings(Opts1).
+ Opts1 =
+ case {Opts#options.init_plts, Opts#options.analysis_type} of
+ {[],incremental} -> Opts#options{init_plts=[dialyzer_iplt:get_default_iplt_filename()]};
+ {[],_} -> Opts#options{init_plts=[dialyzer_cplt:get_default_cplt_filename()]};
+ {[_|_],_} -> Opts
+ end,
+ check_file_existence(Opts1),
+ check_metrics_file_validity(Opts1),
+ check_module_lookup_file_validity(Opts1),
+ Opts2 = check_output_plt(Opts1),
+ check_init_plt_kind(Opts2),
+ Opts3 = manage_default_incremental_apps(Opts2),
+ adapt_get_warnings(Opts3).
+
+check_metrics_file_validity(#options{analysis_type = incremental, metrics_file = none}) ->
+ ok;
+check_metrics_file_validity(#options{analysis_type = incremental, metrics_file = FileName}) ->
+ assert_filename(FileName);
+check_metrics_file_validity(#options{analysis_type = _NotIncremental, metrics_file = none}) ->
+ ok;
+check_metrics_file_validity(#options{analysis_type = _NotIncremental, metrics_file = FileName}) ->
+ bad_option("A metrics filename may only be given when in incremental mode", {metrics_file, FileName}).
+
+check_module_lookup_file_validity(#options{analysis_type = incremental, module_lookup_file = none}) ->
+ ok;
+check_module_lookup_file_validity(#options{analysis_type = incremental, module_lookup_file = FileName}) ->
+ assert_filename(FileName);
+check_module_lookup_file_validity(#options{analysis_type = _NotIncremental, module_lookup_file = none}) ->
+ ok;
+check_module_lookup_file_validity(#options{analysis_type = _NotIncremental, module_lookup_file = FileName}) ->
+ bad_option("A module lookup filename may only be given when in incremental mode", {module_lookup_file, FileName}).
check_file_existence(#options{analysis_type = plt_remove}) -> ok;
-check_file_existence(#options{files = Files, files_rec = FilesRec}) ->
+check_file_existence(#options{files = Files, files_rec = FilesRec,
+ warning_files = WarningFiles, warning_files_rec = WarningFilesRec}) ->
assert_filenames_exist(Files),
- assert_filenames_exist(FilesRec).
+ assert_filenames_exist(FilesRec),
+ assert_filenames_exist(WarningFiles),
+ assert_filenames_exist(WarningFilesRec).
check_output_plt(Opts = #options{analysis_type = Mode, from = From,
output_plt = OutPLT}) ->
@@ -98,6 +156,89 @@ check_output_plt(Opts = #options{analysis_type = Mode, from = From,
end
end.
+check_init_plt_kind(#options{analysis_type = incremental, init_plts = InitPlts}) ->
+ RunCheck = fun(FileName) ->
+ case dialyzer_plt:plt_kind(FileName) of
+ no_file -> ok;
+ iplt -> ok;
+ cplt ->
+ bad_option("Given file is a classic PLT file, "
+ "but in incremental mode, "
+ "an incremental PLT file is expected",
+ {init_plt_file, FileName});
+ bad_file ->
+ bad_option("Given file is not a PLT file", {init_plt_file, FileName})
+ end
+ end,
+ lists:foreach(RunCheck, InitPlts);
+check_init_plt_kind(#options{analysis_type = _NotIncremental, init_plts = InitPlts}) ->
+ RunCheck = fun(FileName) ->
+ case dialyzer_plt:plt_kind(FileName) of
+ no_file -> ok;
+ cplt -> ok;
+ iplt ->
+ bad_option("Given file is an incremental PLT file, "
+ "but outside of incremental mode, "
+ "a classic PLT file is expected",
+ {init_plt_file, FileName});
+ bad_file ->
+ bad_option("Given file is not a PLT file", {init_plt_file, FileName})
+ end
+ end,
+ lists:foreach(RunCheck, InitPlts).
+
+%% If no apps are set explicitly, we fall back to config
+manage_default_incremental_apps(Opts = #options{analysis_type = incremental, files = [], files_rec = [], warning_files = [], warning_files_rec = []}) ->
+ set_default_apps(get_config(), Opts);
+manage_default_incremental_apps(Opts) ->
+ Opts.
+
+set_default_apps([ConfigElem|MoreConfig], Opts) ->
+ case ConfigElem of
+ {incremental, {default_apps, DefaultApps}=Term} when
+ is_list(DefaultApps) ->
+ AppDirs = get_app_dirs(DefaultApps),
+ assert_filenames_form(Term, AppDirs),
+ Opts#options{files_rec = AppDirs};
+ {incremental, {default_apps, DefaultApps}=TermApps,
+ {default_warning_apps, DefaultWarningApps}=TermWarns} when
+ is_list(DefaultApps), is_list(DefaultWarningApps) ->
+ AppDirs = get_app_dirs(DefaultApps ++ DefaultWarningApps),
+ assert_filenames_form(TermApps, AppDirs),
+ WarningAppDirs = get_app_dirs(DefaultWarningApps),
+ assert_filenames_form(TermWarns, WarningAppDirs),
+ Opts#options{files_rec = AppDirs, warning_files_rec = WarningAppDirs};
+ _ when element(1, ConfigElem) =:= incremental ->
+ bad_option("Given Erlang terms in 'incremental' section could not be understood as Dialyzer config", ConfigElem);
+ _ ->
+ set_default_apps(MoreConfig, Opts)
+ end;
+set_default_apps([], Opts) ->
+ Opts.
+
+get_config() ->
+ DefaultConfig = get_default_config_filename(),
+ case filelib:is_regular(DefaultConfig) of
+ true ->
+ case file:consult(DefaultConfig) of
+ {ok, Config} when is_list(Config) -> Config;
+ {error, Reason} ->
+ bad_option(file:format_error(Reason), DefaultConfig)
+ end;
+ false ->
+ []
+ end.
+
+% Intended to work like dialyzer_iplt:get_default_iplt_filename()
+-spec get_default_config_filename() -> string().
+get_default_config_filename() ->
+ case os:getenv("DIALYZER_CONFIG") of
+ false ->
+ CacheDir = filename:basedir(user_config, "erlang"),
+ filename:join(CacheDir, "dialyzer.config");
+ UserSpecConfig -> UserSpecConfig
+ end.
+
adapt_get_warnings(Opts = #options{analysis_type = Mode,
get_warnings = Warns}) ->
%% Warnings are off by default in plt mode, and on by default in
@@ -138,6 +279,18 @@ build_options([{OptionName, Value} = Term|Rest], Options) ->
OldValues = Options#options.files_rec,
assert_filenames_form(Term, Value),
build_options(Rest, Options#options{files_rec = Value ++ OldValues});
+ warning_apps ->
+ OldValues = Options#options.warning_files_rec,
+ AppDirs = get_app_dirs(Value),
+ assert_filenames_form(Term, AppDirs),
+ build_options(Rest, Options#options{warning_files_rec = AppDirs ++ OldValues});
+ warning_files ->
+ assert_filenames_form(Term, Value),
+ build_options(Rest, Options#options{warning_files = Value});
+ warning_files_rec ->
+ OldValues = Options#options.warning_files_rec,
+ assert_filenames_form(Term, Value),
+ build_options(Rest, Options#options{warning_files_rec = Value ++ OldValues});
analysis_type ->
NewOptions =
case Value of
@@ -146,6 +299,7 @@ build_options([{OptionName, Value} = Term|Rest], Options) ->
plt_build -> Options#options{analysis_type = Value};
plt_check -> Options#options{analysis_type = Value};
plt_remove -> Options#options{analysis_type = Value};
+ incremental -> Options#options{analysis_type = Value};
dataflow -> bad_option("Analysis type is no longer supported", Term);
old_style -> bad_option("Analysis type is no longer supported", Term);
Other -> bad_option("Unknown analysis type", Other)
@@ -164,20 +318,28 @@ build_options([{OptionName, Value} = Term|Rest], Options) ->
get_warnings ->
build_options(Rest, Options#options{get_warnings = Value});
plts ->
- assert_filenames(Term, Value),
+ %assert_filenames(Term, Value),
build_options(Rest, Options#options{init_plts = Value});
include_dirs ->
assert_filenames(Term, Value),
OldVal = Options#options.include_dirs,
NewVal = ordsets:union(ordsets:from_list(Value), OldVal),
build_options(Rest, Options#options{include_dirs = NewVal});
- use_spec ->
+ use_spec when is_boolean(Value) ->
build_options(Rest, Options#options{use_contracts = Value});
+ no_spec when is_boolean(Value) ->
+ build_options(Rest, Options#options{use_contracts = not Value});
old_style ->
bad_option("Analysis type is no longer supported", old_style);
output_file ->
assert_filename(Value),
build_options(Rest, Options#options{output_file = Value});
+ metrics_file ->
+ assert_filename(Value),
+ build_options(Rest, Options#options{metrics_file = Value});
+ module_lookup_file ->
+ assert_filename(Value),
+ build_options(Rest, Options#options{module_lookup_file = Value});
output_format ->
assert_output_format(Value),
build_options(Rest, Options#options{output_format = Value});
@@ -199,6 +361,9 @@ build_options([{OptionName, Value} = Term|Rest], Options) ->
callgraph_file ->
assert_filename(Value),
build_options(Rest, Options#options{callgraph_file = Value});
+ mod_deps_file ->
+ assert_filename(Value),
+ build_options(Rest, Options#options{mod_deps_file = Value});
error_location ->
assert_error_location(Value),
build_options(Rest, Options#options{error_location = Value});
@@ -220,10 +385,31 @@ build_options([], Options) ->
Options.
get_app_dirs(Apps) when is_list(Apps) ->
- dialyzer_cl_parse:get_lib_dir([atom_to_list(A) || A <- Apps]);
+ get_lib_dir([atom_to_list(A) || A <- Apps]);
get_app_dirs(Apps) ->
bad_option("Use a list of otp applications", Apps).
+get_lib_dir(Apps) ->
+ get_lib_dir(Apps, []).
+
+get_lib_dir([H|T], Acc) ->
+ NewElem =
+ case code:lib_dir(list_to_atom(H)) of
+ {error, bad_name} -> H;
+ LibDir when H =:= "erts" -> % hack for including erts in an un-installed system
+ EbinDir = filename:join([LibDir,"ebin"]),
+ case file:read_file_info(EbinDir) of
+ {error,enoent} ->
+ filename:join([LibDir,"preloaded","ebin"]);
+ _ ->
+ EbinDir
+ end;
+ LibDir -> filename:join(LibDir,"ebin")
+ end,
+ get_lib_dir(T, [NewElem|Acc]);
+get_lib_dir([], Acc) ->
+ lists:reverse(Acc).
+
assert_filenames(Term, Files) ->
assert_filenames_form(Term, Files),
assert_filenames_exist(Files).
@@ -271,7 +457,7 @@ assert_filename_opt(fullpath) ->
assert_filename_opt(Term) ->
bad_option("Illegal value for filename_opt", Term).
-assert_plt_op(#options{analysis_type = OldVal},
+assert_plt_op(#options{analysis_type = OldVal},
#options{analysis_type = NewVal}) ->
case is_plt_mode(OldVal) andalso is_plt_mode(NewVal) of
true -> bad_option("Options cannot be combined", [OldVal, NewVal]);
@@ -282,6 +468,7 @@ is_plt_mode(plt_add) -> true;
is_plt_mode(plt_build) -> true;
is_plt_mode(plt_remove) -> true;
is_plt_mode(plt_check) -> true;
+is_plt_mode(incremental) -> true;
is_plt_mode(succ_typings) -> false.
assert_error_location(column) ->
@@ -312,6 +499,8 @@ build_warnings([Opt|Opts], Warnings) ->
ordsets:del_element(?WARN_RETURN_NO_RETURN, Warnings);
no_unused ->
ordsets:del_element(?WARN_NOT_CALLED, Warnings);
+ no_unknown ->
+ ordsets:del_element(?WARN_UNKNOWN, Warnings);
no_improper_lists ->
ordsets:del_element(?WARN_NON_PROPER_LIST, Warnings);
no_fun_app ->
@@ -362,6 +551,8 @@ build_warnings([Opt|Opts], Warnings) ->
ordsets:del_element(?WARN_CONTRACT_MISSING_RETURN, Warnings);
unknown ->
ordsets:add_element(?WARN_UNKNOWN, Warnings);
+ overlapping_contract ->
+ ordsets:add_element(?WARN_OVERLAPPING_CONTRACT, Warnings);
OtherAtom ->
bad_option("Unknown dialyzer warning option", OtherAtom)
end,
diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl
index 409d1f65c5..f58c84da25 100644
--- a/lib/dialyzer/src/dialyzer_plt.erl
+++ b/lib/dialyzer/src/dialyzer_plt.erl
@@ -16,21 +16,17 @@
%%% File : dialyzer_plt.erl
%%% Author : Tobias Lindahl <tobiasl@it.uu.se>
%%% Description : Interface to display information in the persistent
-%%% lookup tables.
+%%% lookup tables stored in memory, and other commonality
+%%% between the various kinds of persisted PLT files.
%%%
%%% Created : 23 Jul 2004 by Tobias Lindahl <tobiasl@it.uu.se>
%%%-------------------------------------------------------------------
-module(dialyzer_plt).
--export([check_plt/3,
- compute_md5_from_files/1,
- contains_mfa/2,
+-export([contains_mfa/2,
all_modules/1,
delete_list/2,
delete_module/2,
- included_files/1,
- from_file/1,
- get_default_plt/0,
get_module_types/2,
get_exported_types/1,
insert_list/2,
@@ -43,32 +39,23 @@
lookup_contract/2,
lookup_callbacks/2,
lookup_module/2,
- merge_plts/1,
- merge_plts_or_report_conflicts/2,
+ merge_plts/1,
new/0,
- plt_and_info_from_file/1,
get_specs/1,
get_specs/4,
- to_file/4,
- delete/1,
- get_all_types/1,
- get_all_contracts/1,
- get_all_callbacks/1
+ delete/1,
+ get_all_types/1,
+ get_all_contracts/1,
+ get_all_callbacks/1,
+ plt_kind/1
]).
-%% Debug utilities
--export([pp_non_returning/0, pp_mod/1]).
-
--export_type([plt/0, file_md5/0]).
+-export_type([plt/0]).
-include_lib("stdlib/include/ms_transform.hrl").
%%----------------------------------------------------------------------
--type mod_deps() :: dialyzer_callgraph:mod_deps().
-
--type deep_string() :: string() | [deep_string()].
-
%% The following are used for searching the PLT when using the GUI
%% (e.g. in show or search PLT contents). The user might be searching
%% with a partial specification, in which case the missing items
@@ -78,30 +65,9 @@
%%----------------------------------------------------------------------
--record(plt, {info :: ets:tid(), %% {mfa() | integer(), ret_args_types()}
- types :: ets:tid(), %% {module(), erl_types:type_table()}
- contracts :: ets:tid(), %% {mfa(), #contract{}}
- callbacks :: ets:tid(), %% {module(),
- %% [{mfa(),
- %% dialyzer_contracts:file_contract()}]
- exported_types :: ets:tid() %% {module(), sets:set()}
- }).
-
--opaque plt() :: #plt{}.
-
-include("dialyzer.hrl").
--type file_md5() :: {file:filename(), binary()}.
-
--record(file_plt, {version = "" :: string(),
- file_md5_list = [] :: [file_md5()],
- info = dict:new() :: dict:dict(),
- contracts = dict:new() :: dict:dict(),
- callbacks = dict:new() :: dict:dict(),
- types = dict:new() :: dict:dict(),
- exported_types = sets:new() :: sets:set(),
- mod_deps :: mod_deps(),
- implementation_md5 = [] :: [file_md5()]}).
+-type plt() :: #plt{}.
%%----------------------------------------------------------------------
@@ -232,123 +198,6 @@ all_modules(#plt{info = Info, contracts = Cs}) ->
contains_mfa(#plt{info = Info, contracts = Contracts}, MFA) ->
ets:member(Info, MFA) orelse ets:member(Contracts, MFA).
--spec get_default_plt() -> file:filename().
-
-get_default_plt() ->
- case os:getenv("DIALYZER_PLT") of
- false ->
- CacheDir = filename:basedir(user_cache, "erlang"),
- filename:join(CacheDir, ".dialyzer_plt");
- UserSpecPlt -> UserSpecPlt
- end.
-
--spec plt_and_info_from_file(file:filename()) -> {plt(), #plt_info{}}.
-
-plt_and_info_from_file(FileName) ->
- from_file(FileName, true).
-
--spec from_file(file:filename()) -> plt().
-
-from_file(FileName) ->
- from_file(FileName, false).
-
-from_file(FileName, ReturnInfo) ->
- Plt = new(),
- Fun = fun() -> from_file1(Plt, FileName, ReturnInfo) end,
- case subproc(Fun) of
- {ok, Return} ->
- Return;
- {error, Msg} ->
- delete(Plt),
- plt_error(Msg)
- end.
-
-from_file1(Plt, FileName, ReturnInfo) ->
- case get_record_from_file(FileName) of
- {ok, Rec} ->
- case check_version(Rec) of
- error ->
- Msg = io_lib:format("Old PLT file ~ts\n", [FileName]),
- {error, Msg};
- ok ->
- #file_plt{info = FileInfo,
- contracts = FileContracts,
- callbacks = FileCallbacks,
- types = FileTypes,
- exported_types = FileExpTypes} = Rec,
- Types = [{Mod, maps:from_list(dict:to_list(Types))} ||
- {Mod, Types} <- dict:to_list(FileTypes)],
- CallbacksList = dict:to_list(FileCallbacks),
- CallbacksByModule =
- [{M, [Cb || {{M1,_,_},_} = Cb <- CallbacksList, M1 =:= M]} ||
- M <- lists:usort([M || {{M,_,_},_} <- CallbacksList])],
- #plt{info = ETSInfo,
- types = ETSTypes,
- contracts = ETSContracts,
- callbacks = ETSCallbacks,
- exported_types = ETSExpTypes} = Plt,
- [true, true, true] =
- [ets:insert(ETS, Data) ||
- {ETS, Data} <- [{ETSInfo, dict:to_list(FileInfo)},
- {ETSTypes, Types},
- {ETSContracts, dict:to_list(FileContracts)}]],
- true = ets:insert(ETSCallbacks, CallbacksByModule),
- true = ets:insert(ETSExpTypes, [{ET} ||
- ET <- sets:to_list(FileExpTypes)]),
- case ReturnInfo of
- false -> {ok, Plt};
- true ->
- PltInfo = #plt_info{files = Rec#file_plt.file_md5_list,
- mod_deps = Rec#file_plt.mod_deps},
- {ok, {Plt, PltInfo}}
- end
- end;
- {error, Reason} ->
- Msg = io_lib:format("Could not read PLT file ~ts: ~p\n",
- [FileName, Reason]),
- {error, Msg}
- end.
-
--type err_rsn() :: 'not_valid' | 'no_such_file' | 'read_error'.
-
--spec included_files(file:filename()) -> {'ok', [file:filename()]}
- | {'error', err_rsn()}.
-
-included_files(FileName) ->
- Fun = fun() -> included_files1(FileName) end,
- subproc(Fun).
-
-included_files1(FileName) ->
- case get_record_from_file(FileName) of
- {ok, #file_plt{file_md5_list = Md5}} ->
- {ok, [File || {File, _} <- Md5]};
- {error, _What} = Error ->
- Error
- end.
-
-check_version(#file_plt{version = ?VSN, implementation_md5 = ImplMd5}) ->
- case compute_new_md5(ImplMd5, [], []) of
- ok -> ok;
- {differ, _, _} -> error;
- {error, _} -> error
- end;
-check_version(#file_plt{}) -> error.
-
-get_record_from_file(FileName) ->
- case file:read_file(FileName) of
- {ok, Bin} ->
- try binary_to_term(Bin) of
- #file_plt{} = FilePLT -> {ok, FilePLT};
- _ -> {error, not_valid}
- catch
- _:_ -> {error, not_valid}
- end;
- {error, enoent} ->
- {error, no_such_file};
- {error, _} ->
- {error, read_error}
- end.
-
-spec merge_plts([plt()]) -> plt().
%% One of the PLTs of the list is augmented with the contents of the
@@ -364,23 +213,6 @@ merge_plts(List) ->
callbacks = table_merge(CallbacksList)
}.
--spec merge_disj_plts([plt()]) -> plt().
-
-%% One of the PLTs of the list is augmented with the contents of the
-%% other PLTs, and returned. The other PLTs are deleted.
-%%
-%% The keys are compared when checking for disjointness. Sometimes the
-%% key is a module(), sometimes an mfa(). It boils down to checking if
-%% any module occurs more than once.
-merge_disj_plts(List) ->
- {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList} =
- group_fields(List),
- #plt{info = table_disj_merge(InfoList),
- types = table_disj_merge(TypesList),
- exported_types = sets_disj_merge(ExpTypesList),
- contracts = table_disj_merge(ContractsList),
- callbacks = table_disj_merge(CallbacksList)
- }.
group_fields(List) ->
InfoList = [Info || #plt{info = Info} <- List],
@@ -390,199 +222,6 @@ group_fields(List) ->
CallbacksList = [Callbacks || #plt{callbacks = Callbacks} <- List],
{InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList}.
--spec merge_plts_or_report_conflicts([file:filename()], [plt()]) -> plt().
-
-merge_plts_or_report_conflicts(PltFiles, Plts) ->
- try
- merge_disj_plts(Plts)
- catch throw:{dialyzer_error, not_disjoint_plts} ->
- IncFiles = lists:append([begin {ok, Fs} = included_files(F), Fs end
- || F <- PltFiles]),
- ConfFiles = find_duplicates(IncFiles),
- Msg = io_lib:format("Could not merge PLTs since they are not disjoint\n"
- "The following files are included in more than one "
- "PLTs:\n~tp\n", [ConfFiles]),
- plt_error(Msg)
- end.
-
-find_duplicates(List) ->
- ModList = [filename:basename(E) || E <- List],
- SortedList = lists:usort(ModList),
- lists:usort(ModList -- SortedList).
-
--spec to_file(file:filename(), plt(), mod_deps(), #plt_info{}) -> 'ok'.
-
-%% Write the PLT to file, and delete the PLT.
-to_file(FileName, Plt, ModDeps, MD5_OldModDeps) ->
- Fun = fun() -> to_file1(FileName, Plt, ModDeps, MD5_OldModDeps) end,
- Return = subproc(Fun),
- delete(Plt),
- case Return of
- ok -> ok;
- {error, Msg} -> plt_error(Msg)
- end.
-
-to_file1(FileName,
- #plt{info = ETSInfo, types = ETSTypes, contracts = ETSContracts,
- callbacks = ETSCallbacks, exported_types = ETSExpTypes},
- ModDeps, #plt_info{files = MD5, mod_deps = OldModDeps}) ->
- NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) ->
- ordsets:union(OldVal, NewVal)
- end,
- OldModDeps, ModDeps),
- ImplMd5 = compute_implementation_md5(),
- CallbacksList =
- [Cb ||
- {_M, Cbs} <- tab2list(ETSCallbacks),
- Cb <- Cbs],
- Callbacks = dict:from_list(CallbacksList),
- Info = dict:from_list(tab2list(ETSInfo)),
- Types = tab2list(ETSTypes),
- Contracts = dict:from_list(tab2list(ETSContracts)),
- ExpTypes = sets:from_list([E || {E} <- tab2list(ETSExpTypes)]),
- FileTypes = dict:from_list([{Mod, dict:from_list(maps:to_list(MTypes))} ||
- {Mod, MTypes} <- Types]),
- Record = #file_plt{version = ?VSN,
- file_md5_list = MD5,
- info = Info,
- contracts = Contracts,
- callbacks = Callbacks,
- types = FileTypes,
- exported_types = ExpTypes,
- mod_deps = NewModDeps,
- implementation_md5 = ImplMd5},
- Bin = term_to_binary(Record, [compressed]),
- case file:write_file(FileName, Bin) of
- ok -> ok;
- {error, Reason} ->
- Msg = io_lib:format("Could not write PLT file ~ts: ~w\n",
- [FileName, Reason]),
- {error, Msg}
- end.
-
--type md5_diff() :: [{'differ', atom()} | {'removed', atom()}].
--type check_error() :: err_rsn() | {'no_file_to_remove', file:filename()}.
-
--spec check_plt(file:filename(), [file:filename()], [file:filename()]) ->
- 'ok'
- | {'error', check_error()}
- | {'differ', [file_md5()], md5_diff(), mod_deps()}
- | {'old_version', [file_md5()]}.
-
-check_plt(FileName, RemoveFiles, AddFiles) ->
- Fun = fun() -> check_plt1(FileName, RemoveFiles, AddFiles) end,
- subproc(Fun).
-
-check_plt1(FileName, RemoveFiles, AddFiles) ->
- case get_record_from_file(FileName) of
- {ok, #file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} ->
- case check_version(Rec) of
- ok ->
- case compute_new_md5(Md5, RemoveFiles, AddFiles) of
- ok -> ok;
- {differ, NewMd5, DiffMd5} -> {differ, NewMd5, DiffMd5, ModDeps};
- {error, _What} = Err -> Err
- end;
- error ->
- case compute_new_md5(Md5, RemoveFiles, AddFiles) of
- ok -> {old_version, Md5};
- {differ, NewMd5, _DiffMd5} -> {old_version, NewMd5};
- {error, _What} = Err -> Err
- end
- end;
- Error -> Error
- end.
-
-compute_new_md5(Md5, [], []) ->
- compute_new_md5_1(Md5, [], []);
-compute_new_md5(Md5, RemoveFiles0, AddFiles0) ->
- %% Assume that files are first removed and then added. Files that
- %% are both removed and added will be checked for consistency in the
- %% normal way. If they have moved, we assume that they differ.
- RemoveFiles = RemoveFiles0 -- AddFiles0,
- AddFiles = AddFiles0 -- RemoveFiles0,
- InitDiffList = init_diff_list(RemoveFiles, AddFiles),
- case init_md5_list(Md5, RemoveFiles, AddFiles) of
- {ok, NewMd5} -> compute_new_md5_1(NewMd5, [], InitDiffList);
- {error, _What} = Error -> Error
- end.
-
-compute_new_md5_1([{File, Md5} = Entry|Entries], NewList, Diff) ->
- case compute_md5_from_file(File) of
- Md5 -> compute_new_md5_1(Entries, [Entry|NewList], Diff);
- NewMd5 ->
- ModName = beam_file_to_module(File),
- compute_new_md5_1(Entries, [{File, NewMd5}|NewList], [{differ, ModName}|Diff])
- end;
-compute_new_md5_1([], _NewList, []) ->
- ok;
-compute_new_md5_1([], NewList, Diff) ->
- {differ, lists:keysort(1, NewList), Diff}.
-
--spec compute_implementation_md5() -> [file_md5()].
-
-compute_implementation_md5() ->
- Dir = code:lib_dir(dialyzer),
- Files1 = ["erl_bif_types.beam", "erl_types.beam"],
- Files2 = [filename:join([Dir, "ebin", F]) || F <- Files1],
- compute_md5_from_files(Files2).
-
--spec compute_md5_from_files([file:filename()]) -> [file_md5()].
-
-compute_md5_from_files(Files) ->
- lists:keysort(1, [{F, compute_md5_from_file(F)} || F <- Files]).
-
-compute_md5_from_file(File) ->
- case beam_lib:all_chunks(File) of
- {ok, _, Chunks} ->
- %% We cannot use beam_lib:md5 because it does not consider
- %% the debug_info chunk, where typespecs are likely stored.
- %% So we consider almost all chunks except the useless ones.
- Filtered = [[ID, Chunk] || {ID, Chunk} <- Chunks, ID =/= "CInf", ID =/= "Docs"],
- erlang:md5(lists:sort(Filtered));
- {error, beam_lib, {file_error, _, enoent}} ->
- Msg = io_lib:format("File not found: ~ts\n", [File]),
- plt_error(Msg);
- {error, beam_lib, _} ->
- Msg = io_lib:format("Could not compute MD5 for .beam: ~ts\n", [File]),
- plt_error(Msg)
- end.
-
-init_diff_list(RemoveFiles, AddFiles) ->
- RemoveSet0 = sets:from_list([beam_file_to_module(F) || F <- RemoveFiles]),
- AddSet0 = sets:from_list([beam_file_to_module(F) || F <- AddFiles]),
- DiffSet = sets:intersection(AddSet0, RemoveSet0),
- RemoveSet = sets:subtract(RemoveSet0, DiffSet),
- %% Added files and diff files will appear as diff files from the md5 check.
- [{removed, F} || F <- sets:to_list(RemoveSet)].
-
-init_md5_list(Md5, RemoveFiles, AddFiles) ->
- Files = [{remove, F} || F <- RemoveFiles] ++ [{add, F} || F <- AddFiles],
- DiffFiles = lists:keysort(2, Files),
- Md5Sorted = lists:keysort(1, Md5),
- init_md5_list_1(Md5Sorted, DiffFiles, []).
-
-init_md5_list_1([{File, _Md5}|Md5Left], [{remove, File}|DiffLeft], Acc) ->
- init_md5_list_1(Md5Left, DiffLeft, Acc);
-init_md5_list_1([{File, _Md5} = Entry|Md5Left], [{add, File}|DiffLeft], Acc) ->
- init_md5_list_1(Md5Left, DiffLeft, [Entry|Acc]);
-init_md5_list_1([{File1, _Md5} = Entry|Md5Left] = Md5List,
- [{Tag, File2}|DiffLeft] = DiffList, Acc) ->
- case File1 < File2 of
- true -> init_md5_list_1(Md5Left, DiffList, [Entry|Acc]);
- false ->
- %% Just an assert.
- true = File1 > File2,
- case Tag of
- add -> init_md5_list_1(Md5List, DiffLeft, [{File2, <<>>}|Acc]);
- remove -> {error, {no_file_to_remove, File2}}
- end
- end;
-init_md5_list_1([], DiffList, Acc) ->
- AddFiles = [{F, <<>>} || {add, F} <- DiffList],
- {ok, lists:reverse(Acc, AddFiles)};
-init_md5_list_1(Md5List, [], Acc) ->
- {ok, lists:reverse(Acc, Md5List)}.
-spec delete(plt()) -> 'ok'.
@@ -598,24 +237,6 @@ delete(#plt{info = ETSInfo,
true = ets:delete(ETSExpTypes),
ok.
-tab2list(Tab) ->
- dialyzer_utils:ets_tab2list(Tab).
-
-subproc(Fun) ->
- F = fun() ->
- exit(try Fun()
- catch throw:T ->
- {thrown, T}
- end)
- end,
- {Pid, Ref} = erlang:spawn_monitor(F),
- receive {'DOWN', Ref, process, Pid, Return} ->
- case Return of
- {thrown, T} -> throw(T);
- _ -> Return
- end
- end.
-
%%---------------------------------------------------------------------------
%% Edoc
@@ -627,9 +248,6 @@ get_specs(#plt{info = Info}) ->
{{_,_,_} = MFA, Val} <- table_to_list(Info)]),
lists:flatten(create_specs(L, [])).
-beam_file_to_module(Filename) ->
- list_to_atom(filename:basename(Filename, ".beam")).
-
-spec get_specs(plt(), atom(), atom(), arity_patt()) -> 'none' | string().
get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) ->
@@ -663,10 +281,6 @@ expand_args([ArgType|Left]) ->
end ++
","|expand_args(Left)].
--spec plt_error(deep_string()) -> no_return().
-
-plt_error(Msg) ->
- throw({dialyzer_error, lists:flatten(Msg)}).
%%---------------------------------------------------------------------------
%% Ets table
@@ -735,19 +349,6 @@ table_merge([Plt|Plts], Acc) ->
NewAcc = merge_tables(Plt, Acc),
table_merge(Plts, NewAcc).
-table_disj_merge([H|T]) ->
- table_disj_merge(T, H).
-
-table_disj_merge([], Acc) ->
- Acc;
-table_disj_merge([Plt|Plts], Acc) ->
- case table_is_disjoint(Plt, Acc) of
- true ->
- NewAcc = merge_tables(Plt, Acc),
- table_disj_merge(Plts, NewAcc);
- false -> throw({dialyzer_error, not_disjoint_plts})
- end.
-
sets_merge([H|T]) ->
sets_merge(T, H).
@@ -757,31 +358,6 @@ sets_merge([Plt|Plts], Acc) ->
NewAcc = merge_tables(Plt, Acc),
sets_merge(Plts, NewAcc).
-sets_disj_merge([H|T]) ->
- sets_disj_merge(T, H).
-
-sets_disj_merge([], Acc) ->
- Acc;
-sets_disj_merge([Plt|Plts], Acc) ->
- case table_is_disjoint(Plt, Acc) of
- true ->
- NewAcc = merge_tables(Plt, Acc),
- sets_disj_merge(Plts, NewAcc);
- false -> throw({dialyzer_error, not_disjoint_plts})
- end.
-
-table_is_disjoint(T1, T2) ->
- tab_is_disj(ets:first(T1), T1, T2).
-
-tab_is_disj('$end_of_table', _T1, _T2) ->
- true;
-tab_is_disj(K1, T1, T2) ->
- case ets:member(T2, K1) of
- false ->
- tab_is_disj(ets:next(T1, K1), T1, T2);
- true ->
- false
- end.
merge_tables(T1, T2) ->
tab_merge(ets:first(T1), T1, T2).
@@ -801,53 +377,6 @@ tab_merge(K1, T1, T2) ->
true = ets:insert(T2, Vs),
tab_merge(NextK1, T1, T2).
-%%---------------------------------------------------------------------------
-%% Debug utilities.
-
--spec pp_non_returning() -> 'ok'.
-
-pp_non_returning() ->
- PltFile = get_default_plt(),
- Plt = from_file(PltFile),
- List = table_to_list(Plt#plt.info),
- Unit = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List,
- erl_types:t_is_unit(Ret)],
- None = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List,
- erl_types:t_is_none(Ret)],
- io:format("=========================================\n"),
- io:format("= Loops =\n"),
- io:format("=========================================\n\n"),
- lists:foreach(fun({{M, F, _}, Type}) ->
- io:format("~w:~tw~ts.\n",
- [M, F, dialyzer_utils:format_sig(Type)])
- end, lists:sort(Unit)),
- io:format("\n"),
- io:format("=========================================\n"),
- io:format("= Errors =\n"),
- io:format("=========================================\n\n"),
- lists:foreach(fun({{M, F, _}, Type}) ->
- io:format("~w:~w~s.\n",
- [M, F, dialyzer_utils:format_sig(Type)])
- end, lists:sort(None)),
- delete(Plt).
-
--spec pp_mod(atom()) -> 'ok'.
-
-pp_mod(Mod) when is_atom(Mod) ->
- PltFile = get_default_plt(),
- Plt = from_file(PltFile),
- case lookup_module(Plt, Mod) of
- {value, List} ->
- lists:foreach(fun({{_, F, _}, Ret, Args}) ->
- T = erl_types:t_fun(Args, Ret),
- S = dialyzer_utils:format_sig(T),
- io:format("-spec ~tw~ts.\n", [F, S])
- end, lists:sort(List));
- none ->
- io:format("dialyzer: Found no module named '~s' in the PLT\n", [Mod])
- end,
- delete(Plt).
-
%% Returns all contracts stored in the PLT
-spec get_all_contracts(plt()) -> #{mfa() => #contract{}}.
@@ -857,14 +386,27 @@ get_all_contracts(#plt{contracts = ETSContracts}) ->
%% Returns all callbacks stored in the PLT
-spec get_all_callbacks(plt()) -> #{mfa() => #contract{}}.
get_all_callbacks(#plt{callbacks = ETSCallbacks}) ->
- CallbacksList =
- [Cb ||
- {_M, Cbs} <- ets:tab2list(ETSCallbacks),
- Cb <- Cbs],
- maps:from_list(CallbacksList).
+ #{K => V ||
+ {_M, Cbs} <- ets:tab2list(ETSCallbacks),
+ {K, V} <- Cbs}.
%% Returns all types stored in the PLT
-spec get_all_types(plt()) -> #{module() => erl_types:type_table()}.
get_all_types(#plt{types = ETSTypes}) ->
Types = ets:tab2list(ETSTypes),
maps:from_list(Types).
+
+-spec plt_kind(file:filename()) -> 'iplt' | 'cplt' | 'bad_file' | 'no_file'.
+plt_kind(FileName) ->
+ case filelib:is_regular(FileName) of
+ true ->
+ case dialyzer_iplt:is_iplt(FileName) of
+ true -> iplt;
+ false ->
+ case dialyzer_cplt:is_cplt(FileName) of
+ true -> cplt;
+ false -> bad_file
+ end
+ end;
+ false -> no_file
+ end.
diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl
index 248f78a43f..18b402f54f 100644
--- a/lib/dialyzer/src/dialyzer_succ_typings.erl
+++ b/lib/dialyzer/src/dialyzer_succ_typings.erl
@@ -212,17 +212,17 @@ add_to_result(Labels, Result, {_Codeserver, Callgraph, _Plt, _Solver}) ->
get_success_typings(Callgraph, Plt, Codeserver, TimingServer, Solvers) ->
%% Condense the call graph to its strongly connected components (SCCs).
- {SCCs, Callgraph1} =
+ {LabelledSCCs, Callgraph1} =
?timing(TimingServer, "order", dialyzer_callgraph:finalize(Callgraph)),
State = #st{callgraph = Callgraph1, plt = Plt,
codeserver = Codeserver,
timing_server = TimingServer, solvers = Solvers},
- get_refined_success_typings(SCCs, State).
+ get_refined_success_typings(LabelledSCCs, State).
-get_refined_success_typings(SCCs, #st{callgraph = Callgraph,
+get_refined_success_typings(LabelledSCCs, #st{callgraph = Callgraph,
timing_server = TimingServer} = State) ->
%% Find the success types for the SCCs.
- case find_succ_typings(SCCs, State) of
+ case find_succ_typings(LabelledSCCs, State) of
[] ->
%% No new type information was discovered. We are done.
State;
@@ -233,7 +233,6 @@ get_refined_success_typings(SCCs, #st{callgraph = Callgraph,
?timing(TimingServer, "order", _C1,
dialyzer_callgraph:module_postorder_from_funs(NotFixpoint1,
Callgraph)),
- ?debug("Module postorder: ~p\n", [Modules]),
ModState = State#st{callgraph = ModCallgraph},
case refine_succ_typings(ModulePostorder, ModState) of
@@ -242,20 +241,20 @@ get_refined_success_typings(SCCs, #st{callgraph = Callgraph,
ModState;
NotFixpoint2 ->
%% Need to reset the callgraph before repeating.
- {NewSCCs, Callgraph2} =
+ {NewLabelledSCCs, Callgraph2} =
?timing(TimingServer, "order", _C2,
dialyzer_callgraph:reset_from_funs(NotFixpoint2,
ModCallgraph)),
NewState = ModState#st{callgraph = Callgraph2},
- get_refined_success_typings(NewSCCs, NewState)
+ get_refined_success_typings(NewLabelledSCCs, NewState)
end
end.
-find_succ_typings(SCCs, State) ->
+find_succ_typings(LabelledSCCs, State) ->
{Init, Timing} = init_pass_data(State),
Updated =
?timing(Timing, "typesig",
- dialyzer_coordinator:parallel_job(typesig, SCCs, Init, Timing)),
+ dialyzer_coordinator:parallel_job(typesig, LabelledSCCs, Init, Timing)),
?debug("==================== Typesig done ====================\n\n", []),
Updated.
diff --git a/lib/dialyzer/src/dialyzer_typegraph.erl b/lib/dialyzer/src/dialyzer_typegraph.erl
index 5818236fa7..c01bff461f 100644
--- a/lib/dialyzer/src/dialyzer_typegraph.erl
+++ b/lib/dialyzer/src/dialyzer_typegraph.erl
@@ -29,17 +29,14 @@ module_type_deps(UseContracts, CodeServer, Modules) ->
Contracts =
case UseContracts of
true -> maps:from_list(dict:to_list(dialyzer_codeserver:get_contracts(CodeServer)));
- false -> []
+ false -> #{}
end,
Callbacks = maps:from_list(dialyzer_codeserver:get_callbacks(CodeServer)),
TypeDefinitions =
- maps:from_list(
- [{M, dialyzer_codeserver:lookup_mod_records(M, CodeServer)} || M <- Modules]
- ),
+ #{M => dialyzer_codeserver:lookup_mod_records(M, CodeServer) ||
+ M <- Modules},
Behaviours =
- maps:from_list(
- [{M, get_behaviours_for_module(M, CodeServer)} || M <- Modules]
- ),
+ #{M => get_behaviours_for_module(M, CodeServer) || M <- Modules},
collect_module_type_deps(Contracts, Callbacks, TypeDefinitions, Behaviours).
-spec get_behaviours_for_module(module(), dialyzer_codeserver:codeserver()) -> [module()].
@@ -59,12 +56,12 @@ get_behaviours_for_module(M, CodeServer) ->
collect_module_type_deps(Specs, Callbacks, TypeDefinitions, Behaviours) ->
Contracts =
- [{M, Spec} || {{M, _F, _A}, {_FileLine, Spec, _Extra}} <- maps:to_list(Specs)] ++
- [{M, Callback} || {{M, _F, _A}, {_FileLine, Callback, _Extra}} <- maps:to_list(Callbacks)],
+ [{M, Spec} || {M, _F, _A} := {_FileLine, Spec, _Extra} <- Specs] ++
+ [{M, Callback} || {M, _F, _A} := {_FileLine, Callback, _Extra} <- Callbacks],
ModulesMentionedInTypeDefinitions =
[{FromTypeDefM, erl_types:module_type_deps_of_type_defs(TypeTable)}
- || {FromTypeDefM, TypeTable} <- maps:to_list(TypeDefinitions)],
+ || FromTypeDefM := TypeTable <- TypeDefinitions],
ModulesMentionedInContracts =
[{FromContractM, module_type_deps_of_contract(C)}
diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl
index bbddf2a0a3..0f08ea560e 100644
--- a/lib/dialyzer/src/dialyzer_typesig.erl
+++ b/lib/dialyzer/src/dialyzer_typesig.erl
@@ -21,6 +21,7 @@
%%%-------------------------------------------------------------------
-module(dialyzer_typesig).
+-feature(maybe_expr, enable).
-export([analyze_scc/7]).
-export([get_safe_underapprox/2]).
@@ -471,21 +472,19 @@ traverse(Tree, DefinedVars, State) ->
{NewEvars, TmpState} = lists:mapfoldl(Fun, State1, EVars),
{TmpState, t_tuple(NewEvars)}
end,
- case Elements of
- [Tag|Fields] ->
- case cerl:is_c_atom(Tag) andalso is_literal_record(Tree) of
- true ->
- %% Check if a record is constructed.
- Arity = length(Fields),
- case lookup_record(State2, cerl:atom_val(Tag), Arity) of
- {error, State3} -> {State3, TupleType};
- {ok, RecType, State3} ->
- State4 = state__store_conj(TupleType, sub, RecType, State3),
- {State4, TupleType}
- end;
- false -> {State2, TupleType}
- end;
- [] -> {State2, TupleType}
+ maybe
+ [Tag|Fields] ?= Elements,
+ true ?= cerl:is_c_atom(Tag) andalso is_literal_record(Tree),
+ %% Check if a record is constructed.
+ Arity = length(Fields),
+ case lookup_record(State2, cerl:atom_val(Tag), Arity) of
+ {error, State3} -> {State3, TupleType};
+ {ok, RecType, State3} ->
+ State4 = state__store_conj(TupleType, sub, RecType, State3),
+ {State4, TupleType}
+ end
+ else
+ _ -> {State2, TupleType}
end;
map ->
Entries = cerl:map_es(Tree),
@@ -2392,20 +2391,15 @@ unsafe_lookup_type(Key, Map) ->
unsafe_lookup_type_list(List, Map) ->
[unsafe_lookup_type(X, Map) || X <- List].
-lookup_type(Key, Map) when is_integer(Key) ->
- case maps:find(Key, Map) of
- error -> t_any();
- {ok, Val} -> Val
- end;
lookup_type(#fun_var{'fun' = Fun}, Map) ->
Fun(Map);
+lookup_type(Key, Map) when is_integer(Key) ->
+ case Map of
+ #{Key := Val} -> Val;
+ #{} -> t_any()
+ end;
lookup_type(Key, Map) ->
- %% Seems unused and dialyzer complains about it -- commented out.
- %% case cerl:is_literal(Key) of
- %% true -> t_from_term(cerl:concrete(Key));
- %% false ->
t_subst(Key, Map).
- %% end.
mk_var(Var) ->
case cerl:is_literal(Var) of
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index 292c01f7aa..b920d22b30 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -45,7 +45,9 @@
ets_tab2list/1,
ets_move/2,
parallelism/0,
- family/1
+ family/1,
+ p_foreach/2,
+ p_map/2
]).
%% For dialyzer_worker.
@@ -550,14 +552,53 @@ get_spec_info([], SpecMap, CallbackMap,
{ok, SpecMap, CallbackMap}.
core_to_attr_tuples(Core) ->
- [{cerl:concrete(Key), get_core_location(cerl:get_ann(Key)), cerl:concrete(Value)} ||
- {Key, Value} <- cerl:module_attrs(Core)].
+ As = [{cerl:concrete(Key), get_core_location(cerl:get_ann(Key)), cerl:concrete(Value)} ||
+ {Key, Value} <- cerl:module_attrs(Core)],
+ case cerl:concrete(cerl:module_name(Core)) of
+ erlang ->
+ As;
+ _ ->
+ %% Starting from Erlang/OTP 26, locally defining a type having
+ %% the same name as a built-in type is allowed. Change the tag
+ %% from `type` to `user_type` for all such redefinitions.
+ massage_forms(As, sets:new([{version, 2}]))
+ end.
get_core_location([L | _As]) when is_integer(L) -> L;
get_core_location([{L, C} | _As]) when is_integer(L), is_integer(C) -> {L, C};
get_core_location([_ | As]) -> get_core_location(As);
get_core_location([]) -> undefined.
+massage_forms([{type, Loc, [{Name, Type0, Args}]} | T], Defs0) ->
+ Type = massage_type(Type0, Defs0),
+ Defs = sets:add_element({Name, length(Args)}, Defs0),
+ [{type, Loc, [{Name, Type, Args}]} | massage_forms(T, Defs)];
+massage_forms([{spec, Loc, [{Name, [Type0]}]} | T], Defs) ->
+ Type = massage_type(Type0, Defs),
+ [{spec, Loc, [{Name, [Type]}]} | massage_forms(T, Defs)];
+massage_forms([H | T], Defs) ->
+ [H | massage_forms(T, Defs)];
+massage_forms([], _Defs) ->
+ [].
+
+massage_type({type, Loc, Name, Args0}, Defs) when is_list(Args0) ->
+ case sets:is_element({Name, length(Args0)}, Defs) of
+ true ->
+ %% This name for a built-in type has been overriden locally
+ %% with a new definition.
+ {user_type, Loc, Name, Args0};
+ false ->
+ Args = massage_type_list(Args0, Defs),
+ {type, Loc, Name, Args}
+ end;
+massage_type(Type, _Defs) ->
+ Type.
+
+massage_type_list([H|T], Defs) ->
+ [massage_type(H, Defs) | massage_type_list(T, Defs)];
+massage_type_list([], _Defs) ->
+ [].
+
-spec get_fun_meta_info(module(), cerl:c_module(), [dial_warn_tag()]) ->
dialyzer_codeserver:fun_meta_info() | {'error', string()}.
@@ -948,9 +989,15 @@ pp_map(Node, Ctxt, Cont) ->
prettypr:beside(Cont(Arg,Ctxt),
prettypr:floating(prettypr:text("#{")))
end,
+ MapEs = case cerl:is_literal(Node) of
+ true ->
+ lists:sort(cerl:map_es(Node));
+ false ->
+ cerl:map_es(Node)
+ end,
prettypr:beside(
Before, prettypr:beside(
- prettypr:par(seq(cerl:map_es(Node),
+ prettypr:par(seq(MapEs,
prettypr:floating(prettypr:text(",")),
Ctxt, Cont)),
prettypr:floating(prettypr:text("}")))).
@@ -1062,7 +1109,7 @@ refold_concrete_pat(Val) ->
%% N.B.: The key in a map pattern is an expression, *not* a pattern.
label(cerl:c_map_pattern([cerl:c_map_pair_exact(cerl:abstract(K),
refold_concrete_pat(V))
- || {K, V} <- maps:to_list(M)]));
+ || K := V <- M]));
_ ->
cerl:abstract(Val)
end.
@@ -1146,3 +1193,79 @@ parallelism() ->
family(L) ->
sofs:to_external(sofs:rel2fam(sofs:relation(L))).
+
+-spec p_foreach(fun((X) -> any()), [X]) -> ok.
+p_foreach(Fun, List) ->
+ N = dialyzer_utils:parallelism(),
+ Ref = make_ref(),
+ start(Fun, List, Ref, N, gb_sets:new()).
+
+start(Fun, [Arg|Rest], Ref, N, Outstanding) when N > 0 ->
+ Self = self(),
+ Pid = spawn_link(
+ fun() ->
+ try Fun(Arg) of
+ _Val -> Self ! {done, Ref, self()}
+ catch
+ throw:Throw -> Self ! {throw, Throw, Ref, self()}
+ end
+ end),
+ start(Fun, Rest, Ref, N-1, gb_sets:add_element(Pid, Outstanding));
+start(Fun, Args, Ref, N, Outstanding) when N >= 0 ->
+ case {gb_sets:is_empty(Outstanding), Args} of
+ {true, []} -> ok;
+ {true, Args} -> start(Fun, Args, Ref, 1, Outstanding);
+ {false, _} ->
+ receive
+ {done, Ref, Pid} ->
+ start(Fun, Args, Ref, N+1, gb_sets:delete(Pid, Outstanding));
+ {throw, Throw, Ref, Pid} ->
+ clean_up(Throw, Ref, gb_sets:delete(Pid, Outstanding))
+ end
+ end.
+
+-spec p_map(fun((X) -> Y), [X]) -> [Y].
+p_map(Fun, List) ->
+ Parent = self(),
+ Batches = batch(List, dialyzer_utils:parallelism()),
+ BatchJobs =
+ [spawn_link(
+ fun() ->
+ try
+ Result = lists:map(Fun,Batch),
+ Parent ! {done, self(), Result}
+ catch
+ throw:Throw -> Parent ! {throw, self(), Throw}
+ end
+ end)
+ || Batch <- Batches],
+ lists:append([
+ receive
+ {done, Pid, BatchResult} -> BatchResult;
+ {throw, Pid, Throw} -> throw(Throw)
+ end
+ || Pid <- BatchJobs]).
+
+-spec batch([X], non_neg_integer()) -> [[X]].
+batch(List, BatchSize) ->
+ batch(BatchSize, 0, List, []).
+batch(_, _, [], Acc) ->
+ [lists:reverse(Acc)];
+batch(BatchSize, BatchSize, List, Acc) ->
+ [lists:reverse(Acc) | batch(BatchSize, 0, List, [])];
+batch(BatchSize, PartialBatchSize, [H|T], Acc) ->
+ batch(BatchSize, PartialBatchSize+1, T, [H|Acc]).
+
+
+clean_up(ThrowVal, Ref, Outstanding) ->
+ case gb_sets:is_empty(Outstanding) of
+ true ->
+ throw(ThrowVal);
+ false ->
+ receive
+ {done, Ref, Pid} ->
+ clean_up(ThrowVal, Ref, gb_sets:delete(Pid, Outstanding));
+ {throw, _Throw, Ref, Pid} ->
+ clean_up(ThrowVal, Ref, gb_sets:delete(Pid, Outstanding))
+ end
+ end.
diff --git a/lib/dialyzer/src/dialyzer_worker.erl b/lib/dialyzer/src/dialyzer_worker.erl
index c9ceb75a40..db33ea0e13 100644
--- a/lib/dialyzer/src/dialyzer_worker.erl
+++ b/lib/dialyzer/src/dialyzer_worker.erl
@@ -56,9 +56,9 @@ launch(Mode, Job, InitData, Coordinator) ->
%%--------------------------------------------------------------------
%% Local functions.
-init(#state{job = SCC, mode = Mode, init_data = InitData} = State)
+init(#state{job = Job, mode = Mode, init_data = InitData} = State)
when Mode =:= 'typesig'; Mode =:= 'dataflow' ->
- wait_for_success_typings(SCC, InitData, State),
+ wait_for_success_typings(Mode, Job, InitData, State),
run(State);
init(#state{mode = Mode} = State) when
Mode =:= 'compile'; Mode =:= 'warnings';
@@ -73,6 +73,7 @@ run(#state{coordinator = Coordinator, job = Job} = State) ->
run_job(#state{mode = Mode, job = Job, init_data = InitData} = State) ->
?debug("~w: ~p: ~p\n", [self(), Mode, Job]),
+ StartableJob = dialyzer_coordinator:get_job_input(Mode, Job),
case Mode of
compile ->
case start_compilation(State) of
@@ -82,16 +83,18 @@ run_job(#state{mode = Mode, job = Job, init_data = InitData} = State) ->
{error, _Reason} = Error ->
Error
end;
- typesig ->
- dialyzer_succ_typings:find_succ_types_for_scc(Job, InitData);
- dataflow ->
- dialyzer_succ_typings:refine_one_module(Job, InitData);
- contract_remote_types ->
- dialyzer_contracts:process_contract_remote_types_module(Job, InitData);
- record_remote_types ->
- dialyzer_utils:process_record_remote_types_module(Job, InitData);
- warnings ->
- dialyzer_succ_typings:collect_warnings(Job, InitData)
+ _ ->
+ StartableJob = dialyzer_coordinator:get_job_input(Mode, Job),
+ case Mode of
+ typesig -> dialyzer_succ_typings:find_succ_types_for_scc(StartableJob, InitData);
+ dataflow -> dialyzer_succ_typings:refine_one_module(StartableJob, InitData);
+ contract_remote_types ->
+ dialyzer_contracts:process_contract_remote_types_module(StartableJob, InitData);
+ record_remote_types ->
+ dialyzer_utils:process_record_remote_types_module(StartableJob, InitData);
+ warnings ->
+ dialyzer_succ_typings:collect_warnings(StartableJob, InitData)
+ end
end.
start_compilation(#state{job = Job, init_data = InitData}) ->
@@ -105,6 +108,8 @@ continue_compilation(Label, Data) ->
%% Wait for the results of success typings of modules or SCCs that we
%% depend on. ('typesig' or 'dataflow' mode)
-wait_for_success_typings(SCC, InitData, #state{coordinator = Coordinator}) ->
- DependsOnSCCs = dialyzer_succ_typings:find_depends_on(SCC, InitData),
- dialyzer_coordinator:wait_for_success_typings(DependsOnSCCs, Coordinator).
+wait_for_success_typings(Mode, Job, InitData, #state{coordinator = Coordinator}) ->
+ JobLabel = dialyzer_coordinator:get_job_label(Mode, Job),
+ DependsOnJobLabels = dialyzer_succ_typings:find_depends_on(JobLabel, InitData),
+ ?debug("~w: Deps ~p: ~p\n", [self(), Job, DependsOnJobLabels]),
+ dialyzer_coordinator:wait_for_success_typings(DependsOnJobLabels, Coordinator).
diff --git a/lib/dialyzer/src/erl_types.erl b/lib/dialyzer/src/erl_types.erl
index ed0264cc2f..8068baf8e8 100644
--- a/lib/dialyzer/src/erl_types.erl
+++ b/lib/dialyzer/src/erl_types.erl
@@ -77,7 +77,6 @@
t_from_form_check_remote/4,
t_check_record_fields/6,
t_from_range/2,
- t_from_range_unsafe/2,
t_from_term/1,
t_fun/0,
t_fun/1,
@@ -106,13 +105,11 @@
t_is_any_atom/2, t_is_any_atom/3,
t_is_binary/1, t_is_binary/2,
t_is_bitstr/1, t_is_bitstr/2,
- t_is_bitwidth/1,
t_is_boolean/1, t_is_boolean/2,
t_is_byte/1,
t_is_char/1,
t_is_cons/1, t_is_cons/2,
t_is_equal/2,
- t_is_fixnum/1,
t_is_float/1, t_is_float/2,
t_is_fun/1, t_is_fun/2,
t_is_identifier/1,
@@ -122,7 +119,6 @@
t_is_list/1,
t_is_map/1,
t_is_map/2,
- t_is_matchstate/1,
t_is_nil/1, t_is_nil/2,
t_is_non_neg_integer/1,
t_is_none/1,
@@ -157,13 +153,6 @@
t_map_pairwise_merge/4,
t_map_put/2, t_map_put/3,
t_map_remove/3,
- t_matchstate/0,
- t_matchstate/2,
- t_matchstate_present/1,
- t_matchstate_slot/2,
- t_matchstate_slots/1,
- t_matchstate_update_present/2,
- t_matchstate_update_slot/3,
t_mfa/0,
t_module/0,
t_nil/0,
@@ -185,7 +174,6 @@
%% t_maybe_improper_list/2,
t_product/1,
t_reference/0,
- t_singleton_to_term/2,
t_string/0,
t_subst/2,
t_subtract/2,
@@ -258,9 +246,6 @@
-define(UNIT_MULTIPLIER, 8).
--define(TAG_IMMED1_SIZE, 4).
--define(BITS, (erlang:system_info(wordsize) * 8) - ?TAG_IMMED1_SIZE).
-
-define(MAX_TUPLE_SIZE, (1 bsl 10)).
%%-----------------------------------------------------------------------------
@@ -273,7 +258,6 @@
-define(identifier_tag, identifier).
-define(list_tag, list).
-define(map_tag, map).
--define(matchstate_tag, matchstate).
-define(nil_tag, nil).
-define(number_tag, number).
-define(opaque_tag, opaque).
@@ -284,7 +268,7 @@
-define(var_tag, var).
-type tag() :: ?atom_tag | ?binary_tag | ?function_tag | ?identifier_tag
- | ?list_tag | ?map_tag | ?matchstate_tag | ?nil_tag | ?number_tag
+ | ?list_tag | ?map_tag | ?nil_tag | ?number_tag
| ?opaque_tag | ?product_tag
| ?tuple_tag | ?tuple_set_tag | ?union_tag | ?var_tag.
@@ -327,15 +311,15 @@
arity = 0 :: arity(), struct :: erl_type()}).
-define(atom(Set), #c{tag=?atom_tag, elements=Set}).
--define(bitstr(Unit, Base), #c{tag=?binary_tag, elements=[Unit,Base]}).
+-define(bitstr(Unit, Base), #c{tag=?binary_tag, elements={Unit,Base}}).
-define(float, ?number(?any, ?float_qual)).
-define(function(Domain, Range), #c{tag=?function_tag,
- elements=[Domain, Range]}).
+ elements={Domain,Range}}).
-define(identifier(Types), #c{tag=?identifier_tag, elements=Types}).
-define(integer(Types), ?number(Types, ?integer_qual)).
-define(int_range(From, To), ?integer(#int_rng{from=From, to=To})).
-define(int_set(Set), ?integer(#int_set{set=Set})).
--define(list(Types, Term, Size), #c{tag=?list_tag, elements=[Types,Term],
+-define(list(Types, Term, Size), #c{tag=?list_tag, elements={Types,Term},
qualifier=Size}).
-define(nil, #c{tag=?nil_tag}).
-define(nonempty_list(Types, Term),?list(Types, Term, ?nonempty_qual)).
@@ -350,9 +334,6 @@
-define(tuple_set(Tuples), #c{tag=?tuple_set_tag, elements=Tuples}).
-define(var(Id), #c{tag=?var_tag, elements=Id}).
--define(matchstate(P, Slots), #c{tag=?matchstate_tag, elements=[P,Slots]}).
--define(any_matchstate, ?matchstate(t_bitstr(), ?any)).
-
-define(byte, ?int_range(0, ?MAX_BYTE)).
-define(char, ?int_range(0, ?MAX_CHAR)).
-define(integer_pos, ?int_range(1, pos_inf)).
@@ -363,7 +344,7 @@
-type file_line() :: {file:name(), erl_anno:line()}.
-type record_key() :: {'record', atom()}.
--type type_key() :: {'type' | 'opaque', {atom(), arity()}}.
+-type type_key() :: {'type' | 'opaque', atom(), arity()}.
-type field() :: {atom(), erl_parse:abstract_expr(), erl_type()}.
-type record_value() :: {file_line(),
[{RecordSize :: non_neg_integer(), [field()]}]}.
@@ -380,18 +361,21 @@
%% Unions
%%
--define(union(List), #c{tag=?union_tag, elements=[_,_,_,_,_,_,_,_,_,_]=List}).
-
--define(atom_union(T), ?union([T,?none,?none,?none,?none,?none,?none,?none,?none,?none])).
--define(bitstr_union(T), ?union([?none,T,?none,?none,?none,?none,?none,?none,?none,?none])).
--define(function_union(T), ?union([?none,?none,T,?none,?none,?none,?none,?none,?none,?none])).
--define(identifier_union(T), ?union([?none,?none,?none,T,?none,?none,?none,?none,?none,?none])).
--define(list_union(T), ?union([?none,?none,?none,?none,T,?none,?none,?none,?none,?none])).
--define(number_union(T), ?union([?none,?none,?none,?none,?none,T,?none,?none,?none,?none])).
--define(tuple_union(T), ?union([?none,?none,?none,?none,?none,?none,T,?none,?none,?none])).
--define(matchstate_union(T), ?union([?none,?none,?none,?none,?none,?none,?none,T,?none,?none])).
--define(opaque_union(T), ?union([?none,?none,?none,?none,?none,?none,?none,?none,T,?none])).
--define(map_union(T), ?union([?none,?none,?none,?none,?none,?none,?none,?none,?none,T])).
+-define(union(List), #c{tag=?union_tag, elements=List}).
+-define(untagged_union(A, B, F, I, L, N, T, O, Map), [A,B,F,I,L,N,T,O,Map]).
+
+-define(num_types_in_union, length(?untagged_union(?any, ?any, ?any, ?any, ?any,
+ ?any, ?any, ?any, ?any))).
+
+-define(atom_union(T), ?union([T,?none,?none,?none,?none,?none,?none,?none,?none])).
+-define(bitstr_union(T), ?union([?none,T,?none,?none,?none,?none,?none,?none,?none])).
+-define(function_union(T), ?union([?none,?none,T,?none,?none,?none,?none,?none,?none])).
+-define(identifier_union(T), ?union([?none,?none,?none,T,?none,?none,?none,?none,?none])).
+-define(list_union(T), ?union([?none,?none,?none,?none,T,?none,?none,?none,?none])).
+-define(number_union(T), ?union([?none,?none,?none,?none,?none,T,?none,?none,?none])).
+-define(tuple_union(T), ?union([?none,?none,?none,?none,?none,?none,T,?none,?none])).
+-define(opaque_union(T), ?union([?none,?none,?none,?none,?none,?none,?none,T,?none])).
+-define(map_union(T), ?union([?none,?none,?none,?none,?none,?none,?none,?none,T])).
-define(integer_union(T), ?number_union(T)).
-define(float_union(T), ?number_union(T)).
-define(nil_union(T), ?list_union(T)).
@@ -464,7 +448,7 @@ has_opaque_subtype(T) ->
-spec t_opaque_structure(erl_type()) -> erl_type().
t_opaque_structure(?opaque(Elements)) ->
- t_sup([Struct || #opaque{struct = Struct} <- ordsets:to_list(Elements)]).
+ t_sup([Struct || #opaque{struct = Struct} <- Elements]).
-spec t_contains_opaque(erl_type()) -> boolean().
@@ -489,11 +473,12 @@ t_contains_opaque(?identifier(_Types), _Opaques) -> false;
t_contains_opaque(?int_range(_From, _To), _Opaques) -> false;
t_contains_opaque(?int_set(_Set), _Opaques) -> false;
t_contains_opaque(?integer(_Types), _Opaques) -> false;
+t_contains_opaque(?list(Type, ?nil, _), Opaques) ->
+ t_contains_opaque(Type, Opaques);
t_contains_opaque(?list(Type, Tail, _), Opaques) ->
t_contains_opaque(Type, Opaques) orelse t_contains_opaque(Tail, Opaques);
t_contains_opaque(?map(_, _, _) = Map, Opaques) ->
list_contains_opaque(map_all_types(Map), Opaques);
-t_contains_opaque(?matchstate(_P, _Slots), _Opaques) -> false;
t_contains_opaque(?nil, _Opaques) -> false;
t_contains_opaque(?number(_Set, _Tag), _Opaques) -> false;
t_contains_opaque(?opaque(_)=T, Opaques) ->
@@ -512,8 +497,9 @@ t_contains_opaque(?var(_Id), _Opaques) -> false.
-spec list_contains_opaque([erl_type()], [erl_type()]) -> boolean().
-list_contains_opaque(List, Opaques) ->
- lists:any(fun(E) -> t_contains_opaque(E, Opaques) end, List).
+list_contains_opaque([H|T], Opaques) ->
+ t_contains_opaque(H, Opaques) orelse list_contains_opaque(T, Opaques);
+list_contains_opaque([], _Opaques) -> false.
%% t_find_opaque_mismatch/2 of two types should only be used if their
%% t_inf is t_none() due to some opaque type violation. However,
@@ -667,7 +653,7 @@ decorate(Type, _T, _Opaques) -> Type.
%% Note: it is important that #opaque.struct is a subtype of the
%% opaque type.
decorate_with_opaque(Type, ?opaque(Set2), Opaques) ->
- case decoration(set_to_list(Set2), Type, Opaques, [], false) of
+ case decoration(Set2, Type, Opaques, [], false) of
{[], false} -> Type;
{List, All} when List =/= [] ->
NewType = sup_opaque(List),
@@ -700,9 +686,9 @@ list_decorate(List, L, Opaques) ->
union_decorate(U1, U2, Opaques) ->
Union = union_decorate(U1, U2, Opaques, 0, []),
- [A,B,F,I,L,N,T,M,_,Map] = U1,
- [_,_,_,_,_,_,_,_,Opaque,_] = U2,
- List = [A,B,F,I,L,N,T,M,Map],
+ ?untagged_union(A,B,F,I,L,N,T,_,Map) = U1,
+ ?untagged_union(_,_,_,_,_,_,_,Opaque,_) = U2,
+ List = [A,B,F,I,L,N,T,Map],
DecList = [Dec ||
E <- List,
not t_is_none(E),
@@ -761,25 +747,12 @@ decorate_tuples_in_sets([], _L, _Opaques, Acc) ->
-spec t_opaque_from_records(type_table()) -> [erl_type()].
t_opaque_from_records(RecMap) ->
- OpaqueRecMap =
- maps:filter(fun(Key, _Value) ->
- case Key of
- {opaque, _Name, _Arity} -> true;
- _ -> false
- end
- end, RecMap),
- OpaqueTypeMap =
- maps:map(fun({opaque, Name, _Arity},
- {{Module, _FileLocation, _Form, ArgNames}, _Type}) ->
- %% Args = args_to_types(ArgNames),
- %% List = lists:zip(ArgNames, Args),
- %% TmpVarTab = maps:to_list(List),
- %% Rep = t_from_form(Type, RecDict, TmpVarTab),
- Rep = t_any(), % not used for anything right now
- Args = [t_any() || _ <- ArgNames],
- t_opaque(Module, Name, Args, Rep)
- end, OpaqueRecMap),
- [OpaqueType || {_Key, OpaqueType} <- maps:to_list(OpaqueTypeMap)].
+ Any = t_any(),
+ [begin
+ Rep = Any, % not used for anything right now
+ Args = [Any || _ <- ArgNames],
+ t_opaque(Module, Name, Args, Rep)
+ end || {opaque, Name, _} := {{Module, _, _, ArgNames}, _} <- RecMap].
%%-----------------------------------------------------------------------------
%% Unit type. Signals non termination.
@@ -835,7 +808,7 @@ t_atom_vals(Type, Opaques) ->
do_opaque(Type, Opaques, fun atom_vals/1).
atom_vals(?atom(?any)) -> unknown;
-atom_vals(?atom(Set)) -> set_to_list(Set);
+atom_vals(?atom(Set)) -> Set;
atom_vals(?opaque(_)) -> unknown;
atom_vals(Other) ->
?atom(_) = Atm = t_inf(t_atom(), Other),
@@ -887,15 +860,15 @@ t_is_boolean(Type, Opaques) ->
t_boolean() ->
?atom(set_from_list([false, true])).
-is_boolean(?atom(?any)) -> false;
is_boolean(?atom(Set)) ->
- case set_size(Set) of
- 1 -> set_is_element(true, Set) orelse set_is_element(false, Set);
- 2 -> set_is_element(true, Set) andalso set_is_element(false, Set);
- N when is_integer(N), N > 2 -> false
+ case Set of
+ [Atom] when erlang:is_boolean(Atom) -> true;
+ [false,true] -> true;
+ _ -> false
end;
is_boolean(_) -> false.
+
%%-----------------------------------------------------------------------------
%% Binaries
%%
@@ -997,92 +970,6 @@ is_bitstr(?bitstr(_, _)) -> true;
is_bitstr(_) -> false.
%%-----------------------------------------------------------------------------
-%% Matchstates
-%%
-
--spec t_matchstate() -> erl_type().
-
-t_matchstate() ->
- ?any_matchstate.
-
--spec t_matchstate(erl_type(), non_neg_integer()) -> erl_type().
-
-t_matchstate(Init, 0) ->
- ?matchstate(Init, Init);
-t_matchstate(Init, Max) when is_integer(Max) ->
- Slots = [Init|[?none || _ <- lists:seq(1, Max)]],
- ?matchstate(Init, t_product(Slots)).
-
--spec t_is_matchstate(erl_type()) -> boolean().
-
-t_is_matchstate(?matchstate(_, _)) -> true;
-t_is_matchstate(_) -> false.
-
--spec t_matchstate_present(erl_type()) -> erl_type().
-
-t_matchstate_present(Type) ->
- case t_inf(t_matchstate(), Type) of
- ?matchstate(P, _) -> P;
- _ -> ?none
- end.
-
--spec t_matchstate_slot(erl_type(), non_neg_integer()) -> erl_type().
-
-t_matchstate_slot(Type, Slot) ->
- RealSlot = Slot + 1,
- case t_inf(t_matchstate(), Type) of
- ?matchstate(_, ?any) -> ?any;
- ?matchstate(_, ?product(Vals)) when length(Vals) >= RealSlot ->
- lists:nth(RealSlot, Vals);
- ?matchstate(_, ?product(_)) ->
- ?none;
- ?matchstate(_, SlotType) when RealSlot =:= 1 ->
- SlotType;
- _ ->
- ?none
- end.
-
--spec t_matchstate_slots(erl_type()) -> erl_type().
-
-t_matchstate_slots(?matchstate(_, Slots)) ->
- Slots.
-
--spec t_matchstate_update_present(erl_type(), erl_type()) -> erl_type().
-
-t_matchstate_update_present(New, Type) ->
- case t_inf(t_matchstate(), Type) of
- ?matchstate(_, Slots) ->
- ?matchstate(New, Slots);
- _ -> ?none
- end.
-
--spec t_matchstate_update_slot(erl_type(), erl_type(), non_neg_integer()) -> erl_type().
-
-t_matchstate_update_slot(New, Type, Slot) ->
- RealSlot = Slot + 1,
- case t_inf(t_matchstate(), Type) of
- ?matchstate(Pres, Slots) ->
- NewSlots =
- case Slots of
- ?any ->
- ?any;
- ?product(Vals) when length(Vals) >= RealSlot ->
- NewTuple = setelement(RealSlot, list_to_tuple(Vals), New),
- NewVals = tuple_to_list(NewTuple),
- ?product(NewVals);
- ?product(_) ->
- ?none;
- _ when RealSlot =:= 1 ->
- New;
- _ ->
- ?none
- end,
- ?matchstate(Pres, NewSlots);
- _ ->
- ?none
- end.
-
-%%-----------------------------------------------------------------------------
%% Functions
%%
@@ -1277,7 +1164,7 @@ t_number_vals(Type) ->
t_number_vals(Type, Opaques) ->
do_opaque(Type, Opaques, fun number_vals/1).
-number_vals(?int_set(Set)) -> set_to_list(Set);
+number_vals(?int_set(Set)) -> Set;
number_vals(?number(_, _)) -> unknown;
number_vals(?opaque(_)) -> unknown;
number_vals(Other) ->
@@ -1473,10 +1360,8 @@ t_list() ->
-spec t_list(erl_type()) -> erl_type().
-t_list(?none) -> ?none;
-t_list(?unit) -> ?none;
t_list(Contents) ->
- ?list(Contents, ?nil, ?unknown_qual).
+ t_sup(t_nonempty_list(Contents), t_nil()).
-spec t_list_elements(erl_type()) -> erl_type().
@@ -1607,12 +1492,11 @@ t_widen_to_number(?map(Pairs, DefK, DefV)) ->
L = [{t_widen_to_number(K), MNess, t_widen_to_number(V)} ||
{K, MNess, V} <- Pairs],
t_map(L, t_widen_to_number(DefK), t_widen_to_number(DefV));
-t_widen_to_number(?matchstate(_P, _Slots) = T) -> T;
t_widen_to_number(?nil) -> ?nil;
t_widen_to_number(?number(_Set, _Tag)) -> t_number();
t_widen_to_number(?opaque(Set)) ->
L = [Opaque#opaque{struct = t_widen_to_number(S)} ||
- #opaque{struct = S} = Opaque <- set_to_list(Set)],
+ #opaque{struct = S} = Opaque <- Set],
?opaque(ordsets:from_list(L));
t_widen_to_number(?product(Types)) ->
?product(list_widen_to_number(Types));
@@ -1753,7 +1637,7 @@ normalise_map_optionals([E|T], DefK, DefV, Es, F) ->
%% `#{0 => t(), pos_integer() => t()}' is to be represented by
%% `#{non_neg_integer() => t()}'.
needs_to_be_merged(?int_set(Set), DefK) ->
- [I] = set_to_list(Set),
+ [I] = Set,
Iplus = t_integer(I + 1),
Iminus = t_integer(I - 1),
InfPlus = t_inf(Iplus, DefK),
@@ -2109,9 +1993,9 @@ get_tuple_tags(_) -> [?any].
tuple_tags(?atom(?any)) -> [?any];
tuple_tags(?atom(Set)) ->
- case set_size(Set) > ?TUPLE_TAG_LIMIT of
+ case length(Set) > ?TUPLE_TAG_LIMIT of
true -> [?any];
- false -> [t_atom(A) || A <- set_to_list(Set)]
+ false -> [t_atom(A) || A <- Set]
end;
tuple_tags(_) -> [?any].
@@ -2321,6 +2205,8 @@ t_var_name(?var(Id)) -> Id.
t_has_var(?var(_)) -> true;
t_has_var(?function(Domain, Range)) ->
t_has_var(Domain) orelse t_has_var(Range);
+t_has_var(?list(Contents, ?nil, _)) ->
+ t_has_var(Contents);
t_has_var(?list(Contents, Termination, _)) ->
t_has_var(Contents) orelse t_has_var(Termination);
t_has_var(?product(Types)) -> t_has_var_list(Types);
@@ -2333,7 +2219,7 @@ t_has_var(?map(_, DefK, _)= Map) ->
t_has_var_list(map_all_values(Map)) orelse
t_has_var(DefK);
t_has_var(?opaque(Set)) ->
- t_has_var_list([O#opaque.struct || O <- set_to_list(Set)]);
+ t_has_var_list([O#opaque.struct || O <- Set]);
t_has_var(?union(List)) ->
t_has_var_list(List);
t_has_var(_) -> false.
@@ -2372,7 +2258,7 @@ t_collect_var_names(?map(_, DefK, _) = Map, Acc0) ->
Acc = t_collect_vars_list(map_all_values(Map), Acc0),
t_collect_var_names(DefK, Acc);
t_collect_var_names(?opaque(Set), Acc) ->
- t_collect_vars_list([O#opaque.struct || O <- set_to_list(Set)], Acc);
+ t_collect_vars_list([O#opaque.struct || O <- Set], Acc);
t_collect_var_names(?union(List), Acc) ->
t_collect_vars_list(List, Acc);
t_collect_var_names(_, Acc) ->
@@ -2406,7 +2292,7 @@ t_from_term(T) when is_function(T) ->
t_from_term(T) when is_integer(T) -> t_integer(T);
t_from_term(T) when is_map(T) ->
Pairs = [{t_from_term(K), ?mand, t_from_term(V)}
- || {K, V} <- maps:to_list(T)],
+ || K := V <- T],
{Stons, Rest} = lists:partition(fun({K,_,_}) -> is_singleton_type(K) end,
Pairs),
{DefK, DefV}
@@ -2466,52 +2352,6 @@ t_from_range(pos_inf, neg_inf) -> t_none().
-endif.
--spec t_from_range_unsafe(rng_elem(), rng_elem()) -> erl_type().
-
-t_from_range_unsafe(pos_inf, pos_inf) -> ?integer_pos;
-t_from_range_unsafe(neg_inf, neg_inf) -> ?integer_neg;
-t_from_range_unsafe(neg_inf, pos_inf) -> t_integer();
-t_from_range_unsafe(neg_inf, Y) -> ?int_range(neg_inf, Y);
-t_from_range_unsafe(X, pos_inf) -> ?int_range(X, pos_inf);
-t_from_range_unsafe(X, Y) when is_integer(X), is_integer(Y), X =< Y ->
- if (Y - X) < ?SET_LIMIT -> t_integers(lists:seq(X, Y));
- true -> ?int_range(X, Y)
- end;
-t_from_range_unsafe(X, Y) when is_integer(X), is_integer(Y) -> t_none();
-t_from_range_unsafe(pos_inf, neg_inf) -> t_none().
-
--spec t_is_fixnum(erl_type()) -> boolean().
-
-t_is_fixnum(?int_range(neg_inf, _)) -> false;
-t_is_fixnum(?int_range(_, pos_inf)) -> false;
-t_is_fixnum(?int_range(From, To)) ->
- is_fixnum(From) andalso is_fixnum(To);
-t_is_fixnum(?int_set(Set)) ->
- is_fixnum(set_min(Set)) andalso is_fixnum(set_max(Set));
-t_is_fixnum(_) -> false.
-
--spec is_fixnum(integer()) -> boolean().
-
-is_fixnum(N) when is_integer(N) ->
- Bits = ?BITS,
- (N =< ((1 bsl (Bits - 1)) - 1)) andalso (N >= -(1 bsl (Bits - 1))).
-
-infinity_geq(pos_inf, _) -> true;
-infinity_geq(_, pos_inf) -> false;
-infinity_geq(_, neg_inf) -> true;
-infinity_geq(neg_inf, _) -> false;
-infinity_geq(A, B) -> A >= B.
-
--spec t_is_bitwidth(erl_type()) -> boolean().
-
-t_is_bitwidth(?int_range(neg_inf, _)) -> false;
-t_is_bitwidth(?int_range(_, pos_inf)) -> false;
-t_is_bitwidth(?int_range(From, To)) ->
- infinity_geq(From, 0) andalso infinity_geq(?BITS, To);
-t_is_bitwidth(?int_set(Set)) ->
- infinity_geq(set_min(Set), 0) andalso infinity_geq(?BITS, set_max(Set));
-t_is_bitwidth(_) -> false.
-
-spec number_min(erl_type()) -> rng_elem().
number_min(Type) ->
@@ -2585,24 +2425,29 @@ expand_range_from_set(Range = ?int_range(From, To), Set) ->
%%=============================================================================
%%-----------------------------------------------------------------------------
-%% Supremum
+%% Supremum/join.
%%
-spec t_sup([erl_type()]) -> erl_type().
t_sup([]) -> ?none;
t_sup(Ts) ->
- case lists:any(fun is_any/1, Ts) of
- true -> ?any;
+ case any_any(Ts) of
+ true ->
+ ?any;
false ->
- t_sup1(Ts, [])
+ [Type|NewTs] = Ts,
+ t_sup1(NewTs, Type)
end.
-t_sup1([H1, H2|T], L) ->
- t_sup1(T, [t_sup(H1, H2)|L]);
-t_sup1([T], []) -> do_not_subst_all_vars_to_any(T);
-t_sup1(Ts, L) ->
- t_sup1(Ts++L, []).
+any_any([?any|_]) -> true;
+any_any([_|T]) -> any_any(T);
+any_any([]) -> false.
+
+t_sup1([H|T], Type) ->
+ t_sup1(T, t_sup(H, Type));
+t_sup1([], Type) ->
+ do_not_subst_all_vars_to_any(Type).
-spec t_sup(erl_type(), erl_type()) -> erl_type().
@@ -2625,15 +2470,12 @@ t_sup(?function(Domain1, Range1), ?function(Domain2, Range2)) ->
t_sup(?identifier(Set1), ?identifier(Set2)) ->
?identifier(set_union(Set1, Set2));
t_sup(?opaque(Set1), ?opaque(Set2)) ->
- sup_opaque(set_to_list(ordsets:union(Set1, Set2)));
+ sup_opaque(ordsets:union(Set1, Set2));
%%Disallow unions with opaque types
%%t_sup(T1=?opaque(_,_,_), T2) ->
%% io:format("Debug: t_sup executed with args ~w and ~w~n",[T1, T2]), ?none;
%%t_sup(T1, T2=?opaque(_,_,_)) ->
%% io:format("Debug: t_sup executed with args ~w and ~w~n",[T1, T2]), ?none;
-t_sup(?matchstate(Pres1, Slots1), ?matchstate(Pres2, Slots2)) ->
- ?matchstate(t_sup(Pres1, Pres2), t_sup(Slots1, Slots2));
-t_sup(?nil, ?nil) -> ?nil;
t_sup(?nil, ?list(Contents, Termination, _)) ->
?list(Contents, t_sup(?nil, Termination), ?unknown_qual);
t_sup(?list(Contents, Termination, _), ?nil) ->
@@ -2652,7 +2494,6 @@ t_sup(?list(Contents1, Termination1, Size1),
?list(NewContents, NewTermination, NewSize);
t_sup(?number(_, _), ?number(?any, ?unknown_qual) = T) -> T;
t_sup(?number(?any, ?unknown_qual) = T, ?number(_, _)) -> T;
-t_sup(?float, ?float) -> ?float;
t_sup(?float, ?integer(_)) -> t_number();
t_sup(?integer(_), ?float) -> t_number();
t_sup(?integer(?any) = T, ?integer(_)) -> T;
@@ -2775,7 +2616,7 @@ sup_tuples_in_set(L1, L2) ->
%% We will reach the set limit. Widen now.
[t_tuple(sup_tuple_elements(L1 ++ L2))];
?atom(Set) ->
- case set_size(Set) > ?TUPLE_TAG_LIMIT of
+ case length(Set) > ?TUPLE_TAG_LIMIT of
true ->
%% We will reach the set limit. Widen now.
[t_tuple(sup_tuple_elements(L1 ++ L2))];
@@ -2810,12 +2651,16 @@ sup_union([?none|Left1], [?none|Left2], N, Acc) ->
sup_union([T1|Left1], [T2|Left2], N, Acc) ->
sup_union(Left1, Left2, N+1, [t_sup(T1, T2)|Acc]);
sup_union([], [], N, Acc) ->
- if N =:= 0 -> ?none;
- N =:= 1 ->
+ if
+ N =:= 0 ->
+ ?none;
+ N =:= 1 ->
[Type] = [T || T <- Acc, T =/= ?none],
Type;
- N =:= length(Acc) -> ?any;
- true -> ?union(lists:reverse(Acc))
+ N =:= ?num_types_in_union ->
+ ?any;
+ true ->
+ ?union(lists:reverse(Acc))
end.
force_union(T = ?atom(_)) -> ?atom_union(T);
@@ -2829,7 +2674,6 @@ force_union(T = ?opaque(_)) -> ?opaque_union(T);
force_union(T = ?map(_,_,_)) -> ?map_union(T);
force_union(T = ?tuple(_, _, _)) -> ?tuple_union(T);
force_union(T = ?tuple_set(_)) -> ?tuple_union(T);
-force_union(T = ?matchstate(_, _)) -> ?matchstate_union(T);
force_union(T = ?union(_)) -> T.
%%-----------------------------------------------------------------------------
@@ -2891,7 +2735,7 @@ do_elements(Type0, Opaques) ->
end.
%%-----------------------------------------------------------------------------
-%% Infimum
+%% Infimum/meet.
%%
-spec t_inf([erl_type()]) -> erl_type().
@@ -2967,8 +2811,6 @@ t_inf(?map(_, ADefK, ADefV) = A, ?map(_, BDefK, BDefV) = B, _Opaques) ->
(K, _, V1, _, V2) -> {K, ?mand, t_inf(V1, V2)}
end, A, B),
t_map(Pairs, t_inf(ADefK, BDefK), t_inf(ADefV, BDefV));
-t_inf(?matchstate(Pres1, Slots1), ?matchstate(Pres2, Slots2), _Opaques) ->
- ?matchstate(t_inf(Pres1, Pres2), t_inf(Slots1, Slots2));
t_inf(?nil, ?nil, _Opaques) -> ?nil;
t_inf(?nil, ?nonempty_list(_, _), _Opaques) ->
?none;
@@ -3082,8 +2924,7 @@ inf_opaque1(T1, ?opaque(Set2)=T2, Pos, Opaques) ->
case Opaques =:= 'universe' orelse inf_is_opaque_type(T2, Pos, Opaques) of
false -> ?none;
true ->
- List2 = set_to_list(Set2),
- case inf_collect(T1, List2, Opaques, []) of
+ case inf_collect(T1, Set2, Opaques, []) of
[] -> ?none;
OpL -> ?opaque(ordsets:from_list(OpL))
end
@@ -3138,7 +2979,7 @@ inf_opaque(Set1, Set2, Opaques) ->
%% Optimization: do just one lookup.
inf_look_up(Set, Opaques) ->
[{Opaques =:= 'universe' orelse inf_is_opaque_type2(T, Opaques), T} ||
- T <- set_to_list(Set)].
+ T <- Set].
inf_is_opaque_type2(T, {match, Opaques}) ->
is_opaque_type2(T, Opaques);
@@ -3260,16 +3101,16 @@ inf_tuples_in_sets2(_, [], Acc, _Opaques) -> lists:reverse(Acc).
inf_union(U1, U2, Opaques) ->
OpaqueFun =
fun(Union1, Union2, InfFun) ->
- [_,_,_,_,_,_,_,_,Opaque,_] = Union1,
- [A,B,F,I,L,N,T,M,_,Map] = Union2,
- List = [A,B,F,I,L,N,T,M,Map],
+ ?untagged_union(_,_,_,_,_,_,_,Opaque,_) = Union1,
+ ?untagged_union(A,B,F,I,L,N,T,_,Map) = Union2,
+ List = [A,B,F,I,L,N,T,Map],
inf_union_collect(List, Opaque, InfFun, [], [])
end,
{O1, ThrowList1} =
OpaqueFun(U1, U2, fun(E, Opaque) -> t_inf(Opaque, E, Opaques) end),
- {O2, ThrowList2}
- = OpaqueFun(U2, U1, fun(E, Opaque) -> t_inf(E, Opaque, Opaques) end),
- {Union, ThrowList3} = inf_union(U1, U2, 0, [], [], Opaques),
+ {O2, ThrowList2} =
+ OpaqueFun(U2, U1, fun(E, Opaque) -> t_inf(E, Opaque, Opaques) end),
+ {Union, ThrowList3} = inf_union(U1, U2, ?none, [], [], Opaques),
ThrowList = lists:merge3(ThrowList1, ThrowList2, ThrowList3),
case t_sup([O1, O2, Union]) of
?none when ThrowList =/= [] -> throw({pos, lists:usort(ThrowList)});
@@ -3288,21 +3129,26 @@ inf_union_collect([E|L], Opaque, InfFun, InfList, ThrowList) ->
inf_union_collect(L, Opaque, InfFun, InfList, Ns ++ ThrowList)
end.
-inf_union([?none|Left1], [?none|Left2], N, Acc, ThrowList, Opaques) ->
- inf_union(Left1, Left2, N, [?none|Acc], ThrowList, Opaques);
-inf_union([T1|Left1], [T2|Left2], N, Acc, ThrowList, Opaques) ->
+inf_union([?none|Left1], [?none|Left2], Type, Acc, ThrowList, Opaques) ->
+ inf_union(Left1, Left2, Type, [?none|Acc], ThrowList, Opaques);
+inf_union([T1|Left1], [T2|Left2], Type, Acc, ThrowList, Opaques) ->
try t_inf(T1, T2, Opaques) of
- ?none -> inf_union(Left1, Left2, N, [?none|Acc], ThrowList, Opaques);
- T -> inf_union(Left1, Left2, N+1, [T|Acc], ThrowList, Opaques)
- catch throw:{pos, Ns} ->
- inf_union(Left1, Left2, N, [?none|Acc], Ns ++ ThrowList, Opaques)
+ ?none ->
+ inf_union(Left1, Left2, Type, [?none|Acc], ThrowList, Opaques);
+ T when Type =:= ?none ->
+ inf_union(Left1, Left2, T, [T|Acc], ThrowList, Opaques);
+ T ->
+ inf_union(Left1, Left2, ?union_tag, [T|Acc], ThrowList, Opaques)
+ catch
+ throw:{pos, Ns} ->
+ inf_union(Left1, Left2, Type, [?none|Acc], Ns ++ ThrowList, Opaques)
end;
-inf_union([], [], N, Acc, ThrowList, _Opaques) ->
- if N =:= 0 -> {?none, ThrowList};
- N =:= 1 ->
- [Type] = [T || T <- Acc, T =/= ?none],
- {Type, ThrowList};
- N >= 2 -> {?union(lists:reverse(Acc)), ThrowList}
+inf_union([], [], Type, Acc, ThrowList, _Opaques) ->
+ case Type of
+ ?union_tag ->
+ {?union(lists:reverse(Acc)), ThrowList};
+ _ ->
+ {Type, ThrowList}
end.
inf_bitstr(U1, B1, U2, B2) ->
@@ -3356,9 +3202,9 @@ subst_all_vars_to_any(T) ->
t_subst(T, #{}).
t_subst_aux(?var(Id), Map) ->
- case maps:find(Id, Map) of
- error -> ?any;
- {ok, Type} -> Type
+ case Map of
+ #{Id := Type} -> Type;
+ #{} -> ?any
end;
t_subst_aux(?list(Contents, Termination, Size), Map) ->
case t_subst_aux(Contents, Map) of
@@ -3389,7 +3235,7 @@ t_subst_aux(?map(Pairs, DefK, DefV), Map) ->
t_subst_aux(DefK, Map), t_subst_aux(DefV, Map));
t_subst_aux(?opaque(Es), Map) ->
List = [Opaque#opaque{struct = t_subst_aux(S, Map)} ||
- Opaque = #opaque{struct = S} <- set_to_list(Es)],
+ Opaque = #opaque{struct = S} <- Es],
?opaque(ordsets:from_list(List));
t_subst_aux(?union(List), Map) ->
?union([t_subst_aux(E, Map) || E <- List]);
@@ -3423,14 +3269,18 @@ t_unify_table_only(?var(Id1) = LHS, ?var(Id2) = RHS, VarMap) ->
VarMap#{ Id1 => LHS, Id2 => RHS }
end;
t_unify_table_only(?var(Id), Type, VarMap) ->
- case maps:find(Id, VarMap) of
- error -> VarMap#{Id => Type};
- {ok, VarType} -> t_unify_table_only(VarType, Type, VarMap)
+ case VarMap of
+ #{Id := VarType} ->
+ t_unify_table_only(VarType, Type, VarMap);
+ #{} ->
+ VarMap#{Id => Type}
end;
t_unify_table_only(Type, ?var(Id), VarMap) ->
- case maps:find(Id, VarMap) of
- error -> VarMap#{Id => Type};
- {ok, VarType} -> t_unify_table_only(VarType, Type, VarMap)
+ case VarMap of
+ #{Id := VarType} ->
+ t_unify_table_only(VarType, Type, VarMap);
+ #{} ->
+ VarMap#{Id => Type}
end;
t_unify_table_only(?function(Domain1, Range1), ?function(Domain2, Range2), VarMap) ->
VarMap1 = t_unify_table_only(Domain1, Domain2, VarMap),
@@ -3525,11 +3375,11 @@ unify_union1(?union(List), T1, T2) ->
end.
unify_union(List) ->
- [A,B,F,I,L,N,T,M,O,Map] = List,
+ ?untagged_union(A,B,F,I,L,N,T,O,Map) = List,
if O =:= ?none -> no;
true ->
S = t_opaque_structure(O),
- {yes, t_sup([A,B,F,I,L,N,T,M,S,Map])}
+ {yes, t_sup([A,B,F,I,L,N,T,S,Map])}
end.
-spec is_opaque_type(erl_type(), [erl_type()]) -> boolean().
@@ -3554,94 +3404,6 @@ is_type_name(Mod, Name, Arity, Mod, Name, Arity) ->
is_type_name(_Mod1, _Name1, _Arity1, _Mod2, _Name2, _Arity2) ->
false.
-%%t_assign_variables_to_subtype(T1, T2) ->
-%% try
-%% Dict = assign_vars(T1, T2, dict:new()),
-%% {ok, dict:map(fun(_Param, List) -> t_sup(List) end, Dict)}
-%% catch
-%% throw:error -> error
-%% end.
-
-%%assign_vars(_, ?var(_), _Dict) ->
-%% erlang:error("Variable in right hand side of assignment");
-%%assign_vars(?any, _, Dict) ->
-%% Dict;
-%%assign_vars(?var(_) = Var, Type, Dict) ->
-%% store_var(Var, Type, Dict);
-%%assign_vars(?function(Domain1, Range1), ?function(Domain2, Range2), Dict) ->
-%% DomainList =
-%% case Domain2 of
-%% ?any -> [];
-%% ?product(List) -> List
-%% end,
-%% case any_none([Range2|DomainList]) of
-%% true -> throw(error);
-%% false ->
-%% Dict1 = assign_vars(Domain1, Domain2, Dict),
-%% assign_vars(Range1, Range2, Dict1)
-%% end;
-%%assign_vars(?list(_Contents, _Termination, ?any), ?nil, Dict) ->
-%% Dict;
-%%assign_vars(?list(Contents1, Termination1, Size1),
-%% ?list(Contents2, Termination2, Size2), Dict) ->
-%% Dict1 = assign_vars(Contents1, Contents2, Dict),
-%% Dict2 = assign_vars(Termination1, Termination2, Dict1),
-%% case {Size1, Size2} of
-%% {S, S} -> Dict2;
-%% {?any, ?nonempty_qual} -> Dict2;
-%% {_, _} -> throw(error)
-%% end;
-%%assign_vars(?product(Types1), ?product(Types2), Dict) ->
-%% case length(Types1) =:= length(Types2) of
-%% true -> assign_vars_lists(Types1, Types2, Dict);
-%% false -> throw(error)
-%% end;
-%%assign_vars(?tuple(?any, ?any, ?any), ?tuple(?any, ?any, ?any), Dict) ->
-%% Dict;
-%%assign_vars(?tuple(?any, ?any, ?any), ?tuple(_, _, _), Dict) ->
-%% Dict;
-%%assign_vars(?tuple(Elements1, Arity, _),
-%% ?tuple(Elements2, Arity, _), Dict) when Arity =/= ?any ->
-%% assign_vars_lists(Elements1, Elements2, Dict);
-%%assign_vars(?tuple_set(_) = T, ?tuple_set(List2), Dict) ->
-%% %% All Rhs tuples must already be subtypes of Lhs, so we can take
-%% %% each one separately.
-%% assign_vars_lists([T || _ <- List2], List2, Dict);
-%%assign_vars(?tuple(?any, ?any, ?any), ?tuple_set(_), Dict) ->
-%% Dict;
-%%assign_vars(?tuple(_, Arity, _) = T1, ?tuple_set(List), Dict) ->
-%% case reduce_tuple_tags(List) of
-%% [Tuple = ?tuple(_, Arity, _)] -> assign_vars(T1, Tuple, Dict);
-%% _ -> throw(error)
-%% end;
-%%assign_vars(?tuple_set(List), ?tuple(_, Arity, Tag) = T2, Dict) ->
-%% case [T || ?tuple(_, Arity1, Tag1) = T <- List,
-%% Arity1 =:= Arity, Tag1 =:= Tag] of
-%% [] -> throw(error);
-%% [T1] -> assign_vars(T1, T2, Dict)
-%% end;
-%%assign_vars(?union(U1), T2, Dict) ->
-%% ?union(U2) = force_union(T2),
-%% assign_vars_lists(U1, U2, Dict);
-%%assign_vars(T, T, Dict) ->
-%% Dict;
-%%assign_vars(T1, T2, Dict) ->
-%% case t_is_subtype(T2, T1) of
-%% false -> throw(error);
-%% true -> Dict
-%% end.
-
-%%assign_vars_lists([T1|Left1], [T2|Left2], Dict) ->
-%% assign_vars_lists(Left1, Left2, assign_vars(T1, T2, Dict));
-%%assign_vars_lists([], [], Dict) ->
-%% Dict.
-
-%%store_var(?var(Id), Type, Dict) ->
-%% case dict:find(Id, Dict) of
-%% error -> dict:store(Id, [Type], Dict);
-%% {ok, _VarType0} -> dict:update(Id, fun(X) -> [Type|X] end, Dict)
-%% end.
-
%%-----------------------------------------------------------------------------
%% Subtraction.
%%
@@ -3699,14 +3461,6 @@ t_subtract(?opaque(_)=T1, T2) ->
opaque_subtract(T1, T2);
t_subtract(T1, ?opaque(_)=T2) ->
t_subtract(T1, t_opaque_structure(T2));
-t_subtract(?matchstate(Pres1, Slots1), ?matchstate(Pres2, _Slots2)) ->
- Pres = t_subtract(Pres1, Pres2),
- case t_is_none(Pres) of
- true -> ?none;
- false -> ?matchstate(Pres, Slots1)
- end;
-t_subtract(?matchstate(Present, Slots), _) ->
- ?matchstate(Present, Slots);
t_subtract(?nil, ?nil) ->
?none;
t_subtract(?nil, ?nonempty_list(_, _)) ->
@@ -3881,7 +3635,7 @@ t_subtract(T1, T2) ->
opaque_subtract(?opaque(Set1), T2) ->
List = [T1#opaque{struct = Sub} ||
- #opaque{struct = S1}=T1 <- set_to_list(Set1),
+ #opaque{struct = S1}=T1 <- Set1,
not t_is_none(Sub = t_subtract(S1, T2))],
case List of
[] -> ?none;
@@ -3903,11 +3657,11 @@ t_subtract_lists([], [], Acc) ->
-spec subtract_union([erl_type(),...], [erl_type(),...]) -> erl_type().
subtract_union(U1, U2) ->
- [A1,B1,F1,I1,L1,N1,T1,M1,O1,Map1] = U1,
- [A2,B2,F2,I2,L2,N2,T2,M2,O2,Map2] = U2,
- List1 = [A1,B1,F1,I1,L1,N1,T1,M1,?none,Map1],
- List2 = [A2,B2,F2,I2,L2,N2,T2,M2,?none,Map2],
- Sub1 = subtract_union(List1, List2, 0, []),
+ ?untagged_union(A1,B1,F1,I1,L1,N1,T1,O1,Map1) = U1,
+ ?untagged_union(A2,B2,F2,I2,L2,N2,T2,O2,Map2) = U2,
+ List1 = ?untagged_union(A1,B1,F1,I1,L1,N1,T1,?none,Map1),
+ List2 = ?untagged_union(A2,B2,F2,I2,L2,N2,T2,?none,Map2),
+ Sub1 = subtract_union(List1, List2, ?none, []),
O = if O1 =:= ?none -> O1;
true -> t_subtract(O1, ?union(U2))
end,
@@ -3916,28 +3670,28 @@ subtract_union(U1, U2) ->
end,
t_sup(O, Sub2).
--spec subtract_union([erl_type()], [erl_type()], non_neg_integer(), [erl_type()]) -> erl_type().
-
-subtract_union([T1|Left1], [T2|Left2], N, Acc) ->
+subtract_union([T1|Left1], [T2|Left2], Type, Acc) ->
case t_subtract(T1, T2) of
- ?none -> subtract_union(Left1, Left2, N, [?none|Acc]);
- T -> subtract_union(Left1, Left2, N+1, [T|Acc])
+ ?none -> subtract_union(Left1, Left2, Type, [?none|Acc]);
+ T when Type =:= none -> subtract_union(Left1, Left2, T, [T|Acc]);
+ T -> subtract_union(Left1, Left2, ?union_tag, [T|Acc])
end;
-subtract_union([], [], 0, _Acc) ->
- ?none;
-subtract_union([], [], 1, Acc) ->
- [T] = [X || X <- Acc, X =/= ?none],
- T;
-subtract_union([], [], N, Acc) when is_integer(N), N > 1 ->
- ?union(lists:reverse(Acc)).
-
-replace_nontrivial_element(El1, El2) ->
- replace_nontrivial_element(El1, El2, []).
+subtract_union([], [], Type, Acc) ->
+ case Type of
+ ?union_tag ->
+ ?union(lists:reverse(Acc));
+ _ ->
+ Type
+ end.
-replace_nontrivial_element([T1|Left1], [?none|Left2], Acc) ->
- replace_nontrivial_element(Left1, Left2, [T1|Acc]);
-replace_nontrivial_element([_|Left1], [T2|_], Acc) ->
- lists:reverse(Acc) ++ [T2|Left1].
+%% Helper for tuple and product subtraction. The second list
+%% should contain a single element that is not none. That element
+%% will replace the element in the corresponding position in the
+%% first list.
+replace_nontrivial_element([T1|Left1], [?none|Left2]) ->
+ [T1|replace_nontrivial_element(Left1, Left2)];
+replace_nontrivial_element([_|Left1], [T2|_]) ->
+ [T2|Left1].
subtract_bin(?bitstr(U1, B1), ?bitstr(U1, B1)) ->
?none;
@@ -4027,17 +3781,16 @@ t_unopaque(?product(Types), Opaques) ->
?product([t_unopaque(T, Opaques) || T <- Types]);
t_unopaque(?function(Domain, Range), Opaques) ->
?function(t_unopaque(Domain, Opaques), t_unopaque(Range, Opaques));
-t_unopaque(?union([A,B,F,I,L,N,T,M,O,Map]), Opaques) ->
+t_unopaque(?union(?untagged_union(A,B,F,I,L,N,T,O,Map)), Opaques) ->
UL = t_unopaque(L, Opaques),
UT = t_unopaque(T, Opaques),
UF = t_unopaque(F, Opaques),
- UM = t_unopaque(M, Opaques),
UMap = t_unopaque(Map, Opaques),
{OF,UO} = case t_unopaque(O, Opaques) of
?opaque(_) = O1 -> {O1, []};
Type -> {?none, [Type]}
end,
- t_sup([?union([A,B,UF,I,UL,N,UT,UM,OF,UMap])|UO]);
+ t_sup([?union([A,B,UF,I,UL,N,UT,OF,UMap])|UO]);
t_unopaque(?map(Pairs,DefK,DefV), Opaques) ->
t_map([{K, MNess, t_unopaque(V, Opaques)} || {K, MNess, V} <- Pairs],
t_unopaque(DefK, Opaques),
@@ -4066,11 +3819,12 @@ is_limited(?tuple(?any, ?any, ?any), _K) -> true;
is_limited(?tuple(Elements, _Arity, _), K) ->
if K =:= 1 -> false;
true ->
- K1 = K-1,
- lists:all(fun(E) -> is_limited(E, K1) end, Elements)
+ are_all_limited(Elements, K - 1)
end;
is_limited(?tuple_set(_) = T, K) ->
- lists:all(fun(Tuple) -> is_limited(Tuple, K) end, t_tuple_subtypes(T));
+ are_all_limited(t_tuple_subtypes(T), K);
+is_limited(?list(Elements, ?nil, _Size), K) ->
+ is_limited(Elements, K - 1);
is_limited(?list(Elements, Termination, _Size), K) ->
if K =:= 1 -> is_limited(Termination, K);
true -> is_limited(Termination, K - 1)
@@ -4079,12 +3833,11 @@ is_limited(?list(Elements, Termination, _Size), K) ->
is_limited(?function(Domain, Range), K) ->
is_limited(Domain, K) andalso is_limited(Range, K-1);
is_limited(?product(Elements), K) ->
- K1 = K-1,
- lists:all(fun(X) -> is_limited(X, K1) end, Elements);
+ are_all_limited(Elements, K - 1);
is_limited(?union(Elements), K) ->
- lists:all(fun(X) -> is_limited(X, K) end, Elements);
+ are_all_limited(Elements, K);
is_limited(?opaque(Es), K) ->
- lists:all(fun(#opaque{struct = S}) -> is_limited(S, K) end, set_to_list(Es));
+ lists:all(fun(#opaque{struct = S}) -> is_limited(S, K) end, Es);
is_limited(?map(Pairs, DefK, DefV), K) ->
%% Use the fact that t_sup() does not increase the depth.
K1 = K - 1,
@@ -4094,6 +3847,11 @@ is_limited(?map(Pairs, DefK, DefV), K) ->
andalso is_limited(DefK, K1) andalso is_limited(DefV, K1);
is_limited(_, _K) -> true.
+are_all_limited([E|Es], K) ->
+ is_limited(E, K) andalso are_all_limited(Es, K);
+are_all_limited([], _) ->
+ true.
+
t_limit_k(_, K) when K =< 0 -> ?any;
t_limit_k(?tuple(?any, ?any, ?any) = T, _K) -> T;
t_limit_k(?tuple(Elements, Arity, _), K) ->
@@ -4102,6 +3860,9 @@ t_limit_k(?tuple(Elements, Arity, _), K) ->
end;
t_limit_k(?tuple_set(_) = T, K) ->
t_sup([t_limit_k(Tuple, K) || Tuple <- t_tuple_subtypes(T)]);
+t_limit_k(?list(Elements, ?nil, Size), K) ->
+ NewElements = t_limit_k(Elements, K - 1),
+ ?list(NewElements, ?nil, Size);
t_limit_k(?list(Elements, Termination, Size), K) ->
NewTermination =
if K =:= 1 ->
@@ -4122,7 +3883,7 @@ t_limit_k(?opaque(Es), K) ->
List = [begin
NewS = t_limit_k(S, K),
Opaque#opaque{struct = NewS}
- end || #opaque{struct = S} = Opaque <- set_to_list(Es)],
+ end || #opaque{struct = S} = Opaque <- Es],
?opaque(ordsets:from_list(List));
t_limit_k(?map(Pairs0, DefK0, DefV0), K) ->
Fun = fun({EK, MNess, EV}, {Exact, DefK1, DefV1}) ->
@@ -4203,7 +3964,7 @@ t_to_string(?unit, _RecDict) ->
t_to_string(?atom(?any), _RecDict) ->
"atom()";
t_to_string(?atom(Set), _RecDict) ->
- case set_size(Set) of
+ case length(Set) of
2 ->
case set_is_element(true, Set) andalso set_is_element(false, Set) of
true -> "boolean()";
@@ -4239,16 +4000,13 @@ t_to_string(?identifier(Set), _RecDict) ->
case Set of
?any -> "identifier()";
_ ->
- flat_join([flat_format("~w()", [T]) || T <- set_to_list(Set)], " | ")
+ flat_join([flat_format("~w()", [T]) || T <- Set], " | ")
end;
t_to_string(?opaque(Set), RecDict) ->
flat_join([opaque_type(Mod, Name, Arity, S, RecDict) ||
#opaque{mod = Mod, name = Name, struct = S, arity = Arity}
- <- set_to_list(Set)],
+ <- Set],
" | ");
-t_to_string(?matchstate(Pres, Slots), RecDict) ->
- flat_format("ms(~ts,~ts)", [t_to_string(Pres, RecDict),
- t_to_string(Slots,RecDict)]);
t_to_string(?nil, _RecDict) ->
"[]";
t_to_string(?nonempty_list(Contents, Termination), RecDict) ->
@@ -4449,12 +4207,12 @@ mod_name(Mod, Name) ->
-type cache_key() :: {module(), atom(), expand_depth(),
[erl_type()], type_names()}.
-type mod_type_table() :: ets:tid().
--type mod_records() :: dict:dict(module(), type_table()).
+-type mod_records() :: #{module() => type_table()}.
-type exported_type_table() :: ets:tid().
-record(cache,
{
- types = maps:new() :: #{cache_key() => {erl_type(), expand_limit()}},
- mod_recs = {mrecs, dict:new()} :: {'mrecs', mod_records()}
+ types = #{} :: #{cache_key() => {erl_type(), expand_limit()}},
+ mod_recs = #{} :: mod_records()
}).
-opaque cache() :: #cache{}.
@@ -4471,11 +4229,11 @@ t_from_form(Form, ExpTypes, Site, RecDict, VarTab, Cache) ->
t_from_form_without_remote(Form, Site, TypeTable) ->
Module = site_module(Site),
- ModRecs = dict:from_list([{Module, TypeTable}]),
+ ModRecs = #{Module => TypeTable},
ExpTypes = replace_by_none,
VarTab = var_table__new(),
Cache0 = cache__new(),
- Cache = Cache0#cache{mod_recs = {mrecs, ModRecs}},
+ Cache = Cache0#cache{mod_recs = ModRecs},
{Type, _} = t_from_form1(Form, ExpTypes, Site, undefined, VarTab, Cache),
Type.
@@ -4574,15 +4332,18 @@ from_form_loop(Form, State, D, Limit, C, T0) ->
%%
%% It is assumed that site_module(S) can be found in MR.
+from_form(_, _S, D, L, _C) when not is_integer(D); not is_integer(L) ->
+ error(badarg);
from_form(_, _S, D, L, C) when D =< 0 ; L =< 0 ->
{t_any(), L, C};
from_form({var, _Anno, '_'}, _S, _D, L, C) ->
{t_any(), L, C};
from_form({var, _Anno, Name}, S, _D, L, C) ->
- V = S#from_form.vtab,
- case maps:find(Name, V) of
- error -> {t_var(Name), L, C};
- {ok, Val} -> {Val, L, C}
+ case S#from_form.vtab of
+ #{Name := Val} ->
+ {Val, L, C};
+ #{} ->
+ {t_var(Name), L, C}
end;
from_form({ann_type, _Anno, [_Var, Type]}, S, D, L, C) ->
from_form(Type, S, D, L, C);
@@ -4633,6 +4394,8 @@ from_form({type, _Anno, byte, []}, _S, _D, L, C) ->
{t_byte(), L, C};
from_form({type, _Anno, char, []}, _S, _D, L, C) ->
{t_char(), L, C};
+from_form({type, _Anno, dynamic, []}, _S, _D, L, C) ->
+ {t_any(), L, C};
from_form({type, _Anno, float, []}, _S, _D, L, C) ->
{t_float(), L, C};
from_form({type, _Anno, function, []}, _S, _D, L, C) ->
@@ -4880,7 +4643,7 @@ remote_from_form(Anno, RemMod, Name, Args, S, D, L, C) ->
end.
ext_types_message(MFA, Anno, Site) ->
- {MFA, {site_file(Site), erl_anno:location(Anno)}}.
+ {MFA, {site_file(Site), erl_anno:location(Anno), site_mfa(Site)}}.
remote_from_form1(RemMod, Name, Args, ArgsLen, RemDict, RemType, TypeNames,
Site, S, D, L, C) ->
@@ -5211,6 +4974,9 @@ list_check_record_fields([H|Tail], S, C) ->
site_module({_, {Module, _, _}, _}) ->
Module.
+site_mfa({_, {M, F, A}, _}) ->
+ {M, F, A}.
+
site_file({_, _, File}) ->
File.
@@ -5235,11 +5001,9 @@ cache_key(Module, Name, ArgTypes, TypeNames, D) ->
{erl_type(), expand_limit()} | 'error'.
cache_find(Key, #cache{types = Types}) ->
- case maps:find(Key, Types) of
- {ok, Value} ->
- Value;
- error ->
- error
+ case Types of
+ #{Key := Value} -> Value;
+ #{} -> error
end.
-spec cache_put(cache_key(), erl_type(), expand_limit(), cache()) -> cache().
@@ -5248,7 +5012,7 @@ cache_put(_Key, _Type, DeltaL, Cache) when DeltaL < 0 ->
%% The type is truncated; do not reuse it.
Cache;
cache_put(Key, Type, DeltaL, #cache{types = Types} = Cache) ->
- NewTypes = maps:put(Key, {Type, DeltaL}, Types),
+ NewTypes = Types#{Key => {Type, DeltaL}},
Cache#cache{types = NewTypes}.
-spec t_var_names([parse_form()]) -> [atom()].
@@ -5300,6 +5064,7 @@ t_form_to_string({type, _Anno, binary, [Base, Unit]} = Type) ->
_ -> io_lib:format("Badly formed bitstr type ~w", [Type])
end;
t_form_to_string({type, _Anno, bitstring, []}) -> "bitstring()";
+t_form_to_string({type, _Anno, dynamic, []}) -> "dynamic()";
t_form_to_string({type, _Anno, 'fun', []}) -> "fun()";
t_form_to_string({type, _Anno, 'fun', [{type, _, any}, Range]}) ->
"fun(...) -> " ++ t_form_to_string(Range);
@@ -5423,17 +5188,17 @@ is_erl_type(_) -> false.
'error' | {type_table(), cache()}.
lookup_module_types(Module, CodeTable, Cache) ->
- #cache{mod_recs = {mrecs, MRecs}} = Cache,
- case dict:find(Module, MRecs) of
- {ok, R} ->
+ #cache{mod_recs = MRecs} = Cache,
+ case MRecs of
+ #{Module := R} ->
{R, Cache};
- error ->
- try ets:lookup_element(CodeTable, Module, 2) of
+ #{} ->
+ case ets:lookup_element(CodeTable, Module, 2, error) of
+ error ->
+ error;
R ->
- NewMRecs = dict:store(Module, R, MRecs),
- {R, Cache#cache{mod_recs = {mrecs, NewMRecs}}}
- catch
- _:_ -> error
+ NewMRecs = MRecs#{Module => R},
+ {R, Cache#cache{mod_recs = NewMRecs}}
end
end.
@@ -5441,14 +5206,15 @@ lookup_module_types(Module, CodeTable, Cache) ->
'error' | {'ok', [{atom(), parse_form(), erl_type()}]}.
lookup_record(Tag, Table) when is_atom(Tag) ->
- case maps:find({record, Tag}, Table) of
- {ok, {_FileLocation, [{_Arity, Fields}]}} ->
+ Key = {record, Tag},
+ case Table of
+ #{Key := {_FileLocation, [{_Arity, Fields}]}} ->
{ok, Fields};
- {ok, {_FileLocation, List}} when is_list(List) ->
+ #{Key := {_FileLocation, List}} when is_list(List) ->
%% This will have to do, since we do not know which record we
%% are looking for.
error;
- error ->
+ #{} ->
error
end.
@@ -5456,21 +5222,25 @@ lookup_record(Tag, Table) when is_atom(Tag) ->
'error' | {'ok', [{atom(), parse_form(), erl_type()}]}.
lookup_record(Tag, Arity, Table) when is_atom(Tag) ->
- case maps:find({record, Tag}, Table) of
- {ok, {_FileLocation, [{Arity, Fields}]}} -> {ok, Fields};
- {ok, {_FileLocation, OrdDict}} -> orddict:find(Arity, OrdDict);
- error -> error
+ Key = {record, Tag},
+ case Table of
+ #{Key := {_FileLocation, [{Arity, Fields}]}} ->
+ {ok, Fields};
+ #{Key := {_FileLocation, OrdDict}} ->
+ orddict:find(Arity, OrdDict);
+ #{} ->
+ error
end.
-spec lookup_type(_, _, _) -> {'type' | 'opaque', type_value()} | 'error'.
lookup_type(Name, Arity, Table) ->
- case maps:find({type, Name, Arity}, Table) of
- error ->
- case maps:find({opaque, Name, Arity}, Table) of
- error -> error;
- {ok, Found} -> {opaque, Found}
- end;
- {ok, Found} -> {type, Found}
+ case Table of
+ #{{type, Name, Arity} := Found} ->
+ {type, Found};
+ #{{opaque, Name, Arity} := Found} ->
+ {opaque, Found};
+ #{} ->
+ error
end.
-spec type_is_defined('type' | 'opaque', atom(), arity(), type_table()) ->
@@ -5499,13 +5269,13 @@ do_opaque(?opaque(_) = Type, Opaques, Pred) ->
false -> Pred(Type)
end;
do_opaque(?union(List) = Type, Opaques, Pred) ->
- [A,B,F,I,L,N,T,M,O,Map] = List,
+ ?untagged_union(A,B,F,I,L,N,T,O,Map) = List,
if O =:= ?none -> Pred(Type);
true ->
case Opaques =:= 'universe' orelse is_opaque_type(O, Opaques) of
true ->
S = t_opaque_structure(O),
- do_opaque(t_sup([A,B,F,I,L,N,T,M,S,Map]), Opaques, Pred);
+ do_opaque(t_sup(?untagged_union(A,B,F,I,L,N,T,S,Map)), Opaques, Pred);
false -> Pred(Type)
end
end;
@@ -5536,50 +5306,23 @@ t_is_singleton(Type, Opaques) ->
%% Used to also recognize maps and tuples.
is_singleton_type(?nil) -> true;
is_singleton_type(?atom(?any)) -> false;
-is_singleton_type(?atom(Set)) ->
- ordsets:size(Set) =:= 1;
+is_singleton_type(?atom([_])) -> true;
is_singleton_type(?int_range(V, V)) -> true; % cannot happen
-is_singleton_type(?int_set(Set)) ->
- ordsets:size(Set) =:= 1;
+is_singleton_type(?int_set([_])) -> true;
is_singleton_type(_) ->
false.
-%% Returns the only possible value of a singleton type.
--spec t_singleton_to_term(erl_type(), opaques()) -> term().
-
-t_singleton_to_term(Type, Opaques) ->
- do_opaque(Type, Opaques, fun singleton_type_to_term/1).
-
-singleton_type_to_term(?nil) -> [];
-singleton_type_to_term(?atom(Set)) when Set =/= ?any ->
- case ordsets:size(Set) of
- 1 -> hd(ordsets:to_list(Set));
- _ -> error(badarg)
- end;
-singleton_type_to_term(?int_range(V, V)) -> V;
-singleton_type_to_term(?int_set(Set)) ->
- case ordsets:size(Set) of
- 1 -> hd(ordsets:to_list(Set));
- _ -> error(badarg)
- end;
-singleton_type_to_term(?tuple(Types, Arity, _)) when is_integer(Arity) ->
- lists:map(fun singleton_type_to_term/1, Types);
-singleton_type_to_term(?tuple_set([{Arity, [OnlyTuple]}]))
- when is_integer(Arity) ->
- singleton_type_to_term(OnlyTuple);
-singleton_type_to_term(?map(Pairs, ?none, ?none)) ->
- maps:from_list([{singleton_type_to_term(K), singleton_type_to_term(V)}
- || {K,?mand,V} <- Pairs]).
-
%% -----------------------------------
%% Set
%%
set_singleton(Element) ->
- ordsets:from_list([Element]).
+ [Element].
-set_is_singleton(Element, Set) ->
- set_singleton(Element) =:= Set.
+set_is_singleton(Element, [Element]) ->
+ true;
+set_is_singleton(_, _) ->
+ false.
set_is_element(Element, Set) ->
ordsets:is_element(Element, Set).
@@ -5618,29 +5361,23 @@ set_from_list(List) ->
L when L > ?SET_LIMIT -> ?any
end.
-set_to_list(Set) ->
- ordsets:to_list(Set).
-
-set_filter(Fun, Set) ->
- case ordsets:filter(Fun, Set) of
+set_filter(Pred, Set) ->
+ case [E || E <- Set, Pred(E)] of
[] -> ?none;
NewSet -> NewSet
end.
-set_size(Set) ->
- ordsets:size(Set).
-
set_to_string(Set) ->
L = [case is_atom(X) of
true -> io_lib:write_string(atom_to_list(X), $'); % stupid emacs '
false -> flat_format("~tw", [X])
- end || X <- set_to_list(Set)],
+ end || X <- Set],
flat_join(L, " | ").
set_min([H|_]) -> H.
set_max(Set) ->
- hd(lists:reverse(Set)).
+ lists:last(Set).
flat_format(F, S) ->
lists:flatten(io_lib:format(F, S)).
diff --git a/lib/dialyzer/src/typer.erl b/lib/dialyzer/src/typer.erl
index 9fa2cd78a2..6467ae0093 100644
--- a/lib/dialyzer/src/typer.erl
+++ b/lib/dialyzer/src/typer.erl
@@ -93,21 +93,33 @@ cl(["-I",Dir|Opts]) -> {{inc, Dir}, Opts};
cl(["-I"|_Opts]) -> fatal_error("no include directory specified after -I");
cl(["-I"++Dir|Opts]) -> {{inc, Dir}, Opts};
cl(["-T"|Opts]) ->
- {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts),
+ {Files, RestOpts} = collect_args(Opts),
case Files of
[] -> fatal_error("no file or directory specified after -T");
[_|_] -> {{trusted, Files}, RestOpts}
end;
cl(["-r"|Opts]) ->
- {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts),
+ {Files, RestOpts} = collect_args(Opts),
{{files_r, Files}, RestOpts};
cl(["-pa",Dir|Opts]) -> {{pa,Dir}, Opts};
cl(["-pz",Dir|Opts]) -> {{pz,Dir}, Opts};
cl(["-"++H|_]) -> fatal_error("unknown option -"++H);
cl(Opts) ->
- {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts),
+ {Files, RestOpts} = collect_args(Opts),
{{files, Files}, RestOpts}.
+-spec collect_args([string()]) -> {[string()], [string()]}.
+
+collect_args(List) ->
+ collect_args_1(List, []).
+
+collect_args_1(["-"++_|_] = L, Acc) ->
+ {lists:reverse(Acc), L};
+collect_args_1([Arg|T], Acc) ->
+ collect_args_1(T, [Arg|Acc]);
+collect_args_1([], Acc) ->
+ {lists:reverse(Acc), []}.
+
process_def_list(L) ->
case L of
[Name, Value] ->
@@ -215,6 +227,7 @@ help_message() ->
Prints type information as Edoc @spec comments, not as type specs
--plt PLT
Use the specified dialyzer PLT file rather than the default one
+ (Incremental and non-incremental PLT files are supported)
-T file*
The specified file(s) already contain type specifications and these
are to be trusted in order to print specs for the rest of the files
diff --git a/lib/dialyzer/src/typer_core.erl b/lib/dialyzer/src/typer_core.erl
index c9051f825e..1556d0b09b 100644
--- a/lib/dialyzer/src/typer_core.erl
+++ b/lib/dialyzer/src/typer_core.erl
@@ -438,7 +438,7 @@ get_type({{M, F, A} = MFA, Range, Arg}, CodeServer, Records, Analysis) ->
{{F, A}, {contract, Contract}};
{error, {overlapping_contract, []}} ->
{{F, A}, {contract, Contract}};
- {error, invalid_contract} ->
+ {error, {invalid_contract, _}} ->
CString = dialyzer_contracts:contract_to_string(Contract),
SigString = dialyzer_utils:format_sig(Sig, Records),
Msg = io_lib:format("Error in contract of function ~w:~tw/~w\n"
@@ -960,13 +960,32 @@ get_file([]) -> "no_file". % should not happen
-spec get_dialyzer_plt(analysis()) -> plt().
-get_dialyzer_plt(#analysis{plt = PltFile0}) ->
+get_dialyzer_plt(#analysis{plt = PltFile0}=Analysis) ->
PltFile =
case PltFile0 =:= none of
- true -> dialyzer_plt:get_default_plt();
+ true ->
+ case filelib:is_regular(dialyzer_cplt:get_default_cplt_filename()) of
+ true -> dialyzer_cplt:get_default_cplt_filename();
+ false ->
+ case filelib:is_regular(dialyzer_iplt:get_default_iplt_filename()) of
+ true -> dialyzer_iplt:get_default_iplt_filename();
+ false ->
+ fatal_error(
+ "No PLT file given, and no existing PLT was found at default locations " ++
+ dialyzer_cplt:get_default_cplt_filename() ++
+ " and " ++
+ dialyzer_iplt:get_default_iplt_filename(),
+ Analysis)
+ end
+ end;
false -> PltFile0
end,
- dialyzer_plt:from_file(PltFile).
+ case dialyzer_plt:plt_kind(PltFile) of
+ cplt -> dialyzer_cplt:from_file(PltFile);
+ iplt -> dialyzer_iplt:from_file(PltFile);
+ bad_file -> fatal_error("Invalid PLT file at path " ++ PltFile, Analysis);
+ no_file -> fatal_error("No PLT file found at path " ++ PltFile, Analysis)
+ end.
%% Exported Types
diff --git a/lib/dialyzer/test/Makefile b/lib/dialyzer/test/Makefile
index 7308c20400..4c79fe0581 100644
--- a/lib/dialyzer/test/Makefile
+++ b/lib/dialyzer/test/Makefile
@@ -12,9 +12,12 @@ AUXILIARY_FILES=\
dialyzer_common.erl\
file_utils.erl\
dialyzer_SUITE.erl\
+ dialyzer_utils_SUITE.erl\
dialyzer_cl_SUITE.erl\
abstract_SUITE.erl\
- plt_SUITE.erl\
+ iplt_SUITE.erl\
+ cplt_SUITE.erl\
+ incremental_SUITE.erl\
typer_SUITE.erl\
erl_types_SUITE.erl
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs b/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs
index d1bfc7295c..77ebf486c9 100644
--- a/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs
+++ b/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs
@@ -1,5 +1,6 @@
-my_callbacks_wrong.erl:26:2: The return type #state{parent::pid(),status::'closed' | 'init' | 'open',subscribe::[{pid(),integer()}],counter::integer()} in the specification of callback_init/1 is not a subtype of {'ok',_}, which is the expected return type for the callback of the my_behaviour behaviour
+my_callbacks_wrong.erl:26:2: The return type #state{parent::pid(),status::'closed' | 'init' | 'open',subscribe::[{pid(),integer()}],counter::integer()} in the specification of callback_init/1 has nothing in common with {'ok',_}, which is the expected return type for the callback of the my_behaviour behaviour
my_callbacks_wrong.erl:28:1: The inferred return type of callback_init/1 (#state{parent::pid(),status::'init',subscribe::[],counter::1}) has nothing in common with {'ok',_}, which is the expected return type for the callback of the my_behaviour behaviour
-my_callbacks_wrong.erl:30:2: The return type {'reply',#state{parent::pid(),status::'closed' | 'init' | 'open',subscribe::[{pid(),integer()}],counter::integer()}} in the specification of callback_cast/3 is not a subtype of {'noreply',_}, which is the expected return type for the callback of the my_behaviour behaviour
-my_callbacks_wrong.erl:39:2: The specified type for the 2nd argument of callback_call/3 (atom()) is not a supertype of pid(), which is expected type for this argument in the callback of the my_behaviour behaviour
+my_callbacks_wrong.erl:30:2: The return type {'reply',#state{parent::pid(),status::'closed' | 'init' | 'open',subscribe::[{pid(),integer()}],counter::integer()}} in the specification of callback_cast/3 has nothing in common with {'noreply',_}, which is the expected return type for the callback of the my_behaviour behaviour
+my_callbacks_wrong.erl:33:1: The inferred return type of callback_cast/3 ({'reply',_}) has nothing in common with {'noreply',_}, which is the expected return type for the callback of the my_behaviour behaviour
+my_callbacks_wrong.erl:39:2: The specified type for the 2nd argument of callback_call/3 (atom()) has nothing in common with pid(), which is expected type for this argument in the callback of the my_behaviour behaviour
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args
index a1412f29e6..6a9aacc77e 100644
--- a/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args
+++ b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args
@@ -2,4 +2,4 @@
gen_server_incorrect_args.erl:3:2: Undefined callback function handle_cast/2 (behaviour gen_server)
gen_server_incorrect_args.erl:3:2: Undefined callback function init/1 (behaviour gen_server)
gen_server_incorrect_args.erl:7:1: The inferred return type of handle_call/3 ({'no'} | {'ok'}) has nothing in common with {'noreply',_} | {'noreply',_,'hibernate' | 'infinity' | non_neg_integer() | {'continue',_}} | {'reply',_,_} | {'stop',_,_} | {'reply',_,_,'hibernate' | 'infinity' | non_neg_integer() | {'continue',_}} | {'stop',_,_,_}, which is the expected return type for the callback of the gen_server behaviour
-gen_server_incorrect_args.erl:7:1: The inferred type for the 2nd argument of handle_call/3 ('boo' | 'foo') is not a supertype of {pid(),gen_server:reply_tag()}, which is expected type for this argument in the callback of the gen_server behaviour
+gen_server_incorrect_args.erl:7:1: The inferred type for the 2nd argument of handle_call/3 ('boo' | 'foo') has nothing in common with {pid(),gen_server:reply_tag()}, which is expected type for this argument in the callback of the gen_server behaviour
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/otp_6221 b/lib/dialyzer/test/behaviour_SUITE_data/results/otp_6221
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/dialyzer/test/behaviour_SUITE_data/results/otp_6221
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour
index ab69a698c5..347500277b 100644
--- a/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour
+++ b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour
@@ -3,7 +3,7 @@ sample_callback_wrong.erl:16:1: The inferred return type of sample_callback_2/0
sample_callback_wrong.erl:17:1: The inferred return type of sample_callback_3/0 ('fair') has nothing in common with 'fail' | {'ok',1..255}, which is the expected return type for the callback of the sample_behaviour behaviour
sample_callback_wrong.erl:18:1: The inferred return type of sample_callback_4/1 ('fail') has nothing in common with 'ok', which is the expected return type for the callback of the sample_behaviour behaviour
sample_callback_wrong.erl:20:1: The inferred return type of sample_callback_5/1 (string()) has nothing in common with 'fail' | 'ok', which is the expected return type for the callback of the sample_behaviour behaviour
-sample_callback_wrong.erl:20:1: The inferred type for the 1st argument of sample_callback_5/1 (atom()) is not a supertype of 1..255, which is expected type for this argument in the callback of the sample_behaviour behaviour
+sample_callback_wrong.erl:20:1: The inferred type for the 1st argument of sample_callback_5/1 (atom()) has nothing in common with 1..255, which is expected type for this argument in the callback of the sample_behaviour behaviour
sample_callback_wrong.erl:22:1: The inferred return type of sample_callback_6/3 ({'okk',number()}) has nothing in common with 'fail' | {'ok',1..255}, which is the expected return type for the callback of the sample_behaviour behaviour
-sample_callback_wrong.erl:22:1: The inferred type for the 3rd argument of sample_callback_6/3 (atom()) is not a supertype of string(), which is expected type for this argument in the callback of the sample_behaviour behaviour
+sample_callback_wrong.erl:22:1: The inferred type for the 3rd argument of sample_callback_6/3 (atom()) has nothing in common with string(), which is expected type for this argument in the callback of the sample_behaviour behaviour
sample_callback_wrong.erl:4:2: Undefined callback function sample_callback_1/0 (behaviour sample_behaviour)
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour_old b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour_old
index 6d8145a8ca..5f2b8d2ad4 100644
--- a/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour_old
+++ b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour_old
@@ -1,4 +1,4 @@
-incorrect_args_callback.erl:12:1: The inferred type for the 2nd argument of bar/2 ('yes') is not a supertype of [any()], which is expected type for this argument in the callback of the correct_behaviour behaviour
+incorrect_args_callback.erl:12:1: The inferred type for the 2nd argument of bar/2 ('yes') has nothing in common with [any()], which is expected type for this argument in the callback of the correct_behaviour behaviour
incorrect_return_callback.erl:9:1: The inferred return type of foo/0 ('error') has nothing in common with 'no' | 'yes', which is the expected return type for the callback of the correct_behaviour behaviour
missing_callback.erl:5:2: Undefined callback function foo/0 (behaviour correct_behaviour)
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl
index 0459622dc1..0f485096ff 100644
--- a/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl
+++ b/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl
@@ -28,13 +28,13 @@
callback_init(Parent) -> #state{parent = Parent}. %% Wrong return
-spec callback_cast(state(), pid() | atom(), cast_message()) ->
- {'noreply' | 'reply', state()}. %% More generic spec
+ {'reply', state()}. %% Non-overlapping spec
callback_cast(#state{parent = Pid} = State, Pid, Message)
when Message =:= 'open'; Message =:= 'close' ->
- {noreply, State#state{status = Message}};
+ {reply, State#state{status = Message}};
callback_cast(State, _Pid, _Message) ->
- {noreply, State}.
+ {reply, State}.
-spec callback_call(state(), atom(), call_message()) -> %% Wrong arg spec
{'reply', state(), call_reply()}.
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/otp_6221/my_behaviour.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/otp_6221/my_behaviour.erl
new file mode 100644
index 0000000000..347ea403c2
--- /dev/null
+++ b/lib/dialyzer/test/behaviour_SUITE_data/src/otp_6221/my_behaviour.erl
@@ -0,0 +1,3 @@
+-module(my_behaviour).
+
+-callback foo() -> #{ {{{f,f}, f}, f} => x }.
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/otp_6221/my_callbacks_correct.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/otp_6221/my_callbacks_correct.erl
new file mode 100644
index 0000000000..4a08017c84
--- /dev/null
+++ b/lib/dialyzer/test/behaviour_SUITE_data/src/otp_6221/my_callbacks_correct.erl
@@ -0,0 +1,17 @@
+-module(my_callbacks_correct).
+
+-behaviour(my_behaviour).
+
+-export([foo/0]).
+
+-type pair(A,B) :: {A,B}.
+
+-type nested() :: pair(pair(pair(f,f),f),f).
+
+%% This is correctly implemented, but a combination of Dialyzer
+%% "simplification" logic and subtyping rules for behaviours means
+%% this implementation has historically been erroneously rejected
+-spec foo() -> #{ nested() => x }.
+foo() ->
+ Ret = #{ {{{f,f}, f}, f} => x },
+ Ret.
diff --git a/lib/dialyzer/test/plt_SUITE.erl b/lib/dialyzer/test/cplt_SUITE.erl
index e1c5ecdd95..27e50b54fc 100644
--- a/lib/dialyzer/test/plt_SUITE.erl
+++ b/lib/dialyzer/test/cplt_SUITE.erl
@@ -1,7 +1,4 @@
-%% This suite is the only hand made and simply
-%% checks if we can build and update a plt.
-
--module(plt_SUITE).
+-module(cplt_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("stdlib/include/assert.hrl").
@@ -39,8 +36,12 @@
mod_dep_from_exported_fun_spec_args/1,
mod_dep_from_unexported_fun_spec_return/1,
mod_dep_from_exported_fun_spec_return/1,
- mod_dep_from_unexported_type/1
- ]).
+ mod_dep_from_unexported_type/1,
+ adding_warning_apps_after_a_run_without_them_causes_any_new_warnings_to_be_reported/1,
+ removing_warning_apps_after_a_run_with_them_causes_any_warnings_for_the_removed_apps_not_to_be_reported/1,
+ removing_legal_warnings_with_existing_stored_warnings_in_plt_does_not_result_in_old_warnings_being_printed/1,
+ adding_legal_warnings_with_existing_stored_warnings_in_plt_results_in_new_warnings_being_printed/1
+ ]).
suite() ->
[{timetrap, ?plt_timeout}].
@@ -73,7 +74,11 @@ all() -> [build_plt, build_xdg_plt, beam_tests, update_plt, run_plt_check,
mod_dep_from_exported_fun_spec_args,
mod_dep_from_unexported_fun_spec_return,
mod_dep_from_exported_fun_spec_return,
- mod_dep_from_unexported_type
+ mod_dep_from_unexported_type,
+ adding_warning_apps_after_a_run_without_them_causes_any_new_warnings_to_be_reported,
+ removing_warning_apps_after_a_run_with_them_causes_any_warnings_for_the_removed_apps_not_to_be_reported,
+ removing_legal_warnings_with_existing_stored_warnings_in_plt_does_not_result_in_old_warnings_being_printed,
+ adding_legal_warnings_with_existing_stored_warnings_in_plt_results_in_new_warnings_being_printed
].
build_plt(Config) ->
@@ -103,7 +108,8 @@ build_xdg_plt(Config) ->
fun() ->
?assertMatch([], dialyzer:run(
[{analysis_type, plt_build},
- {apps, [erts]}])),
+ {apps, [erts]},
+ {warnings, [no_unknown]}])),
?assertMatch(
{ok,_}, file:read_file(
filename:join(
@@ -238,7 +244,8 @@ update_plt(Config) ->
Opts = [{check_plt, true}, {from, byte_code}],
[] = dialyzer:run([{analysis_type, plt_build},
{files, [Beam, ErlangBeam]},
- {output_plt, Plt}] ++ Opts),
+ {output_plt, Plt},
+ {warnings, [no_unknown]}] ++ Opts),
Prog2 = <<"-module(plt_gc).
-export([two/0]).
@@ -252,7 +259,7 @@ update_plt(Config) ->
test() ->
plt_gc:one().">>,
{ok, TestBeam} = compile(Config, Test, test, []),
- [{warn_callgraph, _, {call_to_missing, [plt_gc, one, 0]}}] =
+ [{warn_callgraph, {_Filename, {5,19}}, {call_to_missing, [plt_gc,one,0]}}] =
dialyzer:run([{analysis_type, succ_typings},
{files, [TestBeam]},
{init_plt, Plt}] ++ Opts),
@@ -291,7 +298,8 @@ local_fun_same_as_callback(Config) when is_list(Config) ->
Opts = [{check_plt, true}, {from, byte_code}],
[] = dialyzer:run([{analysis_type, plt_build},
{files, [Beam, ErlangBeam]},
- {output_plt, Plt}] ++ Opts),
+ {output_plt, Plt},
+ {warnings, [no_unknown]}] ++ Opts),
Prog2 =
<<"-module(bad_child).
@@ -337,15 +345,18 @@ remove_plt(Config) ->
dialyzer:run([{analysis_type, plt_build},
{files, [Beam1, Beam2]},
{get_warnings, true},
- {output_plt, Plt}] ++ Opts),
+ {output_plt, Plt},
+ {warnings, [no_unknown]}] ++ Opts),
[] = dialyzer:run([{init_plt, Plt},
{files, [Beam2]},
- {analysis_type, plt_remove}]),
+ {analysis_type, plt_remove},
+ {warnings, [no_unknown]}]),
[] = dialyzer:run([{analysis_type, succ_typings},
{files, [Beam1]},
- {init_plt, Plt}] ++ Opts),
+ {init_plt, Plt},
+ {warnings, [no_unknown]}] ++ Opts),
ok.
%% ERL-283, OTP-13979. As of OTP-14323 this test no longer does what
@@ -550,7 +561,7 @@ missing_plt_file_2(Config, PltFile, BeamFile2) ->
end.
missing_plt_file_3() ->
- try dialyzer_plt:from_file("no_such_file"), false
+ try dialyzer_cplt:from_file("no_such_file"), false
catch throw:{dialyzer_error, _} -> true
end.
@@ -562,7 +573,8 @@ create(Config, PltFile) ->
_ = file:delete(PltFile),
[] = dialyzer:run([{files,Files},
{output_plt, PltFile},
- {analysis_type, plt_build}]),
+ {analysis_type, plt_build},
+ {warnings, [no_unknown]}]),
BeamFile.
succ(PltFile, BeamFile2) ->
@@ -872,7 +884,7 @@ check_plt_deps(Config, TestName, DependerSrc, ExpectedTypeDepsInPltUnsorted) ->
{ok, DepsBeamFile} = compile(Config, type_deps, []),
{ok, DependerBeamFile} = compile(Config, DependerSrc, depender, []),
[] = run_dialyzer(plt_build, [DependerBeamFile, DepsBeamFile], [{output_plt, PltFile}]),
- {_ResPlt, #plt_info{mod_deps = DepsByModule}} = dialyzer_plt:plt_and_info_from_file(PltFile),
+ {_ResPlt, #plt_info{mod_deps = DepsByModule}} = dialyzer_cplt:plt_and_info_from_file(PltFile),
ActualTypeDepsInPlt =
lists:sort(dict:to_list(dict:erase(erlang, DepsByModule))),
@@ -895,7 +907,7 @@ erlang_beam() ->
EBeam
end.
-%% Builds the named module using the source in the plt_SUITE_data dir
+%% Builds the named module using the source in the cplt_SUITE_data dir
compile(Config, Module, CompileOpts) ->
Source = lists:concat([Module, ".erl"]),
PrivDir = proplists:get_value(priv_dir,Config),
@@ -917,6 +929,84 @@ compile(Config, Prog, Module, CompileOpts) ->
run_dialyzer(Analysis, Files, Opts) ->
dialyzer:run([{analysis_type, Analysis},
- {files, Files},
- {from, byte_code} |
- Opts]).
+ {files, Files},
+ {from, byte_code},
+ {warnings, [no_unknown]} |
+ Opts]).
+
+m_src_without_warning() -> <<"
+ -module(m).
+ -export([updt/3]).
+
+ updt(X, K, V) -> X#{ K => V }.
+ ">>.
+
+m_src_with_warning() -> <<"
+ -module(m).
+ -export([updt/3]).
+
+ -spec updt(list(), term(), term()) -> list(). % Warning: Spec is wrong! Function takes a map, not a list
+ updt(X, K, V) -> X#{ K => V }.
+ ">>.
+
+adding_warning_apps_after_a_run_without_them_causes_any_new_warnings_to_be_reported(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".plt"),
+ Opts = [{init_plt, [PltFile]}],
+ OptsNoContractWarn = [{init_plt, [PltFile]}, {warnings, [no_contracts]}],
+ {ok, Beam} = compile(Config, m_src_with_warning(), m, []),
+ [] = run_dialyzer(incremental, [Beam], OptsNoContractWarn),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [Beam], Opts)).
+
+removing_warning_apps_after_a_run_with_them_causes_any_warnings_for_the_removed_apps_not_to_be_reported(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".plt"),
+ Opts = [{init_plt, [PltFile]}],
+ OptsNoContractWarn = [{init_plt, [PltFile]}, {warnings, [no_contracts]}],
+ {ok, Beam} = compile(Config, m_src_with_warning(), m, []),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [Beam], Opts)),
+ ?assertEqual(
+ [],
+ run_dialyzer(incremental, [Beam], OptsNoContractWarn)).
+
+removing_legal_warnings_with_existing_stored_warnings_in_plt_does_not_result_in_old_warnings_being_printed(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".plt"),
+ Opts = [{init_plt, [PltFile]}],
+ {ok, BeamFileBefore} = compile(Config, m_src_with_warning(), m, []),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [BeamFileBefore], Opts)),
+ {ok, BeamFileAfter} = compile(Config, m_src_without_warning(), m, []),
+ ?assertEqual(
+ [],
+ run_dialyzer(incremental, [BeamFileAfter], Opts)).
+
+adding_legal_warnings_with_existing_stored_warnings_in_plt_results_in_new_warnings_being_printed(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".plt"),
+ Opts = [{init_plt, [PltFile]}],
+ {ok, BeamFileAfter} = compile(Config, m_src_without_warning(), m, []),
+ ?assertEqual(
+ [],
+ run_dialyzer(incremental, [BeamFileAfter], Opts)),
+ {ok, BeamFileBefore} = compile(Config, m_src_with_warning(), m, []),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [BeamFileBefore], Opts)).
diff --git a/lib/dialyzer/test/plt_SUITE_data/type_deps.erl b/lib/dialyzer/test/cplt_SUITE_data/type_deps.erl
index f6fcfc3d23..f6fcfc3d23 100644
--- a/lib/dialyzer/test/plt_SUITE_data/type_deps.erl
+++ b/lib/dialyzer/test/cplt_SUITE_data/type_deps.erl
diff --git a/lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/dialyzer_options b/lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/dialyzer_options
new file mode 100644
index 0000000000..8413436b67
--- /dev/null
+++ b/lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/dialyzer_options
@@ -0,0 +1 @@
+{dialyzer_options, [{indent_opt, false}]}.
diff --git a/lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/results/default_ignore_overlapping_contract b/lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/results/default_ignore_overlapping_contract
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/results/default_ignore_overlapping_contract
diff --git a/lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/src/default_ignore_overlapping_contract.erl b/lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/src/default_ignore_overlapping_contract.erl
new file mode 100644
index 0000000000..8303cf8ca6
--- /dev/null
+++ b/lib/dialyzer/test/default_ignore_overlapping_contract_SUITE_data/src/default_ignore_overlapping_contract.erl
@@ -0,0 +1,13 @@
+-module(default_ignore_overlapping_contract).
+
+-export([t1/0]).
+
+%% Should not result in a overlapping_contract warning,
+%% as dialyzer_options does not set it to active
+-spec t1() -> list();
+ () -> [atom].
+t1() ->
+ case rand:uniform(2) of
+ 1 -> [test];
+ 2 -> [2]
+ end.
diff --git a/lib/dialyzer/test/dialyzer_SUITE.erl b/lib/dialyzer/test/dialyzer_SUITE.erl
index 058fdc8a50..fe6987192a 100644
--- a/lib/dialyzer/test/dialyzer_SUITE.erl
+++ b/lib/dialyzer/test/dialyzer_SUITE.erl
@@ -20,6 +20,7 @@
-module(dialyzer_SUITE).
-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
%% Default timetrap timeout (set in init_per_testcase).
-define(default_timeout, test_server:minutes(10)).
@@ -31,12 +32,21 @@
-export([init_per_testcase/2, end_per_testcase/2]).
%% Test cases must be exported.
--export([app_test/1, appup_test/1, file_list/1]).
+-export([app_test/1, appup_test/1]).
+-export([cplt_info/1, iplt_info/1,
+ incremental_plt_given_to_classic_mode/1,
+ classic_plt_given_to_incremental_mode/1,
+ if_output_plt_is_missing_incremental_mode_makes_it/1,
+ file_list/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
- [app_test, appup_test, file_list].
+ [app_test, appup_test, cplt_info, iplt_info,
+ incremental_plt_given_to_classic_mode,
+ classic_plt_given_to_incremental_mode,
+ if_output_plt_is_missing_incremental_mode_makes_it,
+ file_list].
groups() ->
[].
@@ -62,6 +72,15 @@ end_per_testcase(_Case, Config) ->
test_server:timetrap_cancel(Dog),
ok.
+compile(Config, Prog, Module, CompileOpts) ->
+ Source = lists:concat([Module, ".erl"]),
+ PrivDir = proplists:get_value(priv_dir,Config),
+ Filename = filename:join([PrivDir, Source]),
+ ok = file:write_file(Filename, Prog),
+ Opts = [{outdir, PrivDir}, debug_info | CompileOpts],
+ {ok, Module} = compile:file(Filename, Opts),
+ {ok, filename:join([PrivDir, lists:concat([Module, ".beam"])])}.
+
%%%
%%% Test cases starts here.
%%%
@@ -77,6 +96,99 @@ app_test(Config) when is_list(Config) ->
appup_test(Config) when is_list(Config) ->
ok = test_server:appup_test(dialyzer).
+cplt_info(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 =
+ <<"-module(foo).
+
+ bar() -> ok.">>,
+ {ok, Beam1} = compile(Config, Prog1, foo, []),
+
+ Plt1 = filename:join(PrivDir, "cplt_info.plt"),
+ _ = dialyzer:run([{analysis_type, plt_build},
+ {files, [Beam1]},
+ {init_plt, Plt1},
+ {from, byte_code}]),
+
+ {ok, [{files, [Beam1]}]} = dialyzer:plt_info(Plt1).
+
+iplt_info(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 =
+ <<"-module(foo).
+
+ bar() -> ok.">>,
+ {ok, Beam1} = compile(Config, Prog1, foo, []),
+
+ Plt1 = filename:join(PrivDir, "iplt_info.plt"),
+ _ = dialyzer:run([{analysis_type, incremental},
+ {files, [Beam1]},
+ {init_plt, Plt1},
+ {from, byte_code}]),
+
+ {ok, {incremental, [{modules, [foo]}]}} = dialyzer:plt_info(Plt1).
+
+incremental_plt_given_to_classic_mode(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 =
+ <<"-module(foo).
+
+ bar() -> ok.">>,
+ {ok, Beam1} = compile(Config, Prog1, foo, []),
+
+ Plt1 = filename:join(PrivDir, "my_incremental.iplt"),
+ _ = dialyzer:run([{analysis_type, incremental},
+ {files, [Beam1]},
+ {init_plt, Plt1},
+ {output_plt, Plt1},
+ {from, byte_code}]),
+
+ ?assertException(throw, {dialyzer_error, "Given file is an incremental PLT file, but outside of incremental mode, a classic PLT file is expected: {init_plt_file," ++ _ },
+ dialyzer:run([{analysis_type, plt_check},
+ {files, [Beam1]},
+ {init_plt, Plt1},
+ {from, byte_code}])).
+
+classic_plt_given_to_incremental_mode(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 =
+ <<"-module(foo).
+
+ bar() -> ok.">>,
+ {ok, Beam1} = compile(Config, Prog1, foo, []),
+
+ Plt1 = filename:join(PrivDir, "my_classic.plt"),
+ _ = dialyzer:run([{analysis_type, plt_build},
+ {files, [Beam1]},
+ {init_plt, Plt1},
+ {output_plt, Plt1},
+ {from, byte_code}]),
+
+ ?assertException(throw, {dialyzer_error, "Given file is a classic PLT file, but in incremental mode, an incremental PLT file is expected: {init_plt_file," ++ _ },
+ dialyzer:run([{analysis_type, incremental},
+ {files, [Beam1]},
+ {init_plt, Plt1},
+ {from, byte_code}])).
+
+if_output_plt_is_missing_incremental_mode_makes_it(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 =
+ <<"-module(foo).
+
+ bar() -> ok.">>,
+ {ok, Beam1} = compile(Config, Prog1, foo, []),
+
+ Plt1 = filename:join(PrivDir, "brand_new.iplt"),
+ ?assertMatch(false, filelib:is_regular(Plt1)),
+
+ _ = dialyzer:run([{analysis_type, incremental},
+ {files, [Beam1]},
+ {init_plt, Plt1},
+ {output_plt, Plt1},
+ {from, byte_code}]),
+
+ ?assertMatch(true, filelib:is_regular(Plt1)).
+
file_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
@@ -129,3 +241,4 @@ expected(Files0) ->
":6:5: The variable _ can never match since previous clauses completely covered the type \n"
" atom()\n" || F <- Files],
iolist_to_binary(S).
+
diff --git a/lib/dialyzer/test/dialyzer_cl_SUITE.erl b/lib/dialyzer/test/dialyzer_cl_SUITE.erl
index ed84fc6068..b9d176197a 100644
--- a/lib/dialyzer/test/dialyzer_cl_SUITE.erl
+++ b/lib/dialyzer/test/dialyzer_cl_SUITE.erl
@@ -9,14 +9,16 @@
%% Test cases must be exported.
-export([
+ can_add_multiple_plts_to_another_plt/1,
unknown_function_warning_includes_callsite/1,
call_to_missing_warning_includes_callsite/1
]).
-suite() -> [{timetrap, {minutes, 1}}].
+suite() -> [{timetrap, {minutes, 3}}].
all() ->
[
+ can_add_multiple_plts_to_another_plt,
unknown_function_warning_includes_callsite,
call_to_missing_warning_includes_callsite
].
@@ -56,7 +58,8 @@ call_to_missing_warning_includes_callsite(Config) when is_list(Config) ->
{ok, BeamFileForPlt} = compile(Config, previously_defined, []),
[] = dialyzer:run([{analysis_type, plt_build},
{files, [BeamFileForPlt]},
- {output_plt, Plt}]),
+ {output_plt, Plt},
+ {warnings, [no_unknown]}]),
{ok, Beam} = compile(Config, call_to_missing_example, []),
Opts =
@@ -119,6 +122,30 @@ unknown_function_warning_includes_callsite(Config) when is_list(Config) ->
ok.
+% See GitHub issue erlang/OTP #6850
+can_add_multiple_plts_to_another_plt(Config) when is_list(Config) ->
+
+ PrivDir = proplists:get_value(priv_dir,Config),
+
+ StdlibPlt = filename:join(PrivDir, "stdlib.plt"),
+ ErtsPlt = filename:join(PrivDir, "erts.plt"),
+ OutputPlt = filename:join(PrivDir, "merged.plt"),
+
+ _ = dialyzer:run([{analysis_type, plt_build},
+ {apps, [stdlib]},
+ {output_plt, StdlibPlt}]),
+ _ = dialyzer:run([{analysis_type, plt_build},
+ {apps, [erts]},
+ {output_plt, ErtsPlt}]),
+ ?assertEqual(
+ [],
+ dialyzer:run([{analysis_type, plt_add},
+ {apps, [erts, stdlib]},
+ {plts, [ErtsPlt, StdlibPlt]},
+ {output_plt, OutputPlt}])),
+
+ ok.
+
compile(Config, Module, CompileOpts) ->
Source = lists:concat([Module, ".erl"]),
PrivDir = proplists:get_value(priv_dir,Config),
diff --git a/lib/dialyzer/test/dialyzer_common.erl b/lib/dialyzer/test/dialyzer_common.erl
index ca1fc1993a..735e22e020 100644
--- a/lib/dialyzer/test/dialyzer_common.erl
+++ b/lib/dialyzer/test/dialyzer_common.erl
@@ -89,7 +89,7 @@ explain_fail_with_lock() ->
obtain_plt(PltFilename) ->
io:format("Obtaining plt:"),
- InitPlt = dialyzer_plt:get_default_plt(),
+ InitPlt = dialyzer_cplt:get_default_cplt_filename(),
io:format("Will try to use ~s as a starting point and add otp apps ~w.",
[InitPlt, ?required_modules]),
try dialyzer:run([{analysis_type, plt_add},
@@ -107,9 +107,14 @@ obtain_plt(PltFilename) ->
build_plt(PltFilename) ->
io:format("Building plt from scratch:"),
+
+ %% build_plt/1 builds the plt using default warning options; -Wunknown is
+ %% enabled by default, so tests that do not satisfy -Wunknown will break.
+ %% for this reason, we must pass no_unknown in this analysis.
+ DefaultWarnings = {warnings, [no_unknown]},
try dialyzer:run([{analysis_type, plt_build},
{apps, ?required_modules},
- {output_plt, PltFilename}]) of
+ {output_plt, PltFilename}, DefaultWarnings]) of
[] ->
io:format("Successfully created plt!"),
ok
@@ -120,7 +125,7 @@ build_plt(PltFilename) ->
end.
-spec check(atom(), dialyzer:dial_options(), string(), string()) ->
- 'same' | {differ, [term()]}.
+ 'same' | {differ, TestCase :: atom(), [term()]}.
check(TestCase, Opts, Dir, OutDir) ->
PltFilename = plt_file(OutDir),
@@ -161,10 +166,10 @@ check(TestCase, Opts, Dir, OutDir) ->
case file_utils:diff(NewResFile, OldResFile) of
'same' -> file:delete(NewResFile),
'same';
- Any -> escape_strings(Any)
+ {'differ', List} -> escape_strings({'differ', TestCase, List})
end
catch
- Kind:Error -> {'dialyzer crashed', Kind, Error}
+ Kind:Error:Stacktrace -> {'dialyzer crashed', Kind, Error, Stacktrace}
end.
fix_options(Opts, Dir) ->
@@ -203,9 +208,9 @@ create_all_suites() ->
Suites = get_suites(Cwd),
lists:foreach(fun create_suite/1, Suites).
-escape_strings({differ,List}) ->
+escape_strings({differ, TestCase, List}) ->
Map = fun({T,L,S}) -> {T,L,xmerl_lib:export_text(S)} end,
- {differ, lists:keysort(3, lists:map(Map, List))}.
+ {differ, TestCase, lists:keysort(3, lists:map(Map, List))}.
-spec get_suites(file:filename()) -> [string()].
@@ -216,7 +221,8 @@ get_suites(Dir) ->
FullFilenames = [filename:join(Dir, F) || F <-Filenames ],
Dirs = [is_suite_data(filename:basename(F), ?suite_data) ||
F <- FullFilenames,
- file_utils:file_type(F) =:= {ok, 'directory'}],
+ file_utils:file_type(F) =:= {ok, 'directory'},
+ file_utils:file_type(filename:join(F, ?input_files_directory)) =:= {ok, 'directory'}],
[S || {yes, S} <- Dirs]
end.
diff --git a/lib/dialyzer/test/dialyzer_utils_SUITE.erl b/lib/dialyzer/test/dialyzer_utils_SUITE.erl
new file mode 100644
index 0000000000..312b3bd589
--- /dev/null
+++ b/lib/dialyzer/test/dialyzer_utils_SUITE.erl
@@ -0,0 +1,52 @@
+-module(dialyzer_utils_SUITE).
+
+-export([all/0,
+ p_map_implements_map/1,
+ p_map_handles_errors_like_map_does/1,
+ p_map_preserves_ordering/1
+ ]).
+
+all() ->
+ [p_map_implements_map,
+ p_map_handles_errors_like_map_does,
+ p_map_preserves_ordering
+ ].
+
+p_map_implements_map(_Config) ->
+ Fun = fun (N) -> N + 2 end,
+ List = [2,1,3,2],
+ Expected = lists:map(Fun, List),
+ Expected = dialyzer_utils:p_map(Fun, List).
+
+p_map_handles_errors_like_map_does(_Config) ->
+ Fun = fun (3) -> throw("an error"); (N) -> N + 2 end,
+ List = [2,1,3,2],
+ ListsOk =
+ try
+ lists:map(Fun, List),
+ false
+ catch _:_ ->
+ true
+ end,
+ case ListsOk of
+ true -> ok;
+ false -> ct:fail("Expected lists:map/2 to throw")
+ end,
+ UtilsOk =
+ try
+ dialyzer_utils:p_map(Fun, List),
+ false
+ catch _:_ ->
+ true
+ end,
+ case UtilsOk of
+ true -> ok;
+ false -> ct:fail("Expected dialyzer_utils:p_map/2 to throw")
+ end.
+
+p_map_preserves_ordering(_Config) ->
+ Fun = fun (N) -> timer:sleep(N * 50), N + 2 end,
+ List = [2,1,3,2,1,5,2,1,3,2,9,4,2,1,3,2,5,4],
+ Expected = lists:map(Fun, List),
+ Expected = dialyzer_utils:p_map(Fun, List).
+
diff --git a/lib/dialyzer/test/incremental_SUITE.erl b/lib/dialyzer/test/incremental_SUITE.erl
new file mode 100644
index 0000000000..1bf5731db8
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE.erl
@@ -0,0 +1,1134 @@
+%% This suite tests the incremental mode which for now woirks as follows:
+%% You can specify which files you want in your plt, incremental mode will
+%% do the analysis needed to get the PLT into a good state and report the
+%% warnings from the modules which it re-analyzed.
+
+-module(incremental_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
+-include("dialyzer_test_constants.hrl").
+
+-export([suite/0,
+ all/0,
+ report_new_plt_test/1,
+ report_degree_of_incrementality_test/1,
+ report_no_stored_warnings_test/1,
+ report_stored_warnings_no_files_changed_test/1,
+ report_stored_warnings_only_files_safely_removed_test/1,
+ report_old_plt_version_number_test/1,
+ report_old_plt_version_hash_test/1,
+ report_legal_warnings_added/1,
+ report_legal_warnings_removed/1,
+ incremental_test/1,
+ incremental_select_warnings_test/1,
+ add_and_remove_test/1,
+ add_and_remove_test_with_unknown_warnings/1,
+ verify_plt_info/1,
+ verify_plt_info_with_unknown_warnings/1,
+ fixing_all_warnings/1,
+ default_apps_config_xdg/1,
+ default_apps_config_env_var/1,
+ default_apps_config_env_var_prioritised_over_xdg/1,
+ legal_warnings_config_xdg/1,
+ paths_config_xdg/1,
+ multiple_plts_unsupported_in_incremental_mode/1]).
+
+suite() ->
+ [{timetrap, ?plt_timeout}].
+
+all() -> [report_new_plt_test,
+ report_degree_of_incrementality_test,
+ report_no_stored_warnings_test,
+ report_stored_warnings_no_files_changed_test,
+ report_stored_warnings_only_files_safely_removed_test,
+ report_old_plt_version_number_test,
+ report_old_plt_version_hash_test,
+ report_legal_warnings_added,
+ report_legal_warnings_removed,
+ incremental_test,
+ incremental_select_warnings_test,
+ add_and_remove_test,
+ add_and_remove_test_with_unknown_warnings,
+ verify_plt_info,
+ verify_plt_info_with_unknown_warnings,
+ fixing_all_warnings,
+ default_apps_config_xdg,
+ default_apps_config_env_var,
+ default_apps_config_env_var_prioritised_over_xdg,
+ legal_warnings_config_xdg,
+ paths_config_xdg,
+ multiple_plts_unsupported_in_incremental_mode].
+
+erlang_module() ->
+ case code:where_is_file("erlang.beam") of
+ non_existing ->
+ filename:join([code:root_dir(),
+ "erts", "preloaded", "ebin",
+ "erlang.beam"]);
+ EBeam ->
+ EBeam
+ end.
+
+verify_plt_info_with_unknown_warnings(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" ||
+ Module <- Modules] end,
+ Plt = filename:join(PrivDir, "verify_plt_info.iplt"),
+ compile_all(DataDir, PrivDir),
+ ErlangModule = erlang_module(),
+ Opts = [{init_plt, [Plt]}, {warnings, [unknown]}],
+ AllSubSets = all_subsets(all_mods()),
+ Run = fun(Mods) -> run_dialyzer(incremental, [ErlangModule | ToPath(Mods)], Opts) end,
+ [run_and_verify_plt_info_with_unknown_warnings(Subset, Run, Plt) ||
+ Subset <- AllSubSets].
+
+run_and_verify_plt_info_with_unknown_warnings(Subset, Run, PltFile) ->
+ _ = Run(Subset),
+ {Plt, {iplt_info, _Md5, _ModDeps, Warnings, _LegalWarnings}} =
+ dialyzer_iplt:plt_and_info_from_file(PltFile),
+ dialyzer_plt:delete(Plt),
+ verify_unknown_warnings(Subset, Warnings).
+
+verify_unknown_warnings(Subset, Warnings) ->
+ Unused = all_mods() -- Subset,
+ lists:foreach(fun(Mod) -> check_unknown_warnings(Mod, Unused, Warnings) end, Subset),
+ lists:foreach(fun(Mod) -> check_unused_mod_warnings(Mod, Warnings) end, Unused).
+
+check_unknown_warnings(Mod, Unused, Warnings) ->
+ Unknown = [unknown_function_warning(M) ||
+ M <- Unused, lists:member(Mod, depends_on(M))],
+ Other = [warning_for_module(Mod)],
+ Expected = lists:sort(Unknown ++ Other),
+ Stored = lists:sort(replace_location(maps:get(Mod, Warnings))),
+ Expected = Stored.
+
+unknown_function_warning(M) ->
+ {warn_unknown, loc, {unknown_function, {M, id, 1}}}.
+
+verify_plt_info(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" ||
+ Module <- Modules] end,
+ Plt = filename:join(PrivDir, "verify_plt_info.iplt"),
+ compile_all(DataDir, PrivDir),
+ ErlangModule = erlang_module(),
+ Opts = [{init_plt, [Plt]}],
+ AllSubSets = all_subsets(all_mods()),
+ Run = fun(Mods) -> run_dialyzer(incremental, [ErlangModule | ToPath(Mods)], Opts) end,
+ [run_and_verify_plt_info(Subset, Run, Plt) || Subset <- AllSubSets].
+
+run_and_verify_plt_info(Subset, Run, PltFile) ->
+ _ = Run(Subset),
+ {Plt, {iplt_info, Md5, ModDeps, Warnings, _LegalWarnings}} =
+ dialyzer_iplt:plt_and_info_from_file(PltFile),
+ dialyzer_plt:delete(Plt),
+ verify_md5_list([erlang|Subset], Md5),
+ verify_mod_deps(Subset, ModDeps),
+ verify_warnings(Subset, Warnings).
+
+verify_md5_list(AllModules, Md5) ->
+ ExpectedModules = lists:sort(AllModules),
+ ActualModules = lists:sort([ModuleName || {ModuleName, _CheckSum} <- Md5]),
+ case ExpectedModules =:= ActualModules of
+ true ->
+ ok;
+ false ->
+ ct:pal("Expected modules: ~p~nActual Modules:~p~n",
+ [ExpectedModules, ActualModules]),
+ ExpectedModules = ActualModules
+ end.
+
+verify_mod_deps(Subset, ModDeps) ->
+ Unused = all_mods() -- Subset,
+ DependsOn = fun(Mod) -> depends_on(Mod) -- Unused end,
+ lists:foreach(fun(Mod) -> check_one_mod(Mod, ModDeps, DependsOn) end, Subset).
+
+check_one_mod(Mod, ModDeps, DependsOn) ->
+ StoredDeps = lists:sort(rewrite_ans(dict:find(Mod, ModDeps))),
+ ExpectedDeps = lists:sort(DependsOn(Mod)),
+ case ExpectedDeps =:= StoredDeps of
+ true ->
+ ok;
+ false ->
+ ct:pal("Expected deps: ~p~nStored deps:~p~n",
+ [ExpectedDeps, StoredDeps]),
+ ExpectedDeps = StoredDeps
+ end.
+
+rewrite_ans({ok, Value}) -> Value;
+rewrite_ans(error) -> [].
+
+verify_warnings(Subset, Warnings) ->
+ Unused = all_mods() -- Subset,
+ lists:foreach(fun(Mod) -> check_warnings(Mod, Warnings) end, Subset),
+ lists:foreach(fun(Mod) -> check_unused_mod_warnings(Mod, Warnings) end, Unused).
+
+check_warnings(Mod, Warnings) ->
+ StoredWarnings = lists:sort(replace_location(maps:get(Mod, Warnings))),
+ ExpectedWarnings = [warning_for_module(Mod)],
+ ExpectedWarnings = StoredWarnings.
+
+check_unused_mod_warnings(Mod, Warnings) ->
+ false = maps:is_key(Mod, Warnings).
+
+add_and_remove_test_with_unknown_warnings(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" ||
+ Module <- Modules] end,
+ Plt = filename:join(PrivDir, "add_and_remove.iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [Plt]}, {warnings, [unknown]}, {report_mode, verbose}],
+ Run = fun(Mods) ->
+ run_dialyzer_for_modules_analyzed(incremental,
+ [erlang_module() | ToPath(Mods)],
+ [{warning_files, ToPath(Mods)} | Opts])
+ end,
+ %% We ignore empty Mods, since no warning modules modules is
+ %% interpreted as a request to show all warning modules (for
+ %% convenience). However, if all module warnings are shown and
+ %% 'unknown' warnings are enabled, we get a load of 'unknown'
+ %% warnings from the built-in Erlang module, so we exclude that
+ %% case here
+ AllSubSets = [Mods || Mods <- all_subsets(all_mods()), Mods =/= []],
+ %Pairs = [{I, N} || I <- AllSubSets, N <- AllSubSets], % All pairs takes ~ 10 mins to run
+ Pairs = some_pairs(AllSubSets),
+ [run_one_add_and_remove_test(Initial, New, Run, true) || {Initial, New} <- Pairs],
+ ok.
+
+%% Run the tests to add and remove modules for some combinations
+add_and_remove_test(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ Plt = filename:join(PrivDir, "add_and_remove.iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [Plt]}, {report_mode, verbose}, {warnings, [no_unknown]}],
+ Run = fun(Mods) -> run_dialyzer_for_modules_analyzed(incremental, [erlang_module() | ToPath(Mods)], Opts) end,
+ AllSubSets = [Mods || Mods <- all_subsets(all_mods())],
+ %Pairs = [{I, N} || I <- AllSubSets, N <- AllSubSets], % All pairs takes ~ 10 mins to run
+ Pairs = some_pairs(AllSubSets),
+ [run_one_add_and_remove_test(Initial, New, Run, false) || {Initial, New} <- Pairs],
+ ok.
+
+
+%% Runs a test where we first run the analysis on the InitialSet, and
+%% then run the analysis on the newset, checking that we get the
+%% warnings we expect given this change of modules. Supports running
+%% with unknown function and types and also with getting warnings for
+%% all modules, not just the re-analyzed ones.
+run_one_add_and_remove_test(InitialSet, NewSet, Run, WithUnknown) ->
+ {ExpectedErrors, ExpectedAnalyzed} =
+ expected_warnings_and_analyzed_add_and_remove(InitialSet, NewSet, WithUnknown),
+ _ = Run(InitialSet),
+ {ErrorsRaw, Analyzed} = Run(NewSet),
+ Errors = ordsets:from_list(replace_location(ErrorsRaw)),
+
+ if Errors =/= ExpectedErrors ->
+ ct:pal("Initial modules: ~p~nNew Modules:~p~nModules analyzed:~p~nModules expected to be analyzed:~p~n"
+ "Returned Warnings:~p~nExpected Warnings:~p~n",
+ [InitialSet, NewSet, Analyzed, ExpectedAnalyzed, Errors, ExpectedErrors]),
+ {Errors, Analyzed} = {ExpectedErrors, ExpectedAnalyzed};
+ true ->
+ ok
+ end.
+
+%% Just create some pairs.
+some_pairs([A, B | Rest]) ->
+ [{A, B} | some_pairs([B|Rest])];
+some_pairs(_) -> [].
+
+
+%% We expect incrementality to minimise the number of modules
+%% re-analysed, but still report warnings for modules which didn't
+%% need to be re-analysed because their warnings were cached. We
+%% expect a module to be re-analyzed when it is freshly added or when
+%% one of its transistive dependencies have been added or removed,
+%% using the dependency graph from the previous run. Adds unknown
+%% function warnings when they are supported, and generates all
+%% warnings when that feature is turned on.
+expected_warnings_and_analyzed_add_and_remove(Initial, New, WithUnknown) ->
+ InitialSet = ordsets:from_list(Initial),
+ NewSet = ordsets:from_list(New),
+ Removed = ordsets:from_list(InitialSet -- NewSet),
+ Added = ordsets:from_list(NewSet -- InitialSet),
+ Changed = ordsets:union(Removed, Added),
+ All = ordsets:from_list(all_mods()),
+ Missing = ordsets:subtract(All, NewSet),
+ DependsOn = fun(Mod) -> depends_on(Mod, fun(X) -> lists:member(X, InitialSet) end) end,
+ Dependencies = ordsets:from_list([Mod || C <- Changed, Mod <- depends_on_transitively(C, DependsOn)]),
+ Analyzed = ordsets:intersection(ordsets:union(Added, Dependencies), NewSet),
+ WarnModules = NewSet,
+ UnknownWarnings =
+ [unknown_function_warning(M) ||
+ WithUnknown,
+ Mod <- WarnModules,
+ M <- Missing,
+ lists:member(Mod, depends_on(M))],
+ {ordsets:from_list([warning_for_module(Mod) ||
+ Mod <- WarnModules] ++ UnknownWarnings), Analyzed}.
+
+%% Run the incremental test for all combination of the 6 modules,
+%% checking that we get the expected level of incrementality, i.e. we
+%% only reanalyze what we need to given what has changed
+incremental_test(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" ||
+ Module <- Modules] end,
+ Change = fun(M) -> change_module(DataDir, PrivDir, M) end,
+ Plt = filename:join(PrivDir, "incremental.iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [Plt]}, {report_mode, verbose}, {warnings, [no_unknown]}],
+ AllWarnings = run_dialyzer(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ Run = fun() ->
+ run_dialyzer_for_modules_analyzed(incremental,
+ [erlang_module() | ToPath(all_mods())],
+ Opts)
+ end,
+ [run_one_incremental_test(SubSet, Change, Run, AllWarnings) ||
+ SubSet <- lists:reverse(all_subsets(all_mods()))],
+ ok.
+
+%% Run the incremental test for all combination of the 6 modules,
+%% checking that with or without touching a file, we get the expected
+%% warnings
+incremental_select_warnings_test(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" ||
+ Module <- Modules] end,
+ Plt = filename:join(PrivDir, "select_warnings.iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [Plt]}],
+ _ = run_dialyzer(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ RunWithoutChange =
+ fun(ExtraOpt) ->
+ run_dialyzer(incremental, [erlang_module() | ToPath(all_mods())], [ExtraOpt|Opts])
+ end,
+ RunWithChange =
+ fun(ExtraOpt) ->
+ change_module(DataDir, PrivDir, m4),
+ run_dialyzer(incremental, [erlang_module() | ToPath(all_mods())], [ExtraOpt|Opts])
+ end,
+ TestSets = [Mods || Mods <- all_subsets(all_mods()), Mods =/= []],
+ [run_one_select_warnings_test(SubSet, RunWithoutChange, ToPath) || SubSet <- TestSets],
+ [run_one_select_warnings_test(SubSet, RunWithChange, ToPath) || SubSet <- TestSets].
+
+run_one_select_warnings_test(WarningMods, Run, ToPath) ->
+ ExpectedWarnings = lists:sort([warning_for_module(M) || M <- WarningMods]),
+ ActualWarnings = lists:sort(replace_location(Run({warning_files, ToPath(WarningMods)}))),
+ case ExpectedWarnings =/= ActualWarnings of
+ true ->
+ ct:pal("Warning modules: ~p~nReturned Warnings:~p~nExpected Warnings:~p~n",
+ [WarningMods, ActualWarnings, ExpectedWarnings]),
+ ExpectedWarnings = ActualWarnings;
+ false ->
+ ok
+ end.
+
+%% Recompiles a set of modules and then runs the analysis verifying that we re-analyze
+%% the changed modules and their transitive dependencies.
+run_one_incremental_test(ChangedMods, Change, Run, ExpectedWarnings) ->
+ lists:foreach(Change, ChangedMods),
+ ExpectedModulesAnalyzed = expected_modules_reanalyzed_incremental(ChangedMods),
+ {Warnings, ModulesAnalyzed} = Run(),
+ if Warnings =/= ExpectedWarnings ->
+ ct:pal("Changed modules: ~p~nModules reanalyzed:~p~nExpected modules reanalyzed:~p~nReturned Warnings:~p~nExpected Warnings:~p~n",
+ [ChangedMods, ModulesAnalyzed, ExpectedModulesAnalyzed, Warnings, ExpectedWarnings]),
+ {Warnings, ModulesAnalyzed} = {ExpectedWarnings, ExpectedModulesAnalyzed};
+ true ->
+ ok
+ end.
+
+check_metrics_file_contents(MetricsFileName, ExpectedMetricsFileStr) ->
+ {ok, MetricsFileBin} = file:read_file(MetricsFileName),
+ MetricsFileStr = unicode:characters_to_list(MetricsFileBin),
+ if MetricsFileStr =/= ExpectedMetricsFileStr ->
+ ct:pal("Metrics file didn't contain expected contents. Expected:~n~p~nActual:~n~p~n",
+ [ExpectedMetricsFileStr, MetricsFileStr]),
+ ExpectedMetricsFileStr = MetricsFileStr;
+ true ->
+ ok
+ end.
+
+report_new_plt_test(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ MetricsFile = PrivDir ++ "my_incrementality_metrics.log",
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ Plt = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [Plt]}, {report_mode, verbose}, {metrics_file, MetricsFile}],
+ {_Warnings, Stdout} = run_dialyzer_capture(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ CandidateLines =
+ lists:filter(
+ fun (Line) ->
+ case string:prefix(Line, "PLT does not yet exist at") of
+ nomatch -> false;
+ _ -> true
+ end
+ end,
+ Stdout),
+ ExpectedLine = "PLT does not yet exist at " ++ Plt ++ ", so an analysis must be run for 7 modules to populate it\n",
+ if CandidateLines =/= [ExpectedLine] ->
+ ct:pal("Failed to find expected log line in stdout.~nExpected:~n ~p~nLines that look close: ~n~p~n",
+ [ExpectedLine, CandidateLines]),
+ CandidateLines = [ExpectedLine];
+ true ->
+ ok
+ end,
+ ExpectedMetricsFileStr =
+ "total_modules: 7\n"
+ "analysed_modules: 7\n"
+ "reason: new_plt_file\n",
+ check_metrics_file_contents(MetricsFile, ExpectedMetricsFileStr).
+
+report_degree_of_incrementality_test(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ MetricsFile = PrivDir ++ "my_incrementality_metrics.log",
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ Plt = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [Plt]}, {report_mode, verbose}, {metrics_file, MetricsFile}],
+ _WarningsBeforeTouchingFile = run_dialyzer(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ change_module(DataDir, PrivDir, m1),
+ change_module(DataDir, PrivDir, m5),
+ {_WarningsAfterTouchingFile, Stdout} = run_dialyzer_capture(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+
+ ExpectedLine =
+ " Of the 7 files being tracked, 2 have been changed or removed, "
+ "resulting in 5 requiring analysis because they depend on those changes\n",
+ case lists:any(fun (Line) -> Line =:= ExpectedLine end, Stdout) of
+ false ->
+ ct:pal("Failed to find expected log line in stdout.~nExpected:~n ~p~nStdout: ~n~p~n",
+ [ExpectedLine, Stdout]),
+ ct:fail("Dialyzer failed to print the expected line to stdout");
+ true ->
+ ok
+ end,
+ ExpectedMetricsFileStr =
+ "total_modules: 7\n"
+ "analysed_modules: 5\n"
+ "reason: incremental_changes\n"
+ "changed_or_removed_modules: 2\n",
+ check_metrics_file_contents(MetricsFile, ExpectedMetricsFileStr).
+
+report_stored_warnings_only_files_safely_removed_test(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ MetricsFile = PrivDir ++ "my_incrementality_metrics.log",
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ Plt = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [Plt]}, {report_mode, verbose}, {metrics_file, MetricsFile}],
+ _WarningsBeforeTouchingFile = run_dialyzer(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ delete_module_beam_file(PrivDir, m1),
+ {_WarningsAfterTouchingFile, Stdout} = run_dialyzer_capture(incremental, [erlang_module() | ToPath(all_mods() -- [m1])], Opts),
+
+ ExpectedLine =
+ "PLT has fully cached the request because nothing depended on the file removed, so no additional analysis is needed\n",
+ case lists:any(fun (Line) -> Line =:= ExpectedLine end, Stdout) of
+ false ->
+ ct:pal("Failed to find expected log line in stdout.~nExpected:~n ~p~nStdout: ~n~p~n",
+ [ExpectedLine, Stdout]),
+ ct:fail("Dialyzer failed to print the expected line to stdout");
+ true ->
+ ok
+ end,
+ ExpectedMetricsFileStr =
+ "total_modules: 6\n"
+ "analysed_modules: 0\n"
+ "reason: incremental_changes\n"
+ "changed_or_removed_modules: 1\n",
+ check_metrics_file_contents(MetricsFile, ExpectedMetricsFileStr).
+
+report_stored_warnings_no_files_changed_test(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ MetricsFile = PrivDir ++ "my_incrementality_metrics.log",
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ Plt = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [Plt]}, {report_mode, verbose}, {metrics_file, MetricsFile}],
+ _WarningsBeforeTouchingFile = run_dialyzer(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ {_WarningsAfterTouchingFile, Stdout} = run_dialyzer_capture(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+
+ ExpectedLine =
+ "PLT has fully cached the request, so no additional analysis is needed\n",
+ case lists:any(fun (Line) -> Line =:= ExpectedLine end, Stdout) of
+ false ->
+ ct:pal("Failed to find expected log line in stdout.~nExpected:~n ~p~nStdout: ~n~p~n",
+ [ExpectedLine, Stdout]),
+ ct:fail("Dialyzer failed to print the expected line to stdout");
+ true ->
+ ok
+ end,
+ ExpectedMetricsFileStr =
+ "total_modules: 7\n"
+ "analysed_modules: 0\n"
+ "reason: incremental_changes\n"
+ "changed_or_removed_modules: 0\n",
+ check_metrics_file_contents(MetricsFile, ExpectedMetricsFileStr).
+
+report_old_plt_version_number_test(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ MetricsFile = PrivDir ++ "my_incrementality_metrics.log",
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ PltFileName = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [PltFileName]}, {report_mode, verbose}, {metrics_file, MetricsFile}],
+
+ % Set up "old" PLT file
+ Plt = dialyzer_plt:new(),
+ ModDeps = dict:new(),
+ ValidButEmptyPltInfo =
+ {iplt_info, [], dict:new(), #{}, ordsets:new()},
+ OldVsn = "0.0.1",
+ dialyzer_iplt:to_file_custom_vsn(PltFileName, Plt, ModDeps, ValidButEmptyPltInfo, OldVsn, none),
+
+ {_WarningsAfterTouchingFile, Stdout} = run_dialyzer_capture(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ ExpectedLine =
+ "PLT is for a different Dialyzer version, so an analysis must be run for 7 modules to rebuild it\n",
+ case lists:any(fun (Line) -> Line =:= ExpectedLine end, Stdout) of
+ false ->
+ ct:pal("Failed to find expected log line in stdout.~nExpected:~n ~p~nStdout: ~n~p~n",
+ [ExpectedLine, Stdout]),
+ ct:fail("Dialyzer failed to print the expected line to stdout");
+ true ->
+ ok
+ end,
+ ExpectedMetricsFileStr =
+ "total_modules: 7\n"
+ "analysed_modules: 7\n"
+ "reason: plt_built_with_different_version\n",
+ check_metrics_file_contents(MetricsFile, ExpectedMetricsFileStr).
+
+report_old_plt_version_hash_test(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ MetricsFile = PrivDir ++ "my_incrementality_metrics.log",
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ PltFileName = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [PltFileName]}, {report_mode, verbose}, {metrics_file, MetricsFile}],
+
+ %% Set up "old" PLT file
+ Plt = dialyzer_plt:new(),
+ ModDeps = dict:new(),
+ ValidButEmptyPltInfo =
+ {iplt_info, [], dict:new(), #{}, ordsets:new()},
+ FakeImplMd5 = [
+ {erl_types, erlang:md5([$f, $o, $o])},
+ {erl_bif_types, erlang:md5([$b, $a, $r])}
+ ],
+ dialyzer_iplt:to_file_custom_vsn(PltFileName, Plt, ModDeps, ValidButEmptyPltInfo, none, FakeImplMd5),
+
+ {_WarningsAfterTouchingFile, Stdout} = run_dialyzer_capture(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ ExpectedLine =
+ "PLT is for a different Dialyzer version, so an analysis must be run for 7 modules to rebuild it\n",
+ case lists:any(fun (Line) -> Line =:= ExpectedLine end, Stdout) of
+ false ->
+ ct:pal("Failed to find expected log line in stdout.~nExpected:~n ~p~nStdout: ~n~p~n",
+ [ExpectedLine, Stdout]),
+ ct:fail("Dialyzer failed to print the expected line to stdout");
+ true ->
+ ok
+ end,
+ ExpectedMetricsFileStr =
+ "total_modules: 7\n"
+ "analysed_modules: 7\n"
+ "reason: plt_built_with_different_version\n",
+ check_metrics_file_contents(MetricsFile, ExpectedMetricsFileStr).
+
+report_no_stored_warnings_test(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ MetricsFile = PrivDir ++ "my_incrementality_metrics.log",
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ PltFileName = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [PltFileName]}, {report_mode, verbose}, {metrics_file, MetricsFile}],
+
+ % Run Dialyzer, read the PLT, blank the warning map, then write it back
+ _ = run_dialyzer(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ {Plt, PltInfo} = dialyzer_iplt:plt_and_info_from_file(PltFileName),
+ {iplt_info, Md5, ModDeps, _Warnings, LegalWarnings} = PltInfo,
+ MissingWarningMap = none,
+ ValidPltInfoWithNoWarningMap =
+ {iplt_info, Md5, ModDeps, MissingWarningMap, LegalWarnings},
+ dialyzer_iplt:to_file(PltFileName, Plt, ModDeps, ValidPltInfoWithNoWarningMap),
+
+ {_, Stdout} = run_dialyzer_capture(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ ExpectedLine =
+ "PLT does not contain cached warnings, so an analysis must be run for 7 modules to rebuild it\n",
+ case lists:any(fun (Line) -> Line =:= ExpectedLine end, Stdout) of
+ false ->
+ ct:pal("Failed to find expected log line in stdout.~nExpected:~n ~p~nStdout: ~n~p~n",
+ [ExpectedLine, Stdout]),
+ ct:fail("Dialyzer failed to print the expected line to stdout");
+ true ->
+ ok
+ end,
+ ExpectedMetricsFileStr =
+ "total_modules: 7\n"
+ "analysed_modules: 7\n"
+ "reason: no_stored_warnings_in_plt\n",
+ check_metrics_file_contents(MetricsFile, ExpectedMetricsFileStr).
+
+report_legal_warnings_added(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ MetricsFile = PrivDir ++ "my_incrementality_metrics.log",
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ Plt = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [Plt]}, {report_mode, verbose}, {warnings, [no_unknown]}],
+ _WarningsBeforeTouchingFile = run_dialyzer(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ Opts1 = [{init_plt, [Plt]}, {report_mode, verbose}, {warnings, [unknown]}, {metrics_file, MetricsFile}],
+ {_WarningsAfterTouchingFile, Stdout} = run_dialyzer_capture(incremental, [erlang_module() | ToPath(all_mods())], Opts1),
+
+ ExpectedLine =
+ "PLT was built for a different set of enabled warnings, so an analysis "
+ "must be run for 7 modules to rebuild it\n",
+ case lists:any(fun (Line) -> Line =:= ExpectedLine end, Stdout) of
+ false ->
+ ct:pal("Failed to find expected log line in stdout.~nExpected:~n ~p~nStdout: ~n~p~n",
+ [ExpectedLine, Stdout]),
+ ct:fail("Dialyzer failed to print the expected line to stdout");
+ true ->
+ ok
+ end,
+ ExpectedMetricsFileStr =
+ "total_modules: 7\n"
+ "analysed_modules: 7\n"
+ "reason: warnings_changed\n",
+ check_metrics_file_contents(MetricsFile, ExpectedMetricsFileStr).
+
+report_legal_warnings_removed(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ MetricsFile = PrivDir ++ "my_incrementality_metrics.log",
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ Plt = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ compile_all(DataDir, PrivDir),
+ Opts = [{init_plt, [Plt]}, {report_mode, verbose}, {warnings, [unknown]}],
+ _ = run_dialyzer(incremental, [erlang_module() | ToPath(all_mods())], Opts),
+ Opts1 = [{init_plt, [Plt]}, {report_mode, verbose}, {metrics_file, MetricsFile}, {warnings, [no_unknown]}],
+ {_, Stdout} = run_dialyzer_capture(incremental, [erlang_module() | ToPath(all_mods())], Opts1),
+
+ ExpectedLine =
+ "PLT was built for a different set of enabled warnings, so an analysis "
+ "must be run for 7 modules to rebuild it\n",
+ case lists:any(fun (Line) -> Line =:= ExpectedLine end, Stdout) of
+ false ->
+ ct:pal("Failed to find expected log line in stdout.~nExpected:~n ~p~nStdout: ~n~p~n",
+ [ExpectedLine, Stdout]),
+ ct:fail("Dialyzer failed to print the expected line to stdout");
+ true ->
+ ok
+ end,
+ ExpectedMetricsFileStr =
+ "total_modules: 7\n"
+ "analysed_modules: 7\n"
+ "reason: warnings_changed\n",
+ check_metrics_file_contents(MetricsFile, ExpectedMetricsFileStr).
+
+fixing_all_warnings(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ ToPath = fun(Modules) -> [PrivDir ++ atom_to_list(Module) ++ ".beam" || Module <- Modules] end,
+ Plt = filename:join(PrivDir, "verify_plt_info.iplt"),
+ compile_all(DataDir, PrivDir),
+ ErlangModule = erlang_module(),
+ Opts = [{init_plt, [Plt]}, {warning_files, ToPath([fix, got_fixed])}],
+ [] = run_dialyzer(incremental, [ErlangModule | ToPath([fix, got_fixed])], Opts),
+ break_module(DataDir, PrivDir),
+ [_Error1, _Error2] = run_dialyzer(incremental, [ErlangModule | ToPath([fix, got_fixed])], Opts),
+ fix_module(DataDir, PrivDir),
+ [] = run_dialyzer(incremental, [ErlangModule | ToPath([fix, got_fixed])], Opts).
+
+break_module(DataDir, PrivDir) ->
+ compile:file(filename:join(DataDir, "fix.erl"),
+ [{outdir, PrivDir},
+ debug_info,
+ {d, error, true}]).
+
+fix_module(DataDir, PrivDir) ->
+ compile:file(filename:join(DataDir, "fix.erl"),
+ [{outdir, PrivDir},
+ debug_info]).
+
+%% There is one warning per module which will be reported when that module is re-analyzed
+%% We expect a module to be re-analyzed when it has changed, or when one of its transitive
+%% dependencies have changed.
+expected_modules_reanalyzed_incremental(ChangedMods) ->
+ DependsOn = fun depends_on/1,
+ ordsets:from_list([Mod || C <- ChangedMods,
+ Mod <- [C|depends_on_transitively(C, DependsOn)]]).
+
+change_module(DataDir, PrivDir, Mod) ->
+ compile:file(filename:join(DataDir, atom_to_list(Mod)++".erl"),
+ [{outdir, PrivDir}, debug_info, {d, Mod, erlang:monotonic_time()}]).
+
+delete_module_beam_file(PrivDir, Mod) ->
+ ModuleBeamPath = PrivDir ++ atom_to_list(Mod) ++ ".beam",
+ ok = file:delete(ModuleBeamPath).
+
+%% Common utility functions
+
+replace_location(Warnings) ->
+ [{Type, loc, Warning} || {Type, _Loc, Warning} <- Warnings].
+
+warning_for_module(Mod) -> {warn_contract_types,loc,
+ {invalid_contract,
+ [Mod,wrong,1,{[1],true},"(float()) -> float()","(integer()) -> integer()"]}}.
+
+compile_all(DataDir, PrivDir) ->
+ [{ok, _} = compile:file(File, [{outdir, PrivDir}, debug_info]) || File <- filelib:wildcard(DataDir ++ "*.erl")].
+
+%% This describes the dependencies between modules m1->m6 in
+%% incremental_SUITE_data/ If you change thos deps update here.
+%% Don't add cycles (yet) or at least without changing depends on transitively
+depends_on(m1) -> [];
+depends_on(m2) -> [m1];
+depends_on(m3) -> [];
+depends_on(m4) -> [m2,m3];
+depends_on(m5) -> [m4];
+depends_on(m6) -> [m4].
+
+depends_on(X, IsInPlt) ->
+ [D || D <- depends_on(X), IsInPlt(D)].
+
+depends_on_transitively(M, DependsOn) ->
+ Val = DependsOn(M),
+ Val ++ [Dep || V <- Val, Dep <- [V|depends_on_transitively(V, DependsOn)]].
+
+all_subsets([X|Rest]) ->
+ [Res || SubSet <- all_subsets(Rest), Res <- [[X|SubSet], SubSet]];
+all_subsets([]) -> [[]].
+
+all_mods() -> [m1,m2,m3,m4,m5,m6].
+
+run_dialyzer(Analysis, Files, Opts) ->
+ dialyzer:run([{analysis_type, Analysis},
+ {files, Files},
+ {from, byte_code},
+ {warnings, [no_unknown]}|
+ Opts]).
+
+run_dialyzer_for_modules_analyzed(Analysis, Files, Opts) ->
+ dialyzer:run_report_modules_analyzed([{analysis_type, Analysis},
+ {files, Files},
+ {from, byte_code}|
+ Opts]).
+
+default_apps_config_xdg(Config) ->
+ TestHome = filename:join(?config(priv_dir, Config), ?FUNCTION_NAME),
+
+ %% We change the $HOME of the emulator to run this test
+ HomeEnv =
+ case os:type() of
+ {win32, _} ->
+ [Drive | Path] = filename:split(TestHome),
+ [{"APPDATA", filename:join(TestHome, "AppData")},
+ {"HOMEDRIVE", Drive},
+ {"HOMEPATH", filename:join(Path)}];
+ _ ->
+ [{"HOME", TestHome}]
+ end,
+
+ io:format("~p~n", [HomeEnv]),
+
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+
+ {ok, Peer, Node} = ?CT_PEER(#{ env => HomeEnv }),
+
+ erpc:call(
+ Node,
+ fun() ->
+ %% Find out the path of the config file
+ HomeConfigFilename =
+ filename:join(filename:basedir(user_config, "erlang"),
+ "dialyzer.config"),
+ io:format("~ts\n", [HomeConfigFilename]),
+ ok = filelib:ensure_dir(HomeConfigFilename),
+
+ %% Write configuration file
+ HomeConfig =
+ {incremental, {default_apps, [stdlib, kernel, erts,
+ compiler, mnesia, ftp]}},
+ ok = file:write_file(HomeConfigFilename,
+ io_lib:format("~p.~n", [HomeConfig])),
+
+ %% Run dialyzer and check result
+ _ = dialyzer:run([{analysis_type, incremental},
+ {init_plt,PltFile},
+ {output_plt, PltFile}]),
+ {ok, {incremental, [{modules, Modules}]}} = dialyzer:plt_info(PltFile),
+
+ ExpectedModules = [gb_sets, erlang, compile, mnesia, ftp],
+
+ %% Assert PLT info contains modules from the apps given in the config
+ ?assertMatch([], ordsets:subtract(
+ ordsets:from_list(ExpectedModules),
+ ordsets:from_list(Modules)))
+ end),
+
+ peer:stop(Peer).
+
+
+default_apps_config_env_var(Config) ->
+ TestDir = filename:join(?config(priv_dir, Config), ?FUNCTION_NAME),
+
+ ConfigFilename =
+ filename:join(
+ [TestDir, "some_custom_location", "dialyzer.config"]),
+ ok = filelib:ensure_dir(ConfigFilename),
+
+ Env = [{"DIALYZER_CONFIG", ConfigFilename}],
+
+ DialyzerConfig =
+ {incremental, {default_apps, [stdlib, kernel, erts, compiler, mnesia, ftp]}},
+
+ ok = file:write_file(ConfigFilename, io_lib:format("~p.~n", [DialyzerConfig])),
+
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+
+ {ok, Peer, Node} = ?CT_PEER(#{ env => Env }),
+
+ erpc:call(
+ Node,
+ fun() ->
+ _ = dialyzer:run([{analysis_type, incremental},
+ {init_plt,PltFile},
+ {output_plt, PltFile}]),
+ {ok, {incremental, [{modules, Modules}]}} = dialyzer:plt_info(PltFile),
+
+ ExpectedModules = [gb_sets, erlang, compile, mnesia, ftp],
+
+ % Assert PLT info contains modules from the apps given in the config
+ ?assertMatch([], sets:to_list(sets:subtract(
+ sets:from_list(ExpectedModules),
+ sets:from_list(Modules))))
+ end),
+
+ peer:stop(Peer).
+
+default_apps_config_env_var_prioritised_over_xdg(Config) ->
+ TestHome = filename:join(?config(priv_dir, Config), ?FUNCTION_NAME),
+
+ ConfigSubDirs =
+ case os:type() of
+ {unix, darwin} ->
+ ["Library","Application Support"];
+ {win32, _} ->
+ [];
+ _ ->
+ [".config"]
+ end,
+
+ HomeConfigFilename =
+ filename:join([TestHome] ++ ConfigSubDirs ++ ["erlang", "dialyzer.config"]),
+ ok = filelib:ensure_dir(HomeConfigFilename),
+
+ HomeConfig =
+ {incremental, {default_apps, [stdlib, kernel, erts]}},
+
+ ok = file:write_file(HomeConfigFilename, io_lib:format("~p.~n", [HomeConfig])),
+
+ ConfigFilename =
+ filename:join(
+ [TestHome, "some_custom_location", "dialyzer.config"]),
+ ok = filelib:ensure_dir(ConfigFilename),
+
+
+ %% We change the $HOME of the emulator to run this test
+ HomeEnv = case os:type() of
+ {win32, _} ->
+ [Drive | Path] = filename:split(TestHome),
+ [{"APPDATA", filename:join(TestHome,"AppData")},
+ {"HOMEDRIVE", Drive}, {"HOMEPATH", Path}];
+ _ ->
+ [{"HOME", TestHome}]
+ end,
+
+ Env = HomeEnv ++ [{"DIALYZER_CONFIG", ConfigFilename}],
+
+ EnvVarConfig =
+ {incremental, {default_apps, [compiler, mnesia, ftp]}},
+
+ ok = file:write_file(ConfigFilename, io_lib:format("~p.~n", [EnvVarConfig])),
+
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+
+ {ok, Peer, Node} = ?CT_PEER(#{ env => Env }),
+
+ erpc:call(
+ Node,
+ fun() ->
+ _ = dialyzer:run([{analysis_type, incremental},
+ {init_plt,PltFile},
+ {output_plt, PltFile}]),
+ {ok, {incremental, [{modules, Modules}]}} = dialyzer:plt_info(PltFile),
+
+ ExpectedModules = [compile, mnesia, ftp],
+ UnexpectedModules = [gb_sets],
+
+ % Assert PLT info contains modules from the apps given in the env var config
+ ?assertMatch([], sets:to_list(sets:subtract(
+ sets:from_list(ExpectedModules),
+ sets:from_list(Modules)))),
+
+ % Assert PLT info does not contain modules from the apps given in the xdg config
+ ?assertMatch([], sets:to_list(sets:intersection(
+ sets:from_list(UnexpectedModules),
+ sets:from_list(Modules))))
+ end),
+
+ peer:stop(Peer).
+
+legal_warnings_config_xdg(Config) ->
+ TestHome = filename:join(?config(priv_dir, Config), ?FUNCTION_NAME),
+
+ %% We change the $HOME of the emulator to run this test
+ HomeEnv =
+ case os:type() of
+ {win32, _} ->
+ [Drive | Path] = filename:split(TestHome),
+ [{"APPDATA", filename:join(TestHome, "AppData")},
+ {"HOMEDRIVE", Drive},
+ {"HOMEPATH", filename:join(Path)}];
+ _ ->
+ [{"HOME", TestHome}]
+ end,
+
+ io:format("~p~n", [HomeEnv]),
+
+ {ok, Peer, Node} = ?CT_PEER(#{ env => HomeEnv }),
+
+ SrcWithImproperList = <<"
+ -module(my_improper_list_module).
+ -export([g/0]).
+
+ g() -> [a|b]. % Improper list: Last element is not the empty list
+ ">>,
+
+ {ok, BeamFileWithImproperList} =
+ compile(Config, SrcWithImproperList, my_improper_list_module, []),
+
+ AppsConfig =
+ {incremental, {default_apps, [stdlib, kernel, erts, compiler, mnesia, ftp]}},
+
+ erpc:call(
+ Node,
+ fun() ->
+ %% Find out the path of the config file
+ HomeConfigFilename =
+ filename:join(filename:basedir(user_config, "erlang"),
+ "dialyzer.config"),
+ io:format("~ts\n", [HomeConfigFilename]),
+ ok = filelib:ensure_dir(HomeConfigFilename),
+
+ %% Write configuration file
+ WarningsConfig1 =
+ {warnings, [no_unknown, no_improper_lists]},
+ ok = file:write_file(HomeConfigFilename,
+ io_lib:format("~p.~n~p.~n", [AppsConfig, WarningsConfig1])),
+ WarningsWithConfigSet =
+ dialyzer:run([{analysis_type, incremental},
+ {files, [BeamFileWithImproperList]},
+ {from, byte_code}]),
+ ?assertEqual([], WarningsWithConfigSet),
+
+ %% Write alternative configuration file
+ WarningsConfig2 =
+ {warnings, [no_unknown]},
+ ok = file:write_file(HomeConfigFilename,
+ io_lib:format("~p.~n~p.~n", [AppsConfig, WarningsConfig2])),
+ WarningsWithoutConfigSet =
+ dialyzer:run([{analysis_type, incremental},
+ {files, [BeamFileWithImproperList]},
+ {from, byte_code}]),
+ ?assertMatch([{warn_non_proper_list, _Loc, _Msg}], WarningsWithoutConfigSet)
+ end),
+
+ peer:stop(Peer).
+
+paths_config_xdg(Config) ->
+ TestHome = filename:join(?config(priv_dir, Config), ?FUNCTION_NAME),
+
+ %% We change the $HOME of the emulator to run this test
+ HomeEnv =
+ case os:type() of
+ {win32, _} ->
+ [Drive | Path] = filename:split(TestHome),
+ [{"APPDATA", filename:join(TestHome, "AppData")},
+ {"HOMEDRIVE", Drive},
+ {"HOMEPATH", filename:join(Path)}];
+ _ ->
+ [{"HOME", TestHome}]
+ end,
+
+ io:format("~p~n", [HomeEnv]),
+
+ ExtraModulesDirOrig =
+ filename:join(?config(data_dir, Config), "extra_modules"),
+ ExtraModulesDir =
+ filename:join(?config(priv_dir, Config), "extra_modules"),
+ ok = filelib:ensure_path(ExtraModulesDir),
+ ok = filelib:ensure_path(filename:join(ExtraModulesDir,"ebin")),
+ ok = filelib:ensure_path(filename:join(ExtraModulesDir,"src")),
+
+ {ok, _} =
+ file:copy(
+ filename:join([ExtraModulesDirOrig, "src", "extra_modules.app.src"]),
+ filename:join([ExtraModulesDir, "src", "extra_modules.app.src"])
+ ),
+ {ok, _} =
+ file:copy(
+ filename:join([ExtraModulesDirOrig, "src", "extra_module.erl"]),
+ filename:join([ExtraModulesDir, "src", "extra_module.erl"])
+ ),
+
+ {ok, Peer, Node} = ?CT_PEER(#{ env => HomeEnv }),
+ {ok, _} =
+ compile:file(
+ filename:join([ExtraModulesDir,"src","extra_module.erl"]),
+ [{outdir, filename:join([ExtraModulesDir,"ebin"])}, debug_info]
+ ),
+
+ AppsConfig =
+ {incremental,
+ {default_apps,
+ [stdlib,
+ kernel,
+ erts,
+ extra_modules
+ ]
+ },
+ {default_warning_apps,
+ [extra_modules % Only on path if added explicitly via config below
+ ]
+ }
+ },
+
+ WarningsConfig =
+ {warnings, [no_unknown]},
+
+ erpc:call(
+ Node,
+ fun() ->
+ %% Find out the path of the config file
+ HomeConfigFilename =
+ filename:join(filename:basedir(user_config, "erlang"),
+ "dialyzer.config"),
+ io:format("~ts~n", [HomeConfigFilename]),
+ ok = filelib:ensure_dir(HomeConfigFilename),
+
+ %% Write configuration file
+ PathConfig = {add_pathsa, [ExtraModulesDir, filename:join(ExtraModulesDir, "ebin"), filename:join(ExtraModulesDir, "src")]},
+ ok =
+ file:write_file(
+ HomeConfigFilename,
+ io_lib:format(
+ "~p.~n~p.~n~p.~n",
+ [AppsConfig, WarningsConfig, PathConfig])),
+
+ {Warnings, ModAnalyzed} =
+ % Will analyse apps from config, including the `extra_modules` app
+ % which contains a Dialyzer error
+ dialyzer:run_report_modules_analyzed([
+ {analysis_type, incremental},
+ {from, byte_code}]),
+
+ % Check we did actually analyze the module
+ ?assert(
+ lists:member(extra_module, ModAnalyzed),
+ lists:flatten(io_lib:format("Looking for 'extra_module' in ~tp~n", [ModAnalyzed]))),
+
+ % Check we got the warnings we expected from modules
+ % added to the path
+ ?assertMatch(
+ [ {warn_contract_types, {_,_}, {invalid_contract, [extra_module,f,1, {[1],true}, "(atom()) -> string()", "(integer()) -> nonempty_improper_list(integer(),3)"]}},
+ {warn_non_proper_list, {_,_}, {improper_list_constr,["3"]}}
+ ],
+ Warnings)
+ end),
+
+ peer:stop(Peer).
+
+multiple_plts_unsupported_in_incremental_mode(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ BazPltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ "-baz.iplt"),
+ QuuxPltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ "-quux.iplt"),
+
+ BazSrc = <<"
+ -module(baz).
+
+ f() -> ok.
+ ">>,
+ QuuxSrc = <<"
+ -module(quux).
+
+ g() -> undefined.
+ ">>,
+
+ {ok, BazBeamFile} = compile(Config, BazSrc, baz, []),
+ {ok, QuuxBeamFile} = compile(Config, QuuxSrc, quux, []),
+ _ = run_dialyzer(incremental, [BazBeamFile], [{output_plt, BazPltFile}]),
+ _ = run_dialyzer(incremental, [QuuxBeamFile], [{output_plt, QuuxPltFile}]),
+
+ ?assertThrow(
+ {dialyzer_error,"Incremental mode does not support multiple PLT files (" ++ _},
+ run_dialyzer(incremental,
+ [BazBeamFile, QuuxBeamFile],
+ [{plts, [BazPltFile, QuuxPltFile]}])).
+
+compile(Config, Prog, Module, CompileOpts) ->
+ Source = lists:concat([Module, ".erl"]),
+ PrivDir = proplists:get_value(priv_dir,Config),
+ Filename = filename:join([PrivDir, Source]),
+ ok = file:write_file(Filename, Prog),
+ Opts = [{outdir, PrivDir}, debug_info | CompileOpts],
+ {ok, Module} = compile:file(Filename, Opts),
+ {ok, filename:join([PrivDir, lists:concat([Module, ".beam"])])}.
+
+%% https://erlang.org/doc/man/ct.html#capture_get-1 seems to work by redirecting
+%% writes to stdout to be messages to the current pid. Those messages get queued up,
+%% with the intent to read them back later to get stdout.
+%%
+%% At various points, Dialyzer dequeues messages from its worker child processes,
+%% sometimes throwing them away knowing it doesn't need certain results from them.
+%%
+%% Sadly, these two things interact badly and Dialyzer ends up dequeuing (and hence,
+%% deleting) some of what was written to stdout. The solution is to run Dialyzer in
+%% a sub-process, so the messages go to the parent process's mailbox and Dialyzer is
+%% free to do what it wants with its own messages.
+%%
+%% Fundamentally this is an issue with ct:capture_get, since it seems to presume it's
+%% fine to send messages to the test case's pid and they'll never be read by the code
+%% under test!
+run_dialyzer_capture(Analysis, Files, Opts) ->
+ ct:capture_start(),
+ TestCasePid = self(),
+ RunDialyzer = fun() ->
+ Ret = run_dialyzer(Analysis, Files, Opts),
+ TestCasePid ! {dialyzer_done, Ret}
+ end,
+ _DialyzerPid = spawn_link(RunDialyzer),
+ DialyzerRet =
+ receive
+ {dialyzer_done, Ret} -> Ret
+ end,
+ ct:capture_stop(),
+ Stdout = ct:capture_get([]),
+ {DialyzerRet, Stdout}.
diff --git a/lib/dialyzer/test/incremental_SUITE_data/extra_modules/ebin/.gitignore b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/ebin/.gitignore
new file mode 100644
index 0000000000..9aedd49be8
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/ebin/.gitignore
@@ -0,0 +1,5 @@
+# Ignore everything in this directory
+*
+# Except this file, to force the directory to stick around
+# for the tests to later make use of
+!.gitignore
diff --git a/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_module.erl b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_module.erl
new file mode 100644
index 0000000000..21bb42ee61
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_module.erl
@@ -0,0 +1,14 @@
+-module(extra_module).
+
+-export([start/2,stop/1,f/1]).
+
+start(StartType, StartArgs) ->
+ error.
+
+stop(State) ->
+ error.
+
+% Purposely broken to generate a warning if the module is loaded and analysed
+-spec f(atom()) -> string().
+f(N) when is_integer(N) ->
+ [N + 1|3].
diff --git a/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_modules.app.src b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_modules.app.src
new file mode 100644
index 0000000000..01b38e4098
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_modules.app.src
@@ -0,0 +1,8 @@
+{application, extra_modules,
+ [{description, "An app with some extra modules"},
+ {vsn, "1"},
+ {modules, [extra_module]},
+ {registered, []},
+ {applications, [kernel, stdlib]},
+ {mod, {extra_module,[]}}
+ ]}.
diff --git a/lib/dialyzer/test/incremental_SUITE_data/fix.erl b/lib/dialyzer/test/incremental_SUITE_data/fix.erl
new file mode 100644
index 0000000000..6c1e85eda5
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/fix.erl
@@ -0,0 +1,10 @@
+-module(fix).
+
+-export([m/0]).
+
+-spec m() -> integer().
+-ifdef(error).
+m() -> 3.14.
+-else.
+m() -> 3.
+-endif.
diff --git a/lib/dialyzer/test/incremental_SUITE_data/got_fixed.erl b/lib/dialyzer/test/incremental_SUITE_data/got_fixed.erl
new file mode 100644
index 0000000000..e6db525b90
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/got_fixed.erl
@@ -0,0 +1,6 @@
+-module(got_fixed).
+
+-export([m/0]).
+
+-spec m() -> integer().
+m() -> fix:m().
diff --git a/lib/dialyzer/test/incremental_SUITE_data/m1.erl b/lib/dialyzer/test/incremental_SUITE_data/m1.erl
new file mode 100644
index 0000000000..1c49a2714a
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/m1.erl
@@ -0,0 +1,14 @@
+-module(m1).
+
+-export([id/1, m/0, wrong/1]).
+
+-ifdef(m1).
+m() -> ?m1.
+-else.
+m() -> false.
+-endif.
+
+-spec wrong(float()) -> float().
+wrong(X) when is_integer(X) -> X.
+
+id(X) -> m2:id(X).
diff --git a/lib/dialyzer/test/incremental_SUITE_data/m2.erl b/lib/dialyzer/test/incremental_SUITE_data/m2.erl
new file mode 100644
index 0000000000..83c4f2b55e
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/m2.erl
@@ -0,0 +1,14 @@
+-module(m2).
+
+-export([id/1, m/0, wrong/1]).
+
+-ifdef(m2).
+m() -> ?m2.
+-else.
+m() -> false.
+-endif.
+
+-spec wrong(float()) -> float().
+wrong(X) when is_integer(X) -> X.
+
+id(X) -> m4:id(X).
diff --git a/lib/dialyzer/test/incremental_SUITE_data/m3.erl b/lib/dialyzer/test/incremental_SUITE_data/m3.erl
new file mode 100644
index 0000000000..8ea8b8b45b
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/m3.erl
@@ -0,0 +1,14 @@
+-module(m3).
+
+-export([id/1, m/0, wrong/1]).
+
+-ifdef(m3).
+m() -> ?m3.
+-else.
+m() -> false.
+-endif.
+
+-spec wrong(float()) -> float().
+wrong(X) when is_integer(X) -> X.
+
+id(X) -> m4:id(X).
diff --git a/lib/dialyzer/test/incremental_SUITE_data/m4.erl b/lib/dialyzer/test/incremental_SUITE_data/m4.erl
new file mode 100644
index 0000000000..43da7b80c7
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/m4.erl
@@ -0,0 +1,14 @@
+-module(m4).
+
+-export([id/1, m/0, wrong/1]).
+
+-ifdef(m4).
+m() -> ?m4.
+-else.
+m() -> false.
+-endif.
+
+-spec wrong(float()) -> float().
+wrong(X) when is_integer(X) -> X.
+
+id(X) -> m5:id(X), m6:id(X).
diff --git a/lib/dialyzer/test/incremental_SUITE_data/m5.erl b/lib/dialyzer/test/incremental_SUITE_data/m5.erl
new file mode 100644
index 0000000000..0e8e3b2d1b
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/m5.erl
@@ -0,0 +1,14 @@
+-module(m5).
+
+-export([id/1, m/0, wrong/1]).
+
+-ifdef(m5).
+m() -> ?m5.
+-else.
+m() -> false.
+-endif.
+
+-spec wrong(float()) -> float().
+wrong(X) when is_integer(X) -> X.
+
+id(X) -> X.
diff --git a/lib/dialyzer/test/incremental_SUITE_data/m6.erl b/lib/dialyzer/test/incremental_SUITE_data/m6.erl
new file mode 100644
index 0000000000..9d24db5378
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/m6.erl
@@ -0,0 +1,14 @@
+-module(m6).
+
+-export([id/1, m/0, wrong/1]).
+
+-ifdef(m6).
+m() -> ?m6.
+-else.
+m() -> false.
+-endif.
+
+-spec wrong(float()) -> float().
+wrong(X) when is_integer(X) -> X.
+
+id(X) -> X.
diff --git a/lib/dialyzer/test/indent2_SUITE_data/dialyzer_options b/lib/dialyzer/test/indent2_SUITE_data/dialyzer_options
index ee07090337..726f8cca3a 100644
--- a/lib/dialyzer/test/indent2_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/indent2_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{warnings, [no_unused, no_return, specdiffs]}]}.
+{dialyzer_options, [{warnings, [no_unused, no_return, no_unknown, specdiffs]}]}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/dialyzer_options b/lib/dialyzer/test/indent_SUITE_data/dialyzer_options
index 3ff26b87db..901581133f 100644
--- a/lib/dialyzer/test/indent_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/indent_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{warnings, [no_unused, no_return]}]}.
+{dialyzer_options, [{warnings, [no_unused, no_return, no_unknown, overlapping_contract]}]}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/callbacks_and_specs b/lib/dialyzer/test/indent_SUITE_data/results/callbacks_and_specs
index 0d513775a1..6e34494e2a 100644
--- a/lib/dialyzer/test/indent_SUITE_data/results/callbacks_and_specs
+++ b/lib/dialyzer/test/indent_SUITE_data/results/callbacks_and_specs
@@ -3,7 +3,7 @@ my_callbacks_wrong.erl:26:2: The return type
#state{parent :: pid(),
status :: 'closed' | 'init' | 'open',
subscribe :: [{pid(), integer()}],
- counter :: integer()} in the specification of callback_init/1 is not a subtype of
+ counter :: integer()} in the specification of callback_init/1 has nothing in common with
{'ok', _}, which is the expected return type for the callback of the my_behaviour behaviour
my_callbacks_wrong.erl:28:1: The inferred return type of callback_init/1
(#state{parent :: pid(),
@@ -11,13 +11,9 @@ my_callbacks_wrong.erl:28:1: The inferred return type of callback_init/1
subscribe :: [],
counter :: 1}) has nothing in common with
{'ok', _}, which is the expected return type for the callback of the my_behaviour behaviour
-my_callbacks_wrong.erl:30:2: The return type
- {'reply',
- #state{parent :: pid(),
- status :: 'closed' | 'init' | 'open',
- subscribe :: [{pid(), integer()}],
- counter :: integer()}} in the specification of callback_cast/3 is not a subtype of
+my_callbacks_wrong.erl:33:1: The inferred return type of callback_cast/3
+ ({'reply', _}) has nothing in common with
{'noreply', _}, which is the expected return type for the callback of the my_behaviour behaviour
my_callbacks_wrong.erl:39:2: The specified type for the 2nd argument of callback_call/3 (
- atom()) is not a supertype of
+ atom()) has nothing in common with
pid(), which is expected type for this argument in the callback of the my_behaviour behaviour
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/contract3 b/lib/dialyzer/test/indent_SUITE_data/results/contract3
index b290f232a2..117801ff14 100644
--- a/lib/dialyzer/test/indent_SUITE_data/results/contract3
+++ b/lib/dialyzer/test/indent_SUITE_data/results/contract3
@@ -1,3 +1,3 @@
-contract3.erl:17:2: Overloaded contract for contract3:t1/1 has overlapping domains; such contracts are currently unsupported and are simply ignored
-contract3.erl:29:2: Overloaded contract for contract3:t3/3 has overlapping domains; such contracts are currently unsupported and are simply ignored
+contract3.erl:17:2: Overloaded contract for contract3:t1/1 has overlapping domains; such contracts cannot establish a dependency between the overloaded input and output types
+contract3.erl:29:2: Overloaded contract for contract3:t3/3 has overlapping domains; such contracts cannot establish a dependency between the overloaded input and output types
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/contracts_with_subtypes b/lib/dialyzer/test/indent_SUITE_data/results/contracts_with_subtypes
index 039e5e23f6..9294602211 100644
--- a/lib/dialyzer/test/indent_SUITE_data/results/contracts_with_subtypes
+++ b/lib/dialyzer/test/indent_SUITE_data/results/contracts_with_subtypes
@@ -74,8 +74,12 @@ contracts_with_subtypes.erl:238:2: The pattern
contracts_with_subtypes.erl:239:2: The pattern
'alpha' can never match the type
{'ok', _, string()}
-contracts_with_subtypes.erl:23:2: Invalid type specification for function contracts_with_subtypes:extract2/0. The success typing is
+contracts_with_subtypes.erl:23:2: Invalid type specification for function contracts_with_subtypes:extract2/0.
+ The success typing is contracts_with_subtypes:extract2
() -> 'something'
+ But the spec is contracts_with_subtypes:extract2
+ () -> 'ok'
+ The return types do not overlap
contracts_with_subtypes.erl:240:2: The pattern
{'ok', 42} can never match the type
{'ok', _, string()}
@@ -129,8 +133,12 @@ contracts_with_subtypes.erl:78:16: The call contracts_with_subtypes:foo2
contracts_with_subtypes.erl:79:16: The call contracts_with_subtypes:foo3
(5) breaks the contract
(Arg1) -> Res when Arg2 :: atom(), Arg1 :: Arg2, Res :: atom()
-contracts_with_subtypes.erl:7:2: Invalid type specification for function contracts_with_subtypes:extract/0. The success typing is
+contracts_with_subtypes.erl:7:2: Invalid type specification for function contracts_with_subtypes:extract/0.
+ The success typing is contracts_with_subtypes:extract
() -> 'something'
+ But the spec is contracts_with_subtypes:extract
+ () -> 'ok'
+ The return types do not overlap
contracts_with_subtypes.erl:80:16: The call contracts_with_subtypes:foo4
(5) breaks the contract
(Type) -> Type when Type :: atom()
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/record_update b/lib/dialyzer/test/indent_SUITE_data/results/record_update
index 997b3ecb96..9ab8d478b6 100644
--- a/lib/dialyzer/test/indent_SUITE_data/results/record_update
+++ b/lib/dialyzer/test/indent_SUITE_data/results/record_update
@@ -1,3 +1,7 @@
-record_update.erl:7:2: Invalid type specification for function record_update:quux/2. The success typing is
+record_update.erl:7:2: Invalid type specification for function record_update:quux/2.
+ The success typing is record_update:quux
(#foo{bar :: atom()}, atom()) -> #foo{bar :: atom()}
+ But the spec is record_update:quux
+ (#foo{}, string()) -> #foo{}
+ They do not overlap in the 2nd argument
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/sample_behaviour b/lib/dialyzer/test/indent_SUITE_data/results/sample_behaviour
index e7ae7505f6..393d428576 100644
--- a/lib/dialyzer/test/indent_SUITE_data/results/sample_behaviour
+++ b/lib/dialyzer/test/indent_SUITE_data/results/sample_behaviour
@@ -12,12 +12,12 @@ sample_callback_wrong.erl:20:1: The inferred return type of sample_callback_5/1
(string()) has nothing in common with
'fail' | 'ok', which is the expected return type for the callback of the sample_behaviour behaviour
sample_callback_wrong.erl:20:1: The inferred type for the 1st argument of sample_callback_5/1 (
- atom()) is not a supertype of
+ atom()) has nothing in common with
1..255, which is expected type for this argument in the callback of the sample_behaviour behaviour
sample_callback_wrong.erl:22:1: The inferred return type of sample_callback_6/3
({'okk', number()}) has nothing in common with
'fail' | {'ok', 1..255}, which is the expected return type for the callback of the sample_behaviour behaviour
sample_callback_wrong.erl:22:1: The inferred type for the 3rd argument of sample_callback_6/3 (
- atom()) is not a supertype of
+ atom()) has nothing in common with
string(), which is expected type for this argument in the callback of the sample_behaviour behaviour
sample_callback_wrong.erl:4:2: Undefined callback function sample_callback_1/0 (behaviour sample_behaviour)
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/simple b/lib/dialyzer/test/indent_SUITE_data/results/simple
index f33392d5bc..7fea96c502 100644
--- a/lib/dialyzer/test/indent_SUITE_data/results/simple
+++ b/lib/dialyzer/test/indent_SUITE_data/results/simple
@@ -83,8 +83,12 @@ rec_api.erl:29:5: Matching of pattern
rec_api.erl:33:5: The attempt to match a term of type
rec_adt:r1() against the pattern
{'r1', 'a'} breaks the opacity of the term
-rec_api.erl:35:2: Invalid type specification for function rec_api:adt_t1/1. The success typing is
+rec_api.erl:35:2: Invalid type specification for function rec_api:adt_t1/1.
+ The success typing is rec_api:adt_t1
(#r1{f1 :: 'a'}) -> #r1{f1 :: 'a'}
+ But the spec is rec_api:adt_t1
+ (rec_adt:r1()) -> rec_adt:r1()
+ They do not overlap in the 1st argument, and the return types do not overlap
rec_api.erl:40:2: The specification for rec_api:adt_r1/0 has an opaque subtype
rec_adt:r1() which is violated by the success typing
() -> #r1{f1 :: 'a'}
@@ -182,14 +186,26 @@ simple1_api.erl:342:8: Guard test
simple1_api.erl:347:8: Guard test
A :: simple1_adt:b1() =:=
'true' contains an opaque term as 1st argument
-simple1_api.erl:355:2: Invalid type specification for function simple1_api:bool_adt_t6/1. The success typing is
+simple1_api.erl:355:2: Invalid type specification for function simple1_api:bool_adt_t6/1.
+ The success typing is simple1_api:bool_adt_t6
('true') -> 1
+ But the spec is simple1_api:bool_adt_t6
+ (simple1_adt:b1()) -> integer()
+ They do not overlap in the 1st argument
simple1_api.erl:365:8: Clause guard cannot succeed.
-simple1_api.erl:368:2: Invalid type specification for function simple1_api:bool_adt_t8/2. The success typing is
+simple1_api.erl:368:2: Invalid type specification for function simple1_api:bool_adt_t8/2.
+ The success typing is simple1_api:bool_adt_t8
(boolean(), boolean()) -> 1
+ But the spec is simple1_api:bool_adt_t8
+ (simple1_adt:b1(), simple1_adt:b2()) -> integer()
+ They do not overlap in the 1st and 2nd arguments
simple1_api.erl:378:8: Clause guard cannot succeed.
-simple1_api.erl:381:2: Invalid type specification for function simple1_api:bool_adt_t9/2. The success typing is
+simple1_api.erl:381:2: Invalid type specification for function simple1_api:bool_adt_t9/2.
+ The success typing is simple1_api:bool_adt_t9
('false', 'false') -> 1
+ But the spec is simple1_api:bool_adt_t9
+ (simple1_adt:b1(), simple1_adt:b2()) -> integer()
+ They do not overlap in the 1st and 2nd arguments
simple1_api.erl:407:12: The size
simple1_adt:i1() breaks the opacity of A
simple1_api.erl:418:9: The attempt to match a term of type
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl b/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl
index 0459622dc1..31613ca06f 100644
--- a/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl
+++ b/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl
@@ -32,9 +32,9 @@ callback_init(Parent) -> #state{parent = Parent}. %% Wrong return
callback_cast(#state{parent = Pid} = State, Pid, Message)
when Message =:= 'open'; Message =:= 'close' ->
- {noreply, State#state{status = Message}};
+ {reply, State#state{status = Message}}; % Wrong return
callback_cast(State, _Pid, _Message) ->
- {noreply, State}.
+ {reply, State}. % Wrong return
-spec callback_call(state(), atom(), call_message()) -> %% Wrong arg spec
{'reply', state(), call_reply()}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/map_galore.erl b/lib/dialyzer/test/indent_SUITE_data/src/map_galore.erl
index 616d4d62bf..46c4c77d98 100644
--- a/lib/dialyzer/test/indent_SUITE_data/src/map_galore.erl
+++ b/lib/dialyzer/test/indent_SUITE_data/src/map_galore.erl
@@ -2644,7 +2644,7 @@ t_dets(_Config) ->
t_tracing(_Config) ->
- dbg:stop_clear(),
+ dbg:stop(),
{ok,Tracer} = dbg:tracer(process,{fun trace_collector/2, self()}),
dbg:p(self(),c),
@@ -2697,7 +2697,7 @@ t_tracing(_Config) ->
%% Check to extra messages
timeout = getmsg(Tracer),
- dbg:stop_clear(),
+ dbg:stop(),
ok.
getmsg(_Tracer) ->
diff --git a/lib/dialyzer/test/iplt_SUITE.erl b/lib/dialyzer/test/iplt_SUITE.erl
new file mode 100644
index 0000000000..2fe88dbaa3
--- /dev/null
+++ b/lib/dialyzer/test/iplt_SUITE.erl
@@ -0,0 +1,841 @@
+-module(iplt_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
+-include_lib("dialyzer/src/dialyzer.hrl").
+-include("dialyzer_test_constants.hrl").
+
+-export([suite/0, all/0,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ beam_tests/1,
+ local_fun_same_as_callback/1,
+ letrec_rvals/1,
+ missing_plt_file/1,
+ build_xdg_plt/1,
+ mod_dep_from_behaviour/1,
+ mod_dep_from_record_definition_field_value_default_used/1,
+ mod_dep_from_record_definition_field_value_default_unused/1,
+ mod_dep_from_record_definition_field_type/1,
+ mod_dep_from_overloaded_callback/1,
+ mod_dep_from_exported_overloaded_fun_spec/1,
+ mod_dep_from_unexported_overloaded_fun_spec/1,
+ mod_dep_from_callback_constraint/1,
+ mod_dep_from_unexported_fun_spec_constraint/1,
+ mod_dep_from_exported_fun_spec_constraint/1,
+ mod_dep_from_exported_type/1,
+ mod_dep_from_callback_return/1,
+ mod_dep_from_callback_args/1,
+ mod_dep_from_unexported_opaque_type_args/1,
+ mod_dep_from_exported_opaque_type_args/1,
+ mod_dep_from_unexported_opaque_type/1,
+ mod_dep_from_exported_opaque_type/1,
+ mod_dep_from_unexported_type_args/1,
+ mod_dep_from_exported_type_args/1,
+ mod_dep_from_unexported_fun_spec_args/1,
+ mod_dep_from_exported_fun_spec_args/1,
+ mod_dep_from_unexported_fun_spec_return/1,
+ mod_dep_from_exported_fun_spec_return/1,
+ mod_dep_from_unexported_type/1,
+ adding_warning_apps_after_a_run_without_them_causes_any_new_warnings_to_be_reported/1,
+ removing_warning_apps_after_a_run_with_them_causes_any_warnings_for_the_removed_apps_not_to_be_reported/1,
+ removing_legal_warnings_with_existing_stored_warnings_in_plt_does_not_result_in_old_warnings_being_printed/1,
+ adding_legal_warnings_with_existing_stored_warnings_in_plt_results_in_new_warnings_being_printed/1,
+ reading_from_one_plt_and_writing_to_another_does_not_mutate_the_input_plt/1,
+ reading_from_and_writing_to_one_plt_mutates_it/1,
+ beams_with_no_debug_info_are_rejected/1
+ ]).
+
+suite() ->
+ [{timetrap, ?plt_timeout}].
+
+all() -> [build_xdg_plt, beam_tests,
+ local_fun_same_as_callback,
+ letrec_rvals,
+ missing_plt_file,
+ mod_dep_from_behaviour,
+ mod_dep_from_record_definition_field_value_default_used,
+ mod_dep_from_record_definition_field_value_default_unused,
+ mod_dep_from_record_definition_field_type,
+ mod_dep_from_overloaded_callback,
+ mod_dep_from_exported_overloaded_fun_spec,
+ mod_dep_from_unexported_overloaded_fun_spec,
+ mod_dep_from_callback_constraint,
+ mod_dep_from_unexported_fun_spec_constraint,
+ mod_dep_from_exported_fun_spec_constraint,
+ mod_dep_from_exported_type,
+ mod_dep_from_callback_return,
+ mod_dep_from_callback_args,
+ mod_dep_from_unexported_opaque_type_args,
+ mod_dep_from_exported_opaque_type_args,
+ mod_dep_from_unexported_opaque_type,
+ mod_dep_from_exported_opaque_type,
+ mod_dep_from_unexported_type_args,
+ mod_dep_from_exported_type_args,
+ mod_dep_from_unexported_fun_spec_args,
+ mod_dep_from_exported_fun_spec_args,
+ mod_dep_from_unexported_fun_spec_return,
+ mod_dep_from_exported_fun_spec_return,
+ mod_dep_from_unexported_type,
+ adding_warning_apps_after_a_run_without_them_causes_any_new_warnings_to_be_reported,
+ removing_warning_apps_after_a_run_with_them_causes_any_warnings_for_the_removed_apps_not_to_be_reported,
+ removing_legal_warnings_with_existing_stored_warnings_in_plt_does_not_result_in_old_warnings_being_printed,
+ adding_legal_warnings_with_existing_stored_warnings_in_plt_results_in_new_warnings_being_printed,
+ reading_from_one_plt_and_writing_to_another_does_not_mutate_the_input_plt,
+ reading_from_and_writing_to_one_plt_mutates_it,
+ beams_with_no_debug_info_are_rejected
+ ].
+
+init_per_testcase(_TestCase, Config) ->
+ % Always run from a clean default PLT, so tests aren't dependent on what
+ % happens to be in the default user cache when they start
+ _ = file:delete(dialyzer_iplt:get_default_iplt_filename()),
+ Config.
+
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+build_xdg_plt(Config) ->
+ TestHome = filename:join(?config(priv_dir, Config), ?FUNCTION_NAME),
+
+ %% We change the $HOME of the emulator to run this test
+ HomeEnv = case os:type() of
+ {win32, _} ->
+ [Drive | Path] = filename:split(TestHome),
+ [{"APPDATA", filename:join(TestHome,"AppData")},
+ {"HOMEDRIVE", Drive}, {"HOMEPATH", Path}];
+ _ ->
+ [{"HOME", TestHome}]
+ end,
+
+ {ok, Peer, Node} = ?CT_PEER(#{ env => HomeEnv }),
+
+ erpc:call(
+ Node,
+ fun() ->
+ ?assertMatch([], dialyzer:run(
+ [{analysis_type, incremental},
+ {apps, [erts]},
+ {warnings, [no_unknown]}])),
+ ?assertMatch(
+ {ok,_}, file:read_file(
+ filename:join(
+ filename:basedir(user_cache, "erlang"),
+ ".dialyzer_iplt")))
+ end),
+
+ peer:stop(Peer).
+
+beam_tests(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Plt = filename:join(PrivDir, "beam_tests.iplt"),
+ Src = <<"
+ -module(no_auto_import).
+
+ %% Copied from erl_lint_SUITE.erl, clash6
+
+ -export([size/1]).
+
+ size([]) ->
+ 0;
+ size({N,_}) ->
+ N;
+ size([_|T]) ->
+ 1+size(T).
+ ">>,
+ Opts = [no_auto_import],
+ {ok, BeamFile} = compile(Config, Src, no_auto_import, Opts),
+ [] = run_dialyzer(incremental, [BeamFile], [{output_plt, Plt}]),
+ ok.
+
+
+
+%%% If a behaviour module contains an non-exported function with the same name
+%%% as one of the behaviour's callbacks, the callback info was inadvertently
+%%% deleted from the PLT as the dialyzer_plt:delete_list/2 function was cleaning
+%%% up the callback table. This bug was reported by Brujo Benavides.
+
+local_fun_same_as_callback(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 =
+ <<"-module(bad_behaviour).
+ -callback bad() -> bad.
+ -export([publicly_bad/0]).
+
+ %% @doc This function is here just to avoid the 'unused' warning for bad/0
+ publicly_bad() -> bad().
+
+ %% @doc This function overlaps with the callback with the same name, and
+ %% that was an issue for dialyzer since it's a private function.
+ bad() -> bad.">>,
+ {ok, Beam} = compile(Config, Prog1, bad_behaviour, []),
+
+ ErlangBeam = case code:where_is_file("erlang.beam") of
+ non_existing ->
+ filename:join([code:root_dir(),
+ "erts", "preloaded", "ebin",
+ "erlang.beam"]);
+ EBeam ->
+ EBeam
+ end,
+ Plt = filename:join(PrivDir, "plt_bad_behaviour.iplt"),
+ Opts = [{from, byte_code}, {warnings, [no_unknown]}],
+ [] = dialyzer:run([{analysis_type, incremental},
+ {files, [Beam, ErlangBeam]},
+ {output_plt, Plt}] ++ Opts),
+
+ Prog2 =
+ <<"-module(bad_child).
+ -behaviour(bad_behaviour).
+
+ -export([bad/0]).
+
+ %% @doc This function incorrectly implements bad_behaviour.
+ bad() -> not_bad.">>,
+ {ok, TestBeam} = compile(Config, Prog2, bad_child, []),
+
+ [{warn_behaviour, _,
+ {callback_type_mismatch,
+ [bad_behaviour,bad,0,"'not_bad'","'bad'"]}}] =
+ dialyzer:run([{analysis_type, incremental},
+ {files, [TestBeam, Beam, ErlangBeam]},
+ {init_plt, Plt}] ++ Opts),
+ ok.
+
+
+
+letrec_rvals(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Plt = filename:join(PrivDir, "letrec_rvals.iplt"),
+ Prog = <<"
+-module(letrec_rvals).
+
+-export([demo_fun/1]).
+
+demo_fun(_Arg) ->
+ case ok of
+ _ ->
+ _Res = _Arg,
+ [ ok || _ <- [] ]
+ end,
+ _Res.
+
+handle_info() ->
+ case chids_to_audit() of
+ {ChIds, St2} ->
+ [ ChId || ChId <- ChIds ],
+ ok
+ end,
+ check_done(St2).
+
+chids_to_audit() ->
+ some_module:get_audit_list().
+
+check_done(_) ->
+ ok.
+ ">>,
+ {ok, BeamFile} = compile(Config, Prog, letrec_rvals, []),
+ _ = run_dialyzer(incremental, [BeamFile], [{output_plt, Plt}]),
+ ok.
+
+missing_plt_file(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ PltFile = filename:join(PrivDir, "missing_plt_file.iplt"),
+ Prog2 = <<"-module(missing_plt_file2).
+ t() -> foo.">>,
+ {ok, BeamFile2} = compile(Config, Prog2, missing_plt_file2, []),
+
+ true = missing_plt_file_1(Config, PltFile, BeamFile2),
+ true = missing_plt_file_2(Config, PltFile, BeamFile2),
+ ok.
+
+missing_plt_file_1(Config, PltFile, BeamFile2) ->
+ BeamFile = create1(Config, PltFile),
+ ok = file:delete(BeamFile),
+ try incr(PltFile, BeamFile2), true
+ catch throw:{dialyzer_error, _} -> false
+ end.
+
+create1(Config, PltFile) ->
+ Prog = <<"-module(missing_plt_file).
+ t() -> foo.">>,
+ {ok, BeamFile} = compile(Config, Prog, missing_plt_file, []),
+ Files = [BeamFile],
+ _ = file:delete(PltFile),
+ _ = dialyzer:run([{files,Files},
+ {init_plt, PltFile},
+ {output_plt, PltFile},
+ {analysis_type, incremental}]),
+ BeamFile.
+
+missing_plt_file_2(Config, PltFile, BeamFile2) ->
+ BeamFile = create2(Config, PltFile),
+ ok = file:delete(BeamFile),
+ try incr(PltFile, BeamFile2), true
+ catch throw:{dialyzer_error, _} -> false
+ end.
+
+create2(Config, PltFile) ->
+ Prog = <<"-module(missing_plt_file).
+ t() -> foo.">>,
+ {ok, BeamFile} = compile(Config, Prog, missing_plt_file, []),
+ Files = [BeamFile],
+ _ = file:delete(PltFile),
+ _ = dialyzer:run([{files,Files},
+ {output_plt, PltFile},
+ {analysis_type, incremental}]),
+ BeamFile.
+
+incr(PltFile, BeamFile2) ->
+ Files = [BeamFile2],
+ dialyzer:run([{files, Files},
+ {plts,[PltFile]},
+ {analysis_type, incremental}]).
+
+mod_dep_from_record_definition_field_value_default_unused(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -record(my_record,
+ { num_field = type_deps:get_num() :: number(),
+ str_field,
+ bool_field
+ }).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, []}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_record_definition_field_value_default_used(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export([f/0]).
+
+ -record(my_record,
+ { num_field = type_deps:get_num() :: number(),
+ str_field,
+ bool_field
+ }).
+
+ f() -> #my_record{str_field = \"foo\", bool_field = true}. % type_deps:get_num() used implicitly here
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_behaviour(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -behaviour(type_deps).
+ -export([quux/1]).
+
+ quux(N) -> N + 1. % Depends on behaviour module to check the callback implementation
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_record_definition_field_type(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -record(my_record,
+ { num_field = 1 :: type_deps:number_like(),
+ str_field,
+ bool_field
+ }).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_overloaded_callback(Config) ->
+ DependerSrc1 = <<"
+ -module(depender).
+
+ -callback f(string()) -> string()
+ ; (type_deps:number_like()) -> type_deps:number_like().
+ ">>,
+ DependerSrc2 = <<"
+ -module(depender).
+
+ -callback f(type_deps:number_like()) -> type_deps:number_like()
+ ; (string()) -> string().
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc1, ExpectedTypeDepsInPlt),
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc2, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_overloaded_fun_spec(Config) ->
+ DependerSrc1 = <<"
+ -module(depender).
+ -export([f/1]).
+
+ -spec f({a, atom()}) -> atom()
+ ; ({n, type_deps:number_like()}) -> type_deps:number_like().
+ f({a, X}) when is_atom(X) -> X;
+ f({n, X}) when is_number(X) -> X.
+ ">>,
+ DependerSrc2 = <<"
+ -module(depender).
+ -export([f/1]).
+
+ -spec f({n, type_deps:number_like()}) -> type_deps:number_like()
+ ; ({a, atom()}) -> atom().
+ f({n, X}) when is_number(X) -> X;
+ f({a, X}) when is_atom(X) -> X.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc1, ExpectedTypeDepsInPlt),
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc2, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_overloaded_fun_spec(Config) ->
+ DependerSrc1 = <<"
+ -module(depender).
+ -compile({nowarn_unused_function, [f/1]}).
+
+ -spec f({a, atom()}) -> atom()
+ ; ({n, type_deps:number_like()}) -> type_deps:number_like().
+ f({a, X}) when is_atom(X) -> X;
+ f({n, X}) when is_number(X) -> X.
+ ">>,
+ DependerSrc2 = <<"
+ -module(depender).
+ -compile({nowarn_unused_function, [f/1]}).
+
+ -spec f({n, type_deps:number_like()}) -> type_deps:number_like()
+ ; ({a, atom()}) -> atom().
+ f({n, X}) when is_number(X) -> X;
+ f({a, X}) when is_atom(X) -> X.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc1, ExpectedTypeDepsInPlt),
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc2, ExpectedTypeDepsInPlt).
+
+mod_dep_from_callback_constraint(Config) ->
+ DependerSrc1 = <<"
+ -module(depender).
+
+ -callback f(X) -> string() when X :: type_deps:number_like().
+ ">>,
+ DependerSrc2 = <<"
+ -module(depender).
+
+ -callback f(X :: type_deps:number_like()) -> string().
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc1, ExpectedTypeDepsInPlt),
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc2, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_fun_spec_constraint(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -compile({nowarn_unused_function, [f/1]}).
+
+ -spec f(N) -> number() when N :: type_deps:number_like().
+ f(N) -> N.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_fun_spec_constraint(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export([f/1]).
+
+ -spec f(N) -> number() when N :: type_deps:number_like().
+ f(N) -> N.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_callback_return(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -callback f(string()) -> type_deps:number_like().
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_callback_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -callback f(type_deps:number_like()) -> string().
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_opaque_type(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -opaque my_type() :: {string(), type_deps:number_like()}.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_opaque_type(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export_type([my_type/0]).
+
+ -opaque my_type() :: {string(), type_deps:number_like()}.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_opaque_type_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -type my_type() :: type_deps:my_opaque(number()).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_opaque_type_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export_type([my_type/0]).
+
+ -type my_type() :: type_deps:my_opaque(number()).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_type_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -type my_type() :: {string(), type_deps:number_like()}.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_type_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export_type([my_type/0]).
+
+ -type my_type() :: {string(), type_deps:number_like()}.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_fun_spec_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -compile({nowarn_unused_function, [f/1]}).
+
+ -spec f(type_deps:number_like()) -> number().
+ f(N) -> N.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_fun_spec_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export([f/1]).
+
+ -spec f(N :: type_deps:number_like()) -> number().
+ f(N) -> N.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_fun_spec_return(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -compile({nowarn_unused_function, [f/0]}).
+
+ -spec f() -> type_deps:number_like().
+ f() -> 1.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_fun_spec_return(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export([f/0]).
+
+ -spec f() -> type_deps:number_like().
+ f() -> 1.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_type(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -type my_type() :: type_deps:list_like(number()).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_type(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export_type([my_type/0]).
+
+ -type my_type() :: type_deps:list_like(number()).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+check_plt_deps(Config, TestName, DependerSrc, ExpectedTypeDepsInPltUnsorted) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(TestName) ++ ".iplt"),
+ {ok, DepsBeamFile} = compile(Config, type_deps, []),
+ {ok, DependerBeamFile} = compile(Config, DependerSrc, depender, []),
+ [] = run_dialyzer(incremental,
+ [DependerBeamFile, DepsBeamFile],
+ [{init_plt, PltFile}, {output_plt, PltFile}, {warnings, [no_unknown]}]),
+ {_ResPlt, #iplt_info{mod_deps = DepsByModule}} = dialyzer_iplt:plt_and_info_from_file(PltFile),
+
+ ActualTypeDepsInPlt =
+ lists:sort(dict:to_list(dict:erase(erlang, DepsByModule))),
+ ExpectedTypeDepsInPlt =
+ lists:usort(ExpectedTypeDepsInPltUnsorted),
+
+ ?assertEqual(
+ ExpectedTypeDepsInPlt,
+ ActualTypeDepsInPlt,
+ {missing, ExpectedTypeDepsInPlt -- ActualTypeDepsInPlt,
+ extra, ActualTypeDepsInPlt -- ExpectedTypeDepsInPlt}).
+
+%% Builds the named module using the source in the iplt_SUITE_data dir
+compile(Config, Module, CompileOpts) ->
+ Source = lists:concat([Module, ".erl"]),
+ PrivDir = proplists:get_value(priv_dir,Config),
+ DataDir = proplists:get_value(data_dir,Config),
+ SrcFilename = filename:join([DataDir, Source]),
+ Opts = [{outdir, PrivDir}, debug_info | CompileOpts],
+ {ok, Module} = compile:file(SrcFilename, Opts),
+ {ok, filename:join([PrivDir, lists:concat([Module, ".beam"])])}.
+
+%% Builds the named module using the literal source given
+compile(Config, Prog, Module, CompileOpts) ->
+ Source = lists:concat([Module, ".erl"]),
+ PrivDir = proplists:get_value(priv_dir,Config),
+ Filename = filename:join([PrivDir, Source]),
+ ok = file:write_file(Filename, Prog),
+ Opts = [{outdir, PrivDir}, debug_info | CompileOpts],
+ {ok, Module} = compile:file(Filename, Opts),
+ {ok, filename:join([PrivDir, lists:concat([Module, ".beam"])])}.
+
+run_dialyzer(Analysis, Files, Opts) ->
+ dialyzer:run([{analysis_type, Analysis},
+ {files, Files},
+ {from, byte_code},
+ {warnings, [no_unknown]} |
+ Opts]).
+
+
+m_src_without_warning() -> <<"
+ -module(m).
+ -export([updt/3]).
+
+ updt(X, K, V) -> X#{ K => V }.
+ ">>.
+
+m_src_with_warning() -> <<"
+ -module(m).
+ -export([updt/3]).
+
+ -spec updt(list(), term(), term()) -> list(). % Warning: Spec is wrong! Function takes a map, not a list
+ updt(X, K, V) -> X#{ K => V }.
+ ">>.
+
+adding_warning_apps_after_a_run_without_them_causes_any_new_warnings_to_be_reported(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ Opts = [{init_plt, [PltFile]}],
+ OptsNoContractWarn = [{init_plt, [PltFile]}, {warnings, [no_contracts]}],
+ {ok, Beam} = compile(Config, m_src_with_warning(), m, []),
+ ?assertEqual(
+ [],
+ run_dialyzer(incremental, [Beam], OptsNoContractWarn)),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [Beam], Opts)).
+
+removing_warning_apps_after_a_run_with_them_causes_any_warnings_for_the_removed_apps_not_to_be_reported(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ Opts = [{init_plt, [PltFile]}],
+ OptsNoContractWarn = [{init_plt, [PltFile]}, {warnings, [no_contracts]}],
+ {ok, Beam} = compile(Config, m_src_with_warning(), m, []),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [Beam], Opts)),
+ ?assertEqual(
+ [],
+ run_dialyzer(incremental, [Beam], OptsNoContractWarn)).
+
+removing_legal_warnings_with_existing_stored_warnings_in_plt_does_not_result_in_old_warnings_being_printed(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ Opts = [{init_plt, [PltFile]}],
+ {ok, BeamFileBefore} = compile(Config, m_src_with_warning(), m, []),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [BeamFileBefore], Opts)),
+ {ok, BeamFileAfter} = compile(Config, m_src_without_warning(), m, []),
+ ?assertEqual(
+ [],
+ run_dialyzer(incremental, [BeamFileAfter], Opts)).
+
+adding_legal_warnings_with_existing_stored_warnings_in_plt_results_in_new_warnings_being_printed(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ Opts = [{init_plt, [PltFile]}],
+ {ok, BeamFileAfter} = compile(Config, m_src_without_warning(), m, []),
+ ?assertEqual(
+ [],
+ run_dialyzer(incremental, [BeamFileAfter], Opts)),
+ {ok, BeamFileBefore} = compile(Config, m_src_with_warning(), m, []),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [BeamFileBefore], Opts)).
+
+reading_from_one_plt_and_writing_to_another_does_not_mutate_the_input_plt(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 =
+ <<"-module(foo).
+ -export([bar/0]).
+
+ -spec bar() -> ok.
+ bar() -> ok.">>,
+ {ok, Beam1} = compile(Config, Prog1, foo, []),
+
+ Plt1 = filename:join(PrivDir, "prog1.iplt"),
+ ?assertMatch([], dialyzer:run([{analysis_type, incremental},
+ {files, [Beam1]},
+ {init_plt, Plt1},
+ {from, byte_code},
+ {warnings, [no_unknown]}])),
+
+ % Now PLT v1 should exist
+ Plt1ContentsBefore = dialyzer_plt:get_all_contracts(dialyzer_iplt:from_file(Plt1)),
+ ?assertMatch({ok, {contract,_,_,_}},
+ maps:find({foo,bar,0}, Plt1ContentsBefore)),
+ ?assertMatch(error,
+ maps:find({foo,baz,0}, Plt1ContentsBefore)),
+
+ Prog2 =
+ <<"-module(foo).
+ -export([bar/0, baz/0]).
+
+ -spec bar() -> ok.
+ bar() -> ok.
+
+ -spec baz() -> ok.
+ baz() -> ok.">>,
+ {ok, Beam2} = compile(Config, Prog2, foo, []),
+
+ Plt2 = filename:join(PrivDir, "prog2.iplt"),
+ ?assertMatch([], dialyzer:run([{analysis_type, incremental},
+ {files, [Beam2]},
+ {init_plt, Plt1},
+ {output_plt, Plt2},
+ {from, byte_code},
+ {warnings, [no_unknown]}])),
+
+ % Now PLT v1 should be the same, but PLT v2 should have the changes in it
+ Plt1ContentsAfter = dialyzer_plt:get_all_contracts(dialyzer_iplt:from_file(Plt1)),
+ ?assertMatch({ok, {contract,_,_,_}},
+ maps:find({foo,bar,0}, Plt1ContentsAfter)),
+ ?assertMatch(error,
+ maps:find({foo,baz,0}, Plt1ContentsAfter)),
+
+ Plt2Contents = dialyzer_plt:get_all_contracts(dialyzer_iplt:from_file(Plt2)),
+ ?assertMatch({ok, {contract,_,_,_}},
+ maps:find({foo,bar,0}, Plt2Contents)),
+ ?assertMatch({ok, {contract,_,_,_}},
+ maps:find({foo,baz,0}, Plt2Contents)).
+
+reading_from_and_writing_to_one_plt_mutates_it(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 =
+ <<"-module(foo).
+ -export([bar/0]).
+
+ -spec bar() -> ok.
+ bar() -> ok.">>,
+ {ok, Beam1} = compile(Config, Prog1, foo, []),
+
+ Plt = filename:join(PrivDir, "mutate.iplt"),
+ ?assertMatch([], dialyzer:run([{analysis_type, incremental},
+ {files, [Beam1]},
+ {init_plt, Plt},
+ {from, byte_code},
+ {warnings, [no_unknown]}])),
+
+ % Now PLT should exist after running incremental mode
+ PltContentsBefore = dialyzer_plt:get_all_contracts(dialyzer_iplt:from_file(Plt)),
+ ?assertMatch({ok, {contract,_,_,_}},
+ maps:find({foo,bar,0}, PltContentsBefore)),
+ ?assertMatch(error,
+ maps:find({foo,baz,0}, PltContentsBefore)),
+
+ Prog2 =
+ <<"-module(foo).
+ -export([bar/0, baz/0]).
+
+ -spec bar() -> ok.
+ bar() -> ok.
+
+ -spec baz() -> ok.
+ baz() -> ok.">>,
+ {ok, Beam2} = compile(Config, Prog2, foo, []),
+
+ ?assertMatch([], dialyzer:run([{analysis_type, incremental},
+ {files, [Beam2]},
+ {init_plt, Plt},
+ {output_plt, Plt},
+ {from, byte_code},
+ {warnings, [no_unknown]}])),
+
+ % Now PLT should have been mutated to contain the new version of module 'foo'
+ PltContentsAfter = dialyzer_plt:get_all_contracts(dialyzer_iplt:from_file(Plt)),
+ ?assertMatch({ok, {contract,_,_,_}}, maps:find({foo,bar,0}, PltContentsAfter)),
+ ?assertMatch({ok, {contract,_,_,_}}, maps:find({foo,baz,0}, PltContentsAfter)).
+
+beams_with_no_debug_info_are_rejected(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Plt = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
+ Src = <<"
+ -module(my_list).
+
+ -export([my_size/1]).
+
+ -spec my_size(list()) -> non_neg_integer().
+ my_size([]) ->
+ 0;
+ my_size({N,_}) ->
+ N;
+ my_size([_|T]) ->
+ 1+my_size(T).
+ ">>,
+ SrcFilename = filename:join([PrivDir, "my_list.erl"]),
+ ok = file:write_file(SrcFilename, Src),
+ Opts = [{outdir, PrivDir}], % No debug info enabled
+ {ok, Module} = compile:file(SrcFilename, Opts),
+ BeamFile = filename:join([PrivDir, lists:concat([Module, ".beam"])]),
+
+ ?assertThrow(
+ {dialyzer_error, "Could not compute MD5 for .beam (debug_info error) - did you forget to set the debug_info compilation option? " ++ _},
+ run_dialyzer(incremental, [BeamFile], [{output_plt, Plt}])).
diff --git a/lib/dialyzer/test/iplt_SUITE_data/type_deps.erl b/lib/dialyzer/test/iplt_SUITE_data/type_deps.erl
new file mode 100644
index 0000000000..f6fcfc3d23
--- /dev/null
+++ b/lib/dialyzer/test/iplt_SUITE_data/type_deps.erl
@@ -0,0 +1,18 @@
+-module(type_deps).
+
+-export([func/1, get_num/0]).
+
+-export_type([number_like/0, my_opaque/1, list_like/1]).
+
+-type number_like() :: number().
+-type list_like(X) :: [X].
+-opaque my_opaque(X) :: {X,X}.
+
+-callback quux(number()) -> number().
+
+-spec func(T) -> T.
+func(X) ->
+ X + X.
+
+get_num() ->
+ 3.
diff --git a/lib/dialyzer/test/line_SUITE_data/dialyzer_options b/lib/dialyzer/test/line_SUITE_data/dialyzer_options
index bba81934f5..ddb0191d10 100644
--- a/lib/dialyzer/test/line_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/line_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{error_location, line}, {warnings, [no_return]}]}.
+{dialyzer_options, [{error_location, line}, {warnings, [no_return, no_unknown]}]}.
diff --git a/lib/dialyzer/test/map_SUITE_data/dialyzer_options b/lib/dialyzer/test/map_SUITE_data/dialyzer_options
index 1ddeb02c27..c8295d942d 100644
--- a/lib/dialyzer/test/map_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/map_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, [{indent_opt, false}]}.
+{dialyzer_options, [{indent_opt, false}, {warnings, [no_unknown]}]}.
{time_limit, 30}.
diff --git a/lib/dialyzer/test/map_SUITE_data/results/contract_violation b/lib/dialyzer/test/map_SUITE_data/results/contract_violation
index d0dd42a900..782e154100 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/contract_violation
+++ b/lib/dialyzer/test/map_SUITE_data/results/contract_violation
@@ -1,3 +1,6 @@
contract_violation.erl:12:2: The pattern #{I:=Loc} can never match the type #{}
-contract_violation.erl:16:2: Invalid type specification for function contract_violation:beam_disasm_lines/2. The success typing is ('none' | <<_:32,_:_*8>>,_) -> #{pos_integer()=>{'location',_,_}}
+contract_violation.erl:16:2: Invalid type specification for function contract_violation:beam_disasm_lines/2.
+ The success typing is contract_violation:beam_disasm_lines('none' | <<_:32,_:_*8>>,_) -> #{pos_integer()=>{'location',_,_}}
+ But the spec is contract_violation:beam_disasm_lines(binary() | 'none',module()) -> lines()
+ The return types do not overlap
diff --git a/lib/dialyzer/test/map_SUITE_data/results/opaque_key b/lib/dialyzer/test/map_SUITE_data/results/opaque_key
index b70157f1af..c3df7a5560 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/opaque_key
+++ b/lib/dialyzer/test/map_SUITE_data/results/opaque_key
@@ -1,9 +1,24 @@
-opaque_key_adt.erl:35:2: Invalid type specification for function opaque_key_adt:s2/0. The success typing is () -> #{3:='a'}
-opaque_key_adt.erl:41:2: Invalid type specification for function opaque_key_adt:s4/0. The success typing is () -> #{1:='a'}
-opaque_key_adt.erl:44:2: Invalid type specification for function opaque_key_adt:s5/0. The success typing is () -> #{2:=3}
-opaque_key_adt.erl:56:2: Invalid type specification for function opaque_key_adt:smt1/0. The success typing is () -> #{3:='a'}
-opaque_key_adt.erl:59:2: Invalid type specification for function opaque_key_adt:smt2/0. The success typing is () -> #{1:='a'}
+opaque_key_adt.erl:35:2: Invalid type specification for function opaque_key_adt:s2/0.
+ The success typing is opaque_key_adt:s2() -> #{3:='a'}
+ But the spec is opaque_key_adt:s2() -> s(atom() | 3)
+ The return types do not overlap
+opaque_key_adt.erl:41:2: Invalid type specification for function opaque_key_adt:s4/0.
+ The success typing is opaque_key_adt:s4() -> #{1:='a'}
+ But the spec is opaque_key_adt:s4() -> s(integer())
+ The return types do not overlap
+opaque_key_adt.erl:44:2: Invalid type specification for function opaque_key_adt:s5/0.
+ The success typing is opaque_key_adt:s5() -> #{2:=3}
+ But the spec is opaque_key_adt:s5() -> s(1)
+ The return types do not overlap
+opaque_key_adt.erl:56:2: Invalid type specification for function opaque_key_adt:smt1/0.
+ The success typing is opaque_key_adt:smt1() -> #{3:='a'}
+ But the spec is opaque_key_adt:smt1() -> smt(1)
+ The return types do not overlap
+opaque_key_adt.erl:59:2: Invalid type specification for function opaque_key_adt:smt2/0.
+ The success typing is opaque_key_adt:smt2() -> #{1:='a'}
+ But the spec is opaque_key_adt:smt2() -> smt(1)
+ The return types do not overlap
opaque_key_use.erl:13:5: The test opaque_key_use:t() =:= opaque_key_use:t(_) can never evaluate to 'true'
opaque_key_use.erl:24:5: Attempt to test for equality between a term of type opaque_key_adt:t(_) and a term of opaque type opaque_key_adt:t()
opaque_key_use.erl:37:1: Function adt_mm1/0 has no local return
diff --git a/lib/dialyzer/test/map_SUITE_data/src/map_galore.erl b/lib/dialyzer/test/map_SUITE_data/src/map_galore.erl
index 99eb73a5f6..dfb0e18b3a 100644
--- a/lib/dialyzer/test/map_SUITE_data/src/map_galore.erl
+++ b/lib/dialyzer/test/map_SUITE_data/src/map_galore.erl
@@ -2644,7 +2644,7 @@ t_dets(_Config) ->
t_tracing(_Config) ->
- dbg:stop_clear(),
+ dbg:stop(),
{ok,Tracer} = dbg:tracer(process,{fun trace_collector/2, self()}),
dbg:p(self(),c),
@@ -2697,7 +2697,7 @@ t_tracing(_Config) ->
%% Check to extra messages
timeout = getmsg(Tracer),
- dbg:stop_clear(),
+ dbg:stop(),
ok.
getmsg(_Tracer) ->
diff --git a/lib/dialyzer/test/nowarn_function_SUITE_data/dialyzer_options b/lib/dialyzer/test/nowarn_function_SUITE_data/dialyzer_options
new file mode 100644
index 0000000000..43bf4775fe
--- /dev/null
+++ b/lib/dialyzer/test/nowarn_function_SUITE_data/dialyzer_options
@@ -0,0 +1 @@
+{dialyzer_options, [{indent_opt, false}, {error_location, column}]}.
diff --git a/lib/dialyzer/test/nowarn_function_SUITE_data/results/warn_function b/lib/dialyzer/test/nowarn_function_SUITE_data/results/warn_function
new file mode 100644
index 0000000000..da14df5576
--- /dev/null
+++ b/lib/dialyzer/test/nowarn_function_SUITE_data/results/warn_function
@@ -0,0 +1,12 @@
+
+warn_function.erl:12:17: Guard test 1 =:= B::fun((none()) -> no_return()) can never succeed
+warn_function.erl:18:1: Function b/1 has no local return
+warn_function.erl:22:5: Guard test 2 =:= A::fun((none()) -> no_return()) can never succeed
+warn_function.erl:26:1: Function c/0 has no local return
+warn_function.erl:27:5: Record construction #r{a::'a'} violates the declared type of field a::integer()
+warn_function.erl:30:5: Unknown function nonexistent:foo/0
+warn_function.erl:5:2: Invalid type specification for function warn_function:a/1.
+ The success typing is warn_function:a(_) -> fun((_) -> none())
+ But the spec is warn_function:a(_) -> integer()
+ The return types do not overlap
+warn_function.erl:8:9: The created fun has no local return
diff --git a/lib/dialyzer/test/nowarn_function_SUITE_data/src/nowarn_function.erl b/lib/dialyzer/test/nowarn_function_SUITE_data/src/nowarn_function.erl
new file mode 100644
index 0000000000..94759b7596
--- /dev/null
+++ b/lib/dialyzer/test/nowarn_function_SUITE_data/src/nowarn_function.erl
@@ -0,0 +1,34 @@
+-module(nowarn_function).
+
+-export([a/1, b/1, c/0, d/0]).
+
+-dialyzer({nowarn_function, a/1}).
+-spec a(_) -> integer().
+
+a(_) ->
+ A = fun(_) ->
+ B = fun(_) ->
+ x = 7
+ end,
+ B = 1
+ end,
+ A.
+
+-dialyzer({nowarn_function, b/1}).
+-spec b(_) -> integer().
+
+b(_) ->
+ A = fun(_) ->
+ 1
+ end,
+ A = 2.
+
+-record(r, {a = a :: integer()}).
+
+-dialyzer({nowarn_function, c/0}).
+c() ->
+ #r{}.
+
+-dialyzer({nowarn_function, d/0}).
+d() ->
+ nonexistent:foo().
diff --git a/lib/dialyzer/test/nowarn_function_SUITE_data/src/warn_function.erl b/lib/dialyzer/test/nowarn_function_SUITE_data/src/warn_function.erl
new file mode 100644
index 0000000000..c9339ed278
--- /dev/null
+++ b/lib/dialyzer/test/nowarn_function_SUITE_data/src/warn_function.erl
@@ -0,0 +1,30 @@
+-module(warn_function).
+
+-export([a/1, b/1, c/0, d/0]).
+
+-spec a(_) -> integer().
+
+a(_) ->
+ A = fun(_) ->
+ B = fun(_) ->
+ x = 7
+ end,
+ B = 1
+ end,
+ A.
+
+-spec b(_) -> integer().
+
+b(_) ->
+ A = fun(_) ->
+ 1
+ end,
+ A = 2.
+
+-record(r, {a = a :: integer()}).
+
+c() ->
+ #r{}.
+
+d() ->
+ nonexistent:foo().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options b/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options
index 8551a47541..08977cb46c 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, [{indent_opt, false}, {warnings, [no_unused, no_return]}]}.
+{dialyzer_options, [{indent_opt, false}, {warnings, [no_unused, no_return, no_unknown]}]}.
{time_limit, 40}.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/int b/lib/dialyzer/test/opaque_SUITE_data/results/int
index 42fd95e321..504013883f 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/int
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/int
@@ -1,3 +1,9 @@
-int_adt.erl:28:2: Invalid type specification for function int_adt:add_f/2. The success typing is (number() | int_adt:int(),float()) -> number() | int_adt:int()
-int_adt.erl:32:2: Invalid type specification for function int_adt:div_f/2. The success typing is (number() | int_adt:int(),number() | int_adt:int()) -> float()
+int_adt.erl:28:2: Invalid type specification for function int_adt:add_f/2.
+ The success typing is int_adt:add_f(number() | int_adt:int(),float()) -> number() | int_adt:int()
+ But the spec is int_adt:add_f(int(),int()) -> int()
+ They do not overlap in the 2nd argument
+int_adt.erl:32:2: Invalid type specification for function int_adt:div_f/2.
+ The success typing is int_adt:div_f(number() | int_adt:int(),number() | int_adt:int()) -> float()
+ But the spec is int_adt:div_f(int(),int()) -> int()
+ The return types do not overlap
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques b/lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques
index fd702bf1d6..0130be07b7 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques
@@ -1,2 +1,5 @@
-multiple_wrong_opaques.erl:5:2: Invalid type specification for function multiple_wrong_opaques:weird/1. The success typing is ('gazonk') -> 42
+multiple_wrong_opaques.erl:5:2: Invalid type specification for function multiple_wrong_opaques:weird/1.
+ The success typing is multiple_wrong_opaques:weird('gazonk') -> 42
+ But the spec is multiple_wrong_opaques:weird(dict:dict() | gb_trees:tree()) -> 42
+ They do not overlap in the 1st argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/para b/lib/dialyzer/test/opaque_SUITE_data/results/para
index 0ba2a24996..77106c6afa 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/para
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/para
@@ -12,13 +12,22 @@ para2.erl:31:5: The test 'a' =:= 'b' can never evaluate to 'true'
para2.erl:61:5: Attempt to test for equality between a term of type para2_adt:c2() and a term of opaque type para2_adt:c1()
para2.erl:66:5: The test 'a' =:= 'b' can never evaluate to 'true'
para2.erl:88:5: The test para2:circ(_) =:= para2:circ(_,_) can never evaluate to 'true'
-para3.erl:28:2: Invalid type specification for function para3:ot2/0. The success typing is () -> 'foo'
+para3.erl:28:2: Invalid type specification for function para3:ot2/0.
+ The success typing is para3:ot2() -> 'foo'
+ But the spec is para3:ot2() -> ot1()
+ The return types do not overlap
para3.erl:36:5: The pattern {{{17}}} can never match the type {{{{{{_,_,_,_,_}}}}}}
-para3.erl:55:2: Invalid type specification for function para3:t2/0. The success typing is () -> 'foo'
+para3.erl:55:2: Invalid type specification for function para3:t2/0.
+ The success typing is para3:t2() -> 'foo'
+ But the spec is para3:t2() -> t1()
+ The return types do not overlap
para3.erl:65:5: The attempt to match a term of type {{{{{para3_adt:ot1(_,_,_,_,_)}}}}} against the pattern {{{{{17}}}}} breaks the opacity of para3_adt:ot1(_,_,_,_,_)
para3.erl:68:5: The pattern {{{{17}}}} can never match the type {{{{{para3_adt:ot1(_,_,_,_,_)}}}}}
para3.erl:74:2: The specification for para3:exp_adt/0 has an opaque subtype para3_adt:exp1(_) which is violated by the success typing () -> 3
-para4.erl:31:2: Invalid type specification for function para4:t/1. The success typing is (para4:d_all() | para4:d_tuple()) -> [{atom() | integer(),atom() | integer()}]
+para4.erl:31:2: Invalid type specification for function para4:t/1.
+ The success typing is para4:t(para4:d_all() | para4:d_tuple()) -> [{atom() | integer(),atom() | integer()}]
+ But the spec is para4:t(d_tuple()) -> [{tuple(),tuple()}]
+ The return types do not overlap
para4.erl:79:5: The test para4_adt:int(_) =:= para4_adt:int(_) can never evaluate to 'true'
para5.erl:13:5: Attempt to test for inequality between a term of type para5_adt:dd(_) and a term of opaque type para5_adt:d()
para5.erl:8:5: The test para5_adt:d() =:= para5_adt:d() can never evaluate to 'true'
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/simple b/lib/dialyzer/test/opaque_SUITE_data/results/simple
index 4959d14f15..4c211a4425 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/simple
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/simple
@@ -21,7 +21,10 @@ rec_api.erl:123:5: The attempt to match a term of type #r3{f1::10} against the p
rec_api.erl:24:18: Record construction #r1{f1::10} violates the declared type of field f1::rec_api:a()
rec_api.erl:29:5: Matching of pattern {'r1', 10} tagged with a record name violates the declared type of #r1{f1::10}
rec_api.erl:33:5: The attempt to match a term of type rec_adt:r1() against the pattern {'r1', 'a'} breaks the opacity of the term
-rec_api.erl:35:2: Invalid type specification for function rec_api:adt_t1/1. The success typing is (#r1{f1::'a'}) -> #r1{f1::'a'}
+rec_api.erl:35:2: Invalid type specification for function rec_api:adt_t1/1.
+ The success typing is rec_api:adt_t1(#r1{f1::'a'}) -> #r1{f1::'a'}
+ But the spec is rec_api:adt_t1(rec_adt:r1()) -> rec_adt:r1()
+ They do not overlap in the 1st argument, and the return types do not overlap
rec_api.erl:40:2: The specification for rec_api:adt_r1/0 has an opaque subtype rec_adt:r1() which is violated by the success typing () -> #r1{f1::'a'}
rec_api.erl:85:13: The attempt to match a term of type rec_adt:f() against the record field 'f' declared to be of type rec_api:f() breaks the opacity of the term
rec_api.erl:99:18: Record construction #r2{f1::10} violates the declared type of field f1::rec_api:a()
@@ -55,11 +58,20 @@ simple1_api.erl:319:16: Guard test not(and('true','true')) can never succeed
simple1_api.erl:337:8: Clause guard cannot succeed.
simple1_api.erl:342:8: Guard test B::simple1_adt:b2() =:= 'true' contains an opaque term as 1st argument
simple1_api.erl:347:8: Guard test A::simple1_adt:b1() =:= 'true' contains an opaque term as 1st argument
-simple1_api.erl:355:2: Invalid type specification for function simple1_api:bool_adt_t6/1. The success typing is ('true') -> 1
+simple1_api.erl:355:2: Invalid type specification for function simple1_api:bool_adt_t6/1.
+ The success typing is simple1_api:bool_adt_t6('true') -> 1
+ But the spec is simple1_api:bool_adt_t6(simple1_adt:b1()) -> integer()
+ They do not overlap in the 1st argument
simple1_api.erl:365:8: Clause guard cannot succeed.
-simple1_api.erl:368:2: Invalid type specification for function simple1_api:bool_adt_t8/2. The success typing is (boolean(),boolean()) -> 1
+simple1_api.erl:368:2: Invalid type specification for function simple1_api:bool_adt_t8/2.
+ The success typing is simple1_api:bool_adt_t8(boolean(),boolean()) -> 1
+ But the spec is simple1_api:bool_adt_t8(simple1_adt:b1(),simple1_adt:b2()) -> integer()
+ They do not overlap in the 1st and 2nd arguments
simple1_api.erl:378:8: Clause guard cannot succeed.
-simple1_api.erl:381:2: Invalid type specification for function simple1_api:bool_adt_t9/2. The success typing is ('false','false') -> 1
+simple1_api.erl:381:2: Invalid type specification for function simple1_api:bool_adt_t9/2.
+ The success typing is simple1_api:bool_adt_t9('false','false') -> 1
+ But the spec is simple1_api:bool_adt_t9(simple1_adt:b1(),simple1_adt:b2()) -> integer()
+ They do not overlap in the 1st and 2nd arguments
simple1_api.erl:407:12: The size simple1_adt:i1() breaks the opacity of A
simple1_api.erl:418:9: The attempt to match a term of type non_neg_integer() against the variable A breaks the opacity of simple1_adt:i1()
simple1_api.erl:425:9: The attempt to match a term of type non_neg_integer() against the variable B breaks the opacity of simple1_adt:i1()
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/recrec/dialyzer.hrl b/lib/dialyzer/test/opaque_SUITE_data/src/recrec/dialyzer.hrl
index c0f287893e..33b61b1519 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/recrec/dialyzer.hrl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/recrec/dialyzer.hrl
@@ -99,7 +99,7 @@
%%--------------------------------------------------------------------
-type anal_type() :: 'succ_typings' | 'plt_build'.
--type anal_type1() :: anal_type() | 'plt_add' | 'plt_check' | 'plt_remove'.
+-type anal_type1() :: anal_type() | 'plt_add' | 'plt_check' | 'plt_remove' | 'incremental'.
-type contr_constr() :: {'subtype', erl_types:erl_type(), erl_types:erl_type()}.
-type contract_pair() :: {erl_types:erl_type(), [contr_constr()]}.
-type dial_define() :: {atom(), term()}.
@@ -134,6 +134,7 @@
timing = false :: boolean() | 'debug',
timing_server = none :: dialyzer_timing:timing_server(),
callgraph_file = "" :: file:filename(),
+ mod_deps_file = "" :: file:filename(),
solvers :: [solver()]}).
-record(options, {files = [] :: [file:filename()],
@@ -154,6 +155,7 @@
output_format = formatted :: format(),
filename_opt = basename :: fopt(),
callgraph_file = "" :: file:filename(),
+ mod_deps_file = "" :: file:filename(),
check_plt = true :: boolean(),
solvers = [] :: [solver()]}).
diff --git a/lib/dialyzer/test/options1_SUITE_data/dialyzer_options b/lib/dialyzer/test/options1_SUITE_data/dialyzer_options
index ef5887a1eb..9e81465f95 100644
--- a/lib/dialyzer/test/options1_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/options1_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, [{indent_opt, false}, {include_dirs, ["my_include"]}, {defines, [{'COMPILER_VSN', 42}]}, {warnings, [no_improper_lists]}]}.
+{dialyzer_options, [{indent_opt, false}, {include_dirs, ["my_include"]}, {defines, [{'COMPILER_VSN', 42}]}, {warnings, [no_improper_lists, no_unknown]}]}.
{time_limit, 30}.
diff --git a/lib/dialyzer/test/options2_SUITE_data/dialyzer_options b/lib/dialyzer/test/options2_SUITE_data/dialyzer_options
index 6492098d01..7a0a1bec13 100644
--- a/lib/dialyzer/test/options2_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/options2_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{indent_opt, false}, {defines, [{'vsn', 4}]}, {warnings, [unknown, no_return]}]}.
+{dialyzer_options, [{indent_opt, false}, {defines, [{'vsn', 4}]}, {warnings, [no_return]}]}.
diff --git a/lib/dialyzer/test/options2_SUITE_data/results/unknown_function b/lib/dialyzer/test/options2_SUITE_data/results/unknown_function
new file mode 100644
index 0000000000..82bb96a7aa
--- /dev/null
+++ b/lib/dialyzer/test/options2_SUITE_data/results/unknown_function
@@ -0,0 +1,3 @@
+
+unknown_function.erl:10:5: Call to missing or unexported function unknown_function:function/0
+unknown_function.erl:14:5: Unknown function unknown:function/0
diff --git a/lib/dialyzer/test/options2_SUITE_data/src/unknown_function.erl b/lib/dialyzer/test/options2_SUITE_data/src/unknown_function.erl
new file mode 100644
index 0000000000..a935e4cd73
--- /dev/null
+++ b/lib/dialyzer/test/options2_SUITE_data/src/unknown_function.erl
@@ -0,0 +1,14 @@
+-module(unknown_function).
+
+-export([
+ unknown_function_on_unknown_module/0,
+ unknown_function_on_known_module/0
+ ]).
+
+-spec unknown_function_on_known_module() -> ok.
+unknown_function_on_known_module() ->
+ unknown_function:function().
+
+-spec unknown_function_on_unknown_module() -> ok.
+unknown_function_on_unknown_module() ->
+ unknown:function().
diff --git a/lib/dialyzer/test/options3_SUITE_data/dialyzer_options b/lib/dialyzer/test/options3_SUITE_data/dialyzer_options
new file mode 100644
index 0000000000..a873d18282
--- /dev/null
+++ b/lib/dialyzer/test/options3_SUITE_data/dialyzer_options
@@ -0,0 +1 @@
+{dialyzer_options, [{use_spec, false}]}.
diff --git a/lib/dialyzer/test/options3_SUITE_data/results/bad_specs b/lib/dialyzer/test/options3_SUITE_data/results/bad_specs
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/dialyzer/test/options3_SUITE_data/results/bad_specs
diff --git a/lib/dialyzer/test/options3_SUITE_data/src/bad_specs.erl b/lib/dialyzer/test/options3_SUITE_data/src/bad_specs.erl
new file mode 100644
index 0000000000..530e1281c6
--- /dev/null
+++ b/lib/dialyzer/test/options3_SUITE_data/src/bad_specs.erl
@@ -0,0 +1,8 @@
+-module(bad_specs).
+-export([f/1]).
+
+%% There will be a warning unless specs are ignored.
+-spec f(integer()) -> integer().
+f(F) when is_float(F) ->
+ F + 1.
+
diff --git a/lib/dialyzer/test/overlapping_contract_SUITE_data/dialyzer_options b/lib/dialyzer/test/overlapping_contract_SUITE_data/dialyzer_options
new file mode 100644
index 0000000000..6a36904eda
--- /dev/null
+++ b/lib/dialyzer/test/overlapping_contract_SUITE_data/dialyzer_options
@@ -0,0 +1 @@
+{dialyzer_options, [{indent_opt, false}, {warnings, [overlapping_contract]}]}.
diff --git a/lib/dialyzer/test/overlapping_contract_SUITE_data/results/overlapping_contract b/lib/dialyzer/test/overlapping_contract_SUITE_data/results/overlapping_contract
new file mode 100644
index 0000000000..ce44c33a17
--- /dev/null
+++ b/lib/dialyzer/test/overlapping_contract_SUITE_data/results/overlapping_contract
@@ -0,0 +1,2 @@
+
+overlapping_contract.erl:6:2: Overloaded contract for overlapping_contract:t1/0 has overlapping domains; such contracts cannot establish a dependency between the overloaded input and output types
diff --git a/lib/dialyzer/test/overlapping_contract_SUITE_data/src/overlapping_contract.erl b/lib/dialyzer/test/overlapping_contract_SUITE_data/src/overlapping_contract.erl
new file mode 100644
index 0000000000..57b0d7f799
--- /dev/null
+++ b/lib/dialyzer/test/overlapping_contract_SUITE_data/src/overlapping_contract.erl
@@ -0,0 +1,12 @@
+-module(overlapping_contract).
+
+-export([t1/0]).
+
+%% Should result in a overlapping_contract warning
+-spec t1() -> list();
+ () -> [atom].
+t1() ->
+ case rand:uniform(2) of
+ 1 -> [test];
+ 2 -> [2]
+ end.
diff --git a/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options b/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options
index ab6c9439ad..0e8219a34a 100644
--- a/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, [{indent_opt, false}, {defines, [{vsn, 42}]}]}.
+{dialyzer_options, [{indent_opt, false}, {defines, [{vsn, 42}]}, {warnings, [no_unknown]}]}.
{time_limit, 20}.
diff --git a/lib/dialyzer/test/small_SUITE_data/dialyzer_options b/lib/dialyzer/test/small_SUITE_data/dialyzer_options
index 43bf4775fe..c154820022 100644
--- a/lib/dialyzer/test/small_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/small_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{indent_opt, false}, {error_location, column}]}.
+{dialyzer_options, [{indent_opt, false}, {error_location, column}, {warnings, [no_unknown, overlapping_contract]}]}.
diff --git a/lib/dialyzer/test/small_SUITE_data/results/behaviour_info b/lib/dialyzer/test/small_SUITE_data/results/behaviour_info
index 6497ddae80..ea17933586 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/behaviour_info
+++ b/lib/dialyzer/test/small_SUITE_data/results/behaviour_info
@@ -1,2 +1,2 @@
-with_bad_format_status.erl:12:1: The inferred type for the 1st argument of format_status/2 ('bad_arg') is not a supertype of 'normal' | 'terminate', which is expected type for this argument in the callback of the gen_server behaviour
+with_bad_format_status.erl:12:1: The inferred type for the 1st argument of format_status/2 ('bad_arg') has nothing in common with 'normal' | 'terminate', which is expected type for this argument in the callback of the gen_server behaviour
diff --git a/lib/dialyzer/test/small_SUITE_data/results/binary_nonempty b/lib/dialyzer/test/small_SUITE_data/results/binary_nonempty
index 5275482a59..dbfaf63d6e 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/binary_nonempty
+++ b/lib/dialyzer/test/small_SUITE_data/results/binary_nonempty
@@ -1,16 +1,37 @@
binary_nonempty.erl:12:1: Function t2/0 has no local return
binary_nonempty.erl:13:8: The call binary_nonempty:t2(<<>>) breaks the contract (nonempty_binary()) -> 'foo'
-binary_nonempty.erl:15:2: Invalid type specification for function binary_nonempty:t2/1. The success typing is (<<>>) -> 'foo'
+binary_nonempty.erl:15:2: Invalid type specification for function binary_nonempty:t2/1.
+ The success typing is binary_nonempty:t2(<<>>) -> 'foo'
+ But the spec is binary_nonempty:t2(nonempty_binary()) -> 'foo'
+ They do not overlap in the 1st argument
binary_nonempty.erl:19:1: Function t3/0 has no local return
binary_nonempty.erl:20:8: The call binary_nonempty:t3(<<>>) breaks the contract (<<_:1,_:_*1>>) -> 'foo'
-binary_nonempty.erl:22:2: Invalid type specification for function binary_nonempty:t3/1. The success typing is (<<>>) -> 'foo'
+binary_nonempty.erl:22:2: Invalid type specification for function binary_nonempty:t3/1.
+ The success typing is binary_nonempty:t3(<<>>) -> 'foo'
+ But the spec is binary_nonempty:t3(<<_:1,_:_*1>>) -> 'foo'
+ They do not overlap in the 1st argument
binary_nonempty.erl:26:1: Function t4/0 has no local return
binary_nonempty.erl:27:8: The call binary_nonempty:t4(<<>>) breaks the contract (<<_:8,_:_*8>>) -> 'foo'
-binary_nonempty.erl:29:2: Invalid type specification for function binary_nonempty:t4/1. The success typing is (<<>>) -> 'foo'
-binary_nonempty.erl:33:2: Invalid type specification for function binary_nonempty:t5/1. The success typing is (<<>>) -> 'foo'
-binary_nonempty.erl:38:2: Invalid type specification for function binary_nonempty:t6/1. The success typing is (<<_:8>>) -> 'foo'
-binary_nonempty.erl:43:2: Invalid type specification for function binary_nonempty:t7/1. The success typing is (<<_:1>>) -> 'foo'
+binary_nonempty.erl:29:2: Invalid type specification for function binary_nonempty:t4/1.
+ The success typing is binary_nonempty:t4(<<>>) -> 'foo'
+ But the spec is binary_nonempty:t4(<<_:8,_:_*8>>) -> 'foo'
+ They do not overlap in the 1st argument
+binary_nonempty.erl:33:2: Invalid type specification for function binary_nonempty:t5/1.
+ The success typing is binary_nonempty:t5(<<>>) -> 'foo'
+ But the spec is binary_nonempty:t5(nonempty_binary()) -> 'foo'
+ They do not overlap in the 1st argument
+binary_nonempty.erl:38:2: Invalid type specification for function binary_nonempty:t6/1.
+ The success typing is binary_nonempty:t6(<<_:8>>) -> 'foo'
+ But the spec is binary_nonempty:t6(<<>>) -> 'foo'
+ They do not overlap in the 1st argument
+binary_nonempty.erl:43:2: Invalid type specification for function binary_nonempty:t7/1.
+ The success typing is binary_nonempty:t7(<<_:1>>) -> 'foo'
+ But the spec is binary_nonempty:t7(<<>>) -> 'foo'
+ They do not overlap in the 1st argument
binary_nonempty.erl:5:1: Function t1/0 has no local return
binary_nonempty.erl:6:8: The call binary_nonempty:t1(<<>>) breaks the contract (nonempty_bitstring()) -> 'foo'
-binary_nonempty.erl:8:2: Invalid type specification for function binary_nonempty:t1/1. The success typing is (<<>>) -> 'foo'
+binary_nonempty.erl:8:2: Invalid type specification for function binary_nonempty:t1/1.
+ The success typing is binary_nonempty:t1(<<>>) -> 'foo'
+ But the spec is binary_nonempty:t1(nonempty_bitstring()) -> 'foo'
+ They do not overlap in the 1st argument
diff --git a/lib/dialyzer/test/small_SUITE_data/results/binary_redef2 b/lib/dialyzer/test/small_SUITE_data/results/binary_redef2
index 71968b801b..19559b6dfb 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/binary_redef2
+++ b/lib/dialyzer/test/small_SUITE_data/results/binary_redef2
@@ -1,3 +1,9 @@
-binary_redef2.erl:15:2: Invalid type specification for function binary_redef2:t1/1. The success typing is (3) -> 6
-binary_redef2.erl:20:2: Invalid type specification for function binary_redef2:new/0. The success typing is () -> 3
+binary_redef2.erl:15:2: Invalid type specification for function binary_redef2:t1/1.
+ The success typing is binary_redef2:t1(3) -> 6
+ But the spec is binary_redef2:t1(nonempty_bitstring()) -> nonempty_bitstring()
+ They do not overlap in the 1st argument, and the return types do not overlap
+binary_redef2.erl:20:2: Invalid type specification for function binary_redef2:new/0.
+ The success typing is binary_redef2:new() -> 3
+ But the spec is binary_redef2:new() -> nonempty_binary()
+ The return types do not overlap
diff --git a/lib/dialyzer/test/small_SUITE_data/results/bs_segments b/lib/dialyzer/test/small_SUITE_data/results/bs_segments
new file mode 100644
index 0000000000..0c3c9a0717
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/bs_segments
@@ -0,0 +1,3 @@
+
+bs_segments.erl:6:1: Function t/1 has no local return
+bs_segments.erl:6:1: The pattern <<_>> can never match the type any()
diff --git a/lib/dialyzer/test/small_SUITE_data/results/chars b/lib/dialyzer/test/small_SUITE_data/results/chars
index ec7b468e43..a91e21d181 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/chars
+++ b/lib/dialyzer/test/small_SUITE_data/results/chars
@@ -1,4 +1,7 @@
-chars.erl:37:2: Invalid type specification for function chars:f/1. The success typing is (#{'b':=50}) -> 'ok'
+chars.erl:37:2: Invalid type specification for function chars:f/1.
+ The success typing is chars:f(#{'b':=50}) -> 'ok'
+ But the spec is chars:f(#{'a':=49,'b'=>50,'c'=>51}) -> 'ok'
+ They do not overlap in the 1st argument
chars.erl:40:11: The call chars:f(#{'b'=>50}) breaks the contract (#{'a':=49,'b'=>50,'c'=>51}) -> 'ok'
chars.erl:40:1: Function t1/0 has no local return
diff --git a/lib/dialyzer/test/small_SUITE_data/results/contract3 b/lib/dialyzer/test/small_SUITE_data/results/contract3
index b290f232a2..117801ff14 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/contract3
+++ b/lib/dialyzer/test/small_SUITE_data/results/contract3
@@ -1,3 +1,3 @@
-contract3.erl:17:2: Overloaded contract for contract3:t1/1 has overlapping domains; such contracts are currently unsupported and are simply ignored
-contract3.erl:29:2: Overloaded contract for contract3:t3/3 has overlapping domains; such contracts are currently unsupported and are simply ignored
+contract3.erl:17:2: Overloaded contract for contract3:t1/1 has overlapping domains; such contracts cannot establish a dependency between the overloaded input and output types
+contract3.erl:29:2: Overloaded contract for contract3:t3/3 has overlapping domains; such contracts cannot establish a dependency between the overloaded input and output types
diff --git a/lib/dialyzer/test/small_SUITE_data/results/contract5 b/lib/dialyzer/test/small_SUITE_data/results/contract5
index 10ea8ca362..9ffccbc19b 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/contract5
+++ b/lib/dialyzer/test/small_SUITE_data/results/contract5
@@ -1,2 +1,5 @@
-contract5.erl:13:2: Invalid type specification for function contract5:t/0. The success typing is () -> #bar{baz::'not_a_boolean'}
+contract5.erl:13:2: Invalid type specification for function contract5:t/0.
+ The success typing is contract5:t() -> #bar{baz::'not_a_boolean'}
+ But the spec is contract5:t() -> #bar{baz::boolean()}
+ The return types do not overlap
diff --git a/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes b/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes
index 44fd6056bd..8645aa9078 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes
+++ b/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes
@@ -20,7 +20,10 @@ contracts_with_subtypes.erl:218:2: The pattern 42 can never match the type {'ok'
contracts_with_subtypes.erl:235:3: The pattern 1 can never match the type string()
contracts_with_subtypes.erl:238:2: The pattern {'ok', _} can never match the type {'ok',_,string()}
contracts_with_subtypes.erl:239:2: The pattern 'alpha' can never match the type {'ok',_,string()}
-contracts_with_subtypes.erl:23:2: Invalid type specification for function contracts_with_subtypes:extract2/0. The success typing is () -> 'something'
+contracts_with_subtypes.erl:23:2: Invalid type specification for function contracts_with_subtypes:extract2/0.
+ The success typing is contracts_with_subtypes:extract2() -> 'something'
+ But the spec is contracts_with_subtypes:extract2() -> 'ok'
+ The return types do not overlap
contracts_with_subtypes.erl:240:2: The pattern {'ok', 42} can never match the type {'ok',_,string()}
contracts_with_subtypes.erl:241:2: The pattern 42 can never match the type {'ok',_,string()}
contracts_with_subtypes.erl:267:1: Function flat_ets_new_t/0 has no local return
@@ -30,7 +33,10 @@ contracts_with_subtypes.erl:295:22: The call contracts_with_subtypes:factored_et
contracts_with_subtypes.erl:77:16: The call contracts_with_subtypes:foo1(5) breaks the contract (Arg1) -> Res when Arg1 :: atom(), Res :: atom()
contracts_with_subtypes.erl:78:16: The call contracts_with_subtypes:foo2(5) breaks the contract (Arg1) -> Res when Arg1 :: Arg2, Arg2 :: atom(), Res :: atom()
contracts_with_subtypes.erl:79:16: The call contracts_with_subtypes:foo3(5) breaks the contract (Arg1) -> Res when Arg2 :: atom(), Arg1 :: Arg2, Res :: atom()
-contracts_with_subtypes.erl:7:2: Invalid type specification for function contracts_with_subtypes:extract/0. The success typing is () -> 'something'
+contracts_with_subtypes.erl:7:2: Invalid type specification for function contracts_with_subtypes:extract/0.
+ The success typing is contracts_with_subtypes:extract() -> 'something'
+ But the spec is contracts_with_subtypes:extract() -> 'ok'
+ The return types do not overlap
contracts_with_subtypes.erl:80:16: The call contracts_with_subtypes:foo4(5) breaks the contract (Type) -> Type when Type :: atom()
contracts_with_subtypes.erl:81:16: The call contracts_with_subtypes:foo5(5) breaks the contract (Type::atom()) -> Type::atom()
contracts_with_subtypes.erl:82:16: The call contracts_with_subtypes:foo6(5) breaks the contract (Type) -> Type when Type :: atom()
diff --git a/lib/dialyzer/test/small_SUITE_data/results/empty_list_infimum b/lib/dialyzer/test/small_SUITE_data/results/empty_list_infimum
index cf44c15458..b53b251a39 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/empty_list_infimum
+++ b/lib/dialyzer/test/small_SUITE_data/results/empty_list_infimum
@@ -1,2 +1,5 @@
-empty_list_infimum.erl:38:2: Invalid type specification for function empty_list_infimum:list_vhost_permissions/1. The success typing is (_) -> [[{_,_}]]
+empty_list_infimum.erl:38:2: Invalid type specification for function empty_list_infimum:list_vhost_permissions/1.
+ The success typing is empty_list_infimum:list_vhost_permissions(_) -> [[{_,_}]]
+ But the spec is empty_list_infimum:list_vhost_permissions(vhost()) -> infos()
+ The return types do not overlap
diff --git a/lib/dialyzer/test/small_SUITE_data/results/gh_7153 b/lib/dialyzer/test/small_SUITE_data/results/gh_7153
new file mode 100644
index 0000000000..c596a89f82
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/gh_7153
@@ -0,0 +1,3 @@
+
+gh_7153.erl:4:1: Function t/1 has no local return
+gh_7153.erl:5:7: Binary construction will fail since the value field X in segment X/utf8 has type '原å­'
diff --git a/lib/dialyzer/test/small_SUITE_data/results/invalid_spec_2 b/lib/dialyzer/test/small_SUITE_data/results/invalid_spec_2
index bfada119a2..a8026b787f 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/invalid_spec_2
+++ b/lib/dialyzer/test/small_SUITE_data/results/invalid_spec_2
@@ -1,2 +1,5 @@
-scala_user.erl:5:2: Invalid type specification for function scala_user:is_list/2. The success typing is (maybe_improper_list() | tuple(),_) -> boolean()
+scala_user.erl:5:2: Invalid type specification for function scala_user:is_list/2.
+ The success typing is scala_user:is_list(maybe_improper_list() | tuple(),_) -> boolean()
+ But the spec is scala_user:is_list(atom(),scala_data:data()) -> boolean()
+ They do not overlap in the 1st argument
diff --git a/lib/dialyzer/test/small_SUITE_data/results/invalid_specs b/lib/dialyzer/test/small_SUITE_data/results/invalid_specs
index 0de8f0fcb4..306be3f76a 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/invalid_specs
+++ b/lib/dialyzer/test/small_SUITE_data/results/invalid_specs
@@ -1,3 +1,6 @@
-invalid_spec1.erl:5:2: Invalid type specification for function invalid_spec1:get_plan_dirty/1. The success typing is ([string()]) -> {maybe_improper_list(),[atom()]}
+invalid_spec1.erl:5:2: Invalid type specification for function invalid_spec1:get_plan_dirty/1.
+ The success typing is invalid_spec1:get_plan_dirty([string()]) -> {maybe_improper_list(),[atom()]}
+ But the spec is invalid_spec1:get_plan_dirty([string()]) -> {{atom(),any()},[atom()]}
+ The return types do not overlap
invalid_spec2.erl:5:1: Function foo/0 has no local return
diff --git a/lib/dialyzer/test/small_SUITE_data/results/maps_sum b/lib/dialyzer/test/small_SUITE_data/results/maps_sum
index 83e7c73ef2..df2a90387b 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/maps_sum
+++ b/lib/dialyzer/test/small_SUITE_data/results/maps_sum
@@ -1,4 +1,7 @@
-maps_sum.erl:15:2: Invalid type specification for function maps_sum:wrong1/1. The success typing is (maps:iterator(_,_) | map()) -> any()
+maps_sum.erl:15:2: Invalid type specification for function maps_sum:wrong1/1.
+ The success typing is maps_sum:wrong1(maps:iterator(_,_) | map()) -> any()
+ But the spec is maps_sum:wrong1([{atom(),term()}]) -> integer()
+ They do not overlap in the 1st argument
maps_sum.erl:26:1: Function wrong2/1 has no local return
maps_sum.erl:27:17: The call lists:foldl(fun((_,_,_) -> any()),0,Data::any()) will never return since it differs in the 1st argument from the success typing arguments: (fun((_,_) -> any()),any(),[any()])
diff --git a/lib/dialyzer/test/small_SUITE_data/results/predef b/lib/dialyzer/test/small_SUITE_data/results/predef
index f57f78d59e..e89dd8db87 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/predef
+++ b/lib/dialyzer/test/small_SUITE_data/results/predef
@@ -1,8 +1,29 @@
-predef.erl:19:2: Invalid type specification for function predef:array/1. The success typing is (array:array(_)) -> array:array(_)
-predef.erl:24:2: Invalid type specification for function predef:dict/1. The success typing is (dict:dict(_,_)) -> dict:dict(_,_)
-predef.erl:29:2: Invalid type specification for function predef:digraph/1. The success typing is (digraph:graph()) -> [any()]
-predef.erl:39:2: Invalid type specification for function predef:gb_set/1. The success typing is (gb_sets:set(_)) -> gb_sets:set(_)
-predef.erl:44:2: Invalid type specification for function predef:gb_tree/1. The success typing is (gb_trees:tree(_,_)) -> gb_trees:tree(_,_)
-predef.erl:49:2: Invalid type specification for function predef:queue/1. The success typing is (queue:queue(_)) -> queue:queue(_)
-predef.erl:54:2: Invalid type specification for function predef:set/1. The success typing is (sets:set(_)) -> sets:set(_)
+predef.erl:19:2: Invalid type specification for function predef:array/1.
+ The success typing is predef:array(array:array(_)) -> array:array(_)
+ But the spec is predef:array(array()) -> array:array()
+ They do not overlap in the 1st argument
+predef.erl:24:2: Invalid type specification for function predef:dict/1.
+ The success typing is predef:dict(dict:dict(_,_)) -> dict:dict(_,_)
+ But the spec is predef:dict(dict()) -> dict:dict()
+ They do not overlap in the 1st argument
+predef.erl:29:2: Invalid type specification for function predef:digraph/1.
+ The success typing is predef:digraph(digraph:graph()) -> [any()]
+ But the spec is predef:digraph(digraph()) -> [digraph:edge()]
+ They do not overlap in the 1st argument
+predef.erl:39:2: Invalid type specification for function predef:gb_set/1.
+ The success typing is predef:gb_set(gb_sets:set(_)) -> gb_sets:set(_)
+ But the spec is predef:gb_set(gb_set()) -> gb_sets:set()
+ They do not overlap in the 1st argument
+predef.erl:44:2: Invalid type specification for function predef:gb_tree/1.
+ The success typing is predef:gb_tree(gb_trees:tree(_,_)) -> gb_trees:tree(_,_)
+ But the spec is predef:gb_tree(gb_tree()) -> gb_trees:tree()
+ They do not overlap in the 1st argument
+predef.erl:49:2: Invalid type specification for function predef:queue/1.
+ The success typing is predef:queue(queue:queue(_)) -> queue:queue(_)
+ But the spec is predef:queue(queue()) -> queue:queue()
+ They do not overlap in the 1st argument
+predef.erl:54:2: Invalid type specification for function predef:set/1.
+ The success typing is predef:set(sets:set(_)) -> sets:set(_)
+ But the spec is predef:set(set()) -> sets:set()
+ They do not overlap in the 1st argument
diff --git a/lib/dialyzer/test/small_SUITE_data/results/record_update b/lib/dialyzer/test/small_SUITE_data/results/record_update
index b61d2e66b3..d2747d0440 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/record_update
+++ b/lib/dialyzer/test/small_SUITE_data/results/record_update
@@ -1,2 +1,5 @@
-record_update.erl:7:2: Invalid type specification for function record_update:quux/2. The success typing is (#foo{bar::atom()},atom()) -> #foo{bar::atom()}
+record_update.erl:7:2: Invalid type specification for function record_update:quux/2.
+ The success typing is record_update:quux(#foo{bar::atom()},atom()) -> #foo{bar::atom()}
+ But the spec is record_update:quux(#foo{},string()) -> #foo{}
+ They do not overlap in the 2nd argument
diff --git a/lib/dialyzer/test/small_SUITE_data/results/redefine_builtin_type b/lib/dialyzer/test/small_SUITE_data/results/redefine_builtin_type
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/redefine_builtin_type
diff --git a/lib/dialyzer/test/small_SUITE_data/results/redefine_builtins b/lib/dialyzer/test/small_SUITE_data/results/redefine_builtins
new file mode 100644
index 0000000000..c7da0cf72a
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/redefine_builtins
@@ -0,0 +1,5 @@
+
+a.erl:4:2: Invalid type specification for function a:vi/1.
+ The success typing is a:vi(integer()) -> 'ok'
+ But the spec is a:vi(b:integer()) -> 'ok'
+ They do not overlap in the 1st argument
diff --git a/lib/dialyzer/test/small_SUITE_data/results/stack b/lib/dialyzer/test/small_SUITE_data/results/stack
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/stack
diff --git a/lib/dialyzer/test/small_SUITE_data/results/tuple_set_crash b/lib/dialyzer/test/small_SUITE_data/results/tuple_set_crash
index 4d72467a06..9e415b469b 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/tuple_set_crash
+++ b/lib/dialyzer/test/small_SUITE_data/results/tuple_set_crash
@@ -1,12 +1,30 @@
-tuple_set_crash.erl:103:2: Invalid type specification for function tuple_set_crash:parse_device_properties/1. The success typing is (<<_:48>>) -> [{'controller_description',binary()} | {'controller_name',binary()} | {'controller_status',byte()} | {'fw_version',<<_:24>>}]
-tuple_set_crash.erl:123:2: Invalid type specification for function tuple_set_crash:parse_video_target_info/1. The success typing is (<<_:48>>) -> [{'status',byte()} | {'target_id',non_neg_integer()},...]
-tuple_set_crash.erl:127:2: Invalid type specification for function tuple_set_crash:parse_audio_target_info/1. The success typing is (<<_:48>>) -> [{'master_volume',char()} | {'status',byte()} | {'target_id',non_neg_integer()},...]
-tuple_set_crash.erl:138:2: Invalid type specification for function tuple_set_crash:parse_av_device_info/1. The success typing is (<<_:48>>) -> [{'address',byte()} | {'device_id',non_neg_integer()} | {'model',binary()} | {'status',byte()},...]
+tuple_set_crash.erl:103:2: Invalid type specification for function tuple_set_crash:parse_device_properties/1.
+ The success typing is tuple_set_crash:parse_device_properties(<<_:48>>) -> [{'controller_description',binary()} | {'controller_name',binary()} | {'controller_status',byte()} | {'fw_version',<<_:24>>}]
+ But the spec is tuple_set_crash:parse_device_properties(binary()) -> config_change()
+ The return types do not overlap
+tuple_set_crash.erl:123:2: Invalid type specification for function tuple_set_crash:parse_video_target_info/1.
+ The success typing is tuple_set_crash:parse_video_target_info(<<_:48>>) -> [{'status',byte()} | {'target_id',non_neg_integer()},...]
+ But the spec is tuple_set_crash:parse_video_target_info(binary()) -> config_change()
+ The return types do not overlap
+tuple_set_crash.erl:127:2: Invalid type specification for function tuple_set_crash:parse_audio_target_info/1.
+ The success typing is tuple_set_crash:parse_audio_target_info(<<_:48>>) -> [{'master_volume',char()} | {'status',byte()} | {'target_id',non_neg_integer()},...]
+ But the spec is tuple_set_crash:parse_audio_target_info(binary()) -> [config_change()]
+ The return types do not overlap
+tuple_set_crash.erl:138:2: Invalid type specification for function tuple_set_crash:parse_av_device_info/1.
+ The success typing is tuple_set_crash:parse_av_device_info(<<_:48>>) -> [{'address',byte()} | {'device_id',non_neg_integer()} | {'model',binary()} | {'status',byte()},...]
+ But the spec is tuple_set_crash:parse_av_device_info(binary()) -> [config_change()]
+ The return types do not overlap
tuple_set_crash.erl:141:25: The pattern <<TargetId:32/integer-little-unit:1,Rest1/binary>> can never match the type <<_:8>>
-tuple_set_crash.erl:155:2: Invalid type specification for function tuple_set_crash:parse_video_output_info/1. The success typing is (<<_:48>>) -> [{'audio_volume',char()} | {'display_type',binary()} | {'output_id',non_neg_integer()},...]
+tuple_set_crash.erl:155:2: Invalid type specification for function tuple_set_crash:parse_video_output_info/1.
+ The success typing is tuple_set_crash:parse_video_output_info(<<_:48>>) -> [{'audio_volume',char()} | {'display_type',binary()} | {'output_id',non_neg_integer()},...]
+ But the spec is tuple_set_crash:parse_video_output_info(binary()) -> [config_change()]
+ The return types do not overlap
tuple_set_crash.erl:158:25: The pattern <<DeviceId:32/integer-little-unit:1,Rest1/binary>> can never match the type <<_:8>>
-tuple_set_crash.erl:171:2: Invalid type specification for function tuple_set_crash:parse_audio_output_info/1. The success typing is (<<_:48>>) -> [{'output_id',non_neg_integer()},...]
+tuple_set_crash.erl:171:2: Invalid type specification for function tuple_set_crash:parse_audio_output_info/1.
+ The success typing is tuple_set_crash:parse_audio_output_info(<<_:48>>) -> [{'output_id',non_neg_integer()},...]
+ But the spec is tuple_set_crash:parse_audio_output_info(binary()) -> [config_change()]
+ The return types do not overlap
tuple_set_crash.erl:174:25: The pattern <<DeviceId:32/integer-little-unit:1,Rest1/binary>> can never match the type <<_:8>>
tuple_set_crash.erl:177:25: The pattern <<AudioVolume:16/integer-little-unit:1,Rest2/binary>> can never match the type <<_:8>>
tuple_set_crash.erl:180:25: The pattern <<Delay:16/integer-little-unit:1,_Padding/binary>> can never match the type <<_:8>>
diff --git a/lib/dialyzer/test/small_SUITE_data/results/types_arity b/lib/dialyzer/test/small_SUITE_data/results/types_arity
index fae7455996..9842bad61b 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/types_arity
+++ b/lib/dialyzer/test/small_SUITE_data/results/types_arity
@@ -1,2 +1,5 @@
-types_arity.erl:16:2: Invalid type specification for function types_arity:test2/0. The success typing is () -> {'node','a','nil','nil'}
+types_arity.erl:16:2: Invalid type specification for function types_arity:test2/0.
+ The success typing is types_arity:test2() -> {'node','a','nil','nil'}
+ But the spec is types_arity:test2() -> tree()
+ The return types do not overlap
diff --git a/lib/dialyzer/test/small_SUITE_data/src/bs_segments.erl b/lib/dialyzer/test/small_SUITE_data/src/bs_segments.erl
new file mode 100644
index 0000000000..b1b8a2e866
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/bs_segments.erl
@@ -0,0 +1,7 @@
+-module(bs_segments).
+
+-export([t/1]).
+
+%% GH-7138: bogus segment sizes crashed the analysis.
+t(<<_:undefined>>) ->
+ ok.
diff --git a/lib/dialyzer/test/small_SUITE_data/src/gh_7153.erl b/lib/dialyzer/test/small_SUITE_data/src/gh_7153.erl
new file mode 100644
index 0000000000..ef2ef3a25b
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/gh_7153.erl
@@ -0,0 +1,5 @@
+-module(gh_7153).
+-export([t/1]).
+
+t(X = '原å­') ->
+ <<X/utf8>>.
diff --git a/lib/dialyzer/test/small_SUITE_data/src/list_none.erl b/lib/dialyzer/test/small_SUITE_data/src/list_none.erl
new file mode 100644
index 0000000000..7fb40133e7
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/list_none.erl
@@ -0,0 +1,5 @@
+-module(list_none).
+-export([new/0]).
+
+-spec new() -> list(none()).
+new() -> [].
diff --git a/lib/dialyzer/test/small_SUITE_data/src/redefine_builtin_type.erl b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtin_type.erl
new file mode 100644
index 0000000000..9cf80cafb6
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtin_type.erl
@@ -0,0 +1,24 @@
+-module(redefine_builtin_type).
+-export([lookup/2, verify_mfa/1, verify_pid/1]).
+
+-type map() :: {atom(), erlang:map()}.
+
+-spec lookup(atom(), map()) -> {'ok', term()} | 'error'.
+
+lookup(Key, {Key, Map}) when is_atom(Key), is_map(Map) ->
+ {ok, Map};
+lookup(Key1, {Key2, Map}) when is_atom(Key1), is_atom(Key2), is_map(Map) ->
+ error.
+
+%% Type `mfa()` depends on `erlang::module()`. Make sure that `mfa()`
+%% does not attempt to use our local definition of `module()`.
+
+-type module() :: pid().
+
+-spec verify_mfa(mfa()) -> 'ok'.
+verify_mfa({M, F, A}) when is_atom(M), is_atom(F), is_integer(A) ->
+ ok.
+
+-spec verify_pid(module()) -> 'ok'.
+verify_pid(Pid) when is_pid(Pid) ->
+ ok.
diff --git a/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/a.erl b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/a.erl
new file mode 100644
index 0000000000..274906d554
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/a.erl
@@ -0,0 +1,14 @@
+-module(a).
+-export([vi/1, sum/2, vc/1]).
+
+-spec vi(b:integer()) -> 'ok'.
+vi(I) when is_integer(I) ->
+ ok.
+
+-spec sum(b:integer(), integer()) -> integer().
+sum([A], B) ->
+ A + B.
+
+-spec vc(b:collection()) -> 'ok'.
+vc({Int, List}) when length(List) =:= Int ->
+ ok.
diff --git a/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/b.erl b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/b.erl
new file mode 100644
index 0000000000..c11f591036
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/b.erl
@@ -0,0 +1,6 @@
+-module(b).
+-export_type([integer/0, collection/0]).
+
+-type integer() :: [integer()].
+
+-type collection() :: {erlang:integer(), integer()}.
diff --git a/lib/dialyzer/test/small_SUITE_data/src/stack.erl b/lib/dialyzer/test/small_SUITE_data/src/stack.erl
new file mode 100644
index 0000000000..080ce7e3b9
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/stack.erl
@@ -0,0 +1,15 @@
+-module(stack).
+
+-export([new/0, push/2, pop/1]).
+-export_type([stack/1]).
+
+-opaque stack(A) :: [A].
+
+-spec new() -> stack(none()).
+new() -> [].
+
+-spec push(stack(A), A) -> stack(A).
+push(T, H) -> [H | T].
+
+-spec pop(stack(A)) -> {A, stack(A)}.
+pop([H | T]) -> {H, T}.
diff --git a/lib/dialyzer/test/typer_SUITE.erl b/lib/dialyzer/test/typer_SUITE.erl
index da5b961643..d566bd779d 100644
--- a/lib/dialyzer/test/typer_SUITE.erl
+++ b/lib/dialyzer/test/typer_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,37 +19,78 @@
%%
-module(typer_SUITE).
--export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
- init_per_group/2,end_per_group/2,
- smoke/1]).
+-export([all/0,suite/0,
+ smoke/1,
+ smoke_incremental_plt/1,
+ gh_6296_no_spec_flag_does_not_break_records/1,
+ contract_violation/1]).
-include_lib("common_test/include/ct.hrl").
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
- [smoke].
+ [smoke,
+ smoke_incremental_plt,
+ gh_6296_no_spec_flag_does_not_break_records,
+ contract_violation].
-groups() ->
- [].
-
-init_per_suite(Config) ->
+smoke(Config) ->
OutDir = proplists:get_value(priv_dir, Config),
case dialyzer_common:check_plt(OutDir) of
fail -> {skip, "Plt creation/check failed."};
- ok -> [{dialyzer_options, []}|Config]
+ ok ->
+ Code = <<"-module(typer_test_module).
+ -compile([export_all,nowarn_export_all]).
+ a(L) ->
+ L ++ [1,2,3].">>,
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Src = filename:join(PrivDir, "typer_test_module.erl"),
+ ok = file:write_file(Src, Code),
+ Args = "--plt " ++ PrivDir ++ "dialyzer_plt",
+ Res = ["^$",
+ "^%% File:",
+ "^%% ----",
+ "^-spec a",
+ "^_OK_"],
+ run(Config, Args, Src, Res),
+ ok
end.
-end_per_suite(_Config) ->
- ok.
+gh_6296_no_spec_flag_does_not_break_records(Config) ->
+ Code = <<"-module(gh_6296).
+ -export([record_pattern/1]).
-init_per_group(_GroupName, Config) ->
- Config.
+ -record(my_rec, {is_foo :: boolean(),
+ bar :: non_neg_integer()}).
-end_per_group(_GroupName, Config) ->
- Config.
+ -spec record_pattern(#my_rec{}) -> atom().
+ record_pattern(#my_rec{is_foo = IsFoo}) ->
+ IsFoo.
-smoke(Config) ->
+ some_other_function_to_trigger_the_issue(undefined) -> ok.">>,
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Src = filename:join(PrivDir, "gh_6296.erl"),
+ ok = file:write_file(Src, Code),
+ {ok, Beam} = compile(Config, Code, gh_6296, []),
+ Plt = PrivDir ++ "dialyzer_iplt",
+ _ = dialyzer:run([{analysis_type, incremental},
+ {files, [Beam]},
+ {apps, [stdlib, kernel, erts]},
+ {from, byte_code},
+ {init_plt, Plt},
+ {output_plt, Plt}]),
+ Args = io_lib:format("--no_spec --show --plt ~ts", [Plt]),
+ Res = ["^$",
+ "^%% File:",
+ "^%% ----",
+ "^-spec record_pattern",
+ "^-spec some_other_function_to_trigger_the_issue",
+ "^_OK_"],
+ run(Config, Args, Src, Res),
+ ok.
+
+smoke_incremental_plt(Config) ->
Code = <<"-module(typer_test_module).
-compile([export_all,nowarn_export_all]).
a(L) ->
@@ -57,7 +98,15 @@ smoke(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
Src = filename:join(PrivDir, "typer_test_module.erl"),
ok = file:write_file(Src, Code),
- Args = "--plt " ++ PrivDir ++ "dialyzer_plt",
+ {ok, Beam} = compile(Config, Code, typer_test_module, []),
+ Plt = PrivDir ++ "dialyzer_iplt",
+ _ = dialyzer:run([{analysis_type, incremental},
+ {files, [Beam]},
+ {apps, [stdlib, kernel, erts]},
+ {from, byte_code},
+ {init_plt, Plt},
+ {output_plt, Plt}]),
+ Args = "--plt " ++ Plt,
Res = ["^$",
"^%% File:",
"^%% ----",
@@ -66,6 +115,38 @@ smoke(Config) ->
run(Config, Args, Src, Res),
ok.
+contract_violation(Config) ->
+ OutDir = proplists:get_value(priv_dir, Config),
+ case dialyzer_common:check_plt(OutDir) of
+ fail ->
+ {skip, "Plt creation/check failed."};
+ ok ->
+ Code = <<"-module(typer_test_module).
+ -export([foo/1]).
+ -spec foo(boolean()) -> string().
+ foo(N) ->
+ integer_to_list(N).">>,
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Src = filename:join(PrivDir, "typer_test_module.erl"),
+ ok = file:write_file(Src, Code),
+ Args = "--plt " ++ PrivDir ++ "dialyzer_plt",
+ Res = ["^typer: Error in contract of function typer_test_module:foo/1",
+ "^\t The contract is: \\(boolean\\(\\)\\) -> string\\(\\)",
+ "^\t but the inferred signature is: \\(integer\\(\\)\\) -> string\\(\\)",
+ "_ERROR_"],
+ run(Config, Args, Src, Res),
+ ok
+ end.
+
+compile(Config, Prog, Module, CompileOpts) ->
+ Source = lists:concat([Module, ".erl"]),
+ PrivDir = proplists:get_value(priv_dir,Config),
+ Filename = filename:join([PrivDir, Source]),
+ ok = file:write_file(Filename, Prog),
+ Opts = [{outdir, PrivDir}, debug_info | CompileOpts],
+ {ok, Module} = compile:file(Filename, Opts),
+ {ok, filename:join([PrivDir, lists:concat([Module, ".beam"])])}.
+
typer() ->
case os:find_executable("typer") of
false ->
diff --git a/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options b/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options
index 1a9734deb2..99336b96d6 100644
--- a/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{indent_opt, false}, {warnings, [underspecs]}]}.
+{dialyzer_options, [{indent_opt, false}, {warnings, [no_unknown, underspecs]}]}.
diff --git a/lib/dialyzer/test/user_SUITE_data/dialyzer_options b/lib/dialyzer/test/user_SUITE_data/dialyzer_options
index 0a944966f0..5a30063ae7 100644
--- a/lib/dialyzer/test/user_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/user_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, [{indent_opt, false}]}.
-{time_limit, 3}. \ No newline at end of file
+{dialyzer_options, [{indent_opt, false}, {warnings, [no_unknown]}]}.
+{time_limit, 3}.
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 7f6baf666a..f3f677295b 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -113,7 +113,7 @@ enc(_, Opts, #diameter_packet{msg = [#diameter_header{} = Hdr | As]}
try encode_avps(As, Opts) of
Avps ->
Bin = list_to_binary(Avps),
- Len = 20 + size(Bin),
+ Len = 20 + byte_size(Bin),
#diameter_header{version = Vsn,
is_request = R,
@@ -161,7 +161,7 @@ enc(Mod, Opts, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) ->
try encode_avps(Mod, MsgName, Values, Opts) of
Avps ->
Bin = list_to_binary(Avps),
- Len = 20 + size(Bin),
+ Len = 20 + byte_size(Bin),
Hdr = Hdr0#diameter_header{length = Len,
cmd_code = Code,
@@ -628,7 +628,7 @@ pack_avp(#diameter_avp{code = undefined, data = B}, _)
%% from the length header for this reason, to avoid creating a sub
%% binary for no useful reason.
Len = header_length(B),
- Sz = min(5, size(B)),
+ Sz = min(5, byte_size(B)),
<<B:Sz/binary, 0:(5-Sz)/unit:8, Len:24, 0:(Len-8)/unit:8>>;
%% Ignoring errors in Failed-AVP or during a relay encode.
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 495e57e456..733e9105f4 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -291,21 +291,14 @@ init([]) ->
%% Child start as a consequence of add_transport.
init({SvcName, Type, Opts}) ->
- Res = try
- add(SvcName, Type, Opts)
- catch
- ?FAILURE(Reason) -> {error, Reason}
- end,
- proc_lib:init_ack({ok, self(), Res}),
- loop(Res).
-
-%% loop/1
-
-loop({ok, _}) ->
- gen_server:enter_loop(?MODULE, [], #state{role = transport});
-
-loop({error, _}) ->
- ok. %% die
+ try add(SvcName, Type, Opts) of
+ Ref ->
+ proc_lib:init_ack({ok, self(), Ref}),
+ gen_server:enter_loop(?MODULE, [], #state{role = transport})
+ catch
+ ?FAILURE(Reason) ->
+ proc_lib:init_fail({error, Reason}, {exit, normal}) %% die
+ end.
%%% ----------------------------------------------------------
%%% # handle_call/2
@@ -443,8 +436,12 @@ sync({stop_service, SvcName}) ->
%% This is to provide a way for processes to to be notified when the
%% configuration is removed (diameter_reg:subscribe/2).
sync({add, SvcName, Type, Opts}) ->
- {ok, _Pid, Res} = diameter_config_sup:start_child({SvcName, Type, Opts}),
- Res;
+ case diameter_config_sup:start_child({SvcName, Type, Opts}) of
+ {ok, _Pid, Ref} ->
+ {ok, Ref};
+ {error, _} = Res ->
+ Res
+ end;
sync({remove, SvcName, Pred}) ->
Recs = select([{#transport{service = '$1', _ = '_'},
@@ -545,16 +542,12 @@ add(SvcName, Type, Opts0) ->
%% The call to the service returns error if the service isn't
%% started yet, which is harmless. The transport will be started
%% when the service is in that case.
- case start_transport(SvcName, T) of
- ok ->
- insert(#transport{service = SvcName,
- ref = Ref,
- type = Type,
- options = Opts}),
- {ok, Ref};
- {error, _} = No ->
- No
- end.
+ start_transport(SvcName, T),
+ insert(#transport{service = SvcName,
+ ref = Ref,
+ type = Type,
+ options = Opts}),
+ Ref.
transport_opts(Opts) ->
[setopt(transport, T) || T <- Opts].
@@ -751,8 +744,8 @@ start_transport(SvcName, T) ->
ok;
{error, no_service} ->
ok;
- {error, _} = No ->
- No
+ {error, Reason} ->
+ ?THROW(Reason)
end.
%% remove/2
diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index edd9d5a4ce..8bb98ff0f8 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -271,9 +271,9 @@ ipaddr(Addr) ->
%% Already a tuple: ensure non-negative integers of the right size.
ip(T)
- when size(T) == 4;
- size(T) == 8 ->
- Bs = 2*size(T),
+ when tuple_size(T) == 4;
+ tuple_size(T) == 8 ->
+ Bs = 2*tuple_size(T),
[] = lists:filter(fun(N) when 0 =< N -> 0 < N bsr Bs end,
tuple_to_list(T)),
T;
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index b86dcaf923..8fd582329b 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -696,16 +696,16 @@ recv(#diameter_header{length = Len} = H, Bin, Msg, #state{length_errors = E,
dictionary = Dict0}
= S)
when E == handle;
- 0 == Len rem 4, bit_size(Bin) == 8*Len, size(Bin) =< M ->
+ is_binary(Bin), 0 == Len rem 4, bit_size(Bin) == 8*Len, byte_size(Bin) =< M ->
recv1(diameter_codec:msg_name(Dict0, H), H, Msg, S);
recv(H, Bin, _, #state{incoming_maxlen = M})
- when M < size(Bin) ->
- invalid(false, incoming_maxlen_exceeded, {size(Bin), H}),
+ when is_binary(Bin), M < byte_size(Bin) ->
+ invalid(false, incoming_maxlen_exceeded, {byte_size(Bin), H}),
H;
-recv(H, Bin, _, #state{length_errors = E}) ->
- T = {size(Bin), bit_size(Bin) rem 8, H},
+recv(H, Bin, _, #state{length_errors = E}) when is_binary(Bin) ->
+ T = {byte_size(Bin), bit_size(Bin) rem 8, H},
invalid(E, message_length_mismatch, T),
H.
diff --git a/lib/diameter/src/base/diameter_types.erl b/lib/diameter/src/base/diameter_types.erl
index 79312dfe13..ccf335b798 100644
--- a/lib/diameter/src/base/diameter_types.erl
+++ b/lib/diameter/src/base/diameter_types.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -243,8 +243,8 @@
<<0:48>>;
'Address'(decode, <<A:16, B/binary>>, _)
- when 1 == A, 4 == size(B);
- 2 == A, 16 == size(B) ->
+ when 1 == A, 4 == byte_size(B);
+ 2 == A, 16 == byte_size(B) ->
list_to_tuple([N || <<N:A/unit:8>> <= B]);
%% Bytes for non-IP address types are left for the user to interpret.
diff --git a/lib/diameter/src/diameter.app.src b/lib/diameter/src/diameter.app.src
index 27f248e070..b745200e08 100644
--- a/lib/diameter/src/diameter.app.src
+++ b/lib/diameter/src/diameter.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@
{mod, {diameter_app, []}},
{runtime_dependencies, [
"erts-10.0",
- "stdlib-3.0",
+ "stdlib-@OTP-18490@",
"kernel-3.2",
"ssl-9.0"
%, "syntax-tools-1.6.18"
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index 97e95c94b6..4d6fedef61 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -63,25 +63,15 @@
{"2.1.5", [{restart_application, diameter}]}, %% 21.0
{"2.1.6", [{restart_application, diameter}]}, %% 21.1
{"2.2", [{restart_application, diameter}]}, %% 21.3
- {"2.2.1", [{load_module, diameter}, %% 21.3.5
- {load_module, diameter_codec},
- {update, diameter_config},
- {update, diameter_dist},
- {update, diameter_peer_fsm},
- {update, diameter_service},
- {load_module, diameter_traffic},
- {load_module, diameter_types},
- {update, diameter_tcp},
- {update, diameter_sctp}]},
- {"2.2.2", [{load_module, diameter}, %% 22.2.8
- {load_module, diameter_types},
- {update, diameter_sctp}]},
- {"2.2.3", [{load_module, diameter_types},
- {update, diameter_sctp}]}, %% 22.3
- {"2.2.4", [{load_module, diameter_types}]}, %% 23.3.4
- {"2.2.5", [{load_module, diameter_types}]}, %% 24.3
- {"2.2.6", [{restart_application, diameter}]}
- ],
+ {"2.2.1", [{restart_application, diameter}]},
+ {"2.2.2", [{restart_application, diameter}]}, %% 22.2.8
+ {"2.2.3", [{restart_application, diameter}]}, %% 22.3
+ {"2.2.4", [{restart_application, diameter}]}, %% 23.3.4
+ {"2.2.5", [{restart_application, diameter}]}, %% 24.3
+ {"2.2.6", [{restart_application, diameter}]}, %% 25.0
+ {"2.2.7", [{restart_application, diameter}]}, %% 25.1
+ {"2.3", [{restart_application, diameter}]}
+],
[
{"0.9", [{restart_application, diameter}]},
{"0.10", [{restart_application, diameter}]},
@@ -125,23 +115,13 @@
{"2.1.5", [{restart_application, diameter}]},
{"2.1.6", [{restart_application, diameter}]},
{"2.2", [{restart_application, diameter}]},
- {"2.2.1", [{load_module, diameter},
- {load_module, diameter_codec},
- {update, diameter_config},
- {update, diameter_dist},
- {update, diameter_peer_fsm},
- {update, diameter_service},
- {load_module, diameter_traffic},
- {load_module, diameter_types},
- {update, diameter_tcp},
- {update, diameter_sctp}]},
- {"2.2.2", [{load_module, diameter},
- {load_module, diameter_types},
- {update, diameter_sctp}]},
- {"2.2.3", [{load_module, diameter_types},
- {update, diameter_sctp}]},
- {"2.2.4", [{load_module, diameter_types}]},
- {"2.2.5", [{load_module, diameter_types}]},
- {"2.2.6", [{restart_application, diameter}]}
+ {"2.2.1", [{restart_application, diameter}]},
+ {"2.2.2", [{restart_application, diameter}]},
+ {"2.2.3", [{restart_application, diameter}]},
+ {"2.2.4", [{restart_application, diameter}]},
+ {"2.2.5", [{restart_application, diameter}]},
+ {"2.2.6", [{restart_application, diameter}]},
+ {"2.2.7", [{restart_application, diameter}]},
+ {"2.3", [{restart_application, diameter}]}
]
}.
diff --git a/lib/diameter/src/info/diameter_dbg.erl b/lib/diameter/src/info/diameter_dbg.erl
index 718bbb582b..8ad393eb82 100644
--- a/lib/diameter/src/info/diameter_dbg.erl
+++ b/lib/diameter/src/info/diameter_dbg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -203,7 +203,7 @@ pp(<<Version:8, MsgLength:24,
"Application id",
"Hop by hop id",
"End to end id"],
- [Version, MsgLength, size(AVPs) + 20,
+ [Version, MsgLength, byte_size(AVPs) + 20,
Rbit, Pbit, Ebit, Tbit, Reserved,
CmdCode,
ApplId,
@@ -214,11 +214,11 @@ pp(<<Version:8, MsgLength:24,
N;
pp(<<_Version:8, MsgLength:24, _/binary>> = Bin) ->
- {bad_message_length, MsgLength, size(Bin)};
+ {bad_message_length, MsgLength, byte_size(Bin)};
pp(Bin)
when is_binary(Bin) ->
- {truncated_binary, size(Bin)};
+ {truncated_binary, byte_size(Bin)};
pp(_) ->
not_binary.
@@ -273,7 +273,7 @@ avp(0, Data, Length, Size) ->
data(Data, Length, Size).
data(Bin, Length, Size)
- when size(Bin) >= Length ->
+ when is_binary(Bin), byte_size(Bin) >= Length ->
<<AVP:Length/binary, Rest/binary>> = Bin,
ppp({"Data", AVP}),
unpad(Rest, Size - Length, Length rem 4);
@@ -288,7 +288,7 @@ unpad(Bin, Size, N) ->
un(Bin, Size, 4 - N).
un(Bin, Size, N)
- when size(Bin) >= N ->
+ when is_binary(Bin), byte_size(Bin) >= N ->
ppp({"Padding bytes", N}),
<<Pad:N/binary, Rest/binary>> = Bin,
Bits = N*8,
@@ -357,7 +357,7 @@ p(T) ->
stop() ->
dbg:ctp(),
- dbg:stop_clear().
+ dbg:stop().
%% tpl/1
%% tp/1
diff --git a/lib/diameter/src/info/diameter_info.erl b/lib/diameter/src/info/diameter_info.erl
index 9d1f392c4e..fd675cdc01 100644
--- a/lib/diameter/src/info/diameter_info.erl
+++ b/lib/diameter/src/info/diameter_info.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -688,10 +688,10 @@ f(SFun, Width, [HF | _] = Fields, Values) ->
end.
values(Fields, Rec)
- when length(Fields) == size(Rec) - 1 ->
+ when length(Fields) == tuple_size(Rec) - 1 ->
?VALUES(Rec);
values(Fields, T)
- when length(Fields) == size(T) ->
+ when length(Fields) == tuple_size(T) ->
tuple_to_list(T).
%% format_local/2
diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl
index e4ad139d93..7feef0fa56 100644
--- a/lib/diameter/test/diameter_codec_test.erl
+++ b/lib/diameter/test/diameter_codec_test.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -294,7 +294,7 @@ d(F, Eq, V) ->
end.
z(B) ->
- Sz = size(B),
+ Sz = byte_size(B),
<<0:Sz/unit:8>>.
%% values/1
diff --git a/lib/diameter/test/diameter_config_SUITE.erl b/lib/diameter/test/diameter_config_SUITE.erl
index 90773e8f6d..0aa0d95085 100644
--- a/lib/diameter/test/diameter_config_SUITE.erl
+++ b/lib/diameter/test/diameter_config_SUITE.erl
@@ -227,7 +227,7 @@ run(List)
try
?util:run([[[fun run/1, {F, 5000}] || F <- List]])
after
- dbg:stop_clear(),
+ dbg:stop(),
diameter:stop()
end;
diff --git a/lib/diameter/test/diameter_gen_tcp_SUITE.erl b/lib/diameter/test/diameter_gen_tcp_SUITE.erl
index d1e01da27e..f437ce56d8 100644
--- a/lib/diameter/test/diameter_gen_tcp_SUITE.erl
+++ b/lib/diameter/test/diameter_gen_tcp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2014-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2014-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -75,14 +75,14 @@ send_long() ->
{Sock, SendF} = connection(),
B = binary:copy(<<$X>>, 1 bsl 20),
ok = SendF(B),
- B = recv(Sock, size(B), []).
+ B = recv(Sock, byte_size(B), []).
recv(_, 0, Acc) ->
list_to_binary(lists:reverse(Acc));
recv(Sock, N, Acc) ->
receive
{tcp, Sock, Bin} ->
- recv(Sock, N - size(Bin), [Bin | Acc]);
+ recv(Sock, N - byte_size(Bin), [Bin | Acc]);
T ->
{T, Acc}
end.
diff --git a/lib/diameter/test/diameter_tls_SUITE.erl b/lib/diameter/test/diameter_tls_SUITE.erl
index 1e7f7ed50a..2033b60355 100644
--- a/lib/diameter/test/diameter_tls_SUITE.erl
+++ b/lib/diameter/test/diameter_tls_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -280,7 +280,7 @@ inband_security(Ids) ->
ssl_options(Dir, Base) ->
Root = filename:join([Dir, Base]),
- [{ssl_options, [{certfile, Root ++ "_ca.pem"},
+ [{ssl_options, [{verify, verify_none},{certfile, Root ++ "_ca.pem"},
{keyfile, Root ++ "_key.pem"}]}].
make_cert(Dir, Base) ->
@@ -290,7 +290,7 @@ make_cert(Dir, Keyfile, Certfile) ->
[KP,CP] = [filename:join([Dir, F]) || F <- [Keyfile, Certfile]],
KC = join(["openssl genrsa -out", KP, "2048"]),
- CC = join(["openssl req -new -x509 -key", KP, "-out", CP, "-days 7",
+ CC = join(["openssl req -new -sha256 -x509 -key", KP, "-out", CP, "-days 7",
"-subj /C=SE/ST=./L=Stockholm/CN=www.erlang.org"]),
%% Hope for the best and only check that files are written.
diff --git a/lib/edoc/test/edoc_SUITE.erl b/lib/edoc/test/edoc_SUITE.erl
index c150ace008..d09e9a2ecb 100644
--- a/lib/edoc/test/edoc_SUITE.erl
+++ b/lib/edoc/test/edoc_SUITE.erl
@@ -24,13 +24,14 @@
%% Test cases
-export([app/1,appup/1,build_std/1,build_map_module/1,otp_12008/1,
- build_app/1, otp_14285/1, infer_module_app_test/1]).
+ build_app/1, otp_14285/1, infer_module_app_test/1,
+ module_with_feature/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[app,appup,build_std,build_map_module,otp_12008, build_app, otp_14285,
- infer_module_app_test].
+ infer_module_app_test, module_with_feature].
groups() ->
[].
@@ -159,3 +160,13 @@ infer_module_app_test_({M, Beam}) ->
R2 = filelib:is_regular(BeamPath2),
R1 orelse R2
end.
+
+module_with_feature(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ Source = filename:join(DataDir, "module_with_feature.erl"),
+ DodgerOpts = [{dir, PrivDir}],
+ ok = edoc:files([Source], DodgerOpts),
+ PreprocessOpts = [{preprocess, true}, {dir, PrivDir}],
+ ok = edoc:files([Source], PreprocessOpts),
+ ok.
diff --git a/lib/edoc/test/edoc_SUITE_data/module_with_feature.erl b/lib/edoc/test/edoc_SUITE_data/module_with_feature.erl
new file mode 100644
index 0000000000..0091b704b4
--- /dev/null
+++ b/lib/edoc/test/edoc_SUITE_data/module_with_feature.erl
@@ -0,0 +1,2 @@
+-module(module_with_feature).
+-feature(maybe_expr, enable).
diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl
index 8bc041f609..881b46ec41 100644
--- a/lib/eldap/test/eldap_basic_SUITE.erl
+++ b/lib/eldap/test/eldap_basic_SUITE.erl
@@ -296,7 +296,7 @@ init_per_testcase(TC, Config) when TC == ssl_connection; TC == ssl_conn_socket_i
ct:log("SSL listening to port ~p (process ~p)",[SSL_Port, Listener]),
[{ssl_listener,Listener},
{ssl_listen_port,SSL_Port},
- {ssl_connect_opts,[]}
+ {ssl_connect_opts,[{verify, verify_none}]}
| Config];
{no_ok,SSL_Other,Listener} ->
ct:log("ssl:listen on port ~p failed: ~p",[SSL_Port,SSL_Other]),
diff --git a/lib/erl_docgen/priv/css/otp_doc.css b/lib/erl_docgen/priv/css/otp_doc.css
index d8615c7c4f..796d2f4096 100644
--- a/lib/erl_docgen/priv/css/otp_doc.css
+++ b/lib/erl_docgen/priv/css/otp_doc.css
@@ -286,7 +286,7 @@ a:visited { color: #1b6ec2; text-decoration: none }
background-color: #f3f3f3;
}
-.note, .warning, .do, .dont {
+.note, .change, .warning, .do, .dont {
border: 1px solid #495057;
margin: 1em 0;
}
@@ -302,6 +302,18 @@ a:visited { color: #1b6ec2; text-decoration: none }
font-size: 0.9em;
padding: 0.5em 1em;
}
+.change .label {
+ background-color: steelblue;
+ color: #fefefe;
+ font-weight: bold;
+ padding: 0.5em 1em;
+}
+.change .content {
+ background: #f8f9fa;
+ line-height: 120%;
+ font-size: 0.9em;
+ padding: 0.5em 1em;
+}
.warning .label {
background: #c92a2a;
color: #fefefe;
diff --git a/lib/erl_docgen/priv/dtd/application.dtd b/lib/erl_docgen/priv/dtd/application.dtd
index 25ef0d1261..5b56251a4d 100644
--- a/lib/erl_docgen/priv/dtd/application.dtd
+++ b/lib/erl_docgen/priv/dtd/application.dtd
@@ -25,6 +25,6 @@
%common.header;
<!ELEMENT application (header,description?,include+) >
-<!ELEMENT description (%block;|quote|br|marker|warning|note|dont|do)* >
+<!ELEMENT description (%block;|quote|br|marker|warning|note|change|dont|do)* >
<!ELEMENT include EMPTY >
<!ATTLIST include file CDATA #REQUIRED>
diff --git a/lib/erl_docgen/priv/dtd/book.dtd b/lib/erl_docgen/priv/dtd/book.dtd
index 326bf3369a..709ffbb901 100644
--- a/lib/erl_docgen/priv/dtd/book.dtd
+++ b/lib/erl_docgen/priv/dtd/book.dtd
@@ -39,7 +39,7 @@
<!ELEMENT pagetext (#PCDATA) >
<!ELEMENT preamble (contents?,preface?) >
-<!ELEMENT preface (title?,(%block;|quote|br|marker|warning|note|dont|do|table)*) >
+<!ELEMENT preface (title?,(%block;|quote|br|marker|warning|note|change|dont|do|table)*) >
<!ELEMENT insidecover (#PCDATA|br|theheader|vfill|vspace|tt|bold|
include)* >
@@ -69,7 +69,7 @@
<!ELEMENT onepart (title?,description?,include+) >
<!ATTLIST onepart lift (yes|no) "no" >
-<!ELEMENT description (%block;|quote|br|marker|warning|note|dont|do)* >
+<!ELEMENT description (%block;|quote|br|marker|warning|note|change|dont|do)* >
<!ELEMENT include EMPTY >
<!ATTLIST include file CDATA #REQUIRED>
diff --git a/lib/erl_docgen/priv/dtd/chapter.dtd b/lib/erl_docgen/priv/dtd/chapter.dtd
index 3e9113d798..b1a48b1c4c 100644
--- a/lib/erl_docgen/priv/dtd/chapter.dtd
+++ b/lib/erl_docgen/priv/dtd/chapter.dtd
@@ -30,9 +30,9 @@
<!-- Structure -->
-<!ELEMENT chapter (header,(%block;|quote|warning|note|dont|do|br|
+<!ELEMENT chapter (header,(%block;|quote|warning|note|change|dont|do|br|
image|marker|table)*,section*) >
<!ELEMENT section (marker*,title,
- (%block;|quote|warning|note|dont|do|br|image|marker|
+ (%block;|quote|warning|note|change|dont|do|br|image|marker|
table|section)*) >
<!ATTLIST section ghlink CDATA #IMPLIED>
diff --git a/lib/erl_docgen/priv/dtd/common.dtd b/lib/erl_docgen/priv/dtd/common.dtd
index 2b7a83aabc..9825e9e9b9 100644
--- a/lib/erl_docgen/priv/dtd/common.dtd
+++ b/lib/erl_docgen/priv/dtd/common.dtd
@@ -34,8 +34,9 @@
<!ELEMENT code (#PCDATA|anno)* >
<!ATTLIST code type (erl|c|none) "none" >
<!ELEMENT quote (p)* >
-<!ELEMENT warning (%block;|quote|br|marker)* >
-<!ELEMENT note (%block;|quote|br|marker)* >
+<!ELEMENT warning (%block;|quote|br|marker|change)* >
+<!ELEMENT note (%block;|quote|br|marker|change)* >
+<!ELEMENT change (%block;|quote|br|marker)* >
<!ELEMENT dont (%block;|quote|br|marker)* >
<!ELEMENT do (%block;|quote|br|marker)* >
<!ELEMENT c (#PCDATA|anno)* >
@@ -53,7 +54,8 @@
<!ATTLIST list type (ordered|bulleted) "bulleted" >
<!ELEMENT taglist (tag,item+)+ >
<!ELEMENT tag (#PCDATA|c|i|em|br|%refs;|marker|anno)* >
-<!ELEMENT item (%inline;|%block;|warning|note|dont|do|quote|table)* >
+<!ATTLIST tag since CDATA #IMPLIED>
+<!ELEMENT item (%inline;|%block;|warning|note|change|dont|do|quote|table)* >
<!-- References -->
diff --git a/lib/erl_docgen/priv/dtd/common.refs.dtd b/lib/erl_docgen/priv/dtd/common.refs.dtd
index 280efcd99c..08e26a8b27 100644
--- a/lib/erl_docgen/priv/dtd/common.refs.dtd
+++ b/lib/erl_docgen/priv/dtd/common.refs.dtd
@@ -25,7 +25,7 @@
<!ENTITY % common.header SYSTEM "common.header.dtd" >
%common.header;
-<!ELEMENT description (%block;|quote|br|marker|warning|note|dont|do)* >
+<!ELEMENT description (%block;|quote|br|marker|warning|note|change|dont|do)* >
<!ATTLIST description ghlink CDATA #IMPLIED>
<!ELEMENT funcs (fsdescription?,func+) >
<!ELEMENT func (name+,fsummary,(type|type_desc)*,desc?) >
@@ -38,15 +38,15 @@
name_i CDATA #IMPLIED>
<!ELEMENT v (#PCDATA|%refs;)* >
<!ELEMENT d (#PCDATA|%refs;|c|i|em)* >
-<!ELEMENT desc (%block;|quote|br|marker|warning|note|dont|do)* >
+<!ELEMENT desc (%block;|quote|br|marker|warning|note|change|dont|do)* >
<!ELEMENT authors (aname,email)+ >
<!ELEMENT aname (#PCDATA) >
<!ELEMENT email (#PCDATA) >
<!ELEMENT section (marker*,title,(%block;|quote|br|marker|
- warning|note|dont|do|section)*) >
+ warning|note|change|dont|do|section)*) >
<!ATTLIST section ghlink CDATA #IMPLIED>
<!ELEMENT fsdescription (marker*,title,(%block;|quote|br|marker|
- warning|note|dont|do|section)*) >
+ warning|note|change|dont|do|section)*) >
<!ATTLIST fsdescription ghlink CDATA #IMPLIED>
<!ELEMENT datatypes (datatype_title?,datatype)+ >
<!ELEMENT datatype_title (#PCDATA) >
diff --git a/lib/erl_docgen/priv/dtd/part.dtd b/lib/erl_docgen/priv/dtd/part.dtd
index 6329413f0e..57de542767 100644
--- a/lib/erl_docgen/priv/dtd/part.dtd
+++ b/lib/erl_docgen/priv/dtd/part.dtd
@@ -25,6 +25,6 @@
%common.header;
<!ELEMENT part (header,description?,include+) >
-<!ELEMENT description (%block;|quote|br|marker|warning|note|dont|do)* >
+<!ELEMENT description (%block;|quote|br|marker|warning|note|change|dont|do)* >
<!ELEMENT include EMPTY >
<!ATTLIST include file CDATA #REQUIRED>
diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl
index f47b294ea9..77b301347a 100644
--- a/lib/erl_docgen/priv/xsl/db_html.xsl
+++ b/lib/erl_docgen/priv/xsl/db_html.xsl
@@ -3,7 +3,7 @@
#
# %CopyrightBegin%
#
- # Copyright Ericsson AB 2009-2021. All Rights Reserved.
+ # Copyright Ericsson AB 2009-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -1251,10 +1251,15 @@
<xsl:template match="taglist/tag">
<xsl:param name="chapnum"/>
- <dt>
+ <dt class="title-link">
<strong>
<xsl:apply-templates/>
</strong>
+ <xsl:if test="string-length(@since) > 0">
+ <div class="title-since since">
+ <xsl:value-of select="@since"/>
+ </div>
+ </xsl:if>
</dt>
</xsl:template>
@@ -1283,6 +1288,21 @@
</div>
</xsl:template>
+ <!-- Change -->
+ <xsl:template match="change">
+ <xsl:param name="chapnum"/>
+ <div class="change">
+ <div class="label">Change</div>
+ <div class="content">
+ <p>
+ <xsl:apply-templates>
+ <xsl:with-param name="chapnum" select="$chapnum"/>
+ </xsl:apply-templates>
+ </p>
+ </div>
+ </div>
+ </xsl:template>
+
<!-- Warning -->
<xsl:template match="warning">
<xsl:param name="chapnum"/>
diff --git a/lib/erl_docgen/priv/xsl/db_man.xsl b/lib/erl_docgen/priv/xsl/db_man.xsl
index de89aa6b17..930e953fd0 100644
--- a/lib/erl_docgen/priv/xsl/db_man.xsl
+++ b/lib/erl_docgen/priv/xsl/db_man.xsl
@@ -3,7 +3,7 @@
#
# %CopyrightBegin%
#
- # Copyright Ericsson AB 2009-2021. All Rights Reserved.
+ # Copyright Ericsson AB 2009-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -539,6 +539,17 @@
<xsl:text>&#10;</xsl:text>
</xsl:template>
+ <!-- Change -->
+ <xsl:template match="change">
+ <xsl:text>&#10;.LP&#10;</xsl:text>
+ <xsl:text>&#10;.RS -4</xsl:text>
+ <xsl:text>&#10;.B&#10;</xsl:text>
+ <xsl:text>Change:</xsl:text>
+ <xsl:text>&#10;.RE</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:template>
+
<!-- Warning -->
<xsl:template match="warning">
<xsl:text>&#10;.LP&#10;</xsl:text>
@@ -572,7 +583,7 @@
<xsl:text>&#10;</xsl:text>
</xsl:template>
- <xsl:template match="warning/p | note/p | dont/p | do/p">
+ <xsl:template match="warning/p | note/p | change/p | dont/p | do/p">
<xsl:variable name="content">
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates/>
diff --git a/lib/erl_docgen/priv/xsl/db_pdf.xsl b/lib/erl_docgen/priv/xsl/db_pdf.xsl
index ba5e3c3491..6a9da06d99 100644
--- a/lib/erl_docgen/priv/xsl/db_pdf.xsl
+++ b/lib/erl_docgen/priv/xsl/db_pdf.xsl
@@ -3,7 +3,7 @@
#
# %CopyrightBegin%
#
- # Copyright Ericsson AB 2009-2021. All Rights Reserved.
+ # Copyright Ericsson AB 2009-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -980,7 +980,7 @@
</fo:block>
- <xsl:apply-templates select="section|quote|warning|note|br|image|marker|table|p|pre|code|list|taglist|codeinclude">
+ <xsl:apply-templates select="section|quote|warning|note|change|br|image|marker|table|p|pre|code|list|taglist|codeinclude">
<xsl:with-param name="partnum" select="$partnum"/>
<xsl:with-param name="chapnum"><xsl:number/></xsl:with-param>
</xsl:apply-templates>
@@ -1145,6 +1145,21 @@
</fo:block>
</xsl:template>
+ <!-- Change -->
+ <xsl:template match="change">
+ <xsl:param name="partnum"/>
+ <fo:block xsl:use-attribute-sets="note-warning">
+ <fo:block xsl:use-attribute-sets="change-title">
+ <xsl:text>Change:</xsl:text>
+ </fo:block>
+ <fo:block xsl:use-attribute-sets="note-warning-content">
+ <xsl:apply-templates>
+ <xsl:with-param name="partnum" select="$partnum"/>
+ </xsl:apply-templates>
+ </fo:block>
+ </fo:block>
+ </xsl:template>
+
<!-- Warning -->
<xsl:template match="warning">
<xsl:param name="partnum"/>
diff --git a/lib/erl_docgen/priv/xsl/db_pdf_params.xsl b/lib/erl_docgen/priv/xsl/db_pdf_params.xsl
index 9bfa991b54..58d7d29aba 100644
--- a/lib/erl_docgen/priv/xsl/db_pdf_params.xsl
+++ b/lib/erl_docgen/priv/xsl/db_pdf_params.xsl
@@ -3,7 +3,7 @@
#
# %CopyrightBegin%
#
- # Copyright Ericsson AB 2009-2018. All Rights Reserved.
+ # Copyright Ericsson AB 2009-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -327,6 +327,23 @@
<xsl:attribute name="margin-right">1mm</xsl:attribute>
</xsl:attribute-set>
+ <xsl:attribute-set name="change-title">
+ <xsl:attribute name="space-before">0.5em</xsl:attribute>
+ <xsl:attribute name="border-style">solid</xsl:attribute>
+ <xsl:attribute name="border-bottom-width">0mm</xsl:attribute>
+ <xsl:attribute name="border-color">#495057</xsl:attribute>
+ <xsl:attribute name="background-color">#4682b4</xsl:attribute>
+ <xsl:attribute name="font-weight">bold</xsl:attribute>
+ <xsl:attribute name="color">#fefefe</xsl:attribute>
+ <xsl:attribute name="padding-before">1mm</xsl:attribute>
+ <xsl:attribute name="padding-after">0.5mm</xsl:attribute>
+ <xsl:attribute name="padding-left">1mm</xsl:attribute>
+ <xsl:attribute name="padding-right">1mm</xsl:attribute>
+ <xsl:attribute name="margin-left">1mm</xsl:attribute>
+ <xsl:attribute name="margin-right">1mm</xsl:attribute>
+ <xsl:attribute name="font-size">1.33em</xsl:attribute>
+ </xsl:attribute-set>
+
<xsl:attribute-set name="module-header">
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
<xsl:attribute name="space-after">2em</xsl:attribute>
diff --git a/lib/erl_docgen/src/docgen_xml_to_chunk.erl b/lib/erl_docgen/src/docgen_xml_to_chunk.erl
index a26412e557..d9d54132a0 100644
--- a/lib/erl_docgen/src/docgen_xml_to_chunk.erl
+++ b/lib/erl_docgen/src/docgen_xml_to_chunk.erl
@@ -1,7 +1,7 @@
%% -*- erlang -*-
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -238,7 +238,7 @@ build_dom({ignorableWhitespace, String},
#state{dom=[{Name,_,_} = _E|_]} = State) ->
case lists:member(Name,
[p,pre,input,code,quote,warning,
- note,dont,do,c,b,i,em,strong,
+ note,change,dont,do,c,b,i,em,strong,
seemfa,seeerl,seetype,seeapp,
seecom,seecref,seefile,seeguide,
tag,item]) of
@@ -387,9 +387,9 @@ transform([{marker,Attrs,Content}|T],Acc) ->
%% transform <url href="external URL"> Content</url> to <a href....
transform([{url,Attrs,Content}|T],Acc) ->
transform(T,[{a,a2b(Attrs),transform(Content,[])}|Acc]);
-%% transform note/warning/do/don't to <p class="thing">
+%% transform note/change/warning/do/don't to <p class="thing">
transform([{What,[],Content}|T],Acc)
- when What =:= note; What =:= warning; What =:= do; What =:= dont ->
+ when What =:= note; What =:= change; What =:= warning; What =:= do; What =:= dont ->
WhatP = {'div',[{class,atom_to_binary(What)}], transform(Content,[])},
transform(T,[WhatP|Acc]);
@@ -485,13 +485,23 @@ transform_types(Dom,Acc) ->
transform_taglist(Attr,Content) ->
Items =
- lists:map(fun({tag,A,C}) ->
- {dt,A,C};
+ lists:map(fun({tag,_A,_C}=Tag) ->
+ transform_tag(Tag);
({item,A,C}) ->
{dd,A,C}
end, Content),
{dl,Attr,Items}.
+transform_tag({tag, Attr0, C}) ->
+ Attr1 = lists:map(fun({since,Vsn}) ->
+ {since,
+ unicode:characters_to_binary(Vsn)};
+ (A) ->
+ A
+ end,
+ Attr0),
+ {dt,Attr1,C}.
+
%% if we have {func,[],[{name,...},{name,....},...]}
%% we convert it to one {func,[],[{name,...}] per arity lowest first.
transform_funcs([Func|T],Acc) ->
diff --git a/lib/erl_interface/configure b/lib/erl_interface/configure
index 049c3371fc..72c01560cf 100755
--- a/lib/erl_interface/configure
+++ b/lib/erl_interface/configure
@@ -750,7 +750,6 @@ enable_threads
enable_mask_real_errno
enable_ei_dynamic_lib
with_gmp
-enable_sanitizers
'
ac_precious_vars='build_alias
host_alias
@@ -1385,8 +1384,6 @@ Optional Features:
--disable-threads use to only build single threaded libs
--disable-mask-real-errno do not mask real 'errno'
--enable-ei-dynamic-lib build ei as a dynamic library
- --enable-sanitizers[=comma-separated list of sanitizers]
- Default=address,undefined
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
@@ -9626,23 +9623,6 @@ fi
-
-
-# Check whether --enable-sanitizers was given.
-if test ${enable_sanitizers+y}
-then :
- enableval=$enable_sanitizers;
-case "$enableval" in
- no) sanitizers= ;;
- yes) sanitizers="-fsanitize=address,undefined" ;;
- *) sanitizers="-fsanitize=$enableval" ;;
-esac
-CFLAGS="$CFLAGS $sanitizers"
-LDFLAGS="$LDFLAGS $sanitizers"
-
-fi
-
-
# ---------------------------------------------------------------------------
# XXX
# ---------------------------------------------------------------------------
diff --git a/lib/erl_interface/configure.ac b/lib/erl_interface/configure.ac
index d24e3f48eb..e660965299 100644
--- a/lib/erl_interface/configure.ac
+++ b/lib/erl_interface/configure.ac
@@ -1,7 +1,7 @@
# -*- Autoconf -*-
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2000-2022. All Rights Reserved.
+# Copyright Ericsson AB 2000-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -361,26 +361,6 @@ fi
ERL_DED_FLAT_BUNDLE=true
ERL_DED_FLAGS
-dnl ----------------------------------------------------------------------
-dnl Enable -fsanitize= flags.
-dnl ----------------------------------------------------------------------
-
-m4_define(DEFAULT_SANITIZERS, [address,undefined])
-AC_ARG_ENABLE(
- sanitizers,
- AS_HELP_STRING(
- [--enable-sanitizers@<:@=comma-separated list of sanitizers@:>@],
- [Default=DEFAULT_SANITIZERS]),
-[
-case "$enableval" in
- no) sanitizers= ;;
- yes) sanitizers="-fsanitize=DEFAULT_SANITIZERS" ;;
- *) sanitizers="-fsanitize=$enableval" ;;
-esac
-CFLAGS="$CFLAGS $sanitizers"
-LDFLAGS="$LDFLAGS $sanitizers"
-])
-
# ---------------------------------------------------------------------------
# XXX
# ---------------------------------------------------------------------------
diff --git a/lib/erl_interface/doc/src/ei_users_guide.xml b/lib/erl_interface/doc/src/ei_users_guide.xml
index f59449c420..951fe32ad5 100644
--- a/lib/erl_interface/doc/src/ei_users_guide.xml
+++ b/lib/erl_interface/doc/src/ei_users_guide.xml
@@ -241,7 +241,7 @@ const char* node_name = "einode@durin";
const char *cookie = NULL;
short creation = time(NULL) + 1;
ei_cnode ec;
-ei_connect_init(ec,
+ei_connect_init(&ec,
node_name,
cookie,
creation); ]]></code>
@@ -257,7 +257,7 @@ ei_connect_init(ec,
<code type="none"><![CDATA[
int sockfd;
const char* node_name = "einode@durin"; /* An example */
-if ((sockfd = ei_connect(ec, nodename)) < 0)
+if ((sockfd = ei_connect(&ec, nodename)) < 0)
fprintf(stderr, "ERROR: ei_connect failed"); ]]></code>
</section>
@@ -289,7 +289,7 @@ if ((sockfd = ei_connect(ec, nodename)) < 0)
<code type="none"><![CDATA[
int pub;
-pub = ei_publish(ec, port); ]]></code>
+pub = ei_publish(&ec, port); ]]></code>
<p><c>pub</c> is a file descriptor now connected to
<c>epmd</c>. <c>epmd</c>
@@ -347,7 +347,7 @@ ei_x_encode_tuple_header(&buf, 2);
ei_x_encode_pid(&buf, ei_self(ec));
ei_x_encode_atom(&buf, "Hello world");
-ei_reg_send(ec,fd,"my_server",buf,buf.index);]]></code>
+ei_reg_send(&ec,fd,"my_server",buf,buf.index);]]></code>
<p>The first element of the tuple that is sent is your own
pid. This enables <c>my_server</c> to reply.
@@ -458,7 +458,7 @@ char **names;
int count;
int i;
-names = ei_global_names(ec,fd,&count);
+names = ei_global_names(&ec,fd,&count);
if (names)
for (i=0; i<count; i++)
@@ -487,7 +487,7 @@ ETERM *pid;
char node[256];
erlang_pid the_pid;
-if (ei_global_whereis(ec,fd,"schedule",&the_pid,node) < 0)
+if (ei_global_whereis(&ec,fd,"schedule",&the_pid,node) < 0)
fprintf(stderr, "ei_global_whereis error\n"); ]]></code>
<p>If <c>"schedule"</c> is known to the
@@ -524,7 +524,7 @@ ei_global_register(fd,servicename,ei_self(ec)); ]]></code>
<p>To unregister a name:</p>
<code type="none"><![CDATA[
-ei_global_unregister(ec,fd,servicename); ]]></code>
+ei_global_unregister(&ec,fd,servicename); ]]></code>
</section>
</chapter>
diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c
index 89a1d2b7fd..3f2becde5a 100644
--- a/lib/erl_interface/src/connect/ei_connect.c
+++ b/lib/erl_interface/src/connect/ei_connect.c
@@ -1058,11 +1058,11 @@ int ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name,
strcpy(thishostname, hp->h_name);
}
}
- if (strlen(this_node_name) + 1 + strlen(thishostname) > MAXNODELEN) {
+ if (snprintf(thisnodename, sizeof(thisnodename), "%s@%s",
+ this_node_name, thishostname) > sizeof(thisnodename)) {
EI_TRACE_ERR0("ei_connect_init_ussi","this node name is too long");
return ERL_ERROR;
}
- sprintf(thisnodename, "%s@%s", this_node_name, thishostname);
res = ei_connect_xinit_ussi(ec, thishostname, thisalivename, thisnodename,
(struct in_addr *)*hp->h_addr_list, cookie, creation,
cbs, cbs_sz, setup_context);
@@ -2249,21 +2249,9 @@ static DistFlags preferred_flags(void)
{
DistFlags flags =
DFLAG_MANDATORY_25_DIGEST
- | DFLAG_EXTENDED_REFERENCES
+ | DFLAG_DIST_MANDATORY
| DFLAG_DIST_MONITOR
- | DFLAG_EXTENDED_PIDS_PORTS
- | DFLAG_FUN_TAGS
- | DFLAG_NEW_FUN_TAGS
- | DFLAG_NEW_FLOATS
- | DFLAG_SMALL_ATOM_TAGS
- | DFLAG_UTF8_ATOMS
- | DFLAG_MAP_TAG
- | DFLAG_BIG_CREATION
- | DFLAG_EXPORT_PTR_TAG
- | DFLAG_BIT_BINARIES
- | DFLAG_HANDSHAKE_23
- | DFLAG_V4_NC
- | DFLAG_UNLINK_ID;
+ | DFLAG_SMALL_ATOM_TAGS;
return flags;
}
diff --git a/lib/erl_interface/src/connect/ei_connect_int.h b/lib/erl_interface/src/connect/ei_connect_int.h
index d9be32d42c..85af58e64c 100644
--- a/lib/erl_interface/src/connect/ei_connect_int.h
+++ b/lib/erl_interface/src/connect/ei_connect_int.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2001-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2001-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -103,7 +103,12 @@ typedef EI_ULONGLONG DistFlags;
| DFLAG_NEW_FLOATS \
| DFLAG_MAP_TAG \
| DFLAG_EXPORT_PTR_TAG \
- | DFLAG_BIT_BINARIES)
+ | DFLAG_BIT_BINARIES \
+ | DFLAG_HANDSHAKE_23)
+
+/* New mandatory flags for distribution in OTP 26. */
+#define DFLAG_DIST_MANDATORY_26 (DFLAG_V4_NC \
+ | DFLAG_UNLINK_ID)
/* Mandatory flags for distribution. */
@@ -111,8 +116,8 @@ typedef EI_ULONGLONG DistFlags;
* Mandatory flags for distribution. Keep them in sync with
* erts/emulator/beam/dist.h.
*/
-#define DFLAG_DIST_MANDATORY DFLAG_DIST_MANDATORY_25
-
+#define DFLAG_DIST_MANDATORY (DFLAG_DIST_MANDATORY_25 \
+ | DFLAG_DIST_MANDATORY_26)
ei_cnode *ei_fd_to_cnode(int fd);
int ei_distversion(int fd);
diff --git a/lib/erl_interface/src/prog/erl_call.c b/lib/erl_interface/src/prog/erl_call.c
index 4548b9f4dd..1fb72c65cb 100644
--- a/lib/erl_interface/src/prog/erl_call.c
+++ b/lib/erl_interface/src/prog/erl_call.c
@@ -441,11 +441,11 @@ int main(int argc, char *argv[])
memcpy(&h_ipadr.s_addr, *hp->h_addr_list, sizeof(struct in_addr));
if (h_alivename) {
- if (strlen(h_alivename) + strlen(h_hostname) + 2 > sizeof(h_nodename_buf)) {
+ if (snprintf(h_nodename_buf, sizeof(h_nodename_buf), "%s@%s",
+ h_alivename, h_hostname) > sizeof(h_nodename_buf)) {;
fprintf(stderr,"erl_call: hostname too long: %s\n", h_hostname);
exit_free_flags_fields(1, &flags);
}
- sprintf(h_nodename, "%s@%s", h_alivename, h_hostname);
}
else {
/* dynamic node name */
@@ -490,11 +490,11 @@ int main(int argc, char *argv[])
}
if (flags.port == -1) {
- if (strlen(flags.node) + strlen(host_name) + 2 > sizeof(nodename)) {
+ if (snprintf(nodename, sizeof(nodename),
+ "%s@%s", flags.node, host_name) > sizeof(nodename)) {
fprintf(stderr,"erl_call: nodename too long: %s\n", flags.node);
exit_free_flags_fields(1, &flags);
}
- sprintf(nodename, "%s@%s", flags.node, host_name);
}
/*
* Try to connect. Start an Erlang system if the
diff --git a/lib/erl_interface/test/ei_accept_SUITE.erl b/lib/erl_interface/test/ei_accept_SUITE.erl
index f563feaa72..a3dbee59d0 100644
--- a/lib/erl_interface/test/ei_accept_SUITE.erl
+++ b/lib/erl_interface/test/ei_accept_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,6 +25,8 @@
-include("ei_accept_SUITE_data/ei_accept_test_cases.hrl").
-export([all/0, suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
init_per_testcase/2,
ei_accept/1,
hopeful_random/1,
@@ -49,6 +51,25 @@ all() ->
ei_threaded_accept,
monitor_ei_process].
+init_per_suite(Config) when is_list(Config) ->
+
+ %% Trigger usage of large pids and ports in 64-bit case...
+ case erlang:system_info(wordsize) of
+ 4 ->
+ ok;
+ 8 ->
+ erts_debug:set_internal_state(available_internal_state,true),
+ erts_debug:set_internal_state(next_pid, 1 bsl 32),
+ erts_debug:set_internal_state(next_port, 1 bsl 32),
+ erts_debug:set_internal_state(available_internal_state,false),
+ ok
+ end,
+
+ Config.
+
+end_per_suite(Config) when is_list(Config) ->
+ Config.
+
init_per_testcase(Case, Config) ->
rand:uniform(), % Make sure rand is initialized and seeded.
%%rand:seed({exsss, [61781477086241372|88832360391433009]}),
diff --git a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
index 074c71733f..64576783a5 100644
--- a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
+++ b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2004-2020. All Rights Reserved.
+ * Copyright Ericsson AB 2004-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -742,11 +742,11 @@ TESTCASE(test_ei_decode_misc)
decode_double(-1.0);
decode_double(1.0);
- EI_DECODE_2(decode_boolean, 8, int, 0);
- EI_DECODE_2(decode_boolean, 7, int, 1);
+ EI_DECODE_2(decode_boolean, 7, int, 0);
+ EI_DECODE_2(decode_boolean, 6, int, 1);
- EI_DECODE_STRING(decode_my_atom, 6, "foo");
- EI_DECODE_STRING(decode_my_atom, 3, "");
+ EI_DECODE_STRING(decode_my_atom, 5, "foo");
+ EI_DECODE_STRING(decode_my_atom, 2, "");
EI_DECODE_STRING(decode_my_atom, 9, "ÅÄÖåäö");
EI_DECODE_STRING(decode_my_string, 6, "foo");
@@ -802,10 +802,10 @@ TESTCASE(test_ei_decode_utf8_atom)
P99({ERLANG_ANY,ERLANG_LATIN1,ERLANG_ASCII}));
EI_DECODE_STRING_4(decode_my_atom_as, 4, "b",
P99({ERLANG_UTF8,ERLANG_LATIN1,ERLANG_ASCII}));
- EI_DECODE_STRING_4(decode_my_atom_as, 4, "c",
- P99({ERLANG_LATIN1,ERLANG_LATIN1,ERLANG_ASCII}));
- EI_DECODE_STRING_4(decode_my_atom_as, 4, "d",
- P99({ERLANG_ASCII,ERLANG_LATIN1,ERLANG_ASCII}));
+ EI_DECODE_STRING_4(decode_my_atom_as, 3, "c",
+ P99({ERLANG_LATIN1,ERLANG_UTF8,ERLANG_ASCII}));
+ EI_DECODE_STRING_4(decode_my_atom_as, 3, "d",
+ P99({ERLANG_ASCII,ERLANG_UTF8,ERLANG_ASCII}));
report(1);
}
diff --git a/lib/erl_interface/test/ei_decode_encode_SUITE.erl b/lib/erl_interface/test/ei_decode_encode_SUITE.erl
index b861426792..5265b68761 100644
--- a/lib/erl_interface/test/ei_decode_encode_SUITE.erl
+++ b/lib/erl_interface/test/ei_decode_encode_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -93,7 +93,10 @@ test_ei_decode_encode(Config) when is_list(Config) ->
%% Test large node containers...
ThisNode = {node(), erlang:system_info(creation)},
- TXPid = mk_pid(ThisNode, 32767, 8191),
+ TXPid = case erlang:system_info(wordsize) of
+ 4 -> mk_pid(ThisNode, 1 bsl 27, 0);
+ 8 -> mk_pid(ThisNode, 1 bsl 27, 1 bsl 31)
+ end,
TXPort = mk_port(ThisNode, 268435455),
TXRef = mk_ref(ThisNode, [262143, 4294967295, 4294967295]),
diff --git a/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c b/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c
index c39851ba49..3721e2e2db 100644
--- a/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c
+++ b/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2004-2020. All Rights Reserved.
+ * Copyright Ericsson AB 2004-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
*/
#include "ei_runner.h"
+#include <string.h>
/*
* Purpose: Read pids, funs and others without real meaning on the C side
@@ -291,6 +292,7 @@ void decode_encode(struct Type** tv, int nobj)
ei_x_new(&arg);
for (i=0; i<nobj; i++) {
struct Type* t = tv[i];
+ int small_port = 0;
MESSAGE("ei_decode_%s, arg is type %s", t->name, t->type);
@@ -355,9 +357,17 @@ void decode_encode(struct Type** tv, int nobj)
}
}
if (size1 != size2) {
- MESSAGE("size1 = %d, size2 = %d\n",size1,size2);
- fail("decode and encode size differs when buf is NULL");
- return;
+ if (strcmp(t->type, "erlang_port") == 0
+ && size1 == size2 + 4
+ && objv[oix].u.port.id <= 0x0fffffff /* 28 bits */) {
+ /* old encoding... */
+ small_port = !0;
+ }
+ else {
+ MESSAGE("size1 = %d, size2 = %d\n",size1,size2);
+ fail("decode and encode size differs when buf is NULL");
+ return;
+ }
}
MESSAGE("ei_encode_%s, arg is type %s", t->name, t->type);
size3 = 0;
@@ -371,9 +381,11 @@ void decode_encode(struct Type** tv, int nobj)
return;
}
if (size1 != size3) {
- MESSAGE("size1 = %d, size2 = %d\n",size1,size3);
- fail("decode and encode size differs");
- return;
+ if (!small_port || size2 != size3) {
+ MESSAGE("size1 = %d, size3 = %d\n",size1,size3);
+ fail("decode and encode size differs");
+ return;
+ }
}
MESSAGE("ei_x_encode_%s, arg is type %s", t->name, t->type);
@@ -394,7 +406,7 @@ void decode_encode(struct Type** tv, int nobj)
}
inp += size1;
- outp += size1;
+ outp += size2;
if (objv[oix].nterms) { /* container term */
if (++oix >= sizeof(objv)/sizeof(*objv))
diff --git a/lib/erl_interface/test/ei_tmo_SUITE.erl b/lib/erl_interface/test/ei_tmo_SUITE.erl
index a6964e3ec0..5264cedc38 100644
--- a/lib/erl_interface/test/ei_tmo_SUITE.erl
+++ b/lib/erl_interface/test/ei_tmo_SUITE.erl
@@ -77,7 +77,9 @@ end_per_testcase(_Case, _Config) ->
-define(DFLAG_MAP_TAG, 16#20000).
-define(DFLAG_BIG_CREATION, 16#40000).
-define(DFLAG_HANDSHAKE_23, 16#1000000).
+-define(DFLAG_UNLINK_ID, 16#2000000).
-define(DFLAG_MANDATORY_25_DIGEST, 16#4000000).
+-define(DFLAG_V4_NC, 16#400000000).
%% From OTP R9 extended references are compulsory.
%% From OTP R10 extended pids and ports are compulsory.
@@ -85,7 +87,8 @@ end_per_testcase(_Case, _Config) ->
%% From OTP 21 NEW_FUN_TAGS is compulsory (no more tuple fallback {fun, ...}).
%% From OTP 23 BIG_CREATION is compulsory.
%% From OTP 25 NEW_FLOATS, MAP_TAG, EXPORT_PTR_TAG, and BIT_BINARIES are compulsory.
--define(COMPULSORY_DFLAGS,
+
+-define(DFLAGS_MANDATORY_25,
(?DFLAG_EXTENDED_REFERENCES bor
?DFLAG_FUN_TAGS bor
?DFLAG_EXTENDED_PIDS_PORTS bor
@@ -98,6 +101,16 @@ end_per_testcase(_Case, _Config) ->
?DFLAG_BIT_BINARIES bor
?DFLAG_HANDSHAKE_23)).
+%% From OTP 26 V4_NC, and UNLINK_ID are compulsory.
+
+-define(DFLAGS_MANDATORY_26,
+ (?DFLAG_V4_NC bor
+ ?DFLAG_UNLINK_ID)).
+
+-define(COMPULSORY_DFLAGS,
+ (?DFLAGS_MANDATORY_25 bor
+ ?DFLAGS_MANDATORY_26)).
+
%% Check the framework.
framework_check(Config) when is_list(Config) ->
%%dbg:tracer(),
@@ -423,9 +436,9 @@ ei_dflags(Config) ->
normal_accept(Config, OurVer, ?COMPULSORY_DFLAGS),
%% Test compatibility with future versions.
- normal_connect(Config, ?DFLAG_MANDATORY_25_DIGEST),
- normal_accept(Config, AssumedVer, ?DFLAG_MANDATORY_25_DIGEST),
- normal_accept(Config, OurVer, ?DFLAG_MANDATORY_25_DIGEST),
+ normal_connect(Config, ?DFLAG_MANDATORY_25_DIGEST bor ?DFLAGS_MANDATORY_26),
+ normal_accept(Config, AssumedVer, ?DFLAG_MANDATORY_25_DIGEST bor ?DFLAGS_MANDATORY_26),
+ normal_accept(Config, OurVer, ?DFLAG_MANDATORY_25_DIGEST bor ?DFLAGS_MANDATORY_26),
ok.
diff --git a/lib/et/doc/src/et_intro.xml b/lib/et/doc/src/et_intro.xml
index 729f95647d..ae5bd7ffe2 100644
--- a/lib/et/doc/src/et_intro.xml
+++ b/lib/et/doc/src/et_intro.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2002</year><year>2016</year>
+ <year>2002</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -53,7 +53,7 @@
<section>
<title>Prerequisites</title>
- <p>The following prerequisites is required for understanding the
+ <p>The following prerequisites are required for understanding the
material in the <c>Event Tracer (ET)</c> User's Guide:</p>
<list type="bulleted">
diff --git a/lib/et/src/et_collector.erl b/lib/et/src/et_collector.erl
index 75ed1179bf..a1b7306c63 100644
--- a/lib/et/src/et_collector.erl
+++ b/lib/et/src/et_collector.erl
@@ -684,7 +684,7 @@ monitor_trace_port(CollectorPid, Parameters) ->
MonitorRef = erlang:monitor(process, CollectorPid),
receive
{'DOWN', MonitorRef, _, _, _} ->
- dbg:stop_clear()
+ dbg:stop()
end
end),
Res.
diff --git a/lib/ftp/doc/specs/.gitignore b/lib/ftp/doc/specs/.gitignore
new file mode 100644
index 0000000000..322eebcb06
--- /dev/null
+++ b/lib/ftp/doc/specs/.gitignore
@@ -0,0 +1 @@
+specs_*.xml
diff --git a/lib/ftp/doc/src/Makefile b/lib/ftp/doc/src/Makefile
index 38ad4f76d0..b80f5a0f72 100644
--- a/lib/ftp/doc/src/Makefile
+++ b/lib/ftp/doc/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2021. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -54,6 +54,10 @@ XML_FILES = \
$(XML_REF3_FILES) \
$(XML_APPLICATION_FILES)
+SPEC_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
+TOP_SPECS_FILE = specs.xml
+
+
# IMAGE_FILES = ftp.gif
include $(ERL_TOP)/make/doc.mk
diff --git a/lib/ftp/doc/src/ftp.xml b/lib/ftp/doc/src/ftp.xml
index a70369a777..3616a1a567 100644
--- a/lib/ftp/doc/src/ftp.xml
+++ b/lib/ftp/doc/src/ftp.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1997</year><year>2021</year>
+ <year>1997</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -41,12 +41,7 @@
<p>The FTP client always tries to use passive FTP mode and only resort
to active FTP mode if this fails. This default behavior can be
changed by start option <seeerl marker="#mode">mode</seeerl>.</p>
-
- <p>An FTP client is always started as part of the ftp application
- and legacy
- <seeerl marker="#service_start">start_service</seeerl> function,
- is deprecated in OTP-24 </p>
-
+
<p>For a simple example of an FTP session, see
<seeguide marker="ftp_client">FTP User's Guide</seeguide>.</p>
@@ -81,203 +76,23 @@
<p>The FTP client can be started and stopped dynamically in runtime by
calling the <c>ftp</c> application API
<c>ftp:open(Host, Options)</c> and
- <c>ftp:close(Client)</c>.</p>
-
- <p>The available configuration options are as follows:</p>
-
- <taglist>
- <tag>{host, Host}</tag>
- <item>
- <marker id="host"></marker>
- <p>Host = <c>string() | ip_address()</c></p>
- </item>
-
- <tag>{port, Port}</tag>
- <item>
- <marker id="port"></marker>
- <p>Port = <c>integer() > 0</c></p>
- <p>Default is <c>0</c> which aliases to <c>21</c> or <c>990</c> when used with
- <seeerl marker="#open"><c>{tls_sec_method,ftps}</c></seeerl>).</p>
- </item>
-
- <tag>{mode, Mode}</tag>
- <item>
- <marker id="mode"></marker>
- <p>Mode = <c>active | passive</c></p>
- <p>Default is <c>passive</c>.</p>
- </item>
-
- <tag>{verbose, Verbose}</tag>
- <item>
- <marker id="verbose"></marker>
- <p>Verbose = <c>boolean()</c> </p>
- <p>Determines if the FTP communication is to be
- verbose or not.</p>
- <p>Default is <c>false</c>.</p>
- </item>
-
- <tag>{debug, Debug}</tag>
- <item>
- <marker id="debug"></marker>
- <p>Debug = <c>trace | debug | disable</c> </p>
- <p>Debugging using the dbg toolkit. </p>
- <p>Default is <c>disable</c>.</p>
- </item>
-
- <tag>{ipfamily, IpFamily}</tag>
- <item>
- <marker id="ipfamily"></marker>
- <p>IpFamily = <c>inet | inet6 | inet6fb4</c> </p>
- <p>With <c>inet6fb4</c> the client behaves as before, that is,
- tries to use IPv6, and only if that does not work it
- uses IPv4).</p>
- <p>Default is <c>inet</c> (IPv4).</p>
- </item>
-
- <tag>{timeout, Timeout}</tag>
- <item>
- <marker id="timeout"></marker>
- <p>Timeout = <c>non_neg_integer()</c></p>
- <p>Connection time-out.</p>
- <p>Default is <c>60000</c> (milliseconds).</p>
- </item>
-
- <tag>{dtimeout, DTimeout}</tag>
- <item>
- <marker id="dtimeout"></marker>
- <p>DTimeout = <c>non_neg_integer() | infinity</c> </p>
- <p>Data connect time-out.
- The time the client waits for the server to connect to the
- data socket.</p>
- <p>Default is <c>infinity</c>. </p>
- </item>
-
- <tag>{progress, Progress}</tag>
- <item>
- <marker id="progress"></marker>
- <p>Progress = <c>ignore | {CBModule, CBFunction, InitProgress}</c></p>
- <p><c>CBModule = atom()</c>, <c>CBFunction = atom()</c></p>
- <p><c>InitProgress = term()</c></p>
- <p>Default is <c>ignore</c>.</p>
- </item>
-
- </taglist>
-
- <p>Option <c>progress</c> is intended to be used by applications that
- want to create some type of progress report, such as a progress bar in
- a GUI. Default for the progress option is <c>ignore</c>,
- that is, the option is not used. When the progress option is
- specified, the following happens when <c>ftp:send/[3,4]</c> or
- <c>ftp:recv/[3,4]</c> are called:</p>
-
- <list type="bulleted">
- <item>
- <p>Before a file is transferred, the following call is
- made to indicate the start of the file transfer and how large
- the file is. The return value of the callback function
- is to be a new value for the <c>UserProgressTerm</c> that will
- be used as input the next time the callback function is
- called.</p>
- <p><c>
- CBModule:CBFunction(InitProgress, File, {file_size, FileSize})
- </c></p>
- </item>
-
- <item>
- <p>Every time a chunk of bytes is transferred the
- following call is made:</p>
- <p><c>
- CBModule:CBFunction(UserProgressTerm, File, {transfer_size, TransferSize})
- </c></p>
- </item>
-
- <item>
- <p>At the end of the file the following call is
- made to indicate the end of the transfer:</p>
- <p><c>
- CBModule:CBFunction(UserProgressTerm, File, {transfer_size, 0})
- </c></p>
- </item>
- </list>
-
- <p>The callback function is to be defined as follows:</p>
-
- <p><c>
- CBModule:CBFunction(UserProgressTerm, File, Size) -> UserProgressTerm
- </c></p>
-
- <p><c>
- CBModule = CBFunction = atom()
- </c></p>
-
- <p><c>
- UserProgressTerm = term()
- </c></p>
-
- <p><c>
- File = string()
- </c></p>
-
- <p><c>
- Size = {transfer_size, integer()} | {file_size, integer()} | {file_size, unknown}
- </c></p>
-
- <p>For remote files, <c>ftp</c> cannot determine the
- file size in a platform independent way. In this case the size
- becomes <c>unknown</c> and it is left to the application to
- determine the size.</p>
-
- <note>
- <p>The callback is made by a middleman process, hence the
- file transfer is not affected by the code in the progress
- callback function. If the callback crashes, this is
- detected by the FTP connection process, which then prints an
- info-report and goes on as if the progress option was set
- to <c>ignore</c>.</p>
- </note>
-
- <p>The file transfer type is set to the default of the FTP server
- when the session is opened. This is usually ASCII mode.
- </p>
-
- <p>The current local working directory (compare <c>lpwd/1</c>) is set
- to the value reported by <c>file:get_cwd/1</c>, the wanted
- local directory.
- </p>
-
- <p>The return value <c>Pid</c> is used as a reference to the
- newly created FTP client in all other functions, and they are to
- be called by the process that created the connection. The FTP
- client process monitors the process that created it and
- terminates if that process terminates.</p>
+ <c>ftp:close(Client)</c>. </p>
</section>
<section>
- <title>DATA TYPES</title>
+ <title>Data Types</title>
<p>The following type definitions are used by more than one
function in the FTP client API:</p>
<p><c>pid()</c> = identifier of an FTP connection</p>
<p><c>string()</c> = list of ASCII characters</p>
- <p><c>shortage_reason()</c> = <c>etnospc | epnospc</c></p>
- <p><c>restriction_reason()</c> = <c>epath | efnamena | elogin | enotbinary</c>
- - all restrictions are not always relevant to all functions
- </p>
- <p><c>common_reason()</c> = <c>econn | eclosed | term()</c>
- - some explanation of what went wrong</p>
-
<marker id="account"></marker>
</section>
<funcs>
<func>
- <name since="">account(Pid, Account) -> ok | {error, Reason}</name>
+ <name since="" name="account" arity="2" />
<fsummary>Specifies which account to use.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Account = string()</v>
- <v>Reason = eacct | common_reason()</v>
- </type>
<desc>
<p>Sets the account for an operation, if needed.</p>
@@ -288,15 +103,10 @@
</func>
<func>
- <name since="">append(Pid, LocalFile) -> </name>
- <name since="">append(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
- <fsummary>Transfers a file to remote server, and appends it to
+ <name since="" name="append" arity="2"/>
+ <name since="" name="append" arity="3"/>
+ <fsummary>Transfers a file to remote server, and appends it to
<c>Remotefile</c>.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>LocalFile = RemoteFile = string()</v>
- <v>Reason = epath | elogin | etnospc | epnospc | efnamena | common_reason</v>
- </type>
<desc>
<p>Transfers the file <c>LocalFile</c> to the remote server. If
<c>RemoteFile</c> is specified, the name of the remote file that the
@@ -309,17 +119,11 @@
</func>
<func>
- <name since="">append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="" name="append_bin" arity="3"/>
<fsummary>Transfers a binary into a remote file.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Bin = binary()</v>
- <v>RemoteFile = string()</v>
- <v>Reason = restriction_reason()| shortage_reason() | common_reason()</v>
- </type>
<desc>
<p>Transfers the binary <c>Bin</c> to the remote server and appends
- it to the file <c>RemoteFile</c>. If the file does not exist, it
+ it to the file <c><anno>RemoteFile</anno></c>. If the file does not exist, it
is created.</p>
<marker id="append_chunk"></marker>
@@ -327,15 +131,10 @@
</func>
<func>
- <name since="">append_chunk(Pid, Bin) -> ok | {error, Reason}</name>
+ <name since="" name="append_chunk" arity="2" />
<fsummary>Appends a chunk to the remote file.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Bin = binary()</v>
- <v>Reason = echunk | restriction_reason() | common_reason()</v>
- </type>
<desc>
- <p>Transfers the chunk <c>Bin</c> to the remote server, which
+ <p>Transfers the chunk <c><anno>Bin</anno></c> to the remote server, which
appends it to the file specified in the call to
<c>append_chunk_start/2</c>.</p>
<p>For some errors, for example, file system full, it is
@@ -347,16 +146,11 @@
</func>
<func>
- <name since="">append_chunk_start(Pid, File) -> ok | {error, Reason}</name>
+ <name since="" name="append_chunk_start" arity="2" />
<fsummary>Starts transfer of file chunks for appending to <c>File</c>.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>File = string()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
<p>Starts the transfer of chunks for appending to the file
- <c>File</c> at the remote server. If the file does not exist,
+ <c><anno>RemoteFile</anno></c> at the remote server. If the file does not exist,
it is created.</p>
<marker id="append_chunk_end"></marker>
@@ -364,12 +158,8 @@
</func>
<func>
- <name since="">append_chunk_end(Pid) -> ok | {error, Reason}</name>
+ <name since="" name="append_chunk_end" arity="1"/>
<fsummary>Stops transfer of chunks for appending.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Reason = echunk | restriction_reason() | shortage_reason() </v>
- </type>
<desc>
<p>Stops transfer of chunks for appending to the remote server.
The file at the remote server, specified in the call to
@@ -380,27 +170,19 @@
</func>
<func>
- <name since="">cd(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="" name="cd" arity="2" />
<fsummary>Changes remote working directory.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Dir = string()</v>
- <v>Reason = restriction_reason() | common_reason() </v>
- </type>
<desc>
<p>Changes the working directory at the remote server to
- <c>Dir</c>.</p>
+ <c><anno>Dir</anno></c>.</p>
<marker id="close"></marker>
</desc>
</func>
<func>
- <name since="">close(Pid) -> ok</name>
+ <name since="" name="close" arity="1" />
<fsummary>Ends the FTP session.</fsummary>
- <type>
- <v>Pid = pid()</v>
- </type>
<desc>
<p>Ends an FTP session, created using function
<seeerl marker="#open">open</seeerl>.</p>
@@ -410,13 +192,8 @@
</func>
<func>
- <name since="">delete(Pid, File) -> ok | {error, Reason}</name>
+ <name since="" name="delete" arity="2"/>
<fsummary>Deletes a file at the remote server.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>File = string()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
<p>Deletes the file <c>File</c> at the remote server.</p>
@@ -425,11 +202,8 @@
</func>
<func>
- <name since="">formaterror(Tag) -> string()</name>
+ <name since="" name="formaterror" arity="1"/>
<fsummary>Returns error diagnostics.</fsummary>
- <type>
- <v>Tag = {error, atom()} | atom()</v>
- </type>
<desc>
<p>Given an error return value <c>{error, AtomReason}</c>,
this function returns a readable string describing the error.</p>
@@ -439,26 +213,18 @@
</func>
<func>
- <name since="">lcd(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="" name="lcd" arity="2" />
<fsummary>Changes local working directory.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Dir = string()</v>
- <v>Reason = restriction_reason()</v>
- </type>
<desc>
- <p>Changes the working directory to <c>Dir</c> for the local client.</p>
+ <p>Changes the working directory to <c><anno>Dir</anno></c> for the local client.</p>
<marker id="lpwd"></marker>
</desc>
</func>
<func>
- <name since="">lpwd(Pid) -> {ok, Dir}</name>
+ <name since="" name="lpwd" arity="1" />
<fsummary>Gets local current working directory.</fsummary>
- <type>
- <v>Pid = pid()</v>
- </type>
<desc>
<p>Returns the current working directory at the local client.</p>
@@ -469,21 +235,15 @@
</func>
<func>
- <name since="">ls(Pid) -> </name>
- <name since="">ls(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
+ <name since="" name="ls" arity="1" />
+ <name since="" name="ls" arity="2" />
<fsummary>List of files.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Pathname = string()</v>
- <v>Listing = string()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
<p>Returns a list of files in long format.</p>
- <p><c>Pathname</c> can be a directory, a group of files, or
- a file. The <c>Pathname</c> string can contain wildcards.</p>
+ <p><c><anno>Dir</anno></c> can be a directory or
+ a file. The <c><anno>Dir</anno></c> string can contain wildcards.</p>
<p><c>ls/1</c> implies the current remote directory of the user.</p>
- <p>The format of <c>Listing</c> depends on the operating system.
+ <p>The format of <c><anno>Listing</anno></c> depends on the operating system.
On UNIX, it is typically produced from the output of the
<c>ls -l</c> shell command.</p>
@@ -492,15 +252,10 @@
</func>
<func>
- <name since="">mkdir(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="" name="mkdir" arity="2" />
<fsummary>Creates a remote directory.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Dir = string()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
- <p>Creates the directory <c>Dir</c> at the remote server.</p>
+ <p>Creates the directory <c><anno>Dir</anno></c> at the remote server.</p>
<marker id="nlist"></marker>
<marker id="nlist1"></marker>
@@ -509,21 +264,15 @@
</func>
<func>
- <name since="">nlist(Pid) -> </name>
- <name since="">nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
+ <name since="" name="nlist" arity="1" />
+ <name since="" name="nlist" arity="2" />
<fsummary>List of files.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Pathname = string()</v>
- <v>Listing = string()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
<p>Returns a list of files in short format.</p>
- <p><c>Pathname</c> can be a directory, a group of files, or
- a file. The <c>Pathname</c> string can contain wildcards.</p>
+ <p><c><anno>Pathname</anno></c> can be a directory or
+ a file. The <c><anno>Pathname</anno></c> string can contain wildcards.</p>
<p><c>nlist/1</c> implies the current remote directory of the user.</p>
- <p>The format of <c>Listing</c> is a stream of
+ <p>The format of <c><anno>Listing</anno></c> is a stream of
filenames where each filename is separated by &lt;CRLF&gt; or
&lt;NL&gt;. Contrary to function <c>ls</c>, the purpose of
<c>nlist</c> is to enable a program to
@@ -534,76 +283,229 @@
</func>
<func>
- <name since="">open(Host) -> {ok, Pid} | {error, Reason}</name>
- <name since="">open(Host, Opts) -> {ok, Pid} | {error, Reason}</name>
+ <name since="" name="open" arity="1" />
+ <name since="" name="open" arity="2" />
<fsummary>Starts a FTP client.</fsummary>
- <type>
- <v>Host = string() | ip_address()</v>
- <v>Opts = options()</v>
- <v>options() = [option()]</v>
- <v>option() = start_option() | open_option()</v>
- <v>start_option() = {verbose, verbose()} | {debug, debug()}</v>
- <v>verbose() = boolean() (default is false)</v>
- <v>debug() = disable | debug | trace (default is disable)</v>
- <v>open_option() = {ipfamily, ipfamily()} | {port, port()} | {mode, mode()} | {tls, tls_options()} | {tls_sec_method, tls_sec_method()} | {tls_ctrl_session_reuse, boolean() (default is false)} | {timeout, timeout()} | {dtimeout, dtimeout()} | {progress, progress()} | {sock_ctrl, sock_opts()} | {sock_data_act, sock_opts()} | {sock_data_pass, sock_opts()}</v>
- <v>ipfamily() = inet | inet6 | inet6fb4 (default is inet)</v>
- <v>port() = non_neg_integer() (default is 0 which aliases to 21 or 990 when used with {tls_sec_method,ftps})</v>
- <v>mode() = active | passive (default is passive)</v>
- <v>tls_options() = [<seetype marker="ssl:ssl#tls_option">ssl:tls_option()</seetype>]</v>
- <v>tls_sec_method() = ftps | ftpes (default is ftpes)</v>
- <v>sock_opts() = [<seetype marker="kernel:gen_tcp#option">gen_tcp:option()</seetype> except for ipv6_v6only, active, packet, mode, packet_size and header</v>
- <v>timeout() = integer() > 0 (default is 60000 milliseconds)</v>
- <v>dtimeout() = integer() > 0 | infinity (default is infinity)</v>
- <v>progress() = ignore | {module(), function(), initial_data()} (default is ignore)</v>
- <v>module() = atom()</v>
- <v>function() = atom()</v>
- <v>initial_data() = term()</v>
- <v>Reason = ehost | term()</v>
- </type>
-
<desc>
<p>Starts a FTP client process and
opens a session with the FTP server at <c>Host</c>. </p>
+ <p>A session opened in this way is closed using function
+ <seeerl marker="#close">close</seeerl>.</p>
- <p>If option <c>{tls, tls_options()}</c> is present, the FTP session
- is transported over <c>tls</c> (<c>ftps</c>, see
- <url href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</url>).
- The list <c>tls_options()</c> can be empty. The function
- <seemfa marker="ssl:ssl#connect/3"><c>ssl:connect/3</c></seemfa>
+ <p>The available configuration options are as follows:</p>
+
+ <taglist>
+ <tag>{host, Host}</tag>
+ <item>
+ <marker id="host"></marker>
+ <p>Host = <c>string() | ip_address()</c></p>
+ </item>
+
+ <tag>{port, Port}</tag>
+ <item>
+ <marker id="port"></marker>
+ <p>Default is <c>0</c> which aliases to <c>21</c> or <c>990</c> when used with
+ <seeerl marker="#open"><c>{tls_sec_method,ftps}</c></seeerl>).</p>
+ </item>
+
+ <tag>{mode, Mode}</tag>
+ <item>
+ <marker id="mode"></marker>
+ <p>Default is <c>passive</c>.</p>
+ </item>
+
+ <tag>{verbose, Verbose}</tag>
+ <item>
+ <marker id="verbose"></marker>
+ <p>Determines if the FTP communication is to be
+ verbose or not.</p>
+ <p>Default is <c>false</c>.</p>
+ </item>
+
+ <tag>{debug, Debug}</tag>
+ <item>
+ <marker id="debug"></marker>
+ <p>Debugging using the dbg toolkit. </p>
+ <p>Default is <c>disable</c>.</p>
+ </item>
+
+ <tag>{ipfamily, IpFamily}</tag>
+ <item>
+ <marker id="ipfamily"></marker>
+ <p>With <c>inet6fb4</c> the client behaves as before, that is,
+ tries to use IPv6, and only if that does not work it
+ uses IPv4).</p>
+ <p>Default is <c>inet</c> (IPv4).</p>
+ </item>
+
+ <tag>{timeout, Timeout}</tag>
+ <item>
+ <marker id="timeout"></marker>
+ <p>Connection time-out.</p>
+ <p>Default is <c>60000</c> (milliseconds).</p>
+ </item>
+
+ <tag>{dtimeout, DTimeout}</tag>
+ <item>
+ <marker id="dtimeout"></marker>
+ <p>Data connect time-out.
+ The time the client waits for the server to connect to the
+ data socket.</p>
+ <p>Default is <c>infinity</c>. </p>
+ </item>
+
+ <tag>{tls, TLSOptions}</tag>
+ <item><marker id="tls_options"></marker>
+ <p>The FTP session is transported over <c>tls</c> (<c>ftps</c>, see
+ <url href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</url>).
+ The list <c><anno>TLSOptions</anno></c> can be empty. The function
+ <seemfa marker="ssl:ssl#connect/3"><c>ssl:connect/3</c></seemfa>
is used for securing both the control connection and the data sessions.
</p>
+ </item>
- <p>The suboption <c>{tls_sec_method, tls_sec_method()}</c> (defaults to
- <c>ftpes</c>) when set to <c>ftps</c> will connect immediately with SSL
+ <tag>{tls_sec_method, TLSSecMethod}</tag>
+ <item><marker id="tls_sec_method"></marker>
+ <p>When set to <c>ftps</c> will connect immediately with SSL
instead of upgrading with STARTTLS. This suboption is ignored unless
- the suboption <c>tls</c> is also set.
- </p>
+ the suboption <c>tls</c> is also set.</p>
+ <p>Default is <c>ftpes</c></p>
+ </item>
- <p>The option <c>{tls_ctrl_session_reuse, boolean()}</c> (defaults to
- <c>false</c>) when set to <c>true</c> the client will re-use the
+ <tag>{tls_ctrl_session_reuse, boolean()}</tag>
+ <item><marker id="tls_ctrl_session_reuse"></marker>
+ <p>When set to <c>true</c> the client will re-use the
TLS session from the control channel on the data channel as enforced by
many FTP servers as (<url href="https://scarybeastsecurity.blogspot.com/2009/02/vsftpd-210-released.html">proposed and implemented first by vsftpd</url>).
</p>
+ <p>Default is <c>false</c>.</p>
+ </item>
+
+ <tag>{sock_ctrl, SocketCtrls :: [SocketControl :: gen_tcp:option()]}</tag>
+ <item>
+ <p>Passes options from <c>SocketCtrls</c> down to the underlying transport layer (tcp).</p>
+ <p><seetype marker="kernel:gen_tcp#option">gen_tcp:option()</seetype> except for <c>ipv6_v6only</c>,
+ <c>active</c>, <c>packet</c>, <c>mode</c>, <c>packet_size</c> and <c>header</c>.</p>
+ <p>Default value is <c>SocketCtrls = []</c>.</p>
+ </item>
+
+ <tag>{sock_data_act, [SocketControl]}</tag>
+ <item>
+ <p>Passes options from <c>[SocketControl]</c> down to the underlying transport layer (tcp).</p>
+ <p><c>sock_data_act</c> uses the value of <c>sock_ctrl</c> as default value.</p>
+ </item>
+
+ <tag>{sock_data_pass, [SocketControl]}</tag>
+ <item>
+ <p>Passes options from <c>[SocketControl]</c> down to the underlying transport layer (tcp).
+ </p>
+ <p><c>sock_data_pass</c> uses the value of <c>sock_ctrl</c> as default value.</p>
+ </item>
- <p>The options <c>sock_ctrl</c>, <c>sock_data_act</c> and <c>sock_data_pass</c> passes options down to
- the underlying transport layer (tcp). The default value for <c>sock_ctrl</c> is <c>[]</c>. Both
- <c>sock_data_act</c> and <c>sock_data_pass</c> uses the value of <c>sock_ctrl</c> as default value.
- </p>
+ <tag>{progress, Progress}</tag>
+ <item>
+ <marker id="progress"></marker>
+ <p>Progress = <c>ignore | {Module, Function, InitialData}</c></p>
+ <p><c>Module = atom()</c>, <c>Function = atom()</c></p>
+ <p><c>InitialData = term()</c></p>
+ <p>Default is <c>ignore</c>.</p>
+
+ <p>Option <c>progress</c> is intended to be used by applications that
+ want to create some type of progress report, such as a progress bar in
+ a GUI. Default for the progress option is <c>ignore</c>,
+ that is, the option is not used. When the progress option is
+ specified, the following happens when <c>ftp:send/[3,4]</c> or
+ <c>ftp:recv/[3,4]</c> are called:</p>
+
+ <list type="bulleted">
+ <item>
+ <p>Before a file is transferred, the following call is
+ made to indicate the start of the file transfer and how large
+ the file is. The return value of the callback function
+ is to be a new value for the <c>UserProgressTerm</c> that will
+ be used as input the next time the callback function is
+ called.</p>
+ <p><c>
+ Module:Function(InitialData, File, {file_size, FileSize})
+ </c></p>
+ </item>
+
+ <item>
+ <p>Every time a chunk of bytes is transferred the
+ following call is made:</p>
+ <p><c>
+ Module:Function(UserProgressTerm, File, {transfer_size, TransferSize})
+ </c></p>
+ </item>
+
+ <item>
+ <p>At the end of the file the following call is
+ made to indicate the end of the transfer:</p>
+ <p><c>
+ Module:Function(UserProgressTerm, File, {transfer_size, 0})
+ </c></p>
+ </item>
+ </list>
+
+ <p>The callback function is to be defined as follows:</p>
+
+ <p><c>
+ Module:Function(UserProgressTerm, File, Size) -> UserProgressTerm
+ </c></p>
+
+ <p><c>
+ UserProgressTerm = term()
+ </c></p>
+
+ <p><c>
+ File = string()
+ </c></p>
+
+ <p><c>
+ Size = {transfer_size, integer()} | {file_size, integer()} | {file_size, unknown}
+ </c></p>
+
+ <p>For remote files, <c>ftp</c> cannot determine the
+ file size in a platform independent way. In this case the size
+ becomes <c>unknown</c> and it is left to the application to
+ determine the size.</p>
+
+ <note>
+ <p>The callback is made by a middleman process, hence the
+ file transfer is not affected by the code in the progress
+ callback function. If the callback crashes, this is
+ detected by the FTP connection process, which then prints an
+ info-report and goes on as if the progress option was set
+ to <c>ignore</c>.</p>
+ </note>
+
+ <p>The file transfer type is set to the default of the FTP server
+ when the session is opened. This is usually ASCII mode.
+ </p>
+
+ <p>The current local working directory (compare <c>lpwd/1</c>) is set
+ to the value reported by <c>file:get_cwd/1</c>, the wanted
+ local directory.
+ </p>
+
+ <p>The return value <c>Pid</c> is used as a reference to the
+ newly created FTP client in all other functions, and they are to
+ be called by the process that created the connection. The FTP
+ client process monitors the process that created it and
+ terminates if that process terminates.</p>
+
+ </item>
+
+ </taglist>
- <p>A session opened in this way is closed using function
- <seeerl marker="#close">close</seeerl>.</p>
<marker id="pwd"></marker>
</desc>
</func>
<func>
- <name since="">pwd(Pid) -> {ok, Dir} | {error, Reason}</name>
+ <name since="" name="pwd" arity="1"/>
<fsummary>Gets the remote current working directory.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
<p>Returns the current working directory at the remote server.</p>
@@ -614,23 +516,17 @@
</func>
<func>
- <name since="">recv(Pid, RemoteFile) -> </name>
- <name since="">recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason}</name>
+ <name since="" name="recv" arity="2"/>
+ <name since="" name="recv" arity="3"/>
<fsummary>Transfers a file from remote server.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>RemoteFile = LocalFile = string()</v>
- <v>Reason = restriction_reason() | common_reason() | file_write_error_reason() </v>
- <v>file_write_error_reason() = see file:write/2</v>
- </type>
<desc>
- <p>Transfers the file <c>RemoteFile</c> from the remote server
+ <p>Transfers the file <c><anno>RemoteFileName</anno></c> from the remote server
to the file system of the local client. If
- <c>LocalFile</c> is specified, the local file will be
- <c>LocalFile</c>, otherwise
- <c>RemoteFile</c>.</p>
- <p>If the file write fails (for example, <c>enospc</c>), the command is
- aborted and <c>{error, file_write_error_reason()}</c> is returned.
+ <c><anno>LocalFileName</anno></c> is specified, the local file will be
+ <c><anno>LocalFileName</anno></c>, otherwise
+ <c><anno>RemoteFileName</anno></c>.</p>
+ <p>If the file write fails, the command is
+ aborted and <c>{error, term()}</c> is returned.
However, the file is <em>not</em> removed.</p>
<marker id="recv_bin"></marker>
@@ -638,16 +534,10 @@
</func>
<func>
- <name since="">recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason}</name>
+ <name since="" name="recv_bin" arity="2"/>
<fsummary>Transfers a file from remote server as a binary.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Bin = binary()</v>
- <v>RemoteFile = string()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
- <p>Transfers the file <c>RemoteFile</c> from the remote server and
+ <p>Transfers the file <c><anno>RemoteFile</anno></c> from the remote server and
receives it as a binary.</p>
<marker id="recv_chunk_start"></marker>
@@ -655,15 +545,10 @@
</func>
<func>
- <name since="">recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="" name="recv_chunk_start" arity="2"/>
<fsummary>Starts chunk-reading of the remote file.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>RemoteFile = string()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
- <p>Starts transfer of the file <c>RemoteFile</c> from the
+ <p>Starts transfer of the file <c><anno>RemoteFile</anno></c> from the
remote server.</p>
<marker id="recv_chunk"></marker>
@@ -671,15 +556,10 @@
</func>
<func>
- <name since="">recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason}</name>
+ <name since="" name="recv_chunk" arity="1" />
<fsummary>Receives a chunk of the remote file.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Bin = binary()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
- <p>Receives a chunk of the remote file (<c>RemoteFile</c> of
+ <p>Receives a chunk of the remote file (<c>RemoteFile</c> of
<c>recv_chunk_start</c>). The return values have the following
meaning:</p>
<list type="bulleted">
@@ -693,30 +573,20 @@
</func>
<func>
- <name since="">rename(Pid, Old, New) -> ok | {error, Reason}</name>
+ <name since="" name="rename" arity="3"/>
<fsummary>Renames a file at the remote server.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>CurrFile = NewFile = string()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
- <p>Renames <c>Old</c> to <c>New</c> at the remote server.</p>
+ <p>Renames <c><anno>Old</anno></c> to <c><anno>New</anno></c> at the remote server.</p>
<marker id="rmdir"></marker>
</desc>
</func>
<func>
- <name since="">rmdir(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="" name="rmdir" arity="2" />
<fsummary>Removes a remote directory.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Dir = string()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
- <p>Removes directory <c>Dir</c> at the remote server.</p>
+ <p>Removes directory <c><anno>Dir</anno></c> at the remote server.</p>
<marker id="send"></marker>
<marker id="send2"></marker>
@@ -725,34 +595,23 @@
</func>
<func>
- <name since="">send(Pid, LocalFile) -></name>
- <name since="">send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="" name="send" arity="2"/>
+ <name since="" name="send" arity="3"/>
<fsummary>Transfers a file to the remote server.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>LocalFile = RemoteFile = string()</v>
- <v>Reason = restriction_reason() | common_reason() | shortage_reason()</v>
- </type>
<desc>
- <p>Transfers the file <c>LocalFile</c> to the remote server. If
- <c>RemoteFile</c> is specified, the name of the remote file is set
- to <c>RemoteFile</c>, otherwise to <c>LocalFile</c>.</p>
+ <p>Transfers the file <c><anno>LocalFileName</anno></c> to the remote server. If
+ <c><anno>RemoteFileName</anno></c> is specified, the name of the remote file is set
+ to <c><anno>RemoteFileName</anno></c>, otherwise to <c><anno>LocalFileName</anno></c>.</p>
<marker id="send_bin"></marker>
</desc>
</func>
<func>
- <name since="">send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="" name="send_bin" arity="3" />
<fsummary>Transfers a binary into a remote file.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Bin = binary()</v>
- <v>RemoteFile = string()</v>
- <v>Reason = restriction_reason() | common_reason() | shortage_reason()</v>
- </type>
<desc>
- <p>Transfers the binary <c>Bin</c> into the file <c>RemoteFile</c>
+ <p>Transfers the binary <c><anno>Bin</anno></c> into the file <c><anno>RemoteFile</anno></c>
at the remote server.</p>
<marker id="send_chunk"></marker>
@@ -760,15 +619,10 @@
</func>
<func>
- <name since="">send_chunk(Pid, Bin) -> ok | {error, Reason}</name>
+ <name since="" name="send_chunk" arity="2" />
<fsummary>Writes a chunk to the remote file.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Bin = binary()</v>
- <v>Reason = echunk | restriction_reason() | common_reason()</v>
- </type>
<desc>
- <p>Transfers the chunk <c>Bin</c> to the remote server, which
+ <p>Transfers the chunk <c><anno>Bin</anno></c> to the remote server, which
writes it into the file specified in the call to
<c>send_chunk_start/2</c>.</p>
<p>For some errors, for example, file system full, it is
@@ -780,15 +634,10 @@
</func>
<func>
- <name since="">send_chunk_start(Pid, File) -> ok | {error, Reason}</name>
+ <name since="" name="send_chunk_start" arity="2" />
<fsummary>Starts transfer of file chunks.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>File = string()</v>
- <v>Reason = restriction_reason() | common_reason()</v>
- </type>
<desc>
- <p>Starts transfer of chunks into the file <c>File</c> at the
+ <p>Starts transfer of chunks into the file <c><anno>RemoteFile</anno></c> at the
remote server.</p>
<marker id="send_chunk_end"></marker>
@@ -796,12 +645,8 @@
</func>
<func>
- <name since="">send_chunk_end(Pid) -> ok | {error, Reason}</name>
+ <name since="" name="send_chunk_end" arity="1" />
<fsummary>Stops transfer of chunks.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Reason = restriction_reason() | common_reason() | shortage_reason()</v>
- </type>
<desc>
<p>Stops transfer of chunks to the remote server. The file at the
remote server, specified in the call to <c>send_chunk_start/2</c>
@@ -812,44 +657,8 @@
</func>
<func>
- <name since="OTP 21.0">start_service(ServiceConfig) -> {ok, Pid} | {error, Reason}</name>
- <fsummary>Dynamically starts an <c>FTP</c>
- session after the <c>ftp</c> application has been started.</fsummary>
- <type>
- <v>ServiceConfig = [{Option, Value}]</v>
- <v>Option = property()</v>
- <v>Value = term()</v>
- </type>
- <desc>
- <p>Dynamically starts an <c>FTP</c> session after the <c>ftp</c>
- application has been started.</p>
- <note>
- <p>As long as the <c>ftp</c> application is operational,
- the FTP sessions are supervised and can be soft code upgraded.</p>
- </note>
- </desc>
- </func>
-
- <func>
- <name since="OTP 21.0">stop_service(Reference) -> ok | {error, Reason} </name>
- <fsummary>Stops an FTP session.</fsummary>
- <type>
- <v>Reference = pid() | term() - service-specified reference</v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p>Stops a started FTP session.</p>
- </desc>
- </func>
-
- <func>
- <name since="">type(Pid, Type) -> ok | {error, Reason}</name>
+ <name since="" name="type" arity="2" />
<fsummary>Sets transfer type to <c>ascii</c>or <c>binary</c>.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Type = ascii | binary</v>
- <v>Reason = etype | restriction_reason() | common_reason()</v>
- </type>
<desc>
<p>Sets the file transfer type to <c>ascii</c> or <c>binary</c>. When
an FTP session is opened, the default transfer type of the
@@ -860,45 +669,30 @@
</func>
<func>
- <name since="">user(Pid, User, Password) -> ok | {error, Reason}</name>
+ <name since="" name="user" arity="3"/>
<fsummary>User login.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>User = Password = string()</v>
- <v>Reason = euser | common_reason()</v>
- </type>
<desc>
- <p>Performs login of <c>User</c> with <c>Password</c>.</p>
+ <p>Performs login of <c><anno>User</anno></c> with <c><anno>Pass</anno></c>.</p>
<marker id="user4"></marker>
</desc>
</func>
<func>
- <name since="">user(Pid, User, Password, Account) -> ok | {error, Reason}</name>
+ <name since="" name="user" arity="4" />
<fsummary>User login.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>User = Password = string()</v>
- <v>Reason = euser | common_reason() </v>
- </type>
<desc>
- <p>Performs login of <c>User</c> with <c>Password</c> to the account
- specified by <c>Account</c>.</p>
+ <p>Performs login of <c><anno>User</anno></c> with <c><anno>Pass</anno></c> to the account
+ specified by <c><anno>Account</anno></c>.</p>
<marker id="quote"></marker>
</desc>
</func>
<func>
- <name since="">quote(Pid, Command) -> [FTPLine]</name>
+ <name since="" name="quote" arity="2" />
<fsummary>Sends an arbitrary FTP command.</fsummary>
- <type>
- <v>Pid = pid()</v>
- <v>Command = string()</v>
- <v>FTPLine = string()</v>
- </type>
- <desc><note><p>The telnet end of line characters, from the FTP
+ <desc><note><p>The telnet end of line characters, from the FTP
protocol definition, CRLF, for example, "\\r\\n" has been removed.</p></note>
<p>Sends an arbitrary FTP command and returns verbatim a list
of the lines sent back by the FTP server. This function is
diff --git a/lib/ftp/doc/src/specs.xml b/lib/ftp/doc/src/specs.xml
new file mode 100644
index 0000000000..a01aa83f78
--- /dev/null
+++ b/lib/ftp/doc/src/specs.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<specs xmlns:xi="http://www.w3.org/2001/XInclude">
+ <xi:include href="../specs/specs_ftp.xml"/>
+</specs>
diff --git a/lib/ftp/src/Makefile b/lib/ftp/src/Makefile
index 7f18498b13..cd93c55a94 100644
--- a/lib/ftp/src/Makefile
+++ b/lib/ftp/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2022. All Rights Reserved.
+# Copyright Ericsson AB 1999-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ BEHAVIOUR_MODULES=
MODULES= \
ftp \
+ ftp_internal \
ftp_app \
ftp_progress \
ftp_response \
diff --git a/lib/ftp/src/ftp.app.src b/lib/ftp/src/ftp.app.src
index 1592e7dafa..88b194f5b5 100644
--- a/lib/ftp/src/ftp.app.src
+++ b/lib/ftp/src/ftp.app.src
@@ -12,6 +12,7 @@
ftp,
ftp_app,
ftp_progress,
+ ftp_internal,
ftp_response,
ftp_sup
]},
diff --git a/lib/ftp/src/ftp.erl b/lib/ftp/src/ftp.erl
index 07bc5184e2..94d0c10e72 100644
--- a/lib/ftp/src/ftp.erl
+++ b/lib/ftp/src/ftp.erl
@@ -21,19 +21,13 @@
-module(ftp).
--behaviour(gen_server).
-
--deprecated([{start_service, 1, "use ftp:open/2 instead"},
- {stop_service, 1, "use ftp:close/1 instead"}]).
+-removed([{start_service, 1, "use ftp:open/2 instead"},
+ {stop_service, 1, "use ftp:close/1 instead"}]).
-export([start/0,
- start_service/1,
- stop/0,
- stop_service/1
+ stop/0
]).
--export([start_link/1, start_link/2]).
-
%% API - Client interface
-export([cd/2, close/1, delete/2, formaterror/1,
lcd/2, lpwd/1, ls/1, ls/2,
@@ -50,83 +44,8 @@
append_chunk/2, append_chunk_end/1, append_chunk_start/2,
info/1, latest_ctrl_response/1]).
-%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2,
- handle_info/2, terminate/2, code_change/3]).
-
-include("ftp_internal.hrl").
-%% Constants used in internal state definition
--define(CONNECTION_TIMEOUT, 60*1000).
--define(DATA_ACCEPT_TIMEOUT, infinity).
--define(DEFAULT_MODE, passive).
--define(PROGRESS_DEFAULT, ignore).
--define(FTP_EXT_DEFAULT, false).
-
-%% Internal Constants
--define(FTP_PORT, 21).
--define(FTPS_PORT, 990).
--define(FILE_BUFSIZE, 4096).
-
-
-%%%=========================================================================
-%%% Data Types
-%%%=========================================================================
-
-%% Internal state
--record(state, {
- csock = undefined, % socket() - Control connection socket
- dsock = undefined, % socket() - Data connection socket
- tls_options = undefined, % list()
- verbose = false, % boolean()
- ldir = undefined, % string() - Current local directory
- type = ftp_server_default, % atom() - binary | ascii
- chunk = false, % boolean() - Receiving data chunks
- mode = ?DEFAULT_MODE, % passive | active
- timeout = ?CONNECTION_TIMEOUT, % integer()
- %% Data received so far on the data connection
- data = <<>>, % binary()
- %% Data received so far on the control connection
- %% {BinStream, AccLines}. If a binary sequence
- %% ends with ?CR then keep it in the binary to
- %% be able to detect if the next received byte is ?LF
- %% and hence the end of the response is reached!
- ctrl_data = {<<>>, [], start}, % {binary(), [bytes()], LineStatus}
- %% pid() - Client pid (note not the same as "From")
- latest_ctrl_response = "",
- owner = undefined,
- client = undefined, % "From" to be used in gen_server:reply/2
- %% Function that activated a connection and maybe some
- %% data needed further on.
- caller = undefined, % term()
- ipfamily, % inet | inet6 | inet6fb4
- sockopts_ctrl = [],
- sockopts_data_passive = [],
- sockopts_data_active = [],
- progress = ignore, % ignore | pid()
- dtimeout = ?DATA_ACCEPT_TIMEOUT, % non_neg_integer() | infinity
- tls_ctrl_session_reuse = false, % boolean()
- tls_upgrading_data_connection = false,
- ftp_extension = ?FTP_EXT_DEFAULT
- }).
-
--record(recv_chunk_closing, {
- dconn_closed = false,
- pos_compl_received = false,
- client_called_us = false
- }).
-
-
--type shortage_reason() :: 'etnospc' | 'epnospc'.
--type restriction_reason() :: 'epath' | 'efnamena' | 'elogin' | 'enotbinary'.
--type common_reason() :: 'econn' | 'eclosed' | term().
--type file_write_error_reason() :: term(). % See file:write for more info
-
--define(DBG(F,A), 'n/a').
-%%-define(DBG(F,A), io:format(F,A)).
-%%-define(DBG(F,A), ct:pal("~p:~p " ++ if is_list(F) -> F; is_atom(F) -> atom_to_list(F) end, [?MODULE,?LINE|A])).
-
-
%%%=========================================================================
%%% API
%%%=========================================================================
@@ -134,143 +53,95 @@
start() ->
application:start(ftp).
-%% This should be made an internal function when we remove the deprecation
-%% ftp client processes should always be part of ftp supervisor tree.
-%% We consider it a bug that the "standalone" concept of inets was
-%% not removed when ftp was broken out, and it is now fixed.
-start_service(Options) ->
- try
- {ok, StartOptions} = start_options(Options),
- case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
- {ok, Pid} ->
- call(Pid, {open, ip_comm, Options}, plain);
- Error1 ->
- Error1
- end
- catch
- throw:Error2 ->
- Error2
- end.
-
stop() ->
application:stop(ftp).
-stop_service(Pid) ->
- close(Pid).
%%%=========================================================================
%%% API - CLIENT FUNCTIONS
%%%=========================================================================
%%--------------------------------------------------------------------------
-%% open(HostOrOtpList, <Port>, <Flags>) -> {ok, Pid} | {error, ehost}
-%% HostOrOtpList = string() | [{option_list, Options}]
-%% Port = integer(),
-%% Flags = [Flag],
-%% Flag = verbose | debug | trace
-%%
%% Description: Start an ftp client and connect to a host.
%%--------------------------------------------------------------------------
-spec open(Host :: string() | inet:ip_address()) ->
- {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}.
+ {'ok', Pid :: pid()} | {'error', Reason :: term()}.
%% <BACKWARD-COMPATIBILLITY>
open({option_list, Options}) when is_list(Options) ->
- start_service(Options);
+ ftp_internal:start_service(Options);
%% </BACKWARD-COMPATIBILLITY>
open(Host) ->
- open(Host, []).
-
--spec open(Host :: string() | inet:ip_address(), Opts :: list()) ->
- {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}.
-
-%% <BACKWARD-COMPATIBILLITY>
-open(Host, Port) when is_integer(Port) ->
- open(Host, [{port, Port}]);
-%% </BACKWARD-COMPATIBILLITY>
-
-open(Host, Options) when is_list(Options) ->
- start_service([{host,Host}|Options]).
+ ftp_internal:open(Host).
+
+
+-spec open(Host :: string() | inet:ip_address(), Opts) ->
+ {'ok', Pid :: pid()} | {'error', Reason :: term()} when
+ Opts :: [Opt],
+ Opt :: StartOption | OpenOption,
+ StartOption :: {verbose, Verbose} | {debug, Debug},
+ Verbose :: boolean(),
+ Debug :: disable | debug | trace,
+ OpenOption :: {ipfamily, IpFamily} | {port, Port :: port()} | {mode, Mode}
+ | {tls, TLSOptions :: [ssl:tls_option()]} | {tls_sec_method, TLSSecMethod :: ftps | ftpes}
+ | {tls_ctrl_session_reuse, TLSSessionReuse :: boolean() } | {timeout, Timeout :: timeout()}
+ | {dtimeout, DTimeout :: timeout()} | {progress, Progress} | {sock_ctrl, SocketCtrls}
+ | {sock_data_act, [SocketControl]} | {sock_data_pass, [SocketControl]},
+ SocketCtrls :: [SocketControl],
+ IpFamily :: inet | inet6 | inet6fb4,
+ Mode :: active | passive,
+ Module :: atom(),
+ Function :: atom(),
+ InitialData :: term(),
+ Progress :: ignore | {Module, Function, InitialData},
+ SocketControl :: gen_tcp:option().
+open(Host, Port) ->
+ ftp_internal:open(Host, Port).
%%--------------------------------------------------------------------------
-%% user(Pid, User, Pass, <Acc>) -> ok | {error, euser} | {error, econn}
-%% | {error, eacct}
-%% Pid = pid(),
-%% User = Pass = Acc = string()
-%%
%% Description: Login with or without a supplied account name.
%%--------------------------------------------------------------------------
-spec user(Pid :: pid(),
User :: string(),
Pass :: string()) ->
- 'ok' | {'error', Reason :: 'euser' | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
user(Pid, User, Pass) ->
- case {is_name_sane(User), is_name_sane(Pass)} of
- {true, true} ->
- call(Pid, {user, User, Pass}, atom);
- _ ->
- {error, euser}
- end.
+ ftp_internal:user(Pid, User, Pass).
-spec user(Pid :: pid(),
User :: string(),
Pass :: string(),
- Acc :: string()) ->
- 'ok' | {'error', Reason :: 'euser' | common_reason()}.
-
-user(Pid, User, Pass, Acc) ->
- case {is_name_sane(User), is_name_sane(Pass), is_name_sane(Acc)} of
- {true, true, true} ->
- call(Pid, {user, User, Pass, Acc}, atom);
- _ ->
- {error, euser}
- end.
+ Account :: string()) ->
+ 'ok' | {'error', Reason :: term()}.
+user(Pid, User, Pass, Account) ->
+ ftp_internal:user(Pid, User, Pass, Account).
%%--------------------------------------------------------------------------
-%% account(Pid, Acc) -> ok | {error, eacct}
-%% Pid = pid()
-%% Acc= string()
-%%
%% Description: Set a user Account.
%%--------------------------------------------------------------------------
-spec account(Pid :: pid(), Acc :: string()) ->
- 'ok' | {'error', Reason :: 'eacct' | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
account(Pid, Acc) ->
- case is_name_sane(Acc) of
- true ->
- call(Pid, {account, Acc}, atom);
- _ ->
- {error, eacct}
- end.
-
+ ftp_internal:account(Pid, Acc).
%%--------------------------------------------------------------------------
-%% pwd(Pid) -> {ok, Dir} | {error, elogin} | {error, econn}
-%% Pid = pid()
-%% Dir = string()
-%%
%% Description: Get the current working directory at remote server.
%%--------------------------------------------------------------------------
-spec pwd(Pid :: pid()) ->
{'ok', Dir :: string()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'error', Reason :: term()}.
pwd(Pid) ->
- call(Pid, pwd, ctrl).
-
+ ftp_internal:pwd(Pid).
%%--------------------------------------------------------------------------
-%% lpwd(Pid) -> {ok, Dir}
-%% Pid = pid()
-%% Dir = string()
-%%
%% Description: Get the current working directory at local server.
%%--------------------------------------------------------------------------
@@ -278,317 +149,179 @@ pwd(Pid) ->
{'ok', Dir :: string()}.
lpwd(Pid) ->
- call(Pid, lpwd, string).
+ ftp_internal:lpwd(Pid).
%%--------------------------------------------------------------------------
-%% cd(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
-%% Pid = pid()
-%% Dir = string()
-%%
%% Description: Change current working directory at remote server.
%%--------------------------------------------------------------------------
-spec cd(Pid :: pid(), Dir :: string()) ->
- 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
cd(Pid, Dir) ->
- case is_name_sane(Dir) of
- true ->
- call(Pid, {cd, Dir}, atom);
- _ ->
- {error, efnamena}
- end.
-
+ ftp_internal:cd(Pid, Dir).
%%--------------------------------------------------------------------------
-%% lcd(Pid, Dir) -> ok | {error, epath}
-%% Pid = pid()
-%% Dir = string()
-%%
%% Description: Change current working directory for the local client.
%%--------------------------------------------------------------------------
-spec lcd(Pid :: pid(), Dir :: string()) ->
- 'ok' | {'error', Reason :: restriction_reason()}.
+ 'ok' | {'error', Reason :: term()}.
lcd(Pid, Dir) ->
- call(Pid, {lcd, Dir}, string).
-
+ ftp_internal:lcd(Pid, Dir).
%%--------------------------------------------------------------------------
-%% ls(Pid) -> Result
-%% ls(Pid, <Dir>) -> Result
-%%
-%% Pid = pid()
-%% Dir = string()
-%% Result = {ok, Listing} | {error, Reason}
-%% Listing = string()
-%% Reason = epath | elogin | econn
-%%
%% Description: Returns a list of files in long format.
%%--------------------------------------------------------------------------
-spec ls(Pid :: pid()) ->
{'ok', Listing :: string()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'error', Reason :: term()}.
ls(Pid) ->
ls(Pid, "").
-spec ls(Pid :: pid(), Dir :: string()) ->
{'ok', Listing :: string()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'error', Reason :: term()}.
ls(Pid, Dir) ->
- case is_name_sane(Dir) of
- true ->
- call(Pid, {dir, long, Dir}, string);
- _ ->
- {error, efnamena}
- end.
+ ftp_internal:ls(Pid, Dir).
%%--------------------------------------------------------------------------
-%% nlist(Pid) -> Result
-%% nlist(Pid, Pathname) -> Result
-%%
-%% Pid = pid()
-%% Pathname = string()
-%% Result = {ok, Listing} | {error, Reason}
-%% Listing = string()
-%% Reason = epath | elogin | econn
-%%
%% Description: Returns a list of files in short format
%%--------------------------------------------------------------------------
-spec nlist(Pid :: pid()) ->
{'ok', Listing :: string()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'error', Reason :: term()}.
nlist(Pid) ->
nlist(Pid, "").
-spec nlist(Pid :: pid(), Pathname :: string()) ->
{'ok', Listing :: string()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'error', Reason :: term()}.
nlist(Pid, Dir) ->
- case is_name_sane(Dir) of
- true ->
- call(Pid, {dir, short, Dir}, string);
- _ ->
- {error, efnamena}
- end.
-
+ ftp_internal:nlist(Pid, Dir).
%%--------------------------------------------------------------------------
-%% rename(Pid, Old, New) -> ok | {error, epath} | {error, elogin}
-%% | {error, econn}
-%% Pid = pid()
-%% CurrFile = NewFile = string()
-%%
%% Description: Rename a file at remote server.
%%--------------------------------------------------------------------------
-spec rename(Pid :: pid(), Old :: string(), New :: string()) ->
- 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
rename(Pid, Old, New) ->
- case {is_name_sane(Old), is_name_sane(New)} of
- {true, true} ->
- call(Pid, {rename, Old, New}, string);
- _ ->
- {error, efnamena}
- end.
-
+ ftp_internal:rename(Pid, Old, New).
%%--------------------------------------------------------------------------
-%% delete(Pid, File) -> ok | {error, epath} | {error, elogin} |
-%% {error, econn}
-%% Pid = pid()
-%% File = string()
-%%
%% Description: Remove file at remote server.
%%--------------------------------------------------------------------------
-spec delete(Pid :: pid(), File :: string()) ->
- 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
delete(Pid, File) ->
- case is_name_sane(File) of
- true ->
- call(Pid, {delete, File}, string);
- _ ->
- {error, efnamena}
- end.
-
+ ftp_internal:delete(Pid, File).
%%--------------------------------------------------------------------------
-%% mkdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
-%% Pid = pid(),
-%% Dir = string()
-%%
%% Description: Make directory at remote server.
%%--------------------------------------------------------------------------
-spec mkdir(Pid :: pid(), Dir :: string()) ->
- 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
mkdir(Pid, Dir) ->
- case is_name_sane(Dir) of
- true ->
- call(Pid, {mkdir, Dir}, atom);
- _ ->
- {error, efnamena}
- end.
-
+ ftp_internal:mkdir(Pid, Dir).
%%--------------------------------------------------------------------------
-%% rmdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
-%% Pid = pid(),
-%% Dir = string()
-%%
%% Description: Remove directory at remote server.
%%--------------------------------------------------------------------------
-spec rmdir(Pid :: pid(), Dir :: string()) ->
- 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
rmdir(Pid, Dir) ->
- case is_name_sane(Dir) of
- true ->
- call(Pid, {rmdir, Dir}, atom);
- _ ->
- {error, efnamena}
- end.
-
+ ftp_internal:rmdir(Pid, Dir).
%%--------------------------------------------------------------------------
-%% type(Pid, Type) -> ok | {error, etype} | {error, elogin} | {error, econn}
-%% Pid = pid()
-%% Type = ascii | binary
-%%
%% Description: Set transfer type.
%%--------------------------------------------------------------------------
-spec type(Pid :: pid(), Type :: ascii | binary) ->
- 'ok' |
- {'error', Reason :: 'etype' | restriction_reason() | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
type(Pid, Type) ->
- call(Pid, {type, Type}, atom).
+ ftp_internal:type(Pid, Type).
%%--------------------------------------------------------------------------
-%% recv(Pid, RemoteFileName [, LocalFileName]) -> ok | {error, epath} |
-%% {error, elogin} | {error, econn}
-%% Pid = pid()
-%% RemoteFileName = LocalFileName = string()
-%%
%% Description: Transfer file from remote server.
%%--------------------------------------------------------------------------
-spec recv(Pid :: pid(), RemoteFileName :: string()) ->
- 'ok' | {'error', Reason :: restriction_reason() |
- common_reason() |
- file_write_error_reason()}.
+ 'ok' | {'error', Reason :: term()}.
-recv(Pid, RemotFileName) ->
- recv(Pid, RemotFileName, RemotFileName).
+recv(Pid, RemoteFileName) ->
+ ftp_internal:recv(Pid, RemoteFileName).
-spec recv(Pid :: pid(),
RemoteFileName :: string(),
LocalFileName :: string()) ->
'ok' | {'error', Reason :: term()}.
-recv(Pid, RemotFileName, LocalFileName) ->
- case is_name_sane(RemotFileName) of
- true ->
- call(Pid, {recv, RemotFileName, LocalFileName}, atom);
- _ ->
- {error, efnamena}
- end.
+recv(Pid, RemoteFileName, LocalFileName) ->
+ ftp_internal:recv(Pid, RemoteFileName, LocalFileName).
%%--------------------------------------------------------------------------
-%% recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, epath} | {error, elogin}
-%% | {error, econn}
-%% Pid = pid()
-%% RemoteFile = string()
-%% Bin = binary()
-%%
%% Description: Transfer file from remote server into binary.
%%--------------------------------------------------------------------------
-spec recv_bin(Pid :: pid(),
RemoteFile :: string()) ->
- {'ok', Bin :: binary()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'ok', Bin :: binary()} | {'error', Reason :: term()}.
recv_bin(Pid, RemoteFile) ->
- case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {recv_bin, RemoteFile}, bin);
- _ ->
- {error, efnamena}
- end.
+ ftp_internal:recv_bin(Pid, RemoteFile).
%%--------------------------------------------------------------------------
-%% recv_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath}
-%% | {error, econn}
-%% Pid = pid()
-%% RemoteFile = string()
-%%
%% Description: Start receive of chunks of remote file.
%%--------------------------------------------------------------------------
-spec recv_chunk_start(Pid :: pid(),
RemoteFile :: string()) ->
- 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
recv_chunk_start(Pid, RemoteFile) ->
- case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {recv_chunk_start, RemoteFile}, atom);
- _ ->
- {error, efnamena}
- end.
+ ftp_internal:recv_chunk_start(Pid, RemoteFile).
%%--------------------------------------------------------------------------
-%% recv_chunk(Pid, RemoteFile) -> ok | {ok, Bin} | {error, Reason}
-%% Pid = pid()
-%% RemoteFile = string()
-%%
%% Description: Transfer file from remote server into binary in chunks
%%--------------------------------------------------------------------------
-spec recv_chunk(Pid :: pid()) ->
'ok' |
{'ok', Bin :: binary()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'error', Reason :: term()}.
recv_chunk(Pid) ->
- call(Pid, recv_chunk, atom).
+ ftp_internal:recv_chunk(Pid).
%%--------------------------------------------------------------------------
-%% send(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath}
-%% | {error, elogin}
-%% | {error, econn}
-%% Pid = pid()
-%% LocalFileName = RemotFileName = string()
-%%
%% Description: Transfer file to remote server.
%%--------------------------------------------------------------------------
-spec send(Pid :: pid(), LocalFileName :: string()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
- shortage_reason()}.
+ 'ok' | {'error', Reason :: term()}.
send(Pid, LocalFileName) ->
send(Pid, LocalFileName, LocalFileName).
@@ -596,74 +329,34 @@ send(Pid, LocalFileName) ->
-spec send(Pid :: pid(),
LocalFileName :: string(),
RemoteFileName :: string()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
- shortage_reason()}.
+ 'ok' | {'error', Reason :: term()}.
send(Pid, LocalFileName, RemotFileName) ->
- case is_name_sane(RemotFileName) of
- true ->
- call(Pid, {send, LocalFileName, RemotFileName}, atom);
- _ ->
- {error, efnamena}
- end.
+ ftp_internal:send(Pid, LocalFileName, RemotFileName).
%%--------------------------------------------------------------------------
-%% send_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin}
-%% | {error, enotbinary} | {error, econn}
-%% Pid = pid()
-%% Bin = binary()
-%% RemoteFile = string()
-%%
%% Description: Transfer a binary to a remote file.
%%--------------------------------------------------------------------------
-spec send_bin(Pid :: pid(), Bin :: binary(), RemoteFile :: string()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
- shortage_reason()}.
-
-send_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
- case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {send_bin, Bin, RemoteFile}, atom);
- _ ->
- {error, efnamena}
- end;
-send_bin(_Pid, _Bin, _RemoteFile) ->
- {error, enotbinary}.
-
-
-%%--------------------------------------------------------------------------
-%% send_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath}
-%% | {error, econn}
-%% Pid = pid()
-%% RemoteFile = string()
-%%
+ 'ok' | {'error', Reason :: term()}.
+
+send_bin(Pid, Bin, RemoteFile) ->
+ ftp_internal:send_bin(Pid, Bin, RemoteFile).
+
+
+%%--------------------------------------------------------------------------
%% Description: Start transfer of chunks to remote file.
%%--------------------------------------------------------------------------
-spec send_chunk_start(Pid :: pid(), RemoteFile :: string()) ->
- 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
send_chunk_start(Pid, RemoteFile) ->
- case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {send_chunk_start, RemoteFile}, atom);
- _ ->
- {error, efnamena}
- end.
-
+ ftp_internal:send_chunk_start(Pid, RemoteFile).
%%--------------------------------------------------------------------------
-%% append_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} |
-%% {error, epath} | {error, econn}
-%% Pid = pid()
-%% RemoteFile = string()
-%%
%% Description: Start append chunks of data to remote file.
%%--------------------------------------------------------------------------
@@ -671,109 +364,58 @@ send_chunk_start(Pid, RemoteFile) ->
'ok' | {'error', Reason :: term()}.
append_chunk_start(Pid, RemoteFile) ->
- case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {append_chunk_start, RemoteFile}, atom);
- _ ->
- {error, efnamena}
- end.
+ ftp_internal:append_chunk_start(Pid, RemoteFile).
%%--------------------------------------------------------------------------
-%% send_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary}
-%% | {error, echunk} | {error, econn}
-%% Pid = pid()
-%% Bin = binary().
-%%
%% Purpose: Send chunk to remote file.
%%--------------------------------------------------------------------------
-spec send_chunk(Pid :: pid(), Bin :: binary()) ->
- 'ok' |
- {'error', Reason :: 'echunk' |
- restriction_reason() |
- common_reason()}.
-
-send_chunk(Pid, Bin) when is_binary(Bin) ->
- call(Pid, {transfer_chunk, Bin}, atom);
-send_chunk(_Pid, _Bin) ->
- {error, enotbinary}.
+ 'ok' | {'error', Reason :: term()}.
+send_chunk(Pid, Bin) ->
+ ftp_internal:send_chunk(Pid, Bin).
%%--------------------------------------------------------------------------
-%% append_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary}
-%% | {error, echunk} | {error, econn}
-%% Pid = pid()
-%% Bin = binary()
-%%
%% Description: Append chunk to remote file.
%%--------------------------------------------------------------------------
-spec append_chunk(Pid :: pid(), Bin :: binary()) ->
- 'ok' |
- {'error', Reason :: 'echunk' |
- restriction_reason() |
- common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
-append_chunk(Pid, Bin) when is_binary(Bin) ->
- call(Pid, {transfer_chunk, Bin}, atom);
-append_chunk(_Pid, _Bin) ->
- {error, enotbinary}.
+append_chunk(Pid, Bin) ->
+ ftp_internal:append_chunk(Pid, Bin).
%%--------------------------------------------------------------------------
-%% send_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk}
-%% | {error, econn}
-%% Pid = pid()
-%%
%% Description: End sending of chunks to remote file.
%%--------------------------------------------------------------------------
-spec send_chunk_end(Pid :: pid()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
- shortage_reason()}.
+ 'ok' | {'error', Reason :: term()}.
send_chunk_end(Pid) ->
- call(Pid, chunk_end, atom).
+ ftp_internal:send_chunk_end(Pid).
%%--------------------------------------------------------------------------
-%% append_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk}
-%% | {error, econn}
-%% Pid = pid()
-%%
%% Description: End appending of chunks to remote file.
%%--------------------------------------------------------------------------
-spec append_chunk_end(Pid :: pid()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
- shortage_reason()}.
+ 'ok' | {'error', Reason :: term()}.
append_chunk_end(Pid) ->
- call(Pid, chunk_end, atom).
+ ftp_internal:append_chunk_end(Pid).
%%--------------------------------------------------------------------------
-%% append(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath}
-%% | {error, elogin}
-%% | {error, econn}
-%% Pid = pid()
-%% LocalFileName = RemotFileName = string()
-%%
%% Description: Append the local file to the remote file
%%--------------------------------------------------------------------------
-spec append(Pid :: pid(), LocalFileName :: string()) ->
- 'ok' |
- {'error', Reason :: 'epath' |
- 'elogin' |
- 'etnospc' |
- 'epnospc' |
- 'efnamena' | common_reason()}.
+ 'ok' | {'error', Reason :: term()}.
append(Pid, LocalFileName) ->
append(Pid, LocalFileName, LocalFileName).
@@ -784,1873 +426,61 @@ append(Pid, LocalFileName) ->
'ok' | {'error', Reason :: term()}.
append(Pid, LocalFileName, RemotFileName) ->
- case is_name_sane(RemotFileName) of
- true ->
- call(Pid, {append, LocalFileName, RemotFileName}, atom);
- _ ->
- {error, efnamena}
- end.
+ ftp_internal:append(Pid, LocalFileName, RemotFileName).
%%--------------------------------------------------------------------------
-%% append_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin}
-%% | {error, enotbinary} | {error, econn}
-%% Pid = pid()
-%% Bin = binary()
-%% RemoteFile = string()
-%%
%% Purpose: Append a binary to a remote file.
%%--------------------------------------------------------------------------
-spec append_bin(Pid :: pid(),
Bin :: binary(),
RemoteFile :: string()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
- shortage_reason()}.
+ 'ok' | {'error', Reason :: term()}.
-append_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
- case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {append_bin, Bin, RemoteFile}, atom);
- _ ->
- {error, efnamena}
- end;
-append_bin(_Pid, _Bin, _RemoteFile) ->
- {error, enotbinary}.
+append_bin(Pid, Bin, RemoteFile) ->
+ ftp_internal:append_bin(Pid, Bin, RemoteFile).
%%--------------------------------------------------------------------------
-%% quote(Pid, Cmd) -> list()
-%% Pid = pid()
-%% Cmd = string()
-%%
%% Description: Send arbitrary ftp command.
%%--------------------------------------------------------------------------
--spec quote(Pid :: pid(), Cmd :: string()) -> list().
+-spec quote(Pid :: pid(), Cmd :: string()) -> [FTPLine :: string()].
quote(Pid, Cmd) when is_list(Cmd) ->
- call(Pid, {quote, Cmd}, atom).
+ ftp_internal:quote(Pid, Cmd).
%%--------------------------------------------------------------------------
-%% close(Pid) -> ok
-%% Pid = pid()
-%%
%% Description: End the ftp session.
%%--------------------------------------------------------------------------
-spec close(Pid :: pid()) -> 'ok'.
-
close(Pid) ->
- cast(Pid, close),
- ok.
+ ftp_internal:close(Pid).
%%--------------------------------------------------------------------------
-%% formaterror(Tag) -> string()
-%% Tag = atom() | {error, atom()}
-%%
%% Description: Return diagnostics.
%%--------------------------------------------------------------------------
--spec formaterror(Tag :: term()) -> string().
+-spec formaterror(Tag :: atom() | {error, atom()}) -> string().
formaterror(Tag) ->
ftp_response:error_string(Tag).
info(Pid) ->
- call(Pid, info, list).
+ ftp_internal:info(Pid).
%%--------------------------------------------------------------------------
-%% latest_ctrl_response(Pid) -> string()
-%% Pid = pid()
-%%
%% Description: The latest received response from the server
%%--------------------------------------------------------------------------
-spec latest_ctrl_response(Pid :: pid()) -> string().
latest_ctrl_response(Pid) ->
- call(Pid, latest_ctrl_response, string).
-
-
-%%%========================================================================
-%%% gen_server callback functions
-%%%========================================================================
-
-%%-------------------------------------------------------------------------
-%% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason}
-%% Description: Initiates the erlang process that manages a ftp connection.
-%%-------------------------------------------------------------------------
-init(Options) ->
- process_flag(trap_exit, true),
-
- %% Keep track of the client
- {value, {client, Client}} = lists:keysearch(client, 1, Options),
- erlang:monitor(process, Client),
-
- %% Make sure inet is started
- _ = inet_db:start(),
-
- %% Where are we
- {ok, Dir} = file:get_cwd(),
-
- %% Maybe activate dbg
- case key_search(debug, Options, disable) of
- trace ->
- dbg:tracer(),
- dbg:p(all, [call]),
- {ok, _} = dbg:tpl(ftp, [{'_', [], [{return_trace}]}]),
- {ok, _} = dbg:tpl(ftp_response, [{'_', [], [{return_trace}]}]),
- {ok, _} = dbg:tpl(ftp_progress, [{'_', [], [{return_trace}]}]),
- ok;
- debug ->
- dbg:tracer(),
- dbg:p(all, [call]),
- {ok, _} = dbg:tp(ftp, [{'_', [], [{return_trace}]}]),
- {ok, _} = dbg:tp(ftp_response, [{'_', [], [{return_trace}]}]),
- {ok, _} = dbg:tp(ftp_progress, [{'_', [], [{return_trace}]}]),
- ok;
- _ ->
- %% Keep silent
- ok
- end,
-
- %% Verbose?
- Verbose = key_search(verbose, Options, false),
-
- %% IpFamily?
- IpFamily = key_search(ipfamily, Options, inet),
-
- State = #state{owner = Client,
- verbose = Verbose,
- ipfamily = IpFamily,
- ldir = Dir},
-
- %% Set process prio
- Priority = key_search(priority, Options, low),
- process_flag(priority, Priority),
-
- %% And we are done
- {ok, State}.
-
-
-%%--------------------------------------------------------------------------
-%% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% Description: Handle incoming requests.
-%%-------------------------------------------------------------------------
-
-%% Anyone can ask this question
-handle_call({_, info}, _, #state{verbose = Verbose,
- mode = Mode,
- timeout = Timeout,
- ipfamily = IpFamily,
- csock = Socket,
- progress = Progress} = State) ->
- {ok, {_, LocalPort}} = sockname(Socket),
- {ok, {Address, Port}} = peername(Socket),
- Options = [{verbose, Verbose},
- {ipfamily, IpFamily},
- {mode, Mode},
- {peer, Address},
- {peer_port, Port},
- {local_port, LocalPort},
- {timeout, Timeout},
- {progress, Progress}],
- {reply, {ok, Options}, State};
-
-handle_call({_,latest_ctrl_response}, _, #state{latest_ctrl_response=Resp} = State) ->
- {reply, {ok,Resp}, State};
-
-%% But everything else must come from the owner
-handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid ->
- {reply, {error, not_connection_owner}, State};
-
-handle_call({_, {open, ip_comm, Options}}, From, State) ->
- {ok, Opts} = open_options(Options),
-
- case key_search(host, Opts, undefined) of
- undefined ->
- {stop, normal, {error, ehost}, State};
- Host ->
- TLSSecMethod = key_search(tls_sec_method, Opts, undefined),
- TLSOpts = key_search(tls, Opts, undefined),
- TLSReuse = key_search(tls_ctrl_session_reuse, Opts, false),
- Mode = key_search(mode, Opts, ?DEFAULT_MODE),
- Port0 = key_search(port, Opts, 0),
- Port = if Port0 == 0, TLSSecMethod == ftps -> ?FTPS_PORT; Port0 == 0 -> ?FTP_PORT; true -> Port0 end,
- Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT),
- DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT),
- Progress = key_search(progress, Opts, ignore),
- IpFamily = key_search(ipfamily, Opts, inet),
- FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT),
-
- {ok, {CtrlOpts, DataPassOpts, DataActOpts}} = socket_options(Options),
-
- State2 = State#state{client = From,
- mode = Mode,
- progress = progress(Progress),
- ipfamily = IpFamily,
- sockopts_ctrl = CtrlOpts,
- sockopts_data_passive = DataPassOpts,
- sockopts_data_active = DataActOpts,
- timeout = Timeout,
- dtimeout = DTimeout,
- ftp_extension = FtpExt},
-
- case setup_ctrl_connection(Host, Port, Timeout, State2) of
- {ok, State3, WaitTimeout} when is_list(TLSOpts), TLSSecMethod == ftps ->
- handle_ctrl_result({tls_upgrade, TLSSecMethod},
- State3#state{tls_options = TLSOpts,
- tls_ctrl_session_reuse = TLSReuse,
- timeout = WaitTimeout });
- {ok, State3, WaitTimeout} when is_list(TLSOpts) ->
- {noreply, State3#state{tls_options = TLSOpts, tls_ctrl_session_reuse = TLSReuse }, WaitTimeout};
- {ok, State3, WaitTimeout} ->
- {noreply, State3, WaitTimeout};
- {error, _Reason} ->
- gen_server:reply(From, {error, ehost}),
- {stop, normal, State2#state{client = undefined}}
- end
- end;
-
-handle_call({_, {user, User, Password}}, From,
- #state{csock = CSock} = State) when (CSock =/= undefined) ->
- handle_user(User, Password, "", State#state{client = From});
-
-handle_call({_, {user, User, Password, Acc}}, From,
- #state{csock = CSock} = State) when (CSock =/= undefined) ->
- handle_user(User, Password, Acc, State#state{client = From});
-
-handle_call({_, {account, Acc}}, From, State)->
- handle_user_account(Acc, State#state{client = From});
-
-handle_call({_, pwd}, From, #state{chunk = false} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("PWD", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{client = From, caller = pwd}};
-
-handle_call({_, lpwd}, From, #state{ldir = LDir} = State) ->
- {reply, {ok, LDir}, State#state{client = From}};
-
-handle_call({_, {cd, Dir}}, From, #state{chunk = false} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{client = From, caller = cd}};
-
-handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) ->
- LDir = filename:absname(Dir, LDir0),
- case file:read_file_info(LDir) of %% FIX better check that LDir is a dir.
- {ok, _ } ->
- {reply, ok, State#state{ldir = LDir}};
- _ ->
- {reply, {error, epath}, State}
- end;
-
-handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From,
- #state{chunk = false} = State) ->
- setup_data_connection(State#state{caller = {dir, Dir, Len},
- client = From});
-handle_call({_, {rename, CurrFile, NewFile}}, From,
- #state{chunk = false} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("RNFR ~s", [CurrFile])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {rename, NewFile}, client = From}};
-
-handle_call({_, {delete, File}}, {_Pid, _} = From,
- #state{chunk = false} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("DELE ~s", [File])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{client = From}};
-
-handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("MKD ~s", [Dir])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{client = From}};
-
-handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("RMD ~s", [Dir])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{client = From}};
-
-handle_call({_,{type, Type}}, From, #state{chunk = false} = State0) ->
- case Type of
- ascii ->
- _ = send_ctrl_message(State0, mk_cmd("TYPE A", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = type, type = ascii,
- client = From}};
- binary ->
- _ = send_ctrl_message(State0, mk_cmd("TYPE I", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = type, type = binary,
- client = From}};
- _ ->
- {reply, {error, etype}, State0}
- end;
-handle_call({_,{recv, RemoteFile, LocalFile}}, From,
- #state{chunk = false, ldir = LocalDir} = State) ->
- progress_report({remote_file, RemoteFile}, State),
- NewLocalFile = filename:absname(LocalFile, LocalDir),
-
- case file_open(NewLocalFile, write) of
- {ok, Fd} ->
- setup_data_connection(State#state{client = From,
- caller =
- {recv_file,
- RemoteFile, Fd}});
- {error, _What} ->
- {reply, {error, epath}, State}
- end;
-handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} =
- State) ->
- setup_data_connection(State#state{caller = {recv_bin, RemoteFile},
- client = From});
-handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false}
- = State) ->
- setup_data_connection(State#state{caller = {start_chunk_transfer,
- "RETR", RemoteFile},
- client = From});
-
-handle_call({_, recv_chunk}, _, #state{chunk = false} = State) ->
- {reply, {error, "ftp:recv_chunk_start/2 not called"}, State};
-handle_call({_, recv_chunk}, _From, #state{chunk = true,
- data = Bin,
- caller = #recv_chunk_closing{dconn_closed = true,
- pos_compl_received = true,
- client_called_us = true
- }
- } = State0) ->
- case Bin of
- <<>> ->
- {reply, ok, State0#state{caller = undefined,
- chunk = false,
- client = undefined}};
- Data ->
- {reply, Data, State0#state{caller = undefined,
- chunk = false,
- client = undefined}}
- end;
-handle_call({_, recv_chunk}, _From, #state{chunk = true,
- caller = #recv_chunk_closing{dconn_closed = true,
- pos_compl_received = true
- }
- } = State0) ->
- %% The ftp:recv_chunk call was the last event we waited for, finnish and clean up
- ?DBG("Data connection closed recv_chunk_closing ftp:recv_chunk, last event",[]),
- State = activate_ctrl_connection(State0),
- {reply, ok, State#state{caller = undefined,
- chunk = false,
- client = undefined}};
-handle_call({_, recv_chunk}, From, #state{chunk = true,
- caller = #recv_chunk_closing{pos_compl_received = true
- } = R
- } = State0) ->
- State = activate_data_connection(State0),
- {noreply, State#state{client = From, caller = R#recv_chunk_closing{client_called_us=true}}};
-
-handle_call({_, recv_chunk}, From, #state{chunk = true,
- caller = #recv_chunk_closing{} = R
- } = State) ->
- %% Waiting for more, don't care what
- ?DBG("recv_chunk_closing ftp:recv_chunk, get more",[]),
- {noreply, State#state{client = From, caller = R#recv_chunk_closing{client_called_us=true}}};
-
-handle_call({_, recv_chunk}, From, #state{chunk = true} = State0) ->
- State = activate_data_connection(State0),
- {noreply, State#state{client = From, caller = recv_chunk}};
-
-handle_call({_, {send, LocalFile, RemoteFile}}, From,
- #state{chunk = false, ldir = LocalDir} = State) ->
- progress_report({local_file, filename:absname(LocalFile, LocalDir)},
- State),
- setup_data_connection(State#state{caller = {transfer_file,
- {"STOR",
- LocalFile, RemoteFile}},
- client = From});
-handle_call({_, {append, LocalFile, RemoteFile}}, From,
- #state{chunk = false} = State) ->
- setup_data_connection(State#state{caller = {transfer_file,
- {"APPE",
- LocalFile, RemoteFile}},
- client = From});
-handle_call({_, {send_bin, Bin, RemoteFile}}, From,
- #state{chunk = false} = State) ->
- setup_data_connection(State#state{caller = {transfer_data,
- {"STOR", Bin, RemoteFile}},
- client = From});
-handle_call({_,{append_bin, Bin, RemoteFile}}, From,
- #state{chunk = false} = State) ->
- setup_data_connection(State#state{caller = {transfer_data,
- {"APPE", Bin, RemoteFile}},
- client = From});
-handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false}
- = State) ->
- setup_data_connection(State#state{caller = {start_chunk_transfer,
- "STOR", RemoteFile},
- client = From});
-handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false}
- = State) ->
- setup_data_connection(State#state{caller = {start_chunk_transfer,
- "APPE", RemoteFile},
- client = From});
-handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) ->
- send_data_message(State, Bin),
- {reply, ok, State};
-
-handle_call({_, {transfer_chunk, _}}, _, #state{chunk = false} = State) ->
- {reply, {error, echunk}, State};
-
-handle_call({_, chunk_end}, From, #state{chunk = true} = State0) ->
- close_data_connection(State0),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{client = From, dsock = undefined,
- caller = end_chunk_transfer, chunk = false}};
-
-handle_call({_, chunk_end}, _, #state{chunk = false} = State) ->
- {reply, {error, echunk}, State};
-
-handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd(Cmd, [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{client = From, caller = quote}};
-
-handle_call({_, _Req}, _From, #state{csock = CSock} = State)
- when (CSock =:= undefined) ->
- {reply, {error, not_connected}, State};
-
-handle_call(_, _, #state{chunk = true} = State) ->
- {reply, {error, echunk}, State};
-
-%% Catch all - This can only happen if the application programmer writes
-%% really bad code that violates the API.
-handle_call(Request, _Timeout, State) ->
- {stop, {'API_violation_connection_closed', Request},
- {error, {connection_terminated, 'API_violation'}}, State}.
-
-%%--------------------------------------------------------------------------
-%% handle_cast(Request, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handles cast messages.
-%%-------------------------------------------------------------------------
-handle_cast({Pid, close}, #state{owner = Pid} = State) ->
- _ = send_ctrl_message(State, mk_cmd("QUIT", [])),
- close_ctrl_connection(State),
- close_data_connection(State),
- {stop, normal, State#state{csock = undefined, dsock = undefined}};
-
-handle_cast({Pid, close}, State) ->
- Report = io_lib:format("A none owner process ~p tried to close an "
- "ftp connection: ~n", [Pid]),
- error_logger:info_report(Report),
- {noreply, State};
-
-%% Catch all - This can only happen if the application programmer writes
-%% really bad code that violates the API.
-handle_cast(Msg, State) ->
- {stop, {'API_violation_connection_closed', Msg}, State}.
-
-%%--------------------------------------------------------------------------
-%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handles tcp messages from the ftp-server.
-%% Note: The order of the function clauses is significant.
-%%--------------------------------------------------------------------------
-
-handle_info(timeout, #state{caller = open} = State) ->
- {stop, timeout, State};
-
-handle_info(timeout, State) ->
- {noreply, State};
-
-%%% Data socket messages %%%
-handle_info({Trpt, Socket, Data},
- #state{dsock = {Trpt,Socket},
- caller = {recv_file, Fd}} = State0) when Trpt==tcp;Trpt==ssl ->
- ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]),
- ok = file_write(binary_to_list(Data), Fd),
- progress_report({binary, Data}, State0),
- State = activate_data_connection(State0),
- {noreply, State};
-
-handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}, client = From,
- caller = recv_chunk}
- = State) when Trpt==tcp;Trpt==ssl ->
- ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State]),
- gen_server:reply(From, {ok, Data}),
- {noreply, State#state{client = undefined, caller = undefined, data = <<>>}};
-
-handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}} = State0) when Trpt==tcp;Trpt==ssl ->
- ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]),
- State = activate_data_connection(State0),
- {noreply, State#state{data = <<(State#state.data)/binary,
- Data/binary>>}};
-
-handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
- caller = {recv_file, Fd}} = State0)
- when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
- file_close(Fd),
- progress_report({transfer_size, 0}, State0),
- State = activate_ctrl_connection(State0),
- ?DBG("Data channel close",[]),
- {noreply, State#state{dsock = undefined, data = <<>>}};
-
-handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
- client = Client,
- caller = recv_chunk} = State0)
- when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
- ?DBG("Data channel close recv_chunk",[]),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{dsock = undefined,
- caller = #recv_chunk_closing{dconn_closed = true,
- client_called_us = Client =/= undefined}
- }};
-handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
- caller = #recv_chunk_closing{client_called_us = true,
- pos_compl_received = true} = R} = State)
- when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
- %% Maybe handle unprocessed chunk message before acking final chunk
- self() ! {Cls, Socket},
- {noreply, State#state{caller = R#recv_chunk_closing{dconn_closed = true}}};
-
-handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin,
- data = Data} = State0)
- when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
- ?DBG("Data channel close",[]),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{dsock = undefined, data = <<>>,
- caller = {recv_bin, Data}}};
-
-handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, data = Data,
- caller = {handle_dir_result, Dir}}
- = State0) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
- ?DBG("Data channel close",[]),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{dsock = undefined,
- caller = {handle_dir_result, Dir, Data},
-% data = <<?CR,?LF>>}};
- data = <<>>}};
-
-handle_info({Err, Socket, Reason}, #state{dsock = {Trpt,Socket},
- client = From} = State)
- when {Err,Trpt}=={tcp_error,tcp} ; {Err,Trpt}=={ssl_error,ssl} ->
- gen_server:reply(From, {error, Reason}),
- close_data_connection(State),
- {noreply, State#state{dsock = undefined, client = undefined,
- data = <<>>, caller = undefined, chunk = false}};
-
-%%% Ctrl socket messages %%%
-handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket},
- verbose = Verbose,
- caller = Caller,
- client = From,
- ctrl_data = {BinCtrlData, AccLines,
- LineStatus}}
- = State0) ->
- ?DBG('--ctrl ~p ----> ~s~p~n',[Socket,<<BinCtrlData/binary, Data/binary>>,State0]),
- case ftp_response:parse_lines(<<BinCtrlData/binary, Data/binary>>,
- AccLines, LineStatus) of
- {ok, Lines, NextMsgData} ->
- verbose(Lines, Verbose, 'receive'),
- CtrlResult = ftp_response:interpret(Lines),
- case Caller of
- quote ->
- gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])),
- {noreply, State0#state{client = undefined,
- caller = undefined,
- latest_ctrl_response = Lines,
- ctrl_data = {NextMsgData, [],
- start}}};
- _ ->
- ?DBG(' ...handle_ctrl_result(~p,...) ctrl_data=~p~n',[CtrlResult,{NextMsgData, [], start}]),
- handle_ctrl_result(CtrlResult,
- State0#state{latest_ctrl_response = Lines,
- ctrl_data =
- {NextMsgData, [], start}})
- end;
- {continue, CtrlData} when CtrlData =/= State0#state.ctrl_data ->
- ?DBG(' ...Continue... ctrl_data=~p~n',[CtrlData]),
- State1 = State0#state{ctrl_data = CtrlData},
- State = activate_ctrl_connection(State1),
- {noreply, State};
- {continue, _CtrlData} ->
- ?DBG(' ...Continue... ctrl_data=~p~n',[_CtrlData]),
- {noreply, State0}
- end;
-
-%% If the server closes the control channel it is
-%% the expected behavior that connection process terminates.
-handle_info({Cls, Socket}, #state{csock = {Trpt, Socket}})
- when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
- exit(normal); %% User will get error message from terminate/2
-
-handle_info({Err, Socket, Reason}, _) when Err==tcp_error ; Err==ssl_error ->
- Report =
- io_lib:format("~p on socket: ~p for reason: ~p~n",
- [Err, Socket, Reason]),
- error_logger:error_report(Report),
- %% If tcp does not work the only option is to terminate,
- %% this is the expected behavior under these circumstances.
- exit(normal); %% User will get error message from terminate/2
-
-%% Monitor messages - if the process owning the ftp connection goes
-%% down there is no point in continuing.
-handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) ->
- {stop, normal, State#state{client = undefined}};
-
-handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) ->
- {stop, normal, State#state{client = undefined}};
-
-handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) ->
- {stop, normal, State#state{client = undefined}};
-
-handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) ->
- {stop, {stopped, {'EXIT', Process, Reason}},
- State#state{client = undefined}};
-
-handle_info({'EXIT', Pid, Reason}, #state{progress = Pid} = State) ->
- Report = io_lib:format("Progress reporting stopped for reason ~p~n",
- [Reason]),
- error_logger:info_report(Report),
- {noreply, State#state{progress = ignore}};
-
-%% Catch all - throws away unknown messages (This could happen by "accident"
-%% so we do not want to crash, but we make a log entry as it is an
-%% unwanted behaviour.)
-handle_info(Info, State) ->
- Report = io_lib:format("ftp : ~p : Unexpected message: ~p~nState: ~p~n",
- [self(), Info, State]),
- error_logger:info_report(Report),
- {noreply, State}.
-
-%%--------------------------------------------------------------------------
-%% terminate/2 and code_change/3
-%%--------------------------------------------------------------------------
-terminate(normal, State) ->
- %% If terminate reason =/= normal the progress reporting process will
- %% be killed by the exit signal.
- progress_report(stop, State),
- do_terminate({error, econn}, State);
-terminate(Reason, State) ->
- Report = io_lib:format("Ftp connection closed due to: ~p~n", [Reason]),
- error_logger:error_report(Report),
- do_terminate({error, eclosed}, State).
-
-do_terminate(ErrorMsg, State) ->
- close_data_connection(State),
- close_ctrl_connection(State),
- case State#state.client of
- undefined ->
- ok;
- From ->
- gen_server:reply(From, ErrorMsg)
- end,
- ok.
-
-code_change(_Vsn, State1, upgrade_from_pre_5_12) ->
- {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
- Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress} = State1,
- IpFamily =
- if
- (IPv6Disable =:= true) ->
- inet;
- true ->
- inet6fb4
- end,
- State2 = #state{csock = CSock,
- dsock = DSock,
- verbose = Verbose,
- ldir = LDir,
- type = Type,
- chunk = Chunk,
- mode = Mode,
- timeout = Timeout,
- data = Data,
- ctrl_data = CtrlData,
- owner = Owner,
- client = Client,
- caller = Caller,
- ipfamily = IpFamily,
- progress = Progress},
- {ok, State2};
-
-code_change(_Vsn, State1, downgrade_to_pre_5_12) ->
- #state{csock = CSock,
- dsock = DSock,
- verbose = Verbose,
- ldir = LDir,
- type = Type,
- chunk = Chunk,
- mode = Mode,
- timeout = Timeout,
- data = Data,
- ctrl_data = CtrlData,
- owner = Owner,
- client = Client,
- caller = Caller,
- ipfamily = IpFamily,
- progress = Progress} = State1,
- IPv6Disable =
- if
- (IpFamily =:= inet) ->
- true;
- true ->
- false
- end,
- State2 =
- {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
- Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress},
- {ok, State2};
-
-code_change(_Vsn, State, _Extra) ->
- {ok, State}.
-
+ ftp_internal:latest_ctrl_response(Pid).
-%%%=========================================================================
-%% Start/stop
-%%%=========================================================================
-%%--------------------------------------------------------------------------
-%% start_link([Opts, GenServerOptions]) -> {ok, Pid} | {error, Reason}
-%%
-%% Description: Callback function for the ftp supervisor. It is called
-%% : when open or legacy is called.
-%%--------------------------------------------------------------------------
-start_link([Opts, GenServerOptions]) ->
- start_link(Opts, GenServerOptions).
-
-start_link(Opts, GenServerOptions) ->
- case lists:keysearch(client, 1, Opts) of
- {value, _} ->
- %% Via the supervisor
- gen_server:start_link(?MODULE, Opts, GenServerOptions);
- false ->
- Opts2 = [{client, self()} | Opts],
- gen_server:start_link(?MODULE, Opts2, GenServerOptions)
- end.
-
-
-%%% Stop functionality is handled by close/1
-
-%%%========================================================================
-%%% Internal functions
-%%%========================================================================
-
-%%--------------------------------------------------------------------------
-%%% Help functions to handle_call and/or handle_ctrl_result
-%%--------------------------------------------------------------------------
-%% User handling
--spec handle_user(User, Password, Account, State) -> Result when
- User :: io:format(),
- Password :: io:format(),
- Account :: io:format(),
- State :: #state{},
- Result :: {noreply, #state{}}.
-handle_user(User, Password, Acc, State0) ->
- _ = send_ctrl_message(State0, mk_cmd("USER ~s", [User])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {handle_user, Password, Acc}}}.
-
-handle_user_passwd(Password, Acc, State0) ->
- _ = send_ctrl_message(State0, mk_cmd("PASS ~s", [Password])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {handle_user_passwd, Acc}}}.
-
-handle_user_account(Acc, State0) ->
- _ = send_ctrl_message(State0, mk_cmd("ACCT ~s", [Acc])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = handle_user_account}}.
-
-
-%%--------------------------------------------------------------------------
-%% handle_ctrl_result
-%%--------------------------------------------------------------------------
--type ctrl_status_operation() :: efnamena
- | elogin
- | enofile
- | epath
- | error
- | etnospc
- | epnospc
- | efnamena
- | econn
- | perm_neg_compl
- | pos_compl
- | pos_interm
- | pos_interm_acct
- | pos_prel
- | tls_upgrade
- | trans_neg_compl.
-
--spec handle_ctrl_result(Operation, State) -> Result when
- Operation :: {ctrl_status_operation(), list() | atom()},
- State :: #state{},
- Result :: {noreply, #state{}, integer()}
- | {noreply, #state{}}
- | {stop, normal | {error, Reason}, #state{}}
- | {error, term()},
- Reason :: term().
-handle_ctrl_result({pos_compl, _}, #state{csock = {tcp, _Socket},
- tls_options = TLSOptions,
- timeout = Timeout,
- caller = open}
- = State0) when is_list(TLSOptions) ->
- _ = send_ctrl_message(State0, mk_cmd("AUTH TLS", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State, Timeout};
-
-handle_ctrl_result({tls_upgrade, S}, #state{csock = {tcp, Socket},
- tls_options = TLSOptions,
- timeout = Timeout,
- caller = open, client = From}
- = State0) when is_list(TLSOptions) ->
- ?DBG('<--ctrl ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]),
- catch ssl:start(),
- case ssl:connect(Socket, TLSOptions, Timeout) of
- {ok, TLSSocket} when S == ftps ->
- State1 = State0#state{csock = {ssl,TLSSocket}},
- State = activate_ctrl_connection(State1),
- {noreply, State#state{tls_upgrading_data_connection = pending}, Timeout};
- {ok, TLSSocket} ->
- State1 = State0#state{csock = {ssl,TLSSocket}},
- handle_ctrl_result({pos_compl, S}, State1#state{tls_upgrading_data_connection = pending});
- {error, _} = Error ->
- gen_server:reply(From, Error),
- {stop, normal, State0#state{client = undefined,
- caller = undefined,
- tls_upgrading_data_connection = false}}
- end;
-
-handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = pending} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("PBSZ 0", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{tls_upgrading_data_connection = {true, pbsz}}};
-
-handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, pbsz}} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("PROT P", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{tls_upgrading_data_connection = {true, prot}}};
-
-handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, prot},
- client = From} = State) ->
- gen_server:reply(From, {ok, self()}),
- {noreply, State#state{client = undefined,
- caller = undefined,
- tls_upgrading_data_connection = false}};
-handle_ctrl_result({pos_compl, _}, #state{caller = open, client = From}
- = State) ->
- gen_server:reply(From, {ok, self()}),
- {noreply, State#state{client = undefined,
- caller = undefined }};
-handle_ctrl_result({_, Lines}, #state{caller = open} = State) ->
- ctrl_result_response(econn, State, {error, Lines});
-
-%%--------------------------------------------------------------------------
-%% Data connection setup active mode
-handle_ctrl_result({pos_compl, _Lines},
- #state{mode = active,
- caller = {setup_data_connection,
- {LSock, Caller}}} = State) ->
- handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}});
-
-handle_ctrl_result({Status, _Lines},
- #state{mode = active,
- caller = {setup_data_connection, {LSock, _}}}
- = State) ->
- close_connection({tcp,LSock}),
- ctrl_result_response(Status, State, {error, Status});
-
-%% Data connection setup passive mode
-handle_ctrl_result({pos_compl, Lines},
- #state{mode = passive,
- ipfamily = inet6,
- client = From,
- caller = {setup_data_connection, Caller},
- csock = CSock,
- sockopts_data_passive = SockOpts,
- timeout = Timeout}
- = State) ->
- [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")),
- {ok, {IP, _}} = peername(CSock),
- case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of
- {ok, _, Socket} ->
- handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}});
- {error, _Reason} = Error ->
- gen_server:reply(From, Error),
- {noreply, State#state{client = undefined, caller = undefined}}
- end;
-
-handle_ctrl_result({pos_compl, Lines},
- #state{mode = passive,
- ipfamily = inet,
- client = From,
- caller = {setup_data_connection, Caller},
- timeout = Timeout,
- sockopts_data_passive = SockOpts,
- ftp_extension = false} = State) ->
-
- {_, [?LEFT_PAREN | Rest]} =
- lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines),
- {NewPortAddr, _} =
- lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest),
- [A1, A2, A3, A4, P1, P2] =
- lists:map(fun(X) -> list_to_integer(X) end,
- string:tokens(NewPortAddr, [$,])),
- IP = {A1, A2, A3, A4},
- Port = (P1 * 256) + P2,
-
- ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,Port,Caller]),
- case connect(IP, Port, SockOpts, Timeout, State) of
- {ok, _, Socket} ->
- handle_caller(State#state{caller = Caller, dsock = {tcp,Socket}});
- {error, _Reason} = Error ->
- gen_server:reply(From, Error),
- {noreply,State#state{client = undefined, caller = undefined}}
- end;
-
-handle_ctrl_result({pos_compl, Lines},
- #state{mode = passive,
- ipfamily = inet,
- client = From,
- caller = {setup_data_connection, Caller},
- csock = CSock,
- timeout = Timeout,
- sockopts_data_passive = SockOpts,
- ftp_extension = true} = State) ->
-
- [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")),
- {ok, {IP, _}} = peername(CSock),
-
- ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,PortStr,Caller]),
- case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of
- {ok, _, Socket} ->
- handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}});
- {error, _Reason} = Error ->
- gen_server:reply(From, Error),
- {noreply, State#state{client = undefined, caller = undefined}}
- end;
-
-
-%% FTP server does not support passive mode: try to fallback on active mode
-handle_ctrl_result(_,
- #state{mode = passive,
- caller = {setup_data_connection, Caller}} = State) ->
- setup_data_connection(State#state{mode = active, caller = Caller});
-
-
-%%--------------------------------------------------------------------------
-%% User handling
-handle_ctrl_result({pos_interm, _},
- #state{caller = {handle_user, PassWord, Acc}} = State) ->
- handle_user_passwd(PassWord, Acc, State);
-handle_ctrl_result({Status, _},
- #state{caller = {handle_user, _, _}} = State) ->
- ctrl_result_response(Status, State, {error, euser});
-
-%% Accounts
-handle_ctrl_result({pos_interm_acct, _},
- #state{caller = {handle_user_passwd, Acc}} = State)
- when Acc =/= "" ->
- handle_user_account(Acc, State);
-handle_ctrl_result({Status, _},
- #state{caller = {handle_user_passwd, _}} = State) ->
- ctrl_result_response(Status, State, {error, euser});
-
-%%--------------------------------------------------------------------------
-%% Print current working directory
-handle_ctrl_result({pos_compl, Lines},
- #state{caller = pwd, client = From} = State) ->
- Dir = pwd_result(Lines),
- gen_server:reply(From, {ok, Dir}),
- {noreply, State#state{client = undefined, caller = undefined}};
-
-%%--------------------------------------------------------------------------
-%% Directory listing
-handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State0) ->
- case accept_data_connection(State0) of
- {ok, State1} ->
- State = activate_data_connection(State1),
- {noreply, State#state{caller = {handle_dir_result, Dir}}};
- {error, _Reason} = Error ->
- ctrl_result_response(error, State0, Error)
- end;
-
-handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, ""=_CurrentDir,
- Data}, client = From}= State) ->
- gen_server:reply(From, {ok, Data}),
- {noreply, State#state{client = undefined,
- caller = undefined}};
-
-handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, _Dir,
- Data}, client = From}= State) ->
- gen_server:reply(From, {ok, Data}),
- {noreply, State#state{client = undefined,
- caller = undefined}};
-
-handle_ctrl_result({pos_compl, _}=Operation, #state{caller = {handle_dir_result, Dir},
- data = Data}= State) ->
- handle_ctrl_result(Operation, State#state{caller = {handle_dir_result, Dir, Data}});
-
-handle_ctrl_result(S={_Status, _},
- #state{caller = {handle_dir_result, _, _}} = State) ->
- %% OTP-5731, macosx
- ctrl_result_response(S, State, {error, epath});
-
-handle_ctrl_result({Status, _}, #state{caller = cd} = State) ->
- ctrl_result_response(Status, State, {error, Status});
-
-handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) ->
- ctrl_result_response(Status, State, {error, epath});
-
-%%--------------------------------------------------------------------------
-%% File renaming
-handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}}
- = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("RNTO ~s", [NewFile])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = rename_second_phase}};
-
-handle_ctrl_result({Status, _},
- #state{caller = {rename, _}} = State) ->
- ctrl_result_response(Status, State, {error, Status});
-
-handle_ctrl_result({Status, _},
- #state{caller = rename_second_phase} = State) ->
- ctrl_result_response(Status, State, {error, Status});
-
-%%--------------------------------------------------------------------------
-%% File handling - recv_bin
-handle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State0) ->
- case accept_data_connection(State0) of
- {ok, State1} ->
- State = activate_data_connection(State1),
- {noreply, State};
- {error, _Reason} = Error ->
- ctrl_result_response(error, State0, Error)
- end;
-
-handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data},
- client = From} = State) ->
- gen_server:reply(From, {ok, Data}),
- close_data_connection(State),
- {noreply, State#state{client = undefined, caller = undefined}};
-
-handle_ctrl_result({Status, _}, #state{caller = recv_bin} = State) ->
- close_data_connection(State),
- ctrl_result_response(Status, State#state{dsock = undefined},
- {error, epath});
-
-handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) ->
- close_data_connection(State),
- ctrl_result_response(Status, State#state{dsock = undefined},
- {error, epath});
-%%--------------------------------------------------------------------------
-%% File handling - start_chunk_transfer
-handle_ctrl_result({pos_prel, _}, #state{caller = start_chunk_transfer}
- = State0) ->
- case accept_data_connection(State0) of
- {ok, State1} ->
- State = start_chunk(State1),
- {noreply, State};
- {error, _Reason} = Error ->
- ctrl_result_response(error, State0, Error)
- end;
-
-%%--------------------------------------------------------------------------
-%% File handling - chunk_transfer complete
-
-handle_ctrl_result({pos_compl, _}, #state{client = From,
- caller = #recv_chunk_closing{dconn_closed = true,
- client_called_us = true,
- pos_compl_received = false
- }}
- = State0) when From =/= undefined ->
- %% The pos_compl was the last event we waited for, finnish and clean up
- ?DBG("recv_chunk_closing pos_compl, last event",[]),
- gen_server:reply(From, ok),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = undefined,
- chunk = false,
- client = undefined}};
-
-handle_ctrl_result({pos_compl, _}, #state{caller = #recv_chunk_closing{}=R}
- = State0) ->
- %% Waiting for more, don't care what
- ?DBG("recv_chunk_closing pos_compl, wait more",[]),
- {noreply, State0#state{caller = R#recv_chunk_closing{pos_compl_received=true}}};
-
-handle_ctrl_result({pos_compl, _}, #state{caller = undefined, chunk = true}
- = State0) ->
- %% Waiting for user to call recv_chunk
- {noreply, State0#state{caller = #recv_chunk_closing{pos_compl_received=true}}};
-
-%%--------------------------------------------------------------------------
-%% File handling - recv_file
-handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State0) ->
- case accept_data_connection(State0) of
- {ok, State1} ->
- State = activate_data_connection(State1),
- {noreply, State};
- {error, _Reason} = Error ->
- ctrl_result_response(error, State0, Error)
- end;
-
-handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) ->
- file_close(Fd),
- close_data_connection(State),
- ctrl_result_response(Status, State#state{dsock = undefined},
- {error, epath});
-%%--------------------------------------------------------------------------
-%% File handling - transfer_*
-handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}}
- = State0) ->
- case accept_data_connection(State0) of
- {ok, State1} ->
- send_file(State1, Fd);
- {error, _Reason} = Error ->
- ctrl_result_response(error, State0, Error)
- end;
-
-handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}}
- = State0) ->
- case accept_data_connection(State0) of
- {ok, State} ->
- send_bin(State, Bin);
- {error, _Reason} = Error ->
- ctrl_result_response(error, State0, Error)
- end;
-
-%%--------------------------------------------------------------------------
-%% Default
-handle_ctrl_result({Status, _Lines}, #state{client = From} = State)
- when From =/= undefined ->
- ctrl_result_response(Status, State, {error, Status});
-handle_ctrl_result(CtrlMsg, #state{caller = undefined} = State) ->
- logger:log(info, #{protocol => ftp, unexpected_msg => CtrlMsg}),
- {noreply, State}.
-
-%%--------------------------------------------------------------------------
-%% Help functions to handle_ctrl_result
-%%--------------------------------------------------------------------------
-
--spec ctrl_result_response(Status, State, Error) -> Result when
- Status :: ctrl_status_operation() | {ctrl_status_operation(), _},
- State :: #state{},
- Error :: {error, Reason},
- Reason :: term(),
- Result :: {noreply, #state{}} | Error.
-ctrl_result_response(pos_compl, #state{client = From} = State, _) ->
- gen_server:reply(From, ok),
- {noreply, State#state{client = undefined, caller = undefined}};
-
-ctrl_result_response(enofile, #state{client = From} = State, _) ->
- gen_server:reply(From, {error, enofile}),
- {noreply, State#state{client = undefined, caller = undefined}};
-
-ctrl_result_response(error, State0, {error, _Reason} = Error) ->
- case State0#state.client of
- undefined ->
- {stop, Error, State0};
- From ->
- gen_server:reply(From, Error),
- State = activate_ctrl_connection(State0),
- {noreply, State}
- end;
-
-ctrl_result_response(Status, #state{client = From} = State, _)
- when (Status =:= etnospc) orelse
- (Status =:= epnospc) orelse
- (Status =:= efnamena) orelse
- (Status =:= econn) ->
- gen_server:reply(From, {error, Status}),
- {stop, normal, State#state{client = undefined}};
-
-ctrl_result_response(_, #state{client = From} = State, ErrorMsg) ->
- gen_server:reply(From, ErrorMsg),
- {noreply, State#state{client = undefined, caller = undefined}}.
-
-%%--------------------------------------------------------------------------
--spec handle_caller(State) -> Result when
- State :: #state{},
- Result :: {noreply, #state{}}.
-handle_caller(#state{caller = {dir, Dir, Len}} = State0) ->
- Cmd = case Len of
- short -> "NLST";
- long -> "LIST"
- end,
- _ = case Dir of
- "" ->
- send_ctrl_message(State0, mk_cmd(Cmd, ""));
- _ ->
- send_ctrl_message(State0, mk_cmd(Cmd ++ " ~s", [Dir]))
- end,
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {dir, Dir}}};
-
-handle_caller(#state{caller = {recv_bin, RemoteFile}} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = recv_bin}};
-
-handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} =
- State0) ->
- _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = start_chunk_transfer}};
-
-handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {recv_file, Fd}}};
-
-handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}},
- ldir = LocalDir, client = From} = State0) ->
- case file_open(filename:absname(LocalFile, LocalDir), read) of
- {ok, Fd} ->
- _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {transfer_file, Fd}}};
- {error, _} ->
- gen_server:reply(From, {error, epath}),
- {noreply, State0#state{client = undefined, caller = undefined,
- dsock = undefined}}
- end;
-
-handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} =
- State0) ->
- _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {transfer_data, Bin}}}.
-
-%% ----------- FTP SERVER COMMUNICATION -------------------------
-
-%% Connect to FTP server at Host (default is TCP port 21)
-%% in order to establish a control connection.
--spec setup_ctrl_connection(Host, Port, Timeout, State) -> Result when
- Host :: inet:socket_address() | inet:hostname(),
- Port :: inet:port_number(),
- Timeout :: timeout(),
- State :: #state{},
- Reason :: timeout | inet:posix(),
- Result :: {ok, State, integer()} | {error, Reason}.
-setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = State0) ->
- MsTime = erlang:monotonic_time(),
- case connect(Host, Port, SockOpts, Timeout, State0) of
- {ok, IpFam, CSock} ->
- State1 = State0#state{csock = {tcp, CSock}, ipfamily = IpFam},
- State = activate_ctrl_connection(State1),
- case Timeout - millisec_passed(MsTime) of
- Timeout2 when (Timeout2 >= 0) ->
- {ok, State#state{caller = open}, Timeout2};
- _ ->
- %% Oups: Simulate timeout
- {ok, State#state{caller = open}, 0}
- end;
- Error ->
- Error
- end.
-
--spec setup_data_connection(State) -> Result when
- State :: #state{},
- Result :: {noreply, State}.
-setup_data_connection(#state{mode = active,
- caller = Caller,
- csock = CSock,
- sockopts_data_active = SockOpts,
- ftp_extension = FtpExt} = State0) ->
- case (catch sockname(CSock)) of
- {ok, {{_, _, _, _, _, _, _, _} = IP0, _}} ->
- IP = proplists:get_value(ip, SockOpts, IP0),
- {ok, LSock} =
- gen_tcp:listen(0, [{ip, IP}, {active, false},
- inet6, binary, {packet, 0} |
- lists:keydelete(ip,1,SockOpts)]),
- {ok, {_, Port}} = sockname({tcp,LSock}),
- IpAddress = inet_parse:ntoa(IP),
- Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]),
- _ = send_ctrl_message(State0, Cmd),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {setup_data_connection,
- {LSock, Caller}}}};
- {ok, {{_,_,_,_} = IP0, _}} ->
- IP = proplists:get_value(ip, SockOpts, IP0),
- {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false},
- binary, {packet, 0} |
- lists:keydelete(ip,1,SockOpts)]),
- {ok, Port} = inet:port(LSock),
- _ = case FtpExt of
- false ->
- {IP1, IP2, IP3, IP4} = IP,
- {Port1, Port2} = {Port div 256, Port rem 256},
- send_ctrl_message(State0,
- mk_cmd("PORT ~w,~w,~w,~w,~w,~w",
- [IP1, IP2, IP3, IP4, Port1, Port2]));
- true ->
- IpAddress = inet_parse:ntoa(IP),
- Cmd = mk_cmd("EPRT |1|~s|~p|", [IpAddress, Port]),
- send_ctrl_message(State0, Cmd)
- end,
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {setup_data_connection,
- {LSock, Caller}}}}
- end;
-
-setup_data_connection(#state{mode = passive, ipfamily = inet6,
- caller = Caller} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("EPSV", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {setup_data_connection, Caller}}};
-
-setup_data_connection(#state{mode = passive, ipfamily = inet,
- caller = Caller,
- ftp_extension = false} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("PASV", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {setup_data_connection, Caller}}};
-
-setup_data_connection(#state{mode = passive, ipfamily = inet,
- caller = Caller,
- ftp_extension = true} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("EPSV", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {setup_data_connection, Caller}}}.
-
--spec connect(Host, Port, SockOpts, Timeout, State) -> Result when
- Host :: inet:socket_address() | inet:hostname(),
- Port :: inet:port_number(),
- SockOpts :: [inet:inet_backend() | gen_tcp:connect_option()],
- Timeout :: timeout(),
- State :: #state{},
- Reason :: timeout | inet:posix(),
- Result :: {ok, inet:address_family(), gen_tcp:socket()} | {error, Reason}.
-connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet = IpFam}) ->
- connect2(Host, Port, IpFam, SockOpts, Timeout);
-
-connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6 = IpFam}) ->
- connect2(Host, Port, IpFam, SockOpts, Timeout);
-
-connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6fb4}) ->
- case inet:getaddr(Host, inet6) of
- {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} ->
- case inet:getaddr(Host, inet) of
- {ok, IPv4} ->
- IpFam = inet,
- connect2(IPv4, Port, IpFam, SockOpts, Timeout);
-
- _ ->
- IpFam = inet6,
- connect2(IPv6, Port, IpFam, SockOpts, Timeout)
- end;
-
- {ok, IPv6} ->
- IpFam = inet6,
- connect2(IPv6, Port, IpFam, SockOpts, Timeout);
-
- _ ->
- case inet:getaddr(Host, inet) of
- {ok, IPv4} ->
- IpFam = inet,
- connect2(IPv4, Port, IpFam, SockOpts, Timeout);
- Error ->
- Error
- end
- end.
-
--spec connect2(Host, Port, IpFam, SockOpts, Timeout) -> Result when
- Host :: inet:socket_address() | inet:hostname(),
- Port :: inet:port_number(),
- SockOpts :: [inet:inet_backend() | gen_tcp:connect_option()],
- Timeout :: timeout(),
- IpFam :: inet:address_family(),
- Reason :: timeout | inet:posix(),
- Result :: {ok, inet:address_family(), gen_tcp:socket()} | {error, Reason}.
-connect2(Host, Port, IpFam, SockOpts, Timeout) ->
- Opts = [IpFam, binary, {packet, 0}, {active, false} | SockOpts],
- case gen_tcp:connect(Host, Port, Opts, Timeout) of
- {ok, Sock} ->
- {ok, IpFam, Sock};
- Error ->
- Error
- end.
-
--spec accept_data_connection_tls_options(State) -> Result when
- State :: #state{},
- Result :: [tuple()].
-accept_data_connection_tls_options(#state{ csock = {ssl,Socket}, tls_options = TO0, tls_ctrl_session_reuse = true }) ->
- TO = lists:keydelete(reuse_sessions, 1, TO0),
- {ok, [{session_id,SSLSessionId},{session_data,SSLSessionData}]} = ssl:connection_information(Socket, [session_id, session_data]),
- lists:keystore(reuse_session, 1, TO, {reuse_session,{SSLSessionId,SSLSessionData}});
-accept_data_connection_tls_options(#state{ tls_options = TO }) ->
- TO.
-
--spec accept_data_connection(State) -> Result when
- State :: #state{},
- Result :: {ok, #state{}} | {error, Reason},
- Reason :: term().
-accept_data_connection(#state{mode = active,
- dtimeout = DTimeout,
- tls_options = TLSOptions0,
- dsock = {lsock, LSock}} = State0) ->
- case gen_tcp:accept(LSock, DTimeout) of
- {ok, Socket} when is_list(TLSOptions0) ->
- gen_tcp:close(LSock),
- TLSOptions = accept_data_connection_tls_options(State0),
- ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]),
- case ssl:connect(Socket, TLSOptions, DTimeout) of
- {ok, TLSSocket} ->
- {ok, State0#state{dsock={ssl,TLSSocket}}};
- {error, Reason} ->
- {error, {ssl_connect_failed, Reason}}
- end;
- {ok, Socket} ->
- gen_tcp:close(LSock),
- {ok, State0#state{dsock={tcp,Socket}}};
- {error, Reason} ->
- {error, {data_connect_failed, Reason}}
- end;
-
-accept_data_connection(#state{mode = passive,
- dtimeout = DTimeout,
- dsock = {tcp,Socket},
- tls_options = TLSOptions0} = State) when is_list(TLSOptions0) ->
- TLSOptions = accept_data_connection_tls_options(State),
- ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State]),
- case ssl:connect(Socket, TLSOptions, DTimeout) of
- {ok, TLSSocket} ->
- {ok, State#state{dsock={ssl,TLSSocket}}};
- {error, Reason} ->
- {error, {ssl_connect_failed, Reason}}
- end;
-accept_data_connection(#state{mode = passive} = State) ->
- {ok,State}.
-
--spec send_ctrl_message(State, Message) -> _ when
- State :: #state{},
- Message :: [term() | Message].
-send_ctrl_message(_S=#state{csock = Socket, verbose = Verbose}, Message) ->
- verbose(lists:flatten(Message),Verbose,send),
- ?DBG('<--ctrl ~p ---- ~s~p~n',[Socket,Message,_S]),
- _ = send_message(Socket, Message).
-
-send_data_message(_S=#state{dsock = Socket}, Message) ->
- ?DBG('<==data ~p ==== ~s~n~p~n',[Socket,Message,_S]),
- case send_message(Socket, Message) of
- ok ->
- ok;
- {error, Reason} ->
- Report = io_lib:format("send/2 for socket ~p failed with "
- "reason ~p~n", [Socket, Reason]),
- error_logger:error_report(Report),
- %% If tcp/ssl does not work the only option is to terminate,
- %% this is the expected behavior under these circumstances.
- exit(normal) %% User will get error message from terminate/2
- end.
-
-send_message({tcp, Socket}, Message) ->
- gen_tcp:send(Socket, Message);
-send_message({ssl, Socket}, Message) ->
- ssl:send(Socket, Message).
-
-activate_ctrl_connection(#state{csock = CSock, ctrl_data = {<<>>, _, _}} = State) ->
- _ = activate_connection(CSock),
- State;
-activate_ctrl_connection(#state{csock = CSock} = State0) ->
- _ = activate_connection(CSock),
- %% We have already received at least part of the next control message,
- %% that has been saved in ctrl_data, process this first.
- {noreply, State} = handle_info({socket_type(CSock), unwrap_socket(CSock), <<>>}, State0),
- State.
-
-activate_data_connection(#state{dsock = DSock} = State) ->
- _ = activate_connection(DSock),
- State.
-
-activate_connection(Socket) ->
- case socket_type(Socket) of
- tcp ->
- _ = activate_connection(inet, tcp_closed, Socket);
- ssl ->
- _ = activate_connection(ssl, ssl_closed, Socket)
- end.
-
-activate_connection(API, CloseTag, Socket0) ->
- Socket = unwrap_socket(Socket0),
- case API:setopts(Socket, [{active, once}]) of
- ok ->
- ok;
- {error, _} -> %% inet can return einval instead of closed
- self() ! {CloseTag, Socket}
- end.
-
-ignore_return_value(_) -> ok.
-
-unwrap_socket({tcp,Socket}) -> Socket;
-unwrap_socket({ssl,Socket}) -> Socket.
-
-socket_type({tcp,_Socket}) -> tcp;
-socket_type({ssl,_Socket}) -> ssl.
-
-close_ctrl_connection(#state{csock = undefined}) -> ok;
-close_ctrl_connection(#state{csock = Socket}) -> close_connection(Socket).
-
-close_data_connection(#state{dsock = undefined}) -> ok;
-close_data_connection(#state{dsock = Socket}) -> close_connection(Socket).
-
-close_connection({lsock,Socket}) -> ignore_return_value( gen_tcp:close(Socket) );
-close_connection({tcp, Socket}) -> ignore_return_value( gen_tcp:close(Socket) );
-close_connection({ssl, Socket}) -> ignore_return_value( ssl:close(Socket) ).
-
-%% ------------ FILE HANDLING ----------------------------------------
-send_file(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Fd) ->
- {noreply, State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_file, Fd}}};
-send_file(State0, Fd) ->
- case file_read(Fd) of
- {ok, N, Bin} when N > 0 ->
- send_data_message(State0, Bin),
- progress_report({binary, Bin}, State0),
- send_file(State0, Fd);
- {ok, _, _} ->
- file_close(Fd),
- close_data_connection(State0),
- progress_report({transfer_size, 0}, State0),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = transfer_file_second_phase,
- dsock = undefined}};
- {error, Reason} ->
- gen_server:reply(State0#state.client, {error, Reason}),
- {stop, normal, State0#state{client = undefined}}
- end.
-
-file_open(File, Option) ->
- file:open(File, [raw, binary, Option]).
-
-file_close(Fd) ->
- ignore_return_value( file:close(Fd) ).
-
-file_read(Fd) ->
- case file:read(Fd, ?FILE_BUFSIZE) of
- {ok, Bytes} when is_binary(Bytes) ->
- {ok, byte_size(Bytes), Bytes};
- eof ->
- {ok, 0, []};
- Other ->
- Other
- end.
-
-file_write(Bytes, Fd) ->
- file:write(Fd, Bytes).
-
-%% -------------- MISC ----------------------------------------------
-
-call(GenServer, Msg, Format) ->
- call(GenServer, Msg, Format, infinity).
-call(GenServer, Msg, Format, Timeout) ->
- Req = {self(), Msg},
- case (catch gen_server:call(GenServer, Req, Timeout)) of
- {ok, Bin} when is_binary(Bin) andalso (Format =:= string) ->
- {ok, binary_to_list(Bin)};
- {'EXIT', _, _} ->
- {error, eclosed};
- {'EXIT', _} ->
- {error, eclosed};
- Result ->
- Result
- end.
-
-cast(GenServer, Msg) ->
- gen_server:cast(GenServer, {self(), Msg}).
-
-send_bin(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Bin) ->
- State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_bin, Bin}};
-send_bin(State0, Bin) ->
- send_data_message(State0, Bin),
- close_data_connection(State0),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = transfer_data_second_phase,
- dsock = undefined}}.
-
-mk_cmd(Fmt, Args) ->
- [io_lib:format(Fmt, Args)| [?CR, ?LF]]. % Deep list ok.
-
-is_name_sane([]) ->
- true;
-is_name_sane([?CR| _]) ->
- false;
-is_name_sane([?LF| _]) ->
- false;
-is_name_sane([_| Rest]) ->
- is_name_sane(Rest).
-
-pwd_result(Lines) ->
- {_, [?DOUBLE_QUOTE | Rest]} =
- lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines),
- {Dir, _} =
- lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest),
- Dir.
-
-
-key_search(Key, List, Default) ->
- case lists:keysearch(Key, 1, List) of
- {value, {_,Val}} ->
- Val;
- false ->
- Default
- end.
-
-verbose(Lines, true, Direction) ->
- DirStr =
- case Direction of
- send ->
- "Sending: ";
- _ ->
- "Receiving: "
- end,
- Str = string:strip(string:strip(Lines, right, ?LF), right, ?CR),
- erlang:display(DirStr++Str);
-verbose(_, false,_) ->
- ok.
-
-progress(Options) ->
- ftp_progress:start_link(Options).
-
-progress_report(_, #state{progress = ignore}) ->
- ok;
-progress_report(stop, #state{progress = ProgressPid}) ->
- ftp_progress:stop(ProgressPid);
-progress_report({binary, Data}, #state{progress = ProgressPid}) when is_binary(Data) ->
- ftp_progress:report(ProgressPid, {transfer_size, byte_size(Data)});
-progress_report(Report, #state{progress = ProgressPid}) ->
- ftp_progress:report(ProgressPid, Report).
-
-
-peername({tcp, Socket}) -> inet:peername(Socket);
-peername({ssl, Socket}) -> ssl:peername(Socket).
-
-sockname({tcp, Socket}) -> inet:sockname(Socket);
-sockname({ssl, Socket}) -> ssl:sockname(Socket).
-
-start_chunk(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State) ->
- State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, start_chunk, undefined}};
-start_chunk(#state{client = From} = State) ->
- gen_server:reply(From, ok),
- State#state{chunk = true,
- client = undefined,
- caller = undefined}.
-
-
-%% This function extracts the start options from the
-%% Valid options:
-%% debug,
-%% verbose
-%% ipfamily
-%% priority
-%% flags (for backward compatibillity)
-start_options(Options) ->
- case lists:keysearch(flags, 1, Options) of
- {value, {flags, Flags}} ->
- Verbose = lists:member(verbose, Flags),
- IsTrace = lists:member(trace, Flags),
- IsDebug = lists:member(debug, Flags),
- DebugLevel =
- if
- (IsTrace =:= true) ->
- trace;
- IsDebug =:= true ->
- debug;
- true ->
- disable
- end,
- {ok, [{verbose, Verbose},
- {debug, DebugLevel},
- {priority, low}]};
- false ->
- ValidateVerbose =
- fun(true) -> true;
- (false) -> true;
- (_) -> false
- end,
- ValidateDebug =
- fun(trace) -> true;
- (debug) -> true;
- (disable) -> true;
- (_) -> false
- end,
- ValidatePriority =
- fun(low) -> true;
- (normal) -> true;
- (high) -> true;
- (_) -> false
- end,
- ValidOptions =
- [{verbose, ValidateVerbose, false, false},
- {debug, ValidateDebug, false, disable},
- {priority, ValidatePriority, false, low}],
- validate_options(Options, ValidOptions, [])
- end.
-
-
-%% This function extracts and validates the open options from the
-%% Valid options:
-%% mode
-%% host
-%% port
-%% timeout
-%% dtimeout
-%% progress
-%% ftp_extension
-
--spec open_options([tuple()]) -> {ok, [tuple()]} | no_return().
-open_options(Options) ->
- ValidateMode =
- fun(active) -> true;
- (passive) -> true;
- (_) -> false
- end,
- ValidateHost =
- fun(Host) when is_list(Host) ->
- true;
- (Host) when tuple_size(Host) =:= 4; tuple_size(Host) =:= 8 ->
- true;
- (_) ->
- false
- end,
- ValidatePort =
- fun(Port) when is_integer(Port) andalso (Port >= 0) -> true;
- (_) -> false
- end,
- ValidateIpFamily =
- fun(inet) -> true;
- (inet6) -> true;
- (inet6fb4) -> true;
- (_) -> false
- end,
- ValidateTLS =
- fun(TLS) when is_list(TLS) -> true;
- (undefined) -> true;
- (_) -> false
- end,
- ValidateTLSSecMethod =
- fun(ftpes) -> true;
- (ftps) -> true;
- (_) -> false
- end,
- ValidateTLSCtrlSessionReuse =
- fun(Reuse) when is_boolean(Reuse) -> true;
- (_) -> false
- end,
- ValidateTimeout =
- fun(Timeout) when is_integer(Timeout) andalso (Timeout >= 0) -> true;
- (_) -> false
- end,
- ValidateDTimeout =
- fun(DTimeout) when is_integer(DTimeout) andalso (DTimeout >= 0) -> true;
- (infinity) -> true;
- (_) -> false
- end,
- ValidateProgress =
- fun(ignore) ->
- true;
- ({Mod, Func, _InitProgress}) when is_atom(Mod) andalso
- is_atom(Func) ->
- true;
- (_) ->
- false
- end,
- ValidateFtpExtension =
- fun(true) -> true;
- (false) -> true;
- (_) -> false
- end,
- ValidOptions =
- [{mode, ValidateMode, false, ?DEFAULT_MODE},
- {host, ValidateHost, true, ehost},
- {port, ValidatePort, false, 0},
- {ipfamily, ValidateIpFamily, false, inet},
- {tls, ValidateTLS, false, undefined},
- {tls_sec_method, ValidateTLSSecMethod, false, ftpes},
- {tls_ctrl_session_reuse, ValidateTLSCtrlSessionReuse, false, false},
- {timeout, ValidateTimeout, false, ?CONNECTION_TIMEOUT},
- {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT},
- {progress, ValidateProgress, false, ?PROGRESS_DEFAULT},
- {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}],
- validate_options(Options, ValidOptions, []).
-
-%% validates socket options and set defaults
--spec socket_options([tuple()]) -> {ok, tuple()} | no_return().
-socket_options(Options) ->
- CtrlOpts = proplists:get_value(sock_ctrl, Options, []),
- DataActOpts = proplists:get_value(sock_data_act, Options, CtrlOpts),
- DataPassOpts = proplists:get_value(sock_data_pass, Options, CtrlOpts),
- case [O || O <- lists:usort(CtrlOpts++DataPassOpts++DataActOpts),
- not valid_socket_option(O)] of
- [] ->
- {ok, {CtrlOpts, DataPassOpts, DataActOpts}};
- Invalid ->
- throw({error,{sock_opts,Invalid}})
- end.
-
-
-valid_socket_option(inet ) -> false;
-valid_socket_option(inet6 ) -> false;
-valid_socket_option({ipv6_v6only, _}) -> false;
-valid_socket_option({active,_} ) -> false;
-valid_socket_option({packet,_} ) -> false;
-valid_socket_option({mode,_} ) -> false;
-valid_socket_option(binary ) -> false;
-valid_socket_option(list ) -> false;
-valid_socket_option({header,_} ) -> false;
-valid_socket_option({packet_size,_} ) -> false;
-valid_socket_option(_) -> true.
-
-
--spec validate_options(Options, ValidOptions, Acc) -> Result when
- Options :: [tuple()],
- ValidOptions :: [tuple()],
- Acc :: [tuple()],
- Result :: {ok, [tuple()]} | no_return().
-validate_options([], [], Acc) ->
- {ok, lists:reverse(Acc)};
-validate_options([], ValidOptions, Acc) ->
- %% Check if any mandatory options are missing!
- case [{Key, Reason} || {Key, _, true, Reason} <- ValidOptions] of
- [] ->
- Defaults =
- [{Key, Default} || {Key, _, _, Default} <- ValidOptions],
- {ok, lists:reverse(Defaults ++ Acc)};
- [{_, Reason}|_Missing] ->
- throw({error, Reason})
- end;
-validate_options([{Key, Value}|Options], ValidOptions, Acc) ->
- case lists:keysearch(Key, 1, ValidOptions) of
- {value, {Key, Validate, _, Default}} ->
- case (catch Validate(Value)) of
- true ->
- NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
- validate_options(Options, NewValidOptions,
- [{Key, Value} | Acc]);
- _ ->
- NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
- validate_options(Options, NewValidOptions,
- [{Key, Default} | Acc])
- end;
- false ->
- validate_options(Options, ValidOptions, Acc)
- end;
-validate_options([_|Options], ValidOptions, Acc) ->
- validate_options(Options, ValidOptions, Acc).
-
-%% Help function, elapsed milliseconds since T0
-millisec_passed(T0) ->
- %% OTP 18
- erlang:convert_time_unit(erlang:monotonic_time() - T0,
- native,
- micro_seconds) div 1000.
diff --git a/lib/ftp/src/ftp_internal.erl b/lib/ftp/src/ftp_internal.erl
new file mode 100644
index 0000000000..2b8ee8f9e1
--- /dev/null
+++ b/lib/ftp/src/ftp_internal.erl
@@ -0,0 +1,2467 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_internal).
+
+-behaviour(gen_server).
+
+-export([start_service/1]).
+
+-export([start_link/1, start_link/2]).
+
+%% API - Client interface
+-export([cd/2, close/1, delete/2,
+ lcd/2, lpwd/1, ls/2,
+ mkdir/2, nlist/2,
+ open/1, open/2,
+ pwd/1, quote/2,
+ recv/2, recv/3, recv_bin/2,
+ recv_chunk_start/2, recv_chunk/1,
+ rename/3, rmdir/2,
+ send/2, send/3, send_bin/3,
+ send_chunk_start/2, send_chunk/2, send_chunk_end/1,
+ type/2, user/3, user/4, account/2,
+ append/3, append_bin/3,
+ append_chunk/2, append_chunk_end/1, append_chunk_start/2,
+ info/1, latest_ctrl_response/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
+
+-include("ftp_internal.hrl").
+
+-define(DBG(F,A), 'n/a').
+%%-define(DBG(F,A), io:format(F,A)).
+%%-define(DBG(F,A), ct:pal("~p:~p " ++ if is_list(F) -> F; is_atom(F) -> atom_to_list(F) end, [?MODULE,?LINE|A])).
+
+%% Constants used in internal state definition
+-define(CONNECTION_TIMEOUT, 60*1000).
+-define(DATA_ACCEPT_TIMEOUT, infinity).
+-define(DEFAULT_MODE, passive).
+-define(PROGRESS_DEFAULT, ignore).
+-define(FTP_EXT_DEFAULT, false).
+
+%% Internal Constants
+-define(FTP_PORT, 21).
+-define(FTPS_PORT, 990).
+-define(FILE_BUFSIZE, 4096).
+
+
+%%%=========================================================================
+%%% Data Types
+%%%=========================================================================
+
+%% Internal state
+-record(state, {
+ csock = undefined :: undefined % Control connection socket
+ | { tcp | ssl, inet:socket() | ssl:socket() | ssl:sslsocket()},
+ dsock = undefined :: undefined % Data connection socket
+ | { tcp | ssl | lsock, inet:socket() | ssl:socket() | ssl:sslsocket() | gen_tcp:socket()},
+ tls_options = undefined :: undefined | [tuple()],
+ verbose = false :: boolean(),
+ ldir = undefined :: undefined | file:filename_all(), % Current local directory
+ type = ftp_server_default :: atom(), % binary | ascii
+ chunk = false :: boolean(), % Receiving data chunks
+ mode = ?DEFAULT_MODE :: active | passive,
+ timeout = ?CONNECTION_TIMEOUT :: timeout(),
+ %% Data received so far on the data connection
+ data = <<>> :: binary(),
+ %% Data received so far on the control connection
+ %% {BinStream, AccLines}. If a binary sequence
+ %% ends with ?CR then keep it in the binary to
+ %% be able to detect if the next received byte is ?LF
+ %% and hence the end of the response is reached!
+ ctrl_data = {<<>>, [], start} :: {binary(), [byte()], term()},
+ %% pid() - Client pid (note not the same as "From")
+ latest_ctrl_response = "" :: string(),
+ owner = undefined :: undefined | term(),
+ client = undefined :: undefined | gen_server:from(), % "From" to be used in gen_server:reply/2
+ %% Function that activated a connection and maybe some
+ %% data needed further on.
+ caller = undefined :: term(),
+ ipfamily :: inet:address_family() | inet6fb4,
+ sockopts_ctrl = [] :: list(),
+ sockopts_data_passive = [] :: list(),
+ sockopts_data_active = [] :: list(),
+ progress = ignore :: ignore | pid(),
+ dtimeout = ?DATA_ACCEPT_TIMEOUT :: non_neg_integer() | infinity,
+ tls_ctrl_session_reuse = false :: boolean(),
+ tls_upgrading_data_connection = false :: boolean() | atom() | tuple(),
+ ftp_extension = ?FTP_EXT_DEFAULT :: boolean()
+ }).
+
+-record(recv_chunk_closing, {
+ dconn_closed = false,
+ pos_compl_received = false,
+ client_called_us = false
+ }).
+
+
+-type shortage_reason() :: 'etnospc' | 'epnospc'.
+-type restriction_reason() :: 'epath' | 'efnamena' | 'elogin' | 'enotbinary'.
+-type common_reason() :: 'econn' | 'eclosed' | term().
+-type file_write_error_reason() :: file:posix() | badarg | terminated. % See file:write for more info
+
+
+%% This should be made an internal function when we remove the deprecation
+%% ftp client processes should always be part of ftp supervisor tree.
+%% We consider it a bug that the "standalone" concept of inets was
+%% not removed when ftp was broken out, and it is now fixed.
+-spec start_service(ServiceConfig) -> {ok, Pid} | {error, Reason} when
+ ServiceConfig :: [{Property, Value}],
+ Property :: proplists:property(),
+ Value :: term(),
+ Pid :: pid(),
+ Reason :: term().
+start_service(Options) ->
+ try
+ {ok, StartOptions} = start_options(Options),
+ case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
+ {ok, Pid} ->
+ call(Pid, {open, ip_comm, Options}, plain);
+ Error1 ->
+ Error1
+ end
+ catch
+ throw:Error2 ->
+ Error2
+ end.
+
+-spec open(Host :: string() | inet:ip_address()) ->
+ {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}.
+
+open(Host) ->
+ open(Host, []).
+
+-spec open(Host :: string() | inet:ip_address(), Opts) ->
+ {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()} when
+ Opts :: [Opt],
+ Opt :: StartOption | OpenOption,
+ StartOption :: {verbose, Verbose} | {debug, Debug},
+ Verbose :: boolean(),
+ Debug :: disable | debug | trace,
+ OpenOption :: {ipfamily, IpFamily} | {port, Port :: port()} | {mode, Mode}
+ | {tls, TLSOptions :: [ssl:tls_option()]} | {tls_sec_method, TLSSecMethod :: ftps | ftpes}
+ | {tls_ctrl_session_reuse, TLSSessionReuse :: boolean() } | {timeout, Timeout :: timeout()}
+ | {dtimeout, DTimeout :: timeout()} | {progress, Progress} | {sock_ctrl, SocketCtrls}
+ | {sock_data_act, [SocketControl]} | {sock_data_pass, [SocketControl]},
+ SocketCtrls :: [SocketControl],
+ IpFamily :: inet | inet6 | inet6fb4,
+ Mode :: active | passive,
+ Module :: atom(),
+ Function :: atom(),
+ InitialData :: term(),
+ Progress :: ignore | {Module, Function, InitialData},
+ SocketControl :: gen_tcp:option().
+%% <BACKWARD-COMPATIBILLITY>
+open(Host, Port) when is_integer(Port) ->
+ open(Host, [{port, Port}]);
+%% </BACKWARD-COMPATIBILLITY>
+
+open(Host, Options) when is_list(Options) ->
+ start_service([{host,Host}|Options]).
+
+
+%%--------------------------------------------------------------------------
+%% Description: Login with or without a supplied account name.
+%%--------------------------------------------------------------------------
+-spec user(Pid :: pid(),
+ User :: string(),
+ Pass :: string()) ->
+ 'ok' | {'error', Reason :: 'euser' | common_reason()}.
+
+user(Pid, User, Pass) ->
+ case {is_name_sane(User), is_name_sane(Pass)} of
+ {true, true} ->
+ call(Pid, {user, User, Pass}, atom);
+ _ ->
+ {error, euser}
+ end.
+
+-spec user(Pid :: pid(),
+ User :: string(),
+ Pass :: string(),
+ Account :: string()) ->
+ 'ok' | {'error', Reason :: 'euser' | common_reason()}.
+
+user(Pid, User, Pass, Account) ->
+ case {is_name_sane(User), is_name_sane(Pass), is_name_sane(Account)} of
+ {true, true, true} ->
+ call(Pid, {user, User, Pass, Account}, atom);
+ _ ->
+ {error, euser}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Set a user Account.
+%%--------------------------------------------------------------------------
+
+-spec account(Pid :: pid(), Acc :: string()) ->
+ 'ok' | {'error', Reason :: 'eacct' | common_reason()}.
+
+account(Pid, Acc) ->
+ case is_name_sane(Acc) of
+ true ->
+ call(Pid, {account, Acc}, atom);
+ _ ->
+ {error, eacct}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Get the current working directory at remote server.
+%%--------------------------------------------------------------------------
+
+-spec pwd(Pid :: pid()) ->
+ {'ok', Dir :: string()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
+
+pwd(Pid) ->
+ call(Pid, pwd, ctrl).
+
+
+%%--------------------------------------------------------------------------
+%% Description: Get the current working directory at local server.
+%%--------------------------------------------------------------------------
+
+-spec lpwd(Pid :: pid()) ->
+ {'ok', Dir :: string()}.
+
+lpwd(Pid) ->
+ call(Pid, lpwd, string).
+
+
+%%--------------------------------------------------------------------------
+%% Description: Change current working directory at remote server.
+%%--------------------------------------------------------------------------
+
+-spec cd(Pid :: pid(), Dir :: string()) ->
+ 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+
+cd(Pid, Dir) ->
+ case is_name_sane(Dir) of
+ true ->
+ call(Pid, {cd, Dir}, atom);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Change current working directory for the local client.
+%%--------------------------------------------------------------------------
+
+-spec lcd(Pid :: pid(), Dir :: string()) ->
+ 'ok' | {'error', Reason :: restriction_reason()}.
+
+lcd(Pid, Dir) ->
+ call(Pid, {lcd, Dir}, string).
+
+
+%%--------------------------------------------------------------------------
+%% Description: Returns a list of files in long format.
+%%--------------------------------------------------------------------------
+
+-spec ls(Pid :: pid(), Dir :: string()) ->
+ {'ok', Listing :: string()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
+
+ls(Pid, Dir) ->
+ case is_name_sane(Dir) of
+ true ->
+ call(Pid, {dir, long, Dir}, string);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Returns a list of files in short format
+%%--------------------------------------------------------------------------
+
+-spec nlist(Pid :: pid(), Pathname :: string()) ->
+ {'ok', Listing :: string()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
+
+nlist(Pid, Dir) ->
+ case is_name_sane(Dir) of
+ true ->
+ call(Pid, {dir, short, Dir}, string);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Rename a file at remote server.
+%%--------------------------------------------------------------------------
+
+-spec rename(Pid :: pid(), Old :: string(), New :: string()) ->
+ 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+
+rename(Pid, Old, New) ->
+ case {is_name_sane(Old), is_name_sane(New)} of
+ {true, true} ->
+ call(Pid, {rename, Old, New}, string);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Remove file at remote server.
+%%--------------------------------------------------------------------------
+
+-spec delete(Pid :: pid(), File :: string()) ->
+ 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+
+delete(Pid, File) ->
+ case is_name_sane(File) of
+ true ->
+ call(Pid, {delete, File}, string);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Make directory at remote server.
+%%--------------------------------------------------------------------------
+
+-spec mkdir(Pid :: pid(), Dir :: string()) ->
+ 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+
+mkdir(Pid, Dir) ->
+ case is_name_sane(Dir) of
+ true ->
+ call(Pid, {mkdir, Dir}, atom);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Remove directory at remote server.
+%%--------------------------------------------------------------------------
+
+-spec rmdir(Pid :: pid(), Dir :: string()) ->
+ 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+
+rmdir(Pid, Dir) ->
+ case is_name_sane(Dir) of
+ true ->
+ call(Pid, {rmdir, Dir}, atom);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Set transfer type.
+%%--------------------------------------------------------------------------
+
+-spec type(Pid :: pid(), Type :: ascii | binary) ->
+ 'ok' |
+ {'error', Reason :: 'etype' | restriction_reason() | common_reason()}.
+
+type(Pid, Type) ->
+ call(Pid, {type, Type}, atom).
+
+
+%%--------------------------------------------------------------------------
+%% Description: Transfer file from remote server.
+%%--------------------------------------------------------------------------
+
+-spec recv(Pid :: pid(), RemoteFileName :: string()) ->
+ 'ok' | {'error', Reason :: restriction_reason() |
+ common_reason() |
+ file_write_error_reason()}.
+
+recv(Pid, RemotFileName) ->
+ recv(Pid, RemotFileName, RemotFileName).
+
+-spec recv(Pid :: pid(),
+ RemoteFileName :: string(),
+ LocalFileName :: string()) ->
+ 'ok' | {'error', Reason :: term()}.
+
+recv(Pid, RemotFileName, LocalFileName) ->
+ case is_name_sane(RemotFileName) of
+ true ->
+ call(Pid, {recv, RemotFileName, LocalFileName}, atom);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Transfer file from remote server into binary.
+%%--------------------------------------------------------------------------
+
+-spec recv_bin(Pid :: pid(),
+ RemoteFile :: string()) ->
+ {'ok', Bin :: binary()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
+
+recv_bin(Pid, RemoteFile) ->
+ case is_name_sane(RemoteFile) of
+ true ->
+ call(Pid, {recv_bin, RemoteFile}, bin);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Start receive of chunks of remote file.
+%%--------------------------------------------------------------------------
+
+-spec recv_chunk_start(Pid :: pid(),
+ RemoteFile :: string()) ->
+ 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+
+recv_chunk_start(Pid, RemoteFile) ->
+ case is_name_sane(RemoteFile) of
+ true ->
+ call(Pid, {recv_chunk_start, RemoteFile}, atom);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Transfer file from remote server into binary in chunks
+%%--------------------------------------------------------------------------
+
+-spec recv_chunk(Pid :: pid()) ->
+ 'ok' |
+ {'ok', Bin :: binary()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
+
+recv_chunk(Pid) ->
+ call(Pid, recv_chunk, atom).
+
+
+%%--------------------------------------------------------------------------
+%% Description: Transfer file to remote server.
+%%--------------------------------------------------------------------------
+
+-spec send(Pid :: pid(), LocalFileName :: string()) ->
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
+ shortage_reason()}.
+
+send(Pid, LocalFileName) ->
+ send(Pid, LocalFileName, LocalFileName).
+
+-spec send(Pid :: pid(),
+ LocalFileName :: string(),
+ RemoteFileName :: string()) ->
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
+ shortage_reason()}.
+
+send(Pid, LocalFileName, RemotFileName) ->
+ case is_name_sane(RemotFileName) of
+ true ->
+ call(Pid, {send, LocalFileName, RemotFileName}, atom);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Transfer a binary to a remote file.
+%%--------------------------------------------------------------------------
+
+-spec send_bin(Pid :: pid(), Bin :: binary(), RemoteFile :: string()) ->
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
+ shortage_reason()}.
+
+send_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
+ case is_name_sane(RemoteFile) of
+ true ->
+ call(Pid, {send_bin, Bin, RemoteFile}, atom);
+ _ ->
+ {error, efnamena}
+ end;
+send_bin(_Pid, _Bin, _RemoteFile) ->
+ {error, enotbinary}.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Start transfer of chunks to remote file.
+%%--------------------------------------------------------------------------
+
+-spec send_chunk_start(Pid :: pid(), RemoteFile :: string()) ->
+ 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+
+send_chunk_start(Pid, RemoteFile) ->
+ case is_name_sane(RemoteFile) of
+ true ->
+ call(Pid, {send_chunk_start, RemoteFile}, atom);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Start append chunks of data to remote file.
+%%--------------------------------------------------------------------------
+
+-spec append_chunk_start(Pid :: pid(), RemoteFile :: string()) ->
+ 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
+
+append_chunk_start(Pid, RemoteFile) ->
+ case is_name_sane(RemoteFile) of
+ true ->
+ call(Pid, {append_chunk_start, RemoteFile}, atom);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Purpose: Send chunk to remote file.
+%%--------------------------------------------------------------------------
+
+-spec send_chunk(Pid :: pid(), Bin :: binary()) ->
+ 'ok' |
+ {'error', Reason :: 'echunk' |
+ restriction_reason() |
+ common_reason()}.
+
+send_chunk(Pid, Bin) when is_binary(Bin) ->
+ call(Pid, {transfer_chunk, Bin}, atom);
+send_chunk(_Pid, _Bin) ->
+ {error, enotbinary}.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Append chunk to remote file.
+%%--------------------------------------------------------------------------
+
+-spec append_chunk(Pid :: pid(), Bin :: binary()) ->
+ 'ok' |
+ {'error', Reason :: 'echunk' |
+ restriction_reason() |
+ common_reason()}.
+
+append_chunk(Pid, Bin) when is_binary(Bin) ->
+ call(Pid, {transfer_chunk, Bin}, atom);
+append_chunk(_Pid, _Bin) ->
+ {error, enotbinary}.
+
+
+%%--------------------------------------------------------------------------
+%% Description: End sending of chunks to remote file.
+%%--------------------------------------------------------------------------
+
+-spec send_chunk_end(Pid :: pid()) ->
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
+ shortage_reason()}.
+
+send_chunk_end(Pid) ->
+ call(Pid, chunk_end, atom).
+
+
+%%--------------------------------------------------------------------------
+%% Description: End appending of chunks to remote file.
+%%--------------------------------------------------------------------------
+
+-spec append_chunk_end(Pid :: pid()) ->
+ 'ok' |
+ {'error', Reason :: echunk |
+ restriction_reason() |
+ common_reason() |
+ shortage_reason()}.
+
+append_chunk_end(Pid) ->
+ call(Pid, chunk_end, atom).
+
+
+%%--------------------------------------------------------------------------
+%% Description: Append the local file to the remote file
+%%--------------------------------------------------------------------------
+
+-spec append(Pid :: pid(),
+ LocalFileName :: string(),
+ RemoteFileName :: string()) ->
+ 'ok' | {'error', Reason} when
+ Reason :: epath | elogin | etnospc | epnospc | efnamena | common_reason().
+
+append(Pid, LocalFileName, RemotFileName) ->
+ case is_name_sane(RemotFileName) of
+ true ->
+ call(Pid, {append, LocalFileName, RemotFileName}, atom);
+ _ ->
+ {error, efnamena}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% Purpose: Append a binary to a remote file.
+%%--------------------------------------------------------------------------
+
+-spec append_bin(Pid :: pid(),
+ Bin :: binary(),
+ RemoteFile :: string()) ->
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
+ shortage_reason()}.
+
+append_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
+ case is_name_sane(RemoteFile) of
+ true ->
+ call(Pid, {append_bin, Bin, RemoteFile}, atom);
+ _ ->
+ {error, efnamena}
+ end;
+append_bin(_Pid, _Bin, _RemoteFile) ->
+ {error, enotbinary}.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Send arbitrary ftp command.
+%%--------------------------------------------------------------------------
+
+-spec quote(Pid :: pid(), Cmd :: string()) -> [FTPLine :: string()].
+
+quote(Pid, Cmd) when is_list(Cmd) ->
+ call(Pid, {quote, Cmd}, atom).
+
+
+%%--------------------------------------------------------------------------
+%% Description: End the ftp session.
+%%--------------------------------------------------------------------------
+
+-spec close(Pid :: pid()) -> 'ok'.
+close(Pid) ->
+ cast(Pid, close),
+ ok.
+
+
+%%--------------------------------------------------------------------------
+%% Description: Return diagnostics.
+%%--------------------------------------------------------------------------
+
+info(Pid) ->
+ call(Pid, info, list).
+
+
+%%--------------------------------------------------------------------------
+%% Description: The latest received response from the server
+%%--------------------------------------------------------------------------
+
+-spec latest_ctrl_response(Pid :: pid()) -> string().
+
+latest_ctrl_response(Pid) ->
+ call(Pid, latest_ctrl_response, string).
+
+
+%%%========================================================================
+%%% gen_server callback functions
+%%%========================================================================
+
+%%-------------------------------------------------------------------------
+%% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason}
+%% Description: Initiates the erlang process that manages a ftp connection.
+%%-------------------------------------------------------------------------
+init(Options) ->
+ process_flag(trap_exit, true),
+
+ %% Keep track of the client
+ {value, {client, Client}} = lists:keysearch(client, 1, Options),
+ erlang:monitor(process, Client),
+
+ %% Make sure inet is started
+ _ = inet_db:start(),
+
+ %% Where are we
+ {ok, Dir} = file:get_cwd(),
+
+ %% Maybe activate dbg
+ case key_search(debug, Options, disable) of
+ trace ->
+ dbg:tracer(),
+ dbg:p(all, [call]),
+ {ok, _} = dbg:tpl(ftp_internal, [{'_', [], [{return_trace}]}]),
+ {ok, _} = dbg:tpl(ftp_response, [{'_', [], [{return_trace}]}]),
+ {ok, _} = dbg:tpl(ftp_progress, [{'_', [], [{return_trace}]}]),
+ ok;
+ debug ->
+ dbg:tracer(),
+ dbg:p(all, [call]),
+ {ok, _} = dbg:tp(ftp_internal, [{'_', [], [{return_trace}]}]),
+ {ok, _} = dbg:tp(ftp_response, [{'_', [], [{return_trace}]}]),
+ {ok, _} = dbg:tp(ftp_progress, [{'_', [], [{return_trace}]}]),
+ ok;
+ _ ->
+ %% Keep silent
+ ok
+ end,
+
+ %% Verbose?
+ Verbose = key_search(verbose, Options, false),
+
+ %% IpFamily?
+ IpFamily = key_search(ipfamily, Options, inet),
+
+ State = #state{owner = Client,
+ verbose = Verbose,
+ ipfamily = IpFamily,
+ ldir = Dir},
+
+ %% Set process prio
+ Priority = key_search(priority, Options, low),
+ process_flag(priority, Priority),
+
+ %% And we are done
+ {ok, State}.
+
+
+%%--------------------------------------------------------------------------
+%% handle_call(Request, From, State) -> {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% Description: Handle incoming requests.
+%%-------------------------------------------------------------------------
+
+%% Anyone can ask this question
+handle_call({_, info}, _, #state{verbose = Verbose,
+ mode = Mode,
+ timeout = Timeout,
+ ipfamily = IpFamily,
+ csock = Socket,
+ progress = Progress} = State) ->
+ {ok, {_, LocalPort}} = sockname(Socket),
+ {ok, {Address, Port}} = peername(Socket),
+ Options = [{verbose, Verbose},
+ {ipfamily, IpFamily},
+ {mode, Mode},
+ {peer, Address},
+ {peer_port, Port},
+ {local_port, LocalPort},
+ {timeout, Timeout},
+ {progress, Progress}],
+ {reply, {ok, Options}, State};
+
+handle_call({_,latest_ctrl_response}, _, #state{latest_ctrl_response=Resp} = State) ->
+ {reply, {ok,Resp}, State};
+
+%% But everything else must come from the owner
+handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid ->
+ {reply, {error, not_connection_owner}, State};
+
+handle_call({_, {open, ip_comm, Options}}, From, State) ->
+ {ok, Opts} = open_options(Options),
+
+ case key_search(host, Opts, undefined) of
+ undefined ->
+ {stop, normal, {error, ehost}, State};
+ Host ->
+ TLSSecMethod = key_search(tls_sec_method, Opts, undefined),
+ TLSOpts = key_search(tls, Opts, undefined),
+ TLSReuse = key_search(tls_ctrl_session_reuse, Opts, false),
+ Mode = key_search(mode, Opts, ?DEFAULT_MODE),
+ Port0 = key_search(port, Opts, 0),
+ Port = if Port0 == 0, TLSSecMethod == ftps -> ?FTPS_PORT; Port0 == 0 -> ?FTP_PORT; true -> Port0 end,
+ Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT),
+ DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT),
+ Progress = key_search(progress, Opts, ignore),
+ IpFamily = key_search(ipfamily, Opts, inet),
+ FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT),
+
+ {ok, {CtrlOpts, DataPassOpts, DataActOpts}} = socket_options(Options),
+
+ State2 = State#state{client = From,
+ mode = Mode,
+ progress = progress(Progress),
+ ipfamily = IpFamily,
+ sockopts_ctrl = CtrlOpts,
+ sockopts_data_passive = DataPassOpts,
+ sockopts_data_active = DataActOpts,
+ timeout = Timeout,
+ dtimeout = DTimeout,
+ ftp_extension = FtpExt},
+
+ case setup_ctrl_connection(Host, Port, Timeout, State2) of
+ {ok, State3, WaitTimeout} when is_list(TLSOpts), TLSSecMethod == ftps ->
+ handle_ctrl_result({tls_upgrade, TLSSecMethod},
+ State3#state{tls_options = TLSOpts,
+ tls_ctrl_session_reuse = TLSReuse,
+ timeout = WaitTimeout });
+ {ok, State3, WaitTimeout} when is_list(TLSOpts) ->
+ {noreply, State3#state{tls_options = TLSOpts, tls_ctrl_session_reuse = TLSReuse }, WaitTimeout};
+ {ok, State3, WaitTimeout} ->
+ {noreply, State3, WaitTimeout};
+ {error, _Reason} ->
+ gen_server:reply(From, {error, ehost}),
+ {stop, normal, State2#state{client = undefined}}
+ end
+ end;
+
+handle_call({_, {user, User, Password}}, From,
+ #state{csock = CSock} = State) when (CSock =/= undefined) ->
+ handle_user(User, Password, "", State#state{client = From});
+
+handle_call({_, {user, User, Password, Acc}}, From,
+ #state{csock = CSock} = State) when (CSock =/= undefined) ->
+ handle_user(User, Password, Acc, State#state{client = From});
+
+handle_call({_, {account, Acc}}, From, State)->
+ handle_user_account(Acc, State#state{client = From});
+
+handle_call({_, pwd}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("PWD", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{client = From, caller = pwd}};
+
+handle_call({_, lpwd}, From, #state{ldir = LDir} = State) ->
+ {reply, {ok, LDir}, State#state{client = From}};
+
+handle_call({_, {cd, Dir}}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{client = From, caller = cd}};
+
+handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) ->
+ LDir = filename:absname(Dir, LDir0),
+ case file:read_file_info(LDir) of %% FIX better check that LDir is a dir.
+ {ok, _ } ->
+ {reply, ok, State#state{ldir = LDir}};
+ _ ->
+ {reply, {error, epath}, State}
+ end;
+
+handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From,
+ #state{chunk = false} = State) ->
+ setup_data_connection(State#state{caller = {dir, Dir, Len},
+ client = From});
+handle_call({_, {rename, CurrFile, NewFile}}, From,
+ #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("RNFR ~s", [CurrFile])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {rename, NewFile}, client = From}};
+
+handle_call({_, {delete, File}}, {_Pid, _} = From,
+ #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("DELE ~s", [File])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{client = From}};
+
+handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("MKD ~s", [Dir])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{client = From}};
+
+handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("RMD ~s", [Dir])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{client = From}};
+
+handle_call({_,{type, Type}}, From, #state{chunk = false} = State0) ->
+ case Type of
+ ascii ->
+ _ = send_ctrl_message(State0, mk_cmd("TYPE A", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = type, type = ascii,
+ client = From}};
+ binary ->
+ _ = send_ctrl_message(State0, mk_cmd("TYPE I", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = type, type = binary,
+ client = From}};
+ _ ->
+ {reply, {error, etype}, State0}
+ end;
+handle_call({_,{recv, RemoteFile, LocalFile}}, From,
+ #state{chunk = false, ldir = LocalDir} = State) ->
+ progress_report({remote_file, RemoteFile}, State),
+ NewLocalFile = filename:absname(LocalFile, LocalDir),
+
+ case file_open(NewLocalFile, write) of
+ {ok, Fd} ->
+ setup_data_connection(State#state{client = From,
+ caller =
+ {recv_file,
+ RemoteFile, Fd}});
+ {error, _What} ->
+ {reply, {error, epath}, State}
+ end;
+handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} =
+ State) ->
+ setup_data_connection(State#state{caller = {recv_bin, RemoteFile},
+ client = From});
+handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false}
+ = State) ->
+ setup_data_connection(State#state{caller = {start_chunk_transfer,
+ "RETR", RemoteFile},
+ client = From});
+
+handle_call({_, recv_chunk}, _, #state{chunk = false} = State) ->
+ {reply, {error, "ftp:recv_chunk_start/2 not called"}, State};
+handle_call({_, recv_chunk}, _From, #state{chunk = true,
+ data = Bin,
+ caller = #recv_chunk_closing{dconn_closed = true,
+ pos_compl_received = true,
+ client_called_us = true
+ }
+ } = State0) ->
+ case Bin of
+ <<>> ->
+ {reply, ok, State0#state{caller = undefined,
+ chunk = false,
+ client = undefined}};
+ Data ->
+ {reply, Data, State0#state{caller = undefined,
+ chunk = false,
+ client = undefined}}
+ end;
+handle_call({_, recv_chunk}, _From, #state{chunk = true,
+ caller = #recv_chunk_closing{dconn_closed = true,
+ pos_compl_received = true
+ }
+ } = State0) ->
+ %% The ftp:recv_chunk call was the last event we waited for, finnish and clean up
+ ?DBG("Data connection closed recv_chunk_closing ftp:recv_chunk, last event",[]),
+ State = activate_ctrl_connection(State0),
+ {reply, ok, State#state{caller = undefined,
+ chunk = false,
+ client = undefined}};
+handle_call({_, recv_chunk}, From, #state{chunk = true,
+ caller = #recv_chunk_closing{pos_compl_received = true
+ } = R
+ } = State0) ->
+ State = activate_data_connection(State0),
+ {noreply, State#state{client = From, caller = R#recv_chunk_closing{client_called_us=true}}};
+
+handle_call({_, recv_chunk}, From, #state{chunk = true,
+ caller = #recv_chunk_closing{} = R
+ } = State) ->
+ %% Waiting for more, don't care what
+ ?DBG("recv_chunk_closing ftp:recv_chunk, get more",[]),
+ {noreply, State#state{client = From, caller = R#recv_chunk_closing{client_called_us=true}}};
+
+handle_call({_, recv_chunk}, From, #state{chunk = true} = State0) ->
+ State = activate_data_connection(State0),
+ {noreply, State#state{client = From, caller = recv_chunk}};
+
+handle_call({_, {send, LocalFile, RemoteFile}}, From,
+ #state{chunk = false, ldir = LocalDir} = State) ->
+ progress_report({local_file, filename:absname(LocalFile, LocalDir)},
+ State),
+ setup_data_connection(State#state{caller = {transfer_file,
+ {"STOR",
+ LocalFile, RemoteFile}},
+ client = From});
+handle_call({_, {append, LocalFile, RemoteFile}}, From,
+ #state{chunk = false} = State) ->
+ setup_data_connection(State#state{caller = {transfer_file,
+ {"APPE",
+ LocalFile, RemoteFile}},
+ client = From});
+handle_call({_, {send_bin, Bin, RemoteFile}}, From,
+ #state{chunk = false} = State) ->
+ setup_data_connection(State#state{caller = {transfer_data,
+ {"STOR", Bin, RemoteFile}},
+ client = From});
+handle_call({_,{append_bin, Bin, RemoteFile}}, From,
+ #state{chunk = false} = State) ->
+ setup_data_connection(State#state{caller = {transfer_data,
+ {"APPE", Bin, RemoteFile}},
+ client = From});
+handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false}
+ = State) ->
+ setup_data_connection(State#state{caller = {start_chunk_transfer,
+ "STOR", RemoteFile},
+ client = From});
+handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false}
+ = State) ->
+ setup_data_connection(State#state{caller = {start_chunk_transfer,
+ "APPE", RemoteFile},
+ client = From});
+handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) ->
+ send_data_message(State, Bin),
+ {reply, ok, State};
+
+handle_call({_, {transfer_chunk, _}}, _, #state{chunk = false} = State) ->
+ {reply, {error, echunk}, State};
+
+handle_call({_, chunk_end}, From, #state{chunk = true} = State0) ->
+ close_data_connection(State0),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{client = From, dsock = undefined,
+ caller = end_chunk_transfer, chunk = false}};
+
+handle_call({_, chunk_end}, _, #state{chunk = false} = State) ->
+ {reply, {error, echunk}, State};
+
+handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd(Cmd, [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{client = From, caller = quote}};
+
+handle_call({_, _Req}, _From, #state{csock = CSock} = State)
+ when (CSock =:= undefined) ->
+ {reply, {error, not_connected}, State};
+
+handle_call(_, _, #state{chunk = true} = State) ->
+ {reply, {error, echunk}, State};
+
+%% Catch all - This can only happen if the application programmer writes
+%% really bad code that violates the API.
+handle_call(Request, _Timeout, State) ->
+ {stop, {'API_violation_connection_closed', Request},
+ {error, {connection_terminated, 'API_violation'}}, State}.
+
+%%--------------------------------------------------------------------------
+%% handle_cast(Request, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handles cast messages.
+%%-------------------------------------------------------------------------
+handle_cast({Pid, close}, #state{owner = Pid} = State) ->
+ _ = send_ctrl_message(State, mk_cmd("QUIT", [])),
+ close_ctrl_connection(State),
+ close_data_connection(State),
+ {stop, normal, State#state{csock = undefined, dsock = undefined}};
+
+handle_cast({Pid, close}, State) ->
+ Report = io_lib:format("A none owner process ~p tried to close an "
+ "ftp connection: ~n", [Pid]),
+ error_logger:info_report(Report),
+ {noreply, State};
+
+%% Catch all - This can only happen if the application programmer writes
+%% really bad code that violates the API.
+handle_cast(Msg, State) ->
+ {stop, {'API_violation_connection_closed', Msg}, State}.
+
+%%--------------------------------------------------------------------------
+%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handles tcp messages from the ftp-server.
+%% Note: The order of the function clauses is significant.
+%%--------------------------------------------------------------------------
+
+handle_info(timeout, #state{caller = open} = State) ->
+ {stop, timeout, State};
+
+handle_info(timeout, State) ->
+ {noreply, State};
+
+%%% Data socket messages %%%
+handle_info({Trpt, Socket, Data},
+ #state{dsock = {Trpt,Socket},
+ caller = {recv_file, Fd}} = State0) when Trpt==tcp;Trpt==ssl ->
+ ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]),
+ ok = file_write(binary_to_list(Data), Fd),
+ progress_report({binary, Data}, State0),
+ State = activate_data_connection(State0),
+ {noreply, State};
+
+handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}, client = From,
+ caller = recv_chunk}
+ = State) when Trpt==tcp;Trpt==ssl ->
+ ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State]),
+ gen_server:reply(From, {ok, Data}),
+ {noreply, State#state{client = undefined, caller = undefined, data = <<>>}};
+
+handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}} = State0) when Trpt==tcp;Trpt==ssl ->
+ ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]),
+ State = activate_data_connection(State0),
+ {noreply, State#state{data = <<(State#state.data)/binary,
+ Data/binary>>}};
+
+handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
+ caller = {recv_file, Fd}} = State0)
+ when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ file_close(Fd),
+ progress_report({transfer_size, 0}, State0),
+ State = activate_ctrl_connection(State0),
+ ?DBG("Data channel close",[]),
+ {noreply, State#state{dsock = undefined, data = <<>>}};
+
+handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
+ client = Client,
+ caller = recv_chunk} = State0)
+ when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ ?DBG("Data channel close recv_chunk",[]),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{dsock = undefined,
+ caller = #recv_chunk_closing{dconn_closed = true,
+ client_called_us = Client =/= undefined}
+ }};
+handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
+ caller = #recv_chunk_closing{client_called_us = true,
+ pos_compl_received = true} = R} = State)
+ when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ %% Maybe handle unprocessed chunk message before acking final chunk
+ self() ! {Cls, Socket},
+ {noreply, State#state{caller = R#recv_chunk_closing{dconn_closed = true}}};
+
+handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin,
+ data = Data} = State0)
+ when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ ?DBG("Data channel close",[]),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{dsock = undefined, data = <<>>,
+ caller = {recv_bin, Data}}};
+
+handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, data = Data,
+ caller = {handle_dir_result, Dir}}
+ = State0) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ ?DBG("Data channel close",[]),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{dsock = undefined,
+ caller = {handle_dir_result, Dir, Data},
+% data = <<?CR,?LF>>}};
+ data = <<>>}};
+
+handle_info({Err, Socket, Reason}, #state{dsock = {Trpt,Socket},
+ client = From} = State)
+ when {Err,Trpt}=={tcp_error,tcp} ; {Err,Trpt}=={ssl_error,ssl} ->
+ gen_server:reply(From, {error, Reason}),
+ close_data_connection(State),
+ {noreply, State#state{dsock = undefined, client = undefined,
+ data = <<>>, caller = undefined, chunk = false}};
+
+%%% Ctrl socket messages %%%
+handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket},
+ verbose = Verbose,
+ caller = Caller,
+ client = From,
+ ctrl_data = {BinCtrlData, AccLines,
+ LineStatus}}
+ = State0) ->
+ ?DBG('--ctrl ~p ----> ~s~p~n',[Socket,<<BinCtrlData/binary, Data/binary>>,State0]),
+ case ftp_response:parse_lines(<<BinCtrlData/binary, Data/binary>>,
+ AccLines, LineStatus) of
+ {ok, Lines, NextMsgData} ->
+ verbose(Lines, Verbose, 'receive'),
+ CtrlResult = ftp_response:interpret(Lines),
+ case Caller of
+ quote ->
+ gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])),
+ {noreply, State0#state{client = undefined,
+ caller = undefined,
+ latest_ctrl_response = Lines,
+ ctrl_data = {NextMsgData, [],
+ start}}};
+ _ ->
+ ?DBG(' ...handle_ctrl_result(~p,...) ctrl_data=~p~n',[CtrlResult,{NextMsgData, [], start}]),
+ handle_ctrl_result(CtrlResult,
+ State0#state{latest_ctrl_response = Lines,
+ ctrl_data =
+ {NextMsgData, [], start}})
+ end;
+ {continue, CtrlData} when CtrlData =/= State0#state.ctrl_data ->
+ ?DBG(' ...Continue... ctrl_data=~p~n',[CtrlData]),
+ State1 = State0#state{ctrl_data = CtrlData},
+ State = activate_ctrl_connection(State1),
+ {noreply, State};
+ {continue, _CtrlData} ->
+ ?DBG(' ...Continue... ctrl_data=~p~n',[_CtrlData]),
+ {noreply, State0}
+ end;
+
+%% If the server closes the control channel it is
+%% the expected behavior that connection process terminates.
+handle_info({Cls, Socket}, #state{csock = {Trpt, Socket}})
+ when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ exit(normal); %% User will get error message from terminate/2
+
+handle_info({Err, Socket, Reason}, _) when Err==tcp_error ; Err==ssl_error ->
+ Report =
+ io_lib:format("~p on socket: ~p for reason: ~p~n",
+ [Err, Socket, Reason]),
+ error_logger:error_report(Report),
+ %% If tcp does not work the only option is to terminate,
+ %% this is the expected behavior under these circumstances.
+ exit(normal); %% User will get error message from terminate/2
+
+%% Monitor messages - if the process owning the ftp connection goes
+%% down there is no point in continuing.
+handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) ->
+ {stop, normal, State#state{client = undefined}};
+
+handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) ->
+ {stop, normal, State#state{client = undefined}};
+
+handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) ->
+ {stop, normal, State#state{client = undefined}};
+
+handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) ->
+ {stop, {stopped, {'EXIT', Process, Reason}},
+ State#state{client = undefined}};
+
+handle_info({'EXIT', Pid, Reason}, #state{progress = Pid} = State) ->
+ Report = io_lib:format("Progress reporting stopped for reason ~p~n",
+ [Reason]),
+ error_logger:info_report(Report),
+ {noreply, State#state{progress = ignore}};
+
+%% Catch all - throws away unknown messages (This could happen by "accident"
+%% so we do not want to crash, but we make a log entry as it is an
+%% unwanted behaviour.)
+handle_info(Info, State) ->
+ Report = io_lib:format("ftp : ~p : Unexpected message: ~p~nState: ~p~n",
+ [self(), Info, State]),
+ error_logger:info_report(Report),
+ {noreply, State}.
+
+%%--------------------------------------------------------------------------
+%% terminate/2 and code_change/3
+%%--------------------------------------------------------------------------
+terminate(normal, State) ->
+ %% If terminate reason =/= normal the progress reporting process will
+ %% be killed by the exit signal.
+ progress_report(stop, State),
+ do_terminate({error, econn}, State);
+terminate(Reason, State) ->
+ Report = io_lib:format("Ftp connection closed due to: ~p~n", [Reason]),
+ error_logger:error_report(Report),
+ do_terminate({error, eclosed}, State).
+
+do_terminate(ErrorMsg, State) ->
+ close_data_connection(State),
+ close_ctrl_connection(State),
+ case State#state.client of
+ undefined ->
+ ok;
+ From ->
+ gen_server:reply(From, ErrorMsg)
+ end,
+ ok.
+
+code_change(_Vsn, State1, upgrade_from_pre_5_12) ->
+ {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
+ Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress} = State1,
+ IpFamily =
+ if
+ (IPv6Disable =:= true) ->
+ inet;
+ true ->
+ inet6fb4
+ end,
+ State2 = #state{csock = CSock,
+ dsock = DSock,
+ verbose = Verbose,
+ ldir = LDir,
+ type = Type,
+ chunk = Chunk,
+ mode = Mode,
+ timeout = Timeout,
+ data = Data,
+ ctrl_data = CtrlData,
+ owner = Owner,
+ client = Client,
+ caller = Caller,
+ ipfamily = IpFamily,
+ progress = Progress},
+ {ok, State2};
+
+code_change(_Vsn, State1, downgrade_to_pre_5_12) ->
+ #state{csock = CSock,
+ dsock = DSock,
+ verbose = Verbose,
+ ldir = LDir,
+ type = Type,
+ chunk = Chunk,
+ mode = Mode,
+ timeout = Timeout,
+ data = Data,
+ ctrl_data = CtrlData,
+ owner = Owner,
+ client = Client,
+ caller = Caller,
+ ipfamily = IpFamily,
+ progress = Progress} = State1,
+ IPv6Disable =
+ if
+ (IpFamily =:= inet) ->
+ true;
+ true ->
+ false
+ end,
+ State2 =
+ {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
+ Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress},
+ {ok, State2};
+
+code_change(_Vsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%%=========================================================================
+%% Start/stop
+%%%=========================================================================
+%%--------------------------------------------------------------------------
+%% start_link([Opts, GenServerOptions]) -> {ok, Pid} | {error, Reason}
+%%
+%% Description: Callback function for the ftp supervisor. It is called
+%% : when open or legacy is called.
+%%--------------------------------------------------------------------------
+start_link([Opts, GenServerOptions]) ->
+ start_link(Opts, GenServerOptions).
+
+start_link(Opts, GenServerOptions) ->
+ case lists:keysearch(client, 1, Opts) of
+ {value, _} ->
+ %% Via the supervisor
+ gen_server:start_link(?MODULE, Opts, GenServerOptions);
+ false ->
+ Opts2 = [{client, self()} | Opts],
+ gen_server:start_link(?MODULE, Opts2, GenServerOptions)
+ end.
+
+
+%%% Stop functionality is handled by close/1
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+
+%%--------------------------------------------------------------------------
+%%% Help functions to handle_call and/or handle_ctrl_result
+%%--------------------------------------------------------------------------
+%% User handling
+-spec handle_user(User, Password, Account, State) -> Result when
+ User :: io:format(),
+ Password :: io:format(),
+ Account :: io:format(),
+ State :: #state{},
+ Result :: {noreply, #state{}}.
+handle_user(User, Password, Acc, State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("USER ~s", [User])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {handle_user, Password, Acc}}}.
+
+handle_user_passwd(Password, Acc, State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("PASS ~s", [Password])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {handle_user_passwd, Acc}}}.
+
+handle_user_account(Acc, State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("ACCT ~s", [Acc])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = handle_user_account}}.
+
+
+%%--------------------------------------------------------------------------
+%% handle_ctrl_result
+%%--------------------------------------------------------------------------
+-type ctrl_status_operation() :: efnamena
+ | elogin
+ | enofile
+ | epath
+ | error
+ | etnospc
+ | epnospc
+ | efnamena
+ | econn
+ | perm_neg_compl
+ | pos_compl
+ | pos_interm
+ | pos_interm_acct
+ | pos_prel
+ | tls_upgrade
+ | trans_neg_compl.
+
+-spec handle_ctrl_result(Operation, State) -> Result when
+ Operation :: {ctrl_status_operation(), atom() | string()},
+ State :: #state{},
+ Result :: {noreply, #state{}, integer()}
+ | {noreply, #state{}}
+ | {stop, normal | {error, Reason}, #state{}}
+ | {error, term()},
+ Reason :: term().
+handle_ctrl_result({pos_compl, _}, #state{csock = {tcp, _Socket},
+ tls_options = TLSOptions,
+ timeout = Timeout,
+ caller = open}
+ = State0) when is_list(TLSOptions) ->
+ _ = send_ctrl_message(State0, mk_cmd("AUTH TLS", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State, Timeout};
+
+handle_ctrl_result({tls_upgrade, S}, #state{csock = {tcp, Socket},
+ tls_options = TLSOptions,
+ timeout = Timeout,
+ caller = open, client = From}
+ = State0) when is_list(TLSOptions) ->
+ ?DBG('<--ctrl ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]),
+ catch ssl:start(),
+ case ssl:connect(Socket, TLSOptions, Timeout) of
+ {ok, TLSSocket} when S == ftps ->
+ State1 = State0#state{csock = {ssl,TLSSocket}},
+ State = activate_ctrl_connection(State1),
+ {noreply, State#state{tls_upgrading_data_connection = pending}, Timeout};
+ {ok, TLSSocket} ->
+ State1 = State0#state{csock = {ssl,TLSSocket}},
+ handle_ctrl_result({pos_compl, S}, State1#state{tls_upgrading_data_connection = pending});
+ {error, _} = Error ->
+ gen_server:reply(From, Error),
+ {stop, normal, State0#state{client = undefined,
+ caller = undefined,
+ tls_upgrading_data_connection = false}}
+ end;
+
+handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = pending} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("PBSZ 0", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{tls_upgrading_data_connection = {true, pbsz}}};
+
+handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, pbsz}} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("PROT P", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{tls_upgrading_data_connection = {true, prot}}};
+
+handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, prot},
+ client = From} = State) ->
+ gen_server:reply(From, {ok, self()}),
+ {noreply, State#state{client = undefined,
+ caller = undefined,
+ tls_upgrading_data_connection = false}};
+handle_ctrl_result({pos_compl, _}, #state{caller = open, client = From}
+ = State) ->
+ gen_server:reply(From, {ok, self()}),
+ {noreply, State#state{client = undefined,
+ caller = undefined }};
+handle_ctrl_result({_, Lines}, #state{caller = open} = State) ->
+ ctrl_result_response(econn, State, {error, Lines});
+
+%%--------------------------------------------------------------------------
+%% Data connection setup active mode
+handle_ctrl_result({pos_compl, _Lines},
+ #state{mode = active,
+ caller = {setup_data_connection,
+ {LSock, Caller}}} = State) ->
+ handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}});
+
+handle_ctrl_result({Status, _Lines},
+ #state{mode = active,
+ caller = {setup_data_connection, {LSock, _}}}
+ = State) ->
+ close_connection({tcp,LSock}),
+ ctrl_result_response(Status, State, {error, Status});
+
+%% Data connection setup passive mode
+handle_ctrl_result({pos_compl, Lines},
+ #state{mode = passive,
+ ipfamily = inet6,
+ client = From,
+ caller = {setup_data_connection, Caller},
+ csock = CSock,
+ sockopts_data_passive = SockOpts,
+ timeout = Timeout}
+ = State) when is_list(Lines) ->
+ [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")),
+ {ok, {IP, _}} = peername(CSock),
+ case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of
+ {ok, _, Socket} ->
+ handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}});
+ {error, _Reason} = Error ->
+ gen_server:reply(From, Error),
+ {noreply, State#state{client = undefined, caller = undefined}}
+ end;
+
+handle_ctrl_result({pos_compl, Lines},
+ #state{mode = passive,
+ ipfamily = inet,
+ client = From,
+ caller = {setup_data_connection, Caller},
+ timeout = Timeout,
+ sockopts_data_passive = SockOpts,
+ ftp_extension = false} = State) when is_list(Lines) ->
+
+ {_, [?LEFT_PAREN | Rest]} =
+ lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines),
+ {NewPortAddr, _} =
+ lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest),
+ [A1, A2, A3, A4, P1, P2] =
+ lists:map(fun(X) -> list_to_integer(X) end,
+ string:tokens(NewPortAddr, [$,])),
+ IP = {A1, A2, A3, A4},
+ Port = (P1 * 256) + P2,
+
+ ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,Port,Caller]),
+ case connect(IP, Port, SockOpts, Timeout, State) of
+ {ok, _, Socket} ->
+ handle_caller(State#state{caller = Caller, dsock = {tcp,Socket}});
+ {error, _Reason} = Error ->
+ gen_server:reply(From, Error),
+ {noreply,State#state{client = undefined, caller = undefined}}
+ end;
+
+handle_ctrl_result({pos_compl, Lines},
+ #state{mode = passive,
+ ipfamily = inet,
+ client = From,
+ caller = {setup_data_connection, Caller},
+ csock = CSock,
+ timeout = Timeout,
+ sockopts_data_passive = SockOpts,
+ ftp_extension = true} = State) when is_list(Lines) ->
+
+ [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")),
+ {ok, {IP, _}} = peername(CSock),
+
+ ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,PortStr,Caller]),
+ case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of
+ {ok, _, Socket} ->
+ handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}});
+ {error, _Reason} = Error ->
+ gen_server:reply(From, Error),
+ {noreply, State#state{client = undefined, caller = undefined}}
+ end;
+
+
+%% FTP server does not support passive mode: try to fallback on active mode
+handle_ctrl_result(_,
+ #state{mode = passive,
+ caller = {setup_data_connection, Caller}} = State) ->
+ setup_data_connection(State#state{mode = active, caller = Caller});
+
+
+%%--------------------------------------------------------------------------
+%% User handling
+handle_ctrl_result({pos_interm, _},
+ #state{caller = {handle_user, PassWord, Acc}} = State) ->
+ handle_user_passwd(PassWord, Acc, State);
+handle_ctrl_result({Status, _},
+ #state{caller = {handle_user, _, _}} = State) ->
+ ctrl_result_response(Status, State, {error, euser});
+
+%% Accounts
+handle_ctrl_result({pos_interm_acct, _},
+ #state{caller = {handle_user_passwd, Acc}} = State)
+ when Acc =/= "" ->
+ handle_user_account(Acc, State);
+handle_ctrl_result({Status, _},
+ #state{caller = {handle_user_passwd, _}} = State) ->
+ ctrl_result_response(Status, State, {error, euser});
+
+%%--------------------------------------------------------------------------
+%% Print current working directory
+handle_ctrl_result({pos_compl, Lines},
+ #state{caller = pwd, client = From} = State) ->
+ Dir = pwd_result(Lines),
+ gen_server:reply(From, {ok, Dir}),
+ {noreply, State#state{client = undefined, caller = undefined}};
+
+%%--------------------------------------------------------------------------
+%% Directory listing
+handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State0) ->
+ case accept_data_connection(State0) of
+ {ok, State1} ->
+ State = activate_data_connection(State1),
+ {noreply, State#state{caller = {handle_dir_result, Dir}}};
+ {error, _Reason} = Error ->
+ ctrl_result_response(error, State0, Error)
+ end;
+
+handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, ""=_CurrentDir,
+ Data}, client = From}= State) ->
+ gen_server:reply(From, {ok, Data}),
+ {noreply, State#state{client = undefined,
+ caller = undefined}};
+
+handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, _Dir,
+ Data}, client = From}= State) ->
+ gen_server:reply(From, {ok, Data}),
+ {noreply, State#state{client = undefined,
+ caller = undefined}};
+
+handle_ctrl_result({pos_compl, _}=Operation, #state{caller = {handle_dir_result, Dir},
+ data = Data}= State) ->
+ handle_ctrl_result(Operation, State#state{caller = {handle_dir_result, Dir, Data}});
+
+handle_ctrl_result(S={_Status, _},
+ #state{caller = {handle_dir_result, _, _}} = State) ->
+ %% OTP-5731, macosx
+ ctrl_result_response(S, State, {error, epath});
+
+handle_ctrl_result({Status, _}, #state{caller = cd} = State) ->
+ ctrl_result_response(Status, State, {error, Status});
+
+handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) ->
+ ctrl_result_response(Status, State, {error, epath});
+
+%%--------------------------------------------------------------------------
+%% File renaming
+handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}}
+ = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("RNTO ~s", [NewFile])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = rename_second_phase}};
+
+handle_ctrl_result({Status, _},
+ #state{caller = {rename, _}} = State) ->
+ ctrl_result_response(Status, State, {error, Status});
+
+handle_ctrl_result({Status, _},
+ #state{caller = rename_second_phase} = State) ->
+ ctrl_result_response(Status, State, {error, Status});
+
+%%--------------------------------------------------------------------------
+%% File handling - recv_bin
+handle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State0) ->
+ case accept_data_connection(State0) of
+ {ok, State1} ->
+ State = activate_data_connection(State1),
+ {noreply, State};
+ {error, _Reason} = Error ->
+ ctrl_result_response(error, State0, Error)
+ end;
+
+handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data},
+ client = From} = State) ->
+ gen_server:reply(From, {ok, Data}),
+ close_data_connection(State),
+ {noreply, State#state{client = undefined, caller = undefined}};
+
+handle_ctrl_result({Status, _}, #state{caller = recv_bin} = State) ->
+ close_data_connection(State),
+ ctrl_result_response(Status, State#state{dsock = undefined},
+ {error, epath});
+
+handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) ->
+ close_data_connection(State),
+ ctrl_result_response(Status, State#state{dsock = undefined},
+ {error, epath});
+%%--------------------------------------------------------------------------
+%% File handling - start_chunk_transfer
+handle_ctrl_result({pos_prel, _}, #state{caller = start_chunk_transfer}
+ = State0) ->
+ case accept_data_connection(State0) of
+ {ok, State1} ->
+ State = start_chunk(State1),
+ {noreply, State};
+ {error, _Reason} = Error ->
+ ctrl_result_response(error, State0, Error)
+ end;
+
+%%--------------------------------------------------------------------------
+%% File handling - chunk_transfer complete
+
+handle_ctrl_result({pos_compl, _}, #state{client = From,
+ caller = #recv_chunk_closing{dconn_closed = true,
+ client_called_us = true,
+ pos_compl_received = false
+ }}
+ = State0) when From =/= undefined ->
+ %% The pos_compl was the last event we waited for, finnish and clean up
+ ?DBG("recv_chunk_closing pos_compl, last event",[]),
+ gen_server:reply(From, ok),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = undefined,
+ chunk = false,
+ client = undefined}};
+
+handle_ctrl_result({pos_compl, _}, #state{caller = #recv_chunk_closing{}=R}
+ = State0) ->
+ %% Waiting for more, don't care what
+ ?DBG("recv_chunk_closing pos_compl, wait more",[]),
+ {noreply, State0#state{caller = R#recv_chunk_closing{pos_compl_received=true}}};
+
+handle_ctrl_result({pos_compl, _}, #state{caller = undefined, chunk = true}
+ = State0) ->
+ %% Waiting for user to call recv_chunk
+ {noreply, State0#state{caller = #recv_chunk_closing{pos_compl_received=true}}};
+
+%%--------------------------------------------------------------------------
+%% File handling - recv_file
+handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State0) ->
+ case accept_data_connection(State0) of
+ {ok, State1} ->
+ State = activate_data_connection(State1),
+ {noreply, State};
+ {error, _Reason} = Error ->
+ ctrl_result_response(error, State0, Error)
+ end;
+
+handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) ->
+ file_close(Fd),
+ close_data_connection(State),
+ ctrl_result_response(Status, State#state{dsock = undefined},
+ {error, epath});
+%%--------------------------------------------------------------------------
+%% File handling - transfer_*
+handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}}
+ = State0) ->
+ case accept_data_connection(State0) of
+ {ok, State1} ->
+ send_file(State1, Fd);
+ {error, _Reason} = Error ->
+ ctrl_result_response(error, State0, Error)
+ end;
+
+handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}}
+ = State0) ->
+ case accept_data_connection(State0) of
+ {ok, State} ->
+ send_bin(State, Bin);
+ {error, _Reason} = Error ->
+ ctrl_result_response(error, State0, Error)
+ end;
+
+%%--------------------------------------------------------------------------
+%% Default
+handle_ctrl_result({Status, _Lines}, #state{client = From} = State)
+ when From =/= undefined ->
+ ctrl_result_response(Status, State, {error, Status});
+handle_ctrl_result(CtrlMsg, #state{caller = undefined} = State) ->
+ logger:log(info, #{protocol => ftp, unexpected_msg => CtrlMsg}),
+ {noreply, State}.
+
+%%--------------------------------------------------------------------------
+%% Help functions to handle_ctrl_result
+%%--------------------------------------------------------------------------
+
+-spec ctrl_result_response(Status, State, Error) -> Result when
+ Status :: ctrl_status_operation() | {ctrl_status_operation(), _},
+ State :: #state{},
+ Error :: {error, string() | Status | atom() | Reason},
+ Reason :: term(),
+ Result :: {noreply, #state{}}
+ | {stop, normal | {error, Reason}, #state{}}
+ | {error, term()}.
+
+ctrl_result_response(pos_compl, #state{client = From} = State, _) ->
+ gen_server:reply(From, ok),
+ {noreply, State#state{client = undefined, caller = undefined}};
+
+ctrl_result_response(enofile, #state{client = From} = State, _) ->
+ gen_server:reply(From, {error, enofile}),
+ {noreply, State#state{client = undefined, caller = undefined}};
+
+ctrl_result_response(error, State0, {error, _Reason} = Error) ->
+ case State0#state.client of
+ undefined ->
+ {stop, Error, State0};
+ From ->
+ gen_server:reply(From, Error),
+ State = activate_ctrl_connection(State0),
+ {noreply, State}
+ end;
+
+ctrl_result_response(Status, #state{client = From} = State, _)
+ when (Status =:= etnospc) orelse
+ (Status =:= epnospc) orelse
+ (Status =:= efnamena) orelse
+ (Status =:= econn) ->
+ gen_server:reply(From, {error, Status}),
+ {stop, normal, State#state{client = undefined}};
+
+ctrl_result_response(_, #state{client = From} = State, ErrorMsg) ->
+ gen_server:reply(From, ErrorMsg),
+ {noreply, State#state{client = undefined, caller = undefined}}.
+
+%%--------------------------------------------------------------------------
+-spec handle_caller(State) -> Result when
+ State :: #state{},
+ Result :: {noreply, #state{}}.
+handle_caller(#state{caller = {dir, Dir, Len}} = State0) ->
+ Cmd = case Len of
+ short -> "NLST";
+ long -> "LIST"
+ end,
+ _ = case Dir of
+ "" ->
+ send_ctrl_message(State0, mk_cmd(Cmd, ""));
+ _ ->
+ send_ctrl_message(State0, mk_cmd(Cmd ++ " ~s", [Dir]))
+ end,
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {dir, Dir}}};
+
+handle_caller(#state{caller = {recv_bin, RemoteFile}} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = recv_bin}};
+
+handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} =
+ State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = start_chunk_transfer}};
+
+handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {recv_file, Fd}}};
+
+handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}},
+ ldir = LocalDir, client = From} = State0)
+ when (is_binary(LocalFile) orelse is_list(LocalFile) orelse is_atom(LocalFile)) ->
+ case file_open(filename:absname(LocalFile, LocalDir), read) of
+ {ok, Fd} ->
+ _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {transfer_file, Fd}}};
+ {error, _} ->
+ gen_server:reply(From, {error, epath}),
+ {noreply, State0#state{client = undefined, caller = undefined,
+ dsock = undefined}}
+ end;
+
+handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} =
+ State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {transfer_data, Bin}}}.
+
+%% ----------- FTP SERVER COMMUNICATION -------------------------
+
+%% Connect to FTP server at Host (default is TCP port 21)
+%% in order to establish a control connection.
+-spec setup_ctrl_connection(Host, Port, Timeout, State) -> Result when
+ Host :: inet:ip_address() | inet:hostname(),
+ Port :: inet:port_number(),
+ Timeout :: non_neg_integer(),
+ State :: #state{},
+ Reason :: timeout | inet:posix(),
+ Result :: {ok, State, integer()} | {error, Reason}.
+setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = State0) ->
+ MsTime = erlang:monotonic_time(),
+ case connect(Host, Port, SockOpts, Timeout, State0) of
+ {ok, IpFam, CSock} ->
+ State1 = State0#state{csock = {tcp, CSock}, ipfamily = IpFam},
+ State = activate_ctrl_connection(State1),
+ case Timeout - millisec_passed(MsTime) of
+ Timeout2 when (Timeout2 >= 0) ->
+ {ok, State#state{caller = open}, Timeout2};
+ _ ->
+ %% Oups: Simulate timeout
+ {ok, State#state{caller = open}, 0}
+ end;
+ Error ->
+ Error
+ end.
+
+-spec setup_data_connection(State) -> Result when
+ State :: #state{},
+ Result :: {noreply, State}.
+setup_data_connection(#state{mode = active,
+ caller = Caller,
+ csock = CSock,
+ sockopts_data_active = SockOpts,
+ ftp_extension = FtpExt} = State0) ->
+ case (catch sockname(CSock)) of
+ {ok, {{_, _, _, _, _, _, _, _} = IP0, _}} ->
+ IP = proplists:get_value(ip, SockOpts, IP0),
+ {ok, LSock} =
+ gen_tcp:listen(0, [{ip, IP}, {active, false},
+ inet6, binary, {packet, 0} |
+ lists:keydelete(ip,1,SockOpts)]),
+ {ok, {_, Port}} = sockname({tcp,LSock}),
+ IpAddress = inet_parse:ntoa(IP),
+ Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]),
+ _ = send_ctrl_message(State0, Cmd),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {setup_data_connection,
+ {LSock, Caller}}}};
+ {ok, {{_,_,_,_} = IP0, _}} ->
+ IP = proplists:get_value(ip, SockOpts, IP0),
+ {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false},
+ binary, {packet, 0} |
+ lists:keydelete(ip,1,SockOpts)]),
+ {ok, Port} = inet:port(LSock),
+ _ = case FtpExt of
+ false ->
+ {IP1, IP2, IP3, IP4} = IP,
+ {Port1, Port2} = {Port div 256, Port rem 256},
+ send_ctrl_message(State0,
+ mk_cmd("PORT ~w,~w,~w,~w,~w,~w",
+ [IP1, IP2, IP3, IP4, Port1, Port2]));
+ true ->
+ IpAddress = inet_parse:ntoa(IP),
+ Cmd = mk_cmd("EPRT |1|~s|~p|", [IpAddress, Port]),
+ send_ctrl_message(State0, Cmd)
+ end,
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {setup_data_connection,
+ {LSock, Caller}}}}
+ end;
+
+setup_data_connection(#state{mode = passive, ipfamily = inet6,
+ caller = Caller} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("EPSV", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {setup_data_connection, Caller}}};
+
+setup_data_connection(#state{mode = passive, ipfamily = inet,
+ caller = Caller,
+ ftp_extension = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("PASV", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {setup_data_connection, Caller}}};
+
+setup_data_connection(#state{mode = passive, ipfamily = inet,
+ caller = Caller,
+ ftp_extension = true} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("EPSV", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {setup_data_connection, Caller}}}.
+
+-spec connect(Host, Port, SockOpts, Timeout, State) -> Result when
+ Host :: inet:ip_address() | inet:hostname(),
+ Port :: inet:port_number(),
+ SockOpts :: [inet:inet_backend() | gen_tcp:connect_option()],
+ Timeout :: timeout(),
+ State :: #state{},
+ Reason :: timeout | inet:posix(),
+ Result :: {ok, inet:address_family(), gen_tcp:socket()} | {error, Reason}.
+connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet = IpFam}) ->
+ connect2(Host, Port, IpFam, SockOpts, Timeout);
+
+connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6 = IpFam}) ->
+ connect2(Host, Port, IpFam, SockOpts, Timeout);
+
+connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6fb4}) ->
+ case inet:getaddr(Host, inet6) of
+ {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} ->
+ case inet:getaddr(Host, inet) of
+ {ok, IPv4} ->
+ IpFam = inet,
+ connect2(IPv4, Port, IpFam, SockOpts, Timeout);
+
+ _ ->
+ IpFam = inet6,
+ connect2(IPv6, Port, IpFam, SockOpts, Timeout)
+ end;
+
+ {ok, IPv6} ->
+ IpFam = inet6,
+ connect2(IPv6, Port, IpFam, SockOpts, Timeout);
+
+ _ ->
+ case inet:getaddr(Host, inet) of
+ {ok, IPv4} ->
+ IpFam = inet,
+ connect2(IPv4, Port, IpFam, SockOpts, Timeout);
+ Error ->
+ Error
+ end
+ end.
+
+-spec connect2(Host, Port, IpFam, SockOpts, Timeout) -> Result when
+ Host :: inet:socket_address() | inet:hostname(),
+ Port :: inet:port_number(),
+ SockOpts :: [inet:inet_backend() | gen_tcp:connect_option()],
+ Timeout :: timeout(),
+ IpFam :: inet:address_family(),
+ Reason :: timeout | inet:posix(),
+ Result :: {ok, inet:address_family(), gen_tcp:socket()} | {error, Reason}.
+connect2(Host, Port, IpFam, SockOpts, Timeout) ->
+ Opts = [IpFam, binary, {packet, 0}, {active, false} | SockOpts],
+ case gen_tcp:connect(Host, Port, Opts, Timeout) of
+ {ok, Sock} ->
+ {ok, IpFam, Sock};
+ Error ->
+ Error
+ end.
+
+-spec accept_data_connection_tls_options(State) -> Result when
+ State :: #state{},
+ Result :: [tuple()].
+accept_data_connection_tls_options(#state{ csock = {ssl,Socket}, tls_options = TO0, tls_ctrl_session_reuse = true }) ->
+ TO = lists:keydelete(reuse_sessions, 1, TO0),
+ {ok, [{session_id,SSLSessionId},{session_data,SSLSessionData}]} = ssl:connection_information(Socket, [session_id, session_data]),
+ lists:keystore(reuse_session, 1, TO, {reuse_session,{SSLSessionId,SSLSessionData}});
+accept_data_connection_tls_options(#state{ tls_options = TO }) ->
+ TO.
+
+-spec accept_data_connection(State) -> Result when
+ State :: #state{},
+ Result :: {ok, #state{}} | {error, Reason},
+ Reason :: term().
+accept_data_connection(#state{mode = active,
+ dtimeout = DTimeout,
+ tls_options = TLSOptions0,
+ dsock = {lsock, LSock}} = State0) ->
+ case gen_tcp:accept(LSock, DTimeout) of
+ {ok, Socket} when is_list(TLSOptions0) ->
+ gen_tcp:close(LSock),
+ TLSOptions = accept_data_connection_tls_options(State0),
+ ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]),
+ case ssl:connect(Socket, TLSOptions, DTimeout) of
+ {ok, TLSSocket} ->
+ {ok, State0#state{dsock={ssl,TLSSocket}}};
+ {error, Reason} ->
+ {error, {ssl_connect_failed, Reason}}
+ end;
+ {ok, Socket} ->
+ gen_tcp:close(LSock),
+ {ok, State0#state{dsock={tcp,Socket}}};
+ {error, Reason} ->
+ {error, {data_connect_failed, Reason}}
+ end;
+
+accept_data_connection(#state{mode = passive,
+ dtimeout = DTimeout,
+ dsock = {tcp,Socket},
+ tls_options = TLSOptions0} = State) when is_list(TLSOptions0) ->
+ TLSOptions = accept_data_connection_tls_options(State),
+ ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State]),
+ case ssl:connect(Socket, TLSOptions, DTimeout) of
+ {ok, TLSSocket} ->
+ {ok, State#state{dsock={ssl,TLSSocket}}};
+ {error, Reason} ->
+ {error, {ssl_connect_failed, Reason}}
+ end;
+accept_data_connection(#state{mode = passive} = State) ->
+ {ok,State}.
+
+-spec send_ctrl_message(State, Message) -> _ when
+ State :: #state{},
+ Message :: [term()].
+send_ctrl_message(_S=#state{csock = Socket, verbose = Verbose}, Message) ->
+ verbose(lists:flatten(Message),Verbose,send),
+ ?DBG('<--ctrl ~p ---- ~s~p~n',[Socket,Message,_S]),
+ _ = send_message(Socket, Message).
+
+send_data_message(_S=#state{dsock = Socket}, Message) ->
+ ?DBG('<==data ~p ==== ~s~n~p~n',[Socket,Message,_S]),
+ case send_message(Socket, Message) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Report = io_lib:format("send/2 for socket ~p failed with "
+ "reason ~p~n", [Socket, Reason]),
+ error_logger:error_report(Report),
+ %% If tcp/ssl does not work the only option is to terminate,
+ %% this is the expected behavior under these circumstances.
+ exit(normal) %% User will get error message from terminate/2
+ end.
+
+send_message({tcp, Socket}, Message) ->
+ gen_tcp:send(Socket, Message);
+send_message({ssl, Socket}, Message) ->
+ ssl:send(Socket, Message).
+
+activate_ctrl_connection(#state{csock = CSock, ctrl_data = {<<>>, _, _}} = State) ->
+ _ = activate_connection(CSock),
+ State;
+activate_ctrl_connection(#state{csock = CSock} = State0) ->
+ _ = activate_connection(CSock),
+ %% We have already received at least part of the next control message,
+ %% that has been saved in ctrl_data, process this first.
+ {noreply, State} = handle_info({socket_type(CSock), unwrap_socket(CSock), <<>>}, State0),
+ State.
+
+activate_data_connection(#state{dsock = DSock} = State) ->
+ _ = activate_connection(DSock),
+ State.
+
+activate_connection(Socket) ->
+ case socket_type(Socket) of
+ tcp ->
+ _ = activate_connection(inet, tcp_closed, Socket);
+ ssl ->
+ _ = activate_connection(ssl, ssl_closed, Socket)
+ end.
+
+activate_connection(API, CloseTag, Socket0) ->
+ Socket = unwrap_socket(Socket0),
+ case API:setopts(Socket, [{active, once}]) of
+ ok ->
+ ok;
+ {error, _} -> %% inet can return einval instead of closed
+ self() ! {CloseTag, Socket}
+ end.
+
+ignore_return_value(_) -> ok.
+
+unwrap_socket({tcp,Socket}) -> Socket;
+unwrap_socket({ssl,Socket}) -> Socket.
+
+socket_type({tcp,_Socket}) -> tcp;
+socket_type({ssl,_Socket}) -> ssl.
+
+close_ctrl_connection(#state{csock = undefined}) -> ok;
+close_ctrl_connection(#state{csock = Socket}) -> close_connection(Socket).
+
+close_data_connection(#state{dsock = undefined}) -> ok;
+close_data_connection(#state{dsock = Socket}) -> close_connection(Socket).
+
+close_connection({lsock,Socket}) -> ignore_return_value( gen_tcp:close(Socket) );
+close_connection({tcp, Socket}) -> ignore_return_value( gen_tcp:close(Socket) );
+close_connection({ssl, Socket}) -> ignore_return_value( ssl:close(Socket) ).
+
+%% ------------ FILE HANDLING ----------------------------------------
+send_file(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Fd) ->
+ {noreply, State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_file, Fd}}};
+send_file(#state{client = Client}=State0, Fd) ->
+ case file_read(Fd) of
+ {ok, N, Bin} when N > 0 ->
+ send_data_message(State0, Bin),
+ progress_report({binary, Bin}, State0),
+ send_file(State0, Fd);
+ {ok, _, _} ->
+ file_close(Fd),
+ close_data_connection(State0),
+ progress_report({transfer_size, 0}, State0),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = transfer_file_second_phase,
+ dsock = undefined}};
+ {error, Reason} ->
+ gen_server:reply(Client, {error, Reason}),
+ {stop, normal, State0#state{client = undefined}}
+ end.
+
+file_open(File, Option) ->
+ file:open(File, [raw, binary, Option]).
+
+file_close(Fd) ->
+ ignore_return_value( file:close(Fd) ).
+
+file_read(Fd) ->
+ case file:read(Fd, ?FILE_BUFSIZE) of
+ {ok, Bytes} when is_binary(Bytes) ->
+ {ok, byte_size(Bytes), Bytes};
+ eof ->
+ {ok, 0, []};
+ Other ->
+ Other
+ end.
+
+file_write(Bytes, Fd) ->
+ file:write(Fd, Bytes).
+
+%% -------------- MISC ----------------------------------------------
+
+call(GenServer, Msg, Format) ->
+ call(GenServer, Msg, Format, infinity).
+call(GenServer, Msg, Format, Timeout) ->
+ Req = {self(), Msg},
+ case (catch gen_server:call(GenServer, Req, Timeout)) of
+ {ok, Bin} when is_binary(Bin) andalso (Format =:= string) ->
+ {ok, binary_to_list(Bin)};
+ {'EXIT', _, _} ->
+ {error, eclosed};
+ {'EXIT', _} ->
+ {error, eclosed};
+ Result ->
+ Result
+ end.
+
+cast(GenServer, Msg) ->
+ gen_server:cast(GenServer, {self(), Msg}).
+
+send_bin(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Bin) ->
+ State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_bin, Bin}};
+send_bin(State0, Bin) ->
+ send_data_message(State0, Bin),
+ close_data_connection(State0),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = transfer_data_second_phase,
+ dsock = undefined}}.
+
+mk_cmd(Fmt, Args) ->
+ [io_lib:format(Fmt, Args)| [?CR, ?LF]]. % Deep list ok.
+
+is_name_sane([]) ->
+ true;
+is_name_sane([?CR| _]) ->
+ false;
+is_name_sane([?LF| _]) ->
+ false;
+is_name_sane([_| Rest]) ->
+ is_name_sane(Rest).
+
+pwd_result(Lines) ->
+ {_, [?DOUBLE_QUOTE | Rest]} =
+ lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines),
+ {Dir, _} =
+ lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest),
+ Dir.
+
+
+key_search(Key, List, Default) ->
+ case lists:keysearch(Key, 1, List) of
+ {value, {_,Val}} ->
+ Val;
+ false ->
+ Default
+ end.
+
+verbose(Lines, true, Direction) ->
+ DirStr =
+ case Direction of
+ send ->
+ "Sending: ";
+ _ ->
+ "Receiving: "
+ end,
+ Str = string:strip(string:strip(Lines, right, ?LF), right, ?CR),
+ erlang:display(DirStr++Str);
+verbose(_, false,_) ->
+ ok.
+
+progress(Options) ->
+ ftp_progress:start_link(Options).
+
+progress_report(_, #state{progress = ignore}) ->
+ ok;
+progress_report(stop, #state{progress = ProgressPid}) when is_pid(ProgressPid) ->
+ ftp_progress:stop(ProgressPid);
+progress_report({binary, Data}, #state{progress = ProgressPid}) when is_binary(Data), is_pid(ProgressPid) ->
+ ftp_progress:report(ProgressPid, {transfer_size, byte_size(Data)});
+progress_report(Report, #state{progress = ProgressPid}) when is_pid(ProgressPid) ->
+ ftp_progress:report(ProgressPid, Report).
+
+
+peername({tcp, Socket}) -> inet:peername(Socket);
+peername({ssl, Socket}) -> ssl:peername(Socket).
+
+sockname({tcp, Socket}) -> inet:sockname(Socket);
+sockname({ssl, Socket}) -> ssl:sockname(Socket).
+
+start_chunk(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State) ->
+ State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, start_chunk, undefined}};
+start_chunk(#state{client = From} = State) ->
+ gen_server:reply(From, ok),
+ State#state{chunk = true,
+ client = undefined,
+ caller = undefined}.
+
+
+%% This function extracts the start options from the
+%% Valid options:
+%% debug,
+%% verbose
+%% ipfamily
+%% priority
+%% flags (for backward compatibillity)
+start_options(Options) ->
+ case lists:keysearch(flags, 1, Options) of
+ {value, {flags, Flags}} ->
+ Verbose = lists:member(verbose, Flags),
+ IsTrace = lists:member(trace, Flags),
+ IsDebug = lists:member(debug, Flags),
+ DebugLevel =
+ if
+ (IsTrace =:= true) ->
+ trace;
+ IsDebug =:= true ->
+ debug;
+ true ->
+ disable
+ end,
+ {ok, [{verbose, Verbose},
+ {debug, DebugLevel},
+ {priority, low}]};
+ false ->
+ ValidateVerbose =
+ fun(true) -> true;
+ (false) -> true;
+ (_) -> false
+ end,
+ ValidateDebug =
+ fun(trace) -> true;
+ (debug) -> true;
+ (disable) -> true;
+ (_) -> false
+ end,
+ ValidatePriority =
+ fun(low) -> true;
+ (normal) -> true;
+ (high) -> true;
+ (_) -> false
+ end,
+ ValidOptions =
+ [{verbose, ValidateVerbose, false, false},
+ {debug, ValidateDebug, false, disable},
+ {priority, ValidatePriority, false, low}],
+ validate_options(Options, ValidOptions, [])
+ end.
+
+
+%% This function extracts and validates the open options from the
+%% Valid options:
+%% mode
+%% host
+%% port
+%% timeout
+%% dtimeout
+%% progress
+%% ftp_extension
+
+-spec open_options([tuple()]) -> {ok, [tuple()]} | no_return().
+open_options(Options) ->
+ ValidateMode =
+ fun(active) -> true;
+ (passive) -> true;
+ (_) -> false
+ end,
+ ValidateHost =
+ fun(Host) when is_list(Host) ->
+ true;
+ (Host) when tuple_size(Host) =:= 4; tuple_size(Host) =:= 8 ->
+ true;
+ (_) ->
+ false
+ end,
+ ValidatePort =
+ fun(Port) when is_integer(Port) andalso (Port >= 0) -> true;
+ (_) -> false
+ end,
+ ValidateIpFamily =
+ fun(inet) -> true;
+ (inet6) -> true;
+ (inet6fb4) -> true;
+ (_) -> false
+ end,
+ ValidateTLS =
+ fun(TLS) when is_list(TLS) -> true;
+ (undefined) -> true;
+ (_) -> false
+ end,
+ ValidateTLSSecMethod =
+ fun(ftpes) -> true;
+ (ftps) -> true;
+ (_) -> false
+ end,
+ ValidateTLSCtrlSessionReuse =
+ fun(Reuse) when is_boolean(Reuse) -> true;
+ (_) -> false
+ end,
+ ValidateTimeout =
+ fun(Timeout) when is_integer(Timeout) andalso (Timeout >= 0) -> true;
+ (_) -> false
+ end,
+ ValidateDTimeout =
+ fun(DTimeout) when is_integer(DTimeout) andalso (DTimeout >= 0) -> true;
+ (infinity) -> true;
+ (_) -> false
+ end,
+ ValidateProgress =
+ fun(ignore) ->
+ true;
+ ({Mod, Func, _InitProgress}) when is_atom(Mod) andalso
+ is_atom(Func) ->
+ true;
+ (_) ->
+ false
+ end,
+ ValidateFtpExtension =
+ fun(true) -> true;
+ (false) -> true;
+ (_) -> false
+ end,
+ ValidOptions =
+ [{mode, ValidateMode, false, ?DEFAULT_MODE},
+ {host, ValidateHost, true, ehost},
+ {port, ValidatePort, false, 0},
+ {ipfamily, ValidateIpFamily, false, inet},
+ {tls, ValidateTLS, false, undefined},
+ {tls_sec_method, ValidateTLSSecMethod, false, ftpes},
+ {tls_ctrl_session_reuse, ValidateTLSCtrlSessionReuse, false, false},
+ {timeout, ValidateTimeout, false, ?CONNECTION_TIMEOUT},
+ {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT},
+ {progress, ValidateProgress, false, ?PROGRESS_DEFAULT},
+ {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}],
+ validate_options(Options, ValidOptions, []).
+
+%% validates socket options and set defaults
+-spec socket_options(Options :: [{atom(), [term()]}]) -> {ok, tuple()} | no_return().
+socket_options(Options) ->
+ CtrlOpts = proplists:get_value(sock_ctrl, Options, []),
+ DataActOpts = proplists:get_value(sock_data_act, Options, CtrlOpts),
+ DataPassOpts = proplists:get_value(sock_data_pass, Options, CtrlOpts),
+ case [O || O <- lists:usort(CtrlOpts++DataPassOpts++DataActOpts),
+ not valid_socket_option(O)] of
+ [] ->
+ {ok, {CtrlOpts, DataPassOpts, DataActOpts}};
+ Invalid ->
+ throw({error,{sock_opts,Invalid}})
+ end.
+
+
+valid_socket_option(inet ) -> false;
+valid_socket_option(inet6 ) -> false;
+valid_socket_option({ipv6_v6only, _}) -> false;
+valid_socket_option({active,_} ) -> false;
+valid_socket_option({packet,_} ) -> false;
+valid_socket_option({mode,_} ) -> false;
+valid_socket_option(binary ) -> false;
+valid_socket_option(list ) -> false;
+valid_socket_option({header,_} ) -> false;
+valid_socket_option({packet_size,_} ) -> false;
+valid_socket_option(_) -> true.
+
+
+-spec validate_options(Options, ValidOptions, Acc) -> Result when
+ Options :: [tuple()],
+ ValidOptions :: [tuple()],
+ Acc :: [tuple()],
+ Result :: {ok, [tuple()]} | no_return().
+validate_options([], [], Acc) ->
+ {ok, lists:reverse(Acc)};
+validate_options([], ValidOptions, Acc) ->
+ %% Check if any mandatory options are missing!
+ case [{Key, Reason} || {Key, _, true, Reason} <- ValidOptions] of
+ [] ->
+ Defaults =
+ [{Key, Default} || {Key, _, _, Default} <- ValidOptions],
+ {ok, lists:reverse(Defaults ++ Acc)};
+ [{_, Reason}|_Missing] ->
+ throw({error, Reason})
+ end;
+validate_options([{Key, Value}|Options], ValidOptions, Acc) ->
+ case lists:keysearch(Key, 1, ValidOptions) of
+ {value, {Key, Validate, _, Default}} ->
+ case (catch Validate(Value)) of
+ true ->
+ NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
+ validate_options(Options, NewValidOptions,
+ [{Key, Value} | Acc]);
+ _ ->
+ NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
+ validate_options(Options, NewValidOptions,
+ [{Key, Default} | Acc])
+ end;
+ false ->
+ validate_options(Options, ValidOptions, Acc)
+ end;
+validate_options([_|Options], ValidOptions, Acc) ->
+ validate_options(Options, ValidOptions, Acc).
+
+%% Help function, elapsed milliseconds since T0
+millisec_passed(T0) ->
+ %% OTP 18
+ erlang:convert_time_unit(erlang:monotonic_time() - T0,
+ native,
+ micro_seconds) div 1000.
diff --git a/lib/ftp/src/ftp_sup.erl b/lib/ftp/src/ftp_sup.erl
index f30046802f..287fbec170 100644
--- a/lib/ftp/src/ftp_sup.erl
+++ b/lib/ftp/src/ftp_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -61,7 +61,7 @@ init(_) ->
%%====================================================================
child_specs() ->
[#{id => undefined,
- start => {ftp, start_link, []},
+ start => {ftp_internal, start_link, []},
restart => temporary,
shutdown => 4000,
type => worker,
diff --git a/lib/ftp/test/ftp_SUITE.erl b/lib/ftp/test/ftp_SUITE.erl
index 961b3b5fe1..c1284f6ff6 100644
--- a/lib/ftp/test/ftp_SUITE.erl
+++ b/lib/ftp/test/ftp_SUITE.erl
@@ -1069,7 +1069,7 @@ error_ehost(_Config) ->
%%%----------------------------------------------------------------
error_datafail() ->
[{doc, "Test that failure to open data channel captures "
- "error emitted on ctrl chanenel"}].
+ "error emitted on ctrl channel"}].
error_datafail(Config) ->
Self = self(),
@@ -1078,13 +1078,13 @@ error_datafail(Config) ->
% and erlang:group_leader/2 does not work under ct
dbg:start(),
dbg:tracer(process, {fun
- ({trace,P,call,{ftp,verbose,[M,_,'receive']}}, ok) when P == Pid -> Self ! M, ok;
+ ({trace,P,call,{ftp_internal,verbose,[M,_,'receive']}}, ok) when P == Pid -> Self ! M, ok;
(_, ok) -> ok
end, ok}),
- dbg:tpl(ftp, verbose, []),
+ dbg:tpl(ftp_internal, verbose, []),
dbg:p(Pid, [call]),
{error,_} = ftp:ls(Pid),
- dbg:stop_clear(),
+ dbg:stop(),
Recv = fun(Recv) ->
receive
Msg when is_list(Msg) ->
diff --git a/lib/inets/doc/src/http_uri.xml b/lib/inets/doc/src/http_uri.xml
index 39b367eb92..41f6271a0d 100644
--- a/lib/inets/doc/src/http_uri.xml
+++ b/lib/inets/doc/src/http_uri.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2012</year><year>2022</year>
+ <year>2012</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -42,53 +42,46 @@
<section>
<title>DATA TYPES</title>
- <p>Type definitions that are used more than once in
- this module:</p>
- <p><c>boolean() = true | false</c></p>
- <p><c>string()</c> = list of ASCII characters</p>
-
- </section>
-
- <section>
- <title>URI DATA TYPES</title>
<p>Type definitions that are related to URI:</p>
-<taglist>
- <tag><c>uri() = string() | binary()</c></tag>
- <item><p>Syntax according to the URI definition in RFC 3986,
- for example, "http://www.erlang.org/"</p></item>
- </taglist>
-
- <p>For more information about URI, see
- <url href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>.</p>
+ <taglist>
+ <tag><c>uri_part() = [byte()] | binary()</c></tag>
+ <item><p>Syntax according to the URI definition in RFC 3986,
+ for example, "http://www.erlang.org/"</p></item>
+ </taglist>
+
+ <p>For more information about URI, see
+ <url href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>.</p>
</section>
<funcs>
<func>
- <name since="OTP R15B01">decode(HexEncodedURI) -> URI</name>
+ <name since="OTP R15B01">decode(EncodedPart) -> DecodePart</name>
- <fsummary>Decodes a hexadecimal encoded URI.</fsummary>
- <type>
- <v>HexEncodedURI = string() | binary() - A possibly hexadecimal encoded URI</v>
- <v>URI = uri()</v>
+ <fsummary>Decodes a percent encoded URI part.</fsummary>
+ <type>
+ <v>EncodedPart = uri_part() - A possibly percent encoded URI part</v>
+ <v>DecodePart = uri_part()</v>
</type>
<desc>
- <p>Decodes a possibly hexadecimal encoded URI.</p>
+ <warning><p>Do not use will be removed use <seemfa marker="stdlib:uri_string#unquote/1">uri_string:unquote/1</seemfa> instead </p></warning>
+ <p>Decodes a possibly percent encoded URI part</p>
</desc>
</func>
<func>
- <name since="OTP R15B01">encode(URI) -> HexEncodedURI</name>
+ <name since="OTP R15B01">encode(DecodedPart) -> EncodedPart</name>
- <fsummary>Encodes a hexadecimal encoded URI.</fsummary>
+ <fsummary>Performes percent encoding</fsummary>
<type>
- <v>URI = uri()</v>
- <v>HexEncodedURI = string() | binary() - Hexadecimal encoded URI</v>
+ <v>DecodePart = uri_part()</v>
+ <v>EncodedPart = uri_part() - Percent encoded URI part</v>
</type>
<desc>
- <p>Encodes a hexadecimal encoded URI.</p>
+ <warning><p>Do not use will be removed use <seemfa marker="stdlib:uri_string#quote/1">uri_string:quote/1</seemfa> instead </p> </warning>
+ <p>Performes prrcent encoding.</p>
<marker id="decode"></marker>
</desc>
diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml
index 0c6437ccec..ba18c7d802 100644
--- a/lib/inets/doc/src/httpc.xml
+++ b/lib/inets/doc/src/httpc.xml
@@ -243,8 +243,12 @@
<tag><c>ssl</c></tag>
<item>
<p>This is the <c>SSL/TLS</c> connecting configuration option.</p>
- <p>Defaults to <c>[]</c>. See <seeerl marker="ssl:ssl">ssl:connect/[2,3,4]</seeerl> for available options.</p>
- </item>
+ <p>Default value is obtained by calling
+ <seemfa marker="#ssl_verify_host_options/1"><c>httpc:ssl_verify_host_options(true)</c>.
+ </seemfa>.
+ See <seeerl marker="ssl:ssl">ssl:connect/[2,3,4]</seeerl> for available options.
+ </p>
+ </item>
<tag><c>autoredirect</c></tag>
<item>
diff --git a/lib/inets/doc/src/httpd_util.xml b/lib/inets/doc/src/httpd_util.xml
index a334ec9f86..f8bd403efa 100644
--- a/lib/inets/doc/src/httpd_util.xml
+++ b/lib/inets/doc/src/httpd_util.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1997</year><year>2022</year>
+ <year>1997</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -87,57 +87,6 @@
</func>
<func>
- <name since="">decode_hex(HexValue) -> DecValue</name>
- <fsummary>Converts a hexadecimal value into its decimal equivalent.</fsummary>
- <type>
- <v>HexValue = DecValue = string()</v>
- </type>
- <desc>
- <p>Converts the hexadecimal value <c>HexValue</c> into its
- decimal equivalent (<c>DecValue</c>).</p>
- </desc>
- </func>
-
- <func>
- <name since="">flatlength(NestedList) -> Size</name>
- <fsummary>Computes the size of a possibly nested list.</fsummary>
- <type>
- <v>NestedList = list()</v>
- <v>Size = integer()</v>
- </type>
- <desc>
- <p><c>flatlength/1</c> computes the size of the possibly nested
- list <c>NestedList</c>, which can contain binaries.</p>
- </desc>
- </func>
-
- <func>
- <name since="">hexlist_to_integer(HexString) -> Number</name>
- <fsummary>Converts a hexadecimal string to an integer.</fsummary>
- <type>
- <v>Number = integer()</v>
- <v>HexString = string()</v>
- </type>
- <desc>
- <p><c>hexlist_to_integer</c> converts the hexadecimal value of
- <c>HexString</c> to an integer.</p>
- </desc>
- </func>
-
- <func>
- <name since="">integer_to_hexlist(Number) -> HexString</name>
- <fsummary>Converts an integer to a hexadecimal string.</fsummary>
- <type>
- <v>Number = integer()</v>
- <v>HexString = string()</v>
- </type>
- <desc>
- <p><c>integer_to_hexlist/1</c> returns a string representing
- <c>Number</c> in a hexadecimal form.</p>
- </desc>
- </func>
-
- <func>
<name since="">lookup(ETSTable,Key) -> Result</name>
<name since="">lookup(ETSTable,Key,Undefined) -> Result</name>
<fsummary>Extracts the first value associated with a <c>Key</c>
@@ -349,39 +298,11 @@
for longest possible <c>Path</c> being a file or a
directory. Everything after the longest possible
<c>Path</c>, isolated with a <c>/</c>, is regarded as
- <c>PathInfo</c>. The resulting <c>Path</c> is decoded using
- <c>decode_hex/1</c> before delivery.</p>
- </desc>
- </func>
-
- <func>
- <name since="">strip(String) -> Stripped</name>
- <fsummary>Returns <c>String</c> where the leading and trailing space
- tabs are removed.</fsummary>
- <type>
- <v>String = Stripped = string()</v>
- </type>
- <desc>
- <p><c>strip/1</c> removes any leading or trailing linear white
- space from the string. Linear white space is to be read as
- horizontal tab or space.</p>
+ <c>PathInfo</c></p>
</desc>
</func>
-
- <func>
- <name since="">suffix(FileName) -> Suffix</name>
- <fsummary>Extracts the file suffix from a given filename.</fsummary>
- <type>
- <v>FileName = Suffix = string()</v>
- </type>
- <desc>
- <p><c>suffix/1</c> is equivalent to
- <c>filename:extension/1</c> with the exception that
- <c>Suffix</c> is returned without a leading dot (<c>.</c>).</p>
- </desc>
- </func>
- </funcs>
-
+ </funcs>
+
<section>
<title>SEE ALSO</title>
<p><seeerl marker="httpd">httpd(3)</seeerl></p>
diff --git a/lib/inets/examples/server_root/Makefile b/lib/inets/examples/server_root/Makefile
index 5dfb8d1704..ccce663881 100644
--- a/lib/inets/examples/server_root/Makefile
+++ b/lib/inets/examples/server_root/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2022. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -41,7 +41,6 @@ AUTH_FILES = auth/group \
auth/passwd
CGI_FILES = cgi-bin/printenv.sh
CONF_FILES = conf/8080.conf \
- conf/8888.conf \
conf/httpd.conf \
conf/ssl.conf \
conf/mime.types
diff --git a/lib/inets/examples/server_root/conf/8080.conf b/lib/inets/examples/server_root/conf/8080.conf
index 7b1b4a15b2..bf8fbba390 100644
--- a/lib/inets/examples/server_root/conf/8080.conf
+++ b/lib/inets/examples/server_root/conf/8080.conf
@@ -1,79 +1,67 @@
-Port 8080
-#ServerName your.server.net
-SocketType ip_comm
-Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_dir mod_get mod_head mod_log mod_disk_log
-ServerAdmin jocke@erix.ericsson.se
-ServerRoot /var/tmp/server_root
-ErrorLog logs/error_log_8080
-TransferLog logs/access_log_8080
-SecurityLog logs/security_log_8080
-ErrorDiskLog logs/error_disk_log_8080
-ErrorDiskLogSize 200000 10
-TransferDiskLog logs/access_disk_log_8080
-TransferDiskLogSize 200000 10
-SecurityDiskLog logs/security_disk_log
-SecurityDiskLogSize 200000 10
-MaxClients 50
-#KeepAlive 5
-#KeepAliveTimeout 10
-DocumentRoot /var/tmp/server_root/htdocs
-DirectoryIndex index.html welcome.html
-DefaultType text/plain
-Alias /icons/ /var/tmp/server_root/icons/
-Alias /pics/ /var/tmp/server_root/icons/
-ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
-ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
-ErlScriptAlias /cgi-bin/erl httpd_example io
-EvalScriptAlias /eval httpd_example io
-#Script HEAD /cgi-bin/printenv.sh
-#Action image/gif /cgi-bin/printenv.sh
-
-<Directory /var/tmp/server_root/htdocs/open>
-AuthDBType plain
-AuthName Open Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require user one Aladdin
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/secret>
-AuthDBType plain
-AuthName Secret Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require group group1 group2
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/secret/top_secret>
-AuthDBType plain
-AuthName Top Secret Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require group group3
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_open>
-AuthDBType mnesia
-AuthName Open Area
-require user one Aladdin
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_secret>
-AuthDBType mnesia
-AuthName Secret Area
-require group group1 group2
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
-AuthDBType mnesia
-AuthName Top Secret Area
-require group group3
-allow from 130.100.34 130.100.35
-deny from 100.234.22.12 194.100.34.1 130.100.34.25
-SecurityDataFile logs/security_data
-SecurityMaxRetries 3
-SecurityBlockTime 10
-SecurityFailExpireTime 1
-SecurityAuthTimeout 1
-SecurityCallbackModule security_callback
-</Directory>
+[{port, 8080},
+ {server_name, "your.server.net"},
+ {socket_type, ip_comm},
+ {modules, [mod_alias,mod_auth,mod_esi,mod_actions,mod_cgi,mod_dir,mod_get,
+ mod_head,mod_log,mod_disk_log]},
+ {server_admin, "jocke@erix.ericsson.se"},
+ {server_root, "/var/tmp/server_root"},
+ {error_log, "logs/error_log_8080"},
+ {transfer_log, "logs/access_log_8080"},
+ {security_log, "logs/security_log_8080"},
+ {error_disk_log, "logs/error_disk_log_8080"},
+ {error_disk_log_size, {200000,10}},
+ {transfer_disk_log, "logs/access_disk_log_8080"},
+ {transfer_disk_log_size, {200000,10}},
+ {security_disk_log, "logs/security_disk_log"},
+ {security_disk_log_size, {200000,10}},
+ {max_clients, 50},
+ %%{keep_alive, 5},
+ %%{keep_alive_timeout, 10},
+ {document_root, "/var/tmp/server_root/htdocs"},
+ {directory_index, ["index.html","welcome.html"]},
+ {default_type, "text/plain"},
+ {alias, {"/icons/","/var/tmp/server_root/icons/"}},
+ {alias, {"/pics/","/var/tmp/server_root/icons/"}},
+ {script_alias, {"/cgi-bin/","/var/tmp/server_root/cgi-bin/"}},
+ {script_alias, {"/htbin/","/var/tmp/server_root/cgi-bin/"}},
+ {erl_script_alias, {"/cgi-bin/erl",[httpd_example]}},
+ %%{script, {"HEAD", "/cgi-bin/printenv.sh"}},
+ %%{action, {"image/gif", "/cgi-bin/printenv.sh"}},
+ {directory, {"/var/tmp/server_root/htdocs/open",
+ [{require,["user","one","Aladdin"]},
+ {auth_group_file,"/var/tmp/server_root/auth/group"},
+ {auth_user_file,"/var/tmp/server_root/auth/passwd"},
+ {auth_name,"Open Area"},
+ {auth_type,"plain"}]}},
+ {directory, {"/var/tmp/server_root/htdocs/secret",
+ [{require,["group","group1","group2"]},
+ {auth_group_file,"/var/tmp/server_root/auth/group"},
+ {auth_user_file,"/var/tmp/server_root/auth/passwd"},
+ {auth_name,"Secret Area"},
+ {auth_type,"plain"}]}},
+ {directory, {"/var/tmp/server_root/htdocs/secret/top_secret",
+ [{require,["group","group3"]},
+ {auth_group_file,"/var/tmp/server_root/auth/group"},
+ {auth_user_file,"/var/tmp/server_root/auth/passwd"},
+ {auth_name,"Top Secret Area"},
+ {auth_type,"plain"}]}},
+ {directory, {"/var/tmp/server_root/htdocs/mnesia_open",
+ [{require,["user","one","Aladdin"]},
+ {auth_name,"Open Area"},
+ {auth_type,"mnesia"}]}},
+ {directory, {"/var/tmp/server_root/htdocs/mnesia_secret",
+ [{require,["group","group1","group2"]},
+ {auth_name,"Secret Area"},
+ {auth_type,"mnesia"}]}},
+ {directory, {"/var/tmp/server_root/htdocs/mnesia_secret/top_secret",
+ [{security_callback_module,"security_callback"},
+ {security_auth_timeout,1},
+ {security_fail_expire_time,1},
+ {security_block_time,10},
+ {security_max_retries,3},
+ {security_data_file,"logs/security_data"},
+ {deny_from,["100.234.22.12","194.100.34.1","130.100.34.25"]},
+ {allow_from,["130.100.34","130.100.35"]},
+ {require,["group","group3"]},
+ {auth_name,"Top Secret Area"},
+ {auth_type,"mnesia"}]}}].
diff --git a/lib/inets/examples/server_root/conf/8888.conf b/lib/inets/examples/server_root/conf/8888.conf
deleted file mode 100644
index 042779fcd0..0000000000
--- a/lib/inets/examples/server_root/conf/8888.conf
+++ /dev/null
@@ -1,63 +0,0 @@
-Port 8888
-#ServerName your.server.net
-SocketType ip_comm
-Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_dir mod_get mod_head mod_log mod_disk_log
-ServerAdmin jocke@erix.ericsson.se
-ServerRoot /var/tmp/server_root
-ErrorLog logs/error_log_8888
-TransferLog logs/access_log_8888
-ErrorDiskLog logs/error_disk_log_8888
-ErrorDiskLogSize 200000 10
-TransferDiskLog logs/access_disk_log_8888
-TransferDiskLogSize 200000 10
-MaxClients 150
-DocumentRoot /var/tmp/server_root/htdocs
-DirectoryIndex index.html welcome.html
-DefaultType text/plain
-Alias /icons/ /var/tmp/server_root/icons/
-Alias /pics/ /var/tmp/server_root/icons/
-ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
-ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
-ErlScriptAlias /cgi-bin/erl httpd_example io
-EvalScriptAlias /eval httpd_example io
-#Script HEAD /cgi-bin/printenv.sh
-#Action image/gif /cgi-bin/printenv.sh
-
-<Directory /var/tmp/server_root/htdocs/open>
-AuthName Open Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require user one Aladdin
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/secret>
-AuthName Secret Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require group group1 group2
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/secret/top_secret>
-AuthName Top Secret Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require group group3
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_open>
-AuthName Open Area
-AuthMnesiaDB On
-require user one Aladdin
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_secret>
-AuthName Secret Area
-AuthMnesiaDB On
-require group group1 group2
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
-AuthName Top Secret Area
-AuthMnesiaDB On
-require group group3
-</Directory>
diff --git a/lib/inets/examples/server_root/conf/httpd.conf b/lib/inets/examples/server_root/conf/httpd.conf
index e44a45c02c..d74e00bf4a 100644
--- a/lib/inets/examples/server_root/conf/httpd.conf
+++ b/lib/inets/examples/server_root/conf/httpd.conf
@@ -1,269 +1,245 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2022. All Rights Reserved.
-#
-# 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.
-#
-# %CopyrightEnd%
-#
-#
-
-# Port: The port the standalone listens to. For ports < 1023, you will
-# need httpd to be run as root initially.
-
-Port 8888
-
-# BindAddress: This directive is used to tell the server which IP address
-# to listen to. It can either contain "*", an IP address, or a fully
-# qualified Internet domain name.
-#
-# It is also possible to specify the ip-family with the directive.
-# There ar three possible value: inet, inet6 and inet6fb4
-# inet: Use IpFamily inet when retrieving the address and
-# fail if that does not work.
-# inet6: Use IpFamily inet6 when retrieving the address and
-# fail if that does not work.
-# inet6fb4: First IpFamily inet6 is tried and if that does not work,
-# inet is used as fallback.
-# Default value for ip-family is inet6fb4
-#
-# The syntax is: <address>[|<ip-family>]
-#
-#BindAddress *
-#BindAddress *|inet
-
-
-# ServerName allows you to set a host name which is sent back to clients for
-# your server if it's different than the one the program would get (i.e. use
-# "www" instead of the host's real name).
-#
-# Note: You cannot just invent host names and hope they work. The name you
-# define here must be a valid DNS name for your host. If you don't understand
-# this, ask your network administrator.
-
-#ServerName your.server.net
-
-# SocketType is either ip_comm, sockets or ssl.
-
-SocketType ip_comm
-
-# Modules: Server run-time plug-in modules written using the Erlang
-# Web Server API (EWSAPI). The server API make it easy to add functionality
-# to the server. Read more about EWSAPI in the Reference Manual.
-# WARNING! Do not tamper with this directive unless you are familiar with
-# EWSAPI.
-
-Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_responsecontrol mod_trace mod_range mod_head mod_dir mod_get mod_log mod_disk_log
-
-# ServerAdmin: Your address, where problems with the server should be
-# e-mailed.
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%
+%% Port: The port the standalone listens to. For ports < 1023, you will
+%% need httpd to be run as root initially.
+
+[{port, 8888},
+
+%% BindAddress: This directive is used to tell the server which IP address
+%% to listen to. It can either contain "*", an IP address, or a fully
+%% qualified Internet domain name.
+%%
+%% It is also possible to specify the ip-family with the directive.
+%% There ar three possible value: inet, inet6 and inet6fb4
+%% inet: Use IpFamily inet when retrieving the address and
+%% fail if that does not work.
+%% inet6: Use IpFamily inet6 when retrieving the address and
+%% fail if that does not work.
+%% inet6fb4: First IpFamily inet6 is tried and if that does not work,
+%% inet is used as fallback.
+%% Default value for ip-family is inet6fb4
+%%
+%% The syntax is: <address>[|<ip-family>]
+%%
+%%BindAddress *
+%%BindAddress *|inet
+
+
+%% ServerName allows you to set a host name which is sent back to clients for
+%% your server if it's different than the one the program would get (i.e. use
+%% "www" instead of the host's real name).
+%%
+%% Note: You cannot just invent host names and hope they work. The name you
+%% define here must be a valid DNS name for your host. If you don't understand
+%% this, ask your network administrator.
+
+{server_name, "your.server.net"},
+
+%% SocketType is either ip_comm, sockets or ssl.
+
+{socket_type, ip_comm},
+
+%% Point certfile to a PEM encoded certificate. If the key is not combined
+%% with the certificate, use keyfile directive to point to the key file.
+%{socket_type, {ssl, [{certfile, "/var/tmp/server_root/ssl/ssl_server.pem"},
+% {keyfile, "/var/tmp/server_root/ssl/ssl_server.pem"},
+% {verify, verify_none}]}},
+
+%% Modules: Server run-time plug-in modules written using the Erlang
+%% Web Server API (EWSAPI). The server API make it easy to add functionality
+%% to the server. Read more about EWSAPI in the Reference Manual.
+%% WARNING! Do not tamper with this directive unless you are familiar with
+%% EWSAPI.
+
+{modules, [mod_alias,mod_auth,mod_esi,mod_actions,mod_cgi,mod_responsecontrol,
+ mod_trace,mod_range,mod_head,mod_dir,mod_get,mod_log,mod_disk_log]},
+
+%% ServerAdmin: Your address, where problems with the server should be
+%% e-mailed.
+
+{server_admin, "jocke@erix.ericsson.se"},
+
+%% ServerRoot: The directory the server's config, error, and log files
+%% are kept in
+
+{server_root, "/var/tmp/server_root"},
+
+%% ErrorLog: The location of the error log file. If this does not start
+%% with /, ServerRoot is prepended to it.
+
+{error_log, "logs/error_log"},
+
+%% TransferLog: The location of the transfer log file. If this does not
+%% start with /, ServerRoot is prepended to it.
+
+{transfer_log, "logs/access_log"}
+
+%% SecurityLog: The location of the security log file (mod_security required)
+
+{security_log, "logs/security_log"},
+
+%% ErrorDiskLog: The location of the error log file. If this does not
+%% start with /, ServerRoot is prepended to it. This log file is managed
+%% with the disk_log module [See disk_log(3)]. The ErrorDiskLogSize directive
+%% takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
+%% MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
+%% truncates the first file.
+
+{error_disk_log, "logs/error_disk_log"},
+{error_disk_log_size, {200000,10}},
-ServerAdmin jocke@erix.ericsson.se
+%% TransferDiskLog: The location of the transfer log file. If this does not
+%% start with /, ServerRoot is prepended to it. This log file is managed
+%% with the disk_log module [See disk_log(3)]. The TransferDiskLogSize directive
+%% takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
+%% MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
+%% truncates the first file.
-# ServerRoot: The directory the server's config, error, and log files
-# are kept in
-
-ServerRoot /var/tmp/server_root
-
-# ErrorLog: The location of the error log file. If this does not start
-# with /, ServerRoot is prepended to it.
+{transfer_disk_log, "logs/access_disk_log"},
+{transfer_disk_log_size, {200000,10}},
-ErrorLog logs/error_log
+%% SecurityDiskLog: The location of the security log file. If this does not
+%% start with /, ServerRoot is prepended to it. This log file is managed
+%% with the disk_log module [See disk_log(3)]. The SecurityDiskLogSize directive
+%% takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
+%% MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
+%% truncates the first file.
-# TransferLog: The location of the transfer log file. If this does not
-# start with /, ServerRoot is prepended to it.
+{security_disk_log, "logs/security_disk_log"},
+{security_disk_log_size, {200000,10}},
-TransferLog logs/access_log
+%% Limit on total number of servers running, i.e., limit on the number
+%% of clients who can simultaneously connect --- if this limit is ever
+%% reached, clients will be LOCKED OUT, so it should NOT BE SET TOO LOW.
+%% It is intended mainly as a brake to keep a runaway server from taking
+%% the server with it as it spirals down...
-# SecurityLog: The location of the security log file (mod_security required)
-#
-SecurityLog logs/security_log
+{max_clients, 50},
-# ErrorDiskLog: The location of the error log file. If this does not
-# start with /, ServerRoot is prepended to it. This log file is managed
-# with the disk_log module [See disk_log(3)]. The ErrorDiskLogSize directive
-# takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
-# MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
-# truncates the first file.
+%% KeepAlive set the flag for persistent connections. For persistent connections
+%% set KeepAlive to on. To use One request per connection set the flag to off
+%% Note: The value has changed since previous version of INETS.
+{keep_alive, false},
-ErrorDiskLog logs/error_disk_log
-ErrorDiskLogSize 200000 10
+%% KeepAliveTimeout sets the number of seconds before a persistent connection
+%% times out and closes.
+{keep_alive_timeout, 10},
-# TransferDiskLog: The location of the transfer log file. If this does not
-# start with /, ServerRoot is prepended to it. This log file is managed
-# with the disk_log module [See disk_log(3)]. The TransferDiskLogSize directive
-# takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
-# MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
-# truncates the first file.
+%% MaxKeepAliveRequests sets the number of seconds before a persistent connection
+%% times out and closes.
+{max_keep_alive_requests, "10"},
-TransferDiskLog logs/access_disk_log
-TransferDiskLogSize 200000 10
+%% DocumentRoot: The directory out of which you will serve your
+%% documents. By default, all requests are taken from this directory, but
+%% symbolic links and aliases may be used to point to other locations.
-# SecurityDiskLog: The location of the security log file. If this does not
-# start with /, ServerRoot is prepended to it. This log file is managed
-# with the disk_log module [See disk_log(3)]. The SecurityDiskLogSize directive
-# takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
-# MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
-# truncates the first file.
+{document_root, "/var/tmp/server_root/htdocs"},
-SecurityDiskLog logs/security_disk_log
-SecurityDiskLogSize 200000 10
+%% DirectoryIndex: Name of the file or files to use as a pre-written HTML
+%% directory index. Separate multiple entries with spaces.
-# Limit on total number of servers running, i.e., limit on the number
-# of clients who can simultaneously connect --- if this limit is ever
-# reached, clients will be LOCKED OUT, so it should NOT BE SET TOO LOW.
-# It is intended mainly as a brake to keep a runaway server from taking
-# the server with it as it spirals down...
+{directory_index, ["index.html","welcome.html"]},
-MaxClients 50
+%% DefaultType is the default MIME type for documents which the server
+%% cannot find the type of from filename extensions.
-# KeepAlive set the flag for persistent connections. For persistent connections
-# set KeepAlive to on. To use One request per connection set the flag to off
-# Note: The value has changed since previous version of INETS.
-KeepAlive on
+{default_type, "text/plain"},
-# KeepAliveTimeout sets the number of seconds before a persistent connection
-# times out and closes.
-KeepAliveTimeout 10
+%% Aliases: Add here as many aliases as you need (with no limit). The format is
+%% Alias fakename realname
-# MaxKeepAliveRequests sets the number of seconds before a persistent connection
-# times out and closes.
-MaxKeepAliveRequests 10
+{alias, {"/icons/", "/var/tmp/server_root/icons/"}},
+{alias, {"/pics/", "/var/tmp/server_root/icons/"}},
+%% ScriptAlias: This controls which directories contain server scripts.
+%% Format: ScriptAlias fakename realname
+{script_alias, {"/cgi-bin/", "/var/tmp/server_root/cgi-bin/"}},
+{script_alias, {"/htbin/", "/var/tmp/server_root/cgi-bin/"}},
-# DocumentRoot: The directory out of which you will serve your
-# documents. By default, all requests are taken from this directory, but
-# symbolic links and aliases may be used to point to other locations.
+%% This directive adds an action, which will activate cgi-script when a
+%% file is requested using the method of method, which can be one of
+%% GET, POST and HEAD. It sends the URL and file path of the requested
+%% document using the standard CGI PATH_INFO and PATH_TRANSLATED
+%% environment variables.
-DocumentRoot /var/tmp/server_root/htdocs
-
-# DirectoryIndex: Name of the file or files to use as a pre-written HTML
-# directory index. Separate multiple entries with spaces.
-
-DirectoryIndex index.html welcome.html
-
-# DefaultType is the default MIME type for documents which the server
-# cannot find the type of from filename extensions.
-
-DefaultType text/plain
-
-# Aliases: Add here as many aliases as you need (with no limit). The format is
-# Alias fakename realname
-
-Alias /icons/ /var/tmp/server_root/icons/
-Alias /pics/ /var/tmp/server_root/icons/
-
-# ScriptAlias: This controls which directories contain server scripts.
-# Format: ScriptAlias fakename realname
-
-ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
-ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
-
-# This directive adds an action, which will activate cgi-script when a
-# file is requested using the method of method, which can be one of
-# GET, POST and HEAD. It sends the URL and file path of the requested
-# document using the standard CGI PATH_INFO and PATH_TRANSLATED
-# environment variables.
-
-#Script HEAD /cgi-bin/printenv.sh
-
-# This directive adds an action, which will activate cgi-script when a
-# file of content type mime-type is requested. It sends the URL and
-# file path of the requested document using the standard CGI PATH_INFO
-# and PATH_TRANSLATED environment variables.
-
-#Action image/gif /cgi-bin/printenv.sh
-
-# ErlScriptAlias: This specifies how "Erl" server scripts are called.
-# Format: ErlScriptAlias fakename realname allowed_modules
-
-ErlScriptAlias /down/erl httpd_example io
-
-# EvalScriptAlias: This specifies how "Eval" server scripts are called.
-# Format: EvalScriptAlias fakename realname allowed_modules
-
-EvalScriptAlias /eval httpd_example io
-
-# Point SSLCertificateFile at a PEM encoded certificate.
-
-SSLCertificateFile /var/tmp/server_root/ssl/ssl_server.pem
-
-# If the key is not combined with the certificate, use this directive to
-# point at the key file.
-
-SSLCertificateKeyFile /var/tmp/server_root/ssl/ssl_server.pem
-
-# Set SSLVerifyClient to:
-# 0 if no certificate is required
-# 1 if the client may present a valid certificate
-# 2 if the client must present a valid certificate
-# 3 if the client may present a valid certificate but it is not required to
-# have a valid CA
-
-SSLVerifyClient 0
-
-# Each directory to which INETS has access, can be configured with respect
-# to which services and features are allowed and/or disabled in that
-# directory (and its subdirectories).
-
-<Directory /var/tmp/server_root/htdocs/open>
-AuthDBType plain
-AuthName Open Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require user one Aladdin
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/secret>
-AuthDBType plain
-AuthName Secret Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require group group1 group2
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/secret/top_secret>
-AuthDBType plain
-AuthName Top Secret Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require group group3
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_open>
-AuthDBType mnesia
-AuthName Open Area
-require user one Aladdin
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_secret>
-AuthDBType mnesia
-AuthName Secret Area
-require group group1 group2
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
-AuthDBType mnesia
-AuthName Top Secret Area
-require group group3
-allow from 130.100.34 130.100.35
-deny from 100.234.22.12 194.100.34.1 130.100.34.25
-SecurityDataFile logs/security_data
-SecurityMaxRetries 3
-SecurityBlockTime 10
-SecurityFailExpireTime 1
-SecurityAuthTimeout 1
-SecurityCallbackModule security_callback
-</Directory>
+%%{script, {"HEAD", "/cgi-bin/printenv.sh"}}
+
+%% This directive adds an action, which will activate cgi-script when a
+%% file of content type mime-type is requested. It sends the URL and
+%% file path of the requested document using the standard CGI PATH_INFO
+%% and PATH_TRANSLATED environment variables.
+
+%%{action, {"image/gif", "/cgi-bin/printenv.sh"}},
+
+%% ErlScriptAlias: This specifies how "Erl" server scripts are called.
+%% Format: ErlScriptAlias fakename realname allowed_modules
+
+{erl_script_alias, {"/down/erl", [httpd_example]}},
+
+%% Each directory to which INETS has access, can be configured with respect
+%% to which services and features are allowed and/or disabled in that
+%% directory (and its subdirectories).
+
+{directory, {"/var/tmp/server_root/htdocs/open",
+ [{require,["user","one","Aladdin"]},
+ {auth_group_file,"/var/tmp/server_root/auth/group"},
+ {auth_user_file,"/var/tmp/server_root/auth/passwd"},
+ {auth_name,"Open Area"},
+ {auth_type,"plain"}]}},
+
+{directory, {"/var/tmp/server_root/htdocs/secret",
+ [{require,["group","group1","group2"]},
+ {auth_group_file,"/var/tmp/server_root/auth/group"},
+ {auth_user_file,"/var/tmp/server_root/auth/passwd"},
+ {auth_name,"Secret Area"},
+ {auth_type,"plain"}]}},
+
+{directory, {"/var/tmp/server_root/htdocs/secret/top_secret",
+ [{require,["group","group3"]},
+ {auth_group_file,"/var/tmp/server_root/auth/group"},
+ {auth_user_file,"/var/tmp/server_root/auth/passwd"},
+ {auth_name,"Top Secret Area"},
+ {auth_type,"plain"}]}},
+
+{directory, {"/var/tmp/server_root/htdocs/mnesia_open",
+ [{require,["user","one","Aladdin"]},
+ {auth_name,"Open Area"},
+ {auth_type,"mnesia"}]}},
+
+{directory, {"/var/tmp/server_root/htdocs/mnesia_secret",
+ [{require,["group","group1","group2"]},
+ {auth_name,"Secret Area"},
+ {auth_type,"mnesia"}]}},
+
+{directory, {"/var/tmp/server_root/htdocs/mnesia_secret/top_secret",
+ [{security_callback_module,"security_callback"},
+ {security_auth_timeout,1},
+ {security_fail_expire_time,1},
+ {security_block_time,10},
+ {security_max_retries,3},
+ {security_data_file,"logs/security_data"},
+ {deny_from,["100.234.22.12","194.100.34.1","130.100.34.25"]},
+ {allow_from,["130.100.34","130.100.35"]},
+ {require,["group","group3"]},
+ {auth_name,"Top Secret Area"},
+ {auth_type,"mnesia"}]}}].
diff --git a/lib/inets/examples/server_root/conf/ssl.conf b/lib/inets/examples/server_root/conf/ssl.conf
index de49ceafd0..85c8770530 100644
--- a/lib/inets/examples/server_root/conf/ssl.conf
+++ b/lib/inets/examples/server_root/conf/ssl.conf
@@ -1,66 +1,51 @@
-Port 8088
-#ServerName your.server.net
-SocketType ssl
-Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_dir mod_get mod_head mod_log mod_disk_log
-ServerAdmin jocke@erix.ericsson.se
-ServerRoot /var/tmp/server_root
-ErrorLog logs/error_log_8088
-TransferLog logs/access_log_8088
-ErrorDiskLog logs/error_disk_log_8088
-ErrorDiskLogSize 200000 10
-TransferDiskLog logs/access_disk_log_8088
-TransferDiskLogSize 200000 10
-MaxClients 150
-DocumentRoot /var/tmp/server_root/htdocs
-DirectoryIndex index.html welcome.html
-DefaultType text/plain
-Alias /icons/ /var/tmp/server_root/icons/
-Alias /pics/ /var/tmp/server_root/icons/
-ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
-ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
-ErlScriptAlias /cgi-bin/erl httpd_example io
-EvalScriptAlias /eval httpd_example io
-SSLCertificateFile /var/tmp/server_root/ssl/ssl_server.pem
-SSLCertificateKeyFile /var/tmp/server_root/ssl/ssl_server.pem
-SSLVerifyClient 0
-#Script HEAD /cgi-bin/printenv.sh
-#Action image/gif /cgi-bin/printenv.sh
-
-<Directory /var/tmp/server_root/htdocs/open>
-AuthName Open Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require user one Aladdin
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/secret>
-AuthName Secret Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require group group1 group2
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/secret/top_secret>
-AuthName Top Secret Area
-AuthUserFile /var/tmp/server_root/auth/passwd
-AuthGroupFile /var/tmp/server_root/auth/group
-require group group3
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_open>
-AuthName Open Area
-AuthMnesiaDB On
-require user one Aladdin
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_secret>
-AuthName Secret Area
-AuthMnesiaDB On
-require group group1 group2
-</Directory>
-
-<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
-AuthName Top Secret Area
-AuthMnesiaDB On
-require group group3
-</Directory>
+[{port, 8088},
+ {server_name, "your.server.net"},
+ {modules, [mod_alias,mod_auth,mod_esi,mod_actions,mod_cgi,mod_dir,mod_get,
+ mod_head,mod_log,mod_disk_log]},
+ {server_admin, "jocke@erix.ericsson.se"},
+ {server_root, "/var/tmp/server_root"},
+ {error_log, "logs/error_log_8088"},
+ {transfer_log, "logs/access_log_8088"},
+ {error_disk_log, "logs/error_disk_log_8088"},
+ {error_disk_log_size, {200000,10}},
+ {transfer_disk_log, "logs/access_disk_log_8088"},
+ {transfer_disk_log_size, {200000,10}},
+ {max_clients, 150},
+ {document_root, "/var/tmp/server_root/htdocs"},
+ {directory_index, ["index.html","welcome.html"]},
+ {default_type, "text/plain"},
+ {alias, {"/icons/", "/var/tmp/server_root/icons/"}},
+ {alias, {"/pics/", "/var/tmp/server_root/icons/"}},
+ {script_alias, {"/cgi-bin/", "/var/tmp/server_root/cgi-bin/"}},
+ {script_alias, {"/htbin/", "/var/tmp/server_root/cgi-bin/"}},
+ {erl_script_alias, {"/cgi-bin/erl", [httpd_example]}},
+ {socket_type, {ssl, [{certfile, "/var/tmp/server_root/ssl/ssl_server_cert.pem"},
+ {keyfile, "/var/tmp/server_root/ssl/ssl_key.pem"},
+ {verify, verify_none}]}},
+ {directory, {"/var/tmp/server_root/htdocs/open",
+ [{require,["user","one","Aladdin"]},
+ {auth_group_file,"/var/tmp/server_root/auth/group"},
+ {auth_user_file,"/var/tmp/server_root/auth/passwd"},
+ {auth_name,"Open Area"}]}},
+ {directory, {"/var/tmp/server_root/htdocs/secret",
+ [{require,["group","group1","group2"]},
+ {auth_group_file,"/var/tmp/server_root/auth/group"},
+ {auth_user_file,"/var/tmp/server_root/auth/passwd"},
+ {auth_name,"Secret Area"}]}},
+ {directory, {"/var/tmp/server_root/htdocs/secret/top_secret",
+ [{require,["group","group3"]},
+ {auth_group_file,"/var/tmp/server_root/auth/group"},
+ {auth_user_file,"/var/tmp/server_root/auth/passwd"},
+ {auth_name,"Top Secret Area"}]}},
+ {directory, {"/var/tmp/server_root/htdocs/mnesia_open",
+ [{require,["user","one","Aladdin"]},
+ {auth_mnesia_d_b,"On"},
+ {auth_name,"Open Area"}]}},
+ {directory, {"/var/tmp/server_root/htdocs/mnesia_secret",
+ [{require,["group","group1","group2"]},
+ {auth_mnesia_d_b,"On"},
+ {auth_name,"Secret Area"}]}},
+ {directory, {"/var/tmp/server_root/htdocs/mnesia_secret/top_secret",
+ [{require,["group","group3"]},
+ {auth_mnesia_d_b,"On"},
+ {auth_name,"Top Secret Area"}]}}].
diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl
index 96d5cd250d..8f62b8906c 100644
--- a/lib/inets/src/http_client/httpc.erl
+++ b/lib/inets/src/http_client/httpc.erl
@@ -119,11 +119,12 @@ request(Url) ->
request(Url, Profile) ->
request(get, {Url, []}, [], [], Profile).
-%%
%% @doc Sends a HTTP-request. The function can be both synchronous and
-%% asynchronous in the later case the function will return `{ok, RequestId}' and
-%% and then the information is delivered to the receiver depending on that
-%% value.
+%% asynchronous in the later case the function will return `{ok, RequestId}'
+%% and later on a message will be sent to the
+%% calling process on the format {http, {RequestId, {StatusLine,
+%% Headers, Body}}} or {http, {RequestId, {error, Reason}}}.
+%% Only octects are accepted in header fields and values.
-spec request(Method, Request, HttpOptions, Options) -> {ok, Result} | {error, term()} when
Method :: head | get | put | patch | post | trace | options | delete,
Request :: { uri_string:uri_string()
@@ -982,11 +983,12 @@ http_options_default() ->
AutoRedirectPost = boolfun(),
SslPost = fun(Value) when is_list(Value) ->
- {ok, {?HTTP_DEFAULT_SSL_KIND, Value}};
- ({ssl, SslOptions}) when is_list(SslOptions) ->
- {ok, {?HTTP_DEFAULT_SSL_KIND, SslOptions}};
+ {ok, {ssl, Value}};
+ ({ssl, SslOptions}) when is_list(SslOptions) ->
+ {ok, {ssl, SslOptions}};
+ %% backwards compat
({essl, SslOptions}) when is_list(SslOptions) ->
- {ok, {essl, SslOptions}};
+ {ok, {ssl, SslOptions}};
(_) ->
error
end,
@@ -1007,12 +1009,14 @@ http_options_default() ->
error
end,
+ SslOpts = ssl_verify_host_options(true),
+
UrlDecodePost = boolfun(),
[
{version, {value, "HTTP/1.1"}, #http_options.version, VersionPost},
{timeout, {value, ?HTTP_REQUEST_TIMEOUT}, #http_options.timeout, TimeoutPost},
{autoredirect, {value, true}, #http_options.autoredirect, AutoRedirectPost},
- {ssl, {value, {?HTTP_DEFAULT_SSL_KIND, []}}, #http_options.ssl, SslPost},
+ {ssl, {value, {ssl, SslOpts}}, #http_options.ssl, SslPost},
{proxy_auth, {value, undefined}, #http_options.proxy_auth, ProxyAuthPost},
{relaxed, {value, false}, #http_options.relaxed, RelaxedPost},
{url_encode, {value, false}, #http_options.url_encode, UrlDecodePost},
diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl
index 63e26f5266..026cea9b1f 100644
--- a/lib/inets/src/http_client/httpc_handler.erl
+++ b/lib/inets/src/http_client/httpc_handler.erl
@@ -50,23 +50,23 @@
-record(state,
{
- request :: request() | undefined,
- session :: session() | undefined,
- status_line :: tuple() | undefined, % {Version, StatusCode, ReasonPharse}
- headers :: http_response_h() | undefined,
- body :: binary() | undefined,
- mfa :: {atom(), atom(), term()} | undefined, % {Module, Function, Args}
- pipeline = queue:new() :: queue:queue(),
- keep_alive = queue:new() :: queue:queue(),
- status :: undefined | new | pipeline | keep_alive | close | {ssl_tunnel, request()},
- canceled = [], % [RequestId]
- max_header_size = nolimit :: nolimit | integer(),
- max_body_size = nolimit :: nolimit | integer(),
- options :: options(),
- timers = #timers{} :: #timers{},
- profile_name :: atom(), % id of httpc_manager process.
- once = inactive :: inactive | once
- }).
+ request :: request() | undefined,
+ session :: session() | undefined,
+ status_line :: tuple() | undefined, % {Version, StatusCode, ReasonPharse}
+ headers :: http_response_h() | undefined,
+ body :: binary() | undefined,
+ mfa :: {atom(), atom(), term()} | undefined, % {Module, Function, Args}
+ pipeline = queue:new() :: queue:queue(),
+ keep_alive = queue:new() :: queue:queue(),
+ status :: undefined | new | pipeline | keep_alive | close | {ssl_tunnel, request()},
+ canceled = [] :: [RequestId::reference()],
+ max_header_size = nolimit :: nolimit | integer(),
+ max_body_size = nolimit :: nolimit | integer(),
+ options :: options(),
+ timers = #timers{} :: #timers{},
+ profile_name :: atom(), % id of httpc_manager process.
+ once = inactive :: inactive | once
+ }).
%%====================================================================
@@ -292,65 +292,35 @@ handle_info(Info, State) ->
%% Function: terminate(Reason, State) -> _ (ignored by gen_server)
%% Description: Shutdown the httpc_handler
%%--------------------------------------------------------------------
-
terminate(normal, #state{session = undefined}) ->
- ok;
-
+ ok;
%% Init error sending, no session information has been setup but
%% there is a socket that needs closing.
-terminate(normal,
- #state{session = #session{id = undefined} = Session}) ->
+terminate(normal,
+ #state{session = #session{id = undefined} = Session}) ->
close_socket(Session);
-
%% Socket closed remotely
-terminate(normal,
- #state{session = #session{socket = {remote_close, Socket},
- socket_type = SocketType,
- id = Id},
- profile_name = ProfileName,
- request = Request,
- timers = Timers,
- pipeline = Pipeline,
- keep_alive = KeepAlive} = State) ->
- %% Clobber session
- (catch httpc_manager:delete_session(Id, ProfileName)),
-
- maybe_retry_queue(Pipeline, State),
- maybe_retry_queue(KeepAlive, State),
-
- %% Cancel timers
+terminate(normal, #state{session = #session{socket = {remote_close, Socket},
+ socket_type = SocketType},
+ request = Request,
+ timers = Timers} = State) ->
+ clobber_and_retry(State),
cancel_timers(Timers),
-
- %% Maybe deliver answers to requests
- deliver_answer(Request),
-
+ maybe_deliver_answer(Request, State),
%% And, just in case, close our side (**really** overkill)
http_transport:close(SocketType, Socket);
-
-terminate(_Reason, #state{session = #session{id = Id,
- socket = Socket,
- socket_type = SocketType},
- request = undefined,
- profile_name = ProfileName,
- timers = Timers,
- pipeline = Pipeline,
- keep_alive = KeepAlive} = State) ->
-
- %% Clobber session
- (catch httpc_manager:delete_session(Id, ProfileName)),
-
- maybe_retry_queue(Pipeline, State),
- maybe_retry_queue(KeepAlive, State),
-
+terminate(_Reason, #state{session = #session{socket = Socket,
+ socket_type = SocketType},
+ request = undefined,
+ timers = Timers} = State) ->
+ clobber_and_retry(State),
cancel_timer(Timers#timers.queue_timer, timeout_queue),
http_transport:close(SocketType, Socket);
-
-terminate(_Reason, #state{request = undefined}) ->
+terminate(_Reason, #state{request = undefined}) ->
ok;
-
-terminate(Reason, #state{request = Request} = State) ->
- NewState = maybe_send_answer(Request,
- httpc_response:error(Request, Reason),
+terminate(Reason, #state{request = Request} = State) ->
+ NewState = maybe_send_answer(Request,
+ httpc_response:error(Request, Reason),
State),
terminate(Reason, NewState#state{request = undefined}).
@@ -706,24 +676,26 @@ call(Msg, Pid) ->
cast(Msg, Pid) ->
gen_server:cast(Pid, Msg).
-maybe_retry_queue(Q, State) ->
- case queue:is_empty(Q) of
- false ->
+maybe_retry_queue(Q, #state{status = new} = State) ->
+ retry_pipeline(queue:to_list(Q), State);
+maybe_retry_queue(Q, #state{request = Request} = State) ->
+ case Request of
+ undefined ->
retry_pipeline(queue:to_list(Q), State);
- true ->
- ok
+ _ ->
+ retry_pipeline(queue:to_list(queue:cons(Request, Q)), State)
end.
-
+
maybe_send_answer(#request{from = answer_sent}, _Reason, State) ->
State;
maybe_send_answer(Request, Answer, State) ->
answer_request(Request, Answer, State).
-deliver_answer(#request{from = From} = Request)
+maybe_deliver_answer(#request{from = From} = Request, #state{status = new})
when From =/= answer_sent ->
Response = httpc_response:error(Request, socket_closed_remotely),
httpc_response:send(From, Response);
-deliver_answer(_Request) ->
+maybe_deliver_answer(_,_) ->
ok.
%%%--------------------------------------------------------------------
@@ -1724,3 +1696,17 @@ format_address({[$[|T], Port}) ->
{Address, Port};
format_address(HostPort) ->
HostPort.
+
+clobber_and_retry(#state{session = #session{id = Id,
+ type = Type},
+ profile_name = ProfileName,
+ pipeline = Pipeline,
+ keep_alive = KeepAlive} = State) ->
+ %% Clobber session
+ (catch httpc_manager:delete_session(Id, ProfileName)),
+ case Type of
+ pipeline ->
+ maybe_retry_queue(Pipeline, State);
+ _ ->
+ maybe_retry_queue(KeepAlive, State)
+ end.
diff --git a/lib/inets/src/http_lib/http_internal.hrl b/lib/inets/src/http_lib/http_internal.hrl
index ca1dad07cd..fbf08f0ca9 100644
--- a/lib/inets/src/http_lib/http_internal.hrl
+++ b/lib/inets/src/http_lib/http_internal.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -31,11 +31,6 @@
-define(HTTP_MAX_METHOD_STRING, 20).
-define(HTTP_MAX_CONTENT_LENGTH, 100000000).
--ifndef(HTTP_DEFAULT_SSL_KIND).
--define(HTTP_DEFAULT_SSL_KIND, essl).
--endif. % -ifdef(HTTP_DEFAULT_SSL_KIND).
-
-
%%% Response headers
-record(http_response_h,{
%%% --- Standard "General" headers
diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl
index 0eb0cc2684..2a51529621 100644
--- a/lib/inets/src/http_lib/http_transport.erl
+++ b/lib/inets/src/http_lib/http_transport.erl
@@ -36,29 +36,19 @@
resolve/0
]).
-export([negotiate/3]).
--export([ipv4_name/1, ipv6_name/1]).
-include_lib("inets/src/inets_app/inets_internal.hrl").
-include("http_internal.hrl").
-
%%%=========================================================================
%%% Internal application API
%%%=========================================================================
-%%-------------------------------------------------------------------------
-%% start(SocketType) -> ok | {error, Reason}
-%% SocketType = ip_comm | {ssl, _}
-%%
-%% Description: Makes sure ssl is started.
-%%-------------------------------------------------------------------------
start(ip_comm) ->
ok;
start({ip_comm, _}) ->
ok;
start({ssl, _}) ->
- do_start_ssl();
-start({essl, _}) ->
do_start_ssl().
do_start_ssl() ->
@@ -70,25 +60,11 @@ do_start_ssl() ->
_:Reason ->
{error, Reason}
end.
-
-
-%%-------------------------------------------------------------------------
-%% connect(SocketType, Address, Options, Timeout) ->
-%% {ok, Socket} | {error, Reason}
-%% SocketType = ip_comm | {ssl, SslConfig}
-%% Address = {Host, Port}
-%% Options = [option()]
-%% Socket = socket()
-%% option() = ipfamily() | {ip, ip_address()} | {port, integer()}
-%% ipfamily() = inet | inet6
-%%
-%% Description: Connects to the Host and Port specified in HTTPRequest.
-%%-------------------------------------------------------------------------
connect(SocketType, Address, Opts) ->
connect(SocketType, Address, Opts, infinity).
connect(ip_comm, {Host, Port}, Opts0, Timeout) ->
- Opts = [binary, {packet, 0}, {active, false}, {reuseaddr, true} | Opts0 ],
+ Opts = [binary, {packet, 0}, {active, false} | Opts0],
try gen_tcp:connect(Host, Port, Opts, Timeout) of
{ok, _} = OK ->
OK;
@@ -101,12 +77,8 @@ connect(ip_comm, {Host, Port}, Opts0, Timeout) ->
{error, {eoptions, Opts}}
end;
-%% Wrapper for backaward compatibillity
-connect({ssl, SslConfig}, Address, Opts, Timeout) ->
- connect({?HTTP_DEFAULT_SSL_KIND, SslConfig}, Address, Opts, Timeout);
-
-connect({essl, SslConfig}, {Host, Port}, Opts0, Timeout) ->
- Opts = [binary, {active, false}, {ssl_imp, new} | Opts0] ++ SslConfig,
+connect({ssl, SslConfig}, {Host, Port}, Opts0, Timeout) ->
+ Opts = [binary, {packet, 0}, {active, false} | Opts0] ++ SslConfig,
case (catch ssl:connect(Host, Port, Opts, Timeout)) of
{'EXIT', Reason} ->
{error, {eoptions, Reason}};
@@ -116,38 +88,19 @@ connect({essl, SslConfig}, {Host, Port}, Opts0, Timeout) ->
ERROR
end.
-
-%%-------------------------------------------------------------------------
-%% listen(SocketType, Addr, Port, Fd) -> {ok, Socket} | {error, Reason}
-%% SocketType = ip_comm | {ssl, SSLConfig}
-%% Port = integer()
-%% Socket = socket()
-%% Fd = undefined | fd()
-%%
-%% Description: Sets up socket to listen on the port Port on the local
-%% host using either gen_tcp or ssl. In the gen_tcp case the port
-%% might already have been initiated by a wrapper-program and is
-%% given as an Fd that can be retrieved by init:get_argument. The
-%% reason for this to enable a HTTP-server not running as root to use
-%% port 80.
-%%-------------------------------------------------------------------------
listen(ip_comm, Addr, Port, Fd, IpFamily) ->
listen_ip_comm(Addr, Port, [], Fd, IpFamily);
listen({ip_comm, SockOpts}, Addr, Port, Fd, IpFamily) ->
listen_ip_comm(Addr, Port, SockOpts, Fd, IpFamily);
-listen({essl, SSLConfig}, Addr, Port, Fd, IpFamily) ->
+listen({ssl, SSLConfig}, Addr, Port, Fd, IpFamily) ->
listen_ssl(Addr, Port, Fd, SSLConfig, IpFamily, []).
listen(ip_comm, Addr, Port, IpFamily) ->
listen_ip_comm(Addr, Port, [], undefined, IpFamily);
-%% Wrapper for backaward compatibillity
listen({ssl, SSLConfig}, Addr, Port, IpFamily) ->
- listen({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Addr, Port, IpFamily);
-
-listen({essl, SSLConfig}, Addr, Port, IpFamily) ->
{SSLConfig2, ExtraOpts} = case proplists:get_value(log_alert, SSLConfig, undefined) of
undefined ->
{SSLConfig, []};
@@ -187,18 +140,7 @@ get_socket_info(Addr, Port, Fd, BaseOpts) ->
Fd ->
{0, sock_opts([{fd, Fd} | BaseOpts])}
end.
-
-%%-------------------------------------------------------------------------
-%% accept(SocketType, ListenSocket) -> {ok, Socket} | {error, Reason}
-%% accept(SocketType, ListenSocket, Timeout) -> ok | {error, Reason}
-%% SocketType = ip_comm | {ssl, SSLConfig}
-%% ListenSocket = socket()
-%% Timeout = infinity | integer() >= 0
-%% Socket = socket()
-%%
-%% Description: Accepts an incoming connection request on a listen socket,
-%% using either gen_tcp or ssl.
-%%-------------------------------------------------------------------------
+
-spec accept(SocketType, ListenSocket) -> {ok, Socket} | {error, Reason} when
SocketType :: ip_comm | {ssl, SSLConfig},
SSLConfig :: term(),
@@ -209,7 +151,7 @@ accept(SocketType, ListenSocket) ->
accept(SocketType, ListenSocket, infinity).
-spec accept(SocketType, ListenSocket, Timeout) -> {ok, Socket} | {error, Reason} when
- SocketType :: ip_comm | {ssl | essl, SSLConfig},
+ SocketType :: ip_comm | {ssl, SSLConfig},
SSLConfig :: term(),
Timeout :: timeout(),
ListenSocket :: gen_tcp:socket(),
@@ -220,19 +162,14 @@ accept(ip_comm, ListenSocket, Timeout) ->
accept({ip_comm, _}, ListenSocket, Timeout) ->
gen_tcp:accept(ListenSocket, Timeout);
-%% Wrapper for backaward compatibillity
-accept({ssl, SSLConfig}, ListenSocket, Timeout) ->
- accept({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, ListenSocket, Timeout);
-
-accept({essl, _SSLConfig}, ListenSocket, Timeout) ->
+accept({ssl, _SSLConfig}, ListenSocket, Timeout) ->
ssl:transport_accept(ListenSocket, Timeout).
-
%%-------------------------------------------------------------------------
%% Description: Assigns a new controlling process to Socket.
%%-------------------------------------------------------------------------
-spec controlling_process(SocketType, Socket, NewOwner) -> Object when
- SocketType :: ip_comm | {ip_comm | ssl | essl, _SSLConfig},
+ SocketType :: ip_comm | {ip_comm | ssl, _Config},
Socket :: gen_tcp:socket(),
NewOwner :: pid(),
Object :: ok | {error, Reason},
@@ -242,11 +179,7 @@ controlling_process(ip_comm, Socket, NewOwner) ->
controlling_process({ip_comm, _}, Socket, NewOwner) ->
gen_tcp:controlling_process(Socket, NewOwner);
-%% Wrapper for backaward compatibillity
-controlling_process({ssl, SSLConfig}, Socket, NewOwner) ->
- controlling_process({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, NewOwner);
-
-controlling_process({essl, _}, Socket, NewOwner) ->
+controlling_process({ssl, _}, Socket, NewOwner) ->
ssl:controlling_process(Socket, NewOwner).
@@ -255,7 +188,7 @@ controlling_process({essl, _}, Socket, NewOwner) ->
%% gen_tcp or ssl.
%%-------------------------------------------------------------------------
-spec setopts(SocketType, Socket, Options) -> ok | {error, inet:posix()} when
- SocketType :: ip_comm | {ip_comm | ssl | essl, _SSLConfig},
+ SocketType :: ip_comm | {ip_comm | ssl, _Config},
Socket :: inet:socket() | ssl:sslsocket(),
Options :: [inet:socket_setopt()] | [gen_tcp:option()].
setopts(ip_comm, Socket, Options) ->
@@ -263,11 +196,7 @@ setopts(ip_comm, Socket, Options) ->
setopts({ip_comm, _}, Socket, Options) ->
inet:setopts(Socket, Options);
-%% Wrapper for backaward compatibillity
-setopts({ssl, SSLConfig}, Socket, Options) ->
- setopts({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Options);
-
-setopts({essl, _}, Socket, Options) ->
+setopts({ssl, _}, Socket, Options) ->
(catch ssl:setopts(Socket, Options)).
@@ -275,7 +204,7 @@ setopts({essl, _}, Socket, Options) ->
%% Description: Gets the values for some options.
%%-------------------------------------------------------------------------
-spec getopts(SocketType, Socket) -> Object when
- SocketType :: ip_comm | {ip_comm | ssl | essl, _SSLConfig},
+ SocketType :: ip_comm | {ip_comm | ssl, _Conf},
Socket :: ssl:sslsocket() | inet:socket(),
Object :: [gen_tcp:option()] | [inet:socket_setopt() | gen_tcp:pktoptions_value()] | [].
getopts(SocketType, Socket) ->
@@ -283,13 +212,10 @@ getopts(SocketType, Socket) ->
getopts(SocketType, Socket, Opts).
-spec getopts(SocketType, Socket, Options) -> Object when
- SocketType :: ip_comm | {ip_comm | ssl | essl, _SSLConfig},
+ SocketType :: ip_comm | {ip_comm | ssl, _Conf},
Socket :: ssl:sslsocket() | inet:socket(),
Options :: [gen_tcp:option_name()],
Object :: [gen_tcp:option()] | [inet:socket_setopt() | gen_tcp:pktoptions_value()] | [].
-getopts({ip_comm, _}, Socket, Options) ->
- getopts(ip_comm, Socket, Options);
-
getopts(ip_comm, Socket, Options) ->
case inet:getopts(Socket, Options) of
{ok, SocketOpts} ->
@@ -297,12 +223,9 @@ getopts(ip_comm, Socket, Options) ->
{error, _} ->
[]
end;
-
-%% Wrapper for backaward compatibillity
-getopts({ssl, SSLConfig}, Socket, Options) ->
- getopts({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Options);
-
-getopts({essl, _}, Socket, Options) ->
+getopts({ip_comm, _}, Socket, Options) ->
+ getopts(ip_comm, Socket, Options);
+getopts({ssl, _}, Socket, Options) ->
getopts_ssl(Socket, Options).
-spec getopts_ssl(SslSocket, Options) ->
@@ -316,15 +239,7 @@ getopts_ssl(Socket, Options) ->
{error, _} ->
[]
end.
-
-%%-------------------------------------------------------------------------
-%% getstat(SocketType, Socket) -> socket_stats()
-%% SocketType = ip_comm | {ssl, _}
-%% Socket = socket()
-%% socket_stats() = list()
-%% Description: Gets the socket stats values for the socket
-%%-------------------------------------------------------------------------
getstat(ip_comm = _SocketType, Socket) ->
case inet:getstat(Socket) of
{ok, Stats} ->
@@ -333,155 +248,67 @@ getstat(ip_comm = _SocketType, Socket) ->
[]
end;
-%% Wrapper for backaward compatibillity
-getstat({ssl, SSLConfig}, Socket) ->
- getstat({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket);
-
-getstat({essl, _} = _SocketType, _Socket) ->
+getstat({ssl, _} = _SocketType, _Socket) ->
[].
-%%-------------------------------------------------------------------------
-%% send(RequestOrSocketType, Socket, Message) -> ok | {error, Reason}
-%% SocketType = ip_comm | {ssl, _}
-%% Socket = socket()
-%% Message = list() | binary()
-%% Description: Sends a packet on a socket, using either gen_tcp or ssl.
-%%-------------------------------------------------------------------------
send(ip_comm, Socket, Message) ->
gen_tcp:send(Socket, Message);
send({ip_comm, _}, Socket, Message) ->
gen_tcp:send(Socket, Message);
-%% Wrapper for backaward compatibillity
-send({ssl, SSLConfig}, Socket, Message) ->
- send({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Message);
-
-send({essl, _}, Socket, Message) ->
+send({ssl, _}, Socket, Message) ->
ssl:send(Socket, Message).
-%%-------------------------------------------------------------------------
-%% close(SocketType, Socket) -> ok | {error, Reason}
-%% SocketType = ip_comm | {ssl, _}
-%% Socket = socket()
-%%
-%% Description: Closes a socket, using either gen_tcp or ssl.
-%%-------------------------------------------------------------------------
close(ip_comm, Socket) ->
gen_tcp:close(Socket);
close({ip_comm, []}, Socket) ->
gen_tcp:close(Socket);
-
-%% Wrapper for backaward compatibillity
-close({ssl, SSLConfig}, Socket) ->
- close({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket);
-
-close({essl, _}, Socket) ->
+close({ssl, _}, Socket) ->
ssl:close(Socket).
-
-%%-------------------------------------------------------------------------
-%% peername(SocketType, Socket) -> {Port, SockName}
-%% SocketType = ip_comm | {ssl, _}
-%% Socket = socket()
-%% Port = integer() (-1 if error occurred)
-%% PeerName = string()
-%%
-%% Description: Returns the address and port for the other end of a
-%% connection, usning either gen_tcp or ssl.
-%%-------------------------------------------------------------------------
peername(ip_comm, Socket) ->
do_peername(inet:peername(Socket));
-peername({ip_comm, _}, Socket) ->
+peername({ip_comm,_}, Socket) ->
do_peername(inet:peername(Socket));
-
-%% Wrapper for backaward compatibillity
-peername({ssl, SSLConfig}, Socket) ->
- peername({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket);
-
-peername({essl, _}, Socket) ->
+peername({ssl, _}, Socket) ->
do_peername(ssl:peername(Socket)).
do_peername({ok, {Addr, Port}})
when tuple_size(Addr) =:= 4 ->
- PeerName = ipv4_name(Addr),
+ PeerName = ip_name(Addr),
{Port, PeerName};
do_peername({ok, {Addr, Port}})
when tuple_size(Addr) =:= 8 ->
- PeerName = ipv6_name(Addr),
+ PeerName = ip_name(Addr),
{Port, PeerName};
do_peername({error, _}) ->
{-1, "unknown"}.
-
-%%-------------------------------------------------------------------------
-%% sockname(SocketType, Socket) -> {Port, SockName}
-%% SocketType = ip_comm | {ssl, _}
-%% Socket = socket()
-%% Port = integer() (-1 if error occurred)
-%% SockName = string()
-%%
-%% Description: Returns the address and port for the local (our) end
-%% other end of connection, using either gen_tcp or ssl.
-%%-------------------------------------------------------------------------
sockname(ip_comm, Socket) ->
do_sockname(inet:sockname(Socket));
-sockname({ip_comm, _}, Socket) ->
+sockname({ip_comm,_}, Socket) ->
do_sockname(inet:sockname(Socket));
-%% Wrapper for backaward compatibillity
-sockname({ssl, SSLConfig}, Socket) ->
- sockname({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket);
-
-sockname({essl, _}, Socket) ->
+sockname({ssl, _}, Socket) ->
do_sockname(ssl:sockname(Socket)).
do_sockname({ok, {Addr, Port}})
when tuple_size(Addr) =:= 4 ->
- SockName = ipv4_name(Addr),
+ SockName = ip_name(Addr),
{Port, SockName};
do_sockname({ok, {Addr, Port}})
when tuple_size(Addr) =:= 8 ->
- SockName = ipv6_name(Addr),
+ SockName = ip_name(Addr),
{Port, SockName};
do_sockname({error, _}) ->
{-1, "unknown"}.
-
-%%-------------------------------------------------------------------------
-%% resolve() -> HostName
-%% HostName = string()
-%%
-%% Description: Returns the local hostname.
-%%-------------------------------------------------------------------------
resolve() ->
{ok, Name} = inet:gethostname(),
Name.
-
-%%-------------------------------------------------------------------------
-%% ipv4_name(Ipv4Addr) -> string()
-%% ipv6_name(Ipv6Addr) -> string()
-%% Ipv4Addr = ip4_address()
-%% Ipv6Addr = ip6_address()
-%%
-%% Description: Returns the local hostname.
-%%-------------------------------------------------------------------------
-ipv4_name({A, B, C, D}) ->
- integer_to_list(A) ++ "." ++
- integer_to_list(B) ++ "." ++
- integer_to_list(C) ++ "." ++
- integer_to_list(D).
-
-ipv6_name({A, B, C, D, E, F, G, H}) ->
- http_util:integer_to_hexlist(A) ++ ":"++
- http_util:integer_to_hexlist(B) ++ ":" ++
- http_util:integer_to_hexlist(C) ++ ":" ++
- http_util:integer_to_hexlist(D) ++ ":" ++
- http_util:integer_to_hexlist(E) ++ ":" ++
- http_util:integer_to_hexlist(F) ++ ":" ++
- http_util:integer_to_hexlist(G) ++ ":" ++
- http_util:integer_to_hexlist(H).
-
+ip_name(Ip) ->
+ inet:ntoa(Ip).
close_tag(ip_comm) ->
tcp_closed;
@@ -510,9 +337,7 @@ negotiate(ip_comm,_,_) ->
ok;
negotiate({ip_comm, _},_,_) ->
ok;
-negotiate({ssl, SSLConfig}, Socket, Timeout) ->
- negotiate({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Timeout);
-negotiate({essl, _}, Socket, Timeout) ->
+negotiate({ssl, _}, Socket, Timeout) ->
negotiate_ssl(Socket, Timeout).
negotiate_ssl(Socket, Timeout) ->
diff --git a/lib/inets/src/http_lib/http_uri.erl b/lib/inets/src/http_lib/http_uri.erl
index d4f352b692..1c5b829134 100644
--- a/lib/inets/src/http_lib/http_uri.erl
+++ b/lib/inets/src/http_lib/http_uri.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -27,12 +27,7 @@
-removed({parse, 2, "use uri_string functions instead"}).
-removed({scheme_defaults, 0, "use uri_string functions instead"}).
--export_type([uri/0]).
-
--type uri() :: string() | binary().
--type hex_uri() :: string() | binary(). %% Hexadecimal encoded URI.
--type maybe_hex_uri() :: string() | binary(). %% A possibly hexadecimal encoded URI.
-
+-removed_type({uri, 0, "use uri_string instead"}).
-removed_type({user_info, 0, "use uri_string instead"}).
-removed_type({scheme, 0, "use uri_string instead"}).
-removed_type({host, 0, "use uri_string instead"}).
@@ -49,7 +44,6 @@ reserved() ->
$#, $[, $], $<, $>, $\", ${, $}, $|, %"
$\\, $', $^, $%, $ ]).
--spec encode(uri()) -> hex_uri().
encode(URI) when is_list(URI) ->
Reserved = reserved(),
lists:append([uri_encode(Char, Reserved) || Char <- URI]);
@@ -57,7 +51,6 @@ encode(URI) when is_binary(URI) ->
Reserved = reserved(),
<< <<(uri_encode_binary(Char, Reserved))/binary>> || <<Char>> <= URI >>.
--spec decode(maybe_hex_uri()) -> uri().
decode(String) when is_list(String) ->
do_decode(String);
decode(String) when is_binary(String) ->
diff --git a/lib/inets/src/http_server/httpd.erl b/lib/inets/src/http_server/httpd.erl
index 54d699f500..0c662522fa 100644
--- a/lib/inets/src/http_server/httpd.erl
+++ b/lib/inets/src/http_server/httpd.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -351,8 +351,6 @@ httpd_ssl_wrapper(Config0) ->
case proplists:get_value(socket_type, Config0) of
{essl, Value} ->
lists:keyreplace(socket_type, 1, Config0, {socket_type, {ssl, Value}});
- {ssl, Value} ->
- lists:keyreplace(socket_type, 1, Config0, {socket_type, {essl, Value}});
_ -> Config0
end.
diff --git a/lib/inets/src/http_server/httpd_acceptor.erl b/lib/inets/src/http_server/httpd_acceptor.erl
index d334b4d233..a933fd5424 100644
--- a/lib/inets/src/http_server/httpd_acceptor.erl
+++ b/lib/inets/src/http_server/httpd_acceptor.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -66,8 +66,7 @@ acceptor_init(Parent, Manager, SocketType, Addr, Port, IpFamily,
acceptor_loop(Manager, SocketType, Addr, Port,
ListenSocket, IpFamily,ConfigDb, AcceptTimeout);
Error ->
- proc_lib:init_ack(Parent, Error),
- error
+ proc_lib:init_fail(Parent, Error, {exit, normal})
end.
do_init(SocketType, Addr, Port, IpFamily) ->
diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl
index 91f2020f77..ffa9a23fec 100644
--- a/lib/inets/src/http_server/httpd_conf.erl
+++ b/lib/inets/src/http_server/httpd_conf.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -206,8 +206,7 @@ validate_config_params([{socket_type, ip_comm} | Rest]) ->
validate_config_params(Rest);
validate_config_params([{socket_type, {Value, Opts}} | Rest]) when Value == ip_comm;
- Value == ssl;
- Value == essl ->
+ Value == ssl ->
%% Make sure not to set socket values used internally
validate_config_params(Opts),
validate_config_params(Rest);
@@ -293,51 +292,6 @@ validate_config_params([{logger, Value} | Rest]) when is_list(Value) ->
validate_config_params([{logger, Value} | _]) ->
throw({logger, Value});
-validate_config_params([{ssl_certificate_file = Key, Value} | Rest]) ->
- ok = httpd_util:file_validate(Key, Value),
- validate_config_params(Rest);
-
-validate_config_params([{ssl_certificate_key_file = Key, Value} | Rest]) ->
- ok = httpd_util:file_validate(Key, Value),
- validate_config_params(Rest);
-
-validate_config_params([{ssl_verify_client, Value} | Rest])
- when (Value =:= 0) orelse (Value =:= 1) orelse (Value =:= 2) ->
- validate_config_params(Rest);
-
-validate_config_params([{ssl_verify_client_depth, Value} | Rest])
- when is_integer(Value) andalso (Value >= 0) ->
- validate_config_params(Rest);
-validate_config_params([{ssl_verify_client_depth, Value} | _]) ->
- throw({ssl_verify_client_depth, Value});
-
-validate_config_params([{ssl_ciphers, Value} | Rest]) when is_list(Value) ->
- validate_config_params(Rest);
-validate_config_params([{ssl_ciphers, Value} | _]) ->
- throw({ssl_ciphers, Value});
-
-validate_config_params([{ssl_ca_certificate_file = Key, Value} | Rest]) ->
- ok = httpd_util:file_validate(Key, Value),
- validate_config_params(Rest);
-
-validate_config_params([{ssl_password_callback_module, Value} | Rest])
- when is_atom(Value) ->
- validate_config_params(Rest);
-validate_config_params([{ssl_password_callback_module, Value} | _]) ->
- throw({ssl_password_callback_module, Value});
-
-validate_config_params([{ssl_password_callback_function, Value} | Rest])
- when is_atom(Value) ->
- validate_config_params(Rest);
-validate_config_params([{ssl_password_callback_function, Value} | _]) ->
- throw({ssl_password_callback_function, Value});
-
-validate_config_params([{ssl_password_callback_arguments, Value} | Rest])
- when is_list(Value) ->
- validate_config_params(Rest);
-validate_config_params([{ssl_password_callback_arguments, Value} | _]) ->
- throw({ssl_password_callback_arguments, Value});
-
validate_config_params([{disable_chunked_transfer_encoding_send, Value} |
Rest])
when (Value =:= true) orelse (Value =:= false) ->
@@ -574,34 +528,9 @@ lookup_socket_type(ConfigDB) ->
{ip_comm, _} = Type ->
Type;
{Tag, Conf} ->
- {Tag, Conf};
- SSL when (SSL =:= ssl) orelse (SSL =:= essl) ->
- SSLTag =
- if
- (SSL =:= ssl) ->
- ?HTTP_DEFAULT_SSL_KIND;
- true ->
- SSL
- end,
- case ssl_certificate_file(ConfigDB) of
- undefined ->
- Reason = "Directive SSLCertificateFile "
- "not found in the config file",
- throw({error, Reason});
- SSLCertificateFile ->
- {SSLTag, SSLCertificateFile ++ ssl_config(ConfigDB)}
- end
+ {Tag, Conf}
end.
-ssl_config(ConfigDB) ->
- ssl_certificate_key_file(ConfigDB) ++
- ssl_verify_client(ConfigDB) ++
- ssl_ciphers(ConfigDB) ++
- ssl_password(ConfigDB) ++
- ssl_verify_depth(ConfigDB) ++
- ssl_ca_certificate_file(ConfigDB) ++
- ssl_log_level(ConfigDB).
-
%%%========================================================================
%%% Internal functions
%%%========================================================================
@@ -703,96 +632,9 @@ remove_traverse(ConfigDB,[Module|Rest]) ->
remove_traverse(ConfigDB,Rest)
end.
-ssl_certificate_file(ConfigDB) ->
- case httpd_util:lookup(ConfigDB,ssl_certificate_file) of
- undefined ->
- undefined;
- SSLCertificateFile ->
- [{certfile,SSLCertificateFile}]
- end.
-
-ssl_certificate_key_file(ConfigDB) ->
- case httpd_util:lookup(ConfigDB,ssl_certificate_key_file) of
- undefined ->
- [];
- SSLCertificateKeyFile ->
- [{keyfile,SSLCertificateKeyFile}]
- end.
-
-ssl_log_level(ConfigDB) ->
- case httpd_util:lookup(ConfigDB,ssl_log_alert) of
- undefined ->
- [];
- SSLLogLevel ->
- [{log_alert,SSLLogLevel}]
- end.
-
-ssl_verify_client(ConfigDB) ->
- case httpd_util:lookup(ConfigDB,ssl_verify_client) of
- undefined ->
- [];
- SSLVerifyClient ->
- [{verify,SSLVerifyClient}]
- end.
-
-ssl_ciphers(ConfigDB) ->
- case httpd_util:lookup(ConfigDB,ssl_ciphers) of
- undefined ->
- [];
- Ciphers ->
- [{ciphers, Ciphers}]
- end.
-
-ssl_password(ConfigDB) ->
- case httpd_util:lookup(ConfigDB,ssl_password_callback_module) of
- undefined ->
- [];
- Module ->
- case httpd_util:lookup(ConfigDB,
- ssl_password_callback_function) of
- undefined ->
- [];
- Function ->
- Args = case httpd_util:lookup(ConfigDB,
- ssl_password_callback_arguments) of
- undefined ->
- [];
- Arguments ->
- [Arguments]
- end,
-
- case catch apply(Module, Function, Args) of
- Password when is_list(Password) ->
- [{password, Password}];
- Error ->
- error_report(ssl_password,Module,Function,Error),
- []
- end
- end
- end.
-
-ssl_verify_depth(ConfigDB) ->
- case httpd_util:lookup(ConfigDB, ssl_verify_client_depth) of
- undefined ->
- [];
- Depth ->
- [{depth, Depth}]
- end.
-
-ssl_ca_certificate_file(ConfigDB) ->
- case httpd_util:lookup(ConfigDB, ssl_ca_certificate_file) of
- undefined ->
- [];
- File ->
- [{cacertfile, File}]
- end.
-
plain_server_tokens() ->
[none, prod, major, minor, minimum, os, full].
-error_report(Where,M,F,Error) ->
- error_logger:error_report([{?MODULE, Where},
- {apply, {M, F, []}}, Error]).
white_space_clean(String) ->
re:replace(String, "^[ \t\n\r\f]*|[ \t\n\r\f]*\$","",
[{return,list}, global]).
diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl
index 8097918e36..ef07bfccb9 100644
--- a/lib/inets/src/http_server/httpd_example.erl
+++ b/lib/inets/src/http_server/httpd_example.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -225,7 +225,7 @@ newformat(SessionID,_,_) ->
%% ------------------------------------------------------
delay(SessionID,_, _) ->
- sleep(10000),
+ sleep(2000),
Reply = delay_reply("delay ok"),
mod_esi:deliver(SessionID, Reply).
diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl
index 26327a2567..3d3191fc7b 100644
--- a/lib/inets/src/http_server/httpd_request_handler.erl
+++ b/lib/inets/src/http_server/httpd_request_handler.erl
@@ -327,16 +327,16 @@ do_terminate(#state{mod = ModData} = State) ->
httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket).
format_status(normal, [_, State]) ->
- [{data, [{"StateData", State}]}];
+ [{data, [{"StateData", State}]}];
format_status(terminate, [_, State]) ->
Mod = (State#state.mod),
case Mod#mod.socket_type of
- ip_comm ->
- [{data, [{"StateData", State}]}];
- {essl, _} ->
- %% Do not print ssl options in superviosr reports
- [{data, [{"StateData",
- State#state{mod = Mod#mod{socket_type = 'TLS'}}}]}]
+ {ssl, _} ->
+ %% Do not print ssl options in supervisor reports
+ [{data, [{"StateData",
+ State#state{mod = Mod#mod{socket_type = 'TLS'}}}]}];
+ _ ->
+ [{data, [{"StateData", State}]}]
end.
%%--------------------------------------------------------------------
diff --git a/lib/inets/src/http_server/httpd_script_env.erl b/lib/inets/src/http_server/httpd_script_env.erl
index d3c7b5e1c6..1a58db9513 100644
--- a/lib/inets/src/http_server/httpd_script_env.erl
+++ b/lib/inets/src/http_server/httpd_script_env.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -61,12 +61,11 @@ which_port(#mod{config_db = ConfigDb}) ->
which_peername(#mod{init_data = #init_data{peername = {_, RemoteAddr}}}) ->
RemoteAddr.
-which_peercert(#mod{socket_type = {Type, _}, socket = Socket}) when Type == essl;
- Type == ssl ->
+which_peercert(#mod{socket_type = {ssl, _}, socket = Socket}) ->
case ssl:peercert(Socket) of
{ok, Cert} ->
Cert;
- {error, no_peercert} ->
+ {error, no_peercert} ->
no_peercert;
_ ->
undefined
diff --git a/lib/inets/src/http_server/httpd_sup.erl b/lib/inets/src/http_server/httpd_sup.erl
index 0d4ad772d4..caf9c8c6f2 100644
--- a/lib/inets/src/http_server/httpd_sup.erl
+++ b/lib/inets/src/http_server/httpd_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -63,7 +63,6 @@ start_child(Config) ->
Error
end.
-
restart_child(Address, Port, Profile) ->
Name = id(Address, Port, Profile),
case supervisor:terminate_child(?MODULE, Name) of
@@ -254,90 +253,12 @@ listen_loop() ->
end.
socket_type(Config) ->
- SocketType = proplists:get_value(socket_type, Config, ip_comm),
- socket_type(SocketType, Config).
-
--spec socket_type(SocketType | Term) -> SocketType when
- Term :: term(),
- SocketType :: ip_comm | {ip_comm, _Value} | {ssl, _Value}.
-socket_type(ip_comm = SocketType, _) ->
- SocketType;
-socket_type({ip_comm, _} = SocketType, _) ->
- SocketType;
-socket_type({essl, _} = SocketType, _) ->
- SocketType;
-socket_type(_, Config) ->
- {essl, ssl_config(Config)}.
-
-%%% Backwards compatibility
-ssl_config(Config) ->
- ssl_certificate_key_file(Config) ++
- ssl_verify_client(Config) ++
- ssl_ciphers(Config) ++
- ssl_password(Config) ++
- ssl_verify_depth(Config) ++
- ssl_ca_certificate_file(Config).
-
-ssl_certificate_key_file(Config) ->
- case proplists:get_value(ssl_certificate_key_file, Config) of
- undefined ->
- [];
- SSLCertificateKeyFile ->
- [{keyfile,SSLCertificateKeyFile}]
- end.
-
-ssl_verify_client(Config) ->
- case proplists:get_value(ssl_verify_client, Config) of
- undefined ->
- [];
- SSLVerifyClient ->
- [{verify,SSLVerifyClient}]
- end.
-
-ssl_ciphers(Config) ->
- case proplists:get_value(ssl_ciphers, Config) of
- undefined ->
- [];
- Ciphers ->
- [{ciphers, Ciphers}]
- end.
-
-ssl_password(Config) ->
- case proplists:get_value(ssl_password_callback_module, Config) of
- undefined ->
- [];
- Module ->
- case proplists:get_value(ssl_password_callback_function, Config) of
- undefined ->
- [];
- Function ->
- Args = case proplists:get_value(ssl_password_callback_arguments, Config) of
- undefined ->
- [];
- Arguments ->
- [Arguments]
- end,
- Password = apply(Module, Function, Args),
- [{password, Password}]
- end
- end.
-
-ssl_verify_depth(Config) ->
- case proplists:get_value(ssl_verify_client_depth, Config) of
- undefined ->
- [];
- Depth ->
- [{depth, Depth}]
- end.
-
-ssl_ca_certificate_file(Config) ->
- case proplists:get_value(ssl_ca_certificate_file, Config) of
- undefined ->
- [];
- File ->
- [{cacertfile, File}]
- end.
-
+ case proplists:get_value(socket_type, Config, ip_comm) of
+ {essl, Value} ->
+ {ssl, Value};
+ Other ->
+ Other
+ end.
-spec get_fd(Port) -> Object when
Port :: integer(),
Object :: {ok, integer() | undefined} | {error, {bad_descriptor, term()}}.
diff --git a/lib/inets/src/http_server/httpd_util.erl b/lib/inets/src/http_server/httpd_util.erl
index 9f176b82d5..ff75303722 100644
--- a/lib/inets/src/http_server/httpd_util.erl
+++ b/lib/inets/src/http_server/httpd_util.erl
@@ -19,31 +19,51 @@
%%
%%
-module(httpd_util).
--export([ip_address/2, lookup/2, lookup/3, multi_lookup/2,
- lookup_mime/2, lookup_mime/3, lookup_mime_default/2,
- lookup_mime_default/3, reason_phrase/1, message/3, rfc1123_date/0,
- rfc1123_date/1, day/1, month/1,
- flatlength/1, split_path/1, split_script_path/1,
- suffix/1, strip_extension_dot/1, split/3, uniq/1,
- make_name/2,make_name/3,make_name/4,strip/1,
- hexlist_to_integer/1,integer_to_hexlist/1,
- convert_request_date/1,create_etag/1,create_etag/2,
- convert_netscapecookie_date/1, enable_debug/1, valid_options/3,
- modules_validate/1, module_validate/1,
- dir_validate/2, file_validate/2, mime_type_validate/1,
- mime_types_validate/1, custom_date/0, error_log/2]).
-
--deprecated({flatlength, 1, "use erlang:iolist_size/1 instead"}).
--deprecated({hexlist_to_integer, 1, "use erlang:list_to_integer/2 with base 16 instead"}).
--deprecated({integer_to_hexlist, 1, "use erlang:integer_to_list/2 with base 16 instead"}).
--deprecated({strip, 1, "use string:trim/1 instead"}).
--deprecated({suffix, 1, "use filename:extension/1 and string:trim/2 instead"}).
--deprecated({decode_hex, 1, "use uri_string:unquote function instead"}).
--deprecated({encode_hex, 1, "use uri_string:quote function instead"}).
-
--compile({nowarn_deprecated_function, [{http_uri, encode, 1}]}).
--compile({nowarn_deprecated_function, [{http_uri, decode, 1}]}).
--export([encode_hex/1, decode_hex/1]).
+-export([ip_address/2,
+ lookup/2,
+ lookup/3,
+ multi_lookup/2,
+ lookup_mime/2,
+ lookup_mime/3,
+ lookup_mime_default/2,
+ lookup_mime_default/3,
+ reason_phrase/1,
+ message/3,
+ rfc1123_date/0,
+ rfc1123_date/1,
+ day/1,
+ month/1,
+ split_path/1,
+ split_script_path/1,
+ strip_extension_dot/1,
+ split/3,
+ uniq/1,
+ make_name/2,
+ make_name/3,
+ make_name/4,
+ convert_request_date/1,
+ create_etag/1,
+ create_etag/2,
+ convert_netscapecookie_date/1,
+ enable_debug/1,
+ valid_options/3,
+ modules_validate/1,
+ module_validate/1,
+ dir_validate/2,
+ file_validate/2,
+ mime_type_validate/1,
+ mime_types_validate/1,
+ custom_date/0,
+ error_log/2]).
+
+-removed({flatlength, 1, "use erlang:iolist_size/1 instead"}).
+-removed({hexlist_to_integer, 1, "use erlang:list_to_integer/2 with base 16 instead"}).
+-removed({integer_to_hexlist, 1, "use erlang:integer_to_list/2 with base 16 instead"}).
+-removed({strip, 1, "use string:trim/1 instead"}).
+-removed({suffix, 1, "use filename:extension/1 and string:trim/2 instead"}).
+-removed({decode_hex, 1, "use uri_string:unquote function instead"}).
+-removed({encode_hex, 1, "use uri_string:quote function instead"}).
+
-include_lib("kernel/include/file.hrl").
-include_lib("inets/include/httpd.hrl").
@@ -396,28 +416,6 @@ month(10) -> "Oct";
month(11) -> "Nov";
month(12) -> "Dec".
-%% decode_hex
-
-decode_hex(URI) ->
- uri_string:unquote(URI).
-
-encode_hex(URI) ->
- SafeChars = "!$()*", %% characters not encoded by deprecated http_uri:encode/1
- uri_string:quote(URI, SafeChars).
-
-%% flatlength
-flatlength(List) ->
- flatlength(List, 0).
-
-flatlength([H|T],L) when is_list(H) ->
- flatlength(H,flatlength(T,L));
-flatlength([H|T],L) when is_binary(H) ->
- flatlength(T,L+byte_size(H));
-flatlength([_H|T],L) ->
- flatlength(T,L+1);
-flatlength([],L) ->
- L.
-
%% split_path, URI has been decoded once when validate
%% and should only be decoded once(RFC3986, 2.4).
@@ -454,16 +452,6 @@ split_script_path(URI) ->
{Path, []}
end.
-%% suffix
-
-suffix(Path) ->
- case filename:extension(Path) of
- [] ->
- [];
- Extension ->
- tl(Extension)
- end.
-
%% strip_extension_dot
strip_extension_dot(Path) ->
case filename:extension(Path) of
@@ -473,17 +461,6 @@ strip_extension_dot(Path) ->
tl(Extension)
end.
-%% strip
-strip(Value)->
- lists:reverse(remove_ws(lists:reverse(remove_ws(Value)))).
-
-remove_ws([$\s|Rest])->
- remove_ws(Rest);
-remove_ws([$\t|Rest]) ->
- remove_ws(Rest);
-remove_ws(Rest) ->
- Rest.
-
%% split
split(String,RegExp,N) ->
@@ -549,22 +526,6 @@ search_and_replace(S,A,B) ->
end,
lists:map(Fun,S).
-
-
-%%----------------------------------------------------------------------
-%% Converts a string that consists of 0-9,A-F,a-f to a
-%% integer
-%%----------------------------------------------------------------------
-
-hexlist_to_integer(List)->
- http_util:hexlist_to_integer(List).
-
-%%----------------------------------------------------------------------
-%%Converts an integer to an hexlist
-%%----------------------------------------------------------------------
-integer_to_hexlist(Num) when is_integer(Num) ->
- http_util:integer_to_hexlist(Num).
-
create_etag(FileInfo) ->
create_etag(FileInfo#file_info.mtime,FileInfo#file_info.size).
diff --git a/lib/inets/src/http_server/mod_alias.erl b/lib/inets/src/http_server/mod_alias.erl
index ef6e928058..e1264deb10 100644
--- a/lib/inets/src/http_server/mod_alias.erl
+++ b/lib/inets/src/http_server/mod_alias.erl
@@ -93,8 +93,9 @@ port_string(Port) ->
get_protocol(ip_comm) ->
"http://";
-get_protocol(_) ->
- %% Should clean up to have only one ssl type essl vs ssl is not relevant any more
+get_protocol({ip_comm, _}) ->
+ "http://";
+get_protocol({ssl, _}) ->
"https://".
%% real_name
diff --git a/lib/inets/src/inets_app/inets.app.src b/lib/inets/src/inets_app/inets.app.src
index cfd963d678..c9b67b6d0c 100644
--- a/lib/inets/src/inets_app/inets.app.src
+++ b/lib/inets/src/inets_app/inets.app.src
@@ -1,7 +1,7 @@
%% This is an -*- erlang -*- file.
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -97,5 +97,7 @@
%% If the "new" ssl is used then 'crypto' must be started before inets.
{applications,[kernel,stdlib]},
{mod,{inets_app,[]}},
- {runtime_dependencies, ["stdlib-4.0","ssl-9.0","runtime_tools-1.8.14",
- "mnesia-4.12","kernel-6.0","erts-6.0", "public_key-1.13"]}]}.
+ {runtime_dependencies,
+ ["stdlib-@OTP-18490@","stdlib-@OTP-18350@","ssl-9.0","runtime_tools-1.8.14",
+ "mnesia-4.12","kernel-@OTP-18350@","erts-@OTP-18350@", "public_key-1.13"]}
+ ]}.
diff --git a/lib/inets/test/http_test_lib.erl b/lib/inets/test/http_test_lib.erl
index f647370f01..f307f8d713 100644
--- a/lib/inets/test/http_test_lib.erl
+++ b/lib/inets/test/http_test_lib.erl
@@ -27,6 +27,7 @@
%% Note: This directive should only be used in test suites.
-compile(export_all).
+-compile(nowarn_export_all).
dummy_server(SocketType, Inet, Extra) ->
dummy_server(self(), SocketType, Inet, Extra).
diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl
index 22bd55355c..f89b06fddd 100644
--- a/lib/inets/test/httpc_SUITE.erl
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
+%%
%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
-%%
+%%
%% 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
@@ -14,12 +14,12 @@
%% 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.
-%%
+%%
%% %CopyrightEnd%
%%
%%
-%%
+%%
%% ct:run("../inets_test", httpc_SUITE).
%%
@@ -32,12 +32,14 @@
-include("http_internal.hrl").
-include("httpc_internal.hrl").
%% Note: This directive should only be used in test suites.
--compile(export_all).
+-compile([export_all, nowarn_export_all]).
-define(URL_START, "http://").
-define(TLS_URL_START, "https://").
-define(NOT_IN_USE_PORT, 8997).
+-define(SSL_NO_VERIFY, {ssl, [{verify, verify_none}]}).
+
%% Using hardcoded file path to keep it below 107 characters
%% (maximum length supported by erlang)
-define(UNIX_SOCKET, "/tmp/inets_httpc_SUITE.sock").
@@ -71,8 +73,10 @@ groups() ->
%% process_leak_on_keepalive is depending on stream_fun_server_close
%% and it shall be the last test case in the suite otherwise cookie
%% will fail.
- {sim_http, [], only_simulated() ++ server_closing_connection() ++ [process_leak_on_keepalive]},
+ {sim_http, [], only_simulated() ++ server_closing_connection() ++
+ [process_leak_on_keepalive]},
{http_internal, [], real_requests_esi()},
+ {http_internal_minimum_bytes, [], [remote_socket_close_parallel]},
{http_unix_socket, [], simulated_unix_socket()},
{https, [], [def_ssl_opt | real_requests()]},
{sim_https, [], only_simulated()},
@@ -219,6 +223,7 @@ sim_mixed() ->
%%--------------------------------------------------------------------
init_per_suite(Config) ->
+ logger:set_primary_config(level, warning),
PrivDir = proplists:get_value(priv_dir, Config),
DataDir = proplists:get_value(data_dir, Config),
inets_test_lib:start_apps([inets]),
@@ -239,14 +244,14 @@ init_per_group(misc = Group, Config) ->
Inet = inet_version(),
ok = httpc:set_options([{ipfamily, Inet}]),
Config;
-
-
init_per_group(Group, Config0) when Group =:= sim_https; Group =:= https;
Group =:= sim_mixed ->
catch crypto:stop(),
try crypto:start() of
ok ->
start_apps(Group),
+ httpc:set_options([{keep_alive_timeout, 50000},
+ {max_keep_alive_length, 5}]),
do_init_per_group(Group, Config0)
catch
_:_ ->
@@ -286,7 +291,7 @@ end_per_group(http_unix_socket, Config) ->
%% it, dummy server waits in gen_tcp:accept and will not process stop request
httpc:request(get, {"http://localhost/v1/kv/foo", []}, [], []),
receive
- {stopped, DummyServerPid} ->
+ {stopped, _DummyServerPid} ->
ok
end,
file:delete(?UNIX_SOCKET),
@@ -319,16 +324,20 @@ init_ssl(Config) ->
ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "client"]),
ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "server"]),
GenCertData =
- public_key:pkix_test_data(#{server_chain =>
- #{root => [{key, inets_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, inets_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, inets_test_lib:hardcode_rsa_key(3)}
- ]},
- client_chain =>
- #{root => [{key, inets_test_lib:hardcode_rsa_key(4)}],
- intermediates => [[{key, inets_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, inets_test_lib:hardcode_rsa_key(6)}]}}),
-
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{key, inets_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, inets_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, inets_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]
+ },
+ client_chain =>
+ #{root => [{key, inets_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256}],
+ intermediates => [[{key, inets_test_lib:hardcode_rsa_key(5)},
+ {digest, sha256}]],
+ peer => [{key, inets_test_lib:hardcode_rsa_key(6)},{digest, sha256}]}}),
Conf = inets_test_lib:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
[{ssl_conf, Conf} | Config].
@@ -345,7 +354,8 @@ init_per_testcase(persistent_connection, Config) ->
{max_keep_alive_length, 3}], persistent),
Config;
-init_per_testcase(wait_for_whole_response, Config) ->
+init_per_testcase(Case, Config) when Case == wait_for_whole_response;
+ Case == remote_socket_close_parallel ->
ct:timetrap({seconds, 60*3}),
Config;
init_per_testcase(Case, Config) when Case == post;
@@ -376,35 +386,31 @@ end_per_testcase(Case, Config)
httpc:request(url(group_name(Config), "/just_close.html", Config)),
ok;
true ->
- ct:pal("Not cleaning up because test case status was ~p", [Status]),
+ ct:log("Not cleaning up because test case status was ~p", [Status]),
ok
end;
-
end_per_testcase(_Case, _Config) ->
ok.
-
-
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
-
head() ->
[{doc, "Test http head request against local server."}].
head(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/dummy.html", Config), []},
- {ok, {{_,200,_}, [_ | _], []}} = httpc:request(head, Request, [], []).
+ {ok, {{_,200,_}, [_ | _], []}} = httpc:request(head, Request, [?SSL_NO_VERIFY], []).
%%--------------------------------------------------------------------
get() ->
[{doc, "Test http get request against local server"}].
get(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/dummy.html", Config), []},
- {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [], []),
+ {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [?SSL_NO_VERIFY], []),
inets_test_lib:check_body(Body),
- {ok, {{_,200,_}, [_ | _], BinBody}} = httpc:request(get, Request, [], [{body_format, binary}]),
+ {ok, {{_,200,_}, [_ | _], BinBody}} = httpc:request(get, Request, [?SSL_NO_VERIFY], [{body_format, binary}]),
true = is_binary(BinBody).
@@ -412,7 +418,7 @@ get_query_string() ->
[{doc, "Test http get request with query string against local server"}].
get_query_string(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/dummy.html?foo=bar", Config), []},
- {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [], []),
+ {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [?SSL_NO_VERIFY], []),
inets_test_lib:check_body(Body).
@@ -421,7 +427,7 @@ get_space() ->
[{"Test http get request with '%20' in the path of the URL."}].
get_space(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/space%20.html", Config), []},
- {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [], []),
+ {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [?SSL_NO_VERIFY], []),
inets_test_lib:check_body(Body).
@@ -445,11 +451,11 @@ post(Config) when is_list(Config) ->
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
httpc:request(post, {URL, [{"expect","100-continue"}],
- "text/plain", Body}, [], []),
+ "text/plain", Body}, [?SSL_NO_VERIFY], []),
{ok, {{_,504,_}, [_ | _], []}} =
httpc:request(post, {URL, [{"expect","100-continue"}],
- "text/plain", "foobar"}, [], []).
+ "text/plain", "foobar"}, [?SSL_NO_VERIFY], []).
%%--------------------------------------------------------------------
delete() ->
[{"Test http delete request against local server. We do in this case "
@@ -468,11 +474,11 @@ delete(Config) when is_list(Config) ->
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
httpc:request(delete, {URL, [{"expect","100-continue"}],
- "text/plain", Body}, [], []),
+ "text/plain", Body}, [?SSL_NO_VERIFY], []),
{ok, {{_,504,_}, [_ | _], []}} =
httpc:request(delete, {URL, [{"expect","100-continue"}],
- "text/plain", "foobar"}, [], []).
+ "text/plain", "foobar"}, [?SSL_NO_VERIFY], []).
%%--------------------------------------------------------------------
patch() ->
@@ -494,7 +500,7 @@ patch(Config) when is_list(Config) ->
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
httpc:request(patch, {URL, [{"expect","100-continue"}],
- "text/plain", Body}, [], []).
+ "text/plain", Body}, [?SSL_NO_VERIFY], []).
%%--------------------------------------------------------------------
post_stream() ->
@@ -522,20 +528,20 @@ post_stream(Config) when is_list(Config) ->
httpc:request(post, {URL,
[{"expect", "100-continue"},
{"content-length", "100"}],
- "text/plain", {BodyFun, 100}}, [], []),
+ "text/plain", {BodyFun, 100}}, [?SSL_NO_VERIFY], []),
{ok, {{_,504,_}, [_ | _], []}} =
httpc:request(post, {URL,
[{"expect", "100-continue"},
{"content-length", "10"}],
- "text/plain", {BodyFun, 10}}, [], []).
+ "text/plain", {BodyFun, 10}}, [?SSL_NO_VERIFY], []).
%%--------------------------------------------------------------------
trace() ->
[{doc, "Perform a TRACE request."}].
trace(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/trace.html", Config), []},
- case httpc:request(trace, Request, [], []) of
+ case httpc:request(trace, Request, [?SSL_NO_VERIFY], []) of
{ok, {{_,200,_}, [_ | _], "TRACE /trace.html" ++ _}} ->
ok;
Other ->
@@ -546,7 +552,7 @@ trace(Config) when is_list(Config) ->
pipeline(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/dummy.html", Config), []},
- {ok, _} = httpc:request(get, Request, [], [], pipeline),
+ {ok, _} = httpc:request(get, Request, [?SSL_NO_VERIFY], [], pipeline),
%% Make sure pipeline session is registered
ct:sleep(4000),
@@ -556,7 +562,7 @@ pipeline(Config) when is_list(Config) ->
persistent_connection(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/dummy.html", Config), []},
- {ok, _} = httpc:request(get, Request, [], [], persistent),
+ {ok, _} = httpc:request(get, Request, [?SSL_NO_VERIFY], [], persistent),
%% Make sure pipeline session is registered
ct:sleep(4000),
@@ -569,7 +575,7 @@ async(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/dummy.html", Config), []},
{ok, RequestId} =
- httpc:request(get, Request, [], [{sync, false}]),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{sync, false}]),
Body =
receive
{http, {RequestId, {{_, 200, _}, _, BinBody}}} ->
@@ -580,7 +586,7 @@ async(Config) when is_list(Config) ->
inets_test_lib:check_body(binary_to_list(Body)),
{ok, NewRequestId} =
- httpc:request(get, Request, [], [{sync, false}]),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{sync, false}]),
ok = httpc:cancel_request(NewRequestId).
%%-------------------------------------------------------------------------
@@ -592,7 +598,7 @@ save_to_file(Config) when is_list(Config) ->
URL = url(group_name(Config), "/dummy.html", Config),
Request = {URL, []},
{ok, saved_to_file}
- = httpc:request(get, Request, [], [{stream, FilePath}]),
+ = httpc:request(get, Request, [?SSL_NO_VERIFY], [{stream, FilePath}]),
{ok, Bin} = file:read_file(FilePath),
{ok, {{_,200,_}, [_ | _], Body}} = httpc:request(URL),
Bin == Body.
@@ -605,7 +611,7 @@ save_to_file_async(Config) when is_list(Config) ->
FilePath = filename:join(PrivDir, "dummy.html"),
URL = url(group_name(Config), "/dummy.html", Config),
Request = {URL, []},
- {ok, RequestId} = httpc:request(get, Request, [],
+ {ok, RequestId} = httpc:request(get, Request, [?SSL_NO_VERIFY],
[{stream, FilePath},
{sync, false}]),
receive
@@ -676,10 +682,10 @@ redirect_multiple_choises(Config) when is_list(Config) ->
URL300 = url(group_name(Config), "/300.html", Config),
catch {ok, {{_,200,_}, [_ | _], [_|_]}}
- = httpc:request(get, {URL300, []}, [], []),
+ = httpc:request(get, {URL300, []}, [?SSL_NO_VERIFY], []),
{ok, {{_,300,_}, [_ | _], _}} =
- httpc:request(get, {URL300, []}, [{autoredirect, false}], []).
+ httpc:request(get, {URL300, []}, [{autoredirect, false},?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
redirect_moved_permanently() ->
[{doc, "The server SHOULD generate a Location header field in the response "
@@ -692,14 +698,14 @@ redirect_moved_permanently(Config) when is_list(Config) ->
URL301 = url(group_name(Config), "/301.html", Config),
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = httpc:request(get, {URL301, []}, [], []),
+ = httpc:request(get, {URL301, []}, [?SSL_NO_VERIFY], []),
{ok, {{_,200,_}, [_ | _], []}}
- = httpc:request(head, {URL301, []}, [], []),
+ = httpc:request(head, {URL301, []}, [?SSL_NO_VERIFY], []),
{ok, {{_,200,_}, [_ | _], [_|_]}}
= httpc:request(post, {URL301, [],"text/plain", "foobar"},
- [], []).
+ [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
redirect_found() ->
[{doc, "The server SHOULD generate a Location header field in the response "
@@ -712,14 +718,14 @@ redirect_found(Config) when is_list(Config) ->
URL302 = url(group_name(Config), "/302.html", Config),
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = httpc:request(get, {URL302, []}, [], []),
+ = httpc:request(get, {URL302, []}, [?SSL_NO_VERIFY], []),
{ok, {{_,200,_}, [_ | _], []}}
- = httpc:request(head, {URL302, []}, [], []),
+ = httpc:request(head, {URL302, []}, [?SSL_NO_VERIFY], []),
{ok, {{_,200,_}, [_ | _], [_|_]}}
= httpc:request(post, {URL302, [],"text/plain", "foobar"},
- [], []).
+ [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
redirect_see_other() ->
[{doc, "The different URI SHOULD be given by the Location field in the response. "
@@ -730,14 +736,14 @@ redirect_see_other(Config) when is_list(Config) ->
URL303 = url(group_name(Config), "/303.html", Config),
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = httpc:request(get, {URL303, []}, [], []),
+ = httpc:request(get, {URL303, []}, [?SSL_NO_VERIFY], []),
{ok, {{_,200,_}, [_ | _], []}}
- = httpc:request(head, {URL303, []}, [], []),
+ = httpc:request(head, {URL303, []}, [?SSL_NO_VERIFY], []),
{ok, {{_,200,_}, [_ | _], [_|_]}}
= httpc:request(post, {URL303, [],"text/plain", "foobar"},
- [], []).
+ [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
redirect_temporary_redirect() ->
[{doc, "The server SHOULD generate a Location header field in the response "
@@ -806,7 +812,7 @@ redirect_loop(Config) when is_list(Config) ->
URL = url(group_name(Config), "/redirectloop.html", Config),
{ok, {{_,300,_}, [_ | _], _}}
- = httpc:request(get, {URL, []}, [], []).
+ = httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
redirect_http_to_https() ->
@@ -818,14 +824,15 @@ redirect_http_to_https(Config) when is_list(Config) ->
Headers = [{"x-test-301-url", TargetUrl}],
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = httpc:request(get, {URL301, Headers}, [], []),
+ = httpc:request(get, {URL301, Headers}, [?SSL_NO_VERIFY], []),
{ok, {{_,200,_}, [_ | _], []}}
- = httpc:request(head, {URL301, Headers}, [], []),
+ = httpc:request(head, {URL301, Headers}, [?SSL_NO_VERIFY], []),
{ok, {{_,200,_}, [_ | _], [_|_]}}
= httpc:request(post, {URL301, Headers, "text/plain", "foobar"},
- [], []).
+ [?SSL_NO_VERIFY], []).
+
%%-------------------------------------------------------------------------
redirect_relative_different_port() ->
[{doc, "Test that a 30X redirect with a relative target, but different "
@@ -859,7 +866,7 @@ cookie(Config) when is_list(Config) ->
Request0 = {url(group_name(Config), "/cookie.html", Config), []},
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = httpc:request(get, Request0, [], []),
+ = httpc:request(get, Request0, [?SSL_NO_VERIFY], []),
%% Populate table to be used by the "dummy" server
ets:new(cookie, [named_table, public, set]),
@@ -868,7 +875,7 @@ cookie(Config) when is_list(Config) ->
Request1 = {url(group_name(Config), "/", Config), []},
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = httpc:request(get, Request1, [], []),
+ = httpc:request(get, Request1, [?SSL_NO_VERIFY], []),
[{session_cookies, [_|_]}] = httpc:which_cookies(httpc:default_profile()),
@@ -887,7 +894,7 @@ cookie_profile(Config) when is_list(Config) ->
Request0 = {url(group_name(Config), "/cookie.html", Config), []},
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = httpc:request(get, Request0, [], [], cookie_test),
+ = httpc:request(get, Request0, [?SSL_NO_VERIFY], [], cookie_test),
%% Populate table to be used by the "dummy" server
ets:new(cookie, [named_table, public, set]),
@@ -896,7 +903,7 @@ cookie_profile(Config) when is_list(Config) ->
Request1 = {url(group_name(Config), "/", Config), []},
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = httpc:request(get, Request1, [], [], cookie_test),
+ = httpc:request(get, Request1, [?SSL_NO_VERIFY], [], cookie_test),
ets:delete(cookie),
inets:stop(httpc, cookie_test).
@@ -910,7 +917,7 @@ empty_set_cookie(Config) when is_list(Config) ->
Request0 = {url(group_name(Config), "/empty_set_cookie.html", Config), []},
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = httpc:request(get, Request0, [], []),
+ = httpc:request(get, Request0, [?SSL_NO_VERIFY], []),
ok = httpc:set_options([{cookies, disabled}]).
@@ -922,7 +929,7 @@ invalid_set_cookie(Config) when is_list(Config) ->
URL = url(group_name(Config), "/invalid_set_cookie.html", Config),
{ok, {{_,200,_}, [_|_], [_|_]}} =
- httpc:request(get, {URL, []}, [], []),
+ httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []),
ok = httpc:set_options([{cookies, disabled}]).
@@ -933,10 +940,10 @@ headers_as_is(Config) when is_list(Config) ->
URL = url(group_name(Config), "/dummy.html", Config),
{ok, {{_,200,_}, [_|_], [_|_]}} =
httpc:request(get, {URL, [{"Host", "localhost"},{"Te", ""}]},
- [], [{headers_as_is, true}]),
+ [?SSL_NO_VERIFY], [{headers_as_is, true}]),
{ok, {{_,400,_}, [_|_], [_|_]}} =
- httpc:request(get, {URL, [{"Te", ""}]},[], [{headers_as_is, true}]).
+ httpc:request(get, {URL, [{"Te", ""}]}, [?SSL_NO_VERIFY], [{headers_as_is, true}]).
%%-------------------------------------------------------------------------
userinfo(doc) ->
@@ -948,12 +955,12 @@ userinfo(Config) when is_list(Config) ->
URLAuth = url(group_name(Config), "alladin:sesame@" ++ Host ++ ":","/userinfo.html", Config),
{ok, {{_,200,_}, [_ | _], _}}
- = httpc:request(get, {URLAuth, []}, [], []),
+ = httpc:request(get, {URLAuth, []}, [?SSL_NO_VERIFY], []),
URLUnAuth = url(group_name(Config), "alladin:foobar@" ++ Host ++ ":","/userinfo.html", Config),
{ok, {{_,401, _}, [_ | _], _}} =
- httpc:request(get, {URLUnAuth, []}, [], []).
+ httpc:request(get, {URLUnAuth, []}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
@@ -962,7 +969,7 @@ page_does_not_exist(doc) ->
page_does_not_exist(Config) when is_list(Config) ->
URL = url(group_name(Config), "/doesnotexist.html", Config),
{ok, {{_,404,_}, [_ | _], [_ | _]}}
- = httpc:request(get, {URL, []}, [], []).
+ = httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
streaming_error(doc) ->
@@ -972,9 +979,9 @@ streaming_error(Config) when is_list(Config) ->
Method = get,
Request = {url(group_name(Config), "/dummy.html", Config), []},
{error, streaming_error} = httpc:request(Method, Request,
- [], [{sync, true}, {stream, {self, once}}]),
+ [?SSL_NO_VERIFY], [{sync, true}, {stream, {self, once}}]),
{error, streaming_error} = httpc:request(Method, Request,
- [], [{sync, true}, {stream, self}]).
+ [?SSL_NO_VERIFY], [{sync, true}, {stream, self}]).
%%-------------------------------------------------------------------------
server_does_not_exist(doc) ->
@@ -984,14 +991,14 @@ server_does_not_exist(Config) when is_list(Config) ->
{error, _} =
httpc:request(get, {"http://localhost:" ++
integer_to_list(?NOT_IN_USE_PORT)
- ++ "/", []},[], []).
+ ++ "/", []},[?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
no_content_204(doc) ->
["Test the case that the HTTP 204 no content header - Solves OTP 6982"];
no_content_204(Config) when is_list(Config) ->
URL = url(group_name(Config), "/no_content.html", Config),
- {ok, {{_,204,_}, [], []}} = httpc:request(URL).
+ {ok, {{_,204,_}, [], []}} = httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
@@ -1000,7 +1007,7 @@ tolerate_missing_CR() ->
"as delimiter. Solves OTP-7304"}].
tolerate_missing_CR(Config) when is_list(Config) ->
URL = url(group_name(Config), "/missing_CR.html", Config),
- {ok, {{_,200,_}, _, [_ | _]}} = httpc:request(URL).
+ {ok, {{_,200,_}, _, [_ | _]}} = httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
empty_body() ->
@@ -1009,7 +1016,7 @@ empty_body() ->
empty_body(Config) when is_list(Config) ->
URL = url(group_name(Config), "/empty.html", Config),
{ok, {{_,200,_}, [_ | _], []}} =
- httpc:request(get, {URL, []}, [], []).
+ httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
@@ -1017,13 +1024,13 @@ transfer_encoding() ->
[{doc, "Transfer encoding is case insensitive. Solves OTP-6807"}].
transfer_encoding(Config) when is_list(Config) ->
URL = url(group_name(Config), "/capital_transfer_encoding.html", Config),
- {ok, {{_,200,_}, [_|_], [_ | _]}} = httpc:request(URL).
+ {ok, {{_,200,_}, [_|_], [_ | _]}} = httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
transfer_encoding_identity(Config) when is_list(Config) ->
URL = url(group_name(Config), "/identity_transfer_encoding.html", Config),
- {ok, {{_,200,_}, [_|_], "IDENTITY"}} = httpc:request(URL).
+ {ok, {{_,200,_}, [_|_], "IDENTITY"}} = httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
@@ -1031,7 +1038,7 @@ empty_response_header() ->
[{doc, "Test the case that the HTTP server does not send any headers. Solves OTP-6830"}].
empty_response_header(Config) when is_list(Config) ->
URL = url(group_name(Config), "/no_headers.html", Config),
- {ok, {{_,200,_}, [], [_ | _]}} = httpc:request(URL).
+ {ok, {{_,200,_}, [], [_ | _]}} = httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
@@ -1043,17 +1050,17 @@ bad_response(Config) when is_list(Config) ->
URL0 = url(group_name(Config), "/missing_crlf.html", Config),
URL1 = url(group_name(Config), "/wrong_statusline.html", Config),
- {error, timeout} = httpc:request(get, {URL0, []}, [{timeout, 400}], []),
- {error, Reason} = httpc:request(URL1),
+ {error, timeout} = httpc:request(get, {URL0, []}, [{timeout, 400},?SSL_NO_VERIFY], []),
+ {error, Reason} = httpc:request(get, {URL1, []}, [?SSL_NO_VERIFY], []),
- ct:print("Wrong Statusline: ~p~n", [Reason]).
+ ct:log("Wrong Statusline: ~p~n", [Reason]).
%%-------------------------------------------------------------------------
timeout_redirect() ->
[{doc, "Test that timeout works for redirects, check ERL-420."}].
timeout_redirect(Config) when is_list(Config) ->
URL = url(group_name(Config), "/redirect_to_missing_crlf.html", Config),
- {error, timeout} = httpc:request(get, {URL, []}, [{timeout, 400}], []).
+ {error, timeout} = httpc:request(get, {URL, []}, [{timeout, 400},?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
@@ -1064,7 +1071,7 @@ internal_server_error(Config) when is_list(Config) ->
URL500 = url(group_name(Config), "/500.html", Config),
{ok, {{_,500,_}, [_ | _], _}}
- = httpc:request(get, {URL500, []}, [], []),
+ = httpc:request(get, {URL500, []}, [?SSL_NO_VERIFY], []),
URL503 = url(group_name(Config), "/503.html", Config),
@@ -1073,12 +1080,12 @@ internal_server_error(Config) when is_list(Config) ->
ets:insert(unavailable, {503, unavailable}),
{ok, {{_,200, _}, [_ | _], [_|_]}} =
- httpc:request(get, {URL503, []}, [], []),
+ httpc:request(get, {URL503, []}, [?SSL_NO_VERIFY], []),
ets:insert(unavailable, {503, long_unavailable}),
{ok, {{_,503, _}, [_ | _], [_|_]}} =
- httpc:request(get, {URL503, []}, [], []),
+ httpc:request(get, {URL503, []}, [?SSL_NO_VERIFY], []),
ets:delete(unavailable).
@@ -1093,9 +1100,9 @@ invalid_http(Config) when is_list(Config) ->
URL = url(group_name(Config), "/invalid_http.html", Config),
{error, {could_not_parse_as_http, _} = Reason} =
- httpc:request(get, {URL, []}, [], []),
+ httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []),
- ct:print("Parse error: ~p ~n", [Reason]).
+ ct:log("Parse error: ~p ~n", [Reason]).
%%-------------------------------------------------------------------------
@@ -1108,9 +1115,9 @@ invalid_chunk_size(Config) when is_list(Config) ->
URL = url(group_name(Config), "/invalid_chunk_size.html", Config),
{error, {chunk_size, _} = Reason} =
- httpc:request(get, {URL, []}, [], []),
+ httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []),
- ct:print("Parse error: ~p ~n", [Reason]).
+ ct:log("Parse error: ~p ~n", [Reason]).
%%-------------------------------------------------------------------------
@@ -1121,10 +1128,10 @@ emulate_lower_versions(Config) when is_list(Config) ->
URL = url(group_name(Config), "/dummy.html", Config),
{ok, {{"HTTP/1.0", 200, _}, [_ | _], Body1 = [_ | _]}} =
- httpc:request(get, {URL, []}, [{version, "HTTP/1.0"}], []),
+ httpc:request(get, {URL, []}, [{version, "HTTP/1.0"}, ?SSL_NO_VERIFY], []),
inets_test_lib:check_body(Body1),
{ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} =
- httpc:request(get, {URL, []}, [{version, "HTTP/1.1"}], []),
+ httpc:request(get, {URL, []}, [{version, "HTTP/1.1"}, ?SSL_NO_VERIFY], []),
inets_test_lib:check_body(Body2).
%%-------------------------------------------------------------------------
@@ -1136,12 +1143,12 @@ relaxed(Config) when is_list(Config) ->
URL = url(group_name(Config), "/missing_reason_phrase.html", Config),
{error, Reason} =
- httpc:request(get, {URL, []}, [{relaxed, false}], []),
+ httpc:request(get, {URL, []}, [{relaxed, false}, ?SSL_NO_VERIFY], []),
- ct:print("Not relaxed: ~p~n", [Reason]),
+ ct:log("Not relaxed: ~p~n", [Reason]),
{ok, {{_, 200, _}, [_ | _], [_ | _]}} =
- httpc:request(get, {URL, []}, [{relaxed, true}], []).
+ httpc:request(get, {URL, []}, [{relaxed, true}, ?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
@@ -1169,7 +1176,7 @@ headers(Config) when is_list(Config) ->
Mod},
{"From","webmaster@erlang.se"},
{"Date", Date}
- ]}, [], []),
+ ]}, [?SSL_NO_VERIFY], []),
Mod1 = httpd_util:rfc1123_date(
calendar:gregorian_seconds_to_datetime(
@@ -1178,7 +1185,7 @@ headers(Config) when is_list(Config) ->
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
httpc:request(get, {URL, [{"If-UnModified-Since",
Mod1}
- ]}, [], []),
+ ]}, [?SSL_NO_VERIFY], []),
Tag = httpd_util:create_etag(FileInfo),
@@ -1186,13 +1193,13 @@ headers(Config) when is_list(Config) ->
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
httpc:request(get, {URL, [{"If-Match",
Tag}
- ]}, [], []),
+ ]}, [?SSL_NO_VERIFY], []),
{ok, {{_,200,_}, [_ | _], _}} =
httpc:request(get, {URL, [{"If-None-Match",
"NotEtag,NeihterEtag"},
{"Connection", "Close"}
- ]}, [], []).
+ ]}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
headers_dummy() ->
["Test the code for handling headers we do not want/can send "
@@ -1251,14 +1258,14 @@ headers_dummy(Config) when is_list(Config) ->
{"Last-Modified", "Sat, 29 Oct 1994 19:43:31 GMT"},
{"Trailer","1#User-Agent"}
], "text/plain", FooBar},
- [], []).
+ [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
headers_with_obs_fold(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/obs_folded_headers.html", Config), []},
- {ok, {{_,200,_}, Headers, [_|_]}} = httpc:request(get, Request, [], []),
+ {ok, {{_,200,_}, Headers, [_|_]}} = httpc:request(get, Request, [?SSL_NO_VERIFY], []),
"a b" = proplists:get_value("folded", Headers).
%%-------------------------------------------------------------------------
@@ -1269,8 +1276,8 @@ headers_conflict_chunked_with_length(doc) ->
"and must receive successful response in relaxed mode"];
headers_conflict_chunked_with_length(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/headers_conflict_chunked_with_length.html", Config), []},
- {error, {could_not_parse_as_http, _}} = httpc:request(get, Request, [{relaxed, false}], []),
- {ok,{{_,200,_},_,_}} = httpc:request(get, Request, [{relaxed, true}], []),
+ {error, {could_not_parse_as_http, _}} = httpc:request(get, Request, [{relaxed, false}, ?SSL_NO_VERIFY], []),
+ {ok,{{_,200,_},_,_}} = httpc:request(get, Request, [{relaxed, true}, ?SSL_NO_VERIFY], []),
ok.
%%-------------------------------------------------------------------------
@@ -1280,13 +1287,13 @@ invalid_headers_key(Config) ->
Request = {url(group_name(Config), "/dummy.html", Config),
[{cookie, "valid cookie"}]},
{error, {headers_error, invalid_field}} =
- httpc:request(get, Request, [], []).
+ httpc:request(get, Request, [?SSL_NO_VERIFY], []).
invalid_headers_value(Config) ->
Request = {url(group_name(Config), "/dummy.html", Config),
[{"cookie", atom_value}]},
{error, {headers_error, invalid_value}} =
- httpc:request(get, Request, [], []).
+ httpc:request(get, Request, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
@@ -1326,7 +1333,7 @@ test_header_type(Config, Method, Value) ->
{Method, Value,
httpc:request(Method,
make_request(Config, Method, Value),
- [],
+ [?SSL_NO_VERIFY],
[])}.
make_request(Config, Method, Value) ->
@@ -1379,11 +1386,14 @@ invalid_method(Config) ->
binary_url(Config) ->
URL = uri_string:normalize(url(group_name(Config), "/dummy.html", Config)),
- {ok, _Response} = httpc:request(unicode:characters_to_binary(URL)).
+ case group_name(Config) of
+ https -> ok;
+ _ -> {ok, _Response} = httpc:request(unicode:characters_to_binary(URL))
+ end.
%%-------------------------------------------------------------------------
-iolist_body(Config) ->
+iolist_body(_Config) ->
{ok, ListenSocket} = gen_tcp:listen(0, [{active,once}, binary]),
{ok,{_,Port}} = inet:sockname(ListenSocket),
@@ -1458,7 +1468,7 @@ invalid_uri(Config) ->
%%-------------------------------------------------------------------------
remote_socket_close(Config) when is_list(Config) ->
URL = url(group_name(Config), "/just_close.html", Config),
- {error, socket_closed_remotely} = httpc:request(URL).
+ {error, socket_closed_remotely} = httpc:request(get, {URL, []}, [?SSL_NO_VERIFY], []).
%%-------------------------------------------------------------------------
@@ -1468,7 +1478,7 @@ remote_socket_close_async(Config) when is_list(Config) ->
Options = [{sync, false}],
Profile = httpc:default_profile(),
{ok, RequestId} =
- httpc:request(get, Request, [], Options, Profile),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], Options, Profile),
receive
{http, {RequestId, {error, socket_closed_remotely}}} ->
ok
@@ -1536,7 +1546,7 @@ inet_opts(Config) when is_list(Config) ->
Request = {url(group_name(Config), "/dummy.html", Config), []},
Timeout = timer:seconds(1),
ConnTimeout = Timeout + timer:seconds(1),
- HttpOptions = [{timeout, Timeout}, {connect_timeout, ConnTimeout}],
+ HttpOptions = [{timeout, Timeout}, {connect_timeout, ConnTimeout}, ?SSL_NO_VERIFY],
Options0 = [{socket_opts, [{tos, 87},
{recbuf, 16#FFFF},
{sndbuf, 16#FFFF}]}],
@@ -1547,7 +1557,7 @@ inet_opts(Config) when is_list(Config) ->
Options1 = [{socket_opts, [{tos, 84},
{recbuf, 32#1FFFF},
{sndbuf, 32#1FFFF}]}],
- {ok, {{_,200,_}, [_ | _], ReplyBody1 = [_ | _]}} = httpc:request(get, Request, [], Options1),
+ {ok, {{_,200,_}, [_ | _], ReplyBody1 = [_ | _]}} = httpc:request(get, Request, [?SSL_NO_VERIFY], Options1),
inets_test_lib:check_body(ReplyBody1).
%%-------------------------------------------------------------------------
@@ -1581,7 +1591,7 @@ timeout_memory_leak(Config) when is_list(Config) ->
{error, timeout} ->
%% And now we check the size of the handler db
Info = httpc:info(),
- ct:print("Info: ~p", [Info]),
+ ct:log("Info: ~p", [Info]),
{value, {handlers, Handlers}} =
lists:keysearch(handlers, 1, Info),
case Handlers of
@@ -1774,7 +1784,7 @@ stream_fun_server_close(Config) when is_list(Config) ->
{ok, RequestId} = httpc:request(get, Request, [], [{sync, false}, {receiver, Fun}]),
receive
{RequestId, {error, Reason}} ->
- ct:pal("Close ~p", [Reason]),
+ ct:log("Close ~p", [Reason]),
ok
after 13000 ->
ct:fail(did_not_receive_close)
@@ -1876,7 +1886,7 @@ post_with_content_type(Config) when is_list(Config) ->
URL = url(group_name(Config), "/delete_no_body.html", Config),
%% Simulated server replies 500 if 'Content-Type' header is present
{ok, {{_,500,_}, _, _}} =
- httpc:request(post, {URL, [], "application/x-www-form-urlencoded", ""}, [], []).
+ httpc:request(post, {URL, [], "application/x-www-form-urlencoded", ""}, [?SSL_NO_VERIFY], []).
%%--------------------------------------------------------------------
request_options() ->
@@ -1899,6 +1909,82 @@ def_ssl_opt(_Config) ->
{'EXIT', _} = catch httpc:ssl_verify_host_options(other),
ok.
+%%-------------------------------------------------------------------------
+remote_socket_close_parallel() ->
+ [{doc,
+ "Verify remote socket closure (related tickets: OTP-18509, OTP-18545,"
+ "ERIERL-937). Transferred data size needs to be significant, so that "
+ "socket is closed, in the middle of a transfer."
+ "Note: test case is require good network and CPU - due to that "
+ " it is not included in all()."}].
+remote_socket_close_parallel(Config0) when is_list(Config0) ->
+ ClientNumber = 200,
+ Config = [{iterations, 10} | Config0],
+ ClientPids =
+ [spawn(?MODULE, connect, [self(), [{client_id, Id} | Config]]) ||
+ Id <- lists:seq(1, ClientNumber)],
+ ct:log("Started ~p clients: ~w", [ClientNumber, ClientPids]),
+ Receive = fun(S) ->
+ receive
+ ok ->
+ ct:log("++ Client finished (~p)", [S]),
+ ok;
+ Other ->
+ ct:fail(Other)
+ end
+ end,
+ [ok = Receive(S) || S <- lists:seq(1, ClientNumber)],
+ ok.
+
+connect(From, Config) ->
+ From ! loop(?config(iterations, Config),
+ io_lib:format("C~p|", [?config(client_id, Config)]),
+ Config).
+
+loop(0, Acc, _Config) ->
+ ct:log("~n~s|", [Acc]),
+ ok;
+loop(Cnt, Acc, Config) ->
+ case request(Config) of
+ {ok, {{_,200,"OK"}, _, _}} ->
+ case process_info(self(), message_queue_len) of
+ {message_queue_len,0} ->
+ loop(Cnt-1, Acc ++ ".", Config);
+ _ ->
+ %% queue is expected to be empty
+ queue_check(),
+ ct:pal("~n~s|", [Acc ++ "x"]),
+ fail
+ end;
+ {ok, NotOk} ->
+ ct:pal("200 OK was not received~n~p", [NotOk]),
+ fail;
+ Error ->
+ ct:pal("Error: ~p",[Error]),
+ fail
+ end.
+
+queue_check() ->
+ receive
+ {http, {ReqId, {_Result, _Head, Data}}} when is_binary(Data) ->
+ ct:pal("Unexpected data received: ~p ",
+ [ReqId]),
+ queue_check();
+ X ->
+ ct:pal("Caught unexpected something else: ~p",[X]),
+ queue_check()
+ after 5000 ->
+ done
+ end.
+
+request(Config) ->
+ Request = {url(group_name(Config), "/httpc_SUITE/foo", Config), []},
+ httpc:request(get, Request, [],[{sync,true}, {body_format,binary}]).
+
+foo(SID, _Env, _Input) ->
+ EightyMillionBits = 80000000, %% ~10MB transferred
+ mod_esi:deliver(SID, [<<0:EightyMillionBits>>]).
+
%%--------------------------------------------------------------------
%% Internal Functions ------------------------------------------------
%%--------------------------------------------------------------------
@@ -1906,7 +1992,7 @@ stream(ReceiverPid, Receiver, Config) ->
Request = {url(group_name(Config), "/dummy.html", Config), []},
Options = [{sync, false}, {receiver, Receiver}],
{ok, RequestId} =
- httpc:request(get, Request, [], Options),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], Options),
Body =
receive
{reply, ReceiverPid, {RequestId, {{_, 200, _}, _, B}}} ->
@@ -1953,9 +2039,9 @@ stream_deliver(ReplyInfo, Type, ReceiverPid) ->
stream_test(Request, To) ->
{ok, {{_,200,_}, [_ | _], Body}} =
- httpc:request(get, Request, [], []),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], []),
{ok, RequestId} =
- httpc:request(get, Request, [], [{sync, false}, To]),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{sync, false}, To]),
StreamedBody =
receive
@@ -1971,9 +2057,9 @@ stream_test(Request, To) ->
not_streamed_test(Request, To) ->
{ok, {{_,Code,_}, [_ | _], Body}} =
- httpc:request(get, Request, [], [{body_format, binary}]),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{body_format, binary}]),
{ok, RequestId} =
- httpc:request(get, Request, [], [{body_format, binary}, {sync, false}, To]),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{body_format, binary}, {sync, false}, To]),
receive
{http, {RequestId, {{_, Code, _}, _Headers, Body}}} ->
@@ -1993,12 +2079,13 @@ url(https, End, Config) ->
Port = proplists:get_value(port, Config),
{ok,Host} = inet:gethostname(),
?TLS_URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ End;
-url(sim_http, End, Config) ->
- url(http, End, Config);
-url(http_internal, End, Config) ->
+url(Group, End, Config) when Group == sim_http;
+ Group == http_internal;
+ Group == http_internal_minimum_bytes ->
url(http, End, Config);
url(sim_https, End, Config) ->
url(https, End, Config).
+
url(http, UserInfo, End, Config) ->
Port = proplists:get_value(port, Config),
?URL_START ++ UserInfo ++ integer_to_list(Port) ++ End;
@@ -2063,39 +2150,32 @@ server_start(_, HttpdConfig) ->
{value, {_, _, Info}} = lists:keysearch(Pid, 2, Serv),
proplists:get_value(port, Info).
-server_config(http, Config) ->
+server_config(base, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
[{port, 0},
{server_name,"httpc_test"},
{server_root, ServerRoot},
{document_root, proplists:get_value(doc_root, Config)},
- {bind_address, any},
- {ipfamily, inet_version()},
- {mime_type, "text/plain"},
- {script_alias, {"/cgi-bin/", filename:join(ServerRoot, "cgi-bin") ++ "/"}}
- ];
-server_config(http_ipv6, Config) ->
+ {mime_type, "text/plain"}];
+server_config(base_http, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
- [{port, 0},
- {server_name,"httpc_test"},
- {server_root, ServerRoot},
- {document_root, proplists:get_value(doc_root, Config)},
- {bind_address, {0,0,0,0,0,0,0,1}},
- {ipfamily, inet6},
- {mime_type, "text/plain"},
- {script_alias, {"/cgi-bin/", filename:join(ServerRoot, "cgi-bin") ++ "/"}}
- ];
+ server_config(base, Config) ++
+ [{script_alias,
+ {"/cgi-bin/", filename:join(ServerRoot, "cgi-bin") ++ "/"}}];
+server_config(http, Config) ->
+ server_config(base_http, Config) ++
+ [{bind_address, any},
+ {ipfamily, inet_version()}];
+server_config(http_ipv6, Config) ->
+ server_config(base_http, Config) ++
+ [{bind_address, {0,0,0,0,0,0,0,1}},
+ {ipfamily, inet6}];
server_config(http_internal, Config) ->
- ServerRoot = proplists:get_value(server_root, Config),
- [{port, 0},
- {server_name,"httpc_test"},
- {server_root, ServerRoot},
- {document_root, proplists:get_value(doc_root, Config)},
- {bind_address, any},
- {ipfamily, inet_version()},
- {mime_type, "text/plain"},
- {erl_script_alias, {"", [httpc_SUITE]}}
- ];
+ server_config(http, Config) ++
+ [{erl_script_alias, {"", [httpc_SUITE]}}];
+server_config(http_internal_minimum_bytes, Config) ->
+ server_config(http_internal, Config) ++
+ [{minimum_bytes_per_second, 100}];
server_config(https, Config) ->
[{socket_type, {ssl, ssl_config(Config)}} | server_config(http, Config)];
server_config(sim_https, Config) ->
@@ -2103,7 +2183,6 @@ server_config(sim_https, Config) ->
server_config(http_unix_socket, _Config) ->
Socket = ?UNIX_SOCKET,
[{unix_socket, Socket}];
-
server_config(_, _) ->
[].
@@ -2156,23 +2235,23 @@ setup_server_dirs(ServerRoot, DocRoot, DataDir) ->
keep_alive_requests(Request, Profile) ->
{ok, RequestIdA0} =
- httpc:request(get, Request, [], [{sync, false}], Profile),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{sync, false}], Profile),
{ok, RequestIdA1} =
- httpc:request(get, Request, [], [{sync, false}], Profile),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{sync, false}], Profile),
{ok, RequestIdA2} =
- httpc:request(get, Request, [], [{sync, false}], Profile),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{sync, false}], Profile),
receive_replys([RequestIdA0, RequestIdA1, RequestIdA2]),
{ok, RequestIdB0} =
- httpc:request(get, Request, [], [{sync, false}], Profile),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{sync, false}], Profile),
{ok, RequestIdB1} =
- httpc:request(get, Request, [], [{sync, false}], Profile),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{sync, false}], Profile),
{ok, RequestIdB2} =
- httpc:request(get, Request, [], [{sync, false}], Profile),
+ httpc:request(get, Request, [?SSL_NO_VERIFY], [{sync, false}], Profile),
ok = httpc:cancel_request(RequestIdB1, Profile),
- ct:print("Cancel ~p~n", [RequestIdB1]),
+ ct:log("Cancel ~p~n", [RequestIdB1]),
receive_replys([RequestIdB0, RequestIdB2]).
@@ -2340,7 +2419,7 @@ handle_request(Module, Function, Args, Socket) ->
end.
handle_http_msg({Method, RelUri, _, {_, Headers}, Body}, Socket, _) ->
- ct:print("Request: ~p ~p", [Method, RelUri]),
+ ct:log("Request: ~p ~p", [Method, RelUri]),
NextRequest =
case RelUri of
@@ -2387,9 +2466,9 @@ handle_http_msg({Method, RelUri, _, {_, Headers}, Body}, Socket, _) ->
_ when is_list(Msg) orelse is_binary(Msg) ->
case Msg of
[] ->
- ct:print("Empty Msg", []);
+ ct:log("Empty Msg", []);
_ ->
- ct:print("Response: ~p", [Msg]),
+ ct:log("Response: ~p", [Msg]),
send(Socket, Msg)
end
end,
@@ -2449,10 +2528,10 @@ content_type_header([_|T]) ->
handle_auth("Basic " ++ UserInfo, Challenge, DefaultResponse) ->
case string:tokens(base64:decode_to_string(UserInfo), ":") of
["alladin", "sesame"] = Auth ->
- ct:print("Auth: ~p~n", [Auth]),
+ ct:log("Auth: ~p~n", [Auth]),
DefaultResponse;
Other ->
- ct:print("UnAuth: ~p~n", [Other]),
+ ct:log("UnAuth: ~p~n", [Other]),
Challenge
end.
@@ -2965,7 +3044,7 @@ receive_streamed_body(RequestId, Body) ->
receive_streamed_body(RequestId, Body, Pid) ->
httpc:stream_next(Pid),
- ct:print("~p:receive_streamed_body -> requested next stream ~n", [?MODULE]),
+ ct:log("~p:receive_streamed_body -> requested next stream ~n", [?MODULE]),
receive
{http, {RequestId, stream, BinBodyPart}} ->
%% Make sure the httpc hasn't sent us the next 'stream'
@@ -3021,19 +3100,19 @@ run_clients(NumClients, ServerPort, SeqNumServer) ->
fun() ->
case httpc:request(Url) of
{ok, {{_,200,_}, _, Resp}} ->
- ct:print("[~w] 200 response: "
+ ct:log("[~w] 200 response: "
"~p~n", [Id, Resp]),
case lists:prefix(Req++"->", Resp) of
true -> exit(normal);
false -> exit({bad_resp,Req,Resp})
end;
{ok, {{_,EC,Reason},_,Resp}} ->
- ct:print("[~w] ~w response: "
+ ct:pal("[~w] ~w response: "
"~s~n~s~n",
[Id, EC, Reason, Resp]),
exit({bad_resp,Req,Resp});
Crap ->
- ct:print("[~w] bad response: ~p",
+ ct:pal("[~w] bad response: ~p",
[Id, Crap]),
exit({bad_resp, Req, Crap})
end
@@ -3111,14 +3190,14 @@ loop_client(N, CSock, SeqNumServer) ->
Response = lists:flatten(io_lib:format("~s->resp~3..0w/~2..0w", [ReqNum, RespSeqNum, N])),
Txt = lists:flatten(io_lib:format("Slow server (~p) got ~p, answering with ~p",
[self(), Req, Response])),
- ct:print("~s...~n", [Txt]),
+ ct:log("~s...~n", [Txt]),
slowly_send_response(CSock, Response),
case parse_connection_type(Req) of
keep_alive ->
- ct:print("~s...done~n", [Txt]),
+ ct:log("~s...done~n", [Txt]),
loop_client(N+1, CSock, SeqNumServer);
close ->
- ct:print("~s...done (closing)~n", [Txt]),
+ ct:log("~s...done (closing)~n", [Txt]),
gen_tcp:close(CSock)
end
end.
@@ -3168,7 +3247,7 @@ otp_8739(Config) when is_list(Config) ->
{error, timeout} ->
%% And now we check the size of the handler db
Info = httpc:info(),
- ct:print("Info: ~p", [Info]),
+ ct:log("Info: ~p", [Info]),
{value, {handlers, Handlers}} =
lists:keysearch(handlers, 1, Info),
case Handlers of
@@ -3235,7 +3314,7 @@ receive_stream_n(Ref, N) ->
{http, {Ref, stream_start, _}} ->
receive_stream_n(Ref, N);
{http, {Ref,stream, Data}} ->
- ct:pal("Data: ~p", [Data]),
+ ct:log("Data: ~p", [Data]),
receive_stream_n(Ref, N-1)
end.
diff --git a/lib/inets/test/httpc_proxy_SUITE.erl b/lib/inets/test/httpc_proxy_SUITE.erl
index 02751dfdf4..f7a97c09a9 100644
--- a/lib/inets/test/httpc_proxy_SUITE.erl
+++ b/lib/inets/test/httpc_proxy_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -42,6 +42,8 @@
[self(),?MODULE] ++ begin A end)
end).
+-define(SSL_NO_VERIFY, {ssl, [{verify, verify_none}]}).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -159,7 +161,7 @@ http_head(Config) when is_list(Config) ->
Method = head,
URL = url("/index.html", Config),
Request = {URL,[]},
- HttpOpts = [],
+ HttpOpts = [?SSL_NO_VERIFY],
Opts = [],
{ok,{{_,200,_},[_|_],[]}} =
httpc:request(Method, Request, HttpOpts, Opts),
@@ -176,13 +178,13 @@ http_get(Config) when is_list(Config) ->
Timeout = timer:seconds(1),
ConnTimeout = Timeout + timer:seconds(1),
- HttpOpts1 = [{timeout,Timeout},{connect_timeout,ConnTimeout}],
+ HttpOpts1 = [{timeout,Timeout},{connect_timeout,ConnTimeout},?SSL_NO_VERIFY],
Opts1 = [],
{ok,{{_,200,_},[_|_],[_|_]=B1}} =
httpc:request(Method, Request, HttpOpts1, Opts1),
inets_test_lib:check_body(B1),
- HttpOpts2 = [],
+ HttpOpts2 = [?SSL_NO_VERIFY],
Opts2 = [{body_format,binary}],
{ok,{{_,200,_},[_|_],B2}} =
httpc:request(Method, Request, HttpOpts2, Opts2),
@@ -196,7 +198,7 @@ http_options(Config) when is_list(Config) ->
Method = options,
URL = url("/index.html", Config),
Request = {URL,[]},
- HttpOpts = [],
+ HttpOpts = [?SSL_NO_VERIFY],
Opts = [],
{ok,{{_,200,_},Headers,_}} =
httpc:request(Method, Request, HttpOpts, Opts),
@@ -211,7 +213,7 @@ http_trace(Config) when is_list(Config) ->
Method = trace,
URL = url("/index.html", Config),
Request = {URL,[]},
- HttpOpts = [],
+ HttpOpts = [?SSL_NO_VERIFY],
Opts = [],
{ok,{{_,200,_},[_|_],"TRACE "++_}} =
httpc:request(Method, Request, HttpOpts, Opts),
@@ -227,7 +229,7 @@ http_post(Config) when is_list(Config) ->
Method = post,
URL = url("/index.html", Config),
Request = {URL,[],"text/plain","foobar"},
- HttpOpts = [],
+ HttpOpts = [?SSL_NO_VERIFY],
Opts = [],
{ok,{{_,200,_},[_|_],[_|_]}} =
httpc:request(Method, Request, HttpOpts, Opts),
@@ -244,7 +246,7 @@ http_put(Config) when is_list(Config) ->
Content =
"<html><body> <h1>foo</h1> <p>bar</p> </body></html>",
Request = {URL,[],"html",Content},
- HttpOpts = [],
+ HttpOpts = [?SSL_NO_VERIFY],
Opts = [],
{ok,{{_,405,_},[_|_],[_|_]}} =
httpc:request(Method, Request, HttpOpts, Opts),
@@ -260,7 +262,7 @@ http_delete(Config) when is_list(Config) ->
Method = delete,
URL = url("/delete.html", Config),
Request = {URL,[]},
- HttpOpts = [],
+ HttpOpts = [?SSL_NO_VERIFY],
Opts = [],
{ok,{{_,405,_},[_|_],[_|_]}} =
httpc:request(Method, Request, HttpOpts, Opts),
@@ -276,7 +278,7 @@ http_delete_body(Config) when is_list(Config) ->
URL = url("/delete.html", Config),
Content = "foo=bar",
Request = {URL,[],"application/x-www-form-urlencoded",Content},
- HttpOpts = [],
+ HttpOpts = [?SSL_NO_VERIFY],
Opts = [],
{ok,{{_,405,_},[_|_],[_|_]}} =
httpc:request(Method, Request, HttpOpts, Opts),
@@ -302,7 +304,7 @@ http_headers(Config) when is_list(Config) ->
{"Referer",
"http://otp.ericsson.se:8000/product/internal"}],
Request = {URL,Headers},
- HttpOpts = [],
+ HttpOpts = [?SSL_NO_VERIFY],
Opts = [],
{ok,{{_,200,_},[_|_],[_|_]}} =
httpc:request(Method, Request, HttpOpts, Opts),
@@ -319,7 +321,7 @@ http_proxy_auth(Config) when is_list(Config) ->
Method = get,
URL = url("/index.html", Config),
Request = {URL,[]},
- HttpOpts = [{proxy_auth,{"foo","bar"}}],
+ HttpOpts = [{proxy_auth,{"foo","bar"}}, ?SSL_NO_VERIFY],
Opts = [],
{ok,{{_,200,_},[_|_],[_|_]}} =
httpc:request(Method, Request, HttpOpts, Opts),
@@ -333,7 +335,7 @@ http_doesnotexist(Config) when is_list(Config) ->
Method = get,
URL = url("/doesnotexist.html", Config),
Request = {URL,[]},
- HttpOpts = [{proxy_auth,{"foo","bar"}}],
+ HttpOpts = [{proxy_auth,{"foo","bar"}}, ?SSL_NO_VERIFY],
Opts = [],
{ok,{{_,404,_},[_|_],[_|_]}} =
httpc:request(Method, Request, HttpOpts, Opts),
@@ -347,7 +349,7 @@ http_stream(Config) when is_list(Config) ->
Method = get,
URL = url("/index.html", Config),
Request = {URL,[]},
- HttpOpts = [],
+ HttpOpts = [?SSL_NO_VERIFY],
Opts1 = [{body_format,binary}],
{ok,{{_,200,_},[_|_],Body}} =
@@ -385,12 +387,12 @@ http_emulate_lower_versions(Config) when is_list(Config) ->
Request = {URL,[]},
Opts = [],
- HttpOpts1 = [{version,"HTTP/1.0"}],
+ HttpOpts1 = [{version,"HTTP/1.0"},?SSL_NO_VERIFY],
{ok,{{_,200,_},[_|_],[_|_]=B2}} =
httpc:request(Method, Request, HttpOpts1, Opts),
inets_test_lib:check_body(B2),
- HttpOpts2 = [{version,"HTTP/1.1"}],
+ HttpOpts2 = [{version,"HTTP/1.1"},?SSL_NO_VERIFY],
{ok,{{_,200,_},[_|_],[_|_]=B3}} =
httpc:request(Method, Request, HttpOpts2, Opts),
inets_test_lib:check_body(B3),
@@ -406,7 +408,7 @@ http_not_modified_otp_6821(Config) when is_list(Config) ->
Opts = [],
Request1 = {URL,[]},
- HttpOpts1 = [],
+ HttpOpts1 = [?SSL_NO_VERIFY],
{ok,{{_,200,_},ReplyHeaders,[_|_]}} =
httpc:request(Method, Request1, HttpOpts1, Opts),
ETag = header_value("etag", ReplyHeaders),
@@ -416,7 +418,7 @@ http_not_modified_otp_6821(Config) when is_list(Config) ->
{URL,
[{"If-None-Match",ETag},
{"If-Modified-Since",LastModified}]},
- HttpOpts2 = [{timeout,15000}], % Limit wait for bug result
+ HttpOpts2 = [{timeout,15000}, ?SSL_NO_VERIFY], % Limit wait for bug result
{ok,{{_,304,_},_,[]}} = % Page Unchanged
httpc:request(Method, Request2, HttpOpts2, Opts),
@@ -440,7 +442,7 @@ https_connect_error(Config) when is_list(Config) ->
URL = "https://" ++ HttpServer ++ ":" ++
integer_to_list(HttpPort) ++ "/index.html",
Opts = [],
- HttpOpts = [],
+ HttpOpts = [?SSL_NO_VERIFY],
Request = {URL,[]},
{error,{failed_connect,[_,{tls,_,_}]}} =
httpc:request(Method, Request, HttpOpts, Opts).
@@ -453,7 +455,7 @@ http_timeout(Config) when is_list(Config) ->
URL = url("/index.html", Config),
Request = {URL,[]},
Timeout = timer:seconds(1),
- HttpOpts1 = [{timeout, Timeout}, {connect_timeout, 0}],
+ HttpOpts1 = [{timeout, Timeout}, {connect_timeout, 0}, ?SSL_NO_VERIFY],
{error,
{failed_connect,
[{to_address,{"localhost",8000}},
@@ -510,14 +512,14 @@ make_cert_files(Config) ->
ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "server"]),
GenCertData =
public_key:pkix_test_data(#{server_chain =>
- #{root => [{key, inets_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, inets_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, inets_test_lib:hardcode_rsa_key(3)}
+ #{root => [{key, inets_test_lib:hardcode_rsa_key(1)}, {digest, sha256}],
+ intermediates => [[{key, inets_test_lib:hardcode_rsa_key(2)}, {digest, sha256}]],
+ peer => [{key, inets_test_lib:hardcode_rsa_key(3)}, {digest, sha256}
]},
client_chain =>
- #{root => [{key, inets_test_lib:hardcode_rsa_key(4)}],
- intermediates => [[{key, inets_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, inets_test_lib:hardcode_rsa_key(6)}]}}),
+ #{root => [{key, inets_test_lib:hardcode_rsa_key(4)},{digest, sha256}],
+ intermediates => [[{key, inets_test_lib:hardcode_rsa_key(5)}, {digest, sha256}]],
+ peer => [{key, inets_test_lib:hardcode_rsa_key(6)}, {digest, sha256}]}}),
inets_test_lib:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
ok.
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index 1df6251d41..df628257ec 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
+%%
%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
-%%
+%%
%% 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
@@ -14,12 +14,12 @@
%% 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.
-%%
+%%
%% %CopyrightEnd%
%%
%%
-%%
+%%
%% ct:run("../inets_test", httpd_SUITE).
%%
-compile({no_auto_import,[alias/1]}).
@@ -38,7 +38,7 @@
-record(httpd_group, {group_name, userlist}).
-define(MAX_HEADER_SIZE, 256).
%% Minutes before failed auths timeout.
--define(FAIL_EXPIRE_TIME,1).
+-define(FAIL_EXPIRE_TIME,1).
%% Seconds before successful auths timeout.
-define(AUTH_TIMEOUT,5).
-define(URL_START, "http://").
@@ -68,7 +68,7 @@ all() ->
{group, https_auth_api_dets},
{group, http_auth_api_mnesia},
{group, https_auth_api_mnesia},
- {group, http_security},
+ {group, http_security},
{group, https_security},
{group, http_reload},
{group, https_reload},
@@ -100,7 +100,7 @@ groups() ->
{https_auth_api, [], [{group, auth_api}]},
{http_auth_api_dets, [], [{group, auth_api_dets}]},
{https_auth_api_dets, [], [{group, auth_api_dets}]},
- {http_auth_api_mnesia, [], [{group, auth_api_mnesia}]},
+ {http_auth_api_mnesia, [], [{group, auth_api_mnesia}]},
{https_auth_api_mnesia, [], [{group, auth_api_mnesia}]},
{http_security, [], [{group, security}]},
{https_security, [], [{group, security}]},
@@ -112,11 +112,11 @@ groups() ->
{https_not_sup, [], [{group, not_sup}]},
{https_alert, [], [tls_alert]},
{http_mime_types, [], [alias_1_1, alias_1_0]},
- {limit, [], [content_length, max_clients_1_1]},
- {custom, [], [customize, add_default]},
+ {limit, [], [content_length, max_clients_1_1]},
+ {custom, [], [customize, add_default]},
{reload, [], [non_disturbing_reconfiger_dies,
disturbing_reconfiger_dies,
- non_disturbing_1_1,
+ non_disturbing_1_1,
non_disturbing_1_0,
disturbing_1_1,
disturbing_1_0,
@@ -148,12 +148,12 @@ basic_groups ()->
http_head() ->
[head].
http_get() ->
- [alias,
- get,
+ [alias,
+ get,
bad_dot_paths,
%%actions, Add configuration so that this test mod_action
- esi,
- bad_hex,
+ esi,
+ bad_hex,
missing_CR,
max_header,
max_content_length,
@@ -164,10 +164,10 @@ http_get() ->
load() ->
- [light, medium
+ [light, medium
%%,heavy
- ].
-
+ ].
+
init_per_suite(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
DataDir = proplists:get_value(data_dir, Config),
@@ -181,7 +181,7 @@ init_per_suite(Config) ->
logger:add_handler_filter(default, inets_httpd, {fun logger_filters:domain/2,
{log, equal,[otp,inets, httpd, httpd_test, error]}}),
%%logger:set_handler_config(default, formatter, {logger_formatter, #{}}),
- Inet =
+ Inet =
case (catch ct:get_config(ipv6_hosts)) of
undefined ->
inet;
@@ -195,11 +195,11 @@ init_per_suite(Config) ->
_ ->
inet
end,
- [{server_root, ServerRoot},
+ [{server_root, ServerRoot},
{doc_root, DocRoot},
{ipfamily, Inet},
{node, node()},
- {host, inets_test_lib:hostname()},
+ {host, inets_test_lib:hostname()},
{address, getaddr()} | Config].
end_per_suite(_Config) ->
@@ -225,7 +225,7 @@ init_per_group(Group, Config0) when Group == https_basic;
catch
_:_ ->
{skip, "Crypto did not start"}
- end;
+ end;
init_per_group(Group, Config0) when Group == http_basic;
Group == http_limit;
Group == http_custom;
@@ -395,7 +395,7 @@ dbg(Case, Config, Status) ->
Config;
'end' ->
io:format("dbg: stopped~n"),
- dbg:stop_clear(),
+ dbg:stop(),
ok
end;
false ->
@@ -1351,7 +1351,7 @@ ignore_invalid_header(Config) when is_list(Config) ->
ssl ->
Conf = proplists:get_value(client_config, proplists:get_value(ssl_conf, Config)),
{"https://" ++ Host ++ ":" ++ integer_to_list(Port) ++ "/cgi-bin/erl/httpd_example:ignore_invalid_header",
- [{"Host", "localhost"},{"Te", ""}, {"Content-Length ", "0"}], [{ssl, Conf}]}
+ [{"Host", "localhost"},{"Te", ""}, {"Content-Length ", "0"}], [{ssl, [{verify, verify_none} | Conf]}]}
end,
{ok,{{_,204,_}, _, _}}
= httpc:request(get, {Url, Header}, [{timeout, 45000} | Opts], [{headers_as_is, true}]).
@@ -1942,7 +1942,7 @@ do_max_clients(Config) ->
ok
end,
inets_test_lib:close(Type, Socket),
- ct:sleep(100), %% Avoid possible timing issues
+ ct:sleep(5000), %% Avoid possible timing issues
ok = httpd_test_lib:verify_request(Type, Host,
Port,
transport_opts(Type, Config),
@@ -1951,7 +1951,7 @@ do_max_clients(Config) ->
[{statuscode, 200},
{version, Version}]).
-setup_server_dirs(ServerRoot, DocRoot, DataDir) ->
+setup_server_dirs(ServerRoot, DocRoot, DataDir) ->
CgiDir = filename:join(ServerRoot, "cgi-bin"),
AuthDir = filename:join(ServerRoot, "auth"),
PicsDir = filename:join(ServerRoot, "icons"),
@@ -1964,33 +1964,33 @@ setup_server_dirs(ServerRoot, DocRoot, DataDir) ->
ok = file:make_dir(PicsDir),
ok = file:make_dir(ConfigDir),
- DocSrc = filename:join(DataDir, "server_root/htdocs"),
- AuthSrc = filename:join(DataDir, "server_root/auth"),
- CgiSrc = filename:join(DataDir, "server_root/cgi-bin"),
- PicsSrc = filename:join(DataDir, "server_root/icons"),
+ DocSrc = filename:join(DataDir, "server_root/htdocs"),
+ AuthSrc = filename:join(DataDir, "server_root/auth"),
+ CgiSrc = filename:join(DataDir, "server_root/cgi-bin"),
+ PicsSrc = filename:join(DataDir, "server_root/icons"),
ConfigSrc = filename:join(DataDir, "server_root/config"),
-
+
inets_test_lib:copy_dirs(DocSrc, DocRoot),
inets_test_lib:copy_dirs(AuthSrc, AuthDir),
inets_test_lib:copy_dirs(CgiSrc, CgiDir),
inets_test_lib:copy_dirs(PicsSrc, PicsDir),
inets_test_lib:copy_dirs(ConfigSrc, ConfigDir),
-
+
Cgi = case os:type() of
{win32, _} ->
"cgi_echo.exe";
_ ->
"cgi_echo"
end,
-
+
inets_test_lib:copy_file(Cgi, DataDir, CgiDir),
AbsCgi = filename:join([CgiDir, Cgi]),
{ok, FileInfo} = file:read_file_info(AbsCgi),
ok = file:write_file_info(AbsCgi, FileInfo#file_info{mode = 8#00755}),
-
+
EnvCGI = filename:join([ServerRoot, "cgi-bin", "printenv.sh"]),
{ok, FileInfo1} = file:read_file_info(EnvCGI),
- ok = file:write_file_info(EnvCGI,
+ ok = file:write_file_info(EnvCGI,
FileInfo1#file_info{mode = 8#00755}).
setup_tmp_dir(PrivDir) ->
@@ -2020,7 +2020,7 @@ start_apps(Group) when Group == http_basic;
Group == http_basic_auth;
Group == http_auth_api;
Group == http_auth_api_dets;
- Group == http_auth_api_mnesia;
+ Group == http_auth_api_mnesia;
Group == http_security;
Group == http_logging;
Group == http_reload;
@@ -2041,18 +2041,17 @@ init_ssl(Group, Config) ->
ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "client"]),
ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "server"]),
GenCertData = #{client_config := CConf} =
- public_key:pkix_test_data(#{server_chain =>
- #{root => [{key, inets_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, inets_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, inets_test_lib:hardcode_rsa_key(3)}
- ]},
- client_chain =>
- #{root => [{key, inets_test_lib:hardcode_rsa_key(4)}],
- intermediates => [[{key, inets_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, inets_test_lib:hardcode_rsa_key(6)}]}}),
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{key, inets_test_lib:hardcode_rsa_key(1)}, {digest, sha256}],
+ intermediates => [[{key, inets_test_lib:hardcode_rsa_key(2)}, {digest, sha256}]],
+ peer => [{key, inets_test_lib:hardcode_rsa_key(3)}, {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, inets_test_lib:hardcode_rsa_key(4)}, {digest, sha256}],
+ intermediates => [[{key, inets_test_lib:hardcode_rsa_key(5)}, {digest, sha256}]],
+ peer => [{key, inets_test_lib:hardcode_rsa_key(6)}, {digest, sha256}]}}),
[_ | CAs] = proplists:get_value(cacerts, CConf),
- AlertConf = [{cacerts, CAs} | proplists:delete(cacerts, CConf)],
- Conf = inets_test_lib:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
+ AlertConf = [{cacerts, CAs} | proplists:delete(cacerts, CConf)],
+ Conf = inets_test_lib:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
case start_apps(Group) of
ok ->
init_httpd(Group, [{client_alert_conf, AlertConf}, {type, ssl}, {ssl_conf, Conf} | Config]);
diff --git a/lib/inets/test/httpd_bench_SUITE.erl b/lib/inets/test/httpd_bench_SUITE.erl
index 65897176a4..85cd67f18e 100644
--- a/lib/inets/test/httpd_bench_SUITE.erl
+++ b/lib/inets/test/httpd_bench_SUITE.erl
@@ -449,7 +449,7 @@ start_web_server(Group, Config) when Group == https_inets;
Group == https_inets_keep_alive ->
Opts = proplists:get_value(server_verification_opts, cert_opts(Config)),
ReuseSessions = ?config(reuse_sessions, Config),
- SSLConfHttpd = [{socket_type, {essl,
+ SSLConfHttpd = [{socket_type, {ssl,
[{nodelay, true}, {reuse_sessions, ReuseSessions} | Opts]}}],
start_inets("https", SSLConfHttpd, Config);
diff --git a/lib/inets/test/httpd_time_test.erl b/lib/inets/test/httpd_time_test.erl
index a31f40ce63..c03ef6c588 100644
--- a/lib/inets/test/httpd_time_test.erl
+++ b/lib/inets/test/httpd_time_test.erl
@@ -47,7 +47,7 @@ t2(Host, Port) ->
t4(Host, Port) ->
- t(essl, Host, Port).
+ t(ssl, Host, Port).
t(SocketType, Host, Port) ->
diff --git a/lib/inets/test/inets_socketwrap_SUITE.erl b/lib/inets/test/inets_socketwrap_SUITE.erl
index b88cff4e90..da97de577a 100644
--- a/lib/inets/test/inets_socketwrap_SUITE.erl
+++ b/lib/inets/test/inets_socketwrap_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -73,12 +73,11 @@ start_httpd_fd(Config) when is_list(Config) ->
InetPort = inets_test_lib:inet_port(node()),
ct:pal("Node: ~p Port ~p~n", [Node, InetPort]),
Wrapper = filename:join(DataDir, "setuid_socket_wrap"),
- Cmd = Wrapper ++
- " -s -httpd_80,0:" ++ integer_to_list(InetPort)
- ++ " -p " ++ os:find_executable("erl") ++
- " -- " ++ NodeArg,
- ct:pal("cmd: ~p~n", [Cmd]),
- case open_port({spawn, Cmd}, [stderr_to_stdout]) of
+ Args = ["-s","-httpd_80,0:" ++ integer_to_list(InetPort),
+ "-p",os:find_executable("erl"),"--" | NodeArg],
+ ct:pal("cmd: ~p ~p~n", [Wrapper, Args]),
+ case open_port({spawn_executable, Wrapper},
+ [stderr_to_stdout,{args,Args}]) of
Port when is_port(Port) ->
wait_node_up(Node, 10),
ct:pal("~p", [rpc:call(Node, init, get_argument, [httpd_80])]),
@@ -97,19 +96,18 @@ start_httpd_fd(Config) when is_list(Config) ->
setup_node_info(nonode@nohost) ->
{skip, needs_distributed_node};
setup_node_info(Node) ->
- Static = "-detached -noinput",
Name = "inets_fd_test",
NameSw = case net_kernel:longnames() of
- false -> "-sname ";
- _ -> "-name "
- end,
- StrNode =
- Static ++ " "
- ++ NameSw ++ " " ++ Name ++ " "
- ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()),
- [_, Location] = string:tokens(atom_to_list(Node), "$@"),
- TestNode = Name ++ "@" ++ Location,
- {list_to_atom(TestNode), StrNode}.
+ false -> "-sname";
+ _ -> "-name"
+ end,
+ NodeArgs = ["-detached","-noinput",
+ NameSw, Name, "-setcookie", atom_to_list(erlang:get_cookie())],
+
+ [_, Location] = string:tokens(atom_to_list(Node), "$@"),
+ TestNode = Name ++ "@" ++ Location,
+
+ {list_to_atom(TestNode), NodeArgs}.
wait_node_up(Node, 0) ->
ct:fail({failed_to_start_node, Node});
diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl
index 1cc4e11e45..324832ca21 100644
--- a/lib/inets/test/inets_test_lib.erl
+++ b/lib/inets/test/inets_test_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
%% Note: This directive should only be used in test suites.
-compile(export_all).
+-compile(nowarn_export_all).
%% -- Misc os command and stuff
@@ -207,12 +208,12 @@ ensure_loaded(App) ->
%%
start_http_server(Conf) ->
- start_http_server(Conf, ?HTTP_DEFAULT_SSL_KIND).
+ start_http_server(Conf, ssl).
-start_http_server(Conf, essl = _SslTag) ->
- tsp("start_http_server(essl) -> try start crypto"),
+start_http_server(Conf, ssl = _SslTag) ->
+ tsp("start_http_server(ssl) -> try start crypto"),
application:start(crypto),
- tsp("start_http_server(essl) -> try start public_key"),
+ tsp("start_http_server(ssl) -> try start public_key"),
application:start(public_key),
do_start_http_server(Conf);
start_http_server(Conf, SslTag) ->
@@ -252,9 +253,9 @@ do_start_http_server(Conf) ->
end.
start_http_server_ssl(FileName) ->
- start_http_server_ssl(FileName, ?HTTP_DEFAULT_SSL_KIND).
+ start_http_server_ssl(FileName, ssl).
-start_http_server_ssl(FileName, essl = _SslTag) ->
+start_http_server_ssl(FileName, ssl = _SslTag) ->
application:start(crypto),
do_start_http_server_ssl(FileName);
start_http_server_ssl(FileName, _SslTag) ->
@@ -457,10 +458,7 @@ connect_bin(SockType, Host, Port) ->
connect_bin(SockType, Host, Port, []).
connect_bin(ssl, Host, Port, Opts0) ->
- Opts = [binary, {packet,0} | Opts0],
- connect(ssl, Host, Port, Opts);
-connect_bin(essl, Host, Port, Opts0) ->
- Opts = [{ssl_imp, new}, binary, {packet,0}| Opts0],
+ Opts = [binary, {packet,0}, {verify, verify_none} | Opts0],
connect(ssl, Host, Port, Opts);
connect_bin(ip_comm, Host, Port, Opts0) ->
Opts = [binary, {packet, 0} | Opts0],
@@ -472,13 +470,10 @@ connect_byte(SockType, Host, Port) ->
connect_byte(SockType, Host, Port, []).
connect_byte(ssl, Host, Port, Opts0) ->
- Opts = [{packet,0} | Opts0],
- connect(ssl, Host, Port, Opts);
-connect_byte(essl, Host, Port, Opts0) ->
- Opts = [{ssl_imp, new}, {packet,0} | Opts0],
+ Opts = [list, {packet,0}, {verify, verify_none} | Opts0],
connect(ssl, Host, Port, Opts);
connect_byte(ip_comm, Host, Port, Opts0) ->
- Opts = [{packet,0} | Opts0],
+ Opts = [list, {packet,0} | Opts0],
connect(ip_comm, Host, Port, Opts);
connect_byte(Type, Host, Port, Opts) ->
connect(Type, Host, Port, Opts).
@@ -498,8 +493,6 @@ connect(openssl_port, Host, Port, Opts) ->
send(ssl, Socket, Data) ->
ssl:send(Socket, Data);
-send(essl, Socket, Data) ->
- ssl:send(Socket, Data);
send(ip_comm,Socket,Data) ->
gen_tcp:send(Socket,Data);
send(openssl_port, Port, Data) ->
@@ -507,8 +500,6 @@ send(openssl_port, Port, Data) ->
ok.
close(ssl,Socket) ->
catch ssl:close(Socket);
-close(essl,Socket) ->
- catch ssl:close(Socket);
close(ip_comm,Socket) ->
catch gen_tcp:close(Socket);
close(openssl_port, Port) ->
@@ -691,7 +682,7 @@ gen_pem_config_files(#{server_config := ServerConf,
ClientCaCertFile),
[{server_config, [{certfile, ServerCertFile},
{keyfile, ServerKeyFile}, {cacertfile, ServerCaCertFile}]},
- {client_config, [{certfile, ClientCertFile},
+ {client_config, [{certfile, ClientCertFile},
{keyfile, ClientKeyFile}, {cacertfile, ClientCaCertFile}]}].
extensions(Exts) ->
[extension(Ext) || Ext <- Exts].
diff --git a/lib/inets/test/make_certs.erl b/lib/inets/test/make_certs.erl
index 7215a59823..71f508fc61 100644
--- a/lib/inets/test/make_certs.erl
+++ b/lib/inets/test/make_certs.erl
@@ -20,6 +20,7 @@
-module(make_certs).
-compile([export_all]).
+-compile(nowarn_export_all).
%-export([all/1, all/2, rootCA/2, intermediateCA/3, endusers/3, enduser/3, revoke/3, gencrl/2, verify/3]).
diff --git a/lib/inets/test/rules.mk b/lib/inets/test/rules.mk
index c4a62a87ed..cc3150934d 100644
--- a/lib/inets/test/rules.mk
+++ b/lib/inets/test/rules.mk
@@ -18,11 +18,7 @@ DEFAULT_TARGETS = opt debug instr release release_docs clean docs
# ----------------------------------------------------
EMULATOR = beam
-ifdef BOOTSTRAP
-ERL_COMPILE_FLAGS += +slim
-else
ERL_COMPILE_FLAGS += +debug_info
-endif
ERLC_WFLAGS = -W
ERLC = erlc $(ERLC_WFLAGS) $(ERLC_FLAGS)
ERL.beam = erl.beam -boot start_clean
@@ -47,8 +43,3 @@ $(EBIN)/%.beam: $(ESRC)/%.erl
.erl.beam:
$(ERLC) -bbeam $(ERL_COMPILE_FLAGS) -o$(dir $@) $<
-
-
-
-
-
diff --git a/lib/jinterface/doc/src/jinterface_users_guide.xml b/lib/jinterface/doc/src/jinterface_users_guide.xml
index cffaa50793..34e272bd8e 100644
--- a/lib/jinterface/doc/src/jinterface_users_guide.xml
+++ b/lib/jinterface/doc/src/jinterface_users_guide.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2000</year><year>2022</year>
+ <year>2000</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -86,11 +86,11 @@
</row>
<row>
<cell align="left" valign="middle">floating point types</cell>
- <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangFloat">OtpErlangFloat</seefile>or <seefile marker="java/com/ericsson/otp/erlang/OtpErlangDouble">OtpErlangDouble</seefile>, depending on the floating point value size</cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangFloat">OtpErlangFloat</seefile> or <seefile marker="java/com/ericsson/otp/erlang/OtpErlangDouble">OtpErlangDouble</seefile>, depending on the floating point value size</cell>
</row>
<row>
<cell align="left" valign="middle">integral types</cell>
- <cell align="left" valign="middle">One of <seefile marker="java/com/ericsson/otp/erlang/OtpErlangByte">OtpErlangByte</seefile>,<seefile marker="java/com/ericsson/otp/erlang/OtpErlangChar">OtpErlangChar</seefile>,<seefile marker="java/com/ericsson/otp/erlang/OtpErlangShort">OtpErlangShort</seefile>,<seefile marker="java/com/ericsson/otp/erlang/OtpErlangUShort">OtpErlangUShort</seefile>,<seefile marker="java/com/ericsson/otp/erlang/OtpErlangInt">OtpErlangInt</seefile>,<seefile marker="java/com/ericsson/otp/erlang/OtpErlangUInt">OtpErlangUInt</seefile>or<seefile marker="java/com/ericsson/otp/erlang/OtpErlangLong">OtpErlangLong</seefile>, depending on the integral value size and sign</cell>
+ <cell align="left" valign="middle">One of <seefile marker="java/com/ericsson/otp/erlang/OtpErlangByte">OtpErlangByte</seefile>, <seefile marker="java/com/ericsson/otp/erlang/OtpErlangChar">OtpErlangChar</seefile>, <seefile marker="java/com/ericsson/otp/erlang/OtpErlangShort">OtpErlangShort</seefile>, <seefile marker="java/com/ericsson/otp/erlang/OtpErlangUShort">OtpErlangUShort</seefile>, <seefile marker="java/com/ericsson/otp/erlang/OtpErlangInt">OtpErlangInt</seefile>, <seefile marker="java/com/ericsson/otp/erlang/OtpErlangUInt">OtpErlangUInt</seefile> or <seefile marker="java/com/ericsson/otp/erlang/OtpErlangLong">OtpErlangLong</seefile>, depending on the integral value size and sign</cell>
</row>
<row>
<cell align="left" valign="middle">list</cell>
diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java
index 9e6a35ec36..b3b72f3d37 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java
@@ -425,29 +425,12 @@ public abstract class AbstractConnection extends Thread {
header.write1(passThrough);
header.write1(version);
- if ((peer.flags & AbstractNode.dFlagUnlinkId) != 0) {
- // header
- header.write_tuple_head(4);
- header.write_long(unlinkIdTag);
- header.write_long(unlink_id);
- header.write_any(from);
- header.write_any(dest);
- }
- else {
- /*
- * A node that isn't capable of talking the new link protocol.
- *
- * Send an old unlink op, and send ourselves an unlink-ack. We may
- * end up in an inconsistent state as we could before the new link
- * protocol was introduced...
- */
- // header
- header.write_tuple_head(3);
- header.write_long(unlinkTag);
- header.write_any(from);
- header.write_any(dest);
- deliver(new OtpMsg(unlinkIdAckTag, dest, from, unlink_id));
- }
+ // header
+ header.write_tuple_head(4);
+ header.write_long(unlinkIdTag);
+ header.write_long(unlink_id);
+ header.write_any(from);
+ header.write_any(dest);
// fix up length in preamble
header.poke4BE(0, header.size() - 4);
@@ -471,26 +454,25 @@ public abstract class AbstractConnection extends Thread {
if (!connected) {
throw new IOException("Not connected");
}
- if ((peer.flags & AbstractNode.dFlagUnlinkId) != 0) {
- @SuppressWarnings("resource")
+
+ @SuppressWarnings("resource")
final OtpOutputStream header = new OtpOutputStream(headerLen);
- // preamble: 4 byte length + "passthrough" tag
- header.write4BE(0); // reserve space for length
- header.write1(passThrough);
- header.write1(version);
+ // preamble: 4 byte length + "passthrough" tag
+ header.write4BE(0); // reserve space for length
+ header.write1(passThrough);
+ header.write1(version);
- // header
- header.write_tuple_head(4);
- header.write_long(unlinkIdAckTag);
- header.write_long(unlink_id);
- header.write_any(from);
- header.write_any(dest);
- // fix up length in preamble
- header.poke4BE(0, header.size() - 4);
+ // header
+ header.write_tuple_head(4);
+ header.write_long(unlinkIdAckTag);
+ header.write_long(unlink_id);
+ header.write_any(from);
+ header.write_any(dest);
+ // fix up length in preamble
+ header.poke4BE(0, header.size() - 4);
- do_send(header);
- }
+ do_send(header);
}
diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java
index 179511c0a5..419ec7e3d7 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java
@@ -111,9 +111,14 @@ public class AbstractNode implements OtpTransportFactory {
| dFlagBitBinaries
| dFlagHandshake23;
+ /* New mandatory flags in OTP 26 */
+ static final long mandatoryFlags26 = dFlagV4PidsRefs
+ | dFlagUnlinkId;
+
/* Mandatory flags for distribution. Keep them in sync with
DFLAG_DIST_MANDATORY in erts/emulator/beam/dist.h. */
- static final long mandatoryFlags = mandatoryFlags25;
+ static final long mandatoryFlags = mandatoryFlags25
+ | mandatoryFlags26;
int ntype = NTYPE_R6;
int proto = 0; // tcp/ip
@@ -121,8 +126,6 @@ public class AbstractNode implements OtpTransportFactory {
int distLow = 6; // Cannot talk to nodes before OTP 23
private int creation = 0x710000;
long flags = mandatoryFlags
- | dFlagUnlinkId
- | dFlagV4PidsRefs
| dFlagMandatory25Digest;
/* initialize hostname and default cookie */
diff --git a/lib/jinterface/prebuild.keep b/lib/jinterface/prebuild.keep
new file mode 100644
index 0000000000..8e695ec83a
--- /dev/null
+++ b/lib/jinterface/prebuild.keep
@@ -0,0 +1 @@
+doc
diff --git a/lib/jinterface/test/jinterface_SUITE.erl b/lib/jinterface/test/jinterface_SUITE.erl
index 8c771feeea..b1df161e4c 100644
--- a/lib/jinterface/test/jinterface_SUITE.erl
+++ b/lib/jinterface/test/jinterface_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -186,6 +186,18 @@ status_handler() ->
init_per_suite(Config) when is_list(Config) ->
+ %% Trigger usage of large pids and ports in 64-bit case...
+ case erlang:system_info(wordsize) of
+ 4 ->
+ ok;
+ 8 ->
+ erts_debug:set_internal_state(available_internal_state,true),
+ erts_debug:set_internal_state(next_pid, 1 bsl 32),
+ erts_debug:set_internal_state(next_port, 1 bsl 32),
+ erts_debug:set_internal_state(available_internal_state,false),
+ ok
+ end,
+
case case code:priv_dir(jinterface) of
{error,bad_name} -> false;
P -> filelib:is_dir(P) end of
diff --git a/lib/jinterface/test/nc_SUITE.erl b/lib/jinterface/test/nc_SUITE.erl
index 115571e21e..98b2a71988 100644
--- a/lib/jinterface/test/nc_SUITE.erl
+++ b/lib/jinterface/test/nc_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -131,8 +131,8 @@ pid_roundtrip(Config) when is_list(Config)->
|| Cr <- [1,2,3,4,16#adec0ded],
{Num, Ser} <- [{4711,4711},{32767, 8191}]],
do_echo([self(),
- mk_pid(ThisNode, 4711, 4711),
- mk_pid(ThisNode, 32767, 8191)
+ mk_pid(ThisNode, 4711, 0),
+ mk_pid(ThisNode, 1 bsl 27, 0)
| RemPids],
Config).
diff --git a/lib/kernel/Makefile b/lib/kernel/Makefile
index 534b564c2c..7586a4c981 100644
--- a/lib/kernel/Makefile
+++ b/lib/kernel/Makefile
@@ -35,7 +35,7 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
-DIA_PLT_APPS=crypto
+DIA_PLT_APPS=crypto compiler
TEST_NEEDS_RELEASE=true
include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/kernel/doc/src/Makefile b/lib/kernel/doc/src/Makefile
index fa2e635324..a9fd26904a 100644
--- a/lib/kernel/doc/src/Makefile
+++ b/lib/kernel/doc/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2021. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -68,7 +68,6 @@ XML_REF3_FILES = application.xml \
seq_trace.xml \
socket.xml \
wrap_log_reader.xml \
- user.xml \
zlib_stub.xml \
$(XML_REF3_ESOCK_EFILES)
diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml
index 2a807e62bb..a1f585f5cd 100644
--- a/lib/kernel/doc/src/application.xml
+++ b/lib/kernel/doc/src/application.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2021</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -69,12 +69,21 @@
<func>
<name name="ensure_all_started" arity="1" since="OTP R16B02"/>
<name name="ensure_all_started" arity="2" since="OTP R16B02"/>
- <fsummary>Load and start an application and its dependencies, recursively.</fsummary>
+ <name name="ensure_all_started" arity="3" since="OTP @OTP-18451@"/>
+ <fsummary>Loads and starts all applications and their dependencies, recursively.</fsummary>
<desc>
- <p>Equivalent to calling
+ <p><c><anno>Applications</anno></c> is either an an <c>atom()</c> or a list
+ of <c>atom()</c> representing multiple applications.</p>
+ <p>This function is equivalent to calling
<seemfa marker="#start/1"><c>start/1,2</c></seemfa>
- repeatedly on all dependencies that are not yet started for an application.
+ repeatedly on all dependencies that are not yet started of each application.
Optional dependencies will also be loaded and started if they are available.</p>
+ <p>The <c><anno>Mode</anno></c> argument controls if the applications should
+ be started in <c>serial</c> mode (one at a time) or <c>concurrent</c> mode.
+ In concurrent mode, a dependency graph is built and the leaves of the graph
+ are started concurrently and recursively. In both modes, no assertion can be
+ made about the order the applications are started. If not supplied, it defaults
+ to <c>serial</c>.</p>
<p>Returns <c>{ok, AppNames}</c> for a successful start or for an already started
application (which is, however, omitted from the <c>AppNames</c> list).</p>
<p>The function reports <c>{error, {AppName,Reason}}</c> for errors, where
@@ -181,6 +190,16 @@
</desc>
</func>
<func>
+ <name name="get_supervisor" arity="1" since="OTP @OTP-18444@"/>
+ <fsummary>Get the supervisor of an application.</fsummary>
+ <desc>
+ <p>Returns the <c><anno>Pid</anno></c> of the supervisor running
+ at the root of <c><anno>Application</anno></c>.</p>
+ <p>If the specified application does not exist or does not
+ define a callback module, the function returns <c>undefined</c>.</p>
+ </desc>
+ </func>
+ <func>
<name name="load" arity="1" since=""/>
<name name="load" arity="2" since=""/>
<fsummary>Load an application.</fsummary>
diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml
index f551fd1ebe..ffa77641de 100644
--- a/lib/kernel/doc/src/code.xml
+++ b/lib/kernel/doc/src/code.xml
@@ -101,6 +101,18 @@
<code>
/usr/local/jungerl:/home/some_user/my_erlang_lib</code>
<p>On Windows, use semi-colon as separator.</p>
+ <p>The code paths specified by <c>$OTP_ROOT</c>, <c>ERL_LIBS</c>,
+ and boot scripts have their listings cached by default (except for ".")
+ since OTP @OTP-18466@.
+ The code server will lookup the contents in their directories once
+ and avoid future file system traversals. Therefore modules added
+ to such directories after the Erlang VM boots won't be picked up.
+ You can disable this behaviour by setting <c>-cache_boot_paths false</c>
+ or by calling <c>code:set_path(code:get_path())</c>.</p>
+ <p>The functions in this module and the command line options <c>-pa</c>
+ and <c>-pz</c> are not cached by default. However, many of the functions
+ that manipulate the code path accept the <c>cache</c> atom as an optional
+ argument, which will enable caching on selected paths.</p>
</section>
<section>
@@ -309,6 +321,12 @@ zip:create("mnesia-4.4.7.ez",
</section>
<datatypes>
<datatype>
+ <name name="add_path_ret"/>
+ </datatype>
+ <datatype>
+ <name name="cache"/>
+ </datatype>
+ <datatype>
<name name="load_ret"/>
</datatype>
<datatype>
@@ -321,14 +339,24 @@ zip:create("mnesia-4.4.7.ez",
<name name="prepared_code"/>
<desc><p>An opaque term holding prepared code.</p></desc>
</datatype>
+ <datatype>
+ <name name="replace_path_ret"/>
+ </datatype>
+ <datatype>
+ <name name="set_path_ret"/>
+ </datatype>
</datatypes>
<funcs>
<func>
<name name="set_path" arity="1" since=""/>
+ <name name="set_path" arity="2" since="OTP @OTP-18466@"/>
<fsummary>Set the code server search path.</fsummary>
<desc>
<p>Sets the code path to the list of directories <c><anno>Path</anno></c>.</p>
+ <p>An optional second argument may be set to the atom <c>cache</c> to
+ control if the contents of the directory must be cached on first traversal.
+ Defaults to <c>nocache</c>.</p>
<p>Returns:</p>
<taglist>
<tag><c>true</c></tag>
@@ -347,13 +375,18 @@ zip:create("mnesia-4.4.7.ez",
</func>
<func>
<name name="add_path" arity="1" since=""/>
+ <name name="add_path" arity="2" since="OTP @OTP-18466@"/>
<name name="add_pathz" arity="1" since=""/>
+ <name name="add_pathz" arity="2" since="OTP @OTP-18466@"/>
<fsummary>Add a directory to the end of the code path.</fsummary>
<type name="add_path_ret"/>
<desc>
<p>Adds <c><anno>Dir</anno></c> to the code path. The directory is added as
the last directory in the new path. If <c><anno>Dir</anno></c> already
exists in the path, it is not added.</p>
+ <p>An optional second argument may be set to the atom <c>cache</c> to
+ control if the contents of the directory must be cached on first traversal.
+ Defaults to <c>nocache</c>.</p>
<p>Returns <c>true</c> if successful, or
<c>{error, bad_directory}</c> if <c><anno>Dir</anno></c> is not the name
of a directory.</p>
@@ -361,12 +394,16 @@ zip:create("mnesia-4.4.7.ez",
</func>
<func>
<name name="add_patha" arity="1" since=""/>
+ <name name="add_patha" arity="2" since="OTP @OTP-18466@"/>
<fsummary>Add a directory to the beginning of the code path.</fsummary>
<type name="add_path_ret"/>
<desc>
<p>Adds <c><anno>Dir</anno></c> to the beginning of the code path. If
<c><anno>Dir</anno></c> exists, it is removed from the old
position in the code path.</p>
+ <p>An optional second argument may be set to the atom <c>cache</c> to
+ control if the contents of the directory must be cached on first traversal.
+ Defaults to <c>nocache</c>.</p>
<p>Returns <c>true</c> if successful, or
<c>{error, bad_directory}</c> if <c><anno>Dir</anno></c> is not the name
of a directory.</p>
@@ -374,17 +411,23 @@ zip:create("mnesia-4.4.7.ez",
</func>
<func>
<name name="add_paths" arity="1" since=""/>
+ <name name="add_paths" arity="2" since="OTP @OTP-18466@"/>
<name name="add_pathsz" arity="1" since=""/>
+ <name name="add_pathsz" arity="2" since="OTP @OTP-18466@"/>
<fsummary>Add directories to the end of the code path.</fsummary>
<desc>
<p>Adds the directories in <c><anno>Dirs</anno></c> to the end of the code
path. If a <c><anno>Dir</anno></c> exists, it is not added.</p>
+ <p>An optional second argument may be set to the atom <c>cache</c> to
+ control if the contents of the directory must be cached on first traversal.
+ Defaults to <c>nocache</c>.</p>
<p>Always returns <c>ok</c>, regardless of the validity
of each individual <c><anno>Dir</anno></c>.</p>
</desc>
</func>
<func>
<name name="add_pathsa" arity="1" since=""/>
+ <name name="add_pathsa" arity="2" since="OTP @OTP-18466@"/>
<fsummary>Add directories to the beginning of the code path.</fsummary>
<desc>
<p>Traverses <c><anno>Dirs</anno></c> and adds
@@ -395,6 +438,9 @@ zip:create("mnesia-4.4.7.ez",
be <c>[Dir2,Dir1|OldCodePath]</c>.</p>
<p>If a <c><anno>Dir</anno></c> already exists in the code
path, it is removed from the old position.</p>
+ <p>An optional second argument may be set to the atom <c>cache</c> to
+ control if the contents of the directory must be cached on first traversal.
+ Defaults to <c>nocache</c>.</p>
<p>Always returns <c>ok</c>, regardless of the validity of each
individual <c><anno>Dir</anno></c>.</p>
</desc>
@@ -420,7 +466,35 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
+ <name name="del_paths" arity="1" since="OTP @OTP-18466@"/>
+ <fsummary>Deletes directories from the code path.</fsummary>
+ <desc>
+ <p>Deletes directories from the code path. The argument is a list of either
+ atoms or complete directory names. If an atom <c><anno>Name</anno></c>,
+ the directory with the name <c>.../<anno>Name</anno>[-Vsn][/ebin]</c> is
+ deleted from the code path.</p>
+ <p>Always returns <c>ok</c>, regardless of the validity
+ of each individual <c><anno>NamesOrDirs</anno></c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name name="clear_cache" arity="0" since="OTP @OTP-18466@"/>
+ <fsummary>Clears the code path cache.</fsummary>
+ <desc>
+ <p>Clear the code path cache. If a directory is cached, its cache is
+ cleared once and then it will be recalculated and cached once more
+ in a future traversal.</p>
+ <p>If you want to clear the cache for a single path, you might re-add it
+ to the code path (with <c>add_path/2</c>) or
+ replace it (with <c>replace_path/3</c>).
+ If you want to disable all cache, you can reset the code path
+ with <c>code:set_path(code:get_path())</c>.</p>
+ <p>Always returns <c>ok</c>.</p>
+ </desc>
+ </func>
+ <func>
<name name="replace_path" arity="2" since=""/>
+ <name name="replace_path" arity="3" since="OTP @OTP-18466@"/>
<fsummary>Replace a directory with another in the code path.</fsummary>
<desc>
<p>Replaces an old occurrence of a directory
@@ -430,6 +504,9 @@ zip:create("mnesia-4.4.7.ez",
directory must also be named <c>.../<anno>Name</anno>[-Vsn][/ebin]</c>.
This function is to be used if a new version of the directory (library) is
added to a running system.</p>
+ <p>An optional third argument may be set to the atom <c>cache</c> to
+ control if the contents of the directory must be cached on first traversal.
+ Defaults to <c>nocache</c>.</p>
<p>Returns:</p>
<taglist>
<tag><c>true</c></tag>
@@ -981,17 +1058,6 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="is_module_native" arity="1" since=""/>
- <fsummary>Test if a module has native code.</fsummary>
- <desc>
- <p>Returns <c>false</c> if the given <c><anno>Module</anno></c> is
- loaded, and <c>undefined</c> if it is not.</p>
- <warning><p>This function is deprecated and will be removed in a future
- release.</p></warning>
- </desc>
- </func>
-
- <func>
<name name="get_mode" arity="0" since="OTP R16B"/>
<fsummary>The mode of the code server.</fsummary>
<desc>
diff --git a/lib/kernel/doc/src/disk_log.xml b/lib/kernel/doc/src/disk_log.xml
index 46ed711906..dc6f9ba5bf 100644
--- a/lib/kernel/doc/src/disk_log.xml
+++ b/lib/kernel/doc/src/disk_log.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>1997</year>
- <year>2021</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -39,7 +39,7 @@
<description>
<p><c>disk_log</c> is a disk-based term logger that enables
efficient logging of items on files.</p>
- <p>Two types of logs are supported:</p>
+ <p>Three types of logs are supported:</p>
<taglist>
<tag>halt logs</tag>
<item><p>Appends items to a single file, which size can
@@ -49,6 +49,17 @@
wrap log file is filled up, further items are logged on to the next
file in the sequence, starting all over with the first file when
the last file is filled up.</p></item>
+ <tag>rotate logs</tag>
+ <item><p>Uses a sequence of rotate log files of limited size. As a
+ log file is filled up, it is rotated and then compressed. There is one active
+ log file and upto the configured number of compressed log files. Only externally
+ formatted logs are supported. It follows the same naming convention as the handler
+ logger_std_h for Logger. For more details about the naming convention check the file
+ parameter for
+ <seemfa marker="#open/1"><c>open/1</c></seemfa>.</p>
+ <p>It follows the same naming convention as that for the compressed files for Linux's
+ logrotate and BSD's newsyslog.</p>
+ </item>
</taglist>
<p>For efficiency reasons, items are always written to files as binaries.</p>
@@ -73,7 +84,7 @@
A process that opens a disk log can be an <em>owner</em>
or an anonymous <em>user</em> of the disk log. Each owner is
linked to the disk log process, and an owner can close the disk log
- either explicitly (by calling <c>close/1</c> or <c>lclose/1,2</c>)
+ either explicitly (by calling <c>close/1</c>)
or by terminating.</p>
<p>Owners can subscribe to <em>notifications</em>,
messages of the form <c>{disk_log, Node, Log, Info}</c>, which are sent
@@ -197,6 +208,9 @@
<datatype>
<name name="file_error"/>
</datatype>
+ <datatype>
+ <name name="next_file_error_rsn"/>
+ </datatype>
</datatypes>
<funcs>
<func>
@@ -207,20 +221,7 @@
</p>
</desc>
</func>
- <func>
- <name name="accessible_logs" arity="0" since=""/>
- <fsummary>Return the accessible disk logs on the current node.</fsummary>
- <desc>
- <p>Returns the names of the disk logs accessible on the current node.
- The first list contains the logs. The second list is always empty
- (before Erlang/OTP 24.0 it used to contain so called distributed
- disk logs).
- </p>
- <note><p>This function is deprecated.
- Use <seemfa marker="#all/0"><c>all/0</c></seemfa> instead.
- </p></note>
- </desc>
- </func>
+
<func>
<name name="alog" arity="2" since=""/>
<name name="balog" arity="2" since=""/>
@@ -326,10 +327,10 @@
but it cannot be decreased to something less than
the current file size.
</p>
- <p>For a wrap log, both the size and the number of files can always
- be increased, as long as the number of files does not
- exceed 65000. If the maximum number of files is decreased, the
- change is not valid until the current file is full and the
+ <p>For a wrap or rotate log, both the size and the number of files can
+ always be increased, as long as the number of files does not
+ exceed 65000. For wrap logs, if the maximum number of files is decreased,
+ the change is not valid until the current file is full and the
log wraps to the next file.
The redundant files are removed the next time the log wraps around,
that is, starts to log to file number 1.
@@ -346,13 +347,16 @@
file (that is, files 7 and 8) are removed the next time file 6
is full.
</p>
+ <p>For rotate logs, if the maximum number of files is decreased,
+ the redundant files are deleted instantly.
+ </p>
<p>If the size of the files is decreased, the change immediately
affects the current log. It does not change the
size of log files already full until the next time they are used.
</p>
<p>If the log size is decreased, for example, to save space,
function
- <seemfa marker="#inc_wrap_file/1"><c>inc_wrap_file/1</c></seemfa>
+ <seemfa marker="#next_file/1"><c>next_file/1</c></seemfa>,
can be used to force the log to wrap.
</p>
</desc>
@@ -644,27 +648,7 @@
</p>
</desc>
</func>
- <func>
- <name name="lclose" arity="1" since=""/>
- <name name="lclose" arity="2" since=""/>
- <fsummary>Close a disk log on one node.</fsummary>
- <type name="lclose_error_rsn"/>
- <desc>
- <p><c>lclose/1</c> closes a disk log on the current node.</p>
- <p><c>lclose/2</c> closes a disk log on the
- current node if <anno>Node</anno> is the current node.</p>
- <p><c>lclose(<anno>Log</anno>)</c> is equivalent to
- <c>lclose(<anno>Log</anno>,&nbsp;node())</c>.
- See also <seeerl marker="#close_1"><c>close/1</c></seeerl>.
- </p>
- <p>If no log with the specified name exist on the current node,
- <c>no_such_log</c> is returned.
- </p>
- <note><p>These functions are deprecated. Use
- <seemfa marker="#close/1"><c>close/1</c></seemfa>
- instead.</p></note>
- </desc>
- </func>
+
<func>
<name name="log" arity="2" since=""/>
<name name="blog" arity="2" since=""/>
@@ -729,6 +713,26 @@
</desc>
</func>
<func>
+ <name name="next_file" arity="1" since="OTP @OTP-18331@"/>
+ <fsummary>Change to the next log file of a disk log.</fsummary>
+ <type name="next_file_error_rsn"/>
+ <type name="invalid_header"/>
+ <desc>
+ <p>For wrap logs, it forces the disk log to start logging to the
+ next log file. It can be used, for example, with
+ <c>change_size/2</c> to reduce the amount of disk space allocated
+ by the disk log.
+ </p>
+ <p>Owners subscribing to notifications normally receive a
+ <c>wrap</c> message, but if an error occurs with a reason tag
+ of <c>invalid_header</c> or <c>file_error</c>, an <c>error_status</c>
+ message is sent.</p>
+ <p>For rotate logs, it forces rotation of the currently active log
+ file, compresses it and opens a new active file for logging.
+ </p>
+ </desc>
+ </func>
+ <func>
<name name="open" arity="1" since=""/>
<fsummary>Open a disk log file.</fsummary>
<type name="dlog_options"/>
@@ -757,9 +761,16 @@
the filename defaults to <c>lists:concat([<anno>Log</anno>, ".LOG"])</c>
for halt logs.</p>
<p>For wrap logs, this is the base name of the files. Each file in
- a wrap log is called <c><![CDATA[<base_name>.N]]></c>, where <c>N</c>
+ a wrap log is called <c><![CDATA[<FileName>.N]]></c>, where <c>N</c>
is an integer. Each wrap log also has two files called
- <c><![CDATA[<base_name>.idx]]></c> and <c><![CDATA[<base_name>.siz]]></c>.
+ <c><![CDATA[<FileName>.idx]]></c> and <c><![CDATA[<FileName>.siz]]></c>.
+ </p>
+ <p>For rotate logs, this is the name of the active log file. The compressed
+ files are named as <c><![CDATA[<FileName>.N.gz]]></c>, where <c>N</c> is
+ an integer and <c><![CDATA[<FileName>.0.gz]]></c> is the latest compressed
+ log file. All the compressed files are renamed at each rotation so that the
+ latest files have the smallest index. The maximum value for N is the value of
+ <c><![CDATA[MaxNoFiles]]></c> minus 1.
</p>
</item>
<tag><c>{linkto, <anno>LinkTo</anno>}</c><marker id="linkto"></marker></tag>
@@ -803,10 +814,10 @@
log more items are rejected. Defaults to
<c>infinity</c>, which for halt implies that there is no
maximum size.</p>
- <p>For wrap logs, parameter <c><anno>Size</anno></c>
+ <p>For wrap and rotate logs, parameter <c><anno>Size</anno></c>
can be a pair
- <c>{<anno>MaxNoBytes</anno>, <anno>MaxNoFiles</anno>}</c> or
- <c>infinity</c>.
+ <c>{<anno>MaxNoBytes</anno>, <anno>MaxNoFiles</anno>}</c>. For wrap logs
+ it can also be <c>infinity</c>.
In the latter case, if the files of an existing wrap log
with the same name can be found, the size is read
from the existing wrap log, otherwise an error is returned.</p>
@@ -837,6 +848,12 @@
opening an existing wrap log for the first time, that
is, when creating the disk log process.</p>
</note>
+ <p>Rotate logs write at most <c><anno>MaxNoBytes</anno></c>
+ bytes on the active log file and keep the latest <c><anno>MaxNoFiles</anno></c>
+ compressed files. Regardless of <c><anno>MaxNoBytes</anno></c>,
+ at least the header (if there is one) and one
+ item are written on each rotate log file before rotation.
+ </p>
<p>When opening an already open halt log, option <c>size</c>
is ignored.</p>
</item>
@@ -910,7 +927,7 @@
<tag><c>{head, <anno>Head</anno>}</c></tag>
<item>
<p>Specifies a header to be
- written first on the log file. If the log is a wrap
+ written first on the log file. If the log is a wrap or rotate
log, the item <c><anno>Head</anno></c> is written first in each new file.
<c><anno>Head</anno></c> is to be a term if the format is
<c>internal</c>, otherwise an <c>iodata()</c>.
@@ -1015,12 +1032,12 @@
<desc>
<p>Renames the log file
to <c><anno>File</anno></c> and then recreates a new log file.
- If a wrap log exists, <c><anno>File</anno></c> is used as the base name
+ If a wrap/rotate log exists, <c><anno>File</anno></c> is used as the base name
of the renamed files.
By default the header given to <c>open/1</c> is written first in
the newly opened log file, but if argument <c><anno>Head</anno></c> or
<c><anno>BHead</anno></c> is specified, this item is used instead.
- The header argument is used only once. Next time a wrap log file
+ The header argument is used only once. Next time a wrap/rotate log file
is opened, the header given to <c>open/1</c> is used.
</p>
<p><c>reopen/2,3</c> are used for internally formatted
@@ -1060,7 +1077,7 @@
If argument <c><anno>Head</anno></c> or <c><anno>BHead</anno></c> is
specified, this item is written first in the newly truncated
log, otherwise the header given to <c>open/1</c> is used.
- The header argument is used only once. Next time a wrap log file
+ The header argument is used only once. Next time a wrap/rotate log file
is opened, the header given to <c>open/1</c> is used.
</p>
<p><c>truncate/1</c> is used for both internally and externally
diff --git a/lib/kernel/doc/src/erpc.xml b/lib/kernel/doc/src/erpc.xml
index 876a8a66b6..1c5ac214b0 100644
--- a/lib/kernel/doc/src/erpc.xml
+++ b/lib/kernel/doc/src/erpc.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2020</year><year>2022</year>
+ <year>2020</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -57,6 +57,14 @@
Note that it is up to the user to ensure that correct code to
execute via <c>erpc</c> is available on the involved nodes.
</p>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ Blocking Signaling Over Distribution</seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ Blocking signaling can, for example, cause timeouts in <c>erpc</c>
+ to be significantly delayed.
+ </p></note>
</description>
<datatypes>
diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml
index 131eba8284..a24dcadcb7 100644
--- a/lib/kernel/doc/src/file.xml
+++ b/lib/kernel/doc/src/file.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -1214,6 +1214,9 @@ f.txt: {person, "kalle", 25}.
<name name="pid2name" arity="1" since=""/>
<fsummary>Return the name of the file handled by a pid.</fsummary>
<desc>
+ <change>
+ <p>This function is deprecated and will be removed in Erlang/OTP 27.</p>
+ </change>
<p>If <c><anno>Pid</anno></c> is an I/O device, that is, a pid returned from
<c>open/2</c>, this function returns the filename, or rather:</p>
<taglist>
diff --git a/lib/kernel/doc/src/global.xml b/lib/kernel/doc/src/global.xml
index 407ca52a8b..9d2442c84a 100644
--- a/lib/kernel/doc/src/global.xml
+++ b/lib/kernel/doc/src/global.xml
@@ -452,4 +452,3 @@
<seeerl marker="net_kernel"><c>net_kernel(3)</c></seeerl></p>
</section>
</erlref>
-
diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml
index 0dac35f9a9..5524000f3d 100644
--- a/lib/kernel/doc/src/inet.xml
+++ b/lib/kernel/doc/src/inet.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1997</year><year>2022</year>
+ <year>1997</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -1092,6 +1092,22 @@ get_tcpi_sacked(Sock) ->
<seemfa marker="gen_tcp#shutdown/2"><c>gen_tcp:shutdown/2</c></seemfa>
to shut down the write side.</p>
</item>
+ <tag><c>{exclusiveaddruse, Boolean}</c>
+ <marker id="option-exclusiveaddruse"/></tag>
+ <item>
+ <p>
+ Enables/disables exclusive address/port usage on Windows. That
+ is, by enabling this option you can prevent other sockets from
+ binding to the same address/port. By default this option is
+ disabled. That is, other sockets may use the same address/port
+ by setting <seeerl marker="#option-reuseaddr"><c>{reuseaddr,
+ true}</c></seeerl> in combination with
+ <seeerl marker="#option-reuseport"><c>{reuseport,
+ true}</c></seeerl> unless <c>{exclusiveaddruse, true}</c>
+ has been set on <c><anno>Socket</anno></c>. On non-Windows
+ systems this option is silently ignored.
+ </p>
+ </item>
<tag><c>{header, Size}</c></tag>
<item>
<p>This option is only meaningful if option <c>binary</c>
@@ -1566,18 +1582,109 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
option.
</p>
</item>
- <tag><c>{reuseaddr, Boolean}</c></tag>
+ <tag><c>{reuseaddr, Boolean}</c><marker id="option-reuseaddr"/></tag>
+ <item>
+ <p>
+ Allows or disallows reuse of local address. By default, reuse
+ is disallowed.
+ </p>
+ <note>
+ <p>
+ On windows <c>{reuseaddr, true}</c> will have no effect unless
+ also <seeerl marker="#option-reuseport"><c>{reuseport,
+ true}</c></seeerl> is set. If both are set, the
+ <c>SO_REUSEADDR</c> Windows socket option will be enabled.
+ This since setting <c>SO_REUSEADDR</c> on Windows more or less
+ has the same behavior as setting both <c>SO_REUSEADDR</c> and
+ <c>SO_REUSEPORT</c> on BSD. This behavior was introduced as
+ of OTP @OTP-18344@.
+ </p>
+ <change>
+ <p>
+ Previous behavior on Windows:
+ </p>
+ <list>
+ <item>
+ Prior to OTP 25.0, the <c>{reuseaddr, true}</c> option was
+ silently ignored.
+ </item>
+ <item>
+ Between OTP 25.0 and up to the predecessor of OTP 25.2,
+ the underlying <c>SO_REUSEADDR</c> socket option was set
+ if <c>{reuseaddr, true}</c> was set.
+ </item>
+ <item>
+ Between OTP 25.2 and up to the predecessor of OTP @OTP-18344@,
+ the underlying <c>SO_REUSEADDR</c> socket option was only
+ set on UDP sockets if <c>{reuseaddr, true}</c> was set, and
+ silently ignored on other sockets.
+ </item>
+ </list>
+ </change>
+ <p>
+ See also the
+ <seeerl marker="#option-exclusiveaddruse"><c>exclusiveaddruse</c></seeerl>
+ option.
+ </p>
+ </note>
+ </item>
+ <tag><c>{reuseport, Boolean}</c><marker id="option-reuseport"/></tag>
+ <item>
+ <p>
+ Allows or disallows reuse of local port which <i>may or may not</i>
+ have load balancing depending on the underlying OS. By default,
+ reuse is disallowed. See also
+ <seeerl marker="#option-reuseport_lb"><c>reuseport_lb</c></seeerl>.
+ </p>
+ <note>
+ <p>
+ On windows <c>{reuseport, true}</c> will have no effect unless
+ also <seeerl marker="#option-reuseaddr"><c>{reuseaddr,
+ true}</c></seeerl> is set. If both are set, the
+ <c>SO_REUSEADDR</c> Windows socket option will be enabled.
+ This since setting <c>SO_REUSEADDR</c> on Windows more or less
+ has the same behavior as setting both <c>SO_REUSEADDR</c> and
+ <c>SO_REUSEPORT</c> on BSD. The <c>reuseport</c> option was
+ introduced as of OTP @OTP-18344@.
+ </p>
+ <p>
+ See also the
+ <seeerl marker="#option-exclusiveaddruse"><c>exclusiveaddruse</c></seeerl>
+ option.
+ </p>
+ </note>
+ <note>
+ <p>
+ <c>reuseport</c> <i>may or may not</i> be the same underlying
+ option as
+ <seeerl marker="#option-reuseport_lb"><c>reuseport_lb</c></seeerl>
+ depending on the underlying OS. They, for example, are on Linux.
+ When they are the same underlying option, operating on both may
+ cause them to interact in surprising ways. For example,
+ by enabling <c>reuseport</c> and then disabling
+ <c>reuseport_lb</c> both will end up being disabled.
+ </p>
+ </note>
+ </item>
+ <tag><c>{reuseport_lb, Boolean}</c><marker id="option-reuseport_lb"/></tag>
<item>
<p>
- Allows or disallows local reuse of address. By
- default, reuse is disallowed.
+ Allows or disallows reuse of local port <i>with</i> load balancing.
+ By default, reuse is disallowed. See also
+ <seeerl marker="#option-reuseport"><c>reuseport</c></seeerl>.
</p>
- <note><p>
- On Windows this option will be ignored unless
- <c><anno>Socket</anno></c> is an UDP socket. This since the
- behavior of <c>reuseaddr</c> is very different on Windows
- compared to other system.
- </p></note>
+ <note>
+ <p>
+ <c>reuseport_lb</c> <i>may or may not</i> be the same underlying
+ option as
+ <seeerl marker="#option-reuseport"><c>reuseport</c></seeerl>
+ depending on the underlying OS. They, for example, are on Linux.
+ When they are the same underlying option, operating on both may
+ cause them to interact in surprising ways. For example,
+ by enabling <c>reuseport_lb</c> and then disabling
+ <c>reuseport</c> both will end up being disabled.
+ </p>
+ </note>
</item>
<tag><c>{send_timeout, Integer}</c></tag>
<item>
diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml
index 326b53a873..17edb81002 100644
--- a/lib/kernel/doc/src/kernel_app.xml
+++ b/lib/kernel/doc/src/kernel_app.xml
@@ -669,7 +669,6 @@ erl -kernel logger '[{handler,default,logger_std_h,#{formatter=>{logger_formatte
<seeerl marker="pg"><c>pg(3)</c></seeerl>,
<seeerl marker="rpc"><c>rpc(3)</c></seeerl>,
<seeerl marker="seq_trace"><c>seq_trace(3)</c></seeerl>,
- <seeerl marker="user"><c>user(3)</c></seeerl>,
<seeerl marker="stdlib:timer"><c>timer(3)</c></seeerl></p>
</section>
</appref>
diff --git a/lib/kernel/doc/src/os.xml b/lib/kernel/doc/src/os.xml
index 9ac0583d0a..11bc1d4b43 100644
--- a/lib/kernel/doc/src/os.xml
+++ b/lib/kernel/doc/src/os.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1997</year><year>2021</year>
+ <year>1997</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -115,8 +115,8 @@
<fsummary>Execute a command in a shell of the target OS.</fsummary>
<desc>
<p>Executes <c><anno>Command</anno></c> in a command shell of the
- target OS, captures the standard output of the command,
- and returns this result as a string.</p>
+ target OS, captures the standard output and standard error of the
+ command, and returns this result as a string.</p>
<p><em>Examples:</em></p>
<code type="none">
LsOut = os:cmd("ls"), % on unix platform
diff --git a/lib/kernel/doc/src/ref_man.xml b/lib/kernel/doc/src/ref_man.xml
index 7864764685..39973158d6 100644
--- a/lib/kernel/doc/src/ref_man.xml
+++ b/lib/kernel/doc/src/ref_man.xml
@@ -4,7 +4,7 @@
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>1996</year><year>2021</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -69,7 +69,6 @@
<xi:include href="rpc.xml"/>
<xi:include href="seq_trace.xml"/>
<xi:include href="socket.xml"/>
- <xi:include href="user.xml"/>
<xi:include href="wrap_log_reader.xml"/>
<xi:include href="zlib_stub.xml"/>
</application>
diff --git a/lib/kernel/doc/src/rpc.xml b/lib/kernel/doc/src/rpc.xml
index 6b471d0d42..c5b110f2e5 100644
--- a/lib/kernel/doc/src/rpc.xml
+++ b/lib/kernel/doc/src/rpc.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -53,6 +53,14 @@
<c>erpc</c>, so the <c>rpc</c> module won't not suffer scalability
wise and performance wise compared to <c>erpc</c>.
</p></note>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ Blocking Signaling Over Distribution</seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ Blocking signaling can, for example, cause timeouts in <c>rpc</c>
+ to be significantly delayed.
+ </p></note>
</description>
<datatypes>
diff --git a/lib/kernel/doc/src/socket.xml b/lib/kernel/doc/src/socket.xml
index 753517bee4..2861108da6 100644
--- a/lib/kernel/doc/src/socket.xml
+++ b/lib/kernel/doc/src/socket.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2018</year><year>2022</year>
+ <year>2018</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -40,48 +40,97 @@
the functions,
e.g. <seemfa marker="#recv/3"><c>recv/3</c></seemfa>,
have a time-out argument. </p>
+
<marker id="asynchronous-call"/>
<note>
<p>
- Some functions allow for an <i>asynchronous</i> call.
- This is achieved by setting the <c>Timeout</c> argument to
- <c>nowait</c>. For instance, if calling the
+ Some functions allow for an <i>asynchronous</i> call.
+ This is achieved by setting the <c>Timeout</c> argument to
+ <c>nowait</c> or to a
+ (<seetype marker="socket#select_handle">select</seetype>
+ or
+ <seetype marker="socket#completion_handle">completion</seetype>)
+ <i>handle</i>.
+ </p>
+ <p>
+ For instance, if calling the
<seeerl marker="#recv-nowait"><c>recv/3</c></seeerl>
function with Timeout set to <c>nowait</c>
(<c>recv(Sock, 0, nowait)</c>)
- when there is actually nothing to read, it will return with
- <c>{select, </c>
- <seetype marker="#select_info"><c>SelectInfo</c></seetype><c>}</c>
- (<c>SelectInfo</c> contains the
- <seetype marker="socket#select_handle">SelectHandle</seetype>).
- When data eventually arrives a 'select' message
- will be sent to the caller:
+ when there is actually nothing to read, it will return with either
+ one of:
</p>
<taglist>
<!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
<tag></tag>
- <item><c>{'$socket', socket(), select, SelectHandle}</c></item>
+ <item><c>{completion, </c>
+ <seetype marker="#completion_info"><c>CompletionInfo</c></seetype><c>}</c></item>
+ <tag></tag>
+ <item><c>{select, </c>
+ <seetype marker="#select_info"><c>SelectInfo</c></seetype><c>}</c></item>
</taglist>
<p>
- The caller can now call the <c>recv</c> function again
- and probably expect data
- (it is really up to the OS network protocol implementation).
+ <c>CompletionInfo</c> contains the
+ <seetype marker="socket#completion_handle">CompletionHandle</seetype>
+ and
+ <c>SelectInfo</c> contains the
+ <seetype marker="socket#select_handle">SelectHandle</seetype>.
</p>
<p>
- Note that all other users are <em>locked out</em> until the
- 'current user' has called the function (<c>recv</c> in this case)
- and its return value shows that the operation has completed.
- An operation can also be cancelled with
+ We have two different implementations.
+ One on <i>Unix</i> (<c>select</c>, based directly on the synchronous
+ standard socket interface)
+ and one on <i>Windows</i> (<c>completion</c>, based on the
+ asynchronous I/O Completion Ports).
+ </p>
+ <p>
+ These two implementations have a slightly different behaviour
+ and message interface.
+ </p>
+ <p>
+ The difference will only manifest for the user, if calls are made
+ with the timeout argument set to 'nowait' (see above).
+ </p>
+
+ <p>
+ When an completion message is received (<em>with</em> the result of the
+ operation), that means that the operation (connect, send, recv, ...)
+ has been <em>completed</em> (successfully or otherwise).
+ When a select message is received, that only means that the operation
+ <em>can now be completed</em>, via a call to, for instance,
+ <seemfa marker="#connect/1"><c>connect/1</c></seemfa>.
+ </p>
+
+ <p>The completion message has the format:</p>
+ <taglist>
+ <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
+ <tag></tag>
+ <item><c>{'$socket', socket(), completion, {CompletionHandle, CompletionStatus}}</c></item>
+ </taglist>
+ <p>The select message has the format: </p>
+ <taglist>
+ <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
+ <tag></tag>
+ <item><c>{'$socket', socket(), select, SelectHandle}</c></item>
+ </taglist>
+ <p>
+ Note that, on select "system", all other users are <em>locked out</em>
+ until the 'current user' has called the function (<c>recv</c>
+ for instance) and its return value shows that the operation has
+ completed.
+ Such an operation can also be cancelled with
<seemfa marker="#cancel/2"><c>cancel/2</c></seemfa>.
</p>
<p>
- Instead of <c>Timeout = nowait</c> it is equivalent to create a
- <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>)
+ Instead of <c>Timeout = nowait</c> it is equivalent to create a
+ <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
+ or
+ <seetype marker="socket#completion_handle"><c>CompletionHandle</c></seetype>
with
<seemfa marker="erts:erlang#make_ref/0"><c>make_ref()</c></seemfa>
and give as <c>Timeout</c>.
- This will then be the <c>SelectHandle</c> in the 'select' message,
- which enables a compiler optimization for receiving
+ This will then be the <c>Handle</c> in the 'completion' or 'select'
+ message, which enables a compiler optimization for receiving
a message containing a newly created <c>reference()</c>
(ignore the part of the message queue that had arrived
before the the <c>reference()</c> was created).
@@ -98,9 +147,11 @@
If, for instance, the socket has been closed (by another process),
<c>Info</c> will be <c>{SelectHandle, closed}</c>. </p>
</note>
+
<note>
- <p>There is currently <em>no</em> support for Windows. </p>
- <p>Support for IPv6 has been implemented but <em>not</em> tested. </p>
+ <p>The Windows support has currently <em>pre-release</em> status. </p>
+ <p>Support for IPv6 has been implemented but not <em>fully</em>
+ tested. </p>
<p>SCTP has only been partly implemented (and not tested). </p>
</note>
</description>
@@ -206,6 +257,41 @@
</p>
</desc>
</datatype>
+
+ <datatype>
+ <name name="completion_tag"/>
+ <desc>
+ <p>
+ A tag that describes the ongoing (completion) operation,
+ contained in the returned
+ <seetype marker="#completion_info"><c>completion_info()</c></seetype>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="completion_handle"/>
+ <desc>
+ <p>
+ A <c>reference()</c> that uniquely identifies
+ the (completion) operation, contained in the returned
+ <seetype marker="#completion_info"><c>completion_info()</c></seetype>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="completion_info"/>
+ <desc>
+ <p>
+ Returned by an operation that requires the caller to wait
+ for a
+ <seeerl marker="#asynchronous-call">completion message</seeerl>
+ containing the
+ <seetype marker="#completion_handle"><c>CompletionHandle</c></seetype>
+ <em>and</em> the result of the operation; the <c>CompletionStatus</c>.
+ </p>
+ </desc>
+ </datatype>
+
<datatype>
<name name="info"/>
<desc>
@@ -1399,36 +1485,73 @@
</p>
<p>
When there is no pending connection to return,
- the function will return
- <seetype marker="#select_info"><c>{select, <anno>SelectInfo</anno>}</c></seetype>,
- and the caller will later receive a select message,
- <c>{'$socket', Socket, select, SelectHandle}</c> (
- with the
- <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
- contained in the
- <seetype marker="#select_info">
- <c><anno>SelectInfo</anno></c>
- </seetype>
- ) when a client connects.
- A subsequent call to <c>accept/1,2</c>
- will then return the socket.
+ the function will return (on <i>Unix</i>)
+ <seetype marker="#select_info"><c>{select, <anno>SelectInfo</anno>}</c></seetype>
+ or (on <i>Windows</i>)
+ <seetype marker="#completion_info"><c>{completion, <anno>CompletionInfo</anno>}</c></seetype>,
+ and the caller will later receive either one of these messages
+ (depending on the platform) when the client connects:
</p>
+ <taglist>
+ <tag><c>select</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket', Socket, select, SelectHandle}</c>
+ (with the
+ <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
+ contained in the
+ <seetype marker="#select_info"><c><anno>SelectInfo</anno></c></seetype>).
+ </p>
+ <p>
+ A subsequent call to <c>accept/1,2</c> will then return the
+ socket.
+ </p>
+ </item>
+ <tag><c>completion</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket', Socket, completion, {CompletionHandle, CompletionStatus}}</c>
+ (with the
+ <seetype marker="socket#completion_handle"><c>CompletionHandle</c></seetype>
+ contained in the
+ <seetype marker="#completion_info"><c><anno>CompletionInfo</anno></c></seetype>).
+ </p>
+ <p>
+ The <em>result</em> of the accept will be in the
+ <c>CompletionStatus</c>.
+ </p>
+ </item>
+ </taglist>
<p>
- If the time-out argument is <c>SelectHandle</c>,
+ If the time-out argument is a <c>Handle</c>,
that term will be contained in a returned
- <c><anno>SelectInfo</anno></c>
- and the corresponding select message.
- The <c>SelectHandle</c> is presumed to be
+ <c><anno>SelectInfo</anno></c> or <c><anno>CompletionInfo</anno></c>
+ and the corresponding select or completion message.
+ The <c>Handle</c> is presumed to be
unique to this call.
</p>
<p>
- If the time-out argument is <c>nowait</c>, and a
- <c><anno>SelectInfo</anno></c> is returned,
- it will contain a
- <seetype marker="socket#select_handle">
- <c>select_handle()</c>
- </seetype> generated by the call.
- </p>
+ If the time-out argument is <c>nowait</c>:</p>
+ <taglist>
+ <tag>On <i>Unix</i></tag>
+ <item>
+ <p>
+ And a <c><anno>SelectInfo</anno></c> is returned,
+ it will contain a
+ <seetype marker="socket#select_handle"><c>select_handle()</c></seetype>
+ generated by the call.
+ </p>
+ </item>
+ <tag>On <i>Windows</i></tag>
+ <item>
+ <p>
+ And a <c><anno>CompletionInfo</anno></c> is returned,
+ it will contain a
+ <seetype marker="socket#completion_handle"><c>completion_handle()</c></seetype>
+ generated by the call.
+ </p>
+ </item>
+ </taglist>
<p>
If the caller doesn't want to wait for a connection,
it must immediately call
@@ -1467,10 +1590,10 @@
</func>
<func>
- <name name="cancel" arity="2" since="OTP 22.1"/>
+ <name name="cancel" arity="2" clause_i="1" since="OTP 22.1"/>
<fsummary>Cancel an asynchronous request.</fsummary>
<desc>
- <p>Cancel an asynchronous request.</p>
+ <p>Cancel an asynchronous (select) request.</p>
<p>
Call this function in order to cancel a previous
asynchronous call to, e.g.
@@ -1490,7 +1613,7 @@
has been completed.
</p>
<p>
- If <c><anno>SelectInfo</anno></c> does not match an
+ If <c><anno>SelectInfo</anno></c> does not match an
operation in progress for the calling process,
this function returns
<c>{error,&nbsp;{invalid,&nbsp;SelectInfo}}</c>.
@@ -1499,6 +1622,74 @@
</func>
<func>
+ <name name="cancel" arity="2" clause_i="2" since="OTP 26.0"/>
+ <fsummary>Cancel an asynchronous request.</fsummary>
+ <desc>
+ <p>Cancel an asynchronous (completion) request.</p>
+ <p>
+ Call this function in order to cancel a previous
+ asynchronous call to, e.g.
+ <seemfa marker="#recv/3"><c>recv/3</c></seemfa>.
+ </p>
+ <p>
+ An ongoing asynchronous operation blocks the socket
+ until the operation has been finished in good order,
+ or until it has been cancelled by this function.
+ </p>
+ <p>
+ Any other process that tries an operation
+ of the same basic type (accept / send / recv) will be
+ enqueued and notified with the regular <c>select</c>
+ mechanism for asynchronous operations
+ when the current operation and all enqueued before it
+ has been completed.
+ </p>
+ <p>
+ If <c><anno>CompletionInfo</anno></c> does not match an
+ operation in progress for the calling process,
+ this function returns
+ <c>{error,&nbsp;{invalid,&nbsp;CompletionInfo}}</c>.
+ </p>
+ </desc>
+ </func>
+
+ <!--
+ <func>
+ <name name="cancel" arity="2" clause_i="1" since="OTP 22.1"/>
+ <name name="cancel" arity="2" clause_i="2" since="OTP 26.0"/>
+ <fsummary>Cancel an asynchronous request.</fsummary>
+ <desc>
+ <p>Cancel an asynchronous request.</p>
+ <p>
+ Call this function in order to cancel a previous
+ asynchronous call to, e.g.
+ <seemfa marker="#recv/3"><c>recv/3</c></seemfa>.
+ </p>
+ <p>
+ An ongoing asynchronous operation blocks the socket
+ until the operation has been finished in good order,
+ or until it has been cancelled by this function.
+ </p>
+ <p>
+ Any other process that tries an operation
+ of the same basic type (accept / send / recv) will be
+ enqueued and notified with the regular <c>select</c>
+ mechanism for asynchronous operations
+ when the current operation and all enqueued before it
+ has been completed.
+ </p>
+ <p>
+ If <c><anno>SelectInfo</anno></c> or
+ <c><anno>CompletionInfo</anno></c> does not match an
+ operation in progress for the calling process,
+ this function returns
+ <c>{error,&nbsp;{invalid,&nbsp; SelectInfo | CompletionInfo}}</c>.
+ </p>
+ </desc>
+ </func>
+ -->
+
+ <func>
<name name="close" arity="1" since="OTP 22.0"/>
<fsummary>Close a socket.</fsummary>
<desc>
@@ -1533,6 +1724,11 @@
If a connection attempt is already in progress
(by another process), <c>{error, already}</c> is returned.
</p>
+ <note>
+ <p>
+ On <i>Windows</i> the socket has to be <em>bound</em>.
+ </p>
+ </note>
</desc>
</func>
@@ -1550,6 +1746,9 @@
</p>
<note>
<p>
+ On <i>Windows</i> the socket has to be <em>bound</em>.
+ </p>
+ <p>
Note that when this call has returned
<c>{error, timeout}</c> the connection state of the socket
is uncertain since the platform's network stack
@@ -1620,6 +1819,9 @@
<seemfa marker="#cancel/2"><c>cancel/2</c></seemfa>
to cancel the operation.
</p>
+ <note>
+ <p>On <i>Windows</i> the socket has to be <em>bound</em>.</p>
+ </note>
</desc>
</func>
@@ -1646,6 +1848,11 @@
but that incurs more overhead since the connect address
and time-out are processed in vain.
</p>
+ <note>
+ <p>
+ <em>Not</em> used on <i>Windows</i>.
+ </p>
+ </note>
</desc>
</func>
@@ -2151,6 +2358,9 @@
<fsummary>Listen for connections on a socket.</fsummary>
<desc>
<p>Listen for connections on a socket.</p>
+ <note>
+ <p>On <i>Windows</i> the socket has to be <em>bound</em>.</p>
+ </note>
</desc>
</func>
@@ -2466,7 +2676,8 @@
<desc>
<p>
Receives data from a socket,
- but returns a select continuation if the data
+ but returns a <c>select</c> or <c>completion</c> continuation
+ if the data
could not be returned immediately.
</p>
<p>
@@ -2474,44 +2685,72 @@
<seeerl marker="#recv-infinity">
infinite time-out <c>recv/1,2,3,4</c>
</seeerl>
- but if the data cannot be delivered immediately,
- the function returns
- <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype>,
- and the caller will then receive a select message,
- <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c>
- ( with the
- <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
- contained in the
- <seetype marker="#select_info">
- <c><anno>SelectInfo</anno></c>
- </seetype>
- ) when data has arrived.
- A subsequent call to <c>recv/1,2,3,4</c>
- will then return the data.
+ but if the data can be delivered immediately,
+ the function returns (on <i>Unix</i>)
+ <seetype marker="#select_info"><c>{select, &nbsp;<anno>SelectInfo</anno>}</c></seetype>
+ or (on <i>Windows</i>)
+ <seetype marker="#completion_info"><c>{completion, &nbsp;<anno>CompletionInfo</anno>}</c></seetype>,
+ and the caller will then receive one of these messages:
</p>
+
+ <taglist>
+ <tag><c>select</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c>
+ (with the
+ <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
+ that was contained in the
+ <seetype marker="#select_info"><c><anno>SelectInfo</anno></c></seetype>)
+ when data has arrived.
+ </p>
+ <p>
+ A subsequent call to <c>recv/1,2,3,4</c>
+ will then return the data.
+ </p>
+ </item>
+ <tag><c>completion</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket', Socket, completion, {CompletionHandle, CompletionStatus}}</c>
+ (with the
+ <seetype marker="socket#completion_handle"><c>CompletionHandle</c></seetype>
+ contained in the
+ <seetype marker="#completion_info"><c><anno>CompletionInfo</anno></c></seetype>).
+ </p>
+ <p>
+ The <em>result</em> of the receive will be in the
+ <c>CompletionStatus</c>.
+ </p>
+ </item>
+ </taglist>
+
<p>
- If the time-out argument is <c>SelectHandle</c>,
+ If <c>Handle</c> is a
+ <seetype marker="#select_handle"><c>select_handle()</c></seetype> or
+ <seetype marker="#completion_handle"><c>completion_handle()</c></seetype>,
that term will be contained in a returned
- <c><anno>SelectInfo</anno></c>
- and the corresponding select message.
- The <c>SelectHandle</c> is presumed to be
- unique to this call.
+ <c><anno>SelectInfo</anno></c> or <c><anno>CompletionInfo</anno></c>
+ and the corresponding (select or completion) message.
+ The <c>Handle</c> is presumed to be unique to this call.
</p>
<p>
If the time-out argument is <c>nowait</c>, and a
- <c><anno>SelectInfo</anno></c> is returned,
+ <c><anno>SelectInfo</anno></c> or
+ <c><anno>CompletionInfo</anno></c> is returned,
it will contain a
- <seetype marker="socket#select_handle">
- <c>select_handle()</c>
- </seetype> generated by the call.
+ <seetype marker="socket#select_handle"><c>select_handle()</c></seetype>
+ or
+ <seetype marker="socket#completion_handle"><c>completion_handle()</c></seetype>
+ generated by the call.
</p>
<p>
- Note that for a socket of type <c>stream</c>,
+ Note that for a socket of type <c>stream</c> (on <i>Unix</i>),
if <c><anno>Length</anno>&nbsp;&gt;&nbsp;0</c>
and only part of that amount of data is available,
the function will return
<seetype marker="#select_info">
- <c>{ok, {<anno>Data</anno>, <anno>SelectInfo</anno></c>
+ <c>{ok, {<anno>Data</anno>, <anno>SelectInfo</anno>}}</c>
</seetype>
with partial data. If the caller doesn't want to wait
for more data, it must immediately call
@@ -2605,7 +2844,7 @@
<desc>
<p>
Receives a message from a socket,
- but returns a select continuation if no message
+ but returns a select continuation or a completion term if no message
could be returned immediately.
</p>
<p>
@@ -2613,36 +2852,63 @@
<seeerl marker="#recvfrom-infinity">
infinite time-out <c>recvfrom/1,2,3,4</c>
</seeerl>
- but if no message cannot delivered immediately,
- the function returns
- <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype>,
- and the caller will then receive a select message,
- <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c>
- ( with the
- <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
- contained in the
- <seetype marker="#select_info">
- <c><anno>SelectInfo</anno></c>
- </seetype>
- ) when data has arrived.
- A subsequent call to <c>recvfrom/1,2,3,4</c>
- will then return the message.
+ but if no message can be delivered immediately,
+ the function returns (on <i>/Unix</i>)
+ <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype>
+ or (on <i>Windows</i>) <seetype marker="#completion_info"><c>{completion, &nbsp;<anno>CompletionInfo</anno>}</c></seetype>,
+ and the caller will then receive one of these messages:
</p>
+
+ <taglist>
+ <tag><c>select</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c>
+ (with the
+ <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
+ that was contained in the
+ <seetype marker="#select_info"><c><anno>SelectInfo</anno></c></seetype>)
+ when data has arrived.
+ </p>
+ <p>
+ A subsequent call to <c>recvfrom/1,2,3,4</c>
+ will then return the message.
+ </p>
+ </item>
+ <tag><c>completion</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket', Socket, completion, {CompletionHandle, CompletionStatus}}</c>
+ (with the
+ <seetype marker="socket#completion_handle"><c>CompletionHandle</c></seetype>
+ contained in the
+ <seetype marker="#completion_info"><c><anno>CompletionInfo</anno></c></seetype>).
+ </p>
+ <p>
+ The <em>result</em> of the receive will be in the
+ <c>CompletionStatus</c>.
+ </p>
+ </item>
+ </taglist>
+
<p>
- If the time-out argument is <c>SelectHandle</c>,
+ If the <c>Handle</c> is a
+ <seetype marker="#select_handle"><c>select_handle()</c></seetype> or
+ <seetype marker="#completion_handle"><c>completion_handle()</c></seetype>,
that term will be contained in a returned
- <c><anno>SelectInfo</anno></c>
- and the corresponding select message.
- The <c>SelectHandle</c> is presumed to be
- unique to this call.
+ <c><anno>SelectInfo</anno></c> or <c><anno>CompletionInfo</anno></c>
+ and the corresponding (select or completion) message.
+ The <c>Handle</c> is presumed to be unique to this call.
</p>
<p>
If the time-out argument is <c>nowait</c>, and a
- <c><anno>SelectInfo</anno></c> is returned,
+ <c><anno>SelectInfo</anno></c> or
+ <c><anno>CompletionInfo</anno></c> is returned,
it will contain a
- <seetype marker="socket#select_handle">
- <c>select_handle()</c>
- </seetype> generated by the call.
+ <seetype marker="socket#select_handle"><c>select_handle()</c></seetype>
+ or
+ <seetype marker="socket#completion_handle"><c>completion_handle()</c></seetype>
+ generated by the call.
</p>
<p>
If the caller doesn't want to wait for the data,
@@ -2745,44 +3011,70 @@
<desc>
<p>
Receives a message from a socket,
- but returns a select continuation if no message
+ but returns a select continuation or a completion term if no message
could be returned immediately.
</p>
<p>
The same as
- <seeerl marker="#recvfrom-infinity">
- infinite time-out <c>recvfrom/1,2,3,4</c>
+ <seeerl marker="#recvmsg-infinity">
+ infinite time-out <c>recvmsg/1,2,3,4</c>
</seeerl>
- but if no message cannot delivered immediately,
- the function returns
- <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype>,
- and the caller will then receive a select message,
- <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c>
- ( with the
- <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
- contained in the
- <seetype marker="#select_info">
- <c><anno>SelectInfo</anno></c>
- </seetype>
- ) when data has arrived.
- A subsequent call to <c>recvmsg/1,2,3,4,5</c>
- will then return the data.
+ but if no message can delivered immediately,
+ the function returns (on <i>Unix</i>)
+ <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype>
+ or (on <i>Windows</i>) <seetype marker="#completion_info"><c>{completion, &nbsp;<anno>CompletionInfo</anno>}</c></seetype>,
+ and the caller will then receive one of these messages:
</p>
+
+ <taglist>
+ <tag><c>select</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c>
+ (with the
+ <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
+ that was contained in the
+ <seetype marker="#select_info"><c><anno>SelectInfo</anno></c></seetype>)
+ when data has arrived.
+ </p>
+ <p>
+ A subsequent call to <c>recvmsg/1,2,3,4,5</c>
+ will then return the data.
+ </p>
+ </item>
+ <tag><c>completion</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket', Socket, completion, {CompletionHandle, CompletionStatus}}</c>
+ (with the
+ <seetype marker="socket#completion_handle"><c>CompletionHandle</c></seetype>
+ contained in the
+ <seetype marker="#completion_info"><c><anno>CompletionInfo</anno></c></seetype>).
+ </p>
+ <p>
+ The <em>result</em> of the receive will be in the
+ <c>CompletionStatus</c>.
+ </p>
+ </item>
+ </taglist>
<p>
- If the time-out argument is <c>SelectHandle</c>,
+ If the <c>Handle</c> is a
+ <seetype marker="#select_handle"><c>select_handle()</c></seetype> or
+ <seetype marker="#completion_handle"><c>completion_handle()</c></seetype>,
that term will be contained in a returned
- <c><anno>SelectInfo</anno></c>
- and the corresponding select message.
- The <c>SelectHandle</c> is presumed to be
- unique to this call.
+ <c><anno>SelectInfo</anno></c> or <c><anno>CompletionInfo</anno></c>
+ and the corresponding (select or completion) message.
+ The <c>Handle</c> is presumed to be unique to this call.
</p>
<p>
If the time-out argument is <c>nowait</c>, and a
- <c><anno>SelectInfo</anno></c> is returned,
+ <c><anno>SelectInfo</anno></c> or
+ <c><anno>CompletionInfo</anno></c> is returned,
it will contain a
- <seetype marker="socket#select_handle">
- <c>select_handle()</c>
- </seetype> generated by the call.
+ <seetype marker="socket#select_handle"><c>select_handle()</c></seetype>
+ or
+ <seetype marker="socket#completion_handle"><c>completion_handle()</c></seetype>
+ generated by the call.
</p>
<p>
If the caller doesn't want to wait for the data,
@@ -2932,7 +3224,7 @@
<desc>
<p>
Sends data on a connected socket,
- but returns a select continuation if the data
+ but returns completion <em>or</em> a select continuation if the data
could not be sent immediately.
</p>
<p>
@@ -2942,34 +3234,58 @@
</seeerl>
but if the data is not immediately accepted by
the platform network layer, the function returns
- <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype>,
- and the caller will then receive a select message,
- <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c> (
- with the
- <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
- that was contained in the
- <seetype marker="#select_info">
- <c><anno>SelectInfo</anno></c>
- </seetype>
- ) when there is room for more data.
- A subsequent call to <c>send/2-4</c> will then send the data.
- </p>
+ (on <i>Unix</i>) <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype> or (on <i>Windows</i>) <seetype marker="#completion_info"><c>{completion,&nbsp;<anno>CompletionInfo</anno>}</c></seetype>,
+ and the caller will then receive one of these messages: </p>
+
+ <taglist>
+ <tag><c>select</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c> (
+ with the
+ <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
+ that was contained in the
+ <seetype marker="#select_info">
+ <c><anno>SelectInfo</anno></c>
+ </seetype>
+ ) when there is room for more data. </p>
+ <p>A subsequent call to <c>send/2-4</c> will then send the data.
+ </p>
+ </item>
+ <tag><c>completion</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket', Socket, completion, {CompletionHandle, CompletionStatus}}</c>
+ (with the
+ <seetype marker="socket#completion_handle"><c>CompletionHandle</c></seetype>
+ contained in the
+ <seetype marker="#completion_info"><c><anno>CompletionInfo</anno></c></seetype>).
+ </p>
+ <p>
+ The <em>result</em> of the send will be in the
+ <c>CompletionStatus</c>.
+ </p>
+ </item>
+ </taglist>
<p>
- If <c>SelectHandle</c> is a
- <seetype marker="#select_handle"><c>select_handle()</c></seetype>,
+ If <c>Handle</c> is a
+ <seetype marker="#select_handle"><c>select_handle()</c></seetype> or
+ <seetype marker="#completion_handle"><c>completion_handle()</c></seetype>,
that term will be contained in a returned
- <c><anno>SelectInfo</anno></c>
- and the corresponding select message.
- The <c>SelectHandle</c> is presumed to be
+ <c><anno>SelectInfo</anno></c> or <c><anno>CompletionInfo</anno></c>
+ and the corresponding select or completion message.
+ The <c>Handle</c> is presumed to be
unique to this call.
</p>
<p>
- If <c>SelectHandle</c> is <c>nowait</c>, and a
- <c><anno>SelectInfo</anno></c> is returned,
+ If <c>Handle</c> is <c>nowait</c>, and a
+ <c><anno>SelectInfo</anno></c> or
+ <c><anno>CompletionInfo</anno></c> is returned,
it will contain a
- <seetype marker="socket#select_handle">
- <c>select_handle()</c>
- </seetype> generated by the call.
+ <seetype marker="socket#select_handle"><c>select_handle()</c></seetype>
+ or
+ <seetype marker="socket#completion_handle"><c>completion_handle()</c></seetype>
+ generated by the call.
</p>
<p>
If some of the data was sent, the function will return
@@ -2978,7 +3294,7 @@
{ok,&nbsp;{<anno>RestData</anno>,&nbsp;<anno>SelectInfo</anno>},
</c>
</seetype>
- which can only happen for a socket of
+ which can only happen (on <i>Unix</i>) for a socket of
<seetype marker="#type">type <c>stream</c></seetype>.
If the caller does not want to wait to send the rest of the data,
it should immediately cancel the operation with
@@ -3091,7 +3407,13 @@
The return value indicates the result from
the platform's network layer.
See <seeerl marker="#send-infinity"><c>send/2,3,4</c></seeerl>.
- </p>
+ </p>
+ <note>
+ <p>
+ On Windows, this function can only be used with
+ datagram and raw sockets.
+ </p>
+ </note>
</desc>
</func>
@@ -3116,6 +3438,12 @@
if no data or only some of it
was accepted by the platform's network layer.
</p>
+ <note>
+ <p>
+ On Windows, this function can only be used with
+ datagram and raw sockets.
+ </p>
+ </note>
</desc>
</func>
@@ -3129,7 +3457,7 @@
<desc>
<p>
Sends a message on a socket,
- but returns a select continuation if the data
+ but returns completion <em>or</em> a select continuation if the data
could not be sent immediately.
</p>
<p>
@@ -3138,35 +3466,60 @@
infinity time-out <c>sendmsg/2,3</c>
</seeerl>
but if the data is not immediately accepted by
- the platform network layer, the function returns
- <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype>,
- and the caller will then receive a select message,
- <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c> (
- with the
- <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
- that was contained in the
- <seetype marker="#select_info">
- <c><anno>SelectInfo</anno></c>
- </seetype>
- ) when there is room for more data.
- A subsequent call to <c>sendmsg/2-4</c> will then send the data.
+ the platform network layer,
+ the function returns
+ (on <i>Unix</i>) <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype> or (on <i>Windows</i>) <seetype marker="#completion_info"><c>{completion,&nbsp;<anno>CompletionInfo</anno>}</c></seetype>,
+ and the caller will then receive one of these messages:
</p>
+
+ <taglist>
+ <tag><c>select</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c> (
+ with the
+ <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
+ that was contained in the
+ <seetype marker="#select_info">
+ <c><anno>SelectInfo</anno></c>
+ </seetype>
+ ) when there is room for more data.
+ A subsequent call to <c>sendmsg/2-4</c> will then send the data.
+ </p>
+ </item>
+ <tag><c>completion</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket', Socket, completion, {CompletionHandle, CompletionStatus}}</c>
+ (with the
+ <seetype marker="socket#completion_handle"><c>CompletionHandle</c></seetype>
+ contained in the
+ <seetype marker="#completion_info"><c><anno>CompletionInfo</anno></c></seetype>).
+ </p>
+ <p>
+ The <em>result</em> of the send will be in the
+ <c>CompletionStatus</c>.
+ </p>
+ </item>
+ </taglist>
<p>
- If <c>SelectHandle</c>, is a
- <seetype marker="#select_handle"><c>select_handle()</c></seetype>,
+ If <c>Handle</c>, is a
+ <seetype marker="#select_handle"><c>select_handle()</c></seetype> or
+ <seetype marker="#completion_handle"><c>completion_handle()</c></seetype>,
that term will be contained in a returned
- <c><anno>SelectInfo</anno></c>
- and the corresponding select message.
- The <c>SelectHandle</c> is presumed to be
+ <c><anno>SelectInfo</anno></c> or <c><anno>CompletionInfo</anno></c>
+ and the corresponding select or completion message.
+ The <c>Handle</c> is presumed to be
unique to this call.
</p>
<p>
- If <c>SelectHandle</c> is <c>nowait</c>, and a
- <c><anno>SelectInfo</anno></c> is returned,
+ If <c>Timeout</c> is <c>nowait</c>, and a
+ <c><anno>SelectInfo</anno></c> or
+ <c><anno>CompletionInfo</anno></c> is returned,
it will contain a
- <seetype marker="socket#select_handle">
- <c>select_handle()</c>
- </seetype> generated by the call.
+ <seetype marker="socket#select_handle"><c>select_handle()</c></seetype> or
+ <seetype marker="socket#completion_handle"><c>completion_handle()</c></seetype>
+ generated by the call.
</p>
<p>
If some of the data was sent, the function will return
@@ -3181,6 +3534,12 @@
it should immediately cancel the operation with
<seemfa marker="#cancel/2"><c>cancel/2</c></seemfa>.
</p>
+ <note>
+ <p>
+ On Windows, this function can only be used with
+ datagram and raw sockets.
+ </p>
+ </note>
</desc>
</func>
@@ -3296,7 +3655,7 @@
<desc>
<p>
Sends data on a socket,
- but returns a select continuation if the data
+ but returns completion <em>or</em> a select continuation if the data
could not be sent immediately.
</p>
<p>
@@ -3307,34 +3666,58 @@
but if the data is not immediately accepted
by the platform network layer,
the function returns
- <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype>,
- and the caller will then receive a select message,
- <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c> (
- with the
- <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
- that was contained in the
- <seetype marker="#select_info">
- <c><anno>SelectInfo</anno></c>
- </seetype>
- ) when there is room for more data.
- A subsequent call to <c>sendto/3-5</c> will then send the data.
- </p>
+ (on <i>Unix</i>) <seetype marker="#select_info"><c>{select,&nbsp;<anno>SelectInfo</anno>}</c></seetype> or (on <i>Windows</i>) <seetype marker="#completion_info"><c>{completion,&nbsp;<anno>CompletionInfo</anno>}</c></seetype>,
+ and the caller will then receive one of these messages: </p>
+
+ <taglist>
+ <tag><c>select</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket',&nbsp;Socket,&nbsp;select,&nbsp;SelectHandle}</c> (
+ with the
+ <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>
+ that was contained in the
+ <seetype marker="#select_info">
+ <c><anno>SelectInfo</anno></c>
+ </seetype>
+ ) when there is room for more data. </p>
+ <p>A subsequent call to <c>send/2-4</c> will then send the data.
+ </p>
+ </item>
+ <tag><c>completion</c> message</tag>
+ <item>
+ <p>
+ <c>{'$socket', Socket, completion, {CompletionHandle, CompletionStatus}}</c>
+ (with the
+ <seetype marker="socket#completion_handle"><c>CompletionHandle</c></seetype>
+ contained in the
+ <seetype marker="#completion_info"><c><anno>CompletionInfo</anno></c></seetype>).
+ </p>
+ <p>
+ The <em>result</em> of the send will be in the
+ <c>CompletionStatus</c>.
+ </p>
+ </item>
+ </taglist>
<p>
- If <c>SelectHandle</c> is a
- <seetype marker="#select_handle"><c>select_handle()</c></seetype>,
+ If <c>Handle</c> is a
+ <seetype marker="#select_handle"><c>select_handle()</c></seetype> or
+ <seetype marker="#completion_handle"><c>completion_handle()</c></seetype>,
that term will be contained in a returned
- <c><anno>SelectInfo</anno></c>
- and the corresponding select message.
- The <c>SelectHandle</c> is presumed to be
+ <c><anno>SelectInfo</anno></c> or <c><anno>CompletionInfo</anno></c>
+ and the corresponding select or completion message.
+ The <c>Handle</c> is presumed to be
unique to this call.
</p>
<p>
- If <c>SelectHandle</c> is <c>nowait</c>, and a
- <c><anno>SelectInfo</anno></c> is returned,
+ If <c>Handle</c> is <c>nowait</c>, and a
+ <c><anno>SelectInfo</anno></c> or
+ <c><anno>CompletionInfo</anno></c> is returned,
it will contain a
- <seetype marker="socket#select_handle">
- <c>select_handle()</c>
- </seetype> generated by the call.
+ <seetype marker="socket#select_handle"><c>select_handle()</c></seetype>
+ or
+ <seetype marker="socket#completion_handle"><c>completion_handle()</c></seetype>
+ generated by the call.
</p>
<p>
If some of the data was sent, the function will return
@@ -3343,7 +3726,7 @@
{ok,&nbsp;{<anno>RestData</anno>,&nbsp;<anno>SelectInfo</anno>},
</c>
</seetype>
- which can only happen for a socket of
+ which can only happen (on <i>Unix</i>) for a socket of
<seetype marker="#type">type <c>stream</c></seetype>.
If the caller does not want to wait to send the rest of the data,
it should immediately cancel the operation with
diff --git a/lib/kernel/doc/src/socket_usage.xml b/lib/kernel/doc/src/socket_usage.xml
index 39d9ded99e..dfa551426d 100644
--- a/lib/kernel/doc/src/socket_usage.xml
+++ b/lib/kernel/doc/src/socket_usage.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2018</year><year>2021</year>
+ <year>2018</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -41,38 +41,79 @@
option used. </p>
<section>
<title>Asynchronous calls</title>
- <p>Some functions allow for an <i>asynchronous</i> call
- (<seeerl marker="socket#accept-nowait"><c>accept/2</c></seeerl>,
- <seeerl marker="socket#connect-nowait"><c>connect/3</c></seeerl>,
- <seeerl marker="socket#recv-nowait"><c>recv/3,4</c></seeerl>,
- <seeerl marker="socket#recvfrom-nowait"><c>recvfrom/3,4</c></seeerl>,
- <seeerl marker="socket#recvmsg-nowait"><c>recvmsg/2,3,5</c></seeerl>,
- <seeerl marker="socket#send-nowait"><c>send/3,4</c></seeerl>,
- <seeerl marker="socket#sendmsg-nowait"><c>sendmsg/3,4</c></seeerl> and
- <seeerl marker="socket#sendto-nowait"><c>sendto/4,5</c></seeerl>).
- This is achieved by setting the <c>Timeout</c> argument to
- <c>nowait</c>. For instance, if calling the
- <seeerl marker="socket#recv-nowait"><c>recv/3</c></seeerl>
- function with Timeout set to <c>nowait</c> (i.e.
- <c>recv(Sock, 0, nowait)</c>)
- when there is actually nothing to read, it will return with
- <c>{select, </c>
- <seetype marker="socket#select_info"><c>SelectInfo</c></seetype><c>}</c>
- (<c>SelectInfo</c> contains the
- <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>).
- When data eventually arrives a 'select message'
+ <p>
+ Some functions allow for an <i>asynchronous</i> call
+ (<seeerl marker="socket#accept-nowait"><c>accept/2</c></seeerl>,
+ <seeerl marker="socket#connect-nowait"><c>connect/3</c></seeerl>,
+ <seeerl marker="socket#recv-nowait"><c>recv/3,4</c></seeerl>,
+ <seeerl marker="socket#recvfrom-nowait"><c>recvfrom/3,4</c></seeerl>,
+ <seeerl marker="socket#recvmsg-nowait"><c>recvmsg/2,3,5</c></seeerl>,
+ <seeerl marker="socket#send-nowait"><c>send/3,4</c></seeerl>,
+ <seeerl marker="socket#sendmsg-nowait"><c>sendmsg/3,4</c></seeerl> and
+ <seeerl marker="socket#sendto-nowait"><c>sendto/4,5</c></seeerl>).
+ This is achieved by setting the <c>Timeout</c> argument to
+ <c>nowait</c>. For instance, if calling the
+ <seeerl marker="socket#recv-nowait"><c>recv/3</c></seeerl>
+ function with Timeout set to <c>nowait</c> (i.e.
+ <c>recv(Sock, 0, nowait)</c>)
+ when there is actually nothing to read, it will return with:
+ </p>
+ <taglist>
+ <tag>On Unix</tag>
+ <item>
+ <p>
+ <c>{select, </c>
+ <seetype marker="socket#select_info"><c>SelectInfo</c></seetype><c>}</c>
+ </p>
+ <p>
+ <c>SelectInfo</c> contains the
+ <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>.
+ </p>
+ </item>
+
+ <tag>On Windows</tag>
+ <item>
+ <p>
+ <c>{completion, </c>
+ <seetype marker="socket#completion_info"><c>CompletionInfo</c></seetype><c>}</c>
+ </p>
+ <p>
+ <c>CompletionInfo</c> contains the
+ <seetype marker="socket#completion_handle"><c>CompletionHandle</c></seetype>.
+ </p>
+ </item>
+ </taglist>
+ <p>When data eventually arrives a 'select' or 'completion' message
will be sent to the caller:</p>
<taglist>
- <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
- <tag></tag>
- <item><c>{'$socket', socket(), select, SelectHandle}</c></item>
+ <tag>On Unix</tag>
+ <item>
+ <p>
+ <c>{'$socket', socket(), select, SelectHandle}</c>
+ </p>
+ <p>
+ The caller can then make another
+ call to the recv function and now expect data.
+ </p>
+ <p>
+ Note that all other users are <em>locked out</em> until the
+ 'current user' has called the function (recv in this case).
+ So either immediately call the function or
+ <seemfa marker="socket#cancel/2"><c>cancel</c></seemfa>.
+ </p>
+ </item>
+
+ <tag>On Windows</tag>
+ <item>
+ <p>
+ <c>{'$socket', socket(), completion, {CompletionHandle, CompletionStatus}}</c>
+ </p>
+ <p>
+ The <c>CompletionStatus</c> contains the result of the
+ operation (read).
+ </p>
+ </item>
</taglist>
- <p>The caller can then make another
- call to the recv function and now expect data.</p>
- <p>Note that all other users are <em>locked out</em> until the
- 'current user' has called the function (recv in this case). So either
- immediately call the function or
- <seemfa marker="socket#cancel/2"><c>cancel</c></seemfa>. </p>
<p>The user must also be prepared to receive an abort message: </p>
<taglist>
<!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
@@ -101,6 +142,10 @@
<cell>select_handle()</cell>
</row>
<row>
+ <cell>completion</cell>
+ <cell>{completion_handle(), CompletionStatus}</cell>
+ </row>
+ <row>
<cell>abort</cell>
<cell>{select_handle(), Reason :: term()}</cell>
</row>
@@ -108,6 +153,8 @@
</table>
<p>The <c>select_handle()</c> is the same as was returned in the
<seetype marker="socket#select_info"><c>SelectInfo</c></seetype>. </p>
+ <p>The <c>completion_handle()</c> is the same as was returned in the
+ <seetype marker="socket#completion_info"><c>CompletionInfo</c></seetype>. </p>
</section>
</section>
@@ -191,8 +238,11 @@
<cell>default | pos_integer() | {pos_integer(), pos_ineteger()}</cell>
<cell>yes</cell>
<cell>yes</cell>
- <cell>'default' only valid for set.
- The tuple form is only valid for type 'stream' and protocol 'tcp'.</cell>
+ <cell>
+ The tuple format is <em>not</em> allowed on Windows.
+ 'default' only valid for set.
+ The tuple form is only valid for type 'stream' and protocol 'tcp'.
+ </cell>
</row>
<row>
<cell>rcvctrlbuf</cell>
diff --git a/lib/kernel/doc/src/specs.xml b/lib/kernel/doc/src/specs.xml
index e856351ce4..fd3877d9a8 100644
--- a/lib/kernel/doc/src/specs.xml
+++ b/lib/kernel/doc/src/specs.xml
@@ -35,7 +35,6 @@
<xi:include href="../specs/specs_rpc.xml"/>
<xi:include href="../specs/specs_seq_trace.xml"/>
<xi:include href="../specs/specs_socket.xml"/>
- <xi:include href="../specs/specs_user.xml"/>
<xi:include href="../specs/specs_wrap_log_reader.xml"/>
<xi:include href="../specs/specs_zlib_stub.xml"/>
</specs>
diff --git a/lib/kernel/doc/src/user.xml b/lib/kernel/doc/src/user.xml
deleted file mode 100644
index 38eac1c221..0000000000
--- a/lib/kernel/doc/src/user.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE erlref SYSTEM "erlref.dtd">
-
-<erlref>
- <header>
- <copyright>
- <year>1996</year>
- <year>2016</year>
- <holder>Ericsson AB, All Rights Reserved</holder>
- </copyright>
- <legalnotice>
- 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.
-
- The Initial Developer of the Original Code is Ericsson AB.
- </legalnotice>
-
- <title>user</title>
- <prepared>Robert Virding</prepared>
- <docno>1</docno>
- <date>1996-10-10</date>
- <rev>A</rev>
- </header>
- <module>user</module>
- <modulesummary>Standard I/O server.</modulesummary>
- <description>
- <p><c>user</c> is a server that responds to all messages
- defined in the I/O interface. The code in <c>user.erl</c> can be
- used as a model for building alternative I/O servers.</p>
- </description>
-</erlref>
-
diff --git a/lib/kernel/include/dist.hrl b/lib/kernel/include/dist.hrl
index 16320b64e9..4eef2bb3fc 100644
--- a/lib/kernel/include/dist.hrl
+++ b/lib/kernel/include/dist.hrl
@@ -72,6 +72,14 @@
?DFLAG_BIG_CREATION bor
?DFLAG_HANDSHAKE_23)).
+%% New mandatory flags in OTP 26
+-define(MANDATORY_DFLAGS_26, (?DFLAG_V4_NC bor
+ ?DFLAG_UNLINK_ID)).
+
+%% All mandatory flags
+-define(DFLAGS_MANDATORY, (?MANDATORY_DFLAGS_25 bor
+ ?MANDATORY_DFLAGS_26)).
+
%% Also update dflag2str() in ../src/dist_util.erl
%% when adding flags...
diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile
index 2149e89776..f67044f5e7 100644
--- a/lib/kernel/src/Makefile
+++ b/lib/kernel/src/Makefile
@@ -100,6 +100,8 @@ MODULES = \
inet_config \
inet_db \
inet_dns \
+ inet_epmd_dist \
+ inet_epmd_socket \
inet_gethost_native \
inet_hosts \
inet_parse \
@@ -138,9 +140,9 @@ MODULES = \
seq_trace \
socket \
standard_error \
- user \
user_drv \
user_sup \
+ prim_tty \
raw_file_io \
raw_file_io_compressed \
raw_file_io_inflate \
diff --git a/lib/kernel/src/application.erl b/lib/kernel/src/application.erl
index 3eb87d73c7..b3e6eea64f 100644
--- a/lib/kernel/src/application.erl
+++ b/lib/kernel/src/application.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,7 +19,8 @@
%%
-module(application).
--export([ensure_all_started/1, ensure_all_started/2, start/1, start/2,
+-export([ensure_all_started/1, ensure_all_started/2, ensure_all_started/3,
+ start/1, start/2,
start_boot/1, start_boot/2, stop/1,
load/1, load/2, unload/1, takeover/2,
which_applications/0, which_applications/1,
@@ -28,7 +29,7 @@
-export([set_env/1, set_env/2, set_env/3, set_env/4, unset_env/2, unset_env/3]).
-export([get_env/1, get_env/2, get_env/3, get_all_env/0, get_all_env/1]).
-export([get_key/1, get_key/2, get_all_key/0, get_all_key/1]).
--export([get_application/0, get_application/1, info/0]).
+-export([get_application/0, get_application/1, get_supervisor/1, info/0]).
-export([start_type/0]).
-export_type([start_type/0]).
@@ -117,28 +118,50 @@ unload(Application) ->
application_controller:unload_application(Application).
--spec ensure_all_started(Application) -> {'ok', Started} | {'error', Reason} when
- Application :: atom(),
+-spec ensure_all_started(Applications) -> {'ok', Started} | {'error', Reason} when
+ Applications :: atom() | [atom()],
Started :: [atom()],
Reason :: term().
ensure_all_started(Application) ->
- ensure_all_started(Application, temporary).
+ ensure_all_started(Application, temporary, serial).
--spec ensure_all_started(Application, Type) -> {'ok', Started} | {'error', Reason} when
- Application :: atom(),
+-spec ensure_all_started(Applications, Type) -> {'ok', Started} | {'error', AppReason} when
+ Applications :: atom() | [atom()],
Type :: restart_type(),
Started :: [atom()],
- Reason :: term().
+ AppReason :: {atom(), term()}.
ensure_all_started(Application, Type) ->
- case ensure_all_started([Application], [], Type, []) of
- {ok, Started} ->
- {ok, lists:reverse(Started)};
- {error, Reason, Started} ->
- _ = [stop(App) || App <- Started],
- {error, Reason}
+ ensure_all_started(Application, Type, serial).
+
+-spec ensure_all_started(Applications, Type, Mode) -> {'ok', Started} | {'error', AppReason} when
+ Applications :: atom() | [atom()],
+ Type :: restart_type(),
+ Mode :: serial | concurrent,
+ Started :: [atom()],
+ AppReason :: {atom(), term()}.
+ensure_all_started(Application, Type, Mode) when is_atom(Application) ->
+ ensure_all_started([Application], Type, Mode);
+ensure_all_started(Applications, Type, Mode) when is_list(Applications) ->
+ Opts = #{type => Type, mode => Mode},
+
+ case enqueue_or_start(Applications, [], #{}, [], [], Opts) of
+ {ok, DAG, _Pending, Started} when Mode =:= concurrent ->
+ ReqIDs = gen_server:reqids_new(),
+ concurrent_dag_start(maps:to_list(DAG), ReqIDs, [], Started, Type);
+ {ok, DAG, _Pending, Started} when Mode =:= serial ->
+ 0 = map_size(DAG),
+ {ok, lists:reverse(Started)};
+ {error, AppReason, Started} ->
+ _ = [stop(Name) || Name <- Started],
+ {error, AppReason}
end.
-ensure_all_started([App | Apps], OptionalApps, Type, Started) ->
+enqueue_or_start([App | Apps], Optional, DAG, Pending, Started, Opts)
+ when is_map_key(App, DAG) ->
+ %% We already traversed the application, so only add it as pending
+ enqueue_or_start(Apps, Optional, DAG, [App | Pending], Started, Opts);
+
+enqueue_or_start([App | Apps], Optional, DAG, Pending, Started, Opts) when is_atom(App) ->
%% In case the app is already running, we just skip it instead
%% of attempting to start all of its children - which would
%% have already been loaded and started anyway.
@@ -146,16 +169,16 @@ ensure_all_started([App | Apps], OptionalApps, Type, Started) ->
false ->
case ensure_loaded(App) of
{ok, Name} ->
- case ensure_started(Name, App, Type, Started) of
- {ok, NewStarted} ->
- ensure_all_started(Apps, OptionalApps, Type, NewStarted);
- Error ->
- Error
+ case enqueue_or_start_app(Name, App, DAG, Pending, Started, Opts) of
+ {ok, NewDAG, NewPending, NewStarted} ->
+ enqueue_or_start(Apps, Optional, NewDAG, NewPending, NewStarted, Opts);
+ ErrorAppReasonStarted ->
+ ErrorAppReasonStarted
end;
{error, {"no such file or directory", _} = Reason} ->
- case lists:member(App, OptionalApps) of
+ case lists:member(App, Optional) of
true ->
- ensure_all_started(Apps, OptionalApps, Type, Started);
+ enqueue_or_start(Apps, Optional, DAG, Pending, Started, Opts);
false ->
{error, {App, Reason}, Started}
end;
@@ -163,27 +186,91 @@ ensure_all_started([App | Apps], OptionalApps, Type, Started) ->
{error, {App, Reason}, Started}
end;
true ->
- ensure_all_started(Apps, OptionalApps, Type, Started)
+ enqueue_or_start(Apps, Optional, DAG, Pending, Started, Opts)
end;
-ensure_all_started([], _OptionalApps, _Type, Started) ->
- {ok, Started}.
+enqueue_or_start([], _Optional, DAG, Pending, Started, _Opts) ->
+ {ok, DAG, Pending, Started}.
-ensure_started(Name, App, Type, Started) ->
+enqueue_or_start_app(Name, App, DAG, Pending, Started, Opts) ->
+ #{type := Type, mode := Mode} = Opts,
{ok, ChildApps} = get_key(Name, applications),
{ok, OptionalApps} = get_key(Name, optional_applications),
+ {ok, Mod} = get_key(Name, mod),
+
+ %% If the application has no dependencies and we are either
+ %% on serial mode or the app does not have a module callback,
+ %% we start it immediately. At the end of serial mode, the DAG
+ %% is always empty.
+ case enqueue_or_start(ChildApps, OptionalApps, DAG, [], Started, Opts) of
+ {ok, NewDAG, NewPending, NewStarted}
+ when NewPending =:= [], (Mode =:= serial) or (Mod =:= []) ->
+ case application_controller:start_application(App, Type) of
+ ok ->
+ {ok, NewDAG, Pending, [App | NewStarted]};
+ {error, {already_started, App}} ->
+ {ok, NewDAG, Pending, NewStarted};
+ {error, Reason} ->
+ {error, {App, Reason}, NewStarted}
+ end;
+ {ok, NewDAG, NewPending, NewStarted} ->
+ {ok, NewDAG#{App => NewPending}, [App | Pending], NewStarted};
+ ErrorAppReasonStarted ->
+ ErrorAppReasonStarted
+ end.
- case ensure_all_started(ChildApps, OptionalApps, Type, Started) of
- {ok, NewStarted} ->
- case application_controller:start_application(Name, Type) of
- ok ->
- {ok, [App | NewStarted]};
- {error, {already_started, App}} ->
- {ok, NewStarted};
- {error, Reason} ->
- {error, {App, Reason}, NewStarted}
- end;
- Error ->
- Error
+concurrent_dag_start([], ReqIDs, _Done, Started, _Type) ->
+ wait_all_enqueued(ReqIDs, Started, false);
+concurrent_dag_start(Pending0, ReqIDs0, Done, Started0, Type) ->
+ {Pending1, ReqIDs1} = enqueue_dag_leaves(Pending0, ReqIDs0, [], Done, Type),
+
+ case wait_one_enqueued(ReqIDs1, Started0) of
+ {ok, App, ReqIDs2, Started1} ->
+ concurrent_dag_start(Pending1, ReqIDs2, [App], Started1, Type);
+ {error, AppReason, ReqIDs2} ->
+ wait_all_enqueued(ReqIDs2, Started0, AppReason)
+ end.
+
+enqueue_dag_leaves([{App, Children} | Rest], ReqIDs, Acc, Done, Type) ->
+ case Children -- Done of
+ [] ->
+ Req = application_controller:start_application_request(App, Type),
+ NewReqIDs = gen_server:reqids_add(Req, App, ReqIDs),
+ enqueue_dag_leaves(Rest, NewReqIDs, Acc, Done, Type);
+ NewChildren ->
+ NewAcc = [{App, NewChildren} | Acc],
+ enqueue_dag_leaves(Rest, ReqIDs, NewAcc, Done, Type)
+ end;
+enqueue_dag_leaves([], ReqIDs, Acc, _Done, _Type) ->
+ {Acc, ReqIDs}.
+
+wait_one_enqueued(ReqIDs0, Started) ->
+ case gen_server:wait_response(ReqIDs0, infinity, true) of
+ {{reply, ok}, App, ReqIDs1} ->
+ {ok, App, ReqIDs1, [App | Started]};
+ {{reply, {error, {already_started, App}}}, App, ReqIDs1} ->
+ {ok, App, ReqIDs1, Started};
+ {{reply, {error, Reason}}, App, ReqIDs1} ->
+ {error, {App, Reason}, ReqIDs1};
+ {{error, {Reason, _Ref}}, _App, _ReqIDs1} ->
+ exit(Reason);
+ no_request ->
+ exit(deadlock)
+ end.
+
+wait_all_enqueued(ReqIDs0, Started0, LastAppReason) ->
+ case gen_server:reqids_size(ReqIDs0) of
+ 0 when LastAppReason =:= false ->
+ {ok, lists:reverse(Started0)};
+ 0 ->
+ _ = [stop(App) || App <- Started0],
+ {error, LastAppReason};
+ _ ->
+ case wait_one_enqueued(ReqIDs0, Started0) of
+ {ok, _App, ReqIDs1, Started1} ->
+ wait_all_enqueued(ReqIDs1, Started1, LastAppReason);
+ {error, NewAppReason, ReqIDs1} ->
+ wait_all_enqueued(ReqIDs1, Started0, NewAppReason)
+ end
end.
-spec start(Application) -> 'ok' | {'error', Reason} when
@@ -397,13 +484,8 @@ get_env(Application, Key) ->
Def :: term(),
Val :: term().
-get_env(Application, Key, Def) ->
- case get_env(Application, Key) of
- {ok, Val} ->
- Val;
- undefined ->
- Def
- end.
+get_env(Application, Key, Default) ->
+ application_controller:get_env(Application, Key, Default).
-spec get_all_env() -> Env when
Env :: [{Par :: atom(), Val :: term()}].
@@ -466,6 +548,20 @@ get_application(Pid) when is_pid(Pid) ->
get_application(Module) when is_atom(Module) ->
application_controller:get_application_module(Module).
+-spec get_supervisor(Application) -> 'undefined' | {'ok', Pid} when
+ Pid :: pid(),
+ Application :: atom().
+
+get_supervisor(Application) when is_atom(Application) ->
+ case application_controller:get_master(Application) of
+ undefined -> undefined;
+ Master ->
+ case application_master:get_child(Master) of
+ {Root, _App} -> {ok, Root};
+ error -> undefined
+ end
+ end.
+
-spec start_type() -> StartType | 'undefined' | 'local' when
StartType :: start_type().
diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl
index 1f428c560b..60080c155e 100644
--- a/lib/kernel/src/application_controller.erl
+++ b/lib/kernel/src/application_controller.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,12 +22,13 @@
%% External exports
-export([start/1,
load_application/1, unload_application/1,
- start_application/2, start_boot_application/2, stop_application/1,
+ start_application/2, start_application_request/2,
+ start_boot_application/2, stop_application/1,
control_application/1, is_running/1,
change_application_data/2, prep_config_change/0, config_change/1,
which_applications/0, which_applications/1,
loaded_applications/0, info/0, set_env/2,
- get_pid_env/2, get_env/2, get_pid_all_env/1, get_all_env/1,
+ get_pid_env/2, get_env/2, get_env/3, get_pid_all_env/1, get_all_env/1,
get_pid_key/2, get_key/2, get_pid_all_key/1, get_all_key/1,
get_master/1, get_application/1, get_application_module/1,
start_type/1, permit_application/2, do_config_diff/2,
@@ -237,6 +238,9 @@ unload_application(AppName) ->
start_application(AppName, RestartType) ->
gen_server:call(?AC, {start_application, AppName, RestartType}, infinity).
+start_application_request(AppName, RestartType) ->
+ gen_server:send_request(?AC, {start_application, AppName, RestartType}).
+
%%-----------------------------------------------------------------
%% Func: is_running/1
%% Args: Application = atom()
@@ -343,11 +347,15 @@ get_pid_env(Master, Key) ->
end.
get_env(AppName, Key) ->
- case ets:lookup(ac_tab, {env, AppName, Key}) of
- [{_, Val}] -> {ok, Val};
- _ -> undefined
+ NotFound = make_ref(),
+ case ets:lookup_element(ac_tab, {env, AppName, Key}, 2, NotFound) of
+ NotFound -> undefined;
+ Val -> {ok, Val}
end.
+get_env(AppName, Key, Default) ->
+ ets:lookup_element(ac_tab, {env, AppName, Key}, 2, Default).
+
get_pid_all_env(Master) ->
case ets:match(ac_tab, {{application_master, '$1'}, Master}) of
[[AppName]] -> get_all_env(AppName);
@@ -442,10 +450,7 @@ start_type(Master) ->
get_master(AppName) ->
- case ets:lookup(ac_tab, {application_master, AppName}) of
- [{_, Pid}] -> Pid;
- _ -> undefined
- end.
+ ets:lookup_element(ac_tab, {application_master, AppName}, 2, undefined).
get_application(Master) ->
case ets:match(ac_tab, {{application_master, '$1'}, Master}) of
@@ -1541,6 +1546,11 @@ make_appl_i({application, Name, Opts}) when is_atom(Name), is_list(Opts) ->
end,
Phases = get_opt(start_phases, Opts, undefined),
Env = get_opt(env, Opts, []),
+ case check_para(Env, Name) of
+ ok -> ok;
+ {error, Reason} ->
+ throw({error, {invalid_options, Reason}})
+ end,
MaxP = get_opt(maxP, Opts, infinity),
MaxT = get_opt(maxT, Opts, infinity),
IncApps = get_opt(included_applications, Opts, []),
diff --git a/lib/kernel/src/application_master.erl b/lib/kernel/src/application_master.erl
index 35c9db170e..fa8fed6d28 100644
--- a/lib/kernel/src/application_master.erl
+++ b/lib/kernel/src/application_master.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -75,7 +75,7 @@ call(AppMaster, Req) ->
AppMaster ! {Req, Tag, self()},
receive
{'DOWN', Ref, process, _, _Info} ->
- ok;
+ error;
{Tag, Res} ->
erlang:demonitor(Ref, [flush]),
Res
diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl
index 001f3ac981..13fed657d1 100644
--- a/lib/kernel/src/code.erl
+++ b/lib/kernel/src/code.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,17 +26,15 @@
%% some implementation details. See also related modules: code_*.erl
%% in this directory.
--export([objfile_extension/0,
- set_path/1,
- get_path/0,
+-export([objfile_extension/0,
+ set_path/1, set_path/2,
+ get_path/0,
load_file/1,
ensure_loaded/1,
ensure_modules_loaded/1,
load_abs/1,
load_abs/2,
load_binary/3,
- load_native_partial/2,
- load_native_sticky/3,
atomic_load/1,
prepare_loading/1,
finish_loading/1,
@@ -59,18 +57,20 @@
unstick_mod/1,
is_sticky/1,
get_object_code/1,
- add_path/1,
- add_pathsz/1,
- add_paths/1,
- add_pathsa/1,
- add_patha/1,
- add_pathz/1,
+ add_paths/1, add_paths/2,
+ add_path/1, add_path/2,
+ add_pathsa/1, add_pathsa/2,
+ add_pathsz/1, add_pathsz/2,
+ add_patha/1, add_patha/2,
+ add_pathz/1, add_pathz/2,
del_path/1,
- replace_path/2,
- rehash/0,
+ del_paths/1,
+ clear_cache/0,
+ replace_path/2,replace_path/3,
start_link/0,
which/1,
get_doc/1,
+ get_doc/2,
where_is_file/1,
where_is_file/2,
set_primary_archive/4,
@@ -80,8 +80,8 @@
modified_modules/0,
get_mode/0]).
--deprecated({rehash,0,"the code path cache feature has been removed"}).
--deprecated({is_module_native,1,"HiPE has been removed"}).
+-removed({rehash,0,"the code path cache feature has been removed"}).
+-removed({is_module_native,1,"HiPE has been removed"}).
-export_type([load_error_rsn/0, load_ret/0]).
-export_type([prepared_code/0]).
@@ -93,6 +93,8 @@
%% Some types for basic exported functions of this module
%%----------------------------------------------------------------------------
+-define(is_cache(T), T =:= cache orelse T =:= nocache).
+-type cache() :: cache | nocache.
-type load_error_rsn() :: 'badfile'
| 'nofile'
| 'not_purged'
@@ -110,7 +112,7 @@
%%% BIFs
--export([get_chunk/2, is_module_native/1, module_md5/1]).
+-export([get_chunk/2, module_md5/1]).
-spec get_chunk(Bin, Chunk) ->
binary() | undefined when
@@ -134,16 +136,6 @@ get_chunk_1(Beam, Chunk) ->
erlang:raise(error, Reason, [{Mod,get_chunk,L,Loc}|Rest])
end.
--spec is_module_native(Module) -> true | false | undefined when
- Module :: module().
-is_module_native(Module) when is_atom(Module) ->
- case is_loaded(Module) of
- {file, _} -> false;
- false -> undefined
- end;
-is_module_native(Module) ->
- erlang:error(badarg, [Module]).
-
-spec module_md5(binary()) -> binary() | undefined.
module_md5(<<"FOR1", _/bits>>=Beam) ->
@@ -183,7 +175,10 @@ objfile_extension() ->
-spec load_file(Module) -> load_ret() when
Module :: module().
load_file(Mod) when is_atom(Mod) ->
- call({load_file,Mod}).
+ case get_object_code(Mod) of
+ error -> {error,nofile};
+ {Mod,Binary,File} -> load_module(Mod, File, Binary, false, false)
+ end.
-spec ensure_loaded(Module) -> {module, Module} | {error, What} when
Module :: module(),
@@ -191,20 +186,36 @@ load_file(Mod) when is_atom(Mod) ->
ensure_loaded(Mod) when is_atom(Mod) ->
case erlang:module_loaded(Mod) of
true -> {module, Mod};
- false -> call({ensure_loaded,Mod})
+ false ->
+ case get_object_code(Mod) of
+ error -> call({sync_ensure_on_load, Mod});
+ {Mod,Binary,File} ->
+ load_module(Mod, File, Binary, false, true)
+ end
end.
%% XXX File as an atom is allowed only for backwards compatibility.
-spec load_abs(Filename) -> load_ret() when
Filename :: file:filename().
load_abs(File) when is_list(File); is_atom(File) ->
- Mod = list_to_atom(filename:basename(File)),
- call({load_abs,File,Mod}).
+ load_abs(File, list_to_atom(filename:basename(File))).
%% XXX Filename is also an atom(), e.g. 'cover_compiled'
-spec load_abs(Filename :: loaded_filename(), Module :: module()) -> load_ret().
load_abs(File, M) when (is_list(File) orelse is_atom(File)), is_atom(M) ->
- call({load_abs,File,M}).
+ case modp(File) of
+ true ->
+ FileName0 = lists:concat([File, objfile_extension()]),
+ FileName = code_server:absname(FileName0),
+ case erl_prim_loader:get_file(FileName) of
+ {ok,Bin,_} ->
+ load_module(M, FileName, Bin, false, false);
+ error ->
+ {error, nofile}
+ end;
+ false ->
+ {error,badarg}
+ end.
%% XXX Filename is also an atom(), e.g. 'cover_compiled'
-spec load_binary(Module, Filename, Binary) ->
@@ -215,17 +226,26 @@ load_abs(File, M) when (is_list(File) orelse is_atom(File)), is_atom(M) ->
What :: badarg | load_error_rsn().
load_binary(Mod, File, Bin)
when is_atom(Mod), (is_list(File) orelse is_atom(File)), is_binary(Bin) ->
- call({load_binary,Mod,File,Bin}).
+ case modp(File) of
+ true -> load_module(Mod, File, Bin, true, false);
+ false -> {error,badarg}
+ end.
+
+load_module(Mod, File, Bin, Purge, EnsureLoaded) ->
+ case erlang:prepare_loading(Mod, Bin) of
+ {error,_}=Error ->
+ Error;
+ Prepared ->
+ call({load_module, Prepared, Mod, File, Purge, EnsureLoaded})
+ end.
--spec load_native_partial(Module :: module(), Binary :: binary()) -> load_ret().
-load_native_partial(Mod, Bin) when is_atom(Mod), is_binary(Bin) ->
- call({load_native_partial,Mod,Bin}).
+modp(Atom) when is_atom(Atom) -> true;
+modp(List) when is_list(List) -> int_list(List);
+modp(_) -> false.
--spec load_native_sticky(Module :: module(), Binary :: binary(), WholeModule :: 'false' | binary()) -> load_ret().
-load_native_sticky(Mod, Bin, WholeModule)
- when is_atom(Mod), is_binary(Bin),
- (is_binary(WholeModule) orelse WholeModule =:= false) ->
- call({load_native_sticky,Mod,Bin,WholeModule}).
+int_list([H|T]) when is_integer(H) -> int_list(T);
+int_list([_|_]) -> false;
+int_list([]) -> true.
-spec delete(Module) -> boolean() when
Module :: module().
@@ -242,7 +262,8 @@ soft_purge(Mod) when is_atom(Mod) -> call({soft_purge,Mod}).
-spec is_loaded(Module) -> {'file', Loaded} | false when
Module :: module(),
Loaded :: loaded_filename().
-is_loaded(Mod) when is_atom(Mod) -> call({is_loaded,Mod}).
+is_loaded(Mod) when is_atom(Mod) ->
+ code_server:is_loaded(Mod).
-spec get_object_code(Module) -> {Module, Binary, Filename} | error when
Module :: module(),
@@ -345,12 +366,18 @@ unstick_mod(Mod) when is_atom(Mod) -> call({unstick_mod,Mod}).
-spec is_sticky(Module) -> boolean() when
Module :: module().
-is_sticky(Mod) when is_atom(Mod) -> call({is_sticky,Mod}).
+is_sticky(Mod) when is_atom(Mod) ->
+ code_server:is_sticky(Mod).
--spec set_path(Path) -> 'true' | {'error', What} when
- Path :: [Dir :: file:filename()],
- What :: 'bad_directory'.
-set_path(PathList) when is_list(PathList) -> call({set_path,PathList}).
+-type set_path_ret() :: 'true' | {'error', 'bad_directory'}.
+-spec set_path(Path) -> set_path_ret() when
+ Path :: [Dir :: file:filename()].
+set_path(PathList) -> set_path(PathList, nocache).
+
+-spec set_path(Path, cache()) -> set_path_ret() when
+ Path :: [Dir :: file:filename()].
+set_path(PathList, Cache) when is_list(PathList), ?is_cache(Cache) ->
+ call({set_path,PathList,Cache}).
-spec get_path() -> Path when
Path :: [Dir :: file:filename()].
@@ -359,27 +386,51 @@ get_path() -> call(get_path).
-type add_path_ret() :: 'true' | {'error', 'bad_directory'}.
-spec add_path(Dir) -> add_path_ret() when
Dir :: file:filename().
-add_path(Dir) when is_list(Dir) -> call({add_path,last,Dir}).
+add_path(Dir) -> add_path(Dir, nocache).
+
+-spec add_path(Dir, cache()) -> add_path_ret() when
+ Dir :: file:filename().
+add_path(Dir, Cache) when is_list(Dir), ?is_cache(Cache) -> call({add_path,last,Dir,Cache}).
-spec add_pathz(Dir) -> add_path_ret() when
Dir :: file:filename().
-add_pathz(Dir) when is_list(Dir) -> call({add_path,last,Dir}).
+add_pathz(Dir) -> add_pathz(Dir, nocache).
+
+-spec add_pathz(Dir, cache()) -> add_path_ret() when
+ Dir :: file:filename().
+add_pathz(Dir, Cache) when is_list(Dir), ?is_cache(Cache) -> call({add_path,last,Dir,Cache}).
-spec add_patha(Dir) -> add_path_ret() when
Dir :: file:filename().
-add_patha(Dir) when is_list(Dir) -> call({add_path,first,Dir}).
+add_patha(Dir) -> add_patha(Dir, nocache).
+
+-spec add_patha(Dir, cache()) -> add_path_ret() when
+ Dir :: file:filename().
+add_patha(Dir, Cache) when is_list(Dir), ?is_cache(Cache) -> call({add_path,first,Dir,Cache}).
-spec add_paths(Dirs) -> 'ok' when
Dirs :: [Dir :: file:filename()].
-add_paths(Dirs) when is_list(Dirs) -> call({add_paths,last,Dirs}).
+add_paths(Dirs) -> add_paths(Dirs, nocache).
+
+-spec add_paths(Dirs, cache()) -> 'ok' when
+ Dirs :: [Dir :: file:filename()].
+add_paths(Dirs, Cache) when is_list(Dirs), ?is_cache(Cache) -> call({add_paths,last,Dirs,Cache}).
-spec add_pathsz(Dirs) -> 'ok' when
Dirs :: [Dir :: file:filename()].
-add_pathsz(Dirs) when is_list(Dirs) -> call({add_paths,last,Dirs}).
+add_pathsz(Dirs) -> add_pathsz(Dirs, nocache).
+
+-spec add_pathsz(Dirs, cache()) -> 'ok' when
+ Dirs :: [Dir :: file:filename()].
+add_pathsz(Dirs, Cache) when is_list(Dirs), ?is_cache(Cache) -> call({add_paths,last,Dirs,Cache}).
-spec add_pathsa(Dirs) -> 'ok' when
Dirs :: [Dir :: file:filename()].
-add_pathsa(Dirs) when is_list(Dirs) -> call({add_paths,first,Dirs}).
+add_pathsa(Dirs) -> add_pathsa(Dirs, nocache).
+
+-spec add_pathsa(Dirs, cache()) -> 'ok' when
+ Dirs :: [Dir :: file:filename()].
+add_pathsa(Dirs, Cache) when is_list(Dirs), ?is_cache(Cache) -> call({add_paths,first,Dirs,Cache}).
-spec del_path(NameOrDir) -> boolean() | {'error', What} when
NameOrDir :: Name | Dir,
@@ -388,22 +439,33 @@ add_pathsa(Dirs) when is_list(Dirs) -> call({add_paths,first,Dirs}).
What :: 'bad_name'.
del_path(Name) when is_list(Name) ; is_atom(Name) -> call({del_path,Name}).
--spec replace_path(Name, Dir) -> 'true' | {'error', What} when
+-spec del_paths(NamesOrDirs) -> 'ok' when
+ NamesOrDirs :: [Name | Dir],
+ Name :: atom(),
+ Dir :: file:filename().
+del_paths(Dirs) when is_list(Dirs) -> call({del_paths,Dirs}).
+
+-type replace_path_ret() :: 'true' |
+ {'error', 'bad_directory' | 'bad_name' | {'badarg',_}}.
+-spec replace_path(Name, Dir) -> replace_path_ret() when
Name:: atom(),
- Dir :: file:filename(),
- What :: 'bad_directory' | 'bad_name' | {'badarg',_}.
-replace_path(Name, Dir) when (is_atom(Name) orelse is_list(Name)),
- (is_atom(Dir) orelse is_list(Dir)) ->
- call({replace_path,Name,Dir}).
-
--spec rehash() -> 'ok'.
-rehash() ->
- cache_warning(),
- ok.
+ Dir :: file:filename().
+replace_path(Name, Dir) ->
+ replace_path(Name, Dir, nocache).
+
+-spec replace_path(Name, Dir, cache()) -> replace_path_ret() when
+ Name:: atom(),
+ Dir :: file:filename().
+replace_path(Name, Dir, Cache) when (is_atom(Name) orelse is_list(Name)),
+ (is_atom(Dir) orelse is_list(Dir)), ?is_cache(Cache) ->
+ call({replace_path,Name,Dir,Cache}).
-spec get_mode() -> 'embedded' | 'interactive'.
get_mode() -> call(get_mode).
+-spec clear_cache() -> ok.
+clear_cache() -> call(clear_cache).
+
%%%
%%% Loading of several modules in parallel.
%%%
@@ -431,8 +493,8 @@ ensure_modules_loaded_1(Ms0) ->
end,
ensure_modules_loaded_2(OnLoad, Error1).
-ensure_modules_loaded_2([{M,_}|Ms], Errors) ->
- case ensure_loaded(M) of
+ensure_modules_loaded_2([{M,{Prepared,File}}|Ms], Errors) ->
+ case call({load_module, Prepared, M, File, false, true}) of
{module,M} ->
ensure_modules_loaded_2(Ms, Errors);
{error,Err} ->
@@ -578,12 +640,12 @@ prepare_check_uniq_1([], [_|_]=Errors) ->
{error,Errors}.
partition_on_load(Prep) ->
- P = fun({_,{PC,_,_}}) ->
+ P = fun({_,{PC,_}}) ->
erlang:has_prepared_code_on_load(PC)
end,
lists:partition(P, Prep).
-verify_prepared([{M,{Prep,Name,_Native}}|T])
+verify_prepared([{M,{Prep,Name}}|T])
when is_atom(M), is_list(Name) ->
try erlang:has_prepared_code_on_load(Prep) of
false ->
@@ -599,62 +661,35 @@ verify_prepared([]) ->
verify_prepared(_) ->
error.
-finish_loading(Prepared0, EnsureLoaded) ->
- Prepared = [{M,{Bin,File}} || {M,{Bin,File,_}} <- Prepared0],
- Native0 = [{M,Code} || {M,{_,_,Code}} <- Prepared0,
- Code =/= undefined],
- case call({finish_loading,Prepared,EnsureLoaded}) of
- ok ->
- finish_loading_native(Native0);
- {error,Errors}=E when EnsureLoaded ->
- S0 = sofs:relation(Errors),
- S1 = sofs:domain(S0),
- R0 = sofs:relation(Native0),
- R1 = sofs:drestriction(R0, S1),
- Native = sofs:to_external(R1),
- finish_loading_native(Native),
- E;
- {error,_}=E ->
- E
- end.
-
-finish_loading_native([{Mod,Code}|Ms]) ->
- _ = load_native_partial(Mod, Code),
- finish_loading_native(Ms);
-finish_loading_native([]) ->
- ok.
+finish_loading(Prepared, EnsureLoaded) ->
+ call({finish_loading,Prepared,EnsureLoaded}).
load_mods([]) ->
{[],[]};
load_mods(Mods) ->
- Path = get_path(),
- F = prepare_loading_fun(),
- {ok,{Succ,Error0}} = erl_prim_loader:get_modules(Mods, F, Path),
- Error = [case E of
- badfile -> {M,E};
- _ -> {M,nofile}
- end || {M,E} <- Error0],
- {Succ,Error}.
+ F = fun(Mod) ->
+ case get_object_code(Mod) of
+ {Mod, Beam, File} -> prepare_loading(Mod, File, Beam);
+ error -> {error, nofile}
+ end
+ end,
+ do_par(F, Mods).
load_bins([]) ->
{[],[]};
load_bins(BinItems) ->
- F = prepare_loading_fun(),
+ F = fun({Mod, File, Beam}) -> prepare_loading(Mod, File, Beam) end,
do_par(F, BinItems).
--type prep_fun_type() :: fun((module(), file:filename(), binary()) ->
- {ok,_} | {error,_}).
+-spec prepare_loading(module(), file:filename(), binary()) ->
+ {ok,_} | {error,_}.
--spec prepare_loading_fun() -> prep_fun_type().
-
-prepare_loading_fun() ->
- fun(Mod, FullName, Beam) ->
- case erlang:prepare_loading(Mod, Beam) of
- {error,_}=Error ->
- Error;
- Prepared ->
- {ok,{Prepared,FullName,undefined}}
- end
+prepare_loading(Mod, FullName, Beam) ->
+ case erlang:prepare_loading(Mod, Beam) of
+ {error,_}=Error ->
+ Error;
+ Prepared ->
+ {ok,{Prepared,FullName}}
end.
do_par(Fun, L) ->
@@ -664,31 +699,36 @@ do_par(Fun, L) ->
Res
end.
--spec do_par_fun(prep_fun_type(), list()) -> fun(() -> no_return()).
+-type par_fun_type() :: fun((module() | {module(), file:filename(), binary()}) ->
+ {ok,_} | {error,_}).
+
+-spec do_par_fun(par_fun_type(), list()) -> fun(() -> no_return()).
do_par_fun(Fun, L) ->
fun() ->
- _ = [spawn_monitor(do_par_fun_2(Fun, Item)) ||
- Item <- L],
- exit(do_par_recv(length(L), [], []))
+ _ = [spawn_monitor(do_par_fun_each(Fun, Item)) || Item <- L],
+ exit(do_par_recv(length(L), [], []))
end.
--spec do_par_fun_2(prep_fun_type(),
- {module(),file:filename(),binary()}) ->
+-spec do_par_fun_each(par_fun_type(), term()) ->
fun(() -> no_return()).
-do_par_fun_2(Fun, Item) ->
+do_par_fun_each(Fun, Mod) when is_atom(Mod) ->
+ do_par_fun_each(Fun, Mod, Mod);
+do_par_fun_each(Fun, {Mod, _, _} = Item) ->
+ do_par_fun_each(Fun, Mod, Item).
+
+do_par_fun_each(Fun, Mod, Item) ->
fun() ->
- {Mod,Filename,Bin} = Item,
- try Fun(Mod, Filename, Bin) of
- {ok,Res} ->
- exit({good,{Mod,Res}});
- {error,Error} ->
- exit({bad,{Mod,Error}})
- catch
- _:Error ->
- exit({bad,{Mod,Error}})
- end
+ try Fun(Item) of
+ {ok,Res} ->
+ exit({good,{Mod,Res}});
+ {error,Error} ->
+ exit({bad,{Mod,Error}})
+ catch
+ _:Error ->
+ exit({bad,{Mod,Error}})
+ end
end.
do_par_recv(0, Good, Bad) ->
@@ -860,34 +900,52 @@ where_is_file(Tail, File, Path, Files) ->
Res :: #docs_v1{},
Reason :: non_existing | missing | file:posix().
get_doc(Mod) when is_atom(Mod) ->
+ get_doc(Mod, #{sources => [eep48, debug_info]}).
+
+get_doc(Mod, #{sources:=[Source|Sources]}=Options) ->
+ GetDoc = fun(Fn) -> R = case Source of
+ debug_info -> get_doc_chunk_from_ast(Fn);
+ eep48 -> get_doc_chunk(Fn, Mod)
+ end,
+ case R of
+ {error, missing} -> get_doc(Mod, Options#{sources=>Sources});
+ _ -> R
+ end
+ end,
case which(Mod) of
preloaded ->
- Fn = filename:join([code:lib_dir(erts),"ebin",atom_to_list(Mod) ++ ".beam"]),
- get_doc_chunk(Fn, Mod);
+ ErtsDir = code:lib_dir(erts),
+ ErtsEbinDir =
+ case filelib:is_dir(filename:join([ErtsDir,"ebin"])) of
+ true -> filename:join([ErtsDir,"ebin"]);
+ false -> filename:join([ErtsDir,"preloaded","ebin"])
+ end,
+ Fn = filename:join([ErtsEbinDir, atom_to_list(Mod) ++ ".beam"]),
+ GetDoc(Fn);
Error when is_atom(Error) ->
{error, Error};
Fn ->
- get_doc_chunk(Fn, Mod)
- end.
+ GetDoc(Fn)
+ end;
+get_doc(_, #{sources:=[]}) ->
+ {error, missing}.
get_doc_chunk(Filename, Mod) when is_atom(Mod) ->
case beam_lib:chunks(Filename, ["Docs"]) of
{error,beam_lib,{missing_chunk,_,_}} ->
- case get_doc_chunk(Filename, atom_to_list(Mod)) of
- {error,missing} ->
- get_doc_chunk_from_ast(Filename);
- Error ->
- Error
- end;
+ get_doc_chunk(Filename, atom_to_list(Mod));
{error,beam_lib,{file_error,_Filename,_Err}} ->
get_doc_chunk(Filename, atom_to_list(Mod));
{ok, {Mod, [{"Docs",Bin}]}} ->
{ok,binary_to_term(Bin)}
end;
get_doc_chunk(Filename, Mod) ->
+ RootDir = code:root_dir(),
case filename:dirname(Filename) of
Filename ->
{error,missing};
+ RootDir ->
+ {error,missing};
Dir ->
ChunkFile = filename:join([Dir,"doc","chunks",Mod ++ ".chunk"]),
case file:read_file(ChunkFile) of
@@ -904,24 +962,36 @@ get_doc_chunk_from_ast(Filename) ->
case beam_lib:chunks(Filename, [abstract_code]) of
{error,beam_lib,{missing_chunk,_,_}} ->
{error,missing};
+ {error,beam_lib,{file_error,_,_}} ->
+ {error, missing};
{ok, {_Mod, [{abstract_code,
{raw_abstract_v1, AST}}]}} ->
Docs = get_function_docs_from_ast(AST),
+ Types = get_type_docs_from_ast(AST),
{ok, #docs_v1{ anno = 0, beam_language = erlang,
module_doc = none,
- metadata = #{ generated => true, otp_doc_vsn => ?CURR_DOC_VERSION },
- docs = Docs }};
+ metadata = #{ generated => true, otp_doc_vsn => ?CURR_DOC_VERSION},
+ docs = Docs++Types }};
{ok, {_Mod, [{abstract_code,no_abstract_code}]}} ->
{error,missing};
Error ->
Error
end.
+get_type_docs_from_ast(AST) ->
+ lists:flatmap(fun(E) -> get_type_docs_from_ast(E, AST) end, AST).
+get_type_docs_from_ast({attribute, Anno, type, {TypeName, _, Ps}}=Meta, _) ->
+ Arity = length(Ps),
+ Signature = io_lib:format("~p/~p",[TypeName,Arity]),
+ [{{type, TypeName, Arity},Anno,[unicode:characters_to_binary(Signature)],none,#{signature => [Meta]}}];
+get_type_docs_from_ast(_, _) ->
+ [].
+
get_function_docs_from_ast(AST) ->
lists:flatmap(fun(E) -> get_function_docs_from_ast(E, AST) end, AST).
get_function_docs_from_ast({function,Anno,Name,Arity,_Code}, AST) ->
Signature = io_lib:format("~p/~p",[Name,Arity]),
- Specs = lists:filter(
+ Specs = lists:filter(
fun({attribute,_Ln,spec,{FA,_}}) ->
case FA of
{F,A} ->
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl
index af8531271f..f420337eb7 100644
--- a/lib/kernel/src/code_server.erl
+++ b/lib/kernel/src/code_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,7 +22,8 @@
%% This file holds the server part of the code_server.
-export([start_link/1,
- call/1,
+ call/1, absname/1,
+ is_loaded/1, is_sticky/1,
system_code_change/4,
error_msg/2, info_msg/2
]).
@@ -31,6 +32,7 @@
-include_lib("stdlib/include/ms_transform.hrl").
-import(lists, [foreach/2]).
+-define(moddb, code_server).
-type on_load_action() ::
fun((term(), state()) -> {'reply',term(),state()} |
@@ -41,7 +43,7 @@
-record(state, {supervisor :: pid(),
root :: file:name_all(),
- path :: [file:name_all()],
+ path :: [{file:name_all(), cache | nocache}],
moddb :: ets:table(),
namedb :: ets:table(),
mode = interactive :: 'interactive' | 'embedded',
@@ -58,6 +60,14 @@ start_link(Args) ->
{Ref,Res} -> Res
end.
+is_loaded(Mod) ->
+ case ets:lookup(?moddb, Mod) of
+ [{Mod,File}] -> {file,File};
+ [] -> false
+ end.
+
+is_sticky(Mod) ->
+ is_sticky(Mod, ?moddb).
%% -----------------------------------------------------------
%% Init the code_server process.
@@ -67,7 +77,7 @@ init(Ref, Parent, [Root,Mode]) ->
register(?MODULE, self()),
process_flag(trap_exit, true),
- Db = ets:new(code, [private]),
+ Db = ets:new(?moddb, [named_table, protected]),
foreach(fun (M) ->
%% Pre-loaded modules are always sticky.
ets:insert(Db, [{M,preloaded},{{sticky,M},true}])
@@ -251,22 +261,19 @@ handle_call({dir,Dir}, _From, S) ->
Resp = do_dir(Root,Dir,S#state.namedb),
{reply,Resp,S};
-handle_call({load_file,Mod}, From, St) when is_atom(Mod) ->
- load_file(Mod, From, St);
-
-handle_call({add_path,Where,Dir0}, _From,
+handle_call({add_path,Where,Dir0,Cache}, _From,
#state{namedb=Namedb,path=Path0}=S) ->
- {Resp,Path} = add_path(Where, Dir0, Path0, Namedb),
+ {Resp,Path} = add_path(Where, Dir0, Path0, Cache, Namedb),
{reply,Resp,S#state{path=Path}};
-handle_call({add_paths,Where,Dirs0}, _From,
+handle_call({add_paths,Where,Dirs0,Cache}, _From,
#state{namedb=Namedb,path=Path0}=S) ->
- {Resp,Path} = add_paths(Where, Dirs0, Path0, Namedb),
+ {Resp,Path} = add_paths(Where, Dirs0, Path0, Cache, Namedb),
{reply,Resp,S#state{path=Path}};
-handle_call({set_path,PathList}, _From,
+handle_call({set_path,PathList,Cache}, _From,
#state{root=Root,path=Path0,namedb=Namedb}=S) ->
- {Resp,Path,NewDb} = set_path(PathList, Path0, Namedb, Root),
+ {Resp,Path,NewDb} = set_path(PathList, Path0, Cache, Namedb, Root),
{reply,Resp,S#state{path=Path,namedb=NewDb}};
handle_call({del_path,Name}, _From,
@@ -274,35 +281,31 @@ handle_call({del_path,Name}, _From,
{Resp,Path} = del_path(Name, Path0, Namedb),
{reply,Resp,S#state{path=Path}};
-handle_call({replace_path,Name,Dir}, _From,
+handle_call({del_paths,Names}, _From,
+ #state{path=Path0,namedb=Namedb}=S) ->
+ {Resp,Path} = del_paths(Names, Path0, Namedb),
+ {reply,Resp,S#state{path=Path}};
+
+handle_call({replace_path,Name,Dir,Cache}, _From,
#state{path=Path0,namedb=Namedb}=S) ->
- {Resp,Path} = replace_path(Name, Dir, Path0, Namedb),
+ {Resp,Path} = replace_path(Name, Dir, Path0, Cache, Namedb),
{reply,Resp,S#state{path=Path}};
handle_call(get_path, _From, S) ->
- {reply,S#state.path,S};
-
-%% Messages to load, delete and purge modules/files.
-handle_call({load_abs,File,Mod}, From, S) when is_atom(Mod) ->
- case modp(File) of
- false ->
- {reply,{error,badarg},S};
- true ->
- load_abs(File, Mod, From, S)
- end;
-
-handle_call({load_binary,Mod,File,Bin}, From, S) when is_atom(Mod) ->
- do_load_binary(Mod, File, Bin, From, S);
-
-handle_call({ensure_loaded,Mod}, From, St) when is_atom(Mod) ->
- case erlang:module_loaded(Mod) of
- true ->
- {reply,{module,Mod},St};
- false when St#state.mode =:= interactive ->
- ensure_loaded(Mod, From, St);
- false ->
- {reply,{error,embedded},St}
- end;
+ {reply,[P || {P, _Cache} <- S#state.path],S};
+
+handle_call(clear_cache, _From, S) ->
+ Path = [{P, if is_atom(Cache) -> Cache; true -> cache end} ||
+ {P, Cache} <- S#state.path],
+ {reply,ok,S#state{path=Path}};
+
+handle_call({load_module,PC,Mod,File,Purge,EnsureLoaded}, From, S)
+ when is_atom(Mod) ->
+ case Purge andalso erlang:module_loaded(Mod) of
+ true -> do_purge(Mod);
+ false -> ok
+ end,
+ try_finish_module(File, Mod, PC, EnsureLoaded, From, S);
handle_call({delete,Mod}, _From, St) when is_atom(Mod) ->
case catch erlang:delete_module(Mod) of
@@ -319,23 +322,16 @@ handle_call({purge,Mod}, _From, St) when is_atom(Mod) ->
handle_call({soft_purge,Mod}, _From, St) when is_atom(Mod) ->
{reply,do_soft_purge(Mod),St};
-handle_call({is_loaded,Mod}, _From, St) when is_atom(Mod) ->
- {reply,is_loaded(Mod, St#state.moddb),St};
-
handle_call(all_loaded, _From, S) ->
Db = S#state.moddb,
{reply,all_loaded(Db),S};
-handle_call({get_object_code,Mod}, _From, St) when is_atom(Mod) ->
- case get_object_code(St, Mod) of
- {_,Bin,FName} -> {reply,{Mod,Bin,FName},St};
- Error -> {reply,Error,St}
+handle_call({get_object_code,Mod}, _From, St0) when is_atom(Mod) ->
+ case get_object_code(St0, Mod) of
+ {Bin,FName,St1} -> {reply,{Mod,Bin,FName},St1};
+ {error,St1} -> {reply,error,St1}
end;
-handle_call({is_sticky, Mod}, _From, S) ->
- Db = S#state.moddb,
- {reply, is_sticky(Mod,Db), S};
-
handle_call(stop,_From, S) ->
{stop,normal,stopped,S};
@@ -355,6 +351,20 @@ handle_call(get_mode, _From, S=#state{mode=Mode}) ->
handle_call({finish_loading,Prepared,EnsureLoaded}, _From, S) ->
{reply,finish_loading(Prepared, EnsureLoaded, S),S};
+%% Handles pending on_load events when we cannot find any
+%% object code in code:ensure_loaded/1. It's possible that
+%% the user has loaded a binary that has an on_load
+%% function, and in that case we need to suspend them until
+%% the on_load function finishes.
+handle_call({sync_ensure_on_load, Mod}, From, S) ->
+ handle_pending_on_load(
+ fun(_, St) ->
+ case erlang:module_loaded(Mod) of
+ true -> {reply, {module, Mod}, St};
+ false -> {reply, {error, nofile}, St}
+ end
+ end, Mod, From, S);
+
handle_call(Other,_From, S) ->
error_msg(" ** Codeserver*** ignoring ~w~n ",[Other]),
{noreply,S}.
@@ -494,26 +504,42 @@ try_ebin_dirs([]) ->
%%
%% Add the erl_prim_loader path.
-%%
%%
add_loader_path(IPath0,Mode) ->
{ok,PrimP0} = erl_prim_loader:get_path(),
+
+ %% All boot paths except for "." are cached by default but this can be disabled.
+ %% -pa and -pz are never cached by default.
case Mode of
embedded ->
- strip_path(PrimP0, Mode); % i.e. only normalize
+ cache_path(strip_path(PrimP0, Mode)); % i.e. only normalize
_ ->
Pa0 = get_arg(pa),
Pz0 = get_arg(pz),
Pa = patch_path(Pa0),
Pz = patch_path(Pz0),
- PrimP = patch_path(PrimP0),
- IPath = patch_path(IPath0),
+ PrimP = patch_path(PrimP0),
+ IPath = patch_path(IPath0),
+
+ Path0 = exclude_pa_pz(PrimP,Pa,Pz),
+ Path1 = strip_path(Path0, Mode),
+ Path2 = merge_path(Path1, IPath, []),
+ Path3 = cache_path(Path2),
+ add_pa_pz(Path3,Pa,Pz)
+ end.
+
+cache_path(Path) ->
+ Default = cache_boot_paths(),
+ [{P, do_cache_path(P, Default)} || P <- Path].
+
+do_cache_path(".", _) -> nocache;
+do_cache_path(_, Default) -> Default.
- P = exclude_pa_pz(PrimP,Pa,Pz),
- Path0 = strip_path(P, Mode),
- Path = add(Path0, IPath, []),
- add_pa_pz(Path,Pa,Pz)
+cache_boot_paths() ->
+ case init:get_argument(cache_boot_paths) of
+ {ok,[["false"]]} -> nocache;
+ _ -> cache
end.
patch_path(Path) ->
@@ -524,15 +550,10 @@ patch_path(Path) ->
%% As the erl_prim_loader path includes the -pa and -pz
%% directories they have to be removed first !!
+exclude_pa_pz(P0,Pa,[]) ->
+ P0 -- Pa;
exclude_pa_pz(P0,Pa,Pz) ->
- P1 = excl(Pa, P0),
- P = excl(Pz, lists:reverse(P1)),
- lists:reverse(P).
-
-excl([], P) ->
- P;
-excl([D|Ds], P) ->
- excl(Ds, lists:delete(D, P)).
+ lists:reverse(lists:reverse(P0 -- Pa) -- Pz).
%%
%% Keep only 'valid' paths in code server.
@@ -559,27 +580,32 @@ strip_path(_, _) ->
%% e.g. .../test-3.2/ebin should exclude .../test-*/ebin (and .../test/ebin).
%% Put the Path directories first in resulting path.
%%
-add(Path,["."|IPath],Acc) ->
- RPath = add1(Path,IPath,Acc),
+merge_path(Path,["."|IPath],Acc) ->
+ RPath = merge_path1(Path,IPath,Acc),
["."|lists:delete(".",RPath)];
-add(Path,IPath,Acc) ->
- add1(Path,IPath,Acc).
+merge_path(Path,IPath,Acc) ->
+ merge_path1(Path,IPath,Acc).
-add1([P|Path],IPath,Acc) ->
+merge_path1([P|Path],IPath,Acc) ->
case lists:member(P,Acc) of
true ->
- add1(Path,IPath,Acc); % Already added
+ merge_path1(Path,IPath,Acc); % Already added
false ->
IPath1 = exclude(P,IPath),
- add1(Path,IPath1,[P|Acc])
+ merge_path1(Path,IPath1,[P|Acc])
end;
-add1(_,IPath,Acc) ->
+merge_path1(_,IPath,Acc) ->
lists:reverse(Acc) ++ IPath.
add_pa_pz(Path0, Patha, Pathz) ->
- {_,Path1} = add_paths(first,Patha,Path0,false),
- {_,Path2} = add_paths(first,Pathz,lists:reverse(Path1),false),
- lists:reverse(Path2).
+ {_,Path1} = add_paths(first,Patha,Path0,nocache,false),
+ case Pathz of
+ [] ->
+ Path1;
+ _ ->
+ {_,Path2} = add_paths(first,Pathz,lists:reverse(Path1),nocache,false),
+ lists:reverse(Path2)
+ end.
get_arg(Arg) ->
case init:get_argument(Arg) of
@@ -693,22 +719,22 @@ do_check_path([Dir | Tail], PathChoice, ArchiveExt, Acc) ->
%%
%% Add new path(s).
%%
-add_path(Where,Dir,Path,NameDb) when is_atom(Dir) ->
- add_path(Where,atom_to_list(Dir),Path,NameDb);
-add_path(Where,Dir0,Path,NameDb) when is_list(Dir0) ->
+add_path(Where,Dir,Path,Cache,NameDb) when is_atom(Dir) ->
+ add_path(Where,atom_to_list(Dir),Path,Cache,NameDb);
+add_path(Where,Dir0,Path,Cache,NameDb) when is_list(Dir0) ->
case int_list(Dir0) of
true ->
Dir = filename:join([Dir0]), % Normalize
case check_path([Dir]) of
{ok, [NewDir]} ->
- {true, do_add(Where,NewDir,Path,NameDb)};
+ {true, do_add(Where,NewDir,Path,Cache,NameDb)};
Error ->
{Error, Path}
end;
false ->
{{error, bad_directory}, Path}
end;
-add_path(_,_,Path,_) ->
+add_path(_,_,Path,_,_) ->
{{error, bad_directory}, Path}.
@@ -718,16 +744,16 @@ add_path(_,_,Path,_) ->
%% If NameDb is false we should NOT update NameDb as it is done later
%% then the table is created :-)
%%
-do_add(first,Dir,Path,NameDb) ->
+do_add(first,Dir,Path,Cache,NameDb) ->
update(Dir, NameDb),
- [Dir|lists:delete(Dir,Path)];
-do_add(last,Dir,Path,NameDb) ->
- case lists:member(Dir,Path) of
+ [{Dir, Cache}|lists:keydelete(Dir,1,Path)];
+do_add(last,Dir,Path,Cache,NameDb) ->
+ case lists:keymember(Dir,1,Path) of
true ->
- Path;
+ lists:keyreplace(Dir,1,Path,{Dir,Cache});
false ->
maybe_update(Dir, NameDb),
- Path ++ [Dir]
+ Path ++ [{Dir,Cache}]
end.
%% Do not update if the same name already exists !
@@ -742,13 +768,14 @@ update(Dir, NameDb) ->
%%
%% Set a completely new path.
%%
-set_path(NewPath0, OldPath, NameDb, Root) ->
+set_path(NewPath0, OldPath, Cache, NameDb, Root) ->
NewPath = normalize(NewPath0),
case check_path(NewPath) of
{ok, NewPath2} ->
ets:delete(NameDb),
- NewDb = create_namedb(NewPath2, Root),
- {true, NewPath2, NewDb};
+ NewPath3 = [{P, Cache} || P <- NewPath2],
+ NewDb = create_namedb(NewPath3, Root),
+ {true, NewPath3, NewDb};
Error ->
{Error, OldPath, NameDb}
end.
@@ -796,7 +823,7 @@ create_namedb(Path, Root) ->
end,
Db.
-init_namedb([P|Path], Db) ->
+init_namedb([{P, _Cache}|Path], Db) ->
insert_dir(P, Db),
init_namedb(Path, Db);
init_namedb([], _) ->
@@ -874,7 +901,7 @@ del_path(Name0,Path,NameDb) ->
end
end.
-del_path1(Name,[P|Path],NameDb) ->
+del_path1(Name,[{P, Cache}|Path],NameDb) ->
case get_name(P) of
Name ->
delete_name(Name, NameDb),
@@ -887,12 +914,12 @@ del_path1(Name,[P|Path],NameDb) ->
end,
Path;
_ ->
- [P|del_path1(Name,Path,NameDb)]
+ [{P, Cache}|del_path1(Name,Path,NameDb)]
end;
del_path1(_,[],_) ->
[].
-insert_old_shadowed(Name, [P|Path], NameDb) ->
+insert_old_shadowed(Name, [{P, _Cache}|Path], NameDb) ->
case get_name(P) of
Name -> insert_name(Name, P, NameDb);
_ -> insert_old_shadowed(Name, Path, NameDb)
@@ -904,27 +931,27 @@ insert_old_shadowed(_, [], _) ->
%% Replace an old occurrence of an directory with name .../Name[-*].
%% If it does not exist, put the new directory last in Path.
%%
-replace_path(Name,Dir,Path,NameDb) ->
+replace_path(Name,Dir,Path,Cache,NameDb) ->
case catch check_pars(Name,Dir) of
{ok,N,D} ->
- {true,replace_path1(N,D,Path,NameDb)};
+ {true,replace_path1(N,D,Path,Cache,NameDb)};
{'EXIT',_} ->
{{error,{badarg,[Name,Dir]}},Path};
Error ->
{Error,Path}
end.
-replace_path1(Name,Dir,[P|Path],NameDb) ->
+replace_path1(Name,Dir,[{P, _}=Pair|Path],Cache,NameDb) ->
case get_name(P) of
Name ->
insert_name(Name, Dir, NameDb),
- [Dir|Path];
+ [{Dir, Cache}|Path];
_ ->
- [P|replace_path1(Name,Dir,Path,NameDb)]
+ [Pair|replace_path1(Name,Dir,Path,Cache,NameDb)]
end;
-replace_path1(Name, Dir, [], NameDb) ->
+replace_path1(Name, Dir, [], Cache, NameDb) ->
insert_name(Name, Dir, NameDb),
- [Dir].
+ [{Dir, Cache}].
check_pars(Name,Dir) ->
N = to_list(Name),
@@ -1076,55 +1103,46 @@ get_mods([], _) -> [].
is_sticky(Mod, Db) ->
erlang:module_loaded(Mod) andalso (ets:lookup(Db, {sticky, Mod}) =/= []).
-add_paths(Where,[Dir|Tail],Path,NameDb) ->
- {_,NPath} = add_path(Where,Dir,Path,NameDb),
- add_paths(Where,Tail,NPath,NameDb);
-add_paths(_,_,Path,_) ->
+add_paths(Where,[Dir|Tail],Path,Cache,NameDb) ->
+ {_,NPath} = add_path(Where,Dir,Path,Cache,NameDb),
+ add_paths(Where,Tail,NPath,Cache,NameDb);
+add_paths(_,_,Path,_,_) ->
{ok,Path}.
-do_load_binary(Module, File, Binary, From, St) ->
- case modp(File) andalso is_binary(Binary) of
- true ->
- case erlang:module_loaded(Module) of
- true -> do_purge(Module);
- false -> ok
- end,
- try_load_module(File, Module, Binary, From, St);
- false ->
- {reply,{error,badarg},St}
- end.
-
-modp(Atom) when is_atom(Atom) -> true;
-modp(List) when is_list(List) -> int_list(List);
-modp(_) -> false.
-
-load_abs(File, Mod, From, St) ->
- Ext = objfile_extension(),
- FileName0 = lists:concat([File, Ext]),
- FileName = absname(FileName0),
- case erl_prim_loader:get_file(FileName) of
- {ok,Bin,_} ->
- try_load_module(FileName, Mod, Bin, From, St);
- error ->
- {reply,{error,nofile},St}
- end.
+del_paths([Name | Names],Path,NameDb) ->
+ {_,NPath} = del_path(Name, Path, NameDb),
+ del_paths(Names,NPath,NameDb);
+del_paths(_,Path,_) ->
+ {ok,Path}.
-try_load_module(File, Mod, Bin, From, St) ->
+try_finish_module(File, Mod, PC, true, From, St) ->
Action = fun(_, S) ->
- try_load_module_1(File, Mod, Bin, From, S)
+ case erlang:module_loaded(Mod) of
+ true ->
+ {reply,{module,Mod},S};
+ false when S#state.mode =:= interactive ->
+ try_finish_module_1(File, Mod, PC, From, S);
+ false ->
+ {reply,{error,embedded},S}
+ end
+ end,
+ handle_pending_on_load(Action, Mod, From, St);
+try_finish_module(File, Mod, PC, false, From, St) ->
+ Action = fun(_, S) ->
+ try_finish_module_1(File, Mod, PC, From, S)
end,
handle_pending_on_load(Action, Mod, From, St).
-try_load_module_1(File, Mod, Bin, From, #state{moddb=Db}=St) ->
+try_finish_module_1(File, Mod, PC, From, #state{moddb=Db}=St) ->
case is_sticky(Mod, Db) of
true -> %% Sticky file reject the load
error_msg("Can't load module '~w' that resides in sticky dir\n",[Mod]),
{reply,{error,sticky_directory},St};
false ->
- try_load_module_2(File, Mod, Bin, From, undefined, St)
+ try_finish_module_2(File, Mod, PC, From, St)
end.
-try_load_module_2(File, Mod, Bin, From, _Architecture, St0) ->
+try_finish_module_2(File, Mod, PC, From, St0) ->
Action = fun({module,_}=Module, #state{moddb=Db}=S) ->
ets:insert(Db, {Mod,File}),
{reply,Module,S};
@@ -1134,69 +1152,73 @@ try_load_module_2(File, Mod, Bin, From, _Architecture, St0) ->
error_msg("Loading of ~ts failed: ~p\n", [File, What]),
{reply,Error,S}
end,
- Res = erlang:load_module(Mod, Bin),
+ Res = case erlang:finish_loading([PC]) of
+ ok ->
+ {module,Mod};
+ {Error,[Mod]} ->
+ {error,Error}
+ end,
handle_on_load(Res, Action, Mod, From, St0).
int_list([H|T]) when is_integer(H) -> int_list(T);
int_list([_|_]) -> false;
int_list([]) -> true.
-ensure_loaded(Mod, From, St0) ->
- Action = fun(_, S) ->
- case erlang:module_loaded(Mod) of
- true ->
- {reply,{module,Mod},S};
- false ->
- load_file_1(Mod, From, S)
- end
- end,
- handle_pending_on_load(Action, Mod, From, St0).
-
-load_file(Mod, From, St0) ->
- Action = fun(_, S) ->
- load_file_1(Mod, From, S)
- end,
- handle_pending_on_load(Action, Mod, From, St0).
-
-load_file_1(Mod, From, St) ->
- case get_object_code(St, Mod) of
- error ->
- {reply,{error,nofile},St};
- {Mod,Binary,File} ->
- try_load_module_1(File, Mod, Binary, From, St)
- end.
-
-get_object_code(#state{path=Path}, Mod) when is_atom(Mod) ->
+get_object_code(#state{path=Path} = St, Mod) when is_atom(Mod) ->
ModStr = atom_to_list(Mod),
case erl_prim_loader:is_basename(ModStr) of
true ->
- mod_to_bin(Path, Mod, ModStr ++ objfile_extension());
+ case mod_to_bin(Path, ModStr ++ objfile_extension(), []) of
+ {Binary, File, NewPath} ->
+ {Binary, File, St#state{path=NewPath}};
+
+ {error, NewPath} ->
+ {error, St#state{path=NewPath}}
+ end;
+
false ->
- error
+ {error, St}
end.
-mod_to_bin([Dir|Tail], Mod, ModFile) ->
- File = filename:append(Dir, ModFile),
- case erl_prim_loader:get_file(File) of
- error ->
- mod_to_bin(Tail, Mod, ModFile);
- {ok,Bin,_} ->
- case filename:pathtype(File) of
- absolute ->
- {Mod,Bin,File};
- _ ->
- {Mod,Bin,absname(File)}
- end
+mod_to_bin([{Dir, Cache0}|Tail], ModFile, Acc) ->
+ case with_cache(Cache0, Dir, ModFile) of
+ {true, Cache1} ->
+ File = filename:append(Dir, ModFile),
+
+ case erl_prim_loader:get_file(File) of
+ error ->
+ mod_to_bin(Tail, ModFile, [{Dir, Cache1} | Acc]);
+
+ {ok,Bin,_} ->
+ Path = lists:reverse(Acc, [{Dir, Cache1} | Tail]),
+
+ case filename:pathtype(File) of
+ absolute -> {Bin, File, Path};
+ _ -> {Bin, absname(File), Path}
+ end
+ end;
+ {false, Cache1} ->
+ mod_to_bin(Tail, ModFile, [{Dir, Cache1} | Acc])
end;
-mod_to_bin([], Mod, ModFile) ->
+mod_to_bin([], ModFile, Acc) ->
%% At last, try also erl_prim_loader's own method
case erl_prim_loader:get_file(ModFile) of
- error ->
- error; % No more alternatives !
- {ok,Bin,FName} ->
- {Mod,Bin,absname(FName)}
+ error ->
+ {error, lists:reverse(Acc)}; % No more alternatives !
+ {ok,Bin,FName} ->
+ {Bin, absname(FName), lists:reverse(Acc)}
end.
+with_cache(nocache, _Dir, _ModFile) ->
+ {true, nocache};
+with_cache(cache, Dir, ModFile) ->
+ case erl_prim_loader:list_dir(Dir) of
+ {ok, Entries} -> with_cache(maps:from_keys(Entries, []), Dir, ModFile);
+ error -> {false, cache}
+ end;
+with_cache(Cache, _Dir, ModFile) when is_map(Cache) ->
+ {is_map_key(ModFile, Cache), Cache}.
+
absname(File) ->
case erl_prim_loader:get_cwd() of
{ok,Cwd} -> absname(File, Cwd);
@@ -1232,13 +1254,6 @@ absname_vr([[X, $:]|Name], _, _AbsBase) ->
end,
absname(filename:join(Name), Dcwd).
-
-is_loaded(M, Db) ->
- case ets:lookup(Db, M) of
- [{M,File}] -> {file,File};
- [] -> false
- end.
-
do_purge(Mod) ->
{_WasOld, DidKill} = erts_code_purger:purge(Mod),
DidKill.
@@ -1445,4 +1460,4 @@ archive_extension() ->
init:archive_extension().
to_list(X) when is_list(X) -> X;
-to_list(X) when is_atom(X) -> atom_to_list(X).
+to_list(X) when is_atom(X) -> atom_to_list(X). \ No newline at end of file
diff --git a/lib/kernel/src/disk_log.erl b/lib/kernel/src/disk_log.erl
index 19225c9be5..c6d8922e8f 100644
--- a/lib/kernel/src/disk_log.erl
+++ b/lib/kernel/src/disk_log.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -24,13 +24,13 @@
-export([start/0, istart_link/1,
log/2, log_terms/2, blog/2, blog_terms/2,
alog/2, alog_terms/2, balog/2, balog_terms/2,
- close/1, lclose/1, lclose/2, sync/1, open/1,
+ close/1, sync/1, open/1,
truncate/1, truncate/2, btruncate/2,
reopen/2, reopen/3, breopen/3, inc_wrap_file/1, change_size/2,
change_notify/3, change_header/2,
chunk/2, chunk/3, bchunk/2, bchunk/3, chunk_step/3, chunk_info/1,
block/1, block/2, unblock/1, info/1, format_error/1,
- accessible_logs/0, all/0]).
+ all/0, next_file/1]).
%% Internal exports
-export([init/2, internal_open/2,
@@ -47,9 +47,11 @@
-export_type([continuation/0]).
--deprecated([{accessible_logs, 0, "use disk_log:all/0 instead"},
- {lclose, 1, "use disk_log:close/1 instead"},
- {lclose, 2, "use disk_log:close/1 instead"}]).
+-deprecated([{inc_wrap_file, 1, "use disk_log:next_file/1 instead"}]).
+
+-removed([{accessible_logs, 0, "use disk_log:all/0 instead"},
+ {lclose, 1, "use disk_log:close/1 instead"},
+ {lclose, 2, "use disk_log:close/1 instead"}]).
-type dlog_state_error() :: 'ok' | {'error', term()}.
@@ -185,22 +187,6 @@ balog_terms(Log, Bytess) ->
close(Log) ->
req(Log, close).
--type lclose_error_rsn() :: 'no_such_log'
- | {'file_error', file:filename(), file_error()}.
-
--spec lclose(Log) -> 'ok' | {'error', lclose_error_rsn()} when
- Log :: log().
-lclose(Log) ->
- lclose(Log, node()).
-
--spec lclose(Log, Node) -> 'ok' | {'error', lclose_error_rsn()} when
- Log :: log(),
- Node :: node().
-lclose(Log, Node) when node() =:= Node ->
- req(Log, close);
-lclose(_Log, _Node) ->
- {error, no_such_log}.
-
-type trunc_error_rsn() :: 'no_such_log' | 'nonode'
| {'read_only_mode', log()}
| {'blocked_log', log()}
@@ -253,12 +239,20 @@ reopen(Log, NewFile, NewHead) ->
breopen(Log, NewFile, NewHead) ->
req(Log, {reopen, NewFile, {ok, ensure_binary(NewHead)}, breopen, 3}).
--type inc_wrap_error_rsn() :: 'no_such_log' | 'nonode'
+-type next_file_error_rsn() :: 'no_such_log' | 'nonode'
| {'read_only_mode', log()}
| {'blocked_log', log()} | {'halt_log', log()}
+ | {'rotate_log', log()}
| {'invalid_header', invalid_header()}
| {'file_error', file:filename(), file_error()}.
+-spec next_file(Log) -> 'ok' | {'error', next_file_error_rsn()} when
+ Log :: log().
+next_file(Log) ->
+ req(Log, next_file).
+
+-type inc_wrap_error_rsn() :: next_file_error_rsn().
+
-spec inc_wrap_file(Log) -> 'ok' | {'error', inc_wrap_error_rsn()} when
Log :: log().
inc_wrap_file(Log) ->
@@ -528,11 +522,6 @@ chunk_info(More = #continuation{}) ->
chunk_info(BadCont) ->
{error, {no_continuation, BadCont}}.
--spec accessible_logs() -> {[Log], []} when
- Log :: log().
-accessible_logs() ->
- {disk_log_server:all(), []}.
-
-spec all() -> [Log] when
Log :: log().
all() ->
@@ -592,6 +581,8 @@ check_arg([], Res) ->
{OldSize, Version} =
disk_log_1:read_size_file_version(Res#arg.file),
check_wrap_arg(Ret, OldSize, Version);
+ Res#arg.type =:= rotate ->
+ {ok, Res#arg{format = external}};
true ->
Ret
end;
@@ -620,6 +611,8 @@ check_arg([{size, {MaxB,MaxF}}|Tail], Res) when is_integer(MaxB),
MaxB > 0, MaxB =< ?MAX_BYTES,
MaxF > 0, MaxF < ?MAX_FILES ->
check_arg(Tail, Res#arg{size = {MaxB, MaxF}});
+check_arg([{type, rotate}|Tail], Res) ->
+ check_arg(Tail, Res#arg{type = rotate});
check_arg([{type, wrap}|Tail], Res) ->
check_arg(Tail, Res#arg{type = wrap});
check_arg([{type, halt}|Tail], Res) ->
@@ -879,18 +872,49 @@ handle({From, inc_wrap_file}=Message, S) ->
reply(From, {error, {read_only_mode, L#log.name}}, S);
#log{type = halt}=L ->
reply(From, {error, {halt_log, L#log.name}}, S);
+ #log{type = rotate}=L ->
+ reply(From, {error, {rotate_log, L#log.name}}, S);
#log{status = ok} when S#state.cache_error =/= ok ->
loop(cache_error(S, [From]));
#log{status = ok}=L ->
case catch do_inc_wrap_file(L) of
{ok, L2, Lost} ->
put(log, L2),
- notify_owners({wrap, Lost}),
+ notify_owners({L#log.type, Lost}),
+ reply(From, ok, S#state{cnt = S#state.cnt-Lost});
+ {error, Error, L2} ->
+ put(log, L2),
+ reply(From, Error, state_err(S, Error))
+ end;
+ #log{status = {blocked, false}}=L ->
+ reply(From, {error, {blocked_log, L#log.name}}, S);
+ #log{blocked_by = From}=L ->
+ reply(From, {error, {blocked_log, L#log.name}}, S);
+ _ ->
+ enqueue(Message, S)
+ end;
+handle({From, next_file}=Message, S) ->
+ case get(log) of
+ #log{mode = read_only}=L ->
+ reply(From, {error, {read_only_mode, L#log.name}}, S);
+ #log{type = halt}=L ->
+ reply(From, {error, {halt_log, L#log.name}}, S);
+ #log{status = ok} when S#state.cache_error =/= ok ->
+ loop(cache_error(S, [From]));
+ #log{status = ok, type = wrap}=L ->
+ case catch do_inc_wrap_file(L) of
+ {ok, L2, Lost} ->
+ put(log, L2),
+ notify_owners({L#log.type, Lost}),
reply(From, ok, S#state{cnt = S#state.cnt-Lost});
{error, Error, L2} ->
put(log, L2),
reply(From, Error, state_err(S, Error))
end;
+ #log{status = ok, type = rotate}=L ->
+ {ok, L2} = do_inc_rotate_file(L),
+ put(log, L2),
+ reply(From, ok, S);
#log{status = {blocked, false}}=L ->
reply(From, {error, {blocked_log, L#log.name}}, S);
#log{blocked_by = From}=L ->
@@ -910,7 +934,7 @@ handle({From, {reopen, NewFile, Head, F, A}}, S) ->
case catch close_disk_log2(L) of
closed ->
File = L#log.filename,
- case catch rename_file(File, NewFile, L#log.type) of
+ case catch rename_file(File, NewFile, L) of
ok ->
H = merge_head(Head, L#log.head),
case do_open((S#state.args)#arg{name = L#log.name,
@@ -1233,15 +1257,19 @@ is_owner(Pid, L) ->
end.
%% ok | throw(Error)
-rename_file(File, NewFile, halt) ->
+rename_file(File, NewFile, #log{type = halt}) ->
case file:rename(File, NewFile) of
ok ->
ok;
Else ->
file_error(NewFile, Else)
end;
-rename_file(File, NewFile, wrap) ->
- rename_file(wrap_file_extensions(File), File, NewFile, ok).
+rename_file(File, NewFile, #log{type = wrap}) ->
+ rename_file(wrap_file_extensions(File), File, NewFile, ok);
+rename_file(File, NewFile, #log{type = rotate, extra = Handle}) ->
+ {_MaxB, MaxF} = disk_log_1:get_rotate_size(Handle),
+ disk_log_1:rotate_files(Handle#rotate_handle.file, MaxF),
+ rename_file(rotate_file_extensions(File, MaxF), File, NewFile, ok).
rename_file([Ext|Exts], File, NewFile0, Res) ->
NewFile = add_ext(NewFile0, Ext),
@@ -1299,6 +1327,20 @@ compare_arg(_Attr, _Val, _A) ->
ok.
%% -> {ok, Res, log(), Cnt} | Error
+do_open(#arg{type = rotate} = A) ->
+ #arg{name = Name, size = Size, mode = Mode, head = Head0,
+ format = Format, type = Type, file = FName} = A,
+ Head = mk_head(Head0, Format),
+ case disk_log_1:open_rotate_log_file(FName, Size, Head) of
+ {ok, RotHandle} ->
+ L = #log{name = Name, type = Type, format = Format,
+ filename = FName, size = Size, mode = Mode,
+ extra = RotHandle, format_type = rotate_ext,
+ head = Head},
+ {ok, {ok, Name}, L, 0};
+ Error ->
+ Error
+ end;
do_open(A) ->
#arg{type = Type, format = Format, name = Name, head = Head0,
file = FName, repair = Repair, size = Size, mode = Mode,
@@ -1368,6 +1410,11 @@ do_change_size(#log{type = wrap}=L, NewSize) ->
{ok, Handle} = disk_log_1:change_size_wrap(Extra, NewSize, Version),
erase(is_full),
put(log, L#log{extra = Handle}),
+ ok;
+do_change_size(#log{type =rotate, extra = Extra} = L, NewSize) ->
+ {ok, Handle} = disk_log_1:change_size_rotate(Extra, NewSize),
+ erase(is_full),
+ put(log, L#log{extra = Handle}),
ok.
%% -> {ok, Head} | Error; Head = none | {head, H} | {M,F,A}
@@ -1389,14 +1436,15 @@ check_head({head, Term}, internal) ->
check_head(_Head, _Format) ->
{error, {badarg, head}}.
-check_size(wrap, {NewMaxB,NewMaxF}) when
- is_integer(NewMaxB), is_integer(NewMaxF),
- NewMaxB > 0, NewMaxB =< ?MAX_BYTES, NewMaxF > 0, NewMaxF < ?MAX_FILES ->
- ok;
check_size(halt, NewSize) when is_integer(NewSize), NewSize > 0 ->
ok;
check_size(halt, infinity) ->
ok;
+check_size(Type, {NewMaxB,NewMaxF}) when
+ (Type =:= wrap orelse Type =:= rotate) andalso
+ is_integer(NewMaxB), is_integer(NewMaxF),
+ NewMaxB > 0, NewMaxB =< ?MAX_BYTES, NewMaxF > 0, NewMaxF < ?MAX_FILES ->
+ ok;
check_size(_, _) ->
not_ok.
@@ -1423,6 +1471,13 @@ do_inc_wrap_file(L) ->
end
end.
+%%-----------------------------------------------------------------
+%% Force log rotation.
+%%-----------------------------------------------------------------
+%% -> {ok, log()}
+do_inc_rotate_file(#log{extra = Handle, head = Head} = L) ->
+ Handle2 = disk_log_1:do_rotate(Handle, Head),
+ {ok, L#log{extra = Handle2}}.
%%-----------------------------------------------------------------
%% Open a log file.
@@ -1495,7 +1550,9 @@ close_disk_log2(L) ->
#log{format_type = halt_ext, extra = Halt} ->
disk_log_1:fclose(Halt#halt.fdc, L#log.filename);
#log{format_type = wrap_ext, mode = Mode, extra = Handle} ->
- disk_log_1:mf_ext_close(Handle, Mode)
+ disk_log_1:mf_ext_close(Handle, Mode);
+ #log{type = rotate, extra = Handle} ->
+ disk_log_1:rotate_ext_close(Handle)
end,
closed.
@@ -1591,6 +1648,8 @@ do_info(L, Cnt) ->
Size = case Type of
wrap ->
disk_log_1:get_wrap_size(Extra);
+ rotate ->
+ disk_log_1:get_rotate_size(Extra);
halt ->
Extra#halt.size
end,
@@ -1609,6 +1668,11 @@ do_info(L, Cnt) ->
{current_file, CurF},
{no_overflows, {NewAccFull, NoFull}}
];
+ rotate when Mode =:= read_write ->
+ #rotate_handle{curB = CurB} = Extra,
+ [{no_current_bytes, CurB},
+ {no_items, Cnt}
+ ];
halt when Mode =:= read_write ->
IsFull = case get(is_full) of
undefined -> false;
@@ -1713,7 +1777,11 @@ do_log(#log{format_type = wrap_ext}=L, B, _BSz) ->
{error, Error, Handle, Logged, Lost} ->
put(log, L#log{extra = Handle}),
{error, Error, Logged - Lost}
- end.
+ end;
+do_log(#log{type = rotate}=L, B, _BSz) ->
+ {ok, Handle, Logged} = disk_log_1:rotate_ext_log(L#log.extra, B, L#log.head), %%error case here
+ put(log, L#log{extra = Handle}),
+ Logged.
logl(B, external, undefined) ->
{B, iolist_size(B)};
@@ -1763,6 +1831,10 @@ do_write_cache(#log{filename = FName, type = halt, extra = Halt} = Log) ->
do_write_cache(#log{type = wrap, extra = Handle} = Log) ->
{Reply, NewHandle} = disk_log_1:mf_write_cache(Handle),
put(log, Log#log{extra = NewHandle}),
+ Reply;
+do_write_cache(#log{type = rotate, extra = Handle} = Log) ->
+ {Reply, NewHandle} = disk_log_1:rotate_write_cache(Handle),
+ put(log, Log#log{extra = NewHandle}),
Reply.
%% -> ok | Error
@@ -1770,7 +1842,7 @@ do_sync(#log{filename = FName, type = halt, extra = Halt} = Log) ->
{Reply, NewFdC} = disk_log_1:sync(Halt#halt.fdc, FName),
put(log, Log#log{extra = Halt#halt{fdc = NewFdC}}),
Reply;
-do_sync(#log{type = wrap, extra = Handle} = Log) ->
+do_sync(#log{type = Type, extra = Handle} = Log) when Type == wrap orelse Type == rotate->
{Reply, NewHandle} = disk_log_1:mf_sync(Handle),
put(log, Log#log{extra = NewHandle}),
Reply.
@@ -1815,7 +1887,12 @@ do_trunc(#log{type = wrap}=L, Head) ->
NewLog2 = trunc_wrap(NewLog),
NewHandle = (NewLog2#log.extra)#handle{noFull = 0, accFull = 0},
do_change_size(NewLog2#log{extra = NewHandle, head = OldHead},
- {MaxB, MaxF}).
+ {MaxB, MaxF});
+do_trunc(#log{type = rotate, head = Head, extra = Handle}=L, none) ->
+ Handle1 = disk_log_1:do_rotate(Handle, Head),
+ disk_log_1:remove_files(rotate, Handle1#rotate_handle.file, 0, Handle1#rotate_handle.maxF),
+ put(log, L#log{extra = Handle1}),
+ ok.
trunc_wrap(L) ->
case do_inc_wrap_file(L) of
@@ -1900,7 +1977,7 @@ merge_head(Head, _) ->
Head.
%% -> List of extensions of existing files (no dot included) | throw(FileError)
-wrap_file_extensions(File) ->
+wrap_file_extensions(File) ->
{_CurF, _CurFSz, _TotSz, NoOfFiles} =
disk_log_1:read_index_file(File),
Fs = if
@@ -1919,6 +1996,18 @@ wrap_file_extensions(File) ->
end,
lists:filter(Fun, ["idx", "siz" | Fs]).
+rotate_file_extensions(File, MaxF) ->
+ rotate_file_extensions(File, MaxF, 0, []).
+
+rotate_file_extensions(_File, MaxF, MaxF, Res) ->
+ Res;
+rotate_file_extensions(File, MaxF, N, Res) ->
+ Ext = integer_to_list(N) ++ ".gz",
+ case file:read_file_info(add_ext(File, Ext)) of
+ {ok, _} -> rotate_file_extensions(File, MaxF, N+1, [Ext|Res]);
+ _ -> rotate_file_extensions(File, MaxF, N+1, Res)
+ end.
+
add_ext(File, Ext) ->
lists:concat([File, ".", Ext]).
diff --git a/lib/kernel/src/disk_log.hrl b/lib/kernel/src/disk_log.hrl
index 6cb2c13f02..41844c03d2 100644
--- a/lib/kernel/src/disk_log.hrl
+++ b/lib/kernel/src/disk_log.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -56,7 +56,8 @@
%%------------------------------------------------------------------------
-type dlog_format() :: 'external' | 'internal'.
--type dlog_format_type() :: 'halt_ext' | 'halt_int' | 'wrap_ext' | 'wrap_int'.
+-type dlog_format_type() :: 'halt_ext' | 'halt_int' | 'wrap_ext' | 'wrap_int'
+ | 'rotate_ext'.
-type dlog_head() :: 'none' | {'ok', binary()} | mfa().
-type dlog_head_opt() :: none | term() | iodata().
-type log() :: term(). % XXX: refine
@@ -83,7 +84,7 @@
| {MaxNoBytes :: pos_integer(),
MaxNoFiles :: pos_integer()}.
-type dlog_status() :: 'ok' | {'blocked', 'false' | [_]}. %QueueLogRecords
--type dlog_type() :: 'halt' | 'wrap'.
+-type dlog_type() :: 'halt' | 'wrap' | 'rotate'.
%%------------------------------------------------------------------------
%% Records
@@ -129,7 +130,7 @@
%% time the wrap log has filled the
%% Dir/Name.NewMaxF file.
curB :: non_neg_integer(), %% Number of bytes on current file.
- curF :: integer(), %% Current file number.
+ curF :: integer(), %% Current file number
cur_fdc :: #cache{}, %% Current file descriptor.
cur_name :: file:filename(), %% Current file name for error reports.
cur_cnt :: non_neg_integer(), %% Number of items on current file,
@@ -146,6 +147,18 @@
%% overflows since the log was opened.
).
+-record(rotate_handle,
+ {file :: file:filename(),
+ cur_fdc :: #cache{},
+ inode,
+ file_check,
+ maxB :: pos_integer(),
+ maxF :: pos_integer() | {pos_integer(),pos_integer()},
+ curB = 0 :: non_neg_integer(),
+ firstPos :: non_neg_integer(),
+ compress_on_rotate = true}
+ ).
+
-record(log,
{status = ok :: dlog_status(),
name :: dlog_name(), %% the key leading to this structure
@@ -160,8 +173,8 @@
%% called when wraplog wraps
mode :: dlog_mode(),
size, %% value of open/1 option 'size' (never changed)
- extra :: #halt{} | #handle{}, %% type of the log
- version :: integer()} %% if wrap log file
+ extra :: #halt{} | #handle{} | #rotate_handle{}, %% type of the log
+ version :: integer() | undefined} %% if wrap log file, undefined for halt and rotate
).
-record(continuation, %% Chunk continuation.
diff --git a/lib/kernel/src/disk_log_1.erl b/lib/kernel/src/disk_log_1.erl
index a488a44dcc..5f585a4c39 100644
--- a/lib/kernel/src/disk_log_1.erl
+++ b/lib/kernel/src/disk_log_1.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,10 +23,14 @@
-export([int_open/4, ext_open/4, logl/1, close/3, truncate/3, chunk/5,
sync/2, write_cache/2]).
--export([mf_int_open/7, mf_int_log/3, mf_int_close/2, mf_int_inc/2,
- mf_ext_inc/2, mf_int_chunk/4, mf_int_chunk_step/3,
+-export([mf_int_open/7, mf_int_log/3, mf_int_close/2, mf_int_inc/2,
+ mf_ext_inc/2, mf_int_chunk/4, mf_int_chunk_step/3,
mf_sync/1, mf_write_cache/1]).
-export([mf_ext_open/7, mf_ext_log/3, mf_ext_close/2]).
+-export([open_rotate_log_file/3, do_rotate/2,
+ rotate_ext_log/3, rotate_write_cache/1, rotate_ext_close/1,
+ change_size_rotate/2, get_rotate_size/1,
+ rotate_files/2]).
-export([print_index_file/1]).
-export([read_index_file/1]).
@@ -38,6 +42,7 @@
-export([is_head/1]).
-export([position/3, truncate_at/3, fwrite/4, fclose/2]).
-export([set_quiet/1, is_quiet/0]).
+-export([remove_files/4]).
-compile({inline,[{scan_f2,7}]}).
@@ -455,7 +460,7 @@ new_ext_file(FName, Head) ->
%% -> {FdC, {NoItemsWritten, NoBytesWritten}} | throw(Error)
ext_log_head(Fd, Head) ->
case lh(Head, external) of
- {ok, BinHead} ->
+ {ok, BinHead} ->
Size = byte_size(BinHead),
{ok, FdC} = fwrite_header(Fd, BinHead, Size),
{FdC, {1, Size}};
@@ -678,7 +683,7 @@ mf_int_open(FName, MaxB, MaxF, Repair, Mode, Head, Version) ->
end.
%% -> {ok, handle(), Lost} | {error, Error, handle()}
-mf_int_inc(Handle, Head) ->
+mf_int_inc(Handle, Head) ->
#handle{filename = FName, cur_cnt = CurCnt, acc_cnt = AccCnt,
cur_name = FileName, curF = CurF, maxF = MaxF,
cur_fdc = CurFdC, noFull = NoFull} = Handle,
@@ -732,7 +737,7 @@ mf_int_log(Handle, Bins, Head, No0, Wraps) ->
noFull = NoFull + 1},
case catch close(CurFdC, FileName, read_write) of
ok ->
- mf_int_log(Handle1, Bins, Head, No0 + Nh,
+ mf_int_log(Handle1, Bins, Head, No0 + Nh,
[Lost | Wraps]);
Error ->
Lost1 = Lost + sum(Wraps),
@@ -845,7 +850,10 @@ mf_write_cache(#handle{filename = FName, cur_fdc = FdC} = Handle) ->
%% -> {Reply, handle()}; Reply = ok | Error
mf_sync(#handle{filename = FName, cur_fdc = FdC} = Handle) ->
{Reply, NewFdC} = fsync(FdC, FName),
- {Reply, Handle#handle{cur_fdc = NewFdC}}.
+ {Reply, Handle#handle{cur_fdc = NewFdC}};
+mf_sync(#rotate_handle{file = FName, cur_fdc = FdC} = Handle) ->
+ {Reply, NewFdC} = fsync(FdC, FName),
+ {Reply, Handle#rotate_handle{cur_fdc = NewFdC}}.
%% -> ok | throw(FileError)
mf_int_close(#handle{filename = FName, curF = CurF, cur_name = FileName,
@@ -855,7 +863,7 @@ mf_int_close(#handle{filename = FName, curF = CurF, cur_name = FileName,
ok.
%% -> {ok, handle(), Cnt} | throw(FileError)
-mf_ext_open(FName, MaxB, MaxF, Repair, Mode, Head, Version) ->
+mf_ext_open(FName, MaxB, MaxF, Repair, Mode, Head, Version) ->
{First, Sz, TotSz, NFiles} = read_index_file(Repair, FName, MaxF),
write_size_file(Mode, FName, MaxB, MaxF, Version),
NewMaxF = if
@@ -865,37 +873,37 @@ mf_ext_open(FName, MaxB, MaxF, Repair, Mode, Head, Version) ->
MaxF
end,
{ok, FdC, FileName, Lost, {NoItems, NoBytes}, CurB} =
- ext_file_open(FName, First, 0, 0, Head, Repair, Mode),
+ ext_file_open(FName, First, 0, 0, Head, Repair, Mode),
CurCnt = Sz + NoItems - Lost,
{ok, #handle{filename = FName, maxB = MaxB, cur_name = FileName,
- maxF = NewMaxF, cur_cnt = CurCnt, acc_cnt = -Sz,
- curF = First, cur_fdc = FdC, firstPos = NoBytes,
- curB = CurB, noFull = 0, accFull = 0},
+ maxF = NewMaxF, cur_cnt = CurCnt, acc_cnt = -Sz,
+ curF = First, cur_fdc = FdC, firstPos = NoBytes,
+ curB = CurB, noFull = 0, accFull = 0},
TotSz + CurCnt}.
%% -> {ok, handle(), Lost}
%% | {error, Error, handle()}
%% | throw(FatalError)
%% Fatal errors should always terminate the log.
-mf_ext_inc(Handle, Head) ->
- #handle{filename = FName, cur_cnt = CurCnt, cur_name = FileName,
- acc_cnt = AccCnt, curF = CurF, maxF = MaxF, cur_fdc = CurFdC,
- noFull = NoFull} = Handle,
+mf_ext_inc(Handle, Head) ->
+ #handle{filename = FName, cur_cnt = CurCnt, cur_name = FileName,
+ acc_cnt = AccCnt, curF = CurF, maxF = MaxF, cur_fdc = CurFdC,
+ noFull = NoFull} = Handle,
case catch wrap_ext_log(FName, CurF, MaxF, CurCnt, Head) of
- {NewF, NewMaxF, NewFdC, NewFileName, Nh, FirstPos, Lost} ->
- Handle1 = Handle#handle{cur_fdc = NewFdC, curF = NewF,
- cur_name = NewFileName,
- cur_cnt = Nh, acc_cnt = AccCnt + CurCnt,
- maxF = NewMaxF, firstPos = FirstPos,
- curB = FirstPos, noFull = NoFull + 1},
- case catch fclose(CurFdC, FileName) of
- ok ->
- {ok, Handle1, Lost};
- Error -> % Error in the last file, new file opened.
- {error, Error, Handle1}
- end;
- Error ->
- {error, Error, Handle}
+ {NewF, NewMaxF, NewFdC, NewFileName, Nh, FirstPos, Lost} ->
+ Handle1 = Handle#handle{cur_fdc = NewFdC, curF = NewF,
+ cur_name = NewFileName,
+ cur_cnt = Nh, acc_cnt = AccCnt + CurCnt,
+ maxF = NewMaxF, firstPos = FirstPos,
+ curB = FirstPos, noFull = NoFull + 1},
+ case catch fclose(CurFdC, FileName) of
+ ok ->
+ {ok, Handle1, Lost};
+ Error -> % Error in the last file, new file opened.
+ {error, Error, Handle1}
+ end;
+ Error ->
+ {error, Error, Handle}
end.
%% -> {ok, handle(), Logged, Lost, NoWraps} | {ok, handle(), Logged}
@@ -968,18 +976,201 @@ mf_ext_close(#handle{filename = FName, curF = CurF,
Res.
%% -> {ok, handle()} | throw(FileError)
-change_size_wrap(Handle, {NewMaxB, NewMaxF}, Version) ->
- FName = Handle#handle.filename,
+change_size_wrap(#handle{filename = FName} = Handle, {NewMaxB, NewMaxF}, Version) ->
{_MaxB, MaxF} = get_wrap_size(Handle),
write_size_file(read_write, FName, NewMaxB, NewMaxF, Version),
if
- NewMaxF > MaxF ->
- remove_files(FName, MaxF + 1, NewMaxF),
- {ok, Handle#handle{maxB = NewMaxB, maxF = NewMaxF}};
- NewMaxF < MaxF ->
- {ok, Handle#handle{maxB = NewMaxB, maxF = {NewMaxF, MaxF}}};
- true ->
- {ok, Handle#handle{maxB = NewMaxB, maxF = NewMaxF}}
+ NewMaxF > MaxF ->
+ remove_files(wrap, FName, MaxF + 1, NewMaxF),
+ {ok, Handle#handle{maxB = NewMaxB, maxF = NewMaxF}};
+ NewMaxF < MaxF ->
+ {ok, Handle#handle{maxB = NewMaxB, maxF = {NewMaxF, MaxF}}};
+ true ->
+ {ok, Handle#handle{maxB = NewMaxB, maxF = NewMaxF}}
+ end.
+
+change_size_rotate(#rotate_handle{maxB = MaxB, maxF = MaxF, file = FName} = Handle, {NewMaxB, NewMaxF}) ->
+ {MaxB, MaxF1} = get_size(MaxB, MaxF),
+ if
+ NewMaxF > MaxF1 ->
+ remove_files(rotate, FName, MaxF1, NewMaxF),
+ {ok, Handle#rotate_handle{maxB = NewMaxB, maxF = NewMaxF}};
+ NewMaxF < MaxF1 ->
+ remove_files(rotate, FName, NewMaxF, MaxF1),
+ {ok, Handle#rotate_handle{maxB = NewMaxB, maxF = NewMaxF}};
+ true ->
+ {ok, Handle#rotate_handle{maxB = NewMaxB, maxF = NewMaxF}}
+ end.
+
+open_rotate_log_file(FileName, Size, Head) ->
+ try
+ case filelib:ensure_dir(FileName) of
+ ok ->
+ case file:open(FileName, [raw, binary, read, append]) of
+ {ok, Fd} ->
+ {FdC1, _HeadSize} = ext_log_head(Fd, Head),
+ {FdC, FileSize} = position_close(FdC1, FileName, cur),
+ {ok,#file_info{inode=INode}} =
+ file:read_file_info(FileName,[raw]),
+ {MaxB, MaxF} = Size,
+ RotHandle = #rotate_handle{file = FileName,
+ cur_fdc = FdC,
+ maxB = MaxB,
+ maxF = MaxF,
+ curB = FileSize,
+ firstPos = FileSize,
+ inode = INode},
+ update_rotation(RotHandle),
+ {ok, RotHandle};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end
+ catch
+ _:Reason -> {error,Reason}
+ end.
+
+update_rotation(#rotate_handle{file = FName, maxF = MaxF}) ->
+ maybe_remove_archives(MaxF, FName),
+ maybe_update_compress(0, MaxF,FName).
+
+maybe_remove_archives(Count, FName) ->
+ Archive = rotate_file_name(FName, Count),
+ case file:read_file_info(Archive,[raw]) of
+ {error,enoent} ->
+ ok;
+ _ ->
+ _ = file:delete(Archive),
+ maybe_remove_archives(Count+1, FName)
+ end.
+
+maybe_update_compress(MaxF, MaxF, _FName) ->
+ ok;
+maybe_update_compress(N, MaxF, FName) ->
+ FileName = add_ext(FName, N),
+ case file:read_file_info(FileName,[raw]) of
+ {ok,_} ->
+ compress_file(FileName);
+ _ ->
+ ok
+ end,
+ maybe_update_compress(N+1, MaxF, FName).
+
+do_rotate(#rotate_handle{file = FName, maxF = MaxF, cur_fdc = FdC} = RotHandle, Head) ->
+ #cache{fd = Fd, c = C} = FdC,
+ {_, _C1} = write_cache(Fd, FName, C),
+ _ = delayed_write_close(Fd),
+ rotate_files(FName, MaxF),
+ {ok, NewFdC, FileSize} = ensure_open(FName, Head),
+ {ok,#file_info{inode=INode}} = file:read_file_info(FName,[raw]),
+ RotHandle#rotate_handle{cur_fdc = NewFdC, inode = INode, curB = FileSize, firstPos = FileSize}.
+
+rotate_files(FileName,0) ->
+ _ = file:delete(FileName),
+ ok;
+rotate_files(FileName, 1) ->
+ FileName0 = FileName ++".0",
+ Rename = file:rename(FileName, FileName0),
+ %% Rename may fail if file has been deleted. If it has, then
+ %% we do not need to compress it...
+ if Rename =:= ok -> compress_file(FileName0);
+ true -> ok
+ end,
+ ok;
+rotate_files(FileName, Count) ->
+ _ = file:rename(rotate_file_name(FileName, Count-2), rotate_file_name(FileName, Count-1)),
+ rotate_files(FileName, Count-1).
+
+rotate_file_name(FileName, Count) ->
+ concat([FileName, ".", Count, ".gz"]).
+
+compress_file(FileName) ->
+ {ok,In} = file:open(FileName,[read,binary]),
+ {ok,Out} = file:open(FileName++".gz",[write]),
+ Z = zlib:open(),
+ zlib:deflateInit(Z, default, deflated, 31, 8, default),
+ compress_data(Z,In,Out),
+ zlib:deflateEnd(Z),
+ zlib:close(Z),
+ _ = file:close(In),
+ _ = file:close(Out),
+ _ = file:delete(FileName),
+ ok.
+
+compress_data(Z,In,Out) ->
+ case file:read(In,100000) of
+ {ok,Data} ->
+ Compressed = zlib:deflate(Z, Data),
+ _ = file:write(Out,Compressed),
+ compress_data(Z,In,Out);
+ eof ->
+ Compressed = zlib:deflate(Z, <<>>, finish),
+ _ = file:write(Out,Compressed),
+ ok
+ end.
+
+rotate_ext_log(Handle, Bin, Head) ->
+ rotate_ext_log(Handle, Bin, Head, 0).
+
+rotate_ext_log(Handle, [], _Head, N) ->
+ {ok, Handle, N};
+rotate_ext_log(Handle, Bins, Head, N0) ->
+ #rotate_handle{file = FileName, maxB = MaxB, cur_fdc = CurFdC,
+ curB = CurB, firstPos = FirstPos} = Handle,
+ {FirstBins, LastBins, NoBytes, N} =
+ ext_split_bins(CurB, MaxB, FirstPos, Bins),
+ case FirstBins of
+ [] ->
+ Handle1 = do_rotate(Handle, Head),
+ rotate_ext_log(Handle1, Bins, Head, N0);
+ _ ->
+ case fwrite(CurFdC, FileName, FirstBins, NoBytes) of
+ {ok, NewCurFdC} ->
+ Handle1 = Handle#rotate_handle{cur_fdc = NewCurFdC,
+ curB = CurB + NoBytes},
+ rotate_ext_log(Handle1, LastBins, Head, N0 + N);
+ {Error, NewCurFdC} ->
+ Handle1 = Handle#rotate_handle{cur_fdc = NewCurFdC},
+ {error, Error, Handle1}
+ end
+ end.
+
+%% -> {Reply, handle()}; Reply = ok | Error
+rotate_write_cache(#rotate_handle{file = FName, cur_fdc = FdC} = Handle) ->
+ erase(write_cache_timer_is_running),
+ #cache{fd = Fd, c = C} = FdC,
+ {Reply, NewFdC} = write_cache(Fd, FName, C),
+ {Reply, Handle#rotate_handle{cur_fdc = NewFdC}}.
+
+rotate_ext_close(#rotate_handle{file = FName, cur_fdc = CurFdC}) ->
+ (catch fclose(CurFdC, FName)).
+
+ensure_open(Filename, Head) ->
+ case filelib:ensure_dir(Filename) of
+ ok ->
+ case open_update(Filename) of
+ {ok, Fd} ->
+ {FdC1, _HeadSize} = ext_log_head(Fd, Head),
+ {FdC, FileSize} = position_close(FdC1, Filename, cur),
+ {ok, FdC, FileSize};
+ Error ->
+ exit({could_not_reopen_file,Error})
+ end;
+ Error ->
+ exit({could_not_create_dir_for_file,Error})
+ end.
+
+%% A special close that closes the FD properly when the delayed write close failed
+delayed_write_close(Fd) ->
+ case file:close(Fd) of
+ %% We got an error while closing, could be a delayed write failing
+ %% So we close again in order to make sure the file is closed.
+ {error, _} ->
+ file:close(Fd);
+ Res ->
+ Res
end.
%%-----------------------------------------------------------------
@@ -1037,7 +1228,7 @@ ext_file_open(FName, NewFile, OldFile, OldCnt, Head, Repair, Mode) ->
-define(index_file_name(F), add_ext(F, "idx")).
read_index_file(truncate, FName, MaxF) ->
- remove_files(FName, 2, MaxF),
+ remove_files(wrap, FName, 2, MaxF),
_ = file:delete(?index_file_name(FName)),
{1, 0, 0, 0};
read_index_file(_, FName, _MaxF) ->
@@ -1139,7 +1330,7 @@ write_index_file(read_write, FName, NewFile, OldFile, OldCnt) ->
_ = file:close(Fd),
case R of
{ok, <<Lost:SzSz/unit:8>>} -> Lost;
- {ok, _} ->
+ {ok, _} ->
throw({error, {invalid_index_file, FileName}});
eof -> 0;
Error2 -> file_error(FileName, Error2)
@@ -1342,7 +1533,7 @@ inc_wrap(FName, CurF, MaxF) ->
if
CurF >= NewMaxF ->
%% We are at or above the new number of files
- remove_files(FName, CurF + 1, OldMaxF),
+ remove_files(wrap, FName, CurF + 1, OldMaxF),
if
CurF > NewMaxF ->
%% The change was done while the current file was
@@ -1389,24 +1580,34 @@ file_size(Fname) ->
%% -> ok | throw(FileError)
%% Tries to remove each file with name FName.I, N<=I<=Max.
-remove_files(FName, N, Max) ->
- remove_files(FName, N, Max, ok).
+remove_files(Type, FName, N, Max) ->
+ remove_files(Type, FName, N, Max, ok).
-remove_files(_FName, N, Max, ok) when N > Max ->
+remove_files(_Type, _FName, N, Max, ok) when N > Max ->
ok;
-remove_files(_FName, N, Max, {FileName, Error}) when N > Max ->
+remove_files(_Type, _FName, N, Max, {FileName, Error}) when N > Max ->
file_error(FileName, Error);
-remove_files(FName, N, Max, Reply) ->
- FileName = add_ext(FName, N),
+remove_files(Type, FName, N, Max, Reply) ->
+ FileName =
+ case Type of
+ wrap -> add_ext(FName, N);
+ rotate -> rotate_file_name(FName, N)
+ end,
NewReply = case file:delete(FileName) of
ok -> Reply;
{error, enoent} -> Reply;
Error -> {FileName, Error}
end,
- remove_files(FName, N + 1, Max, NewReply).
+ remove_files(Type, FName, N + 1, Max, NewReply).
%% -> {MaxBytes, MaxFiles}
get_wrap_size(#handle{maxB = MaxB, maxF = MaxF}) ->
+ get_size(MaxB, MaxF).
+
+get_rotate_size(#rotate_handle{maxB = MaxB, maxF = MaxF}) ->
+ get_size(MaxB, MaxF).
+
+get_size(MaxB, MaxF) ->
case MaxF of
{NewMaxF,_} -> {MaxB, NewMaxF};
MaxF -> {MaxB, MaxF}
@@ -1586,6 +1787,7 @@ write_cache_close(Fd, FileName, C) ->
-spec file_error(file:filename(), {'error', file:posix()}) -> no_return().
+
file_error(FileName, {error, Error}) ->
throw({error, {file_error, FileName, Error}}).
diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl
index a1b7c22c21..ab3e8d7478 100644
--- a/lib/kernel/src/dist_util.erl
+++ b/lib/kernel/src/dist_util.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -45,15 +45,6 @@
-define(shutdown_trace(A,B), noop).
-endif.
--define(to_port(FSend, Socket, Data),
- case FSend(Socket, Data) of
- {error, closed} ->
- self() ! {tcp_closed, Socket},
- {error, closed};
- R ->
- R
- end).
-
-define(int16(X), [((X) bsr 8) band 16#ff, (X) band 16#ff]).
@@ -421,7 +412,7 @@ shutdown(_Module, _Line, _Data, Reason) ->
exit(Reason).
%% Use this line to debug connection.
%% Set net_kernel verbose = 1 as well.
-%% exit({Reason, ?MODULE, _Line, _Data, erlang:timestamp()}).
+%% exit({Reason, {?MODULE, _Line, _Data, erlang:timestamp()}}).
handshake_we_started(#hs_data{request_type=ReqType,
this_node=MyNode,
@@ -524,11 +515,13 @@ connection(#hs_data{other_node = Node,
f_setopts = HSData#hs_data.mf_setopts,
f_getopts = HSData#hs_data.mf_getopts},
#tick{});
- _ ->
- ?shutdown2(Node, connection_setup_failed)
+ Error1 ->
+ ?shutdown2(
+ {Node, Socket},
+ {f_setopts_post_nodeup_failed, Error1})
end;
- _ ->
- ?shutdown(Node)
+ Error2 ->
+ ?shutdown2({Node, Socket}, {f_setopts_pre_nodeup_failed, Error2})
end.
%% Generate a message digest from Challenge number and Cookie
@@ -581,7 +574,7 @@ do_setnode(#hs_data{other_node = Node, socket = Socket,
error_msg("** Distribution system limit reached, "
"no table space left for node ~w ** ~n",
[Node]),
- ?shutdown(Node);
+ ?shutdown({Node, Socket});
error:Other:Stacktrace ->
exit({Other, Stacktrace})
end;
@@ -589,7 +582,7 @@ do_setnode(#hs_data{other_node = Node, socket = Socket,
error_msg("** Distribution connection error, "
"could not get low level port for node ~w ** ~n",
[Node]),
- ?shutdown(Node)
+ ?shutdown({Node, Socket})
end.
mark_nodeup(#hs_data{kernel_pid = Kernel,
@@ -626,9 +619,9 @@ con_loop(#state{kernel = Kernel, node = Node,
Tick) ->
receive
{tcp_closed, Socket} ->
- ?shutdown2(Node, connection_closed);
+ ?shutdown2({Node, Socket}, tcp_closed);
{Kernel, disconnect} ->
- ?shutdown2(Node, disconnected);
+ ?shutdown2({Node, Socket}, disconnected);
{Kernel, aux_tick} ->
case getstat(DHandle, Socket, MFGetstat) of
{ok, _, _, PendWrite} ->
@@ -645,17 +638,17 @@ con_loop(#state{kernel = Kernel, node = Node,
error_msg("** Node ~p not responding **~n"
"** Removing (timedout) connection **~n",
[Node]),
- ?shutdown2(Node, net_tick_timeout);
- _Other ->
- ?shutdown2(Node, send_net_tick_failed)
+ ?shutdown2({Node, Socket}, net_tick_timeout);
+ Error1 ->
+ ?shutdown2({Node, Socket}, {send_net_tick_failed, Error1})
end;
{From, get_status} ->
case getstat(DHandle, Socket, MFGetstat) of
{ok, Read, Write, _} ->
From ! {self(), get_status, {ok, Read, Write}},
con_loop(ConData, Tick);
- _ ->
- ?shutdown2(Node, get_status_failed)
+ Error2 ->
+ ?shutdown2({Node, Socket}, {get_status_failed, Error2})
end;
{From, Ref, {setopts, Opts}} ->
Ret = case MFSetOpts of
@@ -689,9 +682,8 @@ send_name(#hs_data{socket = Socket, this_node = Node,
NameLen = byte_size(NameBin),
?trace("send_name: 'N' node=~p creation=~w\n",
[Node, Creation]),
- _ = ?to_port(FSend, Socket,
- [<<$N, Flags:64, Creation:32, NameLen:16>>, NameBin]),
- ok.
+ to_port(FSend, Socket,
+ [<<$N, Flags:64, Creation:32, NameLen:16>>, NameBin]).
to_binary(Atom) when is_atom(Atom) ->
atom_to_binary(Atom, latin1);
@@ -708,23 +700,20 @@ send_challenge(#hs_data{socket = Socket, this_node = Node,
NameLen = byte_size(NodeName),
?trace("send: 'N' challenge=~w creation=~w\n",
[Challenge,Creation]),
- _ = ?to_port(FSend, Socket, [<<$N,
- ThisFlags:64,
- Challenge:32,
- Creation:32,
- NameLen:16>>, NodeName]),
- ok.
+ to_port(FSend, Socket,
+ [<<$N,ThisFlags:64, Challenge:32, Creation:32, NameLen:16>>,
+ NodeName]).
send_challenge_reply(#hs_data{socket = Socket, f_send = FSend},
Challenge, Digest) ->
?trace("send_reply: challenge=~w digest=~p\n",
[Challenge,Digest]),
- ?to_port(FSend, Socket, [$r,?int32(Challenge),Digest]).
+ to_port(FSend, Socket, [$r,?int32(Challenge),Digest]).
send_challenge_ack(#hs_data{socket = Socket, f_send = FSend},
Digest) ->
?trace("send_ack: digest=~p\n", [Digest]),
- ?to_port(FSend, Socket, [$a,Digest]).
+ to_port(FSend, Socket, [$a,Digest]).
%%
@@ -737,8 +726,8 @@ recv_name(#hs_data{socket = Socket, f_recv = Recv} = HSData) ->
recv_name_old(HSData, Data);
{ok, [$N | _] = Data} ->
recv_name_new(HSData, Data);
- _ ->
- ?shutdown(no_node)
+ Other ->
+ ?shutdown2({no_node, Socket}, {recv_name_failed, Other})
end.
%% OTP 25.3:
@@ -783,7 +772,7 @@ recv_name_new(HSData,
end,
{Flags, NodeOrHost, Creation};
false ->
- ?shutdown(Data)
+ ?shutdown({name, Data})
end.
is_node_name(NodeName) ->
@@ -919,7 +908,7 @@ recv_challenge(#hs_data{socket=Socket, f_recv=Recv}=HSData) ->
{ok,[$N | _]=Msg} ->
recv_challenge_new(HSData, Msg);
Other ->
- ?shutdown2(no_node, {recv_challenge_failed, Other})
+ ?shutdown2({no_node, Socket}, {recv_challenge_failed, Other})
end.
recv_challenge_new(#hs_data{other_node=Node},
@@ -997,7 +986,8 @@ recv_challenge_reply(#hs_data{socket = Socket,
?shutdown2(NodeB, {recv_challenge_reply_failed, bad_cookie})
end;
Other ->
- ?shutdown2(no_node, {recv_challenge_reply_failed, Other})
+ ?shutdown2({no_node, Socket},
+ {recv_challenge_reply_failed, Other})
end.
recv_challenge_ack(#hs_data{socket = Socket, f_recv = FRecv,
@@ -1014,15 +1004,21 @@ recv_challenge_ack(#hs_data{socket = Socket, f_recv = FRecv,
_ ->
error_msg("** Connection attempt to node ~w cancelled."
" Invalid challenge ack. **~n", [NodeB]),
- ?shutdown2(NodeB, {recv_challenge_ack_failed, bad_cookie})
+ ?shutdown2(
+ {NodeB, Socket}, {recv_challenge_ack_failed, bad_cookie})
end;
Other ->
- ?shutdown2(NodeB, {recv_challenge_ack_failed, Other})
+ ?shutdown2(
+ {NodeB, Socket}, {recv_challenge_ack_failed, Other})
end.
-recv_status(#hs_data{kernel_pid = Kernel, socket = Socket,
- this_flags = MyFlgs,
- other_node = Node, f_recv = Recv} = HSData) ->
+recv_status(
+ #hs_data{
+ kernel_pid = Kernel,
+ socket = Socket,
+ this_flags = MyFlgs,
+ other_node = Node,
+ f_recv = Recv} = HSData) ->
case Recv(Socket, 0, infinity) of
{ok, "snamed:"++Rest} ->
<<NameLen:16,
@@ -1039,9 +1035,11 @@ recv_status(#hs_data{kernel_pid = Kernel, socket = Socket,
?debug({dist_util,self(),recv_status, Node, Stat}),
case {Stat, name_type(MyFlgs)} of
{"not_allowed", _} ->
- ?shutdown2(Node, {recv_status_failed, not_allowed});
+ ?shutdown2(
+ {Node, Socket}, {recv_status_failed, not_allowed});
{_, dynamic} ->
- ?shutdown2(Node, {recv_status_failed, unexpected, Stat});
+ ?shutdown2(
+ {Node, Socket}, {recv_status_failed, unexpected, Stat});
_ ->
continue
end,
@@ -1056,20 +1054,20 @@ recv_status(#hs_data{kernel_pid = Kernel, socket = Socket,
?debug({is_pending,self(),Reply}),
send_status(HSData, Reply),
if not Reply ->
- ?shutdown(Node);
+ ?shutdown({Node, Socket});
Reply ->
HSData
end;
"ok" -> HSData;
"ok_simultaneous" -> HSData;
Other ->
- ?shutdown2(Node, {recv_status_failed, unknown, Other})
+ ?shutdown2(
+ {Node, Socket}, {recv_status_failed, unknown, Other})
end;
Error ->
- ?debug({dist_util,self(),recv_status_error,
- Node, Error}),
- ?shutdown2(Node, {recv_status_failed, Error})
+ ?debug({dist_util, self(), recv_status_error, Node, Error}),
+ ?shutdown2({Node, Socket}, {recv_status_failed, Error})
end.
@@ -1083,13 +1081,14 @@ recv_status_reply(#hs_data{socket = Socket,
"true" -> true;
"false" -> false;
Other ->
- ?shutdown2(Node, {recv_status_failed, unexpected, Other})
+ ?shutdown2(
+ {Node, Socket}, {recv_status_failed, unexpected, Other})
end;
Error ->
?debug({dist_util,self(),recv_status_error,
Node, Error}),
- ?shutdown2(Node, {recv_status_failed, Error})
+ ?shutdown2({Node, Socket}, {recv_status_failed, Error})
end.
@@ -1104,22 +1103,32 @@ send_status(#hs_data{socket = Socket,
case FSend(Socket, [$s, "named:",
<<NameLen:16, NameBin/binary>>,
<<Creation:32>>]) of
- {error, _} ->
- ?shutdown(Node);
+ {error, _} = Error->
+ ?shutdown2({Node, Socket}, {send_status_failed, Error});
_ ->
- true
+ ok
end;
send_status(#hs_data{socket = Socket, other_node = Node,
f_send = FSend}, Stat) ->
?debug({dist_util,self(),send_status, Node, Stat}),
case FSend(Socket, [$s | atom_to_list(Stat)]) of
- {error, _} ->
- ?shutdown(Node);
- _ ->
- true
+ {error, _} = Error ->
+ ?shutdown2({Node, Socket}, {send_status_failed, Error});
+ _ ->
+ ok
end.
-
-
+
+to_port(FSend, Socket, Data) ->
+ case FSend(Socket, Data) of
+ {error, closed} ->
+ self() ! {tcp_closed, Socket},
+ ok;
+ {error, _} = Error ->
+ ?shutdown2(Socket, {f_send_failed, Error});
+ ok ->
+ ok
+ end.
+
%%
%% Send a TICK to the other side.
@@ -1214,7 +1223,7 @@ setup_timer(Pid, Timeout) ->
setup_timer(Pid, Timeout)
after Timeout ->
?trace("Timer expires ~p, ~p~n",[Pid, Timeout]),
- ?shutdown2(timer, setup_timer_timeout)
+ ?shutdown2({timer, Pid}, setup_timer_timeout)
end.
reset_timer(Timer) ->
@@ -1223,7 +1232,7 @@ reset_timer(Timer) ->
cancel_timer(Timer) ->
unlink(Timer),
- exit(Timer, shutdown).
+ exit(Timer, cancel_setup_timer).
net_ticker_spawn_options() ->
Opts = application:get_env(kernel, net_ticker_spawn_options, []),
diff --git a/lib/kernel/src/erl_compile_server.erl b/lib/kernel/src/erl_compile_server.erl
index f4b719068e..3910d32cac 100644
--- a/lib/kernel/src/erl_compile_server.erl
+++ b/lib/kernel/src/erl_compile_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -51,8 +51,7 @@ start_link() ->
init([]) ->
%% We don't want the current directory in the code path.
%% Remove it.
- Path = [D || D <- code:get_path(), D =/= "."],
- true = code:set_path(Path),
+ _ = code:del_path("."),
Config = init_config(),
{ok, #st{config=Config}, ?IDLE_TIMEOUT}.
@@ -68,7 +67,7 @@ handle_call({compile, Parameters}, From, #st{jobs=Jobs}=St0) ->
case verify_context(PathArgs, Parameters, St0) of
{ok, St1} ->
#{cwd := Cwd, encoding := Enc} = Parameters,
- PidRef = spawn_monitor(fun() -> exit(do_compile(ErlcArgs, Cwd, Enc)) end),
+ PidRef = spawn_monitor(fun() -> exit(do_compile(ErlcArgs, unicode:characters_to_list(Cwd, Enc), Enc)) end),
St = St1#st{jobs=Jobs#{PidRef => From}},
{noreply, St#st{timeout=?IDLE_TIMEOUT}};
wrong_config ->
@@ -145,8 +144,9 @@ do_compile(ErlcArgs, Cwd, Enc) ->
{error, StdOutput, StdErrorOutput}
end.
-parse_command_line(#{command_line := CmdLine, cwd := Cwd}) ->
- parse_command_line_1(CmdLine, Cwd, [], []).
+parse_command_line(#{command_line := CmdLine0, cwd := Cwd, encoding := Enc}) ->
+ CmdLine = lists:map(fun(A) -> unicode:characters_to_list(A, Enc) end, CmdLine0),
+ parse_command_line_1(CmdLine, unicode:characters_to_list(Cwd, Enc), [], []).
parse_command_line_1(["-pa", Pa|T], Cwd, PaAcc, PzAcc) ->
parse_command_line_1(T, Cwd, [Pa|PaAcc], PzAcc);
diff --git a/lib/kernel/src/erl_erts_errors.erl b/lib/kernel/src/erl_erts_errors.erl
index 4d23112b83..d21dc8267b 100644
--- a/lib/kernel/src/erl_erts_errors.erl
+++ b/lib/kernel/src/erl_erts_errors.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -352,8 +352,19 @@ format_erlang_error(demonitor, [_], _) ->
format_erlang_error(demonitor, [Ref,Options], _) ->
Arg1 = must_be_ref(Ref),
[Arg1,maybe_option_list_error(Options, Arg1)];
-format_erlang_error(display_string, [_], _) ->
+format_erlang_error(display_string, [_], none) ->
[not_string];
+format_erlang_error(display_string, [_], Cause) ->
+ maybe_posix_message(Cause, false);
+format_erlang_error(display_string, [Device, _], none) ->
+ case lists:member(Device,[stdin,stdout,stderr]) of
+ true ->
+ [[],not_string];
+ false ->
+ [not_device,[]]
+ end;
+format_erlang_error(display_string, [_, _], Cause) ->
+ maybe_posix_message(Cause, true);
format_erlang_error(element, [Index, Tuple], _) ->
[if
not is_integer(Index) ->
@@ -1430,8 +1441,23 @@ is_flat_char_list([H|T]) ->
is_flat_char_list([]) -> true;
is_flat_char_list(_) -> false.
+maybe_posix_message(Cause, HasDevice) ->
+ case erl_posix_msg:message(Cause) of
+ "unknown POSIX error" ++ _ ->
+ unknown;
+ PosixStr when HasDevice ->
+ [unicode:characters_to_binary(
+ io_lib:format("~ts (~tp)",[PosixStr, Cause]))];
+ PosixStr when not HasDevice ->
+ [{general,
+ unicode:characters_to_binary(
+ io_lib:format("~ts (~tp)",[PosixStr, Cause]))}]
+ end.
+
format_error_map([""|Es], ArgNum, Map) ->
format_error_map(Es, ArgNum + 1, Map);
+format_error_map([{general, E}|Es], ArgNum, Map) ->
+ format_error_map(Es, ArgNum, Map#{ general => expand_error(E)});
format_error_map([E|Es], ArgNum, Map) ->
format_error_map(Es, ArgNum + 1, Map#{ArgNum => expand_error(E)});
format_error_map([], _, Map) ->
@@ -1519,6 +1545,8 @@ expand_error(not_ref) ->
<<"not a reference">>;
expand_error(not_string) ->
<<"not a list of characters">>;
+expand_error(not_device) ->
+ <<"not a valid device type">>;
expand_error(not_tuple) ->
<<"not a tuple">>;
expand_error(range) ->
diff --git a/lib/kernel/src/erl_kernel_errors.erl b/lib/kernel/src/erl_kernel_errors.erl
index 80660d46c8..ee5b77ad25 100644
--- a/lib/kernel/src/erl_kernel_errors.erl
+++ b/lib/kernel/src/erl_kernel_errors.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -75,7 +75,7 @@ format_os_error(_, _, _) ->
maybe_posix_message(Reason) ->
case erl_posix_msg:message(Reason) of
- "unknown POSIX error" ->
+ "unknown POSIX error" ++ _ ->
io_lib:format("open_port failed with reason: ~tp",[Reason]);
PosixStr ->
io_lib:format("~ts (~tp)",[PosixStr, Reason])
diff --git a/lib/kernel/src/error_logger.erl b/lib/kernel/src/error_logger.erl
index 74dc524988..829f28f00d 100644
--- a/lib/kernel/src/error_logger.erl
+++ b/lib/kernel/src/error_logger.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -578,11 +578,9 @@ limit_term(Term) ->
-spec get_format_depth() -> 'unlimited' | pos_integer().
get_format_depth() ->
- case application:get_env(kernel, error_logger_format_depth) of
- {ok, Depth} when is_integer(Depth) ->
+ case application:get_env(kernel, error_logger_format_depth, unlimited) of
+ Depth when is_integer(Depth) ->
max(10, Depth);
- {ok, unlimited} ->
- unlimited;
- undefined ->
- unlimited
+ unlimited ->
+ unlimited
end.
diff --git a/lib/kernel/src/erts_debug.erl b/lib/kernel/src/erts_debug.erl
index ae688ee717..6cf3e211e8 100644
--- a/lib/kernel/src/erts_debug.erl
+++ b/lib/kernel/src/erts_debug.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -447,7 +447,7 @@ lc_graph_to_dot(OutFile, InFile) ->
{ok, [LL0]} = file:consult(InFile),
[{"NO LOCK",0} | LL] = LL0,
- Map = maps:from_list([{Id, Name} || {Name, Id, _, _} <- LL]),
+ Map = #{Id => Name || {Name, Id, _, _} <- LL},
case file:open(OutFile, [exclusive]) of
{ok, Out} ->
diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl
index 49ad387a3a..3d4422dc2a 100644
--- a/lib/kernel/src/file.erl
+++ b/lib/kernel/src/file.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
%% %CopyrightEnd%
%%
-module(file).
+-deprecated([{pid2name,1,"this functionality is no longer supported"}]).
%% Interface module for the file server and the file io servers.
@@ -172,16 +173,9 @@ format_error(ErrorId) ->
Pid :: pid().
pid2name(Pid) when is_pid(Pid) ->
- case whereis(?FILE_SERVER) of
- undefined ->
- undefined;
- _ ->
- case ets:lookup(?FILE_IO_SERVER_TABLE, Pid) of
- [{_, Name} | _] ->
- {ok, Name};
- _ ->
- undefined
- end
+ case file_request(Pid, pid2name) of
+ {ok, _} = Ok -> Ok;
+ _ -> undefined
end.
%%%-----------------------------------------------------------------
diff --git a/lib/kernel/src/file_io_server.erl b/lib/kernel/src/file_io_server.erl
index 723325bdfb..e257857e5a 100644
--- a/lib/kernel/src/file_io_server.erl
+++ b/lib/kernel/src/file_io_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@
-export([count_and_find/3]).
--record(state, {handle,owner,mref,buf,read_mode,unic}).
+-record(state, {handle,owner,mref,name,buf,read_mode,unic}).
-include("file_int.hrl").
@@ -80,8 +80,9 @@ do_start(Spawn, Owner, FileName, ModeList) ->
Self ! {Ref, ok},
server_loop(
#state{handle = Handle,
- owner = Owner,
- mref = M,
+ owner = Owner,
+ mref = M,
+ name = FileName,
buf = <<>>,
read_mode = ReadMode,
unic = UnicodeMode})
@@ -322,7 +323,10 @@ file_request({read_handle_info, Opts},
Reply ->
{reply,Reply,State}
end;
-file_request(Unknown,
+file_request(pid2name,
+ #state{name=Name}=State) ->
+ {reply,{ok,Name},State};
+file_request(Unknown,
#state{}=State) ->
Reason = {request, Unknown},
{error,{error,Reason},State}.
diff --git a/lib/kernel/src/file_server.erl b/lib/kernel/src/file_server.erl
index 29eaa23375..c5450c3fd0 100644
--- a/lib/kernel/src/file_server.erl
+++ b/lib/kernel/src/file_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -37,8 +37,6 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
--define(FILE_IO_SERVER_TABLE, file_io_servers).
-
-define(FILE_SERVER, file_server_2). % Registered name
-define(FILE_IO_SERVER, file_io_server). % Module
-define(PRIM_FILE, prim_file). % Module
@@ -56,7 +54,7 @@ format_error(ErrorId) ->
start() -> do_start(start).
start_link() -> do_start(start_link).
-stop() ->
+stop() ->
gen_server:call(?FILE_SERVER, stop, infinity).
%%%----------------------------------------------------------------------
@@ -77,7 +75,6 @@ stop() ->
init([]) ->
process_flag(trap_exit, true),
- ?FILE_IO_SERVER_TABLE = ets:new(?FILE_IO_SERVER_TABLE, [named_table]),
{ok, undefined}.
%%----------------------------------------------------------------------
@@ -97,14 +94,7 @@ init([]) ->
handle_call({open, Name, ModeList}, {Pid, _Tag} = _From, State)
when is_list(ModeList) ->
- Child = ?FILE_IO_SERVER:start_link(Pid, Name, ModeList),
- case Child of
- {ok, P} when is_pid(P) ->
- ets:insert(?FILE_IO_SERVER_TABLE, {P, Name});
- _ ->
- ok
- end,
- {reply, Child, State};
+ {reply, ?FILE_IO_SERVER:start_link(Pid, Name, ModeList), State};
handle_call({open, _Name, _Mode}, _From, State) ->
{reply, {error, einval}, State};
@@ -228,7 +218,6 @@ handle_cast(Msg, State) ->
{'noreply', state()}.
handle_info({'EXIT', Pid, _Reason}, State) when is_pid(Pid) ->
- ets:delete(?FILE_IO_SERVER_TABLE, Pid),
{noreply, State};
handle_info(Info, State) ->
diff --git a/lib/kernel/src/gen_sctp.erl b/lib/kernel/src/gen_sctp.erl
index f53a9dca3e..d02b9a2379 100644
--- a/lib/kernel/src/gen_sctp.erl
+++ b/lib/kernel/src/gen_sctp.erl
@@ -58,6 +58,7 @@
{buffer, non_neg_integer()} |
{debug, boolean()} |
{dontroute, boolean()} |
+ {exclusiveaddruse, boolean()} |
{high_msgq_watermark, pos_integer()} |
{linger, {boolean(), non_neg_integer()}} |
{low_msgq_watermark, pos_integer()} |
@@ -65,6 +66,8 @@
{priority, non_neg_integer()} |
{recbuf, non_neg_integer()} |
{reuseaddr, boolean()} |
+ {reuseport, boolean()} |
+ {reuseport_lb, boolean()} |
{ipv6_v6only, boolean()} |
{sndbuf, non_neg_integer()} |
{sctp_autoclose, non_neg_integer()} |
@@ -84,6 +87,7 @@
buffer |
debug |
dontroute |
+ exclusiveaddruse |
high_msgq_watermark |
linger |
low_msgq_watermark |
@@ -91,6 +95,8 @@
priority |
recbuf |
reuseaddr |
+ reuseport |
+ reuseport_lb |
ipv6_v6only |
sctp_autoclose |
sctp_disable_fragments |
diff --git a/lib/kernel/src/gen_tcp.erl b/lib/kernel/src/gen_tcp.erl
index 1cba7fa624..e56f9eff1d 100644
--- a/lib/kernel/src/gen_tcp.erl
+++ b/lib/kernel/src/gen_tcp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@
{deliver, port | term} |
{dontroute, boolean()} |
{exit_on_close, boolean()} |
+ {exclusiveaddruse, boolean()} |
{header, non_neg_integer()} |
{high_msgq_watermark, pos_integer()} |
{high_watermark, non_neg_integer()} |
@@ -63,6 +64,8 @@
ValueBin :: binary()} |
{recbuf, non_neg_integer()} |
{reuseaddr, boolean()} |
+ {reuseport, boolean()} |
+ {reuseport_lb, boolean()} |
{send_timeout, non_neg_integer() | infinity} |
{send_timeout_close, boolean()} |
{show_econnreset, boolean()} |
@@ -84,6 +87,7 @@
deliver |
dontroute |
exit_on_close |
+ exclusiveaddruse |
header |
high_msgq_watermark |
high_watermark |
@@ -103,6 +107,8 @@
(ValueBin :: binary())} |
recbuf |
reuseaddr |
+ reuseport |
+ reuseport_lb |
send_timeout |
send_timeout_close |
show_econnreset |
diff --git a/lib/kernel/src/gen_tcp_socket.erl b/lib/kernel/src/gen_tcp_socket.erl
index 7f37e8414d..fb0f807cc1 100644
--- a/lib/kernel/src/gen_tcp_socket.erl
+++ b/lib/kernel/src/gen_tcp_socket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@
%% Undocumented or unsupported
-export([unrecv/2]).
-export([fdopen/2]).
+-export([socket_setopts/2]).
%% gen_statem callbacks
@@ -728,8 +729,10 @@ socket_close(Socket) ->
-compile({inline, [socket_cancel/2]}).
socket_cancel(Socket, SelectInfo) ->
case socket:cancel(Socket, SelectInfo) of
- ok -> ok;
- {error, closed} -> ok
+ ok -> ok;
+ {error, closed} -> ok;
+ {error, _} = ERROR -> ERROR
+
end.
%%% ========================================================================
@@ -1009,6 +1012,7 @@ ignore_optname(Tag) ->
high_msgq_watermark -> true;
high_watermark -> true;
low_msgq_watermark -> true;
+ low_watermark -> true;
nopush -> true;
_ -> false
end.
@@ -1029,7 +1033,6 @@ socket_opts() ->
dontroute => {socket, dontroute},
keepalive => {socket, keepalive},
linger => {socket, linger},
- low_watermark => {socket, rcvlowat},
priority => {socket, priority},
recbuf => {socket, rcvbuf},
reuseaddr => {socket, reuseaddr},
@@ -1574,7 +1577,7 @@ handle_event(
info = SelectInfo, from = From,
listen_socket = ListenSocket},
{P, D}) ->
- socket_cancel(ListenSocket, SelectInfo),
+ _ = socket_cancel(ListenSocket, SelectInfo),
{next_state, 'closed', {P, D},
[{reply, From, {error, timeout}}]};
handle_event(Type, Content, #accept{} = State, P_D) ->
@@ -1660,7 +1663,7 @@ handle_event(
{timeout, connect}, connect,
#connect{info = SelectInfo, from = From},
{#params{socket = Socket} = _P, _D} = P_D) ->
- socket_cancel(Socket, SelectInfo),
+ _ = socket_cancel(Socket, SelectInfo),
_ = socket_close(Socket),
{next_state, 'closed', P_D,
[{reply, From, {error, timeout}}]};
@@ -2267,11 +2270,11 @@ cleanup_close_read(P, D, State, Reason) ->
case State of
#accept{
info = SelectInfo, from = From, listen_socket = ListenSocket} ->
- socket_cancel(ListenSocket, SelectInfo),
+ _ = socket_cancel(ListenSocket, SelectInfo),
{D,
[{reply, From, {error, Reason}}]};
#connect{info = SelectInfo, from = From} ->
- socket_cancel(P#params.socket, SelectInfo),
+ _ = socket_cancel(P#params.socket, SelectInfo),
{D,
[{reply, From, {error, Reason}}]};
_ ->
@@ -2282,7 +2285,7 @@ cleanup_recv(P, D, State, Reason) ->
%% ?DBG({P#params.socket, State, Reason}),
case State of
#recv{info = SelectInfo} ->
- socket_cancel(P#params.socket, SelectInfo),
+ _ = socket_cancel(P#params.socket, SelectInfo),
cleanup_recv_reply(P, D, [], Reason);
_ ->
cleanup_recv_reply(P, D, [], Reason)
@@ -2498,6 +2501,32 @@ tag(Packet) ->
tcp
end.
+
+%% -------
+%% Exported socket option translation
+%%
+socket_setopts(Socket, Opts) ->
+ socket_setopts(
+ Socket,
+ [Opt ||
+ Opt <- internalize_setopts(Opts),
+ element(1, Opt) =/= tcp_module],
+ socket_opts()).
+%%
+socket_setopts(_Socket, [], _SocketOpts) ->
+ ok;
+socket_setopts(Socket, [{Tag,Val} | Opts], SocketOpts) ->
+ case SocketOpts of
+ #{ Tag := Name } ->
+ %% Ignore all errors as an approximation for
+ %% inet_drv ignoring most errors
+ _ = socket_setopt(Socket, Name, Val),
+ socket_setopts(Socket, Opts, SocketOpts);
+ #{} -> % Ignore
+ socket_setopts(Socket, Opts, SocketOpts)
+ end.
+
+
%% -------
%% setopts in server
%%
diff --git a/lib/kernel/src/gen_udp.erl b/lib/kernel/src/gen_udp.erl
index c652a7663f..9debede39b 100644
--- a/lib/kernel/src/gen_udp.erl
+++ b/lib/kernel/src/gen_udp.erl
@@ -38,6 +38,7 @@
{deliver, port | term} |
{dontroute, boolean()} |
{drop_membership, membership()} |
+ {exclusiveaddruse, boolean()} |
{header, non_neg_integer()} |
{high_msgq_watermark, pos_integer()} |
{low_msgq_watermark, pos_integer()} |
@@ -53,6 +54,8 @@
{read_packets, non_neg_integer()} |
{recbuf, non_neg_integer()} |
{reuseaddr, boolean()} |
+ {reuseport, boolean()} |
+ {reuseport_lb, boolean()} |
{sndbuf, non_neg_integer()} |
{tos, non_neg_integer()} |
{tclass, non_neg_integer()} |
@@ -68,6 +71,7 @@
debug |
deliver |
dontroute |
+ exclusiveaddruse |
header |
high_msgq_watermark |
low_msgq_watermark |
@@ -84,6 +88,8 @@
read_packets |
recbuf |
reuseaddr |
+ reuseport |
+ reuseport_lb |
sndbuf |
tos |
tclass |
diff --git a/lib/kernel/src/global.erl b/lib/kernel/src/global.erl
index 4854ed2900..28bd01d228 100644
--- a/lib/kernel/src/global.erl
+++ b/lib/kernel/src/global.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -497,6 +497,7 @@ disconnect() ->
-spec init([]) -> {'ok', state()}.
init([]) ->
+ _ = process_flag(async_dist, true),
process_flag(trap_exit, true),
%% Monitor all 'nodeup'/'nodedown' messages of visible nodes.
@@ -2760,19 +2761,10 @@ inform_connection_loss(_Node, #state{}) ->
%% Volatile send (does not bring up connections and does not
%% preserve signal order) of Msg to global name server at Node...
%%
+
gns_volatile_send(Node, Msg) ->
- To = {global_name_server, Node},
- case erlang:send(To, Msg, [nosuspend, noconnect]) of
- ok ->
- ok;
- noconnect ->
- ok;
- nosuspend ->
- _ = spawn(fun () ->
- _ = erlang:send(To, Msg, [noconnect])
- end),
- ok
- end.
+ _ = erlang:send({global_name_server, Node}, Msg, [noconnect]),
+ ok.
%%
%% Volatile multicast of Msg to all global name servers on known nodes
@@ -2784,14 +2776,17 @@ gns_volatile_multicast(Msg, IgnoreNode, MinVer,
maps:foreach(fun (Node, Ver) when is_atom(Node),
Node =/= IgnoreNode,
Ver >= MinVer ->
- gns_volatile_send(Node, Msg);
+ _ = erlang:send({global_name_server, Node}, Msg,
+ [noconnect]);
({pending, Node}, Ver) when AlsoPend == true,
Node =/= IgnoreNode,
Ver >= MinVer ->
- gns_volatile_send(Node, Msg);
+ _ = erlang:send({global_name_server, Node}, Msg,
+ [noconnect]);
(_, _) ->
ok
- end, Known).
+ end, Known),
+ ok.
is_node_potentially_known(Node, #state{known = Known}) ->
maps:is_key(Node, Known) orelse maps:is_key({pending, Node}, Known).
diff --git a/lib/kernel/src/global_group.erl b/lib/kernel/src/global_group.erl
index 9117a54b61..0fa1854e9e 100644
--- a/lib/kernel/src/global_group.erl
+++ b/lib/kernel/src/global_group.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -260,6 +260,7 @@ start_link() -> gen_server:start_link({local, global_group}, global_group,[],[])
stop() -> gen_server:call(global_group, stop, infinity).
init([]) ->
+ _ = process_flag(async_dist, true),
process_flag(priority, max),
ok = net_kernel:monitor_nodes(true, #{connection_id => true}),
put(registered_names, [undefined]),
@@ -1374,9 +1375,7 @@ make_group_conf(NodeName, KernParamValue) ->
GMap = if OwnNodes == [] ->
all;
true ->
- maps:from_list(lists:map(fun (Node) ->
- {Node, ok}
- end, OwnNodes))
+ #{Node => ok || Node <- OwnNodes}
end,
#gconf{parameter_value = KernParamValue,
node_name = NodeName,
diff --git a/lib/kernel/src/group.erl b/lib/kernel/src/group.erl
index 8410c1a4b5..ca7251d7ec 100644
--- a/lib/kernel/src/group.erl
+++ b/lib/kernel/src/group.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,51 +21,46 @@
%% A group leader process for user io.
--export([start/2, start/3, server/3]).
--export([interfaces/1]).
+-export([start/2, start/3, whereis_shell/0, server/4]).
start(Drv, Shell) ->
start(Drv, Shell, []).
start(Drv, Shell, Options) ->
- spawn_link(group, server, [Drv, Shell, Options]).
+ Ancestors = [self() | case get('$ancestors') of
+ undefined -> [];
+ Anc -> Anc
+ end],
+ spawn_link(group, server, [Ancestors, Drv, Shell, Options]).
-server(Drv, Shell, Options) ->
+server(Ancestors, Drv, Shell, Options) ->
process_flag(trap_exit, true),
+ _ = [put('$ancestors', Ancestors) || Shell =/= {}],
edlin:init(),
put(line_buffer, proplists:get_value(line_buffer, Options, group_history:load())),
put(read_mode, list),
put(user_drv, Drv),
- put(expand_fun,
- proplists:get_value(expand_fun, Options,
- fun(B) -> edlin_expand:expand(B) end)),
+ put(unicode_state, true),
+ ExpandFun = normalize_expand_fun(Options, fun edlin_expand:expand/2),
+ put(expand_fun, ExpandFun),
put(echo, proplists:get_value(echo, Options, true)),
-
- start_shell(Shell),
- server_loop(Drv, get(shell), []).
-
-%% Return the pid of user_drv and the shell process.
-%% Note: We can't ask the group process for this info since it
-%% may be busy waiting for data from the driver.
-interfaces(Group) ->
- case process_info(Group, dictionary) of
- {dictionary,Dict} ->
- get_pids(Dict, [], false);
- _ ->
- []
+ put(expand_below, proplists:get_value(expand_below, Options, true)),
+
+ server_loop(Drv, start_shell(Shell), []).
+
+whereis_shell() ->
+ case node(group_leader()) of
+ Node when Node =:= node() ->
+ case user_drv:whereis_group() of
+ undefined -> undefined;
+ GroupPid ->
+ {dictionary, Dict} = erlang:process_info(GroupPid, dictionary),
+ proplists:get_value(shell, Dict)
+ end;
+ OtherNode ->
+ erpc:call(OtherNode, group, whereis_shell, [])
end.
-get_pids([Drv = {user_drv,_} | Rest], Found, _) ->
- get_pids(Rest, [Drv | Found], true);
-get_pids([Sh = {shell,_} | Rest], Found, Active) ->
- get_pids(Rest, [Sh | Found], Active);
-get_pids([_ | Rest], Found, Active) ->
- get_pids(Rest, Found, Active);
-get_pids([], Found, true) ->
- Found;
-get_pids([], _Found, false) ->
- [].
-
%% start_shell(Shell)
%% Spawn a shell with its group_leader from the beginning set to ourselves.
%% If Shell a pid the set its group_leader.
@@ -81,7 +76,8 @@ start_shell(Shell) when is_function(Shell) ->
start_shell(Shell) when is_pid(Shell) ->
group_leader(self(), Shell), % we are the shells group leader
link(Shell), % we're linked to it.
- put(shell, Shell);
+ put(shell, Shell),
+ Shell;
start_shell(_Shell) ->
ok.
@@ -92,9 +88,11 @@ start_shell1(M, F, Args) ->
Shell when is_pid(Shell) ->
group_leader(G, self()),
link(Shell), % we're linked to it.
- put(shell, Shell);
+ put(shell, Shell),
+ Shell;
Error -> % start failure
exit(Error) % let the group process crash
+
end.
start_shell1(Fun) ->
@@ -104,19 +102,20 @@ start_shell1(Fun) ->
Shell when is_pid(Shell) ->
group_leader(G, self()),
link(Shell), % we're linked to it.
- put(shell, Shell);
+ put(shell, Shell),
+ Shell;
Error -> % start failure
exit(Error) % let the group process crash
end.
server_loop(Drv, Shell, Buf0) ->
receive
- {io_request,From,ReplyAs,Req} when is_pid(From) ->
+ {io_request,From,ReplyAs,Req} when is_pid(From) ->
%% This io_request may cause a transition to a couple of
%% selective receive loops elsewhere in this module.
Buf = io_request(Req, From, ReplyAs, Drv, Shell, Buf0),
server_loop(Drv, Shell, Buf);
- {reply,{{From,ReplyAs},Reply}} ->
+ {reply,{From,ReplyAs},Reply} ->
io_reply(From, ReplyAs, Reply),
server_loop(Drv, Shell, Buf0);
{driver_id,ReplyTo} ->
@@ -152,30 +151,40 @@ exit_shell(Reason) ->
get_tty_geometry(Drv) ->
Drv ! {self(),tty_geometry},
receive
- {Drv,tty_geometry,Geometry} ->
- Geometry
+ {Drv,tty_geometry,Geometry} ->
+ Geometry
after 2000 ->
- timeout
+ timeout
end.
get_unicode_state(Drv) ->
Drv ! {self(),get_unicode_state},
receive
- {Drv,get_unicode_state,UniState} ->
- UniState;
- {Drv,get_unicode_state,error} ->
- {error, internal}
+ {Drv,get_unicode_state,UniState} ->
+ UniState;
+ {Drv,get_unicode_state,error} ->
+ {error, internal}
after 2000 ->
- {error,timeout}
+ {error,timeout}
end.
set_unicode_state(Drv,Bool) ->
Drv ! {self(),set_unicode_state,Bool},
receive
- {Drv,set_unicode_state,_OldUniState} ->
- ok
+ {Drv,set_unicode_state,_OldUniState} ->
+ ok
+ after 2000 ->
+ timeout
+ end.
+
+get_terminal_state(Drv) ->
+ Drv ! {self(),get_terminal_state},
+ receive
+ {Drv,get_terminal_state,UniState} ->
+ UniState;
+ {Drv,get_terminal_state,error} ->
+ {error, internal}
after 2000 ->
- timeout
+ {error,timeout}
end.
-
io_request(Req, From, ReplyAs, Drv, Shell, Buf0) ->
case io_request(Req, Drv, Shell, {From,ReplyAs}, Buf0) of
@@ -197,12 +206,12 @@ io_request(Req, From, ReplyAs, Drv, Shell, Buf0) ->
end.
-%% Put_chars, unicode is the normal message, characters are always in
+%% Put_chars, unicode is the normal message, characters are always in
%%standard unicode
%% format.
-%% You might be tempted to send binaries unchecked, but the driver
+%% You might be tempted to send binaries unchecked, but the driver
%% expects unicode, so that is what we should send...
-%% io_request({put_chars,unicode,Binary}, Drv, Buf) when is_binary(Binary) ->
+%% io_request({put_chars,unicode,Binary}, Drv, Buf) when is_binary(Binary) ->
%% send_drv(Drv, {put_chars,Binary}),
%% {ok,ok,Buf};
%%
@@ -211,7 +220,7 @@ io_request(Req, From, ReplyAs, Drv, Shell, Buf0) ->
io_request({put_chars,unicode,Chars}, Drv, _Shell, From, Buf) ->
case catch unicode:characters_to_binary(Chars,utf8) of
Binary when is_binary(Binary) ->
- send_drv(Drv, {put_chars_sync, unicode, Binary, {From,ok}}),
+ send_drv(Drv, {put_chars_sync, unicode, Binary, From}),
{noreply,Buf};
_ ->
{error,{error,{put_chars, unicode,Chars}},Buf}
@@ -219,41 +228,51 @@ io_request({put_chars,unicode,Chars}, Drv, _Shell, From, Buf) ->
io_request({put_chars,unicode,M,F,As}, Drv, _Shell, From, Buf) ->
case catch apply(M, F, As) of
Binary when is_binary(Binary) ->
- send_drv(Drv, {put_chars_sync, unicode, Binary, {From,ok}}),
+ send_drv(Drv, {put_chars_sync, unicode, Binary, From}),
{noreply,Buf};
Chars ->
case catch unicode:characters_to_binary(Chars,utf8) of
B when is_binary(B) ->
- send_drv(Drv, {put_chars_sync, unicode, B, {From,ok}}),
+ send_drv(Drv, {put_chars_sync, unicode, B, From}),
{noreply,Buf};
_ ->
{error,{error,F},Buf}
end
end;
io_request({put_chars,latin1,Binary}, Drv, _Shell, From, Buf) when is_binary(Binary) ->
- send_drv(Drv, {put_chars_sync, unicode,
- unicode:characters_to_binary(Binary,latin1),
- {From,ok}}),
+ IsUnicode = get(unicode_state),
+ if IsUnicode ->
+ send_drv(Drv,
+ {put_chars_sync, unicode,
+ unicode:characters_to_binary(Binary,latin1),
+ From});
+ true ->
+ send_drv(Drv, {put_chars_sync, latin1, Binary, From})
+ end,
{noreply,Buf};
io_request({put_chars,latin1,Chars}, Drv, _Shell, From, Buf) ->
- case catch unicode:characters_to_binary(Chars,latin1) of
- Binary when is_binary(Binary) ->
- send_drv(Drv, {put_chars_sync, unicode, Binary, {From,ok}}),
- {noreply,Buf};
- _ ->
- {error,{error,{put_chars,latin1,Chars}},Buf}
+ IsUnicode = get(unicode_state),
+ if IsUnicode ->
+ case catch unicode:characters_to_binary(Chars,latin1) of
+ Binary when is_binary(Binary) ->
+ send_drv(Drv, {put_chars_sync, unicode, Binary, From}),
+ {noreply,Buf};
+ _ ->
+ {error,{error,{put_chars,latin1,Chars}},Buf}
+ end;
+ true -> send_drv(Drv, {put_chars_sync, latin1, Chars, From})
end;
io_request({put_chars,latin1,M,F,As}, Drv, _Shell, From, Buf) ->
case catch apply(M, F, As) of
Binary when is_binary(Binary) ->
send_drv(Drv, {put_chars_sync, unicode,
unicode:characters_to_binary(Binary,latin1),
- {From,ok}}),
+ From}),
{noreply,Buf};
Chars ->
case catch unicode:characters_to_binary(Chars,latin1) of
B when is_binary(B) ->
- send_drv(Drv, {put_chars_sync, unicode, B, {From,ok}}),
+ send_drv(Drv, {put_chars_sync, unicode, B, From}),
{noreply,Buf};
_ ->
{error,{error,F},Buf}
@@ -384,13 +403,15 @@ check_valid_opts(_) ->
false.
do_setopts(Opts, Drv, Buf) ->
- put(expand_fun, proplists:get_value(expand_fun, Opts, get(expand_fun))),
+ put(expand_fun, normalize_expand_fun(Opts, get(expand_fun))),
put(echo, proplists:get_value(echo, Opts, get(echo))),
case proplists:get_value(encoding,Opts) of
Valid when Valid =:= unicode; Valid =:= utf8 ->
- set_unicode_state(Drv,true);
+ set_unicode_state(Drv,true),
+ put(unicode_state, true);
latin1 ->
- set_unicode_state(Drv,false);
+ set_unicode_state(Drv,false),
+ put(unicode_state, false);
_ ->
ok
end,
@@ -408,6 +429,12 @@ do_setopts(Opts, Drv, Buf) ->
{ok,ok,Buf}
end.
+normalize_expand_fun(Options, Default) ->
+ case proplists:get_value(expand_fun, Options, Default) of
+ Fun when is_function(Fun, 1) -> fun(X,_) -> Fun(X) end;
+ Fun -> Fun
+ end.
+
getopts(Drv,Buf) ->
Exp = {expand_fun, case get(expand_fun) of
Func when is_function(Func) ->
@@ -431,8 +458,8 @@ getopts(Drv,Buf) ->
true -> unicode;
_ -> latin1
end},
- {ok,[Exp,Echo,Bin,Uni],Buf}.
-
+ Tty = {terminal, get_terminal_state(Drv)},
+ {ok,[Exp,Echo,Bin,Uni,Tty],Buf}.
%% get_chars_*(Prompt, Module, Function, XtraArgument, Drv, Buffer)
%% Gets characters from the input Drv until as the applied function
@@ -456,47 +483,64 @@ get_chars_n(Prompt, M, F, Xa, Drv, Shell, Buf, Encoding) ->
Pbs = prompt_bytes(Prompt, Encoding),
case get(echo) of
true ->
- get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf, start, Encoding);
+ get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf, start, [], Encoding);
false ->
get_chars_n_loop(Pbs, M, F, Xa, Drv, Shell, Buf, start, Encoding)
end.
get_chars_line(Prompt, M, F, Xa, Drv, Shell, Buf, Encoding) ->
Pbs = prompt_bytes(Prompt, Encoding),
- get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf, start, Encoding).
+ get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf, start, [], Encoding).
-get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf0, State, Encoding) ->
+get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf0, State, LineCont0, Encoding) ->
Result = case get(echo) of
- true ->
- get_line(Buf0, Pbs, Drv, Shell, Encoding);
- false ->
- % get_line_echo_off only deals with lists
- % and does not need encoding...
- get_line_echo_off(Buf0, Pbs, Drv, Shell)
- end,
+ true ->
+ get_line(Buf0, Pbs, LineCont0, Drv, Shell, Encoding);
+ false ->
+ %% get_line_echo_off only deals with lists
+ %% and does not need encoding...
+ get_line_echo_off(Buf0, Pbs, Drv, Shell)
+ end,
case Result of
- {done,Line,Buf} ->
- get_chars_apply(Pbs, M, F, Xa, Drv, Shell, Buf, State, Line, Encoding);
- interrupted ->
- {error,{error,interrupted},[]};
- terminated ->
- {exit,terminated}
+ {done,LineCont1,Buf} ->
+ get_chars_apply(Pbs, M, F, Xa, Drv, Shell, Buf, State, LineCont1, Encoding);
+
+ interrupted ->
+ {error,{error,interrupted},[]};
+ terminated ->
+ {exit,terminated}
end.
-get_chars_apply(Pbs, M, F, Xa, Drv, Shell, Buf, State0, Line, Encoding) ->
- case catch M:F(State0, cast(Line,get(read_mode), Encoding), Encoding, Xa) of
+get_chars_apply(Pbs, M, F, Xa, Drv, Shell, Buf, State0, LineCont, Encoding) ->
+ %% multi line support means that we should not keep the state
+ %% but we need to keep it for oldshell mode
+ {State, Line} = case get(echo) of
+ true -> {start, edlin:current_line(LineCont)};
+ false -> {State0, LineCont}
+ end,
+ case catch M:F(State, cast(Line,get(read_mode), Encoding), Encoding, Xa) of
+ {stop,Result,eof} ->
+ {ok,Result,eof};
{stop,Result,Rest} ->
+ _ = case {M,F} of
+ {io_lib, get_until} ->
+ save_line_buffer(string:trim(Line, both)++"\n", get_lines(new_stack(get(line_buffer))));
+ _ ->
+ skip
+ end,
{ok,Result,append(Rest, Buf, Encoding)};
{'EXIT',_} ->
{error,{error,err_func(M, F, Xa)},[]};
State1 ->
- get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf, State1, Encoding)
+ get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf, State1, LineCont, Encoding)
end.
get_chars_n_loop(Pbs, M, F, Xa, Drv, Shell, Buf0, State, Encoding) ->
try M:F(State, cast(Buf0, get(read_mode), Encoding), Encoding, Xa) of
+ {stop,Result,eof} ->
+ {ok,Result,eof};
{stop,Result,Rest} ->
- {ok, Result, Rest};
+ {ok,Result,append(Rest, [], Encoding)};
State1 ->
case get_chars_echo_off(Pbs, Drv, Shell) of
interrupted ->
@@ -520,54 +564,73 @@ err_func(_, F, _) ->
%% Get a line with eventual line editing. Handle other io requests
%% while getting line.
%% Returns:
-%% {done,LineChars,RestChars}
-%% interrupted
-
-get_line(Chars, Pbs, Drv, Shell, Encoding) ->
- {more_chars,Cont,Rs} = edlin:start(Pbs),
+%% {done,LineChars,RestChars}
+%% interrupted
+get_line(Chars, Pbs, Cont, Drv, Shell, Encoding) ->
+ {more_chars,Cont1,Rs} = case Cont of
+ [] -> edlin:start(Pbs);
+ _ -> edlin:start(Pbs, Cont)
+ end,
send_drv_reqs(Drv, Rs),
- get_line1(edlin:edit_line(Chars, Cont), Drv, Shell, new_stack(get(line_buffer)),
- Encoding).
+ get_line1(edlin:edit_line(Chars, Cont1), Drv, Shell, new_stack(get(line_buffer)),
+ Encoding).
-get_line1({done,Line,Rest,Rs}, Drv, _Shell, Ls, _Encoding) ->
+get_line1({done, Cont, Rest, Rs}, Drv, _Shell, _Ls, _Encoding) ->
send_drv_reqs(Drv, Rs),
- save_line_buffer(Line, get_lines(Ls)),
- {done,Line,Rest};
+ {done, Cont, Rest};
+get_line1({undefined,{_A, Mode, Char}, _Cs, Cont, Rs}, Drv, Shell, Ls0, Encoding)
+ when Mode =:= none, Char =:= $\^O;
+ Mode =:= meta, Char =:= $o ->
+ send_drv_reqs(Drv, Rs),
+ Buffer = edlin:current_line(Cont),
+ send_drv(Drv, {open_editor, Buffer}),
+ receive
+ {Drv, {editor_data, Cs}} ->
+ send_drv_reqs(Drv, edlin:erase_line()),
+ {more_chars,NewCont,NewRs} = edlin:start(edlin:prompt(Cont)),
+ send_drv_reqs(Drv, NewRs),
+ get_line1(edlin:edit_line(Cs, NewCont), Drv, Shell, Ls0, Encoding)
+ end;
+%% Move Up, Down in History: Ctrl+P, Ctrl+N
get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding)
- when ((Mode =:= none) and (Char =:= $\^P))
- or ((Mode =:= meta_left_sq_bracket) and (Char =:= $A)) ->
+ when Mode =:= none, Char =:= $\^P;
+ Mode =:= meta_left_sq_bracket, Char =:= $A ->
send_drv_reqs(Drv, Rs),
case up_stack(save_line(Ls0, edlin:current_line(Cont))) of
- {none,_Ls} ->
- send_drv(Drv, beep),
- get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls0, Encoding);
- {Lcs,Ls} ->
- send_drv_reqs(Drv, edlin:erase_line(Cont)),
- {more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
- send_drv_reqs(Drv, Nrs),
- get_line1(edlin:edit_line1(lists:sublist(Lcs, 1, length(Lcs)-1),
- Ncont),
- Drv,
- Shell,
- Ls, Encoding)
+ {none,_Ls} ->
+ send_drv(Drv, beep),
+ get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls0, Encoding);
+ {Lcs,Ls} ->
+ send_drv_reqs(Drv, edlin:erase_line()),
+ {more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
+ send_drv_reqs(Drv, Nrs),
+ get_line1(edlin:edit_line1(string:to_graphemes(lists:sublist(Lcs,
+ 1,
+ length(Lcs)-1)),
+ Ncont),
+ Drv,
+ Shell,
+ Ls, Encoding)
end;
get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding)
- when ((Mode =:= none) and (Char =:= $\^N))
- or ((Mode =:= meta_left_sq_bracket) and (Char =:= $B)) ->
+ when Mode =:= none, Char =:= $\^N;
+ Mode =:= meta_left_sq_bracket, Char =:= $B ->
send_drv_reqs(Drv, Rs),
case down_stack(save_line(Ls0, edlin:current_line(Cont))) of
- {none,_Ls} ->
- send_drv(Drv, beep),
- get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls0, Encoding);
- {Lcs,Ls} ->
- send_drv_reqs(Drv, edlin:erase_line(Cont)),
- {more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
- send_drv_reqs(Drv, Nrs),
- get_line1(edlin:edit_line1(lists:sublist(Lcs, 1, length(Lcs)-1),
- Ncont),
- Drv,
- Shell,
- Ls, Encoding)
+ {none,_Ls} ->
+ send_drv(Drv, beep),
+ get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls0, Encoding);
+ {Lcs,Ls} ->
+ send_drv_reqs(Drv, edlin:erase_line()),
+ {more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
+ send_drv_reqs(Drv, Nrs),
+ get_line1(edlin:edit_line1(string:to_graphemes(lists:sublist(Lcs,
+ 1,
+ length(Lcs)-1)),
+ Ncont),
+ Drv,
+ Shell,
+ Ls, Encoding)
end;
%% ^R = backward search, ^S = forward search.
%% Search is tricky to implement and does a lot of back-and-forth
@@ -580,82 +643,151 @@ get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Shell, Ls0, Encoding)
%% the regular ones (none, meta_left_sq_bracket) and handle special
%% cases of history search.
get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Shell, Ls, Encoding)
- when ((Mode =:= none) and (Char =:= $\^R)) ->
+ when Mode =:= none, Char =:= $\^R ->
send_drv_reqs(Drv, Rs),
%% drop current line, move to search mode. We store the current
%% prompt ('N>') and substitute it with the search prompt.
- send_drv_reqs(Drv, edlin:erase_line(Cont)),
- put(search_quit_prompt, edlin:prompt(Cont)),
- Pbs = prompt_bytes("(search)`': ", Encoding),
- {more_chars,Ncont,Nrs} = edlin:start(Pbs, search),
- send_drv_reqs(Drv, Nrs),
+ put(search_quit_prompt, Cont),
+ Pbs = prompt_bytes("\033[;1;4msearch:\033[0m ", Encoding),
+ {more_chars,Ncont,_Nrs} = edlin:start(Pbs, search),
get_line1(edlin:edit_line1(Cs, Ncont), Drv, Shell, Ls, Encoding);
-get_line1({expand, Before, Cs0, Cont,Rs}, Drv, Shell, Ls0, Encoding) ->
+get_line1({Expand, Before, Cs0, Cont,Rs}, Drv, Shell, Ls0, Encoding)
+ when Expand =:= expand; Expand =:= expand_full ->
send_drv_reqs(Drv, Rs),
ExpandFun = get(expand_fun),
- {Found, Add, Matches} = ExpandFun(Before),
+ {Found, CompleteChars, Matches} = ExpandFun(Before, []),
case Found of
- no -> send_drv(Drv, beep);
- yes -> ok
+ no -> send_drv(Drv, beep);
+ _ -> ok
end,
- Cs1 = append(Add, Cs0, Encoding), %%XXX:PaN should this always be unicode?
- Cs = case Matches of
- [] -> Cs1;
- _ -> MatchStr = edlin_expand:format_matches(Matches),
- send_drv(Drv, {put_chars, unicode, unicode:characters_to_binary(MatchStr,unicode)}),
- [$\^L | Cs1]
- end,
+ {Width, _Height} = get_tty_geometry(Drv),
+ Cs1 = append(CompleteChars, Cs0, Encoding),
+
+ MatchStr = case Matches of
+ [] -> [];
+ _ -> edlin_expand:format_matches(Matches, Width)
+ end,
+ Cs = case {Cs1, MatchStr} of
+ {_, []} -> Cs1;
+ {Cs1, _} when Cs1 =/= [] -> Cs1;
+ _ ->
+ NlMatchStr = unicode:characters_to_binary("\n"++MatchStr),
+ case get(expand_below) of
+ true ->
+ Lines = string:split(string:trim(MatchStr), "\n", all),
+ NoLines = length(Lines),
+ if NoLines > 5, Expand =:= expand ->
+ %% Only show 5 lines to start with
+ [L1,L2,L3,L4,L5|_] = Lines,
+ String = lists:join(
+ $\n,
+ [L1,L2,L3,L4,L5,
+ io_lib:format("Press tab to see all ~p expansions",
+ [edlin_expand:number_matches(Matches)])]),
+ send_drv(Drv, {put_expand, unicode,
+ unicode:characters_to_binary(String)}),
+ Cs1;
+ true ->
+ case get_tty_geometry(Drv) of
+ {_, Rows} when Rows > NoLines ->
+ %% If all lines fit on screen, we expand below
+ send_drv(Drv, {put_expand, unicode, NlMatchStr}),
+ Cs1;
+ _ ->
+ %% If there are more results than fit on
+ %% screen we expand above
+ send_drv_reqs(Drv, [{put_chars_keep_state, unicode, NlMatchStr},redraw_prompt]),
+ [$\e, $l | Cs1]
+ end
+ end;
+ false ->
+ send_drv(Drv, {put_chars_keep_state, unicode, NlMatchStr}),
+ [$\e, $l | Cs1]
+ end
+ end,
get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls0, Encoding);
+get_line1({undefined, {_, search_quit, _}, _Cs, _Cont={line, P, Line, none}, Rs}, Drv, Shell, Ls, Encoding) ->
+ get_line1({more_chars, {line, P, Line, search_quit}, Rs}, Drv, Shell, Ls, Encoding);
get_line1({undefined,_Char,Cs,Cont,Rs}, Drv, Shell, Ls, Encoding) ->
send_drv_reqs(Drv, Rs),
send_drv(Drv, beep),
get_line1(edlin:edit_line(Cs, Cont), Drv, Shell, Ls, Encoding);
%% The search item was found and accepted (new line entered on the exact
%% result found)
-get_line1({_What,Cont={line,_Prompt,_Chars,search_found},Rs}, Drv, Shell, Ls0, Encoding) ->
- Line = edlin:current_line(Cont),
- %% this may create duplicate entries.
- Ls = save_line(new_stack(get_lines(Ls0)), Line),
- get_line1({done, Line, "", Rs}, Drv, Shell, Ls, Encoding);
+get_line1({_What,{line,_,_Drv,search_found},Rs}, Drv, Shell, Ls0, Encoding) ->
+ SearchResult = get(search_result),
+ LineCont = case SearchResult of
+ [] -> {[],{[],[]},[]};
+ _ -> [Last| LB] = lists:reverse(SearchResult),
+ {LB, {lists:reverse(Last),[]},[]}
+ end,
+ Prompt = edlin:prompt(get(search_quit_prompt)),
+ send_drv_reqs(Drv, Rs),
+ send_drv_reqs(Drv, edlin:erase_line()),
+ send_drv_reqs(Drv, edlin:redraw_line({line, Prompt, LineCont, none})),
+ put(search_result, []),
+ %% TODO, do even need to save it, won't it be saved by handling {done...}?
+ Ls = save_line(new_stack(get_lines(Ls0)), edlin:current_line({line, edlin:prompt(get(search_quit_prompt)), LineCont, none})),
+ get_line1({done, LineCont, "\n", Rs}, Drv, Shell, Ls, Encoding);
%% The search mode has been exited, but the user wants to remain in line
%% editing mode wherever that was, but editing the search result.
-get_line1({What,Cont={line,_Prompt,_Chars,search_quit},Rs}, Drv, Shell, Ls, Encoding) ->
- Line = edlin:current_chars(Cont),
+get_line1({What,{line,_,_,search_quit},Rs}, Drv, Shell, Ls, Encoding) ->
%% Load back the old prompt with the correct line number.
- case get(search_quit_prompt) of
- undefined -> % should not happen. Fallback.
- LsFallback = save_line(new_stack(get_lines(Ls)), Line),
- get_line1({done, "\n", Line, Rs}, Drv, Shell, LsFallback, Encoding);
- Prompt -> % redraw the line and keep going with the same stack position
- NCont = {line,Prompt,{lists:reverse(Line),[]},none},
- send_drv_reqs(Drv, Rs),
- send_drv_reqs(Drv, edlin:erase_line(Cont)),
- send_drv_reqs(Drv, edlin:redraw_line(NCont)),
- get_line1({What, NCont ,[]}, Drv, Shell, pad_stack(Ls), Encoding)
+ case edlin:prompt(get(search_quit_prompt)) of
+ Prompt -> % redraw the line and keep going with the same stack position
+ SearchResult = get(search_result),
+ L = case SearchResult of
+ [] -> {[],{[],[]},[]};
+ _ -> [Last|LB] = lists:reverse(SearchResult),
+ {LB, {lists:reverse(Last), []}, []}
+ end,
+ NCont = {line,Prompt,L,none},
+ put(search_result, []),
+ send_drv_reqs(Drv, [delete_line|Rs]),
+ send_drv_reqs(Drv, edlin:redraw_line(NCont)),
+ get_line1({What, NCont ,[]}, Drv, Shell, pad_stack(Ls), Encoding)
end;
+get_line1({What,_Cont={line,_,_,search_cancel},Rs}, Drv, Shell, Ls, Encoding) ->
+ NCont = get(search_quit_prompt),
+ put(search_result, []),
+ send_drv_reqs(Drv, [delete_line|Rs]),
+ send_drv_reqs(Drv, edlin:redraw_line(NCont)),
+ get_line1({What, NCont, []}, Drv, Shell, Ls, Encoding);
%% Search mode is entered.
-get_line1({What,{line,Prompt,{RevCmd0,_Aft},search},Rs},
- Drv, Shell, Ls0, Encoding) ->
- send_drv_reqs(Drv, Rs),
+get_line1({What,{line,Prompt,{_,{RevCmd0,_},_},search},_Rs},
+ Drv, Shell, Ls0, Encoding) ->
%% Figure out search direction. ^S and ^R are returned through edlin
%% whenever we received a search while being already in search mode.
{Search, Ls1, RevCmd} = case RevCmd0 of
- [$\^S|RevCmd1] ->
- {fun search_down_stack/2, Ls0, RevCmd1};
- [$\^R|RevCmd1] ->
- {fun search_up_stack/2, Ls0, RevCmd1};
- _ -> % new search, rewind stack for a proper search.
- {fun search_up_stack/2, new_stack(get_lines(Ls0)), RevCmd0}
- end,
+ [$\^S|RevCmd1] ->
+ {fun search_down_stack/2, Ls0, RevCmd1};
+ [$\^R|RevCmd1] ->
+ {fun search_up_stack/2, Ls0, RevCmd1};
+ _ -> % new search, rewind stack for a proper search.
+ {fun search_up_stack/2, new_stack(get_lines(Ls0)), RevCmd0}
+ end,
Cmd = lists:reverse(RevCmd),
{Ls, NewStack} = case Search(Ls1, Cmd) of
- {none, Ls2} ->
- send_drv(Drv, beep),
- {Ls2, {RevCmd, "': "}};
- {Line, Ls2} -> % found. Complete the output edlin couldn't have done.
- send_drv_reqs(Drv, [{put_chars, Encoding, Line}]),
- {Ls2, {RevCmd, "': "++Line}}
- end,
+ {none, Ls2} ->
+ send_drv(Drv, beep),
+ put(search_result, []),
+ send_drv(Drv, delete_line),
+ send_drv(Drv, {put_chars, unicode, unicode:characters_to_binary(Prompt++Cmd)}),
+ {Ls2, {[],{RevCmd, []},[]}};
+ {Line, Ls2} -> % found. Complete the output edlin couldn't have done.
+ Lines = string:split(string:to_graphemes(Line), "\n", all),
+ Output = if length(Lines) > 5 ->
+ [A,B,C,D,E|_]=Lines,
+ (["\n " ++ Line1 || Line1 <- [A,B,C,D,E]] ++
+ [io_lib:format("~n ... (~w lines omitted)",[length(Lines)-5])]);
+ true -> ["\n " ++ Line1 || Line1 <- Lines]
+ end,
+ put(search_result, Lines),
+ send_drv(Drv, delete_line),
+ send_drv(Drv, {put_chars, unicode, unicode:characters_to_binary(Prompt++Cmd)}),
+ send_drv(Drv, {put_expand_no_trim, unicode, unicode:characters_to_binary(Output)}),
+ {Ls2, {[],{RevCmd, []},[]}}
+ end,
Cont = {line,Prompt,NewStack,search},
more_data(What, Cont, Drv, Shell, Ls, Encoding);
get_line1({What,Cont0,Rs}, Drv, Shell, Ls, Encoding) ->
@@ -664,29 +796,32 @@ get_line1({What,Cont0,Rs}, Drv, Shell, Ls, Encoding) ->
more_data(What, Cont0, Drv, Shell, Ls, Encoding) ->
receive
- {Drv,{data,Cs}} ->
- get_line1(edlin:edit_line(Cs, Cont0), Drv, Shell, Ls, Encoding);
- {Drv,eof} ->
- get_line1(edlin:edit_line(eof, Cont0), Drv, Shell, Ls, Encoding);
- {io_request,From,ReplyAs,Req} when is_pid(From) ->
- {more_chars,Cont,_More} = edlin:edit_line([], Cont0),
- send_drv_reqs(Drv, edlin:erase_line(Cont)),
- io_request(Req, From, ReplyAs, Drv, Shell, []), %WRONG!!!
- send_drv_reqs(Drv, edlin:redraw_line(Cont)),
- get_line1({more_chars,Cont,[]}, Drv, Shell, Ls, Encoding);
- {reply,{{From,ReplyAs},Reply}} ->
+ {Drv, activate} ->
+ send_drv_reqs(Drv, edlin:redraw_line(Cont0)),
+ more_data(What, Cont0, Drv, Shell, Ls, Encoding);
+ {Drv,{data,Cs}} ->
+ get_line1(edlin:edit_line(Cs, Cont0), Drv, Shell, Ls, Encoding);
+ {Drv,eof} ->
+ get_line1(edlin:edit_line(eof, Cont0), Drv, Shell, Ls, Encoding);
+ {io_request,From,ReplyAs,Req} when is_pid(From) ->
+ {more_chars,Cont,_More} = edlin:edit_line([], Cont0),
+ send_drv_reqs(Drv, edlin:erase_line()),
+ io_request(Req, From, ReplyAs, Drv, Shell, []), %WRONG!!!
+ send_drv_reqs(Drv, edlin:redraw_line(Cont)),
+ get_line1({more_chars,Cont,[]}, Drv, Shell, Ls, Encoding);
+ {reply,{From,ReplyAs},Reply} ->
%% We take care of replies from puts here as well
io_reply(From, ReplyAs, Reply),
more_data(What, Cont0, Drv, Shell, Ls, Encoding);
- {'EXIT',Drv,interrupt} ->
- interrupted;
- {'EXIT',Drv,_} ->
- terminated;
- {'EXIT',Shell,R} ->
- exit(R)
+ {'EXIT',Drv,interrupt} ->
+ interrupted;
+ {'EXIT',Drv,_} ->
+ terminated;
+ {'EXIT',Shell,R} ->
+ exit(R)
after
- get_line_timeout(What)->
- get_line1(edlin:edit_line([], Cont0), Drv, Shell, Ls, Encoding)
+ get_line_timeout(What)->
+ get_line1(edlin:edit_line([], Cont0), Drv, Shell, Ls, Encoding)
end.
get_line_echo_off(Chars, Pbs, Drv, Shell) ->
@@ -702,7 +837,7 @@ get_line_echo_off1({Chars,[]}, Drv, Shell) ->
{io_request,From,ReplyAs,Req} when is_pid(From) ->
io_request(Req, From, ReplyAs, Drv, Shell, []),
get_line_echo_off1({Chars,[]}, Drv, Shell);
- {reply,{{From,ReplyAs},Reply}} when From =/= undefined ->
+ {reply,{From,ReplyAs},Reply} when From =/= undefined ->
%% We take care of replies from puts here as well
io_reply(From, ReplyAs, Reply),
get_line_echo_off1({Chars,[]},Drv, Shell);
@@ -713,6 +848,8 @@ get_line_echo_off1({Chars,[]}, Drv, Shell) ->
{'EXIT',Shell,R} ->
exit(R)
end;
+get_line_echo_off1(eof, _Drv, _Shell) ->
+ {done,eof,eof};
get_line_echo_off1({Chars,Rest}, _Drv, _Shell) ->
{done,lists:reverse(Chars),case Rest of done -> []; _ -> Rest end}.
@@ -724,21 +861,21 @@ get_chars_echo_off1(Drv, Shell) ->
receive
{Drv, {data, Cs}} ->
Cs;
- {Drv, eof} ->
+ {Drv, eof} ->
eof;
- {io_request,From,ReplyAs,Req} when is_pid(From) ->
- io_request(Req, From, ReplyAs, Drv, Shell, []),
- get_chars_echo_off1(Drv, Shell);
- {reply,{{From,ReplyAs},Reply}} when From =/= undefined ->
+ {io_request,From,ReplyAs,Req} when is_pid(From) ->
+ io_request(Req, From, ReplyAs, Drv, Shell, []),
+ get_chars_echo_off1(Drv, Shell);
+ {reply,{From,ReplyAs},Reply} when From =/= undefined ->
%% We take care of replies from puts here as well
io_reply(From, ReplyAs, Reply),
get_chars_echo_off1(Drv, Shell);
- {'EXIT',Drv,interrupt} ->
- interrupted;
- {'EXIT',Drv,_} ->
- terminated;
- {'EXIT',Shell,R} ->
- exit(R)
+ {'EXIT',Drv,interrupt} ->
+ interrupted;
+ {'EXIT',Drv,_} ->
+ terminated;
+ {'EXIT',Shell,R} ->
+ exit(R)
end.
%% We support line editing for the ICANON mode except the following
@@ -750,8 +887,10 @@ get_chars_echo_off1(Drv, Shell) ->
%% - ^d in posix/icanon mode: eof, delete-forward in edlin
%% - ^r in posix/icanon mode: reprint (silly in echo-off mode :-))
%% - ^w in posix/icanon mode: word-erase (produces a beep in edlin)
+edit_line(eof, []) ->
+ eof;
edit_line(eof, Chars) ->
- {Chars,done};
+ {Chars,eof};
edit_line([],Chars) ->
{Chars,[]};
edit_line([$\r,$\n|Cs],Chars) ->
@@ -817,8 +956,8 @@ get_all_lines({stack, U, {}, []}) ->
U;
get_all_lines({stack, U, {}, D}) ->
case lists:reverse(D, U) of
- ["\n"|Lines] -> Lines;
- Lines -> Lines
+ ["\n"|Lines] -> Lines;
+ Lines -> Lines
end;
get_all_lines({stack, U, L, D}) ->
get_all_lines({stack, U, {}, [L|D]}).
@@ -842,22 +981,22 @@ save_line_buffer(Lines) ->
search_up_stack(Stack, Substr) ->
case up_stack(Stack) of
- {none,NewStack} -> {none,NewStack};
- {L, NewStack} ->
+ {none,NewStack} -> {none,NewStack};
+ {L, NewStack} ->
case string:find(L, Substr) of
nomatch -> search_up_stack(NewStack, Substr);
_ -> {string:trim(L, trailing, "$\n"), NewStack}
- end
+ end
end.
search_down_stack(Stack, Substr) ->
case down_stack(Stack) of
- {none,NewStack} -> {none,NewStack};
- {L, NewStack} ->
- case string:find(L, Substr) of
- nomatch -> search_down_stack(NewStack, Substr);
- _ -> {string:trim(L, trailing, "$\n"), NewStack}
- end
+ {none,NewStack} -> {none,NewStack};
+ {L, NewStack} ->
+ case string:find(L, Substr) of
+ nomatch -> search_down_stack(NewStack, Substr);
+ _ -> {string:trim(L, trailing, "$\n"), NewStack}
+ end
end.
@@ -871,16 +1010,15 @@ get_password1({Chars,[]}, Drv, Shell) ->
{Drv,{data,Cs}} ->
get_password1(edit_password(Cs,Chars),Drv,Shell);
{io_request,From,ReplyAs,Req} when is_pid(From) ->
- %send_drv_reqs(Drv, [{delete_chars, -length(Pbs)}]),
io_request(Req, From, ReplyAs, Drv, Shell, []), %WRONG!!!
%% I guess the reason the above line is wrong is that Buf is
%% set to []. But do we expect anything but plain output?
- get_password1({Chars, []}, Drv, Shell);
- {reply,{{From,ReplyAs},Reply}} ->
+ get_password1({Chars, []}, Drv, Shell);
+ {reply,{From,ReplyAs},Reply} ->
%% We take care of replies from puts here as well
io_reply(From, ReplyAs, Reply),
- get_password1({Chars, []},Drv, Shell);
+ get_password1({Chars, []}, Drv, Shell);
{'EXIT',Drv,interrupt} ->
interrupted;
{'EXIT',Drv,_} ->
diff --git a/lib/kernel/src/group_history.erl b/lib/kernel/src/group_history.erl
index aae4748139..7738a2155e 100644
--- a/lib/kernel/src/group_history.erl
+++ b/lib/kernel/src/group_history.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -310,10 +310,7 @@ disk_log_info(Tag) ->
Value.
find_wrap_values() ->
- ConfSize = case application:get_env(kernel, shell_history_file_bytes) of
- undefined -> ?DEFAULT_SIZE;
- {ok, S} -> S
- end,
+ ConfSize = application:get_env(kernel, shell_history_file_bytes, ?DEFAULT_SIZE),
SizePerFile = max(?MIN_HISTORY_SIZE, ConfSize div ?MAX_HISTORY_FILES),
FileCount = if SizePerFile > ?MIN_HISTORY_SIZE ->
?MAX_HISTORY_FILES
diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl
index d836f6b367..780abec149 100644
--- a/lib/kernel/src/inet.erl
+++ b/lib/kernel/src/inet.erl
@@ -35,8 +35,10 @@
ip/1, is_ipv4_address/1, is_ipv6_address/1, is_ip_address/1,
stats/0, options/0,
pushf/3, popf/1, close/1, gethostname/0, gethostname/1,
- parse_ipv4_address/1, parse_ipv6_address/1, parse_ipv4strict_address/1,
- parse_ipv6strict_address/1, parse_address/1, parse_strict_address/1,
+ parse_ipv4_address/1, parse_ipv6_address/1,
+ parse_ipv4strict_address/1, parse_ipv6strict_address/1,
+ parse_address/1, parse_strict_address/1,
+ parse_address/2, parse_strict_address/2,
ntoa/1, ipv4_mapped_ipv6_address/1]).
-export([connect_options/2, listen_options/2, udp_options/2, sctp_options/2]).
@@ -912,6 +914,19 @@ parse_ipv6strict_address(Addr) ->
parse_address(Addr) ->
inet_parse:address(Addr).
+-spec parse_address(Address, inet) ->
+ {ok, IPAddress} | {error, einval} when
+ Address :: string(),
+ IPAddress :: ip_address();
+ (Address, inet6) ->
+ {ok, IPv6Address} | {error, einval} when
+ Address :: string(),
+ IPv6Address :: ip6_address().
+parse_address(Addr, inet) ->
+ inet_parse:ipv4_address(Addr);
+parse_address(Addr, inet6) ->
+ inet_parse:ipv6_address(Addr).
+
-spec parse_strict_address(Address) ->
{ok, IPAddress} | {error, einval} when
Address :: string(),
@@ -919,6 +934,19 @@ parse_address(Addr) ->
parse_strict_address(Addr) ->
inet_parse:strict_address(Addr).
+-spec parse_strict_address(Address, inet) ->
+ {ok, IPAddress} | {error, einval} when
+ Address :: string(),
+ IPAddress :: ip_address();
+ (Address, inet6) ->
+ {ok, IPv6Address} | {error, einval} when
+ Address :: string(),
+ IPv6Address :: ip6_address().
+parse_strict_address(Addr, inet) ->
+ inet_parse:ipv4strict_address(Addr);
+parse_strict_address(Addr, inet6) ->
+ inet_parse:ipv6strict_address(Addr).
+
-spec ipv4_mapped_ipv6_address(ip_address()) -> ip_address().
ipv4_mapped_ipv6_address({D1,D2,D3,D4})
when (D1 bor D2 bor D3 bor D4) < 256 ->
@@ -953,9 +981,9 @@ stats() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
connect_options() ->
[debug,
- tos, tclass, priority, reuseaddr, keepalive, linger, nodelay,
- sndbuf, recbuf,
- recvtos, recvtclass, ttl, recvttl,
+ tos, tclass, priority, reuseaddr, reuseport, reuseport_lb,
+ exclusiveaddruse, keepalive,
+ linger, nodelay, sndbuf, recbuf, recvtos, recvtclass, ttl, recvttl,
header, active, packet, packet_size, buffer, mode, deliver, line_delimiter,
exit_on_close, high_watermark, low_watermark, high_msgq_watermark,
low_msgq_watermark, send_timeout, send_timeout_close, delay_send, raw,
@@ -1044,8 +1072,8 @@ con_add(Name, Val, #connect_opts{} = R, Opts, AllOpts) ->
listen_options() ->
[debug,
tos, tclass,
- priority, reuseaddr, keepalive, linger, sndbuf, recbuf, nodelay,
- recvtos, recvtclass, ttl, recvttl,
+ priority, reuseaddr, reuseport, reuseport_lb, exclusiveaddruse, keepalive,
+ linger, sndbuf, recbuf, nodelay, recvtos, recvtclass, ttl, recvttl,
header, active, packet, buffer, mode, deliver, backlog, ipv6_v6only,
exit_on_close, high_watermark, low_watermark, high_msgq_watermark,
low_msgq_watermark, send_timeout, send_timeout_close, delay_send,
diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl
index 408f563909..b7d5afcc64 100644
--- a/lib/kernel/src/inet_db.erl
+++ b/lib/kernel/src/inet_db.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@
-export([set_cache_size/1, set_cache_refresh/1]).
-export([set_timeout/1, set_retry/1, set_servfail_retry_timeout/1,
set_inet6/1, set_usevc/1]).
--export([set_edns/1, set_udp_payload_size/1]).
+-export([set_edns/1, set_udp_payload_size/1, set_dnssec_ok/1]).
-export([set_resolv_conf/1, set_hosts_file/1, get_hosts_file/0]).
-export([tcp_module/0, set_tcp_module/1]).
-export([udp_module/0, set_udp_module/1]).
@@ -227,6 +227,8 @@ set_edns(Version) -> res_option(edns, Version).
set_udp_payload_size(Size) -> res_option(udp_payload_size, Size).
+set_dnssec_ok(DnssecOk) -> res_option(dnssec_ok, DnssecOk).
+
set_resolv_conf(Fname) when is_list(Fname) ->
res_option(resolv_conf, Fname).
@@ -312,7 +314,7 @@ valid_lookup() -> [dns, file, yp, nis, nisplus, native].
get_rc() ->
get_rc([hosts, domain, nameservers, search, alt_nameservers,
timeout, retry, servfail_retry_timeout, inet6, usevc,
- edns, udp_payload_size, resolv_conf, hosts_file,
+ edns, udp_payload_size, dnssec_ok, resolv_conf, hosts_file,
socks5_server, socks5_port, socks5_methods, socks5_noproxy,
udp, sctp, tcp, host, cache_size, cache_refresh, lookup], []).
@@ -361,6 +363,10 @@ get_rc([K | Ks], Ls) ->
res_udp_payload_size,
?DNS_UDP_PAYLOAD_SIZE,
Ks, Ls);
+ dnssec_ok -> get_rc(dnssec_ok,
+ res_res_dnssec_ok,
+ false,
+ Ks, Ls);
resolv_conf -> get_rc(resolv_conf,
res_resolv_conf,
undefined,
@@ -483,6 +489,7 @@ res_optname(inet6) -> res_inet6;
res_optname(usevc) -> res_usevc;
res_optname(edns) -> res_edns;
res_optname(udp_payload_size) -> res_udp_payload_size;
+res_optname(dnssec_ok) -> res_dnssec_ok;
res_optname(resolv_conf) -> res_resolv_conf;
res_optname(resolv_conf_name) -> res_resolv_conf;
res_optname(hosts_file) -> res_hosts_file;
@@ -498,7 +505,7 @@ res_check_option(nameservers, NSs) ->
res_check_option(alt_nameservers, NSs) ->
res_check_list(NSs, fun res_check_ns/1);
res_check_option(domain, Dom) ->
- Dom =:= "" orelse inet_parse:visible_string(Dom);
+ inet_parse:visible_string(Dom);
res_check_option(lookup, Methods) ->
try lists_subtract(Methods, valid_lookup()) of
[] -> true;
@@ -517,6 +524,7 @@ res_check_option(inet6, Bool) when is_boolean(Bool) -> true;
res_check_option(usevc, Bool) when is_boolean(Bool) -> true;
res_check_option(edns, V) when V =:= false; V =:= 0 -> true;
res_check_option(udp_payload_size, S) when is_integer(S), S >= 512 -> true;
+res_check_option(dnssec_ok, D) when is_boolean(D) -> true;
res_check_option(resolv_conf, "") -> true;
res_check_option(resolv_conf, F) ->
res_check_option_absfile(F);
@@ -550,7 +558,6 @@ res_check_ns({{A,B,C,D}, Port})
when ?ip(A,B,C,D), Port band 65535 =:= Port -> true;
res_check_ns(_) -> false.
-res_check_search("") -> true;
res_check_search(Dom) -> inet_parse:visible_string(Dom).
socks_option(server) -> db_get(socks5_server);
@@ -579,8 +586,11 @@ res_update(Option, TagTm) ->
end.
db_get(Name) ->
- try ets:lookup_element(inet_db, Name, 2)
- catch error:badarg -> undefined
+ try
+ ets:lookup_element(inet_db, Name, 2, undefined)
+ catch
+ %% Case where the table does not exist yet.
+ error:badarg -> undefined
end.
add_rr(RR) ->
@@ -878,6 +888,7 @@ take_socket_type(MRef) ->
%% res_usevc Bool - use Virtual Circuit (TCP)
%% res_edns false|Integer - false or EDNS version
%% res_udp_payload_size Integer - size for EDNS, both query and reply
+%% res_dnssec_ok Bool - the DO bit in RFC6891 & RFC3225
%% res_resolv_conf Filename - file to watch for resolver config i.e
%% {res_ns, res_search}
%% res_hosts_file Filename - file to watch for hosts config
@@ -955,6 +966,7 @@ reset_db(Db) ->
{res_inet6, false},
{res_edns, false},
{res_udp_payload_size, ?DNS_UDP_PAYLOAD_SIZE},
+ {res_dnssec_ok, false},
{cache_size, ?CACHE_LIMIT},
{cache_refresh_interval,?CACHE_REFRESH},
{socks5_server, ""},
@@ -1044,7 +1056,7 @@ handle_call(Request, From, #state{db=Db}=State) ->
end;
{set_hostname, Name} ->
- case inet_parse:visible_string(Name) of
+ case inet_parse:visible_string(Name) andalso Name =/= "" of
true ->
ets:insert(Db, {hostname, Name}),
{reply, ok, State};
@@ -1638,6 +1650,7 @@ is_res_set(inet6) -> true;
is_res_set(usevc) -> true;
is_res_set(edns) -> true;
is_res_set(udp_payload_size) -> true;
+is_res_set(dnssec_ok) -> true;
is_res_set(resolv_conf) -> true;
is_res_set(hosts_file) -> true;
is_res_set(_) -> false.
diff --git a/lib/kernel/src/inet_dns.erl b/lib/kernel/src/inet_dns.erl
index c236dfd0d6..2aeb6a9847 100644
--- a/lib/kernel/src/inet_dns.erl
+++ b/lib/kernel/src/inet_dns.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
%%
%% RFC 1035: Domain Names - Implementation and Specification
%% RFC 2181: Clarifications to the DNS Specification
-%% RFC 2671: Extension Mechanisms for DNS (EDNS0)
+%% RFC 6891: Extension Mechanisms for DNS (EDNS0)
%% RFC 2782: A DNS RR for specifying the location of services (DNS SRV)
%% RFC 2915: The Naming Authority Pointer (NAPTR) DNS Resource Rec
%% RFC 6488: DNS Certification Authority Authorization (CAA) Resource Record
@@ -49,6 +49,7 @@
make_dns_query/2, make_dns_query/3]).
-include("inet_dns_record_adts.hrl").
+
%% Function merge of #dns_rr{} and #dns_rr_opt{}
%%
@@ -112,6 +113,10 @@ lists_member(H, [H|_]) -> true;
lists_member(H, [_|T]) -> lists_member(H, T).
+-define(in_range(Low, X, High), ((Low =< (X)) andalso ((X) =< High))).
+-define(is_decimal(X), (?in_range(0, (X), 9))).
+
+
%% must match a clause in inet_res:query_nss_e?dns
-define(DECODE_ERROR, formerr).
@@ -230,7 +235,8 @@ decode_rr_section(Bin, N, Buffer, RRs) ->
RR =
case Type of
?S_OPT ->
- <<ExtRcode,Version,Z:16>> = TTL,
+ <<ExtRcode,Version,DO:1,Z:15>> = TTL,
+ DnssecOk = (DO =/= 0),
#dns_rr_opt{
domain = Name,
type = Type,
@@ -238,7 +244,8 @@ decode_rr_section(Bin, N, Buffer, RRs) ->
ext_rcode = ExtRcode,
version = Version,
z = Z,
- data = D};
+ data = D,
+ do = DnssecOk};
_ ->
{Class,CacheFlush} = decode_class(C),
Data = decode_data(D, Class, Type, Buffer),
@@ -297,8 +304,8 @@ encode_query_section(Bin0, Comp0, [#dns_query{domain=DName}=Q | Qs]) ->
{Bin,Comp} = encode_name(Bin0, Comp0, byte_size(Bin0), DName),
encode_query_section(<<Bin/binary,T:16,C:16>>, Comp, Qs).
-%% RFC 1035: 4.1.3. Resource record format
-%% RFC 2671: 4.3, 4.4, 4.6 OPT RR format
+%% RFC 1035: 4.1.3. Resource record format
+%% RFC 6891: 6.1.2, 6.1.3, 6.2.3 Opt RR format
%%
encode_res_section(Bin, Comp, []) -> {Bin,Comp};
encode_res_section(
@@ -321,10 +328,12 @@ encode_res_section(
ext_rcode = ExtRCode,
version = Version,
z = Z,
- data = Data} | Rs]) ->
+ data = Data,
+ do = DnssecOk} | Rs]) ->
+ DO = case DnssecOk of true -> 1; false -> 0 end,
encode_res_section_rr(
Bin, Comp, Rs, DName, ?S_OPT, UdpPayloadSize, false,
- <<ExtRCode,Version,Z:16>>, Data).
+ <<ExtRCode,Version,DO:1,Z:15>>, Data).
encode_res_section_rr(
Bin0, Comp0, Rs, DName, Type, Class, CacheFlush, TTL, Data) ->
@@ -360,6 +369,7 @@ decode_type(Type) ->
?T_MX -> ?S_MX;
?T_TXT -> ?S_TXT;
?T_AAAA -> ?S_AAAA;
+ ?T_LOC -> ?S_LOC;
?T_SRV -> ?S_SRV;
?T_NAPTR -> ?S_NAPTR;
?T_OPT -> ?S_OPT;
@@ -401,6 +411,7 @@ encode_type(Type) ->
?S_MX -> ?T_MX;
?S_TXT -> ?T_TXT;
?S_AAAA -> ?T_AAAA;
+ ?S_LOC -> ?T_LOC;
?S_SRV -> ?T_SRV;
?S_NAPTR -> ?T_NAPTR;
?S_OPT -> ?T_OPT;
@@ -539,6 +550,21 @@ decode_data(Data, ?S_MX, Buffer) ->
Data,
<<Prio:16,Dom/binary>>,
{Prio,decode_domain(Dom, Buffer)});
+decode_data(Data, ?S_LOC, _) ->
+ ?MATCH_ELSE_DECODE_ERROR(
+ Data,
+ <<Version:8, SizeBase:4, SizeExp:4,
+ HorizPreBase:4, HorizPreExp:4, VertPreBase:4, VertPreExp:4,
+ Latitude:32, Longitude:32, Altitude:32>>,
+ ((Version =:= 0) andalso
+ ?is_decimal(SizeBase) andalso ?is_decimal(SizeExp) andalso
+ ?is_decimal(HorizPreBase) andalso ?is_decimal(HorizPreExp) andalso
+ ?is_decimal(VertPreBase) andalso ?is_decimal(VertPreExp)),
+ {{decode_loc_angle(Latitude), decode_loc_angle(Longitude)},
+ decode_loc_altitude(Altitude),
+ decode_loc_size(SizeBase, SizeExp),
+ {decode_loc_size(HorizPreBase, HorizPreExp),
+ decode_loc_size(VertPreBase, VertPreExp)}});
decode_data(Data, ?S_SRV, Buffer) ->
?MATCH_ELSE_DECODE_ERROR(
Data,
@@ -735,6 +761,25 @@ encode_data(Comp, Pos, ?S_MINFO, Data) ->
encode_data(Comp, Pos, ?S_MX, Data) ->
{Pref,Exch} = Data,
encode_name(<<Pref:16>>, Comp, Pos+2, Exch);
+encode_data(Comp, _, ?S_LOC, Data) ->
+ %% Similar to the Master File Format in section 3 of RFC 1876
+ case Data of
+ {{Latitude, Longitude}, Altitude, Size, {HorizPre, VertPre}} ->
+ ok;
+ {{Latitude, Longitude}, Altitude, Size} ->
+ HorizPre = 10_000_00, VertPre = 10_00,
+ ok;
+ {{Latitude, Longitude}, Altitude} ->
+ Size = 1_00, HorizPre = 10_000_00, VertPre = 10_00,
+ ok
+ end,
+ Version = 0,
+ {<<Version:8, (encode_loc_size(Size))/binary,
+ (encode_loc_size(HorizPre))/binary, (encode_loc_size(VertPre))/binary,
+ (encode_loc_angle(Latitude)):32,
+ (encode_loc_angle(Longitude)):32,
+ (encode_loc_altitude(Altitude)):32>>,
+ Comp};
encode_data(Comp, Pos, ?S_SRV, Data) ->
{Prio,Weight,Port,Target} = Data,
encode_name(<<Prio:16,Weight:16,Port:16>>, Comp, Pos+2+2+2, Target);
@@ -854,3 +899,51 @@ encode_labels(Bin, Comp0, Pos, [L|Ls]=Labels)
%% Name compression - point to already encoded name
{<<Bin/binary,3:2,Ptr:14>>,Comp0}
end.
+
+
+decode_loc_angle(X) ->
+ (X - 16#8000_0000) / 3600_000.
+
+encode_loc_angle(X) when is_float(X) ->
+ %% Degrees (1/360 of a turn)
+ encode_loc_angle(round(X * 3600_000));
+encode_loc_angle(X)
+ when is_integer(X), -16#8000_0000 =< X, X =< 16#7FFF_FFFF ->
+ %% 1/1000:s of arc second
+ X + 16#8000_0000. % Zero is encoded as 2^31
+
+
+decode_loc_altitude(X) ->
+ (X - 100_000_00) / 100.
+
+encode_loc_altitude(X) when is_float(X) ->
+ %% Meters
+ encode_loc_altitude(round(X * 100));
+encode_loc_altitude(X)
+ when is_integer(X), -100_000_00 =< X, X =< 16#FFFF_FFFF - 100_000_00 ->
+ %% Centimeters above a base level 100_000 m below
+ %% the GPS reference spheroid [DoD WGS-1984]
+ X + 100_000_00.
+
+
+decode_loc_size(Base, Exponent) ->
+ round(Base * math:pow(10, Exponent)) / 100.
+
+%% Return the smallest encoded value >= X;
+%% a bit like ceil(X) of encoded values
+%%
+encode_loc_size(X) when is_float(X) ->
+ %% Meters
+ encode_loc_size(round(X * 100));
+encode_loc_size(0) ->
+ 0;
+encode_loc_size(X)
+ when is_integer(X), 0 =< X, X =< 9000_000_000 ->
+ %% Centimeters, to be encoded as Digit * 10^Exponent
+ %% with both Digit and Exponent in 0..9,
+ %% limiting the range to 0..9e9
+ %%
+ Exponent = floor(math:log10((X - 0.05) / 0.9)),
+ Multiplier = round(math:pow(10, Exponent)),
+ Base = (X + Multiplier - 1) div Multiplier,
+ <<Base:4, Exponent:4>>.
diff --git a/lib/kernel/src/inet_dns.hrl b/lib/kernel/src/inet_dns.hrl
index 5288c570b2..cf8f984e05 100644
--- a/lib/kernel/src/inet_dns.hrl
+++ b/lib/kernel/src/inet_dns.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -72,11 +72,13 @@
-define(T_MX, 15). %% mail routing information
-define(T_TXT, 16). %% text strings
-define(T_AAAA, 28). %% ipv6 address
+%% LOC (RFC 1876)
+-define(T_LOC, 29). %% location information
%% SRV (RFC 2052)
-define(T_SRV, 33). %% services
%% NAPTR (RFC 2915)
-define(T_NAPTR, 35). %% naming authority pointer
--define(T_OPT, 41). %% EDNS pseudo-rr RFC2671(7)
+-define(T_OPT, 41). %% EDNS pseudo-rr RFC6891(7)
%% SPF (RFC 4408)
-define(T_SPF, 99). %% server policy framework
%% non standard
@@ -114,11 +116,13 @@
-define(S_MX, mx). %% mail routing information
-define(S_TXT, txt). %% text strings
-define(S_AAAA, aaaa). %% ipv6 address
+%% LOC (RFC 1876)
+-define(S_LOC, loc). %% location information
%% SRV (RFC 2052)
-define(S_SRV, srv). %% services
%% NAPTR (RFC 2915)
-define(S_NAPTR, naptr). %% naming authority pointer
--define(S_OPT, opt). %% EDNS pseudo-rr RFC2671(7)
+-define(S_OPT, opt). %% EDNS pseudo-rr RFC6891(7)
%% SPF (RFC 4408)
-define(S_SPF, spf). %% server policy framework
%% non standard
@@ -201,15 +205,16 @@
-define(DNS_UDP_PAYLOAD_SIZE, 1280).
--record(dns_rr_opt, %% EDNS RR OPT (RFC2671), dns_rr{type=opt}
+-record(dns_rr_opt, %% EDNS RR OPT (RFC6891), dns_rr{type=opt}
{
domain = "", %% should be the root domain
type = opt,
- udp_payload_size = ?DNS_UDP_PAYLOAD_SIZE, %% RFC2671(4.5 CLASS)
- ext_rcode = 0, %% RFC2671(4.6 EXTENDED-RCODE)
- version = 0, %% RFC2671(4.6 VERSION)
- z = 0, %% RFC2671(4.6 Z)
- data = [] %% RFC2671(4.4)
+ udp_payload_size = ?DNS_UDP_PAYLOAD_SIZE, %% RFC6891(6.2.3 CLASS)
+ ext_rcode = 0, %% RFC6891(6.1.3 EXTENDED-RCODE)
+ version = 0, %% RFC6891(6.1.3 VERSION)
+ z = 0, %% RFC6891(6.1.3 Z)
+ data = [], %% RFC6891(6.1.2 RDATA)
+ do = false %% RFC6891(6.1.3 DO)
}).
-record(dns_query,
diff --git a/lib/kernel/src/inet_dns_record_adts.pl b/lib/kernel/src/inet_dns_record_adts.pl
index c89d837098..f3331222fc 100644
--- a/lib/kernel/src/inet_dns_record_adts.pl
+++ b/lib/kernel/src/inet_dns_record_adts.pl
@@ -2,7 +2,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2009-2016. All Rights Reserved.
+# Copyright Ericsson AB 2009-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ my %Names = ('msg' => ['dns_rec', 'header', 'qdlist',
'dns_rr' => ['dns_rr', 'domain', 'type', 'class', 'ttl', 'data'],
'dns_rr_opt' => ['dns_rr_opt', 'domain', 'type',
'udp_payload_size', 'ext_rcode', 'version',
- 'z', 'data'],
+ 'z', 'data', 'do'],
'dns_query' => ['dns_query', 'domain', 'type', 'class'],
'header' => ['dns_header', 'id', 'qr', 'opcode', 'aa', 'tc',
'rd', 'ra', 'pr', 'rcode']);
diff --git a/lib/kernel/src/inet_epmd_dist.erl b/lib/kernel/src/inet_epmd_dist.erl
new file mode 100644
index 0000000000..33ad46b858
--- /dev/null
+++ b/lib/kernel/src/inet_epmd_dist.erl
@@ -0,0 +1,822 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(inet_epmd_dist).
+-feature(maybe_expr, enable).
+
+%% DistMod API - own inet_tcp_dist equivalence implementation
+-export([net_address/0, listen_open/2, listen_port/3, listen_close/1,
+ accept_open/2, accept_controller/3, accepted/3,
+ connect/3]).
+%% DistMod helper API
+-export([check_ip/2,
+ hs_data/2, f_address/2, tick/1, getstat/1, setopts/2, getopts/2,
+ nodelay/0, merge_options/3]).
+
+%% net_kernel and dist_util distribution Module API
+-export([address/1, listen/2,
+ accept/1, accept_connection/5,
+ select/1, setup/5,
+ close/1]).
+
+-include("net_address.hrl").
+-include("dist.hrl").
+-include("dist_util.hrl").
+
+-define(DISTNAME, inet_epmd).
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% DistMod API
+%%
+%% Called by net_kernel:
+%%
+%% net_address() ->
+%% #net_address{ protocol = Protocol, family = Family }.
+%%
+%% listen_open(NetAddress, ListenOptions) ->
+%% {ok, StateO} |
+%% {error, _}.
+%%
+%% listen_port(NetAddress, Port, StateO) ->
+%% {ok, {StateL, {Ip, Port}}} |
+%% {error, _}.
+%%
+%% listen_close(StateL) ->
+%% ok.
+%%
+%%
+%% Called by Acceptor:
+%%
+%% %% Wait for incoming connection,
+%% %% return new socket and endpoint addresses
+%% accept_open(NetAddress, StateL) ->
+%% {StateA, PeerAddress}.
+%%
+%% %% Notification about the process id of the Controller
+%% %% - transfer socket ownership to it
+%% accept_controller(NetAddress, Controller, StateA) ->
+%% StateC.
+%%
+%%
+%% Called by Accept Controller:
+%%
+%% accepted(NetAddress, Timer, StateC) ->
+%% #hs_data{} | {error, Reason}
+%%
+%%
+%% Called by Connect Controller:
+%% connect(NetAddress, Timer, ConnectOptions) ->
+%% #hs_data{} | {error, Reason}
+%%
+%% ------------------------------------------------------------
+%% Own DistMod API implementation -
+%% when used by this module the combination
+%% is equivalent to inet_tcp_dist
+
+-define(DRIVER, inet_tcp).
+-define(PROTOCOL, tcp).
+
+%% ------------------------------------------------------------
+net_address() ->
+ Family = ?DRIVER:family(),
+ #net_address{
+ protocol = ?PROTOCOL,
+ family = Family }.
+
+%% ------------------------------------------------------------
+listen_open(_NetAddress, Options) ->
+ {ok, merge_options(Options, [{active, false}, {packet, 2}], [])}.
+
+%% ------------------------------------------------------------
+listen_port(_NetAddress, Port, ListenOptions) ->
+ maybe
+ {ok, ListenSocket} ?=
+ ?DRIVER:listen(Port, ListenOptions),
+ {ok, Address} ?=
+ inet:sockname(ListenSocket),
+ {ok, {ListenSocket, Address}}
+ end.
+
+%% ------------------------------------------------------------
+listen_close(ListenSocket) ->
+ ?DRIVER:close(ListenSocket).
+
+%% ------------------------------------------------------------
+accept_open(_NetAddress, ListenSocket) ->
+ maybe
+ {ok, Socket} ?=
+ ?DRIVER:accept(ListenSocket),
+ {ok, {Ip, _}} ?=
+ inet:sockname(Socket),
+ {ok, {PeerIp, _} = PeerAddress} ?=
+ inet:peername(Socket),
+ check_ip(Ip, PeerIp),
+ {Socket, PeerAddress}
+ else
+ {error, Reason} ->
+ exit({accept, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accept_controller(_NetAddress, Controller, Socket) ->
+ ?DRIVER:controlling_process(Socket, Controller),
+%%% flush_to(Socket, Controller),
+ Socket.
+
+-ifdef(undefined).
+flush_to(Socket, Pid) ->
+ receive
+ {tcp, Socket, Data} ->
+ Pid ! {tcp, Socket, Data},
+ flush_to(Socket, Pid);
+ {tcp_closed, Socket} ->
+ Pid ! {tcp_closed, Socket},
+ flush_to(Socket, Pid)
+ after 0 ->
+ ok
+ end.
+-endif.
+
+%% ------------------------------------------------------------
+accepted(NetAddress, _Timer, Socket) ->
+ hs_data(NetAddress, Socket).
+
+%% ------------------------------------------------------------
+connect(NetAddress, _Timer, Options) ->
+ ConnectOptions =
+ merge_options(Options, [{active, false}, {packet, 2}], []),
+ #net_address{ address = {Ip, Port} } = NetAddress,
+ maybe
+ {ok, Socket} ?=
+ ?DRIVER:connect(Ip, Port, ConnectOptions),
+ hs_data(NetAddress, Socket)
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% -------
+hs_data(NetAddress, Socket) ->
+ Nodelay = nodelay(),
+ #hs_data{
+ socket = Socket,
+ f_send = fun ?DRIVER:send/2,
+ f_recv = fun ?DRIVER:recv/3,
+ f_setopts_pre_nodeup =
+ fun (S) when S =:= Socket ->
+ f_setopts_pre_nodeup(S, Nodelay)
+ end,
+ f_setopts_post_nodeup =
+ fun (S) when S =:= Socket ->
+ f_setopts_post_nodeup(S, Nodelay)
+ end,
+ f_address =
+ fun (S, Node) when S =:= Socket ->
+ f_address(NetAddress, Node)
+ end,
+ f_getll = fun inet:getll/1,
+ mf_tick = fun ?MODULE:tick/1,
+ mf_getstat = fun ?MODULE:getstat/1,
+ mf_setopts = fun ?MODULE:setopts/2,
+ mf_getopts = fun ?MODULE:getopts/2 }.
+
+f_setopts_pre_nodeup(Socket, Nodelay) ->
+ inet:setopts(Socket, [{active, false}, {packet, 4}, Nodelay]).
+
+f_setopts_post_nodeup(Socket, Nodelay) ->
+ inet:setopts(
+ Socket,
+ [{active, true}, {packet,4}, {deliver, port}, binary, Nodelay]).
+
+f_address(NetAddress, Node) ->
+ case dist_util:split_node(Node) of
+ {node, _Name, Host} ->
+ NetAddress#net_address{
+ host = Host };
+ Other ->
+ ?shutdown2(Node, {split_node, Other})
+ end.
+
+tick(Socket) when is_port(Socket) ->
+ Result = ?DRIVER:send(Socket, [], [force]),
+ _ = (Result =:= {error, closed}) andalso
+ (self() ! {tcp_closed, Socket}),
+ Result.
+
+getstat(Socket) ->
+ case inet:getstat(Socket, [recv_cnt, send_cnt, send_pend]) of
+ {ok, Stat} ->
+ split_stat(Stat,0,0,0);
+ Error ->
+ Error
+ end.
+
+split_stat([{recv_cnt, R}|Stat], _, W, P) ->
+ split_stat(Stat, R, W, P);
+split_stat([{send_cnt, W}|Stat], R, _, P) ->
+ split_stat(Stat, R, W, P);
+split_stat([{send_pend, P}|Stat], R, W, _) ->
+ split_stat(Stat, R, W, P);
+split_stat([], R, W, P) ->
+ {ok, R, W, P}.
+
+setopts(S, Opts) ->
+ case [Opt || {K,_}=Opt <- Opts,
+ K =:= active orelse K =:= deliver orelse K =:= packet] of
+ [] -> inet:setopts(S,Opts);
+ Opts1 -> {error, {badopts,Opts1}}
+ end.
+
+getopts(S, OptNames) ->
+ inet:getopts(S, OptNames).
+
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% net_kernel distribution module API
+
+
+%% ------------------------------------------------------------
+%% Return which #net_address{} we handle
+%% ------------------------------------------------------------
+
+address(Host) ->
+ try
+ pt_init(Host),
+ pt_get(net_address)
+ catch error : Reason : Stacktrace ->
+ error_logger:error_msg(
+ "error : ~p in ~n ~p~n", [Reason, Stacktrace]),
+ erlang:raise(error, Reason, Stacktrace)
+ end.
+
+%% ------------------------------------------------------------
+%% Create the listen socket, i.e. the port that this erlang
+%% node is accessible through.
+%% ------------------------------------------------------------
+
+listen(Name, Host) ->
+ try
+ maybe
+ pt_init(Host),
+ NetAddress = pt_get(net_address),
+ DistMod = pt_get(dist_mod),
+ EpmdMod = net_kernel:epmd_module(),
+ %%
+ {ok, ListenOptions} ?=
+ %% *******
+ DistMod:listen_open(NetAddress, listen_options()),
+ {First, Last} =
+ case
+ call_epmd_function(
+ EpmdMod, listen_port_please, [Name, Host])
+ of
+ {ok, 0} ->
+ get_port_range();
+ {ok, PortNum} ->
+ {PortNum, PortNum}
+ end,
+ #net_address{ family = Family } = NetAddress = pt_get(net_address),
+ %%
+ {ok, Result} ?=
+ listen_loop(First, NetAddress, ListenOptions, Last, DistMod),
+ {StateL, {_Ip, Port} = Address} = Result,
+ %%
+ {ok, Creation} ?=
+ EpmdMod:register_node(Name, Port, Family),
+ NetAddress_1 = NetAddress#net_address{ address = Address },
+ {ok, {{NetAddress_1, StateL}, NetAddress_1, Creation}}
+ else
+ {error, _} = Error ->
+ Error
+ end
+ catch error : Reason : Stacktrace ->
+ error_logger:error_msg(
+ "error : ~p in ~n ~p~n", [Reason, Stacktrace]),
+ erlang:raise(error, Reason, Stacktrace)
+ end.
+
+get_port_range() ->
+ case application:get_env(kernel, inet_dist_listen_min) of
+ {ok,N} when is_integer(N) ->
+ case application:get_env(kernel, inet_dist_listen_max) of
+ {ok,M} when is_integer(M) ->
+ {N,M};
+ _ ->
+ {N,N}
+ end;
+ _ ->
+ {0,0}
+ end.
+
+listen_loop(Port, NetAddress, ListenOptions, LastPort, DistMod)
+ when Port =< LastPort ->
+ case
+ %% *******
+ DistMod:listen_port(NetAddress, Port, ListenOptions)
+ of
+ {error, eaddrinuse} ->
+ listen_loop(
+ Port + 1, NetAddress, ListenOptions, LastPort, DistMod);
+ Result ->
+ Result
+ end;
+listen_loop(_, _, _, _, _) ->
+ {error, eaddrinuse}.
+
+%% ------------------------------------------------------------
+%% Close the listen socket
+%% ------------------------------------------------------------
+close(ListenSocket) ->
+ (pt_get(dist_mod)):listen_close(ListenSocket).
+
+%% ------------------------------------------------------------
+%% Accepts new connection attempts from other Erlang nodes.
+%% Loops accept on the listen socket and notifies net_kernel.
+%% ------------------------------------------------------------
+
+accept({NetAddress, StateL}) ->
+ try
+ NetKernel = self(),
+ DistMod = pt_get(dist_mod),
+ AcceptLoop =
+ spawn_link(
+ fun () ->
+ _ = process_flag(trap_exit, true),
+ accept_loop(
+ StateL, NetAddress, NetKernel, DistMod,
+ erlang:system_info(schedulers_online), #{})
+ end),
+ AcceptLoop
+ catch error : Reason : Stacktrace ->
+ error_logger:error_msg(
+ "error : ~p in ~n ~p~n", [Reason, Stacktrace]),
+ erlang:raise(error, Reason, Stacktrace)
+ end.
+
+%% Open parallel acceptors on the listen socket
+%%
+accept_loop(
+ StateL, NetAddress, NetKernel, DistMod, MaxPending, Acceptors)
+ when map_size(Acceptors) =< MaxPending ->
+ AcceptRef = make_ref(),
+ Acceptor =
+ spawn_link(
+ acceptor_fun(StateL, NetAddress, NetKernel, DistMod, AcceptRef)),
+ accept_loop(
+ StateL, NetAddress, NetKernel, DistMod, MaxPending,
+ Acceptors#{ Acceptor => AcceptRef });
+accept_loop(
+ StateL, NetAddress, NetKernel, DistMod, MaxPending, Acceptors) ->
+ receive Msg ->
+ case Msg of
+ {'EXIT', Acceptor, Reason}
+ when is_map_key(Acceptor, Acceptors) ->
+ AcceptRef = maps:get(Acceptor, Acceptors),
+ case Reason of
+ AcceptRef -> % Done ok
+ accept_loop(
+ StateL, NetAddress, NetKernel, DistMod,
+ MaxPending, maps:remove(Acceptor, Acceptors));
+ {accept, _} ->
+ %% Should mean that accept failed
+ %% so we need to restart the listener
+ exit(Reason);
+ _ ->
+ error_logger:warning_msg(
+ "~w:~w acceptor ~w failed: ~p",
+ [?MODULE, ?FUNCTION_NAME, Acceptor, Reason]),
+ accept_loop(
+ StateL, NetAddress, NetKernel, DistMod,
+ MaxPending, maps:remove(Acceptor, Acceptors))
+ end;
+ {'EXIT', NetKernel, Reason} ->
+ exit(Reason);
+ _ ->
+ error_logger:warning_msg(
+ "~w:~w unknown message: ~p",
+ [?MODULE, ?FUNCTION_NAME, Msg]),
+ accept_loop(
+ StateL, NetAddress, NetKernel, DistMod,
+ MaxPending, Acceptors)
+ end
+ end.
+
+%% Return value with exit/1
+-spec acceptor_fun(_, _, _, _, _) -> fun(() -> no_return()).
+acceptor_fun(
+ StateL,
+ #net_address{ family = Family, protocol = Protocol } = NetAddress,
+ NetKernel, DistMod, AcceptRef) ->
+ fun () ->
+ {StateA, PeerAddress} =
+ %% *******
+ DistMod:accept_open(NetAddress, StateL),
+ %%
+ NetAddress_1 = NetAddress#net_address{ address = PeerAddress },
+ Acceptor = self(),
+ NetKernel ! {accept, Acceptor, NetAddress_1, Family, Protocol},
+ receive
+ {NetKernel, controller, Controller} ->
+ StateD =
+ %% *******
+ DistMod:accept_controller(
+ NetAddress_1, Controller, StateA),
+ Controller !
+ {Acceptor, controller, DistMod, StateD},
+ exit(AcceptRef);
+ {NetKernel, unsupported_protocol = Reason} ->
+ exit(Reason)
+ end
+ end.
+
+%% ------------------------------------------------------------
+%% Accepts a new connection attempt from another Erlang node.
+%% Performs the handshake with the other side.
+%% ------------------------------------------------------------
+
+accept_connection(Acceptor, NetAddress, MyNode, Allowed, SetupTime) ->
+ try
+ NetKernel = self(),
+ Controller =
+ spawn_opt(
+ fun () ->
+ accept_controller(
+ Acceptor, NetAddress, MyNode, Allowed, SetupTime,
+ NetKernel)
+ end, dist_util:net_ticker_spawn_options()),
+ Controller
+ catch error : Reason : Stacktrace ->
+ error_logger:error_msg(
+ "error : ~p in ~n ~p~n", [Reason, Stacktrace]),
+ erlang:raise(error, Reason, Stacktrace)
+ end.
+
+accept_controller(
+ Acceptor, NetAddress, MyNode, Allowed, SetupTime,
+ NetKernel) ->
+ receive
+ {Acceptor, controller, DistMod, StateD} ->
+ Timer = dist_util:start_timer(SetupTime),
+ case
+ %% *******
+ DistMod:accepted(NetAddress, Timer, StateD)
+ of
+ #hs_data{} = HsData ->
+ dist_util:handshake_other_started(
+ HsData
+ #hs_data{
+ kernel_pid = NetKernel,
+ this_node = MyNode,
+ timer = Timer,
+ allowed = Allowed });
+ {error, Reason} ->
+ ?shutdown({{DistMod, accepted}, Reason})
+ end
+ end.
+
+%% ------------------------------------------------------------
+%% Select this protocol based on node name
+%% select(Node) => Bool
+%% ------------------------------------------------------------
+
+select(Node) ->
+ try
+ case dist_util:split_node(Node) of
+ {node, Name, Host} ->
+ #net_address{ family = Family } = pt_get(net_address),
+ EpmdMod = net_kernel:epmd_module(),
+ case
+ call_epmd_function(
+ EpmdMod, address_please, [Name, Host, Family])
+ of
+ {ok, _Addr} -> true;
+ {ok, _Addr, _Port, _Creation} -> true;
+ _ -> false
+ end;
+ _ -> false
+ end
+ catch error : Reason : Stacktrace ->
+ error_logger:error_msg(
+ "error : ~p in ~n ~p~n", [Reason, Stacktrace]),
+ erlang:raise(error, Reason, Stacktrace)
+ end.
+
+%% ------------------------------------------------------------
+%% Setup a new connection to another Erlang node.
+%% Performs the handshake with the other side.
+%% ------------------------------------------------------------
+
+setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
+ try
+ NetKernel = self(),
+ Controller =
+ spawn_opt(
+ fun () ->
+ setup(
+ Node, Type, MyNode, LongOrShortNames, SetupTime,
+ NetKernel)
+ end, dist_util:net_ticker_spawn_options()),
+ Controller
+ catch error : Reason : Stacktrace ->
+ error_logger:error_msg(
+ "error : ~p in ~n ~p~n", [Reason, Stacktrace]),
+ erlang:raise(error, Reason, Stacktrace)
+ end.
+
+setup(Node, Type, MyNode, LongOrShortNames, SetupTime, NetKernel) ->
+ Timer = dist_util:start_timer(SetupTime),
+ DistMod = pt_get(dist_mod),
+ #net_address{ family = Family } = NetAddress = pt_get(net_address),
+ {Name, Host} = split_node(Node, LongOrShortNames, Family),
+ ErlEpmd = net_kernel:epmd_module(),
+ {Address, Version} =
+ case
+ call_epmd_function(
+ ErlEpmd, address_please, [Name, Host, Family])
+ of
+ {ok, Ip, Port, Ver} ->
+ {{Ip, Port}, Ver};
+ {ok, Ip} ->
+ case ErlEpmd:port_please(Name, Ip) of
+ {port, Port, Ver} ->
+ {{Ip, Port}, Ver};
+ Other ->
+ ?shutdown2(Node, {port_please, Other})
+ end;
+ Other ->
+ ?shutdown2(Node, {address_please, Other})
+ end,
+ NetAddress_1 =
+ NetAddress#net_address{
+ host = Host,
+ address = Address },
+ dist_util:reset_timer(Timer),
+ case
+ %% *******
+ DistMod:connect(NetAddress_1, Timer, connect_options())
+ of
+ #hs_data{} = HsData ->
+ dist_util:handshake_we_started(
+ HsData
+ #hs_data{
+ kernel_pid = NetKernel,
+ other_node = Node,
+ this_node = MyNode,
+ timer = Timer,
+ other_version = Version,
+ request_type = Type });
+ {error, Reason} ->
+ ?shutdown2(Node, {{DistMod, connect}, Reason})
+ end.
+
+%% ------------------------------------------------------------
+%% Check that accepted address is on our subnet
+%%
+%% Only accept new connection attempts from nodes at our
+%% own LAN, if the check_ip environment parameter is true.
+%% ------------------------------------------------------------
+check_ip(Ip, PeerIp) ->
+ try
+ case application:get_env(kernel, check_ip) of
+ {ok, true} ->
+ maybe
+ {ok, Ifaddrs} ?= inet:getifaddrs(),
+ {ok, Netmask} ?= find_netmask(Ip, Ifaddrs),
+ mask(Ip, Netmask) =:= mask(PeerIp, Netmask) orelse
+ begin
+ error_logger:error_msg(
+ "** Connection attempt from "
+ "disallowed IP ~w ** ~n",
+ [PeerIp]),
+ ?shutdown(no_node)
+ end,
+ ok
+ else
+ Other ->
+ exit({check_ip, Other})
+ end;
+ _ ->
+ ok
+ end
+ catch error : Reason : Stacktrace ->
+ error_logger:error_msg(
+ "error : ~p in ~n ~p~n", [Reason, Stacktrace]),
+ erlang:raise(error, Reason, Stacktrace)
+ end.
+
+find_netmask(Ip, [{_Name,Items} | Ifaddrs]) ->
+ find_netmask(Ip, Ifaddrs, Items);
+find_netmask(_, []) ->
+ {error, no_netmask}.
+%%
+find_netmask(Ip, _Ifaddrs, [{addr, Ip}, {netmask, Netmask} | _]) ->
+ {ok, Netmask};
+find_netmask(Ip, Ifaddrs, [_ | Items]) ->
+ find_netmask(Ip, Ifaddrs, Items);
+find_netmask(Ip, Ifaddrs, []) ->
+ find_netmask(Ip, Ifaddrs).
+
+mask(Addr, Mask) ->
+ mask(Addr, Mask, 1).
+%%
+mask(Addr, Mask, N) when N =< tuple_size(Addr) ->
+ [element(N, Addr) band element(N, Mask) | mask(Addr, Mask, N + 1)];
+mask(_, _, _) ->
+ [].
+
+
+%% ------------------------------------------------------------
+%% Split and validate node name
+%% ------------------------------------------------------------
+
+split_node(Node, LongOrShortNames, Family) ->
+ case dist_util:split_node(Node) of
+ {node, Name, Host} ->
+ Dots = members($., Host),
+ if
+ LongOrShortNames =:= longnames, 0 < Dots ->
+ {Name, Host};
+ LongOrShortNames =:= longnames ->
+ case inet:parse_strict_address(Host, Family) of
+ {ok, _} ->
+ %% We count an IP address as a long name
+ %% since it is not relative to the current
+ %% domain.
+ %%
+ %% This clause is for for IPv6 addresses
+ %% that are : separated.
+ {Name, Host};
+ {error, Reason} ->
+ error_logger:error_msg(
+ "** System running to use fully qualified "
+ "hostnames **~n"
+ "** Hostname ~ts is illegal **~n",
+ [Host]),
+ ?shutdown2(Node, {parse_address, Reason})
+ end;
+ LongOrShortNames =:= shortnames, 0 < Dots ->
+ error_logger:error_msg(
+ "** System NOT running to use fully qualified "
+ "hostnames **~n"
+ "** Hostname ~ts is illegal **~n",
+ [Host]),
+ ?shutdown(Node);
+ LongOrShortNames =:= shortnames ->
+ {Name, Host}
+ end;
+ Other ->
+ error_logger:error_msg("** Nodename ~p illegal **~n", [Node]),
+ ?shutdown2(Node, {split_node, Other})
+ end.
+
+%% Count list members
+members(X, [X | T]) -> members(X, T) + 1;
+members(X, [_ | T]) -> members(X, T);
+members(_, []) -> 0.
+
+%% ------------------------------------------------------------
+%% Determine if EPMD module supports the called functions.
+%% If not call the builtin erl_epmd
+%% ------------------------------------------------------------
+call_epmd_function(Mod, Fun, Args) ->
+ case erlang:function_exported(Mod, Fun, length(Args)) of
+ true -> apply(Mod,Fun,Args);
+ _ -> apply(erl_epmd, Fun, Args)
+ end.
+
+
+%% ------------------------------------------------------------
+
+listen_options() ->
+ DefaultOpts = [{reuseaddr, true}, {backlog, 128}],
+ ForcedOpts =
+ case application:get_env(kernel, inet_dist_use_interface) of
+ {ok, Ip} -> [{ip, Ip}];
+ undefined -> []
+ end,
+ InetDistListenOpts =
+ case application:get_env(kernel, inet_dist_listen_options) of
+ {ok, Opts} -> Opts;
+ undefined -> []
+ end,
+ merge_options(InetDistListenOpts, ForcedOpts, DefaultOpts).
+
+connect_options() ->
+ case application:get_env(kernel, inet_dist_connect_options) of
+ {ok, ConnectOpts} ->
+ ConnectOpts;
+ _ ->
+ []
+ end.
+
+nodelay() ->
+ case application:get_env(kernel, dist_nodelay) of
+ undefined ->
+ {nodelay, true};
+ {ok, true} ->
+ {nodelay, true};
+ {ok, false} ->
+ {nodelay, false};
+ _ ->
+ {nodelay, true}
+ end.
+
+
+merge_options(Opts, ForcedOpts, DefaultOpts) ->
+ Forced = merge_options(ForcedOpts),
+ Default = merge_options(DefaultOpts),
+ ForcedOpts ++ merge_options(Opts, Forced, DefaultOpts, Default).
+
+%% Collect expanded 2-tuple options in a map
+merge_options(Opts) ->
+ lists:foldr(
+ fun (Opt, Acc) ->
+ case expand_option(Opt) of
+ {OptName, OptVal} ->
+ maps:put(OptName, OptVal, Acc);
+ _ ->
+ Acc
+ end
+ end, #{}, Opts).
+
+%% Pass through all options that are not forced,
+%% which we already have prepended,
+%% and remove options that we see from the Default map
+%%
+merge_options([Opt | Opts], Forced, DefaultOpts, Default) ->
+ case expand_option(Opt) of
+ {OptName, _} ->
+ %% Remove from the Default map
+ Default_1 = maps:remove(OptName, Default),
+ if
+ is_map_key(OptName, Forced) ->
+ %% Forced option - do not pass through
+ merge_options(Opts, Forced, DefaultOpts, Default_1);
+ true ->
+ %% Pass through
+ [Opt |
+ merge_options(Opts, Forced, DefaultOpts, Default_1)]
+ end;
+ _ ->
+ %% Unhandled options e.g {raw, ...} - pass through
+ [Opt | merge_options(Opts, Forced, DefaultOpts, Default)]
+ end;
+merge_options([], _Forced, DefaultOpts, Default) ->
+ %% Append the needed default options (that we have not seen)
+ [Opt ||
+ Opt <- DefaultOpts,
+ is_map_key(element(1, expand_option(Opt)), Default)].
+
+%% Expand an atom option into its tuple equivalence,
+%% pass through others
+expand_option(Opt) ->
+ if
+ Opt =:= list; Opt =:= binary ->
+ {mode, Opt};
+ Opt =:= inet; Opt =:= inet6; Opt =:= local ->
+ %% 'family' is not quite an option name, but could/should be
+ {family, Opt};
+ true ->
+ Opt
+ end.
+
+%% ------------------------------------------------------------
+%% Cache distribution module parameters
+%% ------------------------------------------------------------
+
+pt_get(Key)
+ when Key =:= dist_mod;
+ Key =:= net_address ->
+ persistent_term:get({?MODULE, Key}).
+
+pt_init(Host) ->
+ maybe
+ {ok, [[DistModStr]]} ?= init:get_argument(?DISTNAME),
+ DistMod = list_to_atom(atom_to_list(?DISTNAME) ++ "_" ++ DistModStr),
+ persistent_term:put({?MODULE, dist_mod}, DistMod),
+ NetAddress =
+ %% *******
+ (DistMod:net_address())
+ #net_address{ host = Host },
+ persistent_term:put({?MODULE, net_address}, NetAddress)
+ else
+ Other ->
+ exit({{init,get_argument,[?DISTNAME]},Other})
+ end.
diff --git a/lib/kernel/src/inet_epmd_socket.erl b/lib/kernel/src/inet_epmd_socket.erl
new file mode 100644
index 0000000000..431c1076d6
--- /dev/null
+++ b/lib/kernel/src/inet_epmd_socket.erl
@@ -0,0 +1,485 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(inet_epmd_socket).
+-feature(maybe_expr, enable).
+
+%% DistMod API
+-export([net_address/0, listen_open/2, listen_port/3, listen_close/1,
+ accept_open/2, accept_controller/3, accepted/3,
+ connect/3]).
+
+-export([supported/0]).
+
+-export([start_dist_ctrl/2]).
+
+-include("net_address.hrl").
+-include("dist.hrl").
+-include("dist_util.hrl").
+
+-define(PROTOCOL, tcp).
+-define(FAMILY, inet).
+
+%% ------------------------------------------------------------
+net_address() ->
+ #net_address{
+ protocol = ?PROTOCOL,
+ family = ?FAMILY }.
+
+%% ------------------------------------------------------------
+listen_open(#net_address{ family = Family}, ListenOptions) ->
+ maybe
+ Key = backlog,
+ Default = 128,
+ Backlog = proplists:get_value(Key, ListenOptions, Default),
+ {ok, ListenSocket} ?=
+ socket:open(Family, stream),
+ ok ?=
+ setopts(
+ ListenSocket,
+ [inet_epmd_dist:nodelay() |
+ proplists:delete(
+ Key,
+ proplists:delete(nodelay, ListenOptions))]),
+ {ok, {ListenSocket, Backlog}}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+setopts(Socket, Options) ->
+ gen_tcp_socket:socket_setopts(Socket, Options).
+
+%% ------------------------------------------------------------
+listen_port(
+ #net_address{ family = Family }, Port, {ListenSocket, Backlog}) ->
+ maybe
+ Sockaddr =
+ #{family => Family,
+ addr => any,
+ port => Port},
+ ok ?=
+ socket:bind(ListenSocket, Sockaddr),
+ ok ?=
+ socket:listen(ListenSocket, Backlog),
+ {ok, #{ addr := Ip, port := ListenPort}} ?=
+ socket:sockname(ListenSocket),
+ {ok, {ListenSocket, {Ip, ListenPort}}}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+listen_close(ListenSocket) ->
+ socket:close(ListenSocket).
+
+%% ------------------------------------------------------------
+accept_open(_NetAddress, ListenSocket) ->
+ maybe
+ {ok, Socket} ?=
+ socket:accept(ListenSocket),
+ {ok, #{ addr := Ip }} ?=
+ socket:sockname(Socket),
+ {ok, #{ addr := PeerIp, port := PeerPort }} ?=
+ socket:peername(Socket),
+ inet_epmd_dist:check_ip(Ip, PeerIp),
+ {Socket, {PeerIp, PeerPort}}
+ else
+ {error, Reason} ->
+ exit({?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accept_controller(_NetAddress, Controller, Socket) ->
+ maybe
+ ok ?=
+ socket:setopt(Socket, {otp,controlling_process}, Controller),
+ Socket
+ else
+ {error, Reason} ->
+ exit({?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accepted(NetAddress, _Timer, Socket) ->
+ start_dist_ctrl(NetAddress, Socket).
+
+%% ------------------------------------------------------------
+connect(
+ #net_address{ address = {Ip, Port}, family = Family } = NetAddress,
+ _Timer, ConnectOptions) ->
+ maybe
+ {ok, Socket} ?=
+ socket:open(Family, stream),
+ ok ?=
+ setopts(Socket, ConnectOptions),
+ ConnectAddress =
+ #{ family => Family,
+ addr => Ip,
+ port => Port },
+ ok ?=
+ socket:connect(Socket, ConnectAddress),
+ start_dist_ctrl(NetAddress, Socket)
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+start_dist_ctrl(NetAddress, Socket) ->
+ Controller = self(),
+ DistCtrlTag = make_ref(),
+ DistCtrl =
+ spawn_link(
+ fun () ->
+ receive
+ {DistCtrlTag, handshake_complete, From, DistHandle} ->
+ Sync = make_ref(),
+ DistC = self(),
+ InputHandler =
+ spawn_link(
+ fun () ->
+ link(Controller),
+ DistC ! Sync,
+ receive Sync -> ok end,
+ input_handler_start(
+ Socket, DistHandle)
+ end),
+ false =
+ erlang:dist_ctrl_set_opt(
+ DistHandle, get_size, true),
+ ok =
+ erlang:dist_ctrl_input_handler(
+ DistHandle, InputHandler),
+ receive Sync -> InputHandler ! Sync end,
+ From ! {DistCtrlTag, handshake_complete}, % Reply
+ output_handler_start(Socket, DistHandle)
+ end
+ end),
+ #hs_data{
+ socket = Socket,
+ f_send =
+ fun (S, Packet) when S =:= Socket ->
+ send_packet_2(S, Packet)
+ end,
+ f_recv =
+ fun (S, 0, infinity) when S =:= Socket ->
+ recv_packet_2(S)
+ end,
+ f_setopts_pre_nodeup = f_ok(Socket),
+%%% fun (S) when S =:= Socket ->
+%%% socket:setopt(S, {otp,debug}, true)
+%%% end,
+ f_setopts_post_nodeup = f_ok(Socket),
+ f_address =
+ fun (S, Node) when S =:= Socket ->
+ inet_epmd_dist:f_address(NetAddress, Node)
+ end,
+ f_getll =
+ fun (S) when S =:= Socket ->
+ {ok, DistCtrl}
+ end,
+
+ f_handshake_complete =
+ fun (S, _Node, DistHandle) when S =:= Socket ->
+ handshake_complete(DistCtrl, DistCtrlTag, DistHandle)
+ end,
+
+ mf_tick =
+ fun (S) when S =:= Socket ->
+ tick(DistCtrl)
+ end }.
+
+%%% mf_getstat =
+%%% fun (S) when S =:= Socket ->
+%%% getstat(S)
+%%% end,
+%%% mf_setopts = mf_setopts(Socket),
+%%% mf_getopts = mf_getopts(Socket) }.
+
+send_packet_2(Socket, Packet) ->
+ Size = iolist_size(Packet),
+ true = Size < 1 bsl 16,
+ socket:send(Socket, [<<Size:16>>, Packet]).
+
+recv_packet_2(Socket) ->
+ maybe
+ {ok, <<Size:16>>} ?=
+ socket:recv(Socket, 2),
+ {ok, Data} ?=
+ socket:recv(Socket, Size),
+ {ok, binary_to_list(Data)}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+f_ok(Socket) ->
+ fun (S) when S =:= Socket ->
+ ok
+ end.
+
+-ifdef(undefined).
+getstat(S) ->
+ #{ counters :=
+ #{ read_pkg := ReadPkg,
+ write_pkg := WritePkg } } = socket:info(S),
+ %% Ignoring that the counters may wrap since dist_util
+ %% only looks for changing values anyway
+ {ok, [{recv_cnt, ReadPkg}, {send_cnt, WritePkg}, {send_pend, 0}]}.
+
+mf_setopts(Socket) ->
+ f_ok(Socket).
+
+mf_getopts(Socket) ->
+ fun (S, Opts) when S =:= Socket, is_list(Opts) ->
+ {ok, []}
+ end.
+-endif.
+
+handshake_complete(DistCtrl, DistCtrlTag, DistHandle) ->
+ DistCtrl ! {DistCtrlTag, handshake_complete, self(), DistHandle},
+ receive
+ {DistCtrlTag, handshake_complete} ->
+ ok
+ end.
+
+tick(DistCtrl) ->
+ DistCtrl ! dist_tick,
+ ok.
+
+%% ------------------------------------------------------------
+-spec output_handler_start(_, _) -> no_return(). % Server loop
+output_handler_start(Socket, DistHandle) ->
+ try
+ erlang:dist_ctrl_get_data_notification(DistHandle),
+ output_handler(Socket, DistHandle)
+ catch
+ Class : Reason : Stacktrace when Class =:= error ->
+ error_logger:error_report(
+ [output_handler_exception,
+ {class, Class},
+ {reason, Reason},
+ {stacktrace, Stacktrace}]),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+output_handler(Socket, DistHandle) ->
+ receive Msg ->
+ case Msg of
+ dist_tick ->
+ output_handler_tick(Socket, DistHandle);
+ dist_data ->
+ output_handler_data(Socket, DistHandle);
+ _ -> % Ignore
+ output_handler(Socket, DistHandle)
+ end
+ end.
+
+output_handler_tick(Socket, DistHandle) ->
+ receive Msg ->
+ case Msg of
+ dist_tick ->
+ output_handler_tick(Socket, DistHandle);
+ dist_data ->
+ output_handler_data(Socket, DistHandle);
+ _ -> % Ignore
+ output_handler_tick(Socket, DistHandle)
+ end
+ after 0 ->
+ output_data(Socket, [<<0:32>>]),
+ output_handler(Socket, DistHandle)
+ end.
+
+output_handler_data(Socket, DistHandle) ->
+ output_handler_data(Socket, DistHandle, [], 0).
+%%
+output_handler_data(Socket, DistHandle, Buffer, Size)
+ when 1 bsl 16 =< Size ->
+ output_data(Socket, Buffer),
+ output_handler_data(Socket, DistHandle);
+output_handler_data(Socket, DistHandle, Buffer, Size) ->
+ case erlang:dist_ctrl_get_data(DistHandle) of
+ none ->
+ if
+ Size =:= 0 ->
+ [] = Buffer, % ASSERT
+ erlang:dist_ctrl_get_data_notification(DistHandle),
+ output_handler(Socket, DistHandle);
+ true ->
+ output_data(Socket, Buffer),
+ output_handler_data(Socket, DistHandle)
+ end;
+ {Len, Iovec} ->
+ %% erlang:display({Len, '==>>'}),
+ output_handler_data(
+ Socket, DistHandle,
+ lists:reverse(Iovec, [<<Len:32>> | Buffer]), Len + 4 + Size)
+ end.
+
+%% Output data to socket
+output_data(Socket, Buffer) ->
+ Iovec = lists:reverse(Buffer),
+ case socket:sendmsg(Socket, #{ iov => Iovec }) of
+ ok ->
+ %% erlang:display({iolist_size(Iovec), '>>'}),
+ ok;
+ {error, Reason} ->
+ exit(Reason)
+ end.
+
+%% ------------------------------------------------------------
+-spec input_handler_start(_, _) -> no_return(). % Server loop
+input_handler_start(Socket, DistHandle) ->
+ try input_handler(Socket, DistHandle)
+ catch
+ Class : Reason : Stacktrace when Class =:= error ->
+ error_logger:error_report(
+ [input_handler_exception,
+ {class, Class},
+ {reason, Reason},
+ {stacktrace, Stacktrace}]),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+input_handler(Socket, DistHandle) ->
+ input_handler(Socket, DistHandle, <<>>, [], 0).
+
+input_handler(Socket, DistHandle, First, Buffer, Size) ->
+ %% Size is size of First + Buffer
+ case First of
+ <<PacketSize1:32, Packet1:PacketSize1/binary,
+ PacketSize2:32, Packet2:PacketSize2/binary, Rest/binary>> ->
+ put_data(DistHandle, PacketSize1, Packet1),
+ put_data(DistHandle, PacketSize2, Packet2),
+ DataSize = 4 + PacketSize1 + 4 + PacketSize2,
+ input_handler(
+ Socket, DistHandle, Rest, Buffer, Size - DataSize);
+ <<PacketSize:32, Packet:PacketSize/binary, Rest/binary>> ->
+ DataSize = 4 + PacketSize,
+ put_data(DistHandle, PacketSize, Packet),
+ input_handler(
+ Socket, DistHandle, Rest, Buffer, Size - DataSize);
+ <<PacketSize:32, PacketStart/binary>> ->
+ input_handler(
+ Socket, DistHandle, PacketStart, Buffer, Size - 4,
+ PacketSize);
+ <<Bin/binary>> ->
+ if
+ 4 =< Size ->
+ {First_1, Buffer_1, PacketSize} =
+ input_get_packet_size(Bin, lists:reverse(Buffer)),
+ input_handler(
+ Socket, DistHandle, First_1, Buffer_1, Size - 4,
+ PacketSize);
+ true ->
+ Data = input_data(Socket),
+ Buffer_1 = [Data | Buffer],
+ DataSize = byte_size(Data),
+ input_handler(
+ Socket, DistHandle, First, Buffer_1, Size + DataSize)
+ end
+ end.
+
+%% PacketSize has been matched in PacketStart
+input_handler(Socket, DistHandle, PacketStart, Buffer, Size, PacketSize) ->
+ %% Size is size of PacketStart + Buffer
+ RestSize = Size - PacketSize,
+ if
+ RestSize < 0 ->
+ %% Incomplete packet received so far
+ More = input_data(Socket),
+ MoreSize = byte_size(More),
+ input_handler(
+ Socket, DistHandle, PacketStart,
+ [More | Buffer], Size + MoreSize, PacketSize);
+ 0 < RestSize, Buffer =:= [] ->
+ %% Rest data in PacketStart
+ <<Packet:PacketSize/binary, Rest/binary>> = PacketStart,
+ put_data(DistHandle, PacketSize, Packet),
+ input_handler(Socket, DistHandle, Rest, [], RestSize);
+ Buffer =:= [] ->
+ %% No rest data
+ RestSize = 0, % ASSERT
+ put_data(DistHandle, PacketSize, PacketStart),
+ input_handler(Socket, DistHandle);
+ true ->
+ %% Split packet from rest data
+ Bin = hd(Buffer),
+ LastSize = byte_size(Bin) - RestSize,
+ <<LastBin:LastSize/binary, Rest/binary>> = Bin,
+ Packet = [PacketStart|lists:reverse(tl(Buffer), [LastBin])],
+ put_data(DistHandle, PacketSize, Packet),
+ input_handler(Socket, DistHandle, Rest, [], RestSize)
+ end.
+
+%% There are enough bytes (4) in First + [Bin|Buffer]
+%% to get the packet size, but not enough in First
+input_get_packet_size(First, [Bin|Buffer]) ->
+ MissingSize = 4 - byte_size(First),
+ if
+ MissingSize =< byte_size(Bin) ->
+ <<Last:MissingSize/binary, Rest/binary>> = Bin,
+ <<PacketSize:32>> = <<First/binary, Last/binary>>,
+ {Rest, lists:reverse(Buffer), PacketSize};
+ true ->
+ input_get_packet_size(<<First/binary, Bin/binary>>, Buffer)
+ end.
+
+%% Input data from socket
+input_data(Socket) ->
+ case socket:recv(Socket) of
+ {ok, Data} ->
+ %% erlang:display({'<<', byte_size(Data)}),
+ Data;
+ {error, Reason} ->
+ exit(Reason)
+ end.
+
+%%% put_data(_DistHandle, 0, _) ->
+%%% ok;
+%% We deliver ticks (packets size 0) to the VM,
+%% so that erlang:dist_get_stat(DistHandle) that
+%% dist_util:getstat/3 falls back to becomes good enough
+put_data(DistHandle, _PacketSize, Packet) ->
+ %% erlang:display({'<<==', _PacketSize}),
+ erlang:dist_ctrl_put_data(DistHandle, Packet).
+
+%% ------------------------------------------------------------
+supported() ->
+ try socket:info() of
+ #{io_backend := #{name := BackendName}}
+ when (BackendName =/= win_esaio) ->
+ ok;
+ _ ->
+ {skip, "Temporary exclusion"}
+ catch
+ error : notsup ->
+ {skip, "esock not supported"};
+ error : undef ->
+ {skip, "esock not configured"}
+ end.
+ %% try socket:is_supported(ipv6) of
+ %% _ ->
+ %% ok
+ %% catch error : notsup ->
+ %% "Module 'socket' not supported"
+ %% end.
diff --git a/lib/kernel/src/inet_int.hrl b/lib/kernel/src/inet_int.hrl
index f4e16c6a76..2f50f2c23c 100644
--- a/lib/kernel/src/inet_int.hrl
+++ b/lib/kernel/src/inet_int.hrl
@@ -135,6 +135,9 @@
-define(UDP_OPT_ADD_MEMBERSHIP, 14).
-define(UDP_OPT_DROP_MEMBERSHIP, 15).
-define(INET_OPT_IPV6_V6ONLY, 16).
+-define(INET_OPT_REUSEPORT, 17).
+-define(INET_OPT_REUSEPORT_LB, 18).
+-define(INET_OPT_EXCLUSIVEADDRUSE, 19).
% "Local" options: codes start from 20:
-define(INET_LOPT_BUFFER, 20).
-define(INET_LOPT_HEADER, 21).
diff --git a/lib/kernel/src/inet_parse.erl b/lib/kernel/src/inet_parse.erl
index 31d759428d..9d54d2c6bd 100644
--- a/lib/kernel/src/inet_parse.erl
+++ b/lib/kernel/src/inet_parse.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -386,14 +386,9 @@ port_proto([$/ | Proto], Port) when Port =/= 0 ->
%% Check if a String is a string with visible characters #21..#7E
%% visible_string(String) -> Bool
%%
-visible_string([H|T]) ->
- is_vis1([H|T]);
-visible_string(_) ->
- false.
-
-is_vis1([C | Cs]) when C >= 16#21, C =< 16#7e -> is_vis1(Cs);
-is_vis1([]) -> true;
-is_vis1(_) -> false.
+visible_string([C | Cs]) when C >= 16#21, C =< 16#7e -> visible_string(Cs);
+visible_string([]) -> true;
+visible_string(_) -> false.
%%
%% Check if a String is a domain name according to RFC XXX.
diff --git a/lib/kernel/src/inet_res.erl b/lib/kernel/src/inet_res.erl
index 4e7809564c..0fb46332f2 100644
--- a/lib/kernel/src/inet_res.erl
+++ b/lib/kernel/src/inet_res.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
%%
%% %CopyrightEnd%
%%
-%% RFC 1035, 2671, 2782, 2915.
+%% RFC 1035, 2782, 2915, 6891.
%%
-module(inet_res).
@@ -61,6 +61,7 @@
| {retry, integer()}
| {timeout, integer()}
| {udp_payload_size, integer()}
+ | {dnssec_ok, boolean()}
| {usevc, boolean()}
| {nxdomain_reply, boolean()}.
@@ -263,7 +264,7 @@ do_nslookup(Name, Class, Type, Opts, Timeout) ->
%% options record
%%
-record(options, { % These must be sorted!
- alt_nameservers,edns,inet6,nameservers,
+ alt_nameservers,dnssec_ok,edns,inet6,nameservers,
nxdomain_reply, % this is a local option, not in inet_db
recurse,retry,servfail_retry_timeout,timeout,
udp_payload_size,usevc,
@@ -573,12 +574,13 @@ res_getby_search(Name, [Dom | Ds], _Reason, Type, Timer) ->
QueryName =
%% Join Name and Dom with a single dot.
%% Allow Dom to be "." or "", but not to lead with ".".
- %% Do not allow Name to be "".
if
- Name =/= "" andalso (Dom =:= "." orelse Dom =:= "") ->
+ Dom =:= "."; Dom =:= "" ->
Name;
- Name =/= "" andalso hd(Dom) =/= $. ->
- Name++"."++Dom;
+ Name =/= "", hd(Dom) =/= $. ->
+ Name ++ "." ++ Dom;
+ Name =:= "", hd(Dom) =/= $. ->
+ Dom;
true ->
erlang:error({if_clause, Name, Dom})
end,
@@ -672,9 +674,12 @@ make_query(Dname, Class, Type, Options, Edns) ->
ARList = case Edns of
false -> [];
_ ->
- PSz = Options#options.udp_payload_size,
+ #options{
+ udp_payload_size = PSz,
+ dnssec_ok = DnssecOk } = Options,
[#dns_rr_opt{udp_payload_size=PSz,
- version=Edns}]
+ version=Edns,
+ do=DnssecOk}]
end,
Msg = #dns_rec{header=#dns_header{id=Id,
qr=false,
diff --git a/lib/kernel/src/inet_tcp_dist.erl b/lib/kernel/src/inet_tcp_dist.erl
index b53de6281b..8c5c8a1cf2 100644
--- a/lib/kernel/src/inet_tcp_dist.erl
+++ b/lib/kernel/src/inet_tcp_dist.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
+%%
%% 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
@@ -14,10 +14,11 @@
%% 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.
-%%
+%%
%% %CopyrightEnd%
%%
-module(inet_tcp_dist).
+-feature(maybe_expr, enable).
%% Handles the connection setup phase with other Erlang nodes.
@@ -30,6 +31,11 @@
%% Generalized dist API
-export([gen_listen/3, gen_accept/2, gen_accept_connection/6,
gen_setup/6, gen_select/2, gen_address/1]).
+-export([fam_select/2, fam_address/1, fam_listen/4, fam_setup/4]).
+%% OTP internal (e.g ssl)
+-export([gen_hs_data/2, nodelay/0]).
+
+-export([merge_options/2, merge_options/3]).
%% internal exports
@@ -42,20 +48,28 @@
-include("dist.hrl").
-include("dist_util.hrl").
+-define(DRIVER, inet_tcp).
+-define(PROTOCOL, tcp).
+
%% ------------------------------------------------------------
%% Select this protocol based on node name
%% select(Node) => Bool
%% ------------------------------------------------------------
select(Node) ->
- gen_select(inet_tcp, Node).
+ gen_select(?DRIVER, Node).
gen_select(Driver, Node) ->
+ fam_select(Driver:family(), Node).
+
+fam_select(Family, Node) ->
case dist_util:split_node(Node) of
{node, Name, Host} ->
- case call_epmd_function(
- net_kernel:epmd_module(), address_please,
- [Name, Host, Driver:family()]) of
+ EpmdMod = net_kernel:epmd_module(),
+ case
+ call_epmd_function(
+ EpmdMod, address_please, [Name, Host, Family])
+ of
{ok, _Addr} -> true;
{ok, _Addr, _Port, _Creation} -> true;
_ -> false
@@ -67,47 +81,115 @@ gen_select(Driver, Node) ->
%% Get the address family that this distribution uses
%% ------------------------------------------------------------
address() ->
- gen_address(inet_tcp).
+ gen_address(?DRIVER).
+
gen_address(Driver) ->
- get_tcp_address(Driver).
+ fam_address(Driver:family()).
+
+fam_address(Family) ->
+ {ok, Host} = inet:gethostname(),
+ #net_address{
+ host = Host,
+ protocol = ?PROTOCOL,
+ family = Family
+ }.
+
+%% ------------------------------------------------------------
+%% Set up the general fields in #hs_data{}
+%% ------------------------------------------------------------
+gen_hs_data(Driver, Socket) ->
+ %% The only thing Driver actually is used for is to
+ %% implement non-blocking send of distribution tick
+ Nodelay = nodelay(),
+ #hs_data{
+ socket = Socket,
+ f_send = fun Driver:send/2,
+ f_recv = fun Driver:recv/3,
+ f_setopts_pre_nodeup =
+ fun (S) ->
+ inet:setopts(
+ S,
+ [{active, false}, {packet, 4}, Nodelay])
+ end,
+ f_setopts_post_nodeup =
+ fun (S) ->
+ inet:setopts(
+ S,
+ [{active, true}, {packet,4},
+ {deliver, port}, binary, Nodelay])
+ end,
+ f_getll = fun inet:getll/1,
+ mf_tick = fun (S) -> ?MODULE:tick(Driver, S) end,
+ mf_getstat = fun ?MODULE:getstat/1,
+ mf_setopts = fun ?MODULE:setopts/2,
+ mf_getopts = fun ?MODULE:getopts/2}.
%% ------------------------------------------------------------
%% Create the listen socket, i.e. the port that this erlang
%% node is accessible through.
%% ------------------------------------------------------------
-listen(Name, Host) ->
- gen_listen(inet_tcp, Name, Host).
-
-%% Keep this clause for third-party dist controllers reusing this API
+%% Keep this function for third-party dist controllers reusing this API
listen(Name) ->
{ok, Host} = inet:gethostname(),
listen(Name, Host).
+listen(Name, Host) ->
+ gen_listen(?DRIVER, Name, Host).
+
gen_listen(Driver, Name, Host) ->
- ErlEpmd = net_kernel:epmd_module(),
- case gen_listen(ErlEpmd, Name, Host, Driver) of
- {ok, Socket} ->
- TcpAddress = get_tcp_address(Driver, Socket),
- {_,Port} = TcpAddress#net_address.address,
- case ErlEpmd:register_node(Name, Port, Driver) of
- {ok, Creation} ->
- {ok, {Socket, TcpAddress, Creation}};
- Error ->
- Error
- end;
- Error ->
- Error
+ ForcedOptions = [{active, false}, {packet,2}, {nodelay, true}],
+ ListenFun =
+ fun (First, Last, ListenOptions) ->
+ listen_loop(
+ Driver, First, Last,
+ merge_options(ListenOptions, ForcedOptions))
+ end,
+ Family = Driver:family(),
+ maybe
+ %%
+ {ok, {ListenSocket, Address, Creation}} ?=
+ fam_listen(Family, Name, Host, ListenFun),
+ NetAddress =
+ #net_address{
+ host = Host,
+ protocol = ?PROTOCOL,
+ family = Family,
+ address = Address},
+ {ok, {ListenSocket, NetAddress, Creation}}
+ end.
+
+listen_loop(_Driver, First, Last, _Options) when First > Last ->
+ {error,eaddrinuse};
+listen_loop(Driver, First, Last, Options) ->
+ case Driver:listen(First, Options) of
+ {error, eaddrinuse} ->
+ listen_loop(Driver, First+1, Last, Options);
+ Other ->
+ Other
end.
-gen_listen(ErlEpmd, Name, Host, Driver) ->
- ListenOptions = listen_options(),
- case call_epmd_function(ErlEpmd, listen_port_please, [Name, Host]) of
- {ok, 0} ->
- {First,Last} = get_port_range(),
- do_listen(Driver, First, Last, ListenOptions);
- {ok, Prt} ->
- do_listen(Driver, Prt, Prt, ListenOptions)
+
+fam_listen(Family, Name, Host, ListenFun) ->
+ maybe
+ EpmdMod = net_kernel:epmd_module(),
+ %%
+ {ok, ListenSocket} ?=
+ case
+ call_epmd_function(
+ EpmdMod, listen_port_please, [Name, Host])
+ of
+ {ok, 0} ->
+ {First,Last} = get_port_range(),
+ ListenFun(First, Last, listen_options());
+ {ok, PortNum} ->
+ ListenFun(PortNum, PortNum, listen_options())
+ end,
+ {ok, {_IP,Port} = Address} = inet:sockname(ListenSocket),
+ %%
+ {ok, Creation} ?=
+ EpmdMod:register_node(Name, Port, Family),
+ {ok, {ListenSocket, Address, Creation}}
end.
get_port_range() ->
@@ -123,70 +205,89 @@ get_port_range() ->
{0,0}
end.
-do_listen(_Driver, First,Last,_) when First > Last ->
- {error,eaddrinuse};
-do_listen(Driver, First,Last,Options) ->
- case Driver:listen(First, Options) of
- {error, eaddrinuse} ->
- do_listen(Driver, First+1,Last,Options);
- Other ->
- Other
- end.
listen_options() ->
DefaultOpts = [{reuseaddr, true}, {backlog, 128}],
ForcedOpts =
- [{active, false}, {packet,2} |
- case application:get_env(kernel, inet_dist_use_interface) of
- {ok, Ip} -> [{ip, Ip}];
- undefined -> []
- end],
- Force = maps:from_list(ForcedOpts),
+ case application:get_env(kernel, inet_dist_use_interface) of
+ {ok, Ip} -> [{ip, Ip}];
+ undefined -> []
+ end,
InetDistListenOpts =
case application:get_env(kernel, inet_dist_listen_options) of
{ok, Opts} -> Opts;
undefined -> []
end,
- ListenOpts = listen_options(InetDistListenOpts, ForcedOpts, Force),
- Seen =
- maps:from_list(
- lists:filter(
- fun ({_,_}) -> true;
- (_) -> false
- end, ListenOpts)),
- lists:filter(
- fun ({OptName,_}) when is_map_key(OptName, Seen) ->
- false;
- (_) ->
- true
- end, DefaultOpts) ++ ListenOpts.
-
-%% Pass through all but forced
-listen_options([Opt | Opts], ForcedOpts, Force) ->
- case Opt of
- {OptName,_} ->
- case is_map_key(OptName, Force) of
+ merge_options(InetDistListenOpts, ForcedOpts, DefaultOpts).
+
+
+merge_options(Opts, ForcedOpts) ->
+ merge_options(Opts, ForcedOpts, []).
+%%
+merge_options(Opts, ForcedOpts, DefaultOpts) ->
+ Forced = merge_options(ForcedOpts),
+ Default = merge_options(DefaultOpts),
+ ForcedOpts ++ merge_options(Opts, Forced, DefaultOpts, Default).
+
+%% Collect expanded 2-tuple options in a map
+merge_options(Opts) ->
+ lists:foldr(
+ fun (Opt, Acc) ->
+ case expand_option(Opt) of
+ {OptName, OptVal} ->
+ maps:put(OptName, OptVal, Acc);
+ _ ->
+ Acc
+ end
+ end, #{}, Opts).
+
+%% Pass through all options that are not forced,
+%% which we already have prepended,
+%% and remove options that we see from the Default map
+%%
+merge_options([Opt | Opts], Forced, DefaultOpts, Default) ->
+ case expand_option(Opt) of
+ {OptName, _} ->
+ %% Remove from the Default map
+ Default_1 = maps:remove(OptName, Default),
+ if
+ is_map_key(OptName, Forced) ->
+ %% Forced option - do not pass through
+ merge_options(Opts, Forced, DefaultOpts, Default_1);
true ->
- listen_options(Opts, ForcedOpts, Force);
- false ->
+ %% Pass through
[Opt |
- listen_options(Opts, ForcedOpts, Force)]
+ merge_options(Opts, Forced, DefaultOpts, Default_1)]
end;
_ ->
- [Opt |
- listen_options(Opts, ForcedOpts, Force)]
+ %% Unhandled options e.g {raw, ...} - pass through
+ [Opt | merge_options(Opts, Forced, DefaultOpts, Default)]
end;
-listen_options([], ForcedOpts, _Force) ->
- %% Append forced
- ForcedOpts.
-
+merge_options([], _Forced, DefaultOpts, Default) ->
+ %% Append the needed default options (that we have not seen)
+ [Opt ||
+ Opt <- DefaultOpts,
+ is_map_key(element(1, expand_option(Opt)), Default)].
+
+%% Expand an atom option into its tuple equivalence,
+%% pass through others
+expand_option(Opt) ->
+ if
+ Opt =:= list; Opt =:= binary ->
+ {mode, Opt};
+ Opt =:= inet; Opt =:= inet6; Opt =:= local ->
+ %% 'family' is not quite an option name, but could/should be
+ {family, Opt};
+ true ->
+ Opt
+ end.
%% ------------------------------------------------------------
%% Accepts new connection attempts from other Erlang nodes.
%% ------------------------------------------------------------
accept(Listen) ->
- gen_accept(inet_tcp, Listen).
+ gen_accept(?DRIVER, Listen).
gen_accept(Driver, Listen) ->
spawn_opt(?MODULE, accept_loop, [Driver, self(), Listen], [link, {priority, max}]).
@@ -194,7 +295,7 @@ gen_accept(Driver, Listen) ->
accept_loop(Driver, Kernel, Listen) ->
case Driver:accept(Listen) of
{ok, Socket} ->
- Kernel ! {accept,self(),Socket,Driver:family(),tcp},
+ Kernel ! {accept,self(),Socket,Driver:family(),?PROTOCOL},
_ = controller(Driver, Kernel, Socket),
accept_loop(Driver, Kernel, Listen);
Error ->
@@ -230,7 +331,7 @@ flush_controller(Pid, Socket) ->
%% ------------------------------------------------------------
accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
- gen_accept_connection(inet_tcp, AcceptPid, Socket, MyNode, Allowed, SetupTime).
+ gen_accept_connection(?DRIVER, AcceptPid, Socket, MyNode, Allowed, SetupTime).
gen_accept_connection(Driver, AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
spawn_opt(?MODULE, do_accept,
@@ -243,40 +344,19 @@ do_accept(Driver, Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
Timer = dist_util:start_timer(SetupTime),
case check_ip(Driver, Socket) of
true ->
- HSData = #hs_data{
- kernel_pid = Kernel,
- this_node = MyNode,
- socket = Socket,
- timer = Timer,
- this_flags = 0,
- allowed = Allowed,
- f_send = fun Driver:send/2,
- f_recv = fun Driver:recv/3,
- f_setopts_pre_nodeup =
- fun(S) ->
- inet:setopts(S,
- [{active, false},
- {packet, 4},
- nodelay()])
- end,
- f_setopts_post_nodeup =
- fun(S) ->
- inet:setopts(S,
- [{active, true},
- {deliver, port},
- {packet, 4},
- binary,
- nodelay()])
- end,
- f_getll = fun(S) ->
- inet:getll(S)
- end,
- f_address = fun(S, Node) -> get_remote_id(Driver, S, Node) end,
- mf_tick = fun(S) -> ?MODULE:tick(Driver, S) end,
- mf_getstat = fun ?MODULE:getstat/1,
- mf_setopts = fun ?MODULE:setopts/2,
- mf_getopts = fun ?MODULE:getopts/2
- },
+ Family = Driver:family(),
+ HSData =
+ (gen_hs_data(Driver, Socket))
+ #hs_data{
+ kernel_pid = Kernel,
+ this_node = MyNode,
+ timer = Timer,
+ this_flags = 0,
+ allowed = Allowed,
+ f_address =
+ fun (S, Node) ->
+ get_remote_id(Family, S, Node)
+ end},
dist_util:handshake_other_started(HSData);
{false,IP} ->
error_msg("** Connection attempt from "
@@ -304,13 +384,13 @@ nodelay() ->
%% ------------------------------------------------------------
%% Get remote information about a Socket.
%% ------------------------------------------------------------
-get_remote_id(Driver, Socket, Node) ->
+get_remote_id(Family, Socket, Node) ->
case inet:peername(Socket) of
{ok,Address} ->
case split_node(atom_to_list(Node), $@, []) of
[_,Host] ->
#net_address{address=Address,host=Host,
- protocol=tcp,family=Driver:family()};
+ protocol=?PROTOCOL,family=Family};
_ ->
%% No '@' or more than one '@' in node name.
?shutdown(no_node)
@@ -325,130 +405,109 @@ get_remote_id(Driver, Socket, Node) ->
%% ------------------------------------------------------------
setup(Node, Type, MyNode, LongOrShortNames,SetupTime) ->
- gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames, SetupTime).
+ gen_setup(?DRIVER, Node, Type, MyNode, LongOrShortNames, SetupTime).
gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- spawn_opt(?MODULE, do_setup,
+ spawn_opt(?MODULE, do_setup,
[Driver, self(), Node, Type, MyNode, LongOrShortNames, SetupTime],
dist_util:net_ticker_spawn_options()).
do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- ?trace("~p~n",[{inet_tcp_dist,self(),setup,Node}]),
- [Name, Address] = splitnode(Driver, Node, LongOrShortNames),
- AddressFamily = Driver:family(),
- ErlEpmd = net_kernel:epmd_module(),
+ ?trace("~p~n",[{?MODULE,self(),setup,Node}]),
Timer = dist_util:start_timer(SetupTime),
- case call_epmd_function(ErlEpmd,address_please,[Name, Address, AddressFamily]) of
+ Family = Driver:family(),
+ {#net_address{ address = {Ip, TcpPort} } = NetAddress,
+ ConnectOptions,
+ Version} =
+ fam_setup(
+ Family, Node, LongOrShortNames, fun Driver:parse_address/1),
+ dist_util:reset_timer(Timer),
+ case Driver:connect(Ip, TcpPort, ConnectOptions) of
+ {ok, Socket} ->
+ HSData =
+ (gen_hs_data(Driver, Socket))
+ #hs_data{
+ kernel_pid = Kernel,
+ other_node = Node,
+ this_node = MyNode,
+ timer = Timer,
+ this_flags = 0,
+ other_version = Version,
+ f_address =
+ fun(_,_) ->
+ NetAddress
+ end,
+ request_type = Type},
+ dist_util:handshake_we_started(HSData);
+ _ ->
+ %% Other Node may have closed since
+ %% discovery !
+ ?trace("other node (~p) "
+ "closed since discovery (port_please).~n",
+ [Node]),
+ ?shutdown(Node)
+ end.
+
+fam_setup(Family, Node, LongOrShortNames, ParseAddress) ->
+ ?trace("~p~n",[{?MODULE,self(),?FUNCTION_NAME,Node}]),
+ [Name, Host] = splitnode(ParseAddress, Node, LongOrShortNames),
+ ErlEpmd = net_kernel:epmd_module(),
+ case
+ call_epmd_function(
+ ErlEpmd, address_please, [Name, Host, Family])
+ of
{ok, Ip, TcpPort, Version} ->
- ?trace("address_please(~p) -> version ~p~n",
- [Node,Version]),
- do_setup_connect(Driver, Kernel, Node, Address, AddressFamily,
- Ip, TcpPort, Version, Type, MyNode, Timer);
- {ok, Ip} ->
+ ?trace("address_please(~p) -> version ~p~n", [Node,Version]),
+ fam_setup(Family, Host, Ip, TcpPort, Version);
+ {ok, Ip} ->
case ErlEpmd:port_please(Name, Ip) of
{port, TcpPort, Version} ->
- ?trace("port_please(~p) -> version ~p~n",
+ ?trace("port_please(~p) -> version ~p~n",
[Node,Version]),
- do_setup_connect(Driver, Kernel, Node, Address, AddressFamily,
- Ip, TcpPort, Version, Type, MyNode, Timer);
+ fam_setup(Family, Host, Ip, TcpPort, Version);
_ ->
?trace("port_please (~p) failed.~n", [Node]),
?shutdown(Node)
end;
_Other ->
- ?trace("inet_getaddr(~p) "
- "failed (~p).~n", [Node,_Other]),
+ ?trace("inet_getaddr(~p) failed (~p).~n", [Node,_Other]),
?shutdown(Node)
end.
-%%
-%% Actual setup of connection
-%%
-do_setup_connect(Driver, Kernel, Node, Address, AddressFamily,
- Ip, TcpPort, Version, Type, MyNode, Timer) ->
- dist_util:reset_timer(Timer),
- case
- Driver:connect(
- Ip, TcpPort,
- connect_options([{active, false}, {packet, 2}]))
- of
- {ok, Socket} ->
- HSData = #hs_data{
- kernel_pid = Kernel,
- other_node = Node,
- this_node = MyNode,
- socket = Socket,
- timer = Timer,
- this_flags = 0,
- other_version = Version,
- f_send = fun Driver:send/2,
- f_recv = fun Driver:recv/3,
- f_setopts_pre_nodeup =
- fun(S) ->
- inet:setopts
- (S,
- [{active, false},
- {packet, 4},
- nodelay()])
- end,
- f_setopts_post_nodeup =
- fun(S) ->
- inet:setopts
- (S,
- [{active, true},
- {deliver, port},
- {packet, 4},
- nodelay()])
- end,
-
- f_getll = fun inet:getll/1,
- f_address =
- fun(_,_) ->
- #net_address{
- address = {Ip,TcpPort},
- host = Address,
- protocol = tcp,
- family = AddressFamily}
- end,
- mf_tick = fun(S) -> ?MODULE:tick(Driver, S) end,
- mf_getstat = fun ?MODULE:getstat/1,
- request_type = Type,
- mf_setopts = fun ?MODULE:setopts/2,
- mf_getopts = fun ?MODULE:getopts/2
- },
- dist_util:handshake_we_started(HSData);
- _ ->
- %% Other Node may have closed since
- %% discovery !
- ?trace("other node (~p) "
- "closed since discovery (port_please).~n",
- [Node]),
- ?shutdown(Node)
- end.
-
-connect_options(Opts) ->
- case application:get_env(kernel, inet_dist_connect_options) of
- {ok,ConnectOpts} ->
- ConnectOpts ++ Opts;
- _ ->
- Opts
- end.
+fam_setup(Family, Host, Ip, TcpPort, Version) ->
+ NetAddress =
+ #net_address{
+ address = {Ip, TcpPort},
+ host = Host,
+ protocol = ?PROTOCOL,
+ family = Family},
+ {NetAddress, connect_options(), Version}.
+
+connect_options() ->
+ merge_options(
+ case application:get_env(kernel, inet_dist_connect_options) of
+ {ok, ConnectOpts} ->
+ ConnectOpts;
+ _ ->
+ []
+ end, [{active, false}, {packet, 2}]).
+
%%
%% Close a socket.
%%
close(Socket) ->
- inet_tcp:close(Socket).
+ ?DRIVER:close(Socket).
%% If Node is illegal terminate the connection setup!!
-splitnode(Driver, Node, LongOrShortNames) ->
+splitnode(ParseAddress, Node, LongOrShortNames) ->
case split_node(atom_to_list(Node), $@, []) of
[Name|Tail] when Tail =/= [] ->
Host = lists:append(Tail),
case split_node(Host, $., []) of
[_] when LongOrShortNames =:= longnames ->
- case Driver:parse_address(Host) of
+ case ParseAddress(Host) of
{ok, _} ->
[Name, Host];
_ ->
@@ -482,21 +541,6 @@ split_node([H|T], Chr, Ack) -> split_node(T, Chr, [H|Ack]);
split_node([], _, Ack) -> [lists:reverse(Ack)].
%% ------------------------------------------------------------
-%% Fetch local information about a Socket.
-%% ------------------------------------------------------------
-get_tcp_address(Driver, Socket) ->
- {ok, Address} = inet:sockname(Socket),
- NetAddr = get_tcp_address(Driver),
- NetAddr#net_address{ address = Address }.
-get_tcp_address(Driver) ->
- {ok, Host} = inet:gethostname(),
- #net_address {
- host = Host,
- protocol = tcp,
- family = Driver:family()
- }.
-
-%% ------------------------------------------------------------
%% Determine if EPMD module supports the called functions.
%% If not call the builtin erl_epmd
%% ------------------------------------------------------------
diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src
index 0b760b12fe..5e76e55911 100644
--- a/lib/kernel/src/kernel.app.src
+++ b/lib/kernel/src/kernel.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -55,6 +55,8 @@
inet6_udp,
inet6_sctp,
inet_config,
+ inet_epmd_dist,
+ inet_epmd_socket,
inet_hosts,
inet_gethost_native,
inet_tcp_dist,
@@ -83,9 +85,9 @@
os,
ram_file,
rpc,
- user,
user_drv,
user_sup,
+ prim_tty,
disk_log,
disk_log_1,
disk_log_server,
@@ -158,6 +160,7 @@
{shell_docs_ansi,auto}
]},
{mod, {kernel, []}},
- {runtime_dependencies, ["erts-13.1.3", "stdlib-4.1.1", "sasl-3.0", "crypto-5.0"]}
+ {runtime_dependencies, ["erts-@OTP-18248:OTP-18344@", "stdlib-@OTP-17932@",
+ "sasl-3.0", "crypto-5.0"]}
]
}.
diff --git a/lib/kernel/src/kernel.erl b/lib/kernel/src/kernel.erl
index 9aec16ace7..5fd95871ca 100644
--- a/lib/kernel/src/kernel.erl
+++ b/lib/kernel/src/kernel.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -58,14 +58,19 @@ config_change(Changed, New, Removed) ->
%%% | kernel_sup (A)|
%%% ---------------
%%% |
-%%% -------------------------------
-%%% | | |
-%%% <std services> ------------- -------------
-%%% (file,code, | erl_dist (A)| | safe_sup (1)|
-%%% rpc, ...) ------------- -------------
-%%% | |
-%%% (net_kernel, (disk_log, pg,
-%%% auth, ...) ...)
+%%% ------------------------------------------
+%%% | | | | |
+%%% <std services> | ------------- | -------------
+%%% (code, [stderr], | | erl_dist (A)| | | safe_sup (1)|
+%%% ...) | ------------- | -------------
+%%% | | | |
+%%% <on_load> ([net_sup], | (disk_log, pg,
+%%% (transient) rpc, global, | ...)
+%%% ...) |
+%%% |
+%%% <more services>
+%%% (file, peer, [user],
+%%% [logger], ...)
%%%
%%% The rectangular boxes are supervisors. All supervisors except
%%% for kernel_safe_sup terminates the entire erlang node if any of
@@ -118,6 +123,15 @@ init([]) ->
type => supervisor,
modules => [standard_error]},
+ OnLoad = #{id => on_load,
+ start =>
+ {proc_lib, start_link,
+ [?MODULE, ?FUNCTION_NAME, [on_load]]},
+ restart => transient,
+ shutdown => 2000,
+ type => worker,
+ modules => [?MODULE]},
+
User = #{id => user,
start => {user_sup, start, []},
restart => temporary,
@@ -145,10 +159,34 @@ init([]) ->
none -> []
end,
+ %% Start the file server early, if possible, so on_load functions
+ %% can do file server operations.
+ %%
+ %% This is a workaround. The combination of having a remote
+ %% file server i.e the file server on a master node, with on_load
+ %% functions that uses file operations over the file server,
+ %% will not, and cannot work.
+ %%
+ %% A "proper" solution will have to sort out how code loading
+ %% over all code server backends should interact with all
+ %% file server backends and nif on_load functions
+ %% that need to find a shared object file.
+ %%
+ case init:get_argument(master) of
+ {ok, [[_MasterNode]]} ->
+ EarlyFile = [],
+ LateFile = [File];
+ _ ->
+ EarlyFile = [File],
+ LateFile = []
+ end,
+
case init:get_argument(mode) of
{ok, [["minimal"]|_]} ->
{ok, {SupFlags,
- [Code, File, StdError] ++ Peer ++
+ [Code, StdError | EarlyFile] ++
+ [OnLoad | LateFile] ++
+ Peer ++
[User, LoggerSup, Config, RefC, SafeSup]}};
_ ->
DistChildren =
@@ -175,11 +213,19 @@ init([]) ->
CompileServer = start_compile_server(),
{ok, {SupFlags,
- [Code, InetDb | DistChildren] ++
- [File, SigSrv, StdError] ++ Peer ++
- [User, Config, RefC, SafeSup, LoggerSup] ++
+ [Code, StdError | EarlyFile] ++
+ [OnLoad, InetDb | DistChildren] ++ LateFile ++
+ [SigSrv | Peer] ++
+ [User, LoggerSup, Config, RefC, SafeSup] ++
Timer ++ CompileServer}}
end;
+init(on_load) ->
+ %% Run the on_load handlers for all modules that have been
+ %% loaded so far. Running them at this point means that
+ %% on_load handlers can safely call some essential kernel processes,
+ %% in particular call code:priv_dir/1 or code:lib_dir/1.
+ init:run_on_load_handlers(),
+ proc_lib:init_ack({ok, self()});
init(safe) ->
SupFlags = #{strategy => one_for_one,
intensity => 4,
@@ -189,12 +235,6 @@ init(safe) ->
DiskLog = start_disk_log(),
Pg = start_pg(),
- %% Run the on_load handlers for all modules that have been
- %% loaded so far. Running them at this point means that
- %% on_load handlers can safely call kernel processes
- %% (and in particular call code:priv_dir/1 or code:lib_dir/1).
- init:run_on_load_handlers(),
-
{ok, {SupFlags, Boot ++ DiskLog ++ Pg}}.
start_distribution() ->
diff --git a/lib/kernel/src/logger_formatter.erl b/lib/kernel/src/logger_formatter.erl
index 579b4f8f73..75d85de744 100644
--- a/lib/kernel/src/logger_formatter.erl
+++ b/lib/kernel/src/logger_formatter.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -48,7 +48,8 @@ format(#{level:=Level,msg:=Msg0,meta:=Meta},Config0)
Config = add_default_config(Config0),
Meta1 = maybe_add_legacy_header(Level,Meta,Config),
Template = maps:get(template,Config),
- {BT,AT0} = lists:splitwith(fun(msg) -> false; (_) -> true end, Template),
+ LinearTemplate = linearize_template(Meta1,Template),
+ {BT,AT0} = lists:splitwith(fun(msg) -> false; (_) -> true end, LinearTemplate),
{DoMsg,AT} =
case AT0 of
[msg|Rest] -> {true,Rest};
@@ -89,6 +90,18 @@ format(#{level:=Level,msg:=Msg0,meta:=Meta},Config0)
end,
truncate(B,MsgStr,A,maps:get(max_size,Config)).
+linearize_template(Data,[{Key,IfExist,Else}|Format]) ->
+ BranchForUse =
+ case value(Key,Data) of
+ {ok,_Value} -> linearize_template(Data,IfExist);
+ error -> linearize_template(Data,Else)
+ end,
+ BranchForUse ++ linearize_template(Data,Format);
+linearize_template(Data,[StrOrKey|Format]) ->
+ [StrOrKey|linearize_template(Data,Format)];
+linearize_template(_Data,[]) ->
+ [].
+
trim([H|T],Rev) when H==$\s; H==$\r; H==$\n ->
trim(T,Rev);
trim([H|T],false) when is_list(H) ->
@@ -110,13 +123,6 @@ trim(String,_) ->
do_format(Level,Data,[level|Format],Config) ->
[to_string(level,Level,Config)|do_format(Level,Data,Format,Config)];
-do_format(Level,Data,[{Key,IfExist,Else}|Format],Config) ->
- String =
- case value(Key,Data) of
- {ok,Value} -> do_format(Level,Data#{Key=>Value},IfExist,Config);
- error -> do_format(Level,Data,Else,Config)
- end,
- [String|do_format(Level,Data,Format,Config)];
do_format(Level,Data,[Key|Format],Config)
when is_atom(Key) orelse
(is_list(Key) andalso is_atom(hd(Key))) ->
diff --git a/lib/kernel/src/logger_h_common.erl b/lib/kernel/src/logger_h_common.erl
index f0db587af1..3c99d49f3d 100644
--- a/lib/kernel/src/logger_h_common.erl
+++ b/lib/kernel/src/logger_h_common.erl
@@ -350,7 +350,7 @@ call(_, Name, Op) ->
{error,{badarg,{Op,[Name]}}}.
notify({mode_change,Mode0,Mode1},#{id:=Name}=State) ->
- log_handler_info(Name,"Handler ~p switched from ~p to ~p mode",
+ log_handler_info(Name,"Handler ~p switched from ~p to ~p mode",
[Name,Mode0,Mode1], State);
notify({flushed,Flushed},#{id:=Name}=State) ->
log_handler_info(Name, "Handler ~p flushed ~w log events",
diff --git a/lib/kernel/src/logger_olp.erl b/lib/kernel/src/logger_olp.erl
index 1379f8cf54..6bbf9963b6 100644
--- a/lib/kernel/src/logger_olp.erl
+++ b/lib/kernel/src/logger_olp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -193,13 +193,11 @@ init([Name,Module,Args,Options]) ->
gen_server:enter_loop(?MODULE, [], State);
Error ->
unregister(Name),
- proc_lib:init_ack(Error),
- ignore %% Just to shut Dialyzer up - ignored
+ proc_lib:init_fail(Error, {exit,normal})
catch
- _:Error ->
+ _:Reason ->
unregister(Name),
- proc_lib:init_ack(Error),
- ignore %% Just to shut Dialyzer up - ignored
+ proc_lib:init_fail({error,Reason}, {exit,normal})
end.
%% This is the synchronous load event.
diff --git a/lib/kernel/src/logger_simple_h.erl b/lib/kernel/src/logger_simple_h.erl
index fe8db09e83..d35c533b6d 100644
--- a/lib/kernel/src/logger_simple_h.erl
+++ b/lib/kernel/src/logger_simple_h.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -68,14 +68,14 @@ log(#{msg:=_,meta:=#{time:=_}=M}=Log,_Config) ->
%% Log directly from client just to get it out
case maps:get(internal_log_event, M, false) of
false ->
- do_log(
+ do_log(simple,
#{level=>error,
msg=>{report,{error,simple_handler_process_dead}},
meta=>#{time=>logger:timestamp()}});
true ->
ok
end,
- do_log(Log);
+ do_log(simple,Log);
_ ->
?MODULE ! {log,Log}
end,
@@ -90,9 +90,9 @@ log(_,_) ->
init(Starter) ->
register(?MODULE,self()),
Starter ! {self(),started},
- loop(#{buffer_size=>10,dropped=>0,buffer=>[]}).
+ loop(rich, #{buffer_size=>10,dropped=>0,buffer=>[]}).
-loop(Buffer) ->
+loop(Mode, Buffer) ->
receive
stop ->
%% We replay the logger messages if there is
@@ -109,11 +109,11 @@ loop(Buffer) ->
unlink(whereis(logger)),
ok;
{log,#{msg:=_,meta:=#{time:=_}}=Log} ->
- do_log(Log),
- loop(update_buffer(Buffer,Log));
+ NewMode = do_log(Mode, Log),
+ loop(NewMode, update_buffer(Buffer,Log));
_ ->
%% Unexpected message - flush it!
- loop(Buffer)
+ loop(Mode, Buffer)
end.
update_buffer(#{buffer_size:=0,dropped:=D}=Buffer,_Log) ->
@@ -139,16 +139,42 @@ drop_msg(N) ->
%%%-----------------------------------------------------------------
%%% Internal
-do_log(Log) ->
- try
- Str = logger_formatter:format(Log,
- #{ legacy_header => true, single_line => false
- ,depth => unlimited, time_offset => ""
- }),
- erlang:display_string(lists:flatten(unicode:characters_to_list(Str)))
- catch _E:_R:_ST ->
- % erlang:display({_E,_R,_ST}),
- display_log(Log)
+%% If the init process is busy (for instance doing a shutdown)
+%% we can get blocked while trying to load code. So we spawn a process
+%% for each log message that can potentially block. If the logging cannot
+%% be done within 300ms, we instead log the raw log message to stdout
+%% and switch mode to always log using the raw format.
+do_log(simple, Log) ->
+ display_log(Log), simple;
+do_log(rich = Mode, Log) ->
+
+ {Pid, Ref} =
+ spawn_monitor(
+ fun() ->
+ Str = logger_formatter:format(
+ Log,
+ #{ legacy_header => true, single_line => false,
+ depth => unlimited, time_offset => ""
+ }),
+ erlang:display_string(stdout, lists:flatten(unicode:characters_to_list(Str)))
+ end),
+ receive
+ {'DOWN', Ref, _, _, normal} ->
+ Mode;
+ {'DOWN', Ref, _, _, _Else} ->
+ display_log(Log),
+ Mode
+ after 300 ->
+ %% init:terminate/3 sleeps for 500 ms before exiting,
+ %% so we wait for 300 ms for the log to happen
+ exit(Pid, kill),
+ receive
+ {'DOWN', Ref, _, _, normal} ->
+ Mode;
+ {'DOWN', Ref, _, _, _Else} ->
+ display_log(Log),
+ simple
+ end
end.
display_log(#{msg:={report,Report},
@@ -165,6 +191,7 @@ display_date(Timestamp) when is_integer(Timestamp) ->
{{Y,Mo,D},{H,Mi,S}} = erlang:universaltime_to_localtime(
erlang:posixtime_to_universaltime(Sec)),
erlang:display_string(
+ stdout,
integer_to_list(Y) ++ "-" ++
pad(Mo,2) ++ "-" ++
pad(D,2) ++ " " ++
@@ -182,7 +209,8 @@ pad(Str,Size) ->
display({string,Chardata}) ->
try unicode:characters_to_list(Chardata) of
- String -> erlang:display_string(String), erlang:display_string("\n")
+ String -> erlang:display_string(stdout, String),
+ erlang:display_string(stdout, "\n")
catch _:_ -> erlang:display(Chardata)
end;
display({report,Report}) when is_map(Report) ->
@@ -190,9 +218,9 @@ display({report,Report}) when is_map(Report) ->
display({report,Report}) ->
display_report(Report);
display({F, A}) when is_list(F), is_list(A) ->
- erlang:display_string(F ++ "\n"),
+ erlang:display_string(stdout, F ++ "\n"),
[begin
- erlang:display_string("\t"),
+ erlang:display_string(stdout, "\t"),
erlang:display(Arg)
end || Arg <- A],
ok.
@@ -203,7 +231,7 @@ display_report(Atom, A) when is_atom(Atom) ->
AtomString = atom_to_list(Atom),
AtomLength = length(AtomString),
Padding = lists:duplicate(ColumnWidth - AtomLength, $\s),
- erlang:display_string(AtomString ++ Padding),
+ erlang:display_string(stdout, AtomString ++ Padding),
display_report(A);
display_report(F, A) ->
erlang:display({F, A}).
@@ -216,13 +244,12 @@ display_report([A, []]) ->
display_report(A = [_|_]) ->
case lists:all(fun({Key,_Value}) -> is_atom(Key); (_) -> false end, A) of
true ->
- erlang:display_string("\n"),
+ erlang:display_string(stdout, "\n"),
lists:foreach(
fun({Key, Value}) ->
erlang:display_string(
- " " ++
- atom_to_list(Key) ++
- ": "),
+ stdout,
+ " " ++ atom_to_list(Key) ++ ": "),
erlang:display(Value)
end, A);
false ->
diff --git a/lib/kernel/src/logger_std_h.erl b/lib/kernel/src/logger_std_h.erl
index 1fe740d5ec..1b2fabad72 100644
--- a/lib/kernel/src/logger_std_h.erl
+++ b/lib/kernel/src/logger_std_h.erl
@@ -507,6 +507,15 @@ ensure_open(Filename, Modes) ->
exit({could_not_create_dir_for_file,Error})
end.
+write_to_dev(Bin,#{dev:=standard_io}=State) ->
+ try
+ io:put_chars(user, Bin)
+ catch _E:_R ->
+ io:put_chars(
+ standard_error, "Failed to write log message to stdout, trying stderr\n"),
+ io:put_chars(standard_error, Bin)
+ end,
+ State;
write_to_dev(Bin,#{dev:=DevName}=State) ->
io:put_chars(DevName, Bin),
State;
diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl
index 2123e3e1bf..a3d983d310 100644
--- a/lib/kernel/src/net_kernel.erl
+++ b/lib/kernel/src/net_kernel.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@
-behaviour(gen_server).
-define(nodedown(N, State), verbose({?MODULE, ?LINE, nodedown, N}, 1, State)).
--define(nodeup(N, State), verbose({?MODULE, ?LINE, nodeup, N}, 1, State)).
+%%-define(nodeup(N, State), verbose({?MODULE, ?LINE, nodeup, N}, 1, State)).
%%-define(dist_debug, true).
@@ -577,6 +577,10 @@ init(#{name := Name,
supervisor := Supervisor,
dist_listen := DistListen,
hidden := Hidden}) ->
+ %% We enable async_dist so that we won't need to do the
+ %% nosuspend/spawn trick which just cause even larger
+ %% memory consumption...
+ _ = process_flag(async_dist, true),
process_flag(trap_exit,true),
persistent_term:put({?MODULE, publish_type},
if Hidden -> hidden;
@@ -794,12 +798,9 @@ handle_call({is_auth, _Node}, From, State) ->
%%
%% Not applicable any longer !?
%%
-handle_call({apply,_Mod,_Fun,_Args}, {From,Tag}, State)
- when is_pid(From), node(From) =:= node() ->
- async_gen_server_reply({From,Tag}, not_implemented),
-% Port = State#state.port,
-% catch apply(Mod,Fun,[Port|Args]),
- {noreply,State};
+handle_call({apply,_Mod,_Fun,_Args}, {Pid, _Tag} = From, State)
+ when is_pid(Pid), node(Pid) =:= node() ->
+ async_reply({reply, not_implemented, State}, From);
handle_call(longnames, From, State) ->
async_reply({reply, get(longnames), State}, From);
@@ -985,7 +986,7 @@ handle_info({auto_connect,Node, DHandle}, State) ->
%%
%% accept a new connection.
%%
-handle_info({accept,AcceptPid,Socket,Family,Proto}, State) ->
+handle_info({accept,AcceptPid,Socket,Family,Proto}=Accept, State) ->
case get_proto_mod(Family,Proto,State#state.listen) of
{ok, Mod} ->
Pid = Mod:accept_connection(AcceptPid,
@@ -993,9 +994,11 @@ handle_info({accept,AcceptPid,Socket,Family,Proto}, State) ->
State#state.node,
State#state.allowed,
State#state.connecttime),
+ verbose({Accept,Pid}, 2, State),
AcceptPid ! {self(), controller, Pid},
{noreply,State};
_ ->
+ verbose({Accept,unsupported_protocol}, 2, State),
AcceptPid ! {self(), unsupported_protocol},
{noreply, State}
end;
@@ -1012,6 +1015,7 @@ handle_info({dist_ctrlr, Ctrlr, Node, SetupPid} = Msg,
andalso (is_port(Ctrlr) orelse is_pid(Ctrlr))
andalso (node(Ctrlr) == node()) ->
link(Ctrlr),
+ verbose(Msg, 2, State),
ets:insert(sys_dist, Conn#connection{ctrlr = Ctrlr}),
{noreply, State#state{dist_ctrlrs = DistCtrlrs#{Ctrlr => Node}}};
_ ->
@@ -1022,7 +1026,7 @@ handle_info({dist_ctrlr, Ctrlr, Node, SetupPid} = Msg,
%%
%% A node has successfully been connected.
%%
-handle_info({SetupPid, {nodeup,Node,Address,Type,NamedMe}},
+handle_info({SetupPid, {nodeup,Node,Address,Type,NamedMe} = Nodeup},
#state{tick = Tick} = State) ->
case ets:lookup(sys_dist, Node) of
[Conn] when (Conn#connection.state =:= pending)
@@ -1043,6 +1047,8 @@ handle_info({SetupPid, {nodeup,Node,Address,Type,NamedMe}},
true -> State#state{node = node()};
false -> State
end,
+ verbose(Nodeup, 1, State1),
+ verbose({nodeup,Node,SetupPid,Conn#connection.ctrlr}, 2, State1),
{noreply, State1};
_ ->
SetupPid ! {self(), bad_request},
@@ -1060,6 +1066,7 @@ handle_info({AcceptPid, {accept_pending,MyNode,NodeOrHost,Type}}, State0) ->
if
MyNode > Node ->
AcceptPid ! {self(),{accept_pending,nok_pending}},
+ verbose({accept_pending_nok, Node, AcceptPid}, 2, State),
{noreply,State};
true ->
%%
@@ -1069,13 +1076,18 @@ handle_info({AcceptPid, {accept_pending,MyNode,NodeOrHost,Type}}, State0) ->
OldOwner = Conn#connection.owner,
case maps:is_key(OldOwner, State#state.conn_owners) of
true ->
+ verbose({remark,OldOwner,AcceptPid}, 2, State),
?debug({net_kernel, remark, old, OldOwner, new, AcceptPid}),
exit(OldOwner, remarked),
receive
- {'EXIT', OldOwner, _} ->
+ {'EXIT', OldOwner, _} = Exit ->
+ verbose(Exit, 2, State),
true
end;
false ->
+ verbose(
+ {accept_pending, OldOwner, inconsistency},
+ 2, State),
ok % Owner already exited
end,
ets:insert(sys_dist, Conn#connection{owner = AcceptPid}),
@@ -1157,7 +1169,6 @@ handle_info({'DOWN', ReqId, process, _Pid, Reason},
%% Handle different types of process terminations.
%%
handle_info({'EXIT', From, Reason}, State) ->
- verbose({'EXIT', From, Reason}, 1, State),
handle_exit(From, Reason, State);
%%
@@ -1271,30 +1282,33 @@ handle_exit(Pid, Reason, State) ->
catch do_handle_exit(Pid, Reason, State).
do_handle_exit(Pid, Reason, State) ->
- listen_exit(Pid, State),
- accept_exit(Pid, State),
+ listen_exit(Pid, Reason, State),
+ accept_exit(Pid, Reason, State),
conn_own_exit(Pid, Reason, State),
dist_ctrlr_exit(Pid, Reason, State),
- pending_own_exit(Pid, State),
- ticker_exit(Pid, State),
- restarter_exit(Pid, State),
+ pending_own_exit(Pid, Reason, State),
+ ticker_exit(Pid, Reason, State),
+ restarter_exit(Pid, Reason, State),
+ verbose({'EXIT', Pid, Reason}, 2, State),
{noreply,State}.
-listen_exit(Pid, State) ->
+listen_exit(Pid, Reason, State) ->
case lists:keymember(Pid, ?LISTEN_ID, State#state.listen) of
true ->
+ verbose({listen_exit, Pid, Reason}, 2, State),
error_msg("** Netkernel terminating ... **\n", []),
throw({stop,no_network,State});
false ->
false
end.
-accept_exit(Pid, State) ->
+accept_exit(Pid, Reason, State) ->
Listen = State#state.listen,
case lists:keysearch(Pid, ?ACCEPT_ID, Listen) of
{value, ListenR} ->
ListenS = ListenR#listen.listen,
Mod = ListenR#listen.module,
+ verbose({accept_exit, Pid, Reason, Mod}, 2, State),
AcceptPid = Mod:accept(ListenS),
L = lists:keyreplace(Pid, ?ACCEPT_ID, Listen,
ListenR#listen{accept = AcceptPid}),
@@ -1306,16 +1320,20 @@ accept_exit(Pid, State) ->
conn_own_exit(Pid, Reason, #state{conn_owners = Owners} = State) ->
case maps:get(Pid, Owners, undefined) of
undefined -> false;
- Node -> throw({noreply, nodedown(Pid, Node, Reason, State)})
+ Node ->
+ verbose({conn_own_exit, Pid, Reason, Node}, 2, State),
+ throw({noreply, nodedown(Pid, Node, Reason, State)})
end.
dist_ctrlr_exit(Pid, Reason, #state{dist_ctrlrs = DCs} = State) ->
case maps:get(Pid, DCs, undefined) of
undefined -> false;
- Node -> throw({noreply, nodedown(Pid, Node, Reason, State)})
+ Node ->
+ verbose({dist_ctrlr_exit, Pid, Reason, Node}, 2, State),
+ throw({noreply, nodedown(Pid, Node, Reason, State)})
end.
-pending_own_exit(Pid, #state{pend_owners = Pend} = State) ->
+pending_own_exit(Pid, Reason, #state{pend_owners = Pend} = State) ->
case maps:get(Pid, Pend, undefined) of
undefined ->
false;
@@ -1323,31 +1341,43 @@ pending_own_exit(Pid, #state{pend_owners = Pend} = State) ->
State1 = State#state { pend_owners = maps:remove(Pid, Pend)},
case get_conn(Node) of
{ok, Conn} when Conn#connection.state =:= up_pending ->
+ verbose(
+ {pending_own_exit, Pid, Reason, Node, up_pending},
+ 2, State),
reply_waiting(Node,Conn#connection.waiting, true),
Conn1 = Conn#connection { state = up,
waiting = [],
pending_owner = undefined },
ets:insert(sys_dist, Conn1);
_ ->
+ verbose({pending_own_exit, Pid, Reason, Node}, 2, State),
ok
end,
throw({noreply, State1})
end.
-ticker_exit(Pid, #state{tick = #tick{ticker = Pid, time = T} = Tck} = State) ->
+ticker_exit(
+ Pid, Reason,
+ #state{tick = #tick{ticker = Pid, time = T} = Tck} = State) ->
+ verbose({ticker_exit, Pid, Reason, Tck}, 2, State),
Tckr = restart_ticker(T),
throw({noreply, State#state{tick = Tck#tick{ticker = Tckr}}});
-ticker_exit(Pid, #state{tick = #tick_change{ticker = Pid,
- time = T} = TckCng} = State) ->
+ticker_exit(
+ Pid, Reason,
+ #state{tick = #tick_change{ticker = Pid, time = T} = TckCng} = State) ->
+ verbose({ticker_exit, Pid, Reason, TckCng}, 2, State),
Tckr = restart_ticker(T),
- throw({noreply, State#state{tick = TckCng#tick_change{ticker = Tckr}}});
-ticker_exit(_, _) ->
+ throw({noreply, Reason, State#state{tick = TckCng#tick_change{ticker = Tckr}}});
+ticker_exit(_, _, _) ->
false.
-restarter_exit(Pid, State) ->
+restarter_exit(Pid, Reason, State) ->
case State#state.supervisor of
{restart, Pid} ->
- error_msg("** Distribution restart failed, net_kernel terminating... **\n", []),
+ verbose({restarter_exit, Pid, Reason}, 2, State),
+ error_msg(
+ "** Distribution restart failed, net_kernel terminating... **\n",
+ []),
throw({stop, restarter_exit, State});
_ ->
false
@@ -1783,6 +1813,9 @@ setup(Node, ConnId, Type, From, State) ->
MyNode,
State#state.type,
State#state.connecttime),
+ verbose(
+ {setup,Node,Type,MyNode,State#state.type,Pid},
+ 2, State),
Addr = LAddr#net_address {
address = undefined,
host = undefined },
@@ -2026,7 +2059,7 @@ start_protos(Node, Ps, CleanHalt, Listen) ->
end.
start_protos_no_listen(Node, [Proto | Ps], Ls, CleanHalt) ->
- {Name, "@"++_Host} = split_node(Node),
+ {Name, "@"++Host} = split_node(Node),
Ok = case Name of
"undefined" ->
erts_internal:dynamic_node_name(true),
@@ -2038,9 +2071,14 @@ start_protos_no_listen(Node, [Proto | Ps], Ls, CleanHalt) ->
true ->
auth:sync_cookie(),
Mod = list_to_atom(Proto ++ "_dist"),
+ Address =
+ try Mod:address(Host)
+ catch error:undef ->
+ Mod:address()
+ end,
L = #listen {
listen = undefined,
- address = Mod:address(),
+ address = Address,
accept = undefined,
module = Mod },
start_protos_no_listen(Node, Ps, [L|Ls], CleanHalt);
@@ -2124,7 +2162,7 @@ register_error(false, Proto, Reason) ->
proto_error(false, Proto, lists:flatten(S));
register_error(true, Proto, Reason) ->
S = "Protocol '" ++ Proto ++ "': register/listen error: ",
- erlang:display_string(S),
+ erlang:display_string(stdout, S),
erlang:display(Reason).
proto_error(CleanHalt, Proto, String) ->
@@ -2248,7 +2286,7 @@ reply_waiting(_Node, Waiting, Rep) ->
reply_waiting1(lists:reverse(Waiting), Rep).
reply_waiting1([From|W], Rep) ->
- async_gen_server_reply(From, Rep),
+ gen_server:reply(From, Rep),
reply_waiting1(W, Rep);
reply_waiting1([], _) ->
ok.
@@ -2354,24 +2392,23 @@ return_call({noreply, _State}=R, _From) ->
return_call(R, From) ->
async_reply(R, From).
-async_reply({reply, Msg, State}, From) ->
- async_gen_server_reply(From, Msg),
- {noreply, State}.
-
-async_gen_server_reply(From, Msg) ->
- {Pid, Tag} = From,
- M = {Tag, Msg},
- try erlang:send(Pid, M, [nosuspend, noconnect]) of
- ok ->
- ok;
- nosuspend ->
- _ = spawn(fun() -> catch erlang:send(Pid, M, [noconnect]) end),
- ok;
- noconnect ->
- ok % The gen module takes care of this case.
- catch
- _:_ -> ok
- end.
+-compile({inline, [async_reply/2]}).
+async_reply({reply, _Msg, _State} = Res, _From) ->
+ %% This function call is kept in order to not unnecessarily create a huge diff
+ %% in the code.
+ %%
+ %% Here we used to send the reply explicitly using 'noconnect' and 'nosuspend'.
+ %%
+ %% * 'noconnect' since setting up a connection from net_kernel itself would
+ %% deadlock when connects were synchronous. Since connects nowadays are
+ %% asynchronous this is no longer an issue.
+ %% * 'nosuspend' and spawn a process taking care of the reply in case
+ %% we would have suspended. This in order not to block net_kernel. We now
+ %% use 'async_dist' enabled and by this prevent the blocking, keep the
+ %% signal order, avoid one extra copying of the reply, avoid the overhead
+ %% of creating a process, and avoid the extra memory consumption due to the
+ %% extra process.
+ Res.
handle_async_response(ResponseType, ReqId, Result, #state{req_map = ReqMap0} = S0) ->
if ResponseType == down -> ok;
@@ -2385,20 +2422,19 @@ handle_async_response(ResponseType, ReqId, Result, #state{req_map = ReqMap0} = S
reply -> Result;
down -> {error, noconnection}
end,
- S1 = S0#state{req_map = ReqMap1},
- async_reply({reply, Reply, S1}, From);
+ gen_server:reply(From, Reply),
+ {noreply, S0#state{req_map = ReqMap1}};
{{setopts_new, Op}, ReqMap1} ->
case maps:get(Op, ReqMap1) of
{setopts_new, From, 1} ->
%% Last response for this operation...
+ gen_server:reply(From, ok),
ReqMap2 = maps:remove(Op, ReqMap1),
- S1 = S0#state{req_map = ReqMap2},
- async_reply({reply, ok, S1}, From);
+ {noreply, S0#state{req_map = ReqMap2}};
{setopts_new, From, N} ->
ReqMap2 = ReqMap1#{Op => {setopts_new, From, N-1}},
- S1 = S0#state{req_map = ReqMap2},
- {noreply, S1}
+ {noreply, S0#state{req_map = ReqMap2}}
end
end.
diff --git a/lib/kernel/src/pg.erl b/lib/kernel/src/pg.erl
index 017b1942b4..8204576ba7 100644
--- a/lib/kernel/src/pg.erl
+++ b/lib/kernel/src/pg.erl
@@ -204,10 +204,10 @@ get_members(Group) ->
-spec get_members(Scope :: atom(), Group :: group()) -> [pid()].
get_members(Scope, Group) ->
try
- ets:lookup_element(Scope, Group, 2)
+ ets:lookup_element(Scope, Group, 2, [])
catch
- error:badarg ->
- []
+ %% Case where the table does not exist yet.
+ error:badarg -> []
end.
%%--------------------------------------------------------------------
@@ -220,10 +220,10 @@ get_local_members(Group) ->
-spec get_local_members(Scope :: atom(), Group :: group()) -> [pid()].
get_local_members(Scope, Group) ->
try
- ets:lookup_element(Scope, Group, 3)
+ ets:lookup_element(Scope, Group, 3, [])
catch
- error:badarg ->
- []
+ %% Case where the table does not exist yet.
+ error:badarg -> []
end.
%%--------------------------------------------------------------------
@@ -305,7 +305,7 @@ handle_call({leave_local, Group, PidOrPids}, _From, #state{scope = Scope, local
handle_call(monitor, {Pid, _Tag}, #state{scope = Scope, scope_monitors = ScopeMon} = State) ->
%% next line could also be done with iterating over process state, but it appears to be slower
- Local = maps:from_list([{G,P} || [G,P] <- ets:match(Scope, {'$1', '$2', '_'})]),
+ Local = #{G => P || [G,P] <- ets:match(Scope, {'$1', '$2', '_'})},
MRef = erlang:monitor(process, Pid), %% monitor the monitor, to discard it upon termination, and generate MRef
{reply, {MRef, Local}, State#state{scope_monitors = ScopeMon#{MRef => Pid}}};
@@ -339,10 +339,7 @@ handle_call(_Request, _From, _S) ->
erlang:error(badarg).
-spec handle_cast(
- {sync, Peer :: pid(), Groups :: [{group(), [pid()]}]} |
- {discover, Peer :: pid()} |
- {join, Peer :: pid(), group(), pid() | [pid()]} |
- {leave, Peer :: pid(), pid() | [pid()], [group()]},
+ {sync, Peer :: pid(), Groups :: [{group(), [pid()]}]},
State :: state()) -> {noreply, state()}.
handle_cast({sync, Peer, Groups}, #state{scope = Scope, remote = Remote, scope_monitors = ScopeMon,
@@ -354,6 +351,7 @@ handle_cast(_, _State) ->
-spec handle_info(
{discover, Peer :: pid()} |
+ {discover, Peer :: pid(), any()} |
{join, Peer :: pid(), group(), pid() | [pid()]} |
{leave, Peer :: pid(), pid() | [pid()], [group()]} |
{'DOWN', reference(), process, pid(), term()} |
@@ -395,17 +393,13 @@ handle_info({leave, Peer, PidOrPids, Groups}, #state{scope = Scope, remote = Rem
end;
%% we're being discovered, let's exchange!
-handle_info({discover, Peer}, #state{remote = Remote, local = Local} = State) ->
- gen_server:cast(Peer, {sync, self(), all_local_pids(Local)}),
- %% do we know who is looking for us?
- case maps:is_key(Peer, Remote) of
- true ->
- {noreply, State};
- false ->
- MRef = erlang:monitor(process, Peer),
- erlang:send(Peer, {discover, self()}, [noconnect]),
- {noreply, State#state{remote = Remote#{Peer => {MRef, #{}}}}}
- end;
+handle_info({discover, Peer}, State) ->
+ handle_discover(Peer, State);
+
+%% New discover message sent by a future pg version.
+%% Accepted first in OTP 26, to be used by OTP 28 or later.
+handle_info({discover, Peer, _ProtocolVersion}, State) ->
+ handle_discover(Peer, State);
%% handle local process exit, or a local monitor exit
handle_info({'DOWN', MRef, process, Pid, _Info}, #state{scope = Scope, local = Local,
@@ -440,7 +434,7 @@ handle_info({nodedown, _Node}, State) ->
handle_info({nodeup, Node}, State) when Node =:= node() ->
{noreply, State};
handle_info({nodeup, Node}, #state{scope = Scope} = State) ->
- {Scope, Node} ! {discover, self()},
+ erlang:send({Scope, Node}, {discover, self()}, [noconnect]),
{noreply, State};
handle_info(_Info, _State) ->
@@ -453,6 +447,21 @@ terminate(_Reason, #state{scope = Scope}) ->
%%--------------------------------------------------------------------
%% Internal implementation
+handle_discover(Peer, #state{remote = Remote, local = Local} = State) ->
+ gen_server:cast(Peer, {sync, self(), all_local_pids(Local)}),
+ %% do we know who is looking for us?
+ case maps:is_key(Peer, Remote) of
+ true ->
+ {noreply, State};
+ false ->
+ MRef = erlang:monitor(process, Peer),
+ erlang:send(Peer, {discover, self()}, [noconnect]),
+ {noreply, State#state{remote = Remote#{Peer => {MRef, #{}}}}}
+ end;
+handle_discover(_, _) ->
+ erlang:error(badarg).
+
+
%% Ensures argument is either a node-local pid or a list of such, or it throws an error
ensure_local(Pid) when is_pid(Pid), node(Pid) =:= node() ->
ok;
@@ -492,7 +501,7 @@ sync_groups(Scope, ScopeMon, MG, RemoteGroups, [{Group, Pids} | Tail]) ->
{Pids, NewRemoteGroups} ->
sync_groups(Scope, ScopeMon, MG, NewRemoteGroups, Tail);
{OldPids, NewRemoteGroups} ->
- [{Group, AllOldPids, LocalPids}] = ets:lookup(Scope, Group),
+ [{_Group, AllOldPids, LocalPids}] = ets:lookup(Scope, Group),
%% should be really rare...
AllNewPids = Pids ++ AllOldPids -- OldPids,
true = ets:insert(Scope, {Group, AllNewPids, LocalPids}),
@@ -517,7 +526,7 @@ join_local([Pid | Tail], Group, Local) ->
join_local_update_ets(Scope, ScopeMon, MG, Group, Pid) when is_pid(Pid) ->
case ets:lookup(Scope, Group) of
- [{Group, All, Local}] ->
+ [{_Group, All, Local}] ->
ets:insert(Scope, {Group, [Pid | All], [Pid | Local]});
[] ->
ets:insert(Scope, {Group, [Pid], [Pid]})
@@ -525,7 +534,7 @@ join_local_update_ets(Scope, ScopeMon, MG, Group, Pid) when is_pid(Pid) ->
notify_group(ScopeMon, MG, join, Group, [Pid]);
join_local_update_ets(Scope, ScopeMon, MG, Group, Pids) ->
case ets:lookup(Scope, Group) of
- [{Group, All, Local}] ->
+ [{_Group, All, Local}] ->
ets:insert(Scope, {Group, Pids ++ All, Pids ++ Local});
[] ->
ets:insert(Scope, {Group, Pids, Pids})
@@ -534,7 +543,7 @@ join_local_update_ets(Scope, ScopeMon, MG, Group, Pids) ->
join_remote_update_ets(Scope, ScopeMon, MG, Group, Pid) when is_pid(Pid) ->
case ets:lookup(Scope, Group) of
- [{Group, All, Local}] ->
+ [{_Group, All, Local}] ->
ets:insert(Scope, {Group, [Pid | All], Local});
[] ->
ets:insert(Scope, {Group, [Pid], []})
@@ -542,7 +551,7 @@ join_remote_update_ets(Scope, ScopeMon, MG, Group, Pid) when is_pid(Pid) ->
notify_group(ScopeMon, MG, join, Group, [Pid]);
join_remote_update_ets(Scope, ScopeMon, MG, Group, Pids) ->
case ets:lookup(Scope, Group) of
- [{Group, All, Local}] ->
+ [{_Group, All, Local}] ->
ets:insert(Scope, {Group, Pids ++ All, Local});
[] ->
ets:insert(Scope, {Group, Pids, []})
@@ -576,10 +585,10 @@ leave_local([Pid | Tail], Group, Local) ->
leave_local_update_ets(Scope, ScopeMon, MG, Group, Pid) when is_pid(Pid) ->
case ets:lookup(Scope, Group) of
- [{Group, [Pid], [Pid]}] ->
+ [{_Group, [Pid], [Pid]}] ->
ets:delete(Scope, Group),
notify_group(ScopeMon, MG, leave, Group, [Pid]);
- [{Group, All, Local}] ->
+ [{_Group, All, Local}] ->
ets:insert(Scope, {Group, lists:delete(Pid, All), lists:delete(Pid, Local)}),
notify_group(ScopeMon, MG, leave, Group, [Pid]);
[] ->
@@ -588,7 +597,7 @@ leave_local_update_ets(Scope, ScopeMon, MG, Group, Pid) when is_pid(Pid) ->
end;
leave_local_update_ets(Scope, ScopeMon, MG, Group, Pids) ->
case ets:lookup(Scope, Group) of
- [{Group, All, Local}] ->
+ [{_Group, All, Local}] ->
case All -- Pids of
[] ->
ets:delete(Scope, Group);
@@ -603,10 +612,10 @@ leave_local_update_ets(Scope, ScopeMon, MG, Group, Pids) ->
leave_remote_update_ets(Scope, ScopeMon, MG, Pid, Groups) when is_pid(Pid) ->
_ = [
case ets:lookup(Scope, Group) of
- [{Group, [Pid], []}] ->
+ [{_Group, [Pid], []}] ->
ets:delete(Scope, Group),
notify_group(ScopeMon, MG, leave, Group, [Pid]);
- [{Group, All, Local}] ->
+ [{_Group, All, Local}] ->
ets:insert(Scope, {Group, lists:delete(Pid, All), Local}),
notify_group(ScopeMon, MG, leave, Group, [Pid]);
[] ->
@@ -616,7 +625,7 @@ leave_remote_update_ets(Scope, ScopeMon, MG, Pid, Groups) when is_pid(Pid) ->
leave_remote_update_ets(Scope, ScopeMon, MG, Pids, Groups) ->
_ = [
case ets:lookup(Scope, Group) of
- [{Group, All, Local}] ->
+ [{_Group, All, Local}] ->
case All -- Pids of
[] when Local =:= [] ->
ets:delete(Scope, Group);
diff --git a/lib/kernel/src/prim_tty.erl b/lib/kernel/src/prim_tty.erl
new file mode 100644
index 0000000000..a03e746cc9
--- /dev/null
+++ b/lib/kernel/src/prim_tty.erl
@@ -0,0 +1,1265 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(prim_tty).
+
+%% Todo:
+%% * Try to move buffer handling logic to Erlang
+%% * This may not be possible for performance reasons, but should be tried
+%% * It seems like unix decodes and then encodes utf8 when emitting it
+%% * user_drv module should be able to handle both nif and driver without too many changes.
+%%
+%% Problems to solve:
+%% * Do not use non blocking io
+%% * Reset tty settings at _exit
+%% * Allow remsh in oldshell (can we do this?)
+%% * See if we can run a tty in windows shell
+%% * Allow unicode detection for noshell/noinput
+%% * ?Allow multi-line editing?
+%% * The current implementation only allows the cursor to move and edit on current line
+%%
+%% Concepts to keep in mind:
+%% Code point: A single unicode "thing", examples: "a", "😀" (unicode smilie)
+%% Grapheme cluster: One or more code points, "
+%% Logical character: Any character that the user typed or printed.
+%% One unicode grapheme cluster is a logical character
+%% Examples: "a", "\t", "😀" (unicode smilie), "\x{1F600}", "\e[0m" (ansi sequences),
+%% "^C"
+%% When we step or delete we count logical characters even if they are multiple chars.
+%% (I'm unsure how ansi should be handled with regard to delete?)
+%%
+%% Actual characters: The actual unicode grapheme clusters printed
+%% Column: The number of rendered columns for a logical character
+%%
+%% When navigating using move(left) and move(right) the terminal will move one
+%% actual character, so if we want to move one logical character we may have to
+%% emit more moves. The same is true when overwriting.
+%%
+%% When calculating the current column position we have to use the column size
+%% of the characters as otherwise smilies will becomes incorrect.
+%%
+%% In the current ttysl_drv and also this implementation there are never any newlines
+%% in the buffer.
+%%
+%% Printing of unicode characters:
+%% Read this post: https://jeffquast.com/post/terminal_wcwidth_solution/
+%% Summary: the wcwidth implementation in libc is often lacking. So we should
+%% create our own. We can get the size of all unicode graphemes by rendering
+%% them on a terminal and see how much the cursor moves. We can query where the
+%% cursor is by using "\033[6n"[1]. How many valid grapheme clusters are there?
+%%
+%% On consoles that does support fetching the surrent cursor position, we may get
+%% away with only using that to check where we are and where to go. And on consoles
+%% that do not we just have to make a best effort and use libc wcwidth.
+%%
+%% We need to know the width of characters when:
+%% * Printing a \t
+%% * Compensating for xn
+%% [1]: https://www.linuxquestions.org/questions/programming-9/get-cursor-position-in-c-947833/
+%% Notes:
+%% - [129306,127996] (hand with skintone) seems to move the cursor more than
+%% it should on iTerm...The skintone code point seems to not
+%% work at all on Linux and instead emits hand + brown square which is 4 characters
+%% wide which is what the cursor on iTerm was positioned at. So maybe
+%% there is a mismatch somewhere in iTerm wcwidth handling.
+%% - edlin and user needs to agree on what one "character" is, right now edlin uses
+%% code points and not grapheme clusters.
+%% - We can deal with xn by always emitting it after a put_chars command. We must make
+%% sure not to emit it before we emit any newline in the put_chars though as that
+%% potentially will insert two newlines instead of one.
+%%
+%% Windows:
+%% Since 2017:ish we can use Virtual Terminal Sequences[2] to control the terminal.
+%% It seems like these are mostly ANSI escape comparitible, so it should be possible
+%% to just use the same mechanism on windows and unix.
+%% [2]: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
+%% Windows does not seem to have any wcwidth, so maybe we have to generate a similar table
+%% as the PR in [3] and add string:width/1.
+%% [3]: https://github.com/microsoft/terminal/pull/5795
+%%
+%%
+%% Things I've tried and discarded:
+%% * Use get position to figure out xn fix for newlines
+%% * There is too large a latency (about 10ms) to get the position, so things like
+%% `c:i()` becomes a lot slower.
+%% * Use tty insert mode
+%% * This only works when the cursor is on the last line, when it is on a previous
+%% line it only edit that line.
+%% * Use tty delete mode
+%% * Same problem as insert mode, it only deleted current line, and does not move
+%% to previous line automatically.
+
+-export([init/1, reinit/2, isatty/1, handles/1, unicode/1, unicode/2,
+ handle_signal/2, window_size/1, handle_request/2, write/2, write/3, npwcwidth/1,
+ npwcwidthstring/1]).
+-export([reader_stop/1, disable_reader/1, enable_reader/1]).
+
+-nifs([isatty/1, tty_create/0, tty_init/3, tty_set/1, setlocale/1,
+ tty_select/3, tty_window_size/1, write_nif/2, read_nif/2, isprint/1,
+ wcwidth/1, wcswidth/1,
+ sizeof_wchar/0, tgetent_nif/1, tgetnum_nif/1, tgetflag_nif/1, tgetstr_nif/1,
+ tgoto_nif/2, tgoto_nif/3, tty_read_signal/2]).
+
+%% Exported in order to remove "unused function" warning
+-export([sizeof_wchar/0, wcswidth/1, tgoto/2, tgoto/3]).
+
+%% proc_lib exports
+-export([reader/1, writer/1]).
+
+-on_load(on_load/0).
+
+%%-define(debug, true).
+-ifdef(debug).
+-define(dbg(Term), dbg(Term)).
+-else.
+-define(dbg(Term), ok).
+-endif.
+%% Copied from https://github.com/chalk/ansi-regex/blob/main/index.js
+-define(ANSI_REGEXP, <<"^[\e",194,155,"][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?",7,")|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))">>).
+-record(state, {tty,
+ reader,
+ writer,
+ options,
+ unicode,
+ lines_before = [], %% All lines before the current line in reverse order
+ lines_after = [], %% All lines after the current line.
+ buffer_before = [], %% Current line before cursor in reverse
+ buffer_after = [], %% Current line after cursor not in reverse
+ buffer_expand, %% Characters in expand buffer
+ cols = 80,
+ rows = 24,
+ xn = false,
+ clear = <<"\e[H\e[2J">>,
+ up = <<"\e[A">>,
+ down = <<"\n">>,
+ left = <<"\b">>,
+ right = <<"\e[C">>,
+ %% Tab to next 8 column windows is "\e[1I", for unix "ta" termcap
+ tab = <<"\e[1I">>,
+ delete_after_cursor = <<"\e[J">>,
+ insert = false,
+ delete = false,
+ position = <<"\e[6n">>, %% "u7" on my Linux
+ position_reply = <<"\e\\[([0-9]+);([0-9]+)R">>,
+ ansi_regexp
+ }).
+
+-type options() :: #{ tty => boolean(),
+ input => boolean(),
+ canon => boolean(),
+ echo => boolean(),
+ sig => boolean()
+ }.
+-type request() ::
+ {putc_raw, binary()} |
+ {putc, unicode:unicode_binary()} |
+ {putc_keep_state, unicode:unicode_binary()} |
+ {expand, unicode:unicode_binary()} |
+ {expand_with_trim, unicode:unicode_binary()} |
+ {insert, unicode:unicode_binary()} |
+ {delete, integer()} |
+ delete_after_cursor |
+ delete_line |
+ redraw_prompt |
+ {redraw_prompt, string(), string(), tuple()} |
+ redraw_prompt_pre_deleted |
+ new_prompt |
+ {move, integer()} |
+ {move_line, integer()} |
+ {move_combo, integer(), integer(), integer()} |
+ clear |
+ beep.
+-opaque state() :: #state{}.
+-export_type([state/0]).
+
+-spec on_load() -> ok.
+on_load() ->
+ on_load(#{}).
+
+-spec on_load(Extra) -> ok when
+ Extra :: map().
+on_load(Extra) ->
+ case erlang:load_nif(atom_to_list(?MODULE), Extra) of
+ ok -> ok;
+ {error,{reload,_}} ->
+ ok
+ end.
+
+-spec window_size(state()) -> {ok, {non_neg_integer(), non_neg_integer()}} | {error, term()}.
+window_size(State = #state{ tty = TTY }) ->
+ case tty_window_size(TTY) of
+ {error, enotsup} when map_get(tty, State#state.options) ->
+ %% When the TTY is enabled, we should return a "dummy" row and column
+ %% when we cannot find the proper size.
+ {ok, {State#state.cols, State#state.rows}};
+ WinSz ->
+ WinSz
+ end.
+
+-spec init(options()) -> state().
+init(UserOptions) when is_map(UserOptions) ->
+
+ Options = options(UserOptions),
+ {ok, TTY} = tty_create(),
+
+ %% Initialize the locale to see if we support utf-8 or not
+ UnicodeMode =
+ case setlocale(TTY) of
+ primitive ->
+ lists:any(
+ fun(Key) ->
+ string:find(os:getenv(Key,""),"UTF-8") =/= nomatch
+ end, ["LC_ALL", "LC_CTYPE", "LANG"]);
+ UnicodeLocale when is_boolean(UnicodeLocale) ->
+ UnicodeLocale
+ end,
+ {ok, ANSI_RE_MP} = re:compile(?ANSI_REGEXP, [unicode]),
+ init_term(#state{ tty = TTY, unicode = UnicodeMode, options = Options, ansi_regexp = ANSI_RE_MP }).
+init_term(State = #state{ tty = TTY, options = Options }) ->
+ TTYState =
+ case maps:get(tty, Options) of
+ true ->
+ ok = tty_init(TTY, stdout, Options),
+ NewState = init(State, os:type()),
+ ok = tty_set(TTY),
+ NewState;
+ false ->
+ State
+ end,
+
+ WriterState =
+ if TTYState#state.writer =:= undefined ->
+ {ok, Writer} = proc_lib:start_link(?MODULE, writer, [State#state.tty]),
+ TTYState#state{ writer = Writer };
+ true ->
+ TTYState
+ end,
+ ReaderState =
+ case {maps:get(input, Options), TTYState#state.reader} of
+ {true, undefined} ->
+ {ok, Reader} = proc_lib:start_link(?MODULE, reader, [[State#state.tty, self()]]),
+ WriterState#state{ reader = Reader };
+ {true, _} ->
+ WriterState;
+ {false, undefined} ->
+ WriterState
+ end,
+
+ update_geometry(ReaderState).
+
+-spec reinit(state(), options()) -> state().
+reinit(State, UserOptions) ->
+ init_term(State#state{ options = options(UserOptions) }).
+
+options(UserOptions) ->
+ maps:merge(
+ #{ input => true,
+ tty => true,
+ canon => false,
+ echo => false }, UserOptions).
+
+init(State, {unix,_}) ->
+
+ case os:getenv("TERM") of
+ false ->
+ error(enotsup);
+ Term ->
+ case tgetent(Term) of
+ ok -> ok;
+ {error,_} -> error(enotsup)
+ end
+ end,
+
+ %% See https://www.gnu.org/software/termutils/manual/termcap-1.3/html_mono/termcap.html#SEC23
+ %% for a list of all possible termcap capabilities
+ Clear = case tgetstr("clear") of
+ {ok, C} -> C;
+ false -> (#state{})#state.clear
+ end,
+ Cols = case tgetnum("co") of
+ {ok, Cs} -> Cs;
+ _ -> (#state{})#state.cols
+ end,
+ Up = case tgetstr("up") of
+ {ok, U} -> U;
+ false -> error(enotsup)
+ end,
+ Down = case tgetstr("do") of
+ false -> (#state{})#state.down;
+ {ok, D} -> D
+ end,
+ Left = case {tgetflag("bs"),tgetstr("bc")} of
+ {true,_} -> (#state{})#state.left;
+ {_,false} -> (#state{})#state.left;
+ {_,{ok, L}} -> L
+ end,
+
+ Right = case tgetstr("nd") of
+ {ok, R} -> R;
+ false -> error(enotsup)
+ end,
+ Insert =
+ case tgetstr("IC") of
+ {ok, IC} -> IC;
+ false -> (#state{})#state.insert
+ end,
+
+ Tab = case tgetstr("ta") of
+ {ok, TA} -> TA;
+ false -> (#state{})#state.tab
+ end,
+
+ Delete = case tgetstr("DC") of
+ {ok, DC} -> DC;
+ false -> (#state{})#state.delete
+ end,
+
+ Position = case tgetstr("u7") of
+ {ok, <<"\e[6n">> = U7} ->
+ %% User 7 should contain the codes for getting
+ %% cursor position.
+ %% User 6 should contain how to parse the reply
+ {ok, <<"\e[%i%d;%dR">>} = tgetstr("u6"),
+ <<"\e[6n">> = U7;
+ false -> (#state{})#state.position
+ end,
+
+ %% According to the manual this should only be issued when the cursor
+ %% is at position 0, but until we encounter such a console we keep things
+ %% simple and issue this with the cursor anywhere
+ DeleteAfter = case tgetstr("cd") of
+ {ok, DA} ->
+ DA;
+ false ->
+ (#state{})#state.delete_after_cursor
+ end,
+
+ State#state{
+ cols = Cols,
+ clear = Clear,
+ xn = tgetflag("xn"),
+ up = Up,
+ down = Down,
+ left = Left,
+ right = Right,
+ insert = Insert,
+ delete = Delete,
+ tab = Tab,
+ position = Position,
+ delete_after_cursor = DeleteAfter
+ };
+init(State, {win32, _}) ->
+ State#state{
+ %% position = false,
+ xn = true }.
+
+-spec handles(state()) -> #{ read := undefined | reference(),
+ write := reference() }.
+handles(#state{ reader = undefined,
+ writer = {_WriterPid, WriterRef}}) ->
+ #{ read => undefined, write => WriterRef };
+handles(#state{ reader = {_ReaderPid, ReaderRef},
+ writer = {_WriterPid, WriterRef}}) ->
+ #{ read => ReaderRef, write => WriterRef }.
+
+-spec unicode(state()) -> boolean().
+unicode(State) ->
+ State#state.unicode.
+
+-spec unicode(state(), boolean()) -> state().
+unicode(#state{ reader = Reader } = State, Bool) ->
+ case Reader of
+ {ReaderPid, _} ->
+ call(ReaderPid, {set_unicode_state, Bool});
+ undefined ->
+ ok
+ end,
+ State#state{ unicode = Bool }.
+
+-spec reader_stop(state()) -> state().
+reader_stop(#state{ reader = {ReaderPid, _} } = State) ->
+ {error, _} = call(ReaderPid, stop),
+ State#state{ reader = undefined }.
+
+-spec handle_signal(state(), winch | cont) -> state().
+handle_signal(State, winch) ->
+ update_geometry(State);
+handle_signal(State, cont) ->
+ tty_set(State#state.tty),
+ State.
+
+-spec disable_reader(state()) -> ok.
+disable_reader(#state{ reader = {ReaderPid, _} }) ->
+ ok = call(ReaderPid, disable).
+
+-spec enable_reader(state()) -> ok.
+enable_reader(#state{ reader = {ReaderPid, _} }) ->
+ ok = call(ReaderPid, enable).
+
+call(Pid, Msg) ->
+ Alias = erlang:monitor(process, Pid, [{alias, reply_demonitor}]),
+ Pid ! {Alias, Msg},
+ receive
+ {Alias, Reply} ->
+ Reply;
+ {'DOWN',Alias,_,_,Reason} ->
+ {error, Reason}
+ end.
+
+reader([TTY, Parent]) ->
+ register(user_drv_reader, self()),
+ ReaderRef = make_ref(),
+ SignalRef = make_ref(),
+ ok = tty_select(TTY, SignalRef, ReaderRef),
+ proc_lib:init_ack({ok, {self(), ReaderRef}}),
+ FromEnc = case os:type() of
+ {unix, _} -> utf8;
+ {win32, _} ->
+ case isatty(stdin) of
+ true ->
+ {utf16, little};
+ _ ->
+ %% When not reading from a console
+ %% the data read is utf8 encoded
+ utf8
+ end
+ end,
+ reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, <<>>).
+
+reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, Acc) ->
+ receive
+ {DisableAlias, disable} ->
+ DisableAlias ! {DisableAlias, ok},
+ receive
+ {EnableAlias, enable} ->
+ EnableAlias ! {EnableAlias, ok},
+ reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, Acc)
+ end;
+ {select, TTY, SignalRef, ready_input} ->
+ {ok, Signal} = tty_read_signal(TTY, SignalRef),
+ Parent ! {ReaderRef,{signal,Signal}},
+ reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, Acc);
+ {Alias, {set_unicode_state, _}} when FromEnc =:= {utf16, little} ->
+ %% Ignore requests on windows
+ Alias ! {Alias, true},
+ reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, Acc);
+ {Alias, {set_unicode_state, Bool}} ->
+ Alias ! {Alias, FromEnc =/= latin1},
+ NewFromEnc = if Bool -> utf8; not Bool -> latin1 end,
+ reader_loop(TTY, Parent, SignalRef, ReaderRef, NewFromEnc, Acc);
+ {_Alias, stop} ->
+ ok;
+ {select, TTY, ReaderRef, ready_input} ->
+ case read_nif(TTY, ReaderRef) of
+ {error, closed} ->
+ Parent ! {ReaderRef, eof},
+ ok;
+ {ok, <<>>} ->
+ %% EAGAIN or EINTR
+ reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, Acc);
+ {ok, UtfXBytes} ->
+
+ {Bytes, NewAcc, NewFromEnc} =
+ case unicode:characters_to_binary([Acc, UtfXBytes], FromEnc, utf8) of
+ {error, B, Error} ->
+ %% We should only be able to get incorrect encoded data when
+ %% using utf8 (i.e. we are on unix)
+ FromEnc = utf8,
+ Parent ! {self(), set_unicode_state, false},
+ receive
+ {Alias, {set_unicode_state, false}} ->
+ Alias ! {Alias, true}
+ end,
+ receive
+ {Parent, set_unicode_state, true} -> ok
+ end,
+ Latin1Chars = unicode:characters_to_binary(Error, latin1, utf8),
+ {<<B/binary,Latin1Chars/binary>>, <<>>, latin1};
+ {incomplete, B, Inc} ->
+ {B, Inc, FromEnc};
+ B when is_binary(B) ->
+ {B, <<>>, FromEnc}
+ end,
+ Parent ! {ReaderRef, {data, Bytes}},
+ reader_loop(TTY, Parent, SignalRef, ReaderRef, NewFromEnc, NewAcc)
+ end
+ end.
+
+writer(TTY) ->
+ register(user_drv_writer, self()),
+ WriterRef = make_ref(),
+ proc_lib:init_ack({ok, {self(), WriterRef}}),
+ writer_loop(TTY, WriterRef).
+
+-spec write(state(), unicode:chardata()) -> ok.
+write(#state{ writer = {WriterPid, _}}, Chars) ->
+ WriterPid ! {write, erlang:iolist_to_iovec(Chars)}, ok.
+-spec write(state(), unicode:chardata(), From :: pid()) -> {ok, reference()}.
+write(#state{ writer = {WriterPid, _WriterRef}}, Chars, From) ->
+ Ref = erlang:monitor(process, WriterPid),
+ WriterPid ! {write, From, erlang:iolist_to_iovec(Chars)},
+ {ok, Ref}.
+
+writer_loop(TTY, WriterRef) ->
+ receive
+ {write, []} ->
+ writer_loop(TTY, WriterRef);
+ {write, Chars} ->
+ _ = write_nif(TTY, Chars),
+ writer_loop(TTY, WriterRef);
+ {write, From, []} ->
+ From ! {WriterRef, ok},
+ writer_loop(TTY, WriterRef);
+ {write, From, Chars} ->
+ case write_nif(TTY, Chars) of
+ ok ->
+ From ! {WriterRef, ok},
+ writer_loop(TTY, WriterRef);
+ {error, Reason} ->
+ exit(self(), Reason)
+ end
+ end.
+
+-spec handle_request(state(), request()) -> {erlang:iovec(), state()}.
+handle_request(State = #state{ options = #{ tty := false } }, Request) ->
+ case Request of
+ {putc_raw, Binary} ->
+ {Binary, State};
+ {putc, Binary} ->
+ {encode(Binary, State#state.unicode), State};
+ beep ->
+ {<<7>>, State};
+ _Ignore ->
+ {<<>>, State}
+ end;
+handle_request(State, {redraw_prompt, Pbs, Pbs2, {LB, {Bef, Aft}, LA}}) ->
+ {ClearLine, Cleared} = handle_request(State, delete_line),
+ CL = lists:reverse(Bef,Aft),
+ Text = Pbs ++ lists:flatten(lists:join("\n"++Pbs2, lists:reverse(LB)++[CL|LA])),
+ Moves = if LA /= [] ->
+ [Last|_] = lists:reverse(LA),
+ {move_combo, -logical(Last), length(LA), logical(Bef)};
+ true ->
+ {move, -logical(Aft)}
+ end,
+ {_, InsertedText} = handle_request(Cleared, {insert, unicode:characters_to_binary(Text)}),
+ {_, Moved} = handle_request(InsertedText, Moves),
+ {Redraw, NewState} = handle_request(Moved, redraw_prompt_pre_deleted),
+ {[ClearLine, Redraw], NewState};
+handle_request(State, redraw_prompt) ->
+ {ClearLine, _} = handle_request(State, delete_line),
+ {Redraw, NewState} = handle_request(State, redraw_prompt_pre_deleted),
+ {[ClearLine, Redraw], NewState};
+handle_request(State = #state{unicode = U, cols = W}, redraw_prompt_pre_deleted) ->
+ {Movement, TextInView} = in_view(State),
+ {_, NewPrompt} = handle_request(State, new_prompt),
+ {Redraw, RedrawState} = insert_buf(NewPrompt#state{xn = false}, unicode:characters_to_binary(TextInView)),
+ {Output, _} = case State#state.buffer_expand of
+ undefined ->
+ {[encode(Redraw, U), xnfix(RedrawState, RedrawState#state.buffer_before), Movement], RedrawState};
+ BufferExpand ->
+ BBCols = cols(State#state.buffer_before, U),
+ End = BBCols + cols(State#state.buffer_after,U),
+ {ExpandBuffer, NewState} = insert_buf(RedrawState#state{ buffer_expand = [] }, iolist_to_binary(BufferExpand)),
+ BECols = cols(W, End, NewState#state.buffer_expand, U),
+ MoveToEnd = move_cursor(RedrawState, BECols, End),
+ {[encode(Redraw,U),encode(ExpandBuffer, U), MoveToEnd, Movement], RedrawState}
+
+ end,
+ {Output, State};
+%% Clear the expand buffer after the cursor when we handle any request.
+handle_request(State = #state{ buffer_expand = Expand, unicode = U}, Request)
+ when Expand =/= undefined ->
+ {Redraw, NoExpandState} = handle_request(State#state{ buffer_expand = undefined }, redraw_prompt),
+ {Output, NewState} = handle_request(NoExpandState#state{ buffer_expand = undefined }, Request),
+ {[encode(Redraw, U), encode(Output, U)], NewState};
+handle_request(State, new_prompt) ->
+ {"", State#state{buffer_before = [],
+ buffer_after = [],
+ lines_before = [],
+ lines_after = []}};
+%% Print characters in the expandbuffer after the cursor
+handle_request(State, {expand, Expand}) ->
+ handle_request(State#state{buffer_expand = Expand}, redraw_prompt);
+handle_request(State, {expand_with_trim, Binary}) ->
+ handle_request(State,
+ {expand, iolist_to_binary(["\r\n",string:trim(Binary, both)])});
+%% putc_keep_state prints Binary and keeps the current prompt unchanged
+handle_request(State = #state{ unicode = U }, {putc_keep_state, Binary}) ->
+ {PutBuffer, _NewState} = insert_buf(State, Binary),
+ {encode(PutBuffer, U), State};
+%% putc prints Binary and overwrites any existing characters
+handle_request(State = #state{ unicode = U }, {putc, Binary}) ->
+ %% Todo should handle invalid unicode?
+ {PutBuffer, NewState} = insert_buf(State, Binary),
+ if NewState#state.buffer_after =:= [] ->
+ {encode(PutBuffer, U), NewState};
+ true ->
+ %% Delete any overwritten characters after current the cursor
+ OldLength = logical(State#state.buffer_before) + lists:sum([logical(L) || L <- State#state.lines_before]),
+ NewLength = logical(NewState#state.buffer_before) + lists:sum([logical(L) || L <- NewState#state.lines_before]),
+ {_, _, _, NewBA} = split(NewLength - OldLength, NewState#state.buffer_after, U),
+ {encode(PutBuffer, U), NewState#state{ buffer_after = NewBA }}
+ end;
+handle_request(State, {putc_raw, Binary}) ->
+ handle_request(State, {putc, unicode:characters_to_binary(Binary, latin1)});
+handle_request(State = #state{}, delete_after_cursor) ->
+ {[State#state.delete_after_cursor],
+ State#state{buffer_after = [],
+ lines_after = []}};
+handle_request(State = #state{unicode = U, cols = W, buffer_before = Bef,
+ lines_before = LinesBefore,
+ lines_after = _LinesAfter}, delete_line) ->
+ MoveToBeg = move_cursor(State, cols_multiline(Bef, LinesBefore, W, U), 0),
+ {[MoveToBeg, State#state.delete_after_cursor],
+ State#state{buffer_before = [],
+ buffer_after = [],
+ lines_before = [],
+ lines_after = []}};
+handle_request(State = #state{ unicode = U, cols = W }, {delete, N}) when N > 0 ->
+ {_DelNum, DelCols, _, NewBA} = split(N, State#state.buffer_after, U),
+ BBCols = cols(State#state.buffer_before, U),
+ BACols = cols(State#state.buffer_after, U),
+ NewBACols = cols(NewBA, U),
+ Output = [encode(NewBA, U),
+ lists:duplicate(DelCols, $\s),
+ xnfix(State, BBCols + NewBACols + DelCols),
+ move_cursor(State,
+ BBCols + NewBACols + DelCols,
+ BBCols)],
+ NewState0 = State#state{ buffer_after = NewBA },
+ if State#state.lines_after =/= [], (BBCols + BACols-N) rem W =:= 0 ->
+ {Delete, _} = handle_request(State, delete_line),
+ {Redraw, NewState1} = handle_request(NewState0, redraw_prompt_pre_deleted),
+ {[Delete, Redraw], NewState1};
+ true ->
+ {Output, NewState0}
+ end;
+handle_request(State = #state{ unicode = U, cols = W }, {delete, N}) when N < 0 ->
+ {_DelNum, DelCols, _, NewBB} = split(-N, State#state.buffer_before, U),
+ BBCols = cols(State#state.buffer_before, U),
+ BACols = cols(State#state.buffer_after, U),
+ NewBBCols = cols(NewBB, U),
+ Output = [move_cursor(State, NewBBCols + DelCols, NewBBCols),
+ encode(State#state.buffer_after,U),
+ lists:duplicate(DelCols, $\s),
+ xnfix(State, NewBBCols + BACols + DelCols),
+ move_cursor(State, NewBBCols + BACols + DelCols, NewBBCols)],
+ NewState0 = State#state{ buffer_before = NewBB },
+ if State#state.lines_after =/= [], (BBCols+BACols+N) rem W =:= 0 ->
+ {Delete, _} = handle_request(State, delete_line),
+ {Redraw, NewState1} = handle_request(NewState0, redraw_prompt_pre_deleted),
+ {[Delete, Redraw], NewState1};
+ true ->
+ {Output, NewState0}
+ end;
+handle_request(State, {delete, 0}) ->
+ {"",State};
+%% {move_combo, before_line_movement, line_movement, after_line_movement}
+%% Many of the move operations comes in threes, this is a helper to make
+%% movement a little bit easier. We move to the beginning of
+%% the line before switching line and then move to the right column on
+%% the next line.
+handle_request(State, {move_combo, V1, L, V2}) ->
+ {Moves1, NewState1} = handle_request(State, {move, V1}),
+ {Moves2, NewState2} = handle_request(NewState1, {move_line, L}),
+ {Moves3, NewState3} = handle_request(NewState2, {move, V2}),
+ {Moves1 ++ Moves2 ++ Moves3, NewState3};
+handle_request(State = #state{ cols = W,
+ rows = R,
+ unicode = U,
+ buffer_before = Bef,
+ buffer_after = Aft,
+ lines_before = LinesBefore,
+ lines_after = LinesAfter},
+ {move_line, L}) when L < 0, length(LinesBefore) >= -L ->
+ {LinesJumped, [B|NewLinesBefore]} = lists:split(-L -1, LinesBefore),
+ PrevLinesCols = cols_multiline([B|LinesJumped], W, U),
+ N_Cols = min(cols(Bef, U), cols(B, U)),
+ {_, _, NewBB, NewBA} = split_cols(N_Cols, B, U),
+ Moves = move_cursor(State, PrevLinesCols, 0),
+ CL = lists:reverse(Bef,Aft),
+ NewLinesAfter = lists:reverse([CL|LinesJumped], LinesAfter),
+ NewState = State#state{buffer_before = NewBB,
+ buffer_after = NewBA,
+ lines_before = NewLinesBefore,
+ lines_after = NewLinesAfter},
+ RowsInView = cols_multiline([B,CL|LinesBefore], W, U) div W,
+ Output = if
+ %% When we move up and the view is "full"
+ RowsInView >= R ->
+ {Movement, TextInView} = in_view(NewState),
+ {ClearLine, Cleared} = handle_request(State, delete_line),
+ {Redraw, _} = handle_request(Cleared, {insert, unicode:characters_to_binary(TextInView)}),
+ [ClearLine, Redraw, Movement];
+ true -> Moves
+ end,
+ {Output, NewState};
+handle_request(State = #state{ cols = W,
+ rows = R,
+ unicode = U,
+ buffer_before = Bef,
+ buffer_after = Aft,
+ lines_before = LinesBefore,
+ lines_after = LinesAfter},
+ {move_line, L}) when L > 0, length(LinesAfter) >= L ->
+ {LinesJumped, [A|NewLinesAfter]} = lists:split(L - 1, LinesAfter),
+ NextLinesCols = cols_multiline([(Bef++Aft)|LinesJumped], W, U),
+ N_Cols = min(cols(Bef, U), cols(A, U)),
+ {_, _, NewBB, NewBA} = split_cols(N_Cols, A, U),
+ Moves = move_cursor(State, 0, NextLinesCols),
+ CL = lists:reverse(Bef, Aft),
+ NewLinesBefore = lists:reverse([CL|LinesJumped],LinesBefore),
+ NewState = State#state{buffer_before = NewBB,
+ buffer_after = NewBA,
+ lines_before = NewLinesBefore,
+ lines_after = NewLinesAfter},
+ RowsInView = cols_multiline([A|NewLinesBefore], W, U) div W,
+ Output = if
+ RowsInView >= R ->
+ {Movement, TextInView} = in_view(NewState),
+ {ClearLine, Cleared} = handle_request(State, delete_line),
+ {Redraw, _} = handle_request(Cleared, {insert, unicode:characters_to_binary(TextInView)}),
+ [ClearLine, Redraw, Movement];
+ true -> Moves
+ end,
+ {Output, NewState};
+handle_request(State, {move_line, _}) ->
+ {"", State};
+handle_request(State = #state{ unicode = U }, {move, N}) when N < 0 ->
+ {_DelNum, DelCols, NewBA, NewBB} = split(-N, State#state.buffer_before, U),
+ NewBBCols = cols(NewBB, U),
+ Moves = move_cursor(State, NewBBCols + DelCols, NewBBCols),
+ {Moves, State#state{ buffer_before = NewBB,
+ buffer_after = NewBA ++ State#state.buffer_after} };
+handle_request(State = #state{ unicode = U }, {move, N}) when N > 0 ->
+ {_DelNum, DelCols, NewBB, NewBA} = split(N, State#state.buffer_after, U),
+ BBCols = cols(State#state.buffer_before, U),
+ Moves = move_cursor(State, BBCols, BBCols + DelCols),
+ {Moves, State#state{ buffer_after = NewBA,
+ buffer_before = NewBB ++ State#state.buffer_before} };
+handle_request(State, {move, 0}) ->
+ {"",State};
+handle_request(State = #state{cols = W, xn = OrigXn, unicode = U,lines_after = LinesAfter}, {insert, Chars}) ->
+ {InsertBuffer, NewState0} = insert_buf(State#state{ xn = false }, Chars),
+ NewState1 = NewState0#state{ xn = OrigXn },
+ NewBBCols = cols(NewState1#state.buffer_before, U),
+ NewBACols = cols(NewState1#state.buffer_after, U),
+ Output = [ encode(InsertBuffer, U),
+ encode(NewState1#state.buffer_after, U),
+ xnfix(State, NewBBCols + NewBACols),
+ move_cursor(State, NewBBCols + NewBACols, NewBBCols) ],
+ if LinesAfter =:= []; (NewBBCols + NewBACols) rem W =:= 0 ->
+ {Output, NewState1};
+ true ->
+ {Delete, _} = handle_request(State, delete_line),
+ {Redraw, NewState2} = handle_request(NewState1, redraw_prompt_pre_deleted),
+ {[Delete, Redraw,""], NewState2}
+ end;
+handle_request(State, beep) ->
+ {<<7>>, State};
+handle_request(State, clear) ->
+ {State#state.clear, State#state{buffer_before = [],
+ buffer_after = [],
+ lines_before = [],
+ lines_after = []}};
+handle_request(State, Req) ->
+ erlang:display({unhandled_request, Req}),
+ {"", State}.
+
+%% Split the buffer after N cols
+%% Returns the number of characters deleted, and the column length (N)
+%% of those characters.
+split_cols(N_Cols, Buff, Unicode) ->
+ split_cols(N_Cols, Buff, [], 0, 0, Unicode).
+split_cols(N, [SkipChars | T], Acc, Cnt, Cols, Unicode) when is_binary(SkipChars) ->
+ split_cols(N, T, [SkipChars | Acc], Cnt, Cols, Unicode);
+split_cols(0, Buff, Acc, Chars, Cols, _Unicode) ->
+ {Chars, Cols, Acc, Buff};
+split_cols(N, _Buff, _Acc, _Chars, _Cols, _Unicode) when N < 0 ->
+ error;
+split_cols(_N, [], Acc, Chars, Cols, _Unicode) ->
+ {Chars, Cols, Acc, []};
+split_cols(N, [Char | T], Acc, Cnt, Cols, Unicode) when is_integer(Char) ->
+ split_cols(N - npwcwidth(Char), T, [Char | Acc], Cnt + 1, Cols + npwcwidth(Char, Unicode), Unicode);
+split_cols(N, [Chars | T], Acc, Cnt, Cols, Unicode) when is_list(Chars) ->
+ split_cols(N - length(Chars), T, [Chars | Acc],
+ Cnt + length(Chars), Cols + cols(Chars, Unicode), Unicode).
+
+%% Split the buffer after N logical characters returning
+%% the number of real characters deleted and the column length
+%% of those characters
+split(N, Buff, Unicode) ->
+ ?dbg({?FUNCTION_NAME, N, Buff, Unicode}),
+ split(N, Buff, [], 0, 0, Unicode).
+split(0, Buff, Acc, Chars, Cols, _Unicode) ->
+ ?dbg({?FUNCTION_NAME, {Chars, Cols, Acc, Buff}}),
+ {Chars, Cols, Acc, Buff};
+split(N, _Buff, _Acc, _Chars, _Cols, _Unicode) when N < 0 ->
+ error;
+split(_N, [], Acc, Chars, Cols, _Unicode) ->
+ {Chars, Cols, Acc, []};
+split(N, [Char | T], Acc, Cnt, Cols, Unicode) when is_integer(Char) ->
+ split(N - 1, T, [Char | Acc], Cnt + 1, Cols + npwcwidth(Char, Unicode), Unicode);
+split(N, [Chars | T], Acc, Cnt, Cols, Unicode) when is_list(Chars) ->
+ split(N - length(Chars), T, [Chars | Acc],
+ Cnt + length(Chars), Cols + cols(Chars, Unicode), Unicode);
+split(N, [SkipChars | T], Acc, Cnt, Cols, Unicode) when is_binary(SkipChars) ->
+ split(N, T, [SkipChars | Acc], Cnt, Cols, Unicode).
+
+logical([]) ->
+ 0;
+logical([Char | T]) when is_integer(Char) ->
+ 1 + logical(T);
+logical([Chars | T]) when is_list(Chars) ->
+ length(Chars) + logical(T);
+logical([SkipChars | T]) when is_binary(SkipChars) ->
+ logical(T).
+
+move_cursor(#state{ cols = W } = State, FromCol, ToCol) ->
+ ?dbg({?FUNCTION_NAME, FromCol, ToCol}),
+ [case (ToCol div W) - (FromCol div W) of
+ 0 -> "";
+ N when N < 0 ->
+ ?dbg({move, up, -N}),
+ move(up, State, -N);
+ N ->
+ ?dbg({move, down, N}),
+ move(down, State, N)
+ end,
+ case (ToCol rem W) - (FromCol rem W) of
+ 0 -> "";
+ N when N < 0 ->
+ ?dbg({down, left, -N}),
+ move(left, State, -N);
+ N ->
+ ?dbg({down, right, N}),
+ move(right, State, N)
+ end].
+
+move(up, #state{ up = Up }, N) ->
+ lists:duplicate(N, Up);
+move(down, #state{ down = Down }, N) ->
+ lists:duplicate(N, Down);
+move(left, #state{ left = Left }, N) ->
+ lists:duplicate(N, Left);
+move(right, #state{ right = Right }, N) ->
+ lists:duplicate(N, Right).
+
+in_view(#state{lines_after = LinesAfter, buffer_before = Bef, buffer_after = Aft, lines_before = LinesBefore, rows=R, cols=W, unicode=U, buffer_expand = BufferExpand} = State) ->
+ BufferExpandLines = case BufferExpand of
+ undefined -> [];
+ _ -> string:split(erlang:binary_to_list(BufferExpand), "\r\n", all)
+ end,
+ ExpandRows = (cols_multiline(BufferExpandLines, W, U) div W),
+ InputBeforeRows = (cols_multiline(LinesBefore, W, U) div W),
+ InputRows = (cols_multiline([Bef ++ Aft], W, U) div W),
+ InputAfterRows = (cols_multiline(LinesAfter, W, U) div W),
+ %% Dont print lines after if we have expansion rows
+ SumRows = InputBeforeRows+ InputRows + ExpandRows + InputAfterRows,
+ if SumRows > R ->
+ RowsLeftAfterInputRows = R - InputRows,
+ RowsLeftAfterExpandRows = RowsLeftAfterInputRows - ExpandRows,
+ RowsLeftAfterInputBeforeRows = RowsLeftAfterExpandRows - InputBeforeRows,
+ Cols1 = max(0,W*max(RowsLeftAfterInputBeforeRows, RowsLeftAfterExpandRows)),
+ {_, LBAfter, _, {_, LBAHalf}} = split_cols_multiline(Cols1, LinesBefore, U, W),
+ LBAfter0 = case LBAHalf of [] -> LBAfter;
+ _ -> [LBAHalf|LBAfter]
+ end,
+
+ RowsLeftAfterInputAfterRows = RowsLeftAfterInputBeforeRows - InputAfterRows,
+ LAInViewLines = case BufferExpandLines of
+ [] ->
+ %% We must remove one line extra, since we may have an xnfix at the end which will
+ %% adds one extra line, so for consistency always remove one line
+ Cols2 = max(0,W*max(RowsLeftAfterInputAfterRows, RowsLeftAfterInputBeforeRows)-W),
+ {_, LABefore, _, {LABHalf, _}} = split_cols_multiline(Cols2, LinesAfter, U, W),
+ case LABHalf of [] -> LABefore;
+ _ -> [LABHalf|LABefore]
+ end;
+ _ ->
+ []
+ end,
+ LAInView = lists:flatten(["\n"++LA||LA<-lists:reverse(LAInViewLines)]),
+ LBInView = lists:flatten([LB++"\n"||LB<-LBAfter0]),
+ Text = LBInView ++ lists:reverse(Bef,Aft) ++ LAInView,
+ Movement = move_cursor(State,
+ cols_after_cursor(State#state{lines_after = LAInViewLines++[lists:reverse(Bef, Aft)]}),
+ cols(Bef,U)),
+ {Movement, Text};
+
+ true ->
+ %% Everything fits in the current window, just output everything
+ Movement = move_cursor(State, cols_after_cursor(State#state{lines_after = lists:reverse(LinesAfter)++[lists:reverse(Bef, Aft)]}), cols(Bef,U)),
+ Text = lists:flatten([LB++"\n"||LB<-lists:reverse(LinesBefore)]) ++
+ lists:reverse(Bef,Aft) ++ lists:flatten(["\n"++LA||LA<-LinesAfter]),
+ {Movement, Text}
+ end.
+cols_after_cursor(#state{lines_after=[LAST|LinesAfter],cols=W, unicode=U}) ->
+ cols_multiline(LAST, LinesAfter, W, U).
+split_cols_multiline(Cols, Lines, U, W) ->
+ split_cols_multiline(Cols, Lines, U, W, 0, []).
+split_cols_multiline(0, Lines, _U, _W, ColsAcc, AccBefore) ->
+ {ColsAcc, AccBefore, Lines, {[],[]}};
+split_cols_multiline(_Cols, [], _U, _W, ColsAcc, AccBefore) ->
+ {ColsAcc, AccBefore, [], {[],[]}};
+split_cols_multiline(Cols, [L|Lines], U, W, ColsAcc, AccBefore) ->
+ case cols(L, U) > Cols of
+ true ->
+ {_, _, LB, LA} = split_cols(Cols, L, U),
+ {ColsAcc+Cols, AccBefore, Lines, {lists:reverse(LB), LA}};
+ _ ->
+ Cols2 = (((cols(L,U)-1) div W)*W+W),
+ split_cols_multiline(Cols-Cols2, Lines, U, W, ColsAcc+Cols2, [L|AccBefore])
+ end.
+cols_multiline(Lines, W, U) ->
+ cols_multiline("", Lines, W, U).
+cols_multiline(ExtraCols, Lines, W, U) ->
+ cols(ExtraCols, U) + lists:sum([((cols(LB,U)-1) div W)*W + W || LB <- Lines]).
+
+cols([],_Unicode) ->
+ 0;
+cols([Char | T], Unicode) when is_integer(Char) ->
+ npwcwidth(Char, Unicode) + cols(T, Unicode);
+cols([Chars | T], Unicode) when is_list(Chars) ->
+ cols(Chars, Unicode) + cols(T, Unicode);
+cols([SkipSeq | T], Unicode) when is_binary(SkipSeq) ->
+ %% Any binary should be an ANSI escape sequence
+ %% so we skip that
+ cols(T, Unicode).
+
+cols(ColsPerLine, CurrCols, Chars, Unicode) when CurrCols > ColsPerLine ->
+ ColsPerLine + cols(ColsPerLine, CurrCols - ColsPerLine, Chars, Unicode);
+cols(_ColsPerLine, CurrCols, [], _Unicode) ->
+ CurrCols;
+cols(ColsPerLine, CurrCols, ["\r\n" | T], Unicode) ->
+ CurrCols + (ColsPerLine - CurrCols) + cols(ColsPerLine, 0, T, Unicode);
+cols(ColsPerLine, CurrCols, [H | T], Unicode) ->
+ cols(ColsPerLine, CurrCols + cols([H], Unicode), T, Unicode).
+
+update_geometry(State) ->
+ case tty_window_size(State#state.tty) of
+ {ok, {Cols, Rows}} when Cols > 0 ->
+ ?dbg({?FUNCTION_NAME, Cols}),
+ State#state{ cols = Cols, rows = Rows };
+ _Error ->
+ ?dbg({?FUNCTION_NAME, _Error}),
+ State
+ end.
+
+npwcwidthstring(String) when is_list(String) ->
+ npwcwidthstring(unicode:characters_to_binary(String));
+npwcwidthstring(String) ->
+ case string:next_grapheme(String) of
+ [] -> 0;
+ [$\e | Rest] ->
+ case re:run(String, ?ANSI_REGEXP, [unicode]) of
+ {match, [{0, N}]} ->
+ <<_Ansi:N/binary, AnsiRest/binary>> = String,
+ npwcwidthstring(AnsiRest);
+ _ ->
+ npwcwidth($\e) + npwcwidthstring(Rest)
+ end;
+ [H|Rest] when is_list(H)-> lists:sum([npwcwidth(A)||A<-H]) + npwcwidthstring(Rest);
+ [H|Rest] -> npwcwidth(H) + npwcwidthstring(Rest)
+ end.
+
+npwcwidth(Char) ->
+ npwcwidth(Char, true).
+npwcwidth(Char, true) ->
+ case wcwidth(Char) of
+ {error, not_printable} -> 0;
+ {error, enotsup} ->
+ case unicode_util:is_wide(Char) of
+ true -> 2;
+ false -> 1
+ end;
+ C -> C
+ end;
+npwcwidth(Char, false) ->
+ byte_size(char_to_latin1(Char)).
+
+
+%% Return the xn fix for the current cursor position.
+%% We use get_position to figure out if we need to calculate the current columns
+%% or not.
+%%
+%% We need to know the actual column because get_position will return the last
+%% column number when the cursor is:
+%% * in the last column
+%% * off screen
+%%
+%% and it is when the cursor is off screen that we should do the xnfix.
+xnfix(#state{ position = _, unicode = U } = State) ->
+ xnfix(State, cols(State#state.buffer_before, U)).
+%% Return the xn fix for CurrCols location.
+xnfix(#state{ xn = true, cols = Cols } = State, CurrCols)
+ when CurrCols =/= 0, CurrCols rem Cols == 0 ->
+ [<<"\s">>,move(left, State, 1)];
+xnfix(_, _CurrCols) ->
+ ?dbg({xnfix, _CurrCols}),
+ [].
+
+characters_to_output(Chars) ->
+ try unicode:characters_to_binary(Chars) of
+ Binary ->
+ Binary
+ catch error:badarg ->
+ unicode:characters_to_binary(
+ lists:map(
+ fun({ansi, Ansi}) ->
+ Ansi;
+ (Char) ->
+ Char
+ end, Chars)
+ )
+ end.
+characters_to_buffer(Chars) ->
+ lists:flatmap(
+ fun({ansi, _Ansi}) ->
+ "";
+ (Char) ->
+ [Char]
+ end, Chars).
+
+insert_buf(State, Binary) when is_binary(Binary) ->
+ insert_buf(State, Binary, [], []).
+insert_buf(State, Bin, LineAcc, Acc) ->
+ case string:next_grapheme(Bin) of
+ [] when State#state.buffer_expand =/= undefined ->
+ Expand = lists:reverse(LineAcc),
+ {[Acc, characters_to_output(Expand)],
+ State#state{ buffer_expand = characters_to_buffer(Expand) }};
+ [] ->
+ NewBB = characters_to_buffer(LineAcc) ++ State#state.buffer_before,
+ NewState = State#state{ buffer_before = NewBB },
+ {[Acc, characters_to_output(lists:reverse(LineAcc)), xnfix(NewState)],
+ NewState};
+ [$\t | Rest] ->
+ insert_buf(State, Rest, [State#state.tab | LineAcc], Acc);
+ [$\e | Rest] ->
+ case ansi_sgr(Bin) of
+ none ->
+ case re:run(Bin, State#state.ansi_regexp) of
+ {match, [{0, N}]} ->
+ %% Any other ansi sequences are just printed and
+ %% then dropped as they "should" not effect rendering
+ <<Ansi:N/binary, AnsiRest/binary>> = Bin,
+ insert_buf(State, AnsiRest, [{ansi, Ansi} | LineAcc], Acc);
+ _ ->
+ insert_buf(State, Rest, [$\e | LineAcc], Acc)
+ end;
+ {Ansi, AnsiRest} ->
+ %% We include the graphics ansi sequences in the
+ %% buffer that we step over
+ insert_buf(State, AnsiRest, [Ansi | LineAcc], Acc)
+ end;
+ [NLCR | Rest] when NLCR =:= $\n; NLCR =:= $\r ->
+ Tail =
+ if NLCR =:= $\n ->
+ <<$\r,$\n>>;
+ true ->
+ <<$\r>>
+ end,
+ if State#state.buffer_expand =:= undefined ->
+ CurrentLine = lists:reverse(State#state.buffer_before),
+ LinesBefore = State#state.lines_before,
+ LinesBefore1 =
+ case {CurrentLine, LineAcc} of
+ {[], []} ->
+ LinesBefore;
+ {[],_} ->
+ [lists:reverse(LineAcc)|LinesBefore];
+ {_,_} ->
+ [CurrentLine++lists:reverse(LineAcc)|LinesBefore]
+ end,
+ insert_buf(State#state{ buffer_before = [],
+ buffer_after = State#state.buffer_after,
+ lines_before=LinesBefore1},
+ Rest, [], [Acc, [characters_to_output(lists:reverse(LineAcc)), Tail]]);
+ true -> insert_buf(State, Rest, [binary_to_list(Tail) | LineAcc], Acc)
+ end;
+ [Cluster | Rest] when is_list(Cluster) ->
+ insert_buf(State, Rest, [Cluster | LineAcc], Acc);
+ %% We have gotten a code point that may be part of the previous grapheme cluster.
+ [Char | Rest] when Char >= 128, LineAcc =:= [], State#state.buffer_before =/= [],
+ State#state.buffer_expand =:= undefined ->
+ [PrevChar | BB] = State#state.buffer_before,
+ case string:next_grapheme([PrevChar | Bin]) of
+ [PrevChar | _] ->
+ %% It was not part of the previous cluster, so just insert
+ %% it as a normal character
+ insert_buf(State, Rest, [Char | LineAcc], Acc);
+ [Cluster | ClusterRest] ->
+ %% It was part of the previous grapheme cluster, so we output
+ %% it and insert it into the before_buffer
+ %% TODO: If an xnfix was done on PrevChar,
+ %% then we should rewrite the entire grapheme cluster.
+ {_, ToWrite} = lists:split(length(lists:flatten([PrevChar])), Cluster),
+ insert_buf(State#state{ buffer_before = [Cluster | BB] },
+ ClusterRest, LineAcc,
+ [Acc, unicode:characters_to_binary(ToWrite)])
+ end;
+ [Char | Rest] when Char >= 128 ->
+ insert_buf(State, Rest, [Char | LineAcc], Acc);
+ [Char | Rest] ->
+ case {isprint(Char), Char} of
+ {true,_} ->
+ insert_buf(State, Rest, [Char | LineAcc], Acc);
+ {false, 8#177} -> %% DEL
+ insert_buf(State, Rest, ["^?" | LineAcc], Acc);
+ {false, _} ->
+ insert_buf(State, Rest, ["^" ++ [Char bor 8#40] | LineAcc], Acc)
+ end
+ end.
+
+%% ANSI Select Graphic Rendition parameters:
+%% https://en.wikipedia.org/wiki/ANSI_escape_code#SGR
+%% This function replicates this regex pattern <<"^[\e",194,155,"]\\[[0-9;:]*m">>
+%% calling re:run/2 nif on compiled regex_pattern was significantly
+%% slower than this implementation.
+ansi_sgr(<<N, $[, Rest/binary>>=Bin) when N =:= $\e; N =:= 194; N =:= 155 ->
+ case ansi_sgr(Rest, 2) of
+ {ok, Size} ->
+ <<Result:Size/binary, Bin1/binary>> = Bin,
+ {Result, Bin1};
+ none -> none
+ end;
+ansi_sgr(<<_/binary>>) -> none.
+ansi_sgr(<<M, Rest/binary>>, Size) when $0 =< M, M =< $; ->
+ ansi_sgr(Rest, Size + 1);
+ansi_sgr(<<$m, _Rest/binary>>, Size) ->
+ {ok, Size + 1};
+ansi_sgr(<<_/binary>>, _) -> none.
+
+-spec to_latin1(erlang:binary()) -> erlang:iovec().
+to_latin1(Bin) ->
+ case is_usascii(Bin) of
+ true -> [Bin];
+ false -> lists:flatten([binary_to_latin1(Bin)])
+ end.
+
+is_usascii(<<Char/utf8,T/binary>>) when Char < 128 ->
+ is_usascii(T);
+is_usascii(<<>>) ->
+ true;
+is_usascii(_) ->
+ false.
+
+binary_to_latin1(Buffer) ->
+ [char_to_latin1(CP) || CP <- unicode:characters_to_list(Buffer)].
+char_to_latin1(UnicodeChar) when UnicodeChar >= 512 ->
+ <<"\\x{",(integer_to_binary(UnicodeChar, 16))/binary,"}">>;
+char_to_latin1(UnicodeChar) when UnicodeChar >= 128 ->
+ <<"\\",(integer_to_binary(UnicodeChar, 8))/binary>>;
+char_to_latin1(UnicodeChar) ->
+ <<UnicodeChar>>.
+
+encode(UnicodeChars, true) ->
+ unicode:characters_to_binary(UnicodeChars);
+encode(UnicodeChars, false) ->
+ to_latin1(unicode:characters_to_binary(UnicodeChars)).
+
+%% Using get_position adds about 10ms of latency
+%% get_position(#state{ position = false }) ->
+%% unknown;
+%% get_position(State) ->
+%% [] = write(State, State#state.position),
+%% get_position(State, <<>>).
+%% get_position(State, Acc) ->
+%% receive
+%% {select,TTY,Ref,ready_input}
+%% when TTY =:= State#state.tty,
+%% Ref =:= State#state.read ->
+%% {Bytes, <<>>} = read_input(State#state{ acc = Acc }),
+%% case re:run(Bytes, State#state.position_reply, [unicode]) of
+%% {match,[{Start,Length},Row,Col]} ->
+%% <<Before:Start/binary,_:Length/binary,After/binary>> = Bytes,
+%% %% This should be put in State in order to not screw up the
+%% %% message order...
+%% [State#state.parent ! {{self(), State#state.tty}, {data, <<Before/binary, After/binary>>}}
+%% || Before =/= <<>>, After =/= <<>>],
+%% {binary_to_integer(binary:part(Bytes,Row)),
+%% binary_to_integer(binary:part(Bytes,Col))};
+%% nomatch ->
+%% get_position(State, Bytes)
+%% end
+%% after 1000 ->
+%% unknown
+%% end.
+
+-ifdef(debug).
+dbg(_) ->
+ ok.
+-endif.
+
+%% Nif functions
+-spec isatty(stdin | stdout | stderr) -> boolean() | ebadf.
+isatty(_Fd) ->
+ erlang:nif_error(undef).
+tty_create() ->
+ erlang:nif_error(undef).
+tty_init(_TTY, _Fd, _Options) ->
+ erlang:nif_error(undef).
+tty_set(_TTY) ->
+ erlang:nif_error(undef).
+setlocale(_TTY) ->
+ erlang:nif_error(undef).
+tty_select(_TTY, _SignalRef, _ReadRef) ->
+ erlang:nif_error(undef).
+write_nif(_TTY, _IOVec) ->
+ erlang:nif_error(undef).
+read_nif(_TTY, _Ref) ->
+ erlang:nif_error(undef).
+tty_window_size(_TTY) ->
+ erlang:nif_error(undef).
+isprint(_Char) ->
+ erlang:nif_error(undef).
+wcwidth(_Char) ->
+ erlang:nif_error(undef).
+sizeof_wchar() ->
+ erlang:nif_error(undef).
+wcswidth(_Char) ->
+ erlang:nif_error(undef).
+tgetent(Char) ->
+ tgetent_nif([Char,0]).
+tgetnum(Char) ->
+ tgetnum_nif([Char,0]).
+tgetflag(Char) ->
+ tgetflag_nif([Char,0]).
+tgetstr(Char) ->
+ tgetstr_nif([Char,0]).
+tgoto(Char, Arg) ->
+ tgoto_nif([Char,0], Arg).
+tgoto(Char, Arg1, Arg2) ->
+ tgoto_nif([Char,0], Arg1, Arg2).
+tgetent_nif(_Char) ->
+ erlang:nif_error(undef).
+tgetnum_nif(_Char) ->
+ erlang:nif_error(undef).
+tgetflag_nif(_Char) ->
+ erlang:nif_error(undef).
+tgetstr_nif(_Char) ->
+ erlang:nif_error(undef).
+tgoto_nif(_Ent, _Arg) ->
+ erlang:nif_error(undef).
+tgoto_nif(_Ent, _Arg1, _Arg2) ->
+ erlang:nif_error(undef).
+tty_read_signal(_TTY, _Ref) ->
+ erlang:nif_error(undef).
+
diff --git a/lib/kernel/src/seq_trace.erl b/lib/kernel/src/seq_trace.erl
index d537e21239..47c4355f10 100644
--- a/lib/kernel/src/seq_trace.erl
+++ b/lib/kernel/src/seq_trace.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2020. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -74,7 +74,7 @@ set_token(Type, Val) ->
get_token() ->
element(2,process_info(self(),sequential_trace_token)).
--spec get_token(Component) -> {Component, Val} when
+-spec get_token(Component) -> [] | {Component, Val} when
Component :: component(),
Val :: value().
get_token(Type) ->
diff --git a/lib/kernel/src/socket.erl b/lib/kernel/src/socket.erl
index ccacb84293..02e199d088 100644
--- a/lib/kernel/src/socket.erl
+++ b/lib/kernel/src/socket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
debug/1, socket_debug/1, use_registry/1,
info/0, info/1,
i/0, i/1, i/2,
+ tables/0, table/1,
monitor/1, cancel_monitor/1,
supports/0, supports/1, supports/2,
is_supported/1, is_supported/2, is_supported/3,
@@ -87,6 +88,10 @@
select_handle/0,
select_info/0,
+ completion_tag/0,
+ completion_handle/0,
+ completion_info/0,
+
invalid/0,
socket_counters/0,
@@ -151,15 +156,18 @@
%% We need #file_descriptor{} for sendfile/2,3,4,5
-include("file_int.hrl").
+%% -define(DBG(T), erlang:display({{self(), ?MODULE, ?LINE, ?FUNCTION_NAME}, T})).
+
%% Also in prim_socket
-define(REGISTRY, socket_registry).
-type invalid() :: {invalid, What :: term()}.
-type info() ::
- #{counters := #{atom() := non_neg_integer()},
- iov_max := non_neg_integer(),
- use_registry := boolean()}.
+ #{counters := #{atom() := non_neg_integer()},
+ iov_max := non_neg_integer(),
+ use_registry := boolean(),
+ io_backend := #{name := atom()}}.
-type socket_counters() :: #{read_byte := non_neg_integer(),
read_fails := non_neg_integer(),
@@ -771,18 +779,27 @@
%% Interface term formats
%%
--opaque select_tag() :: atom() | {atom(), ContData :: term()}.
+-opaque select_tag() :: atom() | {atom(), ContData :: term()}.
+-opaque completion_tag() :: atom(). % | {atom(), ContData :: term()}.
-type select_handle() :: reference().
+-type completion_handle() :: reference().
-type select_info() ::
{select_info,
SelectTag :: select_tag(),
SelectHandle :: select_handle()}.
+-type completion_info() ::
+ {completion_info,
+ CompletionTag :: completion_tag(),
+ CompletionHandle :: completion_handle()}.
-define(SELECT_INFO(Tag, SelectHandle),
{select_info, Tag, SelectHandle}).
+-define(COMPLETION_INFO(Tag, CompletionHandle),
+ {completion_info, Tag, CompletionHandle}).
+
%% ===========================================================================
%%
@@ -988,6 +1005,17 @@ use_registry(D) when is_boolean(D) ->
prim_socket:use_registry(D).
+tables() ->
+ #{protocols => table(protocols),
+ options => table(options),
+ ioctl_requests => table(ioctl_requests),
+ ioctl_flags => table(ioctl_flags),
+ msg_flags => table(msg_flags)}.
+
+table(Table) ->
+ prim_socket:p_get(Table).
+
+
%% ===========================================================================
%%
%% i/0,1,2 - List sockets
@@ -1111,12 +1139,23 @@ i_socket_info(_Proto, _Socket, #{domain := Domain} = _Info, domain) ->
atom_to_list(Domain);
i_socket_info(_Proto, _Socket, #{type := Type} = _Info, type) ->
string:to_upper(atom_to_list(Type));
-i_socket_info(Proto, _Socket, _Info, protocol) ->
- string:to_upper(atom_to_list(Proto));
+i_socket_info(Proto, _Socket, #{type := Type} = _Info, protocol) ->
+ string:to_upper(atom_to_list(if
+ (Proto =:= 0) ->
+ case Type of
+ stream -> tcp;
+ dgram -> udp;
+ _ -> unknown
+ end;
+ true ->
+ Proto
+ end));
i_socket_info(_Proto, Socket, _Info, fd) ->
- case socket:getopt(Socket, otp, fd) of
+ try socket:getopt(Socket, otp, fd) of
{ok, FD} -> integer_to_list(FD);
{error, _} -> " "
+ catch
+ _:_ -> " "
end;
i_socket_info(_Proto, _Socket, #{owner := Pid} = _Info, owner) ->
pid_to_list(Pid);
@@ -1128,11 +1167,14 @@ i_socket_info(Proto, Socket, _Info, local_address) ->
" "
end;
i_socket_info(Proto, Socket, _Info, remote_address) ->
- case peername(Socket) of
+ try peername(Socket) of
{ok, Addr} ->
fmt_sockaddr(Addr, Proto);
{error, _} ->
" "
+ catch
+ _:_ ->
+ " "
end;
i_socket_info(_Proto, _Socket,
#{counters := #{read_byte := N}} = _Info, recv) ->
@@ -1552,59 +1594,77 @@ connect(Socket, SockAddr) ->
-spec connect(Socket, SockAddr, Timeout :: 'nowait') ->
'ok' |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- SockAddr :: sockaddr(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid() | 'already';
-
- (Socket, SockAddr, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid() | 'already' |
+ 'not_bound' |
+ {add_socket, posix()} |
+ {update_connect_context, posix()};
+
+ (Socket, SockAddr, Handle :: select_handle() | completion_handle()) ->
'ok' |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- SockAddr :: sockaddr(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid() | 'already';
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid() | 'already' |
+ 'not_bound' |
+ {add_socket, posix()} |
+ {update_connect_context, posix()};
(Socket, SockAddr, Timeout :: 'infinity') ->
'ok' |
{'error', Reason} when
Socket :: socket(),
SockAddr :: sockaddr(),
- Reason :: posix() | 'closed' | invalid() | 'already';
+ Reason :: posix() | 'closed' | invalid() | 'already' |
+ 'not_bound' |
+ {add_socket, posix()} |
+ {update_connect_context, posix()};
(Socket, SockAddr, Timeout :: non_neg_integer()) ->
'ok' |
{'error', Reason} when
Socket :: socket(),
SockAddr :: sockaddr(),
- Reason :: posix() | 'closed' | invalid() | 'already' | 'timeout'.
+ Reason :: posix() | 'closed' | invalid() | 'already' |
+ 'not_bound' | 'timeout' |
+ {add_socket, posix()} |
+ {update_connect_context, posix()}.
%% <KOLLA>
%% Is it possible to connect with family = local for the (dest) sockaddr?
%% </KOLLA>
-connect(?socket(SockRef), SockAddr, Timeout)
+connect(?socket(SockRef), SockAddr, TimeoutOrHandle)
when is_reference(SockRef) ->
- case deadline(Timeout) of
+ case deadline(TimeoutOrHandle) of
invalid ->
- erlang:error({invalid, {timeout, Timeout}});
+ erlang:error({invalid, {timeout, TimeoutOrHandle}});
nowait ->
- SelectHandle = make_ref(),
- connect_nowait(SockRef, SockAddr, SelectHandle);
- select_handle ->
- SelectHandle = Timeout,
- connect_nowait(SockRef, SockAddr, SelectHandle);
+ Handle = make_ref(),
+ connect_nowait(SockRef, SockAddr, Handle);
+ handle ->
+ Handle = TimeoutOrHandle,
+ connect_nowait(SockRef, SockAddr, Handle);
Deadline ->
connect_deadline(SockRef, SockAddr, Deadline)
end;
connect(Socket, SockAddr, Timeout) ->
erlang:error(badarg, [Socket, SockAddr, Timeout]).
-connect_nowait(SockRef, SockAddr, SelectHandle) ->
- case prim_socket:connect(SockRef, SelectHandle, SockAddr) of
+connect_nowait(SockRef, SockAddr, Handle) ->
+ case prim_socket:connect(SockRef, Handle, SockAddr) of
select ->
- {select, ?SELECT_INFO(connect, SelectHandle)};
+ {select, ?SELECT_INFO(connect, Handle)};
+ completion ->
+ {completion, ?COMPLETION_INFO(connect, Handle)};
Result ->
Result
end.
@@ -1624,6 +1684,18 @@ connect_deadline(SockRef, SockAddr, Deadline) ->
_ = cancel(SockRef, connect, Ref),
{error, timeout}
end;
+ completion ->
+ %% Connecting...
+ Timeout = timeout(Deadline),
+ receive
+ ?socket_msg(_Socket, completion, {Ref, CompletionStatus}) ->
+ CompletionStatus;
+ ?socket_msg(_Socket, abort, {Ref, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ _ = cancel(SockRef, connect, Ref),
+ {error, timeout}
+ end;
Result ->
Result
end.
@@ -1650,7 +1722,7 @@ connect(Socket) ->
-spec listen(Socket) -> 'ok' | {'error', Reason} when
Socket :: socket(),
- Reason :: posix() | 'closed'.
+ Reason :: posix() | 'closed' | 'not_bound'.
listen(Socket) ->
listen(Socket, ?ESOCK_LISTEN_BACKLOG_DEFAULT).
@@ -1683,34 +1755,50 @@ accept(ListenSocket) ->
-spec accept(ListenSocket, Timeout :: 'nowait') ->
{'ok', Socket} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
ListenSocket :: socket(),
Socket :: socket(),
SelectInfo :: select_info(),
- Reason :: posix() | closed | invalid();
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | closed | invalid() |
+ {create_accept_socket, posix()} |
+ {add_accept_socket, posix()} |
+ {update_accept_context, posix()};
- (ListenSocket, SelectHandle :: select_handle()) ->
+ (ListenSocket, Handle :: select_handle() | completion_handle()) ->
{'ok', Socket} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
ListenSocket :: socket(),
Socket :: socket(),
SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid() |
+ {create_accept_socket, posix()} |
+ {add_socket, posix()} |
+ {update_accept_context, posix()};
(ListenSocket, Timeout :: 'infinity') ->
{'ok', Socket} |
{'error', Reason} when
ListenSocket :: socket(),
Socket :: socket(),
- Reason :: posix() | 'closed' | invalid();
+ Reason :: posix() | 'closed' | invalid() |
+ {create_accept_socket, posix()} |
+ {add_socket, posix()} |
+ {update_accept_context, posix()};
(ListenSocket, Timeout :: non_neg_integer()) ->
{'ok', Socket} |
{'error', Reason} when
ListenSocket :: socket(),
Socket :: socket(),
- Reason :: posix() | 'closed' | invalid() | 'timeout'.
+ Reason :: posix() | 'closed' | invalid() | 'timeout' |
+ {create_accept_socket, posix()} |
+ {add_socket, posix()} |
+ {update_accept_context, posix()}.
accept(?socket(LSockRef), Timeout)
when is_reference(LSockRef) ->
@@ -1718,23 +1806,25 @@ accept(?socket(LSockRef), Timeout)
invalid ->
erlang:error({invalid, {timeout, Timeout}});
nowait ->
- SelectHandle = make_ref(),
- accept_nowait(LSockRef, SelectHandle);
- select_handle ->
- SelectHandle = Timeout,
- accept_nowait(LSockRef, SelectHandle);
+ Handle = make_ref(),
+ accept_nowait(LSockRef, Handle);
+ handle ->
+ Handle = Timeout,
+ accept_nowait(LSockRef, Handle);
Deadline ->
accept_deadline(LSockRef, Deadline)
end;
accept(ListenSocket, Timeout) ->
erlang:error(badarg, [ListenSocket, Timeout]).
-accept_nowait(LSockRef, SelectHandle) ->
- case prim_socket:accept(LSockRef, SelectHandle) of
+accept_nowait(LSockRef, Handle) ->
+ case prim_socket:accept(LSockRef, Handle) of
select ->
- {select, ?SELECT_INFO(accept, SelectHandle)};
+ {select, ?SELECT_INFO(accept, Handle)};
+ completion ->
+ {completion, ?COMPLETION_INFO(accept, Handle)};
Result ->
- accept_result(LSockRef, SelectHandle, Result)
+ accept_result(LSockRef, Handle, Result)
end.
accept_deadline(LSockRef, Deadline) ->
@@ -1754,6 +1844,22 @@ accept_deadline(LSockRef, Deadline) ->
_ = cancel(LSockRef, accept, AccRef),
{error, timeout}
end;
+ completion ->
+ %% Each call is non-blocking, but even then it takes
+ %% *some* time, so just to be sure, recalculate before
+ %% the receive.
+ Timeout = timeout(Deadline),
+ receive
+ %% CompletionStatus = {ok, Socket} | {error, Reason}
+ ?socket_msg(?socket(LSockRef), completion,
+ {AccRef, CompletionStatus}) ->
+ CompletionStatus;
+ ?socket_msg(_Socket, abort, {AccRef, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ _ = cancel(LSockRef, accept, AccRef),
+ {error, timeout}
+ end;
Result ->
accept_result(LSockRef, AccRef, Result)
end.
@@ -1814,31 +1920,35 @@ send(Socket, Data) ->
RestData :: binary(),
Reason :: posix() | 'closed' | invalid();
- (Socket, Data, SelectHandle :: 'nowait') ->
+ (Socket, Data, Handle :: 'nowait') ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Data, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Data :: iodata(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Data, Handle :: select_handle() | completion_handle()) ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Data :: iodata(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Data, Timeout :: 'infinity') ->
'ok' |
@@ -1870,33 +1980,37 @@ send(Socket, Data, Timeout) ->
send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, Timeout).
--spec send(Socket, Data, Flags, SelectHandle :: 'nowait') ->
+-spec send(Socket, Data, Flags, Handle :: 'nowait') ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- Flags :: [msg_flag() | integer()],
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Data, Flags, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Data :: iodata(),
+ Flags :: [msg_flag() | integer()],
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Data, Flags, Handle :: select_handle() | completion_handle()) ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- Flags :: [msg_flag() | integer()],
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Data :: iodata(),
+ Flags :: [msg_flag() | integer()],
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Data, Flags, Timeout :: 'infinity') ->
'ok' |
@@ -1929,12 +2043,12 @@ send(Socket, Data, Timeout) ->
{'select', {SelectInfo, RestData}} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- Cont :: select_info(),
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Data :: iodata(),
+ Cont :: select_info(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Data, Cont, SelectHandle :: select_handle()) ->
'ok' |
@@ -1984,7 +2098,7 @@ send(?socket(SockRef), Data, ?SELECT_INFO(SelectTag, _) = Cont, Timeout)
nowait ->
SelectHandle = make_ref(),
send_nowait_cont(SockRef, Data, ContData, SelectHandle);
- select_handle ->
+ handle ->
SelectHandle = Timeout,
send_nowait_cont(SockRef, Data, ContData, SelectHandle);
Deadline ->
@@ -2001,11 +2115,11 @@ send(?socket(SockRef), Data, Flags, Timeout)
invalid ->
erlang:error({invalid, {timeout, Timeout}});
nowait ->
- SelectHandle = make_ref(),
- send_nowait(SockRef, Data, Flags, SelectHandle);
- select_handle ->
- SelectHandle = Timeout,
- send_nowait(SockRef, Data, Flags, SelectHandle);
+ Handle = make_ref(),
+ send_nowait(SockRef, Data, Flags, Handle);
+ handle ->
+ Handle = Timeout,
+ send_nowait(SockRef, Data, Flags, Handle);
Deadline ->
send_deadline(SockRef, Data, Flags, Deadline)
end;
@@ -2024,40 +2138,45 @@ send(?socket(SockRef) = Socket, Data, Flags, Timeout)
send(Socket, Data, Flags, Timeout) ->
erlang:error(badarg, [Socket, Data, Flags, Timeout]).
-send_nowait(SockRef, Bin, Flags, SelectHandle) ->
+send_nowait(SockRef, Bin, Flags, Handle) ->
send_common_nowait_result(
- SelectHandle, send,
- prim_socket:send(SockRef, Bin, Flags, SelectHandle)).
+ Handle, send,
+ prim_socket:send(SockRef, Bin, Flags, Handle)).
+%% On Windows, writes either succeed directly (it their entirety),
+%% they are scheduled (completion) or they fail. *No* partial success,
+%% and therefor no need to handle theme here (in cont).
send_nowait_cont(SockRef, Bin, Cont, SelectHandle) ->
send_common_nowait_result(
SelectHandle, send,
prim_socket:send(SockRef, Bin, Cont, SelectHandle)).
send_deadline(SockRef, Bin, Flags, Deadline) ->
- SelectHandle = make_ref(),
+ Handle = make_ref(),
HasWritten = false,
send_common_deadline_result(
- SockRef, Bin, SelectHandle, Deadline, HasWritten,
+ SockRef, Bin, Handle, Deadline, HasWritten,
send, fun send_deadline_cont/5,
- prim_socket:send(SockRef, Bin, Flags, SelectHandle)).
+ prim_socket:send(SockRef, Bin, Flags, Handle)).
send_deadline_cont(SockRef, Bin, Cont, Deadline, HasWritten) ->
- SelectHandle = make_ref(),
+ Handle = make_ref(),
send_common_deadline_result(
- SockRef, Bin, SelectHandle, Deadline, HasWritten,
+ SockRef, Bin, Handle, Deadline, HasWritten,
send, fun send_deadline_cont/5,
- prim_socket:send(SockRef, Bin, Cont, SelectHandle)).
+ prim_socket:send(SockRef, Bin, Cont, Handle)).
-compile({inline, [send_common_nowait_result/3]}).
-send_common_nowait_result(SelectHandle, Op, Result) ->
+send_common_nowait_result(Handle, Op, Result) ->
case Result of
+ completion ->
+ {completion, ?COMPLETION_INFO(Op, Handle)};
{select, ContData} ->
- {select, ?SELECT_INFO({Op, ContData}, SelectHandle)};
+ {select, ?SELECT_INFO({Op, ContData}, Handle)};
{select, Data, ContData} ->
- {select, {?SELECT_INFO({Op, ContData}, SelectHandle), Data}};
+ {select, {?SELECT_INFO({Op, ContData}, Handle), Data}};
%%
Result ->
Result
@@ -2065,7 +2184,7 @@ send_common_nowait_result(SelectHandle, Op, Result) ->
-compile({inline, [send_common_deadline_result/8]}).
send_common_deadline_result(
- SockRef, Data, SelectHandle, Deadline, HasWritten,
+ SockRef, Data, Handle, Deadline, HasWritten,
Op, Fun, SendResult) ->
%%
case SendResult of
@@ -2073,26 +2192,40 @@ send_common_deadline_result(
%% Would block, wait for continuation
Timeout = timeout(Deadline),
receive
- ?socket_msg(_Socket, select, SelectHandle) ->
+ ?socket_msg(_Socket, select, Handle) ->
Fun(SockRef, Data, Cont, Deadline, HasWritten);
- ?socket_msg(_Socket, abort, {SelectHandle, Reason}) ->
+ ?socket_msg(_Socket, abort, {Handle, Reason}) ->
send_common_error(Reason, Data, HasWritten)
after Timeout ->
- _ = cancel(SockRef, Op, SelectHandle),
+ _ = cancel(SockRef, Op, Handle),
send_common_error(timeout, Data, HasWritten)
end;
{select, Data_1, Cont} ->
%% Partial send success, wait for continuation
Timeout = timeout(Deadline),
receive
- ?socket_msg(_Socket, select, SelectHandle) ->
+ ?socket_msg(_Socket, select, Handle) ->
Fun(SockRef, Data_1, Cont, Deadline, true);
- ?socket_msg(_Socket, abort, {SelectHandle, Reason}) ->
+ ?socket_msg(_Socket, abort, {Handle, Reason}) ->
send_common_error(Reason, Data_1, true)
after Timeout ->
- _ = cancel(SockRef, Op, SelectHandle),
+ _ = cancel(SockRef, Op, Handle),
send_common_error(timeout, Data_1, true)
end;
+
+ completion ->
+ %% Would block, wait for continuation
+ Timeout = timeout(Deadline),
+ receive
+ ?socket_msg(_Socket, completion, {Handle, CompletionStatus}) ->
+ CompletionStatus;
+ ?socket_msg(_Socket, abort, {Handle, Reason}) ->
+ send_common_error(Reason, Data, false)
+ after Timeout ->
+ _ = cancel(SockRef, Op, Handle),
+ send_common_error(timeout, Data, false)
+ end;
+
%%
{error, {_Reason, RestIOV}} = Error when is_list(RestIOV) ->
Error;
@@ -2165,45 +2298,49 @@ sendto(Socket, Data, Dest_Cont) ->
RestData :: binary(),
Reason :: posix() | 'closed' | invalid();
- (Socket, Data, Dest, SelectHandle :: 'nowait') ->
+ (Socket, Data, Dest, Handle :: 'nowait') ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- Dest :: sockaddr(),
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Data, Dest, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Data :: iodata(),
+ Dest :: sockaddr(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Data, Dest, Handle :: select_handle() | completion_handle()) ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- Dest :: sockaddr(),
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Data :: iodata(),
+ Dest :: sockaddr(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Data, Dest, Timeout :: 'infinity') ->
'ok' |
{'ok', RestData} |
{'error', Reason} |
{'error', {Reason, RestData}}
- when
- Socket :: socket(),
- Data :: iodata(),
- Dest :: sockaddr(),
- RestData :: binary(),
- Reason :: posix() | 'closed' | invalid();
+ when
+ Socket :: socket(),
+ Data :: iodata(),
+ Dest :: sockaddr(),
+ RestData :: binary(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Data, Dest, Timeout :: non_neg_integer()) ->
'ok' |
@@ -2224,12 +2361,12 @@ sendto(Socket, Data, Dest_Cont) ->
{'select', {SelectInfo, RestData}} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- Cont :: select_info(),
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Data :: iodata(),
+ Cont :: select_info(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Data, Cont, SelectHandle :: select_handle()) ->
'ok' |
@@ -2238,12 +2375,12 @@ sendto(Socket, Data, Dest_Cont) ->
{'select', {SelectInfo, RestData}} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- Cont :: select_info(),
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Data :: iodata(),
+ Cont :: select_info(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Data, Cont, Timeout :: 'infinity') ->
'ok' |
@@ -2301,35 +2438,39 @@ sendto(Socket, Data, Dest, Timeout) ->
sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT, Timeout).
--spec sendto(Socket, Data, Dest, Flags, SelectHandle :: 'nowait') ->
+-spec sendto(Socket, Data, Dest, Flags, Handle :: 'nowait') ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- Dest :: sockaddr(),
- Flags :: [msg_flag() | integer()],
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Data, Dest, Flags, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Data :: iodata(),
+ Dest :: sockaddr(),
+ Flags :: [msg_flag() | integer()],
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Data, Dest, Flags, Handle :: select_handle() | completion_handle()) ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason}
when
- Socket :: socket(),
- Data :: iodata(),
- Dest :: sockaddr(),
- Flags :: [msg_flag() | integer()],
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Data :: iodata(),
+ Dest :: sockaddr(),
+ Flags :: [msg_flag() | integer()],
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Data, Dest, Flags, Timeout :: 'infinity') ->
'ok' |
@@ -2366,9 +2507,9 @@ sendto(?socket(SockRef), Data, Dest, Flags, Timeout)
nowait ->
SelectHandle = make_ref(),
sendto_nowait(SockRef, Data, Dest, Flags, SelectHandle);
- select_handle ->
- SelectHandle = Timeout,
- sendto_nowait(SockRef, Data, Dest, Flags, SelectHandle);
+ handle ->
+ Handle = Timeout,
+ sendto_nowait(SockRef, Data, Dest, Flags, Handle);
Deadline ->
HasWritten = false,
sendto_deadline(SockRef, Data, Dest, Flags, Deadline, HasWritten)
@@ -2395,37 +2536,38 @@ sendto_timeout_cont(SockRef, Bin, Cont, Timeout) ->
nowait ->
SelectHandle = make_ref(),
sendto_nowait_cont(SockRef, Bin, Cont, SelectHandle);
- select_handle ->
- SelectHandle = Timeout,
- sendto_nowait_cont(SockRef, Bin, Cont, SelectHandle);
+ handle ->
+ Handle = Timeout,
+ sendto_nowait_cont(SockRef, Bin, Cont, Handle);
Deadline ->
HasWritten = false,
sendto_deadline_cont(SockRef, Bin, Cont, Deadline, HasWritten)
end.
-sendto_nowait(SockRef, Bin, To, Flags, SelectHandle) ->
+sendto_nowait(SockRef, Bin, To, Flags, Handle) ->
send_common_nowait_result(
- SelectHandle, sendto,
- prim_socket:sendto(SockRef, Bin, To, Flags, SelectHandle)).
+ Handle, sendto,
+ prim_socket:sendto(SockRef, Bin, To, Flags, Handle)).
-sendto_nowait_cont(SockRef, Bin, Cont, SelectHandle) ->
+sendto_nowait_cont(SockRef, Bin, Cont, Handle) ->
send_common_nowait_result(
- SelectHandle, sendto,
- prim_socket:sendto(SockRef, Bin, Cont, SelectHandle)).
+ Handle, sendto,
+ prim_socket:sendto(SockRef, Bin, Cont, Handle)).
sendto_deadline(SockRef, Bin, To, Flags, Deadline, HasWritten) ->
- SelectHandle = make_ref(),
+ Handle = make_ref(),
send_common_deadline_result(
- SockRef, Bin, SelectHandle, Deadline, HasWritten,
+ SockRef, Bin, Handle, Deadline, HasWritten,
sendto, fun sendto_deadline_cont/5,
- prim_socket:sendto(SockRef, Bin, To, Flags, SelectHandle)).
+ prim_socket:sendto(SockRef, Bin, To, Flags, Handle)).
sendto_deadline_cont(SockRef, Bin, Cont, Deadline, HasWritten) ->
- SelectHandle = make_ref(),
+ Handle = make_ref(),
send_common_deadline_result(
- SockRef, Bin, SelectHandle, Deadline, HasWritten,
+ SockRef, Bin, Handle, Deadline, HasWritten,
sendto, fun sendto_deadline_cont/5,
- prim_socket:sendto(SockRef, Bin, Cont, SelectHandle)).
+ prim_socket:sendto(SockRef, Bin, Cont, Handle)).
+
%% ---------------------------------------------------------------------------
%%
@@ -2480,28 +2622,32 @@ sendmsg(Socket, Msg) ->
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, RestData}}
when
- Socket :: socket(),
- Msg :: msg_send(),
- RestData :: erlang:iovec(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Msg, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Msg :: msg_send(),
+ RestData :: erlang:iovec(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Msg, Handle :: select_handle() | completion_handle()) ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, RestData}}
when
- Socket :: socket(),
- Msg :: msg_send(),
- RestData :: erlang:iovec(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Msg :: msg_send(),
+ RestData :: erlang:iovec(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Msg, Timeout :: 'infinity') ->
'ok' |
@@ -2533,35 +2679,39 @@ sendmsg(Socket, Msg, Timeout) ->
sendmsg(Socket, Msg, ?ESOCK_SENDMSG_FLAGS_DEFAULT, Timeout).
--spec sendmsg(Socket, Msg, Flags, SelectHandle :: 'nowait') ->
+-spec sendmsg(Socket, Msg, Flags, Timeout :: 'nowait') ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, RestData}}
when
- Socket :: socket(),
- Msg :: msg_send(),
- Flags :: [msg_flag() | integer()],
- RestData :: erlang:iovec(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Msg, Flags, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Msg :: msg_send(),
+ Flags :: [msg_flag() | integer()],
+ RestData :: erlang:iovec(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Msg, Flags, Handle :: select_handle() | completion_handle()) ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, RestData}}
when
- Socket :: socket(),
- Msg :: msg_send(),
- Flags :: [msg_flag() | integer()],
- RestData :: erlang:iovec(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Msg :: msg_send(),
+ Flags :: [msg_flag() | integer()],
+ RestData :: erlang:iovec(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Msg, Flags, Timeout :: 'infinity') ->
'ok' |
@@ -2587,20 +2737,22 @@ sendmsg(Socket, Msg, Timeout) ->
RestData :: erlang:iovec(),
Reason :: posix() | 'closed' | invalid();
- (Socket, Data, Cont, SelectHandle :: 'nowait') ->
+ (Socket, Data, Cont, Timeout :: 'nowait') ->
'ok' |
{'ok', RestData} |
{'select', SelectInfo} |
{'select', {SelectInfo, RestData}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, RestData}}
when
- Socket :: socket(),
- Data :: msg_send() | erlang:iovec(),
- Cont :: select_info(),
- RestData :: erlang:iovec(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Data :: msg_send() | erlang:iovec(),
+ Cont :: select_info(),
+ RestData :: erlang:iovec(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Data, Cont, SelectHandle :: select_handle()) ->
'ok' |
@@ -2664,11 +2816,11 @@ sendmsg(?socket(SockRef), #{iov := IOV} = Msg, Flags, Timeout)
invalid ->
erlang:error({invalid, {timeout, Timeout}});
nowait ->
- SelectHandle = make_ref(),
- sendmsg_nowait(SockRef, Msg, Flags, SelectHandle, IOV);
- select_handle ->
- SelectHandle = Timeout,
- sendmsg_nowait(SockRef, Msg, Flags, SelectHandle, IOV);
+ Handle = make_ref(),
+ sendmsg_nowait(SockRef, Msg, Flags, Handle, IOV);
+ handle ->
+ Handle = Timeout,
+ sendmsg_nowait(SockRef, Msg, Flags, Handle, IOV);
Deadline ->
HasWritten = false,
sendmsg_deadline(SockRef, Msg, Flags, Deadline, HasWritten, IOV)
@@ -2683,7 +2835,7 @@ sendmsg_timeout_cont(SockRef, RestData, Cont, Timeout) ->
nowait ->
SelectHandle = make_ref(),
sendmsg_nowait_cont(SockRef, RestData, Cont, SelectHandle);
- select_handle ->
+ handle ->
SelectHandle = Timeout,
sendmsg_nowait_cont(SockRef, RestData, Cont, SelectHandle);
Deadline ->
@@ -2692,10 +2844,10 @@ sendmsg_timeout_cont(SockRef, RestData, Cont, Timeout) ->
SockRef, RestData, Cont, Deadline, HasWritten)
end.
-sendmsg_nowait(SockRef, Msg, Flags, SelectHandle, IOV) ->
+sendmsg_nowait(SockRef, Msg, Flags, Handle, IOV) ->
send_common_nowait_result(
- SelectHandle, sendmsg,
- prim_socket:sendmsg(SockRef, Msg, Flags, SelectHandle, IOV)).
+ Handle, sendmsg,
+ prim_socket:sendmsg(SockRef, Msg, Flags, Handle, IOV)).
sendmsg_nowait_cont(SockRef, RestData, Cont, SelectHandle) ->
send_common_nowait_result(
@@ -2703,11 +2855,11 @@ sendmsg_nowait_cont(SockRef, RestData, Cont, SelectHandle) ->
prim_socket:sendmsg(SockRef, RestData, Cont, SelectHandle)).
sendmsg_deadline(SockRef, Msg, Flags, Deadline, HasWritten, IOV) ->
- SelectHandle = make_ref(),
+ Handle = make_ref(),
send_common_deadline_result(
- SockRef, IOV, SelectHandle, Deadline, HasWritten,
+ SockRef, IOV, Handle, Deadline, HasWritten,
sendmsg, fun sendmsg_deadline_cont/5,
- prim_socket:sendmsg(SockRef, Msg, Flags, SelectHandle, IOV)).
+ prim_socket:sendmsg(SockRef, Msg, Flags, Handle, IOV)).
sendmsg_deadline_cont(SockRef, Data, Cont, Deadline, HasWritten) ->
SelectHandle = make_ref(),
@@ -2716,6 +2868,7 @@ sendmsg_deadline_cont(SockRef, Data, Cont, Deadline, HasWritten) ->
sendmsg, fun sendmsg_deadline_cont/5,
prim_socket:sendmsg(SockRef, Data, Cont, SelectHandle)).
+
%% ===========================================================================
%%
%% sendfile - send a file on a socket
@@ -2901,7 +3054,7 @@ sendfile_int(SockRef, State, Timeout) ->
nowait ->
SelectHandle = make_ref(),
sendfile_nowait(SockRef, State, SelectHandle);
- select_handle ->
+ handle ->
SelectHandle = Timeout,
sendfile_nowait(SockRef, State, SelectHandle);
Deadline ->
@@ -3043,52 +3196,55 @@ recv(Socket) ->
recv(Socket, Flags) when is_list(Flags) ->
recv(Socket, 0, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
-recv(Socket, Length) ->
- recv(
- Socket, Length,
- ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
+recv(Socket, Length) when is_integer(Length) andalso (Length >= 0) ->
+ recv(Socket, Length,
+ ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
--spec recv(Socket, Flags, SelectHandle :: 'nowait') ->
+-spec recv(Socket, Flags, Handle :: 'nowait') ->
{'ok', Data} |
{'select', SelectInfo} |
{'select', {SelectInfo, Data}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
- Flags :: [msg_flag() | integer()],
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Flags, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Flags :: [msg_flag() | integer()],
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Flags, Handle :: select_handle() | completion_handle()) ->
{'ok', Data} |
{'select', SelectInfo} |
{'select', {SelectInfo, Data}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
- Flags :: [msg_flag() | integer()],
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Flags :: [msg_flag() | integer()],
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Flags, Timeout :: 'infinity') ->
{'ok', Data} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
+ Socket :: socket(),
Flags :: [msg_flag() | integer()],
- Data :: binary(),
- Reason :: posix() | 'closed' | invalid();
+ Data :: binary(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Flags, Timeout :: non_neg_integer()) ->
{'ok', Data} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
+ Socket :: socket(),
Flags :: [msg_flag() | integer()],
- Data :: binary(),
- Reason :: posix() | 'closed' | invalid() | 'timeout';
+ Data :: binary(),
+ Reason :: posix() | 'closed' | invalid() | 'timeout';
(Socket, Length, Flags) ->
{'ok', Data} |
@@ -3100,47 +3256,51 @@ recv(Socket, Length) ->
Data :: binary(),
Reason :: posix() | 'closed' | invalid();
- (Socket, Length, SelectHandle :: 'nowait') ->
+ (Socket, Length, Handle :: 'nowait') ->
{'ok', Data} |
{'select', SelectInfo} |
{'select', {SelectInfo, Data}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Length, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Length, Handle :: select_handle() | completion_handle()) ->
{'ok', Data} |
{'select', SelectInfo} |
{'select', {SelectInfo, Data}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Length, Timeout :: 'infinity') ->
{'ok', Data} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Data :: binary(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Data :: binary(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Length, Timeout :: non_neg_integer()) ->
{'ok', Data} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Data :: binary(),
- Reason :: posix() | 'closed' | invalid() | 'timeout'.
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Data :: binary(),
+ Reason :: posix() | 'closed' | invalid() | 'timeout'.
recv(Socket, Flags, Timeout) when is_list(Flags) ->
recv(Socket, 0, Flags, Timeout);
@@ -3149,31 +3309,35 @@ recv(Socket, Length, Flags) when is_list(Flags) ->
recv(Socket, Length, Timeout) ->
recv(Socket, Length, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
--spec recv(Socket, Length, Flags, SelectHandle :: 'nowait') ->
+-spec recv(Socket, Length, Flags, Handle :: 'nowait') ->
{'ok', Data} |
{'select', SelectInfo} |
{'select', {SelectInfo, Data}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Flags :: [msg_flag() | integer()],
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Length, Flags, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: [msg_flag() | integer()],
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Length, Flags, Handle :: select_handle() | completion_handle()) ->
{'ok', Data} |
{'select', SelectInfo} |
{'select', {SelectInfo, Data}} |
+ {'completion', CompletionInfo} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Flags :: [msg_flag() | integer()],
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: [msg_flag() | integer()],
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Length, Flags, Timeout :: 'infinity') ->
{'ok', Data} |
@@ -3189,11 +3353,11 @@ recv(Socket, Length, Timeout) ->
{'ok', Data} |
{'error', Reason} |
{'error', {Reason, Data}} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Flags :: [msg_flag() | integer()],
- Data :: binary(),
- Reason :: posix() | 'closed' | invalid() | 'timeout'.
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: [msg_flag() | integer()],
+ Data :: binary(),
+ Reason :: posix() | 'closed' | invalid() | 'timeout'.
recv(?socket(SockRef), Length, Flags, Timeout)
when is_reference(SockRef),
@@ -3203,11 +3367,11 @@ recv(?socket(SockRef), Length, Flags, Timeout)
invalid ->
erlang:error({invalid, {timeout, Timeout}});
nowait ->
- SelectHandle = make_ref(),
- recv_nowait(SockRef, Length, Flags, SelectHandle, <<>>);
- select_handle ->
- SelectHandle = Timeout,
- recv_nowait(SockRef, Length, Flags, SelectHandle, <<>>);
+ Handle = make_ref(),
+ recv_nowait(SockRef, Length, Flags, Handle, <<>>);
+ handle ->
+ Handle = Timeout,
+ recv_nowait(SockRef, Length, Flags, Handle, <<>>);
zero ->
case prim_socket:recv(SockRef, Length, Flags, zero) of
ok ->
@@ -3224,8 +3388,8 @@ recv(Socket, Length, Flags, Timeout) ->
%% We will only recurse with Length == 0 if Length is 0,
%% so Length == 0 means to return all available data also when recursing
-recv_nowait(SockRef, Length, Flags, SelectHandle, Acc) ->
- case prim_socket:recv(SockRef, Length, Flags, SelectHandle) of
+recv_nowait(SockRef, Length, Flags, Handle, Acc) ->
+ case prim_socket:recv(SockRef, Length, Flags, Handle) of
{more, Bin} ->
%% We got what we requested but will not waste more time
%% although there might be more data available
@@ -3233,23 +3397,28 @@ recv_nowait(SockRef, Length, Flags, SelectHandle, Acc) ->
{select, Bin} ->
%% We got less than requested so the caller will
%% get a select message when there might be more to read
- {select, {?SELECT_INFO(recv, SelectHandle), bincat(Acc, Bin)}};
+ {select, {?SELECT_INFO(recv, Handle), bincat(Acc, Bin)}};
select ->
%% The caller will get a select message when there
%% might be data to read
if
byte_size(Acc) =:= 0 ->
- {select, ?SELECT_INFO(recv, SelectHandle)};
+ {select, ?SELECT_INFO(recv, Handle)};
true ->
- {select, {?SELECT_INFO(recv, SelectHandle), Acc}}
+ {select, {?SELECT_INFO(recv, Handle), Acc}}
end;
+ completion ->
+ %% The caller will get a completion message (with the
+ %% result) when the data arrives. *No* further action
+ %% is required.
+ {completion, ?COMPLETION_INFO(recv, Handle)};
Result ->
recv_result(Acc, Result)
end.
recv_deadline(SockRef, Length, Flags, Deadline, Acc) ->
- SelectHandle = make_ref(),
- case prim_socket:recv(SockRef, Length, Flags, SelectHandle) of
+ Handle = make_ref(),
+ case prim_socket:recv(SockRef, Length, Flags, Handle) of
{more, Bin} ->
%% There is more data readily available
%% - repeat unless time's up
@@ -3262,12 +3431,13 @@ recv_deadline(SockRef, Length, Flags, Deadline, Acc) ->
true ->
{ok, bincat(Acc, Bin)}
end;
+
%%
{select, Bin} ->
%% We got less than requested
Timeout = timeout(Deadline),
receive
- ?socket_msg(?socket(SockRef), select, SelectHandle) ->
+ ?socket_msg(?socket(SockRef), select, Handle) ->
if
0 < Timeout ->
%% Recv more
@@ -3277,10 +3447,10 @@ recv_deadline(SockRef, Length, Flags, Deadline, Acc) ->
true ->
{error, {timeout, bincat(Acc, Bin)}}
end;
- ?socket_msg(_Socket, abort, {SelectHandle, Reason}) ->
+ ?socket_msg(_Socket, abort, {Handle, Reason}) ->
{error, {Reason, bincat(Acc, Bin)}}
after Timeout ->
- _ = cancel(SockRef, recv, SelectHandle),
+ _ = cancel(SockRef, recv, Handle),
{error, {timeout, bincat(Acc, Bin)}}
end;
%%
@@ -3288,14 +3458,15 @@ recv_deadline(SockRef, Length, Flags, Deadline, Acc) ->
%% We first got some data and are then asked to wait,
%% but we only want the first that comes
%% - cancel and return what we have
- _ = cancel(SockRef, recv, SelectHandle),
+ _ = cancel(SockRef, recv, Handle),
{ok, Acc};
+ %%
select ->
%% There is nothing just now, but we will be notified when there
%% is something to read (a select message).
Timeout = timeout(Deadline),
receive
- ?socket_msg(?socket(SockRef), select, SelectHandle) ->
+ ?socket_msg(?socket(SockRef), select, Handle) ->
if
0 < Timeout ->
%% Retry
@@ -3304,12 +3475,62 @@ recv_deadline(SockRef, Length, Flags, Deadline, Acc) ->
true ->
recv_error(Acc, timeout)
end;
- ?socket_msg(_Socket, abort, {SelectHandle, Reason}) ->
+ ?socket_msg(_Socket, abort, {Handle, Reason}) ->
recv_error(Acc, Reason)
after Timeout ->
- _ = cancel(SockRef, recv, SelectHandle),
+ _ = cancel(SockRef, recv, Handle),
recv_error(Acc, timeout)
end;
+
+ %%
+ completion ->
+ %% There is nothing just now, but we will be notified when the
+ %% data has been read (with a completion message).
+ Timeout = timeout(Deadline),
+ receive
+ ?socket_msg(?socket(SockRef), completion,
+ {Handle, {ok, _Bin} = OK})
+ when (Length =:= 0) ->
+ recv_result(Acc, OK);
+ ?socket_msg(?socket(SockRef), completion,
+ {Handle, {ok, Bin} = OK})
+ when (Length =:= byte_size(Bin)) ->
+ recv_result(Acc, OK);
+ ?socket_msg(?socket(SockRef), completion,
+ {Handle, {ok, Bin}}) ->
+ if
+ 0 < Timeout ->
+ %% Recv more
+ recv_deadline(
+ SockRef, Length - byte_size(Bin), Flags,
+ Deadline, bincat(Acc, Bin));
+ true ->
+ {error, {timeout, bincat(Acc, Bin)}}
+ end;
+ ?socket_msg(?socket(SockRef), completion,
+ {Handle, {error, Reason}}) ->
+ recv_error(Acc, Reason);
+ ?socket_msg(_Socket, abort, {Handle, Reason}) ->
+ recv_error(Acc, Reason)
+ after Timeout ->
+ _ = cancel(SockRef, recv, Handle),
+ recv_error(Acc, timeout)
+ end;
+
+ %% We got some data, but not all
+ {ok, Bin} when (Length > byte_size(Bin)) ->
+ Timeout = timeout(Deadline),
+ if
+ 0 < Timeout ->
+ %% Recv more
+ recv_deadline(
+ SockRef, Length - byte_size(Bin), Flags,
+ Deadline, bincat(Acc, Bin));
+ true ->
+ {error, {timeout, bincat(Acc, Bin)}}
+ end;
+
+ %%
Result ->
recv_result(Acc, Result)
end.
@@ -3332,6 +3553,7 @@ recv_error(Acc, Reason) ->
{error, {Reason, Acc}}
end.
+
%% ---------------------------------------------------------------------------
%%
%% With recvfrom we get messages, which means that regardless of how
@@ -3382,27 +3604,31 @@ recvfrom(Socket, BufSz) ->
?ESOCK_RECV_FLAGS_DEFAULT,
?ESOCK_RECV_TIMEOUT_DEFAULT).
--spec recvfrom(Socket, Flags, SelectHandle :: 'nowait') ->
+-spec recvfrom(Socket, Flags, Handle :: 'nowait') ->
{'ok', {Source, Data}} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- Flags :: [msg_flag() | integer()],
- Source :: sockaddr_recv(),
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Flags, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Flags :: [msg_flag() | integer()],
+ Source :: sockaddr_recv(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Flags, Handle :: select_handle() | completion_handle()) ->
{'ok', {Source, Data}} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- Flags :: [msg_flag() | integer()],
- Source :: sockaddr_recv(),
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Flags :: [msg_flag() | integer()],
+ Source :: sockaddr_recv(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Flags, Timeout :: 'infinity') ->
{'ok', {Source, Data}} |
@@ -3432,27 +3658,31 @@ recvfrom(Socket, BufSz) ->
Data :: binary(),
Reason :: posix() | 'closed' | invalid();
- (Socket, BufSz, SelectHandle :: 'nowait') ->
+ (Socket, BufSz, Handle :: 'nowait') ->
{'ok', {Source, Data}} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- Source :: sockaddr_recv(),
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, BufSz, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Source :: sockaddr_recv(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, BufSz, Handle :: select_handle() | completion_handle()) ->
{'ok', {Source, Data}} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- Source :: sockaddr_recv(),
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Source :: sockaddr_recv(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, BufSz, Timeout :: 'infinity') ->
{'ok', {Source, Data}} |
@@ -3479,29 +3709,33 @@ recvfrom(Socket, BufSz, Flags) when is_list(Flags) ->
recvfrom(Socket, BufSz, Timeout) ->
recvfrom(Socket, BufSz, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
--spec recvfrom(Socket, BufSz, Flags, SelectHandle :: 'nowait') ->
+-spec recvfrom(Socket, BufSz, Flags, Handle :: 'nowait') ->
{'ok', {Source, Data}} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- Flags :: [msg_flag() | integer()],
- Source :: sockaddr_recv(),
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, BufSz, Flags, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Flags :: [msg_flag() | integer()],
+ Source :: sockaddr_recv(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, BufSz, Flags, Handle :: select_handle() | completion_handle()) ->
{'ok', {Source, Data}} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- Flags :: [msg_flag() | integer()],
- Source :: sockaddr_recv(),
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Flags :: [msg_flag() | integer()],
+ Source :: sockaddr_recv(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, BufSz, Flags, Timeout :: 'infinity') ->
{'ok', {Source, Data}} |
@@ -3531,11 +3765,11 @@ recvfrom(?socket(SockRef), BufSz, Flags, Timeout)
invalid ->
erlang:error({invalid, {timeout, Timeout}});
nowait ->
- SelectHandle = make_ref(),
- recvfrom_nowait(SockRef, BufSz, SelectHandle, Flags);
- select_handle ->
- SelectHandle = Timeout,
- recvfrom_nowait(SockRef, BufSz, SelectHandle, Flags);
+ Handle = make_ref(),
+ recvfrom_nowait(SockRef, BufSz, Handle, Flags);
+ handle ->
+ Handle = Timeout,
+ recvfrom_nowait(SockRef, BufSz, Handle, Flags);
zero ->
case prim_socket:recvfrom(SockRef, BufSz, Flags, zero) of
ok ->
@@ -3549,30 +3783,48 @@ recvfrom(?socket(SockRef), BufSz, Flags, Timeout)
recvfrom(Socket, BufSz, Flags, Timeout) ->
erlang:error(badarg, [Socket, BufSz, Flags, Timeout]).
-recvfrom_nowait(SockRef, BufSz, SelectHandle, Flags) ->
- case prim_socket:recvfrom(SockRef, BufSz, Flags, SelectHandle) of
- select ->
- {select, ?SELECT_INFO(recvfrom, SelectHandle)};
+recvfrom_nowait(SockRef, BufSz, Handle, Flags) ->
+ case prim_socket:recvfrom(SockRef, BufSz, Flags, Handle) of
+ select = Tag ->
+ {Tag, ?SELECT_INFO(recvfrom, Handle)};
+ completion = Tag ->
+ {Tag, ?COMPLETION_INFO(recvfrom, Handle)};
Result ->
recvfrom_result(Result)
end.
recvfrom_deadline(SockRef, BufSz, Flags, Deadline) ->
- SelectHandle = make_ref(),
- case prim_socket:recvfrom(SockRef, BufSz, Flags, SelectHandle) of
+ Handle = make_ref(),
+ case prim_socket:recvfrom(SockRef, BufSz, Flags, Handle) of
select ->
%% There is nothing just now, but we will be notified when there
%% is something to read (a select message).
Timeout = timeout(Deadline),
receive
- ?socket_msg(?socket(SockRef), select, SelectHandle) ->
+ ?socket_msg(?socket(SockRef), select, Handle) ->
recvfrom_deadline(SockRef, BufSz, Flags, Deadline);
- ?socket_msg(_Socket, abort, {SelectHandle, Reason}) ->
+ ?socket_msg(_Socket, abort, {Handle, Reason}) ->
{error, Reason}
after Timeout ->
- _ = cancel(SockRef, recvfrom, SelectHandle),
+ _ = cancel(SockRef, recvfrom, Handle),
{error, timeout}
end;
+
+ completion ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a completion message).
+ Timeout = timeout(Deadline),
+ receive
+ ?socket_msg(?socket(SockRef), completion,
+ {Handle, CompletionStatus}) ->
+ recvfrom_result(CompletionStatus);
+ ?socket_msg(_Socket, abort, {Handle, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ _ = cancel(SockRef, recvfrom, Handle),
+ {error, timeout}
+ end;
+
Result ->
recvfrom_result(Result)
end.
@@ -3612,20 +3864,24 @@ recvmsg(Socket) ->
(Socket, Timeout :: 'nowait') ->
{'ok', Msg} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- Msg :: msg_recv(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Msg :: msg_recv(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
- (Socket, SelectHandle :: select_handle()) ->
+ (Socket, Handle :: select_handle() | completion_handle()) ->
{'ok', Msg} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- Msg :: msg_recv(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Msg :: msg_recv(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Timeout :: 'infinity') ->
{'ok', Msg} |
@@ -3647,27 +3903,31 @@ recvmsg(Socket, Timeout) ->
recvmsg(Socket, 0, 0, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
--spec recvmsg(Socket, BufSz, CtrlSz, SelectHandle :: 'nowait') ->
+-spec recvmsg(Socket, BufSz, CtrlSz, Timeout :: 'nowait') ->
{'ok', Msg} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- CtrlSz :: non_neg_integer(),
- Msg :: msg_recv(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, BufSz, CtrlSz, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ Msg :: msg_recv(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, BufSz, CtrlSz, Handle :: select_handle() | completion_handle()) ->
{'ok', Msg} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- CtrlSz :: non_neg_integer(),
- Msg :: msg_recv(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ Msg :: msg_recv(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, BufSz, CtrlSz, Timeout :: 'infinity') ->
{'ok', Msg} |
@@ -3694,22 +3954,26 @@ recvmsg(Socket, BufSz, CtrlSz, Timeout) ->
-spec recvmsg(Socket, Flags, Timeout :: 'nowait') ->
{'ok', Msg} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- Flags :: [msg_flag() | integer()],
- Msg :: msg_recv(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, Flags, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ Flags :: [msg_flag() | integer()],
+ Msg :: msg_recv(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, Flags, Handle :: select_handle() | completion_handle()) ->
{'ok', Msg} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- Flags :: [msg_flag() | integer()],
- Msg :: msg_recv(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ Flags :: [msg_flag() | integer()],
+ Msg :: msg_recv(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, Flags, Timeout :: 'infinity') ->
{'ok', Msg} |
@@ -3743,29 +4007,33 @@ recvmsg(Socket, BufSz, CtrlSz) when is_integer(BufSz), is_integer(CtrlSz) ->
?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
--spec recvmsg(Socket, BufSz, CtrlSz, Flags, SelectHandle :: 'nowait') ->
+-spec recvmsg(Socket, BufSz, CtrlSz, Flags, Timeout :: 'nowait') ->
{'ok', Msg} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- CtrlSz :: non_neg_integer(),
- Flags :: [msg_flag() | integer()],
- Msg :: msg_recv(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
-
- (Socket, BufSz, CtrlSz, Flags, SelectHandle :: select_handle()) ->
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ Flags :: [msg_flag() | integer()],
+ Msg :: msg_recv(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
+
+ (Socket, BufSz, CtrlSz, Flags, Handle :: select_handle() | completion_handle()) ->
{'ok', Msg} |
{'select', SelectInfo} |
+ {'completion', CompletionInfo} |
{'error', Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- CtrlSz :: non_neg_integer(),
- Flags :: [msg_flag() | integer()],
- Msg :: msg_recv(),
- SelectInfo :: select_info(),
- Reason :: posix() | 'closed' | invalid();
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ Flags :: [msg_flag() | integer()],
+ Msg :: msg_recv(),
+ SelectInfo :: select_info(),
+ CompletionInfo :: completion_info(),
+ Reason :: posix() | 'closed' | invalid();
(Socket, BufSz, CtrlSz, Flags, Timeout :: 'infinity') ->
{'ok', Msg} |
@@ -3796,11 +4064,11 @@ recvmsg(?socket(SockRef), BufSz, CtrlSz, Flags, Timeout)
invalid ->
erlang:error({invalid, {timeout, Timeout}});
nowait ->
- SelectHandle = make_ref(),
- recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags, SelectHandle);
- select_handle ->
- SelectHandle = Timeout,
- recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags, SelectHandle);
+ Handle = make_ref(),
+ recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags, Handle);
+ handle ->
+ Handle = Timeout,
+ recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags, Handle);
zero ->
case prim_socket:recvmsg(SockRef, BufSz, CtrlSz, Flags, zero) of
ok ->
@@ -3814,36 +4082,55 @@ recvmsg(?socket(SockRef), BufSz, CtrlSz, Flags, Timeout)
recvmsg(Socket, BufSz, CtrlSz, Flags, Timeout) ->
erlang:error(badarg, [Socket, BufSz, CtrlSz, Flags, Timeout]).
-recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags, SelectHandle) ->
- case prim_socket:recvmsg(SockRef, BufSz, CtrlSz, Flags, SelectHandle) of
+recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags, Handle) ->
+ case prim_socket:recvmsg(SockRef, BufSz, CtrlSz, Flags, Handle) of
select ->
- {select, ?SELECT_INFO(recvmsg, SelectHandle)};
+ {select, ?SELECT_INFO(recvmsg, Handle)};
+ completion ->
+ {completion, ?COMPLETION_INFO(recvmsg, Handle)};
Result ->
recvmsg_result(Result)
end.
recvmsg_deadline(SockRef, BufSz, CtrlSz, Flags, Deadline) ->
- SelectHandle = make_ref(),
- case prim_socket:recvmsg(SockRef, BufSz, CtrlSz, Flags, SelectHandle) of
+ Handle = make_ref(),
+ case prim_socket:recvmsg(SockRef, BufSz, CtrlSz, Flags, Handle) of
select ->
%% There is nothing just now, but we will be notified when there
%% is something to read (a select message).
Timeout = timeout(Deadline),
receive
- ?socket_msg(?socket(SockRef), select, SelectHandle) ->
+ ?socket_msg(?socket(SockRef), select, Handle) ->
recvmsg_deadline(
SockRef, BufSz, CtrlSz, Flags, Deadline);
- ?socket_msg(_Socket, abort, {SelectHandle, Reason}) ->
+ ?socket_msg(_Socket, abort, {Handle, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ _ = cancel(SockRef, recvmsg, Handle),
+ {error, timeout}
+ end;
+
+ completion ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a completion message).
+ Timeout = timeout(Deadline),
+ receive
+ ?socket_msg(?socket(SockRef), completion,
+ {Handle, CompletionStatus}) ->
+ recvmsg_result(CompletionStatus);
+ ?socket_msg(_Socket, abort, {Handle, Reason}) ->
{error, Reason}
after Timeout ->
- _ = cancel(SockRef, recvmsg, SelectHandle),
+ _ = cancel(SockRef, recvmsg, Handle),
{error, timeout}
end;
+
Result ->
recvmsg_result(Result)
end.
recvmsg_result(Result) ->
+ %% ?DBG([{result, Result}]),
case Result of
{ok, _Msg} = OK ->
OK;
@@ -4258,9 +4545,14 @@ ioctl(Socket, SetRequest, Arg1, Arg2) ->
%%
-spec cancel(Socket, SelectInfo) -> 'ok' | {'error', Reason} when
- Socket :: socket(),
- SelectInfo :: select_info(),
- Reason :: 'closed' | invalid().
+ Socket :: socket(),
+ SelectInfo :: select_info(),
+ Reason :: 'closed' | invalid();
+
+ (Socket, CompletionInfo) -> 'ok' | {'error', Reason} when
+ Socket :: socket(),
+ CompletionInfo :: completion_info(),
+ Reason :: 'closed' | invalid().
cancel(?socket(SockRef), ?SELECT_INFO(SelectTag, SelectHandle) = SelectInfo)
when is_reference(SockRef) ->
@@ -4278,21 +4570,43 @@ cancel(?socket(SockRef), ?SELECT_INFO(SelectTag, SelectHandle) = SelectInfo)
Result ->
Result
end;
-cancel(Socket, SelectInfo) ->
- erlang:error(badarg, [Socket, SelectInfo]).
+cancel(?socket(SockRef),
+ ?COMPLETION_INFO(CompletionTag, CompletionHandle) = CompletionInfo)
+ when is_reference(SockRef) ->
+ case CompletionTag of
+ {Op, _} when is_atom(Op) ->
+ ok;
+ Op when is_atom(Op) ->
+ ok
+ end,
+ case cancel(SockRef, Op, CompletionHandle) of
+ ok ->
+ ok;
+ invalid ->
+ {error, {invalid, CompletionInfo}};
+ Result ->
+ Result
+ end;
+cancel(Socket, Info) ->
+ erlang:error(badarg, [Socket, Info]).
-cancel(SockRef, Op, SelectHandle) ->
- case prim_socket:cancel(SockRef, Op, SelectHandle) of
+cancel(SockRef, Op, Handle) ->
+ case prim_socket:cancel(SockRef, Op, Handle) of
select_sent ->
- flush_select_msg(SockRef, SelectHandle),
- _ = flush_abort_msg(SockRef, SelectHandle),
+ _ = flush_select_msg(SockRef, Handle),
+ _ = flush_abort_msg(SockRef, Handle),
ok;
not_found ->
- _ = flush_abort_msg(SockRef, SelectHandle),
+ _ = flush_completion_msg(SockRef, Handle),
+ _ = flush_abort_msg(SockRef, Handle),
invalid;
Result ->
- _ = flush_abort_msg(SockRef, SelectHandle),
+ %% Since we do not actually if we are using
+ %% select or completion here, so flush both...
+ _ = flush_select_msg(SockRef, Handle),
+ _ = flush_completion_msg(SockRef, Handle),
+ _ = flush_abort_msg(SockRef, Handle),
Result
end.
@@ -4304,6 +4618,14 @@ flush_select_msg(SockRef, Ref) ->
ok
end.
+flush_completion_msg(SockRef, Ref) ->
+ receive
+ ?socket_msg(?socket(SockRef), completion, {Ref, Result}) ->
+ Result
+ after 0 ->
+ ok
+ end.
+
flush_abort_msg(SockRef, Ref) ->
receive
?socket_msg(?socket(SockRef), abort, {Ref, Reason}) ->
@@ -4319,39 +4641,14 @@ flush_abort_msg(SockRef, Ref) ->
%%
%% ===========================================================================
-%% formated_timestamp() ->
-%% format_timestamp(os:timestamp()).
-
-%% format_timestamp(Now) ->
-%% N2T = fun(N) -> calendar:now_to_local_time(N) end,
-%% format_timestamp(Now, N2T, true).
-
-%% format_timestamp({_N1, _N2, N3} = N, N2T, true) ->
-%% FormatExtra = ".~.2.0w",
-%% ArgsExtra = [N3 div 10000],
-%% format_timestamp(N, N2T, FormatExtra, ArgsExtra);
-%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) ->
-%% FormatExtra = "",
-%% ArgsExtra = [],
-%% format_timestamp(N, N2T, FormatExtra, ArgsExtra).
-
-%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) ->
-%% {Date, Time} = N2T(N),
-%% {YYYY,MM,DD} = Date,
-%% {Hour,Min,Sec} = Time,
-%% FormatDate =
-%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra,
-%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra),
-%% lists:flatten(FormatDate).
-
deadline(Timeout) ->
case Timeout of
nowait ->
Timeout;
infinity ->
Timeout;
- SelectHandle when is_reference(SelectHandle) ->
- select_handle;
+ Handle when is_reference(Handle) ->
+ handle;
0 ->
zero;
_ when is_integer(Timeout), 0 < Timeout ->
@@ -4362,7 +4659,7 @@ deadline(Timeout) ->
timeout(Deadline) ->
case Deadline of
- %% nowait | select_handle shall not be passed here
+ %% nowait | handle shall not be passed here
%%
infinity ->
Deadline;
@@ -4392,6 +4689,39 @@ bincat(<<_/binary>> = A, <<_/binary>> = B) ->
f(F, A) ->
lists:flatten(io_lib:format(F, A)).
+%% mq() ->
+%% pi(messages).
+
+%% pi(Item) ->
+%% {Item, Val} = process_info(self(), Item),
+%% Val.
+
+
+%% formated_timestamp() ->
+%% format_timestamp(os:timestamp()).
+
+%% format_timestamp(Now) ->
+%% N2T = fun(N) -> calendar:now_to_local_time(N) end,
+%% format_timestamp(Now, N2T, true).
+
+%% format_timestamp({_N1, _N2, N3} = N, N2T, true) ->
+%% FormatExtra = ".~.2.0w",
+%% ArgsExtra = [N3 div 10000],
+%% format_timestamp(N, N2T, FormatExtra, ArgsExtra);
+%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) ->
+%% FormatExtra = "",
+%% ArgsExtra = [],
+%% format_timestamp(N, N2T, FormatExtra, ArgsExtra).
+
+%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) ->
+%% {Date, Time} = N2T(N),
+%% {YYYY,MM,DD} = Date,
+%% {Hour,Min,Sec} = Time,
+%% FormatDate =
+%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra,
+%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra),
+%% lists:flatten(FormatDate).
+
%% p(F) ->
%% p(F, []).
diff --git a/lib/kernel/src/standard_error.erl b/lib/kernel/src/standard_error.erl
index 1aad064392..4aa4907194 100644
--- a/lib/kernel/src/standard_error.erl
+++ b/lib/kernel/src/standard_error.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -66,6 +66,7 @@ server(PortName,PortSettings) ->
run(P) ->
put(encoding, latin1),
+ put(onlcr, false),
server_loop(P).
server_loop(Port) ->
@@ -161,7 +162,7 @@ io_request({get_geometry,rows},Port) ->
io_request(getopts, _Port) ->
getopts();
io_request({setopts,Opts}, _Port) when is_list(Opts) ->
- setopts(Opts);
+ do_setopts(Opts);
io_request({requests,Reqs}, Port) ->
io_requests(Reqs, {ok,ok}, Port);
io_request(R, _Port) -> %Unknown request
@@ -203,19 +204,27 @@ put_chars(Chars, Port) when is_binary(Chars) ->
{ok,ok}.
%% setopts
-setopts(Opts0) ->
+do_setopts(Opts0) ->
Opts = expand_encoding(Opts0),
case check_valid_opts(Opts) of
true ->
- do_setopts(Opts);
+ lists:foreach(
+ fun({encoding, Enc}) ->
+ put(encoding, Enc);
+ ({onlcr, Bool}) ->
+ put(onlcr, Bool)
+ end, Opts),
+ {ok, ok};
false ->
{error,{error,enotsup}}
end.
check_valid_opts([]) ->
true;
-check_valid_opts([{encoding,Valid}|T]) when Valid =:= unicode;
- Valid =:= utf8; Valid =:= latin1 ->
+check_valid_opts([{encoding,Valid}|T]) when Valid =:= unicode; Valid =:= utf8;
+ Valid =:= latin1 ->
+ check_valid_opts(T);
+check_valid_opts([{onlcr,Bool}|T]) when is_boolean(Bool) ->
check_valid_opts(T);
check_valid_opts(_) ->
false.
@@ -226,27 +235,21 @@ expand_encoding([latin1 | T]) ->
[{encoding,latin1} | expand_encoding(T)];
expand_encoding([unicode | T]) ->
[{encoding,unicode} | expand_encoding(T)];
+expand_encoding([utf8 | T]) ->
+ [{encoding,unicode} | expand_encoding(T)];
+expand_encoding([{encoding,utf8} | T]) ->
+ [{encoding,unicode} | expand_encoding(T)];
expand_encoding([H|T]) ->
[H|expand_encoding(T)].
-do_setopts(Opts) ->
- case proplists:get_value(encoding, Opts) of
- Valid when Valid =:= unicode; Valid =:= utf8 ->
- put(encoding, unicode);
- latin1 ->
- put(encoding, latin1);
- undefined ->
- ok
- end,
- {ok,ok}.
-
getopts() ->
Uni = {encoding,get(encoding)},
- {ok,[Uni]}.
+ Onlcr = {onlcr, get(onlcr)},
+ {ok,[Uni, Onlcr]}.
wrap_characters_to_binary(Chars,From,To) ->
- TrNl = (whereis(user_drv) =/= undefined),
- Limit = case To of
+ TrNl = get(onlcr),
+ Limit = case To of
latin1 ->
255;
_Else ->
diff --git a/lib/kernel/src/user.erl b/lib/kernel/src/user.erl
deleted file mode 100644
index 67c2eafdbe..0000000000
--- a/lib/kernel/src/user.erl
+++ /dev/null
@@ -1,769 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
-%%
-%% 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.
-%%
-%% %CopyrightEnd%
-%%
--module(user).
--compile(inline).
-
-%% Basic standard i/o server for user interface port.
-
--export([start/0, start/1, start_out/0]).
--export([interfaces/1]).
-
--define(NAME, user).
-
-%% Defines for control ops
--define(ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER, 16#018b0900).
--define(CTRL_OP_GET_WINSIZE, (100 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)).
-
-%%
-%% The basic server and start-up.
-%%
-
-start() ->
- start_port([eof,binary]).
-
-start([Mod,Fun|Args]) ->
- %% Mod,Fun,Args should return a pid. That process is supposed to act
- %% as the io port.
- Pid = apply(Mod, Fun, Args), % This better work!
- Id = spawn(fun() -> server(Pid) end),
- register(?NAME, Id),
- Id.
-
-start_out() ->
- %% Output-only version of start/0
- start_port([out,binary]).
-
-start_port(PortSettings) ->
- Id = spawn(fun() -> server({fd,0,1}, PortSettings) end),
- register(?NAME, Id),
- Id.
-
-%% Return the pid of the shell process.
-%% Note: We can't ask the user process for this info since it
-%% may be busy waiting for data from the port.
-interfaces(User) ->
- case process_info(User, dictionary) of
- {dictionary,Dict} ->
- case lists:keysearch(shell, 1, Dict) of
- {value,Sh={shell,Shell}} when is_pid(Shell) ->
- [Sh];
- _ ->
- []
- end;
- _ ->
- []
- end.
-
-server(Pid) when is_pid(Pid) ->
- process_flag(trap_exit, true),
- link(Pid),
- run(Pid).
-
-server(PortName,PortSettings) ->
- process_flag(trap_exit, true),
- Port = open_port(PortName,PortSettings),
- run(Port).
-
-run(P) ->
- put(read_mode,list),
- put(encoding,latin1),
- case init:get_argument(noshell) of
- %% non-empty list -> noshell
- {ok, [_|_]} ->
- put(shell, noshell),
- server_loop(P, queue:new());
- _ ->
- group_leader(self(), self()),
- catch_loop(P, start_init_shell())
- end.
-
-catch_loop(Port, Shell) ->
- catch_loop(Port, Shell, queue:new()).
-
-catch_loop(Port, Shell, Q) ->
- case catch server_loop(Port, Q) of
- new_shell ->
- exit(Shell, kill),
- catch_loop(Port, start_new_shell());
- {unknown_exit,{Shell,Reason},_} -> % shell has exited
- case Reason of
- normal ->
- put_port(<<"*** ">>, Port);
- _ ->
- put_port(<<"*** ERROR: ">>, Port)
- end,
- put_port(<<"Shell process terminated! ***\n">>, Port),
- catch_loop(Port, start_new_shell());
- {unknown_exit,_,Q1} ->
- catch_loop(Port, Shell, Q1);
- {'EXIT',R} ->
- exit(R)
- end.
-
-link_and_save_shell(Shell) ->
- link(Shell),
- put(shell, Shell),
- Shell.
-
-start_init_shell() ->
- link_and_save_shell(shell:start(init)).
-
-start_new_shell() ->
- link_and_save_shell(shell:start()).
-
-server_loop(Port, Q) ->
- receive
- {io_request,From,ReplyAs,Request} when is_pid(From) ->
- server_loop(Port, do_io_request(Request, From, ReplyAs, Port, Q));
- {Port,{data,Bytes}} ->
- case get(shell) of
- noshell ->
- server_loop(Port, queue:snoc(Q, Bytes));
- _ ->
- case contains_ctrl_g_or_ctrl_c(Bytes) of
- false ->
- server_loop(Port, queue:snoc(Q, Bytes));
- _ ->
- throw(new_shell)
- end
- end;
- {Port, eof} ->
- put(eof, true),
- server_loop(Port, Q);
-
- %% Ignore messages from port here.
- {'EXIT',Port,badsig} -> % Ignore badsig errors
- server_loop(Port, Q);
- {'EXIT',Port,What} -> % Port has exited
- exit(What);
-
- %% Check if shell has exited
- {'EXIT',SomePid,What} ->
- case get(shell) of
- noshell ->
- server_loop(Port, Q); % Ignore
- _ ->
- throw({unknown_exit,{SomePid,What},Q})
- end;
-
- _Other -> % Ignore other messages
- server_loop(Port, Q)
- end.
-
-
-get_fd_geometry(Port) ->
- case (catch port_control(Port,?CTRL_OP_GET_WINSIZE,[])) of
- List when length(List) =:= 8 ->
- <<W:32/native,H:32/native>> = list_to_binary(List),
- {W,H};
- _ ->
- error
- end.
-
-
-%% NewSaveBuffer = io_request(Request, FromPid, ReplyAs, Port, SaveBuffer)
-
-do_io_request(Req, From, ReplyAs, Port, Q0) ->
- case io_request(Req, Port, Q0) of
- {_Status,Reply,Q1} ->
- _ = io_reply(From, ReplyAs, Reply),
- Q1;
- {exit,What} ->
- ok = send_port(Port, close),
- exit(What)
- end.
-
-%% New in R13B
-%% Encoding option (unicode/latin1)
-io_request({put_chars,unicode,Chars}, Port, Q) -> % Binary new in R9C
- case wrap_characters_to_binary(Chars, unicode, get(encoding)) of
- error ->
- {error,{error,put_chars},Q};
- Bin ->
- put_chars(Bin, Port, Q)
- end;
-io_request({put_chars,unicode,Mod,Func,Args}, Port, Q) ->
- case catch apply(Mod,Func,Args) of
- Data when is_list(Data); is_binary(Data) ->
- case wrap_characters_to_binary(Data, unicode, get(encoding)) of
- Bin when is_binary(Bin) ->
- put_chars(Bin, Port, Q);
- error ->
- {error,{error,put_chars},Q}
- end;
- Undef ->
- put_chars(Undef, Port, Q)
- end;
-io_request({put_chars,latin1,Chars}, Port, Q) -> % Binary new in R9C
- case catch unicode:characters_to_binary(Chars, latin1, get(encoding)) of
- Data when is_binary(Data) ->
- put_chars(Data, Port, Q);
- _ ->
- {error,{error,put_chars},Q}
- end;
-io_request({put_chars,latin1,Mod,Func,Args}, Port, Q) ->
- case catch apply(Mod,Func,Args) of
- Data when is_list(Data); is_binary(Data) ->
- case
- catch unicode:characters_to_binary(Data,latin1,get(encoding))
- of
- Bin when is_binary(Bin) ->
- put_chars(Bin, Port, Q);
- _ ->
- {error,{error,put_chars},Q}
- end;
- Undef ->
- put_chars(Undef, Port, Q)
- end;
-io_request({get_chars,Enc,Prompt,N}, Port, Q) -> % New in R9C
- get_chars(Prompt, io_lib, collect_chars, N, Port, Q, Enc);
-io_request({get_line,Enc,Prompt}, Port, Q) ->
- case get(read_mode) of
- binary ->
- get_line_bin(Prompt,Port,Q,Enc);
- _ ->
- get_chars(Prompt, io_lib, collect_line, [], Port, Q, Enc)
- end;
-io_request({get_until,Enc,Prompt,M,F,As}, Port, Q) ->
- get_chars(Prompt, io_lib, get_until, {M,F,As}, Port, Q, Enc);
-%% End New in R13B
-io_request(getopts, Port, Q) ->
- getopts(Port, Q);
-io_request({setopts,Opts}, Port, Q) when is_list(Opts) ->
- setopts(Opts, Port, Q);
-io_request({requests,Reqs}, Port, Q) ->
- io_requests(Reqs, {ok,ok,Q}, Port);
-
-%% New in R12
-io_request({get_geometry,columns},Port,Q) ->
- case get_fd_geometry(Port) of
- {W,_H} ->
- {ok,W,Q};
- _ ->
- {error,{error,enotsup},Q}
- end;
-io_request({get_geometry,rows},Port,Q) ->
- case get_fd_geometry(Port) of
- {_W,H} ->
- {ok,H,Q};
- _ ->
- {error,{error,enotsup},Q}
- end;
-%% BC with pre-R13 nodes
-io_request({put_chars,Chars}, Port, Q) ->
- io_request({put_chars,latin1,Chars}, Port, Q);
-io_request({put_chars,Mod,Func,Args}, Port, Q) ->
- io_request({put_chars,latin1,Mod,Func,Args}, Port, Q);
-io_request({get_chars,Prompt,N}, Port, Q) ->
- io_request({get_chars,latin1,Prompt,N}, Port, Q);
-io_request({get_line,Prompt}, Port, Q) ->
- io_request({get_line,latin1,Prompt}, Port, Q);
-io_request({get_until,Prompt,M,F,As}, Port, Q) ->
- io_request({get_until,latin1,Prompt,M,F,As}, Port, Q);
-
-io_request(R, _Port, Q) -> %Unknown request
- {error,{error,{request,R}},Q}. %Ignore but give error (?)
-
-%% Status = io_requests(RequestList, PrevStat, Port)
-%% Process a list of output requests as long as the previous status is 'ok'.
-
-io_requests([R|Rs], {ok,_Res,Q}, Port) ->
- io_requests(Rs, io_request(R, Port, Q), Port);
-io_requests([_|_], Error, _) ->
- Error;
-io_requests([], Stat, _) ->
- Stat.
-
-%% put_port(DeepList, Port)
-%% Take a deep list of characters, flatten and output them to the
-%% port.
-
-put_port(List, Port) ->
- true = port_command(Port, List),
- ok.
-
-%% send_port(Port, Command)
-
-send_port(Port, Command) ->
- Port ! {self(),Command},
- ok.
-
-%% io_reply(From, ReplyAs, Reply)
-%% The function for sending i/o command acknowledgement.
-%% The ACK contains the return value.
-
-io_reply(From, ReplyAs, Reply) ->
- From ! {io_reply,ReplyAs,Reply}.
-
-%% put_chars
-put_chars(Chars, Port, Q) when is_binary(Chars) ->
- ok = put_port(Chars, Port),
- {ok,ok,Q};
-put_chars(Chars, Port, Q) ->
- case catch list_to_binary(Chars) of
- Binary when is_binary(Binary) ->
- put_chars(Binary, Port, Q);
- _ ->
- {error,{error,put_chars},Q}
- end.
-
-expand_encoding([]) ->
- [];
-expand_encoding([latin1 | T]) ->
- [{encoding,latin1} | expand_encoding(T)];
-expand_encoding([unicode | T]) ->
- [{encoding,unicode} | expand_encoding(T)];
-expand_encoding([H|T]) ->
- [H|expand_encoding(T)].
-
-%% setopts
-setopts(Opts0,Port,Q) ->
- Opts = proplists:unfold(
- proplists:substitute_negations(
- [{list,binary}],
- expand_encoding(Opts0))),
- case check_valid_opts(Opts) of
- true ->
- do_setopts(Opts,Port,Q);
- false ->
- {error,{error,enotsup},Q}
- end.
-check_valid_opts([]) ->
- true;
-check_valid_opts([{binary,_}|T]) ->
- check_valid_opts(T);
-check_valid_opts([{encoding,Valid}|T]) when Valid =:= latin1; Valid =:= utf8; Valid =:= unicode ->
- check_valid_opts(T);
-check_valid_opts(_) ->
- false.
-
-do_setopts(Opts, _Port, Q) ->
- case proplists:get_value(encoding,Opts) of
- Valid when Valid =:= unicode; Valid =:= utf8 ->
- put(encoding,unicode);
- latin1 ->
- put(encoding,latin1);
- undefined ->
- ok
- end,
- case proplists:get_value(binary, Opts) of
- true ->
- put(read_mode,binary),
- {ok,ok,Q};
- false ->
- put(read_mode,list),
- {ok,ok,Q};
- _ ->
- {ok,ok,Q}
- end.
-
-getopts(_Port,Q) ->
- Bin = {binary, get(read_mode) =:= binary},
- Uni = {encoding, get(encoding)},
- {ok,[Bin,Uni],Q}.
-
-get_line_bin(Prompt,Port,Q, Enc) ->
- case prompt(Port, Prompt) of
- error ->
- {error,{error,get_line},Q};
- ok ->
- case {get(eof),queue:is_empty(Q)} of
- {true,true} ->
- {ok,eof,Q};
- _ ->
- get_line(Prompt,Port, Q, [], Enc)
- end
- end.
-
-get_line(Prompt, Port, Q, Acc, Enc) ->
- case queue:is_empty(Q) of
- true ->
- receive
- {Port,{data,Bytes}} ->
- get_line_bytes(Prompt, Port, Q, Acc, Bytes, Enc);
- {Port, eof} ->
- put(eof, true),
- {ok, eof, queue:new()};
- {io_request,From,ReplyAs,{get_geometry,_}=Req} when is_pid(From) ->
- do_io_request(Req, From, ReplyAs, Port,
- queue:new()),
- %% No prompt.
- get_line(Prompt, Port, Q, Acc, Enc);
- {io_request,From,ReplyAs,Request} when is_pid(From) ->
- do_io_request(Request, From, ReplyAs, Port, queue:new()),
- case prompt(Port, Prompt) of
- error ->
- {error,{error,get_line},Q};
- ok ->
- get_line(Prompt, Port, Q, Acc, Enc)
- end;
- {'EXIT',From,What} when node(From) =:= node() ->
- {exit,What}
- end;
- false ->
- get_line_doit(Prompt, Port, Q, Acc, Enc)
- end.
-
-get_line_bytes(Prompt, Port, Q, Acc, Bytes, Enc) ->
- case get(shell) of
- noshell ->
- get_line_doit(Prompt, Port, queue:snoc(Q, Bytes),Acc,Enc);
- _ ->
- case contains_ctrl_g_or_ctrl_c(Bytes) of
- false ->
- get_line_doit(Prompt, Port, queue:snoc(Q, Bytes), Acc, Enc);
- _ ->
- throw(new_shell)
- end
- end.
-is_cr_at(Pos,Bin) ->
- case Bin of
- <<_:Pos/binary,$\r,_/binary>> ->
- true;
- _ ->
- false
- end.
-srch(<<>>,_,_) ->
- nomatch;
-srch(<<X:8,_/binary>>,X,N) ->
- {match,[{N,1}]};
-srch(<<_:8,T/binary>>,X,N) ->
- srch(T,X,N+1).
-
-get_line_doit(Prompt, Port, Q, Accu, Enc) ->
- case queue:is_empty(Q) of
- true ->
- case get(eof) of
- true ->
- case Accu of
- [] ->
- {ok,eof,Q};
- _ ->
- {ok,binrev(Accu,[]),Q}
- end;
- _ ->
- get_line(Prompt, Port, Q, Accu, Enc)
- end;
- false ->
- Bin = queue:head(Q),
- case srch(Bin,$\n,0) of
- nomatch ->
- X = byte_size(Bin)-1,
- case is_cr_at(X,Bin) of
- true ->
- <<D:X/binary,_/binary>> = Bin,
- get_line_doit(Prompt, Port, queue:tail(Q),
- [<<$\r>>,D|Accu], Enc);
- false ->
- get_line_doit(Prompt, Port, queue:tail(Q),
- [Bin|Accu], Enc)
- end;
- {match,[{Pos,1}]} ->
- %% We are done
- PosPlus = Pos + 1,
- case Accu of
- [] ->
- {Head,Tail} =
- case is_cr_at(Pos - 1,Bin) of
- false ->
- <<H:PosPlus/binary,
- T/binary>> = Bin,
- {H,T};
- true ->
- PosMinus = Pos - 1,
- <<H:PosMinus/binary,
- _,_,T/binary>> = Bin,
- {binrev([],[H,$\n]),T}
- end,
- case Tail of
- <<>> ->
- {ok, cast(Head,Enc), queue:tail(Q)};
- _ ->
- {ok, cast(Head,Enc),
- queue:cons(Tail, queue:tail(Q))}
- end;
- [<<$\r>>|Stack1] when Pos =:= 0 ->
- <<_:PosPlus/binary,Tail/binary>> = Bin,
- case Tail of
- <<>> ->
- {ok, cast(binrev(Stack1, [$\n]),Enc),
- queue:tail(Q)};
- _ ->
- {ok, cast(binrev(Stack1, [$\n]),Enc),
- queue:cons(Tail, queue:tail(Q))}
- end;
- _ ->
- {Head,Tail} =
- case is_cr_at(Pos - 1,Bin) of
- false ->
- <<H:PosPlus/binary,
- T/binary>> = Bin,
- {H,T};
- true ->
- PosMinus = Pos - 1,
- <<H:PosMinus/binary,
- _,_,T/binary>> = Bin,
- {[H,$\n],T}
- end,
- case Tail of
- <<>> ->
- {ok, cast(binrev(Accu,[Head]),Enc),
- queue:tail(Q)};
- _ ->
- {ok, cast(binrev(Accu,[Head]),Enc),
- queue:cons(Tail, queue:tail(Q))}
- end
- end
- end
- end.
-
-binrev(L, T) ->
- list_to_binary(lists:reverse(L, T)).
-
-%% get_chars(Prompt, Module, Function, XtraArg, Port, Queue, Encoding)
-%% Gets characters from the input port until the applied function
-%% returns {stop,Result,RestBuf}. Does not block output until input
-%% has been received. Encoding is the encoding of the data sent to
-%% the client and to Function.
-%% Returns:
-%% {Status,Result,NewQueue}
-%% {exit,Reason}
-
-%% Entry function.
-get_chars(Prompt, M, F, Xa, Port, Q, Enc) ->
- case prompt(Port, Prompt) of
- error ->
- {error,{error,get_chars},Q};
- ok ->
- case {get(eof),queue:is_empty(Q)} of
- {true,true} ->
- {ok,eof,Q};
- _ ->
- get_chars(Prompt, M, F, Xa, Port, Q, start, Enc)
- end
- end.
-
-%% First loop. Wait for port data. Respond to output requests.
-get_chars(Prompt, M, F, Xa, Port, Q, State, Enc) ->
- case queue:is_empty(Q) of
- true ->
- receive
- {Port,{data,Bytes}} ->
- get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Enc);
- {Port, eof} ->
- put(eof, true),
- {ok, eof, queue:new()};
- {io_request,From,ReplyAs,{get_geometry,_}=Req} when is_pid(From) ->
- do_io_request(Req, From, ReplyAs, Port,
- queue:new()), %Keep Q over this call
- %% No prompt.
- get_chars(Prompt, M, F, Xa, Port, Q, State, Enc);
- {io_request,From,ReplyAs,Request} when is_pid(From) ->
- get_chars_req(Prompt, M, F, Xa, Port, Q, State,
- Request, From, ReplyAs, Enc);
- {'EXIT',From,What} when node(From) =:= node() ->
- {exit,What}
- end;
- false ->
- get_chars_apply(State, M, F, Xa, Port, Q, Enc)
- end.
-
-get_chars_req(Prompt, M, F, XtraArg, Port, Q, State,
- Req, From, ReplyAs, Enc) ->
- do_io_request(Req, From, ReplyAs, Port, queue:new()), %Keep Q over this call
- case prompt(Port, Prompt) of
- error ->
- {error,{error,get_chars},Q};
- ok ->
- get_chars(Prompt, M, F, XtraArg, Port, Q, State, Enc)
- end.
-
-%% Second loop. Pass data to client as long as it wants more.
-%% A ^G in data interrupts loop if 'noshell' is not undefined.
-get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Enc) ->
- case get(shell) of
- noshell ->
- get_chars_apply(State, M, F, Xa, Port, queue:snoc(Q, Bytes),Enc);
- _ ->
- case contains_ctrl_g_or_ctrl_c(Bytes) of
- false ->
- get_chars_apply(State, M, F, Xa, Port,
- queue:snoc(Q, Bytes),Enc);
- _ ->
- throw(new_shell)
- end
- end.
-
-get_chars_apply(State0, M, F, Xa, Port, Q, Enc) ->
- case catch M:F(State0, cast(queue:head(Q),Enc), Enc, Xa) of
- {stop,Result,<<>>} ->
- {ok,Result,queue:tail(Q)};
- {stop,Result,[]} ->
- {ok,Result,queue:tail(Q)};
- {stop,Result,eof} ->
- {ok,Result,queue:tail(Q)};
- {stop,Result,Buf} ->
- {ok,Result,queue:cons(Buf, queue:tail(Q))};
- {'EXIT',_Why} ->
- {error,{error,err_func(M, F, Xa)},queue:new()};
- State1 ->
- get_chars_more(State1, M, F, Xa, Port, queue:tail(Q), Enc)
- end.
-
-get_chars_more(State, M, F, Xa, Port, Q, Enc) ->
- case queue:is_empty(Q) of
- true ->
- case get(eof) of
- undefined ->
- receive
- {Port,{data,Bytes}} ->
- get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Enc);
- {Port,eof} ->
- put(eof, true),
- get_chars_apply(State, M, F, Xa, Port,
- queue:snoc(Q, eof), Enc);
- {'EXIT',From,What} when node(From) =:= node() ->
- {exit,What}
- end;
- _ ->
- get_chars_apply(State, M, F, Xa, Port, queue:snoc(Q, eof), Enc)
- end;
- false ->
- get_chars_apply(State, M, F, Xa, Port, Q, Enc)
- end.
-
-
-%% prompt(Port, Prompt)
-%% Print Prompt onto Port
-
-%% common case, reduces execution time by 20%
-prompt(_Port, '') -> ok;
-prompt(Port, Prompt) ->
- Encoding = get(encoding),
- PromptString = io_lib:format_prompt(Prompt, Encoding),
- case wrap_characters_to_binary(PromptString, unicode, Encoding) of
- Bin when is_binary(Bin) ->
- put_port(Bin, Port);
- error ->
- error
- end.
-
-%% Convert error code to make it look as before
-err_func(io_lib, get_until, {_,F,_}) ->
- F;
-err_func(_, F, _) ->
- F.
-
-%% using regexp reduces execution time by >50% compared to old code
-%% running two regexps in sequence is much faster than \\x03|\\x07
-contains_ctrl_g_or_ctrl_c(BinOrList)->
- case {re:run(BinOrList, <<3>>),re:run(BinOrList, <<7>>)} of
- {nomatch, nomatch} -> false;
- _ -> true
- end.
-
-%% Convert a buffer between list and binary
-cast(Data, _Encoding) when is_atom(Data) ->
- Data;
-cast(Data, Encoding) ->
- IoEncoding = get(encoding),
- cast(Data, get(read_mode), IoEncoding, Encoding).
-
-cast(B, binary, latin1, latin1) when is_binary(B) ->
- B;
-cast(L, binary, latin1, latin1) ->
- case catch erlang:iolist_to_binary(L) of
- Bin when is_binary(Bin) -> Bin;
- _ -> exit({no_translation, latin1, latin1})
- end;
-cast(Data, binary, unicode, latin1) when is_binary(Data); is_list(Data) ->
- case catch unicode:characters_to_binary(Data, unicode, latin1) of
- Bin when is_binary(Bin) -> Bin;
- _ -> exit({no_translation, unicode, latin1})
- end;
-cast(Data, binary, latin1, unicode) when is_binary(Data); is_list(Data) ->
- case catch unicode:characters_to_binary(Data, latin1, unicode) of
- Bin when is_binary(Bin) -> Bin;
- _ -> exit({no_translation, latin1, unicode})
- end;
-cast(B, binary, unicode, unicode) when is_binary(B) ->
- B;
-cast(L, binary, unicode, unicode) ->
- case catch unicode:characters_to_binary(L, unicode) of
- Bin when is_binary(Bin) -> Bin;
- _ -> exit({no_translation, unicode, unicode})
- end;
-cast(B, list, latin1, latin1) when is_binary(B) ->
- binary_to_list(B);
-cast(L, list, latin1, latin1) ->
- case catch erlang:iolist_to_binary(L) of
- Bin when is_binary(Bin) -> binary_to_list(Bin);
- _ -> exit({no_translation, latin1, latin1})
- end;
-cast(Data, list, unicode, latin1) when is_binary(Data); is_list(Data) ->
- case catch unicode:characters_to_list(Data, unicode) of
- Chars when is_list(Chars) ->
- [ case X of
- High when High > 255 ->
- exit({no_translation, unicode, latin1});
- Low ->
- Low
- end || X <- Chars ];
- _ ->
- exit({no_translation, unicode, latin1})
- end;
-cast(Data, list, latin1, unicode) when is_binary(Data); is_list(Data) ->
- case catch unicode:characters_to_list(Data, latin1) of
- Chars when is_list(Chars) -> Chars;
- _ -> exit({no_translation, latin1, unicode})
- end;
-cast(Data, list, unicode, unicode) when is_binary(Data); is_list(Data) ->
- case catch unicode:characters_to_list(Data, unicode) of
- Chars when is_list(Chars) -> Chars;
- _ -> exit({no_translation, unicode, unicode})
- end.
-
-wrap_characters_to_binary(Chars, unicode, latin1) ->
- case catch unicode:characters_to_binary(Chars, unicode, latin1) of
- Bin when is_binary(Bin) ->
- Bin;
- _ ->
- case catch unicode:characters_to_list(Chars, unicode) of
- L when is_list(L) ->
- list_to_binary(
- [ case X of
- High when High > 255 ->
- ["\\x{",erlang:integer_to_list(X, 16),$}];
- Low ->
- Low
- end || X <- L ]);
- _ ->
- error
- end
- end;
-wrap_characters_to_binary(Bin, From, From) when is_binary(Bin) ->
- Bin;
-wrap_characters_to_binary(Chars, From, To) ->
- case catch unicode:characters_to_binary(Chars, From, To) of
- Bin when is_binary(Bin) ->
- Bin;
- _ ->
- error
- end.
diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl
index fa7687bf2a..25ebcbdd68 100644
--- a/lib/kernel/src/user_drv.erl
+++ b/lib/kernel/src/user_drv.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,591 +19,890 @@
%%
-module(user_drv).
-%% Basic interface to a port.
-
--export([start/0,start/1,start/2,start/3,server/2,server/3]).
-
--export([interfaces/1]).
+%% Basic interface to stdin/stdout.
+%%
+%% This is responsible for a couple of things:
+%% - Dispatching I/O messages when erl is running
+%% The messages are listed in the type message/0.
+%% - Any data received from the terminal is sent to the current group like this:
+%% `{DrvPid :: pid(), {data, UnicodeCharacters :: list()}}`
+%% - It serves as the job control manager (i.e. what happens when you type ^G)
+%% - Starts potential -remsh sessions to other nodes
+%%
+-type message() ::
+ %% I/O requests that modify the terminal
+ {Sender :: pid(), request()} |
+ %% Query the server of the current dimensions of the terminal.
+ %% `Sender` will be sent the message:
+ %% `{DrvPid :: pid(), tty_geometry, {Width :: integer(), Height :: integer()}}`
+ {Sender :: pid(), tty_geometry} |
+ %% Query the server if it supports unicode characters
+ %% `Sender` will be sent the message:
+ %% `{DrvPid :: pid(), get_unicode_state, SupportUnicode :: boolean()}`
+ {Sender :: pid(), get_unicode_state} |
+ %% Change whether the server supports unicode characters or not. The reply
+ %% contains the previous unicode state.
+ %% `Sender` will be sent the message:
+ %% `{DrvPid :: pid(), set_unicode_state, SupportedUnicode :: boolean()}`
+ {Sender :: pid(), set_unicode_state, boolean()}.
+-type request() ::
+ %% Put characters at current cursor position,
+ %% overwriting any characters it encounters.
+ {put_chars, unicode, binary()} |
+ %% Same as put_chars/3, but sends Reply to From when the characters are
+ %% guaranteed to have been written to the terminal
+ {put_chars_sync, unicode, binary(), {From :: pid(), Reply :: term()}} |
+ %% Put text in expansion area
+ {put_expand} |
+ {put_expand_no_trim} |
+ %% Move the cursor X characters left or right (negative is left)
+ {move_rel, -32768..32767} |
+ %% Move the cursor Y rows up or down (negative is up)
+ {move_line, -32768..32767} |
+ %% Move combo, helper to simplify some move operations
+ {move_combo, -32768..32767, -32768..32767, -32768..32767} |
+ %% Insert characters at current cursor position moving any
+ %% characters after the cursor.
+ {insert_chars, unicode, binary()} |
+ %% Delete X chars before or after the cursor adjusting any test remaining
+ %% to the right of the cursor.
+ {delete_chars, -32768..32767} |
+ %% Deletes the current prompt and expression
+ delete_line |
+ %% Delete after the cursor
+ delete_after_cursor |
+ %% Trigger a terminal "bell"
+ beep |
+ %% Clears the screen
+ clear |
+ %% Execute multiple request() actions
+ {requests, [request()]} |
+ %% Open external editor
+ {open_editor, string()} |
+ %% Redraws the current prompt and expression
+ redraw_prompt |
+ {redraw_prompt, string(), string(), tuple()} |
+ %% Clears the state, not touching the characters
+ new_prompt.
+
+-export_type([message/0]).
+-export([start/0, start/1, start_shell/0, start_shell/1, whereis_group/0]).
+
+%% gen_statem state callbacks
+-behaviour(gen_statem).
+-export([init/3,server/3,switch_loop/3]).
+
+%% gen_statem callbacks
+-export([init/1, callback_mode/0]).
-include_lib("kernel/include/logger.hrl").
--define(OP_PUTC,0).
--define(OP_MOVE,1).
--define(OP_INSC,2).
--define(OP_DELC,3).
--define(OP_BEEP,4).
--define(OP_PUTC_SYNC,5).
-% Control op
--define(ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER, 16#018b0900).
--define(CTRL_OP_GET_WINSIZE, (100 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)).
--define(CTRL_OP_GET_UNICODE_STATE, (101 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)).
--define(CTRL_OP_SET_UNICODE_STATE, (102 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)).
-
-%% start()
-%% start(ArgumentList)
-%% start(PortName, Shell)
-%% start(InPortName, OutPortName, Shell)
-%% Start the user driver server. The arguments to start/1 are slightly
-%% strange as this may be called both at start up from the command line
-%% and explicitly from other code.
-
+-record(editor, { port :: port(), file :: file:name(), requester :: pid() }).
+-record(state, { tty :: prim_tty:state() | undefined,
+ write :: reference() | undefined,
+ read :: reference() | undefined,
+ shell_started = new :: new | old | false,
+ editor :: #editor{} | undefined,
+ user :: pid(),
+ current_group :: pid() | undefined,
+ groups, queue }).
+
+-type shell() :: {module(), atom(), [term()]} | {node(), module(), atom(), [term()]}.
+-type arguments() :: #{ initial_shell => noshell | shell() |
+ {remote, unicode:charlist()} | {remote, unicode:charlist(), {module(), atom(), [term()]}},
+ input => boolean() }.
+
+%% Default line editing shell
-spec start() -> pid().
+start() ->
+ case init:get_argument(remsh) of
+ {ok,[[Node]]} ->
+ start(#{ initial_shell => {remote, Node} });
+ {ok,[[Node]|_]} ->
+ ?LOG_WARNING("Multiple -remsh given to erl, using the first, ~p", [Node]),
+ start(#{ initial_shell => {remote, Node} });
+ E when E =:= error ; E =:= {ok,[[]]} ->
+ start(#{ })
+ end.
-start() -> %Default line editing shell
- spawn(user_drv, server, ['tty_sl -c -e',{shell,start,[init]}]).
+-spec start_shell() -> ok | {error, Reason :: term()}.
+start_shell() ->
+ start_shell(#{ }).
+-spec start_shell(arguments()) -> ok | {error, already_started}.
+start_shell(Args) ->
+ gen_statem:call(?MODULE, {start_shell, Args}).
+
+-spec whereis_group() -> pid() | undefined.
+whereis_group() ->
+ {dictionary, Dict} =
+ erlang:process_info(whereis(?MODULE), dictionary),
+ proplists:get_value(current_group, Dict).
+
+%% Backwards compatibility with pre OTP-26 for Elixir/LFE etc
+-spec start(['tty_sl -c -e'| shell()]) -> pid();
+ (arguments()) -> pid().
+start(['tty_sl -c -e', Shell]) ->
+ start(#{ initial_shell => Shell });
+start(Args) when is_map(Args) ->
+ case gen_statem:start({local, ?MODULE}, ?MODULE, Args, []) of
+ {ok, Pid} -> Pid;
+ {error, Reason} ->
+ {error, Reason}
+ end.
-start([Pname]) ->
- spawn(user_drv, server, [Pname,{shell,start,[init]}]);
-start([Pname|Args]) ->
- spawn(user_drv, server, [Pname|Args]);
-start(Pname) ->
- spawn(user_drv, server, [Pname,{shell,start,[init]}]).
+callback_mode() -> state_functions.
-start(Pname, Shell) ->
- spawn(user_drv, server, [Pname,Shell]).
+-spec init(arguments()) -> gen_statem:init_result(init).
+init(Args) ->
+ process_flag(trap_exit, true),
-start(Iname, Oname, Shell) ->
- spawn(user_drv, server, [Iname,Oname,Shell]).
+ IsTTY = prim_tty:isatty(stdin) =:= true andalso prim_tty:isatty(stdout) =:= true,
+ StartShell = maps:get(initial_shell, Args, undefined) =/= noshell,
+ OldShell = maps:get(initial_shell, Args, undefined) =:= oldshell,
+ try
+ if
+ not IsTTY andalso StartShell; OldShell ->
+ error(enotsup);
+ IsTTY, StartShell ->
+ TTYState = prim_tty:init(#{}),
+ init_standard_error(TTYState, true),
+ {ok, init, {Args, #state{ user = start_user() } },
+ {next_event, internal, TTYState}};
+ true ->
+ TTYState = prim_tty:init(#{input => maps:get(input, Args, true),
+ tty => false}),
+ init_standard_error(TTYState, false),
+ {ok, init, {Args,#state{ user = start_user() } },
+ {next_event, internal, TTYState}}
+ end
+ catch error:enotsup ->
+ %% This is thrown by prim_tty:init when
+ %% it could not start the terminal,
+ %% probably because TERM=dumb was set.
+ %%
+ %% The oldshell mode is important as it is
+ %% the mode used when running erlang in an
+ %% emacs buffer.
+ CatchTTYState = prim_tty:init(#{tty => false}),
+ init_standard_error(CatchTTYState, false),
+ {ok, init, {Args,#state{ shell_started = old, user = start_user() } },
+ {next_event, internal, CatchTTYState}}
+ end.
+%% Initialize standard_error
+init_standard_error(TTY, NewlineCarriageReturn) ->
+ Encoding = case prim_tty:unicode(TTY) of
+ true -> unicode;
+ false -> latin1
+ end,
+ ok = io:setopts(standard_error, [{encoding, Encoding},
+ {onlcr, NewlineCarriageReturn}]).
+
+init(internal, TTYState, {Args, State = #state{ user = User }}) ->
+
+ %% Cleanup ancestors so that observer looks nice
+ put('$ancestors',[User|get('$ancestors')]),
+
+ #{ read := ReadHandle, write := WriteHandle } = prim_tty:handles(TTYState),
+
+ NewState = State#state{ tty = TTYState,
+ read = ReadHandle, write = WriteHandle,
+ user = User, queue = {false, queue:new()},
+ groups = gr_add_cur(gr_new(), User, {})
+ },
+
+ case Args of
+ #{ initial_shell := noshell } ->
+ init_noshell(NewState);
+ #{ initial_shell := {remote, Node} } ->
+ InitialShell = {shell,start,[]},
+ exit_on_remote_shell_error(
+ Node, InitialShell, init_remote_shell(NewState, Node, InitialShell));
+ #{ initial_shell := {remote, Node, InitialShell} } ->
+ exit_on_remote_shell_error(
+ Node, InitialShell, init_remote_shell(NewState, Node, InitialShell));
+ #{ initial_shell := oldshell } ->
+ old = State#state.shell_started,
+ init_local_shell(NewState, {shell,start,[]});
+ #{ initial_shell := InitialShell } ->
+ init_local_shell(NewState, InitialShell);
+ _ ->
+ init_local_shell(NewState, {shell,start,[init]})
+ end.
-%% Return the pid of the active group process.
-%% Note: We can't ask the user_drv process for this info since it
-%% may be busy waiting for data from the port.
+exit_on_remote_shell_error(RemoteNode, _, {error, noconnection}) ->
+ io:format(standard_error, "Could not connect to ~p\n", [RemoteNode]),
+ erlang:halt(1);
+exit_on_remote_shell_error(RemoteNode, {M, _, _}, {error, Reason}) ->
+ io:format(standard_error, "Could not load ~p on ~p (~p)\n", [RemoteNode, M, Reason]),
+ erlang:halt(1);
+exit_on_remote_shell_error(_, _, Result) ->
+ Result.
+
+%% We have been started with -noshell. In this mode the current_group is
+%% the `user` group process.
+init_noshell(State) ->
+ init_shell(State#state{ shell_started = false }, "").
+
+init_remote_shell(State, Node, {M, F, A}) ->
+
+ case net_kernel:get_state() of
+ #{ started := no } ->
+ {ok, _} = net_kernel:start([undefined, shortnames]),
+ ok;
+ _ ->
+ ok
+ end,
--spec interfaces(pid()) -> [{'current_group', pid()}].
+ LocalNode =
+ case net_kernel:get_state() of
+ #{ name_type := dynamic } ->
+ net_kernel:nodename();
+ #{ name_type := static } ->
+ node()
+ end,
+
+ RemoteNode =
+ case string:find(Node,"@") of
+ nomatch ->
+ list_to_atom(Node ++ string:find(atom_to_list(LocalNode),"@"));
+ _ ->
+ list_to_atom(Node)
+ end,
+
+ case net_kernel:connect_node(RemoteNode) of
+ true ->
+
+ case erpc:call(RemoteNode, code, ensure_loaded, [M]) of
+ {error, Reason} when Reason =/= embedded ->
+ {error, Reason};
+ _ ->
+
+ %% Setup correct net tick times
+ case erpc:call(RemoteNode, net_kernel, get_net_ticktime, []) of
+ {ongoing_change_to, NetTickTime} ->
+ _ = net_kernel:set_net_ticktime(NetTickTime),
+ ok;
+ NetTickTime ->
+ _ = net_kernel:set_net_ticktime(NetTickTime),
+ ok
+ end,
-interfaces(UserDrv) ->
- case process_info(UserDrv, dictionary) of
- {dictionary,Dict} ->
- case lists:keysearch(current_group, 1, Dict) of
- {value,Gr={_,Group}} when is_pid(Group) ->
- [Gr];
- _ ->
- []
- end;
- _ ->
- []
+ RShell = {RemoteNode, M, F, A},
+
+ %% We fetch the shell slogan from the remote node
+ Slogan =
+ case erpc:call(RemoteNode, application, get_env,
+ [stdlib, shell_slogan,
+ erpc:call(RemoteNode, erlang, system_info, [system_version])]) of
+ Fun when is_function(Fun, 0) ->
+ erpc:call(RemoteNode, Fun);
+ SloganEnv ->
+ SloganEnv
+ end,
+
+ Group = group:start(self(), RShell,
+ [{echo,State#state.shell_started =:= new}] ++
+ group_opts(RemoteNode)),
+
+ Gr = gr_add_cur(State#state.groups, Group, RShell),
+
+ init_shell(State#state{ groups = Gr }, [Slogan,$\n])
+ end;
+ false ->
+ {error, noconnection}
end.
-%% server(Pid, Shell)
-%% server(Pname, Shell)
-%% server(Iname, Oname, Shell)
-%% The initial calls to run the user driver. These start the port(s)
-%% then call server1/3 to set everything else up.
-
-server(Pid, Shell) when is_pid(Pid) ->
- server1(Pid, Pid, Shell);
-server(Pname, Shell) ->
- process_flag(trap_exit, true),
- case catch open_port({spawn,Pname}, [eof]) of
- {'EXIT', _} ->
- %% Let's try a dumb user instead
- user:start();
- Port ->
- server1(Port, Port, Shell)
- end.
+init_local_shell(State, InitialShell) ->
+ Slogan =
+ case application:get_env(
+ stdlib, shell_slogan,
+ fun() -> erlang:system_info(system_version) end) of
+ Fun when is_function(Fun, 0) ->
+ Fun();
+ SloganEnv ->
+ SloganEnv
+ end,
-server(Iname, Oname, Shell) ->
- process_flag(trap_exit, true),
- case catch open_port({spawn,Iname}, [eof]) of
- {'EXIT', _} -> %% It might be a dumb terminal lets start dumb user
- user:start();
- Iport ->
- Oport = open_port({spawn,Oname}, [eof]),
- server1(Iport, Oport, Shell)
- end.
+ Gr = gr_add_cur(State#state.groups,
+ group:start(self(), InitialShell,
+ group_opts() ++ [{echo,State#state.shell_started =:= new}]),
+ InitialShell),
-server1(Iport, Oport, Shell) ->
- put(eof, false),
- %% Start user and initial shell.
- User = start_user(),
- Gr1 = gr_add_cur(gr_new(), User, {}),
-
- {Curr,Shell1} =
- case init:get_argument(remsh) of
- {ok,[[Node]]} ->
- ANode =
- if
- node() =:= nonode@nohost ->
- %% We try to connect to the node if the current node is not
- %% a distributed node yet. If this succeeds it means that we
- %% are running using "-sname undefined".
- _ = net_kernel:start([undefined, shortnames]),
- NodeName = append_hostname(Node, net_kernel:nodename()),
- case net_kernel:connect_node(NodeName) of
- true ->
- NodeName;
- _Else ->
- ?LOG_ERROR("Could not connect to ~p",[Node])
- end;
- true ->
- append_hostname(Node, node())
- end,
+ init_shell(State#state{ groups = Gr }, [Slogan,$\n]).
- RShell = {ANode,shell,start,[]},
- RGr = group:start(self(), RShell, rem_sh_opts(ANode)),
- {RGr,RShell};
- E when E =:= error ; E =:= {ok,[[]]} ->
- {group:start(self(), Shell),Shell}
- end,
+init_shell(State, Slogan) ->
+ init_standard_error(State#state.tty, State#state.shell_started =:= new),
+ Curr = gr_cur_pid(State#state.groups),
put(current_group, Curr),
- Gr = gr_add_cur(Gr1, Curr, Shell1),
- %% Print some information.
- io_request({put_chars, unicode,
- flatten(io_lib:format("~ts\n",
- [erlang:system_info(system_version)]))},
- Iport, Oport),
-
- %% Enter the server loop.
- server_loop(Iport, Oport, Curr, User, Gr, {false, queue:new()}).
-
-append_hostname(Node, LocalNode) ->
- case string:find(Node,"@") of
- nomatch ->
- list_to_atom(Node ++ string:find(atom_to_list(LocalNode),"@"));
- _ ->
- list_to_atom(Node)
- end.
-
-rem_sh_opts(Node) ->
- [{expand_fun,fun(B)-> rpc:call(Node,edlin_expand,expand,[B]) end}].
+ {next_state, server, State#state{ current_group = gr_cur_pid(State#state.groups) },
+ {next_event, info,
+ {gr_cur_pid(State#state.groups),
+ {put_chars, unicode,
+ unicode:characters_to_binary(io_lib:format("~ts", [Slogan]))}}}}.
%% start_user()
%% Start a group leader process and register it as 'user', unless,
%% of course, a 'user' already exists.
-
start_user() ->
- case whereis(user_drv) of
- undefined ->
- register(user_drv, self());
- _ ->
- ok
- end,
case whereis(user) of
undefined ->
- User = group:start(self(), {}),
+ User = group:start(self(), {}, [{echo,false}]),
register(user, User),
User;
User ->
User
end.
-
-server_loop(Iport, Oport, User, Gr, IOQueue) ->
- Curr = gr_cur_pid(Gr),
- put(current_group, Curr),
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue).
-
-server_loop(Iport, Oport, Curr, User, Gr, {Resp, IOQ} = IOQueue) ->
- receive
- {Iport,{data,Bs}} ->
- BsBin = list_to_binary(Bs),
- Unicode = unicode:characters_to_list(BsBin,utf8),
- port_bytes(Unicode, Iport, Oport, Curr, User, Gr, IOQueue);
- {Iport,eof} ->
- Curr ! {self(),eof},
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue);
-
- %% We always handle geometry and unicode requests
- {Requester,tty_geometry} ->
- Requester ! {self(),tty_geometry,get_tty_geometry(Iport)},
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue);
- {Requester,get_unicode_state} ->
- Requester ! {self(),get_unicode_state,get_unicode_state(Iport)},
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue);
- {Requester,set_unicode_state, Bool} ->
- Requester ! {self(),set_unicode_state,set_unicode_state(Iport,Bool)},
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue);
-
- Req when element(1,Req) =:= User orelse element(1,Req) =:= Curr,
- tuple_size(Req) =:= 2 orelse tuple_size(Req) =:= 3 ->
- %% We match {User|Curr,_}|{User|Curr,_,_}
- NewQ = handle_req(Req, Iport, Oport, IOQueue),
- server_loop(Iport, Oport, Curr, User, Gr, NewQ);
- {Oport,ok} ->
- %% We get this ok from the port, in io_request we store
- %% info about where to send reply at head of queue
- {Origin,Reply} = Resp,
- Origin ! {reply,Reply},
- NewQ = handle_req(next, Iport, Oport, {false, IOQ}),
- server_loop(Iport, Oport, Curr, User, Gr, NewQ);
- {'EXIT',Iport,_R} ->
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue);
- {'EXIT',Oport,_R} ->
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue);
- {'EXIT',User,shutdown} -> % force data to port
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue);
- {'EXIT',User,_R} -> % keep 'user' alive
- NewU = start_user(),
- server_loop(Iport, Oport, Curr, NewU, gr_set_num(Gr, 1, NewU, {}), IOQueue);
- {'EXIT',Pid,R} -> % shell and group leader exit
- case gr_cur_pid(Gr) of
- Pid when R =/= die ,
- R =/= terminated -> % current shell exited
- if R =/= normal ->
- io_requests([{put_chars,unicode,"*** ERROR: "}], Iport, Oport);
- true -> % exit not caused by error
- io_requests([{put_chars,unicode,"*** "}], Iport, Oport)
- end,
- io_requests([{put_chars,unicode,"Shell process terminated! "}], Iport, Oport),
- Gr1 = gr_del_pid(Gr, Pid),
- case gr_get_info(Gr, Pid) of
- {Ix,{shell,start,Params}} -> % 3-tuple == local shell
- io_requests([{put_chars,unicode,"***\n"}], Iport, Oport),
- %% restart group leader and shell, same index
- Pid1 = group:start(self(), {shell,start,Params}),
- {ok,Gr2} = gr_set_cur(gr_set_num(Gr1, Ix, Pid1,
- {shell,start,Params}), Ix),
- put(current_group, Pid1),
- server_loop(Iport, Oport, Pid1, User, Gr2, IOQueue);
- _ -> % remote shell
- io_requests([{put_chars,unicode,"(^G to start new job) ***\n"}],
- Iport, Oport),
- server_loop(Iport, Oport, Curr, User, Gr1, IOQueue)
- end;
- _ -> % not current, just remove it
- server_loop(Iport, Oport, Curr, User, gr_del_pid(Gr, Pid), IOQueue)
- end;
- {Requester, {put_chars_sync, _, _, Reply}} ->
- %% We need to ack the Req otherwise originating process will hang forever
- %% Do discard the output to non visible shells (as was done previously)
- Requester ! {reply, Reply},
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue);
- _X ->
- %% Ignore unknown messages.
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue)
- end.
-handle_req(next,Iport,Oport,{false,IOQ}=IOQueue) ->
- case queue:out(IOQ) of
- {empty,_} ->
- IOQueue;
- {{value,{Origin,Req}},ExecQ} ->
- case io_request(Req, Iport, Oport) of
- ok ->
- handle_req(next,Iport,Oport,{false,ExecQ});
- Reply ->
- {{Origin,Reply}, ExecQ}
+server({call, From}, {start_shell, Args},
+ State = #state{ tty = TTY, shell_started = false }) ->
+ IsTTY = prim_tty:isatty(stdin) =:= true andalso prim_tty:isatty(stdout) =:= true,
+ StartShell = maps:get(initial_shell, Args, undefined) =/= noshell,
+ OldShell = maps:get(initial_shell, Args, undefined) =:= oldshell,
+ NewState =
+ try
+ if
+ not IsTTY andalso StartShell; OldShell ->
+ error(enotsup);
+ IsTTY, StartShell ->
+ NewTTY = prim_tty:reinit(TTY, #{ }),
+ State#state{ tty = NewTTY,
+ shell_started = new };
+ true ->
+ NewTTY = prim_tty:reinit(TTY, #{ tty => false }),
+ State#state{ tty = NewTTY, shell_started = false }
end
+ catch error:enotsup ->
+ NewTTYState = prim_tty:reinit(TTY, #{ tty => false }),
+ State#state{ tty = NewTTYState, shell_started = old }
+ end,
+ #{ read := ReadHandle, write := WriteHandle } = prim_tty:handles(NewState#state.tty),
+ NewHandleState = NewState#state {
+ read = ReadHandle,
+ write = WriteHandle
+ },
+ {Result, Reply}
+ = case maps:get(initial_shell, Args, undefined) of
+ noshell ->
+ {init_noshell(NewHandleState), ok};
+ {remote, Node} ->
+ case init_remote_shell(NewHandleState, Node, {shell, start, []}) of
+ {error, _} = Error ->
+ {init_noshell(NewHandleState), Error};
+ R ->
+ {R, ok}
+ end;
+ {remote, Node, InitialShell} ->
+ case init_remote_shell(NewHandleState, Node, InitialShell) of
+ {error, _} = Error ->
+ {init_noshell(NewHandleState), Error};
+ R ->
+ {R, ok}
+ end;
+ undefined ->
+ case NewHandleState#state.shell_started of
+ old ->
+ {init_local_shell(NewHandleState, {shell,start,[]}), ok};
+ new ->
+ {init_local_shell(NewHandleState, {shell,start,[init]}), ok};
+ false ->
+ %% This can never happen, but dialyzer complains so we add
+ %% this clause.
+ {keep_state_and_data, ok}
+ end;
+ InitialShell ->
+ {init_local_shell(NewHandleState, InitialShell), ok}
+ end,
+ gen_statem:reply(From, Reply),
+ Result;
+server({call, From}, {start_shell, _Args}, _State) ->
+ gen_statem:reply(From, {error, already_started}),
+ keep_state_and_data;
+server(info, {ReadHandle,{data,UTF8Binary}}, State = #state{ read = ReadHandle })
+ when State#state.current_group =:= State#state.user ->
+ State#state.current_group !
+ {self(), {data, unicode:characters_to_list(UTF8Binary, utf8)}},
+ keep_state_and_data;
+server(info, {ReadHandle,{data,UTF8Binary}}, State = #state{ read = ReadHandle }) ->
+ case contains_ctrl_g_or_ctrl_c(UTF8Binary) of
+ ctrl_g -> {next_state, switch_loop, State, {next_event, internal, init}};
+ ctrl_c ->
+ case gr_get_info(State#state.groups, State#state.current_group) of
+ undefined -> ok;
+ _ -> exit(State#state.current_group, interrupt)
+ end,
+ keep_state_and_data;
+ none ->
+ State#state.current_group !
+ {self(), {data, unicode:characters_to_list(UTF8Binary, utf8)}},
+ keep_state_and_data
end;
-handle_req(Msg,Iport,Oport,{false,IOQ}=IOQueue) ->
- empty = queue:peek(IOQ),
- {Origin,Req} = Msg,
- case io_request(Req, Iport, Oport) of
- ok ->
- IOQueue;
- Reply ->
- {{Origin,Reply}, IOQ}
- end;
-handle_req(Msg,_Iport,_Oport,{Resp, IOQ}) ->
- %% All requests are queued when we have outstanding sync put_chars
- {Resp, queue:in(Msg,IOQ)}.
-
-%% port_bytes(Bytes, InPort, OutPort, CurrentProcess, UserProcess, Group)
-%% Check the Bytes from the port to see if it contains a ^G. If so,
-%% either escape to switch_loop or restart the shell. Otherwise send
-%% the bytes to Curr.
-
-port_bytes([$\^G|_Bs], Iport, Oport, _Curr, User, Gr, IOQueue) ->
- handle_escape(Iport, Oport, User, Gr, IOQueue);
-
-port_bytes([$\^C|_Bs], Iport, Oport, Curr, User, Gr, IOQueue) ->
- interrupt_shell(Iport, Oport, Curr, User, Gr, IOQueue);
-
-port_bytes([B], Iport, Oport, Curr, User, Gr, IOQueue) ->
- Curr ! {self(),{data,[B]}},
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue);
-port_bytes(Bs, Iport, Oport, Curr, User, Gr, IOQueue) ->
- case member($\^G, Bs) of
- true ->
- handle_escape(Iport, Oport, User, Gr, IOQueue);
- false ->
- Curr ! {self(),{data,Bs}},
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue)
- end.
-
-interrupt_shell(Iport, Oport, Curr, User, Gr, IOQueue) ->
- case gr_get_info(Gr, Curr) of
- undefined ->
- ok; % unknown
- _ ->
- exit(Curr, interrupt)
+server(info, {ReadHandle,eof}, State = #state{ read = ReadHandle }) ->
+ State#state.current_group ! {self(), eof},
+ keep_state_and_data;
+server(info,{ReadHandle,{signal,Signal}}, State = #state{ tty = TTYState, read = ReadHandle }) ->
+ {keep_state, State#state{ tty = prim_tty:handle_signal(TTYState, Signal) }};
+
+server(info, {Requester, tty_geometry}, #state{ tty = TTYState }) ->
+ case prim_tty:window_size(TTYState) of
+ {ok, Geometry} ->
+ Requester ! {self(), tty_geometry, Geometry},
+ ok;
+ Error ->
+ Requester ! {self(), tty_geometry, Error},
+ ok
end,
- server_loop(Iport, Oport, Curr, User, Gr, IOQueue).
-
-handle_escape(Iport, Oport, User, Gr, IOQueue) ->
- case application:get_env(stdlib, shell_esc) of
- {ok,abort} ->
- Pid = gr_cur_pid(Gr),
- exit(Pid, die),
+ keep_state_and_data;
+server(info, {Requester, get_unicode_state}, #state{ tty = TTYState }) ->
+ Requester ! {self(), get_unicode_state, prim_tty:unicode(TTYState) },
+ keep_state_and_data;
+server(info, {Requester, set_unicode_state, Bool}, #state{ tty = TTYState } = State) ->
+ OldUnicode = prim_tty:unicode(TTYState),
+ NewTTYState = prim_tty:unicode(TTYState, Bool),
+ ok = io:setopts(standard_error,[{encoding, if Bool -> unicode; true -> latin1 end}]),
+ Requester ! {self(), set_unicode_state, OldUnicode},
+ {keep_state, State#state{ tty = NewTTYState }};
+server(info, {Requester, get_terminal_state}, _State) ->
+ Requester ! {self(), get_terminal_state, prim_tty:isatty(stdout) },
+ keep_state_and_data;
+server(info, {Requester, {open_editor, Buffer}}, #state{tty = TTYState } = State) ->
+ case open_editor(TTYState, Buffer) of
+ false ->
+ Requester ! {self(), {editor_data, Buffer}},
+ keep_state_and_data;
+ {EditorPort, TmpPath} ->
+ {keep_state, State#state{ editor = #editor{ port = EditorPort,
+ file = TmpPath,
+ requester = Requester }}}
+ end;
+server(info, Req, State = #state{ user = User, current_group = Curr, editor = undefined })
+ when element(1,Req) =:= User orelse element(1,Req) =:= Curr,
+ tuple_size(Req) =:= 2 orelse tuple_size(Req) =:= 3 ->
+ %% We match {User|Curr,_}|{User|Curr,_,_}
+ {NewTTYState, NewQueue} = handle_req(Req, State#state.tty, State#state.queue),
+ {keep_state, State#state{ tty = NewTTYState, queue = NewQueue }};
+server(info, {WriteRef, ok}, State = #state{ write = WriteRef,
+ queue = {{Origin, MonitorRef, Reply}, IOQ} }) ->
+ %% We get this ok from the user_drv_writer, in io_request we store
+ %% info about where to send reply at head of queue
+ Origin ! {reply, Reply, ok},
+ erlang:demonitor(MonitorRef, [flush]),
+ {NewTTYState, NewQueue} = handle_req(next, State#state.tty, {false, IOQ}),
+ {keep_state, State#state{ tty = NewTTYState, queue = NewQueue }};
+server(info, {'DOWN', MonitorRef, _, _, Reason},
+ #state{ queue = {{Origin, MonitorRef, Reply}, _IOQ} }) ->
+ %% The writer process died, we send the correct error to the caller and
+ %% then stop this process. This will bring down all linked groups (including 'user').
+ %% All writes from now on will throw badarg terminated.
+ Origin ! {reply, Reply, {error, Reason}},
+ ?LOG_INFO("Failed to write to standard out (~p)", [Reason]),
+ stop;
+server(info,{Requester, {put_chars_sync, _, _, Reply}}, _State) ->
+ %% This is a sync request from an unknown or inactive group.
+ %% We need to ack the Req otherwise originating process will hang forever.
+ %% We discard the output to non visible shells
+ Requester ! {reply, Reply, ok},
+ keep_state_and_data;
+
+server(info,{'EXIT',User, shutdown}, #state{ user = User }) ->
+ keep_state_and_data;
+server(info,{'EXIT',User, _Reason}, State = #state{ user = User }) ->
+ NewUser = start_user(),
+ {keep_state, State#state{ user = NewUser,
+ groups = gr_set_num(State#state.groups, 1, NewUser, {})}};
+server(info, {'EXIT', EditorPort, _R},
+ State = #state{tty = TTYState,
+ editor = #editor{ requester = Requester,
+ port = EditorPort,
+ file = PathTmp}}) ->
+ {ok, Content} = file:read_file(PathTmp),
+ _ = file:del_dir_r(PathTmp),
+ Unicode = case unicode:characters_to_list(Content,unicode) of
+ {error, _, _} -> unicode:characters_to_list(
+ unicode:characters_to_list(Content,latin1), unicode);
+ U -> U
+ end,
+ Requester ! {self(), {editor_data, string:chomp(Unicode)}},
+ ok = prim_tty:enable_reader(TTYState),
+ {keep_state, State#state{editor = undefined}};
+server(info,{'EXIT', Group, Reason}, State) -> % shell and group leader exit
+ case gr_cur_pid(State#state.groups) of
+ Group when Reason =/= die, Reason =/= terminated -> % current shell exited
+ Reqs = [if
+ Reason =/= normal ->
+ {put_chars,unicode,<<"*** ERROR: ">>};
+ true -> % exit not caused by error
+ {put_chars,unicode,<<"*** ">>}
+ end,
+ {put_chars,unicode,<<"Shell process terminated! ">>}],
+ Gr1 = gr_del_pid(State#state.groups, Group),
+ case gr_get_info(State#state.groups, Group) of
+ {Ix,{shell,start,Params}} -> % 3-tuple == local shell
+ NewTTyState = io_requests(Reqs ++ [{put_chars,unicode,<<"***\n">>}],
+ State#state.tty),
+ %% restart group leader and shell, same index
+ NewGroup = group:start(self(), {shell,start,Params}),
+ {ok,Gr2} = gr_set_cur(gr_set_num(Gr1, Ix, NewGroup,
+ {shell,start,Params}), Ix),
+ {keep_state, State#state{ tty = NewTTyState,
+ current_group = NewGroup,
+ groups = Gr2 }};
+ _ -> % remote shell
+ NewTTYState = io_requests(
+ Reqs ++ [{put_chars,unicode,<<"(^G to start new job) ***\n">>}],
+ State#state.tty),
+ {keep_state, State#state{ tty = NewTTYState, groups = Gr1 }}
+ end;
+ _ -> % not current, just remove it
+ {keep_state, State#state{ groups = gr_del_pid(State#state.groups, Group) }}
+ end;
+server(_, _, _) ->
+ keep_state_and_data.
+
+contains_ctrl_g_or_ctrl_c(<<$\^G,_/binary>>) ->
+ ctrl_g;
+contains_ctrl_g_or_ctrl_c(<<$\^C,_/binary>>) ->
+ ctrl_c;
+contains_ctrl_g_or_ctrl_c(<<_/utf8,T/binary>>) ->
+ contains_ctrl_g_or_ctrl_c(T);
+contains_ctrl_g_or_ctrl_c(<<>>) ->
+ none.
+
+switch_loop(internal, init, State) ->
+ case application:get_env(stdlib, shell_esc, jcl) of
+ abort ->
+ CurrGroup = gr_cur_pid(State#state.groups),
+ exit(CurrGroup, die),
Gr1 =
- case gr_get_info(Gr, Pid) of
- {_Ix,{}} -> % no shell
- Gr;
+ case gr_get_info(State#state.groups, CurrGroup) of
+ {_Ix,{}} -> % no shell
+ State#state.groups;
_ ->
- receive {'EXIT',Pid,_} ->
- gr_del_pid(Gr, Pid)
+ receive {'EXIT',CurrGroup,_} ->
+ gr_del_pid(State#state.groups, CurrGroup)
after 1000 ->
- Gr
+ State#state.groups
end
end,
- Pid1 = group:start(self(), {shell,start,[]}),
- io_request({put_chars,unicode,"\n"}, Iport, Oport),
- server_loop(Iport, Oport, User,
- gr_add_cur(Gr1, Pid1, {shell,start,[]}), IOQueue);
-
- _ -> % {ok,jcl} | undefined
- io_request({put_chars,unicode,"\nUser switch command\n"}, Iport, Oport),
+ NewGroup = group:start(self(), {shell,start,[]}),
+ NewTTYState = io_requests([{put_chars,unicode,<<"\n">>}], State#state.tty),
+ {next_state, server,
+ State#state{ tty = NewTTYState,
+ groups = gr_add_cur(Gr1, NewGroup, {shell,start,[]})}};
+ jcl ->
+ NewTTYState =
+ io_requests([{put_chars,unicode,<<"\nUser switch command (type h for help)\n">>}],
+ State#state.tty),
%% init edlin used by switch command and have it copy the
%% text buffer from current group process
- edlin:init(gr_cur_pid(Gr)),
- server_loop(Iport, Oport, User, switch_loop(Iport, Oport, Gr), IOQueue)
- end.
-
-switch_loop(Iport, Oport, Gr) ->
- Line = get_line(edlin:start(" --> "), Iport, Oport),
- switch_cmd(erl_scan:string(Line), Iport, Oport, Gr).
-
-switch_cmd({ok,[{atom,_,c},{integer,_,I}],_}, Iport, Oport, Gr0) ->
- case gr_set_cur(Gr0, I) of
- {ok,Gr} -> Gr;
- undefined -> unknown_group(Iport, Oport, Gr0)
+ edlin:init(gr_cur_pid(State#state.groups)),
+ {keep_state, State#state{ tty = NewTTYState },
+ {next_event, internal, line}}
end;
-switch_cmd({ok,[{atom,_,c}],_}, Iport, Oport, Gr) ->
- case gr_get_info(Gr, gr_cur_pid(Gr)) of
- undefined ->
- unknown_group(Iport, Oport, Gr);
- _ ->
- Gr
+switch_loop(internal, line, State) ->
+ {more_chars, Cont, Rs} = edlin:start(" --> "),
+ {keep_state, {Cont, State#state{ tty = io_requests(Rs, State#state.tty) }}};
+switch_loop(internal, {line, Line}, State) ->
+ case erl_scan:string(Line) of
+ {ok, Tokens, _} ->
+ case switch_cmd(Tokens, State#state.groups) of
+ {ok, Groups} ->
+ Curr = gr_cur_pid(Groups),
+ put(current_group, Curr),
+ Curr ! {self(), activate},
+ {next_state, server,
+ State#state{ current_group = Curr, groups = Groups }};
+ {retry, Requests} ->
+ {keep_state, State#state{ tty = io_requests(Requests, State#state.tty) },
+ {next_event, internal, line}};
+ {retry, Requests, Groups} ->
+ Curr = gr_cur_pid(Groups),
+ put(current_group, Curr),
+ {keep_state, State#state{
+ tty = io_requests(Requests, State#state.tty),
+ current_group = Curr,
+ groups = Groups },
+ {next_event, internal, line}}
+ end;
+ {error, _, _} ->
+ NewTTYState =
+ io_requests([{put_chars,unicode,<<"Illegal input\n">>}], State#state.tty),
+ {keep_state, State#state{ tty = NewTTYState },
+ {next_event, internal, line}}
end;
-switch_cmd({ok,[{atom,_,i},{integer,_,I}],_}, Iport, Oport, Gr) ->
+switch_loop(info,{ReadHandle,{data,Cs}}, {Cont, #state{ read = ReadHandle } = State}) ->
+ case edlin:edit_line(unicode:characters_to_list(Cs), Cont) of
+ {done,{[Line],_,_},_Rest, Rs} ->
+ {keep_state, State#state{ tty = io_requests(Rs, State#state.tty) },
+ {next_event, internal, {line, Line}}};
+ {undefined,_Char,MoreCs,NewCont,Rs} ->
+ {keep_state,
+ {NewCont, State#state{ tty = io_requests(Rs ++ [beep], State#state.tty)}},
+ {next_event, info, {ReadHandle,{data,MoreCs}}}};
+ {more_chars,NewCont,Rs} ->
+ {keep_state,
+ {NewCont, State#state{ tty = io_requests(Rs, State#state.tty)}}};
+ {blink,NewCont,Rs} ->
+ {keep_state,
+ {NewCont, State#state{ tty = io_requests(Rs, State#state.tty)}},
+ 1000}
+ end;
+switch_loop(timeout, _, {_Cont, State}) ->
+ {keep_state_and_data,
+ {next_event, info, {State#state.read,{data,[]}}}};
+switch_loop(info, _Unknown, _State) ->
+ {keep_state_and_data, postpone}.
+
+switch_cmd([{atom,_,Key},{Type,_,Value}], Gr)
+ when Type =:= atom; Type =:= integer ->
+ switch_cmd({Key, Value}, Gr);
+switch_cmd([{atom,_,Key},{atom,_,V1},{atom,_,V2}], Gr) ->
+ switch_cmd({Key, V1, V2}, Gr);
+switch_cmd([{atom,_,Key}], Gr) ->
+ switch_cmd(Key, Gr);
+switch_cmd([{'?',_}], Gr) ->
+ switch_cmd(h, Gr);
+
+switch_cmd(Cmd, Gr) when Cmd =:= c; Cmd =:= i; Cmd =:= k ->
+ switch_cmd({Cmd, gr_cur_index(Gr)}, Gr);
+switch_cmd({c, I}, Gr0) ->
+ case gr_set_cur(Gr0, I) of
+ {ok,Gr} -> {ok, Gr};
+ undefined -> unknown_group()
+ end;
+switch_cmd({i, I}, Gr) ->
case gr_get_num(Gr, I) of
{pid,Pid} ->
exit(Pid, interrupt),
- switch_loop(Iport, Oport, Gr);
+ {retry, []};
undefined ->
- unknown_group(Iport, Oport, Gr)
+ unknown_group()
end;
-switch_cmd({ok,[{atom,_,i}],_}, Iport, Oport, Gr) ->
- Pid = gr_cur_pid(Gr),
- case gr_get_info(Gr, Pid) of
- undefined ->
- unknown_group(Iport, Oport, Gr);
- _ ->
- exit(Pid, interrupt),
- switch_loop(Iport, Oport, Gr)
- end;
-switch_cmd({ok,[{atom,_,k},{integer,_,I}],_}, Iport, Oport, Gr) ->
+switch_cmd({k, I}, Gr) ->
case gr_get_num(Gr, I) of
{pid,Pid} ->
exit(Pid, die),
case gr_get_info(Gr, Pid) of
{_Ix,{}} -> % no shell
- switch_loop(Iport, Oport, Gr);
+ retry;
_ ->
- Gr1 =
- receive {'EXIT',Pid,_} ->
- gr_del_pid(Gr, Pid)
- after 1000 ->
- Gr
- end,
- switch_loop(Iport, Oport, Gr1)
+ receive {'EXIT',Pid,_} ->
+ {retry,[],gr_del_pid(Gr, Pid)}
+ after 1000 ->
+ {retry,[],Gr}
+ end
end;
undefined ->
- unknown_group(Iport, Oport, Gr)
+ unknown_group()
end;
-switch_cmd({ok,[{atom,_,k}],_}, Iport, Oport, Gr) ->
- Pid = gr_cur_pid(Gr),
- Info = gr_get_info(Gr, Pid),
- case Info of
- undefined ->
- unknown_group(Iport, Oport, Gr);
- {_Ix,{}} -> % no shell
- switch_loop(Iport, Oport, Gr);
- _ ->
- exit(Pid, die),
- Gr1 =
- receive {'EXIT',Pid,_} ->
- gr_del_pid(Gr, Pid)
- after 1000 ->
- Gr
- end,
- switch_loop(Iport, Oport, Gr1)
- end;
-switch_cmd({ok,[{atom,_,j}],_}, Iport, Oport, Gr) ->
- io_requests(gr_list(Gr), Iport, Oport),
- switch_loop(Iport, Oport, Gr);
-switch_cmd({ok,[{atom,_,s},{atom,_,Shell}],_}, Iport, Oport, Gr0) ->
+switch_cmd(j, Gr) ->
+ {retry, gr_list(Gr)};
+switch_cmd({s, Shell}, Gr0) when is_atom(Shell) ->
Pid = group:start(self(), {Shell,start,[]}),
Gr = gr_add_cur(Gr0, Pid, {Shell,start,[]}),
- switch_loop(Iport, Oport, Gr);
-switch_cmd({ok,[{atom,_,s}],_}, Iport, Oport, Gr0) ->
- Pid = group:start(self(), {shell,start,[]}),
- Gr = gr_add_cur(Gr0, Pid, {shell,start,[]}),
- switch_loop(Iport, Oport, Gr);
-switch_cmd({ok,[{atom,_,r}],_}, Iport, Oport, Gr0) ->
+ {retry, [], Gr};
+switch_cmd(s, Gr) ->
+ switch_cmd({s, shell}, Gr);
+switch_cmd(r, Gr0) ->
case is_alive() of
true ->
Node = pool:get_node(),
- Pid = group:start(self(), {Node,shell,start,[]}),
+ Pid = group:start(self(), {Node,shell,start,[]}, group_opts(Node)),
Gr = gr_add_cur(Gr0, Pid, {Node,shell,start,[]}),
- switch_loop(Iport, Oport, Gr);
+ {retry, [], Gr};
false ->
- io_request({put_chars,unicode,"Not alive\n"}, Iport, Oport),
- switch_loop(Iport, Oport, Gr0)
+ {retry, [{put_chars,unicode,<<"Node is not alive\n">>}]}
+ end;
+switch_cmd({r, Node}, Gr) when is_atom(Node)->
+ switch_cmd({r, Node, shell}, Gr);
+switch_cmd({r,Node,Shell}, Gr0) when is_atom(Node), is_atom(Shell) ->
+ case is_alive() of
+ true ->
+ Pid = group:start(self(), {Node,Shell,start,[]}, group_opts(Node)),
+ Gr = gr_add_cur(Gr0, Pid, {Node,Shell,start,[]}),
+ {retry, [], Gr};
+ false ->
+ {retry, [{put_chars,unicode,"Node is not alive\n"}]}
end;
-switch_cmd({ok,[{atom,_,r},{atom,_,Node}],_}, Iport, Oport, Gr0) ->
- Pid = group:start(self(), {Node,shell,start,[]}),
- Gr = gr_add_cur(Gr0, Pid, {Node,shell,start,[]}),
- switch_loop(Iport, Oport, Gr);
-switch_cmd({ok,[{atom,_,r},{atom,_,Node},{atom,_,Shell}],_},
- Iport, Oport, Gr0) ->
- Pid = group:start(self(), {Node,Shell,start,[]}),
- Gr = gr_add_cur(Gr0, Pid, {Node,Shell,start,[]}),
- switch_loop(Iport, Oport, Gr);
-switch_cmd({ok,[{atom,_,q}],_}, Iport, Oport, Gr) ->
+
+switch_cmd(q, _Gr) ->
case erlang:system_info(break_ignored) of
true -> % noop
- io_request({put_chars,unicode,"Unknown command\n"}, Iport, Oport),
- switch_loop(Iport, Oport, Gr);
+ {retry, [{put_chars,unicode,<<"Unknown command\n">>}]};
false ->
halt()
end;
-switch_cmd({ok,[{atom,_,h}],_}, Iport, Oport, Gr) ->
- list_commands(Iport, Oport),
- switch_loop(Iport, Oport, Gr);
-switch_cmd({ok,[{'?',_}],_}, Iport, Oport, Gr) ->
- list_commands(Iport, Oport),
- switch_loop(Iport, Oport, Gr);
-switch_cmd({ok,[],_}, Iport, Oport, Gr) ->
- switch_loop(Iport, Oport, Gr);
-switch_cmd({ok,_Ts,_}, Iport, Oport, Gr) ->
- io_request({put_chars,unicode,"Unknown command\n"}, Iport, Oport),
- switch_loop(Iport, Oport, Gr);
-switch_cmd(_Ts, Iport, Oport, Gr) ->
- io_request({put_chars,unicode,"Illegal input\n"}, Iport, Oport),
- switch_loop(Iport, Oport, Gr).
-
-unknown_group(Iport, Oport, Gr) ->
- io_request({put_chars,unicode,"Unknown job\n"}, Iport, Oport),
- switch_loop(Iport, Oport, Gr).
-
-list_commands(Iport, Oport) ->
+switch_cmd(h, _Gr) ->
+ {retry, list_commands()};
+switch_cmd([], _Gr) ->
+ {retry,[]};
+switch_cmd(_Ts, _Gr) ->
+ {retry, [{put_chars,unicode,<<"Unknown command\n">>}]}.
+
+unknown_group() ->
+ {retry,[{put_chars,unicode,<<"Unknown job\n">>}]}.
+
+list_commands() ->
QuitReq = case erlang:system_info(break_ignored) of
- true ->
+ true ->
[];
false ->
- [{put_chars, unicode," q - quit erlang\n"}]
+ [{put_chars, unicode,<<" q - quit erlang\n">>}]
end,
- io_requests([{put_chars, unicode," c [nn] - connect to job\n"},
- {put_chars, unicode," i [nn] - interrupt job\n"},
- {put_chars, unicode," k [nn] - kill job\n"},
- {put_chars, unicode," j - list all jobs\n"},
- {put_chars, unicode," s [shell] - start local shell\n"},
- {put_chars, unicode," r [node [shell]] - start remote shell\n"}] ++
- QuitReq ++
- [{put_chars, unicode," ? | h - this message\n"}],
- Iport, Oport).
-
-get_line({done,Line,_Rest,Rs}, Iport, Oport) ->
- io_requests(Rs, Iport, Oport),
- Line;
-get_line({undefined,_Char,Cs,Cont,Rs}, Iport, Oport) ->
- io_requests(Rs, Iport, Oport),
- io_request(beep, Iport, Oport),
- get_line(edlin:edit_line(Cs, Cont), Iport, Oport);
-get_line({What,Cont0,Rs}, Iport, Oport) ->
- io_requests(Rs, Iport, Oport),
- receive
- {Iport,{data,Cs}} ->
- get_line(edlin:edit_line(Cs, Cont0), Iport, Oport);
- {Iport,eof} ->
- get_line(edlin:edit_line(eof, Cont0), Iport, Oport)
- after
- get_line_timeout(What) ->
- get_line(edlin:edit_line([], Cont0), Iport, Oport)
- end.
-
-get_line_timeout(blink) -> 1000;
-get_line_timeout(more_chars) -> infinity.
-
-% Let driver report window geometry,
-% definitely outside of the common interface
-get_tty_geometry(Iport) ->
- case (catch port_control(Iport,?CTRL_OP_GET_WINSIZE,[])) of
- List when length(List) =:= 8 ->
- <<W:32/native,H:32/native>> = list_to_binary(List),
- {W,H};
- _ ->
- error
- end.
-get_unicode_state(Iport) ->
- case (catch port_control(Iport,?CTRL_OP_GET_UNICODE_STATE,[])) of
- [Int] when Int > 0 ->
- true;
- [Int] when Int =:= 0 ->
- false;
- _ ->
- error
- end.
-
-set_unicode_state(Iport, Bool) ->
- Data = case Bool of
- true -> [1];
- false -> [0]
- end,
- case (catch port_control(Iport,?CTRL_OP_SET_UNICODE_STATE,Data)) of
- [Int] when Int > 0 ->
- {unicode, utf8};
- [Int] when Int =:= 0 ->
- {unicode, false};
- _ ->
- error
+ [{put_chars, unicode,<<" c [nn] - connect to job\n">>},
+ {put_chars, unicode,<<" i [nn] - interrupt job\n">>},
+ {put_chars, unicode,<<" k [nn] - kill job\n">>},
+ {put_chars, unicode,<<" j - list all jobs\n">>},
+ {put_chars, unicode,<<" s [shell] - start local shell\n">>},
+ {put_chars, unicode,<<" r [node [shell]] - start remote shell\n">>}] ++
+ QuitReq ++
+ [{put_chars, unicode,<<" ? | h - this message\n">>}].
+
+group_opts(Node) ->
+ VersionString = erpc:call(Node, erlang, system_info, [otp_release]),
+ Version = list_to_integer(VersionString),
+ ExpandFun =
+ case Version > 25 of
+ true -> [{expand_fun,fun(B, Opts)-> erpc:call(Node,edlin_expand,expand,[B, Opts]) end}];
+ false -> [{expand_fun,fun(B, _)-> erpc:call(Node,edlin_expand,expand,[B]) end}]
+ end,
+ group_opts() ++ ExpandFun.
+group_opts() ->
+ [{expand_below, application:get_env(stdlib, shell_expand_location, below) =:= below}].
+
+-spec io_request(request(), prim_tty:state()) -> {noreply, prim_tty:state()} |
+ {term(), reference(), prim_tty:state()}.
+io_request({requests,Rs}, TTY) ->
+ {noreply, io_requests(Rs, TTY)};
+io_request(redraw_prompt, TTY) ->
+ write(prim_tty:handle_request(TTY, redraw_prompt));
+io_request({redraw_prompt, Pbs, Pbs2, LineState}, TTY) ->
+ write(prim_tty:handle_request(TTY, {redraw_prompt, Pbs, Pbs2, LineState}));
+io_request(new_prompt, TTY) ->
+ write(prim_tty:handle_request(TTY, new_prompt));
+io_request(delete_after_cursor, TTY) ->
+ write(prim_tty:handle_request(TTY, delete_after_cursor));
+io_request(delete_line, TTY) ->
+ write(prim_tty:handle_request(TTY, delete_line));
+io_request({put_chars_keep_state, unicode, Chars}, TTY) ->
+ write(prim_tty:handle_request(TTY, {putc_keep_state, unicode:characters_to_binary(Chars)}));
+io_request({put_chars, unicode, Chars}, TTY) ->
+ write(prim_tty:handle_request(TTY, {putc, unicode:characters_to_binary(Chars)}));
+io_request({put_chars_sync, unicode, Chars, Reply}, TTY) ->
+ {Output, NewTTY} = prim_tty:handle_request(TTY, {putc, unicode:characters_to_binary(Chars)}),
+ {ok, MonitorRef} = prim_tty:write(NewTTY, Output, self()),
+ {Reply, MonitorRef, NewTTY};
+io_request({put_chars_sync, latin1, Chars, Reply}, TTY) ->
+ {Output, NewTTY} = prim_tty:handle_request(TTY, {putc_raw, Chars}),
+ {ok, MonitorRef} = prim_tty:write(NewTTY, Output, self()),
+ {Reply, MonitorRef, NewTTY};
+io_request({put_expand, unicode, Chars}, TTY) ->
+ write(prim_tty:handle_request(TTY, {expand_with_trim, unicode:characters_to_binary(Chars)}));
+io_request({put_expand_no_trim, unicode, Chars}, TTY) ->
+ write(prim_tty:handle_request(TTY, {expand, unicode:characters_to_binary(Chars)}));
+io_request({move_rel, N}, TTY) ->
+ write(prim_tty:handle_request(TTY, {move, N}));
+io_request({move_line, R}, TTY) ->
+ write(prim_tty:handle_request(TTY, {move_line, R}));
+io_request({move_combo, V1, R, V2}, TTY) ->
+ write(prim_tty:handle_request(TTY, {move_combo, V1, R, V2}));
+io_request({insert_chars, unicode, Chars}, TTY) ->
+ write(prim_tty:handle_request(TTY, {insert, unicode:characters_to_binary(Chars)}));
+io_request({delete_chars, N}, TTY) ->
+ write(prim_tty:handle_request(TTY, {delete, N}));
+io_request(clear, TTY) ->
+ write(prim_tty:handle_request(TTY, clear));
+io_request(beep, TTY) ->
+ write(prim_tty:handle_request(TTY, beep)).
+
+write({Output, TTY}) ->
+ ok = prim_tty:write(TTY, Output),
+ {noreply, TTY}.
+
+io_requests([{insert_chars, unicode, C1},{insert_chars, unicode, C2}|Rs], TTY) ->
+ io_requests([{insert_chars, unicode, [C1,C2]}|Rs], TTY);
+io_requests([{put_chars, unicode, C1},{put_chars, unicode, C2}|Rs], TTY) ->
+ io_requests([{put_chars, unicode, [C1,C2]}|Rs], TTY);
+io_requests([{move_rel, N}, {move_line, R}, {move_rel, M}|Rs], TTY) ->
+ io_requests([{move_combo, N, R, M}|Rs], TTY);
+io_requests([{move_rel, N}, {move_line, R}|Rs], TTY) ->
+ io_requests([{move_combo, N, R, 0}|Rs], TTY);
+io_requests([{move_line, R}, {move_rel, M}|Rs], TTY) ->
+ io_requests([{move_combo, 0, R, M}|Rs], TTY);
+io_requests([R|Rs], TTY) ->
+ {noreply, NewTTY} = io_request(R, TTY),
+ io_requests(Rs, NewTTY);
+io_requests([], TTY) ->
+ TTY.
+
+open_editor(TTY, Buffer) ->
+ DefaultEditor =
+ case os:type() of
+ {win32, _} -> "notepad";
+ {unix, _} -> "nano"
+ end,
+ Editor = os:getenv("VISUAL", os:getenv("EDITOR", DefaultEditor)),
+ TmpFile = string:chomp(mktemp()) ++ ".erl",
+ _ = file:write_file(TmpFile, unicode:characters_to_binary(Buffer, unicode)),
+ case filelib:is_file(TmpFile) of
+ true ->
+ ok = prim_tty:disable_reader(TTY),
+ try
+ EditorPort =
+ case os:type() of
+ {win32, _} ->
+ [Cmd | Args] = string:split(Editor," ", all),
+ open_port({spawn_executable, os:find_executable(Cmd)},
+ [{args,Args ++ [TmpFile]}, nouse_stdio]);
+ {unix, _ } ->
+ open_port({spawn, Editor ++ " " ++ TmpFile}, [nouse_stdio])
+ end,
+ {EditorPort, TmpFile}
+ catch error:enoent ->
+ ok = prim_tty:enable_reader(TTY),
+ io:format(standard_error, "Could not find EDITOR '~ts'.~n", [Editor]),
+ false
+ end;
+ false ->
+ io:format(standard_error,
+ "Could not find create temp file '~ts'.~n",
+ [TmpFile]),
+ false
end.
-%% io_request(Request, InPort, OutPort)
-%% io_requests(Requests, InPort, OutPort)
-%% Note: InPort is unused.
-io_request({requests,Rs}, Iport, Oport) ->
- io_requests(Rs, Iport, Oport);
-io_request(Request, _Iport, Oport) ->
- case io_command(Request) of
- {Data, Reply} ->
- true = port_command(Oport, Data),
- Reply;
- unhandled ->
- ok
+mktemp() ->
+ case os:type() of
+ {win32, _} ->
+ os:cmd("powershell \"write-host (& New-TemporaryFile | Select-Object -ExpandProperty FullName)\"");
+ {unix,_} ->
+ os:cmd("mktemp")
end.
-io_requests([R|Rs], Iport, Oport) ->
- io_request(R, Iport, Oport),
- io_requests(Rs, Iport, Oport);
-io_requests([], _Iport, _Oport) ->
- ok.
-
-put_int16(N, Tail) ->
- [(N bsr 8)band 255,N band 255|Tail].
-
-%% When a put_chars_sync command is used, user_drv guarantees that
-%% the bytes have been put in the buffer of the port before an acknowledgement
-%% is sent back to the process sending the request. This command was added in
-%% OTP 18 to make sure that data sent from io:format is actually printed
-%% to the console before the vm stops when calling erlang:halt(integer()).
--dialyzer({no_improper_lists, io_command/1}).
-io_command({put_chars_sync, unicode,Cs,Reply}) ->
- {[?OP_PUTC_SYNC|unicode:characters_to_binary(Cs,utf8)], Reply};
-io_command({put_chars, unicode,Cs}) ->
- {[?OP_PUTC|unicode:characters_to_binary(Cs,utf8)], ok};
-io_command({move_rel,N}) ->
- {[?OP_MOVE|put_int16(N, [])], ok};
-io_command({insert_chars,unicode,Cs}) ->
- {[?OP_INSC|unicode:characters_to_binary(Cs,utf8)], ok};
-io_command({delete_chars,N}) ->
- {[?OP_DELC|put_int16(N, [])], ok};
-io_command(beep) ->
- {[?OP_BEEP], ok};
-io_command(_) ->
- unhandled.
+handle_req(next, TTYState, {false, IOQ} = IOQueue) ->
+ case queue:out(IOQ) of
+ {empty, _} ->
+ {TTYState, IOQueue};
+ {{value, {Origin, Req}}, ExecQ} ->
+ case io_request(Req, TTYState) of
+ {noreply, NewTTYState} ->
+ handle_req(next, NewTTYState, {false, ExecQ});
+ {Reply, MonitorRef, NewTTYState} ->
+ {NewTTYState, {{Origin, MonitorRef, Reply}, ExecQ}}
+ end
+ end;
+handle_req(Msg, TTYState, {false, IOQ} = IOQueue) ->
+ empty = queue:peek(IOQ),
+ {Origin, Req} = Msg,
+ case io_request(Req, TTYState) of
+ {noreply, NewTTYState} ->
+ {NewTTYState, IOQueue};
+ {Reply, MonitorRef, NewTTYState} ->
+ {NewTTYState, {{Origin, MonitorRef, Reply}, IOQ}}
+ end;
+handle_req(Msg,TTYState,{Resp, IOQ}) ->
+ %% All requests are queued when we have outstanding sync put_chars
+ {TTYState, {Resp, queue:in(Msg,IOQ)}}.
%% gr_new()
%% gr_get_num(Group, Index)
@@ -611,100 +910,71 @@ io_command(_) ->
%% gr_add_cur(Group, Pid, Shell)
%% gr_set_cur(Group, Index)
%% gr_cur_pid(Group)
+%% gr_cur_index(Group)
%% gr_del_pid(Group, Pid)
%% Manage the group list. The group structure has the form:
%% {NextIndex,CurrIndex,CurrPid,GroupList}
%%
%% where each element in the group list is:
%% {Index,GroupPid,Shell}
-
+-record(group, { index, pid, shell }).
+-record(gr, { next = 0, current = 0, pid = none, groups = []}).
gr_new() ->
- {0,0,none,[]}.
-
-gr_get_num({_Next,_CurI,_CurP,Gs}, I) ->
- gr_get_num1(Gs, I).
-
-gr_get_num1([{I,_Pid,{}}|_Gs], I) ->
- undefined;
-gr_get_num1([{I,Pid,_S}|_Gs], I) ->
- {pid,Pid};
-gr_get_num1([_G|Gs], I) ->
- gr_get_num1(Gs, I);
-gr_get_num1([], _I) ->
- undefined.
-
-gr_get_info({_Next,_CurI,_CurP,Gs}, Pid) ->
- gr_get_info1(Gs, Pid).
-
-gr_get_info1([{I,Pid,S}|_Gs], Pid) ->
- {I,S};
-gr_get_info1([_G|Gs], I) ->
- gr_get_info1(Gs, I);
-gr_get_info1([], _I) ->
- undefined.
-
-gr_add_cur({Next,_CurI,_CurP,Gs}, Pid, Shell) ->
- {Next+1,Next,Pid,append(Gs, [{Next,Pid,Shell}])}.
-
-gr_set_cur({Next,_CurI,_CurP,Gs}, I) ->
- case gr_get_num1(Gs, I) of
- {pid,Pid} -> {ok,{Next,I,Pid,Gs}};
+ #gr{}.
+gr_new_group(I, P, S) ->
+ #group{ index = I, pid = P, shell = S }.
+
+gr_get_num(#gr{ groups = Gs }, I) ->
+ case lists:keyfind(I, #group.index, Gs) of
+ false -> undefined;
+ #group{ shell = {} } ->
+ undefined;
+ #group{ pid = Pid } ->
+ {pid, Pid}
+ end.
+
+gr_get_info(#gr{ groups = Gs }, Pid) ->
+ case lists:keyfind(Pid, #group.pid, Gs) of
+ false -> undefined;
+ #group{ index = I, shell = S } ->
+ {I, S}
+ end.
+
+gr_add_cur(#gr{ next = Next, groups = Gs}, Pid, Shell) ->
+ put(current_group, Pid),
+ #gr{ next = Next + 1, current = Next, pid = Pid,
+ groups = Gs ++ [gr_new_group(Next, Pid, Shell)]
+ }.
+
+gr_set_cur(Gr, I) ->
+ case gr_get_num(Gr, I) of
+ {pid,Pid} ->
+ put(current_group, Pid),
+ {ok, Gr#gr{ current = I, pid = Pid }};
undefined -> undefined
end.
-gr_set_num({Next,CurI,CurP,Gs}, I, Pid, Shell) ->
- {Next,CurI,CurP,gr_set_num1(Gs, I, Pid, Shell)}.
-
-gr_set_num1([{I,_Pid,_Shell}|Gs], I, NewPid, NewShell) ->
- [{I,NewPid,NewShell}|Gs];
-gr_set_num1([{I,Pid,Shell}|Gs], NewI, NewPid, NewShell) when NewI > I ->
- [{I,Pid,Shell}|gr_set_num1(Gs, NewI, NewPid, NewShell)];
-gr_set_num1(Gs, NewI, NewPid, NewShell) ->
- [{NewI,NewPid,NewShell}|Gs].
-
-gr_del_pid({Next,CurI,CurP,Gs}, Pid) ->
- {Next,CurI,CurP,gr_del_pid1(Gs, Pid)}.
-
-gr_del_pid1([{_I,Pid,_S}|Gs], Pid) ->
- Gs;
-gr_del_pid1([G|Gs], Pid) ->
- [G|gr_del_pid1(Gs, Pid)];
-gr_del_pid1([], _Pid) ->
- [].
-
-gr_cur_pid({_Next,_CurI,CurP,_Gs}) ->
- CurP.
-
-gr_list({_Next,CurI,_CurP,Gs}) ->
- gr_list(Gs, CurI, []).
-
-gr_list([{_I,_Pid,{}}|Gs], Cur, Jobs) ->
- gr_list(Gs, Cur, Jobs);
-gr_list([{Cur,_Pid,Shell}|Gs], Cur, Jobs) ->
- gr_list(Gs, Cur, [{put_chars, unicode,flatten(io_lib:format("~4w* ~w\n", [Cur,Shell]))}|Jobs]);
-gr_list([{I,_Pid,Shell}|Gs], Cur, Jobs) ->
- gr_list(Gs, Cur, [{put_chars, unicode,flatten(io_lib:format("~4w ~w\n", [I,Shell]))}|Jobs]);
-gr_list([], _Cur, Jobs) ->
- lists:reverse(Jobs).
-
-append([H|T], X) ->
- [H|append(T, X)];
-append([], X) ->
- X.
-
-member(X, [X|_Rest]) -> true;
-member(X, [_H|Rest]) ->
- member(X, Rest);
-member(_X, []) -> false.
-
-flatten(List) ->
- flatten(List, [], []).
-
-flatten([H|T], Cont, Tail) when is_list(H) ->
- flatten(H, [T|Cont], Tail);
-flatten([H|T], Cont, Tail) ->
- [H|flatten(T, Cont, Tail)];
-flatten([], [H|Cont], Tail) ->
- flatten(H, Cont, Tail);
-flatten([], [], Tail) ->
- Tail.
+gr_set_num(Gr = #gr{ groups = Groups }, I, Pid, Shell) ->
+ NewGroups = lists:keystore(I, #group.index, Groups, gr_new_group(I,Pid,Shell)),
+ Gr#gr{ groups = NewGroups }.
+
+
+gr_del_pid(Gr = #gr{ groups = Groups }, Pid) ->
+ Gr#gr{ groups = lists:keydelete(Pid, #group.pid, Groups) }.
+
+
+gr_cur_pid(#gr{ pid = Pid }) ->
+ Pid.
+gr_cur_index(#gr{ current = Index }) ->
+ Index.
+
+gr_list(#gr{ current = Current, groups = Groups}) ->
+ lists:flatmap(
+ fun(#group{ shell = {} }) ->
+ [];
+ (#group{ index = I, shell = S }) ->
+ Marker = ["*" || Current =:= I],
+ [{put_chars, unicode,
+ unicode:characters_to_binary(
+ io_lib:format("~4w~.1ts ~w\n", [I,Marker,S]))}]
+ end, Groups).
diff --git a/lib/kernel/src/user_sup.erl b/lib/kernel/src/user_sup.erl
index c1fb1b1a48..25b4b8fbf3 100644
--- a/lib/kernel/src/user_sup.erl
+++ b/lib/kernel/src/user_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -39,11 +39,13 @@ start() ->
-spec init([]) -> 'ignore' | {'error', 'nouser'} | {'ok', pid(), pid()}.
init([]) ->
- case get_user() of
+ init(init:get_arguments());
+init(Flags) ->
+ case get_user(Flags) of
nouser ->
ignore;
{master, Master} ->
- Pid = start_slave(Master),
+ Pid = start_relay(Master),
{ok, Pid, Pid};
{M, F, A} ->
case start_user(M, F, A) of
@@ -54,7 +56,7 @@ init([]) ->
end
end.
-start_slave(Master) ->
+start_relay(Master) ->
case rpc:call(Master, erlang, whereis, [user]) of
User when is_pid(User) ->
spawn(?MODULE, relay, [User]);
@@ -112,19 +114,23 @@ wait_for_user_p(N) ->
wait_for_user_p(N-1)
end.
-get_user() ->
- Flags = init:get_arguments(),
- check_flags(Flags, {user_drv, start, []}).
+get_user(Flags) ->
+ check_flags(Flags, lists:keymember(detached, 1, Flags), {user_drv, start, []}).
%% These flags depend upon what arguments the erl script passes on
%% to erl91.
-check_flags([{nouser, []} |T], _) -> check_flags(T, nouser);
-check_flags([{user, [User]} | T], _) ->
- check_flags(T, {list_to_atom(User), start, []});
-check_flags([{noshell, []} | T], _) -> check_flags(T, {user, start, []});
-check_flags([{oldshell, []} | T], _) -> check_flags(T, {user, start, []});
-check_flags([{noinput, []} | T], _) -> check_flags(T, {user, start_out, []});
-check_flags([{master, [Node]} | T], _) ->
- check_flags(T, {master, list_to_atom(Node)});
-check_flags([_H | T], User) -> check_flags(T, User);
-check_flags([], User) -> User.
+check_flags([{nouser, []} |T], Attached, _) -> check_flags(T, Attached, nouser);
+check_flags([{user, [User]} | T], Attached, _) ->
+ check_flags(T, Attached, {list_to_atom(User), start, []});
+check_flags([{noshell, []} | T], Attached, _) ->
+ check_flags(T, Attached, {user_drv, start, [#{ initial_shell => noshell }]});
+check_flags([{oldshell, []} | T], false, _) ->
+ %% When running in detached mode, we ignore any -oldshell flags as we do not
+ %% want input => true to be set as they may halt the node (on bsd)
+ check_flags(T, false, {user_drv, start, [#{ initial_shell => oldshell }]});
+check_flags([{noinput, []} | T], Attached, _) ->
+ check_flags(T, Attached, {user_drv, start, [#{ initial_shell => noshell, input => false }]});
+check_flags([{master, [Node]} | T], Attached, _) ->
+ check_flags(T, Attached, {master, list_to_atom(Node)});
+check_flags([_H | T], Attached, User) -> check_flags(T, Attached, User);
+check_flags([], _Attached, User) -> User.
diff --git a/lib/kernel/src/wrap_log_reader.erl b/lib/kernel/src/wrap_log_reader.erl
index 3a984e56c7..97ebde3ed1 100644
--- a/lib/kernel/src/wrap_log_reader.erl
+++ b/lib/kernel/src/wrap_log_reader.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -116,11 +116,11 @@ open(File, FileNo) when is_list(File), is_integer(FileNo) ->
close(#wrap_reader{fd = FD}) ->
file:close(FD).
--type chunk_ret() :: {Continuation2, Terms :: [term()]}
- | {Continuation2,
+-type chunk_ret() :: {Continuation2 :: term(), Terms :: [term()]}
+ | {Continuation2 :: term(),
Terms :: [term()],
Badbytes :: non_neg_integer()}
- | {Continuation2, 'eof'}
+ | {Continuation2 :: term(), 'eof'}
| {'error', Reason :: term()}.
-spec chunk(Continuation) -> chunk_ret() when
diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile
index f7776f44fe..0d88e3555e 100644
--- a/lib/kernel/test/Makefile
+++ b/lib/kernel/test/Makefile
@@ -106,6 +106,7 @@ MODULES= \
net_SUITE \
os_SUITE \
pg_SUITE \
+ rtnode \
seq_trace_SUITE \
$(SOCKET_MODULES) \
wrap_log_reader_SUITE \
@@ -217,6 +218,7 @@ release_tests_spec: make_emakefile
$(EMAKEFILE) $(COVERFILE) "$(RELSYSDIR)"
chmod -R u+w "$(RELSYSDIR)"
@tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -)
+ $(INSTALL_DIR) "$(RELSYSDIR)/kernel_SUITE_data"
$(INSTALL_DATA) $(ERL_TOP)/make/otp_version_tickets "$(RELSYSDIR)/kernel_SUITE_data"
release_docs_spec:
diff --git a/lib/kernel/test/application_SUITE.erl b/lib/kernel/test/application_SUITE.erl
index 0668c28692..83f1bfada9 100644
--- a/lib/kernel/test/application_SUITE.erl
+++ b/lib/kernel/test/application_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -30,12 +30,12 @@
otp_1586/1, otp_2078/1, otp_2012/1, otp_2718/1, otp_2973/1,
otp_3002/1, otp_3184/1, otp_4066/1, otp_4227/1, otp_5363/1,
otp_5606/1,
- start_phases/1, get_key/1, get_env/1,
+ start_phases/1, get_key/1, get_env/1, get_supervisor/1,
set_env/1, set_env_persistent/1, set_env_errors/1, optional_applications/1,
permit_false_start_local/1, permit_false_start_dist/1, script_start/1,
nodedown_start/1, init2973/0, loop2973/0, loop5606/1, otp_16504/1]).
--export([config_change/1, persistent_env/1,
+-export([config_change/1, persistent_env/1, invalid_app_file/1,
distr_changed_tc1/1, distr_changed_tc2/1,
ensure_started/1, ensure_all_started/1,
shutdown_func/1, do_shutdown/1, shutdown_timeout/1, shutdown_deadlock/1,
@@ -58,11 +58,11 @@ all() ->
load_use_cache, ensure_started, {group, reported_bugs}, start_phases,
script_start, nodedown_start, permit_false_start_local,
permit_false_start_dist, get_key, get_env, ensure_all_started,
- set_env, set_env_persistent, set_env_errors,
+ set_env, set_env_persistent, set_env_errors, get_supervisor,
{group, distr_changed}, config_change, shutdown_func, shutdown_timeout,
shutdown_deadlock, config_relative_paths, optional_applications,
persistent_env, handle_many_config_files, format_log_1, format_log_2,
- configfd_bash, configfd_port_program].
+ configfd_bash, configfd_port_program, invalid_app_file].
groups() ->
[{reported_bugs, [],
@@ -960,9 +960,13 @@ ensure_started(_Conf) ->
ok = application:unload(app1),
ok.
-%% Test application:ensure_all_started/1-2.
+%% Test application:ensure_all_started/1-2-3.
ensure_all_started(_Conf) ->
+ do_ensure_all_started(serial),
+ do_ensure_all_started(concurrent),
+ ok.
+do_ensure_all_started(Mode) ->
{ok, Fd1} = file:open("app1.app", [write]),
w_app1(Fd1),
file:close(Fd1),
@@ -981,9 +985,10 @@ ensure_all_started(_Conf) ->
%% Single app start/stop
false = lists:keyfind(app1, 1, application:which_applications()),
- {ok, [app1]} = application:ensure_all_started(app1), % app1 started
+ {ok, [app1]} = application:ensure_all_started(app1, temporary, Mode), % app1 started
{app1, _, _} = lists:keyfind(app1, 1, application:which_applications()),
- {ok, []} = application:ensure_all_started(app1), % no start needed
+ {ok, []} = application:ensure_all_started(app1, temporary), % no start needed
+ {ok, []} = application:ensure_all_started(app1, permanent), % no start needed
ok = application:stop(app1),
false = lists:keyfind(app1, 1, application:which_applications()),
ok = application:unload(app1),
@@ -995,13 +1000,26 @@ ensure_all_started(_Conf) ->
%% Start dependencies.
{error, {not_started, app9}} = application:start(app10),
- {ok, [app9,app10]} = application:ensure_all_started(app10, temporary),
+ {ok, [app9,app10]} = application:ensure_all_started(app10, temporary, Mode),
{app9, _, _} = lists:keyfind(app9, 1, application:which_applications()),
{app10, _, _} = lists:keyfind(app10, 1, application:which_applications()),
%% Only report apps/dependencies that actually needed to start
ok = application:stop(app10),
ok = application:unload(app10),
- {ok, [app10]} = application:ensure_all_started(app10, temporary),
+ {ok, [app10]} = application:ensure_all_started(app10, temporary, Mode),
+ ok = application:stop(app9),
+ ok = application:unload(app9),
+ ok = application:stop(app10),
+ ok = application:unload(app10),
+
+ %% Starts several
+ {ok, StartedSeveral} = application:ensure_all_started([app1, app10], temporary, Mode),
+ [app1,app10,app9] = lists:sort(StartedSeveral),
+ {app1, _, _} = lists:keyfind(app1, 1, application:which_applications()),
+ {app9, _, _} = lists:keyfind(app9, 1, application:which_applications()),
+ {app10, _, _} = lists:keyfind(app10, 1, application:which_applications()),
+ ok = application:stop(app1),
+ ok = application:unload(app1),
ok = application:stop(app9),
ok = application:unload(app9),
ok = application:stop(app10),
@@ -1016,16 +1034,16 @@ ensure_all_started(_Conf) ->
%% nor app10 running after failing to start
%% hopefully_not_an_existing_app
{error, {hopefully_not_an_existing_app, {"no such file or directory", _}}}=
- application:ensure_all_started(app_chain_error),
+ application:ensure_all_started(app_chain_error, temporary, Mode),
false = lists:keyfind(app9, 1, application:which_applications()),
false = lists:keyfind(app10, 1, application:which_applications()),
- false = lists:keyfind(app_chain_error2,1,application:which_applications()),
+ false = lists:keyfind(app_chain_error2, 1, application:which_applications()),
false = lists:keyfind(app_chain_error, 1, application:which_applications()),
%% Here we will have app9 already running, and app10 should be
%% able to boot fine.
%% In this dependency failing, we expect app9 to still be running, but
%% not app10 after failing to start hopefully_not_an_existing_app
- {ok, [app9]} = application:ensure_all_started(app9, temporary),
+ {ok, [app9]} = application:ensure_all_started(app9, temporary, Mode),
{error, {hopefully_not_an_existing_app, {"no such file or directory", _}}}=
application:ensure_all_started(app_chain_error),
{app9, _, _} = lists:keyfind(app9, 1, application:which_applications()),
@@ -1652,6 +1670,12 @@ get_env(Conf) when is_list(Conf) ->
default = application:get_env(kernel, error_logger_xyz, default),
ok.
+get_supervisor(Conf) when is_list(Conf) ->
+ undefined = application:get_supervisor(stdlib),
+ {ok, Pid} = application:get_supervisor(kernel),
+ Pid = erlang:whereis(kernel_sup),
+ ok.
+
%%-----------------------------------------------------------------
%% Should be started in a CC view with:
%% erl -sname XXX -rsh ctrsh where XX not in [cp1, cp2, cp3]
@@ -2190,17 +2214,16 @@ do_configfd_test_bash() ->
case application:start(os_mon) of
ok -> case total_memory() of
Memory when is_integer(Memory),
- Memory > 16 ->
+ Memory > 8 ->
application:stop(os_mon),
- true =
- ("magic42" =/=
- RunInBash(
- "erl "
- "-noshell "
- "-configfd 3 "
- "-eval "
- "'io:format(\"magic42\"),erlang:halt()' "
- "3< <(erl -noshell -eval '(fun W(D) -> io:put_chars(D), W([D,D]) end)(<<\"00000000000000000\">>)') "));
+ Res = RunInBash(
+ "erl "
+ "-noshell "
+ "-configfd 3 "
+ "-eval "
+ "'io:format(\"magic42\"),erlang:halt()' "
+ "3< <(erl -noshell -eval '(fun W(D) -> io:put_chars(D), W([D,<<\"00000000000000000\">>]) end)([])') "),
+ {match, _} = re:run(Res,"Max size 134217728 bytes exceeded");
_ ->
io:format("Skipped huge file check to avoid flaky test on machine with less than 8GB of memory")
end;
@@ -2454,6 +2477,19 @@ persistent_env(Conf) when is_list(Conf) ->
%% Clean up
ok = application:unload(appinc).
+%% Test that application app file error handling works as it should
+invalid_app_file(_Config) ->
+
+ {error,{bad_application,{application,"name",[]}}}
+ = application:load({application, "name",[]}),
+ {error,{invalid_options,#{}}}
+ = application:load({application, name,#{}}),
+ {error, {invalid_options,_}} =
+ application:load({application,name,[{env,[{"key",value}]}]}),
+ {error, {invalid_options,_}} =
+ application:load({application,name,[{env,[key]}]}),
+ {error, {invalid_options,_}} =
+ application:load({application,name,[{env,[{key,value},{key,value}]}]}).
%% Test more than one config file defined by one -config parameter:
handle_many_config_files(Conf) when is_list(Conf) ->
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index 7059d477d9..8eadcf9cb3 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@
on_load_deleted/1,
big_boot_embedded/1,
module_status/1,
- get_mode/1,
+ get_mode/1, code_path_cache/1,
normalized_paths/1, mult_embedded_flags/1]).
-export([init_per_testcase/2, end_per_testcase/2,
@@ -62,7 +62,7 @@ all() ->
replace_path, load_file, load_abs, ensure_loaded,
delete, purge, purge_many_exits, soft_purge, is_loaded, all_loaded,
all_available, load_binary, dir_req, object_code, set_path_file,
- upgrade,
+ upgrade, code_path_cache,
sticky_dir, pa_pz_option, add_del_path, dir_disappeared,
ext_mod_dep, clash, where_is_file,
purge_stacktrace, mult_lib_roots,
@@ -316,6 +316,68 @@ replace_path(Config) when is_list(Config) ->
ok.
+code_path_cache(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+
+ non_existing = code:which(?TESTMOD), % verify dummy name not in path
+ code:purge(?TESTMOD), % ensure no previous version in memory
+ code:delete(?TESTMOD),
+ code:purge(?TESTMOD),
+
+ Original = code:get_path(),
+ Dir = filename:join(PrivDir, "myebin"),
+ filelib:ensure_path(Dir),
+ ToBeReplacedDir1 = filename:join(PrivDir, "tobereplaced-1/ebin"),
+ ToBeReplacedDir2 = filename:join(PrivDir, "tobereplaced-2/ebin"),
+ filelib:ensure_path(ToBeReplacedDir1),
+ filelib:ensure_path(ToBeReplacedDir2),
+
+ File = filename:join(Dir, ?TESTMODOBJ),
+ {ok,?TESTMOD,Bin} = compile:forms(dummy_ast(), []),
+
+ %% Adding a cached path and re-adding for clearing
+ [begin
+ error = code:get_object_code(?TESTMOD),
+ true = code:Fun(Dir, cache),
+ error = code:get_object_code(?TESTMOD),
+ ok = file:write_file(File, Bin),
+ error = code:get_object_code(?TESTMOD),
+ true = code:Fun(Dir, cache),
+ {_,_,_} = code:get_object_code(?TESTMOD),
+ code:del_path(Dir),
+ ok = file:delete(File)
+ end || Fun <- [add_path, add_patha, add_pathz]],
+
+ Original = code:get_path(),
+
+ %% Adding several cached paths and explicit cache clearing
+ [begin
+ error = code:get_object_code(?TESTMOD),
+ ok = code:Fun([Dir, ToBeReplacedDir1], cache),
+ error = code:get_object_code(?TESTMOD),
+ ok = file:write_file(File, Bin),
+ error = code:get_object_code(?TESTMOD),
+ ok = code:clear_cache(),
+ {_,_,_} = code:get_object_code(?TESTMOD),
+ ok = code:del_paths([Dir, ToBeReplacedDir1]),
+ ok = file:delete(File)
+ end || Fun <- [add_paths, add_pathsa, add_pathsz]],
+
+ Original = code:get_path(),
+
+ %% Replacing a non-cached path with cache and set_path for clearing
+ error = code:get_object_code(?TESTMOD),
+ true = code:add_path(ToBeReplacedDir1),
+ true = code:replace_path(tobereplaced, ToBeReplacedDir2, cache),
+ error = code:get_object_code(?TESTMOD),
+ ok = file:write_file(filename:join(ToBeReplacedDir2, ?TESTMODOBJ), Bin),
+ error = code:get_object_code(?TESTMOD),
+ true = code:set_path(code:get_path(), nocache),
+ {_,_,_} = code:get_object_code(?TESTMOD),
+
+ true = code:set_path(Original),
+ ok.
+
%% OTP-3977.
dir_disappeared(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
@@ -997,8 +1059,12 @@ purge_stacktrace_test(Config) when is_list(Config) ->
mult_lib_roots(Config) when is_list(Config) ->
DataDir = filename:join(proplists:get_value(data_dir, Config), "mult_lib_roots"),
mult_lib_compile(DataDir, "my_dummy_app-b/ebin/lists"),
- mult_lib_compile(DataDir,
- "my_dummy_app-c/ebin/code_SUITE_mult_root_module"),
+
+ %% We will use this module to test both code path loading and
+ %% code path caching. So we ensure its beam file does not exist.
+ CodeSuiteMultRootBeam =
+ filename:join(DataDir, "first_root/my_dummy_app-c/ebin/code_SUITE_mult_root_module.beam"),
+ file:delete(CodeSuiteMultRootBeam),
%% Set up ERL_LIBS and start a peer node.
ErlLibs = filename:join(DataDir, "first_root") ++ mult_lib_sep() ++
@@ -1026,9 +1092,31 @@ mult_lib_roots(Config) when is_list(Config) ->
E <- lists:sort([Lib1,Lib2,Lib3,Lib4,Lib5])],
io:format("~p\n", [Path]),
+ %% Now let's attempt to dynamically add a module,
+ %% this will fail due to the boot paths cache.
+ error = rpc:call(Node, code, get_object_code, [code_SUITE_mult_root_module]),
+ mult_lib_compile(DataDir, "my_dummy_app-c/ebin/code_SUITE_mult_root_module"),
+ error = rpc:call(Node, code, get_object_code, [code_SUITE_mult_root_module]),
+ %% Test that the CWD is not cached
+ File = filename:join([DataDir, "first_root/my_dummy_app-c/ebin/code_SUITE_mult_root_module"]),
+ {ok, _} = compile:file(File, [{outdir, "."}]),
+ {_,_,_} = rpc:call(Node, code, get_object_code, [code_SUITE_mult_root_module]),
true = rpc:call(Node, code_SUITE_mult_root_module, works_fine, []),
+ file:delete("code_SUITE_mult_root_module.beam"),
+ %% Clean up so we can start again
+ file:delete(CodeSuiteMultRootBeam),
peer:stop(Peer),
+ NoCacheArgs = ["-env", "ERL_LIBS", ErlLibs, "-cache_boot_paths", "false"],
+ {ok, NoCachePeer, NoCacheNode} = ?CT_PEER(NoCacheArgs),
+
+ %% Now the same code should work
+ error = rpc:call(NoCacheNode, code, get_object_code, [code_SUITE_mult_root_module]),
+ mult_lib_compile(DataDir, "my_dummy_app-c/ebin/code_SUITE_mult_root_module"),
+ {_,_,_} = rpc:call(NoCacheNode, code, get_object_code, [code_SUITE_mult_root_module]),
+ true = rpc:call(NoCacheNode, code_SUITE_mult_root_module, works_fine, []),
+
+ peer:stop(NoCachePeer),
ok.
mult_lib_compile(Root, Last) ->
@@ -1660,9 +1748,9 @@ on_load_self_call(_Config) ->
ok.
on_load_do_load(Mod, Code) ->
- spawn(fun() ->
- {module,Mod} = code:load_binary(Mod, "", Code)
- end),
+ spawn_link(fun() ->
+ {module,Mod} = code:load_binary(Mod, "", Code)
+ end),
receive
Any -> Any
end.
@@ -1753,9 +1841,9 @@ on_load_deleted(_Config) ->
" receive _ -> ok end.\n"]),
merl:print(Tree),
{ok,Mod,Code} = merl:compile(Tree),
- spawn(fun() ->
- {module,Mod} = code:load_binary(Mod, "", Code)
- end),
+ spawn_link(fun() ->
+ {module,Mod} = code:load_binary(Mod, "", Code)
+ end),
receive after 1 -> ok end,
{module,OtherMod} = code:load_binary(OtherMod, "",
OtherCode),
@@ -1778,10 +1866,10 @@ delete_before_reload(Mod, Reload) ->
{ok,Mod,Code1} = merl:compile(Tree1),
Self = self(),
- spawn(fun() ->
- {module,Mod} = code:load_binary(Mod, "", Code1),
- Mod:f(Self)
- end),
+ spawn_link(fun() ->
+ {module,Mod} = code:load_binary(Mod, "", Code1),
+ Mod:f(Self)
+ end),
receive started -> ok end,
true = code:delete(Mod),
diff --git a/lib/kernel/test/disk_log_SUITE.erl b/lib/kernel/test/disk_log_SUITE.erl
index 2be791a571..dc4b6c4862 100644
--- a/lib/kernel/test/disk_log_SUITE.erl
+++ b/lib/kernel/test/disk_log_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -34,6 +34,8 @@
-define(datadir(Conf), proplists:get_value(data_dir, Conf)).
-endif.
+-compile(export_all).
+
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
@@ -52,6 +54,9 @@
wrap_ext_1/1, wrap_ext_2/1,
+ rotate_1/1, rotate_truncate/1, rotate_reopen/1,
+ rotate_breopen/1, next_rotate_file/1,
+
head_func/1, plain_head/1, one_header/1,
wrap_notif/1, full_notif/1, trunc_notif/1, blocked_notif/1,
@@ -118,7 +123,7 @@
notif, new_idx_vsn, reopen, block, unblock, open, close,
error, chunk, truncate, many_users, info, change_size,
open_change_size, change_attribute, otp_6278, otp_10131,
- otp_16768, otp_16809]).
+ otp_16768, otp_16809, rotate]).
suite() ->
@@ -127,7 +132,7 @@ suite() ->
all() ->
[{group, halt_int}, {group, wrap_int},
- {group, halt_ext}, {group, wrap_ext},
+ {group, halt_ext}, {group, wrap_ext}, {group, rotate},
{group, read_mode}, {group, head}, {group, notif},
new_idx_vsn, reopen, {group, block}, unblock,
{group, open}, {group, close}, {group, error}, chunk,
@@ -146,6 +151,9 @@ groups() ->
{halt_ext, [], [halt_ext_inf, {group, halt_ext_sz}]},
{halt_ext_sz, [], [halt_ext_sz_1, halt_ext_sz_2]},
{wrap_ext, [], [wrap_ext_1, wrap_ext_2]},
+ {rotate, [],
+ [rotate_1, rotate_truncate, rotate_reopen,
+ rotate_breopen, next_rotate_file]},
{head, [], [head_func, plain_head, one_header]},
{notif, [],
[wrap_notif, full_notif, trunc_notif, blocked_notif]},
@@ -462,7 +470,7 @@ halt_ro_crash(Conf) when is_list(Conf) ->
%% This is how it was before R6B:
%% {C1,T1,15} = disk_log:chunk(a,start),
%% {C2,T2} = disk_log:chunk(a,C1),
- {C1,_OneItem,7478} = disk_log:chunk(a,start),
+ {C1,_OneItem,7476} = disk_log:chunk(a,start),
{C2, [], 7} = disk_log:chunk(a,C1),
eof = disk_log:chunk(a,C2),
ok = disk_log:close(a),
@@ -815,6 +823,166 @@ wrap_ext_2(Conf) when is_list(Conf) ->
del(File3, 3),
ok.
+%% Test rotate disk log, external, size defined.
+rotate_1(Conf) when is_list(Conf) ->
+ Dir = ?privdir(Conf),
+ File = filename:join(Dir, "a.LOG"),
+ Name = a,
+ {ok, Name} = disk_log:open([{name,Name}, {type,rotate}, {size,{8000, 3}},
+ {format,external},
+ {file, File}]),
+ x2simple_log(File, Name),
+ ok = disk_log:close(Name),
+ del_rot_files(File, 4),
+ {ok, Name} = disk_log:open([{name,Name}, {type,rotate}, {size,{8000, 3}},
+ {format,external},
+ {file, File}]),
+ {B1, _T1} = x_mk_bytes(10000), % lost due to rotation
+ {B2, T2} = x_mk_bytes(5000), % file a.LOG.2.gx
+ {B3, T3} = x_mk_bytes(4000), % file a.LOG.1.gz
+ {B4, T4} = x_mk_bytes(2000), % file a.LOG.1.gz
+ {B5, T5} = x_mk_bytes(5000), % file a.LOG.0.gz
+ {B6, T6} = x_mk_bytes(5000), % in the active file
+ ok = disk_log:blog(Name, B1),
+ ok = disk_log:blog(Name, B2),
+ ok = disk_log:blog(Name, B3),
+ ok = disk_log:blog_terms(a, [B4, B5, B6]),
+ case get_list(File ++ ".2.gz", Name, rotate) of
+ T2 ->
+ ok;
+ E2 ->
+ test_server_fail({bad_terms, E2, T2})
+ end,
+ T34 = T3 ++ T4,
+ case get_list(File ++ ".1.gz", Name, rotate) of
+ T34 ->
+ ok;
+ E34 ->
+ test_server_fail({bad_terms, E34, T34})
+ end,
+ case get_list(File ++ ".0.gz", Name, rotate) of
+ T5 ->
+ ok;
+ E5 ->
+ test_server_fail({bad_terms, E5, T5})
+ end,
+ case get_list(File, Name) of
+ T6 ->
+ ok;
+ E6 ->
+ test_server_fail({bad_terms, E6, T6})
+ end,
+ ok = disk_log:close(Name),
+ del_rot_files(File, 3).
+
+%% test truncate/1 for rotate logs
+rotate_truncate(Conf) when is_list(Conf) ->
+ Dir = ?privdir(Conf),
+ File = filename:join(Dir, "a.LOG"),
+ Name = a,
+ {ok, Name} = disk_log:open([{name,Name}, {type,rotate}, {size,{100, 3}},
+ {format,external},
+ {file, File}]),
+ B = mk_bytes(60),
+ ok = disk_log:blog_terms(Name, [B, B, B]),
+ B = get_list(File, Name),
+ B = get_list(File ++ ".0.gz", Name, rotate),
+ B = get_list(File ++ ".1.gz", Name, rotate),
+ ok = disk_log:truncate(Name),
+ [] = get_list(File, Name),
+ {error, enoent} = file:read_file_info(File ++ ".0.gz"),
+ {error, enoent} = file:read_file_info(File ++ ".1.gz"),
+ ok = disk_log:close(Name),
+ file:delete(File).
+
+%% test reopen/2 for rotate logs
+rotate_reopen(Conf) when is_list(Conf) ->
+ Dir = ?privdir(Conf),
+ File = filename:join(Dir, "a.LOG"),
+ Name = a,
+ {ok, Name} = disk_log:open([{name,Name}, {type,rotate}, {size,{100, 3}},
+ {format,external},
+ {file, File}]),
+ B = mk_bytes(60),
+ ok = disk_log:blog_terms(Name, [B, B, B]),
+ B = get_list(File, Name),
+ B = get_list(File ++ ".0.gz", Name, rotate),
+ B = get_list(File ++ ".1.gz", Name, rotate),
+ File1 = filename:join(Dir, "b.LOG"),
+ ok = disk_log:reopen(Name, File1),
+ [] = get_list(File, Name),
+ {error, enoent} = file:read_file_info(File ++ ".0.gz"),
+ {error, enoent} = file:read_file_info(File ++ ".1.gz"),
+ B = get_list(File1 ++ ".0.gz", Name, rotate),
+ B = get_list(File1 ++ ".1.gz", Name, rotate),
+ B = get_list(File1 ++ ".2.gz", Name, rotate),
+ ok = disk_log:close(Name),
+ file:delete(File),
+ del_rot_files(File1, 3).
+
+%% test breopen/3 for rotate logs
+rotate_breopen(Conf) when is_list(Conf) ->
+ Dir = ?privdir(Conf),
+ File1 = filename:join(Dir, "a.LOG"),
+ Name = a,
+ Head1 = "thisishead1",
+ {ok, Name} = disk_log:open([{name,Name}, {type,rotate}, {size,{100, 3}},
+ {format,external},
+ {head, Head1},
+ {file, File1}]),
+ B = mk_bytes(60),
+ ok = disk_log:blog_terms(Name, [B, B, B]),
+ FileCont = Head1 ++ B,
+ FileCont = get_list(File1, Name),
+ FileCont = get_list(File1 ++ ".0.gz", Name, rotate),
+ FileCont = get_list(File1 ++ ".1.gz", Name, rotate),
+ File2 = filename:join(Dir, "b.LOG"),
+ Head2 = "thisishead2",
+ ok = disk_log:breopen(Name, File2, Head2),
+ Head2 = get_list(File1, Name),
+ {error, enoent} = file:read_file_info(File1 ++ ".0.gz"),
+ {error, enoent} = file:read_file_info(File1 ++ ".1.gz"),
+ FileCont = get_list(File2 ++ ".0.gz", Name, rotate),
+ FileCont = get_list(File2 ++ ".1.gz", Name, rotate),
+ FileCont = get_list(File2 ++ ".2.gz", Name, rotate),
+ ok = disk_log:close(Name),
+ file:delete(File1),
+ del_rot_files(File2, 3).
+
+%% Test rotate log, force a change to next file.
+next_rotate_file(Conf) when is_list(Conf) ->
+ Dir = ?privdir(Conf),
+ File1 = filename:join(Dir, "a.LOG"),
+ File2 = filename:join(Dir, "b.LOG"),
+
+ %% Test that halt and wrap logs get error messages
+ {ok, a} = disk_log:open([{name, a}, {type, halt},
+ {format, internal},
+ {file, File1}]),
+ ok = disk_log:log(a, "message one"),
+ {error, {halt_log, a}} = disk_log:next_file(a),
+
+ %% test a rotate log file
+ {ok, b} = disk_log:open([{name, b}, {type, rotate}, {size, {100,3}},
+ {format,external},
+ {file, File2}]),
+ ok = disk_log:blog(b, "message one"),
+ ok = disk_log:next_file(b),
+ ok = disk_log:blog(b, "message two"),
+ ok = disk_log:next_file(b),
+ ok = disk_log:blog(b, "message three"),
+ ok = disk_log:next_file(b),
+ ok = disk_log:blog(b, "message four"),
+ ok = disk_log:sync(b),
+ "message one" = get_list(File2 ++ ".2.gz", b, rotate),
+ "message two" = get_list(File2 ++ ".1.gz", b, rotate),
+ "message three" = get_list(File2 ++ ".0.gz", b, rotate),
+ "message four" = get_list(File2, b),
+ ok = disk_log:close(a),
+ ok = disk_log:close(b),
+ ok = file:delete(File1),
+ del_rot_files(File2, 3).
+
simple_log(Log) ->
T1 = "hej",
T2 = hopp,
@@ -893,6 +1061,37 @@ get_list(File, Log) ->
{ok, B} = file:read_file(File),
binary_to_list(B).
+get_list(File, Log, rotate) ->
+ ct:pal(?HI_VERBOSITY, "File ~p~n", [File]),
+ ok = disk_log:sync(Log),
+ DFile = filename:rootname(File,".gz"),
+ decompress_file(File, DFile),
+ {ok, B} = file:read_file(DFile),
+ file:delete(DFile),
+ binary_to_list(B).
+
+decompress_file(FileName, DFileName) ->
+ {ok,In} = file:open(FileName,[read,binary]),
+ {ok,Out} = file:open(DFileName,[write]),
+ Z = zlib:open(),
+ zlib:inflateInit(Z, 31),
+ decompress_data(Z,In,Out),
+ zlib:inflateEnd(Z),
+ zlib:close(Z),
+ _ = file:close(In),
+ _ = file:close(Out),
+ ok.
+
+decompress_data(Z,In,Out) ->
+ case file:read(In,1000) of
+ {ok,Data} ->
+ Decompressed = zlib:inflate(Z, Data),
+ _ = file:write(Out,Decompressed),
+ decompress_data(Z,In,Out);
+ eof ->
+ ok
+ end.
+
get_all_terms(Log, File, Type) ->
{ok, _Log} = disk_log:open([{name,Log}, {type,Type}, {size,infinity},
@@ -969,6 +1168,13 @@ del(File, N) ->
file:delete(File ++ "." ++ integer_to_list(N)),
del(File, N-1).
+del_rot_files(File, 0) ->
+ file:delete(File ++ ".0.gz"),
+ file:delete(File);
+del_rot_files(File, N) ->
+ file:delete(File ++ "." ++ integer_to_list(N) ++ ".gz"),
+ del_rot_files(File, N-1).
+
test_server_fail(R) ->
exit({?MODULE, get(line), R}).
@@ -2568,16 +2774,16 @@ error_repair(Conf) when is_list(Conf) ->
%% repair a file
P1 = pps(),
{ok, n} = disk_log:open([{name, n}, {file, File}, {type, wrap},
- {format, internal}, {size, {40,No}}]),
+ {format, internal}, {size, {38,No}}]),
ok = disk_log:log_terms(n, [{this,is}]), % first file full
ok = disk_log:log_terms(n, [{some,terms}]), % second file full
ok = disk_log:close(n),
BadFile = add_ext(File, 2), % current file
set_opened(BadFile),
- crash(BadFile, 28), % the binary is now invalid
- {repaired,n,{recovered,0},{badbytes,26}} =
+ crash(BadFile, 26), % the binary is now invalid
+ {repaired,n,{recovered,0},{badbytes,24}} =
disk_log:open([{name, n}, {file, File}, {type, wrap},
- {format, internal}, {size, {40,No}}]),
+ {format, internal}, {size, {38,No}}]),
ok = disk_log:close(n),
check_pps(P1),
del(File, No),
@@ -2592,8 +2798,8 @@ error_repair(Conf) when is_list(Conf) ->
ok = disk_log:close(n),
BadFile2 = add_ext(File, 1), % current file
set_opened(BadFile2),
- crash(BadFile2, 51), % the second binary is now invalid
- {repaired,n,{recovered,1},{badbytes,26}} =
+ crash(BadFile2, 47), % the second binary is now invalid
+ {repaired,n,{recovered,1},{badbytes,24}} =
disk_log:open([{name, n}, {file, File}, {type, wrap},
{format, internal}, {size, {4000,No}}]),
ok = disk_log:close(n),
@@ -2651,7 +2857,7 @@ error_repair(Conf) when is_list(Conf) ->
ok = disk_log:close(n),
set_opened(File),
crash(File, 30),
- {repaired,n,{recovered,3},{badbytes,16}} =
+ {repaired,n,{recovered,3},{badbytes,15}} =
disk_log:open([{name, n}, {file, File}, {type, halt},
{format, internal},{repair,true}, {quiet, true},
{head_func, {?MODULE, head_fun, [{ok,"head"}]}}]),
@@ -2873,12 +3079,12 @@ chunk(Conf) when is_list(Conf) ->
%% Two wrap log files, writing the second one, then reading the first
%% one, where a bogus term resides.
{ok, n} = disk_log:open([{name, n}, {file, File}, {type, wrap},
- {format, internal}, {size, {40,No}}]),
+ {format, internal}, {size, {38,No}}]),
ok = disk_log:log_terms(n, [{this,is}]), % first file full
ok = disk_log:log_terms(n, [{some,terms}]), % second file full
2 = curf(n),
BadFile = add_ext(File, 1),
- crash(BadFile, 28), % the _binary_ is now invalid
+ crash(BadFile, 26), % the _binary_ is now invalid
{error, {corrupt_log_file, BFile}} = disk_log:chunk(n, start, 1),
BadFile = BFile,
ok = disk_log:close(n),
@@ -2888,7 +3094,7 @@ chunk(Conf) when is_list(Conf) ->
{format, internal}]),
ok = disk_log:log_terms(n, [{this,is}]),
ok = disk_log:sync(n),
- crash(File, 28), % the _binary_ is now invalid
+ crash(File, 26), % the _binary_ is now invalid
{error, {corrupt_log_file, File2}} = disk_log:chunk(n, start, 1),
crash(File, 10),
{error,{corrupt_log_file,_}} = disk_log:bchunk(n, start, 1),
@@ -2982,8 +3188,8 @@ chunk(Conf) when is_list(Conf) ->
{ok, n} = disk_log:open([{name, n}, {file, File}, {type, wrap},
{format, internal}, {mode, read_only}]),
CrashFile = add_ext(File, 1),
- crash(CrashFile, 51), % the binary term {some,terms} is now bad
- {H1, [{this,is}], 18} = disk_log:chunk(n, start, 10),
+ crash(CrashFile, 47), % the binary term {some,terms} is now bad
+ {H1, [{this,is}], 16} = disk_log:chunk(n, start, 10),
{H2, [{on,a},{wrap,file}]} = disk_log:chunk(n, H1),
eof = disk_log:chunk(n, H2),
ok = disk_log:close(n),
@@ -2997,8 +3203,8 @@ chunk(Conf) when is_list(Conf) ->
ok = disk_log:close(n),
{ok, n} = disk_log:open([{name, n}, {file, File}, {type, halt},
{format, internal}, {mode, read_only}]),
- crash(File, 51), % the binary term {some,terms} is now bad
- {J1, [{this,is}], 18} = disk_log:chunk(n, start, 10),
+ crash(File, 47), % the binary term {some,terms} is now bad
+ {J1, [{this,is}], 16} = disk_log:chunk(n, start, 10),
{J2, [{on,a},{halt,file}]} = disk_log:chunk(n, J1),
eof = disk_log:chunk(n, J2),
ok = disk_log:close(n),
@@ -3013,8 +3219,8 @@ chunk(Conf) when is_list(Conf) ->
ok = disk_log:close(n),
{ok, n} = disk_log:open([{name, n}, {file, File}, {type, halt},
{format, internal}, {mode, read_only}]),
- crash(File, 44), % the binary term {s} is now bad
- {J11, [{this,is}], 7} = disk_log:chunk(n, start, 10),
+ crash(File, 41), % the binary term {s} is now bad
+ {J11, [{this,is}], 6} = disk_log:chunk(n, start, 10),
{J21, [{on,a},{halt,file}]} = disk_log:chunk(n, J11),
eof = disk_log:chunk(n, J21),
ok = disk_log:close(n),
@@ -3133,7 +3339,7 @@ truncate(Conf) when is_list(Conf) ->
ok = disk_log:truncate(n, apa),
rec(1, {disk_log, node(), n, {truncated, 6}}),
{0, 0} = no_overflows(n),
- 23 = curb(n),
+ 22 = curb(n),
1 = curf(n),
1 = cur_cnt(n),
true = (Size == sz(n)),
@@ -3153,7 +3359,7 @@ truncate(Conf) when is_list(Conf) ->
ok = disk_log:truncate(n, apa),
rec(1, {disk_log, node(), n, {truncated, 3}}),
{0, 0} = no_overflows(n),
- 23 = curb(n),
+ 22 = curb(n),
1 = curf(n),
1 = cur_cnt(n),
true = (Size == sz(n)),
@@ -3262,45 +3468,45 @@ info_current(Conf) when is_list(Conf) ->
%% Internal with header.
{ok, n} = disk_log:open([{name, n}, {file, File}, {type, wrap},
{head, header}, {size, {100,No}}]),
- {26, 1} = {curb(n), cur_cnt(n)},
+ {25, 1} = {curb(n), cur_cnt(n)},
{1, 1} = {no_written_items(n), no_items(n)},
ok = disk_log:log(n, B),
- {94, 2} = {curb(n), cur_cnt(n)},
+ {93, 2} = {curb(n), cur_cnt(n)},
{2, 2} = {no_written_items(n), no_items(n)},
ok = disk_log:close(n),
{ok, n} = disk_log:open([{name, n}, {file, File}, {type, wrap},
{notify, true},
{head, header}, {size, {100,No}}]),
- {94, 2} = {curb(n), cur_cnt(n)},
+ {93, 2} = {curb(n), cur_cnt(n)},
{0, 2} = {no_written_items(n), no_items(n)},
ok = disk_log:log(n, B),
rec(1, {disk_log, node(), n, {wrap, 0}}),
- {94, 2} = {curb(n), cur_cnt(n)},
+ {93, 2} = {curb(n), cur_cnt(n)},
{2, 4} = {no_written_items(n), no_items(n)},
disk_log:inc_wrap_file(n),
rec(1, {disk_log, node(), n, {wrap, 0}}),
- {26, 1} = {curb(n), cur_cnt(n)},
+ {25, 1} = {curb(n), cur_cnt(n)},
{3, 4} = {no_written_items(n), no_items(n)},
ok = disk_log:log_terms(n, [B,B,B]),
%% Used to be one message, but now one per wrapped file.
rec(1, {disk_log, node(), n, {wrap, 0}}),
rec(1, {disk_log, node(), n, {wrap, 2}}),
- {94, 2} = {curb(n), cur_cnt(n)},
+ {93, 2} = {curb(n), cur_cnt(n)},
{8, 7} = {no_written_items(n), no_items(n)},
ok = disk_log:log_terms(n, [B]),
rec(1, {disk_log, node(), n, {wrap, 2}}),
ok = disk_log:log_terms(n, [B]),
rec(1, {disk_log, node(), n, {wrap, 2}}),
- {94, 2} = {curb(n), cur_cnt(n)},
+ {93, 2} = {curb(n), cur_cnt(n)},
{12, 7} = {no_written_items(n), no_items(n)},
ok = disk_log:log_terms(n, [BB,BB]),
%% Used to be one message, but now one per wrapped file.
rec(2, {disk_log, node(), n, {wrap, 2}}),
- {194, 2} = {curb(n), cur_cnt(n)},
+ {193, 2} = {curb(n), cur_cnt(n)},
{16, 7} = {no_written_items(n), no_items(n)},
ok = disk_log:log_terms(n, [SB,SB,SB]),
rec(1, {disk_log, node(), n, {wrap, 2}}),
- {80, 4} = {curb(n), cur_cnt(n)},
+ {79, 4} = {curb(n), cur_cnt(n)},
{20, 9} = {no_written_items(n), no_items(n)},
ok = disk_log:close(n),
del(File, No),
diff --git a/lib/kernel/test/erl_distribution_wb_SUITE.erl b/lib/kernel/test/erl_distribution_wb_SUITE.erl
index 6ca804b5bf..067209a34d 100644
--- a/lib/kernel/test/erl_distribution_wb_SUITE.erl
+++ b/lib/kernel/test/erl_distribution_wb_SUITE.erl
@@ -56,7 +56,9 @@
-define(DFLAG_MAP_TAG, 16#20000).
-define(DFLAG_BIG_CREATION, 16#40000).
-define(DFLAG_HANDSHAKE_23, 16#1000000).
+-define(DFLAG_UNLINK_ID, 16#2000000).
-define(DFLAG_MANDATORY_25_DIGEST, 16#4000000).
+-define(DFLAG_V4_NC, 16#400000000).
%% From OTP R9 extended references are compulsory.
%% From OTP R10 extended pids and ports are compulsory.
@@ -64,7 +66,8 @@
%% From OTP 21 NEW_FUN_TAGS is compulsory (no more tuple fallback {fun, ...}).
%% From OTP 23 BIG_CREATION is compulsory.
%% From OTP 25 HANDSHAKE_23, NEW_FLOATS, MAP_TAG, EXPORT_PTR_TAG, and BIT_BINARIES are compulsory.
--define(COMPULSORY_DFLAGS,
+
+-define(DFLAGS_MANDATORY_25,
(?DFLAG_EXTENDED_REFERENCES bor
?DFLAG_FUN_TAGS bor
?DFLAG_EXTENDED_PIDS_PORTS bor
@@ -75,7 +78,18 @@
?DFLAG_NEW_FLOATS bor
?DFLAG_MAP_TAG bor
?DFLAG_EXPORT_PTR_TAG bor
- ?DFLAG_BIT_BINARIES)).
+ ?DFLAG_BIT_BINARIES bor
+ ?DFLAG_HANDSHAKE_23)).
+
+%% From OTP 26 V4_NC and UNLINK_ID are compulsory.
+
+-define(DFLAGS_MANDATORY_26,
+ (?DFLAG_V4_NC bor
+ ?DFLAG_UNLINK_ID)).
+
+-define(COMPULSORY_DFLAGS,
+ (?DFLAGS_MANDATORY_25 bor
+ ?DFLAGS_MANDATORY_26)).
-define(PASS_THROUGH, $p).
@@ -429,7 +443,8 @@ missing_compulsory_dflags(Config) when is_list(Config) ->
end
|| Version <- lists:seq(?DIST_VER_LOW, ?DIST_VER_HIGH),
MissingFlags <- [?DFLAG_BIT_BINARIES,
- ?DFLAG_HANDSHAKE_23]],
+ ?DFLAG_HANDSHAKE_23,
+ ?DFLAG_V4_NC]],
peer:stop(Peer),
ok.
@@ -445,7 +460,8 @@ dflag_mandatory_25(_Config) ->
PortNo,
[{active,false},{packet,2}]),
OtherNode = list_to_atom(?CT_PEER_NAME()++"@"++atom_to_list(NB)),
- send_name(SocketA, OtherNode, ?DIST_VER_HIGH, ?DFLAG_MANDATORY_25_DIGEST),
+ send_name(SocketA, OtherNode, ?DIST_VER_HIGH,
+ ?DFLAG_MANDATORY_25_DIGEST bor ?DFLAGS_MANDATORY_26),
ok = recv_status(SocketA),
gen_tcp:close(SocketA),
peer:stop(Peer),
diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl
index b791a577f0..42ff6517a2 100644
--- a/lib/kernel/test/file_SUITE.erl
+++ b/lib/kernel/test/file_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -3451,9 +3451,9 @@ pid2name(Config) when is_list(Config) ->
%%
{ok, Pid} = file:open(Name1, [write]),
{ok, Name2} = file:pid2name(Pid),
- undefined = file:pid2name(self()),
+ Dead = spawn(fun() -> ok end),
+ undefined = file:pid2name(Dead),
ok = file:close(Pid),
- ct:sleep(1000),
false = is_process_alive(Pid),
undefined = file:pid2name(Pid),
ok.
diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl
index 297c824965..7b0dbb0d2e 100644
--- a/lib/kernel/test/gen_sctp_SUITE.erl
+++ b/lib/kernel/test/gen_sctp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
-%%
+%%
-module(gen_sctp_SUITE).
-include_lib("common_test/include/ct.hrl").
@@ -770,6 +770,15 @@ api_listen(Config) when is_list(Config) ->
{ok,Sb} = gen_sctp:open(Pb),
{ok,Sa} = gen_sctp:open(),
+
+ element(1, os:type()) =:= unix andalso
+ begin
+ {error, nxdomain} = gen_sctp:connect(Sa, "", 65535, []),
+ {error, nxdomain} = gen_sctp:connect(Sa, '', 65535, [])
+ end,
+ {error, nxdomain} = gen_sctp:connect(Sa, ".", 65535, []),
+ {error, nxdomain} = gen_sctp:connect(Sa, '.', 65535, []),
+
case gen_sctp:connect(Sa, localhost, Pb, []) of
{error,econnrefused} ->
{ok,{Localhost,
diff --git a/lib/kernel/test/gen_tcp_api_SUITE.erl b/lib/kernel/test/gen_tcp_api_SUITE.erl
index 593a784bcc..c2ab0c3df6 100644
--- a/lib/kernel/test/gen_tcp_api_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_api_SUITE.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1998-2022. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
+%%
%% 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
@@ -14,7 +14,7 @@
%% 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.
-%%
+%%
%% %CopyrightEnd%
%%
-module(gen_tcp_api_SUITE).
@@ -353,15 +353,22 @@ t_connect_src_port(Config) when is_list(Config) ->
%% invalid things.
t_connect_bad(Config) when is_list(Config) ->
NonExistingPort = 45638, % Not in use, I hope.
- {error, Reason1} = gen_tcp:connect(localhost, NonExistingPort,
- ?INET_BACKEND_OPTS(Config)),
- io:format("Error for connection attempt to port not in use: ~p",
- [Reason1]),
-
- {error, Reason2} = gen_tcp:connect("non-existing-host-xxx", 7,
- ?INET_BACKEND_OPTS(Config)),
- io:format("Error for connection attempt to non-existing host: ~p",
- [Reason2]),
+ t_connect_bad(Config, localhost, NonExistingPort, "port not in use"),
+ t_connect_bad(Config, "non-existing-host-xxx", 7, "non-existing host"),
+ element(1, os:type()) =:= unix andalso
+ begin
+ t_connect_bad(Config, "", 7, "empty host string"),
+ t_connect_bad(Config, '', 7, "empty host atom")
+ end,
+ t_connect_bad(Config, ".", 7, "root domain string"),
+ t_connect_bad(Config, '.', 7, "root domain atom").
+
+t_connect_bad(Config, Host, Port, Descr) ->
+ {error, Reason} =
+ gen_tcp:connect(Host, Port,?INET_BACKEND_OPTS(Config)),
+ io:format(
+ "Error for connection attempt to " ++ Descr ++ ": ~p~n",
+ [Reason]),
ok.
@@ -1352,7 +1359,9 @@ do_simple_sockaddr_send_recv(SockAddr, _) ->
%% we have to skip on that platform.
s_accept_with_explicit_socket_backend(Config) when is_list(Config) ->
?TC_TRY(s_accept_with_explicit_socket_backend,
- fun() -> is_socket_supported() end,
+ fun() ->
+ is_socket_supported()
+ end,
fun() -> do_s_accept_with_explicit_socket_backend() end).
do_s_accept_with_explicit_socket_backend() ->
@@ -1385,8 +1394,11 @@ do_s_accept_with_explicit_socket_backend() ->
is_socket_supported() ->
try socket:info() of
- _ ->
- ok
+ #{io_backend := #{name := BackendName}}
+ when (BackendName =/= win_esaio) ->
+ ok;
+ _ ->
+ {skip, "Temporary exclusion"}
catch
error : notsup ->
{skip, "esock not supported"};
diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl
index 4419999036..4aefee8a79 100644
--- a/lib/kernel/test/gen_udp_SUITE.erl
+++ b/lib/kernel/test/gen_udp_SUITE.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1998-2022. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
+%%
%% 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
@@ -14,7 +14,7 @@
%% 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.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -37,7 +37,7 @@
init_per_testcase/2, end_per_testcase/2]).
-export([
- send_to_closed/1, active_n/1,
+ send_to_closed/1, send_to_empty/1, active_n/1,
buffer_size/1, binary_passive_recv/1, max_buffer_size/1, bad_address/1,
read_packets/1, recv_poll_after_active_once/1,
open_fd/1, connect/1, reconnect/1, implicit_inet6/1,
@@ -118,6 +118,7 @@ inet_backend_socket_cases() ->
all_cases() ->
[
send_to_closed,
+ send_to_empty,
buffer_size,
binary_passive_recv,
max_buffer_size,
@@ -340,6 +341,23 @@ do_send_to_closed(Config) ->
%%-------------------------------------------------------------
+%% Send to the empty host name
+
+send_to_empty(Config) when is_list(Config) ->
+ ?TC_TRY(?FUNCTION_NAME, fun() -> do_send_to_empty(Config) end).
+
+do_send_to_empty(Config) ->
+ {ok, Sock} = ?OPEN(Config, 0),
+ element(1, os:type()) =:= unix andalso
+ begin
+ {error, nxdomain} = gen_udp:send(Sock, "", ?CLOSED_PORT, "xXx"),
+ {error, nxdomain} = gen_udp:send(Sock, '', ?CLOSED_PORT, "xXx")
+ end,
+ {error, nxdomain} = gen_udp:send(Sock, ".", ?CLOSED_PORT, "xXx"),
+ {error, nxdomain} = gen_udp:send(Sock, '.', ?CLOSED_PORT, "xXx"),
+ ok.
+
+%%-------------------------------------------------------------
%% Test that the UDP socket buffer sizes are settable
%% Test UDP buffer size setting.
@@ -410,7 +428,7 @@ buffer_size_client(Server, IP, Port,
Socket, Cnt, [{B,Replies}|T]=Opts) when is_binary(B) ->
?P("buffer_size_client -> Cnt=~w send size ~w expecting ~p when"
"~n Info: ~p",
- [Cnt, size(B), Replies, inet:info(Socket)]),
+ [Cnt, byte_size(B), Replies, inet:info(Socket)]),
case gen_udp:send(Socket, IP, Port, <<Cnt,B/binary>>) of
ok ->
receive
@@ -446,7 +464,7 @@ buffer_size_client(Server, IP, Port,
?P("<ERROR> Client failed sending ~w bytes of data: "
"~n SndBuf: ~p"
"~n Reason: ~p",
- [size(B), inet:getopts(Socket, [sndbuf]), Reason]),
+ [byte_size(B), inet:getopts(Socket, [sndbuf]), Reason]),
ct:fail(Reason)
end.
@@ -465,7 +483,7 @@ buffer_size_server(Client, IP, Port,
buffer_size_server(Client, IP, Port,
Socket, Cnt, [{B,_}|T]) when is_binary(B) ->
?P("buffer_size_server -> try receive: Cnt=~w and ~w bytes of data",
- [Cnt, size(B)]),
+ [Cnt, byte_size(B)]),
Reply = case buffer_size_server_recv(Socket, IP, Port, Cnt) of
D when is_binary(D) ->
SizeD = byte_size(D),
@@ -496,11 +514,11 @@ buffer_size_server_recv(Socket, IP, Port, Cnt) ->
"~n Cnt: ~p", [Socket, IP, Port, Cnt]),
receive
{udp, Socket, IP, Port, <<Cnt, B/binary>>} ->
- ?P("buffer_size_server -> received (~w) ~w bytes", [Cnt, size(B)]),
+ ?P("buffer_size_server -> received (~w) ~w bytes", [Cnt, byte_size(B)]),
B;
{udp, Socket, IP, Port, <<_B/binary>>} ->
?P("buffer_size_server -> received unexpected ~w bytes",
- [size(_B)]),
+ [byte_size(_B)]),
buffer_size_server_recv(Socket, IP, Port, Cnt);
{udp, Socket, IP, Port, _CRAP} ->
@@ -1744,6 +1762,11 @@ do_connect(Config) when is_list(Config) ->
?P("sleep some"),
ct:sleep({seconds, 5}),
+ ?P("try some doomed connect targets: ~p", [P1]),
+ {error, nxdomain} = gen_udp:connect(S2, "", ?CLOSED_PORT),
+ {error, nxdomain} = gen_udp:connect(S2, '', ?CLOSED_PORT),
+ {error, nxdomain} = gen_udp:connect(S2, ".", ?CLOSED_PORT),
+ {error, nxdomain} = gen_udp:connect(S2, '.', ?CLOSED_PORT),
?P("try connect second socket to: ~p, ~p", [Addr, P1]),
ok = gen_udp:connect(S2, Addr, P1),
?P("try send on second socket"),
@@ -2616,6 +2639,10 @@ do_simple_sockaddr_send_recv(#{family := _Fam} = SockAddr, _) ->
?P("[server] send failed: ~p",
[Reason1]),
exit({skip, Reason1});
+ {error, enetunreach = Reason1} ->
+ ?P("[server] send failed: ~p",
+ [Reason1]),
+ exit({skip, Reason1});
{error, Reason1} ->
exit({send_failed, Reason1})
end
@@ -2641,6 +2668,10 @@ do_simple_sockaddr_send_recv(#{family := _Fam} = SockAddr, _) ->
?P("[server] send failed: ~p",
[Reason2]),
exit({skip, Reason2});
+ {error, enetunreach = Reason2} ->
+ ?P("[server] send failed: ~p",
+ [Reason2]),
+ exit({skip, Reason2});
{error, Reason2} ->
exit({send_failed, Reason2})
end
diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl
index 6298130a09..9061b67be1 100644
--- a/lib/kernel/test/inet_SUITE.erl
+++ b/lib/kernel/test/inet_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@
t_gethostbyaddr/0, t_gethostbyaddr/1,
t_getaddr/0, t_getaddr/1,
- t_gethostbyname/0, t_gethostbyname/1,
+ t_gethostbyname/0, t_gethostbyname/1, t_gethostbyname_empty/1,
t_gethostbyaddr_v6/0, t_gethostbyaddr_v6/1,
t_getaddr_v6/0, t_getaddr_v6/1,
t_gethostbyname_v6/0, t_gethostbyname_v6/1,
@@ -70,7 +70,7 @@ suite() ->
all() ->
[
- t_gethostbyaddr, t_gethostbyname, t_getaddr,
+ t_gethostbyaddr, t_gethostbyname, t_gethostbyname_empty, t_getaddr,
t_gethostbyaddr_v6, t_gethostbyname_v6, t_getaddr_v6,
ipv4_to_ipv6, host_and_addr, is_ip_address, {group, parse},
t_gethostnative, gethostnative_parallell, cname_loop,
@@ -367,6 +367,18 @@ do_gethostbyname(Config) when is_list(Config) ->
{error,nxdomain} = inet:gethostbyname(IP_46_Str),
ok.
+
+t_gethostbyname_empty(Config) when is_list(Config) ->
+ element(1, os:type()) =:= unix andalso
+ begin
+ {error,nxdomain} = inet:gethostbyname(""),
+ {error,nxdomain} = inet:gethostbyname('')
+ end,
+ {error,nxdomain} = inet:gethostbyname("."),
+ {error,nxdomain} = inet:gethostbyname('.'),
+ ok.
+
+
t_gethostbyname_v6() -> required(v6).
%% Test the inet:gethostbyname/1 inet6 function.
t_gethostbyname_v6(Config) when is_list(Config) ->
diff --git a/lib/kernel/test/inet_res_SUITE.erl b/lib/kernel/test/inet_res_SUITE.erl
index 5adce7156e..ac147551f2 100644
--- a/lib/kernel/test/inet_res_SUITE.erl
+++ b/lib/kernel/test/inet_res_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -886,6 +886,9 @@ resolve(Config) when is_list(Config) ->
{ptr,"c.1.0.0.0.0.f.7."++RDomain6,[{ptr,Name}],undefined},
{hinfo,Name,[{hinfo,{"BEAM","Erlang/OTP"}}],undefined},
{mx,RDomain4,[{mx,{10,"mx."++Domain}}],undefined},
+ {loc,"loc."++Name,
+ [{loc,{{42.0625,13.125},17.0,100.0,{10000.0,10.0}}}],
+ undefined},
{srv,"_srv._tcp."++Name,[{srv,{10,3,4711,Name}}],undefined},
{naptr,"naptr."++Name,
[{naptr,{10,5,"s","http","","_srv._tcp."++Name}}],
diff --git a/lib/kernel/test/inet_res_SUITE_data/otptest/otptest.zone b/lib/kernel/test/inet_res_SUITE_data/otptest/otptest.zone
index 9e4a3513f8..98b195fd97 100644
--- a/lib/kernel/test/inet_res_SUITE_data/otptest/otptest.zone
+++ b/lib/kernel/test/inet_res_SUITE_data/otptest/otptest.zone
@@ -42,6 +42,9 @@ wks.resolve IN WKS 127.0.0.28 TCP ( telnet smtp )
resolve IN HINFO "BEAM" "Erlang/OTP"
ns.resolve IN NS resolve
mx.resolve IN MX 10 resolve
+;; The LOC latitude and longitude is chosen to have an exact
+;; decimal degrees floating point representation
+loc.resolve IN LOC 42 3 45 N 13 7 30 E 17m 100m 10000m 10m
_srv._tcp.resolve IN SRV 10 3 4711 resolve
naptr.resolve IN NAPTR 10 5 "S" "HTTP" "" _srv._tcp.resolve
txt.resolve IN TXT "Hej " "du " "glade "
diff --git a/lib/kernel/test/inet_sockopt_SUITE.erl b/lib/kernel/test/inet_sockopt_SUITE.erl
index ff24b2f0c3..5c95781066 100644
--- a/lib/kernel/test/inet_sockopt_SUITE.erl
+++ b/lib/kernel/test/inet_sockopt_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
-define(C_GET_SO_REUSEADDR,14).
-define(C_GET_SO_KEEPALIVE,15).
-define(C_GET_SO_LINGER,16).
+-define(C_GET_SO_DONTROUTE,17).
-define(C_GET_LINGER_SIZE,21).
-define(C_GET_TCP_INFO_SIZE,22).
@@ -58,7 +59,7 @@
large_raw/1,large_raw_getbin/1,combined/1,combined_getbin/1,
ipv6_v6only_udp/1, ipv6_v6only_tcp/1, ipv6_v6only_sctp/1,
use_ipv6_v6only_udp/1,
- type_errors/1]).
+ type_errors/1, windows_reuseaddr/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
@@ -74,7 +75,7 @@ all() ->
large_raw_getbin, combined, combined_getbin,
ipv6_v6only_udp, ipv6_v6only_tcp, ipv6_v6only_sctp,
use_ipv6_v6only_udp,
- type_errors].
+ type_errors, windows_reuseaddr].
groups() ->
[].
@@ -127,10 +128,12 @@ simple(Config) when is_list(Config) ->
%% Loop through all socket options and check that they work.
loop_all(Config) when is_list(Config) ->
ListenFailures =
- lists:foldr(make_check_fun(listen,1),[],all_listen_options()),
+ lists:foldr(make_check_fun(listen),[],all_listen_options()),
+ AcceptFailures =
+ lists:foldr(make_check_fun(accept),[],all_accept_options()),
ConnectFailures =
- lists:foldr(make_check_fun(connect,2),[],all_connect_options()),
- case ListenFailures++ConnectFailures of
+ lists:foldr(make_check_fun(connect),[],all_connect_options()),
+ case ListenFailures++AcceptFailures++ConnectFailures of
[] ->
ok;
Failed ->
@@ -206,56 +209,56 @@ do_multiple_raw(Config, Binary) ->
SoKeepalive = ask_helper(Port, ?C_GET_SO_KEEPALIVE),
SoKeepaliveTrue = {raw,SolSocket,SoKeepalive,<<1:32/native>>},
SoKeepaliveFalse = {raw,SolSocket,SoKeepalive,<<0:32/native>>},
- SoReuseaddr = ask_helper(Port, ?C_GET_SO_REUSEADDR),
- SoReuseaddrTrue = {raw,SolSocket,SoReuseaddr,<<1:32/native>>},
- SoReuseaddrFalse = {raw,SolSocket,SoReuseaddr,<<0:32/native>>},
+ SoDontroute = ask_helper(Port, ?C_GET_SO_DONTROUTE),
+ SoDontrouteTrue = {raw,SolSocket,SoDontroute,<<1:32/native>>},
+ SoDontrouteFalse = {raw,SolSocket,SoDontroute,<<0:32/native>>},
{S1,S2} =
create_socketpair(
- [SoReuseaddrFalse,SoKeepaliveTrue],
- [SoKeepaliveFalse,SoReuseaddrTrue]),
- {ok,[{reuseaddr,false},{keepalive,true}]} =
- inet:getopts(S1, [reuseaddr,keepalive]),
+ [SoDontrouteFalse,SoKeepaliveTrue],
+ [SoKeepaliveFalse,SoDontrouteTrue]),
+ {ok,[{dontroute,false},{keepalive,true}]} =
+ inet:getopts(S1, [dontroute,keepalive]),
{ok,
- [{raw,SolSocket,SoReuseaddr,S1R1},
+ [{raw,SolSocket,SoDontroute,S1R1},
{raw,SolSocket,SoKeepalive,S1K1}]} =
inet:getopts(
S1,
- [{raw,SolSocket,SoReuseaddr,binarify(4, Binary)},
+ [{raw,SolSocket,SoDontroute,binarify(4, Binary)},
{raw,SolSocket,SoKeepalive,binarify(4, Binary)}]),
true = nintbin2int(S1R1) =:= 0,
true = nintbin2int(S1K1) =/= 0,
- {ok,[{keepalive,false},{reuseaddr,true}]} =
- inet:getopts(S2, [keepalive,reuseaddr]),
+ {ok,[{keepalive,false},{dontroute,true}]} =
+ inet:getopts(S2, [keepalive,dontroute]),
{ok,
[{raw,SolSocket,SoKeepalive,S2K1},
- {raw,SolSocket,SoReuseaddr,S2R1}]} =
+ {raw,SolSocket,SoDontroute,S2R1}]} =
inet:getopts(
S2,
[{raw,SolSocket,SoKeepalive,binarify(4, Binary)},
- {raw,SolSocket,SoReuseaddr,binarify(4, Binary)}]),
+ {raw,SolSocket,SoDontroute,binarify(4, Binary)}]),
true = nintbin2int(S2K1) =:= 0,
true = nintbin2int(S2R1) =/= 0,
%%
ok = inet:setopts(
- S1, [SoReuseaddrTrue,SoKeepaliveFalse]),
+ S1, [SoDontrouteTrue,SoKeepaliveFalse]),
ok = inet:setopts(
- S2, [SoKeepaliveTrue,SoReuseaddrFalse]),
+ S2, [SoKeepaliveTrue,SoDontrouteFalse]),
{ok,
- [{raw,SolSocket,SoReuseaddr,S1R2},
+ [{raw,SolSocket,SoDontroute,S1R2},
{raw,SolSocket,SoKeepalive,S1K2}]} =
inet:getopts(
S1,
- [{raw,SolSocket,SoReuseaddr,binarify(4, Binary)},
+ [{raw,SolSocket,SoDontroute,binarify(4, Binary)},
{raw,SolSocket,SoKeepalive,binarify(4, Binary)}]),
true = nintbin2int(S1R2) =/= 0,
true = nintbin2int(S1K2) =:= 0,
{ok,
[{raw,SolSocket,SoKeepalive,S2K2},
- {raw,SolSocket,SoReuseaddr,S2R2}]} =
+ {raw,SolSocket,SoDontroute,S2R2}]} =
inet:getopts(
S2,
[{raw,SolSocket,SoKeepalive,binarify(4, Binary)},
- {raw,SolSocket,SoReuseaddr,binarify(4, Binary)}]),
+ {raw,SolSocket,SoDontroute,binarify(4, Binary)}]),
true = nintbin2int(S2K2) =/= 0,
true = nintbin2int(S2R2) =:= 0,
%%
@@ -802,6 +805,57 @@ type_errors(Config) when is_list(Config) ->
gen_tcp:close(Sock2),
ok.
+windows_reuseaddr(Config) when is_list(Config) ->
+ %% Check that emulation of reuseaddr and reuseport on Windows
+ %% works as expected. That is, only set SO_REUSEADDR if both
+ %% reuseaddr and reuseport are set.
+ case os:type() of
+ {win32, _} ->
+ Port = start_helper(Config),
+ Def = {ask_helper(Port,?C_GET_SOL_SOCKET),
+ ask_helper(Port,?C_GET_SO_REUSEADDR)},
+ stop_helper(Port),
+ {false, false} = windows_reuseaddr_test(Def,
+ [{reuseaddr,false},{reuseport,false}],
+ [{reuseaddr,false},{reuseport,false}]),
+ {false, false} = windows_reuseaddr_test(Def,
+ [{reuseaddr,true},{reuseport,false}],
+ [{reuseaddr,true},{reuseport,false}]),
+ {false, false} = windows_reuseaddr_test(Def,
+ [{reuseaddr,false},{reuseport,true}],
+ [{reuseaddr,false},{reuseport,true}]),
+ {true, true} = windows_reuseaddr_test(Def,
+ [{reuseaddr,true},{reuseport,true}],
+ [{reuseaddr,true},{reuseport,true}]),
+ ok;
+ _ ->
+ {skipped, "Test for Windows only"}
+ end.
+
+windows_reuseaddr_test({SolSocket, SoReuseaddr}, LOpts, COpts) ->
+ OptNames = fun (Opts) ->
+ lists:map(fun ({Name,_}) -> Name end, Opts)
+ end,
+ {L,A,C} = create_socketpair_init(LOpts, COpts),
+ {ok, LOpts} = inet:getopts(L, OptNames(LOpts)),
+ {ok, COpts} = inet:getopts(L, OptNames(COpts)),
+ RawOpts = [{raw, SolSocket, SoReuseaddr, 4}],
+ {ok,[{raw,SolSocket,SoReuseaddr,LRes}]} = inet:getopts(L, RawOpts),
+ LReuseaddr = case nintbin2int(LRes) of
+ 0 -> false;
+ _ -> true
+ end,
+ {ok,[{raw,SolSocket,SoReuseaddr,CRes}]} = inet:getopts(L, RawOpts),
+ CReuseaddr = case nintbin2int(CRes) of
+ 0 -> false;
+ _ -> true
+ end,
+ gen_tcp:close(L),
+ gen_tcp:close(A),
+ gen_tcp:close(C),
+ {LReuseaddr, CReuseaddr}.
+
+
all_ok([]) ->
true;
all_ok([H|T]) when H >= 0 ->
@@ -810,14 +864,23 @@ all_ok(_) ->
false.
-make_check_fun(Type,Element) ->
+make_check_fun(Type) ->
fun({Name,V1,V2,Mand,Chang},Acc) ->
- {LO1,CO1} = setelement(Element,{[],[]}, [{Name,V1}]),
- {LO2,CO2} = setelement(Element,{[],[]}, [{Name,V2}]),
- {X1,Y1} = create_socketpair(LO1,CO1),
- {X2,Y2} = create_socketpair(LO2,CO2),
- S1 = element(Element,{X1,Y1}),
- S2 = element(Element,{X2,Y2}),
+ {LO1,CO1} = case Type of
+ connect -> {[],[{Name,V1}]};
+ _ -> {[{Name,V1}],[]}
+ end,
+ {LO2,CO2} = case Type of
+ connect -> {[],[{Name,V2}]};
+ _ -> {[{Name,V2}],[]}
+ end,
+ {X1,Y1,Z1} = create_socketpair_init(LO1,CO1),
+ {X2,Y2,Z2} = create_socketpair_init(LO2,CO2),
+ {S1,S2} = case Type of
+ listen -> {X1,X2};
+ accept -> {Y1,Y2};
+ connect -> {Z1,Z2}
+ end,
{ok,[{Name,R1}]} = inet:getopts(S1,[Name]),
{ok,[{Name,R2}]} = inet:getopts(S2,[Name]),
NewAcc =
@@ -856,16 +919,23 @@ make_check_fun(Type,Element) ->
end,
gen_tcp:close(X1),
gen_tcp:close(Y1),
+ gen_tcp:close(Z1),
gen_tcp:close(X2),
gen_tcp:close(Y2),
+ gen_tcp:close(Z2),
NewAcc
end.
%% {OptionName,Value1,Value2,Mandatory,Changeable}
all_listen_options() ->
+ OsType = os:type(),
+ OsVersion = os:version(),
[{tos,0,1,false,true},
{priority,0,1,false,true},
- {reuseaddr,false,true,false,true},
+ {reuseaddr,false,true,mandatory_reuseaddr(OsType,OsVersion),false},
+ {reuseport,false,true,mandatory_reuseport(OsType,OsVersion),false},
+ {reuseport_lb,false,true,mandatory_reuseport_lb(OsType,OsVersion),false},
+ {exclusiveaddruse,false,true,mandatory_exclusiveaddruse(OsType,OsVersion),false},
{keepalive,false,true,true,true},
{linger, {false,10}, {true,10},true,true},
{sndbuf,2048,4096,false,true},
@@ -887,10 +957,22 @@ all_listen_options() ->
{delay_send,false,true,true,true},
{packet_size,0,4,true,true}
].
+
+all_accept_options() ->
+ A0 = lists:keydelete(exclusiveaddruse, 1, all_listen_options()),
+ A1 = lists:keydelete(reuseaddr, 1, A0),
+ A2 = lists:keydelete(reuseport, 1, A1),
+ lists:keydelete(reuseport_lb, 1, A2).
+
all_connect_options() ->
+ OsType = os:type(),
+ OsVersion = os:version(),
[{tos,0,1,false,true},
{priority,0,1,false,true},
- {reuseaddr,false,true,false,true},
+ {reuseaddr,false,true,mandatory_reuseaddr(OsType,OsVersion),false},
+ {reuseport,false,true,mandatory_reuseport(OsType,OsVersion),false},
+ {reuseport_lb,false,true,mandatory_reuseport_lb(OsType,OsVersion),false},
+ {exclusiveaddruse,false,true,mandatory_exclusiveaddruse(OsType,OsVersion),false},
{keepalive,false,true,true,true},
{linger, {false,10}, {true,10},true,true},
{sndbuf,2048,4096,false,true},
@@ -914,11 +996,55 @@ all_connect_options() ->
].
-create_socketpair(ListenOptions,ConnectOptions) ->
+%% Mandatory on a lot of system other than those listed below. Please add more...
+mandatory_reuseaddr({unix, linux}, _OsVersion) ->
+ true;
+mandatory_reuseaddr({unix, freebsd}, _OsVersion) ->
+ true;
+mandatory_reuseaddr({unix, darwin}, _OsVersion) ->
+ true;
+mandatory_reuseaddr({win32, _}, _OsVersion) ->
+ true; %% reuseaddr and reuseport are emulated by the inet-driver
+mandatory_reuseaddr(_OsType, _OsVersion) ->
+ false.
+
+%% Mandatory an a lot of system other than those listed below. Please add more...
+mandatory_reuseport({win32, _}, _OsVersion) ->
+ true; %% reuseaddr and reuseport are emulated by the inet-driver
+mandatory_reuseport({unix, linux}, {X,Y,_Z}) when X > 4 orelse X == 4 andalso Y >= 6 ->
+ true;
+mandatory_reuseport({unix, freebsd}, {X,Y,_Z}) when X > 11 orelse X == 11 andalso Y >= 4 ->
+ %% I know that it is available on 11.4, but it may be available earlier...
+ true;
+mandatory_reuseport({unix, darwin}, {X,Y,_Z}) when X > 26 orelse X == 26 andalso Y >= 6 ->
+ %% I know that it is available on 26.6, but it may be available earlier...
+ true;
+mandatory_reuseport(_OsType, _OsVersion) ->
+ false.
+
+%% Perhaps mandatory an system other than those listed below. Please add more...
+mandatory_reuseport_lb({unix, linux}, {X,Y,_Z}) when X > 4 orelse X == 4 andalso Y >= 6 ->
+ true;
+mandatory_reuseport_lb({unix, freebsd}, {X,Y,_Z}) when X > 13 orelse X == 13 andalso Y >= 1 ->
+ %% I know that it is available on 13.1, but it may be available earlier...
+ true;
+mandatory_reuseport_lb(_OsType, _OsVersion) ->
+ false.
+
+mandatory_exclusiveaddruse({win32, _}, {X,Y,_Z}) when X > 5 orelse X == 5 andalso Y >= 2 ->
+ true;
+mandatory_exclusiveaddruse(_OsType, _OsVersion) ->
+ false.
+
+create_socketpair_init(ListenOptions,ConnectOptions) ->
{ok,LS}=gen_tcp:listen(0,ListenOptions),
{ok,Port}=inet:port(LS),
{ok,CS}=gen_tcp:connect(localhost,Port,ConnectOptions),
{ok,AS}=gen_tcp:accept(LS),
+ {LS,AS,CS}.
+
+create_socketpair(ListenOptions,ConnectOptions) ->
+ {LS,AS,CS} = create_socketpair_init(ListenOptions,ConnectOptions),
gen_tcp:close(LS),
{AS,CS}.
diff --git a/lib/kernel/test/inet_sockopt_SUITE_data/sockopt_helper.c b/lib/kernel/test/inet_sockopt_SUITE_data/sockopt_helper.c
index 9c8f8eb91a..31d82f4ba4 100644
--- a/lib/kernel/test/inet_sockopt_SUITE_data/sockopt_helper.c
+++ b/lib/kernel/test/inet_sockopt_SUITE_data/sockopt_helper.c
@@ -40,6 +40,7 @@
#define C_GET_SO_REUSEADDR 14
#define C_GET_SO_KEEPALIVE 15
#define C_GET_SO_LINGER 16
+#define C_GET_SO_DONTROUTE 17
#define C_GET_LINGER_SIZE 21
#define C_GET_TCP_INFO_SIZE 22
@@ -135,6 +136,11 @@ int main(void){
res = sizeof(struct linger);
break;
#endif
+#ifdef SO_DONTROUTE
+ case C_GET_SO_DONTROUTE:
+ res = SO_DONTROUTE;
+ break;
+#endif
#if defined(TCP_INFO) && defined(HAVE_LINUX_TCP_H)
case C_GET_TCP_INFO_SIZE:
res = sizeof(struct tcp_info);
diff --git a/lib/kernel/test/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl
index bc3882b862..ea75f040f2 100644
--- a/lib/kernel/test/init_SUITE.erl
+++ b/lib/kernel/test/init_SUITE.erl
@@ -29,7 +29,7 @@
many_restarts/0, many_restarts/1, restart_with_mode/1,
get_plain_arguments/1,
reboot/1, stop_status/1, stop/1, get_status/1, script_id/1,
- dot_erlang/1,
+ dot_erlang/1, unknown_module/1,
find_system_processes/0]).
-export([boot1/1, boot2/1]).
@@ -48,7 +48,7 @@ all() ->
[get_arguments, get_argument, boot_var,
many_restarts, restart_with_mode,
get_plain_arguments, restart, stop_status, get_status, script_id,
- dot_erlang, {group, boot}].
+ dot_erlang, unknown_module, {group, boot}].
groups() ->
[{boot, [], [boot1, boot2]}].
@@ -689,6 +689,21 @@ dot_erlang(Config) ->
ok.
+unknown_module(Config) when is_list(Config) ->
+ Port = open_port({spawn, "erl -s unknown_module"},
+ [exit_status, use_stdio, stderr_to_stdout]),
+ Error = "Error! Failed to load module 'unknown_module' because it cannot be found.",
+ [_ | _] = string:find(collect_until_exit_one(Port), Error),
+ ok.
+
+collect_until_exit_one(Port) ->
+ receive
+ {Port, {data, Msg}} -> Msg ++ collect_until_exit_one(Port);
+ {Port, {exit_status, 1}} -> []
+ after
+ 30_000 -> ct:fail(erl_timeout)
+ end.
+
%% ------------------------------------------------
%% Start the slave system with -boot flag.
%% ------------------------------------------------
diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl
index ec3887df1d..b413800661 100644
--- a/lib/kernel/test/interactive_shell_SUITE.erl
+++ b/lib/kernel/test/interactive_shell_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,6 +19,21 @@
%%
-module(interactive_shell_SUITE).
-include_lib("kernel/include/file.hrl").
+-include_lib("common_test/include/ct.hrl").
+
+%% Things to add tests for:
+%% - TERM=dumb
+%% - Editing line > MAXSIZE (1 << 16)
+%% - \t tests (use io:format("\t"))
+%% - xn fix after Delete and Backspace
+%% - octal_to_hex > 255 length (is this possible?)
+%% 1222 0 : } else if (lastput == 0) { /* A multibyte UTF8 character */
+%% 1223 0 : for (i = 0; i < ubytes; ++i) {
+%% 1224 0 : outc(ubuf[i]);
+%% 1225 : }
+%% 1226 : } else {
+%% 1227 0 : outc(lastput);
+%% - $TERM set to > 1024 long value
-export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1,
init_per_group/2, end_per_group/2,
@@ -31,27 +46,45 @@
shell_history_custom/1, shell_history_custom_errors/1,
job_control_remote_noshell/1,ctrl_keys/1,
get_columns_and_rows_escript/1,
- remsh_basic/1, remsh_longnames/1, remsh_no_epmd/1]).
+ shell_navigation/1, shell_multiline_navigation/1, shell_xnfix/1, shell_delete/1,
+ shell_transpose/1, shell_search/1, shell_insert/1,
+ shell_update_window/1, shell_small_window_multiline_navigation/1, shell_huge_input/1,
+ shell_invalid_unicode/1, shell_support_ansi_input/1,
+ shell_invalid_ansi/1, shell_suspend/1, shell_full_queue/1,
+ shell_unicode_wrap/1, shell_delete_unicode_wrap/1,
+ shell_delete_unicode_not_at_cursor_wrap/1,
+ shell_expand_location_above/1,
+ shell_expand_location_below/1,
+ shell_update_window_unicode_wrap/1,
+ shell_standard_error_nlcr/1, shell_clear/1,
+ remsh_basic/1, remsh_error/1, remsh_longnames/1, remsh_no_epmd/1,
+ remsh_expand_compatibility_25/1, remsh_expand_compatibility_later_version/1,
+ external_editor/1, external_editor_visual/1,
+ external_editor_unicode/1, shell_ignore_pager_commands/1]).
-%% For spawn
--export([toerl_server/3]).
%% Exports for custom shell history module
-export([load/0, add/1]).
-
+%% For custom prompt testing
+-export([prompt/1]).
+-record(tmux, {peer, node, name, orig_location }).
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,3}}].
all() ->
- [get_columns_and_rows_escript,get_columns_and_rows,
- exit_initial, job_control_local,
- job_control_remote, job_control_remote_noshell,
- ctrl_keys, stop_during_init, wrap,
- {group, shell_history},
- {group, remsh}].
+ [{group, to_erl},
+ {group, tty}].
groups() ->
- [{shell_history, [],
+ [{to_erl,[],
+ [get_columns_and_rows_escript,get_columns_and_rows,
+ exit_initial, job_control_local,
+ job_control_remote, job_control_remote_noshell,
+ ctrl_keys, stop_during_init, wrap,
+ shell_invalid_ansi,
+ {group, shell_history},
+ {group, remsh}]},
+ {shell_history, [],
[shell_history,
shell_history_resize,
shell_history_eaccess,
@@ -65,26 +98,60 @@ groups() ->
shell_history_custom_errors]},
{remsh, [],
[remsh_basic,
+ remsh_error,
remsh_longnames,
- remsh_no_epmd]}
+ remsh_no_epmd,
+ remsh_expand_compatibility_25,
+ remsh_expand_compatibility_later_version]},
+ {tty,[],
+ [{group,tty_unicode},
+ {group,tty_latin1},
+ shell_suspend,
+ shell_full_queue,
+ external_editor,
+ external_editor_visual,
+ shell_ignore_pager_commands
+ ]},
+ {tty_unicode,[parallel],
+ [{group,tty_tests},
+ shell_invalid_unicode,
+ external_editor_unicode
+ %% unicode wrapping does not work right yet
+ %% shell_unicode_wrap,
+ %% shell_delete_unicode_wrap,
+ %% shell_delete_unicode_not_at_cursor_wrap,
+ %% shell_update_window_unicode_wrap
+ ]},
+ {tty_latin1,[],[{group,tty_tests}]},
+ {tty_tests, [parallel],
+ [shell_navigation, shell_multiline_navigation, shell_xnfix, shell_delete,
+ shell_transpose, shell_search, shell_insert,
+ shell_update_window, shell_small_window_multiline_navigation, shell_huge_input,
+ shell_support_ansi_input,
+ shell_standard_error_nlcr,
+ shell_expand_location_above,
+ shell_expand_location_below,
+ shell_clear]}
].
init_per_suite(Config) ->
- case get_progs() of
- {error, Error} ->
- {skip, Error};
- _ ->
- Term = os:getenv("TERM", "dumb"),
- os:putenv("TERM", "vt100"),
- DefShell = get_default_shell(),
- [{default_shell,DefShell},{term,Term}|Config]
- end.
+ Term = os:getenv("TERM", "dumb"),
+ os:putenv("TERM", "vt100"),
+ [{term,Term}|Config].
end_per_suite(Config) ->
Term = proplists:get_value(term,Config),
os:putenv("TERM",Term),
ok.
+init_per_group(to_erl, Config) ->
+ case rtnode:get_progs() of
+ {error, Error} ->
+ {skip, Error};
+ _ ->
+ DefShell = rtnode:get_default_shell(),
+ [{default_shell,DefShell}|Config]
+ end;
init_per_group(remsh, Config) ->
case proplists:get_value(default_shell, Config) of
old -> {skip, "Not supported in old shell"};
@@ -95,13 +162,37 @@ init_per_group(shell_history, Config) ->
old -> {skip, "Not supported in old shell"};
new -> Config
end;
+init_per_group(tty, Config) ->
+ case string:split(tmux("-V")," ") of
+ ["tmux",[Num,$.|_]] when Num >= $3, Num =< $9 ->
+ tmux("kill-session"),
+ "" = tmux("-u new-session -x 50 -y 60 -d"),
+ ["" = tmux(["set-environment '",Name,"' '",Value,"'"])
+ || {Name,Value} <- os:env()],
+ Config;
+ ["tmux", Vsn] ->
+ {skip, "invalid tmux version " ++ Vsn ++ ". Need vsn 3 or later"};
+ Error ->
+ {skip, "tmux not installed " ++ Error}
+ end;
+init_per_group(Group, Config) when Group =:= tty_unicode;
+ Group =:= tty_latin1 ->
+ [Lang,_] =
+ string:split(
+ os:getenv("LC_ALL",
+ os:getenv("LC_CTYPE",
+ os:getenv("LANG","en_US.UTF-8"))),"."),
+ case Group of
+ tty_unicode ->
+ [{encoding, unicode},{env,[{"LC_ALL",Lang++".UTF-8"}]}|Config];
+ tty_latin1 ->
+ % [{encoding, latin1},{env,[{"LC_ALL",Lang++".ISO-8859-1"}]}|Config],
+ {skip, "latin1 tests not implemented yet"}
+ end;
init_per_group(sh_custom, Config) ->
- %% Ensure that ERL_AFLAGS will not override the value of the
- %% shell_history variable.
- Name = interactive_shell_sh_custom,
- Args = "-noshell -kernel shell_history not_overridden",
- {ok, Node} = test_server:start_node(Name, slave, [{args,Args}]),
- try erpc:call(Node, application, get_env, [kernel, shell_history], timeout(normal)) of
+ %% Ensure that ERL_AFLAGS will not override the value of the shell_history variable.
+ {ok, Peer, Node} = ?CT_PEER(["-noshell","-kernel","shell_history","not_overridden"]),
+ try erpc:call(Node, application, get_env, [kernel, shell_history], rtnode:timeout(normal)) of
{ok, not_overridden} ->
Config;
_ ->
@@ -112,23 +203,45 @@ init_per_group(sh_custom, Config) ->
io:format("~p\n~p\n~p\n", [C,R,Stk]),
{skip, "Unexpected error"}
after
- test_server:stop_node(Node)
+ peer:stop(Peer)
end;
init_per_group(_GroupName, Config) ->
Config.
+end_per_group(tty, _Config) ->
+ Windows = string:split(tmux("list-windows"), "\n", all),
+ lists:foreach(
+ fun(W) ->
+ case string:split(W, " ", all) of
+ ["0:" | _] -> ok;
+ [No, _Name | _] ->
+ "" = os:cmd(["tmux select-window -t ", string:split(No,":")]),
+ ct:log("~ts~n~ts",[W, os:cmd(lists:concat(["tmux capture-pane -p -e"]))])
+ end
+ end, Windows),
+% "" = os:cmd("tmux kill-session")
+ ok;
end_per_group(_GroupName, Config) ->
Config.
-init_per_testcase(_Func, Config) ->
- Config.
-
-end_per_testcase(_Case, _Config) ->
- %% Terminate any connected nodes. They may disturb test cases that follow.
- lists:foreach(fun(Node) ->
- catch erpc:call(Node, erlang, halt, [])
- end, nodes()),
- ok.
+init_per_testcase(Func, Config) ->
+ Path = [Func,
+ [proplists:get_value(name,P) ||
+ P <- [proplists:get_value(tc_group_properties,Config,[])] ++
+ proplists:get_value(tc_group_path,Config,[])]],
+ [{tc_path, lists:concat(lists:join("-",lists:flatten(Path)))} | Config].
+
+end_per_testcase(_Case, Config) ->
+ GroupProperties = proplists:get_value(tc_group_properties, Config),
+ case (GroupProperties =/= false) andalso proplists:get_value(parallel, GroupProperties, false) of
+ true -> ok;
+ false ->
+ %% Terminate any connected nodes. They may disturb test cases that follow.
+ lists:foreach(fun(Node) ->
+ catch erpc:call(Node, erlang, halt, [])
+ end, nodes()),
+ ok
+ end.
%%-define(DEBUG,1).
-ifdef(DEBUG).
@@ -155,7 +268,7 @@ run_unbuffer_escript(Rows, Columns, EScript, NoTermStdIn, NoTermStdOut) ->
{true, true} -> io_lib:format(" > ~s < ~s ; cat ~s", [TmpFile, TmpFile, TmpFile])
end,
Command = io_lib:format("unbuffer -p bash -c \"stty rows ~p; stty columns ~p; escript ~s ~s\"",
- [Rows, Columns, EScript, CommandModifier]),
+ [Rows, Columns, EScript, CommandModifier]),
%% io:format("Command: ~s ~n", [Command]),
Out = os:cmd(Command),
%% io:format("Out: ~p ~n", [Out]),
@@ -203,50 +316,1396 @@ get_columns_and_rows(Config) when is_list(Config) ->
ok.
test_columns_and_rows(old, Args) ->
- rtnode([{putline, ""},
- {putline, "2."},
- {expect, "2\r\n"},
- {putline, "io:columns()."},
- {expect, "{error,enotsup}\r\n"},
- {putline, "io:rows()."},
- {expect, "{error,enotsup}\r\n"}
- ], [], [], Args),
-
- rtnode([{putline, ""},
- {putline, "2."},
- {expect, "2\r\n"},
- {putline, "io:columns()."},
- {expect, "{ok,90}\r\n"},
- {putline,"io:rows()."},
- {expect, "{ok,40}\r\n"}],
- [],
- "stty rows 40; stty columns 90; ",
- Args);
+ rtnode:run(
+ [{putline, ""},
+ {putline, "2."},
+ {expect, "2\r\n"},
+ {putline, "io:columns()."},
+ {expect, "{error,enotsup}\r\n"},
+ {putline, "io:rows()."},
+ {expect, "{error,enotsup}\r\n"}
+ ], [], [], Args),
+
+ rtnode:run(
+ [{putline, ""},
+ {putline, "2."},
+ {expect, "2\r\n"},
+ {putline, "io:columns()."},
+ {expect, "{ok,90}\r\n"},
+ {putline,"io:rows()."},
+ {expect, "{ok,40}\r\n"}],
+ [],
+ "stty rows 40; stty columns 90; ",
+ Args);
test_columns_and_rows(new, _Args) ->
- rtnode([{putline, ""},
- {expect, "1> $"},
- {putline, "2."},
- {expect, "\r\n2\r\n"},
- {expect, "> $"},
- {putline, "io:columns()."},
- {expect, "{ok,80}\r\n"},
- {expect, "> $"},
- {putline, "io:rows()."},
- {expect, "\r\n{ok,24}\r\n"}
- ]),
-
- rtnode([{putline, ""},
- {expect, "1> $"},
- {putline, "2."},
- {expect, "\r\n2\r\n"},
- {expect, "> $"},
- {putline, "io:columns()."},
- {expect, "\r\n{ok,90}\r\n"},
- {expect, "> $"},
- {putline, "io:rows()."},
- {expect, "\r\n{ok,40}\r\n"}],
- [],
- "stty rows 40; stty columns 90; ").
+ rtnode:run(
+ [{putline, ""},
+ {expect, "1> $"},
+ {putline, "2."},
+ {expect, "\r\n2\r\n"},
+ {expect, "> $"},
+ {putline, "io:columns()."},
+ {expect, "{ok,80}\r\n"},
+ {expect, "> $"},
+ {putline, "io:rows()."},
+ {expect, "\r\n{ok,24}\r\n"}
+ ]),
+
+ rtnode:run(
+ [{putline, ""},
+ {expect, "1> $"},
+ {putline, "2."},
+ {expect, "\r\n2\r\n"},
+ {expect, "> $"},
+ {putline, "io:columns()."},
+ {expect, "\r\n{ok,90}\r\n"},
+ {expect, "> $"},
+ {putline, "io:rows()."},
+ {expect, "\r\n{ok,40}\r\n"}],
+ [],
+ "stty rows 40; stty columns 90; ").
+
+shell_navigation(Config) ->
+
+ Term = start_tty(Config),
+
+ try
+ [begin
+ send_tty(Term,"{aaa,'b"++U++"b',ccc}"),
+ check_location(Term, {0, 0}), %% Check that cursor jump backward
+ check_content(Term, "{aaa,'b"++U++"b',ccc}$"),
+ timer:sleep(1000), %% Wait for cursor to jump back
+ check_location(Term, {0, width("{aaa,'b"++U++"b',ccc}")}),
+ send_tty(Term,"Home"),
+ check_location(Term, {0, 0}),
+ send_tty(Term,"End"),
+ check_location(Term, {0, width("{aaa,'b"++U++"b',ccc}")}),
+ send_tty(Term,"Left"),
+ check_location(Term, {0, width("{aaa,'b"++U++"b',ccc")}),
+ send_tty(Term,"C-Left"),
+ check_location(Term, {0, width("{aaa,'b"++U++"b',")}),
+ send_tty(Term,"C-Left"),
+ check_location(Term, {0, width("{aaa,")}),
+ send_tty(Term,"C-Right"),
+ check_location(Term, {0, width("{aaa,'b"++U++"b'")}),
+ send_tty(Term,"C-Left"),
+ check_location(Term, {0, width("{aaa,")}),
+ send_tty(Term,"C-Left"),
+ check_location(Term, {0, width("{")}),
+ send_tty(Term,"C-Left"),
+ check_location(Term, {0, 0}),
+ send_tty(Term,"C-E"),
+ check_location(Term, {0, width("{aaa,'b"++U++"b',ccc}")}),
+ send_tty(Term,"C-A"),
+ check_location(Term, {0, 0}),
+ send_tty(Term,"Enter")
+ end || U <- hard_unicode()],
+ ok
+ after
+ stop_tty(Term)
+ end.
+shell_multiline_navigation(Config) ->
+ Term = start_tty(Config),
+
+ try
+ [begin
+ check_location(Term, {0, 0}),
+ send_tty(Term,"{aaa,"),
+ check_location(Term, {0,width("{aaa,")}),
+ send_tty(Term,"\n'b"++U++"b',"),
+ check_location(Term, {0, width("'b"++U++"b',")}),
+ send_tty(Term,"\nccc}"),
+ check_location(Term, {-2, 0}), %% Check that cursor jump backward (blink)
+ timer:sleep(1000), %% Wait for cursor to jump back
+ check_location(Term, {0, width("ccc}")}),
+ send_tty(Term,"Home"),
+ check_location(Term, {0, 0}),
+ send_tty(Term,"End"),
+ check_location(Term, {0, width("ccc}")}),
+ send_tty(Term,"Left"),
+ check_location(Term, {0, width("ccc")}),
+ send_tty(Term,"C-Left"),
+ check_location(Term, {0, 0}),
+ send_tty(Term,"C-Left"),
+ check_location(Term, {-1, width("'b"++U++"b',")}),
+ send_tty(Term,"C-Left"),
+ check_location(Term, {-1, 0}),
+ %send_tty(Term,"C-Left"),
+ %check_location(Term, {-1, 0}),
+ %send_tty(Term,"C-Right"),
+ %check_location(Term, {-1, 1}),
+ send_tty(Term,"C-Right"),
+ check_location(Term, {-1, width("'b"++U++"b'")}),
+ send_tty(Term,"C-Up"),
+ check_location(Term, {-2, width("{aaa,")}),
+ send_tty(Term,"C-Down"),
+ send_tty(Term,"C-Down"),
+ check_location(Term, {0, width("ccc}")}),
+ send_tty(Term,"Left"),
+ send_tty(Term,"C-Up"),
+ check_location(Term, {-1, width("'b"++U)}),
+ send_tty(Term,"M-<"),
+ check_location(Term, {-2, 0}),
+ send_tty(Term,"M->"),
+ send_tty(Term,"Left"),
+ check_location(Term, {0,width("ccc")}),
+ send_tty(Term,"Enter"),
+ send_tty(Term,"Right"),
+ check_location(Term, {0,0}),
+ send_tty(Term,"C-h"), % Backspace
+ check_location(Term, {-1,width("ccc}")}),
+ send_tty(Term,"Left"),
+ send_tty(Term,"M-Enter"),
+ send_tty(Term,"Right"),
+ check_location(Term, {0,1}),
+ send_tty(Term,"M-c"),
+ check_location(Term, {-3,0}),
+ send_tty(Term,"{'"++U++"',\n\n\nworks}.\n")
+ end || U <- hard_unicode()],
+ ok
+ after
+ stop_tty(Term)
+ end.
+shell_clear(Config) ->
+
+ Term = start_tty(Config),
+ {Rows, _Cols} = get_window_size(Term),
+
+ try
+ send_tty(Term,"foobar."),
+ send_tty(Term,"Enter"),
+ check_content(Term, "foobar"),
+ check_location(Term, {0, 0}),
+ send_tty(Term,"bazbat"),
+ check_location(Term, {0, 6}),
+ send_tty(Term,"C-L"),
+ check_location(Term, {-Rows+1, 6}),
+ check_content(Term, "bazbat")
+ after
+ stop_tty(Term)
+ end.
+
+shell_xnfix(Config) ->
+
+ Term = start_tty(Config),
+
+ {_Rows, Cols} = get_window_size(Term),
+ {_Row, Col} = get_location(Term),
+
+ As = lists:duplicate(Cols - Col - 1,"a"),
+
+ try
+ [begin
+ check_location(Term, {0, 0}),
+ send_tty(Term,As),
+ check_content(Term,[As,$$]),
+ check_location(Term, {0, Cols - Col - 1}),
+ send_tty(Term,"a"),
+ check_location(Term, {0, -Col}),
+ send_tty(Term,"aaa"),
+ check_location(Term, {0, -Col + 3}),
+ [send_tty(Term,"Left") || _ <- lists:seq(1,3 + width(U))],
+ send_tty(Term,U),
+ %% a{Cols-1}U\naaaaa
+ check_content(Term,[lists:duplicate(Cols - Col - 1 - width(U),$a),
+ U,"\n",lists:duplicate(3+width(U), $a),"$"]),
+ check_location(Term, {0, -Col}),
+ send_tty(Term,"Left"),
+ send_tty(Term,U),
+ %% a{Cols-1}U\nUaaaaa
+ check_content(Term,[lists:duplicate(Cols - Col - 1 - width(U),$a),
+ U,"\n",U,lists:duplicate(3+width(U), $a),"$"]),
+ check_location(Term, {0, -Col}),
+ %% send_tty(Term,"Left"),
+ %% send_tty(Term,"BSpace"),
+ %% a{Cols-2}U\nUaaaaa
+ %% check_content(Term,[lists:duplicate(Cols - Col - 2 - width(U),$a),
+ %% U,"\n",U,lists:duplicate(3+width(U), $a),"$"]),
+ %% send_tty(Term,"BSpace"),
+ %% check_content(Term,[lists:duplicate(Cols - Col - 1 - width(U),$a),
+ %% U,U,"\n",lists:duplicate(3+width(U), $a),"$"]),
+ %% send_tty(Term,"aa"),
+ %% check_content(Term,[lists:duplicate(Cols - Col - 2 - width(U),$a),
+ %% U,"a\n",U,lists:duplicate(3+width(U), $a),"$"]),
+ %% check_location(Term, {0, -Col}),
+ send_tty(Term,"C-K"),
+ check_location(Term, {0, -Col}),
+ send_tty(Term,"C-A"),
+ check_location(Term, {-1, 0}),
+ send_tty(Term,"C-E"),
+ check_location(Term, {0, -Col}),
+ send_tty(Term,"Enter"),
+ ok
+ end || U <- hard_unicode()]
+ after
+ stop_tty(Term)
+ end.
+
+
+%% Characters that are larger than 2 wide need special handling when they
+%% are at the end of the current line.
+shell_unicode_wrap(Config) ->
+
+ Term = start_tty(Config),
+
+ {_Rows, Cols} = get_window_size(Term),
+ {_Row, Col} = get_location(Term),
+
+ try
+ [begin
+ FirstLine = [U,lists:duplicate(Cols - Col - width(U)*2 + 1,"a")],
+ OtherLineA = [U,lists:duplicate(Cols - width(U) * 2+1,"a")],
+ OtherLineB = [U,lists:duplicate(Cols - width(U) * 2+1,"b")],
+ OtherLineC = [U,lists:duplicate(Cols - width(U) * 2+1,"c")],
+ OtherLineD = [U,lists:duplicate(Cols - width(U) * 2+1,"d")],
+ send_tty(Term,FirstLine),
+ check_content(Term, [FirstLine,$$]),
+ check_location(Term, {0, Cols - Col - width(U)+1}),
+
+ send_tty(Term,OtherLineA),
+ check_content(Term, [OtherLineA,$$]),
+ check_location(Term, {0, Cols - Col - width(U)+1}),
+
+ send_tty(Term,OtherLineB),
+ check_content(Term, [OtherLineB,$$]),
+ check_location(Term, {0, Cols - Col - width(U)+1}),
+
+ send_tty(Term,OtherLineC),
+ check_content(Term, [OtherLineC,$$]),
+ check_location(Term, {0, Cols - Col - width(U)+1}),
+
+ send_tty(Term,OtherLineD),
+ check_content(Term, [OtherLineD,$$]),
+ check_location(Term, {0, Cols - Col - width(U)+1}),
+
+ send_tty(Term,"C-A"),
+ check_location(Term, {-4, 0}), %% Broken
+ send_tty(Term,"Right"),
+ check_location(Term, {-4, width(U)}), %% Broken
+
+ send_tty(Term,"DC"), %% Broken
+ check_content(Term, ["a.*",U,"$"]),
+ check_content(Term, ["^b.*",U,"c$"]),
+ check_content(Term, ["^c.*",U,"dd$"]),
+
+ send_tty(Term,"a"),
+ check_content(Term, [FirstLine,$$]),
+ check_content(Term, [OtherLineA,$$]),
+ check_content(Term, [OtherLineB,$$]),
+ check_content(Term, [OtherLineC,$$]),
+ check_content(Term, [OtherLineD,$$]),
+
+ send_tty(Term,"Enter")
+ end || U <- hard_unicode()]
+ after
+ stop_tty(Term)
+ end.
+
+shell_delete(Config) ->
+
+ Term = start_tty(Config),
+
+ try
+
+ [ begin
+ send_tty(Term,"a"),
+ check_content(Term, "> a$"),
+ check_location(Term, {0, 1}),
+ send_tty(Term,"BSpace"),
+ check_location(Term, {0, 0}),
+ check_content(Term, ">$"),
+ send_tty(Term,"a"),
+ send_tty(Term,U),
+ check_location(Term, {0, width([$a, U])}),
+ send_tty(Term,"a"),
+ send_tty(Term,U),
+ check_location(Term, {0, width([$a,U,$a,U])}),
+ check_content(Term, ["> a",U,$a,U,"$"]),
+ send_tty(Term,"Left"),
+ send_tty(Term,"Left"),
+ send_tty(Term,"BSpace"),
+ check_location(Term, {0, width([$a])}),
+ check_content(Term, ["> aa",U,"$"]),
+ send_tty(Term,U),
+ check_location(Term, {0, width([$a,U])}),
+ send_tty(Term,"Left"),
+ send_tty(Term,"DC"),
+ check_location(Term, {0, width([$a])}),
+ check_content(Term, ["> aa",U,"$"]),
+ send_tty(Term,"DC"),
+ send_tty(Term,"DC"),
+ check_content(Term, ["> a$"]),
+ send_tty(Term,"C-E"),
+ check_location(Term, {0, width([$a])}),
+ send_tty(Term,"BSpace"),
+ check_location(Term, {0, width([])})
+ end || U <- hard_unicode()]
+ after
+ stop_tty(Term)
+ end.
+
+%% When deleting characters at the edge of the screen that are "large",
+%% we need to take special care.
+shell_delete_unicode_wrap(Config) ->
+
+ Term = start_tty(Config),
+
+ {_Rows, Cols} = get_window_size(Term),
+ {_Row, Col} = get_location(Term),
+
+ try
+ [begin
+ send_tty(Term,lists:duplicate(Cols - Col,"a")),
+ check_content(Term,"> a*$"),
+ send_tty(Term,[U,U,"aaaaa"]),
+ check_content(Term,["\n",U,U,"aaaaa$"]),
+ [send_tty(Term,"Left") || _ <- lists:seq(1,5+2)],
+ check_location(Term,{0,-Col}),
+ send_tty(Term,"BSpace"),
+ check_content(Term,"> a* \n"),
+ check_location(Term,{-1,Cols - Col - 1}),
+ send_tty(Term,"BSpace"),
+ check_content(Term,["> a*",U,"\n"]),
+ check_location(Term,{-1,Cols - Col - 2}),
+ send_tty(Term,"BSpace"),
+ check_content(Term,["> a*",U," \n"]),
+ check_location(Term,{-1,Cols - Col - 3}),
+ send_tty(Term,"BSpace"),
+ check_content(Term,["> a*",U,U,"\n"]),
+ check_content(Term,["\naaaaa$"]),
+ check_location(Term,{-1,Cols - Col - 4}),
+ send_tty(Term,"BSpace"),
+ check_content(Term,["> a*",U,U,"a\n"]),
+ check_content(Term,["\naaaa$"]),
+ check_location(Term,{-1,Cols - Col - 5}),
+ send_tty(Term,"Enter")
+ end || U <- hard_unicode()]
+ after
+ stop_tty(Term)
+ end.
+
+%% When deleting characters and a "large" characters is changing line we need
+%% to take extra care
+shell_delete_unicode_not_at_cursor_wrap(Config) ->
+
+ Term = start_tty(Config),
+
+ {_Rows, Cols} = get_window_size(Term),
+ {_Row, Col} = get_location(Term),
+
+ try
+ [begin
+ send_tty(Term,lists:duplicate(Cols - Col,"a")),
+ check_content(Term,"> a*$"),
+ send_tty(Term,["a",U,"aaaaa"]),
+ check_content(Term,["\na",U,"aaaaa$"]),
+ send_tty(Term,"C-A"),
+ send_tty(Term,"DC"),
+ check_content(Term,["\n",U,"aaaaa$"]),
+ send_tty(Term,"DC"),
+ check_content(Term,["\n",U,"aaaaa$"]),
+ check_content(Term,["> a* \n"]),
+ send_tty(Term,"DC"),
+ check_content(Term,["\naaaaa$"]),
+ check_content(Term,["> a*",U,"\n"]),
+ send_tty(Term,"DC"),
+ check_content(Term,["\naaaa$"]),
+ check_content(Term,["> a*",U,"a\n"]),
+ send_tty(Term,"Enter")
+ end || U <- hard_unicode()]
+ after
+ stop_tty(Term)
+ end.
+
+%% When deleting characters and a "large" characters is changing line we need
+%% to take extra care
+shell_update_window_unicode_wrap(Config) ->
+
+ Term = start_tty(Config),
+
+ {_Rows, Cols} = get_window_size(Term),
+ {_Row, Col} = get_location(Term),
+
+ try
+ [begin
+ send_tty(Term,lists:duplicate(Cols - Col - width(U) + 1,"a")),
+ check_content(Term,"> a*$"),
+ send_tty(Term,[U,"aaaaa"]),
+ check_content(Term,["> a* ?\n",U,"aaaaa$"]),
+ tmux(["resize-window -t ",tty_name(Term)," -x ",Cols+1]),
+ check_content(Term,["> a*",U,"\naaaaa$"]),
+ tmux(["resize-window -t ",tty_name(Term)," -x ",Cols]),
+ check_content(Term,["> a* ?\n",U,"aaaaa$"]),
+ send_tty(Term,"Enter")
+ end || U <- hard_unicode()]
+ after
+ stop_tty(Term)
+ end.
+
+shell_transpose(Config) ->
+
+ Term = start_tty(Config),
+
+ Unicode = [[$a]] ++ hard_unicode(),
+
+ try
+ [
+ begin
+ send_tty(Term,"a"),
+ [send_tty(Term,[CP]) || CP <- U],
+ send_tty(Term,"b"),
+ [[send_tty(Term,[CP]) || CP <- U2] || U2 <- Unicode],
+ send_tty(Term,"cde"),
+ check_content(Term, ["a",U,"b",Unicode,"cde$"]),
+ check_location(Term, {0, width(["a",U,"b",Unicode,"cde"])}),
+ send_tty(Term,"Home"),
+ check_location(Term, {0, 0}),
+ send_tty(Term,"Right"),
+ send_tty(Term,"Right"),
+ check_location(Term, {0, 1+width([U])}),
+ send_tty(Term,"C-T"),
+ check_content(Term, ["ab",U,Unicode,"cde$"]),
+ send_tty(Term,"C-T"),
+ check_content(Term, ["ab",hd(Unicode),U,tl(Unicode),"cde$"]),
+ [send_tty(Term,"C-T") || _ <- lists:seq(1,length(Unicode)-1)],
+ check_content(Term, ["ab",Unicode,U,"cde$"]),
+ send_tty(Term,"C-T"),
+ check_content(Term, ["ab",Unicode,"c",U,"de$"]),
+ check_location(Term, {0, width(["ab",Unicode,"c",U])}),
+ send_tty(Term,"End"),
+ check_location(Term, {0, width(["ab",Unicode,"c",U,"de"])}),
+ send_tty(Term,"Left"),
+ send_tty(Term,"Left"),
+ send_tty(Term,"BSpace"),
+ check_content(Term, ["ab",Unicode,"cde$"]),
+ send_tty(Term,"End"),
+ send_tty(Term,"Enter")
+ end || U <- Unicode],
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end.
+
+shell_search(C) ->
+ Term = start_tty(C),
+
+ try
+ send_tty(Term,"a"),
+ send_tty(Term,"."),
+ send_tty(Term,"Enter"),
+ send_tty(Term,"'"),
+ send_tty(Term,"a"),
+ send_tty(Term,[16#1f600]),
+ send_tty(Term,"'"),
+ send_tty(Term,"."),
+ send_tty(Term,"Enter"),
+ check_location(Term, {0, 0}),
+ send_tty(Term,"C-r"),
+ check_content(Term, "search:\\s*\n\\s*'a😀'."),
+ send_tty(Term,"C-a"),
+ check_location(Term, {-1, width(C, "'a😀'.")}),
+ send_tty(Term,"Enter"),
+ send_tty(Term,"C-r"),
+ check_content(Term, "search:\\s*\n\\s*'a😀'."),
+ send_tty(Term,"a"),
+ check_content(Term, "search: a\\s*\n\\s*'a😀'."),
+ send_tty(Term,"C-r"),
+ check_content(Term, "search: a\\s*\n\\s*a."),
+ send_tty(Term,"BSpace"),
+ check_content(Term, "search:\\s*\n\\s*'a😀'."),
+ send_tty(Term,"BSpace"),
+ check_content(Term, "search:\\s*\n\\s*'a😀'."),
+ send_tty(Term,"M-c"),
+ check_location(Term, {-1, 0}),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end.
+
+shell_insert(Config) ->
+ Term = start_tty(Config),
+
+ try
+ send_tty(Term,"abcdefghijklm"),
+ check_content(Term, "abcdefghijklm$"),
+ check_location(Term, {0, 13}),
+ send_tty(Term,"Home"),
+ send_tty(Term,"Right"),
+ send_tty(Term,"C-T"),
+ send_tty(Term,"C-T"),
+ send_tty(Term,"C-T"),
+ send_tty(Term,"C-T"),
+ check_content(Term, "bcdeafghijklm$"),
+ send_tty(Term,"End"),
+ send_tty(Term,"Left"),
+ send_tty(Term,"Left"),
+ send_tty(Term,"BSpace"),
+ check_content(Term, "bcdeafghijlm$"),
+ ok
+ after
+ stop_tty(Term)
+ end.
+
+shell_update_window(Config) ->
+ Term = start_tty(Config),
+
+ Text = lists:flatten(["abcdefghijklmabcdefghijklm"]),
+ {_Row, Col} = get_location(Term),
+
+ try
+ send_tty(Term,Text),
+ check_content(Term,Text),
+ check_location(Term, {0, width(Text)}),
+ tmux(["resize-window -t ",tty_name(Term)," -x ",width(Text)+Col+1]),
+ send_tty(Term,"a"),
+ check_location(Term, {0, -Col}),
+ send_tty(Term,"BSpace"),
+ tmux(["resize-window -t ",tty_name(Term)," -x ",width(Text)+Col]),
+ %% xnfix bug! at least in tmux... seems to work in iTerm as it does not
+ %% need xnfix when resizing
+ check_location(Term, {0, -Col}),
+ tmux(["resize-window -t ",tty_name(Term)," -x ",width(Text) div 2 + Col]),
+ check_location(Term, {0, -Col + width(Text) div 2}),
+ ok
+ after
+ stop_tty(Term)
+ end.
+shell_small_window_multiline_navigation(Config) ->
+ Term0 = start_tty(Config),
+ tmux(["resize-window -t ",tty_name(Term0)," -x ",30, " -y ", 6]),
+ {Row, Col} = get_location(Term0),
+ Term = Term0#tmux{orig_location = {Row, Col}},
+ Text = ("xbcdefghijklmabcdefghijklm\n"++
+ "abcdefghijkl\n"++
+ "abcdefghijklmabcdefghijklm\n"++
+ "abcdefghijklmabcdefghijklx"),
+ try
+ send_tty(Term,Text),
+ check_location(Term, {0, -4}),
+ send_tty(Term,"Home"),
+ check_location(Term, {-1, 0}),
+ send_tty(Term, "C-Up"),
+ check_location(Term, {-2, 0}),
+ send_tty(Term, "C-Down"),
+ check_location(Term, {-1, 0}),
+ send_tty(Term, "Left"),
+ check_location(Term, {-1, -4}),
+ send_tty(Term, "Right"),
+ check_location(Term, {-1, 0}),
+ send_tty(Term, "\e[1;4A"),
+ check_location(Term, {-5, 0}),
+ check_content(Term,"xbc"),
+ send_tty(Term, "\e[1;4B"),
+ check_location(Term, {0, -4}),
+ check_content(Term,"klx"),
+ send_tty(Term, " sets:is_e\t"),
+ check_content(Term,"is_element"),
+ check_content(Term,"is_empty"),
+ check_location(Term, {-3, 6}),
+ send_tty(Term, "C-Up"),
+ send_tty(Term,"Home"),
+ check_location(Term, {-2, 0}),
+ send_tty(Term, "sets:is_e\t"),
+ check_content(Term,"is_element"),
+ check_content(Term,"is_empty"),
+ check_location(Term, {-4, 9}),
+ ok
+ after
+ stop_tty(Term)
+ end.
+shell_huge_input(Config) ->
+ Term = start_tty(Config),
+
+ ManyUnicode = lists:duplicate(100,hard_unicode()),
+
+ try
+ send_tty(Term,ManyUnicode),
+ check_content(Term, hard_unicode_match(Config) ++ "$",
+ #{ replace => {"\n",""} }),
+ send_tty(Term,"Enter"),
+ ok
+ after
+ stop_tty(Term)
+ end.
+
+%% Test that the shell works when invalid utf-8 (aka latin1) is sent to it
+shell_invalid_unicode(Config) ->
+ Term = start_tty(Config),
+
+ InvalidUnicode = <<$å,$ä,$ö>>, %% åäö in latin1
+
+ try
+ send_tty(Term,hard_unicode()),
+ check_content(Term, hard_unicode() ++ "$"),
+ send_tty(Term,"Enter"),
+ check_content(Term, "illegal character"),
+ %% Send invalid utf-8
+ send_stdin(Term,InvalidUnicode),
+ %% Check that the utf-8 was echoed
+ check_content(Term, "\\\\345\\\\344\\\\366$"),
+ send_tty(Term,"Enter"),
+ %% Check that the terminal entered "latin1" mode
+ send_tty(Term,"😀한."),
+ check_content(Term, "\\Q\\360\\237\\230\\200\\355\\225\\234.\\E$"),
+ send_tty(Term,"Enter"),
+ %% Check that we can reset the encoding to unicode
+ send_tty(Term,"io:setopts([{encoding,unicode}])."),
+ send_tty(Term,"Enter"),
+ check_content(Term, "\nok\n"),
+ send_tty(Term,"😀한"),
+ check_content(Term, "😀한$"),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end.
+
+
+%% Test the we can handle ansi insert, navigation and delete
+%% We currently can not so skip this test
+shell_support_ansi_input(Config) ->
+
+ Term = start_tty(Config),
+
+ BoldText = "\e[;1m",
+ ClearText = "\e[0m",
+
+ try
+ send_stdin(Term,["{",BoldText,"a😀b",ClearText,"}"]),
+ timer:sleep(1000),
+ try check_location(Term, {0, width("{1ma😀bm}")}) of
+ _ ->
+ throw({skip, "Do not support ansi input"})
+ catch _:_ ->
+ ok
+ end,
+ check_location(Term, {0, width("{a😀b}")}),
+ check_content(fun() -> get_content(Term,"-e") end,
+ ["{", BoldText, "a😀b", ClearText, "}"]),
+ send_tty(Term,"Left"),
+ send_tty(Term,"Left"),
+ check_location(Term, {0, width("{a😀")}),
+ send_tty(Term,"C-Left"),
+ check_location(Term, {0, width("{")}),
+ send_tty(Term,"End"),
+ send_tty(Term,"BSpace"),
+ send_tty(Term,"BSpace"),
+ check_content(Term, ["{", BoldText, "a😀"]),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end.
+
+shell_expand_location_below(Config) ->
+
+ Term = start_tty(Config),
+
+ {Rows, _} = get_location(Term),
+
+ NumFunctions = lists:seq(0, Rows*2),
+ FunctionName = "a_long_function_name",
+
+ Module = lists:flatten(
+ ["-module(long_module).\n",
+ "-export([",lists:join($,,[io_lib:format("~s~p/0",[FunctionName,I]) || I <- NumFunctions]),"]).\n\n",
+ [io_lib:format("~s~p() -> ok.\n",[FunctionName,I]) || I <- NumFunctions]]),
+
+ DoScan = fun F([]) ->
+ [];
+ F(Str) ->
+ {done,{ok,T,_},C} = erl_scan:tokens([],Str,0),
+ [ T | F(C) ]
+ end,
+ Forms = [ begin {ok,Y} = erl_parse:parse_form(X),Y end || X <- DoScan(Module) ],
+ {ok,_,Bin} = compile:forms(Forms, [debug_info]),
+
+ try
+ tmux(["resize-window -t ",tty_name(Term)," -x 80"]),
+
+ %% First check that basic completion works
+ send_stdin(Term, "escript:"),
+ send_stdin(Term, "\t"),
+ %% Cursor at correct place
+ check_location(Term, {-3, width("escript:")}),
+ %% Nothing after the start( completion
+ check_content(Term, "start\\($"),
+
+ %% Check that completion is cleared when we type
+ send_stdin(Term, "s"),
+ check_location(Term, {-3, width("escript:s")}),
+ check_content(Term, "escript:s$"),
+
+ %% Check that completion works when in the middle of a term
+ send_tty(Term, "Home"),
+ send_tty(Term, "["),
+ send_tty(Term, "End"),
+ send_tty(Term, ", test_after]"),
+ [send_tty(Term, "Left") || _ <- ", test_after]"],
+ send_stdin(Term, "\t"),
+ check_location(Term, {-3, width("[escript:s")}),
+ check_content(Term, "script_name\\([ ]+start\\($"),
+ send_tty(Term, "C-K"),
+
+ %% Check that completion works when in the middle of a long term
+ send_tty(Term, ", "++ lists:duplicate(80*2, $a)++"]"),
+ [send_tty(Term, "Left") || _ <- ", "++ lists:duplicate(80*2, $a)++"]"],
+ send_stdin(Term, "\t"),
+ check_location(Term, {-4, width("[escript:s")}),
+ check_content(Term, "script_name\\([ ]+start\\($"),
+ send_tty(Term, "End"),
+ send_stdin(Term, ".\n"),
+
+ %% Check that we behave as we should with very long completions
+ rpc(Term, fun() ->
+ {module, long_module} = code:load_binary(long_module, "long_module.beam", Bin)
+ end),
+ check_content(Term, "3>"),
+ send_stdin(Term, "long_module:" ++ FunctionName),
+ send_stdin(Term, "\t"),
+ %% Check that correct text is printed below expansion
+ check_content(Term, io_lib:format("Press tab to see all ~p expansions",
+ [length(NumFunctions)])),
+ send_stdin(Term, "\t"),
+ %% The expansion does not fit on screen, verify that
+ %% expand above mode is used
+ check_content(fun() -> get_content(Term, "-S -7") end,
+ "3> long_module:" ++ FunctionName ++ "\nfunctions"),
+ check_content(Term, "3> long_module:" ++ FunctionName ++ "$"),
+
+ %% We resize the terminal to make everything fit and test that
+ %% expand below mode is used
+ tmux(["resize-window -t ", tty_name(Term), " -y ", integer_to_list(Rows+10)]),
+ timer:sleep(1000), %% Sleep to make sure window has resized
+ send_stdin(Term, "\t\t"),
+ check_content(Term, "3> long_module:" ++ FunctionName ++ "\nfunctions(\n|.)*a_long_function_name99\\($"),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end.
+
+shell_expand_location_above(Config) ->
+
+ Term = start_tty([{args,["-stdlib","shell_expand_location","above"]}|Config]),
+
+ try
+ tmux(["resize-window -t ",tty_name(Term)," -x 80"]),
+ send_stdin(Term, "escript:"),
+ send_stdin(Term, "\t"),
+ check_location(Term, {0, width("escript:")}),
+ check_content(Term, "start\\(\n"),
+ check_content(Term, "escript:$"),
+ send_stdin(Term, "s"),
+ send_stdin(Term, "\t"),
+ check_location(Term, {0, width("escript:s")}),
+ check_content(Term, "\nscript_name\\([ ]+start\\(\n"),
+ check_content(Term, "escript:s$"),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end.
+
+%% Test the we can handle invalid ansi escape chars.
+%% tmux cannot handle this... so we test this using to_erl
+shell_invalid_ansi(_Config) ->
+
+ InvalidAnsiPrompt =
+ case proplists:get_value(encoding, io:getopts(user)) of
+ unicode ->
+ ["\e]94m",54620,44397,50612,47,51312,49440,47568,"\e]0m"];
+ latin1 ->
+ ["\e]94minvalid_test\e]0m"]
+ end,
+
+ rtnode:run(
+ [{eval, fun() -> application:set_env(
+ stdlib, shell_prompt_func_test,
+ fun() -> InvalidAnsiPrompt end)
+ end },
+ {putline,"a."},
+ {expect, "a[.]"},
+ {expect, ["\\Q",InvalidAnsiPrompt,"\\E"]}],
+ "", "",
+ ["-pz",filename:dirname(code:which(?MODULE)),
+ "-connect_all","false",
+ "-kernel","logger_level","all",
+ "-kernel","shell_history","disabled",
+ "-kernel","prevent_overlapping_partitions","false",
+ "-eval","shell:prompt_func({interactive_shell_SUITE,prompt})."
+ ]).
+
+shell_ignore_pager_commands(Config) ->
+ Term = start_tty(Config),
+ case code:get_doc(file, #{sources=>[eep48]}) of
+ {error, _} -> {skip, "No documentation available"};
+ _ ->
+ try
+ send_tty(Term, "h(file).\n"),
+ check_content(Term,"\\Qmore (y/n)? (y)\\E"),
+ send_tty(Term, "n\n"),
+ check_content(Term,"ok"),
+ send_tty(Term, "C-P"),
+ check_content(Term,"\\Qh(file).\\E"),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end
+ end.
+
+external_editor(Config) ->
+ case os:find_executable("nano") of
+ false -> {skip, "nano is not installed"};
+ _ ->
+ Term = start_tty(Config),
+ try
+ tmux(["resize-window -t ",tty_name(Term)," -x 80"]),
+ send_tty(Term,"os:putenv(\"EDITOR\",\"nano\").\n"),
+ send_tty(Term, "\"some text with\nnewline in it\""),
+ check_content(Term,"3> \"some text with\\s*\n.+\\s*newline in it\""),
+ send_tty(Term, "C-O"),
+ check_content(Term,"GNU nano [\\d.]+"),
+ check_content(Term,"\"some text with\\s*\n\\s*newline in it\""),
+ send_tty(Term, "Right"),
+ send_tty(Term, "still"),
+ send_tty(Term, "Enter"),
+ send_tty(Term, "C-O"), %% save in nano
+ send_tty(Term, "Enter"),
+ send_tty(Term, "C-X"), %% quit in nano
+ check_content(Term,"3> \"still\\s*\n\\s*.+\\s*some text with\\s*\n.+\\s*newline in it\""),
+ send_tty(Term,".\n"),
+ check_content(Term,"\\Q\"still\\nsome text with\\nnewline in it\"\\E"),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end
+ end.
+
+external_editor_visual(Config) ->
+ case os:find_executable("vim") of
+ false ->
+ {skip, "vim is not installed"};
+ _ ->
+ Term = start_tty(Config),
+ try
+ tmux(["resize-window -t ",tty_name(Term)," -x 80"]),
+ send_tty(Term,"os:putenv(\"EDITOR\",\"nano\").\n"),
+ check_content(Term, "3>"),
+ send_tty(Term,"os:putenv(\"VISUAL\",\"vim -u DEFAULTS -U NONE -i NONE\").\n"),
+ check_content(Term, "4>"),
+ send_tty(Term,"\"hello"),
+ send_tty(Term, "C-O"), %% Open vim
+ check_content(Term, "^\"hello"),
+ send_tty(Term, "$"), %% Set cursor at end
+ send_tty(Term, "a"), %% Enter insert mode at end
+ check_content(Term, "-- INSERT --"),
+ send_tty(Term, "\"."),
+ send_tty(Term,"Escape"),
+ send_tty(Term,":wq"),
+ send_tty(Term,"Enter"),
+ check_content(Term, "\"hello\"[.]$"),
+ send_tty(Term,"Enter"),
+ check_content(Term, "\"hello\""),
+ check_content(Term, "5>$")
+ after
+ stop_tty(Term),
+ ok
+ end
+ end.
+
+external_editor_unicode(Config) ->
+ NanoPath = os:find_executable("nano"),
+ case NanoPath of
+ false -> {skip, "nano is not installed"};
+ _ ->
+ Term = start_tty(Config),
+ try
+ tmux(["resize-window -t ",tty_name(Term)," -x 80"]),
+ send_tty(Term,"os:putenv(\"EDITOR\",\"nano\").\n"),
+ send_tty(Term, hard_unicode()),
+ check_content(Term,"3> " ++ hard_unicode_match(Config)),
+ send_tty(Term, "C-O"), %% open external editor (nano)
+ check_content(Term,"GNU nano [\\d.]+"),
+ send_tty(Term, "still "),
+ check_content(Term,"\nstill "),
+ send_tty(Term, "C-O"), %% save in nano
+ send_tty(Term, "Enter"),
+ send_tty(Term, "C-X"), %% quit in nano
+ check_content(Term,"still "++hard_unicode_match(Config)),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end
+ end.
+
+%% There used to be a race condition when using shell:start_interactive where
+%% the newline handling of standard_error did not change correctly to compensate
+%% for the tty changing to canonical mode
+shell_standard_error_nlcr(Config) ->
+
+ [
+ begin
+ Term = setup_tty([{env,[{"TERM",TERM}]},{args, ["-noshell"]} | Config]),
+ try
+ rpc(Term, io, format, [standard_error,"test~ntest~ntest", []]),
+ check_content(Term, "test\ntest\ntest$"),
+ rpc(Term, fun() -> shell:start_interactive(),
+ io:format(standard_error,"test~ntest~ntest", [])
+ end),
+ check_content(Term, "test\ntest\ntest(\n|.)*test\ntest\ntest")
+ after
+ stop_tty(Term)
+ end
+ end || TERM <- ["dumb",os:getenv("TERM")]].
+
+%% We test that suspending of `erl` and then resuming restores the shell
+shell_suspend(Config) ->
+
+ Name = peer:random_name(proplists:get_value(tc_path,Config)),
+ %% In order to suspend `erl` we need it to run in a shell that has job control
+ %% so we start the peer within a tmux window instead of having it be the original
+ %% process.
+ os:cmd("tmux new-window -n " ++ Name ++ " -d -- bash --norc"),
+
+ Peer = #{ name => Name,
+ post_process_args =>
+ fun(["new-window","-n",_,"-d","--"|CmdAndArgs]) ->
+ FlatCmdAndArgs =
+ lists:join(
+ " ",[[$',A,$'] || A <- CmdAndArgs]),
+ ["send","-t",Name,lists:flatten(FlatCmdAndArgs),"Enter"]
+ end
+ },
+
+
+ Term = start_tty([{peer, Peer}|Config]),
+
+ try
+ send_tty(Term, hard_unicode()),
+ check_content(Term,["2> ",hard_unicode(),"$"]),
+ send_tty(Term, "C-Z"),
+ check_content(Term,"\\Q[1]+\\E\\s*Stopped"),
+ send_tty(Term, "fg"),
+ send_tty(Term, "Enter"),
+ send_tty(Term, "M-l"),
+ check_content(Term,["2> ",hard_unicode(),"$"]),
+ check_location(Term,{0,width(hard_unicode())}),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end.
+
+%% We test that suspending of `erl` and then resuming restores the shell
+shell_full_queue(Config) ->
+
+ [throw({skip,"Need unbuffered to run"}) || os:find_executable("unbuffered") =:= false],
+
+ %% In order to fill the read buffer of the terminal we need to get a
+ %% bit creative. We first need to start erl in bash in order to be
+ %% able to get access to job control for suspended processes.
+ %% We then also wrap `erl` in `unbuffer -p` so that we can suspend
+ %% that program in order to block writing to stdout for a while.
+
+ Name = peer:random_name(proplists:get_value(tc_path,Config)),
+ os:cmd("tmux new-window -n " ++ Name ++ " -d -- bash --norc"),
+
+ Peer = #{ name => Name,
+ post_process_args =>
+ fun(["new-window","-n",_,"-d","--"|CmdAndArgs]) ->
+ FlatCmdAndArgs = ["unbuffer -p "] ++
+ lists:join(
+ " ",[[$',A,$'] || A <- CmdAndArgs]),
+ ["send","-t",Name,lists:flatten(FlatCmdAndArgs),"Enter"]
+ end
+ },
+
+
+ Term = start_tty([{peer, Peer}|Config]),
+
+ UnbufferedPid = os:cmd("ps -o ppid= -p " ++ rpc(Term,os,getpid,[])),
+
+ WriteUntilStopped =
+ fun F(Char) ->
+ rpc(Term,io,format,[user,[Char],[]]),
+ put(bytes,get(bytes,0)+1),
+ receive
+ stop ->
+ rpc(Term,io,format,[user,[Char+1],[]])
+ after 0 -> F(Char)
+ end
+ end,
+
+ WaitUntilBlocked =
+ fun(Pid, Ref) ->
+ (fun F(Cnt) ->
+ receive
+ {'DOWN',Ref,_,_,_} = Down ->
+ ct:fail({io_format_did_not_block, Down})
+ after 1000 ->
+ ok
+ end,
+ case process_info(Pid,dictionary) of
+ {dictionary,[{bytes,Cnt}]} ->
+ ct:log("Bytes until blocked: ~p~n",[Cnt]),
+ %% Add one extra byte as for
+ %% the current blocking call
+ Cnt + 1;
+ {dictionary,[{bytes,NewCnt}]} ->
+ F(NewCnt)
+ end
+ end)(0)
+ end,
+
+ try
+ %% First test that we can suspend and then resume
+ os:cmd("kill -TSTP " ++ UnbufferedPid),
+ check_content(Term,"\\Q[1]+\\E\\s*Stopped"),
+ {Pid, Ref} = spawn_monitor(fun() -> WriteUntilStopped($a) end),
+ WaitUntilBlocked(Pid, Ref),
+ send_tty(Term, "fg"),
+ send_tty(Term, "Enter"),
+ Pid ! stop,
+ check_content(Term,"b$"),
+
+ send_tty(Term, "."),
+ send_tty(Term, "Enter"),
+
+ %% Then we test that all characters are written when system
+ %% is terminated just after writing
+ {ok,Cols} = rpc(Term,io,columns,[user]),
+ send_tty(Term, "Enter"),
+ os:cmd("kill -TSTP " ++ UnbufferedPid),
+ check_content(Term,"\\Q[1]+\\E\\s*Stopped"),
+ {Pid2, Ref2} = spawn_monitor(fun() -> WriteUntilStopped($c) end),
+ Bytes = WaitUntilBlocked(Pid2, Ref2) - 1,
+ stop_tty(Term),
+ send_tty(Term, "fg"),
+ send_tty(Term, "Enter"),
+ check_content(
+ fun() ->
+ tmux(["capture-pane -p -S - -E - -t ",tty_name(Term)])
+ end, lists:flatten([lists:duplicate(Cols,$c) ++ "\n" ||
+ _ <- lists:seq(1,(Bytes) div Cols)]
+ ++ [lists:duplicate((Bytes) rem Cols,$c)])),
+ ct:log("~ts",[tmux(["capture-pane -p -S - -E - -t ",tty_name(Term)])]),
+ ok
+ after
+ stop_tty(Term),
+ ok
+ end.
+
+get(Key,Default) ->
+ case get(Key) of
+ undefined ->
+ Default;
+ Value ->
+ Value
+ end.
+
+%% A list of unicode graphemes that are notoriously hard to render
+hard_unicode() ->
+ ZWJ =
+ case os:type() of
+ %% macOS has very good rendering of ZWJ,
+ %% but the cursor does not agree with it..
+ {unix, darwin} -> [];
+ _ -> [[16#1F91A,16#1F3FC]] % Hand with skintone 🤚ðŸ¼
+ end,
+ [[16#1f600], % Smilie 😀
+ "한", % Hangul
+ "Z̤͔ͧ̑̓","ä͖̭̈̇","lͮ̒ͫ","ǧ̗͚̚","o̔ͮ̇Í̙̇" %% Vertically stacked chars
+ %%"👩â€ðŸ‘©", % Zero width joiner
+ %%"👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦" % Zero width joiner
+ | ZWJ].
+
+hard_unicode_match(Config) ->
+ ["\\Q",[unicode_to_octet(Config, U) || U <- hard_unicode()],"\\E"].
+
+unicode_to_octet(Config, U) ->
+ case ?config(encoding,Config) of
+ unicode -> U;
+ latin1 -> unicode_to_octet(U)
+ end.
+
+unicode_to_octet(U) ->
+ [if Byte >= 128 -> [$\\,integer_to_list(Byte,8)];
+ true -> Byte
+ end || <<Byte>> <= unicode:characters_to_binary(U)].
+
+unicode_to_hex(Config, U) ->
+ case ?config(encoding,Config) of
+ unicode -> U;
+ latin1 -> unicode_to_hex(U)
+ end.
+
+unicode_to_hex(U) when is_integer(U) ->
+ unicode_to_hex([U]);
+unicode_to_hex(Us) ->
+ [if U < 128 -> U;
+ U < 512 -> ["\\",integer_to_list(U,8)];
+ true -> ["\\x{",integer_to_list(U,16),"}"]
+ end || U <- Us].
+
+width(C, Str) ->
+ case ?config(encoding, C) of
+ unicode -> width(Str);
+ latin1 -> width(unicode_to_octet(Str))
+ end.
+width(Str) ->
+ lists:sum(
+ [npwcwidth(CP) || CP <- lists:flatten(Str)]).
+
+npwcwidth(CP) ->
+ try prim_tty:npwcwidth(CP)
+ catch error:undef ->
+ if CP =:= 16#D55C ->
+ 2; %% 한
+ CP =:= 16#1f91A ->
+ 2; %% hand
+ CP =:= 16#1F3Fc ->
+ 2; %% Skintone
+ CP =:= 16#1f600 ->
+ 2; %% smilie
+ true ->
+ case lists:member(CP, [775,776,780,785,786,787,788,791,793,794,
+ 804,813,848,852,854,858,871,875,878]) of
+ true ->
+ 0;
+ false ->
+ 1
+ end
+ end
+ end.
+
+tmux([Cmd|_] = Command) when is_list(Cmd) ->
+ tmux(lists:concat(Command));
+tmux(Command) ->
+ string:trim(os:cmd(["tmux ",Command])).
+
+rpc(#tmux{ node = N }, Fun) ->
+ erpc:call(N, Fun).
+rpc(#tmux{ node = N }, M, F, A) ->
+ erpc:call(N, M, F, A).
+
+%% Setup a TTY, but do not type anything in terminal
+setup_tty(Config) ->
+ Name = maps:get(name,proplists:get_value(peer, Config, #{}),
+ peer:random_name(proplists:get_value(tc_path, Config))),
+
+ Envs = lists:flatmap(fun({Key,Value}) ->
+ ["-env",Key,Value]
+ end, proplists:get_value(env,Config,[])),
+
+ ExtraArgs = proplists:get_value(args,Config,[]),
+
+ ExecArgs = case os:getenv("TMUX_DEBUG") of
+ "strace" ->
+ STraceLog = filename:join(proplists:get_value(priv_dir,Config),
+ Name++".strace"),
+ ct:pal("Link to strace: file://~ts", [STraceLog]),
+ [os:find_executable("strace"),"-f",
+ "-o",STraceLog,
+ "-e","trace=all",
+ "-e","read=0,1,2",
+ "-e","write=0,1,2"
+ ] ++ string:split(ct:get_progname()," ",all);
+ "rr" ->
+ [os:find_executable("cerl"),"-rr"];
+ _ ->
+ string:split(ct:get_progname()," ",all)
+ end,
+ DefaultPeerArgs = #{ name => Name,
+ exec =>
+ {os:find_executable("tmux"),
+ ["new-window","-n",Name,"-d","--"] ++ ExecArgs },
+
+ args => ["-pz",filename:dirname(code:which(?MODULE)),
+ "-connect_all","false",
+% "-kernel","logger_level","all",
+ "-kernel","shell_history","disabled",
+ "-kernel","prevent_overlapping_partitions","false",
+ "-eval","shell:prompt_func({interactive_shell_SUITE,prompt})."
+ ] ++ Envs ++ ExtraArgs,
+ detached => false
+ },
+
+ {ok, Peer, Node} =
+ ?CT_PEER(maps:merge(proplists:get_value(peer,Config,#{}),
+ DefaultPeerArgs)),
+
+ Self = self(),
+
+ %% By default peer links with the starter. For these TCs we however only
+ %% want the peer to die if we die, so we create a "unidirection link" using
+ %% monitors.
+ spawn(fun() ->
+ TCRef = erlang:monitor(process, Self),
+ PeerRef = erlang:monitor(process, Peer),
+ receive
+ {'DOWN',TCRef,_,_,Reason} ->
+ exit(Peer, Reason);
+ {'DOWN',PeerRef,_,_,_} ->
+ ok
+ end
+ end),
+ unlink(Peer),
+
+ "" = tmux(["set-option -t ",Name," remain-on-exit on"]),
+
+ %% We start tracing on the remote node in order to help debugging
+ TraceLog = filename:join(proplists:get_value(priv_dir,Config),Name++".trace"),
+ ct:log("Link to trace: file://~ts",[TraceLog]),
+
+ spawn(Node,
+ fun() ->
+ {ok, _} = dbg:tracer(file,TraceLog),
+ %% dbg:p(whereis(user_drv),[c,m]),
+ %% dbg:p(whereis(user_drv_writer),[c,m]),
+ %% dbg:p(whereis(user_drv_reader),[c,m]),
+ %% dbg:tp(user_drv,x),
+ %% dbg:tp(prim_tty,x),
+ %% dbg:tpl(prim_tty,write_nif,x),
+ %% dbg:tpl(prim_tty,read_nif,x),
+ monitor(process, Self),
+ receive _ -> ok end
+ end),
+
+ #tmux{ peer = Peer, node = Node, name = Name }.
+
+%% Start a tty, setup custom prompt and set cursor at bottom
+start_tty(Config) ->
+
+ Term = setup_tty(Config),
+
+ Prompt = fun() -> ["\e[94m",54620,44397,50612,47,51312,49440,47568,"\e[0m"] end,
+ erpc:call(Term#tmux.node, application, set_env,
+ [stdlib, shell_prompt_func_test,
+ proplists:get_value(shell_prompt_func_test, Config, Prompt)]),
+
+ {Rows, _} = get_window_size(Term),
+
+ %% We send a lot of newlines here in order for the number of rows
+ %% in the window to be max so that we can predict what the cursor
+ %% position is.
+ [send_tty(Term,"\n") || _ <- lists:seq(1, Rows)],
+
+ %% We enter an 'a' here so that we can get the correct orig position
+ %% with an alternative prompt.
+ send_tty(Term,"a.\n"),
+ check_content(Term,"2>$"),
+ OrigLocation = get_location(Term),
+ Term#tmux{ orig_location = OrigLocation }.
+
+prompt(L) ->
+ N = proplists:get_value(history, L, 0),
+ Fun = application:get_env(stdlib, shell_prompt_func_test,
+ fun() -> atom_to_list(node()) end),
+ io_lib:format("(~ts)~w> ",[Fun(),N]).
+
+stop_tty(Term) ->
+ catch peer:stop(Term#tmux.peer),
+ ct:log("~ts",[get_content(Term, "-e")]),
+% "" = tmux("kill-window -t " ++ Term#tmux.name),
+ ok.
+
+tty_name(Term) ->
+ Term#tmux.name.
+
+send_tty(Term, "Home") ->
+ %% https://stackoverflow.com/a/55616731
+ send_tty(Term,"Escape"),
+ send_tty(Term,"OH");
+send_tty(Term, "End") ->
+ send_tty(Term,"Escape"),
+ send_tty(Term,"OF");
+send_tty(#tmux{ name = Name } = _Term,Value) ->
+ [Head | Quotes] = string:split(Value, "'", all),
+ "" = tmux("send -t " ++ Name ++ " '" ++ Head ++ "'"),
+ [begin
+ "" = tmux("send -t " ++ Name ++ " \"'\""),
+ "" = tmux("send -t " ++ Name ++ " '" ++ V ++ "'")
+ end || V <- Quotes].
+
+%% We use send_stdin for testing of things that we cannot sent via
+%% the tmux send command, such as invalid unicode
+send_stdin(Term, Chars) when is_binary(Chars) ->
+ rpc(Term,erlang,display_string,[stdin,Chars]);
+send_stdin(Term, Chars) ->
+ send_stdin(Term, iolist_to_binary(unicode:characters_to_binary(Chars))).
+
+check_location(Term, Where) ->
+ check_location(Term, Where, 5).
+check_location(#tmux{ orig_location = {OrigRow, OrigCol} = Orig } = Term,
+ {AdjRow, AdjCol} = Where, Attempt) ->
+ NewLocation = get_location(Term),
+ case {OrigRow+AdjRow,OrigCol+AdjCol} of
+ NewLocation -> NewLocation;
+ _ when Attempt =:= 0 ->
+ {NewRow, NewCol} = NewLocation,
+ ct:fail({wrong_location, {expected,{AdjRow, AdjCol}},
+ {got,{NewRow - OrigRow, NewCol - OrigCol},
+ {NewLocation, Orig}}});
+ _ ->
+ timer:sleep(50),
+ check_location(Term, Where, Attempt -1)
+ end.
+
+get_location(Term) ->
+ RowAndCol = tmux("display -pF '#{cursor_y} #{cursor_x}' -t "++Term#tmux.name),
+ [Row, Col] = string:lexemes(string:trim(RowAndCol,both)," "),
+ {list_to_integer(Row), list_to_integer(Col)}.
+
+get_window_size(Term) ->
+ RowAndCol = tmux("display -pF '#{window_height} #{window_width}' -t "++Term#tmux.name),
+ [Row, Col] = string:lexemes(string:trim(RowAndCol,both)," "),
+ {list_to_integer(Row), list_to_integer(Col)}.
+
+check_content(Term, Match) ->
+ check_content(Term, Match, #{}).
+check_content(Term, Match, Opts) when is_map(Opts) ->
+ check_content(Term, Match, Opts, 5).
+check_content(Term, Match, Opts, Attempt) ->
+ OrigContent = case Term of
+ #tmux{} -> get_content(Term);
+ Fun when is_function(Fun,0) -> Fun()
+ end,
+ Content = case maps:find(replace, Opts) of
+ {ok, {RE,Repl} } ->
+ re:replace(OrigContent, RE, Repl, [global]);
+ error ->
+ OrigContent
+ end,
+ case re:run(string:trim(Content, both), lists:flatten(Match), [unicode]) of
+ {match,_} ->
+ ok;
+ _ when Attempt =:= 0 ->
+ io:format("Failed to find '~ts' in ~n'~ts'~n",
+ [unicode:characters_to_binary(Match), Content]),
+ io:format("Failed to find '~w' in ~n'~w'~n",
+ [unicode:characters_to_binary(Match), Content]),
+ ct:fail(nomatch);
+ _ ->
+ timer:sleep(500),
+ check_content(Term, Match, Opts, Attempt - 1)
+ end.
+
+get_content(Term) ->
+ get_content(Term, "").
+get_content(#tmux{ name = Name }, Args) ->
+ Content = unicode:characters_to_binary(tmux("capture-pane -p " ++ Args ++ " -t " ++ Name)),
+ case string:split(Content,"a.\na") of
+ [_Ignore,C] ->
+ C;
+ [C] ->
+ C
+ end.
%% Tests that exit of initial shell restarts shell.
exit_initial(Config) when is_list(Config) ->
@@ -260,36 +1719,38 @@ exit_initial(Config) when is_list(Config) ->
ok.
test_exit_initial(old) ->
- rtnode([{putline, ""},
- {putline, "2."},
- {expect, "2\r\n"},
- {putline, "exit()."},
- {expect, "Eshell"},
- {putline, ""},
- {putline, "35."},
- {expect, "35\r\n"}],
- [], [], ["-oldshell"]);
+ rtnode:run(
+ [{putline, ""},
+ {putline, "2."},
+ {expect, "2\r\n"},
+ {putline, "exit()."},
+ {expect, "Eshell"},
+ {putline, ""},
+ {putline, "35."},
+ {expect, "35\r\n"}],
+ [], [], ["-oldshell"]);
test_exit_initial(new) ->
- rtnode([{putline, ""},
- {expect, "1> $"},
- {putline, "2."},
- {expect, "2"},
- {putline,"exit()."},
- {expect, "Eshell"},
- {expect, "1> $"},
- {putline, "35."},
- {expect, "35\r\n"}]).
+ rtnode:run(
+ [{putline, ""},
+ {expect, "1> $"},
+ {putline, "2."},
+ {expect, "2"},
+ {putline,"exit()."},
+ {expect, "Eshell"},
+ {expect, "1> $"},
+ {putline, "35."},
+ {expect, "35\r\n"}]).
stop_during_init(Config) when is_list(Config) ->
- {RunErl,_ToErl,Erl} = get_progs(),
- case create_tempdir() of
+ {RunErl,_ToErl,[Erl|ErlArgs]} = rtnode:get_progs(),
+ case rtnode:create_tempdir() of
{error, Reason} ->
{skip, Reason};
Tempdir ->
XArg = " -kernel shell_history enabled -s init stop",
- start_runerl_command(RunErl, Tempdir, "\\\""++Erl++"\\\""++XArg),
- Logs = rtnode_read_logs(Tempdir),
- rtnode_dump_logs(Logs),
+ rtnode:start_runerl_command(RunErl, Tempdir, "\\\""++Erl++"\\\""++ErlArgs++XArg),
+ Logs = rtnode:read_logs(Tempdir),
+ rtnode:dump_logs(Logs),
nomatch = binary:match(map_get("erlang.log.1", Logs),
<<"*** ERROR: Shell process terminated! ***">>),
ok
@@ -312,16 +1773,16 @@ wrap(Config) when is_list(Config) ->
case proplists:get_value(default_shell, Config) of
new ->
As = lists:duplicate(20,"a"),
- rtnode([{putline, "io:columns()."},
- {expect, "{ok,20}\r\n"},
- {putline, ["io:format(\"~s\",[lists:duplicate(20,\"a\")])."]},
- {expect, As ++ " \b"},
- {putline, ["io:format(\"~s~n~s\",[lists:duplicate(20,\"a\"),lists:duplicate(20,\"a\")])."]},
- {expect, As ++ "\r\n" ++ As ++ " \b"}
- ],
- [],
- "stty rows 40; stty columns 20; ",
- [""]);
+ rtnode:run(
+ [{putline, "io:columns()."},
+ {expect, "{ok,20}\r\n"},
+ {putline, ["io:format(\"~s\",[lists:duplicate(20,\"a\")])."]},
+ {expect, As ++ " \b"},
+ {putline, ["io:format(\"~s~n~s\",[lists:duplicate(20,\"a\"),lists:duplicate(20,\"a\")])."]},
+ {expect, As ++ "\r\n" ++ As ++ " \b"}
+ ],
+ [],
+ "stty rows 40; stty columns 20; ");
_ ->
ok
end,
@@ -336,58 +1797,71 @@ wrap(Config) when is_list(Config) ->
%% commands.
shell_history(Config) when is_list(Config) ->
Path = shell_history_path(Config, "basic"),
- rtnode([
- {putline, "echo1."},
- {expect, "echo1\r\n"},
- {putline, "echo2."},
- {expect, "echo2\r\n"},
- {putline, "echo3."},
- {expect, "echo3\r\n"},
- {putline, "echo4."},
- {expect, "echo4\r\n"},
- {putline, "echo5."},
- {expect, "echo5\r\n"}
- ], [], [], " -kernel shell_history enabled " ++
- "-kernel shell_history_drop '[\\\"init:stop().\\\"]' " ++
- mk_sh_param(Path)),
+ rtnode:run(
+ [{putline, "echo1."},
+ {expect, "echo1\r\n"},
+ {putline, "echo2."},
+ {expect, "echo2\r\n"},
+ {putline, "echo3."},
+ {expect, "echo3\r\n"},
+ {putline, "echo4."},
+ {expect, "echo4\r\n"},
+ {putline, "echo5."},
+ {expect, "echo5\r\n"}
+ ], [], [], mk_history_param(Path)),
+ receive after 1000 -> ok end,
+ rtnode:run(
+ [{sleep,100},
+ {putline, ""},
+ %% the init:stop that stopped the node is dropped
+ {putdata, [$\^p]}, {expect, "echo5[.]$"},
+ {putdata, [$\n]},
+ {expect, "echo5\r\n"},
+ {putdata, [$\^p]}, {expect, "echo5[.]$"},
+ {putdata, [$\^p]}, {expect, "echo4[.]$"},
+ {putdata, [$\^p]}, {expect, "echo3[.]$"},
+ {putdata, [$\^p]}, {expect, "echo2[.]$"},
+ {putdata, [$\^n]}, {expect, "echo3[.]$"},
+ {putdata, [$\^n]}, {expect, "echo4[.]$"},
+ {putdata, [$\^b]}, {sleep,50}, %% the echo4. (cursor moved one left)
+ {putline, ["ECHO"]},
+ {expect, "echo4ECHO\r\n"}
+ ], [], [],
+ mk_history_param(Path)),
receive after 1000 -> ok end,
- rtnode([
- {putline, ""},
- %% the init:stop that stopped the node is dropped
- {putdata, [$\^p]}, {expect, "echo5[.]$"},
- {putdata, [$\n]},
- {expect, "echo5\r\n"},
- {putdata, [$\^p]}, {expect, "echo5[.]$"},
- {putdata, [$\^p]}, {expect, "echo4[.]$"},
- {putdata, [$\^p]}, {expect, "echo3[.]$"},
- {putdata, [$\^p]}, {expect, "echo2[.]$"},
- {putdata, [$\^n]}, {expect, "echo3[.]$"},
- {putdata, [$\^n]}, {expect, "echo4[.]$"},
- {putdata, [$\^b]}, {sleep,50}, %% the echo4. (cursor moved one left)
- {putline, ["ECHO"]},
- {expect, "echo4ECHO\r\n"}
- ], [], [], " -kernel shell_history enabled " ++ mk_sh_param(Path)),
+
+ %% ignore input given after io:get_line, and after h(module) pager
+ rtnode:run(
+ [{putdata, "io:get_line(\"getline>\").\n"},
+ {expect, "getline>"},
+ {putline, "hej"}, {expect, "hej\r\n"},
+ {putdata, [$\^p]}, {expect, "\\Qio:get_line(\"getline>\")\\E[.]$"}
+ ], [], [],
+ mk_history_param(Path)),
ok.
shell_history_resize(Config) ->
Path = shell_history_path(Config, "resize"),
- rtnode([
- {putline, "echo."},
- {expect, "echo\r\n"}
- ], [], [], " -kernel shell_history_file_bytes 123456 " ++
- "-kernel shell_history enabled " ++ mk_sh_param(Path)),
+ rtnode:run(
+ [{putline, "echo."},
+ {expect, "echo\r\n"},
+ {putline, "echo2."},
+ {expect, "echo2\r\n"}
+ ], [], [], ["-kernel","shell_history_file_bytes","123456"] ++
+ mk_history_param(Path)),
{ok, Logs} =
- rtnode([
- {putline, ""},
- {putdata, [$\^p]}, {expect, "init:stop\\(\\)[.]$"},
- {putdata, [$\^p]}, {expect, "echo[.]$"},
- {putdata, [$\n]},
- {expect, "echo"}
- ], [], [], " -kernel shell_history_file_bytes 654321 " ++
- "-kernel shell_history enabled " ++ mk_sh_param(Path)),
-
- rtnode_check_logs(
+ rtnode:run(
+ [{sleep,100},
+ {putline, ""},
+ {putdata, [$\^p]}, {expect, "echo2[.]$$"},
+ {putdata, [$\^p]}, {expect, "echo[.]$"},
+ {putdata, [$\n]},
+ {expect, "echo"}
+ ], [], [], ["-kernel","shell_history_file_bytes","654321"] ++
+ mk_history_param(Path)),
+
+ rtnode:check_logs(
"erlang.log.1",
"The configured log history file size is different from the size "
"of the log file on disk", Logs),
@@ -405,24 +1879,24 @@ shell_history_eaccess(Config) ->
%% Cannot create history log in folder
{ok, Logs1} =
- rtnode([
- {putline, "echo."},
- {expect, "echo\r\n"}
- ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)),
+ rtnode:run(
+ [{putline, "echo."},
+ {expect, "echo\r\n"}
+ ], [], [], mk_history_param(Path)),
- rtnode_check_logs("erlang.log.1", "Error handling file", Logs1),
+ ct:pal("~p",[Logs1]),
+ rtnode:check_logs("erlang.log.1", "Error handling file", Logs1),
%% shell_docs recursively creates the folder to store the
%% logs. This test checks that erlang still starts if we
%% cannot create the folders to the path.
{ok, Logs2} =
- rtnode([
- {putline, "echo."},
- {expect, "echo\r\n"}
- ], [], [], "-kernel shell_history enabled " ++
- mk_sh_param(filename:join(Path,"logs"))),
+ rtnode:run(
+ [{putline, "echo."},
+ {expect, "echo\r\n"}
+ ], [], [], mk_history_param(filename:join(Path,"logs"))),
- rtnode_check_logs("erlang.log.1", "Error handling file", Logs2)
+ rtnode:check_logs("erlang.log.1", "Error handling file", Logs2)
after
file:write_file_info(Path, Info)
@@ -436,15 +1910,15 @@ shell_history_repair(Config) ->
shell_history_halt(Path),
{ok, Logs} =
- rtnode([
- {putline, ""},
- {putdata, [$\^p]}, {expect, "echo[.]$"},
- {putdata, [$\n]},
- {expect, "echo\r\n"}
- ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)),
+ rtnode:run(
+ [{putline, ""},
+ {putdata, [$\^p]}, {expect, "echo[.]$"},
+ {putdata, [$\n]},
+ {expect, "echo\r\n"}
+ ], [], [], mk_history_param(Path)),
%% The regexp below checks that he string is NOT part of the log
- rtnode_check_logs("erlang.log.1",
+ rtnode:check_logs("erlang.log.1",
"The shell history log file was corrupted and was repaired",
false,
Logs),
@@ -462,14 +1936,14 @@ shell_history_repair_corrupt(Config) ->
ok = file:close(D),
{ok, Logs} =
- rtnode([
- {putline, ""},
- {putdata, [$\^p]}, {expect, "echo[.]$"},
- {putdata, [$\n]},
- {expect, "echo\r\n"}
- ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)),
-
- rtnode_check_logs("erlang.log.1",
+ rtnode:run(
+ [{putline, ""},
+ {putdata, [$\^p]}, {expect, "echo[.]$"},
+ {putdata, [$\n]},
+ {expect, "echo\r\n"}
+ ], [], [], mk_history_param(Path)),
+
+ rtnode:check_logs("erlang.log.1",
"The shell history log file was corrupted and was repaired.",
Logs),
ok.
@@ -478,9 +1952,12 @@ shell_history_corrupt(Config) ->
Path = shell_history_path(Config, "corrupt"),
%% We initialize the shell history log with a known value.
- rtnode([{putline, "echo."},
- {expect, "echo\r\n"}
- ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)),
+ rtnode:run(
+ [{putline, "echo."},
+ {expect, "echo\r\n"},
+ {putline, "echo2."},
+ {expect, "echo2\r\n"}
+ ], [], [], mk_history_param(Path)),
%% We corrupt the disklog.
{ok, D} = file:open(filename:join(Path,"erlang-shell-log.1"), [read, append]),
@@ -488,98 +1965,104 @@ shell_history_corrupt(Config) ->
ok = file:close(D),
{ok, Logs} =
- rtnode([
- {putline, ""},
- {putdata, [$\^p]}, {expect, "init:stop\\(\\)[.]$"},
- {putdata, [$\^p]}, {expect, "echo[.]$"},
- {putdata, [$\n]},
- {expect, "echo\r\n"}
- ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path)),
-
- rtnode_check_logs("erlang.log.1", "Invalid chunk in the file", Logs),
+ rtnode:run(
+ [{putline, ""},
+ {putdata, [$\^p]}, {expect, "echo2[.]$"},
+ {putdata, [$\^p]}, {expect, "echo[.]$"},
+ {putdata, [$\n]},
+ {expect, "echo\r\n"}
+ ], [], [], mk_history_param(Path)),
+
+ rtnode:check_logs("erlang.log.1", "Invalid chunk in the file", Logs),
ok.
%% Stop the node without closing the log.
shell_history_halt(Path) ->
try
- rtnode([
- {putline, "echo."},
- {expect, "echo\r\n"},
- {sleep, 2500}, % disk_log internal cache timer is 2000 ms
- {putline, "halt(0)."}
- ], [], [], "-kernel shell_history enabled " ++ mk_sh_param(Path))
+ rtnode:run(
+ [{putline, "echo."},
+ {expect, "echo\r\n"},
+ {sleep, 2500}, % disk_log internal cache timer is 2000 ms
+ {putline, "halt(0)."},
+ {expect, "\r\n"},
+ {sleep, 1000} %% wait for node to terminate
+ ], [], [], mk_history_param(Path))
catch
_:_ ->
ok
end.
shell_history_path(Config, TestCase) ->
- filename:join([proplists:get_value(priv_dir, Config),
- "shell_history", TestCase]).
+ filename:join([proplists:get_value(priv_dir, Config),
+ "shell_history", TestCase]).
-mk_sh_param(Path) ->
- "-kernel shell_history_path '\\\"" ++ Path ++ "\\\"'".
+mk_history_param(Path) ->
+ ["-kernel","shell_history","enabled",
+ "-kernel","shell_history_path","\"" ++ Path ++ "\"",
+ "-kernel","shell_history_drop","[\"init:stop().\"]"
+ ].
shell_history_custom(_Config) ->
%% Up key: Ctrl + P = Cp=[$\^p]
- rtnode([{expect, "1> $"},
- %% {putline, ""},
- {putdata, [$\^p]}, {expect, "0[.]"},
- {putdata, [$\n]},
- {expect, "0\r\n"},
- {putline, "echo."},
- {expect, "!echo\r\n"} % exclamation mark is printed by custom history module
- ], [], [], " -kernel shell_history " ++ atom_to_list(?MODULE) ++
- " -pz " ++ filename:dirname(code:which(?MODULE))),
+ rtnode:run(
+ [{expect, "1> $"},
+ %% {putline, ""},
+ {putdata, [$\^p]}, {expect, "0[.]"},
+ {putdata, [$\n]},
+ {expect, "0\r\n"},
+ {putline, "echo."},
+ {expect, "!echo\r\n"} % exclamation mark is printed by custom history module
+ ], [], [], ["-kernel","shell_history",atom_to_list(?MODULE),
+ "-pz",filename:dirname(code:which(?MODULE))]),
ok.
shell_history_custom_errors(_Config) ->
%% Check that we can start with a node with an undefined
%% provider module.
- rtnode([{expect, "1> $"},
- {putline, "echo."},
- {expect, "echo\r\n"}
- ], [], [], " -kernel shell_history very_broken " ++
- " -pz " ++ filename:dirname(code:which(?MODULE))),
+ rtnode:run(
+ [{putline, "echo."},
+ {expect, "echo\r\n"}
+ ], [], [], ["-kernel","shell_history","very_broken",
+ "-pz",filename:dirname(code:which(?MODULE))]),
%% Check that we can start with a node with a provider module
%% that crashes in load/0.
- rtnode([
- {putline, "echo."},
- {expect, "echo\r\n"}
- ], [], [], " -kernel shell_history " ++ atom_to_list(?MODULE) ++
- " -kernel provider_load crash" ++
- " -pz " ++ filename:dirname(code:which(?MODULE))),
+ rtnode:run(
+ [{putline, "echo."},
+ {expect, "echo\r\n"}
+ ], [], [], ["-kernel","shell_history",atom_to_list(?MODULE),
+ "-kernel","provider_load","crash",
+ "-pz",filename:dirname(code:which(?MODULE))]),
%% Check that we can start with a node with a provider module
%% that return incorrect in load/0.
- rtnode([
- {putline, "echo."},
- {expect, "echo\r\n"}
- ], [], [], " -kernel shell_history " ++ atom_to_list(?MODULE) ++
- " -kernel provider_load badreturn" ++
- " -pz " ++ filename:dirname(code:which(?MODULE))),
+ rtnode:run(
+ [{putline, "echo."},
+ {expect, "echo\r\n"}
+ ], [], [], ["-kernel","shell_history",atom_to_list(?MODULE),
+ "-kernel","provider_load","badreturn",
+ "-pz",filename:dirname(code:which(?MODULE))]),
%% Check that we can start with a node with a provider module
%% that crashes in load/0.
- rtnode([
- {putline, "echo."},
- {expect, "Disabling shell history logging.\r\n"},
- {expect, "echo\r\n"}
- ], [], [], " -kernel shell_history " ++ atom_to_list(?MODULE) ++
- " -kernel provider_add crash" ++
- " -pz " ++ filename:dirname(code:which(?MODULE))),
+ rtnode:run(
+ [{putline, "echo."},
+ {expect, "(Disabling shell history logging.|echo)"},
+ {expect, "(Disabling shell history logging.|echo)"}
+ ], [], [], ["-kernel","shell_history",atom_to_list(?MODULE),
+ "-kernel","provider_add","crash",
+ "-pz",filename:dirname(code:which(?MODULE))]),
%% Check that we can start with a node with a provider module
%% that return incorrect in load/0.
- rtnode([
- {putline, "echo."},
- {expect, "It returned {error,badreturn}.\r\n"},
- {expect, "echo\r\n"}
- ], [], [], " -kernel shell_history " ++ atom_to_list(?MODULE) ++
- " -kernel provider_add badreturn" ++
- " -pz " ++ filename:dirname(code:which(?MODULE))),
+ rtnode:run(
+ [{putline, "echo."},
+ {expect, "It returned {error,badreturn}.\r\n"},
+ {expect, "echo\r\n"}
+ ], [], [], ["-kernel","shell_history",atom_to_list(?MODULE),
+ "-kernel","provider_add","badreturn",
+ "-pz",filename:dirname(code:which(?MODULE))]),
ok.
@@ -613,20 +2096,60 @@ job_control_local(Config) when is_list(Config) ->
{skip,"No new shell found"};
new ->
%% New shell tests
- rtnode([{putline, ""},
- {expect, "1> $"},
- {putline, "2."},
- {expect, "\r\n2\r\n"},
- {putline, "\^g"},
- {expect, ["--> $"]},
- {putline, "s"},
- {expect, ["--> $"]},
- {putline, "c"},
- {expect, ["\r\nEshell"]},
- {expect, ["1> $"]},
- {putline, "35."},
- {expect, "\r\n35\r\n2> $"}],
- []),
+ rtnode:run(
+ [{putline, ""},
+ {expect, "1> $"},
+ {putline, "2."},
+ {expect, "\r\n2\r\n"},
+ {putline, "\^g"},
+ {expect, "--> $"},
+ {putline, "s"},
+ {expect, "--> $"},
+ {putline, "c"},
+ {expect, "\r\nEshell"},
+ {expect, "1> $"},
+ {putline, "35."},
+ {expect, "\r\n35\r\n"},
+ {expect, "2> $"},
+ {putline, "receive M -> M end.\r\n"},
+ {putline, "\^g"},
+ {expect, "--> $"},
+ {putline, "i 3"},
+ {expect, "Unknown job"},
+ {expect, "--> $"},
+ {putline, "i 2"},
+ {expect, "--> $"},
+ {putline, "c"},
+ {expect, "[*][*] exception exit: killed"},
+ {expect, "[23]>"},
+ {putline, "\^g"},
+ {expect, "--> $"},
+ {putline, "k 3"},
+ {expect, "Unknown job"},
+ {expect, "--> $"},
+ {putline, "k 2"},
+ {expect, "--> $"},
+ {putline, "k"},
+ {expect, "Unknown job"},
+ {expect, "--> $"},
+ {putline, "c"},
+ {expect, "Unknown job"},
+ {expect, "--> $"},
+ {putline, "i"},
+ {expect, "Unknown job"},
+ {expect, "--> $"},
+ {putline, "?"},
+ {expect, "this message"},
+ {expect, "--> $"},
+ {putline, "h"},
+ {expect, "this message"},
+ {expect, "--> $"},
+ {putline, "c 1"},
+ {expect, "\r\n"},
+ {putline, "35."},
+ {expect, "\r\n35\r\n"},
+ {expect, "[23]> $"}
+ ]),
ok
end.
@@ -636,11 +2159,12 @@ job_control_remote(Config) when is_list(Config) ->
old ->
{skip,"No new shell found"};
_ ->
- NSNode = start_node(?FUNCTION_NAME, []),
+ {ok, Peer, NSNode} = ?CT_PEER(#{ args => ["-connect_all","false"],
+ peer_down => continue }),
try
test_remote_job_control(NSNode)
after
- test_server:stop_node(NSNode)
+ peer:stop(Peer)
end
end.
@@ -651,48 +2175,69 @@ job_control_remote_noshell(Config) when is_list(Config) ->
old ->
{skip,"No new shell found"};
_ ->
- NSNode = start_node(?FUNCTION_NAME, ["-noshell"]),
+ {ok, Peer, NSNode} = ?CT_PEER(#{ name => peer:random_name(test_remote_job_control),
+ args => ["-connect_all","false","-noshell"],
+ peer_down => continue }),
try
test_remote_job_control(NSNode)
after
- test_server:stop_node(NSNode)
+ peer:stop(Peer)
end
end.
test_remote_job_control(Node) ->
- RemNode = create_nodename(),
+ RemNode = peer:random_name(test_remote_job_control),
Pid = spawn_link(Node, fun() ->
receive die ->
ok
end
- end),
+ end),
PidStr = erpc:call(Node, erlang, pid_to_list, [Pid]),
true = erpc:call(Node, erlang, register, [kalaskula,Pid]),
PrintedNode = printed_atom(Node),
CookieString = printed_atom(erlang:get_cookie()),
- rtnode([{putline, ""},
- {putline, "erlang:get_cookie()."},
- {expect, "\r\n\\Q" ++ CookieString ++ "\\E"},
- {putdata, "\^g"},
- {expect, " --> $"},
- {putline, "r " ++ PrintedNode},
- {expect, "\r\n"},
- {putline, "c"},
- {expect, "\r\n"},
- {expect, "Eshell"},
- {expect, "\\Q(" ++ atom_to_list(Node) ++")1> \\E$"},
- {putline, "whereis(kalaskula)."},
- {expect, PidStr},
- {putline, "exit()."},
- {expect, "[*][*][*] Shell process terminated!"},
- {putdata, "\^g"},
- {expect, " --> $"},
- {putline, "c 1"},
- {expect, "\r\n"},
- {putline, ""},
- {expect, "\\Q("++RemNode++")\\E[12]> $"}
- ], RemNode),
+ rtnode:run(
+ [{putline, ""},
+ {putline, "erlang:get_cookie()."},
+ {expect, "\r\n\\Q" ++ CookieString ++ "\\E"},
+ {putdata, "\^g"},
+ {expect, " --> $"},
+ {putline, "r " ++ PrintedNode},
+ {expect, "\r\n"},
+ {putline, "j"},
+ {expect, "1 {shell,start,\\[init]}"},
+ {expect, "2[*] {\\Q"++PrintedNode++"\\E,shell,start,\\[]}"},
+ {expect, " --> $"},
+ {putline, "c"},
+ {expect, "\r\n"},
+ {expect, "Eshell"},
+ {expect, "\\Q(" ++ atom_to_list(Node) ++")1> \\E$"},
+ {putline, "whereis(kalaskula)."},
+ {expect, PidStr},
+ {putline, "kalaskula ! die."},
+ {putline, "exit()."},
+ {expect, "[*][*][*] Shell process terminated!"},
+ {putdata, "\^g"},
+ {expect, " --> $"},
+ {putline, "j"},
+ {expect, "1 {shell,start,\\[init]}"},
+ {expect, " --> $"},
+ {putline, "c"},
+ {expect, "Unknown job"},
+ {expect, " --> $"},
+ {putline, "c 1"},
+ {expect, "\\Q("++RemNode++"@\\E[^)]*\\)[12]> $"},
+ {putdata, "\^g"},
+ {expect, " --> $"},
+ {putline, "j"},
+ {expect, "1[*] {shell,start,\\[init]}"},
+ {putline, "c"},
+ {expect, "\\Q("++RemNode++"@\\E[^)]*\\)[123]> $"},
+ {sleep, 100},
+ {putline, "35."},
+ {expect, "\\Q("++RemNode++"@\\E[^)]*\\)[123]> $"}
+ ], RemNode),
Pid ! die,
ok.
@@ -703,20 +2248,21 @@ ctrl_keys(_Config) ->
Cy = [$\^y],
Home = [27,$O,$H],
End = [27,$O,$F],
- rtnode([{putline,""},
- {putline,"2."},
- {expect,"2"},
- {putline,"\"hello "++Cw++"world\"."}, % test <CTRL>+W
- {expect,"\"world\""},
- {putline,"\"hello "++Cu++"\"world\"."}, % test <CTRL>+U
- {expect,"\"world\""},
- {putline,"world\"."++Home++"\"hello "}, % test <HOME>
- {expect,"\"hello world\""},
- {putline,"world"++Home++"\"hello "++End++"\"."}, % test <END>
- {expect,"\"hello world\""},
- {putline,"\"hello world\""++Cu++Cy++"."},
- {expect,"\"hello world\""}] ++
- wordLeft() ++ wordRight(), []),
+ rtnode:run(
+ [{putline,""},
+ {putline,"2."},
+ {expect,"2"},
+ {putline,"\"hello "++Cw++"world\"."}, % test <CTRL>+W
+ {expect,"\"world\""},
+ {putline,"\"hello "++Cu++"\"world\"."}, % test <CTRL>+U
+ {expect,"\"world\""},
+ {putline,"world\"."++Home++"\"hello "}, % test <HOME>
+ {expect,"\"hello world\""},
+ {putline,"world"++Home++"\"hello "++End++"\"."}, % test <END>
+ {expect,"\"hello world\""},
+ {putline,"\"hello world\""++Cu++Cy++"."},
+ {expect,"\"hello world\""}] ++
+ wordLeft() ++ wordRight()),
ok.
wordLeft() ->
@@ -743,7 +2289,7 @@ wordRight(Chars) ->
%% Test that -remsh works
remsh_basic(Config) when is_list(Config) ->
- TargetNode = start_node(?FUNCTION_NAME, []),
+ {ok, Peer, TargetNode} = ?CT_PEER(),
TargetNodeStr = printed_atom(TargetNode),
[_Name,Host] = string:split(atom_to_list(node()), "@"),
@@ -756,19 +2302,30 @@ remsh_basic(Config) when is_list(Config) ->
%% Test that remsh works with explicit -sname.
HostNode = atom_to_list(?FUNCTION_NAME) ++ "_host",
HostNodeStr = printed_atom(list_to_atom(HostNode ++ "@" ++ Host)),
- rtnode(PreCmds ++
- [{putline,"nodes()."},
- {expect, "\\Q" ++ HostNodeStr ++ "\\E"}] ++
- PostCmds,
- HostNode, [], "-remsh " ++ TargetNodeStr),
+ rtnode:run(
+ PreCmds ++
+ [{putline,"nodes()."},
+ {expect, "\\Q" ++ HostNodeStr ++ "\\E"}] ++
+ PostCmds,
+ HostNode, " ", "-remsh " ++ TargetNodeStr),
%% Test that remsh works without -sname.
- rtnode(PreCmds ++ PostCmds, [], [], " -remsh " ++ TargetNodeStr),
+ rtnode:run(PreCmds ++ PostCmds, [], " ", "-remsh " ++ TargetNodeStr),
- test_server:stop_node(TargetNode),
+ %% Test that if multiple remsh are given, we select the first
+ rtnode:run([{expect, "Multiple"}] ++ PreCmds ++ PostCmds,
+ [], " ",
+ "-remsh " ++ TargetNodeStr ++ " -remsh invalid_node"),
+
+ peer:stop(Peer),
ok.
+%% Test that if we cannot connect to a node, we get a correct error
+remsh_error(_Config) ->
+ "Could not connect to \"invalid_node\"\n" =
+ os:cmd(ct:get_progname() ++ " -remsh invalid_node").
+
quit_hosting_node() ->
%% Command sequence for entering a shell on the hosting node.
[{putdata, "\^g"},
@@ -788,529 +2345,108 @@ remsh_longnames(Config) when is_list(Config) ->
"@127.0.0.1";
_ -> ""
end,
- case rtstart(" -name " ++ atom_to_list(?FUNCTION_NAME)++Domain) of
- {ok, _SRPid, STPid, SState} ->
+ Name = peer:random_name(?FUNCTION_NAME),
+ case rtnode:start(" -name " ++ Name ++ Domain) of
+ {ok, _SRPid, STPid, SNode, SState} ->
try
- {ok, _CRPid, CTPid, CState} =
- rtstart("-name undefined" ++ Domain ++
- " -remsh " ++ atom_to_list(?FUNCTION_NAME)),
+ {ok, _CRPid, CTPid, CNode, CState} =
+ rtnode:start("-name undefined" ++ Domain ++
+ " -remsh " ++ Name),
try
- ok = send_commands(
+ ok = rtnode:send_commands(
+ SNode,
STPid,
[{putline, ""},
{putline, "node()."},
- {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"}], 1),
- ok = send_commands(
+ {expect, "\\Q" ++ Name ++ "\\E"}], 1),
+ ok = rtnode:send_commands(
+ CNode,
CTPid,
[{putline, ""},
{putline, "node()."},
- {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"} | quit_hosting_node()], 1)
+ {expect, "\\Q" ++ Name ++ "\\E"} | quit_hosting_node()], 1)
after
- rtnode_dump_logs(rtstop(CState))
+ rtnode:dump_logs(rtnode:stop(CState))
end
after
- rtnode_dump_logs(rtstop(SState))
+ rtnode:dump_logs(rtnode:stop(SState))
end;
- Else ->
+ {skip, _} = Else ->
Else
end.
%% Test that -remsh works without epmd.
remsh_no_epmd(Config) when is_list(Config) ->
- EPMD_ARGS = "-start_epmd false -erl_epmd_port 12345 ",
- case rtstart([],"ERL_EPMD_PORT=12345 ",
- EPMD_ARGS ++ " -sname " ++ atom_to_list(?FUNCTION_NAME)) of
- {ok, _SRPid, STPid, SState} ->
+ EPMD_ARGS = "-start_epmd false -erl_epmd_port 12346 ",
+ Name = ?CT_PEER_NAME(),
+ case rtnode:start([],"ERL_EPMD_PORT=12345 ",
+ EPMD_ARGS ++ " -sname " ++ Name) of
+ {ok, _SRPid, STPid, SNode, SState} ->
try
- ok = send_commands(
+ ok = rtnode:send_commands(
+ SNode,
STPid,
[{putline, ""},
{putline, "node()."},
- {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"}], 1),
- {ok, _CRPid, CTPid, CState} =
- rtstart([],"ERL_EPMD_PORT=12345 ",
- EPMD_ARGS ++ " -remsh "++atom_to_list(?FUNCTION_NAME)),
+ {expect, "\\Q" ++ Name ++ "\\E"}], 1),
+ {ok, _CRPid, CTPid, CNode, CState} =
+ rtnode:start([],"ERL_EPMD_PORT=12345 ",
+ EPMD_ARGS ++ " -remsh "++Name),
try
- ok = send_commands(
+ ok = rtnode:send_commands(
+ CNode,
CTPid,
[{putline, ""},
{putline, "node()."},
- {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"} | quit_hosting_node()], 1)
+ {expect, "\\Q" ++ Name ++ "\\E"} | quit_hosting_node()], 1)
after
- rtstop(CState)
+ rtnode:dump_logs(rtnode:stop(CState))
end
after
- rtstop(SState)
+ rtnode:dump_logs(rtnode:stop(SState))
end;
- Else ->
+ {skip, _} = Else ->
Else
end.
-
-rtnode(C) ->
- rtnode(C, []).
-
-rtnode(C, N) ->
- rtnode(C, N, []).
-
-rtnode(Commands, Nodename, ErlPrefix) ->
- rtnode(Commands, Nodename, ErlPrefix, []).
-
-rtnode(Commands, Nodename, ErlPrefix, Args) ->
- case rtstart(Nodename, ErlPrefix, Args) of
- {ok, _SPid, CPid, RTState} ->
- Res = catch send_commands(CPid, Commands, 1),
- Logs = rtstop(RTState),
- case Res of
- ok ->
- rtnode_dump_logs(Logs),
- ok;
- _ ->
- rtnode_dump_logs(Logs),
- ok = Res
- end,
- {ok, Logs};
- Skip ->
- Skip
- end.
-
-rtstart(Args) ->
- rtstart([], [], Args).
-
-rtstart(Nodename, ErlPrefix, Args) ->
- case get_progs() of
- {error,_Reason} ->
- {skip,"No runerl present"};
- {RunErl,ToErl,Erl} ->
- case create_tempdir() of
- {error, Reason2} ->
- {skip, Reason2};
- Tempdir ->
- SPid =
- start_runerl_node(RunErl,ErlPrefix++"\\\""++Erl++"\\\"",
- Tempdir,Nodename,Args),
- CPid = start_toerl_server(ToErl,Tempdir),
- {ok, SPid, CPid, {CPid, SPid, ToErl, Tempdir}}
- end
- end.
-
-rtstop({CPid, SPid, ToErl, Tempdir}) ->
- case stop_runerl_node(CPid) of
- {error,_} ->
- catch rtstop_try_harder(ToErl, Tempdir);
- _ ->
- ok
- end,
- wait_for_runerl_server(SPid),
- Logs = rtnode_read_logs(Tempdir),
- file:del_dir_r(Tempdir),
- Logs.
-
-rtstop_try_harder(ToErl, Tempdir) ->
- CPid = start_toerl_server(ToErl, Tempdir),
- ok = send_commands(CPid,
- [{putline,[7]},
- {expect, " --> $"},
- {putline, "s"},
- {putline, "c"},
- {putline, ""}], 1),
- stop_runerl_node(CPid).
-
-timeout(longest) ->
- timeout(long) + timeout(normal);
-timeout(long) ->
- 2 * timeout(normal);
-timeout(short) ->
- timeout(normal) div 10;
-timeout(normal) ->
- 10000 * test_server:timetrap_scale_factor().
-
-start_node(Name, Args0) ->
- PaDir = filename:dirname(code:which(?MODULE)),
- Args1 = ["-pa",PaDir|Args0],
- Args = lists:append(lists:join(" ", Args1)),
- {ok, Node} = test_server:start_node(Name, slave, [{args,Args}]),
- Node.
-
-send_commands(CPid, [{sleep, X}|T], N) ->
- ?dbg({sleep, X}),
- receive
- after X ->
- send_commands(CPid, T, N+1)
- end;
-send_commands(CPid, [{expect, Expect}|T], N) when is_list(Expect) ->
- ?dbg(Exp),
- case command(CPid, {expect, [Expect], timeout(normal)}) of
- ok ->
- send_commands(CPid, T, N + 1);
- {expect_timeout, Got} ->
- ct:pal("expect timed out waiting for ~p\ngot: ~p\n", [Expect,Got]),
- {error, timeout};
- Other ->
- Other
- end;
-send_commands(CPid, [{putline, Line}|T], N) ->
- send_commands(CPid, [{putdata, Line ++ "\n"}|T], N);
-send_commands(CPid, [{putdata, Data}|T], N) ->
- ?dbg({putdata, Data}),
- case command(CPid, {send_data, Data}) of
- ok ->
- send_commands(CPid, T, N+1);
- Error ->
- Error
- end;
-send_commands(_CPid, [], _) ->
- ok.
-
-command(Pid, Req) ->
- Timeout = timeout(longest),
- Ref = erlang:monitor(process, Pid),
- Pid ! {self(), Ref, Req},
- receive
- {Ref, Reply} ->
- erlang:demonitor(Ref, [flush]),
- Reply;
- {'DOWN', Ref, _, _, Reason} ->
- {error, Reason}
- after Timeout ->
- io:format("timeout while executing ~p\n", [Req]),
- {error, timeout}
- end.
-
-wait_for_runerl_server(SPid) ->
- Ref = erlang:monitor(process, SPid),
- Timeout = timeout(long),
- receive
- {'DOWN', Ref, process, SPid, _Reason} ->
- ok
- after Timeout ->
- {error, runerl_server_timeout}
- end.
-
-stop_runerl_node(CPid) ->
- Ref = erlang:monitor(process, CPid),
- CPid ! {self(), kill_emulator},
- Timeout = timeout(longest),
- receive
- {'DOWN', Ref, process, CPid, noproc} ->
- ok;
- {'DOWN', Ref, process, CPid, normal} ->
- ok;
- {'DOWN', Ref, process, CPid, {error, Reason}} ->
- {error, Reason}
- after Timeout ->
- {error, toerl_server_timeout}
- end.
-
-get_progs() ->
- try
- do_get_progs()
- catch
- throw:Thrown ->
- {error, Thrown}
- end.
-
-do_get_progs() ->
- case os:type() of
- {unix,freebsd} ->
- throw("Can't use run_erl on FreeBSD");
- {unix,openbsd} ->
- throw("Can't use run_erl on OpenBSD");
- {unix,_} ->
- RunErl = find_executable("run_erl"),
- ToErl = find_executable("to_erl"),
- Erl = find_executable("erl"),
- {RunErl, ToErl, Erl};
- _ ->
- throw("Not a Unix OS")
- end.
-
-find_executable(Name) ->
- case os:find_executable(Name) of
- Prog when is_list(Prog) ->
- Prog;
- false ->
- throw("Could not find " ++ Name)
- end.
-
-create_tempdir() ->
- create_tempdir(filename:join(["/tmp","rtnode"++os:getpid()]),$A).
-
-create_tempdir(Dir,X) when X > $Z, X < $a ->
- create_tempdir(Dir,$a);
-create_tempdir(Dir,X) when X > $z ->
- Estr = lists:flatten(
- io_lib:format("Unable to create ~s, reason eexist",
- [Dir++[$z]])),
- {error, Estr};
-create_tempdir(Dir0, Ch) ->
- %% Expect fairly standard unix.
- Dir = Dir0++[Ch],
- case file:make_dir(Dir) of
- {error, eexist} ->
- create_tempdir(Dir0, Ch+1);
- {error, Reason} ->
- Estr = lists:flatten(
- io_lib:format("Unable to create ~s, reason ~p",
- [Dir,Reason])),
- {error,Estr};
- ok ->
- Dir
- end.
-
-create_nodename() ->
- create_nodename($A).
-
-create_nodename(X) when X > $Z, X < $a ->
- create_nodename($a);
-create_nodename(X) when X > $z ->
- {error,out_of_nodenames};
-create_nodename(X) ->
- NN = "rtnode"++os:getpid()++[X],
- case file:read_file_info(filename:join(["/tmp",NN])) of
- {error,enoent} ->
- Host = lists:nth(2,string:tokens(atom_to_list(node()),"@")),
- NN++"@"++Host;
- _ ->
- create_nodename(X+1)
- end.
-
-
-start_runerl_node(RunErl,Erl,Tempdir,Nodename,Args) ->
- XArg = case Nodename of
- [] ->
- [];
- _ ->
- " -sname "++(if is_atom(Nodename) -> atom_to_list(Nodename);
- true -> Nodename
- end)++
- " -setcookie "++atom_to_list(erlang:get_cookie())
- end ++ " " ++ Args,
- spawn(fun() -> start_runerl_command(RunErl, Tempdir, Erl++XArg) end).
-
-start_runerl_command(RunErl, Tempdir, Cmd) ->
- FullCmd = "\""++RunErl++"\" "++Tempdir++"/ "++Tempdir++" \""++Cmd++"\"",
- ct:pal("~ts",[FullCmd]),
- os:cmd(FullCmd).
-
-start_toerl_server(ToErl,Tempdir) ->
- Pid = spawn(?MODULE,toerl_server,[self(),ToErl,Tempdir]),
- receive
- {Pid,started} ->
- Pid;
- {Pid,error,Reason} ->
- {error,Reason}
- end.
-
-try_to_erl(_Command, 0) ->
- {error, cannot_to_erl};
-try_to_erl(Command, N) ->
- ?dbg({?LINE,N}),
- Port = open_port({spawn, Command},[eof]),
- Timeout = timeout(short) div 2,
- receive
- {Port, eof} ->
- timer:sleep(Timeout),
- try_to_erl(Command, N-1)
- after Timeout ->
- ?dbg(Port),
- Port
- end.
-
-toerl_server(Parent, ToErl, TempDir) ->
- Port = try_to_erl("\""++ToErl++"\" "++TempDir++"/ 2>/dev/null", 8),
- case Port of
- P when is_port(P) ->
- Parent ! {self(),started};
- {error,Other} ->
- Parent ! {self(),error,Other},
- exit(Other)
- end,
-
- State = #{port => Port, acc => [], kill_emulator_command => init_stop},
- case toerl_loop(State) of
- normal ->
- ok;
- {error, Reason} ->
- error_logger:error_msg("toerl_server exit with reason ~p~n",
- [Reason]),
- exit(Reason)
- end.
-
-toerl_loop(#{port := Port} = State0) ->
- ?dbg({toerl_loop, Port, map_get(acc, State0),
- maps:get(match, State0, nomatch)}),
-
- State = handle_expect(State0),
-
- receive
- {Port,{data,Data}} when is_port(Port) ->
- ?dbg({?LINE,Port,{data,Data}}),
- toerl_loop(State#{acc => map_get(acc, State) ++ Data});
- {Pid, Ref, {expect, Expect, Timeout}} ->
- toerl_loop(init_expect(Pid, Ref, Expect, Timeout, State));
- {Pid, Ref, {send_data, Data}} ->
- Port ! {self(), {command, Data}},
- Pid ! {Ref, ok},
- toerl_loop(State);
- {_Pid, kill_emulator} ->
- kill_emulator(State);
- {timeout,Timer,expect_timeout} ->
- toerl_loop(handle_expect_timeout(Timer, State));
- {Port, eof} ->
- {error, unexpected_eof};
- Other ->
- {error, {unexpected, Other}}
- end.
-
-kill_emulator(#{port := Port}) ->
- %% If the line happens to end in a ".", issuing "init:stop()."
- %% will result in a syntax error. To avoid that, issue a "\n"
- %% before "init:stop().".
- Port ! {self(),{command, "\ninit:stop().\n"}},
- wait_for_eof(Port).
-
-wait_for_eof(Port) ->
- receive
- {Port,eof} ->
- normal;
- _Other ->
- wait_for_eof(Port)
- after
- timeout(long) ->
- {error, kill_timeout}
- end.
-
-init_expect(Pid, Ref, ExpectList, Timeout, State) ->
- try compile_expect(ExpectList) of
- Expect ->
- Exp = #{expect => Expect,
- ref => Ref,
- source => ExpectList,
- timer => erlang:start_timer(Timeout, self(), expect_timeout),
- from => Pid},
- State#{expect => Exp}
- catch
- Class:Reason:Stk ->
- io:put_chars("Compilation of expect pattern failed:"),
- io:format("~p\n", [ExpectList]),
- io:put_chars(erl_error:format_exception(Class, Reason, Stk)),
- exit(expect_pattern_error)
- end.
-
-handle_expect(#{acc := Acc, expect := Exp} = State) ->
- #{expect := Expect, from := Pid, ref := Ref} = Exp,
- case Expect(Acc) of
- nomatch ->
- State;
- {matched, Eaten, Result} ->
- Pid ! {Ref, Result},
- finish_expect(Eaten, State)
- end;
-handle_expect(State) ->
- State.
-
-handle_expect_timeout(Timer, State) ->
- #{acc := Acc, expect := Exp} = State,
- #{expect := Expect, timer := Timer, from := Pid, ref := Ref} = Exp,
- case Expect({timeout, Acc}) of
- nomatch ->
- Result = {expect_timeout, Acc},
- Pid ! {Ref, Result},
- finish_expect(0, State);
- {matched, Eaten, Result} ->
- Pid ! {Ref, Result},
- finish_expect(Eaten, State)
+remsh_expand_compatibility_25(Config) when is_list(Config) ->
+ {ok, _Peer, TargetNode} = ?CT_PEER(#{}), %% Create a vsn 26 node
+ NodeName = atom_to_list(TargetNode), %% compatibility
+ %% Start a node on vsn 25 but run the shell on vsn 26
+ case rtnode:start(peer:random_name(), "stty columns 200; ERL_AFLAGS= ", "-remsh "++NodeName, [{release, "25"}|Config]) of
+ {ok, _SRPid, STPid, _, SState} ->
+ try
+ ok = rtnode:send_commands(undefined,
+ STPid,
+ [{putdata, "erlang:is_atom\t"},
+ {expect, "\\Qerlang:is_atom(\\E"}|
+ quit_hosting_node()], 1)
+ after
+ Logs = rtnode:stop(SState),
+ rtnode:dump_logs(Logs)
+ end;
+ Else when element(1, Else) =/= ok -> Else
end.
-
-finish_expect(Eaten, #{acc := Acc0,
- expect := #{timer := Timer}}=State) ->
- erlang:cancel_timer(Timer),
- receive
- {timeout,Timer,timeout} ->
- ok
- after 0 ->
- ok
- end,
- Acc = lists:nthtail(Eaten, Acc0),
- maps:remove(expect, State#{acc := Acc}).
-
-compile_expect([{timeout,Action}|T]) when is_function(Action, 1) ->
- Next = compile_expect(T),
- fun({timeout, _}=Tm) ->
- {matched, 0, Action(Tm)};
- (Subject) ->
- Next(Subject)
- end;
-compile_expect([{{re,RE0},Action}|T]) when is_binary(RE0), is_function(Action, 1) ->
- {ok, RE} = re:compile(RE0),
- Next = compile_expect(T),
- fun({timeout, _}=Subject) ->
- Next(Subject);
- (Subject) ->
- case re:run(Subject, RE, [{capture,first,index}]) of
- nomatch ->
- Next(Subject);
- {match, [{Pos,Len}]} ->
- Matched = binary:part(list_to_binary(Subject), Pos, Len),
- {matched, Pos+Len, Action(Matched)}
+remsh_expand_compatibility_later_version(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ case ?CT_PEER_REL([], "25", PrivDir) of
+ not_available -> {skip, "25 not available"};
+ {ok, _Peer, TargetNode} ->
+ NodeName = atom_to_list(TargetNode),
+ %% Start a node on later version but run the shell on vsn 25
+ case rtnode:start(peer:random_name(), "stty columns 200; ERL_AFLAGS= ", "-remsh " ++ NodeName, Config) of
+ {ok, _SRPid, STPid, _, SState} ->
+ try
+ ok = rtnode:send_commands(undefined,
+ STPid,
+ [{putdata, "erlang:is_atom\t"},
+ {expect, "\\Qerlang:is_atom(\\E"}|
+ quit_hosting_node()], 1)
+ after
+ Logs = rtnode:stop(SState),
+ rtnode:dump_logs(Logs)
+ end;
+ Else when element(1, Else) =/= ok -> Else
end
- end;
-compile_expect([RE|T]) when is_list(RE) ->
- Ok = fun(_) -> ok end,
- compile_expect([{{re,list_to_binary(RE)},Ok}|T]);
-compile_expect([]) ->
- fun(_) ->
- nomatch
- end.
-
-rtnode_check_logs(Logname, Pattern, Logs) ->
-rtnode_check_logs(Logname, Pattern, true, Logs).
-rtnode_check_logs(Logname, Pattern, Match, Logs) ->
- case re:run(maps:get(Logname, Logs), Pattern) of
- {match, [_]} when Match ->
- ok;
- nomatch when not Match ->
- ok;
- _ ->
- rtnode_dump_logs(Logs),
- ct:fail("~p not found in log ~ts",[Pattern, Logname])
- end.
-
-rtnode_dump_logs(Logs) ->
- maps:foreach(
- fun(File, Data) ->
- ct:pal("~ts: ~ts",[File, Data])
- end, Logs).
-
-rtnode_read_logs(Tempdir) ->
- {ok, LogFiles0} = file:list_dir(Tempdir),
-
- %% Make sure that we only read log files and not any named pipes.
- LogFiles = [F || F <- LogFiles0,
- case F of
- "erlang.log" ++ _ -> true;
- _ -> false
- end],
-
- lists:foldl(
- fun(File, Acc) ->
- case file:read_file(filename:join(Tempdir, File)) of
- {ok, Data} ->
- Acc#{ File => Data };
- _ ->
- Acc
- end
- end, #{}, LogFiles).
-
-get_default_shell() ->
- try
- rtnode([{putline,""},
- {putline, "is_pid(whereis(user_drv))."},
- {expect, "true\r\n"}], []),
- new
- catch _E:_R ->
- ?dbg({_E,_R}),
- old
end.
printed_atom(A) ->
diff --git a/lib/kernel/test/logger_SUITE.erl b/lib/kernel/test/logger_SUITE.erl
index ef1bef9df3..3041fb0755 100644
--- a/lib/kernel/test/logger_SUITE.erl
+++ b/lib/kernel/test/logger_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1517,5 +1517,5 @@ check_config(_) ->
%% this function is also a test. When logger.hrl used non-qualified
%% apply/3 call, any module that was implementing apply/3 could
%% not use any logging macro
-apply(_Any, _Any, _Any) ->
+apply(_, _, _) ->
ok.
diff --git a/lib/kernel/test/logger_disk_log_h_SUITE.erl b/lib/kernel/test/logger_disk_log_h_SUITE.erl
index 08908b8b7a..fab125e5ae 100644
--- a/lib/kernel/test/logger_disk_log_h_SUITE.erl
+++ b/lib/kernel/test/logger_disk_log_h_SUITE.erl
@@ -674,7 +674,7 @@ sync(Config) ->
check_tracer(100),
ok.
sync(cleanup,_Config) ->
- dbg:stop_clear(),
+ dbg:stop(),
logger:remove_handler(?MODULE).
disk_log_wrap(Config) ->
@@ -720,7 +720,7 @@ disk_log_wrap(Config) ->
%% wait for trace messages
timer:sleep(1000),
- dbg:stop_clear(),
+ dbg:stop(),
Received = lists:flatmap(fun({trace,_M,handle_info,
[_,{disk_log,_Node,_Name,What},_]}) ->
[{trace,What}];
@@ -732,7 +732,7 @@ disk_log_wrap(Config) ->
ok.
disk_log_wrap(cleanup,_Config) ->
- dbg:stop_clear(),
+ dbg:stop(),
logger:remove_handler(?MODULE).
disk_log_full(Config) ->
@@ -766,7 +766,7 @@ disk_log_full(Config) ->
%% wait for trace messages
timer:sleep(2000),
- dbg:stop_clear(),
+ dbg:stop(),
Received = lists:flatmap(fun({trace,_M,handle_info,
[_,{disk_log,_Node,_Name,What},_]}) ->
[{trace,What}];
@@ -782,7 +782,7 @@ disk_log_full(Config) ->
%% {trace,{error_status,{error,{full,_}}}}] = Received,
ok.
disk_log_full(cleanup, _Config) ->
- dbg:stop_clear(),
+ dbg:stop(),
logger:remove_handler(?MODULE).
disk_log_events(_Config) ->
@@ -816,7 +816,7 @@ disk_log_events(_Config) ->
[whereis(h_proc_name()) ! E || E <- Events],
%% wait for trace messages
timer:sleep(2000),
- dbg:stop_clear(),
+ dbg:stop(),
Received = lists:map(fun({trace,_M,handle_info,
[_,Got,_]}) -> Got
end, test_server:messages_get()),
@@ -828,7 +828,7 @@ disk_log_events(_Config) ->
end, Received),
ok.
disk_log_events(cleanup, _Config) ->
- dbg:stop_clear(),
+ dbg:stop(),
logger:remove_handler(?MODULE).
write_failure(Config) ->
@@ -1603,7 +1603,7 @@ tpl([{{M,F,A},MS}|Trace]) ->
{_,_,1} ->
ok;
_ ->
- dbg:stop_clear(),
+ dbg:stop(),
throw({skip,"Can't trace "++atom_to_list(M)++":"++
atom_to_list(F)++"/"++integer_to_list(A)})
end,
@@ -1636,13 +1636,13 @@ maybe_tracer_done(Pid,Expected,Got,Caller) ->
check_tracer(T) ->
receive
tracer_done ->
- dbg:stop_clear(),
+ dbg:stop(),
ok;
{tracer_got_unexpected,Got,Expected} ->
- dbg:stop_clear(),
+ dbg:stop(),
ct:fail({tracer_got_unexpected,Got,Expected})
after T ->
- dbg:stop_clear(),
+ dbg:stop(),
ct:fail({timeout,tracer})
end.
diff --git a/lib/kernel/test/logger_formatter_SUITE.erl b/lib/kernel/test/logger_formatter_SUITE.erl
index f1c64110a1..a2f825f4a9 100644
--- a/lib/kernel/test/logger_formatter_SUITE.erl
+++ b/lib/kernel/test/logger_formatter_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -183,7 +183,7 @@ single_line(_Config) ->
ct:log(String3),
match = re:run(String3,"\\[1,2,3,4,5,6,7,8,9,10\\]",[{capture,none}]),
match = re:run(String3,
- "#{a => map,few => accociations,with => a}",
+ "#{((a => map|with => a|few => accociations)[,}]){3}",
[{capture,none}]),
%% This part is added to make sure that the previous test made
@@ -272,7 +272,7 @@ template(_Config) ->
[[nested,subkey]]),
String8 = format(info,{"~p",[term]},Meta8,#{template=>Template8,
single_line=>true}),
- ct:log(String6),
+ ct:log(String8),
SelfStr = pid_to_list(self()),
RefStr8 = ref_to_list(Ref8),
ListStr = "[list,\"string\",4321,#{},{tuple}]",
@@ -345,6 +345,18 @@ template(_Config) ->
_ -> ct:fail({full_nested_map_unexpected,MultipleKeysStr10})
end,
+ Meta11A = #{time=>Time,be_short=>ok},
+ Meta11B = #{time=>Time},
+ Template11 =
+ [{be_short,
+ ["short:",msg],
+ ["long:[",level,"]",msg]}],
+ String11A = format(info,{"~p",[term]},Meta11A,#{template=>Template11,single_line=>true}),
+ String11B = format(info,{"~p",[term]},Meta11B,#{template=>Template11,single_line=>true}),
+ ct:log(String11A),
+ ct:log(String11B),
+ {"short:term","long:[info]term"} = {String11A,String11B},
+
ok.
format_msg(_Config) ->
diff --git a/lib/kernel/test/logger_simple_h_SUITE.erl b/lib/kernel/test/logger_simple_h_SUITE.erl
index 8ac52e54c7..3e0227d5c5 100644
--- a/lib/kernel/test/logger_simple_h_SUITE.erl
+++ b/lib/kernel/test/logger_simple_h_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -80,6 +80,7 @@ groups() ->
all() ->
[start_stop,
+ start_crash,
replace_default,
replace_file,
replace_disk_log
@@ -101,6 +102,21 @@ start_stop(_Config) ->
start_stop(cleanup,_Config) ->
logger:remove_handler(simple).
+%% Test that the simple logger works during startup crash
+start_crash(_Config) ->
+
+ Output = os:cmd(ct:get_progname() ++ " -user baduser"),
+ ErrorOutput = re:replace(unicode:characters_to_binary(Output),"\r\n","\n",[global]),
+ ct:log("~ts",[ErrorOutput]),
+ {match,[_]} = re:run(ErrorOutput,"(^=SUPERVISOR REPORT====| supervisor_report *\n)",[global,multiline]),
+ {match,[_, _]} = re:run(ErrorOutput," crash_report *\n",[global]),
+ {match,[_]} = re:run(ErrorOutput," std_info *\n",[global]),
+ {match,[[CD]]} = re:run(ErrorOutput,"\nCrash dump is being written to: (.*)\\.\\.\\.done",
+ [{capture, all_but_first, binary}, global]),
+ ok = file:delete(CD),
+ ok.
+
+
%% This testcase just tests that it does not crash, the default handler prints
%% to stdout which we cannot read from in a detached slave.
replace_default(Config) ->
diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl
index ead4418d0d..88aac32f6f 100644
--- a/lib/kernel/test/logger_std_h_SUITE.erl
+++ b/lib/kernel/test/logger_std_h_SUITE.erl
@@ -2219,7 +2219,7 @@ tpl([]) ->
ok.
stop_clear() ->
- dbg:stop_clear(),
+ dbg:stop(),
%% Remove tracer from all processes in order to eliminate
%% race conditions.
erlang:trace(all,false,[all]).
diff --git a/lib/kernel/test/logger_stress_SUITE.erl b/lib/kernel/test/logger_stress_SUITE.erl
index 7fe12ca823..9209026322 100644
--- a/lib/kernel/test/logger_stress_SUITE.erl
+++ b/lib/kernel/test/logger_stress_SUITE.erl
@@ -343,7 +343,7 @@ cascade({PNode,PMFA,_PStatProcs},{CNode,CMFA,_CStatProcs},TestFun) ->
after TO ->
All = ets:lookup_element(Tab,producer,2),
Written = ets:lookup_element(Tab,consumer,2),
- dbg:stop_clear(),
+ dbg:stop(),
?COLLECT_STATS(All,
[{PNode,P,Id} || {Id,P} <- _PStatProcs] ++
[{CNode,P,Id} || {Id,P} <- _CStatProcs]),
diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl
index 01b76d83df..6d6ed337a0 100644
--- a/lib/kernel/test/os_SUITE.erl
+++ b/lib/kernel/test/os_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -401,8 +401,8 @@ error_info(Config) ->
ExhaustFDs =
fun(M,F,A) ->
- case os:type() of
- {unix, _} ->
+ case no_limit_for_opened_files() of
+ false ->
{ok, Peer, Node} = ?CT_PEER(),
FN = filename:join(
proplists:get_value(priv_dir, Config),
@@ -426,7 +426,7 @@ error_info(Config) ->
after
peer:stop(Peer)
end;
- _ ->
+ true ->
apply(M,F,A)
end
end,
@@ -437,7 +437,7 @@ error_info(Config) ->
{cmd, [{no, string}, no_map]},
{cmd, ["echo 1"], [{general, "too many open files \\(emfile\\)"},
{wrapper, ExhaustFDs}] ++
- [no_fail || win32 =:= element(1, os:type())]},
+ [no_fail || no_limit_for_opened_files()]},
{find_executable, 1}, %Not a BIF.
{find_executable, 2}, %Not a BIF.
@@ -469,6 +469,19 @@ error_info(Config) ->
],
error_info_lib:test_error_info(os, L).
+no_limit_for_opened_files() ->
+ case os:type() of
+ {unix, freebsd} ->
+ %% At least some FreeBSD systems support about one million open
+ %% files, which means that we run out of Erlang processes before we
+ %% reach the open file limit.
+ true;
+ {unix, _} ->
+ false;
+ _ ->
+ true
+ end.
+
%% Util functions
comp(Expected, Got) ->
diff --git a/lib/kernel/test/pg_SUITE.erl b/lib/kernel/test/pg_SUITE.erl
index d563209267..f10e89d774 100644
--- a/lib/kernel/test/pg_SUITE.erl
+++ b/lib/kernel/test/pg_SUITE.erl
@@ -59,7 +59,8 @@
group_leave/1,
monitor_nonempty_scope/0, monitor_nonempty_scope/1,
monitor_scope/0, monitor_scope/1,
- monitor/1
+ monitor/1,
+ protocol_upgrade/1
]).
-include_lib("common_test/include/ct.hrl").
@@ -87,7 +88,8 @@ all() ->
groups() ->
[
- {basic, [parallel], [errors, pg, leave_exit_race, single, overlay_missing]},
+ {basic, [parallel], [errors, pg, leave_exit_race, single, overlay_missing,
+ protocol_upgrade]},
{performance, [], [thundering_herd]},
{cluster, [parallel], [process_owner_check, two, initial, netsplit, trisplit, foursplit,
exchange, nolocal, double, scope_restart, missing_scope_join, empty_group_by_remote_leave,
@@ -755,9 +757,32 @@ second_monitor(Msgs) ->
second_monitor([Msg | Msgs])
end.
+protocol_upgrade(Config) when is_list(Config) ->
+ Scope = ?FUNCTION_NAME,
+ Group = ?FUNCTION_NAME,
+ {Peer, Node} = spawn_node(Scope, Config),
+ PgPid = rpc:call(Node, erlang, whereis, [Scope]),
+
+ RemotePid = erlang:spawn(Node, forever()),
+ ok = rpc:call(Node, pg, join, [Scope, Group, RemotePid]),
+
+ %% OTP 26:
+ %% Just do a white-box test and verify that pg accepts
+ %% a "future" discover message and replies with a sync.
+ PgPid ! {discover, self(), "Protocol version (ignore me)"},
+ {'$gen_cast', {sync, PgPid, [{Group, [RemotePid]}]}} = receive_any(),
+
+ %% stop the peer
+ peer:stop(Peer),
+ ok.
+
+
%%--------------------------------------------------------------------
%% Test Helpers - start/stop additional Erlang nodes
+receive_any() ->
+ receive M -> M end.
+
%% flushes GS (GenServer) queue, ensuring that all prior
%% messages have been processed
sync(GS) ->
diff --git a/lib/kernel/test/rtnode.erl b/lib/kernel/test/rtnode.erl
new file mode 100644
index 0000000000..ffa0613ca7
--- /dev/null
+++ b/lib/kernel/test/rtnode.erl
@@ -0,0 +1,575 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2022. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(rtnode).
+
+-export([run/1, run/2, run/3, run/4, start/1, start/3, start/4, send_commands/4, stop/1,
+ start_runerl_command/3,
+ check_logs/3, check_logs/4, read_logs/1, dump_logs/1,
+ get_default_shell/0, get_progs/0, create_tempdir/0, timeout/1]).
+
+-include_lib("common_test/include/ct.hrl").
+
+%% -define(debug, true).
+
+-ifdef(debug).
+-define(dbg(Data),io:format(standard_error, "DBG: ~p\r\n",[Data])).
+-else.
+-define(dbg(Data),noop).
+-endif.
+
+-export([toerl_server/4]).
+
+%%
+%% Tool for running interactive shell, used by interactive_shell and io_proto SUITE
+%%
+run(C) ->
+ run(C, [], [], []).
+
+run(C, N) ->
+ run(C, N, [], []).
+
+run(Commands, Nodename, ErlPrefix) ->
+ run(Commands, Nodename, ErlPrefix, []).
+
+run(Commands, Nodename, ErlPrefix, Args) ->
+ case start(Nodename, ErlPrefix, Args) of
+ {ok, _SPid, CPid, Node, RTState} ->
+ Res = catch send_commands(Node, CPid, Commands, 1),
+ Logs = stop(RTState),
+ case Res of
+ ok ->
+ dump_logs(Logs),
+ ok;
+ _ ->
+ dump_logs(Logs),
+ ok = Res
+ end,
+ {ok, Logs};
+ Skip ->
+ Skip
+ end.
+
+start(Args) ->
+ start([], " ", Args, []).
+start(Nodename, ErlPrefix, Args) ->
+ start(Nodename, ErlPrefix, Args, []).
+start(Nodename, ErlPrefix, Args, Options) ->
+ case get_progs(Options) of
+ {error,Reason} ->
+ {skip,Reason};
+ {RunErl,ToErl,[Erl|ErlArgs] = ErlWArgs} ->
+ case create_tempdir() of
+ {error, Reason2} ->
+ {skip, Reason2};
+ Tempdir when ErlPrefix =/= [] ->
+ {SPid, Node} =
+ start_runerl_node(RunErl,
+ ErlPrefix++"\\\""++Erl++"\\\" "++
+ lists:join($\s, ErlArgs),
+ Tempdir,Nodename,Args),
+ CPid = start_toerl_server(ToErl,Tempdir,undefined),
+ {ok, SPid, CPid, Node, {CPid, SPid, ToErl, Tempdir}};
+ Tempdir ->
+ {SPid, Node} = start_peer_runerl_node(RunErl,ErlWArgs,Tempdir,Nodename,Args),
+ CPid = start_toerl_server(ToErl,Tempdir,SPid),
+ {ok, SPid, CPid, Node, {CPid, SPid, ToErl, Tempdir}}
+ end
+ end.
+
+stop({CPid, SPid, ToErl, Tempdir}) ->
+ %% Unlink from peer so that we don't crash when peer quits
+ unlink(SPid),
+ case stop_runerl_node(CPid) of
+ {error,_} ->
+ catch stop_try_harder(ToErl, Tempdir, SPid);
+ _ ->
+ ok
+ end,
+ wait_for_runerl_server(SPid),
+ Logs = read_logs(Tempdir),
+ file:del_dir_r(Tempdir),
+ Logs.
+
+stop_try_harder(ToErl, Tempdir, SPid) ->
+ CPid = start_toerl_server(ToErl, Tempdir, SPid),
+ ok = send_commands(undefined, CPid,
+ [{putline,[7]},
+ {expect, " --> $"},
+ {putline, "s"},
+ {putline, "c"},
+ {putline, ""}], 1),
+ stop_runerl_node(CPid).
+
+timeout(longest) ->
+ timeout(long) + timeout(normal);
+timeout(long) ->
+ 2 * timeout(normal);
+timeout(short) ->
+ timeout(normal) div 10;
+timeout(normal) ->
+ 10000 * test_server:timetrap_scale_factor().
+
+send_commands(Node, CPid, [{sleep, X}|T], N) ->
+ ?dbg({sleep, X}),
+ receive
+ after X ->
+ send_commands(Node, CPid, T, N+1)
+ end;
+send_commands(Node, CPid, [{expect, Expect}|T], N) when is_list(Expect) ->
+ send_commands(Node, CPid, [{expect, unicode, Expect}|T], N);
+send_commands(Node, CPid, [{expect, Encoding, Expect}|T], N) when is_list(Expect) ->
+ ?dbg({expect, Expect}),
+ case command(CPid, {expect, Encoding, [Expect], timeout(normal)}) of
+ ok ->
+ send_commands(Node, CPid, T, N + 1);
+ {expect_timeout, Got} ->
+ ct:pal("expect timed out waiting for ~p\ngot: ~p\n", [Expect,Got]),
+ {error, timeout};
+ Other ->
+ Other
+ end;
+send_commands(Node, CPid, [{putline, Line}|T], N) ->
+ send_commands(Node, CPid, [{putdata, Line ++ "\n"}|T], N);
+send_commands(Node, CPid, [{putdata, Data}|T], N) ->
+ ?dbg({putdata, Data}),
+ case command(CPid, {send_data, Data}) of
+ ok ->
+ send_commands(Node, CPid, T, N+1);
+ Error ->
+ Error
+ end;
+send_commands(Node, CPid, [{eval, Fun}|T], N) ->
+ ?dbg({eval, Node, Fun}),
+ case erpc:call(Node, Fun) of
+ ok ->
+ ?dbg({eval, ok}),
+ send_commands(Node, CPid, T, N+1);
+ Error ->
+ ?dbg({eval, Error}),
+ Error
+ end;
+send_commands(_Node, _CPid, [], _) ->
+ ok.
+
+command(Pid, Req) ->
+ Timeout = timeout(longest),
+ Ref = erlang:monitor(process, Pid),
+ Pid ! {self(), Ref, Req},
+ receive
+ {Ref, Reply} ->
+ erlang:demonitor(Ref, [flush]),
+ Reply;
+ {'DOWN', Ref, _, _, Reason} ->
+ {error, Reason}
+ after Timeout ->
+ io:format("timeout while executing ~p\n", [Req]),
+ {error, timeout}
+ end.
+
+wait_for_runerl_server(SPid) ->
+ Ref = erlang:monitor(process, SPid),
+ Timeout = timeout(long),
+ receive
+ {'DOWN', Ref, process, SPid, _Reason} ->
+ ok
+ after Timeout ->
+ {error, runerl_server_timeout}
+ end.
+
+stop_runerl_node(CPid) ->
+ Ref = erlang:monitor(process, CPid),
+ CPid ! {self(), kill_emulator},
+ Timeout = timeout(longest),
+ receive
+ {'DOWN', Ref, process, CPid, noproc} ->
+ ok;
+ {'DOWN', Ref, process, CPid, normal} ->
+ ok;
+ {'DOWN', Ref, process, CPid, {error, Reason}} ->
+ {error, Reason}
+ after Timeout ->
+ {error, toerl_server_timeout}
+ end.
+
+get_progs() ->
+ case os:type() of
+ {unix,freebsd} ->
+ {error,"Can't use run_erl on FreeBSD"};
+ {unix,openbsd} ->
+ {error,"Can't use run_erl on OpenBSD"};
+ {unix,_} ->
+ RunErl = find_executable("run_erl"),
+ ToErl = find_executable("to_erl"),
+ Erl = string:split(ct:get_progname()," ",all),
+ {RunErl, ToErl, Erl};
+ _ ->
+ {error,"Not a Unix OS"}
+ end.
+get_progs(Opts) ->
+ case get_progs() of
+ {RunErl, ToErl, Erl} ->
+ case proplists:get_value(release, Opts) of
+ undefined -> {RunErl, ToErl, Erl};
+ Release ->
+ case test_server_node:find_release(Release) of
+ none -> {error, "Could not find release "++Release};
+ R -> {RunErl, ToErl, [R]}
+ end
+ end;
+ E -> E
+ end.
+
+
+find_executable(Name) ->
+ case os:find_executable(Name) of
+ Prog when is_list(Prog) ->
+ Prog;
+ false ->
+ throw("Could not find " ++ Name)
+ end.
+
+create_tempdir() ->
+ create_tempdir(filename:join(["/tmp","rtnode"++os:getpid()]),$A).
+
+create_tempdir(Dir,X) when X > $Z, X < $a ->
+ create_tempdir(Dir,$a);
+create_tempdir(Dir,X) when X > $z ->
+ Estr = lists:flatten(
+ io_lib:format("Unable to create ~s, reason eexist",
+ [Dir++[$z]])),
+ {error, Estr};
+create_tempdir(Dir0, Ch) ->
+ %% Expect fairly standard unix.
+ Dir = Dir0++[Ch],
+ case file:make_dir(Dir) of
+ {error, eexist} ->
+ create_tempdir(Dir0, Ch+1);
+ {error, Reason} ->
+ Estr = lists:flatten(
+ io_lib:format("Unable to create ~s, reason ~p",
+ [Dir,Reason])),
+ {error,Estr};
+ ok ->
+ Dir
+ end.
+
+start_runerl_node(RunErl,Erl,Tempdir,Nodename,Args) ->
+ {XArg, Node} =
+ case Nodename of
+ [] ->
+ {[], undefined};
+ _ ->
+ NodenameStr = if is_atom(Nodename) -> atom_to_list(Nodename);
+ true -> Nodename
+ end,
+ [_Name,Host] = string:split(atom_to_list(node()), "@"),
+ {" -sname "++ NodenameStr ++
+ " -setcookie "++atom_to_list(erlang:get_cookie()),
+ list_to_atom(NodenameStr ++ "@" ++ Host)}
+ end,
+ {spawn(fun() -> start_runerl_command(RunErl, Tempdir, Erl ++ XArg ++ " " ++ Args) end),
+ Node}.
+
+start_runerl_command(RunErl, Tempdir, Cmd) ->
+ FullCmd = "\""++RunErl++"\" "++Tempdir++"/ "++Tempdir++" \""++Cmd++"\"",
+ ct:pal("~ts",[FullCmd]),
+ os:cmd(FullCmd).
+
+start_peer_runerl_node(RunErl,Erl,Tempdir,[],Args) ->
+ start_peer_runerl_node(RunErl,Erl,Tempdir,peer:random_name(),Args);
+start_peer_runerl_node(RunErl,Erl,Tempdir,Nodename,Args) ->
+ {ok, Peer, Node} =
+ ?CT_PEER(#{ name => Nodename,
+ exec => {RunErl,Erl},
+ detached => false,
+ shutdown => 10000,
+ post_process_args =>
+ fun(As) ->
+ [Tempdir++"/",Tempdir,
+ lists:flatten(
+ lists:join(
+ " ",[[$',A,$'] || A <- As]))]
+ end,
+ args => ["-connect_all","false"|Args] }),
+ Self = self(),
+ TraceLog = filename:join(Tempdir,Nodename++".trace"),
+ ct:pal("Link to trace: file://~ts",[TraceLog]),
+
+ spawn(Node,
+ fun() ->
+ try
+ %% {ok, _} = dbg:tracer(file, TraceLog),
+ %% dbg:p(whereis(user_drv),[c,m,timestamp]),
+ %% dbg:p(whereis(user_drv_reader),[c,m,timestamp]),
+ %% dbg:p(whereis(user_drv_writer),[c,m,timestamp]),
+ %% dbg:p(whereis(user),[c,m,timestamp]),
+ %% dbg:tp(user_drv,x),
+ %% dbg:tp(prim_tty,x),
+ %% dbg:tpl(prim_tty,read_nif,x),
+ Ref = monitor(process, Self),
+ receive {'DOWN',Ref,_,_,_} -> ok end
+ catch E:R:ST ->
+ io:format(user,"~p:~p:~p",[E,R,ST]),
+ erlang:raise(E,R,ST)
+ end
+ end),
+ {Peer, Node}.
+
+start_toerl_server(ToErl,Tempdir,SPid) ->
+ Pid = spawn(?MODULE,toerl_server,[self(),ToErl,Tempdir,SPid]),
+ receive
+ {Pid,started} ->
+ Pid;
+ {Pid,error,Reason} ->
+ {error,Reason}
+ end.
+
+try_to_erl(_Command, 0) ->
+ {error, cannot_to_erl};
+try_to_erl(Command, N) ->
+ ?dbg({?LINE,N}),
+ Port = open_port({spawn, Command},[eof]),
+ Timeout = timeout(short) div 2,
+ receive
+ {Port, eof} ->
+ timer:sleep(Timeout),
+ try_to_erl(Command, N-1)
+ after Timeout ->
+ ?dbg(Port),
+ Port
+ end.
+
+toerl_server(Parent, ToErl, TempDir, SPid) ->
+ Port = try_to_erl("\""++ToErl++"\" "++TempDir++"/ 2>/dev/null", 8),
+ case Port of
+ P when is_port(P) ->
+ Parent ! {self(),started};
+ {error,Other} ->
+ Parent ! {self(),error,Other},
+ exit(Other)
+ end,
+
+ {ok, InitialData} = file:read_file(filename:join(TempDir,"erlang.log.1")),
+
+ State = #{port => Port, acc => unicode:characters_to_list(InitialData), spid => SPid},
+ case toerl_loop(State) of
+ normal ->
+ ok;
+ {error, Reason} ->
+ error_logger:error_msg("toerl_server exit with reason ~p~n",
+ [Reason]),
+ exit(Reason)
+ end.
+
+toerl_loop(#{port := Port} = State0) ->
+ ?dbg({toerl_loop, Port, map_get(acc, State0),
+ maps:get(match, State0, nomatch)}),
+
+ State = handle_expect(State0),
+
+ receive
+ {Port,{data,Data}} when is_port(Port) ->
+ ?dbg({?LINE,Port,{data,Data}}),
+ toerl_loop(State#{acc => map_get(acc, State) ++ Data});
+ {Pid, Ref, {expect, Encoding, Expect, Timeout}} ->
+ toerl_loop(init_expect(Pid, Ref, Encoding, Expect, Timeout, State));
+ {Pid, Ref, {send_data, Data}} ->
+ ?dbg({?LINE,Port,{send_data,Data}}),
+ Port ! {self(), {command, Data}},
+ Pid ! {Ref, ok},
+ toerl_loop(State);
+ {_Pid, kill_emulator} ->
+ kill_emulator(State);
+ {timeout,Timer,expect_timeout} ->
+ toerl_loop(handle_expect_timeout(Timer, State));
+ {Port, eof} ->
+ {error, unexpected_eof};
+ Other ->
+ {error, {unexpected, Other}}
+ end.
+
+kill_emulator(#{spid := SPid, port := Port}) when is_pid(SPid) ->
+ catch peer:stop(SPid),
+ wait_for_eof(Port);
+kill_emulator(#{port := Port}) ->
+ %% If the line happens to end in a ".", issuing "init:stop()."
+ %% will result in a syntax error. To avoid that, issue a "\n"
+ %% before "init:stop().".
+ Port ! {self(),{command, "\ninit:stop().\n"}},
+ wait_for_eof(Port).
+
+wait_for_eof(Port) ->
+ receive
+ {Port,eof} ->
+ normal;
+ _Other ->
+ wait_for_eof(Port)
+ after
+ timeout(long) ->
+ {error, kill_timeout}
+ end.
+
+init_expect(Pid, Ref, Encoding, ExpectList, Timeout, State) ->
+ try compile_expect(ExpectList, Encoding) of
+ Expect ->
+ Exp = #{expect => Expect,
+ ref => Ref,
+ source => ExpectList,
+ timer => erlang:start_timer(Timeout, self(), expect_timeout),
+ from => Pid},
+ State#{expect => Exp}
+ catch
+ Class:Reason:Stk ->
+ io:put_chars("Compilation of expect pattern failed:"),
+ io:format("~p\n", [ExpectList]),
+ io:put_chars(erl_error:format_exception(Class, Reason, Stk)),
+ exit(expect_pattern_error)
+ end.
+
+handle_expect(#{acc := Acc, expect := Exp} = State) ->
+ #{expect := Expect, from := Pid, ref := Ref} = Exp,
+ case Expect(Acc) of
+ nomatch ->
+ State;
+ {matched, Eaten, Result} ->
+ ?dbg({matched, Eaten, Result}),
+ Pid ! {Ref, Result},
+ finish_expect(Eaten, State)
+ end;
+handle_expect(State) ->
+ State.
+
+handle_expect_timeout(Timer, State) ->
+ #{acc := Acc, expect := Exp} = State,
+ #{expect := Expect, timer := Timer, from := Pid, ref := Ref} = Exp,
+ case Expect({timeout, Acc}) of
+ nomatch ->
+ Result = {expect_timeout, Acc},
+ Pid ! {Ref, Result},
+ finish_expect(0, State);
+ {matched, Eaten, Result} ->
+ Pid ! {Ref, Result},
+ finish_expect(Eaten, State)
+ end.
+
+finish_expect(Eaten, #{acc := Acc0,
+ expect := #{timer := Timer}}=State) ->
+ erlang:cancel_timer(Timer),
+ receive
+ {timeout,Timer,timeout} ->
+ ok
+ after 0 ->
+ ok
+ end,
+ Acc = lists:nthtail(Eaten, Acc0),
+ maps:remove(expect, State#{acc := Acc}).
+
+compile_expect([{timeout,Action}|T], E) when is_function(Action, 1) ->
+ Next = compile_expect(T, E),
+ fun({timeout, _}=Tm) ->
+ {matched, 0, Action(Tm)};
+ (Subject) ->
+ Next(Subject)
+ end;
+compile_expect([{{re,RE0},Action}|T], E) when is_binary(RE0), is_function(Action, 1) ->
+ {ok, RE} = re:compile(RE0, [unicode || E =:= unicode]),
+ Next = compile_expect(T, E),
+ fun({timeout, _}=Subject) ->
+ Next(Subject);
+ (Subject) ->
+ BinarySubject = if
+ E =:= unicode ->
+ unicode:characters_to_binary(list_to_binary(Subject));
+ E =:= latin1 ->
+ list_to_binary(Subject)
+ end,
+ case re:run(BinarySubject, RE, [{capture,first,index}]) of
+ nomatch ->
+ Next(Subject);
+ {match, [{Pos,Len}]} ->
+ Matched = binary:part(BinarySubject, Pos, Len),
+ {matched, Pos+Len, Action(Matched)}
+ end
+ end;
+compile_expect([RE|T], E) when is_list(RE) ->
+ Ok = fun(_) -> ok end,
+ compile_expect([{{re,unicode:characters_to_binary(RE, unicode, E)},Ok}|T], E);
+compile_expect([], _E) ->
+ fun(_) ->
+ nomatch
+ end.
+
+check_logs(Logname, Pattern, Logs) ->
+ check_logs(Logname, Pattern, true, Logs).
+check_logs(Logname, Pattern, Match, Logs) ->
+ case re:run(maps:get(Logname, Logs), Pattern) of
+ {match, [_]} when Match ->
+ ok;
+ nomatch when not Match ->
+ ok;
+ _ ->
+ dump_logs(Logs),
+ ct:fail("~p not found in log ~ts",[Pattern, Logname])
+ end.
+
+dump_logs(Logs) ->
+ maps:foreach(
+ fun(File, Data) ->
+ try re:replace(Data,"\e","\\\\e",[unicode,global]) of
+ D -> ct:pal("~ts: ~ts",[File, D])
+ catch error:badarg ->
+ ct:pal("~ts: ~s",[File, re:replace(Data,"\e","\\\\e",[global])])
+ end
+ end, Logs).
+
+read_logs(Tempdir) ->
+ {ok, LogFiles0} = file:list_dir(Tempdir),
+
+ %% Make sure that we only read log files and not any named pipes.
+ LogFiles = [F || F <- LogFiles0,
+ case F of
+ "erlang.log" ++ _ -> true;
+ _ -> false
+ end],
+
+ lists:foldl(
+ fun(File, Acc) ->
+ case file:read_file(filename:join(Tempdir, File)) of
+ {ok, Data} ->
+ Acc#{ File => Data };
+ _ ->
+ Acc
+ end
+ end, #{}, LogFiles).
+
+get_default_shell() ->
+ case get_progs() of
+ {error,_} ->
+ noshell;
+ _ ->
+ try
+ run([{putline,""},
+ {putline, "is_pid(whereis(user_drv))."},
+ {expect, "true\r\n"}]),
+ new
+ catch _E:_R ->
+ old
+ end
+ end.
diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl
index 673c905ddf..196af34521 100644
--- a/lib/kernel/test/socket_SUITE.erl
+++ b/lib/kernel/test/socket_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -67,6 +67,15 @@
%%
%% Run a specific test case:
%% ts:run(emulator, socket_SUITE, foo, [batch]).
+%%
+%% S = fun() -> ts:run(kernel, socket_SUITE, [batch]) end.
+%% S = fun() -> ct:run_test([{suite, socket_SUITE}]) end.
+%% G = fun(GROUP) -> ts:run(kernel, socket_SUITE, {group, GROUP}, [batch]) end.
+%% G = fun(GROUP) -> ct:run_test([{suite, socket_SUITE}, {group, GROUP}]) end.
+%% T = fun(TC) -> ts:run(kernel, socket_SUITE, TC, [batch]) end.
+%% T = fun(TC) -> ct:run_test([{suite, socket_SUITE}, {testcase, TC}]) end.
+
+
-module(socket_SUITE).
@@ -89,6 +98,10 @@
api_m_error_bind/1,
%% *** API Basic ***
+ api_b_simple_open_and_close_udp4/1,
+ api_b_simple_open_and_close_udp6/1,
+ api_b_simple_open_and_close_tcp4/1,
+ api_b_simple_open_and_close_tcp6/1,
api_b_open_and_info_udp4/1,
api_b_open_and_info_udp6/1,
api_b_open_and_info_tcp4/1,
@@ -731,8 +744,11 @@
-define(TPP_LARGE_NUM, 50).
-define(TPP_NUM(Config, Base), (Base) div lookup(kernel_factor, 1, Config)).
+-define(WINDOWS, {win32,nt}).
+
-define(TTEST_RUNTIME, ?SECS(1)).
-define(TTEST_MIN_FACTOR, 3).
+-define(TTEST_MIN_FACTOR_WIN, ?TTEST_MIN_FACTOR-1).
-define(TTEST_DEFAULT_SMALL_MAX_OUTSTANDING, 50).
-define(TTEST_DEFAULT_MEDIUM_MAX_OUTSTANDING,
?TTEST_MK_DEFAULT_MAX_OUTSTANDING(
@@ -918,6 +934,10 @@ api_misc_cases() ->
api_basic_cases() ->
[
+ api_b_simple_open_and_close_udp4,
+ api_b_simple_open_and_close_udp6,
+ api_b_simple_open_and_close_tcp4,
+ api_b_simple_open_and_close_tcp6,
api_b_open_and_info_udp4,
api_b_open_and_info_udp6,
api_b_open_and_info_tcp4,
@@ -1387,7 +1407,12 @@ traffic_pp_sendmsg_recvmsg_cases() ->
%% No point in running these cases unless the machine is
%% reasonably fast.
ttest_condition(Config) ->
+ OsType = os:type(),
case ?config(kernel_factor, Config) of
+ Factor when (OsType =:= ?WINDOWS) andalso
+ is_integer(Factor) andalso
+ (Factor =< ?TTEST_MIN_FACTOR_WIN) ->
+ ok;
Factor when is_integer(Factor) andalso (Factor =< ?TTEST_MIN_FACTOR) ->
ok;
Factor when is_integer(Factor) ->
@@ -2581,6 +2606,100 @@ api_m_error_bind(Config) when is_list(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Basically open (create) and then close.
+api_b_simple_open_and_close_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp},
+ ok = api_b_simple_open_and_close(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and then close.
+api_b_simple_open_and_close_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(?FUNCTION_NAME,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp},
+ ok = api_b_simple_open_and_close(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and then close.
+api_b_simple_open_and_close_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = api_b_simple_open_and_close(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and then close.
+api_b_simple_open_and_close_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(?FUNCTION_NAME,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp},
+ ok = api_b_simple_open_and_close(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_b_simple_open_and_close(InitState) ->
+ Seq =
+ [
+ #{desc => "open",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = State) ->
+ case socket:open(Domain, Type, Protocol) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = _State) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
%% Basically open (create) and info of an IPv4 UDP (dgram) socket.
%% With some extra checks...
api_b_open_and_info_udp4(_Config) when is_list(_Config) ->
@@ -3381,7 +3500,10 @@ api_b_send_and_recv_seqpL(_Config) when is_list(_Config) ->
api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) ->
?TT(?SECS(10)),
tc_try(api_b_sendmsg_and_recvmsg_tcp4,
- fun() -> has_support_ipv4() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
Send = fun(Sock, Data) ->
Msg = #{iov => [Data]},
@@ -4530,6 +4652,12 @@ api_b_sendmsg_iov_stream(Domain) ->
DataTooLarge = erlang:iolist_to_binary(IOVTooLarge),
{ok, Sa} = socket:open(Domain, stream),
try
+ case os:type() of
+ {win32,nt} ->
+ ok = socket:bind(Sa, which_local_socket_addr(Domain));
+ _ ->
+ ok
+ end,
{ok, Sb} = socket:open(Domain, stream),
try
ok = socket:bind(Sb, which_local_socket_addr(Domain)),
@@ -4550,6 +4678,9 @@ api_b_sendmsg_iov_stream(Domain) ->
{ok, DataTooLarge} =
socket:recv(Sa, byte_size(DataTooLarge)),
ok
+ catch
+ error:notsup = Reason:_ ->
+ exit({skip, Reason})
after
socket:close(Sc)
end
@@ -4560,6 +4691,7 @@ api_b_sendmsg_iov_stream(Domain) ->
socket:close(Sa)
end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% %%
@@ -6586,7 +6718,7 @@ api_a_connect_tcpD(Domain, Nowait) ->
connect => Connect,
send => Send,
recv => Recv,
- connect_sref => Nowait},
+ connect_ref => Nowait},
api_a_connect_tcp(InitState).
@@ -6777,41 +6909,67 @@ api_a_connect_tcp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, async_connect)
end},
#{desc => "connect (async) to server",
- cmd => fun(#{sock := Sock,
- server_sa := SSA,
- connect := Connect,
- connect_sref := SR} = State) ->
+ cmd => fun(#{sock := Sock,
+ server_sa := SSA,
+ connect := Connect,
+ connect_ref := SR} = State) ->
case Connect(Sock, SSA) of
ok ->
?SEV_IPRINT("ok -> "
"unexpected success => SKIP",
[]),
{skip, unexpected_success};
+
{select, {select_info, ST, SelectRef}}
when SR =:= nowait ->
?SEV_IPRINT("select nowait ->"
"~n tag: ~p"
"~n ref: ~p",
[ST, SelectRef]),
- {ok, State#{connect_stag => ST,
- connect_sref => SelectRef}};
+ {ok, State#{asynch_tag => select,
+ connect_tag => ST,
+ connect_ref => SelectRef}};
{select, {select_info, ST, SR}}
when is_reference(SR) ->
?SEV_IPRINT("select ref ->"
"~n tag: ~p"
"~n ref: ~p", [ST, SR]),
- {ok, State#{connect_stag => ST}};
+ {ok, State#{asynch_tag => select,
+ connect_tag => ST}};
+
+ {completion,
+ {completion_info, CT, CompletionRef}}
+ when SR =:= nowait ->
+ ?SEV_IPRINT("completion nowait ->"
+ "~n tag: ~p"
+ "~n ref: ~p",
+ [CT, CompletionRef]),
+ {ok, State#{asynch_tag => completion,
+ connect_tag => CT,
+ connect_ref => CompletionRef}};
+ {completion,
+ {completion_info, CT, CR}}
+ when is_reference(CR) ->
+ ?SEV_IPRINT("completion ref ->"
+ "~n tag: ~p"
+ "~n ref: ~p", [CT, CR]),
+ {ok, State#{asynch_tag => completion,
+ connect_tag => CT}};
+
{error, _} = ERROR ->
ERROR
end
end},
- #{desc => "announce ready (connect select)",
+ #{desc => "announce ready (connect select|completion)",
cmd => fun(#{tester := Tester}) ->
?SEV_ANNOUNCE_READY(Tester, connect_select),
ok
end},
- #{desc => "await select message",
- cmd => fun(#{sock := Sock, connect_sref := Ref}) ->
+ #{desc => "await select|completion message",
+ cmd => fun(#{sock := Sock,
+ asynch_tag := select,
+ connect_tag := connect,
+ connect_ref := Ref}) ->
receive
{'$socket', Sock, select, Ref} ->
?SEV_IPRINT("select message ->"
@@ -6822,15 +6980,35 @@ api_a_connect_tcp(InitState) ->
"~n message queue: ~p",
[mq()]),
{error, timeout}
+ end;
+ (#{sock := Sock,
+ asynch_tag := completion,
+ connect_tag := connect,
+ connect_ref := Ref}) ->
+ receive
+ {'$socket', Sock, completion, {Ref, ok = Res}} ->
+ ?SEV_IPRINT("completion message ->"
+ "~n ref: ~p"
+ "~n res: ~p", [Ref, Res]),
+ ok
+ after 5000 ->
+ ?SEV_EPRINT("timeout: "
+ "~n message queue: ~p",
+ [mq()]),
+ {error, timeout}
end
end},
- #{desc => "announce ready (select)",
+ #{desc => "announce ready (select|completion)",
cmd => fun(#{tester := Tester}) ->
?SEV_ANNOUNCE_READY(Tester, select),
ok
end},
- #{desc => "connect (async) to server",
- cmd => fun(#{sock := Sock, server_sa := SSA, connect := Connect}) ->
+ #{desc => "(maybe) connect (async) to server",
+ cmd => fun(#{sock := Sock,
+ server_sa := SSA,
+ asynch_tag := select,
+ connect_tag := connect,
+ connect := Connect}) ->
case Connect(Sock, SSA) of
ok ->
ok;
@@ -6838,7 +7016,13 @@ api_a_connect_tcp(InitState) ->
{error, {unexpected_select, SelectInfo}};
{error, _} = ERROR ->
ERROR
- end
+ end;
+ (#{sock := _Sock,
+ server_sa := _SSA,
+ asynch_tag := completion,
+ connect_tag := connect,
+ connect := _Connect}) ->
+ ok
end},
#{desc => "announce ready (connect)",
cmd => fun(#{tester := Tester}) ->
@@ -7080,7 +7264,7 @@ api_a_connect_tcp(InitState) ->
api_a_sendto_and_recvfrom_udp4(Config) when is_list(Config) ->
?TT(?SECS(5)),
Nowait = nowait(Config),
- tc_try(api_a_sendto_and_recvfrom_udp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
Send = fun(Sock, Data, Dest) ->
@@ -7089,10 +7273,10 @@ api_a_sendto_and_recvfrom_udp4(Config) when is_list(Config) ->
Recv = fun(Sock) ->
socket:recvfrom(Sock, 0, Nowait)
end,
- InitState = #{domain => inet,
- send => Send,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet,
+ send => Send,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_send_and_recv_udp(InitState)
end).
@@ -7109,7 +7293,7 @@ api_a_sendto_and_recvfrom_udp4(Config) when is_list(Config) ->
api_a_sendto_and_recvfrom_udp6(Config) when is_list(Config) ->
?TT(?SECS(5)),
Nowait = nowait(Config),
- tc_try(api_a_sendto_and_recvfrom_udp6,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
Send = fun(Sock, Data, Dest) ->
@@ -7118,10 +7302,10 @@ api_a_sendto_and_recvfrom_udp6(Config) when is_list(Config) ->
Recv = fun(Sock) ->
socket:recvfrom(Sock, 0, Nowait)
end,
- InitState = #{domain => inet6,
- send => Send,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet6,
+ send => Send,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_send_and_recv_udp(InitState)
end).
@@ -7156,14 +7340,16 @@ api_a_sendmsg_and_recvmsg_udp4(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet,
- send => Send,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet,
+ send => Send,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_send_and_recv_udp(InitState)
end).
@@ -7198,14 +7384,16 @@ api_a_sendmsg_and_recvmsg_udp6(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet6,
- send => Send,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet6,
+ send => Send,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_send_and_recv_udp(InitState)
end).
@@ -7266,25 +7454,45 @@ api_a_send_and_recv_udp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv)
end},
#{desc => "try recv request (with nowait, expect select)",
- cmd => fun(#{sock := Sock,
- recv := Recv,
- recv_sref := SR} = State) ->
+ cmd => fun(#{sock := Sock,
+ recv := Recv,
+ recv_ref := Ref} = State) ->
case Recv(Sock) of
{select, {select_info, Tag, RecvRef}}
- when SR =:= nowait ->
+ when Ref =:= nowait ->
?SEV_IPRINT("expected select nowait: "
"~n Tag: ~p"
"~n Ref: ~p", [Tag, RecvRef]),
- {ok, State#{recv_stag => Tag,
- recv_sref => RecvRef}};
- {select, {select_info, Tag, SR}}
- when is_reference(SR) ->
+ {ok, State#{async_tag => select,
+ recv_tag => Tag,
+ recv_ref => RecvRef}};
+ {select, {select_info, Tag, Ref}}
+ when is_reference(Ref) ->
?SEV_IPRINT("expected select ref: "
"~n Tag: ~p"
- "~n Ref: ~p", [Tag, SR]),
- {ok, State#{recv_stag => Tag}};
+ "~n Ref: ~p", [Tag, Ref]),
+ {ok, State#{async_tag => select,
+ recv_tag => Tag}};
+
+ {completion, {completion_info, Tag, RecvRef}}
+ when Ref =:= nowait ->
+ ?SEV_IPRINT("expected select nowait: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, RecvRef]),
+ {ok, State#{async_tag => completion,
+ recv_tag => Tag,
+ recv_ref => RecvRef}};
+ {completion, {completion_info, Tag, Ref}}
+ when is_reference(Ref) ->
+ ?SEV_IPRINT("expected completion ref: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, Ref]),
+ {ok, State#{async_tag => completion,
+ recv_tag => Tag}};
+
{ok, X} ->
- {error, {unexpected_succes, X}};
+ {error, {unexpected_success, X}};
+
{error, _} = ERROR ->
ERROR
end
@@ -7294,29 +7502,74 @@ api_a_send_and_recv_udp(InitState) ->
?SEV_ANNOUNCE_READY(Tester, recv_select),
ok
end},
- #{desc => "await select message",
- cmd => fun(#{sock := Sock, recv_sref := RecvRef}) ->
+ #{desc => "await select|completion message",
+ cmd => fun(#{async_tag := select,
+ sock := Sock,
+ recv_ref := RecvRef}) ->
receive
{'$socket', Sock, select, RecvRef} ->
ok
after 5000 ->
- ?SEV_EPRINT("message queue: ~p", [mq()]),
+ ?SEV_EPRINT("timeout when: "
+ "~n Socket Info: ~p"
+ "~n Message Queue: ~p",
+ [socket:info(Sock), mq()]),
+ {error, timeout}
+ end;
+ (#{async_tag := completion,
+ sock := Sock,
+ recv_ref := RecvRef} = State) ->
+ receive
+ %% Recvfrom
+ {'$socket', Sock, completion,
+ {RecvRef, {ok, {Src, ?BASIC_REQ}}}} ->
+ {ok, State#{req_src => Src}};
+ %% Recvmsg
+ {'$socket', Sock, completion,
+ {RecvRef, {ok, #{addr := Src,
+ iov := [?BASIC_REQ]}}}} ->
+ {ok, State#{req_src => Src}};
+ {'$socket', Sock, completion,
+ {RecvRef, {ok, Unexpected}}} ->
+ ?SEV_EPRINT("Unexpected success result: "
+ "~n ~p", [Unexpected]),
+ {error, {unexpected_success_result,
+ Unexpected}};
+ {'$socket', Sock, completion,
+ {RecvRef, {error, Reason} = ERROR}} ->
+ ?SEV_EPRINT("completion with error: "
+ "~n ~p", [Reason]),
+ ERROR
+ after 5000 ->
+ ?SEV_EPRINT("timeout when: "
+ "~n Socket Info: ~p"
+ "~n Message Queue: ~p",
+ [socket:info(Sock), mq()]),
{error, timeout}
end
end},
#{desc => "announce ready (select)",
cmd => fun(#{tester := Tester}) ->
+ %% We are actually done *if* this was
+ %% a completion event, but to make the
+ %% test case simple...
?SEV_ANNOUNCE_READY(Tester, select),
ok
end},
- #{desc => "now read the data (request)",
- cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ #{desc => "now read the data (request), for select",
+ cmd => fun(#{async_tag := select,
+ sock := Sock,
+ recv := Recv} = State) ->
case Recv(Sock) of
{ok, {Src, ?BASIC_REQ}} ->
{ok, State#{req_src => Src}};
{error, _} = ERROR ->
ERROR
- end
+ end;
+ (#{async_tag := completion} = _State) ->
+ %% We are already done!
+ ?SEV_IPRINT("Already done!"),
+ ok
end},
#{desc => "announce ready (recv request)",
cmd => fun(#{tester := Tester}) ->
@@ -7344,10 +7597,11 @@ api_a_send_and_recv_udp(InitState) ->
case ?SEV_AWAIT_TERMINATE(Tester, tester) of
ok ->
State2 = maps:remove(tester, State),
- State3 = maps:remove(recv_stag, State2),
- State4 = maps:remove(recv_sref, State3),
- State5 = maps:remove(req_src, State4),
- {ok, State5};
+ State3 = maps:remove(async_tag, State2),
+ State4 = maps:remove(recv_tag, State3),
+ State5 = maps:remove(recv_ref, State4),
+ State6 = maps:remove(req_src, State5),
+ {ok, State6};
{error, _} = ERROR ->
ERROR
end
@@ -7387,8 +7641,7 @@ api_a_send_and_recv_udp(InitState) ->
#{desc => "open socket",
cmd => fun(#{domain := Domain} = State) ->
Sock = sock_open(Domain, dgram, udp),
- SA = sock_sockname(Sock),
- {ok, State#{sock => Sock, sa => SA}}
+ {ok, State#{sock => Sock}}
end},
#{desc => "bind socket (to local address)",
cmd => fun(#{sock := Sock, lsa := LSA}) ->
@@ -7399,6 +7652,11 @@ api_a_send_and_recv_udp(InitState) ->
ERROR
end
end},
+ #{desc => "sockname",
+ cmd => fun(#{sock := Sock} = State) ->
+ SA = sock_sockname(Sock),
+ {ok, State#{sa => SA}}
+ end},
#{desc => "announce ready (init)",
cmd => fun(#{tester := Tester}) ->
?SEV_ANNOUNCE_READY(Tester, init),
@@ -7424,17 +7682,28 @@ api_a_send_and_recv_udp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv)
end},
#{desc => "try recv reply (with nowait)",
- cmd => fun(#{sock := Sock,
- recv := Recv,
- recv_sref := SR} = State) ->
+ cmd => fun(#{sock := Sock,
+ recv := Recv,
+ recv_ref := Ref} = State) ->
case Recv(Sock) of
{select, {select_info, Tag, RecvRef}}
- when SR =:= nowait ->
- {ok, State#{recv_stag => Tag,
- recv_sref => RecvRef}};
- {select, {select_info, Tag, SR}}
- when is_reference(SR) ->
- {ok, State#{recv_stag => Tag}};
+ when Ref =:= nowait ->
+ {ok, State#{async_tag => select,
+ recv_tag => Tag,
+ recv_ref => RecvRef}};
+ {select, {select_info, Tag, Ref}}
+ when is_reference(Ref) ->
+ {ok, State#{async_tag => select,
+ recv_tag => Tag}};
+ {completion, {completion_info, Tag, RecvRef}}
+ when Ref =:= nowait ->
+ {ok, State#{async_tag => completion,
+ recv_tag => Tag,
+ recv_ref => RecvRef}};
+ {completion, {completion_info, Tag, Ref}}
+ when is_reference(Ref) ->
+ {ok, State#{async_tag => completion,
+ recv_tag => Tag}};
{ok, X} ->
{error, {unexpected_select_info, X}};
{error, _} = ERROR ->
@@ -7447,10 +7716,20 @@ api_a_send_and_recv_udp(InitState) ->
ok
end},
#{desc => "await select message",
- cmd => fun(#{sock := Sock, recv_sref := RecvRef}) ->
+ cmd => fun(#{async_tag := select,
+ sock := Sock,
+ recv_ref := RecvRef}) ->
receive
{'$socket', Sock, select, RecvRef} ->
ok
+ end;
+ (#{async_tag := completion,
+ sock := Sock,
+ recv_ref := RecvRef}) ->
+ receive
+ {'$socket', Sock, completion,
+ {RecvRef, {ok, _}}} ->
+ ok
end
end},
#{desc => "announce ready (select)",
@@ -7459,13 +7738,18 @@ api_a_send_and_recv_udp(InitState) ->
ok
end},
#{desc => "now read the data (reply)",
- cmd => fun(#{sock := Sock, recv := Recv}) ->
+ cmd => fun(#{async_tag := select,
+ sock := Sock,
+ recv := Recv}) ->
case Recv(Sock) of
{ok, {_Src, ?BASIC_REP}} ->
ok;
{error, _} = ERROR ->
ERROR
- end
+ end;
+ (#{async_tag := completion}) ->
+ ?SEV_IPRINT("Already read!"),
+ ok
end},
#{desc => "announce ready (recv reply)",
cmd => fun(#{tester := Tester}) ->
@@ -7479,9 +7763,10 @@ api_a_send_and_recv_udp(InitState) ->
case ?SEV_AWAIT_TERMINATE(Tester, tester) of
ok ->
State2 = maps:remove(tester, State),
- State3 = maps:remove(recv_stag, State2),
- State4 = maps:remove(recv_sref, State3),
- {ok, State4};
+ State3 = maps:remove(async_tag, State2),
+ State4 = maps:remove(recv_tag, State3),
+ State5 = maps:remove(recv_ref, State4),
+ {ok, State5};
{error, _} = ERROR ->
ERROR
end
@@ -7716,7 +8001,10 @@ api_a_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
tc_try(api_a_sendmsg_and_recvmsg_tcp4,
- fun() -> has_support_ipv4() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
Send = fun(Sock, Data) ->
Msg = #{iov => [Data]},
@@ -7842,20 +8130,40 @@ api_a_send_and_recv_tcp(Config, InitState) ->
case socket:accept(LSock, Nowait) of
{select, {select_info, Tag, Ref}}
when Nowait =:= nowait ->
- ?SEV_IPRINT("accept select nowait: "
+ ?SEV_IPRINT("select accept message: "
"~n Tag: ~p"
"~n Ref: ~p", [Tag, Ref]),
- {ok, State#{accept_stag => Tag,
+ {ok, State#{sorc => select,
+ accept_stag => Tag,
accept_sref => Ref}};
{select, {select_info, Tag, Nowait}}
when is_reference(Nowait) ->
- ?SEV_IPRINT("accept select ref: "
+ ?SEV_IPRINT("select accept result: "
"~n Tag: ~p"
"~n Ref: ~p", [Tag, Nowait]),
- {ok, State#{accept_stag => Tag,
+ {ok, State#{sorc => select,
+ accept_stag => Tag,
accept_sref => Nowait}};
+
+ {completion, {completion_info, Tag, Ref}}
+ when Nowait =:= nowait ->
+ ?SEV_IPRINT("completion accept result: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, Ref]),
+ {ok, State#{sorc => completion,
+ accept_stag => Tag,
+ accept_sref => Ref}};
+ {completion, {completion_info, Tag, Nowait}}
+ when is_reference(Nowait) ->
+ ?SEV_IPRINT("completion accept result: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, Nowait]),
+ {ok, State#{sorc => completion,
+ accept_stag => Tag,
+ accept_sref => Nowait}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
{error, _} = ERROR ->
ERROR
end
@@ -7865,11 +8173,25 @@ api_a_send_and_recv_tcp(Config, InitState) ->
?SEV_ANNOUNCE_READY(Tester, accept_select),
ok
end},
- #{desc => "await select message",
- cmd => fun(#{lsock := Sock, accept_sref := Ref}) ->
+ #{desc => "await select|completion message",
+ cmd => fun(#{lsock := Sock, accept_sref := Ref} = State) ->
receive
{'$socket', Sock, select, Ref} ->
- ok
+ ?SEV_IPRINT("select message: "
+ "ready for accept"),
+ ok;
+ {'$socket', Sock, completion,
+ {Ref, {ok, CSock}}} ->
+ ?SEV_IPRINT("completion message: accepted: "
+ "~n CSock: ~p", [Sock]),
+ {ok, State#{csock => CSock}}
+ after 5000 ->
+ ?SEV_EPRINT("select|completion message timeout:"
+ "~n Sock: ~p"
+ "~n Ref: ~p"
+ "~n Message Queue: ~p",
+ [Sock, Ref, mq()]),
+ {error, timeout}
end
end},
#{desc => "announce ready (select)",
@@ -7877,8 +8199,9 @@ api_a_send_and_recv_tcp(Config, InitState) ->
?SEV_ANNOUNCE_READY(Tester, select),
ok
end},
- #{desc => "await connection (again)",
- cmd => fun(#{lsock := LSock} = State) ->
+ #{desc => "try accept (again)",
+ cmd => fun(#{lsock := LSock, sorc := select} = State) ->
+ ?SEV_IPRINT("try accept again"),
case socket:accept(LSock, nowait) of
{ok, Sock} ->
?SEV_IPRINT("accepted: "
@@ -7886,7 +8209,10 @@ api_a_send_and_recv_tcp(Config, InitState) ->
{ok, State#{csock => Sock}};
{error, _} = ERROR ->
ERROR
- end
+ end;
+ (#{sorc := completion})->
+ ?SEV_IPRINT("already accepted"),
+ ok
end},
#{desc => "announce ready (accept)",
cmd => fun(#{tester := Tester}) ->
@@ -7899,8 +8225,8 @@ api_a_send_and_recv_tcp(Config, InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv_req)
end},
#{desc => "try recv request (with nowait, expect select)",
- cmd => fun(#{csock := Sock,
- recv := Recv,
+ cmd => fun(#{csock := Sock,
+ recv := Recv,
recv_sref := SR} = State) ->
case Recv(Sock) of
{select, {select_info, Tag, Ref}}
@@ -7916,8 +8242,23 @@ api_a_send_and_recv_tcp(Config, InitState) ->
"~n Tag: ~p"
"~n Ref: ~p", [Tag, SR]),
{ok, State#{recv_stag => Tag}};
+
+ {completion, {completion_info, Tag, Ref}}
+ when SR =:= nowait ->
+ ?SEV_IPRINT("recv completion nowait: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, Ref]),
+ {ok, State#{recv_stag => Tag,
+ recv_sref => Ref}};
+ {completion, {completion_info, Tag, SR}}
+ when is_reference(SR) ->
+ ?SEV_IPRINT("recv completion ref: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, SR]),
+ {ok, State#{recv_stag => Tag}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
{error, _} = ERROR ->
ERROR
end
@@ -7927,11 +8268,20 @@ api_a_send_and_recv_tcp(Config, InitState) ->
?SEV_ANNOUNCE_READY(Tester, recv_select),
ok
end},
- #{desc => "await select message",
+ #{desc => "await select|completion message",
cmd => fun(#{csock := Sock, recv_sref := RecvRef}) ->
receive
{'$socket', Sock, select, RecvRef} ->
- ok
+ ok;
+ {'$socket', Sock, completion,
+ {RecvRef, {ok, ?BASIC_REQ}}} ->
+ ?SEV_IPRINT("received expected data"),
+ ok;
+ {'$socket', Sock, completion,
+ {RecvRef, {error, Reason} = ERROR}} ->
+ ?SEV_EPRINT("received unexpected error: "
+ "~n ~p", [Reason]),
+ ERROR
end
end},
#{desc => "announce ready (select)",
@@ -7940,13 +8290,19 @@ api_a_send_and_recv_tcp(Config, InitState) ->
ok
end},
#{desc => "now read the data (request)",
- cmd => fun(#{csock := Sock, recv := Recv} = _State) ->
+ cmd => fun(#{sorc := select,
+ csock := Sock,
+ recv := Recv} = _State) ->
case Recv(Sock) of
{ok, ?BASIC_REQ} ->
+ ?SEV_IPRINT("read expected data"),
ok;
{error, _} = ERROR ->
ERROR
- end
+ end;
+ (#{sorc := completion}) ->
+ ?SEV_IPRINT("already received"),
+ ok
end},
#{desc => "announce ready (recv request)",
cmd => fun(#{tester := Tester}) ->
@@ -8066,7 +8422,7 @@ api_a_send_and_recv_tcp(Config, InitState) ->
ok
end},
- #{desc => "try recv reply (with nowait, expect select)",
+ #{desc => "try recv reply (with nowait, expect select|completion)",
cmd => fun(#{sock := Sock,
recv := Recv,
recv_sref := SR} = State) ->
@@ -8076,16 +8432,35 @@ api_a_send_and_recv_tcp(Config, InitState) ->
?SEV_IPRINT("recv select nowait: "
"~n Tag: ~p"
"~n Ref: ~p", [Tag, Ref]),
- {ok, State#{recv_stag => Tag,
+ {ok, State#{sorc => select,
+ recv_stag => Tag,
recv_sref => Ref}};
{select, {select_info, Tag, SR}}
when is_reference(SR) ->
?SEV_IPRINT("recv select ref: "
"~n Tag: ~p"
"~n Ref: ~p", [Tag, SR]),
- {ok, State#{recv_stag => Tag}};
+ {ok, State#{sorc => select,
+ recv_stag => Tag}};
+
+ {completion, {completion_info, Tag, Ref}}
+ when SR =:= nowait ->
+ ?SEV_IPRINT("recv completion nowait: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, Ref]),
+ {ok, State#{sorc => completion,
+ recv_stag => Tag,
+ recv_sref => Ref}};
+ {completion, {completion_info, Tag, SR}}
+ when is_reference(SR) ->
+ ?SEV_IPRINT("recv completion ref: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, SR]),
+ {ok, State#{sorc => completion,
+ recv_stag => Tag}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
{error, _} = ERROR ->
ERROR
end
@@ -8099,6 +8474,10 @@ api_a_send_and_recv_tcp(Config, InitState) ->
cmd => fun(#{sock := Sock, recv_sref := RecvRef}) ->
receive
{'$socket', Sock, select, RecvRef} ->
+ ok;
+ {'$socket', Sock, completion,
+ {RecvRef, {ok, ?BASIC_REP}}} ->
+ ?SEV_IPRINT("received expected reply"),
ok
end
end},
@@ -8108,8 +8487,13 @@ api_a_send_and_recv_tcp(Config, InitState) ->
ok
end},
#{desc => "now read the data (reply)",
- cmd => fun(#{sock := Sock, recv := Recv}) ->
+ cmd => fun(#{sorc := select, sock := Sock, recv := Recv}) ->
{ok, ?BASIC_REP} = Recv(Sock),
+ ?SEV_IPRINT("[select] received expected reply"),
+ ok;
+ (#{sorc := completion}) ->
+ ?SEV_IPRINT("[completion] "
+ "expected reply already received"),
ok
end},
#{desc => "announce ready (recv reply)",
@@ -8311,7 +8695,7 @@ api_a_send_and_recv_tcp(Config, InitState) ->
api_a_recvfrom_cancel_udp4(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
- tc_try(api_a_recvfrom_cancel_udp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
Recv = fun(Sock) ->
@@ -8320,13 +8704,15 @@ api_a_recvfrom_cancel_udp4(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_recv_cancel_udp(InitState)
end).
@@ -8340,7 +8726,7 @@ api_a_recvfrom_cancel_udp4(Config) when is_list(Config) ->
api_a_recvfrom_cancel_udp6(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
- tc_try(api_a_recvfrom_cancel_udp6,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
Recv = fun(Sock) ->
@@ -8349,13 +8735,15 @@ api_a_recvfrom_cancel_udp6(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet6,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet6,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_recv_cancel_udp(InitState)
end).
@@ -8369,7 +8757,7 @@ api_a_recvfrom_cancel_udp6(Config) when is_list(Config) ->
api_a_recvmsg_cancel_udp4(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
- tc_try(api_a_recvmsg_cancel_udp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
Recv = fun(Sock) ->
@@ -8378,13 +8766,15 @@ api_a_recvmsg_cancel_udp4(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_recv_cancel_udp(InitState)
end).
@@ -8398,7 +8788,7 @@ api_a_recvmsg_cancel_udp4(Config) when is_list(Config) ->
api_a_recvmsg_cancel_udp6(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
- tc_try(api_a_recvmsg_cancel_udp6,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
Recv = fun(Sock) ->
@@ -8407,13 +8797,15 @@ api_a_recvmsg_cancel_udp6(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet6,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet6,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_recv_cancel_udp(InitState)
end).
@@ -8473,19 +8865,28 @@ api_a_recv_cancel_udp(InitState) ->
cmd => fun(#{tester := Tester} = _State) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv)
end},
- #{desc => "try recv request (with nowait, expect select)",
- cmd => fun(#{sock := Sock,
- recv := Recv,
- recv_sref := SR} = State) ->
+ #{desc => "try recv request (with nowait, expect select|completion)",
+ cmd => fun(#{sock := Sock,
+ recv := Recv,
+ recv_ref := Ref} = State) ->
case Recv(Sock) of
- {select, SelectInfo} when SR =:= nowait ->
- {ok, State#{recv_select_info => SelectInfo}};
+ {select, SI} when Ref =:= nowait ->
+ {ok, State#{recv_select_info => SI}};
{select,
- {select_info, _Tag, SR} = SelectInfo}
- when is_reference(SR) ->
- {ok, State#{recv_select_info => SelectInfo}};
+ {select_info, _Tag, Ref} = SI}
+ when is_reference(Ref) ->
+ {ok, State#{recv_select_info => SI}};
+
+ {completion, CI} when Ref =:= nowait ->
+ {ok, State#{recv_completion_info => CI}};
+ {completion,
+ {completion_info, _Tag, Ref} = CI}
+ when is_reference(Ref) ->
+ {ok, State#{recv_completion_info => CI}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
+
{error, _} = ERROR ->
ERROR
end
@@ -8495,11 +8896,15 @@ api_a_recv_cancel_udp(InitState) ->
?SEV_ANNOUNCE_READY(Tester, recv_select),
ok
end},
- #{desc => "await select message (without success)",
+ #{desc => "wait for select message - without success",
cmd => fun(#{sock := Sock}) ->
receive
{'$socket', Sock, select, Ref} ->
- {error, {unexpected_select, Ref}}
+ {error, {unexpected_select, Ref}};
+
+ {'$socket', Sock, completion, C} ->
+ {error, {unexpected_completion, C}}
+
after 5000 ->
ok
end
@@ -8514,9 +8919,12 @@ api_a_recv_cancel_udp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, cancel)
end},
#{desc => "cancel",
- cmd => fun(#{sock := Sock, recv_select_info := SelectInfo}) ->
- ok = socket:cancel(Sock, SelectInfo)
+ cmd => fun(#{sock := Sock, recv_select_info := SI}) ->
+ ok = socket:cancel(Sock, SI);
+ (#{sock := Sock, recv_completion_info := CI}) ->
+ ok = socket:cancel(Sock, CI)
end},
+
#{desc => "announce ready (cancel)",
cmd => fun(#{tester := Tester}) ->
?SEV_ANNOUNCE_READY(Tester, cancel),
@@ -8528,11 +8936,10 @@ api_a_recv_cancel_udp(InitState) ->
cmd => fun(#{tester := Tester} = State) ->
case ?SEV_AWAIT_TERMINATE(Tester, tester) of
ok ->
- State2 = maps:remove(tester, State),
- State3 = maps:remove(recv_stag, State2),
- State4 = maps:remove(recv_sref, State3),
- State5 = maps:remove(req_src, State4),
- {ok, State5};
+ State2 = maps:remove(tester, State),
+ State3 = maps:remove(recv_ref, State2),
+ State4 = maps:remove(req_src, State3),
+ {ok, State4};
{error, _} = ERROR ->
ERROR
end
@@ -8638,7 +9045,7 @@ api_a_recv_cancel_udp(InitState) ->
api_a_accept_cancel_tcp4(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
- tc_try(api_a_accept_cancel_tcp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
Accept = fun(Sock) ->
@@ -8647,13 +9054,15 @@ api_a_accept_cancel_tcp4(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet,
- accept => Accept,
- accept_sref => Nowait},
+ InitState = #{domain => inet,
+ accept => Accept,
+ accept_ref => Nowait},
ok = api_a_accept_cancel_tcp(InitState)
end).
@@ -8668,7 +9077,7 @@ api_a_accept_cancel_tcp4(Config) when is_list(Config) ->
api_a_accept_cancel_tcp6(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
- tc_try(api_a_accept_cancel_tcp6,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
Accept = fun(Sock) ->
@@ -8677,13 +9086,15 @@ api_a_accept_cancel_tcp6(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet6,
- accept => Accept,
- accept_sref => Nowait},
+ InitState = #{domain => inet6,
+ accept => Accept,
+ accept_ref => Nowait},
ok = api_a_accept_cancel_tcp(InitState)
end).
@@ -8749,28 +9160,39 @@ api_a_accept_cancel_tcp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, accept)
end},
#{desc => "await connection (nowait)",
- cmd => fun(#{lsock := LSock,
- accept := Accept,
- accept_sref := SR} = State) ->
+ cmd => fun(#{lsock := LSock,
+ accept := Accept,
+ accept_ref := Ref} = State) ->
case Accept(LSock) of
- {select, {select_info, T, R} = SelectInfo}
- when SR =:= nowait ->
+ {select, {select_info, T, R} = SI}
+ when Ref =:= nowait ->
?SEV_IPRINT("accept select nowait: "
"~n T: ~p"
"~n R: ~p", [T, R]),
- {ok,
- State#{accept_select_info =>
- SelectInfo}};
- {select, {select_info, T, SR} = SelectInfo}
- when is_reference(SR) ->
+ {ok, State#{accept_select_info => SI}};
+ {select, {select_info, T, Ref} = SI}
+ when is_reference(Ref) ->
?SEV_IPRINT("accept select ref: "
"~n T: ~p"
- "~n R: ~p", [T, SR]),
- {ok,
- State#{accept_select_info =>
- SelectInfo}};
+ "~n R: ~p", [T, Ref]),
+ {ok, State#{accept_select_info => SI}};
+
+ {completion, {completion_info, T, R} = CI}
+ when Ref =:= nowait ->
+ ?SEV_IPRINT("accept completion nowait: "
+ "~n T: ~p"
+ "~n R: ~p", [T, R]),
+ {ok, State#{accept_completion_info => CI}};
+ {completion, {completion_info, T, Ref} = CI}
+ when is_reference(Ref) ->
+ ?SEV_IPRINT("accept completion ref: "
+ "~n T: ~p"
+ "~n R: ~p", [T, Ref]),
+ {ok, State#{accept_completion_info => CI}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
+
{error, _} = ERROR ->
ERROR
end
@@ -8784,7 +9206,11 @@ api_a_accept_cancel_tcp(InitState) ->
cmd => fun(#{lsock := Sock}) ->
receive
{'$socket', Sock, select, Ref} ->
- {error, {unexpected_select, Ref}}
+ {error, {unexpected_select, Ref}};
+
+ {'$socket', Sock, completion, C} ->
+ {error, {unexpected_completion, C}}
+
after 5000 ->
ok
end
@@ -8799,8 +9225,12 @@ api_a_accept_cancel_tcp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, cancel)
end},
#{desc => "cancel",
- cmd => fun(#{lsock := Sock, accept_select_info := SelectInfo}) ->
- ok = socket:cancel(Sock, SelectInfo)
+ cmd => fun(#{lsock := Sock,
+ accept_select_info := SelectInfo}) ->
+ ok = socket:cancel(Sock, SelectInfo);
+ (#{lsock := Sock,
+ accept_completion_info := CompletionInfo}) ->
+ ok = socket:cancel(Sock, CompletionInfo)
end},
#{desc => "announce ready (cancel)",
cmd => fun(#{tester := Tester}) ->
@@ -8911,15 +9341,15 @@ api_a_accept_cancel_tcp(InitState) ->
api_a_recv_cancel_tcp4(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
- tc_try(api_a_recv_cancel_tcp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
Recv = fun(Sock) ->
socket:recv(Sock, 0, Nowait)
end,
- InitState = #{domain => inet,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_recv_cancel_tcp(InitState)
end).
@@ -8933,15 +9363,15 @@ api_a_recv_cancel_tcp4(Config) when is_list(Config) ->
api_a_recv_cancel_tcp6(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
- tc_try(api_a_recv_cancel_tcp6,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
Recv = fun(Sock) ->
socket:recv(Sock, 0, Nowait)
end,
- InitState = #{domain => inet6,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet6,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_recv_cancel_tcp(InitState)
end).
@@ -8955,15 +9385,18 @@ api_a_recv_cancel_tcp6(Config) when is_list(Config) ->
api_a_recvmsg_cancel_tcp4(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
- tc_try(api_a_recvmsg_cancel_tcp4,
- fun() -> has_support_ipv4() end,
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
Recv = fun(Sock) ->
socket:recvmsg(Sock, Nowait)
end,
- InitState = #{domain => inet,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_recv_cancel_tcp(InitState)
end).
@@ -8977,15 +9410,18 @@ api_a_recvmsg_cancel_tcp4(Config) when is_list(Config) ->
api_a_recvmsg_cancel_tcp6(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
- tc_try(api_a_recvmsg_cancel_tcp6,
- fun() -> has_support_ipv6() end,
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv6()
+ end,
fun() ->
Recv = fun(Sock) ->
socket:recvmsg(Sock, Nowait)
end,
- InitState = #{domain => inet6,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet6,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_recv_cancel_tcp(InitState)
end).
@@ -9068,27 +9504,43 @@ api_a_recv_cancel_tcp(InitState) ->
cmd => fun(#{tester := Tester} = _State) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv)
end},
- #{desc => "try recv request (with nowait, expect select)",
- cmd => fun(#{csock := Sock,
- recv := Recv,
- recv_sref := SR} = State) ->
+
+ #{desc => "try recv request (with nowait, expect select|completion)",
+ cmd => fun(#{csock := Sock,
+ recv := Recv,
+ recv_ref := Ref} = State) ->
case Recv(Sock) of
- {select, {select_info, T, R} = SelectInfo}
- when SR =:= nowait ->
+ {select, {select_info, T, R} = SI}
+ when Ref =:= nowait ->
?SEV_IPRINT("recv select nowait: "
"~n Tag: ~p"
"~n Ref: ~p", [T, R]),
- {ok,
- State#{recv_select_info => SelectInfo}};
- {select, {select_info, T, SR} = SelectInfo}
- when is_reference(SR) ->
+ {ok, State#{recv_select_info => SI}};
+ {select, {select_info, T, Ref} = SI}
+ when is_reference(Ref) ->
?SEV_IPRINT("recv select ref: "
"~n Tag: ~p"
- "~n Ref: ~p", [T, SR]),
- {ok,
- State#{recv_select_info => SelectInfo}};
+ "~n Ref: ~p", [T, Ref]),
+ {ok, State#{recv_select_info => SI}};
+
+ {completion,
+ {completion_info, T, R} = CI}
+ when Ref =:= nowait ->
+ ?SEV_IPRINT("recv completion nowait: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [T, R]),
+ {ok, State#{recv_completion_info => CI}};
+ {completion,
+ {completion_info, T, Ref} = CI}
+ when is_reference(Ref) ->
+ ?SEV_IPRINT("recv completion ref: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [T, Ref]),
+ {ok, State#{recv_completion_info => CI}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
+
{error, _} = ERROR ->
ERROR
end
@@ -9102,7 +9554,11 @@ api_a_recv_cancel_tcp(InitState) ->
cmd => fun(#{csock := Sock}) ->
receive
{'$socket', Sock, select, Ref} ->
- {error, {unexpected_select, Ref}}
+ {error, {unexpected_select, Ref}};
+
+ {'$socket', Sock, completion, C} ->
+ {error, {unexpected_completion, C}}
+
after 5000 ->
ok
end
@@ -9117,8 +9573,11 @@ api_a_recv_cancel_tcp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, cancel)
end},
#{desc => "cancel",
- cmd => fun(#{csock := Sock, recv_select_info := SelectInfo}) ->
- ok = socket:cancel(Sock, SelectInfo)
+ cmd => fun(#{csock := Sock, recv_select_info := SI}) ->
+ ok = socket:cancel(Sock, SI);
+
+ (#{csock := Sock, recv_completion_info := CI}) ->
+ ok = socket:cancel(Sock, CI)
end},
#{desc => "announce ready (cancel)",
cmd => fun(#{tester := Tester}) ->
@@ -9372,13 +9831,15 @@ api_a_mrecvfrom_cancel_udp4(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_mrecv_cancel_udp(InitState)
end).
@@ -9402,13 +9863,15 @@ api_a_mrecvfrom_cancel_udp6(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet6,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet6,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_mrecv_cancel_udp(InitState)
end).
@@ -9432,13 +9895,15 @@ api_a_mrecvmsg_cancel_udp4(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_mrecv_cancel_udp(InitState)
end).
@@ -9462,13 +9927,15 @@ api_a_mrecvmsg_cancel_udp6(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet6,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet6,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_mrecv_cancel_udp(InitState)
end).
@@ -9528,21 +9995,27 @@ api_a_mrecv_cancel_udp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv)
end},
#{desc => "try recv request (with nowait, expect select)",
- cmd => fun(#{sock := Sock,
- recv := Recv,
- recv_sref := SR} = State) ->
+ cmd => fun(#{sock := Sock,
+ recv := Recv,
+ recv_ref := Ref} = State) ->
case Recv(Sock) of
- {select, SelectInfo}
- when SR =:= nowait ->
- {ok,
- State#{recv_select_info => SelectInfo}};
- {select,
- {select_info, _Tag, SR} = SelectInfo}
- when is_reference(SR) ->
- {ok,
- State#{recv_select_info => SelectInfo}};
+ {select, SI}
+ when Ref =:= nowait ->
+ {ok, State#{recv_select_info => SI}};
+ {select, {select_info, _Tag, Ref} = SI}
+ when is_reference(Ref) ->
+ {ok, State#{recv_select_info => SI}};
+
+ {completion, CI}
+ when Ref =:= nowait ->
+ {ok, State#{recv_completion_info => CI}};
+ {completion, {completion_info, _Tag, Ref} = CI}
+ when is_reference(Ref) ->
+ {ok, State#{recv_completion_info => CI}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
+
{error, _} = ERROR ->
ERROR
end
@@ -9554,7 +10027,8 @@ api_a_mrecv_cancel_udp(InitState) ->
end},
#{desc => "await abort message",
cmd => fun(#{sock := Sock,
- recv_select_info := {select_info, _, Ref}} = State) ->
+ recv_select_info := {select_info, _, Ref}} =
+ State) ->
receive
{'$socket', Sock, select, Ref} ->
{error, {unexpected_select, Ref}};
@@ -9563,6 +10037,18 @@ api_a_mrecv_cancel_udp(InitState) ->
after 5000 ->
?SEV_EPRINT("message queue: ~p", [mq()]),
{error, timeout}
+ end;
+ (#{sock := Sock,
+ recv_completion_info := {completion_info, _, Ref}} =
+ State) ->
+ receive
+ {'$socket', Sock, completion, {Ref, CS}} ->
+ {error, {unexpected_completion, CS}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(sock, State)}
+ after 5000 ->
+ ?SEV_EPRINT("message queue: ~p", [mq()]),
+ {error, timeout}
end
end},
#{desc => "announce ready (abort)",
@@ -9576,11 +10062,9 @@ api_a_mrecv_cancel_udp(InitState) ->
cmd => fun(#{tester := Tester} = State) ->
case ?SEV_AWAIT_TERMINATE(Tester, tester) of
ok ->
- State2 = maps:remove(tester, State),
- State3 = maps:remove(recv_stag, State2),
- State4 = maps:remove(recv_sref, State3),
- State5 = maps:remove(req_src, State4),
- {ok, State5};
+ State2 = maps:remove(tester, State),
+ State3 = maps:remove(recv_ref, State2),
+ {ok, State3};
{error, _} = ERROR ->
ERROR
end
@@ -9616,20 +10100,27 @@ api_a_mrecv_cancel_udp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv)
end},
#{desc => "try recv request (with nowait, expect select)",
- cmd => fun(#{sock := Sock,
- recv := Recv,
- recv_sref := SR} = State) ->
+ cmd => fun(#{sock := Sock,
+ recv := Recv,
+ recv_ref := Ref} = State) ->
case Recv(Sock) of
- {select, SelectInfo} when SR =:= nowait ->
- {ok,
- State#{recv_select_info => SelectInfo}};
+ {select, SI} when Ref =:= nowait ->
+ {ok, State#{recv_select_info => SI}};
{select,
- {select_info, _Tag, SR} = SelectInfo}
- when is_reference(SR) ->
- {ok,
- State#{recv_select_info => SelectInfo}};
+ {select_info, _Tag, Ref} = SI}
+ when is_reference(Ref) ->
+ {ok, State#{recv_select_info => SI}};
+
+ {completion, CI} when Ref =:= nowait ->
+ {ok, State#{recv_completion_info => CI}};
+ {completion,
+ {completion_info, _Tag, Ref} = CI}
+ when is_reference(Ref) ->
+ {ok, State#{recv_completion_info => CI}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
+
{error, _} = ERROR ->
ERROR
end
@@ -9650,6 +10141,19 @@ api_a_mrecv_cancel_udp(InitState) ->
after 5000 ->
?SEV_EPRINT("message queue: ~p", [mq()]),
{error, timeout}
+ end;
+
+ (#{sock := Sock,
+ recv_completion_info := {completion_info, _, Ref}} =
+ State) ->
+ receive
+ {'$socket', Sock, completion, {Ref, CS}} ->
+ {error, {unexpected_completion, CS}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(sock, State)}
+ after 5000 ->
+ ?SEV_EPRINT("message queue: ~p", [mq()]),
+ {error, timeout}
end
end},
#{desc => "announce ready (abort)",
@@ -9665,9 +10169,10 @@ api_a_mrecv_cancel_udp(InitState) ->
ok ->
?SEV_IPRINT("terminating"),
State1 = maps:remove(recv_select_info, State),
- State2 = maps:remove(tester, State1),
- State3 = maps:remove(sock, State2),
- {ok, State3};
+ State2 = maps:remove(recv_completion_info, State1),
+ State3 = maps:remove(tester, State2),
+ State4 = maps:remove(sock, State3),
+ {ok, State4};
{error, _} = ERROR ->
ERROR
end
@@ -9879,13 +10384,15 @@ api_a_maccept_cancel_tcp4(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet,
- accept => Accept,
- accept_sref => Nowait},
+ InitState = #{domain => inet,
+ accept => Accept,
+ accept_ref => Nowait},
ok = api_a_maccept_cancel_tcp(InitState)
end).
@@ -9910,13 +10417,15 @@ api_a_maccept_cancel_tcp6(Config) when is_list(Config) ->
OK;
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end
end,
- InitState = #{domain => inet6,
- accept => Accept,
- accept_sref => Nowait},
+ InitState = #{domain => inet6,
+ accept => Accept,
+ accept_ref => Nowait},
ok = api_a_maccept_cancel_tcp(InitState)
end).
@@ -9982,28 +10491,39 @@ api_a_maccept_cancel_tcp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, accept)
end},
#{desc => "await connection (nowait)",
- cmd => fun(#{lsock := LSock,
- accept := Accept,
- accept_sref := SR} = State) ->
+ cmd => fun(#{lsock := LSock,
+ accept := Accept,
+ accept_ref := Ref} = State) ->
case Accept(LSock) of
- {select, {select_info, T, R} = SelectInfo}
- when SR =:= nowait ->
+ {select, {select_info, T, R} = SI}
+ when Ref =:= nowait ->
?SEV_IPRINT("accept select nowait: "
"~n T: ~p"
"~n R: ~p", [T, R]),
- {ok,
- State#{accept_select_info =>
- SelectInfo}};
- {select, {select_info, T, SR} = SelectInfo}
- when is_reference(SR) ->
+ {ok, State#{accept_select_info => SI}};
+ {select, {select_info, T, Ref} = SI}
+ when is_reference(Ref) ->
?SEV_IPRINT("accept select ref: "
"~n T: ~p"
- "~n R: ~p", [T, SR]),
- {ok,
- State#{accept_select_info =>
- SelectInfo}};
+ "~n R: ~p", [T, Ref]),
+ {ok, State#{accept_select_info => SI}};
+
+ {completion, {completion_info, T, R} = CI}
+ when Ref =:= nowait ->
+ ?SEV_IPRINT("accept completion nowait: "
+ "~n T: ~p"
+ "~n R: ~p", [T, R]),
+ {ok, State#{accept_completion_info => CI}};
+ {completion, {completion_info, T, Ref} = CI}
+ when is_reference(Ref) ->
+ ?SEV_IPRINT("accept completion ref: "
+ "~n T: ~p"
+ "~n R: ~p", [T, Ref]),
+ {ok, State#{accept_completion_info => CI}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
+
{error, _} = ERROR ->
ERROR
end
@@ -10022,7 +10542,26 @@ api_a_maccept_cancel_tcp(InitState) ->
{'$socket', Sock, abort, {Ref, closed}} ->
{ok, maps:remove(lsock, State)}
after 5000 ->
- ?SEV_EPRINT("message queue: ~p", [mq()]),
+ ?SEV_EPRINT("timeout when: "
+ "~n Sock: ~p"
+ "~n Ref: ~p"
+ "~n message queue: ~p",
+ [Sock, Ref, mq()]),
+ {error, timeout}
+ end;
+ (#{lsock := Sock,
+ accept_completion_info := {completion_info, _, Ref}} = State) ->
+ receive
+ {'$socket', Sock, completion, {Ref, _} = C} ->
+ {error, {unexpected_completion, C}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(lsock, State)}
+ after 5000 ->
+ ?SEV_EPRINT("timeout when: "
+ "~n Sock: ~p"
+ "~n Ref: ~p"
+ "~n message queue: ~p",
+ [Sock, Ref, mq()]),
{error, timeout}
end
end},
@@ -10073,22 +10612,25 @@ api_a_maccept_cancel_tcp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, accept)
end},
#{desc => "try accept request (with nowait, expect select)",
- cmd => fun(#{lsock := Sock,
- accept := Accept,
- accept_sref := SR} = State) ->
+ cmd => fun(#{lsock := Sock,
+ accept := Accept,
+ accept_ref := Ref} = State) ->
case Accept(Sock) of
- {select, SelectInfo} when SR =:= nowait ->
- {ok,
- State#{accept_select_info =>
- SelectInfo}};
- {select,
- {select_info, _Tag, SR} = SelectInfo}
- when is_reference(SR) ->
- {ok,
- State#{accept_select_info =>
- SelectInfo}};
+ {select, SI} when Ref =:= nowait ->
+ {ok, State#{accept_select_info => SI}};
+ {select, {select_info, _Tag, Ref} = SI}
+ when is_reference(Ref) ->
+ {ok, State#{accept_select_info => SI}};
+
+ {completion, CI} when Ref =:= nowait ->
+ {ok, State#{accept_completion_info => CI}};
+ {completion, {completion_info, _Tag, Ref} = CI}
+ when is_reference(Ref) ->
+ {ok, State#{accept_completion_info => CI}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
+
{error, _} = ERROR ->
ERROR
end
@@ -10107,7 +10649,26 @@ api_a_maccept_cancel_tcp(InitState) ->
{'$socket', Sock, abort, {Ref, closed}} ->
{ok, maps:remove(sock, State)}
after 5000 ->
- ?SEV_EPRINT("message queue: ~p", [mq()]),
+ ?SEV_EPRINT("timeout when: "
+ "~n Sock: ~p"
+ "~n Ref: ~p"
+ "~n message queue: ~p",
+ [Sock, Ref, mq()]),
+ {error, timeout}
+ end;
+ (#{lsock := Sock,
+ accept_completion_info := {completion_info, _, Ref}} = State) ->
+ receive
+ {'$socket', Sock, completion, {Ref, _} = C} ->
+ {error, {unexpected_completion, C}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(sock, State)}
+ after 5000 ->
+ ?SEV_EPRINT("timeout when: "
+ "~n Sock: ~p"
+ "~n Ref: ~p"
+ "~n message queue: ~p",
+ [Sock, Ref, mq()]),
{error, timeout}
end
end},
@@ -10125,8 +10686,9 @@ api_a_maccept_cancel_tcp(InitState) ->
?SEV_IPRINT("terminating"),
State1 = maps:remove(tester, State),
State2 = maps:remove(accept_select_info, State1),
- State3 = maps:remove(lsock, State2),
- {ok, State3};
+ State3 = maps:remove(accept_completion_info, State2),
+ State4 = maps:remove(lsock, State3),
+ {ok, State4};
{error, _} = ERROR ->
ERROR
end
@@ -10322,15 +10884,15 @@ api_a_maccept_cancel_tcp(InitState) ->
api_a_mrecv_cancel_tcp4(Config) when is_list(Config) ->
?TT(?SECS(20)),
Nowait = nowait(Config),
- tc_try(api_a_mrecv_cancel_tcp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
Recv = fun(Sock) ->
socket:recv(Sock, 0, Nowait)
end,
- InitState = #{domain => inet,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_mrecv_cancel_tcp(InitState)
end).
@@ -10345,15 +10907,15 @@ api_a_mrecv_cancel_tcp4(Config) when is_list(Config) ->
api_a_mrecv_cancel_tcp6(Config) when is_list(Config) ->
?TT(?SECS(20)),
Nowait = nowait(Config),
- tc_try(api_a_mrecv_cancel_tcp6,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
Recv = fun(Sock) ->
socket:recv(Sock, 0, Nowait)
end,
- InitState = #{domain => inet6,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet6,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_mrecv_cancel_tcp(InitState)
end).
@@ -10368,15 +10930,18 @@ api_a_mrecv_cancel_tcp6(Config) when is_list(Config) ->
api_a_mrecvmsg_cancel_tcp4(Config) when is_list(Config) ->
?TT(?SECS(20)),
Nowait = nowait(Config),
- tc_try(api_a_mrecvmsg_cancel_tcp4,
- fun() -> has_support_ipv4() end,
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
Recv = fun(Sock) ->
socket:recvmsg(Sock, Nowait)
end,
- InitState = #{domain => inet,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_mrecv_cancel_tcp(InitState)
end).
@@ -10391,15 +10956,18 @@ api_a_mrecvmsg_cancel_tcp4(Config) when is_list(Config) ->
api_a_mrecvmsg_cancel_tcp6(Config) when is_list(Config) ->
?TT(?SECS(20)),
Nowait = nowait(Config),
- tc_try(api_a_mrecvmsg_cancel_tcp6,
- fun() -> has_support_ipv6() end,
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv6()
+ end,
fun() ->
Recv = fun(Sock) ->
socket:recvmsg(Sock, Nowait)
end,
- InitState = #{domain => inet6,
- recv => Recv,
- recv_sref => Nowait},
+ InitState = #{domain => inet6,
+ recv => Recv,
+ recv_ref => Nowait},
ok = api_a_mrecv_cancel_tcp(InitState)
end).
@@ -10482,25 +11050,40 @@ api_a_mrecv_cancel_tcp(InitState) ->
cmd => fun(#{tester := Tester} = _State) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv)
end},
- #{desc => "try recv request (with nowait, expect select)",
- cmd => fun(#{csock := Sock,
- recv := Recv,
- recv_sref := SR} = State) ->
+ #{desc => "try recv request (with nowait, expect select|completion)",
+ cmd => fun(#{csock := Sock,
+ recv := Recv,
+ recv_ref := Ref} = State) ->
case Recv(Sock) of
- {select, {select_info, T, R} = SelectInfo}
- when SR =:= nowait ->
+ {select, {select_info, T, R} = SI}
+ when Ref =:= nowait ->
?SEV_IPRINT("recv select nowait: "
"~n Tag: ~p"
"~n Ref: ~p", [T, R]),
- {ok, State#{recv_select_info => SelectInfo}};
- {select, {select_info, T, SR} = SelectInfo}
- when is_reference(SR) ->
+ {ok, State#{recv_select_info => SI}};
+ {select, {select_info, T, Ref} = SI}
+ when is_reference(Ref) ->
?SEV_IPRINT("recv select nowait: "
"~n Tag: ~p"
- "~n Ref: ~p", [T, SR]),
- {ok, State#{recv_select_info => SelectInfo}};
+ "~n Ref: ~p", [T, Ref]),
+ {ok, State#{recv_select_info => SI}};
+
+ {completion, {completion_info, T, R} = CI}
+ when Ref =:= nowait ->
+ ?SEV_IPRINT("recv completion nowait: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [T, R]),
+ {ok, State#{recv_completion_info => CI}};
+ {completion, {completion_info, T, Ref} = CI}
+ when is_reference(Ref) ->
+ ?SEV_IPRINT("recv completion nowait: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [T, Ref]),
+ {ok, State#{recv_completion_info => CI}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
+
{error, _} = ERROR ->
ERROR
end
@@ -10510,7 +11093,7 @@ api_a_mrecv_cancel_tcp(InitState) ->
?SEV_ANNOUNCE_READY(Tester, recv_select),
ok
end},
- #{desc => "await select message",
+ #{desc => "await select|completion message",
cmd => fun(#{csock := Sock,
recv_select_info := {select_info, _, Ref}} = State) ->
receive
@@ -10520,6 +11103,16 @@ api_a_mrecv_cancel_tcp(InitState) ->
{ok, maps:remove(sock, State)}
after 5000 ->
ok
+ end;
+ (#{csock := Sock,
+ recv_completion_info := {completion_info, _, Ref}} = State) ->
+ receive
+ {'$socket', Sock, completion, {Ref, _} = C} ->
+ {error, {unexpected_completion, C}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(sock, State)}
+ after 5000 ->
+ ok
end
end},
#{desc => "announce ready (abort)",
@@ -10572,20 +11165,25 @@ api_a_mrecv_cancel_tcp(InitState) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv)
end},
#{desc => "try recv request (with nowait, expect select)",
- cmd => fun(#{sock := Sock,
- recv := Recv,
- recv_sref := SR} = State) ->
+ cmd => fun(#{sock := Sock,
+ recv := Recv,
+ recv_ref := Ref} = State) ->
case Recv(Sock) of
- {select, SelectInfo} when SR =:= nowait ->
- {ok,
- State#{recv_select_info => SelectInfo}};
- {select,
- {select_info, _Tag, SR} = SelectInfo}
- when is_reference(SR) ->
- {ok,
- State#{recv_select_info => SelectInfo}};
+ {select, SI} when Ref =:= nowait ->
+ {ok, State#{recv_select_info => SI}};
+ {select, {select_info, _Tag, Ref} = SI}
+ when is_reference(Ref) ->
+ {ok, State#{recv_select_info => SI}};
+
+ {completion, CI} when Ref =:= nowait ->
+ {ok, State#{recv_completion_info => CI}};
+ {completion, {completion_info, _Tag, Ref} = CI}
+ when is_reference(Ref) ->
+ {ok, State#{recv_completion_info => CI}};
+
{ok, X} ->
- {error, {unexpected_select_info, X}};
+ {error, {unexpected_success, X}};
+
{error, _} = ERROR ->
ERROR
end
@@ -10606,6 +11204,17 @@ api_a_mrecv_cancel_tcp(InitState) ->
after 5000 ->
?SEV_EPRINT("message queue: ~p", [mq()]),
{error, timeout}
+ end;
+ (#{sock := Sock,
+ recv_completion_info := {completion_info, _, Ref}} = State) ->
+ receive
+ {'$socket', Sock, completion, {Ref, _} = C} ->
+ {error, {unexpected_completion, C}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(sock, State)}
+ after 5000 ->
+ ?SEV_EPRINT("message queue: ~p", [mq()]),
+ {error, timeout}
end
end},
#{desc => "announce ready (abort)",
@@ -10621,9 +11230,10 @@ api_a_mrecv_cancel_tcp(InitState) ->
ok ->
?SEV_IPRINT("terminating"),
State1 = maps:remove(recv_select_info, State),
- State2 = maps:remove(tester, State1),
- State3 = maps:remove(sock, State2),
- {ok, State3};
+ State2 = maps:remove(recv_completion_info, State1),
+ State3 = maps:remove(tester, State2),
+ State4 = maps:remove(sock, State3),
+ {ok, State4};
{error, _} = ERROR ->
ERROR
end
@@ -11417,8 +12027,10 @@ api_opt_simple_otp_meta_option() ->
%% protocol = tcp.
api_opt_simple_otp_rcvbuf_option(_Config) when is_list(_Config) ->
?TT(?SECS(15)),
- tc_try(api_opt_simple_otp_rcvbuf_option,
- fun() -> api_opt_simple_otp_rcvbuf_option() end).
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ api_opt_simple_otp_rcvbuf_option()
+ end).
api_opt_simple_otp_rcvbuf_option() ->
Get = fun(S) ->
@@ -11557,6 +12169,12 @@ api_opt_simple_otp_rcvbuf_option() ->
end},
#{desc => "attempt to recv",
cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) ->
+ ?SEV_IPRINT("try recv ~w bytes when rcvbuf is ~s",
+ [MsgSz,
+ case Get(Sock) of
+ {ok, RcvBuf} -> f("~w", [RcvBuf]);
+ {error, _} -> "-"
+ end]),
case socket:recv(Sock) of
{ok, Data} when (size(Data) =:= MsgSz) ->
ok;
@@ -11614,6 +12232,10 @@ api_opt_simple_otp_rcvbuf_option() ->
cmd => fun(#{tester := Tester} = State) ->
case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
{ok, {ExpSz, NewRcvBuf}} ->
+ ?SEV_IPRINT("set new rcvbuf:"
+ "~n New RcvBuf: ~p"
+ "~n Expect Size: ~p",
+ [ExpSz, NewRcvBuf]),
{ok, State#{msg_sz => ExpSz,
rcvbuf => NewRcvBuf}};
{error, _} = ERROR ->
@@ -11624,7 +12246,8 @@ api_opt_simple_otp_rcvbuf_option() ->
cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) ->
case Set(Sock, NewRcvBuf) of
ok ->
- ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]),
+ ?SEV_IPRINT("set new rcvbuf: ~p",
+ [NewRcvBuf]),
ok;
{error, _} = ERROR ->
ERROR
@@ -11919,7 +12542,13 @@ api_opt_simple_otp_rcvbuf_option() ->
#{desc => "order server continue (recv 1)",
cmd => fun(#{server := Server, data := Data} = _State) ->
MsgSz = size(Data),
- NewRcvBuf = {2 + (MsgSz div 1024), 1024},
+ NewRcvBuf =
+ case os:type() of
+ {win32, nt} ->
+ (((2 * MsgSz) div 1024) + 1) * 1024;
+ _ ->
+ {2 + (MsgSz div 1024), 1024}
+ end,
?SEV_ANNOUNCE_CONTINUE(Server, recv, NewRcvBuf),
ok
end},
@@ -11956,7 +12585,13 @@ api_opt_simple_otp_rcvbuf_option() ->
#{desc => "order server continue (recv 2)",
cmd => fun(#{server := Server, data := Data} = _State) ->
MsgSz = size(Data),
- NewRcvBuf = {2 + (MsgSz div 2048), 2048},
+ NewRcvBuf =
+ case os:type() of
+ {win32, nt} ->
+ (((3 * MsgSz) div 1024) + 1) * 1024;
+ _ ->
+ {2 + (MsgSz div 2048), 2048}
+ end,
?SEV_ANNOUNCE_CONTINUE(Server, recv, NewRcvBuf),
ok
end},
@@ -11993,12 +12628,18 @@ api_opt_simple_otp_rcvbuf_option() ->
?SEV_SLEEP(?SECS(1)),
#{desc => "order server continue (recv 3)",
cmd => fun(#{server := Server, data := Data} = _State) ->
- MsgSz = size(Data),
- BufSz = 2048,
- N = MsgSz div BufSz - 1,
- NewRcvBuf = {N, BufSz},
+ MsgSz = size(Data),
+ BufSz = 2048,
+ N = MsgSz div BufSz - 1,
+ {ExpSz, NewRcvBuf} =
+ case os:type() of
+ {win32, nt} ->
+ {N*BufSz, N*BufSz};
+ _ ->
+ {N*BufSz, {N, BufSz}}
+ end,
?SEV_ANNOUNCE_CONTINUE(Server, recv,
- {N*BufSz, NewRcvBuf})
+ {ExpSz, NewRcvBuf})
end},
#{desc => "await client ready (send 3)",
cmd => fun(#{server := Server,
@@ -13233,6 +13874,11 @@ api_opt_sock_broadcast() ->
?SEV_IPRINT("Expected Success (bound): ~p",
[Port]),
{ok, State#{sa2 => BSA#{port => Port}}};
+ {error, eaddrnotavail = Reason} ->
+ ?SEV_IPRINT("~p => "
+ "SKIP subnet-directed broadcast test",
+ [Reason]),
+ {ok, State#{sa2 => skip}};
{error, Reason} = ERROR ->
?SEV_EPRINT("Unexpected Failure: ~p",
[Reason]),
@@ -13240,7 +13886,10 @@ api_opt_sock_broadcast() ->
end
end},
#{desc => "[socket 2] UDP socket sockname",
- cmd => fun(#{sock2 := Sock} = _State) ->
+ cmd => fun(#{sa2 := skip} = _State) ->
+ ?SEV_IPRINT("SKIP subnet-directed broadcast test"),
+ ok;
+ (#{sock2 := Sock} = _State) ->
case socket:sockname(Sock) of
{ok, SA} ->
?SEV_IPRINT("SA: ~p", [SA]),
@@ -13346,7 +13995,7 @@ api_opt_sock_broadcast() ->
#{desc => "[socket 3] try send to limited broadcast address",
cmd => fun(#{sa1 := skip} = _State) ->
- ?SEV_IPRINT("SKIP limited broadcast test"),
+ ?SEV_IPRINT("SKIP limited broadcast test (send)"),
ok;
(#{sock3 := Sock,
sa1 := Dest} = _State) ->
@@ -13366,7 +14015,7 @@ api_opt_sock_broadcast() ->
end},
#{desc => "[socket 1] try recv",
cmd => fun(#{sa1 := skip} = _State) ->
- ?SEV_IPRINT("SKIP limited broadcast test"),
+ ?SEV_IPRINT("SKIP limited broadcast test (recv)"),
ok;
(#{sock1 := Sock} = State) ->
case socket:recvfrom(Sock, 0, 5000) of
@@ -13393,8 +14042,12 @@ api_opt_sock_broadcast() ->
?SEV_SLEEP(?SECS(1)),
- #{desc => "[socket 3] try send to subnet-directed broadcast address",
- cmd => fun(#{sock3 := Sock,
+ #{desc => "[socket 2] try send to subnet-directed broadcast address",
+ cmd => fun(#{sa2 := skip} = _State) ->
+ ?SEV_IPRINT("SKIP subnet-directed broadcast test "
+ "(send)"),
+ ok;
+ (#{sock2 := Sock,
sa2 := Dest} = _State) ->
Data = list_to_binary("hejsan"),
?SEV_IPRINT("try send to broadcast address: "
@@ -13404,6 +14057,14 @@ api_opt_sock_broadcast() ->
?SEV_IPRINT("Expected Success: "
"broadcast message sent"),
ok;
+ {error, eaddrnotavail = Reason} ->
+ ?SEV_EPRINT("Unexpected Failure: ~p => SKIP",
+ [Reason]),
+ {skip, Reason};
+ {error, eacces = Reason} ->
+ ?SEV_EPRINT("Unexpected Failure: ~p => SKIP",
+ [Reason]),
+ {skip, Reason};
{error, Reason} = ERROR ->
?SEV_EPRINT("Unexpected Failure: ~p",
[Reason]),
@@ -13411,13 +14072,17 @@ api_opt_sock_broadcast() ->
end
end},
#{desc => "[socket 2] try recv",
- cmd => fun(#{sock2 := Sock, sa1 := SA1} = _State) ->
+ cmd => fun(#{sa2 := skip} = _State) ->
+ ?SEV_IPRINT("SKIP subnet-directed broadcast test "
+ "(recv)"),
+ ok;
+ (#{sock2 := Sock, sa2 := SA2} = _State) ->
case socket:recvfrom(Sock, 0, 5000) of
{ok, _} ->
?SEV_IPRINT("Expected Success: "
"received message"),
ok;
- {error, timeout = Reason} when (SA1 =:= skip) ->
+ {error, timeout = Reason} when (SA2 =:= skip) ->
?SEV_IPRINT("Unexpected Failure: ~p",
[Reason]),
{skip, "receive timeout"};
@@ -17136,7 +17801,11 @@ api_opt_sock_timeo(InitState) ->
api_opt_sock_rcvlowat_udp4(_Config) when is_list(_Config) ->
?TT(?SECS(5)),
tc_try(api_opt_sock_rcvlowat_udp4,
- fun() -> has_support_ipv4(), has_support_sock_rcvlowat() end,
+ fun() ->
+ is_not_windows(), % einval on Windows
+ has_support_ipv4(),
+ has_support_sock_rcvlowat()
+ end,
fun() ->
ok = api_opt_sock_lowat_udp4(rcvlowat)
end).
@@ -17155,7 +17824,11 @@ api_opt_sock_rcvlowat_udp4(_Config) when is_list(_Config) ->
api_opt_sock_sndlowat_udp4(_Config) when is_list(_Config) ->
?TT(?SECS(5)),
tc_try(api_opt_sock_sndlowat_udp4,
- fun() -> has_support_ipv4(), has_support_sock_sndlowat() end,
+ fun() ->
+ is_not_windows(), % einval on Windows
+ has_support_ipv4(),
+ has_support_sock_sndlowat()
+ end,
fun() ->
ok = api_opt_sock_lowat_udp4(sndlowat)
end).
@@ -18819,7 +19492,7 @@ which_local_host_ifname(Domain) ->
api_opt_ip_pktinfo_udp4(_Config) when is_list(_Config) ->
?TT(?SECS(5)),
- tc_try(api_opt_ip_pktinfo_udp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4(), has_support_ip_pktinfo() end,
fun() ->
Set = fun(Sock, Value) ->
@@ -18830,16 +19503,16 @@ api_opt_ip_pktinfo_udp4(_Config) when is_list(_Config) ->
end,
Send = fun(Sock, Data, Dest, default) ->
Msg = #{addr => Dest,
- iov => [Data]},
+ iov => [Data]},
socket:sendmsg(Sock, Msg);
(Sock, Data, Dest, Info) ->
%% We do not support this at the moment!!!
CMsg = #{level => ip,
- type => pktinfo,
- data => Info},
+ type => pktinfo,
+ data => Info},
Msg = #{addr => Dest,
- ctrl => [CMsg],
- iov => [Data]},
+ ctrl => [CMsg],
+ iov => [Data]},
socket:sendmsg(Sock, Msg)
end,
Recv = fun(Sock) ->
@@ -18928,18 +19601,18 @@ api_opt_ip_pktinfo_udp(InitState) ->
"~n ~p", [SADst]),
{ok, State#{sa_dst => SADst}}
end},
- #{desc => "default pktinfo for dst socket",
+ #{desc => "get default pktinfo for dst socket",
cmd => fun(#{sock_dst := Sock, get := Get} = _State) ->
case Get(Sock) of
{ok, false = Value} ->
?SEV_IPRINT("dst recvttl: ~p", [Value]),
ok;
{ok, Unexpected} ->
- ?SEV_EPRINT("Unexpected src recvtos: ~p",
+ ?SEV_EPRINT("Unexpected src pktinfo: ~p",
[Unexpected]),
{error, {unexpected, Unexpected}};
{error, Reason} = ERROR ->
- ?SEV_EPRINT("Failed getting (default) timestamp:"
+ ?SEV_EPRINT("Failed getting (default) pktinfo:"
" ~p", [Reason]),
ERROR
end
@@ -19881,6 +20554,7 @@ api_opt_ip_recvtos_udp4(_Config) when is_list(_Config) ->
?TT(?SECS(5)),
tc_try(api_opt_ip_recvtos_udp4,
fun() ->
+ is_not_windows(), % IP_TOS on windows
has_support_ipv4(),
has_support_ip_recvtos(),
has_support_ip_tos() % Used in the test
@@ -20250,10 +20924,15 @@ api_opt_ip_recvtos_udp(InitState) ->
%% Maybe we should send and receive from different VMs, until then
%% skip darwin and OpenBSD.
%%
+%% Windows:
+%% It seems like its possible to set and get the recvttl option,
+%% but not to use the ttl control message header when sending.
+%% The following is the list of types (for level ip) which are listed
+%% as supported: IP_ORIGINAL_ARRIVAL_IF, IP_PKTINFO and IP_ECN
api_opt_ip_recvttl_udp4(_Config) when is_list(_Config) ->
?TT(?SECS(5)),
- tc_try(api_opt_ip_recvttl_udp4,
+ tc_try(?FUNCTION_NAME,
fun() ->
has_support_ipv4(),
has_support_ip_recvttl(),
@@ -20269,15 +20948,15 @@ api_opt_ip_recvttl_udp4(_Config) when is_list(_Config) ->
end,
Send = fun(Sock, Data, Dest, default) ->
Msg = #{addr => Dest,
- iov => [Data]},
+ iov => [Data]},
socket:sendmsg(Sock, Msg);
(Sock, Data, Dest, TTL) ->
CMsg = #{level => ip,
- type => ttl,
- value => TTL},
+ type => ttl,
+ value => TTL},
Msg = #{addr => Dest,
- ctrl => [CMsg],
- iov => [Data]},
+ ctrl => [CMsg],
+ iov => [Data]},
socket:sendmsg(Sock, Msg)
end,
Recv = fun(Sock) ->
@@ -20442,6 +21121,69 @@ api_opt_ip_recvttl_udp(InitState) ->
(catch socket:close(SSock)),
(catch socket:close(DSock)),
{skip, Reason};
+
+ {error,
+ {get_overlapped_result,
+ #{file := File,
+ function := Function,
+ line := Line,
+ raw_info := RawInfo,
+ info := invalid_parameter = Info}}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TTL: "
+ "~p => SKIP: "
+ "~n File: ~s"
+ "~n Function: ~s"
+ "~n Line: ~p"
+ "~n Raw Info: ~p",
+ [Info,
+ File, Function, Line,
+ RawInfo]),
+ (catch socket:close(SSock)),
+ (catch socket:close(DSock)),
+ {skip,
+ ?F("Cannot send with TTL: ~p", [Info])};
+ {error, {get_overlapped_result,
+ invalid_parameter = Info}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TTL: "
+ "~p => SKIP", [Info]),
+ (catch socket:close(SSock)),
+ (catch socket:close(DSock)),
+ {skip,
+ ?F("Cannot send with TTL: ~p", [Info])};
+
+ {error,
+ {completion_status,
+ #{file := File,
+ function := Function,
+ line := Line,
+ raw_info := RawInfo,
+ info := invalid_parameter = Info}}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TTL: "
+ "~p => SKIP: "
+ "~n File: ~s"
+ "~n Function: ~s"
+ "~n Line: ~p"
+ "~n Raw Info: ~p",
+ [Info,
+ File, Function, Line,
+ RawInfo]),
+ (catch socket:close(SSock)),
+ (catch socket:close(DSock)),
+ {skip,
+ ?F("Cannot send with TTL: ~p", [Info])};
+ {error, {completion_status,
+ invalid_parameter = Info}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TTL: "
+ "~p => SKIP", [Info]),
+ (catch socket:close(SSock)),
+ (catch socket:close(DSock)),
+ {skip,
+ ?F("Cannot send with TTL: ~p", [Info])};
+
{error, _Reason} = ERROR ->
ERROR
end
@@ -20636,8 +21378,12 @@ api_opt_ip_recvttl_udp(InitState) ->
api_opt_ip_tos_udp4(_Config) when is_list(_Config) ->
?TT(?SECS(5)),
- tc_try(api_opt_ip_tos_udp4,
- fun() -> has_support_ipv4(), has_support_ip_tos() end,
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ is_not_windows(), % IP_TOS on windows
+ has_support_ipv4(),
+ has_support_ip_tos()
+ end,
fun() ->
Set = fun(Sock, Value) ->
socket:setopt(Sock, ip, tos, Value)
@@ -20992,16 +21738,33 @@ api_opt_recverr_udp(Config, InitState) ->
{select, SelectInfo} when RecvRef =:= nowait ->
?SEV_IPRINT("expected select nowait: "
"~n ~p", [SelectInfo]),
- {ok, State#{rselect => SelectInfo}};
+ {ok, State#{async_tag => select,
+ rselect => SelectInfo}};
{select,
{select_info, _Tag, RecvRef} = SelectInfo}
when is_reference(RecvRef) ->
?SEV_IPRINT("expected select ref: "
"~n ~p", [SelectInfo]),
- {ok, State#{rselect => SelectInfo}};
+ {ok, State#{async_tag => select,
+ rselect => SelectInfo}};
+
+ {completion, CI} when RecvRef =:= nowait ->
+ ?SEV_IPRINT("expected completion nowait: "
+ "~n ~p", [CI]),
+ {ok, State#{asynch_tag => completion,
+ rcompletion => CI}};
+ {completion,
+ {completion_info, _Tag, RecvRef} = CI}
+ when is_reference(RecvRef) ->
+ ?SEV_IPRINT("expected completion ref: "
+ "~n ~p", [CI]),
+ {ok, State#{asynch_tag => completion,
+ rcompletion => CI}};
+
{ok, _} ->
?SEV_EPRINT("unexpected success"),
{error, unexpected_success};
+
{error, Reason} = ERROR ->
?SEV_EPRINT("unexpected error: ~p", [Reason]),
ERROR
@@ -21010,8 +21773,8 @@ api_opt_recverr_udp(Config, InitState) ->
#{desc => "try send to nowhere",
cmd => fun(#{domain := Domain,
- sock := Sock,
- send := Send} = State) ->
+ sock := Sock,
+ send := Send} = State) ->
SendRef = nowait(Config),
Dest = #{family => Domain,
addr => if
@@ -21024,18 +21787,40 @@ api_opt_recverr_udp(Config, InitState) ->
case Send(Sock, <<"ping">>, Dest, SendRef) of
ok ->
?SEV_IPRINT("sent"),
- ok;
+ {ok, State#{sent => true}};
+
{select, SelectInfo}
when SendRef =:= nowait ->
?SEV_IPRINT("expected select nowait: ~p",
[SelectInfo]),
- {ok, State#{sselect => SelectInfo}};
+ {ok, State#{sent => false,
+ asynch_tag => select,
+ sselect => SelectInfo}};
{select,
{select_info, _Tag, SendRef} = SelectInfo}
when is_reference(SendRef) ->
?SEV_IPRINT("expected select ref: ~p",
[SelectInfo]),
- {ok, State#{sselect => SelectInfo}};
+ {ok, State#{sent => false,
+ asynch_tag => select,
+ sselect => SelectInfo}};
+
+ {completion, CI}
+ when SendRef =:= nowait ->
+ ?SEV_IPRINT("expected completion nowait: ~p",
+ [CI]),
+ {ok, State#{sent => false,
+ asynch_tag => completion,
+ scompletion => CI}};
+ {completion,
+ {completion_info, _Tag, SendRef} = CI}
+ when is_reference(SendRef) ->
+ ?SEV_IPRINT("expected completion ref: ~p",
+ [CI]),
+ {ok, State#{sent => false,
+ asynch_tag => completion,
+ scompletion => CI}};
+
{error, Reason} = ERROR ->
?SEV_EPRINT("unexpected error: ~p",
[Reason]),
@@ -21043,22 +21828,55 @@ api_opt_recverr_udp(Config, InitState) ->
end
end},
- #{desc => "await receive select message",
- cmd => fun(#{sock := Sock,
- rselect := {select_info, _, Ref}} = _State) ->
+ #{desc => "await receive select|completion message",
+ cmd => fun(#{sent := false,
+ asynch_tag := select,
+ sock := Sock,
+ rselect := {select_info, _, Ref}} = _State) ->
receive
{'$socket', Sock, select, Ref} ->
- ?SEV_IPRINT("received expected (read) select message: "
+ ?SEV_IPRINT("received expected (read) "
+ "select message: "
"~n ~p", [Ref]),
ok
- end
+ end;
+ (#{sent := false,
+ asynch_tag := completion,
+ sock := Sock,
+ rcompletion := {completion_info, _, Ref}} = _State) ->
+ receive
+ {'$socket', Sock, completion,
+ {Ref, {error, econnrefused = Reason}}} ->
+ ?SEV_IPRINT("expected failure: ~p",
+ [Reason]),
+ ok;
+
+ {'$socket', Sock, completion,
+ {Ref, {ok, _}}} ->
+ ?SEV_EPRINT("unexpected success"),
+ {error, unexpected_success};
+ {'$socket', Sock, completion,
+ {Ref, {error, Reason} = ERROR}} ->
+ ?SEV_IPRINT("unexpected failure: ~p",
+ [Reason]),
+ ERROR
+
+ end;
+ (#{sent := true} = _State) ->
+ ?SEV_IPRINT("no action needed"),
+ ok
end},
#{desc => "try recv - expect econnrefused",
- cmd => fun(#{sock := Sock, recv := Recv} = _State) ->
+ cmd => fun(#{asynch_tag := completion} = _State) ->
+ ?SEV_IPRINT("already processed"),
+ ok;
+ (#{sock := Sock,
+ recv := Recv} = _State) ->
case Recv(Sock, infinity) of
{error, econnrefused = Reason} ->
- ?SEV_IPRINT("expected failure: ~p", [Reason]),
+ ?SEV_IPRINT("expected failure: ~p",
+ [Reason]),
ok;
{ok, _} ->
?SEV_EPRINT("unexpected success"),
@@ -21096,10 +21914,17 @@ api_opt_recverr_udp(Config, InitState) ->
{ok, {Addr, <<"ring">>}} ->
?SEV_IPRINT("receive expected"),
ok;
+
{select, SelectInfo} ->
?SEV_EPRINT("unexpected select: ~p",
[SelectInfo]),
{error, unexpected_success};
+
+ {completion, CompletionInfo} ->
+ ?SEV_EPRINT("unexpected completion: ~p",
+ [CompletionInfo]),
+ {error, unexpected_success};
+
{error, Reason} = ERROR ->
?SEV_EPRINT("unexpected error: ~p",
[Reason]),
@@ -21108,7 +21933,7 @@ api_opt_recverr_udp(Config, InitState) ->
end},
#{desc => "try recv error queue",
- cmd => fun(#{domain := Domain, sock := Sock}) ->
+ cmd => fun(#{domain := Domain, sock := Sock} = State) ->
%% Note that not all platforms that support
%% recverr, actually supports "encoding" the data
%% part, so we need to adjust for that.
@@ -21139,7 +21964,7 @@ api_opt_recverr_udp(Config, InitState) ->
}]} = Msg} ->
?SEV_IPRINT("expected error queue (decoded): "
"~n ~p", [Msg]),
- ok;
+ {ok, State#{asynch_tag => none}};
{ok, #{addr := #{family := Domain,
addr := _Addr},
flags := [errqueue],
@@ -21147,7 +21972,30 @@ api_opt_recverr_udp(Config, InitState) ->
value := [#{level := Level,
type := recverr}]} = _Msg} ->
?SEV_IPRINT("expected error queue"),
- ok;
+ {ok, State#{asynch_tag => none}};
+
+ {completion, CI} ->
+ ?SEV_IPRINT("completion: "
+ "~n ~p", [CI]),
+ {ok, State#{asynch_tag => completion,
+ completion => CI}};
+
+ {error, timeout = Reason} = ERROR ->
+ case os:type() of
+ {win32, nt} ->
+ ?SEV_IPRINT("failed reading "
+ "error queue: "
+ "~n ~p", [Reason]),
+ {skip,
+ "Test case does not "
+ "work on Windows"};
+ _ ->
+ ?SEV_EPRINT("failed reading "
+ "error queue: "
+ "~n ~p", [Reason]),
+ ERROR
+ end;
+
{error, Reason} = ERROR ->
?SEV_EPRINT("failed reading error queue: "
"~n ~p", [Reason]),
@@ -21155,6 +22003,29 @@ api_opt_recverr_udp(Config, InitState) ->
end
end},
+ #{desc => "await receive select message",
+ cmd => fun(#{asynch_tag := completion,
+ sock := Sock,
+ completion := {completion_info, _, Ref}} = _State) ->
+ receive
+ {'$socket', Sock, completion,
+ {Ref, {ok, Info}}} ->
+ ?SEV_EPRINT("expected success: "
+ "~n ~p", [Info]),
+ ok;
+
+ {'$socket', Sock, completion,
+ {Ref, {error, Reason} = ERROR}} ->
+ ?SEV_IPRINT("unexpected failure: ~p",
+ [Reason]),
+ ERROR
+
+ end;
+ (#{asynch_tag := none} = _State) ->
+ ?SEV_IPRINT("no action needed"),
+ ok
+ end},
+
#{desc => "close socket",
cmd => fun(#{sock := Sock} = State) ->
ok = socket:close(Sock),
@@ -21201,14 +22072,20 @@ api_opt_recverr_udp(Config, InitState) ->
api_opt_ip_mopts_udp4(_Config) when is_list(_Config) ->
?TT(?SECS(5)),
- tc_try(api_opt_ip_mopts_udp4,
+ tc_try(?FUNCTION_NAME,
fun() ->
has_support_ipv4(),
- case is_any_options_supported(
- [{ip, pktinfo},
- {ip, recvorigdstaddr},
- {ip, recvtos},
- {ip, recvttl}]) of
+ Opts =
+ [{ip, pktinfo},
+ {ip, recvorigdstaddr}] ++
+ case os:type() of
+ {win32, nt} ->
+ [];
+ _ ->
+ [{ip, recvtos},
+ {ip, recvttl}]
+ end,
+ case is_any_options_supported(Opts) of
true ->
ok;
false ->
@@ -21244,42 +22121,49 @@ api_opt_ip_mopts_udp4(_Config) when is_list(_Config) ->
false ->
[]
end ++
- case socket:is_supported(options, ip, recvtos) of
- true ->
- %% It seems that sending any of the
- %% TOS or TTL values will fail on:
- %% FreeBSD
- %% Linux when
- %% version =< 3.12.60 (at least)
- %% Don't know when this starts working,
- %% but it works on:
- %% Ubunto 16.04.6 => 4.15.0-65
- %% SLES 12 SP2 => 4.4.120-92.70
- %% so don't!
- %%
- %% The latest we know it not to work was a
- %% SLES 12 (plain) at 3.12.50-52.54
- %%
- [{ip, recvtos, tos,
- case os:type() of
- {unix, freebsd} ->
- default;
- {unix, linux} ->
- case os:version() of
- Vsn when Vsn > {3,12,60} ->
- 42;
- _ ->
- default
- end;
- _ ->
- 42
- end}];
- false ->
- []
+ case os:type() of
+ {win32, nt} ->
+ [];
+ _ ->
+ case socket:is_supported(options, ip, recvtos) of
+ true ->
+ %% It seems that sending any of the
+ %% TOS or TTL values will fail on:
+ %% FreeBSD
+ %% Linux when
+ %% version =< 3.12.60 (at least)
+ %% Don't know when this starts
+ %% working, but it works on:
+ %% Ubunto 16.04.6 => 4.15.0-65
+ %% SLES 12 SP2 => 4.4.120-92.70
+ %% so don't!
+ %%
+ %% The latest we know it not to work
+ %% was a SLES 12 (plain) at 3.12.50-52.54
+ %%
+ [{ip, recvtos, tos,
+ case os:type() of
+ {unix, freebsd} ->
+ default;
+ {unix, linux} ->
+ case os:version() of
+ Vsn when Vsn > {3,12,60} ->
+ 42;
+ _ ->
+ default
+ end;
+ _ ->
+ 42
+ end}];
+ false ->
+ []
+ end
end ++
case os:type() of
{unix, darwin} ->
[];
+ {win32, nt} ->
+ [];
_ ->
case socket:is_supported(options, ip, recvttl) of
true ->
@@ -21314,21 +22198,22 @@ api_opt_ip_mopts_udp4(_Config) when is_list(_Config) ->
end,
Enable = fun(Sock, Level, Opt) ->
- ?SEV_IPRINT("try enable [~w] ~p", [Level, Opt]),
+ ?SEV_IPRINT("try enable [~w] ~p",
+ [Level, Opt]),
socket:setopt(Sock, Level, Opt, true)
end,
Send = fun(Sock, Data, Dest, []) ->
Msg = #{addr => Dest,
- iov => [Data]},
+ iov => [Data]},
socket:sendmsg(Sock, Msg);
(Sock, Data, Dest, Hdrs) when is_list(Hdrs) ->
CMsgs = [#{level => Level,
- type => Type,
- value => Val} ||
- {Level, Type, Val} <- Hdrs],
+ type => Type,
+ value => Val} ||
+ {Level, Type, Val} <- Hdrs],
Msg = #{addr => Dest,
- ctrl => CMsgs,
- iov => [Data]},
+ ctrl => CMsgs,
+ iov => [Data]},
socket:sendmsg(Sock, Msg)
end,
Recv = fun(Sock) ->
@@ -22592,7 +23477,68 @@ api_opt_ipv6_tclass_udp(InitState) ->
#{desc => "send req (to dst) (w explicit tc = 1)",
cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
- Send(Sock, ?BASIC_REQ, Dst, 1)
+ case Send(Sock, ?BASIC_REQ, Dst, 1) of
+ {error,
+ {get_overlapped_result,
+ #{file := File,
+ function := Function,
+ line := Line,
+ raw_info := RawInfo,
+ info := invalid_parameter = Info}}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TClass: "
+ "~p => SKIP: "
+ "~n File: ~s"
+ "~n Function: ~s"
+ "~n Line: ~p"
+ "~n Raw Info: ~p",
+ [Info,
+ File, Function, Line,
+ RawInfo]),
+ (catch socket:close(Sock)),
+ {skip,
+ ?F("Cannot send with TClass: ~p", [Info])};
+ {error, {get_overlapped_result,
+ invalid_parameter = Info}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TClass: "
+ "~p => SKIP", [Info]),
+ (catch socket:close(Sock)),
+ {skip,
+ ?F("Cannot send with TClass: ~p", [Info])};
+
+ {error,
+ {completion_status,
+ #{file := File,
+ function := Function,
+ line := Line,
+ raw_info := RawInfo,
+ info := invalid_parameter = Info}}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TClass: "
+ "~p => SKIP: "
+ "~n File: ~s"
+ "~n Function: ~s"
+ "~n Line: ~p"
+ "~n Raw Info: ~p",
+ [Info,
+ File, Function, Line,
+ RawInfo]),
+ (catch socket:close(Sock)),
+ {skip,
+ ?F("Cannot send with TClass: ~p", [Info])};
+ {error, {completion_status,
+ invalid_parameter = Info}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TClass: "
+ "~p => SKIP", [Info]),
+ (catch socket:close(Sock)),
+ {skip,
+ ?F("Cannot send with TClass: ~p", [Info])};
+
+ Other ->
+ Other
+ end
end},
#{desc => "recv req (from src)",
cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
@@ -22639,7 +23585,8 @@ api_opt_ipv6_tclass_udp(InitState) ->
value => "something"},
?BASIC_REQ, UnexpData]),
{error, {unexpected_data, UnexpData}};
- {error, _} = ERROR ->
+
+ {error, _} = ERROR ->
%% At the moment there is no way to get
%% status or state for the socket...
ERROR
@@ -22700,13 +23647,19 @@ api_opt_ipv6_mopts_udp6(_Config) when is_list(_Config) ->
tc_try(api_opt_ipv6_mopts_udp6,
fun() ->
has_support_ipv6(),
- case is_any_options_supported(
- [{ipv6, recvpktinfo},
- {ipv6, flowinfo},
- {ipv6, recvhoplimit},
- {ipv6, hoplimit},
- {ipv6, recvtclass},
- {ipv6, tclass}]) of
+ Opts =
+ [{ipv6, recvpktinfo},
+ {ipv6, flowinfo},
+ {ipv6, recvhoplimit},
+ {ipv6, hoplimit}] ++
+ case os:type() of
+ {win32, nt} ->
+ [];
+ _ ->
+ [{ipv6, recvtclass},
+ {ipv6, tclass}]
+ end,
+ case is_any_options_supported(Opts) of
true ->
ok;
false ->
@@ -22755,20 +23708,28 @@ api_opt_ipv6_mopts_udp6(_Config) when is_list(_Config) ->
[]
end
end ++
- case socket:is_supported(options, ipv6, recvtclass) of
- true ->
- [{ipv6, recvtclass, tclass, 42}];
- false ->
- case socket:is_supported(options, ipv6, tclass) of
- true ->
- [{ipv6, tclass, tclass, 42}];
- false ->
- []
- end
+ case os:type() of
+ {win32, nt} ->
+ [];
+ _ ->
+ case socket:is_supported(options,
+ ipv6, recvtclass) of
+ true ->
+ [{ipv6, recvtclass, tclass, 42}];
+ false ->
+ case socket:is_supported(options,
+ ipv6, tclass) of
+ true ->
+ [{ipv6, tclass, tclass, 42}];
+ false ->
+ []
+ end
+ end
end,
Enable = fun(Sock, Level, Opt) ->
- ?SEV_IPRINT("try enable [~w] ~p", [Level, Opt]),
+ ?SEV_IPRINT("try enable [~w] ~p",
+ [Level, Opt]),
socket:setopt(Sock, Level, Opt, true)
end,
Send = fun(Sock, Data, Dest, []) ->
@@ -22777,12 +23738,12 @@ api_opt_ipv6_mopts_udp6(_Config) when is_list(_Config) ->
socket:sendmsg(Sock, Msg);
(Sock, Data, Dest, Hdrs) when is_list(Hdrs) ->
CMsgs = [#{level => Level,
- type => Type,
- data => Val} ||
- {Level, Type, Val} <- Hdrs],
+ type => Type,
+ data => Val} ||
+ {Level, Type, Val} <- Hdrs],
Msg = #{addr => Dest,
- ctrl => CMsgs,
- iov => [Data]},
+ ctrl => CMsgs,
+ iov => [Data]},
socket:sendmsg(Sock, Msg)
end,
Recv = fun(Sock) ->
@@ -23293,8 +24254,11 @@ api_opt_tcp_cork_tcp(InitState) ->
api_opt_tcp_maxseg_tcp4(_Config) when is_list(_Config) ->
?TT(?SECS(5)),
- tc_try(api_opt_tcp_maxseg_tcp4,
- fun() -> has_support_ipv4(), has_support_tcp_maxseg() end,
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ has_support_ipv4(),
+ has_support_tcp_maxseg()
+ end,
fun() ->
Set = fun(Sock, Value) when is_integer(Value) ->
socket:setopt(Sock, tcp, maxseg, Value)
@@ -23353,6 +24317,8 @@ api_opt_tcp_maxseg_tcp(InitState) ->
{ok, DefMaxSeg} ->
?SEV_IPRINT("maxseg default: ~p", [DefMaxSeg]),
{ok, State#{def_maxseg => DefMaxSeg}};
+ {error, enoprotoopt = Reason} ->
+ {skip, Reason};
{error, _} = ERROR ->
ERROR
end
@@ -31638,10 +32604,12 @@ sc_lc_receive_response_tcp(InitState) ->
sc_lc_recvfrom_response_udp4(_Config) when is_list(_Config) ->
?TT(?SECS(30)),
- tc_try(sc_lc_recvfrom_response_udp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
- Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end,
+ Recv = fun(Sock, To) ->
+ socket:recvfrom(Sock, [], To)
+ end,
InitState = #{domain => inet,
protocol => udp,
recv => Recv},
@@ -31656,10 +32624,12 @@ sc_lc_recvfrom_response_udp4(_Config) when is_list(_Config) ->
sc_lc_recvfrom_response_udp6(_Config) when is_list(_Config) ->
?TT(?SECS(30)),
- tc_try(sc_lc_recvfrom_response_udp6,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
- Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end,
+ Recv = fun(Sock, To) ->
+ socket:recvfrom(Sock, [], To)
+ end,
InitState = #{domain => inet6,
protocol => udp,
recv => Recv},
@@ -31674,7 +32644,7 @@ sc_lc_recvfrom_response_udp6(_Config) when is_list(_Config) ->
sc_lc_recvfrom_response_udpL(_Config) when is_list(_Config) ->
?TT(?SECS(30)),
- tc_try(sc_lc_recvfrom_response_udpL,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_unix_domain_socket() end,
fun() ->
Recv = fun(Sock, To) ->
@@ -31713,10 +32683,24 @@ sc_lc_receive_response_udp(InitState) ->
#{desc => "open socket",
cmd => fun(#{domain := Domain, protocol := Proto} = State) ->
Sock = sock_open(Domain, dgram, Proto),
- %% SA = sock_sockname(Sock),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "bind socket",
+ cmd => fun(#{sock := Sock, local_sa := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ ok ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "socket name",
+ cmd => fun(#{sock := Sock} = State) ->
case socket:sockname(Sock) of
{ok, SA} ->
- {ok, State#{sock => Sock, sa => SA}};
+ {ok, State#{sa => SA}};
{error, eafnosupport = Reason} ->
?SEV_IPRINT("Failed get socket name: "
"~n ~p", [Reason]),
@@ -31728,17 +32712,6 @@ sc_lc_receive_response_udp(InitState) ->
ERROR
end
end},
- #{desc => "bind socket",
- cmd => fun(#{sock := Sock, local_sa := LSA}) ->
- case sock_bind(Sock, LSA) of
- ok ->
- ?SEV_IPRINT("src bound"),
- ok;
- {error, Reason} = ERROR ->
- ?SEV_EPRINT("src bind failed: ~p", [Reason]),
- ERROR
- end
- end},
#{desc => "announce ready (init)",
cmd => fun(#{tester := Tester, sock := Sock}) ->
?SEV_ANNOUNCE_READY(Tester, init, Sock),
@@ -32099,8 +33072,11 @@ sc_lc_receive_response_udp(InitState) ->
sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) ->
?TT(?SECS(10)),
- tc_try(sc_lc_recvmsg_response_tcp4,
- fun() -> has_support_ipv4() end,
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
Recv = fun(Sock) -> socket:recvmsg(Sock) end,
InitState = #{domain => inet,
@@ -32117,8 +33093,11 @@ sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) ->
sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) ->
?TT(?SECS(10)),
- tc_try(sc_recvmsg_response_tcp6,
- fun() -> has_support_ipv6() end,
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv6()
+ end,
fun() ->
Recv = fun(Sock) -> socket:recvmsg(Sock) end,
InitState = #{domain => inet6,
@@ -32671,7 +33650,7 @@ sc_lc_acceptor_response_tcp(InitState) ->
sc_rc_recv_response_tcp4(_Config) when is_list(_Config) ->
?TT(?SECS(30)),
- tc_try(sc_rc_recv_response_tcp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
Recv = fun(Sock) -> socket:recv(Sock) end,
@@ -32689,7 +33668,7 @@ sc_rc_recv_response_tcp4(_Config) when is_list(_Config) ->
sc_rc_recv_response_tcp6(_Config) when is_list(_Config) ->
?TT(?SECS(30)),
- tc_try(sc_rc_recv_response_tcp6,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
Recv = fun(Sock) -> socket:recv(Sock) end,
@@ -32707,7 +33686,7 @@ sc_rc_recv_response_tcp6(_Config) when is_list(_Config) ->
sc_rc_recv_response_tcpL(_Config) when is_list(_Config) ->
?TT(?SECS(30)),
- tc_try(sc_rc_recv_response_tcpL,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_unix_domain_socket() end,
fun() ->
Recv = fun(Sock) -> socket:recv(Sock) end,
@@ -32865,6 +33844,8 @@ sc_rc_receive_response_tcp(InitState) ->
?SEV_ANNOUNCE_READY(Tester, accept),
ok
end},
+
+
#{desc => "await continue (recv)",
cmd => fun(#{tester := Tester} = _State) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv)
@@ -32885,19 +33866,31 @@ sc_rc_receive_response_tcp(InitState) ->
ok
end},
#{desc => "await ready from handler 1 (recv)",
- cmd => fun(#{tester := Tester, handler1 := Pid} = _State) ->
- case ?SEV_AWAIT_READY(Pid, handler1, recv,
- [{tester, Tester}]) of
+ cmd => fun(#{tester := Tester,
+ handler1 := Pid1,
+ handler2 := Pid2,
+ handler3 := Pid3} = _State) ->
+ case ?SEV_AWAIT_READY(Pid1, handler1, recv,
+ [{tester, Tester},
+ {handler2, Pid2},
+ {handler3, Pid3}]) of
{ok, Result} ->
Result;
- {error, _} = ERROR ->
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected failure: "
+ "~n ~p", [Reason]),
ERROR
end
end},
#{desc => "await ready from handler 2 (recv)",
- cmd => fun(#{tester := Tester, handler2 := Pid} = _State) ->
- case ?SEV_AWAIT_READY(Pid, handler2, recv,
- [{tester, Tester}]) of
+ cmd => fun(#{tester := Tester,
+ handler1 := Pid1,
+ handler2 := Pid2,
+ handler3 := Pid3} = _State) ->
+ case ?SEV_AWAIT_READY(Pid2, handler2, recv,
+ [{tester, Tester},
+ {handler1, Pid1},
+ {handler3, Pid3}]) of
{ok, Result} ->
Result;
{error, _} = ERROR ->
@@ -32905,9 +33898,14 @@ sc_rc_receive_response_tcp(InitState) ->
end
end},
#{desc => "await ready from handler 3 (recv)",
- cmd => fun(#{tester := Tester, handler3 := Pid} = _State) ->
- case ?SEV_AWAIT_READY(Pid, handler3, recv,
- [{tester, Tester}]) of
+ cmd => fun(#{tester := Tester,
+ handler1 := Pid1,
+ handler2 := Pid2,
+ handler3 := Pid3} = _State) ->
+ case ?SEV_AWAIT_READY(Pid3, handler3, recv,
+ [{tester, Tester},
+ {handler1, Pid1},
+ {handler2, Pid2}]) of
{ok, Result} ->
Result;
{error, _} = ERROR ->
@@ -33464,8 +34462,8 @@ sc_rc_receive_response_tcp(InitState) ->
i("await evaluator"),
ok = ?SEV_AWAIT_FINISH([Server,
- Client1, Client2, Client3,
- Tester]).
+ Client1, Client2, Client3,
+ Tester]).
sc_rc_tcp_client_start(Node) ->
@@ -33608,14 +34606,20 @@ sc_rc_tcp_handler_recv(Recv, Sock) ->
try Recv(Sock) of
{error, closed} ->
ok;
- {ok, _} ->
- ?SEV_IPRINT("unexpected success"),
+ {ok, Data} ->
+ ?SEV_IPRINT("unexpected success: "
+ "~n (Unexp) Data: ~p"
+ "~n Socket Info: ~p", [Data, socket:info(Sock)]),
{error, unexpected_success};
{error, Reason} = ERROR ->
?SEV_IPRINT("receive error: "
"~n ~p", [Reason]),
ERROR
catch
+ error:notsup = Error:Stack ->
+ ?SEV_IPRINT("receive ~w error: skip"
+ "~n Stack: ~p", [Error, Stack]),
+ exit({skip, Error});
C:E:S ->
?SEV_IPRINT("receive failure: "
"~n Class: ~p"
@@ -33699,7 +34703,7 @@ sc_rc_recvmsg_response_tcpL(_Config) when is_list(_Config) ->
sc_rs_recv_send_shutdown_receive_tcp4(_Config) when is_list(_Config) ->
?TT(?SECS(30)),
- tc_try(sc_rs_recv_send_shutdown_receive_tcp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
MsgData = ?DATA,
@@ -33726,7 +34730,7 @@ sc_rs_recv_send_shutdown_receive_tcp4(_Config) when is_list(_Config) ->
%% Socket is IPv6.
sc_rs_recv_send_shutdown_receive_tcp6(_Config) when is_list(_Config) ->
- tc_try(sc_rs_recv_send_shutdown_receive_tcp6,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
?TT(?SECS(10)),
@@ -33755,7 +34759,7 @@ sc_rs_recv_send_shutdown_receive_tcp6(_Config) when is_list(_Config) ->
sc_rs_recv_send_shutdown_receive_tcpL(_Config) when is_list(_Config) ->
?TT(?SECS(10)),
- tc_try(sc_rs_recv_send_shutdown_receive_tcpL,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_unix_domain_socket() end,
fun() ->
MsgData = ?DATA,
@@ -34480,11 +35484,14 @@ sc_rs_tcp_client_connect(Sock, ServerSA) ->
sc_rs_tcp_client_send(Sock, Send, Data) ->
i("sc_rs_tcp_client_send -> entry"),
- case Send(Sock, Data) of
+ try Send(Sock, Data) of
ok ->
ok;
{error, Reason} ->
exit({send, Reason})
+ catch
+ error : notsup = Reason : _ ->
+ exit({skip, Reason})
end.
sc_rs_tcp_client_shutdown(Sock) ->
@@ -34568,6 +35575,8 @@ sc_rs_tcp_handler_recv(Recv, Sock, First) ->
"~n ~p", [Reason]),
ERROR
catch
+ error : notsup = Reason : _ ->
+ exit({skip, Reason});
C:E:S ->
?SEV_IPRINT("receive failure: "
"~n Class: ~p"
@@ -34590,7 +35599,7 @@ sc_rs_tcp_handler_announce_ready(Parent, Slogan, Result) ->
%% Socket is IPv4.
sc_rs_recvmsg_send_shutdown_receive_tcp4(_Config) when is_list(_Config) ->
- tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp4,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
?TT(?SECS(30)),
@@ -34626,7 +35635,7 @@ sc_rs_recvmsg_send_shutdown_receive_tcp4(_Config) when is_list(_Config) ->
%% Socket is IPv6.
sc_rs_recvmsg_send_shutdown_receive_tcp6(_Config) when is_list(_Config) ->
- tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp6,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
?TT(?SECS(10)),
@@ -34663,7 +35672,7 @@ sc_rs_recvmsg_send_shutdown_receive_tcp6(_Config) when is_list(_Config) ->
sc_rs_recvmsg_send_shutdown_receive_tcpL(_Config) when is_list(_Config) ->
?TT(?SECS(10)),
- tc_try(sc_rs_recvmsg_send_shutdown_receive_tcpL,
+ tc_try(?FUNCTION_NAME,
fun() -> has_support_unix_domain_socket() end,
fun() ->
{ok, CWD} = file:get_cwd(),
@@ -35587,7 +36596,10 @@ traffic_send_and_recv_counters_tcpL(_Config) when is_list(_Config) ->
traffic_sendmsg_and_recvmsg_counters_tcp4(_Config) when is_list(_Config) ->
?TT(?SECS(15)),
tc_try(traffic_sendmsg_and_recvmsg_counters_tcp4,
- fun() -> has_support_ipv4() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
InitState = #{domain => inet,
proto => tcp,
@@ -35616,7 +36628,10 @@ traffic_sendmsg_and_recvmsg_counters_tcp4(_Config) when is_list(_Config) ->
traffic_sendmsg_and_recvmsg_counters_tcp6(_Config) when is_list(_Config) ->
?TT(?SECS(15)),
tc_try(traffic_sendmsg_and_recvmsg_counters_tcp6,
- fun() -> has_support_ipv6() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv6()
+ end,
fun() ->
InitState = #{domain => inet6,
proto => tcp,
@@ -39199,7 +40214,10 @@ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) ->
Msg = l2b(?TPP_SMALL),
Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4,
- fun() -> has_support_ipv4() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
?TT(?SECS(20)),
InitState = #{domain => inet,
@@ -39223,7 +40241,10 @@ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(Config) when is_list(Config) ->
Msg = l2b(?TPP_SMALL),
Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6,
- fun() -> has_support_ipv6() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv6()
+ end,
fun() ->
?TT(?SECS(20)),
InitState = #{domain => inet6,
@@ -39271,7 +40292,10 @@ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) -
Msg = l2b(?TPP_MEDIUM),
Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4,
- fun() -> has_support_ipv4() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
?TT(?SECS(30)),
InitState = #{domain => inet,
@@ -39295,7 +40319,10 @@ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(Config) when is_list(Config) -
Msg = l2b(?TPP_MEDIUM),
Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6,
- fun() -> has_support_ipv6() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv6()
+ end,
fun() ->
?TT(?SECS(30)),
InitState = #{domain => inet6,
@@ -39343,7 +40370,11 @@ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) ->
Msg = l2b(?TPP_LARGE),
Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM),
tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4,
- fun() -> has_support_ipv4(), traffic_ping_pong_large_sendmsg_and_recvmsg_cond() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4(),
+ traffic_ping_pong_large_sendmsg_and_recvmsg_cond()
+ end,
fun() ->
?TT(?SECS(60)),
InitState = #{domain => inet,
@@ -39378,6 +40409,7 @@ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(Config) when is_list(Config) ->
Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM),
tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6,
fun() ->
+ is_not_windows(),
has_support_ipv6(),
traffic_ping_pong_large_sendmsg_and_recvmsg_cond()
end,
@@ -39661,7 +40693,12 @@ traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) ->
true ->
ok
end,
- ok = socket:setopt(Sock, otp, rcvbuf, {12, 1024})
+ case os:type() of
+ {win32, nt} ->
+ ok = socket:setopt(Sock, otp, rcvbuf, 12*1024);
+ _ ->
+ ok = socket:setopt(Sock, otp, rcvbuf, {12, 1024})
+ end
end,
traffic_ping_pong_send_and_receive_tcp2(InitState#{buf_init => Fun}).
@@ -40178,36 +41215,63 @@ traffic_ping_pong_send_and_receive_tcp2(InitState) ->
{CSent, CReceived, _, CStart, CStop} = CRes,
STime = tdiff(SStart, SStop),
CTime = tdiff(CStart, CStop),
- %% Note that the sizes we are counting is only
- %% the "data" part of the messages. There is also
- %% fixed header for each message, which of course
- %% is small for the large messages, but comparatively
- %% big for the small messages!
- ?SEV_IPRINT("Results: ~w messages exchanged"
- "~n Server: ~w msec"
- "~n ~.2f msec/message (roundtrip)"
- "~n ~.2f messages/msec (roundtrip)"
- "~n ~w bytes/msec sent"
- "~n ~w bytes/msec received"
- "~n Client: ~w msec"
- "~n ~.2f msec/message (roundtrip)"
- "~n ~.2f messages/msec (roundtrip)"
- "~n ~w bytes/msec sent"
- "~n ~w bytes/msec received",
+ ?SEV_IPRINT("process result data:"
+ "~n Num: ~p"
+ "~n Server Sent: ~p"
+ "~n Server Recv: ~p"
+ "~n Server Start: ~p"
+ "~n Server Stop: ~p"
+ "~n Server Time: ~p"
+ "~n Client Sent: ~p"
+ "~n Client Recv: ~p"
+ "~n Client Start: ~p"
+ "~n Client Stop: ~p"
+ "~n Client Time: ~p",
[Num,
+ SSent, SReceived, SStart, SStop,
STime,
- STime / Num,
- Num / STime,
- SSent div STime,
- SReceived div STime,
- CTime,
- CTime / Num,
- Num / CTime,
- CSent div CTime,
- CReceived div CTime]),
- State1 = maps:remove(server_result, State),
- State2 = maps:remove(client_result, State1),
- {ok, State2}
+ CSent, CReceived, CStart, CStop,
+ CTime]),
+ if
+ (STime =:= 0) orelse
+ (CTime =:= 0) ->
+ {skip,
+ ?F("Invalid exec time(s): ~w , ~w",
+ [STime, CTime])};
+ true ->
+ %% Note that the sizes we are counting is
+ %% only the "data" part of the messages.
+ %% There is also fixed header for each
+ %% message, which of course is small for
+ %% the large messages, but comparatively
+ %% big for the small messages!
+ ?SEV_IPRINT(
+ "Results: ~w messages exchanged"
+ "~n Server: ~w msec"
+ "~n ~.2f msec/message (roundtrip)"
+ "~n ~.2f messages/msec (roundtrip)"
+ "~n ~w bytes/msec sent"
+ "~n ~w bytes/msec received"
+ "~n Client: ~w msec"
+ "~n ~.2f msec/message (roundtrip)"
+ "~n ~.2f messages/msec (roundtrip)"
+ "~n ~w bytes/msec sent"
+ "~n ~w bytes/msec received",
+ [Num,
+ STime,
+ STime / Num,
+ Num / STime,
+ SSent div STime,
+ SReceived div STime,
+ CTime,
+ CTime / Num,
+ Num / CTime,
+ CSent div CTime,
+ CReceived div CTime]),
+ State1 = maps:remove(server_result, State),
+ State2 = maps:remove(client_result, State1),
+ {ok, State2}
+ end
end},
%% Terminations
@@ -40582,14 +41646,6 @@ tpp_tcp_send_msg(Sock, Send, Msg, AccSz) when is_binary(Msg) ->
%% size_of_iovec([B|IOVec], Sz) ->
%% size_of_iovec(IOVec, Sz+size(B)).
-mq() ->
- mq(self()).
-
-mq(Pid) when is_pid(Pid) ->
- Tag = messages,
- {Tag, Msgs} = process_info(Pid, Tag),
- Msgs.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -41098,25 +42154,33 @@ traffic_ping_pong_send_and_receive_udp2(InitState) ->
num := Num} = State) ->
{CSent, CReceived, CStart, CStop} = CRes,
CTime = tdiff(CStart, CStop),
- %% Note that the sizes we are counting is only
- %% the "data" part of the messages. There is also
- %% fixed header for each message, which of course
- %% is small for the large messages, but comparatively
- %% big for the small messages!
- ?SEV_IPRINT("Results: ~w messages exchanged"
- "~n Client: ~w msec"
- "~n ~.2f msec/message (roundtrip)"
- "~n ~.2f messages/msec (roundtrip)"
- "~n ~w bytes/msec sent"
- "~n ~w bytes/msec received",
- [Num,
- CTime,
- CTime / Num,
- Num / CTime,
- CSent div CTime,
- CReceived div CTime]),
- State1 = maps:remove(client_result, State),
- {ok, State1}
+ if
+ (CTime =:= 0) ->
+ {skip,
+ ?F("Invalid exec time: ~w ", [CTime])};
+ true ->
+ %% Note that the sizes we are counting is
+ %% only the "data" part of the messages.
+ %% There is also fixed header for each
+ %% message, which of course is small for
+ %% the large messages, but comparatively
+ %% big for the small messages!
+ ?SEV_IPRINT(
+ "Results: ~w messages exchanged"
+ "~n Client: ~w msec"
+ "~n ~.2f msec/message (roundtrip)"
+ "~n ~.2f messages/msec (roundtrip)"
+ "~n ~w bytes/msec sent"
+ "~n ~w bytes/msec received",
+ [Num,
+ CTime,
+ CTime / Num,
+ Num / CTime,
+ CSent div CTime,
+ CReceived div CTime]),
+ State1 = maps:remove(client_result, State),
+ {ok, State1}
+ end
end},
%% Terminations
@@ -47877,7 +48941,7 @@ otp16359_maccept_tcp(InitState) ->
%% This test case is to verify that we do not leak monitors.
otp18240_accept_mon_leak_tcp4(Config) when is_list(Config) ->
- ?TT(?SECS(10)),
+ ?TT(?SECS(30)),
tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
@@ -47892,7 +48956,7 @@ otp18240_accept_mon_leak_tcp4(Config) when is_list(Config) ->
%% This test case is to verify that we do not leak monitors.
otp18240_accept_mon_leak_tcp6(Config) when is_list(Config) ->
- ?TT(?SECS(10)),
+ ?TT(?SECS(30)),
tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
@@ -47918,34 +48982,54 @@ otp18240_accept_tcp(#{domain := Domain,
otp18240_await_acceptor(Pid, Mon) ->
receive
- {'DOWN', Mon, process, Pid, Info} ->
- i("acceptor terminated: "
- "~n ~p", [Info])
+ {'DOWN', Mon, process, Pid, ok} ->
+ i("acceptor successfully terminated"),
+ ok;
+ {'DOWN', Mon, process, Pid, {skip, _} = SKIP} ->
+ i("acceptor successfully terminated"),
+ exit(SKIP);
+ {'DOWN', Mon, process, Pid, Info} ->
+ i("acceptor unexpected termination: "
+ "~n ~p", [Info]),
+ exit({unexpected_result, Info})
after 5000 ->
- i("acceptor info"
+ i("acceptor process (~p) info"
"~n Refs: ~p"
"~n Info: ~p",
- [monitored_by(Pid), erlang:process_info(Pid)]),
+ [Pid, monitored_by(Pid), erlang:process_info(Pid)]),
otp18240_await_acceptor(Pid, Mon)
end.
otp18240_acceptor(Parent, Domain, Proto, NumSocks) ->
i("[acceptor] begin with: "
+ "~n Parent: ~p"
"~n Domain: ~p"
- "~n Protocol: ~p", [Domain, Proto]),
+ "~n Protocol: ~p", [Parent, Domain, Proto]),
MonitoredBy0 = monitored_by(),
- {ok, LSock} = socket:open(Domain, stream, Proto,
- #{use_registry => false}),
- ok = socket:bind(LSock, #{family => Domain, port => 0, addr => any}),
+ ?SLEEP(?SECS(5)),
+ Addr = case ?LIB:which_local_host_info(Domain) of
+ {ok, #{addr := A}} ->
+ A;
+ {error, Reason} ->
+ exit({skip, Reason})
+ end,
+ {ok, LSock} = socket:open(Domain, stream, Proto,
+ #{use_registry => false}),
+ ok = socket:bind(LSock, #{family => Domain, addr => Addr, port => 0}),
ok = socket:listen(LSock, NumSocks),
+ ?SLEEP(?SECS(5)),
MonitoredBy1 = monitored_by(),
- [LSockMon] = MonitoredBy1 -- MonitoredBy0,
i("[acceptor]: listen socket created"
- "~n Montored By before listen socket: ~p"
- "~n Montored By after listen socket: ~p"
- "~n Listen Socket Monitor: ~p"
- "~n Listen Socket info: ~p",
- [MonitoredBy0, MonitoredBy1, LSockMon, socket:info(LSock)]),
+ "~n 'Montored By' before listen socket: ~p"
+ "~n 'Montored By' after listen socket: ~p",
+ [MonitoredBy0, MonitoredBy1]),
+
+ [LSockMon] = MonitoredBy1 -- MonitoredBy0,
+
+ i("[acceptor]: "
+ "~n Listen Socket Monitor: ~p"
+ "~n Listen Socket info: ~p",
+ [LSockMon, socket:info(LSock)]),
{ok, #{port := Port}} = socket:sockname(LSock),
@@ -47953,7 +49037,7 @@ otp18240_acceptor(Parent, Domain, Proto, NumSocks) ->
_Clients = [spawn_link(fun() ->
otp18240_client(CID,
Domain, Proto,
- Port)
+ Addr, Port)
end) || CID <- lists:seq(1, NumSocks)],
i("[acceptor] accept ~w connections", [NumSocks]),
@@ -47987,26 +49071,49 @@ otp18240_acceptor(Parent, Domain, Proto, NumSocks) ->
end.
-otp18240_client(ID, Domain, Proto, PortNo) ->
+otp18240_client(ID, Domain, Proto, Addr, PortNo) ->
i("[connector ~w] try create connector socket", [ID]),
{ok, Sock} = socket:open(Domain, stream, Proto, #{use_registry => false}),
- ok = socket:bind(Sock, #{family => Domain, port => 0, addr => any}),
+ ok = socket:bind(Sock, #{family => Domain, addr => Addr, port => 0}),
%% ok = socket:setopt(Sock, otp, debug, true),
i("[connector ~w] try connect", [ID]),
- ok = socket:connect(Sock, #{family => Domain, addr => any, port => PortNo}),
- i("[connector ~w] connected - now try recv", [ID]),
- case socket:recv(Sock) of
- {ok, Data} ->
- i("[connector ~w] received unexpected data: "
- "~n ~p", [ID, Data]),
- (catch socket:close(Sock)),
- exit('unexpected data');
- {error, closed} ->
- i("[connector ~w] expected socket close", [ID]);
- {error, Reason} ->
- i("[connector ~w] unexpected error when reading: "
- "~n ~p", [ID, Reason]),
- (catch socket:close(Sock))
+ case socket:connect(Sock,
+ #{family => Domain, addr => Addr, port => PortNo}) of
+ ok ->
+ i("[connector ~w] connected - now try recv", [ID]),
+ case socket:recv(Sock) of
+ {ok, Data} ->
+ i("[connector ~w] received unexpected data: "
+ "~n ~p", [ID, Data]),
+ (catch socket:close(Sock)),
+ exit('unexpected data');
+ {error, closed} ->
+ i("[connector ~w] expected socket close", [ID]);
+ {error, Reason} ->
+ i("[connector ~w] unexpected error when reading: "
+ "~n ~p", [ID, Reason]),
+ (catch socket:close(Sock))
+ end;
+ {error, {completion_status, #{info := invalid_netname = R} = Reason}} ->
+ i("[connector ~w] failed connecting: "
+ "~n ~p", [ID, Reason]),
+ (catch socket:close(Sock)),
+ exit({skip, R});
+ {error, {completion_status, invalid_netname = Reason}} ->
+ i("[connector ~w] failed connecting: "
+ "~n ~p", [ID, Reason]),
+ (catch socket:close(Sock)),
+ exit({skip, Reason});
+ {error, enetunreach = Reason} ->
+ i("[connector ~w] failed connecting: "
+ "~n ~p", [ID, Reason]),
+ (catch socket:close(Sock)),
+ exit({skip, Reason});
+
+ {error, Reason} ->
+ i("[connector ~w] failed connecting: "
+ "~n ~p", [ID, Reason]),
+ (catch socket:close(Sock))
end,
i("[connector ~w] done", [ID]),
ok.
@@ -48477,11 +49584,19 @@ is_not_netbsd() ->
is_not_darwin() ->
is_not_platform(darwin, "Darwin").
+is_not_windows() ->
+ case os:type() of
+ {win32, nt} ->
+ skip("This does not work on Windows");
+ _ ->
+ ok
+ end.
+
is_not_platform(Platform, PlatformStr)
when is_atom(Platform) andalso is_list(PlatformStr) ->
case os:type() of
- {unix, Platform} ->
- skip("This does not work on " ++ PlatformStr);
+ {unix, Platform} ->
+ skip("This does not work on " ++ PlatformStr);
_ ->
ok
end.
@@ -48859,6 +49974,16 @@ start_node(Name, Timeout) when is_integer(Timeout) andalso (Timeout > 0) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+mq() ->
+ mq(self()).
+
+mq(Pid) when is_pid(Pid) ->
+ {messages, MQ} = process_info(Pid, messages),
+ MQ.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
nowait(Config) ->
case lists:member({select_handle, true}, Config) of
true ->
diff --git a/lib/kernel/test/socket_test_evaluator.erl b/lib/kernel/test/socket_test_evaluator.erl
index c2bd9d44b8..49b529461f 100644
--- a/lib/kernel/test/socket_test_evaluator.erl
+++ b/lib/kernel/test/socket_test_evaluator.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -134,6 +134,11 @@ loop(ID, [#{desc := Desc,
"~n ~p", [ID, Reason]),
exit({command_failed, ID, Reason, State})
catch
+ error:notsup = Reason:Stack ->
+ ?SEV_IPRINT("command ~w skip: "
+ "~n ~p"
+ "~n ~p", [ID, Reason, Stack]),
+ exit({skip, Reason});
C:{skip, command} = E:_ when ((C =:= throw) orelse (C =:= exit)) ->
%% Secondary skip
exit(E);
@@ -176,14 +181,14 @@ await_finish(Evs, OK, Fails) ->
%% The evaluator can skip the test case:
{'DOWN', _MRef, process, Pid, {skip, Reason}} ->
- %% ?SEV_IPRINT("await_finish -> skip (down) received: "
- %% "~n Pid: ~p"
- %% "~n Reason: ~p", [Pid, Reason]),
+ ?SEV_IPRINT("await_finish -> skip (down) received: "
+ "~n Pid: ~p"
+ "~n Reason: ~p", [Pid, Reason]),
await_finish_skip(Pid, Reason, Evs, OK);
{'EXIT', Pid, {skip, Reason}} ->
- %% ?SEV_IPRINT("await_finish -> skip (exit) received: "
- %% "~n Pid: ~p"
- %% "~n Reason: ~p", [Pid, Reason]),
+ ?SEV_IPRINT("await_finish -> skip (exit) received: "
+ "~n Pid: ~p"
+ "~n Reason: ~p", [Pid, Reason]),
await_finish_skip(Pid, Reason, Evs, OK);
%% Evaluator failed
@@ -242,7 +247,9 @@ await_finish_skip(Pid, Reason, Evs, OK) ->
end,
Evs
end,
+ ?SEV_IPRINT("ensure (~w) evaluator(s) are terminated", [length(Evs)]),
await_evs_terminated(Evs2),
+ ?SEV_IPRINT("issue skip"),
?LIB:skip(Reason).
await_evs_terminated(Evs) ->
@@ -576,7 +583,7 @@ await(ExpPid, Name, Announcement, Slogan, OtherPids)
{'DOWN', _, process, Pid, {skip, SkipReason}} when (Pid =:= ExpPid) ->
iprint("Unexpected SKIP from ~w (~p): "
"~n ~p", [Name, Pid, SkipReason]),
- ?LIB:skip({Name, SkipReason});
+ ?LIB:skip(SkipReason);
{'DOWN', _, process, Pid, Reason} when (Pid =:= ExpPid) ->
eprint("Unexpected DOWN from ~w (~p): "
"~n ~p", [Name, Pid, Reason]),
@@ -590,10 +597,16 @@ await(ExpPid, Name, Announcement, Slogan, OtherPids)
"~n OtherPids: "
"~n ~p", [OtherPid, Reason, OtherPids]),
await(ExpPid, Name, Announcement, Slogan, OtherPids);
+ {skip, SkipName, SkipReason} ->
+ iprint("Unexpected other SKIP from ~w (~p): "
+ "~n ~p", [SkipName, OtherPid, SkipReason]),
+ ?LIB:skip(SkipReason);
{error, _} = ERROR ->
ERROR
end
- after infinity -> % For easy debugging, just change to some valid time (5000)
+
+ %% For easy debugging, just change to some valid time (5000)
+ after infinity ->
iprint("await -> timeout for msg from ~p (~w): "
"~n Announcement: ~p"
"~n Slogan: ~p"
@@ -613,9 +626,14 @@ pi(Pid, Item) ->
check_down(Pid, DownReason, Pids) ->
case lists:keysearch(Pid, 2, Pids) of
{value, {Name, _}} ->
- eprint("Unexpected DOWN from ~w (~p): "
- "~n ~p", [Name, Pid, DownReason]),
- {error, {unexpected_exit, Name, DownReason}};
+ case DownReason of
+ {skip, Reason} ->
+ {skip, Name, Reason};
+ _ ->
+ eprint("Unexpected DOWN from ~w (~p): "
+ "~n ~p", [Name, Pid, DownReason]),
+ {error, {unexpected_exit, Name, DownReason}}
+ end;
false ->
ok
end.
diff --git a/lib/kernel/test/socket_test_lib.erl b/lib/kernel/test/socket_test_lib.erl
index e7028dab3b..da36943461 100644
--- a/lib/kernel/test/socket_test_lib.erl
+++ b/lib/kernel/test/socket_test_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -133,73 +133,79 @@ has_support_ipv4() ->
end.
has_support_ipv6() ->
- case socket:is_supported(ipv6) of
- true ->
- ok;
- false ->
- skip("IPv6 Not Supported")
- end,
- Domain = inet6,
- LocalAddr =
- case which_local_addr(Domain) of
- {ok, Addr} ->
- Addr;
- {error, R1} ->
- skip(f("Local Address eval failed: ~p", [R1]))
- end,
- ServerSock =
- case socket:open(Domain, dgram, udp) of
- {ok, SS} ->
- SS;
- {error, R2} ->
- skip(f("(server) socket open failed: ~p", [R2]))
- end,
- LocalSA = #{family => Domain, addr => LocalAddr},
- ServerPort =
- case socket:bind(ServerSock, LocalSA) of
- ok ->
- {ok, #{port := P1}} = socket:sockname(ServerSock),
- P1;
- {error, R3} ->
- socket:close(ServerSock),
- skip(f("(server) socket bind failed: ~p", [R3]))
- end,
- ServerSA = LocalSA#{port => ServerPort},
- ClientSock =
- case socket:open(Domain, dgram, udp) of
- {ok, CS} ->
- CS;
- {error, R4} ->
- skip(f("(client) socket open failed: ~p", [R4]))
- end,
- case socket:bind(ClientSock, LocalSA) of
- ok ->
- ok;
- {error, R5} ->
- socket:close(ServerSock),
- socket:close(ClientSock),
- skip(f("(client) socket bind failed: ~p", [R5]))
- end,
- case socket:sendto(ClientSock, <<"hejsan">>, ServerSA) of
- ok ->
- ok;
- {error, R6} ->
- socket:close(ServerSock),
- socket:close(ClientSock),
- skip(f("failed socket sendto test: ~p", [R6]))
- end,
- case socket:recvfrom(ServerSock) of
- {ok, {_, <<"hejsan">>}} ->
- socket:close(ServerSock),
- socket:close(ClientSock),
- ok;
- {error, R7} ->
- socket:close(ServerSock),
- socket:close(ClientSock),
- skip(f("failed socket recvfrom test: ~p", [R7]))
- end.
-
-
+ try
+ begin
+ case socket:is_supported(ipv6) of
+ true ->
+ ok;
+ false ->
+ skip("IPv6 Not Supported")
+ end,
+ Domain = inet6,
+ LocalAddr =
+ case which_local_addr(Domain) of
+ {ok, Addr} ->
+ Addr;
+ {error, R1} ->
+ skip(f("Local Address eval failed: ~p", [R1]))
+ end,
+ ServerSock =
+ case socket:open(Domain, dgram, udp) of
+ {ok, SS} ->
+ SS;
+ {error, R2} ->
+ skip(f("(server) socket open failed: ~p", [R2]))
+ end,
+ LocalSA = #{family => Domain, addr => LocalAddr},
+ ServerPort =
+ case socket:bind(ServerSock, LocalSA) of
+ ok ->
+ {ok, #{port := P1}} = socket:sockname(ServerSock),
+ P1;
+ {error, R3} ->
+ socket:close(ServerSock),
+ skip(f("(server) socket bind failed: ~p", [R3]))
+ end,
+ ServerSA = LocalSA#{port => ServerPort},
+ ClientSock =
+ case socket:open(Domain, dgram, udp) of
+ {ok, CS} ->
+ CS;
+ {error, R4} ->
+ skip(f("(client) socket open failed: ~p", [R4]))
+ end,
+ case socket:bind(ClientSock, LocalSA) of
+ ok ->
+ ok;
+ {error, R5} ->
+ socket:close(ServerSock),
+ socket:close(ClientSock),
+ skip(f("(client) socket bind failed: ~p", [R5]))
+ end,
+ case socket:sendto(ClientSock, <<"hejsan">>, ServerSA) of
+ ok ->
+ ok;
+ {error, R6} ->
+ socket:close(ServerSock),
+ socket:close(ClientSock),
+ skip(f("failed socket sendto test: ~p", [R6]))
+ end,
+ case socket:recvfrom(ServerSock) of
+ {ok, {_, <<"hejsan">>}} ->
+ socket:close(ServerSock),
+ socket:close(ClientSock),
+ ok;
+ {error, R7} ->
+ socket:close(ServerSock),
+ socket:close(ClientSock),
+ skip(f("failed socket recvfrom test: ~p", [R7]))
+ end
+ end
+ catch
+ error : notsup ->
+ skip("Not supported: socket")
+ end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -312,7 +318,7 @@ not_yet_implemented() ->
skip("not yet implemented").
skip(Reason) ->
- throw({skip, Reason}).
+ exit({skip, Reason}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/kernel/test/socket_test_ttest_tcp_socket.erl b/lib/kernel/test/socket_test_ttest_tcp_socket.erl
index 07be4740e6..7a084a445d 100644
--- a/lib/kernel/test/socket_test_ttest_tcp_socket.erl
+++ b/lib/kernel/test/socket_test_ttest_tcp_socket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -55,6 +55,15 @@
Reason}).
+-define(SELECT_INFO(TAG, REF), {select_info, TAG, REF}).
+-define(COMPLETION_INFO(TAG, REF), {completion_info, TAG, REF}).
+
+-define(SELECT_MSG(SOCK, REF),
+ {'$socket', (SOCK), select, (REF)}).
+-define(COMPLETION_MSG(SOCK, REF, STATUS),
+ {'$socket', (SOCK), completion, {(REF), (STATUS)}}).
+
+
%% ==========================================================================
%% This does not really work. Its just a placeholder for the time being...
@@ -371,8 +380,9 @@ reader_init(ControllingProcess, Sock, Async, Active, Method)
reader_loop(#{ctrl_proc => ControllingProcess,
ctrl_proc_mref => MRef,
async => Async,
- select_info => undefined,
- select_num => 0, % Count the number of select messages
+ asynch_info => undefined,
+ %% Count the number of select|completion messages
+ asynch_num => 0,
active => Active,
sock => Sock,
method => Method}).
@@ -452,13 +462,16 @@ reader_loop(#{active := once,
end;
reader_loop(#{active := once,
async := true,
- select_info := undefined,
+ asynch_info := undefined,
sock := Sock,
method := Method,
ctrl_proc := Pid} = State) ->
case do_recv(Method, Sock, nowait) of
{select, SelectInfo} ->
- reader_loop(State#{select_info => SelectInfo});
+ reader_loop(State#{asynch_info => SelectInfo});
+ {completion, CompletionInfo} ->
+ reader_loop(State#{asynch_info => CompletionInfo});
+
{ok, Data} ->
Pid ! ?DATA_MSG(Sock, Method, Data),
reader_loop(State#{active => false});
@@ -473,11 +486,17 @@ reader_loop(#{active := once,
end;
reader_loop(#{active := once,
async := true,
- select_info := {select_info, _, Ref},
- select_num := N,
+ asynch_info := AsynchInfo,
+ asynch_num := N,
sock := Sock,
method := Method,
- ctrl_proc := Pid} = State) ->
+ ctrl_proc := Pid} = State) when (AsynchInfo =/= undefined) ->
+ Ref = case AsynchInfo of
+ ?SELECT_INFO(_, SR) ->
+ SR;
+ ?COMPLETION_INFO(_, CR) ->
+ CR
+ end,
receive
{?MODULE, stop} ->
reader_exit(State, stop);
@@ -501,18 +520,42 @@ reader_loop(#{active := once,
reader_loop(State)
end;
- {'$socket', Sock, select, Ref} ->
+ ?SELECT_MSG(Sock, Ref) ->
case do_recv(Method, Sock, nowait) of
{ok, Data} when is_binary(Data) ->
Pid ! ?DATA_MSG(Sock, Method, Data),
reader_loop(State#{active => false,
- select_info => undefined,
- select_num => N+1});
-
+ asynch_info => undefined,
+ asynch_num => N+1});
+
{error, closed} = E1 ->
Pid ! ?CLOSED_MSG(Sock, Method),
reader_exit(State, E1);
-
+
+ {error, Reason} = E2 ->
+ Pid ! ?ERROR_MSG(Sock, Method, Reason),
+ reader_exit(State, E2)
+ end;
+
+ ?COMPLETION_MSG(Sock, Ref, Result) ->
+ %% Note that *Windows* does not support sendmsg/recvmsg
+ %% but we assume we can get it. Just to be future proof
+ case Result of
+ {ok, Data} when is_binary(Data) ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State#{active => false,
+ asynch_info => undefined,
+ asynch_num => N+1});
+ {ok, #{iov := [Data]}} when is_binary(Data) ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State#{active => false,
+ asynch_info => undefined,
+ asynch_num => N+1});
+
+ {error, closed} = E1 ->
+ Pid ! ?CLOSED_MSG(Sock, Method),
+ reader_exit(State, E1);
+
{error, Reason} = E2 ->
Pid ! ?ERROR_MSG(Sock, Method, Reason),
reader_exit(State, E2)
@@ -566,13 +609,16 @@ reader_loop(#{active := true,
end;
reader_loop(#{active := true,
async := true,
- select_info := undefined,
+ asynch_info := undefined,
sock := Sock,
method := Method,
ctrl_proc := Pid} = State) ->
case do_recv(Method, Sock) of
{select, SelectInfo} ->
- reader_loop(State#{select_info => SelectInfo});
+ reader_loop(State#{asynch_info => SelectInfo});
+ {completion, CompletionInfo} ->
+ reader_loop(State#{asynch_info => CompletionInfo});
+
{ok, Data} ->
Pid ! ?DATA_MSG(Sock, Method, Data),
reader_loop(State);
@@ -587,11 +633,17 @@ reader_loop(#{active := true,
end;
reader_loop(#{active := true,
async := true,
- select_info := {select_info, _, Ref},
- select_num := N,
+ asynch_info := AsynchInfo,
+ asynch_num := N,
sock := Sock,
method := Method,
- ctrl_proc := Pid} = State) ->
+ ctrl_proc := Pid} = State) when (AsynchInfo =/= undefined) ->
+ Ref = case AsynchInfo of
+ ?SELECT_INFO(_, SR) ->
+ SR;
+ ?COMPLETION_INFO(_, CR) ->
+ CR
+ end,
receive
{?MODULE, stop} ->
reader_exit(State, stop);
@@ -615,12 +667,12 @@ reader_loop(#{active := true,
reader_loop(State)
end;
- {'$socket', Sock, select, Ref} ->
+ ?SELECT_MSG(Sock, Ref) ->
case do_recv(Method, Sock, nowait) of
{ok, Data} when is_binary(Data) ->
Pid ! ?DATA_MSG(Sock, Method, Data),
- reader_loop(State#{select_info => undefined,
- select_num => N+1});
+ reader_loop(State#{asynch_info => undefined,
+ asynch_num => N+1});
{error, closed} = E1 ->
Pid ! ?CLOSED_MSG(Sock, Method),
@@ -629,6 +681,30 @@ reader_loop(#{active := true,
{error, Reason} = E2 ->
Pid ! ?ERROR_MSG(Sock, Method, Reason),
reader_exit(State, E2)
+ end;
+
+ ?COMPLETION_MSG(Sock, Ref, Result) ->
+ %% Note that *Windows* does not support sendmsg/recvmsg
+ %% but we assume we can get it. Just to be future proof
+ case Result of
+ {ok, Data} when is_binary(Data) ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State#{active => false,
+ asynch_info => undefined,
+ asynch_num => N+1});
+ {ok, #{iov := [Data]}} when is_binary(Data) ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State#{active => false,
+ asynch_info => undefined,
+ asynch_num => N+1});
+
+ {error, closed} = E1 ->
+ Pid ! ?CLOSED_MSG(Sock, Method),
+ reader_exit(State, E1);
+
+ {error, Reason} = E2 ->
+ Pid ! ?ERROR_MSG(Sock, Method, Reason),
+ reader_exit(State, E2)
end
end.
@@ -644,6 +720,8 @@ do_recv(msg, Sock, Timeout) ->
{ok, Bin};
{select, _} = SELECT ->
SELECT;
+ {completion, _} = COMPLETION ->
+ COMPLETION;
{error, _} = ERROR ->
ERROR
end.
@@ -654,55 +732,55 @@ reader_exit(#{async := false, active := Active}, stop) ->
exit(normal);
reader_exit(#{async := true,
active := Active,
- select_info := SelectInfo,
- select_num := N}, stop) ->
+ asynch_info := AsynchInfo,
+ asynch_num := N}, stop) ->
vp("reader stopped when active: ~w"
- "~n Current select info: ~p"
- "~n Number of select messages: ~p", [Active, SelectInfo, N]),
+ "~n Current asynch info: ~p"
+ "~n Number of asynch messages: ~p", [Active, AsynchInfo, N]),
exit(normal);
reader_exit(#{async := false, active := Active}, {ctrl_exit, normal}) ->
vp("reader ctrl exit when active: ~w", [Active]),
exit(normal);
reader_exit(#{async := true,
active := Active,
- select_info := SelectInfo,
- select_num := N}, {ctrl_exit, normal}) ->
+ asynch_info := AsynchInfo,
+ asynch_num := N}, {ctrl_exit, normal}) ->
vp("reader ctrl exit when active: ~w"
- "~n Current select info: ~p"
- "~n Number of select messages: ~p", [Active, SelectInfo, N]),
+ "~n Current asynch info: ~p"
+ "~n Number of asynch messages: ~p", [Active, AsynchInfo, N]),
exit(normal);
reader_exit(#{async := false, active := Active}, {ctrl_exit, Reason}) ->
vp("reader exit when ctrl crash when active: ~w", [Active]),
exit({controlling_process, Reason});
reader_exit(#{async := true,
active := Active,
- select_info := SelectInfo,
- select_num := N}, {ctrl_exit, Reason}) ->
+ asynch_info := AsynchInfo,
+ asynch_num := N}, {ctrl_exit, Reason}) ->
vp("reader exit when ctrl crash when active: ~w"
- "~n Current select info: ~p"
- "~n Number of select messages: ~p", [Active, SelectInfo, N]),
+ "~n Current asynch info: ~p"
+ "~n Number of asynch messages: ~p", [Active, AsynchInfo, N]),
exit({controlling_process, Reason});
reader_exit(#{async := false, active := Active}, {error, closed}) ->
vp("reader exit when socket closed when active: ~w", [Active]),
exit(normal);
reader_exit(#{async := true,
active := Active,
- select_info := SelectInfo,
- select_num := N}, {error, closed}) ->
+ asynch_info := AsynchInfo,
+ asynch_num := N}, {error, closed}) ->
vp("reader exit when socket closed when active: ~w "
- "~n Current select info: ~p"
- "~n Number of select messages: ~p", [Active, SelectInfo, N]),
+ "~n Current asynch info: ~p"
+ "~n Number of asynch messages: ~p", [Active, AsynchInfo, N]),
exit(normal);
reader_exit(#{async := false, active := Active}, {error, Reason}) ->
vp("reader exit when socket error when active: ~w", [Active]),
exit(Reason);
reader_exit(#{async := true,
active := Active,
- select_info := SelectInfo,
- select_num := N}, {error, Reason}) ->
+ asynch_info := AsynchInfo,
+ asynch_num := N}, {error, Reason}) ->
vp("reader exit when socket error when active: ~w: "
- "~n Current select info: ~p"
- "~n Number of select messages: ~p", [Active, SelectInfo, N]),
+ "~n Current asynch info: ~p"
+ "~n Number of asynch messages: ~p", [Active, AsynchInfo, N]),
exit(Reason).
diff --git a/lib/kernel/test/standard_error_SUITE.erl b/lib/kernel/test/standard_error_SUITE.erl
index 1d9026dc58..792d086847 100644
--- a/lib/kernel/test/standard_error_SUITE.erl
+++ b/lib/kernel/test/standard_error_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2014-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2014-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -34,8 +34,10 @@ badarg(Config) when is_list(Config) ->
true = erlang:is_process_alive(whereis(standard_error)),
ok.
+%% Check that standard_out and standard_error have the same encoding
getopts(Config) when is_list(Config) ->
- [{encoding,latin1}] = io:getopts(standard_error),
+ Encoding = proplists:get_value(encoding, io:getopts(user)),
+ Encoding = proplists:get_value(encoding, io:getopts(standard_error)),
ok.
%% Test that writing a lot of output to standard_error does not cause the
diff --git a/lib/megaco/configure b/lib/megaco/configure
index 1ca72e94d3..a74de67977 100755
--- a/lib/megaco/configure
+++ b/lib/megaco/configure
@@ -747,7 +747,6 @@ ac_user_opts='
enable_option_checking
enable_megaco_reentrant_flex_scanner
enable_megaco_flex_scanner_lineno
-enable_sanitizers
'
ac_precious_vars='build_alias
host_alias
@@ -1382,8 +1381,6 @@ Optional Features:
--disable-megaco-reentrant-flex-scanner disable reentrant megaco flex scanner
--enable-megaco-flex-scanner-lineno enable megaco flex scanner lineno
--disable-megaco-flex-scanner-lineno disable megaco flex scanner lineno
- --enable-sanitizers[=comma-separated list of sanitizers]
- Default=address,undefined
Some influential environment variables:
CC C compiler command
@@ -4429,23 +4426,6 @@ fi
fi
-
-
-# Check whether --enable-sanitizers was given.
-if test ${enable_sanitizers+y}
-then :
- enableval=$enable_sanitizers;
-case "$enableval" in
- no) sanitizers= ;;
- yes) sanitizers="-fsanitize=address,undefined" ;;
- *) sanitizers="-fsanitize=$enableval" ;;
-esac
-CFLAGS="$CFLAGS $sanitizers"
-LDFLAGS="$LDFLAGS $sanitizers"
-
-fi
-
-
ac_header= ac_cache=
for ac_item in $ac_header_c_list
do
diff --git a/lib/megaco/configure.ac b/lib/megaco/configure.ac
index ce65e54a85..8d6af651c8 100644
--- a/lib/megaco/configure.ac
+++ b/lib/megaco/configure.ac
@@ -2,7 +2,7 @@ dnl Process this file with autoconf to produce a configure script. -*-m4-*-
dnl
dnl %CopyrightBegin%
dnl
-dnl Copyright Ericsson AB 2001-2021. All Rights Reserved.
+dnl Copyright Ericsson AB 2001-2023. All Rights Reserved.
dnl
dnl Licensed under the Apache License, Version 2.0 (the "License");
dnl you may not use this file except in compliance with the License.
@@ -162,26 +162,6 @@ if test "x$GCC" = xyes; then
LM_TRY_ENABLE_CFLAG([-Werror=return-type], [CFLAGS])
fi
-dnl ----------------------------------------------------------------------
-dnl Enable -fsanitize= flags.
-dnl ----------------------------------------------------------------------
-
-m4_define(DEFAULT_SANITIZERS, [address,undefined])
-AC_ARG_ENABLE(
- sanitizers,
- AS_HELP_STRING(
- [--enable-sanitizers@<:@=comma-separated list of sanitizers@:>@],
- [Default=DEFAULT_SANITIZERS]),
-[
-case "$enableval" in
- no) sanitizers= ;;
- yes) sanitizers="-fsanitize=DEFAULT_SANITIZERS" ;;
- *) sanitizers="-fsanitize=$enableval" ;;
-esac
-CFLAGS="$CFLAGS $sanitizers"
-LDFLAGS="$LDFLAGS $sanitizers"
-])
-
ERL_DED
AC_CHECK_PROG(PERL, perl, perl, no_perl)
diff --git a/lib/megaco/doc/src/megaco_intro.xml b/lib/megaco/doc/src/megaco_intro.xml
index f43b86c633..606ee16d5a 100644
--- a/lib/megaco/doc/src/megaco_intro.xml
+++ b/lib/megaco/doc/src/megaco_intro.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2000</year><year>2020</year>
+ <year>2000</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -73,7 +73,7 @@
<section>
<title>Prerequisites</title>
- <p>The following prerequisites is required for understanding the
+ <p>The following prerequisites are required for understanding the
material in the Megaco User's Guide:</p>
<list type="bulleted">
<item>
diff --git a/lib/mnesia/src/mnesia.app.src b/lib/mnesia/src/mnesia.app.src
index 77bd1a7816..dfb5e82c93 100644
--- a/lib/mnesia/src/mnesia.app.src
+++ b/lib/mnesia/src/mnesia.app.src
@@ -51,4 +51,4 @@
]},
{applications, [kernel, stdlib]},
{mod, {mnesia_app, []}},
- {runtime_dependencies, ["stdlib-3.4","kernel-5.3","erts-9.0"]}]}.
+ {runtime_dependencies, ["stdlib-@OTP-18490@","kernel-5.3","erts-9.0"]}]}.
diff --git a/lib/mnesia/src/mnesia_checkpoint.erl b/lib/mnesia/src/mnesia_checkpoint.erl
index fce588444b..ed1c0df605 100644
--- a/lib/mnesia/src/mnesia_checkpoint.erl
+++ b/lib/mnesia/src/mnesia_checkpoint.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021
+%% Copyright Ericsson AB 1996-2023
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -632,7 +632,8 @@ init(Cp) ->
catch error:Reason -> %% system limit
Msg = "Cannot create an ets table for pending transactions",
Error = {error, {system_limit, Name, Msg, Reason}},
- proc_lib:init_ack(Cp#checkpoint_args.supervisor, Error)
+ proc_lib:init_fail(
+ Cp#checkpoint_args.supervisor, Error, {exit, normal})
end.
prepare_tab(Cp, R) ->
diff --git a/lib/mnesia/src/mnesia_tm.erl b/lib/mnesia/src/mnesia_tm.erl
index 9827ef97ad..d7b123d1e5 100644
--- a/lib/mnesia/src/mnesia_tm.erl
+++ b/lib/mnesia/src/mnesia_tm.erl
@@ -32,6 +32,7 @@
do_update_op/3,
get_info/1,
get_transactions/0,
+ get_transactions_count/0,
info/1,
mnesia_down/1,
prepare_checkpoint/2,
@@ -423,6 +424,10 @@ doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor=
reply(From, {info, gb_trees:values(Participants),
gb_trees:to_list(Coordinators)}, State);
+ {From, transactions_count} ->
+ reply(From, {transactions_count, gb_trees:size(Participants),
+ gb_trees:size(Coordinators)}, State);
+
{mnesia_down, N} ->
verbose("Got mnesia_down from ~p, reconfiguring...~n", [N]),
reconfigure_coordinators(N, gb_trees:to_list(Coordinators)),
@@ -2163,6 +2168,14 @@ tr_status(Tid,Participant) ->
false -> coordinator
end.
+get_transactions_count() ->
+ case req(transactions_count) of
+ {transactions_count, ParticipantsCount, CoordinatorsCount} ->
+ {ParticipantsCount, CoordinatorsCount};
+ Error ->
+ Error
+ end.
+
get_info(Timeout) ->
case whereis(?MODULE) of
undefined ->
diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl
index b33b807bfc..f1901b38f5 100644
--- a/lib/mnesia/test/mnesia_trans_access_test.erl
+++ b/lib/mnesia/test/mnesia_trans_access_test.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
-export([write/1, read/1, wread/1, delete/1,
delete_object_bag/1, delete_object_set/1,
- match_object/1, select/1, select14/1, all_keys/1, transaction/1,
+ match_object/1, select/1, select14/1, all_keys/1, transaction/1, transaction_counters/1,
basic_nested/1, mix_of_nested_activities/1,
nested_trans_both_ok/1, nested_trans_child_dies/1,
nested_trans_parent_dies/1, nested_trans_both_dies/1,
@@ -65,7 +65,7 @@ end_per_testcase(Func, Conf) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
all() ->
[write, read, wread, delete, delete_object_bag, delete_object_set,
- match_object, select, select14, all_keys, transaction,
+ match_object, select, select14, all_keys, transaction, transaction_counters,
{group, nested_activities}, {group, index_tabs},
{group, index_lifecycle}].
@@ -546,6 +546,28 @@ transaction(Config) when is_list(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+transaction_counters(suite) -> [];
+transaction_counters(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+
+ {atomic, {{Participants1, Coordinators1}, {Participants2, Coordinators2}}} =
+ mnesia:transaction(fun get_transactions_counters/0),
+
+ ?match(Coordinators1, Coordinators2),
+ ?match(Coordinators1, 1),
+ ?match(Participants1, Participants2),
+ ?match(Participants1, 0),
+
+ ?verify_mnesia(Nodes, []).
+
+get_transactions_counters() ->
+ {count_sides(mnesia_tm:get_transactions()), mnesia_tm:get_transactions_count()}.
+
+count_sides(TransactionsList) ->
+ lists:foldl(
+ fun({_Tid, _Pid, participant}, {Participants, Coordinators}) -> {Participants + 1, Coordinators};
+ ({_Tid, _Pid, coordinator}, {Participants, Coordinators}) -> {Participants, Coordinators + 1}
+ end, {0, 0}, TransactionsList).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/observer/doc/src/observer.xml b/lib/observer/doc/src/observer.xml
index 3fa59db70a..9185c4be0d 100644
--- a/lib/observer/doc/src/observer.xml
+++ b/lib/observer/doc/src/observer.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2011</year><year>2021</year>
+ <year>2011</year><year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -52,8 +52,41 @@
<fsummary>Start the Observer GUI.</fsummary>
<desc>
<p>Starts the Observer GUI.
- To stop the tool, close the window.
- </p>
+ To stop the tool, close the window or call
+ <seemfa marker="#stop/0">stop/0</seemfa>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name since="OTP 26.0">start(Node) -> ok</name>
+ <fsummary>Start the Observer GUI connected against <c>Node</c>.</fsummary>
+ <desc>
+ <p>Starts the Observer GUI and tries to connect it to <c>Node</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name since="OTP 26.0">start_and_wait() -> ok</name>
+ <fsummary>Start the Observer GUI blocking.</fsummary>
+ <desc>
+ <p>Starts the Observer GUI and only return when it is either stopped or
+ the window is closed</p>
+ </desc>
+ </func>
+ <func>
+ <name since="OTP 26.0">start_and_wait(Node) -> ok</name>
+ <fsummary>Start the Observer GUI blocking and connect it to <c>Node</c>.</fsummary>
+ <desc>
+ <p>Starts the Observer GUI and only return when it is either stopped or
+ the window is closed, connects it directly to <c>Node</c> like
+ <seemfa marker="#start/1">start/1</seemfa>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name since="OTP 26.0">stop() -> ok</name>
+ <fsummary>Stop the Observer GUI.</fsummary>
+ <desc>
+ <p>Stops the Observer GUI.</p>
</desc>
</func>
</funcs>
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl
index a2f88986af..2e9c79a6f3 100644
--- a/lib/observer/src/crashdump_viewer.erl
+++ b/lib/observer/src/crashdump_viewer.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1399,7 +1399,7 @@ get_pid_from_name(Name,Node) ->
[{_,Pid}] when is_pid(Pid) ->
pid_to_list(Pid);
_ ->
- "<unkonwn_pid>"
+ "<unknown_pid>"
end;
_ ->
"<unknown_pid_other_node>"
diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src
index a2f012ebd5..215dfabf40 100644
--- a/lib/observer/src/observer.app.src
+++ b/lib/observer/src/observer.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -67,5 +67,5 @@
{registered, []},
{applications, [kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["wx-1.2","stdlib-3.13","runtime_tools-1.19",
- "kernel-8.1","et-1.5","erts-11.0"]}]}.
+ {runtime_dependencies, ["wx-@OTP-18350@","stdlib-@OTP-18350@","runtime_tools-1.19",
+ "kernel-@OTP-18350@","et-1.5","erts-@OTP-18350@"]}]}.
diff --git a/lib/observer/src/observer.erl b/lib/observer/src/observer.erl
index 79ba7fd614..b2a878e54e 100644
--- a/lib/observer/src/observer.erl
+++ b/lib/observer/src/observer.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,11 +19,51 @@
-module(observer).
--export([start/0, stop/0]).
+-export([start/0, start/1, start_and_wait/0, start_and_wait/1, stop/0]).
start() ->
observer_wx:start().
+start(Node) when is_atom(Node) ->
+ start([Node]);
+start([Node]) ->
+ Node1 = to_atom(Node),
+ case net_kernel:connect_node(Node1) of
+ true ->
+ case observer_wx:start() of
+ ok ->
+ observer_wx:set_node(Node1),
+ ok;
+ Err ->
+ Err
+ end;
+ _ ->
+ {error, failed_to_connect}
+ end.
+
+start_and_wait() ->
+ ok = start(),
+ MonitorRef = monitor(process, observer),
+ receive
+ {'DOWN', MonitorRef, process, _, _} ->
+ ok
+ end.
+
+start_and_wait(Node) when is_atom(Node) ->
+ start_and_wait([Node]);
+start_and_wait(List) when is_list(List) ->
+ ok = start(List),
+ MonitorRef = monitor(process, observer),
+ receive
+ {'DOWN', MonitorRef, process, _, _} ->
+ ok
+ end.
+
stop() ->
observer_wx:stop().
+
+to_atom(Node) when is_atom(Node) ->
+ Node;
+to_atom(Node) when is_list(Node) ->
+ list_to_atom(Node).
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
index 321f189fc9..409fd4fcbb 100644
--- a/lib/observer/src/observer_wx.erl
+++ b/lib/observer/src/observer_wx.erl
@@ -22,7 +22,8 @@
-export([start/0, stop/0]).
-export([create_menus/2, get_attrib/1, get_tracer/0, get_active_node/0, get_menubar/0,
- get_scale/0, set_status/1, create_txt_dialog/4, try_rpc/4, return_to_localnode/2]).
+ get_scale/0, set_status/1, create_txt_dialog/4, try_rpc/4, return_to_localnode/2,
+ set_node/1]).
-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
handle_call/3, handle_info/2, check_page_title/1]).
@@ -89,6 +90,9 @@ get_tracer() ->
get_active_node() ->
wx_object:call(observer, get_active_node).
+set_node(Node) ->
+ wx_object:call(observer, {set_node, Node}).
+
get_menubar() ->
wx_object:call(observer, get_menubar).
@@ -427,6 +431,10 @@ handle_call(get_tracer, _From, State=#state{panels=Panels}) ->
handle_call(get_active_node, _From, State=#state{node=Node}) ->
{reply, Node, State};
+handle_call({set_node, Node}, _From, State) ->
+ State2 = change_node_view(Node, State),
+ {reply, ok, State2};
+
handle_call(get_menubar, _From, State=#state{menubar=MenuBar}) ->
{reply, MenuBar, State};
@@ -455,6 +463,7 @@ handle_info({nodedown, Node},
State3 = update_node_list(State2),
Msg = ["Node down: " | atom_to_list(Node)],
create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION),
+ filter_nodedown_messages(Node),
{noreply, State3};
handle_info({open_link, Id0}, State = #state{panels=Panels,frame=Frame}) ->
@@ -499,10 +508,10 @@ handle_info(_Info, State) ->
{noreply, State}.
stop_servers(#state{node=Node, log=LogOn, panels=Panels} = _State) ->
- LogOn andalso rpc:block_call(Node, rb, stop, []),
Me = self(),
- save_config(Panels),
Stop = fun() ->
+ LogOn andalso rpc:block_call(Node, rb, stop, []),
+ save_config(Panels),
try
_ = [wx_object:stop(Panel) || {_, Panel, _} <- Panels],
ok
@@ -874,6 +883,14 @@ is_rb_server_running(Node, LogState) ->
ok
end.
+filter_nodedown_messages(Node) ->
+ receive
+ {nodedown, Node} ->
+ filter_nodedown_messages(Node)
+ after
+ 0 ->
+ ok
+ end.
%% d(F) ->
%% d(F, []).
diff --git a/lib/observer/src/ttb.erl b/lib/observer/src/ttb.erl
index c4161f4e2a..e7a1ae164f 100644
--- a/lib/observer/src/ttb.erl
+++ b/lib/observer/src/ttb.erl
@@ -784,7 +784,7 @@ do_stop(nofetch, Sender, NodeInfo, SessionInfo) ->
ok,
NodeInfo),
stop_ip_to_file_trace_ports(SessionInfo),
- dbg:stop_clear(),
+ dbg:stop(),
ets:delete(?history_table),
Sender ! {?MODULE, stopped};
@@ -807,7 +807,7 @@ do_stop({FetchOrFormat, UserDir}, Sender, NodeInfo, SessionInfo) ->
[],
NodeInfo),
stop_ip_to_file_trace_ports(SessionInfo),
- dbg:stop_clear(),
+ dbg:stop(),
AllNodes =
lists:map(
fun({Node,MetaFile}) ->
@@ -1055,7 +1055,7 @@ format(Files,Out,Handler,DisableSort) when is_list(Files), is_list(hd(Files)) ->
file:close(Fd),
ets:delete(?MODULE),
case StopDbg of
- true -> dbg:stop_clear();
+ true -> dbg:stop();
false -> ok
end,
R.
diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl
index 1a1dca0fe0..0c0d741251 100644
--- a/lib/observer/test/observer_SUITE.erl
+++ b/lib/observer/test/observer_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -35,7 +35,8 @@
%% Test cases
-export([app_file/1, appup_file/1,
basic/1, process_win/1, table_win/1,
- port_win_when_tab_not_initiated/1
+ port_win_when_tab_not_initiated/1,
+ blocking_start/1, remote_node/1
]).
%% Default timetrap timeout (set in init_per_testcase)
@@ -58,7 +59,9 @@ groups() ->
[basic,
process_win,
table_win,
- port_win_when_tab_not_initiated
+ port_win_when_tab_not_initiated,
+ blocking_start,
+ remote_node
]
}].
@@ -468,6 +471,40 @@ table_win(Config) when is_list(Config) ->
?P("table_win -> done"),
ok.
+remote_node(_Config) ->
+ {ok, Peer, Node} = ?CT_PEER(),
+ ok = observer:start(Node),
+ timer:sleep(1000),
+ Node = observer_wx:get_active_node(),
+ observer:stop(),
+ ensure_observer_stopped(?SECS(3)),
+ peer:stop(Peer).
+
+blocking_start(_Config) ->
+ {Pid, SpawnerRef} = spawn_monitor(fun observer:start_and_wait/0),
+ timer:sleep(1000),
+ ObserverRef = monitor(process, observer),
+ receive
+ {'DOWN', ObserverRef, _, _, Reason} ->
+ error({observer_stopped_unexpectedly, Reason});
+ {'DOWN', SpawnerRef, _, _, Reason} ->
+ error({spawner_stopped_unexpectedly, Reason})
+ after
+ 500 ->
+ ok
+ end,
+ observer:stop(),
+ ensure_observer_stopped(?SECS(3)),
+ receive
+ {'DOWN', ObserverRef, _, _, _} ->
+ ok
+ after
+ 500 ->
+ error(observer_should_have_stopped)
+ end,
+ false = erlang:is_process_alive(Pid),
+ ok.
+
%% Test PR-1296/OTP-14151
%% Clicking a link to a port before the port tab has been activated the
%% first time crashes observer.
diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c
index 5a01630cbc..e99f16624e 100644
--- a/lib/odbc/c_src/odbcserver.c
+++ b/lib/odbc/c_src/odbcserver.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1999-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1999-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1328,7 +1328,7 @@ static db_result_msg encode_column_name_list(SQLSMALLINT num_of_columns,
&nullable)))
DO_EXIT(EXIT_DESC);
- if(sql_type == SQL_LONGVARCHAR || sql_type == SQL_LONGVARBINARY || sql_type == SQL_WLONGVARCHAR)
+ if(size == 0 && (sql_type == SQL_LONGVARCHAR || sql_type == SQL_LONGVARBINARY || sql_type == SQL_WLONGVARCHAR))
size = MAXCOLSIZE;
(columns(state)[i]).type.decimal_digits = dec_digits;
diff --git a/lib/odbc/configure b/lib/odbc/configure
index e429e5a946..12df3656c6 100755
--- a/lib/odbc/configure
+++ b/lib/odbc/configure
@@ -723,7 +723,6 @@ ac_subst_files=''
ac_user_opts='
enable_option_checking
with_odbc
-enable_sanitizers
'
ac_precious_vars='build_alias
host_alias
@@ -1350,13 +1349,6 @@ if test -n "$ac_init_help"; then
cat <<\_ACEOF
-Optional Features:
- --disable-option-checking ignore unrecognized --enable/--with options
- --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
- --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
- --enable-sanitizers[=comma-separated list of sanitizers]
- Default=address,undefined
-
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
@@ -5895,23 +5887,6 @@ fi
fi
-
-
-# Check whether --enable-sanitizers was given.
-if test ${enable_sanitizers+y}
-then :
- enableval=$enable_sanitizers;
-case "$enableval" in
- no) sanitizers= ;;
- yes) sanitizers="-fsanitize=address,undefined" ;;
- *) sanitizers="-fsanitize=$enableval" ;;
-esac
-CFLAGS="$CFLAGS $sanitizers"
-LDFLAGS="$LDFLAGS $sanitizers"
-
-fi
-
-
ac_config_files="$ac_config_files c_src/$host/Makefile:c_src/Makefile.in"
cat >confcache <<\_ACEOF
diff --git a/lib/odbc/configure.ac b/lib/odbc/configure.ac
index dfc3a6ec04..febdd044a5 100644
--- a/lib/odbc/configure.ac
+++ b/lib/odbc/configure.ac
@@ -1,7 +1,7 @@
dnl
dnl %CopyrightBegin%
dnl
-dnl Copyright Ericsson AB 2005-2021. All Rights Reserved.
+dnl Copyright Ericsson AB 2005-2023. All Rights Reserved.
dnl
dnl Licensed under the Apache License, Version 2.0 (the "License");
dnl you may not use this file except in compliance with the License.
@@ -256,25 +256,5 @@ AS_IF([test "x$GCC" = xyes],
LM_TRY_ENABLE_CFLAG([-Werror=return-type], [CFLAGS])
])
-dnl ----------------------------------------------------------------------
-dnl Enable -fsanitize= flags.
-dnl ----------------------------------------------------------------------
-
-m4_define(DEFAULT_SANITIZERS, [address,undefined])
-AC_ARG_ENABLE(
- sanitizers,
- AS_HELP_STRING(
- [--enable-sanitizers@<:@=comma-separated list of sanitizers@:>@],
- [Default=DEFAULT_SANITIZERS]),
-[
-case "$enableval" in
- no) sanitizers= ;;
- yes) sanitizers="-fsanitize=DEFAULT_SANITIZERS" ;;
- *) sanitizers="-fsanitize=$enableval" ;;
-esac
-CFLAGS="$CFLAGS $sanitizers"
-LDFLAGS="$LDFLAGS $sanitizers"
-])
-
AC_CONFIG_FILES([c_src/$host/Makefile:c_src/Makefile.in])
AC_OUTPUT
diff --git a/lib/os_mon/c_src/cpu_sup.c b/lib/os_mon/c_src/cpu_sup.c
index 415168fc72..fd04d35987 100644
--- a/lib/os_mon/c_src/cpu_sup.c
+++ b/lib/os_mon/c_src/cpu_sup.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1997-2021. All Rights Reserved.
+ * Copyright Ericsson AB 1997-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -87,6 +87,11 @@ typedef struct {
#define CU_BSD_VALUES (6)
#endif
+#if defined(__OpenBSD__)
+#include <sys/types.h>
+#include <sys/sched.h>
+#define CU_OPENBSD_VALUES (6)
+#endif
#define FD_IN (0)
#define FD_OUT (1)
@@ -178,12 +183,17 @@ static int processors_online() {
void getsysctl(const char *, void *, size_t);
#endif
+#if defined(__OpenBSD__)
+static int getncpu(void);
+static int getncpuonline(void);
+#endif
+
int main(int argc, char** argv) {
char cmd;
int rc;
int sz;
unsigned int *rv;
-#if defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) ||defined(__FreeBSD__)
+#if defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD__) || defined(__OpenBSD__)
unsigned int no_of_cpus = 0;
#endif
@@ -207,6 +217,15 @@ int main(int argc, char** argv) {
}
#endif
+#if defined(__OpenBSD__)
+ no_of_cpus = getncpu();
+ if ( no_of_cpus == -1 )
+ error("cpu_sup: sysctl error");
+
+ if ( (rv = (unsigned int*)malloc(sizeof(unsigned int)*(2 + 2*no_of_cpus*CU_OPENBSD_VALUES))) == NULL)
+ error("cpu_sup: malloc error");
+#endif
+
#if defined(__FreeBSD__)
getsysctl("hw.ncpu", &no_of_cpus, sizeof(int));
if ( (rv = (unsigned int*)malloc(sizeof(unsigned int)*(2 + 2*no_of_cpus*CU_BSD_VALUES))) == NULL) {
@@ -244,7 +263,7 @@ int main(int argc, char** argv) {
case AVG5: bsd_loadavg(1); break;
case AVG15: bsd_loadavg(2); break;
#endif
-#if defined(__sun__) || defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD__)
+#if defined(__sun__) || defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD__) || defined(__OpenBSD__)
case UTIL: util_measure(&rv,&sz); sendv(rv, sz); break;
#endif
case QUIT: free((void*)rv); return 0;
@@ -707,11 +726,77 @@ static void util_measure(unsigned int **result_vec, int *result_sz) {
rv[10] = CU_HARD_IRQ; rv[11] = cpu_times[CP_INTR + offset];
rv += CU_BSD_VALUES*2;
}
-
+
+ free((void*) cpu_times);
*result_sz = 2 + 2*CU_BSD_VALUES * no_of_cpus;
}
#endif
+/* ---------------------------- *
+ * OpenBSD stat functions *
+ * ---------------------------- */
+
+#if defined(__OpenBSD__)
+static int getncpu(void) {
+ const int mib[] = { CTL_HW, HW_NCPU };
+ int numcpu;
+ size_t size = sizeof(numcpu);
+
+ if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), &numcpu, &size, NULL, 0) == -1)
+ error("cpu_sup: sysctl error");
+
+ return(numcpu);
+}
+
+static int getncpuonline(void) {
+ const int mib[] = { CTL_HW, HW_NCPUONLINE };
+ int numcpu;
+ size_t size = sizeof(numcpu);
+
+ if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), &numcpu, &size, NULL, 0) == -1)
+ error("cpu_sup: sysctl error");
+
+ return(numcpu);
+}
+
+static void util_measure(unsigned int **result_vec, int *result_sz) {
+ static int mib[] = { CTL_KERN, KERN_CPTIME2, 0 };
+ size_t size_cpu_times;
+ int64_t *cpu_times;
+ unsigned int *rv = NULL;
+ int i;
+ int ncpuonline = getncpuonline();
+
+ rv = *result_vec;
+ rv[0] = ncpuonline;
+ rv[1] = CU_OPENBSD_VALUES;
+ ++rv; /* first value is number of cpus */
+ ++rv; /* second value is number of entries */
+
+ size_cpu_times = sizeof(int64_t) * CPUSTATES;
+ cpu_times = malloc(size_cpu_times);
+ if (!cpu_times)
+ error("cpu_sup: malloc error");
+
+ for (i = 0; i < ncpuonline; ++i) {
+ mib[2] = i;
+ if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), cpu_times, &size_cpu_times, NULL, 0) == -1)
+ error("cpu_sup: sysctl error");
+
+ rv[ 0] = CU_CPU_ID; rv[ 1] = i;
+ rv[ 2] = CU_USER; rv[ 3] = (unsigned int)cpu_times[CP_USER];
+ rv[ 4] = CU_NICE_USER; rv[ 5] = (unsigned int)cpu_times[CP_NICE];
+ rv[ 6] = CU_KERNEL; rv[ 7] = (unsigned int)cpu_times[CP_SYS];
+ rv[ 8] = CU_IDLE; rv[ 9] = (unsigned int)cpu_times[CP_IDLE];
+ rv[10] = CU_HARD_IRQ; rv[11] = (unsigned int)cpu_times[CP_INTR];
+ rv += CU_OPENBSD_VALUES*2;
+ }
+ free((void*) cpu_times);
+
+ *result_sz = 2 + 2*CU_OPENBSD_VALUES * ncpuonline;
+}
+#endif
+
/* ---------------------------- *
* Generic functions *
diff --git a/lib/os_mon/c_src/win32sysinfo.c b/lib/os_mon/c_src/win32sysinfo.c
index 1c9590fb8b..85622e0076 100644
--- a/lib/os_mon/c_src/win32sysinfo.c
+++ b/lib/os_mon/c_src/win32sysinfo.c
@@ -265,31 +265,30 @@ message_loop()
print_error("Erlang has closed");
return;
}
+
if ((res = read(0, &cmd, cmdLen)) == cmdLen){
if (cmdLen == 1) {
- switch (cmd[0]) {
- case MEM_INFO:
- get_avail_mem_ext();
- return_answer(OK);
- break;
- case DISK_INFO:
- get_disk_info_all();
- return_answer(OK);
- break;
- default: /* ignore all other messages */
- break;
- } /* switch */
+ switch (cmd[0]) {
+ case MEM_INFO:
+ get_avail_mem_ext();
+ return_answer(OK);
+ break;
+ case DISK_INFO:
+ get_disk_info_all();
+ return_answer(OK);
+ break;
+ default: /* ignore all other messages */
+ break;
+ } /* switch */
}
- else
- if ((res > 0) && (cmd[0]==DISK_INFO)) {
- cmd[cmdLen] = 0;
- output_drive_info(&cmd[1]);
- return_answer("OK");
- return;
- }
- else
- return_answer("xEND");
- }
+ else {
+ if ((res > 0) && (cmd[0]==DISK_INFO)) {
+ cmd[cmdLen] = 0;
+ output_drive_info(&cmd[1]);
+ }
+ return_answer(OK);
+ }
+ }
else if (res == 0) {
print_error("Erlang has closed");
return;
@@ -297,8 +296,8 @@ message_loop()
else {
print_error("Error reading from Erlang");
return;
- }
- }
+ }
+ }
}
int main(int argc, char ** argv){
diff --git a/lib/os_mon/doc/src/cpu_sup.xml b/lib/os_mon/doc/src/cpu_sup.xml
index e9dd930cf1..e79b3038b3 100644
--- a/lib/os_mon/doc/src/cpu_sup.xml
+++ b/lib/os_mon/doc/src/cpu_sup.xml
@@ -35,7 +35,7 @@
and CPU utilization. It is part of the OS_Mon application, see
<seeapp marker="os_mon_app">os_mon(6)</seeapp>. Available for Unix,
although CPU utilization values (<c>util/0,1</c>) are only
- available for Solaris, Linux and FreeBSD.</p>
+ available for Solaris, Linux, FreeBSD and OpenBSD.</p>
<p>The load values are proportional to how long time a runnable
Unix process has to spend in the run queue before it is scheduled.
Accordingly, higher values mean more system load. The returned
diff --git a/lib/os_mon/doc/src/disksup.xml b/lib/os_mon/doc/src/disksup.xml
index 59ba7eaf9e..4dd81e6099 100644
--- a/lib/os_mon/doc/src/disksup.xml
+++ b/lib/os_mon/doc/src/disksup.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -122,15 +122,15 @@
<name since="">get_disk_data() -> [DiskData]</name>
<fsummary>Get data for the disks in the system</fsummary>
<type>
- <v>DiskData = {Id, KByte, Capacity}</v>
+ <v>DiskData = {Id, TotalKiB, Capacity}</v>
<v>&nbsp;Id = string()</v>
- <v>&nbsp;KByte = int()</v>
+ <v>&nbsp;TotalKiB = int()</v>
<v>&nbsp;Capacity = int()</v>
</type>
<desc>
<p>Returns the result of the latest disk check. <c>Id</c> is a
- string that identifies the disk or partition. <c>KByte</c> is
- the total size of the disk or partition in kbytes.
+ string that identifies the disk or partition. <c>TotalKiB</c> is
+ the total size of the disk or partition in kibibytes.
<c>Capacity</c> is the percentage of disk space used.</p>
<p>The function is asynchronous in the sense that it does not
invoke a disk check, but returns the latest available value.</p>
@@ -139,6 +139,47 @@
</desc>
</func>
<func>
+ <name since="OTP @OTP-18303@">get_disk_info() -> [DiskData]</name>
+ <fsummary>Immediately get information for the disks in the system</fsummary>
+ <type>
+ <v>DiskData = {Id, TotalKiB, AvailableKiB, Capacity}</v>
+ <v>&nbsp;Id = string()</v>
+ <v>&nbsp;TotalKiB = int()</v>
+ <v>&nbsp;AvailableKiB = int()</v>
+ <v>&nbsp;Capacity = int()</v>
+ </type>
+ <desc>
+ <p>Immediately fetches total space, available space and capacity for local disks.
+ <c>Id</c> is a string that identifies the disk or partition.
+ <c>TotalKiB</c> is the total size of the disk or partition in kibibytes.
+ <c>AvailableKiB</c> is the disk space used in kibibytes.
+ <c>Capacity</c> is the percentage of disk space used.</p>
+ <p>Returns <c>[{"none",0,0,0}]</c> if <c>disksup</c> is not
+ available.</p>
+ </desc>
+ </func>
+ <func>
+ <name since="OTP @OTP-18303@">get_disk_info(Path) -> DiskData</name>
+ <fsummary>Immediately get information for a single path</fsummary>
+ <type>
+ <v>DiskData = [{Id, TotalKiB, AvailableKiB, Capacity}]</v>
+ <v>&nbsp;Id = string()</v>
+ <v>&nbsp;TotalKiB = int()</v>
+ <v>&nbsp;AvailableKiB = int()</v>
+ <v>&nbsp;Capacity = int()</v>
+ </type>
+ <desc>
+ <p>Immediately fetches total space, available space and capacity for a path.
+ <c>Id</c> is a string that identifies the disk or partition.
+ <c>TotalKiB</c> is the total size of the disk or partition in kibibytes.
+ <c>AvailableKiB</c> is the disk space used in kibibytes.
+ <c>Capacity</c> is the percentage of disk space used.</p>
+ <p>Returns <c>[{Path,0,0,0}]</c> if the <c>Path</c> is invalid or space
+ can't be determined. Returns <c>[{"none",0,0,0}]</c> if <c>disksup</c> is not
+ available.</p>
+ </desc>
+ </func>
+ <func>
<name since="">get_check_interval() -> MS</name>
<fsummary>Get time interval, in milliseconds, for the periodic disk space check</fsummary>
<type>
diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml
index 1fac53d330..439c1e839a 100644
--- a/lib/os_mon/doc/src/notes.xml
+++ b/lib/os_mon/doc/src/notes.xml
@@ -31,6 +31,23 @@
</header>
<p>This document describes the changes made to the OS_Mon application.</p>
+<section><title>Os_Mon 2.8.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid error report from failing <c>erlang:port_close</c>
+ at shutdown of <c>cpu_sup</c> and <c>memsup</c>. Bug
+ exists since OTP 25.3 (os_mon-2.8.1).</p>
+ <p>
+ Own Id: OTP-18559 Aux Id: ERIERL-942 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Os_Mon 2.8.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/os_mon/src/cpu_sup.erl b/lib/os_mon/src/cpu_sup.erl
index be917021e7..a82dfd9961 100644
--- a/lib/os_mon/src/cpu_sup.erl
+++ b/lib/os_mon/src/cpu_sup.erl
@@ -163,6 +163,7 @@ handle_call({?util, D, PC}, {Client, _Tag},
when Flavor == sunos;
Flavor == linux;
Flavor == freebsd;
+ Flavor == openbsd;
Flavor == darwin ->
case measurement_server_call(State#state.server, {?util, D, PC, Client}) of
{error, Reason} ->
@@ -613,8 +614,8 @@ port_server_loop(Port, Timeout) ->
% Close port and this server
{Pid, ?quit} ->
- port_command(Port, ?quit),
- port_close(Port),
+ Port ! {self(), {command, ?quit}},
+ Port ! {self(), close},
Pid ! {self(), {data, quit}},
ok;
diff --git a/lib/os_mon/src/disksup.erl b/lib/os_mon/src/disksup.erl
index ecf3928d89..8df01f9eb9 100644
--- a/lib/os_mon/src/disksup.erl
+++ b/lib/os_mon/src/disksup.erl
@@ -22,7 +22,7 @@
%% API
-export([start_link/0]).
--export([get_disk_data/0,
+-export([get_disk_data/0, get_disk_info/0, get_disk_info/1,
get_check_interval/0, set_check_interval/1,
get_almost_full_threshold/0, set_almost_full_threshold/1]).
-export([dummy_reply/1, param_type/2, param_default/1]).
@@ -46,6 +46,12 @@ start_link() ->
get_disk_data() ->
os_mon:call(disksup, get_disk_data, infinity).
+get_disk_info() ->
+ os_mon:call(disksup, get_disk_info, infinity).
+
+get_disk_info(Path) ->
+ os_mon:call(disksup, {get_disk_info, Path}, infinity).
+
get_check_interval() ->
os_mon:call(disksup, get_check_interval, infinity).
set_check_interval(Value) ->
@@ -68,6 +74,10 @@ set_almost_full_threshold(Float) ->
dummy_reply(get_disk_data) ->
[{"none", 0, 0}];
+dummy_reply(get_disk_info) ->
+ [{"none", 0, 0, 0}];
+dummy_reply({get_disk_info, Path}) ->
+ [{Path, 0, 0, 0}];
dummy_reply(get_check_interval) ->
case os_mon:get_env(disksup, disk_space_check_interval) of
{TimeUnit, Time} ->
@@ -149,6 +159,12 @@ init([]) ->
handle_call(get_disk_data, _From, State) ->
{reply, State#state.diskdata, State};
+handle_call(get_disk_info, _From, #state{os = OS, port = Port} = State) ->
+ {reply, get_disk_info(OS, Port), State};
+
+handle_call({get_disk_info, Path}, _From, #state{os = OS, port = Port} = State) ->
+ {reply, get_disk_info(Path, OS, Port), State};
+
handle_call(get_check_interval, _From, State) ->
{reply, State#state.timeout, State};
handle_call({set_check_interval, {TimeUnit, Time}}, _From, State) ->
@@ -261,6 +277,134 @@ find_cmd(Cmd, Path) ->
Found
end.
+%%-- Run "df" based on OS ----------------------------------------------
+run_df(OS, Port) ->
+ run_df("", OS, Port).
+
+run_df(Path, {unix, solaris}, Port) ->
+ my_cmd("/usr/bin/df -lk " ++ Path, Port);
+run_df(Path, {unix, irix}, Port) ->
+ my_cmd("/usr/sbin/df -lk " ++ Path, Port);
+run_df(Path, {unix, linux}, Port) ->
+ Df = find_cmd("df", "/bin"),
+ my_cmd(Df ++ " -lk -x squashfs " ++ Path, Port);
+run_df(Path, {unix, posix}, Port) ->
+ my_cmd("df -k -P " ++ Path, Port);
+run_df(Path, {unix, dragonfly}, Port) ->
+ my_cmd("/bin/df -k -t ufs,hammer " ++ Path, Port);
+run_df(Path, {unix, freebsd}, Port) ->
+ my_cmd("/bin/df -k -l " ++ Path, Port);
+run_df(Path, {unix, openbsd}, Port) ->
+ my_cmd("/bin/df -k -l " ++ Path, Port);
+run_df(Path, {unix, netbsd}, Port) ->
+ my_cmd("/bin/df -k -t ffs " ++ Path, Port);
+run_df(Path, {unix, sunos4}, Port) ->
+ my_cmd("df " ++ Path, Port);
+run_df(Path, {unix, darwin}, Port) ->
+ my_cmd("/bin/df -i -k -t ufs,hfs,apfs " ++ Path, Port).
+
+%%--Get disk info-------------------------------------------------------
+%% We use as many absolute paths as possible below as there may be stale
+%% NFS handles in the PATH which cause these commands to hang.
+get_disk_info(OS, Port) ->
+ get_disk_info("", OS, Port).
+
+get_disk_info(Path, OS, Port) ->
+ case do_get_disk_info(Path, OS, Port) of
+ [] -> dummy_reply({get_disk_info, Path});
+ DiskInfo -> DiskInfo
+ end.
+
+do_get_disk_info("", {win32, _}, not_used) ->
+ Result = os_mon_sysinfo:get_disk_info(),
+ disk_info_win32(Result);
+do_get_disk_info(DriveRoot, {win32, _}, not_used) ->
+ Result = os_mon_sysinfo:get_disk_info(DriveRoot),
+ disk_info_win32(Result);
+do_get_disk_info(Path, {unix, solaris}=OS, Port) ->
+ Result = run_df(Path, OS, Port),
+ disk_info_solaris(skip_to_eol(Result));
+do_get_disk_info(Path, {unix, irix}=OS, Port) ->
+ Result = run_df(Path, OS, Port),
+ disk_info_irix(skip_to_eol(Result));
+do_get_disk_info(Path, {unix, linux}=OS, Port) ->
+ Result = run_df(Path, OS, Port),
+ disk_info_solaris(skip_to_eol(Result));
+do_get_disk_info(Path, {unix, posix}=OS, Port) ->
+ Result = run_df(Path, OS, Port),
+ disk_info_solaris(skip_to_eol(Result));
+do_get_disk_info(Path, {unix, dragonfly}=OS, Port) ->
+ Result = run_df(Path, OS, Port),
+ disk_info_solaris(skip_to_eol(Result));
+do_get_disk_info(Path, {unix, freebsd}=OS, Port) ->
+ Result = run_df(Path, OS, Port),
+ disk_info_solaris(skip_to_eol(Result));
+do_get_disk_info(Path, {unix, openbsd}=OS, Port) ->
+ Result = run_df(Path, OS, Port),
+ disk_info_solaris(skip_to_eol(Result));
+do_get_disk_info(Path, {unix, netbsd}=OS, Port) ->
+ Result = run_df(Path, OS, Port),
+ disk_info_solaris(skip_to_eol(Result));
+do_get_disk_info(Path, {unix, sunos4}=OS, Port) ->
+ Result = run_df(Path, OS, Port),
+ disk_info_solaris(skip_to_eol(Result));
+do_get_disk_info(Path, {unix, darwin}=OS, Port) ->
+ Result = run_df(Path, OS, Port),
+ disk_info_susv3(skip_to_eol(Result)).
+
+disk_info_win32([]) ->
+ [];
+disk_info_win32([H|T]) ->
+ case io_lib:fread("~s~s~d~d~d", H) of
+ {ok, [Drive, "DRIVE_FIXED", BAvail, BTot, _TotFree], _RestStr} ->
+ KiBTotal = BTot div 1024,
+ KiBAvailable = BAvail div 1024,
+ BUsed = BTot - BAvail,
+ Capacity = trunc(math:ceil(100 * (BUsed / BTot))),
+ [{Drive, KiBTotal, KiBAvailable, Capacity} | disk_info_win32(T)];
+ {ok, _, _RestStr} ->
+ disk_info_win32(T);
+ _Other ->
+ []
+ end.
+
+% This code works for Linux and FreeBSD as well
+disk_info_solaris("") ->
+ [];
+disk_info_solaris("\n") ->
+ [];
+disk_info_solaris(Str) ->
+ case parse_df(Str, posix) of
+ {ok, {KiBTotal, KiBAvailable, Capacity, MntOn}, RestStr} ->
+ [{MntOn, KiBTotal, KiBAvailable, Capacity} | disk_info_solaris(RestStr)];
+ _Other ->
+ disk_info_solaris(skip_to_eol(Str))
+ end.
+
+%% Irix: like Linux with an extra FS type column and no '%'.
+disk_info_irix("") -> [];
+disk_info_irix("\n") -> [];
+disk_info_irix(Str) ->
+ case io_lib:fread("~s~s~d~d~d~d~s", Str) of
+ {ok, [_FS, _FSType, KiBAvailable, Capacity, _Avail, KiBTotal, MntOn], RestStr} ->
+ [{MntOn, KiBTotal, KiBAvailable, Capacity} | disk_info_irix(RestStr)];
+ _Other ->
+ disk_info_irix(skip_to_eol(Str))
+ end.
+
+% Parse per SUSv3 specification, notably recent OS X
+disk_info_susv3("") ->
+ [];
+disk_info_susv3("\n") ->
+ [];
+disk_info_susv3(Str) ->
+ case parse_df(Str, susv3) of
+ {ok, {KiBTotal, KiBAvailable, Capacity, MntOn}, RestStr} ->
+ [{MntOn, KiBTotal, KiBAvailable, Capacity} | disk_info_susv3(RestStr)];
+ _Other ->
+ disk_info_susv3(skip_to_eol(Str))
+ end.
+
%%--Check disk space----------------------------------------------------
%% We use as many absolute paths as possible below as there may be stale
@@ -268,36 +412,35 @@ find_cmd(Cmd, Path) ->
check_disk_space({win32,_}, not_used, Threshold) ->
Result = os_mon_sysinfo:get_disk_info(),
check_disks_win32(Result, Threshold);
-check_disk_space({unix, solaris}, Port, Threshold) ->
- Result = my_cmd("/usr/bin/df -lk", Port),
+check_disk_space({unix, solaris}=OS, Port, Threshold) ->
+ Result = run_df(OS, Port),
check_disks_solaris(skip_to_eol(Result), Threshold);
-check_disk_space({unix, irix}, Port, Threshold) ->
- Result = my_cmd("/usr/sbin/df -lk",Port),
+check_disk_space({unix, irix}=OS, Port, Threshold) ->
+ Result = run_df(OS, Port),
check_disks_irix(skip_to_eol(Result), Threshold);
-check_disk_space({unix, linux}, Port, Threshold) ->
- Df = find_cmd("df", "/bin"),
- Result = my_cmd(Df ++ " -lk -x squashfs", Port),
+check_disk_space({unix, linux}=OS, Port, Threshold) ->
+ Result = run_df(OS, Port),
check_disks_solaris(skip_to_eol(Result), Threshold);
-check_disk_space({unix, posix}, Port, Threshold) ->
- Result = my_cmd("df -k -P", Port),
+check_disk_space({unix, posix}=OS, Port, Threshold) ->
+ Result = run_df(OS, Port),
check_disks_solaris(skip_to_eol(Result), Threshold);
-check_disk_space({unix, dragonfly}, Port, Threshold) ->
- Result = my_cmd("/bin/df -k -t ufs,hammer", Port),
+check_disk_space({unix, dragonfly}=OS, Port, Threshold) ->
+ Result = run_df(OS, Port),
check_disks_solaris(skip_to_eol(Result), Threshold);
-check_disk_space({unix, freebsd}, Port, Threshold) ->
- Result = my_cmd("/bin/df -k -l", Port),
+check_disk_space({unix, freebsd}=OS, Port, Threshold) ->
+ Result = run_df(OS, Port),
check_disks_solaris(skip_to_eol(Result), Threshold);
-check_disk_space({unix, openbsd}, Port, Threshold) ->
- Result = my_cmd("/bin/df -k -l", Port),
+check_disk_space({unix, openbsd}=OS, Port, Threshold) ->
+ Result = run_df(OS, Port),
check_disks_solaris(skip_to_eol(Result), Threshold);
-check_disk_space({unix, netbsd}, Port, Threshold) ->
- Result = my_cmd("/bin/df -k -t ffs", Port),
+check_disk_space({unix, netbsd}=OS, Port, Threshold) ->
+ Result = run_df(OS, Port),
check_disks_solaris(skip_to_eol(Result), Threshold);
-check_disk_space({unix, sunos4}, Port, Threshold) ->
- Result = my_cmd("df", Port),
+check_disk_space({unix, sunos4}=OS, Port, Threshold) ->
+ Result = run_df(OS, Port),
check_disks_solaris(skip_to_eol(Result), Threshold);
-check_disk_space({unix, darwin}, Port, Threshold) ->
- Result = my_cmd("/bin/df -i -k -t ufs,hfs,apfs", Port),
+check_disk_space({unix, darwin}=OS, Port, Threshold) ->
+ Result = run_df(OS, Port),
check_disks_susv3(skip_to_eol(Result), Threshold).
% This code works for Linux and FreeBSD as well
@@ -307,14 +450,14 @@ check_disks_solaris("\n", _Threshold) ->
[];
check_disks_solaris(Str, Threshold) ->
case parse_df(Str, posix) of
- {ok, {KB, Cap, MntOn}, RestStr} ->
+ {ok, {KiBTotal, _KiBAvailable, Capacity, MntOn}, RestStr} ->
if
- Cap >= Threshold ->
+ Capacity >= Threshold ->
set_alarm({disk_almost_full, MntOn}, []);
true ->
clear_alarm({disk_almost_full, MntOn})
end,
- [{MntOn, KB, Cap} |
+ [{MntOn, KiBTotal, Capacity} |
check_disks_solaris(RestStr, Threshold)];
_Other ->
check_disks_solaris(skip_to_eol(Str),Threshold)
@@ -369,15 +512,15 @@ parse_df_take_word_percent(Input) ->
%% and capacity), skip % sign, (optionally for susv3 can also skip IUsed, IFree
%% and ICap% fields) then take remaining characters as the mount path
-spec parse_df(string(), posix | susv3) ->
- {error, parse_df} | {ok, {integer(), integer(), list()}, string()}.
+ {error, parse_df} | {ok, {integer(), integer(), integer(), list()}, string()}.
parse_df(Input0, Flavor) ->
%% Format of Posix/Linux df output looks like Header + Lines
%% Filesystem 1024-blocks Used Available Capacity Mounted on
%% udev 2467108 0 2467108 0% /dev
Input1 = parse_df_skip_word(Input0), % skip device path field
- {KbStr, Input2} = parse_df_take_word(Input1), % take Kb field
+ {KiBTotalStr, Input2} = parse_df_take_word(Input1), % take Kb field
Input3 = parse_df_skip_word(Input2), % skip Used field
- Input4 = parse_df_skip_word(Input3), % skip Avail field
+ {KiBAvailableStr, Input4} = parse_df_take_word(Input3),
% take Capacity% field; drop a % sign following the capacity
{CapacityStr, Input5} = parse_df_take_word_percent(Input4),
@@ -401,9 +544,10 @@ parse_df(Input0, Flavor) ->
Remaining = lists:dropwhile(fun(X) -> not parse_df_is_not_eol(X) end,
Input7),
try
- Kb = erlang:list_to_integer(KbStr),
+ KiBTotal = erlang:list_to_integer(KiBTotalStr),
+ KiBAvailable = erlang:list_to_integer(KiBAvailableStr),
Capacity = erlang:list_to_integer(CapacityStr),
- {ok, {Kb, Capacity, MountPath}, Remaining}
+ {ok, {KiBTotal, KiBAvailable, Capacity, MountPath}, Remaining}
catch error:badarg ->
{error, parse_df}
end.
@@ -415,14 +559,14 @@ check_disks_susv3("\n", _Threshold) ->
[];
check_disks_susv3(Str, Threshold) ->
case parse_df(Str, susv3) of
- {ok, {KB, Cap, MntOn}, RestStr} ->
+ {ok, {KiBTotal, _KiBAvailable, Capacity, MntOn}, RestStr} ->
if
- Cap >= Threshold ->
+ Capacity >= Threshold ->
set_alarm({disk_almost_full, MntOn}, []);
true ->
clear_alarm({disk_almost_full, MntOn})
end,
- [{MntOn, KB, Cap} |
+ [{MntOn, KiBTotal, Capacity} |
check_disks_susv3(RestStr, Threshold)];
_Other ->
check_disks_susv3(skip_to_eol(Str),Threshold)
diff --git a/lib/os_mon/src/memsup.erl b/lib/os_mon/src/memsup.erl
index 1c93bfbae4..c467adc2e5 100644
--- a/lib/os_mon/src/memsup.erl
+++ b/lib/os_mon/src/memsup.erl
@@ -653,7 +653,7 @@ start_portprogram() ->
port_shutdown(Port) ->
Port ! {self(), {command, [?EXIT]}},
- port_close(Port).
+ Port ! {self(), close}.
%% The connected process loops are a bit awkward (several different
%% functions doing almost the same thing) as
diff --git a/lib/os_mon/src/os_mon.app.src b/lib/os_mon/src/os_mon.app.src
index 76d056de04..883be4f047 100644
--- a/lib/os_mon/src/os_mon.app.src
+++ b/lib/os_mon/src/os_mon.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -31,4 +31,4 @@
{start_memsup, true},
{start_os_sup, false}]},
{mod, {os_mon, []}},
- {runtime_dependencies, ["stdlib-3.4","sasl-2.4","kernel-3.0","erts-6.0"]}]}.
+ {runtime_dependencies, ["stdlib-@OTP-18350@","sasl-@OTP-18350@","kernel-@OTP-18350@","erts-@OTP-18350@"]}]}.
diff --git a/lib/os_mon/src/os_mon_sysinfo.erl b/lib/os_mon/src/os_mon_sysinfo.erl
index 4f5a682a1f..5e9fdd7ae9 100644
--- a/lib/os_mon/src/os_mon_sysinfo.erl
+++ b/lib/os_mon/src/os_mon_sysinfo.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ get_disk_info() ->
gen_server:call(os_mon_sysinfo, get_disk_info).
get_disk_info(DriveRoot) ->
- gen_server:call(os_mon_sysinfo, {get_disk_info,DriveRoot}).
+ gen_server:call(os_mon_sysinfo, {get_disk_info, DriveRoot}).
get_mem_info() ->
gen_server:call(os_mon_sysinfo, get_mem_info).
@@ -65,8 +65,8 @@ init([]) ->
handle_call(get_disk_info, _From, State) ->
{reply, get_disk_info1(State#state.port), State};
-handle_call({get_disk_info,RootList}, _From, State) ->
- {reply, get_disk_info1(State#state.port,RootList), State};
+handle_call({get_disk_info, DriveRoot}, _From, State) ->
+ {reply, get_disk_info1(State#state.port, DriveRoot), State};
handle_call(get_mem_info, _From, State) ->
{reply, get_mem_info1(State#state.port), State}.
@@ -108,8 +108,8 @@ get_disk_info1(Port) ->
Port ! {self(),{command,[?DISK_INFO]}},
get_data(Port,[]).
-get_disk_info1(Port,PathList) ->
- Port ! {self(),{command,[?DISK_INFO|[P++[0]||P <- PathList]]}},
+get_disk_info1(Port, DriveRoot) ->
+ Port ! {self(), {command,[?DISK_INFO|DriveRoot++[0]]}},
get_data(Port,[]).
get_mem_info1(Port) ->
diff --git a/lib/os_mon/test/cpu_sup_SUITE.erl b/lib/os_mon/test/cpu_sup_SUITE.erl
index e719963ea9..f3494da860 100644
--- a/lib/os_mon/test/cpu_sup_SUITE.erl
+++ b/lib/os_mon/test/cpu_sup_SUITE.erl
@@ -63,6 +63,8 @@ all() ->
[load_api, util_api, util_values, port, unavailable];
{unix, freebsd} ->
[load_api, util_api, util_values, port, unavailable];
+ {unix, openbsd} ->
+ [load_api, util_api, util_values, port, unavailable];
{unix, darwin} ->
[load_api, util_api, util_values, port, unavailable];
{unix, netbsd} -> [unavailable];
diff --git a/lib/os_mon/test/disksup_SUITE.erl b/lib/os_mon/test/disksup_SUITE.erl
index 1590b1a735..48e1580801 100644
--- a/lib/os_mon/test/disksup_SUITE.erl
+++ b/lib/os_mon/test/disksup_SUITE.erl
@@ -73,6 +73,9 @@ api(Config) when is_list(Config) ->
%% get_disk_data()
ok = check_get_disk_data(),
+ %% get_disk_info()
+ ok = check_get_disk_info(),
+
%% get_check_interval()
1800000 = disksup:get_check_interval(),
@@ -439,6 +442,25 @@ check_get_disk_data() ->
true = KByte>0,
ok.
+check_get_disk_info() ->
+ DiskInfo = disksup:get_disk_info(),
+ [{Id, TotalKiB, AvailableKiB, Capacity}|_] = DiskInfo,
+ true = io_lib:printable_list(Id),
+ true = is_integer(TotalKiB),
+ true = is_integer(AvailableKiB),
+ true = is_integer(Capacity),
+ true = TotalKiB > 0,
+ true = AvailableKiB > 0,
+
+ [DiskInfoRoot|_] = disksup:get_disk_info("/"),
+ {"/", TotalKiBRoot, AvailableKiBRoot, CapacityRoot} = DiskInfoRoot,
+ true = is_integer(TotalKiBRoot),
+ true = is_integer(AvailableKiBRoot),
+ true = is_integer(CapacityRoot),
+ true = TotalKiBRoot > 0,
+ true = AvailableKiBRoot > 0,
+ ok.
+
% filter get_disk_data and remove entriew with zero capacity
% "non-normal" filesystems report zero capacity
% - Perhaps erroneous 'df -k -l'?
@@ -461,11 +483,11 @@ parse_df_output_posix(Config) when is_list(Config) ->
%% Have a simple example with no funny spaces in mount path
Posix1 = "tmpfs 498048 7288 490760 2% /run\n",
- {ok, {498048, 2, "/run"}, ""} = disksup:parse_df(Posix1, posix),
+ {ok, {498048, 490760, 2, "/run"}, ""} = disksup:parse_df(Posix1, posix),
%% Have a mount path with some spaces in it
Posix2 = "tmpfs 498048 7288 490760 2% /spaces 1 2\n",
- {ok, {498048, 2, "/spaces 1 2"}, ""} = disksup:parse_df(Posix2, posix).
+ {ok, {498048, 490760, 2, "/spaces 1 2"}, ""} = disksup:parse_df(Posix2, posix).
%% @doc Test various expected inputs to 'df' command output (Darwin/SUSv3)
parse_df_output_susv3(Config) when is_list(Config) ->
@@ -478,9 +500,9 @@ parse_df_output_susv3(Config) when is_list(Config) ->
%% Have a simple example with no funny spaces in mount path
Darwin1 = "/dev/disk1 243949060 157002380 86690680 65% 2029724 " ++
"4292937555 0% /\n",
- {ok, {243949060, 65, "/"}, ""} = disksup:parse_df(Darwin1, susv3),
+ {ok, {243949060, 86690680, 65, "/"}, ""} = disksup:parse_df(Darwin1, susv3),
%% Have a mount path with some spaces in it
Darwin2 = "/dev/disk1 243949060 157002380 86690680 65% 2029724 " ++
"4292937555 0% /spaces 1 2\n",
- {ok, {243949060, 65, "/spaces 1 2"}, ""} = disksup:parse_df(Darwin2, susv3).
+ {ok, {243949060, 86690680, 65, "/spaces 1 2"}, ""} = disksup:parse_df(Darwin2, susv3).
diff --git a/lib/os_mon/vsn.mk b/lib/os_mon/vsn.mk
index 9e64a83612..d6878e6773 100644
--- a/lib/os_mon/vsn.mk
+++ b/lib/os_mon/vsn.mk
@@ -1 +1 @@
-OS_MON_VSN = 2.8.1
+OS_MON_VSN = 2.8.2
diff --git a/lib/parsetools/doc/src/leex.xml b/lib/parsetools/doc/src/leex.xml
index d802e46b59..56c44caadb 100644
--- a/lib/parsetools/doc/src/leex.xml
+++ b/lib/parsetools/doc/src/leex.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2009</year><year>2022</year>
+ <year>2009</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -115,6 +115,22 @@
<p>Causes generated -file() attributes to only include
the basename of the file path.</p>
</item>
+ <tag><c>{error_location, line | column}</c></tag>
+ <item>
+ <p>If set to <c>column</c>, error location will be
+ <c>{Line,Column}</c> tuple instead of just <c>Line</c>. Also, <c>StartLoc</c>
+ and <c>EndLoc</c> in <c>string/2</c>, <c>token/3</c>, and <c>tokens/3</c>
+ functions will be <c>{Line,Column}</c> tuple instead of just <c>Line</c>.
+ Default is <c>line</c>. Note that you can use <c>TokenLoc</c> for token
+ location independently, even if the <c>error_location</c> is set to <c>line</c>.</p>
+ <p>Unicode characters are counted as many columns as they use bytes
+ to represent.</p>
+ </item>
+ <tag><c>{tab_size, pos_integer()}</c></tag>
+ <item>
+ <p>Sets the width of <c>\t</c> character (only relevant if <c>error_location</c>
+ is set to <c>column</c>). Default is <c>8</c>.</p>
+ </item>
</taglist>
<p>Any of the Boolean options can be set to <c>true</c> by
stating the name of the option. For example, <c>verbose</c>
@@ -147,17 +163,18 @@
</fsdescription>
<func>
<name since="">Module:string(String) -> StringRet</name>
- <name since="">Module:string(String, StartLine) -> StringRet</name>
+ <name since="">Module:string(String, StartLoc) -> StringRet</name>
<fsummary>Generated by Leex</fsummary>
<type>
<v>String = string()</v>
- <v>StringRet = {ok,Tokens,EndLine} | ErrorInfo</v>
+ <v>StringRet = {ok,Tokens,EndLoc} | ErrorInfo</v>
<v>Tokens = [Token]</v>
- <v>EndLine = StartLine = erl_anno:line()</v>
+ <v>StartLoc = EndLoc = erl_anno:location()</v>
</type>
<desc>
<p>Scans <c>String</c> and returns all the tokens in it, or an
- error.</p>
+ error. <c>StartLoc</c> and <c>EndLoc</c> are either <c>erl_anno:line()</c>
+ or <c>erl_anno:location()</c>, depending on the <c>error_location</c> option.</p>
<note><p>It is an error if not all of the characters in
<c>String</c> are consumed.</p></note>
</desc>
@@ -166,7 +183,7 @@
<func>
<name since="">Module:token(Cont, Chars) -> {more,Cont1} | {done,TokenRet,RestChars}
</name>
- <name since="">Module:token(Cont, Chars, StartLine) -> {more,Cont1}
+ <name since="">Module:token(Cont, Chars, StartLoc) -> {more,Cont1}
| {done,TokenRet,RestChars}
</name>
<fsummary>Generated by Leex</fsummary>
@@ -174,10 +191,10 @@
<v>Cont = [] | Cont1</v>
<v>Cont1 = tuple()</v>
<v>Chars = RestChars = string() | eof</v>
- <v>TokenRet = {ok, Token, EndLine}
- | {eof, EndLine}
+ <v>TokenRet = {ok, Token, EndLoc}
+ | {eof, EndLoc}
| ErrorInfo</v>
- <v>StartLine = EndLine = erl_anno:line()</v>
+ <v>StartLoc = EndLoc = erl_anno:location()</v>
</type>
<desc>
<p>This is a re-entrant call to try and scan one token from
@@ -193,7 +210,7 @@
but used through the i/o system where it can typically be
called in an application by:</p>
<code>
-io:request(InFile, {get_until,unicode,Prompt,Module,token,[Line]})
+io:request(InFile, {get_until,unicode,Prompt,Module,token,[Loc]})
-> TokenRet</code>
</desc>
</func>
@@ -201,7 +218,7 @@ io:request(InFile, {get_until,unicode,Prompt,Module,token,[Line]})
<func>
<name since="">Module:tokens(Cont, Chars) -> {more,Cont1} | {done,TokensRet,RestChars}
</name>
- <name since="">Module:tokens(Cont, Chars, StartLine) ->
+ <name since="">Module:tokens(Cont, Chars, StartLoc) ->
{more,Cont1} | {done,TokensRet,RestChars}
</name>
<fsummary>Generated by Leex</fsummary>
@@ -209,11 +226,11 @@ io:request(InFile, {get_until,unicode,Prompt,Module,token,[Line]})
<v>Cont = [] | Cont1</v>
<v>Cont1 = tuple()</v>
<v>Chars = RestChars = string() | eof</v>
- <v>TokensRet = {ok, Tokens, EndLine}
- | {eof, EndLine}
+ <v>TokensRet = {ok, Tokens, EndLoc}
+ | {eof, EndLoc}
| ErrorInfo</v>
<v>Tokens = [Token]</v>
- <v>StartLine = EndLine = erl_anno:line()</v>
+ <v>StartLoc = EndLoc = erl_anno:location()</v>
</type>
<desc>
<p>This is a re-entrant call to try and scan tokens from
@@ -240,7 +257,7 @@ io:request(InFile, {get_until,unicode,Prompt,Module,token,[Line]})
but used through the i/o system where it can typically be
called in an application by:</p>
<code>
-io:request(InFile, {get_until,unicode,Prompt,Module,tokens,[Line]})
+io:request(InFile, {get_until,unicode,Prompt,Module,tokens,[Loc]})
-> TokensRet</code>
</desc>
</func>
@@ -320,6 +337,14 @@ NAME = VALUE</code>
<tag><c>TokenLine</c></tag>
<item><p>The line number where the token occurred.</p>
</item>
+ <tag><c>TokenCol</c></tag>
+ <item><p>The column number where the token occurred
+ (column of the first character included in the token).</p>
+ </item>
+ <tag><c>TokenLoc</c></tag>
+ <item><p>Token location. Expands to <c>{TokenLine,TokenCol}</c> (even
+ when <c>error_location</c> is set to <c>line</c>.</p>
+ </item>
</taglist>
<p>The code must return:</p>
diff --git a/lib/parsetools/include/leexinc.hrl b/lib/parsetools/include/leexinc.hrl
index 8dfc42f479..a06584ff79 100644
--- a/lib/parsetools/include/leexinc.hrl
+++ b/lib/parsetools/include/leexinc.hrl
@@ -16,261 +16,269 @@
format_error({illegal,S}) -> ["illegal characters ",io_lib:write_string(S)];
format_error({user,S}) -> S.
-string(String) -> string(String, 1).
-
-string(String, Line) -> string(String, Line, String, []).
-
-%% string(InChars, Line, TokenChars, Tokens) ->
-%% {ok,Tokens,Line} | {error,ErrorInfo,Line}.
-%% Note the line number going into yystate, L0, is line of token
-%% start while line number returned is line of token end. We want line
-%% of token start.
-
-string([], L, [], Ts) -> % No partial tokens!
- {ok,yyrev(Ts),L};
-string(Ics0, L0, Tcs, Ts) ->
- case yystate(yystate(), Ics0, L0, 0, reject, 0) of
- {A,Alen,Ics1,L1} -> % Accepting end state
- string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L0), Ts);
- {A,Alen,Ics1,L1,_S1} -> % Accepting transition state
- string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L0), Ts);
- {reject,_Alen,Tlen,_Ics1,L1,_S1} -> % After a non-accepting state
- {error,{L0,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},L1};
- {A,Alen,Tlen,_Ics1,L1,_S1} ->
+%% string(InChars) ->
+%% string(InChars, Loc) ->
+%% {ok,Tokens,EndLoc} | {error,ErrorInfo,EndLoc}.
+%% Loc is the starting location of the token, while EndLoc is the first not scanned
+%% location. Location is either Line or {Line,Column}, depending on the "error_location" option.
+
+##str
+
+do_string([], L, C, [], Ts) -> % No partial tokens!
+ {ok,yyrev(Ts),{L,C}};
+do_string(Ics0, L0, C0, Tcs, Ts) ->
+ case yystate(yystate(), Ics0, L0, C0, 0, reject, 0) of
+ {A,Alen,Ics1,L1,_C1} -> % Accepting end state
+ C2 = adjust_col(Tcs, Alen, C0),
+ string_cont(Ics1, L1, C2, yyaction(A, Alen, Tcs, L0, C0), Ts);
+ {A,Alen,Ics1,L1,_C1,_S1} -> % Accepting transition state
+ C2 = adjust_col(Tcs, Alen, C0),
+ string_cont(Ics1, L1, C2, yyaction(A, Alen, Tcs, L0, C0), Ts);
+ {reject,_Alen,Tlen,_Ics1,_L1,_C1,_S1} -> % After a non-accepting state
+ {error,{{L0, C0} ,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},{L0, C0}};
+ {A,Alen,Tlen,_Ics1,L1, C1,_S1}->
Tcs1 = yysuf(Tcs, Alen),
L2 = adjust_line(Tlen, Alen, Tcs1, L1),
- string_cont(Tcs1, L2, yyaction(A, Alen, Tcs, L0), Ts)
+ C2 = adjust_col(Tcs, Alen, C1),
+ string_cont(Tcs1, L2, C2, yyaction(A, Alen, Tcs, L0,C0), Ts)
end.
-%% string_cont(RestChars, Line, Token, Tokens)
+%% string_cont(RestChars, Line, Col, Token, Tokens)
%% Test for and remove the end token wrapper. Push back characters
%% are prepended to RestChars.
--dialyzer({nowarn_function, string_cont/4}).
+-dialyzer({nowarn_function, string_cont/5}).
-string_cont(Rest, Line, {token,T}, Ts) ->
- string(Rest, Line, Rest, [T|Ts]);
-string_cont(Rest, Line, {token,T,Push}, Ts) ->
+string_cont(Rest, Line, Col, {token,T}, Ts) ->
+ do_string(Rest, Line, Col, Rest, [T|Ts]);
+string_cont(Rest, Line, Col, {token,T,Push}, Ts) ->
NewRest = Push ++ Rest,
- string(NewRest, Line, NewRest, [T|Ts]);
-string_cont(Rest, Line, {end_token,T}, Ts) ->
- string(Rest, Line, Rest, [T|Ts]);
-string_cont(Rest, Line, {end_token,T,Push}, Ts) ->
+ do_string(NewRest, Line, Col, NewRest, [T|Ts]);
+string_cont(Rest, Line, Col, {end_token,T}, Ts) ->
+ do_string(Rest, Line, Col, Rest, [T|Ts]);
+string_cont(Rest, Line, Col, {end_token,T,Push}, Ts) ->
NewRest = Push ++ Rest,
- string(NewRest, Line, NewRest, [T|Ts]);
-string_cont(Rest, Line, skip_token, Ts) ->
- string(Rest, Line, Rest, Ts);
-string_cont(Rest, Line, {skip_token,Push}, Ts) ->
+ do_string(NewRest, Line, Col, NewRest, [T|Ts]);
+string_cont(Rest, Line, Col, skip_token, Ts) ->
+ do_string(Rest, Line, Col, Rest, Ts);
+string_cont(Rest, Line, Col, {skip_token,Push}, Ts) ->
NewRest = Push ++ Rest,
- string(NewRest, Line, NewRest, Ts);
-string_cont(_Rest, Line, {error,S}, _Ts) ->
- {error,{Line,?MODULE,{user,S}},Line}.
+ do_string(NewRest, Line, Col, NewRest, Ts);
+string_cont(_Rest, Line, Col, {error,S}, _Ts) ->
+ {error,{{Line, Col},?MODULE,{user,S}},{Line,Col}}.
%% token(Continuation, Chars) ->
-%% token(Continuation, Chars, Line) ->
+%% token(Continuation, Chars, Loc) ->
%% {more,Continuation} | {done,ReturnVal,RestChars}.
%% Must be careful when re-entering to append the latest characters to the
%% after characters in an accept. The continuation is:
-%% {token,State,CurrLine,TokenChars,TokenLen,TokenLine,AccAction,AccLen}
+%% {token,State,CurrLine,CurrCol,TokenChars,TokenLen,TokenLine,TokenCol,AccAction,AccLen}
-token(Cont, Chars) -> token(Cont, Chars, 1).
+##tkn
-token([], Chars, Line) ->
- token(yystate(), Chars, Line, Chars, 0, Line, reject, 0);
-token({token,State,Line,Tcs,Tlen,Tline,Action,Alen}, Chars, _) ->
- token(State, Chars, Line, Tcs ++ Chars, Tlen, Tline, Action, Alen).
+do_token([], Chars, Line, Col) ->
+ token(yystate(), Chars, Line, Col, Chars, 0, Line, Col, reject, 0);
+do_token({token,State,Line,Col,Tcs,Tlen,Tline,Tcol,Action,Alen}, Chars, _, _) ->
+ token(State, Chars, Line, Col, Tcs ++ Chars, Tlen, Tline, Tcol, Action, Alen).
-%% token(State, InChars, Line, TokenChars, TokenLen, TokenLine,
+%% token(State, InChars, Line, Col, TokenChars, TokenLen, TokenLine, TokenCol
%% AcceptAction, AcceptLen) ->
%% {more,Continuation} | {done,ReturnVal,RestChars}.
%% The argument order is chosen to be more efficient.
-token(S0, Ics0, L0, Tcs, Tlen0, Tline, A0, Alen0) ->
- case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of
+token(S0, Ics0, L0, C0, Tcs, Tlen0, Tline, Tcol, A0, Alen0) ->
+ case yystate(S0, Ics0, L0, C0, Tlen0, A0, Alen0) of
%% Accepting end state, we have a token.
- {A1,Alen1,Ics1,L1} ->
- token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline));
+ {A1,Alen1,Ics1,L1,C1} ->
+ C2 = adjust_col(Tcs, Alen1, C1),
+ token_cont(Ics1, L1, C2, yyaction(A1, Alen1, Tcs, Tline,Tcol));
%% Accepting transition state, can take more chars.
- {A1,Alen1,[],L1,S1} -> % Need more chars to check
- {more,{token,S1,L1,Tcs,Alen1,Tline,A1,Alen1}};
- {A1,Alen1,Ics1,L1,_S1} -> % Take what we got
- token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline));
+ {A1,Alen1,[],L1,C1,S1} -> % Need more chars to check
+ {more,{token,S1,L1,C1,Tcs,Alen1,Tline,Tcol,A1,Alen1}};
+ {A1,Alen1,Ics1,L1,C1,_S1} -> % Take what we got
+ C2 = adjust_col(Tcs, Alen1, C1),
+ token_cont(Ics1, L1, C2, yyaction(A1, Alen1, Tcs, Tline,Tcol));
%% After a non-accepting state, maybe reach accept state later.
- {A1,Alen1,Tlen1,[],L1,S1} -> % Need more chars to check
- {more,{token,S1,L1,Tcs,Tlen1,Tline,A1,Alen1}};
- {reject,_Alen1,Tlen1,eof,L1,_S1} -> % No token match
+ {A1,Alen1,Tlen1,[],L1,C1,S1} -> % Need more chars to check
+ {more,{token,S1,L1,C1,Tcs,Tlen1,Tline,Tcol,A1,Alen1}};
+ {reject,_Alen1,Tlen1,eof,L1,C1,_S1} -> % No token match
%% Check for partial token which is error.
- Ret = if Tlen1 > 0 -> {error,{Tline,?MODULE,
+ Ret = if Tlen1 > 0 -> {error,{{Tline,Tcol},?MODULE,
%% Skip eof tail in Tcs.
- {illegal,yypre(Tcs, Tlen1)}},L1};
- true -> {eof,L1}
+ {illegal,yypre(Tcs, Tlen1)}},{L1,C1}};
+ true -> {eof,{L1,C1}}
end,
{done,Ret,eof};
- {reject,_Alen1,Tlen1,Ics1,L1,_S1} -> % No token match
- Error = {Tline,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}},
- {done,{error,Error,L1},Ics1};
- {A1,Alen1,Tlen1,_Ics1,L1,_S1} -> % Use last accept match
+ {reject,_Alen1,Tlen1,Ics1,_L1,_C1,_S1} -> % No token match
+ Error = {{Tline,Tcol},?MODULE,{illegal,yypre(Tcs, Tlen1+1)}},
+ {done,{error,Error,{Tline,Tcol}},Ics1};
+ {A1,Alen1,Tlen1,_Ics1,L1,_C1,_S1} -> % Use last accept match
Tcs1 = yysuf(Tcs, Alen1),
L2 = adjust_line(Tlen1, Alen1, Tcs1, L1),
- token_cont(Tcs1, L2, yyaction(A1, Alen1, Tcs, Tline))
+ C2 = C0 + Alen1,
+ token_cont(Tcs1, L2, C2, yyaction(A1, Alen1, Tcs, Tline, Tcol))
end.
-%% token_cont(RestChars, Line, Token)
+%% token_cont(RestChars, Line, Col, Token)
%% If we have a token or error then return done, else if we have a
%% skip_token then continue.
--dialyzer({nowarn_function, token_cont/3}).
+-dialyzer({nowarn_function, token_cont/4}).
-token_cont(Rest, Line, {token,T}) ->
- {done,{ok,T,Line},Rest};
-token_cont(Rest, Line, {token,T,Push}) ->
+token_cont(Rest, Line, Col, {token,T}) ->
+ {done,{ok,T,{Line,Col}},Rest};
+token_cont(Rest, Line, Col, {token,T,Push}) ->
NewRest = Push ++ Rest,
- {done,{ok,T,Line},NewRest};
-token_cont(Rest, Line, {end_token,T}) ->
- {done,{ok,T,Line},Rest};
-token_cont(Rest, Line, {end_token,T,Push}) ->
+ {done,{ok,T,{Line,Col}},NewRest};
+token_cont(Rest, Line, Col, {end_token,T}) ->
+ {done,{ok,T,{Line,Col}},Rest};
+token_cont(Rest, Line, Col, {end_token,T,Push}) ->
NewRest = Push ++ Rest,
- {done,{ok,T,Line},NewRest};
-token_cont(Rest, Line, skip_token) ->
- token(yystate(), Rest, Line, Rest, 0, Line, reject, 0);
-token_cont(Rest, Line, {skip_token,Push}) ->
+ {done,{ok,T,{Line,Col}},NewRest};
+token_cont(Rest, Line, Col, skip_token) ->
+ token(yystate(), Rest, Line, Col, Rest, 0, Line, Col, reject, 0);
+token_cont(Rest, Line, Col, {skip_token,Push}) ->
NewRest = Push ++ Rest,
- token(yystate(), NewRest, Line, NewRest, 0, Line, reject, 0);
-token_cont(Rest, Line, {error,S}) ->
- {done,{error,{Line,?MODULE,{user,S}},Line},Rest}.
+ token(yystate(), NewRest, Line, Col, NewRest, 0, Line, Col, reject, 0);
+token_cont(Rest, Line, Col, {error,S}) ->
+ {done,{error,{{Line, Col},?MODULE,{user,S}},{Line, Col}},Rest}.
-%% tokens(Continuation, Chars, Line) ->
+%% tokens(Continuation, Chars) ->
+%% tokens(Continuation, Chars, Loc) ->
%% {more,Continuation} | {done,ReturnVal,RestChars}.
%% Must be careful when re-entering to append the latest characters to the
%% after characters in an accept. The continuation is:
-%% {tokens,State,CurrLine,TokenChars,TokenLen,TokenLine,Tokens,AccAction,AccLen}
-%% {skip_tokens,State,CurrLine,TokenChars,TokenLen,TokenLine,Error,AccAction,AccLen}
+%% {tokens,State,CurrLine,CurrCol,TokenChars,TokenLen,TokenLine,TokenCur,Tokens,AccAction,AccLen}
+%% {skip_tokens,State,CurrLine,CurrCol,TokenChars,TokenLen,TokenLine,TokenCur,Error,AccAction,AccLen}
-tokens(Cont, Chars) -> tokens(Cont, Chars, 1).
+##tks
-tokens([], Chars, Line) ->
- tokens(yystate(), Chars, Line, Chars, 0, Line, [], reject, 0);
-tokens({tokens,State,Line,Tcs,Tlen,Tline,Ts,Action,Alen}, Chars, _) ->
- tokens(State, Chars, Line, Tcs ++ Chars, Tlen, Tline, Ts, Action, Alen);
-tokens({skip_tokens,State,Line,Tcs,Tlen,Tline,Error,Action,Alen}, Chars, _) ->
- skip_tokens(State, Chars, Line, Tcs ++ Chars, Tlen, Tline, Error, Action, Alen).
+do_tokens([], Chars, Line, Col) ->
+ tokens(yystate(), Chars, Line, Col, Chars, 0, Line, Col, [], reject, 0);
+do_tokens({tokens,State,Line,Col,Tcs,Tlen,Tline,Tcol,Ts,Action,Alen}, Chars, _,_) ->
+ tokens(State, Chars, Line, Col, Tcs ++ Chars, Tlen, Tline, Tcol, Ts, Action, Alen);
+do_tokens({skip_tokens,State,Line, Col, Tcs,Tlen,Tline,Tcol,Error,Action,Alen}, Chars, _,_) ->
+ skip_tokens(State, Chars, Line, Col, Tcs ++ Chars, Tlen, Tline, Tcol, Error, Action, Alen).
-%% tokens(State, InChars, Line, TokenChars, TokenLen, TokenLine, Tokens,
+%% tokens(State, InChars, Line, Col, TokenChars, TokenLen, TokenLine, TokenCol,Tokens,
%% AcceptAction, AcceptLen) ->
%% {more,Continuation} | {done,ReturnVal,RestChars}.
-tokens(S0, Ics0, L0, Tcs, Tlen0, Tline, Ts, A0, Alen0) ->
- case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of
+tokens(S0, Ics0, L0, C0, Tcs, Tlen0, Tline, Tcol, Ts, A0, Alen0) ->
+ case yystate(S0, Ics0, L0, C0, Tlen0, A0, Alen0) of
%% Accepting end state, we have a token.
- {A1,Alen1,Ics1,L1} ->
- tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Ts);
+ {A1,Alen1,Ics1,L1,C1} ->
+ C2 = adjust_col(Tcs, Alen1, C1),
+ tokens_cont(Ics1, L1, C2, yyaction(A1, Alen1, Tcs, Tline, Tcol), Ts);
%% Accepting transition state, can take more chars.
- {A1,Alen1,[],L1,S1} -> % Need more chars to check
- {more,{tokens,S1,L1,Tcs,Alen1,Tline,Ts,A1,Alen1}};
- {A1,Alen1,Ics1,L1,_S1} -> % Take what we got
- tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Ts);
+ {A1,Alen1,[],L1,C1,S1} -> % Need more chars to check
+ {more,{tokens,S1,L1,C1,Tcs,Alen1,Tline,Tcol,Ts,A1,Alen1}};
+ {A1,Alen1,Ics1,L1,C1,_S1} -> % Take what we got
+ C2 = adjust_col(Tcs, Alen1, C1),
+ tokens_cont(Ics1, L1, C2, yyaction(A1, Alen1, Tcs, Tline,Tcol), Ts);
%% After a non-accepting state, maybe reach accept state later.
- {A1,Alen1,Tlen1,[],L1,S1} -> % Need more chars to check
- {more,{tokens,S1,L1,Tcs,Tlen1,Tline,Ts,A1,Alen1}};
- {reject,_Alen1,Tlen1,eof,L1,_S1} -> % No token match
+ {A1,Alen1,Tlen1,[],L1,C1,S1} -> % Need more chars to check
+ {more,{tokens,S1,L1,C1,Tcs,Tlen1,Tline,Tcol,Ts,A1,Alen1}};
+ {reject,_Alen1,Tlen1,eof,L1,C1,_S1} -> % No token match
%% Check for partial token which is error, no need to skip here.
- Ret = if Tlen1 > 0 -> {error,{Tline,?MODULE,
+ Ret = if Tlen1 > 0 -> {error,{{Tline,Tcol},?MODULE,
%% Skip eof tail in Tcs.
- {illegal,yypre(Tcs, Tlen1)}},L1};
- Ts == [] -> {eof,L1};
- true -> {ok,yyrev(Ts),L1}
+ {illegal,yypre(Tcs, Tlen1)}},{L1,C1}};
+ Ts == [] -> {eof,{L1,C1}};
+ true -> {ok,yyrev(Ts),{L1,C1}}
end,
{done,Ret,eof};
- {reject,_Alen1,Tlen1,_Ics1,L1,_S1} ->
+ {reject,_Alen1,Tlen1,_Ics1,L1,C1,_S1} ->
%% Skip rest of tokens.
- Error = {L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}},
- skip_tokens(yysuf(Tcs, Tlen1+1), L1, Error);
- {A1,Alen1,Tlen1,_Ics1,L1,_S1} ->
- Token = yyaction(A1, Alen1, Tcs, Tline),
+ Error = {{L1,C1},?MODULE,{illegal,yypre(Tcs, Tlen1+1)}},
+ skip_tokens(yysuf(Tcs, Tlen1+1), L1, C1, Error);
+ {A1,Alen1,Tlen1,_Ics1,L1,_C1,_S1} ->
+ Token = yyaction(A1, Alen1, Tcs, Tline,Tcol),
Tcs1 = yysuf(Tcs, Alen1),
L2 = adjust_line(Tlen1, Alen1, Tcs1, L1),
- tokens_cont(Tcs1, L2, Token, Ts)
+ C2 = C0 + Alen1,
+ tokens_cont(Tcs1, L2, C2, Token, Ts)
end.
-%% tokens_cont(RestChars, Line, Token, Tokens)
+%% tokens_cont(RestChars, Line, Column, Token, Tokens)
%% If we have an end_token or error then return done, else if we have
%% a token then save it and continue, else if we have a skip_token
%% just continue.
--dialyzer({nowarn_function, tokens_cont/4}).
+-dialyzer({nowarn_function, tokens_cont/5}).
-tokens_cont(Rest, Line, {token,T}, Ts) ->
- tokens(yystate(), Rest, Line, Rest, 0, Line, [T|Ts], reject, 0);
-tokens_cont(Rest, Line, {token,T,Push}, Ts) ->
+tokens_cont(Rest, Line, Col, {token,T}, Ts) ->
+ tokens(yystate(), Rest, Line, Col, Rest, 0, Line, Col, [T|Ts], reject, 0);
+tokens_cont(Rest, Line, Col, {token,T,Push}, Ts) ->
NewRest = Push ++ Rest,
- tokens(yystate(), NewRest, Line, NewRest, 0, Line, [T|Ts], reject, 0);
-tokens_cont(Rest, Line, {end_token,T}, Ts) ->
- {done,{ok,yyrev(Ts, [T]),Line},Rest};
-tokens_cont(Rest, Line, {end_token,T,Push}, Ts) ->
+ tokens(yystate(), NewRest, Line, Col, NewRest, 0, Line, Col, [T|Ts], reject, 0);
+tokens_cont(Rest, Line, Col, {end_token,T}, Ts) ->
+ {done,{ok,yyrev(Ts, [T]),{Line,Col}},Rest};
+tokens_cont(Rest, Line, Col, {end_token,T,Push}, Ts) ->
NewRest = Push ++ Rest,
- {done,{ok,yyrev(Ts, [T]),Line},NewRest};
-tokens_cont(Rest, Line, skip_token, Ts) ->
- tokens(yystate(), Rest, Line, Rest, 0, Line, Ts, reject, 0);
-tokens_cont(Rest, Line, {skip_token,Push}, Ts) ->
+ {done,{ok,yyrev(Ts, [T]),{Line, Col}},NewRest};
+tokens_cont(Rest, Line, Col, skip_token, Ts) ->
+ tokens(yystate(), Rest, Line, Col, Rest, 0, Line, Col, Ts, reject, 0);
+tokens_cont(Rest, Line, Col, {skip_token,Push}, Ts) ->
NewRest = Push ++ Rest,
- tokens(yystate(), NewRest, Line, NewRest, 0, Line, Ts, reject, 0);
-tokens_cont(Rest, Line, {error,S}, _Ts) ->
- skip_tokens(Rest, Line, {Line,?MODULE,{user,S}}).
+ tokens(yystate(), NewRest, Line, Col, NewRest, 0, Line, Col, Ts, reject, 0);
+tokens_cont(Rest, Line, Col, {error,S}, _Ts) ->
+ skip_tokens(Rest, Line, Col, {{Line,Col},?MODULE,{user,S}}).
-%%skip_tokens(InChars, Line, Error) -> {done,{error,Error,Line},Ics}.
+%% skip_tokens(InChars, Line, Col, Error) -> {done,{error,Error,{Line,Col}},Ics}.
%% Skip tokens until an end token, junk everything and return the error.
-skip_tokens(Ics, Line, Error) ->
- skip_tokens(yystate(), Ics, Line, Ics, 0, Line, Error, reject, 0).
+skip_tokens(Ics, Line, Col, Error) ->
+ skip_tokens(yystate(), Ics, Line, Col, Ics, 0, Line, Col, Error, reject, 0).
-%% skip_tokens(State, InChars, Line, TokenChars, TokenLen, TokenLine, Tokens,
+%% skip_tokens(State, InChars, Line, Col, TokenChars, TokenLen, TokenLine, TokenCol, Tokens,
%% AcceptAction, AcceptLen) ->
%% {more,Continuation} | {done,ReturnVal,RestChars}.
-skip_tokens(S0, Ics0, L0, Tcs, Tlen0, Tline, Error, A0, Alen0) ->
- case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of
- {A1,Alen1,Ics1,L1} -> % Accepting end state
- skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Error);
- {A1,Alen1,[],L1,S1} -> % After an accepting state
- {more,{skip_tokens,S1,L1,Tcs,Alen1,Tline,Error,A1,Alen1}};
- {A1,Alen1,Ics1,L1,_S1} ->
- skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Error);
- {A1,Alen1,Tlen1,[],L1,S1} -> % After a non-accepting state
- {more,{skip_tokens,S1,L1,Tcs,Tlen1,Tline,Error,A1,Alen1}};
- {reject,_Alen1,_Tlen1,eof,L1,_S1} ->
- {done,{error,Error,L1},eof};
- {reject,_Alen1,Tlen1,_Ics1,L1,_S1} ->
- skip_tokens(yysuf(Tcs, Tlen1+1), L1, Error);
- {A1,Alen1,Tlen1,_Ics1,L1,_S1} ->
- Token = yyaction(A1, Alen1, Tcs, Tline),
+skip_tokens(S0, Ics0, L0, C0, Tcs, Tlen0, Tline, Tcol, Error, A0, Alen0) ->
+ case yystate(S0, Ics0, L0, C0, Tlen0, A0, Alen0) of
+ {A1,Alen1,Ics1,L1, C1} -> % Accepting end state
+ skip_cont(Ics1, L1, C1, yyaction(A1, Alen1, Tcs, Tline, Tcol), Error);
+ {A1,Alen1,[],L1,C1, S1} -> % After an accepting state
+ {more,{skip_tokens,S1,L1,C1,Tcs,Alen1,Tline,Tcol,Error,A1,Alen1}};
+ {A1,Alen1,Ics1,L1,C1,_S1} ->
+ skip_cont(Ics1, L1, C1, yyaction(A1, Alen1, Tcs, Tline, Tcol), Error);
+ {A1,Alen1,Tlen1,[],L1,C1,S1} -> % After a non-accepting state
+ {more,{skip_tokens,S1,L1,C1,Tcs,Tlen1,Tline,Tcol,Error,A1,Alen1}};
+ {reject,_Alen1,_Tlen1,eof,L1,C1,_S1} ->
+ {done,{error,Error,{L1,C1}},eof};
+ {reject,_Alen1,Tlen1,_Ics1,L1,C1,_S1} ->
+ skip_tokens(yysuf(Tcs, Tlen1+1), L1, C1,Error);
+ {A1,Alen1,Tlen1,_Ics1,L1,C1,_S1} ->
+ Token = yyaction(A1, Alen1, Tcs, Tline, Tcol),
Tcs1 = yysuf(Tcs, Alen1),
L2 = adjust_line(Tlen1, Alen1, Tcs1, L1),
- skip_cont(Tcs1, L2, Token, Error)
+ skip_cont(Tcs1, L2, C1, Token, Error)
end.
-%% skip_cont(RestChars, Line, Token, Error)
+%% skip_cont(RestChars, Line, Col, Token, Error)
%% Skip tokens until we have an end_token or error then return done
%% with the original rror.
--dialyzer({nowarn_function, skip_cont/4}).
+-dialyzer({nowarn_function, skip_cont/5}).
-skip_cont(Rest, Line, {token,_T}, Error) ->
- skip_tokens(yystate(), Rest, Line, Rest, 0, Line, Error, reject, 0);
-skip_cont(Rest, Line, {token,_T,Push}, Error) ->
+skip_cont(Rest, Line, Col, {token,_T}, Error) ->
+ skip_tokens(yystate(), Rest, Line, Col, Rest, 0, Line, Col, Error, reject, 0);
+skip_cont(Rest, Line, Col, {token,_T,Push}, Error) ->
NewRest = Push ++ Rest,
- skip_tokens(yystate(), NewRest, Line, NewRest, 0, Line, Error, reject, 0);
-skip_cont(Rest, Line, {end_token,_T}, Error) ->
- {done,{error,Error,Line},Rest};
-skip_cont(Rest, Line, {end_token,_T,Push}, Error) ->
+ skip_tokens(yystate(), NewRest, Line, Col, NewRest, 0, Line, Col, Error, reject, 0);
+skip_cont(Rest, Line, Col, {end_token,_T}, Error) ->
+ {done,{error,Error,{Line,Col}},Rest};
+skip_cont(Rest, Line, Col, {end_token,_T,Push}, Error) ->
NewRest = Push ++ Rest,
- {done,{error,Error,Line},NewRest};
-skip_cont(Rest, Line, skip_token, Error) ->
- skip_tokens(yystate(), Rest, Line, Rest, 0, Line, Error, reject, 0);
-skip_cont(Rest, Line, {skip_token,Push}, Error) ->
+ {done,{error,Error,{Line,Col}},NewRest};
+skip_cont(Rest, Line, Col, skip_token, Error) ->
+ skip_tokens(yystate(), Rest, Line, Col, Rest, 0, Line, Col, Error, reject, 0);
+skip_cont(Rest, Line, Col, {skip_token,Push}, Error) ->
NewRest = Push ++ Rest,
- skip_tokens(yystate(), NewRest, Line, NewRest, 0, Line, Error, reject, 0);
-skip_cont(Rest, Line, {error,_S}, Error) ->
- skip_tokens(yystate(), Rest, Line, Rest, 0, Line, Error, reject, 0).
+ skip_tokens(yystate(), NewRest, Line, Col, NewRest, 0, Line, Col, Error, reject, 0);
+skip_cont(Rest, Line, Col, {error,_S}, Error) ->
+ skip_tokens(yystate(), Rest, Line, Col, Rest, 0, Line, Col, Error, reject, 0).
-compile({nowarn_unused_function, [yyrev/1, yyrev/2, yypre/2, yysuf/2]}).
@@ -292,21 +300,44 @@ adjust_line(T, A, [$\n|Cs], L) ->
adjust_line(T, A, [_|Cs], L) ->
adjust_line(T-1, A, Cs, L).
+%% adjust_col(Chars, AcceptLength, Col) -> NewCol
+%% Handle newlines, tabs and unicode chars.
+adjust_col(_, 0, Col) ->
+ Col;
+adjust_col([$\n | R], L, _) ->
+ adjust_col(R, L-1, 1);
+adjust_col([$\t | R], L, Col) ->
+ adjust_col(R, L-1, tab_forward(Col)+1);
+adjust_col([C | R], L, Col) when C>=0 andalso C=< 16#7F ->
+ adjust_col(R, L-1, Col+1);
+adjust_col([C | R], L, Col) when C>= 16#80 andalso C=< 16#7FF ->
+ adjust_col(R, L-1, Col+2);
+adjust_col([C | R], L, Col) when C>= 16#800 andalso C=< 16#FFFF ->
+ adjust_col(R, L-1, Col+3);
+adjust_col([C | R], L, Col) when C>= 16#10000 andalso C=< 16#10FFFF ->
+ adjust_col(R, L-1, Col+4).
+
+tab_forward(C) ->
+ D = C rem tab_size(),
+ A = tab_size()-D,
+ C+A.
+
+##tab_size
+
%% yystate() -> InitialState.
-%% yystate(State, InChars, Line, CurrTokLen, AcceptAction, AcceptLen) ->
-%% {Action, AcceptLen, RestChars, Line} |
-%% {Action, AcceptLen, RestChars, Line, State} |
-%% {reject, AcceptLen, CurrTokLen, RestChars, Line, State} |
-%% {Action, AcceptLen, CurrTokLen, RestChars, Line, State}.
+%% yystate(State, InChars, Line, Col, CurrTokLen, AcceptAction, AcceptLen) ->
+%% {Action, AcceptLen, RestChars, Line, Col} |
+%% {Action, AcceptLen, RestChars, Line, Col, State} |
+%% {reject, AcceptLen, CurrTokLen, RestChars, Line, Col, State} |
+%% {Action, AcceptLen, CurrTokLen, RestChars, Line, Col, State}.
%% Generated state transition functions. The non-accepting end state
%% return signal either an unrecognised character or end of current
%% input.
##dfa
-%% yyaction(Action, TokenLength, TokenChars, TokenLine) ->
+%% yyaction(Action, TokenLength, TokenChars, TokenLine, TokenCol) ->
%% {token,Token} | {end_token, Token} | skip_token | {error,String}.
%% Generated action function.
##actions
-
diff --git a/lib/parsetools/src/leex.erl b/lib/parsetools/src/leex.erl
index b764678516..c5e61b8149 100644
--- a/lib/parsetools/src/leex.erl
+++ b/lib/parsetools/src/leex.erl
@@ -119,6 +119,8 @@ file(File) -> file(File, []).
| {'verbose', boolean()}
| {'warnings_as_errors', boolean()}
| {'deterministic', boolean()}
+ | {'error_location', line | column}
+ | {'tab_size', pos_integer()}
| 'dfa_graph'
| 'report_errors' | 'report_warnings' | 'report'
| 'return_errors' | 'return_warnings' | 'return'
@@ -281,6 +283,12 @@ check_options([{Option, Boolean} | Options], AllOptions, L)
false ->
badarg
end;
+check_options([{error_location, Loc}=O | Options], AllOptions, L)
+ when Loc =:= line; Loc =:= column ->
+ check_options(Options, AllOptions, [O | L]);
+check_options([{tab_size, S}=O | Options], AllOptions, L)
+ when is_integer(S) andalso S>0 ->
+ check_options(Options, AllOptions, [O | L]);
check_options([], _AllOptions, L) ->
L;
check_options(_Options, _, _L) ->
@@ -289,7 +297,7 @@ check_options(_Options, _, _L) ->
all_options() ->
[dfa_graph,includefile,report_errors,report_warnings,
return_errors,return_warnings,scannerfile,verbose,
- warnings_as_errors, deterministic].
+ warnings_as_errors,deterministic,error_location,tab_size].
default_option(dfa_graph) -> false;
default_option(includefile) -> [];
@@ -300,7 +308,9 @@ default_option(return_warnings) -> false;
default_option(scannerfile) -> [];
default_option(verbose) -> false;
default_option(warnings_as_errors) -> false;
-default_option(deterministic) -> false.
+default_option(deterministic) -> false;
+default_option(error_location) -> line;
+default_option(tab_size) -> 8.
atom_option(dfa_graph) -> {dfa_graph,true};
atom_option(report_errors) -> {report_errors,true};
@@ -596,7 +606,9 @@ parse_rule(S, Line, Atoks, Ms, N, St) ->
TokenChars = var_used('TokenChars', Atoks),
TokenLen = var_used('TokenLen', Atoks),
TokenLine = var_used('TokenLine', Atoks),
- {ok,{R,N},{N,Atoks,TokenChars,TokenLen,TokenLine},St};
+ TokenCol = var_used('TokenCol', Atoks),
+ TokenLoc = var_used('TokenLoc', Atoks),
+ {ok,{R,N},{N,Atoks,TokenChars,TokenLen,TokenLine,TokenCol,TokenLoc},St};
{error,E} ->
add_error({Line,leex,E}, St)
end.
@@ -1415,6 +1427,10 @@ out_file(Ifile, Ofile, St, DFA, DF, Actions, Code, L) ->
case string:slice(Line, 0, 5) of
"##mod" -> out_module(Ofile, St);
"##cod" -> out_erlang_code(Ofile, St, Code, L);
+ "##str" -> out_string(Ofile, St#leex.opts);
+ "##tkn" -> out_token(Ofile, St#leex.opts);
+ "##tks" -> out_tokens(Ofile, St#leex.opts);
+ "##tab" -> out_tab_size(Ofile, St#leex.opts);
"##dfa" -> out_dfa(Ofile, St, DFA, Code, DF, L);
"##act" -> out_actions(Ofile, St#leex.xfile, Deterministic, Actions);
_ -> io:put_chars(Ofile, Line)
@@ -1440,6 +1456,92 @@ out_erlang_code(File, St, Code, L) ->
io:nl(File),
output_file_directive(File, St#leex.ifile, Deterministic, L).
+out_tab_size(File, Opts) ->
+ Size = proplists:get_value(tab_size, Opts),
+ io:fwrite(File, "tab_size() -> ~p.\n", [Size]).
+
+%% Exclude column number if needed
+out_string(File, Opts) ->
+ out_string_1(File, Opts),
+ out_string_2(File, Opts),
+ Vars = lists:join(", ",["Ics","L0","C0","Tcs","Ts"]),
+ out_head(File,string,Vars),
+ EL = proplists:get_value(error_location, Opts),
+ case EL of
+ column ->
+ io:fwrite(File," do_string(~s).\n",[Vars]);
+ line ->
+ io:fwrite(File," case do_string(~s) of\n",[Vars]),
+ io:fwrite(File," {ok, T, {L,_}} -> {ok, T, L};\n",[]),
+ io:fwrite(File," {error, {{EL,_},M,D}, {L,_}} ->\n",[]),
+ io:fwrite(File," EI = {EL,M,D},\n",[]),
+ io:fwrite(File," {error, EI, L}\n",[]),
+ io:fwrite(File," end.\n",[])
+ end.
+
+out_string_1(File, Opts) ->
+ out_head(File,string,"Ics"),
+ EL = proplists:get_value(error_location, Opts),
+ DefLoc = case EL of
+ column -> "{1,1}";
+ line -> "1"
+ end,
+ io:fwrite(File," string(~s).\n",["Ics,"++DefLoc]).
+
+out_string_2(File, Opts) ->
+ EL = proplists:get_value(error_location, Opts),
+ case EL of
+ column ->
+ out_head(File,string,"Ics,{L0,C0}"),
+ CallVars = lists:join(", ", ["Ics","L0","C0","Ics","[]"]),
+ io:fwrite(File," string(~s).\n",[CallVars]);
+ line ->
+ out_head(File,string,"Ics,L0"),
+ CallVars = lists:join(", ", ["Ics","L0","1","Ics","[]"]),
+ io:fwrite(File," string(~s).\n",[CallVars])
+ end.
+
+out_token(File, Opts) ->
+ out_tokens_wrapper(File, Opts, token).
+
+out_tokens(File, Opts) ->
+ out_tokens_wrapper(File, Opts, tokens).
+
+out_tokens_wrapper(File, Opts, Fun) ->
+ out_token_2(File, Opts, Fun),
+ EL = proplists:get_value(error_location, Opts),
+ case EL of
+ column ->
+ VarsCol = lists:join(", ",["Cont","Chars","{Line,Col}"]),
+ out_head(File, Fun, VarsCol),
+ io:fwrite(File," do_~s(~s).\n",[Fun,"Cont,Chars,Line,Col"]);
+ line ->
+ VarsCol = lists:join(", ",["Cont","Chars","Line"]),
+ out_head(File, Fun, VarsCol),
+ io:fwrite(File," case do_~s(~s) of\n",[Fun,"Cont,Chars,Line,1"]),
+ io:fwrite(File," {more, _} = C -> C;\n",[]),
+ io:fwrite(File," {done, Ret0, R} ->\n",[]),
+ io:fwrite(File," Ret1 = case Ret0 of\n",[]),
+ io:fwrite(File," {ok, T, {L,_}} -> {ok, T, L};\n",[]),
+ io:fwrite(File," {eof, {L,_}} -> {eof, L};\n",[]),
+ io:fwrite(File," {error, {{EL,_},M,D},{L,_}} -> {error, {EL,M,D},L}\n",[]),
+ io:fwrite(File," end,\n",[]),
+ io:fwrite(File," {done, Ret1, R}\n",[]),
+ io:fwrite(File," end.\n",[])
+ end.
+
+out_token_2(File, Opts, Fun) ->
+ out_head(File, Fun, "Cont,Chars"),
+ EL = proplists:get_value(error_location, Opts),
+ DefLoc = case EL of
+ column -> "{1,1}";
+ line -> "1"
+ end,
+ io:fwrite(File," ~s(~s).\n",[Fun,"Cont,Chars,"++DefLoc]).
+
+out_head(File, Fun, Vars) ->
+ io:fwrite(File, "~s(~s) -> \n",[Fun,Vars]).
+
file_copy(From, To) ->
case io:get_line(From, leex) of
eof -> ok;
@@ -1455,36 +1557,36 @@ out_dfa(File, St, DFA, Code, DF, L) ->
output_file_directive(File, St#leex.efile, Deterministic, L+(NCodeLines-1)+3),
io:fwrite(File, "yystate() -> ~w.~n~n", [DF]),
foreach(fun (S) -> out_trans(File, S) end, DFA),
- io:fwrite(File, "yystate(S, Ics, Line, Tlen, Action, Alen) ->~n", []),
- io:fwrite(File, " {Action,Alen,Tlen,Ics,Line,S}.~n", []).
+ io:fwrite(File, "yystate(S, Ics, Line, Col, Tlen, Action, Alen) ->~n", []),
+ io:fwrite(File, " {Action,Alen,Tlen,Ics,Line,Col,S}.~n", []).
out_trans(File, #dfa_state{no=N,trans=[],accept={accept,A}}) ->
%% Accepting end state, guaranteed done.
- io:fwrite(File, "yystate(~w, Ics, Line, Tlen, _, _) ->~n", [N]),
- io:fwrite(File, " {~w,Tlen,Ics,Line};~n", [A]);
+ io:fwrite(File, "yystate(~w, Ics, Line, Col, Tlen, _, _) ->~n", [N]),
+ io:fwrite(File, " {~w,Tlen,Ics,Line,Col};~n", [A]);
out_trans(File, #dfa_state{no=N,trans=Tr,accept={accept,A}}) ->
%% Accepting state, but there maybe more.
foreach(fun (T) -> out_accept_tran(File, N, A, T) end, pack_trans(Tr)),
- io:fwrite(File, "yystate(~w, Ics, Line, Tlen, _, _) ->~n", [N]),
- io:fwrite(File, " {~w,Tlen,Ics,Line,~w};~n", [A,N]);
+ io:fwrite(File, "yystate(~w, Ics, Line, Col, Tlen, _, _) ->~n", [N]),
+ io:fwrite(File, " {~w,Tlen,Ics,Line,Col,~w};~n", [A,N]);
out_trans(File, #dfa_state{no=N,trans=Tr,accept=noaccept}) ->
%% Non-accepting transition state.
foreach(fun (T) -> out_noaccept_tran(File, N, T) end, pack_trans(Tr)),
- io:fwrite(File, "yystate(~w, Ics, Line, Tlen, Action, Alen) ->~n", [N]),
- io:fwrite(File, " {Action,Alen,Tlen,Ics,Line,~w};~n", [N]).
+ io:fwrite(File, "yystate(~w, Ics, Line, Col, Tlen, Action, Alen) ->~n", [N]),
+ io:fwrite(File, " {Action,Alen,Tlen,Ics,Line,Col,~w};~n", [N]).
out_accept_tran(File, N, A, {{Cf,maxchar},S}) ->
out_accept_head_max(File, N, Cf),
- out_accept_body(File, S, "Line", A);
+ out_accept_body(File, S, "Line", "Col", A);
out_accept_tran(File, N, A, {{Cf,Cl},S}) ->
out_accept_head_range(File, N, Cf, Cl),
- out_accept_body(File, S, "Line", A);
+ out_accept_body(File, S, "Line", "Col", A);
out_accept_tran(File, N, A, {$\n,S}) ->
out_accept_head_1(File, N, $\n),
- out_accept_body(File, S, "Line+1", A);
+ out_accept_body(File, S, "Line+1", "1", A);
out_accept_tran(File, N, A, {C,S}) ->
out_accept_head_1(File, N, C),
- out_accept_body(File, S, "Line", A).
+ out_accept_body(File, S, "Line", "Col", A).
out_accept_head_1(File, State, Char) ->
out_head_1(File, State, Char, "_", "_").
@@ -1495,21 +1597,21 @@ out_accept_head_max(File, State, Min) ->
out_accept_head_range(File, State, Min, Max) ->
out_head_range(File, State, Min, Max, "_", "_").
-out_accept_body(File, Next, Line, Action) ->
- out_body(File, Next, Line, io_lib:write(Action), "Tlen").
+out_accept_body(File, Next, Line, Col, Action) ->
+ out_body(File, Next, Line, Col, io_lib:write(Action), "Tlen").
out_noaccept_tran(File, N, {{Cf,maxchar},S}) ->
out_noaccept_head_max(File, N, Cf),
- out_noaccept_body(File, S, "Line");
+ out_noaccept_body(File, S, "Line", "Col");
out_noaccept_tran(File, N, {{Cf,Cl},S}) ->
out_noaccept_head_range(File, N, Cf, Cl),
- out_noaccept_body(File, S, "Line");
+ out_noaccept_body(File, S, "Line", "Col");
out_noaccept_tran(File, N, {$\n,S}) ->
out_noaccept_head_1(File, N, $\n),
- out_noaccept_body(File, S, "Line+1");
+ out_noaccept_body(File, S, "Line+1", "1");
out_noaccept_tran(File, N, {C,S}) ->
out_noaccept_head_1(File, N, C),
- out_noaccept_body(File, S, "Line").
+ out_noaccept_body(File, S, "Line", "Col").
out_noaccept_head_1(File, State, Char) ->
out_head_1(File, State, Char, "Action", "Alen").
@@ -1520,24 +1622,27 @@ out_noaccept_head_max(File, State, Min) ->
out_noaccept_head_range(File, State, Min, Max) ->
out_head_range(File, State, Min, Max, "Action", "Alen").
-out_noaccept_body(File, Next, Line) ->
- out_body(File, Next, Line, "Action", "Alen").
+out_noaccept_body(File, Next, Line, Col) ->
+ out_body(File, Next, Line, Col, "Action", "Alen").
+out_head_1(File, State, Char = $\n, Action, Alen) ->
+ io:fwrite(File, "yystate(~w, [~w|Ics], Line, _, Tlen, ~s, ~s) ->\n",
+ [State,Char,Action,Alen]);
out_head_1(File, State, Char, Action, Alen) ->
- io:fwrite(File, "yystate(~w, [~w|Ics], Line, Tlen, ~s, ~s) ->\n",
+ io:fwrite(File, "yystate(~w, [~w|Ics], Line, Col, Tlen, ~s, ~s) ->\n",
[State,Char,Action,Alen]).
out_head_max(File, State, Min, Action, Alen) ->
- io:fwrite(File, "yystate(~w, [C|Ics], Line, Tlen, ~s, ~s) when C >= ~w ->\n",
+ io:fwrite(File, "yystate(~w, [C|Ics], Line, Col, Tlen, ~s, ~s) when C >= ~w ->\n",
[State,Action,Alen,Min]).
out_head_range(File, State, Min, Max, Action, Alen) ->
- io:fwrite(File, "yystate(~w, [C|Ics], Line, Tlen, ~s, ~s) when C >= ~w, C =< ~w ->\n",
+ io:fwrite(File, "yystate(~w, [C|Ics], Line, Col, Tlen, ~s, ~s) when C >= ~w, C =< ~w ->\n",
[State,Action,Alen,Min,Max]).
-out_body(File, Next, Line, Action, Alen) ->
- io:fwrite(File, " yystate(~w, Ics, ~s, Tlen+1, ~s, ~s);\n",
- [Next,Line,Action,Alen]).
+out_body(File, Next, Line, Col, Action, Alen) ->
+ io:fwrite(File, " yystate(~w, Ics, ~s, ~s, Tlen+1, ~s, ~s);\n",
+ [Next,Line,Col,Action,Alen]).
%% pack_trans([{Crange,State}]) -> [{Crange,State}] when
%% Crange = {Char,Char} | Char.
@@ -1581,31 +1686,32 @@ pack_trans([], Pt) -> Pt.
out_actions(File, XrlFile, Deterministic, As) ->
As1 = prep_out_actions(As),
foreach(fun (A) -> out_action(File, A) end, As1),
- io:fwrite(File, "yyaction(_, _, _, _) -> error.~n", []),
+ io:fwrite(File, "yyaction(_, _, _, _, _) -> error.~n", []),
foreach(fun (A) -> out_action_code(File, XrlFile, Deterministic, A) end, As1).
prep_out_actions(As) ->
map(fun ({A,empty_action}) ->
{A,empty_action};
- ({A,Code,TokenChars,TokenLen,TokenLine}) ->
+ ({A,Code,TokenChars,TokenLen,TokenLine,TokenCol,TokenLoc}) ->
Vs = [{TokenChars,"TokenChars"},
{TokenLen,"TokenLen"},
- {TokenLine,"TokenLine"},
+ {TokenLine or TokenLoc,"TokenLine"},
+ {TokenCol or TokenLoc,"TokenCol"},
{TokenChars,"YYtcs"},
{TokenLen or TokenChars,"TokenLen"}],
Vars = [if F -> S; true -> "_" end || {F,S} <- Vs],
Name = list_to_atom(lists:concat([yyaction_,A])),
- [Chars,Len,Line,_,_] = Vars,
- Args = [V || V <- [Chars,Len,Line], V =/= "_"],
+ [Chars,Len,Line,Col,_,_] = Vars,
+ Args = [V || V <- [Chars,Len,Line,Col], V =/= "_"],
ArgsChars = lists:join(", ", Args),
- {A,Code,Vars,Name,Args,ArgsChars}
+ {A,Code,Vars,Name,Args,ArgsChars, TokenLoc}
end, As).
out_action(File, {A,empty_action}) ->
- io:fwrite(File, "yyaction(~w, _, _, _) -> skip_token;~n", [A]);
-out_action(File, {A,_Code,Vars,Name,_Args,ArgsChars}) ->
- [_,_,Line,Tcs,Len] = Vars,
- io:fwrite(File, "yyaction(~w, ~s, ~s, ~s) ->~n", [A,Len,Tcs,Line]),
+ io:fwrite(File, "yyaction(~w, _, _, _, _) -> skip_token;~n", [A]);
+out_action(File, {A,_Code,Vars,Name,_Args,ArgsChars,_TokenLoc}) ->
+ [_,_,Line,Col,Tcs,Len] = Vars,
+ io:fwrite(File, "yyaction(~w, ~s, ~s, ~s, ~s) ->~n", [A,Len,Tcs,Line,Col]),
if
Tcs =/= "_" ->
io:fwrite(File, " TokenChars = yypre(YYtcs, TokenLen),~n", []);
@@ -1615,13 +1721,17 @@ out_action(File, {A,_Code,Vars,Name,_Args,ArgsChars}) ->
out_action_code(_File, _XrlFile, _Deterministic, {_A,empty_action}) ->
ok;
-out_action_code(File, XrlFile, Deterministic, {_A,Code,_Vars,Name,Args,ArgsChars}) ->
+out_action_code(File, XrlFile, Deterministic, {_A,Code,_Vars,Name,Args,ArgsChars, TokenLoc}) ->
%% Should set the file to the .erl file, but instead assumes that
%% ?LEEXINC is syntactically correct.
io:fwrite(File, "\n-compile({inline,~w/~w}).\n", [Name, length(Args)]),
L = erl_scan:line(hd(Code)),
output_file_directive(File, XrlFile, Deterministic, L-2),
io:fwrite(File, "~s(~s) ->~n", [Name, ArgsChars]),
+ if
+ TokenLoc -> io:fwrite(File," TokenLoc={TokenLine,TokenCol},~n",[]);
+ true -> ok
+ end,
io:fwrite(File, " ~ts\n", [pp_tokens(Code, L, File)]).
%% pp_tokens(Tokens, Line, File) -> [char()].
diff --git a/lib/parsetools/test/leex_SUITE.erl b/lib/parsetools/test/leex_SUITE.erl
index ae7a907a60..28142222f7 100644
--- a/lib/parsetools/test/leex_SUITE.erl
+++ b/lib/parsetools/test/leex_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@
pt/1, man/1, ex/1, ex2/1, not_yet/1,
line_wrap/1,
otp_10302/1, otp_11286/1, unicode/1, otp_13916/1, otp_14285/1,
- otp_17023/1, compiler_warnings/1]).
+ otp_17023/1, compiler_warnings/1, column_support/1]).
% Default timetrap timeout (set in init_per_testcase).
-define(default_timeout, test_server:minutes(1)).
@@ -66,7 +66,7 @@ all() ->
groups() ->
[{checks, [], [file, compile, syntax, deterministic]},
- {examples, [], [pt, man, ex, ex2, not_yet, unicode]},
+ {examples, [], [pt, man, ex, ex2, not_yet, unicode, column_support]},
{tickets, [], [otp_10302, otp_11286, otp_13916, otp_14285, otp_17023,
compiler_warnings]},
{bugs, [], [line_wrap]}].
@@ -118,6 +118,17 @@ file(Config) when is_list(Config) ->
{'EXIT', {badarg, _}} =
(catch leex:file(Filename, includefile)),
+ {'EXIT', {badarg, _}} =
+ (catch leex:file(Filename, {tab_size,0})),
+ {'EXIT', {badarg, _}} =
+ (catch leex:file(Filename, {tab_size,"4"})),
+ {'EXIT', {badarg, _}} =
+ (catch leex:file(Filename, {tab_size,3.5})),
+ {'EXIT', {badarg, _}} =
+ (catch leex:file(Filename, {error_location,{line,column}})),
+ {'EXIT', {badarg, _}} =
+ (catch leex:file(Filename, {error_location,col})),
+
Mini = <<"Definitions.\n"
"D = [0-9]\n"
"Rules.\n"
@@ -417,17 +428,17 @@ pt(Config) when is_list(Config) ->
"L = [a-z]\n"
"Rules.\n"
- "{L}+ : {token,{word,TokenLine,TokenChars}}.\n"
+ "{L}+ : {token,{word,TokenLoc,TokenChars}}.\n"
"abc{D}+ : {skip_token,\"sture\" ++ string:substr(TokenChars, 4)}.\n"
- "{D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}.\n"
+ "{D}+ : {token,{integer,TokenLoc,list_to_integer(TokenChars)}}.\n"
"\\s : .\n"
"\\r\\n : {end_token,{crlf,TokenLine}}.\n"
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->
- {ok,[{word,1,\"sture\"},{integer,1,123}],1} =
- string(\"abc123\"), ok. ">>,
+ {ok,[{word,{1,7},\"sture\"},{integer,{1,12},123}],{1,15}} =
+ string(\"abc123\"), ok. ">>,
default,
ok}],
@@ -442,10 +453,10 @@ unicode(Config) when is_list(Config) ->
"Definitions.\n"
"RTLarrow = (â†Â)\n"
"Rules.\n"
- "{RTLarrow} : {token,{\"â†Â\",TokenLine}}.\n"
+ "{RTLarrow} : {token,{\"â†Â\",TokenLoc}}.\n"
"Erlang code.\n"
"-export([t/0]).\n"
- "t() -> {ok, [{\"â†Â\", 1}], 1} = string(\"â†Â\"), ok.">>,
+ "t() -> {ok, [{\"â†Â\", {1,1}}], {1,4}} = string(\"â†Â\"), ok.">>,
default,
ok}],
@@ -460,34 +471,33 @@ man(Config) when is_list(Config) ->
<<"Definitions.\n"
"Rules.\n"
"[a-z][0-9a-zA-Z_]* :\n"
- " {token,{atom,TokenLine,list_to_atom(TokenChars)}}.\n"
+ " {token,{atom,TokenLoc,list_to_atom(TokenChars)}}.\n"
"[A-Z_][0-9a-zA-Z_]* :\n"
- " {token,{var,TokenLine,list_to_atom(TokenChars)}}.\n"
+ " {token,{var,TokenLoc,list_to_atom(TokenChars)}}.\n"
"(\\+|-)?[0-9]+\\.[0-9]+((E|e)(\\+|-)?[0-9]+)? : \n"
- " {token,{float,TokenLine,list_to_float(TokenChars)}}.\n"
+ " {token,{float,TokenLoc,list_to_float(TokenChars)}}.\n"
"\\s : skip_token.\n"
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->\n"
- " {ok,[{float,1,3.14},{atom,1,atom},{var,1,'V314'}],1} =\n"
+ " {ok,[{float,{1,1},3.14},{atom,{1,5},atom},{var,{1,10},'V314'}],{1,14}} =\n"
" string(\"3.14atom V314\"),\n"
" ok.\n">>,
default,
ok},
-
- {man_2,
+ {man_2,
<<"Definitions.\n"
"D = [0-9]\n"
"Rules.\n"
"{D}+ :\n"
- " {token,{integer,TokenLine,list_to_integer(TokenChars)}}.\n"
+ " {token,{integer,TokenLoc,list_to_integer(TokenChars)}}.\n"
"{D}+\\.{D}+((E|e)(\\+|\\-)?{D}+)? :\n"
- " {token,{float,TokenLine,list_to_float(TokenChars)}}.\n"
+ " {token,{float,TokenLoc,list_to_float(TokenChars)}}.\n"
"\\s : skip_token.\n"
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->\n"
- " {ok,[{float,1,3.14},{integer,1,314}],1} = \n"
+ " {ok,[{float,{1,1},3.14},{integer,{1,6},314}],{1,9}} = \n"
" string(\"3.14 314\"),\n"
" ok.\n">>,
default,
@@ -505,13 +515,13 @@ ex(Config) when is_list(Config) ->
"D = [0-543-705-982]\n"
"Rules.\n"
"{D}+ :\n"
- " {token,{integer,TokenLine,list_to_integer(TokenChars)}}.\n"
+ " {token,{integer,TokenLoc,list_to_integer(TokenChars)}}.\n"
"[^235]+ :\n"
- " {token,{list_to_atom(TokenChars),TokenLine}}.\n"
+ " {token,{list_to_atom(TokenChars),TokenLoc}}.\n"
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->\n"
- " {ok,[{integer,1,12},{' c\\na',1},{integer,2,34},{b789a,2}],2} =\n"
+ " {ok,[{integer,{1,1},12},{' c\\na',{1,3}},{integer,{2,2},34},{b789a,{2,4}}],{2,9}} =\n"
" string(\"12 c\\na34b789a\"),\n"
" ok.\n">>,
default,
@@ -528,7 +538,7 @@ ex(Config) when is_list(Config) ->
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->\n"
- " {ok,[chars,zyx],1} = string(\"abcdef zyx123\"),\n"
+ " {ok,[chars,zyx],{1,14}} = string(\"abcdef zyx123\"),\n"
" ok.\n">>,
default,
ok},
@@ -541,7 +551,7 @@ ex(Config) when is_list(Config) ->
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->\n"
- " {ok,[],1} = string(\"\"), ok.\n">>, % string("a") would loop...
+ " {ok,[],{1,1}} = string(\"\"), ok.\n">>, % string("a") would loop...
default,
ok},
@@ -574,12 +584,12 @@ ex(Config) when is_list(Config) ->
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->\n"
- " {ok,[{white,\"\\b\\f\"}],1} = string(\"\\b\\f\"),\n"
- " {ok,[{form,\"ff\\f\"}],1} = string(\"ff\\f\"),\n"
- " {ok,[{string,\"\\\"foo\\\"\"}],1} = string(\"\\\"foo\\\"\"),\n"
- " {ok,[{char,\"$.\"}],1} = string(\"$\\.\"),\n"
- " {ok,[{list,\"[a,b,c]\"}],1} = string(\"[a,b,c]\"),\n"
- " {ok,[{other,\"$^\\\\\"}],1} = string(\"$^\\\\\"),\n"
+ " {ok,[{white,\"\\b\\f\"}],{1,3}} = string(\"\\b\\f\"),\n"
+ " {ok,[{form,\"ff\\f\"}],{1,4}} = string(\"ff\\f\"),\n"
+ " {ok,[{string,\"\\\"foo\\\"\"}],{1,6}} = string(\"\\\"foo\\\"\"),\n"
+ " {ok,[{char,\"$.\"}],{1,3}} = string(\"$\\.\"),\n"
+ " {ok,[{list,\"[a,b,c]\"}],{1,8}} = string(\"[a,b,c]\"),\n"
+ " {ok,[{other,\"$^\\\\\"}],{1,4}} = string(\"$^\\\\\"),\n"
" ok.\n">>,
default,
ok},
@@ -607,7 +617,7 @@ ex(Config) when is_list(Config) ->
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->\n"
- " {ok,[{hex,[17,171,48,172]}],1} =\n"
+ " {ok,[{hex,[17,171,48,172]}],{1,7}} =\n"
" string(\"\\x{11}\\xab0\\xac\"),\n"
" ok.\n">>,
default,
@@ -637,47 +647,47 @@ WS = ([\\000-\\s]|%.*)
Rules.
{D}+\\.{D}+((E|e)(\\+|\\-)?{D}+)? :
- {token,{float,TokenLine,list_to_float(TokenChars)}}.
-{D}+#{H}+ : base(TokenLine, TokenChars).
-{D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}.
+ {token,{float,TokenLoc,list_to_float(TokenChars)}}.
+{D}+#{H}+ : base(TokenLoc, TokenChars).
+{D}+ : {token,{integer,TokenLoc,list_to_integer(TokenChars)}}.
{L}{A}* : Atom = list_to_atom(TokenChars),
{token,case reserved_word(Atom) of
- true -> {Atom,TokenLine};
- false -> {atom,TokenLine,Atom}
+ true -> {Atom,TokenLoc};
+ false -> {atom,TokenLoc,Atom}
end}.
'(\\\\\\^.|\\\\.|[^'])*' :
%% Strip quotes.
S = lists:sublist(TokenChars, 2, TokenLen - 2),
case catch list_to_atom(string_gen(S)) of
{'EXIT',_} -> {error,\"illegal atom \" ++ TokenChars};
- Atom -> {token,{atom,TokenLine,Atom}}
+ Atom -> {token,{atom,TokenLoc,Atom}}
end.
-({U}|_){A}* : {token,{var,TokenLine,list_to_atom(TokenChars)}}.
+({U}|_){A}* : {token,{var,TokenLoc,list_to_atom(TokenChars)}}.
\"(\\\\\\^.|\\\\.|[^\"])*\" :
%% Strip quotes.
S = lists:sublist(TokenChars, 2, TokenLen - 2),
- {token,{string,TokenLine,string_gen(S)}}.
+ {token,{string,TokenLoc,string_gen(S)}}.
\\$(\\\\{O}{O}{O}|\\\\\\^.|\\\\.|.) :
- {token,{char,TokenLine,cc_convert(TokenChars)}}.
--> : {token,{'->',TokenLine}}.
-:- : {token,{':-',TokenLine}}.
-\\|\\| : {token,{'||',TokenLine}}.
-<- : {token,{'<-',TokenLine}}.
-\\+\\+ : {token,{'++',TokenLine}}.
--- : {token,{'--',TokenLine}}.
-=/= : {token,{'=/=',TokenLine}}.
-== : {token,{'==',TokenLine}}.
-=:= : {token,{'=:=',TokenLine}}.
-/= : {token,{'/=',TokenLine}}.
->= : {token,{'>=',TokenLine}}.
-=< : {token,{'=<',TokenLine}}.
-<= : {token,{'<=',TokenLine}}.
-<< : {token,{'<<',TokenLine}}.
->> : {token,{'>>',TokenLine}}.
-:: : {token,{'::',TokenLine}}.
+ {token,{char,TokenLoc,cc_convert(TokenChars)}}.
+-> : {token,{'->',TokenLoc}}.
+:- : {token,{':-',TokenLoc}}.
+\\|\\| : {token,{'||',TokenLoc}}.
+<- : {token,{'<-',TokenLoc}}.
+\\+\\+ : {token,{'++',TokenLoc}}.
+-- : {token,{'--',TokenLoc}}.
+=/= : {token,{'=/=',TokenLoc}}.
+== : {token,{'==',TokenLoc}}.
+=:= : {token,{'=:=',TokenLoc}}.
+/= : {token,{'/=',TokenLoc}}.
+>= : {token,{'>=',TokenLoc}}.
+=< : {token,{'=<',TokenLoc}}.
+<= : {token,{'<=',TokenLoc}}.
+<< : {token,{'<<',TokenLoc}}.
+>> : {token,{'>>',TokenLoc}}.
+:: : {token,{'::',TokenLoc}}.
[]()[}{|!?/;:,.*+#<>=-] :
- {token,{list_to_atom(TokenChars),TokenLine}}.
-\\.{WS} : {end_token,{dot,TokenLine}}.
+ {token,{list_to_atom(TokenChars),TokenLoc}}.
+\\.{WS} : {end_token,{dot,TokenLoc}}.
{WS}+ : skip_token.
Erlang code.
@@ -775,7 +785,7 @@ escape_char(C) -> C.
XrlFile = filename:join(Dir, "erlang_scan.xrl"),
ok = file:write_file(XrlFile, Xrl),
ErlFile = filename:join(Dir, "erlang_scan.erl"),
- {ok, _} = leex:file(XrlFile, []),
+ {ok, _} = leex:file(XrlFile, [{error_location, column}]),
{ok, _} = compile:file(ErlFile, [{outdir,Dir}]),
code:purge(erlang_scan),
AbsFile = filename:rootname(ErlFile, ".erl"),
@@ -785,79 +795,79 @@ escape_char(C) -> C.
erlang_scan:tokens(Cont, Chars, Location)
end,
F1 = fun(Cont, Chars, Location) ->
- erlang_scan:token(Cont, Chars, Location)
- end,
+ erlang_scan:token(Cont, Chars, Location)
+ end,
fun() ->
S = "ab cd. ",
- {ok, Ts, 1} = scan_tokens_1(S, F, 1),
- {ok, Ts, 1} = scan_token_1(S, F1, 1),
- {ok, Ts, 1} = scan_tokens(S, F, 1),
- {ok, Ts, 1} = erlang_scan:string(S, 1)
+ {ok, Ts, {1,8}} = scan_tokens_1(S, F, {1,1}),
+ {ok, Ts, {1,8}} = scan_token_1(S, F1, {1,1}),
+ {ok, Ts, {1,8}} = scan_tokens(S, F, {1,1}),
+ {ok, Ts, {1,8}} = erlang_scan:string(S, {1,1})
end(),
fun() ->
S = "'ab\n cd'. ",
- {ok, Ts, 2} = scan_tokens_1(S, F, 1),
- {ok, Ts, 2} = scan_token_1(S, F1, 1),
- {ok, Ts, 2} = scan_tokens(S, F, 1),
- {ok, Ts, 2} = erlang_scan:string(S, 1)
+ {ok, Ts, {2,7}} = scan_tokens_1(S, F, {1,1}),
+ {ok, Ts, {2,7}} = scan_token_1(S, F1, {1,1}),
+ {ok, Ts, {2,7}} = scan_tokens(S, F, {1,1}),
+ {ok, Ts, {2,7}} = erlang_scan:string(S, {1,1})
end(),
fun() ->
S = "99. ",
- {ok, Ts, 1} = scan_tokens_1(S, F, 1),
- {ok, Ts, 1} = scan_token_1(S, F1, 1),
- {ok, Ts, 1} = scan_tokens(S, F, 1),
- {ok, Ts, 1} = erlang_scan:string(S, 1)
+ {ok, Ts, {1,5}} = scan_tokens_1(S, F, {1,1}),
+ {ok, Ts, {1,5}} = scan_token_1(S, F1, {1,1}),
+ {ok, Ts, {1,5}} = scan_tokens(S, F, {1,1}),
+ {ok, Ts, {1,5}} = erlang_scan:string(S, {1,1})
end(),
- {ok,[{integer,1,99},{dot,1}],1} = erlang_scan:string("99. "),
+ {ok,[{integer,{1,1},99},{dot,{1,3}}],{1,5}} = erlang_scan:string("99. "),
fun() ->
Atom = "'" ++ lists:duplicate(1000,$a) ++ "'",
S = Atom ++ ". ",
Reason = "illegal atom " ++ Atom,
- Err = {error,{1,erlang_scan,{user,Reason}},1},
- {done,Err,[]} = scan_tokens_1(S, F, 1),
- {done,Err,[]} = scan_token_1(S, F1, 1),
- {done,Err,[]} = scan_tokens(S, F, 1),
- Err = erlang_scan:string(S, 1)
+ Err = {error,{{1,1003},erlang_scan,{user,Reason}},{1,1003}},
+ {done,Err,[]} = scan_tokens_1(S, F, {1,1}),
+ {done,Err,[]} = scan_token_1(S, F1, {1,1}),
+ {done,Err,[]} = scan_tokens(S, F, {1,1}),
+ Err = erlang_scan:string(S, {1,1})
end(),
fun() ->
S = "\x{aaa}. ",
- Err = {error,{1,erlang_scan,{illegal,[2730]}},1},
- {done,Err,[]} = scan_tokens_1(S, F, 1),
- {done,Err,[_]} = scan_token_1(S, F1, 1), % Note: Rest non-empty
- {done,Err,[]} = scan_tokens(S, F, 1),
- Err = erlang_scan:string(S, 1)
+ Err = {error,{{1,1},erlang_scan,{illegal,[2730]}},{1,1}},
+ {done,Err,[]} = scan_tokens_1(S, F, {1,1}),
+ {done,Err,[_]} = scan_token_1(S, F1, {1,1}), % Note: Rest non-empty
+ {done,Err,[]} = scan_tokens(S, F, {1,1}),
+ Err = erlang_scan:string(S, {1,1})
end(),
fun() ->
S = "\x{aaa} + 1. 34",
- Err = {error,{1,erlang_scan,{illegal,[2730]}},1},
- {done,Err,[]} = scan_tokens_1(S, F, 1),
- {done,Err,[_]} = scan_token_1(S, F1, 1), % Note: Rest non-empty
- {done,Err,"34"} = scan_tokens(S, F, 1),
- Err = erlang_scan:string(S, 1)
+ Err = {error,{{1,1},erlang_scan,{illegal,[2730]}},{1,1}},
+ {done,Err,[]} = scan_tokens_1(S, F, {1,1}),
+ {done,Err,[_]} = scan_token_1(S, F1, {1,1}), % Note: Rest non-empty
+ {done,Err,"34"} = scan_tokens(S, F, {1,1}),
+ Err = erlang_scan:string(S, {1,1})
end(),
fun() ->
S = "\x{aaa} \x{bbb}. 34",
- Err = {error,{1,erlang_scan,{illegal,[2730]}},1},
- {done,Err,[]} = scan_tokens_1(S, F, 1),
- {done,Err,[_]} = scan_token_1(S, F1, 1), % Note: Rest non-empty
- {done,Err,"34"} = scan_tokens(S, F, 1),
- Err = erlang_scan:string(S, 1)
+ Err = {error,{{1,1},erlang_scan,{illegal,[2730]}},{1,1}},
+ {done,Err,[]} = scan_tokens_1(S, F, {1,1}),
+ {done,Err,[_]} = scan_token_1(S, F1, {1,1}), % Note: Rest non-empty
+ {done,Err,"34"} = scan_tokens(S, F, {1,1}),
+ Err = erlang_scan:string(S, {1,1})
end(),
fun() ->
S = "\x{aaa} 18#34. 34",
- Err = {error,{1,erlang_scan,{illegal,[2730]}},1},
- {done,Err,[]} = scan_tokens_1(S, F, 1),
- {done,Err,[_]} = scan_token_1(S, F1, 1), % Note: Rest non-empty
- {done,Err,"34"} = scan_tokens(S, F, 1),
- Err = erlang_scan:string(S, 1)
+ Err = {error,{{1,1},erlang_scan,{illegal,[2730]}},{1,1}},
+ {done,Err,[]} = scan_tokens_1(S, F, {1,1}),
+ {done,Err,[_]} = scan_token_1(S, F1, {1,1}), % Note: Rest non-empty
+ {done,Err,"34"} = scan_tokens(S, F, {1,1}),
+ Err = erlang_scan:string(S, {1,1})
end(),
fun() ->
S = "\x{aaa}"++eof,
- Err = {error,{1,erlang_scan,{illegal,[2730]}},1},
- {done,Err,eof} = scan_tokens_1(S, F, 1),
- {done,Err,[_]} = scan_token_1(S, F1, 1), % Note: Rest non-empty
- {done,Err,eof} = scan_tokens(S, F, 1),
- Err = erlang_scan:string(S, 1)
+ Err = {error,{{1,1},erlang_scan,{illegal,[2730]}},{1,1}},
+ {done,Err,eof} = scan_tokens_1(S, F, {1,1}),
+ {done,Err,[_]} = scan_token_1(S, F1, {1,1}), % Note: Rest non-empty
+ {done,Err,eof} = scan_tokens(S, F, {1,1}),
+ Err = erlang_scan:string(S, {1,1})
end(),
ok.
@@ -912,8 +922,8 @@ line_wrap(Config) when is_list(Config) ->
<<"
Definitions.
Rules.
-[a]+[\\n]*= : {token, {first, TokenLine}}.
-[a]+ : {token, {second, TokenLine}}.
+[a]+[\\n]*= : {token, {first, TokenLoc}}.
+[a]+ : {token, {second, TokenLoc}}.
[\\s\\r\\n\\t]+ : skip_token.
Erlang code.
">>,
@@ -928,20 +938,20 @@ Erlang code.
code:load_abs(AbsFile, test_line_wrap),
fun() ->
S = "aaa\naaa",
- {ok,[{second,1},{second,2}],2} = test_line_wrap:string(S)
+ {ok,[{second,{1,1}},{second,{2,1}}],2} = test_line_wrap:string(S)
end(),
fun() ->
S = "aaa\naaa",
- {ok,[{second,3},{second,4}],4} = test_line_wrap:string(S, 3)
+ {ok,[{second,{3,1}},{second,{4,1}}],4} = test_line_wrap:string(S, 3)
end(),
fun() ->
- {done,{ok,{second,1},1},"\na"} = test_line_wrap:token([], "a\na"),
+ {done,{ok,{second,{1,1}},1},"\na"} = test_line_wrap:token([], "a\na"),
{more,Cont1} = test_line_wrap:token([], "\na"),
- {done,{ok,{second,2},2},eof} = test_line_wrap:token(Cont1, eof)
+ {done,{ok,{second,{2,1}},2},eof} = test_line_wrap:token(Cont1, eof)
end(),
fun() ->
{more,Cont1} = test_line_wrap:tokens([], "a\na"),
- {done,{ok,[{second,1},{second,2}],2},eof} = test_line_wrap:tokens(Cont1, eof)
+ {done,{ok,[{second,{1,1}},{second,{2,1}}],2},eof} = test_line_wrap:tokens(Cont1, eof)
end(),
ok.
@@ -1044,7 +1054,7 @@ otp_10302(Config) when is_list(Config) ->
"-export([t/0]).\n"
"t() ->\n"
" %% Häpp, 'Häpp',\"\\x{400}B\",\"örn_À\"\n"
- " {ok, [R], 1} = string(\"tip\"),\n"
+ " {ok, [R], {1,4}} = string(\"tip\"),\n"
" {tip,foo,'Häpp',[1024,66],[246,114,110,95,1024]} = R,\n"
" Häpp = foo,\n"
" {tip, Häpp, 'Häpp',\"\\x{400}B\",\"örn_À\"} = R,\n"
@@ -1065,7 +1075,7 @@ otp_10302(Config) when is_list(Config) ->
"-export([t/0]).\n"
"t() ->\n"
" %% Häpp, 'Häpp',\"\\x{400}B\",\"örn_À\"\n"
- " {ok, [R], 1} = string(\"tip\"),\n"
+ " {ok, [R], {1,4}} = string(\"tip\"),\n"
" {tip,foo,'Häpp',[1024,66],[195,182,114,110,95,208,128]} = R,\n"
" Häpp = foo,\n"
" {tip, Häpp, 'Häpp',\"\\x{400}B\",\"örn_À\"} = R,\n"
@@ -1139,23 +1149,23 @@ otp_13916(Config) when is_list(Config) ->
"Rules.\n"
"%% mark line break(s) and empty lines by token 'break'\n"
"%% in order to use as delimiters\n"
- "{B}({S}*{B})+ : {token, {break, TokenLine}}.\n"
- "{B} : {token, {break, TokenLine}}.\n"
- "{S}+ : {token, {blank, TokenLine, TokenChars}}.\n"
- "{W}+ : {token, {word, TokenLine, TokenChars}}.\n"
+ "{B}({S}*{B})+ : {token, {break, TokenLoc}}.\n"
+ "{B} : {token, {break, TokenLoc}}.\n"
+ "{S}+ : {token, {blank, TokenLoc, TokenChars}}.\n"
+ "{W}+ : {token, {word, TokenLoc, TokenChars}}.\n"
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->\n"
- " {ok,[{break,1},{blank,4,\" \"},{word,4,\"breaks\"}],4} =\n"
+ " {ok,[{break,{1,1}},{blank,{4,1},\" \"},{word,{4,3},\"breaks\"}],{4,9}} =\n"
" string(\"\\n\\n \\n breaks\"),\n"
- " {ok,[{break,1},{word,4,\"works\"}],4} =\n"
+ "{ok,[{break,{1,1}},{word,{4,1},\"works\"}],{4,6}} =\n"
" string(\"\\n\\n \\nworks\"),\n"
- " {ok,[{break,1},{word,4,\"L4\"},{break,4},\n"
- " {word,5,\"L5\"},{break,5},{word,7,\"L7\"}], 7} =\n"
+ " {ok,[{break,{1,1}},{word,{4,1},\"L4\"},{break,{4,3}},\n"
+ " {word,{5,1},\"L5\"},{break,{5,3}},{word,{7,1},\"L7\"}], {7,3}} =\n"
" string(\"\\n\\n \\nL4\\nL5\\n\\nL7\"),\n"
- " {ok,[{break,1},{blank,4,\" \"},{word,4,\"L4\"},\n"
- " {break,4},{blank,5,\" \"},{word,5,\"L5\"},\n"
- " {break,5},{blank,7,\" \"},{word,7,\"L7\"}], 7} =\n"
+ "{ok,[{break,{1,1}},{blank,{4,1},\" \"},{word,{4,2} ,\"L4\"},\n"
+ " {break,{4,4}},{blank,{5,1},\" \"},{word,{5,2},\"L5\"},\n"
+ " {break,{5,4}},{blank,{7,1},\" \"},{word,{7,2},\"L7\"}], {7,4}} =\n"
" string(\"\\n\\n \\n L4\\n L5\\n\\n L7\"),\n"
" ok.\n">>,
default,
@@ -1164,6 +1174,7 @@ otp_13916(Config) when is_list(Config) ->
ok.
otp_14285(Config) ->
+ %% x{400} takes 2 bytes to represent
Ts = [{otp_14285_1,
<<"%% encoding: latin-1\n"
"Definitions.\n"
@@ -1173,11 +1184,11 @@ otp_14285(Config) ->
"U = [\\x{400}]\n"
"Rules.\n"
"{L}+ : {token,l}.\n"
- "{U}+ : {token,'\\x{400}'}.\n"
+ "{U}+ : {token,{TokenLine,'\\x{400}'}}.\n"
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->\n"
- " {ok,['\\x{400}'],1} = string(\"\\x{400}\"), ok.\n">>,
+ " {ok,[{1,'\\x{400}'}],{1,3}} = string(\"\\x{400}\"), ok.\n">>,
default,
ok},
{otp_14285_2,
@@ -1193,7 +1204,7 @@ otp_14285(Config) ->
"Erlang code.\n"
"-export([t/0]).\n"
"t() ->\n"
- " {ok,['\x{400}'],1} = string(\"\x{400}\"), ok.\n">>,
+ " {ok,['\x{400}'],{1,3}} = string(\"\x{400}\"), ok.\n"/utf8>>,
default,
ok}],
run(Config, Ts),
@@ -1225,6 +1236,54 @@ otp_17023(Config) ->
end,
ok.
+%% Additional tests added with column support
+column_support(Config) ->
+ Ts = [{token_col_var,
+ <<"Definitions.\n"
+ "D = [0-9]\n"
+ "W = [\\s\\n]\n"
+ "Rules.\n"
+ "{W}+ :\n"
+ "skip_token.\n"
+ "{D}+ :\n"
+ "{token,{integer,{TokenLine,TokenCol},list_to_integer(TokenChars)}}.\n"
+ "{D}+\\.{D}+((E|e)(\\+|\\-)?{D}+)? :\n"
+ "{token,{float,{TokenLine,TokenCol},list_to_float(TokenChars)}}.\n"
+ "Erlang code.\n"
+ "-export([t/0]).\n"
+ "t() ->\n"
+ "{ok,[{float, {2,1}, 4.44},{integer, {3,3}, 5},{integer, {7,3}, 7}],{8,2}}"
+ "= string(\"\n4.44 \n 5 \n \n\n\n 7 \n \"), ok.\n">>,
+ default,
+ ok},
+ {tab,
+ <<"Definitions.\n"
+ "Rules.\n"
+ "[a]+[\\n]*= : {token, {first, TokenLoc}}.\n"
+ "[a]+ : {token, {second, TokenLoc}}.\n"
+ "[\\s\\r\\n\\t]+ : skip_token.\n"
+ "Erlang code.\n"
+ "-export([t/0]).\n"
+ "t() ->\n"
+ "{ok,[{second,{1,27}},{second,{2,19}}],{2,25}} = string(\" \t \t\t a\\n \t \t aaa\t\"), ok.\n">>,
+ default,
+ ok},
+ {tab_custom_size,
+ <<"Definitions.\n"
+ "Rules.\n"
+ "[a]+[\\n]*= : {token, {first, TokenLoc}}.\n"
+ "[a]+ : {token, {second, TokenLoc}}.\n"
+ "[\\s\\r\\n\\t]+ : skip_token.\n"
+ "Erlang code.\n"
+ "-export([t/0]).\n"
+ "t() ->\n"
+ "{ok,[{second,{1,15}},{second,{2,9}}],{2,16}} = string(\" \t \t\t a\\n \t \t aaa\t\"), ok.\n">>,
+ default,
+ [{tab_size,3}],
+ ok}],
+ run(Config, Ts),
+ ok.
+
%% OTP-17499. GH-4918.
compiler_warnings(Config) ->
Xrl =
@@ -1256,18 +1315,23 @@ writable(Fname) ->
ok = file:write_file_info(Fname, Info#file_info{mode = Mode}).
run(Config, Tests) ->
- F = fun({N,P,Pre,E}) ->
- case catch run_test(Config, P, Pre) of
- E ->
- ok;
- Bad ->
- ct:fail("~nTest ~p failed. Expected~n ~p~n"
- "but got~n ~p~n", [N, E, Bad])
- end
+ F = fun F({N,P,Pre,E}) ->
+ F({N,P,Pre,[],E});
+ F({N,P,Pre,Opts,E}) ->
+ case catch run_test(Config,P,Pre,Opts) of
+ E ->
+ ok;
+ Bad ->
+ ct:fail("~nTest ~p failed. Expected~n ~p~n"
+ "but got~n ~p~n", [N, E, Bad])
+ end
end,
lists:foreach(F, Tests).
run_test(Config, Def, Pre) ->
+ run_test(Config, Def, Pre, []).
+
+run_test(Config, Def, Pre, LOpts0) ->
%% io:format("testing ~s~n", [binary_to_list(Def)]),
DefFile = 'leex_test.xrl',
Filename = 'leex_test.erl',
@@ -1276,14 +1340,14 @@ run_test(Config, Def, Pre) ->
ErlFile = filename:join(DataDir, Filename),
Opts = [return, warn_unused_vars,{outdir,DataDir}],
ok = file:write_file(XrlFile, Def),
- LOpts = [return, {report, false} |
+ LOpts = LOpts0 ++ [return, {report, false} |
case Pre of
default ->
[];
_ ->
[{includefile,Pre}]
end],
- XOpts = [verbose, dfa_graph], % just to get some code coverage...
+ XOpts = [verbose, dfa_graph, {error_location, column}], % just to get some code coverage...
LRet = leex:file(XrlFile, XOpts ++ LOpts),
case LRet of
{ok, _Outfile, _LWs} ->
@@ -1313,7 +1377,7 @@ extract(File, Ts) ->
search_for_file_attr(PartialFilePathRegex, Forms) ->
lists:search(fun
({attribute, _, file, {FileAttr, _}}) ->
- case re:run(FileAttr, PartialFilePathRegex) of
+ case re:run(FileAttr, PartialFilePathRegex, [unicode]) of
nomatch -> false;
_ -> true
end;
diff --git a/lib/parsetools/test/yecc_SUITE.erl b/lib/parsetools/test/yecc_SUITE.erl
index e76b98f0f5..383969e7a8 100644
--- a/lib/parsetools/test/yecc_SUITE.erl
+++ b/lib/parsetools/test/yecc_SUITE.erl
@@ -2326,7 +2326,7 @@ safe_second_element(Other) -> Other.
search_for_file_attr(PartialFilePathRegex, Forms) ->
lists:search(fun
({attribute, _, file, {FileAttr, _}}) ->
- case re:run(FileAttr, PartialFilePathRegex) of
+ case re:run(FileAttr, PartialFilePathRegex, [unicode]) of
nomatch -> false;
_ -> true
end;
diff --git a/lib/public_key/.gitignore b/lib/public_key/.gitignore
index 8d0ebe018f..bd43ec2abd 100644
--- a/lib/public_key/.gitignore
+++ b/lib/public_key/.gitignore
@@ -7,3 +7,4 @@ src/PKCS-FRAME.erl
src/PKCS-FRAME.hrl
include/OTP-PUB-KEY.hrl
include/PKCS-FRAME.hrl
+priv/lib/
diff --git a/lib/public_key/c_src/Makefile b/lib/public_key/c_src/Makefile
index 535d6c9218..064868af42 100644
--- a/lib/public_key/c_src/Makefile
+++ b/lib/public_key/c_src/Makefile
@@ -91,7 +91,7 @@ endif
_create_dirs := $(shell mkdir -p $(OBJDIR) $(LIBDIR))
-debug opt valgrind: $(DIRS) $(PUBKEY_LIB)
+debug opt valgrind lcnt asan: $(DIRS) $(PUBKEY_LIB)
$(OBJDIR):
-@mkdir -p $(OBJDIR)
diff --git a/lib/public_key/src/pubkey_ocsp.erl b/lib/public_key/src/pubkey_ocsp.erl
index 249b430660..6bdb9a563f 100644
--- a/lib/public_key/src/pubkey_ocsp.erl
+++ b/lib/public_key/src/pubkey_ocsp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,18 +19,55 @@
%%
-module(pubkey_ocsp).
-
-include("public_key.hrl").
--export([otp_cert/1,
- get_ocsp_responder_id/1,
+-export([find_single_response/3,
+ get_acceptable_response_types_extn/0,
get_nonce_extn/1,
- decode_ocsp_response/1,
+ get_ocsp_responder_id/1,
+ ocsp_status/1,
verify_ocsp_response/3,
- get_acceptable_response_types_extn/0,
- find_single_response/3,
- ocsp_status/1]).
+ decode_ocsp_response/1]).
+%% Tracing
+-export([handle_trace/3]).
+
+-spec get_ocsp_responder_id(#'Certificate'{}) -> binary().
+get_ocsp_responder_id(#'Certificate'{tbsCertificate = TbsCert}) ->
+ public_key:der_encode(
+ 'ResponderID', {byName, TbsCert#'TBSCertificate'.subject}).
+
+-spec get_nonce_extn(undefined | binary()) -> undefined | #'Extension'{}.
+get_nonce_extn(undefined) ->
+ undefined;
+get_nonce_extn(Nonce) when is_binary(Nonce) ->
+ #'Extension'{
+ extnID = ?'id-pkix-ocsp-nonce',
+ extnValue = Nonce
+ }.
+
+-spec verify_ocsp_response(#'BasicOCSPResponse'{}, list(), undefined | binary()) ->
+ {ok, term()} | {error, term()}.
+verify_ocsp_response(OCSPResponse, ResponderCerts, Nonce) ->
+ do_verify_ocsp_response(OCSPResponse, ResponderCerts, Nonce).
+
+-spec get_acceptable_response_types_extn() -> #'Extension'{}.
+get_acceptable_response_types_extn() ->
+ #'Extension'{
+ extnID = ?'id-pkix-ocsp-response',
+ extnValue = public_key:der_encode(
+ 'AcceptableResponses', [?'id-pkix-ocsp-basic'])
+ }.
+
+-spec find_single_response(#'OTPCertificate'{}, #'OTPCertificate'{},
+ [#'SingleResponse'{}]) ->
+ {ok, #'SingleResponse'{}} | {error, no_matched_response}.
+find_single_response(Cert, IssuerCert, SingleResponseList) ->
+ IssuerName = get_subject_name(IssuerCert),
+ IssuerKey = get_public_key(IssuerCert),
+ SerialNum = get_serial_num(Cert),
+ match_single_response(IssuerName, IssuerKey, SerialNum, SingleResponseList).
+-spec ocsp_status({atom(), term()}) -> atom() | {atom(), {atom(), term()}}.
ocsp_status({good, _}) ->
valid;
ocsp_status({unknown, Reason}) ->
@@ -38,61 +75,64 @@ ocsp_status({unknown, Reason}) ->
ocsp_status({revoked, Reason}) ->
{bad_cert, {revoked, Reason}}.
-%%--------------------------------------------------------------------
--spec verify_ocsp_response(binary(), list(), undefined | binary()) ->
- {ok, term()} | {error, term()}.
-%%
-%% Description: Verify the OCSP response to get the certificate status
-%%--------------------------------------------------------------------
-verify_ocsp_response(OCSPResponseDer, ResponderCerts, Nonce) ->
- do_verify_ocsp_response(
- decode_ocsp_response(OCSPResponseDer), ResponderCerts, Nonce
- ).
+decode_ocsp_response(ResponseDer) ->
+ Resp = public_key:der_decode('OCSPResponse', ResponseDer),
+ case Resp#'OCSPResponse'.responseStatus of
+ successful ->
+ decode_response_bytes(
+ Resp#'OCSPResponse'.responseBytes
+ );
+ Error ->
+ {error, Error}
+ end.
%%--------------------------------------------------------------------
--spec do_verify_ocsp_response({ok, #'BasicOCSPResponse'{}} | {error, term()},
- list(), undefined | binary()) ->
- {ok, term()} | {error, term()}.
-%%
-%% Description: Verify the OCSP response to get the certificate status
-%%--------------------------------------------------------------------
-do_verify_ocsp_response(
- {ok, #'BasicOCSPResponse'{
- tbsResponseData = ResponseData,
- signatureAlgorithm = SignatureAlgo,
- signature = Signature,
- certs = Certs
- }}, ResponderCerts, Nonce) ->
+match_single_response(_IssuerName, _IssuerKey, _SerialNum, []) ->
+ {error, no_matched_response};
+match_single_response(IssuerName, IssuerKey, SerialNum,
+ [#'SingleResponse'{
+ certID = #'CertID'{hashAlgorithm = Algo} = CertID} =
+ Response | Responses]) ->
+ HashType = public_key:pkix_hash_type(Algo#'AlgorithmIdentifier'.algorithm),
+ case (SerialNum == CertID#'CertID'.serialNumber) andalso
+ (crypto:hash(HashType, IssuerName) == CertID#'CertID'.issuerNameHash) andalso
+ (crypto:hash(HashType, IssuerKey) == CertID#'CertID'.issuerKeyHash) of
+ true ->
+ {ok, Response};
+ false ->
+ match_single_response(IssuerName, IssuerKey, SerialNum, Responses)
+ end.
- #'ResponseData'{
- responderID = ResponderID
- } = ResponseData,
+get_serial_num(#'OTPCertificate'{tbsCertificate = TbsCert}) ->
+ TbsCert#'OTPTBSCertificate'.serialNumber.
+decode_response_bytes(#'ResponseBytes'{
+ responseType = ?'id-pkix-ocsp-basic',
+ response = Data}) ->
+ {ok, public_key:der_decode('BasicOCSPResponse', Data)};
+decode_response_bytes(#'ResponseBytes'{responseType = RespType}) ->
+ {error, {ocsp_response_type_not_supported, RespType}}.
+
+do_verify_ocsp_response(#'BasicOCSPResponse'{
+ tbsResponseData = ResponseData,
+ signatureAlgorithm = SignatureAlgo,
+ signature = Signature},
+ ResponderCerts, Nonce) ->
+ #'ResponseData'{responderID = ResponderID} = ResponseData,
case verify_ocsp_signature(
- public_key:der_encode('ResponseData', ResponseData),
- SignatureAlgo#'AlgorithmIdentifier'.algorithm,
- Signature, Certs ++ ResponderCerts,
- ResponderID) of
+ public_key:der_encode('ResponseData', ResponseData),
+ SignatureAlgo#'AlgorithmIdentifier'.algorithm,
+ Signature, ResponderCerts,
+ ResponderID) of
ok ->
verify_ocsp_nonce(ResponseData, Nonce);
{error, Reason} ->
{error, Reason}
- end;
-do_verify_ocsp_response({error, Reason}, _ResponderCerts, _Nonce) ->
- {error, Reason}.
+ end.
-%%--------------------------------------------------------------------
--spec verify_ocsp_nonce(#'ResponseData'{}, undefined | binary()) ->
- {ok, term()} | {error, nonce_mismatch}.
-%%
-%% Description: Check if the nonces matches in OCSP response
-%%--------------------------------------------------------------------
verify_ocsp_nonce(ResponseData, Nonce) ->
- #'ResponseData'{
- responses = Responses,
- responseExtensions = ResponseExtns
- } = ResponseData,
-
+ #'ResponseData'{responses = Responses, responseExtensions = ResponseExtns} =
+ ResponseData,
case get_nonce_value(ResponseExtns) of
Nonce ->
{ok, Responses};
@@ -100,12 +140,6 @@ verify_ocsp_nonce(ResponseData, Nonce) ->
{error, nonce_mismatch}
end.
-%%--------------------------------------------------------------------
--spec get_nonce_value(asn1_NOVALUE | list()) ->
- undefined | binary().
-%%
-%% Description: Get the nonce value from extensions
-%%--------------------------------------------------------------------
%% no extensions present in response
get_nonce_value(asn1_NOVALUE) ->
undefined;
@@ -119,44 +153,8 @@ get_nonce_value([#'Extension'{
get_nonce_value([_Extn | Rest]) ->
get_nonce_value(Rest).
-%%--------------------------------------------------------------------
--spec decode_ocsp_response(binary()) ->
- {ok, #'BasicOCSPResponse'{}} | {error, term()}.
-%%
-%% Description: Decode the OCSP response der
-%%--------------------------------------------------------------------
-decode_ocsp_response(Response) ->
- Resp = public_key:der_decode('OCSPResponse', Response),
- case Resp#'OCSPResponse'.responseStatus of
- successful ->
- decode_response_bytes(
- Resp#'OCSPResponse'.responseBytes
- );
- Error ->
- {error, Error}
- end.
-
-%%--------------------------------------------------------------------
--spec decode_response_bytes(#'ResponseBytes'{}) ->
- {ok, #'BasicOCSPResponse'{}} | {error, term()}.
-%%
-%% Description: Get basic ocsp response field
-%%--------------------------------------------------------------------
-decode_response_bytes(#'ResponseBytes'{
- responseType = ?'id-pkix-ocsp-basic',
- response = Data}) ->
- {ok, public_key:der_decode('BasicOCSPResponse', Data)};
-decode_response_bytes(#'ResponseBytes'{responseType = RespType}) ->
- {error, {ocsp_response_type_not_supported, RespType}}.
-
-%%--------------------------------------------------------------------
--spec verify_ocsp_signature(binary(), term(), term(), list(), term()) ->
- ok | {error, term()}.
-%%
-%% Description: Verify the signature of OCSP response
-%%--------------------------------------------------------------------
-verify_ocsp_signature(
- ResponseDataDer, SignatureAlgo, Signature, Certs, ResponderID) ->
+verify_ocsp_signature(ResponseDataDer, SignatureAlgo, Signature,
+ Certs, ResponderID) ->
case find_responder_cert(ResponderID, Certs) of
{ok, Cert} ->
do_verify_ocsp_signature(
@@ -165,12 +163,6 @@ verify_ocsp_signature(
{error, Reason}
end.
-%%--------------------------------------------------------------------
--spec find_responder_cert(term(), list()) ->
- {ok, #'Certificate'{} | #'OTPCertificate'{}} | {error, term()}.
-%%
-%% Description: Find the OCSP responder's cert in input list
-%%--------------------------------------------------------------------
find_responder_cert(_ResponderID, []) ->
{error, ocsp_responder_cert_not_found};
find_responder_cert(ResponderID, [Cert | TCerts]) ->
@@ -181,87 +173,29 @@ find_responder_cert(ResponderID, [Cert | TCerts]) ->
find_responder_cert(ResponderID, TCerts)
end.
-%%--------------------------------------------------------------------
--spec do_verify_ocsp_signature(
- binary(), term(), term(), #'Certificate'{} | #'OTPCertificate'{}) ->
- ok | {error, term()}.
-%%
-%% Description: Verify the signature of OCSP response
-%%--------------------------------------------------------------------
do_verify_ocsp_signature(ResponseDataDer, Signature, AlgorithmID, Cert) ->
{DigestType, _SignatureType} = public_key:pkix_sign_types(AlgorithmID),
case public_key:verify(
- ResponseDataDer, DigestType, Signature,
- get_public_key_rec(Cert)) of
+ ResponseDataDer, DigestType, Signature,
+ get_public_key_rec(Cert)) of
true ->
ok;
false ->
{error, ocsp_response_bad_signature}
end.
-%%--------------------------------------------------------------------
--spec get_public_key_rec(#'Certificate'{} | #'OTPCertificate'{}) ->
- term().
-%%
-%% Description: Get the subject public key field
-%%--------------------------------------------------------------------
-get_public_key_rec(#'Certificate'{} = Cert) ->
- get_public_key_rec(otp_cert(Cert));
get_public_key_rec(#'OTPCertificate'{tbsCertificate = TbsCert}) ->
PKInfo = TbsCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
PKInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey.
-%%--------------------------------------------------------------------
--spec is_responder(tuple(), #'Certificate'{} | #'OTPCertificate'{}) ->
- boolean().
-%%
-%% Description: Check if is OCSP responder's cert
-%%--------------------------------------------------------------------
is_responder({byName, Name}, Cert) ->
public_key:der_encode('Name', Name) == get_subject_name(Cert);
is_responder({byKey, Key}, Cert) ->
Key == crypto:hash(sha, get_public_key(Cert)).
-%%--------------------------------------------------------------------
--spec otp_cert(#'Certificate'{} | #'OTPCertificate'{} | binary()) ->
- #'OTPCertificate'{}.
-%%
-%% Description: Convert to #'OTPCertificate'{}
-%%--------------------------------------------------------------------
-otp_cert(#'OTPCertificate'{} = Cert) ->
- Cert;
-otp_cert(#'Certificate'{} = Cert) ->
- public_key:pkix_decode_cert(
- public_key:der_encode('Certificate', Cert), otp);
-otp_cert(CertDer) when is_binary(CertDer) ->
- public_key:pkix_decode_cert(CertDer, otp).
-
-%%--------------------------------------------------------------------
--spec get_ocsp_responder_id(#'Certificate'{}) -> binary().
-%%
-%% Description: Get the OCSP responder ID der
-%%--------------------------------------------------------------------
-get_ocsp_responder_id(#'Certificate'{tbsCertificate = TbsCert}) ->
- public_key:der_encode(
- 'ResponderID', {byName, TbsCert#'TBSCertificate'.subject}).
-
-%%--------------------------------------------------------------------
--spec get_subject_name(#'Certificate'{} | #'OTPCertificate'{}) -> binary().
-%%
-%% Description: Get the subject der from cert
-%%--------------------------------------------------------------------
-get_subject_name(#'Certificate'{} = Cert) ->
- get_subject_name(otp_cert(Cert));
get_subject_name(#'OTPCertificate'{tbsCertificate = TbsCert}) ->
public_key:pkix_encode('Name', TbsCert#'OTPTBSCertificate'.subject, otp).
-%%--------------------------------------------------------------------
--spec get_public_key(#'Certificate'{} | #'OTPCertificate'{}) -> binary().
-%%
-%% Description: Get the public key from cert
-%%--------------------------------------------------------------------
-get_public_key(#'Certificate'{} = Cert) ->
- get_public_key(otp_cert(Cert));
get_public_key(#'OTPCertificate'{tbsCertificate = TbsCert}) ->
PKInfo = TbsCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
enc_pub_key(PKInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey).
@@ -273,73 +207,35 @@ enc_pub_key({DsaInt, #'Dss-Parms'{}}) when is_integer(DsaInt) ->
enc_pub_key({#'ECPoint'{point = Key}, _ECParam}) ->
Key.
-%%--------------------------------------------------------------------
--spec get_nonce_extn(undefined | binary()) -> undefined | #'Extension'{}.
-%%
-%% Description: Get an OCSP nonce der
-%%--------------------------------------------------------------------
-get_nonce_extn(undefined) ->
- undefined;
-get_nonce_extn(Nonce) when is_binary(Nonce) ->
- #'Extension'{
- extnID = ?'id-pkix-ocsp-nonce',
- extnValue = Nonce
- }.
-
-%%--------------------------------------------------------------------
--spec get_acceptable_response_types_extn() -> #'Extension'{}.
-%%
-%% Description: Get an acceptable response types der
-%%--------------------------------------------------------------------
-get_acceptable_response_types_extn() ->
- #'Extension'{
- extnID = ?'id-pkix-ocsp-response',
- extnValue = public_key:der_encode(
- 'AcceptableResponses', [?'id-pkix-ocsp-basic'])
- }.
-
-%%--------------------------------------------------------------------
--spec get_serial_num(binary | #'Certificate'{} | #'OTPCertificate'{}) ->
- term().
-%%
-%% Description: Get the serial number of a certificate
-%%--------------------------------------------------------------------
-get_serial_num(Cert) ->
- #'OTPCertificate'{tbsCertificate = TbsCert} = otp_cert(Cert),
- TbsCert#'OTPTBSCertificate'.serialNumber.
-
-
-%%--------------------------------------------------------------------
-%% -spec find_single_response(#'OTPCertificate'{}, #'OTPCertificate'{},
-%% [#'SingleResponse'{}]) ->
-%% #'SingleResponse'{} | {error, no_matched_response}.
-%% %%
-%% Description: Find the matched single response.
-%%--------------------------------------------------------------------
-find_single_response(Cert, IssuerCert, SingleResponseList) ->
- IssuerName = get_subject_name(IssuerCert),
- IssuerKey = get_public_key(IssuerCert),
- SerialNum = get_serial_num(Cert),
- match_single_response(
- IssuerName, IssuerKey, SerialNum, SingleResponseList).
-
-match_single_response(_IssuerName, _IssuerKey, _SerialNum, []) ->
- {error, no_matched_response};
-match_single_response(
- IssuerName, IssuerKey, SerialNum,
- [#'SingleResponse'{
- certID = #'CertID'{hashAlgorithm = Algo} = CertID
- } = Response | Responses]) ->
- HashType = public_key:pkix_hash_type(
- Algo#'AlgorithmIdentifier'.algorithm),
- case (SerialNum == CertID#'CertID'.serialNumber) andalso
- (crypto:hash(
- HashType, IssuerName) == CertID#'CertID'.issuerNameHash) andalso
- (crypto:hash(
- HashType, IssuerKey) == CertID#'CertID'.issuerKeyHash) of
- true ->
- {ok, Response};
- false ->
- match_single_response(IssuerName, IssuerKey, SerialNum, Responses)
- end.
-
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(csp,
+ {call, {?MODULE, do_verify_ocsp_response, [BasicOcspResponse | _]}}, Stack) ->
+ #'BasicOCSPResponse'{
+ tbsResponseData =
+ #'ResponseData'{responderID = ResponderID,
+ producedAt = ProducedAt}} = BasicOcspResponse,
+ {io_lib:format("ResponderId = ~W producedAt = ~p", [ResponderID, 5, ProducedAt]), Stack};
+handle_trace(csp,
+ {call, {?MODULE, match_single_response,
+ [_IssuerName, _IssuerKey, _SerialNum,
+ [#'SingleResponse'{thisUpdate = ThisUpdate,
+ nextUpdate = NextUpdate}]]}}, Stack) ->
+ {io_lib:format("ThisUpdate = ~p NextUpdate = ~p", [ThisUpdate, NextUpdate]), Stack};
+handle_trace(csp,
+ {call, {?MODULE, is_responder, [Id, Cert]}}, Stack) ->
+ {io_lib:format("~nId = ~P~nCert = ~P", [Id, 10, Cert, 10]), Stack};
+handle_trace(csp,
+ {call, {?MODULE, find_single_response, [Cert, IssuerCert | _]}}, Stack) ->
+ {io_lib:format("#2 OCSP validation started~nCert = ~W IssuerCert = ~W",
+ [Cert, 7, IssuerCert, 7]), Stack};
+ %% {io_lib:format("#2 OCSP validation started~nCert = ~s IssuerCert = ~s",
+ %% [ssl_test_lib:format_cert(Cert),
+ %% ssl_test_lib:format_cert(IssuerCert)]), Stack};
+
+handle_trace(csp,
+ {return_from, {?MODULE, is_responder, 2}, Return},
+ Stack) ->
+ {io_lib:format("Return = ~p", [Return]), Stack}.
diff --git a/lib/public_key/src/pubkey_os_cacerts.erl b/lib/public_key/src/pubkey_os_cacerts.erl
index 1970c8b3d3..93f0e48353 100644
--- a/lib/public_key/src/pubkey_os_cacerts.erl
+++ b/lib/public_key/src/pubkey_os_cacerts.erl
@@ -204,7 +204,10 @@ load_nif() ->
{error, {load_failed, _}}=Error1 ->
Arch = erlang:system_info(system_architecture),
ArchLibDir = filename:join([PrivDir, "lib", Arch]),
- Candidate = filelib:wildcard(filename:join([ArchLibDir,LibName ++ "*" ])),
+ Candidate =
+ filelib:wildcard(
+ filename:join([ArchLibDir,LibName ++ "*" ]),
+ erl_prim_loader),
case Candidate of
[] -> Error1;
_ ->
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 3cb9fea632..8c3805c219 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -69,6 +69,8 @@
cacerts_load/1,
cacerts_clear/0
]).
+%% Tracing
+-export([handle_trace/3]).
%%----------------
%% Moved to ssh
@@ -77,7 +79,6 @@
{ssh_hostkey_fingerprint,1, "use ssh:hostkey_fingerprint/1 instead"},
{ssh_hostkey_fingerprint,2, "use ssh:hostkey_fingerprint/2 instead"}
]).
-
-export([ssh_curvename2oid/1, oid2ssh_curvename/1]).
%% When removing for OTP-25.0, remember to also remove
%% - most of pubkey_ssh.erl except
@@ -1373,14 +1374,37 @@ pkix_test_root_cert(Name, Opts) ->
%% Description: Validate OCSP staple response
%%--------------------------------------------------------------------
-pkix_ocsp_validate(DerCert, IssuerCert, OcspRespDer, ResponderCerts, NonceExt) when is_binary(DerCert) ->
- pkix_ocsp_validate(pkix_decode_cert(DerCert, otp), IssuerCert, OcspRespDer, ResponderCerts, NonceExt);
-pkix_ocsp_validate(Cert, DerIssuerCert, OcspRespDer, ResponderCerts, NonceExt) when is_binary(DerIssuerCert) ->
- pkix_ocsp_validate(Cert, pkix_decode_cert(DerIssuerCert, otp), OcspRespDer, ResponderCerts, NonceExt);
+pkix_ocsp_validate(DerCert, IssuerCert, OcspRespDer, ResponderCerts, NonceExt)
+ when is_binary(DerCert) ->
+ pkix_ocsp_validate(pkix_decode_cert(DerCert, otp), IssuerCert, OcspRespDer,
+ ResponderCerts, NonceExt);
+pkix_ocsp_validate(Cert, DerIssuerCert, OcspRespDer, ResponderCerts, NonceExt)
+ when is_binary(DerIssuerCert) ->
+ pkix_ocsp_validate(Cert, pkix_decode_cert(DerIssuerCert, otp), OcspRespDer,
+ ResponderCerts, NonceExt);
pkix_ocsp_validate(Cert, IssuerCert, OcspRespDer, ResponderCerts, NonceExt) ->
- case ocsp_responses(OcspRespDer, ResponderCerts, NonceExt) of
+ OcspResponse = pubkey_ocsp:decode_ocsp_response(OcspRespDer),
+ OcspCertResponses =
+ case OcspResponse of
+ {ok, BasicOcspResponse = #'BasicOCSPResponse'{certs = Certs}} ->
+ OcspResponseCerts = [otp_cert(C) || C <- Certs],
+ UserResponderCerts =
+ [otp_cert(pkix_decode_cert(C, plain)) || C <- ResponderCerts],
+ pubkey_ocsp:verify_ocsp_response(
+ BasicOcspResponse, OcspResponseCerts ++ UserResponderCerts,
+ NonceExt);
+ {error, _} = Error ->
+ Error
+ end,
+ case OcspCertResponses of
{ok, Responses} ->
- ocsp_status(Cert, IssuerCert, Responses);
+ case pubkey_ocsp:find_single_response(
+ otp_cert(Cert), otp_cert(IssuerCert), Responses) of
+ {ok, #'SingleResponse'{certStatus = CertStatus}} ->
+ pubkey_ocsp:ocsp_status(CertStatus);
+ {error, no_matched_response = Reason} ->
+ {bad_cert, {revocation_status_undetermined, Reason}}
+ end;
{error, Reason} ->
{bad_cert, {revocation_status_undetermined, Reason}}
end.
@@ -1395,12 +1419,12 @@ ocsp_extensions(Nonce) ->
erlang:is_record(Extn, 'Extension')].
%%--------------------------------------------------------------------
--spec ocsp_responder_id(#'Certificate'{}) -> binary().
+-spec ocsp_responder_id(binary()) -> binary().
%%
%% Description: Get the OCSP responder ID der
%%--------------------------------------------------------------------
-ocsp_responder_id(Cert) ->
- pubkey_ocsp:get_ocsp_responder_id(Cert).
+ocsp_responder_id(CertDer) ->
+ pubkey_ocsp:get_ocsp_responder_id(pkix_decode_cert(CertDer, plain)).
%%--------------------------------------------------------------------
-spec cacerts_get() -> [combined_cert()].
@@ -1618,7 +1642,9 @@ otp_cert(Der) when is_binary(Der) ->
otp_cert(#'OTPCertificate'{} = Cert) ->
Cert;
otp_cert(#cert{otp = OtpCert}) ->
- OtpCert.
+ OtpCert;
+otp_cert(#'Certificate'{} = Cert) ->
+ pkix_decode_cert(der_encode('Certificate', Cert), otp).
der_cert(#'OTPCertificate'{} = Cert) ->
pkix_encode('OTPCertificate', Cert, otp);
@@ -2027,18 +2053,39 @@ format_details([]) ->
no_relevant_crls;
format_details(Details) ->
Details.
-
-ocsp_status(Cert, IssuerCert, Responses) ->
- case pubkey_ocsp:find_single_response(Cert, IssuerCert, Responses) of
- {ok, #'SingleResponse'{certStatus = CertStatus}} ->
- pubkey_ocsp:ocsp_status(CertStatus);
- {error, no_matched_response = Reason} ->
- {bad_cert, {revocation_status_undetermined, Reason}}
- end.
-
-ocsp_responses(OCSPResponseDer, ResponderCerts, Nonce) ->
- pubkey_ocsp:verify_ocsp_response(OCSPResponseDer,
- ResponderCerts, Nonce).
subject_public_key_info(Alg, PubKey) ->
#'OTPSubjectPublicKeyInfo'{algorithm = Alg, subjectPublicKey = PubKey}.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(csp,
+ {call, {?MODULE, ocsp_responder_id, [Cert]}}, Stack) ->
+ {io_lib:format("pkix_decode_cert(Cert, plain) = ~W", [Cert, 5]),
+ %% {io_lib:format("pkix_decode_cert(Cert, plain) = ~s", [ssl_test_lib:format_cert(Cert)]),
+ Stack};
+handle_trace(csp,
+ {return_from, {?MODULE, ocsp_responder_id, 1}, Return},
+ Stack) ->
+ {io_lib:format("OCSP Responder ID = ~P", [Return, 10]), Stack};
+handle_trace(crt,
+ {call, {?MODULE, pkix_decode_cert, [Cert, _Type]}}, Stack) ->
+ {io_lib:format("Cert = ~W", [Cert, 5]), Stack};
+ %% {io_lib:format("Cert = ~s", [ssl_test_lib:format_cert(Cert)]), Stack};
+handle_trace(csp,
+ {call, {?MODULE, pkix_ocsp_validate, [Cert, IssuerCert | _]}}, Stack) ->
+ {io_lib:format("#2 OCSP validation started~nCert = ~W IssuerCert = ~W",
+ [Cert, 7, IssuerCert, 7]), Stack};
+ %% {io_lib:format("#2 OCSP validation started~nCert = ~s IssuerCert = ~s",
+ %% [ssl_test_lib:format_cert(Cert),
+ %% ssl_test_lib:format_cert(IssuerCert)]), Stack};
+handle_trace(csp,
+ {call, {?MODULE, otp_cert, [Cert]}}, Stack) ->
+ {io_lib:format("Cert = ~W", [Cert, 5]), Stack};
+ %% {io_lib:format("Cert = ~s", [ssl_test_lib:format_cert(otp_cert(Cert))]), Stack};
+handle_trace(csp,
+ {return_from, {?MODULE, pkix_ocsp_validate, 5}, Return},
+ Stack) ->
+ {io_lib:format("#2 OCSP validation result = ~p", [Return]), Stack}.
diff --git a/lib/reltool/doc/src/reltool.xml b/lib/reltool/doc/src/reltool.xml
index 49c5424969..ff05d72232 100644
--- a/lib/reltool/doc/src/reltool.xml
+++ b/lib/reltool/doc/src/reltool.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2009</year>
- <year>2022</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -226,10 +226,10 @@
<tag><c>debug_info</c></tag>
<item>
- <p>The <c>debug_info</c> parameter controls whether the debug
- information in the beam file should be kept (<c>keep</c>) or
- stripped <c>strip</c> when the file is copied to the target
- system.</p>
+ <p>The <c>debug_info</c> parameter controls what debug
+ information in the beam file should be kept or stripped.
+ <c>keep</c> keeps all debug info, <c>strip</c> strips all debug
+ info, and a list of chunkids keeps only those chunks.</p>
</item>
<tag><c>excl_lib</c></tag>
@@ -325,40 +325,6 @@
<c>[]</c>.</p>
</item>
- <tag><c>incl_archive_filters</c></tag>
- <item>
- <p>This parameter normally contains a list of regular
- expressions that controls which top level directories in an
- application should be included in an archive file (as
- opposed to being included as a regular directory outside the
- archive). Each top directory in the application must match at
- least one of the listed regular expressions in order to be
- included. Further the files may not match any filter in
- <c>excl_app_filters</c> in order to be included. This
- parameter defaults to <c>[".*"]</c>.</p>
- </item>
-
- <tag><c>excl_archive_filters</c></tag>
- <item>
- <p>This parameter normally contains a list of regular
- expressions that controls which top level directories in an
- application should not be included in an archive file. In
- order to be included in the application archive, a top
- directory must match some filter in <c>incl_archive_filters</c>
- but not any filter in <c>excl_archive_filters</c>. This
- parameter defaults to <c>["^include$","^priv$"]</c>.</p>
- </item>
-
- <tag><c>archive_opts</c></tag>
- <item>
- <p>This parameter contains a list of options that are given to
- <c>zip:create/3</c> when application specific files are
- packaged into an archive. Only a subset of the options are
- supported. The most useful options in this context are the ones
- that control which types of files should be compressed. This
- parameter defaults to <c>[]</c>.</p>
- </item>
-
</taglist>
<p>On application (<c>escript</c>) level, the following options are
@@ -441,24 +407,6 @@
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
</item>
-
- <tag><c>incl_archive_filters</c></tag>
- <item>
- <p>The value of this parameter overrides the parameter with the
- same name on system level.</p>
- </item>
-
- <tag><c>excl_archive_filters</c></tag>
- <item>
- <p>The value of this parameter overrides the parameter with the
- same name on system level.</p>
- </item>
-
- <tag><c>archive_opts</c></tag>
- <item>
- <p>The value of this parameter overrides the parameter with the
- same name on system level.</p>
- </item>
</taglist>
<p>On module (<c>mod</c>) level, the following options are
@@ -511,9 +459,6 @@ sys() = {root_dir, root_dir()}
| {excl_sys_filters, excl_sys_filters()}
| {incl_app_filters, incl_app_filters()}
| {excl_app_filters, excl_app_filters()}
- | {incl_archive_filters, incl_archive_filters()}
- | {excl_archive_filters, excl_archive_filters()}
- | {archive_opts, [archive_opt()]}
app() = {vsn, app_vsn()}
| {lib_dir, lib_dir()}
| {mod, mod_name(), [mod()]}
@@ -526,9 +471,6 @@ app() = {vsn, app_vsn()}
| {excl_sys_filters, excl_sys_filters()}
| {incl_app_filters, incl_app_filters()}
| {excl_app_filters, excl_app_filters()}
- | {incl_archive_filters, incl_archive_filters()}
- | {excl_archive_filters, excl_archive_filters()}
- | {archive_opts, [archive_opt()]}
mod() = {incl_cond, incl_cond()}
| {debug_info, debug_info()}
rel_app() = app_name()
@@ -539,21 +481,18 @@ rel_opt() = {load_dot_erlang, boolean()}
app_name() = atom()
app_type() = permanent | transient | temporary | load | none
app_vsn() = string()
-archive_opt = zip_create_opt()
boot_rel() = rel_name()
app_file() = keep | strip | all
-debug_info() = keep | strip
+debug_info() = keep | strip | [beam_lib:chunkid()]
dir() = string()
escript() = {incl_cond, incl_cond()}
escript_file() = file()
excl_app_filters() = regexps()
-excl_archive_filters() = regexps()
excl_lib() = otp_root
excl_sys_filters() = regexps()
file() = string()
incl_app() = app_name()
incl_app_filters() = regexps()
-incl_archive_filters() = regexps()
incl_cond() = include | exclude | derived
incl_sys_filters() = regexps()
lib_dir() = dir()
@@ -582,7 +521,6 @@ top_file() = file()
target_spec() = [target_spec()]
| {create_dir, base_dir(), [target_spec()]}
| {create_dir, base_dir(), top_dir(), [target_spec()]}
- | {archive, base_file(), [archive_opt()], [target_spec()]}
| {copy_file, base_file()}
| {copy_file, base_file(), top_file()}
| {write_file, base_file(), iolist()}
@@ -632,7 +570,7 @@ target_spec() = [target_spec()]
<c>releases</c> directory contains generated <c>rel</c>,
<c>script</c>, and <c>boot</c> files. The <c>lib</c> directory
contains the applications. Which applications are included
- and if they should be customized (archived, stripped from debug
+ and if they should be customized (stripped from debug
info etc.) is specified with various configuration
parameters. The files in the <c>bin</c> directory are copied
from the <c>erts-vsn/bin</c> directory, but only those files
diff --git a/lib/reltool/doc/src/reltool_examples.xml b/lib/reltool/doc/src/reltool_examples.xml
index 76db47a760..a29403da78 100644
--- a/lib/reltool/doc/src/reltool_examples.xml
+++ b/lib/reltool/doc/src/reltool_examples.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2009</year>
- <year>2022</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -136,9 +136,6 @@ Eshell V9.0 (abort with ^G)
{excl_sys_filters,[]},
{incl_app_filters,[".*"]},
{excl_app_filters,[]},
- {incl_archive_filters,[".*"]},
- {excl_archive_filters,[[...]|...]},
- {archive_opts,[]},
{rel_app_type,...},
{...}|...]}}
6&gt;
@@ -270,9 +267,6 @@ Eshell V9.0 (abort with ^G)
"^erts.*/bin/.*(debug|pdb)"]},
{incl_app_filters,["^ebin","^include","^priv"]},
{excl_app_filters,[]},
- {incl_archive_filters,[".*"]},
- {excl_archive_filters,["^include$","^priv$"]},
- {archive_opts,[]},
{rel_app_type,permanent},
{embedded_app_type,load},
{app_file,keep},
@@ -296,9 +290,6 @@ Eshell V9.0 (abort with ^G)
"^erts.*/bin/.*(debug|pdb)"]},
{incl_app_filters,["^ebin","^priv"]},
{excl_app_filters,["^ebin/.*\\.appup$"]},
- {incl_archive_filters,[".*"]},
- {excl_archive_filters,["^include$","^priv$"]},
- {archive_opts,[]},
{rel_app_type,permanent},
{app_file,keep},
{debug_info,keep}]}}
@@ -492,47 +483,41 @@ Eshell V10.0 (abort with ^G)
{copy_file,...},
{...}]}]},
{create_dir,"lib",
- [{archive,"compiler-7.0.4.ez",[],
- [{create_dir,"compiler-7.0.4",
- [{create_dir,"src",
- [{copy_file,"beam_flatten.erl"},
- {copy_file,[...]},
- {copy_file,...},
- {...}|...]},
- {create_dir,"ebin",
- [{copy_file,[...]},{copy_file,...},{...}|...]}]}]},
- {archive,"crypto-3.7.4.ez",[],
- [{create_dir,"crypto-3.7.4",
- [{create_dir,"src",[{copy_file,[...]},{copy_file,...}]},
- {create_dir,"ebin",[{copy_file,...},{...}|...]}]}]},
+ [{create_dir,"compiler-7.0.4",
+ [{create_dir,"src",
+ [{copy_file,"beam_flatten.erl"},
+ {copy_file,[...]},
+ {copy_file,...},
+ {...}|...]},
+ {create_dir,"ebin",
+ [{copy_file,[...]},{copy_file,...},{...}|...]}]},
+ {create_dir,"crypto-3.7.4",
+ [{create_dir,"src",[{copy_file,[...]},{copy_file,...}]},
+ {create_dir,"ebin",[{copy_file,...},{...}|...]}]},
{create_dir,"crypto-3.7.4",
[{create_dir,"priv",
[{create_dir,"lib",[{copy_file,[...]},{copy_file,...}]},
{create_dir,"obj",[{copy_file,...},{...}|...]}]}]},
- {archive,"erts-10.0.ez",[],
- [{create_dir,"erts-10.0",
- [{create_dir,"src",[{...}|...]},
- {create_dir,"ebin",[...]}]}]},
- {archive,"hipe-3.15.4.ez",[],
- [{create_dir,"hipe-3.15.4",
- [{create_dir,"flow",[...]},
- {copy_file,[...]},
- {create_dir,...},
- {...}|...]}]},
- {archive,"inets-6.3.9.ez",[],
- [{create_dir,"inets-6.3.9",
- [{create_dir,[...],...},{create_dir,...},{...}]}]},
+ {create_dir,"erts-10.0",
+ [{create_dir,"src",[{...}|...]},
+ {create_dir,"ebin",[...]}]},
+ {create_dir,"hipe-3.15.4",
+ [{create_dir,"flow",[...]},
+ {copy_file,[...]},
+ {create_dir,...},
+ {...}|...]},
+ {create_dir,"inets-6.3.9",
+ [{create_dir,[...],...},{create_dir,...},{...}]},
{create_dir,"inets-6.3.9",
[{create_dir,"priv",[{create_dir,[...],...}]},
{create_dir,"include",[{copy_file,...},{...}]}]},
- {archive,"kernel-5.2.ez",[],
- [{create_dir,"kernel-5.2",[{...}|...]}]},
+ {create_dir,"kernel-5.2",[{...}|...]},
{create_dir,"kernel-5.2",
[{create_dir,"include",[{...}|...]}]},
- {archive,"sasl-3.0.3.ez",[],[{create_dir,[...],...}]},
- {archive,"stdlib-3.3.ez",[],[{create_dir,...}]},
+ {create_dir,[...],...},
+ {create_dir,...},
{create_dir,"stdlib-3.3",[{create_dir,...}]},
- {archive,"tools-2.9.1.ez",[],[...]}]}]}
+ ...]}]}
3&gt;
3&gt; TargetDir = "/tmp/my_target_dir".
"/tmp/my_target_dir"
@@ -551,10 +536,10 @@ ok
"releases"]}
8&gt;
8&gt; file:list_dir(filename:join([TargetDir,"lib"])).
-{ok,["tools-2.9.1.ez","kernel-5.2.ez","inets-6.3.9.ez",
- "kernel-5.2","sasl-3.0.3.ez","hipe-3.15.4.ez","inets-6.3.9",
- "crypto-3.7.4","crypto-3.7.4.ez","stdlib-3.3.ez",
- "erts-10.0.ez","stdlib-3.3","compiler-7.0.4.ez"]}
+{ok,["tools-2.9.1","inets-6.3.9",
+ "kernel-5.2","sasl-3.0.3",
+ "crypto-3.7.4","erts-10.0",
+ "stdlib-3.3","compiler-7.0.4"]}
9&gt;
9&gt; file:make_dir("/tmp/yet_another_target_dir").
ok
diff --git a/lib/reltool/doc/src/reltool_intro.xml b/lib/reltool/doc/src/reltool_intro.xml
index 2980ad7977..695d2c21e5 100644
--- a/lib/reltool/doc/src/reltool_intro.xml
+++ b/lib/reltool/doc/src/reltool_intro.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2009</year>
- <year>2016</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -50,7 +50,7 @@
<section>
<title>Prerequisites</title>
- <p>The following prerequisites is required for understanding the material
+ <p>The following prerequisites are required for understanding the material
in the Reltool User's Guide:</p>
<list type="bulleted">
<item>
diff --git a/lib/reltool/src/reltool.app.src b/lib/reltool/src/reltool.app.src
index dc85464750..421ca01ea3 100644
--- a/lib/reltool/src/reltool.app.src
+++ b/lib/reltool/src/reltool.app.src
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -36,6 +36,8 @@
{registered, []},
{applications, [stdlib, kernel]},
{env, []},
- {runtime_dependencies, ["wx-1.2","tools-2.6.14","stdlib-3.4","sasl-2.4",
- "kernel-3.0","erts-7.0"]}
+ {runtime_dependencies,
+ ["wx-@OTP-18350@","tools-2.6.14",
+ "stdlib-@OTP-18490@","stdlib-@OTP-18350@","sasl-@OTP-18350@",
+ "kernel-@OTP-18350@","erts-@OTP-18350@"]}
]}.
diff --git a/lib/reltool/src/reltool.hrl b/lib/reltool/src/reltool.hrl
index 844a3a880a..99f8ee8b38 100644
--- a/lib/reltool/src/reltool.hrl
+++ b/lib/reltool/src/reltool.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
%% derived - Include only those modules that others are dependent on
-type mod_cond() :: all | app | ebin | derived | none.
-type incl_cond() :: include | exclude | derived.
--type debug_info() :: keep | strip.
+-type debug_info() :: keep | strip | [beam_lib:chunkid()].
-type app_file() :: keep | strip | all.
-type re_regexp() :: string(). % re:regexp()
-type regexps() :: [re_regexp()] |
@@ -38,9 +38,6 @@
-type excl_sys_filters() :: regexps().
-type incl_app_filters() :: regexps().
-type excl_app_filters() :: regexps().
--type incl_archive_filters() :: regexps().
--type excl_archive_filters() :: regexps().
--type archive_opt() :: term(). % zip:create()
-type root_dir() :: dir().
-type lib_dir() :: dir().
-type profile() :: development | embedded | standalone.
@@ -72,9 +69,7 @@
| {app_file, app_file()}
| {debug_info, debug_info()}
| {incl_app_filters, incl_app_filters()}
- | {excl_app_filters, excl_app_filters()}
- | {incl_archive_filters, incl_archive_filters()}
- | {excl_archive_filters, excl_archive_filters()}.
+ | {excl_app_filters, excl_app_filters()}.
-type escript() :: {incl_cond, incl_cond()}.
-type sys() :: {mod_cond, mod_cond()}
| {incl_cond, incl_cond()}
@@ -86,9 +81,6 @@
| {excl_sys_filters, excl_sys_filters()}
| {incl_app_filters, incl_app_filters()}
| {excl_app_filters, excl_app_filters()}
- | {incl_archive_filters, incl_archive_filters()}
- | {excl_archive_filters, excl_archive_filters()}
- | {archive_opts, [archive_opt()]}
| {root_dir, root_dir()}
| {lib_dirs, [lib_dir()]}
| {boot_rel, boot_rel()}
@@ -119,7 +111,6 @@
-type target_spec() :: [target_spec()]
| {create_dir, base_dir(), [target_spec()]}
| {create_dir, base_dir(), top_dir(), [target_spec()]}
- | {archive, base_file(), [archive_opt()], [target_spec()]}
| {copy_file, base_file()}
| {copy_file, base_file(), top_file()}
| {write_file, base_file(), binary()}
@@ -198,9 +189,6 @@
app_type :: '_' | app_type() | undefined,
incl_app_filters :: '_' | [#regexp{}] | undefined,
excl_app_filters :: '_' | [#regexp{}] | undefined,
- incl_archive_filters :: '_' | [#regexp{}] | undefined,
- excl_archive_filters :: '_' | [#regexp{}] | undefined,
- archive_opts :: '_' | [archive_opt()] | undefined,
%% Dynamic
status :: '_' | status(),
@@ -247,9 +235,6 @@
excl_sys_filters :: [#regexp{}],
incl_app_filters :: [#regexp{}],
excl_app_filters :: [#regexp{}],
- incl_archive_filters :: [#regexp{}],
- excl_archive_filters :: [#regexp{}],
- archive_opts :: [archive_opt()],
relocatable :: boolean(),
rel_app_type :: app_type(),
embedded_app_type :: app_type() | undefined,
@@ -280,10 +265,6 @@
-define(DEFAULT_APP_FILE, keep).
-define(DEFAULT_DEBUG_INFO, keep).
--define(DEFAULT_INCL_ARCHIVE_FILTERS, [".*"]).
--define(DEFAULT_EXCL_ARCHIVE_FILTERS, ["^include\$", "^priv\$"]).
--define(DEFAULT_ARCHIVE_OPTS, []).
-
-define(DEFAULT_INCL_SYS_FILTERS, [".*"]).
-define(DEFAULT_EXCL_SYS_FILTERS, []).
-define(DEFAULT_INCL_APP_FILTERS, [".*"]).
diff --git a/lib/reltool/src/reltool_server.erl b/lib/reltool/src/reltool_server.erl
index de7d73bf37..e14d171766 100644
--- a/lib/reltool/src/reltool_server.erl
+++ b/lib/reltool/src/reltool_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -129,12 +129,13 @@ gen_spec(Pid) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Server
+-spec init(_) -> no_return().
init([{parent,Parent}|_] = Options) ->
try
do_init(Options)
catch
throw:{error,Reason} ->
- proc_lib:init_ack(Parent,{error,Reason});
+ proc_lib:init_fail(Parent,{error,Reason},{exit,normal});
error:Reason:Stacktrace ->
exit({Reason, Stacktrace})
end.
@@ -196,13 +197,6 @@ default_sys() ->
rel_app_type = ?DEFAULT_REL_APP_TYPE,
embedded_app_type = ?DEFAULT_EMBEDDED_APP_TYPE,
app_file = ?DEFAULT_APP_FILE,
- incl_archive_filters = dec_re(incl_archive_filters,
- ?DEFAULT_INCL_ARCHIVE_FILTERS,
- []),
- excl_archive_filters = dec_re(excl_archive_filters,
- ?DEFAULT_EXCL_ARCHIVE_FILTERS,
- []),
- archive_opts = ?DEFAULT_ARCHIVE_OPTS,
debug_info = ?DEFAULT_DEBUG_INFO}.
dec_re(Key, Regexps, Old) ->
@@ -450,9 +444,6 @@ app_set_config_only([],#app{name = Name,
app_type = undefined,
incl_app_filters = undefined,
excl_app_filters = undefined,
- incl_archive_filters = undefined,
- excl_archive_filters = undefined,
- archive_opts = undefined,
is_escript = false})->
{delete,Name};
app_set_config_only(Mods,#app{name = Name,
@@ -464,9 +455,6 @@ app_set_config_only(Mods,#app{name = Name,
app_type = AppType,
incl_app_filters = InclAppFilters,
excl_app_filters = ExclAppFilters,
- incl_archive_filters = InclArchiveFilters,
- excl_archive_filters = ExclArchiveFilters,
- archive_opts = ArchiveOpts,
vsn = Vsn,
is_escript = IsEscript,
label = Label,
@@ -481,9 +469,6 @@ app_set_config_only(Mods,#app{name = Name,
app_type = AppType,
incl_app_filters = InclAppFilters,
excl_app_filters = ExclAppFilters,
- incl_archive_filters = InclArchiveFilters,
- excl_archive_filters = ExclArchiveFilters,
- archive_opts = ArchiveOpts,
vsn = Vsn,
mods = Mods},
@@ -1563,13 +1548,11 @@ decode(#sys{} = Sys, [{Key, Val} | KeyVals]) ->
Sys#sys{excl_app_filters =
dec_re(Key, Val, Sys#sys.excl_app_filters)};
incl_archive_filters ->
- Sys#sys{incl_archive_filters =
- dec_re(Key, Val, Sys#sys.incl_archive_filters)};
+ io:format("incl_archive_filters is no longer supported in reltool");
excl_archive_filters ->
- Sys#sys{excl_archive_filters =
- dec_re(Key, Val, Sys#sys.excl_archive_filters)};
+ io:format("excl_archive_filters is no longer supported in reltool");
archive_opts when is_list(Val) ->
- Sys#sys{archive_opts = Val};
+ io:format("archive_opts is no longer supported in reltool");
relocatable when Val =:= true; Val =:= false ->
Sys#sys{relocatable = Val};
rel_app_type when Val =:= permanent;
@@ -1587,7 +1570,7 @@ decode(#sys{} = Sys, [{Key, Val} | KeyVals]) ->
Sys#sys{embedded_app_type = Val};
app_file when Val =:= keep; Val =:= strip; Val =:= all ->
Sys#sys{app_file = Val};
- debug_info when Val =:= keep; Val =:= strip ->
+ debug_info when Val =:= keep; Val =:= strip; is_list(Val) ->
Sys#sys{debug_info = Val};
_ ->
reltool_utils:throw_error("Illegal option: ~tp", [{Key, Val}])
@@ -1608,7 +1591,8 @@ decode(#app{} = App, [{Key, Val} | KeyVals]) ->
App#app{incl_cond = Val};
debug_info when Val =:= keep;
- Val =:= strip ->
+ Val =:= strip;
+ is_list(Val) ->
App#app{debug_info = Val};
app_file when Val =:= keep;
Val =:= strip;
@@ -1628,13 +1612,11 @@ decode(#app{} = App, [{Key, Val} | KeyVals]) ->
App#app{excl_app_filters =
dec_re(Key, Val, App#app.excl_app_filters)};
incl_archive_filters ->
- App#app{incl_archive_filters =
- dec_re(Key, Val, App#app.incl_archive_filters)};
+ io:format("incl_archive_filters is no longer supported in reltool");
excl_archive_filters ->
- App#app{excl_archive_filters =
- dec_re(Key, Val, App#app.excl_archive_filters)};
+ io:format("excl_archive_filters is no longer supported in reltool");
archive_opts when is_list(Val) ->
- App#app{archive_opts = Val};
+ io:format("archive_opts is no longer supported in reltool");
vsn when is_list(Val), App#app.use_selected_vsn=:=undefined ->
App#app{use_selected_vsn = vsn, vsn = Val};
lib_dir when is_list(Val), App#app.use_selected_vsn=:=undefined ->
@@ -1663,7 +1645,7 @@ decode(#mod{} = Mod, [{Key, Val} | KeyVals]) ->
case Key of
incl_cond when Val =:= include; Val =:= exclude; Val =:= derived ->
Mod#mod{incl_cond = Val};
- debug_info when Val =:= keep; Val =:= strip ->
+ debug_info when Val =:= keep; Val =:= strip; is_list(Val) ->
Mod#mod{debug_info = Val};
_ ->
reltool_utils:throw_error("Illegal option: ~tp", [{Key, Val}])
diff --git a/lib/reltool/src/reltool_sys_win.erl b/lib/reltool/src/reltool_sys_win.erl
index e24f468f67..7c446c079f 100644
--- a/lib/reltool/src/reltool_sys_win.erl
+++ b/lib/reltool/src/reltool_sys_win.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -183,7 +183,7 @@ do_init([{safe_config, Safe}, {parent, Parent} | Options]) ->
restart_server_safe_config(true,Parent,Reason) ->
io:format("~w(~w): <ERROR> ~tp\n", [?MODULE, ?LINE, Reason]),
- proc_lib:init_ack(Parent, {error,Reason});
+ proc_lib:init_fail(Parent, {error,Reason}, {exit,normal});
restart_server_safe_config(false,Parent,Reason) ->
wx:new(),
Strings =
@@ -200,7 +200,7 @@ restart_server_safe_config(false,Parent,Reason) ->
do_init([{safe_config,true},{parent,Parent},?safe_config]);
?wxID_CANCEL ->
io:format("~w(~w): <ERROR> ~tp\n", [?MODULE, ?LINE, Reason]),
- proc_lib:init_ack(Parent,{error,Reason})
+ proc_lib:init_fail(Parent, {error,Reason}, {exit,normal})
end.
exit_dialog([]) ->
diff --git a/lib/reltool/src/reltool_target.erl b/lib/reltool/src/reltool_target.erl
index 1a0f868be8..561f67c31b 100644
--- a/lib/reltool/src/reltool_target.erl
+++ b/lib/reltool/src/reltool_target.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -81,9 +81,6 @@ do_gen_config(#sys{root_dir = RootDir,
excl_sys_filters = ExclSysFiles,
incl_app_filters = InclAppFiles,
excl_app_filters = ExclAppFiles,
- incl_archive_filters = InclArchiveDirs,
- excl_archive_filters = ExclArchiveDirs,
- archive_opts = ArchiveOpts,
relocatable = Relocatable,
rel_app_type = RelAppType,
embedded_app_type = InclAppType,
@@ -133,9 +130,6 @@ do_gen_config(#sys{root_dir = RootDir,
emit(excl_sys_filters, X(ExclSysFiles), reltool_utils:choose_default(excl_sys_filters, Profile, InclDefs), InclDefs) ++
emit(incl_app_filters, X(InclAppFiles), reltool_utils:choose_default(incl_app_filters, Profile, InclDefs), InclDefs) ++
emit(excl_app_filters, X(ExclAppFiles), reltool_utils:choose_default(excl_app_filters, Profile, InclDefs), InclDefs) ++
- emit(incl_archive_filters, X(InclArchiveDirs), ?DEFAULT_INCL_ARCHIVE_FILTERS, InclDefs) ++
- emit(excl_archive_filters, X(ExclArchiveDirs), ?DEFAULT_EXCL_ARCHIVE_FILTERS, InclDefs) ++
- emit(archive_opts, ArchiveOpts, ?DEFAULT_ARCHIVE_OPTS, InclDefs) ++
emit(rel_app_type, RelAppType, ?DEFAULT_REL_APP_TYPE, InclDefs) ++
emit(embedded_app_type, InclAppType, reltool_utils:choose_default(embedded_app_type, Profile, InclDefs), InclDefs) ++
emit(app_file, AppFile, ?DEFAULT_APP_FILE, InclDefs) ++
@@ -147,9 +141,6 @@ do_gen_config(#app{name = Name,
app_file = AppFile,
incl_app_filters = InclAppFiles,
excl_app_filters = ExclAppFiles,
- incl_archive_filters = InclArchiveDirs,
- excl_archive_filters = ExclArchiveDirs,
- archive_opts = ArchiveOpts,
use_selected_vsn = UseSelected,
vsn = Vsn,
active_dir = ActiveDir,
@@ -164,9 +155,6 @@ do_gen_config(#app{name = Name,
emit(app_file, AppFile, undefined, InclDefs),
emit(incl_app_filters, InclAppFiles, undefined, InclDefs),
emit(excl_app_filters, ExclAppFiles, undefined, InclDefs),
- emit(incl_archive_filters, InclArchiveDirs, undefined, InclDefs),
- emit(excl_archive_filters, ExclArchiveDirs, undefined, InclDefs),
- emit(archive_opts, ArchiveOpts, undefined, InclDefs),
if
IsIncl, InclDefs -> [{vsn, Vsn}, {lib_dir, ActiveDir}];
UseSelected =:= vsn -> [{vsn, Vsn}];
@@ -1075,12 +1063,14 @@ check_apps([Mandatory | Names], Apps) ->
check_apps([], _) ->
ok.
-spec_app(#app{name = Name,
+spec_app(#app{label = Label,
+ name = Name,
mods = Mods,
active_dir = SourceDir,
incl_app_filters = AppInclRegexps,
excl_app_filters = AppExclRegexps} = App,
- #sys{incl_app_filters = SysInclRegexps,
+ #sys{root_dir = RootDir,
+ incl_app_filters = SysInclRegexps,
excl_app_filters = SysExclRegexps,
debug_info = SysDebugInfo} = Sys) ->
%% List files recursively
@@ -1104,47 +1094,7 @@ spec_app(#app{name = Name,
ExclRegexps = reltool_utils:default_val(AppExclRegexps, SysExclRegexps),
AppFiles3 = filter_spec(AppFiles2, InclRegexps, ExclRegexps),
- %% Regular top directory and/or archive
- spec_archive(App, Sys, AppFiles3).
-
-spec_archive(#app{label = Label,
- active_dir = SourceDir,
- incl_archive_filters = AppInclArchiveDirs,
- excl_archive_filters = AppExclArchiveDirs,
- archive_opts = AppArchiveOpts},
- #sys{root_dir = RootDir,
- incl_archive_filters = SysInclArchiveDirs,
- excl_archive_filters = SysExclArchiveDirs,
- archive_opts = SysArchiveOpts},
- Files) ->
- InclArchiveDirs =
- reltool_utils:default_val(AppInclArchiveDirs, SysInclArchiveDirs),
- ExclArchiveDirs =
- reltool_utils:default_val(AppExclArchiveDirs, SysExclArchiveDirs),
- ArchiveOpts =
- reltool_utils:default_val(AppArchiveOpts, SysArchiveOpts),
- Match = fun(F) -> match(element(2, F), InclArchiveDirs, ExclArchiveDirs) end,
- case lists:filter(Match, Files) of
- [] ->
- %% Nothing to archive
- [spec_create_dir(RootDir, SourceDir, Label, Files)];
- ArchiveFiles ->
- OptDir =
- case Files -- ArchiveFiles of
- [] ->
- [];
- ExternalFiles ->
- [spec_create_dir(RootDir,
- SourceDir,
- Label,
- ExternalFiles)]
- end,
- ArchiveOpts =
- reltool_utils:default_val(AppArchiveOpts, SysArchiveOpts),
- ArchiveDir =
- spec_create_dir(RootDir, SourceDir, Label, ArchiveFiles),
- [{archive, Label ++ ".ez", ArchiveOpts, [ArchiveDir]} | OptDir]
- end.
+ [spec_create_dir(RootDir, SourceDir, Label, AppFiles3)].
spec_dir(Dir) ->
Base = filename:basename(Dir),
@@ -1172,7 +1122,9 @@ spec_mod(Mod, DebugInfo) ->
keep ->
{copy_file, File};
strip ->
- {strip_beam, File}
+ {strip_beam, File, []};
+ ChunkIds ->
+ {strip_beam, File, ChunkIds}
end.
spec_app_file(#app{name = Name,
@@ -1277,27 +1229,6 @@ do_eval_spec({create_dir, Dir, OldDir, Files},
TargetDir2 = filename:join([TargetDir, Dir]),
reltool_utils:create_dir(TargetDir2),
do_eval_spec(Files, SourceDir2, SourceDir2, TargetDir2);
-do_eval_spec({archive, Archive, Options, Files},
- OrigSourceDir,
- SourceDir,
- TargetDir) ->
- TmpSpec = {create_dir, "tmp", Files},
- TmpDir = filename:join([TargetDir, "tmp"]),
- reltool_utils:create_dir(TmpDir),
- do_eval_spec(Files, OrigSourceDir, SourceDir, TmpDir),
-
- ArchiveFile = filename:join([TargetDir, Archive]),
- Files2 = [element(2, F) || F <- Files],
- Res = zip:create(ArchiveFile, Files2, [{cwd, TmpDir} | Options]),
-
- cleanup_spec(TmpSpec, TargetDir),
- case Res of
- {ok, _} ->
- ok;
- {error, Reason} ->
- reltool_utils:throw_error("create archive ~ts failed: ~tp",
- [ArchiveFile, Reason])
- end;
do_eval_spec({copy_file, File}, _OrigSourceDir, SourceDir, TargetDir) ->
SourceFile = filename:join([SourceDir, File]),
TargetFile = filename:join([TargetDir, File]),
@@ -1315,11 +1246,14 @@ do_eval_spec({write_file, File, Bin},
TargetDir) ->
TargetFile = filename:join([TargetDir, File]),
reltool_utils:write_file(TargetFile, Bin);
-do_eval_spec({strip_beam, File}, _OrigSourceDir, SourceDir, TargetDir) ->
+do_eval_spec({strip_beam, File, ChunkIds},
+ _OrigSourceDir,
+ SourceDir,
+ TargetDir) ->
SourceFile = filename:join([SourceDir, File]),
TargetFile = filename:join([TargetDir, File]),
BeamBin = reltool_utils:read_file(SourceFile),
- {ok, {_, BeamBin2}} = beam_lib:strip(BeamBin),
+ {ok, {_, BeamBin2}} = beam_lib:strip(BeamBin, ChunkIds),
reltool_utils:write_file(TargetFile, BeamBin2).
cleanup_spec(List, TargetDir) when is_list(List) ->
@@ -1334,12 +1268,6 @@ cleanup_spec({create_dir, Dir, _OldDir, Files}, TargetDir) ->
TargetDir2 = filename:join([TargetDir, Dir]),
cleanup_spec(Files, TargetDir2),
file:del_dir(TargetDir2);
-cleanup_spec({archive, Archive, _Options, Files}, TargetDir) ->
- TargetFile = filename:join([TargetDir, Archive]),
- file:delete(TargetFile),
- TmpDir = filename:join([TargetDir, "tmp"]),
- cleanup_spec(Files, TmpDir),
- file:del_dir(TmpDir);
cleanup_spec({copy_file, File}, TargetDir) ->
TargetFile = filename:join([TargetDir, File]),
file:delete(TargetFile);
@@ -1349,7 +1277,7 @@ cleanup_spec({copy_file, NewFile, _OldFile}, TargetDir) ->
cleanup_spec({write_file, File, _}, TargetDir) ->
TargetFile = filename:join([TargetDir, File]),
file:delete(TargetFile);
-cleanup_spec({strip_beam, File}, TargetDir) ->
+cleanup_spec({strip_beam, File, _ChunkIds}, TargetDir) ->
TargetFile = filename:join([TargetDir, File]),
file:delete(TargetFile).
@@ -1392,21 +1320,6 @@ do_filter_spec(Path,
Files2 when is_list(Files2) ->
{true, {create_dir, NewDir, OldDir, Files2}}
end;
-do_filter_spec(Path,
- {archive, Archive, Options, Files},
- InclRegexps,
- ExclRegexps) ->
- case do_filter_spec(Path, Files, InclRegexps, ExclRegexps) of
- [] ->
- case match(Path, InclRegexps, ExclRegexps) of
- true ->
- {true, {archive, Archive, Options, []}};
- false ->
- false
- end;
- Files2 when is_list(Files2) ->
- {true, {archive, Archive, Options, Files2}}
- end;
do_filter_spec(Path, {copy_file, File}, InclRegexps, ExclRegexps) ->
Path2 = opt_join(Path, File),
match(Path2, InclRegexps, ExclRegexps);
@@ -1419,7 +1332,7 @@ do_filter_spec(Path,
do_filter_spec(Path, {write_file, File, _}, InclRegexps, ExclRegexps) ->
Path2 = opt_join(Path, File),
match(Path2, InclRegexps, ExclRegexps);
-do_filter_spec(Path, {strip_beam, File}, InclRegexps, ExclRegexps) ->
+do_filter_spec(Path, {strip_beam, File, _ChunkIds}, InclRegexps, ExclRegexps) ->
Path2 = opt_join(Path, File),
match(Path2, InclRegexps, ExclRegexps).
diff --git a/lib/reltool/test/reltool_server_SUITE.erl b/lib/reltool/test/reltool_server_SUITE.erl
index 11ae6d6da2..a7125aa779 100644
--- a/lib/reltool/test/reltool_server_SUITE.erl
+++ b/lib/reltool/test/reltool_server_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -281,9 +281,6 @@ get_config(_Config) ->
{excl_sys_filters,[]},
{incl_app_filters,[".*"]},
{excl_app_filters,[]},
- {incl_archive_filters,[".*"]},
- {excl_archive_filters,["^include$","^priv$"]},
- {archive_opts,[]},
{rel_app_type,permanent},
{app_file,keep},
{debug_info,keep}]}},
@@ -312,9 +309,6 @@ get_config(_Config) ->
{excl_sys_filters,[]},
{incl_app_filters,[".*"]},
{excl_app_filters,[]},
- {incl_archive_filters,[".*"]},
- {excl_archive_filters,["^include$","^priv$"]},
- {archive_opts,[]},
{rel_app_type,permanent},
{app_file,keep},
{debug_info,keep}]}},
@@ -1252,7 +1246,7 @@ create_slim(Config) ->
TargetRelDir = filename:join(TargetDir,"releases"),
TargetRelVsnDir = filename:join(TargetRelDir,RelVsn),
- {ok,["a-1.0.ez"]} = file:list_dir(TargetLibDir),
+ {ok,["a-1.0"]} = file:list_dir(TargetLibDir),
RootDir = code:root_dir(),
Erl = filename:join([RootDir, "bin", "erl"]),
@@ -1335,16 +1329,14 @@ otp_9229_dupl_mod_exclude_app(Config) ->
{ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl)),
AbsTargetDir = filename:absname(TargetDir),
- XArchive = "x-1.0.ez",
- AbsXArchive = filename:join([AbsTargetDir,lib,XArchive]),
- XEbin = ["ebin","x-1.0",XArchive],
- YArchive = "y-1.0.ez",
- AbsYArchive = filename:join([AbsTargetDir,lib,YArchive]),
+ AbsX = filename:join([AbsTargetDir,lib,"x-1.0"]),
+ XEbin = ["ebin","x-1.0"],
+ AbsY = filename:join([AbsTargetDir,lib,"y-1.0"]),
- ?m(true, filelib:is_file(AbsXArchive)),
+ ?m(true, filelib:is_file(AbsX)),
?m(XEbin, mod_path(Node,x)),
?m(XEbin, mod_path(Node,mylib)),
- ?m(false, filelib:is_file(AbsYArchive)),
+ ?m(false, filelib:is_file(AbsY)),
?m(non_existing, mod_path(Node,y)),
?msym(ok, stop_node(Node)),
@@ -1382,17 +1374,15 @@ otp_9229_dupl_mod_exclude_mod(Config) ->
{ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl)),
AbsTargetDir = filename:absname(TargetDir),
- XArchive = "x-1.0.ez",
- AbsXArchive = filename:join([AbsTargetDir,lib,XArchive]),
- XEbin = ["ebin","x-1.0",XArchive],
- YArchive = "y-1.0.ez",
- AbsYArchive = filename:join([AbsTargetDir,lib,YArchive]),
- YEbin = ["ebin","y-1.0",YArchive],
-
- ?m(true, filelib:is_file(AbsXArchive)),
+ AbsX = filename:join([AbsTargetDir,lib,"x-1.0"]),
+ XEbin = ["ebin","x-1.0"],
+ AbsY = filename:join([AbsTargetDir,lib,"y-1.0"]),
+ YEbin = ["ebin","y-1.0"],
+
+ ?m(true, filelib:is_file(AbsX)),
?m(XEbin, mod_path(Node,x)),
?m(XEbin, mod_path(Node,mylib)),
- ?m(true, filelib:is_file(AbsYArchive)),
+ ?m(true, filelib:is_file(AbsY)),
?m(YEbin, mod_path(Node,y)),
%% Remove path to XEbin and check that mylib is not located in YEbin
@@ -2197,9 +2187,6 @@ save_config(Config) ->
{excl_sys_filters,[]},
{incl_app_filters,[".*"]},
{excl_app_filters,[]},
- {incl_archive_filters,[".*"]},
- {excl_archive_filters,["^include$","^priv$"]},
- {archive_opts,[]},
{rel_app_type,permanent},
{app_file,keep},
{debug_info,keep}]}]},
@@ -2238,9 +2225,6 @@ save_config(Config) ->
{excl_sys_filters,[]},
{incl_app_filters,[".*"]},
{excl_app_filters,[]},
- {incl_archive_filters,[".*"]},
- {excl_archive_filters,["^include$","^priv$"]},
- {archive_opts,[]},
{rel_app_type,permanent},
{app_file,keep},
{debug_info,keep}]}]},
@@ -2439,7 +2423,6 @@ dep_in_app_not_xref(Config) ->
[
{lib_dirs,[filename:join(datadir(Config),"dep_in_app_not_xref")]},
{incl_cond,exclude},
- {incl_archive_filters,[]},
{erts,[{incl_cond,exclude}]},
{boot_rel, RelName},
{rel, RelName, RelVsn, [kernel, stdlib]},
diff --git a/lib/runtime_tools/doc/src/Makefile b/lib/runtime_tools/doc/src/Makefile
index bed1358730..fb741bf733 100644
--- a/lib/runtime_tools/doc/src/Makefile
+++ b/lib/runtime_tools/doc/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2021. All Rights Reserved.
+# Copyright Ericsson AB 1999-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -39,14 +39,14 @@ XML_APPLICATION_FILES = ref_man.xml
XML_REF3_FILES = \
dbg.xml \
dyntrace.xml \
- erts_alloc_config.xml \
+ instrument.xml \
system_information.xml \
msacc.xml \
scheduler.xml
XML_REF6_FILES = runtime_tools_app.xml
XML_PART_FILES = part.xml
-XML_CHAPTER_FILES = notes.xml LTTng.xml
+XML_CHAPTER_FILES = notes.xml LTTng.xml erts_alloc_config.xml
GENERATED_XML_FILES = DTRACE.xml SYSTEMTAP.xml
diff --git a/lib/runtime_tools/doc/src/dbg.xml b/lib/runtime_tools/doc/src/dbg.xml
index ba7641ffa9..5d9d7486d4 100644
--- a/lib/runtime_tools/doc/src/dbg.xml
+++ b/lib/runtime_tools/doc/src/dbg.xml
@@ -821,7 +821,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<name since="">tracer(Type, Data) -> {ok, pid()} | {error, Error}</name>
<fsummary>Start a tracer server with additional parameters</fsummary>
<type>
- <v>Type = port | process | module</v>
+ <v>Type = port | process | module | file</v>
<v>Data = PortGenerator | HandlerSpec | ModuleSpec</v>
<v>PortGenerator = fun() (no arguments)</v>
<v>Error = term()</v>
@@ -862,6 +862,9 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
be either a tuple describing the <seeerl marker="erts:erl_tracer"><c>erl_tracer</c></seeerl>
module to be used for tracing and the state to be used for
that tracer module or a fun returning the same tuple.</p>
+ <p>if <c>Type</c> is <c>file</c>, then the second parameter
+ should be a filename specifying a file where all the traces
+ are printed.</p>
<p>If an error is returned, it can either be due to a tracer
server already running (<c>{error,already_started}</c>) or
due to the <c>HandlerFun</c> throwing an exception.
diff --git a/lib/runtime_tools/doc/src/erts_alloc_config.xml b/lib/runtime_tools/doc/src/erts_alloc_config.xml
index af41d64881..b5d5dfaff2 100644
--- a/lib/runtime_tools/doc/src/erts_alloc_config.xml
+++ b/lib/runtime_tools/doc/src/erts_alloc_config.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE erlref SYSTEM "erlref.dtd">
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
-<erlref>
+<chapter>
<header>
<copyright>
- <year>2007</year><year>2022</year>
+ <year>2007</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -25,188 +25,17 @@
<title>erts_alloc_config</title>
<prepared>Rickard Green</prepared>
<docno>1</docno>
- <date>07-05-30</date>
- <rev>1</rev>
- <file>erts_alloc_config.sgml</file>
+ <date>23-04-06</date>
+ <rev>2</rev>
+ <file>erts_alloc_config.xml</file>
</header>
- <module since="">erts_alloc_config</module>
- <modulesummary>Configuration tool for erts_alloc</modulesummary>
- <description>
- <warning>
- <p>This (experimental) tool no longer produces good configurations and
- cannot be fixed in a reasonably backwards compatible manner. It has
- therefore been scheduled for removal in <c>OTP 26.0</c>.</p>
- </warning>
- <p><seecref marker="erts:erts_alloc">erts_alloc(3)</seecref> is an
- Erlang Run-Time System internal memory allocator library.
- <c>erts_alloc_config</c> is intended to be used to aid creation
- of an <seecref marker="erts:erts_alloc">erts_alloc(3)</seecref>
- configuration that is suitable for a limited number of runtime
- scenarios. The configuration that <c>erts_alloc_config</c>
- produce is intended as a suggestion, and may need to be
- adjusted manually.</p>
- <p>The configuration is created based on information about a number
- of runtime scenarios. It is obviously impossible to foresee every
- runtime scenario that can occur. The important scenarios are
- those that cause maximum or minimum load on specific memory
- allocators. Load in this context is total size of memory blocks
- allocated.</p>
- <p>The current implementation of <c>erts_alloc_config</c> concentrate
- on configuration of multi-block carriers. Information gathered
- when a runtime scenario is saved is mainly current and maximum use
- of multi-block carriers. If a parameter that change the use of
- multi-block carriers is changed, a previously generated
- configuration is invalid and <c>erts_alloc_config</c> needs
- to be run again. It is mainly the single block carrier threshold
- that effects the use of multi-block carriers, but other
- single-block carrier parameters might as well. If another value of
- a single block carrier parameter than the default is desired, use
- the desired value when running <c>erts_alloc_config</c>.</p>
- <p>A configuration is created in the following way:</p>
- <list type="bulleted">
- <item>
- <p>Pass the <seecref marker="erts:erts_alloc#Mea">+Mea config</seecref>
- command-line flag to the Erlang runtime system you are going
- to use for creation of the allocator configuration. It will
- disable features that prevent <c>erts_alloc_config</c> from
- doing its job. Note, you should <em>not</em> use this flag
- when using the created configuration. Also note that it is
- important that you use the same
- <seecom marker="erts:erl#+S">amount of schedulers</seecom>
- when creating the configuration as you are going the use on
- the system using the configuration.</p>
- </item>
- <item>
- <p>Run your applications with different scenarios (the more
- the better) and save information about each scenario by calling
- <seemfa marker="#save_scenario/0">save_scenario/0</seemfa>.
- It may be hard to know when the applications are at an (for
- <c>erts_alloc_config</c>) important runtime scenario. A good
- approach may therefore be to call
- <seemfa marker="#save_scenario/0">save_scenario/0</seemfa>
- repeatedly, e.g. once every tenth second. Note that it is
- important that your applications reach the runtime scenarios
- that are important for <c>erts_alloc_config</c> when you are
- saving scenarios; otherwise, the configuration may perform
- bad.</p>
- </item>
- <item>
- <p>When you have covered all scenarios, call
- <seemfa marker="#make_config/1">make_config/1</seemfa>
- in order to create a configuration. The configuration is
- written to a file that you have chosen. This configuration
- file can later be read by an Erlang runtime-system at
- startup. Pass the command line argument
- <seecom marker="erts:erl#args_file">-args_file FileName</seecom>
- to the <seecom marker="erts:erl">erl(1)</seecom> command.</p>
- </item>
- <item>
- <p>The configuration produced by <c>erts_alloc_config</c> may
- need to be manually adjusted as already stated. Do not modify the
- file produced by <c>erts_alloc_config</c>; instead, put your
- modifications in another file and load this file after the
- file produced by <c>erts_alloc_config</c>. That is, put the
- <seecom marker="erts:erl#args_file">-args_file FileName</seecom>
- argument that reads your modification file later on the
- command-line than the
- <seecom marker="erts:erl#args_file">-args_file FileName</seecom>
- argument that reads the configuration file produced by
- <c>erts_alloc_config</c>. If a memory allocation parameter
- appear multiple times, the last version of will be used, i.e.,
- you can override parameters in the configuration file produced
- by <c>erts_alloc_config</c>. Doing it this way simplifies
- things when you want to rerun <c>erts_alloc_config</c>.</p>
- </item>
- </list>
- <note>
- <p>The configuration created by <c>erts_alloc_config</c> may
- perform bad, ever horrible, for runtime scenarios that are very
- different from the ones saved when creating the
- configuration. You are, therefore, advised to rerun
- <c>erts_alloc_config</c> if the applications run when the
- configuration was made are changed, or if the load on the
- applications have changed since the configuration was made. You
- are also advised to rerun <c>erts_alloc_config</c> if the Erlang
- runtime system used is changed.</p>
- </note>
- <p><c>erts_alloc_config</c> saves information about runtime scenarios
- and performs computations in a server that is automatically
- started. The server register itself under the name
- <c>'__erts_alloc_config__'</c>.</p>
- </description>
- <funcs>
- <func>
- <name since="">save_scenario() -> ok | {error, Error}</name>
- <fsummary>Saves information about current runtime scenario</fsummary>
- <type>
- <v>Error = term()</v>
- </type>
- <desc>
- <p><c>save_scenario/0</c> saves information about the current
- runtime scenario. This information will later be used when
- <seemfa marker="#make_config/0">make_config/0</seemfa>,
- or <seemfa marker="#make_config/1">make_config/1</seemfa>
- is called.</p>
- <p>The first time <c>save_scenario/0</c> is called a server
- will be started. This server will save runtime scenarios. All
- saved scenarios can be removed by calling
- <seemfa marker="#make_config/0">stop/0</seemfa>.</p>
- </desc>
- </func>
- <func>
- <name since="">make_config() -> ok | {error, Error}</name>
- <fsummary>Creates an erts_alloc configuration</fsummary>
- <type>
- <v>Error = term()</v>
- </type>
- <desc>
- <p>This is the same as calling
- <seemfa marker="#make_config/1">make_config(group_leader())</seemfa>.</p>
- </desc>
- </func>
- <func>
- <name since="">make_config(FileNameOrIODev) -> ok | {error, Error}</name>
- <fsummary>Creates an erts_alloc configuration</fsummary>
- <type>
- <v>FileNameOrIODev = string() | io_device()</v>
- <v>Error = term()</v>
- </type>
- <desc>
- <p><c>make_config/1</c> uses the information previously saved by
- <seemfa marker="#save_scenario/0">save_scenario/0</seemfa>
- in order to produce an <c>erts_alloc</c> configuration. At
- least one scenario have had to be saved. All scenarios
- previously saved will be used when creating the
- configuration. </p>
- <p>If <c>FileNameOrIODev</c> is a <c>string()</c>,
- <c>make_config/1</c> will use <c>FileNameOrIODev</c> as a
- filename. A file named <c>FileNameOrIODev</c> is created and
- the configuration will be written to that file. If
- <c>FileNameOrIODev</c> is an
- <seeerl marker="stdlib:io">io_device()</seeerl> (see the
- documentation of the module
- <seeerl marker="stdlib:io">io</seeerl>), the configuration
- will be written to the io device.</p>
- </desc>
- </func>
- <func>
- <name since="">stop() -> ok | {error, Error}</name>
- <fsummary></fsummary>
- <type>
- <v>Error = term()</v>
- </type>
- <desc>
- <p>Stops the server that saves runtime scenarios.</p>
- </desc>
- </func>
- </funcs>
-
- <section>
- <title>See Also</title>
- <p><seecref marker="erts:erts_alloc">erts_alloc(3)</seecref>,
- <seecom marker="erts:erl">erl(1)</seecom>,
- <seeerl marker="stdlib:io">io(3)</seeerl></p>
+ <section><title>Module Removed</title>
+ <warning>
+ <p>This (experimental) tool no longer produced good configurations and
+ cannot be fixed in a reasonably backwards compatible manner. It has
+ therefore as of OTP 26.0 been removed.</p>
+ </warning>
</section>
-</erlref>
+</chapter>
diff --git a/lib/tools/doc/src/instrument.xml b/lib/runtime_tools/doc/src/instrument.xml
index 80a5e8c3ba..5acdb13dd4 100644
--- a/lib/tools/doc/src/instrument.xml
+++ b/lib/runtime_tools/doc/src/instrument.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1998</year><year>2022</year>
+ <year>1998</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/runtime_tools/doc/src/part.xml b/lib/runtime_tools/doc/src/part.xml
index 34acf69fc8..9b92ab0bfd 100644
--- a/lib/runtime_tools/doc/src/part.xml
+++ b/lib/runtime_tools/doc/src/part.xml
@@ -4,7 +4,7 @@
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2012</year><year>2016</year>
+ <year>2012</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -37,6 +37,7 @@
<xi:include href="LTTng.xml"/>
<xi:include href="DTRACE.xml"/>
<xi:include href="SYSTEMTAP.xml"/>
+ <xi:include href="erts_alloc_config.xml"/>
</part>
diff --git a/lib/runtime_tools/doc/src/ref_man.xml b/lib/runtime_tools/doc/src/ref_man.xml
index fdca65422d..7472956f2b 100644
--- a/lib/runtime_tools/doc/src/ref_man.xml
+++ b/lib/runtime_tools/doc/src/ref_man.xml
@@ -4,7 +4,7 @@
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>1999</year><year>2018</year>
+ <year>1999</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -35,7 +35,7 @@
<xi:include href="runtime_tools_app.xml"/>
<xi:include href="dbg.xml"/>
<xi:include href="dyntrace.xml"/>
- <xi:include href="erts_alloc_config.xml"/>
+ <xi:include href="instrument.xml"/>
<xi:include href="msacc.xml"/>
<xi:include href="scheduler.xml"/>
<xi:include href="system_information.xml"/>
diff --git a/lib/runtime_tools/doc/src/specs.xml b/lib/runtime_tools/doc/src/specs.xml
index 33fe7fa370..f3152d0f0c 100644
--- a/lib/runtime_tools/doc/src/specs.xml
+++ b/lib/runtime_tools/doc/src/specs.xml
@@ -3,4 +3,5 @@
<xi:include href="../specs/specs_system_information.xml"/>
<xi:include href="../specs/specs_msacc.xml"/>
<xi:include href="../specs/specs_scheduler.xml"/>
+ <xi:include href="../specs/specs_instrument.xml"/>
</specs>
diff --git a/lib/runtime_tools/src/Makefile b/lib/runtime_tools/src/Makefile
index 8e8c4074f5..9a80ca48f2 100644
--- a/lib/runtime_tools/src/Makefile
+++ b/lib/runtime_tools/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2022. All Rights Reserved.
+# Copyright Ericsson AB 1999-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/runtime_tools-$(VSN)
MODULES= \
appmon_info \
erts_alloc_config \
+ instrument \
runtime_tools \
runtime_tools_sup \
dbg \
diff --git a/lib/runtime_tools/src/dbg.erl b/lib/runtime_tools/src/dbg.erl
index d5be9ac6a9..8cb65000ec 100644
--- a/lib/runtime_tools/src/dbg.erl
+++ b/lib/runtime_tools/src/dbg.erl
@@ -41,6 +41,7 @@
match_front/2, match_rear/2,
match_0_9/1]).
+-deprecated([{stop_clear,0, "use dbg:stop/0 instead"}]).
%%% Shell callable utility
fun2ms(ShellFun) when is_function(ShellFun) ->
@@ -317,8 +318,17 @@ tracer(process, {Handler,HandlerData}) ->
tracer(module, Fun) when is_function(Fun) ->
start(Fun);
tracer(module, {Module, State}) ->
- start(fun() -> {Module, State} end).
-
+ start(fun() -> {Module, State} end);
+
+tracer(file, Filename) ->
+ tracer(process,
+ {fun F(E, undefined) ->
+ {ok, D} = file:open(Filename, [write]),
+ F(E, D);
+ F(E, D) ->
+ dhandler(E, D),
+ D
+ end, undefined}).
remote_tracer(port, Fun) when is_function(Fun) ->
remote_start(Fun);
@@ -573,14 +583,14 @@ c(M, F, A, Flags) ->
Mref = erlang:monitor(process, Pid),
receive
{'DOWN', Mref, _, _, Reason} ->
- stop_clear(),
+ stop(),
{error, Reason};
{Pid, Res} ->
erlang:demonitor(Mref, [flush]),
%% 'sleep' prevents the tracer (recv_all_traces) from
%% receiving garbage {'EXIT',...} when dbg i stopped.
timer:sleep(1),
- stop_clear(),
+ stop(),
Res
end
end.
@@ -1599,14 +1609,14 @@ new_pattern_table() ->
term_to_binary(x)}),
ets:insert(PT,
{c,
- term_to_binary([{'_',[],[{message,{caller}}]}])}),
+ term_to_binary([{'_',[],[{message,{caller_line}}]}])}),
ets:insert(PT,
{caller_trace,
term_to_binary(c)}),
ets:insert(PT,
{cx,
term_to_binary([{'_',[],[{exception_trace},
- {message,{caller}}]}])}),
+ {message,{caller_line}}]}])}),
ets:insert(PT,
{caller_exception_trace,
term_to_binary(cx)}),
@@ -1944,6 +1954,6 @@ h(stop) ->
h(stop_clear) ->
help_display(
["stop_clear() -> ok",
- " - Stops the dbg server and the tracing of all processes,",
+ " - Deprecated. Stops the dbg server and the tracing of all processes,",
" and clears all trace patterns."]).
diff --git a/lib/runtime_tools/src/dyntrace.erl b/lib/runtime_tools/src/dyntrace.erl
index d0861669e7..65a85ddb01 100644
--- a/lib/runtime_tools/src/dyntrace.erl
+++ b/lib/runtime_tools/src/dyntrace.erl
@@ -1,3 +1,23 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%%
-module(dyntrace).
%%% @doc The Dynamic tracing interface module
@@ -100,7 +120,9 @@ on_load() ->
filename:join([PrivDir, "lib",
erlang:system_info(system_architecture)]),
Candidate =
- filelib:wildcard(filename:join([ArchLibDir,LibName ++ "*" ])),
+ filelib:wildcard(
+ filename:join([ArchLibDir,LibName ++ "*" ]),
+ erl_prim_loader),
case Candidate of
[] -> Error1;
_ ->
diff --git a/lib/runtime_tools/src/erts_alloc_config.erl b/lib/runtime_tools/src/erts_alloc_config.erl
index 18587b0475..e6ac47fa8f 100644
--- a/lib/runtime_tools/src/erts_alloc_config.erl
+++ b/lib/runtime_tools/src/erts_alloc_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,735 +20,6 @@
%% %CopyrightEnd%
%%
-%%%-------------------------------------------------------------------
-%%% File : erts_alloc_config.erl
-%%% Author : Rickard Green
-%%% Description : Generate an erts_alloc configuration suitable for
-%%% a limited amount of runtime scenarios.
-%%%
-%%% Created : 9 May 2007 by Rickard Green
-%%%-------------------------------------------------------------------
-
-module(erts_alloc_config).
--deprecated({'_','_', "this module will be removed in OTP 26.0. See the "
- "documentation for details"}).
-
--record(state, {have_scenario = false,
- alloc}).
-
-
--record(alloc, {name,
- enabled,
- need_config_change,
- alloc_util,
- instances,
- strategy,
- acul,
- low_mbc_blocks_size,
- high_mbc_blocks_size,
- sbct,
- segments}).
-
--record(conf,
- {segments,
- format_to}).
-
--record(segment, {size,number}).
-
--define(PRINT_WITDH, 76).
-
--define(SERVER, '__erts_alloc_config__').
-
--define(KB, 1024).
--define(MB, 1048576).
-
--define(B2KB(B), ((((B) - 1) div ?KB) + 1)).
--define(ROUNDUP(V, R), ((((V) - 1) div (R)) + 1)*(R)).
-
--define(LARGE_GROWTH_ABS_LIMIT, 20*?MB).
--define(MBC_MSEG_LIMIT, 150).
--define(FRAG_FACT, 1.25).
--define(GROWTH_SEG_FACT, 2).
--define(MIN_SEG_SIZE, 1*?MB).
--define(SMALL_GROWTH_SEGS, 5).
-
--define(ALLOC_UTIL_ALLOCATOR(A),
- A == binary_alloc;
- A == std_alloc;
- A == ets_alloc;
- A == fix_alloc;
- A == eheap_alloc;
- A == ll_alloc;
- A == sl_alloc;
- A == temp_alloc;
- A == driver_alloc).
-
--define(ALLOCATORS,
- [binary_alloc,
- ets_alloc,
- eheap_alloc,
- fix_alloc,
- ll_alloc,
- mseg_alloc,
- sl_alloc,
- std_alloc,
- sys_alloc,
- temp_alloc,
- driver_alloc]).
-
--define(MMBCS_DEFAULTS,
- [{binary_alloc, 131072},
- {std_alloc, 131072},
- {ets_alloc, 131072},
- {fix_alloc, 131072},
- {eheap_alloc, 524288},
- {ll_alloc, 131072},
- {sl_alloc, 131072},
- {temp_alloc, 131072},
- {driver_alloc, 131072}]).
-
-%%%
-%%% Exported interface
-%%%
-
--export([save_scenario/0,
- make_config/0,
- make_config/1,
- stop/0]).
-
-%% Test and debug export
--export([state/0]).
-
-
-save_scenario() ->
- req(save_scenario).
-
-make_config() ->
- make_config(group_leader()).
-
-make_config(FileName) when is_list(FileName) ->
- case file:open(FileName, [write]) of
- {ok, IODev} ->
- Res = req({make_config, IODev}),
- ok = file:close(IODev),
- Res;
- Error ->
- Error
- end;
-make_config(IODev) ->
- req({make_config, IODev}).
-
-stop() ->
- req(stop).
-
-
-%% state() is intentionally undocumented, and is for testing
-%% and debugging only...
-
-state() ->
- req(state).
-
-%%%
-%%% Server
-%%%
-
-req(Req) ->
- Ref = make_ref(),
- ReqMsg = {request, self(), Ref, Req},
- req(ReqMsg, Ref, true).
-
-req(ReqMsg, Ref, TryStart) ->
- req(ReqMsg, Ref, TryStart, erlang:monitor(process, ?SERVER)).
-
-req(ReqMsg, Ref, TryStart, Mon) ->
- (catch ?SERVER ! ReqMsg),
- receive
- {response, Ref, Res} ->
- erlang:demonitor(Mon, [flush]),
- Res;
- {'DOWN', Mon, _, _, noproc} ->
- case TryStart of
- true -> start_server(Ref, ReqMsg);
- false -> {error, server_died}
- end;
- {'DOWN', Mon, _, _, Reason} ->
- {error, Reason}
- end.
-
-start_server(Ref, ReqMsg) ->
- Starter = self(),
- Pid = spawn(fun () ->
- register(?SERVER, self()),
- Starter ! {Ref, self(), started},
- server_loop(make_state())
- end),
- Mon = erlang:monitor(process, Pid),
- receive
- {Ref, Pid, started} ->
- req(ReqMsg, Ref, false, Mon);
- {'DOWN', Mon, _, _, _} ->
- req(ReqMsg, Ref, false)
- end.
-
-server_loop(State) ->
- NewState = receive
- {request, From, Ref, save_scenario} ->
- Alloc = save_scenario(State#state.alloc),
- From ! {response, Ref, ok},
- State#state{alloc = Alloc, have_scenario = true};
- {request, From, Ref, {make_config, IODev}} ->
- case State#state.have_scenario of
- true ->
- Conf = #conf{segments = ?MBC_MSEG_LIMIT,
- format_to = IODev},
- Res = mk_config(Conf, State#state.alloc),
- From ! {response, Ref, Res},
- ok;
- _ ->
- From ! {response, Ref, no_scenario_saved},
- ok
- end,
- State;
- {request, From, Ref, stop} ->
- From ! {response, Ref, ok},
- exit(normal);
- {request, From, Ref, state} ->
- From ! {response, Ref, State},
- State;
- {request, From, Ref, Req} ->
- From ! {response, Ref, {unknown_request, Req}},
- State;
- _ ->
- State
- end,
- server_loop(NewState).
-
-carrier_migration_support(aoff) ->
- true;
-carrier_migration_support(aoffcbf) ->
- true;
-carrier_migration_support(aoffcaobf) ->
- true;
-carrier_migration_support(_) ->
- false.
-
-allocator_instances(ll_alloc, Strategy) ->
- case carrier_migration_support(Strategy) of
- true -> erlang:system_info(schedulers);
- false -> 1
- end;
-allocator_instances(_A, undefined) ->
- 1;
-allocator_instances(_A, _Strategy) ->
- erlang:system_info(schedulers).
-
-strategy(temp_alloc, _AI) ->
- af;
-strategy(A, AI) ->
- try
- {A, OptList} = lists:keyfind(A, 1, AI),
- {as, S} = lists:keyfind(as, 1, OptList),
- S
- catch
- _ : _ ->
- undefined
- end.
-
-strategy_str(af) ->
- "A fit";
-strategy_str(gf) ->
- "Good fit";
-strategy_str(bf) ->
- "Best fit";
-strategy_str(aobf) ->
- "Address order best fit";
-strategy_str(aoff) ->
- "Address order first fit";
-strategy_str(aoffcbf) ->
- "Address order first fit carrier best fit";
-strategy_str(aoffcaobf) ->
- "Address order first fit carrier address order best fit";
-strategy_str(ageffcaoff) ->
- "Age order first fit carrier address order first fit";
-strategy_str(ageffcbf) ->
- "Age order first fit carrier best fit";
-strategy_str(ageffcaobf) ->
- "Age order first fit carrier address order best fit".
-
-default_acul(A, S) ->
- case carrier_migration_support(S) of
- false ->
- 0;
- true ->
- case A of
- ll_alloc -> 85;
- eheap_alloc -> 45;
- _ -> 60
- end
- end.
-
-make_state() ->
- {_, _, _, AI} = erlang:system_info(allocator),
- #state{alloc = lists:map(fun (A) ->
- S = strategy(A, AI),
- #alloc{name = A,
- strategy = S,
- acul = default_acul(A, S),
- instances = allocator_instances(A, S)}
- end,
- ?ALLOCATORS)}.
-
-%%
-%% Save scenario
-%%
-
-ai_value(Key1, Key2, AI) ->
- case lists:keysearch(Key1, 1, AI) of
- {value, {Key1, Value1}} ->
- case lists:keysearch(Key2, 1, Value1) of
- {value, Result} -> Result;
- _ -> undefined
- end;
- _ -> undefined
- end.
-
-
-chk_mbcs_blocks_size(#alloc{low_mbc_blocks_size = undefined,
- high_mbc_blocks_size = undefined} = Alc,
- Min,
- Max) ->
- Alc#alloc{low_mbc_blocks_size = Min,
- high_mbc_blocks_size = Max,
- enabled = true};
-chk_mbcs_blocks_size(#alloc{low_mbc_blocks_size = LowBS,
- high_mbc_blocks_size = HighBS} = Alc,
- Min,
- Max) ->
- true = is_integer(LowBS),
- true = is_integer(HighBS),
- Alc1 = case Min < LowBS of
- true -> Alc#alloc{low_mbc_blocks_size = Min};
- false -> Alc
- end,
- case Max > HighBS of
- true -> Alc1#alloc{high_mbc_blocks_size = Max};
- false -> Alc1
- end.
-
-set_alloc_util(#alloc{alloc_util = AU} = Alc, AU) ->
- Alc;
-set_alloc_util(Alc, Val) ->
- Alc#alloc{alloc_util = Val}.
-
-chk_sbct(#alloc{sbct = undefined} = Alc, AI) ->
- case ai_value(options, sbct, AI) of
- {sbct, Bytes} when is_integer(Bytes) -> Alc#alloc{sbct = b2kb(Bytes)};
- _ -> Alc
- end;
-chk_sbct(Alc, _AI) ->
- Alc.
-
-save_scenario(AlcList) ->
- %% The high priority is not really necessary. It is
- %% used since it will make retrieval of allocator
- %% information less spread out in time on a highly
- %% loaded system.
- OP = process_flag(priority, high),
- Res = do_save_scenario(AlcList),
- process_flag(priority, OP),
- Res.
-
-save_ai2(#alloc{name=Name}=Alc0, AI) ->
- Alc1 = chk_sbct(Alc0, AI),
-
- {Alc, IsAUtil} =
- case ai_value(mbcs, blocks, AI) of
- {blocks, Bs} ->
- case ai_value(Name, size, Bs) of
- {size, MinBS, _, MaxBS} ->
- {chk_mbcs_blocks_size(Alc1, MinBS, MaxBS), true};
- _ ->
- {Alc1, false}
- end;
- _ ->
- {Alc1, false}
- end,
-
- set_alloc_util(Alc, IsAUtil).
-
-save_ai(Alc, [{instance, 0, AI}]) ->
- save_ai2(Alc, AI);
-save_ai(Alc, [{instance, _, _}, {instance, _, _}| _]) ->
- Alc#alloc{enabled = true, need_config_change = true};
-save_ai(Alc, AI) ->
- save_ai2(Alc, AI). % Non erts_alloc_util allocator
-
-do_save_scenario(AlcList) ->
- lists:map(fun (#alloc{enabled = false} = Alc) ->
- Alc;
- (#alloc{name = Name} = Alc) ->
- case erlang:system_info({allocator, Name}) of
- undefined ->
- exit({bad_allocator_name, Name});
- false ->
- Alc#alloc{enabled = false};
- AI when is_list(AI) ->
- save_ai(Alc, AI)
- end
- end,
- AlcList).
-
-%%
-%% Make configuration
-%%
-
-conf_size(Bytes) when is_integer(Bytes), Bytes < 0 ->
- exit({bad_value, Bytes});
-conf_size(Bytes) when is_integer(Bytes), Bytes < 1*?MB ->
- ?ROUNDUP(?B2KB(Bytes), 256);
-conf_size(Bytes) when is_integer(Bytes), Bytes < 10*?MB ->
- ?ROUNDUP(?B2KB(Bytes), ?B2KB(1*?MB));
-conf_size(Bytes) when is_integer(Bytes), Bytes < 100*?MB ->
- ?ROUNDUP(?B2KB(Bytes), ?B2KB(2*?MB));
-conf_size(Bytes) when is_integer(Bytes), Bytes < 256*?MB ->
- ?ROUNDUP(?B2KB(Bytes), ?B2KB(5*?MB));
-conf_size(Bytes) when is_integer(Bytes) ->
- ?ROUNDUP(?B2KB(Bytes), ?B2KB(10*?MB)).
-
-sbct(#conf{format_to = FTO}, #alloc{name = A, sbct = SBCT}) ->
- fc(FTO, "Sbc threshold size of ~p kilobytes.", [SBCT]),
- format(FTO, " +M~csbct ~p~n", [alloc_char(A), SBCT]).
-
-default_mmbcs(temp_alloc = A, _Insts) ->
- {value, {A, MMBCS_Default}} = lists:keysearch(A, 1, ?MMBCS_DEFAULTS),
- MMBCS_Default;
-default_mmbcs(A, Insts) ->
- {value, {A, MMBCS_Default}} = lists:keysearch(A, 1, ?MMBCS_DEFAULTS),
- I = case Insts > 4 of
- true -> 4;
- _ -> Insts
- end,
- ?ROUNDUP(MMBCS_Default div I, ?B2KB(1*?KB)).
-
-mmbcs(#conf{format_to = FTO},
- #alloc{name = A, instances = Insts, low_mbc_blocks_size = BlocksSize}) ->
- BS = case A of
- temp_alloc -> BlocksSize;
- _ -> BlocksSize div Insts
- end,
- DefMMBCS = default_mmbcs(A, Insts),
- case {Insts, BS > DefMMBCS} of
- {1, true} ->
- MMBCS = conf_size(BS),
- fc(FTO, "Main mbc size of ~p kilobytes.", [MMBCS]),
- format(FTO, " +M~cmmbcs ~p~n", [alloc_char(A), MMBCS]);
- _ ->
- MMBCS = ?B2KB(DefMMBCS),
- fc(FTO, "Main mbc size of ~p kilobytes.", [MMBCS]),
- format(FTO, " +M~cmmbcs ~p~n", [alloc_char(A), MMBCS]),
- ok
- end.
-
-smbcs_lmbcs(#conf{format_to = FTO},
- #alloc{name = A, segments = Segments}) ->
- MBCS = Segments#segment.size,
- AC = alloc_char(A),
- fc(FTO, "Mseg mbc size of ~p kilobytes.", [MBCS]),
- format(FTO, " +M~csmbcs ~p +M~clmbcs ~p~n", [AC, MBCS, AC, MBCS]),
- ok.
-
-alloc_char(binary_alloc) -> $B;
-alloc_char(std_alloc) -> $D;
-alloc_char(ets_alloc) -> $E;
-alloc_char(fix_alloc) -> $F;
-alloc_char(eheap_alloc) -> $H;
-alloc_char(ll_alloc) -> $L;
-alloc_char(mseg_alloc) -> $M;
-alloc_char(driver_alloc) -> $R;
-alloc_char(sl_alloc) -> $S;
-alloc_char(temp_alloc) -> $T;
-alloc_char(sys_alloc) -> $Y;
-alloc_char(Alloc) ->
- exit({bad_allocator, Alloc}).
-
-conf_alloc(#conf{format_to = FTO},
- #alloc{name = A, enabled = false}) ->
- fcl(FTO, A),
- fcp(FTO,
- "WARNING: ~p has been disabled. Consider enabling ~p by passing "
- "the \"+M~ce true\" command line argument and rerun "
- "erts_alloc_config.",
- [A, A, alloc_char(A)]);
-conf_alloc(#conf{format_to = FTO},
- #alloc{name = A, need_config_change = true}) ->
- fcl(FTO, A),
- fcp(FTO,
- "WARNING: ~p has been configured in a way that prevents "
- "erts_alloc_config from creating a configuration. The configuration "
- "will be automatically adjusted to fit erts_alloc_config if you "
- "use the \"+Mea config\" command line argument while running "
- "erts_alloc_config.",
- [A]);
-conf_alloc(#conf{format_to = FTO} = Conf,
- #alloc{name = A, alloc_util = true} = Alc) ->
- fcl(FTO, A),
- chk_xnote(Conf, Alc),
- au_conf_alloc(Conf, Alc),
- format(FTO, "#~n", []);
-conf_alloc(#conf{format_to = FTO} = Conf, #alloc{name = A} = Alc) ->
- fcl(FTO, A),
- chk_xnote(Conf, Alc).
-
-chk_xnote(#conf{format_to = FTO},
- #alloc{name = sys_alloc}) ->
- fcp(FTO, "Cannot be configured. Default malloc implementation used.");
-chk_xnote(#conf{format_to = FTO},
- #alloc{name = mseg_alloc}) ->
- fcp(FTO, "Default configuration used.");
-chk_xnote(#conf{format_to = FTO},
- #alloc{name = ll_alloc}) ->
- fcp(FTO,
- "Note, blocks allocated with ll_alloc are very "
- "seldom deallocated. Placing blocks in mseg "
- "carriers is therefore very likely only a waste "
- "of resources.");
-chk_xnote(#conf{}, #alloc{}) ->
- ok.
-
-au_conf_alloc(#conf{format_to = FTO} = Conf,
- #alloc{name = A,
- alloc_util = true,
- instances = Insts,
- acul = Acul,
- strategy = Strategy,
- low_mbc_blocks_size = Low,
- high_mbc_blocks_size = High} = Alc) ->
- fcp(FTO, "Usage of mbcs: ~p - ~p kilobytes", [?B2KB(Low), ?B2KB(High)]),
- case Insts of
- 1 ->
- fc(FTO, "One instance used."),
- format(FTO, " +M~ct false~n", [alloc_char(A)]);
- _ ->
- fc(FTO, "~p + 1 instances used.",
- [Insts]),
- format(FTO, " +M~ct true~n", [alloc_char(A)]),
- case Strategy of
- undefined ->
- ok;
- _ ->
- fc(FTO, "Allocation strategy: ~s.",
- [strategy_str(Strategy)]),
- format(FTO, " +M~cas ~s~n", [alloc_char(A),
- atom_to_list(Strategy)])
- end,
- case carrier_migration_support(Strategy) of
- false ->
- ok;
- true ->
- fc(FTO, "Abandon carrier utilization limit of ~p%.", [Acul]),
- format(FTO, " +M~cacul ~p~n", [alloc_char(A), Acul])
- end
- end,
- mmbcs(Conf, Alc),
- smbcs_lmbcs(Conf, Alc),
- sbct(Conf, Alc).
-
-calc_seg_size(Growth, Segs) ->
- conf_size(round(Growth*?FRAG_FACT*?GROWTH_SEG_FACT) div Segs).
-
-calc_growth_segments(Conf, AlcList0) ->
- CalcSmall = fun (#alloc{name = ll_alloc, instances = 1} = Alc, Acc) ->
- {Alc#alloc{segments = #segment{size = conf_size(0),
- number = 0}},
- Acc};
- (#alloc{alloc_util = true,
- instances = Insts,
- low_mbc_blocks_size = LowMBC,
- high_mbc_blocks_size = High} = Alc,
- {SL, AL}) ->
- Low = case Insts of
- 1 -> LowMBC;
- _ -> 0
- end,
- Growth = High - Low,
- case Growth >= ?LARGE_GROWTH_ABS_LIMIT of
- true ->
- {Alc, {SL, AL+1}};
- false ->
- Segs = ?SMALL_GROWTH_SEGS,
- SegSize = calc_seg_size(Growth, Segs),
- {Alc#alloc{segments
- = #segment{size = SegSize,
- number = Segs}},
- {SL - Segs, AL}}
-
- end;
- (Alc, Acc) -> {Alc, Acc}
- end,
- {AlcList1, {SegsLeft, AllocsLeft}}
- = lists:mapfoldl(CalcSmall, {Conf#conf.segments, 0}, AlcList0),
- case AllocsLeft of
- 0 ->
- AlcList1;
- _ ->
- SegsPerAlloc = case (SegsLeft div AllocsLeft) + 1 of
- SPA when SPA < ?SMALL_GROWTH_SEGS ->
- ?SMALL_GROWTH_SEGS;
- SPA ->
- SPA
- end,
- CalcLarge = fun (#alloc{alloc_util = true,
- segments = undefined,
- instances = Insts,
- low_mbc_blocks_size = LowMBC,
- high_mbc_blocks_size = High} = Alc) ->
- Low = case Insts of
- 1 -> LowMBC;
- _ -> 0
- end,
- Growth = High - Low,
- SegSize = calc_seg_size(Growth,
- SegsPerAlloc),
- Alc#alloc{segments
- = #segment{size = SegSize,
- number = SegsPerAlloc}};
- (Alc) ->
- Alc
- end,
- lists:map(CalcLarge, AlcList1)
- end.
-
-mk_config(#conf{format_to = FTO} = Conf, AlcList) ->
- format_header(FTO),
- Res = lists:foreach(fun (Alc) -> conf_alloc(Conf, Alc) end,
- calc_growth_segments(Conf, AlcList)),
- format_footer(FTO),
- Res.
-
-format_header(FTO) ->
- {Y,Mo,D} = erlang:date(),
- {H,Mi,S} = erlang:time(),
- fcl(FTO),
- fcl(FTO, "erts_alloc configuration"),
- fcl(FTO),
- fcp(FTO,
- "This erts_alloc configuration was automatically "
- "generated at ~w-~2..0w-~2..0w ~2..0w:~2..0w.~2..0w by "
- "erts_alloc_config.",
- [Y, Mo, D, H, Mi, S]),
- fcp(FTO,
- "~s was used when generating the configuration.",
- [string:trim(erlang:system_info(system_version), both, "$\n")]),
- case erlang:system_info(schedulers) of
- 1 -> ok;
- Schdlrs ->
- fcp(FTO,
- "NOTE: This configuration was made for ~p schedulers. "
- "It is very important that ~p schedulers are used.",
- [Schdlrs, Schdlrs])
- end,
- fcp(FTO,
- "This configuration is intended as a suggestion and "
- "may need to be adjusted manually. Instead of modifying "
- "this file, you are advised to write another configuration "
- "file and override values that you want to change. "
- "Doing it this way simplifies things when you want to "
- "rerun erts_alloc_config."),
- fcp(FTO,
- "This configuration is based on the actual use of "
- "multi-block carriers (mbcs) for a set of different "
- "runtime scenarios. Note that this configuration may "
- "perform bad, ever horrible, for other runtime "
- "scenarios."),
- fcp(FTO,
- "You are advised to rerun erts_alloc_config if the "
- "applications run when the configuration was made "
- "are changed, or if the load on the applications have "
- "changed since the configuration was made. You are also "
- "advised to rerun erts_alloc_config if the Erlang runtime "
- "system used is changed."),
- fcp(FTO,
- "Note, that the singel-block carrier (sbc) parameters "
- "very much effects the use of mbcs. Therefore, if you "
- "change the sbc parameters, you are advised to rerun "
- "erts_alloc_config."),
- fcp(FTO,
- "For more information see the erts_alloc_config(3) "
- "documentation."),
- ok.
-
-format_footer(FTO) ->
- fcl(FTO).
-
-%%%
-%%% Misc.
-%%%
-
-b2kb(B) when is_integer(B) ->
- MaxKB = (1 bsl erlang:system_info(wordsize)*8) div 1024,
- case ?B2KB(B) of
- KB when KB > MaxKB -> MaxKB;
- KB -> KB
- end.
-
-format(false, _Frmt) ->
- ok;
-format(IODev, Frmt) ->
- io:format(IODev, Frmt, []).
-
-format(false, _Frmt, _Args) ->
- ok;
-format(IODev, Frmt, Args) ->
- io:format(IODev, Frmt, Args).
-
-%% fcp: format comment paragraf
-fcp(IODev, Frmt, Args) ->
- fc(IODev, Frmt, Args),
- format(IODev, "#~n").
-
-fcp(IODev, Frmt) ->
- fc(IODev, Frmt),
- format(IODev, "#~n").
-
-%% fc: format comment
-fc(IODev, Frmt, Args) ->
- fc(IODev, lists:flatten(io_lib:format(Frmt, Args))).
-
-fc(IODev, String) ->
- fc_aux(IODev, string:lexemes(String, " "), 0).
-
-fc_aux(_IODev, [], 0) ->
- ok;
-fc_aux(IODev, [], _Len) ->
- format(IODev, "~n");
-fc_aux(IODev, [T|Ts], 0) ->
- Len = 2 + string:length(T),
- format(IODev, "# ~s", [T]),
- fc_aux(IODev, Ts, Len);
-fc_aux(IODev, [T|Ts] = ATs, Len) ->
- TLength = string:length(T),
- case (TLength + Len) >= ?PRINT_WITDH of
- true ->
- format(IODev, "~n"),
- fc_aux(IODev, ATs, 0);
- false ->
- NewLen = Len + 1 + TLength,
- format(IODev, " ~s", [T]),
- fc_aux(IODev, Ts, NewLen)
- end.
-
-%% fcl: format comment line
-fcl(FTO) ->
- EndStr = "# ",
- Precision = string:length(EndStr),
- FieldWidth = -1*(?PRINT_WITDH),
- format(FTO, "~*.*.*s~n", [FieldWidth, Precision, $-, EndStr]).
-
-fcl(FTO, A) when is_atom(A) ->
- fcl(FTO, atom_to_list(A));
-fcl(FTO, Str) when is_list(Str) ->
- Str2 = "# --- " ++ Str ++ " ",
- Precision = string:length(Str2),
- FieldWidth = -1*(?PRINT_WITDH),
- format(FTO, "~*.*.*s~n", [FieldWidth, Precision, $-, Str2]).
+-removed({'_','_', "this module has as of OTP 26.0 been removed"}).
diff --git a/lib/tools/src/instrument.erl b/lib/runtime_tools/src/instrument.erl
index 6b2de541ec..5941c4a8df 100644
--- a/lib/tools/src/instrument.erl
+++ b/lib/runtime_tools/src/instrument.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -97,9 +97,9 @@ alloc_hist_merge_hist(Index, A, B) ->
InPool :: boolean(),
TotalSize :: non_neg_integer(),
UnscannedSize :: non_neg_integer(),
- Allocations :: {Type :: atom(),
- Count :: non_neg_integer(),
- Size :: non_neg_integer()},
+ Allocations :: [{Type :: atom(),
+ Count :: non_neg_integer(),
+ Size :: non_neg_integer()}],
FreeBlocks :: block_histogram()}]}.
-spec carriers() -> {ok, Result} | {error, Reason} when
diff --git a/lib/runtime_tools/src/runtime_tools.app.src b/lib/runtime_tools/src/runtime_tools.app.src
index dfdd58015b..6daa698cb8 100644
--- a/lib/runtime_tools/src/runtime_tools.app.src
+++ b/lib/runtime_tools/src/runtime_tools.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
{modules, [appmon_info, dbg,observer_backend,runtime_tools,
runtime_tools_sup,erts_alloc_config,
ttb_autostart,dyntrace,system_information,
- scheduler,
+ scheduler, instrument,
msacc]},
{registered, [runtime_tools_sup]},
{applications, [kernel, stdlib]},
diff --git a/lib/runtime_tools/src/system_information.erl b/lib/runtime_tools/src/system_information.erl
index 507071fa2f..3743b641d9 100644
--- a/lib/runtime_tools/src/system_information.erl
+++ b/lib/runtime_tools/src/system_information.erl
@@ -38,7 +38,6 @@
application/1, application/2,
environment/0, environment/1,
module/1, module/2,
- modules/1,
sanity_check/0]).
%% gen_server callbacks
@@ -124,10 +123,6 @@ module(M) when is_atom(M) -> module(M, []).
module(M, Opts) when is_atom(M), is_list(Opts) ->
gen_server:call(?SERVER, {module, M, Opts}, infinity).
-modules(Opt) when is_atom(Opt) ->
- gen_server:call(?SERVER, {modules, Opt}, infinity).
-
-
-spec sanity_check() -> ok | {failed, Failures} when
Application :: atom(),
ApplicationVersion :: string(),
@@ -190,12 +185,6 @@ handle_call({module, M, Opts}, _From, #state{ report = Report } = S) ->
print_modules_from_code(M, Mods, Opts),
{reply, ok, S};
-handle_call({modules, native}, _From, #state{ report = Report } = S) ->
- Codes = get_native_modules_from_code(get_value([code],Report)),
- io:format("~p~n", [Codes]),
- {reply, ok, S};
-
-
handle_call(_Request, _From, State) ->
{reply, ok, State}.
@@ -249,31 +238,6 @@ find_modules(M, [{M, _}=Info|Ms]) -> [Info|find_modules(M,Ms)];
find_modules(M, [_|Ms]) -> find_modules(M, Ms);
find_modules(_, []) -> [].
-get_native_modules_from_code([{application, {App, Info}}|Cs]) ->
- case get_native_modules(get_value([modules], Info)) of
- [] -> get_native_modules_from_code(Cs);
- Mods ->
- Path = get_value([path], Info),
- Vsn = get_value([vsn], Info),
- [{App, Vsn, Path, Mods}|get_native_modules_from_code(Cs)]
- end;
-get_native_modules_from_code([{code, Info}|Cs]) ->
- case get_native_modules(get_value([modules], Info)) of
- [] -> get_native_modules_from_code(Cs);
- Mods ->
- Path = get_value([path], Info),
- [{Path, Mods}|get_native_modules_from_code(Cs)]
- end;
-get_native_modules_from_code([]) -> [].
-
-get_native_modules([]) -> [];
-get_native_modules([{Mod, Info}|Ms]) ->
- case proplists:get_value(native, Info) of
- false -> get_native_modules(Ms);
- _ -> [Mod|get_native_modules(Ms)]
- end.
-
-
%% print information
print_applications([{application, App}|Apps], Opts) ->
@@ -320,14 +284,12 @@ print_module_from_code(M, {Path, [{M,ModInfo}]}) ->
io:format(" from path \"~ts\" (no application):~n", [Path]),
io:format(" - compiler: ~s~n", [get_value([compiler], ModInfo)]),
io:format(" - md5: ~s~n", [get_value([md5], ModInfo)]),
- io:format(" - native: ~w~n", [get_value([native], ModInfo)]),
io:format(" - loaded: ~w~n", [get_value([loaded], ModInfo)]),
ok;
print_module_from_code(M, {App,Vsn,Path,[{M,ModInfo}]}) ->
io:format(" from path \"~ts\" (~w-~s):~n", [Path,App,Vsn]),
io:format(" - compiler: ~s~n", [get_value([compiler], ModInfo)]),
io:format(" - md5: ~s~n", [get_value([md5], ModInfo)]),
- io:format(" - native: ~w~n", [get_value([native], ModInfo)]),
io:format(" - loaded: ~w~n", [get_value([loaded], ModInfo)]),
ok.
@@ -335,7 +297,6 @@ print_module({Mod, ModInfo}) ->
io:format(" - ~w:~n", [Mod]),
io:format(" - compiler: ~s~n", [get_value([compiler], ModInfo)]),
io:format(" - md5: ~s~n", [get_value([md5], ModInfo)]),
- io:format(" - native: ~w~n", [get_value([native], ModInfo)]),
io:format(" - loaded: ~w~n", [get_value([loaded], ModInfo)]),
ok.
@@ -571,7 +532,6 @@ emit_module_info(EmitChunk, Beam) ->
EmitChunk("{~w,["
"{loaded,~w},"
- "{native,false},"
"{compiler,~w},"
"{md5,~w}"
"]}",
diff --git a/lib/runtime_tools/test/Makefile b/lib/runtime_tools/test/Makefile
index c9578d376e..a04e1ae97e 100644
--- a/lib/runtime_tools/test/Makefile
+++ b/lib/runtime_tools/test/Makefile
@@ -5,10 +5,10 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
MODULES = \
dyntrace_SUITE \
dyntrace_lttng_SUITE \
+ instrument_SUITE \
runtime_tools_SUITE \
system_information_SUITE \
dbg_SUITE \
- erts_alloc_config_SUITE \
scheduler_SUITE \
msacc_SUITE \
zzz_SUITE
diff --git a/lib/runtime_tools/test/dbg_SUITE.erl b/lib/runtime_tools/test/dbg_SUITE.erl
index 209af7be19..7983e62389 100644
--- a/lib/runtime_tools/test/dbg_SUITE.erl
+++ b/lib/runtime_tools/test/dbg_SUITE.erl
@@ -23,7 +23,7 @@
-export([all/0, suite/0, init_per_suite/1, end_per_suite/1,
big/1, tiny/1, simple/1, message/1, distributed/1, port/1,
send/1, recv/1,
- ip_port/1, file_port/1, file_port2/1,
+ ip_port/1, file_port/1, file_port2/1, file_tracer/1,
ip_port_busy/1, wrap_port/1, wrap_port_time/1,
with_seq_trace/1, dead_suspend/1, local_trace/1,
saved_patterns/1, tracer_exit_on_stop/1,
@@ -41,7 +41,7 @@ suite() ->
all() ->
[big, tiny, simple, message, distributed, port, ip_port,
send, recv,
- file_port, file_port2, ip_port_busy,
+ file_port, file_port2, file_tracer, ip_port_busy,
wrap_port, wrap_port_time, with_seq_trace, dead_suspend,
local_trace, saved_patterns, tracer_exit_on_stop,
erl_tracer, distributed_erl_tracer].
@@ -50,7 +50,7 @@ init_per_suite(Config) ->
Config.
end_per_suite(_Config) ->
- dbg:stop_clear(),
+ dbg:stop(),
ok.
%% Rudimentary interface test
@@ -89,7 +89,7 @@ big(Config) when is_list(Config) ->
ok=file:set_cwd(OldCurDir)
after
- dbg:stop_clear()
+ dbg:stop()
end,
ok.
@@ -120,7 +120,7 @@ tiny(Config) when is_list(Config) ->
failure
end
after
- dbg:stop_clear(),
+ dbg:stop(),
ok = file:set_cwd(OldCurDir)
end,
ok.
@@ -136,7 +136,7 @@ simple(Config) when is_list(Config) ->
S = self(),
[{trace,S,call,{dbg,ltp,[]}}] = flush()
after
- dbg:stop_clear()
+ dbg:stop()
end,
ok.
@@ -152,7 +152,7 @@ message(Config) when is_list(Config) ->
ok = dbg:ltp(),
ok = dbg:ln()
after
- dbg:stop_clear()
+ dbg:stop()
end,
S = self(),
[{trace,S,call,{dbg,ltp,[]},S},
@@ -235,7 +235,7 @@ send(Config) when is_list(Config) ->
ok
after
- dbg:stop_clear(),
+ dbg:stop(),
peer:stop(Peer)
end.
@@ -347,7 +347,7 @@ recv(Config) when is_list(Config) ->
ok
after
- dbg:stop_clear(),
+ dbg:stop(),
peer:stop(Peer)
end.
@@ -412,7 +412,7 @@ distributed(Config) when is_list(Config) ->
%%
stop()
after
- dbg:stop_clear(),
+ dbg:stop(),
peer:stop(Peer)
end,
ok.
@@ -463,7 +463,7 @@ local_trace(Config) when is_list(Config) ->
{dbg_SUITE,not_exported,1},
{error,badarith}}] = flush()
after
- dbg:stop_clear()
+ dbg:stop()
end,
ok.
@@ -488,7 +488,7 @@ port(Config) when is_list(Config) ->
{trace,Port,getting_linked,S},
{trace,Port,closed,normal}] = flush()
after
- dbg:stop_clear()
+ dbg:stop()
end,
ok.
@@ -514,7 +514,7 @@ saved_patterns(Config) when is_list(Config) ->
S = self(),
[{trace,S,call,{dbg,ltp,[]},blahonga}] = flush()
after
- dbg:stop_clear()
+ dbg:stop()
end,
ok.
@@ -546,7 +546,7 @@ ip_port(Config) when is_list(Config) ->
[{trace,S,call,{dbg,ltp,[]},S},
{trace,S,call,{dbg,ln,[]},hej}] = flush()
after
- dbg:stop_clear()
+ dbg:stop()
end,
ok.
@@ -562,7 +562,7 @@ ip_port_busy(Config) when is_list(Config) ->
io:format("Error reason = ~p~n", [Reason]),
true = port_close(Port)
after
- dbg:stop_clear()
+ dbg:stop()
end,
ok.
@@ -587,7 +587,7 @@ file_port(Config) when is_list(Config) ->
{trace,S,call,{dbg,ln,[]},hej},
end_of_trace] = flush()
after
- dbg:stop_clear(),
+ dbg:stop(),
file:delete(FName)
end,
ok.
@@ -616,7 +616,32 @@ file_port2(Config) when is_list(Config) ->
stop(),
[] = flush()
after
- dbg:stop_clear(),
+ dbg:stop(),
+ file:delete(FName)
+ end,
+ ok.
+
+%% Test tracing to file
+file_tracer(Config) when is_list(Config) ->
+ stop(),
+ FName = make_temp_name(Config),
+ %% Ok, lets try with flush and follow_file.
+ {ok, _} = dbg:tracer(file, FName),
+ try
+ {ok, [{matched, _node, 1}]} = dbg:p(self(),call),
+ {ok, _} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]),
+ {ok, _} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]),
+ ok = dbg:ltp(),
+ timer:sleep(100),
+ {ok, LTP} = file:read_file(FName),
+ <<"dbg:ltp()",_/binary>> = string:find(LTP, "dbg:ltp() ("++pid_to_list(self())++")"),
+ ok = dbg:ln(),
+ timer:sleep(100),
+ {ok, LN} = file:read_file(FName),
+ <<"dbg:ln()",_/binary>> = string:find(LN, "dbg:ln() (hej)"),
+ stop()
+ after
+ dbg:stop(),
file:delete(FName)
end,
ok.
@@ -684,7 +709,7 @@ wrap_port(Config) when is_list(Config) ->
%%
lists:map(fun(F) -> file:delete(F) end, Files)
after
- dbg:stop_clear()
+ dbg:stop()
end,
ok.
@@ -756,7 +781,7 @@ wrap_port_time(Config) when is_list(Config) ->
end_of_trace] = flush(),
lists:map(fun(F) -> file:delete(F) end, Files)
after
- dbg:stop_clear()
+ dbg:stop()
end,
ok.
@@ -782,7 +807,7 @@ with_seq_trace(Config) when is_list(Config) ->
{seq_trace,0,{send,_,Server,S,{dbg,{ok,Tracer}}}}] =
flush()
after
- dbg:stop_clear()
+ dbg:stop()
end,
ok.
@@ -793,7 +818,7 @@ dead_suspend(Config) when is_list(Config) ->
try
survived = run_dead_suspend()
after
- dbg:stop_clear()
+ dbg:stop()
end.
run_dead_suspend() ->
diff --git a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl
deleted file mode 100644
index 10a9560b73..0000000000
--- a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl
+++ /dev/null
@@ -1,193 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
-%%
-%% 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.
-%%
-%% %CopyrightEnd%
-%%
-
--module(erts_alloc_config_SUITE).
-
-%-define(line_trace, 1).
-
--include_lib("common_test/include/ct.hrl").
-
-%-compile(export_all).
--export([all/0, suite/0,
- init_per_suite/1, end_per_suite/1,
- init_per_testcase/2, end_per_testcase/2]).
-
-%% Testcases
--export([basic/1]).
-
-%% internal export
--export([make_basic_config/1]).
-
-suite() ->
- [{ct_hooks,[ts_install_cth]},
- {timetrap, {minutes, 2}}].
-
-all() ->
- [basic].
-
-init_per_suite(Config) ->
- case test_server:is_asan() of
- true ->
- %% No point testing own allocators under address sanitizer.
- {skip, "Address sanitizer"};
- false ->
- Config
- end.
-
-end_per_suite(_Config) ->
- ok.
-
-init_per_testcase(Case, Config) when is_list(Config) ->
- [{testcase, Case},
- {erl_flags_env, save_env()} | Config].
-
-end_per_testcase(_Case, Config) when is_list(Config) ->
- restore_env(proplists:get_value(erl_flags_env, Config)),
- ok.
-
-%%%
-%%% The test cases ------------------------------------------------------------
-%%%
-
-basic(Config) when is_list(Config) ->
- ErtsAllocConfig = privfile("generated", Config),
-
- SbctMod = " +MBsbct 1024 +MHsbct 4096",
-
- %% Make sure we have enabled allocators
- ZFlgs = os:getenv("ERL_ZFLAGS", "") ++ " +Mea max +Mea config",
-
- os:putenv("ERL_ZFLAGS", ZFlgs ++ SbctMod),
-
- {ok, Peer1, Node1} = ?CT_PEER(),
- ok = rpc:call(Node1, ?MODULE, make_basic_config, [ErtsAllocConfig]),
- peer:stop(Peer1),
-
- display_file(ErtsAllocConfig),
-
- ManualConfig = privfile("manual", Config),
- {ok, IOD} = file:open(ManualConfig, [write]),
- io:format(IOD, "~s", ["+MBsbct 2048"]),
- file:close(IOD),
- display_file(ManualConfig),
-
- os:putenv("ERL_ZFLAGS", ZFlgs),
-
- {ok, Peer2, Node2} = ?CT_PEER(["-args_file", ErtsAllocConfig,
- "-args_file", ManualConfig]),
-
- {_, _, _, Cfg} = rpc:call(Node2, erlang, system_info, [allocator]),
-
- peer:stop(Peer2),
-
- {value,{binary_alloc, BCfg}} = lists:keysearch(binary_alloc, 1, Cfg),
- {value,{sbct, 2097152}} = lists:keysearch(sbct, 1, BCfg),
- {value,{eheap_alloc, HCfg}} = lists:keysearch(eheap_alloc, 1, Cfg),
- {value,{sbct, 4194304}} = lists:keysearch(sbct, 1, HCfg),
-
- ok.
-
-make_basic_config(ErtsAllocConfig) ->
- %% Save some different scenarios
- Tester = self(),
- SSBegun = make_ref(),
- SSDone = make_ref(),
- SSFun = fun (F) ->
- receive
- SSDone ->
- ok = erts_alloc_config:save_scenario(),
- Tester ! SSDone
- after 500 ->
- ok = erts_alloc_config:save_scenario(),
- F(F)
- end
- end,
- SS = spawn_link(fun () ->
- ok = erts_alloc_config:save_scenario(),
- Tester ! SSBegun,
- SSFun(SSFun)
- end),
- receive SSBegun -> ok end,
- Ref = make_ref(),
- Tab = ets:new(?MODULE, [bag, public]),
- Ps = lists:map(
- fun (_) ->
- spawn_link(
- fun () ->
- ets:insert(Tab,
- {self(),
- lists:seq(1, 1000)}),
- receive after 1000 -> ok end,
- Tester ! {Ref, self()}
- end)
- end,
- lists:seq(1, 10000)),
- lists:foreach(fun (P) -> receive {Ref, P} -> ok end end, Ps),
- ets:delete(Tab),
- SS ! SSDone,
- receive SSDone -> ok end,
-
- ok = erts_alloc_config:make_config(ErtsAllocConfig).
-
-
-
-%%
-%% Utils ----------------------------------------------------------------------
-%%
-
-display_file(FileName) ->
- io:format("filename: ~s~n", [FileName]),
- {ok, Bin} = file:read_file(FileName),
- io:format("~s", [binary_to_list(Bin)]),
- io:format("eof: ~s~n", [FileName]),
- ok.
-
-privfile(Name, Config) ->
- filename:join([proplists:get_value(priv_dir, Config),
- atom_to_list(proplists:get_value(testcase, Config)) ++ "." ++ Name]).
-
-save_env() ->
- {erl_flags,
- os:getenv("ERL_AFLAGS"),
- os:getenv("ERL_FLAGS"),
- os:getenv("ERL_"++erlang:system_info(otp_release)++"_FLAGS"),
- os:getenv("ERL_ZFLAGS")}.
-
-restore_env(EVar, false) when is_list(EVar) ->
- restore_env(EVar, "");
-restore_env(EVar, "") when is_list(EVar) ->
- case os:getenv(EVar) of
- false -> ok;
- "" -> ok;
- " " -> ok;
- _ -> os:putenv(EVar, " ")
- end;
-restore_env(EVar, Value) when is_list(EVar), is_list(Value) ->
- case os:getenv(EVar) of
- Value -> ok;
- _ -> os:putenv(EVar, Value)
- end.
-
-restore_env({erl_flags, AFlgs, Flgs, RFlgs, ZFlgs}) ->
- restore_env("ERL_AFLAGS", AFlgs),
- restore_env("ERL_FLAGS", Flgs),
- restore_env("ERL_"++erlang:system_info(otp_release)++"_FLAGS", RFlgs),
- restore_env("ERL_ZFLAGS", ZFlgs),
- ok.
diff --git a/lib/tools/test/instrument_SUITE.erl b/lib/runtime_tools/test/instrument_SUITE.erl
index 616bc5a895..8577794e5f 100644
--- a/lib/tools/test/instrument_SUITE.erl
+++ b/lib/runtime_tools/test/instrument_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/runtime_tools/test/system_information_SUITE.erl b/lib/runtime_tools/test/system_information_SUITE.erl
index b8a8ca9a39..6870d95b40 100644
--- a/lib/runtime_tools/test/system_information_SUITE.erl
+++ b/lib/runtime_tools/test/system_information_SUITE.erl
@@ -293,7 +293,6 @@ validate_loaded_report() ->
ok = system_information:application(kernel,[full]),
ok = system_information:module(gen_server),
ok = system_information:module(gen_server,[full]),
- ok = system_information:modules(native),
ok.
diff --git a/lib/sasl/src/sasl.app.src b/lib/sasl/src/sasl.app.src
index 8fc41ca506..26e57119a7 100644
--- a/lib/sasl/src/sasl.app.src
+++ b/lib/sasl/src/sasl.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -42,6 +42,6 @@
{applications, [kernel, stdlib]},
{env, []},
{mod, {sasl, []}},
- {runtime_dependencies, ["tools-2.6.14","stdlib-3.4","kernel-6.0",
+ {runtime_dependencies, ["tools-2.6.14","stdlib-4.0","kernel-6.0",
"erts-10.2"]}]}.
diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl
index b875c445c6..671eaf7745 100644
--- a/lib/sasl/src/systools_make.erl
+++ b/lib/sasl/src/systools_make.erl
@@ -696,7 +696,15 @@ specified([], _) ->
[].
get_items([H|T], Dict) ->
- Item = check_item(keysearch(H, 1, Dict),H),
+ Item = case check_item(keysearch(H, 1, Dict),H) of
+ [Atom|_]=Atoms when is_atom(Atom), is_list(Atoms) ->
+ %% Check for duplicate entries in lists
+ case Atoms =/= lists:uniq(Atoms) of
+ true -> throw({dupl_entry, H, lists:subtract(Atoms, lists:uniq(Atoms))});
+ false -> Atoms
+ end;
+ X -> X
+ end,
[Item|get_items(T, Dict)];
get_items([], _Dict) ->
[].
@@ -2436,6 +2444,8 @@ form_reading({read,File}) ->
io_lib:format("Cannot read ~tp~n",[File]);
form_reading({{bad_param, P},_}) ->
io_lib:format("Bad parameter in .app file: ~tp~n",[P]);
+form_reading({{dupl_entry, P, DE},_}) ->
+ io_lib:format("~tp parameter contains duplicates of: ~tp~n", [P, DE]);
form_reading({{missing_param,P},_}) ->
io_lib:format("Missing parameter in .app file: ~p~n",[P]);
form_reading({badly_formatted_application,_}) ->
diff --git a/lib/sasl/src/systools_rc.erl b/lib/sasl/src/systools_rc.erl
index c570ed00ab..ead3c07dbb 100644
--- a/lib/sasl/src/systools_rc.erl
+++ b/lib/sasl/src/systools_rc.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -426,7 +426,7 @@ translate_dependent_instrs(Mode, Before, After, Appls) ->
{Before ++ NBefore, NAfter}.
translate_dep_loop(G, WCs, [I| Is], Appls, Before, After, Mode)
- when is_tuple(I), size(I) > 1 ->
+ when tuple_size(I) > 1 ->
IName = element(1, I),
case lists:member(IName, ?DEP_INSTRS) of
true ->
@@ -465,7 +465,7 @@ make_dependency_graph(Instructions) ->
{VDs, _} = lists:mapfoldl(
fun(I, N) ->
Mod = element(2, I),
- Mods = element(size(I), I),
+ Mods = element(tuple_size(I), I),
{{Mod, Mods, {N, I}}, N+1}
end, 1, DepIs),
G = digraph:new(),
diff --git a/lib/sasl/test/Makefile b/lib/sasl/test/Makefile
index adc5a927ac..98c6cfb5bb 100644
--- a/lib/sasl/test/Makefile
+++ b/lib/sasl/test/Makefile
@@ -95,6 +95,7 @@ release_tests_spec: make_emakefile
$(INSTALL_DATA) sasl.spec sasl.cover $(EMAKEFILE) "$(RELSYSDIR)"
chmod -R u+w "$(RELSYSDIR)"
@tar cfh - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -)
+ $(INSTALL_DIR) "$(RELSYSDIR)/sasl_SUITE_data"
$(INSTALL_DATA) $(ERL_TOP)/make/otp_version_tickets "$(RELSYSDIR)/sasl_SUITE_data"
release_docs_spec:
diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl
index 570e1bc1fe..734b55155d 100644
--- a/lib/sasl/test/release_handler_SUITE.erl
+++ b/lib/sasl/test/release_handler_SUITE.erl
@@ -411,7 +411,8 @@ move_system_unix(NodeA, PeerA, TestRootDir, ErtsBinDir, NewSystemPath) ->
[{env,[{"PATH",TestRootDir ++ ":" ++ os:getenv("PATH")}]}]),
%% Wait for node to start
- receive _ -> ok end,
+ receive M1 -> ct:pal("~p",[M1]) end,
+ receive M2 -> ct:pal("~p",[M2]) end,
hej = erpc:call(LinkNode, app_callback_module, get_response, []),
diff --git a/lib/sasl/test/systools_SUITE.erl b/lib/sasl/test/systools_SUITE.erl
index 801660ddac..534a1824b1 100644
--- a/lib/sasl/test/systools_SUITE.erl
+++ b/lib/sasl/test/systools_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@ groups() ->
src_tests_script, crazy_script, optional_apps_script,
included_script, included_override_script,
included_fail_script, included_bug_script, exref_script,
- duplicate_modules_script,
+ duplicate_modules_script, duplicate_entries_script,
otp_3065_circular_dependenies, included_and_used_sort_script]},
{tar, [],
[tar_options, relname_tar, normal_tar, no_mod_vsn_tar, system_files_tar,
@@ -467,6 +467,18 @@ variable_script(Config) when is_list(Config) ->
ok = file:set_cwd(OldDir),
ok.
+%% make_script: Duplicate entries in app file
+duplicate_entries_script(Config) when is_list(Config) ->
+ DataDir = ?datadir,
+ create_apps_duplicate_entry(DataDir),
+ {LatestDir, LatestName} = create_script(latest_t21,Config),
+ error = systools:make_script(LatestName,
+ [{path, [DataDir, LatestDir]}]),
+ {LatestDir2, LatestName2} = create_script(latest_t22,Config),
+ ok = systools:make_script(LatestName2,
+ [{path, [DataDir, LatestDir2]}]),
+ ok.
+
%% make_script: Abnormal cases.
abnormal_script(Config) when is_list(Config) ->
{ok, OldDir} = file:get_cwd(),
@@ -1109,9 +1121,9 @@ erts_tar(Config) ->
{win32, _} ->
{["beam.smp.pdb","erl.exe",
"erl.pdb","erl_log.exe","erlexec.dll","erlsrv.exe","heart.exe",
- "start_erl.exe","werl.exe","beam.smp.dll",
+ "start_erl.exe","beam.smp.dll",
"epmd.exe","erl.ini","erl_call.exe",
- "erlexec.pdb","escript.exe","inet_gethost.exe","werl.pdb"],
+ "erlexec.pdb","escript.exe","inet_gethost.exe"],
["dialyzer.exe","erlc.exe","yielding_c_fun.exe","ct_run.exe","typer.exe"]}
end,
@@ -2575,6 +2587,12 @@ create_script(latest_app_start_type2,Config) ->
{xmerl,current,none}],
Apps = core_apps(current) ++ OtherApps,
do_create_script(latest_app_start_type2,Config,current,Apps);
+create_script(latest_t21, Config) ->
+ Apps = core_apps(current) ++ [{t21, "1.0"}],
+ do_create_script(latest_t21, Config, "4.4", Apps);
+create_script(latest_t22, Config) ->
+ Apps = core_apps(current) ++ [{t22, "1.0"}],
+ do_create_script(latest_t22, Config, "4.4", Apps);
create_script(current_all_no_sasl,Config) ->
Apps = [{kernel,current},{stdlib,current},{db,"2.1"},{fe,"3.1"}],
do_create_script(current_all_no_sasl,Config,current,Apps);
@@ -2906,7 +2924,6 @@ create_include_files(sort_apps_rev, Config) ->
file:write_file(Name ++ ".rel", list_to_binary(Rel)),
{filename:dirname(Name), filename:basename(Name)}.
-
create_apps(Dir) ->
T1 = "{application, t1,\n"
" [{vsn, \"1.0\"},\n"
@@ -3025,8 +3042,6 @@ create_apps2(Dir) ->
" {registered, []}]}.\n",
file:write_file(fname(Dir, 't13.app'), list_to_binary(T13)).
-
-
create_apps_3065(Dir) ->
T11 = "{application, chTraffic,\n"
" [{vsn, \"1.0\"},\n"
@@ -3119,6 +3134,24 @@ create_sort_apps(Dir) ->
" {registered, []}]}.\n",
file:write_file(fname(Dir, 't20.app'), list_to_binary(T20)).
+create_apps_duplicate_entry(Dir) ->
+ T21 = "{application, t21,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, []},\n"
+ " {included_applications, []},\n"
+ " {registered, [test, test]}]}.\n",
+ file:write_file(fname(Dir, 't21.app'), list_to_binary(T21)),
+ T22 = "{application, t22,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, []},\n"
+ " {included_applications, []},\n"
+ " {registered, [test]}]}.\n",
+ file:write_file(fname(Dir, 't22.app'), list_to_binary(T22)).
+
fname(N) ->
filename:join(N).
diff --git a/lib/snmp/doc/src/snmp_intro.xml b/lib/snmp/doc/src/snmp_intro.xml
index ec3f2af956..819a34fc9c 100644
--- a/lib/snmp/doc/src/snmp_intro.xml
+++ b/lib/snmp/doc/src/snmp_intro.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2016</year>
+ <year>1997</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -81,7 +81,7 @@
<section>
<title>Prerequisites</title>
<p>The following prerequisites
- is required for understanding the material in the SNMP
+ are required for understanding the material in the SNMP
User's Guide:
</p>
<list type="bulleted">
diff --git a/lib/snmp/src/agent/snmpa_net_if.erl b/lib/snmp/src/agent/snmpa_net_if.erl
index 72e3ac3e41..ce8cb8f0ee 100644
--- a/lib/snmp/src/agent/snmpa_net_if.erl
+++ b/lib/snmp/src/agent/snmpa_net_if.erl
@@ -245,12 +245,12 @@ init(Prio, NoteStore, MasterAgent, Parent, Opts) ->
info
end,
proc_lib:init_ack({error, {Class, udp_open, PortNo, Reason}});
- {error, Reason} ->
- %% config_err("failed starting net-if: ~n~p", [Reason]),
- proc_lib:init_ack({error, Reason});
+ {error, Reason} ->
+ %% config_err("failed starting net-if: ~n~p", [Reason]),
+ proc_lib:init_fail({error, Reason}, {exit, normal});
Error ->
%% config_err("failed starting net-if: ~n~p", [Error]),
- proc_lib:init_ack({error, Error})
+ proc_lib:init_fail({error, Error}, {exit, normal})
end.
do_init(Prio, NoteStore, MasterAgent, Parent, Opts) ->
diff --git a/lib/snmp/src/app/snmp.app.src b/lib/snmp/src/app/snmp.app.src
index a18ea083fd..2d97cb652d 100644
--- a/lib/snmp/src/app/snmp.app.src
+++ b/lib/snmp/src/app/snmp.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -141,5 +141,6 @@
%% before snmp.
{applications, [kernel, stdlib]},
{mod, {snmp_app, []}},
- {runtime_dependencies, ["stdlib-2.5","runtime_tools-1.8.14","mnesia-4.12",
- "kernel-8.0","erts-12.0","crypto-4.6"]}]}.
+ {runtime_dependencies,
+ ["stdlib-@OTP-18490@","runtime_tools-1.8.14","mnesia-4.12",
+ "kernel-8.0","erts-12.0","crypto-4.6"]}]}.
diff --git a/lib/ssh/src/.gitignore b/lib/ssh/src/.gitignore
new file mode 100644
index 0000000000..3f9cfafd33
--- /dev/null
+++ b/lib/ssh/src/.gitignore
@@ -0,0 +1 @@
+deps
diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src
index 2cb8d80488..5e0c756cba 100644
--- a/lib/ssh/src/ssh.app.src
+++ b/lib/ssh/src/ssh.app.src
@@ -59,9 +59,9 @@
{mod, {ssh_app, []}},
{runtime_dependencies, [
"crypto-5.0",
- "erts-11.0",
- "kernel-6.0",
+ "erts-@OTP-17932@",
+ "kernel-@OTP-17932@",
"public_key-1.6.1",
- "stdlib-3.15",
+ "stdlib-@OTP-18490@","stdlib-@OTP-17932@",
"runtime_tools-1.15.1"
]}]}.
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index 00587eecba..7952574de7 100644
--- a/lib/ssh/src/ssh_acceptor.erl
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -93,7 +93,7 @@ acceptor_init(Parent, SystemSup,
Opts1 = ?DELETE_INTERNAL_OPT(lsocket, Opts),
acceptor_loop(Port, Address, Opts1, NewLSock, AcceptTimeout, SystemSup);
{error,Error} ->
- proc_lib:init_ack(Parent, {error,Error})
+ proc_lib:init_fail(Parent, {error,Error}, {exit, normal})
end
end;
@@ -104,7 +104,7 @@ acceptor_init(Parent, SystemSup,
proc_lib:init_ack(Parent, {ok, self()}),
acceptor_loop(Port, Address, Opts, LSock, AcceptTimeout, SystemSup);
{error,Error} ->
- proc_lib:init_ack(Parent, {error,Error})
+ proc_lib:init_fail(Parent, {error,Error}, {exit, normal})
end
end.
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index 13a44beea3..f83eba30bc 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -281,6 +281,10 @@ handle_msg({Group, get_unicode_state}, State) ->
Group ! {self(), get_unicode_state, false},
{ok, State};
+handle_msg({Group, get_terminal_state}, State) ->
+ Group ! {self(), get_terminal_state, true},
+ {ok, State};
+
handle_msg({Group, tty_geometry}, #state{group = Group,
pty = Pty
} = State) ->
@@ -428,6 +432,8 @@ io_request({move_rel, N}, Buf, Tty, _Group) ->
move_rel(N, Buf, Tty);
io_request({delete_chars,N}, Buf, Tty, _Group) ->
delete_chars(N, Buf, Tty);
+io_request(clear, Buf, _Tty, _Group) ->
+ {"\e[H\e[2J", Buf};
io_request(beep, Buf, _Tty, _Group) ->
{[7], Buf};
@@ -447,7 +453,7 @@ io_request(tty_geometry, Buf, Tty, Group) ->
io_request({put_chars_sync, Class, Cs, Reply}, Buf, Tty, Group) ->
%% We handle these asynchronous for now, if we need output guarantees
%% we have to handle these synchronously
- Group ! {reply, Reply},
+ Group ! {reply, Reply, ok},
io_request({put_chars, Class, Cs}, Buf, Tty, Group);
io_request(_R, Buf, _Tty, _Group) ->
@@ -666,7 +672,9 @@ start_shell(ConnectionHandler, State) ->
{_,_,_} = Shell ->
Shell
end,
- State#state{group = group:start(self(), ShellSpawner, [{echo, get_echo(State#state.pty)}]),
+ State#state{group = group:start(self(), ShellSpawner,
+ [{expand_below, false},
+ {echo, get_echo(State#state.pty)}]),
buf = empty_buf()}.
%%--------------------------------------------------------------------
@@ -687,7 +695,8 @@ start_exec_shell(ConnectionHandler, Cmd, State) ->
{M,F,A} ->
{M, F, A++[Cmd]}
end,
- State#state{group = group:start(self(), ExecShellSpawner, [{echo,false}]),
+ State#state{group = group:start(self(), ExecShellSpawner, [{expand_below, false},
+ {echo,false}]),
buf = empty_buf()}.
%%--------------------------------------------------------------------
@@ -771,7 +780,8 @@ exec_in_self_group(ConnectionHandler, ChannelId, WantReply, State, Fun) ->
end
end)
end,
- {ok, State#state{group = group:start(self(), Exec, [{echo,false}]),
+ {ok, State#state{group = group:start(self(), Exec, [{expand_below, false},
+ {echo,false}]),
buf = empty_buf()}}.
diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl
index 85b3399295..7ef69630fe 100644
--- a/lib/ssh/src/ssh_dbg.erl
+++ b/lib/ssh/src/ssh_dbg.erl
@@ -113,7 +113,7 @@ start(IoFmtFun) when is_function(IoFmtFun,2) ; is_function(IoFmtFun,3) ->
stop() ->
try
- dbg:stop_clear(),
+ dbg:stop(),
gen_server:stop(?SERVER)
catch
_:_ -> ok
diff --git a/lib/ssl/Makefile b/lib/ssl/Makefile
index 5cc111ec14..40f97eff5c 100644
--- a/lib/ssl/Makefile
+++ b/lib/ssl/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2021. All Rights Reserved.
+# Copyright Ericsson AB 1999-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -40,4 +40,6 @@ include $(ERL_TOP)/make/otp_subdir.mk
DIA_PLT_APPS=crypto runtime_tools inets public_key
+TEST_NEEDS_RELEASE=true
+
include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 803c0f789a..b4a30a68c8 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1999</year><year>2022</year>
+ <year>1999</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -229,6 +229,10 @@
</datatype>
<datatype>
+ <name name="legacy_named_curve"/>
+ </datatype>
+
+ <datatype>
<name name="psk_identity"/>
</datatype>
@@ -473,16 +477,10 @@
{sha384, ecdsa},
{sha384, rsa},
{sha256, ecdsa},
-{sha256, rsa},
-{sha224, ecdsa},
-{sha224, rsa},
-%% SHA
-{sha, ecdsa},
-{sha, rsa},
-{sha, dsa}
+{sha256, rsa}
]</code>
-<p>Support for {md5, rsa} was removed from the the TLS-1.2 default in ssl-8.0 (OTP-22) </p>
+<p>Support for {md5, rsa} was removed from the the TLS-1.2 default in ssl-8.0 (OTP-22) and support for SHA1 {sha, _} and SHA224 {sha224, _} was removed in ssl-11.0 (OTP-26) </p>
<p><c> rsa_pss_schemes =</c></p>
<code>
@@ -500,8 +498,6 @@ rsa_pss_rsae_sha256]
rsa_pkcs1_sha512, %% Corresponds to {sha512, rsa}
rsa_pkcs1_sha384, %% Corresponds to {sha384, rsa}
rsa_pkcs1_sha256, %% Corresponds to {sha256, rsa}
-ecdsa_sha1, %% Corresponds to {sha, ecdsa}
-rsa_pkcs1_sha1 %% Corresponds to {sha, rsa}
]
</code>
@@ -520,13 +516,13 @@ ecdsa_secp256r1_sha256] ++
rsa_pss_schemes()
</code>
- <p>EDDSA was made highest priority in ssl-11.0 (OTP-25) </p>
+ <p>EDDSA was made highest priority in ssl-10.8 (OTP-25) </p>
<p>TLS-1.3 default is</p>
-<code>Default_TLS_13_Schemes ++ Legacy_TLS_13_Schemes </code>
+<code>Default_TLS_13_Schemes</code>
<p>If both TLS-1.3 and TLS-1.2 are supported the default will be</p>
-<code>Default_TLS_13_Schemes ++ Default_TLS_12_Alg_Pairs </code>
+<code>Default_TLS_13_Schemes ++ TLS_13_Legacy_Schemes ++ Default_TLS_12_Alg_Pairs (not represented in TLS_13_Legacy_Schemes) </code>
<p>so appropriate algorithms can be chosen for the negotiated
version.
@@ -821,7 +817,7 @@ marker="public_key:public_key#pkix_path_validation/3">public_key:pkix_path_valid
<desc>
<code>
fun(Chain::[public_key:der_encoded()]) ->
- {trusted_ca, DerCert::public_key:der_encoded()} | unknown_ca}
+ {trusted_ca, DerCert::public_key:der_encoded()} | unknown_ca.
</code>
<p>Claim an intermediate CA in the chain as trusted. TLS then
performs <seemfa
@@ -1160,7 +1156,10 @@ fun(srp, Username :: binary(), UserState :: term()) ->
<name name="customize_hostname_check"/>
<desc>
<p> Customizes the hostname verification of the peer certificate, as different protocols that use
- TLS such as HTTP or LDAP may want to do it differently, for possible options see
+ TLS such as HTTP or LDAP may want to do it differently. For example the get standard HTTPS handling
+ provide the already implememnted fun from the public_key application for HTTPS.
+ <c>{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}</c>
+ For futher description of customize options see
<seemfa marker="public_key:public_key#pkix_verify_hostname/3">public_key:pkix_verify_hostname/3</seemfa> </p>
</desc>
</datatype>
@@ -1231,6 +1230,34 @@ fun(srp, Username :: binary(), UserState :: term()) ->
</desc>
</datatype>
+ <datatype>
+ <name name="use_srtp"/>
+ <desc>
+ <p>Configures the <c>use_srtp</c> DTLS hello extension.</p>
+ <p>In order to negotiate the use of SRTP data protection, clients
+ include an extension of type "use_srtp" in the DTLS extended client
+ hello. This extension MUST only be used when the data being
+ transported is RTP or RTCP.</p>
+ <p>The value is a map with a mandatory <c>protection_profiles</c> and
+ an optional <c>mki</c> parameters.</p>
+ <p><c>protection_profiles</c> configures the list of the client's acceptable
+ SRTP Protection Profiles. Each profile is a 2-byte binary. Example:
+ <c>#{protection_profiles =&gt; [&lt;&lt;0,2&gt;&gt;, &lt;&lt;0,5&gt;&gt;]}</c></p>
+ <p><c>mki</c> configures the SRTP Master Key Identifier chosen by the client.</p>
+ <p>The srtp_mki field contains the value of the SRTP MKI which is associated
+ with the SRTP master keys derived from this handshake. Each SRTP
+ session MUST have exactly one master key that is used to protect
+ packets at any given time. The client MUST choose the MKI value so
+ that it is distinct from the last MKI value that was used, and it
+ SHOULD make these values unique for the duration of the TLS session.</p>
+ <note><p>This extension MUST only be used with DTLS, and not with TLS.</p></note>
+ <note><p>OTP does not handle SRTP, so an external implementations of SRTP
+ encoder/decoder and a packet demultiplexer are needed to make use
+ of the <c>use_srtp</c> extension. See also
+ <seetype marker="#transport_option">cb_info</seetype> option.</p></note>
+ </desc>
+ </datatype>
+
<!-- <datatype> -->
<!-- <name name="ocsp_stapling"/> -->
<!-- <desc><p>If true, OCSP stapling will be enabled, an extension of type "status_request" will be -->
@@ -1472,15 +1499,34 @@ fun(srp, Username :: binary(), UserState :: term()) ->
<datatype>
<name name="server_session_tickets"/>
<desc>
- <p>Configures the session ticket functionality. Allowed values are <c>disabled</c>,
- <c>stateful</c> and <c>stateless</c>.</p>
- <p>If it is set to <c>stateful</c> or
- <c>stateless</c>, session resumption with pre-shared keys is enabled and the server will
- send stateful or stateless session tickets to the client after successful connections.</p>
+ <p>Configures the session ticket functionality. Allowed values are <c>disabled</c>,
+ <c>stateful</c>, <c>stateless</c>, <c>stateful_with_cert</c>, <c>stateless_with_cert</c>.</p>
+ <p>If it is not set to <c>disabled</c>,
+ session resumption with pre-shared keys is enabled and the server will
+ send stateful or stateless session tickets to the client after successful connections.</p>
+
+ <note><p>
+ Pre-shared key session ticket resumption does not include any certificate exchange,
+ hence the function <seemfa marker="ssl:ssl#peercert/1">ssl:peercert/1</seemfa> will not
+ be able to return the peer certificate as it is only communicated in the initial handshake.
+ The server options <c>stateful_with_cert</c> or <c>stateless_with_cert</c> may be used
+ to make a server associate the client certificate from the original handshake
+ with the tickets it issues.
+ </p></note>
+
<p>A stateful session ticket is a database reference to internal state information.
A stateless session ticket is a self-encrypted binary that contains both cryptographic keying
material and state data.
</p>
+
+ <warning><p>
+ If it is set to <c>stateful_with_cert</c> the client certificate
+ is stored with the internal state information, increasing memory consumption.
+ If it is set to <c>stateless_with_cert</c> the client certificate is
+ encoded in the self-encrypted binary that is sent to the client,
+ increasing the payload size.
+ </p></warning>
+
<note><p>This option is supported by TLS 1.3 and above. See also
<seeguide marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">
SSL's Users Guide, Session Tickets and Session Resumption in TLS 1.3</seeguide>
@@ -1489,6 +1535,26 @@ fun(srp, Username :: binary(), UserState :: term()) ->
</datatype>
<datatype>
+ <name name="stateless_tickets_seed"/>
+ <desc>
+ <p>Configures the seed used for the encryption of stateless session tickets.
+ Allowed values are any randomly generated <c>binary()</c>. If this option is not
+ configured, an encryption seed will be randomly generated.</p>
+
+ <warning><p>Reusing the ticket encryption seed between multiple server
+ instances enables stateless session tickets to work across multiple server
+ instances, but it breaks anti-replay protection across instances.</p>
+
+ <p>Inaccurate time synchronization between server instances can also
+ affect session ticket freshness checks, potentially causing false negatives as
+ well as false positives.</p></warning>
+
+ <note><p>This option is supported by TLS 1.3 and above and only with stateless
+ session tickets.</p></note>
+ </desc>
+ </datatype>
+
+ <datatype>
<name name="anti_replay"/>
<desc>
<p>Configures the server's built-in anti replay feature based on Bloom filters.</p>
@@ -1496,7 +1562,7 @@ fun(srp, Username :: binary(), UserState :: term()) ->
<p>Allowed values are the pre-defined <c>'10k'</c>, <c>'100k'</c> or a custom 3-tuple that
defines the properties of the bloom filters: <c>{WindowSize, HashFunctions, Bits}</c>.
<c>WindowSize</c> is the number of seconds after the current Bloom filter is rotated
- and also the window size used for freshness checks. <c>HashFunctions</c> is the number
+ and also the window size used for freshness checks of ClientHello. <c>HashFunctions</c> is the number
hash functions and <c>Bits</c> is the number of bits in the bit vector.
<c>'10k'</c> and <c>'100k'</c> are simple defaults with the following properties:</p>
<list type="bulleted">
@@ -1544,6 +1610,40 @@ fun(srp, Username :: binary(), UserState :: term()) ->
</datatype>
<datatype>
+ <name name="use_srtp"/>
+ <desc>
+ <p>Configures the <c>use_srtp</c> DTLS hello extension.</p>
+ <p>Servers that receive an extended hello containing a "use_srtp"
+ extension can agree to use SRTP by including an extension of type
+ "use_srtp", with the chosen protection profile in the extended server
+ hello. This extension MUST only be used when the data being
+ transported is RTP or RTCP.</p>
+ <p>The value is a map with a mandatory <c>protection_profiles</c> and
+ an optional <c>mki</c> parameters.</p>
+ <list type="bulleted">
+ <item><p><c>protection_profiles</c> configures the list of the server's chosen
+ SRTP Protection Profile as a list of a single 2-byte binary. Example:
+ <c>#{protection_profiles =&gt; [&lt;&lt;0,5&gt;&gt;]}</c></p></item>
+ <item><p><c>mki</c> configures the server's SRTP Master Key Identifier.</p>
+ <p>Upon receipt of a "use_srtp" extension containing a "srtp_mki" field,
+ the server MUST either (assuming it accepts the extension at all):</p>
+ <list type="bulleted">
+ <item><p>include a matching "srtp_mki" value in its "use_srtp" extension
+ to indicate that it will make use of the MKI, or</p></item>
+ <item><p>return an empty "srtp_mki" value to indicate that it cannot
+ make use of the MKI (default).</p></item>
+ </list>
+ </item>
+ </list>
+ <note><p>This extension MUST only be used with DTLS, and not with TLS.</p></note>
+ <note><p>OTP does not handle SRTP, so an external implementations of SRTP
+ encoder/decoder and a packet demultiplexer are needed to make use
+ of the <c>use_srtp</c> extension. See also
+ <seetype marker="#transport_option">cb_info</seetype> option.</p></note>
+ </desc>
+ </datatype>
+
+ <datatype>
<name name="connection_info"/>
</datatype>
@@ -2019,6 +2119,12 @@ fun(srp, Username :: binary(), UserState :: term()) ->
<c>{error, renegotiation_rejected}</c> indicating that the peer
refused to go through with the renegotiation, but the connection
is still active using the previously negotiated session.</p>
+ <p>TLS-1.3 has removed the renegotiate feature of earlier TLS versions
+ and instead adds a new feature called key update that replaces the most
+ important part of renegotiate, that is the refreshing of session keys.
+ This is triggered automatically after reaching a plaintext limit and
+ can be configured by option <seetype marker="ssl:ssl#key_update_at">key_update_at</seetype>.
+ </p>
</desc>
</func>
@@ -2067,6 +2173,60 @@ fun(srp, Username :: binary(), UserState :: term()) ->
is useful.</p>
</desc>
</func>
+
+ <func>
+ <name since="OTP @OTP-18572@" name="signature_algs" arity="2" />
+ <fsummary>Returns a list of signature algorithms/schemes </fsummary>
+ <desc>
+ <p>Lists all possible signature algorithms corresponding to
+ <c>Description</c> that are available. The
+ <c>exclusive</c> option will exclusively list
+ algorithms/schemes for that protocol version, whereas the
+ <c>default</c> and <c>all</c> options lists the combined list to support the
+ range of protocols from (D)TLS-1.2, the first version to support
+ configuration of the signature algorithms, to <c>Version</c>.</p>
+
+ <p> Example: <c>
+
+ 1&gt; ssl:signature_algs(default, 'tlsv1.3').
+ [eddsa_ed25519,eddsa_ed448,ecdsa_secp521r1_sha512,
+ ecdsa_secp384r1_sha384,ecdsa_secp256r1_sha256,
+ rsa_pss_pss_sha512,rsa_pss_pss_sha384,rsa_pss_pss_sha256,
+ rsa_pss_rsae_sha512,rsa_pss_rsae_sha384,rsa_pss_rsae_sha256,
+ rsa_pkcs1_sha512,rsa_pkcs1_sha384,rsa_pkcs1_sha256,
+ {sha512,ecdsa},
+ {sha384,ecdsa},
+ {sha256,ecdsa}]
+
+ 2&gt;ssl:signature_algs(all, 'tlsv1.3').
+ [eddsa_ed25519,eddsa_ed448,ecdsa_secp521r1_sha512,
+ ecdsa_secp384r1_sha384,ecdsa_secp256r1_sha256,
+ rsa_pss_pss_sha512,rsa_pss_pss_sha384,rsa_pss_pss_sha256,
+ rsa_pss_rsae_sha512,rsa_pss_rsae_sha384,rsa_pss_rsae_sha256,
+ rsa_pkcs1_sha512,rsa_pkcs1_sha384,rsa_pkcs1_sha256,
+ {sha512,ecdsa},
+ {sha384,ecdsa},
+ {sha256,ecdsa},
+ {sha224,ecdsa},
+ {sha224,rsa},
+ {sha,rsa},
+ {sha,dsa}]
+
+ 3&gt; ssl:signature_algs(exclusive, 'tlsv1.3').
+ [eddsa_ed25519,eddsa_ed448,ecdsa_secp521r1_sha512,
+ ecdsa_secp384r1_sha384,ecdsa_secp256r1_sha256,
+ rsa_pss_pss_sha512,rsa_pss_pss_sha384,rsa_pss_pss_sha256,
+ rsa_pss_rsae_sha512,rsa_pss_rsae_sha384,rsa_pss_rsae_sha256]
+ </c></p>
+
+ <note><p>Some TLS-1-3 scheme names overlap with TLS-1.2
+ algorithm-tuple-pair-names and then TLS-1.3 names will be
+ used, for example <c>rsa_pkcs1_sha256</c> instead of
+ <c>{sha256, rsa}</c> these are legacy algorithms in TLS-1.3
+ that apply only to certificate signatures in this version of
+ the protocol.</p></note>
+ </desc>
+ </func>
<func>
<name since="" name="sockname" arity="1" />
diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml
index 0311174978..725c219f13 100644
--- a/lib/ssl/doc/src/standards_compliance.xml
+++ b/lib/ssl/doc/src/standards_compliance.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2015</year>
- <year>2022</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -327,8 +327,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">use_srtp (RFC5764)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">26.0</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -466,8 +466,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">use_srtp (RFC5764)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">26.0</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -594,6 +594,12 @@
<cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle"><em>22.1</em></cell>
</row>
+ <row>
+ <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle">use_srtp (RFC5764)</cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">26.0</cell>
+ </row>
<row>
<cell align="left" valign="middle"></cell>
@@ -625,6 +631,12 @@
<cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle"><em>22</em></cell>
</row>
+ <row>
+ <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle">use_srtp (RFC5764)</cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">26.0</cell>
+ </row>
<row>
<cell align="left" valign="middle">
@@ -910,14 +922,14 @@
</url>
</cell>
<cell align="left" valign="middle"><em>Client</em></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">24.3</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>Server</em></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"><em></em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>24.3</em></cell>
</row>
<row>
@@ -1254,8 +1266,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">application_layer_protocol_negotiation (RFC7301)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">23.0</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1277,13 +1289,6 @@
</row>
<row>
<cell align="left" valign="middle"></cell>
- <cell align="left" valign="middle">supported_versions (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
- </row>
-
- <row>
- <cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>Server</em></cell>
<cell align="left" valign="middle"><em>PC</em></cell>
<cell align="left" valign="middle"><em>22</em></cell>
@@ -1321,8 +1326,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">application_layer_protocol_negotiation (RFC7301)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">23.0</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1343,13 +1348,6 @@
<cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
- <cell align="left" valign="middle"></cell>
- <cell align="left" valign="middle">supported_versions (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
- </row>
-
- <row>
<cell align="left" valign="middle">
<url href="https://tools.ietf.org/html/rfc8446#section-4.3.2">
4.3.2. Certificate Request
diff --git a/lib/ssl/doc/src/using_ssl.xml b/lib/ssl/doc/src/using_ssl.xml
index 318e367245..148c7d8dfc 100644
--- a/lib/ssl/doc/src/using_ssl.xml
+++ b/lib/ssl/doc/src/using_ssl.xml
@@ -776,7 +776,7 @@ ssl:connect("localhost", 9999, [{verify, verify_peer},
less than ticket lifetime.</p></item>
<item><p>Actual ticket age shall be less than the ticket lifetime (stateless session
tickets contain the servers timestamp when the ticket was issued).</p></item>
- <item><p>Ticket shall be used within specified time window (freshness checks).</p></item>
+ <item><p>ClientHello created with the ticket shall be sent relatively recently (freshness checks).</p></item>
<item><p>If all above checks passed both <em>current</em> and <em>old</em> Bloom filters
are checked to detect if binder was already seen. Being a probabilistic data structure,
false positives can occur and they trigger a full handshake.</p></item>
diff --git a/lib/ssl/src/.gitignore b/lib/ssl/src/.gitignore
new file mode 100644
index 0000000000..3f9cfafd33
--- /dev/null
+++ b/lib/ssl/src/.gitignore
@@ -0,0 +1 @@
+deps
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 789bed5c3f..6395834fe9 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -39,6 +39,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssl-$(VSN)
# ----------------------------------------------------
BEHAVIOUR_MODULES= \
+ ssl_trace \
ssl_crl_cache_api \
ssl_session_cache_api
@@ -94,8 +95,10 @@ MODULES= \
tls_dtls_connection \
tls_connection \
tls_connection_sup \
- tls_connection_1_3 \
- tls_gen_connection \
+ tls_server_connection_1_3 \
+ tls_client_connection_1_3 \
+ tls_gen_connection_1_3 \
+ tls_gen_connection \
tls_handshake \
tls_handshake_1_3 \
tls_record \
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 08229d8bb5..899e7d3305 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -88,7 +88,7 @@
%% | Abbrev Flight 1 to Abbrev Flight 2 part 1
%% |
%% New session | Resumed session
-%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED
+%% WAIT_OCSP_STAPLING CERTIFY <----------------------------------> ABBREVIATED
%%
%% <- Possibly Receive -- | |
%% OCSP Stapel ------> | Send/ Recv Flight 5 |
@@ -155,17 +155,20 @@
code_change/4,
format_status/2]).
+%% Tracing
+-export([handle_trace/3]).
+
%%====================================================================
%% Internal application API
-%%====================================================================
+%%====================================================================
%%====================================================================
%% Setup
-%%====================================================================
+%%====================================================================
init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
process_flag(trap_exit, true),
State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
try
- State = ssl_gen_statem:ssl_config(State0#state.ssl_options,
+ State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options,
Role, State0),
gen_statem:enter_loop(?MODULE, [], initial_hello, State)
catch
@@ -175,8 +178,8 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
%%====================================================================
-%% Handshake
-%%====================================================================
+%% Handshake
+%%====================================================================
renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
@@ -191,7 +194,7 @@ renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) -
dtls_gen_connection:next_event(hello, no_record, State, Actions ++ MoreActions).
%%--------------------------------------------------------------------
-%% State functions
+%% State functions
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
-spec initial_hello(gen_statem:event_type(),
@@ -199,7 +202,7 @@ renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) -
gen_statem:state_function_result().
%%--------------------------------------------------------------------
initial_hello(enter, _, State) ->
- {keep_state, State};
+ {keep_state, State};
initial_hello({call, From}, {start, Timeout},
#state{static_env = #static_env{host = Host,
port = Port,
@@ -297,34 +300,32 @@ hello(internal, #client_hello{cookie = <<>>,
catch throw:#alert{} = Alert ->
alert_or_reset_connection(Alert, ?FUNCTION_NAME, State0)
end;
-hello(internal, #hello_verify_request{cookie = Cookie},
+hello(internal, #hello_verify_request{cookie = Cookie},
#state{static_env = #static_env{role = client,
host = Host,
port = Port},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
ocsp_stapling_state = OcspState0} = HsEnv,
connection_env = CEnv,
- ssl_options = #{ocsp_stapling := OcspStaplingOpt,
- ocsp_nonce := OcspNonceOpt} = SslOpts,
+ ssl_options = SslOpts,
session = #session{session_id = Id},
connection_states = ConnectionStates0,
protocol_specific = PS
} = State0) ->
- OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
+ OcspNonce = tls_handshake:ocsp_nonce(SslOpts),
Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0,
SslOpts, Id, Renegotiation, OcspNonce),
Version = Hello#client_hello.client_version,
- State1 = prepare_flight(State0#state{handshake_env =
- HsEnv#handshake_env{tls_handshake_history
- = ssl_handshake:init_handshake_history(),
- ocsp_stapling_state =
- OcspState0#{ocsp_nonce => OcspNonce}}}),
-
- {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1),
-
- State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, % RequestedVersion
- protocol_specific = PS#{current_cookie_secret => Cookie}
- },
+ State1 =
+ prepare_flight(
+ State0#state{handshake_env =
+ HsEnv#handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}}}),
+ {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1),
+ State = State2#state{connection_env =
+ CEnv#connection_env{negotiated_version = Version}, % RequestedVersion
+ protocol_specific = PS#{current_cookie_secret => Cookie}},
dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, Actions);
hello(internal, #client_hello{extensions = Extensions} = Hello,
#state{handshake_env = #handshake_env{continue_status = pause},
@@ -372,11 +373,11 @@ hello(internal, #server_hello{} = Hello,
try
{Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} =
dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId),
- tls_dtls_connection:handle_session(Hello,
- Version, NewId, ConnectionStates, ProtoExt, Protocol,
- State#state{handshake_env =
- HsEnv#handshake_env{
- ocsp_stapling_state = maps:merge(OcspState0,OcspState)}})
+ tls_dtls_connection:handle_session(
+ Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol,
+ State#state{handshake_env =
+ HsEnv#handshake_env{
+ ocsp_stapling_state = maps:merge(OcspState0,OcspState)}})
catch throw:#alert{} = Alert ->
ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State)
end;
@@ -478,10 +479,7 @@ wait_cert_verify(info, Event, State) ->
wait_cert_verify(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
wait_cert_verify(Type, Event, State) ->
- try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State)
- catch throw:#alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State)
- end.
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(), term(), #state{}) ->
@@ -505,7 +503,7 @@ cipher(internal = Type, #finished{} = Event, #state{connection_states = Connecti
cipher(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -654,7 +652,7 @@ format_status(Type, Data) ->
%%% Internal functions
%%--------------------------------------------------------------------
initial_state(Role, Host, Port, Socket,
- {#{client_renegotiation := ClientRenegotiation} = SSLOptions, SocketOptions, Trackers}, User,
+ {SSLOptions, SocketOptions, Trackers}, User,
{CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
put(log_level, maps:get(log_level, SSLOptions)),
BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled),
@@ -681,13 +679,11 @@ initial_state(Role, Host, Port, Socket,
handshake_env = #handshake_env{
tls_handshake_history = ssl_handshake:init_handshake_history(),
renegotiation = {false, first},
- allow_renegotiate = ClientRenegotiation
+ allow_renegotiate = maps:get(client_renegotiation, SSLOptions, undefined)
},
connection_env = #connection_env{user_application = {Monitor, User}},
socket_options = SocketOptions,
- %% We do not want to save the password in the state so that
- %% could be written in the clear into error logs.
- ssl_options = SSLOptions#{password => undefined},
+ ssl_options = SSLOptions,
session = #session{is_resumable = false},
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
@@ -762,6 +758,8 @@ alert_or_reset_connection(Alert, StateName, #state{connection_states = Cs} = Sta
{next_state, connection, NewState}
end.
+gen_handshake(_, {call, _From}, {application_data, _Data}, _State) ->
+ {keep_state_and_data, [postpone]};
gen_handshake(StateName, Type, Event, State) ->
try tls_dtls_connection:StateName(Type, Event, State)
catch
@@ -877,3 +875,12 @@ is_time_to_renegotiate(N, M) when N < M->
is_time_to_renegotiate(_,_) ->
true.
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(hbn,
+ {call, {?MODULE, connection,
+ [_Type = info, Event, _State]}},
+ Stack) ->
+ {io_lib:format("Type = info Event = ~W ", [Event, 10]), Stack}.
diff --git a/lib/ssl/src/dtls_gen_connection.erl b/lib/ssl/src/dtls_gen_connection.erl
index c075fa5879..446a065ac3 100644
--- a/lib/ssl/src/dtls_gen_connection.erl
+++ b/lib/ssl/src/dtls_gen_connection.erl
@@ -82,10 +82,10 @@
%%====================================================================
%% Setup
%%====================================================================
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts,
+start_fsm(Role, Host, Port, Socket, {_,_, Tracker} = Opts,
User, {CbModule, _, _, _, _} = CbInfo,
- Timeout) ->
- try
+ Timeout) ->
+ try
{ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket,
Opts, User, CbInfo]),
{ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker),
@@ -545,10 +545,9 @@ handle_info({CloseTag, Socket}, StateName,
%% with widespread implementation practice.
case (Active == false) andalso (CTs =/= []) of
false ->
- case Version of
- {254, N} when N =< 253 ->
+ if (?DTLS_GTE(Version, ?DTLS_1_2)) ->
ok;
- _ ->
+ true ->
%% As invalidate_sessions here causes performance issues,
%% we will conform to the widespread implementation
%% practice and go against the spec
@@ -634,7 +633,7 @@ next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
ssl_options = SslOpts} = State0) ->
case dtls_record:get_dtls_records(Data,
{DataTag, StateName, Version,
- [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]},
+ [dtls_record:protocol_version_name(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]},
Buf0, SslOpts) of
{Records, Buf1} ->
CT1 = CT0 ++ Records,
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 10258dcc50..85faad11b6 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -118,11 +118,12 @@ hello(#client_hello{client_version = ClientVersion} = Hello,
Version = ssl_handshake:select_version(dtls_record, ClientVersion, Versions),
handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation).
-cookie(Key, Address, Port, #client_hello{client_version = {Major, Minor},
+cookie(Key, Address, Port, #client_hello{client_version = Version,
random = Random,
session_id = SessionId,
cipher_suites = CipherSuites,
compression_methods = CompressionMethods}) ->
+ {Major, Minor} = Version,
CookieData = [address_to_bin(Address, Port),
<<?BYTE(Major), ?BYTE(Minor)>>,
Random, SessionId, CipherSuites, CompressionMethods],
@@ -189,7 +190,7 @@ handle_client_hello(Version,
TLSVersion = dtls_v1:corresponding_tls_version(Version),
AvailableHashSigns = ssl_handshake:available_signature_algs(
ClientHashSigns, SupportedHashSigns, TLSVersion),
- ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, TLSVersion, ECCOrder),
+ ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder),
{Type, #session{cipher_suite = CipherSuite,
own_certificates = [OwnCert |_]} = Session1}
= ssl_handshake:select_session(SugesstedId, CipherSuites,
@@ -219,32 +220,35 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation, HashSign) ->
{Session, ConnectionStates, Protocol, ServerHelloExt} =
ssl_handshake:handle_client_hello_extensions(dtls_record, Random, CipherSuites,
- HelloExt, dtls_v1:corresponding_tls_version(Version),
- SslOpts, Session0,
+ HelloExt,
+ dtls_v1:corresponding_tls_version(Version),
+ SslOpts, Session0,
ConnectionStates0, Renegotiation,
Session0#session.is_resumable),
{Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}.
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
- Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) ->
+ Compression, HelloExt, SslOpt, ConnectionStates0,
+ Renegotiation, IsNew) ->
{ConnectionStates, ProtoExt, Protocol, OcspState} =
- ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
- Compression, HelloExt,
- dtls_v1:corresponding_tls_version(Version),
- SslOpt, ConnectionStates0, Renegotiation, IsNew),
+ ssl_handshake:handle_server_hello_extensions(
+ dtls_record, Random, CipherSuite, Compression, HelloExt,
+ dtls_v1:corresponding_tls_version(Version), SslOpt, ConnectionStates0,
+ Renegotiation, IsNew),
{Version, SessionId, ConnectionStates, ProtoExt, Protocol, OcspState}.
%%--------------------------------------------------------------------
-enc_handshake(#hello_verify_request{protocol_version = {Major, Minor},
+enc_handshake(#hello_verify_request{protocol_version = Version,
cookie = Cookie}, _Version) ->
CookieLength = byte_size(Cookie),
+ {Major,Minor} = Version,
{?HELLO_VERIFY_REQUEST, <<?BYTE(Major), ?BYTE(Minor),
?BYTE(CookieLength),
Cookie:CookieLength/binary>>};
enc_handshake(#hello_request{}, _Version) ->
{?HELLO_REQUEST, <<>>};
-enc_handshake(#client_hello{client_version = {Major, Minor},
+enc_handshake(#client_hello{client_version = ClientVersion,
random = Random,
session_id = SessionID,
cookie = Cookie,
@@ -257,8 +261,8 @@ enc_handshake(#client_hello{client_version = {Major, Minor},
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
- ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions,
- dtls_v1:corresponding_tls_version({Major, Minor})),
+ ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions),
+ {Major,Minor} = ClientVersion,
{?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
@@ -362,8 +366,8 @@ decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?UINT24(_), ?UINT16(_),
?BYTE(Major), ?BYTE(Minor),
?BYTE(CookieLength),
Cookie:CookieLength/binary>>) ->
- #hello_verify_request{protocol_version = {Major, Minor},
- cookie = Cookie};
+ #hello_verify_request{protocol_version = {Major,Minor},
+ cookie = Cookie};
decode_handshake(Version, Tag, <<?UINT24(_), ?UINT16(_),
?UINT24(_), ?UINT24(_), Msg/binary>>) ->
%% DTLS specifics stripped
@@ -388,9 +392,9 @@ decode_handshake_fragments(<<?BYTE(Type), ?UINT24(Length),
reassemble(Version, #handshake_fragment{message_seq = Seq} = Fragment,
#protocol_buffers{dtls_handshake_next_seq = Seq,
- dtls_handshake_next_fragments = Fragments0,
- dtls_handshake_later_fragments = LaterFragments0} =
- Buffers0)->
+ dtls_handshake_next_fragments = Fragments0,
+ dtls_handshake_later_fragments = LaterFragments0} =
+ Buffers0)->
case reassemble_fragments(Fragment, Fragments0) of
{more_data, Fragments} ->
{more_data, Buffers0#protocol_buffers{dtls_handshake_next_fragments = Fragments}};
diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl
index 2c89ce15d5..ea7a1d72a0 100644
--- a/lib/ssl/src/dtls_handshake.hrl
+++ b/lib/ssl/src/dtls_handshake.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -29,9 +29,10 @@
-include("tls_handshake.hrl"). %% Common TLS and DTLS records and Constantes
-include("ssl_handshake.hrl"). %% Common TLS and DTLS records and Constantes
-include("ssl_api.hrl").
+-include("ssl_record.hrl").
-define(HELLO_VERIFY_REQUEST, 3).
--define(HELLO_VERIFY_REQUEST_VERSION, {254, 255}).
+-define(HELLO_VERIFY_REQUEST_VERSION, ?DTLS_1_0).
-record(hello_verify_request, {
protocol_version,
@@ -48,10 +49,9 @@
}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% RFC 7764 Datagram Transport Layer Security (DTLS) Extension to Establish Keys
+%% RFC 5764 Datagram Transport Layer Security (DTLS) Extension to Establish Keys
%% for the Secure Real-time Transport Protocol (SRTP)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Not supported
--define(USE_SRTP, 14).
+%% Defined in ssl_handshake.hrl because extension parsing code is in ssl_handshake.erl
-endif. % -ifdef(dtls_handshake).
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index dadb16d250..c0030fe1dc 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -43,7 +43,7 @@
-export([decode_cipher_text/2]).
%% Protocol version handling
--export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2,
+-export([protocol_version/1, protocol_version_name/1, lowest_protocol_version/1, lowest_protocol_version/2,
highest_protocol_version/1, highest_protocol_version/2,
is_higher/2, supported_protocol_versions/0,
is_acceptable_version/2, hello_version/2]).
@@ -141,14 +141,14 @@ set_connection_state_by_epoch(ReadState, Epoch, #{saved_read := #{epoch := Epoch
%%--------------------------------------------------------------------
-spec init_connection_state_seq(ssl_record:ssl_version(), ssl_record:connection_states()) ->
- ssl_record:connection_state().
+ ssl_record:connection_state().
%%
%% Description: Copy the read sequence number to the write sequence number
%% This is only valid for DTLS in the first client_hello
%%--------------------------------------------------------------------
-init_connection_state_seq({254, _},
+init_connection_state_seq(Version,
#{current_read := #{epoch := 0, sequence_number := Seq},
- current_write := #{epoch := 0} = Write} = ConnnectionStates0) ->
+ current_write := #{epoch := 0} = Write} = ConnnectionStates0) when ?DTLS_1_X(Version)->
ConnnectionStates0#{current_write => Write#{sequence_number => Seq}};
init_connection_state_seq(_, ConnnectionStates) ->
ConnnectionStates.
@@ -263,85 +263,83 @@ decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnnectionStates0) ->
%% Protocol version handling
%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec protocol_version_name(dtls_atom_version()) -> ssl_record:ssl_version().
+%%
+%% Description: Creates a protocol version record from a version atom
+%% or vice versa.
%%--------------------------------------------------------------------
--spec protocol_version(dtls_atom_version() | ssl_record:ssl_version()) ->
- ssl_record:ssl_version() | dtls_atom_version().
+
+protocol_version_name('dtlsv1.2') ->
+ ?DTLS_1_2;
+protocol_version_name(dtlsv1) ->
+ ?DTLS_1_0.
+
+%%--------------------------------------------------------------------
+-spec protocol_version(ssl_record:ssl_version()) -> dtls_atom_version().
+
%%
%% Description: Creates a protocol version record from a version atom
%% or vice versa.
%%--------------------------------------------------------------------
-protocol_version('dtlsv1.2') ->
- {254, 253};
-protocol_version(dtlsv1) ->
- {254, 255};
-protocol_version({254, 253}) ->
+
+protocol_version(?DTLS_1_2) ->
'dtlsv1.2';
-protocol_version({254, 255}) ->
+protocol_version(?DTLS_1_0) ->
dtlsv1.
%%--------------------------------------------------------------------
-spec lowest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version().
%%
%% Description: Lowes protocol version of two given versions
%%--------------------------------------------------------------------
-lowest_protocol_version(Version = {M, N}, {M, O}) when N > O ->
- Version;
-lowest_protocol_version({M, _}, Version = {M, _}) ->
- Version;
-lowest_protocol_version(Version = {M,_}, {N, _}) when M > N ->
- Version;
-lowest_protocol_version(_,Version) ->
- Version.
+lowest_protocol_version(Version1, Version2) when ?DTLS_LT(Version1, Version2) ->
+ Version1;
+lowest_protocol_version(_, Version2) ->
+ Version2.
%%--------------------------------------------------------------------
-spec lowest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version().
%%
%% Description: Lowest protocol version present in a list
%%--------------------------------------------------------------------
-lowest_protocol_version([]) ->
- lowest_protocol_version();
lowest_protocol_version(Versions) ->
- [Ver | Vers] = Versions,
- lowest_list_protocol_version(Ver, Vers).
+ check_protocol_version(Versions, fun lowest_protocol_version/2).
%%--------------------------------------------------------------------
-spec highest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version().
%%
%% Description: Highest protocol version present in a list
%%--------------------------------------------------------------------
-highest_protocol_version([]) ->
- highest_protocol_version();
highest_protocol_version(Versions) ->
- [Ver | Vers] = Versions,
- highest_list_protocol_version(Ver, Vers).
+ check_protocol_version(Versions, fun highest_protocol_version/2).
+
+
+check_protocol_version([], Fun) -> check_protocol_version(supported_protocol_versions(), Fun);
+check_protocol_version([Ver | Versions], Fun) -> lists:foldl(Fun, Ver, Versions).
%%--------------------------------------------------------------------
-spec highest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version().
%%
%% Description: Highest protocol version of two given versions
%%--------------------------------------------------------------------
-highest_protocol_version(Version = {M, N}, {M, O}) when N < O ->
- Version;
-highest_protocol_version({M, _},
- Version = {M, _}) ->
- Version;
-highest_protocol_version(Version = {M,_},
- {N, _}) when M < N ->
- Version;
-highest_protocol_version(_,Version) ->
- Version.
+
+highest_protocol_version(Version1, Version2) when ?DTLS_GT(Version1, Version2) ->
+ Version1;
+highest_protocol_version(_, Version2) ->
+ Version2.
%%--------------------------------------------------------------------
-spec is_higher(V1 :: ssl_record:ssl_version(), V2::ssl_record:ssl_version()) -> boolean().
%%
%% Description: Is V1 > V2
%%--------------------------------------------------------------------
-is_higher({M, N}, {M, O}) when N < O ->
- true;
-is_higher({M, _}, {N, _}) when M < N ->
+is_higher(V1, V2) when ?DTLS_GT(V1, V2) ->
true;
is_higher(_, _) ->
false.
+
%%--------------------------------------------------------------------
-spec supported_protocol_versions() -> [ssl_record:ssl_version()].
%%
@@ -349,7 +347,7 @@ is_higher(_, _) ->
%%--------------------------------------------------------------------
supported_protocol_versions() ->
Fun = fun(Version) ->
- protocol_version(Version)
+ protocol_version_name(Version)
end,
case application:get_env(ssl, dtls_protocol_version) of
undefined ->
@@ -397,7 +395,7 @@ is_acceptable_version(Version, Versions) ->
-spec hello_version(ssl_record:ssl_version(), [ssl_record:ssl_version()]) -> ssl_record:ssl_version().
hello_version(Version, Versions) ->
case dtls_v1:corresponding_tls_version(Version) of
- TLSVersion when TLSVersion >= {3, 3} ->
+ TLSVersion when ?TLS_GTE(TLSVersion, ?TLS_1_2) ->
Version;
_ ->
lowest_protocol_version(Versions)
@@ -433,10 +431,11 @@ get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo,
orelse ((StateName == abbreviated) andalso (DataTag == udp)))
andalso ((Type == ?HANDSHAKE) orelse (Type == ?ALERT)) ->
ssl_logger:debug(LogLevel, inbound, 'record', [RawDTLSRecord]),
- Acc = [#ssl_tls{type = Type, version = {MajVer, MinVer},
+ Version = {MajVer,MinVer},
+ Acc = [#ssl_tls{type = Type, version = Version,
epoch = Epoch, sequence_number = SequenceNumber,
fragment = Data} | Acc0],
- case is_acceptable_version({MajVer, MinVer}, Versions) of
+ case is_acceptable_version(Version, Versions) of
true ->
get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel);
false ->
@@ -452,13 +451,14 @@ get_dtls_records_aux({_, _, Version, Versions} = Vinfo,
(Type == ?ALERT) orelse
(Type == ?CHANGE_CIPHER_SPEC) ->
ssl_logger:debug(LogLevel, inbound, 'record', [RawDTLSRecord]),
- Acc = [#ssl_tls{type = Type, version = {MajVer,MinVer},
+ Version1 = {MajVer,MinVer},
+ Acc = [#ssl_tls{type = Type, version = Version,
epoch = Epoch, sequence_number = SequenceNumber,
fragment = Data} | Acc0],
- if {MajVer, MinVer} =:= Version ->
+ if Version1 =:= Version ->
get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel);
Type == ?HANDSHAKE ->
- case is_acceptable_version({MajVer, MinVer}, Versions) of
+ case is_acceptable_version(Version1, Versions) of
true ->
get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel);
false ->
@@ -529,9 +529,10 @@ update_replay_window(SequenceNumber,
%%--------------------------------------------------------------------
-encode_dtls_cipher_text(Type, {MajVer, MinVer}, Fragment,
+encode_dtls_cipher_text(Type, Version, Fragment,
#{epoch := Epoch, sequence_number := Seq} = WriteState) ->
Length = erlang:iolist_size(Fragment),
+ {MajVer,MinVer} = Version,
{[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Epoch),
?UINT48(Seq), ?UINT16(Length)>>, Fragment],
WriteState#{sequence_number => Seq + 1}}.
@@ -632,32 +633,19 @@ calc_mac_hash(Type, Version, #{mac_secret := MacSecret,
mac_hash(Version, MacAlg, MacSecret, Epoch, SeqNo, Type,
Length, Fragment).
-mac_hash({Major, Minor}, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment) ->
+mac_hash(Version, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment) ->
+ {Major,Minor} = Version,
Value = [<<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type),
?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>,
Fragment],
dtls_v1:hmac_hash(MacAlg, MacSecret, Value).
-start_additional_data(Type, {MajVer, MinVer}, Epoch, SeqNo) ->
+start_additional_data(Type, Version, Epoch, SeqNo) ->
+ {MajVer,MinVer} = Version,
<<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>.
%%--------------------------------------------------------------------
-lowest_list_protocol_version(Ver, []) ->
- Ver;
-lowest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest).
-
-highest_list_protocol_version(Ver, []) ->
- Ver;
-highest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest).
-
-highest_protocol_version() ->
- highest_protocol_version(supported_protocol_versions()).
-
-lowest_protocol_version() ->
- lowest_protocol_version(supported_protocol_versions()).
sufficient_dtlsv1_2_crypto_support() ->
CryptoSupport = crypto:supports(),
diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl
index 3ac6d8226e..851de78963 100644
--- a/lib/ssl/src/dtls_v1.erl
+++ b/lib/ssl/src/dtls_v1.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
-module(dtls_v1).
-include("ssl_cipher.hrl").
+-include("ssl_record.hrl").
-export([suites/1,
all_suites/1,
@@ -35,13 +36,13 @@
-define(COOKIE_BASE_TIMEOUT, 30000).
--spec suites(Minor:: 253|255) -> [ssl_cipher_format:cipher_suite()].
+-spec suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
-suites(Minor) ->
+suites(Version) ->
lists:filter(fun(Cipher) ->
is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
end,
- tls_v1:suites(corresponding_minor_tls_version(Minor))).
+ tls_v1:suites(corresponding_tls_version(Version))).
all_suites(Version) ->
lists:filter(fun(Cipher) ->
is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
@@ -54,27 +55,30 @@ anonymous_suites(Version) ->
end,
ssl_cipher:anonymous_suites(corresponding_tls_version(Version))).
-exclusive_suites(Minor) ->
+exclusive_suites(Version) ->
lists:filter(fun(Cipher) ->
is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
end,
- tls_v1:exclusive_suites(corresponding_minor_tls_version(Minor))).
+ tls_v1:exclusive_suites(corresponding_tls_version(Version))).
-exclusive_anonymous_suites(Minor) ->
+exclusive_anonymous_suites(Version) ->
lists:filter(fun(Cipher) ->
is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
end,
- tls_v1:exclusive_anonymous_suites(corresponding_minor_tls_version(Minor))).
+ tls_v1:exclusive_anonymous_suites(corresponding_tls_version(Version))).
hmac_hash(MacAlg, MacSecret, Value) ->
tls_v1:hmac_hash(MacAlg, MacSecret, Value).
-ecc_curves({_Major, Minor}) ->
- tls_v1:ecc_curves(corresponding_minor_tls_version(Minor)).
+ecc_curves(Version) ->
+ tls_v1:ecc_curves(corresponding_tls_version(Version)).
-corresponding_tls_version({254, Minor}) ->
- {3, corresponding_minor_tls_version(Minor)}.
+
+corresponding_tls_version(?DTLS_1_0) ->
+ ?TLS_1_1;
+corresponding_tls_version(?DTLS_1_2) ->
+ ?TLS_1_2.
cookie_secret() ->
crypto:strong_rand_bytes(32).
@@ -82,18 +86,12 @@ cookie_secret() ->
cookie_timeout() ->
%% Cookie will live for two timeouts periods
round(rand:uniform() * ?COOKIE_BASE_TIMEOUT/2).
-
-corresponding_minor_tls_version(255) ->
- 2;
-corresponding_minor_tls_version(253) ->
- 3.
-
-corresponding_dtls_version({3, Minor}) ->
- {254, corresponding_minor_dtls_version(Minor)}.
-
-corresponding_minor_dtls_version(2) ->
- 255;
-corresponding_minor_dtls_version(3) ->
- 253.
+
+
+corresponding_dtls_version(?TLS_1_1) ->
+ ?DTLS_1_0;
+corresponding_dtls_version(?TLS_1_2) ->
+ ?DTLS_1_2.
+
is_acceptable_cipher(Suite) ->
not ssl_cipher:is_stream_ciphersuite(Suite).
diff --git a/lib/ssl/src/inet6_tls_dist.erl b/lib/ssl/src/inet6_tls_dist.erl
index 5ca0cd6904..0e51f9a190 100644
--- a/lib/ssl/src/inet6_tls_dist.erl
+++ b/lib/ssl/src/inet6_tls_dist.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,26 +25,30 @@
-export([listen/2, accept/1, accept_connection/5,
setup/5, close/1, select/1, address/0]).
+-define(FAMILY, inet6).
+
childspecs() ->
inet_tls_dist:childspecs().
select(Node) ->
- inet_tls_dist:gen_select(inet6_tcp, Node).
+ inet_tls_dist:fam_select(?FAMILY, Node).
address() ->
- inet_tls_dist:gen_address(inet6_tcp).
+ inet_tls_dist:fam_address(?FAMILY).
listen(Name, Host) ->
- inet_tls_dist:gen_listen(inet6_tcp, Name, Host).
+ inet_tls_dist:fam_listen(?FAMILY, Name, Host).
accept(Listen) ->
- inet_tls_dist:gen_accept(inet6_tcp, Listen).
+ inet_tls_dist:fam_accept(?FAMILY, Listen).
accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
- inet_tls_dist:gen_accept_connection(inet6_tcp, AcceptPid, Socket, MyNode, Allowed, SetupTime).
+ inet_tls_dist:fam_accept_connection(
+ ?FAMILY, AcceptPid, Socket, MyNode, Allowed, SetupTime).
setup(Node, Type, MyNode, LongOrShortNames,SetupTime) ->
- inet_tls_dist:gen_setup(inet6_tcp, Node, Type, MyNode, LongOrShortNames,SetupTime).
+ inet_tls_dist:fam_setup(
+ ?FAMILY, Node, Type, MyNode, LongOrShortNames,SetupTime).
close(Socket) ->
- inet_tls_dist:gen_close(inet6_tcp, Socket).
+ inet_tls_dist:close(Socket).
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index 9118fb59f6..c93bb27596 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -20,19 +20,24 @@
%%
-module(inet_tls_dist).
+-feature(maybe_expr, enable).
-export([childspecs/0]).
--export([listen/2, accept/1, accept_connection/5,
- setup/5, close/1, select/1, address/0, is_node_name/1]).
+-export([select/1, address/0, is_node_name/1,
+ listen/2, accept/1, accept_connection/5,
+ setup/5, close/1]).
%% Generalized dist API
--export([gen_listen/3, gen_accept/2, gen_accept_connection/6,
- gen_setup/6, gen_close/2, gen_select/2, gen_address/1]).
-
--export([nodelay/0]).
+-export([fam_select/2, fam_address/1, fam_listen/3, fam_accept/2,
+ fam_accept_connection/6, fam_setup/6]).
-export([verify_client/3, cert_nodes/1]).
+%% kTLS helpers
+-export([inet_ktls_setopt/3, inet_ktls_getopt/3,
+ set_ktls/1, set_ktls_ulp/2, set_ktls_cipher/5,
+ ktls_os/0, ktls_opt_ulp/1, ktls_opt_cipher/6]).
+
-export([dbg/0]). % Debug
-include_lib("kernel/include/net_address.hrl").
@@ -41,37 +46,68 @@
-include_lib("public_key/include/public_key.hrl").
-include("ssl_api.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_record.hrl").
-include_lib("kernel/include/logger.hrl").
+-define(FAMILY, inet).
+-define(DRIVER, inet_tcp). % Implies ?FAMILY = inet through inet_drv.c
+-define(PROTOCOL, tls).
+
%% -------------------------------------------------------------------------
childspecs() ->
{ok, [{ssl_dist_sup,{ssl_dist_sup, start_link, []},
permanent, infinity, supervisor, [ssl_dist_sup]}]}.
+%% -------------------------------------------------------------------------
+%% Select this protocol based on node name
select(Node) ->
- gen_select(inet_tcp, Node).
-
-gen_select(Driver, Node) ->
- inet_tcp_dist:gen_select(Driver, Node).
-
-%% ------------------------------------------------------------
-%% Get the address family that this distribution uses
-%% ------------------------------------------------------------
+ fam_select(?FAMILY, Node).
+fam_select(Family, Node) ->
+ inet_tcp_dist:fam_select(Family, Node).
+%% -------------------------------------------------------------------------
+%% Get the #net_address this distribution uses
address() ->
- gen_address(inet_tcp).
-gen_address(Driver) ->
- inet_tcp_dist:gen_address(Driver).
-
+ fam_address(?FAMILY).
+fam_address(Family) ->
+ NetAddress = inet_tcp_dist:fam_address(Family),
+ NetAddress#net_address{ protocol = ?PROTOCOL }.
%% -------------------------------------------------------------------------
-
+%% Is this one really needed??
is_node_name(Node) ->
dist_util:is_node_name(Node).
-
%% -------------------------------------------------------------------------
-hs_data_common(#sslsocket{pid = [_, DistCtrl|_]} = SslSocket) ->
+hs_data_inet_tcp(Driver, Socket) ->
+ Family = Driver:family(),
+ {ok, Peername} =
+ maybe
+ {error, einval} ?= inet:peername(Socket),
+ ?shutdown({Driver, closed})
+ end,
+ (inet_tcp_dist:gen_hs_data(Driver, Socket))
+ #hs_data{
+ f_address =
+ fun(_, Node) ->
+ {node, _, Host} = dist_util:split_node(Node),
+ #net_address{
+ address = Peername,
+ host = Host,
+ protocol = ?PROTOCOL,
+ family = Family
+ }
+ end}.
+
+hs_data_ssl(Family, #sslsocket{pid = [_, DistCtrl|_]} = SslSocket) ->
+ {ok, Address} =
+ maybe
+ {error, einval} ?= ssl:peername(SslSocket),
+ ?shutdown({sslsocket, closed})
+ end,
#hs_data{
+ socket = DistCtrl,
f_send =
fun (_Ctrl, Packet) ->
f_send(SslSocket, Packet)
@@ -95,7 +131,7 @@ hs_data_common(#sslsocket{pid = [_, DistCtrl|_]} = SslSocket) ->
end,
f_address =
fun (Ctrl, Node) when Ctrl == DistCtrl ->
- f_address(SslSocket, Node)
+ f_address(Family, Address, Node)
end,
mf_tick =
fun (Ctrl) when Ctrl == DistCtrl ->
@@ -133,22 +169,21 @@ f_setopts_pre_nodeup(_SslSocket) ->
ok.
f_setopts_post_nodeup(SslSocket) ->
- ssl:setopts(SslSocket, [nodelay()]).
+ ssl:setopts(SslSocket, [inet_tcp_dist:nodelay()]).
f_getll(DistCtrl) ->
{ok, DistCtrl}.
-f_address(SslSocket, Node) ->
- case ssl:peername(SslSocket) of
- {ok, Address} ->
- case dist_util:split_node(Node) of
- {node,_,Host} ->
- #net_address{
- address=Address, host=Host,
- protocol=tls, family=inet};
- _ ->
- {error, no_node}
- end
+f_address(Family, Address, Node) ->
+ case dist_util:split_node(Node) of
+ {node,_,Host} ->
+ #net_address{
+ address = Address,
+ host = Host,
+ protocol = ?PROTOCOL,
+ family = Family};
+ _ ->
+ {error, no_node}
end.
mf_tick(DistCtrl) ->
@@ -194,52 +229,79 @@ split_stat([], R, W, P) ->
%% -------------------------------------------------------------------------
listen(Name, Host) ->
- gen_listen(inet_tcp, Name, Host).
-
-gen_listen(Driver, Name, Host) ->
- case inet_tcp_dist:gen_listen(Driver, Name, Host) of
- {ok, {Socket, Address, Creation}} ->
- inet:setopts(Socket, [{packet, 4}, {nodelay, true}]),
- {ok, {Socket, Address#net_address{protocol=tls}, Creation}};
- Other ->
- Other
+ fam_listen(?FAMILY, Name, Host).
+
+fam_listen(Family, Name, Host) ->
+ ForcedOptions =
+ [Family, {active, false}, {packet, 4}, {nodelay, true}],
+ ListenFun =
+ fun (First, Last, ListenOptions) ->
+ listen_loop(
+ First, Last,
+ inet_tcp_dist:merge_options(ListenOptions, ForcedOptions))
+ end,
+ maybe
+ %%
+ {ok, {ListenSocket, Address, Creation}} ?=
+ inet_tcp_dist:fam_listen(Family, Name, Host, ListenFun),
+ NetAddress =
+ #net_address{
+ host = Host,
+ protocol = ?PROTOCOL,
+ family = Family,
+ address = Address},
+ {ok, {ListenSocket, NetAddress, Creation}}
end.
+listen_loop(First, Last, ListenOptions) when First =< Last ->
+ case gen_tcp:listen(First, ListenOptions) of
+ {error, eaddrinuse} ->
+ listen_loop(First + 1, Last, ListenOptions);
+ Result ->
+ Result
+ end;
+listen_loop(_, _, _) ->
+ {error, eaddrinuse}.
+
%% -------------------------------------------------------------------------
-accept(Listen) ->
- gen_accept(inet_tcp, Listen).
+accept(ListenSocket) ->
+ fam_accept(?FAMILY, ListenSocket).
-gen_accept(Driver, Listen) ->
- Kernel = self(),
+fam_accept(Family, ListenSocket) ->
+ NetKernel = self(),
monitor_pid(
spawn_opt(
fun () ->
- process_flag(trap_exit, true),
- LOpts = application:get_env(kernel, inet_dist_listen_options, []),
- MaxPending =
- case lists:keyfind(backlog, 1, LOpts) of
- {backlog, Backlog} -> Backlog;
- false -> 128
- end,
- DLK = {Driver, Listen, Kernel},
- accept_loop(DLK, spawn_accept(DLK), MaxPending, #{})
+ process_flag(trap_exit, true),
+ MaxPending = erlang:system_info(schedulers_online),
+ Continue = make_ref(),
+ FLNC = {Family, ListenSocket, NetKernel, Continue},
+ Pending = #{},
+ accept_loop(
+ FLNC, Continue, spawn_accept(FLNC), MaxPending,
+ Pending)
end,
- [link, {priority, max}])).
+ dist_util:net_ticker_spawn_options())).
%% Concurrent accept loop will spawn a new HandshakePid when
%% there is no HandshakePid already running, and Pending map is
%% smaller than MaxPending
-accept_loop(DLK, undefined, MaxPending, Pending) when map_size(Pending) < MaxPending ->
- accept_loop(DLK, spawn_accept(DLK), MaxPending, Pending);
-accept_loop({_, _, NetKernelPid} = DLK, HandshakePid, MaxPending, Pending) ->
+accept_loop(FLNC, Continue, undefined, MaxPending, Pending)
+ when map_size(Pending) < MaxPending ->
+ accept_loop(FLNC, Continue, spawn_accept(FLNC), MaxPending, Pending);
+accept_loop({_, _, NetKernelPid, _} = FLNC, Continue, HandshakePid, MaxPending, Pending) ->
receive
- {continue, HandshakePid} when is_pid(HandshakePid) ->
- accept_loop(DLK, undefined, MaxPending, Pending#{HandshakePid => true});
+ {Continue, HandshakePid} when is_pid(HandshakePid) ->
+ accept_loop(
+ FLNC, Continue, undefined, MaxPending,
+ Pending#{HandshakePid => true});
{'EXIT', Pid, Reason} when is_map_key(Pid, Pending) ->
Reason =/= normal andalso
?LOG_ERROR("TLS distribution handshake failed: ~p~n", [Reason]),
- accept_loop(DLK, HandshakePid, MaxPending, maps:remove(Pid, Pending));
+ accept_loop(
+ FLNC, Continue, HandshakePid, MaxPending,
+ maps:remove(Pid, Pending));
{'EXIT', HandshakePid, Reason} when is_pid(HandshakePid) ->
%% HandshakePid crashed before turning into Pending, which means
%% error happened in accept. Need to restart the listener.
@@ -248,20 +310,21 @@ accept_loop({_, _, NetKernelPid} = DLK, HandshakePid, MaxPending, Pending) ->
%% Since we're trapping exits, need to manually propagate this signal
exit(Reason);
Unexpected ->
- ?LOG_WARNING("TLS distribution: unexpected message: ~p~n" ,[Unexpected]),
- accept_loop(DLK, HandshakePid, MaxPending, Pending)
+ ?LOG_WARNING(
+ "TLS distribution: unexpected message: ~p~n", [Unexpected]),
+ accept_loop(FLNC, Continue, HandshakePid, MaxPending, Pending)
end.
-spawn_accept({Driver, Listen, Kernel}) ->
+spawn_accept({Family, ListenSocket, NetKernel, Continue}) ->
AcceptLoop = self(),
spawn_link(
fun () ->
- case Driver:accept(Listen) of
+ case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
- AcceptLoop ! {continue, self()},
- case check_ip(Driver, Socket) of
+ AcceptLoop ! {Continue, self()},
+ case check_ip(Socket) of
true ->
- accept_one(Driver, Kernel, Socket);
+ accept_one(Family, Socket, NetKernel);
{false,IP} ->
?LOG_ERROR(
"** Connection attempt from "
@@ -273,33 +336,37 @@ spawn_accept({Driver, Listen, Kernel}) ->
end
end).
-accept_one(Driver, Kernel, Socket) ->
+accept_one(Family, Socket, NetKernel) ->
Opts = setup_verify_client(Socket, get_ssl_options(server)),
- wait_for_code_server(),
+ KTLS = proplists:get_value(ktls, Opts, false),
case
ssl:handshake(
Socket,
trace([{active, false},{packet, 4}|Opts]),
net_kernel:connecttime())
of
- {ok, #sslsocket{pid = [_, DistCtrl| _]} = SslSocket} ->
- trace(
- Kernel !
- {accept, self(), DistCtrl,
- Driver:family(), tls}),
- receive
- {Kernel, controller, Pid} ->
- case ssl:controlling_process(SslSocket, Pid) of
+ {ok, SslSocket} ->
+ Receiver = hd(SslSocket#sslsocket.pid),
+ case KTLS of
+ true ->
+ {ok, KtlsInfo} = ssl_gen_statem:ktls_handover(Receiver),
+ case inet_set_ktls(KtlsInfo) of
ok ->
- trace(Pid ! {self(), controller});
- Error ->
- trace(Pid ! {self(), exit}),
+ accept_one(
+ Family, maps:get(socket, KtlsInfo), NetKernel,
+ fun gen_tcp:controlling_process/2);
+ {error, KtlsReason} ->
?LOG_ERROR(
- "Cannot control TLS distribution connection: ~p~n",
- [Error])
+ [{slogan, set_ktls_failed},
+ {reason, KtlsReason},
+ {pid, self()}]),
+ close(Socket),
+ trace({ktls_error, KtlsReason})
end;
- {Kernel, unsupported_protocol} ->
- trace(unsupported_protocol)
+ false ->
+ accept_one(
+ Family, SslSocket, NetKernel,
+ fun ssl:controlling_process/2)
end;
{error, {options, _}} = Error ->
%% Bad options: that's probably our fault.
@@ -307,12 +374,31 @@ accept_one(Driver, Kernel, Socket) ->
?LOG_ERROR(
"Cannot accept TLS distribution connection: ~s~n",
[ssl:format_error(Error)]),
- gen_tcp:close(Socket),
+ close(Socket),
trace(Error);
Other ->
- gen_tcp:close(Socket),
+ close(Socket),
trace(Other)
end.
+%%
+accept_one(
+ Family, DistSocket, NetKernel, ControllingProcessFun) ->
+ trace(NetKernel ! {accept, self(), DistSocket, Family, ?PROTOCOL}),
+ receive
+ {NetKernel, controller, Pid} ->
+ case ControllingProcessFun(DistSocket, Pid) of
+ ok ->
+ trace(Pid ! {self(), controller});
+ {error, Reason} ->
+ trace(Pid ! {self(), exit}),
+ ?LOG_ERROR(
+ [{slogan, controlling_process_failed},
+ {reason, Reason},
+ {pid, self()}])
+ end;
+ {NetKernel, unsupported_protocol} ->
+ trace(unsupported_protocol)
+ end.
%% {verify_fun,{fun ?MODULE:verify_client/3,_}} is used
@@ -398,72 +484,50 @@ verify_client(PeerCert, valid_peer, {AllowedHosts,PeerIP} = S) ->
end.
-wait_for_code_server() ->
- %% This is an ugly hack. Upgrading a socket to TLS requires the
- %% crypto module to be loaded. Loading the crypto module triggers
- %% its on_load function, which calls code:priv_dir/1 to find the
- %% directory where its NIF library is. However, distribution is
- %% started earlier than the code server, so the code server is not
- %% necessarily started yet, and code:priv_dir/1 might fail because
- %% of that, if we receive an incoming connection on the
- %% distribution port early enough.
- %%
- %% If the on_load function of a module fails, the module is
- %% unloaded, and the function call that triggered loading it fails
- %% with 'undef', which is rather confusing.
- %%
- %% Thus, the accept process will terminate, and be
- %% restarted by ssl_dist_sup. However, it won't have any memory
- %% of being asked by net_kernel to listen for incoming
- %% connections. Hence, the node will believe that it's open for
- %% distribution, but it actually isn't.
- %%
- %% So let's avoid that by waiting for the code server to start.
- case whereis(code_server) of
- undefined ->
- timer:sleep(10),
- wait_for_code_server();
- Pid when is_pid(Pid) ->
- ok
- end.
-
%% -------------------------------------------------------------------------
-accept_connection(AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) ->
- gen_accept_connection(
- inet_tcp, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime).
+accept_connection(AcceptPid, DistSocket, MyNode, Allowed, SetupTime) ->
+ fam_accept_connection(
+ ?FAMILY, AcceptPid, DistSocket, MyNode, Allowed, SetupTime).
-gen_accept_connection(
- Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) ->
+fam_accept_connection(
+ Family, AcceptPid, DistSocket, MyNode, Allowed, SetupTime) ->
Kernel = self(),
monitor_pid(
spawn_opt(
fun() ->
do_accept(
- Driver, AcceptPid, DistCtrl,
+ Family, AcceptPid, DistSocket,
MyNode, Allowed, SetupTime, Kernel)
end,
dist_util:net_ticker_spawn_options())).
do_accept(
- _Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime, Kernel) ->
+ Family, AcceptPid, DistSocket, MyNode, Allowed, SetupTime, Kernel) ->
MRef = erlang:monitor(process, AcceptPid),
receive
{AcceptPid, controller} ->
erlang:demonitor(MRef, [flush]),
- {ok, SslSocket} = tls_sender:dist_tls_socket(DistCtrl),
- Timer = dist_util:start_timer(SetupTime),
- NewAllowed = allowed_nodes(SslSocket, Allowed),
- HSData0 = hs_data_common(SslSocket),
+ Timer = dist_util:start_timer(SetupTime),
+ {HSData0, NewAllowed} =
+ case DistSocket of
+ SslSocket = #sslsocket{pid = [_Receiver, Sender| _]} ->
+ link(Sender),
+ {hs_data_ssl(Family, SslSocket),
+ allowed_nodes(SslSocket, Allowed)};
+ PortSocket when is_port(DistSocket) ->
+ %%% XXX Breaking abstraction barrier
+ Driver = erlang:port_get_data(PortSocket),
+ {hs_data_inet_tcp(Driver, PortSocket),
+ Allowed}
+ end,
HSData =
HSData0#hs_data{
kernel_pid = Kernel,
this_node = MyNode,
- socket = DistCtrl,
timer = Timer,
this_flags = 0,
allowed = NewAllowed},
- link(DistCtrl),
dist_util:handshake_other_started(trace(HSData));
{AcceptPid, exit} ->
%% this can happen when connection was initiated, but dropped
@@ -535,138 +599,147 @@ allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host) ->
allowed_nodes(PeerCert, Allowed, PeerIP)
end.
+
+%% -------------------------------------------------------------------------
+
setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames, SetupTime).
+ fam_setup(?FAMILY, Node, Type, MyNode, LongOrShortNames, SetupTime).
-gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- Kernel = self(),
+fam_setup(Family, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
+ NetKernel = self(),
monitor_pid(
- spawn_opt(setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime),
- dist_util:net_ticker_spawn_options())).
+ spawn_opt(
+ setup_fun(
+ Family, Node, Type, MyNode, LongOrShortNames, SetupTime,
+ NetKernel),
+ dist_util:net_ticker_spawn_options())).
-spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()).
-setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
+setup_fun(
+ Family, Node, Type, MyNode, LongOrShortNames, SetupTime, NetKernel) ->
fun() ->
do_setup(
- Driver, Kernel, Node, Type,
- MyNode, LongOrShortNames, SetupTime)
+ Family, Node, Type, MyNode, LongOrShortNames, SetupTime,
+ NetKernel)
end.
-
-spec do_setup(_,_,_,_,_,_,_) -> no_return().
-do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- {Name, Address} = split_node(Driver, Node, LongOrShortNames),
- ErlEpmd = net_kernel:epmd_module(),
- {ARMod, ARFun} = get_address_resolver(ErlEpmd, Driver),
+do_setup(
+ Family, Node, Type, MyNode, LongOrShortNames, SetupTime, NetKernel) ->
Timer = trace(dist_util:start_timer(SetupTime)),
- case ARMod:ARFun(Name,Address,Driver:family()) of
- {ok, Ip, TcpPort, Version} ->
- do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer);
- {ok, Ip} ->
- case ErlEpmd:port_please(Name, Ip) of
- {port, TcpPort, Version} ->
- do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer);
- Other ->
- ?shutdown2(
- Node,
- trace(
- {port_please_failed, ErlEpmd, Name, Ip, Other}))
- end;
- Other ->
- ?shutdown2(
- Node,
- trace({getaddr_failed, Driver, Address, Other}))
- end.
-
--spec do_setup_connect(_,_,_,_,_,_,_,_,_,_) -> no_return().
-
-do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer) ->
- Opts = trace(connect_options(get_ssl_options(client))),
+ ParseAddress = fun (A) -> inet:parse_strict_address(A, Family) end,
+ {#net_address{
+ host = Host,
+ address = {Ip, PortNum}},
+ ConnectOptions,
+ Version} =
+ trace(inet_tcp_dist:fam_setup(
+ Family, Node, LongOrShortNames, ParseAddress)),
+ Opts =
+ inet_tcp_dist:merge_options(
+ inet_tcp_dist:merge_options(
+ ConnectOptions,
+ get_ssl_options(client)),
+ [Family, binary, {active, false}, {packet, 4}, {nodelay, true}],
+ [{server_name_indication, Host}]),
+ KTLS = proplists:get_value(ktls, Opts, false),
dist_util:reset_timer(Timer),
- case ssl:connect(
- Ip, TcpPort,
- [binary, {active, false}, {packet, 4}, {server_name_indication, Address},
- Driver:family(), {nodelay, true}] ++ Opts,
- net_kernel:connecttime()) of
- {ok, #sslsocket{pid = [_, DistCtrl| _]} = SslSocket} ->
- _ = monitor_pid(DistCtrl),
- ok = ssl:controlling_process(SslSocket, self()),
- HSData0 = hs_data_common(SslSocket),
+ maybe
+ {ok, #sslsocket{pid = [Receiver, Sender| _]} = SslSocket} ?=
+ ssl:connect(Ip, PortNum, Opts, net_kernel:connecttime()),
HSData =
- HSData0#hs_data{
- kernel_pid = Kernel,
- other_node = Node,
- this_node = MyNode,
- socket = DistCtrl,
- timer = Timer,
- this_flags = 0,
- other_version = Version,
- request_type = Type},
- link(DistCtrl),
- dist_util:handshake_we_started(trace(HSData));
- Other ->
- %% Other Node may have closed since
- %% port_please !
- ?shutdown2(
- Node,
- trace(
- {ssl_connect_failed, Ip, TcpPort, Other}))
+ case KTLS of
+ true ->
+ {ok, KtlsInfo} =
+ ssl_gen_statem:ktls_handover(Receiver),
+ Socket = maps:get(socket, KtlsInfo),
+ case inet_set_ktls(KtlsInfo) of
+ ok when is_port(Socket) ->
+ %% XXX Breaking abstraction barrier
+ Driver = erlang:port_get_data(Socket),
+ hs_data_inet_tcp(Driver, Socket);
+ {error, KtlsReason} ->
+ ?shutdown2(
+ Node,
+ trace({set_ktls_failed, KtlsReason}))
+ end;
+ false ->
+ _ = monitor_pid(Sender),
+ ok = ssl:controlling_process(SslSocket, self()),
+ link(Sender),
+ hs_data_ssl(Family, SslSocket)
+ end
+ #hs_data{
+ kernel_pid = NetKernel,
+ other_node = Node,
+ this_node = MyNode,
+ timer = Timer,
+ this_flags = 0,
+ other_version = Version,
+ request_type = Type},
+ dist_util:handshake_we_started(trace(HSData))
+ else
+ Other ->
+ %% Other Node may have closed since
+ %% port_please !
+ ?shutdown2(
+ Node,
+ trace({ssl_connect_failed, Ip, PortNum, Other}))
end.
-close(Socket) ->
- gen_close(inet, Socket).
-
-gen_close(Driver, Socket) ->
- trace(Driver:close(Socket)).
+close(Socket) ->
+ gen_tcp:close(Socket).
-%% ------------------------------------------------------------
-%% Determine if EPMD module supports address resolving. Default
-%% is to use inet_tcp:getaddr/2.
-%% ------------------------------------------------------------
-get_address_resolver(EpmdModule, _Driver) ->
- case erlang:function_exported(EpmdModule, address_please, 3) of
- true -> {EpmdModule, address_please};
- _ -> {erl_epmd, address_please}
- end.
%% ------------------------------------------------------------
%% Do only accept new connection attempts from nodes at our
%% own LAN, if the check_ip environment parameter is true.
%% ------------------------------------------------------------
-check_ip(Driver, Socket) ->
+check_ip(Socket) ->
case application:get_env(check_ip) of
{ok, true} ->
- case get_ifs(Socket) of
- {ok, IFs, IP} ->
- check_ip(Driver, IFs, IP);
- Other ->
- ?shutdown2(
- no_node, trace({check_ip_failed, Socket, Other}))
- end;
+ maybe
+ {ok, {IP, _}} ?= inet:sockname(Socket),
+ ok ?= if is_tuple(IP) -> ok;
+ true -> {error, {no_ip_address, IP}}
+ end,
+ {ok, Ifaddrs} ?= inet:getifaddrs(),
+ {ok, Netmask} ?= find_netmask(IP, Ifaddrs),
+ {ok, {PeerIP, _}} ?= inet:sockname(Socket),
+ ok ?= if is_tuple(PeerIP) -> ok;
+ true -> {error, {no_ip_address, PeerIP}}
+ end,
+ mask(IP, Netmask) =:= mask(PeerIP, Netmask)
+ orelse {false, PeerIP}
+ else
+ Other ->
+ exit({check_ip, Other})
+ end;
_ ->
true
end.
-check_ip(Driver, [{OwnIP, _, Netmask}|IFs], PeerIP) ->
- case {Driver:mask(Netmask, PeerIP), Driver:mask(Netmask, OwnIP)} of
- {M, M} -> true;
- _ -> check_ip(IFs, PeerIP)
- end;
-check_ip(_Driver, [], PeerIP) ->
- {false, PeerIP}.
-
-get_ifs(Socket) ->
- case inet:peername(Socket) of
- {ok, {IP, _}} ->
- %% XXX this is seriously broken for IPv6
- case inet:getif(Socket) of
- {ok, IFs} -> {ok, IFs, IP};
- Error -> Error
- end;
- Error ->
- Error
- end.
+find_netmask(IP, [{_Name,Items} | Ifaddrs]) ->
+ find_netmask(IP, Ifaddrs, Items);
+find_netmask(_, []) ->
+ {error, no_netmask}.
+%%
+find_netmask(IP, _Ifaddrs, [{addr, IP}, {netmask, Netmask} | _]) ->
+ {ok, Netmask};
+find_netmask(IP, Ifaddrs, [_ | Items]) ->
+ find_netmask(IP, Ifaddrs, Items);
+find_netmask(IP, Ifaddrs, []) ->
+ find_netmask(IP, Ifaddrs).
+
+mask(Addr, Mask) ->
+ list_to_tuple(mask(Addr, Mask, 1)).
+%%
+mask(Addr, Mask, N) when N =< tuple_size(Addr) ->
+ [element(N, Addr) band element(N, Mask) | mask(Addr, Mask, N + 1)];
+mask(_, _, _) ->
+ [].
+
%% Look in Extensions, in all subjectAltName:s
@@ -744,90 +817,32 @@ parse_rdn([_|Rdn]) ->
parse_rdn(Rdn).
-%% If Node is illegal terminate the connection setup!!
-split_node(Driver, Node, LongOrShortNames) ->
- case dist_util:split_node(Node) of
- {node, Name, Host} ->
- check_node(Driver, Node, Name, Host, LongOrShortNames);
- {host, _} ->
- ?LOG_ERROR(
- "** Nodename ~p illegal, no '@' character **~n",
- [Node]),
- ?shutdown2(Node, trace({illegal_node_n@me, Node}));
- _ ->
- ?LOG_ERROR(
- "** Nodename ~p illegal **~n", [Node]),
- ?shutdown2(Node, trace({illegal_node_name, Node}))
- end.
-
-check_node(Driver, Node, Name, Host, LongOrShortNames) ->
- case string:split(Host, ".", all) of
- [_] when LongOrShortNames =:= longnames ->
- case Driver:parse_address(Host) of
- {ok, _} ->
- {Name, Host};
- _ ->
- ?LOG_ERROR(
- "** System running to use "
- "fully qualified hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown2(Node, trace({not_longnames, Host}))
- end;
- [_,_|_] when LongOrShortNames =:= shortnames ->
- ?LOG_ERROR(
- "** System NOT running to use "
- "fully qualified hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown2(Node, trace({not_shortnames, Host}));
- _ ->
- {Name, Host}
- end.
-
%% -------------------------------------------------------------------------
-
-connect_options(Opts) ->
- case application:get_env(kernel, inet_dist_connect_options) of
- {ok,ConnectOpts} ->
- lists:ukeysort(1, ConnectOpts ++ Opts);
- _ ->
- Opts
- end.
-
-%% we may not always want the nodelay behaviour
-%% for performance reasons
-nodelay() ->
- case application:get_env(kernel, dist_nodelay) of
- undefined ->
- {nodelay, true};
- {ok, true} ->
- {nodelay, true};
- {ok, false} ->
- {nodelay, false};
- _ ->
- {nodelay, true}
- end.
-
-
get_ssl_options(Type) ->
- try ets:lookup(ssl_dist_opts, Type) of
- [{Type, Opts0}] ->
- [{erl_dist, true} | dist_defaults(Opts0)];
- _ ->
- get_ssl_dist_arguments(Type)
- catch
- error:badarg ->
- get_ssl_dist_arguments(Type)
- end.
-
-get_ssl_dist_arguments(Type) ->
- case init:get_argument(ssl_dist_opt) of
- {ok, Args} ->
- [{erl_dist, true} | dist_defaults(ssl_options(Type, lists:append(Args)))];
- _ ->
- [{erl_dist, true}]
- end.
+ [{erl_dist, true} |
+ case
+ case init:get_argument(ssl_dist_opt) of
+ {ok, Args} ->
+ ssl_options(Type, lists:append(Args));
+ _ ->
+ []
+ end
+ ++
+ try ets:lookup(ssl_dist_opts, Type) of
+ [{Type, Opts0}] ->
+ Opts0;
+ _ ->
+ []
+ catch
+ error:badarg ->
+ []
+ end
+ of
+ [] ->
+ [];
+ Opts1 ->
+ dist_defaults(Opts1)
+ end].
dist_defaults(Opts) ->
case proplists:get_value(versions, Opts, undefined) of
@@ -874,7 +889,13 @@ ssl_option(client, Opt) ->
"secure_renegotiate" -> fun atomize/1;
"depth" -> fun erlang:list_to_integer/1;
"hibernate_after" -> fun erlang:list_to_integer/1;
- "ciphers" -> fun listify/1;
+ "ciphers" ->
+ %% Allows just one cipher, for now (could be , separated)
+ fun (Val) -> [listify(Val)] end;
+ "versions" ->
+ %% Allows just one version, for now (could be , separated)
+ fun (Val) -> [atomize(Val)] end;
+ "ktls" -> fun atomize/1;
_ -> error
end.
@@ -900,6 +921,174 @@ verify_fun(Value) ->
error(malformed_ssl_dist_opt, [Value])
end.
+
+inet_set_ktls(
+ #{ socket := Socket, socket_options := SocketOptions } = KtlsInfo) ->
+ %%
+ maybe
+ ok ?=
+ set_ktls(
+ KtlsInfo
+ #{ setopt_fun => fun ?MODULE:inet_ktls_setopt/3,
+ getopt_fun => fun ?MODULE:inet_ktls_getopt/3 }),
+ %%
+ #socket_options{
+ mode = _Mode,
+ packet = Packet,
+ packet_size = PacketSize,
+ header = Header,
+ active = Active
+ } = SocketOptions,
+ case
+ inet:setopts(
+ Socket,
+ [list, {packet, Packet}, {packet_size, PacketSize},
+ {header, Header}, {active, Active}])
+ of
+ ok ->
+ ok;
+ {error, SetoptError} ->
+ {error, {ktls_setopt_failed, SetoptError}}
+ end
+ end.
+
+inet_ktls_setopt(Socket, {Level, Opt}, Value)
+ when is_integer(Level), is_integer(Opt), is_binary(Value) ->
+ inet:setopts(Socket, [{raw, Level, Opt, Value}]).
+
+inet_ktls_getopt(Socket, {Level, Opt}, Size)
+ when is_integer(Level), is_integer(Opt), is_integer(Size) ->
+ case inet:getopts(Socket, [{raw, Level, Opt, Size}]) of
+ {ok, [{raw, Level, Opt, Value}]} ->
+ {ok, Value};
+ {ok, _} = Error ->
+ {error, Error};
+ {error, _} = Error ->
+ Error
+ end.
+
+
+set_ktls(KtlsInfo) ->
+ maybe
+ {ok, OS} ?= ktls_os(),
+ ok ?= set_ktls_ulp(KtlsInfo, OS),
+ #{ write_state := WriteState,
+ write_seq := WriteSeq,
+ read_state := ReadState,
+ read_seq := ReadSeq } = KtlsInfo,
+ ok ?= set_ktls_cipher(KtlsInfo, OS, WriteState, WriteSeq, tx),
+ set_ktls_cipher(KtlsInfo, OS, ReadState, ReadSeq, rx)
+ end.
+
+set_ktls_ulp(
+ #{ socket := Socket,
+ setopt_fun := SetoptFun,
+ getopt_fun := GetoptFun },
+ OS) ->
+ %%
+ {Option, Value} = ktls_opt_ulp(OS),
+ Size = byte_size(Value),
+ _ = SetoptFun(Socket, Option, Value),
+ %%
+ %% Check if kernel module loaded,
+ %% i.e if getopts Level, Opt returns Value
+ %%
+ case GetoptFun(Socket, Option, Size + 1) of
+ {ok, <<Value:Size/binary, 0>>} ->
+ ok;
+ Other ->
+ {error, {ktls_set_ulp_failed, Option, Value, Other}}
+ end.
+
+%% Set kTLS cipher
+%%
+set_ktls_cipher(
+ _KtlsInfo =
+ #{ tls_version := TLS_version,
+ cipher_suite := CipherSuite,
+ %%
+ socket := Socket,
+ setopt_fun := SetoptFun,
+ getopt_fun := GetoptFun },
+ OS, CipherState, CipherSeq, TxRx) ->
+ maybe
+ {ok, {Option, Value}} ?=
+ ktls_opt_cipher(
+ OS, TLS_version, CipherSuite, CipherState, CipherSeq, TxRx),
+ _ = SetoptFun(Socket, Option, Value),
+ case TxRx of
+ tx ->
+ Size = byte_size(Value),
+ case GetoptFun(Socket, Option, Size) of
+ {ok, Value} ->
+ ok;
+ Other ->
+ {error, {ktls_set_cipher_failed, Other}}
+ end;
+ rx ->
+ ok
+ end
+ end.
+
+ktls_os() ->
+ OS = {os:type(), os:version()},
+ case OS of
+ {{unix,linux}, OsVersion} when {5,2,0} =< OsVersion ->
+ {ok, OS};
+ _ ->
+ {error, {ktls_notsup, {os,OS}}}
+ end.
+
+ktls_opt_ulp(_OS) ->
+ %%
+ %% See https://www.kernel.org/doc/html/latest/networking/tls.html
+ %% and include/netinet/tcp.h
+ %%
+ SOL_TCP = 6, TCP_ULP = 31,
+ KtlsMod = <<"tls">>,
+ {{SOL_TCP,TCP_ULP}, KtlsMod}.
+
+ktls_opt_cipher(
+ _OS,
+ _TLS_version = ?TLS_1_3, % 'tlsv1.3'
+ _CipherSpec = ?TLS_AES_256_GCM_SHA384,
+ #cipher_state{
+ key = <<Key:32/bytes>>,
+ iv = <<Salt:4/bytes, IV:8/bytes>> },
+ CipherSeq,
+ TxRx) when is_integer(CipherSeq) ->
+ %%
+ %% See include/linux/tls.h
+ %%
+ TLS_1_3_VERSION_MAJOR = 3,
+ TLS_1_3_VERSION_MINOR = 4,
+ TLS_1_3_VERSION =
+ (TLS_1_3_VERSION_MAJOR bsl 8) bor TLS_1_3_VERSION_MINOR,
+ TLS_CIPHER_AES_GCM_256 = 52,
+ SOL_TLS = 282,
+ TLS_TX = 1,
+ TLS_RX = 2,
+ Value =
+ <<TLS_1_3_VERSION:16/native,
+ TLS_CIPHER_AES_GCM_256:16/native,
+ IV/bytes, Key/bytes,
+ Salt/bytes, CipherSeq:64/native>>,
+ %%
+ SOL_TLS = 282,
+ TLS_TX = 1,
+ TLS_RX = 2,
+ TLS_TxRx =
+ case TxRx of
+ tx -> TLS_TX;
+ rx -> TLS_RX
+ end,
+ {ok, {{SOL_TLS,TLS_TxRx}, Value}};
+ktls_opt_cipher(
+ _OS, TLS_version, CipherSpec, _CipherState, _CipherSeq, _TxRx) ->
+ {error,
+ {ktls_notsup, {cipher, TLS_version, CipherSpec, _CipherState}}}.
+
+
%% -------------------------------------------------------------------------
%% Trace point
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index b5cb6b5d91..abc5d278a8 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -4,7 +4,9 @@
{modules, [
%% TLS/SSL
tls_connection,
- tls_connection_1_3,
+ tls_client_connection_1_3,
+ tls_server_connection_1_3,
+ tls_gen_connection_1_3,
tls_handshake,
tls_handshake_1_3,
tls_record,
@@ -75,6 +77,7 @@
ssl_crl_hash_dir,
%% Logging
ssl_logger,
+ ssl_trace,
%% App structure
ssl_app,
ssl_sup,
@@ -85,6 +88,6 @@
{applications, [crypto, public_key, kernel, stdlib]},
{env, []},
{mod, {ssl_app, []}},
- {runtime_dependencies, ["stdlib-4.1","public_key-1.11.3","kernel-8.4",
- "erts-10.0","crypto-5.0", "inets-5.10.7",
+ {runtime_dependencies, ["stdlib-4.1","public_key-1.11.3","kernel-@OTP-18235@",
+ "erts-@OTP-18248@","crypto-5.0", "inets-5.10.7",
"runtime_tools-1.15.1"]}]}.
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index ad5028655d..0a1db06804 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -81,6 +81,7 @@
filter_cipher_suites/2,
prepend_cipher_suites/2,
append_cipher_suites/2,
+ signature_algs/2,
eccs/0,
eccs/1,
versions/0,
@@ -94,12 +95,14 @@
connection_information/1,
connection_information/2]).
%% Misc
--export([handle_options/2,
- handle_options/3,
- tls_version/1,
+-export([handle_options/3,
+ update_options/3,
+ tls_version/1,
suite_to_str/1,
suite_to_openssl_str/1,
str_to_suite/1]).
+%% Tracing
+-export([handle_trace/3]).
-removed({ssl_accept, '_',
"use ssl_handshake/1,2,3 instead"}).
@@ -178,16 +181,14 @@
des_cbc |
'3des_ede_cbc'.
--type hash() :: sha |
- sha2() |
+-type hash() :: sha2() |
legacy_hash(). % exported
--type sha2() :: sha224 |
- sha256 |
- sha384 |
- sha512.
+-type sha2() :: sha256 |
+ sha384 |
+ sha512.
--type legacy_hash() :: md5.
+-type legacy_hash() :: sha224 | sha | md5.
-type sign_algo() :: rsa | dsa | ecdsa | eddsa. % exported
@@ -245,7 +246,9 @@
brainpoolP256r1 |
secp256k1 |
secp256r1 |
- sect239k1 |
+ legacy_named_curve(). % exported
+
+-type legacy_named_curve() :: sect239k1 |
sect233k1 |
sect233r1 |
secp224k1 |
@@ -259,9 +262,9 @@
sect163r2 |
secp160k1 |
secp160r1 |
- secp160r2. % exported
+ secp160r2.
--type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 |
+-type group() :: x25519 | x448 | secp256r1 | secp384r1 | secp521r1 | ffdhe2048 |
ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. % exported
-type srp_param_type() :: srp_1024 |
@@ -386,7 +389,7 @@
-type log_alert() :: boolean().
-type logging_level() :: logger:level() | none | all.
-type client_session_tickets() :: disabled | manual | auto.
--type server_session_tickets() :: disabled | stateful | stateless.
+-type server_session_tickets() :: disabled | stateful | stateless | stateful_with_cert | stateless_with_cert.
-type session_tickets() :: client_session_tickets() | server_session_tickets().
-type key_update_at() :: pos_integer().
-type bloom_filter_window_size() :: integer().
@@ -400,6 +403,7 @@
-type middlebox_comp_mode() :: boolean().
-type client_early_data() :: binary().
-type server_early_data() :: disabled | enabled.
+-type use_srtp() :: #{protection_profiles := [binary()], mki => binary()}.
-type spawn_opts() :: [erlang:spawn_opt_option()].
%% -------------------------------------------------------------------------------------------------------
@@ -421,7 +425,8 @@
{certificate_authorities, client_certificate_authorities()} |
{session_tickets, client_session_tickets()} |
{use_ticket, use_ticket()} |
- {early_data, client_early_data()}.
+ {early_data, client_early_data()} |
+ {use_srtp, use_srtp()}.
%% {ocsp_stapling, ocsp_stapling()} |
%% {ocsp_responder_certs, ocsp_responder_certs()} |
%% {ocsp_nonce, ocsp_nonce()}.
@@ -470,9 +475,11 @@
{honor_ecc_order, honor_ecc_order()} |
{client_renegotiation, client_renegotiation()}|
{session_tickets, server_session_tickets()} |
+ {stateless_tickets_seed, stateless_tickets_seed()} |
{anti_replay, anti_replay()} |
{cookie, cookie()} |
- {early_data, server_early_data()}.
+ {early_data, server_early_data()} |
+ {use_srtp, use_srtp()}.
-type server_cacerts() :: [public_key:der_encoded()] | [public_key:combined_cert()].
-type server_cafile() :: file:filename().
@@ -486,10 +493,11 @@
-type server_reuse_session() :: fun().
-type server_reuse_sessions() :: boolean().
-type sni_hosts() :: [{hostname(), [server_option() | common_option()]}].
--type sni_fun() :: fun().
+-type sni_fun() :: fun((string()) -> [] | undefined).
-type honor_cipher_order() :: boolean().
-type honor_ecc_order() :: boolean().
-type client_renegotiation() :: boolean().
+-type stateless_tickets_seed() :: binary().
-type cookie() :: boolean().
-type server_certificate_authorities() :: boolean().
%% -------------------------------------------------------------------------------------------------------
@@ -593,11 +601,11 @@ connect(Socket, SslOptions) ->
connect(Socket, SslOptions0, Timeout) when is_list(SslOptions0) andalso
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- CbInfo = handle_option_cb_info(SslOptions0, tls),
- Transport = element(1, CbInfo),
- try handle_options(Transport, Socket, SslOptions0, client, undefined) of
- {ok, Config} ->
- tls_socket:upgrade(Socket, Config, Timeout)
+ try
+ CbInfo = handle_option_cb_info(SslOptions0, tls),
+ Transport = element(1, CbInfo),
+ {ok, Config} = handle_options(Transport, Socket, SslOptions0, client, undefined),
+ tls_socket:upgrade(Socket, Config, Timeout)
catch
_:{error, Reason} ->
{error, Reason}
@@ -642,7 +650,7 @@ listen(_Port, []) ->
{error, nooptions};
listen(Port, Options0) ->
try
- {ok, Config} = handle_options(Options0, server),
+ {ok, Config} = handle_options(Options0, server, undefined),
do_listen(Port, Config, Config#config.connection_cb)
catch
Error = {error, _} ->
@@ -729,7 +737,7 @@ handshake(ListenSocket, SslOptions) ->
Reason :: closed | timeout | {options, any()} | error_alert().
handshake(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
- (Timeout == infinity)->
+ (Timeout == infinity)->
handshake(Socket, Timeout);
handshake(#sslsocket{fd = {_, _, _, Trackers}} = Socket, SslOpts, Timeout) when
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
@@ -751,19 +759,20 @@ handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout)
Error = {error, _Reason} -> Error
end;
handshake(Socket, SslOptions, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- CbInfo = handle_option_cb_info(SslOptions, tls),
- Transport = element(1, CbInfo),
- ConnetionCb = connection_cb(SslOptions),
- try handle_options(Transport, Socket, SslOptions, server, undefined) of
- {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} ->
- ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()),
- {ok, Port} = tls_socket:port(Transport, Socket),
- {ok, SessionIdHandle} = tls_socket:session_id_tracker(ssl_unknown_listener, SslOpts),
- ssl_gen_statem:handshake(ConnetionCb, Port, Socket,
- {SslOpts,
- tls_socket:emulated_socket_options(EmOpts, #socket_options{}),
- [{session_id_tracker, SessionIdHandle}]},
- self(), CbInfo, Timeout)
+ try
+ CbInfo = handle_option_cb_info(SslOptions, tls),
+ Transport = element(1, CbInfo),
+ ConnetionCb = connection_cb(SslOptions),
+ {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} =
+ handle_options(Transport, Socket, SslOptions, server, undefined),
+ ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()),
+ {ok, Port} = tls_socket:port(Transport, Socket),
+ {ok, SessionIdHandle} = tls_socket:session_id_tracker(ssl_unknown_listener, SslOpts),
+ ssl_gen_statem:handshake(ConnetionCb, Port, Socket,
+ {SslOpts,
+ tls_socket:emulated_socket_options(EmOpts, #socket_options{}),
+ [{session_id_tracker, SessionIdHandle}]},
+ self(), CbInfo, Timeout)
catch
Error = {error, _Reason} -> Error
end.
@@ -1001,7 +1010,7 @@ negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
%%--------------------------------------------------------------------
-spec cipher_suites(Description, Version) -> ciphers() when
Description :: default | all | exclusive | anonymous | exclusive_anonymous,
- Version :: protocol_version().
+ Version :: protocol_version() | ssl_record:ssl_version().
%% Description: Returns all default and all supported cipher suites for a
%% TLS/DTLS version
@@ -1010,17 +1019,17 @@ cipher_suites(Description, Version) when Version == 'tlsv1.3';
Version == 'tlsv1.2';
Version == 'tlsv1.1';
Version == tlsv1 ->
- cipher_suites(Description, tls_record:protocol_version(Version));
+ cipher_suites(Description, tls_record:protocol_version_name(Version));
cipher_suites(Description, Version) when Version == 'dtlsv1.2';
Version == 'dtlsv1'->
- cipher_suites(Description, dtls_record:protocol_version(Version));
+ cipher_suites(Description, dtls_record:protocol_version_name(Version));
cipher_suites(Description, Version) ->
[ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Description, Version)].
%%--------------------------------------------------------------------
-spec cipher_suites(Description, Version, rfc | openssl) -> [string()] when
Description :: default | all | exclusive | anonymous,
- Version :: protocol_version().
+ Version :: protocol_version() | ssl_record:ssl_version().
%% Description: Returns all default and all supported cipher suites for a
%% TLS/DTLS version
@@ -1029,10 +1038,10 @@ cipher_suites(Description, Version, StringType) when Version == 'tlsv1.3';
Version == 'tlsv1.2';
Version == 'tlsv1.1';
Version == tlsv1 ->
- cipher_suites(Description, tls_record:protocol_version(Version), StringType);
+ cipher_suites(Description, tls_record:protocol_version_name(Version), StringType);
cipher_suites(Description, Version, StringType) when Version == 'dtlsv1.2';
Version == 'dtlsv1'->
- cipher_suites(Description, dtls_record:protocol_version(Version), StringType);
+ cipher_suites(Description, dtls_record:protocol_version_name(Version), StringType);
cipher_suites(Description, Version, rfc) ->
[ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite))
|| Suite <- supported_suites(Description, Version)];
@@ -1097,6 +1106,41 @@ append_cipher_suites(Filters, Suites) ->
(Suites -- Deferred) ++ Deferred.
%%--------------------------------------------------------------------
+-spec signature_algs(Description, Version) -> [signature_algs()] when
+ Description :: default | all | exclusive,
+ Version :: protocol_version().
+
+%% Description: Returns possible signature algorithms/schemes
+%% for TLS/DTLS version
+%%--------------------------------------------------------------------
+
+signature_algs(default, 'tlsv1.3') ->
+ tls_v1:default_signature_algs([tls_record:protocol_version_name('tlsv1.3'),
+ tls_record:protocol_version_name('tlsv1.2')]);
+signature_algs(default, 'tlsv1.2') ->
+ tls_v1:default_signature_algs([tls_record:protocol_version_name('tlsv1.2')]);
+signature_algs(all, 'tlsv1.3') ->
+ tls_v1:default_signature_algs([tls_record:protocol_version_name('tlsv1.3'),
+ tls_record:protocol_version_name('tlsv1.2')]) ++
+ tls_v1:legacy_signature_algs_pre_13();
+signature_algs(all, 'tlsv1.2') ->
+ tls_v1:default_signature_algs([tls_record:protocol_version_name('tlsv1.2')]) ++
+ tls_v1:legacy_signature_algs_pre_13();
+signature_algs(exclusive, 'tlsv1.3') ->
+ tls_v1:default_signature_algs([tls_record:protocol_version_name('tlsv1.3')]);
+signature_algs(exclusive, 'tlsv1.2') ->
+ Algs = tls_v1:default_signature_algs([tls_record:protocol_version_name('tlsv1.2')]),
+ Algs ++ tls_v1:legacy_signature_algs_pre_13();
+signature_algs(Description, 'dtlsv1.2') ->
+ signature_algs(Description, 'tlsv1.2');
+signature_algs(Description, Version) when Description == default;
+ Description == all;
+ Description == exclusive->
+ {error, {signature_algs_not_supported_in_protocol_version, Version}};
+signature_algs(Description,_) ->
+ {error, {badarg, Description}}.
+
+%%--------------------------------------------------------------------
-spec eccs() -> NamedCurves when
NamedCurves :: [named_curve()].
@@ -1134,14 +1178,14 @@ eccs_filter_supported(Curves) ->
%% Description: returns all supported groups (TLS 1.3 and later)
%%--------------------------------------------------------------------
groups() ->
- tls_v1:groups(4).
+ tls_v1:groups().
%%--------------------------------------------------------------------
-spec groups(default) -> [group()].
%% Description: returns the default groups (TLS 1.3 and later)
%%--------------------------------------------------------------------
groups(default) ->
- tls_v1:default_groups(4).
+ tls_v1:default_groups().
%%--------------------------------------------------------------------
-spec getopts(SslSocket, OptionNames) ->
@@ -1328,8 +1372,8 @@ versions() ->
SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- ConfTLSVsns, TLSCryptoSupported(Vsn)],
SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- ConfDTLSVsns, DTLSCryptoSupported(Vsn)],
- AvailableTLSVsns = [Vsn || Vsn <- ImplementedTLSVsns, TLSCryptoSupported(tls_record:protocol_version(Vsn))],
- AvailableDTLSVsns = [Vsn || Vsn <- ImplementedDTLSVsns, DTLSCryptoSupported(dtls_record:protocol_version(Vsn))],
+ AvailableTLSVsns = [Vsn || Vsn <- ImplementedTLSVsns, TLSCryptoSupported(tls_record:protocol_version_name(Vsn))],
+ AvailableDTLSVsns = [Vsn || Vsn <- ImplementedDTLSVsns, DTLSCryptoSupported(dtls_record:protocol_version_name(Vsn))],
[{ssl_app, ?VSN},
{supported, SupportedTLSVsns},
@@ -1346,13 +1390,18 @@ versions() ->
%%
%% Description: Initiates a renegotiation.
%%--------------------------------------------------------------------
-renegotiate(#sslsocket{pid = [Pid, Sender |_]}) when is_pid(Pid),
+renegotiate(#sslsocket{pid = [Pid, Sender |_]} = Socket) when is_pid(Pid),
is_pid(Sender) ->
- case tls_sender:renegotiate(Sender) of
- {ok, Write} ->
- tls_dtls_connection:renegotiation(Pid, Write);
- Error ->
- Error
+ case ssl:connection_information(Socket, [protocol]) of
+ {ok, [{protocol, 'tlsv1.3'}]} ->
+ {error, notsup};
+ _ ->
+ case tls_sender:renegotiate(Sender) of
+ {ok, Write} ->
+ tls_dtls_connection:renegotiation(Pid, Write);
+ Error ->
+ Error
+ end
end;
renegotiate(#sslsocket{pid = [Pid |_]}) when is_pid(Pid) ->
tls_dtls_connection:renegotiation(Pid);
@@ -1378,7 +1427,7 @@ update_keys(#sslsocket{pid = [Pid, Sender |_]}, Type0) when is_pid(Pid) andalso
read_write ->
update_requested
end,
- tls_connection_1_3:send_key_update(Sender, Type);
+ tls_gen_connection_1_3:send_key_update(Sender, Type);
update_keys(_, Type) ->
{error, {illegal_parameter, Type}}.
@@ -1418,9 +1467,9 @@ format_error({error, Reason}) ->
format_error(Reason) ->
do_format_error(Reason).
-tls_version({3, _} = Version) ->
+tls_version(Version) when ?TLS_1_X(Version) ->
Version;
-tls_version({254, _} = Version) ->
+tls_version(Version) when ?DTLS_1_X(Version) ->
dtls_v1:corresponding_tls_version(Version).
%%--------------------------------------------------------------------
@@ -1470,57 +1519,132 @@ str_to_suite(CipherSuiteName) ->
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
-supported_suites(exclusive, {3,Minor}) ->
- tls_v1:exclusive_suites(Minor);
-supported_suites(exclusive, {254, Minor}) ->
- dtls_v1:exclusive_suites(Minor);
+supported_suites(exclusive, Version) when ?TLS_1_X(Version) ->
+ tls_v1:exclusive_suites(Version);
+supported_suites(exclusive, Version) when ?DTLS_1_X(Version) ->
+ dtls_v1:exclusive_suites(Version);
supported_suites(default, Version) ->
ssl_cipher:suites(Version);
supported_suites(all, Version) ->
ssl_cipher:all_suites(Version);
supported_suites(anonymous, Version) ->
ssl_cipher:anonymous_suites(Version);
-supported_suites(exclusive_anonymous, {3, Minor}) ->
- tls_v1:exclusive_anonymous_suites(Minor);
-supported_suites(exclusive_anonymous, {254, Minor}) ->
- dtls_v1:exclusive_anonymous_suites(Minor).
+supported_suites(exclusive_anonymous, Version) when ?TLS_1_X(Version) ->
+ tls_v1:exclusive_anonymous_suites(Version);
+supported_suites(exclusive_anonymous, Version) when ?DTLS_1_X(Version) ->
+ dtls_v1:exclusive_anonymous_suites(Version).
do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_gen_connection) ->
tls_socket:listen(Transport, Port, Config);
do_listen(Port, Config, dtls_gen_connection) ->
dtls_socket:listen(Port, Config).
-
--spec handle_options([any()], client | server) -> {ok, #config{}};
- ([any()], ssl_options()) -> ssl_options().
-handle_options(Opts, Role) ->
- handle_options(undefined, undefined, Opts, Role, undefined).
-
-handle_options(Opts, Role, InheritedSslOpts) ->
- handle_options(undefined, undefined, Opts, Role, InheritedSslOpts).
+ssl_options() ->
+ [
+ alpn_advertised_protocols, alpn_preferred_protocols,
+ anti_replay,
+ beast_mitigation,
+ cacertfile, cacerts,
+ cert, certs_keys,certfile,
+ certificate_authorities,
+ ciphers,
+ client_renegotiation,
+ cookie,
+ crl_cache, crl_check,
+ customize_hostname_check,
+ depth,
+ dh, dhfile,
+
+ early_data,
+ eccs,
+ erl_dist,
+ fail_if_no_peer_cert,
+ fallback,
+ handshake,
+ hibernate_after,
+ honor_cipher_order, honor_ecc_order,
+ keep_secrets,
+ key, keyfile,
+ key_update_at,
+ ktls,
+
+ log_level,
+ max_handshake_size,
+ middlebox_comp_mode,
+ max_fragment_length,
+ next_protocol_selector, next_protocols_advertised,
+ ocsp_stapling, ocsp_responder_certs, ocsp_nonce,
+ padding_check,
+ partial_chain,
+ password,
+ protocol,
+ psk_identity,
+ receiver_spawn_opts,
+ renegotiate_at,
+ reuse_session, reuse_sessions,
+
+ secure_renegotiate,
+ sender_spawn_opts,
+ server_name_indication,
+ session_tickets,
+ stateless_tickets_seed,
+ signature_algs, signature_algs_cert,
+ sni_fun,
+ sni_hosts,
+ srp_identity,
+ supported_groups,
+ use_ticket,
+ use_srtp,
+ user_lookup_fun,
+ verify, verify_fun,
+ versions
+ ].
%% Handle ssl options at handshake, handshake_continue
-handle_options(_, _, Opts0, Role, InheritedSslOpts) when is_map(InheritedSslOpts) ->
- {SslOpts, _} = expand_options(Opts0, ?RULES),
- process_options(SslOpts, InheritedSslOpts, #{role => Role,
- rules => ?RULES});
+-spec update_options([any()], client | server, map()) -> map().
+update_options(Opts, Role, InheritedSslOpts) when is_map(InheritedSslOpts) ->
+ {UserSslOpts, _} = split_options(Opts, ssl_options()),
+ process_options(UserSslOpts, InheritedSslOpts, #{role => Role}).
+
+process_options(UserSslOpts, SslOpts0, Env) ->
+ %% Reverse option list so we get the last set option if set twice,
+ %% users depend on it.
+ UserSslOptsMap = proplists:to_map(lists:reverse(UserSslOpts)),
+ SslOpts1 = opt_protocol_versions(UserSslOptsMap, SslOpts0, Env),
+ SslOpts2 = opt_verification(UserSslOptsMap, SslOpts1, Env),
+ SslOpts3 = opt_certs(UserSslOptsMap, SslOpts2, Env),
+ SslOpts4 = opt_tickets(UserSslOptsMap, SslOpts3, Env),
+ SslOpts5 = opt_ocsp(UserSslOptsMap, SslOpts4, Env),
+ SslOpts6 = opt_sni(UserSslOptsMap, SslOpts5, Env),
+ SslOpts7 = opt_signature_algs(UserSslOptsMap, SslOpts6, Env),
+ SslOpts8 = opt_alpn(UserSslOptsMap, SslOpts7, Env),
+ SslOpts9 = opt_mitigation(UserSslOptsMap, SslOpts8, Env),
+ SslOpts10 = opt_server(UserSslOptsMap, SslOpts9, Env),
+ SslOpts11 = opt_client(UserSslOptsMap, SslOpts10, Env),
+ SslOpts12 = opt_renegotiate(UserSslOptsMap, SslOpts11, Env),
+ SslOpts13 = opt_reuse_sessions(UserSslOptsMap, SslOpts12, Env),
+ SslOpts14 = opt_identity(UserSslOptsMap, SslOpts13, Env),
+ SslOpts15 = opt_supported_groups(UserSslOptsMap, SslOpts14, Env),
+ SslOpts16 = opt_crl(UserSslOptsMap, SslOpts15, Env),
+ SslOpts17 = opt_handshake(UserSslOptsMap, SslOpts16, Env),
+ SslOpts18 = opt_use_srtp(UserSslOptsMap, SslOpts17, Env),
+ SslOpts = opt_process(UserSslOptsMap, SslOpts18, Env),
+ SslOpts.
+
+-spec handle_options([any()], client | server, undefined|host()) -> {ok, #config{}}.
+handle_options(Opts, Role, Host) ->
+ handle_options(undefined, undefined, Opts, Role, Host).
+
%% Handle all options in listen, connect and handshake
handle_options(Transport, Socket, Opts0, Role, Host) ->
- {SslOpts0, SockOpts0} = expand_options(Opts0, ?RULES),
-
- %% Ensure all options are evaluated at startup
- SslOpts1 = add_missing_options(SslOpts0, ?RULES),
- SslOpts2 = #{protocol := Protocol}
- = process_options(SslOpts1,
- #{},
- #{role => Role,
- host => Host,
- rules => ?RULES}),
-
- maybe_client_warn_no_verify(SslOpts2, Role),
- SslOpts = maps:without([warn_verify_none], SslOpts2),
+ {UserSslOptsList, SockOpts0} = split_options(Opts0, ssl_options()),
+
+ Env = #{role => Role, host => Host},
+ SslOpts = process_options(UserSslOptsList, #{}, Env),
+
%% Handle special options
+ #{protocol := Protocol} = SslOpts,
{Sock, Emulated} = emulated_options(Transport, Socket, Protocol, SockOpts0),
ConnetionCb = connection_cb(Protocol),
CbInfo = handle_option_cb_info(Opts0, Protocol),
@@ -1535,811 +1659,367 @@ handle_options(Transport, Socket, Opts0, Role, Host) ->
}}.
-%% process_options(SSLOptions, OptionsMap, Env) where
-%% SSLOptions is the following tuple:
-%% {InOptions, SkippedOptions, Counter}
-%%
-%% The list of options is processed in multiple passes. When
-%% processing an option all dependencies must already be resolved.
-%% If there are unresolved dependencies the option will be
-%% skipped and processed in a subsequent pass.
-%% Counter is equal to the number of unprocessed options at
-%% the beginning of a pass. Its value must monotonically decrease
-%% after each successful pass.
-%% If the value of the counter is unchanged at the end of a pass,
-%% the processing stops due to faulty input data.
-process_options({[], [], _}, OptionsMap, _Env) ->
- OptionsMap;
-process_options({[], [_|_] = Skipped, Counter}, OptionsMap, Env)
- when length(Skipped) < Counter ->
- %% Continue handling options if current pass was successful
- process_options({Skipped, [], length(Skipped)}, OptionsMap, Env);
-process_options({[], [_|_], _Counter}, _OptionsMap, _Env) ->
- throw({error, faulty_configuration});
-process_options({[{K0,V} = E|T], S, Counter}, OptionsMap0, Env) ->
- K = maybe_map_key_internal(K0),
- case check_dependencies(K, OptionsMap0, Env) of
- true ->
- OptionsMap = handle_option(K, V, OptionsMap0, Env),
- process_options({T, S, Counter}, OptionsMap, Env);
- false ->
- %% Skip option for next pass
- process_options({T, [E|S], Counter}, OptionsMap0, Env)
- end.
+opt_protocol_versions(UserOpts, Opts, Env) ->
+ {_, PRC} = get_opt_of(protocol, [tls, dtls], tls, UserOpts, Opts),
+
+ LogLevels = [none, all, emergency, alert, critical, error,
+ warning, notice, info, debug],
-handle_option(anti_replay = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(anti_replay = Option, Value0,
- #{session_tickets := SessionTickets,
- versions := Versions} = OptionsMap, #{rules := Rules}) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- assert_option_dependency(Option, session_tickets, [SessionTickets], [stateless]),
- case SessionTickets of
- stateless ->
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
- _ ->
- OptionsMap#{Option => default_value(Option, Rules)}
- end;
-handle_option(beast_mitigation = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(beast_mitigation = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts,
- verify := Verify,
- verify_fun := VerifyFun} = OptionsMap, _Env)
- when Verify =:= verify_none orelse
- Verify =:= 0 ->
- Value = validate_option(Option, ca_cert_default(verify_none, VerifyFun, CaCerts)),
- OptionsMap#{Option => Value};
-handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts,
- verify := Verify,
- verify_fun := VerifyFun} = OptionsMap, _Env)
- when Verify =:= verify_peer orelse
- Verify =:= 1 orelse
- Verify =:= 2 ->
- Value = validate_option(Option, ca_cert_default(verify_peer, VerifyFun, CaCerts)),
- OptionsMap#{Option => Value};
-handle_option(cacertfile = Option, Value0, OptionsMap, _Env) ->
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(ciphers = Option, unbound, #{versions := Versions} = OptionsMap, #{rules := Rules}) ->
- Value = handle_cipher_option(default_value(Option, Rules), Versions),
- OptionsMap#{Option => Value};
-handle_option(ciphers = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- Value = handle_cipher_option(Value0, Versions),
- OptionsMap#{Option => Value};
-handle_option(client_renegotiation = Option, unbound, OptionsMap, #{role := Role}) ->
- Value = default_option_role(server, true, Role),
- OptionsMap#{Option => Value};
-handle_option(client_renegotiation = Option, Value0,
- #{versions := Versions} = OptionsMap, #{role := Role}) ->
- assert_role(server_only, Role, Option, Value0),
- assert_option_dependency(Option, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(early_data = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(early_data = Option, Value0, #{session_tickets := SessionTickets,
- versions := Versions} = OptionsMap,
- #{role := server = Role}) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- assert_option_dependency(Option, session_tickets, [SessionTickets],
- [stateful, stateless]),
- Value = validate_option(Option, Value0, Role),
- OptionsMap#{Option => Value};
-handle_option(early_data = Option, Value0, #{session_tickets := SessionTickets,
- use_ticket := UseTicket,
- versions := Versions} = OptionsMap,
- #{role := client = Role}) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- assert_option_dependency(Option, session_tickets, [SessionTickets],
- [manual, auto]),
- case UseTicket of
- undefined when SessionTickets =/= auto ->
- throw({error, {options, dependency, {Option, use_ticket}}});
- _ ->
- ok
- end,
- Value = validate_option(Option, Value0, Role),
- OptionsMap#{Option => Value};
-handle_option(eccs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) ->
- Value = handle_eccs_option(eccs(), HighestVersion),
- OptionsMap#{Option => Value};
-handle_option(eccs = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
- Value = handle_eccs_option(Value0, HighestVersion),
- OptionsMap#{Option => Value};
-handle_option(fallback = Option, unbound, OptionsMap, #{role := Role}) ->
- Value = default_option_role(client, false, Role),
- OptionsMap#{Option => Value};
-handle_option(fallback = Option, Value0, OptionsMap, #{role := Role}) ->
- assert_role(client_only, Role, Option, Value0),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(certificate_authorities = Option, unbound, OptionsMap, #{role := server}) ->
- OptionsMap#{Option => true};
-handle_option(certificate_authorities = Option, unbound, OptionsMap, #{role := client}) ->
- OptionsMap#{Option => false};
-handle_option(certificate_authorities = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(cookie = Option, unbound, OptionsMap, #{role := Role}) ->
- Value = default_option_role(server, true, Role),
- OptionsMap#{Option => Value};
-handle_option(cookie = Option, Value0, #{versions := Versions} = OptionsMap, #{role := Role}) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- assert_role(server_only, Role, Option, Value0),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(honor_cipher_order = Option, unbound, OptionsMap, #{role := Role}) ->
- Value = default_option_role(server, false, Role),
- OptionsMap#{Option => Value};
-handle_option(honor_cipher_order = Option, Value0, OptionsMap, #{role := Role}) ->
- assert_role(server_only, Role, Option, Value0),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(honor_ecc_order = Option, unbound, OptionsMap, #{role := Role}) ->
- Value = default_option_role(server, false, Role),
- OptionsMap#{Option => Value};
-handle_option(honor_ecc_order = Option, Value0, OptionsMap, #{role := Role}) ->
- assert_role(server_only, Role, Option, Value0),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(keyfile = Option, unbound, #{certfile := CertFile} = OptionsMap, _Env) ->
- Value = validate_option(Option, CertFile),
- OptionsMap#{Option => Value};
-handle_option(key_update_at = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(key_update_at = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(log_level = Option, unbound, OptionsMap, _Env) ->
DefaultLevel = case logger:get_module_level(?MODULE) of
[] -> notice;
[{ssl,Level}] -> Level
end,
- Value = validate_option(Option, DefaultLevel),
- OptionsMap#{Option => Value};
-handle_option(next_protocols_advertised = Option, unbound, OptionsMap,
- #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(next_protocols_advertised = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(next_protocols_advertised, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(next_protocol_selector = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = default_value(Option, Rules),
- OptionsMap#{Option => Value};
-handle_option(next_protocol_selector = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(client_preferred_next_protocols, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = make_next_protocol_selector(
- validate_option(client_preferred_next_protocols, Value0)),
- OptionsMap#{Option => Value};
-handle_option(padding_check = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(padding_check = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(password = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{password => Value};
-handle_option(password = Option, Value0, OptionsMap, _Env) ->
- Value = validate_option(Option, Value0),
- OptionsMap#{password => Value};
-handle_option(certs_keys, unbound, OptionsMap, _Env) ->
- OptionsMap;
-handle_option(certs_keys = Option, Value0, OptionsMap, _Env) ->
- Value = validate_option(Option, Value0),
- OptionsMap#{certs_keys => Value};
-handle_option(psk_identity = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(psk_identity = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(secure_renegotiate = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(secure_renegotiate= Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(secure_renegotiate, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) ->
- Value =
- case Role of
- client ->
- undefined;
- server ->
- fun(_, _, _, _) -> true end
+
+ {_, LL} = get_opt_of(log_level, LogLevels, DefaultLevel, UserOpts, Opts),
+
+ Opts1 = set_opt_bool(keep_secrets, false, UserOpts, Opts),
+
+ {DistW, Dist} = get_opt_bool(erl_dist, false, UserOpts, Opts1),
+ option_incompatible(PRC =:= dtls andalso Dist, [{protocol, PRC}, {erl_dist, Dist}]),
+ Opts2 = set_opt_new(DistW, erl_dist, false, Dist, Opts1),
+
+ {KtlsW, Ktls} = get_opt_bool(ktls, false, UserOpts, Opts1),
+ option_incompatible(PRC =:= dtls andalso Ktls, [{protocol, PRC}, {ktls, Ktls}]),
+ Opts3 = set_opt_new(KtlsW, ktls, false, Ktls, Opts2),
+
+ opt_versions(UserOpts, Opts3#{protocol => PRC, log_level => LL}, Env).
+
+opt_versions(UserOpts, #{protocol := Protocol} = Opts, _Env) ->
+ Versions = case get_opt(versions, unbound, UserOpts, Opts) of
+ {default, unbound} -> default_versions(Protocol);
+ {new, Vs} -> validate_versions(Protocol, Vs);
+ {old, Vs} -> Vs
+ end,
+
+ {Where, MCM} = get_opt_bool(middlebox_comp_mode, true, UserOpts, Opts),
+ assert_version_dep(Where =:= new, middlebox_comp_mode, Versions, ['tlsv1.3']),
+ Opts1 = set_opt_new(Where, middlebox_comp_mode, true, MCM, Opts),
+ Opts1#{versions => Versions}.
+
+default_versions(tls) ->
+ Vsns0 = tls_record:supported_protocol_versions(),
+ lists:sort(fun tls_record:is_higher/2, Vsns0);
+default_versions(dtls) ->
+ Vsns0 = dtls_record:supported_protocol_versions(),
+ lists:sort(fun dtls_record:is_higher/2, Vsns0).
+
+validate_versions(tls, Vsns0) ->
+ Validate =
+ fun(Version) ->
+ try tls_record:sufficient_crypto_support(Version) of
+ true -> tls_record:protocol_version_name(Version);
+ false -> option_error(insufficient_crypto_support,
+ {Version, {versions, Vsns0}})
+ catch error:function_clause ->
+ option_error(Version, {versions, Vsns0})
+ end
end,
- OptionsMap#{Option => Value};
-handle_option(reuse_session = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(reuse_session, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-%% TODO: validate based on role
-handle_option(reuse_sessions = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(reuse_sessions = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(reuse_sessions, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(server_name_indication = Option, unbound, OptionsMap, #{host := Host,
- role := Role}) ->
- Value = default_option_role(client, server_name_indication_default(Host), Role),
- OptionsMap#{Option => Value};
-handle_option(server_name_indication = Option, Value0, OptionsMap, _Env) ->
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(session_tickets = Option, unbound, OptionsMap, #{role := Role,
- rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules), Role),
- OptionsMap#{Option => Value};
-handle_option(session_tickets = Option, Value0, #{versions := Versions} = OptionsMap, #{role := Role}) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- Value = validate_option(Option, Value0, Role),
- OptionsMap#{Option => Value};
-handle_option(signature_algs = Option, unbound, #{versions := [HighestVersion | _] = Versions} = OptionsMap, #{role := Role}) ->
- Value =
- handle_hashsigns_option(
- default_option_role_sign_algs(
- server,
- tls_v1:default_signature_algs(Versions),
- Role,
- HighestVersion),
- tls_version(HighestVersion)),
- OptionsMap#{Option => Value};
-handle_option(signature_algs = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
- Value = handle_hashsigns_option(Value0, tls_version(HighestVersion)),
- OptionsMap#{Option => Value};
-handle_option(signature_algs_cert = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
- %% Do not send by default
- Value = handle_signature_algorithms_option(undefined, tls_version(HighestVersion)),
- OptionsMap#{Option => Value};
-handle_option(signature_algs_cert = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
- Value = handle_signature_algorithms_option(Value0, tls_version(HighestVersion)),
- OptionsMap#{Option => Value};
-handle_option(sni_fun = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = default_value(Option, Rules),
- OptionsMap#{Option => Value};
-handle_option(sni_fun = Option, Value0, OptionsMap, _Env) ->
- validate_option(Option, Value0),
- OptHosts = maps:get(sni_hosts, OptionsMap, undefined),
- Value =
- case {Value0, OptHosts} of
- {undefined, _} ->
- Value0;
- {_, []} ->
- Value0;
- _ ->
- throw({error, {conflict_options, [sni_fun, sni_hosts]}})
+ Vsns = [Validate(V) || V <- Vsns0],
+ tls_validate_version_gap(Vsns0),
+ option_error([] =:= Vsns, versions, Vsns0),
+ lists:sort(fun tls_record:is_higher/2, Vsns);
+validate_versions(dtls, Vsns0) ->
+ Validate =
+ fun(Version) ->
+ try tls_record:sufficient_crypto_support(
+ dtls_v1:corresponding_tls_version(
+ dtls_record:protocol_version_name(Version))) of
+ true -> dtls_record:protocol_version_name(Version);
+ false-> option_error(insufficient_crypto_support,
+ {Version, {versions, Vsns0}})
+ catch error:function_clause ->
+ option_error(Version, {versions, Vsns0})
+ end
end,
- OptionsMap#{Option => Value};
-handle_option(srp_identity = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(srp_identity = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(srp_identity, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(supported_groups = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) ->
- Value = handle_supported_groups_option(groups(default), HighestVersion),
- OptionsMap#{Option => Value};
-handle_option(supported_groups = Option, Value0,
- #{versions := [HighestVersion|_] = Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- Value = handle_supported_groups_option(Value0, HighestVersion),
- OptionsMap#{Option => Value};
-handle_option(use_ticket = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(use_ticket = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(user_lookup_fun = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(user_lookup_fun = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(verify = Option, unbound, OptionsMap, #{rules := Rules}) ->
- handle_verify_option(default_value(Option, Rules), OptionsMap#{warn_verify_none => true});
-handle_option(verify = _Option, Value, OptionsMap, _Env) ->
- handle_verify_option(Value, OptionsMap);
-handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, #{rules := Rules})
- when Verify =:= verify_none ->
- OptionsMap#{Option => default_value(Option, Rules)};
-handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, _Env)
- when Verify =:= verify_peer ->
- OptionsMap#{Option => undefined};
-handle_option(verify_fun = Option, Value0, OptionsMap, _Env) ->
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(versions = Option, unbound, #{protocol := Protocol} = OptionsMap, _Env) ->
- RecordCb = record_cb(Protocol),
- Vsns0 = RecordCb:supported_protocol_versions(),
- Value = lists:sort(fun RecordCb:is_higher/2, Vsns0),
- OptionsMap#{Option => Value};
-handle_option(versions = Option, Vsns0, #{protocol := Protocol} = OptionsMap, _Env) ->
- validate_option(versions, Vsns0),
- RecordCb = record_cb(Protocol),
- Vsns1 = [RecordCb:protocol_version(Vsn) || Vsn <- Vsns0],
- Value = lists:sort(fun RecordCb:is_higher/2, Vsns1),
- OptionsMap#{Option => Value};
-%% Special options
-handle_option(cb_info = Option, unbound, #{protocol := Protocol} = OptionsMap, _Env) ->
- Default = default_cb_info(Protocol),
- validate_option(Option, Default),
- Value = handle_cb_info(Default),
- OptionsMap#{Option => Value};
-handle_option(cb_info = Option, Value0, OptionsMap, _Env) ->
- validate_option(Option, Value0),
- Value = handle_cb_info(Value0),
- OptionsMap#{Option => Value};
-%% Generic case
-handle_option(Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(Option, Value0, OptionsMap, _Env) ->
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value}.
-
-handle_option_cb_info(Options, Protocol) ->
- Value = proplists:get_value(cb_info, Options, default_cb_info(Protocol)),
- #{cb_info := CbInfo} = handle_option(cb_info, Value, #{protocol => Protocol}, #{}),
- CbInfo.
-
-
-maybe_map_key_internal(client_preferred_next_protocols) ->
- next_protocol_selector;
-maybe_map_key_internal(K) ->
- K.
-
-maybe_map_key_external(next_protocol_selector) ->
- client_preferred_next_protocols;
-maybe_map_key_external(K) ->
- K.
-
-check_dependencies(K, OptionsMap, Env) ->
- Rules = maps:get(rules, Env),
- Deps = get_dependencies(K, Rules),
- case Deps of
- [] ->
- true;
- L ->
- option_already_defined(K,OptionsMap) orelse
- dependecies_already_defined(L, OptionsMap)
+ Vsns = [Validate(V) || V <- Vsns0],
+ option_error([] =:= Vsns, versions, Vsns0),
+ lists:sort(fun dtls_record:is_higher/2, Vsns).
+
+opt_verification(UserOpts, Opts0, #{role := Role} = Env) ->
+ {Verify, Opts1} =
+ case get_opt_of(verify, [verify_none, verify_peer], default_verify(Role), UserOpts, Opts0) of
+ {old, Val} ->
+ {Val, Opts0};
+ {_, verify_none} ->
+ {verify_none, Opts0#{verify => verify_none, verify_fun => {none_verify_fun(), []}}};
+ {_, verify_peer} ->
+ %% If 'verify' is changed from verify_none to verify_peer, (via update_options/3)
+ %% the 'verify_fun' must also be changed to undefined.
+ %% i.e remove verify_none fun
+ Temp = Opts0#{verify => verify_peer, verify_fun => undefined},
+ {verify_peer, maps:remove(fail_if_no_peer_cert, Temp)}
+ end,
+ Opts2 = opt_cacerts(UserOpts, Opts1, Env),
+ {_, PartialChain} = get_opt_fun(partial_chain, 1, fun(_) -> unknown_ca end, UserOpts, Opts2),
+
+ DefFailNoPeer = Role =:= server andalso Verify =:= verify_peer,
+ {_, FailNoPeerCert} = get_opt_bool(fail_if_no_peer_cert, DefFailNoPeer, UserOpts, Opts2),
+ assert_server_only(Role, FailNoPeerCert, fail_if_no_peer_cert),
+ option_incompatible(FailNoPeerCert andalso Verify =:= verify_none,
+ [{verify, verify_none}, {fail_if_no_peer_cert, true}]),
+
+ Opts = set_opt_int(depth, 0, 255, ?DEFAULT_DEPTH, UserOpts, Opts2),
+
+ case Role of
+ client ->
+ opt_verify_fun(UserOpts, Opts#{partial_chain => PartialChain},
+ Env);
+ server ->
+ opt_verify_fun(UserOpts, Opts#{partial_chain => PartialChain,
+ fail_if_no_peer_cert => FailNoPeerCert},
+ Env)
end.
+default_verify(client) ->
+ %% Server authenication is by default requiered
+ verify_peer;
+default_verify(server) ->
+ %% Client certification is an optional part of the protocol
+ verify_none.
+
+opt_verify_fun(UserOpts, Opts, _Env) ->
+ %%DefVerifyNoneFun = {default_verify_fun(), []},
+ VerifyFun = case get_opt(verify_fun, undefined, UserOpts, Opts) of
+ {_, {F,_} = FA} when is_function(F, 3); is_function(F, 4) ->
+ FA;
+ {_, UserFun} when is_function(UserFun, 1) ->
+ {convert_verify_fun(), UserFun};
+ {_, undefined} ->
+ undefined;
+ {_, Value} ->
+ option_error(verify_fun, Value)
+ end,
+ Opts#{verify_fun => VerifyFun}.
+
+none_verify_fun() ->
+ fun(_, {bad_cert, _}, UserState) ->
+ {valid, UserState};
+ (_, {extension, #'Extension'{critical = true}}, UserState) ->
+ %% This extension is marked as critical, so
+ %% certificate verification should fail if we don't
+ %% understand the extension. However, this is
+ %% `verify_none', so let's accept it anyway.
+ {valid, UserState};
+ (_, {extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end.
+
+convert_verify_fun() ->
+ fun(_,{bad_cert, _} = Reason, OldFun) ->
+ case OldFun([Reason]) of
+ true -> {valid, OldFun};
+ false -> {fail, Reason}
+ end;
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end.
-%% Handle options that are not present in the map
-get_dependencies(K, _) when K =:= cb_info orelse K =:= log_alert->
- [];
-get_dependencies(K, Rules) ->
- {_, Deps} = maps:get(K, Rules),
- Deps.
-
-
-option_already_defined(K, Map) ->
- maps:get(K, Map, unbound) =/= unbound.
-
-
-dependecies_already_defined(L, OptionsMap) ->
- Fun = fun (E) -> option_already_defined(E, OptionsMap) end,
- lists:all(Fun, L).
+opt_certs(UserOpts, #{log_level := LogLevel} = Opts0, Env) ->
+ case get_opt_list(certs_keys, [], UserOpts, Opts0) of
+ {Where, []} when Where =/= new ->
+ opt_old_certs(UserOpts, #{}, Opts0, Env);
+ {old, [CertKey]} ->
+ opt_old_certs(UserOpts, CertKey, Opts0, Env);
+ {Where, CKs} when is_list(CKs) ->
+ warn_override(Where, UserOpts, certs_keys, [cert,certfile,key,keyfile,password], LogLevel),
+ Opts0#{certs_keys => [check_cert_key(CK, #{}, LogLevel) || CK <- CKs]}
+ end.
+opt_old_certs(UserOpts, CertKeys, #{log_level := LogLevel}=SSLOpts, _Env) ->
+ CK = check_cert_key(UserOpts, CertKeys, LogLevel),
+ case maps:keys(CK) =:= [] of
+ true ->
+ SSLOpts#{certs_keys => []};
+ false ->
+ SSLOpts#{certs_keys => [CK]}
+ end.
-expand_options(Opts0, Rules) ->
- Opts1 = proplists:expand([{binary, [{mode, binary}]},
- {list, [{mode, list}]}], Opts0),
- Opts2 = handle_option_format(Opts1, []),
+check_cert_key(UserOpts, CertKeys, LogLevel) ->
+ CertKeys0 = case get_opt(cert, undefined, UserOpts, CertKeys) of
+ {Where, Cert} when is_binary(Cert) ->
+ warn_override(Where, UserOpts, cert, [certfile], LogLevel),
+ CertKeys#{cert => [Cert]};
+ {Where, [C0|_] = Certs} when is_binary(C0) ->
+ warn_override(Where, UserOpts, cert, [certfile], LogLevel),
+ CertKeys#{cert => Certs};
+ {new, Err0} ->
+ option_error(cert, Err0);
+ {_, undefined} ->
+ case get_opt_file(certfile, unbound, UserOpts, CertKeys) of
+ {default, unbound} -> CertKeys;
+ {_, CertFile} -> CertKeys#{certfile => CertFile}
+ end
+ end,
- %% Remove deprecated ssl_imp option
- Opts = proplists:delete(ssl_imp, Opts2),
- AllOpts = maps:keys(Rules),
- SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) end,
- Opts,
- AllOpts ++
- [ssl_imp, %% TODO: remove ssl_imp
- cb_info,
- client_preferred_next_protocols, %% next_protocol_selector
- log_alert]), %% obsoleted by log_level
-
- SslOpts0 = Opts -- SockOpts,
- SslOpts = {SslOpts0, [], length(SslOpts0)},
- {SslOpts, SockOpts}.
-
-
-add_missing_options({L0, S, _C}, Rules) ->
- Fun = fun(K0, Acc) ->
- K = maybe_map_key_external(K0),
- case proplists:is_defined(K, Acc) of
- true ->
- Acc;
- false ->
- Default = unbound,
- [{K, Default}|Acc]
- end
- end,
- AllOpts = maps:keys(Rules),
- L = lists:foldl(Fun, L0, AllOpts),
- {L, S, length(L)}.
+ CertKeys1 = case get_opt(key, undefined, UserOpts, CertKeys) of
+ {_, undefined} ->
+ case get_opt_file(keyfile, <<>>, UserOpts, CertKeys) of
+ {new, KeyFile} ->
+ CertKeys0#{keyfile => KeyFile};
+ {_, <<>>} ->
+ case maps:get(certfile, CertKeys0, unbound) of
+ unbound -> CertKeys0;
+ CF -> CertKeys0#{keyfile => CF}
+ end;
+ {old, _} ->
+ CertKeys0
+ end;
+ {_, {KF, K0} = Key}
+ when is_binary(K0), KF =:= rsa; KF =:= dsa;
+ KF == 'RSAPrivateKey'; KF == 'DSAPrivateKey';
+ KF == 'ECPrivateKey'; KF == 'PrivateKeyInfo' ->
+ CertKeys0#{key => Key};
+ {_, #{engine := _, key_id := _, algorithm := _} = Key} ->
+ CertKeys0#{key => Key};
+ {new, Err1} ->
+ option_error(key, Err1)
+ end,
-default_value(Key, Rules) ->
- {Default, _} = maps:get(Key, Rules, {undefined, []}),
- Default.
+ CertKeys2 = case get_opt(password, unbound, UserOpts,CertKeys) of
+ {default, _} -> CertKeys1;
+ {_, Pwd} when is_binary(Pwd); is_list(Pwd) ->
+ CertKeys1#{password => fun() -> Pwd end};
+ {_, Pwd} when is_function(Pwd, 0) ->
+ CertKeys1#{password => Pwd};
+ {_, Err2} ->
+ option_error(password, Err2)
+ end,
+ CertKeys2.
+
+opt_cacerts(UserOpts, #{verify := Verify, log_level := LogLevel, versions := Versions} = Opts,
+ #{role := Role}) ->
+ {_, CaCerts} = get_opt_list(cacerts, undefined, UserOpts, Opts),
+
+ CaCertFile = case get_opt_file(cacertfile, <<>>, UserOpts, Opts) of
+ {Where1, _FileName} when CaCerts =/= undefined ->
+ warn_override(Where1, UserOpts, cacerts, [cacertfile], LogLevel),
+ <<>>;
+ {new, FileName} -> unambiguous_path(FileName);
+ {_, FileName} -> FileName
+ end,
+ option_incompatible(CaCertFile =:= <<>> andalso CaCerts =:= undefined andalso Verify =:= verify_peer,
+ [{verify, verify_peer}, {cacerts, undefined}]),
+
+ {Where2, CA} = get_opt_bool(certificate_authorities, Role =:= server, UserOpts, Opts),
+ assert_version_dep(Where2 =:= new, certificate_authorities, Versions, ['tlsv1.3']),
+
+ Opts1 = set_opt_new(new, cacertfile, <<>>, CaCertFile, Opts),
+ Opts2 = set_opt_new(Where2, certificate_authorities, Role =:= server, CA, Opts1),
+ Opts2#{cacerts => CaCerts}.
+
+opt_tickets(UserOpts, #{versions := Versions} = Opts, #{role := client}) ->
+ {_, SessionTickets} = get_opt_of(session_tickets, [disabled,manual,auto], disabled, UserOpts, Opts),
+ assert_version_dep(SessionTickets =/= disabled, session_tickets, Versions, ['tlsv1.3']),
+
+ {_, UseTicket} = get_opt_list(use_ticket, undefined, UserOpts, Opts),
+ option_error(UseTicket =:= [], use_ticket, UseTicket),
+ option_incompatible(UseTicket =/= undefined andalso SessionTickets =/= manual,
+ [{use_ticket, UseTicket}, {session_tickets, SessionTickets}]),
+
+ {_, EarlyData} = get_opt_bin(early_data, undefined, UserOpts, Opts),
+ option_incompatible(is_binary(EarlyData) andalso SessionTickets =:= disabled,
+ [early_data, {session_tickets, disabled}]),
+ option_incompatible(is_binary(EarlyData) andalso SessionTickets =:= manual andalso UseTicket =:= undefined,
+ [early_data, {session_tickets, manual}, {use_ticket, undefined}]),
+
+ assert_server_only(anti_replay, UserOpts),
+ assert_server_only(stateless_tickets_seed, UserOpts),
+ Opts#{session_tickets => SessionTickets, use_ticket => UseTicket, early_data => EarlyData};
+opt_tickets(UserOpts, #{versions := Versions} = Opts, #{role := server}) ->
+ {_, SessionTickets} =
+ get_opt_of(session_tickets,
+ [disabled, stateful, stateless, stateful_with_cert, stateless_with_cert],
+ disabled,
+ UserOpts,
+ Opts),
+ assert_version_dep(SessionTickets =/= disabled, session_tickets, Versions, ['tlsv1.3']),
+
+ {_, EarlyData} = get_opt_of(early_data, [enabled, disabled], disabled, UserOpts, Opts),
+ option_incompatible(SessionTickets =:= disabled andalso EarlyData =:= enabled,
+ [early_data, {session_tickets, disabled}]),
+
+ Stateless = lists:member(SessionTickets, [stateless, stateless_with_cert]),
+
+ AntiReplay =
+ case get_opt(anti_replay, undefined, UserOpts, Opts) of
+ {_, undefined} -> undefined;
+ {_,AR} when not Stateless ->
+ option_incompatible([{anti_replay, AR}, {session_tickets, SessionTickets}]);
+ {_,'10k'} -> {10, 5, 72985}; %% n = 10000 p = 0.030003564 (1 in 33) m = 72985 (8.91KiB) k = 5
+ {_,'100k'} -> {10, 5, 729845}; %% n = 10000 p = 0.03000428 (1 in 33) m = 729845 (89.09KiB) k = 5
+ {_, {_,_,_} = AR} -> AR;
+ {_, AR} -> option_error(anti_replay, AR)
+ end,
+ {_, STS} = get_opt_bin(stateless_tickets_seed, undefined, UserOpts, Opts),
+ option_incompatible(STS =/= undefined andalso not Stateless,
+ [stateless_tickets_seed, {session_tickets, SessionTickets}]),
-assert_role(client_only, client, _, _) ->
- ok;
-assert_role(server_only, server, _, _) ->
- ok;
-assert_role(client_only, _, _, undefined) ->
- ok;
-assert_role(server_only, _, _, undefined) ->
- ok;
-assert_role(Type, _, Key, _) ->
- throw({error, {option, Type, Key}}).
+ assert_client_only(use_ticket, UserOpts),
+ Opts#{session_tickets => SessionTickets, early_data => EarlyData,
+ anti_replay => AntiReplay, stateless_tickets_seed => STS}.
-assert_option_dependency(Option, OptionDep, Values0, AllowedValues) ->
- case is_dtls_configured(Values0) of
+opt_ocsp(UserOpts, #{versions := _Versions} = Opts, #{role := Role}) ->
+ {Stapling, SMap} =
+ case get_opt(ocsp_stapling, ?DEFAULT_OCSP_STAPLING, UserOpts, Opts) of
+ {old, Map} when is_map(Map) -> {true, Map};
+ {_, Bool} when is_boolean(Bool) -> {Bool, #{}};
+ {_, Value} -> option_error(ocsp_stapling, Value)
+ end,
+ assert_client_only(Role, Stapling, ocsp_stapling),
+ {_, Nonce} = get_opt_bool(ocsp_nonce, ?DEFAULT_OCSP_NONCE, UserOpts, SMap),
+ option_incompatible(Stapling =:= false andalso Nonce =:= false,
+ [{ocsp_nonce, false}, {ocsp_stapling, false}]),
+ {_, ORC} = get_opt_list(ocsp_responder_certs, ?DEFAULT_OCSP_RESPONDER_CERTS,
+ UserOpts, SMap),
+ CheckBinary = fun(Cert) when is_binary(Cert) -> ok;
+ (_Cert) -> option_error(ocsp_responder_certs, ORC)
+ end,
+ [CheckBinary(C) || C <- ORC],
+ option_incompatible(Stapling =:= false andalso ORC =/= [],
+ [ocsp_responder_certs, {ocsp_stapling, false}]),
+ case Stapling of
true ->
- %% TODO: Check option dependency for DTLS
- ok;
+ Opts#{ocsp_stapling =>
+ #{ocsp_nonce => Nonce,
+ ocsp_responder_certs => ORC}};
false ->
- %% special handling for version
- Values =
- case OptionDep of
- versions ->
- lists:map(fun tls_record:protocol_version/1, Values0);
- _ ->
- Values0
- end,
- Set1 = sets:from_list(Values),
- Set2 = sets:from_list(AllowedValues),
- case sets:size(sets:intersection(Set1, Set2)) > 0 of
- true ->
- ok;
- false ->
- throw({error, {options, dependency,
- {Option, {OptionDep, AllowedValues}}}})
- end
+ Opts
end.
-is_dtls_configured(Versions) ->
- Fun = fun (Version) when Version =:= {254, 253} orelse
- Version =:= {254, 255} ->
- true;
- (_) ->
- false
- end,
- lists:any(Fun, Versions).
-
-validate_option(Option, Value) ->
- validate_option(Option, Value, undefined).
-%%
-validate_option(Opt, Value, _)
- when Opt =:= alpn_advertised_protocols orelse
- Opt =:= alpn_preferred_protocols,
- is_list(Value) ->
- validate_binary_list(Opt, Value),
- Value;
-validate_option(Opt, Value, _)
- when Opt =:= alpn_advertised_protocols orelse
- Opt =:= alpn_preferred_protocols,
- Value =:= undefined ->
- undefined;
-validate_option(anti_replay, '10k', _) ->
- %% n = 10000
- %% p = 0.030003564 (1 in 33)
- %% m = 72985 (8.91KiB)
- %% k = 5
- {10, 5, 72985};
-validate_option(anti_replay, '100k', _) ->
- %% n = 100000
- %% p = 0.03000428 (1 in 33)
- %% m = 729845 (89.09KiB)
- %% k = 5
- {10, 5, 729845};
-validate_option(anti_replay, Value, _)
- when (is_tuple(Value) andalso
- tuple_size(Value) =:= 3) ->
- Value;
-validate_option(beast_mitigation, Value, _)
- when Value == one_n_minus_one orelse
- Value == zero_n orelse
- Value == disabled ->
- Value;
-%% certfile must be present in some cases otherwise it can be set
-%% to the empty string.
-validate_option(cacertfile, undefined, _) ->
- <<>>;
-validate_option(cacertfile, Value, _)
- when is_binary(Value) ->
- unambiguous_path(Value);
-validate_option(cacertfile, Value, _)
- when is_list(Value), Value =/= ""->
- binary_filename(unambiguous_path(Value));
-validate_option(cacerts, Value, _)
- when Value == undefined;
- is_list(Value) ->
- Value;
-validate_option(cb_info, {V1, V2, V3, V4} = Value, _)
- when is_atom(V1),
- is_atom(V2),
- is_atom(V3),
- is_atom(V4) ->
- Value;
-validate_option(cb_info, {V1, V2, V3, V4, V5} = Value, _)
- when is_atom(V1),
- is_atom(V2),
- is_atom(V3),
- is_atom(V4),
- is_atom(V5) ->
- Value;
-validate_option(cert, Value, _) when Value == undefined;
- is_list(Value)->
- Value;
-validate_option(cert, Value, _) when Value == undefined;
- is_binary(Value)->
- [Value];
-validate_option(certificate_authorities, Value, _) when is_boolean(Value)->
- Value;
-validate_option(certfile, undefined = Value, _) ->
- Value;
-validate_option(certfile, Value, _)
- when is_binary(Value) ->
- Value;
-validate_option(certfile, Value, _)
- when is_list(Value) ->
- binary_filename(Value);
-validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols}, _)
- when is_list(PreferredProtocols) ->
- validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
- validate_npn_ordering(Precedence),
- {Precedence, PreferredProtocols, ?NO_PROTOCOL};
-validate_option(client_preferred_next_protocols,
- {Precedence, PreferredProtocols, Default} = Value, _)
- when is_list(PreferredProtocols), is_binary(Default),
- byte_size(Default) > 0, byte_size(Default) < 256 ->
- validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
- validate_npn_ordering(Precedence),
- Value;
-validate_option(client_preferred_next_protocols, undefined, _) ->
- undefined;
-validate_option(client_renegotiation, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(cookie, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(crl_cache, {Cb, {_Handle, Options}} = Value, _)
- when is_atom(Cb) and is_list(Options) ->
- Value;
-validate_option(crl_check, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(crl_check, Value, _)
- when (Value == best_effort) or
- (Value == peer) ->
- Value;
-validate_option(customize_hostname_check, Value, _)
- when is_list(Value) ->
- Value;
-validate_option(depth, Value, _)
- when is_integer(Value),
- Value >= 0, Value =< 255->
- Value;
-validate_option(dh, Value, _)
- when Value == undefined;
- is_binary(Value) ->
- Value;
-validate_option(dhfile, undefined = Value, _) ->
- Value;
-validate_option(dhfile, Value, _)
- when is_binary(Value) ->
- Value;
-validate_option(dhfile, Value, _)
- when is_list(Value), Value =/= "" ->
- binary_filename(Value);
-validate_option(early_data, Value, server)
- when Value =:= disabled orelse
- Value =:= enabled ->
- Value;
-validate_option(early_data = Option, Value, server) ->
- throw({error,
- {options, role, {Option, {Value, {server, [disabled, enabled]}}}}});
-validate_option(early_data, Value, client)
- when is_binary(Value) ->
- Value;
-validate_option(early_data = Option, Value, client) ->
- throw({error,
- {options, type, {Option, {Value, not_binary}}}});
-validate_option(erl_dist, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(fail_if_no_peer_cert, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(fallback, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(handshake, hello = Value, _) ->
- Value;
-validate_option(handshake, full = Value, _) ->
- Value;
-validate_option(hibernate_after, undefined, _) -> %% Backwards compatibility
- infinity;
-validate_option(hibernate_after, infinity, _) ->
- infinity;
-validate_option(hibernate_after, Value, _)
- when is_integer(Value), Value >= 0 ->
- Value;
-validate_option(honor_cipher_order, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(honor_ecc_order, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(keep_secrets, Value, _) when is_boolean(Value) ->
- Value;
-validate_option(key, undefined, _) ->
- undefined;
-validate_option(key, {KeyType, Value}, _)
- when is_binary(Value),
- KeyType == rsa; %% Backwards compatibility
- KeyType == dsa; %% Backwards compatibility
- KeyType == 'RSAPrivateKey';
- KeyType == 'DSAPrivateKey';
- KeyType == 'ECPrivateKey';
- KeyType == 'PrivateKeyInfo' ->
- {KeyType, Value};
-validate_option(key, #{algorithm := _} = Value, _) ->
- Value;
-validate_option(keyfile, undefined, _) ->
- <<>>;
-validate_option(keyfile, Value, _)
- when is_binary(Value) ->
- Value;
-validate_option(keyfile, Value, _)
- when is_list(Value), Value =/= "" ->
- binary_filename(Value);
-validate_option(key_update_at, Value, _)
- when is_integer(Value) andalso
- Value > 0 ->
- Value;
-validate_option(log_level, Value, _) when
- is_atom(Value) andalso
- (Value =:= none orelse
- Value =:= all orelse
- Value =:= emergency orelse
- Value =:= alert orelse
- Value =:= critical orelse
- Value =:= error orelse
- Value =:= warning orelse
- Value =:= notice orelse
- Value =:= info orelse
- Value =:= debug) ->
- Value;
-%% RFC 6066, Section 4
-validate_option(max_fragment_length, I, _)
- when I == ?MAX_FRAGMENT_LENGTH_BYTES_1;
- I == ?MAX_FRAGMENT_LENGTH_BYTES_2;
- I == ?MAX_FRAGMENT_LENGTH_BYTES_3;
- I == ?MAX_FRAGMENT_LENGTH_BYTES_4 ->
- I;
-validate_option(max_fragment_length, undefined, _) ->
- undefined;
-validate_option(max_handshake_size, Value, _)
- when is_integer(Value) andalso
- Value =< ?MAX_UNIT24 ->
- Value;
-validate_option(middlebox_comp_mode, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(next_protocols_advertised, Value, _) when is_list(Value) ->
- validate_binary_list(next_protocols_advertised, Value),
- Value;
-validate_option(next_protocols_advertised, undefined, _) ->
- undefined;
-validate_option(ocsp_nonce, Value, _)
- when Value =:= true orelse
- Value =:= false ->
- Value;
-%% The OCSP responders' certificates can be given as a suggestion and
-%% will be used to verify the OCSP response.
-validate_option(ocsp_responder_certs, Value, _)
- when is_list(Value) ->
- [public_key:pkix_decode_cert(CertDer, plain) || CertDer <- Value,
- is_binary(CertDer)];
-validate_option(ocsp_stapling, Value, _)
- when Value =:= true orelse
- Value =:= false ->
- Value;
-validate_option(padding_check, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(partial_chain, Value, _)
- when is_function(Value) ->
- Value;
-validate_option(password, Value, _)
- when is_list(Value); is_binary(Value) ->
- Value;
-validate_option(password, Value, _)
- when is_function(Value, 0) ->
- Value;
-validate_option(certs_keys, Value, _) when is_list(Value) ->
- Value;
-validate_option(protocol, Value = tls, _) ->
- Value;
-validate_option(protocol, Value = dtls, _) ->
- Value;
-validate_option(psk_identity, undefined, _) ->
- undefined;
-validate_option(psk_identity, Identity, _)
- when is_list(Identity), Identity =/= "", length(Identity) =< 65535 ->
- binary_filename(Identity);
-validate_option(receiver_spawn_opts, Value, _)
- when is_list(Value) ->
- Value;
-validate_option(renegotiate_at, Value, _) when is_integer(Value) ->
- erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT);
-validate_option(reuse_session, undefined, _) ->
- undefined;
-validate_option(reuse_session, Value, _)
- when is_function(Value) ->
- Value;
-validate_option(reuse_session, Value, _)
- when is_binary(Value) ->
- Value;
-validate_option(reuse_session, {Id, Data} = Value, _)
- when is_binary(Id) andalso
- is_binary(Data) ->
- Value;
-validate_option(reuse_sessions, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(reuse_sessions, save = Value, _) ->
- Value;
-validate_option(secure_renegotiate, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(sender_spawn_opts, Value, _)
- when is_list(Value) ->
- Value;
-validate_option(server_name_indication, Value, _)
- when is_list(Value) ->
+opt_sni(UserOpts, #{versions := _Versions} = Opts, #{role := server}) ->
+ {_, SniHosts} = get_opt_list(sni_hosts, [], UserOpts, Opts),
+ %% Postpone option checking until all other options are checked FIXME
+ Check = fun({[_|_], SO}) when is_list(SO) ->
+ case proplists:get_value(sni_hosts, SO, undefined) of
+ undefined -> ok;
+ Recursive -> option_error(sni_hosts, Recursive)
+ end;
+ (HostOpts) -> option_error(sni_hosts, HostOpts)
+ end,
+ [Check(E) || E <- SniHosts],
+
+ {Where, SniFun0} = get_opt_fun(sni_fun, 1, undefined, UserOpts, Opts),
+
+ option_incompatible(is_function(SniFun0) andalso SniHosts =/= [] andalso Where =:= new,
+ [sni_fun, sni_hosts]),
+ assert_client_only(server_name_indication, UserOpts),
+
+ SniFun = case SniFun0 =:= undefined of
+ true -> fun(Host) -> proplists:get_value(Host, SniHosts) end;
+ false -> SniFun0
+ end,
+
+ Opts#{sni_fun => SniFun};
+opt_sni(UserOpts, #{versions := _Versions} = Opts, #{role := client} = Env) ->
%% RFC 6066, Section 3: Currently, the only server names supported are
%% DNS hostnames
%% case inet_parse:domain(Value) of
@@ -2351,195 +2031,572 @@ validate_option(server_name_indication, Value, _)
%%
%% But the definition seems very diffuse, so let all strings through
%% and leave it up to public_key to decide...
- Value;
-validate_option(server_name_indication, undefined, _) ->
- undefined;
-validate_option(server_name_indication, disable, _) ->
- disable;
-validate_option(session_tickets, Value, server)
- when Value =:= disabled orelse
- Value =:= stateful orelse
- Value =:= stateless ->
- Value;
-validate_option(session_tickets, Value, server) ->
- throw({error,
- {options, role,
- {session_tickets,
- {Value, {server, [disabled, stateful, stateless]}}}}});
-validate_option(session_tickets, Value, client)
- when Value =:= disabled orelse
- Value =:= manual orelse
- Value =:= auto ->
- Value;
-validate_option(session_tickets, Value, client) ->
- throw({error,
- {options, role,
- {session_tickets,
- {Value, {client, [disabled, manual, auto]}}}}});
-validate_option(sni_fun, undefined, _) ->
- undefined;
-validate_option(sni_fun, Fun, _)
- when is_function(Fun) ->
- Fun;
-validate_option(sni_hosts, [], _) ->
- [];
-validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail], _)
- when is_list(Hostname) ->
- RecursiveSNIOptions = proplists:get_value(sni_hosts, SSLOptions, undefined),
- case RecursiveSNIOptions of
- undefined ->
- [{Hostname, validate_options(SSLOptions)} |
- validate_option(sni_hosts, Tail)];
- _ ->
- throw({error, {options, {sni_hosts, RecursiveSNIOptions}}})
- end;
-validate_option(srp_identity, undefined, _) ->
- undefined;
-validate_option(srp_identity, {Username, Password}, _)
- when is_list(Username),
- is_list(Password), Username =/= "",
- length(Username) =< 255 ->
- {unicode:characters_to_binary(Username),
- unicode:characters_to_binary(Password)};
-validate_option(user_lookup_fun, undefined, _) ->
- undefined;
-validate_option(user_lookup_fun, {Fun, _} = Value, _)
- when is_function(Fun, 3) ->
- Value;
-validate_option(use_ticket, Value, _)
- when is_list(Value) ->
- Value;
-validate_option(verify, Value, _)
- when Value == verify_none; Value == verify_peer ->
- Value;
-validate_option(verify_fun, undefined, _) ->
- undefined;
-%% Backwards compatibility
-validate_option(verify_fun, Fun, _) when is_function(Fun) ->
- {fun(_,{bad_cert, _} = Reason, OldFun) ->
- case OldFun([Reason]) of
- true ->
- {valid, OldFun};
- false ->
- {fail, Reason}
- end;
- (_,{extension, _}, UserState) ->
- {unknown, UserState};
- (_, valid, UserState) ->
- {valid, UserState};
- (_, valid_peer, UserState) ->
- {valid, UserState}
- end, Fun};
-validate_option(verify_fun, {Fun, _} = Value, _) when is_function(Fun) ->
- Value;
-validate_option(versions, Versions, _) ->
- validate_versions(Versions, Versions);
-validate_option(Opt, undefined = Value, _) ->
- AllOpts = maps:keys(?RULES),
- case lists:member(Opt, AllOpts) of
- true ->
- Value;
- false ->
- throw({error, {options, {Opt, Value}}})
+ SNI = case get_opt(server_name_indication, unbound, UserOpts, Opts) of
+ {_, unbound} -> server_name_indication_default(maps:get(host, Env, undefined));
+ {_, [_|_] = SN} -> SN;
+ {_, disable} -> disable;
+ {_, SN} -> option_error(server_name_indication, SN)
+ end,
+ assert_server_only(sni_fun, UserOpts),
+ assert_server_only(sni_hosts, UserOpts),
+ Opts#{server_name_indication => SNI}.
+
+server_name_indication_default(Host) when is_list(Host) ->
+ %% SNI should not contain a trailing dot that a hostname may
+ string:strip(Host, right, $.);
+server_name_indication_default(_) ->
+ undefined.
+
+opt_signature_algs(UserOpts, #{versions := Versions} = Opts, _Env) ->
+ [TlsVersion|_] = TlsVsns = [tls_version(V) || V <- Versions],
+ SA = case get_opt_list(signature_algs, undefined, UserOpts, Opts) of
+ {default, undefined} when ?TLS_GTE(TlsVersion, ?TLS_1_2) ->
+ DefAlgs = tls_v1:default_signature_algs(TlsVsns),
+ handle_hashsigns_option(DefAlgs, TlsVersion);
+ {new, Algs} ->
+ assert_version_dep(signature_algs, Versions, ['tlsv1.2', 'tlsv1.3']),
+ SA0 = handle_hashsigns_option(Algs, TlsVersion),
+ option_error(SA0 =:= [], no_supported_algorithms, {signature_algs, Algs}),
+ SA0;
+ {_, Algs} ->
+ Algs
+ end,
+ SAC = case get_opt_list(signature_algs_cert, undefined, UserOpts, Opts) of
+ {new, Schemes} ->
+ %% Do not send by default
+ assert_version_dep(signature_algs_cert, Versions, ['tlsv1.2', 'tlsv1.3']),
+ SAC0 = handle_signature_algorithms_option(Schemes, TlsVersion),
+ option_error(SAC0 =:= [], no_supported_signature_schemes, {signature_algs_cert, Schemes}),
+ SAC0;
+ {_, Schemes} ->
+ Schemes
+ end,
+ Opts#{signature_algs => SA, signature_algs_cert => SAC}.
+
+opt_alpn(UserOpts, #{versions := Versions} = Opts, #{role := server}) ->
+ {_, APP} = get_opt_list(alpn_preferred_protocols, undefined, UserOpts, Opts),
+ validate_protocols(is_list(APP), alpn_preferred_protocols, APP),
+
+ {Where, NPA} = get_opt_list(next_protocols_advertised, undefined, UserOpts, Opts),
+ validate_protocols(is_list(NPA), next_protocols_advertised, NPA),
+ assert_version_dep(is_list(NPA), next_protocols_advertised, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+
+ assert_client_only(alpn_advertised_protocols, UserOpts),
+ assert_client_only(client_preferred_next_protocols, UserOpts),
+
+ Opts1 = set_opt_new(Where, next_protocols_advertised, undefined, NPA, Opts),
+ Opts1#{alpn_preferred_protocols => APP};
+opt_alpn(UserOpts, #{versions := Versions} = Opts, #{role := client}) ->
+ {_, AAP} = get_opt_list(alpn_advertised_protocols, undefined, UserOpts, Opts),
+ validate_protocols(is_list(AAP), alpn_advertised_protocols, AAP),
+
+ {Where, NPS} = case get_opt(client_preferred_next_protocols, undefined, UserOpts, Opts) of
+ {new, CPNP} ->
+ assert_version_dep(client_preferred_next_protocols,
+ Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ {new, make_next_protocol_selector(CPNP)};
+ CPNP ->
+ CPNP
+ end,
+
+ validate_protocols(is_list(NPS), client_preferred_next_protocols, NPS),
+
+ assert_server_only(alpn_preferred_protocols, UserOpts),
+ assert_server_only(next_protocols_advertised, UserOpts),
+
+ Opts1 = set_opt_new(Where, next_protocol_selector, undefined, NPS, Opts),
+ Opts1#{alpn_advertised_protocols => AAP}.
+
+validate_protocols(false, _Opt, _List) -> ok;
+validate_protocols(true, Opt, List) ->
+ Check = fun(Bin) ->
+ IsOK = is_binary(Bin) andalso byte_size(Bin) > 0 andalso byte_size(Bin) < 256,
+ option_error(not IsOK, Opt, {invalid_protocol, Bin})
+ end,
+ lists:foreach(Check, List).
+
+opt_mitigation(UserOpts, #{versions := Versions} = Opts, _Env) ->
+ DefBeast = case ?TLS_GT(lists:last(Versions), ?TLS_1_0) of
+ true -> disabled;
+ false -> one_n_minus_one
+ end,
+ {Where1, BM} = get_opt_of(beast_mitigation, [disabled, one_n_minus_one, zero_n], DefBeast, UserOpts, Opts),
+ assert_version_dep(Where1 =:= new, beast_mitigation, Versions, ['tlsv1']),
+
+ {Where2, PC} = get_opt_bool(padding_check, true, UserOpts, Opts),
+ assert_version_dep(Where2 =:= new, padding_check, Versions, ['tlsv1']),
+
+ %% Use 'new' we need to check for non default 'one_n_minus_one'
+ Opts1 = set_opt_new(new, beast_mitigation, disabled, BM, Opts),
+ set_opt_new(Where2, padding_check, true, PC, Opts1).
+
+opt_server(UserOpts, #{versions := Versions, log_level := LogLevel} = Opts, #{role := server}) ->
+ {_, ECC} = get_opt_bool(honor_ecc_order, false, UserOpts, Opts),
+
+ {_, Cipher} = get_opt_bool(honor_cipher_order, false, UserOpts, Opts),
+
+ {Where1, Cookie} = get_opt_bool(cookie, true, UserOpts, Opts),
+ assert_version_dep(Where1 =:= new, cookie, Versions, ['tlsv1.3']),
+
+ {Where2, ReNeg} = get_opt_bool(client_renegotiation, true, UserOpts, Opts),
+ assert_version_dep(Where2 =:= new, client_renegotiation, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+
+ Opts1 = case get_opt(dh, undefined, UserOpts, Opts) of
+ {Where, DH} when is_binary(DH) ->
+ warn_override(Where, UserOpts, dh, [dhfile], LogLevel),
+ Opts#{dh => DH};
+ {new, DH} ->
+ option_error(dh, DH);
+ {_, undefined} ->
+ case get_opt_file(dhfile, unbound, UserOpts, Opts) of
+ {default, unbound} -> Opts;
+ {_, DHFile} -> Opts#{dhfile => DHFile}
+ end
+ end,
+
+ Opts1#{honor_ecc_order => ECC, honor_cipher_order => Cipher,
+ cookie => Cookie, client_renegotiation => ReNeg};
+opt_server(UserOpts, Opts, #{role := client}) ->
+ assert_server_only(honor_ecc_order, UserOpts),
+ assert_server_only(honor_cipher_order, UserOpts),
+ assert_server_only(cookie, UserOpts),
+ assert_server_only(client_renegotiation, UserOpts),
+ assert_server_only(dh, UserOpts),
+ assert_server_only(dhfile, UserOpts),
+ Opts.
+
+opt_client(UserOpts, #{versions := Versions} = Opts, #{role := client}) ->
+ {Where, FB} = get_opt_bool(fallback, false, UserOpts, Opts),
+ assert_version_dep(Where =:= new, fallback, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+
+ {_, CHC} = get_opt_list(customize_hostname_check, [], UserOpts, Opts),
+
+ ValidMFL = [undefined, ?MAX_FRAGMENT_LENGTH_BYTES_1, ?MAX_FRAGMENT_LENGTH_BYTES_2, %% RFC 6066, Section 4
+ ?MAX_FRAGMENT_LENGTH_BYTES_3, ?MAX_FRAGMENT_LENGTH_BYTES_4],
+ {_, MFL} = get_opt_of(max_fragment_length, ValidMFL, undefined, UserOpts, Opts),
+
+ Opts#{fallback => FB, customize_hostname_check => CHC, max_fragment_length => MFL};
+opt_client(UserOpts, Opts, #{role := server}) ->
+ assert_client_only(fallback, UserOpts),
+ assert_client_only(customize_hostname_check, UserOpts),
+ assert_client_only(max_fragment_length, UserOpts),
+ Opts#{customize_hostname_check => []}.
+
+opt_renegotiate(UserOpts, #{versions := Versions} = Opts, _Env) ->
+ {Where1, KUA} = get_opt_pos_int(key_update_at, ?KEY_USAGE_LIMIT_AES_GCM, UserOpts, Opts),
+ assert_version_dep(Where1 =:= new, key_update_at, Versions, ['tlsv1.3']),
+
+ %% Undocumented, old ?
+ {_, RA0} = get_opt_pos_int(renegotiate_at, ?DEFAULT_RENEGOTIATE_AT, UserOpts, Opts),
+ RA = min(RA0, ?DEFAULT_RENEGOTIATE_AT), %% Override users choice without notifying ??
+
+ {Where3, SR} = get_opt_bool(secure_renegotiate, true, UserOpts, Opts),
+ assert_version_dep(Where3 =:= new, secure_renegotiate, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+
+ Opts#{secure_renegotiate => SR, key_update_at => KUA, renegotiate_at => RA}.
+
+opt_reuse_sessions(UserOpts, #{versions := Versions} = Opts, #{role := client}) ->
+ {Where1, RUSS} = get_opt_of(reuse_sessions, [true, false, save], true, UserOpts, Opts),
+
+ {Where2, RS} = RST = get_opt(reuse_session, undefined, UserOpts, Opts),
+ case RST of
+ {new, Bin} when is_binary(Bin) -> ok;
+ {new, {B1,B2}} when is_binary(B1), is_binary(B2) -> ok;
+ {new, Bad} -> option_error(reuse_session, Bad);
+ {_, _} -> ok
+ end,
+
+ assert_version_dep(Where1 =:= new, reuse_sessions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ assert_version_dep(Where2 =:= new, reuse_session, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ Opts#{reuse_sessions => RUSS, reuse_session => RS};
+opt_reuse_sessions(UserOpts, #{versions := Versions} = Opts, #{role := server}) ->
+ {Where1, RUSS} = get_opt_bool(reuse_sessions, true, UserOpts, Opts),
+
+ DefRS = fun(_, _, _, _) -> true end,
+ {Where2, RS} = get_opt_fun(reuse_session, 4, DefRS, UserOpts, Opts),
+
+ assert_version_dep(Where1 =:= new, reuse_sessions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ assert_version_dep(Where2 =:= new, reuse_session, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ Opts#{reuse_sessions => RUSS, reuse_session => RS}.
+
+opt_identity(UserOpts, #{versions := Versions} = Opts, _Env) ->
+ PSK = case get_opt_list(psk_identity, undefined, UserOpts, Opts) of
+ {new, PSK0} ->
+ PSK1 = unicode:characters_to_binary(PSK0),
+ PSKSize = byte_size(PSK1),
+ assert_version_dep(psk_identity, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ option_error(not (0 < PSKSize andalso PSKSize < 65536),
+ psk_identity, {psk_identity, PSK0}),
+ PSK1;
+ {_, PSK0} ->
+ PSK0
+ end,
+
+ SRP = case get_opt(srp_identity, undefined, UserOpts, Opts) of
+ {new, {S1, S2}} when is_list(S1), is_list(S2) ->
+ User = unicode:characters_to_binary(S1),
+ UserSize = byte_size(User),
+ assert_version_dep(srp_identity, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ option_error(not (0 < UserSize andalso UserSize < 65536),
+ srp_identity, {srp_identity, PSK0}),
+ {User, unicode:characters_to_binary(S2)};
+ {new, Err} ->
+ option_error(srp_identity, Err);
+ {_, SRP0} ->
+ SRP0
+ end,
+
+ ULF = case get_opt(user_lookup_fun, undefined, UserOpts, Opts) of
+ {new, {Fun, _} = ULF0} when is_function(Fun, 3) ->
+ assert_version_dep(user_lookup_fun, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ ULF0;
+ {new, ULF0} ->
+ option_error(user_lookup_fun, ULF0);
+ {_, ULF0} ->
+ ULF0
+ end,
+
+ Opts#{psk_identity => PSK, srp_identity => SRP, user_lookup_fun => ULF}.
+
+opt_supported_groups(UserOpts, #{versions := Versions} = Opts, _Env) ->
+ [TlsVersion|_] = TlsVsns = [tls_version(V) || V <- Versions],
+ SG = case get_opt_list(supported_groups, undefined, UserOpts, Opts) of
+ {default, undefined} ->
+ handle_supported_groups_option(groups(default), TlsVersion);
+ {new, SG0} ->
+ assert_version_dep(supported_groups, TlsVsns, ['tlsv1.3']),
+ handle_supported_groups_option(SG0, TlsVersion);
+ {old, SG0} ->
+ SG0
+ end,
+
+ CPHS = case get_opt_list(ciphers, [], UserOpts, Opts) of
+ {old, CPS0} -> CPS0;
+ {_, CPS0} -> handle_cipher_option(CPS0, Versions)
+ end,
+
+ ECCS = case get_opt_list(eccs, undefined, UserOpts, Opts) of
+ {old, ECCS0} -> ECCS0;
+ {default, _} -> handle_eccs_option(tls_v1:ecc_curves(all), TlsVersion);
+ {new, ECCS0} -> handle_eccs_option(ECCS0, TlsVersion)
+ end,
+
+ Opts#{ciphers => CPHS, eccs => ECCS, supported_groups => SG}.
+
+opt_crl(UserOpts, Opts, _Env) ->
+ {_, Check} = get_opt_of(crl_check, [best_effort, peer, true, false], false, UserOpts, Opts),
+ Cache = case get_opt(crl_cache, {ssl_crl_cache, {internal, []}}, UserOpts, Opts) of
+ {_, {Cb, {_Handle, Options}} = Value} when is_atom(Cb), is_list(Options) ->
+ Value;
+ {_, Err} ->
+ option_error(crl_cache, Err)
+ end,
+ Opts#{crl_check => Check, crl_cache => Cache}.
+
+opt_handshake(UserOpts, Opts, _Env) ->
+ {_, HS} = get_opt_of(handshake, [hello, full], full, UserOpts, Opts),
+
+ {_, MHSS} = get_opt_int(max_handshake_size, 1, ?MAX_UNIT24, ?DEFAULT_MAX_HANDSHAKE_SIZE,
+ UserOpts, Opts),
+
+ Opts#{handshake => HS, max_handshake_size => MHSS}.
+
+opt_use_srtp(UserOpts, #{protocol := Protocol} = Opts, _Env) ->
+ UseSRTP = case get_opt_map(use_srtp, undefined, UserOpts, Opts) of
+ {old, UseSRTP0} ->
+ UseSRTP0;
+ {default, undefined} ->
+ undefined;
+ {new, UseSRTP1} ->
+ assert_protocol_dep(use_srtp, Protocol, [dtls]),
+ validate_use_srtp(UseSRTP1)
+ end,
+ case UseSRTP of
+ #{} -> Opts#{use_srtp => UseSRTP};
+ _ -> Opts
+ end.
+
+validate_use_srtp(#{protection_profiles := [_|_] = PPs} = UseSRTP) ->
+ case maps:keys(UseSRTP) -- [protection_profiles, mki] of
+ [] -> ok;
+ Extra -> option_error(use_srtp, {unknown_parameters, Extra})
+ end,
+ IsValidProfile = fun(<<_, _>>) -> true; (_) -> false end,
+ case lists:all(IsValidProfile, PPs) of
+ true -> ok;
+ false -> option_error(use_srtp, {invalid_protection_profiles, PPs})
+ end,
+ case UseSRTP of
+ #{mki := MKI} when not is_binary(MKI) ->
+ option_error(use_srtp, {invalid_mki, MKI});
+ #{mki := _} ->
+ UseSRTP;
+ #{} ->
+ UseSRTP#{mki => <<>>}
end;
-validate_option(Opt, Value, _) ->
- throw({error, {options, {Opt, Value}}}).
+
+validate_use_srtp(#{} = UseSRTP) ->
+ option_error(use_srtp, {no_protection_profiles, UseSRTP}).
+
+
+opt_process(UserOpts, Opts0, _Env) ->
+ Opts1 = set_opt_list(receiver_spawn_opts, [], UserOpts, Opts0),
+ Opts2 = set_opt_list(sender_spawn_opts, [], UserOpts, Opts1),
+ %% {_, SSO} = get_opt_list(sender_spawn_opts, [], UserOpts, Opts),
+ %% Opts = Opts1#{receiver_spawn_opts => RSO, sender_spawn_opts => SSO},
+ set_opt_int(hibernate_after, 0, infinity, infinity, UserOpts, Opts2).
+
+%%%%
+
+get_opt(Opt, Default, UserOpts, Opts) ->
+ case maps:get(Opt, UserOpts, unbound) of
+ unbound ->
+ case maps:get(maybe_map_key_internal(Opt), Opts, unbound) of
+ unbound -> %% Uses default value
+ {default, Default};
+ Value -> %% Uses already set value (merge)
+ {old, Value}
+ end;
+ Value -> %% Uses new user option
+ {new, Value}
+ end.
+
+get_opt_of(Opt, Valid, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {new, Value} = Res ->
+ case lists:member(Value, Valid) of
+ true -> Res;
+ false -> option_error(Opt, Value)
+ end;
+ Res ->
+ Res
+ end.
+
+get_opt_bool(Opt, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {_, Value} = Res when is_boolean(Value) -> Res;
+ {_, Value} -> option_error(Opt, Value)
+ end.
+
+get_opt_pos_int(Opt, Default, UserOpts, Opts) ->
+ get_opt_int(Opt, 1, infinity, Default, UserOpts, Opts).
+
+get_opt_int(Opt, Min, Max, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {_, Value} = Res when is_integer(Value), Min =< Value, Value =< Max ->
+ Res;
+ {_, Value} = Res when Value =:= infinity, Max =:= infinity ->
+ Res;
+ {_, Value} ->
+ option_error(Opt, Value)
+ end.
+
+get_opt_fun(Opt, Arity, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {_, Fun} = Res when is_function(Fun, Arity) -> Res;
+ {new, Err} -> option_error(Opt, Err);
+ Res -> Res
+ end.
+
+get_opt_list(Opt, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {new, Err} when not is_list(Err) -> option_error(Opt, Err);
+ Res -> Res
+ end.
+
+get_opt_bin(Opt, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {new, Err} when not is_binary(Err) -> option_error(Opt, Err);
+ Res -> Res
+ end.
+
+get_opt_file(Opt, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {new, File} -> {new, validate_filename(File, Opt)};
+ Res -> Res
+ end.
+
+set_opt_bool(Opt, Default, UserOpts, Opts) ->
+ case maps:get(Opt, UserOpts, Default) of
+ Default -> Opts;
+ Value when is_boolean(Value) -> Opts#{Opt => Value};
+ Value -> option_error(Opt, Value)
+ end.
+
+get_opt_map(Opt, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {new, Err} when not is_map(Err) -> option_error(Opt, Err);
+ Res -> Res
+ end.
+
+set_opt_int(Opt, Min, Max, Default, UserOpts, Opts) ->
+ case maps:get(Opt, UserOpts, Default) of
+ Default ->
+ Opts;
+ Value when is_integer(Value), Min =< Value, Value =< Max ->
+ Opts#{Opt => Value};
+ Value when Value =:= infinity, Max =:= infinity ->
+ Opts#{Opt => Value};
+ Value ->
+ option_error(Opt, Value)
+ end.
+
+set_opt_list(Opt, Default, UserOpts, Opts) ->
+ case maps:get(Opt, UserOpts, []) of
+ Default ->
+ Opts;
+ List when is_list(List) ->
+ Opts#{Opt => List};
+ Value ->
+ option_error(Opt, Value)
+ end.
+
+set_opt_new(new, Opt, Default, Value, Opts)
+ when Default =/= Value ->
+ Opts#{Opt => Value};
+set_opt_new(_, _, _, _, Opts) ->
+ Opts.
+
+%%%%
+
+default_cb_info(tls) ->
+ {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive};
+default_cb_info(dtls) ->
+ {gen_udp, udp, udp_closed, udp_error, udp_passive}.
handle_cb_info({V1, V2, V3, V4}) ->
{V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "_passive")};
+handle_cb_info(CbInfo) when tuple_size(CbInfo) =:= 5 ->
+ CbInfo;
handle_cb_info(CbInfo) ->
- CbInfo.
+ option_error(cb_info, CbInfo).
-handle_hashsigns_option(Value, Version) when is_list(Value)
- andalso Version >= {3, 4} ->
- case tls_v1:signature_schemes(Version, Value) of
- [] ->
- throw({error, {options,
- no_supported_signature_schemes,
- {signature_algs, Value}}});
- _ ->
- Value
- end;
-handle_hashsigns_option(Value, Version) when is_list(Value)
- andalso Version =:= {3, 3} ->
- case tls_v1:signature_algs(Version, Value) of
- [] ->
- throw({error, {options, no_supported_algorithms, {signature_algs, Value}}});
- _ ->
- Value
- end;
-handle_hashsigns_option(_, Version) when Version =:= {3, 3} ->
- handle_hashsigns_option(tls_v1:default_signature_algs([Version]), Version);
-handle_hashsigns_option(_, _Version) ->
- undefined.
+handle_option_cb_info(Options, Protocol) ->
+ CbInfo = proplists:get_value(cb_info, Options, default_cb_info(Protocol)),
+ handle_cb_info(CbInfo).
-handle_signature_algorithms_option(Value, Version) when is_list(Value)
- andalso Version >= {3, 3} ->
- case tls_v1:signature_schemes(Version, Value) of
- [] ->
- throw({error, {options,
- no_supported_signature_schemes,
- {signature_algs_cert, Value}}});
- _ ->
- Value
- end;
-handle_signature_algorithms_option(_, _Version) ->
- undefined.
+maybe_map_key_internal(client_preferred_next_protocols) ->
+ next_protocol_selector;
+maybe_map_key_internal(K) ->
+ K.
-validate_options([]) ->
- [];
-validate_options([{Opt, Value} | Tail]) ->
- [{Opt, validate_option(Opt, Value)} | validate_options(Tail)].
+split_options(Opts0, AllOptions) ->
+ Opts1 = proplists:expand([{binary, [{mode, binary}]},
+ {list, [{mode, list}]}], Opts0),
+ Opts2 = handle_option_format(Opts1, []),
+ %% Remove deprecated ssl_imp option
+ Opts = proplists:delete(ssl_imp, Opts2),
-validate_npn_ordering(client) ->
- ok;
-validate_npn_ordering(server) ->
+ DeleteUserOpts = fun(Key, PropList) -> proplists:delete(Key, PropList) end,
+ AllOpts = [cb_info, client_preferred_next_protocols] ++ AllOptions,
+ SockOpts = lists:foldl(DeleteUserOpts, Opts, AllOpts),
+ {Opts -- SockOpts, SockOpts}.
+
+assert_server_only(Option, Opts) ->
+ Value = maps:get(Option, Opts, undefined),
+ role_error(Value =/= undefined, server_only, Option).
+assert_client_only(Option, Opts) ->
+ Value = maps:get(Option, Opts, undefined),
+ role_error(Value =/= undefined, client_only, Option).
+
+assert_server_only(client, Bool, Option) ->
+ role_error(Bool, server_only, Option);
+assert_server_only(_, _, _) ->
+ ok.
+assert_client_only(server, Bool, Option) ->
+ role_error(Bool, client_only, Option);
+assert_client_only(_, _, _) ->
+ ok.
+
+role_error(false, _ErrorDesc, _Option) ->
ok;
-validate_npn_ordering(Value) ->
- throw({error, {options, {client_preferred_next_protocols, {invalid_precedence, Value}}}}).
-
-validate_binary_list(Opt, List) ->
- lists:foreach(
- fun(Bin) when is_binary(Bin),
- byte_size(Bin) > 0,
- byte_size(Bin) < 256 ->
- ok;
- (Bin) ->
- throw({error, {options, {Opt, {invalid_protocol, Bin}}}})
- end, List).
-validate_versions([], Versions) ->
- Versions;
-validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3';
- Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == tlsv1 ->
- case tls_record:sufficient_crypto_support(Version) of
- true ->
- tls_validate_versions(Rest, Versions);
- false ->
- throw({error, {options, {insufficient_crypto_support, {Version, {versions, Versions}}}}})
- end;
-validate_versions([Version | Rest], Versions) when Version == 'dtlsv1';
- Version == 'dtlsv1.2'->
- DTLSVer = dtls_record:protocol_version(Version),
- case tls_record:sufficient_crypto_support(dtls_v1:corresponding_tls_version(DTLSVer)) of
- true ->
- dtls_validate_versions(Rest, Versions);
+role_error(true, ErrorDesc, Option)
+ when ErrorDesc =:= client_only; ErrorDesc =:= server_only ->
+ throw_error({option, ErrorDesc, Option}).
+
+option_incompatible(false, _Options) -> ok;
+option_incompatible(true, Options) -> option_incompatible(Options).
+
+-spec option_incompatible(_) -> no_return().
+option_incompatible(Options) ->
+ throw_error({options, incompatible, Options}).
+
+option_error(false, _, _What) -> true;
+option_error(true, Tag, What) -> option_error(Tag,What).
+
+-spec option_error(_,_) -> no_return().
+option_error(Tag, What) ->
+ throw_error({options, {Tag, What}}).
+
+-spec throw_error(_) -> no_return().
+throw_error(Err) ->
+ throw({error, Err}).
+
+assert_protocol_dep(Option, Protocol, AllowedProtos) ->
+ case lists:member(Protocol, AllowedProtos) of
+ true -> ok;
+ false -> option_incompatible([Option, {protocol, Protocol}])
+ end.
+
+assert_version_dep(Option, Vsns, AllowedVsn) ->
+ assert_version_dep(true, Option, Vsns, AllowedVsn).
+
+assert_version_dep(false, _, _, _) -> true;
+assert_version_dep(true, Option, SSLVsns, AllowedVsn) ->
+ case is_dtls_configured(SSLVsns) of
+ true -> %% TODO: Check option dependency for DTLS
+ true;
false ->
- throw({error, {options, {insufficient_crypto_support, {Version, {versions, Versions}}}}})
- end;
-validate_versions([Version| _], Versions) ->
- throw({error, {options, {Version, {versions, Versions}}}}).
-
-tls_validate_versions([], Versions) ->
- tls_validate_version_gap(Versions);
-tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3';
- Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == tlsv1 ->
- tls_validate_versions(Rest, Versions);
-tls_validate_versions([Version| _], Versions) ->
- throw({error, {options, {Version, {versions, Versions}}}}).
+ APIVsns = lists:map(fun tls_record:protocol_version/1, SSLVsns),
+ Set1 = sets:from_list(APIVsns),
+ Set2 = sets:from_list(AllowedVsn),
+ case sets:size(sets:intersection(Set1, Set2)) > 0 of
+ true -> ok;
+ false -> option_incompatible([Option, {versions, APIVsns}])
+ end
+ end.
+
+warn_override(new, UserOpts, NewOpt, OldOpts, LogLevel) ->
+ Check = fun(Key) -> maps:is_key(Key,UserOpts) end,
+ case lists:filter(Check, OldOpts) of
+ [] -> ok;
+ Ignored ->
+ Desc = lists:flatten(io_lib:format("Options ~w are ignored", [Ignored])),
+ Reas = lists:flatten(io_lib:format("Option ~w is set", [NewOpt])),
+ ssl_logger:log(notice, LogLevel, #{description => Desc, reason => Reas}, ?LOCATION)
+ end;
+warn_override(_, _UserOpts, _NewOpt, _OldOpts, _LogLevel) ->
+ ok.
+
+is_dtls_configured(Versions) ->
+ lists:any(fun (Ver) -> ?DTLS_1_X(Ver) end, Versions).
+
+handle_hashsigns_option(Value, Version) ->
+ try
+ if ?TLS_GTE(Version, ?TLS_1_3) ->
+ tls_v1:signature_schemes(Version, Value);
+ (Version =:= ?TLS_1_2) ->
+ tls_v1:signature_algs(Version, Value);
+ true ->
+ undefined
+ end
+ catch error:function_clause ->
+ option_error(signature_algs, Value)
+ end.
+
+handle_signature_algorithms_option(Value, Version) ->
+ try tls_v1:signature_schemes(Version, Value)
+ catch error:function_clause ->
+ option_error(signature_algs_cert, Value)
+ end.
+
+validate_filename(FN, _Option) when is_binary(FN), FN =/= <<>> ->
+ FN;
+validate_filename([_|_] = FN, _Option) ->
+ Enc = file:native_name_encoding(),
+ unicode:characters_to_binary(FN, unicode, Enc);
+validate_filename(FN, Option) ->
+ option_error(Option, FN).
%% Do not allow configuration of TLS 1.3 with a gap where TLS 1.2 is not supported
%% as that configuration can trigger the built in version downgrade protection
@@ -2556,25 +2613,7 @@ tls_validate_version_gap(Versions) ->
_ ->
Versions
end.
-dtls_validate_versions([], Versions) ->
- Versions;
-dtls_validate_versions([Version | Rest], Versions) when Version == 'dtlsv1';
- Version == 'dtlsv1.2'->
- dtls_validate_versions(Rest, Versions);
-dtls_validate_versions([Ver| _], Versions) ->
- throw({error, {options, {Ver, {versions, Versions}}}}).
-
-%% The option cacerts overrides cacertfile
-ca_cert_default(_,_, [_|_]) ->
- undefined;
-ca_cert_default(verify_none, _, _) ->
- undefined;
-ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) ->
- undefined;
-%% Server that wants to verify_peer and has no verify_fun must have
-%% some trusted certs.
-ca_cert_default(verify_peer, undefined, _) ->
- "".
+
emulated_options(undefined, undefined, Protocol, Opts) ->
case Protocol of
tls ->
@@ -2594,16 +2633,16 @@ handle_cipher_option(Value, Versions) when is_list(Value) ->
Suites
catch
exit:_ ->
- throw({error, {options, {ciphers, Value}}});
+ option_error(ciphers, Value);
error:_->
- throw({error, {options, {ciphers, Value}}})
+ option_error(ciphers, Value)
end.
-binary_cipher_suites([{3,4} = Version], []) ->
+binary_cipher_suites([?TLS_1_3], []) ->
%% Defaults to all supported suites that does
%% not require explicit configuration TLS-1.3
%% only mode.
- default_binary_suites(exclusive, Version);
+ default_binary_suites(exclusive, ?TLS_1_3);
binary_cipher_suites([Version| _], []) ->
%% Defaults to all supported suites that does
%% not require explicit configuration
@@ -2633,15 +2672,15 @@ binary_cipher_suites(Versions, Ciphers0) ->
Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- string:lexemes(Ciphers0, ":")],
binary_cipher_suites(Versions, Ciphers).
-default_binary_suites(exclusive, {_, Minor}) ->
- ssl_cipher:filter_suites(tls_v1:exclusive_suites(Minor));
+default_binary_suites(exclusive, Version) ->
+ ssl_cipher:filter_suites(tls_v1:exclusive_suites(Version));
default_binary_suites(default, Version) ->
ssl_cipher:filter_suites(ssl_cipher:suites(Version)).
-all_suites([{3, 4 = Minor}]) ->
- tls_v1:exclusive_suites(Minor);
-all_suites([{3, 4} = Version0, Version1 |_]) ->
- all_suites([Version0]) ++
+all_suites([?TLS_1_3]) ->
+ tls_v1:exclusive_suites(?TLS_1_3);
+all_suites([?TLS_1_3, Version1 |_]) ->
+ all_suites([?TLS_1_3]) ++
ssl_cipher:all_suites(Version1) ++
ssl_cipher:anonymous_suites(Version1);
all_suites([Version|_]) ->
@@ -2669,22 +2708,26 @@ tuple_to_map_mac(chacha20_poly1305, _) ->
tuple_to_map_mac(_, MAC) ->
MAC.
-handle_eccs_option(Value, Version) when is_list(Value) ->
- {_Major, Minor} = tls_version(Version),
- try tls_v1:ecc_curves(Minor, Value) of
- Curves -> #elliptic_curves{elliptic_curve_list = Curves}
+%% TODO: remove Version
+handle_eccs_option(Value, _Version0) when is_list(Value) ->
+ try tls_v1:ecc_curves(Value) of
+ Curves ->
+ option_error(Curves =:= [], eccs, none_valid),
+ #elliptic_curves{elliptic_curve_list = Curves}
catch
- exit:_ -> throw({error, {options, {eccs, Value}}});
- error:_ -> throw({error, {options, {eccs, Value}}})
+ exit:_ -> option_error(eccs, Value);
+ error:_ -> option_error(eccs, Value)
end.
-handle_supported_groups_option(Value, Version) when is_list(Value) ->
- {_Major, Minor} = tls_version(Version),
- try tls_v1:groups(Minor, Value) of
- Groups -> #supported_groups{supported_groups = Groups}
+%% TODO: remove Version
+handle_supported_groups_option(Value, _Version0) when is_list(Value) ->
+ try tls_v1:groups(Value) of
+ Groups ->
+ option_error(Groups =:= [], supported_groups, none_valid),
+ #supported_groups{supported_groups = Groups}
catch
- exit:_ -> throw({error, {options, {supported_groups, Value}}});
- error:_ -> throw({error, {options, {supported_groups, Value}}})
+ exit:_ -> option_error(supported_groups, Value);
+ error:_ -> option_error(supported_groups, Value)
end.
@@ -2698,9 +2741,10 @@ handle_supported_groups_option(Value, Version) when is_list(Value) ->
| InetError
| OtherReason) -> string()
when
- FileType :: cacertfile | certfile | keyfile | dhfile,
- OtherReason :: term(),
- InetError :: inet:posix() | system_limit.
+ FileType :: cacertfile | certfile | keyfile | dhfile,
+ OtherReason :: term(),
+ Error :: term(),
+ InetError :: inet:posix() | system_limit.
do_format_error(Reason) when is_list(Reason) ->
Reason;
@@ -2734,7 +2778,7 @@ unexpected_format(Error) ->
file_error_format({error, Error})->
case file:format_error(Error) of
- "unknown POSIX error" ->
+ "unknown POSIX error" ++ _ ->
"decoding error";
Str ->
Str
@@ -2751,42 +2795,37 @@ file_desc(keyfile) ->
file_desc(dhfile) ->
"Invalid DH params file ".
-detect(_Pred, []) ->
- undefined;
-detect(Pred, [H|T]) ->
- case Pred(H) of
- true ->
- H;
- _ ->
- detect(Pred, T)
- end.
-
make_next_protocol_selector(undefined) ->
undefined;
-make_next_protocol_selector({client, AllProtocols, DefaultProtocol}) ->
- fun(AdvertisedProtocols) ->
- case detect(fun(PreferredProtocol) ->
- lists:member(PreferredProtocol, AdvertisedProtocols)
- end, AllProtocols) of
- undefined ->
- DefaultProtocol;
- PreferredProtocol ->
- PreferredProtocol
- end
+make_next_protocol_selector({Precedence, PrefProtcol} = V) ->
+ option_error(not is_list(PrefProtcol), client_preferred_next_protocols, V),
+ make_next_protocol_selector({Precedence, PrefProtcol, ?NO_PROTOCOL});
+make_next_protocol_selector({Precedence, AllProtocols, DefP} = V) ->
+ option_error(not is_list(AllProtocols), client_preferred_next_protocols, V),
+ option_error(not (is_binary(DefP) andalso byte_size(DefP) < 256), client_preferred_next_protocols, V),
+ validate_protocols(true, client_preferred_next_protocols, AllProtocols),
+ case Precedence of
+ client ->
+ fun(Advertised) ->
+ Search = fun(P) -> lists:member(P, Advertised) end,
+ case lists:search(Search, AllProtocols) of
+ false -> DefP;
+ {value, Preferred} -> Preferred
+ end
+ end;
+ server ->
+ fun(Advertised) ->
+ Search = fun(P) -> lists:member(P, AllProtocols) end,
+ case lists:search(Search, Advertised) of
+ false -> DefP;
+ {value, Preferred} -> Preferred
+ end
+ end;
+ Value ->
+ option_error(client_preferred_next_protocols, {invalid_precedence, Value})
end;
-
-make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) ->
- fun(AdvertisedProtocols) ->
- case detect(fun(PreferredProtocol) ->
- lists:member(PreferredProtocol, AllProtocols)
- end,
- AdvertisedProtocols) of
- undefined ->
- DefaultProtocol;
- PreferredProtocol ->
- PreferredProtocol
- end
- end.
+make_next_protocol_selector(What) ->
+ option_error(client_preferred_next_protocols, What).
connection_cb(tls) ->
tls_gen_connection;
@@ -2795,16 +2834,6 @@ connection_cb(dtls) ->
connection_cb(Opts) ->
connection_cb(proplists:get_value(protocol, Opts, tls)).
-record_cb(tls) ->
- tls_record;
-record_cb(dtls) ->
- dtls_record;
-record_cb(Opts) ->
- record_cb(proplists:get_value(protocol, Opts, tls)).
-
-binary_filename(FileName) ->
- Enc = file:native_name_encoding(),
- unicode:characters_to_binary(FileName, unicode, Enc).
%% Assert that basic options are on the format {Key, Value}
%% with a few exceptions and phase out log_alert
@@ -2812,12 +2841,12 @@ handle_option_format([], Acc) ->
lists:reverse(Acc);
handle_option_format([{log_alert, Bool} | Rest], Acc) when is_boolean(Bool) ->
case proplists:get_value(log_level, Acc ++ Rest, undefined) of
- undefined ->
+ undefined ->
handle_option_format(Rest, [{log_level,
map_log_level(Bool)} | Acc]);
- _ ->
+ _ ->
handle_option_format(Rest, Acc)
- end;
+ end;
handle_option_format([{Key,_} = Opt | Rest], Acc) when is_atom(Key) ->
handle_option_format(Rest, [Opt | Acc]);
%% Handle exceptions
@@ -2828,53 +2857,13 @@ handle_option_format([inet = Opt | Rest], Acc) ->
handle_option_format([inet6 = Opt | Rest], Acc) ->
handle_option_format(Rest, [Opt | Acc]);
handle_option_format([Value | _], _) ->
- throw({option_not_a_key_value_tuple, Value}).
+ option_error(option_not_a_key_value_tuple, Value).
map_log_level(true) ->
notice;
map_log_level(false) ->
none.
-handle_verify_option(verify_none, #{fail_if_no_peer_cert := false} = OptionsMap) ->
- OptionsMap#{verify => verify_none};
-handle_verify_option(verify_none, #{fail_if_no_peer_cert := true}) ->
- throw({error, {options, incompatible,
- {verify, verify_none},
- {fail_if_no_peer_cert, true}}});
-%% The option 'verify' is simulated by the configured 'verify_fun' that is mostly
-%% hidden from the end user. When 'verify' is set to verify_none, the option
-%% 'verify_fun' is also set to a default verify-none-verify_fun when processing
-%% the configuration. If 'verify' is later changed from verify_none to verify_peer,
-%% the 'verify_fun' must also be changed to undefined. When 'verify_fun' is set to
-%% undefined, public_key's default verify_fun will be used that performs a full
-%% verification.
-handle_verify_option(verify_peer, #{verify := verify_none} = OptionsMap) ->
- OptionsMap#{verify => verify_peer,
- verify_fun => undefined};
-handle_verify_option(verify_peer, OptionsMap) ->
- OptionsMap#{verify => verify_peer};
-handle_verify_option(Value, _) ->
- throw({error, {options, {verify, Value}}}).
-
-%% Added to handle default values for signature_algs in TLS 1.3
-default_option_role_sign_algs(_, Value, _, Version) when Version >= {3,4} ->
- Value;
-default_option_role_sign_algs(Role, Value, Role, _) ->
- Value;
-default_option_role_sign_algs(_, _, _, _) ->
- undefined.
-
-default_option_role(Role, Value, Role) ->
- Value;
-default_option_role(_,_,_) ->
- undefined.
-
-
-default_cb_info(tls) ->
- {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive};
-default_cb_info(dtls) ->
- {gen_udp, udp, udp_closed, udp_error, udp_passive}.
-
include_security_info([]) ->
false;
include_security_info([Item | Items]) ->
@@ -2885,39 +2874,46 @@ include_security_info([Item | Items]) ->
include_security_info(Items)
end.
-server_name_indication_default(Host) when is_list(Host) ->
- %% SNI should not contain a trailing dot that a hostname may
- string:strip(Host, right, $.);
-server_name_indication_default(_) ->
- undefined.
add_filter(undefined, Filters) ->
Filters;
add_filter(Filter, Filters) ->
[Filter | Filters].
-maybe_client_warn_no_verify(#{verify := verify_none,
- warn_verify_none := true,
- log_level := LogLevel}, client) ->
- ssl_logger:log(warning, LogLevel,
- #{description => "Server authenticity is not verified since certificate path validation is not enabled",
- reason => "The option {verify, verify_peer} and one of the options 'cacertfile' or "
- "'cacerts' are required to enable this."}, ?LOCATION);
-maybe_client_warn_no_verify(_,_) ->
- %% Warning not needed. Note client certificate validation is optional in TLS
- ok.
-
unambiguous_path(Value) ->
AbsName = filename:absname(Value),
- case file:read_link(AbsName) of
- {ok, PathWithNoLink} ->
- case filename:pathtype(PathWithNoLink) of
- relative ->
- Dirname = filename:dirname(AbsName),
- filename:join([Dirname, PathWithNoLink]);
- _ ->
- PathWithNoLink
- end;
- _ ->
- AbsName
- end.
+ UP = case file:read_link(AbsName) of
+ {ok, PathWithNoLink} ->
+ case filename:pathtype(PathWithNoLink) of
+ relative ->
+ Dirname = filename:dirname(AbsName),
+ filename:join([Dirname, PathWithNoLink]);
+ _ ->
+ PathWithNoLink
+ end;
+ _ ->
+ AbsName
+ end,
+ validate_filename(UP, cacertfile).
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(csp, {call, {?MODULE, opt_ocsp, [UserOpts | _]}}, Stack) ->
+ {format_ocsp_params(UserOpts), Stack};
+handle_trace(csp, {return_from, {?MODULE, opt_ocsp, 3}, Return}, Stack) ->
+ {format_ocsp_params(Return), Stack};
+handle_trace(rle, {call, {?MODULE, listen, Args}}, Stack0) ->
+ Role = server,
+ {io_lib:format("(*~w) Args = ~W", [Role, Args, 10]), [{role, Role} | Stack0]};
+handle_trace(rle, {call, {?MODULE, connect, Args}}, Stack0) ->
+ Role = client,
+ {io_lib:format("(*~w) Args = ~W", [Role, Args, 10]), [{role, Role} | Stack0]}.
+
+format_ocsp_params(Map) ->
+ Stapling = maps:get(ocsp_stapling, Map, '?'),
+ Nonce = maps:get(ocsp_nonce, Map, '?'),
+ Certs = maps:get(ocsp_responder_certs, Map, '?'),
+ io_lib:format("Stapling = ~W Nonce = ~W Certs = ~W",
+ [Stapling, 5, Nonce, 5, Certs, 5]).
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 2e2b43f564..ec1816fdde 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022 All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023 All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -65,6 +65,7 @@
-include("ssl_handshake.hrl").
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
+-include("ssl_record.hrl").
-include_lib("public_key/include/public_key.hrl").
-export([trusted_cert_and_paths/4,
@@ -85,6 +86,9 @@
available_cert_key_pairs/2
]).
+%% Tracing
+-export([handle_trace/3]).
+
%%====================================================================
%% Internal application API
%%====================================================================
@@ -342,13 +346,14 @@ available_cert_key_pairs(CertKeyGroups) ->
%% Create the prioritized list of cert key pairs that
%% are availble for use in the negotiated version
-available_cert_key_pairs(CertKeyGroups, {3, 4}) ->
+available_cert_key_pairs(CertKeyGroups, ?TLS_1_3) ->
RevAlgos = [rsa, rsa_pss_pss, ecdsa, eddsa],
cert_key_group_to_list(RevAlgos, CertKeyGroups, []);
-available_cert_key_pairs(CertKeyGroups, {3, 3}) ->
+available_cert_key_pairs(CertKeyGroups, ?TLS_1_2) ->
RevAlgos = [dsa, rsa, rsa_pss_pss, ecdsa],
cert_key_group_to_list(RevAlgos, CertKeyGroups, []);
-available_cert_key_pairs(CertKeyGroups, {3, N}) when N < 3->
+available_cert_key_pairs(CertKeyGroups, Version)
+ when ?TLS_LT(Version, ?TLS_1_2) ->
RevAlgos = [dsa, rsa, ecdsa],
cert_key_group_to_list(RevAlgos, CertKeyGroups, []).
@@ -578,10 +583,12 @@ verify_cert_extensions(Cert, UserState, [], _) ->
{valid, UserState#{issuer => Cert}};
verify_cert_extensions(Cert, #{ocsp_responder_certs := ResponderCerts,
ocsp_state := OscpState,
- issuer := Issuer} = UserState,
- [#certificate_status{response = OcspResponsDer} | Exts], Context) ->
+ issuer := Issuer} = UserState,
+ [#certificate_status{response = OcspResponsDer} | Exts],
+ Context) ->
#{ocsp_nonce := Nonce} = OscpState,
- case public_key:pkix_ocsp_validate(Cert, Issuer, OcspResponsDer, ResponderCerts, Nonce) of
+ case public_key:pkix_ocsp_validate(Cert, Issuer, OcspResponsDer,
+ ResponderCerts, Nonce) of
valid ->
verify_cert_extensions(Cert, UserState, Exts, Context);
{bad_cert, _} = Status ->
@@ -591,21 +598,22 @@ verify_cert_extensions(Cert, UserState, [_|Exts], Context) ->
%% Skip unknown extensions!
verify_cert_extensions(Cert, UserState, Exts, Context).
-verify_sign(_, #{version := {_, Minor}}) when Minor < 3 ->
+verify_sign(_, #{version := Version})
+ when ?TLS_LT(Version, ?TLS_1_2) ->
%% This verification is not applicable pre TLS-1.2
true;
-verify_sign(Cert, #{version := {3, 3},
+verify_sign(Cert, #{version := ?TLS_1_2,
signature_algs := SignAlgs,
signature_algs_cert := undefined}) ->
is_supported_signature_algorithm_1_2(Cert, SignAlgs);
-verify_sign(Cert, #{version := {3, 3},
+verify_sign(Cert, #{version := ?TLS_1_2,
signature_algs_cert := SignAlgs}) ->
is_supported_signature_algorithm_1_2(Cert, SignAlgs);
-verify_sign(Cert, #{version := {3, 4},
+verify_sign(Cert, #{version := ?TLS_1_3,
signature_algs := SignAlgs,
signature_algs_cert := undefined}) ->
is_supported_signature_algorithm_1_3(Cert, SignAlgs);
-verify_sign(Cert, #{version := {3, 4},
+verify_sign(Cert, #{version := ?TLS_1_3,
signature_algs_cert := SignAlgs}) ->
is_supported_signature_algorithm_1_3(Cert, SignAlgs).
@@ -620,7 +628,7 @@ is_supported_signature_algorithm_1_2(#'OTPCertificate'{signatureAlgorithm =
is_supported_signature_algorithm_1_2(#'OTPCertificate'{signatureAlgorithm = SignAlg}, SignAlgs) ->
Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
{Hash, Sign, _ } = ssl_cipher:scheme_to_components(Scheme),
- ssl_cipher:is_supported_sign({pre_1_3_hash(Hash), pre_1_3_sign(Sign)}, ssl_cipher:signature_schemes_1_2(SignAlgs)).
+ ssl_cipher:is_supported_sign({Hash, pre_1_3_sign(Sign)}, ssl_cipher:signature_schemes_1_2(SignAlgs)).
is_supported_signature_algorithm_1_3(#'OTPCertificate'{signatureAlgorithm = SignAlg}, SignAlgs) ->
Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
ssl_cipher:is_supported_sign(Scheme, SignAlgs).
@@ -629,10 +637,6 @@ pre_1_3_sign(rsa_pkcs1) ->
rsa;
pre_1_3_sign(Other) ->
Other.
-pre_1_3_hash(sha1) ->
- sha;
-pre_1_3_hash(Hash) ->
- Hash.
paths(Chain, CertDbHandle) ->
paths(Chain, Chain, CertDbHandle, []).
@@ -825,3 +829,37 @@ cert_issuers(OTPCerts) ->
cert_auth_member(ChainSubjects, CertAuths) ->
CommonAuthorities = sets:intersection(sets:from_list(ChainSubjects), sets:from_list(CertAuths)),
not sets:is_empty(CommonAuthorities).
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(crt,
+ {call, {?MODULE, validate, [Cert, StatusOrExt| _]}}, Stack) ->
+ {io_lib:format("[~W] StatusOrExt = ~W", [Cert, 3, StatusOrExt, 10]), Stack};
+ %% {io_lib:format("(~s) StatusOrExt = ~W",
+ %% [ssl_test_lib:format_cert(Cert), StatusOrExt, 10]), Stack};
+handle_trace(crt, {call, {?MODULE, verify_cert_extensions,
+ [Cert,
+ _UserState,
+ [], _Context]}}, Stack) ->
+ {io_lib:format(" no more extensions [~W]", [Cert, 3]), Stack};
+ %% {io_lib:format(" no more extensions (~s)", [ssl_test_lib:format_cert(Cert)]), Stack};
+handle_trace(crt, {call, {?MODULE, verify_cert_extensions,
+ [Cert,
+ #{ocsp_responder_certs := _ResponderCerts,
+ ocsp_state := OcspState,
+ issuer := Issuer} = _UserState,
+ [#certificate_status{response = OcspResponsDer} |
+ _Exts], _Context]}}, Stack) ->
+ {io_lib:format("#2 OcspState = ~W Issuer = [~W] OcspResponsDer = ~W [~W]",
+ [OcspState, 10, Issuer, 3, OcspResponsDer, 2, Cert, 3]),
+ Stack};
+ %% {io_lib:format("#2 OcspState = ~W Issuer = (~s) OcspResponsDer = ~W (~s)",
+ %% [OcspState, 10, ssl_test_lib:format_cert(Issuer),
+ %% OcspResponsDer, 2, ssl_test_lib:format_cert(Cert)]),
+handle_trace(crt, {return_from,
+ {ssl_certificate, verify_cert_extensions, 4},
+ {valid, #{issuer := Issuer}}}, Stack) ->
+ {io_lib:format(" extensions valid Issuer = ~W", [Issuer, 3]), Stack}.
+ %% {io_lib:format(" extensions valid Issuer = ~s", [ssl_test_lib:format_cert(Issuer)]), Stack}.
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 6e952a4584..8d982f7fa2 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -214,16 +214,15 @@ build_cipher_block(BlockSz, Mac, Fragment) ->
[Fragment, Mac, padding_with_len(TotSz, BlockSz)].
block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
- Mac, Fragment, {3, N})
- when N == 0; N == 1 ->
+ Mac, Fragment, ?TLS_1_0) ->
L = build_cipher_block(BlockSz, Mac, Fragment),
T = Fun(Key, IV, L),
NextIV = next_iv(T, IV),
{T, CS0#cipher_state{iv=NextIV}};
block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV, state = IV_Cache0} = CS0,
- Mac, Fragment, {3, N})
- when N == 2; N == 3; N == 4 ->
+ Mac, Fragment, Version)
+ when ?TLS_GT(Version, ?TLS_1_0)->
IV_Size = byte_size(IV),
<<NextIV:IV_Size/binary, IV_Cache/binary>> =
case IV_Cache0 of
@@ -321,45 +320,42 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
%%
%% Description: Returns a list of supported cipher suites.
%%--------------------------------------------------------------------
-suites({3, Minor}) ->
- tls_v1:suites(Minor);
-suites({_, Minor}) ->
- dtls_v1:suites(Minor).
-all_suites({3, 4} = Version) ->
- suites(Version)
- ++ tls_v1:psk_suites({3,3})
- ++ tls_v1:srp_suites({3,3})
- ++ tls_v1:rsa_suites({3,3})
- ++ tls_v1:des_suites({3,3})
- ++ tls_v1:rc4_suites({3,3});
-all_suites({3, _} = Version) ->
- suites(Version)
- ++ tls_v1:psk_suites(Version)
- ++ tls_v1:srp_suites(Version)
- ++ tls_v1:rsa_suites(Version)
- ++ tls_v1:des_suites(Version)
- ++ tls_v1:rc4_suites(Version);
+suites(Version) when ?TLS_1_X(Version) ->
+ tls_v1:suites(Version);
+suites(Version) when ?DTLS_1_X(Version) ->
+ dtls_v1:suites(Version).
+all_suites(?TLS_1_3 = Version) ->
+ suites(Version) ++ tls_legacy_suites(?TLS_1_2);
+all_suites(Version) when ?TLS_1_X(Version) ->
+ suites(Version) ++ tls_legacy_suites(Version);
all_suites(Version) ->
dtls_v1:all_suites(Version).
+tls_legacy_suites(Version) ->
+ Tests = [fun tls_v1:psk_suites/1,
+ fun tls_v1:srp_suites/1,
+ fun tls_v1:rsa_suites/1,
+ fun tls_v1:des_suites/1,
+ fun tls_v1:rc4_suites/1],
+ lists:flatmap(fun (Fun) -> Fun(Version) end, Tests).
+
%%--------------------------------------------------------------------
--spec anonymous_suites(ssl_record:ssl_version() | integer()) ->
- [ssl_cipher_format:cipher_suite()].
+-spec anonymous_suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the anonymous cipher suites, only supported
%% if explicitly set by user. Intended only for testing.
%%--------------------------------------------------------------------
-anonymous_suites({3, N}) ->
- anonymous_suites(N);
-anonymous_suites({254, _} = Version) ->
- dtls_v1:anonymous_suites(Version);
-
-anonymous_suites(1 = N) ->
- tls_v1:exclusive_anonymous_suites(N);
-anonymous_suites(4 = N) ->
- tls_v1:exclusive_anonymous_suites(N);
-anonymous_suites(N) when N > 1->
- tls_v1:exclusive_anonymous_suites(N) ++ anonymous_suites(N-1).
+
+anonymous_suites(Version) when ?TLS_1_X(Version) ->
+ SuitesToTest = anonymous_suite_to_test(Version),
+ lists:flatmap(fun tls_v1:exclusive_anonymous_suites/1, SuitesToTest);
+anonymous_suites(Version) when ?DTLS_1_X(Version) ->
+ dtls_v1:anonymous_suites(Version).
+
+anonymous_suite_to_test(?TLS_1_0) -> [?TLS_1_0];
+anonymous_suite_to_test(?TLS_1_1) -> [?TLS_1_1, ?TLS_1_0];
+anonymous_suite_to_test(?TLS_1_2) -> [?TLS_1_2, ?TLS_1_1, ?TLS_1_0];
+anonymous_suite_to_test(?TLS_1_3) -> [?TLS_1_3].
%%--------------------------------------------------------------------
-spec filter(undefined | binary(), [ssl_cipher_format:cipher_suite()],
@@ -393,11 +389,11 @@ filter(DerCert, Ciphers0, Version) ->
%% Description: Filter suites using supplied filter funs
%%-------------------------------------------------------------------
filter_suites(Suites, Filters) ->
- ApplyFilters = fun(Suite) ->
- filter_suite(Suite, Filters)
- end,
- lists:filter(ApplyFilters, Suites).
-
+ Fn = fun (Suite) when is_map_key(key_exchange, Suite) -> Suite;
+ (Suite) -> ssl_cipher_format:suite_bin_to_map(Suite)
+ end,
+ lists:filter(fun(Suite) -> filter_suite(Fn(Suite), Filters) end, Suites).
+
filter_suite(#{key_exchange := KeyExchange,
cipher := Cipher,
mac := Hash,
@@ -406,12 +402,10 @@ filter_suite(#{key_exchange := KeyExchange,
cipher_filters := CipherFilters,
mac_filters := HashFilters,
prf_filters := PrfFilters}) ->
- all_filters(KeyExchange, KeyFilters) andalso
- all_filters(Cipher, CipherFilters) andalso
- all_filters(Hash, HashFilters) andalso
- all_filters(Prf, PrfFilters);
-filter_suite(Suite, Filters) ->
- filter_suite(ssl_cipher_format:suite_bin_to_map(Suite), Filters).
+ KeyPairs = [{KeyExchange, KeyFilters}, {Cipher, CipherFilters},
+ {Hash, HashFilters}, {Prf, PrfFilters}],
+ lists:all(fun all_filters/1, KeyPairs).
+
%%--------------------------------------------------------------------
-spec filter_suites([ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]) ->
@@ -423,15 +417,9 @@ filter_suites(Suites) ->
Filters = crypto_support_filters(),
filter_suites(Suites, Filters).
-all_filters(_, []) ->
- true;
-all_filters(Value, [Filter| Rest]) ->
- case Filter(Value) of
- true ->
- all_filters(Value, Rest);
- false ->
- false
- end.
+all_filters({Value, Filters}) ->
+ lists:all(fun (FilterFn) -> FilterFn(Value) end, Filters).
+
crypto_support_filters() ->
Algos = crypto:supports(),
Hashs = proplists:get_value(hashs, Algos),
@@ -568,20 +556,17 @@ hash_size(sha384) ->
hash_size(sha512) ->
64.
-is_supported_sign({Hash, rsa} = SignAlgo, HashSigns) -> %% PRE TLS-1.3
- lists:member(SignAlgo, HashSigns) orelse
- lists:member({Hash, rsa_pss_rsae}, HashSigns);
-is_supported_sign(rsa_pkcs1_sha256 = SignAlgo, HashSigns) -> %% TLS-1.3 legacy
- lists:member(SignAlgo, HashSigns) orelse
- lists:member(rsa_pss_rsae_sha256, HashSigns);
-is_supported_sign(rsa_pkcs1_sha384 = SignAlgo, HashSigns) -> %% TLS-1.3 legacy
- lists:member(SignAlgo, HashSigns) orelse
- lists:member(rsa_pss_rsae_sha384, HashSigns);
-is_supported_sign(rsa_pkcs1_sha512 = SignAlgo, HashSigns) -> %% TLS-1.3 legacy
- lists:member(SignAlgo, HashSigns) orelse
- lists:member(rsa_pss_rsae_sha512, HashSigns);
-is_supported_sign(SignAlgo, HashSigns) -> %% PRE TLS-1.3 SignAlgo::tuple() TLS-1.3 SignAlgo::atom()
- lists:member(SignAlgo, HashSigns).
+is_supported_sign(SignAlgo, HashSigns) ->
+ lists:any(fun (SignAlgo0) -> lists:member(SignAlgo0, HashSigns) end,
+ [SignAlgo, supported_signalgo(SignAlgo)]).
+
+supported_signalgo({Hash, rsa}) -> {Hash,rsa_pss_rsae}; %% PRE TLS-1.3 %% PRE TLS-1.3
+supported_signalgo(rsa_pkcs1_sha256) -> rsa_pss_rsae_sha256; %% TLS-1.3 legacy
+supported_signalgo(rsa_pkcs1_sha384) -> rsa_pss_rsae_sha384; %% TLS-1.3 legacy
+supported_signalgo(rsa_pkcs1_sha512) -> rsa_pss_rsae_sha512; %% TLS-1.3 legacy
+supported_signalgo(_) -> skip_test. %% made up atom, PRE TLS-1.3 SignAlgo::tuple() TLS-1.3 SignAlgo::atom()
+
+
signature_scheme(rsa_pkcs1_sha256) -> ?RSA_PKCS1_SHA256;
signature_scheme(rsa_pkcs1_sha384) -> ?RSA_PKCS1_SHA384;
@@ -687,8 +672,8 @@ scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}.
mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type,
_Length, _Fragment) ->
<<>>;
-mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment)
- when N =:= 1; N =:= 2; N =:= 3; N =:= 4 ->
+mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment)
+ when ?TLS_LTE(Version, ?TLS_1_2), Version =/= ?SSL_3_0 ->
tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
Length, Fragment).
@@ -828,9 +813,9 @@ block_size(Cipher) when Cipher == aes_128_cbc;
Cipher == chacha20_poly1305 ->
16.
-prf_algorithm(default_prf, {3, N}) when N >= 3 ->
+prf_algorithm(default_prf, ?TLS_1_2) ->
?SHA256;
-prf_algorithm(default_prf, {3, _}) ->
+prf_algorithm(default_prf, Version) when ?TLS_1_X(Version) ->
?MD5SHA;
prf_algorithm(Algo, _) ->
hash_algorithm(Algo).
@@ -933,8 +918,7 @@ signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS'
%% We return the original (possibly invalid) PadLength in any case.
%% An invalid PadLength will be caught by is_correct_padding/2
%%
-generic_block_cipher_from_bin({3, N}, T, IV, HashSize)
- when N == 0; N == 1 ->
+generic_block_cipher_from_bin(?TLS_1_0, T, IV, HashSize)->
Sz1 = byte_size(T) - 1,
<<_:Sz1/binary, ?BYTE(PadLength0)>> = T,
PadLength = if
@@ -948,8 +932,8 @@ generic_block_cipher_from_bin({3, N}, T, IV, HashSize)
padding=Padding, padding_length=PadLength0,
next_iv = IV};
-generic_block_cipher_from_bin({3, N}, T, IV, HashSize)
- when N == 2; N == 3; N == 4 ->
+generic_block_cipher_from_bin(Version, T, IV, HashSize)
+ when Version == ?TLS_1_1; Version == ?TLS_1_2 ->
Sz1 = byte_size(T) - 1,
<<_:Sz1/binary, ?BYTE(PadLength)>> = T,
IVLength = byte_size(IV),
@@ -968,14 +952,14 @@ generic_stream_cipher_from_bin(T, HashSz) ->
mac=Mac}.
is_correct_padding(#generic_block_cipher{padding_length = Len,
- padding = Padding}, {3, 0}, _) ->
+ padding = Padding}, ?SSL_3_0, _) ->
Len == byte_size(Padding); %% Only length check is done in SSL 3.0 spec
%% For interoperability reasons it is possible to disable
-%% the padding check when using TLS 1.0, as it is not strictly required
+%% the padding check when using TLS 1.0 (mimicking SSL-3.0), as it is not strictly required
%% in the spec (only recommended), however this makes TLS 1.0 vunrable to the Poodle attack
%% so by default this clause will not match
-is_correct_padding(GenBlockCipher, {3, 1}, false) ->
- is_correct_padding(GenBlockCipher, {3, 0}, false);
+is_correct_padding(GenBlockCipher, ?TLS_1_0, false) ->
+ is_correct_padding(GenBlockCipher, ?SSL_3_0, false);
%% Padding must be checked in TLS 1.1 and after
is_correct_padding(#generic_block_cipher{padding_length = Len,
padding = Padding}, _, _) ->
@@ -1053,14 +1037,14 @@ filter_suites_pubkey(ecdsa, Ciphers, _, OtpCert) ->
ec_ecdhe_suites(Ciphers)),
filter_keyuse_suites(keyAgreement, Uses, CiphersSuites, ec_ecdh_suites(Ciphers)).
-filter_suites_signature(_, Ciphers, {3, N}) when N >= 3 ->
+filter_suites_signature(_, Ciphers, Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
Ciphers;
filter_suites_signature(rsa, Ciphers, Version) ->
- (Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers, Version);
+ (Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers);
filter_suites_signature(dsa, Ciphers, Version) ->
(Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- rsa_signed_suites(Ciphers, Version);
filter_suites_signature(ecdsa, Ciphers, Version) ->
- (Ciphers -- rsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers, Version).
+ (Ciphers -- rsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers).
%% From RFC 5246 - Section 7.4.2. Server Certificate
@@ -1079,7 +1063,7 @@ filter_suites_signature(ecdsa, Ciphers, Version) ->
%% extension. The names DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are
%% historical.
%% Note: DH_DSS and DH_RSA is not supported
-rsa_signed({3,N}) when N >= 3 ->
+rsa_signed(?TLS_1_2) ->
fun(rsa) -> true;
(dhe_rsa) -> true;
(ecdhe_rsa) -> true;
@@ -1098,11 +1082,9 @@ rsa_signed(_) ->
end.
%% Cert should be signed by RSA
rsa_signed_suites(Ciphers, Version) ->
- filter_suites(Ciphers, #{key_exchange_filters => [rsa_signed(Version)],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
-ecdsa_signed({3,N}) when N >= 3 ->
+ filter_kex(Ciphers, rsa_signed(Version)).
+
+ecdsa_signed(Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
fun(ecdhe_ecdsa) -> true;
(_) -> false
end;
@@ -1114,10 +1096,7 @@ ecdsa_signed(_) ->
%% Cert should be signed by ECDSA
ecdsa_signed_suites(Ciphers, Version) ->
- filter_suites(Ciphers, #{key_exchange_filters => [ecdsa_signed(Version)],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, ecdsa_signed(Version)).
rsa_keyed(dhe_rsa) ->
true;
@@ -1134,98 +1113,67 @@ rsa_keyed(_) ->
%% Certs key is an RSA key
rsa_keyed_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> rsa_keyed(Kex) end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun rsa_keyed/1).
%% RSA Certs key can be used for encipherment
rsa_suites_encipher(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(rsa) -> true;
- (rsa_psk) -> true;
- (_) -> false
- end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun(rsa) -> true;
+ (rsa_psk) -> true;
+ (_) -> false
+ end).
-dss_keyed(dhe_dss) ->
- true;
-dss_keyed(spr_dss) ->
- true;
-dss_keyed(_) ->
- false.
%% Cert should be have DSS key (DSA)
dss_keyed_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> dss_keyed(Kex) end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun (dhe_dss) -> true;
+ (spr_dss) -> true;
+ (_) -> false
+ end).
%% Cert should be signed by DSS (DSA)
-dsa_signed_suites(Ciphers, Version) ->
- filter_suites(Ciphers, #{key_exchange_filters => [dsa_signed(Version)],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
-dsa_signed(_) ->
- fun(dhe_dss) -> true;
- (_) -> false
- end.
+dsa_signed_suites(Ciphers) ->
+ filter_kex(Ciphers, fun(dhe_dss) -> true;
+ (_) -> false
+ end).
dss_dhe_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(dhe_dss) -> true;
- (_) -> false
- end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
-
-ec_keyed(ecdh_ecdsa) ->
- true;
-ec_keyed(ecdh_rsa) ->
- true;
-ec_keyed(ecdhe_ecdsa) ->
- true;
-ec_keyed(_) ->
- false.
-
+ filter_kex(Ciphers, fun(dhe_dss) -> true;
+ (_) -> false
+ end).
%% Certs key is an ECC key
ec_keyed_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> ec_keyed(Kex) end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun (ecdh_ecdsa) -> true;
+ (ecdh_rsa) -> true;
+ (ecdhe_ecdsa) -> true;
+ (_) -> false
+ end).
%% EC Certs key usage keyAgreement
ec_ecdh_suites(Ciphers)->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true;
- (_) -> false
- end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun(ecdh_ecdsa) -> true;
+ (_) -> false
+ end).
%% EC Certs key usage digitalSignature
ec_ecdhe_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdhe_ecdsa) -> true;
- (ecdhe_rsa) -> true;
- (_) -> false
- end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun(ecdhe_ecdsa) -> true;
+ (ecdhe_rsa) -> true;
+ (_) -> false
+ end).
%% RSA Certs key usage digitalSignature
rsa_ecdhe_dhe_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(dhe_rsa) -> true;
- (ecdhe_rsa) -> true;
- (_) -> false
- end],
+ filter_kex(Ciphers, fun(dhe_rsa) -> true;
+ (ecdhe_rsa) -> true;
+ (_) -> false
+ end).
+
+filter_kex(Ciphers, Fn) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [Fn],
cipher_filters => [],
mac_filters => [],
prf_filters => []}).
+
key_uses(OtpCert) ->
TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
TBSExtensions = TBSCert#'OTPTBSCertificate'.extensions,
@@ -1256,21 +1204,12 @@ generate_server_share(Group) ->
key_exchange = Key
}}.
-generate_client_shares([]) ->
- #key_share_client_hello{client_shares = []};
generate_client_shares(Groups) ->
- generate_client_shares(Groups, []).
-%%
-generate_client_shares([], Acc) ->
- #key_share_client_hello{client_shares = lists:reverse(Acc)};
-generate_client_shares([Group|Groups], Acc) ->
- Key = generate_key_exchange(Group),
- KeyShareEntry = #key_share_entry{
- group = Group,
- key_exchange = Key
- },
- generate_client_shares(Groups, [KeyShareEntry|Acc]).
-
+ KeyShareEntry = fun (Group) ->
+ #key_share_entry{group = Group, key_exchange = generate_key_exchange(Group)}
+ end,
+ ClientShares = lists:map(KeyShareEntry, Groups),
+ #key_share_client_hello{client_shares = ClientShares}.
generate_key_exchange(secp256r1) ->
public_key:generate_key({namedCurve, secp256r1});
@@ -1308,10 +1247,22 @@ encrypt_ticket(#stateless_ticket{
pre_shared_key = PSK,
ticket_age_add = TicketAgeAdd,
lifetime = Lifetime,
- timestamp = Timestamp
+ timestamp = Timestamp,
+ certificate = Certificate
}, Shard, IV) ->
- Plaintext = <<(ssl_cipher:hash_algorithm(Hash)):8,PSK/binary,
+ Plaintext1 = <<(ssl_cipher:hash_algorithm(Hash)):8,PSK/binary,
?UINT64(TicketAgeAdd),?UINT32(Lifetime),?UINT32(Timestamp)>>,
+ CertificateLength = case Certificate of
+ undefined -> 0;
+ _ -> byte_size(Certificate)
+ end,
+ Plaintext = case CertificateLength of
+ 0 ->
+ <<Plaintext1/binary,?UINT16(0)>>;
+ _ ->
+ <<Plaintext1/binary,?UINT16(CertificateLength),
+ Certificate/binary>>
+ end,
encrypt_ticket_data(Plaintext, Shard, IV).
@@ -1323,13 +1274,19 @@ decrypt_ticket(CipherFragment, Shard, IV) ->
<<?BYTE(HKDF),T/binary>> = Plaintext,
Hash = hash_algorithm(HKDF),
HashSize = hash_size(Hash),
- <<PSK:HashSize/binary,?UINT64(TicketAgeAdd),?UINT32(Lifetime),?UINT32(Timestamp),_/binary>> = T,
+ <<PSK:HashSize/binary,?UINT64(TicketAgeAdd),?UINT32(Lifetime),?UINT32(Timestamp),
+ ?UINT16(CertificateLength),Certificate1:CertificateLength/binary,_/binary>> = T,
+ Certificate = case CertificateLength of
+ 0 -> undefined;
+ _ -> Certificate1
+ end,
#stateless_ticket{
hash = Hash,
pre_shared_key = PSK,
ticket_age_add = TicketAgeAdd,
lifetime = Lifetime,
- timestamp = Timestamp
+ timestamp = Timestamp,
+ certificate = Certificate
}
end.
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index 77305c1e77..cf5bd06a35 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -34,7 +34,8 @@
pre_shared_key,
ticket_age_add,
lifetime,
- timestamp
+ timestamp,
+ certificate
}).
%%% SSL cipher protocol %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl
index 46578c05a5..761a4f4315 100644
--- a/lib/ssl/src/ssl_config.erl
+++ b/lib/ssl/src/ssl_config.erl
@@ -40,40 +40,24 @@
%%====================================================================
%% Internal application API
%%====================================================================
-init(#{erl_dist := ErlDist,
- dh := DH,
- dhfile := DHFile} = SslOpts, Role) ->
-
- init_manager_name(ErlDist),
+init(SslOpts, Role) ->
+ init_manager_name(maps:get(erl_dist, SslOpts, false)),
#{pem_cache := PemCache} = Config = init_cacerts(SslOpts, Role),
- DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role),
-
+ DHParams = init_diffie_hellman(PemCache, SslOpts, Role),
CertKeyAlts = init_certs_keys(SslOpts, Role, PemCache),
-
{ok, Config#{cert_key_alts => CertKeyAlts, dh_params => DHParams}}.
init_certs_keys(#{certs_keys := CertsKeys}, Role, PemCache) ->
- Pairs = lists:map(fun(CertKey) -> cert_key_pair(CertKey, Role, PemCache) end, CertsKeys),
+ Pairs = lists:map(fun(CertKey) -> init_cert_key_pair(CertKey, Role, PemCache) end, CertsKeys),
CertKeyGroups = group_pairs(Pairs),
- prioritize_groups(CertKeyGroups);
-init_certs_keys(SslOpts, Role, PemCache) ->
- KeyPair = init_cert_key_pair(SslOpts, Role, PemCache),
- group_pairs([KeyPair]).
-
-init_cert_key_pair(#{key := Key,
- keyfile := KeyFile,
- password := Password} = Opts, Role, PemCache) ->
- {ok, Certs} = init_certificates(Opts, PemCache, Role),
- PrivateKey =
- init_private_key(PemCache, Key, KeyFile, Password, Role),
- #{private_key => PrivateKey, certs => Certs}.
-
-cert_key_pair(CertKey, Role, PemCache) ->
- CertKeyPairConf = cert_conf(key_conf(CertKey)),
- init_cert_key_pair(CertKeyPairConf, Role, PemCache).
+ prioritize_groups(CertKeyGroups).
+init_cert_key_pair(CertKey, Role, PemCache) ->
+ Certs = init_certificates(CertKey, PemCache, Role),
+ PrivateKey = init_private_key(maps:get(key, CertKey, undefined), CertKey, PemCache),
+ #{private_key => PrivateKey, certs => Certs}.
-group_pairs([#{certs := [[]]}]) ->
+group_pairs([#{certs := []}]) ->
#{eddsa => [],
ecdsa => [],
rsa_pss_pss => [],
@@ -87,8 +71,7 @@ group_pairs(Pairs) ->
rsa => [],
dsa => []
}).
-group_pairs([], Group) ->
- Group;
+
group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed25519'}}} = Pair | Rest], #{eddsa := EDDSA} = Group) ->
group_pairs(Rest, Group#{eddsa => [Pair | EDDSA]});
group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed448'}}} = Pair | Rest], #{eddsa := EDDSA} = Group) ->
@@ -106,7 +89,10 @@ group_pairs([#{private_key := #{algorithm := dss, engine := _}} = Pair | Rest],
group_pairs(Rest, Group#{dsa => [Pair | Pairs]});
group_pairs([#{private_key := #{algorithm := Alg, engine := _}} = Pair | Rest], Group) ->
Pairs = maps:get(Alg, Group),
- group_pairs(Rest, Group#{Alg => [Pair | Pairs]}).
+ group_pairs(Rest, Group#{Alg => [Pair | Pairs]});
+group_pairs([], Group) ->
+ Group.
+
prioritize_groups(#{eddsa := EDDSA,
ecdsa := ECDSA,
@@ -178,25 +164,6 @@ prio_dsa(DSA) ->
end,
lists:sort(Order, DSA).
-key_conf(#{key := _} = Conf) ->
- Conf#{certfile => <<>>,
- keyfile => <<>>,
- password => undefined};
-key_conf(#{keyfile := _} = Conf) ->
- case maps:get(password, Conf, undefined) of
- undefined ->
- Conf#{key => undefined,
- password => undefined};
- _ ->
- Conf#{key => undefined}
- end.
-
-cert_conf(#{cert := Bin} = Conf) when is_binary(Bin)->
- Conf#{cert => [Bin]};
-cert_conf(#{cert := _} = Conf) ->
- Conf#{certfile => <<>>};
-cert_conf(#{certfile := _} = Conf) ->
- Conf#{cert => undefined}.
pre_1_3_session_opts(Role) ->
{Cb, InitArgs} = session_cb_opts(Role),
@@ -261,17 +228,13 @@ init_manager_name(true) ->
put(ssl_manager, ssl_manager:name(dist)),
put(ssl_pem_cache, ssl_pem_cache:name(dist)).
-init_cacerts(#{cacerts := CaCerts,
- cacertfile := CACertFile,
- crl_cache := CRLCache
- }, Role) ->
+init_cacerts(#{cacerts := CaCerts, crl_cache := CRLCache} = Opts, Role) ->
+ CACertFile = maps:get(cacertfile, Opts, <<>>),
{ok, Config} =
- try
+ try
Certs = case CaCerts of
- undefined ->
- CACertFile;
- _ ->
- {der, CaCerts}
+ undefined -> CACertFile;
+ _ -> {der, CaCerts}
end,
{ok,_} = ssl_manager:connection_init(Certs, Role, CRLCache)
catch
@@ -280,65 +243,56 @@ init_cacerts(#{cacerts := CaCerts,
end,
Config.
-init_certificates(#{certfile := CertFile,
- cert := OwnCerts}, PemCache, Role) ->
- init_certificates(OwnCerts, PemCache, CertFile, Role).
-
-init_certificates(undefined, _, <<>>, _) ->
- {ok, [[]]};
-init_certificates(undefined, PemCache, CertFile, client) ->
- try
- %% OwnCert | [OwnCert | Chain]
- OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache),
- {ok, OwnCerts}
- catch _Error:_Reason ->
- {ok, [[]]}
- end;
-init_certificates(undefined, PemCache, CertFile, server) ->
- try
- %% OwnCert | [OwnCert | Chain]
- OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache),
- {ok, OwnCerts}
+init_certificates(CertKey, PemCache, Role) ->
+ case maps:get(cert, CertKey, undefined) of
+ undefined ->
+ init_certificate_file(maps:get(certfile, CertKey, <<>>), PemCache, Role);
+ Bin when is_binary(Bin) ->
+ [Bin];
+ Certs when is_list(Certs) ->
+ Certs
+ end.
+
+init_certificate_file(<<>>, _PemCache, _Role) ->
+ [];
+init_certificate_file(CertFile, PemCache, Role) ->
+ try %% OwnCert | [OwnCert | Chain]
+ ssl_certificate:file_to_certificats(CertFile, PemCache)
catch
- _:Reason ->
- file_error(CertFile, {certfile, Reason})
- end;
-init_certificates(OwnCerts, _, _, _) when is_binary(OwnCerts)->
- {ok, [OwnCerts]};
-init_certificates(OwnCerts, _, _, _) ->
- {ok, OwnCerts}.
-
-init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa;
- Alg == rsa;
- Alg == dss ->
+ _Error:_Reason when Role =:= client ->
+ [];
+ _Error:Reason ->
+ file_error(CertFile, {certfile, Reason})
+ end.
+
+init_private_key(#{algorithm := Alg} = Key, _, _PemCache)
+ when Alg =:= ecdsa; Alg =:= rsa; Alg =:= dss ->
case maps:is_key(engine, Key) andalso maps:is_key(key_id, Key) of
- true ->
- Key;
- false ->
- throw({key, {invalid_key_id, Key}})
- end;
-init_private_key(_, undefined, <<>>, _Password, _Client) ->
- #{};
-init_private_key(DbHandle, undefined, KeyFile, Password, _) ->
- try
- {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle),
- [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List,
- PKey =:= 'RSAPrivateKey' orelse
- PKey =:= 'DSAPrivateKey' orelse
- PKey =:= 'ECPrivateKey' orelse
- PKey =:= 'PrivateKeyInfo'
- ],
- private_key(public_key:pem_entry_decode(PemEntry, Password))
- catch
- _:Reason ->
- file_error(KeyFile, {keyfile, Reason})
+ true -> Key;
+ false -> throw({key, {invalid_key_id, Key}})
end;
-
-init_private_key(_,{Asn1Type, PrivateKey},_,_,_) ->
- private_key(init_private_key(Asn1Type, PrivateKey)).
-
-init_private_key(Asn1Type, PrivateKey) ->
- public_key:der_decode(Asn1Type, PrivateKey).
+init_private_key({Asn1Type, PrivateKey},_,_) ->
+ private_key(public_key:der_decode(Asn1Type, PrivateKey));
+init_private_key(undefined, CertKey, DbHandle) ->
+ case maps:get(keyfile, CertKey, undefined) of
+ undefined ->
+ #{};
+ KeyFile ->
+ Password = maps:get(password, CertKey, undefined),
+ try
+ {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle),
+ [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List,
+ PKey =:= 'RSAPrivateKey' orelse
+ PKey =:= 'DSAPrivateKey' orelse
+ PKey =:= 'ECPrivateKey' orelse
+ PKey =:= 'PrivateKeyInfo'
+ ],
+ private_key(public_key:pem_entry_decode(PemEntry, Password))
+ catch
+ _:Reason ->
+ file_error(KeyFile, {keyfile, Reason})
+ end
+ end.
private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
#'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'rsaEncryption'},
@@ -370,27 +324,35 @@ file_error(File, Throw) ->
throw(Throw)
end.
-init_diffie_hellman(_,Params, _,_) when is_binary(Params)->
- public_key:der_decode('DHParameter', Params);
-init_diffie_hellman(_,_,_, client) ->
+init_diffie_hellman(_, _, client) ->
undefined;
-init_diffie_hellman(_,_,undefined, _) ->
- ?DEFAULT_DIFFIE_HELLMAN_PARAMS;
-init_diffie_hellman(DbHandle,_, DHParamFile, server) ->
+init_diffie_hellman(DbHandle, Opts, server) ->
+ case maps:get(dh, Opts, undefined) of
+ Bin when is_binary(Bin) ->
+ public_key:der_decode('DHParameter', Bin);
+ _ ->
+ case maps:get(dh, Opts, undefined) of
+ undefined ->
+ ?DEFAULT_DIFFIE_HELLMAN_PARAMS;
+ DHParamFile ->
+ dh_file(DbHandle, DHParamFile)
+ end
+ end.
+
+dh_file(DbHandle, DHParamFile) ->
try
- {ok, List} = ssl_manager:cache_pem_file(DHParamFile,DbHandle),
- case [Entry || Entry = {'DHParameter', _ , _} <- List] of
- [Entry] ->
- public_key:pem_entry_decode(Entry);
- [] ->
- ?DEFAULT_DIFFIE_HELLMAN_PARAMS
- end
+ {ok, List} = ssl_manager:cache_pem_file(DHParamFile,DbHandle),
+ case [Entry || Entry = {'DHParameter', _ , _} <- List] of
+ [Entry] ->
+ public_key:pem_entry_decode(Entry);
+ [] ->
+ ?DEFAULT_DIFFIE_HELLMAN_PARAMS
+ end
catch
- _:Reason ->
- file_error(DHParamFile, {dhfile, Reason})
+ _:Reason ->
+ file_error(DHParamFile, {dhfile, Reason})
end.
-
session_cb_init_args(client) ->
case application:get_env(ssl, client_session_cb_init_args) of
undefined ->
diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl
index eed0025ad7..12a4721a09 100644
--- a/lib/ssl/src/ssl_gen_statem.erl
+++ b/lib/ssl/src/ssl_gen_statem.erl
@@ -38,7 +38,8 @@
init/1]).
%% TLS connection setup
--export([ssl_config/3,
+-export([init_ssl_config/3,
+ ssl_config/3,
connect/8,
handshake/7,
handshake/2,
@@ -60,7 +61,8 @@
set_opts/2,
peer_certificate/1,
negotiated_protocol/1,
- connection_information/2
+ connection_information/2,
+ ktls_handover/1
]).
%% Erlang Distribution export
@@ -96,6 +98,9 @@
%% Log handling
-export([format_status/2]).
+%% Tracing
+-export([handle_trace/3]).
+
%%--------------------------------------------------------------------
%%% Initial Erlang process setup
%%--------------------------------------------------------------------
@@ -106,7 +111,8 @@
%% Description: Creates a process which calls Module:init/1 to
%% choose appropriat gen_statem and initialize.
%%--------------------------------------------------------------------
-start_link(Role, Sender, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverOpts}, _, _} = Options, User, CbInfo) ->
+start_link(Role, Sender, Host, Port, Socket, {SslOpts, _, _} = Options, User, CbInfo) ->
+ ReceiverOpts = maps:get(receiver_spawn_opts, SslOpts, []),
Opts = [link | proplists:delete(link, ReceiverOpts)],
Pid = proc_lib:spawn_opt(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]], Opts),
{ok, Pid}.
@@ -118,7 +124,8 @@ start_link(Role, Sender, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverO
%% Description: Creates a gen_statem process which calls Module:init/1 to
%% initialize.
%%--------------------------------------------------------------------
-start_link(Role, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverOpts}, _, _} = Options, User, CbInfo) ->
+start_link(Role, Host, Port, Socket, {SslOpts, _, _} = Options, User, CbInfo) ->
+ ReceiverOpts = maps:get(receiver_spawn_opts, SslOpts, []),
Opts = [link | proplists:delete(link, ReceiverOpts)],
Pid = proc_lib:spawn_opt(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]], Opts),
{ok, Pid}.
@@ -128,30 +135,51 @@ start_link(Role, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverOpts}, _,
-spec init(list()) -> no_return().
%% Description: Initialization
%%--------------------------------------------------------------------
-init([_Role, _Sender, _Host, _Port, _Socket, {#{erl_dist := ErlDist} = TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
+init([Role, _Sender, _Host, _Port, _Socket, {TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
process_flag(trap_exit, true),
- case ErlDist of
+ case maps:get(erl_dist, TLSOpts, false) of
true ->
process_flag(priority, max);
_ ->
ok
end,
- ConnectionFsm = tls_connection_fsm(TLSOpts),
- ConnectionFsm:init(InitArgs);
-init([_Role, _Host, _Port, _Socket, {TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
+ case {Role, TLSOpts} of
+ {?CLIENT_ROLE, #{versions := [?TLS_1_3]}} ->
+ tls_client_connection_1_3:init(InitArgs);
+ {?SERVER_ROLE, #{versions := [?TLS_1_3]}} ->
+ tls_server_connection_1_3:init(InitArgs);
+ {_,_} ->
+ tls_connection:init(InitArgs)
+ end;
+init([_Role, _Host, _Port, _Socket, _TLSOpts, _User, _CbInfo] = InitArgs) ->
process_flag(trap_exit, true),
- ConnectionFsm = dtls_connection_fsm(TLSOpts),
- ConnectionFsm:init(InitArgs).
+ dtls_connection:init(InitArgs).
%%====================================================================
%% TLS connection setup
%%====================================================================
%%--------------------------------------------------------------------
+-spec init_ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
+%%--------------------------------------------------------------------
+init_ssl_config(Opts, Role, #state{ssl_options = #{handshake := Handshake},
+ handshake_env = HsEnv} = State0) ->
+ ContinueStatus = case Handshake of
+ hello ->
+ %% Will pause handshake after hello message to
+ %% enable user to react to hello extensions
+ pause;
+ full ->
+ Handshake
+ end,
+ ssl_config(Opts, Role,
+ State0#state{handshake_env =
+ HsEnv#handshake_env{continue_status = ContinueStatus}}).
+
+%%--------------------------------------------------------------------
-spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
%%--------------------------------------------------------------------
ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
- ssl_options = #{handshake := Handshake},
handshake_env = HsEnv,
connection_env = CEnv} = State0) ->
{ok, #{cert_db_ref := Ref,
@@ -165,15 +193,6 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
TimeStamp = erlang:monotonic_time(),
Session = State0#state.session,
- ContinueStatus = case Handshake of
- hello ->
- %% Will pause handshake after hello message to
- %% enable user to react to hello extensions
- pause;
- full ->
- Handshake
- end,
-
State0#state{session = Session#session{time_stamp = TimeStamp},
static_env = InitStatEnv0#static_env{
file_ref_db = FileRefHandle,
@@ -182,8 +201,8 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
crl_db = CRLDbHandle,
session_cache = CacheHandle
},
- handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams,
- continue_status = ContinueStatus},
+ handshake_env =
+ HsEnv#handshake_env{diffie_hellman_params = DHParams},
connection_env = CEnv#connection_env{cert_key_alts = CertKeyAlts},
ssl_options = Opts}.
@@ -292,21 +311,23 @@ socket_control(Connection, Socket, Pid, Transport) ->
-spec socket_control(tls_gen_connection | dtls_gen_connection, port(), [pid()], atom(), [pid()] | atom()) ->
{ok, #sslsocket{}} | {error, reason()}.
%%--------------------------------------------------------------------
-socket_control(dtls_gen_connection = Connection, Socket, Pids, Transport, udp_listener) ->
+socket_control(dtls_gen_connection, Socket, Pids, Transport, udp_listener) ->
%% dtls listener process must have the socket control
- {ok, Connection:socket(Pids, Transport, Socket, undefined)};
+ {ok, dtls_gen_connection:socket(Pids, Transport, Socket, undefined)};
-socket_control(tls_gen_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
+socket_control(tls_gen_connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
+ {ok, tls_gen_connection:socket(Pids, Transport, Socket, Trackers)};
{error, Reason} ->
{error, Reason}
end;
-socket_control(dtls_gen_connection = Connection, {PeerAddrPort, Socket}, [Pid|_] = Pids, Transport, Trackers) ->
+socket_control(dtls_gen_connection, {PeerAddrPort, Socket},
+ [Pid|_] = Pids, Transport, Trackers) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, Connection:socket(Pids, Transport, {PeerAddrPort, Socket}, Trackers)};
+ {ok, dtls_gen_connection:socket(Pids, Transport, {PeerAddrPort, Socket},
+ Trackers)};
{error, Reason} ->
{error, Reason}
end.
@@ -420,6 +441,14 @@ peer_certificate(ConnectionPid) ->
negotiated_protocol(ConnectionPid) ->
call(ConnectionPid, negotiated_protocol).
+%%--------------------------------------------------------------------
+-spec ktls_handover(pid()) -> {ok, map()} | {error, reason()}.
+%%
+%% Description: Returns the negotiated protocol
+%%--------------------------------------------------------------------
+ktls_handover(ConnectionPid) ->
+ call(ConnectionPid, ktls_handover).
+
dist_handshake_complete(ConnectionPid, DHandle) ->
gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}).
@@ -460,8 +489,6 @@ initial_hello({call, From}, {start, Timeout},
%% Versions is a descending list of supported versions.
versions := [HelloVersion|_] = Versions,
session_tickets := SessionTickets,
- ocsp_stapling := OcspStaplingOpt,
- ocsp_nonce := OcspNonceOpt,
early_data := EarlyData} = SslOpts,
session = Session,
connection_states = ConnectionStates0
@@ -471,9 +498,9 @@ initial_hello({call, From}, {start, Timeout},
%% Update UseTicket in case of automatic session resumption. The automatic ticket handling
%% also takes it into account if the ticket is suitable for sending early data not exceeding
%% the max_early_data_size or if it can only be used for session resumption.
- {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0),
+ {UseTicket, State1} = tls_client_connection_1_3:maybe_automatic_session_resumption(State0),
TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
- OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
+ OcspNonce = tls_handshake:ocsp_nonce(SslOpts),
Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Session#session.session_id,
Renegotiation,
@@ -507,21 +534,24 @@ initial_hello({call, From}, {start, Timeout},
%% ServerHello is processed.
RequestedVersion = tls_record:hello_version(Versions),
- {Ref,Maybe} = tls_handshake_1_3:maybe(),
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
try
%% Send Early Data
- State4 = Maybe(tls_handshake_1_3:maybe_send_early_data(State3)),
+ State4 = Maybe(tls_client_connection_1_3:maybe_send_early_data(State3)),
{#state{handshake_env = HsEnv1} = State5, _} =
Connection:send_handshake_flight(State4),
+ OcspStaplingKeyPresent = maps:is_key(ocsp_stapling, SslOpts),
State = State5#state{
connection_env = CEnv#connection_env{
negotiated_version = RequestedVersion},
session = Session,
- handshake_env = HsEnv1#handshake_env{
- ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce,
- ocsp_stapling => OcspStaplingOpt}},
+ handshake_env =
+ HsEnv1#handshake_env{
+ ocsp_stapling_state =
+ OcspState0#{ocsp_nonce => OcspNonce,
+ ocsp_stapling => OcspStaplingKeyPresent}},
start_or_recv_from = From,
key_share = KeyShare},
NextState = next_statem_state(Versions, Role),
@@ -534,17 +564,17 @@ initial_hello({call, From}, {start, Timeout},
initial_hello({call, From}, {start, Timeout}, #state{static_env = #static_env{role = Role,
protocol_cb = Connection},
ssl_options = #{versions := Versions}} = State0) ->
-
- NextState = next_statem_state(Versions, Role),
+
+ NextState = next_statem_state(Versions, Role),
Connection:next_event(NextState, no_record, State0#state{start_or_recv_from = From},
[{{timeout, handshake}, Timeout, close}]);
-
+
initial_hello({call, From}, {start, {Opts, EmOpts}, Timeout},
#state{static_env = #static_env{role = Role},
ssl_options = OrigSSLOptions,
socket_options = SockOpts} = State0) ->
try
- SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions),
+ SslOpts = ssl:update_options(Opts, Role, OrigSSLOptions),
State = ssl_config(SslOpts, Role, State0),
initial_hello({call, From}, {start, Timeout},
State#state{ssl_options = SslOpts,
@@ -646,6 +676,45 @@ connection({call, From},
{error, timeout} ->
{stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
end;
+connection({call, From}, ktls_handover, #state{
+ static_env = #static_env{
+ transport_cb = Transport,
+ socket = Socket
+ },
+ connection_env = #connection_env{
+ user_application = {_Mon, Pid},
+ negotiated_version = TlsVersion
+ },
+ ssl_options = #{ktls := true},
+ socket_options = SocketOpts,
+ connection_states = #{
+ current_write := #{
+ security_parameters := #security_parameters{cipher_suite = CipherSuite},
+ cipher_state := WriteState,
+ sequence_number := WriteSeq
+ },
+ current_read := #{
+ cipher_state := ReadState,
+ sequence_number := ReadSeq
+ }
+ }
+}) ->
+ Reply = case Transport:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, #{
+ socket => Socket,
+ tls_version => TlsVersion,
+ cipher_suite => CipherSuite,
+ socket_options => SocketOpts,
+ write_state => WriteState,
+ write_seq => WriteSeq,
+ read_state => ReadState,
+ read_seq => ReadSeq
+ }};
+ {error, Reason} ->
+ {error, Reason}
+ end,
+ {stop_and_reply, {shutdown, ktls}, [{reply, From, Reply}]};
connection({call, From}, Msg, State) ->
handle_call(Msg, From, ?FUNCTION_NAME, State);
connection(cast, {dist_handshake_complete, DHandle},
@@ -953,8 +1022,9 @@ passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear},
%%====================================================================
hibernate_after(connection = StateName,
- #state{ssl_options= #{hibernate_after := HibernateAfter}} = State,
+ #state{ssl_options= SslOpts} = State,
Actions) ->
+ HibernateAfter = maps:get(hibernate_after, SslOpts, infinity),
{next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
hibernate_after(StateName, State, Actions) ->
{next_state, StateName, State, Actions}.
@@ -1006,26 +1076,8 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role =
Pids = Connection:pids(State),
alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
-handle_alert(#alert{level = ?FATAL} = Alert0, StateName,
- #state{static_env = #static_env{role = Role,
- socket = Socket,
- host = Host,
- port = Port,
- trackers = Trackers,
- transport_cb = Transport,
- protocol_cb = Connection},
- connection_env = #connection_env{user_application = {_Mon, Pid}},
- ssl_options = #{log_level := LogLevel},
- start_or_recv_from = From,
- session = Session,
- socket_options = Opts} = State) ->
- invalidate_session(Role, Host, Port, Session),
- Alert = Alert0#alert{role = opposite_role(Role)},
- log_alert(LogLevel, Role, Connection:protocol_name(),
- StateName, Alert),
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
- {stop, {shutdown, normal}, State};
+handle_alert(#alert{level = ?FATAL} = Alert, StateName, State) ->
+ handle_fatal_alert(Alert, StateName, State);
handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
downgrade= StateName, State) ->
{next_state, StateName, State, [{next_event, internal, Alert}]};
@@ -1092,27 +1144,61 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert,
%% Go back to connection!
State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}),
Connection:next_event(connection, no_record, State);
+handle_alert(#alert{level = ?WARNING, description = ?USER_CANCELED} = Alert, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = #{log_level := LogLevel}} = State) when StateName =/= connection ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName,
+ Alert#alert{role = opposite_role(Role)}),
+ %% Wait for close alert that should follow or handshake timeout
+ Connection:next_event(StateName, no_record, State);
%% Gracefully log and ignore all other warning alerts pre TLS-1.3
handle_alert(#alert{level = ?WARNING} = Alert, StateName,
#state{static_env = #static_env{role = Role,
protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{log_level := LogLevel}} = State) when Version < {3,4} ->
+ ssl_options = #{log_level := LogLevel}} = State) when ?TLS_LT(Version, ?TLS_1_3) ->
log_alert(LogLevel, Role,
Connection:protocol_name(), StateName,
Alert#alert{role = opposite_role(Role)}),
Connection:next_event(StateName, no_record, State);
-handle_alert(Alert0, StateName, State) ->
+handle_alert(Alert, StateName, State) ->
%% In TLS-1.3 all error alerts are fatal not matter of legacy level
- handle_alert(Alert0#alert{level = ?FATAL}, StateName, State).
+ %% but keep the level for the log so that users looking at what is
+ %% sent and what is logged are not confused! Or if some one sends
+ %% user cancel alert in connection which is inappropriate!
+ handle_fatal_alert(Alert, StateName, State).
+
+handle_fatal_alert(Alert0, StateName,
+ #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ host = Host,
+ port = Port,
+ trackers = Trackers,
+ transport_cb = Transport,
+ protocol_cb = Connection},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
+ ssl_options = #{log_level := LogLevel},
+ start_or_recv_from = From,
+ session = Session,
+ socket_options = Opts} = State) ->
+ invalidate_session(Role, Host, Port, Session),
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ log_alert(LogLevel, Role, Connection:protocol_name(),
+ StateName, Alert),
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
+ {stop, {shutdown, normal}, State}.
-handle_trusted_certs_db(#state{ssl_options =
- #{cacertfile := <<>>, cacerts := []}}) ->
+handle_trusted_certs_db(#state{ssl_options =#{cacerts := []} = Opts})
+ when not is_map_key(cacertfile, Opts) ->
%% No trusted certs specified
ok;
handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
cert_db = CertDb},
- ssl_options = #{cacertfile := <<>>}}) when CertDb =/= undefined ->
+ ssl_options = Opts})
+ when CertDb =/= undefined, not is_map_key(cacertfile, Opts) ->
%% Certs provided as DER directly can not be shared
%% with other connections and it is safe to delete them when the connection ends.
ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
@@ -1130,9 +1216,9 @@ handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
ok
end.
-maybe_invalidate_session({3, 4},_, _, _, _, _) ->
+maybe_invalidate_session(?TLS_1_3,_, _, _, _, _) ->
ok;
-maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 ->
+maybe_invalidate_session(Version, Type, Role, Host, Port, Session) when ?TLS_LT(Version, ?TLS_1_3) ->
maybe_invalidate_session(Type, Role, Host, Port, Session).
maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) ->
@@ -1140,6 +1226,9 @@ maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) ->
maybe_invalidate_session(_, _, _, _, _) ->
ok.
+terminate({shutdown, ktls}, connection, State) ->
+ %% Socket shall not be closed as it should be returned to user
+ handle_trusted_certs_db(State);
terminate({shutdown, downgrade}, downgrade, State) ->
%% Socket shall not be closed as it should be returned to user
handle_trusted_certs_db(State);
@@ -1191,10 +1280,9 @@ format_status(normal, [_, StateName, State]) ->
[{data, [{"State", {StateName, State}}]}];
format_status(terminate, [_, StateName, State]) ->
SslOptions = (State#state.ssl_options),
- NewOptions = SslOptions#{password => ?SECRET_PRINTOUT,
- cert => ?SECRET_PRINTOUT,
+ NewOptions = SslOptions#{
+ certs_keys => ?SECRET_PRINTOUT,
cacerts => ?SECRET_PRINTOUT,
- key => ?SECRET_PRINTOUT,
dh => ?SECRET_PRINTOUT,
psk_identity => ?SECRET_PRINTOUT,
srp_identity => ?SECRET_PRINTOUT},
@@ -1210,24 +1298,16 @@ format_status(terminate, [_, StateName, State]) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-tls_connection_fsm(#{versions := [{3,4}]}) ->
- tls_connection_1_3;
-tls_connection_fsm(_) ->
- tls_connection.
-
-dtls_connection_fsm(_) ->
- dtls_connection.
-
next_statem_state([Version], client) ->
case ssl:tls_version(Version) of
- {3,4} ->
+ ?TLS_1_3 ->
wait_sh;
_ ->
hello
end;
next_statem_state([Version], server) ->
case ssl:tls_version(Version) of
- {3,4} ->
+ ?TLS_1_3 ->
start;
_ ->
hello
@@ -1276,11 +1356,8 @@ is_sni_value(Hostname) ->
true
end.
-is_hostname_recognized(#{sni_fun := undefined,
- sni_hosts := SNIHosts}, Hostname) ->
- proplists:is_defined(Hostname, SNIHosts);
-is_hostname_recognized(_, _) ->
- true.
+is_hostname_recognized(#{sni_fun := Fun}, Hostname) ->
+ Fun(Hostname) =:= undefined.
handle_sni_hostname(Hostname,
#state{static_env = #static_env{role = Role} = InitStatEnv0,
@@ -1290,7 +1367,7 @@ handle_sni_hostname(Hostname,
NewOptions = update_ssl_options_from_sni(Opts, Hostname),
case NewOptions of
undefined ->
- case maps:get(server_name_indication, Opts) of
+ case maps:get(server_name_indication, Opts, undefined) of
disable when Role == client->
State0;
_ ->
@@ -1320,23 +1397,14 @@ handle_sni_hostname(Hostname,
}
end.
-update_ssl_options_from_sni(#{sni_fun := SNIFun,
- sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) ->
- SSLOptions =
- case SNIFun of
- undefined ->
- proplists:get_value(SNIHostname,
- SNIHosts);
- SNIFun ->
- SNIFun(SNIHostname)
- end,
- case SSLOptions of
+update_ssl_options_from_sni(#{sni_fun := SNIFun} = OrigSSLOptions, SNIHostname) ->
+ case SNIFun(SNIHostname) of
undefined ->
undefined;
- _ ->
+ SSLOptions ->
VersionsOpt = proplists:get_value(versions, SSLOptions, []),
FallBackOptions = filter_for_versions(VersionsOpt, OrigSSLOptions),
- ssl:handle_options(SSLOptions, server, FallBackOptions)
+ ssl:update_options(SSLOptions, server, FallBackOptions)
end.
filter_for_versions([], OrigSSLOptions) ->
@@ -1897,7 +1965,7 @@ connection_info(#state{handshake_env = #handshake_env{sni_hostname = SNIHostname
security_info(#state{connection_states = ConnectionStates,
static_env = #static_env{role = Role},
- ssl_options = #{keep_secrets := KeepSecrets},
+ ssl_options = Opts,
protocol_specific = ProtocolSpecific}) ->
ReadState = ssl_record:current_connection_state(ConnectionStates, read),
#{security_parameters :=
@@ -1908,6 +1976,8 @@ security_info(#state{connection_states = ConnectionStates,
client_early_data_secret = ServerEarlyData
}} = ReadState,
BaseSecurityInfo = [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}],
+
+ KeepSecrets = maps:get(keep_secrets, Opts, false),
if KeepSecrets =/= true ->
BaseSecurityInfo;
true ->
@@ -2175,12 +2245,37 @@ keylog_secret(SecretBin, sha384) ->
keylog_secret(SecretBin, sha512) ->
io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]).
-maybe_generate_client_shares(#{versions := [Version|_],
+maybe_generate_client_shares(#{versions := [?TLS_1_3|_],
supported_groups :=
#supported_groups{
- supported_groups = [Group|_]}})
- when Version =:= {3,4} ->
+ supported_groups = [Group|_]}}) ->
%% Generate only key_share entry for the most preferred group
ssl_cipher:generate_client_shares([Group]);
maybe_generate_client_shares(_) ->
undefined.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(api,
+ {call, {?MODULE, connect, [Connection | _]}}, Stack0) ->
+ {io_lib:format("Connection = ~w", [Connection]), Stack0};
+handle_trace(rle,
+ {call, {?MODULE, init, Args = [[Role | _]]}}, Stack0) ->
+ {io_lib:format("(*~w) Args = ~W", [Role, Args, 3]), [{role, Role} | Stack0]};
+handle_trace(hbn,
+ {call, {?MODULE, hibernate_after,
+ [_StateName = connection, State, Actions]}},
+ Stack) ->
+ #state{ssl_options= #{hibernate_after := HibernateAfter}} = State,
+ {io_lib:format("* * * maybe hibernating in ~w ms * * * Actions = ~W ",
+ [HibernateAfter, Actions, 10]), Stack};
+handle_trace(hbn,
+ {return_from, {?MODULE, hibernate_after, 3},
+ {Cmd, Arg,_State, Actions}},
+ Stack) ->
+ {io_lib:format("Cmd = ~w Arg = ~w Actions = ~W", [Cmd, Arg, Actions, 10]), Stack};
+handle_trace(hbn,
+ {call, {?MODULE, handle_common_event, [timeout, hibernate, connection | _]}}, Stack) ->
+ {io_lib:format("* * * hibernating * * *", []), Stack}.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 56a1ca81b9..fb30372999 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
%%----------------------------------------------------------------------
-module(ssl_handshake).
+-feature(maybe_expr,enable).
-include("ssl_handshake.hrl").
-include("ssl_record.hrl").
@@ -59,7 +60,7 @@
]).
%% Encode
--export([encode_handshake/2, encode_hello_extensions/2, encode_extensions/1, encode_extensions/2,
+-export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2,
encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]).
%% Decode
-export([decode_handshake/3, decode_vector/1, decode_hello_extensions/4, decode_extensions/3,
@@ -69,13 +70,13 @@
%% Cipher suites handling
-export([available_suites/2, available_signature_algs/2, available_signature_algs/3,
- cipher_suites/3, prf/6, select_session/9, supported_ecc/1,
+ cipher_suites/3, prf/6, select_session/9,
premaster_secret/2, premaster_secret/3, premaster_secret/4]).
%% Extensions handling
-export([client_hello_extensions/10,
handle_client_hello_extensions/10, %% Returns server hello extensions
- handle_server_hello_extensions/10, select_curve/3, select_curve/4,
+ handle_server_hello_extensions/10, select_curve/2, select_curve/3,
select_hashsign/4, select_hashsign/5,
select_hashsign_algs/3, empty_extensions/2, add_server_share/3,
add_alpn/2, add_selected_version/1, decode_alpn/1, max_frag_enum/1
@@ -84,11 +85,12 @@
-export([get_cert_params/1,
select_own_cert/1,
server_name/3,
- path_validate/9,
path_validation/10,
validation_fun_and_state/4,
path_validation_alert/1]).
+%% Tracing
+-export([handle_trace/3]).
%%====================================================================
%% Create handshake messages
%%====================================================================
@@ -337,10 +339,9 @@ next_protocol(SelectedProtocol) ->
%% Description: Handles a certificate handshake message
%%--------------------------------------------------------------------
certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
- #{server_name_indication := ServerNameIndication,
- partial_chain := PartialChain} = SSlOptions,
+ #{partial_chain := PartialChain} = SSlOptions,
CRLDbHandle, Role, Host, Version, CertExt) ->
- ServerName = server_name(ServerNameIndication, Host, Role),
+ ServerName = server_name(SSlOptions, Host, Role),
[PeerCert | _ChainCerts ] = ASN1Certs,
try
PathsAndAnchors =
@@ -391,26 +392,29 @@ verify_signature(_, Msg, {HashAlgo, SignAlgo}, Signature,
SignAlgo == rsa_pss_pss ->
Options = verify_options(SignAlgo, HashAlgo, PubKeyParams),
public_key:verify(Msg, HashAlgo, Signature, PubKey, Options);
-verify_signature({3, Minor}, Msg, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, PubKeyParams})
- when Minor >= 3 ->
+verify_signature(Version, Msg, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, PubKeyParams})
+ when ?TLS_GTE(Version, ?TLS_1_2) ->
Options = verify_options(SignAlgo, HashAlgo, PubKeyParams),
public_key:verify(Msg, HashAlgo, Signature, PubKey, Options);
-verify_signature({3, Minor}, {digest, Digest}, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) when Minor =< 2 ->
+verify_signature(Version, {digest, Digest}, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams})
+ when ?TLS_LTE(Version, ?TLS_1_1) ->
case public_key:decrypt_public(Signature, PubKey,
[{rsa_pad, rsa_pkcs1_padding}]) of
Digest -> true;
_ -> false
end;
-verify_signature({3, 4}, Msg, {_, eddsa}, Signature, {?'id-Ed25519', PubKey, PubKeyParams}) ->
+verify_signature(?TLS_1_3, Msg, {_, eddsa}, Signature, {?'id-Ed25519', PubKey, PubKeyParams}) ->
public_key:verify(Msg, none, Signature, {PubKey, PubKeyParams});
-verify_signature({3, 4}, Msg, {_, eddsa}, Signature, {?'id-Ed448', PubKey, PubKeyParams}) ->
+verify_signature(?TLS_1_3, Msg, {_, eddsa}, Signature, {?'id-Ed448', PubKey, PubKeyParams}) ->
public_key:verify(Msg, none, Signature, {PubKey, PubKeyParams});
verify_signature(_, Msg, {HashAlgo, _SignAlg}, Signature,
{?'id-ecPublicKey', PublicKey, PublicKeyParams}) ->
public_key:verify(Msg, HashAlgo, Signature, {PublicKey, PublicKeyParams});
-verify_signature({3, Minor}, _Msg, {_HashAlgo, anon}, _Signature, _) when Minor =< 3 ->
+verify_signature(Version, _Msg, {_HashAlgo, anon}, _Signature, _)
+ when ?TLS_1_X(Version), ?TLS_LTE(Version, ?TLS_1_2) ->
true;
-verify_signature({3, Minor}, Msg, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) when Minor =< 3->
+verify_signature(Version, Msg, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams})
+ when ?TLS_1_X(Version), ?TLS_LTE(Version, ?TLS_1_2) ->
public_key:verify(Msg, HashAlgo, Signature, {PublicKey, PublicKeyParams}).
%%--------------------------------------------------------------------
@@ -521,17 +525,13 @@ select_version(RecordCB, ClientVersion, Versions) ->
%% Called by TLS 1.2/1.3 Server when "supported_versions" is present
%% in ClientHello.
%% Input lists are ordered (highest first)
-select_supported_version([], _ServerVersions) ->
- undefined;
-select_supported_version([ClientVersion|T], ServerVersions) ->
- case lists:member(ClientVersion, ServerVersions) of
- true ->
- ClientVersion;
- false ->
- select_supported_version(T, ServerVersions)
+select_supported_version(ClientVersions, ServerVersions) ->
+ Fn = fun (ClientVersion) -> lists:member(ClientVersion, ServerVersions) end,
+ case lists:search(Fn, ClientVersions) of
+ {value, ClientVersion} -> ClientVersion;
+ false -> undefined
end.
-
%%====================================================================
%% Encode handshake
%%====================================================================
@@ -540,14 +540,15 @@ encode_handshake(#next_protocol{selected_protocol = SelectedProtocol}, _Version)
PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32),
{?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary,
?BYTE(PaddingLength), 0:(PaddingLength * 8)>>};
-encode_handshake(#server_hello{server_version = {Major, Minor} = Version,
+encode_handshake(#server_hello{server_version = ServerVersion,
random = Random,
session_id = Session_ID,
cipher_suite = CipherSuite,
compression_method = Comp_method,
extensions = Extensions}, _Version) ->
SID_length = byte_size(Session_ID),
- ExtensionsBin = encode_hello_extensions(Extensions, Version),
+ {Major,Minor} = ServerVersion,
+ ExtensionsBin = encode_hello_extensions(Extensions),
{?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID/binary,
CipherSuite/binary, ?BYTE(Comp_method), ExtensionsBin/binary>>};
@@ -564,7 +565,7 @@ encode_handshake(#server_key_params{params_bin = Keys, hashsign = HashSign,
encode_handshake(#certificate_request{certificate_types = CertTypes,
hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos},
certificate_authorities = CertAuths},
- {3,3}) ->
+ ?TLS_1_2) ->
HashSigns = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> ||
SignatureScheme <- HashSignAlgos >>,
EncCertAuths = encode_cert_auths(CertAuths),
@@ -588,17 +589,15 @@ encode_handshake(#certificate_request{certificate_types = CertTypes,
};
encode_handshake(#server_hello_done{}, _Version) ->
{?SERVER_HELLO_DONE, <<>>};
-encode_handshake(#client_key_exchange{exchange_keys = ExchangeKeys}, Version) ->
- {?CLIENT_KEY_EXCHANGE, encode_client_key(ExchangeKeys, Version)};
+encode_handshake(#client_key_exchange{exchange_keys = ExchangeKeys}, _Version) ->
+ {?CLIENT_KEY_EXCHANGE, encode_client_key(ExchangeKeys)};
encode_handshake(#certificate_verify{signature = BinSig, hashsign_algorithm = HashSign}, Version) ->
EncSig = enc_sign(HashSign, BinSig, Version),
{?CERTIFICATE_VERIFY, EncSig};
encode_handshake(#finished{verify_data = VerifyData}, _Version) ->
{?FINISHED, VerifyData}.
-encode_hello_extensions(_, {3, 0}) ->
- <<>>;
-encode_hello_extensions(Extensions, _) ->
+encode_hello_extensions(Extensions) ->
encode_extensions(hello_extensions_list(Extensions), <<>>).
encode_extensions(Exts) ->
@@ -695,6 +694,15 @@ encode_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
?BYTE(?SNI_NAMETYPE_HOST_NAME),
?UINT16(HostLen), HostnameBin/binary,
Acc/binary>>);
+encode_extensions([#use_srtp{protection_profiles = Profiles, mki = MKI} | Rest], Acc) ->
+ ProfilesBin = iolist_to_binary(Profiles),
+ ProfilesLength = byte_size(ProfilesBin),
+ MKILength = byte_size(MKI),
+ ExtLength = ProfilesLength + 2 + MKILength + 1,
+ encode_extensions(Rest, <<?UINT16(?USE_SRTP_EXT), ?UINT16(ExtLength),
+ ?UINT16(ProfilesLength), ProfilesBin/binary,
+ ?BYTE(MKILength), MKI/binary,
+ Acc/binary>>);
encode_extensions([#max_frag_enum{enum = MaxFragEnum} | Rest], Acc) ->
ExtLength = 1,
encode_extensions(Rest, <<?UINT16(?MAX_FRAGMENT_LENGTH_EXT), ?UINT16(ExtLength), ?BYTE(MaxFragEnum),
@@ -823,14 +831,12 @@ encode_protocols_advertised_on_server(Protocols) ->
extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
encode_cert_auths(Auths) ->
- encode_cert_auths(Auths, []).
-
-encode_cert_auths([], Acc) ->
- list_to_binary(lists:reverse(Acc));
-encode_cert_auths([Auth | Auths], Acc) ->
- DNEncodedBin = public_key:pkix_encode('Name', Auth, otp),
- DNEncodedLen = byte_size(DNEncodedBin),
- encode_cert_auths(Auths, [<<?UINT16(DNEncodedLen), DNEncodedBin/binary>> | Acc]).
+ DNEncode = fun (Auth) ->
+ DNEncodedBin = public_key:pkix_encode('Name', Auth, otp),
+ DNEncodedLen = byte_size(DNEncodedBin),
+ <<?UINT16(DNEncodedLen), DNEncodedBin/binary>>
+ end,
+ list_to_binary(lists:map(DNEncode, Auths)).
%%====================================================================
%% Decode handshake
@@ -879,7 +885,7 @@ decode_handshake(_Version, ?CERTIFICATE_STATUS, <<?BYTE(?CERTIFICATE_STATUS_TYPE
response = ASN1OcspResponse};
decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) ->
#server_key_exchange{exchange_keys = Keys};
-decode_handshake({3, 3} = Version, ?CERTIFICATE_REQUEST,
+decode_handshake(?TLS_1_2 = Version, ?CERTIFICATE_REQUEST,
<<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary,
?UINT16(HashSignsLen), HashSigns:HashSignsLen/binary,
?UINT16(CertAuthsLen), EncCertAuths:CertAuthsLen/binary>>) ->
@@ -894,9 +900,8 @@ decode_handshake(_Version, ?CERTIFICATE_REQUEST,
certificate_authorities = decode_cert_auths(EncCertAuths, [])};
decode_handshake(_Version, ?SERVER_HELLO_DONE, <<>>) ->
#server_hello_done{};
-decode_handshake({Major, Minor}, ?CERTIFICATE_VERIFY,<<HashSign:2/binary, ?UINT16(SignLen),
- Signature:SignLen/binary>>)
- when Major == 3, Minor >= 3 ->
+decode_handshake(?TLS_1_2, ?CERTIFICATE_VERIFY,<<HashSign:2/binary, ?UINT16(SignLen),
+ Signature:SignLen/binary>>) ->
#certificate_verify{hashsign_algorithm = dec_hashsign(HashSign), signature = Signature};
decode_handshake(_Version, ?CERTIFICATE_VERIFY,<<?UINT16(SignLen), Signature:SignLen/binary>>)->
#certificate_verify{signature = Signature};
@@ -1000,16 +1005,15 @@ available_suites(ServerCert, UserSuites, Version, undefined, Curve) ->
filter_unavailable_ecc_suites(Curve, Suites);
available_suites(ServerCert, UserSuites, Version, HashSigns, Curve) ->
Suites = available_suites(ServerCert, UserSuites, Version, undefined, Curve),
- filter_hashsigns(Suites, [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- Suites], HashSigns,
- Version, []).
+ filter_hashsigns(Suites, [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- Suites], HashSigns, Version).
available_signature_algs(undefined, _) ->
undefined;
-available_signature_algs(SupportedHashSigns, Version) when Version >= {3, 3} ->
+available_signature_algs(SupportedHashSigns, Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
case contains_scheme(SupportedHashSigns) of
true ->
case Version of
- {3,3} ->
+ ?TLS_1_2 ->
#hash_sign_algos{hash_sign_algos = ssl_cipher:signature_schemes_1_2(SupportedHashSigns)};
_ ->
#signature_algorithms{signature_scheme_list = SupportedHashSigns}
@@ -1021,12 +1025,12 @@ available_signature_algs(_, _) ->
undefined.
available_signature_algs(undefined, SupportedHashSigns, Version) when
- Version >= {3,3} ->
+ ?TLS_GTE(Version, ?TLS_1_2) ->
SupportedHashSigns;
available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns0,
- Version) when Version >= {3,3} ->
+ Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
SupportedHashSigns =
- case (Version == {3,3}) andalso contains_scheme(SupportedHashSigns0) of
+ case (Version == ?TLS_1_2) andalso contains_scheme(SupportedHashSigns0) of
true ->
ssl_cipher:signature_schemes_1_2(SupportedHashSigns0);
false ->
@@ -1037,18 +1041,15 @@ available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, Su
available_signature_algs(_, _, _) ->
undefined.
-contains_scheme([]) ->
- false;
-contains_scheme([Scheme | _]) when is_atom(Scheme) ->
- true;
-contains_scheme([_| Rest]) ->
- contains_scheme(Rest).
+contains_scheme(Schemes) ->
+ lists:any(fun erlang:is_atom/1, Schemes).
cipher_suites(Suites, Renegotiation, true) ->
%% TLS_FALLBACK_SCSV should be placed last -RFC7507
cipher_suites(Suites, Renegotiation) ++ [?TLS_FALLBACK_SCSV];
cipher_suites(Suites, Renegotiation, false) ->
cipher_suites(Suites, Renegotiation).
+
cipher_suites(Suites, false) ->
[?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites];
cipher_suites(Suites, true) ->
@@ -1059,7 +1060,8 @@ cipher_suites(Suites, true) ->
%%
%% Description: use the TLS PRF to generate key material
%%--------------------------------------------------------------------
-prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) ->
+prf(Version, PRFAlgo, Secret, Label, Seed, WantedLength)
+ when ?TLS_1_X(Version)->
{ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}.
select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, Session0, Version, SslOpts, CertKeyAlts) ->
@@ -1121,8 +1123,9 @@ server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, cert
end
end.
-is_acceptable_cert(Cert, HashSigns, {Major, Minor}) when Major == 3,
- Minor >= 3 ->
+is_acceptable_cert(Cert, HashSigns, Version)
+ when ?TLS_1_X(Version),
+ ?TLS_GTE(Version, ?TLS_1_2) ->
{SignAlgo0, Param, _, _, _} = get_cert_params(Cert),
SignAlgo = sign_algo(SignAlgo0, Param),
is_acceptable_hash_sign(SignAlgo, HashSigns);
@@ -1130,12 +1133,6 @@ is_acceptable_cert(_,_,_) ->
%% Not negotiable pre TLS-1.2. So if cert is available for version it is acceptable
true.
-supported_ecc({Major, Minor}) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) ->
- Curves = tls_v1:ecc_curves(Minor),
- #elliptic_curves{elliptic_curve_list = Curves};
-supported_ecc(_) ->
- #elliptic_curves{elliptic_curve_list = []}.
-
premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) ->
try
public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params)
@@ -1242,12 +1239,11 @@ client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renego
add_tls12_extensions(_Version,
#{alpn_advertised_protocols := AlpnAdvertisedProtocols,
- next_protocol_selector := NextProtocolSelector,
- server_name_indication := ServerNameIndication,
- max_fragment_length := MaxFragmentLength} = SslOpts,
+ max_fragment_length := MaxFragmentLength} = SslOpts,
ConnectionStates,
Renegotiation) ->
SRP = srp_user(SslOpts),
+ NextProtocolSelector = maps:get(next_protocol_selector, SslOpts, undefined),
#{renegotiation_info => renegotiation_info(tls_record, client,
ConnectionStates, Renegotiation),
srp => SRP,
@@ -1255,12 +1251,13 @@ add_tls12_extensions(_Version,
next_protocol_negotiation =>
encode_client_protocol_negotiation(NextProtocolSelector,
Renegotiation),
- sni => sni(ServerNameIndication),
+ sni => sni(SslOpts),
+ use_srtp => use_srtp_ext(SslOpts),
max_frag_enum => max_frag_enum(MaxFragmentLength)
}.
-add_common_extensions({3,4},
+add_common_extensions(?TLS_1_3,
HelloExtensions,
_CipherSuites,
#{eccs := SupportedECCs,
@@ -1297,10 +1294,9 @@ add_common_extensions(Version,
signature_algs_cert =>
signature_algs_cert(SignatureCertSchemes)}.
-maybe_add_tls13_extensions({3,4},
+maybe_add_tls13_extensions(?TLS_1_3,
HelloExtensions0,
- #{versions := SupportedVersions,
- certificate_authorities := Bool},
+ #{versions := SupportedVersions} = Opts,
KeyShare,
TicketData, CertDbHandle, CertDbRef) ->
HelloExtensions1 =
@@ -1308,18 +1304,15 @@ maybe_add_tls13_extensions({3,4},
#client_hello_versions{versions = SupportedVersions}},
HelloExtensions2 = maybe_add_key_share(HelloExtensions1, KeyShare),
HelloExtensions = maybe_add_pre_shared_key(HelloExtensions2, TicketData),
- maybe_add_certificate_auths(HelloExtensions, CertDbHandle, CertDbRef, Bool);
+ AddCA = maps:get(certificate_authorities, Opts, false),
+ maybe_add_certificate_auths(HelloExtensions, CertDbHandle, CertDbRef, AddCA);
maybe_add_tls13_extensions(_, HelloExtensions, _, _, _, _,_) ->
HelloExtensions.
-maybe_add_certificate_status_request(
- _Version, #{ocsp_stapling := false}, _OcspNonce, HelloExtensions) ->
- HelloExtensions;
-maybe_add_certificate_status_request(
- _Version, #{ocsp_stapling := true,
- ocsp_responder_certs := OcspResponderCerts},
- OcspNonce, HelloExtensions) ->
+maybe_add_certificate_status_request(_Version, #{ocsp_stapling := OcspStapling},
+ OcspNonce, HelloExtensions) ->
+ OcspResponderCerts = maps:get(ocsp_responder_certs, OcspStapling),
OcspResponderList = get_ocsp_responder_list(OcspResponderCerts),
OcspRequestExtns = public_key:ocsp_extensions(OcspNonce),
Req = #ocsp_status_request{responder_id_list = OcspResponderList,
@@ -1328,16 +1321,13 @@ maybe_add_certificate_status_request(
status_type = ?CERTIFICATE_STATUS_TYPE_OCSP,
request = Req
},
- HelloExtensions#{status_request => CertStatusReqExtn}.
+ HelloExtensions#{status_request => CertStatusReqExtn};
+maybe_add_certificate_status_request(_Version, _SslOpts, _OcspNonce,
+ HelloExtensions) ->
+ HelloExtensions.
get_ocsp_responder_list(ResponderCerts) ->
- get_ocsp_responder_list(ResponderCerts, []).
-
-get_ocsp_responder_list([], Acc) ->
- Acc;
-get_ocsp_responder_list([ResponderCert | T], Acc) ->
- get_ocsp_responder_list(
- T, [public_key:ocsp_responder_id(ResponderCert) | Acc]).
+ lists:map(fun public_key:ocsp_responder_id/1, ResponderCerts).
%% TODO: Add support for PSK key establishment
@@ -1441,7 +1431,7 @@ add_alpn(Extensions, ALPN0) ->
Extensions#{alpn => ALPN}.
add_selected_version(Extensions) ->
- SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ SupportedVersions = #server_hello_selected_version{selected_version = ?TLS_1_3},
Extensions#{server_hello_selected_version => SupportedVersions}.
kse_remove_private_key(#key_share_entry{
@@ -1472,6 +1462,13 @@ signature_algs_cert(undefined) ->
signature_algs_cert(SignatureSchemes) ->
#signature_algorithms_cert{signature_scheme_list = SignatureSchemes}.
+
+use_srtp_ext(#{use_srtp := #{protection_profiles := Profiles, mki := MKI}}) ->
+ #use_srtp{protection_profiles = Profiles, mki = MKI};
+use_srtp_ext(#{}) ->
+ undefined.
+
+
handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
Exts, Version,
#{secure_renegotiate := SecureRenegotation,
@@ -1498,6 +1495,7 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
ConnectionStates, Renegotiation),
ec_point_formats => server_ecc_extension(Version,
maps:get(ec_point_formats, Exts, undefined)),
+ use_srtp => use_srtp_ext(Opts),
max_frag_enum => ServerMaxFragEnum
},
@@ -1519,9 +1517,8 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
Exts, Version,
- #{secure_renegotiate := SecureRenegotation,
- next_protocol_selector := NextProtoSelector,
- ocsp_stapling := Stapling},
+ #{secure_renegotiate := SecureRenegotation} =
+ SslOpts,
ConnectionStates0, Renegotiation, IsNew) ->
ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version,
maps:get(renegotiation_info, Exts, undefined), Random,
@@ -1544,7 +1541,7 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
ok
end,
- case handle_ocsp_extension(Stapling, Exts) of
+ case handle_ocsp_extension(SslOpts, Exts) of
#alert{} = Alert ->
Alert;
OcspState ->
@@ -1560,7 +1557,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
{ConnectionStates, alpn, undefined, OcspState};
undefined ->
NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
- Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation),
+ NextProtocolSelector = maps:get(next_protocol_selector, SslOpts, undefined),
+ Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtocolSelector, Renegotiation),
{ConnectionStates, npn, Protocol, OcspState};
{error, Reason} ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
@@ -1571,12 +1569,11 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
end
end.
-select_curve(Client, Server, Version) ->
- select_curve(Client, Server, Version, false).
+select_curve(Client, Server) ->
+ select_curve(Client, Server, false).
select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves},
#elliptic_curves{elliptic_curve_list = ServerCurves},
- _,
ServerOrder) ->
case ServerOrder of
false ->
@@ -1584,25 +1581,25 @@ select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves},
true ->
select_shared_curve(ServerCurves, ClientCurves)
end;
-select_curve(undefined, _, {_,Minor}, _) ->
+select_curve(undefined, _, _) ->
%% Client did not send ECC extension use default curve if
%% ECC cipher is negotiated
- case tls_v1:ecc_curves(Minor, [secp256r1]) of
+ case tls_v1:ecc_curves([secp256r1]) of
[] ->
%% Curve not supported by cryptolib ECC algorithms can not be negotiated
no_curve;
[CurveOid] ->
{namedCurve, CurveOid}
end;
-select_curve({supported_groups, Groups}, Server,{_, Minor} = Version, HonorServerOrder) ->
+select_curve({supported_groups, Groups}, Server, HonorServerOrder) ->
%% TLS-1.3 hello but lesser version chosen
TLSCommonCurves = [secp256r1,secp384r1,secp521r1],
Curves = [tls_v1:enum_to_oid(tls_v1:group_to_enum(Name)) || Name <- Groups, lists:member(Name, TLSCommonCurves)],
- case tls_v1:ecc_curves(Minor, Curves) of
+ case tls_v1:ecc_curves(Curves) of
[] ->
- select_curve(undefined, Server, Version, HonorServerOrder);
+ select_curve(undefined, Server, HonorServerOrder);
[_|_] = ClientCurves ->
- select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, Server, Version, HonorServerOrder)
+ select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, Server, HonorServerOrder)
end.
%%--------------------------------------------------------------------
@@ -1623,12 +1620,12 @@ select_hashsign(_, _, KeyExAlgo, _, _Version) when KeyExAlgo == dh_anon;
%% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have
%% negotiated a lower version.
select_hashsign({ClientHashSigns, ClientSignatureSchemes},
- Cert, KeyExAlgo, undefined, {3, 3} = Version) ->
+ Cert, KeyExAlgo, undefined, ?TLS_1_2 = Version) ->
select_hashsign({ClientHashSigns, ClientSignatureSchemes}, Cert, KeyExAlgo,
tls_v1:default_signature_algs([Version]), Version);
select_hashsign({#hash_sign_algos{hash_sign_algos = ClientHashSigns},
ClientSignatureSchemes0},
- Cert, KeyExAlgo, SupportedHashSigns, {3, 3}) ->
+ Cert, KeyExAlgo, SupportedHashSigns, ?TLS_1_2) ->
ClientSignatureSchemes = get_signature_scheme(ClientSignatureSchemes0),
{SignAlgo0, Param, PublicKeyAlgo0, _, _} = get_cert_params(Cert),
SignAlgo = sign_algo(SignAlgo0, Param),
@@ -1685,7 +1682,7 @@ select_hashsign(#certificate_request{
certificate_types = Types},
Cert,
SupportedHashSigns,
- {3, 3}) ->
+ ?TLS_1_2) ->
{SignAlgo0, Param, PublicKeyAlgo0, _, _} = get_cert_params(Cert),
SignAlgo = {_, KeyType} = sign_algo(SignAlgo0, Param),
PublicKeyAlgo = ssl_certificate:public_key_type(PublicKeyAlgo0),
@@ -1877,9 +1874,9 @@ get_signature_scheme(#signature_algorithms_cert{
%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}.
%%--------------------------------------------------------------------
-select_hashsign_algs(HashSign, _, {3, 3}) when HashSign =/= undefined ->
+select_hashsign_algs(HashSign, _, ?TLS_1_2) when HashSign =/= undefined ->
HashSign;
-select_hashsign_algs(undefined, ?rsaEncryption, {3,3}) ->
+select_hashsign_algs(undefined, ?rsaEncryption, ?TLS_1_2) ->
{sha, rsa};
select_hashsign_algs(undefined,?'id-ecPublicKey', _) ->
{sha, ecdsa};
@@ -1897,6 +1894,8 @@ extension_value(undefined) ->
undefined;
extension_value(#sni{hostname = HostName}) ->
HostName;
+extension_value(#use_srtp{protection_profiles = ProtectionProfiles, mki = MKI}) ->
+ #{protection_profiles => ProtectionProfiles, mki => MKI};
extension_value(#ec_point_formats{ec_point_format_list = List}) ->
List;
extension_value(#elliptic_curves{elliptic_curve_list = List}) ->
@@ -1936,21 +1935,21 @@ extension_value(#psk_key_exchange_modes{ke_modes = Modes}) ->
extension_value(#cookie{cookie = Cookie}) ->
Cookie.
-handle_ocsp_extension(true = Stapling, Extensions) ->
+handle_ocsp_extension(#{ocsp_stapling := _OcspStapling}, Extensions) ->
case maps:get(status_request, Extensions, false) of
undefined -> %% status_request in server hello is empty
- #{ocsp_stapling => Stapling,
+ #{ocsp_stapling => true,
ocsp_expect => staple};
false -> %% status_request is missing (not negotiated)
- #{ocsp_stapling => Stapling,
+ #{ocsp_stapling => true,
ocsp_expect => no_staple};
_Else ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, status_request_not_empty)
end;
-handle_ocsp_extension(false = Stapling, Extensions) ->
+handle_ocsp_extension(_SslOpts, Extensions) ->
case maps:get(status_request, Extensions, false) of
false -> %% status_request is missing (not negotiated)
- #{ocsp_stapling => Stapling,
+ #{ocsp_stapling => false,
ocsp_expect => no_staple};
_Else -> %% unsolicited status_request
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_status_request)
@@ -1973,7 +1972,7 @@ int_to_bin(I) ->
%% The end-entity certificate provided by the client MUST contain a
%% key that is compatible with certificate_types.
-certificate_types(Version) when Version =< {3,3} ->
+certificate_types(Version) when ?TLS_LTE(Version, ?TLS_1_2) ->
ECDSA = supported_cert_type_or_empty(ecdsa, ?ECDSA_SIGN),
RSA = supported_cert_type_or_empty(rsa, ?RSA_SIGN),
DSS = supported_cert_type_or_empty(dss, ?DSS_SIGN),
@@ -2103,8 +2102,7 @@ path_validation_alert({bad_cert, unknown_critical_extension}) ->
path_validation_alert({bad_cert, {revoked, _}}) ->
?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED);
path_validation_alert({bad_cert, {revocation_status_undetermined, Details}}) ->
- Alert = ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE),
- Alert#alert{reason = Details};
+ ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE, Details);
path_validation_alert({bad_cert, selfsigned_peer}) ->
?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
path_validation_alert({bad_cert, unknown_ca}) ->
@@ -2121,21 +2119,21 @@ digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey)))
end.
-do_digitally_signed({3, Minor}, Msg, HashAlgo, {#'RSAPrivateKey'{} = Key,
- #'RSASSA-PSS-params'{}}, SignAlgo) when Minor >= 3 ->
+do_digitally_signed(Version, Msg, HashAlgo, {#'RSAPrivateKey'{} = Key,
+ #'RSASSA-PSS-params'{}}, SignAlgo) when ?TLS_GTE(Version, ?TLS_1_2) ->
Options = signature_options(SignAlgo, HashAlgo),
public_key:sign(Msg, HashAlgo, Key, Options);
-do_digitally_signed({3, Minor}, {digest, Digest}, _HashAlgo, #'RSAPrivateKey'{} = Key, rsa) when Minor =< 2 ->
+do_digitally_signed(Version, {digest, Digest}, _HashAlgo, #'RSAPrivateKey'{} = Key, rsa) when ?TLS_LTE(Version, ?TLS_1_1) ->
public_key:encrypt_private(Digest, Key,
[{rsa_pad, rsa_pkcs1_padding}]);
-do_digitally_signed({3, Minor}, {digest, Digest}, _,
- #{algorithm := rsa} = Engine, rsa) when Minor =< 2->
+do_digitally_signed(Version, {digest, Digest}, _,
+ #{algorithm := rsa} = Engine, rsa) when ?TLS_LTE(Version, ?TLS_1_1) ->
crypto:private_encrypt(rsa, Digest, maps:remove(algorithm, Engine),
rsa_pkcs1_padding);
do_digitally_signed(_, Msg, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) ->
Options = signature_options(SignAlgo, HashAlgo),
crypto:sign(Alg, HashAlgo, Msg, maps:remove(algorithm, Engine), Options);
-do_digitally_signed({3, Minor}, {digest, _} = Msg , HashAlgo, Key, _) when Minor =< 2 ->
+do_digitally_signed(Version, {digest, _} = Msg , HashAlgo, Key, _) when ?TLS_LTE(Version,?TLS_1_1) ->
public_key:sign(Msg, HashAlgo, Key);
do_digitally_signed(_, Msg, HashAlgo, Key, SignAlgo) ->
Options = signature_options(SignAlgo, HashAlgo),
@@ -2172,21 +2170,25 @@ bad_key(#{algorithm := rsa}) ->
bad_key(#{algorithm := ecdsa}) ->
unacceptable_ecdsa_key.
-cert_status_check(_, #{ocsp_state := #{ocsp_stapling := true,
- ocsp_expect := stapled}}, _VerifyResult, _, _) ->
- valid; %% OCSP staple will now be checked by ssl_certifcate:verify_cert_extensions/2 in ssl_certifcate:validate
-cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := false}} = SslState, VerifyResult, CertPath, LogLevel) ->
+cert_status_check(_,
+ #{ocsp_state := #{ocsp_stapling := true,
+ ocsp_expect := stapled}},
+ _VerifyResult, _, _) ->
+ %% OCSP staple will now be checked by
+ %% ssl_certificate:verify_cert_extensions/2 in ssl_certificate:validate
+ valid;
+cert_status_check(OtpCert,
+ #{ocsp_state := #{ocsp_stapling := false}} = SslState,
+ VerifyResult, CertPath, LogLevel) ->
maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel);
-cert_status_check(_OtpCert, #{ocsp_state := #{ocsp_stapling := true,
- ocsp_expect := undetermined}},
+cert_status_check(_OtpCert,
+ #{ocsp_state := #{ocsp_stapling := true,
+ ocsp_expect := undetermined}},
_VerifyResult, _CertPath, _LogLevel) ->
{bad_cert, {revocation_status_undetermined, not_stapled}};
-cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := best_effort, %% TODO support this ?
- ocsp_expect := undetermined}} = SslState,
- VerifyResult, CertPath, LogLevel) ->
- maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel);
-cert_status_check(_OtpCert, #{ocsp_state := #{ocsp_stapling := true,
- ocsp_expect := no_staple}},
+cert_status_check(_OtpCert,
+ #{ocsp_state := #{ocsp_stapling := true,
+ ocsp_expect := no_staple}},
_VerifyResult, _CertPath, _LogLevel) ->
{bad_cert, {revocation_status_undetermined, not_stapled}}.
@@ -2242,12 +2244,12 @@ crl_check_same_issuer(OtpCert, _, Dps, Options) ->
dps_and_crls(OtpCert, Callback, CRLDbHandle, ext, LogLevel) ->
case public_key:pkix_dist_points(OtpCert) of
- [] ->
- no_dps;
- DistPoints ->
- Issuer = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer,
- CRLs = distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle, LogLevel),
- dps_and_crls(DistPoints, CRLs, [])
+ [] ->
+ no_dps;
+ DistPoints ->
+ Issuer = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer,
+ CRLs = distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle, LogLevel),
+ [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || DP <- DistPoints, CRL <- CRLs]
end;
dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer, LogLevel) ->
@@ -2266,12 +2268,7 @@ dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer, LogLevel) ->
end, GenNames),
[{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs].
-dps_and_crls([], _, Acc) ->
- Acc;
-dps_and_crls([DP | Rest], CRLs, Acc) ->
- DpCRL = [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs],
- dps_and_crls(Rest, CRLs, DpCRL ++ Acc).
-
+
distpoints_lookup([],_, _, _, _) ->
[];
distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle, LogLevel) ->
@@ -2305,10 +2302,12 @@ encrypted_premaster_secret(Secret, RSAPublicKey) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed))
end.
-calc_certificate_verify({3, N}, HashAlgo, _MasterSecret, Handshake) ->
- tls_v1:certificate_verify(HashAlgo, N, lists:reverse(Handshake)).
-calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) ->
- tls_v1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)).
+-spec calc_certificate_verify(ssl_record:ssl_version(), md5sha | ssl:hash(), _, [binary()]) -> binary().
+calc_certificate_verify(Version, HashAlgo, _MasterSecret, Handshake) when ?TLS_1_X(Version) ->
+ tls_v1:certificate_verify(HashAlgo, lists:reverse(Handshake)).
+
+calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake) when ?TLS_1_X(Version) ->
+ tls_v1:finished(Role, Version, PrfAlgo, MasterSecret, lists:reverse(Handshake)).
master_secret(Version, MasterSecret,
#security_parameters{
@@ -2336,11 +2335,12 @@ master_secret(Version, MasterSecret,
{MasterSecret,
ssl_record:set_pending_cipher_state(ConnStates2, ClientCipherState,
ServerCipherState, Role)}.
-setup_keys({3,N}, PrfAlgo, MasterSecret,
- ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) ->
- tls_v1:setup_keys(N, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
+setup_keys(Version, PrfAlgo, MasterSecret,
+ ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) when ?TLS_1_X(Version)->
+ tls_v1:setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KML, IVS).
-calc_master_secret({3,_}, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) ->
+calc_master_secret(Version, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom)
+ when ?TLS_LT(Version, ?TLS_1_3) ->
tls_v1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom).
%% Update pending connection states with parameters exchanged via
@@ -2464,50 +2464,48 @@ encode_server_key(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}
<<?UINT16(NLen), N/binary, ?UINT16(GLen), G/binary,
?BYTE(SLen), S/binary, ?UINT16(BLen), B/binary>>.
-encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS},{3, 0}) ->
- PKEPMS;
-encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS}, _) ->
+encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS}) ->
PKEPMSLen = byte_size(PKEPMS),
<<?UINT16(PKEPMSLen), PKEPMS/binary>>;
-encode_client_key(#client_diffie_hellman_public{dh_public = DHPublic}, _) ->
+encode_client_key(#client_diffie_hellman_public{dh_public = DHPublic}) ->
Len = byte_size(DHPublic),
<<?UINT16(Len), DHPublic/binary>>;
-encode_client_key(#client_ec_diffie_hellman_public{dh_public = DHPublic}, _) ->
+encode_client_key(#client_ec_diffie_hellman_public{dh_public = DHPublic}) ->
Len = byte_size(DHPublic),
<<?BYTE(Len), DHPublic/binary>>;
-encode_client_key(#client_psk_identity{identity = undefined}, _) ->
+encode_client_key(#client_psk_identity{identity = undefined}) ->
Id = <<"psk_identity">>,
Len = byte_size(Id),
<<?UINT16(Len), Id/binary>>;
-encode_client_key(#client_psk_identity{identity = Id}, _) ->
+encode_client_key(#client_psk_identity{identity = Id}) ->
Len = byte_size(Id),
<<?UINT16(Len), Id/binary>>;
-encode_client_key(Identity = #client_dhe_psk_identity{identity = undefined}, Version) ->
- encode_client_key(Identity#client_dhe_psk_identity{identity = <<"psk_identity">>}, Version);
-encode_client_key(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic}, _) ->
+encode_client_key(Identity = #client_dhe_psk_identity{identity = undefined}) ->
+ encode_client_key(Identity#client_dhe_psk_identity{identity = <<"psk_identity">>});
+encode_client_key(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic}) ->
Len = byte_size(Id),
DHLen = byte_size(DHPublic),
<<?UINT16(Len), Id/binary, ?UINT16(DHLen), DHPublic/binary>>;
-encode_client_key(Identity = #client_ecdhe_psk_identity{identity = undefined}, Version) ->
- encode_client_key(Identity#client_ecdhe_psk_identity{identity = <<"psk_identity">>}, Version);
-encode_client_key(#client_ecdhe_psk_identity{identity = Id, dh_public = DHPublic}, _) ->
+encode_client_key(Identity = #client_ecdhe_psk_identity{identity = undefined}) ->
+ encode_client_key(Identity#client_ecdhe_psk_identity{identity = <<"psk_identity">>});
+encode_client_key(#client_ecdhe_psk_identity{identity = Id, dh_public = DHPublic}) ->
Len = byte_size(Id),
DHLen = byte_size(DHPublic),
<<?UINT16(Len), Id/binary, ?BYTE(DHLen), DHPublic/binary>>;
-encode_client_key(Identity = #client_rsa_psk_identity{identity = undefined}, Version) ->
- encode_client_key(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>}, Version);
-encode_client_key(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}, Version) ->
- EncPMS = encode_client_key(ExchangeKeys, Version),
+encode_client_key(Identity = #client_rsa_psk_identity{identity = undefined}) ->
+ encode_client_key(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>});
+encode_client_key(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}) ->
+ EncPMS = encode_client_key(ExchangeKeys),
Len = byte_size(Id),
<<?UINT16(Len), Id/binary, EncPMS/binary>>;
-encode_client_key(#client_srp_public{srp_a = A}, _) ->
+encode_client_key(#client_srp_public{srp_a = A}) ->
Len = byte_size(A),
<<?UINT16(Len), A/binary>>.
enc_sign({_, anon}, _Sign, _Version) ->
<<>>;
-enc_sign({HashAlg, SignAlg}, Signature, _Version = {Major, Minor})
- when Major == 3, Minor >= 3->
+enc_sign({HashAlg, SignAlg}, Signature, Version)
+ when ?TLS_GTE(Version, ?TLS_1_2)->
SignLen = byte_size(Signature),
HashSign = enc_hashsign(HashAlg, SignAlg),
<<HashSign/binary, ?UINT16(SignLen), Signature/binary>>;
@@ -2561,61 +2559,32 @@ encode_alpn(Protocols, _) ->
encode_versions(Versions) ->
- encode_versions(lists:reverse(Versions), <<>>).
-%%
-encode_versions([], Acc) ->
- Acc;
-encode_versions([{M,N}|T], Acc) ->
- encode_versions(T, <<?BYTE(M),?BYTE(N),Acc/binary>>).
+ << <<?BYTE(M),?BYTE(N)>> || {M,N} <- Versions>>.
encode_client_shares(ClientShares) ->
- encode_client_shares(ClientShares, <<>>).
-%%
-encode_client_shares([], Acc) ->
- Acc;
-encode_client_shares([KeyShareEntry0|T], Acc) ->
- KeyShareEntry = encode_key_share_entry(KeyShareEntry0),
- encode_client_shares(T, <<Acc/binary,KeyShareEntry/binary>>).
+ << << (encode_key_share_entry(KeyShareEntry0))/binary >> || KeyShareEntry0 <- ClientShares >>.
-encode_key_share_entry(#key_share_entry{
- group = Group,
- key_exchange = KeyExchange}) ->
+encode_key_share_entry(#key_share_entry{group = Group,
+ key_exchange = KeyExchange}) ->
Len = byte_size(KeyExchange),
<<?UINT16((tls_v1:group_to_enum(Group))),?UINT16(Len),KeyExchange/binary>>.
encode_psk_key_exchange_modes(KEModes) ->
- encode_psk_key_exchange_modes(lists:reverse(KEModes), <<>>).
-%%
-encode_psk_key_exchange_modes([], Acc) ->
- Acc;
-encode_psk_key_exchange_modes([psk_ke|T], Acc) ->
- encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_KE),Acc/binary>>);
-encode_psk_key_exchange_modes([psk_dhe_ke|T], Acc) ->
- encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_DHE_KE),Acc/binary>>).
-
+ << <<?BYTE((choose_psk_key(PskKey)))>> || PskKey <- KEModes>>.
+%
+choose_psk_key(psk_ke) -> ?PSK_KE;
+choose_psk_key(psk_dhe_ke) -> ?PSK_DHE_KE.
encode_psk_identities(Identities) ->
- encode_psk_identities(Identities, <<>>).
-%%
-encode_psk_identities([], Acc) ->
- Len = byte_size(Acc),
- <<?UINT16(Len), Acc/binary>>;
-encode_psk_identities([#psk_identity{
- identity = Identity,
- obfuscated_ticket_age = Age}|T], Acc) ->
- IdLen = byte_size(Identity),
- encode_psk_identities(T, <<Acc/binary,?UINT16(IdLen),Identity/binary,?UINT32(Age)>>).
-
+ Result = << << ?UINT16((byte_size(Identity))), Identity/binary,?UINT32(Age) >>
+ || #psk_identity{ identity = Identity, obfuscated_ticket_age = Age} <- Identities >>,
+ Len = byte_size(Result),
+ <<?UINT16(Len), Result/binary>>.
encode_psk_binders(Binders) ->
- encode_psk_binders(Binders, <<>>).
-%%
-encode_psk_binders([], Acc) ->
- Len = byte_size(Acc),
- <<?UINT16(Len), Acc/binary>>;
-encode_psk_binders([Binder|T], Acc) ->
- Len = byte_size(Binder),
- encode_psk_binders(T, <<Acc/binary,?BYTE(Len),Binder/binary>>).
+ Result = << << ?BYTE((byte_size(Binder))),Binder/binary >> || Binder <- Binders >>,
+ Len = byte_size(Result),
+ <<?UINT16(Len), Result/binary>>.
hello_extensions_list(HelloExtensions) ->
@@ -2699,8 +2668,6 @@ dec_server_key(<<?UINT16(NLen), N:NLen/binary,
dec_server_key(_, KeyExchange, _) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_key_exchange, KeyExchange})).
-dec_client_key(PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) ->
- #encrypted_premaster_secret{premaster_secret = PKEPMS};
dec_client_key(<<?UINT16(_), PKEPMS/binary>>, ?KEY_EXCHANGE_RSA, _) ->
#encrypted_premaster_secret{premaster_secret = PKEPMS};
dec_client_key(<<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
@@ -2724,10 +2691,6 @@ dec_client_key(<<?UINT16(Len), Id:Len/binary,
?BYTE(DH_YLen), DH_Y:DH_YLen/binary>>,
?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK, _) ->
#client_ecdhe_psk_identity{identity = Id, dh_public = DH_Y};
-dec_client_key(<<?UINT16(Len), Id:Len/binary, PKEPMS/binary>>,
- ?KEY_EXCHANGE_RSA_PSK, {3, 0}) ->
- #client_rsa_psk_identity{identity = Id,
- exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}};
dec_client_key(<<?UINT16(Len), Id:Len/binary, ?UINT16(_), PKEPMS/binary>>,
?KEY_EXCHANGE_RSA_PSK, _) ->
#client_rsa_psk_identity{identity = Id,
@@ -2741,27 +2704,27 @@ dec_server_key_params(Len, Keys, Version) ->
dec_server_key_signature(Params, Signature, Version).
dec_server_key_signature(Params, <<?BYTE(8), ?BYTE(SignAlgo),
- ?UINT16(0)>>, {Major, Minor})
- when Major == 3, Minor >= 3 ->
+ ?UINT16(0)>>, Version)
+ when ?TLS_GTE(Version, ?TLS_1_2) ->
<<?UINT16(Scheme0)>> = <<?BYTE(8), ?BYTE(SignAlgo)>>,
Scheme = ssl_cipher:signature_scheme(Scheme0),
{Hash, Sign, _} = ssl_cipher:scheme_to_components(Scheme),
{Params, {Hash, Sign}, <<>>};
dec_server_key_signature(Params, <<?BYTE(8), ?BYTE(SignAlgo),
- ?UINT16(Len), Signature:Len/binary>>, {Major, Minor})
- when Major == 3, Minor >= 3 ->
+ ?UINT16(Len), Signature:Len/binary>>, Version)
+ when ?TLS_GTE(Version, ?TLS_1_2) ->
<<?UINT16(Scheme0)>> = <<?BYTE(8), ?BYTE(SignAlgo)>>,
Scheme = ssl_cipher:signature_scheme(Scheme0),
{Hash, Sign, _} = ssl_cipher:scheme_to_components(Scheme),
{Params, {Hash, Sign}, Signature};
dec_server_key_signature(Params, <<?BYTE(HashAlgo), ?BYTE(SignAlgo),
- ?UINT16(0)>>, {Major, Minor})
- when Major == 3, Minor >= 3 ->
+ ?UINT16(0)>>, Version)
+ when ?TLS_GTE(Version, ?TLS_1_2) ->
HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)},
{Params, HashSign, <<>>};
dec_server_key_signature(Params, <<?BYTE(HashAlgo), ?BYTE(SignAlgo),
- ?UINT16(Len), Signature:Len/binary>>, {Major, Minor})
- when Major == 3, Minor >= 3 ->
+ ?UINT16(Len), Signature:Len/binary>>, Version)
+ when ?TLS_GTE(Version, ?TLS_1_2) ->
HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)},
{Params, HashSign, Signature};
dec_server_key_signature(Params, <<>>, _) ->
@@ -2847,7 +2810,7 @@ decode_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version < {3,3} ->
+ when ?TLS_LT(Version, ?TLS_1_2) ->
SignAlgoListLen = Len - 2,
<<?UINT16(SignAlgoListLen), SignAlgoList/binary>> = ExtData,
HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} ||
@@ -2857,8 +2820,7 @@ decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
#hash_sign_algos{hash_sign_algos =
HashSignAlgos}});
decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version =:= {3,3} ->
+ ExtData:Len/binary, Rest/binary>>, ?TLS_1_2=Version, MessageType, Acc) ->
SignSchemeListLen = Len - 2,
<<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData,
HashSigns = decode_sign_alg(Version, SignSchemeList),
@@ -2867,8 +2829,7 @@ decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
#hash_sign_algos{
hash_sign_algos = HashSigns}});
decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version =:= {3,4} ->
+ ExtData:Len/binary, Rest/binary>>, ?TLS_1_3=Version, MessageType, Acc) ->
SignSchemeListLen = Len - 2,
<<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData,
SignSchemes = decode_sign_alg(Version, SignSchemeList),
@@ -2878,8 +2839,7 @@ decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
signature_scheme_list = SignSchemes}});
decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version =:= {3,4} ->
+ ExtData:Len/binary, Rest/binary>>, ?TLS_1_3=Version, MessageType, Acc) ->
SignSchemeListLen = Len - 2,
<<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData,
%% Ignore unknown signature algorithms
@@ -2918,9 +2878,19 @@ decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT), ?UINT16(Len),
#signature_algorithms_cert{
signature_scheme_list = SignSchemes}});
+decode_extensions(<<?UINT16(?USE_SRTP_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
+ <<?UINT16(ProfilesLen), ProfilesBin:ProfilesLen/binary, ?BYTE(MKILen), MKI:MKILen/binary>> = ExtData,
+ Profiles = [P || <<P:2/binary>> <= ProfilesBin],
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{use_srtp =>
+ #use_srtp{
+ protection_profiles = Profiles,
+ mki = MKI}});
+
decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version < {3,4} ->
+ when ?TLS_LT(Version, ?TLS_1_3) ->
<<?UINT16(_), EllipticCurveList/binary>> = ExtData,
%% Ignore unknown curves
Pick = fun(Enum) ->
@@ -2938,8 +2908,7 @@ decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
EllipticCurves}});
decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version =:= {3,4} ->
+ ExtData:Len/binary, Rest/binary>>, ?TLS_1_3=Version, MessageType, Acc) ->
<<?UINT16(_), GroupList/binary>> = ExtData,
%% Ignore unknown curves
Pick = fun(Enum) ->
@@ -2993,8 +2962,7 @@ decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
when Len =:= 2, SelectedVersion =:= 16#0304 ->
decode_extensions(Rest, Version, MessageType,
Acc#{server_hello_selected_version =>
- #server_hello_selected_version{selected_version =
- {3,4}}});
+ #server_hello_selected_version{selected_version = ?TLS_1_3}});
decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len),
ExtData:Len/binary, Rest/binary>>,
@@ -3113,7 +3081,7 @@ decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>
decode_extensions(_, _, _, Acc) ->
Acc.
-decode_sign_alg({3,3}, SignSchemeList) ->
+decode_sign_alg(?TLS_1_2, SignSchemeList) ->
%% Ignore unknown signature algorithms
Fun = fun(Elem) ->
case ssl_cipher:signature_scheme(Elem) of
@@ -3138,7 +3106,7 @@ decode_sign_alg({3,3}, SignSchemeList) ->
end,
lists:filtermap(Fun, [SignScheme ||
<<?UINT16(SignScheme)>> <= SignSchemeList]);
-decode_sign_alg({3,4}, SignSchemeList) ->
+decode_sign_alg(?TLS_1_3, SignSchemeList) ->
%% Ignore unknown signature algorithms
Fun = fun(Elem) ->
case ssl_cipher:signature_scheme(Elem) of
@@ -3152,7 +3120,7 @@ decode_sign_alg({3,4}, SignSchemeList) ->
<<?UINT16(SignScheme)>> <= SignSchemeList]).
dec_hashsign(Value) ->
- [HashSign] = decode_sign_alg({3,3}, Value),
+ [HashSign] = decode_sign_alg(?TLS_1_2, Value),
HashSign.
@@ -3308,14 +3276,11 @@ select_cipher_suite(CipherSuites, Suites, false) ->
select_cipher_suite(CipherSuites, Suites, true) ->
select_cipher_suite(Suites, CipherSuites).
-select_cipher_suite([], _) ->
- no_suite;
-select_cipher_suite([Suite | ClientSuites], SupportedSuites) ->
- case is_member(Suite, SupportedSuites) of
- true ->
- Suite;
- false ->
- select_cipher_suite(ClientSuites, SupportedSuites)
+select_cipher_suite(ClientSuites, SupportedSuites) ->
+ F = fun(Suite) -> is_member(Suite, SupportedSuites) end,
+ case lists:search(F, ClientSuites) of
+ {value, Suite} -> Suite;
+ false -> no_suite
end.
is_member(Suite, SupportedSuites) ->
@@ -3350,25 +3315,41 @@ handle_psk_identity(_PSKIdentity, LookupFun)
handle_psk_identity(PSKIdentity, {Fun, UserState}) ->
Fun(psk, PSKIdentity, UserState).
-filter_hashsigns([], [], _, _, Acc) ->
- lists:reverse(Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
- Acc) when KeyExchange == dhe_ecdsa;
- KeyExchange == ecdhe_ecdsa ->
- do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Version, Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
- Acc) when KeyExchange == rsa;
+
+filter_hashsigns(Suites, Algos, HashSigns, Version) ->
+ %% HashSigns, and Version never change
+ ZipperF = fun (Suite, #{key_exchange := KeyExchange}) -> {Suite, KeyExchange} end,
+ SuiteAlgoPairs = lists:zipwith(ZipperF, Suites, Algos),
+ FilterHashSign = fun ({Suite, Kex}) ->
+ maybe true ?= filter_hashsigns_helper(Kex, HashSigns, Version),
+ {true, Suite}
+ end
+ end,
+ lists:filtermap(FilterHashSign, SuiteAlgoPairs).
+
+filter_hashsigns_helper(KeyExchange, HashSigns, _Version)
+ when KeyExchange == dhe_ecdsa;
+ KeyExchange == ecdhe_ecdsa ->
+ lists:keymember(ecdsa, 2, HashSigns);
+filter_hashsigns_helper(KeyExchange, HashSigns, ?TLS_1_2) when KeyExchange == rsa;
+ KeyExchange == dhe_rsa;
+ KeyExchange == ecdhe_rsa;
+ KeyExchange == srp_rsa;
+ KeyExchange == rsa_psk ->
+ lists:any(fun (H) -> lists:keymember(H, 2, HashSigns) end,
+ [rsa, rsa_pss_rsae, rsa_pss_pss]);
+filter_hashsigns_helper(KeyExchange, HashSigns, _Version) when KeyExchange == rsa;
KeyExchange == dhe_rsa;
KeyExchange == ecdhe_rsa;
KeyExchange == srp_rsa;
KeyExchange == rsa_psk ->
- do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Version, Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, Acc) when
+ lists:keymember(rsa, 2, HashSigns);
+filter_hashsigns_helper(KeyExchange, HashSigns, _Version) when
KeyExchange == dhe_dss;
- KeyExchange == srp_dss ->
- do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Version, Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
- Acc) when
+ KeyExchange == srp_dss ->
+ lists:keymember(dsa, 2, HashSigns);
+
+filter_hashsigns_helper(KeyExchange, _HashSigns, _Version) when
KeyExchange == dh_dss;
KeyExchange == dh_rsa;
KeyExchange == dh_ecdsa;
@@ -3377,9 +3358,8 @@ filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], Has
%% Fixed DH certificates MAY be signed with any hash/signature
%% algorithm pair appearing in the hash_sign extension. The names
%% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical.
- filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
- Acc) when
+ true;
+filter_hashsigns_helper(KeyExchange, _HashSigns, _Version) when
KeyExchange == dh_anon;
KeyExchange == ecdh_anon;
KeyExchange == srp_anon;
@@ -3387,24 +3367,7 @@ filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], Has
KeyExchange == dhe_psk;
KeyExchange == ecdhe_psk ->
%% In this case hashsigns is not used as the kexchange is anonaymous
- filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]).
-
-do_filter_hashsigns(rsa = SignAlgo, Suite, Suites, Algos, HashSigns, {3,3} = Version, Acc) ->
- case (lists:keymember(SignAlgo, 2, HashSigns) orelse
- lists:keymember(rsa_pss_rsae, 2, HashSigns) orelse
- lists:keymember(rsa_pss_pss, 2, HashSigns)) of
- true ->
- filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]);
- false ->
- filter_hashsigns(Suites, Algos, HashSigns, Version, Acc)
- end;
-do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Version, Acc) ->
- case lists:keymember(SignAlgo, 2, HashSigns) of
- true ->
- filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]);
- false ->
- filter_hashsigns(Suites, Algos, HashSigns, Version, Acc)
- end.
+ true.
filter_unavailable_ecc_suites(no_curve, Suites) ->
ECCSuites = ssl_cipher:filter_suites(Suites, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true;
@@ -3476,10 +3439,9 @@ handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)-
handle_next_protocol_on_server(undefined, _Renegotiation, _SslOpts) ->
undefined;
-
handle_next_protocol_on_server(#next_protocol_negotiation{extension_data = <<>>},
- false, #{next_protocols_advertised := Protocols}) ->
- Protocols;
+ false, SslOpts) ->
+ maps:get(next_protocols_advertised, SslOpts, undefined);
handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension).
@@ -3564,10 +3526,13 @@ sign_type(ecdsa) ->
server_name(_, _, server) ->
undefined; %% Not interesting to check your own name.
-server_name(undefined, Host, client) ->
- {fallback, Host}; %% Fallback to Host argument to connect
-server_name(SNI, _, client) ->
- SNI. %% If Server Name Indication is available
+server_name(SSLOpts, Host, client) ->
+ case maps:get(server_name_indication, SSLOpts, undefined) of
+ undefined ->
+ {fallback, Host}; %% Fallback to Host argument to connect
+ SNI ->
+ SNI %% If Server Name Indication is available
+ end.
client_ecc_extensions(SupportedECCs) ->
CryptoSupport = proplists:get_value(public_keys, crypto:supports()),
@@ -3599,39 +3564,25 @@ handle_ecc_point_fmt_extension(undefined) ->
handle_ecc_point_fmt_extension(_) ->
#ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}.
-advertises_ec_ciphers([]) ->
- false;
-advertises_ec_ciphers([#{key_exchange := ecdh_ecdsa} | _]) ->
- true;
-advertises_ec_ciphers([#{key_exchange := ecdhe_ecdsa} | _]) ->
- true;
-advertises_ec_ciphers([#{key_exchange := ecdh_rsa} | _]) ->
- true;
-advertises_ec_ciphers([#{key_exchange := ecdhe_rsa} | _]) ->
- true;
-advertises_ec_ciphers([#{key_exchange := ecdh_anon} | _]) ->
- true;
-advertises_ec_ciphers([{ecdhe_psk, _,_,_} | _]) ->
- true;
-advertises_ec_ciphers([_| Rest]) ->
- advertises_ec_ciphers(Rest).
+advertises_ec_ciphers(ListKex) ->
+ KeyExchanges = [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon],
+ F = fun (#{key_exchange := Kex}) -> lists:member(Kex, KeyExchanges);
+ ({ecdhe_psk, _,_,_}) -> true
+ end,
+ lists:any(F, ListKex).
-select_shared_curve([], _) ->
- no_curve;
-select_shared_curve([Curve | Rest], Curves) ->
- case lists:member(Curve, Curves) of
- true ->
- {namedCurve, Curve};
- false ->
- select_shared_curve(Rest, Curves)
+select_shared_curve(SharedCurves, Curves) ->
+ case lists:search(fun (Curve) -> lists:member(Curve, Curves) end, SharedCurves) of
+ {value, SharedCurve} -> {namedCurve, SharedCurve};
+ false -> no_curve
end.
-sni(undefined) ->
- undefined;
-sni(disable) ->
- undefined;
-sni(Hostname) ->
- #sni{hostname = Hostname}.
+sni(SslOpts) ->
+ case maps:get(server_name_indication, SslOpts, undefined) of
+ undefined -> undefined;
+ disable -> undefined;
+ Hostname -> #sni{hostname = Hostname}
+ end.
%% convert max_fragment_length (in bytes) to the RFC 6066 ENUM
max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_1) ->
@@ -3759,14 +3710,14 @@ cert_curve(Cert, ECCCurve0, CipherSuite) ->
empty_extensions() ->
#{}.
-empty_extensions({3,4}, client_hello) ->
+empty_extensions(?TLS_1_3, client_hello) ->
#{
sni => undefined,
%% max_frag_enum => undefined,
%% status_request => undefined,
elliptic_curves => undefined,
signature_algs => undefined,
- %% use_srtp => undefined,
+ use_srtp => undefined,
%% heartbeat => undefined,
alpn => undefined,
%% signed_cert_timestamp => undefined,
@@ -3783,8 +3734,8 @@ empty_extensions({3,4}, client_hello) ->
%% post_handshake_auth => undefined,
signature_algs_cert => undefined
};
-empty_extensions({3, 3}, client_hello) ->
- Ext = empty_extensions({3,2}, client_hello),
+empty_extensions(?TLS_1_2, client_hello) ->
+ Ext = empty_extensions(?TLS_1_1, client_hello),
Ext#{signature_algs => undefined};
empty_extensions(_, client_hello) ->
#{renegotiation_info => undefined,
@@ -3794,12 +3745,12 @@ empty_extensions(_, client_hello) ->
ec_point_formats => undefined,
elliptic_curves => undefined,
sni => undefined};
-empty_extensions({3,4}, server_hello) ->
+empty_extensions(?TLS_1_3, server_hello) ->
#{server_hello_selected_version => undefined,
key_share => undefined,
pre_shared_key => undefined
};
-empty_extensions({3,4}, hello_retry_request) ->
+empty_extensions(?TLS_1_3, hello_retry_request) ->
#{server_hello_selected_version => undefined,
key_share => undefined,
pre_shared_key => undefined, %% TODO remove!
@@ -3857,8 +3808,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR
#{verify_fun := VerifyFun,
customize_hostname_check := CustomizeHostnameCheck,
crl_check := CrlCheck,
- log_level := Level,
- depth := Depth} = Opts,
+ log_level := Level} = Opts,
#{cert_ext := CertExt,
ocsp_responder_certs := OcspResponderCerts,
ocsp_state := OcspState}) ->
@@ -3881,7 +3831,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR
ocsp_responder_certs => OcspResponderCerts,
ocsp_state => OcspState},
Path, Level),
- Options = [{max_path_length, Depth},
+ Options = [{max_path_length, maps:get(depth, Opts, ?DEFAULT_DEPTH)},
{verify_fun, ValidationFunAndState}],
public_key:pkix_path_validation(TrustedCert, Path, Options).
@@ -3890,7 +3840,20 @@ error_to_propagate({error, {bad_cert, root_cert_expired}} = Error, _) ->
error_to_propagate(_, Error) ->
Error.
-path_validation_cb({3,4}) ->
+path_validation_cb(?TLS_1_3) ->
tls_handshake_1_3;
path_validation_cb(_) ->
?MODULE.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(csp,
+ {call, {?MODULE, maybe_add_certificate_status_request,
+ [_Version, SslOpts,
+ _OcspNonce, _HelloExtensions]}},
+ Stack) ->
+ OcspStapling = maps:get(ocsp_stapling, SslOpts, false),
+ {io_lib:format("#1 ADD crt status request / OcspStapling option = ~W",
+ [OcspStapling, 10]), Stack}.
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index 6dd47019f4..ada0c774d5 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -83,9 +83,14 @@
-define(CERTIFICATE_VERIFY, 15).
-define(CLIENT_KEY_EXCHANGE, 16).
-define(FINISHED, 20).
-
-define(MAX_UNIT24, 8388607).
--define(DEFAULT_MAX_HANDSHAKE_SIZE, (256*1024)).
+
+%% Usually the biggest handshake message will be the message conveying the
+%% certificate chain. This size should be sufficient for usual certificate
+%% chains, certificates without special extensions have a typical size of
+%% 1-2kB. By dividing the old default value by 2 we still have a slightly
+%% bigger margin than OpenSSL
+-define(DEFAULT_MAX_HANDSHAKE_SIZE, ((256*1024) div 2)).
-record(random, {
gmt_unix_time, % uint32
@@ -371,6 +376,17 @@
-define(ECPOINT_ANSIX962_COMPRESSED_CHAR2, 2).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 5764 section 4 Datagram Transport Layer Security (DTLS) Extensions
+%% for SRTP (Secure Real-time Transport Protocol) Key Establishment
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-define(USE_SRTP_EXT, 14).
+
+-record(use_srtp, {
+ protection_profiles,
+ mki
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% ECC RFC 4492 Handshake Messages, Section 5
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index cdb3154cb6..f98be277bf 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,6 +26,9 @@
-include_lib("kernel/include/logger.hrl").
-include_lib("public_key/include/public_key.hrl").
+-define(CLIENT_ROLE, client).
+-define(SERVER_ROLE, server).
+
-define(SECRET_PRINTOUT, "***").
-type reason() :: any().
@@ -118,109 +121,6 @@
-define(DEFAULT_MAX_EARLY_DATA_SIZE, 16384).
-%% This map stores all supported options with default values and
-%% list of dependencies:
-%% #{<option> => {<default_value>, [<option>]},
-%% ...}
--define(RULES,
- #{
- alpn_advertised_protocols => {undefined, [versions]},
- alpn_preferred_protocols => {undefined, [versions]},
- anti_replay => {undefined, [versions, session_tickets]},
- beast_mitigation => {one_n_minus_one, [versions]},
- cacertfile => {undefined, [versions,
- verify_fun,
- cacerts]},
- cacerts => {undefined, [versions]},
- cert => {undefined, [versions]},
- certs_keys => {undefined, [versions]},
- certfile => {<<>>, [versions]},
- certificate_authorities => {false, [versions]},
- ciphers => {[], [versions]},
- client_renegotiation => {undefined, [versions]},
- cookie => {true, [versions]},
- crl_cache => {{ssl_crl_cache, {internal, []}}, [versions]},
- crl_check => {false, [versions]},
- customize_hostname_check => {[], [versions]},
- depth => {10, [versions]},
- dh => {undefined, [versions]},
- dhfile => {undefined, [versions]},
- early_data => {undefined, [versions,
- session_tickets,
- use_ticket]},
- eccs => {undefined, [versions]},
- erl_dist => {false, [versions]},
- fail_if_no_peer_cert => {false, [versions]},
- fallback => {false, [versions]},
- handshake => {full, [versions]},
- hibernate_after => {infinity, [versions]},
- honor_cipher_order => {false, [versions]},
- honor_ecc_order => {undefined, [versions]},
- keep_secrets => {false, [versions]},
- key => {undefined, [versions]},
- keyfile => {undefined, [versions,
- certfile]},
- key_update_at => {?KEY_USAGE_LIMIT_AES_GCM, [versions]},
- log_level => {notice, [versions]},
- max_handshake_size => {?DEFAULT_MAX_HANDSHAKE_SIZE, [versions]},
- middlebox_comp_mode => {true, [versions]},
- max_fragment_length => {undefined, [versions]},
- next_protocol_selector => {undefined, [versions]},
- next_protocols_advertised => {undefined, [versions]},
- %% If enable OCSP stapling
- ocsp_stapling => {false, [versions]},
- %% Optional arg, if give suggestion of OCSP responders
- ocsp_responder_certs => {[], [versions,
- ocsp_stapling]},
- %% Optional arg, if add nonce extension in request
- ocsp_nonce => {true, [versions,
- ocsp_stapling]},
- padding_check => {true, [versions]},
- partial_chain => {fun(_) -> unknown_ca end, [versions]},
- password => {"", [versions]},
- protocol => {tls, []},
- psk_identity => {undefined, [versions]},
- receiver_spawn_opts => {[], [versions]},
- renegotiate_at => {?DEFAULT_RENEGOTIATE_AT, [versions]},
- reuse_session => {undefined, [versions]},
- reuse_sessions => {true, [versions]},
- secure_renegotiate => {true, [versions]},
- sender_spawn_opts => {[], [versions]},
- server_name_indication => {undefined, [versions]},
- session_tickets => {disabled, [versions]},
- signature_algs => {undefined, [versions]},
- signature_algs_cert => {undefined, [versions]},
- sni_fun => {undefined, [versions,
- sni_hosts]},
- sni_hosts => {[], [versions]},
- srp_identity => {undefined, [versions]},
- supported_groups => {undefined, [versions]},
- use_ticket => {undefined, [versions]},
- user_lookup_fun => {undefined, [versions]},
- verify => {verify_none, [versions,
- fail_if_no_peer_cert,
- partial_chain]},
- verify_fun =>
- {
- {fun(_, {bad_cert, _}, UserState) ->
- {valid, UserState};
- (_, {extension, #'Extension'{critical = true}}, UserState) ->
- %% This extension is marked as critical, so
- %% certificate verification should fail if we don't
- %% understand the extension. However, this is
- %% `verify_none', so let's accept it anyway.
- {valid, UserState};
- (_, {extension, _}, UserState) ->
- {unknown, UserState};
- (_, valid, UserState) ->
- {valid, UserState};
- (_, valid_peer, UserState) ->
- {valid, UserState}
- end, []},
- [versions, verify]},
- versions => {[], [protocol]}
- }).
-
-define('TLS-1_3_ONLY_OPTIONS', [anti_replay,
certificate_authorities,
cookie,
@@ -300,10 +200,8 @@
max_size %% max early data size allowed by this ticket
}).
-
+-define(DEFAULT_DEPTH, 10).
+-define(DEFAULT_OCSP_STAPLING, false).
+-define(DEFAULT_OCSP_NONCE, true).
+-define(DEFAULT_OCSP_RESPONDER_CERTS, []).
-endif. % -ifdef(ssl_internal).
-
-
-
-
-
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
index 8777233b81..e9131b28a5 100644
--- a/lib/ssl/src/ssl_logger.erl
+++ b/lib/ssl/src/ssl_logger.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -290,19 +290,20 @@ get_server_version(Version, Extensions) ->
Version
end.
-version({3,4}) ->
+-spec version(ssl_record:ssl_version()) -> string().
+version(?TLS_1_3) ->
"TLS 1.3";
-version({3,3}) ->
+version(?TLS_1_2) ->
"TLS 1.2";
-version({3,2}) ->
+version(?TLS_1_1) ->
"TLS 1.1";
-version({3,1}) ->
+version(?TLS_1_0) ->
"TLS 1.0";
-version({3,0}) ->
+version(?SSL_3_0) ->
"SSL 3.0";
-version({254,253}) ->
+version(?DTLS_1_2) ->
"DTLS 1.2";
-version({254,255}) ->
+version(?DTLS_1_0) ->
"DTLS 1.0";
version({M,N}) ->
io_lib:format("TLS/DTLS [0x0~B0~B]", [M,N]).
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index b7b68edd82..9daee92c5b 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -55,14 +55,13 @@
-export([cipher/4, cipher/5, decipher/4,
cipher_aead/4, cipher_aead/5, decipher_aead/5,
is_correct_mac/2, nonce_seed/3]).
--define(TLS_1_3, {3, 4}).
-export_type([ssl_version/0, ssl_atom_version/0, connection_states/0, connection_state/0]).
--type ssl_version() :: {integer(), integer()}.
--type ssl_atom_version() :: tls_record:tls_atom_version().
+-type ssl_version() :: {non_neg_integer(), non_neg_integer()}.
+-type ssl_atom_version() :: tls_record:tls_atom_version().
-type connection_states() :: map(). %% Map
--type connection_state() :: map(). %% Map
+-type connection_state() :: map(). %% Map
%%====================================================================
%% Connection state handling
@@ -496,7 +495,7 @@ init_security_parameters(?SERVER, Version) ->
#security_parameters{connection_end = ?SERVER,
server_random = make_random(Version)}.
-make_random({_Major, _Minor} = Version) when Version >= ?TLS_1_3 ->
+make_random(Version) when ?TLS_GTE(Version, ?TLS_1_3) ->
ssl_cipher:random_bytes(32);
make_random(_Version) ->
Secs_since_1970 = calendar:datetime_to_gregorian_seconds(
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index e39ca92bc2..c58a931ab5 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -163,9 +163,6 @@
%% minor % unit 8
%% }).
--define(LOWEST_MAJOR_SUPPORTED_VERSION, 3).
-
-
-record(generic_stream_cipher, {
content, % opaque content[TLSCompressed.length];
mac % opaque MAC[CipherSpec.hash_size];
@@ -180,4 +177,33 @@
next_iv % opaque IV[SecurityParameters.record_iv_length];
}).
+-define(PROTOCOL_TO_BINARY_VERSION(Version), (Version)).
+-define(BINARY_PROTOCOL_TO_INTERNAL_REPRESENTATION(Version), (Version)).
+
+-define(TLS_1_X(Version), (element(1,Version) == 3)).
+-define(DTLS_1_X(Version), (element(1,Version) == 254)).
+
+-define(TLS_GTE(Version1, Version2), (Version1 >= Version2)).
+-define(TLS_GT(Version1, Version2), (Version1 > Version2)).
+-define(TLS_LTE(Version1, Version2), (Version1 =< Version2)).
+-define(TLS_LT(Version1, Version2), (Version1 < Version2)).
+
+-define(DTLS_GTE(Version1, Version2), (Version1 =< Version2)).
+-define(DTLS_GT(Version1, Version2), (Version1 < Version2)).
+-define(DTLS_LTE(Version1, Version2), (Version >= Version2)).
+-define(DTLS_LT(Version1, Version2), (Version1 > Version2)).
+
+%% Atoms used to refer to protocols
+
+-define(TLS_1_3, {3,4}).
+-define(TLS_1_2, {3,3}).
+-define(TLS_1_1, {3,2}).
+-define(TLS_1_0, {3,1}).
+
+-define(DTLS_1_2, {254,253}).
+-define(DTLS_1_0, {254,255}).
+
+-define(SSL_3_0, {3,0}).
+-define(SSL_2_0, {2,0}).
+
-endif. % -ifdef(ssl_record).
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index 3999b2fc0e..721a9ef4d5 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
-include("ssl_api.hrl").
+-include("ssl_record.hrl").
%% Internal application API
-export([is_new/2,
@@ -39,17 +40,19 @@
-type seconds() :: integer().
%%--------------------------------------------------------------------
--spec legacy_session_id() -> ssl:session_id().
+-spec legacy_session_id(map()) -> ssl:session_id().
%%
%% Description: TLS-1.3 deprecates the session id but has a dummy
%% value for it for protocol backwards-compatibility reasons.
%% If now lower versions are configured this function can be called
%% for a dummy value.
%%--------------------------------------------------------------------
-legacy_session_id(#{middlebox_comp_mode := true}) ->
- legacy_session_id();
-legacy_session_id(_) ->
- ?EMPTY_ID.
+legacy_session_id(Opts) ->
+ case maps:get(middlebox_comp_mode, Opts, true) of
+ true -> legacy_session_id();
+ false -> ?EMPTY_ID
+ end.
+
%%--------------------------------------------------------------------
-spec is_new(ssl:session_id() | #session{}, ssl:session_id()) -> boolean().
%%
@@ -87,7 +90,7 @@ client_select_session({_, _, #{versions := Versions,
HVersion = RecordCb:highest_protocol_version(Versions),
case LVersion of
- {3, 4} ->
+ ?TLS_1_3 ->
%% Session reuse is not supported, do pure legacy
%% middlebox comp mode negotiation, by providing either
%% empty session id (no middle box) or random id (middle
@@ -239,7 +242,12 @@ record_cb(dtls) ->
legacy_session_id() ->
crypto:strong_rand_bytes(32).
-maybe_handle_middlebox({3, 4}, #session{session_id = ?EMPTY_ID} = Session, #{middlebox_comp_mode := true})->
- Session#session{session_id = legacy_session_id()};
+maybe_handle_middlebox(?TLS_1_3, #session{session_id = ?EMPTY_ID} = Session, Options)->
+ case maps:get(middlebox_comp_mode, Options,true) of
+ true ->
+ Session#session{session_id = legacy_session_id()};
+ false ->
+ Session
+ end;
maybe_handle_middlebox(_, Session, _) ->
Session.
diff --git a/lib/ssl/src/ssl_trace.erl b/lib/ssl/src/ssl_trace.erl
new file mode 100644
index 0000000000..c8ac32712e
--- /dev/null
+++ b/lib/ssl/src/ssl_trace.erl
@@ -0,0 +1,512 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Purpose:
+%%% This module implements support for using the Erlang trace in a simple way
+%%% for ssl tracing.
+%%%
+%%% Begin the session with ssl_trace:start(). This will do a dbg:start()
+%%% if needed and then dbg:p/2 to set some flags.
+%%%
+%%% Next select trace profiles to activate: for example plain text
+%%% printouts of messages sent or received. This is switched on and off with
+%%% ssl_trace:on(TraceProfile(s)) and ssl_trace:off(TraceProfile(s)).
+%%% For example:
+%%%
+%%% ssl_trace:on(rle) -- switch on printing role traces
+%%% ssl_trace:on([api, rle]) -- switch on printing role and api traces
+%%% ssl_trace:on() -- switch on all ssl trace profiles
+%%%
+%%% To switch, use the off/0 or off/1 function in the same way, for example:
+%%%
+%%% ssl_trace:off(api) -- switch off api tracing, keep all other
+%%% ssl_trace:off() -- switch off all ssl tracing
+%%%
+%%% Present the trace result with some other method than the default
+%%% io:format/2:
+%%% ssl_trace:start(fun(Format,Args) ->
+%%% my_special( io_lib:format(Format,Args) )
+%%% end)
+%%% Write traces to text file with budget of 1000 trace entries:
+%%% ssl_trace:start(IoFmt, [file, {budget, 1000}])
+%%%
+-module(ssl_trace).
+
+-export([start/0, start/1, start/2, stop/0, on/0, on/1, off/0, off/1, is_on/0,
+ is_off/0, write/2]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
+%% Internal apply_after:
+-export([ets_delete/2]).
+%% Test purpose
+-export([trace_profiles/0]).
+
+-behaviour(gen_server).
+-define(SERVER, ?MODULE).
+-define(CALL_TIMEOUT, 15000). % 3x the default
+-define(TRACE_BUDGET, 10000).
+-define(TRACE_FILE, "ssl_trace.txt").
+
+-record(state, {
+ file = undefined,
+ types_on = [],
+ io_device = undefined,
+ write_fun
+ }).
+
+%%%----------------------------------------------------------------
+start() -> start(fun io:format/2).
+
+start(file) ->
+ start(fun io:format/2, [file]);
+start(IoFmtFun) when is_function(IoFmtFun,2) ; is_function(IoFmtFun,3) ->
+ start(IoFmtFun, []).
+
+start(IoFmtFun, TraceOpts) when is_function(IoFmtFun,2);
+ is_function(IoFmtFun,3);
+ is_list(TraceOpts) ->
+ WriteFun = fun(F,A,S) -> IoFmtFun(F,A), S end,
+ {ok, Pid} = gen_server:start({local,?SERVER}, ?MODULE,
+ [{write_fun, WriteFun}, TraceOpts], []),
+ true = is_process_alive(Pid),
+ catch dbg:start(),
+ start_tracer(IoFmtFun, TraceOpts),
+ dbg:p(all, [timestamp, c]),
+ {ok, get_all_trace_profiles()}.
+
+stop() ->
+ try
+ dbg:stop(),
+ ok = gen_server:call(?SERVER, file_close, ?CALL_TIMEOUT),
+ gen_server:stop(?SERVER)
+ catch
+ _:_ -> ok
+ end.
+
+on() ->
+ on(get_all_trace_profiles()).
+
+on(Type) ->
+ switch(on, Type).
+
+off() ->
+ off(get_all_trace_profiles()).
+
+off(Type) ->
+ switch(off, Type).
+
+is_on() ->
+ gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT).
+
+is_off() ->
+ get_all_trace_profiles() -- is_on().
+
+write(Fmt, Args) ->
+ gen_server:call(?SERVER, {write, Fmt, Args}, ?CALL_TIMEOUT).
+
+%%%----------------------------------------------------------------
+init(Args) ->
+ try
+ ets:new(?MODULE, [public, named_table])
+ catch
+ exit:badarg ->
+ ok
+ end,
+ {ok, #state{write_fun = proplists:get_value(write_fun, Args)}}.
+
+handle_call({switch,on,Profiles}, _From, State) ->
+ [enable_profile(P) || P <- Profiles],
+ NowOn = lists:usort(Profiles ++ State#state.types_on),
+ {reply, {ok,NowOn}, State#state{types_on = NowOn}};
+handle_call({switch,off,Profiles}, _From, State) ->
+ StillOn = State#state.types_on -- Profiles,
+ [disable_profile(P) || P <- Profiles],
+ {reply, {ok,StillOn}, State#state{types_on = StillOn}};
+handle_call(get_on, _From, State) ->
+ {reply, State#state.types_on, State};
+handle_call({file_open, File}, _From, State) ->
+ {ok, IODevice} = file:open(File, [write]),
+ {reply, {ok, IODevice}, State#state{io_device = IODevice}};
+handle_call(file_close, _From, #state{io_device = IODevice} = State) ->
+ case is_pid(IODevice) of
+ true ->
+ ok = file:close(IODevice);
+ _ ->
+ ok
+ end,
+ {reply, ok, State#state{io_device = undefined}};
+handle_call({write, Fmt, Args}, _From, State) ->
+ #state{io_device = IODevice, write_fun = WriteFun0} = State,
+ WriteFun = get_write_fun(IODevice, WriteFun0),
+ WriteFun(Fmt, Args, processed),
+ {reply, ok, State};
+handle_call(C, _From, State) ->
+ io:format('*** Unknown call: ~p~n',[C]),
+ {reply, {error,{unknown_call,C}}, State}.
+
+handle_cast({new_proc,Pid}, State) ->
+ monitor(process, Pid),
+ {noreply, State};
+handle_cast(C, State) ->
+ io:format('*** Unknown cast: ~p~n',[C]),
+ {noreply, State}.
+
+handle_info({'DOWN', _MonitorRef, process, Pid, _Info}, State) ->
+ %% Universal real-time synchronization (there might be dbg msgs in the queue to the tracer):
+ timer:apply_after(20000, ?MODULE, ets_delete, [?MODULE, Pid]),
+ {noreply, State};
+handle_info(C, State) ->
+ io:format('*** Unknown info: ~p~n',[C]),
+ {noreply, State}.
+
+%%%----------------------------------------------------------------
+get_proc_stack(Pid) when is_pid(Pid) ->
+ try ets:lookup_element(?MODULE, Pid, 2)
+ catch
+ error:badarg ->
+ %% Non-existing item
+ new_proc(Pid),
+ ets:insert(?MODULE, {Pid,[]}),
+ []
+ end.
+
+new_proc(Pid) when is_pid(Pid) ->
+ gen_server:cast(?SERVER, {new_proc,Pid}).
+
+put_proc_stack(Pid, Stack) when is_pid(Pid),
+ is_list(Stack) ->
+ ets:insert(?MODULE, {Pid, Stack}).
+
+ets_delete(Tab, Key) ->
+ catch ets:delete(Tab, Key).
+
+start_tracer(WriteFun, TraceOpts) when is_function(WriteFun,2) ->
+ start_tracer(fun(F,A,S) -> WriteFun(F,A), S end, TraceOpts);
+start_tracer(WriteFun, TraceOpts) when is_function(WriteFun,3) ->
+ Acc0 = [{budget, proplists:get_value(budget, TraceOpts, ?TRACE_BUDGET)}],
+ Acc1 = case lists:member(file, TraceOpts) of
+ true ->
+ TraceFile =
+ case init:get_argument(ssl_trace_file) of
+ {ok, [[Path]]} -> Path;
+ _ -> ?TRACE_FILE
+ end,
+ [{file, TraceFile} | Acc0];
+ _ ->
+ Acc0
+ end,
+ start_dbg_tracer(WriteFun, Acc1).
+
+start_dbg_tracer(WriteFun, InitHandlerAcc0) when is_function(WriteFun, 3) ->
+ Handler =
+ fun(Arg, Acc0) ->
+ try_handle_trace(gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT),
+ Arg, WriteFun,
+ Acc0)
+ end,
+ InitHandlerAcc1 =
+ case proplists:get_value(file, InitHandlerAcc0) of
+ undefined ->
+ InitHandlerAcc0;
+ File ->
+ {ok, IODevice} = gen_server:call(?SERVER, {file_open, File}, ?CALL_TIMEOUT),
+ [{io_device, IODevice} | InitHandlerAcc0]
+ end,
+ dbg:tracer(process, {Handler,InitHandlerAcc1}).
+
+try_handle_trace(ProfilesOn, Arg, WriteFun0, HandlerAcc) ->
+ IODevice = proplists:get_value(io_device, HandlerAcc),
+ WriteFun = get_write_fun(IODevice, WriteFun0),
+ Budget0 = proplists:get_value(budget, HandlerAcc, 0),
+ Timestamp = trace_ts(Arg),
+ Pid = trace_pid(Arg),
+ TraceInfo = trace_info(Arg),
+ Module = trace_module(TraceInfo),
+ ProcessStack = get_proc_stack(Pid),
+ Role = proplists:get_value(role, ProcessStack, '?'),
+ Budget1 =
+ lists:foldl(
+ fun(Profile, BAcc) ->
+ case BAcc > 1 of
+ true ->
+ try
+ Module:handle_trace(Profile, TraceInfo, ProcessStack)
+ of
+ {skip, NewProcessStack} ->
+ %% Don't try to process this later
+ put_proc_stack(Pid, NewProcessStack),
+ reduce_budget(BAcc, WriteFun);
+ {Txt, NewProcessStack} when is_list(Txt) ->
+ put_proc_stack(Pid, NewProcessStack),
+ write_txt(WriteFun, Timestamp, Pid,
+ common_prefix(TraceInfo, Role,
+ Profile) ++ Txt),
+ reduce_budget(BAcc, WriteFun)
+ catch
+ _:_ ->
+ %% not processed by custom handler
+ BAcc
+ end;
+ _ ->
+ BAcc
+ end
+ end, Budget0, ProfilesOn),
+ %% generate default trace if was not processed by any custom handler
+ Budget2 =
+ case (Budget1 == Budget0 andalso Budget0 > 0) of
+ true ->
+ WriteFun("~.100s ~W~n",
+ [io_lib:format("~s ~p ~s ",
+ [lists:flatten(Timestamp),Pid,
+ common_prefix(TraceInfo, Role,
+ " ")]),
+ TraceInfo, 7], processed),
+ reduce_budget(Budget0, WriteFun);
+ _ ->
+ Budget1
+ end,
+ [{budget, Budget2} | proplists:delete(budget, HandlerAcc)].
+
+get_write_fun(IODevice, WriteFun0) ->
+ case is_pid(IODevice) of
+ true ->
+ fun(Format, Args, Return) ->
+ ok = io:format(IODevice, Format, Args),
+ Return
+ end;
+ false ->
+ WriteFun0
+ end.
+
+reduce_budget(B, _) when B > 1 ->
+ B - 1;
+reduce_budget(_, WriteFun) ->
+ case get(no_budget_msg_written) of
+ undefined ->
+ WriteFun("No more trace budget!~n", [], processed),
+ put(no_budget_msg_written, true);
+ _ ->
+ ok
+ end,
+ 0.
+
+write_txt(WriteFun, Timestamp, Pid, Txt) when is_list(Txt) ->
+ WriteFun("~s ~p ~ts~n", [Timestamp, Pid, Txt], processed).
+
+get_all_trace_profiles() ->
+ Unsorted = [Profile ||
+ {Profile, _TraceOn, _TraceOff, _TracedFuns}
+ <- trace_profiles()],
+ lists:usort(Unsorted).
+
+switch(X, Profile) when is_atom(Profile); is_tuple(Profile) ->
+ switch(X, [Profile]);
+switch(X, Profiles) when is_list(Profiles) ->
+ case whereis(?SERVER) of
+ undefined ->
+ start();
+ _ ->
+ ok
+ end,
+ case unknown_types(Profiles, get_all_trace_profiles(), []) of
+ [] ->
+ gen_server:call(?SERVER, {switch,X,Profiles}, ?CALL_TIMEOUT);
+ L ->
+ {error, {unknown, L}}
+ end.
+
+unknown_types([], _AllProfiles, Acc) -> Acc;
+unknown_types([Profile | Tail], AllProfiles, Acc)
+ when is_atom(Profile) ->
+ case lists:member(Profile, AllProfiles) of
+ false -> unknown_types(Tail, AllProfiles, [Profile | Acc]);
+ _ -> unknown_types(Tail, AllProfiles, Acc)
+ end;
+unknown_types([ModProfile = {_Mod, Profile} | Tail], AllProfiles, Acc)
+ when is_tuple(ModProfile) ->
+ unknown_types([Profile | Tail], AllProfiles, Acc).
+
+%%%----------------------------------------------------------------
+%%% Format of trace messages are described in reference manual for erlang:trace/4
+%%% {call,MFA}
+%%% {return_from,{M,F,N},Result}
+%%% {send,Msg,To}
+%%% {'receive',Msg}
+
+%% Pick 2nd element, the Pid
+trace_pid(T) when element(1,T)==trace
+ ; element(1,T)==trace_ts ->
+ element(2,T).
+
+%% Pick last element, the Time Stamp, and format it
+trace_ts(T) when element(1,T)==trace_ts ->
+ ts( element(tuple_size(T), T) ).
+
+ts({_,_,Usec}=Now) when is_integer(Usec) ->
+ {_Date,{HH,MM,SS}} = calendar:now_to_local_time(Now),
+ io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]);
+ts(_) ->
+ "-".
+
+%% Make a tuple of all elements but the 1st, 2nd and last
+trace_info(T) ->
+ case tuple_to_list(T) of
+ [trace,_Pid | Info] -> list_to_tuple(Info);
+ [trace_ts,_Pid | InfoTS] -> list_to_tuple(
+ lists:droplast(InfoTS))
+ end.
+
+trace_module(Info) ->
+ {Module, _, _} = element(2, Info),
+ Module.
+
+common_prefix({call, {M, F, Args}}, Role, Profile) ->
+ [io_lib:format("~s (~w) -> ~w:~w/~w ",
+ [Profile, Role, M, F, length(Args)])];
+common_prefix({return_from, {M, F, Arity}, _Return}, Role, Profile) ->
+ [io_lib:format("~s (~w) <- ~w:~w/~w returned ",
+ [Profile, Role, M, F, Arity])];
+common_prefix({exception_from, {M, F, Arity}, Reason}, Role, Profile) ->
+ [io_lib:format("~s (~w) exception_from ~w:~w/~w ~w",
+ [Profile, Role, M, F, Arity, Reason])];
+common_prefix(_E, _Role, _Profile) ->
+ [].
+
+enable_profile(Profile) when is_atom(Profile) ->
+ [enable_profile({M, Profile}) || M <- modules(Profile)];
+enable_profile({Module, Profile}) when is_atom(Module); is_atom(Profile) ->
+ {Profile, TraceOn, _, AllFuns} = profile(Profile),
+ Funs = proplists:get_value(Module, AllFuns),
+ process_profile(Module, TraceOn, Funs).
+
+disable_profile(Profile) when is_atom(Profile) ->
+ [disable_profile({M, Profile}) || M <- modules(Profile)];
+disable_profile({Module, Profile}) when is_atom(Module); is_atom(Profile) ->
+ {Profile, _, TraceOff, AllFuns} = profile(Profile),
+ Funs = proplists:get_value(Module, AllFuns),
+ process_profile(Module, TraceOff, Funs).
+
+process_profile(Module, Action, Funs) when is_atom(Module) ->
+ [Action(Module, F, A) || {F, A} <- Funs].
+
+profile(P) ->
+ lists:keyfind(P, 1, trace_profiles()).
+
+modules(P) ->
+ {_, _, _, Funs} = profile(P),
+ proplists:get_keys(Funs).
+
+trace_profiles() ->
+ [{api,
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{ssl,
+ [{listen,2}, {connect,3}, {handshake,2}, {close, 1}]},
+ {ssl_gen_statem,
+ [{initial_hello,3}, {connect, 8}, {close, 2}, {terminate_alert, 1}]},
+ {tls_gen_connection,
+ [{start_connection_tree, 5}, {socket_control, 6}]}
+ ]},
+ {csp, %% OCSP
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{ssl_handshake, [{maybe_add_certificate_status_request, 4},
+ {client_hello_extensions, 10}, {cert_status_check, 5},
+ {get_ocsp_responder_list, 1}, {handle_ocsp_extension, 2},
+ {path_validation, 10},
+ {handle_server_hello_extensions, 10},
+ {handle_client_hello_extensions, 10},
+ {cert_status_check, 5}]},
+ {public_key, [{ocsp_extensions, 1}, {pkix_ocsp_validate, 5},
+ {ocsp_responder_id, 1}, {otp_cert, 1}]},
+ {pubkey_ocsp, [{find_responder_cert, 2}, {do_verify_ocsp_signature, 4},
+ {verify_ocsp_response, 3}, {verify_ocsp_nonce, 2},
+ {verify_ocsp_signature, 5}, {do_verify_ocsp_response, 3},
+ {is_responder, 2}, {find_single_response, 3},
+ {ocsp_status, 1}, {match_single_response, 4}]},
+ {ssl, [{opt_ocsp, 3}]},
+ {ssl_certificate, [{verify_cert_extensions, 4}]},
+ {ssl_test_lib, [{init_openssl_server, 3}, {openssl_server_loop, 3}]},
+ {tls_connection, [{wait_ocsp_stapling, 3}]},
+ {dtls_connection, [{initial_hello, 3}, {hello, 3}, {connection, 3}]},
+ {tls_dtls_connection, [{wait_ocsp_stapling, 3}, {certify, 3}]},
+ {tls_handshake, [{ocsp_nonce, 1}, {ocsp_expect, 1}, {client_hello, 11}]},
+ {dtls_handshake, [{client_hello, 8}]}]},
+ {crt, %% certificates
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{public_key, [{pkix_path_validation, 3}, {path_validation, 2},
+ {pkix_decode_cert, 2}]},
+ {ssl_certificate, [{validate, 3}, {trusted_cert_and_paths, 4},
+ {certificate_chain, 3}, {certificate_chain, 5},
+ {issuer, 1}]},
+ {ssl_cipher, [{filter, 3}]},
+ {ssl_gen_statem, [{initial_hello, 3}]},
+ {ssl_handshake, [{path_validate, 11}, {path_validation, 10},
+ {select_hashsign, 5}, {get_cert_params, 1},
+ {cert_curve, 3},
+ {maybe_check_hostname, 3}, {maybe_check_hostname, 3}]},
+ {ssl_pkix_db, [{decode_cert, 2}]},
+ {tls_handshake_1_3, [{path_validation, 10}]},
+ {tls_server_connection_1_3, [{init,1}]},
+ {tls_client_connection_1_3, [{init,1}]},
+ {tls_connection, [{init,1}]},
+ {dtls_connection, [{init,1}]}]},
+ {kdt, %% key update
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{tls_gen_connection_1_3, [{handle_key_update, 2}]},
+ {tls_sender, [{init, 3}, {time_to_rekey, 6},
+ {send_post_handshake_data, 4}]},
+ {tls_v1, [{update_traffic_secret, 2}]}]},
+ {rle, %% role
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{ssl, [{listen,2}, {connect,3}]},
+ {ssl_gen_statem, [{init, 1}]},
+ {tls_server_session_ticket, [{init,1}]},
+ {tls_sender, [{init, 3}]}]},
+ {ssn, %% session
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{tls_server_session_ticket,
+ [{handle_call,3}, {handle_cast,2}, {handle_info,2},
+ {terminate,2}, {start_link,7},
+ {init,1}, {initial_state,1}, {validate_binder,5}, {stateful_store,0},
+ {stateful_ticket_store,6}, {stateful_use,4}, {stateful_use,6},
+ {stateful_usable_ticket,5}, {stateful_living_ticket,2},
+ {stateful_psk_ticket_id,1}, {generate_stateless_ticket,5}, {stateless_use,6},
+ {stateless_usable_ticket,5}, {stateless_living_ticket,5}, {in_window,2},
+ {stateless_anti_replay,5}]},
+ {tls_handshake_1_3,
+ [{get_ticket_data,3}]}]},
+ {hbn, %% hibernate
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{tls_sender,
+ [{connection, 3}, {hibernate_after, 3}]},
+ {dtls_connection,
+ [{connection,3},
+ {gen_info, 3}]},
+ {dtls_gen_connection,
+ [{handle_info,3}]},
+ {ssl_gen_statem,
+ [{hibernate_after, 3}, {handle_common_event, 4}]}]}].
diff --git a/lib/ssl/src/tls_client_connection_1_3.erl b/lib/ssl/src/tls_client_connection_1_3.erl
new file mode 100644
index 0000000000..8f7486d419
--- /dev/null
+++ b/lib/ssl/src/tls_client_connection_1_3.erl
@@ -0,0 +1,1013 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: TLS-1.3 FSM (client side)
+%%----------------------------------------------------------------------
+%% INITIAL_HELLO
+%% Client send
+%% first ClientHello
+%% | ---> CONFIG_ERROR
+%% | Send error to user
+%% | and shutdown
+%% |
+%% V
+%% RFC 8446
+%% A.1. Client
+%%
+%% START <----+
+%% Send ClientHello | | Recv HelloRetryRequest
+%% [K_send = early data] | |
+%% v |
+%% / WAIT_SH ----+
+%% | | Recv ServerHello
+%% | | K_recv = handshake
+%% Can | V
+%% send | WAIT_EE
+%% early | | Recv EncryptedExtensions
+%% data | +--------+--------+
+%% | Using | | Using certificate
+%% | PSK | v
+%% | | WAIT_CERT_CR
+%% | | Recv | | Recv CertificateRequest
+%% | | Certificate | v
+%% | | | WAIT_CERT
+%% | | | | Recv Certificate
+%% | | v v
+%% | | WAIT_CV
+%% | | | Recv CertificateVerify
+%% | +> WAIT_FINISHED <+
+%% | | Recv Finished
+%% \ | [Send EndOfEarlyData]
+%% | K_send = handshake
+%% | [Send Certificate [+ CertificateVerify]]
+%% Can send | Send Finished
+%% app data --> | K_send = K_recv = application
+%% after here v
+%% CONNECTED
+%%
+
+-module(tls_client_connection_1_3).
+
+-include_lib("public_key/include/public_key.hrl").
+
+-include("ssl_alert.hrl").
+-include("ssl_connection.hrl").
+-include("tls_connection.hrl").
+-include("tls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
+
+-behaviour(gen_statem).
+
+%% gen_statem callbacks
+-export([init/1,
+ callback_mode/0,
+ terminate/3,
+ code_change/4,
+ format_status/2]).
+
+%% gen_statem state functions
+-export([config_error/3,
+ initial_hello/3,
+ user_hello/3,
+ start/3,
+ wait_sh/3,
+ hello_middlebox_assert/3,
+ hello_retry_middlebox_assert/3,
+ wait_ee/3,
+ wait_cert_cr/3,
+ wait_cert/3,
+ wait_cv/3,
+ wait_finished/3,
+ connection/3,
+ downgrade/3
+ ]).
+
+%% Internal API
+-export([maybe_send_early_data/1,
+ maybe_automatic_session_resumption/1
+ ]).
+
+%%--------------------------------------------------------------------
+%% gen_statem callbacks
+%%--------------------------------------------------------------------
+callback_mode() ->
+ [state_functions, state_enter].
+
+init([?CLIENT_ROLE, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
+ State0 = #state{protocol_specific = Map} =
+ tls_gen_connection_1_3:initial_state(?CLIENT_ROLE, Sender,
+ Host, Port, Socket,
+ Options, User, CbInfo),
+ try
+ State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options,
+ ?CLIENT_ROLE, State0),
+ tls_gen_connection:initialize_tls_sender(State),
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
+ catch throw:Error ->
+ EState = State0#state{protocol_specific = Map#{error => Error}},
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
+ end.
+
+terminate({shutdown, {sender_died, Reason}}, _StateName,
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport}}
+ = State) ->
+ ssl_gen_statem:handle_trusted_certs_db(State),
+ tls_gen_connection:close(Reason, Socket, Transport, undefined);
+terminate(Reason, StateName, State) ->
+ ssl_gen_statem:terminate(Reason, StateName, State).
+
+format_status(Type, Data) ->
+ ssl_gen_statem:format_status(Type, Data).
+
+code_change(_OldVsn, StateName, State, _) ->
+ {ok, StateName, State}.
+
+%--------------------------------------------------------------------
+%% state functions
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+initial_hello(enter, _, State) ->
+ {keep_state, State};
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+config_error(enter, _, State) ->
+ {keep_state, State};
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec user_hello(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+user_hello(enter, _, State) ->
+ {keep_state, State};
+user_hello({call, From}, cancel, State) ->
+ gen_statem:reply(From, ok),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED,
+ user_canceled),
+ ?FUNCTION_NAME, State);
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{handshake_env = #handshake_env{continue_status = pause} = HSEnv,
+ ssl_options = Options0} = State0) ->
+ try ssl:update_options(NewOptions, ?CLIENT_ROLE, Options0) of
+ Options ->
+ State = ssl_gen_statem:ssl_config(Options, ?CLIENT_ROLE, State0),
+ {next_state, wait_sh, State#state{start_or_recv_from = From,
+ handshake_env =
+ HSEnv#handshake_env{continue_status
+ = continue}
+ },
+ [{{timeout, handshake}, Timeout, close}]}
+ catch
+ throw:{error, Reason} ->
+ gen_statem:reply(From, {error, Reason}),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), ?FUNCTION_NAME, State0)
+ end;
+user_hello(Type, Msg, State) ->
+ tls_gen_connection_1_3:user_hello(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec start(gen_statem:event_type(),
+ #server_hello{} | #change_cipher_spec{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+start(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+start(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
+ ?FUNCTION_NAME, State);
+start(internal,
+ #server_hello{extensions =
+ #{server_hello_selected_version :=
+ #server_hello_selected_version{selected_version
+ = Version}}}
+ = ServerHello,
+ #state{ssl_options = #{handshake := full,
+ versions := SupportedVersions}} = State) ->
+ case tls_record:is_acceptable_version(Version, SupportedVersions) of
+ true ->
+ handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State);
+ false ->
+ ssl_gen_statem:handle_own_alert(
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
+ end;
+start(internal, #server_hello{extensions =
+ #{server_hello_selected_version :=
+ #server_hello_selected_version{
+ selected_version = Version}}
+ = Extensions},
+ #state{ssl_options = #{versions := SupportedVersions},
+ start_or_recv_from = From,
+ handshake_env = #handshake_env{continue_status = pause}}
+ = State) ->
+ case tls_record:is_acceptable_version(Version, SupportedVersions) of
+ true ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined},
+ [{postpone, true},
+ {reply, From, {ok, Extensions}}]};
+ false ->
+ ssl_gen_statem:handle_own_alert(
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
+ end;
+start(internal, #server_hello{} = ServerHello,
+ #state{handshake_env =
+ #handshake_env{continue_status = continue}} = State) ->
+ handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State);
+start(internal, #server_hello{}, State0) ->
+ %% Missing mandantory TLS-1.3 extensions,
+ %%so it is a previous version hello.
+ ssl_gen_statem:handle_own_alert(
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0);
+start(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+start(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_sh(gen_statem:event_type(),
+ #server_hello{} | #change_cipher_spec{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_sh(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_sh(internal = Type, #change_cipher_spec{} = Msg, State)->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
+ ?FUNCTION_NAME, State);
+wait_sh(internal, #server_hello{extensions = Extensions},
+ #state{handshake_env = #handshake_env{continue_status = pause},
+ start_or_recv_from = From} = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined},
+ [{postpone, true},{reply, From, {ok, Extensions}}]};
+wait_sh(internal, #server_hello{session_id = ?EMPTY_ID} = Hello,
+ #state{session = #session{session_id = ?EMPTY_ID},
+ ssl_options = #{middlebox_comp_mode := false}} = State0) ->
+ case handle_server_hello(Hello, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0);
+ {State1, start, ServerHello} ->
+ %% hello_retry_request: go to start
+ {next_state, start, State1, [{next_event, internal, ServerHello}]};
+ {State1, wait_ee} ->
+ tls_gen_connection:next_event(wait_ee, no_record, State1)
+ end;
+wait_sh(internal, #server_hello{} = Hello,
+ #state{protocol_specific = PS,
+ ssl_options = SSLOpts} = State0)
+ when not is_map_key(middlebox_comp_mode, SSLOpts) ->
+ IsRetry = maps:get(hello_retry, PS, false),
+ case handle_server_hello(Hello, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0);
+ {State1 = #state{}, start, ServerHello} ->
+ %% hello_retry_request
+ {next_state, start, State1, [{next_event, internal, ServerHello}]};
+ {State1, wait_ee} when IsRetry == true ->
+ tls_gen_connection:next_event(wait_ee, no_record, State1);
+ {State1, wait_ee} when IsRetry == false ->
+ tls_gen_connection:next_event(hello_middlebox_assert,
+ no_record, State1)
+ end;
+wait_sh(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_sh(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec hello_middlebox_assert(gen_statem:event_type(),
+ #change_cipher_spec{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+hello_middlebox_assert(enter, _, State) ->
+ {keep_state, State};
+hello_middlebox_assert(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(wait_ee, no_record, State);
+hello_middlebox_assert(internal = Type, #encrypted_extensions{} = Msg, #state{ssl_options = #{log_level := Level}} = State) ->
+ ssl_logger:log(warning, Level, #{description => "Failed to assert middlebox server message",
+ reason => [{missing, #change_cipher_spec{}}]}, ?LOCATION),
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State);
+hello_middlebox_assert(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+hello_middlebox_assert(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec hello_retry_middlebox_assert(gen_statem:event_type(),
+ #server_hello{} | #change_cipher_spec{}
+ | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+hello_retry_middlebox_assert(enter, _, State) ->
+ {keep_state, State};
+hello_retry_middlebox_assert(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(wait_sh, no_record, State);
+hello_retry_middlebox_assert(internal = Type, #server_hello{} = Msg, #state{ssl_options = #{log_level := Level}} = State) ->
+ ssl_logger:log(warning, Level, #{description => "Failed to assert middlebox server message",
+ reason => [{missing, #change_cipher_spec{}}]}, ?LOCATION),
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State);
+hello_retry_middlebox_assert(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+hello_retry_middlebox_assert(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_ee(gen_statem:event_type(),
+ #encrypted_extensions{} | #change_cipher_spec{}
+ | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_ee(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_ee(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
+ ?FUNCTION_NAME, State);
+wait_ee(internal, #encrypted_extensions{extensions = Extensions}, State0) ->
+ case handle_encrypted_extensions(Extensions, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0);
+ {State, NextState} ->
+ tls_gen_connection:next_event(NextState, no_record, State)
+ end;
+wait_ee(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_ee(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert_cr(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert_cr(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_cert_cr(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
+ ?FUNCTION_NAME, State);
+wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) ->
+ case handle_certificate(Certificate, State0) of
+ {#alert{} = Alert, State} ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_cert_cr, State);
+ {State1, NextState} ->
+ tls_gen_connection:next_event(NextState, no_record, State1)
+ end;
+wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest,
+ State0) ->
+ case handle_certificate_request(CertificateRequest, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_cert_cr, State0);
+ {State1, NextState} ->
+ tls_gen_connection:next_event(NextState, no_record, State1)
+ end;
+wait_cert_cr(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cert_cr(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert(Type, Msg, State) ->
+ tls_gen_connection_1_3:wait_cert(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cv(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cv(internal,
+ #certificate_verify_1_3{} = CertificateVerify, State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ {State, NextState}
+ = Maybe(tls_handshake_1_3:verify_certificate_verify(State0,
+ CertificateVerify)),
+ tls_gen_connection:next_event(NextState, no_record, State)
+ catch
+ {Ref, {#alert{} = Alert, AState}} ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_cv, AState)
+ end;
+wait_cv(Type, Msg, State) ->
+ tls_gen_connection_1_3:wait_cv(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec wait_finished(gen_statem:event_type(),
+ #finished{} | #change_cipher_spec{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_finished(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
+ ?FUNCTION_NAME, State);
+wait_finished(internal,
+ #finished{verify_data = VerifyData},
+ #state{static_env = #static_env{protocol_cb = Connection}}
+ = State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ Maybe(tls_handshake_1_3:validate_finished(State0, VerifyData)),
+ %% D.4. Middlebox Compatibility Mode
+ State1 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State0,
+ first),
+ %% Signal change of cipher
+ State2 = maybe_send_end_of_early_data(State1),
+ %% Maybe send Certificate + CertificateVerify
+ State3 = Maybe(maybe_queue_cert_cert_cv(State2)),
+ Finished = tls_handshake_1_3:finished(State3),
+ %% Encode Finished
+ State4 = Connection:queue_handshake(Finished, State3),
+ %% Send first flight
+ {State5, _} = Connection:send_handshake_flight(State4),
+ State6 = tls_handshake_1_3:calculate_traffic_secrets(State5),
+ State7 =
+ tls_handshake_1_3:maybe_calculate_resumption_master_secret(State6),
+ State8 = tls_handshake_1_3:forget_master_secret(State7),
+ %% Configure traffic keys
+ State9 = ssl_record:step_encryption_state(State8),
+ {Record, State} = ssl_gen_statem:prepare_connection(State9,
+ tls_gen_connection),
+ tls_gen_connection:next_event(connection, Record, State,
+ [{{timeout, handshake}, cancel}])
+ catch
+ {Ref, #alert{} = Alert} ->
+ ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0)
+ end;
+wait_finished(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_finished(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connection(Type, Msg, State) ->
+ tls_gen_connection_1_3:connection(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec downgrade(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+downgrade(Type, Msg, State) ->
+ tls_gen_connection_1_3:downgrade(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State0) ->
+ case do_handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello,
+ State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, start, State0);
+ {State, NextState} ->
+ {next_state, NextState, State, []}
+ end.
+
+do_handle_exlusive_1_3_hello_or_hello_retry_request(
+ #server_hello{cipher_suite = SelectedCipherSuite,
+ session_id = SessionId,
+ extensions = Extensions},
+ #state{static_env = #static_env{host = Host,
+ port = Port,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
+ ocsp_stapling_state = OcspState},
+ connection_env = #connection_env{negotiated_version =
+ NegotiatedVersion},
+ protocol_specific = PS,
+ ssl_options = #{ciphers := ClientCiphers,
+ supported_groups := ClientGroups0,
+ use_ticket := UseTicket,
+ session_tickets := SessionTickets,
+ log_level := LogLevel} = SslOpts,
+ session = Session0,
+ connection_states = ConnectionStates0
+ } = State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ ClientGroups =
+ Maybe(tls_handshake_1_3:get_supported_groups(ClientGroups0)),
+ Cookie = maps:get(cookie, Extensions, undefined),
+
+ KeyShare = maps:get(key_share, Extensions, undefined),
+ SelectedGroup = server_group(KeyShare),
+
+ %% Upon receipt of this extension in a HelloRetryRequest, the client
+ %% MUST verify that (1) the selected_group field corresponds to a group
+ %% which was provided in the "supported_groups" extension in the
+ %% original ClientHello and (2) the selected_group field does not
+ %% correspond to a group which was provided in the "key_share" extension
+ %% in the original ClientHello. If either of these checks fails, then
+ %% the client MUST abort the handshake with an "illegal_parameter"
+ %% alert.
+ case KeyShare of
+ #key_share_hello_retry_request{} ->
+ Maybe(validate_selected_group(SelectedGroup, ClientGroups));
+ _ ->
+ ok
+ end,
+ Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
+
+ %% Otherwise, when sending the new ClientHello, the client MUST
+ %% replace the original "key_share" extension with one containing only a
+ %% new KeyShareEntry for the group indicated in the selected_group field
+ %% of the triggering HelloRetryRequest.
+ ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]),
+ TicketData =
+ tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
+ OcspNonce = maps:get(ocsp_nonce, OcspState, undefined),
+ Hello0 = tls_handshake:client_hello(Host, Port,
+ ConnectionStates0, SslOpts,
+ SessionId, Renegotiation,
+ ClientKeyShare,
+ TicketData, OcspNonce,
+ CertDbHandle, CertDbRef),
+ %% Echo cookie received in HelloRetryrequest
+ Hello1 = tls_handshake_1_3:maybe_add_cookie_extension(Cookie, Hello0),
+
+ %% Update state
+ State1 =
+ tls_handshake_1_3:update_start_state(State0,
+ #{cipher => SelectedCipherSuite,
+ key_share => ClientKeyShare,
+ session_id => SessionId,
+ group => SelectedGroup}),
+
+ %% Replace ClientHello1 with a special synthetic handshake message
+ State2 = tls_handshake_1_3:replace_ch1_with_message_hash(State1),
+ #state{handshake_env =
+ #handshake_env{tls_handshake_history = HHistory0}} = State2,
+
+ %% Update pre_shared_key extension with binders (TLS 1.3)
+ Hello =
+ tls_handshake_1_3:maybe_add_binders(Hello1, HHistory0,
+ TicketData, NegotiatedVersion),
+
+ {BinMsg0, ConnectionStates, HHistory} =
+ Connection:encode_handshake(Hello,
+ NegotiatedVersion,
+ ConnectionStates0,
+ HHistory0),
+
+ %% D.4. Middlebox Compatibility Mode
+ {#state{handshake_env = HsEnv} = State3, BinMsg} =
+ tls_gen_connection_1_3:maybe_prepend_change_cipher_spec(State2,
+ BinMsg0),
+
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Hello),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
+
+ State = State3#state{
+ connection_states = ConnectionStates,
+ session = Session0#session{session_id =
+ Hello#client_hello.session_id},
+ handshake_env =
+ HsEnv#handshake_env{tls_handshake_history = HHistory},
+ key_share = ClientKeyShare},
+
+ %% If it is a hello_retry and middlebox mode is
+ %% used assert the change_cipher_spec message
+ %% that the server should send next
+ case (maps:get(hello_retry, PS, false)) andalso
+ (maps:get(middlebox_comp_mode, SslOpts, true))
+ of
+ true ->
+ {State, hello_retry_middlebox_assert};
+ false ->
+ {State, wait_sh}
+ end
+ catch
+ {Ref, #alert{} = Alert} ->
+ Alert
+ end.
+
+handle_server_hello(#server_hello{cipher_suite = SelectedCipherSuite,
+ session_id = SessionId,
+ extensions = Extensions} = ServerHello,
+ #state{key_share = ClientKeyShare,
+ ssl_options = #{ciphers := ClientCiphers,
+ supported_groups := ClientGroups0,
+ session_tickets := SessionTickets,
+ use_ticket := UseTicket}} = State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ ClientGroups =
+ Maybe(tls_handshake_1_3:get_supported_groups(ClientGroups0)),
+ ServerKeyShare = server_share(maps:get(key_share, Extensions)),
+ ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),
+
+ %% Go to state 'start' if server replies with 'HelloRetryRequest'.
+ Maybe(tls_handshake_1_3:maybe_hello_retry_request(ServerHello, State0)),
+
+ %% Resumption and PSK
+ State1 = tls_gen_connection_1_3:handle_resumption(State0,
+ ServerPreSharedKey),
+
+ Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
+ Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)),
+
+ %% Get server public key
+ #key_share_entry{group = SelectedGroup,
+ key_exchange = ServerPublicKey} = ServerKeyShare,
+
+ ClientPrivateKey =
+ client_private_key(SelectedGroup,
+ ClientKeyShare#key_share_client_hello.client_shares),
+ %% Update state
+ State2 = tls_handshake_1_3:update_start_state(State1,
+ #{cipher => SelectedCipherSuite,
+ key_share => ClientKeyShare,
+ session_id => SessionId,
+ group => SelectedGroup,
+ peer_public_key => ServerPublicKey}),
+
+ #state{connection_states = ConnectionStates} = State2,
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ PSK = Maybe(tls_handshake_1_3:get_pre_shared_key(SessionTickets,
+ UseTicket,
+ HKDFAlgo,
+ ServerPreSharedKey)),
+ State3 =
+ tls_handshake_1_3:calculate_handshake_secrets(ServerPublicKey,
+ ClientPrivateKey,
+ SelectedGroup,
+ PSK, State2),
+ State4 = ssl_record:step_encryption_state_read(State3),
+ {State4, wait_ee}
+ catch
+ {Ref, {State, StateName, ServerHello}} ->
+ {State, StateName, ServerHello};
+ {Ref, #alert{} = Alert} ->
+ Alert
+ end.
+
+handle_encrypted_extensions(Extensions, State0) ->
+ {Ref, Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ ALPNProtocol0 = maps:get(alpn, Extensions, undefined),
+ ALPNProtocol = decode_alpn(ALPNProtocol0),
+ EarlyDataIndication = maps:get(early_data, Extensions, undefined),
+
+ %% RFC 6066: handle received/expected maximum fragment length
+ Maybe(maybe_max_fragment_length(Extensions, State0)),
+
+ %% Check if early_data is accepted/rejected
+ State1 = maybe_check_early_data_indication(EarlyDataIndication, State0),
+
+ %% Go to state 'wait_finished' if using PSK.
+ Maybe(maybe_resumption(State1)),
+
+ %% Update state
+ #state{handshake_env = HsEnv} = State1,
+ State2 = State1#state{handshake_env =
+ HsEnv#handshake_env{alpn = ALPNProtocol}},
+ {State2, wait_cert_cr}
+ catch
+ {Ref, #alert{} = Alert} ->
+ Alert;
+ {Ref, {_, _} = Next} ->
+ Next
+ end.
+
+handle_certificate(#certificate_1_3{} = Certificate, State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ Maybe(tls_handshake_1_3:process_certificate(Certificate, State0))
+ catch
+ {Ref, #alert{} = Alert} ->
+ {Alert, State0};
+ {Ref, {#alert{} = Alert, State}} ->
+ {Alert, State}
+ end.
+handle_certificate_request(#certificate_request_1_3{} =
+ CertificateRequest, State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ Maybe(tls_handshake_1_3:process_certificate_request(
+ CertificateRequest, State0))
+ catch
+ {Ref, #alert{} = Alert} ->
+ {Alert, State0}
+ end.
+
+maybe_send_early_data(#state{
+ handshake_env =
+ #handshake_env{tls_handshake_history = {Hist, _}},
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [?TLS_1_3|_],
+ use_ticket := UseTicket,
+ session_tickets := SessionTickets,
+ early_data := EarlyData} = _SslOpts0
+ } = State0) when UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined ->
+ %% D.4. Middlebox Compatibility Mode
+ State1 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State0, last),
+ %% Early traffic secret
+ EarlyDataSize = tls_handshake_1_3:early_data_size(EarlyData),
+ case tls_handshake_1_3:get_pre_shared_key_early_data(SessionTickets,
+ UseTicket) of
+ {ok, {PSK, Cipher, HKDF, MaxSize}} when EarlyDataSize =< MaxSize ->
+ State2 =
+ tls_handshake_1_3:calculate_client_early_traffic_secret(Hist,
+ PSK,
+ Cipher,
+ HKDF,
+ State1),
+ %% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData
+ State3 = ssl_record:step_encryption_state_write(State2),
+ {ok, tls_handshake_1_3:encode_early_data(Cipher, State3)};
+ {ok, {_, _, _, MaxSize}} ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
+ {too_much_early_data, {max, MaxSize}})};
+ {error, Alert} ->
+ {error, Alert}
+ end;
+maybe_send_early_data(State) ->
+ {ok, State}.
+
+maybe_send_end_of_early_data(
+ #state{
+ handshake_env = #handshake_env{early_data_accepted = true},
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [?TLS_1_3|_],
+ use_ticket := UseTicket,
+ early_data := EarlyData},
+ static_env = #static_env{protocol_cb = Connection}
+ } = State0) when UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined ->
+ %% EndOfEarlydata is encrypted with the 0-RTT traffic keys
+ State1 = Connection:queue_handshake(#end_of_early_data{}, State0),
+ %% Use handshake keys after EndOfEarlyData is sent
+ ssl_record:step_encryption_state_write(State1);
+maybe_send_end_of_early_data(State) ->
+ State.
+
+%% Configure a suitable session ticket
+maybe_automatic_session_resumption(#state{ssl_options =
+ #{versions := [Version|_],
+ ciphers := UserSuites,
+ early_data := EarlyData,
+ session_tickets :=
+ SessionTickets,
+ server_name_indication := SNI}
+ = SslOpts0
+ } = State0)
+ when ?TLS_GTE(Version, ?TLS_1_3) andalso
+ SessionTickets =:= auto ->
+ AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
+ HashAlgos = cipher_hash_algos(AvailableCipherSuites),
+ Ciphers = tls_handshake_1_3:ciphers_for_early_data(AvailableCipherSuites),
+ %% Find a pair of tickets KeyPair = {Ticket0, Ticket2} where
+ %% Ticket0 satisfies requirements for early_data and session
+ %% resumption while Ticket2 can only be used for session
+ %% resumption.
+ EarlyDataSize = tls_handshake_1_3:early_data_size(EarlyData),
+ KeyPair =
+ tls_client_ticket_store:find_ticket(self(), Ciphers, HashAlgos,
+ SNI, EarlyDataSize),
+ UseTicket = tls_handshake_1_3:choose_ticket(KeyPair, EarlyData),
+ tls_client_ticket_store:lock_tickets(self(), [UseTicket]),
+ State = State0#state{ssl_options = SslOpts0#{use_ticket => [UseTicket]}},
+ {[UseTicket], State};
+maybe_automatic_session_resumption(#state{
+ ssl_options = #{use_ticket := UseTicket}
+ } = State) ->
+ {UseTicket, State}.
+
+maybe_resumption(#state{handshake_env =
+ #handshake_env{resumption = true}} = State) ->
+ {error, {State, wait_finished}};
+maybe_resumption(_) ->
+ ok.
+
+server_group(undefined) ->
+ undefined;
+server_group(#key_share_server_hello{server_share = #key_share_entry{group = Group}}) ->
+ Group;
+server_group(#key_share_hello_retry_request{selected_group = Group}) ->
+ Group.
+
+server_share(#key_share_server_hello{server_share = Share}) ->
+ Share;
+server_share(#key_share_hello_retry_request{selected_group = Share}) ->
+ Share.
+
+client_private_key(Group, ClientShares) ->
+ case lists:keysearch(Group, 2, ClientShares) of
+ {value, #key_share_entry{key_exchange =
+ ClientPrivateKey = #'ECPrivateKey'{}}} ->
+ ClientPrivateKey;
+ {value, #key_share_entry{key_exchange = {_, ClientPrivateKey}}} ->
+ ClientPrivateKey;
+ false ->
+ no_suitable_key
+ end.
+
+maybe_check_early_data_indication(EarlyDataIndication,
+ #state{
+ handshake_env = HsEnv,
+ ssl_options = #{versions := [?TLS_1_3|_],
+ use_ticket := UseTicket,
+ early_data := EarlyData}
+ } = State)
+ when UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined andalso
+ EarlyDataIndication =/= undefined ->
+ signal_user_early_data(State, accepted),
+ State#state{handshake_env = HsEnv#handshake_env{early_data_accepted = true}};
+maybe_check_early_data_indication(EarlyDataIndication,
+ #state{
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [?TLS_1_3|_],
+ use_ticket := UseTicket,
+ early_data := EarlyData}
+ = _SslOpts0
+ } = State)
+ when UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined andalso
+ EarlyDataIndication =:= undefined ->
+ signal_user_early_data(State, rejected),
+ %% Use handshake keys if early_data is rejected.
+ ssl_record:step_encryption_state_write(State);
+maybe_check_early_data_indication(_, State) ->
+ %% Use handshake keys if there is no early_data.
+ ssl_record:step_encryption_state_write(State).
+
+signal_user_early_data(#state{
+ connection_env =
+ #connection_env{
+ user_application = {_, User}},
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ trackers = Trackers}} = State,
+ Result) ->
+ CPids = Connection:pids(State),
+ SslSocket = Connection:socket(CPids, Transport, Socket, Trackers),
+ User ! {ssl, SslSocket, {early_data, Result}}.
+
+maybe_max_fragment_length(Extensions, State) ->
+ ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined),
+ ClientMaxFragEnum = ssl_handshake:max_frag_enum(
+ maps:get(max_fragment_length,
+ State#state.ssl_options, undefined)),
+ if ServerMaxFragEnum == ClientMaxFragEnum ->
+ ok;
+ true ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
+ end.
+
+cipher_hash_algos(Ciphers) ->
+ Fun = fun(Cipher) ->
+ #{prf := Hash} = ssl_cipher_format:suite_bin_to_map(Cipher),
+ Hash
+ end,
+ lists:map(Fun, Ciphers).
+
+maybe_queue_cert_cert_cv(#state{client_certificate_status = not_requested}
+ = State) ->
+ {ok, State};
+maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
+ session = #session{session_id = _SessionId,
+ own_certificates = OwnCerts},
+ ssl_options = #{} = _SslOpts,
+ key_share = _KeyShare,
+ handshake_env =
+ #handshake_env{tls_handshake_history =
+ _HHistory0},
+ static_env = #static_env{
+ protocol_cb = Connection,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ %% Create Certificate
+ Certificate = Maybe(tls_handshake_1_3:certificate(OwnCerts,
+ CertDbHandle,
+ CertDbRef, <<>>,
+ client)),
+
+ %% Encode Certificate
+ State1 = Connection:queue_handshake(Certificate, State0),
+ %% Maybe create and queue CertificateVerify
+ State = Maybe(maybe_queue_cert_verify(Certificate, State1)),
+ {ok, State}
+ catch
+ {Ref, #alert{} = Alert} ->
+ {error, Alert}
+ end.
+
+%% Clients MUST send this message whenever authenticating via a certificate
+%% (i.e., when the Certificate message is non-empty).
+maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) ->
+ {ok, State};
+maybe_queue_cert_verify(_Certificate,
+ #state{connection_states = _ConnectionStates0,
+ session = #session{sign_alg = SignatureScheme,
+ private_key = CertPrivateKey},
+ static_env = #static_env{protocol_cb = Connection}
+ } = State) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ CertificateVerify =
+ Maybe(tls_handshake_1_3:certificate_verify(CertPrivateKey,
+ SignatureScheme,
+ State, client)),
+ {ok, Connection:queue_handshake(CertificateVerify, State)}
+ catch
+ {Ref, #alert{} = Alert} ->
+ {error, Alert}
+ end.
+
+decode_alpn(undefined) ->
+ undefined;
+decode_alpn(Encoded) ->
+ [Decoded] = ssl_handshake:decode_alpn(Encoded),
+ Decoded.
+
+%% Verify that selected group is offered by the client.
+validate_server_key_share([], _) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
+validate_server_key_share([Group |_ClientGroups], #key_share_entry{group = Group}) ->
+ ok;
+validate_server_key_share([_|ClientGroups], #key_share_entry{} = ServerKeyShare) ->
+ validate_server_key_share(ClientGroups, ServerKeyShare).
+
+
+validate_selected_group(SelectedGroup, [SelectedGroup|_]) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
+ "Selected group sent by the server shall not correspond to a group"
+ " which was provided in the key_share extension")};
+validate_selected_group(SelectedGroup, ClientGroups) ->
+ case lists:member(SelectedGroup, ClientGroups) of
+ true ->
+ ok;
+ false ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
+ "Selected group sent by the server shall correspond to a group"
+ " which was provided in the supported_groups extension")}
+ end.
+
+%% RFC 8446 4.1.3 ServerHello
+%% A client which receives a cipher suite that was not offered MUST abort the
+%% handshake with an "illegal_parameter" alert.
+validate_cipher_suite(Cipher, ClientCiphers) ->
+ case lists:member(Cipher, ClientCiphers) of
+ true ->
+ ok;
+ false ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
+ end.
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 6cc9e21cfb..83ebdbd167 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -71,10 +71,10 @@
%% | Send/Recv Flight 2 or Abbrev Flight 1 - Abbrev Flight 2 part 1
%% |
%% New session | Resumed session
-%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED
+%% WAIT_OCSP_STAPLING CERTIFY <----------------------------------> ABBREVIATED
%% WAIT_CERT_VERIFY
%% <- Possibly Receive -- | |
-%% OCSP Stapel/CertVerify -> | Flight 3 part 1 |
+%% OCSP Staple/CertVerify -> | Flight 3 part 1 |
%% | |
%% V | Abbrev Flight 2 part 2 to Abbrev Flight 3
%% CIPHER |
@@ -135,7 +135,9 @@
terminate/3,
code_change/4,
format_status/2]).
-
+
+%% Tracing
+-export([handle_trace/3]).
%%====================================================================
%% Internal application API
%%====================================================================
@@ -147,7 +149,7 @@ init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
},
connection_env = #connection_env{cert_key_alts = CertKeyAlts},
ssl_options = SslOptions,
- session = Session0} = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
+ session = Session0} = ssl_gen_statem:init_ssl_config(State0#state.ssl_options, Role, State0),
State = case Role of
client ->
CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts),
@@ -232,7 +234,7 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello,
case choose_tls_fsm(SslOpts, Hello) of
tls_1_3_fsm ->
{next_state, start, State1,
- [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]};
+ [{change_callback_module, tls_server_connection_1_3}, {next_event, internal, Hello}]};
tls_1_0_to_1_2_fsm ->
{ServerHelloExt, Type, State} = handle_client_hello(Hello, State1),
{next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]}
@@ -254,18 +256,23 @@ hello(internal, #server_hello{} = Hello,
case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of
%% Legacy TLS 1.2 and older
{Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} ->
- tls_dtls_connection:handle_session(Hello,
- Version, NewId, ConnectionStates, ProtoExt, Protocol,
- State#state{
- handshake_env = HsEnv#handshake_env{
- ocsp_stapling_state = maps:merge(OcspState0,OcspState)}});
+ tls_dtls_connection:handle_session(
+ Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol,
+ State#state{
+ handshake_env =
+ HsEnv#handshake_env{
+ ocsp_stapling_state = maps:merge(OcspState0,OcspState)}});
%% TLS 1.3
{next_state, wait_sh, SelectedVersion, OcspState} ->
%% Continue in TLS 1.3 'wait_sh' state
{next_state, wait_sh,
- State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state = maps:merge(OcspState0,OcspState)},
- connection_env = CEnv#connection_env{negotiated_version = SelectedVersion}},
- [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]}
+ State#state{handshake_env =
+ HsEnv#handshake_env{ocsp_stapling_state =
+ maps:merge(OcspState0, OcspState)},
+ connection_env =
+ CEnv#connection_env{negotiated_version = SelectedVersion}},
+ [{change_callback_module, tls_client_connection_1_3},
+ {next_event, internal, Hello}]}
end
catch throw:#alert{} = Alert ->
ssl_gen_statem:handle_own_alert(Alert, hello, State)
@@ -476,10 +483,8 @@ code_change(_OldVsn, StateName, State, _) ->
initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User,
{CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
put(log_level, maps:get(log_level, SSLOptions)),
- #{erl_dist := IsErlDist,
- %% Use highest supported version for client/server random nonce generation
- versions := [Version|_],
- client_renegotiation := ClientRenegotiation} = SSLOptions,
+ %% Use highest supported version for client/server random nonce generation
+ #{versions := [Version|_]} = SSLOptions,
BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled),
ConnectionStates = tls_record:init_connection_states(Role,
Version,
@@ -505,7 +510,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
handshake_env = #handshake_env{
tls_handshake_history = ssl_handshake:init_handshake_history(),
renegotiation = {false, first},
- allow_renegotiate = ClientRenegotiation
+ allow_renegotiate = maps:get(client_renegotiation, SSLOptions, undefined)
},
connection_env = #connection_env{user_application = {UserMonitor, User}},
socket_options = SocketOptions,
@@ -517,7 +522,8 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
start_or_recv_from = undefined,
flight_buffer = [],
protocol_specific = #{sender => Sender,
- active_n => ssl_config:get_internal_active_n(IsErlDist),
+ active_n => ssl_config:get_internal_active_n(
+ maps:get(erl_dist, SSLOptions, false)),
active_n_toggle => true
}
}.
@@ -591,10 +597,19 @@ choose_tls_fsm(#{versions := Versions},
}
}) ->
case ssl_handshake:select_supported_version(ClientVersions, Versions) of
- {3,4} ->
+ ?TLS_1_3 ->
tls_1_3_fsm;
_Else ->
tls_1_0_to_1_2_fsm
end;
choose_tls_fsm(_, _) ->
tls_1_0_to_1_2_fsm.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(csp,
+ {call, {?MODULE, wait_ocsp_stapling,
+ [Type, Event|_]}}, Stack) ->
+ {io_lib:format("Type = ~w Event = ~W", [Type, Event, 10]), Stack}.
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
deleted file mode 100644
index 8041e48eb0..0000000000
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ /dev/null
@@ -1,740 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
-%%
-%% 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.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%%----------------------------------------------------------------------
-%% Purpose: TLS-1.3 FSM
-%%----------------------------------------------------------------------
-%% INITIAL_HELLO
-%% Client send
-%% first ClientHello
-%% | ---> CONFIG_ERROR
-%% | Send error to user
-%% | and shutdown
-%% |
-%% V
-%% RFC 8446
-%% A.1. Client
-%%
-%% START <----+
-%% Send ClientHello | | Recv HelloRetryRequest
-%% [K_send = early data] | |
-%% v |
-%% / WAIT_SH ----+
-%% | | Recv ServerHello
-%% | | K_recv = handshake
-%% Can | V
-%% send | WAIT_EE
-%% early | | Recv EncryptedExtensions
-%% data | +--------+--------+
-%% | Using | | Using certificate
-%% | PSK | v
-%% | | WAIT_CERT_CR
-%% | | Recv | | Recv CertificateRequest
-%% | | Certificate | v
-%% | | | WAIT_CERT
-%% | | | | Recv Certificate
-%% | | v v
-%% | | WAIT_CV
-%% | | | Recv CertificateVerify
-%% | +> WAIT_FINISHED <+
-%% | | Recv Finished
-%% \ | [Send EndOfEarlyData]
-%% | K_send = handshake
-%% | [Send Certificate [+ CertificateVerify]]
-%% Can send | Send Finished
-%% app data --> | K_send = K_recv = application
-%% after here v
-%% CONNECTED
-%%
-%% A.2. Server
-%%
-%% START <-----+
-%% Recv ClientHello | | Send HelloRetryRequest
-%% v |
-%% RECVD_CH ----+
-%% | Select parameters
-%% v
-%% NEGOTIATED
-%% | Send ServerHello
-%% | K_send = handshake
-%% | Send EncryptedExtensions
-%% | [Send CertificateRequest]
-%% Can send | [Send Certificate + CertificateVerify]
-%% app data | Send Finished
-%% after --> | K_send = application
-%% here +--------+--------+
-%% No 0-RTT | | 0-RTT
-%% | |
-%% K_recv = handshake | | K_recv = early data
-%% [Skip decrypt errors] | +------> WAIT_EOED -+
-%% | | Recv | | Recv EndOfEarlyData
-%% | | early data | | K_recv = handshake
-%% | +------------+ |
-%% | |
-%% +> WAIT_FLIGHT2 <--------+
-%% |
-%% +--------+--------+
-%% No auth | | Client auth
-%% | |
-%% | v
-%% | WAIT_CERT
-%% | Recv | | Recv Certificate
-%% | empty | v
-%% | Certificate | WAIT_CV
-%% | | | Recv
-%% | v | CertificateVerify
-%% +-> WAIT_FINISHED <---+
-%% | Recv Finished
-%% | K_recv = application
-%% v
-%% CONNECTED
-
--module(tls_connection_1_3).
-
--include("ssl_alert.hrl").
--include("ssl_connection.hrl").
--include("tls_connection.hrl").
--include("tls_handshake.hrl").
--include("tls_handshake_1_3.hrl").
-
--behaviour(gen_statem).
-
-%% gen_statem callbacks
--export([init/1, callback_mode/0, terminate/3, code_change/4, format_status/2]).
-
-%% gen_statem state functions
--export([initial_hello/3,
- config_error/3,
- user_hello/3,
- start/3,
- hello_middlebox_assert/3,
- hello_retry_middlebox_assert/3,
- negotiated/3,
- wait_cert/3,
- wait_cv/3,
- wait_finished/3,
- wait_sh/3,
- wait_ee/3,
- wait_cert_cr/3,
- wait_eoed/3,
- connection/3,
- downgrade/3
- ]).
-
-%% Internal API
--export([setopts/3,
- getopts/3,
- send_key_update/2,
- update_cipher_key/2]).
-
-%%====================================================================
-%% Internal API
-%%====================================================================
-
-setopts(Transport, Socket, Other) ->
- tls_socket:setopts(Transport, Socket, Other).
-
-getopts(Transport, Socket, Tag) ->
- tls_socket:getopts(Transport, Socket, Tag).
-
-send_key_update(Sender, Type) ->
- KeyUpdate = tls_handshake_1_3:key_update(Type),
- tls_sender:send_post_handshake(Sender, KeyUpdate).
-
-update_cipher_key(ConnStateName, #state{connection_states = CS0} = State0) ->
- CS = update_cipher_key(ConnStateName, CS0),
- State0#state{connection_states = CS};
-update_cipher_key(ConnStateName, CS0) ->
- #{security_parameters := SecParams0,
- cipher_state := CipherState0} = ConnState0 = maps:get(ConnStateName, CS0),
- HKDF = SecParams0#security_parameters.prf_algorithm,
- CipherSuite = SecParams0#security_parameters.cipher_suite,
- ApplicationTrafficSecret0 = SecParams0#security_parameters.application_traffic_secret,
- ApplicationTrafficSecret = tls_v1:update_traffic_secret(HKDF, ApplicationTrafficSecret0),
-
- %% Calculate traffic keys
- KeyLength = tls_v1:key_length(CipherSuite),
- {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, KeyLength, ApplicationTrafficSecret),
-
- SecParams = SecParams0#security_parameters{application_traffic_secret = ApplicationTrafficSecret},
- CipherState = CipherState0#cipher_state{key = Key, iv = IV},
- ConnState = ConnState0#{security_parameters => SecParams,
- cipher_state => CipherState,
- sequence_number => 0},
- CS0#{ConnStateName => ConnState}.
-
-%--------------------------------------------------------------------
-%% gen_statem callbacks
-%%--------------------------------------------------------------------
-callback_mode() ->
- [state_functions, state_enter].
-
-init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
- State0 = #state{protocol_specific = Map} = initial_state(Role, Sender,
- Host, Port, Socket, Options, User, CbInfo),
- try
- State = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
- tls_gen_connection:initialize_tls_sender(State),
- gen_statem:enter_loop(?MODULE, [], initial_hello, State)
- catch throw:Error ->
- EState = State0#state{protocol_specific = Map#{error => Error}},
- gen_statem:enter_loop(?MODULE, [], config_error, EState)
- end.
-
-terminate({shutdown, {sender_died, Reason}}, _StateName,
- #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport}}
- = State) ->
- ssl_gen_statem:handle_trusted_certs_db(State),
- tls_gen_connection:close(Reason, Socket, Transport, undefined);
-terminate(Reason, StateName, State) ->
- ssl_gen_statem:terminate(Reason, StateName, State).
-
-format_status(Type, Data) ->
- ssl_gen_statem:format_status(Type, Data).
-
-code_change(_OldVsn, StateName, State, _) ->
- {ok, StateName, State}.
-
-%--------------------------------------------------------------------
-%% state callbacks
-%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
--spec initial_hello(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-initial_hello(enter, _, State) ->
- {keep_state, State};
-initial_hello(Type, Event, State) ->
- ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec config_error(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-config_error(enter, _, State) ->
- {keep_state, State};
-config_error(Type, Event, State) ->
- ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
-
-user_hello(enter, _, State) ->
- {keep_state, State};
-user_hello({call, From}, cancel, State) ->
- gen_statem:reply(From, ok),
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
- ?FUNCTION_NAME, State);
-user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
- #state{static_env = #static_env{role = client = Role},
- handshake_env = HSEnv,
- ssl_options = Options0} = State0) ->
- Options = ssl:handle_options(NewOptions, Role, Options0),
- State = ssl_gen_statem:ssl_config(Options, Role, State0),
- {next_state, wait_sh, State#state{start_or_recv_from = From,
- handshake_env = HSEnv#handshake_env{continue_status = continue}},
- [{{timeout, handshake}, Timeout, close}]};
-user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
- #state{static_env = #static_env{role = server = Role},
- handshake_env = #handshake_env{continue_status = {pause, ClientVersions}} = HSEnv,
- ssl_options = Options0} = State0) ->
- Options = #{versions := Versions} = ssl:handle_options(NewOptions, Role, Options0),
- State = ssl_gen_statem:ssl_config(Options, Role, State0),
- case ssl_handshake:select_supported_version(ClientVersions, Versions) of
- {3,4} ->
- {next_state, start, State#state{start_or_recv_from = From,
- handshake_env = HSEnv#handshake_env{continue_status = continue}},
- [{{timeout, handshake}, Timeout, close}]};
- undefined ->
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State);
- _Else ->
- {next_state, hello, State#state{start_or_recv_from = From,
- handshake_env = HSEnv#handshake_env{continue_status = continue}},
- [{change_callback_module, tls_connection},
- {{timeout, handshake}, Timeout, close}]}
- end;
-user_hello(info, {'DOWN', _, _, _, _} = Event, State) ->
- ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State);
-user_hello(_, _, _) ->
- {keep_state_and_data, [postpone]}.
-
-start(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-start(internal = Type, #change_cipher_spec{} = Msg,
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{tls_handshake_history = Hist}} = State) ->
- case ssl_handshake:init_handshake_history() of
- Hist -> %% First message must always be client hello
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State);
- _ ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State)
- end;
-start(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-start(internal, #client_hello{extensions = #{client_hello_versions :=
- #client_hello_versions{versions = ClientVersions}
- }} = Hello,
- #state{ssl_options = #{handshake := full}} = State) ->
- case tls_record:is_acceptable_version({3,4}, ClientVersions) of
- true ->
- do_server_start(Hello, State);
- false ->
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
- end;
-start(internal, #client_hello{extensions = #{client_hello_versions :=
- #client_hello_versions{versions = ClientVersions}
- }= Extensions},
- #state{start_or_recv_from = From,
- handshake_env = #handshake_env{continue_status = pause} = HSEnv} = State) ->
- {next_state, user_hello,
- State#state{start_or_recv_from = undefined, handshake_env = HSEnv#handshake_env{continue_status = {pause, ClientVersions}}},
- [{postpone, true}, {reply, From, {ok, Extensions}}]};
-start(internal, #client_hello{} = Hello,
- #state{handshake_env = #handshake_env{continue_status = continue}} = State) ->
- do_server_start(Hello, State);
-start(internal, #client_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions, so it is a previous version hello.
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0);
-start(internal, #server_hello{extensions = #{server_hello_selected_version :=
- #server_hello_selected_version{selected_version = Version}}} = ServerHello,
- #state{ssl_options = #{handshake := full,
- versions := SupportedVersions}} = State) ->
- case tls_record:is_acceptable_version(Version, SupportedVersions) of
- true ->
- do_client_start(ServerHello, State);
- false ->
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
- end;
-start(internal, #server_hello{extensions = #{server_hello_selected_version :=
- #server_hello_selected_version{selected_version = Version}}
- = Extensions},
- #state{ssl_options = #{versions := SupportedVersions},
- start_or_recv_from = From,
- handshake_env = #handshake_env{continue_status = pause}}
- = State) ->
- case tls_record:is_acceptable_version(Version, SupportedVersions) of
- true ->
- {next_state, user_hello,
- State#state{start_or_recv_from = undefined}, [{postpone, true}, {reply, From, {ok, Extensions}}]};
- false ->
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
- end;
-start(internal, #server_hello{} = ServerHello,
- #state{handshake_env = #handshake_env{continue_status = continue}} = State) ->
- do_client_start(ServerHello, State);
-start(internal, #server_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions, so it is a previous version hello.
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0);
-start(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-start(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-negotiated(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-negotiated(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-negotiated(internal, Message, State0) ->
- case tls_handshake_1_3:do_negotiated(Message, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, negotiated, State0);
- {State, NextState} ->
- {next_state, NextState, State, []}
- end;
-negotiated(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State).
-
-wait_cert(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_cert(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_cert(internal,
- #certificate_1_3{} = Certificate, State0) ->
- case tls_handshake_1_3:do_wait_cert(Certificate, State0) of
- {#alert{} = Alert, State} ->
- ssl_gen_statem:handle_own_alert(Alert, wait_cert, State);
- {State, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State)
- end;
-wait_cert(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_cert(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_cv(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_cv(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_cv(internal,
- #certificate_verify_1_3{} = CertificateVerify, State0) ->
- case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of
- {#alert{} = Alert, State} ->
- ssl_gen_statem:handle_own_alert(Alert, wait_cv, State);
- {State, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State)
- end;
-wait_cv(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_cv(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_finished(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_finished(internal,
- #finished{} = Finished, State0) ->
- case tls_handshake_1_3:do_wait_finished(Finished, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, finished, State0);
- State1 ->
- {Record, State} = ssl_gen_statem:prepare_connection(State1, tls_gen_connection),
- tls_gen_connection:next_event(connection, Record, State,
- [{{timeout, handshake}, cancel}])
- end;
-wait_finished(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_finished(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_sh(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_sh(internal = Type, #change_cipher_spec{} = Msg, State)->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_sh(internal, #server_hello{extensions = Extensions},
- #state{handshake_env = #handshake_env{continue_status = pause},
- start_or_recv_from = From} = State) ->
- {next_state, user_hello,
- State#state{start_or_recv_from = undefined}, [{postpone, true},{reply, From, {ok, Extensions}}]};
-wait_sh(internal, #server_hello{session_id = ?EMPTY_ID} = Hello, #state{session = #session{session_id = ?EMPTY_ID},
- ssl_options = #{middlebox_comp_mode := false}} = State0) ->
- case tls_handshake_1_3:do_wait_sh(Hello, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0);
- {State1, start, ServerHello} ->
- %% hello_retry_request: go to start
- {next_state, start, State1, [{next_event, internal, ServerHello}]};
- {State1, wait_ee} ->
- tls_gen_connection:next_event(wait_ee, no_record, State1)
- end;
-wait_sh(internal, #server_hello{} = Hello,
- #state{protocol_specific = PS, ssl_options = #{middlebox_comp_mode := true}} = State0) ->
- IsRetry = maps:get(hello_retry, PS, false),
- case tls_handshake_1_3:do_wait_sh(Hello, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0);
- {State1 = #state{}, start, ServerHello} ->
- %% hello_retry_request: go to start
- {next_state, start, State1, [{next_event, internal, ServerHello}]};
- {State1, wait_ee} when IsRetry == true ->
- tls_gen_connection:next_event(wait_ee, no_record, State1);
- {State1, wait_ee} when IsRetry == false ->
- tls_gen_connection:next_event(hello_middlebox_assert, no_record, State1)
- end;
-wait_sh(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_sh(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-hello_middlebox_assert(enter, _, State) ->
- {keep_state, State};
-hello_middlebox_assert(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(wait_ee, no_record, State);
-hello_middlebox_assert(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-hello_middlebox_assert(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-hello_retry_middlebox_assert(enter, _, State) ->
- {keep_state, State};
-hello_retry_middlebox_assert(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(wait_sh, no_record, State);
-hello_retry_middlebox_assert(internal, #server_hello{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, [postpone]);
-hello_retry_middlebox_assert(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-hello_retry_middlebox_assert(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_ee(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_ee(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_ee(internal, #encrypted_extensions{} = EE, State0) ->
- case tls_handshake_1_3:do_wait_ee(EE, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, wait_ee, State0);
- {State1, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State1)
- end;
-wait_ee(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_ee(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_cert_cr(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_cert_cr(internal = Type, #change_cipher_spec{} = Msg,
- State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) ->
- case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of
- {#alert{} = Alert, State} ->
- ssl_gen_statem:handle_own_alert(Alert, wait_cert_cr, State);
- {State1, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State1)
- end;
-wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0) ->
- case tls_handshake_1_3:do_wait_cert_cr(CertificateRequest, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, wait_cert_cr, State0);
- {State1, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State1)
- end;
-wait_cert_cr(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_cert_cr(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_eoed(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_eoed(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_eoed(internal, #end_of_early_data{} = EOED, State0) ->
- case tls_handshake_1_3:do_wait_eoed(EOED, State0) of
- {#alert{} = Alert, State} ->
- ssl_gen_statem:handle_own_alert(Alert, wait_eoed, State);
- {State1, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State1)
- end;
-wait_eoed(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_eoed(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-connection(enter, _, State) ->
- {keep_state, State};
-connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
- handle_new_session_ticket(NewSessionTicket, State),
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
-
-connection(internal, #key_update{} = KeyUpdate, State0) ->
- case handle_key_update(KeyUpdate, State0) of
- {ok, State} ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
- {error, State, Alert} ->
- ssl_gen_statem:handle_own_alert(Alert, connection, State),
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State)
- end;
-connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{alpn = undefined}} = State) ->
- ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
-connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
- negotiated_protocol = undefined}} = State) ->
- ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
- [{reply, From, {ok, SelectedProtocol}}]);
-connection(Type, Event, State) ->
- ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
-
-downgrade(enter, _, State) ->
- {keep_state, State};
-downgrade(internal, #new_session_ticket{} = NewSessionTicket, State) ->
- _ = handle_new_session_ticket(NewSessionTicket, State),
- {next_state, ?FUNCTION_NAME, State};
-downgrade(Type, Event, State) ->
- ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
-
-%--------------------------------------------------------------------
-%% internal functions
-%%--------------------------------------------------------------------
-
-do_server_start(ClientHello, State0) ->
- case tls_handshake_1_3:do_start(ClientHello, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, start, State0);
- {State, start} ->
- {next_state, start, State, []};
- {State, negotiated} ->
- {next_state, negotiated, State, [{next_event, internal, {start_handshake, undefined}}]};
- {State, negotiated, PSK} -> %% Session Resumption with PSK
- {next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]}
- end.
-
-do_client_start(ServerHello, State0) ->
- case tls_handshake_1_3:do_start(ServerHello, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, start, State0);
- {State, NextState} ->
- {next_state, NextState, State, []}
- end.
-
-initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User,
- {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
- put(log_level, maps:get(log_level, SSLOptions)),
- #{erl_dist := IsErlDist,
- %% Use highest supported version for client/server random nonce generation
- versions := [Version|_],
- client_renegotiation := ClientRenegotiation} = SSLOptions,
- MaxEarlyDataSize = init_max_early_data_size(Role),
- ConnectionStates = tls_record:init_connection_states(Role,
- Version,
- disabled,
- MaxEarlyDataSize),
- UserMonitor = erlang:monitor(process, User),
- InitStatEnv = #static_env{
- role = Role,
- transport_cb = CbModule,
- protocol_cb = tls_gen_connection,
- data_tag = DataTag,
- close_tag = CloseTag,
- error_tag = ErrorTag,
- passive_tag = PassiveTag,
- host = Host,
- port = Port,
- socket = Socket,
- trackers = Trackers
- },
- #state{
- static_env = InitStatEnv,
- handshake_env = #handshake_env{
- tls_handshake_history = ssl_handshake:init_handshake_history(),
- renegotiation = {false, first},
- allow_renegotiate = ClientRenegotiation
- },
- connection_env = #connection_env{user_application = {UserMonitor, User}},
- socket_options = SocketOptions,
- ssl_options = SSLOptions,
- session = #session{is_resumable = false,
- session_id = ssl_session:legacy_session_id(SSLOptions)},
- connection_states = ConnectionStates,
- protocol_buffers = #protocol_buffers{},
- user_data_buffer = {[],0,[]},
- start_or_recv_from = undefined,
- flight_buffer = [],
- protocol_specific = #{sender => Sender,
- active_n => internal_active_n(IsErlDist),
- active_n_toggle => true
- }
- }.
-
-internal_active_n(true) ->
- %% Start with a random number between 1 and ?INTERNAL_ACTIVE_N
- %% In most cases distribution connections are established all at
- %% the same time, and flow control engages with ?INTERNAL_ACTIVE_N for
- %% all connections. Which creates a wave of "passive" messages, leading
- %% to significant bump of memory & scheduler utilisation. Starting with
- %% a random number between 1 and ?INTERNAL_ACTIVE_N helps to spread the
- %% spike.
- erlang:system_time() rem ?INTERNAL_ACTIVE_N + 1;
-internal_active_n(false) ->
- case application:get_env(ssl, internal_active_n) of
- {ok, N} when is_integer(N) ->
- N;
- _ ->
- ?INTERNAL_ACTIVE_N
- end.
-
-handle_new_session_ticket(_, #state{ssl_options = #{session_tickets := disabled}}) ->
- ok;
-handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
- #state{connection_states = ConnectionStates,
- ssl_options = #{session_tickets := SessionTickets,
- server_name_indication := SNI},
- connection_env = #connection_env{user_application = {_, User}}})
- when SessionTickets =:= manual ->
- #{security_parameters := SecParams} =
- ssl_record:current_connection_state(ConnectionStates, read),
- CipherSuite = SecParams#security_parameters.cipher_suite,
- #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- HKDF = SecParams#security_parameters.prf_algorithm,
- RMS = SecParams#security_parameters.resumption_master_secret,
- PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
- send_ticket_data(User, NewSessionTicket, {Cipher, HKDF}, SNI, PSK);
-handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
- #state{connection_states = ConnectionStates,
- ssl_options = #{session_tickets := SessionTickets,
- server_name_indication := SNI}})
- when SessionTickets =:= auto ->
- #{security_parameters := SecParams} =
- ssl_record:current_connection_state(ConnectionStates, read),
- CipherSuite = SecParams#security_parameters.cipher_suite,
- #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- HKDF = SecParams#security_parameters.prf_algorithm,
- RMS = SecParams#security_parameters.resumption_master_secret,
- PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
- tls_client_ticket_store:store_ticket(NewSessionTicket, {Cipher, HKDF}, SNI, PSK).
-
-send_ticket_data(User, NewSessionTicket, CipherSuite, SNI, PSK) ->
- Timestamp = erlang:system_time(millisecond),
- TicketData = #{cipher_suite => CipherSuite,
- sni => SNI,
- psk => PSK,
- timestamp => Timestamp,
- ticket => NewSessionTicket},
- User ! {ssl, session_ticket, TicketData}.
-
-handle_key_update(#key_update{request_update = update_not_requested}, State0) ->
- %% Update read key in connection
- {ok, update_cipher_key(current_read, State0)};
-handle_key_update(#key_update{request_update = update_requested},
- #state{protocol_specific = #{sender := Sender}} = State0) ->
- %% Update read key in connection
- State1 = update_cipher_key(current_read, State0),
- %% Send key_update and update sender's write key
- case send_key_update(Sender, update_not_requested) of
- ok ->
- {ok, State1};
- {error, Reason} ->
- {error, State1, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason)}
- end.
-
-init_max_early_data_size(client) ->
- %% Disable trial decryption on the client side
- %% Servers do trial decryption of max_early_data bytes of plain text.
- %% Setting it to 0 means that a decryption error will result in an Alert.
- 0;
-init_max_early_data_size(server) ->
- ssl_config:get_max_early_data_size().
-
-handle_middlebox(#state{protocol_specific = PS} = State0) ->
- %% Always be prepared to ignore one change cipher spec
- %% for maximum interopablility, even if middlebox mode
- %% is not enabled.
- State0#state{protocol_specific = PS#{change_cipher_spec => ignore}}.
-
-
-handle_change_cipher_spec(Type, Msg, StateName, #state{protocol_specific = PS0} = State) ->
- case maps:get(change_cipher_spec, PS0) of
- ignore ->
- PS = PS0#{change_cipher_spec => fail},
- tls_gen_connection:next_event(StateName, no_record,
- State#state{protocol_specific = PS});
- fail ->
- ssl_gen_statem:handle_common_event(Type, Msg, StateName, State)
- end.
diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl
index 6882e8af34..c2edbffe30 100644
--- a/lib/ssl/src/tls_dtls_connection.erl
+++ b/lib/ssl/src/tls_dtls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -63,6 +63,9 @@
downgrade/3,
gen_handshake/4]).
+%% Tracing
+-export([handle_trace/3]).
+
%%--------------------------------------------------------------------
-spec internal_renegotiation(pid(), ssl_record:connection_states()) ->
ok.
@@ -171,12 +174,18 @@ user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
#state{static_env = #static_env{role = Role},
handshake_env = HSEnv,
ssl_options = Options0} = State0) ->
- Options = ssl:handle_options(NewOptions, Role, Options0),
- State = ssl_gen_statem:ssl_config(Options, Role, State0),
- {next_state, hello, State#state{start_or_recv_from = From,
- handshake_env = HSEnv#handshake_env{continue_status = continue}
- },
- [{{timeout, handshake}, Timeout, close}]};
+ try ssl:update_options(NewOptions, Role, Options0) of
+ Options ->
+ State = ssl_gen_statem:ssl_config(Options, Role, State0),
+ {next_state, hello, State#state{start_or_recv_from = From,
+ handshake_env = HSEnv#handshake_env{continue_status = continue}
+ },
+ [{{timeout, handshake}, Timeout, close}]}
+ catch
+ throw:{error, Reason} ->
+ gen_statem:reply(From, {error, Reason}),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), ?FUNCTION_NAME, State0)
+ end;
user_hello(info, {'DOWN', _, _, _, _} = Event, State) ->
ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State);
user_hello(_, _, _) ->
@@ -252,9 +261,10 @@ abbreviated(internal,
handshake_env = HsEnv} = State) ->
ConnectionStates1 =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
- ConnectionStates1,
- handshake_env = HsEnv#handshake_env{expecting_finished = true}});
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{connection_states =
+ ConnectionStates1,
+ handshake_env = HsEnv#handshake_env{expecting_finished = true}});
abbreviated(info, Msg, State) ->
handle_info(Msg, ?FUNCTION_NAME, State);
abbreviated(internal, #hello_request{}, _) ->
@@ -275,24 +285,28 @@ wait_ocsp_stapling(internal, #certificate{},
%% Receive OCSP staple message
wait_ocsp_stapling(internal, #certificate_status{} = CertStatus,
#state{static_env = #static_env{protocol_cb = _Connection},
- handshake_env = #handshake_env{
- ocsp_stapling_state = OcspState} = HsEnv} = State) ->
- {next_state, certify, State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state =
- OcspState#{ocsp_expect => stapled,
- ocsp_response => CertStatus}}}};
+ handshake_env =
+ #handshake_env{ocsp_stapling_state = OcspState} = HsEnv} = State) ->
+ {next_state, certify,
+ State#state{handshake_env =
+ HsEnv#handshake_env{ocsp_stapling_state =
+ OcspState#{ocsp_expect => stapled,
+ ocsp_response => CertStatus}}}};
%% Server did not send OCSP staple message
-wait_ocsp_stapling(internal, Msg, #state{static_env = #static_env{protocol_cb = _Connection},
- handshake_env = #handshake_env{
- ocsp_stapling_state = OcspState} = HsEnv} = State)
+wait_ocsp_stapling(internal, Msg,
+ #state{static_env = #static_env{protocol_cb = _Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = OcspState} = HsEnv} = State)
when is_record(Msg, server_key_exchange) orelse
is_record(Msg, hello_request) orelse
is_record(Msg, certificate_request) orelse
is_record(Msg, server_hello_done) orelse
is_record(Msg, client_key_exchange) ->
- {next_state, certify, State#state{handshake_env =
- HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}},
+ {next_state, certify,
+ State#state{handshake_env =
+ HsEnv#handshake_env{ocsp_stapling_state =
+ OcspState#{ocsp_expect => undetermined}}},
[{postpone, true}]};
-
wait_ocsp_stapling(internal, #hello_request{}, _) ->
keep_state_and_data;
wait_ocsp_stapling(Type, Event, State) ->
@@ -1633,8 +1647,9 @@ handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
throw(Alert)
end.
-make_premaster_secret({MajVer, MinVer}, rsa) ->
+make_premaster_secret(Version, rsa) ->
Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ {MajVer,MinVer} = Version,
<<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>;
make_premaster_secret(_, _) ->
undefined.
@@ -1663,23 +1678,21 @@ handle_sni_extension(#state{static_env =
throw(Alert)
end.
-ensure_tls({254, _} = Version) ->
+ensure_tls(Version) when ?DTLS_1_X(Version) ->
dtls_v1:corresponding_tls_version(Version);
ensure_tls(Version) ->
Version.
-ocsp_info(#{ocsp_expect := stapled,
- ocsp_response := CertStatus} = OcspState,
- #{ocsp_responder_certs := OcspResponderCerts}, PeerCert) ->
+ocsp_info(#{ocsp_expect := stapled, ocsp_response := CertStatus} = OcspState,
+ #{ocsp_stapling := OcspStapling} = _SslOpts, PeerCert) ->
+ #{ocsp_responder_certs := OcspResponderCerts} = OcspStapling,
#{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]},
ocsp_responder_certs => OcspResponderCerts,
- ocsp_state => OcspState
- };
+ ocsp_state => OcspState};
ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) ->
#{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []},
ocsp_responder_certs => [],
- ocsp_state => OcspState
- }.
+ ocsp_state => OcspState}.
select_client_cert_key_pair(Session0,_,
[#{private_key := NoKey, certs := [[]] = NoCerts}],
@@ -1724,3 +1737,11 @@ default_cert_key_pair_return(undefined, Session) ->
Session;
default_cert_key_pair_return(Default, _) ->
Default.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(csp,
+ {call, {?MODULE, wait_ocsp_stapling, [Type, Msg | _]}}, Stack) ->
+ {io_lib:format("Type = ~w Msg = ~W", [Type, Msg, 10]), Stack}.
diff --git a/lib/ssl/src/tls_dyn_connection_sup.erl b/lib/ssl/src/tls_dyn_connection_sup.erl
index cb0576cbd6..5905889225 100644
--- a/lib/ssl/src/tls_dyn_connection_sup.erl
+++ b/lib/ssl/src/tls_dyn_connection_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2021-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2021-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -74,5 +74,10 @@ receiver(Args) ->
type => worker,
significant => true,
start => {ssl_gen_statem, start_link, Args},
- modules => [ssl_gen_statem, tls_connection, tls_connection_1_3]
+ modules => [ssl_gen_statem,
+ tls_connection,
+ tls_gen_connection,
+ tls_client_connection_1_3,
+ tls_server_connection_1_3,
+ tls_gen_connection_1_3]
}.
diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl
index 940666f104..76e7bc334e 100644
--- a/lib/ssl/src/tls_gen_connection.erl
+++ b/lib/ssl/src/tls_gen_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@
-include("ssl_alert.hrl").
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
+-include("tls_record_1_3.hrl").
%% Setup
-export([start_fsm/8,
@@ -77,9 +78,11 @@
%% Setup
%%====================================================================
start_fsm(Role, Host, Port, Socket,
- {#{erl_dist := ErlDist, sender_spawn_opts := SenderSpawnOpts}, _, Trackers} = Opts,
+ {SSLOpts, _, Trackers} = Opts,
User, {CbModule, _, _, _, _} = CbInfo,
Timeout) ->
+ ErlDist = maps:get(erl_dist, SSLOpts, false),
+ SenderSpawnOpts = maps:get(sender_spawn_opts, SSLOpts, []),
SenderOptions = handle_sender_options(ErlDist, SenderSpawnOpts),
Starter = start_connection_tree(User, ErlDist, SenderOptions,
Role, [Host, Port, Socket, Opts, User, CbInfo]),
@@ -149,16 +152,16 @@ initialize_tls_sender(#state{static_env = #static_env{
socket_options = SockOpts,
ssl_options = #{renegotiate_at := RenegotiateAt,
key_update_at := KeyUpdateAt,
- erl_dist := ErlDist,
- log_level := LogLevel,
- hibernate_after := HibernateAfter},
+ log_level := LogLevel
+ } = SSLOpts,
connection_states = #{current_write := ConnectionWriteState},
protocol_specific = #{sender := Sender}}) ->
+ HibernateAfter = maps:get(hibernate_after, SSLOpts, infinity),
Init = #{current_write => ConnectionWriteState,
role => Role,
socket => Socket,
socket_options => SockOpts,
- erl_dist => ErlDist,
+ erl_dist => maps:get(erl_dist, SSLOpts, false),
trackers => Trackers,
transport_cb => Transport,
negotiated_version => Version,
@@ -354,6 +357,11 @@ handle_info({CloseTag, Socket}, StateName,
%% is called after all data has been deliver.
{next_state, StateName, State#state{protocol_specific = PS#{active_n_toggle => true}}, []}
end;
+handle_info({ssl_tls, Port, Type, {Major, Minor}, Data}, StateName,
+ #state{static_env = #static_env{data_tag = Protocol},
+ ssl_options = #{ktls := true}} = State0) ->
+ Len = byte_size(Data),
+ handle_info({Protocol, Port, <<Type, Major, Minor, Len:16, Data/binary>>}, StateName, State0);
handle_info(Msg, StateName, State) ->
ssl_gen_statem:handle_info(Msg, StateName, State).
@@ -631,7 +639,7 @@ next_tls_record(Data, StateName,
%% This does not allow SSL-3.0 connections, that we do not support
%% or interfere with TLS-1.3 extensions to handle version negotiation.
AllHelloVersions = [ 'sslv3' | ?ALL_AVAILABLE_VERSIONS],
- [tls_record:protocol_version(Vsn) || Vsn <- AllHelloVersions];
+ [tls_record:protocol_version_name(Vsn) || Vsn <- AllHelloVersions];
_ ->
State0#state.connection_env#connection_env.negotiated_version
end,
@@ -673,6 +681,8 @@ next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []
next_record(_, State) ->
{no_record, State}.
+flow_ctrl(#state{ssl_options = #{ktls := true}} = State) ->
+ {no_record, State};
%%% bytes_to_read equals the integer Length arg of ssl:recv
%%% the actual value is only relevant for packet = raw | 0
%%% bytes_to_read = undefined means no recv call is ongoing
@@ -732,7 +742,7 @@ activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n :
next_record(State, CipherTexts, ConnectionStates, Check) ->
next_record(State, CipherTexts, ConnectionStates, Check, [], false).
%%
-next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State,
+next_record(#state{connection_env = #connection_env{negotiated_version = ?TLS_1_3 = Version}} = State,
[CT|CipherTexts], ConnectionStates0, Check, Acc, IsEarlyData) ->
case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
{Record = #ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
@@ -822,7 +832,7 @@ next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, Connec
%% field in TLS-1.3 client hello). The versions are instead negotiated with an hello extension. When
%% decoding the server_hello messages we want to go through TLS-1.3 decode functions to be able
%% to handle TLS-1.3 extensions if TLS-1.3 will be the negotiated version.
-handle_unnegotiated_version({3,3} , #{versions := [{3,4} = Version |_]} = Options, Data, Buffer, client, hello) ->
+handle_unnegotiated_version(?LEGACY_VERSION , #{versions := [?TLS_1_3 = Version |_]} = Options, Data, Buffer, client, hello) ->
%% The effective version for decoding the server hello message should be the TLS-1.3. Possible coalesced TLS-1.2
%% server handshake messages should be decoded with the negotiated version in later state.
<<_:8, ?UINT24(Length), _/binary>> = Data,
@@ -830,7 +840,7 @@ handle_unnegotiated_version({3,3} , #{versions := [{3,4} = Version |_]} = Option
{HSPacket, <<>> = NewHsBuffer} = tls_handshake:get_tls_handshakes(Version, FirstPacket, Buffer, Options),
{HSPacket, NewHsBuffer, RecordRest};
%% TLS-1.3 RetryRequest
-handle_unnegotiated_version({3,3} , #{versions := [{3,4} = Version |_]} = Options, Data, Buffer, client, wait_sh) ->
+handle_unnegotiated_version(?TLS_1_2 , #{versions := [?TLS_1_3 = Version |_]} = Options, Data, Buffer, client, wait_sh) ->
tls_handshake:get_tls_handshakes(Version, Data, Buffer, Options);
%% When the `negotiated_version` variable is not yet set use the highest supported version.
handle_unnegotiated_version(undefined, #{versions := [Version|_]} = Options, Data, Buff, _, _) ->
diff --git a/lib/ssl/src/tls_gen_connection_1_3.erl b/lib/ssl/src/tls_gen_connection_1_3.erl
new file mode 100644
index 0000000000..eec4aa324e
--- /dev/null
+++ b/lib/ssl/src/tls_gen_connection_1_3.erl
@@ -0,0 +1,382 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(tls_gen_connection_1_3).
+
+-include("ssl_alert.hrl").
+-include("ssl_connection.hrl").
+-include("tls_connection.hrl").
+-include("tls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
+
+%% Internal API
+
+
+% gen_statem state help functions
+-export([initial_state/8,
+ user_hello/3,
+ wait_cert/3,
+ wait_cv/3,
+ connection/3,
+ downgrade/3
+ ]).
+
+-export([maybe_queue_change_cipher_spec/2,
+ maybe_prepend_change_cipher_spec/2,
+ maybe_append_change_cipher_spec/2,
+ handle_change_cipher_spec/4,
+ handle_middlebox/1,
+ handle_resumption/2,
+ send_key_update/2,
+ update_cipher_key/2,
+ do_maybe/0]).
+
+%%--------------------------------------------------------------------
+%% Internal API
+%%--------------------------------------------------------------------
+initial_state(Role, Sender, Host, Port, Socket,
+ {SSLOptions, SocketOptions, Trackers}, User,
+ {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
+ %% Use highest supported version for client/server random nonce generation
+ #{versions := [Version|_]} = SSLOptions,
+ MaxEarlyDataSize = init_max_early_data_size(Role),
+ ConnectionStates = tls_record:init_connection_states(Role,
+ Version,
+ disabled,
+ MaxEarlyDataSize),
+ UserMonitor = erlang:monitor(process, User),
+ InitStatEnv = #static_env{
+ role = Role,
+ transport_cb = CbModule,
+ protocol_cb = tls_gen_connection,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ passive_tag = PassiveTag,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ trackers = Trackers
+ },
+ #state{
+ static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history =
+ ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first}
+ },
+ connection_env = #connection_env{user_application = {UserMonitor, User}},
+ socket_options = SocketOptions,
+ ssl_options = SSLOptions,
+ session = #session{is_resumable = false,
+ session_id =
+ ssl_session:legacy_session_id(SSLOptions)},
+ connection_states = ConnectionStates,
+ protocol_buffers = #protocol_buffers{},
+ user_data_buffer = {[],0,[]},
+ start_or_recv_from = undefined,
+ flight_buffer = [],
+ protocol_specific = #{sender => Sender,
+ active_n => internal_active_n(SSLOptions, Socket),
+ active_n_toggle => true
+ }
+ }.
+
+user_hello(info, {'DOWN', _, _, _, _} = Event, State) ->
+ ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State);
+user_hello(_, _, _) ->
+ {keep_state_and_data, [postpone]}.
+
+wait_cert(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_cert(internal = Type, #change_cipher_spec{} = Msg,
+ #state{session = #session{session_id = Id}} = State)
+ when Id =/= ?EMPTY_ID ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+wait_cert(internal,
+ #certificate_1_3{} = Certificate, State0) ->
+ case do_wait_cert(Certificate, State0) of
+ {#alert{} = Alert, State} ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_cert, State);
+ {State, NextState} ->
+ tls_gen_connection:next_event(NextState, no_record, State)
+ end;
+wait_cert(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cert(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+wait_cv(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_cv(internal = Type, #change_cipher_spec{} = Msg,
+ #state{session = #session{session_id = Id}} = State)
+ when Id =/= ?EMPTY_ID ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+wait_cv(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cv(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+connection(enter, _, State) ->
+ {keep_state, State};
+connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
+ handle_new_session_ticket(NewSessionTicket, State),
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+
+connection(internal, #key_update{} = KeyUpdate, State0) ->
+ case handle_key_update(KeyUpdate, State0) of
+ {ok, State} ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+ {error, State, Alert} ->
+ ssl_gen_statem:handle_own_alert(Alert, connection, State),
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State)
+ end;
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env =
+ #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} =
+ State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+downgrade(enter, _, State) ->
+ {keep_state, State};
+downgrade(internal, #new_session_ticket{} = NewSessionTicket, State) ->
+ _ = handle_new_session_ticket(NewSessionTicket, State),
+ {next_state, ?FUNCTION_NAME, State};
+downgrade(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%% Description: Enqueues a change_cipher_spec record as the first/last
+%% message of the current flight buffer
+maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0,
+ first) ->
+ {State, FlightBuffer} =
+ maybe_prepend_change_cipher_spec(State0, FlightBuffer0),
+ State#state{flight_buffer = FlightBuffer};
+maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0,
+ last) ->
+ {State, FlightBuffer} = maybe_append_change_cipher_spec(State0,
+ FlightBuffer0),
+ State#state{flight_buffer = FlightBuffer}.
+
+handle_change_cipher_spec(Type, Msg, StateName,
+ #state{protocol_specific = PS0} = State) ->
+ case maps:get(change_cipher_spec, PS0) of
+ ignore ->
+ PS = PS0#{change_cipher_spec => fail},
+ tls_gen_connection:next_event(StateName, no_record,
+ State#state{protocol_specific = PS});
+ fail ->
+ ssl_gen_statem:handle_common_event(Type, Msg, StateName, State)
+ end.
+
+handle_middlebox(#state{protocol_specific = PS} = State0) ->
+ %% Always be prepared to ignore one change cipher spec
+ %% for maximum interopablility, even if middlebox mode
+ %% is not enabled.
+ State0#state{protocol_specific = PS#{change_cipher_spec => ignore}}.
+
+handle_resumption(State, undefined) ->
+ State;
+handle_resumption(#state{handshake_env = HSEnv0} = State, _) ->
+ HSEnv = HSEnv0#handshake_env{resumption = true},
+ State#state{handshake_env = HSEnv}.
+
+do_maybe() ->
+ Ref = erlang:make_ref(),
+ Ok = fun(ok) -> ok;
+ ({ok,R}) -> R;
+ ({error,Reason}) ->
+ throw({Ref,Reason})
+ end,
+ {Ref,Ok}.
+
+%% Take care of including a change_cipher_spec message in the
+%% correct place if middlebox mod is used. From RFC: 8446 "D.4.
+%% Middlebox Compatibility Mode If not offering early data, the
+%% client sends a dummy change_cipher_spec record (see the third
+%% paragraph of Section 5) immediately before its second flight.
+%% This may either be before its second ClientHello or before its
+%% encrypted handshake flight. If offering early data, the
+%% record is placed immediately after the first ClientHello."
+maybe_prepend_change_cipher_spec(#state{
+ session = #session{session_id = Id},
+ handshake_env =
+ #handshake_env{
+ change_cipher_spec_sent = false}
+ = HSEnv}
+ = State, Bin) when Id =/= ?EMPTY_ID ->
+ CCSBin = tls_handshake_1_3:create_change_cipher_spec(State),
+ {State#state{handshake_env =
+ HSEnv#handshake_env{change_cipher_spec_sent = true}},
+ [CCSBin|Bin]};
+maybe_prepend_change_cipher_spec(State, Bin) ->
+ {State, Bin}.
+
+maybe_append_change_cipher_spec(#state{
+ session = #session{session_id = Id},
+ handshake_env =
+ #handshake_env{
+ change_cipher_spec_sent = false}
+ = HSEnv}
+ = State, Bin) when Id =/= ?EMPTY_ID ->
+ CCSBin = tls_handshake_1_3:create_change_cipher_spec(State),
+ {State#state{handshake_env =
+ HSEnv#handshake_env{change_cipher_spec_sent = true}},
+ Bin ++ [CCSBin]};
+maybe_append_change_cipher_spec(State, Bin) ->
+ {State, Bin}.
+
+send_key_update(Sender, Type) ->
+ KeyUpdate = tls_handshake_1_3:key_update(Type),
+ tls_sender:send_post_handshake(Sender, KeyUpdate).
+
+update_cipher_key(ConnStateName, #state{connection_states = CS0} = State0) ->
+ CS = update_cipher_key(ConnStateName, CS0),
+ State0#state{connection_states = CS};
+update_cipher_key(ConnStateName, CS0) ->
+ #{security_parameters := SecParams0,
+ cipher_state := CipherState0} = ConnState0 = maps:get(ConnStateName, CS0),
+ HKDF = SecParams0#security_parameters.prf_algorithm,
+ CipherSuite = SecParams0#security_parameters.cipher_suite,
+ ApplicationTrafficSecret0 =
+ SecParams0#security_parameters.application_traffic_secret,
+ ApplicationTrafficSecret =
+ tls_v1:update_traffic_secret(HKDF,
+ ApplicationTrafficSecret0),
+
+ %% Calculate traffic keys
+ KeyLength = tls_v1:key_length(CipherSuite),
+ {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, KeyLength,
+ ApplicationTrafficSecret),
+
+ SecParams = SecParams0#security_parameters{application_traffic_secret = ApplicationTrafficSecret},
+ CipherState = CipherState0#cipher_state{key = Key, iv = IV},
+ ConnState = ConnState0#{security_parameters => SecParams,
+ cipher_state => CipherState,
+ sequence_number => 0},
+ CS0#{ConnStateName => ConnState}.
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+
+do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
+ {Ref,Maybe} = do_maybe(),
+ try
+ Maybe(tls_handshake_1_3:process_certificate(Certificate, State0))
+ catch
+ {Ref, #alert{} = Alert} ->
+ {Alert, State0};
+ {Ref, {#alert{} = Alert, State}} ->
+ {Alert, State}
+ end.
+
+handle_new_session_ticket(_, #state{ssl_options = #{session_tickets := disabled}}) ->
+ ok;
+handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce}
+ = NewSessionTicket,
+ #state{connection_states = ConnectionStates,
+ ssl_options = #{session_tickets := SessionTickets} = SslOpts,
+ connection_env = #connection_env{user_application = {_, User}}})
+ when SessionTickets =:= manual ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ CipherSuite = SecParams#security_parameters.cipher_suite,
+ #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ HKDF = SecParams#security_parameters.prf_algorithm,
+ RMS = SecParams#security_parameters.resumption_master_secret,
+ PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
+ SNI = maps:get(server_name_indication, SslOpts, undefined),
+ send_ticket_data(User, NewSessionTicket, {Cipher, HKDF}, SNI, PSK);
+handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce}
+ = NewSessionTicket,
+ #state{connection_states = ConnectionStates,
+ ssl_options = #{session_tickets := SessionTickets} = SslOpts})
+ when SessionTickets =:= auto ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ CipherSuite = SecParams#security_parameters.cipher_suite,
+ #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ HKDF = SecParams#security_parameters.prf_algorithm,
+ RMS = SecParams#security_parameters.resumption_master_secret,
+ PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
+ SNI = maps:get(server_name_indication, SslOpts, undefined),
+ tls_client_ticket_store:store_ticket(NewSessionTicket, {Cipher, HKDF}, SNI, PSK).
+
+send_ticket_data(User, NewSessionTicket, CipherSuite, SNI, PSK) ->
+ Timestamp = erlang:system_time(millisecond),
+ TicketData = #{cipher_suite => CipherSuite,
+ sni => SNI,
+ psk => PSK,
+ timestamp => Timestamp,
+ ticket => NewSessionTicket},
+ User ! {ssl, session_ticket, TicketData}.
+
+handle_key_update(#key_update{request_update = update_not_requested}, State0) ->
+ %% Update read key in connection
+ {ok, update_cipher_key(current_read, State0)};
+handle_key_update(#key_update{request_update = update_requested},
+ #state{protocol_specific = #{sender := Sender}} = State0) ->
+ %% Update read key in connection
+ State1 = update_cipher_key(current_read, State0),
+ %% Send key_update and update sender's write key
+ case send_key_update(Sender, update_not_requested) of
+ ok ->
+ {ok, State1};
+ {error, Reason} ->
+ {error, State1, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason)}
+ end.
+
+init_max_early_data_size(client) ->
+ %% Disable trial decryption on the client side
+ %% Servers do trial decryption of max_early_data bytes of plain text.
+ %% Setting it to 0 means that a decryption error will result in an Alert.
+ 0;
+init_max_early_data_size(server) ->
+ ssl_config:get_max_early_data_size().
+
+internal_active_n(#{ktls := true}, Socket) ->
+ inet:setopts(Socket, [{packet, ssl_tls}]),
+ 1;
+internal_active_n(#{erl_dist := true}, _) ->
+ %% Start with a random number between 1 and ?INTERNAL_ACTIVE_N
+ %% In most cases distribution connections are established all at
+ %% the same time, and flow control engages with ?INTERNAL_ACTIVE_N for
+ %% all connections. Which creates a wave of "passive" messages, leading
+ %% to significant bump of memory & scheduler utilisation. Starting with
+ %% a random number between 1 and ?INTERNAL_ACTIVE_N helps to spread the
+ %% spike.
+ erlang:system_time() rem ?INTERNAL_ACTIVE_N + 1;
+internal_active_n(_,_) ->
+ case application:get_env(ssl, internal_active_n) of
+ {ok, N} when is_integer(N) ->
+ N;
+ _ ->
+ ?INTERNAL_ACTIVE_N
+ end.
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 7704ff7b6b..ec53b65959 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@
-export([get_tls_handshakes/4, decode_handshake/3]).
%% Handshake helper
--export([ocsp_nonce/2]).
+-export([ocsp_nonce/1]).
-type tls_handshake() :: #client_hello{} | ssl_handshake:ssl_handshake().
@@ -74,15 +74,15 @@ client_hello(_Host, _Port, ConnectionStates,
%% legacy_version field MUST be set to 0x0303, which is the version
%% number for TLS 1.2.
LegacyVersion =
- case tls_record:is_higher(Version, {3,2}) of
+ case tls_record:is_higher(Version, ?TLS_1_1) of
true ->
- {3,3};
+ ?TLS_1_2;
false ->
Version
end,
- #{security_parameters := SecParams} =
+ #{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates, read),
- AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
+ AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
Extensions = ssl_handshake:client_hello_extensions(Version,
AvailableCipherSuites,
SslOpts,
@@ -157,24 +157,26 @@ hello(#server_hello{server_version = LegacyVersion,
cipher_suite = CipherSuite,
compression_method = Compression,
session_id = SessionId,
- extensions = #{server_hello_selected_version :=
- #server_hello_selected_version{selected_version = Version}} = HelloExt},
- #{versions := SupportedVersions,
- ocsp_stapling := Stapling} = SslOpt,
+ extensions =
+ #{server_hello_selected_version :=
+ #server_hello_selected_version{
+ selected_version = Version}} = HelloExt},
+ #{versions := SupportedVersions} = SslOpt,
ConnectionStates0, Renegotiation, OldId) ->
+ Stapling = maps:get(ocsp_stapling, SslOpt, ?DEFAULT_OCSP_STAPLING),
%% In TLS 1.3, the TLS server indicates its version using the "supported_versions" extension
%% (Section 4.2.1), and the legacy_version field MUST be set to 0x0303, which is the version
%% number for TLS 1.2.
%% The "supported_versions" extension is supported from TLS 1.2.
- case LegacyVersion > {3,3} orelse
- LegacyVersion =:= {3,3} andalso Version < {3,3} of
+ case ?TLS_LT(LegacyVersion, ?TLS_1_2) orelse
+ LegacyVersion =:= ?TLS_1_2 andalso ?TLS_LT(Version, ?TLS_1_2) of
true ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER));
false ->
case tls_record:is_acceptable_version(Version, SupportedVersions) of
true ->
case Version of
- {3,3} ->
+ ?TLS_1_2 ->
IsNew = ssl_session:is_new(OldId, SessionId),
%% TLS 1.2 ServerHello with "supported_versions" (special case)
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
@@ -182,8 +184,9 @@ hello(#server_hello{server_version = LegacyVersion,
ConnectionStates0, Renegotiation, IsNew);
SelectedVersion ->
%% TLS 1.3
- {next_state, wait_sh, SelectedVersion, #{ocsp_stapling => Stapling,
- ocsp_expect => ocsp_expect(Stapling)}}
+ {next_state, wait_sh, SelectedVersion,
+ #{ocsp_stapling => Stapling,
+ ocsp_expect => ocsp_expect(Stapling)}}
end;
false ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
@@ -305,14 +308,17 @@ get_tls_handshakes(Version, Data, Buffer, Options) ->
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec ocsp_nonce(boolean(), boolean()) -> binary() | undefined.
+-spec ocsp_nonce(map()) -> binary() | undefined.
%%
%% Description: Get an OCSP nonce
%%--------------------------------------------------------------------
-ocsp_nonce(true, true) ->
- public_key:der_encode('Nonce', crypto:strong_rand_bytes(8));
-ocsp_nonce(_OcspNonceOpt, _OcspStaplingOpt) ->
- undefined.
+ocsp_nonce(SslOpts) ->
+ case maps:get(ocsp_stapling, SslOpts, disabled) of
+ #{ocsp_nonce := true} ->
+ public_key:der_encode('Nonce', crypto:strong_rand_bytes(8));
+ _ ->
+ undefined
+ end.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -336,7 +342,7 @@ handle_client_hello(Version,
ClientSignatureSchemes = get_signature_ext(signature_algs_cert, HelloExt, Version),
AvailableHashSigns = ssl_handshake:available_signature_algs(
ClientHashSigns, SupportedHashSigns, Version),
- ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, Version, ECCOrder),
+ ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder),
{Type, #session{cipher_suite = CipherSuite,
own_certificates = [OwnCert |_]} = Session1}
= ssl_handshake:select_session(SugesstedId, CipherSuites,
@@ -403,9 +409,9 @@ do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) -
end.
%%--------------------------------------------------------------------
-enc_handshake(#hello_request{}, {3, N}) when N < 4 ->
+enc_handshake(#hello_request{}, Version) when ?TLS_LT(Version, ?TLS_1_3)->
{?HELLO_REQUEST, <<>>};
-enc_handshake(#client_hello{client_version = {Major, Minor} = Version,
+enc_handshake(#client_hello{client_version = ServerVersion,
random = Random,
session_id = SessionID,
cipher_suites = CipherSuites,
@@ -416,35 +422,37 @@ enc_handshake(#client_hello{client_version = {Major, Minor} = Version,
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
- ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions, Version),
+ ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions),
+ {Major,Minor} = ServerVersion,
{?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
?UINT16(CsLength), BinCipherSuites/binary,
?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>};
-enc_handshake(HandshakeMsg, {3, 4}) ->
+enc_handshake(HandshakeMsg, ?TLS_1_3) ->
tls_handshake_1_3:encode_handshake(HandshakeMsg);
enc_handshake(HandshakeMsg, Version) ->
ssl_handshake:encode_handshake(HandshakeMsg, Version).
%%--------------------------------------------------------------------
get_tls_handshakes_aux(Version, <<?BYTE(Type), ?UINT24(Length),
- Body:Length/binary,Rest/binary>>,
+ Body:Length/binary,Rest/binary>>,
#{log_level := LogLevel} = Opts, Acc) ->
Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
try decode_handshake(Version, Type, Body) of
- Handshake ->
+ Handshake ->
ssl_logger:debug(LogLevel, inbound, 'handshake', Handshake),
- get_tls_handshakes_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc])
+ get_tls_handshakes_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc])
catch
- error:Reason:ST ->
+ error:Reason:ST ->
?SSL_LOG(info, handshake_error, [{reason,Reason}, {stacktrace, ST}]),
- throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, handshake_decode_error))
+ throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, handshake_decode_error))
end;
get_tls_handshakes_aux(_Version, Data, _, Acc) ->
{lists:reverse(Acc), Data}.
-decode_handshake({3, N}, ?HELLO_REQUEST, <<>>) when N < 4 ->
+decode_handshake(Version, ?HELLO_REQUEST, <<>>)
+ when ?TLS_LT(Version, ?TLS_1_3) ->
#hello_request{};
decode_handshake(Version, ?CLIENT_HELLO,
<<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
@@ -463,7 +471,7 @@ decode_handshake(Version, ?CLIENT_HELLO,
compression_methods = erlang:binary_to_list(Comp_methods),
extensions = DecodedExtensions
};
-decode_handshake({3, 4}, Tag, Msg) ->
+decode_handshake(?TLS_1_3, Tag, Msg) ->
tls_handshake_1_3:decode_handshake(Tag, Msg);
decode_handshake(Version, Tag, Msg) ->
ssl_handshake:decode_handshake(Version, Tag, Msg).
@@ -474,7 +482,7 @@ ocsp_expect(true) ->
ocsp_expect(_) ->
no_staple.
-get_signature_ext(Ext, HelloExt, {3,3}) ->
+get_signature_ext(Ext, HelloExt, ?TLS_1_2) ->
case maps:get(Ext, HelloExt, undefined) of
%% Signature algorithms was not sent
undefined ->
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 750a79887f..0861db4607 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -38,31 +38,58 @@
-export([encode_handshake/1, decode_handshake/2]).
%% Create handshake messages
--export([certificate/5,
- certificate_verify/4,
- encrypted_extensions/1,
- key_update/1]).
-
--export([do_start/2,
- do_negotiated/2,
- do_wait_cert/2,
- do_wait_cv/2,
- do_wait_finished/2,
- do_wait_sh/2,
- do_wait_ee/2,
- do_wait_cert_cr/2,
- do_wait_eoed/2,
- early_data_size/1,
- get_ticket_data/3,
+-export([server_hello/5,
+ maybe_add_cookie_extension/2,
maybe_add_binders/3,
maybe_add_binders/4,
maybe_add_early_data_indication/3,
- maybe_automatic_session_resumption/1,
- maybe_send_early_data/1]).
+ supported_groups_from_extensions/1,
+ maybe_hello_retry_request/2,
+ certificate/5,
+ certificate_verify/4,
+ certificate_request/5,
+ encrypted_extensions/1,
+ key_update/1,
+ finished/1,
+ create_change_cipher_spec/1
+ ]).
+
+%% Handle handshake messages
+-export([process_certificate_request/2,
+ process_certificate/2,
+ calculate_handshake_secrets/5,
+ verify_certificate_verify/2,
+ validate_finished/2,
+ maybe_calculate_resumption_master_secret/1,
+ replace_ch1_with_message_hash/1,
+ select_common_groups/2,
+ verify_signature_algorithm/2,
+ forget_master_secret/1,
+ set_client_random/2,
+ handle_pre_shared_key/3,
+ update_start_state/2,
+ get_signature_scheme_list/1,
+ get_certificate_authorities/1,
+ get_certificate_params/1,
+ select_sign_algo/5
+ ]).
+
+-export([early_data_size/1,
+ get_pre_shared_key/2,
+ get_pre_shared_key/4,
+ get_pre_shared_key_early_data/2,
+ get_supported_groups/1,
+ calculate_traffic_secrets/1,
+ calculate_client_early_traffic_secret/5,
+ calculate_client_early_traffic_secret/2,
+ encode_early_data/2,
+ get_ticket_data/3,
+ ciphers_for_early_data/1,
+ choose_ticket/2]).
-export([get_max_early_data/1,
is_valid_binder/4,
- maybe/0,
+ check_cert_sign_algo/4,
path_validation/10]).
%% crypto:hash(sha256, "HelloRetryRequest").
@@ -79,7 +106,7 @@ server_hello(MsgType, SessionId, KeyShare, PSK, ConnectionStates) ->
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates, read),
Extensions = server_hello_extensions(MsgType, KeyShare, PSK),
- #server_hello{server_version = {3,3}, %% legacy_version
+ #server_hello{server_version = ?LEGACY_VERSION, %% legacy_version
cipher_suite = SecParams#security_parameters.cipher_suite,
compression_method = 0, %% legacy attribute
random = server_hello_random(MsgType, SecParams),
@@ -96,19 +123,20 @@ server_hello(MsgType, SessionId, KeyShare, PSK, ConnectionStates) ->
%% ClientHello, with the exception of optionally the "cookie" (see
%% Section 4.2.2) extension.
server_hello_extensions(hello_retry_request = MsgType, KeyShare, _) ->
- SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
- Extensions = #{server_hello_selected_version => SupportedVersions},
+ Extensions = server_hello_extensions_versions(),
ssl_handshake:add_server_share(MsgType, Extensions, KeyShare);
server_hello_extensions(MsgType, KeyShare, undefined) ->
- SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
- Extensions = #{server_hello_selected_version => SupportedVersions},
+ Extensions = server_hello_extensions_versions(),
ssl_handshake:add_server_share(MsgType, Extensions, KeyShare);
server_hello_extensions(MsgType, KeyShare, {SelectedIdentity, _}) ->
- SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ Extensions = server_hello_extensions_versions(),
PreSharedKey = #pre_shared_key_server_hello{selected_identity = SelectedIdentity},
- Extensions = #{server_hello_selected_version => SupportedVersions,
- pre_shared_key => PreSharedKey},
- ssl_handshake:add_server_share(MsgType, Extensions, KeyShare).
+ ssl_handshake:add_server_share(MsgType, Extensions#{pre_shared_key => PreSharedKey}, KeyShare).
+
+server_hello_extensions_versions() ->
+ SupportedVersions = #server_hello_selected_version{selected_version = ?TLS_1_3},
+ #{server_hello_selected_version => SupportedVersions}.
+
server_hello_random(server_hello, #security_parameters{server_random = Random}) ->
@@ -150,29 +178,9 @@ maybe_add_cookie_extension(undefined, ClientHello) ->
ClientHello;
maybe_add_cookie_extension(Cookie,
#client_hello{extensions = Extensions0} = ClientHello) ->
- Extensions = Extensions0#{cookie => #cookie{cookie = Cookie}},
+ Extensions = Extensions0#{cookie => Cookie},
ClientHello#client_hello{extensions = Extensions}.
-validate_cookie(_Cookie, #state{ssl_options = #{cookie := false}}) ->
- ok;
-validate_cookie(undefined, #state{ssl_options = #{cookie := true}}) ->
- ok;
-validate_cookie(Cookie0, #state{ssl_options = #{cookie := true},
- handshake_env =
- #handshake_env{
- tls_handshake_history =
- {[_CH2,_HRR,MessageHash|_], _},
- cookie_iv_shard = {IV, Shard}}}) ->
- Cookie = ssl_cipher:decrypt_data(<<"cookie">>, Cookie0, Shard, IV),
- case Cookie =:= iolist_to_binary(MessageHash) of
- true ->
- ok;
- false ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
- end;
-validate_cookie(_,_) ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}.
-
encrypted_extensions(#state{handshake_env = HandshakeEnv}) ->
E0 = #{},
E1 = case HandshakeEnv#handshake_env.alpn of
@@ -203,7 +211,6 @@ encrypted_extensions(#state{handshake_env = HandshakeEnv}) ->
extensions = E
}.
-
certificate_request(SignAlgs0, SignAlgsCert0, CertDbHandle, CertDbRef, CertAuthBool) ->
%% Input arguments contain TLS 1.2 algorithms due to backward compatibility
%% reasons. These {Hash, Algo} tuples must be filtered before creating the
@@ -237,14 +244,14 @@ add_signature_algorithms_cert(Extensions, SignAlgsCert) ->
filter_tls13_algs(undefined) -> undefined;
filter_tls13_algs(Algo) ->
- lists:foldl(fun(Atom, Acc) when is_atom(Atom) ->
+ lists:foldl(fun(Atom, Acc) when is_atom(Atom) ->
[Atom | Acc];
({sha512, rsa}, Acc) ->
[rsa_pkcs1_sha512 | Acc];
({sha384, rsa}, Acc) ->
[rsa_pkcs1_sha384 | Acc];
({sha256, rsa}, Acc) ->
- [rsa_pkcs1_sha256 | Acc];
+ [rsa_pkcs1_sha256 | Acc];
({sha, rsa}, Acc) ->
[rsa_pkcs1_sha1 | Acc];
({sha, ecdsa}, Acc) ->
@@ -337,6 +344,22 @@ certificate_verify(PrivateKey, SignatureScheme,
{error, Alert}
end.
+%% For reasons of backward compatibility with middleboxes (see
+%% Appendix D.4), the HelloRetryRequest message uses the same structure
+%% as the ServerHello, but with Random set to the special value of the
+%% SHA-256 of "HelloRetryRequest":
+%%
+%% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
+%% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
+%%
+%% Upon receiving a message with type server_hello, implementations MUST
+%% first examine the Random value and, if it matches this value, process
+%% it as described in Section 4.1.4).
+maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello,
+ #state{protocol_specific = PS} = State0) ->
+ {error, {State0#state{protocol_specific = PS#{hello_retry => true}}, start, ServerHello}};
+maybe_hello_retry_request(_, _) ->
+ ok.
finished(#state{connection_states = ConnectionStates,
handshake_env =
@@ -357,19 +380,167 @@ finished(#state{connection_states = ConnectionStates,
key_update(Type) ->
#key_update{request_update = Type}.
+create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) ->
+ %% Dummy connection_states with NULL cipher
+ ConnectionStates =
+ #{current_write =>
+ #{compression_state => undefined,
+ cipher_state => undefined,
+ sequence_number => 1,
+ security_parameters =>
+ #security_parameters{
+ bulk_cipher_algorithm = 0,
+ compression_algorithm = ?NULL,
+ mac_algorithm = ?NULL
+ },
+ mac_secret => undefined}},
+ {BinChangeCipher, _} =
+ tls_record:encode_change_cipher_spec(?LEGACY_VERSION, ConnectionStates),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher),
+ [BinChangeCipher].
+
+%%====================================================================
+%% Handle handshake messages
+%%====================================================================
+
+process_certificate_request(#certificate_request_1_3{
+ extensions = Extensions},
+ #state{ssl_options = #{signature_algs := ClientSignAlgs},
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts,
+ negotiated_version = Version},
+ static_env = #static_env{cert_db = CertDbHandle, cert_db_ref = CertDbRef},
+ session = Session0} =
+ State) ->
+ ServerSignAlgs = get_signature_scheme_list(
+ maps:get(signature_algs, Extensions, undefined)),
+ ServerSignAlgsCert = get_signature_scheme_list(
+ maps:get(signature_algs_cert, Extensions, undefined)),
+ CertAuths = get_certificate_authorities(maps:get(certificate_authorities, Extensions, undefined)),
+
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, Version),
+ Session = select_client_cert_key_pair(Session0, CertKeyPairs,
+ ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs,
+ CertDbHandle, CertDbRef, CertAuths, undefined),
+ {ok, {State#state{client_certificate_status = requested, session = Session}, wait_cert}}.
+
+process_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #{fail_if_no_peer_cert := false}} = State) ->
+ {ok, {State, wait_finished}};
+process_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #{fail_if_no_peer_cert := true}} = State0) ->
+ %% At this point the client believes that the connection is up and starts using
+ %% its traffic secrets. In order to be able send an proper Alert to the client
+ %% the server should also change its connection state and use the traffic
+ %% secrets.
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}};
+process_certificate(#certificate_1_3{certificate_list = CertEntries},
+ #state{ssl_options = SslOptions,
+ static_env =
+ #static_env{
+ role = Role,
+ host = Host,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ crl_db = CRLDbHandle},
+ handshake_env =
+ #handshake_env{
+ ocsp_stapling_state = OcspState}} = State0) ->
+ case validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
+ SslOptions, CRLDbHandle, Role, Host, OcspState) of
+ #alert{} = Alert ->
+ State = update_encryption_state(Role, State0),
+ {error, {Alert, State}};
+ {PeerCert, PublicKeyInfo} ->
+ State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
+ {ok, {State, wait_cv}}
+ end.
+
+verify_certificate_verify(#state{static_env = #static_env{role = Role},
+ connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ public_key_info = PublicKeyInfo,
+ tls_handshake_history = HHistory}} = State0,
+ #certificate_verify_1_3{
+ algorithm = SignatureScheme,
+ signature = Signature}) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, write),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ {HashAlgo, SignAlg, _} =
+ ssl_cipher:scheme_to_components(SignatureScheme),
+
+ Messages = get_handshake_context_cv(HHistory),
+
+ Context = lists:reverse(Messages),
+
+ %% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
+ THash = tls_v1:transcript_hash(Context, HKDFAlgo),
+
+ ContextString = peer_context_string(Role),
+
+ %% Digital signatures use the hash function defined by the selected signature
+ %% scheme.
+ case verify(THash, ContextString, HashAlgo, SignAlg, Signature, PublicKeyInfo) of
+ {ok, true} ->
+ {ok, {State0, wait_finished}};
+ {ok, false} ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ "Failed to verify CertificateVerify"), State}};
+ {error, #alert{} = Alert} ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {Alert, State}}
+ end.
+
+%% Recipients of Finished messages MUST verify that the contents are
+%% correct and if incorrect MUST terminate the connection with a
+%% "decrypt_error" alert.
+validate_finished(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = {Messages0, _}}}, VerifyData) ->
+ #{security_parameters := SecParamsR,
+ cipher_state := #cipher_state{finished_key = FinishedKey}} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ %% Drop the peer's finished message, it is not part of the handshake context
+ %% when the client/server calculates its finished message.
+ [_|Messages] = Messages0,
+
+ ControlData = tls_v1:finished_verify_data(FinishedKey, HKDFAlgo, Messages),
+ compare_verify_data(ControlData, VerifyData).
+
+
+compare_verify_data(Data, Data) ->
+ ok;
+compare_verify_data(_, _) ->
+ {error, ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)}.
%%====================================================================
%% Encode handshake
%%====================================================================
encode_handshake(#certificate_request_1_3{
- certificate_request_context = Context,
+ certificate_request_context = Context,
extensions = Exts})->
EncContext = encode_cert_req_context(Context),
BinExts = encode_extensions(Exts),
{?CERTIFICATE_REQUEST, <<EncContext/binary, BinExts/binary>>};
encode_handshake(#certificate_1_3{
- certificate_request_context = Context,
+ certificate_request_context = Context,
certificate_list = Entries}) ->
EncContext = encode_cert_req_context(Context),
EncEntries = encode_cert_entries(Entries),
@@ -381,12 +552,12 @@ encode_handshake(#certificate_verify_1_3{
EncSign = encode_signature(Signature),
{?CERTIFICATE_VERIFY, <<EncAlgo/binary, EncSign/binary>>};
encode_handshake(#encrypted_extensions{extensions = Exts})->
- {?ENCRYPTED_EXTENSIONS, encode_extensions(Exts)};
+ {?ENCRYPTED_EXTENSIONS, encode_extensions(Exts)};
encode_handshake(#new_session_ticket{
- ticket_lifetime = LifeTime,
- ticket_age_add = Age,
- ticket_nonce = Nonce,
- ticket = Ticket,
+ ticket_lifetime = LifeTime,
+ ticket_age_add = Age,
+ ticket_nonce = Nonce,
+ ticket = Ticket,
extensions = Exts}) ->
TicketSize = byte_size(Ticket),
NonceSize = byte_size(Nonce),
@@ -401,7 +572,27 @@ encode_handshake(#key_update{request_update = Update}) ->
EncUpdate = encode_key_update(Update),
{?KEY_UPDATE, <<EncUpdate/binary>>};
encode_handshake(HandshakeMsg) ->
- ssl_handshake:encode_handshake(HandshakeMsg, {3,4}).
+ ssl_handshake:encode_handshake(HandshakeMsg, ?TLS_1_3).
+
+encode_early_data(Cipher,
+ #state{
+ flight_buffer = Flight0,
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [Version|_],
+ early_data := EarlyData} = _SslOpts0
+ } = State0) ->
+ #state{connection_states =
+ #{current_write :=
+ #{security_parameters := SecurityParameters0} = Write0} = ConnectionStates0} = State0,
+ BulkCipherAlgo = ssl_cipher:bulk_cipher_algorithm(Cipher),
+ SecurityParameters = SecurityParameters0#security_parameters{
+ cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BulkCipherAlgo},
+ Write = Write0#{security_parameters => SecurityParameters},
+ ConnectionStates1 = ConnectionStates0#{current_write => Write},
+ {BinEarlyData, ConnectionStates} = tls_record:encode_data([EarlyData], Version, ConnectionStates1),
+ State0#state{connection_states = ConnectionStates,
+ flight_buffer = Flight0 ++ [BinEarlyData]}.
%%====================================================================
@@ -414,7 +605,7 @@ decode_handshake(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
Cipher_suite:2/binary, ?BYTE(Comp_method),
?UINT16(ExtLen), Extensions:ExtLen/binary>>)
when Random =:= ?HELLO_RETRY_REQUEST_RANDOM ->
- HelloExtensions = ssl_handshake:decode_hello_extensions(Extensions, {3,4}, {Major, Minor},
+ HelloExtensions = ssl_handshake:decode_hello_extensions(Extensions, ?TLS_1_3, {Major, Minor},
hello_retry_request),
#server_hello{
server_version = {Major,Minor},
@@ -436,14 +627,14 @@ decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(CSize), Context:CSize/binary,
extensions = Exts};
decode_handshake(?CERTIFICATE, <<?BYTE(0), ?UINT24(Size), Certs:Size/binary>>) ->
CertList = decode_cert_entries(Certs),
- #certificate_1_3{
+ #certificate_1_3{
certificate_request_context = <<>>,
certificate_list = CertList
};
decode_handshake(?CERTIFICATE, <<?BYTE(CSize), Context:CSize/binary,
?UINT24(Size), Certs:Size/binary>>) ->
CertList = decode_cert_entries(Certs),
- #certificate_1_3{
+ #certificate_1_3{
certificate_request_context = Context,
certificate_list = CertList
};
@@ -461,17 +652,17 @@ decode_handshake(?NEW_SESSION_TICKET, <<?UINT32(LifeTime), ?UINT32(Age),
?UINT16(TicketSize), Ticket:TicketSize/binary,
?UINT16(BinExtSize), BinExts:BinExtSize/binary>>) ->
Exts = decode_extensions(BinExts, encrypted_extensions),
- #new_session_ticket{ticket_lifetime = LifeTime,
- ticket_age_add = Age,
- ticket_nonce = Nonce,
- ticket = Ticket,
+ #new_session_ticket{ticket_lifetime = LifeTime,
+ ticket_age_add = Age,
+ ticket_nonce = Nonce,
+ ticket = Ticket,
extensions = Exts};
decode_handshake(?END_OF_EARLY_DATA, _) ->
#end_of_early_data{};
decode_handshake(?KEY_UPDATE, <<?BYTE(Update)>>) ->
#key_update{request_update = decode_key_update(Update)};
decode_handshake(Tag, HandshakeMsg) ->
- ssl_handshake:decode_handshake({3,4}, Tag, HandshakeMsg).
+ ssl_handshake:decode_handshake(?TLS_1_3, Tag, HandshakeMsg).
is_valid_binder(Binder, HHistory, PSK, Hash) ->
case HHistory of
@@ -537,36 +728,27 @@ decode_key_update(N) ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {request_update,N})).
decode_cert_entries(Entries) ->
- decode_cert_entries(Entries, []).
-
-decode_cert_entries(<<>>, Acc) ->
- lists:reverse(Acc);
-decode_cert_entries(<<?UINT24(DSize), Data:DSize/binary, ?UINT16(Esize), BinExts:Esize/binary,
- Rest/binary>>, Acc) ->
- Exts = decode_extensions(BinExts, certificate_request),
- decode_cert_entries(Rest, [#certificate_entry{data = Data,
- extensions = Exts} | Acc]).
+ [ #certificate_entry{data = Data, extensions = decode_extensions(BinExts, certificate_request)}
+ || <<?UINT24(DSize), Data:DSize/binary, ?UINT16(Esize), BinExts:Esize/binary>> <= Entries ].
encode_extensions(Exts)->
ssl_handshake:encode_extensions(extensions_list(Exts)).
decode_extensions(Exts, MessageType) ->
- ssl_handshake:decode_extensions(Exts, {3,4}, MessageType).
+ ssl_handshake:decode_extensions(Exts, ?TLS_1_3, MessageType).
extensions_list(Extensions) ->
[Ext || {_, Ext} <- maps:to_list(Extensions)].
-
%% TODO: add extensions!
chain_to_cert_list(L) ->
chain_to_cert_list(L, []).
-%%
+
chain_to_cert_list([], Acc) ->
lists:reverse(Acc);
chain_to_cert_list([H|T], Acc) ->
chain_to_cert_list(T, [certificate_entry(H)|Acc]).
-
certificate_entry(DER) ->
#certificate_entry{
data = DER,
@@ -592,7 +774,7 @@ certificate_entry(DER) ->
sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) ->
Content = build_content(Context, THash),
try
- {ok, ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo)}
+ {ok, ssl_handshake:digitally_signed(?TLS_1_3, Content, HashAlgo, PrivateKey, SignAlgo)}
catch throw:Alert ->
{error, Alert}
end.
@@ -600,7 +782,7 @@ sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) ->
verify(THash, Context, HashAlgo, SignAlgo, Signature, PublicKeyInfo) ->
Content = build_content(Context, THash),
- try ssl_handshake:verify_signature({3, 4}, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of
+ try ssl_handshake:verify_signature(?TLS_1_3, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of
Result ->
{ok, Result}
catch
@@ -614,895 +796,6 @@ build_content(Context, THash) ->
<<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>.
-%%====================================================================
-%% Handle handshake messages
-%%====================================================================
-
-
-%% TLS Server
-do_start(#client_hello{cipher_suites = ClientCiphers,
- session_id = SessionId,
- extensions = Extensions} = Hello,
- #state{ssl_options = #{ciphers := ServerCiphers,
- signature_algs := ServerSignAlgs,
- supported_groups := ServerGroups0,
- alpn_preferred_protocols := ALPNPreferredProtocols,
- keep_secrets := KeepSecrets,
- honor_cipher_order := HonorCipherOrder,
- early_data := EarlyDataEnabled}} = State0) ->
- SNI = maps:get(sni, Extensions, undefined),
- EarlyDataIndication = maps:get(early_data, Extensions, undefined),
- {Ref,Maybe} = maybe(),
- try
- ClientGroups0 = Maybe(supported_groups_from_extensions(Extensions)),
- ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
- ServerGroups = Maybe(get_supported_groups(ServerGroups0)),
-
- ClientShares0 = maps:get(key_share, Extensions, undefined),
- ClientShares = get_key_shares(ClientShares0),
-
- OfferedPSKs = get_offered_psks(Extensions),
-
- ClientALPN0 = maps:get(alpn, Extensions, undefined),
- ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
-
- ClientSignAlgs = get_signature_scheme_list(
- maps:get(signature_algs, Extensions, undefined)),
- ClientSignAlgsCert = get_signature_scheme_list(
- maps:get(signature_algs_cert, Extensions, undefined)),
- CertAuths = get_certificate_authorities(maps:get(certificate_authorities, Extensions, undefined)),
- CookieExt = maps:get(cookie, Extensions, undefined),
- Cookie = get_cookie(CookieExt),
-
- #state{connection_states = ConnectionStates0,
- session = Session0,
- connection_env = #connection_env{cert_key_alts = CertKeyAlts}} = State1 =
- Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)),
-
- Maybe(validate_cookie(Cookie, State1)),
-
- %% Handle ALPN extension if ALPN is configured
- ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)),
-
- %% If the server does not select a PSK, then the server independently selects a
- %% cipher suite, an (EC)DHE group and key share for key establishment,
- %% and a signature algorithm/certificate pair to authenticate itself to
- %% the client.
- Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)),
- Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)),
- Maybe(validate_client_key_share(ClientGroups, ClientShares)),
- CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, {3,4}),
- #session{own_certificates = [Cert|_]} = Session =
- Maybe(select_server_cert_key_pair(Session0, CertKeyPairs, ClientSignAlgs,
- ClientSignAlgsCert, CertAuths, State0,
- undefined)),
- {PublicKeyAlgo, _, _, RSAKeySize, Curve} = get_certificate_params(Cert),
-
- %% Select signature algorithm (used in CertificateVerify message).
- SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs, Curve)),
-
- %% Select client public key. If no public key found in ClientShares or
- %% ClientShares is empty, trigger HelloRetryRequest as we were able
- %% to find an acceptable set of parameters but the ClientHello does not
- %% contain sufficient information.
- {Group, ClientPubKey} = get_client_public_key(Groups, ClientShares),
-
- %% Generate server_share
- KeyShare = ssl_cipher:generate_server_share(Group),
-
- State2 = case maps:get(max_frag_enum, Extensions, undefined) of
- MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) ->
- ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
- HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum},
- State1#state{handshake_env = HsEnv1,
- session = Session,
- connection_states = ConnectionStates1};
- _ ->
- State1#state{session = Session}
- end,
-
- State3 = if KeepSecrets =:= true ->
- set_client_random(State2, Hello#client_hello.random);
- true ->
- State2
- end,
-
- State4 = update_start_state(State3,
- #{cipher => Cipher,
- key_share => KeyShare,
- session_id => SessionId,
- group => Group,
- sign_alg => SelectedSignAlg,
- peer_public_key => ClientPubKey,
- alpn => ALPNProtocol}),
-
- %% 4.1.4. Hello Retry Request
- %%
- %% The server will send this message in response to a ClientHello
- %% message if it is able to find an acceptable set of parameters but the
- %% ClientHello does not contain sufficient information to proceed with
- %% the handshake.
- case Maybe(send_hello_retry_request(State4, ClientPubKey, KeyShare, SessionId)) of
- {_, start} = NextStateTuple ->
- NextStateTuple;
- {State5, negotiated} ->
- %% Determine if early data is accepted
- State = handle_early_data(State5, EarlyDataEnabled, EarlyDataIndication),
- %% Exclude any incompatible PSKs.
- PSK = Maybe(handle_pre_shared_key(State, OfferedPSKs, Cipher)),
- Maybe(session_resumption({State, negotiated}, PSK))
- end
- catch
- {Ref, #alert{} = Alert} ->
- Alert
- end;
-%% TLS Client
-do_start(#server_hello{cipher_suite = SelectedCipherSuite,
- session_id = SessionId,
- extensions = Extensions},
- #state{static_env = #static_env{role = client,
- host = Host,
- port = Port,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- protocol_cb = Connection,
- transport_cb = Transport,
- socket = Socket},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
- ocsp_stapling_state = OcspState},
- connection_env = #connection_env{negotiated_version = NegotiatedVersion},
- protocol_specific = PS,
- ssl_options = #{ciphers := ClientCiphers,
- supported_groups := ClientGroups0,
- use_ticket := UseTicket,
- session_tickets := SessionTickets,
- log_level := LogLevel} = SslOpts,
- session = Session0,
- connection_states = ConnectionStates0
- } = State0) ->
- {Ref,Maybe} = maybe(),
- try
- ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
- CookieExt = maps:get(cookie, Extensions, undefined),
- Cookie = get_cookie(CookieExt),
-
- ServerKeyShare = maps:get(key_share, Extensions, undefined),
- SelectedGroup = get_selected_group(ServerKeyShare),
-
- %% Upon receipt of this extension in a HelloRetryRequest, the client
- %% MUST verify that (1) the selected_group field corresponds to a group
- %% which was provided in the "supported_groups" extension in the
- %% original ClientHello and (2) the selected_group field does not
- %% correspond to a group which was provided in the "key_share" extension
- %% in the original ClientHello. If either of these checks fails, then
- %% the client MUST abort the handshake with an "illegal_parameter"
- %% alert.
- Maybe(validate_selected_group(SelectedGroup, ClientGroups)),
-
- Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
-
- %% Otherwise, when sending the new ClientHello, the client MUST
- %% replace the original "key_share" extension with one containing only a
- %% new KeyShareEntry for the group indicated in the selected_group field
- %% of the triggering HelloRetryRequest.
- ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]),
- TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
- OcspNonce = maps:get(ocsp_nonce, OcspState, undefined),
- Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- SessionId, Renegotiation, ClientKeyShare,
- TicketData, OcspNonce, CertDbHandle, CertDbRef),
- %% Echo cookie received in HelloRetryrequest
- Hello1 = maybe_add_cookie_extension(Cookie, Hello0),
-
- %% Update state
- State1 = update_start_state(State0,
- #{cipher => SelectedCipherSuite,
- key_share => ClientKeyShare,
- session_id => SessionId,
- group => SelectedGroup}),
-
- %% Replace ClientHello1 with a special synthetic handshake message
- State2 = replace_ch1_with_message_hash(State1),
- #state{handshake_env = #handshake_env{tls_handshake_history = HHistory0}} = State2,
-
- %% Update pre_shared_key extension with binders (TLS 1.3)
- Hello = tls_handshake_1_3:maybe_add_binders(Hello1, HHistory0, TicketData, NegotiatedVersion),
-
- {BinMsg0, ConnectionStates, HHistory} =
- Connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0),
-
- %% D.4. Middlebox Compatibility Mode
- {#state{handshake_env = HsEnv} = State3, BinMsg} =
- maybe_prepend_change_cipher_spec(State2, BinMsg0),
-
- tls_socket:send(Transport, Socket, BinMsg),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Hello),
- ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
-
- State = State3#state{
- connection_states = ConnectionStates,
- session = Session0#session{session_id = Hello#client_hello.session_id},
- handshake_env = HsEnv#handshake_env{tls_handshake_history = HHistory},
- key_share = ClientKeyShare},
-
- %% If it is a hello_retry and middlebox mode is
- %% used assert the change_cipher_spec message
- %% that the server should send next
- case (maps:get(hello_retry, PS, false)) andalso
- (maps:get(middlebox_comp_mode, SslOpts, true))
- of
- true ->
- {State, hello_retry_middlebox_assert};
- false ->
- {State, wait_sh}
- end
- catch
- {Ref, #alert{} = Alert} ->
- Alert
- end.
-
-do_negotiated({start_handshake, PSK0},
- #state{connection_states = ConnectionStates0,
- handshake_env =
- #handshake_env{
- early_data_accepted = EarlyDataAccepted},
- static_env = #static_env{protocol_cb = Connection},
- session = #session{session_id = SessionId,
- ecc = SelectedGroup,
- dh_public_value = ClientPublicKey},
- ssl_options = #{} = SslOpts,
- key_share = KeyShare} = State0) ->
- ServerPrivateKey = get_server_private_key(KeyShare),
-
- #{security_parameters := SecParamsR} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{prf_algorithm = HKDF} = SecParamsR,
-
- {Ref,Maybe} = maybe(),
- try
- %% Create server_hello
- ServerHello = server_hello(server_hello, SessionId, KeyShare, PSK0, ConnectionStates0),
- State1 = Connection:queue_handshake(ServerHello, State0),
- %% D.4. Middlebox Compatibility Mode
- State2 = maybe_queue_change_cipher_spec(State1, last),
-
- PSK = get_pre_shared_key(PSK0, HKDF),
-
- State3 =
- calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup,
- PSK, State2),
-
- %% Step only write state if early_data is accepted
- State4 =
- case EarlyDataAccepted of
- true ->
- ssl_record:step_encryption_state_write(State3);
- false ->
- %% Read state is overwritten when handshake secrets are set.
- %% Trial_decryption and early_data_accepted must be set here!
- update_current_read(
- ssl_record:step_encryption_state(State3),
- true, %% trial_decryption
- false %% early_data_accepted
- )
-
- end,
-
- %% Create EncryptedExtensions
- EncryptedExtensions = encrypted_extensions(State4),
-
- %% Encode EncryptedExtensions
- State5 = Connection:queue_handshake(EncryptedExtensions, State4),
-
- %% Create and send CertificateRequest ({verify, verify_peer})
- {State6, NextState} = maybe_send_certificate_request(State5, SslOpts, PSK0),
-
- %% Create and send Certificate (if PSK is undefined)
- State7 = Maybe(maybe_send_certificate(State6, PSK0)),
-
- %% Create and send CertificateVerify (if PSK is undefined)
- State8 = Maybe(maybe_send_certificate_verify(State7, PSK0)),
-
- %% Create Finished
- Finished = finished(State8),
-
- %% Encode Finished
- State9 = Connection:queue_handshake(Finished, State8),
-
- %% Send first flight
- {State, _} = Connection:send_handshake_flight(State9),
-
- {State, NextState}
-
- catch
- {Ref, #alert{} = Alert} ->
- Alert;
- error:badarg=Reason:ST ->
- ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]),
- ?ALERT_REC(?ILLEGAL_PARAMETER, illegal_parameter_to_compute_key)
- end.
-
-
-do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
- {Ref,Maybe} = maybe(),
- try
- Maybe(process_certificate(Certificate, State0))
- catch
- {Ref, #alert{} = Alert} ->
- {Alert, State0};
- {Ref, {#alert{} = Alert, State}} ->
- {Alert, State}
- end.
-
-
-do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, #state{static_env = #static_env{role = Role}} = State0) ->
- {Ref,Maybe} = maybe(),
- try
- State1 = case Role of
- server ->
- Maybe(verify_signature_algorithm(State0, CertificateVerify));
- client ->
- State0
- end,
- Maybe(verify_certificate_verify(State1, CertificateVerify))
- catch
- {Ref, {#alert{} = Alert, State}} ->
- {Alert, State}
- end.
-
-%% TLS Server
-do_wait_finished(#finished{verify_data = VerifyData},
- #state{static_env = #static_env{role = server}} = State0) ->
- {Ref,Maybe} = maybe(),
-
- try
- Maybe(validate_finished(State0, VerifyData)),
-
- State1 = calculate_traffic_secrets(State0),
- State2 = maybe_calculate_resumption_master_secret(State1),
- State3 = forget_master_secret(State2),
-
- %% Configure traffic keys
- State4 = ssl_record:step_encryption_state(State3),
-
- %% Send session ticket
- maybe_send_session_ticket(State4)
-
- catch
- {Ref, #alert{} = Alert} ->
- Alert
- end;
-%% TLS Client
-do_wait_finished(#finished{verify_data = VerifyData},
- #state{static_env = #static_env{role = client,
- protocol_cb = Connection}} = State0) ->
-
- {Ref,Maybe} = maybe(),
-
- try
- Maybe(validate_finished(State0, VerifyData)),
- %% D.4. Middlebox Compatibility Mode
- State1 = maybe_queue_change_cipher_spec(State0, first),
- %% Signal change of cipher
- State2 = maybe_send_end_of_early_data(State1),
- %% Maybe send Certificate + CertificateVerify
- State3 = Maybe(maybe_queue_cert_cert_cv(State2)),
- Finished = finished(State3),
- %% Encode Finished
- State4 = Connection:queue_handshake(Finished, State3),
- %% Send first flight
- {State5, _} = Connection:send_handshake_flight(State4),
- State6 = calculate_traffic_secrets(State5),
- State7 = maybe_calculate_resumption_master_secret(State6),
- State8 = forget_master_secret(State7),
- %% Configure traffic keys
- ssl_record:step_encryption_state(State8)
- catch
- {Ref, #alert{} = Alert} ->
- Alert
- end.
-
-
-do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
- session_id = SessionId,
- extensions = Extensions} = ServerHello,
- #state{key_share = ClientKeyShare0,
- ssl_options = #{ciphers := ClientCiphers,
- supported_groups := ClientGroups0,
- session_tickets := SessionTickets,
- use_ticket := UseTicket}} = State0) ->
-
- {Ref,Maybe} = maybe(),
- try
- ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
- ServerKeyShare0 = maps:get(key_share, Extensions, undefined),
- ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),
- SelectedIdentity = get_selected_identity(ServerPreSharedKey),
- ClientKeyShare = get_key_shares(ClientKeyShare0),
-
- %% Go to state 'start' if server replies with 'HelloRetryRequest'.
- Maybe(maybe_hello_retry_request(ServerHello, State0)),
-
- %% Resumption and PSK
- State1 = handle_resumption(State0, SelectedIdentity),
- ServerKeyShare = get_key_shares(ServerKeyShare0),
-
- Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
- Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)),
-
- %% Get server public key
- {SelectedGroup, ServerPublicKey} = get_server_public_key(ServerKeyShare),
-
- {_, ClientPrivateKey} = get_client_private_key([SelectedGroup], ClientKeyShare),
-
- %% Update state
- State2 = update_start_state(State1,
- #{cipher => SelectedCipherSuite,
- key_share => ClientKeyShare0,
- session_id => SessionId,
- group => SelectedGroup,
- peer_public_key => ServerPublicKey}),
-
- #state{connection_states = ConnectionStates} = State2,
- #{security_parameters := SecParamsR} =
- ssl_record:pending_connection_state(ConnectionStates, read),
- #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
-
- PSK = Maybe(get_pre_shared_key(SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity)),
- State3 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup,
- PSK, State2),
- %% State4 = ssl_record:step_encryption_state(State3),
- State4 = ssl_record:step_encryption_state_read(State3),
- {State4, wait_ee}
-
- catch
- {Ref, {State, StateName, ServerHello}} ->
- {State, StateName, ServerHello};
- {Ref, #alert{} = Alert} ->
- Alert
- end.
-
-
-do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) ->
-
- ALPNProtocol0 = maps:get(alpn, Extensions, undefined),
- ALPNProtocol = get_alpn(ALPNProtocol0),
- EarlyDataIndication = maps:get(early_data, Extensions, undefined),
-
- {Ref, Maybe} = maybe(),
-
- try
- %% RFC 6066: handle received/expected maximum fragment length
- Maybe(maybe_max_fragment_length(Extensions, State0)),
-
- %% Check if early_data is accepted/rejected
- State1 = maybe_check_early_data_indication(EarlyDataIndication, State0),
-
- %% Go to state 'wait_finished' if using PSK.
- Maybe(maybe_resumption(State1)),
-
- %% Update state
- #state{handshake_env = HsEnv} = State1,
- State2 = State1#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}},
-
- {State2, wait_cert_cr}
- catch
- {Ref, {State, StateName}} ->
- {State, StateName};
- {Ref, #alert{} = Alert} ->
- Alert
- end.
-
-
-do_wait_cert_cr(#certificate_1_3{} = Certificate, State0) ->
- {Ref,Maybe} = maybe(),
- try
- Maybe(process_certificate(Certificate, State0))
- catch
- {Ref, #alert{} = Alert} ->
- {Alert, State0};
- {Ref, {#alert{} = Alert, State}} ->
- {Alert, State}
- end;
-do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) ->
- {Ref,Maybe} = maybe(),
- try
- Maybe(process_certificate_request(CertificateRequest, State0))
- catch
- {Ref, #alert{} = Alert} ->
- {Alert, State0}
- end.
-
-
-do_wait_eoed(#end_of_early_data{}, State0) ->
- {Ref,_Maybe} = maybe(),
- try
- %% Step read state to enable reading handshake messages from the client.
- %% Write state is already stepped in state 'negotiated'.
- State1 = ssl_record:step_encryption_state_read(State0),
-
- %% Early data has been received, no more early data is expected.
- HsEnv = (State1#state.handshake_env)#handshake_env{early_data_accepted = false},
- State2 = State1#state{handshake_env = HsEnv},
- {State2, wait_finished}
- catch
- {Ref, #alert{} = Alert} ->
- {Alert, State0};
- {Ref, {#alert{} = Alert, State}} ->
- {Alert, State}
- end.
-
-
-%% For reasons of backward compatibility with middleboxes (see
-%% Appendix D.4), the HelloRetryRequest message uses the same structure
-%% as the ServerHello, but with Random set to the special value of the
-%% SHA-256 of "HelloRetryRequest":
-%%
-%% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
-%% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
-%%
-%% Upon receiving a message with type server_hello, implementations MUST
-%% first examine the Random value and, if it matches this value, process
-%% it as described in Section 4.1.4).
-maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello,
- #state{protocol_specific = PS} = State0) ->
- {error, {State0#state{protocol_specific = PS#{hello_retry => true}}, start, ServerHello}};
-maybe_hello_retry_request(_, _) ->
- ok.
-
-maybe_max_fragment_length(Extensions, State) ->
- ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined),
- ClientMaxFragEnum = ssl_handshake:max_frag_enum(
- maps:get(max_fragment_length, State#state.ssl_options, undefined)),
- if ServerMaxFragEnum == ClientMaxFragEnum ->
- ok;
- true ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
- end.
-
-
-maybe_resumption(#state{handshake_env = #handshake_env{resumption = true}} = State) ->
- {error, {State, wait_finished}};
-maybe_resumption(_) ->
- ok.
-
-
-handle_resumption(State, undefined) ->
- State;
-handle_resumption(#state{handshake_env = HSEnv0} = State, _) ->
- HSEnv = HSEnv0#handshake_env{resumption = true},
- State#state{handshake_env = HSEnv}.
-
-%% @doc Enqueues a change_cipher_spec record as the first/last message of
-%% the current flight buffer
-%% @end
-maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, first) ->
- {State, FlightBuffer} = maybe_prepend_change_cipher_spec(State0, FlightBuffer0),
- State#state{flight_buffer = FlightBuffer};
-maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, last) ->
- {State, FlightBuffer} = maybe_append_change_cipher_spec(State0, FlightBuffer0),
- State#state{flight_buffer = FlightBuffer}.
-
-%% @doc Prepends a change_cipher_spec record to the input binary
-%%
-%% It can only prepend the change_cipher_spec record only once in
-%% order to accurately emulate a legacy TLS 1.2 connection.
-%%
-%% D.4. Middlebox Compatibility Mode
-%% If not offering early data, the client sends a dummy
-%% change_cipher_spec record (see the third paragraph of Section 5)
-%% immediately before its second flight. This may either be before
-%% its second ClientHello or before its encrypted handshake flight.
-%% If offering early data, the record is placed immediately after the
-%% first ClientHello.
-%% @end
-maybe_prepend_change_cipher_spec(#state{
- session = #session{session_id = Id},
- handshake_env =
- #handshake_env{
- change_cipher_spec_sent = false} = HSEnv} = State, Bin) when Id =/= ?EMPTY_ID ->
- CCSBin = create_change_cipher_spec(State),
- {State#state{handshake_env =
- HSEnv#handshake_env{change_cipher_spec_sent = true}},
- [CCSBin|Bin]};
-maybe_prepend_change_cipher_spec(State, Bin) ->
- {State, Bin}.
-
-%% @doc Appends a change_cipher_spec record to the input binary
-%% @end
-maybe_append_change_cipher_spec(#state{
- session = #session{session_id = Id},
- handshake_env =
- #handshake_env{
- change_cipher_spec_sent = false} = HSEnv} = State, Bin) when Id =/= ?EMPTY_ID ->
- CCSBin = create_change_cipher_spec(State),
- {State#state{handshake_env =
- HSEnv#handshake_env{change_cipher_spec_sent = true}},
- Bin ++ [CCSBin]};
-maybe_append_change_cipher_spec(State, Bin) ->
- {State, Bin}.
-
-maybe_queue_cert_cert_cv(#state{client_certificate_status = not_requested} = State) ->
- {ok, State};
-maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
- session = #session{session_id = _SessionId,
- own_certificates = OwnCerts},
- ssl_options = #{} = _SslOpts,
- key_share = _KeyShare,
- handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
- static_env = #static_env{
- role = client,
- protocol_cb = Connection,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- socket = _Socket,
- transport_cb = _Transport}
- } = State0) ->
- {Ref,Maybe} = maybe(),
- try
- %% Create Certificate
- Certificate = Maybe(certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, client)),
-
- %% Encode Certificate
- State1 = Connection:queue_handshake(Certificate, State0),
- %% Maybe create and queue CertificateVerify
- State = Maybe(maybe_queue_cert_verify(Certificate, State1)),
- {ok, State}
- catch
- {Ref, #alert{} = Alert} ->
- {error, Alert}
- end.
-
-
-%% Clients MUST send this message whenever authenticating via a certificate
-%% (i.e., when the Certificate message is non-empty).
-maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) ->
- {ok, State};
-maybe_queue_cert_verify(_Certificate,
- #state{connection_states = _ConnectionStates0,
- session = #session{sign_alg = SignatureScheme,
- private_key = CertPrivateKey},
- static_env = #static_env{role = client,
- protocol_cb = Connection}
- } = State) ->
- {Ref,Maybe} = maybe(),
- try
- CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)),
- {ok, Connection:queue_handshake(CertificateVerify, State)}
- catch
- {Ref, #alert{} = Alert} ->
- {error, Alert}
- end.
-
-
-%% Recipients of Finished messages MUST verify that the contents are
-%% correct and if incorrect MUST terminate the connection with a
-%% "decrypt_error" alert.
-validate_finished(#state{connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- tls_handshake_history = {Messages0, _}}}, VerifyData) ->
- #{security_parameters := SecParamsR,
- cipher_state := #cipher_state{finished_key = FinishedKey}} =
- ssl_record:current_connection_state(ConnectionStates, read),
- #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
-
- %% Drop the peer's finished message, it is not part of the handshake context
- %% when the client/server calculates its finished message.
- [_|Messages] = Messages0,
-
- ControlData = tls_v1:finished_verify_data(FinishedKey, HKDFAlgo, Messages),
- compare_verify_data(ControlData, VerifyData).
-
-
-compare_verify_data(Data, Data) ->
- ok;
-compare_verify_data(_, _) ->
- {error, ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)}.
-
-
-send_hello_retry_request(#state{connection_states = ConnectionStates0,
- static_env = #static_env{protocol_cb = Connection}} = State0,
- no_suitable_key, KeyShare, SessionId) ->
- ServerHello0 = server_hello(hello_retry_request, SessionId, KeyShare, undefined, ConnectionStates0),
- {State1, ServerHello} = maybe_add_cookie_extension(State0, ServerHello0),
-
- State2 = Connection:queue_handshake(ServerHello, State1),
- %% D.4. Middlebox Compatibility Mode
- State3 = maybe_queue_change_cipher_spec(State2, last),
- {State4, _} = Connection:send_handshake_flight(State3),
-
- %% Update handshake history
- State5 = replace_ch1_with_message_hash(State4),
-
- {ok, {State5, start}};
-send_hello_retry_request(State0, _, _, _) ->
- %% Suitable key found.
- {ok, {State0, negotiated}}.
-
-session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State, negotiated}, _) ->
- {ok, {State, negotiated}};
-session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State, negotiated}, undefined)
- when Tickets =/= disabled ->
- {ok, {State, negotiated}};
-session_resumption({#state{ssl_options = #{session_tickets := Tickets},
- handshake_env = #handshake_env{
- early_data_accepted = false}} = State0, negotiated}, PSK)
- when Tickets =/= disabled ->
- State = handle_resumption(State0, ok),
- {ok, {State, negotiated, PSK}};
-session_resumption({#state{ssl_options = #{session_tickets := Tickets},
- handshake_env = #handshake_env{
- early_data_accepted = true}} = State0, negotiated}, PSK0)
- when Tickets =/= disabled ->
- State1 = handle_resumption(State0, ok),
- %% TODO Refactor PSK-tuple {Index, PSK}, index might not be needed.
- {_ , PSK} = PSK0,
- State2 = calculate_client_early_traffic_secret(State1, PSK),
- %% Set 0-RTT traffic keys for reading early_data
- State3 = ssl_record:step_encryption_state_read(State2),
- State = update_current_read(State3, true, true),
- {ok, {State, negotiated, PSK0}}.
-
-%% Session resumption with early_data
-maybe_send_certificate_request(#state{
- handshake_env =
- #handshake_env{
- early_data_accepted = true}} = State,
- _, PSK) when PSK =/= undefined ->
- %% Go wait for End of Early Data
- {State, wait_eoed};
-%% Do not send CR during session resumption
-maybe_send_certificate_request(State, _, PSK) when PSK =/= undefined ->
- {State, wait_finished};
-maybe_send_certificate_request(State, #{verify := verify_none}, _) ->
- {State, wait_finished};
-maybe_send_certificate_request(#state{static_env = #static_env{protocol_cb = Connection,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef}} = State,
- #{verify := verify_peer,
- signature_algs := SignAlgs,
- signature_algs_cert := SignAlgsCert,
- certificate_authorities := CertAuthBool}, _) ->
- CertificateRequest = certificate_request(SignAlgs, SignAlgsCert, CertDbHandle, CertDbRef, CertAuthBool),
- {Connection:queue_handshake(CertificateRequest, State), wait_cert}.
-
-maybe_send_certificate(State, PSK) when PSK =/= undefined ->
- {ok, State};
-maybe_send_certificate(#state{session = #session{own_certificates = OwnCerts},
- static_env = #static_env{
- protocol_cb = Connection,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef}} = State, _) ->
- case certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, server) of
- {ok, Certificate} ->
- {ok, Connection:queue_handshake(Certificate, State)};
- Error ->
- Error
- end.
-
-
-maybe_send_certificate_verify(State, PSK) when PSK =/= undefined ->
- {ok, State};
-maybe_send_certificate_verify(#state{session = #session{sign_alg = SignatureScheme,
- private_key = CertPrivateKey},
- static_env = #static_env{protocol_cb = Connection}
- } = State, _) ->
- case certificate_verify(CertPrivateKey, SignatureScheme, State, server) of
- {ok, CertificateVerify} ->
- {ok, Connection:queue_handshake(CertificateVerify, State)};
- Error ->
- Error
- end.
-
-
-maybe_send_session_ticket(State) ->
- Number = case application:get_env(ssl, server_session_tickets_amount) of
- {ok, Size} when is_integer(Size) andalso
- Size > 0 ->
- Size;
- _ ->
- 3
- end,
- maybe_send_session_ticket(State, Number).
-%%
-maybe_send_session_ticket(#state{ssl_options = #{session_tickets := disabled}} = State, _) ->
- %% Do nothing!
- State;
-maybe_send_session_ticket(State, 0) ->
- State;
-maybe_send_session_ticket(#state{connection_states = ConnectionStates,
- static_env = #static_env{trackers = Trackers,
- protocol_cb = Connection}
-
- } = State0, N) ->
- Tracker = proplists:get_value(session_tickets_tracker, Trackers),
- #{security_parameters := SecParamsR} =
- ssl_record:current_connection_state(ConnectionStates, read),
- #security_parameters{prf_algorithm = HKDF,
- resumption_master_secret = RMS} = SecParamsR,
- Ticket = tls_server_session_ticket:new(Tracker, HKDF, RMS),
- {State, _} = Connection:send_handshake(Ticket, State0),
- maybe_send_session_ticket(State, N - 1).
-
-create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) ->
- %% Dummy connection_states with NULL cipher
- ConnectionStates =
- #{current_write =>
- #{compression_state => undefined,
- cipher_state => undefined,
- sequence_number => 1,
- security_parameters =>
- #security_parameters{
- bulk_cipher_algorithm = 0,
- compression_algorithm = ?NULL,
- mac_algorithm = ?NULL
- },
- mac_secret => undefined}},
- {BinChangeCipher, _} =
- tls_record:encode_change_cipher_spec(?LEGACY_VERSION, ConnectionStates),
- ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher),
- [BinChangeCipher].
-
-process_certificate_request(#certificate_request_1_3{
- extensions = Extensions},
- #state{ssl_options = #{signature_algs := ClientSignAlgs},
- connection_env = #connection_env{cert_key_alts = CertKeyAlts,
- negotiated_version = Version},
- static_env = #static_env{cert_db = CertDbHandle, cert_db_ref = CertDbRef},
- session = Session0} =
- State) ->
- ServerSignAlgs = get_signature_scheme_list(
- maps:get(signature_algs, Extensions, undefined)),
- ServerSignAlgsCert = get_signature_scheme_list(
- maps:get(signature_algs_cert, Extensions, undefined)),
- CertAuths = get_certificate_authorities(maps:get(certificate_authorities, Extensions, undefined)),
-
- CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, Version),
- Session = select_client_cert_key_pair(Session0, CertKeyPairs,
- ServerSignAlgs, ServerSignAlgsCert, filter_tls13_algs(ClientSignAlgs),
- CertDbHandle, CertDbRef, CertAuths, undefined),
- {ok, {State#state{client_certificate_status = requested, session = Session}, wait_cert}}.
-
-process_certificate(#certificate_1_3{
- certificate_request_context = <<>>,
- certificate_list = []},
- #state{ssl_options =
- #{fail_if_no_peer_cert := false}} = State) ->
- {ok, {State, wait_finished}};
-process_certificate(#certificate_1_3{
- certificate_request_context = <<>>,
- certificate_list = []},
- #state{ssl_options =
- #{fail_if_no_peer_cert := true}} = State0) ->
- %% At this point the client believes that the connection is up and starts using
- %% its traffic secrets. In order to be able send an proper Alert to the client
- %% the server should also change its connection state and use the traffic
- %% secrets.
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
- {error, {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}};
-process_certificate(#certificate_1_3{certificate_list = CertEntries},
- #state{ssl_options = SslOptions,
- static_env =
- #static_env{
- role = Role,
- host = Host,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- crl_db = CRLDbHandle},
- handshake_env = #handshake_env{
- ocsp_stapling_state = OcspState}} = State0) ->
- case validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
- SslOptions, CRLDbHandle, Role, Host, OcspState) of
- #alert{} = Alert ->
- State = update_encryption_state(Role, State0),
- {error, {Alert, State}};
- {PeerCert, PublicKeyInfo} ->
- State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
- {ok, {State, wait_cv}}
- end.
-
%% Sets correct encryption state when sending Alerts in shared states that use different secrets.
%% - If client: use handshake secrets.
%% - If server: use traffic secrets as by this time the client's state machine
@@ -1515,29 +808,33 @@ update_encryption_state(client, State) ->
validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
- #{ocsp_responder_certs := OcspResponderCerts
- } = SslOptions, CRLDbHandle, Role, Host, OcspState0) ->
+ SslOptions, CRLDbHandle, Role, Host, OcspState0) ->
{Certs, CertExt, OcspState} = split_cert_entries(CertEntries, OcspState0),
-
+ OcspResponderCerts =
+ case maps:get(ocsp_stapling, SslOptions, disabled) of
+ #{ocsp_responder_certs := V} ->
+ V;
+ disabled ->
+ ?DEFAULT_OCSP_RESPONDER_CERTS
+ end,
ssl_handshake:certify(#certificate{asn1_certificates = Certs}, CertDbHandle, CertDbRef,
- SslOptions, CRLDbHandle, Role, Host, {3,4},
+ SslOptions, CRLDbHandle, Role, Host, ?TLS_1_3,
#{cert_ext => CertExt,
ocsp_state => OcspState,
ocsp_responder_certs => OcspResponderCerts}).
-
store_peer_cert(#state{session = Session,
handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) ->
State#state{session = Session#session{peer_certificate = PeerCert},
handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}}.
-
split_cert_entries(CertEntries, OcspState) ->
split_cert_entries(CertEntries, OcspState, [], #{}).
+
split_cert_entries([], OcspState, Chain, Ext) ->
{lists:reverse(Chain), Ext, OcspState};
-split_cert_entries([#certificate_entry{data = DerCert,
- extensions = Extensions0} | CertEntries], OcspState0, Chain, Ext) ->
+split_cert_entries([#certificate_entry{data = DerCert, extensions = Extensions0} | CertEntries],
+ OcspState0, Chain, Ext) ->
Id = public_key:pkix_subject_id(DerCert),
Extensions = [ExtValue || {_, ExtValue} <- maps:to_list(Extensions0)],
OcspState = case maps:get(status_request, Extensions0, undefined) of
@@ -1548,7 +845,6 @@ split_cert_entries([#certificate_entry{data = DerCert,
end,
split_cert_entries(CertEntries, OcspState, [DerCert | Chain], Ext#{Id => Extensions}).
-
%% 4.4.1. The Transcript Hash
%%
%% As an exception to this general rule, when the server responds to a
@@ -1644,7 +940,7 @@ calculate_client_early_traffic_secret(#state{connection_states = ConnectionState
calculate_client_early_traffic_secret(
ClientHello, PSK, Cipher, HKDFAlgo,
#state{connection_states = ConnectionStates,
- ssl_options = #{keep_secrets := KeepSecrets},
+ ssl_options = Opts,
static_env = #static_env{role = Role}} = State0) ->
EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
ClientEarlyTrafficSecret =
@@ -1657,7 +953,7 @@ calculate_client_early_traffic_secret(
case Role of
client ->
PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates, write),
- PendingWrite1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret,
+ PendingWrite1 = maybe_store_early_data_secret(Opts, ClientEarlyTrafficSecret,
PendingWrite0),
PendingWrite = update_connection_state(PendingWrite1, undefined, undefined,
undefined,
@@ -1665,7 +961,7 @@ calculate_client_early_traffic_secret(
State0#state{connection_states = ConnectionStates#{pending_write => PendingWrite}};
server ->
PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read),
- PendingRead1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret,
+ PendingRead1 = maybe_store_early_data_secret(Opts, ClientEarlyTrafficSecret,
PendingRead0),
PendingRead = update_connection_state(PendingRead1, undefined, undefined,
undefined,
@@ -1673,20 +969,20 @@ calculate_client_early_traffic_secret(
State0#state{connection_states = ConnectionStates#{pending_read => PendingRead}}
end.
-update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataExpected) ->
- Read0 = ssl_record:current_connection_state(CS, read),
- Read = Read0#{trial_decryption => TrialDecryption,
- early_data_accepted => EarlyDataExpected},
- State#state{connection_states = CS#{current_read => Read}}.
-maybe_store_early_data_secret(true, EarlySecret, State) ->
+
+maybe_store_early_data_secret(#{keep_secrets := true}, EarlySecret, State) ->
#{security_parameters := SecParams0} = State,
SecParams = SecParams0#security_parameters{client_early_data_secret = EarlySecret},
State#{security_parameters := SecParams};
-maybe_store_early_data_secret(false, _, State) ->
+maybe_store_early_data_secret(_, _, State) ->
State.
%% Server
+%% get_pre_shared_key(undefined, HKDFAlgo) ->
+%% binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo));
+%% get_pre_shared_key(#pre_shared_key_server_hello{selected_identity = PSK}, _) ->
+%% PSK.
get_pre_shared_key(undefined, HKDFAlgo) ->
binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo));
get_pre_shared_key({_, PSK}, _) ->
@@ -1702,9 +998,9 @@ get_pre_shared_key(undefined, _, HKDFAlgo, _) ->
get_pre_shared_key(_, undefined, HKDFAlgo, _) ->
{ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
%% Session resumption
-get_pre_shared_key(manual = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) ->
+get_pre_shared_key(manual = SessionTickets, UseTicket, HKDFAlgo, ServerPSK) ->
TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
- case choose_psk(TicketData, SelectedIdentity) of
+ case choose_psk(TicketData, ServerPSK) of
undefined -> %% full handshake, default PSK
{ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
illegal_parameter ->
@@ -1712,9 +1008,9 @@ get_pre_shared_key(manual = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentit
{_, PSK, _, _, _} ->
{ok, PSK}
end;
-get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) ->
+get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, ServerPSK) ->
TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
- case choose_psk(TicketData, SelectedIdentity) of
+ case choose_psk(TicketData, ServerPSK) of
undefined -> %% full handshake, default PSK
tls_client_ticket_store:unlock_tickets(self(), UseTicket),
{ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
@@ -1730,7 +1026,7 @@ get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity)
%% Early Data
get_pre_shared_key_early_data(SessionTickets, UseTicket) ->
TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
- case choose_psk(TicketData, 0) of
+ case choose_psk(TicketData, #pre_shared_key_server_hello{selected_identity = 0}) of
undefined -> %% Should not happen
{error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
illegal_parameter ->
@@ -1739,6 +1035,11 @@ get_pre_shared_key_early_data(SessionTickets, UseTicket) ->
{ok, {PSK, Cipher, HKDF, MaxSize}}
end.
+get_supported_groups(undefined = Groups) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {supported_groups, Groups})};
+get_supported_groups(#supported_groups{supported_groups = Groups}) ->
+ {ok, Groups}.
+
choose_psk(undefined, _) ->
undefined;
choose_psk([], _) ->
@@ -1748,7 +1049,7 @@ choose_psk([#ticket_data{
pos = SelectedIdentity,
psk = PSK,
cipher_suite = {Cipher, HKDF},
- max_size = MaxSize}|_], SelectedIdentity) ->
+ max_size = MaxSize}|_], #pre_shared_key_server_hello{selected_identity = SelectedIdentity}) ->
{Key, PSK, Cipher, HKDF, MaxSize};
choose_psk([_|T], SelectedIdentity) ->
choose_psk(T, SelectedIdentity).
@@ -1789,16 +1090,7 @@ calculate_traffic_secrets(#state{
WriteKey, WriteIV, undefined).
-get_server_private_key(#key_share_server_hello{server_share = ServerShare}) ->
- get_private_key(ServerShare).
-get_private_key(#key_share_entry{
- key_exchange = #'ECPrivateKey'{} = PrivateKey}) ->
- PrivateKey;
-get_private_key(#key_share_entry{
- key_exchange =
- {_, PrivateKey}}) ->
- PrivateKey.
%% X25519, X448
calculate_shared_secret(OthersKey, MyKey, Group)
@@ -1967,7 +1259,7 @@ update_start_state(#state{connection_states = ConnectionStates0,
sign_alg = SelectedSignAlg,
dh_public_value = PeerPublicKey,
cipher_suite = Cipher},
- connection_env = CEnv#connection_env{negotiated_version = {3,4}}}.
+ connection_env = CEnv#connection_env{negotiated_version = ?TLS_1_3}}.
update_resumption_master_secret(#state{connection_states = ConnectionStates0} = State,
@@ -2096,46 +1388,7 @@ maybe_update_selected_sign_alg(State, _, _) ->
State.
-verify_certificate_verify(#state{static_env = #static_env{role = Role},
- connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- public_key_info = PublicKeyInfo,
- tls_handshake_history = HHistory}} = State0,
- #certificate_verify_1_3{
- algorithm = SignatureScheme,
- signature = Signature}) ->
- #{security_parameters := SecParamsR} =
- ssl_record:pending_connection_state(ConnectionStates, write),
- #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
-
- {HashAlgo, SignAlg, _} =
- ssl_cipher:scheme_to_components(SignatureScheme),
-
- Messages = get_handshake_context_cv(HHistory),
- Context = lists:reverse(Messages),
-
- %% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
- THash = tls_v1:transcript_hash(Context, HKDFAlgo),
-
- ContextString = peer_context_string(Role),
-
- %% Digital signatures use the hash function defined by the selected signature
- %% scheme.
- case verify(THash, ContextString, HashAlgo, SignAlg, Signature, PublicKeyInfo) of
- {ok, true} ->
- {ok, {State0, wait_finished}};
- {ok, false} ->
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
- {error, {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- "Failed to verify CertificateVerify"), State}};
- {error, #alert{} = Alert} ->
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
- {error, {Alert, State}}
- end.
context_string(server) ->
@@ -2166,146 +1419,6 @@ select_common_groups(ServerGroups, ClientGroups) ->
{ok, L}
end.
-
-%% RFC 8446 - 4.2.8. Key Share
-%% This vector MAY be empty if the client is requesting a
-%% HelloRetryRequest. Each KeyShareEntry value MUST correspond to a
-%% group offered in the "supported_groups" extension and MUST appear in
-%% the same order. However, the values MAY be a non-contiguous subset
-%% of the "supported_groups" extension and MAY omit the most preferred
-%% groups.
-%%
-%% Clients can offer as many KeyShareEntry values as the number of
-%% supported groups it is offering, each representing a single set of
-%% key exchange parameters.
-%%
-%% Clients MUST NOT offer multiple KeyShareEntry values
-%% for the same group. Clients MUST NOT offer any KeyShareEntry values
-%% for groups not listed in the client's "supported_groups" extension.
-%% Servers MAY check for violations of these rules and abort the
-%% handshake with an "illegal_parameter" alert if one is violated.
-validate_client_key_share(_ ,[]) ->
- ok;
-validate_client_key_share([], _) ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
-validate_client_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) ->
- validate_client_key_share(ClientGroups, ClientShares);
-validate_client_key_share([_|ClientGroups], [_|_] = ClientShares) ->
- validate_client_key_share(ClientGroups, ClientShares).
-
-
-%% Verify that selected group is offered by the client.
-validate_server_key_share([], _) ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
-validate_server_key_share([G|_ClientGroups], {_, G, _}) ->
- ok;
-validate_server_key_share([_|ClientGroups], {_, _, _} = ServerKeyShare) ->
- validate_server_key_share(ClientGroups, ServerKeyShare).
-
-
-validate_selected_group(SelectedGroup, [SelectedGroup|_]) ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
- "Selected group sent by the server shall not correspond to a group"
- " which was provided in the key_share extension")};
-validate_selected_group(SelectedGroup, ClientGroups) ->
- case lists:member(SelectedGroup, ClientGroups) of
- true ->
- ok;
- false ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
- "Selected group sent by the server shall correspond to a group"
- " which was provided in the supported_groups extension")}
- end.
-
-
-get_client_public_key([Group|_] = Groups, ClientShares) ->
- get_client_public_key(Groups, ClientShares, Group).
-%%
-get_client_public_key(_, [], PreferredGroup) ->
- {PreferredGroup, no_suitable_key};
-get_client_public_key([], _, PreferredGroup) ->
- {PreferredGroup, no_suitable_key};
-get_client_public_key([Group|Groups], ClientShares, PreferredGroup) ->
- case lists:keysearch(Group, 2, ClientShares) of
- {value, {_, _, ClientPublicKey}} ->
- {Group, ClientPublicKey};
- false ->
- get_client_public_key(Groups, ClientShares, PreferredGroup)
- end.
-
-get_client_private_key([Group|_] = Groups, ClientShares) ->
- get_client_private_key(Groups, ClientShares, Group).
-%%
-get_client_private_key(_, [], PreferredGroup) ->
- {PreferredGroup, no_suitable_key};
-get_client_private_key([], _, PreferredGroup) ->
- {PreferredGroup, no_suitable_key};
-get_client_private_key([Group|Groups], ClientShares, PreferredGroup) ->
- case lists:keysearch(Group, 2, ClientShares) of
- {value, {_, _, {_, ClientPrivateKey}}} ->
- {Group, ClientPrivateKey};
- {value, {_, _, #'ECPrivateKey'{} = ClientPrivateKey}} ->
- {Group, ClientPrivateKey};
- false ->
- get_client_private_key(Groups, ClientShares, PreferredGroup)
- end.
-
-
-get_server_public_key({key_share_entry, Group, PublicKey}) ->
- {Group, PublicKey}.
-
-
-%% RFC 7301 - Application-Layer Protocol Negotiation Extension
-%% It is expected that a server will have a list of protocols that it
-%% supports, in preference order, and will only select a protocol if the
-%% client supports it. In that case, the server SHOULD select the most
-%% highly preferred protocol that it supports and that is also
-%% advertised by the client. In the event that the server supports no
-%% protocols that the client advertises, then the server SHALL respond
-%% with a fatal "no_application_protocol" alert.
-handle_alpn(undefined, _) ->
- {ok, undefined};
-handle_alpn([], _) ->
- {error, ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL)};
-handle_alpn([_|_], undefined) ->
- {ok, undefined};
-handle_alpn([ServerProtocol|T], ClientProtocols) ->
- case lists:member(ServerProtocol, ClientProtocols) of
- true ->
- {ok, ServerProtocol};
- false ->
- handle_alpn(T, ClientProtocols)
- end.
-
-
-select_cipher_suite(_, [], _) ->
- {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher)};
-%% If honor_cipher_order is set to true, use the server's preference for
-%% cipher suite selection.
-select_cipher_suite(true, ClientCiphers, ServerCiphers) ->
- select_cipher_suite(false, ServerCiphers, ClientCiphers);
-select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) ->
- case lists:member(Cipher, tls_v1:exclusive_suites(4)) andalso
- lists:member(Cipher, ServerCiphers) of
- true ->
- {ok, Cipher};
- false ->
- select_cipher_suite(false, ClientCiphers, ServerCiphers)
- end.
-
-
-%% RFC 8446 4.1.3 ServerHello
-%% A client which receives a cipher suite that was not offered MUST abort the
-%% handshake with an "illegal_parameter" alert.
-validate_cipher_suite(Cipher, ClientCiphers) ->
- case lists:member(Cipher, ClientCiphers) of
- true ->
- ok;
- false ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
- end.
-
-
%% RFC 8446 (TLS 1.3)
%% TLS 1.3 provides two extensions for indicating which signature
%% algorithms may be used in digital signatures. The
@@ -2474,36 +1587,6 @@ get_certificate_authorities(#certificate_authorities{authorities = Auths}) ->
get_certificate_authorities(undefined) ->
[].
-get_supported_groups(undefined = Groups) ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {supported_groups, Groups})};
-get_supported_groups(#supported_groups{supported_groups = Groups}) ->
- {ok, Groups}.
-
-get_key_shares(undefined) ->
- [];
-get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
- ClientShares;
-get_key_shares(#key_share_server_hello{server_share = ServerShare}) ->
- ServerShare.
-
-get_cookie(undefined) ->
- undefined;
-get_cookie(#cookie{cookie = Cookie}) ->
- Cookie.
-
-get_selected_identity(undefined) ->
- undefined;
-get_selected_identity(#pre_shared_key_server_hello{selected_identity = SelectedIdentity}) ->
- SelectedIdentity.
-
-get_offered_psks(Extensions) ->
- PSK = maps:get(pre_shared_key, Extensions, undefined),
- case PSK of
- undefined ->
- undefined;
- #pre_shared_key_client_hello{offered_psks = OfferedPSKs} ->
- OfferedPSKs
- end.
%% Prior to accepting PSK key establishment, the server MUST validate
@@ -2521,33 +1604,13 @@ handle_pre_shared_key(#state{ssl_options = #{session_tickets := disabled}}, _, _
{ok, undefined};
handle_pre_shared_key(#state{ssl_options = #{session_tickets := Tickets},
handshake_env = #handshake_env{tls_handshake_history = {HHistory, _}},
- static_env = #static_env{trackers = Trackers}},
- OfferedPreSharedKeys, Cipher) when Tickets =/= disabled ->
+ static_env = #static_env{trackers = Trackers}},
+ #pre_shared_key_client_hello{offered_psks =
+ OfferedPreSharedKeys}, Cipher) when Tickets =/= disabled ->
Tracker = proplists:get_value(session_tickets_tracker, Trackers),
#{prf := CipherHash} = ssl_cipher_format:suite_bin_to_map(Cipher),
tls_server_session_ticket:use(Tracker, OfferedPreSharedKeys, CipherHash, HHistory).
-get_selected_group(#key_share_hello_retry_request{selected_group = SelectedGroup}) ->
- SelectedGroup.
-
-get_alpn(ALPNProtocol0) ->
- case ssl_handshake:decode_alpn(ALPNProtocol0) of
- undefined ->
- undefined;
- [ALPNProtocol] ->
- ALPNProtocol
- end.
-
-maybe() ->
- Ref = erlang:make_ref(),
- Ok = fun(ok) -> ok;
- ({ok,R}) -> R;
- ({error,Reason}) ->
- throw({Ref,Reason})
- end,
- {Ref,Ok}.
-
-
%% If the handshake includes a HelloRetryRequest, the initial
%% ClientHello and HelloRetryRequest are included in the transcript
%% along with the new ClientHello. For instance, if the client sends
@@ -2571,25 +1634,25 @@ maybe() ->
%% message, as described in Section 4.4.1.
maybe_add_binders(Hello, undefined, _) ->
Hello;
-maybe_add_binders(Hello0, TicketData, Version) when Version =:= {3,4} ->
+maybe_add_binders(Hello0, TicketData, ?TLS_1_3=Version) ->
HelloBin0 = tls_handshake:encode_handshake(Hello0, Version),
HelloBin1 = iolist_to_binary(HelloBin0),
Truncated = truncate_client_hello(HelloBin1),
Binders = create_binders([Truncated], TicketData),
update_binders(Hello0, Binders);
-maybe_add_binders(Hello, _, Version) when Version =< {3,3} ->
+maybe_add_binders(Hello, _, Version) when ?TLS_LTE(Version, ?TLS_1_2) ->
Hello.
%%
%% HelloRetryRequest
maybe_add_binders(Hello, _, undefined, _) ->
Hello;
-maybe_add_binders(Hello0, {[HRR,MessageHash|_], _}, TicketData, Version) when Version =:= {3,4} ->
+maybe_add_binders(Hello0, {[HRR,MessageHash|_], _}, TicketData, ?TLS_1_3=Version) ->
HelloBin0 = tls_handshake:encode_handshake(Hello0, Version),
HelloBin1 = iolist_to_binary(HelloBin0),
Truncated = truncate_client_hello(HelloBin1),
Binders = create_binders([MessageHash,HRR,Truncated], TicketData),
update_binders(Hello0, Binders);
-maybe_add_binders(Hello, _, _, Version) when Version =< {3,3} ->
+maybe_add_binders(Hello, _, _, Version) when ?TLS_LTE(Version, ?TLS_1_2) ->
Hello.
create_binders(Context, TicketData) ->
@@ -2615,7 +1678,7 @@ truncate_client_hello(HelloBin0) ->
<<?BYTE(Type), ?UINT24(_Length), Body/binary>> = HelloBin0,
CH0 = #client_hello{
extensions = #{pre_shared_key := PSK0} = Extensions0} =
- tls_handshake:decode_handshake({3,4}, Type, Body),
+ tls_handshake:decode_handshake(?TLS_1_3, Type, Body),
#pre_shared_key_client_hello{offered_psks = OfferedPsks0} = PSK0,
OfferedPsks = OfferedPsks0#offered_psks{binders = []},
PSK = PSK0#pre_shared_key_client_hello{offered_psks = OfferedPsks},
@@ -2628,8 +1691,8 @@ truncate_client_hello(HelloBin0) ->
%% The original length of the binders can still be determined by
%% re-encoding the original ClientHello and using its size as reference
%% when we subtract the size of the truncated binary.
- TruncatedSize = iolist_size(tls_handshake:encode_handshake(CH, {3,4})),
- RefSize = iolist_size(tls_handshake:encode_handshake(CH0, {3,4})),
+ TruncatedSize = iolist_size(tls_handshake:encode_handshake(CH, ?TLS_1_3)),
+ RefSize = iolist_size(tls_handshake:encode_handshake(CH0, ?TLS_1_3)),
BindersSize = RefSize - TruncatedSize,
%% Return the truncated ClientHello by cutting of the binders from the original
@@ -2640,9 +1703,8 @@ truncate_client_hello(HelloBin0) ->
maybe_add_early_data_indication(#client_hello{
extensions = Extensions0} = ClientHello,
EarlyData,
- Version)
- when Version =:= {3,4} andalso
- is_binary(EarlyData) andalso
+ ?TLS_1_3)
+ when is_binary(EarlyData) andalso
byte_size(EarlyData) > 0 ->
Extensions = Extensions0#{early_data =>
#early_data_indication{}},
@@ -2650,6 +1712,17 @@ maybe_add_early_data_indication(#client_hello{
maybe_add_early_data_indication(ClientHello, _, _) ->
ClientHello.
+supported_groups_from_extensions(Extensions) ->
+ case maps:get(elliptic_curves, Extensions, undefined) of
+ #supported_groups{} = Groups->
+ {ok, Groups};
+ %% We do not support legacy for TLS-1.2 in TLS-1.3
+ #elliptic_curves{} ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
+ undefined ->
+ {ok, undefined}
+ end.
+
%% The PskBinderEntry is computed in the same way as the Finished
%% message (Section 4.4.4) but with the BaseKey being the binder_key
%% derived via the key schedule from the corresponding PSK which is
@@ -2679,32 +1752,6 @@ update_binders(#client_hello{extensions =
Extensions = Extensions0#{pre_shared_key => PreSharedKey},
Hello#client_hello{extensions = Extensions}.
-%% Configure a suitable session ticket
-maybe_automatic_session_resumption(#state{
- ssl_options = #{versions := [Version|_],
- ciphers := UserSuites,
- early_data := EarlyData,
- session_tickets := SessionTickets,
- server_name_indication := SNI} = SslOpts0
- } = State0)
- when Version >= {3,4} andalso
- SessionTickets =:= auto ->
- AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
- HashAlgos = cipher_hash_algos(AvailableCipherSuites),
- Ciphers = ciphers_for_early_data(AvailableCipherSuites),
- %% Find a pair of tickets KeyPair = {Ticket0, Ticket2} where Ticket0 satisfies
- %% requirements for early_data and session resumption while Ticket2 can only
- %% be used for session resumption.
- EarlyDataSize = early_data_size(EarlyData),
- KeyPair = tls_client_ticket_store:find_ticket(self(), Ciphers, HashAlgos, SNI, EarlyDataSize),
- UseTicket = choose_ticket(KeyPair, EarlyData),
- tls_client_ticket_store:lock_tickets(self(), [UseTicket]),
- State = State0#state{ssl_options = SslOpts0#{use_ticket => [UseTicket]}},
- {[UseTicket], State};
-maybe_automatic_session_resumption(#state{
- ssl_options = #{use_ticket := UseTicket}
- } = State) ->
- {UseTicket, State}.
early_data_size(undefined) ->
undefined;
@@ -2728,138 +1775,17 @@ choose_ticket(_, _) ->
%% here prevents session resumption instead.
undefined.
-maybe_send_early_data(#state{
- handshake_env = #handshake_env{tls_handshake_history = {Hist, _}},
- protocol_specific = #{sender := _Sender},
- ssl_options = #{versions := [Version|_],
- use_ticket := UseTicket,
- session_tickets := SessionTickets,
- early_data := EarlyData} = _SslOpts0
- } = State0) when Version =:= {3,4} andalso
- UseTicket =/= [undefined] andalso
- EarlyData =/= undefined ->
- %% D.4. Middlebox Compatibility Mode
- State1 = maybe_queue_change_cipher_spec(State0, last),
- %% Early traffic secret
- EarlyDataSize = early_data_size(EarlyData),
- case get_pre_shared_key_early_data(SessionTickets, UseTicket) of
- {ok, {PSK, Cipher, HKDF, MaxSize}} when EarlyDataSize =< MaxSize ->
- State2 = calculate_client_early_traffic_secret(Hist, PSK, Cipher, HKDF, State1),
- %% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData
- State3 = ssl_record:step_encryption_state_write(State2),
- {ok, encode_early_data(Cipher, State3)};
- {ok, {_, _, _, MaxSize}} ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {too_much_early_data, {max, MaxSize}})};
- {error, Alert} ->
- {error, Alert}
- end;
-maybe_send_early_data(State) ->
- {ok, State}.
-
-encode_early_data(Cipher,
- #state{
- flight_buffer = Flight0,
- protocol_specific = #{sender := _Sender},
- ssl_options = #{versions := [Version|_],
- early_data := EarlyData} = _SslOpts0
- } = State0) ->
- #state{connection_states =
- #{current_write :=
- #{security_parameters := SecurityParameters0} = Write0} = ConnectionStates0} = State0,
- BulkCipherAlgo = ssl_cipher:bulk_cipher_algorithm(Cipher),
- SecurityParameters = SecurityParameters0#security_parameters{
- cipher_type = ?AEAD,
- bulk_cipher_algorithm = BulkCipherAlgo},
- Write = Write0#{security_parameters => SecurityParameters},
- ConnectionStates1 = ConnectionStates0#{current_write => Write},
- {BinEarlyData, ConnectionStates} = tls_record:encode_data([EarlyData], Version, ConnectionStates1),
- State0#state{connection_states = ConnectionStates,
- flight_buffer = Flight0 ++ [BinEarlyData]}.
-
-maybe_send_end_of_early_data(
- #state{
- handshake_env = #handshake_env{early_data_accepted = true},
- protocol_specific = #{sender := _Sender},
- ssl_options = #{versions := [Version|_],
- use_ticket := UseTicket,
- early_data := EarlyData},
- static_env = #static_env{protocol_cb = Connection}
- } = State0) when Version =:= {3,4} andalso
- UseTicket =/= [undefined] andalso
- EarlyData =/= undefined ->
- %% EndOfEarlydata is encrypted with the 0-RTT traffic keys
- State1 = Connection:queue_handshake(#end_of_early_data{}, State0),
- %% Use handshake keys after EndOfEarlyData is sent
- ssl_record:step_encryption_state_write(State1);
-maybe_send_end_of_early_data(State) ->
- State.
-
-maybe_check_early_data_indication(EarlyDataIndication,
- #state{
- handshake_env = HsEnv,
- ssl_options = #{versions := [Version|_],
- use_ticket := UseTicket,
- early_data := EarlyData}
- } = State) when Version =:= {3,4} andalso
- UseTicket =/= [undefined] andalso
- EarlyData =/= undefined andalso
- EarlyDataIndication =/= undefined ->
- signal_user_early_data(State, accepted),
- State#state{handshake_env = HsEnv#handshake_env{early_data_accepted = true}};
-maybe_check_early_data_indication(EarlyDataIndication,
- #state{
- protocol_specific = #{sender := _Sender},
- ssl_options = #{versions := [Version|_],
- use_ticket := UseTicket,
- early_data := EarlyData} = _SslOpts0
- } = State) when Version =:= {3,4} andalso
- UseTicket =/= [undefined] andalso
- EarlyData =/= undefined andalso
- EarlyDataIndication =:= undefined ->
- signal_user_early_data(State, rejected),
- %% Use handshake keys if early_data is rejected.
- ssl_record:step_encryption_state_write(State);
-maybe_check_early_data_indication(_, State) ->
- %% Use handshake keys if there is no early_data.
- ssl_record:step_encryption_state_write(State).
-
-signal_user_early_data(#state{
- connection_env =
- #connection_env{
- user_application = {_, User}},
- static_env =
- #static_env{
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- trackers = Trackers}} = State,
- Result) ->
- CPids = Connection:pids(State),
- SslSocket = Connection:socket(CPids, Transport, Socket, Trackers),
- User ! {ssl, SslSocket, {early_data, Result}}.
-
-handle_early_data(State, enabled, #early_data_indication{}) ->
- %% Accept early data
- HsEnv = (State#state.handshake_env)#handshake_env{early_data_accepted = true},
- State#state{handshake_env = HsEnv};
-handle_early_data(State, _, _) ->
- State.
-
-cipher_hash_algos(Ciphers) ->
- Fun = fun(Cipher) ->
- #{prf := Hash} = ssl_cipher_format:suite_bin_to_map(Cipher),
- Hash
- end,
- lists:map(Fun, Ciphers).
-
ciphers_for_early_data(CipherSuites0) ->
- %% Use only supported TLS 1.3 cipher suites
- Supported = lists:filter(fun(CipherSuite) ->
- lists:member(CipherSuite, tls_v1:exclusive_suites(4)) end,
- CipherSuites0),
%% Return supported block cipher algorithms
- lists:map(fun(#{cipher := Cipher}) -> Cipher end,
- lists:map(fun ssl_cipher_format:suite_bin_to_map/1, Supported)).
+ lists:filtermap(fun ciphers_for_early_data0/1, CipherSuites0).
+
+ciphers_for_early_data0(CipherSuite) ->
+ %% Use only supported TLS 1.3 cipher suites
+ case lists:member(CipherSuite, tls_v1:exclusive_suites(?TLS_1_3)) of
+ true -> {true, maps:get(cipher, ssl_cipher_format:suite_bin_to_map(CipherSuite))};
+ false -> false
+ end.
+
get_ticket_data(_, undefined, _) ->
undefined;
@@ -2938,102 +1864,34 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR
crl_check := CrlCheck,
log_level := LogLevel,
signature_algs := SignAlgos,
- signature_algs_cert := SignAlgosCert,
- depth := Depth},
+ signature_algs_cert := SignAlgosCert} = Opts,
#{cert_ext := CertExt,
ocsp_responder_certs := OcspResponderCerts,
ocsp_state := OcspState}) ->
- ValidationFunAndState =
- ssl_handshake:validation_fun_and_state(VerifyFun, #{role => Role,
- certdb => CertDbHandle,
- certdb_ref => CertDbRef,
- server_name => ServerName,
- customize_hostname_check =>
- CustomizeHostnameCheck,
- crl_check => CrlCheck,
- crl_db => CRLDbHandle,
- signature_algs => filter_tls13_algs(SignAlgos),
- signature_algs_cert =>
- filter_tls13_algs(SignAlgosCert),
- version => Version,
- issuer => TrustedCert,
- cert_ext => CertExt,
- ocsp_responder_certs => OcspResponderCerts,
- ocsp_state => OcspState
- },
+ ValidationFunAndState =
+ ssl_handshake:validation_fun_and_state(VerifyFun,
+ #{role => Role,
+ certdb => CertDbHandle,
+ certdb_ref => CertDbRef,
+ server_name => ServerName,
+ customize_hostname_check =>
+ CustomizeHostnameCheck,
+ crl_check => CrlCheck,
+ crl_db => CRLDbHandle,
+ signature_algs => filter_tls13_algs(SignAlgos),
+ signature_algs_cert =>
+ filter_tls13_algs(SignAlgosCert),
+ version => Version,
+ issuer => TrustedCert,
+ cert_ext => CertExt,
+ ocsp_responder_certs => OcspResponderCerts,
+ ocsp_state => OcspState
+ },
Path, LogLevel),
- Options = [{max_path_length, Depth},
+ Options = [{max_path_length, maps:get(depth, Opts, ?DEFAULT_DEPTH)},
{verify_fun, ValidationFunAndState}],
public_key:pkix_path_validation(TrustedCert, Path, Options).
-supported_groups_from_extensions(Extensions) ->
- case maps:get(elliptic_curves, Extensions, undefined) of
- #supported_groups{} = Groups->
- {ok, Groups};
- %% We do not support legacy for TLS-1.2 in TLS-1.3
- #elliptic_curves{} ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
- undefined ->
- {ok, undefined}
- end.
-
-select_server_cert_key_pair(_,[], _,_,_,_, #session{}=Session) ->
- %% Conformant Cert-Key pair with advertised signature algorithm is
- %% selected.
- {ok, Session};
-select_server_cert_key_pair(_,[], _,_,_,_, {fallback, #session{}=Session}) ->
- %% Use fallback Cert-Key pair as no conformant pair to the advertised
- %% signature algorithms was found.
- {ok, Session};
-select_server_cert_key_pair(_,[], _,_,_,_, undefined) ->
- {error, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unable_to_supply_acceptable_cert)};
-select_server_cert_key_pair(Session, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest],
- ClientSignAlgs, ClientSignAlgsCert, CertAuths,
- #state{static_env = #static_env{cert_db = CertDbHandle,
- cert_db_ref = CertDbRef} = State},
- Default0) ->
- {_, SignAlgo, SignHash, _, _} = get_certificate_params(Cert),
- %% TODO: We do validate the signature algorithm and signature hash but we could also check
- %% if the signing cert has a key on a curve supported by the client for ECDSA/EDDSA certs
- case check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert) of
- ok ->
- case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of
- {ok, EncodeChain} -> %% Chain fullfills certificate_authorities extension
- {ok, Session#session{own_certificates = EncodeChain, private_key = Key}};
- {error, EncodeChain, not_in_auth_domain} ->
- %% If this is the first chain to fulfill the signing requirement, use it as default,
- %% if not later alternative also fulfills certificate_authorities extension
- Default = Session#session{own_certificates = EncodeChain, private_key = Key},
- select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
- CertAuths, State, default_or_fallback(Default0, Default))
- end;
- _ ->
- %% If the server cannot produce a certificate chain that is signed only
- %% via the indicated supported algorithms, then it SHOULD continue the
- %% handshake by sending the client a certificate chain of its choice
- case SignHash of
- sha ->
- %% According to "Server Certificate Selection - RFC 8446"
- %% Never send cert using sha1 unless client allows it
- select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
- CertAuths, State, Default0);
- _ ->
- %% If there does not exist a default or fallback from previous alternatives
- %% use this alternative as fallback.
- Fallback = {fallback, Session#session{own_certificates = Certs, private_key = Key}},
- select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
- CertAuths, State,
- default_or_fallback(Default0, Fallback))
- end
- end.
-
-default_or_fallback(undefined, DefaultOrFallback) ->
- DefaultOrFallback;
-default_or_fallback({fallback, _}, #session{} = Default) ->
- Default;
-default_or_fallback(Default, _) ->
- Default.
-
select_client_cert_key_pair(Session0,
[#{private_key := NoKey, certs := [[]] = NoCerts}],
_,_,_,_,_,_, _) ->
@@ -3048,11 +1906,11 @@ select_client_cert_key_pair(Session, [],_,_,_,_,_,_, undefined) ->
select_client_cert_key_pair(_,[],_,_,_,_,_,_, #session{} = Plausible) ->
%% If we do not find an alternative chain with a cert signed in auth_domain,
%% but have a single cert without chain certs it might be verifiable by
- %% a server that has the means to recreate the chain
+ %% a server that has the means to recreate the chain
Plausible;
select_client_cert_key_pair(Session0, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest],
- ServerSignAlgs, ServerSignAlgsCert,
- ClientSignAlgs, CertDbHandle, CertDbRef,
+ ServerSignAlgs, ServerSignAlgsCert,
+ ClientSignAlgs, CertDbHandle, CertDbRef,
CertAuths, Plausible0) ->
{PublicKeyAlgo, SignAlgo, SignHash, MaybeRSAKeySize, Curve} = get_certificate_params(Cert),
case select_sign_algo(PublicKeyAlgo, MaybeRSAKeySize, ServerSignAlgs, ClientSignAlgs, Curve) of
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index ff23b2a99b..01f85624bf 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -49,7 +49,7 @@
-export([build_tls_record/1]).
%% Protocol version handling
--export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2,
+-export([protocol_version/1, protocol_version_name/1, lowest_protocol_version/1, lowest_protocol_version/2,
highest_protocol_version/1, highest_protocol_version/2,
is_higher/2, supported_protocol_versions/0, sufficient_crypto_support/1,
is_acceptable_version/1, is_acceptable_version/2, hello_version/1]).
@@ -130,7 +130,7 @@ get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, MaxFragLen, SslOpts) -
%
%% Description: Encodes a handshake message to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_handshake(Frag, {3, 4}, ConnectionStates) ->
+encode_handshake(Frag, ?TLS_1_3, ConnectionStates) ->
tls_record_1_3:encode_handshake(Frag, ConnectionStates);
encode_handshake(Frag, Version,
#{current_write :=
@@ -158,7 +158,7 @@ encode_handshake(Frag, Version,
%%
%% Description: Encodes an alert message to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_alert_record(Alert, {3, 4}, ConnectionStates) ->
+encode_alert_record(Alert, ?TLS_1_3, ConnectionStates) ->
tls_record_1_3:encode_alert_record(Alert, ConnectionStates);
encode_alert_record(#alert{level = Level, description = Description},
Version, ConnectionStates) ->
@@ -180,7 +180,7 @@ encode_change_cipher_spec(Version, ConnectionStates) ->
%%
%% Description: Encodes data to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_data(Data, {3, 4}, ConnectionStates) ->
+encode_data(Data, ?TLS_1_3, ConnectionStates) ->
tls_record_1_3:encode_data(Data, ConnectionStates);
encode_data(Data, Version,
#{current_write := #{beast_mitigation := BeastMitigation,
@@ -207,7 +207,7 @@ encode_data(Data, Version,
%%
%% Description: Decode cipher text
%%--------------------------------------------------------------------
-decode_cipher_text({3,4}, CipherTextRecord, ConnectionStates, _) ->
+decode_cipher_text(?TLS_1_3, CipherTextRecord, ConnectionStates, _) ->
tls_record_1_3:decode_cipher_text(CipherTextRecord, ConnectionStates);
decode_cipher_text(_, CipherTextRecord,
#{current_read :=
@@ -219,7 +219,8 @@ decode_cipher_text(_, CipherTextRecord,
}
} = ConnectionStates0, _) ->
SeqBin = <<?UINT64(Seq)>>,
- #ssl_tls{type = Type, version = {MajVer,MinVer} = Version, fragment = Fragment} = CipherTextRecord,
+ #ssl_tls{type = Type, version = Version, fragment = Fragment} = CipherTextRecord,
+ {MajVer,MinVer} = Version,
StartAdditionalData = <<SeqBin/binary, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>,
CipherS = ssl_record:nonce_seed(BulkCipherAlgo, SeqBin, CipherS0),
case ssl_record:decipher_aead(
@@ -272,100 +273,92 @@ decode_cipher_text(_, #ssl_tls{version = Version,
%%====================================================================
%%--------------------------------------------------------------------
--spec protocol_version(tls_atom_version() | tls_version()) ->
- tls_version() | tls_atom_version().
+-spec protocol_version_name(tls_atom_version()) -> tls_version().
%%
%% Description: Creates a protocol version record from a version atom
%% or vice versa.
%%--------------------------------------------------------------------
-protocol_version('tlsv1.3') ->
- {3, 4};
-protocol_version('tlsv1.2') ->
- {3, 3};
-protocol_version('tlsv1.1') ->
- {3, 2};
-protocol_version(tlsv1) ->
- {3, 1};
-protocol_version(sslv3) ->
- {3, 0};
-protocol_version(sslv2) -> %% Backwards compatibility
- {2, 0};
-protocol_version({3, 4}) ->
+protocol_version_name('tlsv1.3') ->
+ ?TLS_1_3;
+protocol_version_name('tlsv1.2') ->
+ ?TLS_1_2;
+protocol_version_name('tlsv1.1') ->
+ ?TLS_1_1;
+protocol_version_name(tlsv1) ->
+ ?TLS_1_0;
+protocol_version_name(sslv3) ->
+ ?SSL_3_0;
+protocol_version_name(sslv2) -> %% Backwards compatibility
+ ?SSL_2_0.
+
+%%--------------------------------------------------------------------
+-spec protocol_version(tls_version()) -> tls_atom_version().
+%%
+%% Description: Creates a protocol version record from a version atom
+%% or vice versa.
+%%--------------------------------------------------------------------
+
+protocol_version(?TLS_1_3) ->
'tlsv1.3';
-protocol_version({3, 3}) ->
+protocol_version(?TLS_1_2) ->
'tlsv1.2';
-protocol_version({3, 2}) ->
+protocol_version(?TLS_1_1) ->
'tlsv1.1';
-protocol_version({3, 1}) ->
+protocol_version(?TLS_1_0) ->
tlsv1;
-protocol_version({3, 0}) ->
+protocol_version(?SSL_3_0) ->
sslv3.
%%--------------------------------------------------------------------
-spec lowest_protocol_version(tls_version(), tls_version()) -> tls_version().
%%
%% Description: Lowes protocol version of two given versions
%%--------------------------------------------------------------------
-lowest_protocol_version(Version = {M, N}, {M, O}) when N < O ->
- Version;
-lowest_protocol_version({M, _},
- Version = {M, _}) ->
- Version;
-lowest_protocol_version(Version = {M,_},
- {N, _}) when M < N ->
- Version;
-lowest_protocol_version(_,Version) ->
- Version.
+lowest_protocol_version(Version1, Version2) when ?TLS_LT(Version1, Version2) ->
+ Version1;
+lowest_protocol_version(_, Version2) ->
+ Version2.
%%--------------------------------------------------------------------
-spec lowest_protocol_version([tls_version()]) -> tls_version().
%%
%% Description: Lowest protocol version present in a list
%%--------------------------------------------------------------------
-lowest_protocol_version([]) ->
- lowest_protocol_version();
lowest_protocol_version(Versions) ->
- [Ver | Vers] = Versions,
- lowest_list_protocol_version(Ver, Vers).
+ check_protocol_version(Versions, fun lowest_protocol_version/2).
%%--------------------------------------------------------------------
-spec highest_protocol_version([tls_version()]) -> tls_version().
%%
%% Description: Highest protocol version present in a list
%%--------------------------------------------------------------------
-highest_protocol_version([]) ->
- highest_protocol_version();
highest_protocol_version(Versions) ->
- [Ver | Vers] = Versions,
- highest_list_protocol_version(Ver, Vers).
+ check_protocol_version(Versions, fun highest_protocol_version/2).
+
+
+check_protocol_version([], Fun) -> check_protocol_version(supported_protocol_versions(), Fun);
+check_protocol_version([Ver | Versions], Fun) -> lists:foldl(Fun, Ver, Versions).
%%--------------------------------------------------------------------
-spec highest_protocol_version(tls_version(), tls_version()) -> tls_version().
%%
%% Description: Highest protocol version of two given versions
%%--------------------------------------------------------------------
-highest_protocol_version(Version = {M, N}, {M, O}) when N > O ->
- Version;
-highest_protocol_version({M, _},
- Version = {M, _}) ->
- Version;
-highest_protocol_version(Version = {M,_},
- {N, _}) when M > N ->
- Version;
-highest_protocol_version(_,Version) ->
- Version.
+highest_protocol_version(Version1, Version2) when ?TLS_GT(Version1, Version2) ->
+ Version1;
+highest_protocol_version(_, Version2) ->
+ Version2.
%%--------------------------------------------------------------------
-spec is_higher(V1 :: tls_version(), V2::tls_version()) -> boolean().
%%
%% Description: Is V1 > V2
%%--------------------------------------------------------------------
-is_higher({M, N}, {M, O}) when N > O ->
+is_higher(V1, V2) when ?TLS_GT(V1, V2) ->
true;
-is_higher({M, _}, {N, _}) when M > N ->
- true;
is_higher(_, _) ->
false.
+
%%--------------------------------------------------------------------
-spec supported_protocol_versions() -> [tls_version()].
%%
@@ -373,7 +366,7 @@ is_higher(_, _) ->
%%--------------------------------------------------------------------
supported_protocol_versions() ->
Fun = fun(Version) ->
- protocol_version(Version)
+ protocol_version_name(Version)
end,
case application:get_env(ssl, protocol_version) of
undefined ->
@@ -399,8 +392,6 @@ supported_protocol_versions([_|_] = Vsns) ->
sufficient_crypto_support(Version) ->
sufficient_crypto_support(crypto:supports(), Version).
-sufficient_crypto_support(CryptoSupport, {_,_} = Version) ->
- sufficient_crypto_support(CryptoSupport, protocol_version(Version));
sufficient_crypto_support(CryptoSupport, Version) when Version == 'tlsv1';
Version == 'tlsv1.1' ->
Hashes = proplists:get_value(hashs, CryptoSupport),
@@ -453,28 +444,28 @@ sufficient_crypto_support(CryptoSupport, 'tlsv1.3') ->
%% {public_keys, eddsa}, %% TODO
{curves, secp256r1}, %% key exchange with secp256r1
{curves, x25519}], %% key exchange with X25519
- lists:all(Fun, L).
+ lists:all(Fun, L);
+sufficient_crypto_support(CryptoSupport, Version) ->
+ sufficient_crypto_support(CryptoSupport, protocol_version(Version)).
+
is_algorithm_supported(CryptoSupport, Group, Algorithm) ->
proplists:get_bool(Algorithm, proplists:get_value(Group, CryptoSupport)).
-spec is_acceptable_version(tls_version()) -> boolean().
-is_acceptable_version({N,_})
- when N >= ?LOWEST_MAJOR_SUPPORTED_VERSION ->
+is_acceptable_version(Version)
+ when ?TLS_1_X(Version) ->
true;
is_acceptable_version(_) ->
false.
-spec is_acceptable_version(tls_version(), Supported :: [tls_version()]) -> boolean().
-is_acceptable_version({N,_} = Version, Versions)
- when N >= ?LOWEST_MAJOR_SUPPORTED_VERSION ->
- lists:member(Version, Versions);
-is_acceptable_version(_,_) ->
- false.
+is_acceptable_version(Version, Versions) ->
+ ?TLS_1_X(Version) andalso lists:member(Version, Versions).
-spec hello_version([tls_version()]) -> tls_version().
-hello_version([Highest|_]) when Highest >= {3,3} ->
- {3,3};
+hello_version([Highest|_]) when ?TLS_GTE(Highest, ?TLS_1_2) ->
+ ?TLS_1_2;
hello_version(Versions) ->
lowest_protocol_version(Versions).
@@ -505,8 +496,9 @@ initial_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) ->
}.
%% Used by logging to recreate the received bytes
-build_tls_record(#ssl_tls{type = Type, version = {MajVer, MinVer}, fragment = Fragment}) ->
+build_tls_record(#ssl_tls{type = Type, version = Version, fragment = Fragment}) ->
Length = byte_size(Fragment),
+ {MajVer, MinVer} = Version,
<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),?UINT16(Length), Fragment/binary>>.
@@ -520,10 +512,13 @@ decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, undefine
if
5 =< Size ->
{<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(5, Q0),
- validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
+ %% TODO: convert the MajVer and MinVer to corresponding macro
+ Version = {MajVer,MinVer},
+ validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
3 =< Size ->
{<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(3, Q0),
- validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
+ Version = {MajVer,MinVer},
+ validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, undefined);
1 =< Size ->
{<<?BYTE(Type)>>, Q} = binary_from_front(1, Q0),
validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, undefined, undefined);
@@ -534,10 +529,12 @@ decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, un
if
4 =< Size ->
{<<?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(4, Q0),
- validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
+ Version = {MajVer,MinVer},
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
2 =< Size ->
{<<?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(2, Q0),
- validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
+ Version = {MajVer,MinVer},
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, undefined);
true ->
validate_tls_record_version(Versions, Q0, MaxFragLen, SslOpts, Acc, Type, undefined, undefined)
end;
@@ -552,38 +549,36 @@ decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, Ve
decode_tls_records(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length).
+
+%% TODO: separate validation logic from modification of the record
validate_tls_records_type(_Versions, Q, _MaxFragLen, _SslOpts, Acc, undefined, _Version, _Length) ->
{lists:reverse(Acc),
{undefined, Q}};
-validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
- if
- ?KNOWN_RECORD_TYPE(Type) ->
- validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
- true ->
- %% Not ?KNOWN_RECORD_TYPE(Type)
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, {unsupported_record_type, Type})
- end.
+validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) when ?KNOWN_RECORD_TYPE(Type) ->
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
+validate_tls_records_type(_Versions, _Q, _MaxFragLen, _SslOpts, _Acc, Type, _Version, _Length) ->
+ %% Not ?KNOWN_RECORD_TYPE(Type)
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, {unsupported_record_type, Type}).
+
validate_tls_record_version(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, undefined, _Length) ->
{lists:reverse(Acc),
{#ssl_tls{type = Type, version = undefined, fragment = undefined}, Q}};
-validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
- case Versions of
- _ when is_list(Versions) ->
- case is_acceptable_version(Version, Versions) of
- true ->
- validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
- false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version})
- end;
- {3, 4} when Version =:= {3, 3} ->
- validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
- Version ->
- %% Exact version match
+validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) when is_list(Versions) ->
+ case is_acceptable_version(Version, Versions) of
+ true ->
validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
- _ ->
+ false ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version})
- end.
+ end;
+validate_tls_record_version(?TLS_1_3=Versions, Q, MaxFragLen, SslOpts, Acc, Type, ?TLS_1_2=Version, Length) ->
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
+validate_tls_record_version(Version, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
+ %% Exact version match
+ validate_tls_record_length(Version, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
+validate_tls_record_version(_Versions, _Q, _MaxFragLen, _SslOpts, _Acc, _Type, Version, _Length) ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version}).
+
validate_tls_record_length(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, Version, undefined) ->
{lists:reverse(Acc),
@@ -713,20 +708,20 @@ encode_fragments(Type, Version, [Text|Data],
CipherHeader = <<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>,
encode_fragments(Type, Version, Data, CS, CompS, CipherS, Seq + 1,
[[CipherHeader, CipherFragment] | CipherFragments]).
+
+
%%--------------------------------------------------------------------
%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 ciphers are
%% not vulnerable to this attack.
-split_iovec(Data, Version, BCA, one_n_minus_one, MaxLength)
- when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
- {3, 0} == Version) ->
+split_iovec(Data, ?TLS_1_0, BCA, one_n_minus_one, MaxLength)
+ when BCA =/= ?RC4 ->
{Part, RestData} = split_iovec(Data, 1, []),
[Part|split_iovec(RestData, MaxLength)];
%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1
%% splitting.
-split_iovec(Data, Version, BCA, zero_n, MaxLength)
- when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
- {3, 0} == Version) ->
+split_iovec(Data, ?TLS_1_0, BCA, zero_n, MaxLength)
+ when BCA =/= ?RC4 ->
{Part, RestData} = split_iovec(Data, 0, []),
[Part|split_iovec(RestData, MaxLength)];
split_iovec(Data, _Version, _BCA, _BeatMitigation, MaxLength) ->
@@ -747,23 +742,9 @@ split_iovec([], _SplitSize, Acc) ->
{lists:reverse(Acc),[]}.
%%--------------------------------------------------------------------
-lowest_list_protocol_version(Ver, []) ->
- Ver;
-lowest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest).
-
-highest_list_protocol_version(Ver, []) ->
- Ver;
-highest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest).
-
-highest_protocol_version() ->
- highest_protocol_version(supported_protocol_versions()).
-lowest_protocol_version() ->
- lowest_protocol_version(supported_protocol_versions()).
-max_len([{3,4}|_])->
+max_len([?TLS_1_3|_])->
?TLS13_MAX_CIPHER_TEXT_LENGTH;
max_len(_) ->
?MAX_CIPHER_TEXT_LENGTH.
diff --git a/lib/ssl/src/tls_record.hrl b/lib/ssl/src/tls_record.hrl
index e446e481a7..d2e6ad262f 100644
--- a/lib/ssl/src/tls_record.hrl
+++ b/lib/ssl/src/tls_record.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@
%% Used to handle tls_plain_text, tls_compressed and tls_cipher_text
-record(ssl_tls, {
type,
- version,
+ version :: tls_record:tls_version() | undefined,
fragment,
early_data = false % TLS-1.3
}).
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
index 440d9e0998..abd67774ce 100644
--- a/lib/ssl/src/tls_record_1_3.erl
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -171,7 +171,7 @@ decode_cipher_text(#ssl_tls{type = ?ALERT,
fragment = <<?FATAL,?ILLEGAL_PARAMETER>>},
ConnectionStates0) ->
{#ssl_tls{type = ?ALERT,
- version = {3,4}, %% Internally use real version
+ version = ?TLS_1_3, %% Internally use real version
fragment = <<?FATAL,?ILLEGAL_PARAMETER>>}, ConnectionStates0};
%% TLS 1.3 server can receive a User Cancelled Alert when handshake is
%% paused and then cancelled on the client side.
@@ -180,7 +180,7 @@ decode_cipher_text(#ssl_tls{type = ?ALERT,
fragment = <<?FATAL,?USER_CANCELED>>},
ConnectionStates0) ->
{#ssl_tls{type = ?ALERT,
- version = {3,4}, %% Internally use real version
+ version = ?TLS_1_3, %% Internally use real version
fragment = <<?FATAL,?USER_CANCELED>>}, ConnectionStates0};
%% RFC8446 - TLS 1.3
%% D.4. Middlebox Compatibility Mode
@@ -195,7 +195,7 @@ decode_cipher_text(#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
fragment = <<1>>},
ConnectionStates0) ->
{#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
- version = {3,4}, %% Internally use real version
+ version = ?TLS_1_3, %% Internally use real version
fragment = <<1>>}, ConnectionStates0};
decode_cipher_text(#ssl_tls{type = Type,
version = ?LEGACY_VERSION,
@@ -206,7 +206,7 @@ decode_cipher_text(#ssl_tls{type = Type,
cipher_suite = ?TLS_NULL_WITH_NULL_NULL}
}} = ConnnectionStates0) ->
{#ssl_tls{type = Type,
- version = {3,4}, %% Internally use real version
+ version = ?TLS_1_3, %% Internally use real version
fragment = CipherFragment}, ConnnectionStates0};
decode_cipher_text(#ssl_tls{type = Type}, _) ->
%% Version mismatch is already asserted
@@ -288,7 +288,7 @@ encode_plain_text(#inner_plaintext{
Encoded = cipher_aead(PlainText, BulkCipherAlgo, Key, Seq, IV, TagLen),
%% 23 (application_data) for outward compatibility
#tls_cipher_text{opaque_type = ?OPAQUE_TYPE,
- legacy_version = {3,3},
+ legacy_version = ?LEGACY_VERSION,
encoded_record = Encoded};
encode_plain_text(#inner_plaintext{
content = Data,
@@ -301,7 +301,7 @@ encode_plain_text(#inner_plaintext{
%% When record protection has not yet been engaged, TLSPlaintext
%% structures are written directly onto the wire.
#tls_cipher_text{opaque_type = Type,
- legacy_version = {3,3},
+ legacy_version = ?TLS_1_2,
encoded_record = Data}.
additional_data(Length) ->
@@ -330,10 +330,11 @@ cipher_aead(Fragment, BulkCipherAlgo, Key, Seq, IV, TagLen) ->
<<Content/binary, CipherTag/binary>>.
encode_tls_cipher_text(#tls_cipher_text{opaque_type = Type,
- legacy_version = {MajVer, MinVer},
+ legacy_version = Version,
encoded_record = Encoded},
#{sequence_number := Seq} = Write) ->
Length = erlang:iolist_size(Encoded),
+ {MajVer,MinVer} = Version,
{[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Encoded],
Write#{sequence_number => Seq +1}}.
@@ -375,7 +376,7 @@ decode_inner_plaintext(PlainText) ->
Type =:= ?HANDSHAKE orelse
Type =:= ?ALERT ->
#ssl_tls{type = Type,
- version = {3,4}, %% Internally use real version
+ version = ?TLS_1_3, %% Internally use real version
fragment = init_binary(PlainText)};
_Else ->
?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_alert)
diff --git a/lib/ssl/src/tls_record_1_3.hrl b/lib/ssl/src/tls_record_1_3.hrl
index c6214a5de3..c2624e8fde 100644
--- a/lib/ssl/src/tls_record_1_3.hrl
+++ b/lib/ssl/src/tls_record_1_3.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@
%% } ContentType;
-define(INVALID, 0).
--define(LEGACY_VERSION, {3,3}).
+-define(LEGACY_VERSION, ?TLS_1_2).
-define(OPAQUE_TYPE, 23).
-record(inner_plaintext, {
@@ -55,7 +55,7 @@
%% decrypted version will still use #ssl_tls for code reuse purposes
%% with real values for content type and version
opaque_type = ?OPAQUE_TYPE,
- legacy_version = ?LEGACY_VERSION,
+ legacy_version = ?LEGACY_VERSION :: ssl_record:ssl_version(),
encoded_record
}).
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index 3b1ceeca8a..455684bd87 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -54,6 +54,8 @@
connection/3,
handshake/3,
death_row/3]).
+%% Tracing
+-export([handle_trace/3]).
-record(static,
{connection_pid,
@@ -566,14 +568,14 @@ send_post_handshake_data(Handshake, From, StateName,
maybe_update_cipher_key(#data{connection_states = ConnectionStates0,
static = Static0} = StateData, #key_update{}) ->
- ConnectionStates = tls_connection_1_3:update_cipher_key(current_write, ConnectionStates0),
+ ConnectionStates = tls_gen_connection_1_3:update_cipher_key(current_write, ConnectionStates0),
Static = Static0#static{bytes_sent = 0},
StateData#data{connection_states = ConnectionStates,
static = Static};
maybe_update_cipher_key(StateData, _) ->
StateData.
-update_bytes_sent(Version, StateData, _) when Version < {3,4} ->
+update_bytes_sent(Version, StateData, _) when ?TLS_LT(Version, ?TLS_1_3) ->
StateData;
%% Count bytes sent in TLS 1.3 for AES-GCM
update_bytes_sent(_, #data{static = #static{key_update_at = seq_num_wrap}} = StateData, _) ->
@@ -586,20 +588,12 @@ update_bytes_sent(_, #data{static = #static{bytes_sent = Sent} = Static} = State
%% approximately 2^-57 for Authenticated Encryption (AE) security. For
%% ChaCha20/Poly1305, the record sequence number would wrap before the
%% safety limit is reached.
-key_update_at(Version, #{security_parameters :=
+key_update_at(?TLS_1_3, #{security_parameters :=
#security_parameters{
- bulk_cipher_algorithm = CipherAlgo}}, KeyUpdateAt)
- when Version >= {3,4} ->
- case CipherAlgo of
- ?AES_GCM ->
- KeyUpdateAt;
- ?CHACHA20_POLY1305 ->
- seq_num_wrap;
- ?AES_CCM ->
- KeyUpdateAt;
- ?AES_CCM_8 ->
- KeyUpdateAt
- end;
+ bulk_cipher_algorithm = ?CHACHA20_POLY1305}}, _KeyUpdateAt) ->
+ seq_num_wrap;
+key_update_at(?TLS_1_3, _, KeyUpdateAt) ->
+ KeyUpdateAt;
key_update_at(_, _, KeyUpdateAt) ->
KeyUpdateAt.
@@ -622,11 +616,11 @@ set_opts(SocketOptions, [{packet, N}]) ->
time_to_rekey(Version, _Data,
#{current_write := #{sequence_number := ?MAX_SEQUENCE_NUMBER}},
- _, _, _) when Version >= {3,4} ->
+ _, _, _) when ?TLS_GTE(Version, ?TLS_1_3) ->
key_update;
-time_to_rekey(Version, _Data, _, _, seq_num_wrap, _) when Version >= {3,4} ->
+time_to_rekey(Version, _Data, _, _, seq_num_wrap, _) when ?TLS_GTE(Version, ?TLS_1_3) ->
false;
-time_to_rekey(Version, Data, _, _, KeyUpdateAt, BytesSent) when Version >= {3,4} ->
+time_to_rekey(Version, Data, _, _, KeyUpdateAt, BytesSent) when ?TLS_GTE(Version, ?TLS_1_3) ->
DataSize = iolist_size(Data),
case (BytesSent + DataSize) > KeyUpdateAt of
true ->
@@ -719,3 +713,51 @@ hibernate_after(connection = StateName,
{next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
hibernate_after(StateName, State, Actions) ->
{next_state, StateName, State, Actions}.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(kdt,
+ {call, {?MODULE, time_to_rekey,
+ [_Version, Data, Map, _RenegotiateAt,
+ KeyUpdateAt, BytesSent]}}, Stack) ->
+ #{current_write := #{sequence_number := Sn}} = Map,
+ DataSize = iolist_size(Data),
+ {io_lib:format("~w) (BytesSent:~w + DataSize:~w) > KeyUpdateAt:~w",
+ [Sn, BytesSent, DataSize, KeyUpdateAt]), Stack};
+handle_trace(kdt,
+ {call, {?MODULE, send_post_handshake_data,
+ [{key_update, update_requested}|_]}}, Stack) ->
+ {io_lib:format("KeyUpdate procedure 1/4 - update_requested sent", []), Stack};
+handle_trace(kdt,
+ {call, {?MODULE, send_post_handshake_data,
+ [{key_update, update_not_requested}|_]}}, Stack) ->
+ {io_lib:format("KeyUpdate procedure 3/4 - update_not_requested sent", []), Stack};
+handle_trace(hbn,
+ {call, {?MODULE, connection,
+ [timeout, hibernate | _]}}, Stack) ->
+ {io_lib:format("* * * hibernating * * *", []), Stack};
+handle_trace(hbn,
+ {call, {?MODULE, hibernate_after,
+ [_StateName = connection, State, Actions]}},
+ Stack) ->
+ #data{static=#static{hibernate_after = HibernateAfter}} = State,
+ {io_lib:format("* * * maybe hibernating in ~w ms * * * Actions = ~W ",
+ [HibernateAfter, Actions, 10]), Stack};
+handle_trace(hbn,
+ {return_from, {?MODULE, hibernate_after, 3},
+ {Cmd, Arg,_State, Actions}},
+ Stack) ->
+ {io_lib:format("Cmd = ~w Arg = ~w Actions = ~W", [Cmd, Arg, Actions, 10]), Stack};
+handle_trace(rle,
+ {call, {?MODULE, init, [Type, Opts, _StateData]}}, Stack0) ->
+ {Pid, #{role := Role,
+ socket := _Socket,
+ key_update_at := KeyUpdateAt,
+ erl_dist := IsErlDist,
+ trackers := Trackers,
+ negotiated_version := _Version}} = Opts,
+ {io_lib:format("(*~w) Type = ~w Pid = ~w Trackers = ~w Dist = ~w KeyUpdateAt = ~w",
+ [Role, Type, Pid, Trackers, IsErlDist, KeyUpdateAt]),
+ [{role, Role} | Stack0]}.
diff --git a/lib/ssl/src/tls_server_connection_1_3.erl b/lib/ssl/src/tls_server_connection_1_3.erl
new file mode 100644
index 0000000000..f00cf12d74
--- /dev/null
+++ b/lib/ssl/src/tls_server_connection_1_3.erl
@@ -0,0 +1,914 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: TLS-1.3 FSM (server side)
+%%----------------------------------------------------------------------
+%% A.2. Server
+%%
+%% START <-----+
+%% Recv ClientHello | | Send HelloRetryRequest
+%% v |
+%% RECVD_CH ----+
+%% | Select parameters
+%% v
+%% NEGOTIATED
+%% | Send ServerHello
+%% | K_send = handshake
+%% | Send EncryptedExtensions
+%% | [Send CertificateRequest]
+%% Can send | [Send Certificate + CertificateVerify]
+%% app data | Send Finished
+%% after --> | K_send = application
+%% here +--------+--------+
+%% No 0-RTT | | 0-RTT
+%% | |
+%% K_recv = handshake | | K_recv = early data
+%% [Skip decrypt errors] | +------> WAIT_EOED -+
+%% | | Recv | | Recv EndOfEarlyData
+%% | | early data | | K_recv = handshake
+%% | +------------+ |
+%% | |
+%% +> WAIT_FLIGHT2 <--------+
+%% |
+%% +--------+--------+
+%% No auth | | Client auth
+%% | |
+%% | v
+%% | WAIT_CERT
+%% | Recv | | Recv Certificate
+%% | empty | v
+%% | Certificate | WAIT_CV
+%% | | | Recv
+%% | v | CertificateVerify
+%% +-> WAIT_FINISHED <---+
+%% | Recv Finished
+%% | K_recv = application
+%% v
+%% CONNECTED
+
+
+-module(tls_server_connection_1_3).
+
+-include_lib("public_key/include/public_key.hrl").
+
+-include("ssl_alert.hrl").
+-include("ssl_connection.hrl").
+-include("tls_connection.hrl").
+-include("tls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
+
+-behaviour(gen_statem).
+
+%% gen_statem callbacks
+-export([init/1,
+ callback_mode/0,
+ terminate/3,
+ code_change/4,
+ format_status/2]).
+
+%% gen_statem state functions
+-export([config_error/3,
+ initial_hello/3,
+ user_hello/3,
+ start/3,
+ negotiated/3,
+ wait_cert/3,
+ wait_cv/3,
+ wait_finished/3,
+ wait_eoed/3,
+ connection/3,
+ downgrade/3
+ ]).
+
+%--------------------------------------------------------------------
+%% gen_statem callbacks
+%%--------------------------------------------------------------------
+callback_mode() ->
+ [state_functions, state_enter].
+
+init([?SERVER_ROLE, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
+ State0 = #state{protocol_specific = Map} =
+ tls_gen_connection_1_3:initial_state(?SERVER_ROLE, Sender,
+ Host, Port, Socket, Options, User, CbInfo),
+ try
+ State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options, ?SERVER_ROLE, State0),
+ tls_gen_connection:initialize_tls_sender(State),
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
+ catch throw:Error ->
+ EState = State0#state{protocol_specific = Map#{error => Error}},
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
+ end.
+
+terminate({shutdown, {sender_died, Reason}}, _StateName,
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport}}
+ = State) ->
+ ssl_gen_statem:handle_trusted_certs_db(State),
+ tls_gen_connection:close(Reason, Socket, Transport, undefined);
+terminate(Reason, StateName, State) ->
+ ssl_gen_statem:terminate(Reason, StateName, State).
+
+format_status(Type, Data) ->
+ ssl_gen_statem:format_status(Type, Data).
+
+code_change(_OldVsn, StateName, State, _) ->
+ {ok, StateName, State}.
+
+%--------------------------------------------------------------------
+%% state functions
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+initial_hello(enter, _, State) ->
+ {keep_state, State};
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+config_error(enter, _, State) ->
+ {keep_state, State};
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec user_hello(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+user_hello(enter, _, State) ->
+ {keep_state, State};
+user_hello({call, From}, cancel, State) ->
+ gen_statem:reply(From, ok),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
+ ?FUNCTION_NAME, State);
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{handshake_env = #handshake_env{continue_status = {pause, ClientVersions}} = HSEnv,
+ ssl_options = Options0} = State0) ->
+ try ssl:update_options(NewOptions, ?SERVER_ROLE, Options0) of
+ Options = #{versions := Versions} ->
+ State = ssl_gen_statem:ssl_config(Options, ?SERVER_ROLE, State0),
+ case ssl_handshake:select_supported_version(ClientVersions, Versions) of
+ ?TLS_1_3 ->
+ {next_state, start, State#state{start_or_recv_from = From,
+ handshake_env = HSEnv#handshake_env{continue_status = continue}},
+ [{{timeout, handshake}, Timeout, close}]};
+ undefined ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State);
+ _Else ->
+ {next_state, hello, State#state{start_or_recv_from = From,
+ handshake_env = HSEnv#handshake_env{continue_status = continue}},
+ [{change_callback_module, tls_connection},
+ {{timeout, handshake}, Timeout, close}]}
+ end
+ catch
+ throw:{error, Reason} ->
+ gen_statem:reply(From, {error, Reason}),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), ?FUNCTION_NAME, State0)
+ end;
+user_hello(Type, Msg, State) ->
+ tls_gen_connection_1_3:user_hello(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec start(gen_statem:event_type(),
+ #client_hello{} | #change_cipher_spec{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+start(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+start(internal = Type, #change_cipher_spec{} = Msg,
+ #state{handshake_env = #handshake_env{tls_handshake_history = Hist}} = State) ->
+ case ssl_handshake:init_handshake_history() of
+ Hist -> %% First message must always be client hello
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State);
+ _ ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State)
+ end;
+start(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+start(internal, #client_hello{extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }} = Hello,
+ #state{ssl_options = #{handshake := full}} = State) ->
+ case tls_record:is_acceptable_version(?TLS_1_3, ClientVersions) of
+ true ->
+ handle_client_hello(Hello, State);
+ false ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION),
+ ?FUNCTION_NAME, State)
+ end;
+start(internal, #client_hello{extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }= Extensions},
+ #state{start_or_recv_from = From,
+ handshake_env = #handshake_env{continue_status = pause} = HSEnv} = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined,
+ handshake_env = HSEnv#handshake_env{continue_status = {pause, ClientVersions}}},
+ [{postpone, true}, {reply, From, {ok, Extensions}}]};
+start(internal, #client_hello{} = Hello,
+ #state{handshake_env = #handshake_env{continue_status = continue}} = State) ->
+ handle_client_hello(Hello, State);
+start(internal, #client_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions,
+ %% so it is a previous version hello.
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0);
+start(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+start(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec negotiated(gen_statem:event_type(),
+ {start_handshake, #pre_shared_key_server_hello{} | undefined} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+negotiated(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+negotiated(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+negotiated(internal, {start_handshake, _} = Message, State0) ->
+ case send_hello_flight(Message, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, negotiated, State0);
+ {State, NextState} ->
+ {next_state, NextState, State, []}
+ end;
+negotiated(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+negotiated(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert(Type, Msg, State) ->
+ tls_gen_connection_1_3:wait_cert(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cv(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cv(internal,
+ #certificate_verify_1_3{} = CertificateVerify, State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ State1 = Maybe(tls_handshake_1_3:verify_signature_algorithm(State0,
+ CertificateVerify)),
+ {State, NextState} =
+ Maybe(tls_handshake_1_3:verify_certificate_verify(State1, CertificateVerify)),
+ tls_gen_connection:next_event(NextState, no_record, State)
+ catch
+ {Ref, {#alert{} = Alert, AState}} ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_cv, AState)
+ end;
+wait_cv(Type, Msg, State) ->
+ tls_gen_connection_1_3:wait_cv(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec wait_finished(gen_statem:event_type(),
+ #finished{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_finished(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+wait_finished(internal,
+ #finished{verify_data = VerifyData}, State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ Maybe(tls_handshake_1_3:validate_finished(State0, VerifyData)),
+
+ State1 = tls_handshake_1_3:calculate_traffic_secrets(State0),
+ State2 = tls_handshake_1_3:maybe_calculate_resumption_master_secret(State1),
+ State3 = tls_handshake_1_3:forget_master_secret(State2),
+
+ %% Configure traffic keys
+ State4 = ssl_record:step_encryption_state(State3),
+
+ State5 = maybe_send_session_ticket(State4),
+
+ {Record, State} = ssl_gen_statem:prepare_connection(State5, tls_gen_connection),
+ tls_gen_connection:next_event(connection, Record, State,
+ [{{timeout, handshake}, cancel}])
+ catch
+ {Ref, #alert{} = Alert} ->
+ ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0)
+ end;
+wait_finished(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_finished(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_eoed(gen_statem:event_type(),
+ #end_of_early_data{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_eoed(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_eoed(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+wait_eoed(internal, #end_of_early_data{}, #state{handshake_env = HsEnv0} = State0) ->
+ try
+ State = ssl_record:step_encryption_state_read(State0),
+ HsEnv = HsEnv0#handshake_env{early_data_accepted = false},
+ tls_gen_connection:next_event(wait_finished, no_record, State#state{handshake_env = HsEnv})
+ catch
+ error:Reason:ST ->
+ ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason),
+ wait_eoed, State0)
+ end;
+wait_eoed(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_eoed(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connection(Type, Msg, State) ->
+ tls_gen_connection_1_3:connection(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec downgrade(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+downgrade(Type, Msg, State) ->
+ tls_gen_connection_1_3:downgrade(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+handle_client_hello(ClientHello, State0) ->
+ case do_handle_client_hello(ClientHello, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, start, State0);
+ {State, start} ->
+ {next_state, start, State, []};
+ {State, negotiated, PSK} -> %% Session Resumption with PSK i PSK =/= undefined
+ {next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]}
+ end.
+
+do_handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
+ session_id = SessionId,
+ extensions = Extensions} = Hello,
+ #state{ssl_options = #{ciphers := ServerCiphers,
+ signature_algs := ServerSignAlgs,
+ supported_groups := ServerGroups0,
+ alpn_preferred_protocols := ALPNPreferredProtocols,
+ honor_cipher_order := HonorCipherOrder,
+ early_data := EarlyDataEnabled} = Opts} = State0) ->
+ SNI = maps:get(sni, Extensions, undefined),
+ EarlyDataIndication = maps:get(early_data, Extensions, undefined),
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ ClientGroups0 = Maybe(tls_handshake_1_3:supported_groups_from_extensions(Extensions)),
+ ClientGroups = Maybe(tls_handshake_1_3:get_supported_groups(ClientGroups0)),
+ ServerGroups = Maybe(tls_handshake_1_3:get_supported_groups(ServerGroups0)),
+
+ ClientShares = maps:get(key_share, Extensions, []),
+ OfferedPSKs = maps:get(pre_shared_key, Extensions, undefined),
+
+ ClientALPN0 = maps:get(alpn, Extensions, undefined),
+ ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
+
+ ClientSignAlgs = tls_handshake_1_3:get_signature_scheme_list(
+ maps:get(signature_algs, Extensions, undefined)),
+ ClientSignAlgsCert = tls_handshake_1_3:get_signature_scheme_list(
+ maps:get(signature_algs_cert, Extensions, undefined)),
+ CertAuths = tls_handshake_1_3:get_certificate_authorities(maps:get(certificate_authorities,
+ Extensions, undefined)),
+ Cookie = maps:get(cookie, Extensions, undefined),
+
+ #state{connection_states = ConnectionStates0,
+ session = Session0,
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts}} = State1 =
+ Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)),
+
+ Maybe(validate_cookie(Cookie, State1)),
+
+ %% Handle ALPN extension if ALPN is configured
+ ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)),
+
+ %% If the server does not select a PSK, then the server independently selects a
+ %% cipher suite, an (EC)DHE group and key share for key establishment,
+ %% and a signature algorithm/certificate pair to authenticate itself to
+ %% the client.
+ Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)),
+ Groups = Maybe(tls_handshake_1_3:select_common_groups(ServerGroups, ClientGroups)),
+ Maybe(validate_client_key_share(ClientGroups,
+ ClientShares#key_share_client_hello.client_shares)),
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, ?TLS_1_3),
+ #session{own_certificates = [Cert|_]} = Session =
+ Maybe(select_server_cert_key_pair(Session0, CertKeyPairs, ClientSignAlgs,
+ ClientSignAlgsCert, CertAuths, State0,
+ undefined)),
+ {PublicKeyAlgo, _, _, RSAKeySize, Curve} = tls_handshake_1_3:get_certificate_params(Cert),
+
+ %% Select signature algorithm (used in CertificateVerify message).
+ SelectedSignAlg = Maybe(tls_handshake_1_3:select_sign_algo(PublicKeyAlgo,
+ RSAKeySize, ClientSignAlgs,
+ ServerSignAlgs, Curve)),
+
+ %% Select client public key. If no public key found in ClientShares or
+ %% ClientShares is empty, trigger HelloRetryRequest as we were able
+ %% to find an acceptable set of parameters but the ClientHello does not
+ %% contain sufficient information.
+ {Group, ClientPubKey} = select_client_public_key(Groups, ClientShares),
+
+ %% Generate server_share
+ KeyShare = ssl_cipher:generate_server_share(Group),
+
+ State2 = case maps:get(max_frag_enum, Extensions, undefined) of
+ MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) ->
+ ConnectionStates1 =
+ ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
+ HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum},
+ State1#state{handshake_env = HsEnv1,
+ session = Session,
+ connection_states = ConnectionStates1};
+ _ ->
+ State1#state{session = Session}
+ end,
+
+ State3 = case maps:get(keep_secrets, Opts, false) of
+ true -> tls_handshake_1_3:set_client_random(State2, Hello#client_hello.random);
+ false -> State2
+ end,
+
+ State4 = tls_handshake_1_3:update_start_state(State3,
+ #{cipher => Cipher,
+ key_share => KeyShare,
+ session_id => SessionId,
+ group => Group,
+ sign_alg => SelectedSignAlg,
+ peer_public_key => ClientPubKey,
+ alpn => ALPNProtocol}),
+
+ %% 4.1.4. Hello Retry Request
+ %%
+ %% The server will send this message in response to a ClientHello
+ %% message if it is able to find an acceptable set of parameters but the
+ %% ClientHello does not contain sufficient information to proceed with
+ %% the handshake.
+ case Maybe(send_hello_retry_request(State4, ClientPubKey, KeyShare, SessionId)) of
+ {_, start} = NextStateTuple ->
+ NextStateTuple;
+ {State5, negotiated} ->
+ %% Determine if early data is accepted
+ State = handle_early_data(State5, EarlyDataEnabled, EarlyDataIndication),
+ %% Exclude any incompatible PSKs.
+ PSK = Maybe(tls_handshake_1_3:handle_pre_shared_key(State, OfferedPSKs, Cipher)),
+ Maybe(session_resumption({State, negotiated}, PSK))
+ end
+ catch
+ {Ref, #alert{} = Alert} ->
+ Alert
+ end.
+
+send_hello_flight({start_handshake, PSK0},
+ #state{connection_states = ConnectionStates0,
+ handshake_env =
+ #handshake_env{
+ early_data_accepted = EarlyDataAccepted},
+ static_env = #static_env{protocol_cb = Connection},
+ session = #session{session_id = SessionId,
+ ecc = SelectedGroup,
+ dh_public_value = ClientPublicKey},
+ ssl_options = #{} = SslOpts,
+ key_share = KeyShare} = State0) ->
+ ServerPrivateKey = select_server_private_key(KeyShare),
+
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{prf_algorithm = HKDF} = SecParamsR,
+
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ %% Create server_hello
+ ServerHello = tls_handshake_1_3:server_hello(server_hello, SessionId,
+ KeyShare, PSK0, ConnectionStates0),
+ State1 = Connection:queue_handshake(ServerHello, State0),
+ %% D.4. Middlebox Compatibility Mode
+ State2 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State1, last),
+
+ PSK = tls_handshake_1_3:get_pre_shared_key(PSK0, HKDF),
+
+ State3 =
+ tls_handshake_1_3:calculate_handshake_secrets(ClientPublicKey,
+ ServerPrivateKey, SelectedGroup,
+ PSK, State2),
+ %% Step only write state if early_data is accepted
+ State4 =
+ case EarlyDataAccepted of
+ true ->
+ ssl_record:step_encryption_state_write(State3);
+ false ->
+ %% Read state is overwritten when handshake secrets are set.
+ %% Trial_decryption and early_data_accepted must be set here!
+ update_current_read(
+ ssl_record:step_encryption_state(State3),
+ true, %% trial_decryption
+ false %% early_data_accepted
+ )
+
+ end,
+
+ %% Create EncryptedExtensions
+ EncryptedExtensions = tls_handshake_1_3:encrypted_extensions(State4),
+
+ %% Encode EncryptedExtensions
+ State5 = Connection:queue_handshake(EncryptedExtensions, State4),
+
+ %% Create and send CertificateRequest ({verify, verify_peer})
+ {State6, NextState} = maybe_send_certificate_request(State5, SslOpts, PSK0),
+
+ %% Create and send Certificate (if PSK is undefined)
+ State7 = Maybe(maybe_send_certificate(State6, PSK0)),
+
+ %% Create and send CertificateVerify (if PSK is undefined)
+ State8 = Maybe(maybe_send_certificate_verify(State7, PSK0)),
+
+ %% Create Finished
+ Finished = tls_handshake_1_3:finished(State8),
+
+ %% Encode Finished
+ State9 = Connection:queue_handshake(Finished, State8),
+
+ %% Send first flight
+ {State, _} = Connection:send_handshake_flight(State9),
+
+ {State, NextState}
+
+ catch
+ {Ref, #alert{} = Alert} ->
+ Alert;
+ error:badarg ->
+ ?ALERT_REC(?ILLEGAL_PARAMETER, illegal_parameter_to_compute_key)
+ end.
+
+validate_cookie(_Cookie, #state{ssl_options = #{cookie := false}}) ->
+ ok;
+validate_cookie(undefined, #state{ssl_options = #{cookie := true}}) ->
+ ok;
+validate_cookie(#cookie{cookie = Cookie0}, #state{ssl_options = #{cookie := true},
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history =
+ {[_CH2,_HRR,MessageHash|_], _},
+ cookie_iv_shard = {IV, Shard}}}) ->
+ Cookie = ssl_cipher:decrypt_data(<<"cookie">>, Cookie0, Shard, IV),
+ case Cookie =:= iolist_to_binary(MessageHash) of
+ true ->
+ ok;
+ false ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
+ end;
+validate_cookie(_,_) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}.
+
+session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State, negotiated}, _) ->
+ {ok, {State, negotiated, undefined}}; % Resumption prohibited
+session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State, negotiated}, undefined = PSK)
+ when Tickets =/= disabled ->
+ {ok, {State, negotiated, PSK}}; % No resumption
+session_resumption({#state{ssl_options = #{session_tickets := Tickets},
+ handshake_env = #handshake_env{
+ early_data_accepted = false}} = State0, negotiated}, PSKInfo)
+ when Tickets =/= disabled -> % Resumption but early data prohibited
+ State1 = tls_gen_connection_1_3:handle_resumption(State0, ok),
+ {Index, PSK, PeerCert} = PSKInfo,
+ State = maybe_store_peer_cert(State1, PeerCert),
+ State = maybe_store_peer_cert(State1, PeerCert),
+ {ok, {State, negotiated, {Index, PSK}}};
+session_resumption({#state{ssl_options = #{session_tickets := Tickets},
+ handshake_env = #handshake_env{
+ early_data_accepted = true}} = State0, negotiated}, PSKInfo)
+ when Tickets =/= disabled -> % Resumption with early data allowed
+ State1 = tls_gen_connection_1_3:handle_resumption(State0, ok),
+ %% TODO Refactor PSK-tuple {Index, PSK}, index might not be needed.
+ {Index, PSK, PeerCert} = PSKInfo,
+ State2 = tls_handshake_1_3:calculate_client_early_traffic_secret(State1, PSK),
+ %% Set 0-RTT traffic keys for reading early_data
+ State3 = ssl_record:step_encryption_state_read(State2),
+ State4 = maybe_store_peer_cert(State3, PeerCert),
+ State = update_current_read(State4, true, true),
+ {ok, {State, negotiated, {Index, PSK}}}.
+
+maybe_store_peer_cert(State, undefined) ->
+ State;
+maybe_store_peer_cert(#state{session = Session} = State, PeerCert) ->
+ State#state{session = Session#session{peer_certificate = PeerCert}}.
+
+maybe_send_session_ticket(State) ->
+ Number = case application:get_env(ssl, server_session_tickets_amount) of
+ {ok, Size} when is_integer(Size) andalso
+ Size > 0 ->
+ Size;
+ _ ->
+ 3
+ end,
+ maybe_send_session_ticket(State, Number).
+
+maybe_send_session_ticket(#state{ssl_options = #{session_tickets := disabled}} = State, _) ->
+ %% Do nothing!
+ State;
+maybe_send_session_ticket(State, 0) ->
+ State;
+maybe_send_session_ticket(#state{connection_states = ConnectionStates,
+ static_env = #static_env{trackers = Trackers,
+ protocol_cb = Connection}
+ } = State0, N) ->
+ Tracker = proplists:get_value(session_tickets_tracker, Trackers),
+ #{security_parameters := SecParamsR} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDF,
+ resumption_master_secret = RMS} = SecParamsR,
+ Ticket = new_session_ticket(Tracker, HKDF, RMS, State0),
+ {State, _} = Connection:send_handshake(Ticket, State0),
+ maybe_send_session_ticket(State, N - 1).
+
+new_session_ticket(Tracker, HKDF, RMS, #state{ssl_options = #{session_tickets := stateful_with_cert},
+ session = #session{peer_certificate = PeerCert}}) ->
+ tls_server_session_ticket:new(Tracker, HKDF, RMS, PeerCert);
+new_session_ticket(Tracker, HKDF, RMS, #state{ssl_options = #{session_tickets := stateless_with_cert},
+ session = #session{peer_certificate = PeerCert}}) ->
+ tls_server_session_ticket:new(Tracker, HKDF, RMS, PeerCert);
+new_session_ticket(Tracker, HKDF, RMS, _) ->
+ tls_server_session_ticket:new(Tracker, HKDF, RMS, undefined).
+
+select_server_cert_key_pair(_,[], _,_,_,_, #session{}=Session) ->
+ %% Conformant Cert-Key pair with advertised signature algorithm is
+ %% selected.
+ {ok, Session};
+select_server_cert_key_pair(_,[], _,_,_,_, {fallback, #session{}=Session}) ->
+ %% Use fallback Cert-Key pair as no conformant pair to the advertised
+ %% signature algorithms was found.
+ {ok, Session};
+select_server_cert_key_pair(_,[], _,_,_,_, undefined) ->
+ {error, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unable_to_supply_acceptable_cert)};
+select_server_cert_key_pair(Session, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest],
+ ClientSignAlgs, ClientSignAlgsCert, CertAuths,
+ #state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef} = State},
+ Default0) ->
+ {_, SignAlgo, SignHash, _, _} = tls_handshake_1_3:get_certificate_params(Cert),
+ %% TODO: We do validate the signature algorithm and signature hash
+ %% but we could also check if the signing cert has a key on a
+ %% curve supported by the client for ECDSA/EDDSA certs
+ case tls_handshake_1_3:check_cert_sign_algo(SignAlgo, SignHash,
+ ClientSignAlgs, ClientSignAlgsCert) of
+ ok ->
+ case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of
+ {ok, EncodeChain} -> %% Chain fullfills certificate_authorities extension
+ {ok, Session#session{own_certificates = EncodeChain, private_key = Key}};
+ {error, EncodeChain, not_in_auth_domain} ->
+ %% If this is the first chain to fulfill the
+ %% signing requirement, use it as default, if not
+ %% later alternative also fulfills
+ %% certificate_authorities extension
+ Default = Session#session{own_certificates = EncodeChain, private_key = Key},
+ select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
+ CertAuths, State,
+ default_or_fallback(Default0, Default))
+ end;
+ _ ->
+ %% If the server cannot produce a certificate chain that
+ %% is signed only via the indicated supported algorithms,
+ %% then it SHOULD continue the handshake by sending the
+ %% client a certificate chain of its choice
+ case SignHash of
+ sha ->
+ %% According to "Server Certificate Selection -
+ %% RFC 8446" Never send cert using sha1 unless
+ %% client allows it
+ select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
+ CertAuths, State, Default0);
+ _ ->
+ %% If there does not exist a default or fallback
+ %% from previous alternatives use this alternative
+ %% as fallback.
+ Fallback = {fallback, Session#session{own_certificates = Certs, private_key = Key}},
+ select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
+ CertAuths, State,
+ default_or_fallback(Default0, Fallback))
+ end
+ end.
+
+default_or_fallback(undefined, DefaultOrFallback) ->
+ DefaultOrFallback;
+default_or_fallback({fallback, _}, #session{} = Default) ->
+ Default;
+default_or_fallback(Default, _) ->
+ Default.
+
+select_server_private_key(#key_share_server_hello{server_share = ServerShare}) ->
+ select_private_key(ServerShare).
+
+select_private_key(#key_share_entry{
+ key_exchange = #'ECPrivateKey'{} = PrivateKey}) ->
+ PrivateKey;
+select_private_key(#key_share_entry{
+ key_exchange =
+ {_, PrivateKey}}) ->
+ PrivateKey.
+
+
+select_client_public_key([Group|_] = Groups, ClientShares) ->
+ select_client_public_key(Groups, ClientShares, Group).
+
+select_client_public_key([], _, PreferredGroup) ->
+ {PreferredGroup, no_suitable_key};
+select_client_public_key([Group|Groups],
+ #key_share_client_hello{client_shares = ClientShares} = KeyS,
+ PreferredGroup) ->
+ case lists:keysearch(Group, 2, ClientShares) of
+ {value, #key_share_entry{key_exchange = ClientPublicKey}} ->
+ {Group, ClientPublicKey};
+ false ->
+ select_client_public_key(Groups, KeyS, PreferredGroup)
+ end.
+
+send_hello_retry_request(#state{connection_states = ConnectionStates0,
+ static_env = #static_env{protocol_cb = Connection}} = State0,
+ no_suitable_key, KeyShare, SessionId) ->
+ ServerHello0 = tls_handshake_1_3:server_hello(hello_retry_request, SessionId,
+ KeyShare, undefined, ConnectionStates0),
+ {State1, ServerHello} = tls_handshake_1_3:maybe_add_cookie_extension(State0, ServerHello0),
+
+ State2 = Connection:queue_handshake(ServerHello, State1),
+ %% D.4. Middlebox Compatibility Mode
+ State3 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State2, last),
+ {State4, _} = Connection:send_handshake_flight(State3),
+
+ %% Update handshake history
+ State5 = tls_handshake_1_3:replace_ch1_with_message_hash(State4),
+
+ {ok, {State5, start}};
+send_hello_retry_request(State0, _, _, _) ->
+ %% Suitable key found.
+ {ok, {State0, negotiated}}.
+
+update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataExpected) ->
+ Read0 = ssl_record:current_connection_state(CS, read),
+ Read = Read0#{trial_decryption => TrialDecryption,
+ early_data_accepted => EarlyDataExpected},
+ State#state{connection_states = CS#{current_read => Read}}.
+
+handle_early_data(State, enabled, #early_data_indication{}) ->
+ %% Accept early data
+ HsEnv = (State#state.handshake_env)#handshake_env{early_data_accepted = true},
+ State#state{handshake_env = HsEnv};
+handle_early_data(State, _, _) ->
+ State.
+
+%% Session resumption with early_data
+maybe_send_certificate_request(#state{
+ handshake_env =
+ #handshake_env{
+ early_data_accepted = true}} = State,
+ _, PSK) when PSK =/= undefined ->
+ %% Go wait for End of Early Data
+ {State, wait_eoed};
+%% Do not send CR during session resumption
+maybe_send_certificate_request(State, _, PSK) when PSK =/= undefined ->
+ {State, wait_finished};
+maybe_send_certificate_request(State, #{verify := verify_none}, _) ->
+ {State, wait_finished};
+maybe_send_certificate_request(#state{static_env = #static_env{protocol_cb = Connection,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef}} = State,
+ #{verify := verify_peer,
+ signature_algs := SignAlgs,
+ signature_algs_cert := SignAlgsCert} = Opts, _) ->
+ AddCertAuth = maps:get(certificate_authorities, Opts, true),
+ CertificateRequest = tls_handshake_1_3:certificate_request(SignAlgs, SignAlgsCert, CertDbHandle,
+ CertDbRef, AddCertAuth),
+ {Connection:queue_handshake(CertificateRequest, State), wait_cert}.
+
+maybe_send_certificate(State, PSK) when PSK =/= undefined ->
+ {ok, State};
+maybe_send_certificate(#state{session = #session{own_certificates = OwnCerts},
+ static_env = #static_env{
+ protocol_cb = Connection,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef}} = State, _) ->
+ case tls_handshake_1_3:certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, server) of
+ {ok, Certificate} ->
+ {ok, Connection:queue_handshake(Certificate, State)};
+ Error ->
+ Error
+ end.
+
+maybe_send_certificate_verify(State, PSK) when PSK =/= undefined ->
+ {ok, State};
+maybe_send_certificate_verify(#state{session = #session{sign_alg = SignatureScheme,
+ private_key = CertPrivateKey},
+ static_env = #static_env{protocol_cb = Connection}
+ } = State, _) ->
+ case tls_handshake_1_3:certificate_verify(CertPrivateKey, SignatureScheme, State, server) of
+ {ok, CertificateVerify} ->
+ {ok, Connection:queue_handshake(CertificateVerify, State)};
+ Error ->
+ Error
+ end.
+
+%% RFC 8446 - 4.2.8. Key Share
+%% This vector MAY be empty if the client is requesting a
+%% HelloRetryRequest. Each KeyShareEntry value MUST correspond to a
+%% group offered in the "supported_groups" extension and MUST appear in
+%% the same order. However, the values MAY be a non-contiguous subset
+%% of the "supported_groups" extension and MAY omit the most preferred
+%% groups.
+%%
+%% Clients can offer as many KeyShareEntry values as the number of
+%% supported groups it is offering, each representing a single set of
+%% key exchange parameters.
+%%
+%% Clients MUST NOT offer multiple KeyShareEntry values
+%% for the same group. Clients MUST NOT offer any KeyShareEntry values
+%% for groups not listed in the client's "supported_groups" extension.
+%% Servers MAY check for violations of these rules and abort the
+%% handshake with an "illegal_parameter" alert if one is violated.
+validate_client_key_share(_ ,[]) ->
+ ok;
+validate_client_key_share([], _) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
+validate_client_key_share([Group | ClientGroups], [#key_share_entry{group = Group} | ClientShares]) ->
+ validate_client_key_share(ClientGroups, ClientShares);
+validate_client_key_share([_|ClientGroups], [_|_] = ClientShares) ->
+ validate_client_key_share(ClientGroups, ClientShares).
+
+select_cipher_suite(_, [], _) ->
+ {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher)};
+%% If honor_cipher_order is set to true, use the server's preference for
+%% cipher suite selection.
+select_cipher_suite(true, ClientCiphers, ServerCiphers) ->
+ select_cipher_suite(false, ServerCiphers, ClientCiphers);
+select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) ->
+ case lists:member(Cipher, tls_v1:exclusive_suites(?TLS_1_3)) andalso
+ lists:member(Cipher, ServerCiphers) of
+ true ->
+ {ok, Cipher};
+ false ->
+ select_cipher_suite(false, ClientCiphers, ServerCiphers)
+ end.
+
+%% RFC 7301 - Application-Layer Protocol Negotiation Extension
+%% It is expected that a server will have a list of protocols that it
+%% supports, in preference order, and will only select a protocol if the
+%% client supports it. In that case, the server SHOULD select the most
+%% highly preferred protocol that it supports and that is also
+%% advertised by the client. In the event that the server supports no
+%% protocols that the client advertises, then the server SHALL respond
+%% with a fatal "no_application_protocol" alert.
+handle_alpn(undefined, _) ->
+ {ok, undefined};
+handle_alpn([], _) ->
+ {error, ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL)};
+handle_alpn([_|_], undefined) ->
+ {ok, undefined};
+handle_alpn([ServerProtocol|T], ClientProtocols) ->
+ case lists:member(ServerProtocol, ClientProtocols) of
+ true ->
+ {ok, ServerProtocol};
+ false ->
+ handle_alpn(T, ClientProtocols)
+ end.
diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl
index f6b91404fb..a2e5c327a0 100644
--- a/lib/ssl/src/tls_server_session_ticket.erl
+++ b/lib/ssl/src/tls_server_session_ticket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -31,8 +31,8 @@
-include("ssl_cipher.hrl").
%% API
--export([start_link/6,
- new/3,
+-export([start_link/7,
+ new/4,
use/4
]).
@@ -40,6 +40,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, format_status/2]).
+%% Tracing
+-export([handle_trace/3]).
+
-define(SERVER, ?MODULE).
-record(state, {
@@ -54,15 +57,24 @@
%%%===================================================================
%%% API
%%%===================================================================
--spec start_link(term(), atom(), integer(), integer(), integer(), tuple()) -> {ok, Pid :: pid()} |
+-spec start_link(term(), Mode, integer(), integer(), integer(), tuple(), Seed) ->
+ {ok, Pid :: pid()} |
{error, Error :: {already_started, pid()}} |
{error, Error :: term()} |
- ignore.
-start_link(Listener, Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay) ->
- gen_server:start_link(?MODULE, [Listener, Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay], []).
-
-new(Pid, Prf, MasterSecret) ->
- gen_server:call(Pid, {new_session_ticket, Prf, MasterSecret}, infinity).
+ ignore
+ when Mode :: stateful | stateless | stateful_with_cert | stateless_with_cert,
+ Seed :: undefined | binary().
+start_link(Listener, Mode1, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay, Seed) ->
+ Mode = case Mode1 of
+ stateful_with_cert -> stateful;
+ stateless_with_cert -> stateless;
+ _ -> Mode1
+ end,
+ gen_server:start_link(?MODULE, [Listener, Mode, Lifetime, TicketStoreSize,
+ MaxEarlyDataSize, AntiReplay, Seed], []).
+
+new(Pid, Prf, MasterSecret, PeerCert) ->
+ gen_server:call(Pid, {new_session_ticket, Prf, MasterSecret, PeerCert}, infinity).
use(Pid, Identifiers, Prf, HandshakeHist) ->
gen_server:call(Pid, {use_ticket, Identifiers, Prf, HandshakeHist},
@@ -76,12 +88,12 @@ use(Pid, Identifiers, Prf, HandshakeHist) ->
init([Listener | Args]) ->
process_flag(trap_exit, true),
Monitor = inet:monitor(Listener),
- State = inital_state(Args),
+ State = initial_state(Args),
{ok, State#state{listen_monitor = Monitor}}.
-spec handle_call(Request :: term(), From :: {pid(), term()}, State :: term()) ->
{reply, Reply :: term(), NewState :: term()} .
-handle_call({new_session_ticket, Prf, MasterSecret}, _From,
+handle_call({new_session_ticket, Prf, MasterSecret, PeerCert}, _From,
#state{nonce = Nonce,
lifetime = LifeTime,
max_early_data_size = MaxEarlyDataSize,
@@ -89,14 +101,14 @@ handle_call({new_session_ticket, Prf, MasterSecret}, _From,
Id = stateful_psk_ticket_id(IdGen),
PSK = tls_v1:pre_shared_key(MasterSecret, ticket_nonce(Nonce), Prf),
SessionTicket = new_session_ticket(Id, Nonce, LifeTime, MaxEarlyDataSize),
- State = stateful_ticket_store(Id, SessionTicket, Prf, PSK, State0),
+ State = stateful_ticket_store(Id, SessionTicket, Prf, PSK, PeerCert, State0),
{reply, SessionTicket, State};
-handle_call({new_session_ticket, Prf, MasterSecret}, _From,
+handle_call({new_session_ticket, Prf, MasterSecret, PeerCert}, _From,
#state{nonce = Nonce,
stateless = #{}} = State) ->
BaseSessionTicket = new_session_ticket_base(State),
SessionTicket = generate_stateless_ticket(BaseSessionTicket, Prf,
- MasterSecret, State),
+ MasterSecret, PeerCert, State),
{reply, SessionTicket, State#state{nonce = Nonce+1}};
handle_call({use_ticket, Identifiers, Prf, HandshakeHist}, _From,
#state{stateful = #{}} = State0) ->
@@ -118,10 +130,13 @@ handle_cast(_Request, State) ->
{noreply, NewState :: term()}.
handle_info(rotate_bloom_filters,
#state{stateless = #{bloom_filter := BloomFilter0,
+ warm_up_windows_remaining := WarmUp0,
window := Window} = Stateless} = State) ->
BloomFilter = tls_bloom_filter:rotate(BloomFilter0),
erlang:send_after(Window * 1000, self(), rotate_bloom_filters),
- {noreply, State#state{stateless = Stateless#{bloom_filter => BloomFilter}}};
+ WarmUp = max(WarmUp0 - 1, 0),
+ {noreply, State#state{stateless = Stateless#{bloom_filter => BloomFilter,
+ warm_up_windows_remaining => WarmUp}}};
handle_info({'DOWN', Monitor, _, _, _}, #state{listen_monitor = Monitor} = State) ->
{stop, normal, State};
handle_info(_Info, State) ->
@@ -144,29 +159,28 @@ code_change(_OldVsn, State, _Extra) ->
Status :: list()) -> Status :: term().
format_status(_Opt, Status) ->
Status.
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
-
-inital_state([stateless, Lifetime, _, MaxEarlyDataSize, undefined]) ->
+initial_state([stateless, Lifetime, _, MaxEarlyDataSize, undefined, Seed]) ->
#state{nonce = 0,
- stateless = #{seed => {crypto:strong_rand_bytes(16),
- crypto:strong_rand_bytes(32)},
+ stateless = #{seed => stateless_seed(Seed),
window => undefined},
lifetime = Lifetime,
max_early_data_size = MaxEarlyDataSize
};
-inital_state([stateless, Lifetime, _, MaxEarlyDataSize, {Window, K, M}]) ->
+initial_state([stateless, Lifetime, _, MaxEarlyDataSize, {Window, K, M}, Seed]) ->
erlang:send_after(Window * 1000, self(), rotate_bloom_filters),
#state{nonce = 0,
stateless = #{bloom_filter => tls_bloom_filter:new(K, M),
- seed => {crypto:strong_rand_bytes(16),
- crypto:strong_rand_bytes(32)},
+ warm_up_windows_remaining => warm_up_windows(Seed),
+ seed => stateless_seed(Seed),
window => Window},
lifetime = Lifetime,
max_early_data_size = MaxEarlyDataSize
};
-inital_state([stateful, Lifetime, TicketStoreSize, MaxEarlyDataSize|_]) ->
+initial_state([stateful, Lifetime, TicketStoreSize, MaxEarlyDataSize|_]) ->
%% statfeful servers replay
%% protection is that it saves
%% all valid tickets
@@ -229,18 +243,18 @@ validate_binder(Binder, HandshakeHist, PSK, Prf, AlertDetail) ->
stateful_store() ->
gb_trees:empty().
-stateful_ticket_store(Ref, NewSessionTicket, Hash, Psk,
+stateful_ticket_store(Ref, NewSessionTicket, Hash, Psk, PeerCert,
#state{nonce = Nonce,
stateful = #{db := Tree0,
max := Max,
ref_index := Index0} = Stateful}
= State0) ->
Id = {erlang:monotonic_time(), erlang:unique_integer([monotonic])},
- StatefulTicket = {NewSessionTicket, Hash, Psk},
+ StatefulTicket = {NewSessionTicket, Hash, Psk, PeerCert},
case gb_trees:size(Tree0) of
Max ->
%% Trow away oldest ticket
- {_, {#new_session_ticket{ticket = OldRef},_,_}, Tree1}
+ {_, {#new_session_ticket{ticket = OldRef},_,_,_}, Tree1}
= gb_trees:take_smallest(Tree0),
Tree = gb_trees:insert(Id, StatefulTicket, Tree1),
Index = maps:without([OldRef], Index0),
@@ -272,8 +286,8 @@ stateful_use([#psk_identity{identity = Ref} | Refs], [Binder | Binders],
HandshakeHist, Tree0) of
true ->
RefIndex = maps:without([Ref], RefIndex0),
- {{_,_, PSK}, Tree} = gb_trees:take(Key, Tree0),
- {{ok, {Index, PSK}},
+ {{_,_, PSK, PeerCert}, Tree} = gb_trees:take(Key, Tree0),
+ {{ok, {Index, PSK, PeerCert}},
State#state{stateful = Stateful#{db => Tree,
ref_index => RefIndex}}};
false ->
@@ -291,7 +305,7 @@ stateful_usable_ticket(Key, Prf, Binder, HandshakeHist, Tree) ->
case gb_trees:lookup(Key, Tree) of
none ->
false;
- {value, {NewSessionTicket, Prf, PSK}} ->
+ {value, {NewSessionTicket, Prf, PSK, _PeerCert}} ->
case stateful_living_ticket(Key, NewSessionTicket) of
true ->
validate_binder(Binder, HandshakeHist, PSK, Prf, stateful);
@@ -323,7 +337,7 @@ stateful_psk_ticket_id(Key) ->
generate_stateless_ticket(#new_session_ticket{ticket_nonce = Nonce,
ticket_age_add = TicketAgeAdd,
ticket_lifetime = Lifetime}
- = Ticket, Prf, MasterSecret,
+ = Ticket, Prf, MasterSecret, PeerCert,
#state{stateless = #{seed := {IV, Shard}}}) ->
PSK = tls_v1:pre_shared_key(MasterSecret, Nonce, Prf),
Timestamp = erlang:system_time(second),
@@ -332,7 +346,8 @@ generate_stateless_ticket(#new_session_ticket{ticket_nonce = Nonce,
pre_shared_key = PSK,
ticket_age_add = TicketAgeAdd,
lifetime = Lifetime,
- timestamp = Timestamp
+ timestamp = Timestamp,
+ certificate = PeerCert
}, Shard, IV),
Ticket#new_session_ticket{ticket = Encrypted}.
@@ -351,11 +366,12 @@ stateless_use([#psk_identity{identity = Encrypted,
window := Window}} = State) ->
case ssl_cipher:decrypt_ticket(Encrypted, Shard, IV) of
#stateless_ticket{hash = Prf,
- pre_shared_key = PSK} = Ticket ->
+ pre_shared_key = PSK,
+ certificate = PeerCert} = Ticket ->
case stateless_usable_ticket(Ticket, ObfAge, Binder,
HandshakeHist, Window) of
true ->
- stateless_anti_replay(Index, PSK, Binder, State);
+ stateless_anti_replay(Index, PSK, Binder, PeerCert, State);
false ->
stateless_use(Ids, Binders, Prf, HandshakeHist,
Index+1, State);
@@ -382,19 +398,62 @@ stateless_usable_ticket(#stateless_ticket{hash = Prf,
stateless_living_ticket(0, _, _, _, _) ->
true;
+%% If `anti_replay` is not enabled, then a ticket is considered to be living
+%% if it has not exceeded its lifetime.
+%%
+%% If `anti_replay` is enabled, we must additionally perform a freshness check
+%% as is outlined in section 8.3 Freshness Checks - RFC 8446
stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime, Timestamp, Window) ->
- ReportedAge = ObfAge - TicketAgeAdd,
+ %% RealAge is the server's view of the age of the ticket in seconds.
RealAge = erlang:system_time(second) - Timestamp,
+
+ %% ReportedAge is the client's view of the age of the ticket in milliseconds.
+ ReportedAge = ObfAge - TicketAgeAdd,
+
+ %% DeltaAge is the difference of the client's view of the age of the ticket
+ %% and the server's view of the age of the ticket in seconds.
+ DeltaAge = abs(RealAge - (ReportedAge / 1000)),
+
+ %% We ensure that both the client's view of the age of the ticket and the
+ %% server's view of the age of the ticket do not exceed the lifetime specified.
(ReportedAge =< Lifetime * 1000)
andalso (RealAge =< Lifetime)
- andalso (in_window(RealAge, Window)).
+ andalso (in_window(DeltaAge, Window)).
in_window(_, undefined) ->
true;
+%% RFC 8446 - section 8.2 Client Hello Recording
+%% describes an anti-replay implementation that can use bounded memory
+%% by storing a unique value from a ClientHello (in our case the PSK binder)
+%% withing a given time window.
+%%
+%% In order implement this, when a ClientHello is received, the server
+%% must ensure that a ClientHello has been sent relatively recently.
+%% We do this by ensuring that the client and server view of the age
+%% of the ticket is not larger than our recording window.
+%%
+%% In the case of an attempted replay attack, there are 2 possible
+%% outcomes:
+%% - A ClientHello is replayed within the recording window
+%% * The ticket looks valid, `in_window` returns true
+%% so we proceed to check the unique value
+%% * The unique value (PSK Binder) is stored in the bloom filter
+%% and we reject the ticket.
+%%
+%% - A ClientHello is replayed outside the recording window
+%% * We reject the ticket as `in_window` returns false.
in_window(Age, Window) when is_integer(Window) ->
Age =< Window.
-stateless_anti_replay(Index, PSK, Binder,
+stateless_anti_replay(_Index, _PSK, _Binder, _PeerCert,
+ #state{stateless = #{warm_up_windows_remaining := WarmUpRemaining}
+ } = State) when WarmUpRemaining > 0 ->
+ %% Reject all tickets during the warm-up period:
+ %% RFC 8446 8.2 Client Hello Recording
+ %% "When implementations are freshly started, they SHOULD reject 0-RTT as
+ %% long as any portion of their recording window overlaps the startup time."
+ {{ok, undefined}, State};
+stateless_anti_replay(Index, PSK, Binder, PeerCert,
#state{stateless = #{bloom_filter := BloomFilter0}
= Stateless} = State) ->
case tls_bloom_filter:contains(BloomFilter0, Binder) of
@@ -403,8 +462,110 @@ stateless_anti_replay(Index, PSK, Binder,
{{ok, undefined}, State};
false ->
BloomFilter = tls_bloom_filter:add_elem(BloomFilter0, Binder),
- {{ok, {Index, PSK}},
+ {{ok, {Index, PSK, PeerCert}},
State#state{stateless = Stateless#{bloom_filter => BloomFilter}}}
end;
-stateless_anti_replay(Index, PSK, _, State) ->
- {{ok, {Index, PSK}}, State}.
+stateless_anti_replay(Index, PSK, _Binder, PeerCert, State) ->
+ {{ok, {Index, PSK, PeerCert}}, State}.
+
+-spec stateless_seed(Seed :: undefined | binary()) ->
+ {IV :: binary(), Shard :: binary()}.
+stateless_seed(undefined) ->
+ {crypto:strong_rand_bytes(16), crypto:strong_rand_bytes(32)};
+stateless_seed(Seed) ->
+ <<IV:16/binary, Shard:32/binary, _/binary>> = crypto:hash(sha512, Seed),
+ {IV, Shard}.
+
+-spec warm_up_windows(Seed :: undefined | binary()) -> 0 | 2.
+warm_up_windows(undefined) ->
+ 0;
+warm_up_windows(_) ->
+ %% When the encryption seed is specified, "warm up" the bloom filter for
+ %% 2*WindowSize to ensure tickets from a previous instance of the server
+ %% (before a restart) cannot be reused, if the ticket encryption seed is reused.
+ 2.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(rle, {call, {?MODULE, init, [[ListenSocket, Mode, Lifetime,
+ StoreSize | _T]]}}, Stack) ->
+ {io_lib:format("(*server) ([ListenSocket = ~w Mode = ~w Lifetime = ~w "
+ "StoreSize = ~w, ...])",
+ [ListenSocket, Mode, Lifetime, StoreSize]),
+ [{role, server} | Stack]};
+handle_trace(ssn,
+ {call, {?MODULE, terminate, [Reason, _State]}}, Stack) ->
+ {io_lib:format("(Reason ~w)", [Reason]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, handle_call,
+ [CallTuple, _From, _State]}}, Stack) ->
+ {io_lib:format("(Call = ~w)", [element(1, CallTuple)]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE,handle_call,
+ [{Call = use_ticket,
+ {offered_psks,
+ [{psk_identity, PskIdentity, _ObfAge}],
+ [Binder]},
+ _Prf,
+ _HandshakeHist}, _From, _State]}}, Stack) ->
+ {io_lib:format("(Call = ~w PskIdentity = ~W Binder = ~W)",
+ [Call, PskIdentity, 5, Binder, 5]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, validate_binder,
+ [Binder, _HandshakeHist, PSK, _Prf, _AlertDetail]}}, Stack) ->
+ {io_lib:format("(Binder = ~W PSK = ~W)", [Binder, 5, PSK, 5]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, initial_state,
+ [[Mode, _Lifetime, _StoreSize,_MaxEarlyDataSize,
+ Window, Seed]]}}, Stack) ->
+ {io_lib:format("(Mode = ~w Window = ~w Seed = ~W)", [Mode, Window, Seed, 5]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, generate_stateless_ticket,
+ [_BaseTicket, _Prf, MasterSecret, _State]}}, Stack) ->
+ {io_lib:format("(MasterSecret = ~W)", [MasterSecret, 5]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, stateless_use,
+ [[{psk_identity, Encrypted, _ObfAge} | _],
+ [Binder | _],
+ _Prf, _HandshakeHist, _Index, _State]}}, Stack) ->
+ {io_lib:format("(Encrypted = ~W Binder = ~W)", [Encrypted, 5, Binder, 5]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, in_window, [RealAge, Window]}}, Stack) ->
+ {io_lib:format("(RealAge = ~w Window = ~w)",
+ [RealAge, Window]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, stateless_usable_ticket,
+ [{stateless_ticket, _Prf, _PreSharedKey, _TicketAgeAdd,
+ _Lifetime, _Timestamp}, _ObfAge, Binder,
+ _HandshakeHist, Window]}}, Stack) ->
+ {io_lib:format("(Binder = ~W Window = ~w)", [Binder, 5, Window]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, stateless_anti_replay,
+ [_Index, PSK, _Binder, _State]}}, Stack) ->
+ {io_lib:format("(PSK = ~W)", [PSK, 5]), Stack};
+handle_trace(ssn,
+ {return_from, {?MODULE, stateless_use, 6},
+ {{ok, {_Index, PSK}}, _State}}, Stack) ->
+ {io_lib:format("PSK = ~W", [PSK, 5]), Stack};
+handle_trace(ssn,
+ {return_from, {?MODULE, generate_stateless_ticket, 4},
+ {new_session_ticket, _LifeTime, _AgeAdd, _Nonce, Ticket,
+ _Extensions}}, Stack) ->
+ {io_lib:format("Ticket = ~W", [Ticket, 5]), Stack};
+handle_trace(ssn,
+ {return_from, {?MODULE, initial_state, 1},
+ {state, _Stateless = #{seed := {IV, Shard}, window := Window},
+ _Stateful = undefined, _Nonce, _Lifetime ,
+ _MaxEarlyDataSize, ListenMonitor}}, Stack) ->
+ {io_lib:format("IV = ~W Shard = ~W Window = ~w ListenMonitor = ~w",
+ [IV, 5, Shard, 5, Window, ListenMonitor]), Stack};
+handle_trace(ssn,
+ {return_from, {?MODULE, stateless_anti_replay, 4},
+ {{ok, {_Index, PSK}}, _State}}, Stack) ->
+ {io_lib:format("ticket OK ~W", [PSK, 5]), Stack};
+handle_trace(ssn,
+ {return_from, {?MODULE, stateless_anti_replay, 4},
+ Return}, Stack) ->
+ {io_lib:format("ticket REJECTED ~W", [Return, 5]), Stack}.
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index 779043f1de..fd919f7356 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
-include("ssl_internal.hrl").
-include("ssl_api.hrl").
+-include("ssl_record.hrl").
-export([send/3,
listen/3,
@@ -250,46 +251,49 @@ internal_inet_values() ->
default_inet_values() ->
[{packet_size, 0}, {packet,0}, {header, 0}, {active, true}, {mode, list}].
-inherit_tracker(ListenSocket, EmOpts, #{erl_dist := false} = SslOpts) ->
- ssl_listen_tracker_sup:start_child([ListenSocket, EmOpts, SslOpts]);
inherit_tracker(ListenSocket, EmOpts, #{erl_dist := true} = SslOpts) ->
- ssl_listen_tracker_sup:start_child_dist([ListenSocket, EmOpts, SslOpts]).
+ ssl_listen_tracker_sup:start_child_dist([ListenSocket, EmOpts, SslOpts]);
+inherit_tracker(ListenSocket, EmOpts, SslOpts) ->
+ ssl_listen_tracker_sup:start_child([ListenSocket, EmOpts, SslOpts]).
-session_tickets_tracker(_,_, _, _, #{erl_dist := false,
- session_tickets := disabled}) ->
- {ok, disabled};
-session_tickets_tracker(ListenSocket, Lifetime, TicketStoreSize, MaxEarlyDataSize,
- #{erl_dist := false,
- session_tickets := Mode,
- anti_replay := AntiReplay}) ->
- tls_server_session_ticket_sup:start_child([ListenSocket, Mode, Lifetime,
- TicketStoreSize, MaxEarlyDataSize, AntiReplay]);
session_tickets_tracker(ListenSocket, Lifetime, TicketStoreSize, MaxEarlyDataSize,
#{erl_dist := true,
session_tickets := Mode,
- anti_replay := AntiReplay}) ->
+ anti_replay := AntiReplay,
+ stateless_tickets_seed := Seed}) ->
SupName = tls_server_session_ticket_sup:sup_name(dist),
Children = supervisor:count_children(SupName),
Workers = proplists:get_value(workers, Children),
case Workers of
0 ->
tls_server_session_ticket_sup:start_child([ListenSocket, Mode, Lifetime,
- TicketStoreSize, MaxEarlyDataSize, AntiReplay]);
+ TicketStoreSize, MaxEarlyDataSize,
+ AntiReplay, Seed]);
1 ->
[{_,Child,_, _}] = supervisor:which_children(SupName),
{ok, Child}
- end.
-session_id_tracker(_, #{versions := [{3,4}]}) ->
+ end;
+session_tickets_tracker(_,_, _, _, #{session_tickets := disabled}) ->
+ {ok, disabled};
+session_tickets_tracker(ListenSocket, Lifetime, TicketStoreSize, MaxEarlyDataSize,
+ #{session_tickets := Mode,
+ anti_replay := AntiReplay,
+ stateless_tickets_seed := Seed}) ->
+ tls_server_session_ticket_sup:start_child([ListenSocket, Mode, Lifetime,
+ TicketStoreSize, MaxEarlyDataSize,
+ AntiReplay, Seed]).
+
+session_id_tracker(_, #{versions := [?TLS_1_3]}) ->
{ok, not_relevant};
%% Regardless of the option reuse_sessions we need the session_id_tracker
%% to generate session ids, but no sessions will be stored unless
%% reuse_sessions = true.
-session_id_tracker(ssl_unknown_listener, #{erl_dist := false}) ->
- ssl_upgrade_server_session_cache_sup:start_child(normal);
-session_id_tracker(ListenSocket, #{erl_dist := false}) ->
- ssl_server_session_cache_sup:start_child(ListenSocket);
session_id_tracker(_, #{erl_dist := true}) ->
- ssl_upgrade_server_session_cache_sup:start_child(dist).
+ ssl_upgrade_server_session_cache_sup:start_child(dist);
+session_id_tracker(ssl_unknown_listener, _) ->
+ ssl_upgrade_server_session_cache_sup:start_child(normal);
+session_id_tracker(ListenSocket, _) ->
+ ssl_server_session_cache_sup:start_child(ListenSocket).
get_emulated_opts(TrackerPid) ->
call(TrackerPid, get_emulated_opts).
@@ -391,9 +395,10 @@ code_change(_OldVsn, State, _Extra) ->
call(Pid, Msg) ->
gen_server:call(Pid, Msg, infinity).
-start_tls_server_connection(#{sender_spawn_opts := SenderOpts} = SslOpts, ConnectionCb, Transport, Port, Socket, EmOpts, Trackers, CbInfo) ->
+start_tls_server_connection(SslOpts, ConnectionCb, Transport, Port, Socket, EmOpts, Trackers, CbInfo) ->
try
{ok, DynSup} = tls_connection_sup:start_child([]),
+ SenderOpts = maps:get(sender_spawn_opts, SslOpts, []),
{ok, Sender} = tls_dyn_connection_sup:start_child(DynSup, sender, [[{spawn_opt, SenderOpts}]]),
ConnArgs = [server, Sender, "localhost", Port, Socket,
{SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Trackers}, self(), CbInfo],
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index dd891967bc..8eda30506a 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -29,13 +29,13 @@
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
--export([master_secret/4,
- finished/5,
- certificate_verify/3,
- mac_hash/7,
+-export([master_secret/4,
+ finished/5,
+ certificate_verify/2,
+ mac_hash/7,
hmac_hash/3,
- setup_keys/8,
- suites/1,
+ setup_keys/8,
+ suites/1,
exclusive_suites/1,
exclusive_anonymous_suites/1,
psk_suites/1,
@@ -44,52 +44,55 @@
srp_suites/1,
srp_suites_anon/1,
srp_exclusive/1,
- rc4_suites/1,
+ rc4_suites/1,
rc4_exclusive/1,
des_suites/1,
des_exclusive/1,
rsa_suites/1,
rsa_exclusive/1,
prf/5,
- ecc_curves/1,
- ecc_curves/2,
- oid_to_enum/1,
- enum_to_oid/1,
- default_signature_algs/1,
+ ecc_curves/1,
+ oid_to_enum/1,
+ enum_to_oid/1,
+ default_signature_algs/1,
+ legacy_signature_algs_pre_13/0,
signature_algs/2,
signature_schemes/2,
rsa_schemes/0,
- groups/1,
- groups/2,
- group_to_enum/1,
- enum_to_group/1,
- default_groups/1]).
-
--export([derive_secret/4,
- hkdf_expand_label/5,
- hkdf_extract/3,
+ groups/0,
+ groups/1,
+ group_to_enum/1,
+ enum_to_group/1,
+ default_groups/0]).
+
+-export([derive_secret/4,
+ hkdf_expand_label/5,
+ hkdf_extract/3,
hkdf_expand/4,
key_length/1,
- key_schedule/3,
- key_schedule/4,
+ key_schedule/3,
+ key_schedule/4,
create_info/3,
- external_binder_key/2,
+ external_binder_key/2,
resumption_binder_key/2,
- client_early_traffic_secret/3,
+ client_early_traffic_secret/3,
early_exporter_master_secret/3,
- client_handshake_traffic_secret/3,
+ client_handshake_traffic_secret/3,
server_handshake_traffic_secret/3,
- client_application_traffic_secret_0/3,
+ client_application_traffic_secret_0/3,
server_application_traffic_secret_0/3,
- exporter_master_secret/3,
+ exporter_master_secret/3,
resumption_master_secret/3,
- update_traffic_secret/2,
+ update_traffic_secret/2,
calculate_traffic_keys/3,
- transcript_hash/2,
- finished_key/2,
- finished_verify_data/3,
+ transcript_hash/2,
+ finished_key/2,
+ finished_verify_data/3,
pre_shared_key/3]).
+%% Tracing
+-export([handle_trace/3]).
+
-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 |
sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 |
sect283k1 | sect283r1 | brainpoolP256r1 | secp256k1 | secp256r1 |
@@ -115,7 +118,7 @@ derive_secret(Secret, Label, Messages, Algo) ->
Hash, ssl_cipher:hash_size(Algo), Algo).
-spec hkdf_expand_label(Secret::binary(), Label0::binary(),
- Context::binary(), Length::integer(),
+ Context::binary(), Length::integer(),
Algo::ssl:hash()) -> KeyingMaterial::binary().
hkdf_expand_label(Secret, Label0, Context, Length, Algo) ->
HkdfLabel = create_info(Label0, Context, Length),
@@ -140,14 +143,14 @@ create_info(Label0, Context0, Length) ->
-spec hkdf_extract(MacAlg::ssl:hash(), Salt::binary(),
KeyingMaterial::binary()) -> PseudoRandKey::binary().
-hkdf_extract(MacAlg, Salt, KeyingMaterial) ->
+hkdf_extract(MacAlg, Salt, KeyingMaterial) ->
hmac_hash(MacAlg, Salt, KeyingMaterial).
-spec hkdf_expand(PseudoRandKey::binary(), ContextInfo::binary(),
Length::integer(), Algo::ssl:hash()) -> KeyingMaterial::binary().
-
-hkdf_expand(PseudoRandKey, ContextInfo, Length, Algo) ->
+
+hkdf_expand(PseudoRandKey, ContextInfo, Length, Algo) ->
Iterations = erlang:ceil(Length / ssl_cipher:hash_size(Algo)),
hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, 1, Iterations, <<>>, <<>>).
@@ -170,10 +173,15 @@ master_secret(PrfAlgo, PreMasterSecret, ClientRandom, ServerRandom) ->
[ClientRandom, ServerRandom], 48).
%% TLS 1.0 -1.2 ---------------------------------------------------
--spec finished(client | server, integer(), integer(), binary(), [binary()]) -> binary().
+-spec finished(Role, Version, PrfAlgo, MasterSecret, Handshake) -> binary() when
+ Role :: client | server,
+ Version :: ssl_record:ssl_version(),
+ PrfAlgo :: integer(),
+ MasterSecret :: binary(),
+ Handshake :: [binary()].
%% TLS 1.0 -1.1 ---------------------------------------------------
finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
- when Version == 1; Version == 2; PrfAlgo == ?MD5SHA ->
+ when Version == ?TLS_1_0; Version == ?TLS_1_1; PrfAlgo == ?MD5SHA ->
%% RFC 2246 & 4346 - 7.4.9. Finished
%% struct {
%% opaque verify_data[12];
@@ -188,8 +196,7 @@ finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
%% TLS 1.0 -1.1 ---------------------------------------------------
%% TLS 1.2 ---------------------------------------------------
-finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
- when Version == 3 ->
+finished(Role, ?TLS_1_2, PrfAlgo, MasterSecret, Handshake) ->
%% RFC 5246 - 7.4.9. Finished
%% struct {
%% opaque verify_data[12];
@@ -203,30 +210,29 @@ finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
%% TODO 1.3 finished
--spec certificate_verify(md5sha | sha, integer(), [binary()]) -> binary().
-
+-spec certificate_verify(HashAlgo, Handshake) -> binary() when
+ HashAlgo :: md5sha | ssl:hash(),
+ Handshake :: [binary()].
%% TLS 1.0 -1.1 ---------------------------------------------------
-certificate_verify(md5sha, _Version, Handshake) ->
+certificate_verify(md5sha, Handshake) ->
MD5 = crypto:hash(md5, Handshake),
SHA = crypto:hash(sha, Handshake),
{digest, <<MD5/binary, SHA/binary>>};
%% TLS 1.0 -1.1 ---------------------------------------------------
%% TLS 1.2 ---------------------------------------------------
-certificate_verify(_HashAlgo, _Version, Handshake) ->
+certificate_verify(_HashAlgo, Handshake) ->
%% crypto:hash(HashAlgo, Handshake).
%% Optimization: Let crypto calculate the hash in sign/verify call
Handshake.
%% TLS 1.2 ---------------------------------------------------
-
--spec setup_keys(integer(), integer(), binary(), binary(), binary(), integer(),
+-spec setup_keys(ssl_record:ssl_version(), integer(), binary(), binary(), binary(), integer(),
integer(), integer()) -> {binary(), binary(), binary(),
binary(), binary(), binary()}.
%% TLS v1.0 ---------------------------------------------------
-setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
- KeyMatLen, IVSize)
- when Version == 1 ->
+setup_keys(?TLS_1_0, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
+ KeyMatLen, IVSize) ->
%% RFC 2246 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
@@ -251,9 +257,8 @@ setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize
%% TLS v1.0 ---------------------------------------------------
%% TLS v1.1 ---------------------------------------------------
-setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
- KeyMatLen, IVSize)
- when Version == 2 ->
+setup_keys(?TLS_1_1, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
+ KeyMatLen, IVSize) ->
%% RFC 4346 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
@@ -279,9 +284,8 @@ setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize
%% TLS v1.1 ---------------------------------------------------
%% TLS v1.2 ---------------------------------------------------
-setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
- KeyMatLen, IVSize)
- when Version == 3; Version == 4 ->
+setup_keys(?TLS_1_2, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
+ KeyMatLen, IVSize) ->
%% RFC 5246 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
@@ -492,12 +496,12 @@ key_length(CipherSuite) ->
-spec mac_hash(integer() | atom(), binary(), integer(), integer(), tls_record:tls_version(),
integer(), binary()) -> binary().
-mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
- Length, Fragment) ->
+mac_hash(Method, Mac_write_secret, Seq_num, Type, Version,Length, Fragment) ->
%% RFC 2246 & 4346 - 6.2.3.1.
%% HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type +
%% TLSCompressed.version + TLSCompressed.length +
%% TLSCompressed.fragment));
+ {Major,Minor} = Version,
Mac = hmac_hash(Method, Mac_write_secret,
[<<?UINT64(Seq_num), ?BYTE(Type),
?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>,
@@ -505,19 +509,19 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
Mac.
%% TLS 1.0 -1.2 ---------------------------------------------------
-%% TODO 1.3 same as above?
+-spec suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
--spec suites(1|2|3|4) -> [ssl_cipher_format:cipher_suite()].
+suites(Version) when ?TLS_1_X(Version) ->
+ lists:flatmap(fun exclusive_suites/1, suites_to_test(Version)).
-suites(Minor) when Minor == 1; Minor == 2 ->
- exclusive_suites(1);
-suites(3) ->
- exclusive_suites(3) ++ suites(2);
+suites_to_test(?TLS_1_0) -> [?TLS_1_0];
+suites_to_test(?TLS_1_1) -> [?TLS_1_0];
+suites_to_test(?TLS_1_2) -> [?TLS_1_2, ?TLS_1_0];
+suites_to_test(?TLS_1_3) -> [?TLS_1_3, ?TLS_1_2, ?TLS_1_0].
-suites(4) ->
- exclusive_suites(4) ++ suites(3).
+-spec exclusive_suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
-exclusive_suites(4) ->
+exclusive_suites(?TLS_1_3) ->
[?TLS_AES_256_GCM_SHA384,
?TLS_AES_128_GCM_SHA256,
@@ -526,7 +530,7 @@ exclusive_suites(4) ->
?TLS_AES_128_CCM_SHA256,
?TLS_AES_128_CCM_8_SHA256
];
-exclusive_suites(3) ->
+exclusive_suites(?TLS_1_2) ->
[?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
@@ -565,12 +569,12 @@ exclusive_suites(3) ->
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
-
+
?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
-
+
?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
-
+
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
@@ -580,13 +584,13 @@ exclusive_suites(3) ->
%% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
%% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256
];
-exclusive_suites(2) ->
+exclusive_suites(?TLS_1_1) ->
[];
-exclusive_suites(1) ->
+exclusive_suites(?TLS_1_0) ->
[
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
-
+
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
@@ -601,17 +605,18 @@ exclusive_suites(1) ->
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA
].
-
+
%%--------------------------------------------------------------------
--spec exclusive_anonymous_suites(Minor:: integer()) -> [ssl_cipher_format:cipher_suite()].
+-spec exclusive_anonymous_suites(ssl_record:ssl_version()) ->
+ [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the anonymous cipher suites introduced
%% in Version, only supported if explicitly set by user.
%%--------------------------------------------------------------------
-exclusive_anonymous_suites(4) ->
+exclusive_anonymous_suites(?TLS_1_3) ->
[];
-exclusive_anonymous_suites(3 = N) ->
- psk_anon_exclusive(N) ++
+exclusive_anonymous_suites(?TLS_1_2=Version) ->
+ psk_anon_exclusive(Version) ++
[?TLS_DH_anon_WITH_AES_128_GCM_SHA256,
?TLS_DH_anon_WITH_AES_256_GCM_SHA384,
?TLS_DH_anon_WITH_AES_128_CBC_SHA256,
@@ -622,35 +627,34 @@ exclusive_anonymous_suites(3 = N) ->
?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
?TLS_DH_anon_WITH_RC4_128_MD5];
-exclusive_anonymous_suites(2 = N) ->
- psk_anon_exclusive(N) ++
+exclusive_anonymous_suites(?TLS_1_1=Version) ->
+ psk_anon_exclusive(Version) ++
[?TLS_ECDH_anon_WITH_AES_128_CBC_SHA,
?TLS_ECDH_anon_WITH_AES_256_CBC_SHA,
?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
-
+
?TLS_DH_anon_WITH_DES_CBC_SHA,
?TLS_DH_anon_WITH_RC4_128_MD5];
-exclusive_anonymous_suites(N = 1) ->
- psk_anon_exclusive(N) ++
+exclusive_anonymous_suites(?TLS_1_0=Version) ->
+ psk_anon_exclusive(Version) ++
[?TLS_DH_anon_WITH_RC4_128_MD5,
?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
?TLS_DH_anon_WITH_DES_CBC_SHA
- ] ++ srp_suites_anon({3,1}).
+ ] ++ srp_suites_anon(Version).
%%--------------------------------------------------------------------
--spec psk_suites(ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()].
+-spec psk_suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the PSK cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-psk_suites({3, 3}) ->
- psk_exclusive(3);
-psk_suites({3, N}) ->
- psk_exclusive(N).
-
-psk_exclusive(3) ->
- psk_exclusive(1) -- [?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA];
-psk_exclusive(1) ->
+psk_suites(Version) when ?TLS_1_X(Version) ->
+ psk_exclusive(Version).
+
+-spec psk_exclusive(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
+psk_exclusive(?TLS_1_2) ->
+ psk_exclusive(?TLS_1_0) -- [?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA];
+psk_exclusive(?TLS_1_0) ->
[
?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
@@ -664,15 +668,17 @@ psk_exclusive(_) ->
[].
%%--------------------------------------------------------------------
--spec psk_suites_anon(ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()].
+-spec psk_suites_anon(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the anonymous PSK cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-psk_suites_anon({3, _}) ->
- psk_anon_exclusive(3) ++ psk_anon_exclusive(1).
+psk_suites_anon(Version) when ?TLS_1_X(Version) ->
+ psk_anon_exclusive(?TLS_1_2) ++ psk_anon_exclusive(?TLS_1_0).
-psk_anon_exclusive(3) ->
+-spec psk_anon_exclusive(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
+
+psk_anon_exclusive(?TLS_1_2) ->
[
?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384,
?TLS_PSK_WITH_AES_256_GCM_SHA384,
@@ -692,7 +698,7 @@ psk_anon_exclusive(3) ->
?TLS_PSK_WITH_AES_128_CCM,
?TLS_PSK_WITH_AES_128_CCM_8
];
-psk_anon_exclusive(1) ->
+psk_anon_exclusive(?TLS_1_0) ->
[
?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384,
?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384,
@@ -720,15 +726,19 @@ psk_anon_exclusive(_) ->
%% Description: Returns a list of the SRP cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-srp_suites({3, 3}) ->
- srp_exclusive(1) -- [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA
- ];
-srp_suites({3, N}) when N == 1;
- N == 2 ->
- srp_exclusive(1).
-
-srp_exclusive(1) ->
+srp_suites(?TLS_1_2) ->
+ srp_exclusive(?TLS_1_0) -- [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA
+ ];
+srp_suites(?TLS_1_1) ->
+ srp_exclusive(?TLS_1_0);
+srp_suites(?TLS_1_0) ->
+ srp_exclusive(?TLS_1_0).
+
+
+-spec srp_exclusive(tls_record:tls_version()) -> [ssl_cipher_format:cipher_suite()].
+
+srp_exclusive(?TLS_1_0) ->
[?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,
?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,
?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
@@ -745,30 +755,36 @@ srp_exclusive(_) ->
%% Description: Returns a list of the SRP anonymous cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-srp_suites_anon({3, 3}) ->
- srp_exclusive_anon(1) -- [?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA];
-srp_suites_anon({3, N}) when N == 1;
- N == 2 ->
- srp_exclusive_anon(1).
-srp_exclusive_anon(1) ->
+srp_suites_anon(?TLS_1_2) ->
+ srp_exclusive_anon(?TLS_1_0) -- [?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA];
+srp_suites_anon(?TLS_1_1) ->
+ srp_exclusive_anon(?TLS_1_0);
+srp_suites_anon(?TLS_1_0) ->
+ srp_exclusive_anon(?TLS_1_0).
+
+
+srp_exclusive_anon(?TLS_1_0) ->
[?TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
?TLS_SRP_SHA_WITH_AES_256_CBC_SHA,
?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA
].
%%--------------------------------------------------------------------
--spec rc4_suites(Version::ssl_record:ssl_version() | integer()) ->
- [ssl_cipher_format:cipher_suite()].
+-spec rc4_suites(Version::ssl_record:ssl_version()) ->
+ [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the RSA|(ECDH/RSA)| (ECDH/ECDSA)
%% with RC4 cipher suites, only supported if explicitly set by user.
%% Are not considered secure any more. Other RC4 suites already
%% belonged to the user configured only category.
%%--------------------------------------------------------------------
-rc4_suites({3, _}) ->
- rc4_exclusive(1).
+rc4_suites(Version) when ?TLS_1_X(Version) ->
+ rc4_exclusive(?TLS_1_0).
-rc4_exclusive(1) ->
+-spec rc4_exclusive(Version::ssl_record:ssl_version()) ->
+ [ssl_cipher_format:cipher_suite()].
+
+rc4_exclusive(?TLS_1_0) ->
[?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
?TLS_ECDHE_RSA_WITH_RC4_128_SHA,
?TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
@@ -785,10 +801,10 @@ rc4_exclusive(_) ->
%% with DES cipher, only supported if explicitly set by user.
%% Are not considered secure any more.
%%--------------------------------------------------------------------
-des_suites({3, _}) ->
- des_exclusive(1).
+des_suites(Version) when ?TLS_1_X(Version) ->
+ des_exclusive(?TLS_1_0).
-des_exclusive(1)->
+des_exclusive(?TLS_1_0)->
[?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
@@ -800,27 +816,28 @@ des_exclusive(1)->
des_exclusive(_) ->
[].
%%--------------------------------------------------------------------
--spec rsa_suites(Version::ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()].
+-spec rsa_suites(Version::ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the RSA key exchange
%% cipher suites, only supported if explicitly set by user.
%% Are not considered secure any more.
%%--------------------------------------------------------------------
-rsa_suites({3, 3}) ->
- rsa_exclusive(3) ++ rsa_exclusive(1);
-rsa_suites({3, 2}) ->
- rsa_exclusive(1);
-rsa_suites({3, 1}) ->
- rsa_exclusive(1).
-
-rsa_exclusive(3) ->
+rsa_suites(Version) when ?TLS_1_X(Version) ->
+ lists:flatmap(fun rsa_exclusive/1, rsa_suites_to_test(Version)).
+
+rsa_suites_to_test(?TLS_1_2) -> [?TLS_1_2, ?TLS_1_0];
+rsa_suites_to_test(?TLS_1_1) -> [?TLS_1_0];
+rsa_suites_to_test(?TLS_1_0) -> [?TLS_1_0].
+
+-spec rsa_exclusive(Version::ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
+rsa_exclusive(?TLS_1_2) ->
[
?TLS_RSA_WITH_AES_256_GCM_SHA384,
?TLS_RSA_WITH_AES_256_CBC_SHA256,
?TLS_RSA_WITH_AES_128_GCM_SHA256,
?TLS_RSA_WITH_AES_128_CBC_SHA256
];
-rsa_exclusive(1) ->
+rsa_exclusive(?TLS_1_0) ->
[?TLS_RSA_WITH_AES_256_CBC_SHA,
?TLS_RSA_WITH_AES_128_CBC_SHA,
?TLS_RSA_WITH_3DES_EDE_CBC_SHA
@@ -828,15 +845,15 @@ rsa_exclusive(1) ->
rsa_exclusive(_) ->
[].
-signature_algs({3, 4}, HashSigns) ->
- signature_algs({3, 3}, HashSigns);
-signature_algs({3, 3}, HashSigns) ->
+signature_algs(?TLS_1_3, HashSigns) ->
+ signature_algs(?TLS_1_2, HashSigns);
+signature_algs(?TLS_1_2, HashSigns) ->
CryptoSupports = crypto:supports(),
Hashes = proplists:get_value(hashs, CryptoSupports),
PubKeys = proplists:get_value(public_keys, CryptoSupports),
Schemes = rsa_schemes(),
Supported = lists:foldl(fun({Hash, dsa = Sign} = Alg, Acc) ->
- case proplists:get_bool(dss, PubKeys)
+ case proplists:get_bool(dss, PubKeys)
andalso proplists:get_bool(Hash, Hashes)
andalso is_pair(Hash, Sign, Hashes)
of
@@ -845,9 +862,9 @@ signature_algs({3, 3}, HashSigns) ->
false ->
Acc
end;
- ({Hash, Sign} = Alg, Acc) ->
- case proplists:get_bool(Sign, PubKeys)
- andalso proplists:get_bool(Hash, Hashes)
+ ({Hash, Sign} = Alg, Acc) ->
+ case proplists:get_bool(Sign, PubKeys)
+ andalso proplists:get_bool(Hash, Hashes)
andalso is_pair(Hash, Sign, Hashes)
of
true ->
@@ -858,7 +875,7 @@ signature_algs({3, 3}, HashSigns) ->
(Alg, Acc) when is_atom(Alg) ->
case lists:member(Alg, Schemes) of
true ->
- [NewAlg] = signature_schemes({3,4}, [Alg]),
+ [NewAlg] = signature_schemes(?TLS_1_3, [Alg]),
[NewAlg| Acc];
false ->
Acc
@@ -866,11 +883,12 @@ signature_algs({3, 3}, HashSigns) ->
end, [], HashSigns),
lists:reverse(Supported).
-default_signature_algs([{3, 4} = Version]) ->
- default_signature_schemes(Version) ++ legacy_signature_schemes(Version);
-default_signature_algs([{3, 4}, {3,3} | _]) ->
- default_signature_schemes({3,4}) ++ default_pre_1_3_signature_algs_only();
-default_signature_algs([{3, 3} = Version |_]) ->
+default_signature_algs([?TLS_1_3]) ->
+ default_signature_schemes(?TLS_1_3) ++ legacy_signature_schemes(?TLS_1_3);
+default_signature_algs([?TLS_1_3, ?TLS_1_2 | _]) ->
+ default_signature_schemes(?TLS_1_3) ++ legacy_signature_schemes(?TLS_1_3)
+ ++ default_pre_1_3_signature_algs_only();
+default_signature_algs([?TLS_1_2 = Version |_]) ->
Default = [%% SHA2 ++ PSS
{sha512, ecdsa},
rsa_pss_pss_sha512,
@@ -883,13 +901,8 @@ default_signature_algs([{3, 3} = Version |_]) ->
{sha256, ecdsa},
rsa_pss_pss_sha256,
rsa_pss_rsae_sha256,
- {sha256, rsa},
- {sha224, ecdsa},
- {sha224, rsa},
- %% SHA
- {sha, ecdsa},
- {sha, rsa},
- {sha, dsa}],
+ {sha256, rsa}
+ ],
signature_algs(Version, Default);
default_signature_algs(_) ->
undefined.
@@ -897,22 +910,16 @@ default_signature_algs(_) ->
default_pre_1_3_signature_algs_only() ->
Default = [%% SHA2
{sha512, ecdsa},
- {sha512, rsa},
{sha384, ecdsa},
- {sha384, rsa},
- {sha256, ecdsa},
- {sha256, rsa},
- {sha224, ecdsa},
- {sha224, rsa},
- %% SHA
- {sha, ecdsa},
- {sha, rsa},
- {sha, dsa}],
- signature_algs({3,3}, Default).
+ {sha256, ecdsa}
+ ],
+ signature_algs(?TLS_1_2, Default).
+legacy_signature_algs_pre_13() ->
+ [{sha224, ecdsa}, {sha224, rsa}, {sha, rsa}, {sha, dsa}].
signature_schemes(Version, [_|_] =SignatureSchemes) when is_tuple(Version)
- andalso Version >= {3, 3} ->
+ andalso ?TLS_GTE(Version, ?TLS_1_2) ->
CryptoSupports = crypto:supports(),
Hashes = proplists:get_value(hashs, CryptoSupports),
PubKeys = proplists:get_value(public_keys, CryptoSupports),
@@ -995,12 +1002,10 @@ legacy_signature_schemes(Version) ->
%% MAY appear in "signature_algorithms" and
%% "signature_algorithms_cert" for backward compatibility with
%% TLS 1.2.
- LegacySchemes =
+ LegacySchemes =
[rsa_pkcs1_sha512,
rsa_pkcs1_sha384,
- rsa_pkcs1_sha256,
- ecdsa_sha1,
- rsa_pkcs1_sha1],
+ rsa_pkcs1_sha256],
signature_schemes(Version, LegacySchemes).
rsa_schemes() ->
@@ -1037,7 +1042,7 @@ hmac_hash(?NULL, _, _) ->
hmac_hash(Alg, Key, Value) ->
crypto:mac(hmac, mac_algo(Alg), Key, Value).
-mac_algo(Alg) when is_atom(Alg) ->
+mac_algo(Alg) when is_atom(Alg) ->
Alg;
mac_algo(?MD5) -> md5;
mac_algo(?SHA) -> sha;
@@ -1127,22 +1132,23 @@ is_pair(Hash, rsa, Hashs) ->
is_pair(_,_,_) ->
false.
+%% Should we create a new function for ecc_curves(Version)
+%% and another explicit one for ecc_curve_named([TLSCurves])?
%% list ECC curves in preferred order
--spec ecc_curves(1..3 | all) -> [named_curve()].
+-spec ecc_curves(TLS | DTLS | all) -> [named_curve()] when
+ TLS :: ?TLS_1_0 | ?TLS_1_1 | ?TLS_1_2,
+ DTLS :: ?DTLS_1_0 | ?DTLS_1_2;
+ ([named_curve()]) -> [named_curve()].
ecc_curves(all) ->
[sect571r1,sect571k1,secp521r1,brainpoolP512r1,
sect409k1,sect409r1,brainpoolP384r1,secp384r1,
- sect283k1,sect283r1,brainpoolP256r1,secp256k1,secp256r1,
- sect239k1,sect233k1,sect233r1,secp224k1,secp224r1,
- sect193r1,sect193r2,secp192k1,secp192r1,sect163k1,
- sect163r1,sect163r2,secp160k1,secp160r1,secp160r2];
+ sect283k1,sect283r1,brainpoolP256r1,secp256k1,secp256r1];
-ecc_curves(Minor) ->
+ecc_curves(Version) when is_tuple(Version) ->
TLSCurves = ecc_curves(all),
- ecc_curves(Minor, TLSCurves).
+ ecc_curves(TLSCurves);
--spec ecc_curves(1..3, [named_curve()]) -> [named_curve()].
-ecc_curves(_Minor, TLSCurves) ->
+ecc_curves(TLSCurves) when is_list(TLSCurves) ->
CryptoCurves = crypto:ec_curves(),
lists:foldr(fun(Curve, Curves) ->
case proplists:get_bool(Curve, CryptoCurves) of
@@ -1151,7 +1157,11 @@ ecc_curves(_Minor, TLSCurves) ->
end
end, [], TLSCurves).
--spec groups(4 | all | default) -> [group()].
+groups() ->
+ TLSGroups = groups(all),
+ groups(TLSGroups).
+
+-spec groups(all | default | TLSGroups :: list()) -> [group()].
groups(all) ->
[x25519,
x448,
@@ -1168,18 +1178,13 @@ groups(default) ->
x448,
secp256r1,
secp384r1];
-groups(Minor) ->
- TLSGroups = groups(all),
- groups(Minor, TLSGroups).
-%%
--spec groups(4, [group()]) -> [group()].
-groups(_Minor, TLSGroups) ->
+groups(TLSGroups) when is_list(TLSGroups) ->
CryptoGroups = supported_groups(),
lists:filter(fun(Group) -> proplists:get_bool(Group, CryptoGroups) end, TLSGroups).
-default_groups(Minor) ->
+default_groups() ->
TLSGroups = groups(default),
- groups(Minor, TLSGroups).
+ groups(TLSGroups).
supported_groups() ->
%% TODO: Add new function to crypto?
@@ -1273,3 +1278,23 @@ enum_to_oid(29) -> ?'id-X25519';
enum_to_oid(30) -> ?'id-X448';
enum_to_oid(_) ->
undefined.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(kdt,
+ {call, {?MODULE, update_traffic_secret,
+ [_HKDF, ApplicationTrafficSecret0]}},
+ Stack) ->
+ ATS0 = string:sub_string(
+ binary:bin_to_list(
+ binary:encode_hex(ApplicationTrafficSecret0)), 1, 5) ++ "...",
+ {io_lib:format("ApplicationTrafficSecret0 = \"~s\"", [ATS0]), Stack};
+handle_trace(kdt,
+ {return_from, {?MODULE, update_traffic_secret, 2},
+ Return}, Stack) ->
+ ATS = string:sub_string(
+ binary:bin_to_list(
+ binary:encode_hex(Return)), 1, 5) ++ "...",
+ {io_lib:format("ApplicationTrafficSecret = \"~s\"", [ATS]), Stack}.
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index ac1318bd14..d8bb5b5913 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2022. All Rights Reserved.
+# Copyright Ericsson AB 1999-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -56,6 +56,7 @@ MODULES = \
openssl_sni_SUITE\
ssl_mfl_SUITE\
openssl_mfl_SUITE\
+ ssl_use_srtp_SUITE\
ssl_reject_SUITE\
ssl_renegotiate_SUITE\
openssl_renegotiate_SUITE\
@@ -79,6 +80,7 @@ MODULES = \
ssl_session_cache_SUITE \
ssl_session_cache_api_SUITE\ \
ssl_session_ticket_SUITE \
+ ssl_trace_SUITE \
openssl_session_ticket_SUITE \
openssl_session_SUITE \
ssl_ECC_SUITE \
@@ -94,7 +96,12 @@ MODULES = \
ssl_socket_SUITE\
make_certs \
x509_test \
- inet_crypto_dist \
+ cryptcookie \
+ dist_cryptcookie \
+ inet_epmd_cryptcookie_inet_ktls \
+ inet_epmd_dist_cryptcookie_inet \
+ inet_epmd_dist_cryptcookie_socket \
+ inet_epmd_cryptcookie_socket_ktls \
openssl_ocsp_SUITE \
tls_server_session_ticket_SUITE \
tls_client_ticket_store_SUITE
diff --git a/lib/ssl/test/cryptcookie.erl b/lib/ssl/test/cryptcookie.erl
new file mode 100644
index 0000000000..19a26651ac
--- /dev/null
+++ b/lib/ssl/test/cryptcookie.erl
@@ -0,0 +1,747 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% -------------------------------------------------------------------------
+%%
+%% Module for an encrypted Erlang stream based only on
+%% the cookie as a shared secret
+%%
+-module(cryptcookie).
+-feature(maybe_expr, enable).
+
+-export([supported/0, start_keypair_server/0, init/1, init/2,
+ encrypt_and_send_chunk/4, recv_and_decrypt_chunk/2]).
+
+%% For kTLS integration
+-export([ktls_info/1, ktls_info/0]).
+-include_lib("ssl/src/ssl_cipher.hrl").
+-include_lib("ssl/src/ssl_internal.hrl").
+-include_lib("ssl/src/ssl_record.hrl").
+
+
+-define(PROTOCOL, (?MODULE)).
+
+%% -------------------------------------------------------------------------
+%% The curve choice greatly affects setup time,
+%% we really want an Edwards curve but that would
+%% require a very new openssl version.
+%%
+%% Twisted brainpool curves (*t1) are faster than
+%% non-twisted (*r1), 256 is much faster than 384,
+%% and so on...
+%%
+%%% -define(CURVE, brainpoolP384t1).
+%%% -define(CURVE, brainpoolP256t1).
+-define(CURVE, secp256r1). % Portability
+-define(CIPHER, aes_256_gcm). % kTLS compatible
+-define(HMAC, sha384).
+
+%% kTLS integration
+-define(TLS_VERSION, ?TLS_1_3).
+-define(CIPHER_SUITE, (?TLS_AES_256_GCM_SHA384)).
+
+
+supported() ->
+ maybe
+ true ?= crypto_supports(curves, ?CURVE),
+ true ?= crypto_supports(ciphers, ?CIPHER),
+ true ?= crypto_supports(macs, hmac),
+ true ?= crypto_supports(hashs, ?HMAC),
+ ok
+ end.
+
+crypto_supports(Tag, Item) ->
+ lists:member(Item, crypto:supports(Tag))
+ orelse "Crypto does not support "
+ ++ atom_to_list(Tag) ++ ": " ++ atom_to_list(Item).
+
+%% -------------------------------------------------------------------------
+
+-define(PACKET_SIZE, (1 bsl 16)). % 2 byte size header
+%% Plenty of room for AEAD tag and chunk type
+-define(CHUNK_SIZE, (?PACKET_SIZE - 256)).
+
+-record(params,
+ {hmac_algorithm = ?HMAC,
+ aead_cipher = ?CIPHER,
+ iv,
+ key,
+ tag_len = 16,
+ rekey_count = 256 * 1024,
+ rekey_time = 2 * 3600, % (seconds): 2 hours
+ rekey_timestamp,
+ rekey_key
+ }).
+
+params() ->
+ #{ iv_length := IVLen, key_length := KeyLen } =
+ crypto:cipher_info(?CIPHER),
+ #params{ iv = IVLen, key = KeyLen, rekey_timestamp = timestamp() }.
+
+
+-record(keypair,
+ {type = ecdh,
+ params = ?CURVE,
+ public,
+ private,
+ life_count = 256, % Number of connection setups
+ life_time = 3600, % 1 hour
+ life_timestamp
+ }).
+
+%% -------------------------------------------------------------------------
+%% Keep the node's public/private key pair in the process state
+%% of a key pair server linked to the net_kernel process.
+%% Create the key pair the first time it is needed
+%% so crypto gets time to start first.
+%%
+
+start_keypair_server() ->
+ Parent = self(),
+ Ref = make_ref(),
+ _ =
+ spawn_link(
+ fun () ->
+ try register(?MODULE, self()) of
+ true ->
+ Parent ! Ref,
+ keypair_server()
+ catch error : badarg ->
+ %% Already started - simply exit
+ %% and let the other run
+ Parent ! Ref,
+ ok
+ end
+ end),
+ receive Ref ->
+ ?PROTOCOL
+ end.
+
+keypair_server() ->
+ keypair_server(undefined, 1).
+%%
+keypair_server(KeyPair) ->
+ keypair_server(KeyPair, KeyPair#keypair.life_count).
+%%
+keypair_server(_KeyPair, 0) ->
+ keypair_server();
+keypair_server(KeyPair, Count) ->
+ receive
+ {RefAlias, get_keypair} when is_reference(RefAlias) ->
+ case KeyPair of
+ undefined ->
+ KeyPair_1 = generate_keypair(),
+ RefAlias ! {RefAlias, KeyPair_1},
+ keypair_server(KeyPair_1);
+ #keypair{} ->
+ RefAlias ! {RefAlias, KeyPair},
+ keypair_server(KeyPair, Count - 1)
+ end;
+ {RefAlias, get_new_keypair} ->
+ KeyPair_1 = generate_keypair(),
+ RefAlias ! {RefAlias, KeyPair_1},
+ keypair_server(KeyPair_1)
+ end.
+
+call_keypair_server(Request) ->
+ Pid = whereis(?MODULE),
+ RefAlias = erlang:monitor(process, Pid, [{alias, reply_demonitor}]),
+ Pid ! {RefAlias, Request},
+ receive
+ {RefAlias, Reply} ->
+ Reply;
+ {'DOWN', RefAlias, process, Pid, Reason} ->
+ error({keypair_server, Reason})
+ end.
+
+generate_keypair() ->
+ #keypair{ type = Type, params = Params } = #keypair{},
+ {Public, Private} = crypto:generate_key(Type, Params),
+ #keypair{
+ public = Public, private = Private,
+ life_timestamp = timestamp() }.
+
+
+get_keypair() ->
+ call_keypair_server(?FUNCTION_NAME).
+
+get_new_keypair() ->
+ call_keypair_server(?FUNCTION_NAME).
+
+compute_shared_secret(
+ #keypair{
+ type = PublicKeyType,
+ params = PublicKeyParams,
+ private = PrivKey }, PubKey) ->
+ %%
+ crypto:compute_key(PublicKeyType, PubKey, PrivKey, PublicKeyParams).
+
+%% -------------------------------------------------------------------------
+
+-define(DATA_CHUNK, 2).
+-define(TICK_CHUNK, 3).
+-define(REKEY_CHUNK, 4).
+
+%% -------------------------------------------------------------------------
+%% Crypto strategy
+%% -------
+%% The crypto strategy is as simple as possible to get an encrypted
+%% connection as benchmark reference. It is geared around AEAD
+%% ciphers in particular AES-GCM.
+%%
+%% The init message and the start message must fit in the TCP buffers
+%% since both sides start with sending the init message, waits
+%% for the other end's init message, sends the start message
+%% and waits for the other end's start message. So if the send
+%% blocks we have a deadlock.
+%%
+%% The init + start sequence tries to implement Password Encrypted
+%% Key Exchange using a node public/private key pair and the
+%% shared secret (the Cookie) to create session encryption keys
+%% that can not be re-created if the shared secret is compromized,
+%% which should create forward secrecy. You need both nodes'
+%% key pairs and the shared secret to decrypt the traffic
+%% between the nodes.
+%%
+%% All exchanged messages uses {packet, 2} i.e 16 bit size header.
+%%
+%% The init message contains a random number and encrypted: the public key
+%% and two random numbers. The encryption is done with Key and IV hashed
+%% from the unencrypted random number and the shared secret.
+%%
+%% The other node's public key is used with the own node's private
+%% key to create a shared key that is hashed with one of the encrypted
+%% random numbers from each side to create Key and IV for the session.
+%%
+%% The start message contains the two encrypted random numbers
+%% this time encrypted with the session keys for verification
+%% by the other side, plus the rekey count. The rekey count
+%% is just there to get an early check for if the other side's
+%% maximum rekey count is acceptable, it is just an embryo
+%% of some better check. Any side may rekey earlier but if the
+%% rekey count is exceeded the connection fails. Rekey is also
+%% triggered by a timer.
+%%
+%% Subsequent encrypted messages has the sequence number and the length
+%% of the message as AAD data, and an incrementing IV. These messages
+%% has got a message type that differentiates data from ticks and rekeys.
+%% Ticks have a random size in an attempt to make them less obvious to spot.
+%%
+%% Rekeying is done by the sender that creates a new key pair and
+%% a new shared secret from the other end's public key and with
+%% this and the current key and iv hashes a new key and iv.
+%% The new public key is sent to the other end that uses it
+%% and its old private key to create the same new shared
+%% secret and from that a new key and iv.
+%% So the receiver keeps its private key, and the sender keeps
+%% the receivers public key for the connection's life time.
+%% While the sender generates a new key pair at every rekey,
+%% which changes the shared secret at every rekey.
+%%
+%% The only reaction to errors is to crash noisily (?) which will bring
+%% down the connection and hopefully produce something useful
+%% in the local log, but all the other end sees is a closed connection.
+%% -------------------------------------------------------------------------
+
+
+%% -------------------------------------------------------------------------
+%% Initialize encryption on Stream; initial handshake
+%%
+%% init(Stream, Secret) ->
+%% {NewStream, ChunkSize, [RecvSeq|RecvParams], [SendSeq|SendParams]}.
+
+init(Stream) ->
+ Secret = atom_to_binary(auth:get_cookie(), latin1),
+ init(Stream, Secret).
+
+init(Stream = {_, OutStream, _}, Secret) ->
+ #keypair{ public = PubKey } = KeyPair = get_keypair(),
+ Params = params(),
+ {R2, R3, Msg} = init_msg(Params, Secret, PubKey),
+ OutStream_1 = init_send_block(OutStream, Msg, iolist_size(Msg)),
+ Stream_1 = setelement(2, Stream, OutStream_1),
+ init_recv(Stream_1, Params, Secret, KeyPair, R2, R3).
+
+init_recv(
+ {InStream, OutStream, ControllingProcessFun},
+ Params = #params{ iv = IVLen },
+ Secret, KeyPair, R2, R3) ->
+ %%
+ [InitMsg | InStream_1] = init_recv_block(InStream),
+ IVSaltLen = IVLen - 6,
+ try
+ case init_msg(Params, Secret, KeyPair, R2, R3, InitMsg) of
+ {SendParams =
+ #params{iv = <<IV2ASalt:IVSaltLen/binary, IV2ANo:48>> },
+ RecvParams,
+ SendStartMsg} ->
+ OutStream_1 =
+ init_send_block(
+ OutStream, SendStartMsg, iolist_size(SendStartMsg)),
+ [RecvStartMsg | InStream_2] = init_recv_block(InStream_1),
+ RecvParams_1 =
+ #params{
+ iv = <<IV2BSalt:IVSaltLen/binary, IV2BNo:48>> } =
+ start_msg(RecvParams, R2, R3, RecvStartMsg),
+ Stream_2 = {InStream_2, OutStream_1, ControllingProcessFun},
+ RecvSeqParams =
+ [0 | RecvParams_1#params{ iv = {IV2BSalt, IV2BNo} }],
+ SendSeqParams =
+ [0 | SendParams#params{ iv = {IV2ASalt, IV2ANo} }],
+ CipherState = {RecvSeqParams, SendSeqParams},
+ {Stream_2, ?CHUNK_SIZE, CipherState}
+ end
+ catch
+ Class : Reason : Stacktrace when Class =:= error ->
+ error_report(
+ [init_recv_exception,
+ {class, Class},
+ {reason, Reason},
+ {stacktrace, Stacktrace}]),
+ _ = trace({Reason, Stacktrace}),
+ exit(connection_closed)
+ end.
+
+init_msg(
+ #params{
+ hmac_algorithm = HmacAlgo,
+ aead_cipher = AeadCipher,
+ key = KeyLen,
+ iv = IVLen,
+ tag_len = TagLen }, Secret, PubKeyA) ->
+ %%
+ RLen = KeyLen + IVLen,
+ <<R1A:RLen/binary, R2A:RLen/binary, R3A:RLen/binary>> =
+ crypto:strong_rand_bytes(3 * RLen),
+ {Key1A, IV1A} = hmac_key_iv(HmacAlgo, R1A, Secret, KeyLen, IVLen),
+ Plaintext = [R2A, R3A, PubKeyA],
+ MsgLen = byte_size(R1A) + TagLen + iolist_size(Plaintext),
+ AAD = [<<MsgLen:32>>, R1A],
+ {Ciphertext, Tag} =
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key1A, IV1A, Plaintext, AAD, TagLen, true),
+ Msg = [R1A, Tag, Ciphertext],
+ {R2A, R3A, Msg}.
+%%
+init_msg(
+ #params{
+ hmac_algorithm = HmacAlgo,
+ aead_cipher = AeadCipher,
+ key = KeyLen,
+ iv = IVLen,
+ tag_len = TagLen,
+ rekey_count = RekeyCount } = Params,
+ Secret, KeyPair, R2A, R3A, Msg) ->
+ %%
+ RLen = KeyLen + IVLen,
+ case Msg of
+ <<R1B:RLen/binary, Tag:TagLen/binary, Ciphertext/binary>> ->
+ {Key1B, IV1B} = hmac_key_iv(HmacAlgo, R1B, Secret, KeyLen, IVLen),
+ MsgLen = byte_size(Msg),
+ AAD = [<<MsgLen:32>>, R1B],
+ case
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key1B, IV1B, Ciphertext, AAD, Tag, false)
+ of
+ <<R2B:RLen/binary, R3B:RLen/binary, PubKeyB/binary>> ->
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ %%
+ {Key2A, IV2A} =
+ hmac_key_iv(
+ HmacAlgo, SharedSecret, [R2A, R3B], KeyLen, IVLen),
+ SendParams =
+ Params#params{
+ rekey_key = PubKeyB,
+ key = Key2A, iv = IV2A },
+ %%
+ StartCleartext = [R2B, R3B, <<RekeyCount:32>>],
+ StartMsgLen = TagLen + iolist_size(StartCleartext),
+ StartAAD = <<StartMsgLen:32>>,
+ {StartCiphertext, StartTag} =
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key2A, IV2A,
+ StartCleartext, StartAAD, TagLen, true),
+ StartMsg = [StartTag, StartCiphertext],
+ %%
+ {Key2B, IV2B} =
+ hmac_key_iv(
+ HmacAlgo, SharedSecret, [R2B, R3A], KeyLen, IVLen),
+ RecvParams =
+ Params#params{
+ rekey_key = KeyPair,
+ key = Key2B, iv = IV2B },
+ %%
+ {SendParams, RecvParams, StartMsg}
+ end
+ end.
+
+start_msg(
+ #params{
+ aead_cipher = AeadCipher,
+ key = Key2B,
+ iv = IV2B,
+ tag_len = TagLen,
+ rekey_count = RekeyCountA } = RecvParams, R2A, R3A, Msg) ->
+ %%
+ case Msg of
+ <<Tag:TagLen/binary, Ciphertext/binary>> ->
+ KeyLen = byte_size(Key2B),
+ IVLen = byte_size(IV2B),
+ RLen = KeyLen + IVLen,
+ MsgLen = byte_size(Msg),
+ AAD = <<MsgLen:32>>,
+ case
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key2B, IV2B, Ciphertext, AAD, Tag, false)
+ of
+ <<R2A:RLen/binary, R3A:RLen/binary, RekeyCountB:32>>
+ when RekeyCountA =< (RekeyCountB bsl 2),
+ RekeyCountB =< (RekeyCountA bsl 2) ->
+ RecvParams#params{ rekey_count = RekeyCountB }
+ end
+ end.
+
+hmac_key_iv(HmacAlgo, MacKey, Data, KeyLen, IVLen) ->
+ <<Key:KeyLen/binary, IV:IVLen/binary>> =
+ crypto:macN(hmac, HmacAlgo, MacKey, Data, KeyLen + IVLen),
+ {Key, IV}.
+
+
+init_send_block(OutStream, Chunk, Size) ->
+ OutStream_1 = send_block(OutStream, Chunk, Size),
+ if
+ hd(OutStream_1) =:= closed ->
+ error_report(
+ [?FUNCTION_NAME,
+ {reason, closed}]),
+ _ = trace({?FUNCTION_NAME, closed}),
+ exit(connection_closed);
+ true ->
+ OutStream_1
+ end.
+
+init_recv_block(InStream) ->
+ Result = [Data | _] = recv_block(InStream),
+ if
+ Data =:= closed ->
+ error_report(
+ [?FUNCTION_NAME,
+ {reason, closed}]),
+ _ = trace({?FUNCTION_NAME, closed}),
+ exit(connection_closed);
+ true ->
+ Result
+ end.
+
+
+%% -------------------------------------------------------------------------
+encrypt_and_send_chunk(
+ OutStream,
+ [Seq |
+ Params =
+ #params{ rekey_count = RekeyCount,
+ rekey_time = RekeyTime,
+ rekey_timestamp = RekeyTimestamp }],
+ Chunk, Size) ->
+ %%
+ Timestamp = timestamp(),
+ if
+ RekeyCount =< Seq;
+ RekeyTimestamp + RekeyTime =< Timestamp ->
+ {OutStream_1, Params_1} =
+ encrypt_and_send_rekey_chunk(
+ OutStream, Seq, Params, Timestamp),
+ if
+ hd(OutStream_1) =:= closed ->
+ {OutStream_1, [0 | Params_1]};
+ true ->
+ {encrypt_and_send_chunk(
+ OutStream_1, 0, Params_1, Chunk, Size),
+ [1 | Params_1]}
+ end;
+ true ->
+ {encrypt_and_send_chunk(OutStream, Seq, Params, Chunk, Size),
+ [Seq + 1 | Params]}
+ end.
+
+encrypt_and_send_chunk(OutStream, Seq, Params, Chunk, 0) -> % Tick
+ <<>> = Chunk, % ASSERT
+ %% A ticks are sent as a somewhat random size block
+ %% to make it less obvious to spot
+ <<S:8>> = crypto:strong_rand_bytes(1),
+ TickSize = 8 + (S band 63),
+ TickData = crypto:strong_rand_bytes(TickSize),
+ encrypt_and_send_block(
+ OutStream, Seq, Params, [?TICK_CHUNK, TickData], 1 + TickSize);
+encrypt_and_send_chunk(OutStream, Seq, Params, Chunk, Size) ->
+ encrypt_and_send_block(
+ OutStream, Seq, Params, [?DATA_CHUNK, Chunk], 1 + Size).
+
+encrypt_and_send_rekey_chunk(
+ OutStream, Seq,
+ Params =
+ #params{
+ rekey_key = PubKeyB,
+ key = Key,
+ iv = {IVSalt, IVNo},
+ hmac_algorithm = HmacAlgo },
+ Timestamp) ->
+ %%
+ KeyLen = byte_size(Key),
+ IVSaltLen = byte_size(IVSalt),
+ #keypair{ public = PubKeyA } = KeyPair = get_new_keypair(),
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ IV = <<(IVNo + Seq):48>>,
+ {Key_1, <<IVSalt_1:IVSaltLen/binary, IVNo_1:48>>} =
+ hmac_key_iv(
+ HmacAlgo, SharedSecret, [Key, IVSalt, IV],
+ KeyLen, IVSaltLen + 6),
+ %%
+ {encrypt_and_send_block(
+ OutStream, Seq, Params,
+ [?REKEY_CHUNK, PubKeyA], 1 + byte_size(PubKeyA)),
+ Params#params{
+ key = Key_1, iv = {IVSalt_1, IVNo_1},
+ rekey_timestamp = Timestamp }}.
+
+encrypt_and_send_block(OutStream, Seq, Params, Block, Size) ->
+ {EncryptedBlock, EncryptedSize} =
+ encrypt_block(Seq, Params, Block, Size),
+ send_block(OutStream, EncryptedBlock, EncryptedSize).
+
+encrypt_block(
+ Seq,
+ #params{
+ aead_cipher = AeadCipher,
+ iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen },
+ Block, Size) ->
+ %%
+ EncryptedSize = Size + TagLen,
+ AAD = <<Seq:32, EncryptedSize:32>>,
+ IVBin = <<IVSalt/binary, (IVNo + Seq):48>>,
+ {Ciphertext, CipherTag} =
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key, IVBin, Block, AAD, TagLen, true),
+ EncryptedBlock = [Ciphertext, CipherTag],
+ {EncryptedBlock, EncryptedSize}.
+
+%% Send packet=2
+%%
+send_block(OutStream, Block, Size) ->
+ Msg =
+ if
+ is_binary(Block) -> [<<Size:16>>, Block];
+ is_list(Block) -> [<<Size:16>> | Block]
+ end,
+ (hd(OutStream))(OutStream, Msg).
+
+
+%% -------------------------------------------------------------------------
+
+recv_and_decrypt_chunk(InStream, SeqParams = [Seq | Params]) ->
+ case recv_block(InStream) of
+ Result = [closed | _] ->
+ {Result, SeqParams};
+ [Ciphertext | InStream_1] ->
+ case decrypt_block(Seq, Params, Ciphertext) of
+ <<?DATA_CHUNK, DataChunk/binary>> ->
+ {[DataChunk | InStream_1], [Seq + 1 | Params]};
+ <<?TICK_CHUNK, _/binary>> ->
+ {[<<>> | InStream_1], [Seq + 1 | Params]};
+ <<?REKEY_CHUNK, RekeyChunk>> ->
+ case decrypt_rekey(Params, RekeyChunk) of
+ Params_1 = #params{} ->
+ recv_and_decrypt_chunk(
+ InStream_1, [0 | Params_1]);
+ Problem when is_atom(Problem) ->
+ error_report(
+ [?FUNCTION_NAME, {reason, Problem}]),
+ {[closed | InStream_1], [Seq + 1 | Params]}
+ end;
+ <<_UnknownChunk/binary>> ->
+ error_report([?FUNCTION_NAME, {reason, unknown_chunk}]),
+ {[closed | InStream_1], [Seq + 1 | Params]};
+ Problem when is_atom(Problem) ->
+ error_report([?FUNCTION_NAME, {reason, Problem}]),
+ {[closed | InStream_1], [Seq + 1 | Params]}
+ end
+ end.
+
+%% Non-optimized receive packet=2
+%%
+recv_block(InStream) ->
+ Result_1 = [Data | InStream_1] = (hd(InStream))(InStream, 2),
+ if
+ Data =:= closed ->
+ Result_1;
+ is_binary(Data) ->
+ recv_block(InStream_1, Data);
+ is_list(Data) ->
+ recv_block(InStream_1, iolist_to_binary(Data))
+ end.
+%%
+recv_block(InStream, <<0:16>>) ->
+ [<<>> | InStream];
+recv_block(InStream, <<Size:16>>) ->
+ case (hd(InStream))(InStream, Size) of
+ [Data | InStream_1] when is_list(Data) ->
+ [iolist_to_binary(Data) | InStream_1];
+ Result = [Data | _]
+ when is_binary(Data);
+ Data =:= closed ->
+ Result
+ end.
+
+decrypt_block(
+ Seq, #params{ rekey_count = RekeyCount }, _Ciphertext)
+ when RekeyCount =:= Seq ->
+ %% This was one chunk too many without rekeying
+ rekey_overdue;
+decrypt_block(
+ Seq,
+ #params{
+ aead_cipher = AeadCipher,
+ iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen },
+ Ciphertext) ->
+ %%
+ CiphertextSize = byte_size(Ciphertext),
+ if
+ CiphertextSize < TagLen ->
+ decrypt_short_block;
+ true ->
+ AAD = <<Seq:32, CiphertextSize:32>>,
+ IV = <<IVSalt/binary, (IVNo + Seq):48>>,
+ Size = CiphertextSize - TagLen,
+ <<EncryptedBlock:Size/binary, CipherTag:TagLen/binary>> =
+ Ciphertext,
+ case
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key, IV, EncryptedBlock, AAD, CipherTag,
+ false)
+ of
+ Block when is_binary(Block) ->
+ Block;
+ error ->
+ decrypt_error
+ end
+ end.
+
+decrypt_rekey(
+ Params =
+ #params{
+ iv = IV,
+ key = Key,
+ rekey_key = #keypair{public = PubKeyA} = KeyPair,
+ hmac_algorithm = HmacAlgorithm},
+ RekeyChunk) ->
+ %%
+ PubKeyLen = byte_size(PubKeyA),
+ case RekeyChunk of
+ <<PubKeyB:PubKeyLen/binary>> ->
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ KeyLen = byte_size(Key),
+ IVLen = byte_size(IV),
+ IVSaltLen = IVLen - 6,
+ {Key_1, <<IVSalt_1:IVSaltLen/binary, IVNo_1:48>>} =
+ hmac_key_iv(
+ HmacAlgorithm, SharedSecret, [Key, IV], KeyLen, IVLen),
+ Params#params{
+ iv = {IVSalt_1, IVNo_1},
+ key = Key_1 };
+ _ ->
+ decrypt_bad_rekey_chunk
+ end.
+
+
+%% -------------------------------------------------------------------------
+ktls_info(
+ {[RecvSeq |
+ #params{
+ iv = {RecvSalt, RecvIV},
+ key = RecvKey,
+ aead_cipher = ?CIPHER } = _RecvParams],
+ [SendSeq |
+ #params{
+ iv = {SendSalt, SendIV},
+ key = SendKey,
+ aead_cipher = ?CIPHER } = _SendParams]}) ->
+ %%
+ RecvState =
+ #cipher_state{
+ key = <<RecvKey/bytes>>,
+ iv = <<RecvSalt/bytes, (RecvIV + RecvSeq):48>> },
+ SendState =
+ #cipher_state{
+ key = <<SendKey/bytes>>,
+ iv = <<SendSalt/bytes, (SendIV + SendSeq):48>> },
+ #{ tls_version => ?TLS_VERSION,
+ cipher_suite => ?CIPHER_SUITE,
+ read_state => RecvState,
+ read_seq => RecvSeq,
+ write_state => SendState,
+ write_seq => SendSeq }.
+
+%% Dummy cipher parameters to use when checking if kTLS is supported.
+%% Completely random to avoid accidentally creating an unsafe connection.
+%%
+ktls_info() ->
+ #params{ iv = IVLen, key = KeyLen } = params(),
+ <<RecvSeq:48, RecvKey:KeyLen/bytes, RecvIV:IVLen/bytes>> =
+ crypto:strong_rand_bytes(6 + KeyLen + IVLen),
+ <<SendSeq:48, SendKey:KeyLen/bytes, SendIV:IVLen/bytes>> =
+ crypto:strong_rand_bytes(6 + KeyLen + IVLen),
+ RecvState = #cipher_state{ key = RecvKey, iv = RecvIV },
+ SendState = #cipher_state{ key = SendKey, iv = SendIV },
+ #{ tls_version => ?TLS_VERSION,
+ cipher_suite => ?CIPHER_SUITE,
+ read_state => RecvState,
+ read_seq => RecvSeq,
+ write_state => SendState,
+ write_seq => SendSeq }.
+
+
+%% -------------------------------------------------------------------------
+-ifdef(undefined).
+-define(RECORD_TO_MAP(Name),
+ record_to_map(Record) when element(1, (Record)) =:= (Name) ->
+ record_to_map(record_info(fields, Name), Record, 2, #{})).
+?RECORD_TO_MAP(params).
+%%
+record_to_map([Field | Fields], Record, Index, Map) ->
+ record_to_map(
+ Fields, Record, Index + 1,
+ Map#{ Field => element(Index, Record) });
+record_to_map([], _Record, _Index, Map) ->
+ Map.
+-endif.
+
+timestamp() ->
+ erlang:monotonic_time(second).
+
+
+error_report(Report) ->
+ error_logger:error_report(Report).
+
+-ifdef(undefined).
+info_report(Report) ->
+ error_logger:info_report(Report).
+-endif.
+
+%% Trace point
+trace(Term) -> Term.
diff --git a/lib/ssl/test/dist_cryptcookie.erl b/lib/ssl/test/dist_cryptcookie.erl
new file mode 100644
index 0000000000..703d828606
--- /dev/null
+++ b/lib/ssl/test/dist_cryptcookie.erl
@@ -0,0 +1,595 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% -------------------------------------------------------------------------
+%%
+%% Module for encrypted Erlang protocol - a minimal encrypted
+%% distribution protocol based on only a shared secret
+%% and the crypto application, over a Stream
+%%
+-module(dist_cryptcookie).
+-feature(maybe_expr, enable).
+
+%% inet_epmd_* API
+-export([protocol/0,
+ start_dist_ctrl/1,
+ controlling_process/2,
+ hs_data/2]).
+
+-include_lib("kernel/include/dist_util.hrl").
+
+%% ------------------------------------------------------------
+protocol() ->
+ cryptcookie:start_keypair_server().
+
+%% ------------------------------------------------------------
+start_dist_ctrl({Stream, ChunkSize, CipherState}) ->
+ ControllingProcess = self(),
+ Tag = make_ref(),
+ Pid =
+ spawn_opt(
+ fun () ->
+ receive
+ {Tag, Alias, {start, Stream_2}} ->
+ reply(Alias, trace(started)),
+ handshake(
+ Stream_2, Tag, ControllingProcess, ChunkSize,
+ CipherState)
+ end
+ end,
+ [link,
+ {priority, max},
+ {message_queue_data, off_heap},
+ {fullsweep_after, 0}]),
+ ControllingProcessFun = element(3, Stream),
+ Stream_1 = ControllingProcessFun(Stream, Pid),
+ DistCtrlHandle = {Pid, Tag},
+ started = call(DistCtrlHandle, {start, Stream_1}),
+ DistCtrlHandle.
+
+
+call({Pid, Tag}, Request) ->
+ Ref = Alias = monitor(process, Pid, [{alias, reply_demonitor}]),
+ Pid ! {Tag, Alias, Request},
+ receive
+ {Alias, Response} ->
+ Response;
+ {'DOWN', Ref, process, Pid, Reason} ->
+ error({dist_ctrl, Reason})
+ end.
+
+reply(Alias, Response) when is_reference(Alias) ->
+ Alias ! {Alias, Response},
+ ok.
+
+%% ------------------------------------------------------------
+controlling_process(DistCtrlHandle, Pid) ->
+ call(DistCtrlHandle, {controlling_process, Pid}),
+ DistCtrlHandle.
+
+%% ------------------------------------------------------------
+hs_data(NetAddress, {DistCtrl, _} = DistCtrlHandle) ->
+ #hs_data{
+ socket = DistCtrlHandle,
+ f_send =
+ fun (S, Packet) when S =:= DistCtrlHandle ->
+ call(S, {send, Packet})
+ end,
+ f_recv =
+ fun (S, 0, infinity) when S =:= DistCtrlHandle ->
+ call(S, recv)
+ end,
+ f_setopts_pre_nodeup = f_ok(DistCtrlHandle),
+ f_setopts_post_nodeup = f_ok(DistCtrlHandle),
+ f_address =
+ fun (S, Node) when S =:= DistCtrlHandle ->
+ inet_epmd_dist:f_address(NetAddress, Node)
+ end,
+ f_getll =
+ fun (S) when S =:= DistCtrlHandle ->
+ {ok, DistCtrl}
+ end,
+ f_handshake_complete =
+ fun (S, _Node, DistHandle) when S =:= DistCtrlHandle ->
+ call(S, {handshake_complete, DistHandle})
+ end,
+ %%
+ %%
+ mf_tick =
+ fun (S) when S =:= DistCtrlHandle ->
+ DistCtrl ! dist_tick
+ end }.
+
+f_ok(DistCtrlHandle) ->
+ fun (S) when S =:= DistCtrlHandle -> ok end.
+
+
+%% -------------------------------------------------------------------------
+%% net_kernel distribution handshake in progress
+%%
+
+handshake(
+ Stream, Tag, ControllingProcess, ChunkSize,
+ {DecryptState, EncryptState} = _CipherState) ->
+ handshake(
+ Stream, Tag, ControllingProcess,
+ ChunkSize, DecryptState, EncryptState).
+%%
+handshake(
+ Stream = {InStream, OutStream, ControllingProcessFun},
+ Tag, ControllingProcess, ChunkSize, DecryptState, EncryptState) ->
+ receive
+ {Tag, From, {controlling_process, NewControllingProcess}} ->
+ link(NewControllingProcess),
+ unlink(ControllingProcess),
+ reply(From, ok),
+ handshake(
+ Stream, Tag, NewControllingProcess,
+ ChunkSize, DecryptState, EncryptState);
+ {Tag, From, {handshake_complete, DistHandle}} ->
+ InputHandler =
+ spawn_opt(
+ fun () ->
+ link(ControllingProcess),
+ receive
+ {Tag, dist_handle, DistHandle, InStream_2} ->
+ input_handler_start(
+ InStream_2, DecryptState, DistHandle)
+ end
+ end,
+ [link,
+ {priority, normal},
+ {message_queue_data, off_heap},
+ {fullsweep_after, 0}]),
+ _ = monitor(process, InputHandler), % For the benchmark test
+ {InStream_1, OutStream_1, _} =
+ ControllingProcessFun(Stream, InputHandler),
+ false = erlang:dist_ctrl_set_opt(DistHandle, get_size, true),
+ ok = erlang:dist_ctrl_input_handler(DistHandle, InputHandler),
+ InputHandler ! {Tag, dist_handle, DistHandle, InStream_1},
+ reply(From, ok),
+ process_flag(priority, normal),
+ output_handler_start(
+ OutStream_1, EncryptState, ChunkSize, DistHandle);
+ %%
+ {Tag, From, {send, Data}} ->
+ {OutStream_1, EncryptState_1} =
+ cryptcookie:encrypt_and_send_chunk(
+ OutStream, EncryptState, Data, iolist_size(Data)),
+ if
+ hd(OutStream_1) =:= closed ->
+ reply(From, {error, closed}),
+ death_row({send, trace(closed)});
+ true ->
+ reply(From, ok),
+ handshake(
+ setelement(2, Stream, OutStream_1),
+ Tag, ControllingProcess,
+ ChunkSize, DecryptState, EncryptState_1)
+ end;
+ {Tag, From, recv} ->
+ {[Data | InStream_1], DecryptState_1} =
+ cryptcookie:recv_and_decrypt_chunk(InStream, DecryptState),
+ if
+ Data =:= closed ->
+ reply(From, {error, Data}),
+ death_row({recv, trace(Data)});
+ true ->
+ reply(From, {ok, binary_to_list(Data)}),
+ handshake(
+ setelement(1, Stream, InStream_1),
+ Tag, ControllingProcess,
+ ChunkSize, DecryptState_1, EncryptState)
+ end;
+ %%
+ Alien ->
+ _ = trace(Alien),
+ handshake(
+ Stream, Tag, ControllingProcess,
+ ChunkSize, DecryptState, EncryptState)
+ end.
+
+
+%% -------------------------------------------------------------------------
+%% Output handler process
+%%
+%% Await an event about what to do; fetch dist data from the VM,
+%% or send a dist tick.
+%%
+%% In case we are overloaded we could get many accumulated
+%% dist_tick messages; make sure to flush all of them
+%% before proceeding with what to do. But, do not use selective
+%% receive since that does not perform well when there are
+%% many messages in the process mailbox.
+
+%% Entry function
+output_handler_start(OutStream, EncryptState, ChunkSize, DistHandle) ->
+ try
+ erlang:dist_ctrl_get_data_notification(DistHandle),
+ output_handler(OutStream, EncryptState, [ChunkSize|DistHandle])
+ catch
+ Class : Reason : Stacktrace when Class =:= error ->
+ error_report(
+ [output_handler_exception,
+ {class, Class},
+ {reason, Reason},
+ {stacktrace, Stacktrace}]),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+%% Loop top
+%%
+%% Awaiting outbound data or tick
+%%
+output_handler(OutStream, EncryptState, CS_DH) ->
+ receive
+ Msg ->
+ case Msg of
+ dist_data ->
+ output_handler_data(
+ OutStream, EncryptState, CS_DH);
+ dist_tick ->
+ output_handler_tick(
+ OutStream, EncryptState, CS_DH);
+ _ ->
+ %% Ignore
+ _ = trace(Msg),
+ output_handler(OutStream, EncryptState, CS_DH)
+ end
+ end.
+
+%% We have received at least one dist_tick but no dist_data message
+%%
+output_handler_tick(OutStream, EncryptState, CS_DH) ->
+ receive
+ Msg ->
+ case Msg of
+ dist_data ->
+ output_handler_data(
+ OutStream, EncryptState, CS_DH);
+ dist_tick ->
+ %% Consume all dist_tick messages
+ %%
+ output_handler_tick(OutStream, EncryptState, CS_DH);
+ _ ->
+ %% Ignore
+ _ = trace(Msg),
+ output_handler_tick(OutStream, EncryptState, CS_DH)
+ end
+ after 0 ->
+ {OutStream_1, EncryptState_1} =
+ cryptcookie:encrypt_and_send_chunk(
+ OutStream, EncryptState, <<>>, 0),
+ if
+ hd(OutStream_1) =:= closed ->
+ death_row({send_tick, trace(closed)});
+ true ->
+ output_handler(OutStream_1, EncryptState_1, CS_DH)
+ end
+ end.
+
+%% We have received a dist_data notification
+%%
+output_handler_data(OutStream, EncryptState, CS_DH) ->
+ {OutStream_1, EncryptState_1} =
+ output_handler_xfer(OutStream, EncryptState, CS_DH, [], 0, []),
+ erlang:dist_ctrl_get_data_notification(tl(CS_DH)),
+ output_handler(OutStream_1, EncryptState_1, CS_DH).
+
+%% Get outbound data from VM; encrypt and send,
+%% until the VM has no more
+%%
+%% Front,Size,Rear is an Okasaki queue of binaries with total byte Size
+%%
+output_handler_xfer(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear)
+ when hd(CS_DH) =< Size ->
+ %%
+ %% We have a full chunk or more
+ %% -> collect one chunk or less and send
+ output_handler_collect(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear);
+output_handler_xfer(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear) ->
+ %% when Size < hd(CS_DH) ->
+ %%
+ %% We do not have a full chunk -> try to fetch more from VM
+ case erlang:dist_ctrl_get_data(tl(CS_DH)) of
+ none ->
+ if
+ Size =:= 0 ->
+ %% No more data from VM, nothing buffered
+ %% -> done, for now
+ {OutStream, EncryptState};
+ true ->
+ %% The VM had no more -> send what we have
+ output_handler_collect(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear)
+ end;
+ {Len,Iov} ->
+ output_handler_enq(
+ OutStream, EncryptState, CS_DH,
+ Front, Size + 4 + Len, [<<Len:32>>|Rear],
+ Iov)
+ end.
+
+%% Enqueue VM data while splitting large binaries into
+%% chunk size; hd(CS_DH)
+%%
+output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, []) ->
+ output_handler_xfer(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear);
+output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, [Bin|Iov]) ->
+ output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, Iov, Bin).
+%%
+output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, Iov, Bin) ->
+ BinSize = byte_size(Bin),
+ ChunkSize = hd(CS_DH),
+ if
+ BinSize =< ChunkSize ->
+ output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, [Bin|Rear],
+ Iov);
+ true ->
+ <<Bin1:ChunkSize/binary, Bin2/binary>> = Bin,
+ output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, [Bin1|Rear],
+ Iov, Bin2)
+ end.
+
+%% Collect small binaries into chunks of at most
+%% chunk size; hd(CS_DH)
+%%
+output_handler_collect(OutStream, EncryptState, CS_DH, [], Zero, []) ->
+ 0 = Zero, % ASSERT
+ %% No more enqueued -> try to get more form VM
+ output_handler_xfer(OutStream, EncryptState, CS_DH, [], Zero, []);
+output_handler_collect(OutStream, EncryptState, CS_DH, Front, Size, Rear) ->
+ output_handler_collect(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, [], 0).
+%%
+output_handler_collect(
+ OutStream, EncryptState, CS_DH, [], Zero, [], Acc, DataSize) ->
+ 0 = Zero, % ASSERT
+ output_handler_chunk(
+ OutStream, EncryptState, CS_DH, [], Zero, [], Acc, DataSize);
+output_handler_collect(
+ OutStream, EncryptState, CS_DH, [], Size, Rear, Acc, DataSize) ->
+ %% Okasaki queue transfer Rear -> Front
+ output_handler_collect(
+ OutStream, EncryptState, CS_DH, lists:reverse(Rear), Size, [],
+ Acc, DataSize);
+output_handler_collect(
+ OutStream, EncryptState, CS_DH, [Bin|Iov] = Front, Size, Rear,
+ Acc, DataSize) ->
+ ChunkSize = hd(CS_DH),
+ BinSize = byte_size(Bin),
+ DataSize_1 = DataSize + BinSize,
+ if
+ ChunkSize < DataSize_1 ->
+ %% Bin does not fit in chunk -> send Acc
+ output_handler_chunk(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear,
+ Acc, DataSize);
+ DataSize_1 < ChunkSize ->
+ %% Chunk not full yet -> try to accumulate more
+ output_handler_collect(
+ OutStream, EncryptState, CS_DH, Iov, Size - BinSize, Rear,
+ [Bin|Acc], DataSize_1);
+ true -> % DataSize_1 == ChunkSize ->
+ %% Optimize one iteration; Bin fits exactly
+ %% -> accumulate and send
+ output_handler_chunk(
+ OutStream, EncryptState, CS_DH, Iov, Size - BinSize, Rear,
+ [Bin|Acc], DataSize_1)
+ end.
+
+%% Encrypt and send a chunk
+%%
+output_handler_chunk(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, Acc, DataSize) ->
+ Data = lists:reverse(Acc),
+ {OutStream_1, EncryptState_1} =
+ cryptcookie:encrypt_and_send_chunk(
+ OutStream, EncryptState, Data, DataSize),
+ if
+ hd(OutStream_1) =:= closed ->
+ death_row({send_chunk, trace(closed)});
+ true ->
+ output_handler_collect(
+ OutStream_1, EncryptState_1, CS_DH, Front, Size, Rear)
+ end.
+
+
+%% -------------------------------------------------------------------------
+%% Input handler process
+%%
+
+%% Entry function
+input_handler_start(InStream, DecryptState, DistHandle) ->
+ try
+ input_handler(InStream, DecryptState, DistHandle)
+ catch
+ Class : Reason : Stacktrace when Class =:= error ->
+ error_report(
+ [input_handler_exception,
+ {class, Class},
+ {reason, Reason},
+ {stacktrace, Stacktrace}]),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+%% Loop top
+input_handler(InStream, DecryptState, DistHandle) ->
+ %% Shortcut into the loop
+ {InStream_1, DecryptState_1, Chunk} =
+ input_chunk(InStream, DecryptState),
+ input_handler(
+ InStream_1, DecryptState_1, DistHandle, Chunk, [], byte_size(Chunk)).
+%%
+input_handler(InStream, DecryptState, DistHandle, First, Buffer, Size) ->
+ %% Size is size of First + Buffer
+ case First of
+ <<Packet1Size:32, Packet1:Packet1Size/binary,
+ Packet2Size:32, Packet2:Packet2Size/binary, Rest/binary>> ->
+ erlang:dist_ctrl_put_data(DistHandle, Packet1),
+ erlang:dist_ctrl_put_data(DistHandle, Packet2),
+ input_handler(
+ InStream, DecryptState, DistHandle,
+ Rest, Buffer, Size - (8 + Packet1Size + Packet2Size));
+ <<PacketSize:32, Packet:PacketSize/binary, Rest/binary>> ->
+ erlang:dist_ctrl_put_data(DistHandle, Packet),
+ input_handler(
+ InStream, DecryptState, DistHandle,
+ Rest, Buffer, Size - (4 + PacketSize));
+ <<PacketSize:32, PacketStart/binary>> ->
+ %% Partial packet in First
+ input_handler(
+ InStream, DecryptState, DistHandle,
+ PacketStart, Buffer, Size - 4, PacketSize);
+ Tick = <<>> ->
+ erlang:dist_ctrl_put_data(DistHandle, Tick),
+ if
+ Buffer =:= [] ->
+ Size = 0, % ASSERT
+ input_handler(InStream, DecryptState, DistHandle);
+ true ->
+ [First_1 | Buffer_1] = lists:reverse(Buffer),
+ input_handler(
+ InStream, DecryptState, DistHandle,
+ First_1, Buffer_1, Size)
+ end;
+ <<Bin/binary>> ->
+ %% Partial header in First
+ if
+ 4 =< Size ->
+ %% Complete header in First + Buffer
+ {First_1, Buffer_1, PacketSize} =
+ input_get_packet_size(Bin, lists:reverse(Buffer)),
+ input_handler(
+ InStream, DecryptState, DistHandle,
+ First_1, Buffer_1, Size - 4, PacketSize);
+ true ->
+ %% Incomplete header received so far
+ {InStream_1, DecryptState_1, Chunk} =
+ input_chunk(InStream, DecryptState),
+ input_handler(
+ InStream_1, DecryptState_1, DistHandle,
+ Bin, [Chunk|Buffer], Size + byte_size(Chunk))
+ end
+ end.
+%%
+input_handler(
+ InStream, DecryptState, DistHandle,
+ PacketStart, Buffer, Size, PacketSize) ->
+ %%
+ %% Size is size of PacketStart + Buffer
+ RestSize = Size - PacketSize,
+ if
+ RestSize < 0 ->
+ %% Incomplete packet received so far
+ {InStream_1, DecryptState_1, Chunk} =
+ input_chunk(InStream, DecryptState),
+ input_handler(
+ InStream_1, DecryptState_1, DistHandle,
+ PacketStart, [Chunk|Buffer], Size + byte_size(Chunk),
+ PacketSize);
+ 0 < RestSize, Buffer =:= [] ->
+ %% Rest data in PacketStart
+ <<Packet:PacketSize/binary, Rest/binary>> = PacketStart,
+ erlang:dist_ctrl_put_data(DistHandle, Packet),
+ input_handler(
+ InStream, DecryptState, DistHandle, Rest, [], RestSize);
+ Buffer =:= [] -> % RestSize == 0, Size == 0
+ %% No rest data
+ erlang:dist_ctrl_put_data(DistHandle, PacketStart),
+ input_handler(InStream, DecryptState, DistHandle);
+ true ->
+ %% Split packet from rest data
+ LastBin = hd(Buffer),
+ <<PacketLast:(byte_size(LastBin) - RestSize)/binary,
+ Rest/binary>> = LastBin,
+ Packet = [PacketStart|lists:reverse(tl(Buffer), PacketLast)],
+ erlang:dist_ctrl_put_data(DistHandle, Packet),
+ input_handler(
+ InStream, DecryptState, DistHandle, Rest, [], RestSize)
+ end.
+
+input_get_packet_size(First, [Bin|Buffer]) ->
+ MissingSize = 4 - byte_size(First),
+ if
+ MissingSize =< byte_size(Bin) ->
+ <<Last:MissingSize/binary, Rest/binary>> = Bin,
+ <<PacketSize:32>> = <<First/binary, Last/binary>>,
+ {Rest, lists:reverse(Buffer), PacketSize};
+ true ->
+ input_get_packet_size(<<First/binary, Bin/binary>>, Buffer)
+ end.
+
+input_chunk(InStream, DecryptState) ->
+ {[Chunk | InStream_1], DecryptState_1} =
+ cryptcookie:recv_and_decrypt_chunk(InStream, DecryptState),
+ if
+ is_atom(Chunk) ->
+ _ = Chunk =:= closed
+ orelse
+ error_report(
+ [?FUNCTION_NAME,
+ {reason, Chunk}]),
+ _ = trace({?FUNCTION_NAME, Chunk}),
+ exit(connection_closed);
+ is_binary(Chunk) ->
+ {InStream_1, DecryptState_1, Chunk}
+ end.
+
+
+%% -------------------------------------------------------------------------
+
+%% Wait for getting killed by process link,
+%% and if that does not happen - drop dead
+
+death_row(Reason) ->
+ receive
+ after 5000 ->
+ death_row_timeout(Reason)
+ end.
+
+death_row_timeout(Reason) ->
+ error_report(
+ [?FUNCTION_NAME,
+ {reason, Reason},
+ {pid, self()}]),
+ exit(Reason).
+
+%% -------------------------------------------------------------------------
+
+error_report(Report) ->
+ error_logger:error_report(Report).
+
+-ifdef(undefined).
+info_report(Report) ->
+ error_logger:info_report(Report).
+-endif.
+
+%% Trace point
+trace(Term) -> Term.
diff --git a/lib/ssl/test/dtls_api_SUITE.erl b/lib/ssl/test/dtls_api_SUITE.erl
index 611c552858..35e19d86f2 100644
--- a/lib/ssl/test/dtls_api_SUITE.erl
+++ b/lib/ssl/test/dtls_api_SUITE.erl
@@ -191,7 +191,7 @@ dtls_listen_reopen() ->
[{doc, "Test that you close a DTLS 'listner' socket and open a new one for the same port"}].
dtls_listen_reopen(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -356,7 +356,7 @@ client_restarts() ->
[{doc, "Test re-connection "}].
client_restarts(Config) ->
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -435,7 +435,7 @@ client_restarts_multiple_acceptors(Config) ->
%% closed.
%% Then do a new openssl connect with the same client port.
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl
deleted file mode 100644
index 902bb5c252..0000000000
--- a/lib/ssl/test/inet_crypto_dist.erl
+++ /dev/null
@@ -1,1746 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
-%%
-%% 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.
-%%
-%% %CopyrightEnd%
-%%
-%% -------------------------------------------------------------------------
-%%
-%% Module for encrypted Erlang protocol - a minimal encrypted
-%% distribution protocol based on only a shared secret
-%% and the crypto application
-%%
--module(inet_crypto_dist).
--define(DIST_NAME, inet_crypto).
--define(DIST_PROTO, crypto).
--define(DRIVER, inet_tcp).
--define(FAMILY, inet).
-
--export([supported/0, listen/1, accept/1, accept_connection/5,
- setup/5, close/1, select/1, is_node_name/1]).
-
-%% Generalized dist API, for sibling IPv6 module inet6_crypto_dist
--export([gen_listen/2, gen_accept/2, gen_accept_connection/6,
- gen_setup/6, gen_close/2, gen_select/2]).
-
--export([nodelay/0]).
-
-%% Debug
-%%%-compile(export_all).
--export([dbg/0, test_server/0, test_client/1]).
-
--include_lib("kernel/include/net_address.hrl").
--include_lib("kernel/include/dist.hrl").
--include_lib("kernel/include/dist_util.hrl").
-
--define(PACKET_SIZE, 65536).
--define(BUFFER_SIZE, (?PACKET_SIZE bsl 4)).
-
-%% -------------------------------------------------------------------------
-
-%% The curve choice greatly affects setup time,
-%% we really want an Edwards curve but that would
-%% require a very new openssl version.
-%% Twisted brainpool curves (*t1) are faster than
-%% non-twisted (*r1), 256 is much faster than 384,
-%% and so on...
-%%% -define(CURVE, brainpoolP384t1).
-%%% -define(CURVE, brainpoolP256t1).
--define(CURVE, secp256r1).
--define(CIPHER, aes_gcm).
--define(HMAC, sha256).
-
--record(params,
- {socket,
- dist_handle,
- hmac_algorithm = ?HMAC,
- aead_cipher = ?CIPHER,
- rekey_key,
- iv = 12,
- key = 16,
- tag_len = 16,
- rekey_count = 262144,
- rekey_time = 7200000, % 2 hours
- rekey_msg
- }).
-
-params(Socket) ->
- #params{socket = Socket}.
-
-
--record(key_pair,
- {type = ecdh,
- params = ?CURVE,
- public,
- private,
- life_time = 3600000, % 1 hour
- life_count = 256 % Number of connection setups
- }).
-
-supported() ->
- Curve = lists:member(?CURVE, crypto:supports(curves)),
- Cipher = lists:member(?CIPHER, crypto:supports(ciphers)),
- Hmac =
- lists:member(hmac, crypto:supports(macs)) andalso
- lists:member(?HMAC, crypto:supports(hashs)),
- if
- not Curve ->
- "curve " ++ atom_to_list(?CURVE);
- not Cipher ->
- "cipher " ++ atom_to_list(?CIPHER);
- not Hmac ->
- "HMAC " ++ atom_to_list(?HMAC);
- true ->
- ok
- end.
-
-%% -------------------------------------------------------------------------
-%% Keep the node's public/private key pair in the process state
-%% of a key pair server linked to the acceptor process.
-%% Create the key pair the first time it is needed
-%% so crypto gets time to start first.
-%%
-
-start_key_pair_server() ->
- monitor_dist_proc(
- key_pair_server,
- spawn_link(
- fun () ->
- register(?MODULE, self()),
- key_pair_server()
- end)).
-
-key_pair_server() ->
- key_pair_server(undefined, undefined, undefined).
-%%
-key_pair_server(
- #key_pair{life_time = LifeTime, life_count = LifeCount} = KeyPair) ->
- %% Presuming: 1 < LifeCount
- Timer =
- case LifeCount of
- 1 ->
- undefined;
- _ ->
- erlang:start_timer(LifeTime, self(), discard)
- end,
- key_pair_server(KeyPair, Timer, LifeCount - 1).
-%%
-key_pair_server(_KeyPair, Timer, 0) ->
- cancel_timer(Timer),
- key_pair_server();
-key_pair_server(KeyPair, Timer, Count) ->
- receive
- {Pid, Tag, get_key_pair} ->
- case KeyPair of
- undefined ->
- KeyPair_1 = generate_key_pair(),
- Pid ! {Tag, KeyPair_1},
- key_pair_server(KeyPair_1);
- #key_pair{} ->
- Pid ! {Tag, KeyPair},
- key_pair_server(KeyPair, Timer, Count - 1)
- end;
- {Pid, Tag, get_new_key_pair} ->
- cancel_timer(Timer),
- KeyPair_1 = generate_key_pair(),
- Pid ! {Tag, KeyPair_1},
- key_pair_server(KeyPair_1);
- {timeout, Timer, discard} when is_reference(Timer) ->
- key_pair_server()
- end.
-
-generate_key_pair() ->
- #key_pair{type = Type, params = Params} = #key_pair{},
- {Public, Private} =
- crypto:generate_key(Type, Params),
- #key_pair{public = Public, private = Private}.
-
-
-cancel_timer(undefined) ->
- ok;
-cancel_timer(Timer) ->
- erlang_cancel_timer(Timer).
-
-start_rekey_timer(Time) ->
- Timer = erlang:start_timer(Time, self(), rekey_time),
- {timeout, Timer, rekey_time}.
-
-cancel_rekey_timer({timeout, Timer, rekey_time}) ->
- erlang_cancel_timer(Timer).
-
-erlang_cancel_timer(Timer) ->
- case erlang:cancel_timer(Timer) of
- false ->
- receive
- {timeout, Timer, _} -> ok
- end;
- _RemainingTime ->
- ok
- end.
-
-get_key_pair() ->
- call_key_pair_server(get_key_pair).
-
-get_new_key_pair() ->
- call_key_pair_server(get_new_key_pair).
-
-call_key_pair_server(Request) ->
- Pid = whereis(?MODULE),
- Ref = erlang:monitor(process, Pid),
- Pid ! {self(), Ref, Request},
- receive
- {Ref, Reply} ->
- erlang:demonitor(Ref, [flush]),
- Reply;
- {'DOWN', Ref, process, Pid, Reason} ->
- error(Reason)
- end.
-
-compute_shared_secret(
- #key_pair{
- type = PublicKeyType,
- params = PublicKeyParams,
- private = PrivKey}, PubKey) ->
- %%
- crypto:compute_key(PublicKeyType, PubKey, PrivKey, PublicKeyParams).
-
-%% -------------------------------------------------------------------------
-%% Erlang distribution plugin structure explained to myself
-%% -------
-%% These are the processes involved in the distribution:
-%% * net_kernel
-%% * The Acceptor
-%% * The Controller | Handshaker | Ticker
-%% * The DistCtrl process that may be split into:
-%% + The Output controller
-%% + The Input controller
-%% For the regular inet_tcp_dist distribution module, DistCtrl
-%% is not one or two processes, but one port - a gen_tcp socket
-%%
-%% When the VM is started with the argument "-proto_dist inet_crypto"
-%% net_kernel registers the module inet_crypto_dist acli,oams distribution
-%% module. net_kernel calls listen/1 to create a listen socket
-%% and then accept/1 with the listen socket as argument to spawn
-%% the Acceptor process, which is linked to net_kernel. Apparently
-%% the listen socket is owned by net_kernel - I wonder if it could
-%% be owned by the Acceptor process instead...
-%%
-%% The Acceptor process calls blocking accept on the listen socket
-%% and when an incoming socket is returned it spawns the DistCtrl
-%% process a linked to the Acceptor. The ownership of the accepted
-%% socket is transferred to the DistCtrl process.
-%% A message is sent to net_kernel to inform it that an incoming
-%% connection has appeared and the Acceptor awaits a reply from net_kernel.
-%%
-%% net_kernel then calls accept_connection/5 to spawn the Controller |
-%% Handshaker | Ticker process that is linked to net_kernel.
-%% The Controller then awaits a message from the Acceptor process.
-%%
-%% When net_kernel has spawned the Controller it replies with a message
-%% to the Acceptor that then calls DistCtrl to changes its links
-%% so DistCtrl ends up linked to the Controller and not to the Acceptor.
-%% The Acceptor then sends a message to the Controller. The Controller
-%% then changes role into the Handshaker creates a #hs_data{} record
-%% and calls dist_util:handshake_other_started/1. After this
-%% the Acceptor goes back into a blocking accept on the listen socket.
-%%
-%% For the regular distribution inet_tcp_dist DistCtrl is a gen_tcp socket
-%% and when it is a process it also acts as a socket. The #hs_data{}
-%% record used by dist_util presents a set of funs that are used
-%% by dist_util to perform the distribution handshake. These funs
-%% make sure to transfer the handshake messages through the DistCtrl
-%% "socket".
-%%
-%% When the handshake is finished a fun for this purpose in #hs_data{}
-%% is called, which tells DistCtrl that it does not need to be prepared
-%% for any more #hs_data{} handshake calls. The DistCtrl process in this
-%% module then spawns the Input controller process that gets ownership
-%% of the connection's gen_tcp socket and changes into {active, N} mode
-%% so now it gets all incoming traffic and delivers that to the VM.
-%% The original DistCtrl process changes role into the Output controller
-%% process and starts asking the VM for outbound messages and transfers
-%% them on the connection socket.
-%%
-%% The Handshaker now changes into the Ticker role, and uses only two
-%% functions in the #hs_data{} record; one to get socket statistics
-%% and one to send a tick. None of these may block for any reason
-%% in particular not for a congested socket since that would destroy
-%% connection supervision.
-%%
-%%
-%% For an connection net_kernel calls setup/5 which spawns the
-%% Controller process as linked to net_kernel. This Controller process
-%% connects to the other node's listen socket and when that is successful
-%% spawns the DistCtrl process as linked to the controller and transfers
-%% socket ownership to it.
-%%
-%% Then the Controller creates the #hs_data{} record and calls
-%% dist_util:handshake_we_started/1 which changes the process role
-%% into Handshaker.
-%%
-%% When the distribution handshake is finished the procedure is just
-%% as for an incoming connection above.
-%%
-%%
-%% To sum it up.
-%%
-%% There is an Acceptor process that is linked to net_kernel and
-%% informs it when new connections arrive.
-%%
-%% net_kernel spawns Controllers for incoming and for outgoing connections.
-%% these Controllers use the DistCtrl processes to do distribution
-%% handshake and after that becomes Tickers that supervise the connection.
-%%
-%% The Controller | Handshaker | Ticker is linked to net_kernel, and to
-%% DistCtrl, one or both. If any of these connection processes would die
-%% all others should be killed by the links. Therefore none of them may
-%% terminate with reason 'normal'.
-%% -------------------------------------------------------------------------
-
--compile({inline, [socket_options/0]}).
-socket_options() ->
- [binary, {active, false}, {packet, 2}, {nodelay, true},
- {sndbuf, ?BUFFER_SIZE}, {recbuf, ?BUFFER_SIZE},
- {buffer, ?BUFFER_SIZE}].
-
-%% -------------------------------------------------------------------------
-%% select/1 is called by net_kernel to ask if this distribution protocol
-%% is willing to handle Node
-%%
-
-select(Node) ->
- gen_select(Node, ?DRIVER).
-
-gen_select(Node, Driver) ->
- case dist_util:split_node(Node) of
- {node, _, Host} ->
- case Driver:getaddr(Host) of
- {ok, _} -> true;
- _ -> false
- end;
- _ ->
- false
- end.
-
-%% -------------------------------------------------------------------------
-
-is_node_name(Node) ->
- dist_util:is_node_name(Node).
-
-%% -------------------------------------------------------------------------
-%% Called by net_kernel to create a listen socket for this
-%% distribution protocol. This listen socket is used by
-%% the Acceptor process.
-%%
-
-listen(Name) ->
- gen_listen(Name, ?DRIVER).
-
-gen_listen(Name, Driver) ->
- {ok, Host} = inet:gethostname(),
- case inet_tcp_dist:gen_listen(Driver, Name, Host) of
- {ok, {Socket, Address, Creation}} ->
- inet:setopts(Socket, socket_options()),
- {ok,
- {Socket, Address#net_address{protocol = ?DIST_PROTO}, Creation}};
- Other ->
- Other
- end.
-
-%% -------------------------------------------------------------------------
-%% Called by net_kernel to spawn the Acceptor process that awaits
-%% new connection in a blocking accept and informs net_kernel
-%% when a new connection has appeared, and starts the DistCtrl
-%% "socket" process for the connection.
-%%
-
-accept(Listen) ->
- gen_accept(Listen, ?DRIVER).
-
-gen_accept(Listen, Driver) ->
- NetKernel = self(),
- %%
- %% Spawn Acceptor process
- %%
- monitor_dist_proc(
- acceptor,
- spawn_opt(
- fun () ->
- start_key_pair_server(),
- accept_loop(Listen, Driver, NetKernel)
- end,
- [link, {priority, max}])).
-
-accept_loop(Listen, Driver, NetKernel) ->
- case Driver:accept(trace(Listen)) of
- {ok, Socket} ->
- wait_for_code_server(),
- Timeout = net_kernel:connecttime(),
- DistCtrl = start_dist_ctrl(trace(Socket), Timeout),
- %% DistCtrl is a "socket"
- NetKernel !
- trace({accept,
- self(), DistCtrl, Driver:family(), ?DIST_PROTO}),
- receive
- {NetKernel, controller, Controller} ->
- call_dist_ctrl(DistCtrl, {controller, Controller, self()}),
- Controller ! {self(), controller, Socket};
- {NetKernel, unsupported_protocol} ->
- exit(unsupported_protocol)
- end,
- accept_loop(Listen, Driver, NetKernel);
- AcceptError ->
- exit({accept, AcceptError})
- end.
-
-wait_for_code_server() ->
- %% This is an ugly hack. Starting encryption on a connection
- %% requires the crypto module to be loaded. Loading the crypto
- %% module triggers its on_load function, which calls
- %% code:priv_dir/1 to find the directory where its NIF library is.
- %% However, distribution is started earlier than the code server,
- %% so the code server is not necessarily started yet, and
- %% code:priv_dir/1 might fail because of that, if we receive
- %% an incoming connection on the distribution port early enough.
- %%
- %% If the on_load function of a module fails, the module is
- %% unloaded, and the function call that triggered loading it fails
- %% with 'undef', which is rather confusing.
- %%
- %% So let's avoid that by waiting for the code server to start.
- %%
- case whereis(code_server) of
- undefined ->
- timer:sleep(10),
- wait_for_code_server();
- Pid when is_pid(Pid) ->
- ok
- end.
-
-%% -------------------------------------------------------------------------
-%% Called by net_kernel when a new connection has appeared, to spawn
-%% a Controller process that performs the handshake with the new node,
-%% and then becomes the Ticker connection supervisor.
-%% -------------------------------------------------------------------------
-
-accept_connection(Acceptor, DistCtrl, MyNode, Allowed, SetupTime) ->
- gen_accept_connection(
- Acceptor, DistCtrl, MyNode, Allowed, SetupTime, ?DRIVER).
-
-gen_accept_connection(
- Acceptor, DistCtrl, MyNode, Allowed, SetupTime, Driver) ->
- NetKernel = self(),
- %%
- %% Spawn Controller/handshaker/ticker process
- %%
- monitor_dist_proc(
- accept_controller,
- spawn_opt(
- fun() ->
- do_accept(
- Acceptor, DistCtrl,
- trace(MyNode), Allowed, SetupTime, Driver, NetKernel)
- end,
- [link, {priority, max}])).
-
-do_accept(
- Acceptor, DistCtrl, MyNode, Allowed, SetupTime, Driver, NetKernel) ->
- %%
- receive
- {Acceptor, controller, Socket} ->
- Timer = dist_util:start_timer(SetupTime),
- HSData =
- hs_data_common(
- NetKernel, MyNode, DistCtrl, Timer,
- Socket, Driver:family()),
- HSData_1 =
- HSData#hs_data{
- this_node = MyNode,
- this_flags = 0,
- allowed = Allowed},
- dist_util:handshake_other_started(trace(HSData_1))
- end.
-
-%% -------------------------------------------------------------------------
-%% Called by net_kernel to spawn a Controller process that sets up
-%% a new connection to another Erlang node, performs the handshake
-%% with the other it, and then becomes the Ticker process
-%% that supervises the connection.
-%% -------------------------------------------------------------------------
-
-setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- gen_setup(Node, Type, MyNode, LongOrShortNames, SetupTime, ?DRIVER).
-
-gen_setup(Node, Type, MyNode, LongOrShortNames, SetupTime, Driver) ->
- NetKernel = self(),
- %%
- %% Spawn Controller/handshaker/ticker process
- %%
- monitor_dist_proc(
- setup_controller,
- spawn_opt(
- setup_fun(
- Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel),
- [link, {priority, max}])).
-
--spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()).
-setup_fun(
- Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) ->
- %%
- fun() ->
- do_setup(
- trace(Node), Type, MyNode, LongOrShortNames, SetupTime,
- Driver, NetKernel)
- end.
-
--spec do_setup(_,_,_,_,_,_,_) -> no_return().
-do_setup(
- Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) ->
- %%
- {Name, Address} = split_node(Driver, Node, LongOrShortNames),
- ErlEpmd = net_kernel:epmd_module(),
- {ARMod, ARFun} = get_address_resolver(ErlEpmd, Driver),
- Timer = trace(dist_util:start_timer(SetupTime)),
- case ARMod:ARFun(Name, Address, Driver:family()) of
- {ok, Ip, TcpPort, Version} ->
- do_setup_connect(
- Node, Type, MyNode, Timer, Driver, NetKernel,
- Ip, TcpPort, Version);
- {ok, Ip} ->
- case ErlEpmd:port_please(Name, Ip) of
- {port, TcpPort, Version} ->
- do_setup_connect(
- Node, Type, MyNode, Timer, Driver, NetKernel,
- Ip, TcpPort, trace(Version));
- Other ->
- _ = trace(
- {ErlEpmd, port_please, [Name, Ip], Other}),
- ?shutdown(Node)
- end;
- Other ->
- _ = trace(
- {ARMod, ARFun, [Name, Address, Driver:family()],
- Other}),
- ?shutdown(Node)
- end.
-
--spec do_setup_connect(_,_,_,_,_,_,_,_,_) -> no_return().
-
-do_setup_connect(
- Node, Type, MyNode, Timer, Driver, NetKernel,
- Ip, TcpPort, Version) ->
- dist_util:reset_timer(Timer),
- ConnectOpts = trace(connect_options(socket_options())),
- case Driver:connect(Ip, TcpPort, ConnectOpts) of
- {ok, Socket} ->
- DistCtrl =
- try start_dist_ctrl(Socket, net_kernel:connecttime())
- catch error : {dist_ctrl, _} = DistCtrlError ->
- _ = trace(DistCtrlError),
- ?shutdown(Node)
- end,
- %% DistCtrl is a "socket"
- HSData =
- hs_data_common(
- NetKernel, MyNode, DistCtrl, Timer,
- Socket, Driver:family()),
- HSData_1 =
- HSData#hs_data{
- other_node = Node,
- this_flags = 0,
- other_version = Version,
- request_type = Type},
- dist_util:handshake_we_started(trace(HSData_1));
- ConnectError ->
- _ = trace(
- {Driver, connect, [Ip, TcpPort, ConnectOpts],
- ConnectError}),
- ?shutdown(Node)
- end.
-
-%% -------------------------------------------------------------------------
-%% close/1 is only called by net_kernel on the socket returned by listen/1.
-
-close(Socket) ->
- gen_close(Socket, ?DRIVER).
-
-gen_close(Socket, Driver) ->
- Driver:close(trace(Socket)).
-
-%% -------------------------------------------------------------------------
-
-
-hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) ->
- %% Field 'socket' below is set to DistCtrl, which makes
- %% the distribution handshake process (ticker) call
- %% the funs below with DistCtrl as the S argument.
- %% So, S =:= DistCtrl below...
- #hs_data{
- kernel_pid = NetKernel,
- this_node = MyNode,
- socket = DistCtrl,
- timer = Timer,
- %%
- f_send = % -> ok | {error, closed}=>?shutdown()
- fun (S, Packet) when S =:= DistCtrl ->
- try call_dist_ctrl(S, {send, Packet})
- catch error : {dist_ctrl, Reason} ->
- _ = trace(Reason),
- {error, closed}
- end
- end,
- f_recv = % -> {ok, List} | Other=>?shutdown()
- fun (S, 0, infinity) when S =:= DistCtrl ->
- try call_dist_ctrl(S, recv) of
- {ok, Bin} when is_binary(Bin) ->
- {ok, binary_to_list(Bin)};
- Error ->
- Error
- catch error : {dist_ctrl, Reason} ->
- {error, trace(Reason)}
- end
- end,
- f_setopts_pre_nodeup =
- fun (S) when S =:= DistCtrl ->
- ok
- end,
- f_setopts_post_nodeup =
- fun (S) when S =:= DistCtrl ->
- ok
- end,
- f_getll =
- fun (S) when S =:= DistCtrl ->
- {ok, S} %% DistCtrl is the distribution port
- end,
- f_address = % -> #net_address{} | ?shutdown()
- fun (S, Node) when S =:= DistCtrl ->
- try call_dist_ctrl(S, peername) of
- {ok, Address} ->
- case dist_util:split_node(Node) of
- {node, _, Host} ->
- #net_address{
- address = Address,
- host = Host,
- protocol = ?DIST_PROTO,
- family = Family};
- _ ->
- ?shutdown(Node)
- end;
- Error ->
- _ = trace(Error),
- ?shutdown(Node)
- catch error : {dist_ctrl, Reason} ->
- _ = trace(Reason),
- ?shutdown(Node)
- end
- end,
- f_handshake_complete = % -> ok | ?shutdown()
- fun (S, Node, DistHandle) when S =:= DistCtrl ->
- try call_dist_ctrl(S, {handshake_complete, DistHandle})
- catch error : {dist_ctrl, Reason} ->
- _ = trace(Reason),
- ?shutdown(Node)
- end
- end,
- %%
- %% mf_tick/1, mf_getstat/1, mf_setopts/2 and mf_getopts/2
- %% are called by the ticker any time after f_handshake_complete/3
- %% so they may not block the caller even for congested socket
- mf_tick =
- fun (S) when S =:= DistCtrl ->
- S ! dist_tick
- end,
- mf_getstat = % -> {ok, RecvCnt, SendCnt, SendPend} | Other=>ignore_it
- fun (S) when S =:= DistCtrl ->
- case
- inet:getstat(Socket, [recv_cnt, send_cnt, send_pend])
- of
- {ok, Stat} ->
- split_stat(Stat, 0, 0, 0);
- Error ->
- trace(Error)
- end
- end,
- mf_setopts =
- fun (S, Opts) when S =:= DistCtrl ->
- inet:setopts(Socket, setopts_filter(Opts))
- end,
- mf_getopts =
- fun (S, Opts) when S =:= DistCtrl ->
- inet:getopts(Socket, Opts)
- end}.
-
-setopts_filter(Opts) ->
- [Opt ||
- Opt <- Opts,
- case Opt of
- {K, _} when K =:= active; K =:= deliver; K =:= packet -> false;
- K when K =:= list; K =:= binary -> false;
- K when K =:= inet; K =:= inet6 -> false;
- _ -> true
- end].
-
-split_stat([{recv_cnt, R}|Stat], _, W, P) ->
- split_stat(Stat, R, W, P);
-split_stat([{send_cnt, W}|Stat], R, _, P) ->
- split_stat(Stat, R, W, P);
-split_stat([{send_pend, P}|Stat], R, W, _) ->
- split_stat(Stat, R, W, P);
-split_stat([], R, W, P) ->
- {ok, R, W, P}.
-
-%% ------------------------------------------------------------
-%% Determine if EPMD module supports address resolving. Default
-%% is to use inet_tcp:getaddr/2.
-%% ------------------------------------------------------------
-get_address_resolver(EpmdModule, _Driver) ->
- case erlang:function_exported(EpmdModule, address_please, 3) of
- true -> {EpmdModule, address_please};
- _ -> {erl_epmd, address_please}
- end.
-
-
-%% If Node is illegal terminate the connection setup!!
-split_node(Driver, Node, LongOrShortNames) ->
- case dist_util:split_node(Node) of
- {node, Name, Host} ->
- check_node(Driver, Node, Name, Host, LongOrShortNames);
- {host, _} ->
- error_logger:error_msg(
- "** Nodename ~p illegal, no '@' character **~n",
- [Node]),
- ?shutdown2(Node, trace({illegal_node_n@me, Node}));
- _ ->
- error_logger:error_msg(
- "** Nodename ~p illegal **~n", [Node]),
- ?shutdown2(Node, trace({illegal_node_name, Node}))
- end.
-
-check_node(Driver, Node, Name, Host, LongOrShortNames) ->
- case string:split(Host, ".", all) of
- [_] when LongOrShortNames =:= longnames ->
- case Driver:parse_address(Host) of
- {ok, _} ->
- {Name, Host};
- _ ->
- error_logger:error_msg(
- "** System running to use "
- "fully qualified hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown2(Node, trace({not_longnames, Host}))
- end;
- [_, _|_] when LongOrShortNames =:= shortnames ->
- error_logger:error_msg(
- "** System NOT running to use "
- "fully qualified hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown2(Node, trace({not_shortnames, Host}));
- _ ->
- {Name, Host}
- end.
-
-%% -------------------------------------------------------------------------
-
-connect_options(Opts) ->
- case application:get_env(kernel, inet_dist_connect_options) of
- {ok, ConnectOpts} ->
- Opts ++ setopts_filter(ConnectOpts);
- _ ->
- Opts
- end.
-
-%% we may not always want the nodelay behaviour
-%% for performance reasons
-nodelay() ->
- case application:get_env(kernel, dist_nodelay) of
- undefined ->
- {nodelay, true};
- {ok, true} ->
- {nodelay, true};
- {ok, false} ->
- {nodelay, false};
- _ ->
- {nodelay, true}
- end.
-
-%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-%% The DistCtrl process(es).
-%%
-%% At net_kernel handshake_complete spawns off the input controller that
-%% takes over the socket ownership, and itself becomes the output controller
-%%
-%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%% XXX Missing to "productified":
-%%% * Cryptoanalysis by experts, this is crypto amateur work.
-%%% * Is it useful over inet_tls_dist; i.e to not have to bother
-%%% with certificates but instead manage a secret cluster cookie?
-%%% * An application to belong to (kernel)
-%%% * Restart and/or code reload policy (not needed in kernel)
-%%% * Fitting into the epmd/Erlang distro protocol version framework
-%%% (something needs to be created for multiple protocols, epmd,
-%%% multiple address families, fallback to previous version, etc)
-
-
-%% Debug client and server
-
-test_server() ->
- {ok, Listen} = gen_tcp:listen(0, socket_options()),
- {ok, Port} = inet:port(Listen),
- io:format(?MODULE_STRING":test_client(~w).~n", [Port]),
- {ok, Socket} = gen_tcp:accept(Listen),
- test(Socket).
-
-test_client(Port) ->
- {ok, Socket} = gen_tcp:connect(localhost, Port, socket_options()),
- test(Socket).
-
-test(Socket) ->
- start_dist_ctrl(Socket, 10000).
-
-%% -------------------------------------------------------------------------
-
-start_dist_ctrl(Socket, Timeout) ->
- Secret = atom_to_binary(auth:get_cookie(), latin1),
- Controller = self(),
- Server =
- monitor_dist_proc(
- output_handler,
- spawn_opt(
- fun () ->
- receive
- {?MODULE, From, start} ->
- {SendParams, RecvParams} =
- init(Socket, Secret),
- reply(From, self()),
- handshake(SendParams, 1, RecvParams, 1, Controller)
- end
- end,
- [link,
- {priority, max},
- {message_queue_data, off_heap},
- {fullsweep_after, 0}])),
- ok = gen_tcp:controlling_process(Socket, Server),
- call_dist_ctrl(Server, start, Timeout).
-
-
-call_dist_ctrl(Server, Msg) ->
- call_dist_ctrl(Server, Msg, infinity).
-%%
-call_dist_ctrl(Server, Msg, Timeout) ->
- Ref = erlang:monitor(process, Server),
- Server ! {?MODULE, {Ref, self()}, Msg},
- receive
- {Ref, Res} ->
- erlang:demonitor(Ref, [flush]),
- Res;
- {'DOWN', Ref, process, Server, Reason} ->
- error({dist_ctrl, Reason})
- after Timeout -> % Timeout < infinity is only used by start_dist_ctrl/2
- receive
- {'DOWN', Ref, process, Server, _} ->
- receive {Ref, _} -> ok after 0 -> ok end,
- error({dist_ctrl, timeout})
- %% Server will be killed by link
- end
- end.
-
-reply({Ref, Pid}, Msg) ->
- Pid ! {Ref, Msg},
- ok.
-
-%% -------------------------------------------------------------------------
-
--define(TCP_ACTIVE, 16).
--define(CHUNK_SIZE, (?PACKET_SIZE - 512)).
-
--define(HANDSHAKE_CHUNK, 1).
--define(DATA_CHUNK, 2).
--define(TICK_CHUNK, 3).
--define(REKEY_CHUNK, 4).
-
-%% -------------------------------------------------------------------------
-%% Crypto strategy
-%% -------
-%% The crypto strategy is as simple as possible to get an encrypted
-%% connection as benchmark reference. It is geared around AEAD
-%% ciphers in particular AES-GCM.
-%%
-%% The init message and the start message must fit in the TCP buffers
-%% since both sides start with sending the init message, waits
-%% for the other end's init message, sends the start message
-%% and waits for the other end's start message. So if the send
-%% blocks we have a deadlock.
-%%
-%% The init + start sequence tries to implement Password Encrypted
-%% Key Exchange using a node public/private key pair and the
-%% shared secret (the Cookie) to create session encryption keys
-%% that can not be re-created if the shared secret is compromized,
-%% which should create forward secrecy. You need both nodes'
-%% key pairs and the shared secret to decrypt the traffic
-%% between the nodes.
-%%
-%% All exchanged messages uses {packet, 2} i.e 16 bit size header.
-%%
-%% The init message contains a random number and encrypted: the public key
-%% and two random numbers. The encryption is done with Key and IV hashed
-%% from the unencrypted random number and the shared secret.
-%%
-%% The other node's public key is used with the own node's private
-%% key to create a shared key that is hashed with one of the encrypted
-%% random numbers from each side to create Key and IV for the session.
-%%
-%% The start message contains the two encrypted random numbers
-%% this time encrypted with the session keys for verification
-%% by the other side, plus the rekey count. The rekey count
-%% is just there to get an early check for if the other side's
-%% maximum rekey count is acceptable, it is just an embryo
-%% of some better check. Any side may rekey earlier but if the
-%% rekey count is exceeded the connection fails. Rekey is also
-%% triggered by a timer.
-%%
-%% Subsequent encrypted messages has the sequence number and the length
-%% of the message as AAD data, and an incrementing IV. These messages
-%% has got a message type that differentiates data from ticks and rekeys.
-%% Ticks have a random size in an attempt to make them less obvious to spot.
-%%
-%% Rekeying is done by the sender that creates a new key pair and
-%% a new shared secret from the other end's public key and with
-%% this and the current key and iv hashes a new key and iv.
-%% The new public key is sent to the other end that uses it
-%% and its old private key to create the same new shared
-%% secret and from that a new key and iv.
-%% So the receiver keeps its private key, and the sender keeps
-%% the receivers public key for the connection's life time.
-%% While the sender generates a new key pair at every rekey,
-%% which changes the shared secret at every rekey.
-%%
-%% The only reaction to errors is to crash noisily (?) which will bring
-%% down the connection and hopefully produce something useful
-%% in the local log, but all the other end sees is a closed connection.
-%% -------------------------------------------------------------------------
-
-init(Socket, Secret) ->
- #key_pair{public = PubKey} = KeyPair = get_key_pair(),
- Params = params(Socket),
- {R2, R3, Msg} = init_msg(Params, PubKey, Secret),
- ok = gen_tcp:send(Socket, Msg),
- init_recv(Params, Secret, KeyPair, R2, R3).
-
-init_recv(
- #params{socket = Socket, iv = IVLen} = Params, Secret, KeyPair, R2, R3) ->
- %%
- {ok, InitMsg} = gen_tcp:recv(Socket, 0),
- IVSaltLen = IVLen - 6,
- try
- case init_msg(Params, Secret, KeyPair, R2, R3, InitMsg) of
- {#params{iv = <<IV2ASalt:IVSaltLen/binary, IV2ANo:48>>} =
- SendParams,
- RecvParams, SendStartMsg} ->
- ok = gen_tcp:send(Socket, SendStartMsg),
- {ok, RecvStartMsg} = gen_tcp:recv(Socket, 0),
- #params{
- iv = <<IV2BSalt:IVSaltLen/binary, IV2BNo:48>>} =
- RecvParams_1 =
- start_msg(RecvParams, R2, R3, RecvStartMsg),
- {SendParams#params{iv = {IV2ASalt, IV2ANo}},
- RecvParams_1#params{iv = {IV2BSalt, IV2BNo}}}
- end
- catch
- Class : Reason : Stacktrace when Class =:= error ->
- error_logger:info_report(
- [init_recv_exception,
- {class, Class},
- {reason, Reason},
- {stacktrace, Stacktrace}]),
- _ = trace({Reason, Stacktrace}),
- exit(connection_closed)
- end.
-
-
-
-init_msg(
- #params{
- hmac_algorithm = HmacAlgo,
- aead_cipher = AeadCipher,
- key = KeyLen,
- iv = IVLen,
- tag_len = TagLen}, PubKeyA, Secret) ->
- %%
- RLen = KeyLen + IVLen,
- <<R1A:RLen/binary, R2A:RLen/binary, R3A:RLen/binary>> =
- crypto:strong_rand_bytes(3 * RLen),
- {Key1A, IV1A} = hmac_key_iv(HmacAlgo, R1A, Secret, KeyLen, IVLen),
- Plaintext = [R2A, R3A, PubKeyA],
- MsgLen = byte_size(R1A) + TagLen + iolist_size(Plaintext),
- AAD = [<<MsgLen:32>>, R1A],
- {Ciphertext, Tag} =
- crypto:crypto_one_time_aead(
- AeadCipher, Key1A, IV1A, Plaintext, AAD, TagLen, true),
- Msg = [R1A, Tag, Ciphertext],
- {R2A, R3A, Msg}.
-%%
-init_msg(
- #params{
- hmac_algorithm = HmacAlgo,
- aead_cipher = AeadCipher,
- key = KeyLen,
- iv = IVLen,
- tag_len = TagLen,
- rekey_count = RekeyCount} = Params,
- Secret, KeyPair, R2A, R3A, Msg) ->
- %%
- RLen = KeyLen + IVLen,
- case Msg of
- <<R1B:RLen/binary, Tag:TagLen/binary, Ciphertext/binary>> ->
- {Key1B, IV1B} = hmac_key_iv(HmacAlgo, R1B, Secret, KeyLen, IVLen),
- MsgLen = byte_size(Msg),
- AAD = [<<MsgLen:32>>, R1B],
- case
- crypto:crypto_one_time_aead(
- AeadCipher, Key1B, IV1B, Ciphertext, AAD, Tag, false)
- of
- <<R2B:RLen/binary, R3B:RLen/binary, PubKeyB/binary>> ->
- SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
- %%
- {Key2A, IV2A} =
- hmac_key_iv(
- HmacAlgo, SharedSecret, [R2A, R3B], KeyLen, IVLen),
- SendParams =
- Params#params{
- rekey_key = PubKeyB,
- key = Key2A, iv = IV2A},
- %%
- StartCleartext = [R2B, R3B, <<RekeyCount:32>>],
- StartMsgLen = TagLen + iolist_size(StartCleartext),
- StartAAD = <<StartMsgLen:32>>,
- {StartCiphertext, StartTag} =
- crypto:crypto_one_time_aead(
- AeadCipher, Key2A, IV2A,
- StartCleartext, StartAAD, TagLen, true),
- StartMsg = [StartTag, StartCiphertext],
- %%
- {Key2B, IV2B} =
- hmac_key_iv(
- HmacAlgo, SharedSecret, [R2B, R3A], KeyLen, IVLen),
- RecvParams =
- Params#params{
- rekey_key = KeyPair,
- key = Key2B, iv = IV2B},
- %%
- {SendParams, RecvParams, StartMsg}
- end
- end.
-
-start_msg(
- #params{
- aead_cipher = AeadCipher,
- key = Key2B,
- iv = IV2B,
- tag_len = TagLen,
- rekey_count = RekeyCountA} = RecvParams, R2A, R3A, Msg) ->
- %%
- case Msg of
- <<Tag:TagLen/binary, Ciphertext/binary>> ->
- KeyLen = byte_size(Key2B),
- IVLen = byte_size(IV2B),
- RLen = KeyLen + IVLen,
- MsgLen = byte_size(Msg),
- AAD = <<MsgLen:32>>,
- case
- crypto:crypto_one_time_aead(
- AeadCipher, Key2B, IV2B, Ciphertext, AAD, Tag, false)
- of
- <<R2A:RLen/binary, R3A:RLen/binary, RekeyCountB:32>>
- when RekeyCountA =< (RekeyCountB bsl 2),
- RekeyCountB =< (RekeyCountA bsl 2) ->
- RecvParams#params{rekey_count = RekeyCountB}
- end
- end.
-
-hmac_key_iv(HmacAlgo, MacKey, Data, KeyLen, IVLen) ->
- <<Key:KeyLen/binary, IV:IVLen/binary>> =
- crypto:macN(hmac, HmacAlgo, MacKey, Data, KeyLen + IVLen),
- {Key, IV}.
-
-%% -------------------------------------------------------------------------
-%% net_kernel distribution handshake in progress
-%%
-
-handshake(
- SendParams, SendSeq,
- #params{socket = Socket} = RecvParams, RecvSeq, Controller) ->
- receive
- {?MODULE, From, {controller, Controller_1, Parent}} ->
- Result = link(Controller_1),
- true = unlink(Parent),
- reply(From, Result),
- handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller_1);
- {?MODULE, From, {handshake_complete, DistHandle}} ->
- InputHandler =
- monitor_dist_proc(
- input_handler,
- spawn_opt(
- fun () ->
- link(Controller),
- receive
- DistHandle ->
- input_handler(
- RecvParams, RecvSeq, DistHandle)
- end
- end,
- [link,
- {priority, normal},
- {message_queue_data, off_heap},
- {fullsweep_after, 0}])),
- _ = monitor(process, InputHandler), % For the benchmark test
- ok = gen_tcp:controlling_process(Socket, InputHandler),
- false = erlang:dist_ctrl_set_opt(DistHandle, get_size, true),
- ok = erlang:dist_ctrl_input_handler(DistHandle, InputHandler),
- InputHandler ! DistHandle,
- reply(From, ok),
- process_flag(priority, normal),
- output_handler(SendParams, SendSeq, DistHandle);
- %%
- {?MODULE, From, {send, Data}} ->
- {SendParams_1, SendSeq_1, Result} =
- encrypt_and_send_chunk(
- SendParams, SendSeq,
- [?HANDSHAKE_CHUNK, Data], 1 + iolist_size(Data)),
- if
- Result =:= ok ->
- reply(From, ok),
- handshake(
- SendParams_1, SendSeq_1, RecvParams, RecvSeq,
- Controller);
- true ->
- reply(From, {error, closed}),
- death_row({send, trace(Result)})
- end;
- {?MODULE, From, recv} ->
- {RecvParams_1, RecvSeq_1, Result} =
- recv_and_decrypt_chunk(RecvParams, RecvSeq),
- case Result of
- {ok, _} ->
- reply(From, Result),
- handshake(
- SendParams, SendSeq, RecvParams_1, RecvSeq_1,
- Controller);
- {error, _} ->
- reply(From, Result),
- death_row({recv, trace(Result)})
- end;
- {?MODULE, From, peername} ->
- reply(From, inet:peername(Socket)),
- handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller);
- %%
- _Alien ->
- handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller)
- end.
-
-recv_and_decrypt_chunk(#params{socket = Socket} = RecvParams, RecvSeq) ->
- case gen_tcp:recv(Socket, 0) of
- {ok, Chunk} ->
- case decrypt_chunk(RecvParams, RecvSeq, Chunk) of
- <<?HANDSHAKE_CHUNK, Cleartext/binary>> ->
- {RecvParams, RecvSeq + 1, {ok, Cleartext}};
- UnknownChunk when is_binary(UnknownChunk) ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,unknown_chunk}]),
- {RecvParams, RecvSeq + 1, {error, unknown_chunk}};
- #params{} = RecvParams_1 ->
- recv_and_decrypt_chunk(RecvParams_1, 0);
- error ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,decrypt_error}]),
- {RecvParams, RecvSeq, {error, decrypt_error}}
- end;
- Error ->
- {RecvParams, RecvSeq, Error}
- end.
-
-%% -------------------------------------------------------------------------
-%% Output handler process
-%%
-%% Await an event about what to do; fetch dist data from the VM,
-%% send a dist tick, or rekey outbound encryption parameters.
-%%
-%% In case we are overloaded and could get many accumulated
-%% dist_data or dist_tick messages; make sure to flush all of them
-%% before proceeding with what to do. But, do not use selective
-%% receive since that does not perform well when there are
-%% many messages in the process mailbox.
-
-%% Entry function
-output_handler(Params, Seq, DistHandle) ->
- try
- _ = crypto:rand_seed_alg(crypto_cache),
- erlang:dist_ctrl_get_data_notification(DistHandle),
- output_handler(
- Params#params{
- dist_handle = DistHandle,
- rekey_msg = start_rekey_timer(Params#params.rekey_time)},
- Seq)
- catch
- Class : Reason : Stacktrace ->
- error_logger:info_report(
- [output_handler_exception,
- {class, Class},
- {reason, Reason},
- {stacktrace, Stacktrace}]),
- erlang:raise(Class, Reason, Stacktrace)
- end.
-
-%% Loop top
-%%
-%% State: lurking until any interesting message
-output_handler(Params, Seq) ->
- receive
- Msg ->
- case Msg of
- dist_data ->
- output_handler_data(Params, Seq);
- dist_tick ->
- output_handler_tick(Params, Seq);
- _ when Msg =:= Params#params.rekey_msg ->
- output_handler_rekey(Params, Seq);
- _ ->
- %% Ignore
- _ = trace(Msg),
- output_handler(Params, Seq)
- end
- end.
-
-%% State: we have received at least one dist_data message
-output_handler_data(Params, Seq) ->
- receive
- Msg ->
- case Msg of
- dist_data ->
- output_handler_data(Params, Seq);
- dist_tick ->
- output_handler_data(Params, Seq);
- _ when Msg =:= Params#params.rekey_msg ->
- output_handler_rekey(Params, Seq);
- _ ->
- %% Ignore
- _ = trace(Msg),
- output_handler_data(Params, Seq)
- end
- after 0 ->
- {Params_1, Seq_1} = output_handler_xfer(Params, Seq),
- erlang:dist_ctrl_get_data_notification(Params#params.dist_handle),
- output_handler(Params_1, Seq_1)
- end.
-
-%% State: we have received at least one dist_tick but no dist_data message
-output_handler_tick(Params, Seq) ->
- receive
- Msg ->
- case Msg of
- dist_data ->
- output_handler_data(Params, Seq);
- dist_tick ->
- output_handler_tick(Params, Seq);
- _ when Msg =:= Params#params.rekey_msg ->
- output_handler_rekey(Params, Seq);
- _ ->
- %% Ignore
- _ = trace(Msg),
- output_handler_tick(Params, Seq)
- end
- after 0 ->
- TickSize = 7 + rand:uniform(56),
- TickData = binary:copy(<<0>>, TickSize),
- {Params_1, Seq_1, Result} =
- encrypt_and_send_chunk(
- Params, Seq, [?TICK_CHUNK, TickData], 1 + TickSize),
- if
- Result =:= ok ->
- output_handler(Params_1, Seq_1);
- true ->
- death_row({send_tick, trace(Result)})
- end
- end.
-
-output_handler_rekey(Params, Seq) ->
- case encrypt_and_send_rekey_chunk(Params, Seq) of
- #params{} = Params_1 ->
- output_handler(Params_1, 0);
- SendError ->
- death_row({send_rekey, trace(SendError)})
- end.
-
-
-%% Get outbound data from VM; encrypt and send,
-%% until the VM has no more
-%%
-output_handler_xfer(Params, Seq) ->
- output_handler_xfer(Params, Seq, [], 0, []).
-%%
-%% Front,Size,Rear is an Okasaki queue of binaries with total byte Size
-%%
-output_handler_xfer(Params, Seq, Front, Size, Rear)
- when ?CHUNK_SIZE =< Size ->
- %%
- %% We have a full chunk or more
- %% -> collect one chunk or less and send
- output_handler_collect(Params, Seq, Front, Size, Rear);
-output_handler_xfer(Params, Seq, Front, Size, Rear) ->
- %% when Size < ?CHUNK_SIZE ->
- %%
- %% We do not have a full chunk -> try to fetch more from VM
- case erlang:dist_ctrl_get_data(Params#params.dist_handle) of
- none ->
- if
- Size =:= 0 ->
- %% No more data from VM, nothing buffered
- %% -> go back to lurking
- {Params, Seq};
- true ->
- %% The VM had no more -> send what we have
- output_handler_collect(Params, Seq, Front, Size, Rear)
- end;
- {Len,Iov} ->
- output_handler_enq(
- Params, Seq, Front, Size + 4 + Len, [<<Len:32>>|Rear], Iov)
- end.
-
-%% Enqueue VM data while splitting large binaries into ?CHUNK_SIZE
-%%
-output_handler_enq(Params, Seq, Front, Size, Rear, []) ->
- output_handler_xfer(Params, Seq, Front, Size, Rear);
-output_handler_enq(Params, Seq, Front, Size, Rear, [Bin|Iov]) ->
- output_handler_enq(Params, Seq, Front, Size, Rear, Iov, Bin).
-%%
-output_handler_enq(Params, Seq, Front, Size, Rear, Iov, Bin) ->
- BinSize = byte_size(Bin),
- if
- BinSize =< ?CHUNK_SIZE ->
- output_handler_enq(
- Params, Seq, Front, Size, [Bin|Rear], Iov);
- true ->
- <<Bin1:?CHUNK_SIZE/binary, Bin2/binary>> = Bin,
- output_handler_enq(
- Params, Seq, Front, Size, [Bin1|Rear], Iov, Bin2)
- end.
-
-%% Collect small binaries into chunks of at most ?CHUNK_SIZE
-%%
-output_handler_collect(Params, Seq, [], Zero, []) ->
- 0 = Zero, % Assert
- %% No more enqueued -> try to get more form VM
- output_handler_xfer(Params, Seq);
-output_handler_collect(Params, Seq, Front, Size, Rear) ->
- output_handler_collect(Params, Seq, Front, Size, Rear, [], 0).
-%%
-output_handler_collect(Params, Seq, [], Zero, [], Acc, DataSize) ->
- 0 = Zero, % Assert
- output_handler_chunk(Params, Seq, [], Zero, [], Acc, DataSize);
-output_handler_collect(Params, Seq, [], Size, Rear, Acc, DataSize) ->
- %% Okasaki queue transfer Rear -> Front
- output_handler_collect(
- Params, Seq, lists:reverse(Rear), Size, [], Acc, DataSize);
-output_handler_collect(
- Params, Seq, [Bin|Iov] = Front, Size, Rear, Acc, DataSize) ->
- BinSize = byte_size(Bin),
- DataSize_1 = DataSize + BinSize,
- if
- ?CHUNK_SIZE < DataSize_1 ->
- %% Bin does not fit in chunk -> send Acc
- output_handler_chunk(
- Params, Seq, Front, Size, Rear, Acc, DataSize);
- DataSize_1 < ?CHUNK_SIZE ->
- %% Chunk not full yet -> try to accumulate more
- output_handler_collect(
- Params, Seq, Iov, Size - BinSize, Rear, [Bin|Acc], DataSize_1);
- true -> % DataSize_1 == ?CHUNK_SIZE ->
- %% Optimize one iteration; Bin fits exactly -> accumulate and send
- output_handler_chunk(
- Params, Seq, Iov, Size - BinSize, Rear, [Bin|Acc], DataSize_1)
- end.
-
-%% Encrypt and send a chunk
-%%
-output_handler_chunk(Params, Seq, Front, Size, Rear, Acc, DataSize) ->
- Data = lists:reverse(Acc),
- {Params_1, Seq_1, Result} =
- encrypt_and_send_chunk(Params, Seq, [?DATA_CHUNK|Data], 1 + DataSize),
- if
- Result =:= ok ->
- %% Try to collect another chunk
- output_handler_collect(Params_1, Seq_1, Front, Size, Rear);
- true ->
- death_row({send_chunk, trace(Result)})
- end.
-
-%% -------------------------------------------------------------------------
-%% Input handler process
-%%
-
-%% Entry function
-input_handler(#params{socket = Socket} = Params, Seq, DistHandle) ->
- try
- ok =
- inet:setopts(
- Socket, [{active, ?TCP_ACTIVE}, nodelay()]),
- input_handler(
- Params#params{dist_handle = DistHandle},
- Seq)
- catch
- Class : Reason : Stacktrace ->
- error_logger:info_report(
- [input_handler_exception,
- {class, Class},
- {reason, Reason},
- {stacktrace, Stacktrace}]),
- erlang:raise(Class, Reason, Stacktrace)
- end.
-
-%% Loop top
-input_handler(Params, Seq) ->
- %% Shortcut into the loop
- {Params_1, Seq_1, Data} = input_data(Params, Seq),
- input_handler(Params_1, Seq_1, Data, [], byte_size(Data)).
-%%
-input_handler(Params, Seq, First, Buffer, Size) ->
- %% Size is size of First + Buffer
- case First of
- <<Packet1Size:32, Packet1:Packet1Size/binary,
- Packet2Size:32, Packet2:Packet2Size/binary, Rest/binary>> ->
- DistHandle = Params#params.dist_handle,
- erlang:dist_ctrl_put_data(DistHandle, Packet1),
- erlang:dist_ctrl_put_data(DistHandle, Packet2),
- input_handler(
- Params, Seq, Rest,
- Buffer, Size - (8 + Packet1Size + Packet2Size));
- <<PacketSize:32, Packet:PacketSize/binary, Rest/binary>> ->
- DistHandle = Params#params.dist_handle,
- erlang:dist_ctrl_put_data(DistHandle, Packet),
- input_handler(
- Params, Seq, Rest, Buffer, Size - (4 + PacketSize));
- <<PacketSize:32, PacketStart/binary>> ->
- %% Partial packet in First
- input_handler(
- Params, Seq, PacketStart, Buffer, Size - 4, PacketSize);
- <<Bin/binary>> ->
- %% Partial header in First
- if
- 4 =< Size ->
- %% Complete header in First + Buffer
- {First_1, Buffer_1, PacketSize} =
- input_get_packet_size(Bin, lists:reverse(Buffer)),
- input_handler(
- Params, Seq, First_1, Buffer_1, Size - 4, PacketSize);
- true ->
- %% Incomplete header received so far
- {Params_1, Seq_1, More} = input_data(Params, Seq),
- input_handler(
- Params_1, Seq_1, Bin,
- [More|Buffer], Size + byte_size(More))
- end
- end.
-%%
-input_handler(Params, Seq, PacketStart, Buffer, Size, PacketSize) ->
- %% Size is size of PacketStart + Buffer
- RestSize = Size - PacketSize,
- if
- RestSize < 0 ->
- %% Incomplete packet received so far
- {Params_1, Seq_1, More} = input_data(Params, Seq),
- input_handler(
- Params_1, Seq_1, PacketStart,
- [More|Buffer], Size + byte_size(More), PacketSize);
- 0 < RestSize, Buffer =:= [] ->
- %% Rest data in PacketStart
- <<Packet:PacketSize/binary, Rest/binary>> = PacketStart,
- DistHandle = Params#params.dist_handle,
- erlang:dist_ctrl_put_data(DistHandle, Packet),
- input_handler(Params, Seq, Rest, [], RestSize);
- Buffer =:= [] -> % RestSize == 0
- %% No rest data
- DistHandle = Params#params.dist_handle,
- erlang:dist_ctrl_put_data(DistHandle, PacketStart),
- input_handler(Params, Seq);
- true ->
- %% Split packet from rest data
- LastBin = hd(Buffer),
- <<PacketLast:(byte_size(LastBin) - RestSize)/binary,
- Rest/binary>> = LastBin,
- Packet = [PacketStart|lists:reverse(tl(Buffer), PacketLast)],
- DistHandle = Params#params.dist_handle,
- erlang:dist_ctrl_put_data(DistHandle, Packet),
- input_handler(Params, Seq, Rest, [], RestSize)
- end.
-
-input_get_packet_size(First, [Bin|Buffer]) ->
- MissingSize = 4 - byte_size(First),
- if
- MissingSize =< byte_size(Bin) ->
- <<Last:MissingSize/binary, Rest/binary>> = Bin,
- <<PacketSize:32>> = <<First/binary, Last/binary>>,
- {Rest, lists:reverse(Buffer), PacketSize};
- true ->
- input_get_packet_size(<<First/binary, Bin/binary>>, Buffer)
- end.
-
-input_data(Params, Seq) ->
- receive Msg -> input_data(Params, Seq, Msg) end.
-%%
-input_data(#params{socket = Socket} = Params, Seq, Msg) ->
- case Msg of
- {tcp_passive, Socket} ->
- ok = inet:setopts(Socket, [{active, ?TCP_ACTIVE}]),
- input_data(Params, Seq);
- {tcp, Socket, Ciphertext} ->
- case decrypt_chunk(Params, Seq, Ciphertext) of
- <<?DATA_CHUNK, Chunk/binary>> ->
- {Params, Seq + 1, Chunk};
- <<?TICK_CHUNK, _Dummy/binary>> ->
- input_data(Params, Seq + 1);
- <<UnknownChunk/binary>> ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason, unknown_chunk}]),
- _ = trace(UnknownChunk),
- exit(connection_closed);
- #params{} = Params_1 ->
- input_data(Params_1, 0);
- error ->
- _ = trace(decrypt_error),
- exit(connection_closed)
- end;
- {tcp_closed = Reason, Socket} ->
- error_logger:info_report(
- [?FUNCTION_NAME,
- {reason, Reason}]),
- exit(connection_closed);
- Other ->
- %% Ignore...
- _ = trace(Other),
- input_data(Params, Seq)
- end.
-
-%% -------------------------------------------------------------------------
-%% Encryption and decryption helpers
-
-encrypt_and_send_chunk(
- #params{
- socket = Socket, rekey_count = RekeyCount, rekey_msg = RekeyMsg} = Params,
- Seq, Cleartext, Size) when Seq =:= RekeyCount ->
- %%
- cancel_rekey_timer(RekeyMsg),
- case encrypt_and_send_rekey_chunk(Params, Seq) of
- #params{} = Params_1 ->
- Result =
- gen_tcp:send(
- Socket, encrypt_chunk(Params, 0, Cleartext, Size)),
- {Params_1, 1, Result};
- SendError ->
- {Params, Seq + 1, SendError}
- end;
-encrypt_and_send_chunk(
- #params{socket = Socket} = Params, Seq, Cleartext, Size) ->
- Result =
- gen_tcp:send(Socket, encrypt_chunk(Params, Seq, Cleartext, Size)),
- {Params, Seq + 1, Result}.
-
-encrypt_and_send_rekey_chunk(
- #params{
- socket = Socket,
- rekey_key = PubKeyB,
- key = Key,
- iv = {IVSalt, IVNo},
- hmac_algorithm = HmacAlgo} = Params,
- Seq) ->
- %%
- KeyLen = byte_size(Key),
- IVSaltLen = byte_size(IVSalt),
- #key_pair{public = PubKeyA} = KeyPair = get_new_key_pair(),
- case
- gen_tcp:send(
- Socket,
- encrypt_chunk(
- Params, Seq, [?REKEY_CHUNK, PubKeyA], 1 + byte_size(PubKeyA)))
- of
- ok ->
- SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
- IV = <<(IVNo + Seq):48>>,
- {Key_1, <<IVSalt_1:IVSaltLen/binary, IVNo_1:48>>} =
- hmac_key_iv(
- HmacAlgo, SharedSecret, [Key, IVSalt, IV],
- KeyLen, IVSaltLen + 6),
- Params#params{
- key = Key_1, iv = {IVSalt_1, IVNo_1},
- rekey_msg = start_rekey_timer(Params#params.rekey_time)};
- SendError ->
- SendError
- end.
-
-encrypt_chunk(
- #params{
- aead_cipher = AeadCipher,
- iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen},
- Seq, Cleartext, Size) ->
- %%
- ChunkLen = Size + TagLen,
- AAD = <<Seq:32, ChunkLen:32>>,
- IVBin = <<IVSalt/binary, (IVNo + Seq):48>>,
- {Ciphertext, CipherTag} =
- crypto:crypto_one_time_aead(
- AeadCipher, Key, IVBin, Cleartext, AAD, TagLen, true),
- Chunk = [Ciphertext,CipherTag],
- Chunk.
-
-decrypt_chunk(
- #params{
- aead_cipher = AeadCipher,
- iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen} = Params, Seq, Chunk) ->
- %%
- ChunkLen = byte_size(Chunk),
- if
- ChunkLen < TagLen ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,short_chunk}]),
- error;
- true ->
- AAD = <<Seq:32, ChunkLen:32>>,
- IVBin = <<IVSalt/binary, (IVNo + Seq):48>>,
- CiphertextLen = ChunkLen - TagLen,
- <<Ciphertext:CiphertextLen/binary,
- CipherTag:TagLen/binary>> = Chunk,
- block_decrypt(
- Params, Seq, AeadCipher, Key, IVBin,
- Ciphertext, AAD, CipherTag)
- end.
-
-block_decrypt(
- #params{
- rekey_key = #key_pair{public = PubKeyA} = KeyPair,
- rekey_count = RekeyCount} = Params,
- Seq, AeadCipher, Key, IV, Ciphertext, AAD, CipherTag) ->
- case
- crypto:crypto_one_time_aead(
- AeadCipher, Key, IV, Ciphertext, AAD, CipherTag, false)
- of
- <<?REKEY_CHUNK, Chunk/binary>> ->
- PubKeyLen = byte_size(PubKeyA),
- case Chunk of
- <<PubKeyB:PubKeyLen/binary>> ->
- SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
- KeyLen = byte_size(Key),
- IVLen = byte_size(IV),
- IVSaltLen = IVLen - 6,
- {Key_1, <<IVSalt:IVSaltLen/binary, IVNo:48>>} =
- hmac_key_iv(
- Params#params.hmac_algorithm,
- SharedSecret, [Key, IV], KeyLen, IVLen),
- Params#params{iv = {IVSalt, IVNo}, key = Key_1};
- _ ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,bad_rekey_chunk}]),
- error
- end;
- Chunk when is_binary(Chunk) ->
- case Seq of
- RekeyCount ->
- %% This was one chunk too many without rekeying
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,rekey_overdue}]),
- error;
- _ ->
- Chunk
- end;
- error ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,decrypt_error}]),
- error
- end.
-
-%% -------------------------------------------------------------------------
-
-%% Wait for getting killed by process link,
-%% and if that does not happen - drop dead
-
-death_row(Reason) ->
- error_logger:info_report(
- [?FUNCTION_NAME,
- {reason, Reason},
- {pid, self()}]),
- receive
- after 5000 ->
- death_row_timeout(Reason)
- end.
-
-death_row_timeout(Reason) ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason, Reason},
- {pid, self()}]),
- exit(Reason).
-
-%% -------------------------------------------------------------------------
-
-%% Trace point
-trace(Term) -> Term.
-
-%% Keep an eye on this Pid (debug)
--ifdef(undefined).
-monitor_dist_proc(_Tag, Pid) ->
- Pid.
--else.
-monitor_dist_proc(Tag, Pid) ->
- spawn(
- fun () ->
- MRef = erlang:monitor(process, Pid),
- error_logger:info_report(
- [?FUNCTION_NAME,
- {type, Tag},
- {pid, Pid}]),
- receive
- {'DOWN', MRef, _, _, normal} ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason, normal},
- {pid, Pid}]);
- {'DOWN', MRef, _, _, Reason} ->
- error_logger:info_report(
- [?FUNCTION_NAME,
- {reason, Reason},
- {pid, Pid}])
- end
- end),
- Pid.
--endif.
-
-dbg() ->
- dbg:stop(),
- dbg:tracer(),
- dbg:p(all, c),
- dbg:tpl(?MODULE, trace, cx),
- dbg:tpl(erlang, dist_ctrl_get_data_notification, cx),
- dbg:tpl(erlang, dist_ctrl_get_data, cx),
- dbg:tpl(erlang, dist_ctrl_put_data, cx),
- ok.
diff --git a/lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl b/lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl
new file mode 100644
index 0000000000..d350cc8139
--- /dev/null
+++ b/lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl
@@ -0,0 +1,230 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% -------------------------------------------------------------------------
+%%
+%% Plug-in module for inet_epmd distribution
+%% with cryptcookie over inet_tcp with kTLS offloading
+%%
+-module(inet_epmd_cryptcookie_inet_ktls).
+-feature(maybe_expr, enable).
+
+%% DistMod API
+-export([net_address/0, listen_open/2, listen_port/3, listen_close/1,
+ accept_open/2, accept_controller/3, accepted/3,
+ connect/3]).
+
+-export([supported/0]).
+
+%% Socket I/O Stream internal exports (export entry fun()s)
+-export([stream_recv/2, stream_send/2,
+ stream_controlling_process/2]).
+
+-include_lib("kernel/include/net_address.hrl").
+-include_lib("kernel/include/dist.hrl").
+-include_lib("kernel/include/dist_util.hrl").
+
+-define(FAMILY, inet).
+-define(DRIVER, inet_tcp).
+
+%% ------------------------------------------------------------
+net_address() ->
+ Family = ?DRIVER:family(),
+ Protocol = cryptcookie:start_keypair_server(),
+ #net_address{
+ protocol = Protocol,
+ family = Family }.
+
+%% ------------------------------------------------------------
+listen_open(_NetAddress, Options) ->
+ {ok,
+ inet_epmd_dist:merge_options(
+ Options,
+ [{active, false}, {mode, binary}, {packet, 0},
+ inet_epmd_dist:nodelay()],
+ [])}.
+
+%% ------------------------------------------------------------
+listen_port(_NetAddress, Port, ListenOptions) ->
+ maybe
+ {ok, ListenSocket} ?=
+ ?DRIVER:listen(Port, ListenOptions),
+ {ok, Address} ?=
+ inet:sockname(ListenSocket),
+ {ok, {ListenSocket, Address}}
+ end.
+
+%% ------------------------------------------------------------
+listen_close(ListenSocket) ->
+ ?DRIVER:close(ListenSocket).
+
+%% ------------------------------------------------------------
+accept_open(_NetAddress, ListenSocket) ->
+ maybe
+ {ok, Socket} ?=
+ ?DRIVER:accept(ListenSocket),
+ {ok, {Ip, _}} ?=
+ inet:sockname(Socket),
+ {ok, {PeerIp, _} = PeerAddress} ?=
+ inet:peername(Socket),
+ inet_epmd_dist:check_ip(Ip, PeerIp),
+ Stream = stream(Socket),
+ {_Stream_1, _, CipherState} = cryptcookie:init(Stream),
+ KtlsInfo =
+ inet_ktls_info(Socket, cryptcookie:ktls_info(CipherState)),
+ ok ?= inet_tls_dist:set_ktls(KtlsInfo),
+ ok ?= inet:setopts(Socket, [{packet, 2}, {mode, list}]),
+ {Socket, PeerAddress}
+ else
+ {error, Reason} ->
+ exit({accept, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accept_controller(_NetAddress, Controller, Socket) ->
+ ok = ?DRIVER:controlling_process(Socket, Controller),
+ Socket.
+
+%% ------------------------------------------------------------
+accepted(NetAddress, _Timer, Socket) ->
+ inet_epmd_dist:hs_data(NetAddress, Socket).
+
+%% ------------------------------------------------------------
+connect(NetAddress, _Timer, Options) ->
+ ConnectOptions =
+ inet_epmd_dist:merge_options(
+ Options,
+ [{active, false}, {mode, binary}, {packet, 0},
+ inet_epmd_dist:nodelay()],
+ []),
+ #net_address{ address = {Ip, Port} } = NetAddress,
+ maybe
+ {ok, Socket} ?=
+ ?DRIVER:connect(Ip, Port, ConnectOptions),
+ Stream = stream(Socket),
+ {_Stream_1, _, CipherState} = cryptcookie:init(Stream),
+ KtlsInfo =
+ inet_ktls_info(Socket, cryptcookie:ktls_info(CipherState)),
+ ok ?= inet_tls_dist:set_ktls(KtlsInfo),
+ ok ?= inet:setopts(Socket, [{packet, 2}, {mode, list}]),
+ inet_epmd_dist:hs_data(NetAddress, Socket)
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+%% A socket as an I/O Stream
+%%
+%% Stream :: {InStream, OutStream, ControllingProcessFun}.
+%%
+%% InStream :: [InFun | InState].
+%% InFun :: fun (InStream, Size) ->
+%% [Data | NewInStream] |
+%% [closed | DebugTerm]
+%% NewInStream :: InStream
+%% %% If Size == 0 and there is no pending input data;
+%% %% return immediately with empty Data,
+%% %% otherwise wait for Size bytes of data
+%% %% or any amount of data > 0
+%%
+%% OutStream :: [OutFun | OutState]
+%% OutFun :: fun (OutStream, Data) ->
+%% NewOutStream |
+%% [closed | DebugTerm]
+%% NewOutStream :: OutStream
+%%
+%% Data :: binary() or list(binary())
+%%
+%% ControllingProcessFun :: fun (Stream, pid()) -> NewStream
+%%
+%% NewSTream :: Stream
+
+stream(Socket) ->
+ {stream_in(Socket), stream_out(Socket),
+ fun ?MODULE:stream_controlling_process/2}.
+
+stream_in(Socket) ->
+ [fun ?MODULE:stream_recv/2 | Socket].
+
+stream_recv(InStream = [_ | Socket], Size) ->
+ case
+ if
+ Size =:= 0 ->
+ ?DRIVER:recv(Socket, 0, 0);
+ true ->
+ ?DRIVER:recv(Socket, Size, infinity)
+ end
+ of
+ {ok, Data} ->
+ [Data | InStream];
+ {error, timeout} ->
+ [<<>> | InStream];
+ {error, closed} ->
+ [closed | InStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+stream_out(Socket) ->
+ [fun ?MODULE:stream_send/2 | Socket].
+
+stream_send(OutStream = [_ | Socket], Data) ->
+ case ?DRIVER:send(Socket, Data) of
+ ok ->
+ OutStream;
+ {error, closed} ->
+ [closed | OutStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason, [OutStream, Data]})
+ end.
+
+stream_controlling_process(Stream = {_, [_ | Socket], _}, Pid) ->
+ %%
+ case ?DRIVER:controlling_process(Socket, Pid) of
+ ok ->
+ Stream;
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+supported() ->
+ maybe
+ ok ?= cryptcookie:supported(),
+ %%
+ {ok, Listen} = ?DRIVER:listen(0, [{active, false}]),
+ {ok, Port} = inet:port(Listen),
+ {ok, Client} =
+ ?DRIVER:connect({127,0,0,1}, Port, [{active, false}]),
+ try
+ inet_tls_dist:set_ktls(
+ inet_ktls_info(Client, cryptcookie:ktls_info()))
+ after
+ _ = ?DRIVER:close(Client),
+ _ = ?DRIVER:close(Listen)
+ end
+ end.
+
+
+inet_ktls_info(Socket, KtlsInfo) ->
+ KtlsInfo
+ #{ socket => Socket,
+ setopt_fun => fun inet_tls_dist:inet_ktls_setopt/3,
+ getopt_fun => fun inet_tls_dist:inet_ktls_getopt/3 }.
diff --git a/lib/ssl/test/inet_epmd_cryptcookie_socket_ktls.erl b/lib/ssl/test/inet_epmd_cryptcookie_socket_ktls.erl
new file mode 100644
index 0000000000..bab43ea331
--- /dev/null
+++ b/lib/ssl/test/inet_epmd_cryptcookie_socket_ktls.erl
@@ -0,0 +1,276 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% -------------------------------------------------------------------------
+%%
+%% Plug-in module for inet_epmd distribution
+%% with cryptcookie over inet_tcp with kTLS offloading
+%%
+-module(inet_epmd_cryptcookie_socket_ktls).
+-feature(maybe_expr, enable).
+
+%% DistMod API
+-export([net_address/0, listen_open/2, listen_port/3, listen_close/1,
+ accept_open/2, accept_controller/3, accepted/3,
+ connect/3]).
+
+-export([supported/0]).
+
+%% Socket I/O Stream internal exports (export entry fun()s)
+-export([stream_recv/2, stream_send/2,
+ stream_controlling_process/2]).
+
+-include_lib("kernel/include/net_address.hrl").
+-include_lib("kernel/include/dist.hrl").
+-include_lib("kernel/include/dist_util.hrl").
+
+-define(FAMILY, inet).
+
+%% ------------------------------------------------------------
+net_address() ->
+ #net_address{
+ protocol = dist_cryptcookie:protocol(),
+ family = ?FAMILY }.
+
+%% ------------------------------------------------------------
+listen_open(#net_address{ family = Family}, ListenOptions) ->
+ maybe
+ Key = backlog,
+ Default = 128,
+ Backlog = proplists:get_value(Key, ListenOptions, Default),
+ {ok, ListenSocket} ?=
+ socket:open(Family, stream),
+ ok ?=
+ setopts(
+ ListenSocket,
+ inet_epmd_dist:merge_options(
+ ListenOptions, [inet_epmd_dist:nodelay()], [])),
+ {ok, {ListenSocket, Backlog}}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+setopts(Socket, Options) ->
+ gen_tcp_socket:socket_setopts(Socket, Options).
+
+%% ------------------------------------------------------------
+listen_port(
+ #net_address{ family = Family }, Port, {ListenSocket, Backlog}) ->
+ maybe
+ Sockaddr =
+ #{family => Family,
+ addr => any,
+ port => Port},
+ ok ?=
+ socket:bind(ListenSocket, Sockaddr),
+ ok ?=
+ socket:listen(ListenSocket, Backlog),
+ {ok, #{ addr := Ip, port := ListenPort}} ?=
+ socket:sockname(ListenSocket),
+ {ok, {ListenSocket, {Ip, ListenPort}}}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+listen_close(ListenSocket) ->
+ socket:close(ListenSocket).
+
+%% ------------------------------------------------------------
+accept_open(_NetAddress, ListenSocket) ->
+ maybe
+ {ok, Socket} ?=
+ socket:accept(ListenSocket),
+ {ok, #{ addr := Ip }} ?=
+ socket:sockname(Socket),
+ {ok, #{ addr := PeerIp, port := PeerPort }} ?=
+ socket:peername(Socket),
+ inet_epmd_dist:check_ip(Ip, PeerIp),
+ Stream = stream(Socket),
+ {_Stream_1, _, CipherState} = cryptcookie:init(Stream),
+ KtlsInfo =
+ socket_ktls_info(Socket, cryptcookie:ktls_info(CipherState)),
+ ok ?=
+ inet_tls_dist:set_ktls(KtlsInfo),
+ {Socket, {PeerIp, PeerPort}}
+ else
+ {error, Reason} ->
+ exit({?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accept_controller(_NetAddress, Controller, Socket) ->
+ maybe
+ ok ?=
+ socket:setopt(Socket, {otp,controlling_process}, Controller),
+ Socket
+ else
+ {error, Reason} ->
+ exit({?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accepted(NetAddress, _Timer, Socket) ->
+ inet_epmd_socket:start_dist_ctrl(NetAddress, Socket).
+
+%% ------------------------------------------------------------
+connect(
+ #net_address{ address = {Ip, Port}, family = Family } = NetAddress,
+ _Timer, ConnectOptions) ->
+ maybe
+ {ok, Socket} ?=
+ socket:open(Family, stream),
+ ok ?=
+ setopts(
+ Socket,
+ inet_epmd_dist:merge_options(
+ ConnectOptions, [inet_epmd_dist:nodelay()], [])),
+ ConnectAddress =
+ #{ family => Family,
+ addr => Ip,
+ port => Port },
+ ok ?=
+ socket:connect(Socket, ConnectAddress),
+ Stream = stream(Socket),
+ {_Stream_1, _, CipherState} = cryptcookie:init(Stream),
+ KtlsInfo =
+ socket_ktls_info(Socket, cryptcookie:ktls_info(CipherState)),
+ ok ?=
+ inet_tls_dist:set_ktls(KtlsInfo),
+ inet_epmd_socket:start_dist_ctrl(NetAddress, Socket)
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+%% A socket as an I/O Stream
+%%
+%% Stream :: {InStream, OutStream, ControllingProcessFun}.
+%%
+%% InStream :: [InFun | InState].
+%% InFun :: fun (InStream, Size) ->
+%% [Data | NewInStream] |
+%% [closed | DebugTerm]
+%% NewInStream :: InStream
+%% %% If Size == 0 and there is no pending input data;
+%% %% return immediately with empty Data,
+%% %% otherwise wait for Size bytes of data
+%% %% or any amount of data > 0
+%%
+%% OutStream :: [OutFun | OutState]
+%% OutFun :: fun (OutStream, Data) ->
+%% NewOutStream |
+%% [closed | DebugTerm]
+%% NewOutStream :: OutStream
+%%
+%% Data :: binary() or list(binary())
+%%
+%% ControllingProcessFun :: fun (Stream, pid()) -> NewStream
+%%
+%% NewSTream :: Stream
+
+stream(Socket) ->
+ {stream_in(Socket), stream_out(Socket),
+ fun ?MODULE:stream_controlling_process/2}.
+
+stream_in(Socket) ->
+ [fun ?MODULE:stream_recv/2 | Socket].
+
+stream_recv(InStream = [_ | Socket], Size) ->
+ case
+ if
+ Size =:= 0 ->
+ socket:recv(Socket, 0, 0);
+ true ->
+ socket:recv(Socket, Size, infinity)
+ end
+ of
+ {ok, Data} ->
+ [Data | InStream];
+ {error, {Reason, _Data}} ->
+ stream_recv_error(InStream, Reason);
+ {error, timeout} ->
+ [<<>> | InStream];
+ {error, Reason} ->
+ stream_recv_error(InStream, Reason)
+ end.
+
+stream_recv_error(InStream, Reason) ->
+ if
+ Reason =:= closed;
+ Reason =:= econnreset ->
+ [closed | InStream];
+ true ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+stream_out(Socket) ->
+ [fun ?MODULE:stream_send/2 | Socket].
+
+stream_send(OutStream, Bin) when is_binary(Bin) ->
+ stream_send(OutStream, [Bin]);
+stream_send(OutStream = [_ | Socket], Data) ->
+ case socket:sendmsg(Socket, #{ iov => Data }) of
+ ok ->
+ OutStream;
+ {error, closed} ->
+ [closed | OutStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason, [OutStream, Data]})
+ end.
+
+stream_controlling_process(Stream = {_, [_ | Socket], _}, Pid) ->
+ %%
+ case socket:setopt(Socket, {otp,controlling_process}, Pid) of
+ ok ->
+ Stream;
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+supported() ->
+ maybe
+ ok ?= inet_epmd_socket:supported(),
+ ok ?= cryptcookie:supported(),
+ %%
+ {ok, Listen} = socket:open(?FAMILY, stream),
+ ok = socket:bind(Listen, loopback),
+ ok = socket:listen(Listen),
+ {ok, Addr} = socket:sockname(Listen),
+ {ok, Client} = socket:open(?FAMILY, stream),
+ ok = socket:connect(Client, Addr),
+ try
+ inet_tls_dist:set_ktls(
+ socket_ktls_info(Client, cryptcookie:ktls_info()))
+ after
+ _ = socket:close(Client),
+ _ = socket:close(Listen)
+ end
+ end.
+
+
+socket_ktls_info(Socket, KtlsInfo) ->
+ KtlsInfo
+ #{ socket => Socket,
+ setopt_fun => fun socket:setopt_native/3,
+ getopt_fun => fun socket:getopt_native/3 }.
diff --git a/lib/ssl/test/inet_epmd_dist_cryptcookie_inet.erl b/lib/ssl/test/inet_epmd_dist_cryptcookie_inet.erl
new file mode 100644
index 0000000000..26fa27d404
--- /dev/null
+++ b/lib/ssl/test/inet_epmd_dist_cryptcookie_inet.erl
@@ -0,0 +1,200 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% -------------------------------------------------------------------------
+%%
+%% Module for dist_cryptcookie over inet_tcp
+%%
+-module(inet_epmd_dist_cryptcookie_inet).
+-feature(maybe_expr, enable).
+
+%% DistMod API
+-export([net_address/0, listen_open/2, listen_port/3, listen_close/1,
+ accept_open/2, accept_controller/3, accepted/3,
+ connect/3]).
+
+-export([supported/0]).
+
+%% Socket I/O Stream internal exports (export entry fun()s)
+-export([stream_recv/2, stream_send/2,
+ stream_controlling_process/2]).
+
+-include_lib("kernel/include/net_address.hrl").
+-include_lib("kernel/include/dist.hrl").
+-include_lib("kernel/include/dist_util.hrl").
+
+-define(FAMILY, inet).
+-define(DRIVER, inet_tcp).
+
+%% ------------------------------------------------------------
+net_address() ->
+ Family = ?DRIVER:family(),
+ #net_address{
+ protocol = dist_cryptcookie:protocol(),
+ family = Family }.
+
+%% ------------------------------------------------------------
+listen_open(_NetAddress, Options) ->
+ {ok,
+ inet_epmd_dist:merge_options(
+ Options,
+ [{active, false}, {mode, binary}, {packet, 0},
+ inet_epmd_dist:nodelay()],
+ [])}.
+
+%% ------------------------------------------------------------
+listen_port(_NetAddress, Port, ListenOptions) ->
+ maybe
+ {ok, ListenSocket} ?=
+ ?DRIVER:listen(Port, ListenOptions),
+ {ok, Address} ?=
+ inet:sockname(ListenSocket),
+ {ok, {ListenSocket, Address}}
+ end.
+
+%% ------------------------------------------------------------
+listen_close(ListenSocket) ->
+ ?DRIVER:close(ListenSocket).
+
+%% ------------------------------------------------------------
+accept_open(_NetAddress, ListenSocket) ->
+ maybe
+ {ok, Socket} ?=
+ ?DRIVER:accept(ListenSocket),
+ {ok, {Ip, _}} ?=
+ inet:sockname(Socket),
+ {ok, {PeerIp, _} = PeerAddress} ?=
+ inet:peername(Socket),
+ inet_epmd_dist:check_ip(Ip, PeerIp),
+ Stream = stream(Socket),
+ CryptcookieInit = cryptcookie:init(Stream),
+ DistCtrlHandle = dist_cryptcookie:start_dist_ctrl(CryptcookieInit),
+ {DistCtrlHandle, PeerAddress}
+ else
+ {error, Reason} ->
+ exit({accept, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accept_controller(_NetAddress, Controller, DistCtrlHandle) ->
+ dist_cryptcookie:controlling_process(DistCtrlHandle, Controller).
+
+%% ------------------------------------------------------------
+accepted(NetAddress, _Timer, DistCtrlHandle) ->
+ dist_cryptcookie:hs_data(NetAddress, DistCtrlHandle).
+
+%% ------------------------------------------------------------
+connect(NetAddress, _Timer, Options) ->
+ ConnectOptions =
+ inet_epmd_dist:merge_options(
+ Options,
+ [{active, false}, {mode, binary}, {packet, 0},
+ inet_epmd_dist:nodelay()],
+ []),
+ #net_address{ address = {Ip, Port} } = NetAddress,
+ maybe
+ {ok, Socket} ?=
+ ?DRIVER:connect(Ip, Port, ConnectOptions),
+ Stream = stream(Socket),
+ CryptcookieInit = cryptcookie:init(Stream),
+ DistCtrlHandle = dist_cryptcookie:start_dist_ctrl(CryptcookieInit),
+ dist_cryptcookie:hs_data(NetAddress, DistCtrlHandle)
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+%% A socket as an I/O Stream
+%%
+%% Stream :: {InStream, OutStream, ControllingProcessFun}.
+%%
+%% InStream :: [InFun | InState].
+%% InFun :: fun (InStream, Size) ->
+%% [Data | NewInStream] |
+%% [closed | DebugTerm]
+%% NewInStream :: InStream
+%% %% If Size == 0 and there is no pending input data;
+%% %% return immediately with empty Data,
+%% %% otherwise wait for Size bytes of data
+%% %% or any amount of data > 0
+%%
+%% OutStream :: [OutFun | OutState]
+%% OutFun :: fun (OutStream, Data) ->
+%% NewOutStream |
+%% [closed | DebugTerm]
+%% NewOutStream :: OutStream
+%%
+%% Data :: binary() or list(binary())
+%%
+%% ControllingProcessFun :: fun (Stream, pid()) -> NewStream
+%%
+%% NewSTream :: Stream
+
+stream(Socket) ->
+ {stream_in(Socket), stream_out(Socket),
+ fun ?MODULE:stream_controlling_process/2}.
+
+stream_in(Socket) ->
+ [fun ?MODULE:stream_recv/2 | Socket].
+
+stream_recv(InStream = [_ | Socket], Size) ->
+ case
+ if
+ Size =:= 0 ->
+ ?DRIVER:recv(Socket, 0, 0);
+ true ->
+ ?DRIVER:recv(Socket, Size, infinity)
+ end
+ of
+ {ok, Data} ->
+ [Data | InStream];
+ {error, timeout} ->
+ [<<>> | InStream];
+ {error, closed} ->
+ [closed | InStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+stream_out(Socket) ->
+ [fun ?MODULE:stream_send/2 | Socket].
+
+stream_send(OutStream = [_ | Socket], Data) ->
+ case ?DRIVER:send(Socket, Data) of
+ ok ->
+ OutStream;
+ {error, closed} ->
+ [closed | OutStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason, [OutStream, Data]})
+ end.
+
+stream_controlling_process(Stream = {_, [_ | Socket], _}, Pid) ->
+ %%
+ case ?DRIVER:controlling_process(Socket, Pid) of
+ ok ->
+ Stream;
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+supported() ->
+ cryptcookie:supported().
diff --git a/lib/ssl/test/inet_epmd_dist_cryptcookie_socket.erl b/lib/ssl/test/inet_epmd_dist_cryptcookie_socket.erl
new file mode 100644
index 0000000000..fb817060bd
--- /dev/null
+++ b/lib/ssl/test/inet_epmd_dist_cryptcookie_socket.erl
@@ -0,0 +1,241 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%% -------------------------------------------------------------------------
+%%
+%% Module for dist_cryptcookie over socket
+%%
+-module(inet_epmd_dist_cryptcookie_socket).
+-feature(maybe_expr, enable).
+
+%% DistMod API
+-export([net_address/0, listen_open/2, listen_port/3, listen_close/1,
+ accept_open/2, accept_controller/3, accepted/3,
+ connect/3]).
+
+-export([supported/0]).
+
+%% Socket I/O Stream internal exports (export entry fun()s)
+-export([stream_recv/2, stream_send/2,
+ stream_controlling_process/2]).
+
+-include_lib("kernel/include/net_address.hrl").
+-include_lib("kernel/include/dist.hrl").
+-include_lib("kernel/include/dist_util.hrl").
+
+-define(FAMILY, inet).
+
+%% ------------------------------------------------------------
+net_address() ->
+ #net_address{
+ protocol = dist_cryptcookie:protocol(),
+ family = ?FAMILY }.
+
+%% ------------------------------------------------------------
+listen_open(#net_address{ family = Family}, ListenOptions) ->
+ maybe
+ Key = backlog,
+ Default = 128,
+ Backlog = proplists:get_value(Key, ListenOptions, Default),
+ {ok, ListenSocket} ?=
+ socket:open(Family, stream),
+ ok ?=
+ setopts(
+ ListenSocket,
+ inet_epmd_dist:merge_options(
+ ListenOptions, [inet_epmd_dist:nodelay()], [])),
+ {ok, {ListenSocket, Backlog}}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+setopts(Socket, Options) ->
+ gen_tcp_socket:socket_setopts(Socket, Options).
+
+%% ------------------------------------------------------------
+listen_port(
+ #net_address{ family = Family }, Port, {ListenSocket, Backlog}) ->
+ maybe
+ Sockaddr =
+ #{family => Family,
+ addr => any,
+ port => Port},
+ ok ?=
+ socket:bind(ListenSocket, Sockaddr),
+ ok ?=
+ socket:listen(ListenSocket, Backlog),
+ {ok, #{ addr := Ip, port := ListenPort}} ?=
+ socket:sockname(ListenSocket),
+ {ok, {ListenSocket, {Ip, ListenPort}}}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+listen_close(ListenSocket) ->
+ socket:close(ListenSocket).
+
+%% ------------------------------------------------------------
+accept_open(_NetAddress, ListenSocket) ->
+ maybe
+ {ok, Socket} ?=
+ socket:accept(ListenSocket),
+ {ok, #{ addr := Ip }} ?=
+ socket:sockname(Socket),
+ {ok, #{ addr := PeerIp, port := PeerPort }} ?=
+ socket:peername(Socket),
+ inet_epmd_dist:check_ip(Ip, PeerIp),
+ Stream = stream(Socket),
+ CryptcookieInit = cryptcookie:init(Stream),
+ DistCtrlHandle = dist_cryptcookie:start_dist_ctrl(CryptcookieInit),
+ {DistCtrlHandle, {PeerIp, PeerPort}}
+ else
+ {error, Reason} ->
+ exit({?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accept_controller(_NetAddress, Controller, DistCtrlHandle) ->
+ dist_cryptcookie:controlling_process(DistCtrlHandle, Controller).
+
+%% ------------------------------------------------------------
+accepted(NetAddress, _Timer, DistCtrlHandle) ->
+ dist_cryptcookie:hs_data(NetAddress, DistCtrlHandle).
+
+%% ------------------------------------------------------------
+connect(
+ #net_address{ address = {Ip, Port}, family = Family } = NetAddress,
+ _Timer, ConnectOptions) ->
+ maybe
+ {ok, Socket} ?=
+ socket:open(Family, stream),
+ ok ?=
+ setopts(
+ Socket,
+ inet_epmd_dist:merge_options(
+ ConnectOptions, [inet_epmd_dist:nodelay()], [])),
+ ConnectAddress =
+ #{ family => Family,
+ addr => Ip,
+ port => Port },
+ ok ?=
+ socket:connect(Socket, ConnectAddress),
+ Stream = stream(Socket),
+ CryptcookieInit = cryptcookie:init(Stream),
+ DistCtrlHandle = dist_cryptcookie:start_dist_ctrl(CryptcookieInit),
+ dist_cryptcookie:hs_data(NetAddress, DistCtrlHandle)
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+%% A socket as an I/O Stream
+%%
+%% Stream :: {InStream, OutStream, ControllingProcessFun}.
+%%
+%% InStream :: [InFun | InState].
+%% InFun :: fun (InStream, Size) ->
+%% [Data | NewInStream] |
+%% [closed | DebugTerm]
+%% NewInStream :: InStream
+%% %% If Size == 0 and there is no pending input data;
+%% %% return immediately with empty Data,
+%% %% otherwise wait for Size bytes of data
+%% %% or any amount of data > 0
+%%
+%% OutStream :: [OutFun | OutState]
+%% OutFun :: fun (OutStream, Data) ->
+%% NewOutStream |
+%% [closed | DebugTerm]
+%% NewOutStream :: OutStream
+%%
+%% Data :: binary() or list(binary())
+%%
+%% ControllingProcessFun :: fun (Stream, pid()) -> NewStream
+%%
+%% NewSTream :: Stream
+
+stream(Socket) ->
+ {stream_in(Socket), stream_out(Socket),
+ fun ?MODULE:stream_controlling_process/2}.
+
+stream_in(Socket) ->
+ [fun ?MODULE:stream_recv/2 | Socket].
+
+stream_recv(InStream = [_ | Socket], Size) ->
+ case
+ if
+ Size =:= 0 ->
+ socket:recv(Socket, 0, 0);
+ true ->
+ socket:recv(Socket, Size, infinity)
+ end
+ of
+ {ok, Data} ->
+ [Data | InStream];
+ {error, {Reason, _Data}} ->
+ stream_recv_error(InStream, Reason);
+ {error, timeout} ->
+ [<<>> | InStream];
+ {error, Reason} ->
+ stream_recv_error(InStream, Reason)
+ end.
+
+stream_recv_error(InStream, Reason) ->
+ if
+ Reason =:= closed;
+ Reason =:= econnreset ->
+ [closed | InStream];
+ true ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+stream_out(Socket) ->
+ [fun ?MODULE:stream_send/2 | Socket].
+
+stream_send(OutStream, Bin) when is_binary(Bin) ->
+ stream_send(OutStream, [Bin]);
+stream_send(OutStream = [_ | Socket], Data) ->
+ case socket:sendmsg(Socket, #{ iov => Data }) of
+ ok ->
+ OutStream;
+ {error, closed} ->
+ [closed | OutStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason, [OutStream, Data]})
+ end.
+
+stream_controlling_process(Stream = {_, [_ | Socket], _}, Pid) ->
+ %%
+ case socket:setopt(Socket, {otp,controlling_process}, Pid) of
+ ok ->
+ Stream;
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+supported() ->
+ maybe
+ ok ?= inet_epmd_socket:supported(),
+ cryptcookie:supported()
+ end.
diff --git a/lib/ssl/test/openssl_alpn_SUITE.erl b/lib/ssl/test/openssl_alpn_SUITE.erl
index 2836e9a0a7..1d0bc82c4e 100644
--- a/lib/ssl/test/openssl_alpn_SUITE.erl
+++ b/lib/ssl/test/openssl_alpn_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -194,7 +194,7 @@ erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) ->
erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) ->
ClientOpts = proplists:get_value(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
Protocol = <<"spdy/2">>,
Server = ssl_test_lib:start_server(erlang, [{from, self()}],
[{server_opts, [{alpn_preferred_protocols,
@@ -414,7 +414,7 @@ erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) ->
[{client_opts,
[{alpn_advertised_protocols, [AlpnProtocol]},
{client_preferred_next_protocols,
- {client, [<<"spdy/3">>, <<"http/1.1">>]}}]} | ClientOpts] ++ Config),
+ {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts]}] ++ Config),
case ssl:negotiated_protocol(CSocket) of
{ok, AlpnProtocol} ->
ok;
diff --git a/lib/ssl/test/openssl_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_cipher_suite_SUITE.erl
index 8724595724..fce70645d3 100644
--- a/lib/ssl/test/openssl_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/openssl_cipher_suite_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -527,21 +527,25 @@ init_certs(rsa_psk, Config) ->
{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}} | ClientOpts]}} |
proplists:delete(tls_config, Config)];
init_certs(rsa, Config) ->
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ SigAlgs = ssl_test_lib:sig_algs(rsa, Version),
Ext = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]),
{ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
[[ssl_test_lib:digest()],[ssl_test_lib:digest()],
[ssl_test_lib:digest(), {extensions, Ext}]]}
],
Config, "_peer_keyEncipherment"),
- [{tls_config, #{server_config => ServerOpts,
- client_config => ClientOpts}} |
+ [{tls_config, #{server_config => SigAlgs ++ ServerOpts,
+ client_config => SigAlgs ++ ClientOpts}} |
proplists:delete(tls_config, Config)];
init_certs(dhe_dss, Config) ->
- {ClientOpts, ServerOpts} = ssl_test_lib:make_dsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ SigAlgs = ssl_test_lib:sig_algs(dsa, Version),
+ {ClientOpts, ServerOpts} = ssl_test_lib:make_dsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
{client_chain, ssl_test_lib:default_cert_chain_conf()}],
- Config, ""),
- [{tls_config, #{server_config => ServerOpts,
- client_config => ClientOpts}} |
+ Config, ""),
+ [{tls_config, #{server_config => SigAlgs ++ ServerOpts,
+ client_config => SigAlgs ++ClientOpts}} |
proplists:delete(tls_config, Config)];
init_certs(srp_dss, Config) ->
{ClientOpts, ServerOpts} = ssl_test_lib:make_dsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
@@ -954,10 +958,6 @@ test_ciphers(Kex, Cipher, Version) ->
end, Ciphers).
-openssl_suitestr_to_map(OpenSSLSuiteStrs) ->
- [ssl_cipher_format:suite_openssl_str_to_map(SuiteStr) || SuiteStr <- OpenSSLSuiteStrs].
-
-
supported_cipher(Cipher, CipherStr) ->
SupCrypto = proplists:get_value(ciphers, crypto:supports()),
SupOpenssl = [OCipher || OCipher <- ssl_test_lib:openssl_ciphers(), string:find(OCipher, CipherStr) =/= nomatch],
diff --git a/lib/ssl/test/openssl_client_cert_SUITE.erl b/lib/ssl/test/openssl_client_cert_SUITE.erl
index 018b49e0b7..36b098bd49 100644
--- a/lib/ssl/test/openssl_client_cert_SUITE.erl
+++ b/lib/ssl/test/openssl_client_cert_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -71,7 +71,7 @@
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
-all() ->
+all() ->
[
{group, openssl_client}
].
@@ -99,7 +99,7 @@ groups() ->
].
protocol_groups() ->
- case ssl_test_lib:openssl_sane_dtls() of
+ case ssl_test_lib:openssl_sane_dtls() of
true ->
[{group, 'tlsv1.3'},
{group, 'tlsv1.2'},
@@ -113,7 +113,7 @@ protocol_groups() ->
{group, 'tlsv1.1'},
{group, 'tlsv1'}
]
- end.
+ end.
pre_tls_1_3_protocol_groups() ->
[{group, rsa},
@@ -168,34 +168,34 @@ init_per_group(Group, Config0) when Group == rsa;
SOpts = proplists:get_value(server_rsa_opts, Config),
%% Make sure _rsa* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) ->
true;
- (ecdhe_rsa) ->
+ (ecdhe_rsa) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, rsa} |
- lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
[] ->
{skip, {no_sup, Group, Version}}
end;
-init_per_group(Alg, Config) when
+init_per_group(Alg, Config) when
Alg == rsa_pss_rsae;
Alg == rsa_pss_pss;
Alg == rsa_pss_rsae_1_3;
Alg == rsa_pss_pss_1_3 ->
Supports = crypto:supports(),
RSAOpts = proplists:get_value(rsa_opts, Supports),
-
- case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
- andalso lists:member(rsa_pss_saltlen, RSAOpts)
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
andalso lists:member(rsa_mgf1_md, RSAOpts)
andalso ssl_test_lib:is_sane_oppenssl_pss(rsa_alg(Alg))
of
@@ -214,9 +214,9 @@ init_per_group(Alg, Config) when
init_per_group(Group, Config0) when Group == ecdsa;
Group == ecdsa_1_3 ->
PKAlg = crypto:supports(public_keys),
- case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
- lists:member(dh, PKAlg))
- andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
+ case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
+ lists:member(dh, PKAlg))
+ andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
@@ -224,20 +224,20 @@ init_per_group(Group, Config0) when Group == ecdsa;
SOpts = proplists:get_value(server_ecdsa_opts, Config),
%% Make sure ecdh* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) ->
true;
- (ecdhe_ecdsa) ->
+ (ecdhe_ecdsa) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, ecdsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))]
)];
[] ->
@@ -277,42 +277,46 @@ init_per_group(eddsa_1_3, Config0) ->
end;
init_per_group(Group, Config0) when Group == dsa ->
PKAlg = crypto:supports(public_keys),
- case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg)
- andalso (ssl_test_lib:openssl_dsa_suites() =/= []) of
+ NVersion = ssl_test_lib:n_version(proplists:get_value(version, Config0)),
+ SigAlgs = ssl_test_lib:sig_algs(dsa, NVersion),
+ case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg)
+ andalso (ssl_test_lib:openssl_dsa_suites() =/= [])
+ andalso (ssl_test_lib:check_sane_openssl_dsa(Config0))
+ of
true ->
- Config = ssl_test_lib:make_dsa_cert(Config0),
- COpts = proplists:get_value(client_dsa_opts, Config),
- SOpts = proplists:get_value(server_dsa_opts, Config),
+ Config = ssl_test_lib:make_dsa_cert(Config0),
+ COpts = SigAlgs ++ proplists:get_value(client_dsa_opts, Config),
+ SOpts = SigAlgs ++ proplists:get_value(server_dsa_opts, Config),
%% Make sure dhe_dss* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
true;
- (dhe_dss) ->
+ (dhe_dss) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, dsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
+ {server_cert_opts, [{ciphers, Ciphers} | SOpts]} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
[] ->
{skip, {no_sup, Group, Version}}
end;
false ->
{skip, "Missing DSS crypto support"}
- end;
+ end;
init_per_group(GroupName, Config) ->
ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
ssl_test_lib:end_per_group(GroupName, Config).
-init_per_testcase(TestCase, Config) when
+init_per_testcase(TestCase, Config) when
TestCase == client_auth_empty_cert_accepted;
TestCase == client_auth_empty_cert_rejected ->
Version = ssl_test_lib:protocol_version(Config),
@@ -323,7 +327,7 @@ init_per_testcase(TestCase, Config) when
%% instead of sending EMPTY cert message in SSL-3.0 so empty cert test are not
%% relevant
{skip, openssl_behaves_differently};
- _ ->
+ _ ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 30}),
Config
@@ -333,7 +337,7 @@ init_per_testcase(_TestCase, Config) ->
ct:timetrap({seconds, 30}),
Config.
-end_per_testcase(_TestCase, Config) ->
+end_per_testcase(_TestCase, Config) ->
Config.
%%--------------------------------------------------------------------
@@ -366,10 +370,15 @@ client_auth_use_partial_chain() ->
[{doc, "Server does not trust an intermediat CA and fails the connetion as ROOT has expired"}].
client_auth_use_partial_chain(Config) when is_list(Config) ->
Prop = proplists:get_value(tc_group_properties, Config),
- DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(proplists:get_value(name, Prop)),
+ Group = proplists:get_value(name, Prop),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ Alg = proplists:get_value(cert_key_alg, Config),
+ DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(Group),
+ Ciphers = appropriate_ciphers(Group, Version),
+
{Year, Month, Day} = date(),
#{client_config := ClientOpts0,
- server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(proplists:get_value(cert_key_alg, Config),
+ server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(Alg,
[{client_chain,
[[{validity, {{Year-2, Month, Day},
{Year-1, Month, Day}}}],
@@ -391,7 +400,7 @@ client_auth_use_partial_chain(Config) when is_list(Config) ->
end
end,
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, {partial_chain, PartialChain} |
- ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config)],
+ ssl_test_lib:ssl_options(extra_server, [{ciphers, Ciphers} | ServerOpts0], Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
%% Have to use partial chain functionality on side running Erlang (we are not testing OpenSSL features)
@@ -399,10 +408,15 @@ client_auth_do_not_use_partial_chain() ->
ssl_cert_tests:client_auth_do_not_use_partial_chain().
client_auth_do_not_use_partial_chain(Config) when is_list(Config) ->
Prop = proplists:get_value(tc_group_properties, Config),
- DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(proplists:get_value(name, Prop)),
+ Group = proplists:get_value(name, Prop),
+ DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(Group),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ Alg = proplists:get_value(cert_key_alg, Config),
+ Ciphers = appropriate_ciphers(Group, Version),
+
{Year, Month, Day} = date(),
#{client_config := ClientOpts0,
- server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(proplists:get_value(cert_key_alg, Config),
+ server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(Alg,
[{client_chain,
[[{validity, {{Year-2, Month, Day},
{Year-1, Month, Day}}}],
@@ -415,7 +429,7 @@ client_auth_do_not_use_partial_chain(Config) when is_list(Config) ->
end,
ClientOpts = ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config),
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, {partial_chain, PartialChain} |
- ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config)],
+ ssl_test_lib:ssl_options(extra_server, [{ciphers, Ciphers} | ServerOpts0], Config)],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_expired).
%%--------------------------------------------------------------------
@@ -423,24 +437,29 @@ client_auth_do_not_use_partial_chain(Config) when is_list(Config) ->
client_auth_partial_chain_fun_fail() ->
ssl_cert_tests:client_auth_partial_chain_fun_fail().
client_auth_partial_chain_fun_fail(Config) when is_list(Config) ->
- Prop = proplists:get_value(tc_group_properties, Config),
- DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(proplists:get_value(name, Prop)),
+ Prop = proplists:get_value(tc_group_properties, Config),
+ Group = proplists:get_value(name, Prop),
+ DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(Group),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ Alg = proplists:get_value(cert_key_alg, Config),
+ Ciphers = appropriate_ciphers(Group, Version),
+
{Year, Month, Day} = date(),
#{client_config := ClientOpts0,
- server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(proplists:get_value(cert_key_alg, Config),
+ server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(Alg,
[{client_chain,
[[{validity, {{Year-2, Month, Day},
{Year-1, Month, Day}}}],
[],
[]
]},
- {server_chain, DefaultCertConf}], Config, "do_not_use_partial_chain"),
+ {server_chain, DefaultCertConf}], Config, "partial_chain_fun_fail"),
PartialChain = fun(_CertChain) ->
error(crash_on_purpose)
end,
ClientOpts = ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config),
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, {partial_chain, PartialChain} |
- ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config)],
+ ssl_test_lib:ssl_options(extra_server, [{ciphers, Ciphers} | ServerOpts0], Config)],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_expired).
%%--------------------------------------------------------------------
@@ -509,3 +528,8 @@ openssl_sig_algs(rsa_pss_pss_1_3) ->
[{sigalgs, "rsa_pss_rsae_sha512:rsa_pss_rsae_sha384:rsa_pss_pss_sha256"}];
openssl_sig_algs(rsa_pss_rsae_1_3) ->
[{sigalgs,"rsa_pss_rsae_sha512:rsa_pss_rsae_sha384:rsa_pss_rsae_sha256"}].
+
+appropriate_ciphers(dsa, Version) ->
+ ssl:cipher_suites(all, Version);
+appropriate_ciphers(_, Version) ->
+ ssl:cipher_suites(default, Version).
diff --git a/lib/ssl/test/openssl_mfl_SUITE.erl b/lib/ssl/test/openssl_mfl_SUITE.erl
index 54a6788966..1acd18e422 100644
--- a/lib/ssl/test/openssl_mfl_SUITE.erl
+++ b/lib/ssl/test/openssl_mfl_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -111,7 +111,7 @@ openssl_client(Config) when is_list(Config) ->
%--------------------------------------------------------------------------------
reuse_session_erlang_server(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = proplists:get_value(client_rsa_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
Protocol = proplists:get_value(protocol, ServerOpts, tls),
{_, ServerNode, _} = ssl_test_lib:run_where(Config),
MFL = 512,
@@ -135,7 +135,7 @@ reuse_session_erlang_server(Config) when is_list(Config) ->
reuse_session_erlang_client(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = proplists:get_value(server_rsa_opts, Config),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
Protocol = proplists:get_value(protocol, ClientOpts0, tls),
@@ -180,7 +180,7 @@ reuse_session_erlang_client(Config) when is_list(Config) ->
openssl_client(MFL, Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = proplists:get_value(client_rsa_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
Protocol = proplists:get_value(protocol, ServerOpts, tls),
{_, ServerNode, _} = ssl_test_lib:run_where(Config),
@@ -204,7 +204,7 @@ openssl_client(MFL, Config) ->
%%--------------------------------------------------------------------------------
openssl_server(MFL, Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = proplists:get_value(server_rsa_opts, Config),
Protocol = proplists:get_value(protocol, ClientOpts, tls),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
diff --git a/lib/ssl/test/openssl_npn_SUITE.erl b/lib/ssl/test/openssl_npn_SUITE.erl
index ccfd6e45c0..fb8aee6ef7 100644
--- a/lib/ssl/test/openssl_npn_SUITE.erl
+++ b/lib/ssl/test/openssl_npn_SUITE.erl
@@ -37,6 +37,7 @@
%% Test cases
-export([erlang_client_openssl_server_npn/0,
erlang_client_openssl_server_npn/1,
+ erlang_server_openssl_client_npn/0,
erlang_server_openssl_client_npn/1,
erlang_server_openssl_client_npn_only_client/1,
erlang_server_openssl_client_npn_only_server/1,
@@ -225,8 +226,8 @@ erlang_server_openssl_client_npn(Config) when is_list(Config) ->
%%--------------------------------------------------------------------------
-erlang_server_openssl_client_npn_renegotiate() ->
- [{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}].
+%% erlang_server_openssl_client_npn_renegotiate() ->
+%% [{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}].
erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) ->
ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
@@ -317,17 +318,18 @@ erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
Server =
ssl_test_lib:start_server(erlang, [{from, self()}],
- [{server_opts, [{client_preferred_next_protocols,
- {client, [<<"spdy/2">>], <<"http/1.1">>}
- } | ServerOpts]} | Config]),
+ [{server_opts,
+ [{next_protocols_advertised,
+ [<<"spdy/2">>, <<"http/1.1">>]}
+ | ServerOpts]} | Config]),
Port = ssl_test_lib:inet_port(Server),
- {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port},
- {options, ClientOpts},
+ {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port},
+ {options, ClientOpts},
return_port], Config),
-
+
Server ! get_socket,
- SSocket =
- receive
+ SSocket =
+ receive
{Server, {socket, Socket}} ->
Socket
end,
diff --git a/lib/ssl/test/openssl_ocsp_SUITE.erl b/lib/ssl/test/openssl_ocsp_SUITE.erl
index 800ce3ce78..045915cc84 100644
--- a/lib/ssl/test/openssl_ocsp_SUITE.erl
+++ b/lib/ssl/test/openssl_ocsp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -35,17 +35,18 @@
end_per_testcase/2]).
%% Testcases
--export([ocsp_stapling_basic/0,ocsp_stapling_basic/1,
- ocsp_stapling_with_nonce/0, ocsp_stapling_with_nonce/1,
- ocsp_stapling_with_responder_cert/0,ocsp_stapling_with_responder_cert/1,
- ocsp_stapling_revoked/0, ocsp_stapling_revoked/1,
- ocsp_stapling_undetermined/0, ocsp_stapling_undetermined/1,
- ocsp_stapling_no_staple/0, ocsp_stapling_no_staple/1
+-export([stapling_basic/0, stapling_basic/1,
+ stapling_with_nonce/0, stapling_with_nonce/1,
+ stapling_with_responder_cert/0, stapling_with_responder_cert/1,
+ stapling_revoked/0, stapling_revoked/1,
+ stapling_undetermined/0, stapling_undetermined/1,
+ stapling_no_staple/0, stapling_no_staple/1
]).
%% spawn export
--export([ocsp_responder_init/3]).
-
+-export([ocsp_responder_init/4]).
+-define(OCSP_RESPONDER_LOG, "ocsp_resp_log.txt").
+-define(DEBUG, false).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -61,17 +62,18 @@ groups() ->
{'dtlsv1.2', [], ocsp_tests()}].
ocsp_tests() ->
- [ocsp_stapling_basic,
- ocsp_stapling_with_nonce,
- ocsp_stapling_with_responder_cert,
- ocsp_stapling_revoked,
- ocsp_stapling_undetermined,
- ocsp_stapling_no_staple
+ [stapling_basic,
+ stapling_with_nonce,
+ stapling_with_responder_cert,
+ stapling_revoked,
+ stapling_undetermined,
+ stapling_no_staple
].
%%--------------------------------------------------------------------
init_per_suite(Config0) ->
- Config = ssl_test_lib:init_per_suite(Config0, openssl),
+ Config = lists:merge([{debug, ?DEBUG}],
+ ssl_test_lib:init_per_suite(Config0, openssl)),
case ssl_test_lib:openssl_ocsp_support(Config) of
true ->
do_init_per_suite(Config);
@@ -87,7 +89,7 @@ do_init_per_suite(Config) ->
{ok, _} = make_certs:all(DataDir, PrivDir),
ResponderPort = get_free_port(),
- Pid = start_ocsp_responder(ResponderPort, PrivDir),
+ Pid = start_ocsp_responder(ResponderPort, PrivDir, ?config(debug, Config)),
NewConfig =
lists:merge(
@@ -97,10 +99,10 @@ do_init_per_suite(Config) ->
ssl_test_lib:cert_options(NewConfig).
-
end_per_suite(Config) ->
ResponderPid = proplists:get_value(responder_pid, Config),
ssl_test_lib:close(ResponderPid),
+ [ssl_test_lib:ct_pal_file(?OCSP_RESPONDER_LOG) || ?config(debug, Config)],
ssl_test_lib:end_per_suite(Config).
%%--------------------------------------------------------------------
@@ -122,31 +124,30 @@ end_per_testcase(_TestCase, Config) ->
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
-ocsp_stapling_basic() ->
+stapling_basic() ->
[{doc, "Verify OCSP stapling works without nonce and responder certs."}].
-ocsp_stapling_basic(Config)
+stapling_basic(Config)
when is_list(Config) ->
- ocsp_stapling_helper(Config, [{ocsp_nonce, false}]).
+ stapling_helper(Config, [{ocsp_nonce, false}]).
-ocsp_stapling_with_nonce() ->
+stapling_with_nonce() ->
[{doc, "Verify OCSP stapling works with nonce."}].
-ocsp_stapling_with_nonce(Config)
+stapling_with_nonce(Config)
when is_list(Config) ->
- ocsp_stapling_helper(Config, [{ocsp_nonce, true}]).
+ stapling_helper(Config, [{ocsp_nonce, true}]).
-ocsp_stapling_with_responder_cert() ->
+stapling_with_responder_cert() ->
[{doc, "Verify OCSP stapling works with nonce and responder certs."}].
-ocsp_stapling_with_responder_cert(Config)
+stapling_with_responder_cert(Config)
when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
{ok, ResponderCert} =
file:read_file(filename:join(PrivDir, "b.server/cert.pem")),
[{'Certificate', Der, _IsEncrypted}] =
public_key:pem_decode(ResponderCert),
- ocsp_stapling_helper(Config, [{ocsp_nonce, true},
- {ocsp_responder_certs, [Der]}]).
+ stapling_helper(Config, [{ocsp_nonce, true}, {ocsp_responder_certs, [Der]}]).
-ocsp_stapling_helper(Config, Opts) ->
+stapling_helper(Config, Opts) ->
PrivDir = proplists:get_value(priv_dir, Config),
CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"),
Data = "ping", %% 4 bytes
@@ -159,7 +160,8 @@ ocsp_stapling_helper(Config, Opts) ->
ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer},
{cacertfile, CACertsFile},
{server_name_indication, disable},
- {ocsp_stapling, true}] ++ Opts, Config),
+ {ocsp_stapling, true}] ++ Opts,
+ Config),
Client = ssl_test_lib:start_client(erlang,
[{port, Port},
{options, ClientOpts}], Config),
@@ -169,29 +171,29 @@ ocsp_stapling_helper(Config, Opts) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
-ocsp_stapling_revoked() ->
+stapling_revoked() ->
[{doc, "Verify OCSP stapling works with revoked certificate."}].
-ocsp_stapling_revoked(Config)
+stapling_revoked(Config)
when is_list(Config) ->
- ocsp_stapling_negative_helper(Config, "revoked/cacerts.pem",
+ stapling_negative_helper(Config, "revoked/cacerts.pem",
openssl_ocsp_revoked, certificate_revoked).
-ocsp_stapling_undetermined() ->
+stapling_undetermined() ->
[{doc, "Verify OCSP stapling works with certificate with undetermined status."}].
-ocsp_stapling_undetermined(Config)
+stapling_undetermined(Config)
when is_list(Config) ->
- ocsp_stapling_negative_helper(Config, "undetermined/cacerts.pem",
+ stapling_negative_helper(Config, "undetermined/cacerts.pem",
openssl_ocsp_undetermined, bad_certificate).
-ocsp_stapling_no_staple() ->
+stapling_no_staple() ->
[{doc, "Verify OCSP stapling works with a missing OCSP response."}].
-ocsp_stapling_no_staple(Config)
+stapling_no_staple(Config)
when is_list(Config) ->
%% Start a server that will not include an OCSP response.
- ocsp_stapling_negative_helper(Config, "a.server/cacerts.pem",
+ stapling_negative_helper(Config, "a.server/cacerts.pem",
openssl, bad_certificate).
-ocsp_stapling_negative_helper(Config, CACertsPath, ServerVariant, ExpectedError) ->
+stapling_negative_helper(Config, CACertsPath, ServerVariant, ExpectedError) ->
PrivDir = proplists:get_value(priv_dir, Config),
CACertsFile = filename:join(PrivDir, CACertsPath),
GroupName = undefined,
@@ -214,12 +216,13 @@ ocsp_stapling_negative_helper(Config, CACertsPath, ServerVariant, ExpectedError)
ssl_test_lib:check_client_alert(Client, ExpectedError).
%%--------------------------------------------------------------------
-%% Intrernal functions -----------------------------------------------
+%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
-start_ocsp_responder(ResponderPort, PrivDir) ->
+start_ocsp_responder(ResponderPort, PrivDir, Debug) ->
Starter = self(),
- Pid = erlang:spawn_link(
- ?MODULE, ocsp_responder_init, [ResponderPort, PrivDir, Starter]),
+ Pid = erlang:spawn(
+ ?MODULE, ocsp_responder_init,
+ [ResponderPort, PrivDir, Starter, Debug]),
receive
{started, Pid} ->
Pid;
@@ -227,31 +230,40 @@ start_ocsp_responder(ResponderPort, PrivDir) ->
throw({unable_to_start_ocsp_service, Reason})
end.
-ocsp_responder_init(ResponderPort, PrivDir, Starter) ->
+ocsp_responder_init(ResponderPort, PrivDir, Starter, Debug) ->
Index = filename:join(PrivDir, "otpCA/index.txt"),
CACerts = filename:join(PrivDir, "b.server/cacerts.pem"),
Cert = filename:join(PrivDir, "b.server/cert.pem"),
Key = filename:join(PrivDir, "b.server/key.pem"),
-
+ DebugArgs = case Debug of
+ true -> ["-text", "-out", ?OCSP_RESPONDER_LOG];
+ _ -> []
+ end,
Args = ["ocsp", "-index", Index, "-CA", CACerts, "-rsigner", Cert,
- "-rkey", Key, "-port", erlang:integer_to_list(ResponderPort)],
+ "-rkey", Key, "-port", erlang:integer_to_list(ResponderPort)] ++
+ DebugArgs,
process_flag(trap_exit, true),
Port = ssl_test_lib:portable_open_port("openssl", Args),
+ ?CT_LOG("OCSP responder: Started Port = ~p", [Port]),
ocsp_responder_loop(Port, {new, Starter}).
ocsp_responder_loop(Port, {Status, Starter} = State) ->
receive
- {_Port, closed} ->
- ?LOG("Port Closed"),
+ close ->
+ ?CT_LOG("OCSP responder: received close", []),
+ ok;
+ {Port, closed} ->
+ ?CT_LOG("OCSP responder: Port = ~p Closed", [Port]),
ok;
- {'EXIT', _Port, Reason} ->
- ?LOG("Port Closed ~p",[Reason]),
+ {'EXIT', Sender, _Reason} ->
+ ?CT_LOG("OCSP responder: Sender = ~p Closed",[Sender]),
ok;
- {Port, {data, _Msg}} when Status == new ->
+ {Port, {data, Msg}} when Status == new ->
+ ?CT_LOG("OCSP responder: Msg = ~p", [Msg]),
Starter ! {started, self()},
ocsp_responder_loop(Port, {started, undefined});
{Port, {data, Msg}} ->
- ?PAL("Responder Msg ~p",[Msg]),
+ ?CT_LOG("OCSP responder: Responder Msg = ~p",[Msg]),
ocsp_responder_loop(Port, State)
after 1000 ->
case Status of
diff --git a/lib/ssl/test/openssl_renegotiate_SUITE.erl b/lib/ssl/test/openssl_renegotiate_SUITE.erl
index 01b5a7a8a9..5102730396 100644
--- a/lib/ssl/test/openssl_renegotiate_SUITE.erl
+++ b/lib/ssl/test/openssl_renegotiate_SUITE.erl
@@ -230,7 +230,7 @@ erlang_server_openssl_client_nowrap_seqnum() ->
erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) ->
process_flag(trap_exit, true),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{_, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
diff --git a/lib/ssl/test/openssl_server_cert_SUITE.erl b/lib/ssl/test/openssl_server_cert_SUITE.erl
index 7f7a9b739e..057d80b6f3 100644
--- a/lib/ssl/test/openssl_server_cert_SUITE.erl
+++ b/lib/ssl/test/openssl_server_cert_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -74,7 +74,7 @@
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
-all() ->
+all() ->
[
{group, openssl_server}].
@@ -103,7 +103,7 @@ groups() ->
].
protocol_groups() ->
- case ssl_test_lib:openssl_sane_dtls() of
+ case ssl_test_lib:openssl_sane_dtls() of
true ->
[{group, 'tlsv1.3'},
{group, 'tlsv1.2'},
@@ -117,7 +117,7 @@ protocol_groups() ->
{group, 'tlsv1.1'},
{group, 'tlsv1'}
]
- end.
+ end.
pre_tls_1_3_protocol_groups() ->
[{group, rsa},
@@ -156,27 +156,27 @@ end_per_suite(Config) ->
init_per_group(openssl_server, Config0) ->
Config = proplists:delete(server_type, proplists:delete(client_type, Config0)),
- [{client_type, erlang}, {server_type, openssl} | Config];
+ [{client_type, erlang}, {server_type, openssl} | Config];
init_per_group(rsa = Group, Config0) ->
Config = ssl_test_lib:make_rsa_cert(Config0),
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
%% Make sure _rsa* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) ->
true;
- (ecdhe_rsa) ->
+ (ecdhe_rsa) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, rsa} |
- lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
[] ->
{skip, {no_sup, Group, Version}}
@@ -203,9 +203,9 @@ init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
Alg == rsa_pss_pss ->
Supports = crypto:supports(),
RSAOpts = proplists:get_value(rsa_opts, Supports),
-
- case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
- andalso lists:member(rsa_pss_saltlen, RSAOpts)
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
andalso lists:member(rsa_mgf1_md, RSAOpts)
andalso ssl_test_lib:is_sane_oppenssl_pss(Alg)
of
@@ -223,9 +223,9 @@ init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
end;
init_per_group(ecdsa = Group, Config0) ->
PKAlg = crypto:supports(public_keys),
- case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
- lists:member(dh, PKAlg))
- andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
+ case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
+ lists:member(dh, PKAlg))
+ andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
@@ -233,20 +233,20 @@ init_per_group(ecdsa = Group, Config0) ->
SOpts = proplists:get_value(server_ecdsa_opts, Config),
%% Make sure ecdh* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) ->
true;
- (ecdhe_ecdsa) ->
+ (ecdhe_ecdsa) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, ecdsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))]
)];
[] ->
@@ -258,8 +258,8 @@ init_per_group(ecdsa = Group, Config0) ->
init_per_group(ecdsa_1_3 = Group, Config0) ->
PKAlg = crypto:supports(public_keys),
case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
- lists:member(dh, PKAlg))
- andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
+ lists:member(dh, PKAlg))
+ andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
@@ -311,35 +311,37 @@ init_per_group(eddsa_1_3, Config0) ->
end;
init_per_group(dsa = Group, Config0) ->
PKAlg = crypto:supports(public_keys),
- case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) andalso
- (ssl_test_lib:openssl_dsa_suites() =/= []) of
+ case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) andalso
+ (ssl_test_lib:openssl_dsa_suites() =/= [])
+ andalso (ssl_test_lib:check_sane_openssl_dsa(Config0))
+ of
true ->
- Config = ssl_test_lib:make_dsa_cert(Config0),
+ Config = ssl_test_lib:make_dsa_cert(Config0),
COpts = proplists:get_value(client_dsa_opts, Config),
SOpts = proplists:get_value(server_dsa_opts, Config),
%% Make sure dhe_dss* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
true;
- (dhe_dss) ->
+ (dhe_dss) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, dsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts] ++ ssl_test_lib:sig_algs(dsa, Version)},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
[] ->
{skip, {no_sup, Group, Version}}
end;
false ->
{skip, "Missing DSS crypto support"}
- end;
+ end;
init_per_group(GroupName, Config) ->
case ssl_test_lib:is_protocol_version(GroupName) of
true ->
@@ -361,7 +363,7 @@ init_per_testcase(_TestCase, Config) ->
ct:timetrap({seconds, 30}),
Config.
-end_per_testcase(_TestCase, Config) ->
+end_per_testcase(_TestCase, Config) ->
Config.
%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/property_test/ssl_eqc_chain.erl b/lib/ssl/test/property_test/ssl_eqc_chain.erl
index 8ac8446cab..26bba1fc4a 100644
--- a/lib/ssl/test/property_test/ssl_eqc_chain.erl
+++ b/lib/ssl/test/property_test/ssl_eqc_chain.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2018-2021. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -58,12 +58,14 @@
%%--------------------------------------------------------------------
prop_tls_unordered_path(PrivDir) ->
?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), unordered_options(Version, PrivDir)),
- try
+ try
[TLSVersion] = proplists:get_value(versions, ClientOptions),
- ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
- {client_type, erlang},
- {version, TLSVersion}
- ])
+ SigAlgs = signature_algs(TLSVersion),
+ ssl_test_lib:basic_test(SigAlgs ++ ClientOptions,
+ SigAlgs ++ ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
of
_ ->
true
@@ -75,16 +77,18 @@ prop_tls_unordered_path(PrivDir) ->
prop_tls_extraneous_path(PrivDir) ->
?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), extraneous_options(Version, PrivDir)),
- try
+ try
[TLSVersion] = proplists:get_value(versions, ClientOptions),
- ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
- {client_type, erlang},
- {version, TLSVersion}
- ])
- of
+ SigAlgs = signature_algs(TLSVersion),
+ ssl_test_lib:basic_test(SigAlgs ++ ClientOptions,
+ SigAlgs ++ ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
_ ->
- true
- catch
+ true
+ catch
_:_ ->
false
end
@@ -92,15 +96,17 @@ prop_tls_extraneous_path(PrivDir) ->
prop_tls_extraneous_paths() ->
?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), extra_extraneous_options(Version)),
- try
+ try
[TLSVersion] = proplists:get_value(versions, ClientOptions),
- ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
- {client_type, erlang},
- {version, TLSVersion}
- ])
+ SigAlgs = signature_algs(TLSVersion),
+ ssl_test_lib:basic_test(SigAlgs ++ ClientOptions,
+ SigAlgs ++ ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
of
- _ ->
- true
+ _ ->
+ true
catch
_:_ ->
false
@@ -109,15 +115,17 @@ prop_tls_extraneous_paths() ->
prop_tls_extraneous_and_unordered_path() ->
?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), unordered_extraneous_options(Version)),
- try
+ try
[TLSVersion] = proplists:get_value(versions, ClientOptions),
- ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
- {client_type, erlang},
- {version, TLSVersion}
- ])
+ SigAlgs = signature_algs(TLSVersion),
+ ssl_test_lib:basic_test(SigAlgs ++ ClientOptions,
+ SigAlgs ++ ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
of
- _ ->
- true
+ _ ->
+ true
catch
_:_ ->
false
@@ -128,13 +136,16 @@ prop_client_cert_auth() ->
?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), client_cert_auth_opts(Version)),
try
[TLSVersion] = proplists:get_value(versions, ClientOptions),
- ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
- {client_type, erlang},
- {version, TLSVersion}
- ])
+ SigAlgs = signature_algs(TLSVersion),
+ ssl_test_lib:basic_test(SigAlgs ++ ClientOptions,
+ SigAlgs ++ ServerOptions,
+ [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
of
- _ ->
- true
+ _ ->
+ true
catch
_:_ ->
false
@@ -146,24 +157,24 @@ prop_client_cert_auth() ->
%%--------------------------------------------------------------------
tls_version() ->
Versions = [Version || Version <- ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', 'tlsv1', 'dtlsv1.2', 'dtlsv1'],
- ssl_test_lib:sufficient_crypto_support(Version)
+ ssl_test_lib:sufficient_crypto_support(Version)
],
oneof(Versions).
key_alg(Version) when Version == 'tlsv1.3';
Version == 'tlsv1.2';
Version == 'dtlsv1.2'->
- oneof([rsa, ecdsa]);
+ oneof([rsa, ecdsa]);
key_alg(_) ->
oneof([rsa]).
server_options('tlsv1.3') ->
- [{verify, verify_peer},
- {fail_if_no_peer_cert, true},
+ [{verify, verify_peer},
+ {fail_if_no_peer_cert, true},
{reuseaddr, true}];
server_options(_) ->
- [{verify, verify_peer},
- {fail_if_no_peer_cert, true},
+ [{verify, verify_peer},
+ {fail_if_no_peer_cert, true},
{reuse_sessions, false},
{reuseaddr, true}].
@@ -184,28 +195,28 @@ unordered_der_cert_chain_opts(Version, Alg) ->
client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => #{root => root_key(Alg),
intermediates => intermediates(Alg, 4),
peer => peer_key(Alg)},
- client_chain => #{root => root_key(Alg),
+ client_chain => #{root => root_key(Alg),
intermediates => intermediates(Alg, 4),
- peer => peer_key(Alg)}}),
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ClientConf)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ServerConf)]}.
+ peer => peer_key(Alg)}}),
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ClientConf)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ServerConf)]}.
unordered_pem_cert_chain_opts(Version, Alg, PrivDir) ->
Index = integer_to_list(erlang:unique_integer()),
DerConfig = public_key:pkix_test_data(#{server_chain => #{root => root_key(Alg),
intermediates => intermediates(Alg, 4),
peer => peer_key(Alg)},
- client_chain => #{root => root_key(Alg),
+ client_chain => #{root => root_key(Alg),
intermediates => intermediates(Alg, 4),
- peer => peer_key(Alg)}}),
+ peer => peer_key(Alg)}}),
ClientBase = filename:join(PrivDir, "client_prop_test" ++ Index),
- SeverBase = filename:join(PrivDir, "server_prop_test" ++ Index),
+ SeverBase = filename:join(PrivDir, "server_prop_test" ++ Index),
PemConfig = x509_test:gen_pem_config_files(DerConfig, ClientBase, SeverBase),
ClientConf = proplists:get_value(client_config, PemConfig),
ServerConf = proplists:get_value(server_config, PemConfig),
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ClientConf)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ServerConf)]}.
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ClientConf)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ServerConf)]}.
unordered_der_conf(Config) ->
Cert = proplists:get_value(cert, Config),
@@ -253,101 +264,101 @@ client_cert_auth_opts(Version) ->
?LET({SAlg, CAlg}, {key_alg(Version), key_alg(Version)}, der_cert_chains(Version, CAlg,SAlg)).
extraneous_der_cert_chain_opts(Version, Alg) ->
- #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
- #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
#{server_config := ServerConf0,
client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
intermediates => intermediates(Alg, 1),
peer => peer_key(ecdsa)},
- client_chain => #{root => CRoot,
+ client_chain => #{root => CRoot,
intermediates => intermediates(Alg, 1),
peer => peer_key(ecdsa)}}),
-
+
{ClientChain, ClientRoot} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 1),
{ServerChain, ServerRoot} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 1),
-
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ClientChain, ServerRoot, [OrgSRoot], ClientConf0)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ServerChain, ClientRoot, [OrgCRoot], ServerConf0)]}.
+
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot, [OrgSRoot], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot, [OrgCRoot], ServerConf0)]}.
extraneous_pem_cert_chain_opts(Version, Alg, PrivDir) ->
- #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
- #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
#{server_config := ServerConf0,
client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
intermediates => intermediates(Alg, 1),
peer => peer_key(ecdsa)},
- client_chain => #{root => CRoot,
+ client_chain => #{root => CRoot,
intermediates => intermediates(Alg, 1),
peer => peer_key(ecdsa)}}),
{ClientChain, ClientRoot} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 1),
{ServerChain, ServerRoot} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 1),
-
+
%% Only use files in final step
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_pem_conf(ClientChain, ServerRoot, OrgSRoot, ClientConf0, PrivDir)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_pem_conf(ServerChain, ClientRoot, OrgCRoot, ServerConf0, PrivDir)]}.
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_pem_conf(ClientChain, ServerRoot, OrgSRoot, ClientConf0, PrivDir)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_pem_conf(ServerChain, ClientRoot, OrgCRoot, ServerConf0, PrivDir)]}.
extra_extraneous_der_cert_chain_opts(Version, Alg) ->
- #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
- #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
#{server_config := ServerConf0,
client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
intermediates => intermediates(Alg, 3),
peer => peer_key(ecdsa)},
- client_chain => #{root => CRoot,
+ client_chain => #{root => CRoot,
intermediates => intermediates(Alg, 3),
peer => peer_key(ecdsa)}}),
-
-
+
+
{ClientChain0, ClientRoot0} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
{ServerChain0, ServerRoot0} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
-
+
{ClientChain1, ClientRoot1} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
{ServerChain1, ServerRoot1} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
-
+
{ClientChain, ServerChain} = create_extraneous_chains(ClientChain0, ClientChain1,
ServerChain0, ServerChain1),
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
der_extraneous_and_unorder_chain(Version, Alg) ->
- #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
- #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
#{server_config := ServerConf0,
- client_config := ClientConf0} =
+ client_config := ClientConf0} =
public_key:pkix_test_data(#{server_chain => #{root => SRoot,
intermediates => intermediates(Alg, 3),
peer => peer_key(ecdsa)},
- client_chain => #{root => CRoot,
+ client_chain => #{root => CRoot,
intermediates => intermediates(Alg, 3),
peer => peer_key(ecdsa)}}),
{ClientChain0, ClientRoot0} = chain_and_root(ClientConf0),
{ServerChain0, ServerRoot0} = chain_and_root(ServerConf0),
-
+
{ClientChain1, ClientRoot1} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
{ServerChain1, ServerRoot1} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
-
+
{ClientChain, ServerChain} = create_extraneous_and_unorded(ClientChain0, ClientChain1,
ServerChain0, ServerChain1),
-
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
+
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
der_cert_chains(Version, CAlg, SAlg) ->
SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(SAlg)),
@@ -384,7 +395,7 @@ extraneous_der_conf(Chain, NewRoot, OrgRoots,Config0) ->
CaCerts = proplists:get_value(cacerts, Config0),
Config1 = [{cert, Chain} | proplists:delete(cert, Config0)],
[{cacerts, [NewRoot | CaCerts -- OrgRoots]} | proplists:delete(cacerts, Config1)].
-
+
extraneous_pem_conf(Chain, NewRoot, OldRoot, Config0, PrivDir) ->
Int = erlang:unique_integer(),
FileName = filename:join(PrivDir, "prop_test" ++ integer_to_list(Int)),
@@ -409,7 +420,7 @@ create_extraneous_chains([Client, _CCA0, _CCA1, CCA2, _CCA3], [Client, OCCA0, OC
create_extraneous_and_unorded([Client, _CCA0, _CCA1, CCA2, _CCA3], [Client, OCCA0, OCCA1, OCCA2, OCROOT],
[Server, _SCA0, _SCA1, SCA2, _SROOT], [Server, OSCA0, OSCA1, OSCA2, OSROOT]) ->
{[Client, OCCA0, CCA2, OCCA2, OCROOT, OCCA1], [Server, OSCA0, SCA2, OSCA2, OSROOT, OSCA1]}.
-
+
root_key(ecdsa) ->
[{key,{namedCurve, ?secp256r1}}]; %% Use a curve that will be default supported in all TLS versions
root_key(rsa) ->
@@ -436,8 +447,16 @@ hardcode_rsa_keys([Head | Tail], N, Acc) ->
new_intermediat(CA0, Key) ->
OTPCert = public_key:pkix_decode_cert(CA0, otp),
- TBSCert = OTPCert#'OTPCertificate'.tbsCertificate,
+ TBSCert = OTPCert#'OTPCertificate'.tbsCertificate,
Num = TBSCert#'OTPTBSCertificate'.serialNumber,
- public_key:pkix_sign(TBSCert#'OTPTBSCertificate'{serialNumber = Num+1}, Key).
+ public_key:pkix_sign(TBSCert#'OTPTBSCertificate'{serialNumber = Num+1}, Key).
+
+signature_algs('tlsv1.3') ->
+ [ssl_test_lib:all_sig_algs()];
+signature_algs(Version) when Version == 'tlsv1.2';
+ Version == 'dtlsv1.2' ->
+ [ssl_test_lib:all_1_2_sig_algs()];
+signature_algs(_) ->
+ [].
diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
index 6d9d84e6ae..8f5aaedd1c 100644
--- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl
+++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -57,11 +57,8 @@
-include_lib("ssl/src/ssl_handshake.hrl").
-include_lib("ssl/src/ssl_alert.hrl").
-include_lib("ssl/src/ssl_internal.hrl").
+-include_lib("ssl/src/ssl_record.hrl").
--define('TLS_v1.3', {3,4}).
--define('TLS_v1.2', {3,3}).
--define('TLS_v1.1', {3,2}).
--define('TLS_v1', {3,1}).
%%--------------------------------------------------------------------
%% Properties --------------------------------------------------------
@@ -87,7 +84,7 @@ prop_tls_hs_encode_decode() ->
%% Message Generators -----------------------------------------------
%%--------------------------------------------------------------------
-tls_msg(?'TLS_v1.3'= Version) ->
+tls_msg(?TLS_1_3= Version) ->
oneof([client_hello(Version),
server_hello(Version),
%%new_session_ticket()
@@ -116,9 +113,9 @@ tls_msg(Version) ->
%%
%% Shared messages
%%
-client_hello(?'TLS_v1.3' = Version) ->
+client_hello(?TLS_1_3 = Version) ->
#client_hello{session_id = session_id(),
- client_version = ?'TLS_v1.2',
+ client_version = ?TLS_1_2,
cipher_suites = cipher_suites(Version),
compression_methods = compressions(Version),
random = client_random(Version),
@@ -133,8 +130,8 @@ client_hello(Version) ->
extensions = client_hello_extensions(Version)
}.
-server_hello(?'TLS_v1.3' = Version) ->
- #server_hello{server_version = ?'TLS_v1.2',
+server_hello(?TLS_1_3 = Version) ->
+ #server_hello{server_version = ?TLS_1_2,
session_id = session_id(),
random = server_random(Version),
cipher_suite = cipher_suite(Version),
@@ -184,7 +181,7 @@ finished() ->
%%
encrypted_extensions() ->
- ?LET(Exts, extensions(?'TLS_v1.3', encrypted_extensions),
+ ?LET(Exts, extensions(?TLS_1_3, encrypted_extensions),
#encrypted_extensions{extensions = Exts}).
@@ -197,7 +194,7 @@ key_update() ->
%%--------------------------------------------------------------------
tls_version() ->
- oneof([?'TLS_v1.3', ?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1']).
+ oneof([?TLS_1_3, ?TLS_1_2, ?TLS_1_1, ?TLS_1_0]).
cipher_suite(Version) ->
oneof(cipher_suites(Version)).
@@ -290,14 +287,14 @@ pre_shared_keyextension() ->
%% | | |
%% | signature_algorithms_cert (RFC 8446) | CH, CR |
%% +--------------------------------------------------+-------------+
-extensions(?'TLS_v1.3' = Version, MsgType = client_hello) ->
+extensions(?TLS_1_3 = Version, MsgType = client_hello) ->
?LET({
ServerName,
%% MaxFragmentLength,
%% StatusRequest,
SupportedGroups,
SignatureAlgorithms,
- %% UseSrtp,
+ UseSrtp,
%% Heartbeat,
ALPN,
%% SignedCertTimestamp,
@@ -320,7 +317,7 @@ extensions(?'TLS_v1.3' = Version, MsgType = client_hello) ->
%% oneof([status_request(), undefined]),
oneof([supported_groups(Version), undefined]),
oneof([signature_algs(Version), undefined]),
- %% oneof([use_srtp(), undefined]),
+ oneof([use_srtp(), undefined]),
%% oneof([heartbeat(), undefined]),
oneof([alpn(), undefined]),
%% oneof([signed_cert_timestamp(), undefined]),
@@ -348,7 +345,7 @@ extensions(?'TLS_v1.3' = Version, MsgType = client_hello) ->
%% status_request => StatusRequest,
elliptic_curves => SupportedGroups,
signature_algs => SignatureAlgorithms,
- %% use_srtp => UseSrtp,
+ use_srtp => UseSrtp,
%% heartbeat => Heartbeat,
alpn => ALPN,
%% signed_cert_timestamp => SignedCertTimestamp,
@@ -398,7 +395,7 @@ extensions(Version, client_hello) ->
srp => SRP
%% renegotiation_info => RenegotiationInfo
}));
-extensions(?'TLS_v1.3' = Version, MsgType = server_hello) ->
+extensions(?TLS_1_3 = Version, MsgType = server_hello) ->
?LET({
KeyShare,
PreSharedKey,
@@ -443,7 +440,7 @@ extensions(Version, server_hello) ->
next_protocol_negotiation => NextP
%% renegotiation_info => RenegotiationInfo
}));
-extensions(?'TLS_v1.3' = Version, encrypted_extensions) ->
+extensions(?TLS_1_3 = Version, encrypted_extensions) ->
?LET({
ServerName,
%% MaxFragmentLength,
@@ -551,25 +548,25 @@ signature() ->
76,105,212,176,25,6,148,49,194,106,253,241,212,200,
37,154,227,53,49,216,72,82,163>>.
-client_hello_versions(?'TLS_v1.3') ->
+client_hello_versions(?TLS_1_3) ->
?LET(SupportedVersions,
- oneof([[{3,4}],
+ oneof([[?TLS_1_3],
%% This list breaks the property but can be used for negative tests
- %% [{3,3},{3,4}],
- [{3,4},{3,3}],
- [{3,4},{3,3},{3,2},{3,1},{3,0}]
+ %% [?TLS_1_2,?TLS_1_3],
+ [?TLS_1_3,?TLS_1_2],
+ [?TLS_1_3,?TLS_1_2,?TLS_1_1,?TLS_1_0,?SSL_3_0]
]),
#client_hello_versions{versions = SupportedVersions});
client_hello_versions(_) ->
?LET(SupportedVersions,
- oneof([[{3,3}],
- [{3,3},{3,2}],
- [{3,3},{3,2},{3,1},{3,0}]
+ oneof([[?TLS_1_2],
+ [?TLS_1_2,?TLS_1_1],
+ [?TLS_1_2,?TLS_1_1,?TLS_1_0,?SSL_3_0]
]),
#client_hello_versions{versions = SupportedVersions}).
server_hello_selected_version() ->
- #server_hello_selected_version{selected_version = {3,4}}.
+ #server_hello_selected_version{selected_version = ?TLS_1_3}.
request_update() ->
oneof([update_not_requested, update_requested]).
@@ -626,11 +623,11 @@ cert_conf()->
peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}).
cert_auths() ->
- certificate_authorities(?'TLS_v1.3').
+ certificate_authorities(?TLS_1_3).
certificate_request_1_3() ->
#certificate_request_1_3{certificate_request_context = <<>>,
- extensions = #{certificate_authorities => certificate_authorities(?'TLS_v1.3')}
+ extensions = #{certificate_authorities => certificate_authorities(?TLS_1_3)}
}.
certificate_request(Version) ->
#certificate_request{certificate_types = certificate_types(Version),
@@ -640,17 +637,17 @@ certificate_request(Version) ->
certificate_types(_) ->
iolist_to_binary([<<?BYTE(?ECDSA_SIGN)>>, <<?BYTE(?RSA_SIGN)>>, <<?BYTE(?DSS_SIGN)>>]).
-signature_algs({3,4}) ->
+signature_algs(?TLS_1_3) ->
?LET(Algs, signature_algorithms(),
Algs);
-signature_algs({3,3} = Version) ->
+signature_algs(?TLS_1_2 = Version) ->
#hash_sign_algos{hash_sign_algos = hash_alg_list(Version)};
-signature_algs(Version) when Version < {3,3} ->
+signature_algs(Version) when ?TLS_LT(Version, ?TLS_1_2) ->
undefined.
-hashsign_algorithms({_, N} = Version) when N >= 3 ->
+hashsign_algorithms(Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
#hash_sign_algos{hash_sign_algos = hash_alg_list(Version)};
hashsign_algorithms(_) ->
undefined.
@@ -666,9 +663,9 @@ hash_alg(Version) ->
{hash_algorithm(Version, Alg), Alg}
).
-hash_algorithm(?'TLS_v1.3', _) ->
+hash_algorithm(?TLS_1_3, _) ->
oneof([sha, sha224, sha256, sha384, sha512]);
-hash_algorithm(?'TLS_v1.2', rsa) ->
+hash_algorithm(?TLS_1_2, rsa) ->
oneof([sha, sha224, sha256, sha384, sha512]);
hash_algorithm(_, rsa) ->
oneof([md5, sha, sha224, sha256, sha384, sha512]);
@@ -677,13 +674,19 @@ hash_algorithm(_, ecdsa) ->
hash_algorithm(_, dsa) ->
sha.
-sign_algorithm(?'TLS_v1.3') ->
+sign_algorithm(?TLS_1_3) ->
oneof([rsa, ecdsa]);
sign_algorithm(_) ->
oneof([rsa, dsa, ecdsa]).
-certificate_authorities(?'TLS_v1.3') ->
- Auths = certificate_authorities(?'TLS_v1.2'),
+use_srtp() ->
+ FullProfiles = [<<0,1>>, <<0,2>>, <<0,5>>],
+ NullProfiles = [<<0,5>>],
+ ?LET(PP, oneof([FullProfiles, NullProfiles]), #use_srtp{protection_profiles = PP, mki = <<>>}).
+
+certificate_authorities(?TLS_1_3) ->
+ Auths = certificate_authorities(?TLS_1_2),
+
#certificate_authorities{authorities = Auths};
certificate_authorities(_) ->
#{server_config := ServerConf} = cert_conf(),
@@ -712,13 +715,13 @@ ec_point_formats() ->
ec_point_format_list() ->
[?ECPOINT_UNCOMPRESSED].
-elliptic_curves({_, Minor}) when Minor < 4 ->
- Curves = tls_v1:ecc_curves(Minor),
+elliptic_curves(Version) when ?TLS_LT(Version, ?TLS_1_3) ->
+ Curves = tls_v1:ecc_curves(Version),
#elliptic_curves{elliptic_curve_list = Curves}.
%% RFC 8446 (TLS 1.3) renamed the "elliptic_curve" extension.
-supported_groups({_, Minor}) when Minor >= 4 ->
- SupportedGroups = tls_v1:groups(Minor),
+supported_groups(Version) when ?TLS_GTE(Version, ?TLS_1_3) ->
+ SupportedGroups = tls_v1:groups(),
#supported_groups{supported_groups = SupportedGroups}.
diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl
index 4b43954139..379cc6371b 100644
--- a/lib/ssl/test/ssl_ECC_SUITE.erl
+++ b/lib/ssl/test/ssl_ECC_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
-behaviour(ct_suite).
+-include_lib("ssl/src/ssl_record.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
@@ -180,7 +181,7 @@ client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) ->
ecc_default_order(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa,
@@ -195,7 +196,7 @@ ecc_default_order(Config) ->
ecc_default_order_custom_curves(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa,
@@ -210,7 +211,7 @@ ecc_default_order_custom_curves(Config) ->
ecc_client_order(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa,
@@ -225,7 +226,7 @@ ecc_client_order(Config) ->
ecc_client_order_custom_curves(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa,
@@ -245,12 +246,14 @@ ecc_unknown_curve(Config) ->
ecdhe_ecdsa, ecdhe_ecdsa, Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{eccs, ['123_fake_curve']}],
- ssl_test_lib:ecc_test_error(COpts, SOpts, [], ECCOpts, Config).
+ ECCALL = ssl:eccs(),
+ SECCOpts = [{eccs, [hd(ECCALL)]}],
+ CECCOpts = [{eccs, tl(ECCALL)}],
+ ssl_test_lib:ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config).
client_ecdh_rsa_server_ecdhe_ecdsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdh_rsa, ecdhe_ecdsa, Config),
@@ -264,7 +267,7 @@ client_ecdh_rsa_server_ecdhe_ecdsa_server_custom(Config) ->
client_ecdh_rsa_server_ecdhe_rsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdh_rsa, ecdhe_rsa, Config),
@@ -279,7 +282,7 @@ client_ecdh_rsa_server_ecdhe_rsa_server_custom(Config) ->
client_ecdhe_rsa_server_ecdhe_ecdsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_rsa, ecdhe_ecdsa, Config),
@@ -293,7 +296,7 @@ client_ecdhe_rsa_server_ecdhe_ecdsa_server_custom(Config) ->
client_ecdhe_rsa_server_ecdhe_rsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_rsa, ecdhe_rsa, Config),
@@ -307,7 +310,7 @@ client_ecdhe_rsa_server_ecdhe_rsa_server_custom(Config) ->
end.
client_ecdhe_rsa_server_ecdh_rsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
Ext = x509_test:extensions([{key_usage, [keyEncipherment]}]),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, [[], [], [{extensions, Ext}]]},
{client_chain, Default}],
@@ -325,7 +328,7 @@ client_ecdhe_rsa_server_ecdh_rsa_server_custom(Config) ->
client_ecdhe_ecdsa_server_ecdhe_ecdsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa, Config),
@@ -339,7 +342,7 @@ client_ecdhe_ecdsa_server_ecdhe_ecdsa_server_custom(Config) ->
client_ecdhe_ecdsa_server_ecdhe_rsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_rsa, Config),
@@ -353,7 +356,7 @@ client_ecdhe_ecdsa_server_ecdhe_rsa_server_custom(Config) ->
client_ecdhe_ecdsa_server_ecdhe_ecdsa_client_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa, Config),
@@ -367,7 +370,7 @@ client_ecdhe_ecdsa_server_ecdhe_ecdsa_client_custom(Config) ->
client_ecdhe_rsa_server_ecdhe_ecdsa_client_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_rsa, ecdhe_ecdsa, Config),
diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl
index fc56323afd..215097bd4d 100644
--- a/lib/ssl/test/ssl_api_SUITE.erl
+++ b/lib/ssl/test/ssl_api_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,7 +25,9 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("ssl/src/ssl_api.hrl").
+-include_lib("ssl/src/ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include("ssl_record.hrl").
%% Common test
-export([all/0,
@@ -133,6 +135,7 @@
options_not_proplist/1,
invalid_options/0,
invalid_options/1,
+ options_whitebox/0, options_whitebox/1,
cb_info/0,
cb_info/1,
log_alert/0,
@@ -175,14 +178,12 @@
server_options_negative_version_gap/1,
server_options_negative_dependency_role/0,
server_options_negative_dependency_role/1,
+ server_options_negative_stateless_tickets_seed/0,
+ server_options_negative_stateless_tickets_seed/1,
invalid_options_tls13/0,
invalid_options_tls13/1,
cookie/0,
cookie/1,
- warn_verify_none/0,
- warn_verify_none/1,
- suppress_warn_verify_none/0,
- suppress_warn_verify_none/1,
check_random_nonce/0,
check_random_nonce/1,
cipher_listing/0,
@@ -231,7 +232,7 @@ all() ->
{group, 'tlsv1'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
- ].
+ ] ++ simple_api_tests().
groups() ->
[
@@ -247,15 +248,9 @@ groups() ->
{'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()},
{'tlsv1.1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3()},
{'tlsv1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3() ++ beast_mitigation_test()},
- {'dtlsv1.2', [], (gen_api_tests() --
- [invalid_keyfile, invalid_certfile, invalid_cacertfile,
- invalid_options, new_options_in_handshake,
- hibernate_server]) ++
+ {'dtlsv1.2', [], gen_api_tests() -- [new_options_in_handshake, hibernate_server] ++
handshake_paus_tests() -- [handshake_continue_tls13_client] ++ pre_1_3()},
- {'dtlsv1', [], (gen_api_tests() --
- [invalid_keyfile, invalid_certfile, invalid_cacertfile,
- invalid_options, new_options_in_handshake,
- hibernate_server]) ++
+ {'dtlsv1', [], gen_api_tests() -- [new_options_in_handshake, hibernate_server] ++
handshake_paus_tests() -- [handshake_continue_tls13_client] ++ pre_1_3()}
].
@@ -273,6 +268,17 @@ pre_1_3() ->
prf
].
+simple_api_tests() ->
+ [
+ invalid_keyfile,
+ invalid_certfile,
+ invalid_cacertfile,
+ invalid_options,
+ options_not_proplist,
+ options_whitebox
+ ].
+
+
gen_api_tests() ->
[
peercert,
@@ -283,6 +289,7 @@ gen_api_tests() ->
secret_connection_info,
keylog_connection_info,
versions,
+ new_options_in_handshake,
active_n,
dh_params,
hibernate_client,
@@ -307,18 +314,10 @@ gen_api_tests() ->
honor_client_cipher_order,
ipv6,
der_input,
- new_options_in_handshake,
max_handshake_size,
- invalid_certfile,
- invalid_cacertfile,
- invalid_keyfile,
- options_not_proplist,
- invalid_options,
cb_info,
log_alert,
getstat,
- warn_verify_none,
- suppress_warn_verify_none,
check_random_nonce,
cipher_listing
].
@@ -356,6 +355,7 @@ tls13_group() ->
server_options_negative_early_data,
server_options_negative_version_gap,
server_options_negative_dependency_role,
+ server_options_negative_stateless_tickets_seed,
invalid_options_tls13,
cookie
].
@@ -470,7 +470,7 @@ end_per_testcase(_TestCase, Config) ->
peercert() ->
[{doc,"Test API function peercert/1"}].
peercert(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -504,8 +504,8 @@ peercert(Config) when is_list(Config) ->
peercert_with_client_cert() ->
[{doc,"Test API function peercert/1"}].
peercert_with_client_cert(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
@@ -561,52 +561,65 @@ select_sha1_cert() ->
select_sha1_cert(Config) when is_list(Config) ->
Version = ssl_test_lib:protocol_version(Config),
- TestConfRSA = public_key:pkix_test_data(#{server_chain => #{root => [{digest, sha},{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{digest, sha}, {key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{digest, sha}, {key, ssl_test_lib:hardcode_rsa_key(3)}]
- },
- client_chain => #{root => [{digest, sha},{key, ssl_test_lib:hardcode_rsa_key(3)}],
- intermediates => [[{digest, sha},{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{digest, sha}, {key, ssl_test_lib:hardcode_rsa_key(1)}]}}),
-
- TestConfECDSA = public_key:pkix_test_data(#{server_chain => #{root => [{digest, sha},{key, {namedCurve, secp256r1}}],
- intermediates => [[{digest, sha}, {key,{namedCurve, secp256r1} }]],
- peer => [{digest, sha}, {key,{namedCurve, secp256r1}}]
- },
- client_chain => #{root => [{digest, sha},{key, {namedCurve, secp256r1}}],
- intermediates => [[{digest, sha},{key, {namedCurve, secp256r1}}]],
- peer => [{digest, sha}, {key, {namedCurve, secp256r1}}]}}),
- case (Version == 'tlsv1.3') orelse (Version == 'tlsv1.2') of
- true ->
- test_sha1_cert_conf(Version, TestConfRSA, TestConfECDSA, Config);
- false ->
- test_sha1_cert_conf(Version, TestConfRSA, undefined, Config)
- end.
+ TestConfRSA =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{digest, sha},
+ {key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{digest, sha},
+ {key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{digest, sha}, {key, ssl_test_lib:hardcode_rsa_key(3)}]
+ },
+ client_chain =>
+ #{root => [{digest, sha},
+ {key, ssl_test_lib:hardcode_rsa_key(3)}],
+ intermediates => [[{digest, sha},
+ {key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{digest, sha},
+ {key, ssl_test_lib:hardcode_rsa_key(1)}]}}),
+
+ TestConfECDSA =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{digest, sha},
+ {key, {namedCurve, secp256r1}}],
+ intermediates =>
+ [[{digest, sha},
+ {key,{namedCurve, secp256r1}}]],
+ peer => [{digest, sha},
+ {key,{namedCurve, secp256r1}}]
+ },
+ client_chain =>
+ #{root => [{digest, sha},
+ {key, {namedCurve, secp256r1}}],
+ intermediates => [[{digest, sha},
+ {key, {namedCurve, secp256r1}}]],
+ peer => [{digest, sha},
+ {key, {namedCurve, secp256r1}}]}}),
+ test_sha1_cert_conf(Version, TestConfRSA, TestConfECDSA, Config).
%%--------------------------------------------------------------------
connection_information() ->
[{doc,"Test the API function ssl:connection_information/1"}].
-connection_information(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+connection_information(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
{mfa, {?MODULE, connection_information_result, []}},
{options, ServerOpts}]),
-
+
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
- {from, self()},
+ {from, self()},
{mfa, {?MODULE, connection_information_result, []}},
{options, ClientOpts}]),
-
+
ct:log("Testcase ~p, Client ~p Server ~p ~n",
[self(), Client, Server]),
-
+
ssl_test_lib:check_result(Server, ok, Client, ok),
-
+
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
@@ -632,7 +645,7 @@ do_run_conn_info_srp_test(ErlangCipherSuite, Version, Config) ->
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
SOpts = [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}],
- COpts = [{srp_identity, {"Test-User", "secret"}}],
+ COpts = [{verify, verify_none}, {srp_identity, {"Test-User", "secret"}}],
ServerOpts = ssl_test_lib:ssl_options(SOpts, Config),
ClientOpts = ssl_test_lib:ssl_options(COpts, Config),
@@ -665,7 +678,7 @@ do_run_conn_info_srp_test(ErlangCipherSuite, Version, Config) ->
secret_connection_info() ->
[{doc,"Test the API function ssl:connection_information/2"}].
secret_connection_info(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -697,7 +710,7 @@ keylog_connection_info(Config) when is_list(Config) ->
keylog_connection_info(Config, true),
keylog_connection_info(Config, false).
keylog_connection_info(Config, KeepSecrets) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -759,7 +772,7 @@ dh_params() ->
[{doc,"Test to specify DH-params file in server."}].
dh_params(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
DataDir = proplists:get_value(data_dir, Config),
DHParamFile = filename:join(DataDir, "dHParam.pem"),
@@ -788,7 +801,7 @@ dh_params(Config) when is_list(Config) ->
conf_signature_algs() ->
[{doc,"Test to set the signature_algs option on both client and server"}].
conf_signature_algs(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -820,7 +833,7 @@ no_common_signature_algs() ->
[{doc,"Set the signature_algs option so that there client and server does not share any hash sign algorithms"}].
no_common_signature_algs(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -843,12 +856,13 @@ no_common_signature_algs(Config) when is_list(Config) ->
handshake_continue() ->
[{doc, "Test API function ssl:handshake_continue/3"}].
handshake_continue(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {host, Hostname},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
{options, ssl_test_lib:ssl_options([{reuseaddr, true},
@@ -867,10 +881,10 @@ handshake_continue(Config) when is_list(Config) ->
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
{options, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ClientOpts
+ {verify, verify_none} | ClientOpts
],
Config)},
- {continue_options, proplists:delete(reuseaddr, ClientOpts)}]),
+ {continue_options, [{verify, verify_peer} | ClientOpts]}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
@@ -881,7 +895,7 @@ handshake_continue(Config) when is_list(Config) ->
handshake_continue_tls13_client() ->
[{doc, "Test API function ssl:handshake_continue/3 with fixed TLS 1.3 client"}].
handshake_continue_tls13_client(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
@@ -953,7 +967,7 @@ handshake_continue_tls13_client(Config) when is_list(Config) ->
handshake_continue_timeout() ->
[{doc, "Test API function ssl:handshake_continue/3 with short timeout"}].
handshake_continue_timeout(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -961,9 +975,9 @@ handshake_continue_timeout(Config) when is_list(Config) ->
{from, self()},
{timeout, 1},
{options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello},
- {verify, verify_peer} | ServerOpts],
+ {verify, verify_none} | ServerOpts],
Config)},
- {continue_options, proplists:delete(reuseaddr, ServerOpts)}
+ {continue_options, [{verify, verify_peer} | ServerOpts]}
]),
Port = ssl_test_lib:inet_port(Server),
@@ -981,7 +995,7 @@ handshake_continue_change_verify() ->
[{doc, "Test API function ssl:handshake_continue with updated verify option. "
"Use a verification that will fail to make sure verification is run"}].
handshake_continue_change_verify(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1001,7 +1015,8 @@ handshake_continue_change_verify(Config) when is_list(Config) ->
{from, self()},
{options, ssl_test_lib:ssl_options(
[{handshake, hello},
- {server_name_indication, "foobar"}
+ {server_name_indication, "foobar"},
+ {verify, verify_none}
| ClientOpts], Config)},
{continue_options, [{verify, verify_peer} | ClientOpts]}]),
ssl_test_lib:check_client_alert(Client, handshake_failure).
@@ -1010,16 +1025,17 @@ handshake_continue_change_verify(Config) when is_list(Config) ->
hello_client_cancel() ->
[{doc, "Test API function ssl:handshake_cancel/1 on the client side"}].
hello_client_cancel(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {host, Hostname},
{from, self()},
{options, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ServerOpts], Config)},
- {continue_options, proplists:delete(reuseaddr, ServerOpts)}]),
+ {verify, verify_none} | ServerOpts], Config)},
+ {continue_options, [{verify, verify_peer} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
@@ -1028,16 +1044,16 @@ hello_client_cancel(Config) when is_list(Config) ->
ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
- {options, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ClientOpts], Config)},
+ {options, ssl_test_lib:ssl_options([{handshake, hello}
+ | ClientOpts], Config)},
{continue_options, cancel}]),
ssl_test_lib:check_server_alert(Server, user_canceled).
%%--------------------------------------------------------------------
hello_server_cancel() ->
[{doc, "Test API function ssl:handshake_cancel/1 on the server side"}].
hello_server_cancel(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -1074,7 +1090,7 @@ versions_option_based_on_sni() ->
[{doc,"Test that SNI versions option is selected over default versions"}].
versions_option_based_on_sni(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
TestVersion = ssl_test_lib:protocol_version(Config),
{Version, Versions} = test_versions_for_option_based_on_sni(TestVersion),
@@ -1112,9 +1128,10 @@ active_n() ->
[{doc,"Test {active,N} option"}].
active_n(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
Port = ssl_test_lib:inet_port(node()),
+ Host = net_adm:localhost(),
N = 3,
LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])),
[{active,N}] = ok(ssl:getopts(LS, [active])),
@@ -1128,7 +1145,7 @@ active_n(Config) when is_list(Config) ->
ssl:controlling_process(S, Self),
Self ! {server, S}
end),
- C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])),
+ C = ok(ssl:connect(Host, Port, [{active,N}|ClientOpts])),
[{active,N}] = ok(ssl:getopts(C, [active])),
S = receive
{server, S0} -> S0
@@ -1196,7 +1213,7 @@ hibernate_client() ->
"option hibernate_after indeed hibernates after inactivity"}].
hibernate_client(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
StartServerOpts = [return_socket, {node, ServerNode},
@@ -1217,7 +1234,7 @@ hibernate_server() ->
"Note: for DTLS test will not be stable, because cookie secret refresh "
"mechanism might disturb hibernation of a server process."}].
hibernate_server(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
StartServerOpts = [return_socket, {node, ServerNode}, {port, 0},
@@ -1303,8 +1320,8 @@ peername() ->
[{doc,"Test API function peername/1"}].
peername(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server(
@@ -1344,7 +1361,7 @@ recv_active() ->
[{doc,"Test recv on active socket"}].
recv_active(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -1370,7 +1387,7 @@ recv_active_once() ->
[{doc,"Test recv on active (once) socket"}].
recv_active_once(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -1396,7 +1413,7 @@ recv_active_n() ->
[{doc,"Test recv on active (n) socket"}].
recv_active_n(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -1422,7 +1439,7 @@ recv_timeout() ->
recv_timeout(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1448,7 +1465,7 @@ recv_timeout(Config) ->
recv_close() ->
[{doc,"Special case of call error handling"}].
recv_close(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1472,7 +1489,7 @@ recv_no_active_msg() ->
[{doc,"If we have a passive socket and do not call recv and peer closes we should no get"
"receive an active message"}].
recv_no_active_msg(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1494,7 +1511,7 @@ controlling_process() ->
[{doc,"Test API function controlling_process/2"}].
controlling_process(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
ClientMsg = "Server hello",
@@ -1531,7 +1548,7 @@ controlling_process(Config) when is_list(Config) ->
controller_dies() ->
[{doc,"Test that the socket is closed after controlling process dies"}].
controller_dies(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
ClientMsg = "Hello server",
@@ -1622,7 +1639,7 @@ controlling_process_transport_accept_socket() ->
[{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}].
controlling_process_transport_accept_socket(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server_transport_control([{node, ServerNode},
@@ -1642,7 +1659,7 @@ controlling_process_transport_accept_socket(Config) when is_list(Config) ->
close_with_timeout() ->
[{doc,"Test normal (not downgrade) ssl:close/2"}].
close_with_timeout(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1670,7 +1687,7 @@ close_in_error_state(Config) when is_list(Config) ->
_ = spawn(?MODULE, run_error_server_close, [[self() | ServerOpts]]),
receive
{_Pid, Port} ->
- spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]])
+ spawn_link(?MODULE, run_client_error, [[Port, [{verify, verify_none} | ClientOpts]]])
end,
receive
ok ->
@@ -1689,7 +1706,7 @@ call_in_error_state(Config) when is_list(Config) ->
Pid = spawn(?MODULE, run_error_server, [[self() | ServerOpts]]),
receive
{Pid, Port} ->
- spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]])
+ spawn_link(?MODULE, run_client_error, [[Port, [{verify, verify_none} | ClientOpts]]])
end,
receive
{error, closed} ->
@@ -1723,7 +1740,7 @@ abuse_transport_accept_socket() ->
[{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}].
abuse_transport_accept_socket(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server_transport_abuse_socket([{node, ServerNode},
@@ -1745,7 +1762,7 @@ abuse_transport_accept_socket(Config) when is_list(Config) ->
invalid_keyfile() ->
[{doc,"Test what happens with an invalid key file"}].
invalid_keyfile(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
BadKeyFile = filename:join([proplists:get_value(priv_dir, Config),
"badkey.pem"]),
@@ -1830,7 +1847,7 @@ ipv6(Config) when is_list(Config) ->
case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of
true ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} =
ssl_test_lib:run_where(Config, ipv6),
@@ -1877,14 +1894,13 @@ der_input(Config) when is_list(Config) ->
SeverVerifyOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ServerCert, ServerKey, ServerCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} |
SeverVerifyOpts]),
- ClientVerifyOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientVerifyOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientCert, ClientKey, ClientCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} |
ClientVerifyOpts]),
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true},
{dh, DHParams},
{cert, ServerCert}, {key, ServerKey}, {cacerts, ServerCaCerts}],
- ClientOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true},
- {dh, DHParams},
+ ClientOpts = [{verify, verify_peer},
{cert, ClientCert}, {key, ClientKey}, {cacerts, ClientCaCerts}],
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -1909,8 +1925,7 @@ der_input(Config) when is_list(Config) ->
{cert, ServerCert}, {key, ServerKey},
{cacerts, [ #cert{der=Der, otp=public_key:pkix_decode_cert(Der, otp)}
|| Der <- ServerCaCerts]}],
- ClientOpts1 = [{verify, verify_peer}, {fail_if_no_peer_cert, true},
- {dh, DHParams},
+ ClientOpts1 = [{verify, verify_peer},
{cert, ClientCert}, {key, ClientKey},
{cacerts, [ #cert{der=Der, otp=public_key:pkix_decode_cert(Der, otp)}
|| Der <- ClientCaCerts]}],
@@ -1938,7 +1953,7 @@ invalid_certfile() ->
[{doc,"Test what happens with an invalid cert file"}].
invalid_certfile(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
BadCertFile = filename:join([proplists:get_value(priv_dir, Config),
"badcert.pem"]),
@@ -1970,7 +1985,7 @@ invalid_cacertfile() ->
[{doc,"Test what happens with an invalid cacert file"}].
invalid_cacertfile(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
BadCACertFile = filename:join([proplists:get_value(priv_dir, Config),
"badcacert.pem"]),
@@ -2022,7 +2037,7 @@ invalid_cacertfile(Config) when is_list(Config) ->
new_options_in_handshake() ->
[{doc,"Test that you can set ssl options in handshake/3 and not only in tcp upgrade"}].
new_options_in_handshake(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
Version = ssl_test_lib:protocol_version(Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -2069,7 +2084,7 @@ max_handshake_size() ->
[{doc,"Test that we can set max_handshake_size to max value."}].
max_handshake_size(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -2095,7 +2110,7 @@ options_not_proplist() ->
options_not_proplist(Config) when is_list(Config) ->
BadOption = {client_preferred_next_protocols,
client, [<<"spdy/3">>,<<"http/1.1">>], <<"http/1.1">>},
- {option_not_a_key_value_tuple, BadOption} =
+ {error, {options, {option_not_a_key_value_tuple, BadOption}}} =
ssl:connect("twitter.com", 443, [binary, {active, false},
BadOption]).
@@ -2122,7 +2137,6 @@ invalid_options(Config) when is_list(Config) ->
TestOpts =
[{versions, [sslv2, sslv3]},
- {verify, 4},
{verify_fun, function},
{fail_if_no_peer_cert, 0},
{depth, four},
@@ -2130,7 +2144,6 @@ invalid_options(Config) when is_list(Config) ->
{keyfile,'key.pem' },
{password, foo},
{cacertfile, ""},
- {dhfile,'dh.pem' },
{ciphers, [{foo, bar, sha, ignore}]},
{reuse_session, foo},
{reuse_sessions, 0},
@@ -2143,31 +2156,19 @@ invalid_options(Config) when is_list(Config) ->
{key, 'key.pem' }],
TestOpts2 =
- [{[{anti_replay, '10k'}],
- %% anti_replay is a server only option but tested with client
- %% for simplicity
- {options,dependency,{anti_replay,{versions,['tlsv1.3']}}}},
- {[{cookie, false}],
- {options,dependency,{cookie,{versions,['tlsv1.3']}}}},
- {[{supported_groups, []}],
- {options,dependency,{supported_groups,{versions,['tlsv1.3']}}}},
- {[{use_ticket, [<<1,2,3,4>>]}],
- {options,dependency,{use_ticket,{versions,['tlsv1.3']}}}},
- {[{verify, verify_none}, {fail_if_no_peer_cert, true}],
- {options, incompatible,
- {verify, verify_none},
- {fail_if_no_peer_cert, true}}}],
+ [{[{supported_groups, []}, {versions, [tlsv1]}],
+ {options,incompatible,[supported_groups,{versions,['tlsv1']}]}}],
[begin
Server =
ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
- {options, [TestOpt | ServerOpts]}]),
+ {options, ServerOpts ++ [TestOpt]}]),
%% Will never reach a point where port is used.
Client =
ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0},
{host, Hostname}, {from, self()},
- {options, [TestOpt | ClientOpts]}]),
+ {options, ClientOpts ++ [TestOpt]}]),
Check(Client, Server, TestOpt),
ok
end || TestOpt <- TestOpts],
@@ -2178,6 +2179,871 @@ invalid_options(Config) when is_list(Config) ->
end || {TestOpt, ErrorMsg} <- TestOpts2],
ok.
+options_whitebox() ->
+ [{doc,"Whitebox tests of option handling"}].
+
+
+customize_defaults(Opts, Role, Host) ->
+ %% In many options test scenarios we do not care about verifcation options
+ %% but the client now requiers verification options by default.
+ ClientIgnorDef = case proplists:get_value(verify, Opts, undefined) of
+ undefined when Role == client ->
+ [{verify, verify_none}];
+ _ ->
+ []
+ end,
+ case proplists:get_value(protocol, Opts, tls) of
+ dtls ->
+ {ok, #config{ssl=DOpts}} = ssl:handle_options([{verify, verify_none}, {protocol, dtls}], Role, Host),
+ {DOpts, ClientIgnorDef ++ Opts};
+ tls ->
+ {ok, #config{ssl=DOpts}} = ssl:handle_options([{verify, verify_none}], Role, Host),
+ case proplists:get_value(versions, Opts) of
+ undefined ->
+ {DOpts, ClientIgnorDef ++ [{versions, ['tlsv1.2','tlsv1.3']}|Opts]};
+ _ ->
+ {DOpts, ClientIgnorDef ++ Opts}
+ end;
+ _ ->
+ {ok, #config{ssl=DOpts}} = ssl:handle_options(ClientIgnorDef, Role, Host),
+ {DOpts, ClientIgnorDef ++ Opts}
+ end.
+
+-define(OK(EXP, Opts, Role), ?OK(EXP,Opts, Role, [])).
+-define(OK(EXP, Opts, Role, ShouldBeMissing),
+ fun() ->
+ Host = "dummy.host.org",
+ {__DefOpts, __Opts} = customize_defaults(Opts, Role, Host),
+ try ssl:handle_options(__Opts, Role, Host) of
+ {ok, #config{ssl=EXP = __ALL}} ->
+ ShouldBeMissing = ShouldBeMissing -- maps:keys(__ALL);
+ Other ->
+ ct:pal("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]),
+ error({unexpected, Other})
+ catch
+ throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored;
+ C:Other:ST ->
+ ct:pal("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]),
+ error({unexpected, C, Other,ST})
+ end,
+ try ssl:update_options(__Opts, Role, __DefOpts) of
+ EXP = __ALL2 ->
+ ShouldBeMissing = ShouldBeMissing -- maps:keys(__ALL2);
+ Other2 ->
+ ct:pal("{ok,Cfg} = ssl:handle_options([],~p,~p),"
+ "ssl:update_options(~w,~w, element(2,Cfg)).",
+ [Role,Host,__Opts,Role]),
+ error({unexpected2, Other2})
+ catch
+ throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored;
+ C2:Other2:ST2 ->
+ ct:pal("{ok,Cfg} = ssl:handle_options([],~p,~p),"
+ "ssl:update_options(~p,~p, element(2,Cfg)).",
+ [Role,Host,__Opts,Role]),
+ error({unexpected, C2, Other2, ST2})
+ end
+ end()).
+
+-define(ERR(EXP, Opts, Role),
+ fun() ->
+ Host = "dummy.host.org",
+ {__DefOpts, __Opts} = customize_defaults(Opts, Role, Host),
+ try ssl:handle_options(__Opts, Role, Host) of
+ Other ->
+ ct:pal("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]),
+ error({unexpected, Other})
+ catch
+ throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored;
+ throw:{error, {options, EXP}} -> ok;
+ throw:{error, EXP} -> ok;
+ C:Other:ST ->
+ ct:pal("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]),
+ error({unexpected, C, Other,ST})
+ end,
+ try ssl:update_options(__Opts, Role, __DefOpts) of
+ Other2 ->
+ ct:pal("{ok,Cfg} = ssl:handle_options([],~p,~p),"
+ "ssl:update_options(~p,~p, element(2,Cfg)).",
+ [Role,Host,__Opts,Role]),
+ error({unexpected, Other2})
+ catch
+ throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored;
+ throw:{error, {options, EXP}} -> ok;
+ throw:{error, EXP} -> ok;
+ C2:Other2:ST2 ->
+ ct:pal("{ok,Cfg} = ssl:handle_options([],~p,~p),"
+ "ssl:update_options(~p,~p, element(2,Cfg)).",
+ [Role,Host,__Opts,Role]),
+ error({unexpected, C2, Other2,ST2})
+ end
+ end()).
+
+options_whitebox(Config) when is_list(Config) ->
+ Cert = proplists:get_value(cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)),
+ true = is_binary(Cert),
+ ?OK(#{verify := verify_peer}, %% Check Option order last wins
+ [{verify, verify_none}, {verify,verify_peer}, {cacerts, [Cert]}], client),
+ options_protocol(Config),
+ options_version(Config),
+ options_alpn(Config),
+ options_anti_replay(Config),
+ options_beast_mitigation(Config),
+ options_cacerts(Config),
+ options_cert(Config),
+ options_certificate_authorities(Config),
+ options_ciphers(Config),
+ options_client_renegotiation(Config),
+ options_cookie(Config),
+ options_crl(Config),
+ options_hostname_check(Config),
+ options_dh(Config),
+ options_early_data(Config),
+ options_eccs(Config),
+ options_verify(Config),
+ options_fallback(Config),
+ options_handshake(Config),
+ options_process(Config),
+ options_honor(Config),
+ options_debug(Config),
+ options_renegotiate(Config),
+ options_middlebox(Config),
+ options_frag_len(Config),
+ options_oscp(Config),
+ options_padding(Config),
+ options_identity(Config),
+ options_reuse_session(Config),
+ options_sni(Config),
+ options_sign_alg(Config),
+ options_supported_groups(Config),
+ options_use_srtp(Config),
+ ok.
+
+options_protocol(_Config) ->
+ ?OK(#{protocol := tls}, [], client),
+ ?OK(#{protocol := tls}, [{protocol, tls}], client),
+ ?OK(#{protocol := dtls}, [{protocol, dtls}], client),
+
+ %% Errors
+ ?ERR({protocol, foo}, [{protocol, 'foo'}], client),
+
+ begin %% erl_dist
+ ?OK(#{}, [], client, [erl_dist]),
+ ?OK(#{}, [{erl_dist, false}], client, [erl_dist]),
+ ?OK(#{erl_dist := true}, [{erl_dist, true}], client),
+ ?OK(#{}, [], client, [ktls]),
+ ?OK(#{}, [{ktls, false}], client, [ktls]),
+ ?OK(#{ktls := true}, [{ktls, true}], client)
+ end,
+ ok.
+
+options_version(_Config) ->
+ ?OK(#{versions := [_|_]}, [], client), %% Hmm some machines still default only ?TLS_1_2
+ ?OK(#{versions := [?DTLS_1_2]}, [{protocol, dtls}], client),
+
+ ?OK(#{versions := [?TLS_1_3,?TLS_1_2,?TLS_1_1,?TLS_1_0]},
+ [{versions, ['tlsv1','tlsv1.1','tlsv1.2','tlsv1.3']}],
+ client),
+ ?OK(#{versions := [?TLS_1_3]}, [{versions, ['tlsv1.3']}], client),
+
+ ?OK(#{versions := [?DTLS_1_2,?DTLS_1_0]},
+ [{protocol, dtls}, {versions, ['dtlsv1', 'dtlsv1.2']}], client),
+ ?OK(#{versions := [?DTLS_1_2]}, [{protocol, dtls}, {versions, ['dtlsv1.2']}], client),
+
+
+ %% Errors
+ ?ERR({versions, []}, [{versions, []}], client),
+ ?ERR({versions, []}, [{protocol, dtls}, {versions, []}], client),
+
+ ?ERR({'tlsv1.4',{versions, ['tlsv1.4']}},
+ [{versions, ['tlsv1.4']}], client),
+ ?ERR({'dtlsv1', {versions, _}},
+ [{versions, ['dtlsv1', 'dtlsv1.2']}], client),
+
+ ?ERR({'tlsv1', {versions, _}},
+ [{protocol, dtls}, {versions, ['tlsv1','tlsv1.1','tlsv1.2','tlsv1.3']}],
+ client),
+ ?ERR({'dtlsv1.3',{versions, ['dtlsv1.3']}},
+ [{protocol, dtls}, {versions, ['dtlsv1.3']}],
+ client),
+ ?ERR({options,missing_version,{'tlsv1.2',_}},
+ [{versions, ['tlsv1.1','tlsv1.3']}],
+ client),
+ ok.
+
+options_alpn(_Config) -> %% alpn & next_protocols
+ Http = <<"HTTP/2">>,
+ ?OK(#{alpn_advertised_protocols := undefined}, [], client,
+ [alpn_preferred_protocols, next_protocol_selector, next_protocols_advertised]),
+ ?OK(#{alpn_preferred_protocols := undefined}, [], server,
+ [alpn_advertised_protocols, next_protocol_selector, next_protocols_advertised]),
+
+ ?OK(#{alpn_preferred_protocols := [Http]}, [{alpn_preferred_protocols, [Http]}],
+ server, [alpn_advertised_protocols, next_protocol_selector, next_protocols_advertised]),
+ ?OK(#{alpn_advertised_protocols := [Http]}, [{alpn_advertised_protocols, [Http]}],
+ client, [alpn_preferred_protocols, next_protocol_selector, next_protocols_advertised]),
+
+ %% Note names have been swapped in client/server variants
+
+ ?OK(#{alpn_preferred_protocols := undefined, next_protocols_advertised := [Http]},
+ [{next_protocols_advertised, [Http]}], server, [alpn_advertised_protocols, next_protocol_selector]),
+ ?OK(#{alpn_advertised_protocols := undefined, next_protocol_selector := _},
+ [{client_preferred_next_protocols, {server,[Http], Http}}],
+ client, [alpn_preferred_protocols, next_protocols_advertised]),
+
+ %% Errors
+ ?ERR({alpn_preferred_protocols, {invalid_protocol, <<>>}}, [{alpn_preferred_protocols, [Http, <<>>]}], server),
+ ?ERR({alpn_advertised_protocols, Http}, [{alpn_advertised_protocols, Http}], client),
+ ?ERR({alpn_preferred_protocols, undefined}, [{alpn_preferred_protocols, undefined}], server),
+ ?ERR({alpn_advertised_protocols, undefined}, [{alpn_advertised_protocols, undefined}], client),
+ ?ERR({option, server_only, alpn_preferred_protocols}, [{alpn_preferred_protocols, [Http]}], client),
+ ?ERR({option, client_only, alpn_advertised_protocols}, [{alpn_advertised_protocols, [Http]}], server),
+ ?ERR({option, server_only, next_protocols_advertised}, [{next_protocols_advertised, [Http]}], client),
+ ?ERR({option, client_only, client_preferred_next_protocols}, [{client_preferred_next_protocols, [Http]}], server),
+ ok.
+
+options_anti_replay(_Config) ->
+ ?OK(#{anti_replay := undefined}, [], server),
+ ?OK(#{anti_replay := {_,_,_}},
+ [{anti_replay, '10k'}, {session_tickets, stateless}],
+ server),
+ ?OK(#{anti_replay := {_,_,_}},
+ [{anti_replay, {42,4711,21}}, {session_tickets, stateless}],
+ server),
+ ?OK(#{anti_replay := {_,_,_}},
+ [{anti_replay, '10k'}, {session_tickets, stateless_with_cert}],
+ server),
+ ?OK(#{anti_replay := {_,_,_}},
+ [{anti_replay, {42,4711,21}}, {session_tickets, stateless_with_cert}],
+ server),
+
+
+ %% Errors
+ ?ERR({option, server_only, anti_replay},
+ [{anti_replay, '10k'}, {session_tickets, manual}],
+ client),
+ ?ERR({options, incompatible, [{anti_replay, _}, {session_tickets, disabled}]},
+ [{anti_replay, '10k'}],
+ server),
+ ?ERR({options,incompatible, [session_tickets,{versions,['tlsv1']}]},
+ [{anti_replay, '10k'}, {session_tickets, stateless}, {versions, ['tlsv1']}],
+ server),
+ ?ERR({anti_replay, '1k'},
+ [{anti_replay, '1k'}, {session_tickets, stateless}],
+ server),
+ ?ERR({anti_replay, _},
+ [{anti_replay, {1,1,1,1}}, {session_tickets, stateless}],
+ server),
+ ?ERR({options,incompatible, [session_tickets,{versions,['tlsv1']}]},
+ [{anti_replay, '10k'}, {session_tickets, stateless_with_cert}, {versions, ['tlsv1']}],
+ server),
+ ?ERR({anti_replay, '1k'},
+ [{anti_replay, '1k'}, {session_tickets, stateless_with_cert}],
+ server),
+ ?ERR({anti_replay, _},
+ [{anti_replay, {1,1,1,1}}, {session_tickets, stateless_with_cert}],
+ server),
+ ok.
+
+options_beast_mitigation(_Config) -> %% Beast mitigation TLS-1.0 option only
+ ?OK(#{beast_mitigation := one_n_minus_one}, [{versions, [tlsv1,'tlsv1.1']}], client),
+ ?OK(#{}, [{versions, ['tlsv1.1']}], client, [beast_mitigation]),
+ ?OK(#{}, [{beast_mitigation, disabled}, {versions, [tlsv1]}], client,
+ [beast_mitigation]),
+ ?OK(#{beast_mitigation := zero_n},
+ [{beast_mitigation, zero_n}, {versions, [tlsv1]}], client),
+
+ %% Errors
+ ?ERR({beast_mitigation, enabled},
+ [{beast_mitigation, enabled}, {versions, [tlsv1]}], client),
+ ?ERR({options, incompatible, [beast_mitigation, {versions, _}]},
+ [{beast_mitigation, disabled}], client),
+ ok.
+
+options_cacerts(Config) -> %% cacert[s]file
+ Cert = proplists:get_value(cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)),
+ File = case os:type() of
+ {win32, _} -> <<"c:/tmp/foo">>;
+ _ -> <<"/tmp/foo">>
+ end,
+ ?OK(#{cacerts := undefined}, [], client, [cacertfile]),
+ ?OK(#{cacerts := undefined, cacertfile := File},
+ [{cacertfile, File}], client),
+ ?OK(#{cacerts := [Cert]}, [{cacerts, [Cert]}, {verify, verify_peer}],
+ client, [cacertfile]),
+ ?OK(#{cacerts := [#cert{}]}, [{cacerts, [#cert{der=Cert, otp=dummy}]}],
+ client, [cacertfile]),
+ ?OK(#{cacerts := [Cert]}, [{cacerts, [Cert]}, {cacertfile, "/tmp/foo"}],
+ client, [cacertfile]),
+
+ %% Errors
+ ?ERR({options, incompatible, _}, [{verify, verify_peer}], server),
+ ?ERR({cacerts, Cert}, [{cacerts, Cert}], client),
+ ?ERR({cacertfile, cert}, [{cacertfile, cert}], client),
+
+ ?OK(#{}, [], client, [depth]),
+ ?OK(#{depth := 5}, [{depth, 5}], client),
+ %% Error
+ ?ERR({depth, 256}, [{depth, 256}], client),
+ ?ERR({depth, not_an_int}, [{depth, not_an_int}], client),
+ ok.
+
+options_cert(Config) -> %% cert[file] cert_keys keys password
+ Cert = proplists:get_value(cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)),
+ Old = [cert, certfile, key, keyfile, password],
+ ?OK(#{certs_keys := []}, [], client, Old),
+ ?OK(#{certs_keys := [#{cert := [Cert]}]}, [{cert,Cert}], client, Old),
+ ?OK(#{certs_keys := [#{cert := [Cert]}]}, [{cert,[Cert]}], client, Old),
+ ?OK(#{certs_keys := [#{certfile := <<"/tmp/foo">>, keyfile := <<"/tmp/foo">>}]},
+ [{certfile, <<"/tmp/foo">>}], client, Old),
+
+ ?OK(#{certs_keys := [#{}]}, [{certs_keys, [#{}]}], client),
+
+ ?OK(#{certs_keys := [#{key := {rsa, <<>>}}]},
+ [{key, {rsa, <<>>}}], client, Old),
+ ?OK(#{certs_keys := [#{key := #{}}]},
+ [{key, #{engine => foo, algorithm => foo, key_id => foo}}], client, Old),
+
+ ?OK(#{certs_keys := [#{password := _}]}, [{password, "foobar"}], client, Old),
+ ?OK(#{certs_keys := [#{password := _}]}, [{password, <<"foobar">>}], client, Old),
+ Pwd = fun() -> "foobar" end,
+ ?OK(#{certs_keys := [#{password := Pwd}]}, [{password, Pwd}], client, Old),
+
+ ?OK(#{certs_keys := [#{certfile := <<"/tmp/foo">>, keyfile := <<"/tmp/baz">>}]},
+ [{certfile, <<"/tmp/foo">>}, {keyfile, "/tmp/baz"}], client, Old),
+
+ ?OK(#{certs_keys := [#{}]},
+ [{cert, Cert}, {certfile, "/tmp/foo"}, {certs_keys, [#{}]}],
+ client, Old),
+
+ %% Errors
+ ?ERR({cert, #{}}, [{cert, #{}}], client),
+ ?ERR({certfile, cert}, [{certfile, cert}], client),
+ ?ERR({certs_keys, #{}}, [{certs_keys, #{}}], client),
+ ?ERR({keyfile, #{}}, [{keyfile, #{}}], client),
+ ?ERR({key, <<>>}, [{key, <<>>}], client),
+ ?ERR({password, _}, [{password, fun(Arg) -> Arg end}], client),
+ ok.
+
+options_certificate_authorities(_Config) ->
+ ?OK(#{}, [], client, [certificate_authorities]),
+ ?OK(#{}, [], server, [certificate_authorities]),
+ ?OK(#{certificate_authorities := true}, [{certificate_authorities, true}], client),
+ ?OK(#{}, [{certificate_authorities, false}], client, [certificate_authorities]),
+ ?OK(#{certificate_authorities := false}, [{certificate_authorities, false}], server),
+ ?OK(#{}, [{certificate_authorities, true}], server, [certificate_authorities]),
+
+ %% Errors
+ ?ERR({certificate_authorities, []},
+ [{certificate_authorities, []}], client),
+ ?ERR({options, incompatible, [certificate_authorities, {versions, _}]},
+ [{certificate_authorities, true}, {versions, ['tlsv1.2']}],
+ client),
+ ok.
+
+options_ciphers(_Config) ->
+ CipherSuite = ssl_test_lib:ecdh_dh_anonymous_suites(?TLS_1_2),
+ ?OK(#{ciphers := [_|_]}, [], client),
+ ?OK(#{ciphers := [_|_]}, [{ciphers, CipherSuite}], client),
+ ?OK(#{ciphers := [_|_]}, [{ciphers, "RC4-SHA:RC4-MD5"}], client),
+ ?OK(#{ciphers := [_|_]}, [{ciphers, ["RC4-SHA", "RC4-MD5"]}], client),
+
+ %% FIXME extend this
+ ok.
+
+options_client_renegotiation(_Config) ->
+ ?OK(#{client_renegotiation := true}, [], server),
+ ?OK(#{client_renegotiation := false}, [{client_renegotiation, false}], server),
+
+ %% Errors
+ ?ERR({client_renegotiation, []}, [{client_renegotiation, []}], server),
+ ?ERR({option, server_only, client_renegotiation}, [{client_renegotiation, true}], client),
+ ?ERR({options, incompatible, [client_renegotiation, {versions, _}]},
+ [{client_renegotiation, true}, {versions, ['tlsv1.3']}],
+ server),
+ ok.
+
+
+options_cookie(_Config) ->
+ ?OK(#{cookie := true}, [], server),
+ ?OK(#{cookie := false}, [{cookie, false}], server),
+
+ %% Errors
+ ?ERR({cookie, []}, [{cookie, []}], server),
+ ?ERR({option, server_only, cookie}, [{cookie, true}], client),
+ ?ERR({options, incompatible, [cookie, {versions, _}]},
+ [{cookie, true}, {versions, ['tlsv1.2']}], server),
+ ok.
+
+options_crl(_Config) ->
+ ?OK(#{crl_cache := {ssl_crl_cache, _}, crl_check := false}, [], server),
+ ?OK(#{crl_cache := {ssl_crl_cache, _}, crl_check := true}, [{crl_check, true}], server),
+ ?OK(#{crl_cache := {ssl_crl_hash_dir, {_,_}}, crl_check := true},
+ [{crl_check, true}, {crl_cache, {ssl_crl_hash_dir, {internal, [{dir, "/tmp"}]}}}],
+ server),
+ ?OK(#{crl_cache := {ssl_crl_cache, {_,_}}, crl_check := true},
+ [{crl_check, true}, {crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}}],
+ server),
+ %% Errors
+ ?ERR({crl_check, foo}, [{crl_check, foo}], server),
+ ?ERR({crl_cache, {a,b,c}}, [{crl_cache, {a,b,c}}], server),
+ ok.
+
+options_hostname_check(_Config) ->
+ ?OK(#{customize_hostname_check := []}, [], client),
+ ?OK(#{customize_hostname_check := [{match_fun, _}]},
+ [{customize_hostname_check, [{match_fun, fun() -> ok end}]}],
+ client),
+ %% Error
+ ?ERR({customize_hostname_check, _}, [{customize_hostname_check, {match_fun, pb_fun}}], client),
+ ok.
+
+options_dh(_Config) -> %% dh dhfile
+ ?OK(#{}, [], server, [dh, dhfile]),
+ ?OK(#{dh := <<>>}, [{dh, <<>>}], server, [dhfile]),
+ ?OK(#{dhfile := <<"/tmp/foo">>}, [{dhfile, <<"/tmp/foo">>}], server, [dh]),
+ ?OK(#{dh := <<>>}, [{dh, <<>>}, {dhfile, <<"/tmp/foo">>}], server, [dhfile]),
+
+ %% Should be an error
+ ?OK(#{dhfile := <<"/tmp/foo">>}, %% Not available in 1.3
+ [{dhfile, <<"/tmp/foo">>}, {versions, ['tlsv1.3']}], server, [dh]),
+
+ %% Error
+ ?ERR({dh, not_a_bin}, [{dh, not_a_bin}], server),
+ ?ERR({dhfile, not_a_filename}, [{dhfile, not_a_filename}], server),
+ ?ERR({option, server_only, dhfile}, [{dhfile, "file"}], client),
+ ?ERR({option, server_only, dh}, [{dh, <<"DER">>}], client),
+ ok.
+
+options_early_data(_Config) -> %% early_data, session_tickets and use_ticket
+ ?OK(#{early_data := undefined, session_tickets := disabled},
+ [], client),
+ ?OK(#{early_data := disabled, session_tickets := disabled, stateless_tickets_seed := undefined},
+ [], server),
+
+ ?OK(#{early_data := <<>>, session_tickets := auto},
+ [{early_data, <<>>}, {session_tickets, auto}], client),
+ ?OK(#{early_data := <<>>, session_tickets := manual, use_ticket := [<<1>>]},
+ [{early_data, <<>>}, {session_tickets, manual}, {use_ticket, [<<1>>]}],
+ client),
+
+ ?OK(#{early_data := enabled, stateless_tickets_seed := <<"foo">>},
+ [{early_data, enabled}, {session_tickets, stateless}, {stateless_tickets_seed, <<"foo">>}], server),
+ ?OK(#{early_data := enabled, stateless_tickets_seed := <<"foo">>},
+ [{early_data, enabled}, {session_tickets, stateless_with_cert}, {stateless_tickets_seed, <<"foo">>}], server),
+
+ ?OK(#{early_data := disabled}, [{early_data, disabled}], server),
+
+ %% Errors
+ ?ERR({option, client_only, use_ticket}, [{use_ticket, []}], server),
+ ?ERR({options, incompatible, _}, [{use_ticket, [<<>>]}], client),
+
+ ?ERR({options, {session_tickets, foo}}, [{session_tickets, foo}], server),
+ ?ERR({options, {session_tickets, manual}}, [{session_tickets, manual}], server),
+ ?ERR({options, {session_tickets, stateful}}, [{session_tickets, stateful}], client),
+ ?ERR({options, incompatible, [session_tickets, {versions, _}]},
+ [{session_tickets, stateful}, {versions, ['tlsv1.2']}], server),
+ ?ERR({options, incompatible, [session_tickets, {versions, _}]},
+ [{session_tickets, stateful_with_cert}, {versions, ['tlsv1.2']}], server),
+
+ ?ERR({use_ticket, foo},
+ [{use_ticket, foo}, {session_tickets, manual}], client),
+
+ ?ERR({early_data,undefined}, [{early_data, undefined}], client),
+ ?ERR({options, incompatible, [early_data, {session_tickets, _}]},
+ [{early_data, <<>>}], client),
+ ?ERR({options, {early_data, enabled}},
+ [{early_data, enabled}, {session_tickets, auto}], client),
+ ?ERR({options, incompatible, [early_data, _, {use_ticket, _}]},
+ [{early_data, <<>>}, {session_tickets, manual}], client),
+
+ ?ERR({options, incompatible, [early_data, {session_tickets, _}]},
+ [{early_data, enabled}], server),
+ ?ERR({options, {early_data, <<>>}},
+ [{early_data, <<>>}, {session_tickets, stateless}], server),
+ ?ERR({options, {early_data, <<>>}},
+ [{early_data, <<>>}, {session_tickets, stateless_with_cert}], server),
+
+ ?ERR({options, incompatible, [stateless_tickets_seed, {session_tickets, stateful}]},
+ [{stateless_tickets_seed, <<"foo">>}, {session_tickets, stateful}],
+ server),
+ ?ERR({options, incompatible, [stateless_tickets_seed, {session_tickets, stateful_with_cert}]},
+ [{stateless_tickets_seed, <<"foo">>}, {session_tickets, stateful_with_cert}],
+ server),
+ ?ERR({stateless_tickets_seed, foo},
+ [{stateless_tickets_seed, foo}, {session_tickets, stateless}],
+ server),
+ ?ERR({stateless_tickets_seed, foo},
+ [{stateless_tickets_seed, foo}, {session_tickets, stateless_with_cert}],
+ server),
+ ?ERR({option, server_only, stateless_tickets_seed},
+ [{stateless_tickets_seed, <<"foo">>}], client),
+ ok.
+
+options_eccs(_Config) ->
+ Curves = tl(ssl:eccs()),
+ ?OK(#{eccs := {elliptic_curves, [_|_]}}, [], client),
+ ?OK(#{eccs := {elliptic_curves, [_|_]}}, [{eccs, Curves}], client),
+
+ %% Errors
+ ?ERR({eccs, not_a_list}, [{eccs, not_a_list}], client),
+ ?ERR({eccs, none_valid}, [{eccs, []}], client),
+ ?ERR({eccs, none_valid}, [{eccs, [foo]}], client),
+ ok.
+
+options_verify(Config) -> %% fail_if_no_peer_cert, verify, verify_fun, partial_chain
+ Cert = proplists:get_value(cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)),
+ {ok, #config{ssl = DefOpts = #{verify_fun := {DefVerify,_}}}} = ssl:handle_options([{verify, verify_none}], client, "dummy.host.org"),
+
+ ?OK(#{fail_if_no_peer_cert := false, verify := verify_none, verify_fun := {DefVerify, []}, partial_chain := _},
+ [], server),
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := undefined, partial_chain := _},
+ [{fail_if_no_peer_cert, true}, {verify, verify_peer}, {cacerts, [Cert]}],
+ server),
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := undefined, partial_chain := _},
+ [{verify, verify_peer}, {cacerts, [Cert]}], server),
+
+ NewF3 = fun(_,_,_) -> ok end,
+ NewF4 = fun(_,_,_,_) -> ok end,
+ ?OK(#{}, [], client, [fail_if_no_peer_cert]),
+ ?OK(#{verify := verify_none, verify_fun := {NewF3, foo}, partial_chain := _},
+ [{verify_fun, {NewF3, foo}}], client),
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := {NewF3, foo}, partial_chain := _},
+ [{verify_fun, {NewF3, foo}}, {verify, verify_peer}, {cacerts, [Cert]}],
+ server),
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := {NewF4, foo}, partial_chain := _},
+ [{verify_fun, {NewF4, foo}}, {verify, verify_peer}, {cacerts, [Cert]}],
+ server),
+
+
+ %% check verify_fun in update_options case
+ #{verify_fun := undefined} = ssl:update_options([{verify, verify_peer}, {cacerts, [Cert]}], client, DefOpts),
+ #{verify_fun := {NewF3, bar}} = ssl:update_options([{verify, verify_peer}, {cacerts, [Cert]},
+ {verify_fun, {NewF3, bar}}],
+ client, DefOpts),
+
+ %% Errors
+ ?ERR({partial_chain, undefined}, [{partial_chain, undefined}], client),
+ ?ERR({options, incompatible, [{verify, verify_none}, {fail_if_no_peer_cert, true}]},
+ [{fail_if_no_peer_cert, true}], server),
+ ?ERR({options, incompatible, [{verify, _}, {cacerts, undefined}]}, [{verify, verify_peer}], client),
+ ?ERR({option, server_only, fail_if_no_peer_cert},
+ [{fail_if_no_peer_cert, true}, {verify, verify_peer}, {cacerts, [Cert]}],
+ client),
+ ?ERR({verify, verify}, [{verify, verify}], client),
+ ?ERR({options, incompatible, [{verify, _}, {cacerts, undefined}]}, [{verify, verify_peer}], server),
+ ?ERR({partial_chain, not_a_fun}, [{partial_chain, not_a_fun}], client),
+ ?ERR({verify_fun, not_a_fun}, [{verify_fun, not_a_fun}], client),
+ ok.
+
+options_fallback(_Config) ->
+ ?OK(#{fallback := false}, [], client),
+ ?OK(#{fallback := true}, [{fallback, true}], client),
+
+ %% Errors
+ ?ERR({option, client_only, fallback}, [{fallback, true}], server),
+ ?ERR({fallback, []}, [{fallback, []}], client),
+ ?ERR({options, incompatible, [fallback, {versions, _}]},
+ [{fallback, true}, {versions, ['tlsv1.3']}], client),
+ ok.
+
+options_handshake(_Config) -> %% handshake
+ ?OK(#{handshake := full, max_handshake_size := 131072},
+ [], client),
+ ?OK(#{handshake := hello, max_handshake_size := 123800},
+ [{handshake, hello}, {max_handshake_size, 123800}], client),
+
+
+ %% Errors
+ ?ERR({handshake, []}, [{handshake, []}], server),
+ ?ERR({max_handshake_size, 8388608}, [{max_handshake_size, 8388608}], server),
+ ?ERR({max_handshake_size, -1}, [{handshake, hello}, {max_handshake_size, -1}], client),
+ ok.
+
+options_process(_Config) -> % hibernate_after, spawn_opts
+ ?OK(#{}, [], client, [hibernate_after, receiver_spawn_opts, sender_spawn_opts]),
+ ?OK(#{hibernate_after := 10000,
+ receiver_spawn_opts := [{fullsweep_after, 500}],
+ sender_spawn_opts := [{fullsweep_after, 500}]},
+ [{hibernate_after, 10000},
+ {receiver_spawn_opts,[{fullsweep_after, 500}]},
+ {sender_spawn_opts, [{fullsweep_after, 500}]}],
+ client),
+ %% Errors
+ ?ERR({hibernate_after, -1}, [{hibernate_after, -1}], server),
+ ?ERR({receiver_spawn_opts, not_a_list}, [{receiver_spawn_opts, not_a_list}], server),
+ ?ERR({sender_spawn_opts, not_a_list}, [{sender_spawn_opts, not_a_list}], server),
+ ok.
+
+options_honor(_Config) -> %% honor_cipher_order & honor_ecc_order
+ ?OK(#{honor_cipher_order := false, honor_ecc_order := false},
+ [], server),
+ ?OK(#{honor_cipher_order := true, honor_ecc_order := true},
+ [{honor_cipher_order, true}, {honor_ecc_order, true}],
+ server),
+ %% Errors
+ ?ERR({option, server_only, honor_cipher_order},
+ [{honor_cipher_order, true}], client),
+ ?ERR({option, server_only, honor_ecc_order},
+ [{honor_ecc_order, true}], client),
+ ?ERR({honor_ecc_order, foo}, [{honor_ecc_order, foo}], server),
+ ok.
+
+options_debug(_Config) -> %% debug log_level keep_secrets
+ ?OK(#{log_level := notice}, [], server, [keep_secrets]),
+ ?OK(#{log_level := debug, keep_secrets := true},
+ [{log_level, debug}, {keep_secrets, true}], server),
+ ?OK(#{log_level := info},
+ [{log_level, info}, {keep_secrets, false}], server, [keep_secrets]),
+
+ %% Errors
+ ?ERR({log_level, foo}, [{log_level, foo}], server),
+ ?ERR({keep_secrets, foo}, [{keep_secrets, foo}], server),
+ ok.
+
+options_renegotiate(_Config) -> %% key_update_at renegotiate_at secure_renegotiate
+ ?OK(#{key_update_at := ?KEY_USAGE_LIMIT_AES_GCM, renegotiate_at := 268435456, secure_renegotiate := true},
+ [], server),
+ ?OK(#{key_update_at := 123456, renegotiate_at := 64000, secure_renegotiate := false},
+ [{key_update_at, 123456}, {renegotiate_at, 64000}, {secure_renegotiate, false}],
+ server),
+
+ %% Errors
+ ?ERR({options, incompatible, [key_update_at, {versions, _}]},
+ [{key_update_at, 123456}, {versions, ['tlsv1.2']}], server),
+ ?ERR({options, incompatible, [secure_renegotiate, {versions, _}]},
+ [{secure_renegotiate, true}, {versions, ['tlsv1.3']}], server),
+
+ ?ERR({key_update_at, -1}, [{key_update_at, -1}], server),
+ ?ERR({renegotiate_at, not_a_int}, [{renegotiate_at, not_a_int}], server),
+ ?ERR({renegotiate_at, -1}, [{renegotiate_at, -1}], server),
+ ok.
+
+options_middlebox(_Config) -> %% middlebox_comp_mode
+ ?OK(#{}, [], client, [middlebox_comp_mode]),
+ ?OK(#{}, [{middlebox_comp_mode, true}], client, [middlebox_comp_mode]),
+ ?OK(#{middlebox_comp_mode := false}, [{middlebox_comp_mode, false}], client),
+
+ %% Errors
+ ?ERR({middlebox_comp_mode, foo}, [{middlebox_comp_mode, foo}], server),
+ ?ERR({options, incompatible, [middlebox_comp_mode, {versions, _}]},
+ [{middlebox_comp_mode, false}, {versions, ['tlsv1.2']}], server),
+ ok.
+
+options_frag_len(_Config) -> %% max_fragment_length
+ ?OK(#{max_fragment_length := undefined}, [], client),
+ ?OK(#{max_fragment_length := 2048}, [{max_fragment_length, 2048}], client),
+
+ %% Errors
+ ?ERR({option, client_only, max_fragment_length},
+ [{max_fragment_length, 2048}], server),
+ ?ERR({max_fragment_length,2000}, [{max_fragment_length, 2000}], client),
+ ok.
+
+options_oscp(Config) ->
+ Cert = proplists:get_value(
+ cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)),
+ NoOcspOption = [ocsp_stapling, ocsp_nonce, ocsp_responder_certs],
+
+ ?OK(#{}, [], client, NoOcspOption),
+ ?OK(#{}, [{ocsp_stapling, false}], client, NoOcspOption),
+ ?OK(#{ocsp_stapling := #{ocsp_nonce := true, ocsp_responder_certs := []}},
+ [{ocsp_stapling, true}], client, [ocsp_nonce, ocsp_responder_certs]),
+ ?OK(#{ocsp_stapling :=
+ #{ocsp_nonce := false, ocsp_responder_certs := [_, _]}},
+ [{ocsp_stapling, true}, {ocsp_nonce, false},
+ {ocsp_responder_certs, [Cert,Cert]}],
+ client, [ocsp_nonce, ocsp_responder_certs]),
+ %% Errors
+ ?ERR({ocsp_stapling, foo}, [{ocsp_stapling, 'foo'}], client),
+ ?ERR({ocsp_nonce, foo}, [{ocsp_nonce, 'foo'}], client),
+ ?ERR({ocsp_responder_certs, foo}, [{ocsp_responder_certs, 'foo'}], client),
+ ?ERR({options, incompatible, [{ocsp_nonce, false}, {ocsp_stapling, false}]},
+ [{ocsp_nonce, false}], client),
+ ?ERR({options, incompatible, [ocsp_responder_certs, {ocsp_stapling, false}]},
+ [{ocsp_responder_certs, [Cert]}], server),
+ ?ERR({ocsp_responder_certs, [_]},
+ [{ocsp_stapling, true}, {ocsp_responder_certs, ['NOT A BINARY']}],
+ client),
+ ok.
+
+options_padding(_Config) ->
+ ?OK(#{}, [], server, [padding_check]),
+ ?OK(#{padding_check := false}, [{padding_check, false}, {versions, [tlsv1]}], server),
+ %% Errors
+ ?ERR({padding_check, foo}, [{padding_check, foo}, {versions, [tlsv1]}], server),
+ ?ERR({options, incompatible, [padding_check, {versions, ['tlsv1.3','tlsv1.2']}]},
+ [{padding_check, false}], server),
+ ok.
+
+options_identity(_Config) -> %% psk_identity srp_identity and user_lookup_fun
+ ?OK(#{psk_identity := undefined, srp_identity := undefined, user_lookup_fun := undefined},
+ [], client),
+ ?OK(#{psk_identity := <<"foobar">>, srp_identity := undefined, user_lookup_fun := undefined},
+ [{psk_identity, "foobar"}], server),
+ ?OK(#{psk_identity := undefined, srp_identity := {<<"user">>, <<"pwd">>}, user_lookup_fun := undefined},
+ [{srp_identity, {"user", "pwd"}}], client),
+ ?OK(#{psk_identity := undefined, srp_identity := undefined, user_lookup_fun := {_, args}},
+ [{user_lookup_fun, {fun(_,_,_) -> ok end, args}}], client),
+
+ %% Should fail client option only
+ ?OK(#{psk_identity := undefined, srp_identity := {<<"user">>, <<"pwd">>}, user_lookup_fun := undefined},
+ [{srp_identity, {"user", "pwd"}}], server),
+
+ %% Errors
+ ?ERR({srp_identity, {_,_}}, %% FIXME doesn't like binary strings
+ [{srp_identity, {<<"user">>, <<"pwd">>}}], client),
+ ?ERR({psk_identity, _}, %% FIXME doesn't like binary strings
+ [{psk_identity, <<"user">>}], client),
+ ?ERR({srp_identity, _}, [{srp_identity, "user"}], client),
+ ?ERR({user_lookup_fun, _},
+ [{user_lookup_fun, {fun(_,_) -> ok end, args}}],
+ client),
+
+ ?ERR({options, incompatible, [psk_identity, _]},
+ [{psk_identity, "foobar"},{versions, ['tlsv1.3']}],
+ server),
+ ?ERR({options, incompatible, [srp_identity, _]},
+ [{srp_identity, {"user", "pwd"}},{versions, ['tlsv1.3']}],
+ client),
+ ?ERR({options, incompatible, [user_lookup_fun, _]},
+ [{user_lookup_fun, {fun(_,_,_) -> ok end, args}}, {versions, ['tlsv1.3']}],
+ client),
+ ok.
+
+options_reuse_session(_Config) ->
+ ?OK(#{reuse_session := undefined, reuse_sessions := true}, [], client),
+ ?OK(#{reuse_session := _, reuse_sessions := true}, [], server),
+
+ ?OK(#{reuse_session := <<>>, reuse_sessions := save},
+ [{reuse_session, <<>>}, {reuse_sessions, save}], client),
+ ?OK(#{reuse_session := {<<>>, <<>>}, reuse_sessions := false},
+ [{reuse_session, {<<>>, <<>>}}, {reuse_sessions, false}], client),
+
+ RS_F = fun(_,_,_,_) -> ok end,
+ ?OK(#{reuse_session := RS_F, reuse_sessions := false},
+ [{reuse_session, RS_F}, {reuse_sessions, false}],
+ server),
+
+ %% Errors
+ ?ERR({options, incompatible, [reuse_session, _]},
+ [{reuse_session, RS_F}, {versions, ['tlsv1.3']}],
+ server),
+ ?ERR({options, incompatible, [reuse_sessions, _]},
+ [{reuse_sessions, true}, {versions, ['tlsv1.3']}],
+ server),
+ ?ERR({reuse_session, RS_F},
+ [{reuse_session, RS_F},{reuse_sessions, false}],
+ client),
+ ?ERR({reuse_sessions, foo}, [{reuse_sessions, foo}], server),
+ ?ERR({reuse_session, foo}, [{reuse_session, foo}], server),
+ ?ERR({reuse_sessions, save}, [{reuse_sessions, save}], server),
+ ?ERR({reuse_session, <<>>}, [{reuse_session, <<>>}], server),
+
+ ok.
+
+options_sni(_Config) -> %% server_name_indication
+ ?OK(#{server_name_indication := "dummy.host.org"}, [], client),
+ ?OK(#{}, [], server, [server_name_indication]),
+ ?OK(#{server_name_indication := disable}, [{server_name_indication, disable}], client),
+ ?OK(#{server_name_indication := "dummy.org"}, [{server_name_indication, "dummy.org"}], client),
+
+ ?OK(#{sni_fun := _}, [], server, [sni_hosts]),
+
+ ?OK(#{sni_fun := _}, [{sni_hosts, [{"a", []}]}], server, [sni_hosts]),
+ SNI_F = fun(_) -> sni end,
+ ?OK(#{sni_fun := SNI_F}, [{sni_fun, SNI_F}], server, [sni_hosts]),
+
+ %% Errors
+ ?ERR({option, client_only, server_name_indication},
+ [{server_name_indication, "dummy.org"}], server),
+ ?ERR({option, server_only, sni_hosts}, [{sni_hosts, [{"a", []}]}], client),
+ ?ERR({option, server_only, sni_fun}, [{sni_fun, SNI_F}], client),
+
+ ?ERR({server_name_indication, foo}, [{server_name_indication, foo}], client),
+ ?ERR({sni_hosts, foo}, [{sni_hosts, foo}], server),
+ ?ERR({sni_fun, foo}, [{sni_fun, foo}], server),
+
+ ?ERR({options, incompatible, [sni_fun, sni_hosts]},
+ [{sni_fun, SNI_F}, {sni_hosts, [{"a", []}]}], server),
+ ok.
+
+options_sign_alg(_Config) -> %% signature_algs[_cert]
+ ?OK(#{signature_algs := [_|_], signature_algs_cert := undefined},
+ [], client),
+ ?OK(#{signature_algs := [rsa_pss_rsae_sha512,{sha512,rsa}], signature_algs_cert := undefined},
+ [{signature_algs, [rsa_pss_rsae_sha512,{sha512,rsa}]}], client),
+ ?OK(#{signature_algs := [_|_], signature_algs_cert := [eddsa_ed25519, rsa_pss_rsae_sha512]},
+ [{signature_algs_cert, [eddsa_ed25519, rsa_pss_rsae_sha512]}],
+ client),
+
+ %% Errors
+ ?ERR({signature_algs, not_a_list}, [{signature_algs, not_a_list}], client),
+ ?ERR({signature_algs_cert, not_a_list}, [{signature_algs_cert, not_a_list}], client),
+ ?ERR({signature_algs, [foobar]}, [{signature_algs, [foobar]}], client),
+ ?ERR({signature_algs_cert, [foobar]}, [{signature_algs_cert, [foobar]}], client),
+ ?ERR({options, incompatible, [signature_algs_cert, {versions, _}]},
+ [{signature_algs_cert, [eddsa_ed25519, rsa_pss_rsae_sha512]}, {versions, ['tlsv1.1']}],
+ client),
+ ?ERR({options, incompatible, [signature_algs_cert, {versions, _}]},
+ [{signature_algs_cert, [eddsa_ed25519, rsa_pss_rsae_sha512]}, {versions, ['tlsv1.1']}],
+ server),
+ ?ERR({options, {no_supported_algorithms, {signature_algs,[]}}},
+ [{signature_algs, []}], client),
+ ?ERR({options, {no_supported_signature_schemes, {signature_algs_cert,[]}}},
+ [{signature_algs_cert, []}],
+ client),
+ ok.
+
+options_supported_groups(_Config) ->
+ %% FIXME group() type doesn't cover the values below
+ ?OK(#{supported_groups := {supported_groups, [x25519,x448,secp256r1,secp384r1]}},
+ [], client),
+ ?OK(#{supported_groups := {supported_groups, [secp521r1, ffdhe2048]}},
+ [{supported_groups, [secp521r1, ffdhe2048]}], client),
+
+ %% ERRORs
+ ?ERR({{'tlvs1.2'},{versions,[{'tlvs1.2'}]}},
+ [{supported_groups, []}, {versions, [{'tlvs1.2'}]}], client),
+ ?ERR({supported_groups, not_a_list}, [{supported_groups, not_a_list}], client),
+ ?ERR({supported_groups, none_valid}, [{supported_groups, []}], client),
+ ?ERR({supported_groups, none_valid}, [{supported_groups, [foo]}], client),
+ ok.
+
+options_use_srtp(_Config) ->
+ ?OK(#{use_srtp := #{protection_profiles := [<<0,2>>, <<0,5>>], mki := <<>>}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<0,2>>, <<0,5>>]}}], client),
+ ?OK(#{use_srtp := #{protection_profiles := [<<0,2>>], mki := <<>>}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<0,2>>]}}], server),
+ ?OK(#{use_srtp := #{protection_profiles := [<<0,2>>, <<0,5>>], mki := <<"123">>}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<0,2>>, <<0,5>>], mki => <<"123">>}}], client),
+ ?OK(#{use_srtp := #{protection_profiles := [<<0,2>>], mki := <<"123">>}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<0,2>>], mki => <<"123">>}}], server),
+
+ %% invalid parameters
+ ?ERR({options, {use_srtp, {no_protection_profiles, _}}},
+ [{protocol, dtls},
+ {use_srtp, #{}}], client),
+ ?ERR({options, {use_srtp, {no_protection_profiles, _}}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => []}}], client),
+ ?ERR({options, {use_srtp, {invalid_protection_profiles, _}}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<"bad">>]}}], client),
+ ?ERR({options, {use_srtp, {unknown_parameters,[whatever]}}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<0,2>>], whatever => xxx}}], client),
+
+ %% use_srtp is DTLS-only extension, by default setting its options should raise an error
+ ?ERR({options, incompatible, _},
+ [{use_srtp, #{protection_profiles => [<<0,2>>, <<0,5>>]}}], client),
+ ?ERR({options, incompatible, _},
+ [{use_srtp, #{protection_profiles => [<<0,2>>]}}], server),
+ ok.
+
%%-------------------------------------------------------------------
default_reject_anonymous()->
@@ -2209,7 +3075,7 @@ cb_info() ->
[{doc,"Test that we can set cb_info."}].
cb_info(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
Protocol = proplists:get_value(protocol, Config, tls),
CbInfo =
@@ -2240,7 +3106,7 @@ log_alert() ->
" that has replaced this option"}].
log_alert(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -2360,8 +3226,8 @@ client_options_negative_dependency_version() ->
client_options_negative_dependency_version(Config) when is_list(Config) ->
start_client_negative(Config, [{versions, ['tlsv1.1', 'tlsv1.2']},
{session_tickets, manual}],
- {options,dependency,
- {session_tickets,{versions,['tlsv1.3']}}}).
+ {options,incompatible,
+ [session_tickets,{versions,['tlsv1.2', 'tlsv1.1']}]}).
%%--------------------------------------------------------------------
client_options_negative_dependency_stateless() ->
@@ -2370,8 +3236,7 @@ client_options_negative_dependency_stateless(Config) when is_list(Config) ->
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{anti_replay, '10k'},
{session_tickets, manual}],
- {options,dependency,
- {anti_replay,{session_tickets,[stateless]}}}).
+ {option, server_only, anti_replay}).
%%--------------------------------------------------------------------
@@ -2380,45 +3245,37 @@ client_options_negative_dependency_role() ->
client_options_negative_dependency_role(Config) when is_list(Config) ->
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, stateless}],
- {options,role,
- {session_tickets,{stateless,{client,[disabled,manual,auto]}}}}).
+ {options,{session_tickets,stateless}}).
%%--------------------------------------------------------------------
client_options_negative_early_data() ->
[{doc,"Test client option early_data."}].
client_options_negative_early_data(Config) when is_list(Config) ->
start_client_negative(Config, [{versions, ['tlsv1.2']},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{versions,['tlsv1.3']}}}),
+ {session_tickets, manual},
+ {early_data, <<"test">>}],
+ {options,incompatible,
+ [session_tickets,{versions,['tlsv1.2']}]}),
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{session_tickets,[manual,auto]}}}),
+ {early_data, <<"test">>}],
+ {options,incompatible,
+ [early_data,{session_tickets,disabled}]}),
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, stateful},
- {early_data, "test"}],
- {options,role,
- {session_tickets,
- {stateful,{client,[disabled,manual,auto]}}}}),
- start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
- {session_tickets, disabled},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{session_tickets,[manual,auto]}}}),
+ {early_data, <<"test">>}],
+ {options, {session_tickets, stateful}}),
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, manual},
- {early_data, "test"}],
- {options,dependency,
- {early_data, use_ticket}}),
+ {early_data, <<"test">>}],
+ {options,incompatible,
+ [early_data, {session_tickets, manual}, {use_ticket, undefined}]}),
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, manual},
{use_ticket, [<<"ticket">>]},
{early_data, "test"}],
- {options, type,
- {early_data, {"test", not_binary}}}),
+ {options, {early_data, "test"}}),
%% All options are ok but there is no server
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, manual},
@@ -2426,11 +3283,6 @@ client_options_negative_early_data(Config) when is_list(Config) ->
{early_data, <<"test">>}],
econnrefused),
- start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
- {session_tickets, auto},
- {early_data, "test"}],
- {options, type,
- {early_data, {"test", not_binary}}}),
%% All options are ok but there is no server
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, auto},
@@ -2442,31 +3294,25 @@ server_options_negative_early_data() ->
[{doc,"Test server option early_data."}].
server_options_negative_early_data(Config) when is_list(Config) ->
start_server_negative(Config, [{versions, ['tlsv1.2']},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{versions,['tlsv1.3']}}}),
+ {early_data, enabled},
+ {session_tickets, stateful}
+ ],
+ {options,incompatible,
+ [session_tickets,{versions,['tlsv1.2']}]}),
start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{session_tickets,[stateful,stateless]}}}),
+ {early_data, enabled}],
+ {options,incompatible,
+ [early_data,{session_tickets,disabled}]}),
start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, manual},
- {early_data, "test"}],
- {options,role,
- {session_tickets,
- {manual,{server,[disabled,stateful,stateless]}}}}),
- start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
- {session_tickets, disabled},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{session_tickets,[stateful,stateless]}}}),
+ {early_data, enabled}],
+ {options, {session_tickets, manual}}),
start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, stateful},
{early_data, "test"}],
- {options,role,
- {early_data,{"test",{server,[disabled,enabled]}}}}).
+ {options, {early_data, "test"}}).
%%--------------------------------------------------------------------
server_options_negative_version_gap() ->
@@ -2482,8 +3328,36 @@ server_options_negative_dependency_role() ->
server_options_negative_dependency_role(Config) when is_list(Config) ->
start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, manual}],
- {options,role,
- {session_tickets,{manual,{server,[disabled,stateful,stateless]}}}}).
+ {options,{session_tickets,manual}}).
+
+%%--------------------------------------------------------------------
+server_options_negative_stateless_tickets_seed() ->
+ [{doc, "Test server option stateless_tickets_seed"}].
+server_options_negative_stateless_tickets_seed(Config) ->
+ Seed = crypto:strong_rand_bytes(32),
+
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {stateless_tickets_seed, Seed}],
+ {options, incompatible,
+ [stateless_tickets_seed, {session_tickets, disabled}]}),
+
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, disabled},
+ {stateless_tickets_seed, Seed}],
+ {options, incompatible,
+ [stateless_tickets_seed, {session_tickets, disabled}]}),
+
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, stateful},
+ {stateless_tickets_seed, Seed}],
+ {options, incompatible,
+ [stateless_tickets_seed, {session_tickets, stateful}]}),
+
+ InvalidSeed1 = 12345,
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, stateless},
+ {stateless_tickets_seed, InvalidSeed1}],
+ {options, {stateless_tickets_seed, InvalidSeed1}}).
%%--------------------------------------------------------------------
honor_server_cipher_order_tls13() ->
@@ -2541,27 +3415,24 @@ invalid_options_tls13() ->
invalid_options_tls13(Config) when is_list(Config) ->
TestOpts =
[{{beast_mitigation, one_n_minus_one},
- {options, dependency,
- {beast_mitigation,{versions,[tlsv1]}}},
+ {options, incompatible,
+ [beast_mitigation,{versions,['tlsv1.3']}]},
common},
{{next_protocols_advertised, [<<"http/1.1">>]},
- {options, dependency,
- {next_protocols_advertised,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible,
+ [next_protocols_advertised, {versions,['tlsv1.3']}]},
server},
{{client_preferred_next_protocols,
{client, [<<"http/1.1">>]}},
- {options, dependency,
- {client_preferred_next_protocols,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible,
+ [client_preferred_next_protocols, {versions,['tlsv1.3']}]},
client},
{{client_renegotiation, false},
- {options, dependency,
- {client_renegotiation,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible,
+ [client_renegotiation, {versions,['tlsv1.3']}]},
server
},
@@ -2571,49 +3442,36 @@ invalid_options_tls13(Config) when is_list(Config) ->
},
{{padding_check, false},
- {options, dependency,
- {padding_check,{versions,[tlsv1]}}},
+ {options, incompatible, [padding_check,{versions,['tlsv1.3']}]},
common},
{{psk_identity, "Test-User"},
- {options, dependency,
- {psk_identity,{versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [psk_identity,{versions,['tlsv1.3']}]},
common},
{{user_lookup_fun,
{fun ssl_test_lib:user_lookup/3, <<1,2,3>>}},
- {options, dependency,
- {user_lookup_fun,{versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [user_lookup_fun,{versions,['tlsv1.3']}]},
common},
{{reuse_session, fun(_,_,_,_) -> false end},
- {options, dependency,
- {reuse_session,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [reuse_session, {versions,['tlsv1.3']}]},
server},
{{reuse_session, <<1,2,3,4>>},
- {options, dependency,
- {reuse_session,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [reuse_session, {versions,['tlsv1.3']}]},
client},
{{reuse_sessions, true},
- {options, dependency,
- {reuse_sessions,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [reuse_sessions, {versions,['tlsv1.3']}]},
common},
{{secure_renegotiate, false},
- {options, dependency,
- {secure_renegotiate,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [secure_renegotiate, {versions,['tlsv1.3']}]},
common},
- {{srp_identity, false},
- {options, dependency,
- {srp_identity,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {{srp_identity, {"user", "passwd"}},
+ {options, incompatible, [srp_identity, {versions,['tlsv1.3']}]},
client}
],
@@ -2644,65 +3502,6 @@ cookie(Config) when is_list(Config) ->
cookie_extension(Config, true),
cookie_extension(Config, false).
-warn_verify_none() ->
- [{doc, "Test that verify_none default generates warning."}].
-warn_verify_none(Config) when is_list(Config)->
- ok = logger:add_handler(?MODULE,?MODULE,#{config=>self()}),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
-
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {options, ServerOpts},
- {mfa, {ssl_test_lib, no_result, []}}]),
- Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {from, self()},
- {host, Hostname},
- {options, ClientOpts},
- {mfa, {ssl_test_lib, no_result, []}}]),
- receive
- warning_generated ->
- ok = logger:remove_handler(?MODULE)
- after 500 ->
- ok = logger:remove_handler(?MODULE),
- ct:fail(no_warning)
- end,
- ssl_test_lib:close(Client),
- ssl_test_lib:close(Server).
-
-suppress_warn_verify_none() ->
- [{doc, "Test that explicit verify_none suppresses warning."}].
-suppress_warn_verify_none(Config) when is_list(Config)->
- ok = logger:add_handler(?MODULE,?MODULE,#{config=>self()}),
-
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
-
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {options, ServerOpts},
- {mfa, {ssl_test_lib, no_result, []}}]),
- Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {from, self()},
- {host, Hostname},
- {options, [{verify, verify_none} | ClientOpts]},
- {mfa, {ssl_test_lib, no_result, []}}]),
- receive
- warning_generated ->
- ok = logger:remove_handler(?MODULE),
- ct:fail(warning)
- after 500 ->
- ok = logger:remove_handler(?MODULE)
- end,
- ssl_test_lib:close(Client),
- ssl_test_lib:close(Server).
-
%%--------------------------------------------------------------------
check_random_nonce() ->
[{doc,"Test random nonce - expecting 32B random for TLS1.3 and 4B UTC "
@@ -3058,13 +3857,10 @@ active_n_common(S, N) ->
ok({ok,V}) -> V.
repeat(N, Fun) ->
- repeat(N, N, Fun).
+ Repeat = fun F(Arg) when is_integer(Arg), Arg > 0 -> Fun(N - Arg), F(Arg - 1);
+ F(_) -> ok end,
+ Repeat(N).
-repeat(N, T, Fun) when is_integer(N), N > 0 ->
- Fun(T-N),
- repeat(N-1, T, Fun);
-repeat(_, _, _) ->
- ok.
get_close(Pid, Where) ->
receive
@@ -3224,55 +4020,69 @@ log(#{msg:={report,_Report}},#{config:=Pid}) ->
log(_,_) ->
ok.
-length_exclusive({3,_} = Version) ->
- length(exclusive_default_up_to_version(Version, [])) +
- length(exclusive_non_default_up_to_version(Version, []));
-length_exclusive({254,_} = Version) ->
- length(dtls_exclusive_default_up_to_version(Version, [])) +
- length(dtls_exclusive_non_default_up_to_version(Version, [])).
+length_exclusive(Version) when ?TLS_1_X(Version) ->
+ length(exclusive_default_up_to_version(Version)) +
+ length(exclusive_non_default_up_to_version(Version));
+length_exclusive(Version) when ?DTLS_1_X(Version)->
+ length(dtls_exclusive_default_up_to_version(Version)) +
+ length(dtls_exclusive_non_default_up_to_version(Version)).
length_all(Version) ->
length(ssl:cipher_suites(all, Version)).
-exclusive_default_up_to_version({3, 1} = Version, Acc) ->
- ssl:cipher_suites(exclusive, Version) ++ Acc;
-exclusive_default_up_to_version({3, Minor} = Version, Acc) when Minor =< 4 ->
- Suites = ssl:cipher_suites(exclusive, Version),
- exclusive_default_up_to_version({3, Minor-1}, Suites ++ Acc).
-
-dtls_exclusive_default_up_to_version({254, 255} = Version, Acc) ->
- ssl:cipher_suites(exclusive, Version) ++ Acc;
-dtls_exclusive_default_up_to_version({254, 253} = Version, Acc) ->
- Suites = ssl:cipher_suites(exclusive, Version),
- dtls_exclusive_default_up_to_version({254, 255}, Suites ++ Acc).
-
-exclusive_non_default_up_to_version({3, 1} = Version, Acc) ->
- exclusive_non_default_version(Version) ++ Acc;
-exclusive_non_default_up_to_version({3, 4}, Acc) ->
- exclusive_non_default_up_to_version({3, 3}, Acc);
-exclusive_non_default_up_to_version({3, Minor} = Version, Acc) when Minor =< 3 ->
- Suites = exclusive_non_default_version(Version),
- exclusive_non_default_up_to_version({3, Minor-1}, Suites ++ Acc).
-
-dtls_exclusive_non_default_up_to_version({254, 255} = Version, Acc) ->
- dtls_exclusive_non_default_version(Version) ++ Acc;
-dtls_exclusive_non_default_up_to_version({254, 253} = Version, Acc) ->
- Suites = dtls_exclusive_non_default_version(Version),
- dtls_exclusive_non_default_up_to_version({254, 255}, Suites ++ Acc).
-
-exclusive_non_default_version({_, Minor}) ->
- tls_v1:psk_exclusive(Minor) ++
- tls_v1:srp_exclusive(Minor) ++
- tls_v1:rsa_exclusive(Minor) ++
- tls_v1:des_exclusive(Minor) ++
- tls_v1:rc4_exclusive(Minor).
+exclusive_default_up_to_version(Version) ->
+ lists:flatmap(fun (Vsn) -> ssl:cipher_suites(exclusive, Vsn) end
+ , exclusive_default_up_to_version_helper(Version)).
+
+exclusive_default_up_to_version_helper(?TLS_1_3) -> [?TLS_1_3, ?TLS_1_2, ?TLS_1_1, ?TLS_1_0];
+exclusive_default_up_to_version_helper(?TLS_1_2) -> [?TLS_1_2, ?TLS_1_1, ?TLS_1_0];
+exclusive_default_up_to_version_helper(?TLS_1_1) -> [?TLS_1_1, ?TLS_1_0];
+exclusive_default_up_to_version_helper(?TLS_1_0) -> [?TLS_1_0].
+
+
+
+dtls_exclusive_default_up_to_version(Version) ->
+ lists:flatmap( fun (Vsn) -> ssl:cipher_suites(exclusive, Vsn) end
+ , dtls_exclusive_default_up_to_version_helper(Version)).
+
+dtls_exclusive_default_up_to_version_helper(?DTLS_1_2) -> [?DTLS_1_0, ?DTLS_1_2];
+dtls_exclusive_default_up_to_version_helper(?DTLS_1_0) -> [?DTLS_1_0].
+
+
+
+exclusive_non_default_up_to_version(Version) ->
+ lists:flatmap(fun exclusive_non_default_version/1
+ , exclusive_non_default_up_to_version_helper(Version)).
+
+exclusive_non_default_up_to_version_helper(?TLS_1_3) -> [?TLS_1_2, ?TLS_1_1, ?TLS_1_0];
+exclusive_non_default_up_to_version_helper(?TLS_1_2) -> [?TLS_1_2, ?TLS_1_1, ?TLS_1_0];
+exclusive_non_default_up_to_version_helper(?TLS_1_1) -> [?TLS_1_1, ?TLS_1_0];
+exclusive_non_default_up_to_version_helper(?TLS_1_0) -> [?TLS_1_0].
+
+
+dtls_exclusive_non_default_up_to_version(Version) ->
+ lists:flatmap( fun dtls_exclusive_non_default_version/1
+ , dtls_exclusive_non_default_up_to_version_helper(Version)).
+
+dtls_exclusive_non_default_up_to_version_helper(?DTLS_1_2) -> [?DTLS_1_0, ?DTLS_1_2];
+dtls_exclusive_non_default_up_to_version_helper(?DTLS_1_0) -> [?DTLS_1_0].
+
+
+exclusive_non_default_version(Version) ->
+ Ls = [ fun tls_v1:psk_exclusive/1
+ , fun tls_v1:srp_exclusive/1
+ , fun tls_v1:rsa_exclusive/1
+ , fun tls_v1:des_exclusive/1
+ , fun tls_v1:rc4_exclusive/1],
+ lists:flatmap(fun(Fn) -> Fn(Version) end, Ls).
dtls_exclusive_non_default_version(DTLSVersion) ->
- {_,Minor} = ssl:tls_version(DTLSVersion),
- tls_v1:psk_exclusive(Minor) ++
- tls_v1:srp_exclusive(Minor) ++
- tls_v1:rsa_exclusive(Minor) ++
- tls_v1:des_exclusive(Minor).
+ Version = ssl:tls_version(DTLSVersion),
+ Fns = [ fun tls_v1:psk_exclusive/1
+ , fun tls_v1:srp_exclusive/1
+ , fun tls_v1:rsa_exclusive/1
+ , fun tls_v1:des_exclusive/1],
+ lists:flatmap(fun (Fn) -> Fn(Version) end, Fns).
selected_peer(ExpectedClient,
ExpectedServer, ClientOpts, ServerOpts, Config) ->
@@ -3342,21 +4152,36 @@ test_config('tlsv1.2', _) ->
{SDSACert, SDSAKey, SDSACACerts} = get_single_options(cert, key, cacerts, SDSAOpts),
{CDSACert, CDSAKey, CDSACACerts} = get_single_options(cert, key, cacerts, CDSAOpts),
+ AllSigAlgs1_2 = ssl_test_lib:all_1_2_sig_algs(),
- [{#{server_config => [{certs_keys, [#{cert => SDSACert, key => SDSAKey}, #{cert => SRSACert, key => SRSAKey}]},
- {verify, verify_peer}, {versions, ['tlsv1.3', 'tlsv1.2']},
+ [{#{server_config => [{certs_keys,
+ [#{cert => SDSACert, key => SDSAKey},
+ #{cert => SRSACert, key => SRSAKey}]},
+ {verify, verify_peer},
+ {versions, ['tlsv1.3', 'tlsv1.2']},
{cacerts, SRSACACerts ++ SDSACACerts}],
- client_config => [{certs_keys, [#{cert => CDSACert, key => CDSAKey}, #{cert => CRSACert, key => CRSAKey}]},
- {verify, verify_peer}, {versions, ['tlsv1.3', 'tlsv1.2']},
+ client_config => [{certs_keys,
+ [#{cert => CDSACert, key => CDSAKey},
+ #{cert => CRSACert, key => CRSAKey}]},
+ {verify, verify_peer},
+ {versions, ['tlsv1.3', 'tlsv1.2']},
{cacerts, CRSACACerts ++ CDSACACerts}]
- }, {client_peer, SRSACert}, {server_peer, CRSACert}},
- {#{server_config => [{certs_keys, [#{cert => SDSACert, key => SDSAKey}, #{cert => SRSACert, key => SRSAKey}]},
- {verify, verify_peer}, {versions, ['tlsv1.2']},
+ },
+ {client_peer, SRSACert}, {server_peer, CRSACert}},
+ {#{server_config => [{certs_keys, [#{cert => SDSACert, key => SDSAKey},
+ #{cert => SRSACert, key => SRSAKey}]},
+ {verify, verify_peer},
+ AllSigAlgs1_2,
+ {versions, ['tlsv1.2']},
{cacerts, SRSACACerts ++ SDSACACerts}],
- client_config => [{certs_keys, [#{cert => CDSACert, key => CDSAKey}, #{cert => CRSACert, key => CRSAKey}]},
- {verify, verify_peer}, {versions, ['tlsv1.2']},
+ client_config => [{certs_keys, [#{cert => CDSACert, key => CDSAKey},
+ #{cert => CRSACert, key => CRSAKey}]},
+ {verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ AllSigAlgs1_2,
{cacerts, CRSACACerts ++ CDSACACerts}]
- }, {client_peer, SDSACert}, {server_peer, CDSACert}}];
+ },
+ {client_peer, SDSACert}, {server_peer, CDSACert}}];
test_config('dtlsv1.2', Config) ->
#{server_config := SRSAPSSOpts,
client_config := CRSAPSSOpts} = ssl_test_lib:make_rsa_pss_pem(rsa_pss_pss, [], Config, "dtls_pss_pss_conf"),
@@ -3376,6 +4201,7 @@ test_config('dtlsv1.2', Config) ->
client_config => [{certs_keys, [#{certfile => CRSAPSSRSAECert, keyfile => CRSAPSSRSAEKey},
#{certfile => CRSAPSSCert, keyfile => CRSAPSSKey}]},
{verify, verify_peer},
+ {signature_algs, [rsa_pss_pss_sha256]},
{cacertfile, CRSAPSSCACerts}]
},
{client_peer, pem_to_der_cert(SRSAPSSCert)}, {server_peer, pem_to_der_cert(CRSAPSSCert)}},
@@ -3460,7 +4286,8 @@ pem_to_der_cert(Pem) ->
test_sha1_cert_conf('tlsv1.3'= Version, RSA, ECDSA, Config) ->
run_sha1_cert_conf(Version, ECDSA, Config, ecdsa_sha1),
run_sha1_cert_conf(Version, RSA, Config, rsa_pkcs1_sha1);
-test_sha1_cert_conf('tlsv1.2'= Version, RSA, ECDSA, Config) ->
+test_sha1_cert_conf(Version, RSA, ECDSA, Config) when Version == 'tlsv1.2';
+ Version == 'dtlsv1.2' ->
run_sha1_cert_conf(Version, RSA, Config, {sha, rsa}),
run_sha1_cert_conf(Version, ECDSA, Config, {sha, ecdsa});
test_sha1_cert_conf(Version,RSA,_,Config) ->
@@ -3488,7 +4315,8 @@ run_sha1_cert_conf('tlsv1.3', #{client_config := ClientOpts, server_config := Se
ssl_test_lib:basic_test([{verify, verify_peer}, {signature_algs, IncludeLegacyAlg} | ClientOpts],
[{signature_algs, IncludeLegacyAlg} | ServerOpts], Config);
-run_sha1_cert_conf('tlsv1.2', #{client_config := ClientOpts, server_config := ServerOpts}, Config, LegacyAlg) ->
+run_sha1_cert_conf(Version, #{client_config := ClientOpts, server_config := ServerOpts}, Config, LegacyAlg) when Version == 'tlsv1.2';
+ Version == 'dtlsv1.2' ->
SigAlgs = [%% SHA2
{sha512, ecdsa},
{sha512, rsa},
@@ -3501,5 +4329,9 @@ run_sha1_cert_conf('tlsv1.2', #{client_config := ClientOpts, server_config := Se
IncludeLegacyAlg = SigAlgs ++ [LegacyAlg],
ssl_test_lib:basic_test( [{verify, verify_peer}, {signature_algs, IncludeLegacyAlg} | ClientOpts],
[{signature_algs, IncludeLegacyAlg} | ServerOpts], Config);
-run_sha1_cert_conf(_, #{client_config := ClientOpts, server_config := ServerOpts}, Config, _) ->
- ssl_test_lib:basic_test([{verify, verify_peer} | ClientOpts], ServerOpts, Config).
+run_sha1_cert_conf(_, #{client_config := ClientOpts, server_config := ServerOpts}, Config, LegacyAlg) ->
+ NVersion = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ SigOpts = ssl_test_lib:sig_algs(LegacyAlg, NVersion),
+ ssl_test_lib:basic_test([{verify, verify_peer} | ClientOpts] ++ SigOpts, ServerOpts, Config).
+
+
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index db306419aa..52242d8728 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -483,7 +483,8 @@ fake_root(Config) when is_list(Config) ->
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = #{cert := Cert,
key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
- {extensions, Ext}]),
+ {digest, sha256},
+ {extensions, Ext}]),
FakeKey = ssl_test_lib:hardcode_rsa_key(1),
OTPCert = public_key:pkix_decode_cert(Cert, otp),
TBS = OTPCert#'OTPCertificate'.tbsCertificate,
@@ -495,29 +496,42 @@ fake_root(Config) when is_list(Config) ->
AuthExt,
false}]),
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
- {extensions, [AuthKeyExt]}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => #{cert => FakeCert, key => FakeKey},
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
- {extensions, [AuthKeyExt]}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {digest, sha256},
+ {extensions, [AuthKeyExt]}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
+
+ #{server_config := FakeServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256},
+ {extensions, [AuthKeyExt]}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
+
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf,
+ ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
fake_root_no_intermediate() ->
[{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
@@ -528,7 +542,8 @@ fake_root_no_intermediate(Config) when is_list(Config) ->
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = #{cert := Cert,
key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
- {extensions, Ext}]),
+ {digest, sha256},
+ {extensions, Ext}]),
FakeKey = ssl_test_lib:hardcode_rsa_key(1),
OTPCert = public_key:pkix_decode_cert(Cert, otp),
@@ -541,28 +556,38 @@ fake_root_no_intermediate(Config) when is_list(Config) ->
AuthExt,
false}]),
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
- {extensions, [AuthKeyExt]}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => #{cert => FakeCert, key => FakeKey},
- intermediates => [],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
- {extensions, [AuthKeyExt]}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
- test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256},
+ {extensions, [AuthKeyExt]}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}),
+
+ #{server_config := FakeServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256},
+ {extensions, [AuthKeyExt]}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}),
+
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf,
+ ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
fake_root_legacy() ->
[{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
@@ -572,34 +597,44 @@ fake_root_legacy(Config) when is_list(Config) ->
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = #{cert := Cert,
key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
- {extensions, Ext}]),
+ {digest, sha256},
+ {extensions, Ext}]),
FakeKey = ssl_test_lib:hardcode_rsa_key(1),
OTPCert = public_key:pkix_decode_cert(Cert, otp),
TBS = OTPCert#'OTPCertificate'.tbsCertificate,
FakeCert = public_key:pkix_sign(TBS, FakeKey),
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => #{cert => FakeCert, key => FakeKey},
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
-
- test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}),
+ #{server_config := FakeServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256} ],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf,
+ ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
fake_root_no_intermediate_legacy() ->
[{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
@@ -609,7 +644,8 @@ fake_root_no_intermediate_legacy(Config) when is_list(Config) ->
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = #{cert := Cert,
key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
- {extensions, Ext}]),
+ {digest, sha256},
+ {extensions, Ext}]),
FakeKey = ssl_test_lib:hardcode_rsa_key(1),
OTPCert = public_key:pkix_decode_cert(Cert, otp),
@@ -617,26 +653,35 @@ fake_root_no_intermediate_legacy(Config) when is_list(Config) ->
FakeCert = public_key:pkix_sign(TBS, FakeKey),
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => #{cert => FakeCert, key => FakeKey},
- intermediates => [],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
- test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+ #{server_config := FakeServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}
+ ],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}),
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf,
+ ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
fake_intermediate_cert() ->
[{doc,"Test that we can not use a fake intermediat cert claiming to be signed by a trusted ROOT but is not."}].
@@ -646,32 +691,48 @@ fake_intermediate_cert(Config) when is_list(Config) ->
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = #{cert := Cert,
key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {digest, sha256},
{extensions, Ext}]),
OtherSROOT = #{cert := OtherSCert,
- key := OtherSKey} = public_key:pkix_test_root_cert("OTHER SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(3)},
- {extensions, Ext}]),
+ key := OtherSKey} =
+ public_key:pkix_test_root_cert("OTHER SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256},
+ {extensions, Ext}]),
OtherCROOT = #{cert := OtherCCert,
- key := _OtherCKey} = public_key:pkix_test_root_cert("OTHER Client ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(1)},
- {extensions, Ext}]),
- #{client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := OtherServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => OtherSROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]},
- client_chain =>
- #{root => OtherCROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
+ key := _OtherCKey} =
+ public_key:pkix_test_root_cert("OTHER Client ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256},
+ {extensions, Ext}]),
+ #{client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
+
+ #{server_config := OtherServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => OtherSROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => OtherCROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
OTPCert = public_key:pkix_decode_cert(Cert, otp),
TBS = OTPCert#'OTPCertificate'.tbsCertificate,
TBSExt = TBS#'OTPTBSCertificate'.extensions,
@@ -711,32 +772,44 @@ incomplete_chain_length(Config) when is_list(Config)->
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {digest, sha256},
{extensions, Ext}]),
OtherROOT = public_key:pkix_test_root_cert("OTHER SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(3)},
{extensions, Ext}]),
- #{client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := ServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => OtherROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}],
- [{key, ssl_test_lib:hardcode_rsa_key(3)}]
- ],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]},
+ #{client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}, {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256} ],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
+
+ #{server_config := ServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => OtherROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256} ],
+ [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256} ]
+ ],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}]},
client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
VerifyFun = {fun(_,{bad_cert, unknown_ca}, UserState) ->
%% accept this error to provoke the
diff --git a/lib/ssl/test/ssl_bench_test_lib.erl b/lib/ssl/test/ssl_bench_test_lib.erl
index 648b42fb03..474a69fe17 100644
--- a/lib/ssl/test/ssl_bench_test_lib.erl
+++ b/lib/ssl/test/ssl_bench_test_lib.erl
@@ -71,43 +71,6 @@ find_executable(Prog) ->
P -> P
end.
--ifdef(undefined).
-setup(Name) ->
- Host = case os:getenv(?remote_host) of
- false ->
- {ok, This} = inet:gethostname(),
- This;
- RemHost ->
- RemHost
- end,
- Node = list_to_atom(atom_to_list(Name) ++ "@" ++ Host),
- SlaveArgs = case init:get_argument(pa) of
- {ok, PaPaths} ->
- lists:append([" -pa " ++ P || [P] <- PaPaths]);
- _ -> []
- end,
- %% ct:pal("Slave args: ~p~n",[SlaveArgs]),
- Prog =
- case os:find_executable("erl") of
- false -> "erl";
- P -> P
- end,
- ct:pal("Prog = ~p~n", [Prog]),
-
- case net_adm:ping(Node) of
- pong -> ok;
- pang ->
- {ok, Node} =
- slave:start(Host, Name, SlaveArgs, no_link, Prog)
- end,
- Path = code:get_path(),
- true = rpc:call(Node, code, set_path, [Path]),
- ok = rpc:call(Node, ?MODULE, setup_server, [node()]),
- ct:pal("Client (~p) using ~ts~n",[node(), code:which(ssl)]),
- (Node =:= node()) andalso restrict_schedulers(client),
- Node.
--endif.
-
setup_server(ClientNode) ->
(ClientNode =:= node()) andalso restrict_schedulers(server),
ct:pal("Server (~p) using ~ts~n",[node(), code:which(ssl)]),
diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl
index 315c0e20b1..28f8d06aa7 100644
--- a/lib/ssl/test/ssl_cert_SUITE.erl
+++ b/lib/ssl/test/ssl_cert_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include("ssl_record.hrl").
%% Common test
-export([all/0,
@@ -66,6 +67,8 @@
missing_root_cert_auth_user_verify_fun_accept/1,
missing_root_cert_auth_user_verify_fun_reject/0,
missing_root_cert_auth_user_verify_fun_reject/1,
+ missing_root_cert_auth_user_old_verify_fun_accept/0,
+ missing_root_cert_auth_user_old_verify_fun_accept/1,
verify_fun_always_run_client/0,
verify_fun_always_run_client/1,
verify_fun_always_run_server/0,
@@ -235,6 +238,7 @@ all_version_tests() ->
missing_root_cert_auth,
missing_root_cert_auth_user_verify_fun_accept,
missing_root_cert_auth_user_verify_fun_reject,
+ missing_root_cert_auth_user_old_verify_fun_accept,
verify_fun_always_run_client,
verify_fun_always_run_server,
incomplete_chain_auth,
@@ -270,11 +274,11 @@ init_per_group(GroupName, Config) ->
case ssl_test_lib:is_protocol_version(GroupName) of
true ->
ssl_test_lib:clean_start(),
- ssl_test_lib:init_per_group(GroupName,
+ ssl_test_lib:init_per_group(GroupName,
[{client_type, erlang},
{server_type, erlang},
{version, GroupName} | Config]);
- false ->
+ false ->
do_init_per_group(GroupName, Config)
end.
@@ -282,18 +286,22 @@ do_init_per_group(Group, Config0) when Group == rsa;
Group == rsa_1_3 ->
Config1 = ssl_test_lib:make_rsa_cert(Config0),
Config = ssl_test_lib:make_rsa_1024_cert(Config1),
- COpts = proplists:get_value(client_rsa_opts, Config),
+ COpts = proplists:get_value(client_rsa_verify_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
- [{cert_key_alg, rsa} |
- lists:delete(cert_key_alg,
- [{client_cert_opts, COpts},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ Version = proplists:get_value(version, Config),
+ [{cert_key_alg, rsa},
+ {extra_client, ssl_test_lib:sig_algs(rsa, Version)},
+ {extra_server, ssl_test_lib:sig_algs(rsa, Version)} |
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
do_init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
Alg == rsa_pss_pss ->
Supports = crypto:supports(),
RSAOpts = proplists:get_value(rsa_opts, Supports),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
andalso lists:member(rsa_pss_saltlen, RSAOpts)
@@ -302,8 +310,8 @@ do_init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
#{client_config := COpts,
server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(rsa_alg(Alg), [], Config, ""),
[{cert_key_alg, Alg},
- {extra_client, sig_algs(Alg)},
- {extra_server, sig_algs(Alg)} |
+ {extra_client, ssl_test_lib:sig_algs(Alg, Version)},
+ {extra_server, ssl_test_lib:sig_algs(Alg, Version)} |
lists:delete(cert_key_alg,
[{client_cert_opts, COpts},
{server_cert_opts, SOpts} |
@@ -340,13 +348,13 @@ do_init_per_group(Group, Config0) when Group == ecdsa;
case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
- COpts = proplists:get_value(client_ecdsa_opts, Config),
+ COpts = proplists:get_value(client_ecdsa_verify_opts, Config),
SOpts = proplists:get_value(server_ecdsa_opts, Config),
[{cert_key_alg, ecdsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, COpts},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))]
)];
false ->
@@ -377,18 +385,23 @@ do_init_per_group(eddsa_1_3, Config0) ->
false ->
{skip, "Missing EC crypto support"}
end;
-do_init_per_group(dsa, Config0) ->
+do_init_per_group(dsa = Alg, Config0) ->
PKAlg = crypto:supports(public_keys),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config0)),
case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of
true ->
Config = ssl_test_lib:make_dsa_cert(Config0),
COpts = proplists:get_value(client_dsa_opts, Config),
SOpts = proplists:get_value(server_dsa_opts, Config),
- [{cert_key_alg, dsa} |
+ [{cert_key_alg, dsa},
+ {extra_client, ssl_test_lib:sig_algs(Alg, Version) ++
+ [{ciphers, ssl_test_lib:dsa_suites(Version)}]},
+ {extra_server, ssl_test_lib:sig_algs(Alg, Version) ++
+ [{ciphers, ssl_test_lib:dsa_suites(Version)}]} |
lists:delete(cert_key_alg,
- [{client_cert_opts, COpts},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
false ->
{skip, "Missing DSS crypto support"}
@@ -400,11 +413,11 @@ end_per_group(GroupName, Config) ->
ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(signature_algorithms_bad_curve_secp256r1, Config) ->
- init_rsa_ecdsa_opts(Config, secp256r1);
+ init_ecdsa_opts(Config, secp256r1);
init_per_testcase(signature_algorithms_bad_curve_secp384r1, Config) ->
- init_rsa_ecdsa_opts(Config, secp384r1);
+ init_ecdsa_opts(Config, secp384r1);
init_per_testcase(signature_algorithms_bad_curve_secp521r1, Config) ->
- init_rsa_ecdsa_opts(Config, secp521r1);
+ init_ecdsa_opts(Config, secp521r1);
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 10}),
@@ -413,17 +426,18 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
-init_rsa_ecdsa_opts(Config0, Curve) ->
+init_ecdsa_opts(Config0, Curve) ->
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config0)),
PKAlg = crypto:supports(public_keys),
case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of
true ->
Config = ssl_test_lib:make_rsa_ecdsa_cert(Config0, Curve),
- COpts = proplists:get_value(client_rsa_ecdsa_opts, Config),
- SOpts = proplists:get_value(server_rsa_ecdsa_opts, Config),
+ COpts = proplists:get_value(client_ecdsa_verify_opts, Config),
+ SOpts = proplists:get_value(server_ecdsa_opts, Config),
[{cert_key_alg, ecdsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, COpts},
- {server_cert_opts, SOpts} |
+ [{client_cert_opts, ssl_test_lib:sig_algs(ecdsa, Version) ++ COpts},
+ {server_cert_opts, ssl_test_lib:sig_algs(ecdsa, Version) ++ SOpts} |
lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))]
)];
@@ -500,13 +514,15 @@ missing_root_cert_auth() ->
missing_root_cert_auth(Config) when is_list(Config) ->
ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)),
{ClientNode, ServerNode, _} = ssl_test_lib:run_where(Config),
- Version = proplists:get_value(version, Config),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
- {options, no_reuse(n_version(Version)) ++ [{verify, verify_peer}
+ {options, no_reuse(Version) ++ [{verify, verify_peer}
| ServerOpts]}]),
- ssl_test_lib:check_result(Server, {error, {options, {cacertfile, ""}}}),
+ Error = {error, {options, incompatible,
+ [{verify,verify_peer},{cacerts,undefined}]}},
+ ssl_test_lib:check_result(Server, Error),
ClientOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config)),
Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0},
@@ -514,7 +530,7 @@ missing_root_cert_auth(Config) when is_list(Config) ->
{options, [{verify, verify_peer}
| ClientOpts]}]),
- ssl_test_lib:check_result(Client, {error, {options, {cacertfile, ""}}}).
+ ssl_test_lib:check_result(Client, Error).
%%--------------------------------------------------------------------
missing_root_cert_auth_user_verify_fun_accept() ->
@@ -523,6 +539,7 @@ missing_root_cert_auth_user_verify_fun_accept() ->
missing_root_cert_auth_user_verify_fun_accept(Config) ->
ServerOpts = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config),
+ ClientCaCerts = public_key:cacerts_get(),
FunAndState = {fun(_,{bad_cert, unknown_ca}, UserState) ->
{valid, UserState};
(_,{bad_cert, _} = Reason, _) ->
@@ -534,16 +551,19 @@ missing_root_cert_auth_user_verify_fun_accept(Config) ->
(_, valid_peer, UserState) ->
{valid, UserState}
end, []},
- ClientOpts = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer},
- {verify_fun, FunAndState}], Config),
+ ClientOpts = ssl_test_lib:ssl_options(extra_client,
+ [{verify, verify_peer}, {verify_fun, FunAndState},
+ {cacerts, ClientCaCerts}],
+ Config),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
-missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept() ->
+missing_root_cert_auth_user_old_verify_fun_accept() ->
[{doc, "Test old style verify fun"}].
-missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept(Config) ->
+missing_root_cert_auth_user_old_verify_fun_accept(Config) ->
ServerOpts = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config),
+ ClientCaCerts = public_key:cacerts_get(),
AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc;
(Other, Acc) -> [Other | Acc]
end,
@@ -554,8 +574,10 @@ missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept(Config) ->
[_|_] -> false
end
end,
- ClientOpts = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer},
- {verify_fun, VerifyFun}], Config),
+ ClientOpts = ssl_test_lib:ssl_options(extra_client,
+ [{verify, verify_peer},
+ {verify_fun, VerifyFun},
+ {cacerts, ClientCaCerts}], Config),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
@@ -565,6 +587,7 @@ missing_root_cert_auth_user_verify_fun_reject() ->
missing_root_cert_auth_user_verify_fun_reject(Config) ->
ServerOpts = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config),
+ ClientCaCerts = public_key:cacerts_get(),
FunAndState = {fun(_,{bad_cert, unknown_ca} = Reason, _UserState) ->
{fail, Reason};
(_,{bad_cert, _} = Reason, _) ->
@@ -576,8 +599,11 @@ missing_root_cert_auth_user_verify_fun_reject(Config) ->
(_, valid_peer, UserState) ->
{valid, UserState}
end, []},
- ClientOpts = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer},
- {verify_fun, FunAndState}], Config),
+ ClientOpts = ssl_test_lib:ssl_options(extra_client,
+ [{verify, verify_peer},
+ {verify_fun, FunAndState},
+ {cacerts, ClientCaCerts}],
+ Config),
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca).
@@ -641,7 +667,7 @@ verify_fun_always_run_client(Config) when is_list(Config) ->
{from, self()},
{mfa, {ssl_test_lib,
no_result, []}},
- {options, no_reuse(n_version(Version)) ++ ServerOpts}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
%% If user verify fun is called correctly we fail the connection.
@@ -705,7 +731,7 @@ verify_fun_always_run_server(Config) when is_list(Config) ->
{mfa, {ssl_test_lib,
no_result, []}},
{options,
- no_reuse(n_version(Version)) ++ [{verify, verify_peer},
+ no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_peer},
{verify_fun, FunAndState} |
ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
@@ -754,7 +780,7 @@ critical_extension_auth(Config) when is_list(Config) ->
[{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
- {options, no_reuse(n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client_error(
[{node, ClientNode}, {port, Port},
@@ -786,7 +812,7 @@ critical_extension_client_auth(Config) when is_list(Config) ->
[{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
- {options, no_reuse(n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client_error(
[{node, ClientNode}, {port, Port},
@@ -841,7 +867,7 @@ extended_key_usage_auth(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, no_reuse(n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -876,7 +902,7 @@ extended_key_usage_client_auth(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, no_reuse(n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -913,7 +939,7 @@ cert_expired(Config) when is_list(Config) ->
Version = proplists:get_value(version, Config),
Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
- {options, no_reuse(n_version(Version)) ++ ServerOpts}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -975,34 +1001,35 @@ key_auth_ext_sign_only(Config) when is_list(Config) ->
Version = proplists:get_value(version, Config),
ClientOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config)],
ServerOpts = [{verify, verify_peer}, {ciphers,
- ssl_test_lib:rsa_non_signed_suites(n_version(Version))}
- | ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config)],
+ ssl_test_lib:rsa_non_signed_suites(ssl_test_lib:n_version(Version))}
+ | ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
longer_chain() ->
[{doc,"Test depth option"}].
-longer_chain(Config) when is_list(Config) ->
+longer_chain(Config) when is_list(Config) ->
#{server_config := ServerOpts0,
- client_config := ClientOpts0} =
+ client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}],
[{key, ssl_test_lib:hardcode_rsa_key(3)}],
[{key, ssl_test_lib:hardcode_rsa_key(4)}]],
peer => [{key, ssl_test_lib:hardcode_rsa_key(5)}]},
- client_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(3)}],
+ client_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(3)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]}}),
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]}}),
[ServerRoot| _] = ServerCas = proplists:get_value(cacerts, ServerOpts0),
ClientCas = proplists:get_value(cacerts, ClientOpts0),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
ServerOpts = ssl_test_lib:ssl_options(extra_server, [{verify, verify_peer}, {cacerts, [ServerRoot]} |
- proplists:delete(cacerts, ServerOpts0)], Config),
+ proplists:delete(cacerts, ServerOpts0)] ++ ssl_test_lib:sig_algs(rsa, Version), Config),
ClientOpts = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer},
{depth, 5},
- {cacerts, ServerCas ++ ClientCas} |
- proplists:delete(cacerts, ClientOpts0)], Config),
+ {cacerts, ServerCas ++ ClientCas} |
+ proplists:delete(cacerts, ClientOpts0)]++ ssl_test_lib:sig_algs(rsa, Version) , Config),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
cross_signed_chain() ->
@@ -1031,26 +1058,28 @@ cross_signed_chain(Config)
ServerCas0 = proplists:get_value(cacerts, ServerOpts0),
ClientCas0 = proplists:get_value(cacerts, ClientOpts0),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
{[Peer,CI1,CI2,CROld], CROld} = chain_and_root(ClientOpts0),
{[_Peer,CI1New,CI2New,CRNew], CRNew} = chain_and_root(ClientOptsNew),
ServerCas = [CRNew|ServerCas0 -- [CROld]],
ServerOpts = ssl_test_lib:ssl_options(extra_server, [{verify, verify_peer} |
- lists:keyreplace(cacerts, 1, ServerOpts0, {cacerts, ServerCas})],
+ lists:keyreplace(cacerts, 1, ServerOpts0, {cacerts, ServerCas})]
+ ++ ssl_test_lib:sig_algs(rsa, Version),
Config),
ClientOpts = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer} |
lists:keyreplace(cacerts, 1,
lists:keyreplace(cert, 1, ClientOpts0,
{cert, [Peer,CI1New,CI2New,CI1,CI2,CRNew,CROld]}),
- {cacerts, ClientCas0})],
+ {cacerts, ClientCas0})] ++ ssl_test_lib:sig_algs(rsa, Version),
Config),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config),
ClientOpts2 = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer} |
lists:keyreplace(cacerts, 1,
lists:keyreplace(cert, 1, ClientOpts0,
{cert, [Peer,CI1,CI1New,CI2,CI2New,CROld,CRNew]}),
- {cacerts, ClientCas0})],
+ {cacerts, ClientCas0})] ++ ssl_test_lib:sig_algs(rsa, Version),
Config),
ssl_test_lib:basic_test(ClientOpts2, ServerOpts, Config),
ok.
@@ -1072,12 +1101,15 @@ expired_root_with_cross_signed_root(Config) when is_list(Config) ->
#{cert := Root} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", [{key, Key1},
{validity, {{Year-2, Month, Day},
{Year-1, Month, Day}}}]),
- #{server_config := ServerOpts, client_config := ClientOpts} =
+ #{server_config := ServerOpts0, client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain => #{root => SRoot,
intermediates => [[{key, Key2}], [{key, Key3}]],
peer => [{key, Key4}]},
client_chain => #{root => [{key, Key5}],
peer => [{key, Key6}]}}),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ClientOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
+ ServerOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
SCert = proplists:get_value(cert, ServerOpts),
SCerts = proplists:get_value(cacerts, ServerOpts),
@@ -1190,7 +1222,7 @@ hello_retry_client_auth(Config) ->
{supported_groups, [secp256r1, x25519]}|ClientOpts0],
ServerOpts = [{verify, verify_peer},
{fail_if_no_peer_cert, true} | ServerOpts1],
-
+
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
hello_retry_client_auth_empty_cert_accepted() ->
@@ -1277,12 +1309,14 @@ signature_algorithms_bad_curve_secp521r1(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
%% Set versions
- ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0],
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {signature_algs, [ecdsa_secp512r1_sha256,
+ {sha256,rsa}]}
+ | ServerOpts0],
ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']},
{signature_algs, [ecdsa_secp256r1_sha256,
ecdsa_secp384r1_sha384,
{sha256,rsa}]}|ClientOpts0],
-
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security).
%%--------------------------------------------------------------------
@@ -1337,17 +1371,6 @@ server_certificate_authorities_disabled(Config) ->
%%--------------------------------------------------------------------
%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
-n_version(Version) when
- Version == 'tlsv1.3';
- Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == 'tlsv1';
- Version == 'sslv3' ->
- tls_record:protocol_version(Version);
-n_version(Version) when Version == 'dtlsv1.2';
- Version == 'dtlsv1' ->
- dtls_record:protocol_version(Version).
-
rsa_alg(rsa_pss_rsae_1_3) ->
rsa_pss_rsae;
rsa_alg(rsa_pss_pss_1_3) ->
@@ -1355,7 +1378,7 @@ rsa_alg(rsa_pss_pss_1_3) ->
rsa_alg(Atom) ->
Atom.
-no_reuse({3, N}) when N >= 4 ->
+no_reuse(?TLS_1_3) ->
[];
no_reuse(_) ->
[{reuse_sessions, false}].
@@ -1366,11 +1389,3 @@ chain_and_root(Config) ->
{ok, Root, Chain} = ssl_certificate:certificate_chain(OwnCert, ets:new(foo, []), ExtractedCAs, [], encoded),
{Chain, Root}.
-sig_algs(rsa_pss_pss) ->
- [{signature_algs, [rsa_pss_pss_sha512,
- rsa_pss_pss_sha384,
- rsa_pss_pss_sha256]}];
-sig_algs(rsa_pss_rsae) ->
- [{signature_algs, [rsa_pss_rsae_sha512,
- rsa_pss_rsae_sha384,
- rsa_pss_rsae_sha256]}].
diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl
index b6fb9f4724..a551025ea5 100644
--- a/lib/ssl/test/ssl_cert_tests.erl
+++ b/lib/ssl/test/ssl_cert_tests.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -102,9 +102,10 @@ client_auth_empty_cert_accepted() ->
[{doc,"Client sends empty cert chain as no cert is configured and server allows it"}].
client_auth_empty_cert_accepted(Config) ->
- ClientOpts = proplists:delete(keyfile,
- proplists:delete(certfile,
- ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config))),
+ ClientOpts = [{verify, verify_peer} |
+ proplists:delete(keyfile,
+ proplists:delete(certfile,
+ ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config)))],
ServerOpts0 = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config),
ServerOpts = [{verify, verify_peer},
{fail_if_no_peer_cert, false} | ServerOpts0],
@@ -115,8 +116,8 @@ client_auth_empty_cert_rejected() ->
client_auth_empty_cert_rejected(Config) ->
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}
- | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)],
- ClientOpts0 = ssl_test_lib:ssl_options(extra_client, [], Config),
+ | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)],
+ ClientOpts0 = [{verify, verify_none} | ssl_test_lib:ssl_options(extra_client, [], Config)],
%% Delete Client Cert and Key
ClientOpts1 = proplists:delete(certfile, ClientOpts0),
ClientOpts = proplists:delete(keyfile, ClientOpts1),
@@ -140,11 +141,11 @@ client_auth_no_suitable_chain(Config) when is_list(Config) ->
client_chain => #{root => CRoot,
intermediates => [[]],
peer => []}}),
- ClientOpts = ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config),
+ ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config)],
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}
- | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)],
+ | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)],
Version = proplists:get_value(version, Config),
-
+
case Version of
'tlsv1.3' ->
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required);
@@ -274,10 +275,14 @@ client_auth_seelfsigned_peer(Config) when is_list(Config) ->
#{cert := Cert,
key := Key} = public_key:pkix_test_root_cert("OTP test server ROOT", [{key, ssl_test_lib:hardcode_rsa_key(6)},
{extensions, Ext}]),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
DerKey = public_key:der_encode('RSAPrivateKey', Key),
- ssl_test_lib:basic_alert(ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer}, {cacerts , [Cert]}], Config),
+ ssl_test_lib:basic_alert(ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer}, {cacerts , [Cert]}] ++
+ ssl_test_lib:sig_algs(rsa, Version), Config),
ssl_test_lib:ssl_options(extra_server, [{cert, Cert},
- {key, {'RSAPrivateKey', DerKey}}], Config), Config, bad_certificate).
+ {key, {'RSAPrivateKey', DerKey}}] ++
+ ssl_test_lib:sig_algs(rsa, Version), Config),
+ Config, bad_certificate).
%%--------------------------------------------------------------------
missing_root_cert_no_auth() ->
[{doc,"Test that the client succeeds if the ROOT CA is unknown in verify_none mode"}].
@@ -285,12 +290,12 @@ missing_root_cert_no_auth() ->
missing_root_cert_no_auth(Config) ->
ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config)],
ServerOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)],
-
+
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
invalid_signature_client() ->
- [{doc,"Test server with invalid signature"}].
+ [{doc,"Test that server detects invalid client signature"}].
invalid_signature_client(Config) when is_list(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config),
@@ -314,7 +319,7 @@ invalid_signature_client(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
invalid_signature_server() ->
- [{doc,"Test client with invalid signature"}].
+ [{doc,"Test that client detects invalid server signature"}].
invalid_signature_server(Config) when is_list(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config),
@@ -462,8 +467,8 @@ test_ciphers(_, 'tlsv1.3' = Version) ->
end, Ciphers);
test_ciphers(_, Version) when Version == 'dtlsv1';
Version == 'dtlsv1.2' ->
- {_, Minor} = dtls_record:protocol_version(Version),
- Ciphers = [ssl_cipher_format:suite_bin_to_map(Bin) || Bin <- dtls_v1:suites(Minor)],
+ NVersion = dtls_record:protocol_version_name(Version),
+ Ciphers = [ssl_cipher_format:suite_bin_to_map(Bin) || Bin <- dtls_v1:suites(NVersion)],
ct:log("Version ~p Testing ~p~n", [Version, Ciphers]),
OpenSSLCiphers = openssl_ciphers(),
ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]),
diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl
index 442cc5f790..687bbd6f58 100644
--- a/lib/ssl/test/ssl_cipher_SUITE.erl
+++ b/lib/ssl/test/ssl_cipher_SUITE.erl
@@ -25,6 +25,7 @@
-include_lib("common_test/include/ct.hrl").
-include("tls_record.hrl").
-include("ssl_cipher.hrl").
+-include("ssl_record.hrl").
%% Callback functions
-export([all/0,
@@ -97,10 +98,9 @@ aes_decipher_good() ->
aes_decipher_good(Config) when is_list(Config) ->
HashSz = 32,
CipherState = correct_cipher_state(),
- decipher_check_good(HashSz, CipherState, {3,0}),
- decipher_check_good(HashSz, CipherState, {3,1}),
- decipher_check_good(HashSz, CipherState, {3,2}),
- decipher_check_good(HashSz, CipherState, {3,3}).
+ decipher_check_good(HashSz, CipherState, ?TLS_1_0),
+ decipher_check_good(HashSz, CipherState, ?TLS_1_1),
+ decipher_check_good(HashSz, CipherState, ?TLS_1_2).
%%--------------------------------------------------------------------
aes_decipher_fail() ->
@@ -109,19 +109,17 @@ aes_decipher_fail() ->
aes_decipher_fail(Config) when is_list(Config) ->
HashSz = 32,
CipherState = incorrect_cipher_state(),
- decipher_check_fail(HashSz, CipherState, {3,0}),
- decipher_check_fail(HashSz, CipherState, {3,1}),
- decipher_check_fail(HashSz, CipherState, {3,2}),
- decipher_check_fail(HashSz, CipherState, {3,3}).
+ decipher_check_fail(HashSz, CipherState, ?TLS_1_0),
+ decipher_check_fail(HashSz, CipherState, ?TLS_1_1),
+ decipher_check_fail(HashSz, CipherState, ?TLS_1_2).
%%--------------------------------------------------------------------
padding_test(Config) when is_list(Config) ->
HashSz = 16,
CipherState = correct_cipher_state(),
- pad_test(HashSz, CipherState, {3,0}),
- pad_test(HashSz, CipherState, {3,1}),
- pad_test(HashSz, CipherState, {3,2}),
- pad_test(HashSz, CipherState, {3,3}).
+ pad_test(HashSz, CipherState, ?TLS_1_0),
+ pad_test(HashSz, CipherState, ?TLS_1_1),
+ pad_test(HashSz, CipherState, ?TLS_1_2).
%%--------------------------------------------------------------------
sign_algorithms(Config) when is_list(Config) ->
@@ -140,21 +138,14 @@ decipher_check_fail(HashSz, CipherState, Version) ->
true = {Content, Mac, #cipher_state{iv = NextIV}} =/=
ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, aes_fragment(Version), Version, true).
-pad_test(HashSz, CipherState, {3,0} = Version) ->
- %% 3.0 does not have padding test
- {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version),
- {Content, Mac, #cipher_state{iv = NextIV}} =
- ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, true),
- {Content, Mac, #cipher_state{iv = NextIV}} =
- ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, false);
-pad_test(HashSz, CipherState, {3,1} = Version) ->
+pad_test(HashSz, CipherState, ?TLS_1_0 = Version) ->
%% 3.1 should have padding test, but may be disabled
{Content, NextIV, Mac} = badpad_content_nextiv_mac(Version),
BadCont = badpad_content(Content),
{Content, Mac, #cipher_state{iv = NextIV}} =
- ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,1}) , {3,1}, false),
+ ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment(?TLS_1_0) , ?TLS_1_0, false),
{BadCont, Mac, #cipher_state{iv = NextIV}} =
- ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,1}), {3,1}, true);
+ ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment(?TLS_1_0), ?TLS_1_0, true);
pad_test(HashSz, CipherState, Version) ->
%% 3.2 and 3.3 must have padding test
{Content, NextIV, Mac} = badpad_content_nextiv_mac(Version),
@@ -164,7 +155,7 @@ pad_test(HashSz, CipherState, Version) ->
{BadCont, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES_CBC, HashSz, CipherState,
badpad_aes_fragment(Version), Version, true).
-aes_fragment({3,N}) when N == 0; N == 1->
+aes_fragment(?TLS_1_0) ->
<<197,9,6,109,242,87,80,154,85,250,110,81,119,95,65,185,53,206,216,153,246,169,
119,177,178,238,248,174,253,220,242,81,33,0,177,251,91,44,247,53,183,198,165,
63,20,194,159,107>>;
@@ -175,7 +166,7 @@ aes_fragment(_) ->
198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122,
108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>.
-badpad_aes_fragment({3,N}) when N == 0; N == 1 ->
+badpad_aes_fragment(?TLS_1_0) ->
<<186,139,125,10,118,21,26,248,120,108,193,104,87,118,145,79,225,55,228,10,105,
30,190,37,1,88,139,243,210,99,65,41>>;
badpad_aes_fragment(_) ->
@@ -183,7 +174,7 @@ badpad_aes_fragment(_) ->
94,121,137,117,157,109,99,113,61,190,138,131,229,201,120,142,179,172,48,77,
234,19,240,33,38,91,93>>.
-content_nextiv_mac({3,N}) when N == 0; N == 1 ->
+content_nextiv_mac(?TLS_1_0) ->
{<<"HELLO\n">>,
<<72,196,247,97,62,213,222,109,210,204,217,186,172,184, 197,148>>,
<<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>};
@@ -192,7 +183,7 @@ content_nextiv_mac(_) ->
<<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>>,
<<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>}.
-badpad_content_nextiv_mac({3,N}) when N == 0; N == 1 ->
+badpad_content_nextiv_mac(?TLS_1_0) ->
{<<"HELLO\n">>,
<<225,55,228,10,105,30,190,37,1,88,139,243,210,99,65,41>>,
<<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>>
diff --git a/lib/ssl/test/ssl_cipher_suite_SUITE.erl b/lib/ssl/test/ssl_cipher_suite_SUITE.erl
index 0ddbf59e56..04425bbb1e 100644
--- a/lib/ssl/test/ssl_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/ssl_cipher_suite_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -530,57 +530,75 @@ end_per_testcase(_TestCase, Config) ->
init_certs(srp_rsa, Config) ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0}
= public_key:pkix_test_data(CertChainConf),
- [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}} | ServerOpts],
- client_config => [{srp_identity, {"Test-User", "secret"}} | ClientOpts]}} |
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
+ [{tls_config, #{server_config =>
+ [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}} | ServerOpts],
+ client_config =>
+ [{srp_identity, {"Test-User", "secret"}}, {verify, verify_none} | ClientOpts]}} |
proplists:delete(tls_config, Config)];
init_certs(srp_anon, Config) ->
[{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}],
- client_config => [{srp_identity, {"Test-User", "secret"}}]}} |
+ client_config => [{srp_identity, {"Test-User", "secret"}}, {verify, verify_none}]}} |
proplists:delete(tls_config, Config)];
init_certs(rsa_psk, Config) ->
ClientExt = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]),
- {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
- [[],[],[{extensions, ClientExt}]]}],
+ {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
+ [[],[],[{extensions, ClientExt}]]}],
Config, "_peer_keyEncipherment"),
PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>,
[{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}} | ServerOpts],
client_config => [{psk_identity, "Test-User"},
- {user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}} | ClientOpts]}} |
+ {user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}},
+ {verify, verify_none} | ClientOpts]}} |
proplists:delete(tls_config, Config)];
init_certs(rsa, Config) ->
ClientExt = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]),
- {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
- [[],[],[{extensions, ClientExt}]]}],
+ {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
+ [[],[],[{extensions, ClientExt}]]}],
Config, "_peer_keyEncipherment"),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
init_certs(ecdhe_1_3_rsa_cert, Config) ->
ClientExt = x509_test:extensions([{key_usage, [digitalSignature]}]),
- {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
- [[],[],[{extensions, ClientExt}]]}],
+ {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
+ [[],[],[{extensions, ClientExt}]]}],
Config, "_peer_rsa_digitalsign"),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
init_certs(dhe_dss, Config) ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
- CertChainConf = ssl_test_lib:gen_conf(dsa, dsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
+ CertChainConf = ssl_test_lib:gen_conf(dsa, dsa, DefConf, DefConf),
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0}
= public_key:pkix_test_data(CertChainConf),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(dsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(dsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
init_certs(srp_dss, Config) ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
CertChainConf = ssl_test_lib:gen_conf(dsa, dsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0}
= public_key:pkix_test_data(CertChainConf),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(dsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(dsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}} | ServerOpts],
client_config => [{srp_identity, {"Test-User", "secret"}} | ClientOpts]}} |
proplists:delete(tls_config, Config)];
@@ -588,9 +606,12 @@ init_certs(GroupName, Config) when GroupName == dhe_rsa;
GroupName == ecdhe_rsa ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0}
= public_key:pkix_test_data(CertChainConf),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
@@ -598,9 +619,12 @@ init_certs(GroupName, Config) when GroupName == dhe_ecdsa;
GroupName == ecdhe_ecdsa ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
CertChainConf = ssl_test_lib:gen_conf(ecdsa, ecdsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0}
= public_key:pkix_test_data(CertChainConf),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(ecdsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(ecdsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
@@ -609,17 +633,17 @@ init_certs(GroupName, Config) when GroupName == psk;
GroupName == ecdhe_psk ->
PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>,
[{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}}],
- client_config => [{psk_identity, "Test-User"},
+ client_config => [{verify, verify_none}, {psk_identity, "Test-User"},
{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}}]}} |
- proplists:delete(tls_config, Config)];
-init_certs(srp, Config) ->
+ proplists:delete(tls_config, Config)];
+init_certs(srp, Config) ->
[{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}],
- client_config => [{srp_identity, {"Test-User", "secret"}}]}} |
+ client_config => [{verify, verify_none},{srp_identity, {"Test-User", "secret"}}]}} |
proplists:delete(tls_config, Config)];
-init_certs(_GroupName, Config) ->
+init_certs(_GroupName, Config) ->
%% Anonymous does not need certs
- [{tls_config, #{server_config => [],
- client_config => []}} |
+ [{tls_config, #{server_config => [{verify, verify_none}],
+ client_config => [{verify, verify_none}]}} |
proplists:delete(tls_config, Config)].
%%--------------------------------------------------------------------
@@ -978,14 +1002,14 @@ cipher_suite_test(ErlangCipherSuite, Version, Config) ->
test_ciphers(Kex, Cipher, Version) ->
- ssl:filter_cipher_suites(ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version),
- [{key_exchange,
- fun(Kex0) when Kex0 == Kex -> true;
- (_) -> false
- end},
- {cipher,
- fun(Cipher0) when Cipher0 == Cipher -> true;
- (_) -> false
+ ssl:filter_cipher_suites(ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version),
+ [{key_exchange,
+ fun(Kex0) when Kex0 == Kex -> true;
+ (_) -> false
+ end},
+ {cipher,
+ fun(Cipher0) when Cipher0 == Cipher -> true;
+ (_) -> false
end}]).
diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl
index c6154855e5..0194ff4dce 100644
--- a/lib/ssl/test/ssl_dist_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
%%
-module(ssl_dist_SUITE).
+-feature(maybe_expr, enable).
-behaviour(ct_suite).
@@ -38,6 +39,12 @@
%% Test cases
-export([basic/0,
basic/1,
+ embedded/0,
+ embedded/1,
+ ktls_encrypt_decrypt/0,
+ ktls_encrypt_decrypt/1,
+ ktls_verify/0,
+ ktls_verify/1,
monitor_nodes/1,
payload/0,
payload/1,
@@ -107,6 +114,9 @@ start_ssl_node_name(Name, Args) ->
%%--------------------------------------------------------------------
all() ->
[basic,
+ embedded,
+ ktls_encrypt_decrypt,
+ ktls_verify,
monitor_nodes,
payload,
dist_port_overload,
@@ -143,31 +153,46 @@ init_per_suite(Config0) ->
end_per_suite(_Config) ->
application:stop(crypto).
-init_per_testcase(plain_verify_options = Case, Config) when is_list(Config) ->
- SslFlags = setup_tls_opts(Config),
- Flags = case os:getenv("ERL_FLAGS") of
- false ->
- os:putenv("ERL_FLAGS", SslFlags),
- "";
- OldFlags ->
- os:putenv("ERL_FLAGS", OldFlags ++ " " ++ SslFlags),
- OldFlags
- end,
- common_init(Case, [{old_flags, Flags} | Config]);
-init_per_testcase(Case, Config) when is_list(Config) ->
+init_per_testcase(Case, Config) ->
+ try init_per_tc(Case, Config)
+ catch
+ Class : Reason : Stacktrace ->
+ {fail, {Class, Reason, Stacktrace}}
+ end.
+
+init_per_tc(embedded, Config) ->
+ LibDir = code:lib_dir(),
+ case
+ lists:all(
+ fun ({App,_,VSN}) ->
+ filelib:is_dir(
+ filename:join(
+ LibDir, atom_to_list(App)++"-"++VSN))
+ end, application_controller:which_applications())
+ of
+ false ->
+ {skip, "Must be run from a real Erlang installation"};
+ true ->
+ Config
+ end;
+init_per_tc(Case, Config)
+ when Case =:= ktls_verify, is_list(Config) ->
+ case ktls_encrypt_decrypt(false) of
+ ok ->
+ common_init(Case, Config);
+ Skip ->
+ Skip
+ end;
+%%
+init_per_tc(Case, Config) when is_list(Config) ->
common_init(Case, Config).
common_init(Case, Config) ->
ct:timetrap({seconds, ?DEFAULT_TIMETRAP_SECS}),
[{testcase, Case}|Config].
-end_per_testcase(Case, Config) when is_list(Config) ->
- Flags = proplists:get_value(old_flags, Config),
- catch os:putenv("ERL_FLAGS", Flags),
- common_end(Case, Config).
-
-common_end(_, _Config) ->
+end_per_testcase(_, _Config) ->
ok.
%%--------------------------------------------------------------------
@@ -179,6 +204,183 @@ basic() ->
basic(Config) when is_list(Config) ->
gen_dist_test(basic_test, Config).
+embedded() ->
+ [{doc,"Test that two nodes can connect via ssl distribution in embedded mode"}].
+embedded(Config) when is_list(Config) ->
+ ReleaseDir = filename:join(proplists:get_value(priv_dir,Config), "embedded"),
+ EbinDir = filename:join(ReleaseDir,"ebin/"),
+
+ %% Create an application for the test modules
+ Modules = [ssl_dist_test_lib, ?MODULE],
+ App = {application, tls_test,
+ [{description, "Erlang/OTP SSL test application"},
+ {vsn, "1.0"},
+ {modules, Modules},
+ {registered,[]},
+ {applications, [kernel, stdlib]}]},
+ ok = filelib:ensure_path(EbinDir),
+ [{ok,_} = file:copy(code:which(Mod), filename:join(EbinDir, atom_to_list(Mod)++".beam"))
+ || Mod <- Modules],
+ ok = file:write_file(filename:join(EbinDir,"tls_test.app"),
+ io_lib:format("~p.",[App])),
+
+ %% Create a release that we can boot from
+ Rel = {release, {"tls","1.0"}, {erts, get_app_vsn(erts)},
+ [{tls_test, "1.0"},
+ {kernel, get_app_vsn(kernel)},
+ {stdlib, get_app_vsn(stdlib)},
+ {public_key, get_app_vsn(public_key)},
+ {asn1, get_app_vsn(asn1)},
+ {sasl, get_app_vsn(sasl)},
+ {crypto, get_app_vsn(crypto)},
+ {ssl, get_app_vsn(ssl)}]},
+ TlsRel = filename:join(ReleaseDir, "tls"),
+ ok = file:write_file(TlsRel ++ ".rel", io_lib:format("~p.",[Rel])),
+ code:add_patha(EbinDir),
+ ok = systools:make_script(TlsRel),
+ ok = systools:script2boot(TlsRel),
+
+ %% Start two nodes in embedded mode and make sure they can connect
+ %% There used to be a bug here where crypto was not loaded early enough
+ %% for the distributed connection to work.
+ NodeConfig = [{app_opts, "-boot "++TlsRel++" -mode embedded -pa "++EbinDir++" "} | Config],
+ Node1 = peer:random_name(),
+ Node2 = peer:random_name(),
+
+ NH1 = start_ssl_node([{node_name,Node1}|NodeConfig]),
+ %% The second node does `sync_nodes_mandatory` with the first in order for
+ %% a connection to be established very early in the boot sequence
+ ok = file:write_file(
+ filename:join(ReleaseDir,"node2.config"),
+ io_lib:format(
+ "~p.",[[{kernel,
+ [{sync_nodes_timeout,infinity},
+ {sync_nodes_mandatory,
+ [list_to_atom(Node1++"@"++inet_db:gethostname())]}]}]])),
+ NH2 = start_ssl_node([{node_name,Node2}|NodeConfig],
+ " -config " ++ filename:join(ReleaseDir,"node2")),
+
+ try
+ basic_test(NH1, NH2, Config)
+ catch
+ _:Reason ->
+ stop_ssl_node(NH1),
+ stop_ssl_node(NH2),
+ ct:fail(Reason)
+ end,
+ stop_ssl_node(NH1),
+ stop_ssl_node(NH2),
+ success(Config).
+
+%%--------------------------------------------------------------------
+ktls_encrypt_decrypt() ->
+ [{doc,"Test that kTLS encryption offloading works"}].
+ktls_encrypt_decrypt(Config) when is_list(Config) ->
+ ktls_encrypt_decrypt(true);
+%%
+%% ktls_encrypt_decrypt(false) is used by init_per_tc(ktls_verify, _)
+%%
+ktls_encrypt_decrypt(Test) when is_boolean(Test) ->
+ %%
+ %% We need a connected socket
+ {ok, Listen} = gen_tcp:listen(0, [{active, false}]),
+ {ok, Port} = inet:port(Listen),
+ {ok, Client} =
+ gen_tcp:connect({127,0,0,1}, Port, [{active, false}]),
+ {ok, Server} = gen_tcp:accept(Listen),
+ try
+ maybe
+ {ok, OS} ?= ssl_test_lib:ktls_os(),
+ ok ?= ssl_test_lib:ktls_set_ulp(Client, OS),
+ ok ?= ssl_test_lib:ktls_set_cipher(Client, OS, tx, 11),
+ case Test of
+ false ->
+ ok;
+ true ->
+ ktls_encrypt_decrypt(Client, Server)
+ end
+ else
+ {error, Reason} ->
+ {skip, Reason}
+ end
+ after
+ _ = gen_tcp:close(Server),
+ _ = gen_tcp:close(Client),
+ _ = gen_tcp:close(Listen)
+ end.
+
+ktls_encrypt_decrypt(Client, Server) ->
+ %%
+ %% Test to transfer encrypted data,
+ %% and also to not activate RX encryption and transfer data.
+ %%
+ Data = "The quick brown fox jumps over a lazy dog 0123456789",
+ %%
+ %% Send encrypted from Client before Server has activated decryption
+ ok = gen_tcp:send(Client, Data),
+ receive after 500 -> ok end, % Give time for data to arrive
+ %%
+ %% Activate Server TX encryption
+ {ok, OS} = ssl_test_lib:ktls_os(),
+ ok = ssl_test_lib:ktls_set_ulp(Server, OS),
+ ok = ssl_test_lib:ktls_set_cipher(Server, OS, tx, 17),
+ %% Send encrypted from Server
+ ok = gen_tcp:send(Server, Data),
+ %% Receive encrypted data without decryption
+ case gen_tcp:recv(Client, 0, 1000) of
+ {ok, Data} ->
+ ct:fail(recv_cleartext_data);
+ {ok, RandomData} when length(Data) < length(RandomData) ->
+ ct:log("Received ~p", [RandomData]),
+ %% A TLS block should be longer than Data
+ ok
+ end,
+ %% Finally, activate Server decryption
+ ok = ssl_test_lib:ktls_set_cipher(Server, OS, rx, 11),
+ %% Receive and decrypt the data that was first sent
+ {ok, Data} = gen_tcp:recv(Server, 0, 1000),
+ ok.
+
+%%--------------------------------------------------------------------
+ktls_verify() ->
+ [{doc,
+ "Test that two nodes can connect via ssl distribution over kTLS"}].
+ktls_verify(Config) ->
+ KTLSOpts = "-ssl_dist_opt "
+ "client_versions tlsv1.3 "
+ "server_versions tlsv1.3 "
+ "client_ciphers TLS_AES_256_GCM_SHA384 "
+ "server_ciphers TLS_AES_256_GCM_SHA384 "
+ "client_ktls true "
+ "server_ktls true ",
+ KTLSConfig = [{tls_verify_opts, KTLSOpts} | Config],
+ gen_dist_test(
+ fun (NH1, NH2) ->
+ basic_test(NH1, NH2, KTLSConfig),
+ 0 = ktls_count_tls_dist(NH1),
+ 0 = ktls_count_tls_dist(NH2),
+ ok
+ end, KTLSConfig).
+
+%% Verify that kTLS was activated (whitebox verification);
+%% check that a specific supervisor has no child supervisor
+%% which indicates that ssl_gen_statem:ktls_handover/1 has succeeded
+%%
+ktls_count_tls_dist(Node) ->
+ Key = supervisors,
+ case
+ lists:keyfind(
+ Key, 1,
+ apply_on_ssl_node(
+ Node, supervisor, count_children,
+ [tls_dist_connection_sup]))
+ of
+ {Key, N} ->
+ N;
+ false ->
+ 0
+ end.
+
%%--------------------------------------------------------------------
%% Test net_kernel:monitor_nodes with nodedown_reason (OTP-17838)
monitor_nodes(Config) when is_list(Config) ->
@@ -194,16 +396,20 @@ payload(Config) when is_list(Config) ->
dist_port_overload() ->
[{doc, "Test that TLS distribution connections can be accepted concurrently"}].
dist_port_overload(Config) when is_list(Config) ->
+ (RequiredConcurrency = 2) =< erlang:system_info(schedulers_online)
+ orelse
+ throw({skip, "Not enough schedulers online"}),
+ %%
%% Start a node, and get the port number it's listening on.
#node_handle{nodename = NodeName} = NH1 = start_ssl_node(Config),
[Name, Host] = string:lexemes(atom_to_list(NodeName), "@"),
{ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0),
{Name, Port} = lists:keyfind(Name, 1, NodesPorts),
- %% Run 4 connections concurrently. When TLS handshake is not concurrent,
- %% and with default net_setuptime of 7 seconds, only one connection per 7
- %% seconds is closed from server side. With concurrent accept, all 7 will
- %% be dropped in 7 seconds
- RequiredConcurrency = 4,
+ %% Run RequiredConcurrency connections concurrently.
+ %% When TLS handshake is not concurrent,
+ %% and with default net_setuptime of 7 seconds,
+ %% only one connection per 7 seconds is closed from server side.
+ %% With concurrent accept they will be closed in parallel.
Started = [connect(self(), Host, Port) || _ <- lists:seq(1, RequiredConcurrency)],
%% give 10 seconds (more than 7, less than 2x7 seconds)
Responded = barrier(RequiredConcurrency, [], erlang:system_time(millisecond) + 10000),
@@ -259,11 +465,15 @@ plain_options(Config) when is_list(Config) ->
plain_verify_options() ->
[{doc,"Test specifying tls options including certificate verification options"}].
plain_verify_options(Config) when is_list(Config) ->
- TLSOpts = "-ssl_dist_opt server_secure_renegotiate true "
- "client_secure_renegotiate true "
- "server_hibernate_after 500 client_hibernate_after 500"
- "server_reuse_sessions true client_reuse_sessions true "
- "server_depth 1 client_depth 1 ",
+ TLSOpts = "-ssl_dist_opt "
+ "server_secure_renegotiate true "
+ "client_secure_renegotiate true "
+ "server_hibernate_after 500 "
+ "client_hibernate_after 500 "
+ "server_reuse_sessions true "
+ "client_reuse_sessions true "
+ "server_depth 1 "
+ "client_depth 1 ",
gen_dist_test(plain_verify_options_test, [{tls_verify_opts, TLSOpts} | Config]).
%%--------------------------------------------------------------------
@@ -454,16 +664,20 @@ address_please(_, _, _) ->
gen_dist_test(Test, Config) ->
NH1 = start_ssl_node(Config),
NH2 = start_ssl_node(Config),
- try
- ?MODULE:Test(NH1, NH2, Config)
+ try
+ if
+ is_atom(Test) ->
+ ?MODULE:Test(NH1, NH2, Config);
+ is_function(Test, 2) ->
+ Test(NH1, NH2)
+ end
catch
- _:Reason ->
- stop_ssl_node(NH1),
- stop_ssl_node(NH2),
- ct:fail(Reason)
+ Class:Reason:Stacktrace ->
+ ct:fail({Class,Reason,Stacktrace})
+ after
+ stop_ssl_node(NH1),
+ stop_ssl_node(NH2)
end,
- stop_ssl_node(NH1),
- stop_ssl_node(NH2),
success(Config).
%% ssl_node side api
@@ -486,6 +700,7 @@ try_setting_priority(TestFun, Config) ->
{error,_} ->
{skip, "Can not set priority on socket"}
end.
+
basic_test(NH1, NH2, _) ->
Node1 = NH1#node_handle.nodename,
Node2 = NH2#node_handle.nodename,
@@ -493,6 +708,8 @@ basic_test(NH1, NH2, _) ->
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end),
+ verify_tls(NH1, NH2),
+
%% The test_server node has the same cookie as the ssl nodes
%% but it should not be able to communicate with the ssl nodes
%% via the erlang distribution.
@@ -582,6 +799,8 @@ payload_test(NH1, NH2, _) ->
pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
+ verify_tls(NH1, NH2),
+
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end),
@@ -618,6 +837,8 @@ plain_options_test(NH1, NH2, _) ->
pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
+ verify_tls(NH1, NH2),
+
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end).
@@ -627,6 +848,8 @@ plain_verify_options_test(NH1, NH2, _) ->
pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
+ verify_tls(NH1, NH2),
+
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end).
@@ -758,38 +981,52 @@ start_ssl_node(Config, XArgs) ->
App = proplists:get_value(app_opts, Config),
SSLOpts = setup_tls_opts(Config),
start_ssl_node_name(
- Name, App ++ " " ++ SSLOpts ++ XArgs).
+ Name, App ++ " " ++ SSLOpts ++ " " ++ XArgs).
mk_node_name(Config) ->
- N = erlang:unique_integer([positive]),
- Case = proplists:get_value(testcase, Config),
- Hostname =
- case proplists:get_value(hostname, Config) of
- undefined -> "";
- Host -> "@" ++ Host
- end,
- atom_to_list(?MODULE)
- ++ "_"
- ++ atom_to_list(Case)
- ++ "_"
- ++ integer_to_list(N) ++ Hostname.
-
+ case proplists:get_value(node_name, Config) of
+ undefined ->
+ N = erlang:unique_integer([positive]),
+ Case = proplists:get_value(testcase, Config),
+ Hostname =
+ case proplists:get_value(hostname, Config) of
+ undefined -> "";
+ Host -> "@" ++ Host
+ end,
+ atom_to_list(?MODULE)
+ ++ "_"
+ ++ atom_to_list(Case)
+ ++ "_"
+ ++ integer_to_list(N) ++ Hostname;
+ Name ->
+ Name
+ end.
setup_certs(Config) ->
+ {ok,Host} = inet:gethostname(),
+ Extensions =
+ {extensions,
+ [#'Extension'{
+ extnID = ?'id-ce-subjectAltName',
+ extnValue = [{dNSName, Host}],
+ critical = false}]},
PrivDir = proplists:get_value(priv_dir, Config),
- DerConfig = public_key:pkix_test_data(#{server_chain => #{root => rsa_root_key(1),
- intermediates => [rsa_intermediate(2)],
- peer => rsa_peer_key(3)},
- client_chain => #{root => rsa_root_key(1),
- intermediates => [rsa_intermediate(5)],
- peer => rsa_peer_key(6)}}),
+ DerConfig =
+ public_key:pkix_test_data(
+ #{server_chain =>
+ #{root => [rsa_root_key(1), {digest,sha256}],
+ intermediates => [rsa_intermediate_conf(2)],
+ peer => [rsa_peer_key(3), {digest, sha256}, Extensions]},
+ client_chain =>
+ #{root => [rsa_root_key(1), {digest, sha256}],
+ intermediates => [rsa_intermediate_conf(5)],
+ peer => [rsa_peer_key(6), {digest, sha256}, Extensions]}}),
ClientBase = filename:join([PrivDir, "rsa"]),
- SeverBase = filename:join([PrivDir, "rsa"]),
-
- _ = x509_test:gen_pem_config_files(DerConfig, ClientBase, SeverBase).
-
-setup_tls_opts(Config) ->
+ ServerBase = filename:join([PrivDir, "rsa"]),
+ _ = x509_test:gen_pem_config_files(DerConfig, ClientBase, ServerBase).
+
+setup_tls_opts(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
SC = filename:join([PrivDir, "rsa_server_cert.pem"]),
SK = filename:join([PrivDir, "rsa_server_key.pem"]),
@@ -801,7 +1038,7 @@ setup_tls_opts(Config) ->
case proplists:get_value(tls_only_basic_opts, Config, []) of
[_|_] = BasicOpts -> %% No verify but server still need to have cert
"-proto_dist inet_tls " ++ "-ssl_dist_opt server_certfile " ++ SC ++ " "
- ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " " ++ BasicOpts;
+ ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " " ++ " -ssl_dist_opt client_verify verify_none " ++ BasicOpts;
[] -> %% Verify
TlsVerifyOpts = proplists:get_value(tls_verify_opts, Config, []),
case TlsVerifyOpts of
@@ -819,7 +1056,7 @@ setup_tls_opts(Config) ->
++ TlsVerifyOpts;
_ -> %% No verify, no extra opts
"-proto_dist inet_tls " ++ "-ssl_dist_opt server_certfile " ++ SC ++ " "
- ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " "
+ ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " " ++ "-ssl_dist_opt client_verify verify_none "
end
end.
@@ -842,9 +1079,10 @@ add_ssl_opts_config(Config) ->
KrnlDir = filename:join([LibDir, "kernel-" ++ KRNL_VSN]),
{ok, _} = file:read_file_info(StdlDir),
{ok, _} = file:read_file_info(KrnlDir),
- SSL_VSN = vsn(ssl),
- VSN_CRYPTO = vsn(crypto),
- VSN_PKEY = vsn(public_key),
+ SSL_VSN = get_app_vsn(ssl),
+ VSN_CRYPTO = get_app_vsn(crypto),
+ VSN_ASN1 = get_app_vsn(asn1),
+ VSN_PKEY = get_app_vsn(public_key),
SslDir = filename:join([LibDir, "ssl-" ++ SSL_VSN]),
{ok, _} = file:read_file_info(SslDir),
@@ -858,6 +1096,7 @@ add_ssl_opts_config(Config) ->
" [{kernel, \"~s\"},~n"
" {stdlib, \"~s\"},~n"
" {crypto, \"~s\"},~n"
+ " {asn1, \"~s\"},~n"
" {public_key, \"~s\"},~n"
" {ssl, \"~s\"}]}.~n",
[case catch erlang:system_info(otp_release) of
@@ -868,13 +1107,23 @@ add_ssl_opts_config(Config) ->
KRNL_VSN,
STDL_VSN,
VSN_CRYPTO,
+ VSN_ASN1,
VSN_PKEY,
SSL_VSN]),
ok = file:close(RelFile),
- ok = systools:make_script(Script, []),
+ ct:pal("Bootscript: ~p", [Script]),
+ case systools:make_script(Script, []) of
+ ok ->
+ ok;
+ NotOk ->
+ ct:pal("Bootscript problem: ~p", [NotOk]),
+ erlang:error(NotOk)
+ end,
[{app_opts, "-boot " ++ Script} | Config]
catch
- _:_ ->
+ Class : Reason : Stacktrace ->
+ ct:pal("Exception while generating bootscript:~n~p",
+ [{Class, Reason, Stacktrace}]),
[{app_opts, "-pa \"" ++ filename:dirname(code:which(ssl))++"\""}
| add_comment_config(
"Bootscript wasn't used since the test wasn't run on an "
@@ -896,19 +1145,12 @@ success(Config) ->
_ -> ok
end.
-vsn(App) ->
- application:start(App),
- try
- {value,
- {ssl,
- _,
- VSN}} = lists:keysearch(App,
- 1,
- application:which_applications()),
- VSN
- after
- application:stop(ssl)
- end.
+get_app_vsn(erts) ->
+ erlang:system_info(version);
+get_app_vsn(App) ->
+ application:load(App),
+ {ok, AppKeys} = application:get_all_key(App),
+ proplists:get_value(vsn, AppKeys).
verify_fail_always(_Certificate, _Event, _State) ->
%% Create an ETS table, to record the fact that the verify function ran.
@@ -940,6 +1182,18 @@ verify_pass_always(_Certificate, _Event, State) ->
receive go_ahead -> ok end,
{valid, State}.
+verify_tls(NH1, NH2) ->
+ %% Verify that distribution protocol between nodes is TLS
+ Node1 = NH1#node_handle.nodename,
+ Node2 = NH2#node_handle.nodename,
+ {ok,NodeInfo2} = apply_on_ssl_node(NH1, net_kernel, node_info, [Node2]),
+ {ok,NodeInfo1} = apply_on_ssl_node(NH2, net_kernel, node_info, [Node1]),
+ {address,#net_address{protocol = tls}} =
+ lists:keyfind(address, 1, NodeInfo1),
+ {address,#net_address{protocol = tls}} =
+ lists:keyfind(address, 1, NodeInfo2),
+ ok.
+
localhost_ip(InetVer) ->
{ok, Addr} = inet:getaddr(net_adm:localhost(), InetVer),
Addr.
@@ -963,14 +1217,14 @@ inet_ver() ->
rsa_root_key(N) ->
%% As rsa keygen is not guaranteed to be fast
- [{key, ssl_test_lib:hardcode_rsa_key(N)}].
+ {key, ssl_test_lib:hardcode_rsa_key(N)}.
rsa_peer_key(N) ->
%% As rsa keygen is not guaranteed to be fast
- [{key, ssl_test_lib:hardcode_rsa_key(N)}].
+ {key, ssl_test_lib:hardcode_rsa_key(N)}.
-rsa_intermediate(N) ->
- [{key, ssl_test_lib:hardcode_rsa_key(N)}].
+rsa_intermediate_conf(N) ->
+ [{key, ssl_test_lib:hardcode_rsa_key(N)}, {digest, sha256}].
maybe_quote_tuple_list(String) ->
diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl
index b556701869..989007bec0 100644
--- a/lib/ssl/test/ssl_dist_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl
@@ -1,7 +1,7 @@
%%%-------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
%% %CopyrightEnd%
%%
-module(ssl_dist_bench_SUITE).
+-feature(maybe_expr, enable).
-behaviour(ct_suite).
@@ -33,8 +34,10 @@
%% Test cases
-export(
[setup/1,
+ parallel_setup/1,
roundtrip/1,
sched_utilization/1,
+ mean_load_cpu_margin/1,
throughput_0/1,
throughput_64/1,
throughput_1024/1,
@@ -45,27 +48,44 @@
throughput_1048576/1]).
%% Debug
--export([payload/1, roundtrip_runner/3, setup_runner/3, throughput_runner/4]).
+-export([payload/1, roundtrip_runner/3, setup_runner/3, throughput_runner/4,
+ mem/0]).
%%%-------------------------------------------------------------------
suite() -> [{ct_hooks, [{ts_install_cth, [{nodenames, 2}]}]}].
all() ->
- [{group, ssl},
- {group, crypto},
- {group, plain}].
+ [{group, smoketest}].
groups() ->
- [{benchmark, all()},
+ [{smoketest, protocols()},
+ {benchmark, protocols()},
+ %%
+ %% protocols()
+ {ssl, ssl_backends()},
+ {cryptcookie, cryptcookie_backends()},
+ {plain, categories()},
+ {socket, categories()},
+ %%
+ %% ssl_backends()
+ {tls, categories()},
+ {ktls, categories()},
%%
- {ssl, all_groups()},
- {crypto, all_groups()},
- {plain, all_groups()},
+ %% cryptcookie_backends()
+ {dist_cryptcookie_socket, categories()},
+ {cryptcookie_socket_ktls, categories()},
+ {dist_cryptcookie_inet, categories()},
+ {cryptcookie_inet_ktls, categories()},
%%
- {setup, [{repeat, 1}], [setup]},
+ %% categories()
+ {setup, [{repeat, 1}],
+ [setup,
+ parallel_setup]},
{roundtrip, [{repeat, 1}], [roundtrip]},
- {sched_utilization,[{repeat, 1}], [sched_utilization]},
+ {sched_utilization,[{repeat, 1}],
+ [sched_utilization,
+ mean_load_cpu_margin]},
{throughput, [{repeat, 1}],
[throughput_0,
throughput_64,
@@ -76,7 +96,23 @@ groups() ->
throughput_262144,
throughput_1048576]}].
-all_groups() ->
+protocols() ->
+ [{group, ssl},
+ {group, cryptcookie},
+ {group, plain},
+ {group, socket}].
+
+ssl_backends() ->
+ [{group, tls},
+ {group, ktls}].
+
+cryptcookie_backends() ->
+ [{group, dist_cryptcookie_socket},
+ {group, cryptcookie_socket_ktls},
+ {group, dist_cryptcookie_inet},
+ {group, cryptcookie_inet_ktls}].
+
+categories() ->
[{group, setup},
{group, roundtrip},
{group, throughput},
@@ -84,31 +120,37 @@ all_groups() ->
].
init_per_suite(Config) ->
- Digest = sha1,
+ Digest = sha256,
ECCurve = secp521r1,
- TLSVersion = 'tlsv1.2',
+%%% TLSVersion = 'tlsv1.2',
+%%% TLSCipher =
+%%% #{key_exchange => ecdhe_ecdsa,
+%%% cipher => aes_128_cbc,
+%%% mac => sha256,
+%%% prf => sha256},
+ TLSVersion = 'tlsv1.3',
TLSCipher =
#{key_exchange => ecdhe_ecdsa,
- cipher => aes_128_cbc,
- mac => sha256,
- prf => sha256},
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384},
%%
Node = node(),
- Skipped = make_ref(),
+ Skip = make_ref(),
try
Node =/= nonode@nohost orelse
- throw({Skipped,"Node not distributed"}),
+ throw({Skip,"Node not distributed"}),
verify_node_src_addr(),
{supported, SSLVersions} =
lists:keyfind(supported, 1, ssl:versions()),
lists:member(TLSVersion, SSLVersions) orelse
throw(
- {Skipped,
+ {Skip,
"SSL does not support " ++ term_to_string(TLSVersion)}),
- lists:member(ECCurve, ssl:eccs(TLSVersion)) orelse
- throw(
- {Skipped,
- "SSL does not support " ++ term_to_string(ECCurve)}),
+%%% lists:member(ECCurve, ssl:eccs(TLSVersion)) orelse
+%%% throw(
+%%% {Skip,
+%%% "SSL does not support " ++ term_to_string(ECCurve)}),
TLSCipherKeys = maps:keys(TLSCipher),
lists:any(
fun (Cipher) ->
@@ -116,25 +158,11 @@ init_per_suite(Config) ->
end,
ssl:cipher_suites(default, TLSVersion)) orelse
throw(
- {Skipped,
+ {Skip,
"SSL does not support " ++ term_to_string(TLSCipher)}),
%%
%%
%%
- PrivDir = proplists:get_value(priv_dir, Config),
- [_, HostA] = split_node(Node),
- NodeAName = ?MODULE_STRING ++ "_node_a",
- NodeAString = NodeAName ++ "@" ++ HostA,
- NodeAConfFile = filename:join(PrivDir, NodeAString ++ ".conf"),
- NodeA = list_to_atom(NodeAString),
- %%
- ServerNode = ssl_bench_test_lib:setup(dist_server),
- [_, HostB] = split_node(ServerNode),
- NodeBName = ?MODULE_STRING ++ "_node_b",
- NodeBString = NodeBName ++ "@" ++ HostB,
- NodeBConfFile = filename:join(PrivDir, NodeBString ++ ".conf"),
- NodeB = list_to_atom(NodeBString),
- %%
CertOptions =
[{digest, Digest},
{key, {namedCurve, ECCurve}}],
@@ -152,56 +180,153 @@ init_per_suite(Config) ->
| SSLConf],
ClientConf = SSLConf,
%%
+ PrivDir = proplists:get_value(priv_dir, Config),
+ %%
+ ServerNode = ssl_bench_test_lib:setup(dist_server),
+ [_, ServerHost] = split_node(ServerNode),
+ ServerName = ?MODULE_STRING ++ "_server",
+ ServerString = ServerName ++ "@" ++ ServerHost,
+ ServerConfFile = filename:join(PrivDir, ServerString ++ ".conf"),
+ Server = list_to_atom(ServerString),
+ %%
write_node_conf(
- NodeAConfFile, NodeA, ServerConf, ClientConf,
- CertOptions, RootCert),
- write_node_conf(
- NodeBConfFile, NodeB, ServerConf, ClientConf,
+ ServerConfFile, Server, ServerConf, ClientConf,
CertOptions, RootCert),
%%
- [{node_a_name, NodeAName},
- {node_a, NodeA},
- {node_a_dist_args,
+ Schedulers =
+ erpc:call(ServerNode, erlang, system_info, [schedulers]),
+ [_, ClientHost] = split_node(Node),
+ [{server_node, ServerNode},
+ {server_name, ServerName},
+ {server, Server},
+ {server_dist_args,
"-proto_dist inet_tls "
- "-ssl_dist_optfile " ++ NodeAConfFile ++ " "},
- {node_b_name, NodeBName},
- {node_b, NodeB},
- {node_b_dist_args,
- "-proto_dist inet_tls "
- "-ssl_dist_optfile " ++ NodeBConfFile ++ " "},
- {server_node, ServerNode}
- |Config]
+ "-ssl_dist_optfile " ++ ServerConfFile ++ " "},
+ {clients, Schedulers} |
+ init_client_node(
+ ClientHost, Schedulers, PrivDir, ServerConf, ClientConf,
+ CertOptions, RootCert, Config)]
catch
- throw : {Skipped, Reason} ->
- {skipped, Reason};
+ throw : {Skip, Reason} ->
+ {skip, Reason};
Class : Reason : Stacktrace ->
- {failed, {Class, Reason, Stacktrace}}
+ {fail, {Class, Reason, Stacktrace}}
end.
+init_client_node(
+ _ClientHost, 0, _PrivDir, _ServerConf, _ClientConf,
+ _CertOptions, _RootCert, Config) ->
+ Config;
+init_client_node(
+ ClientHost, N, PrivDir, ServerConf, ClientConf,
+ CertOptions, RootCert, Config) ->
+ ClientName = ?MODULE_STRING ++ "_client_" ++ integer_to_list(N),
+ ClientString = ClientName ++ "@" ++ ClientHost,
+ ClientConfFile = filename:join(PrivDir, ClientString ++ ".conf"),
+ Client = list_to_atom(ClientString),
+ %%
+ write_node_conf(
+ ClientConfFile, Client, ServerConf, ClientConf,
+ CertOptions, RootCert),
+ init_client_node(
+ ClientHost, N - 1, PrivDir, ServerConf, ClientConf,
+ CertOptions, RootCert,
+ [{{client_name, N}, ClientName},
+ {{client, N}, Client},
+ {{client_dist_args, N},
+ "-proto_dist inet_tls "
+ "-ssl_dist_optfile " ++ ClientConfFile ++ " "} | Config]).
+
end_per_suite(Config) ->
ServerNode = proplists:get_value(server_node, Config),
ssl_bench_test_lib:cleanup(ServerNode).
+init_per_group(benchmark, Config) ->
+ [{effort,10}|Config];
+%%
init_per_group(ssl, Config) ->
[{ssl_dist, true}, {ssl_dist_prefix, "SSL"}|Config];
-init_per_group(crypto, Config) ->
- try inet_crypto_dist:supported() of
+init_per_group(dist_cryptcookie_socket, Config) ->
+ try inet_epmd_dist_cryptcookie_socket:supported() of
+ ok ->
+ [{ssl_dist, false}, {ssl_dist_prefix, "Crypto-Socket"},
+ {ssl_dist_args,
+ "-proto_dist inet_epmd -inet_epmd dist_cryptcookie_socket"}
+ | Config];
+ Problem ->
+ {skip, Problem}
+ catch
+ Class : Reason : Stacktrace ->
+ {fail, {Class, Reason, Stacktrace}}
+ end;
+init_per_group(cryptcookie_socket_ktls, Config) ->
+ try inet_epmd_cryptcookie_socket_ktls:supported() of
+ ok ->
+ [{ssl_dist, false}, {ssl_dist_prefix, "Crypto-Socket-kTLS"},
+ {ssl_dist_args,
+ "-proto_dist inet_epmd -inet_epmd cryptcookie_socket_ktls"}
+ | Config];
+ Problem ->
+ {skip, Problem}
+ catch
+ Class : Reason : Stacktrace ->
+ {fail, {Class, Reason, Stacktrace}}
+ end;
+init_per_group(dist_cryptcookie_inet, Config) ->
+ try inet_epmd_dist_cryptcookie_inet:supported() of
ok ->
- [{ssl_dist, false}, {ssl_dist_prefix, "Crypto"},
+ [{ssl_dist, false}, {ssl_dist_prefix, "Crypto-Inet"},
{ssl_dist_args,
- "-proto_dist inet_crypto"}
- |Config];
+ "-proto_dist inet_epmd -inet_epmd dist_cryptcookie_inet"}
+ | Config];
Problem ->
- {skipped,
- "Crypto does not support " ++ Problem}
+ {skip, Problem}
catch
Class : Reason : Stacktrace ->
- {failed, {Class, Reason, Stacktrace}}
+ {fail, {Class, Reason, Stacktrace}}
+ end;
+init_per_group(cryptcookie_inet_ktls, Config) ->
+ try inet_epmd_cryptcookie_inet_ktls:supported() of
+ ok ->
+ [{ssl_dist, false}, {ssl_dist_prefix, "Crypto-Inet-kTLS"},
+ {ssl_dist_args,
+ "-proto_dist inet_epmd -inet_epmd cryptcookie_inet_ktls"}
+ | Config];
+ Problem ->
+ {skip, Problem}
+ catch
+ Class : Reason : Stacktrace ->
+ {fail, {Class, Reason, Stacktrace}}
end;
init_per_group(plain, Config) ->
[{ssl_dist, false}, {ssl_dist_prefix, "Plain"}|Config];
-init_per_group(benchmark, Config) ->
- [{effort,10}|Config];
+%%
+init_per_group(socket, Config) ->
+ try inet_epmd_socket:supported() of
+ ok ->
+ [{ssl_dist, false},
+ {ssl_dist_prefix, "Socket"},
+ {ssl_dist_args,
+ "-proto_dist inet_epmd -inet_epmd socket"}
+ | Config];
+ Problem ->
+ {skip, Problem}
+ catch
+ Class : Reason : Stacktrace ->
+ {fail, {Class, Reason, Stacktrace}}
+ end;
+%%
+init_per_group(ktls, Config) ->
+ case ktls_supported() of
+ ok ->
+ [{ktls, true},
+ {ssl_dist_prefix,
+ proplists:get_value(ssl_dist_prefix, Config) ++ "-kTLS"}
+ | proplists:delete(ssl_dist_prefix, Config)];
+ {error, Reason} ->
+ {skip, Reason}
+ end;
+%%
init_per_group(_GroupName, Config) ->
Config.
@@ -227,6 +352,24 @@ init_per_testcase(Func, Conf) ->
end_per_testcase(_Func, _Conf) ->
ok.
+
+ktls_supported() ->
+ {ok, Listen} = gen_tcp:listen(0, [{active, false}]),
+ {ok, Port} = inet:port(Listen),
+ {ok, Client} =
+ gen_tcp:connect({127,0,0,1}, Port, [{active, false}]),
+ try
+ maybe
+ {ok, OS} ?= ssl_test_lib:ktls_os(),
+ ok ?= ssl_test_lib:ktls_set_ulp(Client, OS),
+ ssl_test_lib:ktls_set_cipher(Client, OS, tx, 1)
+ end
+ after
+ _ = gen_tcp:close(Client),
+ _ = gen_tcp:close(Listen)
+ end.
+
+
%%%-------------------------------------------------------------------
%%% CommonTest API helpers
@@ -297,18 +440,31 @@ setup(Config) ->
run_nodepair_test(fun setup/6, Config).
setup(A, B, Prefix, Effort, HA, HB) ->
- Rounds = 5 * Effort,
+ Rounds = 100 * Effort,
[] = ssl_apply(HA, erlang, nodes, []),
[] = ssl_apply(HB, erlang, nodes, []),
+ pong = ssl_apply(HA, net_adm, ping, [B]),
+ _ = ssl_apply(HA, fun () -> set_cpu_affinity(client) end),
+ {Log, Before, After} =
+ ssl_apply(HB, fun () -> set_cpu_affinity(server) end),
+ ct:pal("Server CPU affinity: ~w -> ~w~n~s", [Before, After, Log]),
+ MemStart = mem_start(HA, HB),
+ ChildCountResult =
+ ssl_dist_test_lib:apply_on_ssl_node(
+ HA, supervisor, count_children, [tls_dist_connection_sup]),
+ ct:log("TLS Connection Child Count Result: ~p", [ChildCountResult]),
{SetupTime, CycleTime} =
ssl_apply(HA, fun () -> setup_runner(A, B, Rounds) end),
ok = ssl_apply(HB, fun () -> setup_wait_nodedown(A, 10000) end),
+ {MemA, MemB, MemSuffix} = mem_stop(HA, HB, MemStart),
%% [] = ssl_apply(HA, erlang, nodes, []),
%% [] = ssl_apply(HB, erlang, nodes, []),
SetupSpeed = round((Rounds*1000000*1000) / SetupTime),
CycleSpeed = round((Rounds*1000000*1000) / CycleTime),
+ _ = report(Prefix++" Setup Mem A", MemA, "KByte"),
+ _ = report(Prefix++" Setup Mem B", MemB, "KByte"),
_ = report(Prefix++" Setup", SetupSpeed, "setups/1000s"),
- report(Prefix++" Setup Cycle", CycleSpeed, "cycles/1000s").
+ report(Prefix++" Setup Cycle", CycleSpeed, "cycles/1000s " ++ MemSuffix).
%% Runs on node A against rex in node B
setup_runner(A, B, Rounds) ->
@@ -320,7 +476,14 @@ setup_loop(_A, _B, T, 0) ->
T;
setup_loop(A, B, T, N) ->
StartTime = start_time(),
- [N,A] = [N|rpc:block_call(B, erlang, nodes, [])],
+ try erpc:call(B, net_adm, ping, [A]) of
+ pong -> ok;
+ Other ->
+ error({N,Other})
+ catch
+ Class : Reason : Stacktrace ->
+ erlang:raise(Class, {N,Reason}, Stacktrace)
+ end,
Time = elapsed_time(StartTime),
[N,B] = [N|erlang:nodes()],
Mref = erlang:monitor(process, {rex,B}),
@@ -348,6 +511,145 @@ setup_wait_nodedown(A, Time) ->
end.
+set_cpu_affinity(client) ->
+ set_cpu_affinity(1);
+set_cpu_affinity(server) ->
+ set_cpu_affinity(2);
+set_cpu_affinity(Index) when is_integer(Index) ->
+ case erlang:system_info(cpu_topology) of
+ undefined ->
+ {"", undefined, undefined};
+ CpuTopology ->
+ Log = taskset(element(Index, split_cpus(CpuTopology))),
+ %% Update Schedulers
+ _ = erlang:system_info(update_cpu_info),
+ Schedulers = erlang:system_info(logical_processors_available),
+ {Log,
+ erlang:system_flag(schedulers_online, Schedulers),
+ Schedulers}
+ end.
+
+taskset(LogicalProcessors) ->
+ os:cmd(
+ "taskset -c -p " ++
+ lists:flatten(
+ lists:join(
+ ",",
+ [integer_to_list(Id) || Id <- LogicalProcessors]),
+ " ") ++ os:getpid()).
+
+split_cpus([{_Tag, List}]) ->
+ split_cpus(List);
+split_cpus(List = [_ | _]) ->
+ {A, B} = lists:split(length(List) bsr 1, List),
+ {logical_processors(A), logical_processors(B)}.
+
+logical_processors([{_Tag, {logical, Id}} | Items]) ->
+ [Id | logical_processors(Items)];
+logical_processors([{_Tag, List} | Items]) ->
+ logical_processors(List) ++ logical_processors(Items);
+logical_processors([]) ->
+ [].
+
+
+%%----------------
+%% Parallel setup
+
+parallel_setup(Config) ->
+ Clients = proplists:get_value(clients, Config),
+ parallel_setup(Config, Clients, Clients, []).
+
+parallel_setup(Config, Clients, I, HNs) when 0 < I ->
+ Key = {client, I},
+ Node = proplists:get_value(Key, Config),
+ Handle = start_ssl_node(Key, Config),
+ _ = ssl_apply(Handle, fun () -> set_cpu_affinity(client) end),
+ try
+ parallel_setup(Config, Clients, I - 1, [{Handle, Node} | HNs])
+ after
+ stop_ssl_node(Key, Handle, Config)
+ end;
+parallel_setup(Config, Clients, _0, HNs) ->
+ Key = server,
+ ServerNode = proplists:get_value(Key, Config),
+ ServerHandle = start_ssl_node(Key, Config, 0),
+ Effort = proplists:get_value(effort, Config, 1),
+ TotalRounds = 1000 * Effort,
+ Rounds = round(TotalRounds / Clients),
+ try
+ {Log, Before, After} =
+ ssl_apply(ServerHandle, fun () -> set_cpu_affinity(server) end),
+ ct:pal("Server CPU affinity: ~w -> ~w~n~s", [Before, After, Log]),
+ ServerMemBefore =
+ ssl_apply(ServerHandle, fun mem/0),
+ parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore,
+ [parallel_setup_runner(Handle, Node, ServerNode, Rounds)
+ || {Handle, Node} <- HNs])
+ after
+ stop_ssl_node(Key, ServerHandle, Config)
+ end.
+
+parallel_setup_runner(Handle, Node, ServerNode, Rounds) ->
+ Collector = self(),
+ Tag = make_ref(),
+ _ =
+ spawn_link(
+ fun () ->
+ Collector !
+ {Tag,
+ try
+ MemBefore =
+ ssl_apply(Handle, fun mem/0),
+ Result =
+ ssl_apply(
+ Handle, ?MODULE, setup_runner,
+ [Node, ServerNode, Rounds]),
+ MemAfter =
+ ssl_apply(Handle, fun mem/0),
+ {MemBefore, Result, MemAfter}
+ catch Class : Reason : Stacktrace ->
+ {Class, Reason, Stacktrace}
+ end}
+ end),
+ Tag.
+
+parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore, Tags) ->
+ parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore, Tags,
+ 0, 0, 0).
+%%
+parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore, [Tag | Tags],
+ SetupTime, CycleTime, Mem) ->
+ receive
+ {Tag, {Mem1, {ST, CT}, Mem2}}
+ when is_integer(ST), is_integer(CT) ->
+ parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore, Tags,
+ SetupTime + ST, CycleTime + CT, Mem + Mem2 - Mem1);
+ {Tag, Error} ->
+ exit(Error)
+ end;
+parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore, [],
+ SetupTime, CycleTime, Mem) ->
+ ServerMemAfter =
+ ssl_apply(ServerHandle, fun mem/0),
+ ServerMem = ServerMemAfter - ServerMemBefore,
+ Clients = proplists:get_value(clients, Config),
+ Prefix = proplists:get_value(ssl_dist_prefix, Config),
+ SetupSpeed = 1000 * round(TotalRounds / (SetupTime/1000000)),
+ CycleSpeed = 1000 * round(TotalRounds / (CycleTime/1000000)),
+ {MemC, MemS, MemSuffix} = mem_result({Mem / Clients, ServerMem}),
+ _ = report(Prefix++" Parallel Setup Mem Clients", MemC, "KByte"),
+ _ = report(Prefix++" Parallel Setup Mem Server", MemS, "KByte"),
+ _ = report(Prefix++" Parallel Setup", SetupSpeed, "setups/1000s"),
+ report(
+ Prefix++" Parallel Setup Cycle", CycleSpeed, "cycles/1000s "
+ ++ MemSuffix).
+
%%----------------
%% Roundtrip speed
@@ -358,28 +660,33 @@ roundtrip(A, B, Prefix, Effort, HA, HB) ->
Rounds = 4000 * Effort,
[] = ssl_apply(HA, erlang, nodes, []),
[] = ssl_apply(HB, erlang, nodes, []),
+ MemStart = mem_start(HA, HB),
ok = ssl_apply(HA, net_kernel, allow, [[B]]),
ok = ssl_apply(HB, net_kernel, allow, [[A]]),
Time = ssl_apply(HA, fun () -> roundtrip_runner(A, B, Rounds) end),
[B] = ssl_apply(HA, erlang, nodes, []),
[A] = ssl_apply(HB, erlang, nodes, []),
+ {MemA, MemB, MemSuffix} = mem_stop(HA, HB, MemStart),
Speed = round((Rounds*1000000) / Time),
- report(Prefix++" Roundtrip", Speed, "pings/s").
+ _ = report(Prefix++" Roundtrip Mem A", MemA, "KByte"),
+ _ = report(Prefix++" Roundtrip Mem B", MemB, "KByte"),
+ report(Prefix++" Roundtrip", Speed, "pings/s " ++ MemSuffix).
%% Runs on node A and spawns a server on node B
roundtrip_runner(A, B, Rounds) ->
ClientPid = self(),
- [A] = rpc:call(B, erlang, nodes, []),
+ [A] = erpc:call(B, erlang, nodes, []),
ServerPid =
erlang:spawn(
B,
- fun () -> roundtrip_server(ClientPid, Rounds) end),
+ fun () ->
+ roundtrip_server(ClientPid, Rounds)
+ end),
ServerMon = erlang:monitor(process, ServerPid),
- microseconds(
- roundtrip_client(ServerPid, ServerMon, start_time(), Rounds)).
+ roundtrip_client(ServerPid, ServerMon, start_time(), Rounds).
roundtrip_server(_Pid, 0) ->
- ok;
+ exit(ok);
roundtrip_server(Pid, N) ->
receive
N ->
@@ -390,7 +697,7 @@ roundtrip_server(Pid, N) ->
roundtrip_client(_Pid, Mon, StartTime, 0) ->
Time = elapsed_time(StartTime),
receive
- {'DOWN', Mon, _, _, normal} ->
+ {'DOWN', Mon, _, _, ok} ->
Time;
{'DOWN', Mon, _, _, Other} ->
exit(Other)
@@ -416,6 +723,7 @@ sched_utilization(A, B, Prefix, Effort, HA, HB, Config) ->
SSL = proplists:get_value(ssl_dist, Config),
[] = ssl_apply(HA, erlang, nodes, []),
[] = ssl_apply(HB, erlang, nodes, []),
+ MemStart = mem_start(HA, HB),
PidA = ssl_apply(HA, os, getpid, []),
PidB = ssl_apply(HB, os, getpid, []),
ct:pal("Starting scheduler utilization run effort ~w:~n"
@@ -435,6 +743,7 @@ sched_utilization(A, B, Prefix, Effort, HA, HB, Config) ->
ct:log("Got ~p busy_dist_port msgs",[tail(BusyDistPortMsgs)]),
[B] = ssl_apply(HA, erlang, nodes, []),
[A] = ssl_apply(HB, erlang, nodes, []),
+ {MemA, MemB, MemSuffix} = mem_stop(HA, HB, MemStart),
ct:log("Microstate accounting for node ~w:", [A]),
msacc:print(ClientMsacc),
ct:log("Microstate accounting for node ~w:", [B]),
@@ -459,26 +768,30 @@ sched_utilization(A, B, Prefix, Effort, HA, HB, Config) ->
ct:log("Stray Msgs: ~p", [BusyDistPortMsgs]),
" ???"
end,
+ _ = report(Prefix++" Sched Utilization Client Mem", MemA, "KByte"),
+ _ = report(Prefix++" Sched Utilization Server Mem", MemB, "KByte"),
{comment, ClientComment} =
report(Prefix ++ " Sched Utilization Client" ++ Verdict,
- SchedUtilClient, "/100 %" ++ Verdict),
+ SchedUtilClient, " %" ++ Verdict),
{comment, ServerComment} =
report(Prefix++" Sched Utilization Server" ++ Verdict,
- SchedUtilServer, "/100 %" ++ Verdict),
- {comment, "Client " ++ ClientComment ++ ", Server " ++ ServerComment}.
+ SchedUtilServer, " %" ++ Verdict),
+ {comment,
+ "Client " ++ ClientComment ++ ", Server " ++ ServerComment ++
+ " " ++ MemSuffix}.
%% Runs on node A and spawns a server on node B
%% We want to avoid getting busy_dist_port as it hides the true SU usage
%% of the receiver and sender.
sched_util_runner(A, B, Effort, true, Config) ->
- sched_util_runner(A, B, Effort, 250, Config);
+ sched_util_runner(A, B, Effort, 100, Config);
sched_util_runner(A, B, Effort, false, Config) ->
- sched_util_runner(A, B, Effort, 250, Config);
+ sched_util_runner(A, B, Effort, 100, Config);
sched_util_runner(A, B, Effort, Senders, Config) ->
process_flag(trap_exit, true),
- Payload = payload(5),
+ Payload = payload(100),
Time = 1000 * Effort,
- [A] = rpc:call(B, erlang, nodes, []),
+ [A] = erpc:call(B, erlang, nodes, []),
ServerPids =
[erlang:spawn_link(
B, fun () -> throughput_server() end)
@@ -514,8 +827,9 @@ sched_util_runner(A, B, Effort, Senders, Config) ->
end
end),
erlang:system_monitor(self(),[busy_dist_port]),
- %% We spawn 250 senders which should mean that we
- %% have a load of 25 msgs/msec
+ %% We spawn 100 senders that send a message every 10 ms
+ %% which should produce a load of 10000 msgs/s with
+ %% payload 100 bytes each -> 1 MByte/s
_Clients =
[spawn_link(
fun() ->
@@ -594,6 +908,146 @@ throughput_client(Pid, Payload) ->
receive after 10 -> throughput_client(Pid, Payload) end.
%%-----------------
+%% Mean load CPU margin
+%%
+%% Start pairs of processes with the client on node A
+%% and the server on node B. The clients sends requests
+%% with random interval and payload and the servers reply
+%% immediately.
+%%
+%% Also, besides each server there is a compute process
+%% that does CPU work with low process priority and we measure
+%% how much such work that gets done.
+
+mean_load_cpu_margin(Config) ->
+ run_nodepair_test(fun run_mlcm/6, Config).
+
+-define(MLCM_NO, 100).
+
+run_mlcm(A, B, Prefix, Effort, HA, HB) ->
+ [] = ssl_apply(HA, erlang, nodes, []),
+ [] = ssl_apply(HB, erlang, nodes, []),
+ MemStart = mem_start(HA, HB),
+ pong = ssl_apply(HB, net_adm, ping, [A]),
+ Count = ssl_apply(HA, fun () -> mlcm(B, Effort) end),
+ {MemA, MemB, MemSuffix} = mem_stop(HA, HB, MemStart),
+ _ = report(Prefix++" CPU Margin Mem A", MemA, "KByte"),
+ _ = report(Prefix++" CPU Margin Mem B", MemB, "KByte"),
+ report(
+ Prefix++" CPU Margin",
+ round(Count/?MLCM_NO/Effort),
+ "stones " ++ MemSuffix).
+
+mlcm(Node, Effort) ->
+ Payloads = mlcm_payloads(),
+ Clients =
+ [mlcm_client_start(Node, Payloads) || _ <- lists:seq(1, ?MLCM_NO)],
+ receive after 1000 * Effort -> ok end,
+ [Alias ! {Alias,stop} || {_Monitor, Alias} <- Clients],
+ Counts =
+ [receive
+ {'DOWN',Monitor,_,_,{Alias, Count}} ->
+ Count;
+ {'DOWN',Monitor,_,_,Reason} ->
+ exit(Reason)
+ end || {Monitor, Alias} <- Clients],
+ lists:sum(Counts).
+
+mlcm_payloads() ->
+ Bin = list_to_binary([rand:uniform(256) - 1 || _ <- lists:seq(1, 512)]),
+ lists:foldl(
+ fun (N, Payloads) ->
+ Payloads#{N => binary:copy(Bin, N)}
+ end, #{}, lists:seq(0, 255)).
+
+%%-------
+
+mlcm_client_start(Node, Payloads) ->
+ Parent = self(),
+ StartRef = make_ref(),
+ {_,Monitor} =
+ spawn_monitor(
+ fun () ->
+ Alias = alias(),
+ Parent ! {StartRef, Alias},
+ Server = mlcm_server_start(Node, Alias),
+ mlcm_client(Alias, Server, Payloads, 0)
+ end),
+ receive
+ {StartRef, Alias} ->
+ {Monitor, Alias};
+ {'DOWN',Monitor,_,_,Reason} ->
+ exit(Reason)
+ end.
+
+mlcm_client(Alias, Server, Payloads, Seq) ->
+ {Time, Index} = mlcm_rand(),
+ Payload = maps:get(Index, Payloads),
+ receive after Time -> ok end,
+ Server ! {Alias, Seq, Payload},
+ receive
+ {Alias, Seq, Pl} when byte_size(Pl) =:= byte_size(Payload) ->
+ mlcm_client(Alias, Server, Payloads, Seq + 1);
+ {Alias, stop} = Msg ->
+ Server ! Msg,
+ receive after infinity -> ok end
+ end.
+
+%% Approximate normal distribution Index with an average of 6 uniform bytes
+%% and use the 7:th byte for uniform Time
+mlcm_rand() ->
+ mlcm_rand(6, rand:uniform(1 bsl (1+6)*8) - 1, 0).
+%%
+mlcm_rand(0, X, I) ->
+ Time = X + 1, % 1..256
+ Index = abs((I - 3*256) div 3), % 0..255 upper half or normal distribution
+ {Time, Index};
+mlcm_rand(N, X, I) ->
+ mlcm_rand(N - 1, X bsr 8, I + (X band 255)).
+
+%%-------
+
+mlcm_server_start(Node, Alias) ->
+ spawn_link(
+ Node,
+ fun () ->
+ Compute = mlcm_compute_start(Alias),
+ mlcm_server(Alias, 0, Compute)
+ end).
+
+mlcm_server(Alias, Seq, Compute) ->
+ receive
+ {Alias, Seq, _Payload} = Msg ->
+ Alias ! Msg,
+ mlcm_server(Alias, Seq + 1, Compute);
+ {Alias, stop} = Msg ->
+ Compute ! Msg,
+ receive after infinity -> om end
+ end.
+
+%%-------
+
+mlcm_compute_start(Alias) ->
+ spawn_opt(
+ fun () ->
+ rand:seed(exro928ss),
+ mlcm_compute(Alias, 0, 0)
+ end,
+ [link, {priority,low}]).
+
+mlcm_compute(Alias, State, Count) ->
+ receive {Alias, stop} -> exit({Alias, Count})
+ after 0 -> ok
+ end,
+ mlcm_compute(
+ Alias,
+ %% CPU payload
+ (State +
+ lists:sum([rand:uniform(1 bsl 48) || _ <- lists:seq(1, 999)]))
+ div 1000,
+ Count + 1).
+
+%%-----------------
%% Throughput speed
throughput_0(Config) ->
@@ -647,8 +1101,8 @@ throughput_1048576(Config) ->
throughput(A, B, Prefix, HA, HB, Packets, Size) ->
[] = ssl_apply(HA, erlang, nodes, []),
[] = ssl_apply(HB, erlang, nodes, []),
+ MemStart = mem_start(HA, HB),
#{time := Time,
- client_dist_stats := ClientDistStats,
client_msacc_stats := ClientMsaccStats,
client_prof := ClientProf,
server_msacc_stats := ServerMsaccStats,
@@ -658,15 +1112,19 @@ throughput(A, B, Prefix, HA, HB, Packets, Size) ->
ssl_apply(HA, fun () -> throughput_runner(A, B, Packets, Size) end),
[B] = ssl_apply(HA, erlang, nodes, []),
[A] = ssl_apply(HB, erlang, nodes, []),
+ {MemA, MemB, MemSuffix} = mem_stop(HA, HB, MemStart),
ClientMsaccStats =:= undefined orelse
msacc:print(ClientMsaccStats),
- io:format("ClientDistStats: ~p~n", [ClientDistStats]),
Overhead =
50 % Distribution protocol headers (empirical) (TLS+=54)
+ byte_size(erlang:term_to_binary([0|<<>>])), % Benchmark overhead
Bytes = Packets * (Size + Overhead),
io:format("~w bytes, ~.4g s~n", [Bytes,Time/1000000]),
SizeString = integer_to_list(Size),
+ _ = report(
+ Prefix++" Throughput_" ++ SizeString ++ " Mem A", MemA, "KByte"),
+ _ = report(
+ Prefix++" Throughput_" ++ SizeString ++ " Mem B", MemB, "KByte"),
ClientMsaccStats =:= undefined orelse
report(
Prefix ++ " Sender_RelativeCoreLoad_" ++ SizeString,
@@ -687,12 +1145,13 @@ throughput(A, B, Prefix, HA, HB, Packets, Size) ->
io:format("******* Server GC Before:~n~p~n", [Server_GC_Before]),
io:format("******* Server GC After:~n~p~n", [Server_GC_After]),
Speed = round((Bytes * 1000000) / (1024 * Time)),
- report(Prefix ++ " Throughput_" ++ SizeString, Speed, "kB/s").
+ report(
+ Prefix ++ " Throughput_" ++ SizeString, Speed, "kB/s " ++ MemSuffix).
%% Runs on node A and spawns a server on node B
throughput_runner(A, B, Rounds, Size) ->
Payload = payload(Size),
- [A] = rpc:call(B, erlang, nodes, []),
+ [A] = erpc:call(B, erlang, nodes, []),
ClientPid = self(),
ServerPid =
erlang:spawn_opt(
@@ -721,74 +1180,10 @@ throughput_runner(A, B, Rounds, Size) ->
undefined
end,
Prof = prof_end(),
- [{_Node,Socket}] = dig_dist_node_sockets(),
- DistStats = inet:getstat(Socket),
Result#{time := microseconds(Time),
- client_dist_stats => DistStats,
client_msacc_stats => MsaccStats,
client_prof => Prof}.
-dig_dist_node_sockets() ->
- DistCtrl2Node =
- maps:from_list(
- [{DistCtrl, Node}
- || {Node, DistCtrl}
- <- erlang:system_info(dist_ctrl), is_pid(DistCtrl)]),
- TlsDistConnSup = whereis(tls_dist_connection_sup),
- InetCryptoDist = whereis(inet_crypto_dist),
- [NodeSocket
- || {_, Socket} = NodeSocket
- <- erlang:system_info(dist_ctrl), is_port(Socket)]
- ++
- if
- TlsDistConnSup =/= undefined ->
- [case ConnSpec of
- {undefined, ConnSup, supervisor, _} ->
- [{receiver, ReceiverPid, worker, _},
- {sender, SenderPid, worker, _}] =
- lists:sort(supervisor:which_children(ConnSup)),
- {links,ReceiverLinks} =
- process_info(ReceiverPid, links),
- [Socket] = [S || S <- ReceiverLinks, is_port(S)],
- {maps:get(SenderPid, DistCtrl2Node), Socket}
- end
- || ConnSpec <- supervisor:which_children(TlsDistConnSup)];
- InetCryptoDist =/= undefined ->
- [begin
- {monitors,[{process,InputHandler}]} =
- erlang:process_info(DistCtrl, monitors),
- {links,InputHandlerLinks} =
- erlang:process_info(InputHandler, links),
- [Socket] =
- [S || S <- InputHandlerLinks, is_port(S)],
- {Node, Socket}
- end
- || {DistCtrl, Node} <- maps:to_list(DistCtrl2Node)];
- true ->
- []
- end.
-
--ifdef(undefined).
-dig_dist_node_sockets() ->
- [case DistCtrl of
- {_Node,Socket} = NodeSocket when is_port(Socket) ->
- NodeSocket;
- {Node,DistCtrlPid} when is_pid(DistCtrlPid) ->
- [{links,DistCtrlLinks}] = process_info(DistCtrlPid, [links]),
- case [S || S <- DistCtrlLinks, is_port(S)] of
- [Socket] ->
- {Node,Socket};
- [] ->
- [{monitors,[{process,DistSenderPid}]}] =
- process_info(DistCtrlPid, [monitors]),
- [{links,DistSenderLinks}] =
- process_info(DistSenderPid, [links]),
- [Socket] = [S || S <- DistSenderLinks, is_port(S)],
- {Node,Socket}
- end
- end || DistCtrl <- erlang:system_info(dist_ctrl)].
--endif.
-
throughput_server(Pid, N) ->
GC_Before = get_server_gc_info(),
%% dbg:tracer(port, dbg:trace_port(file, "throughput_server_gc.log")),
@@ -917,17 +1312,19 @@ prof_print([]) ->
%%% Test cases helpers
run_nodepair_test(TestFun, Config) ->
- A = proplists:get_value(node_a, Config),
- B = proplists:get_value(node_b, Config),
+ A = proplists:get_value({client,1}, Config),
+ B = proplists:get_value(server, Config),
Prefix = proplists:get_value(ssl_dist_prefix, Config),
Effort = proplists:get_value(effort, Config, 1),
- HA = start_ssl_node_a(Config),
- HB = start_ssl_node_b(Config),
- try TestFun(A, B, Prefix, Effort, HA, HB)
+ HA = start_ssl_node({client,1}, Config),
+ try
+ HB = start_ssl_node(server, Config),
+ try TestFun(A, B, Prefix, Effort, HA, HB)
+ after
+ stop_ssl_node(server, HB, Config)
+ end
after
- stop_ssl_node_a(HA),
- stop_ssl_node_b(HB, Config),
- ok
+ stop_ssl_node({client,1}, HA, Config)
end.
ssl_apply(Handle, M, F, Args) ->
@@ -946,33 +1343,39 @@ ssl_apply(Handle, Fun) ->
Result
end.
-start_ssl_node_a(Config) ->
- Name = proplists:get_value(node_a_name, Config),
- Args = get_node_args(node_a_dist_args, Config),
+start_ssl_node(Spec, Config) ->
+ start_ssl_node(Spec, Config, 0).
+%%
+start_ssl_node({client, N}, Config, Verbose) ->
+ Name = proplists:get_value({client_name, N}, Config),
+ Args = get_node_args({client_dist_args, N}, Config),
Pa = filename:dirname(code:which(?MODULE)),
ssl_dist_test_lib:start_ssl_node(
- Name, "-pa " ++ Pa ++ " " ++ Args).
-
-start_ssl_node_b(Config) ->
- Name = proplists:get_value(node_b_name, Config),
- Args = get_node_args(node_b_dist_args, Config),
+ Name, "-pa " ++ Pa ++ " +Muacul 0 " ++ Args, Verbose);
+start_ssl_node(server, Config, Verbose) ->
+ Name = proplists:get_value(server_name, Config),
+ Args = get_node_args(server_dist_args, Config),
Pa = filename:dirname(code:which(?MODULE)),
ServerNode = proplists:get_value(server_node, Config),
- rpc:call(
+ erpc:call(
ServerNode, ssl_dist_test_lib, start_ssl_node,
- [Name, "-pa " ++ Pa ++ " " ++ Args]).
+ [Name, "-pa " ++ Pa ++ " +Muacul 0 " ++ Args, Verbose]).
-stop_ssl_node_a(HA) ->
- ssl_dist_test_lib:stop_ssl_node(HA).
-
-stop_ssl_node_b(HB, Config) ->
+stop_ssl_node({client, _}, HA, _Config) ->
+ ssl_dist_test_lib:stop_ssl_node(HA);
+stop_ssl_node(server, HB, Config) ->
ServerNode = proplists:get_value(server_node, Config),
- rpc:call(ServerNode, ssl_dist_test_lib, stop_ssl_node, [HB]).
+ erpc:call(ServerNode, ssl_dist_test_lib, stop_ssl_node, [HB]).
get_node_args(Tag, Config) ->
case proplists:get_value(ssl_dist, Config) of
true ->
- proplists:get_value(Tag, Config);
+ case proplists:get_value(ktls, Config, false) of
+ true ->
+ "-ssl_dist_opt client_ktls true server_ktls true ";
+ false ->
+ ""
+ end ++ proplists:get_value(Tag, Config);
false ->
proplists:get_value(ssl_dist_args, Config, "")
end.
@@ -1018,13 +1421,13 @@ elapsed_time(StartTime) ->
microseconds(Time) ->
erlang:convert_time_unit(Time, native, microsecond).
-report(Name, Value, Unit) ->
- ct:pal("~s: ~w ~s", [Name, Value, Unit]),
+report(Name, Value, Suffix) ->
+ ct:pal("~s: ~w ~s", [Name, Value, Suffix]),
ct_event:notify(
#event{
name = benchmark_data,
data = [{value, Value}, {suite, "ssl_dist"}, {name, Name}]}),
- {comment, term_to_string(Value) ++ " " ++ Unit}.
+ {comment, term_to_string(Value) ++ " " ++ Suffix}.
term_to_string(Term) ->
unicode:characters_to_list(
@@ -1032,3 +1435,114 @@ term_to_string(Term) ->
msacc_available() ->
msacc:available().
+
+
+mem_start(HA, HB) ->
+ MemA = ssl_apply(HA, fun mem/0),
+ MemB = ssl_apply(HB, fun mem/0),
+ {MemA, MemB}.
+
+mem_stop(HA, HB, Mem1) ->
+ MemA2 = ssl_apply(HA, fun mem/0),
+ MemB2 = ssl_apply(HB, fun mem/0),
+ mem_result(mem_diff(Mem1, {MemA2, MemB2})).
+
+mem_diff({MemA1, MemB1}, {MemA2, MemB2}) ->
+ {MemA2 - MemA1, MemB2 - MemB1}.
+
+mem_result({MemDiffA, MemDiffB}) ->
+ MemSuffix =
+ io_lib:format(
+ "~.5g|~.5g MByte", [MemDiffA / (1 bsl 20), MemDiffB / (1 bsl 20)]),
+ {round(MemDiffA / (1 bsl 10)), round(MemDiffB / (1 bsl 10)), MemSuffix}.
+
+memory(Type) ->
+ try erlang:memory(Type)
+ catch error : notsup ->
+ 0
+ end.
+
+-ifdef(undefined).
+
+mem() ->
+ lists:foldl(
+ fun ({Type, F}, Acc) ->
+ F*memory(Type) + Acc
+ end, 0,
+ [{total, 1}, {processes, -1}, {atom, -1}, {code, -1},
+ {processes_used, 1}, {atom_used, 1}]).
+
+-else.
+
+mem() ->
+ {_Current, _MaxSince, MaxEver} =
+ traverse(
+ fun mem/3,
+ [erlang:system_info({allocator_sizes, Alloc})
+ || Alloc <- erlang:system_info(alloc_util_allocators)],
+ {0, 0, 0}),
+ MaxEver - memory(code). % Kind of assuming code stays allocated
+
+%% allocator_sizes traversal fun
+mem(
+ T = {instance, _, L}, [], Acc)
+ when is_list(L) ->
+ {tuple_size(T), Acc};
+mem(
+ T = {_, L}, [{instance, _, _}], Acc)
+ when is_list(L) ->
+ {tuple_size(T), Acc};
+mem(
+ T = {blocks, L}, [{_, _}, {instance, _, _}], Acc)
+ when is_list(L) ->
+ {tuple_size(T), Acc};
+mem(
+ T = {_, L}, [{blocks, _}, {_, _}, {instance, _, _}], Acc)
+ when is_list(L) ->
+ {tuple_size(T), Acc};
+mem(
+ {size, Current, MaxSince, MaxEver},
+ [{_, _}, {blocks, _}, {_, _}, {instance, _, _}],
+ {C, S, E}) ->
+ {0, {C + Current, S + MaxSince, E + MaxEver}};
+mem(
+ {size, Current},
+ [{_, _}, {blocks, _}, {_, _}, {instance, _, _}],
+ {C, S, E}) ->
+ %% Use Current as Max since we do not have any Max values
+ %% XXX future improvement when that gets added to
+ %% erlang:system_info(allocator_sizes, _)
+ {0, {C + Current, S + Current, E + Current}};
+mem(_, _, Acc) ->
+ {0, Acc}.
+
+%% Traverse (Fold) over all lists in a deep term;
+%% descend into the selected element of a tuple;
+%% record the descent Path and supply it to Fun
+%%
+%% Acc cannot be an integer
+traverse(Fun, Term, Acc) ->
+ traverse(Fun, Term, [], Acc).
+%%
+traverse(Fun, Term, Path, Acc) ->
+ if
+ is_list(Term) ->
+ traverse_list(Fun, Term, Path, Acc);
+ is_tuple(Term) ->
+ case Fun(Term, Path, Acc) of
+ {0, NewAcc} ->
+ NewAcc;
+ {N, NewAcc} when is_integer(N) ->
+ traverse(Fun, element(N, Term), [Term | Path], NewAcc)
+ end;
+ true ->
+ Acc
+ end.
+
+traverse_list(Fun, [Term | Terms], Path, Acc) ->
+ NewAcc = traverse(Fun, Term, Path, Acc),
+ traverse_list(Fun, Terms, Path, NewAcc);
+traverse_list(_Fun, [], _Path, Acc) ->
+ Acc.
+
+-endif.
diff --git a/lib/ssl/test/ssl_dist_test_lib.erl b/lib/ssl/test/ssl_dist_test_lib.erl
index 90aae473e2..3df721f2d7 100644
--- a/lib/ssl/test/ssl_dist_test_lib.erl
+++ b/lib/ssl/test/ssl_dist_test_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@
-export([tstsrvr_format/2, send_to_tstcntrl/1]).
-export([apply_on_ssl_node/4, apply_on_ssl_node/2]).
--export([stop_ssl_node/1, start_ssl_node/2]).
+-export([stop_ssl_node/1, start_ssl_node/2, start_ssl_node/3]).
%%
-export([cnct2tstsrvr/1]).
@@ -94,23 +94,22 @@ stop_ssl_node(#node_handle{connection_handler = Handler,
erlang:demonitor(Mon, [flush]),
ct:pal("stop_ssl_node/1 ~s Warning ~p ~n", [Name,Error])
end,
- case file:read_file(LogPath) of
- {ok, Binary} ->
- ct:pal("LogPath(~pB) = ~p~n~s", [filelib:file_size(LogPath), LogPath,
- Binary]);
- _ ->
- ok
- end,
+ ssl_test_lib:ct_pal_file(LogPath),
ct:pal("DumpPath(~pB) = ~p~n", [filelib:file_size(DumpPath), DumpPath]).
start_ssl_node(Name, Args) ->
- {ok, LSock} = gen_tcp:listen(0,
- [binary, {packet, 4}, {active, false}]),
+ start_ssl_node(Name, Args, 1).
+%%
+start_ssl_node(Name, Args, Verbose) ->
+ {ok, LSock} =
+ gen_tcp:listen(0, [binary, {packet, 4}, {active, false}]),
{ok, ListenPort} = inet:port(LSock),
{ok, Pwd} = file:get_cwd(),
LogFilePath = filename:join([Pwd, "error_log." ++ Name]),
DumpFilePath = filename:join([Pwd, "erl_crash_dump." ++ Name]),
- CmdLine = mk_node_cmdline(ListenPort, Name, Args, LogFilePath, DumpFilePath),
+ CmdLine =
+ mk_node_cmdline(
+ ListenPort, Name, Args, Verbose, LogFilePath, DumpFilePath),
test_server:format("Attempting to start ssl node ~ts: ~ts~n", [Name, CmdLine]),
case open_port({spawn, CmdLine}, []) of
Port when is_port(Port) ->
@@ -134,7 +133,7 @@ host_name() ->
%% atom_to_list(node())),
Host.
-mk_node_cmdline(ListenPort, Name, Args, LogPath, DumpPath) ->
+mk_node_cmdline(ListenPort, Name, Args, Verbose, LogPath, DumpPath) ->
Static = "-detached -noinput",
Prog = case catch init:get_argument(progname) of
{ok,[[P]]} -> P;
@@ -148,7 +147,7 @@ mk_node_cmdline(ListenPort, Name, Args, LogPath, DumpPath) ->
++ Static ++ " "
++ NameSw ++ " " ++ Name ++ " "
++ "-run application start crypto -run application start public_key "
- ++ "-eval 'net_kernel:verbose(1)' "
+ ++ "-eval 'net_kernel:verbose("++integer_to_list(Verbose)++")' "
++ "-run " ++ atom_to_list(?MODULE) ++ " cnct2tstsrvr "
++ host_name() ++ " "
++ integer_to_list(ListenPort) ++ " "
diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl
index 1bde66a80b..f462fcefad 100644
--- a/lib/ssl/test/ssl_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_handshake_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -52,8 +52,7 @@
decode_empty_server_sni_correctly/1,
select_proper_tls_1_2_rsa_default_hashsign/1,
ignore_hassign_extension_pre_tls_1_2/1,
- signature_algorithms/1,
- encode_decode_srp/1]).
+ signature_algorithms/1]).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -67,8 +66,7 @@ all() -> [decode_hello_handshake,
decode_empty_server_sni_correctly,
select_proper_tls_1_2_rsa_default_hashsign,
ignore_hassign_extension_pre_tls_1_2,
- signature_algorithms,
- encode_decode_srp].
+ signature_algorithms].
%%--------------------------------------------------------------------
init_per_suite(Config) ->
@@ -128,9 +126,9 @@ decode_hello_handshake(_Config) ->
16#00, 16#00, 16#33, 16#74, 16#00, 16#07, 16#06, 16#73,
16#70, 16#64, 16#79, 16#2f, 16#32>>,
- Version = {3, 0},
- {Records, _Buffer} = tls_handshake:get_tls_handshakes(Version, HelloPacket, <<>>,
- default_options_map()),
+ Version = ?SSL_3_0,
+ DefOpts = ssl:update_options([{verify, verify_none}], client, #{}),
+ {Records, _Buffer} = tls_handshake:get_tls_handshakes(Version, HelloPacket, <<>>, DefOpts),
{Hello, _Data} = hd(Records),
Extensions = Hello#server_hello.extensions,
@@ -138,7 +136,7 @@ decode_hello_handshake(_Config) ->
decode_single_hello_extension_correctly(_Config) ->
Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>,
- Extensions = ssl_handshake:decode_extensions(Renegotiation, {3,3}, undefined),
+ Extensions = ssl_handshake:decode_extensions(Renegotiation, ?TLS_1_2, undefined),
#{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions.
decode_supported_elliptic_curves_hello_extension_correctly(_Config) ->
@@ -150,13 +148,13 @@ decode_supported_elliptic_curves_hello_extension_correctly(_Config) ->
Len = ListLen + 2,
Extension = <<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), ?UINT16(ListLen), EllipticCurveList/binary>>,
% after decoding we should see only valid curves
- Extensions = ssl_handshake:decode_hello_extensions(Extension, {3,2}, {3,2}, client),
+ Extensions = ssl_handshake:decode_hello_extensions(Extension, ?TLS_1_1, ?TLS_1_1, client),
#{elliptic_curves := #elliptic_curves{elliptic_curve_list = [?sect233k1, ?sect193r2]}} = Extensions.
decode_unknown_hello_extension_correctly(_Config) ->
FourByteUnknown = <<16#CA,16#FE, ?UINT16(4), 3, 0, 1, 2>>,
Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>,
- Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, {3,2}, {3,2}, client),
+ Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, ?TLS_1_1, ?TLS_1_1, client),
#{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions.
@@ -171,21 +169,21 @@ encode_single_hello_sni_extension_correctly(_Config) ->
decode_single_hello_sni_extension_correctly(_Config) ->
SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08,
$t, $e, $s, $t, $., $c, $o, $m>>,
- Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, client),
+ Decoded = ssl_handshake:decode_hello_extensions(SNI, ?TLS_1_2, ?TLS_1_2, client),
#{sni := #sni{hostname = "test.com"}} = Decoded.
decode_empty_server_sni_correctly(_Config) ->
SNI = <<?UINT16(?SNI_EXT),?UINT16(0)>>,
- Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, server),
+ Decoded = ssl_handshake:decode_hello_extensions(SNI, ?TLS_1_2, ?TLS_1_2, server),
#{sni := #sni{hostname = ""}} = Decoded.
select_proper_tls_1_2_rsa_default_hashsign(_Config) ->
% RFC 5246 section 7.4.1.4.1 tells to use {sha1,rsa} as default signature_algorithm for RSA key exchanges
- {sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,3}),
+ {sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, ?TLS_1_2),
% Older versions use MD5/SHA1 combination
- {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,2}),
- {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,0}).
+ {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, ?TLS_1_1),
+ {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, ?SSL_3_0).
ignore_hassign_extension_pre_tls_1_2(Config) ->
@@ -193,35 +191,10 @@ ignore_hassign_extension_pre_tls_1_2(Config) ->
CertFile = proplists:get_value(certfile, Opts),
[{_, Cert, _}] = ssl_test_lib:pem_to_der(CertFile),
HashSigns = #hash_sign_algos{hash_sign_algos = [{sha512, rsa}, {sha, dsa}, {sha256, rsa}]},
- {sha512, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([{3,3}]), {3,3}),
+ {sha512, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([?TLS_1_2]), ?TLS_1_2),
%%% Ignore
- {md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([{3,2}]), {3,2}),
- {md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([{3,0}]), {3,0}).
-
-encode_decode_srp(_Config) ->
- Exts = #{srp => #srp{username = <<"foo">>},
- sni => #sni{hostname = "bar"},
- renegotiation_info => undefined,
- signature_algs => undefined,
- alpn => undefined,
- next_protocol_negotiation => undefined,
- ec_point_formats => undefined,
- elliptic_curves => undefined
- },
- EncodedExts0 = <<0,20, % Length
- 0,12, % SRP extension
- 0,4, % Length
- 3, % srp_I length
- 102,111,111, % username = "foo"
- 0,0, % SNI extension
- 0,8, % Length
- 0,6, % ServerNameLength
- 0, % NameType (host_name)
- 0,3, % HostNameLength
- 98,97,114>>, % hostname = "bar"
- EncodedExts0 = <<?UINT16(_),EncodedExts/binary>> =
- ssl_handshake:encode_hello_extensions(Exts, {3,3}),
- Exts = ssl_handshake:decode_hello_extensions(EncodedExts, {3,3}, {3,3}, client).
+ {md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([?TLS_1_1]), ?TLS_1_1),
+ {md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([?SSL_3_0]), ?SSL_3_0).
signature_algorithms(Config) ->
Opts = proplists:get_value(server_opts, Config),
@@ -238,16 +211,16 @@ signature_algorithms(Config) ->
{sha512, rsa} = ssl_handshake:select_hashsign(
{HashSigns0, Schemes0},
Cert, ecdhe_rsa,
- tls_v1:default_signature_algs([{3,3}]),
- {3,3}),
+ tls_v1:default_signature_algs([?TLS_1_2]),
+ ?TLS_1_2),
HashSigns1 = #hash_sign_algos{
hash_sign_algos = [{sha, dsa},
{sha256, rsa}]},
{sha256, rsa} = ssl_handshake:select_hashsign(
{HashSigns1, Schemes0},
Cert, ecdhe_rsa,
- tls_v1:default_signature_algs([{3,3}]),
- {3,3}),
+ tls_v1:default_signature_algs([?TLS_1_2]),
+ ?TLS_1_2),
Schemes1 = #signature_algorithms_cert{
signature_scheme_list = [rsa_pkcs1_sha1,
ecdsa_sha1]},
@@ -255,22 +228,22 @@ signature_algorithms(Config) ->
#alert{} = ssl_handshake:select_hashsign(
{HashSigns1, Schemes1},
Cert, ecdhe_rsa,
- tls_v1:default_signature_algs([{3,3}]),
- {3,3}),
+ tls_v1:default_signature_algs([?TLS_1_2]),
+ ?TLS_1_2),
%% No scheme, hashsign is used
{sha256, rsa} = ssl_handshake:select_hashsign(
{HashSigns1, undefined},
Cert, ecdhe_rsa,
- tls_v1:default_signature_algs([{3,3}]),
- {3,3}),
+ tls_v1:default_signature_algs([?TLS_1_2]),
+ ?TLS_1_2),
HashSigns2 = #hash_sign_algos{
hash_sign_algos = [{sha, dsa}]},
%% Signature not supported
#alert{} = ssl_handshake:select_hashsign(
{HashSigns2, Schemes1},
Cert, ecdhe_rsa,
- tls_v1:default_signature_algs([{3,3}]),
- {3,3}).
+ tls_v1:default_signature_algs([?TLS_1_2]),
+ ?TLS_1_2).
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
@@ -280,7 +253,3 @@ is_supported(Hash) ->
Algos = crypto:supports(),
Hashs = proplists:get_value(hashs, Algos),
lists:member(Hash, Hashs).
-
-default_options_map() ->
- Fun = fun (_Key, {Default, _}) -> Default end,
- maps:map(Fun, ?RULES).
diff --git a/lib/ssl/test/ssl_mfl_SUITE.erl b/lib/ssl/test/ssl_mfl_SUITE.erl
index 3ad466590f..0cff561938 100644
--- a/lib/ssl/test/ssl_mfl_SUITE.erl
+++ b/lib/ssl/test/ssl_mfl_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
+-include("ssl_record.hrl").
%% Common test
-export([all/0,
@@ -107,11 +108,10 @@ client_option(Config) when is_list(Config) ->
ok.
%--------------------------------------------------------------------------------
-%% check max_fragment_length option on the server is ignored
-%% and both sides can successfully send > 512 bytes
+%% check default max_fragment_length both sides can successfully send > 512 bytes
server_option(Config) when is_list(Config) ->
Data = "mfl_server_options " ++ lists:duplicate(512, $x),
- run_mfl_handshake(Config, undefined, Data, [], [{max_fragment_length, 512}]).
+ run_mfl_handshake(Config, undefined, Data, [], []).
%--------------------------------------------------------------------------------
%% check max_fragment_length option on the client is accepted and reused
@@ -187,7 +187,7 @@ run_mfl_handshake_continue(Config, MFL) ->
receive {Client, {ext, ClientExt}} ->
ct:log("Client handshake Ext ~p~n", [ClientExt]),
case maps:get(server_hello_selected_version, ClientExt, undefined) of
- {3,4} ->
+ ?TLS_1_3 ->
%% For TLS 1.3 the ssl {handshake, hello} API is inconsistent:
%% the server gets all the extensions CH+EE, but the client only CH
ignore;
diff --git a/lib/ssl/test/ssl_npn_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl
index 26c27b88cb..7cd4818088 100644
--- a/lib/ssl/test/ssl_npn_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -139,10 +139,11 @@ validate_empty_protocols_are_not_allowed(Config) when is_list(Config) ->
[{next_protocols_advertised, [<<"foo/1">>, <<"">>]}])),
{error, {options, {client_preferred_next_protocols, {invalid_protocol, <<>>}}}}
= (catch ssl:connect({127,0,0,1}, 9443,
- [{client_preferred_next_protocols,
+ [{verify, verify_none}, {client_preferred_next_protocols,
{client, [<<"foo/1">>, <<"">>], <<"foox/1">>}}], infinity)),
Option = {client_preferred_next_protocols, {invalid_protocol, <<"">>}},
- {error, {options, Option}} = (catch ssl:connect({127,0,0,1}, 9443, [Option], infinity)).
+ {error, {options, Option}} = (catch ssl:connect({127,0,0,1}, 9443,
+ [{verify, verify_none}, Option], infinity)).
%--------------------------------------------------------------------------------
@@ -154,12 +155,13 @@ validate_empty_advertisement_list_is_allowed(Config) when is_list(Config) ->
validate_advertisement_must_be_a_binary_list(Config) when is_list(Config) ->
Option = {next_protocols_advertised, blah},
- {error, {options, Option}} = (catch ssl:listen(9443, [Option])).
+ {error, {options, Option}} = (catch ssl:listen(9443, [{verify, verify_none}, Option])).
%--------------------------------------------------------------------------------
validate_client_protocols_must_be_a_tuple(Config) when is_list(Config) ->
Option = {client_preferred_next_protocols, [<<"foo/1">>]},
- {error, {options, Option}} = (catch ssl:connect({127,0,0,1}, 9443, [Option])).
+ {error, {options, Option}} = (catch ssl:connect({127,0,0,1}, 9443,
+ [{verify, verify_none}, Option])).
%--------------------------------------------------------------------------------
diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl
index ee8825a724..b097a311eb 100644
--- a/lib/ssl/test/ssl_npn_hello_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -123,12 +123,12 @@ encode_and_decode_npn_server_hello_test(Config) ->
%%--------------------------------------------------------------------
create_server_hello_with_no_advertised_protocols_test(_Config) ->
- Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), #{}),
+ Hello = ssl_handshake:server_hello(<<>>, ?SSL_3_0, create_connection_states(), #{}),
Extensions = Hello#server_hello.extensions,
#{} = Extensions.
%%--------------------------------------------------------------------
create_server_hello_with_advertised_protocols_test(_Config) ->
- Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(),
+ Hello = ssl_handshake:server_hello(<<>>, ?SSL_3_0, create_connection_states(),
#{next_protocol_negotiation => [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]}),
Extensions = Hello#server_hello.extensions,
#{next_protocol_negotiation := [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]} = Extensions.
@@ -171,5 +171,4 @@ create_connection_states() ->
}.
default_options_map() ->
- Fun = fun (_Key, {Default, _}) -> Default end,
- maps:map(Fun, ?RULES).
+ ssl:update_options([{verify, verify_none}], client, #{}).
diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl
index 53c95c0cb7..0c26388e8c 100644
--- a/lib/ssl/test/ssl_pem_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -478,9 +478,9 @@ check_tables(ExpectedTables) ->
true ->
ok;
_ ->
- ?PAL("Mismatch for table ~w", [ActualLabel]),
- ?PAL("Expected = ~w", [ExpectedTableSorted]),
- ?PAL("Actual = ~w", [ActualTableSorted]),
+ ?CT_PAL("Mismatch for table ~w", [ActualLabel]),
+ ?CT_PAL("Expected = ~w", [ExpectedTableSorted]),
+ ?CT_PAL("Actual = ~w", [ActualTableSorted]),
ct:fail({data_mismatch, ActualLabel})
end
end,
@@ -512,7 +512,7 @@ new_root_pem_helper(Config, CleanMode,
%% ConnectedN - state after establishing Nth connection
%% Cleaned - state after periodical cleanup
%% DisconnectedN - state after closing Nth connection
- ?PAL(">>> IntermediateServerKeyId = ~w", [IntermediateServerKeyId]),
+ ?CT_PAL(">>> IntermediateServerKeyId = ~w", [IntermediateServerKeyId]),
{ServerCAFile, ClientConf0, ServerConf, ServerRootCert0, ClientBase, ServerBase} =
create_initial_config(Config),
@@ -691,16 +691,16 @@ create_initial_config(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
#{cert := ServerRootCert0} = SRoot =
public_key:pkix_test_root_cert("OTP test server ROOT",
- [{key, ?KEY(6)}]),
+ [{key, ?KEY(6)}, {digest, sha256}]),
DerConfig =
public_key:pkix_test_data(
#{server_chain =>
#{root => SRoot,
- intermediates => [[{key, ?KEY(5)}]],
- peer => [{key, ?KEY(4)}]},
+ intermediates => [[{key, ?KEY(5)}, {digest, sha256}]],
+ peer => [{key, ?KEY(4)}, {digest, sha256}]},
client_chain =>
- #{root => [{key, ?KEY(1)}],
- intermediates => [[{key, ?KEY(2)}]],
+ #{root => [{key, ?KEY(1)}, {digest, sha256}],
+ intermediates => [[{key, ?KEY(2)}, {digest, sha256} ]],
peer => [{key, ?KEY(3)}]}}),
ClientBase = filename:join(PrivDir, "client_test"),
ServerBase = filename:join(PrivDir, "server_test"),
@@ -725,12 +725,12 @@ overwrite_files_with_new_configuration(ServerRootCert0, ClientBase,
public_key:pkix_test_data(
#{server_chain =>
#{root => #{cert => ServerRootCert1, key => Key},
- intermediates => [[{key, ?KEY(IntermediateServerKey)}]],
- peer => [{key, ?KEY(4)}]},
+ intermediates => [[{key, ?KEY(IntermediateServerKey)}, {digest, sha256}]],
+ peer => [{key, ?KEY(4)}, {digest, sha256} ]},
client_chain =>
- #{root => [{key, ?KEY(1)}],
- intermediates => [[{key, ?KEY(2)}]],
- peer => [{key, ?KEY(3)}]}}),
+ #{root => [{key, ?KEY(1)}, {digest, sha256} ],
+ intermediates => [[{key, ?KEY(2)}, {digest, sha256}]],
+ peer => [{key, ?KEY(3)}, {digest, sha256}]}}),
%% Overwrite old config files
_ = x509_test:gen_pem_config_files(DerConfig1, ClientBase, ServerBase),
ServerRootCert1.
diff --git a/lib/ssl/test/ssl_reject_SUITE.erl b/lib/ssl/test/ssl_reject_SUITE.erl
index 7221b629ac..be79e0543b 100644
--- a/lib/ssl/test/ssl_reject_SUITE.erl
+++ b/lib/ssl/test/ssl_reject_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2021-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2021-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@
-module(ssl_reject_SUITE).
-include_lib("common_test/include/ct.hrl").
--include_lib("ssl/src/ssl_record.hrl").
+-include("ssl_record.hrl").
-include_lib("ssl/src/ssl_alert.hrl").
-include_lib("ssl/src/ssl_handshake.hrl").
@@ -48,15 +48,15 @@
accept_sslv3_record_hello/1
]).
--define(TLS_MAJOR, 3).
--define(SSL_3_0_MAJOR, 3).
--define(SSL_3_0_MINOR, 0).
--define(TLS_1_0_MINOR, 1).
--define(TLS_1_1_MINOR, 2).
--define(TLS_1_2_MINOR, 3).
--define(TLS_1_3_MINOR, 4).
--define(SSL_2_0_MAJOR, 0).
--define(SSL_2_0_MINOR, 1).
+-define(TLS_MAJOR, (element(1, ?TLS_1_2))).
+-define(SSL_3_0_MAJOR, (element(1, ?SSL_3_0))).
+-define(SSL_3_0_MINOR, (element(2, ?SSL_3_0))).
+-define(TLS_1_0_MINOR, (element(2, ?TLS_1_0))).
+-define(TLS_1_1_MINOR, (element(2, ?TLS_1_1))).
+-define(TLS_1_2_MINOR, (element(2, ?TLS_1_2))).
+-define(TLS_1_3_MINOR, (element(2, ?TLS_1_3))).
+-define(SSL_2_0_MAJOR, (element(1, ?SSL_2_0))).
+-define(SSL_2_0_MINOR, (element(2, ?SSL_2_0))).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -184,9 +184,12 @@ accept_sslv3_record_hello(Config) when is_list(Config) ->
Allversions = all_versions(),
+ AllSigAlgs = ssl:signature_algs(all, 'tlsv1.3'),
+
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
- {options, [{versions, Allversions} | ServerOpts]}]),
+ {options, [{versions, Allversions},
+ {signature_algs, AllSigAlgs} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
%% TLS-1.X Hello with SSL-3.0 record version
@@ -194,10 +197,11 @@ accept_sslv3_record_hello(Config) when is_list(Config) ->
{ok, Socket} = gen_tcp:connect(Hostname, Port, [{active, false}]),
gen_tcp:send(Socket, ClientHello),
+ TLS_Major = ?TLS_MAJOR,
case gen_tcp:recv(Socket, 3, 5000) of
%% Minor needs to be a TLS version that is a version
%% above SSL-3.0
- {ok, [?HANDSHAKE, ?TLS_MAJOR, Minor]} when Minor > ?SSL_3_0_MINOR ->
+ {ok, [?HANDSHAKE, TLS_Major, Minor]} when Minor > ?SSL_3_0_MINOR ->
ok;
{error, timeout} ->
ct:fail(ssl3_record_not_accepted)
diff --git a/lib/ssl/test/ssl_renegotiate_SUITE.erl b/lib/ssl/test/ssl_renegotiate_SUITE.erl
index 4b46863415..2a58d5ee5b 100644
--- a/lib/ssl/test/ssl_renegotiate_SUITE.erl
+++ b/lib/ssl/test/ssl_renegotiate_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include("ssl_record.hrl").
%% Common test
-export([all/0,
@@ -87,11 +88,10 @@ all() ->
groups() ->
[{'dtlsv1.2', [], renegotiate_tests()},
- {'dtlsv1', [], renegotiate_tests()},
- {'tlsv1.3', [], renegotiate_tests()},
- {'tlsv1.2', [], renegotiate_tests()},
- {'tlsv1.1', [], renegotiate_tests()},
- {'tlsv1', [], renegotiate_tests()}
+ {'dtlsv1', [], renegotiate_tests()},
+ {'tlsv1.2', [], renegotiate_tests()},
+ {'tlsv1.1', [], renegotiate_tests()},
+ {'tlsv1', [], renegotiate_tests()}
].
renegotiate_tests() ->
@@ -107,17 +107,6 @@ renegotiate_tests() ->
renegotiate_dos_mitigate_passive,
renegotiate_dos_mitigate_absolute].
-ssl3_renegotiate_tests() ->
- [client_renegotiate,
- server_renegotiate,
- client_renegotiate_reused_session,
- server_renegotiate_reused_session,
- client_no_wrap_sequence_number,
- server_no_wrap_sequence_number,
- renegotiate_dos_mitigate_active,
- renegotiate_dos_mitigate_passive,
- renegotiate_dos_mitigate_absolute].
-
init_per_suite(Config) ->
catch crypto:stop(),
try crypto:start() of
@@ -518,9 +507,7 @@ renegotiate_rejected(Socket) ->
ok.
%% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast
-treashold(N, {3,0}) ->
- (N div 2) + 1;
-treashold(N, {3,1}) ->
+treashold(N, ?TLS_1_0) ->
(N div 2) + 1;
treashold(N, _) ->
N + 1.
diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl
index b1f093351e..b94932bfc6 100644
--- a/lib/ssl/test/ssl_session_SUITE.erl
+++ b/lib/ssl/test/ssl_session_SUITE.erl
@@ -668,9 +668,9 @@ faulty_client(Host, Port) ->
encode_client_hello(CH, Random) ->
- HSBin = tls_handshake:encode_handshake(CH, {3,3}),
+ HSBin = tls_handshake:encode_handshake(CH, ?TLS_1_2),
CS = connection_states(Random),
- {Encoded, _} = tls_record:encode_handshake(HSBin, {3,3}, CS),
+ {Encoded, _} = tls_record:encode_handshake(HSBin, ?TLS_1_2, CS),
Encoded.
client_hello(Random) ->
@@ -746,7 +746,7 @@ client_hello(Random) ->
srp =>
undefined},
- #client_hello{client_version = {3,3},
+ #client_hello{client_version = ?TLS_1_2,
random = Random,
session_id = crypto:strong_rand_bytes(32),
cipher_suites = CipherSuites,
diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl
index 0c981b5b83..6e07a845e9 100644
--- a/lib/ssl/test/ssl_session_ticket_SUITE.erl
+++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl
@@ -1,6 +1,4 @@
%%
-%% %CopyrightBegin%
-%%
%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
@@ -45,6 +43,8 @@
ticket_reuse_anti_replay/1,
ticket_reuse_anti_replay_server_restart/0,
ticket_reuse_anti_replay_server_restart/1,
+ ticket_reuse_anti_replay_server_restart_reused_seed/0,
+ ticket_reuse_anti_replay_server_restart_reused_seed/1,
basic_stateful_stateless/0,
basic_stateful_stateless/1,
basic_stateless_stateful/0,
@@ -78,7 +78,9 @@
early_data_basic/0,
early_data_basic/1,
early_data_basic_auth/0,
- early_data_basic_auth/1]).
+ early_data_basic_auth/1,
+ stateless_multiple_servers/0,
+ stateless_multiple_servers/1]).
-include("tls_handshake.hrl").
@@ -98,13 +100,13 @@ all() ->
groups() ->
[{'tlsv1.3', [], [{group, stateful},
{group, stateless},
+ {group, stateful_with_cert},
+ {group, stateless_with_cert},
{group, mixed}]},
{stateful, [], session_tests()},
- {stateless, [], session_tests() ++
- [ticketage_smaller_than_windowsize_anti_replay,
- ticketage_bigger_than_windowsize_anti_replay,
- ticketage_out_of_lifetime_anti_replay, ticket_reuse_anti_replay,
- ticket_reuse_anti_replay_server_restart]},
+ {stateless, [], session_tests() ++ anti_replay_tests()},
+ {stateful_with_cert, [], session_tests()},
+ {stateless_with_cert, [], session_tests() ++ anti_replay_tests()},
{mixed, [], mixed_tests()}].
session_tests() ->
@@ -121,6 +123,16 @@ session_tests() ->
early_data_basic,
early_data_basic_auth].
+anti_replay_tests() ->
+ [
+ ticketage_smaller_than_windowsize_anti_replay,
+ ticketage_bigger_than_windowsize_anti_replay,
+ ticketage_out_of_lifetime_anti_replay, ticket_reuse_anti_replay,
+ ticket_reuse_anti_replay_server_restart,
+ ticket_reuse_anti_replay_server_restart_reused_seed,
+ stateless_multiple_servers
+ ].
+
mixed_tests() ->
[
basic_stateful_stateless,
@@ -145,10 +157,12 @@ end_per_suite(_Config) ->
ssl:stop(),
application:stop(crypto).
-init_per_group(stateful, Config) ->
- [{server_ticket_mode, stateful} | proplists:delete(server_ticket_mode, Config)];
-init_per_group(stateless, Config) ->
- [{server_ticket_mode, stateless} | proplists:delete(server_ticket_mode, Config)];
+init_per_group(GroupName, Config)
+ when GroupName == stateful
+ orelse GroupName == stateless
+ orelse GroupName == stateful_with_cert
+ orelse GroupName == stateless_with_cert ->
+ [{server_ticket_mode, GroupName} | proplists:delete(server_ticket_mode, Config)];
init_per_group(GroupName, Config) ->
ssl_test_lib:init_per_group(GroupName, Config).
@@ -164,6 +178,7 @@ init_per_testcase(_, Config) ->
end_per_testcase(_TestCase, Config) ->
application:unset_env(ssl, server_session_ticket_max_early_data),
+ application:unset_env(ssl, server_session_ticket_lifetime),
Config.
%%--------------------------------------------------------------------
@@ -202,6 +217,15 @@ basic(Config) when is_list(Config) ->
{from, self()}, {options, ClientOpts}]),
ssl_test_lib:check_result(Server0, ok, Client0, ok),
+ Server0 ! get_socket,
+ SSocket0 =
+ receive
+ {Server0, {socket, Socket0}} ->
+ Socket0
+ end,
+
+ {ok, ClientCert} = ssl:peercert(SSocket0),
+
Server0 ! {listen, {mfa, {ssl_test_lib,
verify_active_session_resumption,
[true]}}},
@@ -220,6 +244,21 @@ basic(Config) when is_list(Config) ->
{from, self()}, {options, ClientOpts}]),
ssl_test_lib:check_result(Server0, ok, Client1, ok),
+ Server0 ! get_socket,
+ SSocket1 =
+ receive
+ {Server0, {socket, Socket1}} ->
+ Socket1
+ end,
+
+ ExpectedPeercert = case ServerTicketMode of
+ stateful_with_cert -> {ok, ClientCert};
+ stateless_with_cert -> {ok, ClientCert};
+ _ -> {error, no_peercert}
+ end,
+
+ ExpectedPeercert = ssl:peercert(SSocket1),
+
process_flag(trap_exit, false),
ssl_test_lib:close(Server0),
ssl_test_lib:close(Client1).
@@ -243,7 +282,7 @@ ticketage_smaller_than_windowsize_anti_replay(Config) when is_list(Config) ->
ticketage_bigger_than_windowsize_anti_replay() ->
[{doc, "Session resumption with stateless tickets and anti_replay enabled."
"Fresh ClientHellos."
- "Ticket age bigger than windowsize. 0-RTT is expected to fail."
+ "Ticket age bigger than windowsize. 0-RTT is expected to succeed."
"(Erlang client - Erlang server)"}].
ticketage_bigger_than_windowsize_anti_replay(Config) when is_list(Config) ->
WindowSize = 3,
@@ -252,10 +291,10 @@ ticketage_bigger_than_windowsize_anti_replay(Config) when is_list(Config) ->
ssl_test_lib:check_result(Server0, ok, Client0, ok),
Client1 = anti_replay_helper_connect(Server0, Client0, Port0, ClientNode,
Hostname, ClientOpts,
- {seconds, WindowSize + 2}, false),
+ {seconds, WindowSize + 2}, true),
Client2 = anti_replay_helper_connect(Server0, Client0, Port0, ClientNode,
Hostname, ClientOpts,
- {seconds, 2*WindowSize + 2}, false),
+ {seconds, 2*WindowSize + 2}, true),
process_flag(trap_exit, false),
[ssl_test_lib:close(A) || A <- [Server0, Client0, Client1, Client2]].
@@ -325,6 +364,36 @@ ticket_reuse_anti_replay_server_restart(Config) when is_list(Config) ->
process_flag(trap_exit, false),
[ssl_test_lib:close(A) || A <- [Server0, Client2, Server1]].
+ticket_reuse_anti_replay_server_restart_reused_seed() ->
+ [{doc, "Verify 2 connection attempts with same stateless tickets "
+ "and server restart between, with the server using the same session "
+ "ticket encryption seed between restarts. Second attempt is expected to "
+ "fail as long as the Bloom filter window overlaps with startup time."
+ }].
+ticket_reuse_anti_replay_server_restart_reused_seed(Config) when is_list(Config) ->
+ WindowSize = 10,
+ Seed = crypto:strong_rand_bytes(32),
+ Config1 = [{server_ticket_seed, Seed} | Config],
+ {Server1 , Port1} = anti_replay_helper_start_server(Config1, WindowSize),
+ {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ClientOpts1 = [{session_tickets, manual},
+ {versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0],
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% full handshake
+ verify_active_session_resumption,
+ [false, wait_reply, {tickets, 1}]}},
+ {from, self()}, {options, ClientOpts1}]),
+ [Ticket] = ssl_test_lib:check_tickets(Client1),
+ ssl_test_lib:check_result(Server1, ok),
+ ClientOpts2 = [{use_ticket, [Ticket]} | ClientOpts1],
+ {Server2, Port2} = anti_replay_helper_start_server(Config1, WindowSize),
+ Client2 = anti_replay_helper_connect(Server2, Client1, Port2, ClientNode,
+ Hostname, ClientOpts2, 0, false, false),
+ process_flag(trap_exit, false),
+ [ssl_test_lib:close(A) || A <- [Server1, Client2, Server2]].
+
anti_replay_helper_init(Config, Mode, WindowSize) ->
DefaultLifetime = ssl_config:get_ticket_lifetime(),
anti_replay_helper_init(Config, Mode, WindowSize, DefaultLifetime).
@@ -360,9 +429,17 @@ anti_replay_helper_start_server(Config, WindowSize) ->
{_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+ ServerTicketSeed =
+ case proplists:get_value(server_ticket_seed, Config) of
+ undefined ->
+ [];
+ Seed ->
+ [{stateless_tickets_seed, Seed}]
+ end,
ServerOpts = [{session_tickets, ServerTicketMode},
{anti_replay, {WindowSize, 5, 72985}},
- {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0
+ ] ++ ServerTicketSeed,
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -1227,6 +1304,69 @@ early_data_basic_auth(Config) when is_list(Config) ->
ssl_test_lib:close(Server0),
ssl_test_lib:close(Client1).
+stateless_multiple_servers() ->
+ [{doc, "Test session resumption with session tickets, resuming on different server"}].
+stateless_multiple_servers(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Seed = crypto:strong_rand_bytes(64),
+
+ %% Configure session tickets
+ ClientOpts = [{session_tickets, auto},
+ {versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0],
+ ServerOpts = [{session_tickets, stateless},
+ {stateless_tickets_seed, Seed},
+ {versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ Server1 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {options, ServerOpts}]),
+ Port1 = ssl_test_lib:inet_port(Server1),
+
+ %% Store ticket from first connection to server 0
+ Client0 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket when connecting to server 1
+ Client1 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts}]),
+ ssl_test_lib:check_result(Server1, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Server1),
+ ssl_test_lib:close(Client1).
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl
index ab243bc6c9..3cc4d49684 100644
--- a/lib/ssl/test/ssl_sni_SUITE.erl
+++ b/lib/ssl/test/ssl_sni_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -112,8 +112,8 @@ init_per_suite(Config0) ->
#{server_config := LServerConf,
client_config := LClientConf}} = ssl_test_lib:make_rsa_sni_configs(),
%% RSA certs files needed by *dot cases
- ssl_test_lib:make_rsa_cert([{client_opts, ClientConf},
- {client_local_opts, LClientConf},
+ ssl_test_lib:make_rsa_cert([{client_opts, [{verify, verify_peer} | ClientConf]},
+ {client_local_opts, [{verify, verify_peer} | LClientConf]},
{sni_server_opts, [{sni_hosts, [{Hostname, ServerConf}]} | LServerConf]}
| Config0])
catch _:_ ->
@@ -197,8 +197,8 @@ sni_no_match_fun(Config) ->
dns_name(Config) ->
Hostname = "OTP.test.server",
- #{server_config := ServerConf,
- client_config := ClientConf} =
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain =>
#{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
@@ -211,6 +211,9 @@ dns_name(Config) ->
#{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config),
successfull_connect(ServerConf, [{verify, verify_peer},
{server_name_indication, Hostname} | ClientConf], undefined, Config),
@@ -223,8 +226,8 @@ ip_fallback(Config) ->
Hostname = net_adm:localhost(),
{ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()),
IPStr = tuple_to_list(IP),
- #{server_config := ServerConf,
- client_config := ClientConf} =
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain =>
#{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
@@ -238,14 +241,17 @@ ip_fallback(Config) ->
#{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config).
no_ip_fallback(Config) ->
Hostname = net_adm:localhost(),
{ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()),
- #{server_config := ServerConf,
- client_config := ClientConf} =
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain =>
#{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
@@ -259,13 +265,16 @@ no_ip_fallback(Config) ->
#{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config).
dns_name_reuse(Config) ->
SNIHostname = "OTP.test.server",
- #{server_config := ServerConf,
- client_config := ClientConf} =
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain =>
#{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
@@ -280,39 +289,42 @@ dns_name_reuse(Config) ->
#{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
-
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
+
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config),
-
- Server =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, session_info_result, []}},
{options, ServerConf}]),
Port = ssl_test_lib:inet_port(Server),
Client0 =
- ssl_test_lib:start_client([{node, ClientNode},
+ ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, [{verify, verify_peer},
- {server_name_indication, SNIHostname} | ClientConf]}]),
+ {from, self()}, {options, [{verify, verify_peer},
+ {server_name_indication, SNIHostname} | ClientConf]}]),
receive
{Server, _} ->
ok
end,
-
+
Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
+
%% Make sure session is registered
ct:sleep(1000),
-
+
Client1 =
ssl_test_lib:start_client_error([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, session_info_result, []}},
{from, self()}, {options, [{verify, verify_peer} | ClientConf]}]),
-
+
ssl_test_lib:check_client_alert(Client1, handshake_failure),
ssl_test_lib:close(Client0).
@@ -371,8 +383,8 @@ customize_hostname_check(Config) when is_list(Config) ->
sni_no_trailing_dot() ->
[{doc,"Test that sni may not include a triling dot"}].
sni_no_trailing_dot(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(sni_server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -395,8 +407,8 @@ hostname_trailing_dot() ->
[{doc,"Test that fallback sni removes trailing dot of hostname"}].
hostname_trailing_dot(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(sni_server_opts, Config),
{ClientNode, ServerNode, Hostname0} = ssl_test_lib:run_where(Config),
case trailing_dot_hostname(Hostname0) of
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 4b0618bc86..64ee3c9d0d 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -25,6 +25,9 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("ssl/src/tls_handshake_1_3.hrl").
+-include_lib("ssl/src/ssl_cipher.hrl").
+-include_lib("ssl/src/ssl_internal.hrl").
+-include_lib("ssl/src/ssl_record.hrl").
-export([clean_start/0,
clean_start/1,
@@ -42,6 +45,7 @@
default_tls_version/1,
check_sane_openssl_renegotiate/2,
check_openssl_npn_support/1,
+ check_sane_openssl_dsa/1,
start_server/1,
start_server/2,
start_client/1,
@@ -116,7 +120,6 @@
session_id/1,
update_keys/2,
sanity_check/2,
- oscp_responder/6,
supported_eccs/1,
no_result/1,
receive_tickets/1,
@@ -149,6 +152,7 @@
]).
-export([tls_version/1,
+ n_version/1,
is_protocol_version/1,
is_tls_version/1,
is_dtls_version/1,
@@ -182,8 +186,8 @@
default_cert_chain_conf/0,
cert_options/1,
rsa_non_signed_suites/1,
+ dsa_suites/1,
ecdh_dh_anonymous_suites/1,
- ecdsa_suites/1,
der_to_pem/2,
pem_to_der/1,
appropriate_sha/1,
@@ -191,7 +195,11 @@
format_cert/1,
ecdsa_conf/0,
eddsa_conf/0,
- default_ecc_cert_chain_conf/1
+ default_ecc_cert_chain_conf/1,
+ sig_algs/2,
+ all_sig_algs/0,
+ all_1_3_sig_algs/0,
+ all_1_2_sig_algs/0
]).
-export([maybe_force_ipv4/1,
@@ -214,8 +222,16 @@
portable_cmd/2,
portable_open_port/2,
close_port/1,
- verify_early_data/1
+ verify_early_data/1,
+ trace/0,
+ ct_pal_file/1
]).
+%% Tracing
+-export([handle_trace/3]).
+
+-export([ktls_os/0,
+ ktls_set_ulp/2,
+ ktls_set_cipher/4]).
-record(sslsocket, { fd = nil, pid = nil}).
-define(SLEEP, 1000).
@@ -444,9 +460,79 @@ default_ecc_cert_chain_conf(eddsa_1_3) ->
default_ecc_cert_chain_conf(_) ->
default_cert_chain_conf().
+sig_algs(rsa_pss_pss, _) ->
+ [{signature_algs, [rsa_pss_pss_sha512,
+ rsa_pss_pss_sha384,
+ rsa_pss_pss_sha256]}];
+sig_algs(rsa_pss_rsae, _) ->
+ [{signature_algs, [rsa_pss_rsae_sha512,
+ rsa_pss_rsae_sha384,
+ rsa_pss_rsae_sha256]}];
+sig_algs(rsa, Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
+ [{signature_algs, [rsa_pss_rsae_sha512,
+ rsa_pss_rsae_sha384,
+ rsa_pss_rsae_sha256,
+ {sha512, rsa},
+ {sha384, rsa},
+ {sha256, rsa},
+ {sha, rsa}
+ ]}];
+sig_algs(ecdsa, Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
+ [{signature_algs, [
+ {sha512, ecdsa},
+ {sha384, ecdsa},
+ {sha256, ecdsa},
+ {sha, ecdsa}]}];
+sig_algs(dsa, Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
+ [{signature_algs, [{sha,dsa}]}];
+sig_algs(_,_) ->
+ [].
+
+all_sig_algs() ->
+ {signature_algs, list_1_3_sig_algs() ++ list_common_sig_algs() ++ list_1_2_sig_algs()}.
+
+all_1_3_sig_algs() ->
+ {signature_algs, list_1_3_sig_algs() ++ list_common_sig_algs()}.
+
+all_1_2_sig_algs() ->
+ {signature_algs, list_common_sig_algs() ++ list_1_2_sig_algs()}.
+
%%====================================================================
%% Internal functions
%%====================================================================
+list_1_3_sig_algs() ->
+ [
+ eddsa_ed25519,
+ eddsa_ed448,
+ ecdsa_secp521r1_sha512,
+ ecdsa_secp384r1_sha384,
+ ecdsa_secp256r1_sha256
+ ].
+
+list_common_sig_algs() ->
+ [
+ rsa_pss_pss_sha512,
+ rsa_pss_pss_sha384,
+ rsa_pss_pss_sha256,
+ rsa_pss_rsae_sha512,
+ rsa_pss_rsae_sha384,
+ rsa_pss_rsae_sha256
+ ].
+
+list_1_2_sig_algs() ->
+ [
+ {sha512, ecdsa},
+ {sha512, rsa},
+ {sha384, ecdsa},
+ {sha384, rsa},
+ {sha256, ecdsa},
+ {sha256, rsa},
+ {sha224, ecdsa},
+ {sha224, rsa},
+ {sha, ecdsa},
+ {sha, rsa},
+ {sha, dsa}
+ ].
%% For now always run locally
run_where(_) ->
@@ -503,7 +589,7 @@ run_server(Opts) ->
Options = proplists:get_value(options, Opts),
Pid = proplists:get_value(from, Opts),
Transport = proplists:get_value(transport, Opts, ssl),
- ?LOG("~nssl:listen(~p, ~p)~n", [Port, format_options(Options)]),
+ ?CT_LOG("~nssl:listen(~p, ~p)~n", [Port, format_options(Options)]),
case Transport:listen(Port, Options) of
{ok, ListenSocket} ->
Pid ! {listen, up},
@@ -526,11 +612,11 @@ run_server(ListenSocket, Opts, N) ->
run_server(ListenSocket, Opts, N-1).
do_run_server(_, {error, _} = Result, Opts) ->
- ?LOG("Server error result ~p~n", [Result]),
+ ?CT_LOG("Server error result ~p~n", [Result]),
Pid = proplists:get_value(from, Opts),
Pid ! {self(), Result};
do_run_server(_, ok = Result, Opts) ->
- ?LOG("Server cancel result ~p~n", [Result]),
+ ?CT_LOG("Server cancel result ~p~n", [Result]),
Pid = proplists:get_value(from, Opts),
Pid ! {self(), Result};
do_run_server(ListenSocket, AcceptSocket, Opts) ->
@@ -541,7 +627,7 @@ do_run_server(ListenSocket, AcceptSocket, Opts) ->
no_result_msg ->
ok;
Msg ->
- ?LOG("~nServer Msg: ~p ~n", [Msg]),
+ ?CT_LOG("~nServer Msg: ~p ~n", [Msg]),
case lists:member(return_socket, Opts) of
true -> Pid ! {self(), {Msg, AcceptSocket}};
false -> Pid ! {self(), Msg}
@@ -552,14 +638,14 @@ do_run_server(ListenSocket, AcceptSocket, Opts) ->
server_apply_mfa(_, undefined) ->
no_result_msg;
server_apply_mfa(AcceptSocket, {Module, Function, Args}) ->
- ?LOG("~nServer: apply(~p,~p,~p)~n",
+ ?CT_LOG("~nServer: apply(~p,~p,~p)~n",
[Module, Function, [AcceptSocket | Args]]),
apply(Module, Function, [AcceptSocket | Args]).
client_apply_mfa(_, undefined) ->
no_result_msg;
client_apply_mfa(AcceptSocket, {Module, Function, Args}) ->
- ?LOG("~nClient: apply(~p,~p,~p)~n",
+ ?CT_LOG("~nClient: apply(~p,~p,~p)~n",
[Module, Function, [AcceptSocket | Args]]),
apply(Module, Function, [AcceptSocket | Args]).
@@ -567,7 +653,7 @@ client_apply_mfa(AcceptSocket, {Module, Function, Args}) ->
do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid) ->
receive
{data, Data} ->
- ?LOG("[server] Send: ~p~n", [Data]),
+ ?CT_LOG("[server] Send: ~p~n", [Data]),
case Transport:send(AcceptSocket, Data) of
ok ->
Pid ! {self(), ok};
@@ -578,17 +664,17 @@ do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid) ->
{active_receive, Data} ->
case active_recv(AcceptSocket, length(Data)) of
ReceivedData ->
- ?LOG("[server] Received: ~p~n", [Data]),
+ ?CT_LOG("[server] Received: ~p~n", [Data]),
Pid ! {self(), ReceivedData}
end,
do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid);
{update_keys, Type} ->
case ssl:update_keys(AcceptSocket, Type) of
ok ->
- ?LOG("[server] Update keys: ~p", [Type]),
+ ?CT_LOG("[server] Update keys: ~p", [Type]),
Pid ! {self(), ok};
{error, Reason} ->
- ?LOG("[server] Update keys failed: ~p", [Type]),
+ ?CT_LOG("[server] Update keys failed: ~p", [Type]),
Pid ! {self(), Reason}
end,
do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid);
@@ -600,10 +686,10 @@ do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid) ->
{listen, MFA} ->
run_server(ListenSocket, [MFA | proplists:delete(mfa, Opts)]);
close ->
- ?LOG("~nServer closing~n", []),
+ ?CT_LOG("~nServer closing~n", []),
Result = Transport:close(AcceptSocket),
Result1 = Transport:close(ListenSocket),
- ?LOG("~nResult ~p : ~p ~n", [Result, Result1])
+ ?CT_LOG("~nResult ~p : ~p ~n", [Result, Result1])
end.
%%% To enable to test with s_client -reconnect
@@ -622,35 +708,35 @@ connect(#sslsocket{} = ListenSocket, Opts) ->
AcceptSocket
end;
connect(ListenSocket, _Opts) ->
- ?LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
+ ?CT_LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
{ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
AcceptSocket.
connect(_, _, 0, AcceptSocket, _, _, _) ->
AcceptSocket;
connect(ListenSocket, Node, _N, _, Timeout, SslOpts, cancel) ->
- ?LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
+ ?CT_LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
- ?LOG("~nssl:handshake(~p,~p,~p)~n", [AcceptSocket, format_options(SslOpts),Timeout]),
+ ?CT_LOG("~nssl:handshake(~p,~p,~p)~n", [AcceptSocket, format_options(SslOpts),Timeout]),
case ssl:handshake(AcceptSocket, SslOpts, Timeout) of
{ok, Socket0, Ext} ->
- ?LOG("Ext ~p:~n", [Ext]),
- ?LOG("~nssl:handshake_cancel(~p)~n", [Socket0]),
+ ?CT_LOG("Ext ~p:~n", [Ext]),
+ ?CT_LOG("~nssl:handshake_cancel(~p)~n", [Socket0]),
ssl:handshake_cancel(Socket0);
Result ->
- ?LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
+ ?CT_LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
Result
end;
connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts0) ->
- ?LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
+ ?CT_LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
- ?LOG("~nssl:handshake(~p,~p,~p)~n", [AcceptSocket, SslOpts,Timeout]),
+ ?CT_LOG("~nssl:handshake(~p,~p,~p)~n", [AcceptSocket, SslOpts,Timeout]),
case ssl:handshake(AcceptSocket, SslOpts, Timeout) of
{ok, Socket0, Ext} ->
[_|_] = maps:get(sni, Ext),
- ?LOG("Ext ~p:~n", [Ext]),
+ ?CT_LOG("Ext ~p:~n", [Ext]),
ContOpts = case lists:keytake(want_ext, 1, ContOpts0) of
{value, {_, WantExt}, ContOpts1} ->
if is_pid(WantExt) ->
@@ -662,34 +748,34 @@ connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts0) ->
_ ->
ContOpts0
end,
- ?LOG("~nssl:handshake_continue(~p,~p,~p)~n", [Socket0, ContOpts,Timeout]),
+ ?CT_LOG("~nssl:handshake_continue(~p,~p,~p)~n", [Socket0, ContOpts,Timeout]),
case ssl:handshake_continue(Socket0, ContOpts, Timeout) of
{ok, Socket} ->
connect(ListenSocket, Node, N-1, Socket, Timeout, SslOpts, ContOpts0);
Error ->
- ?LOG("~nssl:handshake_continue@~p ret ~p",[Node,Error]),
+ ?CT_LOG("~nssl:handshake_continue@~p ret ~p",[Node,Error]),
Error
end;
Result ->
- ?LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
+ ?CT_LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
Result
end;
connect(ListenSocket, Node, N, _, Timeout, [], ContOpts) ->
- ?LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
+ ?CT_LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
- ?LOG("~nssl:handshake(~p, ~p)~n", [AcceptSocket, Timeout]),
+ ?CT_LOG("~nssl:handshake(~p, ~p)~n", [AcceptSocket, Timeout]),
case ssl:handshake(AcceptSocket, Timeout) of
{ok, Socket} ->
connect(ListenSocket, Node, N-1, Socket, Timeout, [], ContOpts);
Result ->
- ?LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
+ ?CT_LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
Result
end;
connect(ListenSocket, _Node, _, _, Timeout, Opts, _) ->
- ?LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
+ ?CT_LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
- ?LOG("ssl:handshake(~p,~p, ~p)~n", [AcceptSocket, Opts, Timeout]),
+ ?CT_LOG("ssl:handshake(~p,~p, ~p)~n", [AcceptSocket, Opts, Timeout]),
ssl:handshake(AcceptSocket, Opts, Timeout),
AcceptSocket.
@@ -715,7 +801,7 @@ transport_accept_abuse(Opts) ->
Options = proplists:get_value(options, Opts),
Pid = proplists:get_value(from, Opts),
Transport = proplists:get_value(transport, Opts, ssl),
- ?LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
+ ?CT_LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
{ok, ListenSocket} = Transport:listen(Port, Options),
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
@@ -729,7 +815,7 @@ transport_switch_control(Opts) ->
Options = proplists:get_value(options, Opts),
Pid = proplists:get_value(from, Opts),
Transport = proplists:get_value(transport, Opts, ssl),
- ?LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
+ ?CT_LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
{ok, ListenSocket} = Transport:listen(Port, Options),
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
@@ -841,15 +927,6 @@ init_openssl_server(Mode, ResponderPort, Options) when Mode == openssl_ocsp orel
Pid ! {self(), {port, Port}},
openssl_server_loop(Pid, SslPort, Args).
-oscp_responder(Port, Index, CACerts, Cert, Key, Starter) ->
- Args = ["ocsp", "-index", Index, "-CA", CACerts, "-rsigner", Cert,
- "-rkey", Key, "-port", erlang:integer_to_list(Port)],
- Responder = portable_open_port("openssl", Args),
- wait_for_openssl_server(Port, tls),
-
- openssl_server_loop(Starter, Responder, []).
-
-
openssl_dtls_opt('dtlsv1.2') ->
["-dtls"];
openssl_dtls_opt(_Other) ->
@@ -860,34 +937,34 @@ openssl_server_loop(Pid, SslPort, Args) ->
{data, Data} ->
case port_command(SslPort, Data, [nosuspend]) of
true ->
- ?LOG("[openssl server] Send data: ~p~n", [Data]),
+ ?CT_LOG("[openssl server] Send data: ~p~n", [Data]),
Pid ! {self(), ok};
_Else ->
- ?LOG("[openssl server] Send failed, data: ~p~n", [Data]),
+ ?CT_LOG("[openssl server] Send failed, data: ~p~n", [Data]),
Pid ! {self(), {error, port_command_failed}}
end,
openssl_server_loop(Pid, SslPort, Args);
{active_receive, Data} ->
case active_recv(SslPort, length(Data)) of
ReceivedData ->
- ?LOG("[openssl server] Received: ~p~n", [Data]),
+ ?CT_LOG("[openssl server] Received: ~p~n", [Data]),
Pid ! {self(), ReceivedData}
end,
openssl_server_loop(Pid, SslPort, Args);
{update_keys, Type} ->
case Type of
write ->
- ?LOG("[openssl server] Update keys: ~p", [Type]),
+ ?CT_LOG("[openssl server] Update keys: ~p", [Type]),
true = port_command(SslPort, "k", [nosuspend]),
Pid ! {self(), ok};
read_write ->
- ?LOG("[openssl server] Update keys: ~p", [Type]),
+ ?CT_LOG("[openssl server] Update keys: ~p", [Type]),
true = port_command(SslPort, "K", [nosuspend]),
Pid ! {self(), ok}
end,
openssl_server_loop(Pid, SslPort, Args);
close ->
- ?LOG("~n[openssl server] Server closing~n", []),
+ ?CT_LOG("~n[openssl server] Server closing~n", []),
catch port_close(SslPort);
{ssl_closed, _Socket} ->
%% TODO
@@ -896,15 +973,19 @@ openssl_server_loop(Pid, SslPort, Args) ->
start_openssl_client(Args0, Config) ->
{ClientNode, _, Hostname} = run_where(Config),
- ClientOpts = get_client_opts(Config),
+
+ %% io:format("~p:~p: ~p~n",[?MODULE, ?LINE, Args0]),
+ %% io:format("~p:~p: ~p~n",[?MODULE, ?LINE, Config]),
+
+ ClientOpts0 = get_client_opts(Config),
+ ClientOpts = proplists:get_value(options, Args0, []) ++ ClientOpts0,
DefaultVersions = default_tls_version(ClientOpts),
[Version | _] = proplists:get_value(versions, ClientOpts, DefaultVersions),
Node = proplists:get_value(node, Args0, ClientNode),
- Args = [{from, self()},
- {host, Hostname},
- {options, ClientOpts} | Args0],
+ Args = [{from, self()}, {host, Hostname} | ClientOpts ++ Args0],
- Result = spawn_link(Node, ?MODULE, init_openssl_client, [[{version, Version} | lists:delete(return_port, Args)]]),
+ Result = spawn_link(Node, ?MODULE, init_openssl_client,
+ [[{version, Version} | lists:delete(return_port, Args)]]),
receive
{connected, OpenSSLPort} ->
case lists:member(return_port, Args) of
@@ -937,17 +1018,17 @@ openssl_client_loop_core(Pid, SslPort, Args) ->
{data, Data} ->
case port_command(SslPort, Data, [nosuspend]) of
true ->
- ?LOG("[openssl client] Send data: ~p~n", [Data]),
+ ?CT_LOG("[openssl client] Send data: ~p~n", [Data]),
Pid ! {self(), ok};
_Else ->
- ?LOG("[openssl client] Send failed, data: ~p~n", [Data]),
+ ?CT_LOG("[openssl client] Send failed, data: ~p~n", [Data]),
Pid ! {self(), {error, port_command_failed}}
end,
openssl_client_loop_core(Pid, SslPort, Args);
{active_receive, Data} ->
case active_recv(SslPort, length(Data)) of
ReceivedData ->
- ?LOG("[openssl client] Received: ~p~n (forward to PID=~p)~n",
+ ?CT_LOG("[openssl client] Received: ~p~n (forward to PID=~p)~n",
[Data, Pid]),
Pid ! {self(), ReceivedData}
end,
@@ -955,17 +1036,17 @@ openssl_client_loop_core(Pid, SslPort, Args) ->
{update_keys, Type} ->
case Type of
write ->
- ?LOG("[openssl client] Update keys: ~p", [Type]),
+ ?CT_LOG("[openssl client] Update keys: ~p", [Type]),
true = port_command(SslPort, "k", [nosuspend]),
Pid ! {self(), ok};
read_write ->
- ?LOG("[openssl client] Update keys: ~p", [Type]),
+ ?CT_LOG("[openssl client] Update keys: ~p", [Type]),
true = port_command(SslPort, "K", [nosuspend]),
Pid ! {self(), ok}
end,
openssl_client_loop_core(Pid, SslPort, Args);
close ->
- ?LOG("~nClient closing~n", []),
+ ?CT_LOG("~nClient closing~n", []),
catch port_close(SslPort);
{ssl_closed, _Socket} ->
%% TODO
@@ -1010,8 +1091,8 @@ run_client(Opts) ->
Options0 = proplists:get_value(options, Opts),
Options = patch_dtls_options(Options0),
ContOpts = proplists:get_value(continue_options, Opts, []),
- ?LOG("~n~p:connect(~p, ~p)@~p~n", [Transport, Host, Port, Node]),
- ?LOG("SSLOpts:~n ~0.p", [format_options(Options)]),
+ ?CT_LOG("~n~p:connect(~p, ~p)@~p~n", [Transport, Host, Port, Node]),
+ ?CT_LOG("SSLOpts:~n ~0.p", [format_options(Options)]),
case ContOpts of
[] ->
client_loop(Node, Host, Port, Pid, Transport, Options, Opts);
@@ -1023,7 +1104,7 @@ client_loop(_Node, Host, Port, Pid, Transport, Options, Opts) ->
case Transport:connect(Host, Port, Options) of
{ok, Socket} ->
Pid ! {connected, Socket},
- ?LOG("~nClient: connected~n", []),
+ ?CT_LOG("~nClient: connected~n", []),
%% In special cases we want to know the client port, it will
%% be indicated by sending {port, 0} in options list!
send_selected_port(Pid, proplists:get_value(port, Options), Socket),
@@ -1032,7 +1113,7 @@ client_loop(_Node, Host, Port, Pid, Transport, Options, Opts) ->
no_result_msg ->
ok;
Msg ->
- ?LOG("~nClient Msg: ~p ~n", [Msg]),
+ ?CT_LOG("~nClient Msg: ~p ~n", [Msg]),
Pid ! {self(), Msg}
end,
client_loop_core(Socket, Pid, Transport);
@@ -1043,35 +1124,35 @@ client_loop(_Node, Host, Port, Pid, Transport, Options, Opts) ->
_ ->
case get(retries) of
N when N < 5 ->
- ?LOG("~neconnrefused retries=~p sleep ~p",[N,?SLEEP]),
+ ?CT_LOG("~neconnrefused retries=~p sleep ~p",[N,?SLEEP]),
put(retries, N+1),
ct:sleep(?SLEEP),
run_client(Opts);
_ ->
- ?LOG("~nClient failed several times: connection failed: ~p ~n", [Reason]),
+ ?CT_LOG("~nClient failed several times: connection failed: ~p ~n", [Reason]),
Pid ! {self(), {error, Reason}}
end
end;
{error, econnreset = Reason} ->
case get(retries) of
N when N < 5 ->
- ?LOG("~neconnreset retries=~p sleep ~p",[N,?SLEEP]),
+ ?CT_LOG("~neconnreset retries=~p sleep ~p",[N,?SLEEP]),
put(retries, N+1),
ct:sleep(?SLEEP),
run_client(Opts);
_ ->
- ?LOG("~nClient failed several times: connection failed: ~p ~n", [Reason]),
+ ?CT_LOG("~nClient failed several times: connection failed: ~p ~n", [Reason]),
Pid ! {self(), {error, Reason}}
end;
{error, Reason} ->
- ?LOG("~nClient: connection failed: ~p ~n", [Reason]),
+ ?CT_LOG("~nClient: connection failed: ~p ~n", [Reason]),
Pid ! {connect_failed, Reason}
end.
client_loop_core(Socket, Pid, Transport) ->
receive
{data, Data} ->
- ?LOG("[client] Send: ~p~n", [Data]),
+ ?CT_LOG("[client] Send: ~p~n", [Data]),
case Transport:send(Socket, Data) of
ok ->
Pid ! {self(), ok};
@@ -1082,17 +1163,17 @@ client_loop_core(Socket, Pid, Transport) ->
{active_receive, Data} ->
case active_recv(Socket, length(Data)) of
ReceivedData ->
- ?LOG("[client] Received: ~p~n", [Data]),
+ ?CT_LOG("[client] Received: ~p~n", [Data]),
Pid ! {self(), ReceivedData}
end,
client_loop_core(Socket, Pid, Transport);
{update_keys, Type} ->
case ssl:update_keys(Socket, Type) of
ok ->
- ?LOG("[client] Update keys: ~p", [Type]),
+ ?CT_LOG("[client] Update keys: ~p", [Type]),
Pid ! {self(), ok};
{error, Reason} ->
- ?LOG("[client] Update keys failed: ~p", [Type]),
+ ?CT_LOG("[client] Update keys failed: ~p", [Type]),
Pid ! {self(), Reason}
end,
client_loop_core(Socket, Pid, Transport);
@@ -1100,7 +1181,7 @@ client_loop_core(Socket, Pid, Transport) ->
Pid ! {self(), {socket, Socket}},
client_loop_core(Socket, Pid, Transport);
close ->
- ?LOG("~nClient closing~n", []),
+ ?CT_LOG("~nClient closing~n", []),
Transport:close(Socket);
{ssl_closed, Socket} ->
ok;
@@ -1124,10 +1205,10 @@ client_cont_loop(_Node, Host, Port, Pid, Transport, Options, cancel, _Opts) ->
case Transport:connect(Host, Port, Options) of
{ok, Socket, _} ->
Result = Transport:handshake_cancel(Socket),
- ?LOG("~nClient: Cancel: ~p ~n", [Result]),
+ ?CT_LOG("~nClient: Cancel: ~p ~n", [Result]),
Pid ! {connect_failed, Result};
{error, Reason} ->
- ?LOG("~nClient: connection failed: ~p ~n", [Reason]),
+ ?CT_LOG("~nClient: connection failed: ~p ~n", [Reason]),
Pid ! {connect_failed, Reason}
end;
@@ -1145,44 +1226,44 @@ client_cont_loop(_Node, Host, Port, Pid, Transport, Options, ContOpts0, Opts) ->
_ ->
ContOpts0
end,
- ?LOG("~nClient: handshake_continue(~p, ~p, infinity) ~n", [Socket0, ContOpts]),
+ ?CT_LOG("~nClient: handshake_continue(~p, ~p, infinity) ~n", [Socket0, ContOpts]),
case Transport:handshake_continue(Socket0, ContOpts) of
{ok, Socket} ->
Pid ! {connected, Socket},
- {Module, Function, Args} = proplists:get_value(mfa, Opts),
- ?LOG("~nClient: apply(~p,~p,~p)~n",
- [Module, Function, [Socket | Args]]),
- case apply(Module, Function, [Socket | Args]) of
+ MFA = proplists:get_value(mfa, Opts),
+ ?CT_LOG(
+ "~nClient: client_apply_mfa(~p,~p)~n", [Socket, MFA]),
+ case client_apply_mfa(Socket, MFA) of
no_result_msg ->
ok;
Msg ->
- ?LOG("~nClient Msg: ~p ~n", [Msg]),
+ ?CT_LOG("~nClient Msg: ~p ~n", [Msg]),
Pid ! {self(), Msg}
end
end;
{error, Reason} ->
- ?LOG("~nClient: connection failed: ~p ~n", [Reason]),
+ ?CT_LOG("~nClient: connection failed: ~p ~n", [Reason]),
Pid ! {connect_failed, Reason}
end.
close(Pid) ->
- ?LOG("~nClose ~p ~n", [Pid]),
+ ?CT_LOG("~nClose ~p ~n", [Pid]),
Monitor = erlang:monitor(process, Pid),
Pid ! close,
receive
{'DOWN', Monitor, process, Pid, Reason} ->
erlang:demonitor(Monitor),
- ?LOG("~nPid: ~p down due to:~p ~n", [Pid, Reason])
+ ?CT_LOG("~nPid: ~p down due to:~p ~n", [Pid, Reason])
end.
close(Pid, Timeout) ->
- ?LOG("~n Close ~p ~n", [Pid]),
+ ?CT_LOG("~n Close ~p ~n", [Pid]),
Monitor = erlang:monitor(process, Pid),
Pid ! close,
receive
{'DOWN', Monitor, process, Pid, Reason} ->
erlang:demonitor(Monitor),
- ?LOG("~nPid: ~p down due to:~p ~n", [Pid, Reason])
+ ?CT_LOG("~nPid: ~p down due to:~p ~n", [Pid, Reason])
after
Timeout ->
exit(Pid, kill)
@@ -1191,65 +1272,42 @@ close(Pid, Timeout) ->
get_result(Pids) ->
get_result(Pids, []).
-get_result([], Acc) ->
- Acc;
+get_result([], Acc) -> Acc;
get_result([Pid | Tail], Acc) ->
- receive
- {Pid, Msg} ->
- get_result(Tail, [{Pid, Msg} | Acc])
+ receive {Pid, Msg} -> get_result(Tail, [{Pid, Msg} | Acc])
end.
+check_result(Pid, Msg) ->
+ check_result([{Pid, Msg}]).
check_result(Server, ServerMsg, Client, ClientMsg) ->
- {ClientIP, ClientPort} = get_ip_port(ServerMsg),
+ check_result([{Server, ServerMsg}, {Client, ClientMsg}]).
+
+check_result([]) -> ok;
+check_result(Msgs) ->
receive
- {Server, ServerMsg} ->
- check_result(Client, ClientMsg);
- %% Workaround to accept local addresses (127.0.0.0/24)
- {Server, {ok, {{127,_,_,_}, ClientPort}}} when ClientIP =:= localhost ->
- check_result(Client, ClientMsg);
- {Client, ClientMsg} ->
- check_result(Server, ServerMsg);
- {Port, {data,Debug}} when is_port(Port) ->
- ?LOG("~n Openssl ~s~n",[Debug]),
- check_result(Server, ServerMsg, Client, ClientMsg);
- {Port,closed} when is_port(Port) ->
- ?LOG("~n Openssl port closed ~n",[]),
- check_result(Server, ServerMsg, Client, ClientMsg);
- {'EXIT', epipe} ->
- ?LOG("~n Openssl port died ~n",[]),
- check_result(Server, ServerMsg, Client, ClientMsg);
- Unexpected ->
- Reason = {{expected, {Client, ClientMsg}},
- {expected, {Server, ServerMsg}}, {got, Unexpected}},
- ct:fail(Reason)
+ Msg -> match_result_msg(Msg, Msgs)
end.
-check_result(Pid, Msg) ->
- {ClientIP, ClientPort} = get_ip_port(Msg),
- receive
- {Pid, Msg} ->
- ok;
- %% Workaround to accept local addresses (127.0.0.0/24)
- {Pid, {ok, {{127,_,_,_}, ClientPort}}} when ClientIP =:= localhost ->
- ok;
- {Port, {data,Debug}} when is_port(Port) ->
- ?LOG("~n Openssl ~s~n",[Debug]),
- check_result(Pid,Msg);
- {Port,closed} when is_port(Port)->
- ?LOG(" Openssl port closed ~n",[]),
- check_result(Pid, Msg);
- Unexpected ->
- Reason = {{expected, {Pid, Msg}},
- {got, Unexpected}},
- ct:fail(Reason)
+match_result_msg(Msg, Msgs) ->
+ case lists:member(Msg, Msgs) of
+ true -> check_result(lists:delete(Msg, Msgs));
+ false -> match_result_msg2(Msg, Msgs)
end.
-
-get_ip_port({ok,{ClientIP, ClientPort}}) ->
- {ClientIP, ClientPort};
-get_ip_port(_) ->
- {undefined, undefined}.
-
+match_result_msg2({Pid, {ok, {{127,_,_,_}, Port}}} = Msg, Msgs) ->
+ Match = {Pid, {ok, {localhost, Port}}},
+ case lists:member(Match, Msgs) of
+ true -> check_result(lists:delete(Match, Msgs));
+ false -> ct:fail({{expected, Msgs}, {got, Msg}})
+ end;
+match_result_msg2({Port, {data,Debug}}, Msgs) when is_port(Port) ->
+ ?CT_LOG(" Openssl (~p) ~s~n",[Port, Debug]),
+ check_result(Msgs);
+match_result_msg2({Port, closed}, Msgs) when is_port(Port) ->
+ ?CT_LOG(" Openssl port (~p) closed ~n",[Port]),
+ check_result(Msgs);
+match_result_msg2(Msg, Msgs) ->
+ ct:fail({{expected, Msgs}, {got, Msg}}).
check_server_alert(Pid, Alert) ->
receive
@@ -1334,7 +1392,7 @@ wait_for_result(Server, ServerMsg, Client, ClientMsg) ->
%% Unexpected
end;
{Port, {data,Debug}} when is_port(Port) ->
- ?LOG("~nopenssl ~s~n",[Debug]),
+ ?CT_LOG("~nopenssl ~s~n",[Debug]),
wait_for_result(Server, ServerMsg, Client, ClientMsg)
%% Unexpected ->
%% Unexpected
@@ -1355,7 +1413,7 @@ wait_for_result(Pid, Msg) ->
{Pid, Msg} ->
ok;
{Port, {data,Debug}} when is_port(Port) ->
- ?LOG("~nopenssl ~s~n",[Debug]),
+ ?CT_LOG("~nopenssl ~s~n",[Debug]),
wait_for_result(Pid,Msg)
%% Unexpected ->
%% Unexpected
@@ -1403,78 +1461,82 @@ format_cert(#'OTPCertificate'{tbsCertificate = Cert} = OtpCert) ->
{error, _} ->
io_lib:format("~.3w:~s -> :~s", [Nr, format_subject(Subject), format_subject(Issuer)])
end
- end.
+ end;
+format_cert(Cert) ->
+ io_lib:format("Format failed for ~p", [Cert]).
format_subject({rdnSequence, Seq}) ->
format_subject(Seq);
format_subject([[{'AttributeTypeAndValue', ?'id-at-commonName', {_, String}}]|_]) ->
String;
format_subject([_|R]) ->
- format_subject(R).
+ format_subject(R);
+format_subject([]) ->
+ "no commonname".
cert_options(Config) ->
- ClientCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ ClientCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
"client", "cacerts.pem"]),
- ClientCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ ClientCertFile = filename:join([proplists:get_value(priv_dir, Config),
"client", "cert.pem"]),
ClientCertFileDigitalSignatureOnly = filename:join([proplists:get_value(priv_dir, Config),
- "client", "digital_signature_only_cert.pem"]),
- ServerCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ "client", "digital_signature_only_cert.pem"]),
+ ServerCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
"server", "cacerts.pem"]),
- ServerCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ ServerCertFile = filename:join([proplists:get_value(priv_dir, Config),
"server", "cert.pem"]),
- ServerKeyFile = filename:join([proplists:get_value(priv_dir, Config),
- "server", "key.pem"]),
- ClientKeyFile = filename:join([proplists:get_value(priv_dir, Config),
- "client", "key.pem"]),
- ServerKeyCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ ServerKeyFile = filename:join([proplists:get_value(priv_dir, Config),
+ "server", "key.pem"]),
+ ClientKeyFile = filename:join([proplists:get_value(priv_dir, Config),
+ "client", "key.pem"]),
+ ServerKeyCertFile = filename:join([proplists:get_value(priv_dir, Config),
"server", "keycert.pem"]),
- ClientKeyCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ ClientKeyCertFile = filename:join([proplists:get_value(priv_dir, Config),
"client", "keycert.pem"]),
- BadCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ BadCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
"badcacert.pem"]),
- BadCertFile = filename:join([proplists:get_value(priv_dir, Config),
- "badcert.pem"]),
- BadKeyFile = filename:join([proplists:get_value(priv_dir, Config),
- "badkey.pem"]),
-
- [{client_opts, [{cacertfile, ClientCaCertFile},
- {certfile, ClientCertFile},
- {keyfile, ClientKeyFile}]},
- {client_verification_opts, [{cacertfile, ServerCaCertFile},
- {certfile, ClientCertFile},
- {keyfile, ClientKeyFile},
- {verify, verify_peer}]},
+ BadCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ "badcert.pem"]),
+ BadKeyFile = filename:join([proplists:get_value(priv_dir, Config),
+ "badkey.pem"]),
+
+ [{client_opts, [{cacertfile, ClientCaCertFile},
+ {certfile, ClientCertFile},
+ {keyfile, ClientKeyFile}, {verify, verify_none}]},
+ {client_verification_opts, [{cacertfile, ServerCaCertFile},
+ {certfile, ClientCertFile},
+ {keyfile, ClientKeyFile},
+ {verify, verify_peer}]},
{client_verification_opts_digital_signature_only, [{cacertfile, ServerCaCertFile},
- {certfile, ClientCertFileDigitalSignatureOnly},
- {keyfile, ClientKeyFile},
- {ssl_imp, new}]},
- {server_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile},
- {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
- {server_verification_opts, [{ssl_imp, new},{reuseaddr, true},
- {cacertfile, ClientCaCertFile},
- {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
- {client_kc_opts, [{certfile, ClientKeyCertFile}, {ssl_imp, new}]},
- {server_kc_opts, [{ssl_imp, new},{reuseaddr, true},
- {certfile, ServerKeyCertFile}]},
- {client_bad_ca, [{cacertfile, BadCaCertFile},
- {certfile, ClientCertFile},
- {keyfile, ClientKeyFile},
- {ssl_imp, new}]},
- {client_bad_cert, [{cacertfile, ClientCaCertFile},
- {certfile, BadCertFile},
- {keyfile, ClientKeyFile},
- {ssl_imp, new}]},
- {server_bad_ca, [{ssl_imp, new},{cacertfile, BadCaCertFile},
- {certfile, ServerCertFile},
- {keyfile, ServerKeyFile}]},
- {server_bad_cert, [{ssl_imp, new},{cacertfile, ServerCaCertFile},
- {certfile, BadCertFile}, {keyfile, ServerKeyFile}]},
- {server_bad_key, [{ssl_imp, new},{cacertfile, ServerCaCertFile},
- {certfile, ServerCertFile}, {keyfile, BadKeyFile}]}
- | Config].
-
+ {certfile, ClientCertFileDigitalSignatureOnly},
+ {keyfile, ClientKeyFile},
+ {verify, verify_peer}]},
+ {server_opts, [{reuseaddr, true}, {cacertfile, ServerCaCertFile},
+ {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
+ {server_verification_opts, [{verify, verify_peer},{reuseaddr, true},
+ {cacertfile, ClientCaCertFile},
+ {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
+ {client_kc_opts, [{certfile, ClientKeyCertFile}, {verify, verify_none}]},
+ {server_kc_opts, [{reuseaddr, true},
+ {certfile, ServerKeyCertFile}]},
+ {client_bad_ca, [{cacertfile, BadCaCertFile},
+ {certfile, ClientCertFile},
+ {keyfile, ClientKeyFile},
+ {verify, verify_peer}]},
+ {client_bad_cert, [{cacertfile, ClientCaCertFile},
+ {certfile, BadCertFile},
+ {keyfile, ClientKeyFile},
+ {verify, verify_peer}]},
+ {server_bad_ca, [{cacertfile, BadCaCertFile},
+ {certfile, ServerCertFile},
+ {keyfile, ServerKeyFile}]},
+ {server_bad_cert, [{cacertfile, ServerCaCertFile},
+ {certfile, BadCertFile}, {keyfile, ServerKeyFile}]},
+ {server_bad_key, [{cacertfile, ServerCaCertFile},
+ {certfile, ServerCertFile}, {keyfile, BadKeyFile}]}
+ | Config].
+
make_dsa_cert(Config) ->
CryptoSupport = crypto:supports(),
case proplists:get_bool(dss, proplists:get_value(public_keys, CryptoSupport)) of
@@ -1493,7 +1555,7 @@ make_dsa_cert(Config) ->
{server_dsa_verify_opts, [{verify, verify_peer} | ServerConf]},
{client_dsa_opts, ClientConf}
| Config];
- false ->
+ false ->
Config
end.
@@ -1578,9 +1640,7 @@ make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config, Curve) -
[{server_config, ServerConf},
{client_config, ClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
- {[{verify, verify_peer} | ClientConf],
- [{reuseaddr, true}, {verify, verify_peer} | ServerConf]
- }.
+ {ClientConf, [{reuseaddr, true} | ServerConf]}.
default_cert_chain_conf() ->
%% Use only default options
@@ -1818,11 +1878,12 @@ make_ecdsa_cert(Config) ->
[{server_config, ServerConf},
{client_config, ClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
- [{server_ecdsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
+ [{server_ecdsa_opts, [{reuseaddr, true} | ServerConf]},
- {server_ecdsa_verify_opts, [{ssl_imp, new}, {reuseaddr, true},
+ {server_ecdsa_verify_opts, [{reuseaddr, true},
{verify, verify_peer} | ServerConf]},
- {client_ecdsa_opts, ClientConf}
+ {client_ecdsa_opts, [{verify, verify_none} | ClientConf]},
+ {client_ecdsa_verify_opts, [{verify, verify_peer} | ClientConf]}
| Config];
false ->
Config
@@ -1844,11 +1905,11 @@ make_rsa_cert(Config) ->
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
[{server_rsa_opts, [{reuseaddr, true} | ServerConf]},
{server_rsa_verify_opts, [{reuseaddr, true}, {verify, verify_peer} | ServerConf]},
- {client_rsa_opts, ClientConf},
- {client_rsa_verify_opts, [{verify, verify_peer} |ClientConf]},
- {server_rsa_der_opts, [{reuseaddr, true} | ServerDerConf]},
+ {client_rsa_opts, [{verify, verify_none} | ClientConf]},
+ {client_rsa_verify_opts, [{verify, verify_peer} | ClientConf]},
+ {server_rsa_der_opts, [{reuseaddr, true}, {verify, verify_none} | ServerDerConf]},
{server_rsa_der_verify_opts, [{reuseaddr, true}, {verify, verify_peer} | ServerDerConf]},
- {client_rsa_der_opts, ClientDerConf},
+ {client_rsa_der_opts, [{verify, verify_none} | ClientDerConf]},
{client_rsa_der_verify_opts, [{verify, verify_peer} |ClientDerConf]}
| Config];
false ->
@@ -1898,12 +1959,12 @@ make_rsa_1024_cert(Config) ->
{client_config, ClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
[{server_rsa_1024_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
- {server_rsa_1024_verify_opts, [{ssl_imp, new}, {reuseaddr, true}, {verify, verify_peer} | ServerConf]},
- {client_rsa_1024_opts, ClientConf},
+ {server_rsa_1024_verify_opts, [{reuseaddr, true}, {verify, verify_peer} | ServerConf]},
+ {client_rsa_1024_opts, [{verify, verify_none} | ClientConf]},
{client_rsa_1024_verify_opts, [{verify, verify_peer} |ClientConf]},
- {server_rsa_1024_der_opts, [{ssl_imp, new},{reuseaddr, true} | ServerDerConf]},
- {server_rsa_1024_der_verify_opts, [{ssl_imp, new}, {reuseaddr, true}, {verify, verify_peer} | ServerDerConf]},
- {client_rsa_1024_der_opts, ClientDerConf},
+ {server_rsa_1024_der_opts, [{reuseaddr, true} | ServerDerConf]},
+ {server_rsa_1024_der_verify_opts, [{reuseaddr, true}, {verify, verify_peer} | ServerDerConf]},
+ {client_rsa_1024_der_opts, [{verify, verify_none} | ClientDerConf]},
{client_rsa_1024_der_verify_opts, [{verify, verify_peer} |ClientDerConf]}
| Config];
false ->
@@ -1970,10 +2031,10 @@ make_rsa_ecdsa_cert(Config, Curve) ->
{client_config, ClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
- [{server_rsa_ecdsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
+ [{server_rsa_ecdsa_opts, [{reuseaddr, true} | ServerConf]},
{server_rsa_ecdsa_verify_opts, [{ssl_imp, new},{reuseaddr, true},
{verify, verify_peer} | ServerConf]},
- {client_rsa_ecdsa_opts, ClientConf} | Config];
+ {client_rsa_ecdsa_opts, [{verify, verify_none} | ClientConf]} | Config];
_ ->
Config
end.
@@ -1994,31 +2055,31 @@ run_upgrade_server(Opts) ->
SslOptions = proplists:get_value(ssl_options, Opts),
Pid = proplists:get_value(from, Opts),
- ?LOG("~ngen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]),
+ ?CT_LOG("~ngen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]),
{ok, ListenSocket} = gen_tcp:listen(Port, TcpOptions),
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
- ?LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
+ ?CT_LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
{ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
try
{ok, SslAcceptSocket} = case TimeOut of
infinity ->
- ?LOG("~nssl:handshake(~p, ~p)~n",
+ ?CT_LOG("~nssl:handshake(~p, ~p)~n",
[AcceptSocket, SslOptions]),
ssl:handshake(AcceptSocket, SslOptions);
_ ->
- ?LOG("~nssl:handshake(~p, ~p, ~p)~n",
+ ?CT_LOG("~nssl:handshake(~p, ~p, ~p)~n",
[AcceptSocket, SslOptions, TimeOut]),
ssl:handshake(AcceptSocket, SslOptions, TimeOut)
end,
{Module, Function, Args} = proplists:get_value(mfa, Opts),
Msg = apply(Module, Function, [SslAcceptSocket | Args]),
- ?LOG("~nUpgrade Server Msg: ~p ~n", [Msg]),
+ ?CT_LOG("~nUpgrade Server Msg: ~p ~n", [Msg]),
Pid ! {self(), Msg},
receive
close ->
- ?LOG("~nUpgrade Server closing~n", []),
+ ?CT_LOG("~nUpgrade Server closing~n", []),
ssl:close(SslAcceptSocket)
end
catch error:{badmatch, Error} ->
@@ -2036,24 +2097,24 @@ run_upgrade_client(Opts) ->
TcpOptions = proplists:get_value(tcp_options, Opts),
SslOptions = proplists:get_value(ssl_options, Opts),
- ?LOG("~ngen_tcp:connect(~p, ~p, ~p)~n",
+ ?CT_LOG("~ngen_tcp:connect(~p, ~p, ~p)~n",
[Host, Port, TcpOptions]),
{ok, Socket} = gen_tcp:connect(Host, Port, TcpOptions),
send_selected_port(Pid, Port, Socket),
- ?LOG("~nssl:connect(~p, ~p)~n", [Socket, SslOptions]),
+ ?CT_LOG("~nssl:connect(~p, ~p)~n", [Socket, SslOptions]),
{ok, SslSocket} = ssl:connect(Socket, SslOptions),
{Module, Function, Args} = proplists:get_value(mfa, Opts),
- ?LOG("~napply(~p, ~p, ~p)~n",
+ ?CT_LOG("~napply(~p, ~p, ~p)~n",
[Module, Function, [SslSocket | Args]]),
Msg = apply(Module, Function, [SslSocket | Args]),
- ?LOG("~nUpgrade Client Msg: ~p ~n", [Msg]),
+ ?CT_LOG("~nUpgrade Client Msg: ~p ~n", [Msg]),
Pid ! {self(), Msg},
receive
close ->
- ?LOG("~nUpgrade Client closing~n", []),
+ ?CT_LOG("~nUpgrade Client closing~n", []),
ssl:close(SslSocket)
end.
@@ -2068,11 +2129,11 @@ run_upgrade_client_error(Opts) ->
Timeout = proplists:get_value(timeout, Opts, infinity),
TcpOptions = proplists:get_value(tcp_options, Opts),
SslOptions = proplists:get_value(ssl_options, Opts),
- ?LOG("gen_tcp:connect(~p, ~p, ~p)",
+ ?CT_LOG("gen_tcp:connect(~p, ~p, ~p)",
[Host, Port, TcpOptions]),
{ok, Socket} = gen_tcp:connect(Host, Port, TcpOptions),
send_selected_port(Pid, Port, Socket),
- ?LOG("ssl:connect(~p, ~p)", [Socket, SslOptions]),
+ ?CT_LOG("ssl:connect(~p, ~p)", [Socket, SslOptions]),
Error = ssl:connect(Socket, SslOptions, Timeout),
Pid ! {self(), Error}.
@@ -2091,19 +2152,19 @@ run_upgrade_server_error(Opts) ->
SslOptions = proplists:get_value(ssl_options, Opts),
Pid = proplists:get_value(from, Opts),
- ?LOG("~ngen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]),
+ ?CT_LOG("~ngen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]),
{ok, ListenSocket} = gen_tcp:listen(Port, TcpOptions),
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
- ?LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
+ ?CT_LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
{ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
Error = case TimeOut of
infinity ->
- ?LOG("~nssl:handshake(~p, ~p)~n",
+ ?CT_LOG("~nssl:handshake(~p, ~p)~n",
[AcceptSocket, SslOptions]),
ssl:handshake(AcceptSocket, SslOptions);
_ ->
- ?LOG("~nssl:ssl_handshake(~p, ~p, ~p)~n",
+ ?CT_LOG("~nssl:ssl_handshake(~p, ~p, ~p)~n",
[AcceptSocket, SslOptions, TimeOut]),
ssl:handshake(AcceptSocket, SslOptions, TimeOut)
end,
@@ -2121,7 +2182,7 @@ run_server_error(Opts) ->
Options = proplists:get_value(options, Opts),
Pid = proplists:get_value(from, Opts),
Transport = proplists:get_value(transport, Opts, ssl),
- ?LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
+ ?CT_LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
Timeout = proplists:get_value(timeout, Opts, infinity),
case Transport:listen(Port, Options) of
{ok, #sslsocket{} = ListenSocket} ->
@@ -2129,19 +2190,19 @@ run_server_error(Opts) ->
%% get {error, closed} and not {error, connection_refused}
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
- ?LOG("~nssl:transport_accept(~p)~n", [ListenSocket]),
+ ?CT_LOG("~nssl:transport_accept(~p)~n", [ListenSocket]),
case Transport:transport_accept(ListenSocket, Timeout) of
{error, _} = Error ->
Pid ! {self(), Error};
{ok, AcceptSocket} ->
- ?LOG("~nssl:handshake(~p)~n", [AcceptSocket]),
+ ?CT_LOG("~nssl:handshake(~p)~n", [AcceptSocket]),
Error = ssl:handshake(AcceptSocket),
Pid ! {self(), Error}
end;
{ok, ListenSocket} ->
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
- ?LOG("~n~p:accept(~p)~n", [Transport, ListenSocket]),
+ ?CT_LOG("~n~p:accept(~p)~n", [Transport, ListenSocket]),
case Transport:accept(ListenSocket) of
{error, _} = Error ->
Pid ! {self(), Error}
@@ -2164,7 +2225,7 @@ run_client_error(Opts) ->
Transport = proplists:get_value(transport, Opts, ssl),
Options0 = proplists:get_value(options, Opts),
Options = patch_dtls_options(Options0),
- ?LOG("~nssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]),
+ ?CT_LOG("~nssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]),
Error = Transport:connect(Host, Port, Options),
case Error of
{error, _} ->
@@ -2390,7 +2451,7 @@ start_server(erlang, _, ServerOpts, Config) ->
{mfa, {ssl_test_lib,
check_key_exchange_send_active,
[KeyEx]}},
- {options, [{verify, verify_peer} | ServerOpts]}]),
+ {options, ServerOpts}]),
{Server, inet_port(Server)}.
sig_algs(undefined) ->
@@ -2645,14 +2706,14 @@ rsa_non_signed_suites(Version) ->
true
end,
available_suites(Version)).
-
-ecdsa_suites(Version) ->
- lists:filter(fun({ecdhe_ecdsa, _, _}) ->
- true;
- (_) ->
- false
- end,
- available_suites(Version)).
+dsa_suites(Version) ->
+ ssl:filter_cipher_suites(available_suites(Version),
+ [{key_exchange,
+ fun(dhe_dss) ->
+ true;
+ (_) ->
+ false
+ end}]).
openssl_dsa_suites() ->
Ciphers = openssl_ciphers(),
@@ -2696,7 +2757,7 @@ der_to_pem(File, Entries) ->
cipher_result(Socket, Result) ->
{ok, Info} = ssl:connection_information(Socket),
Result = {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_cipher_suite, Info)}},
- ?LOG("~nSuccessfull connect: ~p~n", [Result]),
+ ?CT_LOG("~nSuccessfull connect: ~p~n", [Result]),
%% Importante to send two packets here
%% to properly test "cipher state" handling
Hello = "Hello\n",
@@ -2778,7 +2839,7 @@ openssl_tls_version_support(Version, Config0) ->
true ->
openssl_tls_version_support(tls, TLSOpts, Port, Exe, TLSArgs);
false ->
- DTLSTupleVersion = dtls_record:protocol_version(Version),
+ DTLSTupleVersion = dtls_record:protocol_version_name(Version),
CorrespondingTLSVersion = dtls_v1:corresponding_tls_version(DTLSTupleVersion),
AtomTLSVersion = tls_record:protocol_version(CorrespondingTLSVersion),
CorrTLSOpts = [{protocol,tls}, {versions, [AtomTLSVersion]},
@@ -2805,21 +2866,21 @@ openssl_tls_version_support(Proto, Opts, Port, Exe, Args0) ->
close_port(OpensslPort),
true;
{error, {tls_alert, {protocol_version, _}}} ->
- ?PAL("OpenSSL does not support ~p", [proplists:get_value(versions, Opts)]),
+ ?CT_PAL("OpenSSL does not support ~p", [proplists:get_value(versions, Opts)]),
close_port(OpensslPort),
false;
{error, {tls_alert, Alert}} ->
- ?PAL("OpenSSL returned alert ~p", [Alert]),
+ ?CT_PAL("OpenSSL returned alert ~p", [Alert]),
close_port(OpensslPort),
false;
{error, timeout} ->
- ?PAL("Timed out connection to OpenSSL", []),
+ ?CT_PAL("Timed out connection to OpenSSL", []),
close_port(OpensslPort),
false
end
catch
_:_ ->
- ?PAL("OpenSSL does not support ~p", [proplists:get_value(versions, Opts)]),
+ ?CT_PAL("OpenSSL does not support ~p", [proplists:get_value(versions, Opts)]),
close_port(OpensslPort),
false
end.
@@ -2842,6 +2903,8 @@ init_protocol_version(Version, Config) ->
[{protocol, tls} | NewConfig].
clean_protocol_version(Config) ->
+ application:unset_env(ssl, protocol_version),
+ application:unset_env(ssl, dtls_protocol_version),
proplists:delete(version, proplists:delete(protocol_opts, proplists:delete(protocol, Config))).
sufficient_crypto_support(Version)
@@ -2870,20 +2933,20 @@ check_key_exchange_send_active(Socket, KeyEx) ->
send_recv_result_active(Socket).
check_key_exchange({KeyEx,_, _}, KeyEx, _) ->
- ?LOG("Kex: ~p", [KeyEx]),
+ ?CT_LOG("Kex: ~p", [KeyEx]),
true;
check_key_exchange({KeyEx,_,_,_}, KeyEx, _) ->
- ?LOG("Kex: ~p", [KeyEx]),
+ ?CT_LOG("Kex: ~p", [KeyEx]),
true;
check_key_exchange(KeyEx1, KeyEx2, Version) ->
- ?LOG("Kex: ~p ~p", [KeyEx1, KeyEx2]),
+ ?CT_LOG("Kex: ~p ~p", [KeyEx1, KeyEx2]),
case Version of
'tlsv1.2' ->
v_1_2_check(element(1, KeyEx1), KeyEx2);
'dtlsv1.2' ->
v_1_2_check(element(1, KeyEx1), KeyEx2);
_ ->
- ?PAL("Negotiated ~p Expected ~p", [KeyEx1, KeyEx2]),
+ ?CT_PAL("Negotiated ~p Expected ~p", [KeyEx1, KeyEx2]),
false
end.
@@ -2927,10 +2990,10 @@ check_active_receive(Pid, Data) ->
check_active_receive_loop(Pid, Data) ->
receive
{Pid, Data} ->
- ?LOG("Received: ~p~n (from ~p)~n", [Data, Pid]),
+ ?CT_LOG("Received: ~p~n (from ~p)~n", [Data, Pid]),
Data;
{Pid, Data2} ->
- ?LOG("Received unexpected message: ~p~n (from ~p)~n", [Data2, Pid]),
+ ?CT_LOG("Received unexpected message: ~p~n (from ~p)~n", [Data2, Pid]),
check_active_receive_loop(Pid, Data)
end.
@@ -2964,15 +3027,15 @@ verify_active_session_resumption(Socket, SessionResumption, WaitForReply, Ticket
case ssl:connection_information(Socket, [session_resumption]) of
{ok, [{session_resumption, SessionResumption}]} ->
Msg = boolean_to_log_msg(SessionResumption),
- ?LOG("~nSession resumption verified! (expected ~p, got ~p)!",
+ ?CT_LOG("~nSession resumption verified! (expected ~p, got ~p)!",
[Msg, Msg]);
{ok, [{session_resumption, Got0}]} ->
Expected = boolean_to_log_msg(SessionResumption),
Got = boolean_to_log_msg(Got0),
- ?FAIL("~nFailed to verify session resumption! (expected ~p, got ~p)",
+ ?CT_FAIL("~nFailed to verify session resumption! (expected ~p, got ~p)",
[Expected, Got]);
{error, Reason} ->
- ?FAIL("~nFailed to verify session resumption! Reason: ~p",
+ ?CT_FAIL("~nFailed to verify session resumption! Reason: ~p",
[Reason])
end,
@@ -2984,7 +3047,7 @@ verify_active_session_resumption(Socket, SessionResumption, WaitForReply, Ticket
no_reply ->
ok;
Else1 ->
- ?FAIL("~nFaulty parameter: ~p", [Else1])
+ ?CT_FAIL("~nFaulty parameter: ~p", [Else1])
end,
Tickets =
case TicketOption of
@@ -2993,7 +3056,7 @@ verify_active_session_resumption(Socket, SessionResumption, WaitForReply, Ticket
no_tickets ->
ok;
Else2 ->
- ?FAIL("~nFaulty parameter: ~p", [Else2])
+ ?CT_FAIL("~nFaulty parameter: ~p", [Else2])
end,
case EarlyData of
{verify_early_data, Atom} ->
@@ -3001,28 +3064,28 @@ verify_active_session_resumption(Socket, SessionResumption, WaitForReply, Ticket
ok ->
Tickets;
Else ->
- ?FAIL("~nFailed to verify early_data! (expected ~p, got ~p)",
+ ?CT_FAIL("~nFailed to verify early_data! (expected ~p, got ~p)",
[Atom, Else])
end;
no_early_data ->
Tickets;
Else3 ->
- ?FAIL("~nFaulty parameter: ~p", [Else3])
+ ?CT_FAIL("~nFaulty parameter: ~p", [Else3])
end.
verify_server_early_data(Socket, WaitForReply, EarlyData) ->
case ssl:connection_information(Socket, [session_resumption]) of
{ok, [{session_resumption, true}]} ->
Msg = boolean_to_log_msg(true),
- ?LOG("~nSession resumption verified! (expected ~p, got ~p)!",
+ ?CT_LOG("~nSession resumption verified! (expected ~p, got ~p)!",
[Msg, Msg]);
{ok, [{session_resumption, Got0}]} ->
Expected = boolean_to_log_msg(true),
Got = boolean_to_log_msg(Got0),
- ?FAIL("~nFailed to verify session resumption! (expected ~p, got ~p)",
+ ?CT_FAIL("~nFailed to verify session resumption! (expected ~p, got ~p)",
[Expected, Got]);
{error, Reason} ->
- ?FAIL("~nFailed to verify session resumption! Reason: ~p",
+ ?CT_FAIL("~nFailed to verify session resumption! Reason: ~p",
[Reason])
end,
Data = "Hello world",
@@ -3034,14 +3097,14 @@ verify_server_early_data(Socket, WaitForReply, EarlyData) ->
_ ->
binary_to_list(EarlyData) ++ Data
end,
- ?LOG("Expected Reply: ~p~n", [Reply]),
+ ?CT_LOG("Expected Reply: ~p~n", [Reply]),
case WaitForReply of
wait_reply ->
Reply = active_recv(Socket, length(Reply));
no_reply ->
ok;
Else1 ->
- ?FAIL("~nFaulty parameter: ~p", [Else1])
+ ?CT_FAIL("~nFaulty parameter: ~p", [Else1])
end,
ok.
@@ -3052,10 +3115,10 @@ verify_session_ticket_extension([Ticket0|_], MaxEarlyDataSize) ->
indication = Size}}}} = Ticket0,
case Size of
MaxEarlyDataSize ->
- ?LOG("~nmax_early_data_size verified! (expected ~p, got ~p)!",
+ ?CT_LOG("~nmax_early_data_size verified! (expected ~p, got ~p)!",
[MaxEarlyDataSize, Size]);
Else ->
- ?LOG("~nFailed to verify max_early_data_size! (expected ~p, got ~p)!",
+ ?CT_LOG("~nFailed to verify max_early_data_size! (expected ~p, got ~p)!",
[MaxEarlyDataSize, Else])
end.
@@ -3064,7 +3127,7 @@ update_session_ticket_extension([Ticket|_], MaxEarlyDataSize) ->
extensions = #{early_data :=
#early_data_indication_nst{
indication = Size}}}} = Ticket,
- ?LOG("~nOverwrite max_early_data_size (from ~p to ~p)!",
+ ?CT_LOG("~nOverwrite max_early_data_size (from ~p to ~p)!",
[Size, MaxEarlyDataSize]),
#{ticket := #new_session_ticket{
extensions = #{early_data := _Extensions0}} = NST0} = Ticket,
@@ -3095,17 +3158,17 @@ check_tickets(Client) ->
Tickets
after
5000 ->
- ?FAIL("~nNo tickets received!", [])
+ ?CT_FAIL("~nNo tickets received!", [])
end.
active_recv_loop(Pid, SslPort, Data) ->
case active_recv(SslPort, length(Data)) of
Data ->
- ?LOG("[openssl server] Received: ~p~n (forward to PID=~p)~n",
+ ?CT_LOG("[openssl server] Received: ~p~n (forward to PID=~p)~n",
[Data, Pid]),
Pid ! {self(), Data};
Unexpected ->
- ?LOG("[openssl server] Received unexpected: ~p~n (dropping message)~n",
+ ?CT_LOG("[openssl server] Received unexpected: ~p~n (dropping message)~n",
[Unexpected]),
active_recv_loop(Pid, SslPort, Data)
end.
@@ -3298,6 +3361,22 @@ check_sane_openssl_version(Version, Config) ->
false ->
false
end.
+
+
+%% If other DSA checks have passed also check the following
+check_sane_openssl_dsa(Config) ->
+ case not is_fips(openssl, Config) of
+ true ->
+ case proplists:get_value(openssl_version, Config) of
+ "OpenSSL 1.0." ++ _ ->
+ false;
+ _ ->
+ true
+ end;
+ false ->
+ false
+ end.
+
check_sane_openssl_renegotiate(Config, Version) when Version == 'tlsv1';
Version == 'tlsv1.1';
Version == 'tlsv1.2' ->
@@ -3406,28 +3485,28 @@ close_port(Port) ->
close_loop(Port, Time, SentClose) ->
receive
{Port, {data,Debug}} when is_port(Port) ->
- ?LOG("openssl ~s~n",[Debug]),
+ ?CT_LOG("openssl ~s~n",[Debug]),
close_loop(Port, Time, SentClose);
{ssl,_,Msg} ->
- ?LOG("ssl Msg ~s~n",[Msg]),
+ ?CT_LOG("ssl Msg ~s~n",[Msg]),
close_loop(Port, Time, SentClose);
{Port, closed} ->
- ?LOG("Port Closed~n",[]),
+ ?CT_LOG("Port Closed~n",[]),
ok;
{'EXIT', Port, Reason} ->
- ?LOG("Port Closed ~p~n",[Reason]),
+ ?CT_LOG("Port Closed ~p~n",[Reason]),
ok;
Msg ->
- ?LOG("Port Msg ~p~n",[Msg]),
+ ?CT_LOG("Port Msg ~p~n",[Msg]),
close_loop(Port, Time, SentClose)
after Time ->
case SentClose of
false ->
- ?LOG("Closing port ~n",[]),
+ ?CT_LOG("Closing port ~n",[]),
catch erlang:port_close(Port),
close_loop(Port, Time, true);
true ->
- ?LOG("Timeout~n",[])
+ ?CT_LOG("Timeout~n",[])
end
end.
@@ -3439,7 +3518,7 @@ portable_open_port("openssl" = Exe, Args0) ->
case IsWindows andalso os:getenv("WSLENV") of
false ->
AbsPath = os:find_executable(Exe),
- ?LOG("open_port({spawn_executable, ~p}, [stderr_to_stdout,~n {args, \"~s\"}]).",
+ ?CT_LOG("open_port({spawn_executable, ~p}, [stderr_to_stdout,~n {args, \"~s\"}]).",
[AbsPath, lists:join($\s, Args0)]),
open_port({spawn_executable, AbsPath},
[{args, Args0}, stderr_to_stdout]);
@@ -3456,14 +3535,14 @@ portable_open_port("openssl" = Exe, Args0) ->
Args1 = [Translate(Arg) || Arg <- Args0],
Args = ["/C","wsl","openssl"| Args1] ++ ["2>&1"],
Cmd = os:find_executable("cmd"),
- ?LOG("open_port({spawn_executable, ~p}, [stderr_to_stdout,~n {args, \"~s\"}]).",
+ ?CT_LOG("open_port({spawn_executable, ~p}, [stderr_to_stdout,~n {args, \"~s\"}]).",
[Cmd, lists:join($\s, Args0)]),
open_port({spawn_executable, Cmd},
[{args, Args}, stderr_to_stdout, hide])
end;
portable_open_port(Exe, Args) ->
AbsPath = os:find_executable(Exe),
- ?LOG("open_port({spawn_executable, ~p}, [{args, ~p}, stderr_to_stdout]).", [AbsPath, Args]),
+ ?CT_LOG("open_port({spawn_executable, ~p}, [{args, ~p}, stderr_to_stdout]).", [AbsPath, Args]),
open_port({spawn_executable, AbsPath},
[{args, Args}, stderr_to_stdout]).
@@ -3546,7 +3625,7 @@ do_supports_ssl_tls_version(Port, Acc) ->
"s_client: Unknown option: " ++ _->
false;
Info when length(Info) >= 24 ->
- ?LOG("~p", [Info]),
+ ?CT_LOG("~p", [Info]),
true;
_ ->
do_supports_ssl_tls_version(Port, Acc ++ Data)
@@ -3600,8 +3679,8 @@ protocol_version(Config, atom) ->
case proplists:get_value(protocol, Config) of
dtls ->
dtls_record:protocol_version(protocol_version(Config, tuple));
- _ ->
- tls_record:protocol_version(protocol_version(Config, tuple))
+ _ ->
+ tls_record:protocol_version(protocol_version(Config, tuple))
end.
protocol_options(Config, Options) ->
@@ -3612,9 +3691,9 @@ protocol_options(Config, Options) ->
ct_log_supported_protocol_versions(Config) ->
case proplists:get_value(protocol, Config) of
dtls ->
- ?LOG("DTLS version ~p~n ", [dtls_record:supported_protocol_versions()]);
+ ?CT_LOG("DTLS version ~p~n ", [dtls_record:supported_protocol_versions()]);
_ ->
- ?LOG("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()])
+ ?CT_LOG("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()])
end.
clean_env() ->
@@ -3655,11 +3734,23 @@ clean_start(keep_version) ->
tls_version('dtlsv1' = Atom) ->
- dtls_v1:corresponding_tls_version(dtls_record:protocol_version(Atom));
+ dtls_v1:corresponding_tls_version(dtls_record:protocol_version_name(Atom));
tls_version('dtlsv1.2' = Atom) ->
- dtls_v1:corresponding_tls_version(dtls_record:protocol_version(Atom));
+ dtls_v1:corresponding_tls_version(dtls_record:protocol_version_name(Atom));
tls_version(Atom) ->
- tls_record:protocol_version(Atom).
+ tls_record:protocol_version_name(Atom).
+
+
+n_version(Version) when
+ Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
+ Version == 'tlsv1.1';
+ Version == 'tlsv1';
+ Version == 'sslv3' ->
+ tls_record:protocol_version_name(Version);
+n_version(Version) when Version == 'dtlsv1.2';
+ Version == 'dtlsv1' ->
+ dtls_record:protocol_version_name(Version).
consume_port_exit(OpenSSLPort) ->
receive
@@ -3807,10 +3898,10 @@ client_msg(Client, ClientMsg) ->
{Client, ClientMsg} ->
ok;
{Client, {error,closed}} ->
- ?LOG("client got close", []),
+ ?CT_LOG("client got close", []),
ok;
{Client, {error, Reason}} ->
- ?LOG("client got econnaborted: ~p", [Reason]),
+ ?CT_LOG("client got econnaborted: ~p", [Reason]),
ok;
Unexpected ->
ct:fail(Unexpected)
@@ -3820,10 +3911,10 @@ server_msg(Server, ServerMsg) ->
{Server, ServerMsg} ->
ok;
{Server, {error,closed}} ->
- ?LOG("server got close", []),
+ ?CT_LOG("server got close", []),
ok;
{Server, {error, Reason}} ->
- ?LOG("server got econnaborted: ~p", [Reason]),
+ ?CT_LOG("server got econnaborted: ~p", [Reason]),
ok;
Unexpected ->
ct:fail(Unexpected)
@@ -3995,7 +4086,7 @@ new_config(PrivDir, ServerOpts0) ->
ServerOpts = proplists:delete(keyfile, ServerOpts2),
{ok, PEM} = file:read_file(NewCaCertFile),
- ?LOG("CA file content: ~p~n", [public_key:pem_decode(PEM)]),
+ ?CT_LOG("CA file content: ~p~n", [public_key:pem_decode(PEM)]),
[{cacertfile, NewCaCertFile}, {certfile, NewCertFile},
{keyfile, NewKeyFile} | ServerOpts].
@@ -4096,11 +4187,11 @@ openssl_maxfraglen_support() ->
assert_mfl(Socket, undefined) ->
InfoMFL = ssl:connection_information(Socket, [max_fragment_length]),
- ?LOG("Connection MFL ~p, Expecting: [] ~n", [InfoMFL]),
+ ?CT_LOG("Connection MFL ~p, Expecting: [] ~n", [InfoMFL]),
{ok, []} = InfoMFL;
assert_mfl(Socket, MFL) ->
InfoMFL = ssl:connection_information(Socket, [max_fragment_length]),
- ?LOG("Connection MFL ~p, Expecting: ~p ~n", [InfoMFL, MFL]),
+ ?CT_LOG("Connection MFL ~p, Expecting: ~p ~n", [InfoMFL, MFL]),
{ok, [{max_fragment_length, ConnMFL}]} = InfoMFL,
ConnMFL = MFL.
-define(BIG_BUF, 10000000).
@@ -4146,3 +4237,54 @@ curve_default(eddsa) ->
ed25519;
curve_default(_) ->
?DEFAULT_CURVE.
+
+trace() ->
+ ssl_trace:start(fun ct:pal/2, []),
+ ssl_trace:on().
+
+handle_trace(rle,
+ {call, {?MODULE, init_openssl_server, [Mode, ResponderPort | _]}}, Stack0) ->
+ Role = server,
+ {io_lib:format("(*~w) Mode = ~w ResponderPort = ~w",
+ [Role, Mode, ResponderPort]),
+ [{role, Role} | Stack0]}.
+
+
+ktls_os() ->
+ inet_tls_dist:ktls_os().
+
+%% Set UserLand Protocol
+ktls_set_ulp(Socket, OS) ->
+ inet_tls_dist:set_ktls_ulp(
+ #{ socket => Socket,
+ setopt_fun => fun inet_tls_dist:inet_ktls_setopt/3,
+ getopt_fun => fun inet_tls_dist:inet_ktls_getopt/3 },
+ OS).
+
+ktls_set_cipher(Socket, OS, TxRx, Seed) ->
+ TLS_version = ?TLS_1_3,
+ TLS_cipher = ?TLS_AES_256_GCM_SHA384,
+ TLS_IV = binary:copy(<<(Seed + 0)>>, 8),
+ TLS_KEY = binary:copy(<<(Seed + 1)>>, 32),
+ TLS_SALT = binary:copy(<<(Seed + 2)>>, 4),
+ KtlsInfo =
+ #{ socket => Socket,
+ tls_version => TLS_version,
+ cipher_suite => TLS_cipher,
+ setopt_fun => fun inet_tls_dist:inet_ktls_setopt/3,
+ getopt_fun => fun inet_tls_dist:inet_ktls_getopt/3 },
+ CipherState =
+ #cipher_state{
+ key = TLS_KEY,
+ iv = <<TLS_SALT/binary, TLS_IV/binary>> },
+ inet_tls_dist:set_ktls_cipher(KtlsInfo, OS, CipherState, 0, TxRx).
+
+ct_pal_file(FilePath) ->
+ case file:read_file(FilePath) of
+ {ok, Binary} ->
+ ?CT_PAL("~s ~pB~n~s",
+ [FilePath, filelib:file_size(FilePath), Binary]);
+ _ ->
+ ?CT_PAL("Failed to log ~s", [FilePath]),
+ ok
+ end.
diff --git a/lib/ssl/test/ssl_test_lib.hrl b/lib/ssl/test/ssl_test_lib.hrl
index 817e3e0904..d6d928610e 100644
--- a/lib/ssl/test/ssl_test_lib.hrl
+++ b/lib/ssl/test/ssl_test_lib.hrl
@@ -1,6 +1,16 @@
--define(FORMAT, "(~s ~p:~p in ~p) ").
--define(ARGS, [erlang:pid_to_list(self()), ?MODULE, ?LINE, ?FUNCTION_NAME]).
--define(LOG(F), ct:log(?FORMAT ++ F, ?ARGS, [esc_chars])).
--define(LOG(F, Args), ct:log(?FORMAT ++ F, ?ARGS ++ Args, [esc_chars])).
--define(PAL(F, Args), ct:pal(?FORMAT ++ F, ?ARGS ++ Args)).
--define(FAIL(F, Args), ct:fail(?FORMAT ++ F, ?ARGS ++ Args)).
+-define(SSL_TEST_LIB_FORMAT, "(~s ~p:~p in ~p) ").
+-define(SSL_TEST_LIB_ARGS,
+ [erlang:pid_to_list(self()), ?MODULE, ?LINE, ?FUNCTION_NAME]).
+-define(CT_LOG(F),
+ (ct:log(?SSL_TEST_LIB_FORMAT ++ F, ?SSL_TEST_LIB_ARGS, [esc_chars]))).
+-define(CT_LOG(F, Args),
+ (ct:log(
+ ?SSL_TEST_LIB_FORMAT ++ F,
+ ?SSL_TEST_LIB_ARGS ++ Args,
+ [esc_chars]))).
+-define(CT_PAL(F),
+ (ct:pal(?SSL_TEST_LIB_FORMAT ++ F, ?SSL_TEST_LIB_ARGS))).
+-define(CT_PAL(F, Args),
+ (ct:pal(?SSL_TEST_LIB_FORMAT ++ F, ?SSL_TEST_LIB_ARGS ++ Args))).
+-define(CT_FAIL(F, Args),
+ (ct:fail(?SSL_TEST_LIB_FORMAT ++ F, ?SSL_TEST_LIB_ARGS ++ Args))).
diff --git a/lib/ssl/test/ssl_trace_SUITE.erl b/lib/ssl/test/ssl_trace_SUITE.erl
new file mode 100644
index 0000000000..c33b29e90c
--- /dev/null
+++ b/lib/ssl/test/ssl_trace_SUITE.erl
@@ -0,0 +1,493 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssl_trace_SUITE).
+
+-include("ssl_test_lib.hrl").
+-include_lib("common_test/include/ct.hrl").
+-include_lib("ssl/src/ssl_api.hrl").
+
+-export([suite/0,
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+-export([tc_basic/0,
+ tc_basic/1,
+ tc_no_trace/0,
+ tc_no_trace/1,
+ tc_api_profile/0,
+ tc_api_profile/1,
+ tc_rle_profile/0,
+ tc_rle_profile/1,
+ tc_budget_option/0,
+ tc_budget_option/1,
+ tc_file_option/0,
+ tc_file_option/1,
+ tc_write/0,
+ tc_write/1,
+ tc_check_profiles/0,
+ tc_check_profiles/1]).
+-define(TRACE_FILE, "ssl_trace.txt").
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+suite() -> [{ct_hooks,[ts_install_cth]},
+ {timetrap,{seconds,60}}].
+
+all() -> [tc_basic, tc_no_trace, tc_api_profile, tc_rle_profile,
+ tc_budget_option, tc_write, tc_file_option, tc_check_profiles].
+
+init_per_suite(Config) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ ssl_test_lib:make_rsa_cert(Config)
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ ssl:stop(),
+ application:stop(crypto).
+
+init_per_testcase(_TC, Config) ->
+ Config.
+
+end_per_testcase(_TC, Config) ->
+ ssl_trace:stop(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+tc_basic() ->
+ [{doc, "Basic test of ssl_trace API"}].
+tc_basic(_Config) ->
+ {ok, L0} = ssl_trace:start(),
+ true = is_pid(whereis(ssl_trace)),
+ true = is_list(L0),
+ {ok,L0} = ssl_trace:on(),
+ {ok,L0} = ssl_trace:on(),
+ L0 = ssl_trace:is_on(),
+ [] = ssl_trace:is_off(),
+
+ L1 = [hd(L0)],
+ L2 = tl(L0),
+ {ok,L1} = ssl_trace:off(L2),
+
+ L1 = ssl_trace:is_on(),
+ L2 = ssl_trace:is_off(),
+
+ {ok,[]} = ssl_trace:off(),
+ {ok,[]} = ssl_trace:off(),
+
+ [] = ssl_trace:is_on(),
+ L0 = ssl_trace:is_off(),
+ ok = ssl_trace:stop(),
+ undefined = whereis(ssl_trace),
+
+ {ok, [api, crt, csp, hbn, kdt, rle, ssn]} = ssl_trace:start(),
+ {ok, [api]} = ssl_trace:on(api),
+ {ok, []} = ssl_trace:off(api),
+ ok = ssl_trace:stop(),
+ ok.
+
+tc_no_trace() ->
+ [{doc, "Verify there are no traces if not enabled"}].
+tc_no_trace(Config) ->
+ Ref = ssl_trace_start(),
+ [Server, Client] = ssl_connect(Config),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ ExpectedTraces =
+ #{call => [], processed => [], exception_from => [], return_from => []},
+ ExpectedTraces = receive_map(Ref),
+ ok = ssl_trace:stop(),
+ ok.
+
+tc_api_profile() ->
+ [{doc, "Verify traces for 'api' trace profile"}].
+tc_api_profile(Config) ->
+ On = [api, rle],
+ Off = [],
+ TracesAfterConnect =
+ #{
+ call =>
+ [{" (server) -> ssl:handshake/2", ssl, handshake},
+ {" (server) -> ssl_gen_statem:initial_hello/3",
+ ssl_gen_statem, initial_hello},
+ {" (client) -> ssl_gen_statem:initial_hello/3",
+ ssl_gen_statem, initial_hello}],
+ return_from =>
+ [{" (server) <- ssl:listen/2 returned", ssl, listen},
+ {" (server) <- ssl_gen_statem:initial_hello/3 returned",
+ ssl_gen_statem, initial_hello},
+ {" (client) <- ssl_gen_statem:initial_hello/3 returned",
+ ssl_gen_statem, initial_hello},
+ {" (client) <- ssl_gen_statem:connect/8 returned",
+ ssl_gen_statem, connect},
+ {" (client) <- ssl:connect/3 returned", ssl, connect},
+ {" (server) <- ssl:handshake/2 returned", ssl, handshake},
+ {" (client) <- tls_sender:init/3 returned", tls_sender, init},
+ {" (server) <- tls_sender:init/3 returned", tls_sender, init}],
+ processed =>
+ ["rle ('?') -> ssl_gen_statem:init/1 (*client)",
+ "rle ('?') -> ssl_gen_statem:init/1 (*server)",
+ "rle ('?') -> ssl:listen/2 (*server) Args",
+ "rle ('?') -> ssl:connect/3 (*client) Args",
+ "rle ('?') -> tls_sender:init/3 (*server)",
+ "rle ('?') -> tls_sender:init/3 (*client)",
+ "api (client) -> ssl_gen_statem:connect/8"]},
+ TracesAfterDisconnect =
+ #{
+ call =>
+ [{" (client) -> ssl:close/1", ssl, close},
+ {" (client) -> ssl:close/1", ssl, close},
+ {" (client) -> ssl_gen_statem:close/2", ssl_gen_statem, close},
+ {" (client) -> ssl_gen_statem:terminate_alert/1",
+ ssl_gen_statem, terminate_alert},
+ {" (server) -> ssl:close/1", ssl, close},
+ {" (server) -> ssl_gen_statem:close/2", ssl_gen_statem, close},
+ {" (server) -> ssl_gen_statem:terminate_alert/1",
+ ssl_gen_statem, terminate_alert}],
+ return_from =>
+ [{" (client) <- ssl:close/1 returned", ssl, close},
+ {" (client) <- ssl:close/1 returned", ssl, close},
+ {" (client) <- ssl_gen_statem:close/2 returned",
+ ssl_gen_statem, close},
+ {" (client) <- ssl_gen_statem:terminate_alert/1 returned",
+ ssl_gen_statem, terminate_alert},
+ {" (server) <- ssl:close/1 returned", ssl, close},
+ {" (server) <- ssl_gen_statem:close/2 returned",
+ ssl_gen_statem, close},
+ {" (server) <- ssl_gen_statem:terminate_alert/1 returned",
+ ssl_gen_statem, terminate_alert}],
+ exception_from =>
+ [{" (server) exception_from ssl_gen_statem:init/1 {exit,{shutdown,normal}}",
+ ssl_gen_statem, init},
+ {" (client) exception_from ssl_gen_statem:init/1 {exit,{shutdown,normal}}",
+ ssl_gen_statem, init}]},
+ Ref = ssl_trace_start(),
+ {ok, On} = ssl_trace:on(On),
+ Delta = On -- Off,
+ {ok, Delta} = ssl_trace:off(Off),
+ [Server, Client] = ssl_connect(Config),
+ UnhandledTraceCnt1 =
+ #{call => 2, processed => 0, exception_from => no_trace_received,
+ return_from => 2},
+ check_trace_map(Ref, TracesAfterConnect, UnhandledTraceCnt1),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ UnhandledTraceCnt2 =
+ #{call => 0, processed => no_trace_received, exception_from => 0,
+ return_from => 0},
+ check_trace_map(Ref, TracesAfterDisconnect, UnhandledTraceCnt2),
+ ssl_trace:stop(),
+ ok.
+
+tc_rle_profile() ->
+ [{doc, "Verify traces for 'rle' trace profile"}].
+tc_rle_profile(Config) ->
+ On = [rle],
+ ExpectedTraces =
+ #{
+ call =>
+ [],
+ return_from =>
+ [{" (client) <- ssl:connect/3 returned", ssl, connect},
+ {" (server) <- ssl:listen/2 returned", ssl, listen},
+ {" (client) <- tls_sender:init/3 returned", tls_sender, init},
+ {" (server) <- tls_sender:init/3 returned", tls_sender, init}],
+ processed =>
+ ["rle ('?') -> ssl:listen/2 (*server) Args =",
+ "rle ('?') -> ssl:connect/3 (*client) Args",
+ "rle ('?') -> ssl_gen_statem:init/1 (*server) Args = [[server",
+ "rle ('?') -> ssl_gen_statem:init/1 (*client) Args = [[client",
+ "rle ('?') -> tls_sender:init/3 (*server)",
+ "rle ('?') -> tls_sender:init/3 (*client)"]},
+ Ref = ssl_trace_start(),
+ {ok, On} = ssl_trace:on(On),
+ [Server, Client] = ssl_connect(Config),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ UnhandledTraceCnt =
+ #{call => no_trace_received, processed => 0, exception_from => 2,
+ return_from => 0},
+ check_trace_map(Ref, ExpectedTraces, UnhandledTraceCnt),
+ ssl_trace:stop(),
+ ok.
+
+tc_budget_option() ->
+ [{doc, "Verify that budget option limits amount of traces"}].
+tc_budget_option(Config) ->
+ Ref = ssl_trace_start(make_ref(), [{budget, 10}]),
+ {ok, [api,rle]} = ssl_trace:on([api,rle]),
+ ssl_trace:write("Not a trace from dbg - not included in budget", []),
+ [Server, Client] = ssl_connect(Config),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ CountReceived = fun(Reference) ->
+ ReceiveStats = check_trace_map(Reference, #{}),
+ ReceivedNumbers =
+ lists:filter(fun is_number/1,
+ maps:values(ReceiveStats)),
+ lists:sum(ReceivedNumbers)
+ end,
+ ssl_trace:stop(),
+ ExpectedTraceCnt = 10,
+ ActualTraceCnt = CountReceived(Ref),
+ case ExpectedTraceCnt == ActualTraceCnt of
+ true ->
+ ok;
+ _ ->
+ ?CT_FAIL("Expected ~w traces, but found ~w",
+ [ExpectedTraceCnt, ActualTraceCnt])
+ end.
+
+tc_file_option() ->
+ [{doc, "Verify that file option redirects traces to file"}].
+tc_file_option(Config) ->
+ _Ref = ssl_trace_start(make_ref(), [{budget, 10}, file]),
+ {ok, [api,rle]} = ssl_trace:on([api,rle]),
+ [Server, Client] = ssl_connect(Config),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ ActualTraceCnt = count_line(?TRACE_FILE),
+ ExpectedTraceCnt = 11, %% budget + 1 message about end of budget
+ ssl_trace:stop(),
+ case ExpectedTraceCnt == ActualTraceCnt of
+ true ->
+ ok;
+ _ ->
+ ?CT_FAIL("Expected ~w traces, but found ~w",
+ [ExpectedTraceCnt, ActualTraceCnt])
+ end.
+
+tc_write() ->
+ [{doc, "Verify that custom messages can be written"}].
+tc_write(_Config) ->
+ _Ref = ssl_trace_start(make_ref(), [{budget, 10}, file]),
+ {ok, [api,rle]} = ssl_trace:on([api,rle]),
+ ssl_trace:write("Custom trace message ~w", [msg]),
+ ActualTraceCnt = count_line(?TRACE_FILE),
+ ExpectedTraceCnt = 1,
+ ssl_trace:stop(),
+ case ExpectedTraceCnt == ActualTraceCnt of
+ true ->
+ ok;
+ _ ->
+ ?CT_FAIL("Expected ~w traces, but found ~w",
+ [ExpectedTraceCnt, ActualTraceCnt])
+ end.
+
+tc_check_profiles() ->
+ [{doc, "Verify that trace profile contain valid functions"}].
+tc_check_profiles(_Config) ->
+ CheckFun =
+ fun(Profile, Module, Fun, DefinedFunctions) ->
+ case lists:member(Fun, DefinedFunctions) of
+ true -> ok;
+ _ ->
+ {F, A} = Fun,
+ ct:fail("~w:~w/~w from '~w' trace profile not found",
+ [Module, F, A, Profile])
+ end
+ end,
+ CheckModule =
+ fun(Profile, {Module, Funs}) ->
+ DefinedFunctions = Module:module_info(functions),
+ [CheckFun(Profile, Module, F, DefinedFunctions) ||
+ F <- Funs]
+ end,
+ CheckTProfile =
+ fun({Profile, _, _, ModFunsTuples}) ->
+ [CheckModule(Profile, MFTuple) ||
+ MFTuple <- ModFunsTuples]
+ end,
+ [CheckTProfile(P) || P <- ssl_trace:trace_profiles()],
+ ok.
+
+%%%----------------------------------------------------------------
+ssl_trace_start() ->
+ ssl_trace_start(make_ref(), []).
+
+ssl_trace_start(Ref, TraceOpts) ->
+ TestProcess = self(),
+ {ok, [_|_]} = ssl_trace:start(fun(Format,Args) ->
+ ct:log(Format, Args),
+ TestProcess ! {Ref, Args}
+ end,
+ TraceOpts),
+ Ref.
+
+receive_map(Ref) ->
+ Empty = #{call => [], return_from => [], exception_from => [],
+ processed => []},
+ receive_map(Ref, Empty).
+
+receive_map(Ref,
+ Map = #{call := Call, return_from := Return,
+ exception_from := Exception, processed := Processed}) ->
+ receive
+ {Ref, Msg = [_, {call, {_, _, _}}, _]} ->
+ receive_map(Ref, Map#{call => [Msg|Call]});
+ {Ref, Msg = [_, {return_from, {_, _, _}, _}, _]} ->
+ receive_map(Ref, Map#{return_from => [Msg|Return]});
+ {Ref, Msg = [_, {exception_from, {_, _, _}, _}, _]} ->
+ receive_map(Ref, Map#{exception_from => [Msg|Exception]});
+ {Ref, Msg = [_Timestamp, _Pid, _ExpectString]} ->
+ %% processed means a trace was processed by Module:handle_trace
+ %% function and is not received as a trace tuple
+ receive_map(Ref, Map#{processed => [Msg|Processed]})
+ after 5000 ->
+ Map
+ end.
+
+check_trace_map(Ref, ExpectedTraces) ->
+ Received = receive_map(Ref),
+ L = [check_key(Type, ExpectedTraces, maps:get(Type, Received)) ||
+ Type <- maps:keys(Received)],
+ maps:from_list(L).
+
+check_trace_map(Ref, ExpectedTraces, ExpectedRemainders) ->
+ ActualRemainders = check_trace_map(Ref, ExpectedTraces),
+ case ExpectedRemainders == ActualRemainders of
+ true ->
+ ok;
+ _ ->
+ ?CT_FAIL("Expected trace remainders = ~w ~n"
+ "Actual trace remainders = ~w",
+ [ExpectedRemainders, ActualRemainders])
+ end.
+
+check_key(Type, ExpectedTraces, ReceivedPerType) ->
+ ReceivedPerTypeCnt = length(ReceivedPerType),
+ ?CT_LOG("Received Type = ~w Messages# = ~w", [Type, ReceivedPerTypeCnt]),
+ case ReceivedPerTypeCnt > 0 of
+ true ->
+ ExpectedPerType = maps:get(Type, ExpectedTraces, []),
+ ExpectedPerTypeCnt = length(ExpectedPerType),
+ check_trace(Type, ExpectedPerType, ReceivedPerType),
+ {Type, ReceivedPerTypeCnt - ExpectedPerTypeCnt};
+ _ ->
+ {Type, no_trace_received}
+ end.
+
+-define(CHECK_TRACE(PATTERN, Expected),
+ fun({ExpectedString, Module, Function}) ->
+ P2 = fun(Received) ->
+ PATTERN = Received,
+ SearchResult =
+ string:str(lists:flatten(Txt), ExpectedString),
+ case {Module == M, Function == F, SearchResult > 0} of
+ {true, true, true} ->
+ true;
+ _ -> false
+ end
+ end,
+ Result = lists:any(P2, ReceivedPerType),
+ case Result of
+ false ->
+ F = "Trace not found: {~s, ~w, ~w}",
+ ?CT_FAIL(F, [ExpectedString, Module, Function]);
+ _ -> ok
+ end,
+ Result
+ end).
+
+-define(CHECK_PROCESSED_TRACE(PATTERN, Expected),
+ fun(ExpectedString) ->
+ P2 = fun(Received) ->
+ PATTERN = Received,
+ SearchResult =
+ string:str(lists:flatten(Txt), ExpectedString),
+ SearchResult > 0
+ end,
+ Result = lists:any(P2, ReceivedPerType),
+ case Result of
+ false ->
+ F = "Processed trace not found: ~s",
+ ?CT_FAIL(F, [ExpectedString]);
+ _ -> ok
+ end,
+ Result
+ end).
+
+check_trace(call, ExpectedPerType, ReceivedPerType) ->
+ P1 = ?CHECK_TRACE([Txt, {call, {M, F, _Args}}, _], Expected),
+ true = lists:all(P1, ExpectedPerType);
+check_trace(return_from, ExpectedPerType, ReceivedPerType) ->
+ P1 = ?CHECK_TRACE([Txt, {return_from, {M, F, _Args}, _Return}, _], Expected),
+ true = lists:all(P1, ExpectedPerType);
+check_trace(exception_from, ExpectedPerType, ReceivedPerType) ->
+ P1 = ?CHECK_TRACE([Txt, {exception_from, {M, F, _Args}, _Return}, _], Expected),
+ true = lists:all(P1, ExpectedPerType);
+check_trace(processed, ExpectedPerType, ReceivedPerType) ->
+ P1 = ?CHECK_PROCESSED_TRACE([_Timestamp, _Pid, Txt], Expected),
+ true = lists:all(P1, ExpectedPerType);
+check_trace(Type, _ExpectedPerType, _ReceivedPerType) ->
+ ?CT_FAIL("Type = ~w not checked", [Type]),
+ ok.
+
+count_line(Filename) ->
+ case file:open(Filename, [read]) of
+ {ok, IoDevice} ->
+ Count = count_line(IoDevice, 0),
+ file:close(IoDevice),
+ Count;
+ {error, Reason} ->
+ ?CT_PAL("~s open error reason:~s~n", [Filename, Reason]),
+ ct:fail(Reason)
+ end.
+
+count_line(IoDevice, Count) ->
+ case file:read_line(IoDevice) of
+ {ok, _} -> count_line(IoDevice, Count+1);
+ eof -> Count
+ end.
+
+ssl_connect(Config) when is_list(Config) ->
+ ?CT_LOG("Establishing connection for producing traces", []),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result, []}},
+ {options, [{keepalive, true},{active, false}
+ | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result, []}},
+ {options, [{keepalive, true},{active, false}
+ | ClientOpts]}]),
+ ?CT_LOG("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]),
+ [Server, Client].
diff --git a/lib/ssl/test/ssl_use_srtp_SUITE.erl b/lib/ssl/test/ssl_use_srtp_SUITE.erl
new file mode 100644
index 0000000000..a3397ce403
--- /dev/null
+++ b/lib/ssl/test/ssl_use_srtp_SUITE.erl
@@ -0,0 +1,176 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ssl_use_srtp_SUITE).
+
+-behaviour(ct_suite).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/inet.hrl").
+
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([srtp_profiles/1,
+ srtp_mki/1
+ ]).
+
+-define(TIMEOUT, {seconds, 6}).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+all() ->
+ [
+ {group, 'dtlsv1.2'},
+ {group, 'dtlsv1'}
+ ].
+
+groups() ->
+ [
+ {'dtlsv1.2', [], use_srtp_tests()},
+ {'dtlsv1', [], use_srtp_tests()}
+ ].
+
+use_srtp_tests() ->
+ [
+ srtp_profiles,
+ srtp_mki
+ ].
+
+init_per_suite(Config0) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ {#{server_config := _ServerConf,
+ client_config := ClientConf},
+ #{server_config := _LServerConf,
+ client_config := LClientConf}} = ssl_test_lib:make_rsa_sni_configs(),
+ %% RSA certs files needed by *dot cases
+ ssl_test_lib:make_rsa_cert([{client_opts, ClientConf},
+ {client_local_opts, LClientConf}
+ | Config0])
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+init_per_group(GroupName, Config) ->
+ ssl_test_lib:init_per_group(GroupName, Config).
+
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
+
+end_per_suite(_) ->
+ ssl:stop(),
+ application:stop(crypto).
+
+init_per_testcase(_TestCase, Config) ->
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap(?TIMEOUT),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+srtp_profiles(Config) ->
+ % Client sends a list of SRTP profiles it supports in client_hello
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ClentSrtpOpts = [{use_srtp, #{protection_profiles => [<<0,1>>,<<0,2>>,<<0,5>>]}}],
+ ClientOpts = ClentSrtpOpts ++ [{handshake, hello}] ++ ClientOpts0,
+ ClientContOpts = [{continue_options, [{want_ext, self()}]}],
+ % Server responds with a single chosen profile in server_hello
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = [{handshake, hello}] ++ ServerOpts0,
+ ServerSrtpOts = [{use_srtp, #{protection_profiles => [<<0,2>>]}}],
+ ServerContOpts = [{continue_options, [{want_ext, self()}|ServerSrtpOts]}],
+
+ Server = ssl_test_lib:start_server(ServerContOpts,
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{port, Port} | ClientContOpts],
+ [{client_opts, ClientOpts} | Config]),
+
+ receive
+ {Server, {ext, C2SExt}} ->
+ C2SSRTP = maps:get(use_srtp, C2SExt),
+ #{protection_profiles := [<<0,1>>,<<0,2>>,<<0,5>>]} = C2SSRTP,
+ #{mki := <<>>} = C2SSRTP,
+ ssl_test_lib:close(Server)
+ end,
+ receive
+ {Client, {ext, S2CExt}} ->
+ S2CSRTP = maps:get(use_srtp, S2CExt),
+ #{protection_profiles := [<<0,2>>]} = S2CSRTP,
+ #{mki := <<>>} = S2CSRTP,
+ ssl_test_lib:close(Client)
+ end,
+ ok.
+
+
+srtp_mki(Config) ->
+ % Client sends some MKI in a client_hello
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ClientSrtpOpts = [{use_srtp, #{protection_profiles => [<<0,1>>,<<0,2>>,<<0,5>>],
+ mki => <<"client_mki">>}}],
+ ClientOpts = ClientSrtpOpts ++ [{handshake, hello}] ++ ClientOpts0,
+ ClientContOpts = [{continue_options, [{want_ext, self()}]}],
+ % Server responds with its own MKI just to ensure it is delivered to the client
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = [{handshake, hello}] ++ ServerOpts0,
+ ServerSrtpOpts = [{use_srtp, #{protection_profiles => [<<0,2>>],
+ mki => <<"server_mki">>}}],
+ ServerContOpts = [{continue_options, [{want_ext, self()}|ServerSrtpOpts]}],
+
+ Server = ssl_test_lib:start_server(ServerContOpts,
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client(
+ [{port, Port}, {options, ClientOpts} | ClientContOpts], Config),
+
+ receive
+ {Server, {ext, C2SExt}} ->
+ C2SSRTP = maps:get(use_srtp, C2SExt),
+ #{mki := <<"client_mki">>} = C2SSRTP,
+ ssl_test_lib:close(Server)
+ end,
+ receive
+ {Client, {ext, S2CExt}} ->
+ S2CSRTP = maps:get(use_srtp, S2CExt),
+ #{mki := <<"server_mki">>} = S2CSRTP,
+ ssl_test_lib:close(Client)
+ end,
+ ok.
diff --git a/lib/ssl/test/tls_1_3_record_SUITE.erl b/lib/ssl/test/tls_1_3_record_SUITE.erl
index 75819d0565..c08bd90a02 100644
--- a/lib/ssl/test/tls_1_3_record_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_record_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -174,9 +174,9 @@ encode_decode(_Config) ->
146,152,146,151,107,126,216,210,9,93,0,0>>],
{[_Header|Encoded], _} = tls_record_1_3:encode_plain_text(22, PlainText, ConnectionStates),
- CipherText = #ssl_tls{type = 23, version = {3,3}, fragment = Encoded},
+ CipherText = #ssl_tls{type = 23, version = ?TLS_1_2, fragment = Encoded},
- {#ssl_tls{type = 22, version = {3,4}, fragment = DecodedText}, _} =
+ {#ssl_tls{type = 22, version = ?TLS_1_3, fragment = DecodedText}, _} =
tls_record_1_3:decode_cipher_text(CipherText, ConnectionStates),
DecodedText = iolist_to_binary(PlainText),
@@ -260,7 +260,7 @@ encode_decode(_Config) ->
01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"),
{CHEncrypted, _} =
- tls_record:encode_handshake(ClientHello, {3,4}, ConnStatesNull),
+ tls_record:encode_handshake(ClientHello, ?TLS_1_3, ConnStatesNull),
ClientHelloRecord = iolist_to_binary(CHEncrypted),
%% {server} extract secret "early":
@@ -515,7 +515,7 @@ encode_decode(_Config) ->
cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"),
{SHEncrypted, _} =
- tls_record:encode_handshake(ServerHello, {3,4}, ConnStatesNull),
+ tls_record:encode_handshake(ServerHello, ?TLS_1_3, ConnStatesNull),
ServerHelloRecord = iolist_to_binary(SHEncrypted),
%% {server} derive write traffic keys for handshake data:
@@ -685,7 +685,7 @@ encode_decode(_Config) ->
FinishedHS = #finished{verify_data = FinishedVerifyData},
- FinishedIOList = tls_handshake:encode_handshake(FinishedHS, {3,4}),
+ FinishedIOList = tls_handshake:encode_handshake(FinishedHS, ?TLS_1_3),
FinishedHSBin = iolist_to_binary(FinishedIOList),
%% {server} derive secret "tls13 c ap traffic":
@@ -907,7 +907,7 @@ encode_decode(_Config) ->
CFinished = #finished{verify_data = CFinishedVerifyData},
- CFinishedIOList = tls_handshake:encode_handshake(CFinished, {3,4}),
+ CFinishedIOList = tls_handshake:encode_handshake(CFinished, ?TLS_1_3),
CFinishedBin = iolist_to_binary(CFinishedIOList),
%% {client} derive write traffic keys for application data:
@@ -1054,7 +1054,7 @@ encode_decode(_Config) ->
ticket_nonce = Nonce,
ticket = Ticket,
extensions = _Extensions
- } = tls_handshake:decode_handshake({3,4}, NWT, TicketBody),
+ } = tls_handshake:decode_handshake(?TLS_1_3, NWT, TicketBody),
%% ResPRK = resumption master secret
ResExpanded = tls_v1:pre_shared_key(ResPRK, Nonce, HKDFAlgo),
@@ -1288,7 +1288,7 @@ encode_decode(_Config) ->
<<?BYTE(CH), ?UINT24(_Length), ClientHelloBody/binary>> = ClientHelloRecord,
#client_hello{extensions = #{pre_shared_key := PreSharedKey}} =
- tls_handshake:decode_handshake({3,4}, CH, ClientHelloBody),
+ tls_handshake:decode_handshake(?TLS_1_3, CH, ClientHelloBody),
#pre_shared_key_client_hello{
offered_psks = #offered_psks{
diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl
index 8a3ff288f7..b5aa0d3cad 100644
--- a/lib/ssl/test/tls_1_3_version_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_version_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -57,7 +57,9 @@
middle_box_tls12_enabled_client/0,
middle_box_tls12_enabled_client/1,
middle_box_client_tls_v2_session_reused/0,
- middle_box_client_tls_v2_session_reused/1
+ middle_box_client_tls_v2_session_reused/1,
+ renegotiate_error/0,
+ renegotiate_error/1
]).
@@ -90,7 +92,8 @@ tls_1_3_1_2_tests() ->
tls12_client_tls_server,
middle_box_tls13_client,
middle_box_tls12_enabled_client,
- middle_box_client_tls_v2_session_reused
+ middle_box_client_tls_v2_session_reused,
+ renegotiate_error
].
legacy_tests() ->
[tls_client_tls10_server,
@@ -329,6 +332,26 @@ middle_box_client_tls_v2_session_reused(Config) when is_list(Config) ->
{reuse_session, {SessionId, SessData}} | ClientOpts]}]),
{ok,[{session_id, SessionId}]} = ssl:connection_information(CSock1, [session_id]).
+renegotiate_error() ->
+ [{doc, "Test that an error is returned when ssl:renegotiate/1 is called on a connection running TLS-1.3"}].
+renegotiate_error(Config) when is_list(Config) ->
+ {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{versions, ['tlsv1.3']} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Options = [{versions, ['tlsv1.3']} | ClientOpts],
+ case ssl:connect(Hostname, Port, Options) of
+ {ok, Socket} ->
+ {error, notsup} = ssl:renegotiate(Socket);
+ {error, Reason} ->
+ ct:fail(Reason)
+ end.
+
%%--------------------------------------------------------------------
%% Internal functions and callbacks -----------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl
index ccba623861..74b4e76566 100644
--- a/lib/ssl/test/tls_api_SUITE.erl
+++ b/lib/ssl/test/tls_api_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -109,7 +109,9 @@
accept_pool/0,
accept_pool/1,
reuseaddr/0,
- reuseaddr/1
+ reuseaddr/1,
+ signature_algs/0,
+ signature_algs/1
]).
%% Apply export
@@ -296,7 +298,7 @@ tls_upgrade_new_opts_with_sni_fun() ->
[{doc,"Test that you can upgrade an tcp connection to an ssl connection with new versions option provided by sni_fun"}].
tls_upgrade_new_opts_with_sni_fun(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
TcpOpts = [binary, {reuseaddr, true}],
@@ -322,8 +324,7 @@ tls_upgrade_new_opts_with_sni_fun(Config) when is_list(Config) ->
{from, self()},
{mfa, {?MODULE, upgrade_result, []}},
{tcp_options, [binary]},
- {ssl_options, [{verify, verify_peer},
- {versions, [Version |NewVersions]},
+ {ssl_options, [{versions, [Version |NewVersions]},
{ciphers, Ciphers},
{server_name_indication, Hostname} | ClientOpts]}]),
@@ -789,10 +790,10 @@ tls_reject_warning_alert_in_initial_hs() ->
[{doc,"Test sending warning ALERT instead of client hello"}].
tls_reject_warning_alert_in_initial_hs(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+ {_Clientnode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
{Major, Minor} = case ssl_test_lib:protocol_version(Config, tuple) of
- {3,4} ->
- {3,3};
+ ?TLS_1_3 ->
+ ?TLS_1_2;
Other ->
Other
end,
@@ -815,8 +816,8 @@ tls_reject_fake_warning_alert_in_initial_hs(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
{Major, Minor} = case ssl_test_lib:protocol_version(Config, tuple) of
- {3,4} ->
- {3,3};
+ ?TLS_1_3 ->
+ ?TLS_1_2;
Other ->
Other
end,
@@ -841,8 +842,8 @@ tls_app_data_in_initial_hs_state(Config) when is_list(Config) ->
{_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
Version = ssl_test_lib:protocol_version(Config, tuple),
{Major, Minor} = case Version of
- {3,4} ->
- {3,3};
+ ?TLS_1_3 ->
+ ?TLS_1_2;
Other ->
Other
end,
@@ -853,7 +854,7 @@ tls_app_data_in_initial_hs_state(Config) when is_list(Config) ->
Port = ssl_test_lib:inet_port(Server),
{ok, Socket} = gen_tcp:connect("localhost", Port, [{active, false}, binary]),
AppData = case Version of
- {3, 4} ->
+ ?TLS_1_3 ->
<<?BYTE(?APPLICATION_DATA), ?BYTE(3), ?BYTE(3), ?UINT16(4), ?BYTE($F),
?BYTE($O), ?BYTE($O), ?BYTE(?APPLICATION_DATA)>>;
_ ->
@@ -1308,6 +1309,24 @@ tls_password_badarg(Config) when is_list(Config) ->
{error, {keyfile,badarg}}).
%%--------------------------------------------------------------------
+signature_algs() ->
+ [{doc, "Check that listing of signature algorithms for different version and configure combinations"}].
+signature_algs(Config) when is_list(Config) ->
+ true = [] =/= [Alg || Alg <- ssl:signature_algs(default, 'tlsv1.3'), is_tuple(Alg)],
+ true = [] == [Alg || Alg <- ssl:signature_algs(exclusive, 'tlsv1.3'), is_tuple(Alg)],
+ true = ssl:signature_algs(exclusive, 'tlsv1.3') =/= ssl:signature_algs(exclusive, 'tlsv1.2'),
+ true = length(ssl:signature_algs(defalt, 'tlsv1.2')) <
+ length(ssl:signature_algs(all, 'tlsv1.2')),
+ TLS_1_3_All = ssl:signature_algs(all, 'tlsv1.3'),
+ true = lists:member(rsa_pkcs1_sha512, TLS_1_3_All) andalso (not lists:member({sha512, rsa}, TLS_1_3_All)),
+ true = lists:member(rsa_pkcs1_sha384, TLS_1_3_All) andalso (not lists:member({sha384, rsa}, TLS_1_3_All)),
+ true = lists:member(rsa_pkcs1_sha256, TLS_1_3_All) andalso (not lists:member({sha256, rsa}, TLS_1_3_All)),
+ TLS_1_2_All = ssl:signature_algs(all, 'tlsv1.2'),
+ true = (not lists:member(rsa_pkcs1_sha512, TLS_1_2_All)) andalso lists:member({sha512, rsa}, TLS_1_2_All),
+ true = (not lists:member(rsa_pkcs1_sha384, TLS_1_2_All)) andalso lists:member({sha384, rsa}, TLS_1_2_All),
+ true = (not lists:member(rsa_pkcs1_sha256, TLS_1_2_All)) andalso lists:member({sha256, rsa}, TLS_1_2_All).
+
+%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/tls_server_session_ticket_SUITE.erl b/lib/ssl/test/tls_server_session_ticket_SUITE.erl
index 954afa9fb5..3f5b0f71b2 100644
--- a/lib/ssl/test/tls_server_session_ticket_SUITE.erl
+++ b/lib/ssl/test/tls_server_session_ticket_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
-include_lib("ssl/src/ssl_cipher.hrl").
-include_lib("ssl/src/ssl_internal.hrl").
-include_lib("ssl/src/tls_handshake_1_3.hrl").
+-include("ssl_record.hrl").
%% Callback functions
-export([all/0,
@@ -43,25 +44,38 @@
main_test/0,
main_test/1,
misc_test/0,
- misc_test/1]).
+ misc_test/1,
+ valid_ticket_older_than_windowsize_test/0,
+ valid_ticket_older_than_windowsize_test/1,
+ certificate_encoding_test/0,
+ certificate_encoding_test/1]).
--define(LIFETIME, 1). % tickets expire after 1s
+-define(LIFETIME, 3). % tickets expire after 3s
-define(TICKET_STORE_SIZE, 1).
-define(MASTER_SECRET, "master_secret").
-define(PRF, sha).
--define(VERSION, {3,4}).
+-define(VERSION, ?TLS_1_3).
-define(PSK, <<15,168,18,43,216,33,227,142,114,190,70,183,137,57,64,64,66,152,115,94>>).
+-define(WINDOW_SIZE, 1).
+-define(SEED, <<1,2,3,4,5>>).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
all() ->
- [{group, stateful}, {group, stateless}, {group, stateless_antireplay}].
+ [{group, stateful},
+ {group, stateful_with_cert},
+ {group, stateless},
+ {group, stateless_with_cert},
+ {group, stateless_antireplay}
+ ].
groups() ->
[{stateful, [], [main_test, expired_ticket_test, invalid_ticket_test]},
- {stateless, [], [expired_ticket_test, invalid_ticket_test, main_test]},
- {stateless_antireplay, [], [main_test, misc_test]}
+ {stateful_with_cert, [], [main_test, expired_ticket_test, invalid_ticket_test]},
+ {stateless, [], [expired_ticket_test, invalid_ticket_test, main_test, certificate_encoding_test]},
+ {stateless_with_cert, [], [expired_ticket_test, invalid_ticket_test, main_test, certificate_encoding_test]},
+ {stateless_antireplay, [], [main_test, misc_test, valid_ticket_older_than_windowsize_test, certificate_encoding_test]}
].
init_per_suite(Config0) ->
@@ -80,11 +94,13 @@ end_per_suite(_Config) ->
init_per_group(stateless_antireplay, Config) ->
check_environment([{server_session_tickets, stateless},
- {anti_replay, {10, 20, 30}}]
+ {anti_replay, {?WINDOW_SIZE, 20, 30}}]
++ Config);
-init_per_group(Group = stateless, Config) ->
+init_per_group(Group, Config)
+ when Group == stateless orelse Group == stateless_with_cert ->
check_environment([{server_session_tickets, Group} | Config]);
-init_per_group(Group = stateful, Config) ->
+init_per_group(Group, Config)
+ when Group == stateful orelse Group == stateful_with_cert ->
[{server_session_tickets, Group} | Config].
end_per_group(_GroupName, Config) ->
@@ -92,10 +108,17 @@ end_per_group(_GroupName, Config) ->
init_per_testcase(_TestCase, Config) ->
{ok, ListenSocket} = gen_tcp:listen(0, [{active, false}]),
+ AntiReplay = ?config(anti_replay, Config),
{ok, Pid} = tls_server_session_ticket:start_link(
ListenSocket, ?config(server_session_tickets, Config),
?LIFETIME, ?TICKET_STORE_SIZE, _MaxEarlyDataSize = 100,
- ?config(anti_replay, Config)),
+ AntiReplay, ?SEED),
+ % For all anti-replay test-cases we will sleep longer than the warmup period
+ case AntiReplay of
+ undefined -> undefined;
+ _ ->
+ ct:sleep({seconds, 2 * ?LIFETIME})
+ end,
[{server_pid, Pid}, {listen_socket, ListenSocket} | Config].
end_per_testcase(_TestCase, Config) ->
@@ -113,17 +136,17 @@ main_test() ->
main_test(Config) when is_list(Config) ->
Pid = ?config(server_pid, Config),
% Fill in GB tree store for stateful setup
- tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET),
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
% Reach ticket store size limit - force GB tree pruning
SessionTicket = #new_session_ticket{} =
- tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET),
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
TicketRecvTime = erlang:system_time(millisecond),
%% Sleep more than the ticket lifetime (which is in seconds) in
%% milliseconds, to confirm that the client reported age (which is in
%% milliseconds) is compared correctly with the lifetime
ct:sleep(5 * ?LIFETIME),
{HandshakeHist, OferredPsks} = get_handshake_hist(SessionTicket, TicketRecvTime, ?PSK),
- AcceptResponse = {ok, {0, ?PSK}},
+ AcceptResponse = {ok, {0, ?PSK, undefined}},
AcceptResponse = tls_server_session_ticket:use(Pid, OferredPsks, ?PRF,
[iolist_to_binary(HandshakeHist)]),
% check replay attempt result
@@ -137,7 +160,7 @@ invalid_ticket_test() ->
invalid_ticket_test(Config) when is_list(Config) ->
Pid = ?config(server_pid, Config),
#new_session_ticket{ticket=Ticket} =
- tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET),
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
Ids = [#psk_identity{identity = <<"wrongidentity">>,
obfuscated_ticket_age = 0},
#psk_identity{identity = Ticket,
@@ -159,7 +182,7 @@ expired_ticket_test() ->
[{doc, "Expired ticket scenario"}].
expired_ticket_test(Config) when is_list(Config) ->
Pid = ?config(server_pid, Config),
- SessionTicket = tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET),
+ SessionTicket = tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
TicketRecvTime = erlang:system_time(millisecond),
ct:sleep({seconds, 2 * ?LIFETIME}),
{HandshakeHist, OFPSKs} = get_handshake_hist(SessionTicket, TicketRecvTime, ?PSK),
@@ -167,6 +190,29 @@ expired_ticket_test(Config) when is_list(Config) ->
[iolist_to_binary(HandshakeHist)]),
true = is_process_alive(Pid).
+valid_ticket_older_than_windowsize_test() ->
+ [{doc, "Verify valid ticket handling of tickets older than WindowSize"}].
+
+valid_ticket_older_than_windowsize_test(Config) when is_list(Config) ->
+ Pid = ?config(server_pid, Config),
+ % Fill in GB tree store for stateful setup (Stateless tests also fail without this)
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
+ % Reach ticket store size limit - force GB tree pruning
+ SessionTicket = #new_session_ticket{} =
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
+ TicketRecvTime = erlang:system_time(millisecond),
+ %% Sleep more than the window length (which is in seconds)
+ ct:sleep({seconds, 2 * ?WINDOW_SIZE}),
+ {HandshakeHist, OferredPsks} = get_handshake_hist(SessionTicket, TicketRecvTime, ?PSK),
+ AcceptResponse = {ok, {0, ?PSK, undefined}},
+ AcceptResponse = tls_server_session_ticket:use(Pid, OferredPsks, ?PRF,
+ [iolist_to_binary(HandshakeHist)]),
+ % check replay attempt result
+ ExpReplyResult = get_replay_expected_result(Config, AcceptResponse),
+ ExpReplyResult = tls_server_session_ticket:use(Pid, OferredPsks, ?PRF,
+ [iolist_to_binary(HandshakeHist)]),
+ true = is_process_alive(Pid).
+
misc_test() ->
[{doc, "Miscellaneous functionality"}].
misc_test(Config) when is_list(Config) ->
@@ -178,6 +224,22 @@ misc_test(Config) when is_list(Config) ->
Pid = tls_server_session_ticket:format_status(not_relevant, Pid),
true = is_process_alive(Pid).
+certificate_encoding_test() ->
+ [{doc, "Verify certifcate encoding/decoding in ticket"}].
+
+certificate_encoding_test(Config) when is_list(Config) ->
+ Pid = ?config(server_pid, Config),
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
+ Certificate = crypto:strong_rand_bytes(100),
+ SessionTicket = #new_session_ticket{} =
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, Certificate),
+ TicketRecvTime = erlang:system_time(millisecond),
+ {HandshakeHist, OferredPsks} = get_handshake_hist(SessionTicket, TicketRecvTime, ?PSK),
+ AcceptResponse = {ok, {0, ?PSK, Certificate}},
+ AcceptResponse = tls_server_session_ticket:use(Pid, OferredPsks, ?PRF,
+ [iolist_to_binary(HandshakeHist)]),
+ true = is_process_alive(Pid).
+
%%--------------------------------------------------------------------
%% Helpers -----------------------------------------------------------
%%--------------------------------------------------------------------
@@ -214,6 +276,9 @@ get_replay_expected_result(Config, AcceptResponse) ->
stateless ->
% no protection - replayed ticket is accepted
AcceptResponse;
+ stateless_with_cert ->
+ % no protection - replayed ticket is accepted
+ AcceptResponse;
_ ->
{ok, undefined}
end.
@@ -222,6 +287,8 @@ get_alert_reason(Config) ->
case get_group(Config) of
stateful ->
stateful;
+ stateful_with_cert ->
+ stateful;
_ ->
stateless
end.
diff --git a/lib/stdlib/Makefile b/lib/stdlib/Makefile
index f1db3c77e3..f1a72cd6b9 100644
--- a/lib/stdlib/Makefile
+++ b/lib/stdlib/Makefile
@@ -39,6 +39,7 @@ include $(ERL_TOP)/make/otp_subdir.mk
DIA_PLT_APPS=compiler crypto
TEST_NEEDS_RELEASE=true
+NO_TEST_TARGET:=1 # Avoid warning about ignoring old recipe for target 'test'
include $(ERL_TOP)/make/app_targets.mk
# Enable feature maybe_expr in runtime when running tests.
diff --git a/lib/stdlib/doc/src/Makefile b/lib/stdlib/doc/src/Makefile
index 8cd2ceb53c..d13fa47064 100644
--- a/lib/stdlib/doc/src/Makefile
+++ b/lib/stdlib/doc/src/Makefile
@@ -33,6 +33,7 @@ APPLICATION=stdlib
XML_APPLICATION_FILES = ref_man.xml
XML_REF3_FILES = \
+ argparse.xml \
array.xml \
base64.xml \
beam_lib.xml \
@@ -43,6 +44,7 @@ XML_REF3_FILES = \
dict.xml \
digraph.xml \
digraph_utils.xml \
+ edlin_expand.xml \
epp.xml \
erl_anno.xml \
erl_error.xmlsrc \
diff --git a/lib/stdlib/doc/src/argparse.xml b/lib/stdlib/doc/src/argparse.xml
new file mode 100644
index 0000000000..20e1f3a721
--- /dev/null
+++ b/lib/stdlib/doc/src/argparse.xml
@@ -0,0 +1,739 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<!-- %ExternalCopyright% -->
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2020</year><year>2023</year>
+ <holder>Maxim Fedorov</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>argparse</title>
+ <prepared>maximfca@gmail.com</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date></date>
+ <rev>A</rev>
+ <file>argparse.xml</file>
+ </header>
+ <module since="OTP 26.0">argparse</module>
+ <modulesummary>Command line arguments parser.</modulesummary>
+ <description>
+
+ <p>This module implements command line parser. Parser operates with
+ <em>commands</em> and <em>arguments</em> represented as a tree. Commands
+ are branches, and arguments are leaves of the tree. Parser always starts with the
+ root command, named after <c>progname</c> (the name of the program which started Erlang).
+ </p>
+
+ <p>
+ A <seetype marker="#command"><c>command specification</c></seetype> may contain handler
+ definition for each command, and a number argument specifications. When parser is
+ successful, <c>argparse</c> calls the matching handler, passing arguments extracted
+ from the command line. Arguments can be positional (occupying specific position in
+ the command line), and optional, residing anywhere but prefixed with a specified
+ character.
+ </p>
+
+ <p>
+ <c>argparse</c> automatically generates help and usage messages. It will also issue
+ errors when users give the program invalid arguments.
+ </p>
+
+ </description>
+
+ <section>
+ <title>Quick start</title>
+
+ <p><c>argparse</c> is designed to work with <seecom marker="erts:escript"><c>escript</c></seecom>.
+ The example below is a fully functioning Erlang program accepting two command line
+ arguments and printing their product.</p>
+
+ <code>
+#!/usr/bin/env escript
+
+main(Args) ->
+ argparse:run(Args, cli(), #{progname => mul}).
+
+cli() ->
+ #{
+ arguments => [
+ #{name => left, type => integer},
+ #{name => right, type => integer}
+ ],
+ handler =>
+ fun (#{left := Left, right := Right}) ->
+ io:format("~b~n", [Left * Right])
+ end
+ }.
+ </code>
+
+ <p>Running this script with no arguments results in an error, accompanied
+ by the usage information.</p>
+
+ <p>
+ The <c>cli</c> function defines a single command with embedded handler
+ accepting a map. Keys of the map are argument names as defined by
+ the <c>argument</c> field of the command, <c>left</c> and <c>right</c>
+ in the example. Values are taken from the command line, and converted
+ into integers, as requested by the type specification. Both arguments
+ in the example above are required (and therefore defined as positional).
+ </p>
+ </section>
+
+ <section>
+ <title>Command hierarchy</title>
+
+ <p>A command may contain nested commands, forming a hierarchy. Arguments
+ defined at the upper level command are automatically added to all nested
+ commands. Nested commands example (assuming <c>progname</c> is <c>nested</c>):
+ </p>
+
+ <code>
+cli() ->
+ #{
+ %% top level argument applicable to all commands
+ arguments => [#{name => top}],
+ commands => #{
+ "first" => #{
+ %% argument applicable to "first" command and
+ %% all commands nested into "first"
+ arguments => [#{name => mid}],
+ commands => #{
+ "second" => #{
+ %% argument only applicable for "second" command
+ arguments => [#{name => bottom}],
+ handler => fun (A) -> io:format("~p~n", [A]) end
+ }
+ }
+ }
+ }
+ }.
+ </code>
+
+ <p>In the example above, a 3-level hierarchy is defined. First is the script
+ itself (<c>nested</c>), accepting the only argument <c>top</c>. Since it
+ has no associated handler, <seemfa marker="#run/3">run/3</seemfa> will
+ not accept user input omitting nested command selection. For this example,
+ user has to supply 5 arguments in the command line, two being command
+ names, and another 3 - required positional arguments:</p>
+
+ <code>
+./nested.erl one first second two three
+#{top => "one",mid => "two",bottom => "three"}
+ </code>
+
+ <p>Commands have preference over positional argument values. In the example
+ above, commands and positional arguments are interleaving, and <c>argparse</c>
+ matches command name first.</p>
+
+ </section>
+
+ <section>
+ <title>Arguments</title>
+ <p><c>argparse</c> supports positional and optional arguments. Optional arguments,
+ or options for short, must be prefixed with a special character (<c>-</c> is the default
+ on all operating systems). Both options and positional arguments have 1 or more associated
+ values. See <seetype marker="#argument"><c>argument specification</c></seetype> to
+ find more details about supported combinations.</p>
+
+ <p>In the user input, short options may be concatenated with their values. Long
+ options support values separated by <c>=</c>. Consider this definition:</p>
+
+ <code>
+cli() ->
+ #{
+ arguments => [
+ #{name => long, long => "-long"},
+ #{name => short, short => $s}
+ ],
+ handler => fun (Args) -> io:format("~p~n", [Args]) end
+ }.
+ </code>
+
+ <p>Running <c>./args --long=VALUE</c> prints <c>#{long => "VALUE"}</c>, running
+ <c>./args -sVALUE</c> prints <c>#{short => "VALUE"}</c></p>
+
+ <p><c>argparse</c> supports boolean flags concatenation: it is possible to shorten
+ <c>-r -f -v</c> to <c>-rfv</c>.</p>
+
+ <p>Shortened option names are not supported: it is not possible to use <c>--my-argum</c>
+ instead of <c>--my-argument-name</c> even when such option can be unambiguously found.</p>
+ </section>
+
+ <datatypes>
+ <datatype>
+ <name name="arg_type"/>
+ <desc>
+ <p>Defines type conversion applied to the string retrieved from the user input.
+ If the conversion is successful, resulting value is validated using optional
+ <c>Choices</c>, or minimums and maximums (for integer and floating point values
+ only). Strings and binary values may be validated using regular expressions.
+ It's possible to define custom type conversion function, accepting a string
+ and returning Erlang term. If this function raises error with <c>badarg</c>
+ reason, argument is treated as invalid.
+ </p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="argument_help"/>
+ <desc>
+ <p>User-defined help template to print in the command usage. First element of
+ a tuple must be a string. It is printed as a part of the usage header. Second
+ element of the tuple can be either a string printed as-is, a list
+ containing strings, <c>type</c> and <c>default</c> atoms, or a user-defined
+ function that must return a string.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="argument_name"/>
+ <desc>
+ <p>Argument name is used to populate argument map.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="argument"/>
+ <desc>
+ <p>Argument specification. Defines a single named argument that is returned
+ in the <seetype marker="#arg_map"><c>argument map</c></seetype>. The only
+ required field is <c>name</c>, all other fields have defaults.</p>
+ <p>If either of the <c>short</c> or <c>long</c> fields is specified, the
+ argument is treated as optional. Optional arguments do not have specific
+ order and may appear anywhere in the command line. Positional arguments
+ are ordered the same way as they appear in the arguments list of the command
+ specification.</p>
+ <p>By default, all positional arguments must be present in the command line.
+ The parser will return an error otherwise. Options, however, may be omitted,
+ in which case resulting argument map will either contain the default value,
+ or not have the key at all.</p>
+ <taglist>
+ <tag><c>name</c></tag>
+ <item>
+ <p>Sets the argument name in the parsed argument map. If <c>help</c> is not defined,
+ name is also used to generate the default usage message.
+ </p>
+ </item>
+ <tag><c>short</c></tag>
+ <item>
+ <p>Defines a short (single character) form of an optional argument.</p>
+ <code>
+%% Define a command accepting argument named myarg, with short form $a:
+1> Cmd = #{arguments => [#{name => myarg, short => $a}]}.
+%% Parse command line "-a str":
+2> {ok, ArgMap, _, _} = argparse:parse(["-a", "str"], Cmd), ArgMap.
+
+#{myarg => "str"}
+
+%% Option value can be concatenated with the switch: "-astr"
+3> {ok, ArgMap, _, _} = argparse:parse(["-astr"], Cmd), ArgMap.
+
+#{myarg => "str"}
+ </code>
+ <p>By default all options expect a single value following the option switch.
+ The only exception is an option of a boolean type.</p>
+ </item>
+ <tag><c>long</c></tag>
+ <item>
+ <p>Defines a long form of an optional argument.</p>
+ <code>
+1> Cmd = #{arguments => [#{name => myarg, long => "name"}]}.
+%% Parse command line "-name Erlang":
+2> {ok, ArgMap, _, _} = argparse:parse(["-name", "Erlang"], Cmd), ArgMap.
+
+#{myarg => "Erlang"}
+%% Or use "=" to separate the switch and the value:
+3> {ok, ArgMap, _, _} = argparse:parse(["-name=Erlang"], Cmd), ArgMap.
+
+#{myarg => "Erlang"}
+ </code>
+ <p>If neither <c>short</c> not <c>long</c> is defined, the
+ argument is treated as positional.</p>
+ </item>
+ <tag><c>required</c></tag>
+ <item>
+ <p>Forces the parser to expect the argument to be present in the
+ command line. By default, all positional argument are required,
+ and all options are not.</p>
+ </item>
+ <tag><c>default</c></tag>
+ <item>
+ <p>Specifies the default value to put in the parsed argument map
+ if the value is not supplied in the command line.</p>
+ <code>
+1> argparse:parse([], #{arguments => [#{name => myarg, short => $m}]}).
+
+{ok,#{}, ...
+2> argparse:parse([], #{arguments => [#{name => myarg, short => $m, default => "def"}]}).
+
+{ok,#{myarg => "def"}, ...
+ </code>
+ </item>
+ <tag><c>type</c></tag>
+ <item>
+ <p>Defines type conversion and validation routine. The default is <c>string</c>,
+ assuming no conversion.</p>
+ </item>
+ <tag><c>nargs</c></tag>
+ <item>
+ <p>Defines the number of following arguments to consume from the command line.
+ By default, the parser consumes the next argument and converts it into an
+ Erlang term according to the specified type.
+ </p>
+ <taglist>
+ <tag><c>pos_integer()</c></tag>
+ <item><p> Consume exactly this number of positional arguments, fail if there
+ is not enough. Value in the argument map contains a list of exactly this
+ length. Example, defining a positional argument expecting 3 integer values:</p>
+ <code>
+1> Cmd = #{arguments => [#{name => ints, type => integer, nargs => 3}]},
+argparse:parse(["1", "2", "3"], Cmd).
+
+{ok, #{ints => [1, 2, 3]}, ...
+ </code>
+ <p>Another example defining an option accepted as <c>-env</c> and
+ expecting two string arguments:</p>
+ <code>
+1> Cmd = #{arguments => [#{name => env, long => "env", nargs => 2}]},
+argparse:parse(["-env", "key", "value"], Cmd).
+
+{ok, #{env => ["key", "value"]}, ...
+ </code>
+ </item>
+ <tag><c>list</c></tag>
+ <item>
+ <p>Consume all following arguments until hitting the next option (starting
+ with an option prefix). May result in an empty list added to the arguments
+ map.</p>
+ <code>
+1> Cmd = #{arguments => [
+ #{name => nodes, long => "nodes", nargs => list},
+ #{name => verbose, short => $v, type => boolean}
+]},
+argparse:parse(["-nodes", "one", "two", "-v"], Cmd).
+
+{ok, #{nodes => ["one", "two"], verbose => true}, ...
+ </code>
+ </item>
+ <tag><c>nonempty_list</c></tag>
+ <item>
+ <p>Same as <c>list</c>, but expects at least one argument. Returns an error
+ if the following command line argument is an option switch (starting with the
+ prefix).</p>
+ </item>
+ <tag><c>'maybe'</c></tag>
+ <item>
+ <p>Consumes the next argument from the command line, if it does not start
+ with an option prefix. Otherwise, adds a default value to the arguments
+ map.</p>
+ <code>
+1> Cmd = #{arguments => [
+ #{name => level, short => $l, nargs => 'maybe', default => "error"},
+ #{name => verbose, short => $v, type => boolean}
+]},
+argparse:parse(["-l", "info", "-v"], Cmd).
+
+{ok,#{level => "info",verbose => true}, ...
+
+%% When "info" is omitted, argument maps receives the default "error"
+2> argparse:parse(["-l", "-v"], Cmd).
+
+{ok,#{level => "error",verbose => true}, ...
+ </code>
+ </item>
+ <tag><c>{'maybe', term()}</c></tag>
+ <item>
+ <p>Consumes the next argument from the command line, if it does not start
+ with an option prefix. Otherwise, adds a specified Erlang term to the
+ arguments map.</p>
+ </item>
+ <tag><c>all</c></tag>
+ <item>
+ <p>Fold all remaining command line arguments into a list, ignoring
+ any option prefixes or switches. Useful for proxying arguments
+ into another command line utility.</p>
+ <code>
+1> Cmd = #{arguments => [
+ #{name => verbose, short => $v, type => boolean},
+ #{name => raw, long => "-", nargs => all}
+]},
+argparse:parse(["-v", "--", "-kernel", "arg", "opt"], Cmd).
+
+{ok,#{raw => ["-kernel","arg","opt"],verbose => true}, ...
+ </code>
+ </item>
+ </taglist>
+ </item>
+ <tag><c>action</c></tag>
+ <item>
+ <p>Defines an action to take when the argument is found in the command line. The
+ default action is <c>store</c>.</p>
+ <taglist>
+ <tag><c>store</c></tag>
+ <item><p>
+ Store the value in the arguments map. Overwrites the value previously written.
+ </p>
+ <code>
+1> Cmd = #{arguments => [#{name => str, short => $s}]},
+argparse:parse(["-s", "one", "-s", "two"], Cmd).
+
+{ok, #{str => "two"}, ...
+ </code>
+ </item>
+ <tag><c>{store, term()}</c></tag>
+ <item><p>
+ Stores the specified term instead of reading the value from the command line.
+ </p>
+ <code>
+1> Cmd = #{arguments => [#{name => str, short => $s, action => {store, "two"}}]},
+argparse:parse(["-s"], Cmd).
+
+{ok, #{str => "two"}, ...
+ </code>
+ </item>
+ <tag><c>append</c></tag>
+ <item><p>
+ Appends the repeating occurrences of the argument instead of overwriting.
+ </p>
+ <code>
+1> Cmd = #{arguments => [#{name => node, short => $n, action => append}]},
+argparse:parse(["-n", "one", "-n", "two", "-n", "three"], Cmd).
+
+{ok, #{node => ["one", "two", "three"]}, ...
+
+%% Always produces a list - even if there is one occurrence
+2> argparse:parse(["-n", "one"], Cmd).
+
+{ok, #{node => ["one"]}, ...
+ </code>
+ </item>
+ <tag><c>{append, term()}</c></tag>
+ <item><p>
+ Same as <c>append</c>, but instead of consuming the argument from the
+ command line, appends a provided <c>term()</c>.
+ </p></item>
+ <tag><c>count</c></tag>
+ <item><p>
+ Puts a counter as a value in the arguments map. Useful for implementing
+ verbosity option:
+ </p>
+ <code>
+1> Cmd = #{arguments => [#{name => verbose, short => $v, action => count}]},
+argparse:parse(["-v"], Cmd).
+
+{ok, #{verbose => 1}, ...
+
+2> argparse:parse(["-vvvv"], Cmd).
+
+{ok, #{verbose => 4}, ...
+ </code>
+ </item>
+ <tag><c>extend</c></tag>
+ <item><p>
+ Works as <c>append</c>, but flattens the resulting list.
+ Valid only for <c>nargs</c> set to <c>list</c>, <c>nonempty_list</c>,
+ <c>all</c> or <c>pos_integer()</c>.
+ </p>
+ <code>
+1> Cmd = #{arguments => [#{name => duet, short => $d, nargs => 2, action => extend}]},
+argparse:parse(["-d", "a", "b", "-d", "c", "d"], Cmd).
+
+{ok, #{duet => ["a", "b", "c", "d"]}, ...
+
+%% 'append' would result in {ok, #{duet => [["a", "b"],["c", "d"]]},
+ </code>
+ </item>
+ </taglist>
+ </item>
+ <tag><c>help</c></tag>
+ <item>
+ <p>Specifies help/usage text for the argument. <c>argparse</c> provides automatic
+ generation based on the argument name, type and default value, but for better
+ usability it is recommended to have a proper description. Setting this field
+ to <c>hidden</c> suppresses usage output for this argument.</p>
+ </item>
+ </taglist>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="arg_map"/>
+ <desc>
+ <p>Arguments map is the map of argument names to the values extracted from the
+ command line. It is passed to the matching command handler.
+ If an argument is omitted, but has the default value is specified,
+ it is added to the map. When no default value specified, and argument is not
+ present in the command line, corresponding key is not present in the resulting
+ map.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="handler"/>
+ <desc>
+ <p>Command handler specification. Called by <seemfa marker="#run/3"><c>run/3</c>
+ </seemfa> upon successful parser return.</p>
+ <taglist>
+ <tag><c>fun((arg_map()) -> term())</c></tag>
+ <item><p>
+ Function accepting <seetype marker="#arg_map"><c>argument map</c></seetype>.
+ See the basic example in the <seeerl marker="#quick-start">Quick Start</seeerl>
+ section.
+ </p></item>
+ <tag><c>{Module :: module(), Function :: atom()}</c></tag>
+ <item><p>
+ Function named <c>Function</c>, exported from <c>Module</c>, accepting
+ <seetype marker="#arg_map"><c>argument map</c></seetype>.
+ </p></item>
+ <tag><c>{fun(() -> term()), Default :: term()}</c></tag>
+ <item><p>
+ Function accepting as many arguments as there are in the <c>arguments</c>
+ list for this command. Arguments missing from the parsed map are replaced
+ with the <c>Default</c>. Convenient way to expose existing functions.
+ </p>
+ <code>
+1> Cmd = #{arguments => [
+ #{name => x, type => float},
+ #{name => y, type => float, short => $p}],
+ handler => {fun math:pow/2, 1}},
+argparse:run(["2", "-p", "3"], Cmd, #{}).
+
+8.0
+
+%% default term 1 is passed to math:pow/2
+2> argparse:run(["2"], Cmd, #{}).
+
+2.0
+ </code>
+ </item>
+ <tag><c>{Module :: module(), Function :: atom(), Default :: term()}</c></tag>
+ <item><p>Function named <c>Function</c>, exported from <c>Module</c>, accepting
+ as many arguments as defined for this command. Arguments missing from the parsed
+ map are replaced with the <c>Default</c>. Effectively, just a different syntax
+ to the same functionality as demonstrated in the code above.</p></item>
+ </taglist>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="command_help"/>
+ <desc>
+ <p>User-defined help template. Use this option to mix custom and predefined usage text.
+ Help template may contain unicode strings, and following atoms:</p>
+ <taglist>
+ <tag>usage</tag>
+ <item><p>
+ Formatted command line usage text, e.g. <c>rm [-rf] &lt;directory&gt;</c>.
+ </p></item>
+ <tag>commands</tag>
+ <item><p>
+ Expanded list of sub-commands.
+ </p></item>
+ <tag>arguments</tag>
+ <item><p>
+ Detailed description of positional arguments.
+ </p></item>
+ <tag>options</tag>
+ <item><p>
+ Detailed description of optional arguments.
+ </p></item>
+ </taglist>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="command"/>
+ <desc>
+ <p>Command specification. May contain nested commands, forming a hierarchy.</p>
+ <taglist>
+ <tag><c>commands</c></tag>
+ <item><p>
+ Maps of nested commands. Keys must be strings, matching command line input.
+ Basic utilities do not need to specify any nested commands.
+ </p>
+ </item>
+ <tag><c>arguments</c></tag>
+ <item><p>
+ List of arguments accepted by this command, and all nested commands in the
+ hierarchy.
+ </p></item>
+ <tag><c>help</c></tag>
+ <item><p>
+ Specifies help/usage text for this command. Pass <c>hidden</c> to remove
+ this command from the usage output.
+ </p></item>
+ <tag><c>handler</c></tag>
+ <item><p>
+ Specifies a callback function to call by <seemfa marker="#run/3">run/3</seemfa>
+ when the parser is successful.
+ </p></item>
+ </taglist>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="cmd_path"/>
+ <desc>
+ <p>Path to the nested command. First element is always the <c>progname</c>,
+ subsequent elements are nested command names.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="parser_error"/>
+ <desc>
+ <p>Returned from <seemfa marker="#parse/3"><c>parse/2,3</c></seemfa> when the
+ user input cannot be parsed according to the command specification.</p>
+ <p>First element is the path to the command that was considered when the
+ parser detected an error. Second element, <c>Expected</c>, is the argument
+ specification that caused an error. It could be <c>undefined</c>, meaning
+ that <c>Actual</c> argument had no corresponding specification in the
+ arguments list for the current command. </p>
+ <p>When <c>Actual</c> is set to <c>undefined</c>, it means that a required
+ argument is missing from the command line. If both <c>Expected</c> and
+ <c>Actual</c> have values, it means validation error.</p>
+ <p>Use <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa> to
+ generate a human-readable error description, unless there is a need to
+ provide localised error messages.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="parser_options"/>
+ <desc>
+ <p>Options changing parser behaviour.</p>
+ <taglist>
+ <tag><c>prefixes</c></tag>
+ <item><p>
+ Changes the option prefix (the default is <c>-</c>).
+ </p></item>
+ <tag><c>default</c></tag>
+ <item><p>
+ Specifies the default value for all optional arguments. When
+ this field is set, resulting argument map will contain all
+ argument names. Useful for easy pattern matching on the
+ argument map in the handler function.
+ </p></item>
+ <tag><c>progname</c></tag>
+ <item><p>
+ Specifies the program (root command) name. Returned as the
+ first element of the command path, and printed in help/usage
+ text. It is recommended to have this value set, otherwise the
+ default one is determined with <c>init:get_argument(progname)</c>
+ and is often set to <c>erl</c> instead of the actual script name.
+ </p></item>
+ <tag><c>command</c></tag>
+ <item><p>
+ Specifies the path to the nested command for
+ <seemfa marker="#help/2"><c>help/2</c></seemfa>. Useful to
+ limit output for complex utilities with multiple commands,
+ and used by the default error handling logic.
+ </p></item>
+ <tag><c>columns</c></tag>
+ <item><p>
+ Specifies the help/usage text width (characters) for
+ <seemfa marker="#help/2"><c>help/2</c></seemfa>. Default value
+ is 80.
+ </p></item>
+ </taglist>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="parse_result"/>
+ <desc>
+ <p>Returned from <seemfa marker="#parse/3"><c>parse/2,3</c></seemfa>. Contains
+ arguments extracted from the command line, path to the nested command (if any),
+ and a (potentially nested) command specification that was considered when
+ the parser finished successfully. It is expected that the command contains
+ a handler definition, that will be called passing the argument map.</p>
+ </desc>
+ </datatype>
+
+ </datatypes>
+
+ <funcs>
+
+ <func>
+ <name name="format_error" arity="1" since="OTP 26.0"/>
+ <fsummary>Generates human-readable text for parser errors.</fsummary>
+ <desc>
+ <p>Generates human-readable text for
+ <seetype marker="#parser_error"><c>parser error</c></seetype>. Does
+ not include help/usage information, and does not provide localisation.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="help" arity="1" since="OTP 26.0"/>
+ <name name="help" arity="2" since="OTP 26.0"/>
+ <fsummary>Generates help/usage information text.</fsummary>
+ <desc>
+ <p>Generates help/usage information text for the command
+ supplied, or any nested command when <c>command</c>
+ option is specified. Does not provide localisaton.
+ Expects <c>progname</c> to be set, otherwise defaults to
+ return value of <c>init:get_argument(progname)</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="parse" arity="2" since="OTP 26.0"/>
+ <name name="parse" arity="3" since="OTP 26.0"/>
+ <fsummary>Parses command line arguments according to the command specification.</fsummary>
+ <desc>
+ <p>Parses command line arguments according to the command specification.
+ Raises an exception if the command specification is not valid. Use
+ <seemfa marker="erl_error#format_exception/3"><c>erl_error:format_exception/3,4</c>
+ </seemfa> to see a friendlier message. Invalid command line input
+ does not raise an exception, but makes <c>parse/2,3</c> to return a tuple
+ <seetype marker="#parser_error"><c>{error, parser_error()}</c></seetype>.
+ </p>
+ <p>This function does not call command handler.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="run" arity="3" since="OTP 26.0"/>
+ <fsummary>Parses command line arguments and calls the matching command handler.</fsummary>
+ <desc>
+ <p>Parses command line arguments and calls the matching command handler.
+ Prints human-readable error, help/usage information for the discovered
+ command, and halts the emulator with code 1 if there is any error in the
+ command specification or user-provided command line input.
+ </p>
+ <warning>
+ <p>This function is designed to work as an entry point to a standalone
+ <seecom marker="erts:escript"><c>escript</c></seecom>. Therefore, it halts
+ the emulator for any error detected. Do not use this function through
+ remote procedure call, or it may result in an unexpected shutdown of a remote
+ node.</p>
+ </warning>
+ </desc>
+ </func>
+
+ </funcs>
+
+</erlref>
+
diff --git a/lib/stdlib/doc/src/base64.xml b/lib/stdlib/doc/src/base64.xml
index bb45927c3f..a4ab294336 100644
--- a/lib/stdlib/doc/src/base64.xml
+++ b/lib/stdlib/doc/src/base64.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2007</year><year>2021</year>
+ <year>2007</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -40,7 +40,27 @@
<datatypes>
<datatype>
<name name="base64_alphabet"/>
- <desc><p>Base 64 Encoding alphabet, see <url href="https://www.ietf.org/rfc/rfc4648.txt">RFC 4648</url>.</p>
+ <desc><p>Base 64 Encoding alphabet, see
+ <url href="https://datatracker.ietf.org/doc/html/rfc4648">RFC 4648</url>.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="base64_mode"/>
+ <desc>
+ <p>Selector for the Base 64 Encoding alphabet used for
+ <seemfa marker="#encode/2">encoding</seemfa> and
+ <seemfa marker="#decode/2">decoding</seemfa>.
+ See <url href="https://datatracker.ietf.org/doc/html/rfc4648">RFC 4648</url>
+ Sections <url href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">4</url>
+ and <url href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">5</url>.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="options" />
+ <desc>
+ <p>Customises the behaviour of the encode and decode functions.
+ Default value if omitted entirely or partially is
+ <c>#{mode => standard, padding => true}</c>.</p>
</desc>
</datatype>
<datatype>
@@ -67,15 +87,62 @@
<name name="mime_decode" arity="1" since=""/>
<name name="mime_decode_to_string" arity="1" since=""/>
<fsummary>Decode a base64 encoded string to data.</fsummary>
- <type variable="Base64" name_i="1"/>
+ <type variable="Base64"/>
<type variable="Data" name_i="1"/>
<type variable="DataString" name_i="2"/>
<desc>
- <p>Decodes a base64-encoded string to plain ASCII. See
- <url href="https://www.ietf.org/html/rfc4648">RFC 4648</url>.</p>
+ <p>Decodes a base64 string encoded using the standard alphabet according
+ to <url href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">RFC 4648
+ Section 4</url> to plain ASCII.</p>
<p><c>mime_decode/1</c> and <c>mime_decode_to_string/1</c> strip away
illegal characters, while <c>decode/1</c> and
<c>decode_to_string/1</c> only strip away whitespace characters.</p>
+ <p>Checks the correct number of <c>=</c> padding characters
+ at the end of the encoded string.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="decode" arity="2" since="OTP @OTP-18247@"/>
+ <name name="decode_to_string" arity="2" since="OTP @OTP-18247@"/>
+ <name name="mime_decode" arity="2" since="OTP @OTP-18247@"/>
+ <name name="mime_decode_to_string" arity="2" since="OTP @OTP-18247@"/>
+ <fsummary>Decode a base64 encoded string to data.</fsummary>
+ <type variable="Base64"/>
+ <type variable="Options" name_i="1"/>
+ <type variable="Data" name_i="1"/>
+ <type variable="DataString" name_i="2"/>
+ <desc>
+ <p>Decodes a base64 string encoded using the alphabet indicated by the
+ <c>mode</c> option to plain ASCII.</p>
+ <p><c>mime_decode/2</c> and <c>mime_decode_to_string/2</c> strip away
+ illegal characters, while <c>decode/2</c> and
+ <c>decode_to_string/2</c> only strip away whitespace characters.</p>
+ <p>The <c>mode</c> option can be one of the following:</p>
+ <taglist>
+ <tag><c>standard</c></tag>
+ <item>Default. Decode the given string using the standard base64 alphabet according
+ to <url href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">RFC 4648
+ Section 4</url>, that is <c>"+"</c> and <c>"/"</c> are representing bytes <c>62</c>
+ and <c>63</c> respectively, while <c>"-"</c> and <c>"_"</c> are illegal
+ characters.</item>
+ <tag><c>urlsafe</c></tag>
+ <item>Decode the given string using the alternative "URL and Filename safe" base64
+ alphabet according to
+ <url href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">RFC 4648
+ Section 5</url>, that is <c>"-"</c> and <c>"_"</c> are representing bytes <c>62</c>
+ and <c>63</c> respectively, while <c>"+"</c> and <c>"/"</c> are illegal
+ characters.</item>
+ </taglist>
+ <p>The <c>padding</c> option can be one of the following:</p>
+ <taglist>
+ <tag><c>true</c></tag>
+ <item>Default. Checks the correct number of <c>=</c> padding characters
+ at the end of the encoded string.</item>
+ <tag><c>false</c></tag>
+ <item>Accepts an encoded string with missing <c>=</c> padding characters
+ at the end.</item>
+ </taglist>
</desc>
</func>
@@ -87,10 +154,46 @@
<type variable="Base64" name_i="1"/>
<type variable="Base64String"/>
<desc>
- <p>Encodes a plain ASCII string into base64. The result is 33% larger
- than the data.</p>
+ <p>Encodes a plain ASCII string into base64 using the standard alphabet
+ according to <url href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">RFC 4648
+ Section 4</url>. The result is 33% larger than the data.</p>
+ <p>Always appends correct number of <c>=</c> padding characters
+ to the encoded string.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="encode" arity="2" since="OTP @OTP-18247@"/>
+ <name name="encode_to_string" arity="2" since="OTP @OTP-18247@"/>
+ <fsummary>Encode data into base64.</fsummary>
+ <type variable="Data"/>
+ <type variable="Options"/>
+ <type variable="Base64" name_i="1"/>
+ <type variable="Base64String"/>
+ <desc>
+ <p>Encodes a plain ASCII string into base64 using the alphabet indicated by
+ the <c>mode</c> option. The result is 33% larger than the data.</p>
+ <p>The <c>mode</c> option can be one of the following:</p>
+ <taglist>
+ <tag><c>standard</c></tag>
+ <item>Default. Encode the given string using the standard base64 alphabet according
+ to <url href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">RFC 4648
+ Section 4</url>.</item>
+ <tag><c>urlsafe</c></tag>
+ <item>Encode the given string using the alternative "URL and Filename safe" base64
+ alphabet according to
+ <url href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">RFC 4648
+ Section 5</url>.</item>
+ </taglist>
+ <p>The <c>padding</c> option can be one of the following:</p>
+ <taglist>
+ <tag><c>true</c></tag>
+ <item>Default. Appends correct number of <c>=</c> padding characters
+ to the encoded string.</item>
+ <tag><c>false</c></tag>
+ <item>Skips appending <c>=</c> padding characters to the encoded string.</item>
+ </taglist>
</desc>
</func>
</funcs>
</erlref>
-
diff --git a/lib/stdlib/doc/src/binary.xml b/lib/stdlib/doc/src/binary.xml
index 220caaaaee..07f55ed30e 100644
--- a/lib/stdlib/doc/src/binary.xml
+++ b/lib/stdlib/doc/src/binary.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2009</year>
- <year>2021</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -244,15 +244,23 @@
<func>
<name name="encode_hex" arity="1" since="OTP 24.0"/>
- <fsummary>Encodes a binary into a hex encoded binary.</fsummary>
+ <name name="encode_hex" arity="2" since="OTP @OTP-18354@"/>
+ <fsummary>Encodes a binary into a hex encoded binary with specified case</fsummary>
<desc>
- <p>Encodes a binary into a hex encoded binary.</p>
+ <p>Encodes a binary into a hex encoded binary using the specified case for the hexadecimal digits "a" to "f".</p>
+ <p>The default case is <c>uppercase</c>.</p>
+ <p><em>Example:</em></p>
- <p><em>Example:</em></p>
-
- <code>
+ <code>
1> binary:encode_hex(&lt;&lt;"f"&gt;&gt;).
-&lt;&lt;"66"&gt;&gt;</code>
+&lt;&lt;"66"&gt;&gt;
+2> binary:encode_hex(&lt;&lt;"/"&gt;&gt;).
+&lt;&lt;"2F"&gt;&gt;
+3> binary:encode_hex(&lt;&lt;"/"&gt;&gt;, lowercase).
+&lt;&lt;"2f"&gt;&gt;
+4> binary:encode_hex(&lt;&lt;"/"&gt;&gt;, uppercase).
+&lt;&lt;"2F"&gt;&gt;
+ </code>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/edlin_expand.xml b/lib/stdlib/doc/src/edlin_expand.xml
new file mode 100644
index 0000000000..62e634c1a5
--- /dev/null
+++ b/lib/stdlib/doc/src/edlin_expand.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2023</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>edlin_expand</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module since="OTP @OTP-14835@">edlin_expand</module>
+ <modulesummary>Shell expansion and formatting of expansion suggestions.</modulesummary>
+ <description>
+ <p>This module provides an expand_fun for the erlang shell
+ <seemfa marker="#expand/1"><c>expand/1,2</c></seemfa>.
+ It is possible to override this expand_fun
+ <seemfa marker="io#setopts/1"><c>io:setopts/1,2</c></seemfa>.</p>
+ </description>
+ <funcs>
+ <func>
+ <name name="expand" arity="1" since="OTP @OTP-14835@"/>
+ <name name="expand" arity="2" since="OTP @OTP-14835@"/>
+ <fsummary>Standard expanion function for the erl shell.</fsummary>
+ <desc>
+ <p>The standard expansion function is able to expand strings to
+ valid erlang terms. This includes module names:</p>
+ <pre>
+1> erla
+modules
+erlang:
+ </pre>
+ <p>function names:</p>
+ <pre>
+1> is_ato
+functions
+is_atom(
+2> erlang:is_ato
+functions
+is_atom(
+ </pre>
+<p>
+ function types:
+</p>
+<pre>
+1> erlang:is_atom(
+typespecs
+erlang:is_atom(Term)
+any()
+</pre>
+<p>
+ and automatically add , or closing parenthesis when no other
+ valid expansion is possible. The expand function also completes:
+ shell bindings, record names, record fields and map keys.
+</p>
+<p>
+ As seen below, function headers are grouped together if they've got the same
+ expansion suggestion, in this case all had the same suggestions, that is '}'.
+ There is also limited support for filtering out function typespecs that that does
+ not match the types on the terms on the prompt. Only 4 suggestions are shown below
+ but there exists plenty more typespecs for <c>erlang:system_info</c>.
+ </p>
+<pre>
+1> erlang:system_info({allocator, my_allocator
+typespecs
+erlang:system_info(wordsize | {wordsize, ...} | {wordsize, ...})
+erlang:system_info({allocator, ...})
+erlang:system_info({allocator_sizes, ...})
+erlang:system_info({cpu_topology, ...})
+}
+</pre>
+ <p>The return type of <c>expand</c> function specifies either a list of <c>Element</c>
+ tuples or a list of <c>Section</c> maps. The section concept was introduced to enable
+ more formatting options for the expansion results. For example, the shell expansion has
+ support to highlight text and hide suggestions.
+ There are also a <c>{highlight, Text}</c> that highlights all occurances of
+ <c>Text</c> in the title, and a <c>highlight_all</c> for simplicity which
+ highlights the whole title, as can be seen above for <c>functions</c> and <c>typespecs</c>.</p>
+
+ <p>By setting the <c>{hide, result}</c> or <c>{hide, title}</c> options you may hide
+ suggestions. Sometimes the title isn't useful and just produces text noise, in the example
+ above the <c>any()</c> result is part of a section with title <c>Types</c>. Hiding results
+ is currently not in use, but the idea is that a section can be selected in the expand area
+ and all the other section entries should be collapsed.
+ </p>
+
+ <p>Its possible to set a custom separator between the title and the results. This can be
+ done with <c>{separator, Separator}</c>.
+ By default its set to be <c>\n</c>, some results display a <c>type_name() :: </c>
+ followed by all types that define <c>type_name()</c>.
+ </p>
+
+ <p>The <c>{ending, Text}</c> ElementOption just appends Text to the <c>Element</c>.
+ </p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
diff --git a/lib/stdlib/doc/src/epp.xml b/lib/stdlib/doc/src/epp.xml
index 11130b0065..45c0f241d2 100644
--- a/lib/stdlib/doc/src/epp.xml
+++ b/lib/stdlib/doc/src/epp.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -142,6 +142,10 @@
<p>The option <c>location</c> is forwarded
to the Erlang token scanner, see
<seemfa marker="erl_scan#tokens/3"><c>erl_scan:tokens/3,4</c></seemfa>.</p>
+ <p>The <c>{compiler_internal,term()}</c> option is forwarded
+ to the Erlang token scanner, see
+ <seeerl marker="erl_scan#compiler_interal">
+ <c>{compiler_internal,term()}</c></seeerl>.</p>
</desc>
</func>
@@ -193,6 +197,10 @@
<p>The option <c>location</c> is forwarded
to the Erlang token scanner, see
<seemfa marker="erl_scan#tokens/3"><c>erl_scan:tokens/3,4</c></seemfa>.</p>
+ <p>The <c>{compiler_internal,term()}</c> option is forwarded
+ to the Erlang token scanner, see
+ <seeerl marker="erl_scan#compiler_interal">
+ <c>{compiler_internal,term()}</c></seeerl>.</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/erl_scan.xml b/lib/stdlib/doc/src/erl_scan.xml
index 960ff9d019..1f0a4b2cb8 100644
--- a/lib/stdlib/doc/src/erl_scan.xml
+++ b/lib/stdlib/doc/src/erl_scan.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -237,6 +237,22 @@
If neither are present the text will not be saved in the
token annotation.</p>
</item>
+ <tag><marker id="compiler_interal"/>
+ <c>{compiler_internal, term()}</c>
+ </tag>
+ <item><p>Pass compiler-internal options to the scanner. The
+ set of internal options understood by the scanner should
+ be considered experimental and can thus be changed at any time
+ without prior warning.</p>
+ <p>The following options are currently understood:</p>
+ <taglist>
+ <tag><c>ssa_checks</c></tag>
+ <item>
+ <p>Tokenizes source code annotations used for encoding
+ tests on the BEAM SSA code produced by the compiler.</p>
+ </item>
+ </taglist>
+ </item>
</taglist>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index ce116c50be..2aa05a599a 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -999,6 +999,32 @@ Error: fun containing local Erlang function calls
</func>
<func>
+ <name name="lookup_element" arity="4" since="OTP @OTP-18279@"/>
+ <fsummary>Return the <c>Pos</c>:th element of all objects with a
+ specified key in an ETS table, or <c>Default</c> if there is no such object.</fsummary>
+ <desc>
+ <p>For a table <c><anno>Table</anno></c> of type <c>set</c> or
+ <c>ordered_set</c>, the function returns the
+ <c><anno>Pos</anno></c>:th
+ element of the object with key <c><anno>Key</anno></c>.</p>
+ <p>For tables of type <c>bag</c> or <c>duplicate_bag</c>,
+ the functions returns a list with the <c><anno>Pos</anno></c>:th
+ element of every object with key <c><anno>Key</anno></c>.</p>
+ <p>If no object with key <c><anno>Key</anno></c> exists, the
+ function returns <c><anno>Default</anno></c>.</p>
+ <p>If <c><anno>Pos</anno></c> is larger than the size of
+ any tuple with a matching key, the function exits with
+ reason <c>badarg</c>.</p>
+ <p>The difference between <c>set</c>, <c>bag</c>, and
+ <c>duplicate_bag</c> on one hand, and <c>ordered_set</c> on
+ the other, regarding the fact that <c>ordered_set</c>
+ view keys as equal when they <em>compare equal</em>
+ whereas the other table types regard them equal only when
+ they <em>match</em>, holds for <c>lookup_element/4</c>.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="match" arity="1" since=""/>
<fsummary>Continues matching objects in an ETS table.</fsummary>
<desc>
@@ -1239,8 +1265,8 @@ ets:select(Table, MatchSpec),</code>
means that to an <c>ordered_set</c> table, <c>integer()</c>
<c>1</c> and <c>float()</c> <c>1.0</c> are regarded as equal.
This also means that the
- key used to lookup an element not necessarily
- <em>matches</em> the key in the returned elements, if
+ key used to lookup an element does not necessarily
+ <em>match</em> the key in the returned elements, if
<c>float()</c>'s and <c>integer()</c>'s are mixed in
keys of a table.</p>
</item>
@@ -1361,7 +1387,7 @@ ets:select(Table, MatchSpec),</code>
</note>
<marker id="new_2_read_concurrency"></marker>
</item>
- <tag><c>{read_concurrency,boolean()}</c></tag>
+ <tag since="OTP R14B"><c>{read_concurrency,boolean()}</c></tag>
<item>
<p>Performance tuning. Defaults to <c>false</c>. When set to
<c>true</c>, the table is optimized for concurrent read
@@ -1386,7 +1412,7 @@ ets:select(Table, MatchSpec),</code>
read bursts and large concurrent write bursts are common.</p>
<marker id="new_2_decentralized_counters"></marker>
</item>
- <tag><c>{decentralized_counters,boolean()}</c></tag>
+ <tag since="OTP 23.0"><c>{decentralized_counters,boolean()}</c></tag>
<item>
<p>
Performance tuning. Defaults to <c>true</c> for all
@@ -1421,7 +1447,7 @@ ets:select(Table, MatchSpec),</code>
</p>
<marker id="new_2_compressed"></marker>
</item>
- <tag><c>compressed</c></tag>
+ <tag since="OTP R14B01"><c>compressed</c></tag>
<item>
<p>If this option is present, the table data is stored in a more
compact format to consume less memory. However, it will make
diff --git a/lib/stdlib/doc/src/gb_sets.xml b/lib/stdlib/doc/src/gb_sets.xml
index 3477c2c90e..283c3f9198 100644
--- a/lib/stdlib/doc/src/gb_sets.xml
+++ b/lib/stdlib/doc/src/gb_sets.xml
@@ -68,48 +68,10 @@
<section>
<title>Compatibility</title>
- <p>The following functions in this module also exist and provides
- the same functionality in the
- <seeerl marker="sets"><c>sets(3)</c></seeerl> and
- <seeerl marker="ordsets"><c>ordsets(3)</c></seeerl>
- modules. That is, by only changing the module name for each call,
- you can try out different set representations.</p>
- <list type="bulleted">
- <item><seemfa marker="#add_element/2"><c>add_element/2</c></seemfa>
- </item>
- <item><seemfa marker="#del_element/2"><c>del_element/2</c></seemfa>
- </item>
- <item><seemfa marker="#filter/2"><c>filter/2</c></seemfa>
- </item>
- <item><seemfa marker="#fold/3"><c>fold/3</c></seemfa>
- </item>
- <item><seemfa marker="#from_list/1"><c>from_list/1</c></seemfa>
- </item>
- <item><seemfa marker="#intersection/1"><c>intersection/1</c></seemfa>
- </item>
- <item><seemfa marker="#intersection/2"><c>intersection/2</c></seemfa>
- </item>
- <item><seemfa marker="#is_element/2"><c>is_element/2</c></seemfa>
- </item>
- <item><seemfa marker="#is_empty/1"><c>is_empty/1</c></seemfa>
- </item>
- <item><seemfa marker="#is_set/1"><c>is_set/1</c></seemfa>
- </item>
- <item><seemfa marker="#is_subset/2"><c>is_subset/2</c></seemfa>
- </item>
- <item><seemfa marker="#new/0"><c>new/0</c></seemfa>
- </item>
- <item><seemfa marker="#size/1"><c>size/1</c></seemfa>
- </item>
- <item><seemfa marker="#subtract/2"><c>subtract/2</c></seemfa>
- </item>
- <item><seemfa marker="#to_list/1"><c>to_list/1</c></seemfa>
- </item>
- <item><seemfa marker="#union/1"><c>union/1</c></seemfa>
- </item>
- <item><seemfa marker="#union/2"><c>union/2</c></seemfa>
- </item>
- </list>
+ <p>See the <seeerl marker="sets#compatibility">Compatibility Section
+ in the <c>sets(3)</c> module</seeerl> for information about
+ the compatibility of the different implementations of sets in the
+ Standard Library.</p>
</section>
<datatypes>
diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml
index 26a7f5646b..25d02d83f9 100644
--- a/lib/stdlib/doc/src/gen_event.xml
+++ b/lib/stdlib/doc/src/gen_event.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -113,6 +113,15 @@ gen_event:stop -----> Module:terminate/2
<p>Unless otherwise stated, all functions in this module fail if
the specified event manager does not exist or if bad arguments are
specified.</p>
+
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ Blocking signaling can, for example, cause call timeouts in
+ <c>gen_event</c> to be significantly delayed.
+ </p></note>
</description>
<datatypes>
diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml
index 4948418b3d..ad1deed965 100644
--- a/lib/stdlib/doc/src/gen_server.xml
+++ b/lib/stdlib/doc/src/gen_server.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -107,6 +107,15 @@ gen_server:abcast -----> Module:handle_cast/2
Processes</seeguide> in the Reference Manual for details
regarding error handling using exit signals.</p>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ Blocking signaling can, for example, cause call timeouts in
+ <c>gen_server</c> to be significantly delayed.
+ </p></note>
+
</description>
@@ -1916,6 +1925,7 @@ format_status(Status) ->
<v>&nbsp;&nbsp;| {ok,State,hibernate}</v>
<v>&nbsp;&nbsp;| {ok,State,{continue,Continue}}</v>
<v>&nbsp;&nbsp;| {stop,Reason}</v>
+ <v>&nbsp;&nbsp;| {error,Reason}</v>
<v>&nbsp;&nbsp;| ignore</v>
<v>&nbsp;State = term()</v>
<v>
@@ -1960,14 +1970,22 @@ format_status(Status) ->
</item>
<tag>
<c>{stop,Reason}</c><br/>
+ <c>{error,Reason}</c><br/>
<c>ignore</c>
</tag>
<item>
<p>
Initialization failed.
- An exit signal with this <c>Reason</c>
- (or with reason <c>normal</c> if <c>ignore</c> is returned)
- is sent to linked processes and ports,
+ An exit signal with reason</p>
+ <taglist>
+ <tag>stop:</tag>
+ <item><c>Reason</c></item>
+ <tag>error:</tag>
+ <item><c>normal</c></item>
+ <tag>ignore:</tag>
+ <item><c>normal</c></item>
+ </taglist>
+ <p>is sent to linked processes and ports,
notably to the process starting the gen_server when
<seemfa marker="#start_link/3">
<c>start_link/3,4</c>
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 9dd1c6f270..319fafee07 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2016</year><year>2022</year>
+ <year>2016</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -381,6 +381,14 @@ erlang:'!' -----> Module:StateName/3
Processes</seeguide> in the Reference Manual for details regarding error
handling using exit signals.
</p>
+ <note><p>
+ For some important information about distributed signals, see the
+ <seeguide marker="system/reference_manual:processes#blocking-signaling-over-distribution">
+ <i>Blocking Signaling Over Distribution</i></seeguide> section in the
+ <i>Processes</i> chapter of the <i>Erlang Reference Manual</i>.
+ Blocking signaling can, for example, cause call timeouts in
+ <c>gen_statem</c> to be significantly delayed.
+ </p></note>
</description>
<section>
@@ -1494,7 +1502,8 @@ handle_event(_, _, State, Data) ->
</p>
<p>
For an unsuccesful initialization,
- <c>{stop,<anno>Reason</anno>}</c>
+ <c>{stop, <anno>Reason</anno>}</c>,
+ <c>{error, <anno>Reason</anno>}</c>
or <c>ignore</c> should be used; see
<seemfa marker="#start_link/3"><c>start_link/3,4</c></seemfa>.
</p>
@@ -1773,43 +1782,17 @@ handle_event(_, _, State, Data) ->
which is the default. If no reply is received within
the specified time, the function call fails.
</p>
- <note>
- <p>
- For <c><anno>Timeout</anno> &lt; infinity</c>,
- to avoid getting a late reply in the caller's
- inbox if the caller should catch exceptions,
- this function spawns a proxy process that
- does the call. A late reply gets delivered to the
- dead proxy process, hence gets discarded. This is
- less efficient than using
- <c><anno>Timeout</anno> == infinity</c>.
- </p>
- </note>
<p>
- <c><anno>Timeout</anno></c> can also be a tuple
- <c>{clean_timeout,<anno>T</anno>}</c> or
- <c>{dirty_timeout,<anno>T</anno>}</c>, where
- <c><anno>T</anno></c> is the time-out time.
- <c>{clean_timeout,<anno>T</anno>}</c> works like
- just <c>T</c> described in the note above
- and uses a proxy process
- while <c>{dirty_timeout,<anno>T</anno>}</c>
- bypasses the proxy process which is more lightweight.
+ Previous issue with late replies that could occur when having
+ network issues or using <c>dirty_timeout</c> is now prevented
+ by use of
+ <seeguide marker="system/reference_manual:processes#process-aliases"><i>process
+ aliases</i></seeguide>. <c>{clean_timeout, <anno>T</anno>}</c>
+ and <c>{dirty_timeout, <anno>T</anno>}</c> therefore no longer
+ serves any purpose and will work the same as
+ <c><anno>Timeout</anno></c> while all of them also being
+ equally efficient.
</p>
- <note>
- <p>
- If you combine catching exceptions from this function
- with <c>{dirty_timeout,<anno>T</anno>}</c>
- to avoid that the calling process dies when the call
- times out, you will have to be prepared to handle
- a late reply. Note that there is an odd chance
- to get a late reply even with
- <c>{dirty_timeout,infinity}</c> or <c>infinity</c>
- for example in the event of network problems.
- So why not just let the calling process die
- by not catching the exception?
- </p>
- </note>
<p>
The call can also fail, for example, if the <c>gen_statem</c>
dies before or during this function call.
@@ -2495,9 +2478,10 @@ handle_event(_, _, State, Data) ->
<p>
If <c>Module:init/1</c> fails with <c>Reason</c>,
this function returns
- <seetype marker="#start_ret"><c>{error,Reason}</c></seetype>.
+ <seetype marker="#start_ret"><c>{error, Reason}</c></seetype>.
If <c>Module:init/1</c> returns
- <seetype marker="#start_ret"><c>{stop,Reason}</c></seetype>
+ <seetype marker="#start_ret"><c>{stop, Reason}</c></seetype>,
+ <seetype marker="#start_ret"><c>{shutdown, Reason}</c></seetype>
or
<seetype marker="#start_ret"><c>ignore</c></seetype>,
the process is terminated and this function
@@ -2510,6 +2494,9 @@ handle_event(_, _, State, Data) ->
<c>Module:init/1</c> returns <c>ignore</c>) is set to linked processes
and ports, including the process calling <c>start_link/3,4</c>.
</p>
+ <p>The difference between returning <c>{stop, Reason}</c> and
+ <c>{error, Reason}</c> (from <c>Module:init/1</c>) is that
+ <c>error</c> results in a graceful ("silent") termination. </p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml
index a400d2af23..aed39279c9 100644
--- a/lib/stdlib/doc/src/io.xml
+++ b/lib/stdlib/doc/src/io.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2021</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -83,6 +83,9 @@
<datatype>
<name name="opt_pair"/>
</datatype>
+ <datatype>
+ <name name="get_opt_pair"/>
+ </datatype>
<datatype>
<name name="expand_fun"/>
</datatype>
@@ -174,9 +177,38 @@ ok</pre>
<item>
<p><c>Mod</c> is the control sequence modifier. This is
one or more characters that change the interpretation of
- <c>Data</c>. The current modifiers are <c>t</c>, for Unicode
- translation, and <c>l</c>, for stopping <c>p</c> and <c>P</c>
- from detecting printable characters.</p>
+ <c>Data</c>.</p>
+ <p>The current modifiers are:</p>
+ <taglist>
+ <tag><c>t</c></tag>
+ <item>
+ <p>For Unicode translation.</p>
+ </item>
+ <tag><c>l</c></tag>
+ <item>
+ <p>For stopping <c>p</c> and <c>P</c> from detecting
+ printable characters.</p>
+ </item>
+ <tag><c>k</c></tag>
+ <item>
+ <p>For use with <c>p</c>, <c>P</c>, <c>w</c>, and <c>W</c>
+ to format maps in map-key <c>ordered</c> order (see
+ <seetype marker="maps#iterator_order">maps:iterator_order()</seetype>).</p>
+ </item>
+ <tag><c>K</c></tag>
+ <item>
+ <p>Similar to <c>k</c>, for formatting maps in map-key order,
+ but takes an extra argument that specifies the
+ <seetype marker="maps#iterator_order">maps:iterator_order()</seetype>.</p>
+ <p>For example:</p>
+ <pre>
+> <input>M = #{ a => 1, b => 2 }.</input>
+#{a => 1,b => 2}
+> <input><![CDATA[io:format("~Kp~n", [reversed, M]).]]></input>
+#{b => 2,a => 1}
+ok</pre>
+ </item>
+ </taglist>
</item>
</list>
<p>If <c>F</c>, <c>P</c>, or <c>Pad</c> is a <c>*</c> character,
@@ -779,9 +811,14 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
[{expand_fun,#Fun&lt;group.0.120017273&gt;},
{echo,true},
{binary,false},
- {encoding,unicode}]</pre>
+ {encoding,unicode},
+ {terminal,true}]</pre>
<p>This example is, as can be seen, run in an environment where the
terminal supports Unicode input and output.</p>
+ <p>The <c>terminal</c> option is read only and indicates whether
+ the output stream is a terminal or not.
+ See <seemfa marker="#setopts/1"><c>setopts/1</c></seemfa> for a description
+ of the other options.</p>
</desc>
</func>
@@ -1135,13 +1172,18 @@ enter><input>1.0er.</input>
<seemfa marker="#get_line/1"><c>get_line/1,2</c></seemfa>.</p>
<p>The function is called with the current line, up to
the cursor, as a reversed string. It is to return a
- three-tuple: <c>{yes|no, string(), [string(), ...]}</c>. The
+ three-tuple: <c>{yes|no, string(), list()}</c>. The
first element gives a beep if <c>no</c>, otherwise the
expansion is silent; the second is a string that will be
entered at the cursor position; the third is a list of
possible expansions. If this list is not empty,
- it is printed and the current input line is written
- once again.</p>
+ it is printed below the current input line.
+ The list of possible expansions can be formatted in
+ different ways to make more advanced expansion suggestions
+ more readable to the user, see
+ <seemfa marker="edlin_expand#expand/2">
+ <c>edlin_expand:expand/2</c></seemfa> for
+ documentation of that.</p>
<p>Trivial example (beep on anything except empty line, which
is expanded to <c>"quit"</c>):</p>
<code type="none">
@@ -1169,13 +1211,19 @@ fun("") -> {yes, "quit", []};
is in <c>{encoding, unicode}</c> mode if the I/O device supports
it. The mode can be changed, if the assumption of the runtime
system is wrong, by setting this option.</p>
- <p>The I/O device used when Erlang is started with the "-oldshell"
- or "-noshell" flags is by default set to <c>latin1</c> encoding,
- meaning that any characters &gt; codepoint 255 are escaped
- and that input is expected to be plain 8-bit ISO Latin-1.
- If the encoding is changed to Unicode, input and output from
- the standard file descriptors are in UTF-8 (regardless of
- operating system).</p>
+ <note><p>
+ Prior to OTP 26.0, when Erlang was started with the
+ <c>-oldshell</c> or <c>-noshell</c> flags (for example, in an
+ <c>escript</c>), the default encoding for <c>standard_io</c> was
+ set to <c>latin1</c>, meaning that any characters &gt; codepoint
+ 255 were escaped and that input was expected to be plain 8-bit
+ ISO Latin-1. As of OTP 26.0, <c>standard_io</c> always defaults
+ to <c>unicode</c> if its supported, otherwise <c>latin1</c>.
+ </p><p>
+ If you want to send raw bytes on <c>standard_io</c>, you now
+ always need to explicitly set the encoding to <c>latin1</c>;
+ otherwise, code points 128-255 will be converted to UTF-8.
+ </p></note>
<p>Files can also be set in <c>{encoding, unicode}</c>, meaning
that data is written and read as UTF-8. More encodings are
possible for files, see below.</p>
diff --git a/lib/stdlib/doc/src/io_lib.xml b/lib/stdlib/doc/src/io_lib.xml
index 3b7aea529e..f882e632bd 100644
--- a/lib/stdlib/doc/src/io_lib.xml
+++ b/lib/stdlib/doc/src/io_lib.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2020</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -92,6 +92,10 @@
<item><p><c>strings</c> is set to <c>false</c> if modifier
<c>l</c> is present.</p>
</item>
+ <item><p><c>maps_order</c> is set to <c>undefined</c> by default,
+ <c>ordered</c> if modifier <c>k</c> is present, or <c>reversed</c>
+ or <c>CmpFun</c> if modifier <c>K</c> is present.</p>
+ </item>
</list>
</desc>
</datatype>
diff --git a/lib/stdlib/doc/src/io_protocol.xml b/lib/stdlib/doc/src/io_protocol.xml
index 67352543ec..e47ace0228 100644
--- a/lib/stdlib/doc/src/io_protocol.xml
+++ b/lib/stdlib/doc/src/io_protocol.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>1999</year>
- <year>2021</year>
+ <year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -449,10 +449,10 @@ ok
<c>columns</c>.</item>
</list>
- <p>The I/O server is to send the <c>Reply</c> as:</p>
+ <p>The I/O server is to send one of the following as <c>Reply</c>:</p>
<pre>
-{ok, N}
+N
{error, Error}</pre>
<list type="bulleted">
diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml
index d2d9870aee..1a14654821 100644
--- a/lib/stdlib/doc/src/lists.xml
+++ b/lib/stdlib/doc/src/lists.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -176,43 +176,33 @@
<func>
<name name="enumerate" arity="1" since="OTP 25.0"/>
+ <name name="enumerate" arity="2" since="OTP 25.0"/>
+ <name name="enumerate" arity="3" since="OTP @OTP-18495@"/>
<fsummary>Annotates elements with their index.</fsummary>
<desc>
<p>Returns <c><anno>List1</anno></c> with each element
- <c>H</c> replaced by a tuple of form <c>{I, H}</c> where
- <c>I</c> is the position of <c>H</c> in
- <c><anno>List1</anno></c>. The enumeration starts with 1 and
- increases by 1 in each step.</p>
- <p>That is, <c>enumerate/1</c> behaves as if it had been defined as follows:</p>
+ <c>H</c> replaced by a tuple of form <c>{I, H}</c> where
+ <c>I</c> is the position of <c>H</c> in
+ <c><anno>List1</anno></c>. The enumeration starts with
+ <c><anno>Index</anno></c> and increases by <c><anno>Step</anno></c>
+ in each step.</p>
+ <p>That is, <c>enumerate/3</c> behaves as if it had been defined as follows:</p>
<code type="erl">
-enumerate(List) ->
- {List1, _ } = lists:mapfoldl(fun(T, Acc) -> {{Acc, T}, Acc+1} end, 1, List),
+enumerate(I, S, List) ->
+ {List1, _ } = lists:mapfoldl(fun(T, Acc) -> {{Acc, T}, Acc+S} end, I, List),
List1.</code>
- <p><em>Example:</em></p>
+ <p>The default values for <c><anno>Index</anno></c> and
+ <c><anno>Step</anno></c> are both <c>1</c>.</p>
+ <p><em>Examples:</em></p>
<pre>
> <input>lists:enumerate([a,b,c]).</input>
[{1,a},{2,b},{3,c}]</pre>
- </desc>
- </func>
-
- <func>
- <name name="enumerate" arity="2" since="OTP 25.0"/>
- <fsummary>Annotates elements with their index.</fsummary>
- <desc>
- <p>Returns <c><anno>List1</anno></c> with each element
- <c>H</c> replaced by a tuple of form <c>{I, H}</c> where
- <c>I</c> is the position of <c>H</c> in
- <c><anno>List1</anno></c>. The enumeration starts with
- <c><anno>Index</anno></c> and increases by 1 in each step.</p>
- <p>That is, <c>enumerate/2</c> behaves as if it had been defined as follows:</p>
- <code type="erl">
-enumerate(I, List) ->
- {List1, _ } = lists:mapfoldl(fun(T, Acc) -> {{Acc, T}, Acc+1} end, I, List),
- List1.</code>
- <p><em>Example:</em></p>
<pre>
> <input>lists:enumerate(10, [a,b,c]).</input>
[{10,a},{11,b},{12,c}]</pre>
+ <pre>
+> <input>lists:enumerate(0, -2, [a,b,c]).</input>
+[{0,a},{-2,b},{-4,c}]</pre>
</desc>
</func>
@@ -1069,35 +1059,69 @@ splitwith(Pred, List) ->
<func>
<name name="zip" arity="2" since=""/>
+ <name name="zip" arity="3" since="OTP @OTP-18318@"/>
<fsummary>Zip two lists into a list of two-tuples.</fsummary>
<desc>
- <p>"Zips" two lists of equal length into one list of two-tuples,
+ <p>"Zips" two lists into one list of two-tuples,
where the first element of each tuple is taken from the first
list and the second element is taken from the corresponding
element in the second list.</p>
+ <p>The <c><anno>How</anno></c> parameter specifies the behavior
+ if the given lists are of different lengths.</p>
+ <taglist>
+ <tag><c>fail</c></tag>
+ <item>The call will fail if the given lists are not of equal
+ length. This is the default.</item>
+ <tag><c>trim</c></tag>
+ <item>Surplus elements from the longer list will be ignored.
+ <p><em>Examples:</em></p>
+ <pre>
+> <input>lists:zip([a, b], [1, 2, 3], trim).</input>
+[{a,1},{b,2}]
+> <input>lists:zip([a, b, c], [1, 2], trim).</input>
+[{a,1},{b,2}]</pre>
+ </item>
+ <tag><c>{pad, Defaults}</c></tag>
+ <item>The shorter list will be padded to the length of the
+ longer list, using the respective elements from the given
+ <c>Defaults</c> tuple.
+ <p><em>Examples:</em></p>
+ <pre>
+> <input>lists:zip([a, b], [1, 2, 3], {pad, {x, 0}}).</input>
+[{a,1},{b,2},{x,3}]
+> <input>lists:zip([a, b, c], [1, 2], {pad, {x, 0}}).</input>
+[{a,1},{b,2},{c,0}]</pre>
+ </item>
+ </taglist>
</desc>
</func>
<func>
<name name="zip3" arity="3" since=""/>
+ <name name="zip3" arity="4" since="OTP @OTP-18318@"/>
<fsummary>Zip three lists into a list of three-tuples.</fsummary>
<desc>
- <p>"Zips" three lists of equal length into one list of
+ <p>"Zips" three lists into one list of
three-tuples, where the first element of each tuple is taken
from the first list, the second element is taken from
the corresponding element in the second list, and the third
element is taken from the corresponding element in the third list.</p>
+ <p>For a description of the <c><anno>How</anno></c> parameter, see
+ <seemfa marker="#zip/3"><c>zip/3</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="zipwith" arity="3" since=""/>
+ <name name="zipwith" arity="4" since="OTP @OTP-18318@"/>
<fsummary>Zip two lists into one list according to a fun.</fsummary>
<desc>
- <p>Combines the elements of two lists of equal length into one list.
+ <p>Combines the elements of two lists into one list.
For each pair <c><anno>X</anno>, <anno>Y</anno></c> of list elements
from the two lists, the element in the result list is
<c><anno>Combine</anno>(<anno>X</anno>, <anno>Y</anno>)</c>.</p>
+ <p>For a description of the <c><anno>How</anno></c> parameter, see
+ <seemfa marker="#zip/3"><c>zip/3</c></seemfa>.</p>
<p><c>zipwith(fun(X, Y) -> {X,Y} end, List1, List2)</c> is
equivalent to <c>zip(List1, List2)</c>.</p>
<p><em>Example:</em></p>
@@ -1109,13 +1133,16 @@ splitwith(Pred, List) ->
<func>
<name name="zipwith3" arity="4" since=""/>
+ <name name="zipwith3" arity="5" since="OTP @OTP-18318@"/>
<fsummary>Zip three lists into one list according to a fun.</fsummary>
<desc>
- <p>Combines the elements of three lists of equal length into one
+ <p>Combines the elements of three lists into one
list. For each triple <c><anno>X</anno>, <anno>Y</anno>,
<anno>Z</anno></c> of list elements from the three lists, the element
in the result list is <c><anno>Combine</anno>(<anno>X</anno>,
<anno>Y</anno>, <anno>Z</anno>)</c>.</p>
+ <p>For a description of the <c><anno>How</anno></c> parameter, see
+ <seemfa marker="#zip/3"><c>zip/3</c></seemfa>.</p>
<p><c>zipwith3(fun(X, Y, Z) -> {X,Y,Z} end, List1, List2, List3)</c> is
equivalent to <c>zip3(List1, List2, List3)</c>.</p>
<p><em>Examples:</em></p>
diff --git a/lib/stdlib/doc/src/maps.xml b/lib/stdlib/doc/src/maps.xml
index 203eeccaf3..8a2c573920 100644
--- a/lib/stdlib/doc/src/maps.xml
+++ b/lib/stdlib/doc/src/maps.xml
@@ -42,7 +42,8 @@
<desc>
<p>An iterator representing the associations in a map with keys of type
<c><anno>Key</anno></c> and values of type <c><anno>Value</anno></c>.</p>
- <p>Created using <seemfa marker="#iterator/1"><c>maps:iterator/1</c></seemfa>.</p>
+ <p>Created using <seemfa marker="#iterator/1"><c>maps:iterator/1</c></seemfa> or
+ <seemfa marker="#iterator/2"><c>maps:iterator/2</c></seemfa>.</p>
<p>Consumed by:</p>
<list type="bulleted">
<item><seemfa marker="#next/1"><c>maps:next/1</c></seemfa></item>
@@ -51,6 +52,7 @@
<item><seemfa marker="#fold/3"><c>maps:fold/3</c></seemfa></item>
<item><seemfa marker="#foreach/2"><c>maps:foreach/2</c></seemfa></item>
<item><seemfa marker="#map/2"><c>maps:map/2</c></seemfa></item>
+ <item><seemfa marker="#to_list/1"><c>maps:to_list/1</c></seemfa></item>
</list>
</desc>
</datatype>
@@ -58,6 +60,25 @@
<datatype>
<name name="iterator" n_vars="0"/>
</datatype>
+
+ <datatype>
+ <name name="iterator_order" n_vars="1"/>
+ <desc>
+ <p>Key-based iterator order option that can be one of <c>undefined</c>
+ (default for <seemfa marker="#iterator/1"><c>maps:iterator/1</c></seemfa>),
+ <c>ordered</c> (sorted in map-key order), <c>reversed</c>,
+ or a custom sorting function.</p>
+ <p>Used by <seemfa marker="#iterator/2"><c>maps:iterator/2</c></seemfa>.</p>
+ <p>The
+ <seeguide
+ marker="system/reference_manual:expressions#term-comparisons">
+ Expressions section</seeguide> contains descriptions of how terms are ordered.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="iterator_order" n_vars="0"/>
+ </datatype>
</datatypes>
<funcs>
@@ -92,7 +113,7 @@
returns <c>true</c>, the association is copied to the result map. If
it returns <c>false</c>, the association is not copied. If it returns
<c>{true, NewValue}</c>, the value for <c><anno>Key</anno></c> is
- replaced with <c>NewValue</c>at this position is replaced in the
+ replaced with <c>NewValue</c> at this position is replaced in the
result map.</p>
<p>The call fails with a <c>{badmap,Map}</c> exception if
<c><anno>MapOrIter</anno></c> is not a map or valid iterator,
@@ -237,15 +258,19 @@ val1
<func>
<name name="groups_from_list" arity="2" since="OTP 25.0"/>
- <fsummary>Splits the list into groups using a function as discriminator.</fsummary>
- <desc>
- <p>The result is a map where each key is given by <anno>Fun</anno>
- and each value is a list of elements. The order of elements within
- each list is preserved from the list.</p>
+ <fsummary>Partitions a list into groups using a function as discriminator.</fsummary>
+ <desc>
+ <p>Partitions the given <c><anno>List</anno></c> into a map of groups.</p>
+ <p>The result is a map where each key is given by <c><anno>KeyFun</anno></c>
+ and each value is a list of elements from the given <c><anno>List</anno></c>
+ for which <c><anno>KeyFun</anno></c> returned the same key.</p>
+ <p>The order of elements within each group list is preserved from the original
+ list.</p>
<p><em>Examples:</em></p>
<pre>
-> <input>maps:groups_from_list(fun(X) -> X rem 2 end, [1,2,3]).</input>
-#{0 => [2], 1 => [1, 3]}
+> <input>EvenOdd = fun(X) -> case X rem 2 of 0 -> even; 1 -> odd end end,</input>
+<input>maps:groups_from_list(EvenOdd, [1, 2, 3]).</input>
+#{even => [2], odd => [1, 3]}
> <input>maps:groups_from_list(fun erlang:length/1, ["ant", "buffalo", "cat", "dingo"]).</input>
#{3 => ["ant", "cat"], 5 => ["dingo"], 7 => ["buffalo"]}</pre>
</desc>
@@ -253,18 +278,26 @@ val1
<func>
<name name="groups_from_list" arity="3" since="OTP 25.0"/>
- <fsummary>Splits the list into groups using a function as discriminator.</fsummary>
- <desc>
- <p>The result is a map where each key is given by
- <anno>Fun</anno> and each value is a list of elements given by
- the <anno>ValueFun</anno>. The order of elements within each
- list is preserved from the list.</p>
+ <fsummary>Partitions a list into groups using a function as discriminator.</fsummary>
+ <desc>
+ <p>Partitions the given <c><anno>List</anno></c> into a map of groups.</p>
+ <p>The result is a map where each key is given by <c><anno>KeyFun</anno></c>
+ and each value is a list of elements from the given <c><anno>List</anno></c>,
+ mapped via <c><anno>ValueFun</anno></c>, for which <c><anno>KeyFun</anno></c>
+ returned the same key.</p>
+ <p>The order of elements within each group list is preserved from the
+ original list.</p>
<p><em>Examples:</em></p>
<pre>
-> <input>maps:groups_from_list(fun(X) -> X rem 2 end, fun(X) -> X*X end, [1,2,3]).</input>
-#{0 => [4], 1 => [1, 9]}
-> <input>maps:groups_from_list(fun erlang:length/1, fun lists:reverse/1, ["ant", "buffalo", "cat", "dingo"]).</input>
-#{3 => ["tna","tac"],5 => ["ognid"],7 => ["olaffub"]}</pre>
+> <input>EvenOdd = fun(X) -> case X rem 2 of 0 -> even; 1 -> odd end end,</input>
+> <input>Square = fun(X) -> X * X end,</input>
+> <input>maps:groups_from_list(EvenOdd, Square, [1, 2, 3]).</input>
+#{even => [4], odd => [1, 9]}
+> <input>maps:groups_from_list(</input>
+<input> fun erlang:length/1,</input>
+<input> fun lists:reverse/1,</input>
+<input> ["ant", "buffalo", "cat", "dingo"]).</input>
+#{3 => ["tna", "tac"],5 => ["ognid"],7 => ["olaffub"]}</pre>
</desc>
</func>
@@ -362,6 +395,59 @@ none</code>
</func>
<func>
+ <name name="iterator" arity="2" since="OTP 26.0"/>
+ <fsummary>Create a map iterator.</fsummary>
+ <desc>
+ <p>Returns a map iterator <c><anno>Iterator</anno></c> that can
+ be used by <seemfa marker="#next/1"><c>maps:next/1</c></seemfa>
+ to traverse the key-value associations in a map sorted by key using
+ the given <c><anno>Order</anno></c>.</p>
+ <p>The call fails with a <c>{badmap,Map}</c> exception if
+ <c><anno>Map</anno></c> is not a map or if <c><anno>Order</anno></c>
+ is invalid.</p>
+ <p><em>Example (when </em><c><anno>Order</anno></c><em> is </em><c>ordered</c><em>):</em></p>
+ <code type="none"><![CDATA[
+> M = #{ a => 1, b => 2 }.
+#{a => 1,b => 2}
+> OrdI = maps:iterator(M, ordered), ok.
+ok
+> {K1, V1, OrdI2} = maps:next(OrdI), {K1, V1}.
+{a,1}
+> {K2, V2, OrdI3} = maps:next(OrdI2),{K2, V2}.
+{b,2}
+> maps:next(OrdI3).
+none
+ ]]></code>
+ <p><em>Example (when </em><c><anno>Order</anno></c><em> is </em><c>reversed</c><em>):</em></p>
+ <code type="none"><![CDATA[
+> M = #{ a => 1, b => 2 }.
+#{a => 1,b => 2}
+> RevI = maps:iterator(M, reversed), ok.
+ok
+> {K2, V2, RevI2} = maps:next(RevI), {K2, V2}.
+{b,2}
+> {K1, V1, RevI3} = maps:next(RevI2),{K1, V1}.
+{a,1}
+> maps:next(RevI3).
+none
+ ]]></code>
+ <p><em>Example (when </em><c><anno>Order</anno></c><em> is an arithmetic sorting function):</em></p>
+ <code type="none"><![CDATA[
+> M = #{ -1 => a, -1.0 => b, 0 => c, 0.0 => d }.
+#{-1 => a,0 => c,-1.0 => b,0.0 => d}
+> ArithOrdI = maps:iterator(M, fun(A, B) -> A =< B end), ok.
+ok
+> maps:to_list(ArithOrdI).
+[{-1,a},{-1.0,b},{0,c},{0.0,d}]
+> ArithRevI = maps:iterator(M, fun(A, B) -> B < A end), ok.
+ok
+> maps:to_list(ArithRevI).
+[{0.0,d},{0,c},{-1.0,b},{-1,a}]
+ ]]></code>
+ </desc>
+ </func>
+
+ <func>
<name name="keys" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
@@ -575,15 +661,24 @@ error</code>
<fsummary></fsummary>
<desc>
<p>Returns a list of pairs representing the key-value associations of
- <c><anno>Map</anno></c>, where the pairs
+ <c><anno>MapOrIterator</anno></c>, where the pairs
<c>[{K1,V1}, ..., {Kn,Vn}]</c> are returned in arbitrary order.</p>
<p>The call fails with a <c>{badmap,Map}</c> exception if
- <c><anno>Map</anno></c> is not a map.</p>
+ <c><anno>MapOrIterator</anno></c> is not a map or an iterator obtained
+ by a call to <seemfa marker="#iterator/1">iterator/1</seemfa> or
+ <seemfa marker="#iterator/2">iterator/2</seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> Map = #{42 => value_three,1337 => "value two","a" => 1},
maps:to_list(Map).
[{42,value_three},{1337,"value two"},{"a",1}]</code>
+ <p><em>Example (using </em><seemfa marker="#iterator/2">iterator/2</seemfa><em>):</em></p>
+<code type="none"><![CDATA[
+> Map = #{ z => 1, y => 2, x => 3 }.
+#{x => 3,y => 2,z => 1}
+> maps:to_list(maps:iterator(Map, ordered)).
+[{x,3},{y,2},{z,1}]
+ ]]></code>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/math.xml b/lib/stdlib/doc/src/math.xml
index 59a05a4e5a..69df237496 100644
--- a/lib/stdlib/doc/src/math.xml
+++ b/lib/stdlib/doc/src/math.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>1996</year>
- <year>2020</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -102,9 +102,20 @@ erf(X) = 2/sqrt(pi)*integral from 0 to X of exp(-t*t) dt.</pre>
<func>
<name name="pi" arity="0" since=""/>
- <fsummary>A useful number.</fsummary>
+ <fsummary>Ratio of the circumference of a circle to its diameter.</fsummary>
<desc>
- <p>A useful number.</p>
+ <p>Ratio of the circumference of a circle to its diameter.</p>
+ <p>Floating point approximation of mathematical constant pi.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="tau" arity="0" since="OTP @OTP-18361@"/>
+ <fsummary>Ratio of the circumference of a circle to its radius.</fsummary>
+ <desc>
+ <p>Ratio of the circumference of a circle to its radius.</p>
+ <p>This constant is equivalent to a full turn when described in radians.</p>
+ <p>The same as <c>2 * pi()</c>.</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/orddict.xml b/lib/stdlib/doc/src/orddict.xml
index 796cb42ede..46370358e6 100644
--- a/lib/stdlib/doc/src/orddict.xml
+++ b/lib/stdlib/doc/src/orddict.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2000</year><year>2020</year>
+ <year>2000</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -38,7 +38,7 @@
<p>This module provides a <c>Key</c>-<c>Value</c> dictionary.
An <c>orddict</c> is a representation of a dictionary, where a
list of pairs is used to store the keys and values. The list is
- ordered after the keys in the <em>Erlang term order</em>.</p>
+ ordered after the keys in the <seeguide marker="system/reference_manual:expressions#term-comparisons">Erlang term order</seeguide>.</p>
<p>This module provides the same interface as the
<seeerl marker="dict"><c>dict(3)</c></seeerl> module
@@ -69,6 +69,24 @@
generated if the initial value associated with <c><anno>Key</anno></c>
is not a list of values.</p>
<p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
+ <p><em>Example 1:</em></p>
+ <pre>
+1> <input>OrdDict1 = orddict:from_list([{x, []}]).</input>
+[{x,[]}]
+2> <input>OrdDict2 = orddict:append(x, 1, OrdDict1).</input>
+[{x,[1]}]
+3> <input>OrdDict3 = orddict:append(x, 2, OrdDict2).</input>
+[{x,[1,2]}]
+4> <input>orddict:append(y, 3, OrdDict3).</input>
+[{x,[1,2]},{y,[3]}]</pre>
+ <p><em>Example 2:</em></p>
+ <pre>
+1> <input>OrdDict1 = orddict:from_list([{a, no_list}]).</input>
+[{a,no_list}]
+2> <input>orddict:append(a, 1, OrdDict1).</input>
+** exception error: bad argument
+ in operator ++/2
+ called as no_list ++ [1]</pre>
</desc>
</func>
@@ -81,6 +99,14 @@
An exception is generated if the initial value associated with
<c><anno>Key</anno></c> is not a list of values.</p>
<p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>OrdDict1 = orddict:from_list([{x, []}]).</input>
+[{x,[]}]
+2> <input>OrdDict2 = orddict:append_list(x, [1,2], OrdDict1).</input>
+[{x,[1,2]}]
+3> <input>OrdDict3 = orddict:append_list(y, [3,4], OrdDict2).</input>
+[{x,[1,2]},{y,[3,4]}]</pre>
</desc>
</func>
@@ -89,6 +115,12 @@
<fsummary>Erase a key from a dictionary.</fsummary>
<desc>
<p>Erases all items with a specified key from a dictionary.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:erase(a, OrdDict1).</input>
+[{b,2}]</pre>
</desc>
</func>
@@ -101,6 +133,14 @@
the <c><anno>Key</anno></c> is present in the dictionary. An exception
is generated if <c><anno>Key</anno></c> is not in the dictionary.</p>
<p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
+ <p><em>Example:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:fetch(a, OrdDict1).</input>
+1
+3> <input>orddict:fetch(missing, OrdDict1).</input>
+** exception error: no function clause matching orddict:fetch(missing,[])</pre>
</desc>
</func>
@@ -109,6 +149,12 @@
<fsummary>Return all keys in a dictionary.</fsummary>
<desc>
<p>Returns a list of all keys in a dictionary.</p>
+ <p><em>Example:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:fetch_keys(OrdDict1).</input>
+[a,b]</pre>
</desc>
</func>
@@ -118,6 +164,14 @@
<desc>
<p>This function returns value from dictionary and new dictionary without this value.
Returns <c>error</c> if the key is not present in the dictionary.</p>
+ <p><em>Example:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:take(a, OrdDict1).</input>
+{1, [{b,2}]}
+3> <input>orddict:take(missing, OrdDict1).</input>
+error</pre>
</desc>
</func>
@@ -129,6 +183,12 @@
in <c><anno>Orddict1</anno></c> for which
<c><anno>Pred</anno>(<anno>Key</anno>, <anno>Value</anno>)</c> is
<c>true</c>.</p>
+ <p><em>Example:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:filter(fun (K, V) -> V > 1 end, OrdDict1).</input>
+[{b,2}]</pre>
</desc>
</func>
@@ -141,6 +201,14 @@
the value associated with <c><anno>Key</anno></c>, or <c>error</c> if
the key is not present in the dictionary.</p>
<p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
+ <p><em>Example:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:find(a, OrdDict1).</input>
+{ok,1}
+3> <input>orddict:find(c, OrdDict1).</input>
+error</pre>
</desc>
</func>
@@ -153,6 +221,12 @@
(short for accumulator). <c><anno>Fun</anno></c> must return a new
accumulator that is passed to the next call. <c><anno>Acc0</anno></c>
is returned if the list is empty.</p>
+ <p><em>Example:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:fold(fun (K, V, Acc) -> [{K, V+100} | Acc] end, [], OrdDict1).</input>
+[{b,102},{a,101}]</pre>
</desc>
</func>
@@ -188,7 +262,13 @@
<fsummary>Map a function over a dictionary.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno></c> on successive keys and values of
- <c><anno>Orddict1</anno></c> tvo return a new value for each key.</p>
+ <c><anno>Orddict1</anno></c> to return a new value for each key.</p>
+ <p><em>Example:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:map(fun (_K, V) -> V + 100 end, OrdDict1).</input>
+[{a,101},{b,102}]</pre>
</desc>
</func>
@@ -208,6 +288,14 @@ merge(Fun, D1, D2) ->
fold(fun (K, V1, D) ->
update(K, fun (V2) -> Fun(K, V1, V2) end, V1, D)
end, D2, D1).</code>
+ <p><em>Example:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>OrdDict2 = orddict:from_list([{b, 7}, {c, 8}]).</input>
+[{b,7},{c,8}]
+3> <input>orddict:merge(fun (K, V1, V2) -> V1 * V2 end, OrdDict1, OrdDict2).</input>
+[{a, 1},{b, 14},{c,8}]</pre>
</desc>
</func>
@@ -236,6 +324,14 @@ merge(Fun, D1, D2) ->
dictionary. If the <c><anno>Key</anno></c> already exists in
<c><anno>Orddict1</anno></c>,
the associated value is replaced by <c><anno>Value</anno></c>.</p>
+ <p><em>Example:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:store(a, 99, OrdDict1).</input>
+[{a,99},{b,2}]
+3> <input>orddict:store(c, 100, OrdDict1).</input>
+[{a,1},{b,2},{c,99}]</pre>
</desc>
</func>
@@ -254,6 +350,12 @@ merge(Fun, D1, D2) ->
<p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c>
on the value to get a new value. An exception is generated if
<c><anno>Key</anno></c> is not present in the dictionary.</p>
+ <p><em>Example:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:update(a, fun (V) -> V1 + 100 end, OrdDict1).</input>
+[{a, 101}, {b, 102}]</pre>
</desc>
</func>
@@ -269,6 +371,18 @@ merge(Fun, D1, D2) ->
<code type="none">
append(Key, Val, D) ->
update(Key, fun (Old) -> Old ++ [Val] end, [Val], D).</code>
+ <p><em>Example 1:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:update(c, fun (V) -> V1 + 100 end, 99, OrdDict1).</input>
+[{a,1},{b,2},{c,99}]</pre>
+ <p><em>Example 2:</em></p>
+<pre>
+1> <input>OrdDict1 = orddict:from_list([{a, 1}, {b, 2}]).</input>
+[{a,1},{b,2}]
+2> <input>orddict:update(a, fun (V) -> V1 + 100 end, 99, OrdDict1).</input>
+[{a,101},{b,2}]</pre>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/ordsets.xml b/lib/stdlib/doc/src/ordsets.xml
index 35127dcf95..7b02d13ab3 100644
--- a/lib/stdlib/doc/src/ordsets.xml
+++ b/lib/stdlib/doc/src/ordsets.xml
@@ -48,6 +48,11 @@
that while <c>sets</c> considers two elements as different if they
do not match (<c>=:=</c>), this module considers two elements as
different if and only if they do not compare equal (<c>==</c>).</p>
+
+ <p>See the <seeerl marker="sets#compatibility">Compatibility Section
+ in the <c>sets(3)</c> module</seeerl> for more information about
+ the compatibility of the different implementations of sets in the
+ Standard Library.</p>
</description>
<datatypes>
diff --git a/lib/stdlib/doc/src/peer.xml b/lib/stdlib/doc/src/peer.xml
index d8ac800605..71bf71e75e 100644
--- a/lib/stdlib/doc/src/peer.xml
+++ b/lib/stdlib/doc/src/peer.xml
@@ -99,60 +99,60 @@
of the same test suite running in parallel</item>
</list>
<code type="erl">
- -module(my_SUITE).
- -behaviour(ct_suite).
- -export([all/0, groups/0]).
- -export([basic/1, args/1, named/1, restart_node/1, multi_node/1]).
-
- -include_lib("common_test/include/ct.hrl").
-
- groups() ->
- [{quick, [parallel],
- [basic, args, named, restart_node, multi_node]}].
-
- all() ->
- [{group, quick}].
-
- basic(Config) when is_list(Config) ->
- {ok, Peer, _Node} = ?CT_PEER(),
- peer:stop(Peer).
-
- args(Config) when is_list(Config) ->
- %% specify additional arguments to the new node
- {ok, Peer, _Node} = ?CT_PEER(["-emu_flavor", "smp"]),
- peer:stop(Peer).
-
- named(Config) when is_list(Config) ->
- %% pass test case name down to function starting nodes
- Peer = start_node_impl(named_test),
- peer:stop(Peer).
-
- start_node_impl(ActualTestCase) ->
- {ok, Peer, Node} = ?CT_PEER(#{name => ?CT_PEER_NAME(ActualTestCase)}),
- %% extra setup needed for multiple test cases
- ok = rpc:call(Node, application, set_env, [kernel, key, value]),
- Peer.
-
- restart_node(Config) when is_list(Config) ->
- Name = ?CT_PEER_NAME(),
- {ok, Peer, Node} = ?CT_PEER(#{name => Name}),
- peer:stop(Peer),
- %% restart the node with the same name as before
- {ok, Peer2, Node} = ?CT_PEER(#{name => Name, args => ["+fnl"]}),
- peer:stop(Peer2).
+-module(my_SUITE).
+-behaviour(ct_suite).
+-export([all/0, groups/0]).
+-export([basic/1, args/1, named/1, restart_node/1, multi_node/1]).
+
+-include_lib("common_test/include/ct.hrl").
+
+groups() ->
+ [{quick, [parallel],
+ [basic, args, named, restart_node, multi_node]}].
+
+all() ->
+ [{group, quick}].
+
+basic(Config) when is_list(Config) ->
+ {ok, Peer, _Node} = ?CT_PEER(),
+ peer:stop(Peer).
+
+args(Config) when is_list(Config) ->
+ %% specify additional arguments to the new node
+ {ok, Peer, _Node} = ?CT_PEER(["-emu_flavor", "smp"]),
+ peer:stop(Peer).
+
+named(Config) when is_list(Config) ->
+ %% pass test case name down to function starting nodes
+ Peer = start_node_impl(named_test),
+ peer:stop(Peer).
+
+start_node_impl(ActualTestCase) ->
+ {ok, Peer, Node} = ?CT_PEER(#{name => ?CT_PEER_NAME(ActualTestCase)}),
+ %% extra setup needed for multiple test cases
+ ok = rpc:call(Node, application, set_env, [kernel, key, value]),
+ Peer.
+
+restart_node(Config) when is_list(Config) ->
+ Name = ?CT_PEER_NAME(),
+ {ok, Peer, Node} = ?CT_PEER(#{name => Name}),
+ peer:stop(Peer),
+ %% restart the node with the same name as before
+ {ok, Peer2, Node} = ?CT_PEER(#{name => Name, args => ["+fnl"]}),
+ peer:stop(Peer2).
</code>
<p>
The next example demonstrates how to start multiple nodes concurrently:
</p>
<code type="erl">
- multi_node(Config) when is_list(Config) ->
- Peers = [?CT_PEER(#{wait_boot => {self(), tag}})
- || _ &lt;- lists:seq(1, 4)],
- %% wait for all nodes to complete boot process, get their names:
- _Nodes = [receive {tag, {started, Node, Peer}} -> Node end
- || {ok, Peer} &lt;- Peers],
- [peer:stop(Peer) || {ok, Peer} &lt;- Peers].
+multi_node(Config) when is_list(Config) ->
+ Peers = [?CT_PEER(#{wait_boot => {self(), tag}})
+ || _ &lt;- lists:seq(1, 4)],
+ %% wait for all nodes to complete boot process, get their names:
+ _Nodes = [receive {tag, {started, Node, Peer}} -> Node end
+ || {ok, Peer} &lt;- Peers],
+ [peer:stop(Peer) || {ok, Peer} &lt;- Peers].
</code>
<p>
@@ -161,9 +161,9 @@
prompt.
</p>
<code type="erl">
- Ssh = os:find_executable("ssh"),
- peer:start_link(#{exec => {Ssh, ["another_host", "erl"]},
- connection => standard_io}),
+Ssh = os:find_executable("ssh"),
+peer:start_link(#{exec => {Ssh, ["another_host", "erl"]},
+ connection => standard_io}),
</code>
<p>
@@ -172,76 +172,76 @@
running inside containers form an Erlang cluster.
</p>
<code type="erl">
- docker(Config) when is_list(Config) ->
- Docker = os:find_executable("docker"),
- PrivDir = proplists:get_value(priv_dir, Config),
- build_release(PrivDir),
- build_image(PrivDir),
-
- %% start two Docker containers
- {ok, Peer, Node} = peer:start_link(#{name => lambda,
- connection => standard_io,
- exec => {Docker, ["run", "-h", "one", "-i", "lambda"]}}),
- {ok, Peer2, Node2} = peer:start_link(#{name => lambda,
- connection => standard_io,
- exec => {Docker, ["run", "-h", "two", "-i", "lambda"]}}),
-
- %% find IP address of the second node using alternative connection RPC
- {ok, Ips} = peer:call(Peer2, inet, getifaddrs, []),
- {"eth0", Eth0} = lists:keyfind("eth0", 1, Ips),
- {addr, Ip} = lists:keyfind(addr, 1, Eth0),
-
- %% make first node to discover second one
- ok = peer:call(Peer, inet_db, set_lookup, [[file]]),
- ok = peer:call(Peer, inet_db, add_host, [Ip, ["two"]]),
-
- %% join a cluster
- true = peer:call(Peer, net_kernel, connect_node, [Node2]),
- %% verify that second peer node has only the first node visible
- [Node] = peer:call(Peer2, erlang, nodes, []),
-
- %% stop peers, causing containers to also stop
- peer:stop(Peer2),
- peer:stop(Peer).
-
- build_release(Dir) ->
- %% load sasl.app file, otherwise application:get_key will fail
- application:load(sasl),
- %% create *.rel - release file
- RelFile = filename:join(Dir, "lambda.rel"),
- Release = {release, {"lambda", "1.0.0"},
- {erts, erlang:system_info(version)},
- [{App, begin {ok, Vsn} = application:get_key(App, vsn), Vsn end}
- || App &lt;- [kernel, stdlib, sasl]]},
- ok = file:write_file(RelFile, list_to_binary(lists:flatten(
- io_lib:format("~tp.", [Release])))),
- RelFileNoExt = filename:join(Dir, "lambda"),
-
- %% create boot script
- {ok, systools_make, []} = systools:make_script(RelFileNoExt,
- [silent, {outdir, Dir}]),
- %% package release into *.tar.gz
- ok = systools:make_tar(RelFileNoExt, [{erts, code:root_dir()}]).
-
- build_image(Dir) ->
- %% Create Dockerfile example, working only for Ubuntu 20.04
- %% Expose port 4445, and make Erlang distribution to listen
- %% on this port, and connect to it without EPMD
- %% Set cookie on both nodes to be the same.
- BuildScript = filename:join(Dir, "Dockerfile"),
- Dockerfile =
- "FROM ubuntu:20.04 as runner\n"
- "EXPOSE 4445\n"
- "WORKDIR /opt/lambda\n"
- "COPY lambda.tar.gz /tmp\n"
- "RUN tar -zxvf /tmp/lambda.tar.gz -C /opt/lambda\n"
- "ENTRYPOINT [\"/opt/lambda/erts-" ++ erlang:system_info(version) ++
- "/bin/dyn_erl\", \"-boot\", \"/opt/lambda/releases/1.0.0/start\","
- " \"-kernel\", \"inet_dist_listen_min\", \"4445\","
- " \"-erl_epmd_port\", \"4445\","
- " \"-setcookie\", \"secret\"]\n",
- ok = file:write_file(BuildScript, Dockerfile),
- os:cmd("docker build -t lambda " ++ Dir).
+docker(Config) when is_list(Config) ->
+ Docker = os:find_executable("docker"),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ build_release(PrivDir),
+ build_image(PrivDir),
+
+ %% start two Docker containers
+ {ok, Peer, Node} = peer:start_link(#{name => lambda,
+ connection => standard_io,
+ exec => {Docker, ["run", "-h", "one", "-i", "lambda"]}}),
+ {ok, Peer2, Node2} = peer:start_link(#{name => lambda,
+ connection => standard_io,
+ exec => {Docker, ["run", "-h", "two", "-i", "lambda"]}}),
+
+ %% find IP address of the second node using alternative connection RPC
+ {ok, Ips} = peer:call(Peer2, inet, getifaddrs, []),
+ {"eth0", Eth0} = lists:keyfind("eth0", 1, Ips),
+ {addr, Ip} = lists:keyfind(addr, 1, Eth0),
+
+ %% make first node to discover second one
+ ok = peer:call(Peer, inet_db, set_lookup, [[file]]),
+ ok = peer:call(Peer, inet_db, add_host, [Ip, ["two"]]),
+
+ %% join a cluster
+ true = peer:call(Peer, net_kernel, connect_node, [Node2]),
+ %% verify that second peer node has only the first node visible
+ [Node] = peer:call(Peer2, erlang, nodes, []),
+
+ %% stop peers, causing containers to also stop
+ peer:stop(Peer2),
+ peer:stop(Peer).
+
+build_release(Dir) ->
+ %% load sasl.app file, otherwise application:get_key will fail
+ application:load(sasl),
+ %% create *.rel - release file
+ RelFile = filename:join(Dir, "lambda.rel"),
+ Release = {release, {"lambda", "1.0.0"},
+ {erts, erlang:system_info(version)},
+ [{App, begin {ok, Vsn} = application:get_key(App, vsn), Vsn end}
+ || App &lt;- [kernel, stdlib, sasl]]},
+ ok = file:write_file(RelFile, list_to_binary(lists:flatten(
+ io_lib:format("~tp.", [Release])))),
+ RelFileNoExt = filename:join(Dir, "lambda"),
+
+ %% create boot script
+ {ok, systools_make, []} = systools:make_script(RelFileNoExt,
+ [silent, {outdir, Dir}]),
+ %% package release into *.tar.gz
+ ok = systools:make_tar(RelFileNoExt, [{erts, code:root_dir()}]).
+
+build_image(Dir) ->
+ %% Create Dockerfile example, working only for Ubuntu 20.04
+ %% Expose port 4445, and make Erlang distribution to listen
+ %% on this port, and connect to it without EPMD
+ %% Set cookie on both nodes to be the same.
+ BuildScript = filename:join(Dir, "Dockerfile"),
+ Dockerfile =
+ "FROM ubuntu:20.04 as runner\n"
+ "EXPOSE 4445\n"
+ "WORKDIR /opt/lambda\n"
+ "COPY lambda.tar.gz /tmp\n"
+ "RUN tar -zxvf /tmp/lambda.tar.gz -C /opt/lambda\n"
+ "ENTRYPOINT [\"/opt/lambda/erts-" ++ erlang:system_info(version) ++
+ "/bin/dyn_erl\", \"-boot\", \"/opt/lambda/releases/1.0.0/start\","
+ " \"-kernel\", \"inet_dist_listen_min\", \"4445\","
+ " \"-erl_epmd_port\", \"4445\","
+ " \"-setcookie\", \"secret\"]\n",
+ ok = file:write_file(BuildScript, Dockerfile),
+ os:cmd("docker build -t lambda " ++ Dir).
</code>
</section>
@@ -270,13 +270,6 @@
is, <c>peer</c> follows compatibility behaviour and uses the origin node name.
</p>
</item>
- <tag><c>host</c></tag>
- <item>
- <p>
- Enforces a specific host name. Can be used to override the default
- behaviour and start "node@localhost" instead of "node@realhostname".
- </p>
- </item>
<tag><c>longnames</c></tag>
<item>
<p>
@@ -285,6 +278,13 @@
short names is the default.
</p>
</item>
+ <tag><c>host</c></tag>
+ <item>
+ <p>
+ Enforces a specific host name. Can be used to override the default
+ behaviour and start "node@localhost" instead of "node@realhostname".
+ </p>
+ </item>
<tag><c>peer_down</c></tag>
<item>
<p>
@@ -296,6 +296,11 @@
the controlling process to exit abnormally.
</p>
</item>
+ <tag><c>connection</c></tag>
+ <item>
+ <p>Alternative connection specification. See the
+ <seetype marker="#connection"><c>connection</c> datatype</seetype>.</p>
+ </item>
<tag><c>exec</c></tag>
<item>
<p>
@@ -303,16 +308,32 @@
default bash.
</p>
</item>
- <tag><c>connection</c></tag>
+ <tag><c>detached</c></tag>
<item>
- <p>Alternative connection specification. See the
- <seetype marker="#connection"><c>connection</c> datatype</seetype>.</p>
+ <p>Defines whether to pass the <c>-detached</c> flag to the started peer.
+ This option cannot be set to <c>false</c> using the standard_io alternative
+ connection type. Default is <c>true</c>.
+ </p>
</item>
<tag><c>args</c></tag>
<item>
<p>Extra command line arguments to append to the "erl" command. Arguments are
passed as is, no escaping or quoting is needed or accepted.</p>
</item>
+ <tag><c>post_process_args</c></tag>
+ <item>
+ <p>Allows the user to change the arguments passed to <c>exec</c> before the
+ peer is started. This can for example be useful when the <c>exec</c> program
+ wants the arguments to "erl" as a single argument. Example:
+ </p>
+ <code type="erl">
+peer:start(#{ name => peer:random_name(),
+ exec => {os:find_executable("bash"),["-c","erl"]},
+ post_process_args =>
+ fun(["-c"|Args]) -> ["-c", lists:flatten(lists:join($\s, Args))] end
+ }).
+ </code>
+ </item>
<tag><c>env</c></tag>
<item>
<p>
diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml
index aa649a280a..a064c8341e 100644
--- a/lib/stdlib/doc/src/proc_lib.xml
+++ b/lib/stdlib/doc/src/proc_lib.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2020</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -178,16 +178,34 @@
<name name="init_ack" arity="2" since=""/>
<fsummary>Used by a process when it has started.</fsummary>
<desc>
- <p>This function must be used by a process that has been started by
- a <seemfa marker="#start/3"><c>start[_link]/3,4,5</c></seemfa>
+ <p>
+ This function must only be used by a process
+ that has been started by a
+ <seemfa marker="#start/3"><c>start[_link|_monitor]/3,4,5</c></seemfa>
function. It tells <c><anno>Parent</anno></c> that the process has
- initialized itself, has started, or has failed to initialize
- itself.</p>
+ initialized itself and started.
+ </p>
<p>Function <c>init_ack/1</c> uses the parent value
previously stored by the start function used.</p>
- <p>If this function is not called, the start function
- returns an error tuple (if a link and/or a time-out is used) or
- hang otherwise.</p>
+ <p>
+ If neither this function nor
+ <seemfa marker="#init_fail/2"><c>init_fail/2,3</c></seemfa>
+ is called by the started process, the start function
+ returns an error tuple when the started process exits,
+ or when the start function time-out (if used) has passed,
+ see <seemfa marker="#start/3"><c>start/3,4,5</c></seemfa>.
+ </p>
+ <warning>
+ <p>
+ Do not use this function to return an error indicating
+ that the process start failed. When doing so
+ the start function can return before the failing
+ process has exited, which may block VM resources
+ required for a new start attempt to succeed. Use
+ <seemfa marker="#init_fail/2"><c>init_fail/2,3</c></seemfa>
+ for that purpose.
+ </p>
+ </warning>
<p>The following example illustrates how this function and
<c>proc_lib:start_link/3</c> are used:</p>
<code type="none">
@@ -212,6 +230,76 @@ init(Parent) ->
</func>
<func>
+ <name since="OTP 26.0">init_fail(Ret, Exception) -> no_return()</name>
+ <name since="OTP 26.0">init_fail(Parent, Ret, Exception) -> no_return()</name>
+ <fsummary>Used by a process that fails to start.</fsummary>
+ <type>
+ <v>Parent = <seetype marker="erts:erlang#pid">pid()</seetype></v>
+ <v>Ret = <seetype marker="erts:erlang#term">term()</seetype></v>
+ <v>Exception = {Class, Reason} | {Class, Reason, Stacktrace}</v>
+ </type>
+ <desc>
+ <p>
+ This function must only be used by a process
+ that has been started by a
+ <seemfa marker="#start/3"><c>start[_link|_monitor]/3,4,5</c></seemfa>
+ function. It tells <c>Parent</c> that the process has failed to
+ initialize, and immediately raises an exception
+ according to <c>Exception</c>.
+ The start function then returns <c>Ret</c>.
+ </p>
+ <p>
+ See
+ <seemfa marker="erts:erlang#raise/3"><c>erlang:raise/3</c></seemfa>
+ for a description of <c>Class</c>, <c>Reason</c>
+ and <c>Stacktrace</c>.
+ </p>
+ <p>
+ Function <c>init_fail/2</c> uses the parent value
+ previously stored by the start function used.
+ </p>
+ <warning>
+ <p>
+ Do not consider catching the exception from this function.
+ That would defeat its purpose. A process started by a
+ <seemfa marker="#start/3"><c>start[_link|_monitor]/3,4,5</c></seemfa>
+ function should end in a value (that will be ignored)
+ or an exception that will be handled by this module.
+ See <seeerl marker="#description">Description</seeerl>.
+ </p>
+ </warning>
+ <p>
+ If neither this function nor
+ <seemfa marker="#init_ack/1"><c>init_ack/1,2</c></seemfa>
+ is called by the started process, the start function
+ returns an error tuple when the started process exits,
+ or when the start function time-out (if used) has passed,
+ see <seemfa marker="#start/3"><c>start/3,4,5</c></seemfa>.
+ </p>
+ <p>The following example illustrates how this function and
+ <c>proc_lib:start_link/3</c> can be used:</p>
+ <code type="none">
+-module(my_proc).
+-export([start_link/0]).
+-export([init/1]).
+
+start_link() ->
+ proc_lib:start_link(my_proc, init, [self()]).
+
+init(Parent) ->
+ case do_initialization() of
+ ok ->
+ proc_lib:init_ack(Parent, {ok, self()});
+ {error, Reason} = Error ->
+ proc_lib:init_fail(Parent, Error, {exit, normal})
+ end,
+ loop().
+
+...</code>
+ </desc>
+ </func>
+
+ <func>
<name name="initial_call" arity="1" since=""/>
<fsummary>Extract the initial call of a <c>proc_lib</c>spawned process.
</fsummary>
@@ -304,17 +392,42 @@ init(Parent) ->
<name name="start" arity="5" since=""/>
<fsummary>Start a new process synchronously.</fsummary>
<desc>
- <p>Starts a new process synchronously. Spawns the process and
- waits for it to start. When the process has started, it
- <em>must</em> call
+ <p>
+ Starts a new process synchronously. Spawns the process and
+ waits for it to start.
+ </p>
+ <p>
+ To indicate a succesful start,
+ the started process <em>must</em> call
<seemfa marker="#init_ack/2"><c>init_ack(Parent, Ret)</c></seemfa>
- or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>,
- where <c>Parent</c> is the process that evaluates this
- function. At this time, <c>Ret</c> is returned.</p>
+ where <c>Parent</c> is the process that evaluates this function,
+ or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>.
+ <c>Ret</c> is then returned by this function.
+ </p>
+ <p>
+ If the process fails to start, it <em>must</em> fail;
+ preferably by calling
+ <seemfa marker="#init_fail/3">
+ <c>init_fail(Parent, Ret, Exception)</c>
+ </seemfa>
+ where <c>Parent</c> is the process that evaluates this function,
+ or <seemfa marker="#init_fail/2"><c>init_fail(Ret, Exception)</c></seemfa>.
+ <c>Ret</c> is then returned by this function,
+ and the started process fails with <c>Exception</c>.
+ </p>
+ <p>
+ If the process instead fails before calling
+ <c>init_ack/1,2</c> or <c>init_fail/2,3</c>,
+ this function returns <c>{error, Reason}</c>
+ where <c>Reason</c> depends a bit on the exception
+ just like for a process link <c>{'EXIT',Pid,Reason}</c>
+ message.
+ </p>
<p>If <c><anno>Time</anno></c> is specified as an integer, this
function waits for <c><anno>Time</anno></c> milliseconds for the
- new process to call <c>init_ack</c>, or <c>Ret = {error, timeout}</c>
- will be returned, and the process is killed.</p>
+ new process to call <c>init_ack/1,2</c> or <c>init_fail/2,3</c>,
+ otherwise the process gets killed
+ and <c>Ret = {error, timeout}</c> is returned.</p>
<p>Argument <c><anno>SpawnOpts</anno></c>, if specified, is passed
as the last argument to the <seemfa marker="erts:erlang#spawn_opt/2">
<c>spawn_opt/2,3,4,5</c></seemfa> BIF.</p>
@@ -322,6 +435,11 @@ init(Parent) ->
<p>Using spawn option <c>monitor</c> is not
allowed. It causes the function to fail with reason
<c>badarg</c>.</p>
+ <p>
+ Using spawn option <c>link</c> will set a link to
+ the spawned process, just like
+ <seemfa marker="#start_link/3">start_link/3,4,5</seemfa>.
+ </p>
</note>
</desc>
</func>
@@ -335,22 +453,31 @@ init(Parent) ->
<p>
Starts a new process synchronously. Spawns the process and
waits for it to start. A link is atomically set on the
- newly spawned process. When the process has started, it
- <em>must</em> call
- <seemfa marker="#init_ack/2"><c>init_ack(Parent, Ret)</c></seemfa>
- or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>,
- where <c>Parent</c> is the process that evaluates this
- function. At this time, <c>Ret</c> is returned.</p>
- <p>If <c><anno>Time</anno></c> is specified as an integer, this
- function waits for <c><anno>Time</anno></c> milliseconds for the
- new process to call <c>init_ack</c>, or <c>Ret = {error, timeout}</c>
- will be returned, and the process is killed.</p>
- <p>If the process crashes before it has called <c>init_ack/1,2</c>,
- <c>Ret = {error, <anno>Reason</anno>}</c> will be returned if
- the calling process traps exits.</p>
- <p>Argument <c><anno>SpawnOpts</anno></c>, if specified, is passed
- as the last argument to the <seemfa marker="erts:erlang#spawn_opt/2">
- <c>spawn_opt/2,3,4,5</c></seemfa> BIF.</p>
+ newly spawned process.
+ </p>
+ <note>
+ <p>
+ If the started process gets killed or crashes with a reason
+ that is not `normal`, the process link will kill the calling
+ process so this function does not return,
+ unless the calling process traps exits.
+ For example, if this function times out it will kill
+ the spawned process, and then the link might kill
+ the calling process.
+ </p>
+ </note>
+ <p>
+ Besides setting a link on the spawned process
+ this function behaves like
+ <seemfa marker="#start/3">start/3,4,5</seemfa>.
+ </p>
+ <p>
+ When the calling process traps exits;
+ if this function returns due to the spawned process exiting
+ (any error return), this function receives (consumes)
+ the <c>'EXIT'</c> message, also when this function times out
+ and kills the spawned process.
+ </p>
<note>
<p>Using spawn option <c>monitor</c> is not
allowed. It causes the function to fail with reason
@@ -368,34 +495,36 @@ init(Parent) ->
<p>
Starts a new process synchronously. Spawns the process and
waits for it to start. A monitor is atomically set on the
- newly spawned process. When the process has started, it
- <em>must</em> call
- <seemfa marker="#init_ack/2"><c>init_ack(Parent, Ret)</c></seemfa>
- or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>,
- where <c>Parent</c> is the process that evaluates this
- function. At this time, <c>Ret</c> is returned.</p>
- <p>If <c><anno>Time</anno></c> is specified as an integer, this
- function waits for <c><anno>Time</anno></c> milliseconds for the
- new process to call <c>init_ack</c>, or <c>Ret = {error, timeout}</c>
- will be returned, and the process is killed.</p>
+ newly spawned process.
+ </p>
+ <p>
+ Besides setting a monitor on the spawned process
+ this function behaves like
+ <seemfa marker="#start/3">start/3,4,5</seemfa>.
+ </p>
<p>
The return value is <c>{Ret, Mon}</c> where <c>Ret</c> corresponds
- to the <c>Ret</c> argument in the call to <c>init_ack()</c>, and
- <c>Mon</c> is the monitor reference of the monitor that has been
- set up.
+ to the <c>Ret</c> argument in the call to <c>init_ack/1,2</c>
+ or <c>init_fail/2,3</c>, and <c>Mon</c> is the monitor reference
+ of the monitor that has been set up.
</p>
<p>
- A <c>'DOWN'</c> message will be delivered to the caller if
- this function returns, and the spawned process terminates. This is
- true also in the case when the operation times out.
+ If this function returns due to the spawned process exiting,
+ that is returns any error value,
+ a <c>'DOWN'</c> message will be delivered to the calling process,
+ also when this function times out and kills the spawned process.
</p>
- <p>Argument <c><anno>SpawnOpts</anno></c>, if specified, is passed
- as the last argument to the <seemfa marker="erts:erlang#spawn_opt/2">
- <c>spawn_opt/2,3,4,5</c></seemfa> BIF.</p>
<note>
- <p>Using spawn option <c>monitor</c> is not
+ <p>
+ Using spawn option <c>monitor</c> is not
allowed. It causes the function to fail with reason
- <c>badarg</c>.</p>
+ <c>badarg</c>.
+ </p>
+ <p>
+ Using spawn option <c>link</c> will set a link to
+ the spawned process, just like
+ <seemfa marker="#start_link/3">start_link/3,4,5</seemfa>.
+ </p>
</note>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/queue.xml b/lib/stdlib/doc/src/queue.xml
index 2e6f424a84..480ca89963 100644
--- a/lib/stdlib/doc/src/queue.xml
+++ b/lib/stdlib/doc/src/queue.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -44,7 +44,6 @@
not lists. Improper lists cause internal crashes.
An index out of range for a queue also causes
a failure with reason <c>badarg</c>.</p>
-
<p>Some functions, where noted, fail with reason <c>empty</c>
for an empty queue.</p>
@@ -89,7 +88,7 @@
are reverse operations on the queue.</p>
<p>This module has three sets of interface functions: the
- "Original API", the "Extended API", and the "Okasaki API".</p>
+ <em>"Original API"</em>, the <em>"Extended API"</em>, and the <em>"Okasaki API"</em>.</p>
<p>The "Original API" and the "Extended API" both use the
mental picture of a waiting line of items. Both
@@ -133,6 +132,13 @@
<p>Returns <c>true</c> if <c><anno>Pred</anno>(<anno>Item</anno>)</c>
returns <c>true</c> for all items <c><anno>Item</anno></c> in
<c><anno>Q</anno></c>, otherwise <c>false</c>.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+2> <input>queue:all(fun (E) -> E > 3 end, Queue).</input>
+false
+3> <input>queue:all(fun (E) -> E > 0 end, Queue).</input>
+true</pre>
</desc>
</func>
@@ -144,6 +150,13 @@
<p>Returns <c>true</c> if <c><anno>Pred</anno>(<anno>Item</anno>)</c>
returns <c>true</c> for at least one item <c><anno>Item</anno></c>
in <c><anno>Q</anno></c>, otherwise <c>false</c>.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+2> <input>queue:any(fun (E) -> E > 10 end, Queue).</input>
+false
+3> <input>queue:any(fun (E) -> E > 3 end, Queue).</input>
+true</pre>
</desc>
</func>
@@ -154,6 +167,12 @@
<p>Returns a copy of <c><anno>Q1</anno></c> where the first item
matching <c><anno>Item</anno></c> is deleted, if there is such an
item.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+2> <input>Queue1 = queue:delete(3, Queue).</input>
+3> <input>queue:member(3, Queue1).</input>
+false</pre>
</desc>
</func>
@@ -164,6 +183,12 @@
<p>Returns a copy of <c><anno>Q1</anno></c> where the last item
matching <c><anno>Item</anno></c> is deleted, if there is such an
item.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,3,5]).</input>
+2> <input>Queue1 = queue:delete_r(3, Queue).</input>
+3> <input>queue:to_list(Queue1).</input>
+[1,2,3,4,5]</pre>
</desc>
</func>
@@ -175,6 +200,12 @@
<p>Returns a copy of <c><anno>Q1</anno></c> where the first item
for which <c><anno>Pred</anno></c> returns <c>true</c> is deleted,
if there is such an item.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([100,1,2,3,4,5]).</input>
+2> <input>Queue1 = queue:delete_with(fun (E) -> E > 0, Queue).</input>
+3> <input>queue:to_list(Queue1).</input>
+[1,2,3,4,5]</pre>
</desc>
</func>
@@ -186,6 +217,12 @@
<p>Returns a copy of <c><anno>Q1</anno></c> where the last item
for which <c><anno>Pred</anno></c> returns <c>true</c> is deleted,
if there is such an item.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5,100]).</input>
+2> <input>Queue1 = queue:delete_with(fun (E) -> E > 10, Queue).</input>
+3> <input>queue:to_list(Queue1).</input>
+[1,2,3,4,5]</pre>
</desc>
</func>
@@ -201,12 +238,28 @@
<c><anno>Item</anno></c> is not copied. If it returns a list,
the list elements are inserted instead of <c>Item</c> in the
result queue.</p>
+ <p><em>Example 1:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>Queue1 = queue:filter(fun (E) -> E > 2 end, Queue).</input>
+{[5],[3,4]}
+3> <input>queue:to_list(Queue1).</input>
+[3,4,5]</pre>
<p>So, <c><anno>Fun</anno>(<anno>Item</anno>)</c> returning
<c>[<anno>Item</anno>]</c> is thereby
semantically equivalent to returning <c>true</c>, just
as returning <c>[]</c> is semantically equivalent to
returning <c>false</c>. But returning a list builds
more garbage than returning an atom.</p>
+ <p><em>Example 2:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>Queue1 = queue:filter(fun (E) -> [E, E+1] end, Queue).</input>
+{[6,5,5,4,4,3],[1,2,2,3]}
+3> <input>queue:to_list(Queue1).</input>
+[1,2,2,3,3,4,4,5,5,6]</pre>
</desc>
</func>
@@ -222,6 +275,18 @@
<c><anno>Item</anno></c> is not copied. If it returns
<c>{true, NewItem}</c>, the queue element at this position is replaced
with <c>NewItem</c> in the result queue.</p>
+ <p><em>Example 1:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>Queue1 = queue:filtermap(fun (E) -> E > 2 end, Queue).</input>
+{[5],[3,4]}
+3> <input>queue:to_list(Queue1).</input>
+[3,4,5]
+4> <input>Queue1 = queue:filtermap(fun (E) -> {true, E+100} end, Queue).</input>
+{"ihg","ef"}
+5> <input>queue:to_list(Queue1).</input>
+"efghi</pre>
</desc>
</func>
@@ -239,9 +304,9 @@
empty.</p>
<p><em>Example:</em></p>
<pre>
-> <input>queue:fold(fun(X, Sum) -> X + Sum end, 0, queue:from_list([1,2,3,4,5])).</input>
+1> <input>queue:fold(fun(X, Sum) -> X + Sum end, 0, queue:from_list([1,2,3,4,5])).</input>
15
-> <input>queue:fold(fun(X, Prod) -> X * Prod end, 1, queue:from_list([1,2,3,4,5])).</input>
+2> <input>queue:fold(fun(X, Prod) -> X * Prod end, 1, queue:from_list([1,2,3,4,5])).</input>
120</pre>
</desc>
</func>
@@ -263,6 +328,14 @@
<p>Inserts <c><anno>Item</anno></c> at the rear of queue
<c><anno>Q1</anno></c>.
Returns the resulting queue <c><anno>Q2</anno></c>.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>Queue1 = queue:in(100, Queue).</input>
+{[100,5,4,3],[1,2]}
+3> <input>queue:to_list(Queue1).</input>
+[1,2,3,4,5,100]</pre>
</desc>
</func>
@@ -273,6 +346,14 @@
<p>Inserts <c><anno>Item</anno></c> at the front of queue
<c><anno>Q1</anno></c>.
Returns the resulting queue <c><anno>Q2</anno></c>.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>Queue1 = queue:in_r(100, Queue).</input>
+{[5,4,3],[100,1,2]}
+3> <input>queue:to_list(Queue1).</input>
+[100,1,2,3,4,5]</pre>
</desc>
</func>
@@ -306,6 +387,14 @@
<p>Returns a queue <c><anno>Q3</anno></c> that is the result of joining
<c><anno>Q1</anno></c> and <c><anno>Q2</anno></c> with
<c><anno>Q1</anno></c> in front of <c><anno>Q2</anno></c>.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue1 = queue:from_list([1,3]).</input>
+{[3],[1]}
+2> <input>Queue2 = queue:from_list([2,4]).</input>
+{[4],[2]}
+3> <input>queue:to_list(queue:join(Queue1, Queue2)).</input>
+[1,3,2,4]</pre>
</desc>
</func>
@@ -344,6 +433,14 @@
<c><anno>Q2</anno></c> is the resulting queue. If
<c><anno>Q1</anno></c> is empty, tuple
<c>{empty, <anno>Q1</anno>}</c> is returned.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>{{value, 1=Item}, Queue1} = queue:out(Queue).</input>
+{{value,1},{[5,4,3],[2]}}
+3> <input>queue:to_list(Queue1).</input>
+[2,3,4,5]</pre>
</desc>
</func>
@@ -356,6 +453,14 @@
where <c><anno>Item</anno></c> is the item removed and
<c><anno>Q2</anno></c> is the new queue. If <c><anno>Q1</anno></c> is
empty, tuple <c>{empty, <anno>Q1</anno>}</c> is returned.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>{{value, 5=Item}, Queue1} = queue:out_r(Queue).</input>
+{{value,5},{[4,3],[1,2]}}
+3> <input>queue:to_list(Queue1).</input>
+[1,2,3,4]</pre>
</desc>
</func>
@@ -384,6 +489,12 @@
<desc>
<p>Returns a list of the items in the queue in the same order;
the front item of the queue becomes the head of the list.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>List == queue:to_list(Queue).</input>
+true</pre>
</desc>
</func>
</funcs>
@@ -401,6 +512,14 @@
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
the front item from <c><anno>Q1</anno></c>.</p>
<p>Fails with reason <c>empty</c> if <c><anno>Q1</anno></c> is empty.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>Queue = queue:drop(Queue).</input>
+{[5,4,3],[2]}
+3> <input>queue:to_list(Queue1).</input>
+[2,3,4,5]</pre>
</desc>
</func>
@@ -411,6 +530,14 @@
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
the rear item from <c><anno>Q1</anno></c>.</p>
<p>Fails with reason <c>empty</c> if <c><anno>Q1</anno></c> is empty.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>Queue = queue:drop_r(Queue).</input>
+{[4,3],[1,2]}
+3> <input>queue:to_list(Queue1).</input>
+[1,2,3,4]</pre>
</desc>
</func>
@@ -421,6 +548,12 @@
<p>Returns <c><anno>Item</anno></c> at the front of queue
<c><anno>Q</anno></c>.</p>
<p>Fails with reason <c>empty</c> if <c><anno>Q</anno></c> is empty.</p>
+ <p><em>Example 1:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>1 == queue:get(Queue).</input>
+true</pre>
</desc>
</func>
@@ -431,6 +564,12 @@
<p>Returns <c><anno>Item</anno></c> at the rear of queue
<c><anno>Q</anno></c>.</p>
<p>Fails with reason <c>empty</c> if <c><anno>Q</anno></c> is empty.</p>
+ <p><em>Example 1:</em></p>
+ <pre>
+1> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+2> <input>5 == queue:get_r(Queue).</input>
+true</pre>
</desc>
</func>
@@ -441,6 +580,14 @@
<p>Returns tuple <c>{value, <anno>Item</anno>}</c>, where
<c><anno>Item</anno></c> is the front item of <c><anno>Q</anno></c>,
or <c>empty</c> if <c><anno>Q</anno></c> is empty.</p>
+ <p><em>Example 1:</em></p>
+ <pre>
+1> <input>queue:peek(queue:new()).</input>
+empty
+2> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+3> <input>queue:peek(Queue).</input>
+{value, 1}</pre>
</desc>
</func>
@@ -451,12 +598,18 @@
<p>Returns tuple <c>{value, <anno>Item</anno>}</c>, where
<c><anno>Item</anno></c> is the rear item of <c><anno>Q</anno></c>,
or <c>empty</c> if <c><anno>Q</anno></c> is empty.</p>
+ <p><em>Example 1:</em></p>
+ <pre>
+1> <input>queue:peek_r(queue:new()).</input>
+empty
+2> <input>Queue = queue:from_list([1,2,3,4,5]).</input>
+{[5,4,3],[1,2]}
+3> <input>queue:peek_r(Queue).</input>
+{value, 5}</pre>
</desc>
</func>
</funcs>
-
-
<funcs>
<fsdescription>
<title>Okasaki API</title>
@@ -468,6 +621,12 @@
<p>Inserts <c><anno>Item</anno></c> at the head of queue
<c><anno>Q1</anno></c>. Returns
the new queue <c><anno>Q2</anno></c>.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:cons(0, queue:from_list([1,2,3])).</input>
+{[3,2],[0,1]}
+2> <input>queue:to_list(Queue).</input>
+[0,1,2,3]</pre>
</desc>
</func>
@@ -477,6 +636,10 @@
<desc>
<p>Returns the tail item of queue <c><anno>Q</anno></c>.</p>
<p>Fails with reason <c>empty</c> if <c><anno>Q</anno></c> is empty.</p>
+ <p><em>Example 1:</em></p>
+ <pre>
+1> <input>queue:daeh(queue:from_list([1,2,3])).</input>
+3</pre>
</desc>
</func>
@@ -487,6 +650,10 @@
<p>Returns <c><anno>Item</anno></c> from the head of queue
<c><anno>Q</anno></c>.</p>
<p>Fails with reason <c>empty</c> if <c><anno>Q</anno></c> is empty.</p>
+ <p><em>Example 1:</em></p>
+ <pre>
+1> <input>queue:head(queue:from_list([1,2,3])).</input>
+1</pre>
</desc>
</func>
@@ -497,6 +664,12 @@
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
the tail item from <c><anno>Q1</anno></c>.</p>
<p>Fails with reason <c>empty</c> if <c><anno>Q1</anno></c> is empty.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:init(queue:from_list([1,2,3])).</input>
+{[2],[1]}
+2> <input>queue:to_list(Queue).</input>
+[1,2]</pre>
</desc>
</func>
@@ -517,6 +690,10 @@
<desc>
<p>Returns the tail item of queue <c><anno>Q</anno></c>.</p>
<p>Fails with reason <c>empty</c> if <c><anno>Q</anno></c> is empty.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>queue:last(queue:from_list([1,2,3])).</input>
+3</pre>
</desc>
</func>
@@ -527,6 +704,12 @@
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
the tail item from <c><anno>Q1</anno></c>.</p>
<p>Fails with reason <c>empty</c> if <c><anno>Q1</anno></c> is empty.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:liat(queue:from_list([1,2,3])).</input>
+{[2],[1]}
+2> <input>queue:to_list(Queue).</input>
+[1,2]</pre>
</desc>
</func>
@@ -537,6 +720,12 @@
<p>Inserts <c><anno>Item</anno></c> as the tail item of queue
<c><anno>Q1</anno></c>. Returns
the new queue <c><anno>Q2</anno></c>.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>Queue = queue:snoc(queue:from_list([1,2,3]), 4).</input>
+{[4,3,2],[1]}
+2> <input>queue:to_list(Queue).</input>
+[1,2,3,4]</pre>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/rand.xml b/lib/stdlib/doc/src/rand.xml
index 471a23f6b9..fb8075cb32 100644
--- a/lib/stdlib/doc/src/rand.xml
+++ b/lib/stdlib/doc/src/rand.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2015</year><year>2022</year>
+ <year>2015</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -78,7 +78,7 @@
</p>
<taglist>
- <tag><c>exsss</c></tag>
+ <tag since="OTP 22.0"><c>exsss</c></tag>
<item>
<p>Xorshift116**, 58 bits precision and period of 2^116-1</p>
<p>Jump function: equivalent to 2^64 calls</p>
@@ -104,7 +104,7 @@
thanks to its statistical qualities.
</p>
</item>
- <tag><c>exro928ss</c></tag>
+ <tag since="OTP 22.0"><c>exro928ss</c></tag>
<item>
<p>Xoroshiro928**, 58 bits precision and a period of 2^928-1</p>
<p>Jump function: equivalent to 2^512 calls</p>
@@ -127,17 +127,17 @@
the 58 bit adaption.
</p>
</item>
- <tag><c>exrop</c></tag>
+ <tag since="OTP 20.0"><c>exrop</c></tag>
<item>
<p>Xoroshiro116+, 58 bits precision and period of 2^116-1</p>
<p>Jump function: equivalent to 2^64 calls</p>
</item>
- <tag><c>exs1024s</c></tag>
+ <tag since="OTP 20.0"><c>exs1024s</c></tag>
<item>
<p>Xorshift1024*, 64 bits precision and a period of 2^1024-1</p>
<p>Jump function: equivalent to 2^512 calls</p>
</item>
- <tag><c>exsp</c></tag>
+ <tag since="OTP 20.0"><c>exsp</c></tag>
<item>
<p>Xorshift116+, 58 bits precision and period of 2^116-1</p>
<p>Jump function: equivalent to 2^64 calls</p>
@@ -668,7 +668,7 @@ end.</pre>
<seemfa marker="#uniform/0"><c>uniform/0</c></seemfa>
because all bits in the mantissa are random.
This property, in combination with the fact that exactly zero
- is never returned is useful for algoritms doing for example
+ is never returned is useful for algorithms doing for example
<c>1.0 / <anno>X</anno></c> or <c>math:log(<anno>X</anno>)</c>.
</p>
</note>
@@ -751,7 +751,7 @@ end.</pre>
<seemfa marker="#uniform_s/1"><c>uniform_s/1</c></seemfa>
because all bits in the mantissa are random.
This property, in combination with the fact that exactly zero
- is never returned is useful for algoritms doing for example
+ is never returned is useful for algorithms doing for example
<c>1.0 / <anno>X</anno></c> or <c>math:log(<anno>X</anno>)</c>.
</p>
</note>
diff --git a/lib/stdlib/doc/src/re.xml b/lib/stdlib/doc/src/re.xml
index e16ef12f16..d18b976d65 100644
--- a/lib/stdlib/doc/src/re.xml
+++ b/lib/stdlib/doc/src/re.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2007</year>
- <year>2021</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -75,6 +75,9 @@
<datatype>
<name name="compile_option"/>
</datatype>
+ <datatype>
+ <name name="replace_fun"/>
+ </datatype>
</datatypes>
<funcs>
@@ -363,7 +366,7 @@
elements with Replacement.</fsummary>
<desc>
<p>Replaces the matched part of the <c><anno>Subject</anno></c> string
- with the contents of <c><anno>Replacement</anno></c>.</p>
+ with <c><anno>Replacement</anno></c>.</p>
<p>The permissible options are the same as for
<seemfa marker="#run/3"><c>run/3</c></seemfa>, except that option<c>
capture</c> is not allowed. Instead a <c>{return,
@@ -378,8 +381,8 @@
<c>unicode</c> compilation option is specified to this function, both
the regular expression and <c><anno>Subject</anno></c> are to
specified as valid Unicode <c>charlist()</c>s.</p>
- <p>The replacement string can contain the special character
- <c>&amp;</c>, which inserts the whole matching expression in the
+ <p>If the replacement is given as a string, it can contain the special
+ character <c>&amp;</c>, which inserts the whole matching expression in the
result, and the special sequence <c>\</c>N (where N is an integer &gt;
0), <c>\g</c>N, or <c>\g{</c>N<c>}</c>, resulting in the subexpression
number N, is inserted in the result. If no subexpression with that
@@ -401,6 +404,35 @@ re:replace("abcd","c","[\\&amp;]",[{return,list}]).</code>
<p>gives</p>
<code>
"ab[&amp;]d"</code>
+ <p>If the replacement is given as a fun, it will be called with the
+ whole matching expression as the first argument and a list of subexpression
+ matches in the order in which they appear in the regular expression.
+ The returned value will be inserted in the result.</p>
+ <p><em>Example:</em></p>
+ <code>
+re:replace("abcd", ".(.)", fun(Whole, [&lt;&lt;C&gt;&gt;]) -> &lt;&lt;$#, Whole/binary, $-, (C - $a + $A), $#&gt;&gt; end, [{return, list}]).</code>
+ <p>gives</p>
+ <code>
+"#ab-B#cd"</code>
+ <note>
+ <p>Non-matching optional subexpressions will not be included in the list
+ of subexpression matches if they are the last subexpressions in the
+ regular expression.</p>
+ <p><em>Example:</em></p>
+ <p>The regular expression <c>"(a)(b)?(c)?"</c> ("a", optionally followed
+ by "b", optionally followed by "c") will create the following subexpression
+ lists:</p>
+ <list>
+ <item><c>[&lt;&lt;"a"&gt;&gt;, &lt;&lt;"b"&gt;&gt;, &lt;&lt;"c"&gt;&gt;]</c>
+ when applied to the string <c>"abc"</c></item>
+ <item><c>[&lt;&lt;"a"&gt;&gt;, &lt;&lt;&gt;&gt;, &lt;&lt;"c"&gt;&gt;]</c>
+ when applied to the string <c>"acx"</c></item>
+ <item><c>[&lt;&lt;"a"&gt;&gt;, &lt;&lt;"b"&gt;&gt;]</c>
+ when applied to the string <c>"abx"</c></item>
+ <item><c>[&lt;&lt;"a"&gt;&gt;]</c>
+ when applied to the string <c>"axx"</c></item>
+ </list>
+ </note>
<p>As with <c>run/3</c>, compilation errors raise the <c>badarg</c>
exception. <seemfa marker="#compile/2"><c>compile/2</c></seemfa>
can be used to get more information about the error.</p>
@@ -972,7 +1004,7 @@ re:run("ABCabcdABC",".*(?&lt;FOO&gt;abcd).*",[{capture,['FOO']}]).</code>
<p>Here the empty binary (<c>&lt;&lt;&gt;&gt;</c>) represents the
unassigned subpattern. In the <c>binary</c> case, some information
about the matching is therefore lost, as
- <c>&lt;&lt;&gt;&gt;</c> can
+ <c>&lt;&lt;&gt;&gt;</c> can
also be an empty string captured.</p>
<p>If differentiation between empty matches and non-existing
subpatterns is necessary, use the <c>type</c> <c>index</c> and do
diff --git a/lib/stdlib/doc/src/ref_man.xml b/lib/stdlib/doc/src/ref_man.xml
index e63c455ec8..04990db408 100644
--- a/lib/stdlib/doc/src/ref_man.xml
+++ b/lib/stdlib/doc/src/ref_man.xml
@@ -32,6 +32,7 @@
<description>
</description>
<xi:include href="stdlib_app.xml"/>
+ <xi:include href="argparse.xml"/>
<xi:include href="array.xml"/>
<xi:include href="assert_hrl.xml"/>
<xi:include href="base64.xml"/>
@@ -43,6 +44,7 @@
<xi:include href="dict.xml"/>
<xi:include href="digraph.xml"/>
<xi:include href="digraph_utils.xml"/>
+ <xi:include href="edlin_expand.xml"/>
<xi:include href="epp.xml"/>
<xi:include href="erl_anno.xml"/>
<xi:include href="erl_error.xml"/>
diff --git a/lib/stdlib/doc/src/sets.xml b/lib/stdlib/doc/src/sets.xml
index 53b64a3ac0..b3899c440f 100644
--- a/lib/stdlib/doc/src/sets.xml
+++ b/lib/stdlib/doc/src/sets.xml
@@ -69,6 +69,71 @@
</description>
+ <section>
+ <title>Compatibility</title>
+ <p>The following functions in this module also exist and provide
+ the same functionality in the
+ <seeerl marker="gb_sets"><c>gb_sets(3)</c></seeerl> and
+ <seeerl marker="ordsets"><c>ordsets(3)</c></seeerl>
+ modules. That is, by only changing the module name for each call,
+ you can try out different set representations.</p>
+ <list type="bulleted">
+ <item><seemfa marker="#add_element/2"><c>add_element/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#del_element/2"><c>del_element/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#filter/2"><c>filter/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#fold/3"><c>fold/3</c></seemfa>
+ </item>
+ <item><seemfa marker="#from_list/1"><c>from_list/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#intersection/1"><c>intersection/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#intersection/2"><c>intersection/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#is_element/2"><c>is_element/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#is_empty/1"><c>is_empty/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#is_set/1"><c>is_set/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#is_subset/2"><c>is_subset/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#new/0"><c>new/0</c></seemfa>
+ </item>
+ <item><seemfa marker="#size/1"><c>size/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#subtract/2"><c>subtract/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#to_list/1"><c>to_list/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#union/1"><c>union/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#union/2"><c>union/2</c></seemfa>
+ </item>
+ </list>
+ <note>
+ <p>
+ While the three set implementations offer the same <em>functionality</em>
+ with respect to the aforementioned functions, their overall <em>behavior</em>
+ may differ. As mentioned, this module considers elements as different if
+ and only if they do not match (<c>=:=</c>), while both
+ <seeerl marker="ordsets"><c>ordsets</c></seeerl> and
+ <seeerl marker="gb_sets"><c>gb_sets</c></seeerl> consider elements as
+ different if and only if they do not compare equal (<c>==</c>).
+ </p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>sets:is_element(1.0, sets:from_list([1])).</input>
+false
+2> <input>ordsets:is_element(1.0, ordsets:from_list([1])).</input>
+true
+2> <input>gb_sets:is_element(1.0, gb_sets:from_list([1])).</input>
+true</pre>
+ </note>
+ </section>
+
<datatypes>
<datatype>
<name name="set" n_vars="1"/>
diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml
index 928d2686b6..ba6dc771e9 100644
--- a/lib/stdlib/doc/src/shell.xml
+++ b/lib/stdlib/doc/src/shell.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2022</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -957,6 +957,85 @@ q - quit erlang
</func>
<func>
+ <name name="start_interactive" arity="0" since="OTP @OTP-17932@"/>
+ <fsummary>Start the interactive shell</fsummary>
+ <desc>
+ <p>Starts the interactive shell if it has not already been started.
+ It can be used to programatically start the shell from an escript
+ or when erl is started with the -noinput or -noshell flags.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="start_interactive" arity="1" clause_i="1" since="OTP @OTP-17932@"/>
+ <name name="start_interactive" arity="1" clause_i="2" since="OTP @OTP-17932@"/>
+ <name name="start_interactive" arity="1" clause_i="3" since="OTP @OTP-17932@"/>
+ <fsummary>Start the interactive shell</fsummary>
+ <desc>
+ <p>Starts the interactive shell if it has not already been started.
+ It can be used to programatically start the shell from an
+ <seecom marker="erts:escript"><c>escript</c></seecom> or when
+ <seecom marker="erts:erl"><c>erl</c></seecom> is started with the
+ <seecom marker="erts:erl#noinput"><c>-noinput</c></seecom> or
+ <seecom marker="erts:erl#noshell"><c>-noshell</c></seecom> flags.
+ The following options are allowed:</p>
+ <taglist>
+ <tag>noshell</tag>
+ <item>
+ <p>Starts the interactive shell as if <seecom marker="erts:erl#noshell">
+ <c>-noshell</c></seecom> was given to <seecom marker="erts:erl"><c>erl</c></seecom>.
+ This is only useful when erl is started with
+ <seecom marker="erts:erl#noinput"><c>-noinput</c></seecom> and the
+ system want to read input data.
+ </p>
+ </item>
+ <tag><seetype marker="erts:erlang#mfa">mfa()</seetype></tag>
+ <item>
+ <p>Starts the interactive shell using
+ <seetype marker="erts:erlang#mfa"><c>mfa()</c></seetype>
+ as the default shell.</p>
+ </item>
+ <tag>{<seetype marker="erts:erlang#node">node()</seetype>,
+ <seetype marker="erts:erlang#mfa">mfa()</seetype>}</tag>
+ <item>
+ <p>Starts the interactive shell using
+ <seetype marker="erts:erlang#mfa"><c>mfa()</c></seetype> on
+ <seetype marker="erts:erlang#node"><c>node()</c></seetype> as the default shell.</p>
+ </item>
+ <tag>{remote, <seetype marker="erts:erlang#string"><c>string()</c></seetype>}</tag>
+ <item>
+ <p>Starts the interactive shell using as if
+ <seecom marker="erts:erl#remsh"><c>-remsh</c></seecom>
+ was given to <seecom marker="erts:erl"><c>erl</c></seecom>.</p>
+ </item>
+ <tag>{remote,
+ <seetype marker="erts:erlang#string"><c>string()</c></seetype>,
+ <seetype marker="erts:erlang#mfa"><c>mfa()</c></seetype>}</tag>
+ <item>
+ <p>Starts the interactive shell using as if
+ <seecom marker="erts:erl#remsh"><c>-remsh</c></seecom>
+ was given to <seecom marker="erts:erl"><c>erl</c></seecom>
+ but with an alternative shell implementation.</p>
+ </item>
+ </taglist>
+ <p>On error this function will return:</p>
+ <taglist>
+ <tag>already_started</tag>
+ <item>if an interactive shell is already started.</item>
+ <tag>noconnection</tag>
+ <item>if a remote shell was requested but it could not be connected to.</item>
+ <tag>badfile | nofile | on_load_failure</tag>
+ <item>if a remote shell was requested with a custom
+ <seetype marker="erts:erlang#mfa">mfa()</seetype>,
+ but the module could not be loaded. See <seeerl marker="kernel:code#error_reasons">
+ Error Reasons for Code-Loading Functions</seeerl>
+ for a description of the error reasons.
+ </item>
+ </taglist>
+ </desc>
+ </func>
+
+ <func>
<name name="start_restricted" arity="1" since=""/>
<fsummary>Exit a normal shell and starts a restricted shell.</fsummary>
<desc>
@@ -994,6 +1073,17 @@ q - quit erlang
string syntax.</p>
</desc>
</func>
+
+ <func>
+ <name name="whereis" arity="0" since="OTP @OTP-17932@"/>
+ <fsummary>Return the current shell process.</fsummary>
+ <desc>
+ <p>Returns the current shell process on the node where the
+ calling process' group_leader is located. If that node
+ has no shell this function will return undefined.</p>
+ </desc>
+ </func>
+
</funcs>
</erlref>
diff --git a/lib/stdlib/doc/src/specs.xml b/lib/stdlib/doc/src/specs.xml
index 9fdd0d1c21..fc19db4bf3 100644
--- a/lib/stdlib/doc/src/specs.xml
+++ b/lib/stdlib/doc/src/specs.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<specs xmlns:xi="http://www.w3.org/2001/XInclude">
+ <xi:include href="../specs/specs_argparse.xml"/>
<xi:include href="../specs/specs_array.xml"/>
<xi:include href="../specs/specs_base64.xml"/>
<xi:include href="../specs/specs_beam_lib.xml"/>
@@ -10,6 +11,7 @@
<xi:include href="../specs/specs_dict.xml"/>
<xi:include href="../specs/specs_digraph.xml"/>
<xi:include href="../specs/specs_digraph_utils.xml"/>
+ <xi:include href="../specs/specs_edlin_expand.xml"/>
<xi:include href="../specs/specs_epp.xml"/>
<xi:include href="../specs/specs_erl_anno.xml"/>
<xi:include href="../specs/specs_erl_error.xml"/>
diff --git a/lib/stdlib/doc/src/stdlib_app.xml b/lib/stdlib/doc/src/stdlib_app.xml
index 243853140b..e0f50292d6 100644
--- a/lib/stdlib/doc/src/stdlib_app.xml
+++ b/lib/stdlib/doc/src/stdlib_app.xml
@@ -57,6 +57,11 @@
<p>Can be used to set the exception handling of the evaluator process of
Erlang shell.</p>
</item>
+ <tag><marker id="shell_expand_location"/><c>shell_expand_location = above | below</c></tag>
+ <item>
+ <p>Sets where the tab expansion text should appear in the shell.
+ The default is <c>below</c>.</p>
+ </item>
<tag><marker id="shell_history_length"/><c>shell_history_length = integer() >= 0</c></tag>
<item>
<p>Can be used to determine how many commands are saved by the Erlang
@@ -76,6 +81,29 @@
<p>Can be used to determine how many results are saved by the Erlang
shell.</p>
</item>
+ <tag><marker id="shell_session_slogan"/><c>shell_session_slogan = string() | fun() -> string())</c></tag>
+ <item>
+ <p>The slogan printed when starting an Erlang shell. Example: </p>
+ <code type="erl">
+$ erl -stdlib shell_session_slogan '"Test slogan"'
+Erlang/OTP 26 [DEVELOPMENT] [erts-13.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
+
+Test slogan
+1>
+ </code>
+ </item>
+ <tag><marker id="shell_slogan"/><c>shell_slogan = string() | fun(() -> string())</c></tag>
+ <item>
+ <p>The slogan printed when starting the Erlang shell subsystem. Example: </p>
+ <code type="erl">
+$ erl -stdlib shell_slogan '"Test slogan"'
+Test slogan
+Eshell V13.0.2 (abort with ^G)
+1>
+ </code>
+ <p>The default is the return value of <seeerl marker="erts:erlang#system_info_system_version">
+ <c>erlang:system_info(system_version)</c></seeerl>.</p>
+ </item>
<tag><marker id="shell_strings"/><c>shell_strings = boolean()</c></tag>
<item>
<p>Can be used to determine how the Erlang shell outputs lists of
diff --git a/lib/stdlib/doc/src/string.xml b/lib/stdlib/doc/src/string.xml
index 3c8e7250df..5176f6de60 100644
--- a/lib/stdlib/doc/src/string.xml
+++ b/lib/stdlib/doc/src/string.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2021</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -69,7 +69,7 @@
<seemfa marker="#find/3"><c>find/3</c></seemfa>,
<seemfa marker="#replace/3"><c>replace/3</c></seemfa>,
<seemfa marker="#split/2"><c>split/2</c></seemfa>,
- <seemfa marker="#lexemes/2"><c>split/2</c></seemfa> and
+ <seemfa marker="#split/3"><c>split/3</c></seemfa> and
<seemfa marker="#trim/3"><c>trim/3</c></seemfa>.
</p>
<p>
diff --git a/lib/stdlib/doc/src/timer.xml b/lib/stdlib/doc/src/timer.xml
index 11279ff410..3fceddbec6 100644
--- a/lib/stdlib/doc/src/timer.xml
+++ b/lib/stdlib/doc/src/timer.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2021</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -71,10 +71,10 @@
<funcs>
<func>
<name name="apply_after" arity="4" since=""/>
- <fsummary>Apply <c>Module:Function(Arguments)</c> after a specified
- <c>Time</c>.</fsummary>
+ <fsummary>Spawn a process evaluating <c>Module:Function(Arguments)</c>
+ after a specified <c>Time</c>.</fsummary>
<desc>
- <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
+ <p>Evaluates <c>spawn(<anno>Module</anno>, <anno>Function</anno>,
<anno>Arguments</anno>)</c> after <c><anno>Time</anno></c>
milliseconds.</p>
<p>Returns <c>{ok, <anno>TRef</anno>}</c> or
@@ -84,12 +84,53 @@
<func>
<name name="apply_interval" arity="4" since=""/>
- <fsummary>Evaluate <c>Module:Function(Arguments)</c> repeatedly at
- intervals of <c>Time</c>.</fsummary>
+ <fsummary>Spawn a process evaluating <c>Module:Function(Arguments)</c>
+ repeatedly at intervals of <c>Time</c>.</fsummary>
<desc>
- <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
+ <p>Evaluates <c>spawn(<anno>Module</anno>, <anno>Function</anno>,
<anno>Arguments</anno>)</c> repeatedly at intervals of
- <c><anno>Time</anno></c>.</p>
+ <c><anno>Time</anno></c>, irrespective of whether a previously
+ spawned process has finished or not.</p>
+ <warning>
+ <p>If the execution time of the spawned process is, on average,
+ greater than the given <c><anno>Time</anno></c>, multiple such
+ processes will run at the same time. With long execution times,
+ short intervals, and many interval timers running, this may even
+ lead to exceeding the number of allowed processes. As an extreme
+ example, consider
+ <c>[timer:apply_interval(1, timer, sleep, [1000]) || _ &lt;- lists:seq(1, 1000)]</c>,
+ that is, 1,000 interval timers executing a process that takes 1s
+ to complete, started in intervals of 1ms, which would result in
+ 1,000,000 processes running at the same time, far more than a node
+ started with default settings allows (see the
+ <seeguide marker="system/efficiency_guide:advanced#system-limits">System
+ Limits section in the Effiency Guide</seeguide>).</p>
+ </warning>
+ <p>Returns <c>{ok, <anno>TRef</anno>}</c> or
+ <c>{error, <anno>Reason</anno>}</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="apply_repeatedly" arity="4" since="OTP @OTP-18236@"/>
+ <fsummary>Spawn a process evaluating <c>Module:Function(Arguments)</c>
+ repeatedly at intervals of <c>Time</c>.</fsummary>
+ <desc>
+ <p>Evaluates <c>spawn(<anno>Module</anno>, <anno>Function</anno>,
+ <anno>Arguments</anno>)</c> repeatedly at intervals of
+ <c><anno>Time</anno></c>, waiting for the spawned process to
+ finish before starting the next.</p>
+ <p>If the execution time of the spawned process is greater than the
+ given <c><anno>Time</anno></c>, the next process is spawned immediately
+ after the one currently running has finished. Assuming that execution
+ times of the spawned processes performing the applies on average
+ are smaller than <c><anno>Time</anno></c>, the amount of applies
+ made over a large amount of time will be the same even if some
+ individual execution times are larger than
+ <c><anno>Time</anno></c>. The system will try to catch up as soon
+ as possible. For example, if one apply takes
+ <c>2.5*<anno>Time</anno></c>, the following two applies will be
+ made immediately one after the other in sequence.</p>
<p>Returns <c>{ok, <anno>TRef</anno>}</c> or
<c>{error, <anno>Reason</anno>}</c>.</p>
</desc>
@@ -278,14 +319,37 @@
<func>
<name name="tc" arity="1" since="OTP R14B03"/>
- <name name="tc" arity="2" since="OTP R14B"/>
- <name name="tc" arity="3" since=""/>
+ <name name="tc" arity="2" clause_i="1" since="OTP R14B"/>
+ <name name="tc" arity="3" clause_i="1" since=""/>
+ <fsummary>Measure the real time it takes to evaluate <c>Fun</c>.</fsummary>
+ <desc>
+ <taglist>
+ <tag><c>tc/3</c></tag>
+ <item>
+ <p>Calls function <c>timer:tc(Module, Function, Arguments, microsecond)</c>.</p>
+ </item>
+ <tag><c>tc/2</c></tag>
+ <item>
+ <p>Calls function <c>timer:tc(Fun, Arguments, microsecond)</c>.</p>
+ </item>
+ <tag><c>tc/1</c></tag>
+ <item>
+ <p>Calls function <c>timer:tc(Fun, microsecond)</c>.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+
+ <func>
+ <name name="tc" arity="2" clause_i="2" since="OTP @OTP-18355@"/>
+ <name name="tc" arity="3" clause_i="2" since="OTP @OTP-18355@"/>
+ <name name="tc" arity="4" since="OTP @OTP-18355@"/>
<fsummary>Measure the real time it takes to evaluate <c>apply(Module,
Function, Arguments)</c> or <c>apply(Fun, Arguments)</c>.</fsummary>
- <type_desc variable="Time">In microseconds</type_desc>
+ <type_desc variable="Time">In the specified <c>TimeUnit</c></type_desc>
<desc>
<taglist>
- <tag><c>tc/3</c></tag>
+ <tag><c>tc/4</c></tag>
<item>
<p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
<anno>Arguments</anno>)</c> and measures the elapsed real time as
@@ -293,18 +357,18 @@
<c>erlang:monotonic_time/0</c></seemfa>.</p>
<p>Returns <c>{<anno>Time</anno>, <anno>Value</anno>}</c>, where
<c><anno>Time</anno></c> is the elapsed real time in
- <em>microseconds</em>, and <c><anno>Value</anno></c> is what is
+ the specified <c>TimeUnit</c>, and <c><anno>Value</anno></c> is what is
returned from the apply.</p>
</item>
- <tag><c>tc/2</c></tag>
+ <tag><c>tc/3</c></tag>
<item>
<p>Evaluates <c>apply(<anno>Fun</anno>, <anno>Arguments</anno>)</c>.
- Otherwise the same as <c>tc/3</c>.</p>
+ Otherwise the same as <c>tc/4</c>.</p>
</item>
- <tag><c>tc/1</c></tag>
+ <tag><c>tc/2</c></tag>
<item>
<p>Evaluates <c><anno>Fun</anno>()</c>. Otherwise the same as
- <c>tc/2</c>.</p>
+ <c>tc/3</c>.</p>
</item>
</taglist>
</desc>
diff --git a/lib/stdlib/doc/src/unicode.xml b/lib/stdlib/doc/src/unicode.xml
index 1f0133de67..5008f13075 100644
--- a/lib/stdlib/doc/src/unicode.xml
+++ b/lib/stdlib/doc/src/unicode.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>1996</year>
- <year>2020</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -37,7 +37,7 @@
representations. It converts between ISO Latin-1 characters and Unicode
characters, but it can also convert between different Unicode encodings
(like UTF-8, UTF-16, and UTF-32).</p>
- <p>The default Unicode encoding in Erlang is in binaries UTF-8, which is also
+ <p>The default Unicode encoding in Erlang binaries is UTF-8, which is also
the format in which built-in functions and libraries in OTP expect to find
binary Unicode data. In lists, Unicode data is encoded as integers, each
integer representing one character and encoded simply as the Unicode code
diff --git a/lib/stdlib/doc/src/zip.xml b/lib/stdlib/doc/src/zip.xml
index a056621ca1..6ba9f4c212 100644
--- a/lib/stdlib/doc/src/zip.xml
+++ b/lib/stdlib/doc/src/zip.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2006</year><year>2020</year>
+ <year>2006</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -507,6 +507,14 @@
</func>
<func>
+ <name name="zip_get_crc32" arity="2" since="OTP @OTP-18159@"/>
+ <fsummary>Extracts a crc32 checksum from an open archive.</fsummary>
+ <desc>
+ <p>Extracts one crc32 checksum from an open archive.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="zip_list_dir" arity="1" since=""/>
<fsummary>Return a table of files in open zip archive.</fsummary>
<desc>
diff --git a/lib/stdlib/examples/erl_id_trans.erl b/lib/stdlib/examples/erl_id_trans.erl
index d9d1154515..792f944d36 100644
--- a/lib/stdlib/examples/erl_id_trans.erl
+++ b/lib/stdlib/examples/erl_id_trans.erl
@@ -409,13 +409,17 @@ expr({cons,Anno,H0,T0}) ->
T1 = expr(T0), %They see the same variables
{cons,Anno,H1,T1};
expr({lc,Anno,E0,Qs0}) ->
- Qs1 = lc_bc_quals(Qs0),
+ Qs1 = comprehension_quals(Qs0),
E1 = expr(E0),
{lc,Anno,E1,Qs1};
expr({bc,Anno,E0,Qs0}) ->
- Qs1 = lc_bc_quals(Qs0),
+ Qs1 = comprehension_quals(Qs0),
E1 = expr(E0),
{bc,Anno,E1,Qs1};
+expr({mc,Anno,E0,Qs0}) ->
+ Qs1 = comprehension_quals(Qs0),
+ E1 = expr(E0),
+ {mc,Anno,E1,Qs1};
expr({tuple,Anno,Es0}) ->
Es1 = expr_list(Es0),
{tuple,Anno,Es1};
@@ -570,21 +574,25 @@ icr_clauses([C0|Cs]) ->
[C1|icr_clauses(Cs)];
icr_clauses([]) -> [].
-%% -type lc_bc_quals([Qualifier]) -> [Qualifier].
+%% -type comprehension_quals([Qualifier]) -> [Qualifier].
%% Allow filters to be both guard tests and general expressions.
-lc_bc_quals([{generate,Anno,P0,E0}|Qs]) ->
+comprehension_quals([{generate,Anno,P0,E0}|Qs]) ->
+ E1 = expr(E0),
+ P1 = pattern(P0),
+ [{generate,Anno,P1,E1}|comprehension_quals(Qs)];
+comprehension_quals([{b_generate,Anno,P0,E0}|Qs]) ->
E1 = expr(E0),
P1 = pattern(P0),
- [{generate,Anno,P1,E1}|lc_bc_quals(Qs)];
-lc_bc_quals([{b_generate,Anno,P0,E0}|Qs]) ->
+ [{b_generate,Anno,P1,E1}|comprehension_quals(Qs)];
+comprehension_quals([{m_generate,Anno,P0,E0}|Qs]) ->
E1 = expr(E0),
P1 = pattern(P0),
- [{b_generate,Anno,P1,E1}|lc_bc_quals(Qs)];
-lc_bc_quals([E0|Qs]) ->
+ [{m_generate,Anno,P1,E1}|comprehension_quals(Qs)];
+comprehension_quals([E0|Qs]) ->
E1 = expr(E0),
- [E1|lc_bc_quals(Qs)];
-lc_bc_quals([]) -> [].
+ [E1|comprehension_quals(Qs)];
+comprehension_quals([]) -> [].
%% -type fun_clauses([Clause]) -> [Clause].
diff --git a/lib/stdlib/src/Makefile b/lib/stdlib/src/Makefile
index 761d6c4c28..abdb665b09 100644
--- a/lib/stdlib/src/Makefile
+++ b/lib/stdlib/src/Makefile
@@ -42,6 +42,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/stdlib-$(VSN)
# ----------------------------------------------------
MODULES= \
array \
+ argparse \
base64 \
beam_lib \
binary \
@@ -56,7 +57,9 @@ MODULES= \
digraph \
digraph_utils \
edlin \
+ edlin_context \
edlin_expand \
+ edlin_type_suggestion \
epp \
erl_abstract_code \
erl_anno \
@@ -196,7 +199,8 @@ primary_bootstrap_compiler: \
$(BOOTSTRAP_COMPILER)/ebin/erl_parse.beam \
$(BOOTSTRAP_COMPILER)/ebin/erl_lint.beam \
$(BOOTSTRAP_COMPILER)/ebin/io.beam \
- $(BOOTSTRAP_COMPILER)/ebin/otp_internal.beam
+ $(BOOTSTRAP_COMPILER)/ebin/otp_internal.beam \
+ $(BOOTSTRAP_COMPILER)/ebin/erl_internal.beam
$(BOOTSTRAP_COMPILER)/ebin/erl_parse.beam: erl_parse.yrl
diff --git a/lib/stdlib/src/argparse.erl b/lib/stdlib/src/argparse.erl
new file mode 100644
index 0000000000..a5fdd8d3d9
--- /dev/null
+++ b/lib/stdlib/src/argparse.erl
@@ -0,0 +1,1357 @@
+%%
+%%
+%% Copyright Maxim Fedorov
+%%
+%%
+%% 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.
+
+-module(argparse).
+-author("maximfca@gmail.com").
+
+%% API Exports
+-export([
+ run/3,
+ parse/2, parse/3,
+ help/1, help/2,
+ format_error/1
+]).
+
+%% Internal exports for validation and error reporting.
+-export([validate/1, validate/2, format_error/2]).
+
+%%--------------------------------------------------------------------
+%% API
+
+-type arg_type() ::
+ boolean |
+ float |
+ {float, Choice :: [float()]} |
+ {float, [{min, float()} | {max, float()}]} |
+ integer |
+ {integer, Choices :: [integer()]} |
+ {integer, [{min, integer()} | {max, integer()}]} |
+ string |
+ {string, Choices :: [string()]} |
+ {string, Re :: string()} |
+ {string, Re :: string(), ReOptions :: [term()]} |
+ binary |
+ {binary, Choices :: [binary()]} |
+ {binary, Re :: binary()} |
+ {binary, Re :: binary(), ReOptions :: [term()]} |
+ atom |
+ {atom, Choices :: [atom()]} |
+ {atom, unsafe} |
+ {custom, fun((string()) -> term())}.
+%% Built-in types include basic validation abilities
+%% String and binary validation may use regex match (ignoring captured value).
+%% For float, integer, string, binary and atom type, it is possible to specify
+%% available choices instead of regex/min/max.
+
+-type argument_help() :: {
+ unicode:chardata(), %% short form, printed in command usage, e.g. "[--dir <dirname>]", developer is
+ %% responsible for proper formatting (e.g. adding <>, dots... and so on)
+ [unicode:chardata() | type | default] | fun(() -> unicode:chardata())
+}.
+%% Help template definition for argument. Short and long forms exist for every argument.
+%% Short form is printed together with command definition, e.g. "usage: rm [--force]",
+%% while long description is printed in detailed section below: "--force forcefully remove".
+
+-type argument_name() :: atom() | string() | binary().
+
+-type argument() :: #{
+ %% Argument name, and a destination to store value too
+ %% It is allowed to have several arguments named the same, setting or appending to the same variable.
+ name := argument_name(),
+
+ %% short, single-character variant of command line option, omitting dash (example: $b, meaning -b),
+ %% when present, the argument is considered optional
+ short => char(),
+
+ %% long command line option, omitting first dash (example: "kernel" means "-kernel" in the command line)
+ %% long command always wins over short abbreviation (e.g. -kernel is considered before -k -e -r -n -e -l)
+ %% when present, the argument is considered optional
+ long => string(),
+
+ %% makes parser to return an error if the argument is not present in the command line
+ required => boolean(),
+
+ %% default value, produced if the argument is not present in the command line
+ %% parser also accepts a global default
+ default => term(),
+
+ %% parameter type (string by default)
+ type => arg_type(),
+
+ %% action to take when argument is matched
+ action => store | %% default: store argument consumed (last stored wins)
+ {store, term()} | %% does not consume argument, stores term() instead
+ append | %% appends consumed argument to a list
+ {append, term()} | %% does not consume an argument, appends term() to a list
+ count | %% does not consume argument, bumps counter
+ extend, %% uses when nargs is list/nonempty_list/all - appends every element to the list
+
+ %% how many positional arguments to consume
+ nargs =>
+ pos_integer() | %% consume exactly this amount, e.g. '-kernel key value' #{long => "-kernel", args => 2}
+ %% returns #{kernel => ["key", "value"]}
+ 'maybe' | %% if the next argument is positional, consume it, otherwise produce default
+ {'maybe', term()} | %% if the next argument is positional, consume it, otherwise produce term()
+ list | %% consume zero or more positional arguments, until next optional
+ nonempty_list | %% consume at least one positional argument, until next optional
+ all, %% fold remaining command line into this argument
+
+ %% help string printed in usage, hidden help is not printed at all
+ help => hidden | unicode:chardata() | argument_help()
+}.
+%% Command line argument specification.
+%% Argument can be optional - starting with - (dash), and positional.
+
+-type arg_map() :: #{argument_name() => term()}.
+%% Arguments map: argument name to a term, produced by parser. Supplied to the command handler
+
+-type handler() ::
+ optional | %% valid for commands with sub-commands, suppresses parser error when no
+ %% sub-command is selected
+ fun((arg_map()) -> term()) | %% handler accepting arg_map
+ {module(), Fn :: atom()} | %% handler, accepting arg_map, Fn exported from module()
+ {fun(() -> term()), term()} | %% handler, positional form (term() is supplied for omitted args)
+ {module(), atom(), term()}. %% handler, positional form, exported from module()
+%% Command handler. May produce some output. Can accept a map, or be
+%% arbitrary mfa() for handlers accepting positional list.
+%% Special value 'optional' may be used to suppress an error that
+%% otherwise raised when command contains sub-commands, but arguments
+%% supplied via command line do not select any.
+
+-type command_help() :: [unicode:chardata() | usage | commands | arguments | options].
+%% Template for the command help/usage message.
+
+%% Command descriptor
+-type command() :: #{
+ %% Sub-commands are arranged into maps. Command name must not start with <em>prefix</em>.
+ commands => #{string() => command()},
+ %% accepted arguments list. Order is important!
+ arguments => [argument()],
+ %% help line
+ help => hidden | unicode:chardata() | command_help(),
+ %% recommended handler function
+ handler => handler()
+}.
+
+-type cmd_path() :: [string()].
+%% Command path, for nested commands
+
+-export_type([arg_type/0, argument_help/0, argument/0,
+ command/0, handler/0, cmd_path/0, arg_map/0]).
+
+-type parser_error() :: {Path :: cmd_path(),
+ Expected :: argument() | undefined,
+ Actual :: string() | undefined,
+ Details :: unicode:chardata()}.
+%% Returned from `parse/2,3' when command spec is valid, but the command line
+%% cannot be parsed using the spec.
+%% When `Expected' is undefined, but `Actual' is not, it means that the input contains
+%% an unexpected argument which cannot be parsed according to command spec.
+%% When `Expected' is an argument, and `Actual' is undefined, it means that a mandatory
+%% argument is not provided in the command line.
+%% When both `Expected' and `Actual' are defined, it means that the supplied argument
+%% is failing validation.
+%% When both are `undefined', there is some logical issue (e.g. a sub-command is required,
+%% but was not selected).
+
+-type parser_options() :: #{
+ %% allowed prefixes (default is [$-]).
+ prefixes => [char()],
+ %% default value for all missing optional arguments
+ default => term(),
+ %% root command name (program name)
+ progname => string() | atom(),
+ %% considered by `help/2' only
+ command => cmd_path(), %% command to print the help for
+ columns => pos_integer() %% viewport width, in characters
+}.
+%% Parser options
+
+-type parse_result() ::
+ {ok, arg_map(), Path :: cmd_path(), command()} |
+ {error, parser_error()}.
+%% Parser result: argument map, path leading to successfully
+%% matching command (contains only ["progname"] if there were
+%% no subcommands matched), and a matching command.
+
+%% @equiv validate(Command, #{})
+-spec validate(command()) -> Progname :: string().
+validate(Command) ->
+ validate(Command, #{}).
+
+%% @doc Validate command specification, taking Options into account.
+%% Raises an error if the command specification is invalid.
+-spec validate(command(), parser_options()) -> Progname :: string().
+validate(Command, Options) ->
+ Prog = executable(Options),
+ is_list(Prog) orelse erlang:error(badarg, [Command, Options],
+ [{error_info, #{cause => #{2 => <<"progname is not valid">>}}}]),
+ Prefixes = maps:from_list([{P, true} || P <- maps:get(prefixes, Options, [$-])]),
+ _ = validate_command([{Prog, Command}], Prefixes),
+ Prog.
+
+%% @equiv parse(Args, Command, #{})
+-spec parse(Args :: [string()], command()) -> parse_result().
+parse(Args, Command) ->
+ parse(Args, Command, #{}).
+
+%% @doc Parses supplied arguments according to expected command specification.
+%% @param Args command line arguments (e.g. `init:get_plain_arguments()')
+%% @returns argument map, or argument map with deepest matched command
+%% definition.
+-spec parse(Args :: [string()], command(), Options :: parser_options()) -> parse_result().
+parse(Args, Command, Options) ->
+ Prog = validate(Command, Options),
+ %% use maps and not sets v2, because sets:is_element/2 cannot be used in guards (unlike is_map_key)
+ Prefixes = maps:from_list([{P, true} || P <- maps:get(prefixes, Options, [$-])]),
+ try
+ parse_impl(Args, merge_arguments(Prog, Command, init_parser(Prefixes, Command, Options)))
+ catch
+ %% Parser error may happen at any depth, and bubbling the error is really
+ %% cumbersome. Use exceptions and catch it before returning from `parse/2,3' instead.
+ throw:Reason ->
+ {error, Reason}
+ end.
+
+%% @equiv help(Command, #{})
+-spec help(command()) -> string().
+help(Command) ->
+ help(Command, #{}).
+
+%% @doc Returns help for Command formatted according to Options specified
+-spec help(command(), parser_options()) -> unicode:chardata().
+help(Command, Options) ->
+ Prog = validate(Command, Options),
+ format_help({Prog, Command}, Options).
+
+%% @doc
+-spec run(Args :: [string()], command(), parser_options()) -> term().
+run(Args, Command, Options) ->
+ try parse(Args, Command, Options) of
+ {ok, ArgMap, Path, SubCmd} ->
+ handle(Command, ArgMap, tl(Path), SubCmd);
+ {error, Reason} ->
+ io:format("error: ~ts~n", [argparse:format_error(Reason)]),
+ io:format("~ts", [argparse:help(Command, Options#{command => tl(element(1, Reason))})]),
+ erlang:halt(1)
+ catch
+ error:Reason:Stack ->
+ io:format(erl_error:format_exception(error, Reason, Stack)),
+ erlang:halt(1)
+ end.
+
+%% @doc Basic formatter for the parser error reason.
+-spec format_error(Reason :: parser_error()) -> unicode:chardata().
+format_error({Path, undefined, undefined, Details}) ->
+ io_lib:format("~ts: ~ts", [format_path(Path), Details]);
+format_error({Path, undefined, Actual, Details}) ->
+ io_lib:format("~ts: unknown argument: ~ts~ts", [format_path(Path), Actual, Details]);
+format_error({Path, #{name := Name}, undefined, Details}) ->
+ io_lib:format("~ts: required argument missing: ~ts~ts", [format_path(Path), Name, Details]);
+format_error({Path, #{name := Name}, Value, Details}) ->
+ io_lib:format("~ts: invalid argument for ~ts: ~ts ~ts", [format_path(Path), Name, Value, Details]).
+
+-type validator_error() ::
+ {?MODULE, command | argument, cmd_path(), Field :: atom(), Detail :: unicode:chardata()}.
+
+%% @doc Transforms exception thrown by `validate/1,2' according to EEP54.
+%% Use `erl_error:format_exception/3,4' to get the shell-like output.
+-spec format_error(Reason :: validator_error(), erlang:stacktrace()) -> map().
+format_error({?MODULE, command, Path, Field, Reason}, [{_M, _F, [Cmd], Info} | _]) ->
+ #{cause := Cause} = proplists:get_value(error_info, Info, #{}),
+ Cause#{general => <<"command specification is invalid">>, 1 => io_lib:format("~tp", [Cmd]),
+ reason => io_lib:format("command \"~ts\": invalid field '~ts', reason: ~ts", [format_path(Path), Field, Reason])};
+format_error({?MODULE, argument, Path, Field, Reason}, [{_M, _F, [Arg], Info} | _]) ->
+ #{cause := Cause} = proplists:get_value(error_info, Info, #{}),
+ ArgName = maps:get(name, Arg, ""),
+ Cause#{general => "argument specification is invalid", 1 => io_lib:format("~tp", [Arg]),
+ reason => io_lib:format("command \"~ts\", argument '~ts', invalid field '~ts': ~ts",
+ [format_path(Path), ArgName, Field, Reason])}.
+
+%%--------------------------------------------------------------------
+%% Parser implementation
+
+%% Parser state (not available via API)
+-record(eos, {
+ %% prefix character map, by default, only -
+ prefixes :: #{char() => true},
+ %% argument map to be returned
+ argmap = #{} :: arg_map(),
+ %% sub-commands, in reversed orders, allowing to recover the path taken
+ commands = [] :: cmd_path(),
+ %% command being matched
+ current :: command(),
+ %% unmatched positional arguments, in the expected match order
+ pos = [] :: [argument()],
+ %% expected optional arguments, mapping between short/long form and an argument
+ short = #{} :: #{integer() => argument()},
+ long = #{} :: #{string() => argument()},
+ %% flag, whether there are no options that can be confused with negative numbers
+ no_digits = true :: boolean(),
+ %% global default for not required arguments
+ default :: error | {ok, term()}
+}).
+
+init_parser(Prefixes, Cmd, Options) ->
+ #eos{prefixes = Prefixes, current = Cmd, default = maps:find(default, Options)}.
+
+%% Optional or positional argument?
+-define(IS_OPTION(Arg), is_map_key(short, Arg) orelse is_map_key(long, Arg)).
+
+%% helper function to match either a long form of "--arg=value", or just "--arg"
+match_long(Arg, LongOpts) ->
+ case maps:find(Arg, LongOpts) of
+ {ok, Option} ->
+ {ok, Option};
+ error ->
+ %% see if there is '=' equals sign in the Arg
+ case string:split(Arg, "=") of
+ [MaybeLong, Value] ->
+ case maps:find(MaybeLong, LongOpts) of
+ {ok, Option} ->
+ {ok, Option, Value};
+ error ->
+ nomatch
+ end;
+ _ ->
+ nomatch
+ end
+ end.
+
+%% parse_impl implements entire internal parse logic.
+
+%% Clause: option starting with any prefix
+%% No separate clause for single-character short form, because there could be a single-character
+%% long form taking precedence.
+parse_impl([[Prefix | Name] | Tail], #eos{prefixes = Pref} = Eos) when is_map_key(Prefix, Pref) ->
+ %% match "long" option from the list of currently known
+ case match_long(Name, Eos#eos.long) of
+ {ok, Option} ->
+ consume(Tail, Option, Eos);
+ {ok, Option, Value} ->
+ consume([Value | Tail], Option, Eos);
+ nomatch ->
+ %% try to match single-character flag
+ case Name of
+ [Flag] when is_map_key(Flag, Eos#eos.short) ->
+ %% found a flag
+ consume(Tail, maps:get(Flag, Eos#eos.short), Eos);
+ [Flag | Rest] when is_map_key(Flag, Eos#eos.short) ->
+ %% can be a combination of flags, or flag with value,
+ %% but can never be a negative integer, because otherwise
+ %% it will be reflected in no_digits
+ case abbreviated(Name, [], Eos#eos.short) of
+ false ->
+ %% short option with Rest being an argument
+ consume([Rest | Tail], maps:get(Flag, Eos#eos.short), Eos);
+ Expanded ->
+ %% expand multiple flags into actual list, adding prefix
+ parse_impl([[Prefix,E] || E <- Expanded] ++ Tail, Eos)
+ end;
+ MaybeNegative when Prefix =:= $-, Eos#eos.no_digits ->
+ case is_digits(MaybeNegative) of
+ true ->
+ %% found a negative number
+ parse_positional([Prefix|Name], Tail, Eos);
+ false ->
+ catch_all_positional([[Prefix|Name] | Tail], Eos)
+ end;
+ _Unknown ->
+ catch_all_positional([[Prefix|Name] | Tail], Eos)
+ end
+ end;
+
+%% Arguments not starting with Prefix: attempt to match sub-command, if available
+parse_impl([Positional | Tail], #eos{current = #{commands := SubCommands}} = Eos) ->
+ case maps:find(Positional, SubCommands) of
+ error ->
+ %% sub-command not found, try positional argument
+ parse_positional(Positional, Tail, Eos);
+ {ok, SubCmd} ->
+ %% found matching sub-command with arguments, descend into it
+ parse_impl(Tail, merge_arguments(Positional, SubCmd, Eos))
+ end;
+
+%% Clause for arguments that don't have sub-commands (therefore check for
+%% positional argument).
+parse_impl([Positional | Tail], Eos) ->
+ parse_positional(Positional, Tail, Eos);
+
+%% Entire command line has been matched, go over missing arguments,
+%% add defaults etc
+parse_impl([], #eos{argmap = ArgMap0, commands = Commands, current = Current, pos = Pos, default = Def} = Eos) ->
+ %% error if stopped at sub-command with no handler
+ map_size(maps:get(commands, Current, #{})) >0 andalso
+ (not is_map_key(handler, Current)) andalso
+ throw({Commands, undefined, undefined, <<"subcommand expected">>}),
+
+ %% go over remaining positional, verify they are all not required
+ ArgMap1 = fold_args_map(Commands, true, ArgMap0, Pos, Def),
+ %% go over optionals, and either raise an error, or set default
+ ArgMap2 = fold_args_map(Commands, false, ArgMap1, maps:values(Eos#eos.short), Def),
+ ArgMap3 = fold_args_map(Commands, false, ArgMap2, maps:values(Eos#eos.long), Def),
+
+ %% return argument map, command path taken, and the deepest
+ %% last command matched (usually it contains a handler to run)
+ {ok, ArgMap3, Eos#eos.commands, Eos#eos.current}.
+
+%% Generate error for missing required argument, and supply defaults for
+%% missing optional arguments that have defaults.
+fold_args_map(Commands, Req, ArgMap, Args, GlobalDefault) ->
+ lists:foldl(
+ fun (#{name := Name}, Acc) when is_map_key(Name, Acc) ->
+ %% argument present
+ Acc;
+ (#{required := true} = Opt, _Acc) ->
+ %% missing, and required explicitly
+ throw({Commands, Opt, undefined, <<>>});
+ (#{name := Name, required := false, default := Default}, Acc) ->
+ %% explicitly not required argument with default
+ Acc#{Name => Default};
+ (#{name := Name, required := false}, Acc) ->
+ %% explicitly not required with no local default, try global one
+ try_global_default(Name, Acc, GlobalDefault);
+ (#{name := Name, default := Default}, Acc) when Req =:= true ->
+ %% positional argument with default
+ Acc#{Name => Default};
+ (Opt, _Acc) when Req =:= true ->
+ %% missing, for positional argument, implicitly required
+ throw({Commands, Opt, undefined, <<>>});
+ (#{name := Name, default := Default}, Acc) ->
+ %% missing, optional, and there is a default
+ Acc#{Name => Default};
+ (#{name := Name}, Acc) ->
+ %% missing, optional, no local default, try global default
+ try_global_default(Name, Acc, GlobalDefault)
+ end, ArgMap, Args).
+
+try_global_default(_Name, Acc, error) ->
+ Acc;
+try_global_default(Name, Acc, {ok, Term}) ->
+ Acc#{Name => Term}.
+
+%%--------------------------------------------------------------------
+%% argument consumption (nargs) handling
+
+catch_all_positional(Tail, #eos{pos = [#{nargs := all} = Opt]} = Eos) ->
+ action([], Tail, Opt#{type => {list, maps:get(type, Opt, string)}}, Eos);
+%% it is possible that some positional arguments are not required,
+%% and therefore it is possible to catch all skipping those
+catch_all_positional(Tail, #eos{argmap = Args, pos = [#{name := Name, default := Default, required := false} | Pos]} = Eos) ->
+ catch_all_positional(Tail, Eos#eos{argmap = Args#{Name => Default}, pos = Pos});
+%% same as above, but no default specified
+catch_all_positional(Tail, #eos{pos = [#{required := false} | Pos]} = Eos) ->
+ catch_all_positional(Tail, Eos#eos{pos = Pos});
+catch_all_positional([Arg | _Tail], #eos{commands = Commands}) ->
+ throw({Commands, undefined, Arg, <<>>}).
+
+parse_positional(Arg, _Tail, #eos{pos = [], commands = Commands}) ->
+ throw({Commands, undefined, Arg, <<>>});
+parse_positional(Arg, Tail, #eos{pos = Pos} = Eos) ->
+ %% positional argument itself is a value
+ consume([Arg | Tail], hd(Pos), Eos).
+
+%% Adds CmdName to path, and includes any arguments found there
+merge_arguments(CmdName, #{arguments := Args} = SubCmd, Eos) ->
+ add_args(Args, Eos#eos{current = SubCmd, commands = Eos#eos.commands ++ [CmdName]});
+merge_arguments(CmdName, SubCmd, Eos) ->
+ Eos#eos{current = SubCmd, commands = Eos#eos.commands ++ [CmdName]}.
+
+%% adds arguments into current set of discovered pos/opts
+add_args([], Eos) ->
+ Eos;
+add_args([#{short := S, long := L} = Option | Tail], #eos{short = Short, long = Long} = Eos) ->
+ %% remember if this option can be confused with negative number
+ NoDigits = no_digits(Eos#eos.no_digits, Eos#eos.prefixes, S, L),
+ add_args(Tail, Eos#eos{short = Short#{S => Option}, long = Long#{L => Option}, no_digits = NoDigits});
+add_args([#{short := S} = Option | Tail], #eos{short = Short} = Eos) ->
+ %% remember if this option can be confused with negative number
+ NoDigits = no_digits(Eos#eos.no_digits, Eos#eos.prefixes, S, 0),
+ add_args(Tail, Eos#eos{short = Short#{S => Option}, no_digits = NoDigits});
+add_args([#{long := L} = Option | Tail], #eos{long = Long} = Eos) ->
+ %% remember if this option can be confused with negative number
+ NoDigits = no_digits(Eos#eos.no_digits, Eos#eos.prefixes, 0, L),
+ add_args(Tail, Eos#eos{long = Long#{L => Option}, no_digits = NoDigits});
+add_args([PosOpt | Tail], #eos{pos = Pos} = Eos) ->
+ add_args(Tail, Eos#eos{pos = Pos ++ [PosOpt]}).
+
+%% If no_digits is still true, try to find out whether it should turn false,
+%% because added options look like negative numbers, and prefixes include -
+no_digits(false, _, _, _) ->
+ false;
+no_digits(true, Prefixes, _, _) when not is_map_key($-, Prefixes) ->
+ true;
+no_digits(true, _, Short, _) when Short >= $0, Short =< $9 ->
+ false;
+no_digits(true, _, _, Long) ->
+ not is_digits(Long).
+
+%%--------------------------------------------------------------------
+%% additional functions for optional arguments processing
+
+%% Returns true when option (!) description passed requires a positional argument,
+%% hence cannot be treated as a flag.
+requires_argument(#{nargs := {'maybe', _Term}}) ->
+ false;
+requires_argument(#{nargs := 'maybe'}) ->
+ false;
+requires_argument(#{nargs := _Any}) ->
+ true;
+requires_argument(Opt) ->
+ case maps:get(action, Opt, store) of
+ store ->
+ maps:get(type, Opt, string) =/= boolean;
+ append ->
+ maps:get(type, Opt, string) =/= boolean;
+ _ ->
+ false
+ end.
+
+%% Attempts to find if passed list of flags can be expanded
+abbreviated([Last], Acc, AllShort) when is_map_key(Last, AllShort) ->
+ lists:reverse([Last | Acc]);
+abbreviated([_], _Acc, _Eos) ->
+ false;
+abbreviated([Flag | Tail], Acc, AllShort) ->
+ case maps:find(Flag, AllShort) of
+ error ->
+ false;
+ {ok, Opt} ->
+ case requires_argument(Opt) of
+ true ->
+ false;
+ false ->
+ abbreviated(Tail, [Flag | Acc], AllShort)
+ end
+ end.
+
+%%--------------------------------------------------------------------
+%% argument consumption (nargs) handling
+
+%% consume predefined amount (none of which can be an option?)
+consume(Tail, #{nargs := Count} = Opt, Eos) when is_integer(Count) ->
+ {Consumed, Remain} = split_to_option(Tail, Count, Eos, []),
+ length(Consumed) < Count andalso
+ throw({Eos#eos.commands, Opt, Tail,
+ io_lib:format("expected ~b, found ~b argument(s)", [Count, length(Consumed)])}),
+ action(Remain, Consumed, Opt#{type => {list, maps:get(type, Opt, string)}}, Eos);
+
+%% handle 'reminder' by just dumping everything in
+consume(Tail, #{nargs := all} = Opt, Eos) ->
+ action([], Tail, Opt#{type => {list, maps:get(type, Opt, string)}}, Eos);
+
+%% require at least one argument
+consume(Tail, #{nargs := nonempty_list} = Opt, Eos) ->
+ {Consumed, Remains} = split_to_option(Tail, -1, Eos, []),
+ Consumed =:= [] andalso throw({Eos#eos.commands, Opt, Tail, <<"expected argument">>}),
+ action(Remains, Consumed, Opt#{type => {list, maps:get(type, Opt, string)}}, Eos);
+
+%% consume all until next option
+consume(Tail, #{nargs := list} = Opt, Eos) ->
+ {Consumed, Remains} = split_to_option(Tail, -1, Eos, []),
+ action(Remains, Consumed, Opt#{type => {list, maps:get(type, Opt, string)}}, Eos);
+
+%% maybe consume one, maybe not...
+%% special cases for 'boolean maybe', only consume 'true' and 'false'
+consume(["true" | Tail], #{type := boolean} = Opt, Eos) ->
+ action(Tail, true, Opt#{type => raw}, Eos);
+consume(["false" | Tail], #{type := boolean} = Opt, Eos) ->
+ action(Tail, false, Opt#{type => raw}, Eos);
+consume(Tail, #{type := boolean} = Opt, Eos) ->
+ %% neither true nor false means 'undefined' (with the default for boolean being true)
+ action(Tail, undefined, Opt, Eos);
+
+%% maybe behaviour, as '?'
+consume(Tail, #{nargs := 'maybe'} = Opt, Eos) ->
+ case split_to_option(Tail, 1, Eos, []) of
+ {[], _} ->
+ %% no argument given, produce default argument (if not present,
+ %% then produce default value of the specified type)
+ action(Tail, default(Opt), Opt#{type => raw}, Eos);
+ {[Consumed], Remains} ->
+ action(Remains, Consumed, Opt, Eos)
+ end;
+
+%% maybe consume one, maybe not...
+consume(Tail, #{nargs := {'maybe', Const}} = Opt, Eos) ->
+ case split_to_option(Tail, 1, Eos, []) of
+ {[], _} ->
+ action(Tail, Const, Opt, Eos);
+ {[Consumed], Remains} ->
+ action(Remains, Consumed, Opt, Eos)
+ end;
+
+%% default case, which depends on action
+consume(Tail, #{action := count} = Opt, Eos) ->
+ action(Tail, undefined, Opt, Eos);
+
+%% for {store, ...} and {append, ...} don't take argument out
+consume(Tail, #{action := {Act, _Const}} = Opt, Eos) when Act =:= store; Act =:= append ->
+ action(Tail, undefined, Opt, Eos);
+
+%% optional: ensure not to consume another option start
+consume([[Prefix | _] = ArgValue | Tail], Opt, Eos) when ?IS_OPTION(Opt), is_map_key(Prefix, Eos#eos.prefixes) ->
+ case Eos#eos.no_digits andalso is_digits(ArgValue) of
+ true ->
+ action(Tail, ArgValue, Opt, Eos);
+ false ->
+ throw({Eos#eos.commands, Opt, undefined, <<"expected argument">>})
+ end;
+
+consume([ArgValue | Tail], Opt, Eos) ->
+ action(Tail, ArgValue, Opt, Eos);
+
+%% we can only be here if it's optional argument, but there is no value supplied,
+%% and type is not 'boolean' - this is an error!
+consume([], Opt, Eos) ->
+ throw({Eos#eos.commands, Opt, undefined, <<"expected argument">>}).
+
+%% no more arguments for consumption, but last optional may still be action-ed
+%%consume([], Current, Opt, Eos) ->
+%% action([], Current, undefined, Opt, Eos).
+
+%% smart split: ignore arguments that can be parsed as negative numbers,
+%% unless there are arguments that look like negative numbers
+split_to_option([], _, _Eos, Acc) ->
+ {lists:reverse(Acc), []};
+split_to_option(Tail, 0, _Eos, Acc) ->
+ {lists:reverse(Acc), Tail};
+split_to_option([[Prefix | _] = MaybeNumber | Tail] = All, Left,
+ #eos{no_digits = true, prefixes = Prefixes} = Eos, Acc) when is_map_key(Prefix, Prefixes) ->
+ case is_digits(MaybeNumber) of
+ true ->
+ split_to_option(Tail, Left - 1, Eos, [MaybeNumber | Acc]);
+ false ->
+ {lists:reverse(Acc), All}
+ end;
+split_to_option([[Prefix | _] | _] = All, _Left,
+ #eos{no_digits = false, prefixes = Prefixes}, Acc) when is_map_key(Prefix, Prefixes) ->
+ {lists:reverse(Acc), All};
+split_to_option([Head | Tail], Left, Opts, Acc) ->
+ split_to_option(Tail, Left - 1, Opts, [Head | Acc]).
+
+%%--------------------------------------------------------------------
+%% Action handling
+
+action(Tail, ArgValue, #{name := ArgName, action := store} = Opt, #eos{argmap = ArgMap} = Eos) ->
+ Value = convert_type(maps:get(type, Opt, string), ArgValue, Opt, Eos),
+ continue_parser(Tail, Opt, Eos#eos{argmap = ArgMap#{ArgName => Value}});
+
+action(Tail, undefined, #{name := ArgName, action := {store, Value}} = Opt, #eos{argmap = ArgMap} = Eos) ->
+ continue_parser(Tail, Opt, Eos#eos{argmap = ArgMap#{ArgName => Value}});
+
+action(Tail, ArgValue, #{name := ArgName, action := append} = Opt, #eos{argmap = ArgMap} = Eos) ->
+ Value = convert_type(maps:get(type, Opt, string), ArgValue, Opt, Eos),
+ continue_parser(Tail, Opt, Eos#eos{argmap = ArgMap#{ArgName => maps:get(ArgName, ArgMap, []) ++ [Value]}});
+
+action(Tail, undefined, #{name := ArgName, action := {append, Value}} = Opt, #eos{argmap = ArgMap} = Eos) ->
+ continue_parser(Tail, Opt, Eos#eos{argmap = ArgMap#{ArgName => maps:get(ArgName, ArgMap, []) ++ [Value]}});
+
+action(Tail, ArgValue, #{name := ArgName, action := extend} = Opt, #eos{argmap = ArgMap} = Eos) ->
+ Value = convert_type(maps:get(type, Opt, string), ArgValue, Opt, Eos),
+ Extended = maps:get(ArgName, ArgMap, []) ++ Value,
+ continue_parser(Tail, Opt, Eos#eos{argmap = ArgMap#{ArgName => Extended}});
+
+action(Tail, _, #{name := ArgName, action := count} = Opt, #eos{argmap = ArgMap} = Eos) ->
+ continue_parser(Tail, Opt, Eos#eos{argmap = ArgMap#{ArgName => maps:get(ArgName, ArgMap, 0) + 1}});
+
+%% default action is `store' (important to sync the code with the first clause above)
+action(Tail, ArgValue, #{name := ArgName} = Opt, #eos{argmap = ArgMap} = Eos) ->
+ Value = convert_type(maps:get(type, Opt, string), ArgValue, Opt, Eos),
+ continue_parser(Tail, Opt, Eos#eos{argmap = ArgMap#{ArgName => Value}}).
+
+%% pop last positional, unless nargs is list/nonempty_list
+continue_parser(Tail, Opt, Eos) when ?IS_OPTION(Opt) ->
+ parse_impl(Tail, Eos);
+continue_parser(Tail, #{nargs := List}, Eos) when List =:= list; List =:= nonempty_list ->
+ parse_impl(Tail, Eos);
+continue_parser(Tail, _Opt, Eos) ->
+ parse_impl(Tail, Eos#eos{pos = tl(Eos#eos.pos)}).
+
+%%--------------------------------------------------------------------
+%% Type conversion
+
+%% Handle "list" variant for nargs returning list
+convert_type({list, Type}, Arg, Opt, Eos) ->
+ [convert_type(Type, Var, Opt, Eos) || Var <- Arg];
+
+%% raw - no conversion applied (most likely default)
+convert_type(raw, Arg, _Opt, _Eos) ->
+ Arg;
+
+%% Handle actual types
+convert_type(string, Arg, _Opt, _Eos) ->
+ Arg;
+convert_type({string, Choices}, Arg, Opt, Eos) when is_list(Choices), is_list(hd(Choices)) ->
+ lists:member(Arg, Choices) orelse
+ throw({Eos#eos.commands, Opt, Arg, <<"is not one of the choices">>}),
+ Arg;
+convert_type({string, Re}, Arg, Opt, Eos) ->
+ case re:run(Arg, Re) of
+ {match, _X} -> Arg;
+ _ -> throw({Eos#eos.commands, Opt, Arg, <<"does not match">>})
+ end;
+convert_type({string, Re, ReOpt}, Arg, Opt, Eos) ->
+ case re:run(Arg, Re, ReOpt) of
+ match -> Arg;
+ {match, _} -> Arg;
+ _ -> throw({Eos#eos.commands, Opt, Arg, <<"does not match">>})
+ end;
+convert_type(integer, Arg, Opt, Eos) ->
+ get_int(Arg, Opt, Eos);
+convert_type({integer, Opts}, Arg, Opt, Eos) ->
+ minimax(get_int(Arg, Opt, Eos), Opts, Eos, Opt, Arg);
+convert_type(boolean, "true", _Opt, _Eos) ->
+ true;
+convert_type(boolean, undefined, _Opt, _Eos) ->
+ true;
+convert_type(boolean, "false", _Opt, _Eos) ->
+ false;
+convert_type(boolean, Arg, Opt, Eos) ->
+ throw({Eos#eos.commands, Opt, Arg, <<"is not a boolean">>});
+convert_type(binary, Arg, _Opt, _Eos) ->
+ unicode:characters_to_binary(Arg);
+convert_type({binary, Choices}, Arg, Opt, Eos) when is_list(Choices), is_binary(hd(Choices)) ->
+ Conv = unicode:characters_to_binary(Arg),
+ lists:member(Conv, Choices) orelse
+ throw({Eos#eos.commands, Opt, Arg, <<"is not one of the choices">>}),
+ Conv;
+convert_type({binary, Re}, Arg, Opt, Eos) ->
+ case re:run(Arg, Re) of
+ {match, _X} -> unicode:characters_to_binary(Arg);
+ _ -> throw({Eos#eos.commands, Opt, Arg, <<"does not match">>})
+ end;
+convert_type({binary, Re, ReOpt}, Arg, Opt, Eos) ->
+ case re:run(Arg, Re, ReOpt) of
+ match -> unicode:characters_to_binary(Arg);
+ {match, _} -> unicode:characters_to_binary(Arg);
+ _ -> throw({Eos#eos.commands, Opt, Arg, <<"does not match">>})
+ end;
+convert_type(float, Arg, Opt, Eos) ->
+ get_float(Arg, Opt, Eos);
+convert_type({float, Opts}, Arg, Opt, Eos) ->
+ minimax(get_float(Arg, Opt, Eos), Opts, Eos, Opt, Arg);
+convert_type(atom, Arg, Opt, Eos) ->
+ try list_to_existing_atom(Arg)
+ catch error:badarg ->
+ throw({Eos#eos.commands, Opt, Arg, <<"is not an existing atom">>})
+ end;
+convert_type({atom, unsafe}, Arg, _Opt, _Eos) ->
+ list_to_atom(Arg);
+convert_type({atom, Choices}, Arg, Opt, Eos) ->
+ try
+ Atom = list_to_existing_atom(Arg),
+ lists:member(Atom, Choices) orelse throw({Eos#eos.commands, Opt, Arg, <<"is not one of the choices">>}),
+ Atom
+ catch error:badarg ->
+ throw({Eos#eos.commands, Opt, Arg, <<"is not an existing atom">>})
+ end;
+convert_type({custom, Fun}, Arg, Opt, Eos) ->
+ try Fun(Arg)
+ catch error:badarg ->
+ throw({Eos#eos.commands, Opt, Arg, <<"failed faildation">>})
+ end.
+
+%% Given Var, and list of {min, X}, {max, Y}, ensure that
+%% value falls within defined limits.
+minimax(Var, [], _Eos, _Opt, _Orig) ->
+ Var;
+minimax(Var, [{min, Min} | _], Eos, Opt, Orig) when Var < Min ->
+ throw({Eos#eos.commands, Opt, Orig, <<"is less than accepted minimum">>});
+minimax(Var, [{max, Max} | _], Eos, Opt, Orig) when Var > Max ->
+ throw({Eos#eos.commands, Opt, Orig, <<"is greater than accepted maximum">>});
+minimax(Var, [Num | Tail], Eos, Opt, Orig) when is_number(Num) ->
+ lists:member(Var, [Num|Tail]) orelse
+ throw({Eos#eos.commands, Opt, Orig, <<"is not one of the choices">>}),
+ Var;
+minimax(Var, [_ | Tail], Eos, Opt, Orig) ->
+ minimax(Var, Tail, Eos, Opt, Orig).
+
+%% returns integer from string, or errors out with debugging info
+get_int(Arg, Opt, Eos) ->
+ case string:to_integer(Arg) of
+ {Int, []} ->
+ Int;
+ _ ->
+ throw({Eos#eos.commands, Opt, Arg, <<"is not an integer">>})
+ end.
+
+%% returns float from string, that is floating-point, or integer
+get_float(Arg, Opt, Eos) ->
+ case string:to_float(Arg) of
+ {Float, []} ->
+ Float;
+ _ ->
+ %% possibly in disguise
+ case string:to_integer(Arg) of
+ {Int, []} ->
+ Int;
+ _ ->
+ throw({Eos#eos.commands, Opt, Arg, <<"is not a number">>})
+ end
+ end.
+
+%% Returns 'true' if String can be converted to a number
+is_digits(String) ->
+ case string:to_integer(String) of
+ {_Int, []} ->
+ true;
+ {_, _} ->
+ case string:to_float(String) of
+ {_Float, []} ->
+ true;
+ {_, _} ->
+ false
+ end
+ end.
+
+%% 'maybe' nargs for an option that does not have default set still have
+%% to produce something, let's call it hardcoded default.
+default(#{default := Default}) ->
+ Default;
+default(#{type := boolean}) ->
+ true;
+default(#{type := integer}) ->
+ 0;
+default(#{type := float}) ->
+ 0.0;
+default(#{type := string}) ->
+ "";
+default(#{type := binary}) ->
+ <<"">>;
+default(#{type := atom}) ->
+ undefined;
+%% no type given, consider it 'undefined' atom
+default(_) ->
+ undefined.
+
+%% command path is now in direct order
+format_path(Commands) ->
+ lists:join(" ", Commands).
+
+%%--------------------------------------------------------------------
+%% Validation and preprocessing
+%% Theoretically, Dialyzer should do that too.
+%% Practically, so many people ignore Dialyzer and then spend hours
+%% trying to understand why things don't work, that is makes sense
+%% to provide a mini-Dialyzer here.
+
+%% to simplify throwing errors with the right reason
+-define (INVALID(Kind, Entity, Path, Field, Text),
+ erlang:error({?MODULE, Kind, clean_path(Path), Field, Text}, [Entity], [{error_info, #{cause => #{}}}])).
+
+executable(#{progname := Prog}) when is_atom(Prog) ->
+ atom_to_list(Prog);
+executable(#{progname := Prog}) when is_binary(Prog) ->
+ binary_to_list(Prog);
+executable(#{progname := Prog}) ->
+ Prog;
+executable(_) ->
+ {ok, [[Prog]]} = init:get_argument(progname),
+ Prog.
+
+%% Recursive command validator
+validate_command([{Name, Cmd} | _] = Path, Prefixes) ->
+ (is_list(Name) andalso (not is_map_key(hd(Name), Prefixes))) orelse
+ ?INVALID(command, Cmd, tl(Path), commands,
+ <<"command name must be a string not starting with option prefix">>),
+ is_map(Cmd) orelse
+ ?INVALID(command, Cmd, Path, commands, <<"expected command()">>),
+ is_valid_command_help(maps:get(help, Cmd, [])) orelse
+ ?INVALID(command, Cmd, Path, help, <<"must be a printable unicode list, or a command help template">>),
+ is_map(maps:get(commands, Cmd, #{})) orelse
+ ?INVALID(command, Cmd, Path, commands, <<"expected map of #{string() => command()}">>),
+ case maps:get(handler, Cmd, optional) of
+ optional -> ok;
+ {Mod, ModFun} when is_atom(Mod), is_atom(ModFun) -> ok; %% map form
+ {Mod, ModFun, _} when is_atom(Mod), is_atom(ModFun) -> ok; %% positional form
+ {Fun, _} when is_function(Fun) -> ok; %% positional form
+ Fun when is_function(Fun, 1) -> ok;
+ _ -> ?INVALID(command, Cmd, Path, handler, <<"handler must be a valid callback, or an atom 'optional'">>)
+ end,
+ Cmd1 =
+ case maps:find(arguments, Cmd) of
+ error ->
+ Cmd;
+ {ok, Opts} when not is_list(Opts) ->
+ ?INVALID(command, Cmd, Path, arguments, <<"expected a list, [argument()]">>);
+ {ok, Opts} ->
+ Cmd#{arguments => [validate_option(Path, Opt) || Opt <- Opts]}
+ end,
+ %% collect all short & long option identifiers - to figure out any conflicts
+ lists:foldl(
+ fun ({_, #{arguments := Opts}}, Acc) ->
+ lists:foldl(
+ fun (#{short := Short, name := OName} = Arg, {AllS, AllL}) ->
+ is_map_key(Short, AllS) andalso
+ ?INVALID(argument, Arg, Path, short,
+ "short conflicting with previously defined short for "
+ ++ atom_to_list(maps:get(Short, AllS))),
+ {AllS#{Short => OName}, AllL};
+ (#{long := Long, name := OName} = Arg, {AllS, AllL}) ->
+ is_map_key(Long, AllL) andalso
+ ?INVALID(argument, Arg, Path, long,
+ "long conflicting with previously defined long for "
+ ++ atom_to_list(maps:get(Long, AllL))),
+ {AllS, AllL#{Long => OName}};
+ (_, AccIn) ->
+ AccIn
+ end, Acc, Opts);
+ (_, Acc) ->
+ Acc
+ end, {#{}, #{}}, Path),
+ %% verify all sub-commands
+ case maps:find(commands, Cmd1) of
+ error ->
+ {Name, Cmd1};
+ {ok, Sub} ->
+ {Name, Cmd1#{commands => maps:map(
+ fun (K, V) ->
+ {K, Updated} = validate_command([{K, V} | Path], Prefixes),
+ Updated
+ end, Sub)}}
+ end.
+
+%% validates option spec
+validate_option(Path, #{name := Name} = Arg) when is_atom(Name); is_list(Name); is_binary(Name) ->
+ %% verify specific arguments
+ %% help: string, 'hidden', or a tuple of {string(), ...}
+ is_valid_option_help(maps:get(help, Arg, [])) orelse
+ ?INVALID(argument, Arg, Path, help, <<"must be a string or valid help template">>),
+ io_lib:printable_unicode_list(maps:get(long, Arg, [])) orelse
+ ?INVALID(argument, Arg, Path, long, <<"must be a printable string">>),
+ is_boolean(maps:get(required, Arg, true)) orelse
+ ?INVALID(argument, Arg, Path, required, <<"must be a boolean">>),
+ io_lib:printable_unicode_list([maps:get(short, Arg, $a)]) orelse
+ ?INVALID(argument, Arg, Path, short, <<"must be a printable character">>),
+ Opt1 = maybe_validate(action, Arg, fun validate_action/3, Path),
+ Opt2 = maybe_validate(type, Opt1, fun validate_type/3, Path),
+ maybe_validate(nargs, Opt2, fun validate_args/3, Path);
+validate_option(Path, Arg) ->
+ ?INVALID(argument, Arg, Path, name, <<"argument must be a map containing 'name' field">>).
+
+maybe_validate(Key, Map, Fun, Path) when is_map_key(Key, Map) ->
+ maps:put(Key, Fun(maps:get(Key, Map), Path, Map), Map);
+maybe_validate(_Key, Map, _Fun, _Path) ->
+ Map.
+
+%% validate action field
+validate_action(store, _Path, _Opt) ->
+ store;
+validate_action({store, Term}, _Path, _Opt) ->
+ {store, Term};
+validate_action(append, _Path, _Opt) ->
+ append;
+validate_action({append, Term}, _Path, _Opt) ->
+ {append, Term};
+validate_action(count, _Path, _Opt) ->
+ count;
+validate_action(extend, _Path, #{nargs := Nargs}) when
+ Nargs =:= list; Nargs =:= nonempty_list; Nargs =:= all; is_integer(Nargs) ->
+ extend;
+validate_action(extend, _Path, #{type := {custom, _}}) ->
+ extend;
+validate_action(extend, Path, Arg) ->
+ ?INVALID(argument, Arg, Path, action, <<"extend action works only with lists">>);
+validate_action(_Action, Path, Arg) ->
+ ?INVALID(argument, Arg, Path, action, <<"unsupported">>).
+
+%% validate type field
+validate_type(Simple, _Path, _Opt) when Simple =:= boolean; Simple =:= integer; Simple =:= float;
+ Simple =:= string; Simple =:= binary; Simple =:= atom; Simple =:= {atom, unsafe} ->
+ Simple;
+validate_type({custom, Fun}, _Path, _Opt) when is_function(Fun, 1) ->
+ {custom, Fun};
+validate_type({float, Opts}, Path, Arg) ->
+ [?INVALID(argument, Arg, Path, type, <<"invalid validator">>)
+ || {Kind, Val} <- Opts, (Kind =/= min andalso Kind =/= max) orelse (not is_float(Val))],
+ {float, Opts};
+validate_type({integer, Opts}, Path, Arg) ->
+ [?INVALID(argument, Arg, Path, type, <<"invalid validator">>)
+ || {Kind, Val} <- Opts, (Kind =/= min andalso Kind =/= max) orelse (not is_integer(Val))],
+ {integer, Opts};
+validate_type({atom, Choices} = Valid, Path, Arg) when is_list(Choices) ->
+ [?INVALID(argument, Arg, Path, type, <<"unsupported">>) || C <- Choices, not is_atom(C)],
+ Valid;
+validate_type({string, Re} = Valid, _Path, _Opt) when is_list(Re) ->
+ Valid;
+validate_type({string, Re, L} = Valid, _Path, _Opt) when is_list(Re), is_list(L) ->
+ Valid;
+validate_type({binary, Re} = Valid, _Path, _Opt) when is_binary(Re) ->
+ Valid;
+validate_type({binary, Choices} = Valid, _Path, _Opt) when is_list(Choices), is_binary(hd(Choices)) ->
+ Valid;
+validate_type({binary, Re, L} = Valid, _Path, _Opt) when is_binary(Re), is_list(L) ->
+ Valid;
+validate_type(_Type, Path, Arg) ->
+ ?INVALID(argument, Arg, Path, type, <<"unsupported">>).
+
+validate_args(N, _Path, _Opt) when is_integer(N), N >= 1 -> N;
+validate_args(Simple, _Path, _Opt) when Simple =:= all; Simple =:= list; Simple =:= 'maybe'; Simple =:= nonempty_list ->
+ Simple;
+validate_args({'maybe', Term}, _Path, _Opt) -> {'maybe', Term};
+validate_args(_Nargs, Path, Arg) ->
+ ?INVALID(argument, Arg, Path, nargs, <<"unsupported">>).
+
+%% used to throw an error - strips command component out of path
+clean_path(Path) ->
+ {Cmds, _} = lists:unzip(Path),
+ lists:reverse(Cmds).
+
+is_valid_option_help(hidden) ->
+ true;
+is_valid_option_help(Help) when is_list(Help); is_binary(Help) ->
+ true;
+is_valid_option_help({Short, Desc}) when is_list(Short) orelse is_binary(Short), is_list(Desc) ->
+ %% verify that Desc is a list of string/type/default
+ lists:all(fun(type) -> true;
+ (default) -> true;
+ (S) when is_list(S); is_binary(S) -> true;
+ (_) -> false
+ end, Desc);
+is_valid_option_help({Short, Desc}) when is_list(Short) orelse is_binary(Short), is_function(Desc, 0) ->
+ true;
+is_valid_option_help(_) ->
+ false.
+
+is_valid_command_help(hidden) ->
+ true;
+is_valid_command_help(Help) when is_binary(Help) ->
+ true;
+is_valid_command_help(Help) when is_list(Help) ->
+ %% allow printable lists
+ case io_lib:printable_unicode_list(Help) of
+ true ->
+ true;
+ false ->
+ %% ... or a command help template
+ lists:all(
+ fun (Atom) when Atom =:= usage; Atom =:= commands; Atom =:= arguments; Atom =:= options -> true;
+ (Bin) when is_binary(Bin) -> true;
+ (Str) -> io_lib:printable_unicode_list(Str)
+ end, Help)
+ end;
+is_valid_command_help(_) ->
+ false.
+
+%%--------------------------------------------------------------------
+%% Built-in Help formatter
+
+format_help({ProgName, Root}, Format) ->
+ Prefix = hd(maps:get(prefixes, Format, [$-])),
+ Nested = maps:get(command, Format, []),
+ %% descent into commands collecting all options on the way
+ {_CmdName, Cmd, AllArgs} = collect_options(ProgName, Root, Nested, []),
+ %% split arguments into Flags, Options, Positional, and create help lines
+ {_, Longest, Flags, Opts, Args, OptL, PosL} = lists:foldl(fun format_opt_help/2,
+ {Prefix, 0, "", [], [], [], []}, AllArgs),
+ %% collect and format sub-commands
+ Immediate = maps:get(commands, Cmd, #{}),
+ {Long, Subs} = maps:fold(
+ fun (_Name, #{help := hidden}, {Long, SubAcc}) ->
+ {Long, SubAcc};
+ (Name, Sub, {Long, SubAcc}) ->
+ Help = maps:get(help, Sub, ""),
+ {max(Long, string:length(Name)), [{Name, Help}|SubAcc]}
+ end, {Longest, []}, maps:iterator(Immediate, ordered)),
+ %% format sub-commands
+ ShortCmd0 =
+ case map_size(Immediate) of
+ 0 ->
+ [];
+ Small when Small < 4 ->
+ Keys = lists:sort(maps:keys(Immediate)),
+ ["{" ++ lists:append(lists:join("|", Keys)) ++ "}"];
+ _Largs ->
+ ["<command>"]
+ end,
+ %% was it nested command?
+ ShortCmd = if Nested =:= [] -> ShortCmd0; true -> [lists:append(lists:join(" ", Nested)) | ShortCmd0] end,
+ %% format flags
+ FlagsForm = if Flags =:= [] -> [];
+ true -> [unicode:characters_to_list(io_lib:format("[~tc~ts]", [Prefix, Flags]))]
+ end,
+ %% format extended view
+ %% usage line has hardcoded format for now
+ Usage = [ProgName, ShortCmd, FlagsForm, Opts, Args],
+ %% format usage according to help template
+ Template0 = maps:get(help, Root, ""),
+ %% when there is no help defined for the command, or help is a string,
+ %% use the default format (original argparse behaviour)
+ Template =
+ case Template0 =:= "" orelse io_lib:printable_unicode_list(Template0) of
+ true ->
+ %% classic/compatibility format
+ NL = [io_lib:nl()],
+ Template1 = ["Usage:" ++ NL, usage, NL],
+ Template2 = maybe_add("~n", Template0, Template0 ++ NL, Template1),
+ Template3 = maybe_add("~nSubcommands:~n", Subs, commands, Template2),
+ Template4 = maybe_add("~nArguments:~n", PosL, arguments, Template3),
+ maybe_add("~nOptional arguments:~n", OptL, options, Template4);
+ false ->
+ Template0
+ end,
+
+ %% produce formatted output, taking viewport width into account
+ Parts = #{usage => Usage, commands => {Long, Subs},
+ arguments => {Longest, PosL}, options => {Longest, OptL}},
+ Width = maps:get(columns, Format, 80), %% might also use io:columns() here
+ lists:append([format_width(maps:find(Part, Parts), Part, Width) || Part <- Template]).
+
+%% collects options on the Path, and returns found Command
+collect_options(CmdName, Command, [], Args) ->
+ {CmdName, Command, maps:get(arguments, Command, []) ++ Args};
+collect_options(CmdName, Command, [Cmd|Tail], Args) ->
+ Sub = maps:get(commands, Command),
+ SubCmd = maps:get(Cmd, Sub),
+ collect_options(CmdName ++ " " ++ Cmd, SubCmd, Tail, maps:get(arguments, Command, []) ++ Args).
+
+%% conditionally adds text and empty lines
+maybe_add(_ToAdd, [], _Element, Template) ->
+ Template;
+maybe_add(ToAdd, _List, Element, Template) ->
+ Template ++ [io_lib:format(ToAdd, []), Element].
+
+format_width(error, Part, Width) ->
+ wrap_text(Part, 0, Width);
+format_width({ok, [ProgName, ShortCmd, FlagsForm, Opts, Args]}, usage, Width) ->
+ %% make every separate command/option to be a "word", and then
+ %% wordwrap it indented by the ProgName length + 3
+ Words = ShortCmd ++ FlagsForm ++ Opts ++ Args,
+ if Words =:= [] -> io_lib:format(" ~ts", [ProgName]);
+ true ->
+ Indent = string:length(ProgName),
+ Wrapped = wordwrap(Words, Width - Indent, 0, [], []),
+ Pad = lists:append(lists:duplicate(Indent + 3, " ")),
+ ArgLines = lists:join([io_lib:nl() | Pad], Wrapped),
+ io_lib:format(" ~ts~ts", [ProgName, ArgLines])
+ end;
+format_width({ok, {Len, Texts}}, _Part, Width) ->
+ SubFormat = io_lib:format(" ~~-~bts ~~ts~n", [Len]),
+ [io_lib:format(SubFormat, [N, wrap_text(D, Len + 3, Width)]) || {N, D} <- lists:reverse(Texts)].
+
+wrap_text(Text, Indent, Width) ->
+ %% split text into separate lines (paragraphs)
+ NL = io_lib:nl(),
+ Lines = string:split(Text, NL, all),
+ %% wordwrap every paragraph
+ Paragraphs = lists:append([wrap_line(L, Width, Indent) || L <- Lines]),
+ Pad = lists:append(lists:duplicate(Indent, " ")),
+ lists:join([NL | Pad], Paragraphs).
+
+wrap_line([], _Width, _Indent) ->
+ [[]];
+wrap_line(Line, Width, Indent) ->
+ [First | Tail] = string:split(Line, " ", all),
+ wordwrap(Tail, Width - Indent, string:length(First), First, []).
+
+wordwrap([], _Max, _Len, [], Lines) ->
+ lists:reverse(Lines);
+wordwrap([], _Max, _Len, Line, Lines) ->
+ lists:reverse([Line | Lines]);
+wordwrap([Word | Tail], Max, Len, Line, Lines) ->
+ WordLen = string:length(Word),
+ case Len + 1 + WordLen > Max of
+ true ->
+ wordwrap(Tail, Max, WordLen, Word, [Line | Lines]);
+ false ->
+ wordwrap(Tail, Max, WordLen + 1 + Len, [Line, <<" ">>, Word], Lines)
+ end.
+
+%% create help line for every option, collecting together all flags, short options,
+%% long options, and positional arguments
+
+%% format optional argument
+format_opt_help(#{help := hidden}, Acc) ->
+ Acc;
+format_opt_help(Opt, {Prefix, Longest, Flags, Opts, Args, OptL, PosL}) when ?IS_OPTION(Opt) ->
+ Desc = format_description(Opt),
+ %% does it need an argument? look for nargs and action
+ RequiresArg = requires_argument(Opt),
+ %% long form always added to Opts
+ NonOption = maps:get(required, Opt, false) =:= true,
+ {Name0, MaybeOpt0} =
+ case maps:find(long, Opt) of
+ error ->
+ {"", []};
+ {ok, Long} when NonOption, RequiresArg ->
+ FN = [Prefix | Long],
+ {FN, [format_required(true, [FN, " "], Opt)]};
+ {ok, Long} when RequiresArg ->
+ FN = [Prefix | Long],
+ {FN, [format_required(false, [FN, " "], Opt)]};
+ {ok, Long} when NonOption ->
+ FN = [Prefix | Long],
+ {FN, [FN]};
+ {ok, Long} ->
+ FN = [Prefix | Long],
+ {FN, [io_lib:format("[~ts]", [FN])]}
+ end,
+ %% short may go to flags, or Opts
+ {Name, MaybeFlag, MaybeOpt1} =
+ case maps:find(short, Opt) of
+ error ->
+ {Name0, [], MaybeOpt0};
+ {ok, Short} when RequiresArg ->
+ SN = [Prefix, Short],
+ {maybe_concat(SN, Name0), [],
+ [format_required(NonOption, [SN, " "], Opt) | MaybeOpt0]};
+ {ok, Short} ->
+ {maybe_concat([Prefix, Short], Name0), [Short], MaybeOpt0}
+ end,
+ %% apply override for non-default usage (in form of {Quick, Advanced} tuple
+ MaybeOpt2 =
+ case maps:find(help, Opt) of
+ {ok, {Str, _}} ->
+ [Str];
+ _ ->
+ MaybeOpt1
+ end,
+ %% name length, capped at 24
+ NameLen = string:length(Name),
+ Capped = min(24, NameLen),
+ {Prefix, max(Capped, Longest), Flags ++ MaybeFlag, Opts ++ MaybeOpt2, Args, [{Name, Desc} | OptL], PosL};
+
+%% format positional argument
+format_opt_help(#{name := Name} = Opt, {Prefix, Longest, Flags, Opts, Args, OptL, PosL}) ->
+ Desc = format_description(Opt),
+ %% positional, hence required
+ LName = io_lib:format("~ts", [Name]),
+ LPos = case maps:find(help, Opt) of
+ {ok, {Str, _}} ->
+ Str;
+ _ ->
+ format_required(maps:get(required, Opt, true), "", Opt)
+ end,
+ {Prefix, max(Longest, string:length(LName)), Flags, Opts, Args ++ [LPos], OptL, [{LName, Desc} | PosL]}.
+
+%% custom format
+format_description(#{help := {_Short, Fun}}) when is_function(Fun, 0) ->
+ Fun();
+format_description(#{help := {_Short, Desc}} = Opt) ->
+ lists:map(
+ fun (type) ->
+ format_type(Opt);
+ (default) ->
+ format_default(Opt);
+ (String) ->
+ String
+ end, Desc
+ );
+%% default format: "desc", "desc (type)", "desc (default)", "desc (type, default)"
+format_description(#{name := Name} = Opt) ->
+ NameStr = maps:get(help, Opt, io_lib:format("~ts", [Name])),
+ case {NameStr, format_type(Opt), format_default(Opt)} of
+ {"", "", Type} -> Type;
+ {"", Default, ""} -> Default;
+ {Desc, "", ""} -> Desc;
+ {Desc, "", Default} -> [Desc, " (", Default, ")"];
+ {Desc, Type, ""} -> [Desc, " (", Type, ")"];
+ {"", Type, Default} -> [Type, ", ", Default];
+ {Desc, Type, Default} -> [Desc, " (", Type, ", ", Default, ")"]
+ end.
+
+%% option formatting helpers
+maybe_concat(No, []) -> No;
+maybe_concat(No, L) -> [No, ", ", L].
+
+format_required(true, Extra, #{name := Name} = Opt) ->
+ io_lib:format("~ts<~ts>~ts", [Extra, Name, format_nargs(Opt)]);
+format_required(false, Extra, #{name := Name} = Opt) ->
+ io_lib:format("[~ts<~ts>~ts]", [Extra, Name, format_nargs(Opt)]).
+
+format_nargs(#{nargs := Dots}) when Dots =:= list; Dots =:= all; Dots =:= nonempty_list ->
+ "...";
+format_nargs(_) ->
+ "".
+
+format_type(#{type := {integer, Choices}}) when is_list(Choices), is_integer(hd(Choices)) ->
+ io_lib:format("choice: ~s", [lists:join(", ", [integer_to_list(C) || C <- Choices])]);
+format_type(#{type := {float, Choices}}) when is_list(Choices), is_number(hd(Choices)) ->
+ io_lib:format("choice: ~s", [lists:join(", ", [io_lib:format("~g", [C]) || C <- Choices])]);
+format_type(#{type := {Num, Valid}}) when Num =:= integer; Num =:= float ->
+ case {proplists:get_value(min, Valid), proplists:get_value(max, Valid)} of
+ {undefined, undefined} ->
+ io_lib:format("~s", [format_type(#{type => Num})]);
+ {Min, undefined} ->
+ io_lib:format("~s >= ~tp", [format_type(#{type => Num}), Min]);
+ {undefined, Max} ->
+ io_lib:format("~s <= ~tp", [format_type(#{type => Num}), Max]);
+ {Min, Max} ->
+ io_lib:format("~tp <= ~s <= ~tp", [Min, format_type(#{type => Num}), Max])
+ end;
+format_type(#{type := {string, Re, _}}) when is_list(Re), not is_list(hd(Re)) ->
+ io_lib:format("string re: ~ts", [Re]);
+format_type(#{type := {string, Re}}) when is_list(Re), not is_list(hd(Re)) ->
+ io_lib:format("string re: ~ts", [Re]);
+format_type(#{type := {binary, Re}}) when is_binary(Re) ->
+ io_lib:format("binary re: ~ts", [Re]);
+format_type(#{type := {binary, Re, _}}) when is_binary(Re) ->
+ io_lib:format("binary re: ~ts", [Re]);
+format_type(#{type := {StrBin, Choices}}) when StrBin =:= string orelse StrBin =:= binary, is_list(Choices) ->
+ io_lib:format("choice: ~ts", [lists:join(", ", Choices)]);
+format_type(#{type := atom}) ->
+ "existing atom";
+format_type(#{type := {atom, unsafe}}) ->
+ "atom";
+format_type(#{type := {atom, Choices}}) ->
+ io_lib:format("choice: ~ts", [lists:join(", ", [atom_to_list(C) || C <- Choices])]);
+format_type(#{type := boolean}) ->
+ "";
+format_type(#{type := integer}) ->
+ "int";
+format_type(#{type := Type}) when is_atom(Type) ->
+ io_lib:format("~ts", [Type]);
+format_type(_Opt) ->
+ "".
+
+format_default(#{default := Def}) when is_list(Def); is_binary(Def); is_atom(Def) ->
+ io_lib:format("~ts", [Def]);
+format_default(#{default := Def}) ->
+ io_lib:format("~tp", [Def]);
+format_default(_) ->
+ "".
+
+%%--------------------------------------------------------------------
+%% Basic handler execution
+handle(CmdMap, ArgMap, Path, #{handler := {Mod, ModFun, Default}}) ->
+ ArgList = arg_map_to_arg_list(CmdMap, Path, ArgMap, Default),
+ %% if argument count may not match, better error can be produced
+ erlang:apply(Mod, ModFun, ArgList);
+handle(_CmdMap, ArgMap, _Path, #{handler := {Mod, ModFun}}) when is_atom(Mod), is_atom(ModFun) ->
+ Mod:ModFun(ArgMap);
+handle(CmdMap, ArgMap, Path, #{handler := {Fun, Default}}) when is_function(Fun) ->
+ ArgList = arg_map_to_arg_list(CmdMap, Path, ArgMap, Default),
+ %% if argument count may not match, better error can be produced
+ erlang:apply(Fun, ArgList);
+handle(_CmdMap, ArgMap, _Path, #{handler := Handler}) when is_function(Handler, 1) ->
+ Handler(ArgMap).
+
+%% Given command map, path to reach a specific command, and a parsed argument
+%% map, returns a list of arguments (effectively used to transform map-based
+%% callback handler into positional).
+arg_map_to_arg_list(Command, Path, ArgMap, Default) ->
+ AllArgs = collect_arguments(Command, Path, []),
+ [maps:get(Arg, ArgMap, Default) || #{name := Arg} <- AllArgs].
+
+%% recursively descend into Path, ignoring arguments with duplicate names
+collect_arguments(Command, [], Acc) ->
+ Acc ++ maps:get(arguments, Command, []);
+collect_arguments(Command, [H|Tail], Acc) ->
+ Args = maps:get(arguments, Command, []),
+ Next = maps:get(H, maps:get(commands, Command, H)),
+ collect_arguments(Next, Tail, Acc ++ Args).
diff --git a/lib/stdlib/src/array.erl b/lib/stdlib/src/array.erl
index 1504326c61..03dedabd55 100644
--- a/lib/stdlib/src/array.erl
+++ b/lib/stdlib/src/array.erl
@@ -462,7 +462,7 @@ fix_test_() ->
-spec relax(Array :: array(Type)) -> array(Type).
-relax(#array{size = N}=A) ->
+relax(#array{size = N}=A) when is_integer(N), N >= 0 ->
A#array{max = find_max(N-1, ?LEAFSIZE)}.
@@ -489,7 +489,9 @@ relax_test_() ->
array(Type).
resize(Size, #array{size = N, max = M, elements = E}=A)
- when is_integer(Size), Size >= 0 ->
+ when is_integer(Size), Size >= 0,
+ is_integer(N), N >= 0,
+ is_integer(M), M >= 0 ->
if Size > N ->
{E1, M1} = grow(Size-1, E,
if M > 0 -> M;
@@ -570,7 +572,7 @@ resize_test_() ->
-spec set(I :: array_indx(), Value :: Type, Array :: array(Type)) -> array(Type).
set(I, Value, #array{size = N, max = M, default = D, elements = E}=A)
- when is_integer(I), I >= 0 ->
+ when is_integer(I), I >= 0, is_integer(N), is_integer(M) ->
if I < N ->
A#array{elements = set_1(I, E, Value, D)};
I < M ->
@@ -599,7 +601,7 @@ set_1(I, E, X, _D) ->
%% Enlarging the array upwards to accommodate an index `I'
-grow(I, E, _M) when is_integer(E) ->
+grow(I, E, _M) when is_integer(I), is_integer(E) ->
M1 = find_max(I, E),
{M1, M1};
grow(I, E, M) ->
@@ -633,7 +635,7 @@ expand(I, _S, X, D) ->
-spec get(I :: array_indx(), Array :: array(Type)) -> Value :: Type.
get(I, #array{size = N, max = M, elements = E, default = D})
- when is_integer(I), I >= 0 ->
+ when is_integer(I), I >= 0, is_integer(N), is_integer(M) ->
if I < N ->
get_1(I, E, D);
M > 0 ->
@@ -673,7 +675,7 @@ get_1(I, E, _D) ->
-spec reset(I :: array_indx(), Array :: array(Type)) -> array(Type).
reset(I, #array{size = N, max = M, default = D, elements = E}=A)
- when is_integer(I), I >= 0 ->
+ when is_integer(I), I >= 0, is_integer(N), is_integer(M) ->
if I < N ->
try A#array{elements = reset_1(I, E, D)}
catch throw:default -> A
@@ -760,7 +762,7 @@ set_get_test_() ->
to_list(#array{size = 0}) ->
[];
-to_list(#array{size = N, elements = E, default = D}) ->
+to_list(#array{size = N, elements = E, default = D}) when is_integer(N) ->
to_list_1(E, D, N - 1);
to_list(_) ->
erlang:error(badarg).
@@ -833,7 +835,7 @@ to_list_test_() ->
sparse_to_list(#array{size = 0}) ->
[];
-sparse_to_list(#array{size = N, elements = E, default = D}) ->
+sparse_to_list(#array{size = N, elements = E, default = D}) when is_integer(N) ->
sparse_to_list_1(E, D, N - 1);
sparse_to_list(_) ->
erlang:error(badarg).
@@ -1011,7 +1013,7 @@ from_list_test_() ->
to_orddict(#array{size = 0}) ->
[];
-to_orddict(#array{size = N, elements = E, default = D}) ->
+to_orddict(#array{size = N, elements = E, default = D}) when is_integer(N) ->
I = N - 1,
to_orddict_1(E, I, D, I);
to_orddict(_) ->
@@ -1030,7 +1032,7 @@ to_orddict_1(E, R, D, I) when is_integer(E) ->
to_orddict_1(E, R, _D, I) ->
push_tuple_pairs(I+1, R, E, []).
-to_orddict_2(E=?NODEPATTERN(S), R, D, L) ->
+to_orddict_2(E=?NODEPATTERN(S), R, D, L) when is_integer(S) ->
to_orddict_3(?NODESIZE, R, D, L, E, S);
to_orddict_2(E, R, D, L) when is_integer(E) ->
push_pairs(E, R, D, L);
@@ -1103,7 +1105,8 @@ to_orddict_test_() ->
sparse_to_orddict(#array{size = 0}) ->
[];
-sparse_to_orddict(#array{size = N, elements = E, default = D}) ->
+sparse_to_orddict(#array{size = N, elements = E, default = D})
+ when is_integer(N) ->
I = N - 1,
sparse_to_orddict_1(E, I, D, I);
sparse_to_orddict(_) ->
@@ -1122,7 +1125,7 @@ sparse_to_orddict_1(E, _R, _D, _I) when is_integer(E) ->
sparse_to_orddict_1(E, R, D, I) ->
sparse_push_tuple_pairs(I+1, R, D, E, []).
-sparse_to_orddict_2(E=?NODEPATTERN(S), R, D, L) ->
+sparse_to_orddict_2(E=?NODEPATTERN(S), R, D, L) when is_integer(S) ->
sparse_to_orddict_3(?NODESIZE, R, D, L, E, S);
sparse_to_orddict_2(E, _R, _D, L) when is_integer(E) ->
L;
@@ -1223,7 +1226,7 @@ from_orddict_0([], N, _Max, _D, Es) ->
end;
from_orddict_0(Xs=[{Ix1, _}|_], Ix, Max0, D, Es0)
- when Ix1 > Max0, is_integer(Ix1) ->
+ when is_integer(Ix1), Ix1 > Max0 ->
%% We have a hole larger than a leaf
Hole = Ix1-Ix,
Step = Hole - (Hole rem ?LEAFSIZE),
@@ -1393,7 +1396,7 @@ from_orddict_test_() ->
Function :: fun((Index :: array_indx(), Type1) -> Type2).
map(Function, Array=#array{size = N, elements = E, default = D})
- when is_function(Function, 2) ->
+ when is_function(Function, 2), is_integer(N) ->
if N > 0 ->
A = Array#array{elements = []}, % kill reference, for GC
A#array{elements = map_1(N-1, E, 0, Function, D)};
@@ -1485,7 +1488,7 @@ map_test_() ->
Function :: fun((Index :: array_indx(), Type1) -> Type2).
sparse_map(Function, Array=#array{size = N, elements = E, default = D})
- when is_function(Function, 2) ->
+ when is_function(Function, 2), is_integer(N) ->
if N > 0 ->
A = Array#array{elements = []}, % kill reference, for GC
A#array{elements = sparse_map_1(N-1, E, 0, Function, D)};
@@ -1581,7 +1584,7 @@ sparse_map_test_() ->
Function :: fun((Index :: array_indx(), Value :: Type, Acc :: A) -> B).
foldl(Function, A, #array{size = N, elements = E, default = D})
- when is_function(Function, 3) ->
+ when is_function(Function, 3), is_integer(N) ->
if N > 0 ->
foldl_1(N-1, E, A, 0, Function, D);
true ->
@@ -1653,7 +1656,7 @@ foldl_test_() ->
Function :: fun((Index :: array_indx(), Value :: Type, Acc :: A) -> B).
sparse_foldl(Function, A, #array{size = N, elements = E, default = D})
- when is_function(Function, 3) ->
+ when is_function(Function, 3), is_integer(N) ->
if N > 0 ->
sparse_foldl_1(N-1, E, A, 0, Function, D);
true ->
@@ -1730,7 +1733,7 @@ sparse_foldl_test_() ->
Function :: fun((Index :: array_indx(), Value :: Type, Acc :: A) -> B).
foldr(Function, A, #array{size = N, elements = E, default = D})
- when is_function(Function, 3) ->
+ when is_function(Function, 3), is_integer(N) ->
if N > 0 ->
I = N - 1,
foldr_1(I, E, I, A, Function, D);
@@ -1808,7 +1811,7 @@ foldr_test_() ->
Function :: fun((Index :: array_indx(), Value :: Type, Acc :: A) -> B).
sparse_foldr(Function, A, #array{size = N, elements = E, default = D})
- when is_function(Function, 3) ->
+ when is_function(Function, 3), is_integer(N) ->
if N > 0 ->
I = N - 1,
sparse_foldr_1(I, E, I, A, Function, D);
@@ -1862,7 +1865,7 @@ sparse_size(A) ->
try sparse_foldr(F, [], A) of
[] -> 0
catch
- {value, I} ->
+ {value, I} when is_integer(I) ->
I + 1
end.
diff --git a/lib/stdlib/src/base64.erl b/lib/stdlib/src/base64.erl
index be4d9d42b4..62bcd0d24f 100644
--- a/lib/stdlib/src/base64.erl
+++ b/lib/stdlib/src/base64.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,12 +21,22 @@
-module(base64).
--export([encode/1, decode/1, mime_decode/1,
- encode_to_string/1, decode_to_string/1, mime_decode_to_string/1]).
-
+-export([encode/1, encode/2,
+ decode/1, decode/2,
+ mime_decode/1, mime_decode/2,
+ encode_to_string/1, encode_to_string/2,
+ decode_to_string/1, decode_to_string/2,
+ mime_decode_to_string/1, mime_decode_to_string/2,
+ format_error/2]).
%% RFC 4648: Base 64 Encoding alphabet
--type base64_alphabet() :: $A..$Z | $a..$z | $0..$9 | $+ | $/ | $=.
+-type base64_alphabet() :: $A..$Z | $a..$z | $0..$9 | $+ | $/ | $- | $_ | $=.
+
+%% Selector for the Base 64 alphabet, `standard' for RFC 4648
+%% Section 4, `urlsafe' for RFC 4648 Section 5.
+-type base64_mode() :: 'standard' | 'urlsafe'.
+
+-type options() :: #{padding => boolean(), mode => base64_mode()}.
%% The following type is a subtype of string() for return values
%% of encoding functions.
@@ -40,67 +50,126 @@
Data :: byte_string() | binary(),
Base64String :: base64_string().
-encode_to_string(Bin) when is_binary(Bin) ->
- encode_to_string(binary_to_list(Bin));
-encode_to_string(List) when is_list(List) ->
- encode_list_to_string(List).
+encode_to_string(Data) ->
+ encode_to_string(Data, #{}).
+
+-spec encode_to_string(Data, Options) -> Base64String when
+ Data :: byte_string() | binary(),
+ Options :: options(),
+ Base64String :: base64_string().
+
+encode_to_string(Bin, Options) when is_binary(Bin), is_map(Options) ->
+ encode_to_string(binary_to_list(Bin), Options);
+encode_to_string(List, Options) when is_list(List), is_map(Options) ->
+ encode_list_to_string(get_encoding_offset(Options), get_padding(Options), List).
-spec encode(Data) -> Base64 when
Data :: byte_string() | binary(),
Base64 :: base64_binary().
-encode(Bin) when is_binary(Bin) ->
- encode_binary(Bin, <<>>);
-encode(List) when is_list(List) ->
- encode_list(List, <<>>).
+encode(Data) ->
+ encode(Data, #{}).
+
+-spec encode(Data, Options) -> Base64 when
+ Data :: byte_string() | binary(),
+ Options :: options(),
+ Base64 :: base64_binary().
-encode_list_to_string([]) ->
+encode(Bin, Options) when is_binary(Bin), is_map(Options) ->
+ encode_binary(get_encoding_offset(Options), get_padding(Options), Bin, <<>>);
+encode(List, Options) when is_list(List) ->
+ encode_list(get_encoding_offset(Options), get_padding(Options), List, <<>>).
+
+encode_list_to_string(_ModeOffset, _Padding, []) ->
[];
-encode_list_to_string([B1]) ->
- [b64e(B1 bsr 2),
- b64e((B1 band 3) bsl 4), $=, $=];
-encode_list_to_string([B1,B2]) ->
- [b64e(B1 bsr 2),
- b64e(((B1 band 3) bsl 4) bor (B2 bsr 4)),
- b64e((B2 band 15) bsl 2), $=];
-encode_list_to_string([B1,B2,B3|Ls]) ->
+encode_list_to_string(ModeOffset, Padding, [B1]) ->
+ [b64e(B1 bsr 2, ModeOffset),
+ b64e((B1 band 3) bsl 4, ModeOffset) |
+ case Padding of
+ true -> "==";
+ false -> ""
+ end];
+encode_list_to_string(ModeOffset, Padding, [B1,B2]) ->
+ [b64e(B1 bsr 2, ModeOffset),
+ b64e(((B1 band 3) bsl 4) bor (B2 bsr 4), ModeOffset),
+ b64e((B2 band 15) bsl 2, ModeOffset) |
+ case Padding of
+ true -> "=";
+ false -> ""
+ end];
+encode_list_to_string(ModeOffset, Padding, [B1,B2,B3|Ls]) ->
BB = (B1 bsl 16) bor (B2 bsl 8) bor B3,
- [b64e(BB bsr 18),
- b64e((BB bsr 12) band 63),
- b64e((BB bsr 6) band 63),
- b64e(BB band 63) | encode_list_to_string(Ls)].
-
-encode_binary(<<>>, A) ->
+ [b64e(BB bsr 18, ModeOffset),
+ b64e((BB bsr 12) band 63, ModeOffset),
+ b64e((BB bsr 6) band 63, ModeOffset),
+ b64e(BB band 63, ModeOffset) | encode_list_to_string(ModeOffset, Padding, Ls)].
+
+encode_binary(ModeOffset, Padding, <<B1:6, B2:6, B3:6, B4:6, B5:6, B6:6, B7:6, B8:6, Ls/bits>>, A) ->
+ encode_binary(ModeOffset,
+ Padding,
+ Ls,
+ <<A/bits,
+ (b64e(B1, ModeOffset)):8,
+ (b64e(B2, ModeOffset)):8,
+ (b64e(B3, ModeOffset)):8,
+ (b64e(B4, ModeOffset)):8,
+ (b64e(B5, ModeOffset)):8,
+ (b64e(B6, ModeOffset)):8,
+ (b64e(B7, ModeOffset)):8,
+ (b64e(B8, ModeOffset)):8>>);
+encode_binary(_ModeOffset, _Padding, <<>>, A) ->
A;
-encode_binary(<<B1:8>>, A) ->
- <<A/bits,(b64e(B1 bsr 2)):8,(b64e((B1 band 3) bsl 4)):8,$=:8,$=:8>>;
-encode_binary(<<B1:8, B2:8>>, A) ->
- <<A/bits,(b64e(B1 bsr 2)):8,
- (b64e(((B1 band 3) bsl 4) bor (B2 bsr 4))):8,
- (b64e((B2 band 15) bsl 2)):8, $=:8>>;
-encode_binary(<<B1:8, B2:8, B3:8, Ls/bits>>, A) ->
- BB = (B1 bsl 16) bor (B2 bsl 8) bor B3,
- encode_binary(Ls,
- <<A/bits,(b64e(BB bsr 18)):8,
- (b64e((BB bsr 12) band 63)):8,
- (b64e((BB bsr 6) band 63)):8,
- (b64e(BB band 63)):8>>).
+encode_binary(ModeOffset, Padding, <<B1:6, B2:6, B3:6, B4:6, Ls/bits>>, A) ->
+ encode_binary(ModeOffset,
+ Padding,
+ Ls,
+ <<A/bits,
+ (b64e(B1, ModeOffset)):8,
+ (b64e(B2, ModeOffset)):8,
+ (b64e(B3, ModeOffset)):8,
+ (b64e(B4, ModeOffset)):8>>);
+encode_binary(ModeOffset, Padding, <<B1:6, B2:2>>, A) ->
+ E1 = b64e(B1, ModeOffset),
+ E2 = b64e(B2 bsl 4, ModeOffset),
+ case Padding of
+ true -> <<A/bits,E1,E2,$=,$=>>;
+ _ -> <<A/bits,E1,E2>>
+ end;
+encode_binary(ModeOffset, Padding, <<B1:6, B2:6, B3:4>>, A) ->
+ E1 = b64e(B1, ModeOffset),
+ E2 = b64e(B2, ModeOffset),
+ E3 = b64e(B3 bsl 2, ModeOffset),
+ case Padding of
+ true -> <<A/bits,E1,E2,E3,$=>>;
+ _ -> <<A/bits,E1,E2,E3>>
+ end.
-encode_list([], A) ->
+encode_list(_ModeOffset, _Padding, [], A) ->
A;
-encode_list([B1], A) ->
- <<A/bits,(b64e(B1 bsr 2)):8,(b64e((B1 band 3) bsl 4)):8,$=:8,$=:8>>;
-encode_list([B1,B2], A) ->
- <<A/bits,(b64e(B1 bsr 2)):8,
- (b64e(((B1 band 3) bsl 4) bor (B2 bsr 4))):8,
- (b64e((B2 band 15) bsl 2)):8, $=:8>>;
-encode_list([B1,B2,B3|Ls], A) ->
+encode_list(ModeOffset, Padding, [B1], A) ->
+ E1 = b64e(B1 bsr 2, ModeOffset),
+ E2 = b64e((B1 band 3) bsl 4, ModeOffset),
+ case Padding of
+ true -> <<A/bits,E1,E2,$=,$=>>;
+ false -> <<A/bits,E1,E2>>
+ end;
+encode_list(ModeOffset, Padding, [B1,B2], A) ->
+ E1 = b64e(B1 bsr 2, ModeOffset),
+ E2 = b64e(((B1 band 3) bsl 4) bor (B2 bsr 4), ModeOffset),
+ E3 = b64e((B2 band 15) bsl 2, ModeOffset),
+ case Padding of
+ true -> <<A/bits,E1,E2,E3,$=>>;
+ false -> <<A/bits,E1,E2,E3>>
+ end;
+encode_list(ModeOffset, Padding, [B1,B2,B3|Ls], A) ->
BB = (B1 bsl 16) bor (B2 bsl 8) bor B3,
- encode_list(Ls,
- <<A/bits,(b64e(BB bsr 18)):8,
- (b64e((BB bsr 12) band 63)):8,
- (b64e((BB bsr 6) band 63)):8,
- (b64e(BB band 63)):8>>).
+ encode_list(ModeOffset,
+ Padding,
+ Ls,
+ <<A/bits,(b64e(BB bsr 18, ModeOffset)):8,
+ (b64e((BB bsr 12) band 63, ModeOffset)):8,
+ (b64e((BB bsr 6) band 63, ModeOffset)):8,
+ (b64e(BB band 63, ModeOffset)):8>>).
%% mime_decode strips away all characters not Base64 before
%% converting, whereas decode crashes if an illegal character is found
@@ -109,19 +178,35 @@ encode_list([B1,B2,B3|Ls], A) ->
Base64 :: base64_string() | base64_binary(),
Data :: binary().
-decode(Bin) when is_binary(Bin) ->
- decode_binary(Bin, <<>>);
-decode(List) when is_list(List) ->
- decode_list(List, <<>>).
+decode(Base64) ->
+ decode(Base64, #{}).
+
+-spec decode(Base64, Options) -> Data when
+ Base64 :: base64_string() | base64_binary(),
+ Options :: options(),
+ Data :: binary().
+
+decode(Bin, Options) when is_binary(Bin) ->
+ decode_binary(get_decoding_offset(Options), get_padding(Options), Bin, <<>>);
+decode(List, Options) when is_list(List) ->
+ decode_list(get_decoding_offset(Options), get_padding(Options), List, <<>>).
-spec mime_decode(Base64) -> Data when
Base64 :: base64_string() | base64_binary(),
Data :: binary().
-mime_decode(Bin) when is_binary(Bin) ->
- mime_decode_binary(Bin, <<>>);
-mime_decode(List) when is_list(List) ->
- mime_decode_list(List, <<>>).
+mime_decode(Base64) ->
+ mime_decode(Base64, #{}).
+
+-spec mime_decode(Base64, Options) -> Data when
+ Base64 :: base64_string() | base64_binary(),
+ Options :: options(),
+ Data :: binary().
+
+mime_decode(Bin, Options) when is_binary(Bin) ->
+ mime_decode_binary(get_decoding_offset(Options), get_padding(Options), Bin, <<>>);
+mime_decode(List, Options) when is_list(List) ->
+ mime_decode_list(get_decoding_offset(Options), get_padding(Options), List, <<>>).
%% mime_decode_to_string strips away all characters not Base64 before
%% converting, whereas decode_to_string crashes if an illegal
@@ -131,324 +216,439 @@ mime_decode(List) when is_list(List) ->
Base64 :: base64_string() | base64_binary(),
DataString :: byte_string().
-decode_to_string(Bin) when is_binary(Bin) ->
- decode_to_string(binary_to_list(Bin));
-decode_to_string(List) when is_list(List) ->
- decode_list_to_string(List).
+decode_to_string(Base64) ->
+ decode_to_string(Base64, #{}).
+
+-spec decode_to_string(Base64, Options) -> DataString when
+ Base64 :: base64_string() | base64_binary(),
+ Options :: options(),
+ DataString :: byte_string().
+
+decode_to_string(Bin, Options) when is_binary(Bin) ->
+ decode_to_string(binary_to_list(Bin), Options);
+decode_to_string(List, Options) when is_list(List) ->
+ decode_list_to_string(get_decoding_offset(Options), get_padding(Options), List).
-spec mime_decode_to_string(Base64) -> DataString when
Base64 :: base64_string() | base64_binary(),
DataString :: byte_string().
-mime_decode_to_string(Bin) when is_binary(Bin) ->
- mime_decode_to_string(binary_to_list(Bin));
-mime_decode_to_string(List) when is_list(List) ->
- mime_decode_list_to_string(List).
+mime_decode_to_string(Base64) ->
+ mime_decode_to_string(Base64, #{}).
+
+-spec mime_decode_to_string(Base64, Options) -> DataString when
+ Base64 :: base64_string() | base64_binary(),
+ Options :: options(),
+ DataString :: byte_string().
+
+mime_decode_to_string(Bin, Options) when is_binary(Bin) ->
+ mime_decode_to_string(binary_to_list(Bin), Options);
+mime_decode_to_string(List, Options) when is_list(List) ->
+ mime_decode_list_to_string(get_decoding_offset(Options), get_padding(Options), List).
%% Skipping pad character if not at end of string. Also liberal about
%% excess padding and skipping of other illegal (non-base64 alphabet)
%% characters. See section 3.3 of RFC4648
-mime_decode_list([0 | Cs], A) ->
- mime_decode_list(Cs, A);
-mime_decode_list([C1 | Cs], A) ->
- case b64d(C1) of
- B1 when is_integer(B1) -> mime_decode_list(Cs, A, B1);
- _ -> mime_decode_list(Cs, A) % eq is padding
+mime_decode_list(ModeOffset, Padding, [C1 | Cs], A) ->
+ case b64d(C1, ModeOffset) of
+ B1 when is_integer(B1) -> mime_decode_list(ModeOffset, Padding, Cs, A, B1);
+ _ -> mime_decode_list(ModeOffset, Padding, Cs, A) % eq is padding
end;
-mime_decode_list([], A) ->
+mime_decode_list(_ModeOffset, _Padding, [], A) ->
A.
-mime_decode_list([0 | Cs], A, B1) ->
- mime_decode_list(Cs, A, B1);
-mime_decode_list([C2 | Cs], A, B1) ->
- case b64d(C2) of
+mime_decode_list(ModeOffset, Padding, [C2 | Cs], A, B1) ->
+ case b64d(C2, ModeOffset) of
B2 when is_integer(B2) ->
- mime_decode_list(Cs, A, B1, B2);
- _ -> mime_decode_list(Cs, A, B1) % eq is padding
+ mime_decode_list(ModeOffset, Padding, Cs, A, B1, B2);
+ _ -> mime_decode_list(ModeOffset, Padding, Cs, A, B1) % eq is padding
end.
-mime_decode_list([0 | Cs], A, B1, B2) ->
- mime_decode_list(Cs, A, B1, B2);
-mime_decode_list([C3 | Cs], A, B1, B2) ->
- case b64d(C3) of
+mime_decode_list(ModeOffset, Padding, [C3 | Cs], A, B1, B2) ->
+ case b64d(C3, ModeOffset) of
B3 when is_integer(B3) ->
- mime_decode_list(Cs, A, B1, B2, B3);
+ mime_decode_list(ModeOffset, Padding, Cs, A, B1, B2, B3);
eq=B3 ->
- mime_decode_list_after_eq(Cs, A, B1, B2, B3);
- _ -> mime_decode_list(Cs, A, B1, B2)
+ mime_decode_list_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3);
+ _ -> mime_decode_list(ModeOffset, Padding, Cs, A, B1, B2)
+ end;
+mime_decode_list(ModeOffset, Padding, [], A, B1, B2) ->
+ case Padding of
+ true -> missing_padding_error();
+ false -> mime_decode_list_after_eq(ModeOffset, Padding, [], A, B1, B2, eq)
end.
-mime_decode_list([0 | Cs], A, B1, B2, B3) ->
- mime_decode_list(Cs, A, B1, B2, B3);
-mime_decode_list([C4 | Cs], A, B1, B2, B3) ->
- case b64d(C4) of
+mime_decode_list(ModeOffset, Padding, [C4 | Cs], A, B1, B2, B3) ->
+ case b64d(C4, ModeOffset) of
B4 when is_integer(B4) ->
- mime_decode_list(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>);
+ mime_decode_list(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>);
eq ->
- mime_decode_list_after_eq(Cs, A, B1, B2, B3);
- _ -> mime_decode_list(Cs, A, B1, B2, B3)
+ mime_decode_list_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3);
+ _ -> mime_decode_list(ModeOffset, Padding, Cs, A, B1, B2, B3)
+ end;
+mime_decode_list(ModeOffset, Padding, [], A, B1, B2, B3) ->
+ case Padding of
+ true -> missing_padding_error();
+ false -> mime_decode_list_after_eq(ModeOffset, Padding, [], A, B1, B2, B3)
end.
-mime_decode_list_after_eq([0 | Cs], A, B1, B2, B3) ->
- mime_decode_list_after_eq(Cs, A, B1, B2, B3);
-mime_decode_list_after_eq([C | Cs], A, B1, B2, B3) ->
- case b64d(C) of
+mime_decode_list_after_eq(ModeOffset, Padding, [C | Cs], A, B1, B2, B3) ->
+ case b64d(C, ModeOffset) of
B when is_integer(B) ->
%% More valid data, skip the eq as invalid
case B3 of
- eq -> mime_decode_list(Cs, A, B1, B2, B);
- _ -> mime_decode_list(Cs, <<A/bits,B1:6,B2:6,B3:6,B:6>>)
+ eq -> mime_decode_list(ModeOffset, Padding, Cs, A, B1, B2, B);
+ _ -> mime_decode_list(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B:6>>)
end;
- _ -> mime_decode_list_after_eq(Cs, A, B1, B2, B3)
+ _ -> mime_decode_list_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3)
end;
-mime_decode_list_after_eq([], A, B1, B2, eq) ->
+mime_decode_list_after_eq(_ModeOffset, _Padding, [], A, B1, B2, eq) ->
<<A/bits,B1:6,(B2 bsr 4):2>>;
-mime_decode_list_after_eq([], A, B1, B2, B3) ->
+mime_decode_list_after_eq(_ModeOffset, _Padding, [], A, B1, B2, B3) ->
<<A/bits,B1:6,B2:6,(B3 bsr 2):4>>.
-mime_decode_binary(<<0:8, Cs/bits>>, A) ->
- mime_decode_binary(Cs, A);
-mime_decode_binary(<<C1:8, Cs/bits>>, A) ->
- case b64d(C1) of
- B1 when is_integer(B1) -> mime_decode_binary(Cs, A, B1);
- _ -> mime_decode_binary(Cs, A) % eq is padding
+mime_decode_binary(ModeOffset, Padding, <<C1:8, Cs/bits>>, A) ->
+ case b64d(C1, ModeOffset) of
+ B1 when is_integer(B1) -> mime_decode_binary(ModeOffset, Padding, Cs, A, B1);
+ _ -> mime_decode_binary(ModeOffset, Padding, Cs, A) % eq is padding
end;
-mime_decode_binary(<<>>, A) ->
+mime_decode_binary(_ModeOffset, _Padding, <<>>, A) ->
A.
-mime_decode_binary(<<0:8, Cs/bits>>, A, B1) ->
- mime_decode_binary(Cs, A, B1);
-mime_decode_binary(<<C2:8, Cs/bits>>, A, B1) ->
- case b64d(C2) of
+mime_decode_binary(ModeOffset, Padding, <<C2:8, Cs/bits>>, A, B1) ->
+ case b64d(C2, ModeOffset) of
B2 when is_integer(B2) ->
- mime_decode_binary(Cs, A, B1, B2);
- _ -> mime_decode_binary(Cs, A, B1) % eq is padding
+ mime_decode_binary(ModeOffset, Padding, Cs, A, B1, B2);
+ _ -> mime_decode_binary(ModeOffset, Padding, Cs, A, B1) % eq is padding
end.
-mime_decode_binary(<<0:8, Cs/bits>>, A, B1, B2) ->
- mime_decode_binary(Cs, A, B1, B2);
-mime_decode_binary(<<C3:8, Cs/bits>>, A, B1, B2) ->
- case b64d(C3) of
+mime_decode_binary(ModeOffset, Padding, <<C3:8, Cs/bits>>, A, B1, B2) ->
+ case b64d(C3, ModeOffset) of
B3 when is_integer(B3) ->
- mime_decode_binary(Cs, A, B1, B2, B3);
+ mime_decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3);
eq=B3 ->
- mime_decode_binary_after_eq(Cs, A, B1, B2, B3);
- _ -> mime_decode_binary(Cs, A, B1, B2)
+ mime_decode_binary_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3);
+ _ -> mime_decode_binary(ModeOffset, Padding, Cs, A, B1, B2)
+ end;
+mime_decode_binary(ModeOffset, Padding, <<Cs/bits>>, A, B1, B2) ->
+ case Padding of
+ true -> missing_padding_error();
+ false -> mime_decode_binary_after_eq(ModeOffset, Padding, Cs, A, B1, B2, eq)
end.
-mime_decode_binary(<<0:8, Cs/bits>>, A, B1, B2, B3) ->
- mime_decode_binary(Cs, A, B1, B2, B3);
-mime_decode_binary(<<C4:8, Cs/bits>>, A, B1, B2, B3) ->
- case b64d(C4) of
+mime_decode_binary(ModeOffset, Padding, <<C4:8, Cs/bits>>, A, B1, B2, B3) ->
+ case b64d(C4, ModeOffset) of
B4 when is_integer(B4) ->
- mime_decode_binary(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>);
+ mime_decode_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>);
eq ->
- mime_decode_binary_after_eq(Cs, A, B1, B2, B3);
- _ -> mime_decode_binary(Cs, A, B1, B2, B3)
+ mime_decode_binary_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3);
+ _ -> mime_decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3)
+ end;
+mime_decode_binary(ModeOffset, Padding, <<Cs/bits>>, A, B1, B2, B3) ->
+ case Padding of
+ true -> missing_padding_error();
+ false -> mime_decode_binary_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3)
end.
-mime_decode_binary_after_eq(<<0:8, Cs/bits>>, A, B1, B2, B3) ->
- mime_decode_binary_after_eq(Cs, A, B1, B2, B3);
-mime_decode_binary_after_eq(<<C:8, Cs/bits>>, A, B1, B2, B3) ->
- case b64d(C) of
+mime_decode_binary_after_eq(ModeOffset, Padding, <<C:8, Cs/bits>>, A, B1, B2, B3) ->
+ case b64d(C, ModeOffset) of
B when is_integer(B) ->
%% More valid data, skip the eq as invalid
case B3 of
- eq -> mime_decode_binary(Cs, A, B1, B2, B);
- _ -> mime_decode_binary(Cs, <<A/bits,B1:6,B2:6,B3:6,B:6>>)
+ eq -> mime_decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B);
+ _ -> mime_decode_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B:6>>)
end;
- _ -> mime_decode_binary_after_eq(Cs, A, B1, B2, B3)
+ _ -> mime_decode_binary_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3)
end;
-mime_decode_binary_after_eq(<<>>, A, B1, B2, eq) ->
+mime_decode_binary_after_eq(_ModeOffset, _Padding, <<>>, A, B1, B2, eq) ->
<<A/bits,B1:6,(B2 bsr 4):2>>;
-mime_decode_binary_after_eq(<<>>, A, B1, B2, B3) ->
+mime_decode_binary_after_eq(_ModeOffset, _Padding, <<>>, A, B1, B2, B3) ->
<<A/bits,B1:6,B2:6,(B3 bsr 2):4>>.
-mime_decode_list_to_string([0 | Cs]) ->
- mime_decode_list_to_string(Cs);
-mime_decode_list_to_string([C1 | Cs]) ->
- case b64d(C1) of
- B1 when is_integer(B1) -> mime_decode_list_to_string(Cs, B1);
- _ -> mime_decode_list_to_string(Cs) % eq is padding
+mime_decode_list_to_string(ModeOffset, Padding, [C1 | Cs]) ->
+ case b64d(C1, ModeOffset) of
+ B1 when is_integer(B1) -> mime_decode_list_to_string(ModeOffset, Padding, Cs, B1);
+ _ -> mime_decode_list_to_string(ModeOffset, Padding, Cs) % eq is padding
end;
-mime_decode_list_to_string([]) ->
+mime_decode_list_to_string(_ModeOffset, _Padding, []) ->
[].
-mime_decode_list_to_string([0 | Cs], B1) ->
- mime_decode_list_to_string(Cs, B1);
-mime_decode_list_to_string([C2 | Cs], B1) ->
- case b64d(C2) of
+mime_decode_list_to_string(ModeOffset, Padding, [C2 | Cs], B1) ->
+ case b64d(C2, ModeOffset) of
B2 when is_integer(B2) ->
- mime_decode_list_to_string(Cs, B1, B2);
- _ -> mime_decode_list_to_string(Cs, B1) % eq is padding
+ mime_decode_list_to_string(ModeOffset, Padding, Cs, B1, B2);
+ _ -> mime_decode_list_to_string(ModeOffset, Padding, Cs, B1) % eq is padding
end.
-mime_decode_list_to_string([0 | Cs], B1, B2) ->
- mime_decode_list_to_string(Cs, B1, B2);
-mime_decode_list_to_string([C3 | Cs], B1, B2) ->
- case b64d(C3) of
+mime_decode_list_to_string(ModeOffset, Padding, [C3 | Cs], B1, B2) ->
+ case b64d(C3, ModeOffset) of
B3 when is_integer(B3) ->
- mime_decode_list_to_string(Cs, B1, B2, B3);
- eq=B3 -> mime_decode_list_to_string_after_eq(Cs, B1, B2, B3);
- _ -> mime_decode_list_to_string(Cs, B1, B2)
+ mime_decode_list_to_string(ModeOffset, Padding, Cs, B1, B2, B3);
+ eq=B3 -> mime_decode_list_to_string_after_eq(ModeOffset, Padding, Cs, B1, B2, B3);
+ _ -> mime_decode_list_to_string(ModeOffset, Padding, Cs, B1, B2)
+ end;
+mime_decode_list_to_string(ModeOffset, Padding, [], B1, B2) ->
+ case Padding of
+ true -> missing_padding_error();
+ false -> mime_decode_list_to_string_after_eq(ModeOffset, Padding, [], B1, B2, eq)
end.
-mime_decode_list_to_string([0 | Cs], B1, B2, B3) ->
- mime_decode_list_to_string(Cs, B1, B2, B3);
-mime_decode_list_to_string([C4 | Cs], B1, B2, B3) ->
- case b64d(C4) of
+mime_decode_list_to_string(ModeOffset, Padding, [C4 | Cs], B1, B2, B3) ->
+ case b64d(C4, ModeOffset) of
B4 when is_integer(B4) ->
Bits4x6 = (B1 bsl 18) bor (B2 bsl 12) bor (B3 bsl 6) bor B4,
Octet1 = Bits4x6 bsr 16,
Octet2 = (Bits4x6 bsr 8) band 16#ff,
Octet3 = Bits4x6 band 16#ff,
- [Octet1, Octet2, Octet3 | mime_decode_list_to_string(Cs)];
+ [Octet1, Octet2, Octet3 | mime_decode_list_to_string(ModeOffset, Padding, Cs)];
eq ->
- mime_decode_list_to_string_after_eq(Cs, B1, B2, B3);
- _ -> mime_decode_list_to_string(Cs, B1, B2, B3)
+ mime_decode_list_to_string_after_eq(ModeOffset, Padding, Cs, B1, B2, B3);
+ _ -> mime_decode_list_to_string(ModeOffset, Padding, Cs, B1, B2, B3)
+ end;
+mime_decode_list_to_string(ModeOffset, Padding, [], B1, B2, B3) ->
+ case Padding of
+ true -> missing_padding_error();
+ false -> mime_decode_list_to_string_after_eq(ModeOffset, Padding, [], B1, B2, B3)
end.
-mime_decode_list_to_string_after_eq([0 | Cs], B1, B2, B3) ->
- mime_decode_list_to_string_after_eq(Cs, B1, B2, B3);
-mime_decode_list_to_string_after_eq([C | Cs], B1, B2, B3) ->
- case b64d(C) of
+mime_decode_list_to_string_after_eq(ModeOffset, Padding, [C | Cs], B1, B2, B3) ->
+ case b64d(C, ModeOffset) of
B when is_integer(B) ->
%% More valid data, skip the eq as invalid
case B3 of
- eq -> mime_decode_list_to_string(Cs, B1, B2, B);
+ eq -> mime_decode_list_to_string(ModeOffset, Padding, Cs, B1, B2, B);
_ ->
Bits4x6 = (B1 bsl 18) bor (B2 bsl 12) bor (B3 bsl 6) bor B,
Octet1 = Bits4x6 bsr 16,
Octet2 = (Bits4x6 bsr 8) band 16#ff,
Octet3 = Bits4x6 band 16#ff,
- [Octet1, Octet2, Octet3 | mime_decode_list_to_string(Cs)]
+ [Octet1, Octet2, Octet3 | mime_decode_list_to_string(ModeOffset, Padding, Cs)]
end;
- _ -> mime_decode_list_to_string_after_eq(Cs, B1, B2, B3)
+ _ -> mime_decode_list_to_string_after_eq(ModeOffset, Padding, Cs, B1, B2, B3)
end;
-mime_decode_list_to_string_after_eq([], B1, B2, eq) ->
+mime_decode_list_to_string_after_eq(_ModeOffset, _Padding, [], B1, B2, eq) ->
binary_to_list(<<B1:6,(B2 bsr 4):2>>);
-mime_decode_list_to_string_after_eq([], B1, B2, B3) ->
+mime_decode_list_to_string_after_eq(_ModeOffset, _Padding, [], B1, B2, B3) ->
binary_to_list(<<B1:6,B2:6,(B3 bsr 2):4>>).
-decode_list([C1 | Cs], A) ->
- case b64d(C1) of
- ws -> decode_list(Cs, A);
- B1 -> decode_list(Cs, A, B1)
+decode_list(ModeOffset, Padding, [C1 | Cs], A) ->
+ case b64d(C1, ModeOffset) of
+ ws -> decode_list(ModeOffset, Padding, Cs, A);
+ B1 -> decode_list(ModeOffset, Padding, Cs, A, B1)
end;
-decode_list([], A) ->
+decode_list(_ModeOffset, _Padding, [], A) ->
A.
-decode_list([C2 | Cs], A, B1) ->
- case b64d(C2) of
- ws -> decode_list(Cs, A, B1);
- B2 -> decode_list(Cs, A, B1, B2)
+decode_list(ModeOffset, Padding, [C2 | Cs], A, B1) ->
+ case b64d(C2, ModeOffset) of
+ ws -> decode_list(ModeOffset, Padding, Cs, A, B1);
+ B2 -> decode_list(ModeOffset, Padding, Cs, A, B1, B2)
end.
-decode_list([C3 | Cs], A, B1, B2) ->
- case b64d(C3) of
- ws -> decode_list(Cs, A, B1, B2);
- B3 -> decode_list(Cs, A, B1, B2, B3)
+decode_list(ModeOffset, Padding, [C3 | Cs], A, B1, B2) ->
+ case b64d(C3, ModeOffset) of
+ ws -> decode_list(ModeOffset, Padding, Cs, A, B1, B2);
+ B3 -> decode_list(ModeOffset, Padding, Cs, A, B1, B2, B3)
+ end;
+decode_list(ModeOffset, Padding, [], A, B1, B2) ->
+ case Padding of
+ true -> missing_padding_error();
+ false -> decode_list(ModeOffset, Padding, [], A, B1, B2, eq)
end.
-decode_list([C4 | Cs], A, B1, B2, B3) ->
- case b64d(C4) of
- ws -> decode_list(Cs, A, B1, B2, B3);
- eq when B3 =:= eq -> only_ws(Cs, <<A/bits,B1:6,(B2 bsr 4):2>>);
- eq -> only_ws(Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>);
- B4 -> decode_list(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>)
+decode_list(ModeOffset, Padding, [C4 | Cs], A, B1, B2, B3) ->
+ case b64d(C4, ModeOffset) of
+ ws -> decode_list(ModeOffset, Padding, Cs, A, B1, B2, B3);
+ eq when B3 =:= eq -> only_ws(ModeOffset, Padding, Cs, <<A/bits,B1:6,(B2 bsr 4):2>>);
+ eq -> only_ws(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>);
+ B4 -> decode_list(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>)
+ end;
+decode_list(_ModeOffset, Padding, [], A, B1, B2, B3) ->
+ case Padding of
+ true -> missing_padding_error();
+ false when B3 == eq -> <<A/bits,B1:6,(B2 bsr 4):2>>;
+ false -> <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>
end.
-decode_binary(<<C1:8, Cs/bits>>, A) ->
- case b64d(C1) of
- ws -> decode_binary(Cs, A);
- B1 -> decode_binary(Cs, A, B1)
+decode_binary(ModeOffset, Padding, <<C1:8, C2:8, C3:8, C4:8, Cs/bits>>, A) ->
+ case {b64d(C1, ModeOffset), b64d(C2, ModeOffset), b64d(C3, ModeOffset), b64d(C4, ModeOffset)} of
+ {B1, B2, B3, B4} when is_integer(B1), is_integer(B2),
+ is_integer(B3), is_integer(B4) ->
+ decode_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>);
+ {B1, B2, B3, B4} ->
+ dec_bin(ModeOffset, Padding, Cs, B1, B2, B3, B4, A)
end;
-decode_binary(<<>>, A) ->
- A.
+decode_binary(_ModeOffset, _Padding, <<>>, A) ->
+ A;
+decode_binary(ModeOffset, Padding, <<C1:8, Cs/bits>>, A) ->
+ case b64d(C1, ModeOffset) of
+ ws -> decode_binary(ModeOffset, Padding, Cs, A);
+ B1 -> decode_binary(ModeOffset, Padding, Cs, A, B1)
+ end.
-decode_binary(<<C2:8, Cs/bits>>, A, B1) ->
- case b64d(C2) of
- ws -> decode_binary(Cs, A, B1);
- B2 -> decode_binary(Cs, A, B1, B2)
+dec_bin(ModeOffset, Padding, Cs, ws, B2, B3, B4, A) ->
+ dec_bin(ModeOffset, Padding, Cs, B2, B3, B4, A);
+dec_bin(ModeOffset, Padding, Cs, B1, ws, B3, B4, A) ->
+ dec_bin(ModeOffset, Padding, Cs, B1, B3, B4, A);
+dec_bin(ModeOffset, Padding, Cs, B1, B2, ws, B4, A) ->
+ dec_bin(ModeOffset, Padding, Cs, B1, B2, B4, A);
+dec_bin(ModeOffset, Padding, Cs, B1, B2, B3, B4, A) ->
+ case B4 of
+ ws -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3);
+ eq when B3 =:= eq -> only_ws_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,(B2 bsr 4):2>>);
+ eq -> only_ws_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>);
+ B4 -> decode_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>)
end.
-decode_binary(<<C3:8, Cs/bits>>, A, B1, B2) ->
- case b64d(C3) of
- ws -> decode_binary(Cs, A, B1, B2);
- B3 -> decode_binary(Cs, A, B1, B2, B3)
+dec_bin(ModeOffset, Padding, Cs, ws, B2, B3, A) ->
+ dec_bin(ModeOffset, Padding, Cs, B2, B3, A);
+dec_bin(ModeOffset, Padding, Cs, B1, ws, B3, A) ->
+ dec_bin(ModeOffset, Padding, Cs, B1, B3, A);
+dec_bin(ModeOffset, Padding, Cs, B1, B2, ws, A) ->
+ dec_bin(ModeOffset, Padding, Cs, B1, B2, A);
+dec_bin(ModeOffset, Padding, Cs, B1, B2, B3, A) ->
+ decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3).
+
+dec_bin(ModeOffset, Padding, Cs, ws, B2, A) ->
+ dec_bin(ModeOffset, Padding, Cs, B2, A);
+dec_bin(ModeOffset, Padding, Cs, B1, ws, A) ->
+ dec_bin(ModeOffset, Padding, Cs, B1, A);
+dec_bin(ModeOffset, Padding, Cs, B1, B2, A) ->
+ decode_binary(ModeOffset, Padding, Cs, A, B1, B2).
+
+dec_bin(ModeOffset, Padding, Cs, ws, A) ->
+ decode_binary(ModeOffset, Padding, Cs, A);
+dec_bin(ModeOffset, Padding, Cs, B1, A) ->
+ decode_binary(ModeOffset, Padding,Cs, A, B1).
+
+decode_binary(ModeOffset, Padding, <<C2:8, Cs/bits>>, A, B1) ->
+ case b64d(C2, ModeOffset) of
+ ws -> decode_binary(ModeOffset, Padding, Cs, A, B1);
+ B2 -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2)
end.
-decode_binary(<<C4:8, Cs/bits>>, A, B1, B2, B3) ->
- case b64d(C4) of
- ws -> decode_binary(Cs, A, B1, B2, B3);
- eq when B3 =:= eq -> only_ws_binary(Cs, <<A/bits,B1:6,(B2 bsr 4):2>>);
- eq -> only_ws_binary(Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>);
- B4 -> decode_binary(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>)
+decode_binary(ModeOffset, Padding, <<C3:8, Cs/bits>>, A, B1, B2) ->
+ case b64d(C3, ModeOffset) of
+ ws -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2);
+ B3 -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3)
+ end;
+decode_binary(ModeOffset, Padding, <<Cs/bits>>, A, B1, B2) ->
+ case Padding of
+ true -> missing_padding_error();
+ false -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2, eq)
+ end.
+
+decode_binary(ModeOffset, Padding, <<C4:8, Cs/bits>>, A, B1, B2, B3) ->
+ case b64d(C4, ModeOffset) of
+ ws -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3);
+ eq when B3 =:= eq -> only_ws_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,(B2 bsr 4):2>>);
+ eq -> only_ws_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>);
+ B4 -> decode_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>)
+ end;
+decode_binary(_ModeOffset, Padding, <<>>, A, B1, B2, B3) ->
+ case Padding of
+ true -> missing_padding_error();
+ false when B3 =:= eq -> <<A/bits,B1:6,(B2 bsr 4):2>>;
+ false -> <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>
end.
-only_ws_binary(<<>>, A) ->
+only_ws_binary(_ModeOffset, _Padding, <<>>, A) ->
A;
-only_ws_binary(<<C:8, Cs/bits>>, A) ->
- case b64d(C) of
- ws -> only_ws_binary(Cs, A)
+only_ws_binary(ModeOffset, Padding, <<C:8, Cs/bits>>, A) ->
+ case b64d(C, ModeOffset) of
+ ws -> only_ws_binary(ModeOffset, Padding, Cs, A)
end.
-decode_list_to_string([C1 | Cs]) ->
- case b64d(C1) of
- ws -> decode_list_to_string(Cs);
- B1 -> decode_list_to_string(Cs, B1)
+decode_list_to_string(ModeOffset, Padding, [C1 | Cs]) ->
+ case b64d(C1, ModeOffset) of
+ ws -> decode_list_to_string(ModeOffset, Padding, Cs);
+ B1 -> decode_list_to_string(ModeOffset, Padding, Cs, B1)
end;
-decode_list_to_string([]) ->
+decode_list_to_string(_ModeOffset, _Padding, []) ->
[].
-decode_list_to_string([C2 | Cs], B1) ->
- case b64d(C2) of
- ws -> decode_list_to_string(Cs, B1);
- B2 -> decode_list_to_string(Cs, B1, B2)
+decode_list_to_string(ModeOffset, Padding, [C2 | Cs], B1) ->
+ case b64d(C2, ModeOffset) of
+ ws -> decode_list_to_string(ModeOffset, Padding, Cs, B1);
+ B2 -> decode_list_to_string(ModeOffset, Padding, Cs, B1, B2)
end.
-decode_list_to_string([C3 | Cs], B1, B2) ->
- case b64d(C3) of
- ws -> decode_list_to_string(Cs, B1, B2);
- B3 -> decode_list_to_string(Cs, B1, B2, B3)
+decode_list_to_string(ModeOffset, Padding, [C3 | Cs], B1, B2) ->
+ case b64d(C3, ModeOffset) of
+ ws -> decode_list_to_string(ModeOffset, Padding, Cs, B1, B2);
+ B3 -> decode_list_to_string(ModeOffset, Padding, Cs, B1, B2, B3)
+ end;
+decode_list_to_string(ModeOffset, Padding, [], B1, B2) ->
+ case Padding of
+ true -> missing_padding_error();
+ false -> decode_list_to_string(ModeOffset, Padding, [], B1, B2, eq)
end.
-decode_list_to_string([C4 | Cs], B1, B2, B3) ->
- case b64d(C4) of
+decode_list_to_string(ModeOffset, Padding, [C4 | Cs], B1, B2, B3) ->
+ case b64d(C4, ModeOffset) of
ws ->
- decode_list_to_string(Cs, B1, B2, B3);
+ decode_list_to_string(ModeOffset, Padding, Cs, B1, B2, B3);
eq when B3 =:= eq ->
- only_ws(Cs, binary_to_list(<<B1:6,(B2 bsr 4):2>>));
+ only_ws(ModeOffset, Padding, Cs, binary_to_list(<<B1:6,(B2 bsr 4):2>>));
eq ->
- only_ws(Cs, binary_to_list(<<B1:6,B2:6,(B3 bsr 2):4>>));
+ only_ws(ModeOffset, Padding, Cs, binary_to_list(<<B1:6,B2:6,(B3 bsr 2):4>>));
B4 ->
Bits4x6 = (B1 bsl 18) bor (B2 bsl 12) bor (B3 bsl 6) bor B4,
Octet1 = Bits4x6 bsr 16,
Octet2 = (Bits4x6 bsr 8) band 16#ff,
Octet3 = Bits4x6 band 16#ff,
- [Octet1, Octet2, Octet3 | decode_list_to_string(Cs)]
+ [Octet1, Octet2, Octet3 | decode_list_to_string(ModeOffset, Padding, Cs)]
+ end;
+decode_list_to_string(_ModeOffset, Padding, [], B1, B2, B3) ->
+ case Padding of
+ true -> missing_padding_error();
+ false when B3 =:= eq -> binary_to_list(<<B1:6,(B2 bsr 4):2>>);
+ false -> binary_to_list(<<B1:6,B2:6,(B3 bsr 2):4>>)
end.
-only_ws([], A) ->
+only_ws(_ModeOffset, _Padding, [], A) ->
A;
-only_ws([C | Cs], A) ->
- case b64d(C) of
- ws -> only_ws(Cs, A)
+only_ws(ModeOffset, Padding, [C | Cs], A) ->
+ case b64d(C, ModeOffset) of
+ ws -> only_ws(ModeOffset, Padding, Cs, A)
end.
%%%========================================================================
+%%% Error handling functions
+%%%========================================================================
+
+% always inlined for useful stacktraces when called in tail position
+-compile({inline, missing_padding_error/0}).
+missing_padding_error() ->
+ error(missing_padding, none, [{error_info, #{}}]).
+
+format_error(missing_padding, _) ->
+ #{general => "data to decode is missing final = padding characters, if this is intended, use the `padding => false` option"};
+format_error(_, _) ->
+ #{}.
+
+%%%========================================================================
%%% Internal functions
%%%========================================================================
-%% accessors
--compile({inline, [{b64d, 1}]}).
-%% One-based decode map.
-b64d(X) ->
- element(X,
- {bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %1-15
+%% accessors
+
+get_padding(#{padding := Bool}) when is_boolean(Bool) -> Bool;
+get_padding(#{}) -> true.
+
+get_decoding_offset(#{mode := standard}) -> 1;
+get_decoding_offset(#{mode := urlsafe}) -> 257;
+get_decoding_offset(#{}) -> 1.
+
+-compile({inline, [{b64d, 2}]}).
+b64d(X, Off) ->
+ element(X + Off,
+ {
+ %% standard base64 alphabet (RFC 4648 Section 4)
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %0-15
bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, %16-31
ws,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,62,bad,bad,bad,63, %32-47
- 52,53,54,55,56,57,58,59,60,61,bad,bad,bad,eq,bad,bad, %48-63
+ 52,53,54,55,56,57,58,59,60,61,bad,bad,bad,eq,bad,bad, %48-61
bad,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,
15,16,17,18,19,20,21,22,23,24,25,bad,bad,bad,bad,bad,
bad,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
@@ -460,13 +660,44 @@ b64d(X) ->
bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+
+ %% alternative base64url alphabet (RFC 4648 Section 5)
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %0-15
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, %16-31
+ ws,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,62,bad,bad, %32-47
+ 52,53,54,55,56,57,58,59,60,61,bad,bad,bad,eq,bad,bad, %48-61
+ bad,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,
+ 15,16,17,18,19,20,21,22,23,24,25,bad,bad,bad,bad,63,
+ bad,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
+ 41,42,43,44,45,46,47,48,49,50,51,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad}).
--compile({inline, [{b64e, 1}]}).
-b64e(X) ->
- element(X+1,
- {$A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M, $N,
+get_encoding_offset(#{mode := standard}) -> 1;
+get_encoding_offset(#{mode := urlsafe}) -> 65;
+get_encoding_offset(#{}) -> 1.
+
+-compile({inline, [{b64e, 2}]}).
+b64e(X, Off) ->
+ element(X + Off,
+ {
+ %% standard base64 alphabet (RFC 4648 Section 4)
+ $A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M, $N,
+ $O, $P, $Q, $R, $S, $T, $U, $V, $W, $X, $Y, $Z,
+ $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m, $n,
+ $o, $p, $q, $r, $s, $t, $u, $v, $w, $x, $y, $z,
+ $0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $+, $/,
+
+ %% alternative base64url alphabet (RFC 4648 Section 5)
+ $A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M, $N,
$O, $P, $Q, $R, $S, $T, $U, $V, $W, $X, $Y, $Z,
$a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m, $n,
$o, $p, $q, $r, $s, $t, $u, $v, $w, $x, $y, $z,
- $0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $+, $/}).
+ $0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $-, $_}).
diff --git a/lib/stdlib/src/beam_lib.erl b/lib/stdlib/src/beam_lib.erl
index 5eed3c06b6..14c6d76430 100644
--- a/lib/stdlib/src/beam_lib.erl
+++ b/lib/stdlib/src/beam_lib.erl
@@ -51,6 +51,7 @@
-export([make_crypto_key/2, get_crypto_key/1]). %Utilities used by compiler
-export_type([attrib_entry/0, compinfo_entry/0, labeled_entry/0, label/0]).
+-export_type([chunkid/0]).
-export_type([chnk_rsn/0]).
-import(lists, [append/1, delete/2, foreach/2, keysort/2,
@@ -155,7 +156,7 @@ chunks(File, Chunks, Options) ->
catch Error -> Error end.
-spec all_chunks(beam()) ->
- {'ok', 'beam_lib', [{chunkid(), dataB()}]} | {'error', 'beam_lib', info_rsn()}.
+ {'ok', module(), [{chunkid(), dataB()}]} | {'error', 'beam_lib', info_rsn()}.
all_chunks(File) ->
read_all_chunks(File).
@@ -831,8 +832,7 @@ symbol(_, AT, I1, I2, _I3, _Cnt) ->
{atm(AT, I1), I2}.
atm(AT, N) ->
- [{_N, S}] = ets:lookup(AT, N),
- S.
+ ets:lookup_element(AT, N, 2).
%% AT is updated.
ensure_atoms({empty, AT}, Cs) ->
diff --git a/lib/stdlib/src/binary.erl b/lib/stdlib/src/binary.erl
index f3e2f54215..e587cfe98d 100644
--- a/lib/stdlib/src/binary.erl
+++ b/lib/stdlib/src/binary.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,8 +20,8 @@
-module(binary).
%%
%% Implemented in this module:
--export([replace/3,replace/4,
- encode_hex/1, decode_hex/1]).
+-export([replace/3, replace/4,
+ encode_hex/1, encode_hex/2, decode_hex/1]).
-export_type([cp/0]).
@@ -365,127 +365,112 @@ get_opts_replace(_,_) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Hex encoding functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--define(HEX(X), (hex(X)):16).
--compile({inline,[hex/1]}).
+-compile({inline, [hex/2]}).
-spec encode_hex(Bin) -> Bin2 when
Bin :: binary(),
Bin2 :: <<_:_*16>>.
-encode_hex(Data) when byte_size(Data) rem 8 =:= 0 ->
- << <<?HEX(A),?HEX(B),?HEX(C),?HEX(D),?HEX(E),?HEX(F),?HEX(G),?HEX(H)>> || <<A,B,C,D,E,F,G,H>> <= Data >>;
-encode_hex(Data) when byte_size(Data) rem 7 =:= 0 ->
- << <<?HEX(A),?HEX(B),?HEX(C),?HEX(D),?HEX(E),?HEX(F),?HEX(G)>> || <<A,B,C,D,E,F,G>> <= Data >>;
-encode_hex(Data) when byte_size(Data) rem 6 =:= 0 ->
- << <<?HEX(A),?HEX(B),?HEX(C),?HEX(D),?HEX(E),?HEX(F)>> || <<A,B,C,D,E,F>> <= Data >>;
-encode_hex(Data) when byte_size(Data) rem 5 =:= 0 ->
- << <<?HEX(A),?HEX(B),?HEX(C),?HEX(D),?HEX(E)>> || <<A,B,C,D,E>> <= Data >>;
-encode_hex(Data) when byte_size(Data) rem 4 =:= 0 ->
- << <<?HEX(A),?HEX(B),?HEX(C),?HEX(D)>> || <<A,B,C,D>> <= Data >>;
-encode_hex(Data) when byte_size(Data) rem 3 =:= 0 ->
- << <<?HEX(A),?HEX(B),?HEX(C)>> || <<A,B,C>> <= Data >>;
-encode_hex(Data) when byte_size(Data) rem 2 =:= 0 ->
- << <<?HEX(A),?HEX(B)>> || <<A,B>> <= Data >>;
-encode_hex(Data) when is_binary(Data) ->
- << <<?HEX(N)>> || <<N>> <= Data >>;
+encode_hex(Bin) when is_binary(Bin) ->
+ encode_hex(Bin, uppercase);
encode_hex(Bin) ->
- badarg_with_info([Bin]).
+ error_with_info(badarg, [Bin]).
-hex(X) ->
+-spec encode_hex(Bin, Case) -> Bin2 when
+ Bin :: binary(),
+ Case :: lowercase | uppercase,
+ Bin2 :: <<_:_*16>>.
+encode_hex(Bin, uppercase) when is_binary(Bin) ->
+ encode_hex1(Bin, 1);
+encode_hex(Bin, lowercase) when is_binary(Bin) ->
+ encode_hex1(Bin, 257);
+encode_hex(Bin, Case) ->
+ error_with_info(badarg, [Bin, Case]).
+
+encode_hex1(Data, Offset) ->
+ <<First:(bit_size(Data) div 64)/binary-unit:64, Rest/binary>> = Data,
+ Hex = << <<(hex(A, Offset)):16, (hex(B, Offset)):16, (hex(C, Offset)):16, (hex(D, Offset)):16,
+ (hex(E, Offset)):16, (hex(F, Offset)):16, (hex(G, Offset)):16, (hex(H, Offset)):16>> ||
+ <<A,B,C,D,E,F,G,H>> <= First >>,
+ encode_hex2(Rest, Offset, Hex).
+
+encode_hex2(<<A,Data/binary>>, Offset, Acc) ->
+ encode_hex2(Data, Offset, <<Acc/binary, (hex(A, Offset)):16>>);
+encode_hex2(<<>>, _Offset, Acc) ->
+ Acc.
+
+hex(X, Offset) ->
element(
- X+1, {16#3030, 16#3031, 16#3032, 16#3033, 16#3034, 16#3035, 16#3036, 16#3037, 16#3038, 16#3039, 16#3041, 16#3042, 16#3043, 16#3044, 16#3045, 16#3046,
- 16#3130, 16#3131, 16#3132, 16#3133, 16#3134, 16#3135, 16#3136, 16#3137, 16#3138, 16#3139, 16#3141, 16#3142, 16#3143, 16#3144, 16#3145, 16#3146,
- 16#3230, 16#3231, 16#3232, 16#3233, 16#3234, 16#3235, 16#3236, 16#3237, 16#3238, 16#3239, 16#3241, 16#3242, 16#3243, 16#3244, 16#3245, 16#3246,
- 16#3330, 16#3331, 16#3332, 16#3333, 16#3334, 16#3335, 16#3336, 16#3337, 16#3338, 16#3339, 16#3341, 16#3342, 16#3343, 16#3344, 16#3345, 16#3346,
- 16#3430, 16#3431, 16#3432, 16#3433, 16#3434, 16#3435, 16#3436, 16#3437, 16#3438, 16#3439, 16#3441, 16#3442, 16#3443, 16#3444, 16#3445, 16#3446,
- 16#3530, 16#3531, 16#3532, 16#3533, 16#3534, 16#3535, 16#3536, 16#3537, 16#3538, 16#3539, 16#3541, 16#3542, 16#3543, 16#3544, 16#3545, 16#3546,
- 16#3630, 16#3631, 16#3632, 16#3633, 16#3634, 16#3635, 16#3636, 16#3637, 16#3638, 16#3639, 16#3641, 16#3642, 16#3643, 16#3644, 16#3645, 16#3646,
- 16#3730, 16#3731, 16#3732, 16#3733, 16#3734, 16#3735, 16#3736, 16#3737, 16#3738, 16#3739, 16#3741, 16#3742, 16#3743, 16#3744, 16#3745, 16#3746,
- 16#3830, 16#3831, 16#3832, 16#3833, 16#3834, 16#3835, 16#3836, 16#3837, 16#3838, 16#3839, 16#3841, 16#3842, 16#3843, 16#3844, 16#3845, 16#3846,
- 16#3930, 16#3931, 16#3932, 16#3933, 16#3934, 16#3935, 16#3936, 16#3937, 16#3938, 16#3939, 16#3941, 16#3942, 16#3943, 16#3944, 16#3945, 16#3946,
- 16#4130, 16#4131, 16#4132, 16#4133, 16#4134, 16#4135, 16#4136, 16#4137, 16#4138, 16#4139, 16#4141, 16#4142, 16#4143, 16#4144, 16#4145, 16#4146,
- 16#4230, 16#4231, 16#4232, 16#4233, 16#4234, 16#4235, 16#4236, 16#4237, 16#4238, 16#4239, 16#4241, 16#4242, 16#4243, 16#4244, 16#4245, 16#4246,
- 16#4330, 16#4331, 16#4332, 16#4333, 16#4334, 16#4335, 16#4336, 16#4337, 16#4338, 16#4339, 16#4341, 16#4342, 16#4343, 16#4344, 16#4345, 16#4346,
- 16#4430, 16#4431, 16#4432, 16#4433, 16#4434, 16#4435, 16#4436, 16#4437, 16#4438, 16#4439, 16#4441, 16#4442, 16#4443, 16#4444, 16#4445, 16#4446,
- 16#4530, 16#4531, 16#4532, 16#4533, 16#4534, 16#4535, 16#4536, 16#4537, 16#4538, 16#4539, 16#4541, 16#4542, 16#4543, 16#4544, 16#4545, 16#4546,
- 16#4630, 16#4631, 16#4632, 16#4633, 16#4634, 16#4635, 16#4636, 16#4637, 16#4638, 16#4639, 16#4641, 16#4642, 16#4643, 16#4644, 16#4645, 16#4646}).
-
+ X + Offset, {
+ %% Used for Uppercase
+ 16#3030, 16#3031, 16#3032, 16#3033, 16#3034, 16#3035, 16#3036, 16#3037, 16#3038, 16#3039, 16#3041, 16#3042, 16#3043, 16#3044, 16#3045, 16#3046,
+ 16#3130, 16#3131, 16#3132, 16#3133, 16#3134, 16#3135, 16#3136, 16#3137, 16#3138, 16#3139, 16#3141, 16#3142, 16#3143, 16#3144, 16#3145, 16#3146,
+ 16#3230, 16#3231, 16#3232, 16#3233, 16#3234, 16#3235, 16#3236, 16#3237, 16#3238, 16#3239, 16#3241, 16#3242, 16#3243, 16#3244, 16#3245, 16#3246,
+ 16#3330, 16#3331, 16#3332, 16#3333, 16#3334, 16#3335, 16#3336, 16#3337, 16#3338, 16#3339, 16#3341, 16#3342, 16#3343, 16#3344, 16#3345, 16#3346,
+ 16#3430, 16#3431, 16#3432, 16#3433, 16#3434, 16#3435, 16#3436, 16#3437, 16#3438, 16#3439, 16#3441, 16#3442, 16#3443, 16#3444, 16#3445, 16#3446,
+ 16#3530, 16#3531, 16#3532, 16#3533, 16#3534, 16#3535, 16#3536, 16#3537, 16#3538, 16#3539, 16#3541, 16#3542, 16#3543, 16#3544, 16#3545, 16#3546,
+ 16#3630, 16#3631, 16#3632, 16#3633, 16#3634, 16#3635, 16#3636, 16#3637, 16#3638, 16#3639, 16#3641, 16#3642, 16#3643, 16#3644, 16#3645, 16#3646,
+ 16#3730, 16#3731, 16#3732, 16#3733, 16#3734, 16#3735, 16#3736, 16#3737, 16#3738, 16#3739, 16#3741, 16#3742, 16#3743, 16#3744, 16#3745, 16#3746,
+ 16#3830, 16#3831, 16#3832, 16#3833, 16#3834, 16#3835, 16#3836, 16#3837, 16#3838, 16#3839, 16#3841, 16#3842, 16#3843, 16#3844, 16#3845, 16#3846,
+ 16#3930, 16#3931, 16#3932, 16#3933, 16#3934, 16#3935, 16#3936, 16#3937, 16#3938, 16#3939, 16#3941, 16#3942, 16#3943, 16#3944, 16#3945, 16#3946,
+ 16#4130, 16#4131, 16#4132, 16#4133, 16#4134, 16#4135, 16#4136, 16#4137, 16#4138, 16#4139, 16#4141, 16#4142, 16#4143, 16#4144, 16#4145, 16#4146,
+ 16#4230, 16#4231, 16#4232, 16#4233, 16#4234, 16#4235, 16#4236, 16#4237, 16#4238, 16#4239, 16#4241, 16#4242, 16#4243, 16#4244, 16#4245, 16#4246,
+ 16#4330, 16#4331, 16#4332, 16#4333, 16#4334, 16#4335, 16#4336, 16#4337, 16#4338, 16#4339, 16#4341, 16#4342, 16#4343, 16#4344, 16#4345, 16#4346,
+ 16#4430, 16#4431, 16#4432, 16#4433, 16#4434, 16#4435, 16#4436, 16#4437, 16#4438, 16#4439, 16#4441, 16#4442, 16#4443, 16#4444, 16#4445, 16#4446,
+ 16#4530, 16#4531, 16#4532, 16#4533, 16#4534, 16#4535, 16#4536, 16#4537, 16#4538, 16#4539, 16#4541, 16#4542, 16#4543, 16#4544, 16#4545, 16#4546,
+ 16#4630, 16#4631, 16#4632, 16#4633, 16#4634, 16#4635, 16#4636, 16#4637, 16#4638, 16#4639, 16#4641, 16#4642, 16#4643, 16#4644, 16#4645, 16#4646,
+ %% Used for Lowercase
+ 16#3030, 16#3031, 16#3032, 16#3033, 16#3034, 16#3035, 16#3036, 16#3037, 16#3038, 16#3039, 16#3061, 16#3062, 16#3063, 16#3064, 16#3065, 16#3066,
+ 16#3130, 16#3131, 16#3132, 16#3133, 16#3134, 16#3135, 16#3136, 16#3137, 16#3138, 16#3139, 16#3161, 16#3162, 16#3163, 16#3164, 16#3165, 16#3166,
+ 16#3230, 16#3231, 16#3232, 16#3233, 16#3234, 16#3235, 16#3236, 16#3237, 16#3238, 16#3239, 16#3261, 16#3262, 16#3263, 16#3264, 16#3265, 16#3266,
+ 16#3330, 16#3331, 16#3332, 16#3333, 16#3334, 16#3335, 16#3336, 16#3337, 16#3338, 16#3339, 16#3361, 16#3362, 16#3363, 16#3364, 16#3365, 16#3366,
+ 16#3430, 16#3431, 16#3432, 16#3433, 16#3434, 16#3435, 16#3436, 16#3437, 16#3438, 16#3439, 16#3461, 16#3462, 16#3463, 16#3464, 16#3465, 16#3466,
+ 16#3530, 16#3531, 16#3532, 16#3533, 16#3534, 16#3535, 16#3536, 16#3537, 16#3538, 16#3539, 16#3561, 16#3562, 16#3563, 16#3564, 16#3565, 16#3566,
+ 16#3630, 16#3631, 16#3632, 16#3633, 16#3634, 16#3635, 16#3636, 16#3637, 16#3638, 16#3639, 16#3661, 16#3662, 16#3663, 16#3664, 16#3665, 16#3666,
+ 16#3730, 16#3731, 16#3732, 16#3733, 16#3734, 16#3735, 16#3736, 16#3737, 16#3738, 16#3739, 16#3761, 16#3762, 16#3763, 16#3764, 16#3765, 16#3766,
+ 16#3830, 16#3831, 16#3832, 16#3833, 16#3834, 16#3835, 16#3836, 16#3837, 16#3838, 16#3839, 16#3861, 16#3862, 16#3863, 16#3864, 16#3865, 16#3866,
+ 16#3930, 16#3931, 16#3932, 16#3933, 16#3934, 16#3935, 16#3936, 16#3937, 16#3938, 16#3939, 16#3961, 16#3962, 16#3963, 16#3964, 16#3965, 16#3966,
+ 16#6130, 16#6131, 16#6132, 16#6133, 16#6134, 16#6135, 16#6136, 16#6137, 16#6138, 16#6139, 16#6161, 16#6162, 16#6163, 16#6164, 16#6165, 16#6166,
+ 16#6230, 16#6231, 16#6232, 16#6233, 16#6234, 16#6235, 16#6236, 16#6237, 16#6238, 16#6239, 16#6261, 16#6262, 16#6263, 16#6264, 16#6265, 16#6266,
+ 16#6330, 16#6331, 16#6332, 16#6333, 16#6334, 16#6335, 16#6336, 16#6337, 16#6338, 16#6339, 16#6361, 16#6362, 16#6363, 16#6364, 16#6365, 16#6366,
+ 16#6430, 16#6431, 16#6432, 16#6433, 16#6434, 16#6435, 16#6436, 16#6437, 16#6438, 16#6439, 16#6461, 16#6462, 16#6463, 16#6464, 16#6465, 16#6466,
+ 16#6530, 16#6531, 16#6532, 16#6533, 16#6534, 16#6535, 16#6536, 16#6537, 16#6538, 16#6539, 16#6561, 16#6562, 16#6563, 16#6564, 16#6565, 16#6566,
+ 16#6630, 16#6631, 16#6632, 16#6633, 16#6634, 16#6635, 16#6636, 16#6637, 16#6638, 16#6639, 16#6661, 16#6662, 16#6663, 16#6664, 16#6665, 16#6666}).
+
+-compile({inline, [unhex/1]}).
-spec decode_hex(Bin) -> Bin2 when
Bin :: <<_:_*16>>,
Bin2 :: binary().
-decode_hex(Bin) when byte_size(Bin) rem 2 =:= 0 ->
- << <<(unhex(Int))>> || <<Int:16>> <= Bin >>;
-decode_hex(Bin) ->
- badarg_with_info([Bin]).
-
-%% This function pattern-matches on the hexadecimal representation of a pair of characters
-%% for example, 16#3030 is matching on the integers <<48, 48>>, which is ascii for <<"00">>
-unhex(16#3030) -> 0; unhex(16#3031) -> 1; unhex(16#3032) -> 2; unhex(16#3033) -> 3; unhex(16#3034) -> 4; unhex(16#3035) -> 5; unhex(16#3036) -> 6; unhex(16#3037) -> 7; unhex(16#3038) -> 8; unhex(16#3039) -> 9;
-unhex(16#3041) -> 10; unhex(16#3042) -> 11; unhex(16#3043) -> 12; unhex(16#3044) -> 13; unhex(16#3045) -> 14; unhex(16#3046) -> 15;
-unhex(16#3061) -> 10; unhex(16#3062) -> 11; unhex(16#3063) -> 12; unhex(16#3064) -> 13; unhex(16#3065) -> 14; unhex(16#3066) -> 15;
-unhex(16#3130) -> 16; unhex(16#3131) -> 17; unhex(16#3132) -> 18; unhex(16#3133) -> 19; unhex(16#3134) -> 20; unhex(16#3135) -> 21; unhex(16#3136) -> 22; unhex(16#3137) -> 23; unhex(16#3138) -> 24; unhex(16#3139) -> 25;
-unhex(16#3141) -> 26; unhex(16#3142) -> 27; unhex(16#3143) -> 28; unhex(16#3144) -> 29; unhex(16#3145) -> 30; unhex(16#3146) -> 31;
-unhex(16#3161) -> 26; unhex(16#3162) -> 27; unhex(16#3163) -> 28; unhex(16#3164) -> 29; unhex(16#3165) -> 30; unhex(16#3166) -> 31;
-unhex(16#3230) -> 32; unhex(16#3231) -> 33; unhex(16#3232) -> 34; unhex(16#3233) -> 35; unhex(16#3234) -> 36; unhex(16#3235) -> 37; unhex(16#3236) -> 38; unhex(16#3237) -> 39; unhex(16#3238) -> 40; unhex(16#3239) -> 41;
-unhex(16#3241) -> 42; unhex(16#3242) -> 43; unhex(16#3243) -> 44; unhex(16#3244) -> 45; unhex(16#3245) -> 46; unhex(16#3246) -> 47;
-unhex(16#3261) -> 42; unhex(16#3262) -> 43; unhex(16#3263) -> 44; unhex(16#3264) -> 45; unhex(16#3265) -> 46; unhex(16#3266) -> 47;
-unhex(16#3330) -> 48; unhex(16#3331) -> 49; unhex(16#3332) -> 50; unhex(16#3333) -> 51; unhex(16#3334) -> 52; unhex(16#3335) -> 53; unhex(16#3336) -> 54; unhex(16#3337) -> 55; unhex(16#3338) -> 56; unhex(16#3339) -> 57;
-unhex(16#3341) -> 58; unhex(16#3342) -> 59; unhex(16#3343) -> 60; unhex(16#3344) -> 61; unhex(16#3345) -> 62; unhex(16#3346) -> 63;
-unhex(16#3361) -> 58; unhex(16#3362) -> 59; unhex(16#3363) -> 60; unhex(16#3364) -> 61; unhex(16#3365) -> 62; unhex(16#3366) -> 63;
-unhex(16#3430) -> 64; unhex(16#3431) -> 65; unhex(16#3432) -> 66; unhex(16#3433) -> 67; unhex(16#3434) -> 68; unhex(16#3435) -> 69; unhex(16#3436) -> 70; unhex(16#3437) -> 71; unhex(16#3438) -> 72; unhex(16#3439) -> 73;
-unhex(16#3441) -> 74; unhex(16#3442) -> 75; unhex(16#3443) -> 76; unhex(16#3444) -> 77; unhex(16#3445) -> 78; unhex(16#3446) -> 79;
-unhex(16#3461) -> 74; unhex(16#3462) -> 75; unhex(16#3463) -> 76; unhex(16#3464) -> 77; unhex(16#3465) -> 78; unhex(16#3466) -> 79;
-unhex(16#3530) -> 80; unhex(16#3531) -> 81; unhex(16#3532) -> 82; unhex(16#3533) -> 83; unhex(16#3534) -> 84; unhex(16#3535) -> 85; unhex(16#3536) -> 86; unhex(16#3537) -> 87; unhex(16#3538) -> 88; unhex(16#3539) -> 89;
-unhex(16#3541) -> 90; unhex(16#3542) -> 91; unhex(16#3543) -> 92; unhex(16#3544) -> 93; unhex(16#3545) -> 94; unhex(16#3546) -> 95;
-unhex(16#3561) -> 90; unhex(16#3562) -> 91; unhex(16#3563) -> 92; unhex(16#3564) -> 93; unhex(16#3565) -> 94; unhex(16#3566) -> 95;
-unhex(16#3630) -> 96; unhex(16#3631) -> 97; unhex(16#3632) -> 98; unhex(16#3633) -> 99; unhex(16#3634) -> 100; unhex(16#3635) -> 101; unhex(16#3636) -> 102; unhex(16#3637) -> 103; unhex(16#3638) -> 104; unhex(16#3639) -> 105;
-unhex(16#3641) -> 106; unhex(16#3642) -> 107; unhex(16#3643) -> 108; unhex(16#3644) -> 109; unhex(16#3645) -> 110; unhex(16#3646) -> 111;
-unhex(16#3661) -> 106; unhex(16#3662) -> 107; unhex(16#3663) -> 108; unhex(16#3664) -> 109; unhex(16#3665) -> 110; unhex(16#3666) -> 111;
-unhex(16#3730) -> 112; unhex(16#3731) -> 113; unhex(16#3732) -> 114; unhex(16#3733) -> 115; unhex(16#3734) -> 116; unhex(16#3735) -> 117; unhex(16#3736) -> 118; unhex(16#3737) -> 119; unhex(16#3738) -> 120; unhex(16#3739) -> 121;
-unhex(16#3741) -> 122; unhex(16#3742) -> 123; unhex(16#3743) -> 124; unhex(16#3744) -> 125; unhex(16#3745) -> 126; unhex(16#3746) -> 127;
-unhex(16#3761) -> 122; unhex(16#3762) -> 123; unhex(16#3763) -> 124; unhex(16#3764) -> 125; unhex(16#3765) -> 126; unhex(16#3766) -> 127;
-unhex(16#3830) -> 128; unhex(16#3831) -> 129; unhex(16#3832) -> 130; unhex(16#3833) -> 131; unhex(16#3834) -> 132; unhex(16#3835) -> 133; unhex(16#3836) -> 134; unhex(16#3837) -> 135; unhex(16#3838) -> 136; unhex(16#3839) -> 137;
-unhex(16#3841) -> 138; unhex(16#3842) -> 139; unhex(16#3843) -> 140; unhex(16#3844) -> 141; unhex(16#3845) -> 142; unhex(16#3846) -> 143;
-unhex(16#3861) -> 138; unhex(16#3862) -> 139; unhex(16#3863) -> 140; unhex(16#3864) -> 141; unhex(16#3865) -> 142; unhex(16#3866) -> 143;
-unhex(16#3930) -> 144; unhex(16#3931) -> 145; unhex(16#3932) -> 146; unhex(16#3933) -> 147; unhex(16#3934) -> 148; unhex(16#3935) -> 149; unhex(16#3936) -> 150; unhex(16#3937) -> 151; unhex(16#3938) -> 152; unhex(16#3939) -> 153;
-unhex(16#3941) -> 154; unhex(16#3942) -> 155; unhex(16#3943) -> 156; unhex(16#3944) -> 157; unhex(16#3945) -> 158; unhex(16#3946) -> 159;
-unhex(16#3961) -> 154; unhex(16#3962) -> 155; unhex(16#3963) -> 156; unhex(16#3964) -> 157; unhex(16#3965) -> 158; unhex(16#3966) -> 159;
-unhex(16#4130) -> 160; unhex(16#4131) -> 161; unhex(16#4132) -> 162; unhex(16#4133) -> 163; unhex(16#4134) -> 164; unhex(16#4135) -> 165; unhex(16#4136) -> 166; unhex(16#4137) -> 167; unhex(16#4138) -> 168; unhex(16#4139) -> 169;
-unhex(16#4141) -> 170; unhex(16#4142) -> 171; unhex(16#4143) -> 172; unhex(16#4144) -> 173; unhex(16#4145) -> 174; unhex(16#4146) -> 175;
-unhex(16#4161) -> 170; unhex(16#4162) -> 171; unhex(16#4163) -> 172; unhex(16#4164) -> 173; unhex(16#4165) -> 174; unhex(16#4166) -> 175;
-unhex(16#4230) -> 176; unhex(16#4231) -> 177; unhex(16#4232) -> 178; unhex(16#4233) -> 179; unhex(16#4234) -> 180; unhex(16#4235) -> 181; unhex(16#4236) -> 182; unhex(16#4237) -> 183; unhex(16#4238) -> 184; unhex(16#4239) -> 185;
-unhex(16#4241) -> 186; unhex(16#4242) -> 187; unhex(16#4243) -> 188; unhex(16#4244) -> 189; unhex(16#4245) -> 190; unhex(16#4246) -> 191;
-unhex(16#4261) -> 186; unhex(16#4262) -> 187; unhex(16#4263) -> 188; unhex(16#4264) -> 189; unhex(16#4265) -> 190; unhex(16#4266) -> 191;
-unhex(16#4330) -> 192; unhex(16#4331) -> 193; unhex(16#4332) -> 194; unhex(16#4333) -> 195; unhex(16#4334) -> 196; unhex(16#4335) -> 197; unhex(16#4336) -> 198; unhex(16#4337) -> 199; unhex(16#4338) -> 200; unhex(16#4339) -> 201;
-unhex(16#4341) -> 202; unhex(16#4342) -> 203; unhex(16#4343) -> 204; unhex(16#4344) -> 205; unhex(16#4345) -> 206; unhex(16#4346) -> 207;
-unhex(16#4361) -> 202; unhex(16#4362) -> 203; unhex(16#4363) -> 204; unhex(16#4364) -> 205; unhex(16#4365) -> 206; unhex(16#4366) -> 207;
-unhex(16#4430) -> 208; unhex(16#4431) -> 209; unhex(16#4432) -> 210; unhex(16#4433) -> 211; unhex(16#4434) -> 212; unhex(16#4435) -> 213; unhex(16#4436) -> 214; unhex(16#4437) -> 215; unhex(16#4438) -> 216; unhex(16#4439) -> 217;
-unhex(16#4441) -> 218; unhex(16#4442) -> 219; unhex(16#4443) -> 220; unhex(16#4444) -> 221; unhex(16#4445) -> 222; unhex(16#4446) -> 223;
-unhex(16#4461) -> 218; unhex(16#4462) -> 219; unhex(16#4463) -> 220; unhex(16#4464) -> 221; unhex(16#4465) -> 222; unhex(16#4466) -> 223;
-unhex(16#4530) -> 224; unhex(16#4531) -> 225; unhex(16#4532) -> 226; unhex(16#4533) -> 227; unhex(16#4534) -> 228; unhex(16#4535) -> 229; unhex(16#4536) -> 230; unhex(16#4537) -> 231; unhex(16#4538) -> 232; unhex(16#4539) -> 233;
-unhex(16#4541) -> 234; unhex(16#4542) -> 235; unhex(16#4543) -> 236; unhex(16#4544) -> 237; unhex(16#4545) -> 238; unhex(16#4546) -> 239;
-unhex(16#4561) -> 234; unhex(16#4562) -> 235; unhex(16#4563) -> 236; unhex(16#4564) -> 237; unhex(16#4565) -> 238; unhex(16#4566) -> 239;
-unhex(16#4630) -> 240; unhex(16#4631) -> 241; unhex(16#4632) -> 242; unhex(16#4633) -> 243; unhex(16#4634) -> 244; unhex(16#4635) -> 245; unhex(16#4636) -> 246; unhex(16#4637) -> 247; unhex(16#4638) -> 248; unhex(16#4639) -> 249;
-unhex(16#4641) -> 250; unhex(16#4642) -> 251; unhex(16#4643) -> 252; unhex(16#4644) -> 253; unhex(16#4645) -> 254; unhex(16#4646) -> 255;
-unhex(16#4661) -> 250; unhex(16#4662) -> 251; unhex(16#4663) -> 252; unhex(16#4664) -> 253; unhex(16#4665) -> 254; unhex(16#4666) -> 255;
-unhex(16#6130) -> 160; unhex(16#6131) -> 161; unhex(16#6132) -> 162; unhex(16#6133) -> 163; unhex(16#6134) -> 164; unhex(16#6135) -> 165; unhex(16#6136) -> 166; unhex(16#6137) -> 167; unhex(16#6138) -> 168; unhex(16#6139) -> 169;
-unhex(16#6141) -> 170; unhex(16#6142) -> 171; unhex(16#6143) -> 172; unhex(16#6144) -> 173; unhex(16#6145) -> 174; unhex(16#6146) -> 175;
-unhex(16#6161) -> 170; unhex(16#6162) -> 171; unhex(16#6163) -> 172; unhex(16#6164) -> 173; unhex(16#6165) -> 174; unhex(16#6166) -> 175;
-unhex(16#6230) -> 176; unhex(16#6231) -> 177; unhex(16#6232) -> 178; unhex(16#6233) -> 179; unhex(16#6234) -> 180; unhex(16#6235) -> 181; unhex(16#6236) -> 182; unhex(16#6237) -> 183; unhex(16#6238) -> 184; unhex(16#6239) -> 185;
-unhex(16#6241) -> 186; unhex(16#6242) -> 187; unhex(16#6243) -> 188; unhex(16#6244) -> 189; unhex(16#6245) -> 190; unhex(16#6246) -> 191;
-unhex(16#6261) -> 186; unhex(16#6262) -> 187; unhex(16#6263) -> 188; unhex(16#6264) -> 189; unhex(16#6265) -> 190; unhex(16#6266) -> 191;
-unhex(16#6330) -> 192; unhex(16#6331) -> 193; unhex(16#6332) -> 194; unhex(16#6333) -> 195; unhex(16#6334) -> 196; unhex(16#6335) -> 197; unhex(16#6336) -> 198; unhex(16#6337) -> 199; unhex(16#6338) -> 200; unhex(16#6339) -> 201;
-unhex(16#6341) -> 202; unhex(16#6342) -> 203; unhex(16#6343) -> 204; unhex(16#6344) -> 205; unhex(16#6345) -> 206; unhex(16#6346) -> 207;
-unhex(16#6361) -> 202; unhex(16#6362) -> 203; unhex(16#6363) -> 204; unhex(16#6364) -> 205; unhex(16#6365) -> 206; unhex(16#6366) -> 207;
-unhex(16#6430) -> 208; unhex(16#6431) -> 209; unhex(16#6432) -> 210; unhex(16#6433) -> 211; unhex(16#6434) -> 212; unhex(16#6435) -> 213; unhex(16#6436) -> 214; unhex(16#6437) -> 215; unhex(16#6438) -> 216; unhex(16#6439) -> 217;
-unhex(16#6441) -> 218; unhex(16#6442) -> 219; unhex(16#6443) -> 220; unhex(16#6444) -> 221; unhex(16#6445) -> 222; unhex(16#6446) -> 223;
-unhex(16#6461) -> 218; unhex(16#6462) -> 219; unhex(16#6463) -> 220; unhex(16#6464) -> 221; unhex(16#6465) -> 222; unhex(16#6466) -> 223;
-unhex(16#6530) -> 224; unhex(16#6531) -> 225; unhex(16#6532) -> 226; unhex(16#6533) -> 227; unhex(16#6534) -> 228; unhex(16#6535) -> 229; unhex(16#6536) -> 230; unhex(16#6537) -> 231; unhex(16#6538) -> 232; unhex(16#6539) -> 233;
-unhex(16#6541) -> 234; unhex(16#6542) -> 235; unhex(16#6543) -> 236; unhex(16#6544) -> 237; unhex(16#6545) -> 238; unhex(16#6546) -> 239;
-unhex(16#6561) -> 234; unhex(16#6562) -> 235; unhex(16#6563) -> 236; unhex(16#6564) -> 237; unhex(16#6565) -> 238; unhex(16#6566) -> 239;
-unhex(16#6630) -> 240; unhex(16#6631) -> 241; unhex(16#6632) -> 242; unhex(16#6633) -> 243; unhex(16#6634) -> 244; unhex(16#6635) -> 245; unhex(16#6636) -> 246; unhex(16#6637) -> 247; unhex(16#6638) -> 248; unhex(16#6639) -> 249;
-unhex(16#6641) -> 250; unhex(16#6642) -> 251; unhex(16#6643) -> 252; unhex(16#6644) -> 253; unhex(16#6645) -> 254; unhex(16#6646) -> 255;
-unhex(16#6661) -> 250; unhex(16#6662) -> 251; unhex(16#6663) -> 252; unhex(16#6664) -> 253; unhex(16#6665) -> 254; unhex(16#6666) -> 255;
-unhex(Char) ->
- badarg_with_info([<<Char:16>>]).
+decode_hex(Data) when byte_size(Data) rem 2 =:= 0 ->
+ try
+ decode_hex1(Data)
+ catch
+ error:badarg ->
+ badarg_with_info([Data])
+ end;
+decode_hex(Data) ->
+ badarg_with_info([Data]).
+
+decode_hex1(Data) ->
+ <<First:(byte_size(Data) div 8)/binary-unit:64, Rest/binary>> = Data,
+ Bin = << <<(unhex(A)):4, (unhex(B)):4, (unhex(C)):4, (unhex(D)):4,
+ (unhex(E)):4, (unhex(F)):4, (unhex(G)):4, (unhex(H)):4>> ||
+ <<A,B,C,D,E,F,G,H>> <= First >>,
+ decode_hex2(Rest, Bin).
+
+decode_hex2(<<A,Data/binary>>, Acc) ->
+ decode_hex2(Data, <<Acc/binary-unit:4, (unhex(A)):4>>);
+decode_hex2(<<>>, Acc) ->
+ Acc.
+
+unhex(X) ->
+ element(X,
+ {nonono, no, no, no, no, no, no, no, no, no, no, no, no, no, no, %1
+ no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, %16
+ no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, %32
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, no, no, no, no, no, no, %48
+ no, 10, 11, 12, 13, 14, 15, no, no, no, no, no, no, no, no, no, %64
+ no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, %80
+ no, 10, 11, 12, 13, 14, 15, no, no, no, no, no, no, no, no, no %96
+ }).
badarg_with_cause(Args, Cause) ->
erlang:error(badarg, Args, [{error_info, #{module => erl_stdlib_errors,
diff --git a/lib/stdlib/src/dets.erl b/lib/stdlib/src/dets.erl
index 640ad7a81c..133d209ae1 100644
--- a/lib/stdlib/src/dets.erl
+++ b/lib/stdlib/src/dets.erl
@@ -93,7 +93,8 @@
tab_name/0]).
-compile({inline, [{einval,2},{badarg,2},{undefined,1},
- {badarg_exit,2},{lookup_reply,2}]}).
+ {badarg_exit,2},{lookup_reply,2},
+ {pidof,1},{resp,2}]}).
-include_lib("kernel/include/file.hrl").
@@ -1237,16 +1238,25 @@ treq(Tab, R) ->
req(Proc, R) ->
Ref = erlang:monitor(process, Proc),
- Proc ! ?DETS_CALL(self(), R),
+ Proc ! ?DETS_CALL({self(), Ref}, R),
receive
{'DOWN', Ref, process, Proc, _Info} ->
badarg;
- {Proc, Reply} ->
+ {Ref, Reply} ->
erlang:demonitor(Ref, [flush]),
Reply
end.
%% Inlined.
+pidof({Pid, _Tag}) ->
+ Pid.
+
+%% Inlined.
+resp({Pid, Tag} = _From, Message) ->
+ Pid ! {Tag, Message},
+ ok.
+
+%% Inlined.
einval({error, {file_error, _, einval}}, A) ->
erlang:error(badarg, A);
einval({error, {file_error, _, badarg}}, A) ->
@@ -1398,7 +1408,7 @@ apply_op(Op, From, Head, N) ->
true ->
err({error, incompatible_arguments})
end,
- From ! {self(), Res},
+ resp(From, Res),
ok;
auto_save ->
case Head#head.update_mode of
@@ -1419,7 +1429,7 @@ apply_op(Op, From, Head, N) ->
{0, Head}
end;
close ->
- From ! {self(), fclose(Head)},
+ resp(From, fclose(Head)),
_NewHead = unlink_fixing_procs(Head),
?PROFILE(ep:done()),
exit(normal);
@@ -1427,25 +1437,25 @@ apply_op(Op, From, Head, N) ->
%% Used from dets_server when Pid has closed the table,
%% but the table is still opened by some process.
NewHead = remove_fix(Head, Pid, close),
- From ! {self(), status(NewHead)},
+ resp(From, status(NewHead)),
NewHead;
{corrupt, Reason} ->
{H2, Error} = dets_utils:corrupt_reason(Head, Reason),
- From ! {self(), Error},
+ resp(From, Error),
H2;
{delayed_write, WrTime} ->
delayed_write(Head, WrTime);
info ->
{H2, Res} = finfo(Head),
- From ! {self(), Res},
+ resp(From, Res),
H2;
{info, Tag} ->
{H2, Res} = finfo(Head, Tag),
- From ! {self(), Res},
+ resp(From, Res),
H2;
{is_compatible_bchunk_format, Term} ->
Res = test_bchunk_format(Head, Term),
- From ! {self(), Res},
+ resp(From, Res),
ok;
{internal_open, Ref, Args} ->
do_internal_open(Head#head.parent, Head#head.server, From,
@@ -1462,27 +1472,27 @@ apply_op(Op, From, Head, N) ->
end;
{set_verbose, What} ->
set_verbose(What),
- From ! {self(), ok},
+ resp(From, ok),
ok;
{where, Object} ->
{H2, Res} = where_is_object(Head, Object),
- From ! {self(), Res},
+ resp(From, Res),
H2;
_Message when element(1, Head#head.update_mode) =:= error ->
- From ! {self(), status(Head)},
+ resp(From, status(Head)),
ok;
%% The following messages assume that the status of the table is OK.
{bchunk_init, Tab} ->
{H2, Res} = do_bchunk_init(Head, Tab),
- From ! {self(), Res},
+ resp(From, Res),
H2;
{bchunk, State} ->
{H2, Res} = do_bchunk(Head, State),
- From ! {self(), Res},
+ resp(From, Res),
H2;
delete_all_objects ->
{H2, Res} = fdelete_all_objects(Head),
- From ! {self(), Res},
+ resp(From, Res),
erlang:garbage_collect(),
{0, H2};
{delete_key, _Keys} when Head#head.update_mode =:= dirty ->
@@ -1492,16 +1502,16 @@ apply_op(Op, From, Head, N) ->
true ->
stream_op(Op, From, [], Head, N);
false ->
- From ! {self(), badarg},
+ resp(From, badarg),
ok
end;
first ->
{H2, Res} = ffirst(Head),
- From ! {self(), Res},
+ resp(From, Res),
H2;
{initialize, InitFun, Format, MinNoSlots} ->
{H2, Res} = finit(Head, InitFun, Format, MinNoSlots),
- From ! {self(), Res},
+ resp(From, Res),
erlang:garbage_collect(),
H2;
{insert, Objs} when Head#head.update_mode =:= dirty ->
@@ -1509,12 +1519,12 @@ apply_op(Op, From, Head, N) ->
true ->
stream_op(Op, From, [], Head, N);
false ->
- From ! {self(), badarg},
+ resp(From, badarg),
ok
end;
{insert_new, Objs} when Head#head.update_mode =:= dirty ->
{H2, Res} = finsert_new(Head, Objs),
- From ! {self(), Res},
+ resp(From, Res),
{N + 1, H2};
{lookup_keys, _Keys} ->
stream_op(Op, From, [], Head, N);
@@ -1523,48 +1533,48 @@ apply_op(Op, From, Head, N) ->
H2 = case Res of
{cont,_} -> H1;
_ when Safe =:= no_safe-> H1;
- _ when Safe =:= safe -> do_safe_fixtable(H1, From, false)
+ _ when Safe =:= safe -> do_safe_fixtable(H1, pidof(From), false)
end,
- From ! {self(), Res},
+ resp(From, Res),
H2;
{match, MP, Spec, NObjs, Safe} ->
{H2, Res} = fmatch(Head, MP, Spec, NObjs, Safe, From),
- From ! {self(), Res},
+ resp(From, Res),
H2;
{member, _Key} = Op ->
stream_op(Op, From, [], Head, N);
{next, Key} ->
{H2, Res} = fnext(Head, Key),
- From ! {self(), Res},
+ resp(From, Res),
H2;
{match_delete, State} when Head#head.update_mode =:= dirty ->
{H1, Res} = fmatch_delete(Head, State),
H2 = case Res of
{cont,_S,_N} -> H1;
- _ -> do_safe_fixtable(H1, From, false)
+ _ -> do_safe_fixtable(H1, pidof(From), false)
end,
- From ! {self(), Res},
+ resp(From, Res),
{N + 1, H2};
{match_delete_init, MP, Spec} when Head#head.update_mode =:= dirty ->
{H2, Res} = fmatch_delete_init(Head, MP, Spec, From),
- From ! {self(), Res},
+ resp(From, Res),
{N + 1, H2};
{safe_fixtable, Bool} ->
- NewHead = do_safe_fixtable(Head, From, Bool),
- From ! {self(), ok},
+ NewHead = do_safe_fixtable(Head, pidof(From), Bool),
+ resp(From, ok),
NewHead;
{slot, Slot} ->
{H2, Res} = fslot(Head, Slot),
- From ! {self(), Res},
+ resp(From, Res),
H2;
sync ->
{NewHead, Res} = perform_save(Head, true),
- From ! {self(), Res},
+ resp(From, Res),
erlang:garbage_collect(),
{0, NewHead};
{update_counter, Key, Incr} when Head#head.update_mode =:= dirty ->
{NewHead, Res} = do_update_counter(Head, Key, Incr),
- From ! {self(), Res},
+ resp(From, Res),
{N + 1, NewHead};
WriteOp when Head#head.update_mode =:= new_dirty ->
H2 = Head#head{update_mode = dirty},
@@ -1577,12 +1587,12 @@ apply_op(Op, From, Head, N) ->
H2 = Head#head{update_mode = dirty},
apply_op(WriteOp, From, H2, 0);
{NewHead, Error} when is_record(NewHead, head) ->
- From ! {self(), Error},
+ resp(From, Error),
NewHead
end;
WriteOp when is_tuple(WriteOp), Head#head.access =:= read ->
Reason = {access_mode, Head#head.filename},
- From ! {self(), err({error, Reason})},
+ resp(From, err({error, Reason})),
ok
end.
@@ -1603,7 +1613,7 @@ bug_found(Name, Op, Bad, Stacktrace, From) ->
end,
if
From =/= self() ->
- From ! {self(), {error, {dets_bug, Name, Op, Bad}}},
+ resp(From, {error, {dets_bug, Name, Op, Bad}}),
ok;
true -> % auto_save | may_grow | {delayed_write, _}
ok
@@ -1613,10 +1623,10 @@ do_internal_open(Parent, Server, From, Ref, Args) ->
?PROFILE(ep:do()),
case do_open_file(Args, Parent, Server, Ref) of
{ok, Head} ->
- From ! {self(), ok},
+ resp(From, ok),
Head;
Error ->
- From ! {self(), Error},
+ resp(From, Error),
exit(normal)
end.
@@ -1698,7 +1708,7 @@ stream_end1(Pids, Next, N, C, Head, PwriteList) ->
stream_end2(Pids, Pids, Next, N, C, Head1, PR).
stream_end2([Pid | Pids], Ps, Next, N, C, Head, Reply) ->
- Pid ! {self(), Reply},
+ resp(Pid, Reply),
stream_end2(Pids, Ps, Next, N+1, C, Head, Reply);
stream_end2([], Ps, no_more, N, C, Head, _Reply) ->
penalty(Head, Ps, C),
@@ -1710,7 +1720,7 @@ penalty(H, _Ps, _C) when H#head.fixed =:= false ->
ok;
penalty(_H, _Ps, [{{lookup,_Pids},_Keys}]) ->
ok;
-penalty(#head{fixed = {_,[{Pid,_}]}}, [Pid], _C) ->
+penalty(#head{fixed = {_,[{Pid, _}]}}, [{Pid, _Tag} = _From], _C) ->
ok;
penalty(_H, _Ps, _C) ->
timer:sleep(1).
@@ -1729,9 +1739,9 @@ lookup_replies(P, O, [{P2,O2} | L]) ->
%% If a list of Pid then op was {member, Key}. Inlined.
lookup_reply([P], O) ->
- P ! {self(), O =/= []};
+ resp(P, O =/= []);
lookup_reply(P, O) ->
- P ! {self(), O}.
+ resp(P, O).
%%-----------------------------------------------------------------
%% Callback functions for system messages handling.
@@ -2253,7 +2263,7 @@ fmatch(Head, MP, Spec, N, Safe, From) ->
{Head1, []} ->
NewHead =
case Safe of
- safe -> do_safe_fixtable(Head1, From, true);
+ safe -> do_safe_fixtable(Head1, pidof(From), true);
no_safe -> Head1
end,
C0 = init_scan(NewHead, N),
@@ -2370,7 +2380,7 @@ do_fmatch_delete_var_keys(Head, _MP, ?PATTERN_TO_TRUE_MATCH_SPEC('_'), _From)
Reply
end;
do_fmatch_delete_var_keys(Head, MP, _Spec, From) ->
- Head1 = do_safe_fixtable(Head, From, true),
+ Head1 = do_safe_fixtable(Head, pidof(From), true),
{NewHead, []} = write_cache(Head1),
C0 = init_scan(NewHead, default),
{NewHead, {cont, C0#dets_cont{match_program = MP}, 0}}.
diff --git a/lib/stdlib/src/edlin.erl b/lib/stdlib/src/edlin.erl
index 6078c5e67b..88283a54c9 100644
--- a/lib/stdlib/src/edlin.erl
+++ b/lib/stdlib/src/edlin.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,10 +23,9 @@
%% About Latin-1 characters: see the beginning of erl_scan.erl.
-export([init/0,init/1,start/1,start/2,edit_line/2,prefix_arg/1]).
--export([erase_line/1,erase_inp/1,redraw_line/1]).
+-export([erase_line/0,erase_inp/1,redraw_line/1]).
-export([length_before/1,length_after/1,prompt/1]).
-export([current_line/1, current_chars/1]).
-%%-export([expand/1]).
-export([edit_line1/2]).
@@ -72,92 +71,105 @@ start(Pbs) ->
%% Only two modes used: 'none' and 'search'. Other modes can be
%% handled inline through specific character handling.
+start(Pbs, {_,{_,_},_}=Cont) ->
+ Rs1 = erase_line(),
+ Rs2 = redraw(Pbs, Cont, Rs1),
+ Rs3 = reverse(Rs2),
+ {more_chars,{line,Pbs,Cont,none},Rs3};
+
start(Pbs, Mode) ->
- {more_chars,{line,Pbs,{[],[]},Mode},[{put_chars,unicode,Pbs}]}.
+ {more_chars,{line,Pbs,{[],{[],[]},[]},Mode},[new_prompt, {put_chars,unicode,Pbs}]}.
-edit_line(Cs, {line,P,L,{blink,N}}) ->
- edit(Cs, P, L, none, [{move_rel,N}]);
+edit_line(Cs, {line,P,L,{blink,N_Rs}}) ->
+ edit(Cs, P, L, none, N_Rs);
edit_line(Cs, {line,P,L,M}) ->
edit(Cs, P, L, M, []).
-edit_line1(Cs, {line,P,L,{blink,N}}) ->
- edit(Cs, P, L, none, [{move_rel,N}]);
-edit_line1(Cs, {line,P,{[],[]},none}) ->
- {more_chars, {line,P,{string:reverse(Cs),[]},none},[{put_chars, unicode, Cs}]};
+edit_line1(Cs, {line,P,L,{blink,N_Rs}}) ->
+ edit(Cs, P, L, none, N_Rs);
+edit_line1(Cs, {line,P,{B,{[],[]},A},none}) ->
+ [CurrentLine|Lines] = [string:to_graphemes(Line) || Line <- reverse(string:split(Cs, "\n",all))],
+ Cont = {Lines ++ B,{reverse(CurrentLine),[]},A},
+ Rs = reverse(redraw(P, Cont, [])),
+ %erlang:display({P, Cont, Cs, CurrentLine}),
+ {more_chars, {line,P,Cont,none},[delete_line|Rs]};
edit_line1(Cs, {line,P,L,M}) ->
edit(Cs, P, L, M, []).
edit([C|Cs], P, Line, {blink,_}, [_|Rs]) -> %Remove blink here
edit([C|Cs], P, Line, none, Rs);
-edit([C|Cs], P, {Bef,Aft}, Prefix, Rs0) ->
+edit([C|Cs], P, {LB, {Bef,Aft}, LA}=MultiLine, Prefix, Rs0) ->
case key_map(C, Prefix) of
- meta ->
- edit(Cs, P, {Bef,Aft}, meta, Rs0);
+ meta ->
+ edit(Cs, P, MultiLine, meta, Rs0);
meta_o ->
- edit(Cs, P, {Bef,Aft}, meta_o, Rs0);
+ edit(Cs, P, MultiLine, meta_o, Rs0);
meta_csi ->
- edit(Cs, P, {Bef,Aft}, meta_csi, Rs0);
+ edit(Cs, P, MultiLine, meta_csi, Rs0);
meta_meta ->
- edit(Cs, P, {Bef,Aft}, meta_meta, Rs0);
+ edit(Cs, P, MultiLine, meta_meta, Rs0);
{csi, _} = Csi ->
- edit(Cs, P, {Bef,Aft}, Csi, Rs0);
- meta_left_sq_bracket ->
- edit(Cs, P, {Bef,Aft}, meta_left_sq_bracket, Rs0);
- search_meta ->
- edit(Cs, P, {Bef,Aft}, search_meta, Rs0);
- search_meta_left_sq_bracket ->
- edit(Cs, P, {Bef,Aft}, search_meta_left_sq_bracket, Rs0);
- ctlx ->
- edit(Cs, P, {Bef,Aft}, ctlx, Rs0);
- new_line ->
- {done, get_line(Bef, Aft ++ "\n"), Cs,
- reverse(Rs0, [{move_rel,cp_len(Aft)},{put_chars,unicode,"\n"}])};
- redraw_line ->
- Rs1 = erase(P, Bef, Aft, Rs0),
- Rs = redraw(P, Bef, Aft, Rs1),
- edit(Cs, P, {Bef,Aft}, none, Rs);
- tab_expand ->
- {expand, Bef, Cs,
- {line, P, {Bef, Aft}, none},
- reverse(Rs0)};
-
-%% tab ->
-%% %% Always redraw the line since expand/1 might have printed
-%% %% possible expansions.
-%% case expand(Bef) of
-%% {yes,Str} ->
-%% edit([redraw_line|
-%% (Str ++ Cs)], P, {Bef,Aft}, none, Rs0);
-%% no ->
-%% %% don't beep if there's only whitespace before
-%% %% us - user may have pasted in a lot of indented stuff.
-%% case whitespace_only(Bef) of
-%% false ->
-%% edit([redraw_line|Cs], P, {Bef,Aft}, none,
-%% [beep|Rs0]);
-%% true ->
-%% edit([redraw_line|Cs], P, {Bef,Aft}, none, [Rs0])
-%% end
-%% end;
- {undefined,C} ->
- {undefined,{none,Prefix,C},Cs,{line,P,{Bef,Aft},none},
- reverse(Rs0)};
- Op ->
- case do_op(Op, Bef, Aft, Rs0) of
- {blink,N,Line,Rs} ->
- edit(Cs, P, Line, {blink,N}, Rs);
- {Line, Rs, Mode} -> % allow custom modes from do_op
- edit(Cs, P, Line, Mode, Rs);
- {Line,Rs} ->
- edit(Cs, P, Line, none, Rs)
- end
+ edit(Cs, P, MultiLine, Csi, Rs0);
+ meta_left_sq_bracket ->
+ edit(Cs, P, MultiLine, meta_left_sq_bracket, Rs0);
+ search_meta ->
+ edit(Cs, P, MultiLine, search_meta, Rs0);
+ search_meta_left_sq_bracket ->
+ edit(Cs, P, MultiLine, search_meta_left_sq_bracket, Rs0);
+ ctlx ->
+ edit(Cs, P, MultiLine, ctlx, Rs0);
+ new_line ->
+ case Bef of
+ [] -> edit(Cs, P, MultiLine, none, Rs0);
+ _ -> MultiLine1 = {[lists:reverse(Bef)|LB],{[],Aft},LA},
+ edit(Cs, P, MultiLine1, none, redraw(P, MultiLine1, Rs0))
+ end;
+ new_line_finish ->
+ [Last|LAR]=LA1 = lists:reverse([lists:reverse(Bef,Aft)|LA]),
+ MultiLine1 = {LA1 ++ LB,{[],[]},[]},
+ % Move to end and redraw
+ Rs1 = redraw(P, {LAR ++ LB, {lists:reverse(Last), []},[]}, Rs0),
+ {done, MultiLine1, Cs, reverse(Rs1, [{insert_chars, unicode, "\n"}])};
+ redraw_line ->
+ Rs1 = erase_line(Rs0),
+ Rs = redraw(P, MultiLine, Rs1),
+ edit(Cs, P, MultiLine, none, Rs);
+ clear ->
+ Rs = redraw(P, MultiLine, [clear|Rs0]),
+ edit(Cs, P, MultiLine, none, Rs);
+ tab_expand ->
+ {expand, chars_before(MultiLine), Cs,
+ {line, P, MultiLine, tab_expand},
+ reverse(Rs0)};
+ tab_expand_full ->
+ {expand_full, chars_before(MultiLine), Cs,
+ {line, P, MultiLine, tab_expand},
+ reverse(Rs0)};
+ {undefined,C} ->
+ {undefined,{none,Prefix,C},Cs,{line,P,MultiLine,none},
+ reverse(Rs0)};
+ Op ->
+ case do_op(Op, MultiLine, Rs0) of
+ {blink,N,MultiLine1,Rs} ->
+ edit(Cs, P, MultiLine1, {blink,N}, Rs);
+ {redraw, MultiLine1, Rs} ->
+ edit(Cs, P, MultiLine1, none, redraw(P, MultiLine1, Rs));
+ {MultiLine1, Rs, Mode} -> % allow custom modes from do_op
+ edit(Cs, P, MultiLine1, Mode, Rs);
+ {MultiLine1,Rs} ->
+ edit(Cs, P, MultiLine1, none, Rs)
+ end
end;
edit([], P, L, {blink,N}, Rs) ->
- {blink,{line,P,L,{blink,N}},reverse(Rs)};
+ {blink,{line,P,L, {blink,N}},reverse(Rs)};
edit([], P, L, Prefix, Rs) ->
{more_chars,{line,P,L,Prefix},reverse(Rs)};
-edit(eof, _, {Bef,Aft}, _, Rs) ->
- {done,get_line(Bef, Aft),[],reverse(Rs, [{move_rel,cp_len(Aft)}])}.
+edit(eof, _, {_,{Bef,Aft0},LA} = L, _, Rs) ->
+ Aft1 = case LA of
+ [Last|_] -> Last;
+ _ -> Aft0
+ end,
+ {done,L,[],reverse(Rs, [{move_combo,-cp_len(Bef), length(LA), cp_len(Aft1)}])}.
%% %% Assumes that arg is a string
%% %% Horizontal whitespace only.
@@ -183,7 +195,7 @@ prefix_arg(N) -> N.
%% key_map(Char, Prefix)
%% Map a character and a prefix to an action.
-key_map(A, _) when is_atom(A) -> A; % so we can push keywords
+key_map(A, _) when is_atom(A) -> A; % so we can push keywords
key_map($\^A, none) -> beginning_of_line;
key_map($\^B, none) -> backward_char;
key_map($\^D, none) -> forward_delete_char;
@@ -191,10 +203,12 @@ key_map($\^E, none) -> end_of_line;
key_map($\^F, none) -> forward_char;
key_map($\^H, none) -> backward_delete_char;
key_map($\t, none) -> tab_expand;
-key_map($\^L, none) -> redraw_line;
-key_map($\n, none) -> new_line;
+key_map($\t, tab_expand) -> tab_expand_full;
+key_map(C, tab_expand) -> key_map(C, none);
key_map($\^K, none) -> kill_line;
-key_map($\r, none) -> new_line;
+key_map($\^L, none) -> clear;
+key_map($\n, none) -> new_line_finish;
+key_map($\r, none) -> new_line_finish;
key_map($\^T, none) -> transpose_char;
key_map($\^U, none) -> ctlu;
key_map($\^], none) -> auto_blink;
@@ -214,13 +228,20 @@ key_map($], Prefix) when Prefix =/= meta,
key_map($B, meta) -> backward_word;
key_map($D, meta) -> kill_word;
key_map($F, meta) -> forward_word;
+key_map($L, meta) -> redraw_line;
key_map($T, meta) -> transpose_word;
key_map($Y, meta) -> yank_pop;
key_map($b, meta) -> backward_word;
+key_map($c, meta) -> clear_line;
key_map($d, meta) -> kill_word;
key_map($f, meta) -> forward_word;
+key_map($l, meta) -> redraw_line;
key_map($t, meta) -> transpose_word;
key_map($y, meta) -> yank_pop;
+key_map($<, meta) -> beginning_of_expression;
+key_map($>, meta) -> end_of_expression;
+key_map($\n, meta) -> new_line;
+key_map($\r, meta) -> new_line;
key_map($O, meta) -> meta_o;
key_map($H, meta_o) -> beginning_of_line;
key_map($F, meta_o) -> end_of_line;
@@ -231,7 +252,7 @@ key_map($H, meta_left_sq_bracket) -> beginning_of_line;
key_map($F, meta_left_sq_bracket) -> end_of_line;
key_map($D, meta_left_sq_bracket) -> backward_char;
key_map($C, meta_left_sq_bracket) -> forward_char;
-% support a few <CTRL>+<CURSOR LEFT|RIGHT> combinations...
+% support a few <CTRL/ALT>+<CURSOR> combinations...
% - forward: \e\e[C, \e[5C, \e[1;5C
% - backward: \e\e[D, \e[5D, \e[1;5D
key_map($\e, meta) -> meta_meta;
@@ -240,14 +261,31 @@ key_map($C, meta_csi) -> forward_word;
key_map($D, meta_csi) -> backward_word;
key_map($1, meta_left_sq_bracket) -> {csi, "1"};
key_map($3, meta_left_sq_bracket) -> {csi, "3"};
-key_map($5, meta_left_sq_bracket) -> {csi, "5"};
-key_map($5, {csi, "1;"}) -> {csi, "1;5"};
+key_map($C, {csi, "3"}) -> forward_word;
+key_map($D, {csi, "3"}) -> backward_word;
key_map($~, {csi, "3"}) -> forward_delete_char;
+key_map($5, meta_left_sq_bracket) -> {csi, "5"};
key_map($C, {csi, "5"}) -> forward_word;
-key_map($C, {csi, "1;5"}) -> forward_word;
key_map($D, {csi, "5"}) -> backward_word;
-key_map($D, {csi, "1;5"}) -> backward_word;
key_map($;, {csi, "1"}) -> {csi, "1;"};
+key_map($3, {csi, "1;"}) -> {csi, "1;3"};
+key_map($C, {csi, "1;3"}) -> forward_word;
+key_map($D, {csi, "1;3"}) -> backward_word;
+key_map($A, {csi, "1;3"}) -> backward_line;
+key_map($B, {csi, "1;3"}) -> forward_line;
+key_map($4, {csi, "1;"}) -> {csi, "1;4"};
+key_map($A, {csi, "1;4"}) -> beginning_of_expression;
+key_map($B, {csi, "1;4"}) -> end_of_expression;
+key_map($5, {csi, "1;"}) -> {csi, "1;5"};
+key_map($C, {csi, "1;5"}) -> forward_word;
+key_map($D, {csi, "1;5"}) -> backward_word;
+key_map($A, {csi, "1;5"}) -> backward_line;
+key_map($B, {csi, "1;5"}) -> forward_line;
+
+
+
+
+
key_map(C, none) when C >= $\s ->
{insert,C};
%% for search, we need smarter line handling and so
@@ -272,6 +310,8 @@ key_map($\^], search) -> {search, search_quit};
key_map($\^X, search) -> {search, search_quit};
key_map($\^Y, search) -> {search, search_quit};
key_map($\e, search) -> search_meta;
+key_map($c, search_meta) -> {search, search_cancel};
+key_map($C, search_meta) -> {search, search_cancel};
key_map($[, search_meta) -> search_meta_left_sq_bracket;
key_map(_, search_meta) -> {search, search_quit};
key_map(_C, search_meta_left_sq_bracket) -> {search, search_quit};
@@ -280,19 +320,19 @@ key_map(C, _) -> {undefined,C}.
%% do_op(Action, Before, After, Requests)
%% Before and After are of lists of type string:grapheme_cluster()
-do_op({insert,C}, [], [], Rs) ->
- {{[C],[]},[{put_chars, unicode,[C]}|Rs]};
-do_op({insert,C}, [Bef|Bef0], [], Rs) ->
+do_op({insert,C}, {LB,{[],[]},LA}, Rs) ->
+ {{LB,{[C],[]},LA},[{insert_chars, unicode,[C]}|Rs]};
+do_op({insert,C}, {LB,{[Bef|Bef0], []},LA}, Rs) ->
case string:to_graphemes([Bef,C]) of
- [GC] -> {{[GC|Bef0],[]},[{put_chars, unicode,[C]}|Rs]};
- _ -> {{[C,Bef|Bef0],[]},[{put_chars, unicode,[C]}|Rs]}
+ [GC] -> {{LB,{[GC|Bef0],[]},LA},[{insert_chars, unicode,[C]}|Rs]};
+ _ -> {{LB,{[C,Bef|Bef0],[]},LA},[{insert_chars, unicode,[C]}|Rs]}
end;
-do_op({insert,C}, [], Aft, Rs) ->
- {{[C],Aft},[{insert_chars, unicode,[C]}|Rs]};
-do_op({insert,C}, [Bef|Bef0], Aft, Rs) ->
+do_op({insert,C}, {LB,{[], Aft},LA}, Rs) ->
+ {{LB,{[C],Aft},LA},[{insert_chars, unicode,[C]}|Rs]};
+do_op({insert,C}, {LB,{[Bef|Bef0], Aft},LA}, Rs) ->
case string:to_graphemes([Bef,C]) of
- [GC] -> {{[GC|Bef0],Aft},[{insert_chars, unicode,[C]}|Rs]};
- _ -> {{[C,Bef|Bef0],Aft},[{insert_chars, unicode,[C]}|Rs]}
+ [GC] -> {{LB,{[GC|Bef0],Aft},LA},[{insert_chars, unicode,[C]}|Rs]};
+ _ -> {{LB,{[C,Bef|Bef0],Aft},LA},[{insert_chars, unicode,[C]}|Rs]}
end;
%% Search mode prompt always looks like (search)`$TERMS': $RESULT.
%% the {insert_search, _} handlings allow to share this implementation
@@ -303,127 +343,168 @@ do_op({insert,C}, [Bef|Bef0], Aft, Rs) ->
%% search mode), we can use the Bef and Aft variables to hold each
%% part of the line. Bef takes charge of "(search)`$TERMS" and Aft
%% takes charge of "': $RESULT".
-do_op({insert_search, C}, Bef, [], Rs) ->
- Aft="': ",
- {{[C|Bef],Aft},
- [{insert_chars, unicode, [C]++Aft}, {delete_chars,-3} | Rs],
+%%
+%% Since multiline support the search mode prompt always looks like:
+%% search: $TERMS
+%% $ResultLine1
+%% $ResultLine2
+do_op({insert_search, C}, {LB,{Bef, []},LA}, Rs) ->
+ {{LB, {[C|Bef],[]}, LA},
+ [{insert_chars, unicode, [C]}, delete_after_cursor | Rs], search};
+do_op({insert_search, C}, {LB,{Bef, _Aft},LA}, Rs) ->
+ {{LB, {[C|Bef],[]}, LA},
+ [{insert_chars, unicode, [C]}, delete_after_cursor | Rs],
search};
-do_op({insert_search, C}, Bef, Aft, Rs) ->
- Offset= cp_len(Aft),
- NAft = "': ",
- {{[C|Bef],NAft},
- [{insert_chars, unicode, [C]++NAft}, {delete_chars,-Offset} | Rs],
- search};
-do_op({search, backward_delete_char}, [_|Bef], Aft, Rs) ->
+do_op({search, backward_delete_char}, {LB,{[_|Bef], Aft},LA}, Rs) ->
Offset= cp_len(Aft)+1,
- NAft = "': ",
- {{Bef,NAft},
- [{insert_chars, unicode, NAft}, {delete_chars,-Offset}|Rs],
+ {{LB, {Bef,Aft}, LA},
+ [{insert_chars, unicode, Aft}, {delete_chars,-Offset}|Rs],
search};
-do_op({search, backward_delete_char}, [], _Aft, Rs) ->
- Aft="': ",
- {{[],Aft}, Rs, search};
-do_op({search, skip_up}, Bef, Aft, Rs) ->
+do_op({search, backward_delete_char}, {LB,{[], Aft},LA}, Rs) ->
+ {{LB, {[],Aft}, LA}, [{insert_chars, unicode, Aft}, {delete_chars,-cp_len(Aft)}|Rs], search};
+do_op({search, skip_up}, {_,{Bef, Aft},_}, Rs) ->
Offset= cp_len(Aft),
- NAft = "': ",
- {{[$\^R|Bef],NAft}, % we insert ^R as a flag to whoever called us
- [{insert_chars, unicode, NAft}, {delete_chars,-Offset}|Rs],
+ {{[],{[$\^R|Bef],Aft},[]}, % we insert ^R as a flag to whoever called us
+ [{insert_chars, unicode, Aft}, {delete_chars,-Offset}|Rs],
search};
-do_op({search, skip_down}, Bef, Aft, Rs) ->
+do_op({search, skip_down}, {_,{Bef, Aft},_LA}, Rs) ->
Offset= cp_len(Aft),
- NAft = "': ",
- {{[$\^S|Bef],NAft}, % we insert ^S as a flag to whoever called us
- [{insert_chars, unicode, NAft}, {delete_chars,-Offset}|Rs],
+ {{[],{[$\^S|Bef],Aft},[]}, % we insert ^S as a flag to whoever called us
+ [{insert_chars, unicode, Aft}, {delete_chars,-Offset}|Rs],
search};
-do_op({search, search_found}, _Bef, Aft, Rs) ->
- "': "++NAft = Aft,
- {{[],NAft},
- [{put_chars, unicode, "\n"}, {move_rel,-cp_len(Aft)} | Rs],
- search_found};
-do_op({search, search_quit}, _Bef, Aft, Rs) ->
- "': "++NAft = Aft,
- {{[],NAft},
- [{put_chars, unicode, "\n"}, {move_rel,-cp_len(Aft)} | Rs],
- search_quit};
+do_op({search, search_found}, {_,{_Bef, Aft},LA}, Rs) ->
+ {{[],{[],Aft},LA}, Rs, search_found};
+do_op({search, search_quit}, {_,{_Bef, Aft},LA}, Rs) ->
+ {{[],{[],Aft},LA}, Rs, search_quit};
+do_op({search, search_cancel}, _, Rs) ->
+ {{[],{[],[]},[]}, Rs, search_cancel};
%% do blink after $$
-do_op({blink,C,M}, Bef=[$$,$$|_], Aft, Rs) ->
- N = over_paren(Bef, C, M),
- {blink,N+1,{[C|Bef],Aft},[{move_rel,-(N+1)},{insert_chars, unicode,[C]}|Rs]};
+do_op({blink,C,M}, {_,{[$$,$$|_], _},_} = MultiLine, Rs) ->
+ blink(over_paren(chars_before(MultiLine), C, M), C, MultiLine, Rs);
%% don't blink after a $
-do_op({blink,C,_}, Bef=[$$|_], Aft, Rs) ->
- do_op({insert,C}, Bef, Aft, Rs);
-do_op({blink,C,M}, Bef, Aft, Rs) ->
- case over_paren(Bef, C, M) of
- beep ->
- {{[C|Bef], Aft}, [beep,{insert_chars, unicode, [C]}|Rs]};
- N -> {blink,N+1,{[C|Bef],Aft},
- [{move_rel,-(N+1)},{insert_chars, unicode,[C]}|Rs]}
- end;
-do_op(auto_blink, Bef, Aft, Rs) ->
- case over_paren_auto(Bef) of
- {N, Paren} ->
- {blink,N+1,
- {[Paren|Bef], Aft},[{move_rel,-(N+1)},{insert_chars, unicode,[Paren]}|Rs]};
- % N is likely 0
- N -> {blink,N+1,{Bef,Aft},
- [{move_rel,-(N+1)}|Rs]}
- end;
-do_op(forward_delete_char, Bef, [GC|Aft], Rs) ->
- {{Bef,Aft},[{delete_chars,gc_len(GC)}|Rs]};
-do_op(backward_delete_char, [GC|Bef], Aft, Rs) ->
- {{Bef,Aft},[{delete_chars,-gc_len(GC)}|Rs]};
-do_op(transpose_char, [C1,C2|Bef], [], Rs) ->
+do_op({blink,C,_}, {_,{[$$|_], _},_} = MultiLine, Rs) ->
+ do_op({insert,C}, MultiLine, Rs);
+do_op({blink,C,M}, MultiLine, Rs) ->
+ blink(over_paren(chars_before(MultiLine), C, M), C, MultiLine, Rs);
+do_op(auto_blink, MultiLine, Rs) ->
+ blink(over_paren_auto(chars_before(MultiLine)), MultiLine, Rs);
+do_op(forward_delete_char, {LB,{Bef, []},[NextLine|LA]}, Rs) ->
+ NewLine = {LB, {Bef, NextLine}, LA},
+ {redraw, NewLine, Rs};
+do_op(forward_delete_char, {LB,{Bef, [GC|Aft]},LA}, Rs) ->
+ {{LB, {Bef,Aft}, LA},[{delete_chars,gc_len(GC)}|Rs]};
+do_op(backward_delete_char, {[PrevLine|LB],{[], Aft},LA}, Rs) ->
+ NewLine = {LB, {lists:reverse(PrevLine), Aft}, LA},
+ {redraw, NewLine,Rs};
+do_op(backward_delete_char, {LB,{[GC|Bef], Aft},LA}, Rs) ->
+ {{LB, {Bef,Aft}, LA},[{delete_chars,-gc_len(GC)}|Rs]};
+do_op(transpose_char, {LB,{[C1,C2|Bef], []},LA}, Rs) ->
Len = gc_len(C1)+gc_len(C2),
- {{[C2,C1|Bef],[]},[{put_chars, unicode,[C1,C2]},{move_rel,-Len}|Rs]};
-do_op(transpose_char, [C2|Bef], [C1|Aft], Rs) ->
+ {{LB, {[C2,C1|Bef],[]}, LA},[{put_chars, unicode,[C1,C2]},{move_rel,-Len}|Rs]};
+do_op(transpose_char, {LB,{[C2|Bef], [C1|Aft]},LA}, Rs) ->
Len = gc_len(C2),
- {{[C2,C1|Bef],Aft},[{put_chars, unicode,[C1,C2]},{move_rel,-Len}|Rs]};
-do_op(kill_word, Bef, Aft0, Rs) ->
+ {{LB, {[C2,C1|Bef],Aft}, LA},[{put_chars, unicode,[C1,C2]},{move_rel,-Len}|Rs]};
+do_op(kill_word, {LB,{Bef, Aft0},LA}, Rs) ->
{Aft1,Kill0,N0} = over_non_word(Aft0, [], 0),
{Aft,Kill,N} = over_word(Aft1, Kill0, N0),
put(kill_buffer, reverse(Kill)),
- {{Bef,Aft},[{delete_chars,N}|Rs]};
-do_op(backward_kill_word, Bef0, Aft, Rs) ->
+ {{LB, {Bef,Aft}, LA},[{delete_chars,N}|Rs]};
+do_op(backward_kill_word, {LB,{Bef0, Aft},LA}, Rs) ->
{Bef1,Kill0,N0} = over_non_word(Bef0, [], 0),
{Bef,Kill,N} = over_word(Bef1, Kill0, N0),
put(kill_buffer, Kill),
- {{Bef,Aft},[{delete_chars,-N}|Rs]};
-do_op(kill_line, Bef, Aft, Rs) ->
+ {{LB,{Bef,Aft},LA},[{delete_chars,-N}|Rs]};
+do_op(kill_line, {LB, {Bef, Aft}, LA}, Rs) ->
put(kill_buffer, Aft),
- {{Bef,[]},[{delete_chars,cp_len(Aft)}|Rs]};
-do_op(yank, Bef, [], Rs) ->
+ {{LB, {Bef,[]}, LA},[{delete_chars,cp_len(Aft)}|Rs]};
+do_op(clear_line, _, Rs) ->
+ {redraw, {[], {[],[]},[]}, Rs};
+do_op(yank, {LB,{Bef, []},LA}, Rs) ->
Kill = get(kill_buffer),
- {{reverse(Kill, Bef),[]},[{put_chars, unicode,Kill}|Rs]};
-do_op(yank, Bef, Aft, Rs) ->
+ {{LB, {reverse(Kill, Bef),[]}, LA},[{put_chars, unicode,Kill}|Rs]};
+do_op(yank, {LB,{Bef, Aft},LA}, Rs) ->
Kill = get(kill_buffer),
- {{reverse(Kill, Bef),Aft},[{insert_chars, unicode,Kill}|Rs]};
-do_op(forward_char, Bef, [C|Aft], Rs) ->
- {{[C|Bef],Aft},[{move_rel,gc_len(C)}|Rs]};
-do_op(backward_char, [C|Bef], Aft, Rs) ->
- {{Bef,[C|Aft]},[{move_rel,-gc_len(C)}|Rs]};
-do_op(forward_word, Bef0, Aft0, Rs) ->
+ {{LB, {reverse(Kill, Bef),Aft}, LA},[{insert_chars, unicode,Kill}|Rs]};
+do_op(forward_line, {_,_,[]} = MultiLine, Rs) ->
+ {MultiLine, Rs};
+do_op(forward_line, {LB,{Bef, Aft},[AL|LA]}, Rs) ->
+ CL = lists:reverse(Bef, Aft),
+ CursorPos = min(length(Bef), length(AL)),
+ {Bef1, Aft1} = lists:split(CursorPos, AL),
+ {{[CL|LB], {lists:reverse(Bef1), Aft1}, LA}, [{move_combo, -cp_len(Bef), 1, cp_len(Bef1)}|Rs]};
+do_op(backward_line, {[], _, _} = MultiLine, Rs) ->
+ {MultiLine, Rs};
+do_op(backward_line, {[BL|LB],{Bef, Aft},LA}, Rs) ->
+ CL = lists:reverse(Bef, Aft),
+ CursorPos = min(length(Bef), length(BL)),
+ {Bef1, Aft1} = lists:split(CursorPos, BL),
+ {{LB, {lists:reverse(Bef1), Aft1}, [CL|LA]},[{move_combo, -cp_len(Bef), -1, cp_len(Bef1)}|Rs]};
+do_op(forward_char, {LB,{Bef, []}, [AL|LA]}, Rs) ->
+ {{[lists:reverse(Bef)|LB],{[], string:to_graphemes(AL)}, LA}, [{move_combo, -cp_len(Bef), 1, 0}|Rs]};
+do_op(forward_char, {LB,{Bef, [C|Aft]},LA}, Rs) ->
+ {{LB,{[C|Bef],Aft},LA},[{move_rel,gc_len(C)}|Rs]};
+do_op(backward_char, {[BL|LB],{[], Aft},LA}, Rs) ->
+ {{LB,{lists:reverse(string:to_graphemes(BL)), []}, [Aft|LA]}, [{move_combo, 0, -1, cp_len(BL)}|Rs]};
+do_op(backward_char, {LB,{[C|Bef], Aft},LA}, Rs) ->
+ {{LB, {Bef,[C|Aft]}, LA},[{move_rel,-gc_len(C)}|Rs]};
+do_op(forward_word, {LB,{Bef0, []},[NextLine|LA]}, Rs) ->
+ {{[reverse(Bef0)|LB], {[], NextLine}, LA},[{move_combo, -cp_len(Bef0), 1, 0}|Rs]};
+do_op(forward_word, {LB,{Bef0, Aft0},LA}, Rs) ->
{Aft1,Bef1,N0} = over_non_word(Aft0, Bef0, 0),
- {Aft,Bef,N} = over_word(Aft1, Bef1, N0),
- {{Bef,Aft},[{move_rel,N}|Rs]};
-do_op(backward_word, Bef0, Aft0, Rs) ->
+ {Aft, Bef, N} = over_word(Aft1, Bef1, N0),
+ {{LB, {Bef,Aft}, LA},[{move_rel,N}|Rs]};
+do_op(backward_word, {[PrevLine|LB],{[], Aft0},LA}, Rs) ->
+ {{LB, {reverse(PrevLine), []}, [Aft0|LA]},[{move_combo, 0, -1, cp_len(PrevLine)}|Rs]};
+do_op(backward_word, {LB,{Bef0, Aft0},LA}, Rs) ->
{Bef1,Aft1,N0} = over_non_word(Bef0, Aft0, 0),
{Bef,Aft,N} = over_word(Bef1, Aft1, N0),
- {{Bef,Aft},[{move_rel,-N}|Rs]};
-do_op(beginning_of_line, [_|_]=Bef, Aft, Rs) ->
- {{[],reverse(Bef, Aft)},[{move_rel,-(cp_len(Bef))}|Rs]};
-do_op(beginning_of_line, [], Aft, Rs) ->
- {{[],Aft},Rs};
-do_op(end_of_line, Bef, [_|_]=Aft, Rs) ->
- {{reverse(Aft, Bef),[]},[{move_rel,cp_len(Aft)}|Rs]};
-do_op(end_of_line, Bef, [], Rs) ->
- {{Bef,[]},Rs};
-do_op(ctlu, Bef, Aft, Rs) ->
+ {{LB, {Bef,Aft}, LA},[{move_rel,-N}|Rs]};
+do_op(beginning_of_expression, {[],{[], Aft},LA}, Rs) ->
+ {{[], {[],Aft}, LA},Rs};
+do_op(beginning_of_expression, {LB,{Bef, Aft},LA}, Rs) ->
+ [First|Rest] = lists:reverse(LB) ++ [lists:reverse(Bef, Aft)],
+ {{[], {[],First}, Rest ++ LA},[{move_combo, -cp_len(Bef), -length(LB), 0}|Rs]};
+do_op(end_of_expression, {LB,{Bef, []},[]}, Rs) ->
+ {{LB, {Bef,[]}, []},Rs};
+do_op(end_of_expression, {LB,{Bef, Aft},LA}, Rs) ->
+ [Last|Rest] = lists:reverse(LA) ++ [lists:reverse(Bef, Aft)],
+ {{LB ++ Rest, {lists:reverse(Last),[]}, []},[{move_combo, -cp_len(Bef), length(LA), cp_len(Last)}|Rs]};
+do_op(beginning_of_line, {LB,{[_|_]=Bef, Aft},LA}, Rs) ->
+ {{LB, {[],reverse(Bef, Aft)}, LA},[{move_rel,-(cp_len(Bef))}|Rs]};
+do_op(beginning_of_line, {LB,{[], Aft},LA}, Rs) ->
+ {{LB, {[],Aft}, LA},Rs};
+do_op(end_of_line, {LB,{Bef, [_|_]=Aft},LA}, Rs) ->
+ {{LB, {reverse(Aft, Bef),[]}, LA},[{move_rel,cp_len(Aft)}|Rs]};
+do_op(end_of_line, {LB,{Bef, []},LA}, Rs) ->
+ {{LB, {Bef,[]}, LA},Rs};
+do_op(ctlu, {LB,{Bef, Aft},LA}, Rs) ->
put(kill_buffer, reverse(Bef)),
- {{[], Aft}, [{delete_chars, -cp_len(Bef)} | Rs]};
-do_op(beep, Bef, Aft, Rs) ->
- {{Bef,Aft},[beep|Rs]};
-do_op(_, Bef, Aft, Rs) ->
- {{Bef,Aft},[beep|Rs]}.
+ {{LB, {[], Aft}, LA}, [{delete_chars, -cp_len(Bef)} | Rs]};
+do_op(beep, {LB,{Bef, Aft},LA}, Rs) ->
+ {{LB,{Bef,Aft},LA},[beep|Rs]};
+do_op(_, {LB,{Bef, Aft},LA}, Rs) ->
+ {{LB,{Bef,Aft},LA},[beep|Rs]}.
+
+blink(beep, C, {LB, {Bef, Aft}, LA}, Rs) ->
+ {{LB,{[C|Bef], Aft},LA}, [beep,{insert_chars, unicode, [C]}|Rs]};
+blink({N, R}, C, MultiLine, Rs) ->
+ blink({N, R, C}, MultiLine, Rs).
+%% same line
+blink(beep, {LB,{Bef, Aft},LA}, Rs) ->
+ {{LB,{Bef, Aft},LA}, [beep|Rs]};
+blink({N, 0, Paren}, {LB, {Bef, Aft}, LA}, Rs) ->
+ MoveBackToParen = {move_rel,-N-1},
+ MoveForwardToParen = {move_rel, N+1},
+ {blink,[MoveForwardToParen],{LB,{[Paren|Bef],Aft},LA},
+ [MoveBackToParen,{insert_chars, unicode,[Paren]}|Rs]};
+%% multiline
+blink({N, R, Paren}, {LB,{Bef, Aft},LA}, Rs) ->
+ LengthToClosingParen = cp_len([Paren|Bef]),
+ LengthOpeningParen = cp_len(lists:nth(R,LB)) - N - 1,
+ MoveToOpeningParen = {move_combo, -LengthToClosingParen, -R, LengthOpeningParen},
+ MoveToClosingParen = {move_combo, -LengthOpeningParen, R, LengthToClosingParen+1},
+ {blink,[MoveToClosingParen],{LB,{[Paren|Bef],Aft},LA},
+ [MoveToOpeningParen,{insert_chars, unicode,[Paren]}|Rs]}.
%% over_word(Chars, InitialStack, InitialCount) ->
%% {RemainingChars,CharStack,Count}
@@ -431,8 +512,6 @@ do_op(_, Bef, Aft, Rs) ->
%% {RemainingChars,CharStack,Count}
%% Step over word/non-word characters pushing the stepped over ones on
%% the stack.
-
-
over_word(Cs, Stack, N) ->
L = length([1 || $\' <- Cs]),
case L rem 2 of
@@ -493,80 +572,84 @@ word_char(_) -> false.
%% do proper parentheses matching check. Paren has NOT been added.
over_paren(Chars, Paren, Match) ->
- over_paren(Chars, Paren, Match, 1, 1, []).
-
-
-over_paren([C,$$,$$|Cs], Paren, Match, D, N, L) ->
- over_paren([C|Cs], Paren, Match, D, N+2, L);
-over_paren([GC,$$|Cs], Paren, Match, D, N, L) ->
- over_paren(Cs, Paren, Match, D, N+1+gc_len(GC), L);
-over_paren([Match|_], _Paren, Match, 1, N, _) ->
- N;
-over_paren([Match|Cs], Paren, Match, D, N, [Match|L]) ->
- over_paren(Cs, Paren, Match, D-1, N+1, L);
-over_paren([Paren|Cs], Paren, Match, D, N, L) ->
- over_paren(Cs, Paren, Match, D+1, N+1, [Match|L]);
-
-over_paren([$)|Cs], Paren, Match, D, N, L) ->
- over_paren(Cs, Paren, Match, D, N+1, [$(|L]);
-over_paren([$]|Cs], Paren, Match, D, N, L) ->
- over_paren(Cs, Paren, Match, D, N+1, [$[|L]);
-over_paren([$}|Cs], Paren, Match, D, N, L) ->
- over_paren(Cs, Paren, Match, D, N+1, [${|L]);
-
-over_paren([$(|Cs], Paren, Match, D, N, [$(|L]) ->
- over_paren(Cs, Paren, Match, D, N+1, L);
-over_paren([$[|Cs], Paren, Match, D, N, [$[|L]) ->
- over_paren(Cs, Paren, Match, D, N+1, L);
-over_paren([${|Cs], Paren, Match, D, N, [${|L]) ->
- over_paren(Cs, Paren, Match, D, N+1, L);
-
-over_paren([$(|_], _, _, _, _, _) ->
+ over_paren(Chars, Paren, Match, 1, 1, 0, []).
+
+
+over_paren([C,$$,$$|Cs], Paren, Match, D, N, R, L) ->
+ over_paren([C|Cs], Paren, Match, D, N+2, R, L);
+over_paren([GC,$$|Cs], Paren, Match, D, N, R, L) ->
+ over_paren(Cs, Paren, Match, D, N+1+gc_len(GC), R, L);
+over_paren([$\n|Cs], Paren, Match, D, _N, R, L) ->
+ over_paren(Cs, Paren, Match, D, 0, R+1, L);
+over_paren([Match|_], _Paren, Match, 1, N, R, _) ->
+ {N, R};
+over_paren([Match|Cs], Paren, Match, D, N, R, [Match|L]) ->
+ over_paren(Cs, Paren, Match, D-1, N+1, R, L);
+over_paren([Paren|Cs], Paren, Match, D, N, R, L) ->
+ over_paren(Cs, Paren, Match, D+1, N+1, R, [Match|L]);
+
+over_paren([$)|Cs], Paren, Match, D, N, R, L) ->
+ over_paren(Cs, Paren, Match, D, N+1, R, [$(|L]);
+over_paren([$]|Cs], Paren, Match, D, N, R, L) ->
+ over_paren(Cs, Paren, Match, D, N+1, R, [$[|L]);
+over_paren([$}|Cs], Paren, Match, D, N, R, L) ->
+ over_paren(Cs, Paren, Match, D, N+1, R, [${|L]);
+
+over_paren([$(|Cs], Paren, Match, D, N, R, [$(|L]) ->
+ over_paren(Cs, Paren, Match, D, N+1, R, L);
+over_paren([$[|Cs], Paren, Match, D, N, R, [$[|L]) ->
+ over_paren(Cs, Paren, Match, D, N+1, R, L);
+over_paren([${|Cs], Paren, Match, D, N, R, [${|L]) ->
+ over_paren(Cs, Paren, Match, D, N+1, R, L);
+
+over_paren([$(|_], _, _, _, _, _, _) ->
beep;
-over_paren([$[|_], _, _, _, _, _) ->
+over_paren([$[|_], _, _, _, _, _, _) ->
beep;
-over_paren([${|_], _, _, _, _, _) ->
+over_paren([${|_], _, _, _, _, _, _) ->
beep;
-over_paren([GC|Cs], Paren, Match, D, N, L) ->
- over_paren(Cs, Paren, Match, D, N+gc_len(GC), L);
-over_paren([], _, _, _, _, _) ->
- 0.
+over_paren([GC|Cs], Paren, Match, D, N, R, L) ->
+ over_paren(Cs, Paren, Match, D, N+gc_len(GC), R, L);
+over_paren([], _, _, _, _, _, _) ->
+ beep.
over_paren_auto(Chars) ->
- over_paren_auto(Chars, 1, 1, []).
-
-
-over_paren_auto([C,$$,$$|Cs], D, N, L) ->
- over_paren_auto([C|Cs], D, N+2, L);
-over_paren_auto([GC,$$|Cs], D, N, L) ->
- over_paren_auto(Cs, D, N+1+gc_len(GC), L);
-
-over_paren_auto([$(|_], _, N, []) ->
- {N, $)};
-over_paren_auto([$[|_], _, N, []) ->
- {N, $]};
-over_paren_auto([${|_], _, N, []) ->
- {N, $}};
-
-over_paren_auto([$)|Cs], D, N, L) ->
- over_paren_auto(Cs, D, N+1, [$(|L]);
-over_paren_auto([$]|Cs], D, N, L) ->
- over_paren_auto(Cs, D, N+1, [$[|L]);
-over_paren_auto([$}|Cs], D, N, L) ->
- over_paren_auto(Cs, D, N+1, [${|L]);
-
-over_paren_auto([$(|Cs], D, N, [$(|L]) ->
- over_paren_auto(Cs, D, N+1, L);
-over_paren_auto([$[|Cs], D, N, [$[|L]) ->
- over_paren_auto(Cs, D, N+1, L);
-over_paren_auto([${|Cs], D, N, [${|L]) ->
- over_paren_auto(Cs, D, N+1, L);
-
-over_paren_auto([GC|Cs], D, N, L) ->
- over_paren_auto(Cs, D, N+gc_len(GC), L);
-over_paren_auto([], _, _, _) ->
- 0.
+ over_paren_auto(Chars, 1, 1, 0, []).
+
+
+over_paren_auto([C,$$,$$|Cs], D, N, R, L) ->
+ over_paren_auto([C|Cs], D, N+2, R, L);
+over_paren_auto([GC,$$|Cs], D, N, R, L) ->
+ over_paren_auto(Cs, D, N+1+gc_len(GC), R, L);
+over_paren_auto([$\n|Cs], D, _N, R, L) ->
+ over_paren_auto(Cs, D, 0, R+1, L);
+
+over_paren_auto([$(|_], _, N, R, []) ->
+ {N, R, $)};
+over_paren_auto([$[|_], _, N, R, []) ->
+ {N, R, $]};
+over_paren_auto([${|_], _, N, R, []) ->
+ {N, R, $}};
+
+over_paren_auto([$)|Cs], D, N, R, L) ->
+ over_paren_auto(Cs, D, N+1, R, [$(|L]);
+over_paren_auto([$]|Cs], D, N, R, L) ->
+ over_paren_auto(Cs, D, N+1, R, [$[|L]);
+over_paren_auto([$}|Cs], D, N, R, L) ->
+ over_paren_auto(Cs, D, N+1, R, [${|L]);
+
+over_paren_auto([$(|Cs], D, N, R, [$(|L]) ->
+ over_paren_auto(Cs, D, N+1, R, L);
+over_paren_auto([$[|Cs], D, N, R, [$[|L]) ->
+ over_paren_auto(Cs, D, N+1, R, L);
+over_paren_auto([${|Cs], D, N, R, [${|L]) ->
+ over_paren_auto(Cs, D, N+1, R, L);
+
+over_paren_auto([GC|Cs], D, N, R, L) ->
+ over_paren_auto(Cs, D, N+gc_len(GC), R, L);
+over_paren_auto([], _, _, _, _) ->
+ beep.
%% erase_line(Line)
%% erase_inp(Line)
@@ -575,40 +658,57 @@ over_paren_auto([], _, _, _) ->
%% length_after(Line)
%% prompt(Line)
%% current_line(Line)
+%% current_chars(Line)
%% Various functions for accessing bits of a line.
-erase_line({line,Pbs,{Bef,Aft},_}) ->
- reverse(erase(Pbs, Bef, Aft, [])).
+erase_line() ->
+ [delete_line].
+
+erase_inp({line,_, L,_}) ->
+ reverse(erase([], L, [])).
-erase_inp({line,_,{Bef,Aft},_}) ->
- reverse(erase([], Bef, Aft, [])).
+erase_line(Rs) ->
+ [delete_line|Rs].
-erase(Pbs, Bef, Aft, Rs) ->
+erase(Pbs, {_,{Bef, Aft},_}, Rs) ->
[{delete_chars,-cp_len(Pbs)-cp_len(Bef)},{delete_chars,cp_len(Aft)}|Rs].
-redraw_line({line,Pbs,{Bef,Aft},_}) ->
- reverse(redraw(Pbs, Bef, Aft, [])).
+redraw_line({line, Pbs, L,_}) ->
+ redraw(Pbs, L, []).
+
+multi_line_prompt(Pbs) ->
+ lists:duplicate(max(0,prim_tty:npwcwidthstring(Pbs)-3), $ )++".. ".
-redraw(Pbs, Bef, Aft, Rs) ->
- [{move_rel,-cp_len(Aft)},{put_chars, unicode,reverse(Bef, Aft)},{put_chars, unicode,Pbs}|Rs].
+redraw(Pbs, {_,{_,_},_}=L, Rs) ->
+ [{redraw_prompt, Pbs, multi_line_prompt(Pbs), L} |Rs].
-length_before({line,Pbs,{Bef,_Aft},_}) ->
+chars_before({[],{Bef,_},_}) ->
+ Bef;
+chars_before({LB,{Bef,_},_}) ->
+ lists:flatten(lists:join($\n, [Bef| [reverse(Line)|| Line <- LB]])).
+
+length_before({line,Pbs,{_,{Bef,_Aft},_},_}) ->
cp_len(Pbs) + cp_len(Bef).
-length_after({line,_,{_Bef,Aft},_}) ->
+length_after({line,_,{_,{_Bef,Aft},_},_}) ->
cp_len(Aft).
prompt({line,Pbs,_,_}) ->
Pbs.
-current_line({line,_,{Bef, Aft},_}) ->
- get_line(Bef, Aft ++ "\n").
-
-current_chars({line,_,{Bef,Aft},_}) ->
- get_line(Bef, Aft).
-
-get_line(Bef, Aft) ->
- unicode:characters_to_list(reverse(Bef, Aft)).
+current_chars({line,_,MultiLine,_}) ->
+ current_line(MultiLine).
+current_line({line,_,MultiLine,_}) ->
+ current_line(MultiLine) ++ "\n";
+%% Convert a multiline tuple into a string with new lines
+current_line({LinesBefore, {Before, After}, LinesAfter}) ->
+ CurrentLine = lists:reverse(Before, After),
+ unicode:characters_to_list(lists:flatten(
+ lists:filter(
+ fun (X) ->
+ X /= []
+ end,
+ lists:join($\n, lists:reverse(LinesBefore) ++ [CurrentLine] ++ LinesAfter)))).
%% Grapheme length in codepoints
gc_len(CP) when is_integer(CP) -> 1;
@@ -621,148 +721,3 @@ cp_len(Str) ->
cp_len([GC|R], Len) ->
cp_len(R, Len + gc_len(GC));
cp_len([], Len) -> Len.
-
-%% %% expand(CurrentBefore) ->
-%% %% {yes,Expansion} | no
-%% %% Try to expand the word before as either a module name or a function
-%% %% name. We can handle white space around the seperating ':' but the
-%% %% function name must be on the same line. CurrentBefore is reversed
-%% %% and over_word/3 reverses the characters it finds. In certain cases
-%% %% possible expansions are printed.
-
-%% expand(Bef0) ->
-%% {Bef1,Word,_} = over_word(Bef0, [], 0),
-%% case over_white(Bef1, [], 0) of
-%% {[$:|Bef2],_White,_Nwh} ->
-%% {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
-%% {_,Mod,_Nm} = over_word(Bef3, [], 0),
-%% expand_function_name(Mod, Word);
-%% {_,_,_} ->
-%% expand_module_name(Word)
-%% end.
-
-%% expand_module_name(Prefix) ->
-%% match(Prefix, code:all_loaded(), ":").
-
-%% expand_function_name(ModStr, FuncPrefix) ->
-%% Mod = list_to_atom(ModStr),
-%% case erlang:module_loaded(Mod) of
-%% true ->
-%% L = apply(Mod, module_info, []),
-%% case lists:keyfind(exports, 1, L) of
-%% {_, Exports} ->
-%% match(FuncPrefix, Exports, "(");
-%% _ ->
-%% no
-%% end;
-%% false ->
-%% no
-%% end.
-
-%% match(Prefix, Alts, Extra) ->
-%% Matches = match1(Prefix, Alts),
-%% case longest_common_head([N || {N,_} <- Matches]) of
-%% {partial, []} ->
-%% print_matches(Matches),
-%% no;
-%% {partial, Str} ->
-%% case lists:nthtail(length(Prefix), Str) of
-%% [] ->
-%% print_matches(Matches),
-%% {yes, []};
-%% Remain ->
-%% {yes, Remain}
-%% end;
-%% {complete, Str} ->
-%% {yes, lists:nthtail(length(Prefix), Str) ++ Extra};
-%% no ->
-%% no
-%% end.
-
-%% %% Print the list of names L in multiple columns.
-%% print_matches(L) ->
-%% io:nl(),
-%% col_print(lists:sort(L)),
-%% ok.
-
-%% col_print([]) -> ok;
-%% col_print(L) -> col_print(L, field_width(L), 0).
-
-%% col_print(X, Width, Len) when Width + Len > 79 ->
-%% io:nl(),
-%% col_print(X, Width, 0);
-%% col_print([{H0,A}|T], Width, Len) ->
-%% H = if
-%% %% If the second element is an integer, we assume it's an
-%% %% arity, and meant to be printed.
-%% integer(A) ->
-%% H0 ++ "/" ++ integer_to_list(A);
-%% true ->
-%% H0
-%% end,
-%% io:format("~-*s",[Width,H]),
-%% col_print(T, Width, Len+Width);
-%% col_print([], _, _) ->
-%% io:nl().
-
-%% field_width([{H,_}|T]) -> field_width(T, length(H)).
-
-%% field_width([{H,_}|T], W) ->
-%% case length(H) of
-%% L when L > W -> field_width(T, L);
-%% _ -> field_width(T, W)
-%% end;
-%% field_width([], W) when W < 40 ->
-%% W + 4;
-%% field_width([], _) ->
-%% 40.
-
-%% match1(Prefix, Alts) ->
-%% match1(Prefix, Alts, []).
-
-%% match1(Prefix, [{H,A}|T], L) ->
-%% case prefix(Prefix, Str = atom_to_list(H)) of
-%% true ->
-%% match1(Prefix, T, [{Str,A}|L]);
-%% false ->
-%% match1(Prefix, T, L)
-%% end;
-%% match1(_, [], L) ->
-%% L.
-
-%% longest_common_head([]) ->
-%% no;
-%% longest_common_head(LL) ->
-%% longest_common_head(LL, []).
-
-%% longest_common_head([[]|_], L) ->
-%% {partial, reverse(L)};
-%% longest_common_head(LL, L) ->
-%% case same_head(LL) of
-%% true ->
-%% [[H|_]|_] = LL,
-%% LL1 = all_tails(LL),
-%% case all_nil(LL1) of
-%% false ->
-%% longest_common_head(LL1, [H|L]);
-%% true ->
-%% {complete, reverse([H|L])}
-%% end;
-%% false ->
-%% {partial, reverse(L)}
-%% end.
-
-%% same_head([[H|_]|T1]) -> same_head(H, T1).
-
-%% same_head(H, [[H|_]|T]) -> same_head(H, T);
-%% same_head(_, []) -> true;
-%% same_head(_, _) -> false.
-
-%% all_tails(LL) -> all_tails(LL, []).
-
-%% all_tails([[_|T]|T1], L) -> all_tails(T1, [T|L]);
-%% all_tails([], L) -> L.
-
-%% all_nil([]) -> true;
-%% all_nil([[] | Rest]) -> all_nil(Rest);
-%% all_nil(_) -> false.
diff --git a/lib/stdlib/src/edlin_context.erl b/lib/stdlib/src/edlin_context.erl
new file mode 100644
index 0000000000..589bac4a02
--- /dev/null
+++ b/lib/stdlib/src/edlin_context.erl
@@ -0,0 +1,649 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(edlin_context).
+%% description
+%%
+-export([get_context/1, get_context/2, odd_quotes/2]).
+%% The context record is a structure that helps with viewing a nested expression
+%% Typically we do not know the types of a tuple or a map in isolation, but if
+%% we make use of the type information available for a function or a record
+%% then we are able to extract more information.
+%% A nesting can be either be a tuple a list or a map, more types of nestings could
+%% be supported in the future. But it is for these types that we have type information.
+%% We do not need to care about nested records or nested functions since the type information
+%% is available for the deepest nested one.
+%% The context record stores the top most nesting so far, while the nesting field contains
+%% a list of nestings we found, the last element being the deepest nesting.
+-type nesting() :: {'tuple', [{atom(), string()}], {atom(), string()} | []}
+ | {'list', [{atom(), string()}], {atom(), string()} | []}
+ | {'map', [string()], string(), [{atom(), string()}], {atom(), string()} | []}.
+-record(context,{
+ arguments = [] :: [any()],
+ fields = [] :: [string()], %% The Field or Keys in this nesting
+ parameter_count = 0 :: non_neg_integer(), %% Number of parameters in this nesting
+ current_field = [] :: string(), %% field of a record or key of a map field=<tab> %% should be the last element in this context
+ nestings = [] :: [nesting()]}).
+%% get_context - basically get the context to be able to deduce how we should complete the word
+%% If the word is empty, then we do not want to complete with anything if we just closed
+%% a bracket, ended a quote (user has to enter , or . themselves)
+%% but maybe we can help to add ',',},] depending on context in the future
+-spec get_context(Line) -> Context when
+ Line :: string(), %% The whole line in reverse excluding Word
+ Mod :: string(),
+ Fun :: string(),
+ Args :: list(any()),
+ Unfinished :: any(),
+ Binding :: string(), %% map variable
+ Keys :: [string()], %% list of keys in the map MapBind
+ Record :: string(),
+ Fields :: [string()], %% list of keys in the record
+ FieldToComplete :: string(),
+ Nesting :: [nesting()] | [],
+ Context :: {string} %% cursor is inside a string "_
+ | {binding} %% cursor is inside a Binding statement
+ | {term} %% cursor is at a free position, where any value is expected
+ | {term, Args, Unfinished}
+ | {fun_} %% cursor is in a fun statement (either a NewVar, '(' or mfa() is expected)
+ | {fun_, Mod} %% cursor is in a fun mod: statement (mfa)
+ | {fun_, Mod, Fun} %% cursor is in a fun mod:fun statement
+ | {new_fun, Unfinished}
+ | {function}
+ | {function, Mod, Fun, Args, Unfinished, Nesting}
+ | {map, Binding, Keys}
+ | {map_or_record}
+ | {record}
+ | {record, Record, Fields, FieldToComplete, Args, Unfinished, Nesting}
+ | {error, integer()}.
+get_context(Line) ->
+ {Bef0, Word} = edlin_expand:over_word(Line),
+ case {{Bef0, Word}, odd_quotes($", Bef0)} of
+ {_, true} -> {string};
+ {{[$#|_], []}, _} -> {map_or_record};
+ {{_Bef1, Word}, _} ->
+ case is_binding(Word) of
+ true -> {binding};
+ false -> get_context(Bef0, Word)
+ end
+ end.
+get_context(">-" ++ _, L) when is_list(L) -> {term};
+get_context([$?|_], _) ->
+ {macro};
+get_context(Bef0, Word) when is_list(Word) ->
+ get_context(lists:reverse(Word) ++ Bef0, #context{});
+get_context([], #context{arguments = Args, parameter_count = Count, nestings = Nestings} = _CR) ->
+ case Count+1 == length(Args) of
+ true -> {term, lists:droplast(Args), lists:last(Args)};
+ _ ->
+ %% Nestings will not end up as an argument
+ case Nestings of
+ [] -> case Count of
+ 0 when length(Args) > 0 -> {term, lists:droplast(Args), lists:last(Args)};
+ _ -> {term, Args, []}
+ end;
+ [{list, Args1, Arg}] -> {term, Args1, Arg};
+ [{tuple, Args1, Arg}] -> {term, Args1, Arg};
+ [{map, _, _, Args1, Arg}] -> {term, Args1, Arg}
+ end
+ end;
+get_context([$(|Bef], CR) ->
+ %% We have an unclosed opening parenthesis
+ %% Check if we have a function call
+ %% We can deduce the minimum arity based on how many terms we trimmed
+ %% We can check the type of the following Term and suggest those in special cases
+ %% shell_default and erlang are imported, make sure we can do expansion for those
+ {Bef1, Fun} = edlin_expand:over_word(Bef),
+ case Fun of
+ [] -> {term}; % parenthesis
+ _ ->
+ {_, Mod} = over_module(Bef1, Fun),
+ case Mod of
+ "shell" -> {term};
+ "shell_default" -> {term};
+ _ ->
+ case CR#context.parameter_count+1 == length(CR#context.arguments) of
+ true ->
+ %% N arguments N-1 commas, this means that we have an argument
+ %% still being worked on.
+ {function, Mod, Fun, lists:droplast(CR#context.arguments),
+ lists:last(CR#context.arguments),CR#context.nestings};
+ _ ->
+ {function, Mod, Fun, CR#context.arguments,
+ [], CR#context.nestings}
+ end
+ end
+ end;
+get_context([${|Bef], #context{ fields=Fields,
+ current_field=FieldToComplete,
+ arguments = Arguments,
+ parameter_count = Count,
+ nestings=Nestings}) ->
+ {Args, Unfinished} = case Count+1 == length(Arguments) of
+ true -> {lists:droplast(Arguments), lists:last(Arguments)};
+ _ -> {Arguments, []}
+ end,
+ case edlin_expand:over_word(Bef) of
+ {[$#|Bef1], []} -> %% Map
+ {Bef2, Map} = edlin_expand:over_word(Bef1),
+ case Map of
+ [] -> get_context(Bef2, #context{
+ %% We finished a nesting lets reset and read the next nesting
+ nestings = [{'map', Fields, FieldToComplete, Args, Unfinished}|Nestings]});
+ _ -> {map, Map, Fields}
+ end;
+ {_, []} ->
+ get_context(Bef, #context{
+ %% We finished a nesting lets reset and read the next nesting
+ nestings = [{'tuple', Args, Unfinished}|Nestings]});
+ {[$#|_Bef3], Record} -> %% Record
+ {record, Record, Fields, FieldToComplete, Args, Unfinished, Nestings}
+ end;
+get_context([$[|Bef1], #context{arguments = Arguments, parameter_count = Count, nestings=Nestings}) ->
+ {Args, Unfinished} = case Count+1 == length(Arguments) of
+ true -> {lists:droplast(Arguments), lists:last(Arguments)};
+ _ -> {Arguments, []}
+ end,
+ get_context(Bef1, #context{
+ %% We finished a nesting lets reset and read the next nesting
+ nestings = [{'list', Args, Unfinished}|Nestings]});
+get_context([$,|Bef1], #context{parameter_count=Count}=CR) ->
+ get_context(Bef1, CR#context{
+ parameter_count = Count+1});
+get_context([$>,$=|Bef1], #context{ parameter_count=Count,
+ fields=Fields}=CR) ->
+ {Bef2, Field}=edlin_expand:over_word(Bef1),
+ case Count of
+ 0 -> %% If count is 0, then we know its a value we may want to complete.
+ get_context(Bef2, CR#context{fields = [Field|Fields],
+ current_field = Field});
+ _ -> get_context(Bef2, CR#context{fields = [Field|Fields]})
+ end;
+get_context([$=,$:|Bef1], #context{ parameter_count=Count,
+ fields=Fields}=CR) ->
+ {Bef2, Field}=edlin_expand:over_word(Bef1),
+ case Count of
+ 0 -> %% If count is 0, then we know its a value we may want to complete.
+ get_context(Bef2, CR#context{fields = [Field|Fields],
+ current_field = Field});
+ _ -> get_context(Bef2, CR#context{fields = [Field|Fields]})
+ end;
+get_context([$=|Bef1], #context{
+ parameter_count=Count,
+ fields=Fields}=CR) ->
+ {Bef2, Field}=edlin_expand:over_word(Bef1),
+ % if we are here, its always going to be
+ case Count of
+ 0 -> %%[$=|_],
+ get_context(Bef2, CR#context{fields = [Field|Fields],
+ current_field = Field});
+ _ -> get_context(Bef2, CR#context{fields = [Field|Fields]})
+ end;
+get_context([$.|Bef2], CR) ->
+ Arguments = CR#context.arguments,
+ Count = CR#context.parameter_count,
+ {Args, Unfinished} = case Count+1 == length(Arguments) of
+ true -> {lists:droplast(Arguments), lists:last(Arguments)};
+ _ -> {Arguments, []}
+ end,
+ case edlin_expand:over_word(Bef2) of
+ {[$#|_Bef3], Record} -> %% Record
+ {record, Record, CR#context.fields, CR#context.current_field, Args, Unfinished, CR#context.nestings};
+ _ -> {'end'}
+ end;
+get_context([$:|Bef2], _) ->
+ %% look backwards to see if its a fun
+ {Bef3, Mod} = edlin_expand:over_word(Bef2),
+ case edlin_expand:over_word(Bef3) of
+ {_, "fun"} -> {fun_, Mod};
+ _ -> {function}
+ end;
+get_context([$/|Bef1], _) ->
+ {Bef2, Fun} = edlin_expand:over_word(Bef1),
+ {_, Mod} = over_module(Bef2, Fun),
+ {fun_, Mod, Fun};
+get_context([$>,$-|_Bef2], #context{arguments = Args} = CR) ->
+ %% Inside a function
+ case CR#context.parameter_count+1 == length(Args) of
+ true ->
+ {term, lists:droplast(Args), lists:last(Args)};
+ _ -> {term, Args, []}
+ end;
+get_context("nehw " ++ _Bef2, #context{arguments = Args} = CR) ->
+ %% Inside a guard
+ case CR#context.parameter_count+1 == length(Args) of
+ true ->
+ {term, lists:droplast(Args), lists:last(Args)};
+ _ -> {term, Args, []}
+ end;
+get_context([$\ |Bef],CR) -> get_context(Bef, CR); %% matching space here simplifies the other clauses
+get_context(Bef0, #context{arguments=Args, parameter_count=Count} = CR) ->
+ case over_to_opening(Bef0) of
+ {_,[]} -> {term};
+ {error, _}=E -> E;
+ {record} -> {record};
+ {fun_} -> {fun_};
+ {new_fun, _}=F -> F;
+ {Bef1, {fun_, Str}=Arg} ->
+ case Count of
+ 0 ->
+ [_, Mod, Fun| _] = string:tokens(Str, " :/"),
+ {fun_, Mod, Fun};
+ _ -> get_context(Bef1, CR#context{arguments=[Arg|Args]})
+ end;
+ {Bef1, Arg} -> get_context(Bef1, CR#context{arguments=[Arg|Args]})
+ end.
+
+read_operator(Bef) ->
+ read_operator1(Bef).
+
+operator_string() -> "-=><:+*/|&^~".
+read_operator1([$\ |Bef]) -> read_operator1(Bef);
+read_operator1("mer " ++ Bef) -> {Bef, "rem"};
+read_operator1("osladna " ++ Bef) -> {Bef, "andalso"};
+read_operator1("dna " ++ Bef) -> {Bef, "and"};
+read_operator1("eslero " ++ Bef) -> {Bef, "orelse"};
+read_operator1("ro " ++ Bef) -> {Bef, "or"};
+read_operator1([$>,$>,$>|Bef1]) -> {[$>,$>|Bef1], [$>]}; %% comparison with a binary or typo
+read_operator1([$>,$>|Bef1]) -> {[$>|Bef1], [$>]}; %% comparison with a pid, or binary
+read_operator1([$>,$-, C|Bef1]=Bef) ->
+ case lists:member(C, operator_string()) of
+ true -> {Bef1, [C,$-,$>]};
+ false -> {Bef, []}
+ end;
+read_operator1([$>,$=, C|Bef1]=Bef) ->
+ case lists:member(C, operator_string()) of
+ true -> {Bef1, [C,$=,$>]};
+ false -> {Bef, []}
+ end;
+read_operator1([$=,$:, C|Bef1]=Bef) ->
+ case lists:member(C, operator_string()) of
+ true -> {Bef1, [C,$:,$=]};
+ false -> {Bef, []}
+ end;
+read_operator1([$:|_]=Bef) -> {Bef, []}; %% this operator does not count
+read_operator1([Op1,Op2,Op3|Bef])->
+ case {lists:member(Op1, operator_string()),
+ lists:member(Op2, operator_string()),
+ lists:member(Op3, operator_string())} of
+ {true, true, true} -> {Bef, [Op3, Op2, Op1]};
+ {true, true, false} -> {[Op3|Bef], [Op2, Op1]};
+ {true, false, _} -> {[Op2,Op3|Bef], [Op1]};
+ _ -> {[Op1,Op2,Op3|Bef], []}
+ end;
+read_operator1([Op1, Op2]) ->
+ case {lists:member(Op1, operator_string()),
+ lists:member(Op2, operator_string())} of
+ {true, true} -> {[], [Op2, Op1]};
+ {true, false} -> {[Op2], [Op1]};
+ _ -> {[Op1,Op2], []}
+ end;
+read_operator1([Op1]) ->
+ case lists:member(Op1, operator_string()) of
+ true -> {[], [Op1]};
+ _ -> {[Op1], []}
+ end;
+read_operator1(Bef) -> {Bef, []}.
+
+read_opening_char("nehw "++Bef) ->
+ {Bef, "when"};
+read_opening_char([OC|Bef]) when OC =:= $(; OC =:= $[; OC =:= ${; OC =:= $,; OC =:= $. ->
+ {Bef, [OC]};
+read_opening_char([$>,$-|_]=Bef) ->
+ case read_operator(Bef) of
+ {_, []} -> {Bef, "->"};
+ _ -> {Bef, []}
+ end;
+read_opening_char([$\ |Bef]) -> read_opening_char(Bef);
+read_opening_char(Bef) -> {Bef, []}.
+
+over_to_opening(Bef) -> try
+ over_to_opening1(Bef,#{args => []})
+ catch
+ throw:E -> E
+ end.
+over_to_opening1([], #{'args' := Args}) ->
+ over_to_opening_return([], Args);
+over_to_opening1(Bef, Acc = #{args := Args}) ->
+ case edlin_expand:over_word(Bef) of
+ {_, []} -> %% removed spaces
+ case read_opening_char(Bef) of
+ {Bef1, []} -> %% not an opening
+ case extract_argument2(Bef1) of
+ {stop} -> over_to_opening_return(Bef1, Args);
+ {Bef2, []} -> over_to_opening_return(Bef2, Args);
+ {Bef2, Arg} -> over_to_opening1(Bef2, Acc#{args => [Arg | Args]})
+ end;
+ {_Bef1, _Opening} -> over_to_opening_return(Bef, Args)
+ end;
+ _ -> case extract_argument2(Bef) of
+ {stop} -> over_to_opening_return(Bef, Args);
+ {Bef2, []} -> over_to_opening_return(Bef2, Args);
+ {Bef2, Arg} -> over_to_opening1(Bef2, Acc#{args => [Arg | Args]})
+ end
+ end.
+over_to_opening_return(Bef, Args) ->
+ case Args of
+ [] -> {Bef, []};
+ [Arg] -> {Bef, Arg};
+ [{operator, "-"}, {integer, I}] -> {Bef, {integer, "-" ++ I}};
+ [{operator, "-"}, {float, F}] -> {Bef, {float, "-" ++ F}};
+ [{atom, "fun"}, {atom, _}] -> throw({fun_});
+ _ ->
+ case look_for_non_operator_separator(Args) of
+ true -> {Bef, {operation, lists:flatten(lists:join(" ", lists:map(fun({_, Arg}) -> Arg end, Args)))}};
+ false -> {error, length(Bef)}
+ end
+ end.
+look_for_non_operator_separator([{string, _},{string, _}=A|Args]) ->
+ look_for_non_operator_separator([A|Args]);
+look_for_non_operator_separator([{operator, _}, {operator, _}|_]) -> false;
+
+look_for_non_operator_separator([_, {operator, _}=B|Args]) ->
+ look_for_non_operator_separator([B|Args]);
+look_for_non_operator_separator([{operator, _}, B|Args]) ->
+ look_for_non_operator_separator([B|Args]);
+look_for_non_operator_separator([_]) -> true;
+look_for_non_operator_separator(_) -> false.
+
+over_map_record_or_tuple(Bef0) ->
+ case over_to_opening_paren($},Bef0) of
+ {_, []} -> %% no matching {
+ throw({error, length(Bef0)});
+ {Bef3, Clause} ->
+ {Bef4, MaybeRecord} = edlin_expand:over_word(Bef3),
+ case MaybeRecord of
+ [] -> case Bef4 of
+ [$#|Bef5] -> %% Map
+ {Bef6, _Var} = edlin_expand:over_word(Bef5),
+ {Bef6, {map, _Var++"#"++Clause}};
+ _ -> %% Tuple
+ {Bef4, {tuple, Clause}}
+ end;
+ _Record -> %% Record
+ [$#|Bef5] = Bef4,
+ {Bef6, _Var} = edlin_expand:over_word(Bef5),
+ {Bef6, {record, _Var++"#"++_Record++Clause}}
+ end
+ end.
+over_pid_port_or_ref(Bef2) ->
+ %% Extracts argument or part of an operation
+ %% Consume Pid, Ref, FunRef or Binary
+ case over_to_opening_paren($>,Bef2) of
+ {_, []} -> %% no matching <, maybe a '>' operator
+ throw({soft_error, length(Bef2)});
+ {Bef3, Clause} ->
+ case Bef3 of
+ "feR#" ++ Bef4 ->
+ {Bef4, {ref, "#Ref" ++ Clause}};
+ "nuF#" ++ Bef4 ->
+ {Bef4, {'funref', "#Fun" ++ Clause}};
+ "troP#" ++ Bef4 ->
+ {Bef4, {port, "#Port" ++ Clause}};
+ _ -> case edlin_expand:over_word(Bef3) of
+ {Bef3, []} ->
+ case Bef2 of
+ [$>|_] -> %% binary
+ {Bef3, {binary, Clause}};
+ _ -> %% pid
+ %% match <Num.Num.Num>
+ {Bef3, {pid, Clause}}
+ end;
+ _ ->
+ throw({error, length(Bef3)})
+ end
+ end
+ end.
+over_list(Bef2) ->
+ case over_to_opening_paren($],Bef2) of
+ {_, []} -> %% no matching [
+ throw({error, length(Bef2)});
+ {Bef3, Clause} ->
+ {Bef3, {list, Clause}}
+ end.
+over_parenthesis_or_call(Bef2) ->
+ case over_to_opening_paren($),Bef2) of
+ {_, []} -> %% no matching (
+ throw({error, length(Bef2)});
+ {Bef3, Clause} ->
+ {Bef4, Fun} = edlin_expand:over_word(Bef3),
+ {Bef5, ModFun} = case Bef4 of
+ [$:|Bef41] ->
+ {Bef42, Mod} = edlin_expand:over_word(Bef41),
+ {Bef42, Mod++[$:|Fun]};
+ _ -> {Bef4, Fun}
+ end,
+ case ModFun of
+ [] -> {Bef5, {parenthesis, Clause}};
+ "fun" -> throw({new_fun, Clause});
+ _ -> {Bef5, {call, ModFun++Clause}}
+ end
+ end.
+over_keyword_or_fun(Bef1) ->
+ case over_keyword_expression(Bef1) of
+ {Bef2, KeywordExpression} -> {Bef2, {keyword, KeywordExpression ++ " end"}};
+ _ -> throw({error, length(Bef1)})
+ end.
+extract_argument2([$>|Bef0]=Bef)->
+ case read_operator(Bef) of
+ {[$>|_]=Bef1, ">"=Operator} ->
+ try over_pid_port_or_ref(Bef0)
+ catch
+ %% not a pid, port, ref or binary
+ throw:{error, _}=E -> throw(E);
+ throw:{soft_error, _Col} -> {Bef1, {operator, Operator}}
+ end;
+ {Bef1, ">"=Operator} ->
+ try over_pid_port_or_ref(Bef1)
+ catch
+ %% not a pid, port or ref
+ throw:{error, _}=E -> throw(E);
+ throw:{soft_error, _Col} -> {Bef1, {operator, Operator}}
+ end;
+ {_Bef1, []} -> {stop};
+ {Bef1, Operator} -> {Bef1, {operator, Operator}}
+ end;
+extract_argument2(Bef0) ->
+ case read_operator(Bef0) of
+ {[$}|Bef1], []} -> over_map_record_or_tuple(Bef1);
+ {[$)|Bef1], []} -> over_parenthesis_or_call(Bef1);
+ {[$]|Bef1], []} -> over_list(Bef1);
+ {[$"|Bef2], []} -> {Bef3, _Quote} = over_to_opening_quote($", Bef2),
+ {Bef3, {string, _Quote}};
+ {"dne "++Bef1, []} -> over_keyword_or_fun(Bef1);
+ {[$=,$:|_], []} -> {stop};
+ {[$:|_], []} -> {stop};
+ {"nehw" ++ _Bef1,[]} -> {stop};
+ {_, []} -> extract_argument(Bef0);
+ {Bef1, Operator} ->
+ {Bef1, {operator, Operator}}
+ end.
+
+extract_argument(Bef0) ->
+ %% TODO: We probably need to be able to extract Terms with operators...
+ case edlin_expand:over_word(Bef0) of
+ {_Bef1, []} ->
+ case read_char(_Bef1) of
+ {_, []} -> {_Bef1, []};
+ {Bef2, Char} -> {Bef2, {char, Char}}
+ end;
+ {Bef2, Var} ->
+ try list_to_integer(Var) of
+ _ -> %% there is an integer
+ case over_fun_function(Bef0) of
+ {Bef3, "fun " ++ _ModFunArr} -> {Bef3, {fun_, "fun "++_ModFunArr}};
+ _ -> case over_number(Bef0) of
+ {Bef3, []} -> {Bef3, []}; %% how to deal with operators
+ {Bef3, Number} -> {Bef3, Number}
+
+ end
+ end
+ catch
+ _:_ ->
+ case is_binding(Var) of
+ true -> {Bef2,{var, Var}};
+ false -> case Bef2 of
+ [$#|_] -> throw({record});
+ _ -> {Bef2, {atom, Var}}
+ end
+ end
+ end
+ end.
+over_number(Bef) ->
+ case edlin_expand:over_word(Bef) of
+ {_, []} -> {Bef, []};
+ {Bef2, Var} ->
+ try list_to_integer(Var) of
+ _ ->
+ {Bef6, {NumberType, Number}}=Res = case edlin_expand:over_word(Bef2) of
+ {[$.|Bef3],[]} -> %% float
+ {Bef4, Integer} = edlin_expand:over_word(Bef3),
+ {Bef4, {float, Integer ++ "." ++ Var}};
+ {[$#|Bef3],[]} -> %% integer base
+ {Bef4, Base} = edlin_expand:over_word(Bef3),
+ {Bef4, {integer, Base ++ "#" ++ Var}};
+ _ ->
+ {Bef2, {integer, Var}}
+ %% otherwise its an operation that can be very complicated we should read everything up to the closest CC
+ %% and return an {operation, Clause}
+ end,
+ case edlin_expand:over_word(Bef6) of
+ {[$-|Bef5], []} ->
+ case read_opening_char(Bef5) of
+ {_, []} -> Res;
+ _ -> {Bef5, {NumberType, "-" ++Number}}
+ end;
+ _ -> Res
+ end
+ catch
+ _:_ -> {Bef, []}
+ end
+ end.
+read_char([C,$$|Line]) ->
+ {Line, [$$,C]};
+read_char([$$|Line]) ->
+ {Line, "$ "};
+read_char(Line) ->
+ {Line, []}.
+
+over_fun_function(Bef) ->
+ over_fun_function(Bef, []).
+over_fun_function(Bef, Acc) ->
+ case edlin_expand:over_word(Bef) of
+ {[$/|Bef1], Arity} -> over_fun_function(Bef1, [$/|Arity]++Acc);
+ {[$:|Bef1], Fun} -> over_fun_function(Bef1, [$:|Fun]++Acc);
+ {" nuf"++Bef1, ModOrFun} -> over_fun_function(Bef1, "fun "++ModOrFun ++ Acc);
+ _ -> {Bef,Acc}
+ end.
+
+%% Extracts everything within the quote
+over_to_opening_quote(Q, Bef) when Q == $'; Q == $" ->
+ over_to_opening_quote([Q], Bef, [Q]);
+over_to_opening_quote(_, Bef) -> {Bef, []}.
+over_to_opening_quote([], Bef, Word) -> {Bef, Word};
+over_to_opening_quote([Q|Stack], [Q|Bef], Word) ->
+ over_to_opening_quote(Stack, Bef, [Q| Word]);
+over_to_opening_quote([Q|Stack], [Q,EC|Bef], Word) when EC=:=$\\; EC=:=$$ ->
+ over_to_opening_quote([Q|Stack], Bef, [EC,Q| Word]);
+over_to_opening_quote([Stack], [C|Bef], Word) ->
+ over_to_opening_quote([Stack], Bef, [C| Word]);
+over_to_opening_quote(_,_,Word) -> {lists:reverse(Word), []}.
+
+matching_paren($(,$)) -> true;
+matching_paren($[,$]) -> true;
+matching_paren(${,$}) -> true;
+matching_paren($<,$>) -> true;
+matching_paren(_,_) -> false.
+
+%% Extracts everything within the brackets
+%% Recursively extracts nested bracket expressions.
+over_to_opening_paren(CC, Bef) when CC == $); CC == $];
+ CC == $}; CC == $> ->
+ over_to_opening_paren([CC], Bef, [CC]);
+over_to_opening_paren(_, Bef) -> {Bef, []}. %% Not a closing parenthesis
+over_to_opening_paren([], Bef, Word) -> {Bef, Word};
+over_to_opening_paren(_, [], Word) -> {lists:reverse(Word), []}; %% Not a closing parenthesis
+over_to_opening_paren([CC|Stack], [CC,$$|Bef], Word) ->
+ over_to_opening_paren([CC|Stack], Bef, [$$,CC|Word]);
+over_to_opening_paren([CC|Stack], [OC|Bef], Word) when OC==$(; OC==$[; OC==${; OC==$< ->
+ case matching_paren(OC, CC) of
+ true -> over_to_opening_paren(Stack, Bef, [OC|Word]);
+ false -> over_to_opening_paren([CC|Stack], Bef, [OC|Word])
+ end;
+over_to_opening_paren([CC|Stack], [CC|Bef], Word) -> %% Nested parenthesis of same type
+ over_to_opening_paren([CC,CC|Stack], Bef, [CC|Word]);
+over_to_opening_paren(Stack, [Q,NEC|Bef], Word) when Q == $"; Q == $', NEC /= $$, NEC /= $\\ ->
+ %% Consume the whole quoted text, it may contain parenthesis which
+ %% would have confused us.
+ {Bef1, QuotedWord} = over_to_opening_quote(Q, Bef),
+ over_to_opening_paren(Stack, Bef1, QuotedWord ++ Word);
+over_to_opening_paren(CC, [C|Bef], Word) -> over_to_opening_paren(CC, Bef, [C|Word]).
+
+%% Extract a whole keyword expression
+%% Keyword<code>end
+%% Function expects a string of erlang code in reverse, and extracts everything
+%% including a keyword being one of if, fun, case, maybe, receiver (need to add all here)
+%% Recursively extracts nested keyword expressions
+%% Note: In the future we could autocomplete case expressions by looking at the
+%% return type of the expression.
+over_keyword_expression(Bef) ->
+ over_keyword_expression(Bef, []).
+over_keyword_expression("dne"++Bef, Expr)->
+ %% Nested expression
+ {Bef1, KWE}=over_keyword_expression(Bef),
+ over_keyword_expression(Bef1, KWE++"end"++Expr);
+over_keyword_expression("fi"++Bef, Expr) -> {Bef, "if" ++ Expr};
+over_keyword_expression("nuf"++Bef, Expr) -> {Bef, "fun" ++ Expr};
+over_keyword_expression("yrt"++Bef, Expr) -> {Bef, "try" ++ Expr};
+over_keyword_expression("esac"++Bef, Expr) -> {Bef, "case" ++ Expr};
+over_keyword_expression("hctac"++Bef, Expr) ->
+ case over_keyword_expression(Bef, []) of
+ {Bef1, "try" ++ Expr1} -> {Bef1, "try" ++ Expr1 ++ "catch" ++ Expr};
+ _ -> {Bef, "catch" ++ Expr}
+ end;
+over_keyword_expression("nigeb"++Bef, Expr) -> {Bef, "begin" ++ Expr};
+over_keyword_expression("ebyam"++Bef, Expr) -> {Bef, "maybe" ++ Expr};
+over_keyword_expression("eviecer"++Bef, Expr) -> {Bef, "receive" ++ Expr};
+over_keyword_expression([], _) -> {no, [], []};
+over_keyword_expression([C|Bef], Expr) -> over_keyword_expression(Bef, [C|Expr]).
+
+odd_quotes(Q, [Q,C|Line], Acc) when C == $\\; C == $$ ->
+ odd_quotes(Q, Line, Acc);
+odd_quotes(Q, [Q|Line], Acc) ->
+ odd_quotes(Q, Line, Acc+1);
+odd_quotes(Q, [_|Line], Acc) ->
+ odd_quotes(Q, Line, Acc);
+odd_quotes(_, [], Acc) -> Acc band 1 == 1.
+odd_quotes(Q, Line) ->
+ odd_quotes(Q, Line, 0).
+
+over_module(Bef, Fun)->
+ case edlin_expand:over_word(Bef) of
+ {[$:|Bef1], _} ->
+ edlin_expand:over_word(Bef1);
+ {[], _} -> {Bef, edlin_expand:shell_default_or_bif(Fun)};
+ _ -> {Bef, edlin_expand:bif(Fun)}
+ end.
+
+%% Check that the given string starts with a capital letter, or an underscore
+%% followed by an alphanumeric grapheme.
+is_binding(Word) ->
+ Normalized = unicode:characters_to_nfc_list(Word),
+ nomatch =/= re:run(Normalized,
+ "^[_[:upper:]][[:alpha:]]*$",
+ [unicode, ucp]).
diff --git a/lib/stdlib/src/edlin_expand.erl b/lib/stdlib/src/edlin_expand.erl
index bc3de13750..9823534e9b 100644
--- a/lib/stdlib/src/edlin_expand.erl
+++ b/lib/stdlib/src/edlin_expand.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,47 +18,784 @@
%% %CopyrightEnd%
%%
-module(edlin_expand).
+%% a default expand function for edlin, expanding modules, functions
+%% filepaths, variable binding, record names, function parameter values,
+%% record fields and map keys and record field values.
+-include_lib("kernel/include/eep48.hrl").
+-export([expand/1, expand/2, expand/3, format_matches/2, number_matches/1, get_exports/1,
+ shell_default_or_bif/1, bif/1, over_word/1]).
+-export([is_type/3, match_arguments1/3]).
+-record(shell_state,{
+ bindings = [],
+ records = [],
+ functions = []
+ }).
-%% a default expand function for edlin, expanding modules and functions
+-spec expand(Bef0) -> {Res, Completion, Matches} when
+ Bef0 :: string(), %% a line of erlang expressions in reverse
+ Res :: 'yes' | 'no',
+ Completion :: string(),
+ Matches :: [Element] | [Section],
+ Element :: {string(), [ElementOption]},
+ ElementOption :: {ending, string()},
+ Section :: #{title:=string(), elems:=Matches, options:=SectionOption},
+ SectionOption :: {highlight_all} %% highlight the whole title
+ | {highlight, string()} %% highlight this part of the title
+ | {highlight_param, integer()} %% highlight this parameter
+ | {hide, title} %% hide the title
+ | {hide, result} %% hide the results
+ | {separator, string()}. %% specify another separator between title and result
+expand(Bef0) ->
+ expand(Bef0, [{legacy_output, true}]).
+
+-spec expand(Bef0, Opts) -> {Res, Completion, Matches} when
+ Bef0 :: string(), %% a line of erlang expressions in reverse
+ Opts :: [Option],
+ Option :: {legacy_output, boolean()},
+ Res :: 'yes' | 'no',
+ Completion :: string(),
+ Matches :: [Element] | [Section],
+ Element :: {string(), [ElementOption]},
+ ElementOption :: {ending, string()},
+ Section :: #{title:=string(), elems:=Matches, options:=SectionOption},
+ SectionOption :: {highlight_all} %% highlight the whole title
+ | {highlight, string()} %% highlight this part of the title
+ | {highlight_param, integer()} %% highlight this parameter
+ | {hide, title} %% hide the title
+ | {hide, result} %% hide the results
+ | {separator, string()}. %% specify another separator between title and result
+expand(Bef0, Opts) ->
+ ShellState = try
+ shell:get_state()
+ catch
+ _:_ ->
+ %% Running on a shell that does not support get_state()
+ #shell_state{bindings=[],records=[],functions=[]}
+ end,
+ expand(Bef0, Opts, ShellState).
+
+%% Only used for testing
+expand(Bef0, Opts, #shell_state{bindings = Bs, records = RT, functions = FT}) ->
+ LegacyOutput = proplists:get_value(legacy_output, Opts, false),
+ {_Bef1, Word} = over_word(Bef0),
+ {Res, Expansion, Matches} = case edlin_context:get_context(Bef0) of
+
+ {string} -> expand_string(Bef0);
+
+ {binding} -> expand_binding(Word, Bs);
+
+ {term} -> expand_module_function(Bef0, FT);
+ {term, _, {_, Unfinished}} -> expand_module_function(lists:reverse(Unfinished), FT);
+ {error, _Column} ->
+ {no, [], []};
+ {function} -> expand_module_function(Bef0, FT);
+ {fun_} -> expand_module_function(Bef0, FT);
+
+ {fun_, Mod} -> expand_function_name(Mod, Word, "/", FT);
+
+ %% Complete with arity in a 'fun mod:fun/' expression
+ {fun_, Mod, Fun} ->
+ Arities = [integer_to_list(A) || A <- get_arities(Mod, Fun)],
+ match(Word, Arities, "");
+ {new_fun, _ArgsString} -> {no, [], []};
+ %% Suggest type of function parameter
+ %% Complete an unfinished list, tuple or map using type of function parameter
+ {function, Mod, Fun, Args, Unfinished, Nesting} ->
+ Mod2 = case Mod of
+ "user_defined" -> "shell_default";
+ _ -> Mod
+ end,
+ FunExpansion = expand_function_type(Mod2, Fun, Args, Unfinished, Nesting, FT),
+ case Word of
+ [] -> FunExpansion;
+ _ ->
+ ModuleOrBifs = expand_helper(FT, module, Word, ":"),
+ Functions = case Args =/= [] andalso lists:last(Args) of
+ {atom, MaybeMod} -> expand_function_name(MaybeMod, Word, "", FT);
+ _ -> {no, [], []}
+ end,
+ fold_results([FunExpansion] ++ ModuleOrBifs ++ [Functions])
+ end;
+
+ %% Complete an unfinished key or suggest valid keys of a map binding
+ {map, Binding, Keys} -> expand_map(Word, Bs, Binding, Keys);
+
+ {map_or_record} ->
+ {[$#|Bef2], _} = over_word(Bef0),
+ {_, Var} = over_word(Bef2),
+ case Bs of
+ [] -> expand_record(Word, RT);
+ _ ->
+ case proplists:get_value(list_to_atom(Var), Bs) of
+ undefined ->
+ expand_record(Word, RT);
+ Map when is_map(Map) -> {yes, "{", []};
+ RecordTuple when is_tuple(RecordTuple), tuple_size(RecordTuple) > 0 ->
+ Atom = erlang:element(1, RecordTuple),
+ case (is_atom(Atom) andalso lists:keysearch(Atom, 1, RT)) of
+ {value, {Atom, _}} -> match(Word, [Atom], "{");
+ _ -> {no, [], []}
+ end;
+ _ -> {no, [], []}
+ end
+ end;
+
+ {record} -> expand_record(Word, RT);
+
+ {record, Record, Fields, FieldToComplete, Args, Unfinished, Nestings} ->
+ RecordExpansion = expand_record_fields(FieldToComplete, Unfinished, Record, Fields, RT, Args, Nestings, FT),
+ case Word of
+ [] -> RecordExpansion;
+ _ ->
+ ModuleOrBifs = expand_helper(FT, module,Word,":"),
+ fold_results([RecordExpansion] ++ ModuleOrBifs)
+ end;
+ _ -> {no, [], []}
+
+ end,
+ Matches1 = case {Res,number_matches(Matches)} of
+ {yes, 1} -> [];
+ _ -> Matches
+ end,
+ case LegacyOutput of
+ true -> {Res, Expansion, to_legacy_format(Matches1)};
+ false -> {Res, Expansion, Matches1}
+ end.
+expand_map(_, [], _, _) ->
+ {no, [], []};
+expand_map(Word, Bs, Binding, Keys) ->
+ case proplists:get_value(list_to_atom(Binding), Bs) of
+ Map when is_map(Map) ->
+ K1 = sets:from_list(maps:keys(Map)),
+ K2 = sets:subtract(K1, sets:from_list([list_to_atom(K) || K <- Keys])),
+ match(Word, sets:to_list(K2), "=>");
+ _ -> {no, [], []}
+ end.
+
+over_word(Bef) ->
+ {Bef1,_,_} = over_white(Bef, [], 0),
+ {Bef2, Word, _} = edlin:over_word(Bef1, [], 0),
+ {Bef2, Word}.
+
+
+expand_binding(Prefix, Bindings) ->
+ Alts = [strip_quotes(K) || {K,_} <- Bindings],
+ case match(Prefix, Alts, "") of
+ {_Res,_Expansion,[]}=M -> M;
+ {Res, Expansion, Matches} -> {Res,Expansion,[#{title=>"bindings", elems=>Matches, options=>[highlight_all]}]}
+ end.
+
+expand_record(Prefix, RT) ->
+ Alts = [Name || {Name, _} <- RT],
+ case match(Prefix, Alts, "{") of
+ {_Res,_Expansion,[]}=M -> M;
+ {Res, Expansion, Matches} -> {Res,Expansion,[#{title=>"records", elems=>Matches, options=>[highlight_all]}]}
+ end.
+
+expand_record_fields(FieldToComplete, Word, Record, Fields, RT, _Args, Nestings, FT) ->
+ Record2 = list_to_atom(Record),
+ FieldSet2 = sets:from_list([list_to_atom(F) || F <- Fields]),
+ FieldToComplete2 = list_to_atom(FieldToComplete),
+ Word1 = case Word of
+ {_, Word2} -> Word2;
+ [] -> []
+ end,
+ case [RecordSpec || {Record3, RecordSpec} <- RT, Record2 =:= Record3] of
+ [RecordType|_] ->
+ case sets:is_element(FieldToComplete2, FieldSet2) of
+ true ->
+ expand_record_field_content(FieldToComplete2, RecordType, Word1, Nestings, FT);
+ false ->
+ expand_record_field_name(Record2, FieldSet2, RecordType, Word1)
+ end;
+ _ ->
+ {no, [], []}
+ end.
+
+expand_record_field_name(Record, Fields, RecordType, Word) ->
+ RecordFieldsList = extract_record_fields(Record, RecordType),
+ RecordFieldsSet = sets:from_list(RecordFieldsList),
+ RecordFields = sets:subtract(RecordFieldsSet, Fields),
+ Alts = sets:to_list(RecordFields),
+ case match(Word, Alts, "=") of
+ {_Res,_Expansion,[]}=M -> M;
+ {Res, Expansion, Matches} -> {Res,Expansion,[#{title=>"fields", elems=>Matches, options=>[highlight_all]}]}
+ end.
--export([expand/1, format_matches/1]).
+expand_record_field_content(Field,
+ {attribute, _, record,
+ {_Record, FieldTypes}}, Word, Nestings, FT) ->
+ FieldTypesFiltered = [Type1 || {typed_record_field, {record_field, _, {_,_, F}}, Type1} <- FieldTypes, F == Field] ++
+ [Type1 || {typed_record_field, {record_field, _, {_,_, F}, _}, Type1} <- FieldTypes, F == Field],
+ case FieldTypesFiltered of
+ [] -> {no, [], []};
+ [Type] ->
+ T = edlin_type_suggestion:type_tree(erlang, Type, Nestings, FT),
+ Types = edlin_type_suggestion:get_types([], T, Nestings),
+ case Nestings of
+ [] ->
+ Atoms = edlin_type_suggestion:get_atoms([], T, Nestings),
+ case {Word, match(Word, Atoms, ", ")} of
+ {[],{_Res,_Expansion,_}} -> {_Res, _Expansion, [#{title=>"types", elems=>Types, options=>[{hide, title}]}]};
+ {_,{_Res,_Expansion,[]}=M} -> M;
+ {_,{Res,Expansion,Matches}} -> {Res, Expansion, [#{title=>"matches", elems=>Matches, options=>[highlight_all]}]}
+ end;
+ _ ->
+ expand_nesting_content(T, [], Nestings, #{title=>"types", elems=>Types, options=>[{hide, title}]})
+ end
+ end.
+
+%% Check that the actual type on previous arguments
+%% matches with the expected types
+%% Since we are not doing any evaluations at this point we
+%% don't know if a parenthesis, keyword, var, call or fun returns
+%% a value with the wrong type.
+match_arguments({function, {{parameters, Ps}, _}, Cs}, As) ->
+ match_arguments1(Ps, Cs, As);
+match_arguments({{parameters, Ps}, _}, As) ->
+ match_arguments1(Ps, [], As).
+match_arguments1(_,_,[]) -> true;
+%% Just assume that it will evaluate to the correct type.
+match_arguments1([_|Ps], Cs, [{parenthesis, _}|As]) ->
+ match_arguments1(Ps, Cs, As);
+match_arguments1([_|Ps], Cs, [{operation, _}|As]) ->
+ match_arguments1(Ps, Cs, As);
+match_arguments1([_|Ps], Cs, [{keyword, _}|As]) ->
+ match_arguments1(Ps, Cs, As);
+match_arguments1([_|Ps], Cs, [{var, _}|As]) ->
+ match_arguments1(Ps, Cs, As);
+match_arguments1([_|Ps], Cs, [{call, _}|As]) ->
+ match_arguments1(Ps, Cs, As);
+match_arguments1([_|Ps], Cs, [{fun_, _}|As]) ->
+ match_arguments1(Ps, Cs, As);
+match_arguments1([P|Ps], Cs, [{atom, [$'|_]=String}|As]) ->
+ case edlin_context:odd_quotes($', lists:reverse(String)) of
+ true -> false; % we know that the atom is unfinished, and thus cannot match any valid atom
+ _ -> case is_type(P, Cs, String) of
+ true -> match_arguments1(Ps, Cs, As);
+ false -> false
+ end
+ end;
+match_arguments1([P|Ps], Cs, [{_, String}|As]) ->
+ case is_type(P, Cs, String) of
+ true -> match_arguments1(Ps, Cs, As);
+ false -> false
+ end.
+
+is_type(Type, Cs, String) ->
+ {ok, A, _} = erl_scan:string(String++"."),
+ Types = [T || T <- edlin_type_suggestion:get_types(Cs, Type, [], [no_print]) ],
+ try
+ {ok, Term} = erl_parse:parse_term(A),
+ case Term of
+ Atom when is_atom(Atom) ->
+ Atoms = edlin_type_suggestion:get_atoms(Cs, Type, []),
+ lists:member(to_list(Atom), Atoms) orelse
+ lists:member(atom_to_list(Atom), Atoms) orelse
+ find_type(Types, [atom, node, module, 'fun']);
+ Tuple when is_tuple(Tuple) -> find_type(Types, [tuple]);
+ Map when is_map(Map) -> find_type(Types, [map]);
+ Binary when is_binary(Binary) -> find_type(Types, [binary]);
+ Float when is_float(Float) -> find_type(Types, [float]);
+ Integer when is_integer(Integer) -> check_integer_type(Types, Integer);
+ List when is_list(List), length(List) > 0 ->
+ find_type(Types, [list, string, nonempty_list,maybe_improper_list, nonempty_improper_list]);
+ List when is_list(List) -> find_type(Types, [list, string, maybe_improper_list])
+ end
+ catch
+ _:_ ->
+ %% Types not possible to deduce with erl_parse
+ % If string contains variables, erl_parse:parse_term will fail, but we
+ % consider them valid sooo.. lets replace them with the atom var
+ B = [(fun({var, Anno, _}) -> {atom, Anno, var}; (Token) -> Token end)(X) || X <- A],
+ try
+ {ok, Term2} = erl_parse:parse_term(B),
+ case Term2 of
+ Tuple2 when is_tuple(Tuple2) -> find_type(Types, [tuple]);
+ Map2 when is_map(Map2) -> find_type(Types, [map]);
+ Binary2 when is_binary(Binary2) -> find_type(Types, [binary]);
+ List2 when is_list(List2), length(List2) > 0 ->
+ find_type(Types, [list, string, nonempty_list,maybe_improper_list, nonempty_improper_list]);
+ List2 when is_list(List2) -> find_type(Types, [list, string, maybe_improper_list])
+ end
+ catch
+ _:_ ->
+ case A of
+ [{'#',_},{var,_,'Port'},{'<',_},{float,_,_},{'>',_},{dot,_}] -> find_type(Types, [port]);
+ [{'#',_},{var,_,'Ref'},{'<',_},{float,_,_},{'.',_},{float,_,_},{'>',_},{dot,_}] -> find_type(Types, [reference]);
+ [{'fun',_},{'(',_} | _] -> find_type(Types, [parameters, function, 'fun']);
+ [{'#',_},{var,_,'Fun'},{'<',_},{atom,_,erl_eval},{'.',_},{float,_,_},{'>',_}] -> find_type(Types, [parameters, function, 'fun']);
+ [{'<', _}, {float, _, _}, {'.', _}, {integer, _, _}, {'>', _}, {dot, _}] -> find_type(Types, [pid]);
+ [{'#', _}, {atom, _, RecordName},{'{', _}| _] -> find_type(Types, [{record, RecordName}]);
+ _ -> false
+ end
+ end
+ end.
+
+find_type([],_) -> false;
+find_type([any|_], _) -> true; % If we find any then every type is valid
+find_type([{type, any, []}|_], _) -> true;
+find_type([{{parameters, _},_}|Types], ValidTypes) ->
+ case lists:member(parameters, ValidTypes) of
+ true -> true;
+ false -> find_type(Types, ValidTypes)
+ end;
+find_type([{record, _}=Type|Types], ValidTypes) ->
+ case lists:member(Type, ValidTypes) of
+ true -> true;
+ false -> find_type(Types, ValidTypes)
+ end;
+find_type([{Type, _}|Types], ValidTypes) ->
+ case lists:member(Type, ValidTypes) of
+ true -> true;
+ false -> find_type(Types, ValidTypes)
+ end;
+find_type([{type, Type, _}|Types], ValidTypes) ->
+ case lists:member(Type, ValidTypes) of
+ true -> true;
+ false -> find_type(Types, ValidTypes)
+ end;
+find_type([{type, Type, _, any}|Types], ValidTypes) ->
+ case lists:member(Type, ValidTypes) of
+ true -> true;
+ false -> find_type(Types, ValidTypes)
+ end;
+find_type([_|Types], ValidTypes) -> find_type(Types, ValidTypes).
+
+in_range(_, []) -> false;
+in_range(Integer, [{type, range, [{integer, Start}, {integer, End}]}|_]) when Start =< Integer, Integer =< End -> true;
+in_range(Integer, [_|Types]) -> in_range(Integer, Types).
+
+check_integer_type(Types, Integer) when Integer == 0 -> find_type(Types, [integer, non_neg_integer, arity]) orelse in_range(Integer, Types);
+check_integer_type(Types, Integer) when Integer < 0 -> find_type(Types, [integer, neg_integer]) orelse in_range(Integer, Types);
+check_integer_type(Types, Integer) when Integer > 0 -> find_type(Types, [integer, non_neg_integer, pos_integer]) orelse in_range(Integer, Types).
--import(lists, [reverse/1, prefix/2]).
+add_to_last_nesting(Term, Nesting) ->
+ Last = lists:last(Nesting),
+ List = lists:droplast(Nesting),
+ case Last of
+ {tuple, Args, U} ->
+ List ++ [{tuple, Args ++ [Term], U}];
+ {list, Args, U} ->
+ List ++ [{list, Args ++ [Term], U}];
+ {map, F, Fs, Args, U} ->
+ List ++ [{map, F, Fs, Args ++ [Term], U}]
+ end.
+
+close_nesting(Nesting) ->
+ Last = lists:last(Nesting),
+ case Last of
+ {tuple, _Args, _} ->
+ "}";
+ {list, _Args, _} ->
+ "]";
+ {map, _F, _Fs, _Args, _} ->
+ "}"
+ end.
+expand_function_parameter_type(Mod, MFA, FunType, Args, Unfinished, Nestings, FT) ->
+ TypeTree = edlin_type_suggestion:type_tree(Mod, FunType, Nestings, FT),
+
+ {Parameters, Constraints1} = case TypeTree of
+ {function, {{parameters, Parameters1},_}, Constraints} ->
+ {Parameters1, Constraints};
+ {{parameters, Parameters1},_}=_F ->
+ {Parameters1, []}
+ end,
+ case match_arguments(TypeTree, Args) of
+ false -> {no, [], []};
+ true when Parameters == [] -> {yes, ")", [#{title=>MFA, elems=>[")"], options=>[]}]};
+ true ->
+ Parameter = lists:nth(length(Args)+1, Parameters),
+ {T, _Name} = case Parameter of
+ Atom when is_atom(Atom) -> {Atom, atom_to_list(Atom)};
+ {var, Name1}=T1 -> {T1, atom_to_list(Name1)};
+ {ann_type, {var, Name1}, T1} -> {T1, atom_to_list(Name1)};
+ T1 -> {T1, edlin_type_suggestion:print_type(T1, [], [{first_only, true}])}
+ end,
+ Ts = edlin_type_suggestion:get_types(Constraints1, T, Nestings),
+ Types = case Ts of
+ [] -> [];
+ _ ->
+ SectionTypes = [S || #{}=S <- Ts],
+ Types1 = case [E || {_, _}=E<-Ts] of
+ [] -> SectionTypes;
+ Elems ->
+ case SectionTypes of
+ [] -> Elems;
+ ST -> [#{title=>"simple types", elems=>Elems, options=>[{hide, title}]}|ST]
+ end
+ end,
+
+ [#{title=>"types", elems=>(Types1), options=>[{hide, title}]}]
+ end,
+ case Nestings of
+ [] -> %% Expand function type
+ case Unfinished of
+ [] ->
+ case T of
+ Atom1 when is_atom(Atom1) ->
+ CC = case length(Args)+1 < length(Parameters) of
+ true -> ", ";
+ false ->")"
+ end,
+ {Res, Expansion, Matches} = match([], [Atom1], CC),
+ case Matches of
+ [] -> {no, [], []};
+ _ -> {Res, Expansion, [#{title=>MFA, elems=>[], options=>[{highlight_param, length(Args)+1}]}]}
+ end;
+ _ when Types == [] ->
+ {no, [], []};
+ _ ->
+ {no, [], [#{title=>MFA, elems=>Types, options=>[{highlight_param, length(Args)+1}]}]}
+ end;
+ {_, Word} when is_atom(T) ->
+ CC = case length(Args)+1 < length(Parameters) of
+ true -> ", ";
+ false ->")"
+ end,
+ {Res, Expansion, Matches} = match(Word, [T], CC),
+ case Matches of
+ [] -> {no, [], []};
+ _ -> {Res, Expansion, [#{title=>MFA, elems=>[], options=>[{highlight_param, length(Args)+1}]}]}
+ end;
+ {_, Word} ->
+ {Res, Expansion, Matches} = begin
+ CC = case length(Args)+1 < length(Parameters) of
+ true -> ", ";
+ false ->")"
+ end,
+ Atoms1 = edlin_type_suggestion:get_atoms(Constraints1, T, Nestings),
+ {Res1, Expansion1, Matches1} = match(Word, Atoms1, CC),
+ case Matches1 of
+ [] ->
+ case match_arguments(TypeTree, Args ++ [Unfinished]) of
+ false -> {Res1, Expansion1, Matches1};
+ true ->
+ {yes, CC, [{CC, []}]}
+ end;
+ _ ->
+ {Res1, Expansion1, Matches1}
+ end
+ end,
+ Match1 = case Matches of
+ [] -> [];
+ _ -> Atoms = [#{title=>"atoms", elems=>Matches, options=>[{hide, title}]}],
+ [#{title=>MFA, elems=>Atoms, options=>[{highlight_param, length(Args)+1}]}]
+ end,
+ {Res, Expansion,Match1}
+ end;
+ _ -> %% Expand last nesting types
+ expand_nesting_content(T, Constraints1, Nestings, #{title=>MFA, elems=>Types, options=>[{highlight_param, length(Args)+1}]})
+ end
+ end.
+expand_nesting_content(T, Constraints, Nestings, Section) ->
+ {NestingType, UnfinishedNestingArg, NestingArgs} = case lists:last(Nestings) of
+ {tuple, NestingArgs1, Unfinished1} -> {tuple, Unfinished1, NestingArgs1};
+ {list, NestingArgs1, Unfinished1} -> {list, Unfinished1, NestingArgs1};
+ {map, _, _, NestingArgs1, Unfinished1} -> {map, Unfinished1, NestingArgs1}
+ end,
+ %% in the case of
+ %% erlang:system_info({allocator, )
+ %% we have a tuple nesting with an atom
+ %% this should give us "allocator" in the nestingsargs, and empty unfinished part
+ %% but we also know that we have a nesting, if we expect something other than a tuple, we shouldnt print that function
+ %% lets call it NestingType
+ %% now when that is fixed, how do we filter {allocator_sizes, ...} and others
+ Types = [Ts || Ts <- edlin_type_suggestion:get_types(Constraints, T, lists:droplast(Nestings), [no_print]) ],
+ case UnfinishedNestingArg of
+ [] ->
+ case find_type(Types, [NestingType]) of
+ true ->
+ %% if we know had a tuple, {allocator_sizes, } will be allowed
+ %% probably get_arity will return none
+ Nestings2 = add_to_last_nesting({var, "Var"}, Nestings),
+ NestingArities = edlin_type_suggestion:get_arity(Constraints, T, Nestings2),
+
+ fold_results([begin
+ case NestingArity of
+ none -> {no, [], []};
+ _ -> {no, [], [Section]}
+ end
+ end || NestingArity <- NestingArities]);
+ false -> {no, [], []}
+ end;
+ {_, Word} ->
+ Atoms1 = edlin_type_suggestion:get_atoms(Constraints, T, Nestings),
+ {Res1, Expansion1, Matches1} = match(Word, Atoms1, ""),
+ {Res, Expansion, Matches} = case Matches1 of
+ [] ->
+ Nestings2 = add_to_last_nesting(UnfinishedNestingArg, Nestings),
+ NestingArities = edlin_type_suggestion:get_arity(Constraints, T, Nestings2),
+ fold_results([begin
+ case NestingArity of
+ none -> {no, [], []};
+ _ when NestingType =:= tuple ->
+ CC = case length(NestingArgs)+1 < NestingArity of
+ true -> ", ";
+ false -> close_nesting(Nestings)
+ end,
+ {yes, CC, [{CC, []}]};
+ _ when NestingType =:= list ->
+ {no, [], [{", ", []}, {"]", []}]};
+ _ when NestingType =:= map ->
+ {no, [], [{", ",[]},{"}", []}]};
+ _ ->
+ {no, [], []}
+ end
+ end || NestingArity <- NestingArities]);
+ [{Word2,_}] ->
+ Nestings2 = add_to_last_nesting({atom, Word2}, Nestings),
+ NestingArities = edlin_type_suggestion:get_arity(Constraints, T, Nestings2),
+ fold_results([begin
+ case NestingArity of
+ none -> {no, [], []};
+ _ when NestingType =:= tuple ->
+ CC = case length(NestingArgs)+1 < NestingArity of
+ true -> ", ";
+ false -> close_nesting(Nestings)
+ end,
+ {yes, Expansion1++CC, [{Word2, [{ending, CC}]}]};
+ _ ->
+ {Res1, Expansion1, Matches1}
+ end
+ end || NestingArity <- NestingArities]);
+ _ -> {Res1, Expansion1, Matches1}
+ end,
+ Match1 = case Matches of
+ [] -> [];
+ _ -> Atoms = [#{title=>"atoms", elems=>Matches, options=>[{hide, title}]}],
+ [Section#{elems:=Atoms}]
+ end,
+ {Res, Expansion, Match1}
+ end.
+
+extract_record_fields(Record, {attribute,_,record,{Record, Fields}})->
+ [X || X <- [extract_record_field(F) || F <- Fields], X /= []];
+extract_record_fields(_, _)-> error.
+extract_record_field({typed_record_field, {_, _,{atom, _, Field}},_})->
+ Field;
+extract_record_field({typed_record_field, {_, _,{atom, _, Field}, _},_})->
+ Field;
+extract_record_field({record_field, _,{atom, _, Field},_})->
+ Field;
+extract_record_field({record_field, _,{atom, _, Field}})->
+ Field;
+extract_record_field(_) -> [].
+
+fold_results([]) -> {no, [], []};
+fold_results([R|Results]) ->
+ lists:foldl(fun fold_completion_result/2, R, Results).
+
+fold_completion_result({yes, Cmp1, Matches1}, {yes, Cmp2, Matches2}) ->
+ {_, Cmp} = longest_common_head([Cmp1,Cmp2]),
+ case Cmp of
+ [] -> {no, [], ordsets:union([Matches1,Matches2])};
+ _ -> {yes, Cmp, ordsets:union([Matches1,Matches2])}
+ end;
+fold_completion_result({yes, Cmp, Matches}, {no, [], []}) ->
+ {yes, Cmp, Matches};
+fold_completion_result({no, [], []},{yes, Cmp, Matches}) ->
+ {yes, Cmp, Matches};
+fold_completion_result({_, _, Matches1}, {_, [], Matches2}) ->
+ {no, [], ordsets:union([Matches1,Matches2])};
+fold_completion_result(A, B) ->
+ fold_completion_result(B,A).
+
+expand_function_type(ModStr, FunStr, Args, Unfinished, Nestings, FT) ->
+ Mod = list_to_atom(ModStr),
+ Fun = list_to_atom(FunStr),
+ MinArity = if Unfinished =:= [], length(Args) =:= 0 -> 0;
+ true -> length(Args)+1
+ end,
+ case [A || A <- get_arities(ModStr, FunStr, FT), A >= MinArity] of
+ [] -> {no, [], []};
+ Arities ->
+ {Res, Expansion, Matches} = fold_results([begin
+ FunTypes = edlin_type_suggestion:get_function_type(Mod, Fun, Arity, FT),
+ case FunTypes of
+ [] -> MFA = print_function_head(ModStr, FunStr, Arity),
+ case Unfinished of
+ [] -> {no, [], [#{title=>MFA, elems=>[], options=>[]}]};
+ _ -> {no, [], []}
+ end;
+ _ ->
+ fold_results([begin
+ MFA = print_function_head(ModStr, FunStr, FunType, FT),
+ expand_function_parameter_type(Mod, MFA, FunType, Args, Unfinished, Nestings, FT)
+ end || FunType <- FunTypes])
+ end
+ end || Arity <- Arities]),
+ case Matches of
+ [] -> {Res, Expansion, Matches};
+ _ -> {Res, Expansion, [#{title=>"typespecs", elems=>Matches, options=>[highlight_all]}]}
+ end
+ end.
-%% expand(CurrentBefore) ->
-%% {yes, Expansion, Matches} | {no, Matches}
+%% Behaves like zsh
+%% filters all files starting with . unless Word starts with .
+%% outputs / on end of folders
+expand_filepath(PathPrefix, Word) ->
+ Path = case PathPrefix of
+ [$/|_] -> PathPrefix;
+ _ ->
+ {ok, Cwd} = file:get_cwd(),
+ Cwd ++ "/" ++ PathPrefix
+ end,
+ ShowHidden = case Word of
+ "." ++ _ -> true;
+ _ -> false
+ end,
+ Entries = case file:list_dir(Path) of
+ {ok, E} -> lists:map(
+ fun(X)->
+ case filelib:is_dir(Path ++ "/" ++ X) of
+ true -> X ++ "/";
+ false -> X
+ end
+ end, [".."|E]);
+ _ -> []
+ end,
+ EntriesFiltered = [File || File <- Entries,
+ case File of
+ [$.|_] -> ShowHidden;
+ _ -> true
+ end],
+ case match(Word, EntriesFiltered, []) of
+ {yes, Cmp, [Match]} ->
+ case filelib:is_dir(Path ++ "/" ++ Word ++ Cmp) of
+ true -> {yes, Cmp, [Match]};
+ false -> {yes, Cmp ++ "\"", [Match]}
+ end;
+ X -> X
+ end.
+
+shell(Fun) ->
+ case shell:local_func(list_to_atom(Fun)) of
+ true -> "shell";
+ false -> "user_defined"
+ end.
+
+shell_default_or_bif(Fun) ->
+ case lists:member(list_to_atom(Fun), [E || {E,_}<-get_exports(shell_default)]) of
+ true -> "shell_default";
+ _ -> bif(Fun)
+ end.
+bif(Fun) ->
+ case lists:member(list_to_atom(Fun), [E || {E,A}<-get_exports(erlang), erl_internal:bif(E,A)]) of
+ true -> "erlang";
+ _ -> shell(Fun)
+ end.
+
+expand_string(Bef0) ->
+ case over_filepath(Bef0, []) of
+ {_, Filepath} ->
+ {Path, File} = split_at_last_slash(Filepath),
+ expand_filepath(Path, File);
+ _ -> {no, [], []}
+ end.
+%% Extract a whole filepath
+%% Stops as soon as we hit a double quote (")
+%% and returns everything it found before stopping.
+%% assumes the string is not a filepath if it contains unescaped spaces
+over_filepath([],_) -> none;
+over_filepath([$", $\\|Bef1], Filepath) -> over_filepath(Bef1, [$" | Filepath]);
+over_filepath([$"|Bef1], Filepath) -> {Bef1, Filepath};
+over_filepath([$\ ,$\\|Bef1], Filepath) -> over_filepath(Bef1, [$\ |Filepath]);
+over_filepath([$\ |_], _) -> none;
+over_filepath([C|Bef1], Filepath) ->
+ over_filepath(Bef1, [C|Filepath]).
+split_at_last_slash(Filepath) ->
+ {File, Path} = lists:splitwith(fun(X)->X/=$/ end, lists:reverse(Filepath)),
+ {lists:reverse(Path), lists:reverse(File)}.
+
+print_function_head(ModStr, FunStr, Arity) ->
+ lists:flatten(ModStr ++ ":" ++ FunStr ++ "/" ++ integer_to_list(Arity)).
+print_function_head(ModStr, FunStr, FunType, FT) ->
+ lists:flatten(print_function_head_from_type(ModStr, FunStr, FunType, FT)).
+
+print_function_head1(Mod, Fun, Par, _Ret) ->
+ Mod++":"++Fun++"("++lists:join(", ",
+ [case P of
+ Atom when is_atom(Atom) -> atom_to_list(Atom);
+ {var, V} -> atom_to_list(V);
+ {ann_type, {var, V}, _T} -> atom_to_list(V);
+ T -> edlin_type_suggestion:print_type(T, [], [{first_only, true}])
+ end || {_N,P} <- lists:enumerate(Par)])++")".
+print_function_head_from_type(Mod, Fun, FunType, FT) ->
+ case edlin_type_suggestion:type_tree(list_to_atom(Mod), FunType, [], FT) of
+ {function, {{parameters, Parameters},{return, Return}}, _} ->
+ print_function_head1(Mod, Fun, Parameters, Return);
+ {{parameters, Parameters},{return, Return}} ->
+ print_function_head1(Mod, Fun, Parameters, Return)
+ end.
+
+%% expand_module_function(CurrentBefore, FT) -> {yes, Expansion, Matches} | {no, [], Matches}
%% Try to expand the word before as either a module name or a function
%% name. We can handle white space around the seperating ':' but the
%% function name must be on the same line. CurrentBefore is reversed
%% and over_word/3 reverses the characters it finds. In certain cases
-%% possible expansions are printed.
+%% possible expansions are printed.´´´
%%
-%% The function also handles expansion with "h(" for module and functions.
-expand(Bef0) ->
+%% The function also handles expansion with "h(" and "ht("" for module and functions.
+expand_module_function(Bef0, FT) ->
{Bef1,Word,_} = edlin:over_word(Bef0, [], 0),
case over_white(Bef1, [], 0) of
{[$,|Bef2],_White,_Nwh} ->
- {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
- {Bef4,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
+ {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
+ {Bef4,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
case expand_function(Bef4) of
help ->
- expand_function_name(Mod, Word, ",");
+ expand_function_name(Mod, Word, ", ", FT);
+ help_type ->
+ expand_type_name(Mod, Word, ", ");
_ ->
- expand_module_name(Word, ",")
+ fold_results(expand_helper(FT, module, Word, ":"))
end;
{[$:|Bef2],_White,_Nwh} ->
- {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
- {_,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
- expand_function_name(Mod, Word, "(");
- {_,_,_} ->
+ {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
+ {_,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
+ expand_function_name(Mod, Word, "(", FT);
+ {[CC, N_Esc|_], _White, _Nwh} when (CC =:= $] orelse CC =:= $) orelse CC =:= $> orelse CC =:= $}
+ orelse CC =:= $" orelse CC =:= $'),
+ N_Esc =/= $$, N_Esc =/= $- ->
+ {no, [], []};
+ {[], _, _} ->
+ case Word of
+ [] -> {no, [], []}; %fold_results([expand_shell_default(Word), expand_user_defined_functions(FT, Word)]);
+ _ -> fold_results(expand_helper(FT, all, Word, ":"))
+ end;
+ {_,_,_} ->
+ case Word of
+ [] -> {no, [], []};
+ _ ->
+ TypeOfExpand = expand_function(Bef1),
CompleteChar
- = case expand_function(Bef1) of
- help -> ",";
+ = case TypeOfExpand of
+ help -> ", ";
+ help_type -> ", ";
_ -> ":"
end,
- expand_module_name(Word, CompleteChar)
+ fold_results(expand_helper(FT, TypeOfExpand, Word, CompleteChar))
+ end
end.
-
+expand_keyword(Word) ->
+ Keywords = ["begin", "case", "of", "receive", "after", "maybe", "try", "catch", "throw", "if", "fun", "when", "end"],
+ {Res, Expansion, Matches} = match(Word, Keywords, ""),
+ case Matches of
+ [] -> {no, [], []};
+ [{Word, _}] -> {no, [], []}; %% exact match
+ _ -> {Res,Expansion,[#{title=>"keywords", elems=>Matches, options=>[highlight_all]}]}
+ end.
+expand_helper(_, help, Word, CompleteChar) ->
+ [expand_module_name(Word, CompleteChar)];
+expand_helper(_, help_type, Word, CompleteChar) ->
+ [expand_module_name(Word, CompleteChar)];
+expand_helper(FT, all, Word, CompleteChar) ->
+ [expand_module_name(Word, CompleteChar), expand_bifs(Word), expand_shell_default(Word),
+ expand_user_defined_functions(FT, Word), expand_keyword(Word)];
+expand_helper(FT, _, Word, CompleteChar) ->
+ [expand_module_name(Word, CompleteChar), expand_bifs(Word),
+ expand_user_defined_functions(FT, Word), expand_keyword(Word)].
expand_function("("++Str) ->
case edlin:over_word(Str, [], 0) of
{_,"h",_} ->
@@ -71,68 +808,157 @@ expand_function("("++Str) ->
expand_function(_) ->
module.
+expand_bifs(Prefix) ->
+ Alts = [EA || {E,A}=EA <- get_exports(erlang), erl_internal:bif(E,A)],
+ CC = "(",
+ case match(Prefix, Alts, CC) of
+ {_Res,_Expansion,[]}=M -> M;
+ {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"bifs", elems=>Matches, options=>[highlight_all]}]}
+ end.
+
+expand_shell_default(Prefix) ->
+ Alts = get_exports(shell_default) ++ shell:local_func(),
+ CC = "(",
+ case match(Prefix, Alts, CC) of
+ {_Res,_Expansion,[]}=M -> M;
+ {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"commands",elems=>Matches, options=>[highlight_all]}]}
+ end.
+
+expand_user_defined_functions(FT, Prefix) ->
+ Alts = [{Name, Arity}||{{function, {_, Name, Arity}}, _} <- FT],
+ CC = "(",
+ case match(Prefix, Alts, CC) of
+ {_Res,_Expansion,[]}=M -> M;
+ {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"user_defined", elems=>Matches, options=>[highlight_all]}]}
+ end.
+
expand_module_name("",_) ->
{no, [], []};
-expand_module_name(Prefix,CompleteChar) ->
- match(Prefix, [{list_to_atom(M),P} || {M,P,_} <- code:all_available()], CompleteChar).
+expand_module_name(Prefix,CC) ->
+ Alts = [{list_to_atom(M),""} || {M,_,_} <- code:all_available()],
+ case match(Prefix, Alts, CC) of
+ {_Res,_Expansion,[]}=M -> M;
+ {Res,Expansion, Matches} -> {Res,Expansion,[#{title=>"modules", elems=>Matches, options=>[highlight_all]}]}
+ end.
-expand_function_name(ModStr, FuncPrefix, CompleteChar) ->
+get_arities("shell_default"=ModStr, FuncStr, FT) ->
+ case [A || {{function, {_, Fun, A}}, _} <- FT, Fun =:= list_to_atom(FuncStr)] of
+ [] -> get_arities(ModStr, FuncStr);
+ Arities -> Arities
+ end;
+get_arities(ModStr, FuncStr, _) ->
+ get_arities(ModStr, FuncStr).
+get_arities(ModStr, FuncStr) ->
case to_atom(ModStr) of
- {ok, Mod} ->
- Exports =
- case erlang:module_loaded(Mod) of
- true ->
- Mod:module_info(exports);
- false ->
- case beam_lib:chunks(code:which(Mod), [exports]) of
- {ok, {Mod, [{exports,E}]}} ->
- E;
- _ ->
- {no, [], []}
- end
- end,
- case Exports of
+ {ok, Mod} ->
+ Exports = get_exports(Mod),
+ lists:sort(
+ [A || {H, A} <- Exports, string:equal(FuncStr, flat_write(H))]);
+ error ->
+ {no, [], []}
+ end.
+
+get_exports(Mod) ->
+ case erlang:module_loaded(Mod) of
+ true ->
+ Mod:module_info(exports);
+ false ->
+ case beam_lib:chunks(code:which(Mod), [exports]) of
+ {ok, {Mod, [{exports,E}]}} ->
+ E;
+ _ ->
+ []
+ end
+ end.
+
+expand_function_name(ModStr, FuncPrefix, CompleteChar, FT) ->
+ case to_atom(ModStr) of
+ {ok, Mod} ->
+ Extra = case Mod of
+ shell_default -> [{Name, Arity}||{{function, {_, Name, Arity}}, _} <- FT];
+ _ -> []
+ end,
+ Exports = get_exports(Mod) ++ Extra,
+ {Res, Expansion, Matches}=Result = match(FuncPrefix, Exports, CompleteChar),
+ case Matches of
+ [] -> Result;
+ _ -> {Res, Expansion, [#{title=>"functions", elems=>Matches, options=>[highlight_all]}]}
+ end;
+ error ->
+ {no, [], []}
+ end.
+
+get_module_types(Mod) ->
+ case code:get_doc(Mod, #{sources => [debug_info]}) of
+ {ok, #docs_v1{ docs = Docs } } ->
+ [{T, A} || {{type, T, A},_Anno,_Sig,_Doc,_Meta} <- Docs];
+ _ -> {no, [], []}
+ end.
+
+expand_type_name(ModStr, TypePrefix, CompleteChar) ->
+ case to_atom(ModStr) of
+ {ok, Mod} ->
+ case get_module_types(Mod) of
{no, [], []} ->
{no, [], []};
- Exports ->
- match(FuncPrefix, Exports, CompleteChar)
+ Types ->
+ {Res, Expansion, Matches}=Result = match(TypePrefix, Types, CompleteChar),
+ case Matches of
+ [] -> Result;
+ _ -> {Res, Expansion, [#{title=>"types", elems=>Matches, options=>[highlight_all]}]}
+ end
end;
- error ->
- {no, [], []}
+ error ->
+ {no, [], []}
end.
-%% if it's a quoted atom, atom_to_list/1 will do the wrong thing.
to_atom(Str) ->
case erl_scan:string(Str) of
- {ok, [{atom,_,A}], _} ->
- {ok, A};
- _ ->
- error
+ {ok, [{atom,_,A}], _} ->
+ {ok, A};
+ _ ->
+ error
end.
+to_list(Atom) ->
+ io_lib:write_atom(Atom).
+
+strip_quotes(Atom) ->
+ [C || C<-atom_to_list(Atom), C/=$'].
+
+match_preprocess_alt({_,_}=Alt) -> Alt;
+match_preprocess_alt(X) -> {X, ""}.
+
match(Prefix, Alts, Extra0) ->
+ Alts2 = [match_preprocess_alt(A) || A <- Alts],
Len = string:length(Prefix),
Matches = lists:sort(
- [{S, A} || {H, A} <- Alts,
- prefix(Prefix, S=flat_write(H))]),
+ [{S, A} || {H, A} <- Alts2,
+ lists:prefix(Prefix, S=flat_write(H))]),
+ Matches2 = lists:usort(
+ case Extra0 of
+ [] -> [{S,[]} || {S,_} <- Matches];
+ _ -> [{S,[{ending, Extra0}]} || {S,_} <- Matches]
+ end),
case longest_common_head([N || {N, _} <- Matches]) of
- {partial, []} ->
- {no, [], Matches}; % format_matches(Matches)};
- {partial, Str} ->
+ {partial, []} ->
+ {no, [], Matches2};
+ {partial, Str} ->
case string:slice(Str, Len) of
- [] ->
- {yes, [], Matches}; % format_matches(Matches)};
- Remain ->
- {yes, Remain, []}
- end;
- {complete, Str} ->
- Extra = case {Extra0,Matches} of
- {"(",[{Str,0}]} -> "()";
- {_,_} -> Extra0
- end,
- {yes, string:slice(Str, Len) ++ Extra, []};
- no ->
- {no, [], []}
+ [] ->
+ {yes, [], Matches2};
+ Remain ->
+ {yes, Remain, Matches2}
+ end;
+ {complete, Str} ->
+ Extra = case {Extra0,Matches} of
+ {"/",[{Str,N}]} when is_integer(N) -> "/"++integer_to_list(N);
+ {"(",[{Str,0}]} -> "()";
+ {_,_} -> Extra0
+ end,
+ {yes, string:slice(Str, Len) ++ Extra, ordsets:from_list(Matches2)};
+ no ->
+ {no, [], []}
end.
flat_write(T) when is_atom(T) ->
@@ -140,79 +966,198 @@ flat_write(T) when is_atom(T) ->
flat_write(S) ->
S.
-%% Return the list of names L in multiple columns.
-format_matches(L) ->
- {S1, Dots} = format_col(lists:sort(L), []),
- S = case Dots of
- true ->
- {_, Prefix} = longest_common_head(vals(L)),
- PrefixLen = string:length(Prefix),
- case PrefixLen =< 3 of
- true -> S1; % Do not replace the prefix with "...".
- false ->
- LeadingDotsL = leading_dots(L, PrefixLen),
- {S2, _} = format_col(lists:sort(LeadingDotsL), []),
- S2
- end;
- false -> S1
+special_sort1([C|A], B) when C == ${ ; C == $. ; C == $# ->
+ special_sort1(A, B);
+special_sort1(A, [C|B]) when C == ${ ; C == $. ; C == $# ->
+ special_sort1(A,B);
+special_sort1(A,B) ->
+ string:lowercase(A) =< string:lowercase(B).
+special_sort(#{title:=A}, #{title:=B}) ->
+ special_sort1(A,B);
+%% Sections and elemts should not be in the same list
+special_sort(#{}, {}) ->
+ error;
+special_sort({}, #{}) ->
+ error;
+special_sort({A,_},{B,_}) ->
+ special_sort1(A,B);
+special_sort(A,B) ->
+ special_sort1(A,B).
+
+to_legacy_format([]) -> [];
+to_legacy_format([#{title:=Title}|Rest]) when Title =:= "commands"; Title =:= "bifs" ->
+ to_legacy_format(Rest);
+to_legacy_format([#{title:=Title, elems:=Elems}|Rest])
+ when Title =:= "modules"; Title =:= "functions"; Title =:= "bindings";
+ Title =:= "user_defined", Title =:= "records"; Title =:= "fields";
+ Title =:= "types"; Title =:= "atoms"; Title =:= "matches";
+ Title =:= "keywords"; Title =:= "typespecs" ->
+ Elems1 = to_legacy_format(Elems),
+ Elems1 ++ to_legacy_format(Rest);
+to_legacy_format([#{title:=Title, elems:=_Elems}|Rest]) ->
+ [Title] ++ to_legacy_format(Rest);
+to_legacy_format([{Val, _}|Rest]) ->
+ [{Val, ""}] ++ to_legacy_format(Rest).
+
+format_matches([], _LineWidth) -> [];
+format_matches([#{}|_]=FF, LineWidth) ->
+ %% Group function head that have the exact same Type suggestion
+ Groups = maps:groups_from_list(
+ fun(#{title:=Title, elems:=T, options:=Opts}) ->
+ Separator = proplists:get_value(separator, Opts, "\n"),
+ case lists:last(string:split(Title++Separator, "\n", all)) of
+ [] -> format_section_matches(T, LineWidth);
+ Chars -> %% we have chars that compete with the results on the first line
+ Len = length(Chars),
+ format_section_matches(T, LineWidth, Len)
+ end
+ end,
+ fun(F) ->
+ format_title(F, LineWidth)
+ end, FF),
+ S = lists:flatten(
+ [lists:join("", F)++Matches ||
+ {Matches, F}<-lists:sort(fun({_,A},{_,B}) -> A =< B end, maps:to_list(Groups))]),
+ lists:flatten(string:trim(S, trailing)++"\n");
+format_matches(Elems, LineWidth) ->
+ S = format_section_matches1(Elems, LineWidth, 0),
+ lists:flatten(string:trim(S, trailing)++"\n").
+format_title(#{title:=MFA, options:=Options}, _LineWidth) ->
+ case proplists:get_value(hide, Options) of
+ title -> "";
+ _ ->
+ Separator = proplists:get_value(separator, Options, "\n"),
+ HighlightAll = proplists:is_defined(highlight_all, Options),
+ case HighlightAll of
+ true -> "\033[;1;4m"++MFA++"\033[0m"++Separator;
+ _ ->
+ HighlightParam = proplists:get_value(highlight_param, Options, false),
+
+ MFA2 = case HighlightParam of
+ false -> MFA;
+ _ ->
+ PreviousParams = HighlightParam -1,
+ TuplePattern = "(?:\\{[^\\}]+\\})",
+ AtomVarPattern = "(?:\\w+)",
+ TypePattern="(?:(?:"++AtomVarPattern++":)?(?:"++AtomVarPattern++"\\(\\))(?:\\s[><=]+\\s\\d+)?)",
+ SimplePatterns = "(?:"++TuplePattern++"|"++TypePattern++"|"++AtomVarPattern++")",
+ UnionPattern = "(?:"++SimplePatterns++"(?:\\s\\|\\s"++SimplePatterns++")*)",
+ FunPattern="(?:fun\\(\\(" ++ UnionPattern ++ "\\)\\s*->\\s*" ++ UnionPattern ++ "\\))",
+ ArgPattern3 = "(?:"++FunPattern++"|"++UnionPattern++")",
+ PrevArgs="(?:"++ArgPattern3++",\\s){"++integer_to_list(PreviousParams) ++ "}",
+ FunctionHeadStart="^([^\\(]+\\("++PrevArgs++")", %% \\1
+
+ HighlightArg="("++ArgPattern3++")", %\\2
+ NextArgs="(?:,\\s"++ArgPattern3++")*",
+ FunctionHeadEnd="("++NextArgs++"\\)(?:.*))$", % \\3
+
+ re:replace(MFA,
+ FunctionHeadStart ++ HighlightArg ++ FunctionHeadEnd,
+ "\\1\033[;1;4m\\2\033[0m\\3",
+ [global, {return, list}, unicode])
+ end,
+ Highlight = proplists:get_value(highlight, Options, false),
+ case Highlight of
+ false -> MFA2;
+ _ -> re:replace(MFA2, "(\\Q"++Highlight++"\\E)", "\033[;1;4m\\1\033[0m", [global, {return, list}, unicode])
+ end ++ Separator
+ end
+ end;
+format_title(_Elems, _LineWidth) ->
+ %% not a section, old interface
+ %% output empty list
+ "".
+
+format_section_matches(LS, LineWidth) -> format_section_matches(LS, LineWidth, 0).
+format_section_matches([], _, _) -> "\n";
+format_section_matches([#{}|_]=FF, LineWidth, Acc) ->
+ Groups = maps:groups_from_list(
+ fun(#{title:=Title, elems:=T, options:=Opts}) ->
+ Separator = proplists:get_value(separator, Opts, "\n"),
+ case lists:last(string:split(Title++Separator, "\n", trailing)) of
+ [] -> format_section_matches(T, LineWidth);
+ Chars -> %% we have chars that compete with the results on the first line
+ Len = string:length(Chars),
+ format_section_matches(T, LineWidth, Len+Acc)
+ end
end,
- ["\n" | S].
+ fun(F) ->
+ format_title(F, LineWidth)
+ end, FF),
+ lists:flatten(
+ [lists:join("", F)++Matches ||
+ {Matches, F}<-lists:sort(fun({_,A},{_,B}) -> A =< B end, maps:to_list(Groups))]);
+format_section_matches(Elems, LineWidth, Acc) ->
+ format_section_matches1(Elems, LineWidth, Acc).
-format_col([], _) -> [];
-format_col(L, Acc) ->
- LL = 79,
- format_col(L, field_width(L, LL), 0, Acc, LL, false).
+format_section_matches1([], _, _) -> [];
+format_section_matches1(LS, LineWidth, Len) ->
+ L = lists:usort(fun special_sort/2, ordsets:to_list(LS)),
+ Opt = case Len == 0 of
+ true -> [];
+ false -> [{title, Len}]
+ end,
+ S1 = format_col(Opt ++ L, field_width(Opt ++ L, LineWidth), Len, [], LineWidth, Opt),
+ S2 = lists:map(
+ fun(Line) ->
+ case string:length(Line) of
+ Len1 when Len1 > LineWidth ->
+ string:sub_string(Line, 1, LineWidth-4) ++ "...\n";
+ _ -> Line
+ end
+ end,S1),
+ lists:flatten(string:trim(S2, trailing)++"\n").
-format_col(X, Width, Len, Acc, LL, Dots) when Width + Len > LL ->
- format_col(X, Width, 0, ["\n" | Acc], LL, Dots);
-format_col([A|T], Width, Len, Acc0, LL, Dots) ->
+format_col(X, Width, Len, Acc, LL, Opt) when Width + Len > LL ->
+ format_col(X, Width, 0, ["\n" | Acc], LL, Opt);
+format_col([{title,TitleLen}|T], Width, Len, Acc0, LL, Opt) ->
+ Acc = [io_lib:format("~-*ts", [Width-TitleLen, ""])|Acc0],
+ format_col(T, Width, Len+Width, Acc, LL, Opt);
+format_col([A|T], Width, Len, Acc0, LL, _Opt) ->
{H0, R} = format_val(A),
- Hmax = LL - length(R),
- {H, NewDots} =
+ Hmax = LL - string:length(R),
+ {H, _} =
case string:length(H0) > Hmax of
true -> {io_lib:format("~-*ts", [Hmax - 3, H0]) ++ "...", true};
- false -> {H0, Dots}
+ false -> {H0, false}
end,
Acc = [io_lib:format("~-*ts", [Width, H ++ R]) | Acc0],
- format_col(T, Width, Len+Width, Acc, LL, NewDots);
-format_col([], _, _, Acc, _LL, Dots) ->
- {lists:reverse(Acc, "\n"), Dots}.
+ format_col(T, Width, Len+Width, Acc, LL, []);
+format_col([], _, _, Acc, _LL, _Opt) ->
+ lists:reverse(Acc).
+format_val({H, L}) when is_list(L) ->
+ {H, proplists:get_value(ending, L, "")};
format_val({H, I}) when is_integer(I) ->
- %% If it's a tuple {string(), integer()}, we assume it's an
- %% arity, and meant to be printed.
- {H, "/" ++ integer_to_list(I)};
+ {H, "/"++integer_to_list(I)};
format_val({H, _}) ->
{H, ""};
format_val(H) ->
{H, ""}.
field_width(L, LL) -> field_width(L, 0, LL).
-
-field_width([{H,_}|T], W, LL) ->
- case string:length(H) of
+field_width([{title, Len}|T], W, LL) ->
+ case Len of
L when L > W -> field_width(T, L, LL);
_ -> field_width(T, W, LL)
end;
field_width([H|T], W, LL) ->
- case string:length(H) of
+ {H1, Ending} = format_val(H),
+ case string:length(H1++Ending) of
L when L > W -> field_width(T, L, LL);
_ -> field_width(T, W, LL)
end;
-field_width([], W, LL) when W < LL - 3 ->
+field_width([], W, LL) when W < LL ->
W + 4;
field_width([], _, LL) ->
LL.
-vals([]) -> [];
-vals([{S, _}|L]) -> [S|vals(L)];
-vals([S|L]) -> [S|vals(L)].
-
-leading_dots([], _Len) -> [];
-leading_dots([{H, I}|L], Len) ->
- [{"..." ++ string:slice(H, Len), I}|leading_dots(L, Len)];
-leading_dots([H|L], Len) ->
- ["..." ++ string:slice(H, Len)|leading_dots(L, Len)].
+number_matches([#{ elems := Matches }|T]) ->
+ number_matches(Matches) + number_matches(T);
+number_matches([_|T]) ->
+ 1 + number_matches(T);
+number_matches([]) ->
+ 0.
%% Strings are handled naively, but it should be OK here.
longest_common_head([]) ->
@@ -221,24 +1166,23 @@ longest_common_head(LL) ->
longest_common_head(LL, []).
longest_common_head([[]|_], L) ->
- {partial, reverse(L)};
+ {partial, lists:reverse(L)};
longest_common_head(LL, L) ->
case same_head(LL) of
- true ->
- [[H|_]|_] = LL,
- LL1 = all_tails(LL),
- case all_nil(LL1) of
- false ->
- longest_common_head(LL1, [H|L]);
- true ->
- {complete, reverse([H|L])}
- end;
- false ->
- {partial, reverse(L)}
+ true ->
+ [[H|_]|_] = LL,
+ LL1 = all_tails(LL),
+ case all_nil(LL1) of
+ false ->
+ longest_common_head(LL1, [H|L]);
+ true ->
+ {complete, lists:reverse([H|L])}
+ end;
+ false ->
+ {partial, lists:reverse(L)}
end.
same_head([[H|_]|T1]) -> same_head(H, T1).
-
same_head(H, [[H|_]|T]) -> same_head(H, T);
same_head(_, []) -> true;
same_head(_, _) -> false.
diff --git a/lib/stdlib/src/edlin_type_suggestion.erl b/lib/stdlib/src/edlin_type_suggestion.erl
new file mode 100644
index 0000000000..5b148ab998
--- /dev/null
+++ b/lib/stdlib/src/edlin_type_suggestion.erl
@@ -0,0 +1,487 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(edlin_type_suggestion).
+-include_lib("kernel/include/eep48.hrl").
+-export([type_tree/4, get_arity/3, get_atoms/3, get_types/3, get_types/4, get_function_type/4, print_type/3]).
+
+
+%% type_tree/4 returns a unwrapped and trimmed type specification containing
+%% all the valid 'types' that are valid in each function parameter of a function or
+%% each record field of a record.
+%%
+%% Translates the spec AST to a structure that resembles the AST but trimmed of unneeded data.
+%% User types and remote types are fetched and embedded in the structure depending on requested
+%% level of unnestling.
+%% Unions are flattened.
+%% Visited is used to prevent infinite loops when looking up a recursive / cyclic type
+%% FT is a map of type {{type, Type}, {attribute,_,type,{_,TypeAST,_}}})
+type_tree(Mod, FunType, Nestings, FT) ->
+ %% TODO when FT is updated we would be getting incorrect results because of cache
+ %% we should probably store a "dirty bit" in the table to make this aware that
+ %% a new result should be calculated. Preferably only types that depend on the table
+ %% would be invalidated, but it may turn advanced.
+ %% TODO look this over to make sure we don't do unnecessary work,
+ case get({type_traverser, Mod, FunType, Nestings}) of
+ undefined -> Res = type_traverser_cache(Mod, FunType, #{}, length(Nestings)+1, FT),
+ put({type_traverser, Mod, FunType, Nestings}, Res),
+ Res;
+ Res -> Res
+ end.
+type_traverser_cache(Mod, T, Visited, Level, FT) ->
+ case get({Mod, T, Level}) of
+ undefined ->
+ Res = type_traverser(Mod, T, Visited, Level, FT),
+ put({Mod, T, Level}, Res),
+ Res;
+ Res -> Res
+ end.
+
+type_traverser(Mod, {type, _, bounded_fun, [Fun, Constraints]}, Visited, Level, FT) ->
+ Cl = [type_traverser(Mod,X,Visited, Level, FT) || X <- Constraints],
+ F = type_traverser(Mod, Fun, Visited, Level, FT),
+ {function, F, Cl};
+type_traverser(Mod, {type, _, 'fun', [Product, Return]}, Visited, Level, FT) ->
+ P = type_traverser(Mod, Product, Visited, Level, FT),
+ R = type_traverser(Mod, Return, Visited, Level, FT),
+ {P, {return, R}};
+type_traverser(Mod, {type, _, product, Childs}, Visited, Level, FT) ->
+ Cl = [type_traverser(Mod, X, Visited, Level, FT) || X <- Childs],
+ {parameters, Cl};
+type_traverser(Mod, {type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]}, Visited, Level, FT) ->
+ {constraint, type_traverser(Mod, Type1, Visited, Level, FT), type_traverser(Mod, Type2, Visited, Level, FT)};
+type_traverser(_, {var, _, Name}, _Visited, _Level, _FT) ->
+ {var, Name};
+type_traverser(_Mod,{type, _, map, any}, _Visited, _Level, _FT) ->
+ {type, map, []};
+type_traverser(Mod, {type, _, map, Params}, Visited, Level, FT) ->
+ {map, [type_traverser(Mod, X, Visited, Level-1, FT) || X <- Params]};
+type_traverser(Mod, {type, _, map_field_exact, [Type1, Type2]}, Visited, Level, FT) ->
+ {map_field_exact, type_traverser(Mod,Type1, Visited, Level, FT), type_traverser(Mod,Type2, Visited, Level, FT)};
+type_traverser(Mod, {type, _, map_field_assoc, [Type1, Type2]}, Visited, Level, FT) ->
+ {map_field_assoc, type_traverser(Mod,Type1, Visited, Level, FT), type_traverser(Mod,Type2, Visited, Level, FT)};
+type_traverser(_Mod, {atom, _, Atom}, _Visited, _Level, _FT) when is_atom(Atom) ->
+ Atom;
+type_traverser(Mod, {op, _, Op, Type}, Visited, Level, FT) ->
+ {op, Op, type_traverser(Mod, Type, Visited, Level, FT)};
+type_traverser(Mod, {op, _, Op, Type1, Type2}, Visited, Level, FT) ->
+ {op, Op, type_traverser(Mod, Type1, Visited, Level, FT), type_traverser(Mod, Type2, Visited, Level, FT)};
+type_traverser(_Mod, {integer, _, Int}, _Visited, _Level, _FT) ->
+ {integer, Int};
+type_traverser(Mod, {type, _, list, [ChildType]}, Visited, Level, FT) ->
+ {list, type_traverser(Mod, ChildType, Visited, Level-1, FT)};
+type_traverser(_Mod, {type, _, tuple, any}, _Visited, _Level, _FT) ->
+ {type, tuple, []};
+type_traverser(Mod, {type, _, tuple, ChildTypes}, Visited, Level, FT) ->
+ {tuple, [type_traverser(Mod, X, Visited, Level-1, FT) || X <- ChildTypes]};
+type_traverser(Mod, {type, _, union, ChildTypes}, Visited, Level, FT) ->
+ Childs = [type_traverser(Mod, X, Visited, Level, FT) || X <- ChildTypes],
+ ChildsFiltered = [X || X <- Childs, X/=undefined],
+ {UnionChilds, NonUnionChilds} = lists:partition(
+ fun(X) ->
+ case X of
+ {union, _} -> true;
+ _ -> false
+ end
+ end, ChildsFiltered),
+ ChildsFlattened = lists:flatten([T || {union, T} <- UnionChilds]) ++ NonUnionChilds,
+ {union, ChildsFlattened};
+type_traverser(Mod, {ann_type,_,[T1,T2]}, Visited, Level, FT) ->
+ {ann_type, type_traverser(Mod, T1, Visited, Level, FT), type_traverser(Mod, T2, Visited, Level, FT)};
+type_traverser(Mod, {user_type,_,Name,Params}=T, Visited, Level, FT) when 0 >= Level ->
+ %% when we have level 0, do not traverse the type further, just print it
+ case maps:is_key(strip_anno(T), Visited) of
+ false ->
+ {type, Mod, Name, [type_traverser(Mod, P, Visited#{ strip_anno(T) => true }, 0, FT) || P <- Params]};
+ true -> {type, Mod, Name, []}
+ end;
+type_traverser(_, {remote_type,_,[{_,_,Mod},{_,_,Name}, Params]}=T, Visited, Level, FT) when 0 >= Level ->
+ case maps:is_key(strip_anno(T), Visited) of
+ false ->
+ {type, Mod, Name, [type_traverser(Mod, P, Visited#{ strip_anno(T) => true }, 0, FT) || P <- Params]};
+ true -> {type, Mod, Name, []}
+ end;
+type_traverser(Mod, {user_type,_,Name,Params}=T, Visited, 1=Level, FT) ->
+ case maps:is_key(strip_anno(T), Visited) of
+ false ->
+ case get({strip_anno(T), 1}) of
+ undefined ->
+ Res = case lookup_type(Mod, Name, length(Params), FT) of
+ hidden -> {type, Mod, Name, [type_traverser(Mod, P, Visited#{ strip_anno(T) => true }, Level, FT) || P <- Params]};
+ Type -> {user_type, Mod, Name, [type_traverser(Mod, P, Visited#{ strip_anno(T) => true }, Level, FT) || P <- Params], type_traverser(Mod, Type, Visited#{ strip_anno(T) => true }, Level, FT)}
+ end,
+ put({strip_anno(T), 1}, Res),
+ Res;
+ Res -> Res
+ end;
+ true -> {type, Mod, Name, []}
+ end;
+type_traverser(_, {remote_type,_,[{_,_,Mod},{_,_,Name}, Params]}=T, Visited, 1=Level, FT) ->
+ case maps:is_key(strip_anno(T), Visited) of
+ false ->
+ case get({strip_anno(T), 1}) of
+ undefined ->
+ Res = case lookup_type(Mod, Name, length(Params), FT) of
+ hidden -> {type, Mod, Name, [type_traverser(Mod, P, Visited#{ strip_anno(T) => true }, Level, FT) || P <- Params]};
+ Type -> {user_type, Mod, Name, [type_traverser(Mod, P, Visited#{ strip_anno(T) => true }, Level, FT) || P <- Params], type_traverser(Mod, Type, Visited#{ strip_anno(T) => true }, Level, FT)}
+ end,
+ put({strip_anno(T), 1}, Res),
+ Res;
+ Res -> Res
+ end;
+ true -> {type, Mod, Name, []}
+ end;
+type_traverser(Mod, {user_type,_,Name,Params}=T, Visited, Level, FT) ->
+ case maps:is_key(strip_anno(T), Visited) of
+ false ->
+ case get({strip_anno(T), Level}) of
+ undefined ->
+ Res = case lookup_type(Mod, Name, length(Params), FT) of
+ hidden -> {type, Mod, Name, [type_traverser(Mod, P, Visited#{ strip_anno(T) => true }, Level, FT) || P <- Params]};
+ Type -> type_traverser(Mod, Type, Visited#{ strip_anno(T) => true }, Level, FT)
+ end,
+ put({strip_anno(T), Level}, Res),
+ Res;
+ Res -> Res
+ end;
+ true -> {type, Mod, Name, []}
+ end;
+type_traverser(_, {remote_type, _, [{_,_,Mod},{_,_,Name}, Params]}=T, Visited, Level, FT) ->
+ case maps:is_key(strip_anno(T), Visited) of
+ false ->
+ case get({strip_anno(T), Level}) of
+ undefined ->
+ Res = case lookup_type(Mod, Name, length(Params), FT) of
+ hidden -> {type, Mod, Name, [type_traverser(Mod, P, Visited#{ strip_anno(T) => true }, Level, FT) || P <- Params]};
+ Type -> type_traverser(Mod, Type, Visited#{ strip_anno(T) => true }, Level, FT)
+ end,
+ put({strip_anno(T), Level}, Res),
+ Res;
+ Res -> Res
+ end;
+ true -> {type, Mod, Name, []}
+ end;
+type_traverser(_, {type, _, record, [{atom, _, Record}]}, _Visited, _Level, _FT) ->
+ {record, Record};
+type_traverser(_, {type, _, Name, any}, _, _, _) ->
+ {type, Name, []};
+type_traverser(_, {type, _, term}, _, _, _) ->
+ {type, any, []};
+type_traverser(_, {type, _, Name}, _, _, _) ->
+ {type, Name, []};
+type_traverser(_, {type, _, term, _}, _, _, _) ->
+ {type, any, []};
+type_traverser(_, {type, _, Name, Params}=T, Visited, Level, FT) ->
+ case maps:is_key(strip_anno(T), Visited) of
+ false ->
+ case get({strip_anno(T), 1}) of
+ undefined ->
+ Res = case lookup_type(erlang, Name, length(Params), FT) of
+ hidden -> {type, Name, [type_traverser(erlang, P, Visited#{ strip_anno(T) => true }, Level, FT) || P <- Params]};
+ Type -> type_traverser(erlang, Type, Visited#{ strip_anno(T) => true}, Level, FT)
+ end,
+ put({strip_anno(T), 1}, Res),
+ Res;
+ Res -> Res
+ end;
+ true -> {type, Name, []}
+ end.
+
+strip_anno({A, _, B}) -> {A, B};
+strip_anno({A, _, B, C}) -> {A, B, C}.
+
+simplified_type(erlang, binary, 0) -> {type, undefined, binary, []};
+simplified_type(erlang, char, 0) -> {type, undefined, char, []};
+simplified_type(erlang, iolist, 0) -> {type, undefined, iolist, []};
+simplified_type(erlang, string, 0) -> {type, undefined, string, []};
+simplified_type(unicode, chardata, 0) -> {type, erlang, string, []};
+simplified_type(file, filename_all, 0) -> {type, erlang, string, []};
+simplified_type(file, filename, 0) -> {type, erlang, string, []};
+simplified_type(file, name_all, 0) -> {type, erlang, string, []};
+simplified_type(file, name, 0) -> {type, erlang, string, []};
+simplified_type(_Module, _TypeName, _Arity) -> none.
+
+lookup_type(Mod, Type, Arity, FT) ->
+ case simplified_type(Mod, Type, Arity) of
+ none ->
+ case code:get_doc(Mod, #{sources => [debug_info]}) of
+ {ok, #docs_v1{ docs = Docs } } ->
+ FnFunctions =
+ lists:filter(fun({{type, T, A},_Anno,_Sig,_Doc,_Meta}) ->
+ T =:= Type andalso A =:= Arity;
+ (_) ->
+ false
+ end, Docs),
+ case FnFunctions of
+ [] ->
+ case [TypeAST || {{type, Type2}, {attribute,_,type,{_,TypeAST,_}}} <- FT, Type2 =:= Type] of
+ [] -> hidden; %% can be an opaque type or missing type
+ [SingleTypeAST] -> SingleTypeAST
+ end;
+ [{_,_,_,_,#{signature := [{attribute,_,type,{_,TypeAST,_}}]}}] -> TypeAST
+ end;
+ _ ->
+ case [TypeAST || {{type, Type2}, {attribute,_,type,{_,TypeAST,_}}} <- FT, Type2 =:= Type] of
+ [] -> hidden; %% can be an opaque type or missing type
+ [SingleTypeAST] -> SingleTypeAST
+ end
+ end;
+ T -> T
+ end.
+get_function_type(Mod, Fun, Arity, FT) ->
+ case code:get_doc(Mod, #{sources => [debug_info]}) of
+ {ok, #docs_v1{ docs = Docs } } ->
+ R = lists:flatten([FunTypes ||
+ {{function, F, A},_Anno,_Sig,_Doc, #{ signature := [{attribute,_,spec,{_,FunTypes}}]}} <- Docs,
+ F =:= Fun, A =:= Arity]),
+ case {Mod, R} of
+ {shell_default, []} ->
+ lists:flatten([FunTypes ||
+ {{function_type, {shell_default, F, A}},{attribute,_,spec,{_,FunTypes}}} <- FT,
+ F =:= Fun, A =:= Arity]);
+ _ -> R
+ end;
+ _ when Mod =:= shell_default ->
+ lists:flatten([FunTypes || {{function_type, {shell_default, F, A}},{attribute,_,spec,{_,FunTypes}}} <- FT,
+ F =:= Fun, A =:= Arity]);
+ _ -> []
+ end.
+get_arity(Constraints, Type, Nestings) ->
+ case get_arity1(Type, Constraints, Nestings) of
+ List when is_list(List) -> List;
+ Val -> [Val]
+ end.
+get_arity1({var, _Var}=C, Constraints, Nestings) ->
+ case get_constraint(C, Constraints) of
+ {constraint, _, T} -> get_arity1(T, Constraints, Nestings);
+ _ -> none
+ end;
+get_arity1({list, _T}, _Constraints, [{'list', _, _}]) ->
+ 99; %% Can be higher, but probably do not need completion for that
+get_arity1({list, T}, Constraints, [{'list', _, _}|Nestings]) ->
+ get_arity1(T, Constraints, Nestings);
+get_arity1({tuple, LT}, Constraints, [{'tuple', Args, _}]) when length(LT) >= length(Args) ->
+ case edlin_expand:match_arguments1(LT, Constraints, Args) of
+ true -> length(LT);
+ false ->
+ none
+ end;
+get_arity1({tuple, LT}, Constraints, [{'tuple', Args, _}|Nestings]) when length(LT) >= length(Args)+1 ->
+ case edlin_expand:match_arguments1(LT, Constraints, Args) of
+ true -> get_arity1(lists:nth(length(Args)+1, LT), Constraints, Nestings);
+ false -> none
+ end;
+get_arity1({map, Types}, _Constraints, [{'map', _Keys, [], _, _}]) ->
+ length(Types);
+get_arity1({map, Types}, _Constraints, [{'map', _Keys, _Key, _, _}]) ->
+ length(Types);
+get_arity1({map, Types}, Constraints, [{'map', Keys, [], _, _}|Nestings]) ->
+ lists:flatten([get_arity1(T, Constraints, Nestings) || {_, Key, _}=T <- Types, not lists:member(atom_to_list(Key), Keys)]);
+get_arity1({map, Types}, Constraints, [{'map', _Keys, Key, _, _}|Nestings]) ->
+ case [V || {_, K, V} <- Types, K =:= list_to_atom(Key)] of
+ [] -> none;
+ [Type] -> get_arity1(Type, Constraints, Nestings)
+ end;
+get_arity1({map_field_assoc, K, _V}, C, Nestings) ->
+ get_arity1(K, C, Nestings);
+get_arity1({map_field_exact, K, _V}, C, Nestings) ->
+ get_arity1(K, C, Nestings);
+get_arity1({union, Types}, Constraints, Nestings) ->
+ Arities = [get_arity1(T, Constraints, Nestings) || T <- Types],
+ [X || X <- lists:flatten(Arities), X/=none];
+get_arity1({ann_type, _Var, Type}, Constraints, Nestings) ->
+ get_arity1(Type, Constraints, Nestings);
+get_arity1({user_type, _, _, _, Type}, Constraints, Nestings) ->
+ get_arity1(Type, Constraints, Nestings);
+get_arity1(_, _, _) ->
+ none.
+
+%% get_atoms returns the valid atoms in the current context as a list
+get_atoms(Constraints, Type, Nestings) ->
+ case get_atoms1(Type, Constraints, Nestings) of
+ List when is_list(List) -> [io_lib:write_atom(Atom) || Atom <- List];
+ Atom when is_atom(Atom) -> [io_lib:write_atom(Atom)]
+ end.
+get_atoms1({var, _Var}=C, Constraints, Nestings) ->
+ case get_constraint(C, Constraints) of
+ {constraint, _, T} -> get_atoms1(T, Constraints, Nestings);
+ _ -> []
+ end;
+get_atoms1({list, T}, Constraints, [{'list', _, _}|Nestings]) ->
+ get_atoms1(T, Constraints, Nestings);
+get_atoms1({tuple, LT}, Constraints, [{'tuple', Args, _}|Nestings]) when length(LT) >= length(Args)+1 ->
+ case edlin_expand:match_arguments1(LT, Constraints, Args) of
+ true -> get_atoms1(lists:nth(length(Args)+1, LT), Constraints, Nestings);
+ false -> []
+ end;
+get_atoms1({map, Types}, Constraints, [{'map', Keys, [], _, _}|Nestings]) ->
+ lists:flatten([get_atoms1(T, Constraints, Nestings) || {_, Key, _}=T <- Types, not lists:member(atom_to_list(Key), Keys)]);
+get_atoms1({map, Types}, Constraints, [{'map', _Keys, Key, _, _}|Nestings]) ->
+ case [V || {_, K, V} <- Types, K =:= list_to_atom(Key)] of
+ [] -> [];
+ [Type] -> get_atoms1(Type, Constraints, Nestings)
+ end;
+get_atoms1({map_field_assoc, K, _V}, C, Nestings) ->
+ get_atoms1(K, C, Nestings);
+get_atoms1({map_field_exact, K, _V}, C, Nestings) ->
+ get_atoms1(K, C, Nestings);
+get_atoms1( {union, Types}, Constraints, Nestings) ->
+ Atoms = [get_atoms1(T, Constraints, Nestings) || T <- Types],
+ [X || X <- lists:flatten(Atoms), X/=[]];
+get_atoms1(Atom, _Constraints, []) when is_atom(Atom) ->
+ Atom;
+get_atoms1({user_type, _, _, _, Type}, Constraints, Nestings) ->
+ get_atoms1(Type, Constraints, Nestings);
+get_atoms1(_, _, _) ->
+ [].
+
+get_types(Constraints, T, Nestings) ->
+ get_types(Constraints, T, Nestings,[]).
+get_types(Constraints, T, Nestings, Options) ->
+ MaxUserTypeExpansions = 1,
+ case get_types1(T, Constraints, Nestings, MaxUserTypeExpansions, Options) of
+ [] -> [];
+ [_|_]=Types -> [Type || Type <- Types, Type /= []];
+ Type -> [Type]
+ end.
+get_types1({var, _Var}=C, Constraints, Nestings, MaxUserTypeExpansions, Options) ->
+ case get_constraint(C, Constraints) of
+ {constraint, _, T} -> get_types1(T, Constraints, Nestings, MaxUserTypeExpansions, Options);
+ _ -> []
+ end;
+get_types1({union, Types}, Cs, Nestings, MaxUserTypeExpansions, Options) ->
+ lists:flatten([get_types1(T, Cs, Nestings, MaxUserTypeExpansions, Options) || T <- Types]);
+
+get_types1({list, T}, Cs, [{list, _Args, _}|Nestings], MaxUserTypeExpansions, Options) ->
+ get_types1(T, Cs, Nestings, MaxUserTypeExpansions, Options);
+get_types1({tuple, LT}, Cs, [{tuple, Args, _}|Nestings], MaxUserTypeExpansions, Options) when length(LT) >= length(Args)+1 ->
+ case edlin_expand:match_arguments1(LT, Cs, Args) of
+ true -> get_types1(lists:nth(length(Args)+1, LT), Cs, Nestings, MaxUserTypeExpansions, Options);
+ false -> []
+ end;
+get_types1({'map', Types}, Cs, [{'map', Keys, [], _Args, _}|Nestings], MaxUserTypeExpansions, Options) ->
+ lists:flatten([get_types1(T, Cs, Nestings, MaxUserTypeExpansions, Options) || {_, Key, _}=T <- Types, not lists:member(atom_to_list(Key), Keys)]);
+get_types1({'map', Types}, Cs, [{'map', _, Key, _Args, _}|Nestings], MaxUserTypeExpansions, Options) ->
+ case [V || {_, K, V} <- Types, K =:= list_to_atom(Key)] of
+ [] -> [];
+ [Type] -> get_types1(Type, Cs, Nestings, MaxUserTypeExpansions, Options)
+ end;
+get_types1({user_type, _Mod, _Name, _Params, Type}, Cs, Nestings, MaxUserTypeExpansions, [no_print]=Options) when MaxUserTypeExpansions > 0 ->
+ lists:flatten([get_types1(Type, Cs, Nestings, MaxUserTypeExpansions-1, Options)]);
+get_types1({user_type, _, _, _, Type}, Cs, Nestings, 0, [no_print]=Options) ->
+ get_types1(Type, Cs, Nestings, 0, Options);
+get_types1({ann_type, _Var, T}, Cs, Nestings, MaxUserTypeExpansions, [no_print]) ->
+ get_types1(T, Cs, Nestings, MaxUserTypeExpansions, [no_print]);
+get_types1({ann_type, _Var, _T}=Type, Cs, [], _MaxUserTypeExpansions, []) ->
+ {print_type(Type, Cs), ""};
+get_types1({ann_type, _Var, T}, Cs, Nestings, MaxUserTypeExpansions, []) ->
+ get_types1(T, Cs, Nestings, MaxUserTypeExpansions, []);
+get_types1(Type, _Cs, [], _, [no_print]) ->
+ Type;
+get_types1({user_type, Mod, Name, Params, Type}, Cs, Nestings, MaxUserTypeExpansions, []) when MaxUserTypeExpansions > 0 ->
+ Title = print_type({type, Mod, Name, Params}, Cs, []),
+ Elems = lists:flatten([get_types1(Type, Cs, Nestings, MaxUserTypeExpansions-1, [])]),
+ #{title=>Title, elems=>Elems, options=>[{separator, " :: "}, {highlight_all}]};
+get_types1({user_type, _, _, _, Type}, Cs, Nestings, 0, []) ->
+ get_types1(Type, Cs, Nestings, 0, []);
+get_types1(Type, Cs, [],_, []) ->
+ {print_type(Type, Cs), ""};
+get_types1(_, _, _, _, _) -> [].
+
+get_constraint(Type, Constraints) ->
+ case [ X || {constraint, T, _}=X <- Constraints, T == Type] of
+ [C|_] -> C;
+ [] -> []
+ end.
+
+print_type(Type, Constraints) ->
+ lists:flatten(print_type(Type, Constraints, [], [])).
+print_type(Type, Constraints, Options) ->
+ lists:flatten(print_type(Type, Constraints, [], Options)).
+print_type({var, Name}=Var, Constraints, Visited, Options) ->
+ case lists:member(Var, Visited) of
+ true -> atom_to_list(Name);
+ false ->
+ case get_constraint(Var, Constraints) of
+ {constraint, _, T2} -> print_type(T2, Constraints, [Var| Visited], Options);
+ _ -> atom_to_list(Name)
+ end
+ end;
+print_type(Atom, _Cs, _V, _) when is_atom(Atom) -> io_lib:write_atom(Atom);
+print_type({{parameters, Ps}, {return, R}}, Cs, V, Options) ->
+ "fun(("++lists:join(", ", [print_type(X, Cs, V, Options) || X <- Ps]) ++ ") -> " ++ print_type(R, Cs, V, Options) ++ ")";
+print_type({list, Type}, Cs, V, Options)->
+ "[" ++ print_type(Type, Cs, V, Options) ++ "]";
+print_type({tuple, Types}, Cs, V, Options) when is_list(Types) ->
+ Types1 = [print_type(X, Cs, V, Options) || X <- Types],
+ case Types1 of
+ [] -> "{}";
+ _ -> "{"++ lists:nth(1, Types1) ++ ", ...}"
+ end;
+print_type({ann_type, Var, Type}, Cs, V, Options) ->
+ print_type(Var, Cs, V, Options) ++ " :: " ++ print_type(Type, Cs, V, Options);
+print_type({map, Types}, Cs, V, Options) ->
+ Types1 = [print_type(X, Cs, V, Options) || X <- Types],
+ "#{"++lists:join(", ", Types1) ++ "}";
+print_type({map_field_assoc, Type1, Type2}, Cs, V, Options) ->
+ print_type(Type1, Cs, V, Options) ++ "=>" ++ print_type(Type2, Cs, V, Options);
+print_type({map_field_exact, Type1, Type2}, Cs, V, Options) ->
+ print_type(Type1, Cs, V, Options) ++ ":=" ++ print_type(Type2, Cs, V, Options);
+print_type({integer, Int}, _Cs, _V, _) ->
+ integer_to_list(Int);
+print_type({op, Op, Type}, Cs, V, Options) ->
+ "op ("++atom_to_list(Op)++" "++print_type(Type, Cs, V, Options)++")";
+print_type({op, Op, Type1, Type2}, Cs, V, Options) ->
+ "op ("++print_type(Type1, Cs, V, Options)++" "++atom_to_list(Op)++" "++print_type(Type2, Cs, V, Options)++")";
+print_type({record, Record}, _Cs, _V, _) ->
+ "#" ++ atom_to_list(Record);
+print_type({type, range, [{integer, Int1},{integer, Int2}]}, _Cs, _V, _) ->
+ integer_to_list(Int1) ++ ".." ++ integer_to_list(Int2);
+print_type({type, non_neg_integer, []}, _Cs, _V, _) ->
+ "integer() >= 0";
+print_type({type, neg_integer, []}, _Cs, _V, _) ->
+ "integer() < 0";
+print_type({type, pos_integer, []}, _Cs, _V, _) ->
+ "integer() > 0";
+print_type({type, Name, []}, _Cs, _V, _) ->
+ atom_to_list(Name)++"()";
+print_type({type, Name, Params}, _Cs, _V, _) ->
+ atom_to_list(Name) ++ "(" ++ lists:join(", ",[ extract_param(P) || P <- Params]) ++ ")";
+print_type({union, Types}, Cs, V, Options) ->
+ lists:join(" | ", [print_type(X, Cs, V, Options) || X <- Types]);
+print_type({type, Mod, Name, Params}, _Cs, _V, _) ->
+ atom_to_list(Mod) ++ ":" ++ atom_to_list(Name) ++
+ "(" ++ lists:join(", ", [extract_param(P) || P <- Params]) ++ ")";
+print_type({user_type, Mod, Name, Params, Type}, Cs, V, Options) ->
+ First = proplists:get_value(first_only, Options, false),
+ case First of
+ true -> print_type({type, Mod, Name, Params}, Cs, V, Options);
+ _ -> print_type({type, Mod, Name, Params}, Cs, V, Options) ++ " :: " ++ print_type(Type, Cs, V, Options)
+ end;
+print_type(_,_,_,_) -> atom_to_list(unknown).
+
+
+extract_param({var, Var}) ->
+ atom_to_list(Var);
+extract_param({integer, Value}) ->
+ io_lib:format("~p",[Value]);
+extract_param({type, Type,_}) ->
+ io_lib:format("~p", [Type]);
+extract_param(T)->
+ print_type(T, []).
diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl
index bdb0bc64a2..1f7f614b00 100644
--- a/lib/stdlib/src/epp.erl
+++ b/lib/stdlib/src/epp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -125,7 +125,8 @@ open(Name, Path, Pdm) ->
{'name',FileName :: file:name()} |
{'location',StartLocation :: erl_anno:location()} |
{'fd',FileDescriptor :: file:io_device()} |
- 'extra'],
+ 'extra' |
+ {'compiler_internal', [term()]}],
Epp :: epp_handle(),
Extra :: [{'encoding', source_encoding() | 'none'}],
ErrorDescriptor :: term().
@@ -309,7 +310,8 @@ parse_file(Ifile, Path, Predefs) ->
{'location',StartLocation :: erl_anno:location()} |
{'reserved_word_fun', Fun :: fun((atom()) -> boolean())} |
{'features', [Feature :: atom()]} |
- 'extra'],
+ 'extra' |
+ {'compiler_internal', [term()]}],
Form :: erl_parse:abstract_form()
| {'error', ErrorInfo}
| {'eof',Location},
@@ -608,6 +610,8 @@ init_server(Pid, FileName, Options, St0) ->
SourceName = proplists:get_value(source_name, Options, FileName),
Pdm = proplists:get_value(macros, Options, []),
Features = proplists:get_value(features, Options, []),
+ Internal = proplists:get_value(compiler_internal, Options, []),
+ ParseChecks = proplists:get_bool(ssa_checks, Internal),
Ms0 = predef_macros(SourceName, Features),
case user_predef(Pdm, Ms0) of
{ok,Ms1} ->
@@ -631,7 +635,11 @@ init_server(Pid, FileName, Options, St0) ->
default_encoding=DefEncoding,
erl_scan_opts =
[{text_fun, keep_ftr_keywords()},
- {reserved_word_fun, ResWordFun}],
+ {reserved_word_fun, ResWordFun}]
+ ++ if ParseChecks ->
+ [{compiler_internal,[ssa_checks]}];
+ true -> []
+ end,
features = Features,
else_reserved = ResWordFun('else'),
deterministic = Deterministic},
@@ -750,7 +758,7 @@ wait_request(St) ->
wait_request(St);
{epp_request,From,macro_defs} ->
%% Return the old format to avoid any incompability issues.
- Defs = [{{atom,K},V} || {K,V} <- maps:to_list(St#epp.macs)],
+ Defs = [{{atom,K},V} || K := V <- St#epp.macs],
epp_reply(From, Defs),
wait_request(St);
{epp_request,From,close} ->
diff --git a/lib/stdlib/src/erl_eval.erl b/lib/stdlib/src/erl_eval.erl
index 987ba0cf0a..f88cba1ba3 100644
--- a/lib/stdlib/src/erl_eval.erl
+++ b/lib/stdlib/src/erl_eval.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -286,6 +286,8 @@ expr({lc,_,E,Qs}, Bs, Lf, Ef, RBs, FUVs) ->
eval_lc(E, Qs, Bs, Lf, Ef, RBs, FUVs);
expr({bc,_,E,Qs}, Bs, Lf, Ef, RBs, FUVs) ->
eval_bc(E, Qs, Bs, Lf, Ef, RBs, FUVs);
+expr({mc,_,E,Qs}, Bs, Lf, Ef, RBs, FUVs) ->
+ eval_mc(E, Qs, Bs, Lf, Ef, RBs, FUVs);
expr({tuple,_,Es}, Bs0, Lf, Ef, RBs, FUVs) ->
{Vs,Bs} = expr_list(Es, Bs0, Lf, Ef, FUVs),
ret_expr(list_to_tuple(Vs), Bs, RBs);
@@ -760,17 +762,15 @@ do_apply(F, _Anno, FunOrModFun, Args) when is_function(F, 2) ->
eval_lc(E, Qs, Bs, Lf, Ef, RBs, FUVs) ->
ret_expr(lists:reverse(eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, [])), Bs, RBs).
-eval_lc1(E, [{generate,Anno,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
- {value,L1,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
- CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
- eval_generate(L1, P, Anno, Bs0, Lf, Ef, CompFun, Acc0);
-eval_lc1(E, [{b_generate,Anno,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
- {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
- CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
- eval_b_generate(Bin, P, Anno, Bs0, Lf, Ef, CompFun, Acc0);
-eval_lc1(E, [F|Qs], Bs0, Lf, Ef, FUVs, Acc) ->
- CompFun = fun(Bs) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
- eval_filter(F, Bs0, Lf, Ef, CompFun, FUVs, Acc);
+eval_lc1(E, [Q|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
+ case is_generator(Q) of
+ true ->
+ CF = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
+ eval_generator(Q, Bs0, Lf, Ef, FUVs, Acc0, CF);
+ false ->
+ CF = fun(Bs) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc0) end,
+ eval_filter(Q, Bs0, Lf, Ef, CF, FUVs, Acc0)
+ end;
eval_lc1(E, [], Bs, Lf, Ef, FUVs, Acc) ->
{value,V,_} = expr(E, Bs, Lf, Ef, none, FUVs),
[V|Acc].
@@ -782,21 +782,66 @@ eval_lc1(E, [], Bs, Lf, Ef, FUVs, Acc) ->
eval_bc(E, Qs, Bs, Lf, Ef, RBs, FUVs) ->
ret_expr(eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, <<>>), Bs, RBs).
-eval_bc1(E, [{b_generate,Anno,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
- {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
- CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
- eval_b_generate(Bin, P, Anno, Bs0, Lf, Ef, CompFun, Acc0);
-eval_bc1(E, [{generate,Anno,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
- {value,List,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
- CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
- eval_generate(List, P, Anno, Bs0, Lf, Ef, CompFun, Acc0);
-eval_bc1(E, [F|Qs], Bs0, Lf, Ef, FUVs, Acc) ->
- CompFun = fun(Bs) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
- eval_filter(F, Bs0, Lf, Ef, CompFun, FUVs, Acc);
+eval_bc1(E, [Q|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
+ case is_generator(Q) of
+ true ->
+ CF = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
+ eval_generator(Q, Bs0, Lf, Ef, FUVs, Acc0, CF);
+ false ->
+ CF = fun(Bs) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc0) end,
+ eval_filter(Q, Bs0, Lf, Ef, CF, FUVs, Acc0)
+ end;
eval_bc1(E, [], Bs, Lf, Ef, FUVs, Acc) ->
{value,V,_} = expr(E, Bs, Lf, Ef, none, FUVs),
<<Acc/bitstring,V/bitstring>>.
+%% eval_mc(Expr, [Qualifier], Bindings, LocalFunctionHandler,
+%% ExternalFuncHandler, RetBindings) ->
+%% {value,Value,Bindings} | Value
+
+eval_mc(E, Qs, Bs, Lf, Ef, RBs, FUVs) ->
+ L = eval_mc1(E, Qs, Bs, Lf, Ef, FUVs, []),
+ Map = maps:from_list(L),
+ ret_expr(Map, Bs, RBs).
+
+eval_mc1(E, [Q|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
+ case is_generator(Q) of
+ true ->
+ CF = fun(Bs, Acc) -> eval_mc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
+ eval_generator(Q, Bs0, Lf, Ef, FUVs, Acc0, CF);
+ false ->
+ CF = fun(Bs) -> eval_mc1(E, Qs, Bs, Lf, Ef, FUVs, Acc0) end,
+ eval_filter(Q, Bs0, Lf, Ef, CF, FUVs, Acc0)
+ end;
+eval_mc1({map_field_assoc,Lfa,K0,V0}, [], Bs, Lf, Ef, FUVs, Acc) ->
+ {value,KV,_} = expr({tuple,Lfa,[K0,V0]}, Bs, Lf, Ef, none, FUVs),
+ [KV|Acc].
+
+eval_generator({generate,Anno,P,L0}, Bs0, Lf, Ef, FUVs, Acc0, CompFun) ->
+ {value,L1,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
+ eval_generate(L1, P, Anno, Bs0, Lf, Ef, CompFun, Acc0);
+eval_generator({b_generate,Anno,P,Bin0}, Bs0, Lf, Ef, FUVs, Acc0, CompFun) ->
+ {value,Bin,_Bs1} = expr(Bin0, Bs0, Lf, Ef, none, FUVs),
+ eval_b_generate(Bin, P, Anno, Bs0, Lf, Ef, CompFun, Acc0);
+eval_generator({m_generate,Anno,P,Map0}, Bs0, Lf, Ef, FUVs, Acc0, CompFun) ->
+ {map_field_exact,_,K,V} = P,
+ {value,Map,_Bs1} = expr(Map0, Bs0, Lf, Ef, none, FUVs),
+ Iter = case is_map(Map) of
+ true ->
+ maps:iterator(Map);
+ false ->
+ %% Validate iterator.
+ try maps:foreach(fun(_, _) -> ok end, Map) of
+ _ ->
+ Map
+ catch
+ _:_ ->
+ apply_error({bad_generator,Map}, ?STACKTRACE,
+ Anno, Bs0, Ef, none)
+ end
+ end,
+ eval_m_generate(Iter, {tuple,Anno,[K,V]}, Anno, Bs0, Lf, Ef, CompFun, Acc0).
+
eval_generate([V|Rest], P, Anno, Bs0, Lf, Ef, CompFun, Acc) ->
case match(P, V, Anno, new_bindings(Bs0), Bs0, Ef) of
{match,Bsn} ->
@@ -828,6 +873,21 @@ eval_b_generate(<<_/bitstring>>=Bin, P, Anno, Bs0, Lf, Ef, CompFun, Acc) ->
eval_b_generate(Term, _P, Anno, Bs0, _Lf, Ef, _CompFun, _Acc) ->
apply_error({bad_generator,Term}, ?STACKTRACE, Anno, Bs0, Ef, none).
+eval_m_generate(Iter0, P, Anno, Bs0, Lf, Ef, CompFun, Acc0) ->
+ case maps:next(Iter0) of
+ {K,V,Iter} ->
+ case match(P, {K,V}, Anno, new_bindings(Bs0), Bs0, Ef) of
+ {match,Bsn} ->
+ Bs2 = add_bindings(Bsn, Bs0),
+ Acc = CompFun(Bs2, Acc0),
+ eval_m_generate(Iter, P, Anno, Bs0, Lf, Ef, CompFun, Acc);
+ nomatch ->
+ eval_m_generate(Iter, P, Anno, Bs0, Lf, Ef, CompFun, Acc0)
+ end;
+ none ->
+ Acc0
+ end.
+
eval_filter(F, Bs0, Lf, Ef, CompFun, FUVs, Acc) ->
case erl_lint:is_guard_test(F) of
true ->
@@ -844,6 +904,11 @@ eval_filter(F, Bs0, Lf, Ef, CompFun, FUVs, Acc) ->
end
end.
+is_generator({generate,_,_,_}) -> true;
+is_generator({b_generate,_,_,_}) -> true;
+is_generator({m_generate,_,_,_}) -> true;
+is_generator(_) -> false.
+
%% eval_map_fields([Field], Bindings, LocalFunctionHandler,
%% ExternalFuncHandler) ->
%% {[{map_assoc | map_exact,Key,Value}],Bindings}
diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl
index 7715f7d458..8cd78e597d 100644
--- a/lib/stdlib/src/erl_expand_records.erl
+++ b/lib/stdlib/src/erl_expand_records.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -55,8 +55,7 @@ compiler_options(Forms) ->
lists:flatten([C || {attribute,_,compile,C} <- Forms]).
init_calltype(Forms) ->
- Locals = [{{Name,Arity},local} || {function,_,Name,Arity,_} <- Forms],
- Ctype = maps:from_list(Locals),
+ Ctype = #{{Name,Arity} => local || {function,_,Name,Arity,_} <- Forms},
init_calltype_imports(Forms, Ctype).
init_calltype_imports([{attribute,_,import,{Mod,Fs}}|T], Ctype0) ->
@@ -289,6 +288,10 @@ expr({bc,Anno,E0,Qs0}, St0) ->
{Qs1,St1} = lc_tq(Anno, Qs0, St0),
{E1,St2} = expr(E0, St1),
{{bc,Anno,E1,Qs1},St2};
+expr({mc,Anno,E0,Qs0}, St0) ->
+ {Qs1,St1} = lc_tq(Anno, Qs0, St0),
+ {E1,St2} = expr(E0, St1),
+ {{mc,Anno,E1,Qs1},St2};
expr({tuple,Anno,Es0}, St0) ->
{Es1,St1} = expr_list(Es0, St0),
{{tuple,Anno,Es1},St1};
@@ -442,7 +445,9 @@ expr({op,Anno,Op,L0,R0}, St0) when Op =:= 'andalso';
expr({op,Anno,Op,L0,R0}, St0) ->
{L,St1} = expr(L0, St0),
{R,St2} = expr(R0, St1),
- {{op,Anno,Op,L,R},St2}.
+ {{op,Anno,Op,L,R},St2};
+expr(E={ssa_check_when,_,_,_,_,_}, St) ->
+ {E, St}.
expr_list([E0 | Es0], St0) ->
{E,St1} = expr(E0, St0),
@@ -513,6 +518,11 @@ lc_tq(Anno, [{b_generate,AnnoG,P0,G0} | Qs0], St0) ->
{P1,St2} = pattern(P0, St1),
{Qs1,St3} = lc_tq(Anno, Qs0, St2),
{[{b_generate,AnnoG,P1,G1} | Qs1],St3};
+lc_tq(Anno, [{m_generate,AnnoG,P0,G0} | Qs0], St0) ->
+ {G1,St1} = expr(G0, St0),
+ {P1,St2} = pattern(P0, St1),
+ {Qs1,St3} = lc_tq(Anno, Qs0, St2),
+ {[{m_generate,AnnoG,P1,G1} | Qs1],St3};
lc_tq(Anno, [F0 | Qs0], #exprec{calltype=Calltype,raw_records=Records}=St0) ->
%% Allow record/2 and expand out as guard test.
IsOverriden = fun(FA) ->
@@ -694,24 +704,20 @@ record_wildcard_init([]) -> none.
record_update(R, Name, Fs, Us0, St0) ->
Anno = element(2, R),
{Pre,Us,St1} = record_exprs(Us0, St0),
- Nf = length(Fs), %# of record fields
- Nu = length(Us), %# of update fields
- Nc = Nf - Nu, %# of copy fields
%% We need a new variable for the record expression
%% to guarantee that it is only evaluated once.
{Var,St2} = new_var(Anno, St1),
+ %% Honor the `strict_record_updates` option needed by `dialyzer`, otherwise
+ %% expand everything to chains of `setelement/3` as that's far more
+ %% efficient in the JIT.
StrictUpdates = strict_record_updates(St2#exprec.compile),
-
- %% Try to be intelligent about which method of updating record to use.
{Update,St} =
if
- Nu =:= 0 ->
- record_match(Var, Name, Anno, Fs, Us, St2);
- Nu =< Nc, not StrictUpdates -> %Few fields updated
+ not StrictUpdates, Us =/= [] ->
{record_setel(Var, Name, Fs, Us), St2};
- true -> %The wide area inbetween
+ true ->
record_match(Var, Name, Anno, Fs, Us, St2)
end,
{{block,Anno,Pre ++ [{match,Anno,Var,R},Update]},St}.
diff --git a/lib/stdlib/src/erl_features.erl b/lib/stdlib/src/erl_features.erl
index 625a9d7952..44056a27b0 100644
--- a/lib/stdlib/src/erl_features.erl
+++ b/lib/stdlib/src/erl_features.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2021-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2021-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
%% %CopyrightEnd%
%%
-module(erl_features).
+-feature(maybe_expr, enable).
-export([all/0,
configurable/0,
@@ -25,7 +26,6 @@
short/1,
long/1,
enabled/0,
- load_allowed/1,
keywords/0,
keywords/1,
keyword_fun/2,
@@ -371,7 +371,7 @@ init_features() ->
end,
FOps = lists:filtermap(F, FeatureOps),
{Features, _, _} = collect_features(FOps),
- {Enabled, Keywords} =
+ {Enabled0, Keywords} =
lists:foldl(fun(Ftr, {Ftrs, Keys}) ->
case lists:member(Ftr, Ftrs) of
true ->
@@ -385,6 +385,7 @@ init_features() ->
Features),
%% Save state
+ Enabled = lists:uniq(Enabled0),
enabled_features(Enabled),
set_keywords(Keywords),
persistent_term:put({?MODULE, init_done}, true),
@@ -423,32 +424,6 @@ keywords() ->
set_keywords(Words) ->
persistent_term:put({?MODULE, keywords}, Words).
-%% Check that any features used in the module are enabled in the
-%% runtime system. If not, return
-%% {not_allowed, <list of not enabled features>}.
--spec load_allowed(binary()) -> ok | {not_allowed, [feature()]}.
-load_allowed(Binary) ->
- case erts_internal:beamfile_chunk(Binary, "Meta") of
- undefined ->
- ok;
- Meta ->
- MetaData = erlang:binary_to_term(Meta),
- case proplists:get_value(enabled_features, MetaData) of
- undefined ->
- ok;
- Used ->
- Enabled = enabled(),
- case lists:filter(fun(UFtr) ->
- not lists:member(UFtr, Enabled)
- end,
- Used) of
- [] -> ok;
- NotEnabled ->
- {not_allowed, NotEnabled}
- end
- end
- end.
-
%% Return features used by module or beam file
-spec used(module() | file:filename()) -> [feature()].
used(Module) when is_atom(Module) ->
diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl
index 92651084c7..8f0b1e43ee 100644
--- a/lib/stdlib/src/erl_internal.erl
+++ b/lib/stdlib/src/erl_internal.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -78,6 +78,8 @@ guard_bif(is_map_key, 2) -> true;
guard_bif(length, 1) -> true;
guard_bif(map_size, 1) -> true;
guard_bif(map_get, 2) -> true;
+guard_bif(max, 2) -> true;
+guard_bif(min, 2) -> true;
guard_bif(node, 0) -> true;
guard_bif(node, 1) -> true;
guard_bif(round, 1) -> true;
@@ -564,6 +566,7 @@ is_type(bool, 0) -> true;
is_type(boolean, 0) -> true;
is_type(byte, 0) -> true;
is_type(char, 0) -> true;
+is_type(dynamic, 0) -> true;
is_type(float, 0) -> true;
is_type(function, 0) -> true;
is_type(identifier, 0) -> true;
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index b9b58d6576..55e53cad3d 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
%% Do necessary checking of Erlang code.
-module(erl_lint).
+-feature(maybe_expr, enable).
-export([module/1,module/2,module/3,format_error/1]).
-export([exprs/2,exprs_opt/3,used_vars/2]). % Used from erl_eval.erl.
@@ -314,8 +315,6 @@ format_error({too_many_arguments,Arity}) ->
%% --- patterns and guards ---
format_error(illegal_pattern) -> "illegal pattern";
format_error(illegal_map_key) -> "illegal map key in pattern";
-format_error(illegal_bin_pattern) ->
- "binary patterns cannot be matched in parallel using '='";
format_error(illegal_expr) -> "illegal expression";
format_error({illegal_guard_local_call, {F,A}}) ->
io_lib:format("call to local/imported function ~tw/~w is illegal in guard",
@@ -436,12 +435,8 @@ format_error({undefined_type, {TypeName, Arity}}) ->
io_lib:format("type ~tw~s undefined", [TypeName, gen_type_paren(Arity)]);
format_error({unused_type, {TypeName, Arity}}) ->
io_lib:format("type ~tw~s is unused", [TypeName, gen_type_paren(Arity)]);
-format_error({new_builtin_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s is a new builtin type; "
- "its (re)definition is allowed only until the next release",
- [TypeName, gen_type_paren(Arity)]);
-format_error({builtin_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s is a builtin type; it cannot be redefined",
+format_error({redefine_builtin_type, {TypeName, Arity}}) ->
+ io_lib:format("local redefinition of built-in type: ~w~s",
[TypeName, gen_type_paren(Arity)]);
format_error({renamed_type, OldName, NewName}) ->
io_lib:format("type ~w() is now called ~w(); "
@@ -489,9 +484,6 @@ format_error({deprecated_builtin_type, {Name, Arity},
format_error({not_exported_opaque, {TypeName, Arity}}) ->
io_lib:format("opaque type ~tw~s is not exported",
[TypeName, gen_type_paren(Arity)]);
-format_error({underspecified_opaque, {TypeName, Arity}}) ->
- io_lib:format("opaque type ~tw~s is underspecified and therefore meaningless",
- [TypeName, gen_type_paren(Arity)]);
format_error({bad_dialyzer_attribute,Term}) ->
io_lib:format("badly formed dialyzer attribute: ~tw", [Term]);
format_error({bad_dialyzer_option,Term}) ->
@@ -674,13 +666,19 @@ start(File, Opts) ->
true, Opts)},
{keyword_warning,
bool_option(warn_keywords, nowarn_keywords,
- false, Opts)}
+ false, Opts)},
+ {redefined_builtin_type,
+ bool_option(warn_redefined_builtin_type, nowarn_redefined_builtin_type,
+ true, Opts)},
+ {singleton_typevar,
+ bool_option(warn_singleton_typevar, nowarn_singleton_typevar,
+ true, Opts)}
],
Enabled1 = [Category || {Category,true} <- Enabled0],
Enabled = ordsets:from_list(Enabled1),
Calls = case ordsets:is_element(unused_function, Enabled) of
true ->
- maps:from_list([{{module_info,1},pseudolocals()}]);
+ #{{module_info,1} => pseudolocals()};
false ->
undefined
end,
@@ -1416,7 +1414,7 @@ check_unused_records(Forms, St0) ->
maps:remove(Used, Recs)
end, St1#lint.records, UsedRecords),
Unused = [{Name,Anno} ||
- {Name,{Anno,_Fields}} <- maps:to_list(URecs),
+ Name := {Anno,_Fields} <- URecs,
element(1, loc(Anno, St1)) =:= FirstFile],
foldl(fun ({N,Anno}, St) ->
add_warning(Anno, {unused_record, N}, St)
@@ -1749,10 +1747,9 @@ pattern({op,_Anno,'++',{string,_Ai,_S},R}, Vt, Old, St) ->
pattern({match,_Anno,Pat1,Pat2}, Vt0, Old, St0) ->
{Lvt, Lnew, St1} = pattern(Pat1, Vt0, Old, St0),
{Rvt, Rnew, St2} = pattern(Pat2, Vt0, Old, St1),
- St3 = reject_invalid_alias(Pat1, Pat2, Vt0, St2),
- {Vt1, St4} = vtmerge_pat(Lvt, Rvt, St3),
- {New, St5} = vtmerge_pat(Lnew, Rnew, St4),
- {Vt1, New, St5};
+ {Vt1, St3} = vtmerge_pat(Lvt, Rvt, St2),
+ {New, St4} = vtmerge_pat(Lnew, Rnew, St3),
+ {Vt1, New, St4};
%% Catch legal constant expressions, including unary +,-.
pattern(Pat, _Vt, _Old, St) ->
case is_pattern_expr(Pat) of
@@ -1779,101 +1776,6 @@ check_multi_field_init(Fs, Anno, Fields, St) ->
false -> St
end.
-%% reject_invalid_alias(Pat, Expr, Vt, St) -> St'
-%% Reject aliases for binary patterns at the top level.
-%% Reject aliases for maps patterns at the top level.
-%% The variables table (Vt) are for maps checkking.
-
-reject_invalid_alias_expr({bin,_,_}=P, {match,_,P0,E}, Vt, St0) ->
- St = reject_invalid_alias(P, P0, Vt, St0),
- reject_invalid_alias_expr(P, E, Vt, St);
-reject_invalid_alias_expr({map,_,_}=P, {match,_,P0,E}, Vt, St0) ->
- St = reject_invalid_alias(P, P0, Vt, St0),
- reject_invalid_alias_expr(P, E, Vt, St);
-reject_invalid_alias_expr({match,_,_,_}=P, {match,_,P0,E}, Vt, St0) ->
- St = reject_invalid_alias(P, P0, Vt, St0),
- reject_invalid_alias_expr(P, E, Vt, St);
-reject_invalid_alias_expr(_, _, _, St) -> St.
-
-
-
-%% reject_invalid_alias(Pat1, Pat2, St) -> St'
-%% Aliases of binary patterns, such as <<A:8>> = <<B:4,C:4>> or even
-%% <<A:8>> = <<A:8>>, are not allowed. Traverse the patterns in parallel
-%% and generate an error if any binary aliases are found.
-%% We generate an error even if is obvious that the overall pattern can't
-%% possibly match, for instance, {a,<<A:8>>,c}={x,<<A:8>>} WILL generate an
-%% error.
-%% Maps should reject unbound variables here.
-
-reject_invalid_alias({bin,Anno,_}, {bin,_,_}, _, St) ->
- add_error(Anno, illegal_bin_pattern, St);
-reject_invalid_alias({map,_Anno,Ps1}, {map,_,Ps2}, Vt, St0) ->
- Fun = fun ({map_field_exact,_,{var,A,K},_V}, Sti) ->
- case is_var_bound(K,Vt) of
- true ->
- Sti;
- false ->
- add_error(A, {unbound_var,K}, Sti)
- end;
- ({map_field_exact,_A,_K,_V}, Sti) ->
- Sti
- end,
- foldl(Fun, foldl(Fun, St0, Ps1), Ps2);
-reject_invalid_alias({cons,_,H1,T1}, {cons,_,H2,T2}, Vt, St0) ->
- St = reject_invalid_alias(H1, H2, Vt, St0),
- reject_invalid_alias(T1, T2, Vt, St);
-reject_invalid_alias({tuple,_,Es1}, {tuple,_,Es2}, Vt, St) ->
- reject_invalid_alias_list(Es1, Es2, Vt, St);
-reject_invalid_alias({record,_,Name1,Pfs1}, {record,_,Name2,Pfs2}, Vt,
- #lint{records=Recs}=St) ->
- case Recs of
- #{Name1 := {_Anno1,Fields1}, Name2 := {_Anno2,Fields2}} ->
- reject_invalid_alias_rec(Pfs1, Pfs2, Fields1, Fields2, Vt, St);
- #{} ->
- %% One or more non-existing records. (An error messages has
- %% already been generated, so we are done here.)
- St
- end;
-reject_invalid_alias({match,_,P1,P2}, P, Vt, St0) ->
- St = reject_invalid_alias(P1, P, Vt, St0),
- reject_invalid_alias(P2, P, Vt, St);
-reject_invalid_alias(P, {match,_,_,_}=M, Vt, St) ->
- reject_invalid_alias(M, P, Vt, St);
-reject_invalid_alias(_P1, _P2, _Vt, St) -> St.
-
-reject_invalid_alias_list([E1|Es1], [E2|Es2], Vt, St0) ->
- St = reject_invalid_alias(E1, E2, Vt, St0),
- reject_invalid_alias_list(Es1, Es2, Vt, St);
-reject_invalid_alias_list(_, _, _, St) -> St.
-
-reject_invalid_alias_rec(PfsA0, PfsB0, FieldsA0, FieldsB0, Vt, St) ->
- %% We treat records as if they have been converted to tuples.
- PfsA1 = rbia_field_vars(PfsA0),
- PfsB1 = rbia_field_vars(PfsB0),
- FieldsA1 = rbia_fields(lists:reverse(FieldsA0), 0, []),
- FieldsB1 = rbia_fields(lists:reverse(FieldsB0), 0, []),
- FieldsA = sofs:relation(FieldsA1),
- PfsA = sofs:relation(PfsA1),
- A = sofs:join(FieldsA, 1, PfsA, 1),
- FieldsB = sofs:relation(FieldsB1),
- PfsB = sofs:relation(PfsB1),
- B = sofs:join(FieldsB, 1, PfsB, 1),
- C = sofs:join(A, 2, B, 2),
- D = sofs:projection({external,fun({_,_,P1,_,P2}) -> {P1,P2} end}, C),
- E = sofs:to_external(D),
- {Ps1,Ps2} = lists:unzip(E),
- reject_invalid_alias_list(Ps1, Ps2, Vt, St).
-
-rbia_field_vars(Fs) ->
- [{Name,Pat} || {record_field,_,{atom,_,Name},Pat} <- Fs].
-
-rbia_fields([{record_field,_,{atom,_,Name},_}|Fs], I, Acc) ->
- rbia_fields(Fs, I+1, [{Name,I}|Acc]);
-rbia_fields([_|Fs], I, Acc) ->
- rbia_fields(Fs, I+1, Acc);
-rbia_fields([], _, Acc) -> Acc.
-
%% is_pattern_expr(Expression) -> boolean().
%% Test if a general expression is a valid pattern expression.
@@ -1897,14 +1799,14 @@ is_pattern_expr_1({integer,_Anno,_I}) -> true;
is_pattern_expr_1({float,_Anno,_F}) -> true;
is_pattern_expr_1({atom,_Anno,_A}) -> true;
is_pattern_expr_1({tuple,_Anno,Es}) ->
- all(fun is_pattern_expr/1, Es);
+ all(fun is_pattern_expr_1/1, Es);
is_pattern_expr_1({nil,_Anno}) -> true;
is_pattern_expr_1({cons,_Anno,H,T}) ->
is_pattern_expr_1(H) andalso is_pattern_expr_1(T);
is_pattern_expr_1({op,_Anno,Op,A}) ->
erl_internal:arith_op(Op, 1) andalso is_pattern_expr_1(A);
is_pattern_expr_1({op,_Anno,Op,A1,A2}) ->
- erl_internal:arith_op(Op, 2) andalso all(fun is_pattern_expr/1, [A1,A2]);
+ erl_internal:arith_op(Op, 2) andalso all(fun is_pattern_expr_1/1, [A1,A2]);
is_pattern_expr_1(_Other) -> false.
pattern_map(Ps, Vt0, Old, St0) ->
@@ -2084,7 +1986,8 @@ bit_size_check(Anno, all, #bittype{type=Type}, St) ->
binary -> {all,St};
_ -> {unknown,add_error(Anno, illegal_bitsize, St)}
end;
-bit_size_check(Anno, Size, #bittype{type=Type,unit=Unit}, St) ->
+bit_size_check(Anno, Size, #bittype{type=Type,unit=Unit}, St)
+ when is_integer(Size), is_integer(Unit) ->
Sz = Unit * Size, %Total number of bits!
St2 = elemtype_check(Anno, Type, Sz, St),
{Sz,St2}.
@@ -2445,6 +2348,8 @@ expr({lc,_Anno,E,Qs}, Vt, St) ->
handle_comprehension(E, Qs, Vt, St);
expr({bc,_Anno,E,Qs}, Vt, St) ->
handle_comprehension(E, Qs, Vt, St);
+expr({mc,_Anno,E,Qs}, Vt, St) ->
+ handle_comprehension(E, Qs, Vt, St);
expr({tuple,_Anno,Es}, Vt, St) ->
expr_list(Es, Vt, St);
expr({map,_Anno,Es}, Vt, St) ->
@@ -2632,8 +2537,7 @@ expr({'catch',Anno,E}, Vt, St0) ->
{vtupdate(vtunsafe({'catch',Anno}, Evt, Vt), Evt),St};
expr({match,_Anno,P,E}, Vt, St0) ->
{Evt,St1} = expr(E, Vt, St0),
- {Pvt,Pnew,St2} = pattern(P, vtupdate(Evt, Vt), St1),
- St = reject_invalid_alias_expr(P, E, Vt, St2),
+ {Pvt,Pnew,St} = pattern(P, vtupdate(Evt, Vt), St1),
{vtupdate(Pnew, vtmerge(Evt, Pvt)),St};
expr({maybe_match,Anno,P,E}, Vt, St0) ->
expr({match,Anno,P,E}, Vt, St0);
@@ -2665,7 +2569,10 @@ expr({op,_Anno,_Op,L,R}, Vt, St) ->
expr_list([L,R], Vt, St); %They see the same variables
%% The following are not allowed to occur anywhere!
expr({remote,_Anno,M,_F}, _Vt, St) ->
- {[],add_error(erl_parse:first_anno(M), illegal_expr, St)}.
+ {[],add_error(erl_parse:first_anno(M), illegal_expr, St)};
+expr({ssa_check_when,_Anno,_WantedResult,_Args,_Tag,_Exprs}, _Vt, St) ->
+ {[], St}.
+
%% expr_list(Expressions, Variables, State) ->
%% {UsedVarTable,State}
@@ -2986,104 +2893,130 @@ type_def(Attr, Anno, TypeName, ProtoType, Args, St0) ->
not member(no_auto_import_types, St0#lint.compile) of
true ->
case is_obsolete_builtin_type(TypePair) of
- true -> StoreType(St0);
+ true ->
+ StoreType(St0);
false ->
- case is_newly_introduced_builtin_type(TypePair) of
- %% allow some types just for bootstrapping
- true ->
- Warn = {new_builtin_type, TypePair},
- St1 = add_warning(Anno, Warn, St0),
- StoreType(St1);
- false ->
- add_error(Anno, {builtin_type, TypePair}, St0)
- end
+ %% Starting from OTP 26, redefining built-in types
+ %% is allowed.
+ St1 = StoreType(St0),
+ warn_redefined_builtin_type(Anno, TypePair, St1)
end;
false ->
case is_map_key(TypePair, TypeDefs) of
true ->
add_error(Anno, {redefine_type, TypePair}, St0);
false ->
- St1 = case
- Attr =:= opaque andalso
- is_underspecified(ProtoType, Arity)
- of
- true ->
- Warn = {underspecified_opaque, TypePair},
- add_warning(Anno, Warn, St0);
- false -> St0
- end,
- StoreType(St1)
+ StoreType(St0)
end
end.
-is_underspecified({type,_,term,[]}, 0) -> true;
-is_underspecified({type,_,any,[]}, 0) -> true;
-is_underspecified(_ProtType, _Arity) -> false.
+warn_redefined_builtin_type(Anno, TypePair, #lint{compile=Opts}=St) ->
+ case is_warn_enabled(redefined_builtin_type, St) of
+ true ->
+ NoWarn = [Type ||
+ {nowarn_redefined_builtin_type, Type0} <- Opts,
+ Type <- lists:flatten([Type0])],
+ case lists:member(TypePair, NoWarn) of
+ true ->
+ St;
+ false ->
+ Warn = {redefine_builtin_type, TypePair},
+ add_warning(Anno, Warn, St)
+ end;
+ false ->
+ St
+ end.
check_type(Types, St) ->
- {SeenVars, St1} = check_type(Types, maps:new(), St),
+ {SeenVars, St1} = check_type_1(Types, maps:new(), St),
maps:fold(fun(Var, {seen_once, Anno}, AccSt) ->
case atom_to_list(Var) of
"_"++_ -> AccSt;
_ -> add_error(Anno, {singleton_typevar, Var}, AccSt)
end;
+ (Var, {seen_once_union, Anno}, AccSt) ->
+ case is_warn_enabled(singleton_typevar, AccSt) of
+ true ->
+ case atom_to_list(Var) of
+ "_"++_ -> AccSt;
+ _ -> add_warning(Anno, {singleton_typevar, Var}, AccSt)
+ end;
+ false ->
+ AccSt
+ end;
(_Var, seen_multiple, AccSt) ->
AccSt
end, St1, SeenVars).
-check_type({ann_type, _A, [_Var, Type]}, SeenVars, St) ->
- check_type(Type, SeenVars, St);
-check_type({remote_type, A, [{atom, _, Mod}, {atom, _, Name}, Args]},
+check_type_1({type, Anno, TypeName, Args}=Type, SeenVars, #lint{types=Types}=St) ->
+ TypePair = {TypeName,
+ if
+ is_list(Args) -> length(Args);
+ true -> 0
+ end},
+ case is_map_key(TypePair, Types) of
+ true ->
+ check_type_2(Type, SeenVars, used_type(TypePair, Anno, St));
+ false ->
+ check_type_2(Type, SeenVars, St)
+ end;
+check_type_1(Types, SeenVars, St) ->
+ check_type_2(Types, SeenVars, St).
+
+check_type_2({ann_type, _A, [_Var, Type]}, SeenVars, St) ->
+ check_type_1(Type, SeenVars, St);
+check_type_2({remote_type, A, [{atom, _, Mod}, {atom, _, Name}, Args]},
SeenVars, St00) ->
St0 = check_module_name(Mod, A, St00),
St = deprecated_type(A, Mod, Name, Args, St0),
CurrentMod = St#lint.module,
case Mod =:= CurrentMod of
- true -> check_type({user_type, A, Name, Args}, SeenVars, St);
+ true -> check_type_2({user_type, A, Name, Args}, SeenVars, St);
false ->
lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
- check_type(T, AccSeenVars, AccSt)
+ check_type_1(T, AccSeenVars, AccSt)
end, {SeenVars, St}, Args)
end;
-check_type({integer, _A, _}, SeenVars, St) -> {SeenVars, St};
-check_type({atom, _A, _}, SeenVars, St) -> {SeenVars, St};
-check_type({var, _A, '_'}, SeenVars, St) -> {SeenVars, St};
-check_type({var, A, Name}, SeenVars, St) ->
+check_type_2({integer, _A, _}, SeenVars, St) -> {SeenVars, St};
+check_type_2({atom, _A, _}, SeenVars, St) -> {SeenVars, St};
+check_type_2({var, _A, '_'}, SeenVars, St) -> {SeenVars, St};
+check_type_2({var, A, Name}, SeenVars, St) ->
NewSeenVars =
case maps:find(Name, SeenVars) of
{ok, {seen_once, _}} -> maps:put(Name, seen_multiple, SeenVars);
+ {ok, {seen_once_union, _}} -> maps:put(Name, seen_multiple, SeenVars);
{ok, seen_multiple} -> SeenVars;
error -> maps:put(Name, {seen_once, A}, SeenVars)
end,
{NewSeenVars, St};
-check_type({type, A, bool, []}, SeenVars, St) ->
+check_type_2({type, A, bool, []}, SeenVars, St) ->
{SeenVars, add_warning(A, {renamed_type, bool, boolean}, St)};
-check_type({type, A, 'fun', [Dom, Range]}, SeenVars, St) ->
+check_type_2({type, A, 'fun', [Dom, Range]}, SeenVars, St) ->
St1 =
case Dom of
{type, _, product, _} -> St;
{type, _, any} -> St;
_ -> add_error(A, {type_syntax, 'fun'}, St)
end,
- check_type({type, nowarn(), product, [Dom, Range]}, SeenVars, St1);
-check_type({type, A, range, [From, To]}, SeenVars, St) ->
+ check_type_2({type, nowarn(), product, [Dom, Range]}, SeenVars, St1);
+check_type_2({type, A, range, [From, To]}, SeenVars, St) ->
St1 =
case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of
{{integer, _, X}, {integer, _, Y}} when X < Y -> St;
_ -> add_error(A, {type_syntax, range}, St)
end,
{SeenVars, St1};
-check_type({type, _A, map, any}, SeenVars, St) ->
+check_type_2({type, _A, map, any}, SeenVars, St) ->
{SeenVars, St};
-check_type({type, _A, map, Pairs}, SeenVars, St) ->
+check_type_2({type, _A, map, Pairs}, SeenVars, St) ->
lists:foldl(fun(Pair, {AccSeenVars, AccSt}) ->
- check_type(Pair, AccSeenVars, AccSt)
+ check_type_2(Pair, AccSeenVars, AccSt)
end, {SeenVars, St}, Pairs);
-check_type({type, _A, map_field_assoc, [Dom, Range]}, SeenVars, St) ->
- check_type({type, nowarn(), product, [Dom, Range]}, SeenVars, St);
-check_type({type, _A, tuple, any}, SeenVars, St) -> {SeenVars, St};
-check_type({type, _A, any}, SeenVars, St) -> {SeenVars, St};
-check_type({type, A, binary, [Base, Unit]}, SeenVars, St) ->
+check_type_2({type, _A, map_field_assoc, [Dom, Range]}, SeenVars, St) ->
+ check_type_2({type, nowarn(), product, [Dom, Range]}, SeenVars, St);
+check_type_2({type, _A, tuple, any}, SeenVars, St) -> {SeenVars, St};
+check_type_2({type, _A, any}, SeenVars, St) -> {SeenVars, St};
+check_type_2({type, A, binary, [Base, Unit]}, SeenVars, St) ->
St1 =
case {erl_eval:partial_eval(Base), erl_eval:partial_eval(Unit)} of
{{integer, _, BaseVal},
@@ -3091,20 +3024,43 @@ check_type({type, A, binary, [Base, Unit]}, SeenVars, St) ->
_ -> add_error(A, {type_syntax, binary}, St)
end,
{SeenVars, St1};
-check_type({type, A, record, [Name|Fields]}, SeenVars, St) ->
+check_type_2({type, A, record, [Name|Fields]}, SeenVars, St) ->
case Name of
{atom, _, Atom} ->
St1 = used_record(Atom, St),
check_record_types(A, Atom, Fields, SeenVars, St1);
_ -> {SeenVars, add_error(A, {type_syntax, record}, St)}
end;
-check_type({type, _A, Tag, Args}, SeenVars, St) when Tag =:= product;
- Tag =:= union;
- Tag =:= tuple ->
+check_type_2({type, _A, Tag, Args}=_F, SeenVars, St) when Tag =:= product;
+ Tag =:= tuple ->
lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
- check_type(T, AccSeenVars, AccSt)
- end, {SeenVars, St}, Args);
-check_type({type, Anno, TypeName, Args}, SeenVars, St) ->
+ check_type_1(T, AccSeenVars, AccSt)
+ end, {SeenVars, St}, Args);
+check_type_2({type, _A, union, Args}=_F, SeenVars0, St) ->
+ lists:foldl(fun(T, {AccSeenVars0, AccSt}) ->
+ {SeenVars1, St0} = check_type_1(T, SeenVars0, AccSt),
+ AccSeenVars = maps:merge_with(
+ fun (K, {seen_once, Anno}, {seen_once, _}) ->
+ case SeenVars0 of
+ #{K := _} ->
+ %% Unused outside of this union.
+ {seen_once, Anno};
+ #{} ->
+ {seen_once_union, Anno}
+ end;
+ (_K, {seen_once, Anno}, {seen_once_union, _}) ->
+ {seen_once_union, Anno};
+ (_K, {seen_once_union, _}=R, {seen_once, _}) -> R;
+ (_K, {seen_once_union, _}=R, {seen_once_union, _}) -> R;
+ (_K, {seen_once_union, _}, Else) -> Else;
+ (_K, {seen_once, _}, Else) -> Else;
+ (_K, Else, {seen_once_union, _}) -> Else;
+ (_K, Else, {seen_once, _}) -> Else;
+ (_K, Else1, _Else2) -> Else1
+ end, AccSeenVars0, SeenVars1),
+ {AccSeenVars, St0}
+ end, {SeenVars0, St}, Args);
+check_type_2({type, Anno, TypeName, Args}, SeenVars, St) ->
#lint{module = Module, types=Types} = St,
Arity = length(Args),
TypePair = {TypeName, Arity},
@@ -3120,20 +3076,26 @@ check_type({type, Anno, TypeName, Args}, SeenVars, St) ->
Tag = deprecated_builtin_type,
W = {Tag, TypePair, Replacement, Rel},
add_warning(Anno, W, St)
- end;
- _ -> St
- end,
- check_type({type, nowarn(), product, Args}, SeenVars, St1);
-check_type({user_type, A, TypeName, Args}, SeenVars, St) ->
+ end;
+ _ ->
+ case is_default_type(TypePair) of
+ true ->
+ used_type(TypePair, Anno, St);
+ false ->
+ St
+ end
+ end,
+ check_type_2({type, nowarn(), product, Args}, SeenVars, St1);
+check_type_2({user_type, A, TypeName, Args}, SeenVars, St) ->
Arity = length(Args),
TypePair = {TypeName, Arity},
St1 = used_type(TypePair, A, St),
lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
- check_type(T, AccSeenVars, AccSt)
+ check_type_1(T, AccSeenVars, AccSt)
end, {SeenVars, St1}, Args);
-check_type([{typed_record_field,Field,_T}|_], SeenVars, St) ->
+check_type_2([{typed_record_field,Field,_T}|_], SeenVars, St) ->
{SeenVars, add_error(element(2, Field), old_abstract_code, St)};
-check_type(I, SeenVars, St) ->
+check_type_2(I, SeenVars, St) ->
case erl_eval:partial_eval(I) of
{integer,_A,_Integer} -> {SeenVars, St};
_Other ->
@@ -3168,7 +3130,7 @@ check_record_types([{type, _, field_type, [{atom, Anno, FName}, Type]}|Left],
false -> St1
end,
%% Check Type
- {NewSeenVars, St3} = check_type(Type, SeenVars, St2),
+ {NewSeenVars, St3} = check_type_2(Type, SeenVars, St2),
NewSeenFields = ordsets:add_element(FName, SeenFields),
check_record_types(Left, Name, DefFields, NewSeenVars, St3, NewSeenFields);
check_record_types([], _Name, _DefFields, SeenVars, St, _SeenFields) ->
@@ -3184,8 +3146,6 @@ used_type(TypePair, Anno, #lint{usage = Usage, file = File} = St) ->
is_default_type({Name, NumberOfTypeVariables}) ->
erl_internal:is_type(Name, NumberOfTypeVariables).
-is_newly_introduced_builtin_type({Name, _}) when is_atom(Name) -> false.
-
is_obsolete_builtin_type(TypePair) ->
obsolete_builtin_type(TypePair) =/= no.
@@ -3406,7 +3366,7 @@ check_unused_types_1(Forms, #lint{types=Ts}=St) ->
reached_types(#lint{usage = Usage}) ->
Es = [{From, {type, To}} ||
- {To, UsedTs} <- maps:to_list(Usage#usage.used_types),
+ To := UsedTs <- Usage#usage.used_types,
#used_type{at = From} <- UsedTs],
Initial = initially_reached_types(Es),
G = sofs:family_to_digraph(sofs:rel2fam(sofs:relation(Es))),
@@ -3480,12 +3440,12 @@ is_function_dialyzer_option(Option) ->
is_module_dialyzer_option(Option) ->
lists:member(Option,
[no_return,no_unused,no_improper_lists,no_fun_app,
- no_match,no_opaque,no_fail_call,no_contracts,
+ no_match,no_opaque,no_fail_call,no_contracts,no_unknown,
no_behaviours,no_undefined_callbacks,unmatched_returns,
error_handling,race_conditions,no_missing_calls,
specdiffs,overspecs,underspecs,unknown,
no_underspecs,extra_return,no_extra_return,
- missing_return,no_missing_return
+ missing_return,no_missing_return,overlapping_contract
]).
%% try_catch_clauses(Scs, Ccs, In, ImportVarTable, State) ->
@@ -3612,7 +3572,7 @@ icrt_export([], _, _, _, Acc) ->
handle_comprehension(E, Qs, Vt0, St0) ->
{Vt1, Uvt, St1} = lc_quals(Qs, Vt0, St0),
- {Evt,St2} = expr(E, Vt1, St1),
+ {Evt,St2} = comprehension_expr(E, Vt1, St1),
Vt2 = vtupdate(Evt, Vt1),
%% Shadowed global variables.
{_,St3} = check_old_unused_vars(Vt2, Uvt, St2),
@@ -3629,6 +3589,11 @@ handle_comprehension(E, Qs, Vt0, St0) ->
Vt = vt_no_unsafe(vt_no_unused(Vt4)),
{Vt, St}.
+comprehension_expr({map_field_assoc,_,K,V}, Vt0, St0) ->
+ expr_list([K,V], Vt0, St0);
+comprehension_expr(E, Vt, St) ->
+ expr(E, Vt, St).
+
%% lc_quals(Qualifiers, ImportVarTable, State) ->
%% {VarTable,ShadowedVarTable,State}
%% Test list comprehension qualifiers, return all variables. Allow
@@ -3652,6 +3617,9 @@ lc_quals([{b_generate,_Anno,P,E} | Qs], Vt0, Uvt0, St0) ->
St1 = handle_bitstring_gen_pat(P,St0),
{Vt,Uvt,St} = handle_generator(P,E,Vt0,Uvt0,St1),
lc_quals(Qs, Vt, Uvt, St);
+lc_quals([{m_generate,_Anno,P,E} | Qs], Vt0, Uvt0, St0) ->
+ {Vt,Uvt,St} = handle_generator(P,E,Vt0,Uvt0,St0),
+ lc_quals(Qs, Vt, Uvt, St);
lc_quals([F|Qs], Vt, Uvt, St0) ->
Info = is_guard_test2_info(St0),
{Fvt,St1} = case is_guard_test2(F, Info) of
@@ -3673,7 +3641,7 @@ handle_generator(P,E,Vt,Uvt,St0) ->
%% Forget variables local to E immediately.
Vt1 = vtupdate(vtold(Evt, Vt), Vt),
{_, St2} = check_unused_vars(Evt, Vt, St1),
- {Pvt,Pnew,St3} = pattern(P, Vt1, [], St2),
+ {Pvt,Pnew,St3} = comprehension_pattern(P, Vt1, St2),
%% Have to keep fresh variables separated from used variables somehow
%% in order to handle for example X = foo(), [X || <<X:X>> <- bar()].
%% 1 2 2 1
@@ -3685,6 +3653,11 @@ handle_generator(P,E,Vt,Uvt,St0) ->
Vt3 = vtupdate(vtsubtract(Vt2, Pnew), Pnew),
{Vt3,NUvt,St5}.
+comprehension_pattern({map_field_exact,_,K,V}, Vt, St) ->
+ pattern_list([K,V], Vt, [], St);
+comprehension_pattern(P, Vt, St) ->
+ pattern(P, Vt, [], St).
+
handle_bitstring_gen_pat({bin,_,Segments=[_|_]},St) ->
case lists:last(Segments) of
{bin_element,Anno,_,default,Flags} when is_list(Flags) ->
@@ -3957,14 +3930,6 @@ warn_unused_vars(U, Vt, St0) ->
UVt = map(fun ({V,{State,_,As}}) -> {V,{State,used,As}} end, U),
{vtmerge(Vt, UVt), St1}.
-
-is_var_bound(V, Vt) ->
- case orddict:find(V, Vt) of
- {ok,{bound,_Usage,_}} -> true;
- _ -> false
- end.
-
-
%% vtupdate(UpdVarTable, VarTable) -> VarTable.
%% Add the variables in the updated vartable to VarTable. The variables
%% will be updated with their property in UpdVarTable. The state of
@@ -4252,19 +4217,18 @@ keyword_warning(Anno, Atom, St) ->
%% Add warning for bad calls to io:fwrite/format functions.
format_function(DefAnno, M, F, As, St) ->
- case is_format_function(M, F) of
- true ->
- case St#lint.warn_format of
- Lev when Lev > 0 ->
- case check_format_1(As) of
- {warn,Level,Fmt,Fas} when Level =< Lev ->
- add_warning(DefAnno, {format_error,{Fmt,Fas}}, St);
- {warn,Level,Anno,Fmt,Fas} when Level =< Lev ->
- add_warning(Anno, {format_error,{Fmt,Fas}}, St);
- _ -> St
- end;
- _Lev -> St
- end;
+ maybe
+ true ?= is_format_function(M, F),
+ Lev = St#lint.warn_format,
+ true ?= Lev > 0,
+ case check_format_1(As) of
+ {warn,Level,Fmt,Fas} when Level =< Lev ->
+ add_warning(DefAnno, {format_error,{Fmt,Fas}}, St);
+ {warn,Level,Anno,Fmt,Fas} when Level =< Lev ->
+ add_warning(Anno, {format_error,{Fmt,Fas}}, St);
+ _ -> St
+ end
+ else
false -> St
end.
@@ -4383,9 +4347,11 @@ extract_sequences(Fmt, Need0) ->
end
end.
-extract_sequence(1, [$-,C|Fmt], Need) when C >= $0, C =< $9 ->
+extract_sequence(1, [$-,C|Fmt], Need)
+ when is_integer(C), C >= $0, C =< $9 ->
extract_sequence_digits(1, Fmt, Need);
-extract_sequence(1, [C|Fmt], Need) when C >= $0, C =< $9 ->
+extract_sequence(1, [C|Fmt], Need)
+ when is_integer(C), C >= $0, C =< $9 ->
extract_sequence_digits(1, Fmt, Need);
extract_sequence(1, [$-,$*|Fmt], Need) ->
extract_sequence(2, Fmt, [int|Need]);
@@ -4394,7 +4360,8 @@ extract_sequence(1, [$*|Fmt], Need) ->
extract_sequence(1, Fmt, Need) ->
extract_sequence(2, Fmt, Need);
-extract_sequence(2, [$.,C|Fmt], Need) when C >= $0, C =< $9 ->
+extract_sequence(2, [$.,C|Fmt], Need)
+ when is_integer(C), C >= $0, C =< $9 ->
extract_sequence_digits(2, Fmt, Need);
extract_sequence(2, [$.,$*|Fmt], Need) ->
extract_sequence(3, Fmt, [int|Need]);
@@ -4410,36 +4377,23 @@ extract_sequence(3, [$.,_|Fmt], Need) ->
extract_sequence(3, Fmt, Need) ->
extract_sequence(4, Fmt, Need);
-extract_sequence(4, [$t, $l | Fmt], Need) ->
- extract_sequence(4, [$l, $t | Fmt], Need);
-extract_sequence(4, [$t, $c | Fmt], Need) ->
- extract_sequence(5, [$c|Fmt], Need);
-extract_sequence(4, [$t, $s | Fmt], Need) ->
- extract_sequence(5, [$s|Fmt], Need);
-extract_sequence(4, [$t, $p | Fmt], Need) ->
- extract_sequence(5, [$p|Fmt], Need);
-extract_sequence(4, [$t, $P | Fmt], Need) ->
- extract_sequence(5, [$P|Fmt], Need);
-extract_sequence(4, [$t, $w | Fmt], Need) ->
- extract_sequence(5, [$w|Fmt], Need);
-extract_sequence(4, [$t, $W | Fmt], Need) ->
- extract_sequence(5, [$W|Fmt], Need);
-extract_sequence(4, [$t, C | _Fmt], _Need) ->
- {error,"invalid control ~t" ++ [C]};
-extract_sequence(4, [$l, $p | Fmt], Need) ->
- extract_sequence(5, [$p|Fmt], Need);
-extract_sequence(4, [$l, $t, $p | Fmt], Need) ->
- extract_sequence(5, [$p|Fmt], Need);
-extract_sequence(4, [$l, $P | Fmt], Need) ->
- extract_sequence(5, [$P|Fmt], Need);
-extract_sequence(4, [$l, $t, $P | Fmt], Need) ->
- extract_sequence(5, [$P|Fmt], Need);
-extract_sequence(4, [$l, $t, C | _Fmt], _Need) ->
- {error,"invalid control ~lt" ++ [C]};
-extract_sequence(4, [$l, C | _Fmt], _Need) ->
- {error,"invalid control ~l" ++ [C]};
-extract_sequence(4, Fmt, Need) ->
- extract_sequence(5, Fmt, Need);
+extract_sequence(4, Fmt0, Need) ->
+ case extract_modifiers(Fmt0, []) of
+ {error, _} = Error ->
+ Error;
+ {[C|Fmt], Modifiers} ->
+ maybe
+ ok ?= check_modifiers(C, Modifiers),
+ case ordsets:is_element($K, Modifiers) of
+ true ->
+ extract_sequence(5, [C|Fmt], ['fun'|Need]);
+ false ->
+ extract_sequence(5, [C|Fmt], Need)
+ end
+ end;
+ {[], _} ->
+ extract_sequence(5, [], Need)
+ end;
extract_sequence(5, [C|Fmt], Need0) ->
case control_type(C, Need0) of
@@ -4448,11 +4402,56 @@ extract_sequence(5, [C|Fmt], Need0) ->
end;
extract_sequence(_, [], _Need) -> {error,"truncated"}.
-extract_sequence_digits(Fld, [C|Fmt], Need) when C >= $0, C =< $9 ->
+extract_sequence_digits(Fld, [C|Fmt], Need)
+ when is_integer(C), C >= $0, C =< $9 ->
extract_sequence_digits(Fld, Fmt, Need);
extract_sequence_digits(Fld, Fmt, Need) ->
extract_sequence(Fld+1, Fmt, Need).
+extract_modifiers([C|Fmt], Modifiers0) ->
+ case is_modifier(C) of
+ true ->
+ case ordsets:add_element(C, Modifiers0) of
+ Modifiers0 ->
+ {error, "repeated modifier " ++ [C]};
+ Modifiers ->
+ extract_modifiers(Fmt, Modifiers)
+ end;
+ false ->
+ {[C|Fmt], Modifiers0}
+ end;
+extract_modifiers([], Modifiers) ->
+ {[], Modifiers}.
+
+check_modifiers(C, Modifiers) ->
+ maybe
+ ok ?= check_modifiers_1("l", Modifiers, C, "Pp"),
+ ok ?= check_modifiers_1("lt", Modifiers, C, "cPpsWw"),
+ ok ?= check_modifiers_1("Kk", Modifiers, C, "PpWw")
+ end.
+
+check_modifiers_1(M, Modifiers, C, Cs) ->
+ case ordsets:intersection(ordsets:from_list(M), Modifiers) of
+ [_]=Mod ->
+ case lists:member(C, Cs) of
+ true ->
+ ok;
+ false ->
+ {error, "invalid modifier/control combination ~" ++
+ Mod ++ [C]}
+ end;
+ [] ->
+ ok;
+ [_,_]=M ->
+ {error, "conflicting modifiers ~" ++ M ++ [C]}
+ end.
+
+is_modifier($k) -> true;
+is_modifier($K) -> true;
+is_modifier($l) -> true;
+is_modifier($t) -> true;
+is_modifier(_) -> false.
+
control_type($~, Need) -> Need;
control_type($c, Need) -> [int|Need];
control_type($f, Need) -> [float|Need];
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index 505a6b1af8..bef858cdc5 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -31,6 +31,7 @@ pat_expr pat_expr_max map_pat_expr record_pat_expr
pat_argument_list pat_exprs
list tail
list_comprehension lc_expr lc_exprs
+map_comprehension
binary_comprehension
tuple
record_expr record_tuple record_field record_fields
@@ -49,7 +50,31 @@ type_sig type_sigs type_guard type_guards fun_type binary_type
type_spec spec_fun typed_exprs typed_record_fields field_types field_type
map_pair_types map_pair_type
bin_base_type bin_unit_type
-maybe_expr maybe_match_exprs maybe_match.
+maybe_expr maybe_match_exprs maybe_match
+clause_body_exprs
+ssa_check_anno
+ssa_check_anno_clause
+ssa_check_anno_clauses
+ssa_check_args
+ssa_check_binary_lit
+ssa_check_binary_lit_bytes_ls
+ssa_check_binary_lit_rest
+ssa_check_clause_args
+ssa_check_clause_args_ls
+ssa_check_expr
+ssa_check_exprs
+ssa_check_fun_ref
+ssa_check_list_lit
+ssa_check_list_lit_ls
+ssa_check_map_key
+ssa_check_map_key_element
+ssa_check_map_key_elements
+ssa_check_map_key_list
+ssa_check_map_key_tuple_elements
+ssa_check_pat
+ssa_check_pats
+ssa_check_when_clause
+ssa_check_when_clauses.
Terminals
char integer float atom string var
@@ -67,7 +92,8 @@ char integer float atom string var
'!' '=' '::' '..' '...'
'?='
'spec' 'callback' % helper
-dot.
+dot
+'%ssa%'.
Expect 0.
@@ -86,6 +112,7 @@ Left 500 mult_op.
Unary 600 prefix_op.
Nonassoc 700 '#'.
Nonassoc 800 ':'.
+Nonassoc 900 clause_body_exprs.
%% Types
@@ -225,8 +252,7 @@ clause_args -> pat_argument_list : element(1, '$1').
clause_guard -> 'when' guard : '$2'.
clause_guard -> '$empty' : [].
-clause_body -> '->' exprs: '$2'.
-
+clause_body -> '->' clause_body_exprs: '$2'.
expr -> 'catch' expr : {'catch',?anno('$1'),'$2'}.
expr -> expr '=' expr : {match,first_anno('$1'),'$1','$3'}.
@@ -251,6 +277,7 @@ expr_max -> atomic : '$1'.
expr_max -> list : '$1'.
expr_max -> binary : '$1'.
expr_max -> list_comprehension : '$1'.
+expr_max -> map_comprehension : '$1'.
expr_max -> binary_comprehension : '$1'.
expr_max -> tuple : '$1'.
expr_max -> '(' expr ')' : '$2'.
@@ -324,12 +351,15 @@ bit_size_expr -> expr_max : '$1'.
list_comprehension -> '[' expr '||' lc_exprs ']' :
{lc,?anno('$1'),'$2','$4'}.
+map_comprehension -> '#' '{' map_field_assoc '||' lc_exprs '}' :
+ {mc,?anno('$1'),'$3','$5'}.
binary_comprehension -> '<<' expr_max '||' lc_exprs '>>' :
{bc,?anno('$1'),'$2','$4'}.
lc_exprs -> lc_expr : ['$1'].
lc_exprs -> lc_expr ',' lc_exprs : ['$1'|'$3'].
lc_expr -> expr : '$1'.
+lc_expr -> map_field_exact '<-' expr : {m_generate,?anno('$2'),'$1','$3'}.
lc_expr -> expr '<-' expr : {generate,?anno('$2'),'$1','$3'}.
lc_expr -> binary '<=' expr : {b_generate,?anno('$2'),'$1','$3'}.
@@ -500,6 +530,9 @@ pat_argument_list -> '(' pat_exprs ')' : {'$2',?anno('$1')}.
exprs -> expr : ['$1'].
exprs -> expr ',' exprs : ['$1' | '$3'].
+clause_body_exprs -> ssa_check_when_clauses exprs : '$1' ++ '$2'.
+clause_body_exprs -> exprs : '$1'.
+
pat_exprs -> pat_expr : ['$1'].
pat_exprs -> pat_expr ',' pat_exprs : ['$1' | '$3'].
@@ -549,6 +582,132 @@ comp_op -> '>' : '$1'.
comp_op -> '=:=' : '$1'.
comp_op -> '=/=' : '$1'.
+ssa_check_when_clauses -> ssa_check_when_clause : ['$1'].
+ssa_check_when_clauses -> ssa_check_when_clause ssa_check_when_clauses :
+ ['$1'|'$2'].
+
+ssa_check_when_clause -> '%ssa%' atom ssa_check_clause_args_ls 'when' atom '->'
+ ssa_check_exprs '.' :
+ {ssa_check_when, ?anno('$1'), '$2', '$3', '$5', '$7'}.
+
+ssa_check_when_clause -> '%ssa%' ssa_check_clause_args_ls 'when' atom '->'
+ ssa_check_exprs '.' :
+ {ssa_check_when, ?anno('$1'), {atom,?anno('$1'),pass}, '$2', '$4', '$6'}.
+
+ssa_check_exprs -> ssa_check_expr : [add_anno_check('$1', [])].
+ssa_check_exprs -> ssa_check_expr ssa_check_anno : [add_anno_check('$1', '$2')].
+ssa_check_exprs -> ssa_check_expr ',' ssa_check_exprs :
+ [add_anno_check('$1', [])|'$3'].
+ssa_check_exprs -> ssa_check_expr ssa_check_anno ',' ssa_check_exprs :
+ [add_anno_check('$1', '$2')|'$4'].
+
+ssa_check_anno -> '{' ssa_check_anno_clauses '}' : '$2'.
+
+ssa_check_anno_clauses -> ssa_check_anno_clause : ['$1'].
+ssa_check_anno_clauses -> ssa_check_anno_clause ',' ssa_check_anno_clauses :
+ ['$1'|'$3'].
+
+ssa_check_anno_clause -> atom '=>' ssa_check_pat : {term, '$1', '$3'}.
+
+ssa_check_expr -> var '=' atom ssa_check_args :
+ {check_expr, ?anno('$1'), [set, '$1', '$3'|'$4']}.
+ssa_check_expr -> atom ssa_check_args :
+ {check_expr, ?anno('$1'), [none, '$1'|'$2']}.
+ssa_check_expr -> var '=' atom ':' atom ssa_check_args :
+ {check_expr, ?anno('$1'), [set, '$1', {'$3', '$5'}|'$6']}.
+ssa_check_expr -> atom integer :
+ {check_expr, ?anno('$1'), build_ssa_check_label('$1', '$2')}.
+ssa_check_expr -> atom var :
+ {check_expr, ?anno('$1'), build_ssa_check_label('$1', '$2')}.
+
+ssa_check_clause_args_ls -> '(' ')' : [].
+ssa_check_clause_args_ls -> '(' ssa_check_clause_args ')' : '$2'.
+ssa_check_clause_args_ls -> '(' '...' ')' : ['$2'].
+
+ssa_check_clause_args -> var : ['$1'].
+ssa_check_clause_args -> var ',' ssa_check_clause_args : ['$1'|'$3'].
+ssa_check_clause_args -> var ',' '...' : ['$1', '$3'].
+
+ssa_check_args -> '(' ')' : {[], ?anno('$1')}.
+ssa_check_args -> '(' ssa_check_pats ')' : '$2'.
+ssa_check_args -> '(' '...' ')' : ['$2'].
+
+ssa_check_pats -> ssa_check_pat : ['$1'].
+ssa_check_pats -> ssa_check_pat ',' ssa_check_pats : ['$1'|'$3'].
+ssa_check_pats -> ssa_check_pat ',' '...' : ['$1', '$3'].
+
+ssa_check_pat -> var : '$1'.
+ssa_check_pat -> atom : '$1'.
+ssa_check_pat -> integer : '$1'.
+ssa_check_pat -> float : '$1'.
+ssa_check_pat -> float '(' float ')': {float_epsilon, '$1', '$3'}.
+ssa_check_pat -> ssa_check_fun_ref : '$1'.
+ssa_check_pat -> '{' '}' : {tuple, ?anno('$1'), []}.
+ssa_check_pat -> '{' ssa_check_pats '}' : {tuple, ?anno('$1'), '$2'}.
+ssa_check_pat -> '{' '...' '}' : {tuple, ?anno('$1'), ['$2']}.
+ssa_check_pat -> ssa_check_binary_lit : '$1'.
+ssa_check_pat -> ssa_check_list_lit : '$1'.
+ssa_check_pat -> '#' '{' '}' : {map, ?anno('$1'), []}.
+ssa_check_pat -> '#' '{' ssa_check_map_key_elements '}' : {map, ?anno('$1'), '$3'}.
+
+ssa_check_fun_ref -> 'fun' atom '/' integer : {local_fun, '$2', '$4'}.
+ssa_check_fun_ref -> 'fun' atom ':' atom '/' integer : {external_fun, '$2', '$4', '$6'}.
+
+ssa_check_binary_lit -> '<<' '>>' : {binary, ?anno('$1'), []}.
+ssa_check_binary_lit -> '<<' ssa_check_binary_lit_bytes_ls '>>' :
+ {binary, ?anno('$1'), '$2'}.
+ssa_check_binary_lit -> '<<' ssa_check_binary_lit_rest '>>' :
+ {binary, ?anno('$1'), ['$2']}.
+
+ssa_check_binary_lit_bytes_ls -> integer : ['$1'].
+ssa_check_binary_lit_bytes_ls -> integer ',' ssa_check_binary_lit_bytes_ls :
+ ['$1'|'$3'].
+ssa_check_binary_lit_bytes_ls -> integer ',' ssa_check_binary_lit_rest :
+ ['$1', '$3'].
+
+ssa_check_binary_lit_rest -> integer ':' integer : {'$1', '$3'}.
+
+ssa_check_list_lit -> '[' ']' : {list, ?anno('$1'), []}.
+ssa_check_list_lit -> '[' ssa_check_list_lit_ls ']' :
+ {list, ?anno('$1'), '$2'}.
+
+ssa_check_list_lit_ls -> ssa_check_pat : ['$1'].
+ssa_check_list_lit_ls -> ssa_check_pat ',' ssa_check_list_lit_ls : ['$1'|'$3'].
+ssa_check_list_lit_ls -> ssa_check_pat ',' '...' : ['$1', '$3'].
+ssa_check_list_lit_ls -> ssa_check_pat '|' ssa_check_pat : ['$1'|'$3'].
+
+ssa_check_map_key -> atom : '$1'.
+ssa_check_map_key -> integer : '$1'.
+ssa_check_map_key -> float : '$1'.
+ssa_check_map_key -> '{' ssa_check_map_key_tuple_elements '}' :
+ {tuple, ?anno('$1'), '$2'}.
+ssa_check_map_key -> '{' '}' : {tuple, ?anno('$1'), []}.
+ssa_check_map_key -> ssa_check_binary_lit : '$1'.
+ssa_check_map_key -> '[' ssa_check_map_key_list ']' :
+ {list, ?anno('$1'), '$2'}.
+ssa_check_map_key -> '[' ']' : {list, ?anno('$1'), []}.
+ssa_check_map_key -> '#' '{' '}' : {map, ?anno('$1'), []}.
+ssa_check_map_key -> '#' '{' ssa_check_map_key_elements '}' : '$3'.
+
+ssa_check_map_key_list -> ssa_check_map_key : ['$1'].
+ssa_check_map_key_list -> ssa_check_map_key ',' ssa_check_map_key_list :
+ ['$1'|'$3'].
+ssa_check_map_key_list -> ssa_check_map_key '|' ssa_check_map_key :
+ ['$1'|'$3'].
+
+ssa_check_map_key_elements -> ssa_check_map_key_element : ['$1'].
+ssa_check_map_key_elements -> ssa_check_map_key_element ',' ssa_check_map_key_elements :
+ ['$1'|'$3'].
+
+ssa_check_map_key_element -> ssa_check_map_key '=>' ssa_check_map_key:
+ {'$1', '$3'}.
+%% ssa_check_map_key_element -> ssa_check_map_key '::' top_type:
+%% {type, '$1', '$3'}.
+
+ssa_check_map_key_tuple_elements -> ssa_check_map_key : ['$1'].
+ssa_check_map_key_tuple_elements -> ssa_check_map_key ',' ssa_check_map_key_tuple_elements:
+ ['$1'|'$3'].
+
Header
"%% This file was automatically generated from the file \"erl_parse.yrl\"."
"%%"
@@ -679,6 +838,7 @@ Erlang code.
| af_local_call()
| af_remote_call()
| af_list_comprehension()
+ | af_map_comprehension()
| af_binary_comprehension()
| af_block()
| af_if()
@@ -714,6 +874,9 @@ Erlang code.
-type af_list_comprehension() ::
{'lc', anno(), af_template(), af_qualifier_seq()}.
+-type af_map_comprehension() ::
+ {'mc', anno(), af_assoc(abstract_expr()), af_qualifier_seq()}.
+
-type af_binary_comprehension() ::
{'bc', anno(), af_template(), af_qualifier_seq()}.
@@ -724,6 +887,7 @@ Erlang code.
-type af_qualifier() :: af_generator() | af_filter().
-type af_generator() :: {'generate', anno(), af_pattern(), abstract_expr()}
+ | {'m_generate', anno(), af_assoc_exact(af_pattern()), abstract_expr()}
| {'b_generate', anno(), af_pattern(), abstract_expr()}.
-type af_filter() :: abstract_expr().
@@ -1529,16 +1693,16 @@ abstract_list([H|T], String, A, E) ->
abstract_list(T, [H|String], A, E);
false ->
AbstrList = {cons,A,abstract(H, A, E),abstract(T, A, E)},
- not_string(String, AbstrList, A, E)
+ not_string(String, AbstrList, A)
end;
abstract_list([], String, A, _E) ->
{string, A, lists:reverse(String)};
abstract_list(T, String, A, E) ->
- not_string(String, abstract(T, A, E), A, E).
+ not_string(String, abstract(T, A, E), A).
-not_string([C|T], Result, A, E) ->
- not_string(T, {cons, A, {integer, A, C}, Result}, A, E);
-not_string([], Result, _A, _E) ->
+not_string([C|T], Result, A) ->
+ not_string(T, {cons, A, {integer, A, C}, Result}, A);
+not_string([], Result, _A) ->
Result.
abstract_tuple_list([H|T], A, E) ->
@@ -1841,4 +2005,12 @@ modify_anno1([H|T], Ac, Mf) ->
modify_anno1([], Ac, _Mf) -> {[],Ac};
modify_anno1(E, Ac, _Mf) when not is_tuple(E), not is_list(E) -> {E,Ac}.
+build_ssa_check_label({atom,_,label}, Lbl) ->
+ [label, Lbl];
+build_ssa_check_label({atom,L,_}, _) ->
+ return_error(L, "expected 'label'").
+
+add_anno_check({check_expr,Loc,Args}, AnnoCheck) ->
+ {check_expr,Loc,Args,AnnoCheck}.
+
%% vim: ft=erlang
diff --git a/lib/stdlib/src/erl_posix_msg.erl b/lib/stdlib/src/erl_posix_msg.erl
index e86ba81170..2a6676aede 100644
--- a/lib/stdlib/src/erl_posix_msg.erl
+++ b/lib/stdlib/src/erl_posix_msg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -172,4 +172,4 @@ message_1(exfull) -> <<"message tables full">>;
message_1(nxdomain) -> <<"non-existing domain">>;
message_1(exbadport) -> <<"inet_drv bad port state">>;
message_1(exbadseq) -> <<"inet_drv bad request sequence">>;
-message_1(_) -> <<"unknown POSIX error">>.
+message_1(Other) -> <<"unknown POSIX error: ", (atom_to_binary(Other))/binary>>.
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index 191aa75698..50ff87643c 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -580,12 +580,13 @@ lexpr({cons,_,H,T}, _, Opts) ->
lexpr({lc,_,E,Qs}, _Prec, Opts) ->
Lcl = {list,[{step,[lexpr(E, Opts),leaf(" ||")],lc_quals(Qs, Opts)}]},
{list,[{seq,$[,[],[[]],[{force_nl,leaf(" "),[Lcl]}]},$]]};
- %% {list,[{step,$[,Lcl},$]]};
lexpr({bc,_,E,Qs}, _Prec, Opts) ->
P = max_prec(),
Lcl = {list,[{step,[lexpr(E, P, Opts),leaf(" ||")],lc_quals(Qs, Opts)}]},
{list,[{seq,'<<',[],[[]],[{force_nl,leaf(" "),[Lcl]}]},'>>']};
- %% {list,[{step,'<<',Lcl},'>>']};
+lexpr({mc,_,E,Qs}, _Prec, Opts) ->
+ Lcl = {list,[{step,[map_field(E, Opts),leaf(" ||")],lc_quals(Qs, Opts)}]},
+ {list,[{seq,'#{',[],[[]],[{force_nl,leaf(" "),[Lcl]}]},$}]};
lexpr({tuple,_,Elts}, _, Opts) ->
tuple(Elts, Opts);
lexpr({record_index, _, Name, F}, Prec, Opts) ->
@@ -956,6 +957,9 @@ clauses(Type, Opts, Cs) ->
lc_quals(Qs, Opts) ->
{prefer_nl,[$,],lexprs(Qs, fun lc_qual/2, Opts)}.
+lc_qual({m_generate,_,Pat,E}, Opts) ->
+ Pl = map_field(Pat, Opts),
+ {list,[{step,[Pl,leaf(" <-")],lexpr(E, 0, Opts)}]};
lc_qual({b_generate,_,Pat,E}, Opts) ->
Pl = lexpr(Pat, 0, Opts),
{list,[{step,[Pl,leaf(" <=")],lexpr(E, 0, Opts)}]};
@@ -1367,7 +1371,7 @@ wordtable() ->
L = [begin {leaf,Sz,S} = leaf(W), {S,Sz} end ||
W <- [" ->"," =","<<",">>","[]","after","begin","case","catch",
"end","fun","if","of","receive","try","when"," ::","..",
- " |","maybe","else"]],
+ " |","maybe","else","#{"]],
list_to_tuple(L).
word(' ->', WT) -> element(1, WT);
@@ -1390,7 +1394,8 @@ word(' ::', WT) -> element(17, WT);
word('..', WT) -> element(18, WT);
word(' |', WT) -> element(19, WT);
word('maybe', WT) -> element(20, WT);
-word('else', WT) -> element(21, WT).
+word('else', WT) -> element(21, WT);
+word('#{', WT) -> element(22, WT).
%% Make up an unique variable name for Name that won't clash with any
%% name in Used. We first try by converting the name to uppercase and
diff --git a/lib/stdlib/src/erl_scan.erl b/lib/stdlib/src/erl_scan.erl
index f2e9d2d7b9..b7975c6ed2 100644
--- a/lib/stdlib/src/erl_scan.erl
+++ b/lib/stdlib/src/erl_scan.erl
@@ -93,7 +93,7 @@
-type text_fun() :: fun((atom(), string()) -> boolean()).
-type option() :: 'return' | 'return_white_spaces' | 'return_comments'
| 'text' | {'reserved_word_fun', resword_fun()}
- | {'text_fun', text_fun()}.
+ | {'text_fun', text_fun()} | {'compiler_internal', [term()]}.
-type options() :: option() | [option()].
-type symbol() :: atom() | float() | integer() | string().
-type token() :: {category(), Anno :: erl_anno:anno(), symbol()}
@@ -108,7 +108,11 @@
text_fun = fun(_, _) -> false end :: text_fun(),
ws = false :: boolean(),
comment = false :: boolean(),
- has_fun = false :: boolean()}).
+ has_fun = false :: boolean(),
+ %% True if requested to parse %ssa%-check comments
+ checks = false :: boolean(),
+ %% True if we're scanning inside a %ssa%-check comment
+ in_check = false :: boolean()}).
%%----------------------------------------------------------------------------
@@ -264,15 +268,15 @@ string_thing(_) -> "string".
-define(WHITE_SPACE(C),
is_integer(C) andalso
(C >= $\000 andalso C =< $\s orelse C >= $\200 andalso C =< $\240)).
--define(DIGIT(C), C >= $0 andalso C =< $9).
--define(CHAR(C), is_integer(C), C >= 0).
+-define(DIGIT(C), (is_integer(C) andalso $0 =< C andalso C =< $9)).
+-define(CHAR(C), (is_integer(C) andalso 0 =< C andalso C < 16#110000)).
-define(UNICODE(C),
- is_integer(C) andalso
+ (is_integer(C) andalso
(C >= 0 andalso C < 16#D800 orelse
C > 16#DFFF andalso C < 16#FFFE orelse
- C > 16#FFFF andalso C =< 16#10FFFF)).
+ C > 16#FFFF andalso C =< 16#10FFFF))).
--define(UNI255(C), C >= 0, C =< 16#ff).
+-define(UNI255(C), (is_integer(C) andalso 0 =< C andalso C =< 16#ff)).
options(Opts0) when is_list(Opts0) ->
Opts = lists:foldr(fun expand_opt/2, [], Opts0),
@@ -287,6 +291,8 @@ options(Opts0) when is_list(Opts0) ->
WS = proplists:get_bool(return_white_spaces, Opts),
Txt = proplists:get_bool(text, Opts),
TxtFunOpt = proplists:get_value(text_fun, Opts, none),
+ Internal = proplists:get_value(compiler_internal, Opts, []),
+ Checks = proplists:get_bool(ssa_checks, Internal),
DefTxtFun = fun(_, _) -> Txt end,
{HasFun, TxtFun} =
if
@@ -298,7 +304,8 @@ options(Opts0) when is_list(Opts0) ->
comment = Comment,
ws = WS,
text_fun = TxtFun,
- has_fun = HasFun};
+ has_fun = HasFun,
+ checks = Checks};
options(Opt) ->
options([Opt]).
@@ -330,8 +337,8 @@ expand_opt(O, Os) ->
tokens1(Cs, St, Line, Col, Toks, Fun, Any) when ?STRING(Cs); Cs =:= eof ->
case Fun(Cs, St, Line, Col, Toks, Any) of
- {more,{Cs0,Ncol,Ntoks,Nline,Nany,Nfun}} ->
- {more,{erl_scan_continuation,Cs0,Ncol,Ntoks,Nline,St,Nany,Nfun}};
+ {more,{Cs0,Nst,Ncol,Ntoks,Nline,Nany,Nfun}} ->
+ {more,{erl_scan_continuation,Cs0,Ncol,Ntoks,Nline,Nst,Nany,Nfun}};
{ok,Toks0,eof,Nline,Ncol} ->
Res = case Toks0 of
[] ->
@@ -348,8 +355,8 @@ tokens1(Cs, St, Line, Col, Toks, Fun, Any) when ?STRING(Cs); Cs =:= eof ->
string1(Cs, St, Line, Col, Toks) ->
case scan1(Cs, St, Line, Col, Toks) of
- {more,{Cs0,Ncol,Ntoks,Nline,Any,Fun}} ->
- case Fun(Cs0++eof, St, Nline, Ncol, Ntoks, Any) of
+ {more,{Cs0,Nst,Ncol,Ntoks,Nline,Any,Fun}} ->
+ case Fun(Cs0++eof, Nst, Nline, Ncol, Ntoks, Any) of
{ok,Toks1,_Rest,Line2,Col2} ->
{ok,lists:reverse(Toks1),location(Line2, Col2)};
{{error,_,_}=Error,_Rest} ->
@@ -363,7 +370,7 @@ string1(Cs, St, Line, Col, Toks) ->
Error
end.
-scan(Cs, St, Line, Col, Toks, _) ->
+scan(Cs, #erl_scan{}=St, Line, Col, Toks, _) ->
scan1(Cs, St, Line, Col, Toks).
scan1([$\s|Cs], St, Line, Col, Toks) when St#erl_scan.ws ->
@@ -374,10 +381,6 @@ scan1([$\n|Cs], St, Line, Col, Toks) when St#erl_scan.ws ->
scan_newline(Cs, St, Line, Col, Toks);
scan1([$\n|Cs], St, Line, Col, Toks) ->
skip_white_space(Cs, St, Line+1, new_column(Col, 1), Toks, 0);
-scan1([C|Cs], St, Line, Col, Toks) when C >= $A, C =< $Z ->
- scan_variable(Cs, St, Line, Col, Toks, [C]);
-scan1([C|Cs], St, Line, Col, Toks) when C >= $a, C =< $z ->
- scan_atom(Cs, St, Line, Col, Toks, [C]);
%% Optimization: some very common punctuation characters:
scan1([$,|Cs], St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, ",", ',', 1);
@@ -397,21 +400,29 @@ scan1([$;|Cs], St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, ";", ';', 1);
scan1([$_=C|Cs], St, Line, Col, Toks) ->
scan_variable(Cs, St, Line, Col, Toks, [C]);
-%% More punctuation characters below.
+scan1([$\%=C|Cs], St, Line, Col, Toks) when St#erl_scan.checks ->
+ scan_check(Cs, St, Line, Col, Toks, [C]);
scan1([$\%|Cs], St, Line, Col, Toks) when not St#erl_scan.comment ->
skip_comment(Cs, St, Line, Col, Toks, 1);
scan1([$\%=C|Cs], St, Line, Col, Toks) ->
scan_comment(Cs, St, Line, Col, Toks, [C]);
+%% More punctuation characters below.
+scan1([C|_], _St, _Line, _Col0, _Toks) when not ?CHAR(C) ->
+ error({not_character,C});
+scan1([C|Cs], St, Line, Col, Toks) when C >= $A, C =< $Z ->
+ scan_variable(Cs, St, Line, Col, Toks, [C]);
+scan1([C|Cs], St, Line, Col, Toks) when C >= $a, C =< $z ->
+ scan_atom(Cs, St, Line, Col, Toks, [C]);
scan1([C|Cs], St, Line, Col, Toks) when ?DIGIT(C) ->
scan_number(Cs, St, Line, Col, Toks, [C], no_underscore);
scan1("..."++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "...", '...', 3);
-scan1(".."=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1(".."=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
scan1(".."++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "..", '..', 2);
-scan1("."=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("."=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
scan1([$.=C|Cs], St, Line, Col, Toks) ->
scan_dot(Cs, St, Line, Col, Toks, [C]);
scan1([$"|Cs], St, Line, Col, Toks) -> %" Emacs
@@ -443,8 +454,8 @@ scan1([C|Cs], St, Line, Col, Toks) when ?WHITE_SPACE(C) ->
%% ?= for the maybe ... else ... end construct
scan1("?="++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "?=", '?=', 2);
-scan1("?"=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("?"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
%% << <- <=
scan1("<<"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "<<", '<<', 2);
@@ -452,62 +463,62 @@ scan1("<-"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "<-", '<-', 2);
scan1("<="++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "<=", '<=', 2);
-scan1("<"=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("<"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
%% >> >=
scan1(">>"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, ">>", '>>', 2);
scan1(">="++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, ">=", '>=', 2);
-scan1(">"=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1(">"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
%% -> --
scan1("->"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "->", '->', 2);
scan1("--"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "--", '--', 2);
-scan1("-"=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("-"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
%% ++
scan1("++"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "++", '++', 2);
-scan1("+"=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("+"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
%% =:= =/= =< == =>
scan1("=:="++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "=:=", '=:=', 3);
-scan1("=:"=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("=:"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
scan1("=/="++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "=/=", '=/=', 3);
-scan1("=/"=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("=/"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
scan1("=<"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "=<", '=<', 2);
scan1("=>"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "=>", '=>', 2);
scan1("=="++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "==", '==', 2);
-scan1("="=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("="=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
%% /=
scan1("/="++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "/=", '/=', 2);
-scan1("/"=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("/"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
%% ||
scan1("||"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "||", '||', 2);
-scan1("|"=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("|"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
%% :=
scan1(":="++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, ":=", ':=', 2);
%% :: for typed records
scan1("::"++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "::", '::', 2);
-scan1(":"=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1(":"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
%% Optimization: punctuation characters less than 127:
scan1([$=|Cs], St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "=", '=', 1);
@@ -552,44 +563,54 @@ scan1([C|Cs], St, Line, Col, Toks) when ?UNI255(C) ->
scan1([C|Cs], _St, Line, Col, _Toks) when ?CHAR(C) ->
Ncol = incr_column(Col, 1),
scan_error({illegal,character}, Line, Col, Line, Ncol, Cs);
-scan1([]=Cs, _St, Line, Col, Toks) ->
- {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1([]=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
scan1(eof=Cs, _St, Line, Col, Toks) ->
{ok,Toks,Cs,Line,Col}.
+scan_atom_fun(Cs, #erl_scan{}=St, Line, Col, Toks, Ncs) ->
+ scan_atom(Cs, St, Line, Col, Toks, Ncs).
+
scan_atom(Cs0, St, Line, Col, Toks, Ncs0) ->
case scan_name(Cs0, Ncs0) of
{more,Ncs} ->
- {more,{[],Col,Toks,Line,Ncs,fun scan_atom/6}};
+ {more,{[],St,Col,Toks,Line,Ncs,fun scan_atom_fun/6}};
{Wcs,Cs} ->
- case catch list_to_atom(Wcs) of
- Name when is_atom(Name) ->
+ try list_to_atom(Wcs) of
+ Name ->
case (St#erl_scan.resword_fun)(Name) of
true ->
tok2(Cs, St, Line, Col, Toks, Wcs, Name);
false ->
tok3(Cs, St, Line, Col, Toks, atom, Wcs, Name)
- end;
- _Error ->
+ end
+ catch
+ _:_ ->
Ncol = incr_column(Col, length(Wcs)),
scan_error({illegal,atom}, Line, Col, Line, Ncol, Cs)
end
end.
+scan_variable_fun(Cs, #erl_scan{}=St, Line, Col, Toks, Ncs) ->
+ scan_variable(Cs, St, Line, Col, Toks, Ncs).
+
scan_variable(Cs0, St, Line, Col, Toks, Ncs0) ->
case scan_name(Cs0, Ncs0) of
{more,Ncs} ->
- {more,{[],Col,Toks,Line,Ncs,fun scan_variable/6}};
+ {more,{[],St,Col,Toks,Line,Ncs,fun scan_variable_fun/6}};
{Wcs,Cs} ->
- case catch list_to_atom(Wcs) of
- Name when is_atom(Name) ->
- tok3(Cs, St, Line, Col, Toks, var, Wcs, Name);
- _Error ->
+ try list_to_atom(Wcs) of
+ Name ->
+ tok3(Cs, St, Line, Col, Toks, var, Wcs, Name)
+ catch
+ _:_ ->
Ncol = incr_column(Col, length(Wcs)),
scan_error({illegal,var}, Line, Col, Line, Ncol, Cs)
end
end.
+scan_name([C|_]=Cs, Ncs) when not ?CHAR(C) ->
+ {lists:reverse(Ncs),Cs};
scan_name([C|Cs], Ncs) when C >= $a, C =< $z ->
scan_name(Cs, [C|Ncs]);
scan_name([C|Cs], Ncs) when C >= $A, C =< $Z ->
@@ -616,6 +637,9 @@ scan_name(Cs, Ncs) ->
false -> []
end).
+scan_dot([C|_]=Cs, St, Line, Col, Toks, Ncs)
+ when St#erl_scan.in_check, C =/= $. ->
+ tok2(Cs, St#erl_scan{in_check=false}, Line, Col, Toks, Ncs, '.', 1);
scan_dot([$%|_]=Cs, St, Line, Col, Toks, Ncs) ->
Anno = anno(Line, Col, St, ?STR(dot, St, Ncs)),
{ok,[{dot,Anno}|Toks],Cs,Line,incr_column(Col, 1)};
@@ -658,25 +682,36 @@ scan_newline([$\r|Cs], St, Line, Col, Toks) ->
newline_end(Cs, St, Line, Col, Toks, 2, "\n\r");
scan_newline([$\f|Cs], St, Line, Col, Toks) ->
newline_end(Cs, St, Line, Col, Toks, 2, "\n\f");
-scan_newline([], _St, Line, Col, Toks) ->
- {more,{[$\n],Col,Toks,Line,[],fun scan/6}};
+scan_newline([], St, Line, Col, Toks) ->
+ {more,{[$\n],St,Col,Toks,Line,[],fun scan/6}};
scan_newline(Cs, St, Line, Col, Toks) ->
scan_nl_white_space(Cs, St, Line, Col, Toks, "\n").
+scan_nl_spcs_fun(Cs, #erl_scan{}=St, Line, Col, Toks, N)
+ when is_integer(N) ->
+ scan_nl_spcs(Cs, St, Line, Col, Toks, N).
+
scan_nl_spcs([$\s|Cs], St, Line, Col, Toks, N) when N < 17 ->
scan_nl_spcs(Cs, St, Line, Col, Toks, N+1);
-scan_nl_spcs([]=Cs, _St, Line, Col, Toks, N) ->
- {more,{Cs,Col,Toks,Line,N,fun scan_nl_spcs/6}};
+scan_nl_spcs([]=Cs, St, Line, Col, Toks, N) ->
+ {more,{Cs,St,Col,Toks,Line,N,fun scan_nl_spcs_fun/6}};
scan_nl_spcs(Cs, St, Line, Col, Toks, N) ->
newline_end(Cs, St, Line, Col, Toks, N, nl_spcs(N)).
+scan_nl_tabs_fun(Cs, #erl_scan{}=St, Line, Col, Toks, N)
+ when is_integer(N) ->
+ scan_nl_tabs(Cs, St, Line, Col, Toks, N).
+
scan_nl_tabs([$\t|Cs], St, Line, Col, Toks, N) when N < 11 ->
scan_nl_tabs(Cs, St, Line, Col, Toks, N+1);
-scan_nl_tabs([]=Cs, _St, Line, Col, Toks, N) ->
- {more,{Cs,Col,Toks,Line,N,fun scan_nl_tabs/6}};
+scan_nl_tabs([]=Cs, St, Line, Col, Toks, N) ->
+ {more,{Cs,St,Col,Toks,Line,N,fun scan_nl_tabs_fun/6}};
scan_nl_tabs(Cs, St, Line, Col, Toks, N) ->
newline_end(Cs, St, Line, Col, Toks, N, nl_tabs(N)).
+scan_nl_white_space_fun(Cs, #erl_scan{}=St, Line, Col, Toks, Ncs) ->
+ scan_nl_white_space(Cs, St, Line, Col, Toks, Ncs).
+
%% Note: returning {more,Cont} is meaningless here; one could just as
%% well return several tokens. But since tokens() scans up to a full
%% stop anyway, nothing is gained by not collecting all white spaces.
@@ -689,10 +724,11 @@ scan_nl_white_space([$\n|Cs], St, Line, Col, Toks, Ncs0) ->
Anno = anno(Line, Col, St, ?STR(white_space, St, Ncs)),
Token = {white_space,Anno,Ncs},
scan_newline(Cs, St, Line+1, new_column(Col, length(Ncs)), [Token|Toks]);
-scan_nl_white_space([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) ->
+scan_nl_white_space([C|Cs], St, Line, Col, Toks, Ncs)
+ when ?WHITE_SPACE(C) ->
scan_nl_white_space(Cs, St, Line, Col, Toks, [C|Ncs]);
-scan_nl_white_space([]=Cs, _St, Line, Col, Toks, Ncs) ->
- {more,{Cs,Col,Toks,Line,Ncs,fun scan_nl_white_space/6}};
+scan_nl_white_space([]=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_nl_white_space_fun/6}};
scan_nl_white_space(Cs, #erl_scan{has_fun = false}=St, Line, no_col=Col,
Toks, Ncs) ->
Anno = anno(Line),
@@ -706,40 +742,54 @@ scan_nl_white_space(Cs, St, Line, Col, Toks, Ncs0) ->
newline_end(Cs, #erl_scan{has_fun = false}=St, Line, no_col=Col,
Toks, _N, Ncs) ->
scan1(Cs, St, Line+1, Col, [{white_space,anno(Line),Ncs}|Toks]);
-newline_end(Cs, St, Line, Col, Toks, N, Ncs) ->
+newline_end(Cs, #erl_scan{}=St, Line, Col, Toks, N, Ncs) ->
Anno = anno(Line, Col, St, ?STR(white_space, St, Ncs)),
scan1(Cs, St, Line+1, new_column(Col, N), [{white_space,Anno,Ncs}|Toks]).
+scan_spcs_fun(Cs, #erl_scan{}=St, Line, Col, Toks, N)
+ when is_integer(N), N >= 1 ->
+ scan_spcs(Cs, St, Line, Col, Toks, N).
+
scan_spcs([$\s|Cs], St, Line, Col, Toks, N) when N < 16 ->
scan_spcs(Cs, St, Line, Col, Toks, N+1);
-scan_spcs([]=Cs, _St, Line, Col, Toks, N) ->
- {more,{Cs,Col,Toks,Line,N,fun scan_spcs/6}};
+scan_spcs([]=Cs, St, Line, Col, Toks, N) ->
+ {more,{Cs,St,Col,Toks,Line,N,fun scan_spcs_fun/6}};
scan_spcs(Cs, St, Line, Col, Toks, N) ->
white_space_end(Cs, St, Line, Col, Toks, N, spcs(N)).
+scan_tabs_fun(Cs, #erl_scan{}=St, Line, Col, Toks, N)
+ when is_integer(N), N >= 1 ->
+ scan_tabs(Cs, St, Line, Col, Toks, N).
+
scan_tabs([$\t|Cs], St, Line, Col, Toks, N) when N < 10 ->
scan_tabs(Cs, St, Line, Col, Toks, N+1);
-scan_tabs([]=Cs, _St, Line, Col, Toks, N) ->
- {more,{Cs,Col,Toks,Line,N,fun scan_tabs/6}};
+scan_tabs([]=Cs, St, Line, Col, Toks, N) ->
+ {more,{Cs,St,Col,Toks,Line,N,fun scan_tabs_fun/6}};
scan_tabs(Cs, St, Line, Col, Toks, N) ->
white_space_end(Cs, St, Line, Col, Toks, N, tabs(N)).
+skip_white_space_fun(Cs, #erl_scan{}=St, Line, Col, Toks, N) ->
+ skip_white_space(Cs, St, Line, Col, Toks, N).
+
skip_white_space([$\n|Cs], St, Line, Col, Toks, _N) ->
skip_white_space(Cs, St, Line+1, new_column(Col, 1), Toks, 0);
skip_white_space([C|Cs], St, Line, Col, Toks, N) when ?WHITE_SPACE(C) ->
skip_white_space(Cs, St, Line, Col, Toks, N+1);
-skip_white_space([]=Cs, _St, Line, Col, Toks, N) ->
- {more,{Cs,Col,Toks,Line,N,fun skip_white_space/6}};
+skip_white_space([]=Cs, St, Line, Col, Toks, N) ->
+ {more,{Cs,St,Col,Toks,Line,N,fun skip_white_space_fun/6}};
skip_white_space(Cs, St, Line, Col, Toks, N) ->
scan1(Cs, St, Line, incr_column(Col, N), Toks).
+scan_white_space_fun(Cs, #erl_scan{}=St, Line, Col, Toks, Ncs) ->
+ scan_white_space(Cs, St, Line, Col, Toks, Ncs).
+
%% Maybe \t and \s should break the loop.
scan_white_space([$\n|_]=Cs, St, Line, Col, Toks, Ncs) ->
white_space_end(Cs, St, Line, Col, Toks, length(Ncs), lists:reverse(Ncs));
scan_white_space([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) ->
scan_white_space(Cs, St, Line, Col, Toks, [C|Ncs]);
-scan_white_space([]=Cs, _St, Line, Col, Toks, Ncs) ->
- {more,{Cs,Col,Toks,Line,Ncs,fun scan_white_space/6}};
+scan_white_space([]=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_white_space_fun/6}};
scan_white_space(Cs, St, Line, Col, Toks, Ncs) ->
white_space_end(Cs, St, Line, Col, Toks, length(Ncs), lists:reverse(Ncs)).
@@ -751,7 +801,7 @@ white_space_end(Cs, St, Line, Col, Toks, N, Ncs) ->
scan_char([$\\|Cs]=Cs0, St, Line, Col, Toks) ->
case scan_escape(Cs, incr_column(Col, 2)) of
more ->
- {more,{[$$|Cs0],Col,Toks,Line,[],fun scan/6}};
+ {more,{[$$|Cs0],St,Col,Toks,Line,[],fun scan/6}};
{error,Ncs,Error,Ncol} ->
scan_error(Error, Line, Col, Line, Ncol, Ncs);
{eof,Ncol} ->
@@ -773,16 +823,16 @@ scan_char([C|Cs], St, Line, Col, Toks) when ?UNICODE(C) ->
scan1(Cs, St, Line, incr_column(Col, 2), [{char,Anno,C}|Toks]);
scan_char([C|_Cs], _St, Line, Col, _Toks) when ?CHAR(C) ->
scan_error({illegal,character}, Line, Col, Line, incr_column(Col, 1), eof);
-scan_char([], _St, Line, Col, Toks) ->
- {more,{[$$],Col,Toks,Line,[],fun scan/6}};
+scan_char([], St, Line, Col, Toks) ->
+ {more,{[$$],St,Col,Toks,Line,[],fun scan/6}};
scan_char(eof, _St, Line, Col, _Toks) ->
scan_error(char, Line, Col, Line, incr_column(Col, 1), eof).
-scan_string(Cs, St, Line, Col, Toks, {Wcs,Str,Line0,Col0}) ->
+scan_string(Cs, #erl_scan{}=St, Line, Col, Toks, {Wcs,Str,Line0,Col0}) ->
case scan_string0(Cs, St, Line, Col, $\", Str, Wcs) of %"
{more,Ncs,Nline,Ncol,Nstr,Nwcs} ->
State = {Nwcs,Nstr,Line0,Col0},
- {more,{Ncs,Ncol,Toks,Nline,State,fun scan_string/6}};
+ {more,{Ncs,St,Ncol,Toks,Nline,State,fun scan_string/6}};
{char_error,Ncs,Error,Nline,Ncol,EndCol} ->
scan_error(Error, Nline, Ncol, Nline, EndCol, Ncs);
{error,Nline,Ncol,Nwcs,Ncs} ->
@@ -793,22 +843,23 @@ scan_string(Cs, St, Line, Col, Toks, {Wcs,Str,Line0,Col0}) ->
scan1(Ncs, St, Nline, Ncol, [{string,Anno,Nwcs}|Toks])
end.
-scan_qatom(Cs, St, Line, Col, Toks, {Wcs,Str,Line0,Col0}) ->
+scan_qatom(Cs, #erl_scan{}=St, Line, Col, Toks, {Wcs,Str,Line0,Col0}) ->
case scan_string0(Cs, St, Line, Col, $\', Str, Wcs) of %'
{more,Ncs,Nline,Ncol,Nstr,Nwcs} ->
State = {Nwcs,Nstr,Line0,Col0},
- {more,{Ncs,Ncol,Toks,Nline,State,fun scan_qatom/6}};
+ {more,{Ncs,St,Ncol,Toks,Nline,State,fun scan_qatom/6}};
{char_error,Ncs,Error,Nline,Ncol,EndCol} ->
scan_error(Error, Nline, Ncol, Nline, EndCol, Ncs);
{error,Nline,Ncol,Nwcs,Ncs} ->
Estr = string:slice(Nwcs, 0, 16), % Expanded escape chars.
scan_error({string,$\',Estr}, Line0, Col0, Nline, Ncol, Ncs); %'
- {Ncs,Nline,Ncol,Nstr,Nwcs} ->
- case catch list_to_atom(Nwcs) of
+ {Ncs,Nline,Ncol,Nstr,Nwcs} ->
+ try list_to_atom(Nwcs) of
A when is_atom(A) ->
Anno = anno(Line0, Col0, St, ?STR(atom, St, Nstr)),
- scan1(Ncs, St, Nline, Ncol, [{atom,Anno,A}|Toks]);
- _ ->
+ scan1(Ncs, St, Nline, Ncol, [{atom,Anno,A}|Toks])
+ catch
+ _:_ ->
scan_error({illegal,atom}, Line0, Col0, Nline, Ncol, Ncs)
end
end.
@@ -884,10 +935,11 @@ scan_string1([]=Cs, Line, Col, _Q, Str, Wcs) ->
scan_string1(eof, Line, Col, _Q, _Str, Wcs) ->
{error,Line,Col,lists:reverse(Wcs),eof}.
--define(OCT(C), C >= $0, C =< $7).
--define(HEX(C), C >= $0 andalso C =< $9 orelse
- C >= $A andalso C =< $F orelse
- C >= $a andalso C =< $f).
+-define(OCT(C), (is_integer(C) andalso $0 =< C andalso C =< $7)).
+-define(HEX(C), (is_integer(C) andalso
+ (C >= $0 andalso C =< $9 orelse
+ C >= $A andalso C =< $F orelse
+ C >= $a andalso C =< $f))).
%% \<1-3> octal digits
scan_escape([O1,O2,O3|Cs], Col) when ?OCT(O1), ?OCT(O2), ?OCT(O3) ->
@@ -917,12 +969,14 @@ scan_escape([$x,H1], _Col) when ?HEX(H1) ->
more;
scan_escape([$x|Cs], Col) ->
{error,Cs,{illegal,character},incr_column(Col, 1)};
-%% \^X -> CTL-X
-scan_escape([$^=C0,$\n=C|Cs], Col) ->
- {nl,C,[C0,C],Cs,new_column(Col, 1)};
+%% \^X -> Control-X
scan_escape([$^=C0,C|Cs], Col) when ?CHAR(C) ->
- Val = C band 31,
- {Val,[C0,C],Cs,incr_column(Col, 2)};
+ case caret_char_code(C) of
+ error ->
+ {error,[C|Cs],{illegal,character},incr_column(Col, 1)};
+ Code ->
+ {Code,[C0,C],Cs,incr_column(Col, 2)}
+ end;
scan_escape([$^], _Col) ->
more;
scan_escape([$^|eof], Col) ->
@@ -939,26 +993,31 @@ scan_escape([], _Col) ->
scan_escape(eof, Col) ->
{eof,Col}.
-scan_hex([C|Cs], no_col=Col, Wcs) when ?HEX(C) ->
- scan_hex(Cs, Col, [C|Wcs]);
scan_hex([C|Cs], Col, Wcs) when ?HEX(C) ->
- scan_hex(Cs, Col+1, [C|Wcs]);
+ scan_hex(Cs, incr_column(Col, 1), [C|Wcs]);
scan_hex(Cs, Col, Wcs) ->
- scan_esc_end(Cs, Col, Wcs, 16, "x{").
+ scan_hex_end(Cs, Col, Wcs, "x{").
-scan_esc_end([$}|Cs], Col, Wcs0, B, Str0) ->
+scan_hex_end([$}|Cs], Col, [], _Str) ->
+ %% Empty escape sequence.
+ {error,Cs,{illegal,character},incr_column(Col, 1)};
+scan_hex_end([$}|Cs], Col, Wcs0, Str0) ->
Wcs = lists:reverse(Wcs0),
- case catch erlang:list_to_integer(Wcs, B) of
+ try list_to_integer(Wcs, 16) of
Val when ?UNICODE(Val) ->
{Val,Str0++Wcs++[$}],Cs,incr_column(Col, 1)};
- _ ->
+ _Val ->
+ {error,Cs,{illegal,character},incr_column(Col, 1)}
+ catch
+ error:system_limit ->
+ %% Extremely unlikely to occur in practice.
{error,Cs,{illegal,character},incr_column(Col, 1)}
end;
-scan_esc_end([], _Col, _Wcs, _B, _Str0) ->
+scan_hex_end([], _Col, _Wcs, _Str0) ->
more;
-scan_esc_end(eof, Col, _Wcs, _B, _Str0) ->
+scan_hex_end(eof, Col, _Wcs, _Str0) ->
{eof,Col};
-scan_esc_end(Cs, Col, _Wcs, _B, _Str0) ->
+scan_hex_end(Cs, Col, _Wcs, _Str0) ->
{error,Cs,{illegal,character},Col}.
escape_char($n) -> $\n; % \n = LF
@@ -972,7 +1031,11 @@ escape_char($s) -> $\s; % \s = SPC
escape_char($d) -> $\d; % \d = DEL
escape_char(C) -> C.
-scan_number(Cs, St, Line, Col, Toks, {Ncs, Us}) ->
+caret_char_code($?) -> 16#7f;
+caret_char_code(C) when $@ =< C, C =< $_; $a =< C, C =< $z -> C band 16#1f;
+caret_char_code(_) -> error.
+
+scan_number(Cs, #erl_scan{}=St, Line, Col, Toks, {Ncs, Us}) ->
scan_number(Cs, St, Line, Col, Toks, Ncs, Us).
scan_number([C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
@@ -980,30 +1043,36 @@ scan_number([C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
scan_number([$_,Next|Cs], St, Line, Col, Toks, [Prev|_]=Ncs, _Us) when
?DIGIT(Next) andalso ?DIGIT(Prev) ->
scan_number(Cs, St, Line, Col, Toks, [Next,$_|Ncs], with_underscore);
-scan_number([$_]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
- {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_number/6}};
+scan_number([$_]=Cs, St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,St,Col,Toks,Line,{Ncs,Us},fun scan_number/6}};
scan_number([$.,C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
scan_fraction(Cs, St, Line, Col, Toks, [C,$.|Ncs], Us);
-scan_number([$.]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
- {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_number/6}};
+scan_number([$.]=Cs, St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,St,Col,Toks,Line,{Ncs,Us},fun scan_number/6}};
scan_number([$#|Cs]=Cs0, St, Line, Col, Toks, Ncs0, Us) ->
Ncs = lists:reverse(Ncs0),
- case catch list_to_integer(remove_digit_separators(Ncs, Us)) of
- B when B >= 2, B =< 1+$Z-$A+10 ->
+ try list_to_integer(remove_digit_separators(Ncs, Us)) of
+ B when is_integer(B), 2 =< B, B =< 1+$Z-$A+10 ->
Bcs = Ncs++[$#],
scan_based_int(Cs, St, Line, Col, Toks, B, [], Bcs, no_underscore);
- B ->
+ B when is_integer(B) ->
Len = length(Ncs),
scan_error({base,B}, Line, Col, Line, incr_column(Col, Len), Cs0)
+ catch
+ error:system_limit ->
+ %% Extremely unlikely to occur in practice.
+ scan_error({illegal,base}, Line, Col, Line, Col, Cs0)
end;
-scan_number([]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
- {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_number/6}};
+scan_number([]=Cs, St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,St,Col,Toks,Line,{Ncs,Us},fun scan_number/6}};
scan_number(Cs, St, Line, Col, Toks, Ncs0, Us) ->
Ncs = lists:reverse(Ncs0),
- case catch list_to_integer(remove_digit_separators(Ncs, Us)) of
- N when is_integer(N) ->
- tok3(Cs, St, Line, Col, Toks, integer, Ncs, N);
- _ ->
+ try list_to_integer(remove_digit_separators(Ncs, Us), 10) of
+ N ->
+ tok3(Cs, St, Line, Col, Toks, integer, Ncs, N)
+ catch
+ error:system_limit ->
+ %% Extremely unlikely to occur in practice.
Ncol = incr_column(Col, length(Ncs)),
scan_error({illegal,integer}, Line, Col, Line, Ncol, Cs)
end.
@@ -1014,11 +1083,14 @@ remove_digit_separators(Number, with_underscore) ->
[C || C <- Number, C =/= $_].
-define(BASED_DIGIT(C, B),
- ((?DIGIT(C) andalso C < $0 + B)
- orelse (C >= $A andalso B > 10 andalso C < $A + B - 10)
- orelse (C >= $a andalso B > 10 andalso C < $a + B - 10))).
-
-scan_based_int(Cs, St, Line, Col, Toks, {B,NCs,BCs,Us}) ->
+ (is_integer(C)
+ andalso
+ ((?DIGIT(C) andalso C < $0 + B)
+ orelse (C >= $A andalso B > 10 andalso C < $A + B - 10)
+ orelse (C >= $a andalso B > 10 andalso C < $a + B - 10)))).
+
+scan_based_int(Cs, #erl_scan{}=St, Line, Col, Toks, {B,NCs,BCs,Us})
+ when is_integer(B), 2 =< B, B =< 1+$Z-$A+10 ->
scan_based_int(Cs, St, Line, Col, Toks, B, NCs, BCs, Us).
scan_based_int([C|Cs], St, Line, Col, Toks, B, Ncs, Bcs, Us) when
@@ -1028,22 +1100,29 @@ scan_based_int([$_,Next|Cs], St, Line, Col, Toks, B, [Prev|_]=Ncs, Bcs, _Us)
when ?BASED_DIGIT(Next, B) andalso ?BASED_DIGIT(Prev, B) ->
scan_based_int(Cs, St, Line, Col, Toks, B, [Next,$_|Ncs], Bcs,
with_underscore);
-scan_based_int([$_]=Cs, _St, Line, Col, Toks, B, NCs, BCs, Us) ->
- {more,{Cs,Col,Toks,Line,{B,NCs,BCs,Us},fun scan_based_int/6}};
-scan_based_int([]=Cs, _St, Line, Col, Toks, B, NCs, BCs, Us) ->
- {more,{Cs,Col,Toks,Line,{B,NCs,BCs,Us},fun scan_based_int/6}};
-scan_based_int(Cs, St, Line, Col, Toks, B, Ncs0, Bcs, Us) ->
+scan_based_int([$_]=Cs, St, Line, Col, Toks, B, NCs, BCs, Us) ->
+ {more,{Cs,St,Col,Toks,Line,{B,NCs,BCs,Us},fun scan_based_int/6}};
+scan_based_int([]=Cs, St, Line, Col, Toks, B, NCs, BCs, Us) ->
+ {more,{Cs,St,Col,Toks,Line,{B,NCs,BCs,Us},fun scan_based_int/6}};
+scan_based_int(Cs, _St, Line, Col, _Toks, _B, [], Bcs, _Us) ->
+ %% No actual digits following the base.
+ Len = length(Bcs),
+ Ncol = incr_column(Col, Len),
+ scan_error({illegal,integer}, Line, Col, Line, Ncol, Cs);
+scan_based_int(Cs, St, Line, Col, Toks, B, Ncs0, [_|_]=Bcs, Us) ->
Ncs = lists:reverse(Ncs0),
- case catch erlang:list_to_integer(remove_digit_separators(Ncs, Us), B) of
- N when is_integer(N) ->
- tok3(Cs, St, Line, Col, Toks, integer, Bcs++Ncs, N);
- _ ->
+ try list_to_integer(remove_digit_separators(Ncs, Us), B) of
+ N ->
+ tok3(Cs, St, Line, Col, Toks, integer, Bcs++Ncs, N)
+ catch
+ error:system_limit ->
+ %% Extremely unlikely to occur in practice.
Len = length(Bcs)+length(Ncs),
Ncol = incr_column(Col, Len),
scan_error({illegal,integer}, Line, Col, Line, Ncol, Cs)
end.
-scan_fraction(Cs, St, Line, Col, Toks, {Ncs,Us}) ->
+scan_fraction(Cs, #erl_scan{}=St, Line, Col, Toks, {Ncs,Us}) ->
scan_fraction(Cs, St, Line, Col, Toks, Ncs, Us).
scan_fraction([C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
@@ -1051,27 +1130,27 @@ scan_fraction([C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
scan_fraction([$_,Next|Cs], St, Line, Col, Toks, [Prev|_]=Ncs, _Us) when
?DIGIT(Next) andalso ?DIGIT(Prev) ->
scan_fraction(Cs, St, Line, Col, Toks, [Next,$_|Ncs], with_underscore);
-scan_fraction([$_]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
- {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_fraction/6}};
+scan_fraction([$_]=Cs, St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,St,Col,Toks,Line,{Ncs,Us},fun scan_fraction/6}};
scan_fraction([E|Cs], St, Line, Col, Toks, Ncs, Us) when E =:= $e; E =:= $E ->
scan_exponent_sign(Cs, St, Line, Col, Toks, [E|Ncs], Us);
-scan_fraction([]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
- {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_fraction/6}};
+scan_fraction([]=Cs, St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,St,Col,Toks,Line,{Ncs,Us},fun scan_fraction/6}};
scan_fraction(Cs, St, Line, Col, Toks, Ncs, Us) ->
float_end(Cs, St, Line, Col, Toks, Ncs, Us).
-scan_exponent_sign(Cs, St, Line, Col, Toks, {Ncs, Us}) ->
+scan_exponent_sign(Cs, #erl_scan{}=St, Line, Col, Toks, {Ncs, Us}) ->
scan_exponent_sign(Cs, St, Line, Col, Toks, Ncs, Us).
scan_exponent_sign([C|Cs], St, Line, Col, Toks, Ncs, Us) when
C =:= $+; C =:= $- ->
scan_exponent(Cs, St, Line, Col, Toks, [C|Ncs], Us);
-scan_exponent_sign([]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
- {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_exponent_sign/6}};
+scan_exponent_sign([]=Cs, St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,St,Col,Toks,Line,{Ncs,Us},fun scan_exponent_sign/6}};
scan_exponent_sign(Cs, St, Line, Col, Toks, Ncs, Us) ->
scan_exponent(Cs, St, Line, Col, Toks, Ncs, Us).
-scan_exponent(Cs, St, Line, Col, Toks, {Ncs, Us}) ->
+scan_exponent(Cs, #erl_scan{}=St, Line, Col, Toks, {Ncs, Us}) ->
scan_exponent(Cs, St, Line, Col, Toks, Ncs, Us).
scan_exponent([C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
@@ -1079,23 +1158,27 @@ scan_exponent([C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
scan_exponent([$_,Next|Cs], St, Line, Col, Toks, [Prev|_]=Ncs, _) when
?DIGIT(Next) andalso ?DIGIT(Prev) ->
scan_exponent(Cs, St, Line, Col, Toks, [Next,$_|Ncs], with_underscore);
-scan_exponent([$_]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
- {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_exponent/6}};
-scan_exponent([]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
- {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_exponent/6}};
+scan_exponent([$_]=Cs, St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,St,Col,Toks,Line,{Ncs,Us},fun scan_exponent/6}};
+scan_exponent([]=Cs, St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,St,Col,Toks,Line,{Ncs,Us},fun scan_exponent/6}};
scan_exponent(Cs, St, Line, Col, Toks, Ncs, Us) ->
float_end(Cs, St, Line, Col, Toks, Ncs, Us).
float_end(Cs, St, Line, Col, Toks, Ncs0, Us) ->
Ncs = lists:reverse(Ncs0),
- case catch list_to_float(remove_digit_separators(Ncs, Us)) of
- F when is_float(F) ->
- tok3(Cs, St, Line, Col, Toks, float, Ncs, F);
- _ ->
+ try list_to_float(remove_digit_separators(Ncs, Us)) of
+ F ->
+ tok3(Cs, St, Line, Col, Toks, float, Ncs, F)
+ catch
+ _:_ ->
Ncol = incr_column(Col, length(Ncs)),
scan_error({illegal,float}, Line, Col, Line, Ncol, Cs)
end.
+skip_comment_fun(Cs, #erl_scan{}=St, Line, Col, Toks, N) ->
+ skip_comment(Cs, St, Line, Col, Toks, N).
+
skip_comment([C|Cs], St, Line, Col, Toks, N) when C =/= $\n, ?CHAR(C) ->
case ?UNICODE(C) of
true ->
@@ -1104,12 +1187,16 @@ skip_comment([C|Cs], St, Line, Col, Toks, N) when C =/= $\n, ?CHAR(C) ->
Ncol = incr_column(Col, N+1),
scan_error({illegal,character}, Line, Col, Line, Ncol, Cs)
end;
-skip_comment([]=Cs, _St, Line, Col, Toks, N) ->
- {more,{Cs,Col,Toks,Line,N,fun skip_comment/6}};
+skip_comment([]=Cs, St, Line, Col, Toks, N) ->
+ {more,{Cs,St,Col,Toks,Line,N,fun skip_comment_fun/6}};
skip_comment(Cs, St, Line, Col, Toks, N) ->
scan1(Cs, St, Line, incr_column(Col, N), Toks).
-scan_comment([C|Cs], St, Line, Col, Toks, Ncs) when C =/= $\n, ?CHAR(C) ->
+scan_comment_fun(Cs, #erl_scan{}=St, Line, Col, Toks, Ncs) ->
+ scan_comment(Cs, St, Line, Col, Toks, Ncs).
+
+scan_comment([C|Cs], St, Line, Col, Toks, Ncs)
+ when C =/= $\n, ?CHAR(C) ->
case ?UNICODE(C) of
true ->
scan_comment(Cs, St, Line, Col, Toks, [C|Ncs]);
@@ -1117,34 +1204,76 @@ scan_comment([C|Cs], St, Line, Col, Toks, Ncs) when C =/= $\n, ?CHAR(C) ->
Ncol = incr_column(Col, length(Ncs)+1),
scan_error({illegal,character}, Line, Col, Line, Ncol, Cs)
end;
-scan_comment([]=Cs, _St, Line, Col, Toks, Ncs) ->
- {more,{Cs,Col,Toks,Line,Ncs,fun scan_comment/6}};
+scan_comment([]=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_comment_fun/6}};
scan_comment(Cs, St, Line, Col, Toks, Ncs0) ->
Ncs = lists:reverse(Ncs0),
tok3(Cs, St, Line, Col, Toks, comment, Ncs, Ncs).
+scan_check("%%ssa%" ++ Cs, St, Line, Col, Toks, _Ncs) ->
+ scan_check1(Cs, St, Line, Toks, Col, 7);
+scan_check("%%ssa"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check("%%ss"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check("%%s"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check("%%"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check("%"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check("%ssa%" ++ Cs, St, Line, Col, Toks, _Ncs) ->
+ scan_check1(Cs, St, Line, Toks, Col, 6);
+scan_check("%ssa"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check("%ss"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check("%s"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check("ssa%" ++ Cs, St, Line, Col, Toks, _Ncs) ->
+ scan_check1(Cs, St, Line, Toks, Col, 5);
+scan_check("ssa"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check("ss"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check("s"=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check([]=Cs, St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,St,Col,Toks,Line,Ncs,fun scan_check/6}};
+scan_check(Cs, St=#erl_scan{comment=true}, Line, Col, Toks, Ncs) ->
+ scan_comment(Cs, St, Line, Col, Toks, Ncs);
+scan_check(Cs, St, Line, Col, Toks, _Ncs) ->
+ skip_comment(Cs, St, Line, Col, Toks, 1).
+
+scan_check1(Cs, St=#erl_scan{in_check=true}, Line, Toks, Col, NoofCols) ->
+ %% Skip as we are already in the check mode
+ scan1(Cs, St, Line, incr_column(Col, NoofCols), Toks);
+scan_check1(Cs, St, Line, Toks, Col, NoofCols) ->
+ tok2(Cs, St#erl_scan{in_check=true}, Line,
+ Col, Toks, "%ssa%", '%ssa%', NoofCols).
+
tok2(Cs, #erl_scan{has_fun = false}=St, Line, no_col=Col, Toks, _Wcs, P) ->
scan1(Cs, St, Line, Col, [{P,anno(Line)}|Toks]);
-tok2(Cs, St, Line, Col, Toks, Wcs, P) ->
+tok2(Cs, #erl_scan{}=St, Line, Col, Toks, Wcs, P) ->
Anno = anno(Line, Col, St, ?STR(P, St, Wcs)),
scan1(Cs, St, Line, incr_column(Col, length(Wcs)), [{P,Anno}|Toks]).
tok2(Cs, #erl_scan{has_fun = false}=St, Line, no_col=Col, Toks, _Wcs, P, _N) ->
scan1(Cs, St, Line, Col, [{P,anno(Line)}|Toks]);
-tok2(Cs, St, Line, Col, Toks, Wcs, P, N) ->
+tok2(Cs, #erl_scan{}=St, Line, Col, Toks, Wcs, P, N) ->
Anno = anno(Line, Col, St, ?STR(P,St,Wcs)),
scan1(Cs, St, Line, incr_column(Col, N), [{P,Anno}|Toks]).
tok3(Cs, #erl_scan{has_fun = false}=St, Line, no_col=Col, Toks, Item, _S, Sym) ->
scan1(Cs, St, Line, Col, [{Item,anno(Line),Sym}|Toks]);
-tok3(Cs, St, Line, Col, Toks, Item, String, Sym) ->
+tok3(Cs, #erl_scan{}=St, Line, Col, Toks, Item, String, Sym) ->
Token = {Item,anno(Line, Col, St, ?STR(Item, St, String)),Sym},
scan1(Cs, St, Line, incr_column(Col, length(String)), [Token|Toks]).
tok3(Cs, #erl_scan{has_fun = false}=St, Line, no_col=Col, Toks, Item,
_String, Sym, _Length) ->
scan1(Cs, St, Line, Col, [{Item,anno(Line),Sym}|Toks]);
-tok3(Cs, St, Line, Col, Toks, Item, String, Sym, Length) ->
+tok3(Cs, #erl_scan{}=St, Line, Col, Toks, Item, String, Sym, Length) ->
Token = {Item,anno(Line, Col, St, ?STR(Item, St, String)),Sym},
scan1(Cs, St, Line, incr_column(Col, Length), [Token|Toks]).
diff --git a/lib/stdlib/src/erl_stdlib_errors.erl b/lib/stdlib/src/erl_stdlib_errors.erl
index eeccb77db1..a90d6477a7 100644
--- a/lib/stdlib/src/erl_stdlib_errors.erl
+++ b/lib/stdlib/src/erl_stdlib_errors.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -73,10 +73,17 @@ format_binary_error(encode_unsigned, [Subject, Endianness], _) ->
[must_be_non_neg_integer(Subject), must_be_endianness(Endianness)];
format_binary_error(encode_hex, [Subject], _) ->
[must_be_binary(Subject)];
+format_binary_error(encode_hex, [Subject, Case], _) ->
+ [must_be_binary(Subject), must_be_hex_case(Case)];
format_binary_error(decode_hex, [Subject], _) ->
if
- is_binary(Subject), byte_size(Subject) rem 2 == 1 ->
- ["must contain an even number of bytes"];
+ is_binary(Subject) ->
+ if
+ byte_size(Subject) rem 2 =:= 1 ->
+ [<<"must contain an even number of bytes">>];
+ true ->
+ [<<"must only contain hex digits 0-9, A-F, and a-f">>]
+ end;
true ->
[must_be_binary(Subject)]
end;
@@ -237,8 +244,10 @@ format_maps_error(intersect_with, [Combiner, Map1, Map2]) ->
[must_be_fun(Combiner, 3), must_be_map(Map1), must_be_map(Map2)];
format_maps_error(is_key, _Args) ->
[[], not_map];
-format_maps_error(iterator, _Args) ->
- [not_map];
+format_maps_error(iterator, [Map]) ->
+ [must_be_map(Map)];
+format_maps_error(iterator, [Map, Order]) ->
+ [must_be_map(Map), must_be_map_iterator_order(Order)];
format_maps_error(keys, _Args) ->
[not_map];
format_maps_error(map, [Pred, Map]) ->
@@ -247,10 +256,10 @@ format_maps_error(merge, [Map1, Map2]) ->
[must_be_map(Map1), must_be_map(Map2)];
format_maps_error(merge_with, [Combiner, Map1, Map2]) ->
[must_be_fun(Combiner, 3), must_be_map(Map1), must_be_map(Map2)];
-format_maps_error(put, _Args) ->
- [[], [], not_map];
format_maps_error(next, _Args) ->
[bad_iterator];
+format_maps_error(put, _Args) ->
+ [[], [], not_map];
format_maps_error(remove, _Args) ->
[[], not_map];
format_maps_error(size, _Args) ->
@@ -258,7 +267,7 @@ format_maps_error(size, _Args) ->
format_maps_error(take, _Args) ->
[[], not_map];
format_maps_error(to_list, _Args) ->
- [not_map];
+ [not_map_or_iterator];
format_maps_error(update, _Args) ->
[[], [], not_map];
format_maps_error(update_with, [_Key, Fun, Map]) ->
@@ -506,7 +515,7 @@ format_io_error_cause(_, _, _, _HasDevice) ->
maybe_posix_message(Cause, HasDevice) ->
case erl_posix_msg:message(Cause) of
- "unknown POSIX error" ->
+ "unknown POSIX error" ++ _ ->
unknown;
PosixStr when HasDevice ->
[io_lib:format("~ts (~tp)",[PosixStr, Cause])];
@@ -641,6 +650,9 @@ format_ets_error(lookup_element, [_,_,Pos]=Args, Cause) ->
[TabCause, "", PosCause]
end
end;
+format_ets_error(lookup_element, [Tab, Key, Pos, _Default], Cause) ->
+ % The default argument cannot cause an error.
+ format_ets_error(lookup_element, [Tab, Key, Pos], Cause);
format_ets_error(match, [_], _Cause) ->
[bad_continuation];
format_ets_error(match, [_,_,_]=Args, Cause) ->
@@ -913,6 +925,10 @@ must_be_binary(Bin, Error) when is_binary(Bin) -> Error;
must_be_binary(Bin, _Error) when is_bitstring(Bin) -> bitstring;
must_be_binary(_, _) -> not_binary.
+must_be_hex_case(uppercase) -> [];
+must_be_hex_case(lowercase) -> [];
+must_be_hex_case(_) -> bad_hex_case.
+
must_be_endianness(little) -> [];
must_be_endianness(big) -> [];
must_be_endianness(_) -> bad_endianness.
@@ -959,14 +975,21 @@ must_be_list(_) ->
must_be_map(#{}) -> [];
must_be_map(_) -> not_map.
+must_be_map_iterator_order(undefined) ->
+ [];
+must_be_map_iterator_order(ordered) ->
+ [];
+must_be_map_iterator_order(CmpFun) when is_function(CmpFun, 2) ->
+ [];
+must_be_map_iterator_order(_) ->
+ not_map_iterator_order.
+
must_be_map_or_iter(Map) when is_map(Map) ->
[];
must_be_map_or_iter(Iter) ->
- try maps:next(Iter) of
- _ -> []
- catch
- error:_ ->
- not_map_or_iterator
+ case maps:is_iterator_valid(Iter) of
+ true -> [];
+ false -> not_map_or_iterator
end.
must_be_number(N) ->
@@ -1069,6 +1092,8 @@ expand_error(not_atom) ->
<<"not an atom">>;
expand_error(not_binary) ->
<<"not a binary">>;
+expand_error(bad_hex_case) ->
+ <<"not 'uppercase' or 'lowercase'">>;
expand_error(not_compiled_regexp) ->
<<"not a compiled regular expression">>;
expand_error(not_iodata) ->
@@ -1083,6 +1108,8 @@ expand_error(not_integer) ->
<<"not an integer">>;
expand_error(not_list) ->
<<"not a list">>;
+expand_error(not_map_iterator_order) ->
+ <<"not 'undefined', 'ordered', or a fun that takes two arguments">>;
expand_error(not_map_or_iterator) ->
<<"not a map or an iterator">>;
expand_error(not_number) ->
diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl
index 979a75b231..3545c8a186 100644
--- a/lib/stdlib/src/ets.erl
+++ b/lib/stdlib/src/ets.erl
@@ -70,7 +70,7 @@
-export([all/0, delete/1, delete/2, delete_all_objects/1,
delete_object/2, first/1, give_away/3, info/1, info/2,
insert/2, insert_new/2, is_compiled_ms/1, last/1, lookup/2,
- lookup_element/3, match/1, match/2, match/3, match_object/1,
+ lookup_element/3, lookup_element/4, match/1, match/2, match/3, match_object/1,
match_object/2, match_object/3, match_spec_compile/1,
match_spec_run_r/3, member/2, new/2, next/2, prev/2,
rename/2, safe_fixtable/2, select/1, select/2, select/3,
@@ -232,6 +232,16 @@ lookup(_, _) ->
lookup_element(_, _, _) ->
erlang:nif_error(undef).
+-spec lookup_element(Table, Key, Pos, Default) -> Elem when
+ Table :: table(),
+ Key :: term(),
+ Pos :: pos_integer(),
+ Default :: term(),
+ Elem :: term() | [term()].
+
+lookup_element(_, _, _, _) ->
+ erlang:nif_error(undef).
+
-spec match(Table, Pattern) -> [Match] when
Table :: table(),
Pattern :: match_pattern(),
diff --git a/lib/stdlib/src/filename.erl b/lib/stdlib/src/filename.erl
index 8bf4e97b9f..fc8e8110a0 100644
--- a/lib/stdlib/src/filename.erl
+++ b/lib/stdlib/src/filename.erl
@@ -78,8 +78,10 @@
-include_lib("kernel/include/file.hrl").
--define(IS_DRIVELETTER(Letter),(((Letter >= $A) andalso (Letter =< $Z)) orelse
- ((Letter >= $a) andalso (Letter =< $z)))).
+-define(IS_DRIVELETTER(Letter),
+ (is_integer(Letter)
+ andalso (($A =< Letter andalso Letter =< $Z)
+ orelse ($a =< Letter andalso Letter =< $z)))).
%% Converts a relative filename to an absolute filename
%% or the filename itself if it already is an absolute filename
@@ -333,8 +335,7 @@ dirname([$/|Rest], Dir, File, Seps) ->
dirname([DirSep|Rest], Dir, File, {DirSep,_}=Seps) when is_integer(DirSep) ->
dirname(Rest, File++Dir, [$/], Seps);
dirname([Dl,DrvSep|Rest], [], [], {_,DrvSep}=Seps)
- when is_integer(DrvSep), ((($a =< Dl) and (Dl =< $z)) or
- (($A =< Dl) and (Dl =< $Z))) ->
+ when is_integer(DrvSep), ?IS_DRIVELETTER(Dl) ->
dirname(Rest, [DrvSep,Dl], [], Seps);
dirname([Char|Rest], Dir, File, Seps) when is_integer(Char) ->
dirname(Rest, Dir, [Char|File], Seps);
@@ -757,7 +758,8 @@ win32_split([X, $\\|Rest]) when is_integer(X) ->
win32_split([X, $/|Rest]);
win32_split([X, Y, $\\|Rest]) when is_integer(X), is_integer(Y) ->
win32_split([X, Y, $/|Rest]);
-win32_split([UcLetter, $:|Rest]) when UcLetter >= $A, UcLetter =< $Z ->
+win32_split([UcLetter, $:|Rest])
+ when is_integer(UcLetter), $A =< UcLetter, UcLetter =< $Z ->
win32_split([UcLetter+$a-$A, $:|Rest]);
win32_split([Letter, $:, $/|Rest]) ->
split(Rest, [], [[Letter, $:, $/]], win32);
diff --git a/lib/stdlib/src/gb_sets.erl b/lib/stdlib/src/gb_sets.erl
index 8dda0d4ee0..aba1bed156 100644
--- a/lib/stdlib/src/gb_sets.erl
+++ b/lib/stdlib/src/gb_sets.erl
@@ -205,13 +205,13 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec empty() -> Set when
- Set :: set().
+ Set :: set(none()).
empty() ->
{0, nil}.
-spec new() -> Set when
- Set :: set().
+ Set :: set(none()).
new() -> empty().
@@ -259,13 +259,13 @@ is_member_1(_, nil) ->
Set1 :: set(Element),
Set2 :: set(Element).
-insert(Key, {S, T}) ->
+insert(Key, {S, T}) when is_integer(S), S >= 0 ->
S1 = S + 1,
{S1, insert_1(Key, T, ?pow(S1, ?p))}.
insert_1(Key, {Key1, Smaller, Bigger}, S) when Key < Key1 ->
case insert_1(Key, Smaller, ?div2(S)) of
- {T1, H1, S1} when is_integer(H1) ->
+ {T1, H1, S1} when is_integer(H1), is_integer(S1) ->
T = {Key1, T1, Bigger},
{H2, S2} = count(Bigger),
H = ?mul2(erlang:max(H1, H2)),
@@ -282,7 +282,7 @@ insert_1(Key, {Key1, Smaller, Bigger}, S) when Key < Key1 ->
end;
insert_1(Key, {Key1, Smaller, Bigger}, S) when Key > Key1 ->
case insert_1(Key, Bigger, ?div2(S)) of
- {T1, H1, S1} when is_integer(H1) ->
+ {T1, H1, S1} when is_integer(H1), is_integer(S1) ->
T = {Key1, Smaller, T1},
{H2, S2} = count(Smaller),
H = ?mul2(erlang:max(H1, H2)),
@@ -317,7 +317,7 @@ count(nil) ->
Set1 :: set(Element),
Set2 :: set(Element).
-balance({S, T}) ->
+balance({S, T}) when is_integer(S), S >= 0 ->
{S, balance(T, S)}.
balance(T, S) ->
@@ -550,9 +550,9 @@ next([]) ->
Set2 :: set(Element),
Set3 :: set(Element).
-union({N1, T1}, {N2, T2}) when N2 < N1 ->
+union({N1, T1}, {N2, T2}) when is_integer(N1), is_integer(N2), N2 < N1 ->
union(to_list_1(T2), N2, T1, N1);
-union({N1, T1}, {N2, T2}) ->
+union({N1, T1}, {N2, T2}) when is_integer(N1), is_integer(N2) ->
union(to_list_1(T1), N1, T2, N2).
%% We avoid the expensive mathematical computations if there is little
@@ -633,7 +633,7 @@ push([X | Xs], As) ->
push([], As) ->
As.
-balance_revlist(L, S) ->
+balance_revlist(L, S) when is_integer(S) ->
{T, _} = balance_revlist_1(L, S),
T.
@@ -670,9 +670,9 @@ union_list(S, []) -> S.
Set2 :: set(Element),
Set3 :: set(Element).
-intersection({N1, T1}, {N2, T2}) when N2 < N1 ->
+intersection({N1, T1}, {N2, T2}) when is_integer(N1), is_integer(N2), N2 < N1 ->
intersection(to_list_1(T2), N2, T1, N1);
-intersection({N1, T1}, {N2, T2}) ->
+intersection({N1, T1}, {N2, T2}) when is_integer(N1), is_integer(N2) ->
intersection(to_list_1(T1), N1, T2, N2).
intersection(L, _N1, T2, N2) when N2 < 10 ->
@@ -770,7 +770,8 @@ subtract(S1, S2) ->
Set2 :: set(Element),
Set3 :: set(Element).
-difference({N1, T1}, {N2, T2}) ->
+difference({N1, T1}, {N2, T2}) when is_integer(N1), N1 >= 0,
+ is_integer(N2), N2 >= 0 ->
difference(to_list_1(T1), N1, T2, N2).
difference(L, N1, T2, N2) when N2 < 10 ->
@@ -820,7 +821,8 @@ difference_2(Xs, [], As, S) ->
Set1 :: set(Element),
Set2 :: set(Element).
-is_subset({N1, T1}, {N2, T2}) ->
+is_subset({N1, T1}, {N2, T2}) when is_integer(N1), N1 >= 0,
+ is_integer(N2), N2 >= 0 ->
is_subset(to_list_1(T1), N1, T2, N2).
is_subset(L, _N1, T2, N2) when N2 < 10 ->
diff --git a/lib/stdlib/src/gb_trees.erl b/lib/stdlib/src/gb_trees.erl
index c0cdde012e..54a5ab6690 100644
--- a/lib/stdlib/src/gb_trees.erl
+++ b/lib/stdlib/src/gb_trees.erl
@@ -169,7 +169,7 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--spec empty() -> tree().
+-spec empty() -> tree(none(), none()).
empty() ->
{0, nil}.
@@ -279,7 +279,7 @@ insert(Key, Val, {S, T}) when is_integer(S) ->
insert_1(Key, Value, {Key1, V, Smaller, Bigger}, S) when Key < Key1 ->
case insert_1(Key, Value, Smaller, ?div2(S)) of
- {T1, H1, S1} ->
+ {T1, H1, S1} when is_integer(H1), is_integer(S1) ->
T = {Key1, V, T1, Bigger},
{H2, S2} = count(Bigger),
H = ?mul2(erlang:max(H1, H2)),
@@ -296,7 +296,7 @@ insert_1(Key, Value, {Key1, V, Smaller, Bigger}, S) when Key < Key1 ->
end;
insert_1(Key, Value, {Key1, V, Smaller, Bigger}, S) when Key > Key1 ->
case insert_1(Key, Value, Bigger, ?div2(S)) of
- {T1, H1, S1} ->
+ {T1, H1, S1} when is_integer(H1), is_integer(S1) ->
T = {Key1, V, Smaller, T1},
{H2, S2} = count(Smaller),
H = ?mul2(erlang:max(H1, H2)),
@@ -349,7 +349,7 @@ count(nil) ->
Tree1 :: tree(Key, Value),
Tree2 :: tree(Key, Value).
-balance({S, T}) ->
+balance({S, T}) when is_integer(S), S >= 0 ->
{S, balance(T, S)}.
balance(T, S) ->
diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl
index 363094fb15..d03ca78e7c 100644
--- a/lib/stdlib/src/gen.erl
+++ b/lib/stdlib/src/gen.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -40,6 +40,8 @@
-export([format_status_header/2, format_status/4]).
+-export(['@wait_response_recv_opt'/3]).
+
-define(MAX_INT_TIMEOUT, 4294967295).
-define(default_timeout, 5000).
@@ -193,7 +195,8 @@ init_it(GenMod, Starter, Parent, Name, Mod, Args, Options) ->
true ->
init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options);
{false, Pid} ->
- proc_lib:init_ack(Starter, {error, {already_started, Pid}})
+ proc_lib:init_fail(
+ Starter, {error, {already_started, Pid}}, {exit, normal})
end.
init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options) ->
@@ -313,6 +316,17 @@ do_send_request(Process, Tag, Request) ->
_ = erlang:send(Process, {Tag, {self(), [alias|ReqId]}, Request}, [noconnect]),
ReqId.
+-spec '@wait_response_recv_opt'(term(), term(), term()) -> ok.
+'@wait_response_recv_opt'(Process, Tag, Request) ->
+ %% Enables reference optimization in wait_response/2 and
+ %% receive_response/2
+ %%
+ %% This never actually runs and is only used to trigger the optimization,
+ %% see the module comment in beam_ssa_recv for details.
+ _ = wait_response(send_request(Process, Tag, Request), infinity),
+ _ = receive_response(send_request(Process, Tag, Request), infinity),
+ ok.
+
%%
%% Wait for a reply to the client.
%% Note: if timeout is returned monitors are kept.
diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl
index 68ff5e2d4d..6dba530b85 100644
--- a/lib/stdlib/src/gen_fsm.erl
+++ b/lib/stdlib/src/gen_fsm.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -329,30 +329,26 @@ init_it(Starter, self, Name, Mod, Args, Options) ->
init_it(Starter, Parent, Name0, Mod, Args, Options) ->
Name = gen:name(Name0),
Debug = gen:debug_options(Name, Options),
- HibernateAfterTimeout = gen:hibernate_after(Options),
- case catch Mod:init(Args) of
+ HibernateAfterTimeout = gen:hibernate_after(Options),
+ case catch Mod:init(Args) of
{ok, StateName, StateData} ->
- proc_lib:init_ack(Starter, {ok, self()}),
+ proc_lib:init_ack(Starter, {ok, self()}),
loop(Parent, Name, StateName, StateData, Mod, infinity, HibernateAfterTimeout, Debug);
{ok, StateName, StateData, Timeout} ->
- proc_lib:init_ack(Starter, {ok, self()}),
+ proc_lib:init_ack(Starter, {ok, self()}),
loop(Parent, Name, StateName, StateData, Mod, Timeout, HibernateAfterTimeout, Debug);
{stop, Reason} ->
- gen:unregister_name(Name0),
- proc_lib:init_ack(Starter, {error, Reason}),
- exit(Reason);
+ gen:unregister_name(Name0),
+ exit(Reason);
ignore ->
gen:unregister_name(Name0),
- proc_lib:init_ack(Starter, ignore),
- exit(normal);
+ proc_lib:init_fail(Starter, ignore, {exit, normal});
{'EXIT', Reason} ->
gen:unregister_name(Name0),
- proc_lib:init_ack(Starter, {error, Reason}),
- exit(Reason);
+ exit(Reason);
Else ->
- Error = {bad_return_value, Else},
- proc_lib:init_ack(Starter, {error, Error}),
- exit(Error)
+ Reason = {bad_return_value, Else},
+ exit(Reason)
end.
%%-----------------------------------------------------------------
diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl
index af5e04f78a..c574f2aeb2 100644
--- a/lib/stdlib/src/gen_server.erl
+++ b/lib/stdlib/src/gen_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -147,13 +147,29 @@
( (X) =:= infinity orelse ( is_integer(X) andalso (X) >= 0 ) )
).
+-record(callback_cache,{module :: module(),
+ handle_call :: fun((Request :: term(), From :: from(), State :: term()) ->
+ {reply, Reply :: term(), NewState :: term()} |
+ {reply, Reply :: term(), NewState :: term(), timeout() | hibernate | {continue, term()}} |
+ {noreply, NewState :: term()} |
+ {noreply, NewState :: term(), timeout() | hibernate | {continue, term()}} |
+ {stop, Reason :: term(), Reply :: term(), NewState :: term()} |
+ {stop, Reason :: term(), NewState :: term()}),
+ handle_cast :: fun((Request :: term(), State :: term()) ->
+ {noreply, NewState :: term()} |
+ {noreply, NewState :: term(), timeout() | hibernate | {continue, term()}} |
+ {stop, Reason :: term(), NewState :: term()}),
+ handle_info :: fun((Info :: timeout | term(), State :: term()) ->
+ {noreply, NewState :: term()} |
+ {noreply, NewState :: term(), timeout() | hibernate | {continue, term()}} |
+ {stop, Reason :: term(), NewState :: term()})}).
%%%=========================================================================
%%% API
%%%=========================================================================
-callback init(Args :: term()) ->
{ok, State :: term()} | {ok, State :: term(), timeout() | hibernate | {continue, term()}} |
- {stop, Reason :: term()} | ignore.
+ {stop, Reason :: term()} | ignore | {error, Reason :: term()}.
-callback handle_call(Request :: term(), From :: from(),
State :: term()) ->
{reply, Reply :: term(), NewState :: term()} |
@@ -649,7 +665,7 @@ do_abcast([], _,_) -> abcast.
%%
multi_call(Name, Request)
when is_atom(Name) ->
- do_multi_call([node() | nodes()], Name, Request, infinity).
+ multi_call([node() | nodes()], Name, Request, infinity).
-spec multi_call(
Nodes :: [node()],
@@ -663,7 +679,7 @@ multi_call(Name, Request)
%%
multi_call(Nodes, Name, Request)
when is_list(Nodes), is_atom(Name) ->
- do_multi_call(Nodes, Name, Request, infinity).
+ multi_call(Nodes, Name, Request, infinity).
-spec multi_call(
Nodes :: [node()],
@@ -678,8 +694,93 @@ multi_call(Nodes, Name, Request)
%%
multi_call(Nodes, Name, Request, Timeout)
when is_list(Nodes), is_atom(Name), ?is_timeout(Timeout) ->
- do_multi_call(Nodes, Name, Request, Timeout).
+ Alias = alias(),
+ try
+ Timer = if Timeout == infinity -> undefined;
+ true -> erlang:start_timer(Timeout, self(), Alias)
+ end,
+ Reqs = mc_send(Nodes, Name, Alias, Request, Timer, []),
+ mc_recv(Reqs, Alias, Timer, [], [])
+ after
+ _ = unalias(Alias)
+ end.
+-dialyzer({no_improper_lists, mc_send/6}).
+
+mc_send([], _Name, _Alias, _Request, _Timer, Reqs) ->
+ Reqs;
+mc_send([Node|Nodes], Name, Alias, Request, Timer, Reqs) when is_atom(Node) ->
+ NN = {Name, Node},
+ Mon = try
+ erlang:monitor(process, NN, [{tag, Alias}])
+ catch
+ error:badarg ->
+ %% Node not alive...
+ M = make_ref(),
+ Alias ! {Alias, M, process, NN, noconnection},
+ M
+ end,
+ try
+ %% We use 'noconnect' since it is no point in bringing up a new
+ %% connection if it was not brought up by the monitor signal...
+ _ = erlang:send(NN,
+ {'$gen_call', {self(), [[alias|Alias]|Mon]}, Request},
+ [noconnect]),
+ ok
+ catch
+ _:_ ->
+ ok
+ end,
+ mc_send(Nodes, Name, Alias, Request, Timer, [[Node|Mon]|Reqs]);
+mc_send(_BadNodes, _Name, Alias, _Request, Timer, Reqs) ->
+ %% Cleanup then fail...
+ unalias(Alias),
+ mc_cancel_timer(Timer, Alias),
+ _ = mc_recv_tmo(Reqs, Alias, [], []),
+ error(badarg).
+
+mc_recv([], Alias, Timer, Replies, BadNodes) ->
+ mc_cancel_timer(Timer, Alias),
+ unalias(Alias),
+ {Replies, BadNodes};
+mc_recv([[Node|Mon] | RestReqs] = Reqs, Alias, Timer, Replies, BadNodes) ->
+ receive
+ {[[alias|Alias]|Mon], Reply} ->
+ erlang:demonitor(Mon, [flush]),
+ mc_recv(RestReqs, Alias, Timer, [{Node,Reply}|Replies], BadNodes);
+ {Alias, Mon, process, _, _} ->
+ mc_recv(RestReqs, Alias, Timer, Replies, [Node|BadNodes]);
+ {timeout, Timer, Alias} ->
+ unalias(Alias),
+ mc_recv_tmo(Reqs, Alias, Replies, BadNodes)
+ end.
+
+mc_recv_tmo([], _Alias, Replies, BadNodes) ->
+ {Replies, BadNodes};
+mc_recv_tmo([[Node|Mon] | RestReqs], Alias, Replies, BadNodes) ->
+ erlang:demonitor(Mon),
+ receive
+ {[[alias|Alias]|Mon], Reply} ->
+ mc_recv_tmo(RestReqs, Alias, [{Node,Reply}|Replies], BadNodes);
+ {Alias, Mon, process, _, _} ->
+ mc_recv_tmo(RestReqs, Alias, Replies, [Node|BadNodes])
+ after
+ 0 ->
+ mc_recv_tmo(RestReqs, Alias, Replies, [Node|BadNodes])
+ end.
+
+mc_cancel_timer(undefined, _Alias) ->
+ ok;
+mc_cancel_timer(Timer, Alias) ->
+ case erlang:cancel_timer(Timer) of
+ false ->
+ receive
+ {timeout, Timer, Alias} ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
%%-----------------------------------------------------------------
%% enter_loop(Mod, Options, State, <ServerName>, <TimeOut>) ->_
@@ -783,7 +884,8 @@ enter_loop(Mod, Options, State, ServerName, TimeoutOrHibernate)
Parent = gen:get_parent(),
Debug = gen:debug_options(Name, Options),
HibernateAfterTimeout = gen:hibernate_after(Options),
- loop(Parent, Name, State, Mod, TimeoutOrHibernate, HibernateAfterTimeout, Debug);
+ CbCache = create_callback_cache(Mod),
+ loop(Parent, Name, State, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, Debug);
%%
enter_loop(Mod, Options, State, ServerName, {continue, _}=Continue)
when is_atom(Mod), is_list(Options) ->
@@ -791,7 +893,8 @@ enter_loop(Mod, Options, State, ServerName, {continue, _}=Continue)
Parent = gen:get_parent(),
Debug = gen:debug_options(Name, Options),
HibernateAfterTimeout = gen:hibernate_after(Options),
- loop(Parent, Name, State, Mod, Continue, HibernateAfterTimeout, Debug).
+ CbCache = create_callback_cache(Mod),
+ loop(Parent, Name, State, CbCache, Continue, HibernateAfterTimeout, Debug).
%%%========================================================================
%%% Gen-callback functions
@@ -810,19 +913,25 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) ->
Name = gen:name(Name0),
Debug = gen:debug_options(Name, Options),
HibernateAfterTimeout = gen:hibernate_after(Options),
-
+ CbCache = create_callback_cache(Mod),
case init_it(Mod, Args) of
{ok, {ok, State}} ->
- proc_lib:init_ack(Starter, {ok, self()}),
- loop(Parent, Name, State, Mod, infinity, HibernateAfterTimeout, Debug);
- {ok, {ok, State, TimeoutOrHibernate}}
+ proc_lib:init_ack(Starter, {ok, self()}),
+ loop(
+ Parent, Name, State, CbCache, infinity,
+ HibernateAfterTimeout, Debug);
+ {ok, {ok, State, TimeoutOrHibernate}}
when ?is_timeout(TimeoutOrHibernate);
TimeoutOrHibernate =:= hibernate ->
- proc_lib:init_ack(Starter, {ok, self()}),
- loop(Parent, Name, State, Mod, TimeoutOrHibernate, HibernateAfterTimeout, Debug);
+ proc_lib:init_ack(Starter, {ok, self()}),
+ loop(
+ Parent, Name, State, CbCache, TimeoutOrHibernate,
+ HibernateAfterTimeout, Debug);
{ok, {ok, State, {continue, _}=Continue}} ->
- proc_lib:init_ack(Starter, {ok, self()}),
- loop(Parent, Name, State, Mod, Continue, HibernateAfterTimeout, Debug);
+ proc_lib:init_ack(Starter, {ok, self()}),
+ loop(
+ Parent, Name, State, CbCache, Continue,
+ HibernateAfterTimeout, Debug);
{ok, {stop, Reason}} ->
%% For consistency, we must make sure that the
%% registered name (if any) is unregistered before
@@ -831,29 +940,32 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) ->
%% an 'already_started' error if it immediately
%% tried starting the process again.)
gen:unregister_name(Name0),
- proc_lib:init_ack(Starter, {error, Reason}),
- exit(Reason);
+ exit(Reason);
+ {ok, {error, _Reason} = ERROR} ->
+ %% The point of this clause is that we shall have a silent/graceful
+ %% termination. The error reason will be returned to the
+ %% 'Starter' ({error, Reason}), but *no* crash report.
+ gen:unregister_name(Name0),
+ proc_lib:init_fail(Starter, ERROR, {exit, normal});
{ok, ignore} ->
gen:unregister_name(Name0),
- proc_lib:init_ack(Starter, ignore),
- exit(normal);
+ proc_lib:init_fail(Starter, ignore, {exit, normal});
{ok, Else} ->
- Error = {bad_return_value, Else},
- proc_lib:init_ack(Starter, {error, Error}),
- exit(Error);
+ gen:unregister_name(Name0),
+ exit({bad_return_value, Else});
{'EXIT', Class, Reason, Stacktrace} ->
gen:unregister_name(Name0),
- proc_lib:init_ack(Starter, {error, terminate_reason(Class, Reason, Stacktrace)}),
- erlang:raise(Class, Reason, Stacktrace)
+ erlang:raise(Class, Reason, Stacktrace)
end.
init_it(Mod, Args) ->
try
- {ok, Mod:init(Args)}
+ {ok, Mod:init(Args)}
catch
- throw:R -> {ok, R};
- Class:R:S -> {'EXIT', Class, R, S}
+ throw:R -> {ok, R};
+ Class:R:S -> {'EXIT', Class, R, S}
end.
+
%%%========================================================================
%%% Internal functions
%%%========================================================================
@@ -861,58 +973,68 @@ init_it(Mod, Args) ->
%%% The MAIN loop.
%%% ---------------------------------------------------
-loop(Parent, Name, State, Mod, {continue, Continue} = Msg, HibernateAfterTimeout, Debug) ->
- Reply = try_dispatch(Mod, handle_continue, Continue, State),
+loop(Parent, Name, State, CbCache, {continue, Continue} = Msg, HibernateAfterTimeout, Debug) ->
+ Reply = try_handle_continue(CbCache, Continue, State),
case Debug of
- [] ->
- handle_common_reply(Reply, Parent, Name, undefined, Msg, Mod,
- HibernateAfterTimeout, State);
- _ ->
- Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, Msg),
- handle_common_reply(Reply, Parent, Name, undefined, Msg, Mod,
- HibernateAfterTimeout, State, Debug1)
+ [] ->
+ handle_common_reply(Reply, Parent, Name, undefined, Msg, CbCache,
+ HibernateAfterTimeout, State);
+ _ ->
+ Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, Msg),
+ handle_common_reply(Reply, Parent, Name, undefined, Msg, CbCache,
+ HibernateAfterTimeout, State, Debug1)
end;
-loop(Parent, Name, State, Mod, hibernate, HibernateAfterTimeout, Debug) ->
+loop(Parent, Name, State, CbCache, hibernate, HibernateAfterTimeout, Debug) ->
+ Mod = CbCache#callback_cache.module,
proc_lib:hibernate(?MODULE,wake_hib,[Parent, Name, State, Mod, HibernateAfterTimeout, Debug]);
-loop(Parent, Name, State, Mod, infinity, HibernateAfterTimeout, Debug) ->
- receive
- Msg ->
- decode_msg(Msg, Parent, Name, State, Mod, infinity, HibernateAfterTimeout, Debug, false)
- after HibernateAfterTimeout ->
- loop(Parent, Name, State, Mod, hibernate, HibernateAfterTimeout, Debug)
- end;
+loop(Parent, Name, State, CbCache, infinity, HibernateAfterTimeout, Debug) ->
+ receive
+ Msg ->
+ decode_msg(Msg, Parent, Name, State, CbCache, infinity, HibernateAfterTimeout, Debug, false)
+ after HibernateAfterTimeout ->
+ loop(Parent, Name, State, CbCache, hibernate, HibernateAfterTimeout, Debug)
+ end;
-loop(Parent, Name, State, Mod, Time, HibernateAfterTimeout, Debug) ->
+loop(Parent, Name, State, CbCache, Time, HibernateAfterTimeout, Debug) ->
Msg = receive
- Input ->
- Input
- after Time ->
- timeout
- end,
- decode_msg(Msg, Parent, Name, State, Mod, Time, HibernateAfterTimeout, Debug, false).
+ Input ->
+ Input
+ after Time ->
+ timeout
+ end,
+ decode_msg(Msg, Parent, Name, State, CbCache, Time, HibernateAfterTimeout, Debug, false).
+
+-spec create_callback_cache(module()) -> #callback_cache{}.
+create_callback_cache(Mod) ->
+ #callback_cache{module = Mod,
+ handle_call = fun Mod:handle_call/3,
+ handle_cast = fun Mod:handle_cast/2,
+ handle_info = fun Mod:handle_info/2}.
wake_hib(Parent, Name, State, Mod, HibernateAfterTimeout, Debug) ->
Msg = receive
- Input ->
- Input
- end,
- decode_msg(Msg, Parent, Name, State, Mod, hibernate, HibernateAfterTimeout, Debug, true).
+ Input ->
+ Input
+ end,
+ CbCache = create_callback_cache(Mod),
+ decode_msg(Msg, Parent, Name, State, CbCache, hibernate, HibernateAfterTimeout, Debug, true).
-decode_msg(Msg, Parent, Name, State, Mod, Time, HibernateAfterTimeout, Debug, Hib) ->
+decode_msg(Msg, Parent, Name, State, CbCache, Time, HibernateAfterTimeout, Debug, Hib) ->
case Msg of
- {system, From, Req} ->
- sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
- [Name, State, Mod, Time, HibernateAfterTimeout], Hib);
- {'EXIT', Parent, Reason} ->
- terminate(Reason, ?STACKTRACE(), Name, undefined, Msg, Mod, State, Debug);
- _Msg when Debug =:= [] ->
- handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout);
- _Msg ->
- Debug1 = sys:handle_debug(Debug, fun print_event/3,
- Name, {in, Msg}),
- handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout, Debug1)
+ {system, From, Req} ->
+ sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
+ [Name, State, CbCache, Time, HibernateAfterTimeout], Hib);
+ {'EXIT', Parent, Reason} ->
+ #callback_cache{module = Mod} = CbCache,
+ terminate(Reason, ?STACKTRACE(), Name, undefined, Msg, Mod, State, Debug);
+ _Msg when Debug =:= [] ->
+ handle_msg(Msg, Parent, Name, State, CbCache, HibernateAfterTimeout);
+ _Msg ->
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
+ Name, {in, Msg}),
+ handle_msg(Msg, Parent, Name, State, CbCache, HibernateAfterTimeout, Debug1)
end.
%%% ---------------------------------------------------
@@ -925,184 +1047,6 @@ do_send(Dest, Msg) ->
end,
ok.
-do_multi_call([Node], Name, Req, infinity) when Node =:= node() ->
- % Special case when multi_call is used with local node only.
- % In that case we can leverage the benefit of recv_mark optimisation
- % existing in simple gen:call.
- try gen:call(Name, '$gen_call', Req, infinity) of
- {ok, Res} -> {[{Node, Res}],[]}
- catch exit:_ ->
- {[], [Node]}
- end;
-do_multi_call(Nodes, Name, Req, infinity) ->
- Tag = make_ref(),
- Monitors = send_nodes(Nodes, Name, Tag, Req),
- rec_nodes(Tag, Monitors, Name, undefined);
-do_multi_call(Nodes, Name, Req, Timeout) ->
- Tag = make_ref(),
- Caller = self(),
- Receiver =
- spawn(
- fun() ->
- %% Middleman process. Should be unsensitive to regular
- %% exit signals. The sychronization is needed in case
- %% the receiver would exit before the caller started
- %% the monitor.
- process_flag(trap_exit, true),
- Mref = erlang:monitor(process, Caller),
- receive
- {Caller,Tag} ->
- Monitors = send_nodes(Nodes, Name, Tag, Req),
- TimerId = erlang:start_timer(Timeout, self(), ok),
- Result = rec_nodes(Tag, Monitors, Name, TimerId),
- exit({self(),Tag,Result});
- {'DOWN',Mref,_,_,_} ->
- %% Caller died before sending us the go-ahead.
- %% Give up silently.
- exit(normal)
- end
- end),
- Mref = erlang:monitor(process, Receiver),
- Receiver ! {self(),Tag},
- receive
- {'DOWN',Mref,_,_,{Receiver,Tag,Result}} ->
- Result;
- {'DOWN',Mref,_,_,Reason} ->
- %% The middleman code failed. Or someone did
- %% exit(_, kill) on the middleman process => Reason==killed
- exit(Reason)
- end.
-
-send_nodes(Nodes, Name, Tag, Req) ->
- send_nodes(Nodes, Name, Tag, Req, []).
-
-send_nodes([Node|Tail], Name, Tag, Req, Monitors)
- when is_atom(Node) ->
- Monitor = start_monitor(Node, Name),
- %% Handle non-existing names in rec_nodes.
- catch {Name, Node} ! {'$gen_call', {self(), {Tag, Node}}, Req},
- send_nodes(Tail, Name, Tag, Req, [Monitor | Monitors]);
-send_nodes([_Node|Tail], Name, Tag, Req, Monitors) ->
- %% Skip non-atom Node
- send_nodes(Tail, Name, Tag, Req, Monitors);
-send_nodes([], _Name, _Tag, _Req, Monitors) ->
- Monitors.
-
-%% Against old nodes:
-%% If no reply has been delivered within 2 secs. (per node) check that
-%% the server really exists and wait for ever for the answer.
-%%
-%% Against contemporary nodes:
-%% Wait for reply, server 'DOWN', or timeout from TimerId.
-
-rec_nodes(Tag, Nodes, Name, TimerId) ->
- rec_nodes(Tag, Nodes, Name, [], [], 2000, TimerId).
-
-rec_nodes(Tag, [{N,R}|Tail], Name, Badnodes, Replies, Time, TimerId ) ->
- receive
- {'DOWN', R, _, _, _} ->
- rec_nodes(Tag, Tail, Name, [N|Badnodes], Replies, Time, TimerId);
- {{Tag, N}, Reply} -> %% Tag is bound !!!
- erlang:demonitor(R, [flush]),
- rec_nodes(Tag, Tail, Name, Badnodes,
- [{N,Reply}|Replies], Time, TimerId);
- {timeout, TimerId, _} ->
- erlang:demonitor(R, [flush]),
- %% Collect all replies that already have arrived
- rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies)
- end;
-rec_nodes(Tag, [N|Tail], Name, Badnodes, Replies, Time, TimerId) ->
- %% R6 node
- receive
- {nodedown, N} ->
- monitor_node(N, false),
- rec_nodes(Tag, Tail, Name, [N|Badnodes], Replies, 2000, TimerId);
- {{Tag, N}, Reply} -> %% Tag is bound !!!
- receive {nodedown, N} -> ok after 0 -> ok end,
- monitor_node(N, false),
- rec_nodes(Tag, Tail, Name, Badnodes,
- [{N,Reply}|Replies], 2000, TimerId);
- {timeout, TimerId, _} ->
- receive {nodedown, N} -> ok after 0 -> ok end,
- monitor_node(N, false),
- %% Collect all replies that already have arrived
- rec_nodes_rest(Tag, Tail, Name, [N | Badnodes], Replies)
- after Time ->
- case rpc:call(N, erlang, whereis, [Name]) of
- Pid when is_pid(Pid) -> % It exists try again.
- rec_nodes(Tag, [N|Tail], Name, Badnodes,
- Replies, infinity, TimerId);
- _ -> % badnode
- receive {nodedown, N} -> ok after 0 -> ok end,
- monitor_node(N, false),
- rec_nodes(Tag, Tail, Name, [N|Badnodes],
- Replies, 2000, TimerId)
- end
- end;
-rec_nodes(_, [], _, Badnodes, Replies, _, TimerId) ->
- case catch erlang:cancel_timer(TimerId) of
- false -> % It has already sent it's message
- receive
- {timeout, TimerId, _} -> ok
- after 0 ->
- ok
- end;
- _ -> % Timer was cancelled, or TimerId was 'undefined'
- ok
- end,
- {Replies, Badnodes}.
-
-%% Collect all replies that already have arrived
-rec_nodes_rest(Tag, [{N,R}|Tail], Name, Badnodes, Replies) ->
- receive
- {'DOWN', R, _, _, _} ->
- rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies);
- {{Tag, N}, Reply} -> %% Tag is bound !!!
- erlang:demonitor(R, [flush]),
- rec_nodes_rest(Tag, Tail, Name, Badnodes, [{N,Reply}|Replies])
- after 0 ->
- erlang:demonitor(R, [flush]),
- rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies)
- end;
-rec_nodes_rest(Tag, [N|Tail], Name, Badnodes, Replies) ->
- %% R6 node
- receive
- {nodedown, N} ->
- monitor_node(N, false),
- rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies);
- {{Tag, N}, Reply} -> %% Tag is bound !!!
- receive {nodedown, N} -> ok after 0 -> ok end,
- monitor_node(N, false),
- rec_nodes_rest(Tag, Tail, Name, Badnodes, [{N,Reply}|Replies])
- after 0 ->
- receive {nodedown, N} -> ok after 0 -> ok end,
- monitor_node(N, false),
- rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies)
- end;
-rec_nodes_rest(_Tag, [], _Name, Badnodes, Replies) ->
- {Replies, Badnodes}.
-
-
-%%% ---------------------------------------------------
-%%% Monitor functions
-%%% ---------------------------------------------------
-
-start_monitor(Node, Name) when is_atom(Node), is_atom(Name) ->
- if node() =:= nonode@nohost, Node =/= nonode@nohost ->
- Ref = make_ref(),
- self() ! {'DOWN', Ref, process, {Name, Node}, noconnection},
- {Node, Ref};
- true ->
- case catch erlang:monitor(process, {Name, Node}) of
- {'EXIT', _} ->
- %% Remote node is R6
- monitor_node(Node, true),
- Node;
- Ref when is_reference(Ref) ->
- {Node, Ref}
- end
- end.
-
%% ---------------------------------------------------
%% Helper functions for try-catch of callbacks.
%% Returns the return value of the callback, or
@@ -1113,60 +1057,80 @@ start_monitor(Node, Name) when is_atom(Node), is_atom(Name) ->
%% stacktraces.
%% ---------------------------------------------------
-try_dispatch({'$gen_cast', Msg}, Mod, State) ->
- try_dispatch(Mod, handle_cast, Msg, State);
-try_dispatch(Info, Mod, State) ->
- try_dispatch(Mod, handle_info, Info, State).
+try_dispatch({'$gen_cast', Msg}, CbCache, State) ->
+ try_handle_cast(CbCache, Msg, State);
+try_dispatch(Info, CbCache, State) ->
+ try_handle_info(CbCache, Info, State).
+
+try_handle_continue(#callback_cache{module = Mod}, Msg, State) ->
+ try
+ {ok, Mod:handle_continue(Msg, State)}
+ catch
+ throw:R ->
+ {ok, R};
+ Class:R:Stacktrace ->
+ {'EXIT', Class, R, Stacktrace}
+ end.
-try_dispatch(Mod, Func, Msg, State) ->
+try_handle_info(#callback_cache{module = Mod, handle_info = HandleInfo}, Msg, State) ->
try
- {ok, Mod:Func(Msg, State)}
+ {ok, HandleInfo(Msg, State)}
catch
- throw:R ->
- {ok, R};
- error:undef = R:Stacktrace when Func == handle_info ->
+ throw:R ->
+ {ok, R};
+ error:undef = R:Stacktrace ->
case erlang:function_exported(Mod, handle_info, 2) of
false ->
?LOG_WARNING(
- #{label=>{gen_server,no_handle_info},
- module=>Mod,
- message=>Msg},
- #{domain=>[otp],
- report_cb=>fun gen_server:format_log/2,
- error_logger=>
- #{tag=>warning_msg,
- report_cb=>fun gen_server:format_log/1}}),
+ #{label=>{gen_server,no_handle_info},
+ module=>Mod,
+ message=>Msg},
+ #{domain=>[otp],
+ report_cb=>fun gen_server:format_log/2,
+ error_logger=>
+ #{tag=>warning_msg,
+ report_cb=>fun gen_server:format_log/1}}),
{ok, {noreply, State}};
true ->
{'EXIT', error, R, Stacktrace}
end;
- Class:R:Stacktrace ->
- {'EXIT', Class, R, Stacktrace}
+ Class:R:Stacktrace ->
+ {'EXIT', Class, R, Stacktrace}
end.
-try_handle_call(Mod, Msg, From, State) ->
+try_handle_cast(#callback_cache{handle_cast = HandleCast}, Msg, State) ->
try
- {ok, Mod:handle_call(Msg, From, State)}
+ {ok, HandleCast(Msg, State)}
catch
- throw:R ->
- {ok, R};
- Class:R:Stacktrace ->
- {'EXIT', Class, R, Stacktrace}
+ throw:R ->
+ {ok, R};
+ Class:R:Stacktrace ->
+ {'EXIT', Class, R, Stacktrace}
+ end.
+
+try_handle_call(#callback_cache{handle_call = HandleCall}, Msg, From, State) ->
+ try
+ {ok, HandleCall(Msg, From, State)}
+ catch
+ throw:R ->
+ {ok, R};
+ Class:R:Stacktrace ->
+ {'EXIT', Class, R, Stacktrace}
end.
try_terminate(Mod, Reason, State) ->
case erlang:function_exported(Mod, terminate, 2) of
- true ->
- try
- {ok, Mod:terminate(Reason, State)}
- catch
- throw:R ->
- {ok, R};
- Class:R:Stacktrace ->
- {'EXIT', Class, R, Stacktrace}
- end;
- false ->
- {ok, ok}
+ true ->
+ try
+ {ok, Mod:terminate(Reason, State)}
+ catch
+ throw:R ->
+ {ok, R};
+ Class:R:Stacktrace ->
+ {'EXIT', Class, R, Stacktrace}
+ end;
+ false ->
+ {ok, ok}
end.
@@ -1174,69 +1138,72 @@ try_terminate(Mod, Reason, State) ->
%%% Message handling functions
%%% ---------------------------------------------------
-handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, HibernateAfterTimeout) ->
- Result = try_handle_call(Mod, Msg, From, State),
+handle_msg({'$gen_call', From, Msg}, Parent, Name, State, CbCache, HibernateAfterTimeout) ->
+ Result = try_handle_call(CbCache, Msg, From, State),
case Result of
{ok, {reply, Reply, NState}} ->
reply(From, Reply),
- loop(Parent, Name, NState, Mod, infinity, HibernateAfterTimeout, []);
+ loop(Parent, Name, NState, CbCache, infinity, HibernateAfterTimeout, []);
{ok, {reply, Reply, NState, TimeoutOrHibernate}}
when ?is_timeout(TimeoutOrHibernate);
TimeoutOrHibernate =:= hibernate ->
reply(From, Reply),
- loop(Parent, Name, NState, Mod, TimeoutOrHibernate, HibernateAfterTimeout, []);
+ loop(Parent, Name, NState, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, []);
{ok, {reply, Reply, NState, {continue, _}=Continue}} ->
reply(From, Reply),
- loop(Parent, Name, NState, Mod, Continue, HibernateAfterTimeout, []);
+ loop(Parent, Name, NState, CbCache, Continue, HibernateAfterTimeout, []);
{ok, {stop, Reason, Reply, NState}} ->
try
+ Mod = CbCache#callback_cache.module,
terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, [])
after
reply(From, Reply)
end;
- Other -> handle_common_reply(Other, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, State)
+ Other -> handle_common_reply(Other, Parent, Name, From, Msg, CbCache, HibernateAfterTimeout, State)
end;
-handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout) ->
- Reply = try_dispatch(Msg, Mod, State),
- handle_common_reply(Reply, Parent, Name, undefined, Msg, Mod, HibernateAfterTimeout, State).
+handle_msg(Msg, Parent, Name, State, CbCache, HibernateAfterTimeout) ->
+ Reply = try_dispatch(Msg, CbCache, State),
+ handle_common_reply(Reply, Parent, Name, undefined, Msg, CbCache, HibernateAfterTimeout, State).
-handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, HibernateAfterTimeout, Debug) ->
- Result = try_handle_call(Mod, Msg, From, State),
+handle_msg({'$gen_call', From, Msg}, Parent, Name, State, CbCache, HibernateAfterTimeout, Debug) ->
+ Result = try_handle_call(CbCache, Msg, From, State),
case Result of
{ok, {reply, Reply, NState}} ->
Debug1 = reply(Name, From, Reply, NState, Debug),
- loop(Parent, Name, NState, Mod, infinity, HibernateAfterTimeout, Debug1);
+ loop(Parent, Name, NState, CbCache, infinity, HibernateAfterTimeout, Debug1);
{ok, {reply, Reply, NState, TimeoutOrHibernate}}
when ?is_timeout(TimeoutOrHibernate);
TimeoutOrHibernate =:= hibernate ->
Debug1 = reply(Name, From, Reply, NState, Debug),
- loop(Parent, Name, NState, Mod, TimeoutOrHibernate, HibernateAfterTimeout, Debug1);
+ loop(Parent, Name, NState, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, Debug1);
{ok, {reply, Reply, NState, {continue, _}=Continue}} ->
Debug1 = reply(Name, From, Reply, NState, Debug),
- loop(Parent, Name, NState, Mod, Continue, HibernateAfterTimeout, Debug1);
+ loop(Parent, Name, NState, CbCache, Continue, HibernateAfterTimeout, Debug1);
{ok, {stop, Reason, Reply, NState}} ->
try
+ Mod = CbCache#callback_cache.module,
terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, Debug)
after
_ = reply(Name, From, Reply, NState, Debug)
end;
Other ->
- handle_common_reply(Other, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, State, Debug)
+ handle_common_reply(Other, Parent, Name, From, Msg, CbCache, HibernateAfterTimeout, State, Debug)
end;
-handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout, Debug) ->
- Reply = try_dispatch(Msg, Mod, State),
- handle_common_reply(Reply, Parent, Name, undefined, Msg, Mod, HibernateAfterTimeout, State, Debug).
+handle_msg(Msg, Parent, Name, State, CbCache, HibernateAfterTimeout, Debug) ->
+ Reply = try_dispatch(Msg, CbCache, State),
+ handle_common_reply(Reply, Parent, Name, undefined, Msg, CbCache, HibernateAfterTimeout, State, Debug).
-handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, State) ->
+handle_common_reply(Reply, Parent, Name, From, Msg, CbCache, HibernateAfterTimeout, State) ->
+ Mod = CbCache#callback_cache.module,
case Reply of
{ok, {noreply, NState}} ->
- loop(Parent, Name, NState, Mod, infinity, HibernateAfterTimeout, []);
+ loop(Parent, Name, NState, CbCache, infinity, HibernateAfterTimeout, []);
{ok, {noreply, NState, TimeoutOrHibernate}}
when ?is_timeout(TimeoutOrHibernate);
TimeoutOrHibernate =:= hibernate ->
- loop(Parent, Name, NState, Mod, TimeoutOrHibernate, HibernateAfterTimeout, []);
+ loop(Parent, Name, NState, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, []);
{ok, {noreply, NState, {continue, _}=Continue}} ->
- loop(Parent, Name, NState, Mod, Continue, HibernateAfterTimeout, []);
+ loop(Parent, Name, NState, CbCache, Continue, HibernateAfterTimeout, []);
{ok, {stop, Reason, NState}} ->
terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, []);
{'EXIT', Class, Reason, Stacktrace} ->
@@ -1245,20 +1212,21 @@ handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout,
terminate({bad_return_value, BadReply}, ?STACKTRACE(), Name, From, Msg, Mod, State, [])
end.
-handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, State, Debug) ->
+handle_common_reply(Reply, Parent, Name, From, Msg, CbCache, HibernateAfterTimeout, State, Debug) ->
+ Mod = CbCache#callback_cache.module,
case Reply of
{ok, {noreply, NState}} ->
Debug1 = sys:handle_debug(Debug, fun print_event/3, Name,
{noreply, NState}),
- loop(Parent, Name, NState, Mod, infinity, HibernateAfterTimeout, Debug1);
+ loop(Parent, Name, NState, CbCache, infinity, HibernateAfterTimeout, Debug1);
{ok, {noreply, NState, TimeoutOrHibernate}}
when ?is_timeout(TimeoutOrHibernate);
TimeoutOrHibernate =:= hibernate ->
Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}),
- loop(Parent, Name, NState, Mod, TimeoutOrHibernate, HibernateAfterTimeout, Debug1);
+ loop(Parent, Name, NState, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, Debug1);
{ok, {noreply, NState, {continue, _}=Continue}} ->
Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}),
- loop(Parent, Name, NState, Mod, Continue, HibernateAfterTimeout, Debug1);
+ loop(Parent, Name, NState, CbCache, Continue, HibernateAfterTimeout, Debug1);
{ok, {stop, Reason, NState}} ->
terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, Debug);
{'EXIT', Class, Reason, Stacktrace} ->
@@ -1270,32 +1238,34 @@ handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout,
reply(Name, From, Reply, State, Debug) ->
reply(From, Reply),
sys:handle_debug(Debug, fun print_event/3, Name,
- {out, Reply, From, State} ).
+ {out, Reply, From, State} ).
%%-----------------------------------------------------------------
%% Callback functions for system messages handling.
%%-----------------------------------------------------------------
-system_continue(Parent, Debug, [Name, State, Mod, Time, HibernateAfterTimeout]) ->
- loop(Parent, Name, State, Mod, Time, HibernateAfterTimeout, Debug).
+system_continue(Parent, Debug, [Name, State, CbCache, Time, HibernateAfterTimeout]) ->
+ loop(Parent, Name, State, CbCache, Time, HibernateAfterTimeout, Debug).
-spec system_terminate(_, _, _, [_]) -> no_return().
-system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time, _HibernateAfterTimeout]) ->
+system_terminate(Reason, _Parent, Debug, [Name, State, CbCache, _Time, _HibernateAfterTimeout]) ->
+ Mod = CbCache#callback_cache.module,
terminate(Reason, ?STACKTRACE(), Name, undefined, [], Mod, State, Debug).
-system_code_change([Name, State, Mod, Time, HibernateAfterTimeout], _Module, OldVsn, Extra) ->
+system_code_change([Name, State, CbCache, Time, HibernateAfterTimeout], _Module, OldVsn, Extra) ->
+ Mod = CbCache#callback_cache.module,
case catch Mod:code_change(OldVsn, State, Extra) of
- {ok, NewState} -> {ok, [Name, NewState, Mod, Time, HibernateAfterTimeout]};
- Else -> Else
+ {ok, NewState} -> {ok, [Name, NewState, CbCache, Time, HibernateAfterTimeout]};
+ Else -> Else
end.
system_get_state([_Name, State, _Mod, _Time, _HibernateAfterTimeout]) ->
{ok, State}.
-system_replace_state(StateFun, [Name, State, Mod, Time, HibernateAfterTimeout]) ->
+system_replace_state(StateFun, [Name, State, CbCache, Time, HibernateAfterTimeout]) ->
NState = StateFun(State),
- {ok, NState, [Name, NState, Mod, Time, HibernateAfterTimeout]}.
+ {ok, NState, [Name, NState, CbCache, Time, HibernateAfterTimeout]}.
%%-----------------------------------------------------------------
%% Format debug messages. Print them as the call-back module sees
@@ -1348,7 +1318,7 @@ terminate(Class, Reason, Stacktrace, Name, From, Msg, Mod, State, Debug) ->
-spec terminate(_, _, _, _, _, _, _, _, _, _) -> no_return().
terminate(Class, Reason, Stacktrace, ReportStacktrace, Name, From, Msg, Mod, State, Debug) ->
- Reply = try_terminate(Mod, terminate_reason(Class, Reason, Stacktrace), State),
+ Reply = try_terminate(Mod, catch_result(Class, Reason, Stacktrace), State),
case Reply of
{'EXIT', C, R, S} ->
error_info(R, S, Name, From, Msg, Mod, State, Debug),
@@ -1371,8 +1341,9 @@ terminate(Class, Reason, Stacktrace, ReportStacktrace, Name, From, Msg, Mod, Sta
erlang:raise(Class, Reason, Stacktrace)
end.
-terminate_reason(error, Reason, Stacktrace) -> {Reason, Stacktrace};
-terminate_reason(exit, Reason, _Stacktrace) -> Reason.
+%% What an old style `catch` would return
+catch_result(error, Reason, Stacktrace) -> {Reason, Stacktrace};
+catch_result(exit, Reason, _Stacktrace) -> Reason.
error_info(_Reason, _ST, application_controller, _From, _Msg, _Mod, _State, _Debug) ->
%% OTP-5811 Don't send an error report if it's the system process
@@ -1659,7 +1630,8 @@ mod(_) -> "t".
%% Status information
%%-----------------------------------------------------------------
format_status(Opt, StatusData) ->
- [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time, _HibernateAfterTimeout]] = StatusData,
+ [PDict, SysState, Parent, Debug, [Name, State, CbCache, _Time, _HibernateAfterTimeout]] = StatusData,
+ Mod = CbCache#callback_cache.module,
Header = gen:format_status_header("Status for generic server", Name),
Status =
case gen:format_status(Mod, Opt, #{ state => State, log => sys:get_log(Debug) },
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index e3fa14a8a8..849bf45561 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -97,6 +97,9 @@
start_ret/0,
start_mon_ret/0]).
+%% -define(DBG(T), erlang:display({{self(), ?MODULE, ?LINE, ?FUNCTION_NAME}, T})).
+
+
%%%==========================================================================
%%% Interface functions.
%%%==========================================================================
@@ -225,8 +228,9 @@
{ok, State :: StateType, Data :: DataType} |
{ok, State :: StateType, Data :: DataType,
Actions :: [action()] | action()} |
- 'ignore' |
- {'stop', Reason :: term()}.
+ 'ignore' |
+ {'stop', Reason :: term()} |
+ {'error', Reason :: term()}.
%% Old, not advertised
-type state_function_result() ::
@@ -641,15 +645,15 @@ call(ServerRef, Request) ->
{'dirty_timeout',T :: timeout()}) ->
Reply :: term().
call(ServerRef, Request, infinity = T = Timeout) ->
- call_dirty(ServerRef, Request, Timeout, T);
+ call(ServerRef, Request, Timeout, T);
call(ServerRef, Request, {dirty_timeout, T} = Timeout) ->
- call_dirty(ServerRef, Request, Timeout, T);
+ call(ServerRef, Request, Timeout, T);
call(ServerRef, Request, {clean_timeout, T} = Timeout) ->
- call_clean(ServerRef, Request, Timeout, T);
+ call(ServerRef, Request, Timeout, T);
call(ServerRef, Request, {_, _} = Timeout) ->
erlang:error(badarg, [ServerRef,Request,Timeout]);
call(ServerRef, Request, Timeout) ->
- call_clean(ServerRef, Request, Timeout, Timeout).
+ call(ServerRef, Request, Timeout, Timeout).
-spec send_request(ServerRef::server_ref(), Request::term()) ->
ReqId::request_id().
@@ -896,7 +900,8 @@ enter_loop(Module, Opts, State, Data, Server, Actions) ->
wrap_cast(Event) ->
{'$gen_cast',Event}.
-call_dirty(ServerRef, Request, Timeout, T) ->
+-compile({inline, [call/4]}).
+call(ServerRef, Request, Timeout, T) ->
try gen:call(ServerRef, '$gen_call', Request, T) of
{ok,Reply} ->
Reply
@@ -910,63 +915,6 @@ call_dirty(ServerRef, Request, Timeout, T) ->
Stacktrace)
end.
-call_clean(ServerRef, Request, Timeout, T)
- when (is_pid(ServerRef)
- andalso (node(ServerRef) == node()))
- orelse (element(2, ServerRef) == node()
- andalso is_atom(element(1, ServerRef))
- andalso (tuple_size(ServerRef) =:= 2)) ->
- %% No need to use a proxy locally since we know alias will be
- %% used as of OTP 24 which will prevent garbage responses...
- call_dirty(ServerRef, Request, Timeout, T);
-call_clean(ServerRef, Request, Timeout, T) ->
- %% Call server through proxy process to dodge any late reply
- %%
- %% We still need a proxy in the distributed case since we may
- %% communicate with a node that does not understand aliases.
- %% This can be removed when alias support is mandatory.
- %% Probably in OTP 26.
- Ref = make_ref(),
- Self = self(),
- Pid = spawn(
- fun () ->
- Self !
- try gen:call(
- ServerRef, '$gen_call', Request, T) of
- Result ->
- {Ref,Result}
- catch Class:Reason:Stacktrace ->
- {Ref,Class,Reason,Stacktrace}
- end
- end),
- Mref = monitor(process, Pid),
- receive
- {Ref,Result} ->
- demonitor(Mref, [flush]),
- case Result of
- {ok,Reply} ->
- Reply
- end;
- {Ref,Class,Reason,Stacktrace} when Class =:= exit ->
- %% 'gen' raises 'exit' for problems
- demonitor(Mref, [flush]),
- %% Pretend it happened in this process
- erlang:raise(
- Class,
- %% Wrap the reason according to tradition
- {Reason,{?MODULE,call,[ServerRef,Request,Timeout]}},
- Stacktrace);
- {Ref,Class,Reason,Stacktrace} ->
- demonitor(Mref, [flush]),
- %% Pretend it happened in this process
- erlang:raise(Class, Reason, Stacktrace);
- {'DOWN',Mref,_,_,Reason} ->
- %% There is a theoretical possibility that the
- %% proxy process gets killed between try--of and !
- %% so this clause is in case of that
- exit(Reason)
- end.
-
replies([{reply,From,Reply}|Replies]) ->
reply(From, Reply),
replies(Replies);
@@ -1027,12 +975,12 @@ init_it(Starter, Parent, ServerRef, Module, Args, Opts) ->
Name, Debug, HibernateAfterTimeout);
Class:Reason:Stacktrace ->
gen:unregister_name(ServerRef),
- proc_lib:init_ack(Starter, {error,Reason}),
error_info(
Class, Reason, Stacktrace, Debug,
#params{parent = Parent, name = Name, modules = [Module]},
#state{}, []),
- erlang:raise(Class, Reason, Stacktrace)
+ proc_lib:init_fail(
+ Starter, {error,Reason}, {Class,Reason,Stacktrace})
end.
%%---------------------------------------------------------------------------
@@ -1054,21 +1002,24 @@ init_result(
State, Data, Actions);
{stop,Reason} ->
gen:unregister_name(ServerRef),
- proc_lib:init_ack(Starter, {error,Reason}),
- exit(Reason);
+ exit(Reason);
+ {error, _Reason} = ERROR ->
+ %% The point of this clause is that we shall have a *silent*
+ %% termination. The error reason will be returned to the
+ %% 'Starter' ({error, Reason}), but *no* crash report.
+ gen:unregister_name(ServerRef),
+ proc_lib:init_fail(Starter, ERROR, {exit,normal});
ignore ->
gen:unregister_name(ServerRef),
- proc_lib:init_ack(Starter, ignore),
- exit(normal);
+ proc_lib:init_fail(Starter, ignore, {exit,normal});
_ ->
gen:unregister_name(ServerRef),
- Error = {bad_return_from_init,Result},
- proc_lib:init_ack(Starter, {error,Error}),
+ Reason = {bad_return_from_init,Result},
error_info(
- error, Error, ?STACKTRACE(), Debug,
+ error, Reason, ?STACKTRACE(), Debug,
#params{parent = Parent, name = Name, modules = [Module]},
#state{}, []),
- exit(Error)
+ exit(Reason)
end.
%%%==========================================================================
@@ -3074,9 +3025,6 @@ cancel_timer(TimeoutType, Timers) ->
%% Return a list of all pending timeouts
list_timeouts(Timers) ->
{maps:size(Timers) - 1, % Subtract fixed key 't0q'
- maps:fold(
- fun (t0q, _, Acc) ->
- Acc;
- (TimeoutType, {_TimerRef,TimeoutMsg}, Acc) ->
- [{TimeoutType,TimeoutMsg}|Acc]
- end, [], Timers)}.
+ [{TimeoutType, TimeoutMsg}
+ || TimeoutType := {_TimerRef, TimeoutMsg} <- Timers,
+ TimeoutType =/= t0q]}.
diff --git a/lib/stdlib/src/io.erl b/lib/stdlib/src/io.erl
index 18f6ef3dde..067177155e 100644
--- a/lib/stdlib/src/io.erl
+++ b/lib/stdlib/src/io.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -209,19 +209,22 @@ get_password(Io) ->
-type encoding() :: 'latin1' | 'unicode' | 'utf8' | 'utf16' | 'utf32'
| {'utf16', 'big' | 'little'} | {'utf32','big' | 'little'}.
--type expand_fun() :: fun((term()) -> {'yes'|'no', string(), [string(), ...]}).
+-type expand_fun() :: fun((string()) -> {'yes'|'no', string(), list()}).
-type opt_pair() :: {'binary', boolean()}
| {'echo', boolean()}
| {'expand_fun', expand_fun()}
- | {'encoding', encoding()}.
+ | {'encoding', encoding()}
+ | {atom(), term()}.
+-type get_opt_pair() :: opt_pair()
+ | {'terminal', boolean()}.
--spec getopts() -> [opt_pair()] | {'error', Reason} when
+-spec getopts() -> [get_opt_pair()] | {'error', Reason} when
Reason :: term().
getopts() ->
getopts(default_input()).
--spec getopts(IoDevice) -> [opt_pair()] | {'error', Reason} when
+-spec getopts(IoDevice) -> [get_opt_pair()] | {'error', Reason} when
IoDevice :: device(),
Reason :: term().
diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl
index e2823b70f2..5f45165968 100644
--- a/lib/stdlib/src/io_lib.erl
+++ b/lib/stdlib/src/io_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -124,7 +124,9 @@
precision := 'none' | integer(),
pad_char := char(),
encoding := 'unicode' | 'latin1',
- strings := boolean()
+ strings := boolean(),
+ % `maps_order` has been added since OTP26 and is optional
+ maps_order => maps:iterator_order()
}.
%%----------------------------------------------------------------------
@@ -322,7 +324,7 @@ add_modifier(_, C) ->
Term :: term().
write(Term) ->
- write1(Term, -1, latin1).
+ write1(Term, -1, latin1, undefined).
-spec write(term(), depth(), boolean()) -> chars().
@@ -347,64 +349,65 @@ write(Term, Options) when is_list(Options) ->
Depth = get_option(depth, Options, -1),
Encoding = get_option(encoding, Options, epp:default_encoding()),
CharsLimit = get_option(chars_limit, Options, -1),
+ MapsOrder = get_option(maps_order, Options, undefined),
if
Depth =:= 0; CharsLimit =:= 0 ->
"...";
- CharsLimit < 0 ->
- write1(Term, Depth, Encoding);
- CharsLimit > 0 ->
+ is_integer(CharsLimit), CharsLimit < 0, is_integer(Depth) ->
+ write1(Term, Depth, Encoding, MapsOrder);
+ is_integer(CharsLimit), CharsLimit > 0 ->
RecDefFun = fun(_, _) -> no end,
If = io_lib_pretty:intermediate
- (Term, Depth, CharsLimit, RecDefFun, Encoding, _Str=false),
+ (Term, Depth, CharsLimit, RecDefFun, Encoding, _Str=false, MapsOrder),
io_lib_pretty:write(If)
end;
write(Term, Depth) ->
write(Term, [{depth, Depth}, {encoding, latin1}]).
-write1(_Term, 0, _E) -> "...";
-write1(Term, _D, _E) when is_integer(Term) -> integer_to_list(Term);
-write1(Term, _D, _E) when is_float(Term) -> io_lib_format:fwrite_g(Term);
-write1(Atom, _D, latin1) when is_atom(Atom) -> write_atom_as_latin1(Atom);
-write1(Atom, _D, _E) when is_atom(Atom) -> write_atom(Atom);
-write1(Term, _D, _E) when is_port(Term) -> write_port(Term);
-write1(Term, _D, _E) when is_pid(Term) -> pid_to_list(Term);
-write1(Term, _D, _E) when is_reference(Term) -> write_ref(Term);
-write1(<<_/bitstring>>=Term, D, _E) -> write_binary(Term, D);
-write1([], _D, _E) -> "[]";
-write1({}, _D, _E) -> "{}";
-write1([H|T], D, E) ->
+write1(_Term, 0, _E, _O) -> "...";
+write1(Term, _D, _E, _O) when is_integer(Term) -> integer_to_list(Term);
+write1(Term, _D, _E, _O) when is_float(Term) -> io_lib_format:fwrite_g(Term);
+write1(Atom, _D, latin1, _O) when is_atom(Atom) -> write_atom_as_latin1(Atom);
+write1(Atom, _D, _E, _O) when is_atom(Atom) -> write_atom(Atom);
+write1(Term, _D, _E, _O) when is_port(Term) -> write_port(Term);
+write1(Term, _D, _E, _O) when is_pid(Term) -> pid_to_list(Term);
+write1(Term, _D, _E, _O) when is_reference(Term) -> write_ref(Term);
+write1(<<_/bitstring>>=Term, D, _E, _O) -> write_binary(Term, D);
+write1([], _D, _E, _O) -> "[]";
+write1({}, _D, _E, _O) -> "{}";
+write1([H|T], D, E, O) ->
if
D =:= 1 -> "[...]";
true ->
- [$[,[write1(H, D-1, E)|write_tail(T, D-1, E)],$]]
+ [$[,[write1(H, D-1, E, O)|write_tail(T, D-1, E, O)],$]]
end;
-write1(F, _D, _E) when is_function(F) ->
+write1(F, _D, _E, _O) when is_function(F) ->
erlang:fun_to_list(F);
-write1(Term, D, E) when is_map(Term) ->
- write_map(Term, D, E);
-write1(T, D, E) when is_tuple(T) ->
+write1(Term, D, E, O) when is_map(Term) ->
+ write_map(Term, D, E, O);
+write1(T, D, E, O) when is_tuple(T) ->
if
D =:= 1 -> "{...}";
true ->
[${,
- [write1(element(1, T), D-1, E)|write_tuple(T, 2, D-1, E)],
+ [write1(element(1, T), D-1, E, O)|write_tuple(T, 2, D-1, E, O)],
$}]
end.
%% write_tail(List, Depth, Encoding)
%% Test the terminating case first as this looks better with depth.
-write_tail([], _D, _E) -> "";
-write_tail(_, 1, _E) -> [$| | "..."];
-write_tail([H|T], D, E) ->
- [$,,write1(H, D-1, E)|write_tail(T, D-1, E)];
-write_tail(Other, D, E) ->
- [$|,write1(Other, D-1, E)].
+write_tail([], _D, _E, _O) -> "";
+write_tail(_, 1, _E, _O) -> [$| | "..."];
+write_tail([H|T], D, E, O) ->
+ [$,,write1(H, D-1, E, O)|write_tail(T, D-1, E, O)];
+write_tail(Other, D, E, O) ->
+ [$|,write1(Other, D-1, E, O)].
-write_tuple(T, I, _D, _E) when I > tuple_size(T) -> "";
-write_tuple(_, _I, 1, _E) -> [$, | "..."];
-write_tuple(T, I, D, E) ->
- [$,,write1(element(I, T), D-1, E)|write_tuple(T, I+1, D-1, E)].
+write_tuple(T, I, _D, _E, _O) when I > tuple_size(T) -> "";
+write_tuple(_, _I, 1, _E, _O) -> [$, | "..."];
+write_tuple(T, I, D, E, O) ->
+ [$,,write1(element(I, T), D-1, E, O)|write_tuple(T, I+1, D-1, E, O)].
write_port(Port) ->
erlang:port_to_list(Port).
@@ -412,34 +415,34 @@ write_port(Port) ->
write_ref(Ref) ->
erlang:ref_to_list(Ref).
-write_map(_, 1, _E) -> "#{}";
-write_map(Map, D, E) when is_integer(D) ->
- I = maps:iterator(Map),
+write_map(_, 1, _E, _O) -> "#{}";
+write_map(Map, D, E, O) when is_integer(D) ->
+ I = maps:iterator(Map, O),
case maps:next(I) of
{K, V, NextI} ->
D0 = D - 1,
- W = write_map_assoc(K, V, D0, E),
- [$#,${,[W | write_map_body(NextI, D0, D0, E)],$}];
+ W = write_map_assoc(K, V, D0, E, O),
+ [$#,${,[W | write_map_body(NextI, D0, D0, E, O)],$}];
none -> "#{}"
end.
-write_map_body(_, 1, _D0, _E) -> ",...";
-write_map_body(I, D, D0, E) ->
+write_map_body(_, 1, _D0, _E, _O) -> ",...";
+write_map_body(I, D, D0, E, O) ->
case maps:next(I) of
{K, V, NextI} ->
- W = write_map_assoc(K, V, D0, E),
- [$,,W|write_map_body(NextI, D - 1, D0, E)];
+ W = write_map_assoc(K, V, D0, E, O),
+ [$,,W|write_map_body(NextI, D - 1, D0, E, O)];
none -> ""
end.
-write_map_assoc(K, V, D, E) ->
- [write1(K, D, E)," => ",write1(V, D, E)].
+write_map_assoc(K, V, D, E, O) ->
+ [write1(K, D, E, O)," => ",write1(V, D, E, O)].
write_binary(B, D) when is_integer(D) ->
{S, _} = write_binary(B, D, -1),
S.
-write_binary(B, D, T) ->
+write_binary(B, D, T) when is_integer(T) ->
{S, Rest} = write_binary_body(B, D, tsub(T, 4), []),
{[$<,$<,lists:reverse(S),$>,$>], Rest}.
@@ -509,15 +512,16 @@ quote_atom(Atom, Cs0) ->
true -> true;
false ->
case Cs0 of
- [C|Cs] when C >= $a, C =< $z ->
+ [C|Cs] when is_integer(C), C >= $a, C =< $z ->
not name_chars(Cs);
- [C|Cs] when C >= $ß, C =< $ÿ, C =/= $÷ ->
+ [C|Cs] when is_integer(C), C >= $ß, C =< $ÿ, C =/= $÷ ->
not name_chars(Cs);
- _ -> true
+ [C|_] when is_integer(C) -> true;
+ [] -> true
end
end.
-name_chars([C|Cs]) ->
+name_chars([C|Cs]) when is_integer(C) ->
case name_char(C) of
true -> name_chars(Cs);
false -> false
@@ -580,7 +584,7 @@ write_string_as_latin1(S, Q) ->
write_string1(_,[], Q) ->
[Q];
-write_string1(Enc,[C|Cs], Q) ->
+write_string1(Enc,[C|Cs], Q) when is_integer(C) ->
string_char(Enc,C, Q, write_string1(Enc,Cs, Q)).
string_char(_,Q, Q, Tail) -> [$\\,Q|Tail]; %Must check these first!
@@ -803,53 +807,45 @@ collect_chars(Tag, Data, N) ->
collect_chars(Tag, Data, latin1, N).
%% Now we are aware of encoding...
-collect_chars(start, Data, unicode, N) when is_binary(Data) ->
+collect_chars(start, Data, unicode, N) when is_binary(Data), is_integer(N) ->
{Size,Npos} = count_and_find_utf8(Data,N),
- if Size > N ->
+ if Size >= N ->
{B1,B2} = split_binary(Data, Npos),
{stop,B1,B2};
Size < N ->
- {binary,[Data],N-Size};
- true ->
- {stop,Data,eof}
+ {binary,[Data],N-Size}
end;
-collect_chars(start, Data, latin1, N) when is_binary(Data) ->
+collect_chars(start, Data, latin1, N) when is_binary(Data), is_integer(N) ->
Size = byte_size(Data),
- if Size > N ->
+ if Size >= N ->
{B1,B2} = split_binary(Data, N),
{stop,B1,B2};
Size < N ->
- {binary,[Data],N-Size};
- true ->
- {stop,Data,eof}
+ {binary,[Data],N-Size}
end;
-collect_chars(start,Data,_,N) when is_list(Data) ->
+collect_chars(start,Data,_,N) when is_list(Data), is_integer(N) ->
collect_chars_list([], N, Data);
collect_chars(start, eof, _,_) ->
{stop,eof,eof};
collect_chars({binary,Stack,_N}, eof, _,_) ->
{stop,binrev(Stack),eof};
-collect_chars({binary,Stack,N}, Data,unicode, _) ->
+collect_chars({binary,Stack,N}, Data,unicode, _) when is_integer(N) ->
{Size,Npos} = count_and_find_utf8(Data,N),
- if Size > N ->
+ if Size >= N ->
{B1,B2} = split_binary(Data, Npos),
{stop,binrev(Stack, [B1]),B2};
Size < N ->
- {binary,[Data|Stack],N-Size};
- true ->
- {stop,binrev(Stack, [Data]),eof}
+ {binary,[Data|Stack],N-Size}
end;
-collect_chars({binary,Stack,N}, Data,latin1, _) ->
+collect_chars({binary,Stack,N}, Data,latin1, _) when is_integer(N) ->
Size = byte_size(Data),
- if Size > N ->
+ if Size >= N ->
{B1,B2} = split_binary(Data, N),
{stop,binrev(Stack, [B1]),B2};
Size < N ->
- {binary,[Data|Stack],N-Size};
- true ->
- {stop,binrev(Stack, [Data]),eof}
+ {binary,[Data|Stack],N-Size}
end;
-collect_chars({list,Stack,N}, Data, _,_) ->
+collect_chars({list,Stack,N}, Data, _,_) when is_integer(N) ->
collect_chars_list(Stack, N, Data);
%% collect_chars(Continuation, MoreChars, Count)
@@ -857,9 +853,9 @@ collect_chars({list,Stack,N}, Data, _,_) ->
%% {done,Result,RestChars}
%% {more,Continuation}
-collect_chars([], Chars, _, N) ->
+collect_chars([], Chars, _, N) when is_integer(N) ->
collect_chars1(N, Chars, []);
-collect_chars({Left,Sofar}, Chars, _, _N) ->
+collect_chars({Left,Sofar}, Chars, _, _N) when is_integer(Left) ->
collect_chars1(Left, Chars, Sofar).
collect_chars1(N, Chars, Stack) when N =< 0 ->
@@ -991,13 +987,13 @@ binrev(L) ->
binrev(L, T) ->
list_to_binary(lists:reverse(L, T)).
--spec limit_term(term(), non_neg_integer()) -> term().
+-spec limit_term(term(), depth()) -> term().
%% The intention is to mimic the depth limitation of io_lib:write()
%% and io_lib_pretty:print(). The leaves ('...') should never be
%% seen when printed with the same depth. Bitstrings are never
%% truncated, which is OK as long as they are not sent to other nodes.
-limit_term(Term, Depth) ->
+limit_term(Term, Depth) when is_integer(Depth), Depth >= -1 ->
try test_limit(Term, Depth) of
ok -> Term
catch
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
index fb77957036..813125abd9 100644
--- a/lib/stdlib/src/io_lib_format.erl
+++ b/lib/stdlib/src/io_lib_format.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -106,6 +106,8 @@ scan(Format, Args) ->
unscan(Cs) ->
{print(Cs), args(Cs)}.
+args([#{args := As, maps_order := O} | Cs]) when is_function(O, 2); O =:= reversed ->
+ [O | As] ++ args(Cs);
args([#{args := As} | Cs]) ->
As ++ args(Cs);
args([_C | Cs]) ->
@@ -114,17 +116,20 @@ args([]) ->
[].
print([#{control_char := C, width := F, adjust := Ad, precision := P,
- pad_char := Pad, encoding := Encoding, strings := Strings} | Cs]) ->
- print(C, F, Ad, P, Pad, Encoding, Strings) ++ print(Cs);
-print([C | Cs]) ->
+ pad_char := Pad, encoding := Encoding, strings := Strings
+ } = Map | Cs]) ->
+ MapsOrder = maps:get(maps_order, Map, undefined),
+ print(C, F, Ad, P, Pad, Encoding, Strings, MapsOrder) ++ print(Cs);
+print([C | Cs]) when is_integer(C) ->
[C | print(Cs)];
print([]) ->
[].
-print(C, F, Ad, P, Pad, Encoding, Strings) ->
+print(C, F, Ad, P, Pad, Encoding, Strings, MapsOrder) ->
[$~] ++ print_field_width(F, Ad) ++ print_precision(P, Pad) ++
print_pad_char(Pad) ++ print_encoding(Encoding) ++
- print_strings(Strings) ++ [C].
+ print_strings(Strings) ++ print_maps_order(MapsOrder) ++
+ [C].
print_field_width(none, _Ad) -> "";
print_field_width(F, left) -> integer_to_list(-F);
@@ -143,6 +148,11 @@ print_encoding(latin1) -> "".
print_strings(false) -> "l";
print_strings(true) -> "".
+print_maps_order(undefined) -> "";
+print_maps_order(ordered) -> "k";
+print_maps_order(reversed) -> "K";
+print_maps_order(CmpFun) when is_function(CmpFun, 2) -> "K".
+
collect([$~|Fmt0], Args0) ->
{C,Fmt1,Args1} = collect_cseq(Fmt0, Args0),
[C|collect(Fmt1, Args1)];
@@ -159,18 +169,23 @@ collect_cseq(Fmt0, Args0) ->
precision => P,
pad_char => Pad,
encoding => latin1,
- strings => true},
- {Spec1,Fmt4} = modifiers(Fmt3, Spec0),
- {C,As,Fmt5,Args4} = collect_cc(Fmt4, Args3),
+ strings => true,
+ maps_order => undefined},
+ {Spec1,Fmt4,Args4} = modifiers(Fmt3, Args3, Spec0),
+ {C,As,Fmt5,Args5} = collect_cc(Fmt4, Args4),
Spec2 = Spec1#{control_char => C, args => As},
- {Spec2,Fmt5,Args4}.
-
-modifiers([$t|Fmt], Spec) ->
- modifiers(Fmt, Spec#{encoding => unicode});
-modifiers([$l|Fmt], Spec) ->
- modifiers(Fmt, Spec#{strings => false});
-modifiers(Fmt, Spec) ->
- {Spec, Fmt}.
+ {Spec2,Fmt5,Args5}.
+
+modifiers([$t|Fmt], Args, Spec) ->
+ modifiers(Fmt, Args, Spec#{encoding => unicode});
+modifiers([$l|Fmt], Args, Spec) ->
+ modifiers(Fmt, Args, Spec#{strings => false});
+modifiers([$k|Fmt], Args, Spec) ->
+ modifiers(Fmt, Args, Spec#{maps_order => ordered});
+modifiers([$K|Fmt], [MapsOrder | Args], Spec) ->
+ modifiers(Fmt, Args, Spec#{maps_order => MapsOrder});
+modifiers(Fmt, Args, Spec) ->
+ {Spec, Fmt, Args}.
field_width([$-|Fmt0], Args0) ->
{F,Fmt,Args} = field_value(Fmt0, Args0),
@@ -274,12 +289,14 @@ build_small([]) -> [].
build_limited([#{control_char := C, args := As, width := F, adjust := Ad,
precision := P, pad_char := Pad, encoding := Enc,
- strings := Str} | Cs], NumOfPs0, Count0, MaxLen0, I) ->
+ strings := Str} = Map | Cs],
+ NumOfPs0, Count0, MaxLen0, I) ->
+ Ord = maps:get(maps_order, Map, undefined),
MaxChars = if
MaxLen0 < 0 -> MaxLen0;
true -> MaxLen0 div Count0
end,
- S = control_limited(C, As, F, Ad, P, Pad, Enc, Str, MaxChars, I),
+ S = control_limited(C, As, F, Ad, P, Pad, Enc, Str, Ord, MaxChars, I),
NumOfPs = decr_pc(C, NumOfPs0),
Count = Count0 - 1,
MaxLen = if
@@ -371,24 +388,34 @@ control_small($n, [], F, Adj, P, Pad, _Enc) -> newline(F, Adj, P, Pad);
control_small($i, [_A], _F, _Adj, _P, _Pad, _Enc) -> [];
control_small(_C, _As, _F, _Adj, _P, _Pad, _Enc) -> not_small.
-control_limited($s, [L0], F, Adj, P, Pad, latin1=Enc, _Str, CL, _I) ->
+control_limited($s, [L0], F, Adj, P, Pad, latin1=Enc, _Str, _Ord, CL, _I) ->
L = iolist_to_chars(L0, F, CL),
string(L, limit_field(F, CL), Adj, P, Pad, Enc);
-control_limited($s, [L0], F, Adj, P, Pad, unicode=Enc, _Str, CL, _I) ->
+control_limited($s, [L0], F, Adj, P, Pad, unicode=Enc, _Str, _Ord, CL, _I) ->
L = cdata_to_chars(L0, F, CL),
uniconv(string(L, limit_field(F, CL), Adj, P, Pad, Enc));
-control_limited($w, [A], F, Adj, P, Pad, Enc, _Str, CL, _I) ->
- Chars = io_lib:write(A, [{depth, -1}, {encoding, Enc}, {chars_limit, CL}]),
+control_limited($w, [A], F, Adj, P, Pad, Enc, _Str, Ord, CL, _I) ->
+ Chars = io_lib:write(A, [
+ {depth, -1},
+ {encoding, Enc},
+ {chars_limit, CL},
+ {maps_order, Ord}
+ ]),
term(Chars, F, Adj, P, Pad);
-control_limited($p, [A], F, Adj, P, Pad, Enc, Str, CL, I) ->
- print(A, -1, F, Adj, P, Pad, Enc, Str, CL, I);
-control_limited($W, [A,Depth], F, Adj, P, Pad, Enc, _Str, CL, _I)
+control_limited($p, [A], F, Adj, P, Pad, Enc, Str, Ord, CL, I) ->
+ print(A, -1, F, Adj, P, Pad, Enc, Str, Ord, CL, I);
+control_limited($W, [A,Depth], F, Adj, P, Pad, Enc, _Str, Ord, CL, _I)
when is_integer(Depth) ->
- Chars = io_lib:write(A, [{depth, Depth}, {encoding, Enc}, {chars_limit, CL}]),
+ Chars = io_lib:write(A, [
+ {depth, Depth},
+ {encoding, Enc},
+ {chars_limit, CL},
+ {maps_order, Ord}
+ ]),
term(Chars, F, Adj, P, Pad);
-control_limited($P, [A,Depth], F, Adj, P, Pad, Enc, Str, CL, I)
+control_limited($P, [A,Depth], F, Adj, P, Pad, Enc, Str, Ord, CL, I)
when is_integer(Depth) ->
- print(A, Depth, F, Adj, P, Pad, Enc, Str, CL, I).
+ print(A, Depth, F, Adj, P, Pad, Enc, Str, Ord, CL, I).
-ifdef(UNICODE_AS_BINARIES).
uniconv(C) ->
@@ -425,17 +452,18 @@ term(T, F, Adj, P0, Pad) ->
%% Print a term. Field width sets maximum line length, Precision sets
%% initial indentation.
-print(T, D, none, Adj, P, Pad, E, Str, ChLim, I) ->
- print(T, D, 80, Adj, P, Pad, E, Str, ChLim, I);
-print(T, D, F, Adj, none, Pad, E, Str, ChLim, I) ->
- print(T, D, F, Adj, I+1, Pad, E, Str, ChLim, I);
-print(T, D, F, right, P, _Pad, Enc, Str, ChLim, _I) ->
+print(T, D, none, Adj, P, Pad, E, Str, Ord, ChLim, I) ->
+ print(T, D, 80, Adj, P, Pad, E, Str, Ord, ChLim, I);
+print(T, D, F, Adj, none, Pad, E, Str, Ord, ChLim, I) ->
+ print(T, D, F, Adj, I+1, Pad, E, Str, Ord, ChLim, I);
+print(T, D, F, right, P, _Pad, Enc, Str, Ord, ChLim, _I) ->
Options = [{chars_limit, ChLim},
{column, P},
{line_length, F},
{depth, D},
{encoding, Enc},
- {strings, Str}],
+ {strings, Str},
+ {maps_order, Ord}],
io_lib_pretty:print(T, Options).
%% fwrite_e(Float, Field, Adjust, Precision, PadChar)
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index 98eea64b0e..4ed42d6c9f 100644
--- a/lib/stdlib/src/io_lib_pretty.erl
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@
-export([print/1,print/2,print/3,print/4,print/5,print/6]).
%% To be used by io_lib only.
--export([intermediate/6, write/1]).
+-export([intermediate/7, write/1]).
%%%
%%% Exported functions
@@ -64,7 +64,8 @@ print(Term) ->
| {'line_length', line_length()}
| {'line_max_chars', line_max_chars()}
| {'record_print_fun', rec_print_fun()}
- | {'strings', boolean()}.
+ | {'strings', boolean()}
+ | {'maps_order', maps:iterator_order()}.
-type options() :: [option()].
-spec print(term(), rec_print_fun()) -> chars();
@@ -79,7 +80,8 @@ print(Term, Options) when is_list(Options) ->
RecDefFun = get_option(record_print_fun, Options, no_fun),
Encoding = get_option(encoding, Options, epp:default_encoding()),
Strings = get_option(strings, Options, true),
- print(Term, Col, Ll, D, M, T, RecDefFun, Encoding, Strings);
+ MapsOrder = get_option(maps_order, Options, undefined),
+ print(Term, Col, Ll, D, M, T, RecDefFun, Encoding, Strings, MapsOrder);
print(Term, RecDefFun) ->
print(Term, -1, RecDefFun).
@@ -91,7 +93,7 @@ print(Term, Depth, RecDefFun) ->
-spec print(term(), column(), line_length(), depth()) -> chars().
print(Term, Col, Ll, D) ->
- print(Term, Col, Ll, D, _M=-1, _T=-1, no_fun, latin1, true).
+ print(Term, Col, Ll, D, _M=-1, _T=-1, no_fun, latin1, true, undefined).
-spec print(term(), column(), line_length(), depth(), rec_print_fun()) ->
chars().
@@ -102,7 +104,7 @@ print(Term, Col, Ll, D, RecDefFun) ->
rec_print_fun()) -> chars().
print(Term, Col, Ll, D, M, RecDefFun) ->
- print(Term, Col, Ll, D, M, _T=-1, RecDefFun, latin1, true).
+ print(Term, Col, Ll, D, M, _T=-1, RecDefFun, latin1, true, undefined).
%% D = Depth, default -1 (infinite), or LINEMAX=30 when printing from shell
%% T = chars_limit, that is, maximal number of characters, default -1
@@ -111,22 +113,22 @@ print(Term, Col, Ll, D, M, RecDefFun) ->
%% Col = current column, default 1
%% Ll = line length/~p field width, default 80
%% M = CHAR_MAX (-1 if no max, 60 when printing from shell)
-print(_, _, _, 0, _M, _T, _RF, _Enc, _Str) -> "...";
-print(_, _, _, _D, _M, 0, _RF, _Enc, _Str) -> "...";
-print(Term, Col, Ll, D, M, T, RecDefFun, Enc, Str) when Col =< 0 ->
+print(_, _, _, 0, _M, _T, _RF, _Enc, _Str, _Ord) -> "...";
+print(_, _, _, _D, _M, 0, _RF, _Enc, _Str, _Ord) -> "...";
+print(Term, Col, Ll, D, M, T, RecDefFun, Enc, Str, Ord) when Col =< 0 ->
%% ensure Col is at least 1
- print(Term, 1, Ll, D, M, T, RecDefFun, Enc, Str);
-print(Atom, _Col, _Ll, _D, _M, _T, _RF, Enc, _Str) when is_atom(Atom) ->
+ print(Term, 1, Ll, D, M, T, RecDefFun, Enc, Str, Ord);
+print(Atom, _Col, _Ll, _D, _M, _T, _RF, Enc, _Str, _Ord) when is_atom(Atom) ->
write_atom(Atom, Enc);
-print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str) when is_tuple(Term);
+print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str, Ord) when is_tuple(Term);
is_list(Term);
is_map(Term);
is_bitstring(Term) ->
%% preprocess and compute total number of chars
{_, Len, _Dots, _} = If =
case T < 0 of
- true -> print_length(Term, D, T, RecDefFun, Enc, Str);
- false -> intermediate(Term, D, T, RecDefFun, Enc, Str)
+ true -> print_length(Term, D, T, RecDefFun, Enc, Str, Ord);
+ false -> intermediate(Term, D, T, RecDefFun, Enc, Str, Ord)
end,
%% use Len as CHAR_MAX if M0 = -1
M = max_cs(M0, Len),
@@ -143,7 +145,7 @@ print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str) when is_tuple(Term);
1),
pp(If, Col, Ll, M, TInd, indent(Col), 0, 0)
end;
-print(Term, _Col, _Ll, _D, _M, _T, _RF, _Enc, _Str) ->
+print(Term, _Col, _Ll, _D, _M, _T, _RF, _Enc, _Str, _Ord) ->
%% atomic data types (bignums, atoms, ...) are never truncated
io_lib:write(Term).
@@ -442,19 +444,19 @@ write_tail(E, S) ->
}.
-spec intermediate(term(), depth(), pos_integer(), rec_print_fun(),
- encoding(), boolean()) -> intermediate_format().
+ encoding(), boolean(), boolean()) -> intermediate_format().
-intermediate(Term, D, T, RF, Enc, Str) when T > 0 ->
+intermediate(Term, D, T, RF, Enc, Str, Ord) when T > 0 ->
D0 = 1,
- If = print_length(Term, D0, T, RF, Enc, Str),
+ If = print_length(Term, D0, T, RF, Enc, Str, Ord),
case If of
{_, Len, Dots, _} when Dots =:= 0; Len > T; D =:= 1 ->
If;
{_, Len, _, _} ->
- find_upper(If, Term, T, D0, 2, D, RF, Enc, Str, Len)
+ find_upper(If, Term, T, D0, 2, D, RF, Enc, Str, Ord, Len)
end.
-find_upper(Lower, Term, T, Dl, Dd, D, RF, Enc, Str, LastLen) ->
+find_upper(Lower, Term, T, Dl, Dd, D, RF, Enc, Str, Ord, LastLen) ->
Dd2 = Dd * 2,
D1 = case D < 0 of
true -> Dl + Dd2;
@@ -468,14 +470,14 @@ find_upper(Lower, Term, T, Dl, Dd, D, RF, Enc, Str, LastLen) ->
%% Cannot happen if print_length() is free of bugs.
If;
{_, Len, _, _} when Len =< T, D1 < D orelse D < 0 ->
- find_upper(If, Term, T, D1, Dd2, D, RF, Enc, Str, Len);
+ find_upper(If, Term, T, D1, Dd2, D, RF, Enc, Str, Ord, Len);
_ ->
- search_depth(Lower, If, Term, T, Dl, D1, RF, Enc, Str)
+ search_depth(Lower, If, Term, T, Dl, D1, RF, Enc, Str, Ord)
end.
%% Lower has NumOfDots > 0 and Len =< T.
%% Upper has NumOfDots > 0 and Len > T.
-search_depth(Lower, Upper, _Term, T, Dl, Du, _RF, _Enc, _Str)
+search_depth(Lower, Upper, _Term, T, Dl, Du, _RF, _Enc, _Str, _Ord)
when Du - Dl =:= 1 ->
%% The returned intermediate format has Len >= T.
case Lower of
@@ -484,7 +486,7 @@ search_depth(Lower, Upper, _Term, T, Dl, Du, _RF, _Enc, _Str)
_ ->
Upper
end;
-search_depth(Lower, Upper, Term, T, Dl, Du, RF, Enc, Str) ->
+search_depth(Lower, Upper, Term, T, Dl, Du, RF, Enc, Str, Ord) ->
D1 = (Dl + Du) div 2,
If = expand(Lower, T, D1 - Dl),
case If of
@@ -493,9 +495,9 @@ search_depth(Lower, Upper, Term, T, Dl, Du, RF, Enc, Str) ->
%% This is a bit expensive since the work to
%% crate Upper is wasted. It is the price
%% to pay to get a more balanced output.
- search_depth(Lower, If, Term, T, Dl, D1, RF, Enc, Str);
+ search_depth(Lower, If, Term, T, Dl, D1, RF, Enc, Str, Ord);
_ ->
- search_depth(If, Upper, Term, T, D1, Du, RF, Enc, Str)
+ search_depth(If, Upper, Term, T, D1, Du, RF, Enc, Str, Ord)
end.
%% The depth (D) is used for extracting and counting the characters to
@@ -504,16 +506,16 @@ search_depth(Lower, Upper, Term, T, Dl, Du, RF, Enc, Str) ->
%% counted but need to be added later.
%% D =/= 0
-print_length([], _D, _T, _RF, _Enc, _Str) ->
+print_length([], _D, _T, _RF, _Enc, _Str, _Ord) ->
{"[]", 2, 0, no_more};
-print_length({}, _D, _T, _RF, _Enc, _Str) ->
+print_length({}, _D, _T, _RF, _Enc, _Str, _Ord) ->
{"{}", 2, 0, no_more};
-print_length(#{}=M, _D, _T, _RF, _Enc, _Str) when map_size(M) =:= 0 ->
+print_length(#{}=M, _D, _T, _RF, _Enc, _Str, _Ord) when map_size(M) =:= 0 ->
{"#{}", 3, 0, no_more};
-print_length(Atom, _D, _T, _RF, Enc, _Str) when is_atom(Atom) ->
+print_length(Atom, _D, _T, _RF, Enc, _Str, _Ord) when is_atom(Atom) ->
S = write_atom(Atom, Enc),
{S, io_lib:chars_length(S), 0, no_more};
-print_length(List, D, T, RF, Enc, Str) when is_list(List) ->
+print_length(List, D, T, RF, Enc, Str, Ord) when is_list(List) ->
%% only flat lists are "printable"
case Str andalso printable_list(List, D, T, Enc) of
true ->
@@ -527,37 +529,37 @@ print_length(List, D, T, RF, Enc, Str) when is_list(List) ->
%% does not make Prefix longer.
{[S | "..."], 3 + io_lib:chars_length(S), 0, no_more};
false ->
- case print_length_list(List, D, T, RF, Enc, Str) of
+ case print_length_list(List, D, T, RF, Enc, Str, Ord) of
{What, Len, Dots, _More} when Dots > 0 ->
More = fun(T1, Dd) ->
- ?FUNCTION_NAME(List, D+Dd, T1, RF, Enc, Str)
+ ?FUNCTION_NAME(List, D+Dd, T1, RF, Enc, Str, Ord)
end,
{What, Len, Dots, More};
If ->
If
end
end;
-print_length(Fun, _D, _T, _RF, _Enc, _Str) when is_function(Fun) ->
+print_length(Fun, _D, _T, _RF, _Enc, _Str, _Ord) when is_function(Fun) ->
S = io_lib:write(Fun),
{S, iolist_size(S), 0, no_more};
-print_length(R, D, T, RF, Enc, Str) when is_atom(element(1, R)),
- is_function(RF) ->
+print_length(R, D, T, RF, Enc, Str, Ord) when is_atom(element(1, R)),
+ is_function(RF) ->
case RF(element(1, R), tuple_size(R) - 1) of
no ->
- print_length_tuple(R, D, T, RF, Enc, Str);
+ print_length_tuple(R, D, T, RF, Enc, Str, Ord);
RDefs ->
- print_length_record(R, D, T, RF, RDefs, Enc, Str)
+ print_length_record(R, D, T, RF, RDefs, Enc, Str, Ord)
end;
-print_length(Tuple, D, T, RF, Enc, Str) when is_tuple(Tuple) ->
- print_length_tuple(Tuple, D, T, RF, Enc, Str);
-print_length(Map, D, T, RF, Enc, Str) when is_map(Map) ->
- print_length_map(Map, D, T, RF, Enc, Str);
-print_length(<<>>, _D, _T, _RF, _Enc, _Str) ->
+print_length(Tuple, D, T, RF, Enc, Str, Ord) when is_tuple(Tuple) ->
+ print_length_tuple(Tuple, D, T, RF, Enc, Str, Ord);
+print_length(Map, D, T, RF, Enc, Str, Ord) when is_map(Map) ->
+ print_length_map(Map, D, T, RF, Enc, Str, Ord);
+print_length(<<>>, _D, _T, _RF, _Enc, _Str, _Ord) ->
{"<<>>", 4, 0, no_more};
-print_length(<<_/bitstring>> = Bin, 1, _T, RF, Enc, Str) ->
- More = fun(T1, Dd) -> ?FUNCTION_NAME(Bin, 1+Dd, T1, RF, Enc, Str) end,
+print_length(<<_/bitstring>> = Bin, 1, _T, RF, Enc, Str, Ord) ->
+ More = fun(T1, Dd) -> ?FUNCTION_NAME(Bin, 1+Dd, T1, RF, Enc, Str, Ord) end,
{"<<...>>", 7, 3, More};
-print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
+print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str, Ord) ->
D1 = D - 1,
case
Str andalso
@@ -573,13 +575,13 @@ print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
{true, true, Prefix} ->
S = io_lib:write_string(Prefix, $"), %"
More = fun(T1, Dd) ->
- ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str)
+ ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
end,
{[$<,$<,S|"...>>"], 7 + length(S), 3, More};
{false, true, Prefix} ->
S = io_lib:write_string(Prefix, $"), %"
More = fun(T1, Dd) ->
- ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str)
+ ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
end,
{[$<,$<,S|"/utf8...>>"], 12 + io_lib:chars_length(S), 3, More};
false ->
@@ -588,135 +590,135 @@ print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
{{bin, S}, iolist_size(S), 0, no_more};
{S, _Rest} ->
More = fun(T1, Dd) ->
- ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str)
+ ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
end,
{{bin, S}, iolist_size(S), 3, More}
end
end;
-print_length(Term, _D, _T, _RF, _Enc, _Str) ->
+print_length(Term, _D, _T, _RF, _Enc, _Str, _Ord) ->
S = io_lib:write(Term),
%% S can contain unicode, so iolist_size(S) cannot be used here
{S, io_lib:chars_length(S), 0, no_more}.
-print_length_map(Map, 1, _T, RF, Enc, Str) ->
- More = fun(T1, Dd) -> ?FUNCTION_NAME(Map, 1+Dd, T1, RF, Enc, Str) end,
+print_length_map(Map, 1, _T, RF, Enc, Str, Ord) ->
+ More = fun(T1, Dd) -> ?FUNCTION_NAME(Map, 1+Dd, T1, RF, Enc, Str, Ord) end,
{"#{...}", 6, 3, More};
-print_length_map(Map, D, T, RF, Enc, Str) when is_map(Map) ->
- Next = maps:next(maps:iterator(Map)),
- PairsS = print_length_map_pairs(Next, D, D - 1, tsub(T, 3), RF, Enc, Str),
+print_length_map(Map, D, T, RF, Enc, Str, Ord) when is_map(Map) ->
+ Next = maps:next(maps:iterator(Map, Ord)),
+ PairsS = print_length_map_pairs(Next, D, D - 1, tsub(T, 3), RF, Enc, Str, Ord),
{Len, Dots} = list_length(PairsS, 3, 0),
{{map, PairsS}, Len, Dots, no_more}.
-print_length_map_pairs(none, _D, _D0, _T, _RF, _Enc, _Str) ->
+print_length_map_pairs(none, _D, _D0, _T, _RF, _Enc, _Str, _Ord) ->
[];
-print_length_map_pairs(Term, D, D0, T, RF, Enc, Str) when D =:= 1; T =:= 0->
+print_length_map_pairs(Term, D, D0, T, RF, Enc, Str, Ord) when D =:= 1; T =:= 0->
More = fun(T1, Dd) ->
- ?FUNCTION_NAME(Term, D+Dd, D0, T1, RF, Enc, Str)
+ ?FUNCTION_NAME(Term, D+Dd, D0, T1, RF, Enc, Str, Ord)
end,
{dots, 3, 3, More};
-print_length_map_pairs({K, V, Iter}, D, D0, T, RF, Enc, Str) ->
+print_length_map_pairs({K, V, Iter}, D, D0, T, RF, Enc, Str, Ord) ->
Next = maps:next(Iter),
T1 = case Next =:= none of
false -> tsub(T, 1);
true -> T
end,
- Pair1 = print_length_map_pair(K, V, D0, T1, RF, Enc, Str),
+ Pair1 = print_length_map_pair(K, V, D0, T1, RF, Enc, Str, Ord),
{_, Len1, _, _} = Pair1,
[Pair1 |
- print_length_map_pairs(Next, D - 1, D0, tsub(T1, Len1), RF, Enc, Str)].
+ print_length_map_pairs(Next, D - 1, D0, tsub(T1, Len1), RF, Enc, Str, Ord)].
-print_length_map_pair(K, V, D, T, RF, Enc, Str) ->
- {_, KL, KD, _} = P1 = print_length(K, D, T, RF, Enc, Str),
+print_length_map_pair(K, V, D, T, RF, Enc, Str, Ord) ->
+ {_, KL, KD, _} = P1 = print_length(K, D, T, RF, Enc, Str, Ord),
KL1 = KL + 4,
- {_, VL, VD, _} = P2 = print_length(V, D, tsub(T, KL1), RF, Enc, Str),
+ {_, VL, VD, _} = P2 = print_length(V, D, tsub(T, KL1), RF, Enc, Str, Ord),
{{map_pair, P1, P2}, KL1 + VL, KD + VD, no_more}.
-print_length_tuple(Tuple, 1, _T, RF, Enc, Str) ->
- More = fun(T1, Dd) -> ?FUNCTION_NAME(Tuple, 1+Dd, T1, RF, Enc, Str) end,
+print_length_tuple(Tuple, 1, _T, RF, Enc, Str, Ord) ->
+ More = fun(T1, Dd) -> ?FUNCTION_NAME(Tuple, 1+Dd, T1, RF, Enc, Str, Ord) end,
{"{...}", 5, 3, More};
-print_length_tuple(Tuple, D, T, RF, Enc, Str) ->
- L = print_length_tuple1(Tuple, 1, D, tsub(T, 2), RF, Enc, Str),
+print_length_tuple(Tuple, D, T, RF, Enc, Str, Ord) ->
+ L = print_length_tuple1(Tuple, 1, D, tsub(T, 2), RF, Enc, Str, Ord),
IsTagged = is_atom(element(1, Tuple)) and (tuple_size(Tuple) > 1),
{Len, Dots} = list_length(L, 2, 0),
{{tuple,IsTagged,L}, Len, Dots, no_more}.
-print_length_tuple1(Tuple, I, _D, _T, _RF, _Enc, _Str)
+print_length_tuple1(Tuple, I, _D, _T, _RF, _Enc, _Str, _Ord)
when I > tuple_size(Tuple) ->
[];
-print_length_tuple1(Tuple, I, D, T, RF, Enc, Str) when D =:= 1; T =:= 0->
- More = fun(T1, Dd) -> ?FUNCTION_NAME(Tuple, I, D+Dd, T1, RF, Enc, Str) end,
+print_length_tuple1(Tuple, I, D, T, RF, Enc, Str, Ord) when D =:= 1; T =:= 0->
+ More = fun(T1, Dd) -> ?FUNCTION_NAME(Tuple, I, D+Dd, T1, RF, Enc, Str, Ord) end,
{dots, 3, 3, More};
-print_length_tuple1(Tuple, I, D, T, RF, Enc, Str) ->
+print_length_tuple1(Tuple, I, D, T, RF, Enc, Str, Ord) ->
E = element(I, Tuple),
T1 = case I =:= tuple_size(Tuple) of
false -> tsub(T, 1);
true -> T
end,
- {_, Len1, _, _} = Elem1 = print_length(E, D - 1, T1, RF, Enc, Str),
+ {_, Len1, _, _} = Elem1 = print_length(E, D - 1, T1, RF, Enc, Str, Ord),
T2 = tsub(T1, Len1),
- [Elem1 | print_length_tuple1(Tuple, I + 1, D - 1, T2, RF, Enc, Str)].
+ [Elem1 | print_length_tuple1(Tuple, I + 1, D - 1, T2, RF, Enc, Str, Ord)].
-print_length_record(Tuple, 1, _T, RF, RDefs, Enc, Str) ->
+print_length_record(Tuple, 1, _T, RF, RDefs, Enc, Str, Ord) ->
More = fun(T1, Dd) ->
- ?FUNCTION_NAME(Tuple, 1+Dd, T1, RF, RDefs, Enc, Str)
+ ?FUNCTION_NAME(Tuple, 1+Dd, T1, RF, RDefs, Enc, Str, Ord)
end,
{"{...}", 5, 3, More};
-print_length_record(Tuple, D, T, RF, RDefs, Enc, Str) ->
+print_length_record(Tuple, D, T, RF, RDefs, Enc, Str, Ord) ->
Name = [$# | write_atom(element(1, Tuple), Enc)],
NameL = io_lib:chars_length(Name),
T1 = tsub(T, NameL+2),
- L = print_length_fields(RDefs, D - 1, T1, Tuple, 2, RF, Enc, Str),
+ L = print_length_fields(RDefs, D - 1, T1, Tuple, 2, RF, Enc, Str, Ord),
{Len, Dots} = list_length(L, NameL + 2, 0),
{{record, [{Name,NameL} | L]}, Len, Dots, no_more}.
-print_length_fields([], _D, _T, Tuple, I, _RF, _Enc, _Str)
+print_length_fields([], _D, _T, Tuple, I, _RF, _Enc, _Str, _Ord)
when I > tuple_size(Tuple) ->
[];
-print_length_fields(Term, D, T, Tuple, I, RF, Enc, Str)
+print_length_fields(Term, D, T, Tuple, I, RF, Enc, Str, Ord)
when D =:= 1; T =:= 0 ->
More = fun(T1, Dd) ->
- ?FUNCTION_NAME(Term, D+Dd, T1, Tuple, I, RF, Enc, Str)
+ ?FUNCTION_NAME(Term, D+Dd, T1, Tuple, I, RF, Enc, Str, Ord)
end,
{dots, 3, 3, More};
-print_length_fields([Def | Defs], D, T, Tuple, I, RF, Enc, Str) ->
+print_length_fields([Def | Defs], D, T, Tuple, I, RF, Enc, Str, Ord) ->
E = element(I, Tuple),
T1 = case I =:= tuple_size(Tuple) of
false -> tsub(T, 1);
true -> T
end,
- Field1 = print_length_field(Def, D - 1, T1, E, RF, Enc, Str),
+ Field1 = print_length_field(Def, D - 1, T1, E, RF, Enc, Str, Ord),
{_, Len1, _, _} = Field1,
T2 = tsub(T1, Len1),
[Field1 |
- print_length_fields(Defs, D - 1, T2, Tuple, I + 1, RF, Enc, Str)].
+ print_length_fields(Defs, D - 1, T2, Tuple, I + 1, RF, Enc, Str, Ord)].
-print_length_field(Def, D, T, E, RF, Enc, Str) ->
+print_length_field(Def, D, T, E, RF, Enc, Str, Ord) ->
Name = write_atom(Def, Enc),
NameL = io_lib:chars_length(Name) + 3,
{_, Len, Dots, _} =
- Field = print_length(E, D, tsub(T, NameL), RF, Enc, Str),
+ Field = print_length(E, D, tsub(T, NameL), RF, Enc, Str, Ord),
{{field, Name, NameL, Field}, NameL + Len, Dots, no_more}.
-print_length_list(List, D, T, RF, Enc, Str) ->
- L = print_length_list1(List, D, tsub(T, 2), RF, Enc, Str),
+print_length_list(List, D, T, RF, Enc, Str, Ord) ->
+ L = print_length_list1(List, D, tsub(T, 2), RF, Enc, Str, Ord),
{Len, Dots} = list_length(L, 2, 0),
{{list, L}, Len, Dots, no_more}.
-print_length_list1([], _D, _T, _RF, _Enc, _Str) ->
+print_length_list1([], _D, _T, _RF, _Enc, _Str, _Ord) ->
[];
-print_length_list1(Term, D, T, RF, Enc, Str) when D =:= 1; T =:= 0->
- More = fun(T1, Dd) -> ?FUNCTION_NAME(Term, D+Dd, T1, RF, Enc, Str) end,
+print_length_list1(Term, D, T, RF, Enc, Str, Ord) when D =:= 1; T =:= 0->
+ More = fun(T1, Dd) -> ?FUNCTION_NAME(Term, D+Dd, T1, RF, Enc, Str, Ord) end,
{dots, 3, 3, More};
-print_length_list1([E | Es], D, T, RF, Enc, Str) ->
+print_length_list1([E | Es], D, T, RF, Enc, Str, Ord) ->
%% If E is the last element in list, don't account length for a comma.
T1 = case Es =:= [] of
false -> tsub(T, 1);
true -> T
end,
- {_, Len1, _, _} = Elem1 = print_length(E, D - 1, T1, RF, Enc, Str),
- [Elem1 | print_length_list1(Es, D - 1, tsub(T1, Len1), RF, Enc, Str)];
-print_length_list1(E, D, T, RF, Enc, Str) ->
- print_length(E, D - 1, T, RF, Enc, Str).
+ {_, Len1, _, _} = Elem1 = print_length(E, D - 1, T1, RF, Enc, Str, Ord),
+ [Elem1 | print_length_list1(Es, D - 1, tsub(T1, Len1), RF, Enc, Str, Ord)];
+print_length_list1(E, D, T, RF, Enc, Str, Ord) ->
+ print_length(E, D - 1, T, RF, Enc, Str, Ord).
list_length([], Acc, DotsAcc) ->
{Acc, DotsAcc};
diff --git a/lib/stdlib/src/lists.erl b/lib/stdlib/src/lists.erl
index d2cb5aab3c..cb1c008ed1 100644
--- a/lib/stdlib/src/lists.erl
+++ b/lib/stdlib/src/lists.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
%% arguments. Please keep in alphabetical order.
-export([append/1, append/2, concat/1,
delete/2, droplast/1, duplicate/2,
- enumerate/1, enumerate/2,
+ enumerate/1, enumerate/2, enumerate/3,
flatlength/1, flatten/1, flatten/2,
join/2, last/1, min/1, max/1,
nth/2, nthtail/2,
@@ -37,7 +37,7 @@
split/2, sublist/2, sublist/3,
subtract/2, suffix/2, sum/1,
uniq/1, unzip/1, unzip3/1,
- zip/2, zip3/3]).
+ zip/2, zip/3, zip3/3, zip3/4]).
%% Functions taking a list of tuples and a position within the tuple.
-export([keydelete/3, keyreplace/4, keymap/3,
@@ -60,7 +60,7 @@
map/2, mapfoldl/3, mapfoldr/3,
partition/2, search/2,
splitwith/2, takewhile/2, uniq/2,
- zipwith/3, zipwith3/4]).
+ zipwith/3, zipwith/4, zipwith3/4, zipwith3/5]).
%% Undocumented, but used within Erlang/OTP.
-export([zf/2]).
@@ -196,8 +196,12 @@ reverse([A, B | L]) ->
T :: term().
nth(1, [H|_]) -> H;
-nth(N, [_|T]) when N > 1 ->
- nth(N - 1, T).
+nth(N, [_|_]=L) when is_integer(N), N > 1 ->
+ nth_1(N, L).
+
+nth_1(1, [H|_]) -> H;
+nth_1(N, [_|T]) ->
+ nth_1(N - 1, T).
-spec nthtail(N, List) -> Tail when
N :: non_neg_integer(),
@@ -205,10 +209,15 @@ nth(N, [_|T]) when N > 1 ->
Tail :: [T],
T :: term().
+nthtail(0, []) -> [];
+nthtail(0, [_|_]=L) -> L;
nthtail(1, [_|T]) -> T;
-nthtail(N, [_|T]) when N > 1 ->
- nthtail(N - 1, T);
-nthtail(0, L) when is_list(L) -> L.
+nthtail(N, [_|_]=L) when is_integer(N), N > 1 ->
+ nthtail_1(N, L).
+
+nthtail_1(1, [_|T]) -> T;
+nthtail_1(N, [_|T]) ->
+ nthtail_1(N - 1, T).
%% prefix(Prefix, List) -> (true | false)
@@ -416,8 +425,35 @@ delete(_, []) -> [].
A :: term(),
B :: term().
-zip([X | Xs], [Y | Ys]) -> [{X, Y} | zip(Xs, Ys)];
-zip([], []) -> [].
+zip(Xs, Ys) -> zip(Xs, Ys, fail).
+
+-spec zip(List1, List2, How) -> List3 when
+ List1 :: [A],
+ List2 :: [B],
+ List3 :: [{A | DefaultA, B | DefaultB}],
+ A :: term(),
+ B :: term(),
+ How :: 'fail' | 'trim' | {'pad', {DefaultA, DefaultB}},
+ DefaultA :: term(),
+ DefaultB :: term().
+
+zip([X | Xs], [Y | Ys], How) ->
+ [{X, Y} | zip(Xs, Ys, How)];
+zip([], [], fail) ->
+ [];
+zip([], [], trim) ->
+ [];
+zip([], [], {pad, {_, _}}) ->
+ [];
+zip([_ | _], [], trim) ->
+ [];
+zip([], [_ | _], trim) ->
+ [];
+zip([], [_ | _]=Ys, {pad, {X, _}}) ->
+ [{X, Y} || Y <- Ys];
+zip([_ | _]=Xs, [], {pad, {_, Y}}) ->
+ [{X, Y} || X <- Xs].
+
%% Return {[X0, X1, ..., Xn], [Y0, Y1, ..., Yn]}, for a list [{X0, Y0},
%% {X1, Y1}, ..., {Xn, Yn}].
@@ -446,8 +482,43 @@ unzip([], Xs, Ys) -> {reverse(Xs), reverse(Ys)}.
B :: term(),
C :: term().
-zip3([X | Xs], [Y | Ys], [Z | Zs]) -> [{X, Y, Z} | zip3(Xs, Ys, Zs)];
-zip3([], [], []) -> [].
+zip3(Xs, Ys, Zs) -> zip3(Xs, Ys, Zs, fail).
+
+-spec zip3(List1, List2, List3, How) -> List4 when
+ List1 :: [A],
+ List2 :: [B],
+ List3 :: [C],
+ List4 :: [{A | DefaultA, B | DefaultB, C | DefaultC}],
+ A :: term(),
+ B :: term(),
+ C :: term(),
+ How :: 'fail' | 'trim' | {'pad', {DefaultA, DefaultB, DefaultC}},
+ DefaultA :: term(),
+ DefaultB :: term(),
+ DefaultC :: term().
+
+zip3([X | Xs], [Y | Ys], [Z | Zs], How) ->
+ [{X, Y, Z} | zip3(Xs, Ys, Zs, How)];
+zip3([], [], [], fail) ->
+ [];
+zip3([], [], [], trim) ->
+ [];
+zip3(Xs, Ys, Zs, trim) when is_list(Xs), is_list(Ys), is_list(Zs) ->
+ [];
+zip3([], [], [], {pad, {_, _, _}}) ->
+ [];
+zip3([], [], [_ |_]=Zs, {pad, {X, Y, _}}) ->
+ [{X, Y, Z} || Z <- Zs];
+zip3([], [_ | _]=Ys, [], {pad, {X, _, Z}}) ->
+ [{X, Y, Z} || Y <- Ys];
+zip3([_ | _]=Xs, [], [], {pad, {_, Y, Z}}) ->
+ [{X, Y, Z} || X <- Xs];
+zip3([], [Y | Ys], [Z | Zs], {pad, {X, _, _}} = How) ->
+ [{X, Y, Z} | zip3([], Ys, Zs, How)];
+zip3([X | Xs], [], [Z | Zs], {pad, {_, Y, _}} = How) ->
+ [{X, Y, Z} | zip3(Xs, [], Zs, How)];
+zip3([X | Xs], [Y | Ys], [], {pad, {_, _, Z}} = How) ->
+ [{X, Y, Z} | zip3(Xs, Ys, [], How)].
%% Return {[X0, X1, ..., Xn], [Y0, Y1, ..., Yn], [Z0, Z1, ..., Zn]}, for
%% a list [{X0, Y0, Z0}, {X1, Y1, Z1}, ..., {Xn, Yn, Zn}].
@@ -480,8 +551,36 @@ unzip3([], Xs, Ys, Zs) ->
Y :: term(),
T :: term().
-zipwith(F, [X | Xs], [Y | Ys]) -> [F(X, Y) | zipwith(F, Xs, Ys)];
-zipwith(F, [], []) when is_function(F, 2) -> [].
+zipwith(F, Xs, Ys) -> zipwith(F, Xs, Ys, fail).
+
+-spec zipwith(Combine, List1, List2, How) -> List3 when
+ Combine :: fun((X | DefaultX, Y | DefaultY) -> T),
+ List1 :: [X],
+ List2 :: [Y],
+ List3 :: [T],
+ X :: term(),
+ Y :: term(),
+ How :: 'fail' | 'trim' | {'pad', {DefaultX, DefaultY}},
+ DefaultX :: term(),
+ DefaultY :: term(),
+ T :: term().
+
+zipwith(F, [X | Xs], [Y | Ys], How) ->
+ [F(X, Y) | zipwith(F, Xs, Ys, How)];
+zipwith(F, [], [], fail) when is_function(F, 2) ->
+ [];
+zipwith(F, [], [], trim) when is_function(F, 2) ->
+ [];
+zipwith(F, [], [], {pad, {_, _}}) when is_function(F, 2) ->
+ [];
+zipwith(F, [_ | _], [], trim) when is_function(F, 2) ->
+ [];
+zipwith(F, [], [_ | _], trim) when is_function(F, 2) ->
+ [];
+zipwith(F, [], [_ | _]=Ys, {pad, {X, _}}) ->
+ [F(X, Y) || Y <- Ys];
+zipwith(F, [_ | _]=Xs, [], {pad, {_, Y}}) ->
+ [F(X, Y) || X <- Xs].
%% Return [F(X0, Y0, Z0), F(X1, Y1, Z1), ..., F(Xn, Yn, Zn)] for lists
%% [X0, X1, ..., Xn], [Y0, Y1, ..., Yn] and [Z0, Z1, ..., Zn].
@@ -497,9 +596,45 @@ zipwith(F, [], []) when is_function(F, 2) -> [].
Z :: term(),
T :: term().
-zipwith3(F, [X | Xs], [Y | Ys], [Z | Zs]) ->
- [F(X, Y, Z) | zipwith3(F, Xs, Ys, Zs)];
-zipwith3(F, [], [], []) when is_function(F, 3) -> [].
+zipwith3(F, Xs, Ys, Zs) -> zipwith3(F, Xs, Ys, Zs, fail).
+
+-spec zipwith3(Combine, List1, List2, List3, How) -> List4 when
+ Combine :: fun((X | DefaultX, Y | DefaultY, Z | DefaultZ) -> T),
+ List1 :: [X],
+ List2 :: [Y],
+ List3 :: [Z],
+ List4 :: [T],
+ X :: term(),
+ Y :: term(),
+ Z :: term(),
+ How :: 'fail' | 'trim' | {'pad', {DefaultX, DefaultY, DefaultZ}},
+ DefaultX :: term(),
+ DefaultY :: term(),
+ DefaultZ :: term(),
+ T :: term().
+
+zipwith3(F, [X | Xs], [Y | Ys], [Z | Zs], How) ->
+ [F(X, Y, Z) | zipwith3(F, Xs, Ys, Zs, How)];
+zipwith3(F, [], [], [], fail) when is_function(F, 3) ->
+ [];
+zipwith3(F, [], [], [], trim) when is_function(F, 3) ->
+ [];
+zipwith3(F, Xs, Ys, Zs, trim) when is_function(F, 3), is_list(Xs), is_list(Ys), is_list(Zs) ->
+ [];
+zipwith3(F, [], [], [], {pad, {_, _, _}}) when is_function(F, 3) ->
+ [];
+zipwith3(F, [], [], [_ | _]=Zs, {pad, {X, Y, _}}) ->
+ [F(X, Y, Z) || Z <- Zs];
+zipwith3(F, [], [_ | _]=Ys, [], {pad, {X, _, Z}}) ->
+ [F(X, Y, Z) || Y <- Ys];
+zipwith3(F, [_ | _]=Xs, [], [], {pad, {_, Y, Z}}) ->
+ [F(X, Y, Z) || X <- Xs];
+zipwith3(F, [], [Y | Ys], [Z | Zs], {pad, {X, _, _}} = How) ->
+ [F(X, Y, Z) | zipwith3(F, [], Ys, Zs, How)];
+zipwith3(F, [X | Xs], [], [Z | Zs], {pad, {_, Y, _}} = How) ->
+ [F(X, Y, Z) | zipwith3(F, Xs, [], Zs, How)];
+zipwith3(F, [X | Xs], [Y | Ys], [], {pad, {_, _, Z}} = How) ->
+ [F(X, Y, Z) | zipwith3(F, Xs, Ys, [], How)].
%% sort(List) -> L
%% sorts the list L
@@ -575,24 +710,44 @@ merge(L) ->
Y :: term(),
Z :: term().
-merge3(L1, [], L3) ->
- merge(L1, L3);
-merge3(L1, L2, []) ->
- merge(L1, L2);
-merge3(L1, [H2 | T2], [H3 | T3]) ->
- lists:reverse(merge3_1(L1, [], H2, T2, H3, T3), []).
+merge3([_|_]=L1, [H2 | T2], [H3 | T3]) ->
+ lists:reverse(merge3_1(L1, [], H2, T2, H3, T3), []);
+merge3([_|_]=L1, [_|_]=L2, []) ->
+ merge(L1, L2);
+merge3([_|_]=L1, [], [_|_]=L3) ->
+ merge(L1, L3);
+merge3([_|_]=L1, [], []) ->
+ L1;
+merge3([], [_|_]=L2, [_|_]=L3) ->
+ merge(L2, L3);
+merge3([], [_|_]=L2, []) ->
+ L2;
+merge3([], [], [_|_]=L3) ->
+ L3;
+merge3([], [], []) ->
+ [].
%% rmerge3(X, Y, Z) -> L
%% merges three reversed sorted lists X, Y and Z
-spec rmerge3([X], [Y], [Z]) -> [(X | Y | Z)].
-rmerge3(L1, [], L3) ->
- rmerge(L1, L3);
-rmerge3(L1, L2, []) ->
- rmerge(L1, L2);
-rmerge3(L1, [H2 | T2], [H3 | T3]) ->
- lists:reverse(rmerge3_1(L1, [], H2, T2, H3, T3), []).
+rmerge3([_|_]=L1, [H2 | T2], [H3 | T3]) ->
+ lists:reverse(rmerge3_1(L1, [], H2, T2, H3, T3), []);
+rmerge3([_|_]=L1, [_|_]=L2, []) ->
+ rmerge(L1, L2);
+rmerge3([_|_]=L1, [], [_|_]=L3) ->
+ rmerge(L1, L3);
+rmerge3([_|_]=L1, [], []) ->
+ L1;
+rmerge3([], [_|_]=L2, [_|_]=L3) ->
+ rmerge(L2, L3);
+rmerge3([], [_|_]=L2, []) ->
+ L2;
+rmerge3([], [], [_|_]=L3) ->
+ L3;
+rmerge3([], [], []) ->
+ [].
%% merge(X, Y) -> L
%% merges two sorted lists X and Y
@@ -604,10 +759,14 @@ rmerge3(L1, [H2 | T2], [H3 | T3]) ->
X :: term(),
Y :: term().
-merge(T1, []) ->
- T1;
-merge(T1, [H2 | T2]) ->
- lists:reverse(merge2_1(T1, H2, T2, []), []).
+merge([_|_]=T1, [H2 | T2]) ->
+ lists:reverse(merge2_1(T1, H2, T2, []), []);
+merge([_|_]=L1, []) ->
+ L1;
+merge([], [_|_]=L2) ->
+ L2;
+merge([], []) ->
+ [].
%% rmerge(X, Y) -> L
%% merges two reversed sorted lists X and Y
@@ -616,10 +775,14 @@ merge(T1, [H2 | T2]) ->
-spec rmerge([X], [Y]) -> [(X | Y)].
-rmerge(T1, []) ->
- T1;
-rmerge(T1, [H2 | T2]) ->
- lists:reverse(rmerge2_1(T1, H2, T2, []), []).
+rmerge([_|_]=T1, [H2 | T2]) ->
+ lists:reverse(rmerge2_1(T1, H2, T2, []), []);
+rmerge([_|_]=L1, []) ->
+ L1;
+rmerge([], [_|_]=L2) ->
+ L2;
+rmerge([], []) ->
+ [].
%% concat(L) concatenate the list representation of the elements
%% in L - the elements in L can be atoms, numbers of strings.
@@ -845,30 +1008,38 @@ keysort_1(_I, X, _EX, [], R) ->
T2 :: Tuple,
Tuple :: tuple().
-keymerge(Index, T1, L2) when is_integer(Index), Index > 0 ->
- case L2 of
- [] ->
- T1;
- [H2 | T2] ->
- E2 = element(Index, H2),
- M = keymerge2_1(Index, T1, E2, H2, T2, []),
- lists:reverse(M, [])
- end.
+keymerge(Index, L1, L2) when is_integer(Index), Index > 0 ->
+ keymerge_1(Index, L1, L2).
+
+keymerge_1(Index, [_|_]=T1, [H2 | T2]) ->
+ E2 = element(Index, H2),
+ M = keymerge2_1(Index, T1, E2, H2, T2, []),
+ lists:reverse(M, []);
+keymerge_1(_Index, [_|_]=L1, []) ->
+ L1;
+keymerge_1(_Index, [], [_|_]=L2) ->
+ L2;
+keymerge_1(_Index, [], []) ->
+ [].
%% reverse(rkeymerge(I,reverse(A),reverse(B))) is equal to keymerge(I,A,B).
-spec rkeymerge(pos_integer(), [X], [Y]) ->
[R] when X :: tuple(), Y :: tuple(), R :: tuple().
-rkeymerge(Index, T1, L2) when is_integer(Index), Index > 0 ->
- case L2 of
- [] ->
- T1;
- [H2 | T2] ->
- E2 = element(Index, H2),
- M = rkeymerge2_1(Index, T1, E2, H2, T2, []),
- lists:reverse(M, [])
- end.
+rkeymerge(Index, L1, L2) when is_integer(Index), Index > 0 ->
+ rkeymerge_1(Index, L1, L2).
+
+rkeymerge_1(Index, [_|_]=T1, [H2 | T2]) ->
+ E2 = element(Index, H2),
+ M = rkeymerge2_1(Index, T1, E2, H2, T2, []),
+ lists:reverse(M, []);
+rkeymerge_1(_Index, [_|_]=L1, []) ->
+ L1;
+rkeymerge_1(_Index, [], [_|_]=L2) ->
+ L2;
+rkeymerge_1(_Index, [], []) ->
+ [].
-spec ukeysort(N, TupleList1) -> TupleList2 when
N :: pos_integer(),
@@ -948,30 +1119,38 @@ ukeysort_1(_I, X, _EX, []) ->
T2 :: Tuple,
Tuple :: tuple().
-ukeymerge(Index, L1, T2) when is_integer(Index), Index > 0 ->
- case L1 of
- [] ->
- T2;
- [H1 | T1] ->
- E1 = element(Index, H1),
- M = ukeymerge2_2(Index, T1, E1, H1, T2, []),
- lists:reverse(M, [])
- end.
+ukeymerge(Index, L1, L2) when is_integer(Index), Index > 0 ->
+ ukeymerge_1(Index, L1, L2).
+
+ukeymerge_1(Index, [H1 | T1], [_|_]=T2) ->
+ E1 = element(Index, H1),
+ M = ukeymerge2_2(Index, T1, E1, H1, T2, []),
+ lists:reverse(M, []);
+ukeymerge_1(_Index, [_|_]=L1, []) ->
+ L1;
+ukeymerge_1(_Index, [], [_|_]=L2) ->
+ L2;
+ukeymerge_1(_Index, [], []) ->
+ [].
%% reverse(rukeymerge(I,reverse(A),reverse(B))) is equal to ukeymerge(I,A,B).
-spec rukeymerge(pos_integer(), [X], [Y]) ->
[(X | Y)] when X :: tuple(), Y :: tuple().
-rukeymerge(Index, T1, L2) when is_integer(Index), Index > 0 ->
- case L2 of
- [] ->
- T1;
- [H2 | T2] ->
- E2 = element(Index, H2),
- M = rukeymerge2_1(Index, T1, E2, T2, [], H2),
- lists:reverse(M, [])
- end.
+rukeymerge(Index, L1, L2) when is_integer(Index), Index > 0 ->
+ rukeymerge_1(Index, L1, L2).
+
+rukeymerge_1(Index, [_|_]=T1, [H2 | T2]) ->
+ E2 = element(Index, H2),
+ M = rukeymerge2_1(Index, T1, E2, T2, [], H2),
+ lists:reverse(M, []);
+rukeymerge_1(_Index, [_|_]=L1, []) ->
+ L1;
+rukeymerge_1(_Index, [], [_|_]=L2) ->
+ L2;
+rukeymerge_1(_Index, [], []) ->
+ [].
-spec keymap(Fun, N, TupleList1) -> TupleList2 when
Fun :: fun((Term1 :: term()) -> Term2 :: term()),
@@ -990,20 +1169,29 @@ keymap(Fun, Index, []) when is_integer(Index), Index >= 1,
List2 :: [{Index, T}],
Index :: integer(),
T :: term().
-enumerate(List1) when is_list(List1) ->
- enumerate_1(1, List1).
+enumerate(List1) ->
+ enumerate(1, 1, List1).
-spec enumerate(Index, List1) -> List2 when
List1 :: [T],
List2 :: [{Index, T}],
Index :: integer(),
T :: term().
-enumerate(Index, List1) when is_integer(Index), is_list(List1) ->
- enumerate_1(Index, List1).
+enumerate(Index, List1) ->
+ enumerate(Index, 1, List1).
-enumerate_1(Index, [H|T]) ->
- [{Index, H}|enumerate_1(Index + 1, T)];
-enumerate_1(_Index, []) ->
+-spec enumerate(Index, Step, List1) -> List2 when
+ List1 :: [T],
+ List2 :: [{Index, T}],
+ Index :: integer(),
+ Step :: integer(),
+ T :: term().
+enumerate(Index, Step, List1) when is_integer(Index), is_integer(Step) ->
+ enumerate_1(Index, Step, List1).
+
+enumerate_1(Index, Step, [H|T]) ->
+ [{Index, H}|enumerate_1(Index + Step, Step, T)];
+enumerate_1(_Index, _Step, []) ->
[].
%%% Suggestion from OTP-2948: sort and merge with Fun.
@@ -1034,19 +1222,33 @@ sort(Fun, [X, Y | T]) ->
A :: term(),
B :: term().
-merge(Fun, T1, [H2 | T2]) when is_function(Fun, 2) ->
+merge(Fun, L1, L2) when is_function(Fun, 2) ->
+ merge_1(Fun, L1, L2).
+
+merge_1(Fun, [_|_]=T1, [H2 | T2]) ->
lists:reverse(fmerge2_1(T1, H2, Fun, T2, []), []);
-merge(Fun, T1, []) when is_function(Fun, 2) ->
- T1.
+merge_1(_Fun, [_|_]=L1, []) ->
+ L1;
+merge_1(_Fun, [], [_|_]=L2) ->
+ L2;
+merge_1(_Fun, [], []) ->
+ [].
%% reverse(rmerge(F,reverse(A),reverse(B))) is equal to merge(F,A,B).
-spec rmerge(fun((X, Y) -> boolean()), [X], [Y]) -> [(X | Y)].
-rmerge(Fun, T1, [H2 | T2]) when is_function(Fun, 2) ->
+rmerge(Fun, L1, L2) when is_function(Fun, 2) ->
+ rmerge_1(Fun, L1, L2).
+
+rmerge_1(Fun, [_|_]=T1, [H2 | T2]) ->
lists:reverse(rfmerge2_1(T1, H2, Fun, T2, []), []);
-rmerge(Fun, T1, []) when is_function(Fun, 2) ->
- T1.
+rmerge_1(_Fun, [_|_]=L1, []) ->
+ L1;
+rmerge_1(_Fun, [], [_|_]=L2) ->
+ L2;
+rmerge_1(_Fun, [], []) ->
+ [].
-spec usort(Fun, List1) -> List2 when
Fun :: fun((T, T) -> boolean()),
@@ -1087,19 +1289,33 @@ usort_1(Fun, X, [Y | L]) ->
A :: term(),
B :: term().
-umerge(Fun, [], T2) when is_function(Fun, 2) ->
- T2;
-umerge(Fun, [H1 | T1], T2) when is_function(Fun, 2) ->
- lists:reverse(ufmerge2_2(H1, T1, Fun, T2, []), []).
+umerge(Fun, L1, L2) when is_function(Fun, 2) ->
+ umerge_1(Fun, L1, L2).
+
+umerge_1(Fun, [H1 | T1], [_|_]=T2) ->
+ lists:reverse(ufmerge2_2(H1, T1, Fun, T2, []), []);
+umerge_1(_Fun, [_|_]=L1, []) ->
+ L1;
+umerge_1(_Fun, [], [_|_]=L2) ->
+ L2;
+umerge_1(_Fun, [], []) ->
+ [].
%% reverse(rumerge(F,reverse(A),reverse(B))) is equal to umerge(F,A,B).
-spec rumerge(fun((X, Y) -> boolean()), [X], [Y]) -> [(X | Y)].
-rumerge(Fun, T1, []) when is_function(Fun, 2) ->
- T1;
-rumerge(Fun, T1, [H2 | T2]) when is_function(Fun, 2) ->
- lists:reverse(rufmerge2_1(T1, H2, Fun, T2, []), []).
+rumerge(Fun, L1, L2) when is_function(Fun, 2) ->
+ rumerge_1(Fun, L1, L2).
+
+rumerge_1(Fun, [_|_]=T1, [H2 | T2]) ->
+ lists:reverse(rufmerge2_1(T1, H2, Fun, T2, []), []);
+rumerge_1(_Fun, [_|_]=L1, []) ->
+ L1;
+rumerge_1(_Fun, [], [_|_]=L2) ->
+ L2;
+rumerge_1(_Fun, [], []) ->
+ [].
%% usort(List) -> L
%% sorts the list L, removes duplicates
@@ -1184,12 +1400,22 @@ umerge(L) ->
Y :: term(),
Z :: term().
-umerge3(L1, [], L3) ->
- umerge(L1, L3);
-umerge3(L1, L2, []) ->
- umerge(L1, L2);
-umerge3(L1, [H2 | T2], [H3 | T3]) ->
- lists:reverse(umerge3_1(L1, [H2 | H3], T2, H2, [], T3, H3), []).
+umerge3([_|_]=L1, [H2 | T2], [H3 | T3]) ->
+ lists:reverse(umerge3_1(L1, [H2 | H3], T2, H2, [], T3, H3), []);
+umerge3([_|_]=L1, [_|_]=L2, []) ->
+ umerge(L1, L2);
+umerge3([_|_]=L1, [], [_|_]=L3) ->
+ umerge(L1, L3);
+umerge3([_|_]=L1, [], []) ->
+ L1;
+umerge3([], [_|_]=L2, [_|_]=L3) ->
+ umerge(L2, L3);
+umerge3([], [_|_]=L2, []) ->
+ L2;
+umerge3([], [], [_|_]=L3) ->
+ L3;
+umerge3([], [], []) ->
+ [].
%% rumerge3(X, Y, Z) -> L
%% merges three reversed sorted lists X, Y and Z without duplicates,
@@ -1197,12 +1423,22 @@ umerge3(L1, [H2 | T2], [H3 | T3]) ->
-spec rumerge3([X], [Y], [Z]) -> [(X | Y | Z)].
-rumerge3(L1, [], L3) ->
- rumerge(L1, L3);
-rumerge3(L1, L2, []) ->
- rumerge(L1, L2);
-rumerge3(L1, [H2 | T2], [H3 | T3]) ->
- lists:reverse(rumerge3_1(L1, T2, H2, [], T3, H3),[]).
+rumerge3([_|_]=L1, [H2 | T2], [H3 | T3]) ->
+ lists:reverse(rumerge3_1(L1, T2, H2, [], T3, H3),[]);
+rumerge3([_|_]=L1, [_|_]=L2, []) ->
+ rumerge(L1, L2);
+rumerge3([_|_]=L1, [], [_|_]=L3) ->
+ rumerge(L1, L3);
+rumerge3([_|_]=L1, [], []) ->
+ L1;
+rumerge3([], [_|_]=L2, [_|_]=L3) ->
+ rumerge(L2, L3);
+rumerge3([], [_|_]=L2, []) ->
+ L2;
+rumerge3([], [], [_|_]=L3) ->
+ L3;
+rumerge3([], [], []) ->
+ [].
%% umerge(X, Y) -> L
%% merges two sorted lists X and Y without duplicates, removes duplicates
@@ -1214,10 +1450,14 @@ rumerge3(L1, [H2 | T2], [H3 | T3]) ->
X :: term(),
Y :: term().
-umerge([], T2) ->
- T2;
-umerge([H1 | T1], T2) ->
- lists:reverse(umerge2_2(T1, T2, [], H1), []).
+umerge([H1 | T1], [_|_]=T2) ->
+ lists:reverse(umerge2_2(T1, T2, [], H1), []);
+umerge([_|_]=L1, []) ->
+ L1;
+umerge([], [_|_]=L2) ->
+ L2;
+umerge([], []) ->
+ [].
%% rumerge(X, Y) -> L
%% merges two reversed sorted lists X and Y without duplicates,
@@ -1227,10 +1467,14 @@ umerge([H1 | T1], T2) ->
-spec rumerge([X], [Y]) -> [(X | Y)].
-rumerge(T1, []) ->
- T1;
-rumerge(T1, [H2 | T2]) ->
- lists:reverse(rumerge2_1(T1, T2, [], H2), []).
+rumerge([_|_]=T1, [H2 | T2]) ->
+ lists:reverse(rumerge2_1(T1, T2, [], H2), []);
+rumerge([_|_]=L1, []) ->
+ L1;
+rumerge([], [_|_]=L2) ->
+ L2;
+rumerge([], []) ->
+ [].
%% all(Predicate, List)
%% any(Predicate, List)
@@ -1663,24 +1907,24 @@ split_2_1(X, Y, [], R, Rs, S) ->
%% merge/1
-mergel([[] | L], Acc) ->
- mergel(L, Acc);
-mergel([T1, [H2 | T2], [H3 | T3] | L], Acc) ->
- mergel(L, [merge3_1(T1, [], H2, T2, H3, T3) | Acc]);
-mergel([T1, [H2 | T2]], Acc) ->
- rmergel([merge2_1(T1, H2, T2, []) | Acc], []);
-mergel([L], []) ->
- L;
-mergel([L], Acc) ->
- rmergel([lists:reverse(L, []) | Acc], []);
mergel([], []) ->
[];
+mergel([[_|_]=L], []) ->
+ L;
mergel([], Acc) ->
rmergel(Acc, []);
-mergel([A, [] | L], Acc) ->
+mergel([[] | L], Acc) ->
+ mergel(L, Acc);
+mergel([[_|_]=L], Acc) ->
+ rmergel([lists:reverse(L, []) | Acc], []);
+mergel([[_|_]=A, [] | L], Acc) ->
mergel([A | L], Acc);
-mergel([A, B, [] | L], Acc) ->
- mergel([A, B | L], Acc).
+mergel([[_|_]=A, [_|_]=B, [] | L], Acc) ->
+ mergel([A, B | L], Acc);
+mergel([[_|_]=T1, [H2 | T2], [H3 | T3] | L], Acc) ->
+ mergel(L, [merge3_1(T1, [], H2, T2, H3, T3) | Acc]);
+mergel([[_|_]=T1, [H2 | T2]], Acc) ->
+ rmergel([merge2_1(T1, H2, T2, []) | Acc], []).
rmergel([[H3 | T3], [H2 | T2], T1 | L], Acc) ->
rmergel(L, [rmerge3_1(T1, [], H2, T2, H3, T3) | Acc]);
@@ -1896,28 +2140,28 @@ usplit_2_1(X, Y, [], R, Rs, S) ->
umergel(L) ->
umergel(L, [], asc).
+umergel([], [], _O) ->
+ [];
+umergel([[_|_]=L], [], _O) ->
+ L;
+umergel([], Acc, O) ->
+ rumergel(Acc, [], O);
+umergel([[_|_]=L], Acc, O) ->
+ rumergel([lists:reverse(L, []) | Acc], [], O);
umergel([[] | L], Acc, O) ->
umergel(L, Acc, O);
-umergel([T1, [H2 | T2], [H3 | T3] | L], Acc, asc) ->
- umergel(L, [umerge3_1(T1, [H2 | H3], T2, H2, [], T3, H3) | Acc], asc);
-umergel([[H3 | T3], [H2 | T2], T1 | L], Acc, desc) ->
- umergel(L, [umerge3_1(T1, [H2 | H3], T2, H2, [], T3, H3) | Acc], desc);
-umergel([A, [] | L], Acc, O) ->
+umergel([[_|_]=A, [] | L], Acc, O) ->
umergel([A | L], Acc, O);
-umergel([A, B, [] | L], Acc, O) ->
+umergel([[_|_]=A, [_|_]=B, [] | L], Acc, O) ->
umergel([A, B | L], Acc, O);
-umergel([[H1 | T1], T2 | L], Acc, asc) ->
+umergel([[_|_]=T1, [H2 | T2], [H3 | T3] | L], Acc, asc) ->
+ umergel(L, [umerge3_1(T1, [H2 | H3], T2, H2, [], T3, H3) | Acc], asc);
+umergel([[H3 | T3], [H2 | T2], [_|_]=T1 | L], Acc, desc) ->
+ umergel(L, [umerge3_1(T1, [H2 | H3], T2, H2, [], T3, H3) | Acc], desc);
+umergel([[H1 | T1], [_|_]=T2 | L], Acc, asc) ->
umergel(L, [umerge2_2(T1, T2, [], H1) | Acc], asc);
-umergel([T2, [H1 | T1] | L], Acc, desc) ->
- umergel(L, [umerge2_2(T1, T2, [], H1) | Acc], desc);
-umergel([L], [], _O) ->
- L;
-umergel([L], Acc, O) ->
- rumergel([lists:reverse(L, []) | Acc], [], O);
-umergel([], [], _O) ->
- [];
-umergel([], Acc, O) ->
- rumergel(Acc, [], O).
+umergel([[_|_]=T2, [H1 | T1] | L], Acc, desc) ->
+ umergel(L, [umerge2_2(T1, T2, [], H1) | Acc], desc).
rumergel([[H3 | T3], [H2 | T2], T1 | L], Acc, asc) ->
rumergel(L, [rumerge3_1(T1, T2, H2, [], T3, H3) | Acc], asc);
diff --git a/lib/stdlib/src/maps.erl b/lib/stdlib/src/maps.erl
index c7107031fb..a7689f10b3 100644
--- a/lib/stdlib/src/maps.erl
+++ b/lib/stdlib/src/maps.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -24,11 +24,15 @@
map/2, size/1, new/0,
update_with/3, update_with/4,
without/2, with/2,
- iterator/1, next/1,
+ iterator/1, iterator/2,
+ next/1,
intersect/2, intersect_with/3,
merge_with/3,
groups_from_list/2, groups_from_list/3]).
+%% Internal
+-export([is_iterator_valid/1]).
+
%% BIFs
-export([get/2, find/2, from_list/1, from_keys/2,
is_key/2, keys/1, merge/2,
@@ -36,13 +40,18 @@
to_list/1, update/3, values/1]).
-opaque iterator(Key, Value) :: {Key, Value, iterator(Key, Value)} | none
- | nonempty_improper_list(integer(), #{Key => Value}).
+ | nonempty_improper_list(integer(), #{Key => Value})
+ | nonempty_improper_list(list(Key), #{Key => Value}).
-type iterator() :: iterator(term(), term()).
--export_type([iterator/2, iterator/0]).
+-type iterator_order(Key) :: undefined | ordered | reversed
+ | fun((A :: Key, B :: Key) -> boolean()).
+-type iterator_order() :: iterator_order(term()).
+
+-export_type([iterator/2, iterator/0, iterator_order/1, iterator_order/0]).
--dialyzer({no_improper_lists, iterator/1}).
+-dialyzer({no_improper_lists, [iterator/1, iterator/2]}).
%% We must inline these functions so that the stacktrace points to
%% the correct function.
@@ -220,13 +229,22 @@ remove(_,_) -> erlang:nif_error(undef).
take(_,_) -> erlang:nif_error(undef).
--spec to_list(Map) -> [{Key,Value}] when
- Map :: #{Key => Value}.
+-spec to_list(MapOrIterator) -> [{Key, Value}] when
+ MapOrIterator :: #{Key => Value} | iterator(Key, Value).
to_list(Map) when is_map(Map) ->
to_list_internal(erts_internal:map_next(0, Map, []));
-to_list(Map) ->
- error_with_info({badmap,Map}, [Map]).
+to_list(Iter) ->
+ try to_list_from_iterator(next(Iter))
+ catch
+ error:_ ->
+ error_with_info({badmap, Iter}, [Iter])
+ end.
+
+to_list_from_iterator({Key, Value, NextIter}) ->
+ [{Key, Value} | to_list_from_iterator(next(NextIter))];
+to_list_from_iterator(none) ->
+ [].
to_list_internal([Iter, Map | Acc]) when is_integer(Iter) ->
to_list_internal(erts_internal:map_next(Iter, Map, Acc));
@@ -298,31 +316,28 @@ get(Key,Map,Default) ->
MapOrIter :: #{Key => Value} | iterator(Key, Value),
Map :: #{Key => Value}.
-filter(Pred, MapOrIter) when is_function(Pred, 2) ->
- Iter = if
- is_map(MapOrIter) ->
- iterator(MapOrIter);
- true ->
- MapOrIter
- end,
- try next(Iter) of
- Next ->
- maps:from_list(filter_1(Pred, Next))
+filter(Pred, Map) when is_map(Map), is_function(Pred, 2) ->
+ maps:from_list(filter_1(Pred, next(iterator(Map)), undefined));
+filter(Pred, Iter) when is_function(Pred, 2) ->
+ ErrorTag = make_ref(),
+ try filter_1(Pred, try_next(Iter, ErrorTag), ErrorTag) of
+ Result ->
+ maps:from_list(Result)
catch
- error:_ ->
- error_with_info({badmap,MapOrIter}, [Pred, MapOrIter])
+ error:ErrorTag ->
+ error_with_info({badmap, Iter}, [Pred, Iter])
end;
filter(Pred, Map) ->
badarg_with_info([Pred, Map]).
-filter_1(Pred, {K, V, Iter}) ->
+filter_1(Pred, {K, V, Iter}, ErrorTag) ->
case Pred(K, V) of
true ->
- [{K,V} | filter_1(Pred, next(Iter))];
+ [{K,V} | filter_1(Pred, try_next(Iter, ErrorTag), ErrorTag)];
false ->
- filter_1(Pred, next(Iter))
+ filter_1(Pred, try_next(Iter, ErrorTag), ErrorTag)
end;
-filter_1(_Pred, none) ->
+filter_1(_Pred, none, _ErrorTag) ->
[].
-spec filtermap(Fun, MapOrIter) -> Map when
@@ -330,57 +345,52 @@ filter_1(_Pred, none) ->
MapOrIter :: #{Key => Value1} | iterator(Key, Value1),
Map :: #{Key => Value1 | Value2}.
-filtermap(Fun, MapOrIter) when is_function(Fun, 2) ->
- Iter = if
- is_map(MapOrIter) ->
- iterator(MapOrIter);
- true ->
- MapOrIter
- end,
- try next(Iter) of
- Next ->
- maps:from_list(filtermap_1(Fun, Next))
+filtermap(Fun, Map) when is_map(Map), is_function(Fun, 2) ->
+ maps:from_list(filtermap_1(Fun, next(iterator(Map)), undefined));
+filtermap(Fun, Iter) when is_function(Fun, 2) ->
+ ErrorTag = make_ref(),
+ try filtermap_1(Fun, try_next(Iter, ErrorTag), ErrorTag) of
+ Result ->
+ maps:from_list(Result)
catch
- error:_ ->
- error_with_info({badmap,MapOrIter}, [Fun, MapOrIter])
+ error:ErrorTag ->
+ error_with_info({badmap, Iter}, [Fun, Iter])
end;
filtermap(Fun, Map) ->
badarg_with_info([Fun, Map]).
-filtermap_1(Pred, {K, V, Iter}) ->
- case Pred(K, V) of
+filtermap_1(Fun, {K, V, Iter}, ErrorTag) ->
+ case Fun(K, V) of
true ->
- [{K, V} | filtermap_1(Pred, next(Iter))];
+ [{K, V} | filtermap_1(Fun, try_next(Iter, ErrorTag), ErrorTag)];
{true, NewV} ->
- [{K, NewV} | filtermap_1(Pred, next(Iter))];
+ [{K, NewV} | filtermap_1(Fun, try_next(Iter, ErrorTag), ErrorTag)];
false ->
- filtermap_1(Pred, next(Iter))
+ filtermap_1(Fun, try_next(Iter, ErrorTag), ErrorTag)
end;
-filtermap_1(_Pred, none) ->
+filtermap_1(_Fun, none, _ErrorTag) ->
[].
-spec foreach(Fun,MapOrIter) -> ok when
Fun :: fun((Key, Value) -> term()),
MapOrIter :: #{Key => Value} | iterator(Key, Value).
-foreach(Fun, MapOrIter) when is_function(Fun, 2) ->
- Iter = if is_map(MapOrIter) -> iterator(MapOrIter);
- true -> MapOrIter
- end,
- try next(Iter) of
- Next ->
- foreach_1(Fun, Next)
+foreach(Fun, Map) when is_map(Map), is_function(Fun, 2) ->
+ foreach_1(Fun, next(iterator(Map)), undefined);
+foreach(Fun, Iter) when is_function(Fun, 2) ->
+ ErrorTag = make_ref(),
+ try foreach_1(Fun, try_next(Iter, ErrorTag), ErrorTag)
catch
- error:_ ->
- error_with_info({badmap, MapOrIter}, [Fun, MapOrIter])
+ error:ErrorTag ->
+ error_with_info({badmap, Iter}, [Fun, Iter])
end;
-foreach(Pred, Map) ->
- badarg_with_info([Pred, Map]).
+foreach(Fun, Map) ->
+ badarg_with_info([Fun, Map]).
-foreach_1(Fun, {K, V, Iter}) ->
+foreach_1(Fun, {K, V, Iter}, ErrorTag) ->
Fun(K,V),
- foreach_1(Fun, next(Iter));
-foreach_1(_Fun, none) ->
+ foreach_1(Fun, try_next(Iter, ErrorTag), ErrorTag);
+foreach_1(_Fun, none, _ErrorTag) ->
ok.
-spec fold(Fun,Init,MapOrIter) -> Acc when
@@ -390,26 +400,21 @@ foreach_1(_Fun, none) ->
AccIn :: Init | AccOut,
MapOrIter :: #{Key => Value} | iterator(Key, Value).
-fold(Fun, Init, MapOrIter) when is_function(Fun, 3) ->
- Iter = if
- is_map(MapOrIter) ->
- iterator(MapOrIter);
- true ->
- MapOrIter
- end,
- try next(Iter) of
- Next ->
- fold_1(Fun, Init, Next)
+fold(Fun, Init, Map) when is_map(Map), is_function(Fun, 3) ->
+ fold_1(Fun, Init, next(iterator(Map)), undefined);
+fold(Fun, Init, Iter) when is_function(Fun, 3) ->
+ ErrorTag = make_ref(),
+ try fold_1(Fun, Init, try_next(Iter, ErrorTag), ErrorTag)
catch
- error:_ ->
- error_with_info({badmap,MapOrIter}, [Fun, Init, MapOrIter])
+ error:ErrorTag ->
+ error_with_info({badmap, Iter}, [Fun, Init, Iter])
end;
fold(Fun, Init, Map) ->
badarg_with_info([Fun, Init, Map]).
-fold_1(Fun, Acc, {K, V, Iter}) ->
- fold_1(Fun, Fun(K,V,Acc), next(Iter));
-fold_1(_Fun, Acc, none) ->
+fold_1(Fun, Acc, {K, V, Iter}, ErrorTag) ->
+ fold_1(Fun, Fun(K,V,Acc), try_next(Iter, ErrorTag), ErrorTag);
+fold_1(_Fun, Acc, none, _ErrorTag) ->
Acc.
-spec map(Fun,MapOrIter) -> Map when
@@ -417,27 +422,24 @@ fold_1(_Fun, Acc, none) ->
MapOrIter :: #{Key => Value1} | iterator(Key, Value1),
Map :: #{Key => Value2}.
-map(Fun, MapOrIter) when is_function(Fun, 2) ->
- Iter = if
- is_map(MapOrIter) ->
- iterator(MapOrIter);
- true ->
- MapOrIter
- end,
- try next(Iter) of
- Next ->
- maps:from_list(map_1(Fun, Next))
+map(Fun, Map) when is_map(Map), is_function(Fun, 2) ->
+ maps:from_list(map_1(Fun, next(iterator(Map)), undefined));
+map(Fun, Iter) when is_function(Fun, 2) ->
+ ErrorTag = make_ref(),
+ try map_1(Fun, try_next(Iter, ErrorTag), ErrorTag) of
+ Result ->
+ maps:from_list(Result)
catch
- error:_ ->
- error_with_info({badmap,MapOrIter}, [Fun, MapOrIter])
+ error:ErrorTag ->
+ error_with_info({badmap, Iter}, [Fun, Iter])
end;
map(Fun, Map) ->
badarg_with_info([Fun, Map]).
-map_1(Fun, {K, V, Iter}) ->
- [{K, Fun(K, V)} | map_1(Fun, next(Iter))];
-map_1(_Fun, none) ->
+map_1(Fun, {K, V, Iter}, ErrorTag) ->
+ [{K, Fun(K, V)} | map_1(Fun, try_next(Iter, ErrorTag), ErrorTag)];
+map_1(_Fun, none, _ErrorTag) ->
[].
-spec size(Map) -> non_neg_integer() when
@@ -455,16 +457,43 @@ size(Map) ->
Map :: #{Key => Value},
Iterator :: iterator(Key, Value).
-iterator(M) when is_map(M) -> [0 | M];
+iterator(M) when is_map(M) -> iterator(M, undefined);
iterator(M) -> error_with_info({badmap, M}, [M]).
+-spec iterator(Map, Order) -> Iterator when
+ Map :: #{Key => Value},
+ Order :: iterator_order(Key),
+ Iterator :: iterator(Key, Value).
+
+iterator(M, undefined) when is_map(M) ->
+ [0 | M];
+iterator(M, ordered) when is_map(M) ->
+ CmpFun = fun(A, B) -> erts_internal:cmp_term(A, B) =< 0 end,
+ Keys = lists:sort(CmpFun, maps:keys(M)),
+ [Keys | M];
+iterator(M, reversed) when is_map(M) ->
+ CmpFun = fun(A, B) -> erts_internal:cmp_term(B, A) =< 0 end,
+ Keys = lists:sort(CmpFun, maps:keys(M)),
+ [Keys | M];
+iterator(M, CmpFun) when is_map(M), is_function(CmpFun, 2) ->
+ Keys = lists:sort(CmpFun, maps:keys(M)),
+ [Keys | M];
+iterator(M, Order) ->
+ badarg_with_info([M, Order]).
+
-spec next(Iterator) -> {Key, Value, NextIterator} | 'none' when
Iterator :: iterator(Key, Value),
NextIterator :: iterator(Key, Value).
next({K, V, I}) ->
{K, V, I};
-next([Path | Map]) when is_integer(Path), is_map(Map) ->
- erts_internal:map_next(Path, Map, iterator);
+next([Path | Map] = Iterator)
+ when (is_integer(Path) orelse is_list(Path)), is_map(Map) ->
+ try erts_internal:map_next(Path, Map, iterator) of
+ Result -> Result
+ catch
+ error:badarg ->
+ badarg_with_info([Iterator])
+ end;
next(none) ->
none;
next(Iter) ->
@@ -500,12 +529,13 @@ with_1([], _Map) -> [].
%% groups_from_list/2 & groups_from_list/3
--spec groups_from_list(Fun, List) -> MapOut when
- Fun :: fun((Elem :: T) -> Selected),
- MapOut :: #{Selected => List},
- Selected :: term(),
- List :: [T],
- T :: term().
+-spec groups_from_list(KeyFun, List) -> GroupsMap when
+ KeyFun :: fun((Elem) -> Key),
+ GroupsMap :: #{Key => Group},
+ Key :: term(),
+ List :: [Elem],
+ Group :: [Elem],
+ Elem :: term().
groups_from_list(Fun, List0) when is_function(Fun, 1) ->
try lists:reverse(List0) of
@@ -528,15 +558,15 @@ groups_from_list_1(Fun, [H | Tail], Acc) ->
groups_from_list_1(_Fun, [], Acc) ->
Acc.
--spec groups_from_list(Fun, ValueFun, List) -> MapOut when
- Fun :: fun((Elem :: T) -> Key),
- ValueFun :: fun((Elem :: T) -> ValOut),
- MapOut :: #{Key := ListOut},
+-spec groups_from_list(KeyFun, ValueFun, List) -> GroupsMap when
+ KeyFun :: fun((Elem) -> Key),
+ ValueFun :: fun((Elem) -> Value),
+ GroupsMap :: #{Key := Group},
Key :: term(),
- ValOut :: term(),
- List :: [T],
- ListOut :: [ValOut],
- T :: term().
+ Value :: term(),
+ List :: [Elem],
+ Group :: [Value],
+ Elem :: term().
groups_from_list(Fun, ValueFun, List0) when is_function(Fun, 1),
is_function(ValueFun, 1) ->
@@ -579,3 +609,34 @@ badarg_with_info(Args) ->
error_with_info(Reason, Args) ->
erlang:error(Reason, Args, [{error_info, #{module => erl_stdlib_errors}}]).
+
+-spec is_iterator_valid(MaybeIter) -> boolean() when
+ MaybeIter :: iterator() | term().
+
+is_iterator_valid(Iter) ->
+ try is_iterator_valid_1(Iter)
+ catch
+ error:badarg ->
+ false
+ end.
+
+is_iterator_valid_1(none) ->
+ true;
+is_iterator_valid_1({_, _, Next}) ->
+ is_iterator_valid_1(next(Next));
+is_iterator_valid_1(Iter) ->
+ _ = next(Iter),
+ true.
+
+try_next({_, _, _} = KVI, _ErrorTag) ->
+ KVI;
+try_next(none, _ErrorTag) ->
+ none;
+try_next(Iter, undefined) ->
+ next(Iter);
+try_next(Iter, ErrorTag) ->
+ try next(Iter)
+ catch
+ error:badarg ->
+ error(ErrorTag)
+ end.
diff --git a/lib/stdlib/src/math.erl b/lib/stdlib/src/math.erl
index 3a3b384d8f..cf41cd2f1d 100644
--- a/lib/stdlib/src/math.erl
+++ b/lib/stdlib/src/math.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
%%
-module(math).
--export([pi/0]).
+-export([pi/0,tau/0]).
%%% BIFs
@@ -154,5 +154,7 @@ tanh(_) ->
%%% End of BIFs
-spec pi() -> float().
-
pi() -> 3.1415926535897932.
+
+-spec tau() -> float().
+tau() -> 6.2831853071795864.
diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl
index dde8e572a3..01496adb55 100644
--- a/lib/stdlib/src/ms_transform.erl
+++ b/lib/stdlib/src/ms_transform.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -977,6 +977,8 @@ real_guard_function(abs,1) -> true;
real_guard_function(element,2) -> true;
real_guard_function(hd,1) -> true;
real_guard_function(length,1) -> true;
+real_guard_function(max,2) -> true;
+real_guard_function(min,2) -> true;
real_guard_function(node,0) -> true;
real_guard_function(node,1) -> true;
real_guard_function(round,1) -> true;
@@ -1015,6 +1017,9 @@ action_function(set_tcw,1) -> true;
action_function(silent,1) -> true;
action_function(trace,2) -> true;
action_function(trace,3) -> true;
+action_function(caller_line,0) -> true;
+action_function(current_stacktrace,0) -> true;
+action_function(current_stacktrace,1) -> true;
action_function(_,_) -> false.
bool_operator('and',2) ->
diff --git a/lib/stdlib/src/orddict.erl b/lib/stdlib/src/orddict.erl
index 9a2772949b..e78b0187f2 100644
--- a/lib/stdlib/src/orddict.erl
+++ b/lib/stdlib/src/orddict.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@
%%---------------------------------------------------------------------------
--spec new() -> orddict().
+-spec new() -> orddict(none(), none()).
new() -> [].
diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl
index 3afbbfb881..5bc0704b68 100644
--- a/lib/stdlib/src/otp_internal.erl
+++ b/lib/stdlib/src/otp_internal.erl
@@ -31,50 +31,28 @@ obsolete(auth, is_auth, 1) ->
{deprecated, "use net_adm:ping/1 instead"};
obsolete(calendar, local_time_to_universal_time, 1) ->
{deprecated, "use calendar:local_time_to_universal_time_dst/1 instead"};
-obsolete(code, is_module_native, 1) ->
- {deprecated, "HiPE has been removed", "OTP 26"};
-obsolete(code, rehash, 0) ->
- {deprecated, "the code path cache feature has been removed", "OTP 26"};
obsolete(crypto, crypto_dyn_iv_init, 3) ->
{deprecated, "see the documentation for details", "OTP 27"};
obsolete(crypto, crypto_dyn_iv_update, 3) ->
{deprecated, "see the documentation for details", "OTP 27"};
obsolete(crypto, rand_uniform, 2) ->
{deprecated, "use rand:uniform/1 instead"};
-obsolete(disk_log, accessible_logs, 0) ->
- {deprecated, "use disk_log:all/0 instead", "OTP 26"};
-obsolete(disk_log, lclose, 1) ->
- {deprecated, "use disk_log:close/1 instead", "OTP 26"};
-obsolete(disk_log, lclose, 2) ->
- {deprecated, "use disk_log:close/1 instead", "OTP 26"};
+obsolete(dbg, stop_clear, 0) ->
+ {deprecated, "use dbg:stop/0 instead", "OTP 27"};
+obsolete(disk_log, inc_wrap_file, 1) ->
+ {deprecated, "use disk_log:next_file/1 instead", "OTP 28"};
obsolete(erlang, now, 0) ->
{deprecated, "see the \"Time and Time Correction in Erlang\" chapter of the ERTS User's Guide for more information"};
obsolete(erlang, phash, 2) ->
{deprecated, "use erlang:phash2/2 instead"};
-obsolete(ftp, start_service, 1) ->
- {deprecated, "use ftp:open/2 instead", "OTP 26"};
-obsolete(ftp, stop_service, 1) ->
- {deprecated, "use ftp:close/1 instead", "OTP 26"};
+obsolete(file, pid2name, 1) ->
+ {deprecated, "this functionality is no longer supported", "OTP 27"};
obsolete(http_uri, decode, 1) ->
- {deprecated, "use uri_string:unquote function instead", "OTP 26"};
+ {deprecated, "use uri_string:unquote function instead", "OTP 27"};
obsolete(http_uri, encode, 1) ->
- {deprecated, "use uri_string:quote function instead", "OTP 26"};
+ {deprecated, "use uri_string:quote function instead", "OTP 27"};
obsolete(httpd, parse_query, 1) ->
{deprecated, "use uri_string:dissect_query/1 instead"};
-obsolete(httpd_util, decode_hex, 1) ->
- {deprecated, "use uri_string:unquote function instead", "OTP 26"};
-obsolete(httpd_util, encode_hex, 1) ->
- {deprecated, "use uri_string:quote function instead", "OTP 26"};
-obsolete(httpd_util, flatlength, 1) ->
- {deprecated, "use erlang:iolist_size/1 instead", "OTP 26"};
-obsolete(httpd_util, hexlist_to_integer, 1) ->
- {deprecated, "use erlang:list_to_integer/2 with base 16 instead", "OTP 26"};
-obsolete(httpd_util, integer_to_hexlist, 1) ->
- {deprecated, "use erlang:integer_to_list/2 with base 16 instead", "OTP 26"};
-obsolete(httpd_util, strip, 1) ->
- {deprecated, "use string:trim/1 instead", "OTP 26"};
-obsolete(httpd_util, suffix, 1) ->
- {deprecated, "use filename:extension/1 and string:trim/2 instead", "OTP 26"};
obsolete(net, broadcast, 3) ->
{deprecated, "use rpc:eval_everywhere/3 instead"};
obsolete(net, call, 4) ->
@@ -115,6 +93,10 @@ obsolete(zlib, inflateChunk, 2) ->
{deprecated, "use safeInflate/2 instead", "OTP 27"};
obsolete(zlib, setBufSize, 2) ->
{deprecated, "this function will be removed in a future release", "OTP 27"};
+obsolete(code, is_module_native, 1) ->
+ {removed, "HiPE has been removed"};
+obsolete(code, rehash, 0) ->
+ {removed, "the code path cache feature has been removed"};
obsolete(core_lib, get_anno, 1) ->
{removed, "use cerl:get_ann/1 instead"};
obsolete(core_lib, is_literal, 1) ->
@@ -155,6 +137,12 @@ obsolete(crypto, stream_decrypt, 2) ->
{removed, "use crypto:crypto_update/2 instead"};
obsolete(crypto, stream_encrypt, 2) ->
{removed, "use crypto:crypto_update/2 instead"};
+obsolete(disk_log, accessible_logs, 0) ->
+ {removed, "use disk_log:all/0 instead"};
+obsolete(disk_log, lclose, 1) ->
+ {removed, "use disk_log:close/1 instead"};
+obsolete(disk_log, lclose, 2) ->
+ {removed, "use disk_log:close/1 instead"};
obsolete(erl_lint, modify_line, 2) ->
{removed, "use erl_parse:map_anno/2 instead"};
obsolete(erl_parse, get_attribute, 2) ->
@@ -171,6 +159,10 @@ obsolete(erlang, hash, 2) ->
{removed, "use erlang:phash2/2 instead"};
obsolete(filename, safe_relative_path, 1) ->
{removed, "use filelib:safe_relative_path/2 instead"};
+obsolete(ftp, start_service, 1) ->
+ {removed, "use ftp:open/2 instead"};
+obsolete(ftp, stop_service, 1) ->
+ {removed, "use ftp:close/1 instead"};
obsolete(http_uri, parse, 1) ->
{removed, "use uri_string functions instead"};
obsolete(http_uri, parse, 2) ->
@@ -189,6 +181,20 @@ obsolete(httpd_conf, is_file, 1) ->
{removed, "use filelib:is_file/1 instead"};
obsolete(httpd_conf, make_integer, 1) ->
{removed, "use erlang:list_to_integer/1 instead"};
+obsolete(httpd_util, decode_hex, 1) ->
+ {removed, "use uri_string:unquote function instead"};
+obsolete(httpd_util, encode_hex, 1) ->
+ {removed, "use uri_string:quote function instead"};
+obsolete(httpd_util, flatlength, 1) ->
+ {removed, "use erlang:iolist_size/1 instead"};
+obsolete(httpd_util, hexlist_to_integer, 1) ->
+ {removed, "use erlang:list_to_integer/2 with base 16 instead"};
+obsolete(httpd_util, integer_to_hexlist, 1) ->
+ {removed, "use erlang:integer_to_list/2 with base 16 instead"};
+obsolete(httpd_util, strip, 1) ->
+ {removed, "use string:trim/1 instead"};
+obsolete(httpd_util, suffix, 1) ->
+ {removed, "use filename:extension/1 and string:trim/2 instead"};
obsolete(net, relay, 1) ->
{removed, "use fun Relay(Pid) -> receive X -> Pid ! X end, Relay(Pid) instead"};
obsolete(public_key, ssh_decode, 2) ->
@@ -231,14 +237,14 @@ obsolete(ssl, ssl_accept, _) ->
{removed, "use ssl_handshake/1,2,3 instead"};
obsolete(ct_slave, _, _) ->
{deprecated, "use ?CT_PEER(), or the 'peer' module instead", "OTP 27"};
-obsolete(erts_alloc_config, _, _) ->
- {deprecated, "this module will be removed in OTP 26.0. See the documentation for details", "OTP 26"};
obsolete(gen_fsm, _, _) ->
{deprecated, "use the 'gen_statem' module instead"};
obsolete(random, _, _) ->
{deprecated, "use the 'rand' module instead"};
obsolete(slave, _, _) ->
{deprecated, "use the 'peer' module instead", "OTP 27"};
+obsolete(erts_alloc_config, _, _) ->
+ {removed, "this module has as of OTP 26.0 been removed"};
obsolete(os_mon_mib, _, _) ->
{removed, "this module was removed in OTP 22.0"};
obsolete(pg2, _, _) ->
@@ -276,6 +282,8 @@ obsolete_type(http_uri, query, 0) ->
{removed, "use uri_string instead"};
obsolete_type(http_uri, scheme, 0) ->
{removed, "use uri_string instead"};
+obsolete_type(http_uri, uri, 0) ->
+ {removed, "use uri_string instead"};
obsolete_type(http_uri, user_info, 0) ->
{removed, "use uri_string instead"};
obsolete_type(_,_,_) -> no.
diff --git a/lib/stdlib/src/peer.erl b/lib/stdlib/src/peer.erl
index b4867c1bdf..0879b799c5 100644
--- a/lib/stdlib/src/peer.erl
+++ b/lib/stdlib/src/peer.erl
@@ -123,9 +123,12 @@
%% saving exit reason in the state
%% crash: when peer terminates, origin process
%% terminates with underlying reason
- exec => exec(), %% path to executable, or SSH/Docker support
connection => connection(), %% alternative connection specification
+ exec => exec(), %% path to executable, or SSH/Docker support
+ detached => boolean(), %% if the node should be start in detached mode
args => [string()], %% additional command line parameters to append
+ post_process_args =>
+ fun(([string()]) -> [string()]),%% fix the arguments
env => [{string(), string()}], %% additional environment variables
wait_boot => wait_boot(), %% default is synchronous start with 15 sec timeout
shutdown => close | %% close supervision channel
@@ -291,14 +294,20 @@ init([Notify, Options]) ->
Env = maps:get(env, Options, []),
+ PostProcessArgs = maps:get(post_process_args, Options, fun(As) -> As end),
+ FinalArgs = PostProcessArgs(Args),
+
%% close port if running detached
Conn =
case maps:find(connection, Options) of
{ok, standard_io} ->
%% Cannot detach a peer that uses stdio. Request exit_status.
- open_port({spawn_executable, Exec}, [{args, Args}, {env, Env}, hide, binary, exit_status]);
+ open_port({spawn_executable, Exec},
+ [{args, FinalArgs}, {env, Env}, hide,
+ binary, exit_status, stderr_to_stdout]);
_ ->
- Port = open_port({spawn_executable, Exec}, [{args, Args}, {env, Env}, hide, binary]),
+ Port = open_port({spawn_executable, Exec},
+ [{args, FinalArgs}, {env, Env}, hide, binary]),
%% peer can close the port before we get here which will cause
%% port_close to throw. Catch this and ignore.
catch erlang:port_close(Port),
@@ -339,6 +348,22 @@ handle_call({call, M, F, A}, From,
origin_to_peer(tcp, Socket, {call, Seq, M, F, A}),
{noreply, State#peer_state{outstanding = Out#{Seq => From}, seq = Seq + 1}};
+handle_call({starting, Node}, _From, #peer_state{ options = Options } = State) ->
+ case maps:find(shutdown, Options) of
+ {ok, {Timeout, MainCoverNode}} when is_integer(Timeout),
+ is_atom(MainCoverNode) ->
+ %% The node was started using test_server:start_peer/2 with cover enabled
+ %% so we should start cover on the starting node.
+ Modules = erpc:call(MainCoverNode,cover,modules,[]),
+ Sticky = [ begin erpc:call(Node, code, unstick_mod, [M]), M end
+ || M <- Modules, erpc:call(Node, code, is_sticky,[M])],
+ _ = erpc:call(MainCoverNode, cover, start, [Node]),
+ _ = [erpc:call(Node, code,stick_mod,[M]) || M <- Sticky],
+ ok;
+ _ ->
+ ok
+ end,
+ {reply, ok, State};
handle_call(get_node, _From, #peer_state{node = Node} = State) ->
{reply, Node, State};
@@ -538,8 +563,9 @@ verify_args(Options) ->
[error({invalid_arg, Arg}) || Arg <- Args, not io_lib:char_list(Arg)],
%% alternative connection must be requested for non-distributed node,
%% or a distributed node when origin is not alive
- is_map_key(connection, Options) orelse
- (is_map_key(name, Options) andalso erlang:is_alive()) orelse error(not_alive),
+ is_map_key(connection, Options)
+ orelse
+ (is_map_key(name, Options) andalso erlang:is_alive()) orelse error(not_alive),
%% exec must be a string, or a tuple of string(), [string()]
case maps:find(exec, Options) of
{ok, {Exec, Strs}} ->
@@ -579,8 +605,13 @@ verify_args(Options) ->
ok;
{ok, Err2} ->
error({shutdown, Err2})
+ end,
+ case maps:find(detached, Options) of
+ {ok, false} when map_get(connection, Options) =:= standard_io ->
+ error({detached, cannot_detach_with_standard_io});
+ _ ->
+ ok
end.
-
make_notify_ref(infinity) ->
{self(), make_ref()};
@@ -796,6 +827,14 @@ command_line(Listen, Options) ->
NameArg = name_arg(maps:find(name, Options), maps:find(host, Options), maps:find(longnames, Options)),
%% additional command line args
CmdOpts = maps:get(args, Options, []),
+
+ %% If we should detach from the node. We use -detached to tell erl to detach
+ %% and -peer_detached to tell peer:start that we are detached.
+ DetachArgs = case maps:get(detached, Options, true) of
+ true -> ["-detached","-peer_detached"];
+ false -> []
+ end,
+
%% start command
StartCmd =
case Listen of
@@ -803,14 +842,14 @@ command_line(Listen, Options) ->
["-user", atom_to_list(?MODULE)];
undefined ->
Self = base64:encode_to_string(term_to_binary(self())),
- ["-detached", "-noinput", "-user", atom_to_list(?MODULE), "-origin", Self];
+ DetachArgs ++ ["-user", atom_to_list(?MODULE), "-origin", Self];
{Ips, Port} ->
IpStr = lists:concat(lists:join(",", [inet:ntoa(Ip) || Ip <- Ips])),
- ["-detached", "-noinput", "-user", atom_to_list(?MODULE), "-origin", IpStr, integer_to_list(Port)]
+ DetachArgs ++ ["-user", atom_to_list(?MODULE), "-origin", IpStr, integer_to_list(Port)]
end,
%% build command line
{Exec, PreArgs} = exec(Options),
- {Exec, PreArgs ++ NameArg ++ StartCmd ++ CmdOpts}.
+ {Exec, PreArgs ++ NameArg ++ CmdOpts ++ StartCmd}.
exec(#{exec := Prog}) when is_list(Prog) ->
{Prog, []};
@@ -1018,23 +1057,44 @@ start_peer_channel_handler() ->
{ok, [[IpStr, PortString]]} ->
%% enter this clause when -origin IpList Port is specified in the command line.
Port = list_to_integer(PortString),
- Ips = [begin {ok, Addr} = inet:parse_address(Ip), Addr end || Ip <- string:lexemes(IpStr, ",")],
- spawn(fun () -> tcp_init(Ips, Port) end);
+ Ips = [begin {ok, Addr} = inet:parse_address(Ip), Addr end ||
+ Ip <- string:lexemes(IpStr, ",")],
+ TCPConnection = spawn(fun () -> tcp_init(Ips, Port) end),
+ _ = case init:get_argument(peer_detached) of
+ {ok, _} ->
+ _ = register(user, TCPConnection);
+ error ->
+ _= user_sup:init(
+ [Flag || Flag <- init:get_arguments(), Flag =/= {user,["peer"]}])
+ end,
+ TCPConnection;
{ok, [[Base64EncProc]]} ->
%% No alternative connection, but have "-origin Base64EncProc"
OriginProcess = binary_to_term(base64:decode(Base64EncProc)),
- %% setup 'user' process, I/O redirection: ask controlling process
- %% who is the group leader.
- GroupLeader = gen_server:call(OriginProcess, group_leader),
- RelayPid = spawn(fun () -> relay(GroupLeader) end),
- register(user, RelayPid),
- spawn(
- fun () ->
- link(RelayPid),
- MRef = monitor(process, OriginProcess),
- notify_when_started(dist, OriginProcess),
- origin_link(MRef, OriginProcess)
- end);
+ OriginLink = spawn(
+ fun () ->
+ MRef = monitor(process, OriginProcess),
+ notify_when_started(dist, OriginProcess),
+ origin_link(MRef, OriginProcess)
+ end),
+ ok = gen_server:call(OriginProcess, {starting, node()}),
+ _ = case init:get_argument(peer_detached) of
+ {ok, _} ->
+ %% We are detached, so setup 'user' process, I/O redirection:
+ %% ask controlling process who is the group leader.
+ GroupLeader = gen_server:call(OriginProcess, group_leader),
+ RelayPid = spawn(fun () ->
+ link(OriginLink),
+ relay(GroupLeader)
+ end),
+ _ = register(user, RelayPid);
+ error ->
+ %% We are not detached, so after we spawn the link process we
+ %% start the terminal as normal but without the -user peer flag.
+ _ = user_sup:init(
+ [Flag || Flag <- init:get_arguments(), Flag =/= {user,["peer"]}])
+ end,
+ OriginLink;
error ->
%% no -origin specified, meaning that standard I/O is used for alternative
spawn(fun io_server/0)
@@ -1074,7 +1134,6 @@ io_server() ->
tcp_init(IpList, Port) ->
try
Sock = loop_connect(IpList, Port),
- register(user, self()),
erlang:group_leader(self(), self()),
notify_when_started(tcp, Sock),
io_server_loop(tcp, Sock, #{}, #{}, undefined)
diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl
index a95d75b9f6..9c7235b954 100644
--- a/lib/stdlib/src/proc_lib.erl
+++ b/lib/stdlib/src/proc_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
start_monitor/3, start_monitor/4, start_monitor/5,
hibernate/3,
init_ack/1, init_ack/2,
+ init_fail/2, init_fail/3,
init_p/3,init_p/5,format/1,format/2,format/3,report_cb/2,
initial_call/1,
translate_initial_call/1,
@@ -273,6 +274,7 @@ exit_reason(exit, Reason, _Stacktrace) ->
exit_reason(throw, Reason, Stacktrace) ->
{{nocatch, Reason}, Stacktrace}.
+
-spec start(Module, Function, Args) -> Ret when
Module :: module(),
Function :: atom(),
@@ -309,14 +311,20 @@ sync_start({Pid, Ref}, Timeout) ->
{ack, Pid, Return} ->
erlang:demonitor(Ref, [flush]),
Return;
+ {nack, Pid, Return} ->
+ flush_EXIT(Pid),
+ _ = await_DOWN(Pid, Ref),
+ Return;
{'DOWN', Ref, process, Pid, Reason} ->
+ flush_EXIT(Pid),
{error, Reason}
after Timeout ->
- erlang:demonitor(Ref, [flush]),
- kill_flush(Pid),
+ kill_flush_EXIT(Pid),
+ _ = await_DOWN(Pid, Ref),
{error, timeout}
end.
+
-spec start_link(Module, Function, Args) -> Ret when
Module :: module(),
Function :: atom(),
@@ -334,7 +342,7 @@ start_link(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
Ret :: term() | {error, Reason :: term()}.
start_link(M, F, A, Timeout) when is_atom(M), is_atom(F), is_list(A) ->
- sync_start_link(?MODULE:spawn_link(M, F, A), Timeout).
+ sync_start(?MODULE:spawn_opt(M, F, A, [link,monitor]), Timeout).
-spec start_link(Module, Function, Args, Time, SpawnOpts) -> Ret when
Module :: module(),
@@ -346,18 +354,9 @@ start_link(M, F, A, Timeout) when is_atom(M), is_atom(F), is_list(A) ->
start_link(M,F,A,Timeout,SpawnOpts) when is_atom(M), is_atom(F), is_list(A) ->
?VERIFY_NO_MONITOR_OPT(M, F, A, Timeout, SpawnOpts),
- sync_start_link(?MODULE:spawn_opt(M, F, A, [link|SpawnOpts]), Timeout).
+ sync_start(
+ ?MODULE:spawn_opt(M, F, A, [link,monitor|SpawnOpts]), Timeout).
-sync_start_link(Pid, Timeout) ->
- receive
- {ack, Pid, Return} ->
- Return;
- {'EXIT', Pid, Reason} ->
- {error, Reason}
- after Timeout ->
- kill_flush(Pid),
- {error, timeout}
- end.
-spec start_monitor(Module, Function, Args) -> {Ret, Mon} when
Module :: module(),
@@ -393,29 +392,54 @@ start_monitor(M,F,A,Timeout,SpawnOpts) when is_atom(M),
is_atom(F),
is_list(A) ->
?VERIFY_NO_MONITOR_OPT(M, F, A, Timeout, SpawnOpts),
- sync_start_monitor(?MODULE:spawn_opt(M, F, A, [monitor|SpawnOpts]),
- Timeout).
+ sync_start_monitor(
+ ?MODULE:spawn_opt(M, F, A, [monitor|SpawnOpts]), Timeout).
sync_start_monitor({Pid, Ref}, Timeout) ->
receive
{ack, Pid, Return} ->
{Return, Ref};
+ {nack, Pid, Return} ->
+ flush_EXIT(Pid),
+ self() ! await_DOWN(Pid, Ref),
+ {Return, Ref};
{'DOWN', Ref, process, Pid, Reason} = Down ->
+ flush_EXIT(Pid),
self() ! Down,
{{error, Reason}, Ref}
after Timeout ->
- kill_flush(Pid),
+ kill_flush_EXIT(Pid),
+ self() ! await_DOWN(Pid, Ref),
{{error, timeout}, Ref}
end.
--spec kill_flush(Pid) -> 'ok' when
- Pid :: pid().
-kill_flush(Pid) ->
+%% We regard the existence of an {'EXIT', Pid, _} message
+%% as proof enough that there was a link that fired and
+%% we had process_flag(trap_exit, true),
+%% so the message should be flushed.
+%% It is the best we can do.
+%%
+%% After an unlink(Pid) an {'EXIT', Pid, _} link message
+%% cannot arrive so receive after 0 will work,
+
+flush_EXIT(Pid) ->
+ unlink(Pid),
+ receive {'EXIT', Pid, _} -> ok after 0 -> ok end.
+
+kill_flush_EXIT(Pid) ->
+ %% unlink/1 has to be called before exit/2
+ %% or we might be killed by the link
unlink(Pid),
exit(Pid, kill),
- receive {'EXIT', Pid, _} -> ok after 0 -> ok end,
- ok.
+ receive {'EXIT', Pid, _} -> ok after 0 -> ok end.
+
+await_DOWN(Pid, Ref) ->
+ receive
+ {'DOWN', Ref, process, Pid, _} = Down ->
+ Down
+ end.
+
-spec init_ack(Parent, Ret) -> 'ok' when
Parent :: pid(),
@@ -432,6 +456,27 @@ init_ack(Return) ->
[Parent|_] = get('$ancestors'),
init_ack(Parent, Return).
+-spec init_fail(_, _, _) -> no_return().
+init_fail(Parent, Return, Exception) ->
+ _ = Parent ! {nack, self(), Return},
+ case Exception of
+ {error, Reason} ->
+ erlang:error(Reason);
+ {exit, Reason} ->
+ erlang:exit(Reason);
+ {throw, Reason} ->
+ erlang:throw(Reason);
+ {Class, Reason, Stacktrace} ->
+ erlang:error(
+ erlang:raise(Class, Reason, Stacktrace),
+ [Parent, Return, Exception])
+ end.
+
+-spec init_fail(_, _) -> no_return().
+init_fail(Return, Exception) ->
+ [Parent|_] = get('$ancestors'),
+ init_fail(Parent, Return, Exception).
+
%% -----------------------------------------------------
%% Fetch the initial call of a proc_lib spawned process.
%% -----------------------------------------------------
diff --git a/lib/stdlib/src/proplists.erl b/lib/stdlib/src/proplists.erl
index 8f25229cdd..a1198a972a 100644
--- a/lib/stdlib/src/proplists.erl
+++ b/lib/stdlib/src/proplists.erl
@@ -643,7 +643,7 @@ apply_stages(L, []) ->
Rest :: [term()].
split(List, Keys) ->
- {Store, Rest} = split(List, maps:from_list([{K, []} || K <- Keys]), []),
+ {Store, Rest} = split(List, #{K => [] || K <- Keys}, []),
{[lists:reverse(map_get(K, Store)) || K <- Keys],
lists:reverse(Rest)}.
diff --git a/lib/stdlib/src/qlc.erl b/lib/stdlib/src/qlc.erl
index cc4a988552..fab7207d13 100644
--- a/lib/stdlib/src/qlc.erl
+++ b/lib/stdlib/src/qlc.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1326,7 +1326,7 @@ abstr_term(PPR, Anno) when is_pid(PPR); is_port(PPR); is_reference(PPR) ->
abstr_term(Map, Anno) when is_map(Map) ->
{map,Anno,
[{map_field_assoc,Anno,abstr_term(K, Anno),abstr_term(V, Anno)} ||
- {K,V} <- maps:to_list(Map)]};
+ K := V <- Map]};
abstr_term(Simple, Anno) ->
erl_parse:abstract(Simple, erl_anno:line(Anno)).
diff --git a/lib/stdlib/src/queue.erl b/lib/stdlib/src/queue.erl
index a71121ad2d..82eb7b1df0 100644
--- a/lib/stdlib/src/queue.erl
+++ b/lib/stdlib/src/queue.erl
@@ -59,7 +59,7 @@
%% Creation, inspection and conversion
%% O(1)
--spec new() -> queue().
+-spec new() -> queue(none()).
new() -> {[],[]}. %{RearList,FrontList}
%% O(1)
diff --git a/lib/stdlib/src/rand.erl b/lib/stdlib/src/rand.erl
index 5f8d3a4c59..9c0f3a5c43 100644
--- a/lib/stdlib/src/rand.erl
+++ b/lib/stdlib/src/rand.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1466,7 +1466,7 @@ dummy_seed({A1, A2, A3}) ->
%% the same sequence in the reverse order. The generator
%% CX1 = (A * CX0) rem P
%% that uses the multiplicative inverse mod P is, indeed,
-%% an exact equevalent to the corresponding MWC generator.
+%% an exact equivalent to the corresponding MWC generator.
%%
%% An MWC generator has, due to the power of two multiplier
%% in the corresponding MCG, got known statistical weaknesses
@@ -1502,8 +1502,7 @@ dummy_seed({A1, A2, A3}) ->
-type mwc59_state() :: 1..?MWC59_P-1.
-spec mwc59(CX0 :: mwc59_state()) -> CX1 :: mwc59_state().
-mwc59(CX0) -> % when is_integer(CX0), 1 =< CX0, CX0 < ?MWC59_P ->
- CX = ?MASK(59, CX0),
+mwc59(CX) when is_integer(CX), 1 =< CX, CX < ?MWC59_P ->
C = CX bsr ?MWC59_B,
X = ?MASK(?MWC59_B, CX),
?MWC59_A * X + C.
@@ -1521,18 +1520,17 @@ mwc59(CX0) -> % when is_integer(CX0), 1 =< CX0, CX0 < ?MWC59_P ->
%%% mwc59(CX1, N - 1).
-spec mwc59_value32(CX :: mwc59_state()) -> V :: 0..?MASK(32).
-mwc59_value32(CX1) -> % when is_integer(CX1), 1 =< CX1, CX1 < ?MWC59_P ->
+mwc59_value32(CX1) when is_integer(CX1), 1 =< CX1, CX1 < ?MWC59_P ->
CX = ?MASK(32, CX1),
CX bxor ?BSL(32, CX, ?MWC59_XS).
-spec mwc59_value(CX :: mwc59_state()) -> V :: 0..?MASK(59).
-mwc59_value(CX1) -> % when is_integer(CX1), 1 =< CX1, CX1 < ?MWC59_P ->
- CX = ?MASK(59, CX1),
+mwc59_value(CX) when is_integer(CX), 1 =< CX, CX < ?MWC59_P ->
CX2 = CX bxor ?BSL(59, CX, ?MWC59_XS1),
CX2 bxor ?BSL(59, CX2, ?MWC59_XS2).
-spec mwc59_float(CX :: mwc59_state()) -> V :: float().
-mwc59_float(CX1) ->
+mwc59_float(CX1) when is_integer(CX1), 1 =< CX1, CX1 < ?MWC59_P ->
CX = ?MASK(53, CX1),
CX2 = CX bxor ?BSL(53, CX, ?MWC59_XS1),
CX3 = CX2 bxor ?BSL(53, CX2, ?MWC59_XS2),
diff --git a/lib/stdlib/src/re.erl b/lib/stdlib/src/re.erl
index 863bbeb652..0f1f9a137a 100644
--- a/lib/stdlib/src/re.erl
+++ b/lib/stdlib/src/re.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -31,6 +31,8 @@
| bsr_anycrlf | bsr_unicode
| no_start_optimize | ucp | never_utf.
+-type replace_fun() :: fun((binary(), [binary()]) -> iodata() | unicode:charlist()).
+
%%% BIFs
-export([internal_run/4]).
@@ -353,7 +355,7 @@ compile_split(_,_) ->
-spec replace(Subject, RE, Replacement) -> iodata() | unicode:charlist() when
Subject :: iodata() | unicode:charlist(),
RE :: mp() | iodata(),
- Replacement :: iodata() | unicode:charlist().
+ Replacement :: iodata() | unicode:charlist() | replace_fun().
replace(Subject,RE,Replacement) ->
try
@@ -366,7 +368,7 @@ replace(Subject,RE,Replacement) ->
-spec replace(Subject, RE, Replacement, Options) -> iodata() | unicode:charlist() when
Subject :: iodata() | unicode:charlist(),
RE :: mp() | iodata() | unicode:charlist(),
- Replacement :: iodata() | unicode:charlist(),
+ Replacement :: iodata() | unicode:charlist() | replace_fun(),
Options :: [Option],
Option :: anchored | global | notbol | noteol | notempty
| notempty_atstart
@@ -380,11 +382,11 @@ replace(Subject,RE,Replacement) ->
replace(Subject,RE,Replacement,Options) ->
try
- {NewOpt,Convert} = process_repl_params(Options,iodata),
- Unicode = check_for_unicode(RE, Options),
- FlatSubject = to_binary(Subject, Unicode),
- FlatReplacement = to_binary(Replacement, Unicode),
- IoList = do_replace(FlatSubject,Subject,RE,FlatReplacement,NewOpt),
+ {NewOpt,Convert} = process_repl_params(Options,iodata),
+ Unicode = check_for_unicode(RE, Options),
+ FlatSubject = to_binary(Subject, Unicode),
+ Replacement1 = normalize_replacement(Replacement, Unicode),
+ IoList = do_replace(FlatSubject,Subject,RE,Replacement1,NewOpt),
case Convert of
iodata ->
IoList;
@@ -412,6 +414,10 @@ replace(Subject,RE,Replacement,Options) ->
badarg_with_info([Subject,RE,Replacement,Options])
end.
+normalize_replacement(Replacement, _Unicode) when is_function(Replacement, 2) ->
+ Replacement;
+normalize_replacement(Replacement, Unicode) ->
+ to_binary(Replacement, Unicode).
do_replace(FlatSubject,Subject,RE,Replacement,Options) ->
case re:run(FlatSubject,RE,Options) of
@@ -512,7 +518,9 @@ precomp_repl(<<X,Rest/binary>>) ->
[<<X,BHead/binary>> | T0];
Other ->
[<<X>> | Other]
- end.
+ end;
+precomp_repl(Repl) when is_function(Repl) ->
+ Repl.
@@ -540,6 +548,16 @@ do_mlist(Whole,Subject,Pos,Repl,[[{MPos,Count} | Sub] | Tail])
[NewData | do_mlist(Whole,Rest,Pos+EatLength,Repl,Tail)].
+do_replace(Subject, Repl, SubExprs0) when is_function(Repl) ->
+ All = binary:part(Subject, hd(SubExprs0)),
+ SubExprs1 =
+ [ if
+ Pos >= 0, Len > 0 ->
+ binary:part(Subject, Pos, Len);
+ true ->
+ <<>>
+ end || {Pos, Len} <- tl(SubExprs0) ],
+ Repl(All, SubExprs1);
do_replace(_,[Bin],_) when is_binary(Bin) ->
Bin;
do_replace(Subject,Repl,SubExprs0) ->
diff --git a/lib/stdlib/src/sets.erl b/lib/stdlib/src/sets.erl
index bdc0ed40f3..dccc6dcf3a 100644
--- a/lib/stdlib/src/sets.erl
+++ b/lib/stdlib/src/sets.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -89,12 +89,12 @@
%%------------------------------------------------------------------------------
%% new() -> Set
--spec new() -> set().
+-spec new() -> set(none()).
new() ->
Empty = mk_seg(?seg_size),
#set{empty = Empty, segs = {Empty}}.
--spec new([{version, 1..2}]) -> set().
+-spec new([{version, 1..2}]) -> set(none()).
new([{version, 2}]) ->
#{};
new(Opts) ->
@@ -358,31 +358,51 @@ is_disjoint_1(Set, Iter) ->
Set2 :: set(Element),
Set3 :: set(Element).
-subtract(#{}=S1, #{}=S2) ->
- Next = maps:next(maps:iterator(S1)),
- subtract_heuristic(Next, [], [], floor(map_size(S1) * 0.75), S1, S2);
-subtract(S1, S2) ->
- filter(fun (E) -> not is_element(E, S2) end, S1).
+subtract(#{}=LHS, #{}=RHS) ->
+ LSize = map_size(LHS),
+ RSize = map_size(RHS),
+
+ case RSize =< (LSize div 4) of
+ true ->
+ %% If we're guaranteed to keep more than 75% of the keys, it's
+ %% always cheaper to delete them one-by-one from the start.
+ Next = maps:next(maps:iterator(RHS)),
+ subtract_decided(Next, LHS, RHS);
+ false ->
+ %% We might delete more than 25% of the keys. Dynamically
+ %% transition to deleting elements one-by-one if we can determine
+ %% that we'll keep more than 75%.
+ KeepThreshold = (LSize * 3) div 4,
+ Next = maps:next(maps:iterator(LHS)),
+ subtract_heuristic(Next, [], [], KeepThreshold, LHS, RHS)
+ end;
+subtract(LHS, RHS) ->
+ filter(fun (E) -> not is_element(E, RHS) end, LHS).
-%% If we are keeping more than 75% of the keys, then it is
-%% cheaper to delete them. Stop accumulating and start deleting.
subtract_heuristic(Next, _Keep, Delete, 0, Acc, Reference) ->
- subtract_decided(Next, remove_keys(Delete, Acc), Reference);
-subtract_heuristic({Key, _Value, Iterator}, Keep, Delete, KeepCount, Acc, Reference) ->
+ %% We've kept more than 75% of the keys, transition to removing them
+ %% one-by-one.
+ subtract_decided(Next, remove_keys(Delete, Acc), Reference);
+subtract_heuristic({Key, _Value, Iterator}, Keep, Delete,
+ KeepCount, Acc, Reference) ->
Next = maps:next(Iterator),
case Reference of
- #{Key := _} ->
- subtract_heuristic(Next, Keep, [Key | Delete], KeepCount, Acc, Reference);
+ #{ Key := _ } ->
+ subtract_heuristic(Next, Keep, [Key | Delete],
+ KeepCount, Acc, Reference);
_ ->
- subtract_heuristic(Next, [Key | Keep], Delete, KeepCount - 1, Acc, Reference)
+ subtract_heuristic(Next, [Key | Keep], Delete,
+ KeepCount - 1, Acc, Reference)
end;
subtract_heuristic(none, Keep, _Delete, _Count, _Acc, _Reference) ->
maps:from_keys(Keep, ?VALUE).
subtract_decided({Key, _Value, Iterator}, Acc, Reference) ->
case Reference of
- #{Key := _} ->
- subtract_decided(maps:next(Iterator), maps:remove(Key, Acc), Reference);
+ #{ Key := _ } ->
+ subtract_decided(maps:next(Iterator),
+ maps:remove(Key, Acc),
+ Reference);
_ ->
subtract_decided(maps:next(Iterator), Acc, Reference)
end;
@@ -446,23 +466,12 @@ fold_1(Fun, Acc, Iter) ->
Set1 :: set(Element),
Set2 :: set(Element).
filter(F, #{}=D) when is_function(F, 1)->
- maps:from_keys(filter_1(F, maps:iterator(D)), ?VALUE);
+ %% For this purpose, it is more efficient to use
+ %% maps:from_keys than a map comprehension.
+ maps:from_keys([K || K := _ <- D, F(K)], ?VALUE);
filter(F, #set{}=D) when is_function(F, 1)->
filter_set(F, D).
-filter_1(Fun, Iter) ->
- case maps:next(Iter) of
- {K, _, NextIter} ->
- case Fun(K) of
- true ->
- [K | filter_1(Fun, NextIter)];
- false ->
- filter_1(Fun, NextIter)
- end;
- none ->
- []
- end.
-
%% get_slot(Hashdb, Key) -> Slot.
%% Get the slot. First hash on the new range, if we hit a bucket
%% which has not been split use the unsplit buddy bucket.
diff --git a/lib/stdlib/src/shell.erl b/lib/stdlib/src/shell.erl
index 7de78758b0..9f0bc0c811 100644
--- a/lib/stdlib/src/shell.erl
+++ b/lib/stdlib/src/shell.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,10 +20,13 @@
-module(shell).
-export([start/0, start/1, start/2, server/1, server/2, history/1, results/1]).
--export([whereis_evaluator/0, whereis_evaluator/1]).
+-export([get_state/0, get_function/2]).
-export([start_restricted/1, stop_restricted/0]).
--export([local_allowed/3, non_local_allowed/3]).
+-export([local_func/0, local_func/1, local_allowed/3, non_local_allowed/3]).
-export([catch_exception/1, prompt_func/1, strings/1]).
+-export([start_interactive/0, start_interactive/1]).
+-export([read_and_add_records/5]).
+-export([whereis/0]).
-define(LINEMAX, 30).
-define(CHAR_MAX, 60).
@@ -36,7 +39,11 @@
-define(RECORDS, shell_records).
-define(MAXSIZE_HEAPBINARY, 64).
-
+-record(shell_state,{
+ bindings = [],
+ records = [],
+ functions = []
+ }).
%% When used as the fallback restricted shell callback module...
local_allowed(q,[],State) ->
{true,State};
@@ -48,6 +55,24 @@ non_local_allowed({init,stop},[],State) ->
non_local_allowed(_,_,State) ->
{false,State}.
+-spec start_interactive() -> ok | {error, already_started}.
+start_interactive() ->
+ user_drv:start_shell().
+-spec start_interactive(noshell | mfa()) ->
+ ok | {error, already_started};
+ ({remote, string()}) ->
+ ok | {error, already_started | noconnection};
+ ({node(), mfa()} | {remote, string(), mfa()}) ->
+ ok | {error, already_started | noconnection | badfile | nofile | on_load_failure}.
+start_interactive({Node, {M, F, A}}) ->
+ user_drv:start_shell(#{ initial_shell => {Node, M, F ,A} });
+start_interactive(InitialShell) ->
+ user_drv:start_shell(#{ initial_shell => InitialShell }).
+
+-spec whereis() -> pid() | undefined.
+whereis() ->
+ group:whereis_shell().
+
-spec start() -> pid().
start() ->
@@ -60,62 +85,16 @@ start(NoCtrlG) ->
start(NoCtrlG, StartSync) ->
_ = code:ensure_loaded(user_default),
- spawn(fun() -> server(NoCtrlG, StartSync) end).
-
-%% Find the pid of the current evaluator process.
--spec whereis_evaluator() -> 'undefined' | pid().
-
-whereis_evaluator() ->
- %% locate top group leader, always registered as user
- %% can be implemented by group (normally) or user
- %% (if oldshell or noshell)
- case whereis(user) of
- undefined ->
- undefined;
- User ->
- %% get user_drv pid from group, or shell pid from user
- case group:interfaces(User) of
- [] -> % old- or noshell
- case user:interfaces(User) of
- [] ->
- undefined;
- [{shell,Shell}] ->
- whereis_evaluator(Shell)
- end;
- [{user_drv,UserDrv}] ->
- %% get current group pid from user_drv
- case user_drv:interfaces(UserDrv) of
- [] ->
- undefined;
- [{current_group,Group}] ->
- %% get shell pid from group
- GrIfs = group:interfaces(Group),
- case lists:keyfind(shell, 1, GrIfs) of
- {shell, Shell} ->
- whereis_evaluator(Shell);
- false ->
- undefined
- end
- end
- end
- end.
-
--spec whereis_evaluator(pid()) -> 'undefined' | pid().
-
-whereis_evaluator(Shell) ->
- case process_info(Shell, dictionary) of
- {dictionary,Dict} ->
- case lists:keyfind(evaluator, 1, Dict) of
- {_, Eval} when is_pid(Eval) ->
- Eval;
- _ ->
- undefined
- end;
- _ ->
- undefined
- end.
-
-%% Call this function to start a user restricted shell
+ Ancestors = [self() | case get('$ancestors') of
+ undefined -> [];
+ Anc -> Anc
+ end],
+ spawn(fun() ->
+ put('$ancestors', Ancestors),
+ server(NoCtrlG, StartSync)
+ end).
+
+%% Call this function to start a user restricted shell
%% from a normal shell session.
-spec start_restricted(Module) -> {'error', Reason} when
Module :: module(),
@@ -123,16 +102,16 @@ whereis_evaluator(Shell) ->
start_restricted(RShMod) when is_atom(RShMod) ->
case code:ensure_loaded(RShMod) of
- {module,RShMod} ->
- application:set_env(stdlib, restricted_shell, RShMod),
+ {module,RShMod} ->
+ application:set_env(stdlib, restricted_shell, RShMod),
exit(restricted_shell_started);
- {error,What} = Error ->
- error_logger:error_report(
- lists:flatten(
- io_lib:fwrite(
- "Restricted shell module ~w not found: ~tp\n",
- [RShMod,What]))),
- Error
+ {error,What} = Error ->
+ error_logger:error_report(
+ lists:flatten(
+ io_lib:fwrite(
+ "Restricted shell module ~w not found: ~tp\n",
+ [RShMod,What]))),
+ Error
end.
-spec stop_restricted() -> no_return().
@@ -152,27 +131,27 @@ server(NoCtrlG, StartSync) ->
%%% We subscribe with init to get a notification of when.
%%% In older releases we didn't syncronize the shell with init, but let it
-%%% start in parallell with other system processes. This was bad since
+%%% start in parallell with other system processes. This was bad since
%%% accessing the shell too early could interfere with the boot procedure.
%%% Still, by means of a flag, we make it possible to start the shell the
-%%% old way (for backwards compatibility reasons). This should however not
+%%% old way (for backwards compatibility reasons). This should however not
%%% be used unless for very special reasons necessary.
-spec server(boolean()) -> 'terminated'.
server(StartSync) ->
case init:get_argument(async_shell_start) of
- {ok,_} ->
- ok; % no sync with init
- _ when not StartSync ->
- ok;
- _ ->
- case init:notify_when_started(self()) of
- started ->
- ok;
- _ ->
- init:wait_until_started()
- end
+ {ok,_} ->
+ ok; % no sync with init
+ _ when not StartSync ->
+ ok;
+ _ ->
+ case init:notify_when_started(self()) of
+ started ->
+ ok;
+ _ ->
+ init:wait_until_started()
+ end
end,
%% Our spawner has fixed the process groups.
Bs = erl_eval:new_bindings(),
@@ -183,57 +162,71 @@ server(StartSync) ->
RT = ets:new(?RECORDS, [public,ordered_set]),
_ = initiate_records(Bs, RT),
process_flag(trap_exit, true),
+ %% Store function definitions and types in an ets table.
+ FT = ets:new(user_functions, [public,ordered_set]),
%% Check if we're in user restricted mode.
- RShErr =
- case application:get_env(stdlib, restricted_shell) of
- {ok,RShMod} when is_atom(RShMod) ->
- io:fwrite(<<"Restricted ">>, []),
- case code:ensure_loaded(RShMod) of
- {module,RShMod} ->
- undefined;
- {error,What} ->
- {RShMod,What}
- end;
+ RShErr =
+ case application:get_env(stdlib, restricted_shell) of
+ {ok,RShMod} when is_atom(RShMod) ->
+ io:fwrite(<<"Restricted ">>, []),
+ case code:ensure_loaded(RShMod) of
+ {module,RShMod} ->
+ undefined;
+ {error,What} ->
+ {RShMod,What}
+ end;
{ok, Term} ->
{Term,not_an_atom};
- undefined ->
- undefined
- end,
-
- case get(no_control_g) of
- true ->
- io:fwrite(<<"Eshell V~s\n">>, [erlang:system_info(version)]);
- _undefined_or_false ->
- io:fwrite(<<"Eshell V~s (abort with ^G)\n">>,
- [erlang:system_info(version)])
+ undefined ->
+ undefined
+ end,
+
+ JCL =
+ case get(no_control_g) of
+ true -> " (type help(). for help)";
+ _ -> " (press Ctrl+G to abort, type help(). for help)"
+ end,
+ DefaultSessionSlogan =
+ io_lib:format(<<"Eshell V~s">>, [erlang:system_info(version)]),
+ SessionSlogan =
+ case application:get_env(stdlib, shell_session_slogan, DefaultSessionSlogan) of
+ SloganFun when is_function(SloganFun, 0) ->
+ SloganFun();
+ Slogan ->
+ Slogan
+ end,
+ try
+ io:fwrite("~ts~ts\n",[unicode:characters_to_list(SessionSlogan),JCL])
+ catch _:_ ->
+ io:fwrite("Warning! The slogan \"~p\" could not be printed.\n",[SessionSlogan])
end,
erase(no_control_g),
case RShErr of
- undefined ->
+ undefined ->
ok;
- {RShMod2,What2} ->
+ {RShMod2,What2} ->
io:fwrite(
- ("Warning! Restricted shell module ~w not found: ~tp.\n"
- "Only the commands q() and init:stop() will be allowed!\n"),
+ ("Warning! Restricted shell module ~w not found: ~tp.\n"
+ "Only the commands q() and init:stop() will be allowed!\n"),
[RShMod2,What2]),
application:set_env(stdlib, restricted_shell, ?MODULE)
end,
{History,Results} = check_and_get_history_and_results(),
- server_loop(0, start_eval(Bs, RT, []), Bs, RT, [], History, Results).
+ server_loop(0, start_eval(Bs, RT, FT, []), Bs, RT, FT, [], History, Results).
-server_loop(N0, Eval_0, Bs00, RT, Ds00, History0, Results0) ->
+server_loop(N0, Eval_0, Bs00, RT, FT, Ds00, History0, Results0) ->
N = N0 + 1,
- {Eval_1,Bs0,Ds0,Prompt} = prompt(N, Eval_0, Bs00, RT, Ds00),
- {Res,Eval0} = get_command(Prompt, Eval_1, Bs0, RT, Ds0),
+ {Eval_1,Bs0,Ds0,Prompt} = prompt(N, Eval_0, Bs00, RT, FT, Ds00),
+ {Res,Eval0} = get_command(Prompt, Eval_1, Bs0, RT, FT, Ds0),
- case Res of
- {ok,Es0} ->
+ case Res of
+ {ok,Es0} ->
case expand_hist(Es0, N) of
{ok,Es} ->
- {V,Eval,Bs,Ds} = shell_cmd(Es, Eval0, Bs0, RT, Ds0, cmd),
+ {V,Eval,Bs,Ds} = shell_cmd(Es, Eval0, Bs0, RT, FT, Ds0, cmd),
{History,Results} = check_and_get_history_and_results(),
add_cmd(N, Es, V),
HB1 = del_cmd(command, N - History, N - History0, false),
@@ -247,42 +240,83 @@ server_loop(N0, Eval_0, Bs00, RT, Ds00, History0, Results0) ->
true ->
ok
end,
- server_loop(N, Eval, Bs, RT, Ds, History, Results);
+ server_loop(N, Eval, Bs, RT, FT, Ds, History, Results);
{error,E} ->
fwrite_severity(benign, <<"~ts">>, [E]),
- server_loop(N0, Eval0, Bs0, RT, Ds0, History0, Results0)
+ server_loop(N0, Eval0, Bs0, RT, FT, Ds0, History0, Results0)
end;
- {error,{Location,Mod,What}} ->
+ {error,{Location,Mod,What}} ->
fwrite_severity(benign, <<"~s: ~ts">>,
[pos(Location), Mod:format_error(What)]),
- server_loop(N0, Eval0, Bs0, RT, Ds0, History0, Results0);
- {error,terminated} -> %Io process terminated
- exit(Eval0, kill),
- terminated;
- {error,interrupted} -> %Io process interrupted us
- exit(Eval0, kill),
- {_,Eval,_,_} = shell_rep(Eval0, Bs0, RT, Ds0),
- server_loop(N0, Eval, Bs0, RT, Ds0, History0, Results0);
- {error,tokens} -> %Most probably character > 255
- fwrite_severity(benign, <<"~w: Invalid tokens.">>,
+ server_loop(N0, Eval0, Bs0, RT, FT, Ds0, History0, Results0);
+ {error,terminated} -> %Io process terminated
+ exit(Eval0, kill),
+ terminated;
+ {error,interrupted} -> %Io process interrupted us
+ exit(Eval0, kill),
+ {_,Eval,_,_} = shell_rep(Eval0, Bs0, RT, FT, Ds0),
+ server_loop(N0, Eval, Bs0, RT, FT, Ds0, History0, Results0);
+ {error,tokens} -> %Most probably character > 255
+ fwrite_severity(benign, <<"~w: Invalid tokens.">>,
[N]),
- server_loop(N0, Eval0, Bs0, RT, Ds0, History0, Results0);
- eof ->
+ server_loop(N0, Eval0, Bs0, RT, FT, Ds0, History0, Results0);
+ eof ->
fwrite_severity(fatal, <<"Terminating erlang (~w)">>, [node()]),
- halt()
+ halt()
end.
-get_command(Prompt, Eval, Bs, RT, Ds) ->
+get_command(Prompt, Eval, Bs, RT, FT, Ds) ->
+ Ancestors = [self() | get('$ancestors')],
ResWordFun = fun erl_scan:reserved_word/1,
Parse =
fun() ->
+ put('$ancestors', Ancestors),
exit(
case
io:scan_erl_exprs(group_leader(), Prompt, {1,1},
[text,{reserved_word_fun,ResWordFun}])
of
{ok,Toks,_EndPos} ->
- erl_eval:extended_parse_exprs(Toks);
+ %% NOTE: we can handle function definitions, records and soon type declarations
+ %% but this cannot be handled by the function which only expects erl_parse:abstract_expressions()
+ %% for now just pattern match against those types and pass the string to shell local func.
+ case Toks of
+ [{'-', _}, {atom, _, Atom}|_] ->
+ SpecialCase = fun(LocalFunc) ->
+ FakeLine = begin
+ case erl_parse:parse_form(Toks) of
+ {ok, Def} -> lists:flatten(erl_pp:form(Def));
+ E ->
+ exit(E)
+ end
+ end,
+ {done, {ok, FakeResult, _}, _} = erl_scan:tokens(
+ [], atom_to_list(LocalFunc) ++ "(\""++FakeLine++"\").\n",
+ {1,1}, [text,{reserved_word_fun,fun erl_scan:reserved_word/1}]),
+ erl_eval:extended_parse_exprs(FakeResult)
+ end,
+ case Atom of
+ record -> SpecialCase(rd);
+ spec -> SpecialCase(ft);
+ type -> SpecialCase(td)
+ end;
+ [{atom, _, FunName}, {'(', _}|_] ->
+ case erl_parse:parse_form(Toks) of
+ {ok, FunDef} ->
+ case {edlin_expand:shell_default_or_bif(atom_to_list(FunName)), shell:local_func(FunName)} of
+ {"user_defined", false} ->
+ FakeLine =reconstruct(FunDef, FunName),
+ {done, {ok, FakeResult, _}, _} = erl_scan:tokens(
+ [], "fd("++ atom_to_list(FunName) ++ ", " ++ FakeLine ++ ").\n",
+ {1,1}, [text,{reserved_word_fun,fun erl_scan:reserved_word/1}]),
+ erl_eval:extended_parse_exprs(FakeResult);
+ _ -> erl_eval:extended_parse_exprs(Toks)
+ end;
+ _ -> erl_eval:extended_parse_exprs(Toks)
+ end;
+ _ ->
+ erl_eval:extended_parse_exprs(Toks)
+ end;
{eof,_EndPos} ->
eof;
{error,ErrorInfo,_EndPos} ->
@@ -297,30 +331,62 @@ get_command(Prompt, Eval, Bs, RT, Ds) ->
Else ->
Else
end
- )
+ )
end,
Pid = spawn_link(Parse),
- get_command1(Pid, Eval, Bs, RT, Ds).
-
-get_command1(Pid, Eval, Bs, RT, Ds) ->
+ get_command1(Pid, Eval, Bs, RT, FT, Ds).
+
+reconstruct(Fun, Name) ->
+ lists:flatten(erl_pp:expr(reconstruct1(Fun, Name))).
+reconstruct1({function, Anno, Name, Arity, Clauses}, Name) ->
+ {named_fun, Anno, 'RecursiveFuncVar', reconstruct1(Clauses, Name, Arity)}.
+reconstruct1([{call, Anno, {atom, Anno1, Name}, Args}|Body], Name, Arity) when length(Args) =:= Arity ->
+ [{call, Anno, {var, Anno1, 'RecursiveFuncVar'}, reconstruct1(Args, Name, Arity)}| reconstruct1(Body, Name, Arity)];
+reconstruct1([{call, Anno, {atom, Anno1, Name}, Args}|Body], Name, Arity) -> % arity not the same
+ [{call, Anno, {remote, Anno1, {atom, Anno1, shell_default}, {atom, Anno1, Name}}, reconstruct1(Args, Name, Arity)}|
+ reconstruct1(Body, Name, Arity)];
+reconstruct1([{call, Anno, {atom, Anno1, Fun}, Args}|Body], Name, Arity) -> % Name not the same
+ case {edlin_expand:shell_default_or_bif(atom_to_list(Fun)), shell:local_func(Fun)} of
+ {"user_defined", false} ->
+ [{call, Anno, {remote, Anno1, {atom, Anno1, shell_default}, {atom, Anno1, Fun}}, reconstruct1(Args, Name, Arity)}|
+ reconstruct1(Body, Name, Arity)];
+ {"shell_default", false} ->
+ [{call, Anno, {remote, Anno1, {atom, Anno1, shell_default}, {atom, Anno1, Fun}}, reconstruct1(Args, Name, Arity)}| reconstruct1(Body, Name, Arity)];
+ {"erlang", false} ->
+ [{call, Anno, {remote, Anno1, {atom, Anno1, erlang}, {atom, Anno1, Fun}}, reconstruct1(Args, Name, Arity)}| reconstruct1(Body, Name, Arity)];
+ {_, true} ->
+ [{call, Anno, {atom, Anno1, Fun}, reconstruct1(Args, Name, Arity)}| reconstruct1(Body, Name, Arity)]
+ end;
+reconstruct1([E|Body], Name, Arity) when is_tuple(E) ->
+ [list_to_tuple(reconstruct1(tuple_to_list(E), Name, Arity))|reconstruct1(Body, Name, Arity)];
+reconstruct1([E|Body], Name, Arity) when is_list(E) ->
+ [reconstruct1(E, Name, Arity)|reconstruct1(Body, Name, Arity)];
+reconstruct1([E|Body], Name, Arity) ->
+ [E|reconstruct1(Body, Name, Arity)];
+reconstruct1([], _, _) -> [].
+
+get_command1(Pid, Eval, Bs, RT, FT, Ds) ->
receive
- {'EXIT', Pid, Res} ->
- {Res, Eval};
- {'EXIT', Eval, {Reason,Stacktrace}} ->
+ {shell_state, From} ->
+ From ! {shell_state, Bs, RT, FT},
+ get_command1(Pid, Eval, Bs, RT, FT, Ds);
+ {'EXIT', Pid, Res} ->
+ {Res, Eval};
+ {'EXIT', Eval, {Reason,Stacktrace}} ->
report_exception(error, {Reason,Stacktrace}, RT),
- get_command1(Pid, start_eval(Bs, RT, Ds), Bs, RT, Ds);
- {'EXIT', Eval, Reason} ->
+ get_command1(Pid, start_eval(Bs, RT, FT, Ds), Bs, RT, FT, Ds);
+ {'EXIT', Eval, Reason} ->
report_exception(error, {Reason,[]}, RT),
- get_command1(Pid, start_eval(Bs, RT, Ds), Bs, RT, Ds)
+ get_command1(Pid, start_eval(Bs, RT, FT, Ds), Bs, RT, FT, Ds)
end.
-prompt(N, Eval0, Bs0, RT, Ds0) ->
+prompt(N, Eval0, Bs0, RT, FT, Ds0) ->
case get_prompt_func() of
{M,F} ->
A = erl_anno:new(1),
L = {cons,A,{tuple,A,[{atom,A,history},{integer,A,N}]},{nil,A}},
C = {call,A,{remote,A,{atom,A,M},{atom,A,F}},[L]},
- {V,Eval,Bs,Ds} = shell_cmd([C], Eval0, Bs0, RT, Ds0, pmt),
+ {V,Eval,Bs,Ds} = shell_cmd([C], Eval0, Bs0, RT, FT, Ds0, pmt),
{Eval,Bs,Ds,case V of
{pmt,Val} ->
Val;
@@ -333,15 +399,13 @@ prompt(N, Eval0, Bs0, RT, Ds0) ->
end.
get_prompt_func() ->
- case application:get_env(stdlib, shell_prompt_func) of
- {ok,{M,F}=PromptFunc} when is_atom(M), is_atom(F) ->
+ case application:get_env(stdlib, shell_prompt_func, default) of
+ {M,F}=PromptFunc when is_atom(M), is_atom(F) ->
PromptFunc;
- {ok,default=Default} ->
+ default=Default ->
Default;
- {ok,Term} ->
+ Term ->
bad_prompt_func(Term),
- default;
- undefined ->
default
end.
@@ -352,8 +416,8 @@ default_prompt(N) ->
%% Don't bother flattening the list irrespective of what the
%% I/O-protocol states.
case is_alive() of
- true -> io_lib:format(<<"(~s)~w> ">>, [node(), N]);
- false -> io_lib:format(<<"~w> ">>, [N])
+ true -> io_lib:format(<<"(~s)~w> ">>, [node(), N]);
+ false -> io_lib:format(<<"~w> ">>, [N])
end.
%% expand_hist(Expressions, CommandNumber)
@@ -392,7 +456,7 @@ expand_expr({record_field,A,R,Name,F}, C) ->
{record_field,A,expand_expr(R, C),Name,expand_expr(F, C)};
expand_expr({record,A,R,Name,Ups}, C) ->
{record,A,expand_expr(R, C),Name,expand_fields(Ups, C)};
-expand_expr({record_field,A,R,F}, C) -> %This is really illegal!
+expand_expr({record_field,A,R,F}, C) -> %This is really illegal!
{record_field,A,expand_expr(R, C),expand_expr(F, C)};
expand_expr({block,A,Es}, C) ->
{block,A,expand_exprs(Es, C)};
@@ -410,17 +474,17 @@ expand_expr({'receive',A,Cs,To,ToEs}, C) ->
expand_expr({call,A,{atom,_,e},[N]}, C) ->
case get_cmd(N, C) of
{undefined,_,_} ->
- no_command(N);
- {[Ce],_V,_CommandN} ->
- Ce;
- {Ces,_V,_CommandN} when is_list(Ces) ->
- {block,A,Ces}
+ no_command(N);
+ {[Ce],_V,_CommandN} ->
+ Ce;
+ {Ces,_V,_CommandN} when is_list(Ces) ->
+ {block,A,Ces}
end;
expand_expr({call,CA,{atom,VA,v},[N]}, C) ->
case get_cmd(N, C) of
- {_,undefined,_} ->
- no_command(N);
- {Ces,_V,CommandN} when is_list(Ces) ->
+ {undefined,_,_} ->
+ no_command(N);
+ {Ces,_V,CommandN} when is_list(Ces) ->
{call,CA,{atom,VA,v},[{integer,VA,CommandN}]}
end;
expand_expr({call,A,F,Args}, C) ->
@@ -444,7 +508,8 @@ expand_expr({clause,A,H,G,B}, C) ->
{clause,A,H, G, expand_exprs(B, C)};
expand_expr({bin,A,Fs}, C) ->
{bin,A,expand_bin_elements(Fs, C)};
-expand_expr(E, _C) -> % Constants.
+ %expand_expr({'-'})
+expand_expr(E, _C) -> % Constants.
E.
expand_cs([{clause,A,P,G,B}|Cs], C) ->
@@ -488,9 +553,9 @@ getc(N) ->
get_cmd(Num, C) ->
case catch erl_eval:expr(Num, erl_eval:new_bindings()) of
- {value,N,_} when N < 0 -> getc(C+N);
- {value,N,_} -> getc(N);
- _Other -> {undefined,undefined,undefined}
+ {value,N,_} when N < 0 -> getc(C+N);
+ {value,N,_} -> getc(N);
+ _Other -> {undefined,undefined,undefined}
end.
del_cmd(_Type, N, N0, HasBin) when N < N0 ->
@@ -521,61 +586,81 @@ has_bin(T, I) ->
has_bin(element(I, T)),
has_bin(T, I - 1).
+get_state() ->
+ whereis() ! {shell_state, self()},
+ receive
+ {shell_state, Bs, RT, FT} ->
+ #shell_state{bindings = Bs, records = ets:tab2list(RT), functions = ets:tab2list(FT)}
+ end.
+
+get_function(Func, Arity) ->
+ {shell_state, _Bs, _RT, FT} = get_state(),
+ try
+ {value, {_, Fun}} = lists:keysearch({function, {shell_default,Func,Arity}}, 1, FT),
+ Fun
+ catch _:_ ->
+ undefined
+ end.
+
%% shell_cmd(Sequence, Evaluator, Bindings, RecordTable, Dictionary, What)
%% shell_rep(Evaluator, Bindings, RecordTable, Dictionary) ->
-%% {Value,Evaluator,Bindings,Dictionary}
+%% {Value,Evaluator,Bindings,Dictionary}
%% Send a command to the evaluator and wait for the reply. Start a new
%% evaluator if necessary.
%% What = pmt | cmd. When evaluating a prompt ('pmt') the evaluated value
%% must not be displayed, and it has to be returned.
-shell_cmd(Es, Eval, Bs, RT, Ds, W) ->
+shell_cmd(Es, Eval, Bs, RT, FT, Ds, W) ->
Eval ! {shell_cmd,self(),{eval,Es}, W},
- shell_rep(Eval, Bs, RT, Ds).
+ shell_rep(Eval, Bs, RT, FT, Ds).
-shell_rep(Ev, Bs0, RT, Ds0) ->
+shell_rep(Ev, Bs0, RT, FT, Ds0) ->
receive
- {shell_rep,Ev,{value,V,Bs,Ds}} ->
- {V,Ev,Bs,Ds};
+ {shell_rep,Ev,{value,V,Bs,Ds}} ->
+ {V,Ev,Bs,Ds};
{shell_rep,Ev,{command_error,{Location,M,Error}}} ->
fwrite_severity(benign, <<"~s: ~ts">>,
[pos(Location), M:format_error(Error)]),
{{'EXIT',Error},Ev,Bs0,Ds0};
{shell_req,Ev,{get_cmd,N}} ->
- Ev ! {shell_rep,self(),getc(N)},
- shell_rep(Ev, Bs0, RT, Ds0);
- {shell_req,Ev,get_cmd} ->
- Ev ! {shell_rep,self(),get()},
- shell_rep(Ev, Bs0, RT, Ds0);
- {shell_req,Ev,exit} ->
- Ev ! {shell_rep,self(),exit},
- exit(normal);
- {shell_req,Ev,{update_dict,Ds}} -> % Update dictionary
- Ev ! {shell_rep,self(),ok},
- shell_rep(Ev, Bs0, RT, Ds);
+ Ev ! {shell_rep,self(),getc(N)},
+ shell_rep(Ev, Bs0, RT, FT, Ds0);
+ {shell_req,Ev,get_cmd} ->
+ Ev ! {shell_rep,self(),get()},
+ shell_rep(Ev, Bs0, RT, FT, Ds0);
+ {shell_req,Ev,exit} ->
+ Ev ! {shell_rep,self(),exit},
+ exit(normal);
+ {shell_req,Ev,{update_dict,Ds}} -> % Update dictionary
+ Ev ! {shell_rep,self(),ok},
+ shell_rep(Ev, Bs0, RT, FT, Ds);
+ {shell_state, From} ->
+ From ! {shell_state, Bs0, RT, FT},
+ shell_rep(Ev, Bs0, RT, FT, Ds0);
{ev_exit,{Ev,Class,Reason0}} -> % It has exited unnaturally
receive {'EXIT',Ev,normal} -> ok end,
- report_exception(Class, Reason0, RT),
+ report_exception(Class, Reason0, RT),
Reason = nocatch(Class, Reason0),
- {{'EXIT',Reason},start_eval(Bs0, RT, Ds0), Bs0, Ds0};
+ {{'EXIT',Reason},start_eval(Bs0, RT, FT, Ds0), Bs0, Ds0};
{ev_caught,{Ev,Class,Reason0}} -> % catch_exception is in effect
- report_exception(Class, benign, Reason0, RT),
+ report_exception(Class, benign, Reason0, RT),
Reason = nocatch(Class, Reason0),
{{'EXIT',Reason},Ev,Bs0,Ds0};
- {'EXIT',_Id,interrupt} -> % Someone interrupted us
- exit(Ev, kill),
- shell_rep(Ev, Bs0, RT, Ds0);
+ {'EXIT',_Id,interrupt} -> % Someone interrupted us
+ exit(Ev, kill),
+ shell_rep(Ev, Bs0, RT, FT, Ds0);
{'EXIT',Ev,{Reason,Stacktrace}} ->
report_exception(exit, {Reason,Stacktrace}, RT),
- {{'EXIT',Reason},start_eval(Bs0, RT, Ds0), Bs0, Ds0};
+ {{'EXIT',Reason},start_eval(Bs0, RT, FT, Ds0), Bs0, Ds0};
{'EXIT',Ev,Reason} ->
report_exception(exit, {Reason,[]}, RT),
- {{'EXIT',Reason},start_eval(Bs0, RT, Ds0), Bs0, Ds0};
- {'EXIT',_Id,R} ->
- exit(Ev, R),
- exit(R);
- _Other -> % Ignore everything else
- shell_rep(Ev, Bs0, RT, Ds0)
+ {{'EXIT',Reason},start_eval(Bs0, RT, FT, Ds0), Bs0, Ds0};
+ {'EXIT',_Id,R} ->
+ exit(Ev, R),
+ exit(R);
+ _Other -> % Ignore everything else
+ io:format("Throwing ~p~n", [_Other]),
+ shell_rep(Ev, Bs0, RT, FT, Ds0)
end.
nocatch(throw, {Term,Stack}) ->
@@ -599,9 +684,13 @@ report_exception(Class, Severity, {Reason,Stacktrace}, RT) ->
{put_chars, unicode, Str},
nl]).
-start_eval(Bs, RT, Ds) ->
+start_eval(Bs, RT, FT, Ds) ->
Self = self(),
- Eval = spawn_link(fun() -> evaluator(Self, Bs, RT, Ds) end),
+ Ancestors = [self() | get('$ancestors')],
+ Eval = spawn_link(fun() ->
+ put('$ancestors', Ancestors),
+ evaluator(Self, Bs, RT, FT, Ds)
+ end),
put(evaluator, Eval),
Eval.
@@ -609,45 +698,45 @@ start_eval(Bs, RT, Ds) ->
%% Evaluate expressions from the shell. Use the "old" variable bindings
%% and dictionary.
-evaluator(Shell, Bs, RT, Ds) ->
+evaluator(Shell, Bs, RT, FT, Ds) ->
init_dict(Ds),
case application:get_env(stdlib, restricted_shell) of
- undefined ->
- eval_loop(Shell, Bs, RT);
- {ok,RShMod} ->
- case get(restricted_shell_state) of
- undefined -> put(restricted_shell_state, []);
- _ -> ok
- end,
- put(restricted_expr_state, []),
- restricted_eval_loop(Shell, Bs, RT, RShMod)
+ undefined ->
+ eval_loop(Shell, Bs, RT, FT);
+ {ok,RShMod} ->
+ case get(restricted_shell_state) of
+ undefined -> put(restricted_shell_state, []);
+ _ -> ok
+ end,
+ put(restricted_expr_state, []),
+ restricted_eval_loop(Shell, Bs, RT, FT, RShMod)
end.
-eval_loop(Shell, Bs0, RT) ->
+eval_loop(Shell, Bs0, RT, FT) ->
receive
- {shell_cmd,Shell,{eval,Es},W} ->
- Ef = {value,
+ {shell_cmd,Shell,{eval,Es},W} ->
+ Ef = {value,
fun(MForFun, As) -> apply_fun(MForFun, As, Shell) end},
- Lf = local_func_handler(Shell, RT, Ef),
+ Lf = local_func_handler(Shell, RT, FT, Ef),
Bs = eval_exprs(Es, Shell, Bs0, RT, Lf, Ef, W),
- eval_loop(Shell, Bs, RT)
+ eval_loop(Shell, Bs, RT, FT)
end.
-restricted_eval_loop(Shell, Bs0, RT, RShMod) ->
+restricted_eval_loop(Shell, Bs0, RT, FT, RShMod) ->
receive
- {shell_cmd,Shell,{eval,Es}, W} ->
- {LFH,NLFH} = restrict_handlers(RShMod, Shell, RT),
+ {shell_cmd,Shell,{eval,Es}, W} ->
+ {LFH,NLFH} = restrict_handlers(RShMod, Shell, RT, FT),
put(restricted_expr_state, []),
Bs = eval_exprs(Es, Shell, Bs0, RT, {eval,LFH}, {value,NLFH}, W),
- restricted_eval_loop(Shell, Bs, RT, RShMod)
+ restricted_eval_loop(Shell, Bs, RT, FT, RShMod)
end.
eval_exprs(Es, Shell, Bs0, RT, Lf, Ef, W) ->
- try
+ try
{R,Bs2} = exprs(Es, Bs0, RT, Lf, Ef, W),
Shell ! {shell_rep,self(),R},
Bs2
- catch
+ catch
exit:normal ->
exit(normal);
Class:Reason:Stacktrace ->
@@ -672,8 +761,8 @@ do_catch(exit, restricted_shell_stopped) ->
do_catch(exit, restricted_shell_started) ->
false;
do_catch(_Class, _Reason) ->
- case application:get_env(stdlib, shell_catch_exception) of
- {ok, true} ->
+ case application:get_env(stdlib, shell_catch_exception, false) of
+ true ->
true;
_ ->
false
@@ -704,14 +793,14 @@ exprs([E0|Es], Bs1, RT, Lf, Ef, Bs0, W) ->
W =:= pmt ->
{W,V0};
true -> case result_will_be_saved() of
- true -> V0;
- false ->
- erlang:garbage_collect(),
- ignored
- end
+ true -> V0;
+ false ->
+ erlang:garbage_collect(),
+ ignored
+ end
end,
{{value,V,Bs,get()},Bs};
- true ->
+ true ->
exprs(Es, Bs, RT, Lf, Ef, Bs0, W)
end;
{error,Error} ->
@@ -734,7 +823,7 @@ used_record_defs(E, RT) ->
%% Be careful to return a list where used records come before
%% records that use them. The linter wants them ordered that way.
UR = case used_records(E, [], RT, []) of
- [] ->
+ [] ->
[];
L0 ->
L1 = lists:zip(L0, lists:seq(1, length(L0))),
@@ -782,7 +871,7 @@ used_records({call,_,{atom,_,record_info},[A,{atom,_,Name}]}) ->
used_records({call,A,{tuple,_,[M,F]},As}) ->
used_records({call,A,{remote,A,M,F},As});
used_records({type,_,record,[{atom,_,Name}|Fs]}) ->
- {name, Name, Fs};
+ {name, Name, Fs};
used_records(T) when is_tuple(T) ->
{expr, tuple_to_list(T)};
used_records(E) ->
@@ -801,63 +890,63 @@ severity_tag(fatal) -> <<"*** ">>;
severity_tag(serious) -> <<"** ">>;
severity_tag(benign) -> <<"* ">>.
-restrict_handlers(RShMod, Shell, RT) ->
- { fun(F,As,Binds) ->
- local_allowed(F, As, RShMod, Binds, Shell, RT)
+restrict_handlers(RShMod, Shell, RT, FT) ->
+ { fun(F,As,Binds) ->
+ local_allowed(F, As, RShMod, Binds, Shell, RT, FT)
end,
- fun(MF,As) ->
- non_local_allowed(MF, As, RShMod, Shell)
+ fun(MF,As) ->
+ non_local_allowed(MF, As, RShMod, Shell)
end }.
-define(BAD_RETURN(M, F, V),
try erlang:error(reason)
- catch _:_:S -> erlang:raise(exit, {restricted_shell_bad_return,V},
+ catch _:_:S -> erlang:raise(exit, {restricted_shell_bad_return,V},
[{M,F,3} | S])
end).
-local_allowed(F, As, RShMod, Bs, Shell, RT) when is_atom(F) ->
- {LFH,NLFH} = restrict_handlers(RShMod, Shell, RT),
- case not_restricted(F, As) of % Not restricted is the same as builtin.
- % variable and record manipulations local
- % to the shell process. Those are never
- % restricted.
- true ->
- local_func(F, As, Bs, Shell, RT, {eval,LFH}, {value,NLFH});
- false ->
+local_allowed(F, As, RShMod, Bs, Shell, RT, FT) when is_atom(F) ->
+ {LFH,NLFH} = restrict_handlers(RShMod, Shell, RT, FT),
+ case not_restricted(F, As) of % Not restricted is the same as builtin.
+ % variable and record manipulations local
+ % to the shell process. Those are never
+ % restricted.
+ true ->
+ local_func(F, As, Bs, Shell, RT, FT, {eval,LFH}, {value,NLFH});
+ false ->
{AsEv,Bs1} = expr_list(As, Bs, {eval,LFH}, {value,NLFH}),
- case RShMod:local_allowed(F, AsEv, {get(restricted_shell_state),
- get(restricted_expr_state)}) of
- {Result,{RShShSt,RShExprSt}} ->
- put(restricted_shell_state, RShShSt),
- put(restricted_expr_state, RShExprSt),
- if not Result ->
- shell_req(Shell, {update_dict,get()}),
- exit({restricted_shell_disallowed,{F,AsEv}});
- true -> % This is never a builtin,
- % those are handled above.
- non_builtin_local_func(F,AsEv,Bs1)
- end;
- Unexpected -> % The user supplied non conforming module
+ case RShMod:local_allowed(F, AsEv, {get(restricted_shell_state),
+ get(restricted_expr_state)}) of
+ {Result,{RShShSt,RShExprSt}} ->
+ put(restricted_shell_state, RShShSt),
+ put(restricted_expr_state, RShExprSt),
+ if not Result ->
+ shell_req(Shell, {update_dict,get()}),
+ exit({restricted_shell_disallowed,{F,AsEv}});
+ true -> % This is never a builtin,
+ % those are handled above.
+ non_builtin_local_func(F,AsEv,Bs1, FT)
+ end;
+ Unexpected -> % The user supplied non conforming module
?BAD_RETURN(RShMod, local_allowed, Unexpected)
- end
+ end
end.
non_local_allowed(MForFun, As, RShMod, Shell) ->
case RShMod:non_local_allowed(MForFun, As, {get(restricted_shell_state),
- get(restricted_expr_state)}) of
- {Result,{RShShSt,RShExprSt}} ->
- put(restricted_shell_state, RShShSt),
- put(restricted_expr_state, RShExprSt),
- case Result of
- false ->
- shell_req(Shell, {update_dict,get()}),
- exit({restricted_shell_disallowed,{MForFun,As}});
+ get(restricted_expr_state)}) of
+ {Result,{RShShSt,RShExprSt}} ->
+ put(restricted_shell_state, RShShSt),
+ put(restricted_expr_state, RShExprSt),
+ case Result of
+ false ->
+ shell_req(Shell, {update_dict,get()}),
+ exit({restricted_shell_disallowed,{MForFun,As}});
{redirect, NewMForFun, NewAs} ->
apply_fun(NewMForFun, NewAs, Shell);
- _ ->
- apply_fun(MForFun, As, Shell)
- end;
- Unexpected -> % The user supplied non conforming module
+ _ ->
+ apply_fun(MForFun, As, Shell)
+ end;
+ Unexpected -> % The user supplied non conforming module
?BAD_RETURN(RShMod, non_local_allowed, Unexpected)
end.
@@ -880,6 +969,16 @@ not_restricted(catch_exception, [_]) ->
true;
not_restricted(exit, []) ->
true;
+not_restricted(fl, []) ->
+ true;
+not_restricted(fd, [_]) ->
+ true;
+not_restricted(ft, [_]) ->
+ true;
+not_restricted(td, [_]) ->
+ true;
+not_restricted(rd, [_]) ->
+ true;
not_restricted(rd, [_,_]) ->
true;
not_restricted(rf, []) ->
@@ -902,7 +1001,7 @@ not_restricted(_, _) ->
false.
%% When erlang:garbage_collect() is called from the shell,
-%% the shell process process that spawned the evaluating
+%% the shell process process that spawned the evaluating
%% process is garbage collected as well.
%% To garbage collect the evaluating process only the command
%% garbage_collect(self()). can be used.
@@ -940,7 +1039,7 @@ expand_records(UsedRecords, E0) ->
prep_rec({value,_CommandN,_V}=Value) ->
%% erl_expand_records cannot handle the history expansion {value,_,_}.
{atom,Value,ok};
-prep_rec({atom,{value,_CommandN,_V}=Value,ok}) ->
+prep_rec({atom,{value,_CommandN,_V}=Value,ok}) ->
%% Undo the effect of the previous clause...
Value;
prep_rec(T) when is_tuple(T) -> list_to_tuple(prep_rec(tuple_to_list(T)));
@@ -952,39 +1051,101 @@ init_dict([{K,V}|Ds]) ->
init_dict(Ds);
init_dict([]) -> true.
-%% local_func(Function, Args, Bindings, Shell, RecordTable,
+%% local_func(Function, Args, Bindings, Shell, RecordTable,
%% LocalFuncHandler, ExternalFuncHandler) -> {value,Val,Bs}
%% Evaluate local functions, including shell commands.
%%
-%% Note that the predicate not_restricted/2 has to correspond to what's
-%% handled internally - it should return 'true' for all local functions
-%% handled in this module (i.e. those that are not eventually handled by
+%% Note that the predicate not_restricted/2 has to correspond to what's
+%% handled internally - it should return 'true' for all local functions
+%% handled in this module (i.e. those that are not eventually handled by
%% non_builtin_local_func/3 (user_default/shell_default).
-
-local_func(v, [{integer,_,V}], Bs, Shell, _RT, _Lf, _Ef) ->
+local_func() -> [v,h,b,f,fl,rd,rf,rl,rp,rr,history,results,catch_exception].
+local_func(Func) ->
+ lists:member(Func, local_func()).
+local_func(v, [{integer,_,V}], Bs, Shell, _RT, _FT, _Lf, _Ef) ->
%% This command is validated and expanded prior.
{_Ces,Value,_N} = shell_req(Shell, {get_cmd, V}),
{value,Value,Bs};
-local_func(h, [], Bs, Shell, RT, _Lf, _Ef) ->
+local_func(h, [], Bs, Shell, RT, _FT, _Lf, _Ef) ->
Cs = shell_req(Shell, get_cmd),
Cs1 = lists:filter(fun({{command, _},_}) -> true;
- ({{result, _},_}) -> true;
- (_) -> false
- end,
- Cs),
+ ({{result, _},_}) -> true;
+ (_) -> false
+ end,
+ Cs),
Cs2 = lists:map(fun({{T, N}, V}) -> {{N, T}, V} end,
- Cs1),
+ Cs1),
Cs3 = lists:keysort(1, Cs2),
{value,list_commands(Cs3, RT),Bs};
-local_func(b, [], Bs, _Shell, RT, _Lf, _Ef) ->
+local_func(b, [], Bs, _Shell, RT, _FT, _Lf, _Ef) ->
{value,list_bindings(erl_eval:bindings(Bs), RT),Bs};
-local_func(f, [], _Bs, _Shell, _RT, _Lf, _Ef) ->
+local_func(f, [], _Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
{value,ok,erl_eval:new_bindings()};
-local_func(f, [{var,_,Name}], Bs, _Shell, _RT, _Lf, _Ef) ->
+local_func(f, [{var,_,Name}], Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
{value,ok,erl_eval:del_binding(Name, Bs)};
-local_func(f, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) ->
+local_func(f, [_Other], _Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
erlang:raise(error, function_clause, [{shell,f,1}]);
-local_func(rd, [{atom,_,RecName0},RecDef0], Bs, _Shell, RT, _Lf, _Ef) ->
+local_func(fl, [], Bs, _Shell, _RT, FT, _Lf, _Ef) ->
+ {value, ets:tab2list(FT), Bs};
+local_func(fd, [{atom,_,FunName}, FunExpr], Bs, _Shell, _RT, FT, _Lf, _Ef) ->
+ {value, Fun, []} = erl_eval:expr(FunExpr, []),
+ {arity, Arity} = erlang:fun_info(Fun, arity),
+ ets:insert(FT, [{{function, {shell_default, FunName, Arity}}, Fun}]),
+ {value, ok, Bs};
+local_func(fd, [_], _Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
+ erlang:raise(error, function_clause, [{shell, fd, 1}]);
+local_func(ft, [{string, _, TypeDef}], Bs, _Shell, _RT, FT, _Lf, _Ef) ->
+ case erl_scan:tokens([], TypeDef, {1,1}, [text,{reserved_word_fun,fun erl_scan:reserved_word/1}]) of
+ {done, {ok, Toks, _}, _} ->
+ case erl_parse:parse_form(Toks) of
+ {ok, {attribute,_,spec,{{FunName, Arity},_}}=AttrForm} ->
+ ets:insert(FT, [{{function_type, {shell_default, FunName, Arity}}, AttrForm}]),
+ {value, ok, Bs};
+ {error,{_Location,M,ErrDesc}} ->
+ ErrStr = io_lib:fwrite(<<"~ts">>, [M:format_error(ErrDesc)]),
+ exit(lists:flatten(ErrStr))
+ end;
+ {done, {error,{_Location, M, ErrDesc}, _}, _} ->
+ ErrStr = io_lib:fwrite(<<"~ts">>, [M:format_error(ErrDesc)]),
+ exit(lists:flatten(ErrStr))
+ end;
+local_func(ft, [_], _Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
+ erlang:raise(error, function_clause, [{shell, ft, 1}]);
+local_func(td, [{string, _, TypeDef}], Bs, _Shell, _RT, FT, _Lf, _Ef) ->
+ case erl_scan:tokens([], TypeDef, {1,1}, [text,{reserved_word_fun,fun erl_scan:reserved_word/1}]) of
+ {done, {ok, Toks, _}, _} ->
+ case erl_parse:parse_form(Toks) of
+ {ok, {attribute,_,type,{TypeName, _, _}}=AttrForm} ->
+ ets:insert(FT, [{{type, TypeName}, AttrForm}]),
+ {value, ok, Bs};
+ {error,{_Location,M,ErrDesc}} ->
+ ErrStr = io_lib:fwrite(<<"~ts">>, [M:format_error(ErrDesc)]),
+ exit(lists:flatten(ErrStr))
+ end;
+ {done, {error,{_Location, M, ErrDesc}, _}, _} ->
+ ErrStr = io_lib:fwrite(<<"~ts">>, [M:format_error(ErrDesc)]),
+ exit(lists:flatten(ErrStr))
+ end;
+local_func(td, [_], _Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
+ erlang:raise(error, function_clause, [{shell, td, 1}]);
+local_func(rd, [{string, _, TypeDef}], Bs, _Shell, RT, _FT, _Lf, _Ef) ->
+ case erl_scan:tokens([], TypeDef, {1,1}, [text,{reserved_word_fun,fun erl_scan:reserved_word/1}]) of
+ {done, {ok, Toks, _}, _} ->
+ case erl_parse:parse_form(Toks) of
+ {ok,{attribute,_,_,_}=AttrForm} ->
+ [_] = add_records([AttrForm], Bs, RT),
+ {value,ok,Bs};
+ {error,{_Location,M,ErrDesc}} ->
+ ErrStr = io_lib:fwrite(<<"~ts">>, [M:format_error(ErrDesc)]),
+ exit(lists:flatten(ErrStr))
+ end;
+ {done, {error,{_Location, M, ErrDesc}, _}, _} ->
+ ErrStr = io_lib:fwrite(<<"~ts">>, [M:format_error(ErrDesc)]),
+ exit(lists:flatten(ErrStr))
+ end;
+local_func(rd, [_], _Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
+ erlang:raise(error, function_clause, [{shell, rd, 1}]);
+local_func(rd, [{atom,_,RecName0},RecDef0], Bs, _Shell, RT, _FT, _Lf, _Ef) ->
RecDef = expand_value(RecDef0),
RDs = lists:flatten(erl_pp:expr(RecDef)),
RecName = io_lib:write_atom_as_latin1(RecName0),
@@ -998,99 +1159,105 @@ local_func(rd, [{atom,_,RecName0},RecDef0], Bs, _Shell, RT, _Lf, _Ef) ->
ErrStr = io_lib:fwrite(<<"~ts">>, [M:format_error(ErrDesc)]),
exit(lists:flatten(ErrStr))
end;
-local_func(rd, [_,_], _Bs, _Shell, _RT, _Lf, _Ef) ->
+local_func(rd, [_,_], _Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
erlang:raise(error, function_clause, [{shell,rd,2}]);
-local_func(rf, [], Bs, _Shell, RT, _Lf, _Ef) ->
+local_func(rf, [], Bs, _Shell, RT, _FT, _Lf, _Ef) ->
true = ets:delete_all_objects(RT),
{value,initiate_records(Bs, RT),Bs};
-local_func(rf, [A], Bs0, _Shell, RT, Lf, Ef) ->
+local_func(rf, [A], Bs0, _Shell, RT, _FT, Lf, Ef) ->
{[Recs],Bs} = expr_list([A], Bs0, Lf, Ef),
if '_' =:= Recs ->
true = ets:delete_all_objects(RT);
- true ->
+ true ->
lists:foreach(fun(Name) -> true = ets:delete(RT, Name)
end, listify(Recs))
end,
{value,ok,Bs};
-local_func(rl, [], Bs, _Shell, RT, _Lf, _Ef) ->
+local_func(rl, [], Bs, _Shell, RT, _FT, _Lf, _Ef) ->
{value,list_records(ets:tab2list(RT)),Bs};
-local_func(rl, [A], Bs0, _Shell, RT, Lf, Ef) ->
+local_func(rl, [A], Bs0, _Shell, RT, _FT, Lf, Ef) ->
{[Recs],Bs} = expr_list([A], Bs0, Lf, Ef),
{value,list_records(record_defs(RT, listify(Recs))),Bs};
-local_func(rp, [A], Bs0, _Shell, RT, Lf, Ef) ->
+local_func(rp, [A], Bs0, _Shell, RT, _FT, Lf, Ef) ->
{[V],Bs} = expr_list([A], Bs0, Lf, Ef),
Cs = pp(V, _Column=1, _Depth=-1, RT),
io:requests([{put_chars, unicode, Cs}, nl]),
{value,ok,Bs};
-local_func(rr, [A], Bs0, _Shell, RT, Lf, Ef) ->
+local_func(rr, [A], Bs0, _Shell, RT, _FT, Lf, Ef) ->
{[File],Bs} = expr_list([A], Bs0, Lf, Ef),
{value,read_and_add_records(File, '_', [], Bs, RT),Bs};
-local_func(rr, [_,_]=As0, Bs0, _Shell, RT, Lf, Ef) ->
+local_func(rr, [_,_]=As0, Bs0, _Shell, RT, _FT, Lf, Ef) ->
{[File,Sel],Bs} = expr_list(As0, Bs0, Lf, Ef),
{value,read_and_add_records(File, Sel, [], Bs, RT),Bs};
-local_func(rr, [_,_,_]=As0, Bs0, _Shell, RT, Lf, Ef) ->
+local_func(rr, [_,_,_]=As0, Bs0, _Shell, RT, _FT, Lf, Ef) ->
{[File,Sel,Options],Bs} = expr_list(As0, Bs0, Lf, Ef),
{value,read_and_add_records(File, Sel, Options, Bs, RT),Bs};
-local_func(history, [{integer,_,N}], Bs, _Shell, _RT, _Lf, _Ef) ->
+local_func(history, [{integer,_,N}], Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
{value,history(N),Bs};
-local_func(history, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) ->
+local_func(history, [_Other], _Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
erlang:raise(error, function_clause, [{shell,history,1}]);
-local_func(results, [{integer,_,N}], Bs, _Shell, _RT, _Lf, _Ef) ->
+local_func(results, [{integer,_,N}], Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
{value,results(N),Bs};
-local_func(results, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) ->
+local_func(results, [_Other], _Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
erlang:raise(error, function_clause, [{shell,results,1}]);
-local_func(catch_exception, [{atom,_,Bool}], Bs, _Shell, _RT, _Lf, _Ef)
- when Bool; not Bool ->
+local_func(catch_exception, [{atom,_,Bool}], Bs, _Shell, _RT, _FT, _Lf, _Ef)
+ when Bool; not Bool ->
{value,catch_exception(Bool),Bs};
-local_func(catch_exception, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) ->
+local_func(catch_exception, [_Other], _Bs, _Shell, _RT, _FT, _Lf, _Ef) ->
erlang:raise(error, function_clause, [{shell,catch_exception,1}]);
-local_func(exit, [], _Bs, Shell, _RT, _Lf, _Ef) ->
- shell_req(Shell, exit), %This terminates us
+local_func(exit, [], _Bs, Shell, _RT, _FT, _Lf, _Ef) ->
+ shell_req(Shell, exit), %This terminates us
exit(normal);
-local_func(F, As0, Bs0, _Shell, _RT, Lf, Ef) when is_atom(F) ->
+local_func(F, As0, Bs0, _Shell, _RT, FT, Lf, Ef) when is_atom(F) ->
{As,Bs} = expr_list(As0, Bs0, Lf, Ef),
- non_builtin_local_func(F,As,Bs).
+ non_builtin_local_func(F,As,Bs, FT).
-non_builtin_local_func(F,As,Bs) ->
+non_builtin_local_func(F,As,Bs, FT) ->
Arity = length(As),
case erlang:function_exported(user_default, F, Arity) of
- true ->
+ true ->
{eval,erlang:make_fun(user_default, F, Arity),As,Bs};
- false ->
- shell_default(F,As,Bs)
+ false ->
+ shell_default(F,As,Bs, FT)
end.
-shell_default(F,As,Bs) ->
+shell_default(F,As,Bs, FT) ->
M = shell_default,
A = length(As),
case code:ensure_loaded(M) of
- {module, _} ->
- case erlang:function_exported(M,F,A) of
- true ->
- {eval,erlang:make_fun(M, F, A),As,Bs};
- false ->
- shell_undef(F,A)
- end;
- {error, _} ->
- shell_undef(F,A)
+ {module, _} ->
+ case erlang:function_exported(M,F,A) of
+ true ->
+ {eval,erlang:make_fun(M, F, A),As,Bs};
+ false ->
+ shell_default_local_func(F,As, Bs, FT)
+ end;
+ {error, _} ->
+ shell_default_local_func(F,As, Bs, FT)
+ end.
+
+shell_default_local_func(F, As, Bs, FT) ->
+ case ets:lookup(FT, {function, {shell_default, F, length(As)}}) of
+ [] -> shell_undef(F, length(As));
+ [{_, Fun}] -> {eval, Fun, As, Bs}
end.
shell_undef(F,A) ->
erlang:error({shell_undef,F,A,[]}).
-local_func_handler(Shell, RT, Ef) ->
- H = fun(Lf) ->
- fun(F, As, Bs) ->
- local_func(F, As, Bs, Shell, RT, {eval,Lf(Lf)}, Ef)
+local_func_handler(Shell, RT, FT, Ef) ->
+ H = fun(Lf) ->
+ fun(F, As, Bs) ->
+ local_func(F, As, Bs, Shell, RT, FT, {eval,Lf(Lf)}, Ef)
end
- end,
+ end,
{eval,H(H)}.
record_print_fun(RT) ->
fun(Tag, NoFields) ->
case ets:lookup(RT, Tag) of
- [{_,{attribute,_,record,{Tag,Fields}}}]
- when length(Fields) =:= NoFields ->
+ [{_,{attribute,_,record,{Tag,Fields}}}]
+ when length(Fields) =:= NoFields ->
record_fields(Fields);
_ ->
no
@@ -1109,7 +1276,7 @@ record_fields([]) ->
initiate_records(Bs, RT) ->
RNs1 = init_rec(shell_default, Bs, RT),
RNs2 = case code:is_loaded(user_default) of
- {file,_File} ->
+ {file,_File} ->
init_rec(user_default, Bs, RT);
false ->
[]
@@ -1145,11 +1312,12 @@ read_records(File, Selected, Options) ->
RAs;
RAs ->
Sel = listify(Selected),
- [RA || {attribute,_,_,{Name,_}}=RA <- RAs,
+ [RA || {attribute,_,_,{Name,_}}=RA <- RAs,
lists:member(Name, Sel)]
end.
add_records(RAs, Bs0, RT) ->
+ %% TODO store File name to support type completion
Recs = [{Name,D} || {attribute,_,_,{Name,_}}=D <- RAs],
Bs1 = record_bindings(Recs, Bs0),
case check_command([], Bs1) of
@@ -1183,12 +1351,12 @@ expr_list(Es, Bs, Lf, Ef) ->
record_bindings([], Bs) ->
Bs;
record_bindings(Recs0, Bs0) ->
- {Recs1, _} = lists:mapfoldl(fun ({Name,Def}, I) -> {{Name,I,Def},I+1}
+ {Recs1, _} = lists:mapfoldl(fun ({Name,Def}, I) -> {{Name,I,Def},I+1}
end, 0, Recs0),
Recs2 = lists:keysort(2, lists:ukeysort(1, Recs1)),
lists:foldl(fun ({Name,I,Def}, Bs) ->
- erl_eval:add_binding({record,I,Name}, Def, Bs)
- end, Bs0, Recs2).
+ erl_eval:add_binding({record,I,Name}, Def, Bs)
+ end, Bs0, Recs2).
%%% Read record information from file(s)
@@ -1214,7 +1382,7 @@ read_records(FileOrModule, Opts0) ->
find_file(Mod) when is_atom(Mod) ->
case code:which(Mod) of
- File when is_list(File) ->
+ File when is_list(File) ->
%% Special cases:
%% - Modules not in the code path (loaded with code:load_abs/1):
%% code:get_object_code/1 only searches in the code path
@@ -1228,8 +1396,8 @@ find_file(Mod) when is_atom(Mod) ->
error ->
{error, nofile}
end;
- preloaded ->
- {_M, Beam, File} = code:get_object_code(Mod),
+ preloaded ->
+ {_M, Beam, File} = code:get_object_code(Mod),
{beam, Beam, File};
_Else -> % non_existing, interpreted, cover_compiled
{error,nofile}
@@ -1291,10 +1459,10 @@ try_sources([Src|Rest], Os) ->
is_file(Name) ->
case filelib:is_file(Name) of
- true ->
- not filelib:is_dir(Name);
- false ->
- false
+ true ->
+ not filelib:is_dir(Name);
+ false ->
+ false
end.
parse_file(File, Opts) ->
@@ -1327,7 +1495,7 @@ record_attrs(Forms) ->
shell_req(Shell, Req) ->
Shell ! {shell_req,self(),Req},
receive
- {shell_rep,Shell,Rep} -> Rep
+ {shell_rep,Shell,Rep} -> Rep
end.
list_commands([{{N,command},Es0}, {{N,result}, V} |Ds], RT) ->
@@ -1337,7 +1505,7 @@ list_commands([{{N,command},Es0}, {{N,result}, V} |Ds], RT) ->
I = iolist_size(Ns),
io:requests([{put_chars, latin1, Ns},
{format,<<"~ts\n">>,[erl_pp:exprs(Es, I, enc())]},
- {format,<<"-> ">>,[]},
+ {format,<<"-> ">>,[]},
{put_chars, unicode, VS},
nl]),
list_commands(Ds, RT);
@@ -1379,7 +1547,7 @@ list_bindings([], _RT) ->
ok.
list_records(Records) ->
- lists:foreach(fun({_Name,Attr}) ->
+ lists:foreach(fun({_Name,Attr}) ->
io:fwrite(<<"~ts">>, [erl_pp:attribute(Attr, enc())])
end, Records).
@@ -1410,11 +1578,11 @@ prep_list_commands(E) ->
substitute_v1(F, {value,_,_}=Value) ->
F(Value);
-substitute_v1(F, T) when is_tuple(T) ->
+substitute_v1(F, T) when is_tuple(T) ->
list_to_tuple(substitute_v1(F, tuple_to_list(T)));
-substitute_v1(F, [E | Es]) ->
+substitute_v1(F, [E | Es]) ->
[substitute_v1(F, E) | substitute_v1(F, Es)];
-substitute_v1(_F, E) ->
+substitute_v1(_F, E) ->
E.
a0() ->
@@ -1439,13 +1607,7 @@ pp(V, I, RT) ->
pp(V, I, _Depth=?LINEMAX, RT).
pp(V, I, D, RT) ->
- Strings =
- case application:get_env(stdlib, shell_strings) of
- {ok, false} ->
- false;
- _ ->
- true
- end,
+ Strings = application:get_env(stdlib, shell_strings, true) =/= false,
io_lib_pretty:print(V, ([{column, I}, {line_length, columns()},
{depth, D}, {line_max_chars, ?CHAR_MAX},
{strings, Strings},
@@ -1464,8 +1626,8 @@ encoding() ->
enc() ->
case lists:keyfind(encoding, 1, io:getopts()) of
- false -> [{encoding,latin1}]; % should never happen
- Enc -> [Enc]
+ false -> [{encoding,latin1}]; % should never happen
+ Enc -> [Enc]
end.
garb(Shell) ->
@@ -1475,33 +1637,31 @@ garb(Shell) ->
erlang:garbage_collect().
get_env(V, Def) ->
- case application:get_env(stdlib, V) of
- {ok, Val} when is_integer(Val), Val >= 0 ->
- Val;
- _ ->
- Def
+ case application:get_env(stdlib, V, Def) of
+ Val when is_integer(Val), Val >= 0 ->
+ Val;
+ _ ->
+ Def
end.
-
+
check_env(V) ->
- case application:get_env(stdlib, V) of
- undefined ->
- ok;
- {ok, Val} when is_integer(Val), Val >= 0 ->
- ok;
- {ok, Val} ->
+ case application:get_env(stdlib, V, 0) of
+ Val when is_integer(Val), Val >= 0 ->
+ ok;
+ Val ->
Txt = io_lib:fwrite
("Invalid value of STDLIB configuration parameter"
"~tw: ~tp\n", [V, Val]),
- error_logger:info_report(lists:flatten(Txt))
+ error_logger:info_report(lists:flatten(Txt))
end.
-
+
set_env(App, Name, Val, Default) ->
Prev = case application:get_env(App, Name) of
- undefined ->
- Default;
- {ok, Old} ->
- Old
- end,
+ undefined ->
+ Default;
+ {ok, Old} ->
+ Old
+ end,
application_controller:set_env(App, Name, Val),
Prev.
@@ -1521,7 +1681,7 @@ results(L) when is_integer(L), L >= 0 ->
Bool :: boolean().
catch_exception(Bool) ->
- set_env(stdlib, shell_catch_exception, Bool, ?DEF_CATCH_EXCEPTION).
+ set_env(stdlib, shell_catch_exception, Bool, ?DEF_CATCH_EXCEPTION).
-spec prompt_func(PromptFunc) -> PromptFunc2 when
PromptFunc :: 'default' | {module(),atom()},
diff --git a/lib/stdlib/src/shell_default.erl b/lib/stdlib/src/shell_default.erl
index 3d820c9131..669266d255 100644
--- a/lib/stdlib/src/shell_default.erl
+++ b/lib/stdlib/src/shell_default.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,17 +25,17 @@
-export([help/0,lc/1,c/1,c/2,c/3,nc/1,nl/1,l/1,i/0,pid/3,i/3,m/0,m/1,lm/0,mm/0,
memory/0,memory/1,uptime/0,
- erlangrc/1,bi/1, regs/0, flush/0,pwd/0,ls/0,ls/1,cd/1,
+ erlangrc/1,bi/1, regs/0, flush/0,pwd/0,ls/0,ls/1,cd/1,
y/1, y/2,
- xm/1, bt/1, q/0,
+ xm/1, bt/1, q/0,
h/1, h/2, h/3, ht/1, ht/2, ht/3, hcb/1, hcb/2, hcb/3,
- ni/0, nregs/0]).
+ ni/0, nregs/0]).
-export([ih/0,iv/0,im/0,ii/1,ii/2,iq/1,ini/1,ini/2,inq/1,ib/2,ib/3,
- ir/2,ir/3,ibd/2,ibe/2,iba/3,ibc/3,
- ic/0,ir/1,ir/0,il/0,ipb/0,ipb/1,iaa/1,iaa/2,ist/1,ia/1,ia/2,ia/3,
- ia/4,ip/0]).
-
+ ir/2,ir/3,ibd/2,ibe/2,iba/3,ibc/3,
+ ic/0,ir/1,ir/0,il/0,ipb/0,ipb/1,iaa/1,iaa/2,ist/1,ia/1,ia/2,ia/3,
+ ia/4,ip/0]).
+-export(['$handle_undefined_function'/2]).
-import(io, [format/1]).
help() ->
@@ -78,13 +78,13 @@ help() ->
%% these are in alphabetic order it would be nice if they
%% were to *stay* so!
-bi(I) -> c:bi(I).
-bt(Pid) -> c:bt(Pid).
-c(File) -> c:c(File).
+bi(I) -> c:bi(I).
+bt(Pid) -> c:bt(Pid).
+c(File) -> c:c(File).
c(File, Opt) -> c:c(File, Opt).
c(File, Opt, Filter) -> c:c(File, Opt, Filter).
cd(D) -> c:cd(D).
-erlangrc(X) -> c:erlangrc(X).
+erlangrc(X) -> c:erlangrc(X).
flush() -> c:flush().
h(M) -> c:h(M).
h(M,F) -> c:h(M,F).
@@ -95,25 +95,25 @@ ht(M,F,A) -> c:ht(M,F,A).
hcb(M) -> c:hcb(M).
hcb(M,F) -> c:hcb(M,F).
hcb(M,F,A) -> c:hcb(M,F,A).
-i() -> c:i().
-i(X,Y,Z) -> c:i(X,Y,Z).
-l(Mod) -> c:l(Mod).
-lc(X) -> c:lc(X).
+i() -> c:i().
+i(X,Y,Z) -> c:i(X,Y,Z).
+l(Mod) -> c:l(Mod).
+lc(X) -> c:lc(X).
ls() -> c:ls().
ls(S) -> c:ls(S).
-m() -> c:m().
-m(Mod) -> c:m(Mod).
+m() -> c:m().
+m(Mod) -> c:m(Mod).
lm() -> c:lm().
mm() -> c:mm().
memory() -> c:memory().
memory(Type) -> c:memory(Type).
-nc(X) -> c:nc(X).
+nc(X) -> c:nc(X).
ni() -> c:ni().
-nl(Mod) -> c:nl(Mod).
+nl(Mod) -> c:nl(Mod).
nregs() -> c:nregs().
-pid(X,Y,Z) -> c:pid(X,Y,Z).
+pid(X,Y,Z) -> c:pid(X,Y,Z).
pwd() -> c:pwd().
-q() -> c:q().
+q() -> c:q().
regs() -> c:regs().
uptime() -> c:uptime().
xm(Mod) -> c:xm(Mod).
@@ -154,3 +154,11 @@ iv() -> calli(iv, []).
calli(F, Args) ->
c:appcall(debugger, i, F, Args).
+
+'$handle_undefined_function'(Func, Args) ->
+ case shell:get_function(Func, length(Args)) of
+ undefined ->
+ error_handler:raise_undef_exception(?MODULE, Func, Args);
+ Fun when is_function(Fun, length(Args)) ->
+ apply(Fun, Args)
+ end. \ No newline at end of file
diff --git a/lib/stdlib/src/shell_docs.erl b/lib/stdlib/src/shell_docs.erl
index e42b5bb5b8..5cf1e0aed2 100644
--- a/lib/stdlib/src/shell_docs.erl
+++ b/lib/stdlib/src/shell_docs.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -872,10 +872,16 @@ render_element({li,[],Content},[l | _] = State, Pos, Ind,D) ->
render_element({dl,_,Content},State,Pos,Ind,D) ->
render_docs(Content, [dl|State], Pos, Ind,D);
-render_element({dt,_,Content},[dl | _] = State,Pos,Ind,D) ->
+render_element({dt,Attr,Content},[dl | _] = State,Pos,Ind,D) ->
+ Since = case Attr of
+ [{since, Vsn}] ->
+ [" (since ",unicode:characters_to_list(Vsn),$)];
+ [] ->
+ []
+ end,
Underline = sansi(underline),
{Docs, _NewPos} = render_docs(Content, [li | State], Pos, Ind, D),
- {[Underline,Docs,ransi(underline),":","\n"], 0};
+ {[Underline,Docs,ransi(underline),$:,Since,$\n], 0};
render_element({dd,_,Content},[dl | _] = State,Pos,Ind,D) ->
trimnlnl(render_docs(Content, [li | State], Pos, Ind + 2, D));
@@ -1005,18 +1011,18 @@ nl(Chars) ->
init_ansi(#config{ ansi = undefined, io_opts = Opts }) ->
%% We use this as our heuristic to see if we should print ansi or not
case {application:get_env(kernel, shell_docs_ansi),
+ proplists:get_value(terminal, Opts, false),
proplists:is_defined(echo, Opts) andalso
- proplists:is_defined(expand_fun, Opts),
- os:type()} of
+ proplists:is_defined(expand_fun, Opts)} of
{{ok,false}, _, _} ->
put(ansi, noansi);
{{ok,true}, _, _} ->
put(ansi, []);
- {_, _, {win32,_}} ->
- put(ansi, noansi);
- {_, true,_} ->
+ {_, true, _} ->
+ put(ansi, []);
+ {_, _, true} ->
put(ansi, []);
- {_, false,_} ->
+ {_, _, false} ->
put(ansi, noansi)
end;
init_ansi(#config{ ansi = true }) ->
diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src
index 358ebf471d..a71ad0a954 100644
--- a/lib/stdlib/src/stdlib.app.src
+++ b/lib/stdlib/src/stdlib.app.src
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,7 +21,8 @@
{application, stdlib,
[{description, "ERTS CXC 138 10"},
{vsn, "%VSN%"},
- {modules, [array,
+ {modules, [argparse,
+ array,
base64,
beam_lib,
binary,
@@ -36,7 +37,9 @@
digraph,
digraph_utils,
edlin,
+ edlin_context,
edlin_expand,
+ edlin_type_suggestion,
epp,
eval_bits,
erl_abstract_code,
@@ -112,6 +115,6 @@
dets]},
{applications, [kernel]},
{env, []},
- {runtime_dependencies, ["sasl-3.0","kernel-8.5.1","erts-13.1","crypto-4.5",
+ {runtime_dependencies, ["sasl-3.0","kernel-@OTP-17932@","erts-13.1","crypto-4.5",
"compiler-5.0"]}
]}.
diff --git a/lib/stdlib/src/string.erl b/lib/stdlib/src/string.erl
index a418754caf..e0b765948c 100644
--- a/lib/stdlib/src/string.erl
+++ b/lib/stdlib/src/string.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -76,7 +76,9 @@
-import(lists,[member/2]).
-compile({no_auto_import,[length/1]}).
-compile({inline, [btoken/2, rev/1, append/2, stack/2, search_compile/1]}).
--define(ASCII_LIST(CP1,CP2), CP1 < 256, CP2 < 256, CP1 =/= $\r).
+-define(ASCII_LIST(CP1,CP2),
+ is_integer(CP1), 0 =< CP1, CP1 < 256,
+ is_integer(CP2), 0 =< CP2, CP2 < 256, CP1 =/= $\r).
-export_type([grapheme_cluster/0]).
@@ -198,7 +200,7 @@ slice(CD, N, Length)
[] when is_binary(CD) -> <<>>;
L -> slice_trail(L, Length)
end;
-slice(CD, N, infinity) ->
+slice(CD, N, infinity) when is_integer(N), N >= 0 ->
case slice_l0(CD, N) of
[] when is_binary(CD) -> <<>>;
Res -> Res
@@ -261,11 +263,13 @@ trim(Str, Dir) ->
Dir :: direction() | 'both',
Characters :: [grapheme_cluster()].
trim(Str, _, []) -> Str;
-trim(Str, leading, [Sep]) when is_list(Str), Sep < 256 ->
+trim(Str, leading, [Sep])
+ when is_list(Str), is_integer(Sep), 0 =< Sep, Sep < 256 ->
trim_ls(Str, Sep);
trim(Str, leading, Sep) when is_list(Sep) ->
trim_l(Str, Sep);
-trim(Str, trailing, [Sep]) when is_list(Str), Sep < 256 ->
+trim(Str, trailing, [Sep])
+ when is_list(Str), is_integer(Sep), 0 =< Sep, Sep < 256 ->
trim_ts(Str, Sep);
trim(Str, trailing, Seps0) when is_list(Seps0) ->
Seps = search_pattern(Seps0),
@@ -630,9 +634,10 @@ slice_l0(<<CP1/utf8, Bin/binary>>, N) when N > 0 ->
slice_l0(L, N) ->
slice_l(L, N).
-slice_l([CP1|[CP2|_]=Cont], N) when ?ASCII_LIST(CP1,CP2),N > 0 ->
+slice_l([CP1|[CP2|_]=Cont], N)
+ when ?ASCII_LIST(CP1,CP2), is_integer(N), N > 0 ->
slice_l(Cont, N-1);
-slice_l(CD, N) when N > 0 ->
+slice_l(CD, N) when is_integer(N), N > 0 ->
case unicode_util:gc(CD) of
[_|Cont] -> slice_l(Cont, N-1);
[] -> [];
@@ -641,7 +646,8 @@ slice_l(CD, N) when N > 0 ->
slice_l(Cont, 0) ->
Cont.
-slice_lb(<<CP2/utf8, Bin/binary>>, CP1, N) when ?ASCII_LIST(CP1,CP2), N > 1 ->
+slice_lb(<<CP2/utf8, Bin/binary>>, CP1, N)
+ when ?ASCII_LIST(CP1,CP2), is_integer(N), N > 1 ->
slice_lb(Bin, CP2, N-1);
slice_lb(Bin, CP1, N) ->
[_|Rest] = unicode_util:gc([CP1|Bin]),
@@ -693,9 +699,13 @@ slice_bin(CD, CP1, N) when N > 0 ->
slice_bin(CD, CP1, 0) ->
byte_size(CD)+byte_size(<<CP1/utf8>>).
-uppercase_list([CP1|[CP2|_]=Cont], _Changed) when $a =< CP1, CP1 =< $z, CP2 < 256 ->
+uppercase_list([CP1|[CP2|_]=Cont], _Changed)
+ when is_integer(CP1), $a =< CP1, CP1 =< $z,
+ is_integer(CP2), 0 =< CP2, CP2 < 256 ->
[CP1-32|uppercase_list(Cont, true)];
-uppercase_list([CP1|[CP2|_]=Cont], Changed) when CP1 < 128, CP2 < 256 ->
+uppercase_list([CP1|[CP2|_]=Cont], Changed)
+ when is_integer(CP1), 0 =< CP1, CP1 < 128,
+ is_integer(CP2), 0 =< CP2, CP2 < 256 ->
[CP1|uppercase_list(Cont, Changed)];
uppercase_list([], true) ->
[];
@@ -709,16 +719,16 @@ uppercase_list(CPs0, Changed) ->
end.
uppercase_bin(CP1, <<CP2/utf8, Bin/binary>>, _Changed)
- when $a =< CP1, CP1 =< $z, CP2 < 256 ->
+ when is_integer(CP1), $a =< CP1, CP1 =< $z, CP2 < 256 ->
[CP1-32|uppercase_bin(CP2, Bin, true)];
uppercase_bin(CP1, <<CP2/utf8, Bin/binary>>, Changed)
- when CP1 < 128, CP2 < 256 ->
+ when is_integer(CP1), 0 =< CP1, CP1 < 128, CP2 < 256 ->
[CP1|uppercase_bin(CP2, Bin, Changed)];
uppercase_bin(CP1, Bin, Changed) ->
case unicode_util:uppercase([CP1|Bin]) of
[CP1|CPs] ->
case unicode_util:cp(CPs) of
- [Next|Rest] ->
+ [Next|Rest] when is_integer(Next), Next >= 0 ->
[CP1|uppercase_bin(Next, Rest, Changed)];
[] when Changed ->
[CP1];
@@ -729,7 +739,7 @@ uppercase_bin(CP1, Bin, Changed) ->
end;
[Char|CPs] ->
case unicode_util:cp(CPs) of
- [Next|Rest] ->
+ [Next|Rest] when is_integer(Next), Next >= 0 ->
[Char|uppercase_bin(Next, Rest, true)];
[] ->
[Char];
@@ -738,9 +748,13 @@ uppercase_bin(CP1, Bin, Changed) ->
end
end.
-lowercase_list([CP1|[CP2|_]=Cont], _Changed) when $A =< CP1, CP1 =< $Z, CP2 < 256 ->
+lowercase_list([CP1|[CP2|_]=Cont], _Changed)
+ when is_integer(CP1), $A =< CP1, CP1 =< $Z,
+ is_integer(CP2), 0 =< CP2, CP2 < 256 ->
[CP1+32|lowercase_list(Cont, true)];
-lowercase_list([CP1|[CP2|_]=Cont], Changed) when CP1 < 128, CP2 < 256 ->
+lowercase_list([CP1|[CP2|_]=Cont], Changed)
+ when is_integer(CP1), 0 =< CP1, CP1 < 128,
+ is_integer(CP2), 0 =< CP2, CP2 < 256 ->
[CP1|lowercase_list(Cont, Changed)];
lowercase_list([], true) ->
[];
@@ -754,16 +768,16 @@ lowercase_list(CPs0, Changed) ->
end.
lowercase_bin(CP1, <<CP2/utf8, Bin/binary>>, _Changed)
- when $A =< CP1, CP1 =< $Z, CP2 < 256 ->
+ when is_integer(CP1), $A =< CP1, CP1 =< $Z, CP2 < 256 ->
[CP1+32|lowercase_bin(CP2, Bin, true)];
lowercase_bin(CP1, <<CP2/utf8, Bin/binary>>, Changed)
- when CP1 < 128, CP2 < 256 ->
+ when is_integer(CP1), 0 =< CP1, CP1 < 128, CP2 < 256 ->
[CP1|lowercase_bin(CP2, Bin, Changed)];
lowercase_bin(CP1, Bin, Changed) ->
case unicode_util:lowercase([CP1|Bin]) of
[CP1|CPs] ->
case unicode_util:cp(CPs) of
- [Next|Rest] ->
+ [Next|Rest] when is_integer(Next), Next >= 0 ->
[CP1|lowercase_bin(Next, Rest, Changed)];
[] when Changed ->
[CP1];
@@ -774,7 +788,7 @@ lowercase_bin(CP1, Bin, Changed) ->
end;
[Char|CPs] ->
case unicode_util:cp(CPs) of
- [Next|Rest] ->
+ [Next|Rest] when is_integer(Next), Next >= 0 ->
[Char|lowercase_bin(Next, Rest, true)];
[] ->
[Char];
@@ -783,9 +797,13 @@ lowercase_bin(CP1, Bin, Changed) ->
end
end.
-casefold_list([CP1|[CP2|_]=Cont], _Changed) when $A =< CP1, CP1 =< $Z, CP2 < 256 ->
+casefold_list([CP1|[CP2|_]=Cont], _Changed)
+ when is_integer(CP1), $A =< CP1, CP1 =< $Z,
+ is_integer(CP2), 0 =< CP2, CP2 < 256 ->
[CP1+32|casefold_list(Cont, true)];
-casefold_list([CP1|[CP2|_]=Cont], Changed) when CP1 < 128, CP2 < 256 ->
+casefold_list([CP1|[CP2|_]=Cont], Changed)
+ when is_integer(CP1), 0 =< CP1, CP1 < 128,
+ is_integer(CP2), 0 =< CP2, CP2 < 256 ->
[CP1|casefold_list(Cont, Changed)];
casefold_list([], true) ->
[];
@@ -799,16 +817,16 @@ casefold_list(CPs0, Changed) ->
end.
casefold_bin(CP1, <<CP2/utf8, Bin/binary>>, _Changed)
- when $A =< CP1, CP1 =< $Z, CP2 < 256 ->
+ when is_integer(CP1), $A =< CP1, CP1 =< $Z, CP2 < 256 ->
[CP1+32|casefold_bin(CP2, Bin, true)];
casefold_bin(CP1, <<CP2/utf8, Bin/binary>>, Changed)
- when CP1 < 128, CP2 < 256 ->
+ when is_integer(CP1), 0 =< CP1, CP1 < 128, CP2 < 256 ->
[CP1|casefold_bin(CP2, Bin, Changed)];
casefold_bin(CP1, Bin, Changed) ->
case unicode_util:casefold([CP1|Bin]) of
[CP1|CPs] ->
case unicode_util:cp(CPs) of
- [Next|Rest] ->
+ [Next|Rest] when is_integer(Next), Next >= 0 ->
[CP1|casefold_bin(Next, Rest, Changed)];
[] when Changed ->
[CP1];
@@ -819,7 +837,7 @@ casefold_bin(CP1, Bin, Changed) ->
end;
[Char|CPs] ->
case unicode_util:cp(CPs) of
- [Next|Rest] ->
+ [Next|Rest] when is_integer(Next), Next >= 0 ->
[Char|casefold_bin(Next, Rest, true)];
[] ->
[Char];
@@ -1734,7 +1752,7 @@ bin_search_str_2(Bin0, Start, Cont, First, SearchCPs) ->
<<_:Start/binary, Bin/binary>> = Bin0,
case binary:match(Bin, First) of
nomatch -> {nomatch, byte_size(Bin0), Cont};
- {Where0, _} ->
+ {Where0, _} when is_integer(Where0) ->
Where = Start+Where0,
<<Keep:Where/binary, Cs0/binary>> = Bin0,
[GC|Cs]=unicode_util:gc(Cs0),
@@ -1979,7 +1997,7 @@ chars(C, N) -> chars(C, N, []).
Tail :: string(),
String :: string().
-chars(C, N, Tail) when N > 0 ->
+chars(C, N, Tail) when is_integer(N), N > 0 ->
chars(C, N-1, [C|Tail]);
chars(C, 0, Tail) when is_integer(C) ->
Tail.
@@ -2109,7 +2127,7 @@ left(String, Len) when is_integer(Len) -> left(String, Len, $\s).
Number :: non_neg_integer(),
Character :: char().
-left(String, Len, Char) when is_integer(Char) ->
+left(String, Len, Char) when is_integer(Len), is_integer(Char) ->
Slen = erlang:length(String),
if
Slen > Len -> substr(String, 1, Len);
@@ -2134,7 +2152,7 @@ right(String, Len) when is_integer(Len) -> right(String, Len, $\s).
Number :: non_neg_integer(),
Character :: char().
-right(String, Len, Char) when is_integer(Char) ->
+right(String, Len, Char) when is_integer(Len), is_integer(Char) ->
Slen = erlang:length(String),
if
Slen > Len -> substr(String, Slen-Len+1);
@@ -2161,7 +2179,7 @@ centre(String, Len) when is_integer(Len) -> centre(String, Len, $\s).
centre(String, 0, Char) when is_list(String), is_integer(Char) ->
[]; % Strange cases to centre string
-centre(String, Len, Char) when is_integer(Char) ->
+centre(String, Len, Char) when is_integer(Len), is_integer(Char) ->
Slen = erlang:length(String),
if
Slen > Len -> substr(String, (Slen-Len) div 2 + 1, Len);
@@ -2186,7 +2204,8 @@ sub_string(String, Start) -> substr(String, Start).
Start :: pos_integer(),
Stop :: pos_integer().
-sub_string(String, Start, Stop) -> substr(String, Start, Stop - Start + 1).
+sub_string(String, Start, Stop) when is_integer(Start), is_integer(Stop) ->
+ substr(String, Start, Stop - Start + 1).
%% ISO/IEC 8859-1 (latin1) letters are converted, others are ignored
%%
diff --git a/lib/stdlib/src/supervisor.erl b/lib/stdlib/src/supervisor.erl
index 58b943d874..de44ce55ee 100644
--- a/lib/stdlib/src/supervisor.erl
+++ b/lib/stdlib/src/supervisor.erl
@@ -349,7 +349,10 @@ init_children(State, StartSpec) ->
{ok, Children} ->
case start_children(Children, SupName) of
{ok, NChildren} ->
- {ok, State#state{children = NChildren}};
+ %% Static supervisor are not expected to
+ %% have much work to do so hibernate them
+ %% to improve memory handling.
+ {ok, State#state{children = NChildren}, hibernate};
{error, NChildren, Reason} ->
_ = terminate_children(NChildren, SupName),
{stop, {shutdown, Reason}}
@@ -361,6 +364,9 @@ init_children(State, StartSpec) ->
init_dynamic(State, [StartSpec]) ->
case check_startspec([StartSpec], State#state.auto_shutdown) of
{ok, Children} ->
+ %% Simple one for one supervisors are expected to
+ %% have many children coming and going so do not
+ %% hibernate.
{ok, dyn_init(State#state{children = Children})};
Error ->
{stop, {start_spec, Error}}
diff --git a/lib/stdlib/src/timer.erl b/lib/stdlib/src/timer.erl
index 182f5cb4f2..17c12f2931 100644
--- a/lib/stdlib/src/timer.erl
+++ b/lib/stdlib/src/timer.erl
@@ -22,8 +22,9 @@
-export([apply_after/4,
send_after/3, send_after/2,
exit_after/3, exit_after/2, kill_after/2, kill_after/1,
- apply_interval/4, send_interval/3, send_interval/2,
- cancel/1, sleep/1, tc/1, tc/2, tc/3, now_diff/2,
+ apply_interval/4, apply_repeatedly/4,
+ send_interval/3, send_interval/2,
+ cancel/1, sleep/1, tc/1, tc/2, tc/3, tc/4, now_diff/2,
seconds/1, minutes/1, hours/1, hms/3]).
-export([start_link/0, start/0,
@@ -61,7 +62,7 @@
Reason :: term().
apply_after(0, M, F, A)
when ?valid_mfa(M, F, A) ->
- do_apply({M, F, A}),
+ _ = do_apply({M, F, A}, false),
{ok, {instant, make_ref()}};
apply_after(Time, M, F, A)
when ?valid_time(Time),
@@ -160,6 +161,21 @@ apply_interval(Time, M, F, A)
apply_interval(_Time, _M, _F, _A) ->
{error, badarg}.
+-spec apply_repeatedly(Time, Module, Function, Arguments) ->
+ {'ok', TRef} | {'error', Reason}
+ when Time :: time(),
+ Module :: module(),
+ Function :: atom(),
+ Arguments :: [term()],
+ TRef :: tref(),
+ Reason :: term().
+apply_repeatedly(Time, M, F, A)
+ when ?valid_time(Time),
+ ?valid_mfa(M, F, A) ->
+ req(apply_repeatedly, {system_time(), Time, self(), {M, F, A}});
+apply_repeatedly(_Time, _M, _F, _A) ->
+ {error, badarg}.
+
-spec send_interval(Time, Destination, Message) -> {'ok', TRef} | {'error', Reason}
when Time :: time(),
Destination :: pid() | (RegName :: atom()) | {RegName :: atom(), Node :: node()},
@@ -231,41 +247,71 @@ sleep(T) ->
Time :: integer(),
Value :: term().
tc(F) ->
+ tc(F, microsecond).
+
+%%
+%% Measure the execution time (in microseconds) for Fun(Args)
+%% or the execution time (in TimeUnit) for Fun().
+%%
+-spec tc(Fun, Arguments) -> {Time, Value}
+ when Fun :: function(),
+ Arguments :: [term()],
+ Time :: integer(),
+ Value :: term();
+ (Fun, TimeUnit) -> {Time, Value}
+ when Fun :: function(),
+ TimeUnit :: erlang:time_unit(),
+ Time :: integer(),
+ Value :: term().
+tc(F, A) when is_list(A) ->
+ tc(F, A, microsecond);
+tc(F, TimeUnit) ->
T1 = erlang:monotonic_time(),
Val = F(),
T2 = erlang:monotonic_time(),
- Time = erlang:convert_time_unit(T2 - T1, native, microsecond),
+ Time = erlang:convert_time_unit(T2 - T1, native, TimeUnit),
{Time, Val}.
%%
-%% Measure the execution time (in microseconds) for Fun(Args).
+%% Measure the execution time (in microseconds) for an MFA
+%% or the execution time (in TimeUnit) for Fun(Args).
%%
--spec tc(Fun, Arguments) -> {Time, Value}
+-spec tc(Module, Function, Arguments) -> {Time, Value}
+ when Module :: module(),
+ Function :: atom(),
+ Arguments :: [term()],
+ Time :: integer(),
+ Value :: term();
+ (Fun, Arguments, TimeUnit) -> {Time, Value}
when Fun :: function(),
Arguments :: [term()],
+ TimeUnit :: erlang:time_unit(),
Time :: integer(),
Value :: term().
-tc(F, A) ->
+tc(M, F, A) when is_list(A) ->
+ tc(M, F, A, microsecond);
+tc(F, A, TimeUnit) ->
T1 = erlang:monotonic_time(),
Val = apply(F, A),
T2 = erlang:monotonic_time(),
- Time = erlang:convert_time_unit(T2 - T1, native, microsecond),
+ Time = erlang:convert_time_unit(T2 - T1, native, TimeUnit),
{Time, Val}.
%%
-%% Measure the execution time (in microseconds) for an MFA.
+%% Measure the execution time (in TimeUnit) for an MFA.
%%
--spec tc(Module, Function, Arguments) -> {Time, Value}
+-spec tc(Module, Function, Arguments, TimeUnit) -> {Time, Value}
when Module :: module(),
Function :: atom(),
Arguments :: [term()],
+ TimeUnit :: erlang:time_unit(),
Time :: integer(),
Value :: term().
-tc(M, F, A) ->
+tc(M, F, A, TimeUnit) ->
T1 = erlang:monotonic_time(),
Val = apply(M, F, A),
T2 = erlang:monotonic_time(),
- Time = erlang:convert_time_unit(T2 - T1, native, microsecond),
+ Time = erlang:convert_time_unit(T2 - T1, native, TimeUnit),
{Time, Val}.
%%
@@ -382,15 +428,11 @@ maybe_req(Req, Arg) ->
handle_call({apply_once, {Started, Time, MFA}}, _From, Tab) ->
Timeout = Started + Time,
Reply = try
- erlang:start_timer(
- Timeout,
- self(),
- {apply_once, MFA},
- [{abs, true}]
- )
+ erlang:start_timer(Timeout, self(), {apply_once, MFA},
+ [{abs, true}])
of
SRef ->
- ets:insert(Tab, {SRef, SRef}),
+ ets:insert(Tab, {SRef}),
{ok, {once, SRef}}
catch
error:badarg ->
@@ -399,25 +441,13 @@ handle_call({apply_once, {Started, Time, MFA}}, _From, Tab) ->
{reply, Reply, Tab};
%% Start an interval timer.
handle_call({apply_interval, {Started, Time, Pid, MFA}}, _From, Tab) ->
- NextTimeout = Started + Time,
- TRef = monitor(process, Pid),
- Reply = try
- erlang:start_timer(
- NextTimeout,
- self(),
- {apply_interval, NextTimeout, Time, TRef, MFA},
- [{abs, true}]
- )
- of
- SRef ->
- ets:insert(Tab, {TRef, SRef}),
- {ok, {interval, TRef}}
- catch
- error:badarg ->
- demonitor(TRef, [flush]),
- {error, badarg}
- end,
- {reply, Reply, Tab};
+ {TRef, TPid, Tag} = start_interval_loop(Started, Time, Pid, MFA, false),
+ ets:insert(Tab, {TRef, TPid, Tag}),
+ {reply, {ok, {interval, TRef}}, Tab};
+handle_call({apply_repeatedly, {Started, Time, Pid, MFA}}, _From, Tab) ->
+ {TRef, TPid, Tag} = start_interval_loop(Started, Time, Pid, MFA, true),
+ ets:insert(Tab, {TRef, TPid, Tag}),
+ {reply, {ok, {interval, TRef}}, Tab};
%% Cancel a one-shot timer.
handle_call({cancel, {once, TRef}}, _From, Tab) ->
_ = remove_timer(TRef, Tab),
@@ -440,31 +470,14 @@ handle_call(_Req, _From, Tab) ->
when Tab :: ets:tid().
%% One-shot timer timeout.
handle_info({timeout, TRef, {apply_once, MFA}}, Tab) ->
- case ets:take(Tab, TRef) of
- [{TRef, _SRef}] ->
- do_apply(MFA);
- [] ->
- ok
- end,
- {noreply, Tab};
-%% Interval timer timeout.
-handle_info({timeout, _, {apply_interval, CurTimeout, Time, TRef, MFA}}, Tab) ->
- case ets:member(Tab, TRef) of
- true ->
- NextTimeout = CurTimeout + Time,
- SRef = erlang:start_timer(
- NextTimeout,
- self(),
- {apply_interval, NextTimeout, Time, TRef, MFA},
- [{abs, true}]
- ),
- ets:update_element(Tab, TRef, {2, SRef}),
- do_apply(MFA);
- false ->
- ok
- end,
+ _ = case ets:take(Tab, TRef) of
+ [{TRef}] ->
+ do_apply(MFA, false);
+ [] ->
+ ok
+ end,
{noreply, Tab};
-%% A process related to an interval timer died.
+%% An interval timer loop process died.
handle_info({'DOWN', TRef, process, _Pid, _Reason}, Tab) ->
_ = remove_timer(TRef, Tab),
{noreply, Tab};
@@ -480,34 +493,121 @@ handle_cast(_Req, Tab) ->
{noreply, Tab}.
-spec terminate(term(), _Tab) -> 'ok'.
-terminate(_Reason, _Tab) ->
- ok.
+terminate(_Reason, undefined) ->
+ ok;
+terminate(Reason, Tab) ->
+ _ = ets:foldl(fun
+ ({TRef}, Acc) ->
+ _ = cancel_timer(TRef),
+ Acc;
+ ({_TRef, TPid, Tag}, Acc) ->
+ TPid ! {cancel, Tag},
+ Acc
+ end,
+ undefined,
+ Tab),
+ true = ets:delete(Tab),
+ terminate(Reason, undefined).
-spec code_change(term(), State, term()) -> {'ok', State}.
code_change(_OldVsn, Tab, _Extra) ->
%% According to the man for gen server no timer can be set here.
{ok, Tab}.
+start_interval_loop(Started, Time, TargetPid, MFA, WaitComplete) ->
+ Tag = make_ref(),
+ TimeServerPid = self(),
+ {TPid, TRef} = spawn_monitor(fun() ->
+ TimeServerRef = monitor(process, TimeServerPid),
+ TargetRef = monitor(process, TargetPid),
+ TimerRef = schedule_interval_timer(Started, Time,
+ MFA),
+ _ = interval_loop(TimeServerRef, TargetRef, Tag,
+ WaitComplete, TimerRef)
+ end),
+ {TRef, TPid, Tag}.
+
+%% Interval timer loop.
+interval_loop(TimerServerMon, TargetMon, Tag, WaitComplete, TimerRef0) ->
+ receive
+ {cancel, Tag} ->
+ ok = cancel_timer(TimerRef0);
+ {'DOWN', TimerServerMon, process, _, _} ->
+ ok = cancel_timer(TimerRef0);
+ {'DOWN', TargetMon, process, _, _} ->
+ ok = cancel_timer(TimerRef0);
+ {timeout, TimerRef0, {apply_interval, CurTimeout, Time, MFA}} ->
+ case do_apply(MFA, WaitComplete) of
+ {ok, {spawn, ActionMon}} ->
+ receive
+ {cancel, Tag} ->
+ ok;
+ {'DOWN', TimerServerMon, process, _, _} ->
+ ok;
+ {'DOWN', TargetMon, process, _, _} ->
+ ok;
+ {'DOWN', ActionMon, process, _, _} ->
+ TimerRef1 = schedule_interval_timer(CurTimeout, Time, MFA),
+ interval_loop(TimerServerMon, TargetMon, Tag, WaitComplete, TimerRef1)
+ end;
+ _ ->
+ TimerRef1 = schedule_interval_timer(CurTimeout, Time, MFA),
+ interval_loop(TimerServerMon, TargetMon, Tag, WaitComplete, TimerRef1)
+ end
+ end.
+
+schedule_interval_timer(CurTimeout, Time, MFA) ->
+ NextTimeout = CurTimeout + Time,
+ case NextTimeout =< system_time() of
+ true ->
+ TimerRef = make_ref(),
+ self() ! {timeout, TimerRef, {apply_interval, NextTimeout, Time, MFA}},
+ TimerRef;
+ false ->
+ erlang:start_timer(NextTimeout, self(), {apply_interval, NextTimeout, Time, MFA}, [{abs, true}])
+ end.
+
%% Remove a timer.
remove_timer(TRef, Tab) ->
case ets:take(Tab, TRef) of
- [{TRef, SRef}] ->
- ok = erlang:cancel_timer(SRef, [{async, true}, {info, false}]),
+ [{TRef}] -> % One-shot timer.
+ ok = cancel_timer(TRef),
+ true;
+ [{TRef, TPid, Tag}] -> % Interval timer.
+ TPid ! {cancel, Tag},
true;
[] -> % TimerReference does not exist, do nothing
false
end.
+%% Cancel a timer.
+cancel_timer(TRef) ->
+ erlang:cancel_timer(TRef, [{async, true}, {info, false}]).
+
%% Help functions
%% If send op. send directly (faster than spawn)
-do_apply({?MODULE, send, A}) ->
- catch send(A);
+do_apply({?MODULE, send, A}, _) ->
+ try send(A)
+ of _ -> {ok, send}
+ catch _:_ -> error
+ end;
%% If exit op. resolve registered name
-do_apply({erlang, exit, [Name, Reason]}) ->
- catch exit(get_pid(Name), Reason);
-do_apply({M,F,A}) ->
- catch spawn(M, F, A).
+do_apply({erlang, exit, [Name, Reason]}, _) ->
+ try exit(get_pid(Name), Reason)
+ of _ -> {ok, exit}
+ catch _:_ -> error
+ end;
+do_apply({M,F,A}, false) ->
+ try spawn(M, F, A)
+ of _ -> {ok, spawn}
+ catch _:_ -> error
+ end;
+do_apply({M, F, A}, true) ->
+ try spawn_monitor(M, F, A)
+ of {_, Ref} -> {ok, {spawn, Ref}}
+ catch _:_ -> error
+ end.
%% Get current time in milliseconds,
%% ceil'ed to the next millisecond.
diff --git a/lib/stdlib/src/unicode.erl b/lib/stdlib/src/unicode.erl
index f9d52d30d3..d2a1edce3d 100644
--- a/lib/stdlib/src/unicode.erl
+++ b/lib/stdlib/src/unicode.erl
@@ -91,9 +91,9 @@ characters_to_binary(_, _) ->
-spec characters_to_list(Data, InEncoding) -> Result when
Data :: latin1_chardata() | chardata() | external_chardata(),
InEncoding :: encoding(),
- Result :: list()
- | {error, list(), RestData}
- | {incomplete, list(), binary()},
+ Result ::string()
+ | {error, string(), RestData}
+ | {incomplete, string(), binary()},
RestData :: latin1_chardata() | chardata() | external_chardata().
characters_to_list(_, _) ->
@@ -103,9 +103,9 @@ characters_to_list(_, _) ->
-spec characters_to_list(Data) -> Result when
Data :: latin1_chardata() | chardata() | external_chardata(),
- Result :: list()
- | {error, list(), RestData}
- | {incomplete, list(), binary()},
+ Result :: string()
+ | {error, string(), RestData}
+ | {incomplete, string(), binary()},
RestData :: latin1_chardata() | chardata() | external_chardata().
characters_to_list(ML) ->
diff --git a/lib/stdlib/src/zip.erl b/lib/stdlib/src/zip.erl
index c59398a84e..0809dbb492 100644
--- a/lib/stdlib/src/zip.erl
+++ b/lib/stdlib/src/zip.erl
@@ -35,7 +35,7 @@
%% zip server
-export([zip_open/1, zip_open/2,
- zip_get/1, zip_get/2,
+ zip_get/1, zip_get/2, zip_get_crc32/2,
zip_t/1, zip_tt/1,
zip_list_dir/1, zip_list_dir/2,
zip_close/1]).
@@ -267,6 +267,13 @@ do_openzip_get(#openzip{files = Files, in = In0, input = Input,
do_openzip_get(_) ->
throw(einval).
+%% retrieve the crc32 checksum from an open archive
+openzip_get_crc32(FileName, #openzip{files = Files}) ->
+ case file_name_search(FileName, Files) of
+ {_,#zip_file_extra{crc32=CRC}} -> {ok, CRC};
+ _ -> throw(file_not_found)
+ end.
+
%% retrieve a file from an open archive
openzip_get(FileName, OpenZip) ->
case ?CATCH(do_openzip_get(FileName, OpenZip)) of
@@ -1165,6 +1172,9 @@ server_loop(Parent, OpenZip) ->
{From, {get, FileName}} ->
From ! {self(), openzip_get(FileName, OpenZip)},
server_loop(Parent, OpenZip);
+ {From, {get_crc32, FileName}} ->
+ From ! {self(), openzip_get_crc32(FileName, OpenZip)},
+ server_loop(Parent, OpenZip);
{From, list_dir} ->
From ! {self(), openzip_list_dir(OpenZip)},
server_loop(Parent, OpenZip);
@@ -1223,6 +1233,15 @@ zip_close(Pid) when is_pid(Pid) ->
zip_get(FileName, Pid) when is_pid(Pid) ->
request(self(), Pid, {get, FileName}).
+-spec(zip_get_crc32(FileName, ZipHandle) -> {ok, CRC} | {error, Reason} when
+ FileName :: file:name(),
+ ZipHandle :: handle(),
+ CRC :: non_neg_integer(),
+ Reason :: term()).
+
+zip_get_crc32(FileName, Pid) when is_pid(Pid) ->
+ request(self(), Pid, {get_crc32, FileName}).
+
-spec(zip_list_dir(ZipHandle) -> {ok, Result} | {error, Reason} when
Result :: [zip_comment() | zip_file()],
ZipHandle :: handle(),
diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile
index b8e4d89996..2597157004 100644
--- a/lib/stdlib/test/Makefile
+++ b/lib/stdlib/test/Makefile
@@ -7,10 +7,12 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
MODULES= \
array_SUITE \
+ argparse_SUITE \
base64_SUITE \
base64_property_test_SUITE \
beam_lib_SUITE \
binary_module_SUITE \
+ binary_property_test_SUITE \
binref \
c_SUITE \
calendar_SUITE \
@@ -23,6 +25,7 @@ MODULES= \
dummy_h \
dummy_via \
edlin_expand_SUITE \
+ edlin_context_SUITE \
epp_SUITE \
erl_anno_SUITE \
erl_eval_SUITE \
@@ -49,6 +52,7 @@ MODULES= \
io_SUITE \
io_proto_SUITE \
lists_SUITE \
+ lists_property_test_SUITE \
log_mf_h_SUITE \
math_SUITE \
ms_transform_SUITE \
@@ -104,10 +108,12 @@ MODULES= \
ERTS_MODULES= erts_test_utils
SASL_MODULES= otp_vsns
+KERNEL_MODULES= rtnode
ERL_FILES= $(MODULES:%=%.erl) \
$(ERTS_MODULES:%=$(ERL_TOP)/erts/emulator/test/%.erl) \
- $(SASL_MODULES:%=$(ERL_TOP)/lib/sasl/test/%.erl)
+ $(SASL_MODULES:%=$(ERL_TOP)/lib/sasl/test/%.erl) \
+ $(KERNEL_MODULES:%=$(ERL_TOP)/lib/kernel/test/%.erl)
EXTRA_FILES= $(ERL_TOP)/otp_versions.table
@@ -135,7 +141,7 @@ COVERFILE=stdlib.cover
make_emakefile:
$(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) \
- $(MODULES) $(ERTS_MODULES) $(SASL_MODULES) \
+ $(MODULES) $(ERTS_MODULES) $(SASL_MODULES) $(KERNEL_MODULES) \
> $(EMAKEFILE)
tests $(TYPES): make_emakefile
@@ -161,6 +167,7 @@ release_tests_spec: make_emakefile
$(ERL_FILES) $(COVERFILE) $(EXTRA_FILES) "$(RELSYSDIR)"
chmod -R u+w "$(RELSYSDIR)"
@tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -)
+ $(INSTALL_DIR) "$(RELSYSDIR)/stdlib_SUITE_data"
$(INSTALL_DATA) $(ERL_TOP)/make/otp_version_tickets "$(RELSYSDIR)/stdlib_SUITE_data"
release_docs_spec:
diff --git a/lib/stdlib/test/argparse_SUITE.erl b/lib/stdlib/test/argparse_SUITE.erl
new file mode 100644
index 0000000000..fb7eaecda1
--- /dev/null
+++ b/lib/stdlib/test/argparse_SUITE.erl
@@ -0,0 +1,1063 @@
+%%
+%%
+%% Copyright Maxim Fedorov
+%%
+%%
+%% 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.
+
+-module(argparse_SUITE).
+-author("maximfca@gmail.com").
+
+-export([suite/0, all/0, groups/0]).
+
+-export([
+ readme/0, readme/1,
+ basic/0, basic/1,
+ long_form_eq/0, long_form_eq/1,
+ built_in_types/0, built_in_types/1,
+ type_validators/0, type_validators/1,
+ invalid_arguments/0, invalid_arguments/1,
+ complex_command/0, complex_command/1,
+ unicode/0, unicode/1,
+ parser_error/0, parser_error/1,
+ nargs/0, nargs/1,
+ argparse/0, argparse/1,
+ negative/0, negative/1,
+ nodigits/0, nodigits/1,
+ pos_mixed_with_opt/0, pos_mixed_with_opt/1,
+ default_for_not_required/0, default_for_not_required/1,
+ global_default/0, global_default/1,
+ subcommand/0, subcommand/1,
+ very_short/0, very_short/1,
+ multi_short/0, multi_short/1,
+ proxy_arguments/0, proxy_arguments/1,
+
+ usage/0, usage/1,
+ usage_required_args/0, usage_required_args/1,
+ usage_template/0, usage_template/1,
+ parser_error_usage/0, parser_error_usage/1,
+ command_usage/0, command_usage/1,
+ usage_width/0, usage_width/1,
+
+ validator_exception/0, validator_exception/1,
+ validator_exception_format/0, validator_exception_format/1,
+
+ run_handle/0, run_handle/1
+]).
+
+-include_lib("stdlib/include/assert.hrl").
+
+suite() ->
+ [{timetrap, {seconds, 30}}].
+
+groups() ->
+ [
+ {parser, [parallel], [
+ readme, basic, long_form_eq, built_in_types, type_validators,
+ invalid_arguments, complex_command, unicode, parser_error,
+ nargs, argparse, negative, nodigits, pos_mixed_with_opt,
+ default_for_not_required, global_default, subcommand,
+ very_short, multi_short, proxy_arguments
+ ]},
+ {usage, [parallel], [
+ usage, usage_required_args, usage_template,
+ parser_error_usage, command_usage, usage_width
+ ]},
+ {validator, [parallel], [
+ validator_exception, validator_exception_format
+ ]},
+ {run, [parallel], [
+ run_handle
+ ]}
+ ].
+
+all() ->
+ [{group, parser}, {group, validator}, {group, usage}].
+
+%%--------------------------------------------------------------------
+%% Helpers
+
+prog() ->
+ {ok, [[ProgStr]]} = init:get_argument(progname), ProgStr.
+
+parser_error(CmdLine, CmdMap) ->
+ {error, Reason} = parse(CmdLine, CmdMap),
+ unicode:characters_to_list(argparse:format_error(Reason)).
+
+parse_opts(Args, Opts) ->
+ argparse:parse(string:lexemes(Args, " "), #{arguments => Opts}).
+
+parse(Args, Command) ->
+ argparse:parse(string:lexemes(Args, " "), Command).
+
+parse_cmd(Args, Command) ->
+ argparse:parse(string:lexemes(Args, " "), #{commands => Command}).
+
+%% ubiquitous command, containing sub-commands, and all possible option types
+%% with all nargs. Not all combinations though.
+ubiq_cmd() ->
+ #{
+ arguments => [
+ #{name => r, short => $r, type => boolean, help => "recursive"},
+ #{name => f, short => $f, type => boolean, long => "-force", help => "force"},
+ #{name => v, short => $v, type => boolean, action => count, help => "verbosity level"},
+ #{name => interval, short => $i, type => {integer, [{min, 1}]}, help => "interval set"},
+ #{name => weird, long => "-req", help => "required optional, right?"},
+ #{name => float, long => "-float", type => float, default => 3.14, help => "floating-point long form argument"}
+ ],
+ commands => #{
+ "start" => #{help => "verifies configuration and starts server",
+ arguments => [
+ #{name => server, help => "server to start"},
+ #{name => shard, short => $s, type => integer, nargs => nonempty_list, help => "initial shards"},
+ #{name => part, short => $p, type => integer, nargs => list, help => hidden},
+ #{name => z, short => $z, type => {integer, [{min, 1}, {max, 10}]}, help => "between"},
+ #{name => l, short => $l, type => {integer, [{max, 10}]}, nargs => 'maybe', help => "maybe lower"},
+ #{name => more, short => $m, type => {integer, [{max, 10}]}, help => "less than 10"},
+ #{name => optpos, required => false, type => {integer, []}, help => "optional positional"},
+ #{name => bin, short => $b, type => {binary, <<"m">>}, help => "binary with re"},
+ #{name => g, short => $g, type => {binary, <<"m">>, []}, help => "binary with re"},
+ #{name => t, short => $t, type => {string, "m"}, help => "string with re"},
+ #{name => e, long => "--maybe-req", required => true, type => integer, nargs => 'maybe', help => "maybe required int"},
+ #{name => y, required => true, long => "-yyy", short => $y, type => {string, "m", []}, help => "string with re"},
+ #{name => u, short => $u, type => {string, ["1", "2"]}, help => "string choices"},
+ #{name => choice, short => $c, type => {integer, [1,2,3]}, help => "tough choice"},
+ #{name => fc, short => $q, type => {float, [2.1,1.2]}, help => "floating choice"},
+ #{name => ac, short => $w, type => {atom, [one, two]}, help => "atom choice"},
+ #{name => au, long => "-unsafe", type => {atom, unsafe}, help => "unsafe atom"},
+ #{name => as, long => "-safe", type => atom, help => <<"safe atom">>},
+ #{name => name, required => false, nargs => list, help => hidden},
+ #{name => long, long => "foobar", required => false, help => [<<"foobaring option">>]}
+ ], commands => #{
+ "crawler" => #{arguments => [
+ #{name => extra, long => "--extra", help => "extra option very deep"}
+ ],
+ help => "controls crawler behaviour"},
+ "doze" => #{help => "dozes a bit"}}
+ },
+ "stop" => #{help => <<"stops running server">>, arguments => []
+ },
+ "status" => #{help => "prints server status", arguments => [],
+ commands => #{
+ "crawler" => #{
+ arguments => [#{name => extra, long => "--extra", help => "extra option very deep"}],
+ help => "crawler status"}}
+ },
+ "restart" => #{help => hidden, arguments => [
+ #{name => server, help => "server to restart"},
+ #{name => duo, short => $d, long => "-duo", help => "dual option"}
+ ]}
+ }
+ }.
+
+%%--------------------------------------------------------------------
+%% Parser Test Cases
+
+readme() ->
+ [{doc, "Test cases covered in the README"}].
+
+readme(Config) when is_list(Config) ->
+ Prog = prog(),
+ Rm = #{
+ arguments => [
+ #{name => dir},
+ #{name => force, short => $f, type => boolean, default => false},
+ #{name => recursive, short => $r, type => boolean}
+ ]
+ },
+ ?assertEqual({ok, #{dir => "dir", force => true, recursive => true}, [Prog], Rm},
+ argparse:parse(["-rf", "dir"], Rm)),
+ %% override progname
+ ?assertEqual("Usage:\n readme\n",
+ unicode:characters_to_list(argparse:help(#{}, #{progname => "readme"}))),
+ ?assertEqual("Usage:\n readme\n",
+ unicode:characters_to_list(argparse:help(#{}, #{progname => readme}))),
+ ?assertEqual("Usage:\n readme\n",
+ unicode:characters_to_list(argparse:help(#{}, #{progname => <<"readme">>}))),
+ %% test that command has priority over just a positional argument:
+ %% - parsing "opt sub" means "find positional argument "pos", then enter subcommand
+ %% - parsing "sub opt" means "enter sub-command, and then find positional argument"
+ Cmd = #{
+ commands => #{"sub" => #{}},
+ arguments => [#{name => pos}]
+ },
+ ?assertEqual(parse("opt sub", Cmd), parse("sub opt", Cmd)).
+
+basic() ->
+ [{doc, "Basic cases"}].
+
+basic(Config) when is_list(Config) ->
+ Prog = prog(),
+ %% empty command, with full options path
+ ?assertMatch({ok, #{}, [Prog, "cmd"], #{}},
+ argparse:parse(["cmd"], #{commands => #{"cmd" => #{}}})),
+ %% sub-command, with no path, but user-supplied argument
+ ?assertEqual({ok, #{}, [Prog, "cmd", "sub"], #{attr => pos}},
+ argparse:parse(["cmd", "sub"], #{commands => #{"cmd" => #{commands => #{"sub" => #{attr => pos}}}}})),
+ %% command with positional argument
+ PosCmd = #{arguments => [#{name => pos}]},
+ ?assertEqual({ok, #{pos => "arg"}, [Prog, "cmd"], PosCmd},
+ argparse:parse(["cmd", "arg"], #{commands => #{"cmd" => PosCmd}})),
+ %% command with optional argument
+ OptCmd = #{arguments => [#{name => force, short => $f, type => boolean}]},
+ ?assertEqual({ok, #{force => true}, [Prog, "rm"], OptCmd},
+ parse(["rm -f"], #{commands => #{"rm" => OptCmd}}), "rm -f"),
+ %% command with optional and positional argument
+ PosOptCmd = #{arguments => [#{name => force, short => $f, type => boolean}, #{name => dir}]},
+ ?assertEqual({ok, #{force => true, dir => "dir"}, [Prog, "rm"], PosOptCmd},
+ parse(["rm -f dir"], #{commands => #{"rm" => PosOptCmd}}), "rm -f dir"),
+ %% no command, just argument list
+ KernelCmd = #{arguments => [#{name => kernel, long => "kernel", type => atom, nargs => 2}]},
+ ?assertEqual({ok, #{kernel => [port, dist]}, [Prog], KernelCmd},
+ parse(["-kernel port dist"], KernelCmd)),
+ %% same but positional
+ ArgListCmd = #{arguments => [#{name => arg, nargs => 2, type => boolean}]},
+ ?assertEqual({ok, #{arg => [true, false]}, [Prog], ArgListCmd},
+ parse(["true false"], ArgListCmd)).
+
+long_form_eq() ->
+ [{doc, "Tests that long form supports --arg=value"}].
+
+long_form_eq(Config) when is_list(Config) ->
+ Prog = prog(),
+ %% cmd --arg=value
+ PosOptCmd = #{arguments => [#{name => arg, long => "-arg"}]},
+ ?assertEqual({ok, #{arg => "value"}, [Prog, "cmd"], PosOptCmd},
+ parse(["cmd --arg=value"], #{commands => #{"cmd" => PosOptCmd}})),
+ %% --integer=10
+ ?assertMatch({ok, #{int := 10}, _, _},
+ parse(["--int=10"], #{arguments => [#{name => int, type => integer, long => "-int"}]})).
+
+built_in_types() ->
+ [{doc, "Tests all built-in types supplied as a single argument"}].
+
+% built-in types testing
+built_in_types(Config) when is_list(Config) ->
+ Prog = [prog()],
+ Bool = #{arguments => [#{name => meta, type => boolean, short => $b, long => "-boolean"}]},
+ ?assertEqual({ok, #{}, Prog, Bool}, parse([""], Bool)),
+ ?assertEqual({ok, #{meta => true}, Prog, Bool}, parse(["-b"], Bool)),
+ ?assertEqual({ok, #{meta => true}, Prog, Bool}, parse(["--boolean"], Bool)),
+ ?assertEqual({ok, #{meta => false}, Prog, Bool}, parse(["--boolean false"], Bool)),
+ %% integer tests
+ Int = #{arguments => [#{name => int, type => integer, short => $i, long => "-int"}]},
+ ?assertEqual({ok, #{int => 1}, Prog, Int}, parse([" -i 1"], Int)),
+ ?assertEqual({ok, #{int => 1}, Prog, Int}, parse(["--int 1"], Int)),
+ ?assertEqual({ok, #{int => -1}, Prog, Int}, parse(["-i -1"], Int)),
+ %% floating point
+ Float = #{arguments => [#{name => f, type => float, short => $f}]},
+ ?assertEqual({ok, #{f => 44.44}, Prog, Float}, parse(["-f 44.44"], Float)),
+ %% atoms, existing
+ Atom = #{arguments => [#{name => atom, type => atom, short => $a, long => "-atom"}]},
+ ?assertEqual({ok, #{atom => atom}, Prog, Atom}, parse(["-a atom"], Atom)),
+ ?assertEqual({ok, #{atom => atom}, Prog, Atom}, parse(["--atom atom"], Atom)).
+
+type_validators() ->
+ [{doc, "Test that parser return expected conversions for valid arguments"}].
+
+type_validators(Config) when is_list(Config) ->
+ %% successful string regexes
+ ?assertMatch({ok, #{str := "me"}, _, _},
+ parse_opts("me", [#{name => str, type => {string, "m."}}])),
+ ?assertMatch({ok, #{str := "me"}, _, _},
+ parse_opts("me", [#{name => str, type => {string, "m.", []}}])),
+ ?assertMatch({ok, #{"str" := "me"}, _, _},
+ parse_opts("me", [#{name => "str", type => {string, "m.", [{capture, none}]}}])),
+ %% and binary too...
+ ?assertMatch({ok, #{bin := <<"me">>}, _, _},
+ parse_opts("me", [#{name => bin, type => {binary, <<"m.">>}}])),
+ ?assertMatch({ok, #{<<"bin">> := <<"me">>}, _, _},
+ parse_opts("me", [#{name => <<"bin">>, type => {binary, <<"m.">>, []}}])),
+ ?assertMatch({ok, #{bin := <<"me">>}, _, _},
+ parse_opts("me", [#{name => bin, type => {binary, <<"m.">>, [{capture, none}]}}])),
+ %% successful integer with range validators
+ ?assertMatch({ok, #{int := 5}, _, _},
+ parse_opts("5", [#{name => int, type => {integer, [{min, 0}, {max, 10}]}}])),
+ ?assertMatch({ok, #{bin := <<"5">>}, _, _},
+ parse_opts("5", [#{name => bin, type => binary}])),
+ ?assertMatch({ok, #{str := "011"}, _, _},
+ parse_opts("11", [#{name => str, type => {custom, fun(S) -> [$0|S] end}}])),
+ %% choices: valid
+ ?assertMatch({ok, #{bin := <<"K">>}, _, _},
+ parse_opts("K", [#{name => bin, type => {binary, [<<"M">>, <<"K">>]}}])),
+ ?assertMatch({ok, #{str := "K"}, _, _},
+ parse_opts("K", [#{name => str, type => {string, ["K", "N"]}}])),
+ ?assertMatch({ok, #{atom := one}, _, _},
+ parse_opts("one", [#{name => atom, type => {atom, [one, two]}}])),
+ ?assertMatch({ok, #{int := 12}, _, _},
+ parse_opts("12", [#{name => int, type => {integer, [10, 12]}}])),
+ ?assertMatch({ok, #{float := 1.3}, _, _},
+ parse_opts("1.3", [#{name => float, type => {float, [1.3, 1.4]}}])),
+ %% test for unsafe atom
+ %% ensure the atom does not exist
+ ?assertException(error, badarg, list_to_existing_atom("$can_never_be")),
+ {ok, ArgMap, _, _} = parse_opts("$can_never_be", [#{name => atom, type => {atom, unsafe}}]),
+ argparse:validate(#{arguments => [#{name => atom, type => {atom, unsafe}}]}),
+ %% now that atom exists, because argparse created it (in an unsafe way!)
+ ?assertEqual(list_to_existing_atom("$can_never_be"), maps:get(atom, ArgMap)),
+ %% test successful user-defined conversion
+ ?assertMatch({ok, #{user := "VER"}, _, _},
+ parse_opts("REV", [#{name => user, type => {custom, fun (Str) -> lists:reverse(Str) end}}])).
+
+invalid_arguments() ->
+ [{doc, "Test that parser return errors for invalid arguments"}].
+
+invalid_arguments(Config) when is_list(Config) ->
+ %% {float, [{min, float()} | {max, float()}]} |
+ Prog = [prog()],
+ MinFloat = #{name => float, type => {float, [{min, 1.0}]}},
+ ?assertEqual({error, {Prog, MinFloat, "0.0", <<"is less than accepted minimum">>}},
+ parse_opts("0.0", [MinFloat])),
+ MaxFloat = #{name => float, type => {float, [{max, 1.0}]}},
+ ?assertEqual({error, {Prog, MaxFloat, "2.0", <<"is greater than accepted maximum">>}},
+ parse_opts("2.0", [MaxFloat])),
+ %% {int, [{min, integer()} | {max, integer()}]} |
+ MinInt = #{name => int, type => {integer, [{min, 20}]}},
+ ?assertEqual({error, {Prog, MinInt, "10", <<"is less than accepted minimum">>}},
+ parse_opts("10", [MinInt])),
+ MaxInt = #{name => int, type => {integer, [{max, -10}]}},
+ ?assertEqual({error, {Prog, MaxInt, "-5", <<"is greater than accepted maximum">>}},
+ parse_opts("-5", [MaxInt])),
+ %% string: regex & regex with options
+ %% {string, string()} | {string, string(), []}
+ StrRegex = #{name => str, type => {string, "me.me"}},
+ ?assertEqual({error, {Prog, StrRegex, "me", <<"does not match">>}},
+ parse_opts("me", [StrRegex])),
+ StrRegexOpt = #{name => str, type => {string, "me.me", []}},
+ ?assertEqual({error, {Prog, StrRegexOpt, "me", <<"does not match">>}},
+ parse_opts("me", [StrRegexOpt])),
+ %% {binary, {re, binary()} | {re, binary(), []}
+ BinRegex = #{name => bin, type => {binary, <<"me.me">>}},
+ ?assertEqual({error, {Prog, BinRegex, "me", <<"does not match">>}},
+ parse_opts("me", [BinRegex])),
+ BinRegexOpt = #{name => bin, type => {binary, <<"me.me">>, []}},
+ ?assertEqual({error, {Prog, BinRegexOpt, "me", <<"does not match">>}},
+ parse_opts("me", [BinRegexOpt])),
+ %% invalid integer (comma , is not parsed)
+ ?assertEqual({error, {Prog, MinInt, "1,", <<"is not an integer">>}},
+ parse_opts(["1,"], [MinInt])),
+ %% test invalid choices
+ BinChoices = #{name => bin, type => {binary, [<<"M">>, <<"N">>]}},
+ ?assertEqual({error, {Prog, BinChoices, "K", <<"is not one of the choices">>}},
+ parse_opts("K", [BinChoices])),
+ StrChoices = #{name => str, type => {string, ["M", "N"]}},
+ ?assertEqual({error, {Prog, StrChoices, "K", <<"is not one of the choices">>}},
+ parse_opts("K", [StrChoices])),
+ AtomChoices = #{name => atom, type => {atom, [one, two]}},
+ ?assertEqual({error, {Prog, AtomChoices, "K", <<"is not one of the choices">>}},
+ parse_opts("K", [AtomChoices])),
+ IntChoices = #{name => int, type => {integer, [10, 11]}},
+ ?assertEqual({error, {Prog, IntChoices, "12", <<"is not one of the choices">>}},
+ parse_opts("12", [IntChoices])),
+ FloatChoices = #{name => float, type => {float, [1.2, 1.4]}},
+ ?assertEqual({error, {Prog, FloatChoices, "1.3", <<"is not one of the choices">>}},
+ parse_opts("1.3", [FloatChoices])),
+ %% unsuccessful user-defined conversion
+ ?assertMatch({error, {Prog, _, "REV", <<"failed faildation">>}},
+ parse_opts("REV", [#{name => user, type => {custom, fun (Str) -> integer_to_binary(Str) end}}])).
+
+complex_command() ->
+ [{doc, "Parses a complex command that has a mix of optional and positional arguments"}].
+
+complex_command(Config) when is_list(Config) ->
+ Command = #{arguments => [
+ %% options
+ #{name => string, short => $s, long => "-string", action => append, help => "String list option"},
+ #{name => boolean, type => boolean, short => $b, action => append, help => "Boolean list option"},
+ #{name => float, type => float, short => $f, long => "-float", action => append, help => "Float option"},
+ %% positional args
+ #{name => integer, type => integer, help => "Integer variable"},
+ #{name => string, help => "alias for string option", action => extend, nargs => list}
+ ]},
+ CmdMap = #{commands => #{"start" => Command}},
+ Parsed = argparse:parse(string:lexemes("start --float 1.04 -f 112 -b -b -s s1 42 --string s2 s3 s4", " "), CmdMap),
+ Expected = #{float => [1.04, 112], boolean => [true, true], integer => 42, string => ["s1", "s2", "s3", "s4"]},
+ ?assertEqual({ok, Expected, [prog(), "start"], Command}, Parsed).
+
+unicode() ->
+ [{doc, "Tests basic unicode support"}].
+
+unicode(Config) when is_list(Config) ->
+ %% test unicode short & long
+ ?assertMatch({ok, #{one := true}, _, _},
+ parse(["-Ф"], #{arguments => [#{name => one, short => $Ф, type => boolean}]})),
+ ?assertMatch({ok, #{long := true}, _, _},
+ parse(["--åäö"], #{arguments => [#{name => long, long => "-åäö", type => boolean}]})),
+ %% test default, help and value in unicode
+ Cmd = #{arguments => [#{name => text, type => binary, help => "åäö", default => <<"★"/utf8>>}]},
+ Expected = #{text => <<"★"/utf8>>},
+ Prog = [prog()],
+ ?assertEqual({ok, Expected, Prog, Cmd}, argparse:parse([], Cmd)), %% default
+ ?assertEqual({ok, Expected, Prog, Cmd}, argparse:parse(["★"], Cmd)), %% specified in the command line
+ ?assertEqual("Usage:\n " ++ prog() ++ " <text>\n\nArguments:\n text åäö (binary, ★)\n",
+ unicode:characters_to_list(argparse:help(Cmd))),
+ %% test command name and argument name in unicode
+ Uni = #{commands => #{"åäö" => #{help => "öФ"}}, handler => optional,
+ arguments => [#{name => "Ф", short => $ä, long => "åäö"}]},
+ UniExpected = "Usage:\n " ++ prog() ++
+ " {åäö} [-ä <Ф>] [-åäö <Ф>]\n\nSubcommands:\n åäö öФ\n\nOptional arguments:\n -ä, -åäö Ф\n",
+ ?assertEqual(UniExpected, unicode:characters_to_list(argparse:help(Uni))),
+ ParsedExpected = #{"Ф" => "öФ"},
+ ?assertEqual({ok, ParsedExpected, Prog, Uni}, argparse:parse(["-ä", "öФ"], Uni)).
+
+parser_error() ->
+ [{doc, "Tests error tuples that the parser returns"}].
+
+parser_error(Config) when is_list(Config) ->
+ Prog = prog(),
+ %% unknown option at the top of the path
+ ?assertEqual({error, {[Prog], undefined, "arg", <<>>}},
+ parse_cmd(["arg"], #{})),
+ %% positional argument missing in a sub-command
+ Opt = #{name => mode, required => true},
+ ?assertMatch({error, {[Prog, "start"], _, undefined, <<>>}},
+ parse_cmd(["start"], #{"start" => #{arguments => [Opt]}})),
+ %% optional argument missing in a sub-command
+ Opt1 = #{name => mode, short => $o, required => true},
+ ?assertMatch({error, {[Prog, "start"], _, undefined, <<>>}},
+ parse_cmd(["start"], #{"start" => #{arguments => [Opt1]}})),
+ %% positional argument: an atom that does not exist
+ Opt2 = #{name => atom, type => atom},
+ ?assertEqual({error, {[Prog], Opt2, "boo-foo", <<"is not an existing atom">>}},
+ parse_opts(["boo-foo"], [Opt2])),
+ %% optional argument missing some items
+ Opt3 = #{name => kernel, long => "kernel", type => atom, nargs => 2},
+ ?assertEqual({error, {[Prog], Opt3, ["port"], "expected 2, found 1 argument(s)"}},
+ parse_opts(["-kernel port"], [Opt3])),
+ %% positional argument missing some items
+ Opt4 = #{name => arg, type => atom, nargs => 3},
+ ?assertEqual({error, {[Prog], Opt4, ["p1"], "expected 3, found 1 argument(s)"}},
+ parse_opts(["p1"], [Opt4])),
+ %% short option with no argument, when it's needed
+ ?assertMatch({error, {_, _, undefined, <<"expected argument">>}},
+ parse("-1", #{arguments => [#{name => short49, short => 49}]})).
+
+nargs() ->
+ [{doc, "Tests argument consumption option, with nargs"}].
+
+nargs(Config) when is_list(Config) ->
+ Prog = [prog()],
+ %% consume optional list arguments
+ Opts = [
+ #{name => arg, short => $s, nargs => list, type => integer},
+ #{name => bool, short => $b, type => boolean}
+ ],
+ ?assertMatch({ok, #{arg := [1, 2, 3], bool := true}, _, _},
+ parse_opts(["-s 1 2 3 -b"], Opts)),
+ %% consume one_or_more arguments in an optional list
+ Opts2 = [
+ #{name => arg, short => $s, nargs => nonempty_list},
+ #{name => extra, short => $x}
+ ],
+ ?assertMatch({ok, #{extra := "X", arg := ["a","b","c"]}, _, _},
+ parse_opts(["-s port -s a b c -x X"], Opts2)),
+ %% error if there is no argument to consume
+ ?assertMatch({error, {_, _, ["-x"], <<"expected argument">>}},
+ parse_opts(["-s -x"], Opts2)),
+ %% error when positional has nargs = nonempty_list or pos_integer
+ ?assertMatch({error, {_, _, undefined, <<>>}},
+ parse_opts([""], [#{name => req, nargs => nonempty_list}])),
+ %% positional arguments consumption: one or more positional argument
+ OptsPos1 = #{arguments => [
+ #{name => arg, nargs => nonempty_list},
+ #{name => extra, short => $x}
+ ]},
+ ?assertEqual({ok, #{extra => "X", arg => ["b","c"]}, Prog, OptsPos1},
+ parse(["-x port -x a b c -x X"], OptsPos1)),
+ %% positional arguments consumption, any number (maybe zero)
+ OptsPos2 = #{arguments => [
+ #{name => arg, nargs => list},
+ #{name => extra, short => $x}
+ ]},
+ ?assertEqual({ok, #{extra => "X", arg => ["a","b","c"]}, Prog, OptsPos2},
+ parse(["-x port a b c -x X"], OptsPos2)),
+ %% positional: consume ALL arguments!
+ OptsAll = #{arguments => [
+ #{name => arg, nargs => all},
+ #{name => extra, short => $x}
+ ]},
+ ?assertEqual({ok, #{extra => "port", arg => ["a","b","c", "-x", "X"]}, Prog, OptsAll},
+ parse(["-x port a b c -x X"], OptsAll)),
+ %% maybe with a specified default
+ OptMaybe = [
+ #{name => foo, long => "-foo", nargs => {'maybe', c}, default => d},
+ #{name => bar, nargs => 'maybe', default => d}
+ ],
+ ?assertMatch({ok, #{foo := "YY", bar := "XX"}, Prog, _},
+ parse_opts(["XX --foo YY"], OptMaybe)),
+ ?assertMatch({ok, #{foo := c, bar := "XX"}, Prog, _},
+ parse_opts(["XX --foo"], OptMaybe)),
+ ?assertMatch({ok, #{foo := d, bar := d}, Prog, _},
+ parse_opts([""], OptMaybe)),
+ %% maybe with default provided by argparse
+ ?assertMatch({ok, #{foo := d, bar := "XX", baz := ok}, _, _},
+ parse_opts(["XX -b"], [#{name => baz, nargs => 'maybe', short => $b, default => ok} | OptMaybe])),
+ %% maybe arg - with no default given
+ ?assertMatch({ok, #{foo := d, bar := "XX", baz := 0}, _, _},
+ parse_opts(["XX -b"], [#{name => baz, nargs => 'maybe', short => $b, type => integer} | OptMaybe])),
+ ?assertMatch({ok, #{foo := d, bar := "XX", baz := ""}, _, _},
+ parse_opts(["XX -b"], [#{name => baz, nargs => 'maybe', short => $b, type => string} | OptMaybe])),
+ ?assertMatch({ok, #{foo := d, bar := "XX", baz := undefined}, _, _},
+ parse_opts(["XX -b"], [#{name => baz, nargs => 'maybe', short => $b, type => atom} | OptMaybe])),
+ ?assertMatch({ok, #{foo := d, bar := "XX", baz := <<"">>}, _, _},
+ parse_opts(["XX -b"], [#{name => baz, nargs => 'maybe', short => $b, type => binary} | OptMaybe])),
+ %% nargs: optional list, yet it still needs to be 'not required'!
+ OptList = [#{name => arg, nargs => list, required => false, type => integer}],
+ ?assertEqual({ok, #{}, Prog, #{arguments => OptList}}, parse_opts("", OptList)),
+ %% tests that action "count" with nargs "maybe" counts two times, first time
+ %% consuming an argument (for "maybe"), second time just counting
+ Cmd = #{arguments => [
+ #{name => short49, short => $1, long => "-force", action => count, nargs => 'maybe'}]},
+ ?assertEqual({ok, #{short49 => 2}, Prog, Cmd},
+ parse("-1 arg1 --force", Cmd)).
+
+argparse() ->
+ [{doc, "Tests success cases, inspired by argparse in Python"}].
+
+argparse(Config) when is_list(Config) ->
+ Prog = [prog()],
+ Parser = #{arguments => [
+ #{name => sum, long => "-sum", action => {store, sum}, default => max},
+ #{name => integers, type => integer, nargs => nonempty_list}
+ ]},
+ ?assertEqual({ok, #{integers => [1, 2, 3, 4], sum => max}, Prog, Parser},
+ parse("1 2 3 4", Parser)),
+ ?assertEqual({ok, #{integers => [1, 2, 3, 4], sum => sum}, Prog, Parser},
+ parse("1 2 3 4 --sum", Parser)),
+ ?assertEqual({ok, #{integers => [7, -1, 42], sum => sum}, Prog, Parser},
+ parse("--sum 7 -1 42", Parser)),
+ %% name or flags
+ Parser2 = #{arguments => [
+ #{name => bar, required => true},
+ #{name => foo, short => $f, long => "-foo"}
+ ]},
+ ?assertEqual({ok, #{bar => "BAR"}, Prog, Parser2}, parse("BAR", Parser2)),
+ ?assertEqual({ok, #{bar => "BAR", foo => "FOO"}, Prog, Parser2}, parse("BAR --foo FOO", Parser2)),
+ %PROG: error: the following arguments are required: bar
+ ?assertMatch({error, {Prog, _, undefined, <<>>}}, parse("--foo FOO", Parser2)),
+ %% action tests: default
+ ?assertMatch({ok, #{foo := "1"}, Prog, _},
+ parse("--foo 1", #{arguments => [#{name => foo, long => "-foo"}]})),
+ %% action test: store
+ ?assertMatch({ok, #{foo := 42}, Prog, _},
+ parse("--foo", #{arguments => [#{name => foo, long => "-foo", action => {store, 42}}]})),
+ %% action tests: boolean (variants)
+ ?assertMatch({ok, #{foo := true}, Prog, _},
+ parse("--foo", #{arguments => [#{name => foo, long => "-foo", action => {store, true}}]})),
+ ?assertMatch({ok, #{foo := 42}, Prog, _},
+ parse("--foo", #{arguments => [#{name => foo, long => "-foo", type => boolean, action => {store, 42}}]})),
+ ?assertMatch({ok, #{foo := true}, Prog, _},
+ parse("--foo", #{arguments => [#{name => foo, long => "-foo", type => boolean}]})),
+ ?assertMatch({ok, #{foo := true}, Prog, _},
+ parse("--foo true", #{arguments => [#{name => foo, long => "-foo", type => boolean}]})),
+ ?assertMatch({ok, #{foo := false}, Prog, _},
+ parse("--foo false", #{arguments => [#{name => foo, long => "-foo", type => boolean}]})),
+ %% action tests: append & append_const
+ ?assertMatch({ok, #{all := [1, "1"]}, Prog, _},
+ parse("--x 1 -x 1", #{arguments => [
+ #{name => all, long => "-x", type => integer, action => append},
+ #{name => all, short => $x, action => append}]})),
+ ?assertMatch({ok, #{all := ["Z", 2]}, Prog, _},
+ parse("--x -x", #{arguments => [
+ #{name => all, long => "-x", action => {append, "Z"}},
+ #{name => all, short => $x, action => {append, 2}}]})),
+ %% count:
+ ?assertMatch({ok, #{v := 3}, Prog, _},
+ parse("-v -v -v", #{arguments => [#{name => v, short => $v, action => count}]})).
+
+negative() ->
+ [{doc, "Test negative number parser"}].
+
+negative(Config) when is_list(Config) ->
+ Parser = #{arguments => [
+ #{name => x, short => $x, type => integer, action => store},
+ #{name => foo, nargs => 'maybe', required => false}
+ ]},
+ ?assertMatch({ok, #{x := -1}, _, _}, parse("-x -1", Parser)),
+ ?assertMatch({ok, #{x := -1, foo := "-5"}, _, _}, parse("-x -1 -5", Parser)),
+ %%
+ Parser2 = #{arguments => [
+ #{name => one, short => $1},
+ #{name => foo, nargs => 'maybe', required => false}
+ ]},
+
+ %% negative number options present, so -1 is an option
+ ?assertMatch({ok, #{one := "X"}, _, _}, parse("-1 X", Parser2)),
+ %% negative number options present, so -2 is an option
+ ?assertMatch({error, {_, undefined, "-2", _}}, parse("-2", Parser2)),
+
+ %% negative number options present, so both -1s are options
+ ?assertMatch({error, {_, _, undefined, _}}, parse("-1 -1", Parser2)),
+ %% no "-" prefix, can only be an integer
+ ?assertMatch({ok, #{foo := "-1"}, _, _}, argparse:parse(["-1"], Parser2, #{prefixes => "+"})),
+ %% no "-" prefix, can only be an integer, but just one integer!
+ ?assertMatch({error, {_, undefined, "-1", _}},
+ argparse:parse(["-2", "-1"], Parser2, #{prefixes => "+"})),
+ %% just in case, floats work that way too...
+ ?assertMatch({error, {_, undefined, "-2", _}},
+ parse("-2", #{arguments => [#{name => one, long => "1.2"}]})).
+
+nodigits() ->
+ [{doc, "Test prefixes and negative numbers together"}].
+
+nodigits(Config) when is_list(Config) ->
+ %% verify nodigits working as expected
+ Parser3 = #{arguments => [
+ #{name => extra, short => $3},
+ #{name => arg, nargs => list}
+ ]},
+ %% ensure not to consume optional prefix
+ ?assertEqual({ok, #{extra => "X", arg => ["a","b","3"]}, [prog()], Parser3},
+ argparse:parse(string:lexemes("-3 port a b 3 +3 X", " "), Parser3, #{prefixes => "-+"})).
+
+pos_mixed_with_opt() ->
+ [{doc, "Tests that optional argument correctly consumes expected argument"
+ "inspired by https://github.com/python/cpython/issues/59317"}].
+
+pos_mixed_with_opt(Config) when is_list(Config) ->
+ Parser = #{arguments => [
+ #{name => pos},
+ #{name => opt, default => 24, type => integer, long => "-opt"},
+ #{name => vars, nargs => list}
+ ]},
+ ?assertEqual({ok, #{pos => "1", opt => 8, vars => ["8", "9"]}, [prog()], Parser},
+ parse("1 2 --opt 8 8 9", Parser)).
+
+default_for_not_required() ->
+ [{doc, "Tests that default value is used for non-required positional argument"}].
+
+default_for_not_required(Config) when is_list(Config) ->
+ ?assertMatch({ok, #{def := 1}, _, _},
+ parse("", #{arguments => [#{name => def, short => $d, required => false, default => 1}]})),
+ ?assertMatch({ok, #{def := 1}, _, _},
+ parse("", #{arguments => [#{name => def, required => false, default => 1}]})).
+
+global_default() ->
+ [{doc, "Tests that a global default can be enabled for all non-required arguments"}].
+
+global_default(Config) when is_list(Config) ->
+ ?assertMatch({ok, #{def := "global"}, _, _},
+ argparse:parse("", #{arguments => [#{name => def, type => integer, required => false}]},
+ #{default => "global"})).
+
+subcommand() ->
+ [{doc, "Tests subcommands parser"}].
+
+subcommand(Config) when is_list(Config) ->
+ TwoCmd = #{arguments => [#{name => bar}]},
+ Cmd = #{
+ arguments => [#{name => force, type => boolean, short => $f}],
+ commands => #{"one" => #{
+ arguments => [#{name => foo, type => boolean, long => "-foo"}, #{name => baz}],
+ commands => #{
+ "two" => TwoCmd}}}},
+ ?assertEqual({ok, #{force => true, baz => "N1O1O", foo => true, bar => "bar"}, [prog(), "one", "two"], TwoCmd},
+ parse("one N1O1O -f two --foo bar", Cmd)),
+ %% it is an error not to choose subcommand
+ ?assertEqual({error, {[prog(), "one"], undefined, undefined, <<"subcommand expected">>}},
+ parse("one N1O1O -f", Cmd)).
+
+very_short() ->
+ [{doc, "Tests short option appended to the optional itself"}].
+
+very_short(Config) when is_list(Config) ->
+ ?assertMatch({ok, #{x := "V"}, _, _},
+ parse("-xV", #{arguments => [#{name => x, short => $x}]})).
+
+multi_short() ->
+ [{doc, "Tests multiple short arguments blend into one"}].
+
+multi_short(Config) when is_list(Config) ->
+ %% ensure non-flammable argument does not explode, even when it's possible
+ ?assertMatch({ok, #{v := "xv"}, _, _},
+ parse("-vxv", #{arguments => [#{name => v, short => $v}, #{name => x, short => $x}]})),
+ %% ensure 'verbosity' use-case works
+ ?assertMatch({ok, #{v := 3}, _, _},
+ parse("-vvv", #{arguments => [#{name => v, short => $v, action => count}]})),
+ %%
+ ?assertMatch({ok, #{recursive := true, force := true, path := "dir"}, _, _},
+ parse("-rf dir", #{arguments => [
+ #{name => recursive, short => $r, type => boolean},
+ #{name => force, short => $f, type => boolean},
+ #{name => path}
+ ]})).
+
+proxy_arguments() ->
+ [{doc, "Tests nargs => all used to proxy arguments to another script"}].
+
+proxy_arguments(Config) when is_list(Config) ->
+ Cmd = #{
+ commands => #{
+ "start" => #{
+ arguments => [
+ #{name => shell, short => $s, long => "-shell", type => boolean},
+ #{name => skip, short => $x, long => "-skip", type => boolean},
+ #{name => args, required => false, nargs => all}
+ ]
+ },
+ "stop" => #{},
+ "status" => #{
+ arguments => [
+ #{name => skip, required => false, default => "ok"},
+ #{name => args, required => false, nargs => all}
+ ]},
+ "state" => #{
+ arguments => [
+ #{name => skip, required => false},
+ #{name => args, required => false, nargs => all}
+ ]}
+ },
+ arguments => [
+ #{name => node}
+ ],
+ handler => fun (#{}) -> ok end
+ },
+ Prog = prog(),
+ ?assertMatch({ok, #{node := "node1"}, _, _}, parse("node1", Cmd)),
+ ?assertMatch({ok, #{node := "node1"}, [Prog, "stop"], #{}}, parse("node1 stop", Cmd)),
+ ?assertMatch({ok, #{node := "node2.org", shell := true, skip := true}, _, _}, parse("node2.org start -x -s", Cmd)),
+ ?assertMatch({ok, #{args := ["-app","key","value"],node := "node1.org"}, [Prog, "start"], _},
+ parse("node1.org start -app key value", Cmd)),
+ ?assertMatch({ok, #{args := ["-app","key","value", "-app2", "key2", "value2"],
+ node := "node3.org", shell := true}, [Prog, "start"], _},
+ parse("node3.org start -s -app key value -app2 key2 value2", Cmd)),
+ %% test that any non-required positionals are skipped
+ ?assertMatch({ok, #{args := ["-a","bcd"], node := "node2.org", skip := "ok"}, _, _}, parse("node2.org status -a bcd", Cmd)),
+ ?assertMatch({ok, #{args := ["-app", "key"], node := "node2.org"}, _, _}, parse("node2.org state -app key", Cmd)).
+
+%%--------------------------------------------------------------------
+%% Usage Test Cases
+
+usage() ->
+ [{doc, "Basic tests for help formatter, including 'hidden' help"}].
+
+usage(Config) when is_list(Config) ->
+ Cmd = ubiq_cmd(),
+ Usage = "Usage:\n erl start {crawler|doze} [-lrfv] [-s <shard>...] [-z <z>] [-m <more>] [-b <bin>]\n"
+ " [-g <g>] [-t <t>] ---maybe-req -y <y> --yyy <y> [-u <u>] [-c <choice>]\n"
+ " [-q <fc>] [-w <ac>] [--unsafe <au>] [--safe <as>] [-foobar <long>] [--force]\n"
+ " [-i <interval>] [--req <weird>] [--float <float>] <server> [<optpos>]\n\n"
+ "Subcommands:\n"
+ " crawler controls crawler behaviour\n"
+ " doze dozes a bit\n\n"
+ "Arguments:\n"
+ " server server to start\n"
+ " optpos optional positional (int)\n\n"
+ "Optional arguments:\n"
+ " -s initial shards (int)\n"
+ " -z between (1 <= int <= 10)\n"
+ " -l maybe lower (int <= 10)\n"
+ " -m less than 10 (int <= 10)\n"
+ " -b binary with re (binary re: m)\n"
+ " -g binary with re (binary re: m)\n"
+ " -t string with re (string re: m)\n"
+ " ---maybe-req maybe required int (int)\n"
+ " -y, --yyy string with re (string re: m)\n"
+ " -u string choices (choice: 1, 2)\n"
+ " -c tough choice (choice: 1, 2, 3)\n"
+ " -q floating choice (choice: 2.10000, 1.20000)\n"
+ " -w atom choice (choice: one, two)\n"
+ " --unsafe unsafe atom (atom)\n"
+ " --safe safe atom (existing atom)\n"
+ " -foobar foobaring option\n"
+ " -r recursive\n"
+ " -f, --force force\n"
+ " -v verbosity level\n"
+ " -i interval set (int >= 1)\n"
+ " --req required optional, right?\n"
+ " --float floating-point long form argument (float, 3.14)\n",
+ ?assertEqual(Usage, unicode:characters_to_list(argparse:help(Cmd,
+ #{progname => "erl", command => ["start"]}))),
+ FullCmd = "Usage:\n erl"
+ " <command> [-rfv] [--force] [-i <interval>] [--req <weird>] [--float <float>]\n\n"
+ "Subcommands:\n"
+ " start verifies configuration and starts server\n"
+ " status prints server status\n"
+ " stop stops running server\n\n"
+ "Optional arguments:\n"
+ " -r recursive\n"
+ " -f, --force force\n"
+ " -v verbosity level\n"
+ " -i interval set (int >= 1)\n"
+ " --req required optional, right?\n"
+ " --float floating-point long form argument (float, 3.14)\n",
+ ?assertEqual(FullCmd, unicode:characters_to_list(argparse:help(Cmd,
+ #{progname => erl}))),
+ CrawlerStatus = "Usage:\n erl status crawler [-rfv] [---extra <extra>] [--force] [-i <interval>]\n"
+ " [--req <weird>] [--float <float>]\n\nOptional arguments:\n"
+ " ---extra extra option very deep\n -r recursive\n"
+ " -f, --force force\n -v verbosity level\n"
+ " -i interval set (int >= 1)\n"
+ " --req required optional, right?\n"
+ " --float floating-point long form argument (float, 3.14)\n",
+ ?assertEqual(CrawlerStatus, unicode:characters_to_list(argparse:help(Cmd,
+ #{progname => "erl", command => ["status", "crawler"]}))),
+ ok.
+
+usage_required_args() ->
+ [{doc, "Verify that required args are printed as required in usage"}].
+
+usage_required_args(Config) when is_list(Config) ->
+ Cmd = #{commands => #{"test" => #{arguments => [#{name => required, required => true, long => "-req"}]}}},
+ Expected = "Usage:\n " ++ prog() ++ " test --req <required>\n\nOptional arguments:\n --req required\n",
+ ?assertEqual(Expected, unicode:characters_to_list(argparse:help(Cmd, #{command => ["test"]}))).
+
+usage_template() ->
+ [{doc, "Tests templates in help/usage"}].
+
+usage_template(Config) when is_list(Config) ->
+ %% Argument (positional)
+ Cmd = #{arguments => [#{
+ name => shard,
+ type => integer,
+ default => 0,
+ help => {"[-s SHARD]", ["initial number, ", type, <<" with a default value of ">>, default]}}
+ ]},
+ ?assertEqual("Usage:\n " ++ prog() ++ " [-s SHARD]\n\nArguments:\n shard initial number, int with a default value of 0\n",
+ unicode:characters_to_list(argparse:help(Cmd, #{}))),
+ %% Optional
+ Cmd1 = #{arguments => [#{
+ name => shard,
+ short => $s,
+ type => integer,
+ default => 0,
+ help => {<<"[-s SHARD]">>, ["initial number"]}}
+ ]},
+ ?assertEqual("Usage:\n " ++ prog() ++ " [-s SHARD]\n\nOptional arguments:\n -s initial number\n",
+ unicode:characters_to_list(argparse:help(Cmd1, #{}))),
+ %% ISO Date example
+ DefaultRange = {{2020, 1, 1}, {2020, 6, 22}},
+ CmdISO = #{
+ arguments => [
+ #{
+ name => range,
+ long => "-range",
+ short => $r,
+ help => {"[--range RNG]", fun() ->
+ {{FY, FM, FD}, {TY, TM, TD}} = DefaultRange,
+ lists:flatten(io_lib:format("date range, ~b-~b-~b..~b-~b-~b", [FY, FM, FD, TY, TM, TD]))
+ end},
+ type => {custom, fun(S) -> [S, DefaultRange] end},
+ default => DefaultRange
+ }
+ ]
+ },
+ ?assertEqual("Usage:\n " ++ prog() ++ " [--range RNG]\n\nOptional arguments:\n -r, --range date range, 2020-1-1..2020-6-22\n",
+ unicode:characters_to_list(argparse:help(CmdISO, #{}))),
+ ok.
+
+parser_error_usage() ->
+ [{doc, "Tests that parser errors have corresponding usage text"}].
+
+parser_error_usage(Config) when is_list(Config) ->
+ %% unknown arguments
+ Prog = prog(),
+ ?assertEqual(Prog ++ ": unknown argument: arg", parser_error(["arg"], #{})),
+ ?assertEqual(Prog ++ ": unknown argument: -a", parser_error(["-a"], #{})),
+ %% missing argument
+ ?assertEqual(Prog ++ ": required argument missing: need", parser_error([""],
+ #{arguments => [#{name => need}]})),
+ ?assertEqual(Prog ++ ": required argument missing: need", parser_error([""],
+ #{arguments => [#{name => need, short => $n, required => true}]})),
+ %% invalid value
+ ?assertEqual(Prog ++ ": invalid argument for need: foo is not an integer", parser_error(["foo"],
+ #{arguments => [#{name => need, type => integer}]})),
+ ?assertEqual(Prog ++ ": invalid argument for need: cAnNotExIsT is not an existing atom", parser_error(["cAnNotExIsT"],
+ #{arguments => [#{name => need, type => atom}]})).
+
+command_usage() ->
+ [{doc, "Test command help template"}].
+
+command_usage(Config) when is_list(Config) ->
+ Cmd = #{arguments => [
+ #{name => arg, help => "argument help"}, #{name => opt, short => $o, help => "option help"}],
+ help => ["Options:\n", options, arguments, <<"NOTAUSAGE">>, usage, "\n"]
+ },
+ ?assertEqual("Options:\n -o option help\n arg argument help\nNOTAUSAGE " ++ prog() ++ " [-o <opt>] <arg>\n",
+ unicode:characters_to_list(argparse:help(Cmd, #{}))).
+
+usage_width() ->
+ [{doc, "Test usage fitting in the viewport"}].
+
+usage_width(Config) when is_list(Config) ->
+ Cmd = #{arguments => [
+ #{name => arg, help => "argument help that spans way over allowed viewport width, wrapping words"},
+ #{name => opt, short => $o, long => "-option_long_name",
+ help => "another quite long word wrapped thing spanning over several lines"},
+ #{name => v, short => $v, type => boolean},
+ #{name => q, short => $q, type => boolean}],
+ commands => #{
+ "cmd1" => #{help => "Help for command number 1, not fitting at all"},
+ "cmd2" => #{help => <<"Short help">>},
+ "cmd3" => #{help => "Yet another instance of a very long help message"}
+ },
+ help => " Very long help line taking much more than 40 characters allowed by the test case.
+Also containing a few newlines.
+
+ Indented new lines must be honoured!"
+ },
+
+ Expected = "Usage:\n erl {cmd1|cmd2|cmd3} [-vq] [-o <opt>]\n"
+ " [--option_long_name <opt>] <arg>\n\n"
+ " Very long help line taking much more\n"
+ "than 40 characters allowed by the test\n"
+ "case.\n"
+ "Also containing a few newlines.\n\n"
+ " Indented new lines must be honoured!\n\n"
+ "Subcommands:\n"
+ " cmd1 Help for\n"
+ " command number\n"
+ " 1, not fitting\n"
+ " at all\n"
+ " cmd2 Short help\n"
+ " cmd3 Yet another\n"
+ " instance of a\n"
+ " very long help\n"
+ " message\n\n"
+ "Arguments:\n"
+ " arg argument help\n"
+ " that spans way\n"
+ " over allowed\n"
+ " viewport width,\n"
+ " wrapping words\n\n"
+ "Optional arguments:\n"
+ " -o, --option_long_name another quite\n"
+ " long word\n"
+ " wrapped thing\n"
+ " spanning over\n"
+ " several lines\n"
+ " -v v\n"
+ " -q q\n",
+
+ ?assertEqual(Expected, unicode:characters_to_list(argparse:help(Cmd, #{columns => 40, progname => "erl"}))).
+
+%%--------------------------------------------------------------------
+%% Validator Test Cases
+
+validator_exception() ->
+ [{doc, "Tests that the validator throws expected exceptions"}].
+
+validator_exception(Config) when is_list(Config) ->
+ Prg = [prog()],
+ %% conflicting option names
+ ?assertException(error, {argparse, argument, Prg, short, "short conflicting with previously defined short for one"},
+ argparse:validate(#{arguments => [#{name => one, short => $$}, #{name => two, short => $$}]})),
+ ?assertException(error, {argparse, argument, Prg, long, "long conflicting with previously defined long for one"},
+ argparse:validate(#{arguments => [#{name => one, long => "a"}, #{name => two, long => "a"}]})),
+ %% broken options
+ %% long must be a string
+ ?assertException(error, {argparse, argument, Prg, long, _},
+ argparse:validate(#{arguments => [#{name => one, long => ok}]})),
+ %% short must be a printable character
+ ?assertException(error, {argparse, argument, Prg, short, _},
+ argparse:validate(#{arguments => [#{name => one, short => ok}]})),
+ ?assertException(error, {argparse, argument, Prg, short, _},
+ argparse:validate(#{arguments => [#{name => one, short => 7}]})),
+ %% required is a boolean
+ ?assertException(error, {argparse, argument, Prg, required, _},
+ argparse:validate(#{arguments => [#{name => one, required => ok}]})),
+ ?assertException(error, {argparse, argument, Prg, help, _},
+ argparse:validate(#{arguments => [#{name => one, help => ok}]})),
+ %% broken commands
+ try argparse:help(#{}, #{progname => 123}), ?assert(false)
+ catch error:badarg:Stack ->
+ [{_, _, _, Ext} | _] = Stack,
+ #{cause := #{2 := Detail}} = proplists:get_value(error_info, Ext),
+ ?assertEqual(<<"progname is not valid">>, Detail)
+ end,
+ %% not-a-list of arguments provided to a subcommand
+ Prog = prog(),
+ ?assertException(error, {argparse, command, [Prog, "start"], arguments, <<"expected a list, [argument()]">>},
+ argparse:validate(#{commands => #{"start" => #{arguments => atom}}})),
+ %% command is not a map
+ ?assertException(error, {argparse, command, Prg, commands, <<"expected map of #{string() => command()}">>},
+ argparse:validate(#{commands => []})),
+ %% invalid commands field
+ ?assertException(error, {argparse, command, Prg, commands, _},
+ argparse:validate(#{commands => ok})),
+ ?assertException(error, {argparse, command, _, commands, _},
+ argparse:validate(#{commands => #{ok => #{}}})),
+ ?assertException(error, {argparse, command, _, help,
+ <<"must be a printable unicode list, or a command help template">>},
+ argparse:validate(#{commands => #{"ok" => #{help => ok}}})),
+ ?assertException(error, {argparse, command, _, handler, _},
+ argparse:validate(#{commands => #{"ok" => #{handler => fun validator_exception/0}}})),
+ %% extend + maybe: validator exception
+ ?assertException(error, {argparse, argument, _, action, <<"extend action works only with lists">>},
+ parse("-1 -1", #{arguments =>
+ [#{action => extend, name => short49, nargs => 'maybe', short => 49}]})).
+
+validator_exception_format() ->
+ [{doc, "Tests human-readable (EEP-54) format for exceptions thrown by the validator"}].
+
+validator_exception_format(Config) when is_list(Config) ->
+ %% set up as a contract: test that EEP-54 transformation is done (but don't check strings)
+ try
+ argparse:validate(#{commands => #{"one" => #{commands => #{"two" => atom}}}}),
+ ?assert(false)
+ catch
+ error:R1:S1 ->
+ #{1 := Cmd, reason := RR1, general := G} = argparse:format_error(R1, S1),
+ ?assertEqual("command specification is invalid", unicode:characters_to_list(G)),
+ ?assertEqual("command \"" ++ prog() ++ " one two\": invalid field 'commands', reason: expected command()",
+ unicode:characters_to_list(RR1)),
+ ?assertEqual(["atom"], Cmd)
+ end,
+ %% check argument
+ try
+ argparse:validate(#{arguments => [#{}]}),
+ ?assert(false)
+ catch
+ error:R2:S2 ->
+ #{1 := Cmd2, reason := RR2, general := G2} = argparse:format_error(R2, S2),
+ ?assertEqual("argument specification is invalid", unicode:characters_to_list(G2)),
+ ?assertEqual("command \"" ++ prog() ++
+ "\", argument '', invalid field 'name': argument must be a map containing 'name' field",
+ unicode:characters_to_list(RR2)),
+ ?assertEqual(["#{}"], Cmd2)
+ end.
+
+%%--------------------------------------------------------------------
+%% Validator Test Cases
+
+run_handle() ->
+ [{doc, "Very basic tests for argparse:run/3, choice of handlers formats"}].
+
+%% fun((arg_map()) -> term()) | %% handler accepting arg_map
+%% {module(), Fn :: atom()} | %% handler, accepting arg_map, Fn exported from module()
+%% {fun(() -> term()), term()} | %% handler, positional form (term() is supplied for omitted args)
+%% {module(), atom(), term()}
+
+run_handle(Config) when is_list(Config) ->
+ %% no subcommand, basic fun handler with argmap
+ ?assertEqual(6,
+ argparse:run(["-i", "3"], #{handler => fun (#{in := Val}) -> Val * 2 end,
+ arguments => [#{name => in, short => $i, type => integer}]}, #{})),
+ %% subcommand, positional fun() handler
+ ?assertEqual(6,
+ argparse:run(["mul", "2", "3"], #{commands => #{"mul" => #{
+ handler => {fun (match, L, R) -> L * R end, match},
+ arguments => [#{name => opt, short => $o},
+ #{name => l, type => integer}, #{name => r, type => integer}]}}},
+ #{})),
+ %% no subcommand, positional module-based function
+ ?assertEqual(6,
+ argparse:run(["2", "3"], #{handler => {erlang, '*', undefined},
+ arguments => [#{name => l, type => integer}, #{name => r, type => integer}]},
+ #{})),
+ %% subcommand, module-based function accepting argmap
+ ?assertEqual([{arg, "arg"}],
+ argparse:run(["map", "arg"], #{commands => #{"map" => #{
+ handler => {maps, to_list},
+ arguments => [#{name => arg}]}}},
+ #{})). \ No newline at end of file
diff --git a/lib/stdlib/test/base64_SUITE.erl b/lib/stdlib/test/base64_SUITE.erl
index 1fc4c3fc0e..0eae2d9f72 100644
--- a/lib/stdlib/test/base64_SUITE.erl
+++ b/lib/stdlib/test/base64_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,9 +26,11 @@
-export([all/0, suite/0, groups/0, group/1]).
%% Test cases must be exported.
--export([base64_encode/1, base64_decode/1, base64_otp_5635/1,
- base64_otp_6279/1, big/1, illegal/1, mime_decode/1,
- mime_decode_to_string/1,
+-export([base64_encode/1, base64_encode_to_string/1, base64_encode_modes/1,
+ base64_decode/1, base64_decode_to_string/1, base64_decode_modes/1,
+ base64_otp_5635/1, base64_otp_6279/1, big/1, illegal/1,
+ mime_decode/1, mime_decode_modes/1,
+ mime_decode_to_string/1, mime_decode_to_string_modes/1,
roundtrip_1/1, roundtrip_2/1, roundtrip_3/1, roundtrip_4/1]).
%%-------------------------------------------------------------------------
@@ -40,8 +42,11 @@ suite() ->
{timetrap,{minutes,4}}].
all() ->
- [base64_encode, base64_decode, base64_otp_5635,
- base64_otp_6279, big, illegal, mime_decode, mime_decode_to_string,
+ [base64_encode, base64_encode_to_string, base64_encode_modes,
+ base64_decode, base64_decode_to_string, base64_decode_modes,
+ base64_otp_5635, base64_otp_6279, big, illegal,
+ mime_decode, mime_decode_modes,
+ mime_decode_to_string, mime_decode_to_string_modes,
{group, roundtrip}].
groups() ->
@@ -53,44 +58,176 @@ group(roundtrip) ->
[{timetrap,{minutes,10}}].
%%-------------------------------------------------------------------------
-%% Test base64:encode/1.
+%% Test base64:encode/1,2.
base64_encode(Config) when is_list(Config) ->
%% Two pads
<<"QWxhZGRpbjpvcGVuIHNlc2FtZQ==">> =
base64:encode("Aladdin:open sesame"),
+ <<"QWxhZGRpbjpvcGVuIHNlc2FtZQ">> =
+ base64:encode("Aladdin:open sesame", #{padding => false}),
%% One pad
<<"SGVsbG8gV29ybGQ=">> = base64:encode(<<"Hello World">>),
+ <<"SGVsbG8gV29ybGQ">> = base64:encode(<<"Hello World">>, #{padding => false}),
+ %% No pad
+ <<"QWxhZGRpbjpvcGVuIHNlc2Ft">> = base64:encode("Aladdin:open sesam"),
+ <<"QWxhZGRpbjpvcGVuIHNlc2Ft">> =
+ base64:encode("Aladdin:open sesam", #{padding => false}),
+
+ <<"MDEyMzQ1Njc4OSFAIzBeJiooKTs6PD4sLiBbXXt9">> =
+ base64:encode(<<"0123456789!@#0^&*();:<>,. []{}">>),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test base64:encode_to_string/1,2.
+base64_encode_to_string(Config) when is_list(Config) ->
+ %% Two pads
+ "QWxhZGRpbjpvcGVuIHNlc2FtZQ==" =
+ base64:encode_to_string("Aladdin:open sesame"),
+ "QWxhZGRpbjpvcGVuIHNlc2FtZQ" =
+ base64:encode_to_string("Aladdin:open sesame", #{padding => false}),
+ %% One pad
+ "SGVsbG8gV29ybGQ=" = base64:encode_to_string(<<"Hello World">>),
+ "SGVsbG8gV29ybGQ" =
+ base64:encode_to_string(<<"Hello World">>, #{padding => false}),
%% No pad
"QWxhZGRpbjpvcGVuIHNlc2Ft" =
base64:encode_to_string("Aladdin:open sesam"),
+ "QWxhZGRpbjpvcGVuIHNlc2Ft" =
+ base64:encode_to_string("Aladdin:open sesam", #{padding => false}),
"MDEyMzQ1Njc4OSFAIzBeJiooKTs6PD4sLiBbXXt9" =
base64:encode_to_string(<<"0123456789!@#0^&*();:<>,. []{}">>),
ok.
+
+%%-------------------------------------------------------------------------
+%% Test base64:encode/2 and base64:encode_to_string/2.
+base64_encode_modes(Config) when is_list(Config) ->
+ Data = <<23, 234, 63, 163, 239, 129, 253, 175, 171>>,
+
+ <<"F+o/o++B/a+r">> = base64:encode(Data, #{mode => standard}),
+ <<"F-o_o--B_a-r">> = base64:encode(Data, #{mode => urlsafe}),
+
+ "F+o/o++B/a+r" = base64:encode_to_string(Data, #{mode => standard}),
+ "F-o_o--B_a-r" = base64:encode_to_string(Data, #{mode => urlsafe}),
+
+ ok.
+
%%-------------------------------------------------------------------------
-%% Test base64:decode/1.
+%% Test base64:decode/1,2.
base64_decode(Config) when is_list(Config) ->
%% Two pads
<<"Aladdin:open sesame">> =
- base64:decode("QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
+ base64:decode(<<"QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>),
+ <<"Aladdin:open sesame">> =
+ base64:decode("QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
+ <<"Aladdin:open sesame">> =
+ base64:decode(<<"QWxhZGRpbjpvcGVuIHNlc2FtZQ">>, #{padding => false}),
+ <<"Aladdin:open sesame">> =
+ base64:decode("QWxhZGRpbjpvcGVuIHNlc2FtZQ", #{padding => false}),
+ {'EXIT', {'missing_padding', _}} =
+ catch base64:decode(<<"QWxhZGRpbjpvcGVuIHNlc2FtZQ">>),
+ {'EXIT', {'missing_padding', _}} =
+ catch base64:decode("QWxhZGRpbjpvcGVuIHNlc2FtZQ"),
%% One pad
<<"Hello World">> = base64:decode(<<"SGVsbG8gV29ybGQ=">>),
+ <<"Hello World">> = base64:decode("SGVsbG8gV29ybGQ="),
+ <<"Hello World">> = base64:decode(<<"SGVsbG8gV29ybGQ">>, #{padding => false}),
+ <<"Hello World">> = base64:decode("SGVsbG8gV29ybGQ", #{padding => false}),
+ {'EXIT', {'missing_padding', _}} =
+ catch base64:decode_to_string(<<"SGVsbG8gV29ybGQ">>),
+ {'EXIT', {'missing_padding', _}} =
+ catch base64:decode_to_string("SGVsbG8gV29ybGQ"),
%% No pad
<<"Aladdin:open sesam">> =
- base64:decode("QWxhZGRpbjpvcGVuIHNlc2Ft"),
+ base64:decode(<<"QWxhZGRpbjpvcGVuIHNlc2Ft">>),
+ <<"Aladdin:open sesam">> =
+ base64:decode("QWxhZGRpbjpvcGVuIHNlc2Ft"),
+ <<"Aladdin:open sesam">> =
+ base64:decode(<<"QWxhZGRpbjpvcGVuIHNlc2Ft">>, #{padding => false}),
+ <<"Aladdin:open sesam">> =
+ base64:decode("QWxhZGRpbjpvcGVuIHNlc2Ft", #{padding => false}),
Alphabet = list_to_binary(lists:seq(0, 255)),
Alphabet = base64:decode(base64:encode(Alphabet)),
+ Alphabet = base64:decode(base64:encode_to_string(Alphabet)),
+
+ %% Encoded base 64 strings may be divided by non base 64 chars.
+ %% In this cases whitespaces.
+ <<"0123456789!@#0^&*();:<>,. []{}">> =
+ base64:decode(
+ "MDEy MzQ1Njc4 \tOSFAIzBeJ \niooKTs6 PD4sLi \r\nBbXXt9"),
+ <<"0123456789!@#0^&*();:<>,. []{}">> =
+ base64:decode(
+ <<"MDEy MzQ1Njc4 \tOSFAIzBeJ \niooKTs6 PD4sLi \r\nBbXXt9">>),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test base64:decode_to_string/1,2.
+base64_decode_to_string(Config) when is_list(Config) ->
+ %% Two pads
+ "Aladdin:open sesame" =
+ base64:decode_to_string(<<"QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>),
+ "Aladdin:open sesame" =
+ base64:decode_to_string("QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
+ "Aladdin:open sesame" =
+ base64:decode_to_string(<<"QWxhZGRpbjpvcGVuIHNlc2FtZQ">>, #{padding => false}),
+ "Aladdin:open sesame" =
+ base64:decode_to_string("QWxhZGRpbjpvcGVuIHNlc2FtZQ", #{padding => false}),
+ {'EXIT', {'missing_padding', _}} =
+ catch base64:decode_to_string(<<"QWxhZGRpbjpvcGVuIHNlc2FtZQ">>),
+ {'EXIT', {'missing_padding', _}} =
+ catch base64:decode_to_string("QWxhZGRpbjpvcGVuIHNlc2FtZQ"),
+ %% One pad
+ "Hello World" = base64:decode_to_string(<<"SGVsbG8gV29ybGQ=">>),
+ "Hello World" = base64:decode_to_string("SGVsbG8gV29ybGQ="),
+ "Hello World" = base64:decode_to_string(<<"SGVsbG8gV29ybGQ">>, #{padding => false}),
+ "Hello World" = base64:decode_to_string("SGVsbG8gV29ybGQ", #{padding => false}),
+ {'EXIT', {'missing_padding', _}} =
+ catch base64:decode_to_string(<<"SGVsbG8gV29ybGQ">>),
+ {'EXIT', {'missing_padding', _}} =
+ catch base64:decode_to_string("SGVsbG8gV29ybGQ"),
+ %% No pad
+ "Aladdin:open sesam" =
+ base64:decode_to_string(<<"QWxhZGRpbjpvcGVuIHNlc2Ft">>),
+ "Aladdin:open sesam" =
+ base64:decode_to_string("QWxhZGRpbjpvcGVuIHNlc2Ft"),
+ "Aladdin:open sesam" =
+ base64:decode_to_string(<<"QWxhZGRpbjpvcGVuIHNlc2Ft">>, #{padding => false}),
+ "Aladdin:open sesam" =
+ base64:decode_to_string("QWxhZGRpbjpvcGVuIHNlc2Ft", #{padding => false}),
+
+ Alphabet = lists:seq(0, 255),
+ Alphabet = base64:decode_to_string(base64:encode(Alphabet)),
+ Alphabet = base64:decode_to_string(base64:encode_to_string(Alphabet)),
%% Encoded base 64 strings may be divided by non base 64 chars.
%% In this cases whitespaces.
"0123456789!@#0^&*();:<>,. []{}" =
- base64:decode_to_string(
- "MDEy MzQ1Njc4 \tOSFAIzBeJ \niooKTs6 PD4sLi \r\nBbXXt9"),
+ base64:decode_to_string(
+ "MDEy MzQ1Njc4 \tOSFAIzBeJ \niooKTs6 PD4sLi \r\nBbXXt9"),
"0123456789!@#0^&*();:<>,. []{}" =
- base64:decode_to_string(
- <<"MDEy MzQ1Njc4 \tOSFAIzBeJ \niooKTs6 PD4sLi \r\nBbXXt9">>),
+ base64:decode_to_string(
+ <<"MDEy MzQ1Njc4 \tOSFAIzBeJ \niooKTs6 PD4sLi \r\nBbXXt9">>),
ok.
+
+%%-------------------------------------------------------------------------
+%% Test base64:decode/2 and base64:decode_to_string/2
+base64_decode_modes(Config) when is_list(Config) ->
+ DataBin = <<23, 234, 63, 163, 239, 129, 253, 175, 171>>,
+ DataStr = [23, 234, 63, 163, 239, 129, 253, 175, 171],
+
+ DataBin = base64:decode("F+o/o++B/a+r", #{mode => standard}),
+ DataBin = base64:decode("F-o_o--B_a-r", #{mode => urlsafe}),
+ {'EXIT', _} = catch base64:decode("F-o_o--B_a-r", #{mode => standard}),
+ {'EXIT', _} = catch base64:decode("F+o/o++B/a+r", #{mode => urlsafe}),
+
+ DataStr = base64:decode_to_string("F+o/o++B/a+r", #{mode => standard}),
+ DataStr = base64:decode_to_string("F-o_o--B_a-r", #{mode => urlsafe}),
+ {'EXIT', _} = catch base64:decode_to_string("F-o_o--B_a-r", #{mode => standard}),
+ {'EXIT', _} = catch base64:decode_to_string("F+o/o++B/a+r", #{mode => urlsafe}),
+
+ ok.
+
%%-------------------------------------------------------------------------
%% OTP-5635: Some data doesn't pass through base64:decode/1 correctly.
base64_otp_5635(Config) when is_list(Config) ->
@@ -171,6 +308,31 @@ mime_decode(Config) when is_list(Config) ->
<<"o">> = MimeDecode(<<"bw=\000=">>),
ok.
+%% Test base64:mime_decode/2.
+mime_decode_modes(Config) when is_list(Config) ->
+ MimeDecode = fun (In, Options) ->
+ Out = base64:mime_decode(In, Options),
+ Out = base64:mime_decode(binary_to_list(In), Options)
+ end,
+
+ %% The following all decode to the same data.
+ Data = <<23, 234, 63, 163, 239, 129, 253, 175, 171>>,
+ Data = MimeDecode(<<"F+o/o++B/a+r">>, #{mode => standard}),
+ Data = MimeDecode(<<"F-o_o--B_a-r">>, #{mode => urlsafe}),
+
+ %% The following decodes to different data depending on mode.
+ Base64 = <<"AB+C+D/E/FG-H-I_J_KL">>,
+ %% In standard mode, "-" and "_" are invalid and thus ignored.
+ %% The base64 string to be decoded is equivalent to "AB+C+D/E/FGHIJKL".
+ <<0, 31, 130, 248, 63, 196, 252, 81, 135, 32, 146, 139>> =
+ MimeDecode(Base64, #{mode => standard}),
+ %% In urlsafe mode, "+" and "/" are invalid and thus ignored.
+ %% The base64 string to be decoded is equivalent to "ABCDEFG-H-I_J_KL".
+ <<0, 16, 131, 16, 81, 190, 31, 226, 63, 39, 242, 139>> =
+ MimeDecode(Base64, #{mode => urlsafe}),
+
+ ok.
+
%%-------------------------------------------------------------------------
%% Repeat of mime_decode() tests
@@ -221,6 +383,32 @@ mime_decode_to_string(Config) when is_list(Config) ->
"o" = MimeDecodeToString(<<"bw=\000=">>),
ok.
+
+%% Test base64:mime_decode_to_string/2.
+mime_decode_to_string_modes(Config) when is_list(Config) ->
+ MimeDecode = fun (In, Options) ->
+ Out = base64:mime_decode_to_string(In, Options),
+ Out = base64:mime_decode_to_string(binary_to_list(In), Options)
+ end,
+
+ %% The following all decode to the same data.
+ Data = [23, 234, 63, 163, 239, 129, 253, 175, 171],
+ Data = MimeDecode(<<"F+o/o++B/a+r">>, #{mode => standard}),
+ Data = MimeDecode(<<"F-o_o--B_a-r">>, #{mode => urlsafe}),
+
+ %% The following decodes to different data depending on mode.
+ Base64 = <<"AB+C+D/E/FG-H-I_J_KL">>,
+ %% In standard mode, "-" and "_" are invalid and thus ignored.
+ %% The base64 string to be decoded is equivalent to "AB+C+D/E/FGHIJKL".
+ [0, 31, 130, 248, 63, 196, 252, 81, 135, 32, 146, 139] =
+ MimeDecode(Base64, #{mode => standard}),
+ %% In urlsafe mode, "+" and "/" are invalid and thus ignored.
+ %% The base64 string to be decoded is equivalent to "ABCDEFG-H-I_J_KL".
+ [0, 16, 131, 16, 81, 190, 31, 226, 63, 39, 242, 139] =
+ MimeDecode(Base64, #{mode => urlsafe}),
+
+ ok.
+
%%-------------------------------------------------------------------------
roundtrip_1(Config) when is_list(Config) ->
diff --git a/lib/stdlib/test/base64_property_test_SUITE.erl b/lib/stdlib/test/base64_property_test_SUITE.erl
index 7717b62767..68ac5e9ee7 100644
--- a/lib/stdlib/test/base64_property_test_SUITE.erl
+++ b/lib/stdlib/test/base64_property_test_SUITE.erl
@@ -24,18 +24,18 @@
all() ->
[
- encode_case,
- encode_to_string_case,
- decode_case,
- decode_malformed_case,
- decode_noisy_case,
- decode_to_string_case,
- decode_to_string_malformed_case,
- decode_to_string_noisy_case,
- mime_decode_case,
- mime_decode_malformed_case,
- mime_decode_to_string_case,
- mime_decode_to_string_malformed_case
+ encode_1_case, encode_2_case,
+ encode_to_string_1_case, encode_to_string_2_case,
+ decode_1_case, decode_2_case,
+ decode_1_malformed_case, decode_2_malformed_case,
+ decode_1_noisy_case, decode_2_noisy_case,
+ decode_to_string_1_case, decode_to_string_2_case,
+ decode_to_string_1_malformed_case, decode_to_string_2_malformed_case,
+ decode_to_string_1_noisy_case, decode_to_string_2_noisy_case,
+ mime_decode_1_case, mime_decode_2_case,
+ mime_decode_1_malformed_case, mime_decode_2_malformed_case,
+ mime_decode_to_string_1_case, mime_decode_to_string_2_case,
+ mime_decode_to_string_1_malformed_case, mime_decode_to_string_2_malformed_case
].
init_per_suite(Config) ->
@@ -44,41 +44,77 @@ init_per_suite(Config) ->
end_per_suite(Config) ->
Config.
-encode_case(Config) ->
- do_proptest(prop_encode, Config).
+encode_1_case(Config) ->
+ do_proptest(prop_encode_1, Config).
-encode_to_string_case(Config) ->
- do_proptest(prop_encode_to_string, Config).
+encode_2_case(Config) ->
+ do_proptest(prop_encode_2, Config).
-decode_case(Config) ->
- do_proptest(prop_decode, Config).
+encode_to_string_1_case(Config) ->
+ do_proptest(prop_encode_to_string_1, Config).
-decode_malformed_case(Config) ->
- do_proptest(prop_decode_malformed, Config).
+encode_to_string_2_case(Config) ->
+ do_proptest(prop_encode_to_string_2, Config).
-decode_noisy_case(Config) ->
- do_proptest(prop_decode_noisy, Config).
+decode_1_case(Config) ->
+ do_proptest(prop_decode_1, Config).
-decode_to_string_case(Config) ->
- do_proptest(prop_decode_to_string, Config).
+decode_2_case(Config) ->
+ do_proptest(prop_decode_2, Config).
-decode_to_string_malformed_case(Config) ->
- do_proptest(prop_decode_to_string_malformed, Config).
+decode_1_malformed_case(Config) ->
+ do_proptest(prop_decode_1_malformed, Config).
-decode_to_string_noisy_case(Config) ->
- do_proptest(prop_decode_to_string_noisy, Config).
+decode_2_malformed_case(Config) ->
+ do_proptest(prop_decode_2_malformed, Config).
-mime_decode_case(Config) ->
- do_proptest(prop_mime_decode, Config).
+decode_1_noisy_case(Config) ->
+ do_proptest(prop_decode_1_noisy, Config).
-mime_decode_malformed_case(Config) ->
- do_proptest(prop_mime_decode_malformed, Config).
+decode_2_noisy_case(Config) ->
+ do_proptest(prop_decode_2_noisy, Config).
-mime_decode_to_string_case(Config) ->
- do_proptest(prop_mime_decode_to_string, Config).
+decode_to_string_1_case(Config) ->
+ do_proptest(prop_decode_to_string_1, Config).
-mime_decode_to_string_malformed_case(Config) ->
- do_proptest(prop_mime_decode_to_string_malformed, Config).
+decode_to_string_2_case(Config) ->
+ do_proptest(prop_decode_to_string_2, Config).
+
+decode_to_string_1_malformed_case(Config) ->
+ do_proptest(prop_decode_to_string_1_malformed, Config).
+
+decode_to_string_2_malformed_case(Config) ->
+ do_proptest(prop_decode_to_string_2_malformed, Config).
+
+decode_to_string_1_noisy_case(Config) ->
+ do_proptest(prop_decode_to_string_1_noisy, Config).
+
+decode_to_string_2_noisy_case(Config) ->
+ do_proptest(prop_decode_to_string_2_noisy, Config).
+
+mime_decode_1_case(Config) ->
+ do_proptest(prop_mime_decode_1, Config).
+
+mime_decode_2_case(Config) ->
+ do_proptest(prop_mime_decode_2, Config).
+
+mime_decode_1_malformed_case(Config) ->
+ do_proptest(prop_mime_decode_1_malformed, Config).
+
+mime_decode_2_malformed_case(Config) ->
+ do_proptest(prop_mime_decode_2_malformed, Config).
+
+mime_decode_to_string_1_case(Config) ->
+ do_proptest(prop_mime_decode_to_string_1, Config).
+
+mime_decode_to_string_2_case(Config) ->
+ do_proptest(prop_mime_decode_to_string_2, Config).
+
+mime_decode_to_string_1_malformed_case(Config) ->
+ do_proptest(prop_mime_decode_to_string_1_malformed, Config).
+
+mime_decode_to_string_2_malformed_case(Config) ->
+ do_proptest(prop_mime_decode_to_string_2_malformed, Config).
do_proptest(Prop, Config) ->
ct_property_test:quickcheck(
diff --git a/lib/stdlib/test/binary_module_SUITE.erl b/lib/stdlib/test/binary_module_SUITE.erl
index 99f3566eab..954efae9b7 100644
--- a/lib/stdlib/test/binary_module_SUITE.erl
+++ b/lib/stdlib/test/binary_module_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1462,10 +1462,15 @@ error_info(_Config) ->
{split, [<<1,2,3>>, {bm,make_ref()}, []]},
{split, [<<1,2,3>>, <<"2">>, [bad_option]]},
- {encode_hex, [{no,a,binary}]},
- {decode_hex, [{no,a,binary}]},
- {decode_hex, [<<"000">>],[allow_rename]},
- {decode_hex, [<<"GG">>],[allow_rename]}
+ {encode_hex, [{no,binary}]},
+ {encode_hex, [{no,binary}, lowercase]},
+ {encode_hex, [<<"foobar">>, othercase]},
+ {encode_hex, [no_binary, othercase], [{1,".*"}, {2,".*"}]},
+
+ {decode_hex, [{no,binary}]},
+ {decode_hex, [<<"000">>]},
+ {decode_hex, [<<"GG">>]},
+ {decode_hex, [<<255>>]}
],
error_info_lib:test_error_info(binary, L).
@@ -1479,6 +1484,23 @@ hex_encoding(Config) when is_list(Config) ->
<<"666F6F6261">> = binary:encode_hex(<<"fooba">>),
<<"666F6F626172">> = binary:encode_hex(<<"foobar">>),
+
+ <<>> = binary:encode_hex(<<>>, uppercase),
+ <<"66">> = binary:encode_hex(<<"f">>, uppercase),
+ <<"666F">> = binary:encode_hex(<<"fo">>, uppercase),
+ <<"666F6F">> = binary:encode_hex(<<"foo">>, uppercase),
+ <<"666F6F62">> = binary:encode_hex(<<"foob">>, uppercase),
+ <<"666F6F6261">> = binary:encode_hex(<<"fooba">>, uppercase),
+ <<"666F6F626172">> = binary:encode_hex(<<"foobar">>, uppercase),
+
+ <<>> = binary:encode_hex(<<>>, lowercase),
+ <<"66">> = binary:encode_hex(<<"f">>, lowercase),
+ <<"666f">> = binary:encode_hex(<<"fo">>, lowercase),
+ <<"666f6f">> = binary:encode_hex(<<"foo">>, lowercase),
+ <<"666f6f62">> = binary:encode_hex(<<"foob">>, lowercase),
+ <<"666f6f6261">> = binary:encode_hex(<<"fooba">>, lowercase),
+ <<"666f6f626172">> = binary:encode_hex(<<"foobar">>, lowercase),
+
<<>> = binary:decode_hex(<<>>),
<<"f">> = binary:decode_hex(<<"66">>),
<<"fo">> = binary:decode_hex(<<"666F">>),
@@ -1494,8 +1516,29 @@ hex_encoding(Config) when is_list(Config) ->
<<"foobar">> = binary:decode_hex(<<"666f6f626172">>),
<<"foobar">> = binary:decode_hex(<<"666f6F626172">>),
+
+ rand:seed(default),
+ io:format("*** SEED: ~p ***\n", [rand:export_seed()]),
+ Bytes = iolist_to_binary([rand:bytes(256), lists:seq(0, 255)]),
+ do_hex_roundtrip(Bytes),
+
ok.
+do_hex_roundtrip(Bytes) ->
+ UpperHex = binary:encode_hex(Bytes),
+ UpperHex = binary:encode_hex(Bytes, uppercase),
+ LowerHex = binary:encode_hex(Bytes, lowercase),
+
+ Bytes = binary:decode_hex(UpperHex),
+ Bytes = binary:decode_hex(LowerHex),
+
+ case Bytes of
+ <<_, Rest/binary>> ->
+ do_hex_roundtrip(Rest);
+ <<>> ->
+ ok
+ end.
+
%%%
%%% Utilities.
%%%
diff --git a/lib/stdlib/test/binary_property_test_SUITE.erl b/lib/stdlib/test/binary_property_test_SUITE.erl
new file mode 100644
index 0000000000..4ec1ba67ab
--- /dev/null
+++ b/lib/stdlib/test/binary_property_test_SUITE.erl
@@ -0,0 +1,35 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(binary_property_test_SUITE).
+
+-compile([export_all, nowarn_export_all]).
+
+all() ->
+ [encode_hex_case].
+
+init_per_suite(Config) ->
+ ct_property_test:init_per_suite(Config).
+
+end_per_suite(Config) ->
+ Config.
+
+encode_hex_case(Config) ->
+ ct_property_test:quickcheck(binary_prop:prop_hex_encode_2(), Config).
+
diff --git a/lib/stdlib/test/dets_SUITE.erl b/lib/stdlib/test/dets_SUITE.erl
index c50a2d5baf..5a19717c28 100644
--- a/lib/stdlib/test/dets_SUITE.erl
+++ b/lib/stdlib/test/dets_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@
otp_5487/1, otp_6206/1, otp_6359/1, otp_4738/1, otp_7146/1,
otp_8070/1, otp_8856/1, otp_8898/1, otp_8899/1, otp_8903/1,
otp_8923/1, otp_9282/1, otp_11245/1, otp_11709/1, otp_13229/1,
- otp_13260/1, otp_13830/1]).
+ otp_13260/1, otp_13830/1, receive_optimisation/1]).
-export([dets_dirty_loop/0]).
@@ -94,7 +94,7 @@ all() ->
insert_new, repair_continuation, otp_5487, otp_6206,
otp_6359, otp_4738, otp_7146, otp_8070, otp_8856, otp_8898,
otp_8899, otp_8903, otp_8923, otp_9282, otp_11245, otp_11709,
- otp_13229, otp_13260, otp_13830
+ otp_13229, otp_13260, otp_13830, receive_optimisation
].
groups() ->
@@ -3492,6 +3492,34 @@ otp_13830(Config) ->
{ok, Tab} = dets:open_file(Tab, [{file, File}, {version, default}]),
ok = dets:close(Tab).
+receive_optimisation(Config) ->
+ Tab = dets_receive_optimisation_test,
+ FName = filename(Tab, Config),
+
+ % Spam message box
+ lists:foreach(fun(_) -> self() ! {spam, it} end, lists:seq(1, 1_000_000)),
+
+ {ok, _} = dets:open_file(Tab,[{file, FName}]),
+ ok = dets:insert(Tab,{one, record}),
+
+ StartTime = os:system_time(millisecond),
+
+ % We expect one thousand of simple lookups to finish in one second
+ Lookups = 1000,
+ Timeout = 1000,
+ Loop = fun Loop(N) when N =< 0 -> ok;
+ Loop(N) ->
+ Now = os:system_time(millisecond),
+ (Now - StartTime > Timeout) andalso throw({timeout_after, Lookups - N}),
+ [{one, record}] = dets:lookup(Tab, one),
+ Loop(N-1)
+ end,
+
+ ok = Loop(Lookups),
+
+ ok = dets:close(Tab),
+ ok = file:delete(FName).
+
%%
%% Parts common to several test cases
%%
diff --git a/lib/stdlib/test/edlin_context_SUITE.erl b/lib/stdlib/test/edlin_context_SUITE.erl
new file mode 100644
index 0000000000..5e84a90199
--- /dev/null
+++ b/lib/stdlib/test/edlin_context_SUITE.erl
@@ -0,0 +1,189 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(edlin_context_SUITE).
+-include_lib("common_test/include/ct.hrl").
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1]).
+
+-export([get_context/1]).
+
+suite() ->
+ [{timetrap,{minutes,1}}].
+all() ->
+ [get_context].
+groups() ->
+ [].
+init_per_suite(Config) ->
+ Config.
+end_per_suite(_Config) ->
+ ok.
+
+get_context(_Config) ->
+ {term, [], {atom, "h"}} = edlin_context:get_context(lists:reverse("h")),
+ {term} = edlin_context:get_context(lists:reverse("h(file")),
+ {term} = edlin_context:get_context(lists:reverse("h(file,open")),
+ {term, [{call, "h(file,open)"}], {atom, "h"}} = edlin_context:get_context(lists:reverse("h(file,open), h")),
+ {term} = edlin_context:get_context(lists:reverse("h(file,open), h(file")),
+ {term} = edlin_context:get_context(lists:reverse("h(file,open), h(file,open")),
+ {term, [{call, "h(file,open)"}], {call, "h(file,open)"}} = edlin_context:get_context(lists:reverse("h(file,open), h(file,open)")),
+ {function} = edlin_context:get_context(lists:reverse("file:")),
+ {function} = edlin_context:get_context(lists:reverse("file:open")),
+ {function, "file", "open", [], [], []} = edlin_context:get_context(lists:reverse("file:open(")),
+ {string} = edlin_context:get_context(lists:reverse("file:open(\"")),
+ {string} = edlin_context:get_context(lists:reverse("file:open(\"/")),
+ {string} = edlin_context:get_context(lists:reverse("file:open(\"Word")),
+ {function, "file", "open", [], {string, "\"\""}, []} = edlin_context:get_context(lists:reverse("file:open(\"\"")),
+ {function, "file", "open", [{string, "\"\""}], [], []} = edlin_context:get_context(lists:reverse("file:open(\"\",")),
+ {function, "file", "open", [{string, "\"\""}], [], [{list, [], []}]} = edlin_context:get_context(lists:reverse("file:open(\"\",[")),
+ {function, "file", "open", [{string, "\"\""}], [], [{tuple, [], []}]} = edlin_context:get_context(lists:reverse("file:open(\"\",{")),
+ {function, "file", "open", [{string, "\"\""}], [], [{list, [], []},{tuple, [], []}]} = edlin_context:get_context(lists:reverse("file:open(\"\",[{")),
+ {function, "file", "open", [{string, "\"\""}], [], [{list, [], {atom, "atom"}}]} = edlin_context:get_context(lists:reverse("file:open(\"\",[atom")),
+ {function, "file", "open", [{string, "\"\""}], [], [{tuple, [], {atom, "atom"}}]} = edlin_context:get_context(lists:reverse("file:open(\"\",{atom")),
+ {function, "file", "open", [{string, "\"\""}], [], [{list, [], []},{tuple, [], {atom, "atom"}}]} = edlin_context:get_context(lists:reverse("file:open(\"\",[{atom")),
+ {function, "file", "open", [{string, "\"\""}], [], [{list, [], []},{tuple, [{atom, "atom"}], []}]} = edlin_context:get_context(lists:reverse("file:open(\"\",[{atom,")),
+ {function, "file", "open", [{string, "\"\""}], [], [{map, ["atom"], "atom", [], []}]} = edlin_context:get_context(lists:reverse("file:open(\"\",#{ atom =>")),
+ {term, [], {atom, "list"}} = edlin_context:get_context(lists:reverse("#{list")),
+ {term, [], {atom, "list"}} = edlin_context:get_context(lists:reverse("{list")),
+ {term, [], {atom, "list"}} = edlin_context:get_context(lists:reverse("[list")),
+ {map, "M", []} = edlin_context:get_context(lists:reverse("M#{")),
+ {map, "M", []} = edlin_context:get_context(lists:reverse("M#{key")),
+ {map, "M", ["key"]} = edlin_context:get_context(lists:reverse("M#{key=>")),
+ {map, "M", ["key"]} = edlin_context:get_context(lists:reverse("M#{key:=")), %% map value
+ {map, "M", ["key"]} = edlin_context:get_context(lists:reverse("M#{key=>0")),
+ {map, "M", ["key"]} = edlin_context:get_context(lists:reverse("M#{key=>0,")),
+ {map, "M", ["key", "key2"]} = edlin_context:get_context(lists:reverse("M#{key=>0,key2=>")),
+ {map_or_record} = edlin_context:get_context(lists:reverse("#")),
+ {record, "record", [], [], [], [], []} = edlin_context:get_context(lists:reverse("#record{")),
+ {record, "record", [], [], [], [], []} = edlin_context:get_context(lists:reverse("#record.")),
+ {record, "record", [], [], [], {atom, "field"}, []} = edlin_context:get_context(lists:reverse("#record{field")),
+ {record, "record", ["field"], "field", [], [], []} = edlin_context:get_context(lists:reverse("#record{field=>")),
+ {record, "record", ["field"], "field", [], [], []} = edlin_context:get_context(lists:reverse("#record{field:=")), %% record_field value
+ {record, "record", ["field"], [], [{integer, "0"}], [], []} = edlin_context:get_context(lists:reverse("R#record{field=>0,")),
+ {record, "record", ["field", "field2"], "field2", [{integer,"0"}], [], [{list, [], []},{tuple, [{atom, "atom"}], []}]} = edlin_context:get_context(lists:reverse("R#record{field=>0,field2=>[{atom,")),
+ {term,[],{atom,"fun"}} = edlin_context:get_context(lists:reverse("fun")),
+ {term,[],{atom,"fun"}} = edlin_context:get_context(lists:reverse("fun ")),
+ {fun_} = edlin_context:get_context(lists:reverse("fun m")),
+ {fun_, "m"} = edlin_context:get_context(lists:reverse("fun m:")),
+ {fun_, "m"} = edlin_context:get_context(lists:reverse("fun m:f")),
+ {fun_, "m", "f"} = edlin_context:get_context(lists:reverse("fun m:f/")),
+ {fun_, "m", "f"} = edlin_context:get_context(lists:reverse("fun m:f/1")),
+ {fun_, "m", "f"} = edlin_context:get_context(lists:reverse("fun m:f/1 ")),
+ {term,[{fun_,"fun m:f/1"}],[]} = edlin_context:get_context(lists:reverse("fun m:f/1 ,")),
+ {function,"user_defined","my_fun",
+ [{keyword,"receive X -> X end"}],
+ [],[]} = edlin_context:get_context(lists:reverse("my_fun(receive X -> X end, ")),
+ {function,"user_defined","my_fun",
+ [{keyword,"maybe X -> X end"}],
+ [],[]} = edlin_context:get_context(lists:reverse("my_fun(maybe X -> X end, ")),
+ {function,"user_defined","my_fun",
+ [{keyword,"try a end"}],
+ [],[]} = edlin_context:get_context(lists:reverse("my_fun(try a end, ")),
+ {function,"user_defined","my_fun",
+ [{keyword,"catch X -> X end"}],
+ [],[]}= edlin_context:get_context(lists:reverse("my_fun(catch X -> X end, ")),
+ {function,"user_defined","my_fun",
+ [{keyword,"try a catch _:_ -> b end"}],
+ [],[]} = edlin_context:get_context(lists:reverse("my_fun(try a catch _:_ -> b end, ")),
+ {function,"user_defined","my_fun",
+ [{keyword,"begin X end"}],
+ [],[]}= edlin_context:get_context(lists:reverse("my_fun(begin X end, ")),
+ {function,"user_defined","my_fun",
+ [{keyword,"if X -> X end"}],
+ [],[]} = edlin_context:get_context(lists:reverse("my_fun(if X -> X end, ")),
+ {function,"user_defined","my_fun",
+ [{keyword,"case X of _ -> X end"}],
+ [],[]} = edlin_context:get_context(lists:reverse("my_fun(case X of _ -> X end, ")),
+ {binding} = edlin_context:get_context(lists:reverse("fun() -> X")),
+ {term,[],{atom,"x"}} = edlin_context:get_context(lists:reverse("fun() -> x")),
+ {term} = edlin_context:get_context(lists:reverse("fun() ->")),
+ {macro} = edlin_context:get_context(lists:reverse("?")), %% not supported in edlin_expand
+ % unknown map
+ {term,[{operation,"one = a"},{operation,"two = b"}],[]} = edlin_context:get_context(lists:reverse("#{ one = a, two = b, ")),
+ {term,[{atom,"a"},{atom,"b"}],[]} = edlin_context:get_context(lists:reverse("#{ one := a, two := b, ")),
+ {term,[{atom,"a"},{atom,"b"}],[]} = edlin_context:get_context(lists:reverse("#{ one => a, two => b, ")),
+ {term,[{operation,"A = a"},{operation,"B = b"}],[]} = edlin_context:get_context(lists:reverse("A = a, B = b, ")),
+ {term,[{operation,"one = a"}],[]} = edlin_context:get_context(lists:reverse("#{ one = a, two = ")),
+ {term,[{atom,"a"}],[]} = edlin_context:get_context(lists:reverse("#{ one := a, two := ")),
+ {term,[{atom,"a"}],[]} = edlin_context:get_context(lists:reverse("#{ one => a, two => ")),
+ {term,[{operation,"A = a"}],[]} = edlin_context:get_context(lists:reverse("A = a, B = ")),
+ {term,[],{operation,"A = a"}} = edlin_context:get_context(lists:reverse("A = a")),
+ {'end'} = edlin_context:get_context(lists:reverse("a.")),
+ {record,"record",[],[],[],[],[]} = edlin_context:get_context(lists:reverse("#record.")),
+ {record,"record",[],[],[],[],[]} = edlin_context:get_context(lists:reverse("{#record.")),
+ {record,"record",[],[],[],{atom,"a"},[]} = edlin_context:get_context(lists:reverse("#record.a")),
+ {record,"record",[],[],[],{atom,"a"},[]} = edlin_context:get_context(lists:reverse("{#record.a")),
+ {term,[],{record,"#record{}"}} = edlin_context:get_context(lists:reverse("#record{}")),
+ {term,[],{map,"#{ a => b}"}} = edlin_context:get_context(lists:reverse("#{ a => b}")),
+ {term,[{atom,"a"}],{atom,"tuple"}} = edlin_context:get_context(lists:reverse("{a, tuple")),
+ {term,[],{tuple,"{a, tuple}"}} = edlin_context:get_context(lists:reverse("{a, tuple}")),
+ {term,[],{call,"lists:my_fun()"}} = edlin_context:get_context(lists:reverse("lists:my_fun()")),
+ {term} = edlin_context:get_context(lists:reverse("(")),
+ {term,[],{parenthesis,"()"}} = edlin_context:get_context(lists:reverse("()")),
+ {new_fun,"()"} = edlin_context:get_context(lists:reverse("fun()")),
+ %% 256 $]
+ {term,[],{list,"[]"}} = edlin_context:get_context(lists:reverse("[]")),
+ {term,[{atom,"a"}],{atom,"b"}} = edlin_context:get_context(lists:reverse("fun() when a, b")),
+ {term,[{atom,"a"}],{atom,"b"}} = edlin_context:get_context(lists:reverse("fun() -> a, b")),
+ {term,[{atom,"a"},{atom,"b"}],[]} = edlin_context:get_context(lists:reverse("fun() -> a, b, ")),
+ {term, [], {pid, "<1.0.1>"}} = edlin_context:get_context(lists:reverse("<1.0.1>")),
+ {term, [], {funref, "#Fun<erl_eval.0.1>"}} = edlin_context:get_context(lists:reverse("#Fun<erl_eval.0.1>")),
+ {term, [], {ref, "#Ref<1.0.1>"}} = edlin_context:get_context(lists:reverse("#Ref<1.0.1>")),
+ %{term, [], {port, "#Port<1.0>"}} = edlin_context:get_context(lists:reverse("#Port<1.0>")),
+ {term, [], {binary, "<<0>>"}} = edlin_context:get_context(lists:reverse("<<0>>")),
+ {term,[],{keyword,"fun (X) -> X end"}} = edlin_context:get_context(lists:reverse("fun (X) -> X end")),
+ {term,[],{keyword,"fun(X) -> X end"}} = edlin_context:get_context(lists:reverse("fun(X) -> X end")), %% should be fun_ too
+ {term,[],{keyword,"receive X -> X end"}} = edlin_context:get_context(lists:reverse("receive X -> X end")),
+ {error, _} = edlin_context:get_context(lists:reverse("no_keyword -> X end")),
+ {term} = edlin_context:get_context(lists:reverse("@")), %% No valid argument 305
+ {term,[],{char,"$@"}} = edlin_context:get_context(lists:reverse("$@")),
+ {term,[],{char,"$ "}} = edlin_context:get_context(lists:reverse("$ ")),
+ {term,[],{float,"1.0"}} = edlin_context:get_context(lists:reverse("1.0")),
+ {term,[],{integer,"10#10"}} = edlin_context:get_context(lists:reverse("10#10")),
+ {term,[],{integer,"1"}} = edlin_context:get_context(lists:reverse("1")),
+ {binding} = edlin_context:get_context(lists:reverse("{X")),
+ {term,[{var, "X"}], []} = edlin_context:get_context(lists:reverse("{X, ")),
+ {error,_} = edlin_context:get_context(lists:reverse("<abc)")),
+ {error,_} = edlin_context:get_context(lists:reverse("<abc]")),
+ {error,_} = edlin_context:get_context(lists:reverse("<abc}")),
+ {term} = edlin_context:get_context(lists:reverse("(abc>")),
+ {term} = edlin_context:get_context(lists:reverse("\"\\\"\"")), %% odd quotes "\""
+ {error, _} = edlin_context:get_context(lists:reverse("{\"\", $\"}")), %% odd quotes
+ {term} = edlin_context:get_context(lists:reverse("receive X -> ")),
+ %% read operator and argument order validity
+ {error,_} = edlin_context:get_context(lists:reverse("foo bar")), %% TODO what should be returned here, illegal
+ {term,[],{operation,"\" \" \" \""}} = edlin_context:get_context(lists:reverse("\" \" \" \"")), %% " " " "
+ {term,[],{operation,"1 + 2"}} = edlin_context:get_context(lists:reverse("1+2")),
+ {term,[],{operation,"1 andalso 2"}} = edlin_context:get_context(lists:reverse("1 andalso 2")),
+ {term,[],{operation,"1 and 2"}} = edlin_context:get_context(lists:reverse("1 and 2")),
+ {term,[],{operation,"1 orelse 2"}} = edlin_context:get_context(lists:reverse("1 orelse 2")),
+ {term,[],{operation,"1 or 2"}} = edlin_context:get_context(lists:reverse("1 or 2")),
+ {term,[],{operation,"1 or 2"}} = edlin_context:get_context(lists:reverse("1 or 2")),
+ {term,[],{operation,"1 =/= 2"}} = edlin_context:get_context(lists:reverse("1 =/= 2")),
+ {term,[],{operation,"1 =:= 2"}} = edlin_context:get_context(lists:reverse("1 =:= 2")),
+ {term,[],{operation,"1 <=> 2"}} = edlin_context:get_context(lists:reverse("1 <=> 2")),
+ {term,[],{operation,"<<1>> > <<2>>"}} = edlin_context:get_context(lists:reverse("<<1>>><<2>>")),
+ %{term,[],{operation,"<<1>> > <<2>>"}} = edlin_context:get_context(lists:reverse("<<1>> > <<2>>")),
+ {error,_} = edlin_context:get_context(lists:reverse("1 + + 2")),
+ {term,[],{integer,"2"}} = edlin_context:get_context(lists:reverse("1 -> 2")),
+ {term,[],{integer,"2"}} = edlin_context:get_context(lists:reverse("receive X -> 2")),
+ {term} = edlin_context:get_context(lists:reverse("receive X ->")),
+ {term,[{integer,"2"}],{operation,"1 + 3"}} = edlin_context:get_context(lists:reverse("receive X -> 2, 1+3")),
+ {term,[],{integer,"-1"}} = edlin_context:get_context(lists:reverse("-1")),
+ {term,[],{float,"-1.2"}} = edlin_context:get_context(lists:reverse("-1.2")),
+ ok. \ No newline at end of file
diff --git a/lib/stdlib/test/edlin_expand_SUITE.erl b/lib/stdlib/test/edlin_expand_SUITE.erl
index 22b6ba03af..206a8b7246 100644
--- a/lib/stdlib/test/edlin_expand_SUITE.erl
+++ b/lib/stdlib/test/edlin_expand_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,12 +18,22 @@
%% %CopyrightEnd%
%%
-module(edlin_expand_SUITE).
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
- init_per_testcase/2, end_per_testcase/2,
- init_per_group/2,end_per_group/2]).
--export([normal/1, quoted_fun/1, quoted_module/1, quoted_both/1, erl_1152/1,
- erl_352/1, unicode/1]).
-
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+ init_per_testcase/2, end_per_testcase/2,
+ init_per_group/2,end_per_group/2]).
+-export([normal/1, type_completion/1, quoted_fun/1, quoted_module/1, quoted_both/1, erl_1152/1, get_coverage/1,
+ check_trailing/1, unicode/1, filename_completion/1, binding_completion/1, record_completion/1,
+ map_completion/1, function_parameter_completion/1, fun_completion/1]).
+-record(a_record,
+ {a_field :: atom1 | atom2 | btom | 'my atom' | {atom3, {atom4, non_neg_integer()}} | 'undefined',
+ b_field :: boolean() | 'undefined',
+ c_field :: list(term()) | 'undefined',
+ d_field :: non_neg_integer() | 'undefined'}).
+-record('Quoted_record',
+ {'A_field' :: atom1 | atom2 | btom | 'my atom' | {atom3, {atom4, non_neg_integer()}} | 'undefined',
+ b_field :: boolean() | 'undefined',
+ c_field :: list(term()) | 'undefined',
+ d_field :: non_neg_integer() | 'undefined'}).
-include_lib("common_test/include/ct.hrl").
init_per_testcase(_Case, Config) ->
@@ -37,17 +47,20 @@ suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,1}}].
-all() ->
- [normal, quoted_fun, quoted_module, quoted_both, erl_1152, erl_352,
+all() ->
+ [normal, filename_completion, binding_completion, get_coverage, type_completion,
+ record_completion, fun_completion, map_completion, function_parameter_completion,
+ quoted_fun, quoted_module, quoted_both, erl_1152, check_trailing,
unicode].
-groups() ->
+groups() ->
[].
init_per_suite(Config) ->
Config.
end_per_suite(_Config) ->
+ cleanup(),
ok.
init_per_group(_GroupName, Config) ->
@@ -60,53 +73,470 @@ cleanup() ->
[try
code:purge(M),
code:delete(M)
- catch _:_ -> ok end || M <- [expand_test, expand_test1,
- 'ExpandTestCaps', 'ExpandTestCaps2']].
+ catch _:_ -> ok end || M <- [expand_test, expand_test1, expand_function_parameter,
+ 'ExpandTestCaps', 'ExpandTestCaps1',
+ complete_function_parameter]].
normal(Config) when is_list(Config) ->
{module,expand_test} = compile_and_load(Config,expand_test),
%% These tests might fail if another module with the prefix
%% "expand_" happens to also be loaded.
- {yes, "test:", []} = do_expand("expand_"),
+ {yes,"test:",[]} = do_expand("expand_"),
{no, [], []} = do_expand("expandXX_"),
- {no,[],
- [{"a_fun_name",1},
- {"a_less_fun_name",1},
- {"b_comes_after_a",1},
- {"expand0arity_entirely",0},
- {"module_info",0},
- {"module_info",1}]} = do_expand("expand_test:"),
- {yes,[],[{"a_fun_name",1},
- {"a_less_fun_name",1}]} = do_expand("expand_test:a_"),
+ {no,[],[#{
+ title:="functions",
+ elems:=[{"a_fun_name",[{ending,"("}]},
+ {"a_less_fun_name",_},
+ {"b_comes_after_a",_},
+ {"expand0arity_entirely",_},
+ {"module_info",_}]
+ }]} = do_expand("expand_test:"),
+ {yes,[],[#{title:="functions",
+ elems:=[{"a_fun_name",_},{"a_less_fun_name",_}]}]} = do_expand("expand_test:a_"),
{yes,"arity_entirely()",[]} = do_expand("expand_test:expand0"),
ok.
+to_atom(Str) ->
+ case erl_scan:string(Str) of
+ {ok, [{atom,_,A}], _} ->
+ {ok, A};
+ _ ->
+ error
+ end.
+
+type_completion(_Config) ->
+ ct:timetrap({minutes, 20}),
+ {Time,_} = timer:tc(fun() -> do_expand("erl_pp:expr(") end),
+ case Time of
+ Time when Time > 2600000 -> {skip, "Expansion too slow on this machine"};
+ _ ->
+ parallelforeach(
+ fun(Mod) ->
+ Exports = edlin_expand:get_exports(Mod),
+ [try
+ Str = io_lib:write_atom(Mod) ++ ":" ++ atom_to_list(Func) ++ "(",
+ do_expand(Str)
+ catch E:R:ST ->
+ erlang:raise(E, {R, Mod, Func}, ST)
+ end || {Func, _}<- Exports]
+ end, [list_to_atom(M) || {M,_,_} <- code:all_available()]),
+ ok
+ end.
+
+parallelforeach(Fun, List) ->
+ case parallelforeach(Fun, List, #{}, erlang:system_info(schedulers_online)) of
+ [] -> ok;
+ Else -> ct:fail(Else)
+ end.
+parallelforeach(_Fun, [], Workers, _MaxWorkers) when map_size(Workers) =:= 0 ->
+ [];
+parallelforeach(Fun, List, Workers, MaxWorkers) when MaxWorkers =:= map_size(Workers);
+ List =:= [] ->
+ receive
+ {'DOWN', Ref, _, _, normal} ->
+ parallelforeach(Fun, List, maps:remove(Ref, Workers), MaxWorkers);
+
+ {'DOWN', Ref, _, _, Reason} ->
+ {Arg, NewWorkers} = maps:take(Ref, Workers),
+ [{Arg, Reason} | parallelforeach(Fun, List, NewWorkers, MaxWorkers)]
+ end;
+parallelforeach(Fun, [H|T], Workers, MaxWorkers) ->
+ {_Pid, Ref} = spawn_monitor(fun() -> Fun(H) end),
+ parallelforeach(Fun, T, Workers#{ Ref => H }, MaxWorkers).
+
+filename_completion(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ {ok, CWD} = file:get_cwd(),
+ file:set_cwd(DataDir),
+ {yes,"e\"",[]} = do_expand("\"visible\\ fil"),
+ {no,[],[]} = do_expand("\"visible fil"),
+ {no,[],[]} = do_expand("\" "),
+ {no,[],[]} = do_expand("\"\""),
+ {yes, "e\"", []} = do_expand("\".hidden\\ fil"),
+ {yes,"/", []} = do_expand("\".."),
+ {yes,"ta/", _} = do_expand("\"../edlin_expand_SUITE_da"),
+ {yes,"erl\"",[]} = do_expand("\"complete_function_parameter."),
+ R = case {os:type(), file:native_name_encoding()} of
+ {{win32,_}, _} -> {skip, "Unicode on filenames in windows are tricky"};
+ {_,latin1} -> {skip, "Cannot interpret unicode filenames when native_name_encoding is latin1"};
+ _ ->
+ {yes,"isible",
+ [{"visible file",_},{"visible_file",_},{"visible😀_file",_}]} = do_expand("\"v"),
+ {yes,"e\"",[]} = do_expand("\"visible😀_fil"),
+ {yes,[],
+ [{"../",[]},
+ {".hidden file",_},
+ {".hidden_file",_},
+ {".hidden😀_file",_}]} = do_expand("\"."),
+ ok
+ end,
+ file:set_cwd(CWD),
+ R.
+
+record_completion(_Config) ->
+ %% test record completion for loaded records
+ %% test record field name completion
+ %% test record field completion
+ {yes,"ord{", []} = do_expand("#a_rec"),
+ {yes,"uoted_record'{", []} = do_expand("#'Q"),
+ {no, [], [#{title:="fields", elems:=[{"a_field",_}, {"b_field",_}, {"c_field",_}, {"d_field",_}]}]} = do_expand("#a_record{"),
+ {no, [], [#{title:="fields", elems:=[{"a_field",_}, {"b_field",_}, {"c_field",_}, {"d_field",_}]}]} = do_expand("#a_record."),
+ {yes,"eld=", []} = do_expand("#a_record{a_fi"),
+ {no,[],[#{title:="types",elems:=
+ [{"atom1",[]},
+ {"atom2",[]},
+ {"btom",[]},
+ {"'my atom'",[]},
+ {"{atom3, ...}",[]}],
+ options:=[{hide,title}]}]} = do_expand("#a_record{a_field="),
+ %% test that an already specified field does not get suggested again
+ {no,[],
+ [#{title:="fields", elems:=
+ [{"a_field",[{ending,"="}]},
+ {"b_field",[{ending,"="}]},
+ {"c_field",[{ending,"="}]},
+ {"d_field",[{ending,"="}]}],
+ options:=[highlight_all]}]} = do_expand("#a_record{a_field={atom3,b},"),
+ %% test match argument and closing parenthesis completion
+ {yes,", ",[]} = do_expand("#a_record{a_field={atom3"),
+ {no,[],[#{title:="types",elems:=[{"{atom4, ...}",[]}],options:=[{hide,title}]}]} = do_expand("#a_record{a_field={atom3,"),
+ {no,[],[#{title:="types",elems:=[{"integer() >= 0",[]}],options:=[{hide,title}]}]} = do_expand("#a_record{a_field={atom3,{atom4, "),
+ {yes,"}",_} = do_expand("#a_record{a_field={atom3,{atom4, 1"),
+ {yes,"}",_} = do_expand("#a_record{a_field={atom3,{atom4, 1}"),
+ ok.
+
+fun_completion(_Config) ->
+ {yes, "/1", []} = do_expand("fun lists:unzip3"),
+ {no, [], []} = do_expand("fun lists:unzip3/1,"),
+ {no, [], []} = do_expand("lists:unzip3/1"),
+ {no, [], [{"2",_},{"3",_}]} = do_expand("lists:seq/"),
+ %{yes, ", ", _} = do_expand("lists:all(fun erlang:is_atom/1"),
+ {no, [], [#{}]} = do_expand("lists:all(fun erlang:is_atom/1, "),
+ ok.
+
+binding_completion(_Config) ->
+ %% test that bindings in the shell can be completed
+ {yes,"ding",[]} = do_expand("Bin"),
+ {yes,"ding",[]} = do_expand("file:open(Bin"),
+ {yes,"ding",[]} = do_expand("fun (X, Y) -> Bin"),
+ %% test unicode
+ {yes,"öndag", []} = do_expand("S"),
+ {yes,"", []} = do_expand("Ö"),
+ ok.
+
+map_completion(_Config) ->
+ %% test that key suggestion works for a known map in bindings
+ {no,[],[{"a_key",[{ending, "=>"}]},{"b_key",_},{"c_key",_}]} = do_expand("MapBinding#{"),
+ {yes, "_key=>", []} = do_expand("MapBinding#{b"),
+ {yes, "_key=>", []} = do_expand("MapBinding # { b"),
+ %% test that an already specified key does not get suggested again
+ {no, [], [{"a_key",_},{"c_key", _}]} = do_expand("MapBinding#{b_key=>1,"),
+ %% test that unicode works
+ ok.
+
+function_parameter_completion(Config) ->
+ %% test first and second parameter
+ %% test multiple arities with same type on first parameter
+ %% test multiple arities with different type on first parameter
+ %% test that recursive types does not trigger endless loop
+ %% test that getting type of out of bound parameter does not trigger crash
+ compile_and_load2(Config,complete_function_parameter),
+ {no, [], [#{elems:=[#{title:="complete_function_parameter:an_untyped_fun/2", elems:=[]}]}]} = do_expand("complete_function_parameter:an_untyped_fun("),
+ {yes,":",[]} = do_expand("complete_function_parameter:an_untyped_fun(complete_function_parameter"),
+ {no, [], [#{elems:=[#{elems:=[#{title:="types",elems:=[{"integer()",[]}]}]}]}]} = do_expand("complete_function_parameter:a_fun_name("),
+ {no, [], [#{elems:=[#{elems:=[#{elems:=[{"integer()",[]}]}]}]}]} = do_expand("complete_function_parameter:a_fun_name(1,"),
+ {no, [], [#{elems:=[#{elems:=[#{elems:=[{"integer()",[]}]}]}]}]} = do_expand("complete_function_parameter : a_fun_name ( 1 , "),
+ {yes, ")", []} = do_expand("complete_function_parameter:a_fun_name(1,2"),
+ {no, [], []} = do_expand("complete_function_parameter:a_fun_name(1,2,"),
+ {no, [], [#{elems:=[#{elems:=[#{elems:=[{"any()",[]},{"[any() | [Deeplist]]",[]}]}]}]}]} = do_expand("complete_function_parameter:a_deeplist_fun("),
+ {no,[],[#{title:="typespecs",
+ elems:=[#{title:=
+ "complete_function_parameter:multi_arity_fun(T1)",
+ elems:=[#{title:="types",
+ elems:=[{"integer()",[]}],
+ options:=[{hide,title}]}],
+ options:=[{highlight_param,1}]},
+ #{title:=
+ "complete_function_parameter:multi_arity_fun(T1, T2)",
+ elems:=[#{title:="types",
+ elems:=[{"integer()",[]}],
+ options:=[{hide,title}]}],
+ options:=[{highlight_param,1}]},
+ #{title:=
+ "complete_function_parameter:multi_arity_fun()",
+ options:=[],
+ elems:=[")"]}],
+ options:=[highlight_all]}]} = do_expand("complete_function_parameter:multi_arity_fun("),
+ {no, [], [#{elems:=[#{elems:=[#{elems:=[{"true",[]},{"false",[]}]}]}]}]} = do_expand("complete_function_parameter:multi_arity_fun(1,"),
+ {no,[],
+ [#{elems :=
+ [#{elems :=
+ [#{elems := [{"integer()",[]}],
+ options := [{hide,title}],
+ title := "types"}],
+ options := [{highlight_param,1}],
+ title :=
+ "complete_function_parameter:different_multi_arity_fun(T1)"},
+ #{elems :=
+ [#{elems := [{"true",[]},{"false",[]}],
+ options := [{hide,title}],
+ title := "types"}],
+ options := [{highlight_param,1}],
+ title :=
+ "complete_function_parameter:different_multi_arity_fun(B1, T1)"}],
+ options := [highlight_all],
+ title := "typespecs"}]} = do_expand("complete_function_parameter:different_multi_arity_fun("),
+ {no, [], [#{elems:=[#{elems:=[#{elems:=[{"integer()",[]}]}]}]}]} = do_expand("complete_function_parameter:different_multi_arity_fun(false,"),
+ {no, [], [#{elems:=[#{elems:=[#{elems:=[{"{atom1, ...}",[]},
+ {"atom1",[]},
+ {"atom2",[]},
+ {"[atom4 | atom5]",[]}]}]}]}]} = do_expand("complete_function_parameter:advanced_nested_parameter("),
+ {no, [], [#{elems:=[#{elems:=[#{elems:=[{"atom1",[]}]}]}]}]} = do_expand("complete_function_parameter:advanced_nested_parameter({"),
+ {no, [], [#{elems:=[#{elems:=[#{elems:=[{"{integer() >= 0, ...}",[]}]}]}]}]} = do_expand("complete_function_parameter:advanced_nested_parameter({atom1,"),
+ {no, [], [#{elems:=[#{elems:=[#{elems:=[{"atom4",[]},{"atom5",[]}]}]}]}]} = do_expand("complete_function_parameter:advanced_nested_parameter(["),
+ {no, [], [#{elems:=[#{elems:=[#{elems:=[{"atom4",[]},{"atom5",[]}]}]}]}]} = do_expand("complete_function_parameter : advanced_nested_parameter ( [ , "),
+ {no,[],
+ [#{elems :=
+ [#{elems :=
+ [#{elems := [{"integer()",[]}],
+ options := [{hide,title}],
+ title := "types"}],
+ options := [{highlight_param,1}],
+ title :=
+ "complete_function_parameter:different_multi_arity_fun(T1)"},
+ #{elems :=
+ [#{elems := [{"true",[]},{"false",[]}],
+ options := [{hide,title}],
+ title := "types"}],
+ options := [{highlight_param,1}],
+ title :=
+ "complete_function_parameter:different_multi_arity_fun(B1, T1)"}],
+ options := [highlight_all],
+ title := "typespecs"}]} = do_expand("complete_function_parameter:different_multi_arity_fun("),
+ %% Hide results where the type of the first parameters does not match the prototype header
+ {no,[],
+ [#{title:="typespecs",
+ elems:=[#{title:="complete_function_parameter:different_multi_arity_fun(B1, T1)",
+ elems:=[#{title:="types",
+ elems:=[{"integer()",[]}],
+ options:=[{hide,title}]}],
+ options:=[{highlight_param,2}]}],
+ options:=[highlight_all]}]} = do_expand("complete_function_parameter:different_multi_arity_fun(false,"),
+ {no, _, []} = do_expand("complete_function_parameter:different_multi_arity_fun(atom,"),
+ {yes, _, _} = do_expand("complete_function_parameter:'emoji"),
+
+ ok.
+
+get_coverage(Config) ->
+ compile_and_load2(Config,complete_function_parameter),
+ do_expand("\""),
+ do_expand("\"."),
+ do_expand("\"../"),
+ do_expand("\"/"),
+ do_expand("fun m:f"),
+ do_expand("fun m:f/"),
+ do_expand("#"),
+ do_expand("MapBinding#{"),
+ do_expand("B"),
+ do_expand("#a_record{"),
+ do_expand("#a_record{a_field=>"),
+
+ %% match_arguments and is_type tests
+ do_expand("complete_function_parameter:map_parameter_function(#{"),
+ do_expand("complete_function_parameter:map_parameter_function(#{a"),
+ do_expand("complete_function_parameter:map_parameter_function(#{a=>"),
+ do_expand("complete_function_parameter:map_parameter_function(#{a=>1"),
+ do_expand("complete_function_parameter:map_parameter_function(#{a=>1,"),
+ do_expand("complete_function_parameter:map_parameter_function(#{a=>1,d=>err"),
+ do_expand("complete_function_parameter:map_parameter_function(#{a=>1,d=>error"),
+ do_expand("complete_function_parameter:map_parameter_function(#{a=>1,d=>error}"),
+ do_expand("complete_function_parameter:map_parameter_function(#{a=>1,b=>2,c=>3,d=>error"),
+ do_expand("complete_function_parameter:map_parameter_function(#{}, "),
+ do_expand("complete_function_parameter:map_parameter_function(#{V=>1}, "),
+ do_expand("complete_function_parameter:map_parameter_function(#{a=>V}, "),
+ do_expand("complete_function_parameter:tuple_parameter_function({a,b}, "),
+ do_expand("complete_function_parameter:tuple_parameter_function({a,V}, "),
+ do_expand("complete_function_parameter:list_parameter_function([], "),
+ do_expand("complete_function_parameter:list_parameter_function([atom], "),
+ true = {no, [], []} =/= do_expand("complete_function_parameter:list_parameter_function([V], "),
+ do_expand("complete_function_parameter:non_empty_list_parameter_function([atom], "),
+ do_expand("complete_function_parameter:non_empty_list_parameter_function([], "),
+ do_expand("complete_function_parameter:binary_parameter_function(<<0>>, "),
+ do_expand("complete_function_parameter:binary_parameter_function(<<V>>, "),
+ do_expand("complete_function_parameter:integer_parameter_function(0, "),
+ do_expand("complete_function_parameter:non_neg_integer_parameter_function(1, "),
+ do_expand("complete_function_parameter:neg_integer_parameter_function(-1, "),
+ do_expand("complete_function_parameter:float_parameter_function(0.1, "),
+ do_expand("complete_function_parameter:pid_parameter_function(<0.1.0>, "),
+ do_expand("complete_function_parameter:port_parameter_function(#Port<0.1>, "),
+ do_expand("complete_function_parameter:record_parameter_function(#a_record{a = 1}, "),
+ do_expand("complete_function_parameter:function_parameter_function(#Fun<erl_eval.1.0>, "),
+ do_expand("complete_function_parameter:function_parameter_function(fun(X) -> X end, "), %% Todo verify fun arity
+ do_expand("complete_function_parameter:function_parameter_function(receive X -> X end, "),
+ do_expand("complete_function_parameter:function_parameter_function(V, "),
+ do_expand("complete_function_parameter:function_parameter_function((1+2), "),
+ do_expand("complete_function_parameter:function_parameter_function(some_call(), "),
+ do_expand("complete_function_parameter:function_parameter_function(#Nope<1.0>, "),
+ do_expand("complete_function_parameter:reference_parameter_function(#Ref<1.0.1.0>, "),
+ do_expand("complete_function_parameter:any_parameter_function(#Ref<1.0.1.0>, "),
+ do_expand("complete_function_parameter:ann_type_parameter_function(atom, "),
+ true = {no, [], []} =/= do_expand("complete_function_parameter:ann_type_parameter_function2(1, "),
+ do_expand("complete_function_parameter:atom_parameter_function(atom, "),
+ do_expand("complete_function_parameter:ann_type_parameter_function(atom"),
+ do_expand("complete_function_parameter:atom_parameter_function(atom"),
+ %% user_defined function
+ {yes, "func(", _} =
+ do_expand("my_"),
+ {no,[],[#{elems :=
+ [#{elems :=
+ [#{elems := [{"#my_record",[]}],
+ options := [{hide,title}],
+ title := "types"}],
+ options := [{highlight_param,1}],
+ title := "shell_default:my_func(A)"}],
+ options := [highlight_all],
+ title := "typespecs"}]} =
+ do_expand("my_func("),
+ {yes,"=",[]} =
+ do_expand("my_func(#my_record{ field"),
+ {no,[],
+ [#{elems :=
+ [#{elems := [{"a_value",[]},{"b_value",[]}],
+ options := [{separator," :: "},{highlight_all}],
+ title := "erlang:my_type()"}],
+ options := [{hide,title}],
+ title := "types"}]} =
+ do_expand("my_func(#my_record{ field=>"),
+ {yes,"ue, ",[]} =
+ do_expand("my_func(#my_record{field=>a_val"),
+ %% bifs()
+ {yes, "st(", _} =
+ do_expand("integer_to_li"),
+ %% commands()
+ {yes, "(", _} =
+ do_expand("bt"),
+ {yes, ":", _}=edlin_expand:expand(lists:reverse("complete_function_parameter")),
+ {no, [], _}=edlin_expand:expand(lists:reverse("#")),
+ {no, [], _}=edlin_expand:expand(lists:reverse("UnbindedMap#")),
+ {no, [], _}=edlin_expand:expand(lists:reverse("UnbindedMap#{")),
+ {no, [], _}=edlin_expand:expand(lists:reverse("#{")),
+ {yes, "(", _}=edlin_expand:expand(lists:reverse("complete_function_parameter:a_fun_name")),
+
+ do_expand("fun l"),
+ %{yes, ">", _} =
+ do_expand("fun () -"),
+ %{yes, "n ", _} =
+ do_expand("fun () whe "),
+ %{no, [], []} =
+ do_expand("fun () when "),
+ %{no, [], []} =
+ do_expand("fun () -> "),
+ %% {keyword, ...}
+ do_expand("M#"),
+ do_expand("#non_existant_record"),
+ do_expand("#a_record{ non_existand_field"),
+
+
+ %% match_arguments coverage
+ do_expand("complete_function_parameter:integer_parameter_function(atom,"), %% match_argument -> false
+ do_expand("complete_function_parameter:a_zero_arity_fun()"), %% match_argument, parameters empty
+ do_expand("erlang:system_info(thread"),
+ do_expand("erlang:system_info(thread_nope"),
+ do_expand("erlang:system_info(threads"),
+ do_expand("erlang:process_flag(priori"),
+ do_expand("erlang:process_flag(priority_nope"),
+ do_expand("erlang:process_flag(priority, "),
+ do_expand("erlang:process_flag(priority, atom"),
+ do_expand("erlang:process_flag(priority, 1"),
+ do_expand("erlang:system_info({allocator,"),
+ do_expand("lists:seq(1"),
+ do_expand("lists:seq(1, 10"),
+ do_expand("ssh:connect({"),
+ do_expand("ssh:connect({255,"),
+ do_expand("ssh:connect({'$i"),
+ do_expand("ssh:connect({'$x"),
+ do_expand("ssh:connect({1000,"),
+ do_expand("ssh:connect({255,255,255,255,255,255,255,255"),
+ do_expand("ssh:connect({255,255,255,255}, ["),
+ do_expand("ssh:connect(receive V -> V end, "),
+ do_expand("ssh:connect((1+2), "),
+ do_expand("ssh:connect(hej(), "),
+ do_expand("ssh:connect(fun() -> ok end, "),
+ do_expand("ssh:connect(fun a:b/1, "),
+ do_expand("ssh:connect(V, [in"),
+ do_expand("ssh:connect(V, [inet"),
+ do_expand("ssh:connect(V, [inet, "),
+ do_expand("atom_to_list("),
+ do_expand("help("),
+ do_expand("h(lists"),
+ do_expand("ht(lists"),
+ do_expand("h(file,"),
+ do_expand("ht(file,"),
+ do_expand("ht(file,mode"),
+ do_expand("file:get_cwd()"),
+ do_expand("fl"),
+ do_expand("fl("),
+ do_expand("fl()"),
+ do_expand("MapBinding#"),
+ do_expand("RecordBinding#"),
+ do_expand("TupleBinding#"),
+ do_expand("Binding#"),
+ do_expand("MyVar"),
+ {_, _, M0} = do_expand("ssh:connect("),
+ do_format(M0),
+ {_, _, M1} = do_expand("ssh:connect({"),
+ do_format(M1),
+ {_, _, M6} = do_expand("ssh:connect({},["),
+ do_format(M6),
+ lists:flatten(edlin_expand:format_matches(M6, 20)),
+ {_, _, M2} = do_expand("e"),
+ do_format(M2),
+ {_, _, M3} = do_expand("erlang:i"),
+ do_format(M3),
+ {_, _, M4} = do_expand("complete_function_parameter:an_untyped_fun("),
+ do_format(M4),
+ lists:flatten(edlin_expand:format_matches(M4, 20)),
+ {_,_,M5}=edlin_expand:expand("e"),
+ do_format(M5),
+ {_,_,M7}=edlin_expand:expand("erlang:"),
+ do_format(M7),
+ {_,_,M8}=edlin_expand:expand("e"),
+ do_format(M8),
+ lists:flatten(edlin_expand:format_matches(M8, 20)),
+ {_,_,M9}=edlin_expand:expand("complete_function_parameter:an_untyped_fun("),
+ lists:flatten(edlin_expand:format_matches(M9, 20)),
+ do_format(M5),
+ {_, _, M10} = edlin_expand:expand("ssh:connect({},["),
+ do_format(M10),
+ lists:flatten(edlin_expand:format_matches(M10, 20)),
+ ok.
+
%% Normal module name, some function names using quoted atoms.
quoted_fun(Config) when is_list(Config) ->
{module,expand_test} = compile_and_load(Config,expand_test),
{module,expand_test1} = compile_and_load(Config,expand_test1),
%% should be no colon after test this time
- {yes, "test", []} = do_expand("expand_"),
+ {yes, "test", [#{title:="modules", elems:=[{"expand_test",[{ending, ":"}]},{"expand_test1",_}]}]} = do_expand("expand_"),
{no, [], []} = do_expand("expandXX_"),
- {no,[],[{"'#weird-fun-name'",1},
- {"'Quoted_fun_name'",0},
- {"'Quoted_fun_too'",0},
- {"a_fun_name",1},
- {"a_less_fun_name",1},
- {"b_comes_after_a",1},
- {"module_info",0},
- {"module_info",1}]} = do_expand("expand_test1:"),
- {yes,"_",[]} = do_expand("expand_test1:a"),
- {yes,[],[{"a_fun_name",1},
- {"a_less_fun_name",1}]} = do_expand("expand_test1:a_"),
- {yes,[],
- [{"'#weird-fun-name'",1},
- {"'Quoted_fun_name'",0},
- {"'Quoted_fun_too'",0}]} = do_expand("expand_test1:'"),
- {yes,"uoted_fun_",[]} = do_expand("expand_test1:'Q"),
- {yes,[],
- [{"'Quoted_fun_name'",0},
- {"'Quoted_fun_too'",0}]} = do_expand("expand_test1:'Quoted_fun_"),
+ {no,[],[#{title:="functions",
+ elems:=[{"'#weird-fun-name'",_},
+ {"'Quoted_fun_name'",_},
+ {"'Quoted_fun_too'",_},
+ {"a_fun_name",_},
+ {"a_less_fun_name",_},
+ {"b_comes_after_a",_},
+ {"module_info",_}]}]} = do_expand("expand_test1:"),
+ {yes,"_",[#{elems:=[{"a_fun_name",_},
+ {"a_less_fun_name",_}]}]} = do_expand("expand_test1:a"),
+ {yes,[],[#{elems:=[{"a_fun_name",_},
+ {"a_less_fun_name",_}]}]} = do_expand("expand_test1:a_"),
+ {yes,[],[#{elems:=[{"'#weird-fun-name'",_},
+ {"'Quoted_fun_name'",_},
+ {"'Quoted_fun_too'",_}]}]} = do_expand("expand_test1:'"),
+ {yes,"uoted_fun_",[#{elems:=[{"'Quoted_fun_name'",_},
+ {"'Quoted_fun_too'",_}]}]} = do_expand("expand_test1:'Q"),
+ {yes,[],[#{elems:=[{"'Quoted_fun_name'",_},
+ {"'Quoted_fun_too'",_}]}]} = do_expand("expand_test1:'Quoted_fun_"),
{yes,"weird-fun-name'(",[]} = do_expand("expand_test1:'#"),
%% Since there is a module_info/1 as well as a module_info/0
@@ -116,146 +546,127 @@ quoted_fun(Config) when is_list(Config) ->
quoted_module(Config) when is_list(Config) ->
{module,'ExpandTestCaps'} = compile_and_load(Config,'ExpandTestCaps'),
- {yes, "Caps':", []} = do_expand("'ExpandTest"),
- {no,[],
- [{"a_fun_name",1},
- {"a_less_fun_name",1},
- {"b_comes_after_a",1},
- {"module_info",0},
- {"module_info",1}]} = do_expand("'ExpandTestCaps':"),
- {yes,[],[{"a_fun_name",1},
- {"a_less_fun_name",1}]} = do_expand("'ExpandTestCaps':a_"),
+ {yes, "Caps':",[]} = do_expand("'ExpandTest"),
+ {no,[],[#{elems:=[{"a_fun_name",_},
+ {"a_less_fun_name",_},
+ {"b_comes_after_a",_},
+ {"module_info",_}]}]} = do_expand("'ExpandTestCaps':"),
+ {yes,[],[#{title:="functions", elems:=[{"a_fun_name",_},
+ {"a_less_fun_name",_}]}]} = do_expand("'ExpandTestCaps':a_"),
ok.
quoted_both(Config) when is_list(Config) ->
{module,'ExpandTestCaps'} = compile_and_load(Config,'ExpandTestCaps'),
{module,'ExpandTestCaps1'} = compile_and_load(Config,'ExpandTestCaps1'),
%% should be no colon (or quote) after test this time
- {yes, "Caps", []} = do_expand("'ExpandTest"),
- {no,[],[{"'#weird-fun-name'",0},
- {"'Quoted_fun_name'",0},
- {"'Quoted_fun_too'",0},
- {"a_fun_name",1},
- {"a_less_fun_name",1},
- {"b_comes_after_a",1},
- {"module_info",0},
- {"module_info",1}]} = do_expand("'ExpandTestCaps1':"),
- {yes,"_",[]} = do_expand("'ExpandTestCaps1':a"),
- {yes,[],[{"a_fun_name",1},
- {"a_less_fun_name",1}]} = do_expand("'ExpandTestCaps1':a_"),
- {yes,[],
- [{"'#weird-fun-name'",0},
- {"'Quoted_fun_name'",0},
- {"'Quoted_fun_too'",0}]} = do_expand("'ExpandTestCaps1':'"),
- {yes,"uoted_fun_",[]} = do_expand("'ExpandTestCaps1':'Q"),
- {yes,[],
- [{"'Quoted_fun_name'",0},
- {"'Quoted_fun_too'",0}]} = do_expand("'ExpandTestCaps1':'Quoted_fun_"),
+ {yes, "Caps", [#{elems:=[{"'ExpandTestCaps'",[{ending, ":"}]},{"'ExpandTestCaps1'",_}]}]} = do_expand("'ExpandTest"),
+ {no,[],[#{elems:=[{"'#weird-fun-name'",_},
+ {"'Quoted_fun_name'",_},
+ {"'Quoted_fun_too'",_},
+ {"a_fun_name",_},
+ {"a_less_fun_name",_},
+ {"b_comes_after_a",_},
+ {"module_info",_}]}]} = do_expand("'ExpandTestCaps1':"),
+ {yes,"_",[#{elems:=[{"a_fun_name",_},{"a_less_fun_name",_}]}]} = do_expand("'ExpandTestCaps1':a"),
+ {yes,[],[#{elems:=[{"a_fun_name",_},
+ {"a_less_fun_name",_}]}]} = do_expand("'ExpandTestCaps1':a_"),
+ {yes,[],[#{elems:=[{"'#weird-fun-name'",_},
+ {"'Quoted_fun_name'",_},
+ {"'Quoted_fun_too'",_}]}]} = do_expand("'ExpandTestCaps1':'"),
+ {yes,"uoted_fun_",[#{elems:=[{"'Quoted_fun_name'",_},{"'Quoted_fun_too'",_}]}]} = do_expand("'ExpandTestCaps1':'Q"),
+ {yes,[],[#{elems:=[{"'Quoted_fun_name'",_},
+ {"'Quoted_fun_too'",_}]}]} = do_expand("'ExpandTestCaps1':'Quoted_fun_"),
{yes,"weird-fun-name'()",[]} = do_expand("'ExpandTestCaps1':'#"),
ok.
%% Note: pull request #1152.
erl_1152(Config) when is_list(Config) ->
- "\n"++"foo"++" "++[1089]++_ = do_format(["foo",[1089]]),
+ "foo"++" "++[1089]++_ = do_format(["foo",[1089]]),
ok.
-erl_352(Config) when is_list(Config) ->
- erl_352_test(3, 3),
-
- erl_352_test(3, 75),
- erl_352_test(3, 76, [trailing]),
- erl_352_test(4, 74),
- erl_352_test(4, 75, [leading]),
- erl_352_test(4, 76, [leading, trailing]),
-
- erl_352_test(75, 3),
- erl_352_test(76, 3, [leading]),
- erl_352_test(74, 4),
- erl_352_test(75, 4, [leading]),
- erl_352_test(76, 4, [leading]),
-
- erl_352_test(74, 74, [leading]),
- erl_352_test(74, 75, [leading]),
- erl_352_test(74, 76, [leading, trailing]).
-
-erl_352_test(PrefixLen, SuffixLen) ->
- erl_352_test(PrefixLen, SuffixLen, []).
-
-erl_352_test(PrefixLen, SuffixLen, Dots) ->
- io:format("\nPrefixLen = ~w, SuffixLen = ~w\n", [PrefixLen, SuffixLen]),
-
- PrefixM = lists:duplicate(PrefixLen, $p),
- SuffixM = lists:duplicate(SuffixLen, $s),
- LM = [PrefixM ++ S ++ SuffixM || S <- ["1", "2"]],
- StrM = do_format(LM),
- check_leading(StrM, "", PrefixM, SuffixM, Dots),
-
- PrefixF = lists:duplicate(PrefixLen, $p),
- SuffixF = lists:duplicate(SuffixLen-2, $s),
- LF = [{PrefixF ++ S ++ SuffixF, 1} || S <- ["1", "2"]],
- StrF = do_format(LF),
- true = check_leading(StrF, "/1", PrefixF, SuffixF, Dots),
-
+check_trailing(Config) when is_list(Config) ->
+ Str = lists:duplicate(80, $1),
+ StrF = do_format([Str]),
+ {_, "...\n"} = lists:split(76, StrF),
ok.
-check_leading(FormStr, ArityStr, Prefix, Suffix, Dots) ->
- List = string:tokens(FormStr, "\n "),
- io:format("~p\n", [List]),
- true = lists:all(fun(L) -> length(L) < 80 end, List),
- case lists:member(leading, Dots) of
- true ->
- true = lists:all(fun(L) ->
- {"...", Rest} = lists:split(3, L),
- check_trailing(Rest, ArityStr,
- Suffix, Dots)
- end, List);
- false ->
- true = lists:all(fun(L) ->
- {Prefix, Rest} =
- lists:split(length(Prefix), L),
- check_trailing(Rest, ArityStr,
- Suffix, Dots)
- end, List)
- end.
-
-check_trailing([I|Str], ArityStr, Suffix, Dots) ->
- true = lists:member(I, [$1, $2]),
- case lists:member(trailing, Dots) of
- true ->
- {Rest, "..." ++ ArityStr} =
- lists:split(length(Str) - (3 + length(ArityStr)), Str),
- true = lists:prefix(Rest, Suffix);
- false ->
- {Rest, ArityStr} =
- lists:split(length(Str) - length(ArityStr), Str),
- Rest =:= Suffix
- end.
-
unicode(Config) when is_list(Config) ->
{module,unicode_expand} = compile_and_load(Config,'unicode_expand'),
- {no,[],[{"'кlириллиÌчеÑкий атом'",0},
- {"'кlириллиÌчеÑкий атом'",1},
- {"'кlириллиÌчеÑкий атомB'",1},
- {"module_info",0},
- {"module_info",1}]} = do_expand("unicode_expand:"),
- {yes,"риллиÌчеÑкий атом", []} = do_expand("unicode_expand:'кlи"),
- {yes,"еÑкий атом", []} = do_expand("unicode_expand:'кlириллиÌч"),
- {yes,"(",[]} = do_expand("unicode_expand:'кlириллиÌчеÑкий атомB'"),
- "\n'кlириллиÌчеÑкий атом'/0 'кlириллиÌчеÑкий атом'/1 "
- "'кlириллиÌчеÑкий атомB'/1 \nmodule_info/0 "
- "module_info/1 \n" =
- do_format([{"'кlириллиÌчеÑкий атом'",0},
- {"'кlириллиÌчеÑкий атом'",1},
- {"'кlириллиÌчеÑкий атомB'",1},
- {"module_info",0},
- {"module_info",1}]),
+ {no,[], [#{elems:=[{"'кlириллиÌчеÑкий атом'",_},
+ {"'кlириллиÌчеÑкий атомB'",_},
+ {"module_info",_}]}]} = do_expand("unicode_expand:"),
+ {yes,"риллиÌчеÑкий атом", [#{elems:=[{"'кlириллиÌчеÑкий атом'",_},
+ {"'кlириллиÌчеÑкий атомB'",_}]}]} = do_expand("unicode_expand:'кlи"),
+ {yes,"еÑкий атом", [#{elems:=[{"'кlириллиÌчеÑкий атом'",_},
+ {"'кlириллиÌчеÑкий атомB'",_}]}]} = do_expand("unicode_expand:'кlириллиÌч"),
+ {yes,"(", []} = do_expand("unicode_expand:'кlириллиÌчеÑкий атомB'"),
+ "'кlириллиÌчеÑкий атом' 'кlириллиÌчеÑкий атомB' module_info\n" =
+ do_format([{"'кlириллиÌчеÑкий атом'",[]},
+ {"'кlириллиÌчеÑкий атомB'",[]},
+ {"module_info",[]}]),
ok.
do_expand(String) ->
- edlin_expand:expand(lists:reverse(String)).
+ erlang:display(String),
+ Bs = [
+ {'Binding', 0},
+ {'MapBinding', #{a_key=>0, b_key=>1, c_key=>2}},
+ {'RecordBinding', {some_record, 1, 2}},
+ {'TupleBinding', {0, 1, 2}},
+ {'Söndag', 0},
+ {'Ö', 0}],
+ Rt = ets:new(records, []),
+
+ Rt2 = [{my_record, {attribute,[{text,"record"},
+ {location,{1,2}}],
+ record,
+ {my_record, [{typed_record_field,{record_field,[{text,"field"},
+ {location,{1,20}}],
+ {atom,[{text,"field"},{location,{1,20}}],field},
+ {atom,[{text,"a_value"},{location,{1,28}}],a_value}},
+ {user_type,[{text,"my_type"},{location,{1,33}}],
+ my_type,[]}}]}}}],
+ Ft = [{{function,{shell_default,my_func,1}},fun(_A)->0 end},
+ {{function_type,{shell_default,my_func,1}},
+ {attribute,[{text,"spec"},{location,{1,2}}],
+ spec,
+ {{my_func,1},
+ [{type,[{text,"("},{location,{1,14}}],
+ bounded_fun,
+ [{type,[{text,"("},{location,{1,14}}],
+ 'fun',
+ [{type,[{text,"("},{location,{1,14}}],
+ product,
+ [{var,[{text,"A"},{location,{1,15}}],'A'}]},
+ {type,[{text,"integer"},{location,{1,21}}],integer,[]}]},
+ [{type,[{text,"A"},{location,{1,36}}],
+ constraint,
+ [{atom,[{text,"A"},{location,{1,36}}],is_subtype},
+ [{var,[{text,"A"},{location,{1,36}}],'A'},
+ {type,[{text,"#"},{location,{1,41}}],
+ record,
+ [{atom,[{text,"my_record"},{location,{1,42}}],
+ my_record}]}]]}]]}]}}},
+ {{type,my_type},
+ {attribute,[{text,"type"},{location,{1,2}}],
+ type,
+ {my_type,{type,[{text,"a"},{location,{1,20}}],
+ union,
+ [{atom,[{text,"a_value"},{location,{1,20}}],a_value},
+ {atom,[{text,"b_value"},{location,{1,24}}],b_value}]},
+ []}}}],
+ shell:read_and_add_records(edlin_expand_SUITE, '_', [], Bs, Rt),
+ edlin_expand:expand(lists:reverse(String), [], {shell_state, Bs, ets:tab2list(Rt)++Rt2, Ft}).
do_format(StringList) ->
- lists:flatten(edlin_expand:format_matches(StringList)).
+ lists:flatten(edlin_expand:format_matches(StringList, 79)).
+
+compile_and_load2(Config, Module) ->
+ Filename = filename:join(
+ proplists:get_value(data_dir,Config),
+ atom_to_list(Module)),
+ PrivDir = proplists:get_value(priv_dir,Config),
+ c:c(Filename, [debug_info, {outdir, PrivDir}]).
compile_and_load(Config,Module) ->
Filename = filename:join(
diff --git a/lib/stdlib/test/edlin_expand_SUITE_data/.hidden file b/lib/stdlib/test/edlin_expand_SUITE_data/.hidden file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/.hidden file
diff --git a/lib/stdlib/test/edlin_expand_SUITE_data/.hidden_file b/lib/stdlib/test/edlin_expand_SUITE_data/.hidden_file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/.hidden_file
diff --git a/lib/stdlib/test/edlin_expand_SUITE_data/.hidden😀_file b/lib/stdlib/test/edlin_expand_SUITE_data/.hidden😀_file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/.hidden😀_file
diff --git a/lib/stdlib/test/edlin_expand_SUITE_data/complete_function_parameter.erl b/lib/stdlib/test/edlin_expand_SUITE_data/complete_function_parameter.erl
new file mode 100644
index 0000000000..c4ad9e13f8
--- /dev/null
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/complete_function_parameter.erl
@@ -0,0 +1,164 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(complete_function_parameter).
+
+-export(
+ [a_fun_name/2,
+ an_untyped_fun/2,
+ a_deeplist_fun/1,
+ a_zero_arity_fun/0,
+ multi_arity_fun/0,
+ multi_arity_fun/1,
+ multi_arity_fun/2,
+ different_multi_arity_fun/1,
+ different_multi_arity_fun/2,
+ advanced_nested_parameter/1,
+ test_year/1,
+ 'emoji_function🤯'/1,
+ map_parameter_function/1,
+ map_parameter_function/2,
+ tuple_parameter_function/2,
+ list_parameter_function/2,
+ non_empty_list_parameter_function/2,
+ binary_parameter_function/2,
+ neg_integer_parameter_function/2,
+ non_neg_integer_parameter_function/2,
+ integer_parameter_function/2,
+ float_parameter_function/2,
+ port_parameter_function/2,
+ pid_parameter_function/2,
+ record_parameter_function/2,
+ function_parameter_function/2,
+ reference_parameter_function/2,
+ any_parameter_function/2,
+ ann_type_parameter_function/2,
+ ann_type_parameter_function2/2,
+ atom_parameter_function/2
+ ]).
+-record(a_record, {}).
+%% test first and second parameter
+ %% test multiple arities with same type on first parameter
+ %% test multiple arities with different type on first parameter
+ %% test that recursive types does not trigger endless loop
+ %% test that getting type of out of bound parameter does not trigger crash
+-spec a_fun_name(Start, End) -> Return when
+ Start :: integer(),
+ End :: integer(),
+ Return:: integer().
+a_fun_name(_Start, _End) -> 0.
+
+an_untyped_fun(_Start, _End) -> 1.
+
+-spec a_deeplist_fun(Deeplist) -> integer() when
+ Deeplist :: T | [Deeplist],
+ T :: term().
+a_deeplist_fun(Deeplist) -> lists:flatten(Deeplist).
+
+a_zero_arity_fun() -> 0.
+
+-spec multi_arity_fun() -> integer().
+multi_arity_fun() -> 0.
+
+-spec multi_arity_fun(T1) -> integer() when
+ T1 :: integer().
+multi_arity_fun(_T1) -> 1.
+
+-spec multi_arity_fun(T1,T2) -> integer() when
+ T1 :: integer(),
+ T2 :: boolean().
+multi_arity_fun(_T1, _T2) -> 2.
+
+-spec different_multi_arity_fun(T1) -> integer() when
+ T1 :: integer().
+different_multi_arity_fun(_T1) -> 1.
+-spec different_multi_arity_fun(B1, T1) -> integer() when
+ B1 :: boolean(),
+ T1 :: integer().
+different_multi_arity_fun(_T1, _T2) -> 2.
+
+-spec advanced_nested_parameter(T1) -> integer() when
+ T1 :: {atom1, {non_neg_integer(), non_neg_integer()}} | 'atom1' | 'atom2' | ['atom4' | 'atom5'].
+advanced_nested_parameter(_T1) -> 0.
+
+-spec test_year(Y) -> integer() when
+ Y :: calendar:year().
+test_year(_Y) -> 0.
+
+-spec 'emoji_function🤯'(integer()) -> integer().
+'emoji_function🤯'(_) -> 0.
+
+-spec map_parameter_function(Map) -> boolean() when
+ Map :: #{a => 1, b => 2, c => 3, d => error}.
+map_parameter_function(_) -> false.
+-spec map_parameter_function(Map, any()) -> boolean() when
+ Map :: #{a => 1, b => 2, c => 3, d => error}.
+map_parameter_function(_,_) -> false.
+
+-spec binary_parameter_function(binary(), any()) -> boolean().
+binary_parameter_function(_,_) -> false.
+
+-spec tuple_parameter_function(tuple(), any()) -> boolean().
+tuple_parameter_function(_,_) -> false.
+
+-spec list_parameter_function(list(), any()) -> boolean().
+list_parameter_function(_,_) -> false.
+
+-spec non_empty_list_parameter_function(nonempty_list(), any()) -> boolean().
+non_empty_list_parameter_function(_,_) -> false.
+
+-spec integer_parameter_function(integer(), any()) -> boolean().
+integer_parameter_function(_,_) -> false.
+
+-spec non_neg_integer_parameter_function(non_neg_integer(), any()) -> boolean().
+non_neg_integer_parameter_function(_,_) -> false.
+
+-spec neg_integer_parameter_function(neg_integer(), any()) -> boolean().
+neg_integer_parameter_function(_,_) -> false.
+
+-spec float_parameter_function(float(), any()) -> boolean().
+float_parameter_function(_,_) -> false.
+
+-spec pid_parameter_function(pid(), any()) -> boolean().
+pid_parameter_function(_,_) -> false.
+
+-spec port_parameter_function(port(), any()) -> boolean().
+port_parameter_function(_,_) -> false.
+
+-spec record_parameter_function(A, any()) -> boolean() when
+ A :: #a_record{}.
+record_parameter_function(_,_) -> false.
+
+-spec function_parameter_function(fun((any()) -> any()), any()) -> boolean().
+function_parameter_function(_,_) -> false.
+
+-spec reference_parameter_function(reference(), any()) -> boolean().
+reference_parameter_function(_,_) -> false.
+
+-spec any_parameter_function(any(), any()) -> boolean().
+any_parameter_function(_,_) -> false.
+
+-spec atom_parameter_function(atom, any()) -> boolean().
+atom_parameter_function(_,_) -> false.
+
+-spec ann_type_parameter_function(V::atom(), W::any()) -> boolean().
+ann_type_parameter_function(_,_) -> false.
+
+-spec ann_type_parameter_function2(W::any(), V::atom()) -> boolean().
+ann_type_parameter_function2(_,_) -> false.
diff --git a/lib/stdlib/test/edlin_expand_SUITE_data/visible file b/lib/stdlib/test/edlin_expand_SUITE_data/visible file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/visible file
diff --git a/lib/stdlib/test/edlin_expand_SUITE_data/visible_file b/lib/stdlib/test/edlin_expand_SUITE_data/visible_file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/visible_file
diff --git a/lib/stdlib/test/edlin_expand_SUITE_data/visible😀_file b/lib/stdlib/test/edlin_expand_SUITE_data/visible😀_file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/visible😀_file
diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl
index faaa9f727f..af6802254c 100644
--- a/lib/stdlib/test/erl_eval_SUITE.erl
+++ b/lib/stdlib/test/erl_eval_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -55,7 +55,9 @@
otp_14708/1,
otp_16545/1,
otp_16865/1,
- eep49/1]).
+ eep49/1,
+ binary_and_map_aliases/1,
+ eep58/1]).
%%
%% Define to run outside of test server
@@ -96,7 +98,7 @@ all() ->
otp_8133, otp_10622, otp_13228, otp_14826,
funs, custom_stacktrace, try_catch, eval_expr_5, zero_width,
eep37, eep43, otp_15035, otp_16439, otp_14708, otp_16545, otp_16865,
- eep49].
+ eep49, binary_and_map_aliases, eep58].
groups() ->
[].
@@ -1971,6 +1973,64 @@ eep49(Config) when is_list(Config) ->
{else_clause,simply_wrong}),
ok.
+%% GH-6348/OTP-18297: Lift restrictions for matching of binaries and maps.
+binary_and_map_aliases(Config) when is_list(Config) ->
+ check(fun() ->
+ <<A:16>> = <<B:8,C:8>> = <<16#cafe:16>>,
+ {A,B,C}
+ end,
+ "begin <<A:16>> = <<B:8,C:8>> = <<16#cafe:16>>, {A,B,C} end.",
+ {16#cafe,16#ca,16#fe}),
+ check(fun() ->
+ <<A:8/bits,B:24/bits>> =
+ <<C:16,D:16>> =
+ <<E:8,F:8,G:8,H:8>> =
+ <<16#abcdef57:32>>,
+ {A,B,C,D,E,F,G,H}
+ end,
+ "begin <<A:8/bits,B:24/bits>> =
+ <<C:16,D:16>> =
+ <<E:8,F:8,G:8,H:8>> =
+ <<16#abcdef57:32>>,
+ {A,B,C,D,E,F,G,H}
+ end.",
+ {<<16#ab>>,<<16#cdef57:24>>, 16#abcd,16#ef57, 16#ab,16#cd,16#ef,16#57}),
+ check(fun() ->
+ #{K := V} = #{k := K} = #{k => my_key, my_key => 42},
+ V
+ end,
+ "begin #{K := V} = #{k := K} = #{k => my_key, my_key => 42}, V end.",
+ 42),
+ ok.
+
+%% EEP 58: Map comprehensions.
+eep58(Config) when is_list(Config) ->
+ check(fun() -> X = 32, #{X => X*X || X <- [1,2,3]} end,
+ "begin X = 32, #{X => X*X || X <- [1,2,3]} end.",
+ #{1 => 1, 2 => 4, 3 => 9}),
+ check(fun() ->
+ K = V = none,
+ #{K => V*V || K := V <- #{1 => 1, 2 => 2, 3 => 3}}
+ end,
+ "begin K = V = none, #{K => V*V || K := V <- #{1 => 1, 2 => 2, 3 => 3}} end.",
+ #{1 => 1, 2 => 4, 3 => 9}),
+ check(fun() ->
+ #{K => V*V || K := V <- maps:iterator(#{1 => 1, 2 => 2, 3 => 3})}
+ end,
+ "#{K => V*V || K := V <- maps:iterator(#{1 => 1, 2 => 2, 3 => 3})}.",
+ #{1 => 1, 2 => 4, 3 => 9}),
+ check(fun() -> << <<K:8,V:24>> || K := V <- #{42 => 7777} >> end,
+ "<< <<K:8,V:24>> || K := V <- #{42 => 7777} >>.",
+ <<42:8,7777:24>>),
+ check(fun() -> [X || X := X <- #{a => 1, b => b}] end,
+ "[X || X := X <- #{a => 1, b => b}].",
+ [b]),
+
+ error_check("[K+V || K := V <- a].", {bad_generator,a}),
+ error_check("[K+V || K := V <- [-1|#{}]].", {bad_generator,[-1|#{}]}),
+
+ ok.
+
%% Check the string in different contexts: as is; in fun; from compiled code.
check(F, String, Result) ->
check1(F, String, Result),
diff --git a/lib/stdlib/test/erl_expand_records_SUITE.erl b/lib/stdlib/test/erl_expand_records_SUITE.erl
index ea5cc4a354..13aaf0abdb 100644
--- a/lib/stdlib/test/erl_expand_records_SUITE.erl
+++ b/lib/stdlib/test/erl_expand_records_SUITE.erl
@@ -39,7 +39,7 @@
-export([attributes/1, expr/1, guard/1,
init/1, pattern/1, strict/1, update/1,
otp_5915/1, otp_7931/1, otp_5990/1,
- otp_7078/1, otp_7101/1, maps/1,
+ otp_7078/1, maps/1,
side_effects/1]).
init_per_testcase(_Case, Config) ->
@@ -59,7 +59,7 @@ all() ->
groups() ->
[{tickets, [],
- [otp_5915, otp_7931, otp_5990, otp_7078, otp_7101]}].
+ [otp_5915, otp_7931, otp_5990, otp_7078]}].
init_per_suite(Config) ->
Config.
@@ -719,67 +719,8 @@ otp_7078(Config) when is_list(Config) ->
run(Config, Ts, [strict_record_tests]),
ok.
--record(otp_7101, {a,b,c=[],d=[],e=[]}).
-
id(I) -> I.
-%% OTP-7101. Record update: more than one call to setelement/3.
-otp_7101(Config) when is_list(Config) ->
- %% Ensure the compiler won't do any funny constant propagation tricks.
- id(#otp_7101{a=a,b=b,c=c,d=d,e=e}),
- Rec = id(#otp_7101{}),
-
- %% Spawn a tracer process to count the number of setelement/3 calls.
- %% The tracer will forward all trace messages to us.
- Self = self(),
- Tracer = spawn_link(fun() -> otp_7101_tracer(Self, 0) end),
- 1 = erlang:trace_pattern({erlang,setelement,3}, true),
- erlang:trace(self(), true, [{tracer,Tracer},call]),
-
- %% Update the record.
- #otp_7101{a=2,b=1,c=[],d=[],e=[]} = otp_7101_update1(Rec),
- #otp_7101{a=1,b=2,c=[],d=[],e=[]} = otp_7101_update2(Rec),
- #otp_7101{a=2,b=1,c=[],d=[],e=[]} = otp_7101_update3(Rec),
- #otp_7101{a=1,b=2,c=[],d=[],e=[]} = otp_7101_update4(Rec),
-
- %% Verify that setelement/3 was called the same number of times as
- %% the number of record updates.
- Ref = erlang:trace_delivered(Self),
- receive
- {trace_delivered, Self, Ref} ->
- Tracer ! done
- end,
- 1 = erlang:trace_pattern({erlang,setelement,3}, false),
- receive
- 4 ->
- ok;
- Other ->
- ct:fail({unexpected,Other})
- end.
-
-otp_7101_tracer(Parent, N) ->
- receive
- {trace,Parent,call,{erlang,setelement,[_,_,_]}} ->
- otp_7101_tracer(Parent, N+1);
- done ->
- Parent ! N
- end.
-
-otp_7101_update1(R) ->
- R#otp_7101{b=1,
- a=2}.
-
-otp_7101_update2(R) ->
- R#otp_7101{a=1,
- b=2}.
-
-otp_7101_update3(R) ->
- R#otp_7101{b=1,a=2}.
-
-otp_7101_update4(R) ->
- R#otp_7101{a=1,b=2}.
-
-
-record(side_effects, {a,b,c}).
%% Make sure that the record expression is only evaluated once.
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index 770e12e3f0..81bc3e9a0d 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -35,7 +35,8 @@
-export([all/0, suite/0, groups/0]).
--export([unused_vars_warn_basic/1,
+-export([singleton_type_var_errors/1,
+ unused_vars_warn_basic/1,
unused_vars_warn_lc/1,
unused_vars_warn_rec/1,
unused_vars_warn_fun/1,
@@ -49,7 +50,8 @@
unsafe_vars_try/1,
unsized_binary_in_bin_gen_pattern/1,
guard/1, otp_4886/1, otp_4988/1, otp_5091/1, otp_5276/1, otp_5338/1,
- otp_5362/1, otp_5371/1, otp_7227/1, otp_5494/1, otp_5644/1, otp_5878/1,
+ otp_5362/1, otp_5371/1, otp_7227/1, binary_aliases/1,
+ otp_5494/1, otp_5644/1, otp_5878/1,
otp_5917/1, otp_6585/1, otp_6885/1, otp_10436/1, otp_11254/1,
otp_11772/1, otp_11771/1, otp_11872/1,
export_all/1,
@@ -78,7 +80,9 @@
underscore_match/1,
unused_record/1,
unused_type2/1,
- eep49/1]).
+ eep49/1,
+ redefined_builtin_type/1,
+ tilde_k/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -90,7 +94,7 @@ all() ->
unsafe_vars, unsafe_vars2, unsafe_vars_try, guard,
unsized_binary_in_bin_gen_pattern,
otp_4886, otp_4988, otp_5091, otp_5276, otp_5338,
- otp_5362, otp_5371, otp_7227, otp_5494, otp_5644,
+ otp_5362, otp_5371, otp_7227, binary_aliases, otp_5494, otp_5644,
otp_5878, otp_5917, otp_6585, otp_6885, otp_10436, otp_11254,
otp_11772, otp_11771, otp_11872, export_all,
bif_clash, behaviour_basic, behaviour_multiple, otp_11861,
@@ -106,7 +110,10 @@ all() ->
no_load_nif,
inline_nifs, warn_missing_spec, otp_16824,
underscore_match, unused_record, unused_type2,
- eep49].
+ eep49,
+ redefined_builtin_type,
+ tilde_k,
+ singleton_type_var_errors].
groups() ->
[{unused_vars_warn, [],
@@ -166,7 +173,13 @@ c(A) ->
g({M, F}) -> (Z=M):(Z=F)();
g({M, F, Arg}) -> (Z=M):F(Z=Arg).
h(X, Y) -> (Z=X) + (Z=Y).">>,
- [warn_unused_vars], []}],
+ [warn_unused_vars], []},
+ {basic3,
+ <<"f(E) ->
+ X = Y = E.">>,
+ [warn_unused_vars],
+ {warnings,[{{2,19},erl_lint,{unused_var,'X'}},
+ {{2,23},erl_lint,{unused_var,'Y'}}]}}],
[] = run(Config, Ts),
ok.
@@ -297,7 +310,7 @@ unused_vars_warn_lc(Config) when is_list(Config) ->
j(X) ->
[foo || X <- X, % X shadowed.
X <- % X shadowed. X unused.
- X =
+ X =
Y = [[1,2,3]], % Y unused.
X <- [], % X shadowed.
X <- X]. % X shadowed. X unused.
@@ -892,6 +905,126 @@ unused_import(Config) when is_list(Config) ->
[] = run(Config, Ts),
ok.
+%% Test singleton type variables
+singleton_type_var_errors(Config) when is_list(Config) ->
+ Ts = [{singleton_error1,
+ <<"-spec test_singleton_typevars_in_union(Opts) -> term() when
+ Opts :: {ok, Unknown} | {error, Unknown}.
+ test_singleton_typevars_in_union(_) ->
+ error.
+ ">>,
+ [],
+ {warnings,[{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]}},
+
+ {singleton_error2,
+ <<"-spec test_singleton_list_typevars_in_union([Opts]) -> term() when
+ Opts :: {ok, Unknown} | {error, Unknown}.
+ test_singleton_list_typevars_in_union(_) ->
+ error.">>,
+ [],
+ {warnings,[{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]}},
+
+ {singleton_error3,
+ <<"-spec test_singleton_list_typevars_in_list([Opts]) -> term() when
+ Opts :: {ok, Unknown}.
+ test_singleton_list_typevars_in_list(_) ->
+ error.">>,
+ [],
+ {errors,
+ [{{2,36},erl_lint,{singleton_typevar,'Unknown'}}],[]}},
+
+ {singleton_error4,
+ <<"-spec test_singleton_list_typevars_in_list_with_type_subst([{ok, Unknown}]) -> term().
+ test_singleton_list_typevars_in_list_with_type_subst(_) ->
+ error.">>,
+ [],
+ {errors,[{{1,86},erl_lint,{singleton_typevar,'Unknown'}}],[]}},
+
+ {singleton_error5,
+ <<"-spec test_singleton_buried_typevars_in_union(Opts) -> term() when
+ Opts :: {ok, Foo} | {error, Foo},
+ Foo :: {true, X} | {false, X}.
+ test_singleton_buried_typevars_in_union(_) ->
+ error.">>,
+ [],
+ {warnings,[{{3,38},erl_lint,{singleton_typevar,'X'}}]}},
+
+ {singleton_error6,
+ <<"-spec test_multiple_subtypes_to_same_typevar(Opts) -> term() when
+ Opts :: {Foo, Bar} | Y,
+ Foo :: X,
+ Bar :: X,
+ Y :: Z.
+ test_multiple_subtypes_to_same_typevar(_) ->
+ error.">>,
+ [],
+ {errors,[{{5,31},erl_lint,{singleton_typevar,'Z'}}],[]}},
+
+ {singleton_error7,
+ <<"-spec test_duplicate_non_terminal_var_in_union(Opts) -> term() when
+ Opts :: {ok, U, U} | {error, U, U},
+ U :: Foo.
+ test_duplicate_non_terminal_var_in_union(_) ->
+ error.">>,
+ [],
+ {errors,[{{3,31},erl_lint,{singleton_typevar,'Foo'}}],[]}},
+
+ {singleton_error8,
+ <<"-spec test_unused_outside_union(Opts) -> term() when
+ Unused :: Unknown,
+ A :: Unknown,
+ Opts :: {Unknown | A}.
+ test_unused_outside_union(_) ->
+ error.">>,
+ [],
+ {errors,[{{2,21},erl_lint,{singleton_typevar,'Unused'}}],[]}},
+
+ {singleton_disabled_warning,
+ <<"-spec test_singleton_typevars_in_union(Opts) -> term() when
+ Opts :: {ok, Unknown} | {error, Unknown}.
+ test_singleton_typevars_in_union(_) ->
+ error.
+ ">>,
+ [nowarn_singleton_typevar],
+ []},
+
+ {singleton_ok1,
+ <<"-spec test_multiple_occurrences_singleton(Opts) -> term() when
+ Opts :: {Foo, Foo}.
+ test_multiple_occurrences_singleton(_) ->
+ ok.">>,
+ [],
+ []},
+
+ {singleton_ok2,
+ <<"-spec id(X) -> X.
+ id(X) ->
+ X.">>,
+ [],
+ []},
+
+ {singleton_ok3,
+ <<"-spec ok(Opts) -> term() when
+ Opts :: {Unknown, {ok, Unknown} | {error, Unknown}}.
+ ok(_) ->
+ error.">>,
+ [],
+ []},
+
+ {singleton_ok4,
+ <<"-spec ok(Opts) -> term() when
+ Union :: {ok, Unknown} | {error, Unknown},
+ Opts :: {{tag, Unknown} | Union}.
+ ok(_) ->
+ error.">>,
+ [],
+ []}
+
+ ],
+
+ [] = run(Config, Ts),
+ ok.
+
%% Test warnings for unused functions.
unused_function(Config) when is_list(Config) ->
Ts = [{func1,
@@ -966,13 +1099,13 @@ binary_types(Config) when is_list(Config) ->
Ts = [{binary1,
<<"-type nonempty_binary() :: term().">>,
[nowarn_unused_type],
- {errors,[{{1,22},erl_lint,
- {builtin_type,{nonempty_binary,0}}}],[]}},
+ {warnings,[{{1,22},erl_lint,
+ {redefine_builtin_type,{nonempty_binary,0}}}]}},
{binary2,
<<"-type nonempty_bitstring() :: term().">>,
[nowarn_unused_type],
- {errors,[{{1,22},erl_lint,
- {builtin_type,{nonempty_bitstring,0}}}],[]}}],
+ {warnings,[{{1,22},erl_lint,
+ {redefine_builtin_type,{nonempty_bitstring,0}}}]}}],
[] = run(Config, Ts),
ok.
@@ -2296,22 +2429,22 @@ otp_15456(Config) when is_list(Config) ->
ok.
%% OTP-5371. Aliases for bit syntax expressions are no longer allowed.
+%% GH-6348/OTP-18297: Updated for OTP 26 to allow aliases.
otp_5371(Config) when is_list(Config) ->
Ts = [{otp_5371_1,
<<"t(<<A:8>> = <<B:8>>) ->
{A,B}.
">>,
[],
- {errors,[{{1,23},erl_lint,illegal_bin_pattern}],[]}},
+ []},
{otp_5371_2,
<<"x([<<A:8>>] = [<<B:8>>]) ->
{A,B}.
y({a,<<A:8>>} = {b,<<B:8>>}) ->
{A,B}.
">>,
- [],
- {errors,[{{1,24},erl_lint,illegal_bin_pattern},
- {{3,20},erl_lint,illegal_bin_pattern}],[]}},
+ [],
+ {warnings,[{{3,15},v3_core,{nomatch,pattern}}]}},
{otp_5371_3,
<<"-record(foo, {a,b,c}).
-record(bar, {x,y,z}).
@@ -2328,11 +2461,10 @@ otp_5371(Config) when is_list(Config) ->
{X,Y}.
">>,
[],
- {errors,[{{4,26},erl_lint,illegal_bin_pattern},
- {{6,26},erl_lint,illegal_bin_pattern},
- {{8,26},erl_lint,illegal_bin_pattern},
- {{10,30},erl_lint,illegal_bin_pattern},
- {{12,30},erl_lint,illegal_bin_pattern}],[]}},
+ {warnings,[{{4,15},v3_core,{nomatch,pattern}},
+ {{8,15},v3_core,{nomatch,pattern}},
+ {{10,15},v3_core,{nomatch,pattern}},
+ {{12,15},v3_core,{nomatch,pattern}}]}},
{otp_5371_4,
<<"-record(foo, {a,b,c}).
-record(bar, {x,y,z}).
@@ -2352,42 +2484,41 @@ otp_5371(Config) when is_list(Config) ->
[] = run(Config, Ts),
ok.
-%% OTP_7227. Some aliases for bit syntax expressions were still allowed.
+%% OTP-7227. Some aliases for bit syntax expressions were still allowed.
+%% GH-6348/OTP-18297: Updated for OTP 26 to allow aliases.
otp_7227(Config) when is_list(Config) ->
Ts = [{otp_7227_1,
<<"t([<<A:8>> = {C,D} = <<B:8>>]) ->
{A,B,C,D}.
">>,
[],
- {errors,[{{1,42},erl_lint,illegal_bin_pattern}],[]}},
+ {warnings,[{{1,21},v3_core,{nomatch,pattern}}]}},
{otp_7227_2,
<<"t([(<<A:8>> = {C,D}) = <<B:8>>]) ->
{A,B,C,D}.
">>,
[],
- {errors,[{{1,25},erl_lint,illegal_bin_pattern}],[]}},
+ {warnings,[{{1,21},v3_core,{nomatch,pattern}}]}},
{otp_7227_3,
<<"t([(<<A:8>> = {C,D}) = (<<B:8>> = <<C:8>>)]) ->
{A,B,C,D}.
">>,
[],
- {errors,[{{1,45},erl_lint,illegal_bin_pattern},
- {{1,45},erl_lint,illegal_bin_pattern},
- {{1,55},erl_lint,illegal_bin_pattern}],[]}},
+ {warnings,[{{1,21},v3_core,{nomatch,pattern}}]}},
{otp_7227_4,
<<"t(Val) ->
<<A:8>> = <<B:8>> = Val,
{A,B}.
">>,
[],
- {errors,[{{2,19},erl_lint,illegal_bin_pattern}],[]}},
+ []},
{otp_7227_5,
<<"t(Val) ->
<<A:8>> = X = <<B:8>> = Val,
{A,B,X}.
">>,
[],
- {errors,[{{2,19},erl_lint,illegal_bin_pattern}],[]}},
+ []},
{otp_7227_6,
<<"t(X, Y) ->
<<A:8>> = <<X:4,Y:4>>,
@@ -2401,27 +2532,70 @@ otp_7227(Config) when is_list(Config) ->
{A,B,X}.
">>,
[],
- {errors,[{{2,36},erl_lint,illegal_bin_pattern},
- {{2,36},erl_lint,illegal_bin_pattern},
- {{2,46},erl_lint,illegal_bin_pattern}],[]}},
- {otp_7227_8,
+ []},
+ {otp_7227_8,
<<"t(Val) ->
(<<A:8>> = X) = (Y = <<B:8>>) = Val,
{A,B,X,Y}.
">>,
[],
- {errors,[{{2,40},erl_lint,illegal_bin_pattern}],[]}},
+ []},
{otp_7227_9,
<<"t(Val) ->
(Z = <<A:8>> = X) = (Y = <<B:8>> = W) = Val,
{A,B,X,Y,Z,W}.
">>,
[],
- {errors,[{{2,44},erl_lint,illegal_bin_pattern}],[]}}
+ []}
],
[] = run(Config, Ts),
ok.
+%% GH-6348/OTP-18297: Allow aliases of binary patterns.
+binary_aliases(Config) when is_list(Config) ->
+ Ts = [{binary_aliases_1,
+ <<"t([<<Size:8,_/bits>> = <<_:8,Data:Size/bits>>]) ->
+ Data.
+ ">>,
+ [],
+ {errors,[{{1,55},erl_lint,{unbound_var,'Size'}}],[]}},
+ {binary_aliases_2,
+ <<"t(#{key := <<Size:8,_/bits>>} = #{key := <<_:8,Data:Size/bits>>}) ->
+ Data.
+ ">>,
+ [],
+ {errors,[{{1,73},erl_lint,{unbound_var,'Size'}}],[]}},
+ {binary_aliases_3,
+ <<"t(<<_:8,Data:Size/bits>> = <<Size:8,_/bits>>) ->
+ Data.
+ ">>,
+ [],
+ {errors,[{{1,34},erl_lint,{unbound_var,'Size'}}],[]}},
+ {binary_aliases_4,
+ <<"t([<<_:8,Data:Size/bits>> = <<Size:8,_/bits>>]) ->
+ Data.
+ ">>,
+ [],
+ {errors,[{{1,35},erl_lint,{unbound_var,'Size'}}],[]}},
+ {binary_aliases_5,
+ <<"t(Bin) ->
+ <<_:8,A:Size>> = <<_:8,B:Size/bits>> = <<Size:8,_/bits>> = Bin,
+ {A,B,Size}.
+ ">>,
+ [],
+ []},
+ {binary_aliases_6,
+ <<"t(<<_:8,A:Size>> = <<_:8,B:Size/bits>> = <<Size:8,_/bits>>) ->
+ {A,B,Size}.
+ ">>,
+ [],
+ {errors,[{{1,31},erl_lint,{unbound_var,'Size'}},
+ {{1,48},erl_lint,{unbound_var,'Size'}}],
+ []}}
+ ],
+ [] = run(Config, Ts),
+ ok.
+
%% OTP-5494. Warnings for functions exported more than once.
otp_5494(Config) when is_list(Config) ->
Ts = [{otp_5494_1,
@@ -2780,14 +2954,6 @@ otp_10436(Config) when is_list(Config) ->
{warnings,[{{4,14},erl_lint,{not_exported_opaque,{t2,0}}},
{{4,14},erl_lint,{unused_type,{t2,0}}}]} =
run_test2(Config, Ts, []),
- Ts2 = <<"-module(otp_10436_2).
- -export_type([t1/0, t2/0]).
- -opaque t1() :: term().
- -opaque t2() :: any().
- ">>,
- {warnings,[{{3,15},erl_lint,{underspecified_opaque,{t1,0}}},
- {{4,15},erl_lint,{underspecified_opaque,{t2,0}}}]} =
- run_test2(Config, Ts2, []),
ok.
%% OTP-11254. M:F/A could crash the linter.
@@ -2822,9 +2988,9 @@ otp_11772(Config) when is_list(Config) ->
t() ->
1.
">>,
- {errors,[{{7,14},erl_lint,{builtin_type,{node,0}}},
- {{8,14},erl_lint,{builtin_type,{mfa,0}}}],
- []} = run_test2(Config, Ts, []),
+ {warnings,[{{7,14},erl_lint,{redefine_builtin_type,{node,0}}},
+ {{8,14},erl_lint,{redefine_builtin_type,{mfa,0}}}]} =
+ run_test2(Config, Ts, []),
ok.
%% OTP-11771. Do not allow redefinition of the types arity(_) &c..
@@ -2847,11 +3013,11 @@ otp_11771(Config) when is_list(Config) ->
t() ->
1.
">>,
- {errors,[{{7,14},erl_lint,{builtin_type,{arity,0}}},
- {{8,14},erl_lint,{builtin_type,{bitstring,0}}},
- {{9,14},erl_lint,{builtin_type,{iodata,0}}},
- {{10,14},erl_lint,{builtin_type,{boolean,0}}}],
- []} = run_test2(Config, Ts, []),
+ {warnings,[{{7,14},erl_lint,{redefine_builtin_type,{arity,0}}},
+ {{8,14},erl_lint,{redefine_builtin_type,{bitstring,0}}},
+ {{9,14},erl_lint,{redefine_builtin_type,{iodata,0}}},
+ {{10,14},erl_lint,{redefine_builtin_type,{boolean,0}}}]} =
+ run_test2(Config, Ts, []),
ok.
%% OTP-11872. The type map() undefined when exported.
@@ -2863,15 +3029,16 @@ otp_11872(Config) when is_list(Config) ->
-export_type([map/0, product/0]).
- -opaque map() :: dict().
+ -opaque map() :: unknown_type().
-spec t() -> map().
t() ->
1.
">>,
- {errors,[{{6,14},erl_lint,{undefined_type,{product,0}}},
- {{8,14},erl_lint,{builtin_type,{map,0}}}], []} =
+ {error,[{{6,14},erl_lint,{undefined_type,{product,0}}},
+ {{8,30},erl_lint,{undefined_type,{unknown_type,0}}}],
+ [{{8,14},erl_lint,{redefine_builtin_type,{map,0}}}]} =
run_test2(Config, Ts, []),
ok.
@@ -3881,35 +4048,61 @@ maps_type(Config) when is_list(Config) ->
t(M) -> M.
">>,
[],
- {errors,[{{3,7},erl_lint,{builtin_type,{map,0}}}],[]}}],
+ {warnings,[{{3,7},erl_lint,{redefine_builtin_type,{map,0}}}]}}],
[] = run(Config, Ts),
ok.
+%% GH-6348/OTP-18297: In OTP 26 parallel matching of maps
+%% has been extended.
maps_parallel_match(Config) when is_list(Config) ->
- Ts = [{parallel_map_patterns_unbound1,
+ Ts = [{parallel_map_patterns_unbound,
<<"
t(#{} = M) ->
- #{K := V} = #{k := K} = M,
+ #{k := K} = #{K := V} = M,
V.
">>,
[],
- {errors,[{{3,18},erl_lint,{unbound_var,'K'}}],[]}},
- {parallel_map_patterns_unbound2,
+ {errors,[{{3,30},erl_lint,{unbound_var,'K'}}],[]}},
+ {parallel_map_patterns_not_toplevel1,
<<"
t(#{} = M) ->
+ [#{K1 := V1} =
+ #{K2 := V2} =
+ #{k1 := K1,k2 := K2}] = [M],
+ [V1,V2].
+ ">>,
+ [],
+ {errors,[{{3,19},erl_lint,{unbound_var,'K1'}},
+ {{4,19},erl_lint,{unbound_var,'K2'}}],[]}},
+ {parallel_map_patterns_unbound_not_toplevel2,
+ <<"
+ t(#{} = M) ->
+ [#{k := K} = #{K := V}] = [M],
+ V.
+ ">>,
+ [],
+ {errors,[{{3,31},erl_lint,{unbound_var,'K'}}],[]}},
+ {parallel_map_patterns_bound1,
+ <<"
+ t(#{} = M,K1,K2) ->
#{K1 := V1} =
#{K2 := V2} =
#{k1 := K1,k2 := K2} = M,
[V1,V2].
">>,
[],
- {errors,[{{3,18},erl_lint,{unbound_var,'K1'}},
- {{3,18},erl_lint,{unbound_var,'K1'}},
- {{4,18},erl_lint,{unbound_var,'K2'}},
- {{4,18},erl_lint,{unbound_var,'K2'}}],[]}},
- {parallel_map_patterns_bound,
+ []},
+ {parallel_map_patterns_bound2,
<<"
- t(#{} = M,K1,K2) ->
+ t(#{} = M) ->
+ #{K := V} = #{k := K} = M,
+ V.
+ ">>,
+ [],
+ []},
+ {parallel_map_patterns_bound3,
+ <<"
+ t(#{} = M) ->
#{K1 := V1} =
#{K2 := V2} =
#{k1 := K1,k2 := K2} = M,
@@ -4248,8 +4441,8 @@ otp_14323(Config) ->
-dialyzer(nowarn_function). % unknown option
-dialyzer(1). % badly formed
- -dialyzer(malformed). % unkonwn option
- -dialyzer({malformed,f/0}). % unkonwn option
+ -dialyzer(malformed). % unknown option
+ -dialyzer({malformed,f/0}). % unknown option
-dialyzer({nowarn_function,a/1}). % undefined function
-dialyzer({nowarn_function,{a,-1}}). % badly formed
@@ -4849,6 +5042,151 @@ eep49(Config) when is_list(Config) ->
[] = run(Config, Ts),
ok.
+%% GH-6132: Allow local redefinition of types.
+redefined_builtin_type(Config) ->
+ Ts = [{redef1,
+ <<"-type nonempty_binary() :: term().
+ -type map() :: {_,_}.">>,
+ [nowarn_unused_type,
+ nowarn_redefined_builtin_type],
+ []},
+ {redef2,
+ <<"-type nonempty_bitstring() :: term().
+ -type map() :: {_,_}.">>,
+ [nowarn_unused_type,
+ {nowarn_redefined_builtin_type,{map,0}}],
+ {warnings,[{{1,22},erl_lint,
+ {redefine_builtin_type,{nonempty_bitstring,0}}}]}},
+ {redef3,
+ <<"-compile({nowarn_redefined_builtin_type,{map,0}}).
+ -compile({nowarn_redefined_builtin_type,[{nonempty_bitstring,0}]}).
+ -type nonempty_bitstring() :: term().
+ -type map() :: {_,_}.
+ -type list() :: erlang:map().">>,
+ [nowarn_unused_type,
+ {nowarn_redefined_builtin_type,{map,0}}],
+ {warnings,[{{5,16},erl_lint,
+ {redefine_builtin_type,{list,0}}}]}},
+ {redef4,
+ <<"-type tuple() :: 'tuple'.
+ -type map() :: 'map'.
+ -type list() :: 'list'.
+ -spec t(tuple() | map()) -> list().
+ t(_) -> ok.
+ ">>,
+ [],
+ {warnings,[{{1,22},erl_lint,{redefine_builtin_type,{tuple,0}}},
+ {{2,16},erl_lint,{redefine_builtin_type,{map,0}}},
+ {{3,16},erl_lint,{redefine_builtin_type,{list,0}}}
+ ]}},
+ {redef5,
+ <<"-type atom() :: 'atom'.
+ -type integer() :: 'integer'.
+ -type reference() :: 'ref'.
+ -type pid() :: 'pid'.
+ -type port() :: 'port'.
+ -type float() :: 'float'.
+ -type iodata() :: 'iodata'.
+ -type ref_set() :: gb_sets:set(reference()).
+ -type pid_map() :: #{pid() => port()}.
+ -type atom_int_fun() :: fun((atom()) -> integer()).
+ -type collection(Type) :: {'collection', Type}.
+ -callback b1(I :: iodata()) -> atom().
+ -spec t(collection(float())) -> {pid_map(), ref_set(), atom_int_fun()}.
+ t(_) -> ok.
+ ">>,
+ [],
+ {warnings,[{{1,22},erl_lint,{redefine_builtin_type,{atom,0}}},
+ {{2,16},erl_lint,{redefine_builtin_type,{integer,0}}},
+ {{3,16},erl_lint,{redefine_builtin_type,{reference,0}}},
+ {{4,16},erl_lint,{redefine_builtin_type,{pid,0}}},
+ {{5,16},erl_lint,{redefine_builtin_type,{port,0}}},
+ {{6,16},erl_lint,{redefine_builtin_type,{float,0}}},
+ {{7,16},erl_lint,{redefine_builtin_type,{iodata,0}}}
+ ]}},
+ {redef6,
+ <<"-spec bar(function()) -> bar().
+ bar({function, F}) -> F().
+ -type function() :: {function, fun(() -> bar())}.
+ -type bar() :: {bar, binary()}.
+ ">>,
+ [],
+ {warnings,[{{3,16},erl_lint,
+ {redefine_builtin_type,{function,0}}}]}},
+ {redef7,
+ <<"-type function() :: {function, fun(() -> bar())}.
+ -type bar() :: {bar, binary()}.
+ -spec bar(function()) -> bar().
+ bar({function, F}) -> F().
+ ">>,
+ [],
+ {warnings,[{{1,22},erl_lint,
+ {redefine_builtin_type,{function,0}}}]}},
+ {redef8,
+ <<"-type function() :: {function, fun(() -> atom())}.
+ ">>,
+ [],
+ {warnings,[{{1,22},erl_lint,
+ {redefine_builtin_type,{function,0}}},
+ {{1,22},erl_lint,
+ {unused_type,{function,0}}}]}},
+ {redef9,
+ <<"-spec foo() -> fun().
+ foo() -> fun() -> ok end.
+ ">>,
+ [],
+ []}
+ ],
+ [] = run(Config, Ts),
+ ok.
+
+tilde_k(Config) ->
+ Ts = [{tilde_k_1,
+ <<"t(Map) ->
+ io:format(\"~kp\n\", [Map]),
+ io:format(\"~kP\n\", [Map,10]),
+ io:format(\"~kw\n\", [Map]),
+ io:format(\"~kW\n\", [Map,5]),
+ io:format(\"~tkp\n\", [Map]),
+ io:format(\"~klp\n\", [Map]),
+ RevCmpFun = fun erlang:'>='/2,
+ io:format(\"~Kp\n\", [RevCmpFun,Map]),
+ io:format(\"~KP\n\", [RevCmpFun,Map,10]),
+ io:format(\"~Kw\n\", [RevCmpFun,Map]),
+ io:format(\"~KW\n\", [RevCmpFun,Map,5]),
+ ok.">>,
+ [],
+ []},
+ {tilde_k_2,
+ <<"t(Map) ->
+ io:format(\"~kkp\n\", [Map]),
+ io:format(\"~kKp\n\", [Map]),
+ io:format(\"~ks\n\", [Map]),
+ ok.">>,
+ [],
+ {warnings,
+ [{{2,29},
+ erl_lint,
+ {format_error,{"format string invalid (~ts)",
+ ["repeated modifier k"]}}},
+ {{4,29},
+ erl_lint,
+ {format_error,{"format string invalid (~ts)",
+ ["conflicting modifiers ~Kkp"]}}},
+ {{6,29},
+ erl_lint,
+ {format_error,{"format string invalid (~ts)",
+ ["invalid modifier/control combination ~ks"]}}}]}
+ }
+ ],
+ [] = run(Config, Ts),
+
+ ok.
+
+%%%
+%%% Common utilities.
+%%%
+
format_error(E) ->
lists:flatten(erl_lint:format_error(E)).
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
index c8c1a206ca..ef021aa691 100644
--- a/lib/stdlib/test/erl_pp_SUITE.erl
+++ b/lib/stdlib/test/erl_pp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -56,7 +56,7 @@
otp_10302/1, otp_10820/1, otp_11100/1, otp_11861/1, pr_1014/1,
otp_13662/1, otp_14285/1, otp_15592/1, otp_15751/1, otp_15755/1,
otp_16435/1, gh_5093/1,
- eep49/1]).
+ eep49/1, eep58/1]).
%% Internal export.
-export([ehook/6]).
@@ -88,7 +88,7 @@ groups() ->
otp_8473, otp_8522, otp_8567, otp_8664, otp_9147,
otp_10302, otp_10820, otp_11100, otp_11861, pr_1014, otp_13662,
otp_14285, otp_15592, otp_15751, otp_15755, otp_16435,
- gh_5093, eep49]}].
+ gh_5093, eep49, eep58]}].
init_per_suite(Config) ->
Config.
@@ -1393,6 +1393,27 @@ eep49(_Config) ->
" end.\n"),
ok.
+eep58(_Config) ->
+ assert_same("lc_map(Map) ->\n"
+ " [ \n"
+ " {K, V} ||\n"
+ " K := V <- Map\n"
+ " ].\n"),
+
+ assert_same("bc_map(Map) ->\n"
+ " << \n"
+ " <<K:32,V:32>> ||\n"
+ " K := V <- Map\n"
+ " >>.\n"),
+
+ assert_same("mc(Map) ->\n"
+ " #{ \n"
+ " K => V + 1 ||\n"
+ " K := V <- Map\n"
+ " }.\n"),
+
+ ok.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
compile(Config, Tests) ->
diff --git a/lib/stdlib/test/erl_scan_SUITE.erl b/lib/stdlib/test/erl_scan_SUITE.erl
index ee8bc8420f..96c68039ae 100644
--- a/lib/stdlib/test/erl_scan_SUITE.erl
+++ b/lib/stdlib/test/erl_scan_SUITE.erl
@@ -516,12 +516,17 @@ chars() ->
test_string(L, Ts)
end || C <- lists:seq(0, 255)],
- %% $\^\n now increments the line...
+ %% GH-6477. Test legal use of caret notation.
[begin
L = "$\\^" ++ [C],
- Ts = [{char,{1,1},C band 2#11111}],
+ Ts = case C of
+ $? ->
+ [{char,{1,1},127}];
+ _ ->
+ [{char,{1,1},C band 2#11111}]
+ end,
test_string(L, Ts)
- end || C <- lists:seq(0, 255)],
+ end || C <- lists:seq($?, $Z) ++ lists:seq($a, $z)],
[begin
L = "$\\" ++ [C],
@@ -672,10 +677,18 @@ illegal() ->
erl_scan:string(String, {1,1}),
{done,{error,{{1,4},erl_scan,{illegal,character}},{1,14}},"34\". "} =
erl_scan:tokens([], String++". ", {1,1}),
+
+ %% GH-6477. Test for illegal characters in caret notation.
+ _ = [begin
+ S = [$$,$\\,$^,C],
+ {error,{1,erl_scan,{illegal,character}},1} = erl_scan:string(S)
+ end || C <- lists:seq(0, 16#3e) ++ [16#60] ++ lists:seq($z+1, 16#10ffff)],
ok.
crashes() ->
{'EXIT',_} = (catch {foo, erl_scan:string([-1])}), % type error
+ {'EXIT',_} = (catch erl_scan:string("'a" ++ [999999999] ++ "c'")),
+
{'EXIT',_} = (catch {foo, erl_scan:string("$"++[-1])}),
{'EXIT',_} = (catch {foo, erl_scan:string("$\\"++[-1])}),
{'EXIT',_} = (catch {foo, erl_scan:string("$\\^"++[-1])}),
@@ -698,6 +711,7 @@ crashes() ->
(catch {foo, erl_scan:string("% foo"++[a],{1,1})}),
{'EXIT',_} = (catch {foo, erl_scan:string([3.0])}), % type error
+ {'EXIT',_} = (catch {foo, erl_scan:string("A" ++ [999999999])}),
ok.
@@ -867,11 +881,11 @@ unicode() ->
erl_scan:string([1089]),
{error,{{1,1},erl_scan,{illegal,character}},{1,2}} =
erl_scan:string([1089], {1,1}),
- {error,{{1,3},erl_scan,{illegal,character}},{1,4}} =
- erl_scan:string("'a" ++ [999999999] ++ "c'", {1,1}),
+ {error,{{1,1},erl_scan,{illegal,character}},{1,2}} =
+ erl_scan:string([16#D800], {1,1}),
test("\"a"++[1089]++"b\""),
- {ok,[{char,1,1}],1} =
+ {error,{1,erl_scan,{illegal,character}},1} =
erl_scan_string([$$,$\\,$^,1089], 1),
{error,{1,erl_scan,Error},1} =
@@ -908,7 +922,7 @@ unicode() ->
U3 = "\"a\n\\x{fff}\n\"",
{ok,[{string,1,[$a,$\n,$\x{fff},$\n]}],3} = erl_scan_string(U3, 1),
- U4 = "\"\\^\n\\x{aaa}\\^\n\"",
+ U4 = "\"\n\\x{aaa}\n\"",
{ok,[{string,1,[$\n,$\x{aaa},$\n]}],3} = erl_scan_string(U4, 1),
%% Keep these tests:
@@ -1023,7 +1037,7 @@ otp_10302(Config) when is_list(Config) ->
U3 = "\"a\n\\x{fff}\n\"",
{ok,[{string,1,[97,10,4095,10]}],3} = erl_scan_string(U3, 1),
- U4 = "\"\\^\n\\x{aaa}\\^\n\"",
+ U4 = "\"\n\\x{aaa}\n\"",
{ok,[{string,1,[10,2730,10]}],3} = erl_scan_string(U4, 1,[]),
Str1 = "\"ab" ++ [1089] ++ "cd\"",
diff --git a/lib/stdlib/test/error_logger_h_SUITE.erl b/lib/stdlib/test/error_logger_h_SUITE.erl
index d1f375459b..3dcbc2664a 100644
--- a/lib/stdlib/test/error_logger_h_SUITE.erl
+++ b/lib/stdlib/test/error_logger_h_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -66,17 +66,17 @@ logfile(Config) ->
analyse_events(Log, Ev, [AtNode], unlimited),
%% Make sure that the file_io_server process has been stopped
- [] = lists:filtermap(
- fun(X) ->
- case {process_info(X, [current_function]),
- file:pid2name(X)} of
- {[{current_function, {file_io_server, _, _}}],
- {ok,P2N = Log}} ->
- {true, {X, P2N}};
- _ ->
- false
- end
- end, processes()),
+ [] = lists:filtermap(fun(X) ->
+ case process_info(X, [current_function]) of
+ [{current_function, {file_io_server, _, _}}] ->
+ case file:pid2name(X) of
+ {ok, Log} -> {true, {X, Log}};
+ _ -> false
+ end;
+ _ ->
+ false
+ end
+ end, processes()),
peer:stop(Peer),
diff --git a/lib/stdlib/test/escript_SUITE_data/arg_overflow b/lib/stdlib/test/escript_SUITE_data/arg_overflow
index dd5accc051..e3138cabbd 100755
--- a/lib/stdlib/test/escript_SUITE_data/arg_overflow
+++ b/lib/stdlib/test/escript_SUITE_data/arg_overflow
@@ -1,5 +1,5 @@
#! /usr/bin/env escript
%% -*- erlang -*-
-%%!x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
+%%!-x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x
main(_) ->
halt(0).
diff --git a/lib/stdlib/test/escript_SUITE_data/linebuf_overflow b/lib/stdlib/test/escript_SUITE_data/linebuf_overflow
index 33133c1ce9..018be1f26d 100755
--- a/lib/stdlib/test/escript_SUITE_data/linebuf_overflow
+++ b/lib/stdlib/test/escript_SUITE_data/linebuf_overflow
@@ -1,5 +1,5 @@
#! /usr/bin/env escript
%% -*- erlang -*-
-%%!xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+%%!-v xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
main(_) ->
halt(0).
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index ffa088543d..52cf5b9e69 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -40,7 +40,7 @@
-export([tab2file/1, tab2file2/1, tabfile_ext1/1,
tabfile_ext2/1, tabfile_ext3/1, tabfile_ext4/1, badfile/1]).
-export([heavy_lookup/1, heavy_lookup_element/1, heavy_concurrent/1]).
--export([lookup_element_mult/1]).
+-export([lookup_element_mult/1, lookup_element_default/1]).
-export([foldl_ordered/1, foldr_ordered/1, foldl/1, foldr/1, fold_empty/1]).
-export([t_delete_object/1, t_init_table/1, t_whitebox/1,
select_bound_chunk/1, t_delete_all_objects/1, t_test_ms/1,
@@ -195,7 +195,7 @@ groups() ->
privacy]},
{insert, [], [empty, badinsert]},
{lookup, [], [badlookup, lookup_order]},
- {lookup_element, [], [lookup_element_mult]},
+ {lookup_element, [], [lookup_element_mult, lookup_element_default]},
{delete, [],
[delete_elem, delete_tab, delete_large_tab,
delete_large_named_table, evil_delete, table_leak,
@@ -2407,13 +2407,19 @@ update_element_do(Tab,Tuple,Key,UpdPos) ->
Big32 = 16#12345678,
Big64 = 16#123456789abcdef0,
- Values = { 623, -27, 0, Big32, -Big32, Big64, -Big64, Big32*Big32,
+ RefcBin = list_to_binary(lists:seq(1,100)),
+ BigMap1 = maps:from_list([{N,N} || N <- lists:seq(1,33)]),
+ BigMap2 = BigMap1#{key => RefcBin, RefcBin => value},
+ Values = { 623, -27, Big32, -Big32, Big64, -Big64, Big32*Big32,
-Big32*Big32, Big32*Big64, -Big32*Big64, Big64*Big64, -Big64*Big64,
"A", "Sverker", [], {12,-132}, {},
- <<45,232,0,12,133>>, <<234,12,23>>, list_to_binary(lists:seq(1,100)),
+ <<45,232,0,12,133>>, <<234,12,23>>, RefcBin,
(fun(X) -> X*Big32 end),
- make_ref(), make_ref(), self(), ok, update_element, 28, 29 },
- Length = size(Values),
+ make_ref(), make_ref(), self(), ok, update_element,
+ #{a => value, "hello" => "world", 1.0 => RefcBin },
+ BigMap1, BigMap2},
+ Length = tuple_size(Values),
+ 29 = Length,
PosValArgF = fun(ToIx, ResList, [Pos | PosTail], Rand, MeF) ->
NextIx = (ToIx+Rand) rem Length,
@@ -2433,7 +2439,12 @@ update_element_do(Tab,Tuple,Key,UpdPos) ->
true = ets:update_element(Tab, Key, PosValArg),
ArgHash = erlang:phash2({Tab,Key,PosValArg}),
NewTuple = update_tuple(PosValArg,Tuple),
- [NewTuple] = ets:lookup(Tab,Key)
+ [NewTuple] = ets:lookup(Tab,Key),
+ [begin
+ Elem = element(I, NewTuple),
+ Elem = ets:lookup_element(Tab, Key, I)
+ end
+ || I <- lists:seq(1, tuple_size(NewTuple))]
end,
LoopF = fun(_FromIx, Incr, _Times, Checksum, _MeF) when Incr >= Length ->
@@ -4142,6 +4153,41 @@ fill_tab(Tab,Val) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+lookup_element_default(Config) when is_list(Config) ->
+ EtsMem = etsmem(),
+
+ TabSet = ets_new(foo, [set]),
+ ets:insert(TabSet, {key, 42}),
+ 42 = ets:lookup_element(TabSet, key, 2, 13),
+ 13 = ets:lookup_element(TabSet, not_key, 2, 13),
+ {'EXIT',{badarg,_}} = catch ets:lookup_element(TabSet, key, 3, 13),
+ true = ets:delete(TabSet),
+
+ TabOrderedSet = ets_new(foo, [ordered_set]),
+ ets:insert(TabOrderedSet, {key, 42}),
+ 42 = ets:lookup_element(TabOrderedSet, key, 2, 13),
+ 13 = ets:lookup_element(TabOrderedSet, not_key, 2, 13),
+ {'EXIT',{badarg,_}} = catch ets:lookup_element(TabOrderedSet, key, 3, 13),
+ true = ets:delete(TabOrderedSet),
+
+ TabBag = ets_new(foo, [bag]),
+ ets:insert(TabBag, {key, 42}),
+ ets:insert(TabBag, {key, 43, 44}),
+ [42, 43] = ets:lookup_element(TabBag, key, 2, 13),
+ 13 = ets:lookup_element(TabBag, not_key, 2, 13),
+ {'EXIT',{badarg,_}} = catch ets:lookup_element(TabBag, key, 3, 13),
+ true = ets:delete(TabBag),
+
+ TabDuplicateBag = ets_new(foo, [duplicate_bag]),
+ ets:insert(TabDuplicateBag, {key, 42}),
+ ets:insert(TabDuplicateBag, {key, 42}),
+ ets:insert(TabDuplicateBag, {key, 43, 44}),
+ [42, 42, 43] = ets:lookup_element(TabDuplicateBag, key, 2, 13),
+ 13 = ets:lookup_element(TabDuplicateBag, not_key, 2, 13),
+ {'EXIT',{badarg,_}} = catch ets:lookup_element(TabDuplicateBag, key, 3, 13),
+ true = ets:delete(TabDuplicateBag),
+
+ verify_etsmem(EtsMem).
%% OTP-2386. Multiple return elements.
lookup_element_mult(Config) when is_list(Config) ->
@@ -9187,6 +9233,9 @@ error_info(_Config) ->
{lookup_element, [OneKeyTab, one, 4]},
+ {lookup_element, ['$Tab', no_key, 1, default_value], [no_fail]},
+ {lookup_element, [OneKeyTab, one, 4, default_value]},
+
{match, [bad_continuation], [no_table]},
{match, ['$Tab', <<1,2,3>>], [no_fail]},
diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl
index 5fa604d4fd..52f6a2ceb9 100644
--- a/lib/stdlib/test/gen_server_SUITE.erl
+++ b/lib/stdlib/test/gen_server_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,12 +26,16 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
--export([start/1, crash/1, call/1, send_request/1,
+-export([start/1, crash/1, loop_start_fail/1, call/1, send_request/1,
send_request_receive_reqid_collection/1,
send_request_wait_reqid_collection/1,
send_request_check_reqid_collection/1,
- cast/1, cast_fast/1,
- continue/1, info/1, abcast/1, multicall/1, multicall_down/1,
+ cast/1, cast_fast/1, continue/1, info/1, abcast/1,
+ multicall/1, multicall_down/1, multicall_remote/1,
+ multicall_remote_old1/1, multicall_remote_old2/1,
+ multicall_recv_opt_success/1,
+ multicall_recv_opt_timeout/1,
+ multicall_recv_opt_noconnection/1,
call_remote1/1, call_remote2/1, call_remote3/1, calling_self/1,
call_remote_n1/1, call_remote_n2/1, call_remote_n3/1, spec_init/1,
spec_init_local_registered_parent/1,
@@ -59,20 +63,33 @@
spec_init_anonymous_default_timeout/1,
spec_init_not_proc_lib/1, cast_fast_messup/0]).
+%% Internal test specific exports
+-export([multicall_srv_ctrlr/2, multicall_suspender/2]).
%% The gen_server behaviour
-export([init/1, handle_call/3, handle_cast/2, handle_continue/2,
handle_info/2, code_change/3, terminate/2, format_status/2]).
+%% This module needs to compile on old nodes...
+-ifndef(CT_PEER).
+-define(CT_PEER(), {ok, undefined, undefined}).
+-define(CT_PEER(Opts), {ok, undefined, undefined}).
+-endif.
+-ifndef(CT_PEER_REL).
+-define(CT_PEER_REL(Opts, Release, PrivDir), {ok, undefined, undefined}).
+-endif.
+
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,1}}].
all() ->
- [start, {group,stop}, crash, call, send_request,
+ [start, {group,stop}, crash, loop_start_fail, call, send_request,
send_request_receive_reqid_collection, send_request_wait_reqid_collection,
send_request_check_reqid_collection, cast, cast_fast, info, abcast,
- continue, multicall, multicall_down, call_remote1, call_remote2, calling_self,
+ continue,
+ {group, multi_call},
+ call_remote1, call_remote2, calling_self,
call_remote3, call_remote_n1, call_remote_n2,
call_remote_n3, spec_init,
spec_init_local_registered_parent,
@@ -87,6 +104,13 @@ all() ->
groups() ->
[{stop, [],
[stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]},
+ {multi_call, [], [{group, multi_call_parallel}, {group, multi_call_sequence}]},
+ {multi_call_parallel, [parallel],
+ [multicall, multicall_down, multicall_remote, multicall_remote_old1,
+ multicall_remote_old2]},
+ {multi_call_sequence, [],
+ [multicall_recv_opt_success, multicall_recv_opt_timeout,
+ multicall_recv_opt_noconnection]},
{format_status, [],
[call_format_status, error_format_status, terminate_crash_format,
crash_in_format_status, throw_in_format_status, format_all_status]},
@@ -135,7 +159,10 @@ end_per_testcase(_Case, Config) ->
undefined ->
ok;
Peer ->
- peer:stop(Peer)
+ try peer:stop(Peer)
+ catch exit : noproc ->
+ ok
+ end
end,
ok.
@@ -148,6 +175,7 @@ start(Config) when is_list(Config) ->
OldFl = process_flag(trap_exit, true),
%% anonymous
+ io:format("anonymous~n", []),
{ok, Pid0} = gen_server:start(gen_server_SUITE, [], []),
ok = gen_server:call(Pid0, started_p),
ok = gen_server:call(Pid0, stop),
@@ -155,6 +183,7 @@ start(Config) when is_list(Config) ->
{'EXIT', {noproc,_}} = (catch gen_server:call(Pid0, started_p, 1)),
%% anonymous with timeout
+ io:format("try init timeout~n", []),
{ok, Pid00} = gen_server:start(gen_server_SUITE, [],
[{timeout,1000}]),
ok = gen_server:call(Pid00, started_p),
@@ -163,9 +192,16 @@ start(Config) when is_list(Config) ->
[{timeout,100}]),
%% anonymous with ignore
+ io:format("try init ignore~n", []),
ignore = gen_server:start(gen_server_SUITE, ignore, []),
+ %% anonymous with shutdown
+ io:format("try init shutdown~n", []),
+ {error, foobar} =
+ gen_server:start(gen_server_SUITE, {error, foobar}, []),
+
%% anonymous with stop
+ io:format("try init stop~n", []),
{error, stopped} = gen_server:start(gen_server_SUITE, stop, []),
%% anonymous linked
@@ -485,6 +521,55 @@ crash(Config) when is_list(Config) ->
ok.
+
+loop_start_fail(Config) ->
+ _ = process_flag(trap_exit, true),
+ loop_start_fail(
+ Config,
+ [{start, []}, {start, [link]},
+ {start_link, []},
+ {start_monitor, [link]}, {start_monitor, []}]).
+
+loop_start_fail(_Config, []) ->
+ ok;
+loop_start_fail(Config, [{Start, Opts} | Start_Opts]) ->
+ loop_start_fail(
+ fun gen_server:Start/3,
+ {ets, {return, {stop, failed_to_start}}}, Opts,
+ fun ({error, failed_to_start}) -> ok end),
+ loop_start_fail(
+ fun gen_server:Start/3,
+ {ets, {return, ignore}}, Opts,
+ fun (ignore) -> ok end),
+ loop_start_fail(
+ fun gen_server:Start/3,
+ {ets, {return, 4711}}, Opts,
+ fun ({error, {bad_return_value, 4711}}) -> ok end),
+ loop_start_fail(
+ fun gen_server:Start/3,
+ {ets, {crash, error, bailout}}, Opts,
+ fun ({error, {bailout, ST}}) when is_list(ST) -> ok end),
+ loop_start_fail(
+ fun gen_server:Start/3,
+ {ets, {crash, exit, bailout}}, Opts,
+ fun ({error, bailout}) -> ok end),
+ loop_start_fail(
+ fun gen_server:Start/3,
+ {ets, {wait, 1000, void}}, [{timeout, 200} | Opts],
+ fun ({error, timeout}) -> ok end),
+ loop_start_fail(Config, Start_Opts).
+
+loop_start_fail(GenStartFun, Arg, Opts, ValidateFun) ->
+ loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, 5).
+%%
+loop_start_fail(_GenStartFun, _Arg, _Opts, _ValidateFun, 0) ->
+ ok;
+loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, N) ->
+ ok = ValidateFun(GenStartFun(?MODULE, Arg, Opts)),
+ loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, N - 1).
+
+
+
%% --------------------------------------
%% Test gen_server:call and handle_call.
%% Test all different return values from
@@ -1434,6 +1519,251 @@ busy_wait_for_process(Pid,N) ->
_ ->
ok
end.
+
+multicall_remote(Config) when is_list(Config) ->
+ PNs = lists:map(fun (_) ->
+ {ok, P, N} = ?CT_PEER(),
+ {P, N}
+ end, lists:seq(1, 4)),
+ multicall_remote_test(PNs, ?FUNCTION_NAME),
+ ok.
+
+multicall_remote_old1(Config) when is_list(Config) ->
+ multicall_remote_old_test(Config, 1, ?FUNCTION_NAME).
+
+multicall_remote_old2(Config) when is_list(Config) ->
+ multicall_remote_old_test(Config, 2, ?FUNCTION_NAME).
+
+
+multicall_remote_old_test(Config, OldN, Name) ->
+ try
+ {OldRelName, OldRel} = old_release(OldN),
+ PD = proplists:get_value(priv_dir, Config),
+ PNs = lists:map(fun (I) ->
+ Dir = atom_to_list(Name)++"-"++integer_to_list(I),
+ AbsDir = filename:join([PD, Dir]),
+ ok = file:make_dir(AbsDir),
+ case ?CT_PEER_REL(#{connection => 0}, OldRelName, AbsDir) of
+ not_available ->
+ throw({skipped, "No OTP "++OldRel++" available"});
+ {ok, P, N} ->
+ {P, N}
+ end
+ end, lists:seq(1, 4)),
+ OldNodes = lists:map(fun ({_, N}) -> N end, PNs),
+ %% Recompile on one old node and load this on all old nodes...
+ SrcFile = filename:rootname(code:which(?MODULE)) ++ ".erl",
+ {ok, ?MODULE, BeamCode} = erpc:call(hd(OldNodes), compile, file, [SrcFile, [binary]]),
+ LoadResult = lists:duplicate(length(OldNodes), {ok, {module, ?MODULE}}),
+ LoadResult = erpc:multicall(OldNodes, code, load_binary, [?MODULE, SrcFile, BeamCode]),
+ multicall_remote_test(PNs, Name)
+ catch
+ throw:Res ->
+ Res
+ end.
+
+multicall_remote_test([{Peer1, Node1},
+ {Peer2, Node2},
+ {Peer3, Node3},
+ {Peer4, Node4}],
+ Name) ->
+ Tester = self(),
+ ThisNode = node(),
+
+ Nodes = [Node1, Node2, Node3, Node4, ThisNode],
+
+ SrvList =
+ lists:map(fun (Node) ->
+ Ctrl = spawn_link(Node, ?MODULE,
+ multicall_srv_ctrlr,
+ [Tester, Name]),
+ receive
+ {Ctrl, _Srv} = Procs ->
+ {Node, Procs}
+ end
+ end, Nodes),
+ SrvMap = maps:from_list(SrvList),
+
+ Res0 = {lists:map(fun (Node) ->
+ {Node,ok}
+ end, Nodes), []},
+
+ Res0 = gen_server:multi_call(Nodes, Name, started_p),
+
+ true = try
+ _ = gen_server:multi_call([Node1, Node2, Node3, node(), {Node4}],
+ Name, {delayed_answer,1}),
+ false
+ catch
+ _:_ ->
+ true
+ end,
+
+ Res1 = {lists:map(fun (Node) ->
+ {Node,delayed}
+ end, Nodes), []},
+
+ Res1 = gen_server:multi_call(Nodes, Name, {delayed_answer,1}),
+
+ Res2 = {[], Nodes},
+
+ Start = erlang:monotonic_time(millisecond),
+ Res2 = gen_server:multi_call(Nodes, Name, {delayed_answer,1000}, 100),
+ End = erlang:monotonic_time(millisecond),
+ Time = End-Start,
+ ct:log("Time: ~p ms~n", [Time]),
+ true = 200 >= Time,
+
+ {Ctrl2, Srv2} = maps:get(Node2, SrvMap),
+ unlink(Ctrl2),
+ exit(Ctrl2, kill),
+ wait_until(fun () ->
+ false == erpc:call(Node2, erlang,
+ is_process_alive, [Srv2])
+ end),
+
+ {Ctrl3, _Srv3} = maps:get(Node3, SrvMap),
+ unlink(Ctrl3),
+ peer:stop(Peer3),
+
+ {Ctrl4, Srv4} = maps:get(Node4, SrvMap),
+ Spndr = spawn_link(Node4, ?MODULE, multicall_suspender, [Tester, Srv4]),
+
+ Res3 = {[{Node1, delayed}, {ThisNode, delayed}],
+ [Node2, Node3, Node4]},
+
+ Res3 = gen_server:multi_call(Nodes, Name, {delayed_answer,1}, 1000),
+
+ Spndr ! {Tester, resume_it},
+
+ receive Msg -> ct:fail({unexpected_msg, Msg})
+ after 1000 -> ok
+ end,
+
+ unlink(Ctrl4),
+
+ {Ctrl1, _Srv1} = maps:get(Node1, SrvMap),
+
+ unlink(Ctrl1),
+
+ peer:stop(Peer1),
+ peer:stop(Peer2),
+ peer:stop(Peer4),
+
+ ok.
+
+multicall_srv_ctrlr(Tester, Name) ->
+ {ok, Srv} = gen_server:start_link({local, Name},
+ gen_server_SUITE, [], []),
+ Tester ! {self(), Srv},
+ receive after infinity -> ok end.
+
+multicall_suspender(Tester, Suspendee) ->
+ true = erlang:suspend_process(Suspendee),
+ receive
+ {Tester, resume_it} ->
+ erlang:resume_process(Suspendee)
+ end.
+
+multicall_recv_opt_success(Config) when is_list(Config) ->
+ multicall_recv_opt_test(success).
+
+multicall_recv_opt_timeout(Config) when is_list(Config) ->
+ multicall_recv_opt_test(timeout).
+
+multicall_recv_opt_noconnection(Config) when is_list(Config) ->
+ multicall_recv_opt_test(noconnection).
+
+multicall_recv_opt_test(Type) ->
+ Tester = self(),
+ Name = ?FUNCTION_NAME,
+ Loops = 1000,
+ HugeMsgQ = 500000,
+ process_flag(message_queue_data, off_heap),
+
+ {ok, Peer1, Node1} = ?CT_PEER(),
+ {ok, Peer2, Node2} = ?CT_PEER(),
+
+ if Type == noconnection -> peer:stop(Peer2);
+ true -> ok
+ end,
+
+ Nodes = [Node1, Node2],
+
+ SrvList =
+ lists:map(fun (Node) ->
+ Ctrl = spawn_link(Node, ?MODULE,
+ multicall_srv_ctrlr,
+ [Tester, Name]),
+ receive
+ {Ctrl, _Srv} = Procs ->
+ {Node, Procs}
+ end
+ end,
+ if Type == noconnection -> [Node1];
+ true -> Nodes
+ end),
+
+ {Req, ExpRes, Tmo} = case Type of
+ success ->
+ {ping,
+ {[{Node1, pong}, {Node2, pong}], []},
+ infinity};
+ timeout ->
+ {{delayed_answer,100},
+ {[], Nodes},
+ 1};
+ noconnection ->
+ {ping,
+ {[{Node1, pong}], [Node2]},
+ infinity}
+ end,
+
+ _Warmup = time_multicall(ExpRes, Nodes, Name, Req, Tmo, Loops div 10),
+
+ Empty = time_multicall(ExpRes, Nodes, Name, Req, Tmo, Loops),
+ ct:pal("Time with empty message queue: ~p microsecond~n",
+ [erlang:convert_time_unit(Empty, native, microsecond)]),
+
+ make_msgq(HugeMsgQ),
+
+ Huge = time_multicall(ExpRes, Nodes, Name, Req, Tmo, Loops),
+ ct:pal("Time with huge message queue: ~p microsecond~n",
+ [erlang:convert_time_unit(Huge, native, microsecond)]),
+
+ lists:foreach(fun ({_Node, {Ctrl, _Srv}}) -> unlink(Ctrl) end, SrvList),
+
+ peer:stop(Peer1),
+ if Type == noconnection -> ok;
+ true -> peer:stop(Peer2)
+ end,
+
+ Q = Huge / Empty,
+ HugeMsgQ = flush_msgq(),
+ case Q > 10 of
+ true ->
+ ct:fail({ratio, Q});
+ false ->
+ {comment, "Ratio: "++erlang:float_to_list(Q)}
+ end.
+
+time_multicall(Expect, Nodes, Name, Req, Tmo, Times) ->
+ Start = erlang:monotonic_time(),
+ ok = do_time_multicall(Expect, Nodes, Name, Req, Tmo, Times),
+ erlang:monotonic_time() - Start.
+
+do_time_multicall(_Expect, _Nodes, _Name, _Req, _Tmo, 0) ->
+ ok;
+do_time_multicall(Expect, Nodes, Name, Req, Tmo, N) ->
+ Expect = gen_server:multi_call(Nodes, Name, Req, Tmo),
+ do_time_multicall(Expect, Nodes, Name, Req, Tmo, N-1).
+
+make_msgq(0) ->
+ ok;
+make_msgq(N) ->
+ self() ! {a, msg},
+ make_msgq(N-1).
+
%%--------------------------------------------------------------
%% Test gen_server:enter_loop/[3,4,5]. Used when you want to write
%% your own special init-phase.
@@ -2049,11 +2379,10 @@ undef_init(_Config) ->
{error, {undef, [{oc_init_server, init, [_], _}|_]}} =
(catch gen_server:start_link(oc_init_server, [], [])),
receive
- {'EXIT', Server,
- {undef, [{oc_init_server, init, [_], _}|_]}} when is_pid(Server) ->
+ Msg ->
+ ct:fail({unexpected_msg, Msg})
+ after 500 ->
ok
- after 1000 ->
- ct:fail(expected_exit_msg)
end.
%% The upgrade should fail if code_change is expected in the callback module
@@ -2483,23 +2812,44 @@ spec_init_not_proc_lib(Options) ->
init([]) ->
{ok, []};
init(ignore) ->
+ io:format("init(ignore)~n"),
ignore;
+init({error, Reason}) ->
+ io:format("init(error) -> ~w~n", [Reason]),
+ {error, Reason};
init(stop) ->
+ io:format("init(stop)~n"),
{stop, stopped};
init(hibernate) ->
+ io:format("init(hibernate)~n"),
{ok,[],hibernate};
init(sleep) ->
+ io:format("init(sleep)~n"),
ct:sleep(1000),
{ok, []};
init({continue, Pid}) ->
+ io:format("init(continue) -> ~p~n", [Pid]),
self() ! {after_continue, Pid},
{ok, [], {continue, {message, Pid}}};
init({state,State}) ->
- {ok,State}.
+ io:format("init(state) -> ~p~n", [State]),
+ {ok,State};
+init({ets,InitResult}) ->
+ ?MODULE = ets:new(?MODULE, [named_table]),
+ case InitResult of
+ {return, Value} ->
+ Value;
+ {crash, Class, Reason} ->
+ erlang:Class(Reason);
+ {wait, Time, Value} ->
+ receive after Time -> Value end
+ end.
handle_call(started_p, _From, State) ->
io:format("FROZ"),
{reply,ok,State};
+handle_call(ping, _From, State) ->
+ {reply,pong,State};
handle_call({delayed_answer, T}, From, State) ->
{noreply,{reply_to,From,State},T};
handle_call({call_within, T}, _From, _) ->
@@ -2507,6 +2857,7 @@ handle_call({call_within, T}, _From, _) ->
handle_call(next_call, _From, call_within) ->
{reply,ok,[]};
handle_call(next_call, _From, State) ->
+ io:format("handle_call(next_call) -> State: ~p~n", [State]),
{reply,false,State};
handle_call(badreturn, _From, _State) ->
badreturn;
@@ -2645,3 +2996,28 @@ format_status(terminate, [_PDict, State]) ->
{formatted, State};
format_status(normal, [_PDict, _State]) ->
format_status_called.
+
+%% Utils...
+
+wait_until(Fun) ->
+ case catch Fun() of
+ true ->
+ ok;
+ _ ->
+ receive after 100 -> ok end,
+ wait_until(Fun)
+ end.
+
+old_release(N) ->
+ OldRel = integer_to_list(list_to_integer(erlang:system_info(otp_release))-N),
+ {OldRel++"_latest", OldRel}.
+
+flush_msgq() ->
+ flush_msgq(0).
+flush_msgq(N) ->
+ receive
+ _ ->
+ flush_msgq(N+1)
+ after 0 ->
+ N
+ end.
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 9b4ee9413f..d99c7e9786 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@ all() ->
{group, stop_handle_event},
{group, abnormal},
{group, abnormal_handle_event},
- shutdown, stop_and_reply, state_enter, event_order,
+ shutdown, loop_start_fail, stop_and_reply, state_enter, event_order,
state_timeout, timeout_cancel_and_update,
event_types, generic_timers, code_change,
{group, sys},
@@ -61,7 +61,7 @@ groups() ->
{format_log, [], tcs(format_log)}].
tcs(start) ->
- [start1, start2, start3, start4, start5, start6, start7,
+ [start1, start2, start3, start4, start5a, start5b, start6, start7,
start8, start9, start10, start11, start12, next_events];
tcs(stop) ->
[stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10];
@@ -210,7 +210,7 @@ start4(Config) ->
ok = verify_empty_msgq().
%% anonymous with stop
-start5(Config) ->
+start5a(Config) ->
OldFl = process_flag(trap_exit, true),
{error,stopped} = gen_statem:start(?MODULE, start_arg(Config, stop), []),
@@ -218,6 +218,16 @@ start5(Config) ->
process_flag(trap_exit, OldFl),
ok = verify_empty_msgq().
+%% anonymous with shutdown
+start5b(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ {error, foobar} =
+ gen_statem:start(?MODULE, start_arg(Config, {error, foobar}), []),
+
+ process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq().
+
%% anonymous linked
start6(Config) ->
{ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
@@ -676,6 +686,53 @@ shutdown(Config) ->
end.
+loop_start_fail(Config) ->
+ _ = process_flag(trap_exit, true),
+ loop_start_fail(
+ Config,
+ [{start, []}, {start, [link]},
+ {start_link, []},
+ {start_monitor, [link]}, {start_monitor, []}]).
+
+loop_start_fail(_Config, []) ->
+ ok;
+loop_start_fail(Config, [{Start, Opts} | Start_Opts]) ->
+ loop_start_fail(
+ fun gen_statem:Start/3,
+ {ets, {return, {stop, failed_to_start}}}, Opts,
+ fun ({error, failed_to_start}) -> ok end),
+ loop_start_fail(
+ fun gen_statem:Start/3,
+ {ets, {return, ignore}}, Opts,
+ fun (ignore) -> ok end),
+ loop_start_fail(
+ fun gen_statem:Start/3,
+ {ets, {return, 4711}}, Opts,
+ fun ({error, {bad_return_from_init, 4711}}) -> ok end),
+ loop_start_fail(
+ fun gen_statem:Start/3,
+ {ets, {crash, error, bailout}}, Opts,
+ fun ({error, bailout}) -> ok end),
+ loop_start_fail(
+ fun gen_statem:Start/3,
+ {ets, {crash, exit, bailout}}, Opts,
+ fun ({error, bailout}) -> ok end),
+ loop_start_fail(
+ fun gen_statem:Start/3,
+ {ets, {wait, 1000, void}}, [{timeout, 200} | Opts],
+ fun ({error, timeout}) -> ok end),
+ loop_start_fail(Config, Start_Opts).
+
+loop_start_fail(GenStartFun, Arg, Opts, ValidateFun) ->
+ loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, 5).
+%%
+loop_start_fail(_GenStartFun, _Arg, _Opts, _ValidateFun, 0) ->
+ ok;
+loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, N) ->
+ ok = ValidateFun(GenStartFun(?MODULE, Arg, Opts)),
+ loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, N - 1).
+
+
stop_and_reply(_Config) ->
process_flag(trap_exit, true),
@@ -1352,7 +1409,7 @@ terminate_crash_format(Config) ->
terminate_crash_format(Config,format_status_statem,
{{formatted,idle},{formatted,crash_terminate}})
after
- dbg:stop_clear(),
+ dbg:stop(),
process_flag(trap_exit, OldFl),
error_logger_forwarder:unregister()
end.
@@ -1512,11 +1569,12 @@ replace_state(Config) ->
%% Hibernation
hibernate(Config) ->
OldFl = process_flag(trap_exit, true),
+ WaitHibernate = 500,
{ok,Pid0} =
gen_statem:start_link(
?MODULE, start_arg(Config, hiber_now), []),
- wait_erlang_hibernate(Pid0),
+ wait_erlang_hibernate(Pid0, WaitHibernate),
stop_it(Pid0),
receive
{'EXIT',Pid0,normal} -> ok
@@ -1529,38 +1587,38 @@ hibernate(Config) ->
true = ({current_function,{erlang,hibernate,3}} =/=
erlang:process_info(Pid,current_function)),
hibernating = gen_statem:call(Pid, hibernate_sync),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
good_morning = gen_statem:call(Pid, wakeup_sync),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, WaitHibernate),
hibernating = gen_statem:call(Pid, hibernate_sync),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
please_just_five_more = gen_statem:call(Pid, snooze_sync),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
good_morning = gen_statem:call(Pid, wakeup_sync),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, WaitHibernate),
ok = gen_statem:cast(Pid, hibernate_async),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
ok = gen_statem:cast(Pid, wakeup_async),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, WaitHibernate),
ok = gen_statem:cast(Pid, hibernate_async),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
ok = gen_statem:cast(Pid, snooze_async),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
ok = gen_statem:cast(Pid, wakeup_async),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, WaitHibernate),
- Pid ! hibernate_later,
+ Pid ! {hibernate_later, WaitHibernate div 2},
true =
({current_function,{erlang,hibernate,3}} =/=
erlang:process_info(Pid, current_function)),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
'alive!' = gen_statem:call(Pid, 'alive?'),
true =
({current_function,{erlang,hibernate,3}} =/=
erlang:process_info(Pid, current_function)),
Pid ! hibernate_now,
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
'alive!' = gen_statem:call(Pid, 'alive?'),
true =
@@ -1568,37 +1626,37 @@ hibernate(Config) ->
erlang:process_info(Pid, current_function)),
hibernating = gen_statem:call(Pid, hibernate_sync),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
good_morning = gen_statem:call(Pid, wakeup_sync),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, WaitHibernate),
hibernating = gen_statem:call(Pid, hibernate_sync),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
please_just_five_more = gen_statem:call(Pid, snooze_sync),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
good_morning = gen_statem:call(Pid, wakeup_sync),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, WaitHibernate),
ok = gen_statem:cast(Pid, hibernate_async),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
ok = gen_statem:cast(Pid, wakeup_async),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, WaitHibernate),
ok = gen_statem:cast(Pid, hibernate_async),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
ok = gen_statem:cast(Pid, snooze_async),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
ok = gen_statem:cast(Pid, wakeup_async),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, WaitHibernate),
hibernating = gen_statem:call(Pid, hibernate_sync),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
sys:suspend(Pid),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
sys:resume(Pid),
- wait_erlang_hibernate(Pid),
- receive after 1000 -> ok end,
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, WaitHibernate),
+ receive after WaitHibernate -> ok end,
+ wait_erlang_hibernate(Pid, WaitHibernate),
good_morning = gen_statem:call(Pid, wakeup_sync),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, WaitHibernate),
stop_it(Pid),
process_flag(trap_exit, OldFl),
receive
@@ -1611,73 +1669,74 @@ hibernate(Config) ->
%% Auto-hibernation timeout
auto_hibernate(Config) ->
OldFl = process_flag(trap_exit, true),
- HibernateAfterTimeout = 1000,
+ HibernateAfterTimeout = 500,
+ WaitTime = 1000,
{ok,Pid} =
gen_statem:start_link(
?MODULE, start_arg(Config, []),
[{hibernate_after, HibernateAfterTimeout}]),
%% After init test
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
timer:sleep(HibernateAfterTimeout),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
%% After info test
Pid ! {hping, self()},
receive
{Pid, hpong} ->
ok
- after 1000 ->
+ after WaitTime ->
ct:fail(info)
end,
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
timer:sleep(HibernateAfterTimeout),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
%% After cast test
ok = gen_statem:cast(Pid, {hping, self()}),
receive
{Pid, hpong} ->
ok
- after 1000 ->
+ after WaitTime ->
ct:fail(cast)
end,
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
timer:sleep(HibernateAfterTimeout),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
%% After call test
hpong = gen_statem:call(Pid, hping),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
timer:sleep(HibernateAfterTimeout),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
%% Timer test 1
TimerTimeout1 = HibernateAfterTimeout div 2,
ok = gen_statem:call(Pid, {start_htimer, self(), TimerTimeout1}),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
timer:sleep(TimerTimeout1),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
receive
{Pid, htimer_timeout} ->
ok
- after 1000 ->
+ after WaitTime ->
ct:fail(timer1)
end,
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
timer:sleep(HibernateAfterTimeout),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
%% Timer test 2
TimerTimeout2 = HibernateAfterTimeout * 2,
ok = gen_statem:call(Pid, {start_htimer, self(), TimerTimeout2}),
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
timer:sleep(HibernateAfterTimeout),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
receive
{Pid, htimer_timeout} ->
ok
after TimerTimeout2 ->
ct:fail(timer2)
end,
- is_not_in_erlang_hibernate(Pid),
+ is_not_in_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
timer:sleep(HibernateAfterTimeout),
- wait_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid, 2 * HibernateAfterTimeout),
stop_it(Pid),
process_flag(trap_exit, OldFl),
receive
@@ -1688,35 +1747,35 @@ auto_hibernate(Config) ->
ok = verify_empty_msgq().
-wait_erlang_hibernate(Pid) ->
+wait_erlang_hibernate(Pid, Time) ->
receive after 1 -> ok end,
- wait_erlang_hibernate_1(200, Pid).
+ wait_erlang_hibernate_1(Pid, Time, Time div 100).
-wait_erlang_hibernate_1(0, Pid) ->
+wait_erlang_hibernate_1(Pid, Time, _T) when Time =< 0 ->
ct:log("~p\n", [erlang:process_info(Pid, current_function)]),
ct:fail(should_be_in_erlang_hibernate_3);
-wait_erlang_hibernate_1(N, Pid) ->
+wait_erlang_hibernate_1(Pid, Time, T) ->
{current_function,MFA} = erlang:process_info(Pid, current_function),
case MFA of
{erlang,hibernate,3} ->
ok;
_ ->
- receive after 10 -> ok end,
- wait_erlang_hibernate_1(N-1, Pid)
+ receive after T -> ok end,
+ wait_erlang_hibernate_1(Pid, Time - T, T)
end.
-is_not_in_erlang_hibernate(Pid) ->
+is_not_in_erlang_hibernate(Pid, Time) ->
receive after 1 -> ok end,
- is_not_in_erlang_hibernate_1(200, Pid).
+ is_not_in_erlang_hibernate_1(Pid, Time, Time div 100).
-is_not_in_erlang_hibernate_1(0, _Pid) ->
+is_not_in_erlang_hibernate_1(_Pid, Time, _T) when Time =< 0 ->
ct:fail(should_not_be_in_erlang_hibernate_3);
-is_not_in_erlang_hibernate_1(N, Pid) ->
+is_not_in_erlang_hibernate_1(Pid, Time, T) ->
{current_function,MFA} = erlang:process_info(Pid, current_function),
case MFA of
{erlang,hibernate,3} ->
- receive after 10 -> ok end,
- is_not_in_erlang_hibernate_1(N-1, Pid);
+ receive after T -> ok end,
+ is_not_in_erlang_hibernate_1(Pid, Time - T, T);
_ ->
ok
end.
@@ -2751,25 +2810,37 @@ start_arg(Config, Arg) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init(ignore) ->
+ io:format("init(ignore)~n", []),
ignore;
init(stop) ->
+ io:format("init(stop)~n", []),
{stop,stopped};
+init({error, Reason}) ->
+ io:format("init(error) -> Reason: ~p~n", [Reason]),
+ {error, Reason};
init(stop_shutdown) ->
+ io:format("init(stop_shutdown)~n", []),
{stop,shutdown};
init(sleep) ->
+ io:format("init(sleep)~n", []),
ct:sleep(1000),
init_sup({ok,idle,data});
init(hiber) ->
+ io:format("init(hiber)~n", []),
init_sup({ok,hiber_idle,[]});
init(hiber_now) ->
+ io:format("init(hiber_now)~n", []),
init_sup({ok,hiber_idle,[],[hibernate]});
init({data, Data}) ->
+ io:format("init(data)~n", []),
init_sup({ok,idle,Data});
init({callback_mode,CallbackMode,Arg}) ->
+ io:format("init(callback_mode)~n", []),
ets:new(?MODULE, [named_table,private]),
ets:insert(?MODULE, {callback_mode,CallbackMode}),
init(Arg);
init({map_statem,#{init := Init}=Machine,Modes}) ->
+ io:format("init(map_statem)~n", []),
ets:new(?MODULE, [named_table,private]),
ets:insert(?MODULE, {callback_mode,[handle_event_function|Modes]}),
case Init() of
@@ -2780,7 +2851,19 @@ init({map_statem,#{init := Init}=Machine,Modes}) ->
Other ->
init_sup(Other)
end;
+init({ets, InitResult}) ->
+ ?MODULE = ets:new(?MODULE, [named_table]),
+ init_sup(
+ case InitResult of
+ {return, Value} ->
+ Value;
+ {crash, Class, Reason} ->
+ erlang:Class(Reason);
+ {wait, Time, Value} ->
+ receive after Time -> Value end
+ end);
init([]) ->
+ io:format("init~n", []),
init_sup({ok,idle,data}).
%% Supervise state machine parent i.e the test case, and if it dies
@@ -2972,8 +3055,8 @@ hiber_idle({call,From}, hibernate_sync, Data) ->
{next_state,hiber_wakeup,Data,
[{reply,From,hibernating},
hibernate]};
-hiber_idle(info, hibernate_later, _) ->
- Tref = erlang:start_timer(1000, self(), hibernate),
+hiber_idle(info, {hibernate_later, Time}, _) ->
+ Tref = erlang:start_timer(Time, self(), hibernate),
{keep_state,Tref};
hiber_idle(info, hibernate_now, Data) ->
{keep_state,Data,
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index 17fd6d41fd..f26d98cc18 100644
--- a/lib/stdlib/test/io_SUITE.erl
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -33,7 +33,8 @@
maps/1, coverage/1, otp_14178_unicode_atoms/1, otp_14175/1,
otp_14285/1, limit_term/1, otp_14983/1, otp_15103/1, otp_15076/1,
otp_15159/1, otp_15639/1, otp_15705/1, otp_15847/1, otp_15875/1,
- github_4801/1, chars_limit/1, error_info/1, otp_17525/1]).
+ github_4801/1, chars_limit/1, error_info/1, otp_17525/1,
+ unscan_format_without_maps_order/1, build_text_without_maps_order/1]).
-export([pretty/2, trf/3]).
@@ -67,7 +68,8 @@ all() ->
format_string, maps, coverage, otp_14178_unicode_atoms, otp_14175,
otp_14285, limit_term, otp_14983, otp_15103, otp_15076, otp_15159,
otp_15639, otp_15705, otp_15847, otp_15875, github_4801, chars_limit,
- error_info, otp_17525].
+ error_info, otp_17525, unscan_format_without_maps_order,
+ build_text_without_maps_order].
%% Error cases for output.
error_1(Config) when is_list(Config) ->
@@ -2184,14 +2186,16 @@ otp_10755(Suite) when is_list(Suite) ->
" io:format(\"~ltw\", [S]),\n"
" io:format(\"~tlw\", [S]),\n"
" io:format(\"~ltW\", [S, 1]),\n"
- " io:format(\"~tlW\", [S, 1]).\n",
+ " io:format(\"~tlW\", [S, 1]),\n"
+ " io:format(\"~ltp\", [S, 1]).\n",
{ok,l_mod,[{_File,Ws}]} = compile_file("l_mod.erl", Text, Suite),
- ["format string invalid (invalid control ~lw)",
- "format string invalid (invalid control ~lW)",
- "format string invalid (invalid control ~ltw)",
- "format string invalid (invalid control ~ltw)",
- "format string invalid (invalid control ~ltW)",
- "format string invalid (invalid control ~ltW)"] =
+ ["format string invalid (invalid modifier/control combination ~lw)",
+ "format string invalid (invalid modifier/control combination ~lW)",
+ "format string invalid (invalid modifier/control combination ~lw)",
+ "format string invalid (invalid modifier/control combination ~lw)",
+ "format string invalid (invalid modifier/control combination ~lW)",
+ "format string invalid (invalid modifier/control combination ~lW)",
+ "format string invalid (conflicting modifiers ~ltp)"] =
[lists:flatten(M:format_error(E)) || {_L,M,E} <- Ws],
ok.
@@ -2271,34 +2275,64 @@ format_string(_Config) ->
ok.
maps(_Config) ->
- %% Note that order in which a map is printed is arbitrary. In
- %% practice, small maps (non-HAMT) are printed in key order, but
- %% the breakpoint for creating big maps (HAMT) is lower in the
- %% debug-compiled run-time system than in the optimized run-time
- %% system.
- %%
+ %% Note that order in which a map is printed is arbitrary.
%% Therefore, play it completely safe by not assuming any order
%% in a map with more than one element.
+ AOrdCmpFun = fun(A, B) -> A =< B end,
+ ARevCmpFun = fun(A, B) -> B < A end,
+
+ AtomMap1 = #{a => b},
+ AtomMap2 = #{a => b, c => d},
+ AtomMap3 = #{a => b, c => d, e => f},
+
"#{}" = fmt("~w", [#{}]),
- "#{a => b}" = fmt("~w", [#{a=>b}]),
- re_fmt(<<"#\\{(a => b),[.][.][.]\\}">>,
- "~W", [#{a => b,c => d},2]),
- re_fmt(<<"#\\{(a => b),[.][.][.]\\}">>,
- "~W", [#{a => b,c => d,e => f},2]),
+ "#{a => b}" = fmt("~w", [AtomMap1]),
+ re_fmt(<<"#\\{(a => b|c => d),[.][.][.]\\}">>,
+ "~W", [AtomMap2, 2]),
+ re_fmt(<<"#\\{(a => b|c => d|e => f),[.][.][.]\\}">>,
+ "~W", [AtomMap3, 2]),
+ "#{a => b,c => d,e => f}" = fmt("~kw", [AtomMap3]),
+ re_fmt(<<"#\\{(a => b|c => d|e => f),[.][.][.]\\}">>,
+ "~KW", [undefined, AtomMap3, 2]),
+ "#{a => b,c => d,e => f}" = fmt("~Kw", [ordered, AtomMap3]),
+ "#{e => f,c => d,a => b}" = fmt("~Kw", [reversed, AtomMap3]),
+ "#{a => b,c => d,e => f}" = fmt("~Kw", [AOrdCmpFun, AtomMap3]),
+ "#{e => f,c => d,a => b}" = fmt("~Kw", [ARevCmpFun, AtomMap3]),
"#{}" = fmt("~p", [#{}]),
- "#{a => b}" = fmt("~p", [#{a => b}]),
- "#{...}" = fmt("~P", [#{a => b},1]),
+ "#{a => b}" = fmt("~p", [AtomMap1]),
+ "#{...}" = fmt("~P", [AtomMap1, 1]),
re_fmt(<<"#\\{(a => b|c => d),[.][.][.]\\}">>,
- "~P", [#{a => b,c => d},2]),
+ "~P", [AtomMap2, 2]),
re_fmt(<<"#\\{(a => b|c => d|e => f),[.][.][.]\\}">>,
- "~P", [#{a => b,c => d,e => f},2]),
+ "~P", [AtomMap3, 2]),
+ "#{a => b,c => d,e => f}" = fmt("~kp", [AtomMap3]),
+ re_fmt(<<"#\\{(a => b|c => d|e => f),[.][.][.]\\}">>,
+ "~KP", [undefined, AtomMap3, 2]),
+ "#{a => b,c => d,e => f}" = fmt("~Kp", [ordered, AtomMap3]),
+ "#{e => f,c => d,a => b}" = fmt("~Kp", [reversed, AtomMap3]),
+ "#{a => b,c => d,e => f}" = fmt("~Kp", [AOrdCmpFun, AtomMap3]),
+ "#{e => f,c => d,a => b}" = fmt("~Kp", [ARevCmpFun, AtomMap3]),
- List = [{I,I*I} || I <- lists:seq(1, 20)],
+ List = [{I, I * I} || I <- lists:seq(1, 64)],
Map = maps:from_list(List),
- "#{...}" = fmt("~P", [Map,1]),
+ "#{...}" = fmt("~P", [Map, 1]),
+ "#{1 => 1,...}" = fmt("~kP", [Map, 2]),
+ "#{1 => 1,...}" = fmt("~KP", [ordered, Map, 2]),
+ "#{64 => 4096,...}" = fmt("~KP", [reversed, Map, 2]),
+ "#{1 => 1,...}" = fmt("~KP", [AOrdCmpFun, Map, 2]),
+ "#{64 => 4096,...}" = fmt("~KP", [ARevCmpFun, Map, 2]),
+
+ FloatIntegerMap = #{-1.0 => a, 0.0 => b, -1 => c, 0 => d},
+ re_fmt(<<"#\\{(-1.0 => a|0.0 => b|-1 => c|0 => d),[.][.][.]\\}">>,
+ "~P", [FloatIntegerMap, 2]),
+ "#{-1 => c,0 => d,-1.0 => a,0.0 => b}" = fmt("~kp", [FloatIntegerMap]),
+ re_fmt(<<"#\\{(-1.0 => a|0.0 => b|-1 => c|0 => d),[.][.][.]\\}">>,
+ "~KP", [undefined, FloatIntegerMap, 2]),
+ "#{-1 => c,0 => d,-1.0 => a,0.0 => b}" = fmt("~Kp", [ordered, FloatIntegerMap]),
+ "#{0.0 => b,-1.0 => a,0 => d,-1 => c}" = fmt("~Kp", [reversed, FloatIntegerMap]),
%% Print a map and parse it back to a map.
S = fmt("~p\n", [Map]),
@@ -3150,3 +3184,29 @@ otp_17525(_Config) ->
" {...}|...]" =
lists:flatten(S),
ok.
+
+unscan_format_without_maps_order(_Config) ->
+ FormatSpec = #{
+ adjust => right,
+ args => [[<<"1">>]],
+ control_char => 115,
+ encoding => unicode,
+ pad_char => 32,
+ precision => none,
+ strings => true,
+ width => none
+ },
+ {"~ts",[[<<"1">>]]} = io_lib:unscan_format([FormatSpec]).
+
+build_text_without_maps_order(_Config) ->
+ FormatSpec = #{
+ adjust => right,
+ args => [[<<"1">>]],
+ control_char => 115,
+ encoding => unicode,
+ pad_char => 32,
+ precision => none,
+ strings => true,
+ width => none
+ },
+ [["1"]] = io_lib:build_text([FormatSpec]).
diff --git a/lib/stdlib/test/io_proto_SUITE.erl b/lib/stdlib/test/io_proto_SUITE.erl
index 525b479fef..0260b3251c 100644
--- a/lib/stdlib/test/io_proto_SUITE.erl
+++ b/lib/stdlib/test/io_proto_SUITE.erl
@@ -22,55 +22,29 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
--export([init_per_testcase/2, end_per_testcase/2]).
-
-export([setopts_getopts/1,unicode_options/1,unicode_options_gen/1,
binary_options/1, read_modes_gl/1,
- read_modes_ogl/1, broken_unicode/1,eof_on_pipe/1,unicode_prompt/1]).
+ read_modes_ogl/1, broken_unicode/1,eof_on_pipe/1,
+ unicode_prompt/1, shell_slogan/1, raw_stdout/1, raw_stdout_isatty/1]).
-export([io_server_proxy/1,start_io_server_proxy/0, proxy_getall/1,
proxy_setnext/2, proxy_quit/1]).
%% For spawn
--export([toerl_server/3,answering_machine1/3,
- answering_machine2/3]).
-
--export([uprompt/1]).
+-export([answering_machine1/3, answering_machine2/3]).
-%%-define(without_test_server, true).
+-export([uprompt/1, slogan/0, session_slogan/0]).
--ifdef(without_test_server).
--define(line, put(line, ?LINE), ).
--define(config(X,Y), foo).
--define(t, test_server).
--define(privdir(_), "./io_SUITE_priv").
--else.
--include_lib("common_test/include/ct.hrl").
--define(privdir(Conf), proplists:get_value(priv_dir, Conf)).
--endif.
+-export([write_raw_to_stdout/0]).
%%-define(debug, true).
-ifdef(debug).
--define(format(S, A), io:format(S, A)).
-define(dbg(Data),io:format(standard_error, "DBG: ~p\r\n",[Data])).
--define(RM_RF(Dir),begin io:format(standard_error, "Not Removed: ~p\r\n",[Dir]),
- ok end).
-else.
--define(format(S, A), ok).
-define(dbg(Data),noop).
--define(RM_RF(Dir),rm_rf(Dir)).
-endif.
-init_per_testcase(_Case, Config) ->
- Term = os:getenv("TERM", "dumb"),
- os:putenv("TERM","vt100"),
- [{term, Term} | Config].
-end_per_testcase(_Case, Config) ->
- Term = proplists:get_value(term,Config),
- os:putenv("TERM",Term),
- ok.
-
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,5}}].
@@ -78,16 +52,21 @@ suite() ->
all() ->
[setopts_getopts, unicode_options, unicode_options_gen,
binary_options, read_modes_gl, read_modes_ogl,
- broken_unicode, eof_on_pipe, unicode_prompt].
+ broken_unicode, eof_on_pipe, unicode_prompt,
+ shell_slogan, raw_stdout, raw_stdout_isatty].
groups() ->
[].
init_per_suite(Config) ->
- DefShell = get_default_shell(),
- [{default_shell,DefShell}|Config].
+ Term = os:getenv("TERM", "dumb"),
+ os:putenv("TERM","vt100"),
+ DefShell = rtnode:get_default_shell(),
+ [{default_shell,DefShell},{term, Term}|Config].
-end_per_suite(_Config) ->
+end_per_suite(Config) ->
+ Term = proplists:get_value(term,Config),
+ os:putenv("TERM",Term),
ok.
init_per_group(_GroupName, Config) ->
@@ -96,13 +75,11 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
-
-
-record(state, {
- q = [],
- nxt = eof,
- mode = list
- }).
+ q = [],
+ nxt = eof,
+ mode = list
+ }).
uprompt(_L) ->
[1050,1072,1082,1074,1086,32,1077,32,85,110,105,99,111,100,101,32,63].
@@ -111,41 +88,73 @@ uprompt(_L) ->
unicode_prompt(Config) when is_list(Config) ->
PA = filename:dirname(code:which(?MODULE)),
case proplists:get_value(default_shell,Config) of
- old ->
- ok;
new ->
- rtnode([{putline,""},
- {putline, "2."},
- {getline, "2"},
- {putline, "shell:prompt_func({io_proto_SUITE,uprompt})."},
- {getline, "default"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline, "\"hej\\n\""},
- {putline, "io:setopts([{binary,true}])."},
- {getline, "ok"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline, "<<\"hej\\n\">>"}
- ],[],[],"-pa \""++ PA++"\"")
+ rtnode:run(
+ [{putline,""},
+ {putline, "2."},
+ {expect, "[\n ]2"},
+ {putline, "shell:prompt_func({io_proto_SUITE,uprompt})."},
+ {expect, "[\n ]default"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect, "\\Q\"hej\\n\"\\E"},
+ {putline, "io:setopts([{binary,true}])."},
+ {expect, "[\n ]ok"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect,"[\n ]hej"},
+ {expect, "\\Q<<\"hej\\n\">>\\E"}
+ ],[],"",["-pa",PA]);
+ _ ->
+ ok
end,
%% And one with oldshell
- rtnode([{putline,""},
- {putline, "2."},
- {getline_re, ".*2$"},
- {putline, "shell:prompt_func({io_proto_SUITE,uprompt})."},
- {getline_re, ".*default"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline_re, ".*\"hej\\\\n\""},
- {putline, "io:setopts([{binary,true}])."},
- {getline_re, ".*ok"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline_re, ".*<<\"hej\\\\n\">>"}
- ],[],[],"-oldshell -pa \""++PA++"\""),
+ rtnode:run(
+ [{putline,""},
+ {putline, "2."},
+ {expect, "[\n ]2"},
+ {putline, "shell:prompt_func({io_proto_SUITE,uprompt})."},
+ {expect, "default"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect, "\\Q\"hej\\n\"\\E"},
+ {putline, "io:setopts([{binary,true}])."},
+ {expect, "[\n ]\\?*ok"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect,"[\n ]\\?*hej"},
+ {expect, "\\Q<<\"hej\\n\">>\\E"}
+ ],[],"",["-oldshell","-pa",PA]),
ok.
+%% Test that an Unicode prompt does not crash the shell.
+shell_slogan(Config) when is_list(Config) ->
+ PA = filename:dirname(code:which(?MODULE)),
+ case proplists:get_value(default_shell,Config) of
+ new ->
+ rtnode:run(
+ [{expect, "\\Q"++string:trim(erlang:system_info(system_version))++"\\E"},
+ {expect, "\\Q"++io_lib:format("Eshell V~s (press Ctrl+G to abort, type help(). for help)",[erlang:system_info(version)])++"\\E"}
+ ],[],"",[]),
+ rtnode:run(
+ [{expect, "\nTest slogan"},
+ {expect, "\nTest session slogan \\("}
+ ],[],"",["-stdlib","shell_slogan","\"Test slogan\"",
+ "-stdlib","shell_session_slogan","\"Test session slogan\""]),
+ rtnode:run(
+ [{expect, "\nTest slogan"},
+ {expect, "\\Q\nTest session slogan (\\E"}
+ ],[],"",["-stdlib","shell_slogan","fun io_proto_SUITE:slogan/0",
+ "-stdlib","shell_session_slogan","fun io_proto_SUITE:session_slogan/0",
+ "-pa",PA]);
+ _ ->
+ ok
+ end.
+
+slogan() ->
+ "Test slogan".
+session_slogan() ->
+ "Test session slogan".
%% Check io:setopts and io:getopts functions.
setopts_getopts(Config) when is_list(Config) ->
@@ -222,40 +231,42 @@ setopts_getopts(Config) when is_list(Config) ->
eof = io:get_line(RFile,''),
file:close(RFile),
case proplists:get_value(default_shell,Config) of
- old ->
- ok;
new ->
%% So, lets test another node with new interactive shell
- rtnode([{putline,""},
- {putline, "2."},
- {getline, "2"},
- {putline, "lists:keyfind(binary,1,io:getopts())."},
- {getline, "{binary,false}"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline, "\"hej\\n\""},
- {putline, "io:setopts([{binary,true}])."},
- {getline, "ok"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline, "<<\"hej\\n\">>"}
- ],[])
+ rtnode:run(
+ [{putline,""},
+ {putline, "2."},
+ {expect, "[\n ]2[^.]"},
+ {putline, "lists:keyfind(binary,1,io:getopts())."},
+ {expect, "{binary,false}"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect, "\\Q\"hej\\n\"\\E"},
+ {putline, "io:setopts([{binary,true}])."},
+ {expect, "[\n ]ok"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect, "\\Q<<\"hej\\n\">>\\E"}
+ ],[]);
+ _ ->
+ ok
end,
%% And one with oldshell
- rtnode([{putline,""},
- {putline, "2."},
- {getline_re, ".*2$"},
- {putline, "lists:keyfind(binary,1,io:getopts())."},
- {getline_re, ".*{binary,false}"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline_re, ".*\"hej\\\\n\""},
- {putline, "io:setopts([{binary,true}])."},
- {getline_re, ".*ok"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline_re, ".*<<\"hej\\\\n\">>"}
- ],[],[],"-oldshell"),
+ rtnode:run(
+ [{putline,""},
+ {putline, "2."},
+ {expect, "[\n ]2[^.]"},
+ {putline, "lists:keyfind(binary,1,io:getopts())."},
+ {expect, "[\n ]{binary,false}"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect, "\\Q\"hej\\n\"\\E"},
+ {putline, "io:setopts([{binary,true}])."},
+ {expect, "[\n ]ok"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect, "\\Q<<\"hej\\n\">>\\E"}
+ ],[],"",["-oldshell"]),
ok.
@@ -419,42 +430,38 @@ unicode_options(Config) when is_list(Config) ->
[ ok = CannotWriteFile(F,FailDir) || F <- AllNoBom ],
case proplists:get_value(default_shell,Config) of
- old ->
- ok;
new ->
%% OK, time for the group_leaders...
- rtnode([{putline,""},
- {putline, "2."},
- {getline, "2"},
- {putline, "lists:keyfind(encoding,1,io:getopts())."},
- {getline, "{encoding,latin1}"},
- {putline, "io:format(\"~ts~n\",[[1024]])."},
- {getline, "\\x{400}"},
- {putline, "io:setopts([unicode])."},
- {getline, "ok"},
- {putline, "io:format(\"~ts~n\",[[1024]])."},
- {getline,
- binary_to_list(unicode:characters_to_binary(
- [1024],unicode,utf8))}
- ],[],"LC_CTYPE=\""++get_lc_ctype()++"\"; "
- "export LC_CTYPE; ")
+ rtnode:run(
+ [{putline,""},
+ {putline, "2."},
+ {expect, "[\n ]2[^.]"},
+ {putline, "lists:keyfind(encoding,1,io:getopts())."},
+ {expect, "{encoding,latin1}"},
+ {putline, "io:format(\"~ts~n\",[[1024]])."},
+ {expect, "\\Q\\x{400}\\E"},
+ {putline, "io:setopts([unicode])."},
+ {expect, "[\n ]ok"},
+ {putline, "io:format(\"~ts~n\",[[1024]])."},
+ {expect, "[\n ]"++[1024]}
+ ],[],"",["-env","LC_ALL",get_lc_ctype()]);
+ _ ->
+ ok
end,
- rtnode([{putline,""},
- {putline, "2."},
- {getline_re, ".*2$"},
- {putline, "lists:keyfind(encoding,1,io:getopts())."},
- {getline_re, ".*{encoding,latin1}"},
- {putline, "io:format(\"~ts~n\",[[1024]])."},
- {getline_re, ".*\\\\x{400\\}"},
- {putline, "io:setopts([{encoding,unicode}])."},
- {getline_re, ".*ok"},
- {putline, "io:format(\"~ts~n\",[[1024]])."},
- {getline_re,
- ".*"++binary_to_list(unicode:characters_to_binary(
- [1024],unicode,utf8))}
- ],[],"LC_CTYPE=\""++get_lc_ctype()++"\"; export LC_CTYPE; ",
- " -oldshell "),
-
+ rtnode:run(
+ [{putline,""},
+ {putline, "2."},
+ {expect, "[\n ]2[^.]"},
+ {putline, "lists:keyfind(encoding,1,io:getopts())."},
+ {expect, "[\n ]{encoding,latin1}"},
+ {putline, "io:format(\"~ts~n\",[[1024]])."},
+ {expect, "\\Q\\x{400}\\E"},
+ {putline, "io:setopts([{encoding,unicode}])."},
+ {expect, "[\n ]ok"},
+ {putline, "io:format(\"~ts~n\",[[1024]])."},
+ {expect, "[\n ]"++[1024]}
+ ],[],"",
+ ["-oldshell","-env","LC_ALL",get_lc_ctype()]),
ok.
%% Tests various unicode options on random generated files.
@@ -709,115 +716,124 @@ binary_options(Config) when is_list(Config) ->
%% OK, time for the group_leaders...
case proplists:get_value(default_shell,Config) of
- old ->
- ok;
new ->
- rtnode([{putline, "2."},
- {getline, "2"},
- {putline, "lists:keyfind(binary,1,io:getopts())."},
- {getline, "{binary,false}"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline, "\"hej\\n\""},
- {putline, "io:setopts([{binary,true},unicode])."},
- {getline, "ok"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline, "<<\"hej\\n\">>"},
- {putline, "io:get_line('')."},
- {putline, binary_to_list(<<"\345\344\366"/utf8>>)},
- {getline, "<<\""++binary_to_list(<<"\345\344\366"/utf8>>)++"\\n\"/utf8>>"}
- ],[])
+ rtnode:run(
+ [{putline, "2."},
+ {expect, "[\n ]2[^.]"},
+ {putline, "lists:keyfind(binary,1,io:getopts())."},
+ {expect, "[\n ]{binary,false}"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect, "\\Q\"hej\\n\"\\E"},
+ {putline, "io:setopts([{binary,true},unicode])."},
+ {expect, "[\n ]ok"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect, "\\Q<<\"hej\\n\">>\\E"},
+ {putline, "io:get_line('')."},
+ {putline, binary_to_list(<<"\345\344\366"/utf8>>)},
+ {expect, latin1, "[\n ]\\Q<<\""++binary_to_list(<<"\345\344\366"/utf8>>)++"\\n\"/utf8>>\\E"}
+ ],[]);
+ _ ->
+ ok
end,
%% And one with oldshell
- rtnode([{putline, "2."},
- {getline_re, ".*2$"},
- {putline, "lists:keyfind(binary,1,io:getopts())."},
- {getline_re, ".*{binary,false}"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline_re, ".*\"hej\\\\n\""},
- {putline, "io:setopts([{binary,true},unicode])."},
- {getline_re, ".*ok"},
- {putline, "io:get_line('')."},
- {putline, "hej"},
- {getline_re, ".*<<\"hej\\\\n\">>"},
- {putline, "io:get_line('')."},
- {putline, binary_to_list(<<"\345\344\366"/utf8>>)},
- {getline_re, ".*<<\""++binary_to_list(<<"\345\344\366"/utf8>>)++"\\\\n\"/utf8>>"}
- ],[],[],"-oldshell"),
+ rtnode:run(
+ [{putline, "2."},
+ {expect, "[\n ]2[^.]"},
+ {putline, "lists:keyfind(binary,1,io:getopts())."},
+ {expect, "[\n ]{binary,false}"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect, "[\n ]\\Q\"hej\\n\"\\E"},
+ {putline, "io:setopts([{binary,true},unicode])."},
+ {expect, "[\n ]ok"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {expect, "\\Q<<\"hej\\n\">>\\E"},
+ {putline, "io:get_line('')."},
+ {putline, binary_to_list(<<"\345\344\366"/utf8>>)},
+ {expect, latin1, "[\n ]\\Q<<\""++binary_to_list(<<"\345\344\366"/utf8>>)++"\\n\"/utf8>>\\E"}
+ ],[],"",["-oldshell"]),
ok.
-
-
-
answering_machine1(OthNode,OthReg,Me) ->
TestDataLine1 = [229,228,246],
TestDataUtf = binary_to_list(unicode:characters_to_binary(TestDataLine1)),
- rtnode([{putline,""},
- {putline, "2."},
- {getline, "2"},
- {putline, "{"++OthReg++","++OthNode++"} ! group_leader()."},
- {getline, "<"},
- %% get_line
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"},
- %% get_chars
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"},
- %% fread
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"}
-
- ],Me,"LC_CTYPE=\""++get_lc_ctype()++"\"; export LC_CTYPE; "),
+ TestDataLine1Oct = "\\\\345( \b)*\\\\344( \b)*\\\\366",
+ rtnode:run(
+ [{putline,""},
+ {putline, "2."},
+ {expect, "2"},
+ {putline, "io:getopts()."},
+ {expect, ">"},
+ {putline, "{"++OthReg++","++OthNode++"} ! group_leader()."},
+ {expect, "<"},
+ %% get_line
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1Oct},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1Oct},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"},
+ %% get_chars
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1Oct},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1Oct},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"},
+ %% fread
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1Oct},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1Oct},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"}
+
+ ],Me,"",["-env","LC_ALL",get_lc_ctype()]),
O = list_to_atom(OthReg),
O ! {self(),done},
ok.
@@ -825,70 +841,77 @@ answering_machine1(OthNode,OthReg,Me) ->
answering_machine2(OthNode,OthReg,Me) ->
TestDataLine1 = [229,228,246],
TestDataUtf = binary_to_list(unicode:characters_to_binary(TestDataLine1)),
- rtnode([{putline,""},
- {putline, "2."},
- {getline, "2"},
- {putline, "{"++OthReg++","++OthNode++"} ! group_leader()."},
- {getline_re, ".*<[0-9].*"},
- %% get_line
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"},
- %% get_chars
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"},
- %% fread
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, "Hej"},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataLine1},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"},
- {getline_re, ".*Prompt"},
- {putline, TestDataUtf},
- {getline_re, ".*Okej"}
-
- ],Me,"LC_CTYPE=\""++get_lc_ctype()++"\"; export LC_CTYPE; "," -oldshell "),
+ rtnode:run(
+ [{putline,""},
+ {putline, "2."},
+ {expect, "2"},
+ {putline, "{"++OthReg++","++OthNode++"} ! group_leader()."},
+ {expect, "<[0-9].*"},
+ %% get_line
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"},
+ %% get_chars
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"},
+ %% fread
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, "Hej"},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataLine1},
+ {expect, latin1, "\n" ++ TestDataLine1},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"},
+ {expect, "Prompt"},
+ {putline, TestDataUtf},
+ {expect, "Okej"}
+
+ ],Me,"",["-oldshell","-env","LC_ALL",get_lc_ctype()]),
O = list_to_atom(OthReg),
O ! {self(),done},
ok.
@@ -896,20 +919,20 @@ answering_machine2(OthNode,OthReg,Me) ->
%% Test various modes when reading from the group leade from another machine.
read_modes_ogl(Config) when is_list(Config) ->
- case get_progs() of
- {error,Reason} ->
- {skipped,Reason};
+ case proplists:get_value(default_shell,Config) of
+ noshell ->
+ {skipped,"No run_erl"};
_ ->
read_modes_gl_1(Config,answering_machine2)
end.
%% Test various modes when reading from the group leade from another machine.
read_modes_gl(Config) when is_list(Config) ->
- case {get_progs(),proplists:get_value(default_shell,Config)} of
- {{error,Reason},_} ->
- {skipped,Reason};
- {_,old} ->
- {skipper,"No new shell"};
+ case proplists:get_value(default_shell,Config) of
+ noshell ->
+ {skipped,"No run_erl"};
+ old ->
+ {skipped,"No new shell"};
_ ->
read_modes_gl_1(Config,answering_machine1)
end.
@@ -919,7 +942,7 @@ read_modes_gl_1(_Config,Machine) ->
TestDataLine1BinUtf = unicode:characters_to_binary(TestDataLine1),
TestDataLine1BinLatin = list_to_binary(TestDataLine1),
- {ok,N2List} = create_nodename(),
+ N2List = peer:random_name(?FUNCTION_NAME),
MyNodeList = atom2list(node()),
register(io_proto_suite,self()),
AM1 = spawn(?MODULE,Machine,
@@ -1023,14 +1046,10 @@ loop_through_file2(_,{error,_Err},_,_) ->
loop_through_file2(F,Bin,Chunk,Enc) when is_binary(Bin) ->
loop_through_file2(F,io:get_chars(F,'',Chunk),Chunk,Enc).
-
-
%% Test eof before newline on stdin when erlang is in pipe.
eof_on_pipe(Config) when is_list(Config) ->
- case {get_progs(),os:type()} of
- {{error,Reason},_} ->
- {skipped,Reason};
- {{_,_,Erl},{unix,linux}} ->
+ case {ct:get_progname(),os:type()} of
+ {Erl,{unix,linux}} ->
%% Not even Linux is reliable - echo can be both styles
try
EchoLine = case os:cmd("echo -ne \"test\\ntest\"") of
@@ -1074,453 +1093,45 @@ eof_on_pipe(Config) when is_list(Config) ->
{skipped,"Only on linux"}
end.
+raw_stdout(Config) when is_list(Config) ->
+ Cmd = lists:append(
+ [ct:get_progname(),
+ " -noshell -noinput",
+ " -pa ", filename:dirname(code:which(?MODULE)),
+ " -s ", atom_to_list(?MODULE), " write_raw_to_stdout"]),
+ ct:log("~p~n", [Cmd]),
+ Port = open_port({spawn, Cmd}, [stream, eof]),
+ Expected = lists:seq(0,255),
+ Expected = get_all_port_data(Port, []),
+ Port ! {self(), close},
+ ok.
-%%
-%% Tool for running interactive shell (stolen from the kernel
-%% test suite interactive_shell_SUITE)
-%%
--undef(line).
--define(line,).
-rtnode(C,N) ->
- rtnode(C,N,[]).
-rtnode(Commands,Nodename,ErlPrefix) ->
- rtnode(Commands,Nodename,ErlPrefix,[]).
-rtnode(Commands,Nodename,ErlPrefix,Extra) ->
- case get_progs() of
- {error,_Reason} ->
- {skip,"No runerl present"};
- {RunErl,ToErl,Erl} ->
- case create_tempdir() of
- {error, Reason2} ->
- {skip, Reason2};
- Tempdir ->
- SPid = start_runerl_node(RunErl, ErlPrefix++
- "\\\""++Erl++"\\\"",
- Tempdir, Nodename, Extra),
- CPid = start_toerl_server(ToErl, Tempdir),
- put(getline_skipped, []),
- Res = (catch get_and_put(CPid, Commands, 1)),
- case stop_runerl_node(CPid) of
- {error,_} ->
- CPid2 = start_toerl_server(ToErl, Tempdir),
- put(getline_skipped, []),
- ok = get_and_put
- (CPid2,
- [{putline,[7]},
- {sleep,
- timeout(short)},
- {putline,""},
- {getline," -->"},
- {putline,"s"},
- {putline,"c"},
- {putline,""}], 1),
- stop_runerl_node(CPid2);
- _ ->
- ok
- end,
- wait_for_runerl_server(SPid),
- ok = ?RM_RF(Tempdir),
- ok = Res
- end
- end.
-
-timeout(long) ->
- 2 * timeout(normal);
-timeout(short) ->
- timeout(normal) div 10;
-timeout(normal) ->
- 10000 * test_server:timetrap_scale_factor().
-
-
-%% start_noshell_node(Name) ->
-%% PADir = filename:dirname(code:which(?MODULE)),
-%% {ok, Node} = test_server:start_node(Name,slave,[{args," -noshell -pa "++
-%% PADir++" "}]),
-%% Node.
-%% stop_noshell_node(Node) ->
-%% test_server:stop_node(Node).
-
--ifndef(debug).
-rm_rf(Dir) ->
- try
- {ok,List} = file:list_dir(Dir),
- Files = [filename:join([Dir,X]) || X <- List],
- [case file:list_dir(Y) of
- {error, enotdir} ->
- ok = file:delete(Y);
- _ ->
- ok = rm_rf(Y)
- end || Y <- Files],
- ok = file:del_dir(Dir),
- ok
- catch
- _:Exception -> {error, {Exception,Dir}}
- end.
--endif.
-
-get_and_put(_CPid,[],_) ->
- ok;
-get_and_put(CPid, [{sleep, X}|T],N) ->
- ?dbg({sleep, X}),
- receive
- after X ->
- get_and_put(CPid,T,N+1)
- end;
-get_and_put(CPid, [{getline_pred,Pred,Msg}|T]=T0, N)
- when is_function(Pred) ->
- ?dbg({getline, Match}),
- CPid ! {self(), {get_line, timeout(normal)}},
- receive
- {get_line, timeout} ->
- error_logger:error_msg("~p: getline timeout waiting for \"~s\" "
- "(command number ~p, skipped: ~p)~n",
- [?MODULE,Msg,N,get(getline_skipped)]),
- {error, timeout};
- {get_line, Data} ->
- ?dbg({data,Data}),
- case Pred(Data) of
- yes ->
- put(getline_skipped, []),
- get_and_put(CPid, T,N+1);
- no ->
- error_logger:error_msg("~p: getline match failure "
- "\"~s\" "
- "(command number ~p)\n",
- [?MODULE,Msg,N]),
- {error, no_match};
- 'maybe' ->
- List = get(getline_skipped),
- put(getline_skipped, List ++ [Data]),
- get_and_put(CPid, T0, N)
- end
- end;
-get_and_put(CPid, [{getline, Match}|T],N) ->
- ?dbg({getline, Match}),
- F = fun(Data) ->
- case lists:prefix(Match, Data) of
- true -> yes;
- false -> 'maybe'
- end
- end,
- get_and_put(CPid, [{getline_pred,F,Match}|T], N);
-get_and_put(CPid, [{getline_re, Match}|T],N) ->
- F = fun(Data) ->
- case re:run(Data, Match, [{capture,none}]) of
- match -> yes;
- _ -> 'maybe'
- end
- end,
- get_and_put(CPid, [{getline_pred,F,Match}|T], N);
-get_and_put(CPid, [{putline_raw, Line}|T],N) ->
- ?dbg({putline_raw, Line}),
- CPid ! {self(), {send_line, Line}},
- Timeout = timeout(normal),
- receive
- {send_line, ok} ->
- get_and_put(CPid, T,N+1)
- after Timeout ->
- error_logger:error_msg("~p: putline_raw timeout (~p) sending "
- "\"~s\" (command number ~p)~n",
- [?MODULE, Timeout, Line, N]),
- {error, timeout}
- end;
-
-get_and_put(CPid, [{putline, Line}|T],N) ->
- ?dbg({putline, Line}),
- CPid ! {self(), {send_line, Line}},
- Timeout = timeout(normal),
- receive
- {send_line, ok} ->
- get_and_put(CPid, [{getline, []}|T],N)
- after Timeout ->
- error_logger:error_msg("~p: putline timeout (~p) sending "
- "\"~s\" (command number ~p)~n[~p]~n",
- [?MODULE, Timeout, Line, N,get()]),
- {error, timeout}
- end.
-
-wait_for_runerl_server(SPid) ->
- Ref = erlang:monitor(process, SPid),
- Timeout = timeout(long),
- receive
- {'DOWN', Ref, process, SPid, _} ->
- ok
- after Timeout ->
- {error, timeout}
- end.
-
-
-
-stop_runerl_node(CPid) ->
- Ref = erlang:monitor(process, CPid),
- CPid ! {self(), kill_emulator},
- Timeout = timeout(long),
- receive
- {'DOWN', Ref, process, CPid, noproc} ->
- ok;
- {'DOWN', Ref, process, CPid, normal} ->
- ok;
- {'DOWN', Ref, process, CPid, {error, Reason}} ->
- {error, Reason}
- after Timeout ->
- {error, timeout}
- end.
-
-get_progs() ->
- case os:type() of
- {unix,freebsd} ->
- {error,"cant use run_erl on freebsd"};
- {unix,openbsd} ->
- {error,"cant use run_erl on openbsd"};
- {unix,_} ->
- case os:find_executable("run_erl") of
- RE when is_list(RE) ->
- case os:find_executable("to_erl") of
- TE when is_list(TE) ->
- case os:find_executable("erl") of
- E when is_list(E) ->
- {RE,TE,E};
- _ ->
- {error, "Could not find erl command"}
- end;
- _ ->
- {error, "Could not find to_erl command"}
- end;
- _ ->
- {error, "Could not find run_erl command"}
- end;
- _ ->
- {error, "Not a unix OS"}
- end.
-
-create_tempdir() ->
- create_tempdir(filename:join(["/tmp","rtnode"++os:getpid()]),$A).
-
-create_tempdir(Dir,X) when X > $Z, X < $a ->
- create_tempdir(Dir,$a);
-create_tempdir(Dir,X) when X > $z ->
- Estr = lists:flatten(
- io_lib:format("Unable to create ~s, reason eexist",
- [Dir++[$z]])),
- {error, Estr};
-create_tempdir(Dir0, Ch) ->
- %% Expect fairly standard unix.
- Dir = Dir0++[Ch],
- case file:make_dir(Dir) of
- {error, eexist} ->
- create_tempdir(Dir0, Ch+1);
- {error, Reason} ->
- Estr = lists:flatten(
- io_lib:format("Unable to create ~s, reason ~p",
- [Dir,Reason])),
- {error,Estr};
- ok ->
- Dir
- end.
-
-create_nodename() ->
- create_nodename($A).
-
-create_nodename(X) when X > $Z, X < $a ->
- create_nodename($a);
-create_nodename(X) when X > $z ->
- {error,out_of_nodenames};
-create_nodename(X) ->
- NN = "rtnode"++os:getpid()++[X],
- case file:read_file_info(filename:join(["/tmp",NN])) of
- {error,enoent} ->
- Host = lists:nth(2,string:tokens(atom_to_list(node()),"@")),
- {ok,NN++"@"++Host};
- _ ->
- create_nodename(X+1)
- end.
-
-
-start_runerl_node(RunErl,Erl,Tempdir,Nodename,Extra) ->
- XArg = case Nodename of
- [] ->
- [];
- _ ->
- " -sname "++(if is_atom(Nodename) -> atom_to_list(Nodename);
- true -> Nodename
- end)++
- " -setcookie "++atom_to_list(erlang:get_cookie())
- end,
- XXArg = case Extra of
- [] ->
- [];
- _ ->
- " "++Extra
- end,
- spawn(fun() ->
- ?dbg("\""++RunErl++"\" "++Tempdir++"/ "++Tempdir++
- " \""++Erl++XArg++XXArg++"\""),
- os:cmd("\""++RunErl++"\" "++Tempdir++"/ "++Tempdir++
- " \""++Erl++XArg++XXArg++"\"")
- end).
-
-start_toerl_server(ToErl,Tempdir) ->
- Pid = spawn(?MODULE,toerl_server,[self(),ToErl,Tempdir]),
- receive
- {Pid,started} ->
- Pid;
- {Pid,error,Reason} ->
- {error,Reason}
- end.
-
-try_to_erl(_Command, 0) ->
- {error, cannot_to_erl};
-try_to_erl(Command, N) ->
- ?dbg({?LINE,N}),
- Port = open_port({spawn, Command},[eof,{line,1000}]),
- Timeout = timeout(normal) div 2,
- receive
- {Port, eof} ->
- receive after Timeout ->
- ok
- end,
- try_to_erl(Command, N-1)
- after Timeout ->
- ?dbg(Port),
- Port
- end.
-
-toerl_server(Parent,ToErl,Tempdir) ->
- Port = try_to_erl("\""++ToErl++"\" "++Tempdir++"/ 2>/dev/null",8),
- case Port of
- P when is_port(P) ->
- Parent ! {self(),started};
- {error,Other} ->
- Parent ! {self(),error,Other},
- exit(Other)
- end,
- case toerl_loop(Port,[]) of
- normal ->
- ok;
- {error, Reason} ->
- error_logger:error_msg("toerl_server exit with reason ~p~n",
- [Reason]),
- exit(Reason)
- end.
-
-toerl_loop(Port,Acc) ->
- ?dbg({toerl_loop, Port, Acc}),
- receive
- {Port,{data,{Tag0,Data}}} when is_port(Port) ->
- ?dbg({?LINE,Port,{data,{Tag0,Data}}}),
- case Acc of
- [{noeol,Data0}|T0] ->
- toerl_loop(Port,[{Tag0, Data0++Data}|T0]);
- _ ->
- toerl_loop(Port,[{Tag0,Data}|Acc])
- end;
- {Pid,{get_line,Timeout}} ->
- case Acc of
- [] ->
- case get_data_within(Port,Timeout,[]) of
- timeout ->
- Pid ! {get_line, timeout},
- toerl_loop(Port,[]);
- {noeol,Data1} ->
- Pid ! {get_line, timeout},
- toerl_loop(Port,[{noeol,Data1}]);
- {eol,Data2} ->
- Pid ! {get_line, Data2},
- toerl_loop(Port,[])
- end;
- [{noeol,Data3}] ->
- case get_data_within(Port,Timeout,Data3) of
- timeout ->
- Pid ! {get_line, timeout},
- toerl_loop(Port,Acc);
- {noeol,Data4} ->
- Pid ! {get_line, timeout},
- toerl_loop(Port,[{noeol,Data4}]);
- {eol,Data5} ->
- Pid ! {get_line, Data5},
- toerl_loop(Port,[])
- end;
- List ->
- {NewAcc,[{eol,Data6}]} = lists:split(length(List)-1,List),
- Pid ! {get_line,Data6},
- toerl_loop(Port,NewAcc)
- end;
- {Pid, {send_line, Data7}} ->
- Port ! {self(),{command, Data7++"\n"}},
- Pid ! {send_line, ok},
- toerl_loop(Port,Acc);
- {_Pid, kill_emulator} ->
- Port ! {self(),{command, "init:stop().\n"}},
- Timeout1 = timeout(long),
- receive
- {Port,eof} ->
- normal
- after Timeout1 ->
- {error, kill_timeout}
- end;
- {Port, eof} ->
- {error, unexpected_eof};
- Other ->
- {error, {unexpected, Other}}
- end.
-
-millistamp() ->
- erlang:monotonic_time(millisecond).
-
-get_data_within(Port, X, Acc) when X =< 0 ->
- ?dbg({get_data_within, X, Acc, ?LINE}),
+get_all_port_data(Port, Acc) ->
receive
- {Port,{data,{Tag0,Data}}} ->
- ?dbg({?LINE,Port,{data,{Tag0,Data}}}),
- {Tag0, Acc++Data}
- after 0 ->
- case Acc of
- [] ->
- timeout;
- Noeol ->
- {noeol,Noeol}
- end
- end;
-
-
-get_data_within(Port, Timeout, Acc) ->
- ?dbg({get_data_within, Timeout, Acc, ?LINE}),
- T1 = millistamp(),
- receive
- {Port,{data,{noeol,Data}}} ->
- ?dbg({?LINE,Port,{data,{noeol,Data}}}),
- Elapsed = millistamp() - T1 + 1,
- get_data_within(Port, Timeout - Elapsed, Acc ++ Data);
- {Port,{data,{eol,Data1}}} ->
- ?dbg({?LINE,Port,{data,{eol,Data1}}}),
- {eol, Acc ++ Data1}
- after Timeout ->
- timeout
+ {Port, {data, Data}} ->
+ get_all_port_data(Port, [Acc|Data]);
+ {Port, eof} ->
+ lists:flatten(Acc)
end.
-get_default_shell() ->
- Match = fun(Data) ->
- case lists:prefix("undefined", Data) of
- true ->
- yes;
- false ->
- case re:run(Data, "<\\d+[.]\\d+[.]\\d+>",
- [{capture,none}]) of
- match -> no;
- _ -> 'maybe'
- end
- end
- end,
+write_raw_to_stdout() ->
try
- rtnode([{putline,""},
- {putline, "whereis(user_drv)."},
- {getline_pred, Match, "matching of user_drv pid"}], []),
- old
- catch _E:_R ->
- ?dbg({_E,_R}),
- new
+ ok = io:setopts(standard_io, [{encoding, latin1}]),
+ ok = file:write(standard_io, lists:seq(0,255)),
+ halt(0)
+ catch
+ Class:Reason:StackTrace ->
+ io:format(standard_error, "~p~p~p", [Class, Reason, StackTrace]),
+ halt(17)
end.
+raw_stdout_isatty(Config) when is_list(Config) ->
+ rtnode:run(
+ [{putline,"io:setopts(group_leader(), [{encoding, latin1}])."},
+ {putline,"file:write(group_leader(),[90, 127, 128, 255, 131, 90, 10])."},%
+ {expect, "\\QZ^?\\200\\377\\203Z\\E"}
+ ],[]),
+ ok.
%%
%% Test I/O-server
%%
diff --git a/lib/stdlib/test/lists_SUITE.erl b/lib/stdlib/test/lists_SUITE.erl
index b369b6918e..59f2e8bd03 100644
--- a/lib/stdlib/test/lists_SUITE.erl
+++ b/lib/stdlib/test/lists_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -55,6 +55,10 @@
ufunsort_error/1,
uniq_1/1, uniq_2/1,
zip_unzip/1, zip_unzip3/1, zipwith/1, zipwith3/1,
+ zip_fail/1, zip_trim/1, zip_pad/1,
+ zip3_fail/1, zip3_trim/1, zip3_pad/1,
+ zipwith_fail/1, zipwith_trim/1, zipwith_pad/1,
+ zipwith3_fail/1, zipwith3_trim/1, zipwith3_pad/1,
filter_partition/1,
join/1,
otp_5939/1, otp_6023/1, otp_6606/1, otp_7230/1,
@@ -121,7 +125,11 @@ groups() ->
{flatten, [parallel],
[flatten_1, flatten_2, flatten_1_e, flatten_2_e]},
{tickets, [parallel], [otp_5939, otp_6023, otp_6606, otp_7230]},
- {zip, [parallel], [zip_unzip, zip_unzip3, zipwith, zipwith3]},
+ {zip, [parallel], [zip_unzip, zip_unzip3, zipwith, zipwith3,
+ zip_fail, zip_trim, zip_pad,
+ zip3_fail, zip3_trim, zip3_pad,
+ zipwith_fail, zipwith_trim, zipwith_pad,
+ zipwith3_fail, zipwith3_trim, zipwith3_pad]},
{uniq, [parallel], [uniq_1, uniq_2]},
{misc, [parallel], [reverse, member, dropwhile, takewhile,
filter_partition, suffix, subtract, join,
@@ -433,6 +441,8 @@ keyreplace(Config) when is_list(Config) ->
merge(Config) when is_list(Config) ->
+ Singleton = id([a, b, c]),
+
%% merge list of lists
[] = lists:merge([]),
[] = lists:merge([[]]),
@@ -455,6 +465,33 @@ merge(Config) when is_list(Config) ->
Seq = lists:seq(1,100),
true = Seq == lists:merge(lists:map(fun(E) -> [E] end, Seq)),
+ true = erts_debug:same(Singleton, lists:merge([Singleton])),
+ true = erts_debug:same(Singleton, lists:merge([Singleton, []])),
+ true = erts_debug:same(Singleton, lists:merge([[], Singleton])),
+ true = erts_debug:same(Singleton, lists:merge([Singleton, [], []])),
+ true = erts_debug:same(Singleton, lists:merge([[], Singleton, []])),
+ true = erts_debug:same(Singleton, lists:merge([[], [], Singleton])),
+
+ {'EXIT', _} = (catch lists:merge([a])),
+ {'EXIT', _} = (catch lists:merge([a, b])),
+ {'EXIT', _} = (catch lists:merge([a, []])),
+ {'EXIT', _} = (catch lists:merge([[], b])),
+ {'EXIT', _} = (catch lists:merge([a, [1, 2, 3]])),
+ {'EXIT', _} = (catch lists:merge([[1, 2, 3], b])),
+ {'EXIT', _} = (catch lists:merge([a, b, c])),
+ {'EXIT', _} = (catch lists:merge([a, b, []])),
+ {'EXIT', _} = (catch lists:merge([a, [], c])),
+ {'EXIT', _} = (catch lists:merge([a, [], []])),
+ {'EXIT', _} = (catch lists:merge([[], b, c])),
+ {'EXIT', _} = (catch lists:merge([[], b, []])),
+ {'EXIT', _} = (catch lists:merge([[], [], c])),
+ {'EXIT', _} = (catch lists:merge([a, b, [1, 2, 3]])),
+ {'EXIT', _} = (catch lists:merge([a, [1, 2, 3], c])),
+ {'EXIT', _} = (catch lists:merge([a, [1, 2, 3], [4, 5, 6]])),
+ {'EXIT', _} = (catch lists:merge([[1, 2, 3], b, c])),
+ {'EXIT', _} = (catch lists:merge([[1, 2, 3], b, [4, 5, 6]])),
+ {'EXIT', _} = (catch lists:merge([[1, 2, 3], [4, 5, 6], c])),
+
Two = [1,2],
Six = [1,2,3,4,5,6],
@@ -474,6 +511,15 @@ merge(Config) when is_list(Config) ->
[1,2,3,4,5,7] = lists:merge([2,4], [1,3,5,7]),
[1,2,3,4,5,6,7] = lists:merge([2,4,6], [1,3,5,7]),
+ true = erts_debug:same(Singleton, lists:merge([], Singleton)),
+ true = erts_debug:same(Singleton, lists:merge(Singleton, [])),
+
+ {'EXIT', _} = (catch lists:merge(a, b)),
+ {'EXIT', _} = (catch lists:merge(a, [])),
+ {'EXIT', _} = (catch lists:merge([], b)),
+ {'EXIT', _} = (catch lists:merge(a, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:merge([1, 2, 3], b)),
+
%% 3-way merge
[] = lists:merge3([], [], []),
Two = lists:merge3([], [], Two),
@@ -490,11 +536,28 @@ merge(Config) when is_list(Config) ->
Nine = lists:merge3([7,8,9],[4,5,6],[1,2,3]),
Nine = lists:merge3([4,5,6],[7,8,9],[1,2,3]),
+ true = erts_debug:same(Singleton, lists:merge3([], [], Singleton)),
+ true = erts_debug:same(Singleton, lists:merge3([], Singleton, [])),
+ true = erts_debug:same(Singleton, lists:merge3(Singleton, [], [])),
+
+ {'EXIT', _} = (catch lists:merge3(a, b, c)),
+ {'EXIT', _} = (catch lists:merge3(a, b, [])),
+ {'EXIT', _} = (catch lists:merge3(a, [], c)),
+ {'EXIT', _} = (catch lists:merge3(a, [], [])),
+ {'EXIT', _} = (catch lists:merge3([], b, [])),
+ {'EXIT', _} = (catch lists:merge3([], [], c)),
+ {'EXIT', _} = (catch lists:merge3(a, b, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:merge3(a, [1, 2, 3], c)),
+ {'EXIT', _} = (catch lists:merge3(a, [1, 2, 3], [4, 5, 6])),
+ {'EXIT', _} = (catch lists:merge3([1, 2, 3], b, [4, 5, 6])),
+ {'EXIT', _} = (catch lists:merge3([1, 2, 3], [4, 5, 6], c)),
+
ok.
%% reverse merge functions
rmerge(Config) when is_list(Config) ->
+ Singleton = id([a, b, c]),
Two = [2,1],
Six = [6,5,4,3,2,1],
@@ -514,6 +577,15 @@ rmerge(Config) when is_list(Config) ->
[7,5,4,3,2,1] = lists:rmerge([4,2], [7,5,3,1]),
[7,6,5,4,3,2,1] = lists:rmerge([6,4,2], [7,5,3,1]),
+ true = erts_debug:same(Singleton, lists:rmerge([], Singleton)),
+ true = erts_debug:same(Singleton, lists:rmerge(Singleton, [])),
+
+ {'EXIT', _} = (catch lists:rmerge(a, b)),
+ {'EXIT', _} = (catch lists:rmerge(a, [])),
+ {'EXIT', _} = (catch lists:rmerge([], b)),
+ {'EXIT', _} = (catch lists:rmerge(a, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:rmerge([1, 2, 3], b)),
+
Nine = [9,8,7,6,5,4,3,2,1],
%% 3-way reversed merge
@@ -532,6 +604,22 @@ rmerge(Config) when is_list(Config) ->
Nine = lists:rmerge3([9,8,7],[6,5,4],[3,2,1]),
Nine = lists:rmerge3([6,5,4],[9,8,7],[3,2,1]),
+ true = erts_debug:same(Singleton, lists:rmerge3([], [], Singleton)),
+ true = erts_debug:same(Singleton, lists:rmerge3([], Singleton, [])),
+ true = erts_debug:same(Singleton, lists:rmerge3(Singleton, [], [])),
+
+ {'EXIT', _} = (catch lists:rmerge3(a, b, c)),
+ {'EXIT', _} = (catch lists:rmerge3(a, b, [])),
+ {'EXIT', _} = (catch lists:rmerge3(a, [], c)),
+ {'EXIT', _} = (catch lists:rmerge3(a, [], [])),
+ {'EXIT', _} = (catch lists:rmerge3([], b, [])),
+ {'EXIT', _} = (catch lists:rmerge3([], [], c)),
+ {'EXIT', _} = (catch lists:rmerge3(a, b, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:rmerge3(a, [1, 2, 3], c)),
+ {'EXIT', _} = (catch lists:rmerge3(a, [1, 2, 3], [4, 5, 6])),
+ {'EXIT', _} = (catch lists:rmerge3([1, 2, 3], b, [4, 5, 6])),
+ {'EXIT', _} = (catch lists:rmerge3([1, 2, 3], [4, 5, 6], c)),
+
ok.
sort_1(Config) when is_list(Config) ->
@@ -632,6 +720,8 @@ usort_1(Conf) when is_list(Conf) ->
ok.
umerge(Conf) when is_list(Conf) ->
+ Singleton = id([a, b, c]),
+
%% merge list of lists
[] = lists:umerge([]),
[] = lists:umerge([[]]),
@@ -655,6 +745,33 @@ umerge(Conf) when is_list(Conf) ->
Seq = lists:seq(1,100),
true = Seq == lists:umerge(lists:map(fun(E) -> [E] end, Seq)),
+ true = erts_debug:same(Singleton, lists:umerge([Singleton])),
+ true = erts_debug:same(Singleton, lists:umerge([Singleton, []])),
+ true = erts_debug:same(Singleton, lists:umerge([[], Singleton])),
+ true = erts_debug:same(Singleton, lists:umerge([Singleton, [], []])),
+ true = erts_debug:same(Singleton, lists:umerge([[], Singleton, []])),
+ true = erts_debug:same(Singleton, lists:umerge([[], [], Singleton])),
+
+ {'EXIT', _} = (catch lists:umerge([a])),
+ {'EXIT', _} = (catch lists:umerge([a, b])),
+ {'EXIT', _} = (catch lists:umerge([a, []])),
+ {'EXIT', _} = (catch lists:umerge([[], b])),
+ {'EXIT', _} = (catch lists:umerge([a, [1, 2, 3]])),
+ {'EXIT', _} = (catch lists:umerge([[1, 2, 3], b])),
+ {'EXIT', _} = (catch lists:umerge([a, b, c])),
+ {'EXIT', _} = (catch lists:umerge([a, b, []])),
+ {'EXIT', _} = (catch lists:umerge([a, [], c])),
+ {'EXIT', _} = (catch lists:umerge([a, [], []])),
+ {'EXIT', _} = (catch lists:umerge([[], b, c])),
+ {'EXIT', _} = (catch lists:umerge([[], b, []])),
+ {'EXIT', _} = (catch lists:umerge([[], [], c])),
+ {'EXIT', _} = (catch lists:umerge([a, b, [1, 2, 3]])),
+ {'EXIT', _} = (catch lists:umerge([a, [1, 2, 3], c])),
+ {'EXIT', _} = (catch lists:umerge([a, [1, 2, 3], [4, 5, 6]])),
+ {'EXIT', _} = (catch lists:umerge([[1, 2, 3], b, c])),
+ {'EXIT', _} = (catch lists:umerge([[1, 2, 3], b, [4, 5, 6]])),
+ {'EXIT', _} = (catch lists:umerge([[1, 2, 3], [4, 5, 6], c])),
+
Two = [1,2],
Six = [1,2,3,4,5,6],
@@ -681,6 +798,15 @@ umerge(Conf) when is_list(Conf) ->
[1,2,3,4,5,7] = lists:umerge([2,4], [1,2,3,4,5,7]),
[1,2,3,4,5,6,7] = lists:umerge([2,4,6], [1,2,3,4,5,6,7]),
+ true = erts_debug:same(Singleton, lists:umerge([], Singleton)),
+ true = erts_debug:same(Singleton, lists:umerge(Singleton, [])),
+
+ {'EXIT', _} = (catch lists:umerge(a, b)),
+ {'EXIT', _} = (catch lists:umerge(a, [])),
+ {'EXIT', _} = (catch lists:umerge([], b)),
+ {'EXIT', _} = (catch lists:umerge(a, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:umerge([1, 2, 3], b)),
+
%% 3-way unique merge
[] = lists:umerge3([], [], []),
Two = lists:umerge3([], [], Two),
@@ -702,9 +828,27 @@ umerge(Conf) when is_list(Conf) ->
[1,2,3] = lists:umerge3([1,2,3],[2,3],[1,2,3]),
[1,2,3,4] = lists:umerge3([2,3,4],[3,4],[1,2,3]),
+ true = erts_debug:same(Singleton, lists:umerge3([], [], Singleton)),
+ true = erts_debug:same(Singleton, lists:umerge3([], Singleton, [])),
+ true = erts_debug:same(Singleton, lists:umerge3(Singleton, [], [])),
+
+ {'EXIT', _} = (catch lists:umerge3(a, b, c)),
+ {'EXIT', _} = (catch lists:umerge3(a, b, [])),
+ {'EXIT', _} = (catch lists:umerge3(a, [], c)),
+ {'EXIT', _} = (catch lists:umerge3(a, [], [])),
+ {'EXIT', _} = (catch lists:umerge3([], b, [])),
+ {'EXIT', _} = (catch lists:umerge3([], [], c)),
+ {'EXIT', _} = (catch lists:umerge3(a, b, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:umerge3(a, [1, 2, 3], c)),
+ {'EXIT', _} = (catch lists:umerge3(a, [1, 2, 3], [4, 5, 6])),
+ {'EXIT', _} = (catch lists:umerge3([1, 2, 3], b, [4, 5, 6])),
+ {'EXIT', _} = (catch lists:umerge3([1, 2, 3], [4, 5, 6], c)),
+
ok.
rumerge(Conf) when is_list(Conf) ->
+ Singleton = id([a, b, c]),
+
Two = [2,1],
Six = [6,5,4,3,2,1],
@@ -731,6 +875,15 @@ rumerge(Conf) when is_list(Conf) ->
[7,5,4,3,2,1] = lists:rumerge([4,2], [7,5,4,3,2,1]),
[7,6,5,4,3,2,1] = lists:rumerge([6,4,2], [7,6,5,4,3,2,1]),
+ true = erts_debug:same(Singleton, lists:rumerge([], Singleton)),
+ true = erts_debug:same(Singleton, lists:rumerge(Singleton, [])),
+
+ {'EXIT', _} = (catch lists:rumerge(a, b)),
+ {'EXIT', _} = (catch lists:rumerge(a, [])),
+ {'EXIT', _} = (catch lists:rumerge([], b)),
+ {'EXIT', _} = (catch lists:rumerge(a, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:rumerge([1, 2, 3], b)),
+
Nine = [9,8,7,6,5,4,3,2,1],
%% 3-way reversed unique merge
@@ -759,6 +912,23 @@ rumerge(Conf) when is_list(Conf) ->
true =
lists:umerge(L1, L2) ==
lists:reverse(lists:rumerge(lists:reverse(L1), lists:reverse(L2))),
+
+ true = erts_debug:same(Singleton, lists:rumerge3([], [], Singleton)),
+ true = erts_debug:same(Singleton, lists:rumerge3([], Singleton, [])),
+ true = erts_debug:same(Singleton, lists:rumerge3(Singleton, [], [])),
+
+ {'EXIT', _} = (catch lists:rumerge3(a, b, c)),
+ {'EXIT', _} = (catch lists:rumerge3(a, b, [])),
+ {'EXIT', _} = (catch lists:rumerge3(a, [], c)),
+ {'EXIT', _} = (catch lists:rumerge3(a, [], [])),
+ {'EXIT', _} = (catch lists:rumerge3([], b, [])),
+ {'EXIT', _} = (catch lists:rumerge3([], [], c)),
+ {'EXIT', _} = (catch lists:rumerge3(a, b, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:rumerge3(a, [1, 2, 3], c)),
+ {'EXIT', _} = (catch lists:rumerge3(a, [1, 2, 3], [4, 5, 6])),
+ {'EXIT', _} = (catch lists:rumerge3([1, 2, 3], b, [4, 5, 6])),
+ {'EXIT', _} = (catch lists:rumerge3([1, 2, 3], [4, 5, 6], c)),
+
ok.
%% usort/1 on big randomized lists.
@@ -815,6 +985,7 @@ ucheck_stability(L) ->
%% Key merge two lists.
keymerge(Config) when is_list(Config) ->
+ Singleton = id([{1, a}, {2, b}, {3, c}]),
Two = [{1,a},{2,b}],
Six = [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f}],
@@ -843,11 +1014,21 @@ keymerge(Config) when is_list(Config) ->
[{b,2},{c,11},{c,12},{c,21},{c,22},{e,5}] =
lists:keymerge(1,[{c,11},{c,12},{e,5}], [{b,2},{c,21},{c,22}]),
+ true = erts_debug:same(Singleton, lists:keymerge(1, Singleton, [])),
+ true = erts_debug:same(Singleton, lists:keymerge(1, [], Singleton)),
+
+ {'EXIT', _} = (catch lists:keymerge(1, a, b)),
+ {'EXIT', _} = (catch lists:keymerge(1, a, [])),
+ {'EXIT', _} = (catch lists:keymerge(1, [], b)),
+ {'EXIT', _} = (catch lists:keymerge(1, a, [{1, a}, {2, b}, {3, c}])),
+ {'EXIT', _} = (catch lists:keymerge(1, [{1, a}, {2, b}, {3, c}], b)),
+
ok.
%% Reverse key merge two lists.
rkeymerge(Config) when is_list(Config) ->
+ Singleton = id([{1, a}, {2, b}, {3, c}]),
Two = [{2,b},{1,a}],
Six = [{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}],
@@ -880,6 +1061,15 @@ rkeymerge(Config) when is_list(Config) ->
lists:reverse(lists:rkeymerge(1,lists:reverse(L1),
lists:reverse(L2))),
+ true = erts_debug:same(Singleton, lists:rkeymerge(1, Singleton, [])),
+ true = erts_debug:same(Singleton, lists:rkeymerge(1, [], Singleton)),
+
+ {'EXIT', _} = (catch lists:rkeymerge(1, a, b)),
+ {'EXIT', _} = (catch lists:rkeymerge(1, a, [])),
+ {'EXIT', _} = (catch lists:rkeymerge(1, [], b)),
+ {'EXIT', _} = (catch lists:rkeymerge(1, a, [{1, a}, {2, b}, {3, c}])),
+ {'EXIT', _} = (catch lists:rkeymerge(1, [{1, a}, {2, b}, {3, c}], b)),
+
ok.
keysort_1(Config) when is_list(Config) ->
@@ -998,6 +1188,7 @@ keycompare(I, J, A, B) when element(I, A) == element(I, B),
%% Merge two lists while removing duplicates.
ukeymerge(Conf) when is_list(Conf) ->
+ Singleton = id([{1, a}, {2, b}, {3, c}]),
Two = [{1,a},{2,b}],
Six = [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f}],
@@ -1047,11 +1238,21 @@ ukeymerge(Conf) when is_list(Conf) ->
L2 = [{b,1},{b,3},{b,5},{b,7}],
L1 = lists:ukeymerge(2, L1, L2),
+ true = erts_debug:same(Singleton, lists:ukeymerge(1, Singleton, [])),
+ true = erts_debug:same(Singleton, lists:ukeymerge(1, [], Singleton)),
+
+ {'EXIT', _} = (catch lists:ukeymerge(1, a, b)),
+ {'EXIT', _} = (catch lists:ukeymerge(1, a, [])),
+ {'EXIT', _} = (catch lists:ukeymerge(1, [], b)),
+ {'EXIT', _} = (catch lists:ukeymerge(1, a, [{1, a}, {2, b}, {3, c}])),
+ {'EXIT', _} = (catch lists:ukeymerge(1, [{1, a}, {2, b}, {3, c}], b)),
+
ok.
%% Reverse merge two lists while removing duplicates.
rukeymerge(Conf) when is_list(Conf) ->
+ Singleton = id([{1, a}, {2, b}, {3, c}]),
Two = [{2,b},{1,a}],
Six = [{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}],
@@ -1101,6 +1302,15 @@ rukeymerge(Conf) when is_list(Conf) ->
lists:reverse(lists:rukeymerge(2, lists:reverse(L1),
lists:reverse(L2))),
+ true = erts_debug:same(Singleton, lists:rukeymerge(1, Singleton, [])),
+ true = erts_debug:same(Singleton, lists:rukeymerge(1, [], Singleton)),
+
+ {'EXIT', _} = (catch lists:rukeymerge(1, a, b)),
+ {'EXIT', _} = (catch lists:rukeymerge(1, a, [])),
+ {'EXIT', _} = (catch lists:rukeymerge(1, [], b)),
+ {'EXIT', _} = (catch lists:rukeymerge(1, a, [{1, a}, {2, b}, {3, c}])),
+ {'EXIT', _} = (catch lists:rukeymerge(1, [{1, a}, {2, b}, {3, c}], b)),
+
ok.
ukeysort_1(Config) when is_list(Config) ->
@@ -1278,6 +1488,7 @@ ukeycompare(I, J, A, B) when A =/= B,
%% Merge two lists using a fun.
funmerge(Config) when is_list(Config) ->
+ Singleton = id([a, b, c]),
Two = [1,2],
Six = [1,2,3,4,5,6],
F = fun(X, Y) -> X =< Y end,
@@ -1302,11 +1513,21 @@ funmerge(Config) when is_list(Config) ->
[{b,2},{c,11},{c,12},{c,21},{c,22},{e,5}] =
lists:merge(F2,[{c,11},{c,12},{e,5}], [{b,2},{c,21},{c,22}]),
+ true = erts_debug:same(Singleton, lists:merge(F, Singleton, [])),
+ true = erts_debug:same(Singleton, lists:merge(F, [], Singleton)),
+
+ {'EXIT', _} = (catch lists:merge(F, a, b)),
+ {'EXIT', _} = (catch lists:merge(F, a, [])),
+ {'EXIT', _} = (catch lists:merge(F, [], b)),
+ {'EXIT', _} = (catch lists:merge(F, a, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:merge(F, [1, 2, 3], b)),
+
ok.
%% Reverse merge two lists using a fun.
rfunmerge(Config) when is_list(Config) ->
+ Singleton = id([a, b, c]),
Two = [2,1],
Six = [6,5,4,3,2,1],
F = fun(X, Y) -> X =< Y end,
@@ -1334,6 +1555,15 @@ rfunmerge(Config) when is_list(Config) ->
lists:merge(F2, L1, L2) ==
lists:reverse(lists:rmerge(F2,lists:reverse(L1), lists:reverse(L2))),
+ true = erts_debug:same(Singleton, lists:rmerge(F, Singleton, [])),
+ true = erts_debug:same(Singleton, lists:rmerge(F, [], Singleton)),
+
+ {'EXIT', _} = (catch lists:rmerge(F, a, b)),
+ {'EXIT', _} = (catch lists:rmerge(F, a, [])),
+ {'EXIT', _} = (catch lists:rmerge(F, [], b)),
+ {'EXIT', _} = (catch lists:rmerge(F, a, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:rmerge(F, [1, 2, 3], b)),
+
ok.
@@ -1403,6 +1633,7 @@ funsort_check(I, Input, Expected) ->
%% Merge two lists while removing duplicates using a fun.
ufunmerge(Conf) when is_list(Conf) ->
+ Singleton = id([a, b, c]),
Two = [1,2],
Six = [1,2,3,4,5,6],
F = fun(X, Y) -> X =< Y end,
@@ -1437,10 +1668,20 @@ ufunmerge(Conf) when is_list(Conf) ->
[{b,2},{e,5},{c,11},{c,12},{c,21},{c,22}] =
lists:umerge(F2, [{e,5},{c,11},{c,12}], [{b,2},{c,21},{c,22}]),
+ true = erts_debug:same(Singleton, lists:umerge(F, Singleton, [])),
+ true = erts_debug:same(Singleton, lists:umerge(F, [], Singleton)),
+
+ {'EXIT', _} = (catch lists:umerge(F, a, b)),
+ {'EXIT', _} = (catch lists:umerge(F, a, [])),
+ {'EXIT', _} = (catch lists:umerge(F, [], b)),
+ {'EXIT', _} = (catch lists:umerge(F, a, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:umerge(F, [1, 2, 3], b)),
+
ok.
%% Reverse merge two lists while removing duplicates using a fun.
rufunmerge(Conf) when is_list(Conf) ->
+ Singleton = id([a, b, c]),
Two = [2,1],
Six = [6,5,4,3,2,1],
F = fun(X, Y) -> X =< Y end,
@@ -1480,6 +1721,15 @@ rufunmerge(Conf) when is_list(Conf) ->
lists:umerge(F2, L3, L4) ==
lists:reverse(lists:rumerge(F2,lists:reverse(L3), lists:reverse(L4))),
+ true = erts_debug:same(Singleton, lists:rumerge(F, Singleton, [])),
+ true = erts_debug:same(Singleton, lists:rumerge(F, [], Singleton)),
+
+ {'EXIT', _} = (catch lists:rumerge(F, a, b)),
+ {'EXIT', _} = (catch lists:rumerge(F, a, [])),
+ {'EXIT', _} = (catch lists:rumerge(F, [], b)),
+ {'EXIT', _} = (catch lists:rumerge(F, a, [1, 2, 3])),
+ {'EXIT', _} = (catch lists:rumerge(F, [1, 2, 3], b)),
+
ok.
ufunsort_1(Config) when is_list(Config) ->
@@ -2362,6 +2612,41 @@ zip_unzip(Config) when is_list(Config) ->
{'EXIT',{function_clause,_}} = (catch lists:zip([a], [b,c])),
ok.
+zip_fail(Config) when is_list(Config) ->
+ [] = lists:zip([], [], fail),
+ {'EXIT', {function_clause, _}} = (catch lists:zip([a], [], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip([], [c], fail)),
+
+ [{a, c}] = lists:zip([a], [c], fail),
+ {'EXIT', {function_clause, _}} = (catch lists:zip([a, b], [c], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip([a], [c, d], fail)),
+
+ ok.
+
+zip_trim(Config) when is_list(Config) ->
+ [] = lists:zip([], [], trim),
+ [] = lists:zip([a], [], trim),
+ [] = lists:zip([], [c], trim),
+
+ [{a, c}] = lists:zip([a], [c], trim),
+ [{a, c}] = lists:zip([a, b], [c], trim),
+ [{a, c}] = lists:zip([a], [c, d], trim),
+
+ ok.
+
+zip_pad(Config) when is_list(Config) ->
+ How = {pad, {x, y}},
+
+ [] = lists:zip([], [], How),
+ [{a, y}] = lists:zip([a], [], How),
+ [{x, c}] = lists:zip([], [c], How),
+
+ [{a, c}] = lists:zip([a], [c], How),
+ [{a, c}, {b, y}] = lists:zip([a, b], [c], How),
+ [{a, c}, {x, d}] = lists:zip([a], [c, d], How),
+
+ ok.
+
%% Test lists:zip3/3, lists:unzip3/1.
zip_unzip3(Config) when is_list(Config) ->
[] = lists:zip3([], [], []),
@@ -2388,6 +2673,65 @@ zip_unzip3(Config) when is_list(Config) ->
ok.
+zip3_fail(Config) when is_list(Config) ->
+ [] = lists:zip3([], [], [], fail),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([a], [], [], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([], [c], [], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([a], [c], [], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([], [], [e], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([a], [], [e], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([], [c], [e], fail)),
+
+ [{a, c, e}] = lists:zip3([a], [c], [e], fail),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([a, b], [c], [e], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([a], [c, d], [e], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([a, b], [c, d], [e], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([a], [c], [e, f], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([a, b], [c], [e, f], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zip3([a], [c, d], [e, f], fail)),
+
+ ok.
+
+zip3_trim(Config) when is_list(Config) ->
+ [] = lists:zip3([], [], [], trim),
+ [] = lists:zip3([a], [], [], trim),
+ [] = lists:zip3([], [c], [], trim),
+ [] = lists:zip3([a], [c], [], trim),
+ [] = lists:zip3([], [], [e], trim),
+ [] = lists:zip3([a], [], [e], trim),
+ [] = lists:zip3([], [c], [e], trim),
+
+ [{a, c, e}] = lists:zip3([a], [c], [e], trim),
+ [{a, c, e}] = lists:zip3([a, b], [c], [e], trim),
+ [{a, c, e}] = lists:zip3([a], [c, d], [e], trim),
+ [{a, c, e}] = lists:zip3([a, b], [c, d], [e], trim),
+ [{a, c, e}] = lists:zip3([a], [c], [e, f], trim),
+ [{a, c, e}] = lists:zip3([a, b], [c], [e, f], trim),
+ [{a, c, e}] = lists:zip3([a], [c, d], [e, f], trim),
+
+ ok.
+
+zip3_pad(Config) when is_list(Config) ->
+ How = {pad, {x, y, z}},
+
+ [] = lists:zip3([], [], [], How),
+ [{a, y, z}] = lists:zip3([a], [], [], How),
+ [{x, c, z}] = lists:zip3([], [c], [], How),
+ [{a, c, z}] = lists:zip3([a], [c], [], How),
+ [{x, y, e}] = lists:zip3([], [], [e], How),
+ [{a, y, e}] = lists:zip3([a], [], [e], How),
+ [{x, c, e}] = lists:zip3([], [c], [e], How),
+
+ [{a, c, e}] = lists:zip3([a], [c], [e], How),
+ [{a, c, e}, {b, y, z}] = lists:zip3([a, b], [c], [e], How),
+ [{a, c, e}, {x, d, z}] = lists:zip3([a], [c, d], [e], How),
+ [{a, c, e}, {b, d, z}] = lists:zip3([a, b], [c, d], [e], How),
+ [{a, c, e}, {x, y, f}] = lists:zip3([a], [c], [e, f], How),
+ [{a, c, e}, {b, y, f}] = lists:zip3([a, b], [c], [e, f], How),
+ [{a, c, e}, {x, d, f}] = lists:zip3([a], [c, d], [e, f], How),
+
+ ok.
+
%% Test lists:zipwith/3.
zipwith(Config) when is_list(Config) ->
Zip = fun(A, B) -> [A|B] end,
@@ -2410,6 +2754,47 @@ zipwith(Config) when is_list(Config) ->
{'EXIT',{function_clause,_}} = (catch lists:zipwith(Zip, [a], [b,c])),
ok.
+zipwith_fail(Config) when is_list(Config) ->
+ Zip = fun(A, B) -> A * B end,
+
+ [] = lists:zipwith(Zip, [], [], fail),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith(Zip, [2], [], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith(Zip, [], [5], fail)),
+
+ [2 * 5] = lists:zipwith(Zip, [2], [5], fail),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith(Zip, [2, 3], [5], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith(Zip, [2], [5, 7], fail)),
+
+ ok.
+
+zipwith_trim(Config) when is_list(Config) ->
+ Zip = fun(A, B) -> A * B end,
+
+ [] = lists:zipwith(Zip, [], [], trim),
+ [] = lists:zipwith(Zip, [2], [], trim),
+ [] = lists:zipwith(Zip, [], [5], trim),
+
+ [2 * 5] = lists:zipwith(Zip, [2], [5], trim),
+ [2 * 5] = lists:zipwith(Zip, [2, 3], [5], trim),
+ [2 * 5] = lists:zipwith(Zip, [2], [5, 7], trim),
+
+ ok.
+
+zipwith_pad(Config) when is_list(Config) ->
+ How = {pad, {17, 19}},
+
+ Zip = fun(A, B) -> A * B end,
+
+ [] = lists:zipwith(Zip, [], [], How),
+ [ 2 * 19] = lists:zipwith(Zip, [2], [], How),
+ [17 * 5] = lists:zipwith(Zip, [], [5], How),
+
+ [2 * 5] = lists:zipwith(Zip, [2], [5], How),
+ [2 * 5, 3 * 19] = lists:zipwith(Zip, [2, 3], [5], How),
+ [2 * 5, 17 * 7] = lists:zipwith(Zip, [2], [5, 7], How),
+
+ ok.
+
%% Test lists:zipwith3/4.
zipwith3(Config) when is_list(Config) ->
Zip = fun(A, B, C) -> [A,B,C] end,
@@ -2434,6 +2819,69 @@ zipwith3(Config) when is_list(Config) ->
ok.
+zipwith3_fail(Config) when is_list(Config) ->
+ Zip = fun(A, B, C) -> A * B * C end,
+
+ [] = lists:zipwith3(Zip, [], [], [], fail),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [2], [], [], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [], [5], [], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [2], [5], [], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [], [], [11], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [2], [], [11], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [], [5], [11], fail)),
+
+ [2 * 5 * 11] = lists:zipwith3(Zip, [2], [5], [11], fail),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [2, 3], [5], [11], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [2], [5, 7], [11], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [2, 3], [5, 7], [11], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [2], [5], [11, 13], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [2, 3], [5], [11, 13], fail)),
+ {'EXIT', {function_clause, _}} = (catch lists:zipwith3(Zip, [2], [5, 7], [11, 13], fail)),
+
+ ok.
+
+zipwith3_trim(Config) when is_list(Config) ->
+ Zip = fun(A, B, C) -> A * B * C end,
+
+ [] = lists:zipwith3(Zip, [], [], [], trim),
+ [] = lists:zipwith3(Zip, [2], [], [], trim),
+ [] = lists:zipwith3(Zip, [], [5], [], trim),
+ [] = lists:zipwith3(Zip, [], [], [11], trim),
+ [] = lists:zipwith3(Zip, [2], [], [11], trim),
+ [] = lists:zipwith3(Zip, [], [5], [11], trim),
+
+ [2 * 5 * 11] = lists:zipwith3(Zip, [2], [5], [11], trim),
+ [2 * 5 * 11] = lists:zipwith3(Zip, [2, 3], [5], [11], trim),
+ [2 * 5 * 11] = lists:zipwith3(Zip, [2], [5, 7], [11], trim),
+ [2 * 5 * 11] = lists:zipwith3(Zip, [2], [5], [11, 13], trim),
+ [2 * 5 * 11] = lists:zipwith3(Zip, [2, 3], [5], [11, 13], trim),
+ [2 * 5 * 11] = lists:zipwith3(Zip, [2], [5, 7], [11, 13], trim),
+
+ ok.
+
+zipwith3_pad(Config) when is_list(Config) ->
+ How = {pad, {17, 19, 23}},
+
+ Zip = fun(A, B, C) -> A * B * C end,
+
+ [] = lists:zipwith3(Zip, [], [], [], How),
+ [ 2 * 19 * 23] = lists:zipwith3(Zip, [2], [], [], How),
+ [17 * 5 * 23] = lists:zipwith3(Zip, [], [5], [], How),
+ [ 2 * 5 * 23] = lists:zipwith3(Zip, [2], [5], [], How),
+ [17 * 19 * 11] = lists:zipwith3(Zip, [], [], [11], How),
+ [ 2 * 19 * 11] = lists:zipwith3(Zip, [2], [], [11], How),
+ [17 * 5 * 11] = lists:zipwith3(Zip, [], [5], [11], How),
+
+ [2 * 5 * 11] = lists:zipwith3(Zip, [2], [5], [11], How),
+ [2 * 5 * 11, 3 * 19 * 23] = lists:zipwith3(Zip, [2, 3], [5], [11], How),
+ [2 * 5 * 11, 17 * 7 * 23] = lists:zipwith3(Zip, [2], [5, 7], [11], How),
+ [2 * 5 * 11, 3 * 7 * 23] = lists:zipwith3(Zip, [2, 3], [5, 7], [11], How),
+ [2 * 5 * 11, 17 * 19 * 13] = lists:zipwith3(Zip, [2], [5], [11, 13], How),
+ [2 * 5 * 11, 3 * 19 * 13] = lists:zipwith3(Zip, [2, 3], [5], [11, 13], How),
+ [2 * 5 * 11, 17 * 7 * 13] = lists:zipwith3(Zip, [2], [5, 7], [11, 13], How),
+
+ ok.
+
%% Test lists:join/2
join(Config) when is_list(Config) ->
A = [a,b,c],
@@ -2750,15 +3198,39 @@ hof(Config) when is_list(Config) ->
enumerate(Config) when is_list(Config) ->
[] = lists:enumerate([]),
[] = lists:enumerate(10, []),
+ [] = lists:enumerate(-10, []),
+ [] = lists:enumerate(10, 2, []),
+ [] = lists:enumerate(10, -2, []),
+ [] = lists:enumerate(-10, 2, []),
+ [] = lists:enumerate(-10, -2, []),
[{1,a},{2,b},{3,c}] = lists:enumerate([a,b,c]),
[{10,a},{11,b},{12,c}] = lists:enumerate(10, [a,b,c]),
+ [{-10,a},{-9,b},{-8,c}] = lists:enumerate(-10, [a,b,c]),
+ [{10,a},{12,b},{14,c}] = lists:enumerate(10, 2, [a,b,c]),
+ [{10,a},{8,b},{6,c}] = lists:enumerate(10, -2, [a,b,c]),
+ [{-10,a},{-12,b},{-14,c}] = lists:enumerate(-10, -2, [a,b,c]),
{'EXIT', {function_clause, _}} = catch lists:enumerate(0),
{'EXIT', {function_clause, _}} = catch lists:enumerate(0, 10),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(0, 10, 20),
{'EXIT', {function_clause, _}} = catch lists:enumerate(1.0, []),
{'EXIT', {function_clause, _}} = catch lists:enumerate(1.0, [a,b,c]),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(1.0, 2, []),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(1.0, 2, [a,b,c]),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(1, 2.0, []),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(1, 2.0, [a,b,c]),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(1.0, 2.0, []),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(1.0, 2.0, [a,b,c]),
{'EXIT', {function_clause, _}} = catch lists:enumerate(<<1>>, []),
{'EXIT', {function_clause, _}} = catch lists:enumerate(<<1>>, [a,b,c]),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(<<1>>, 2, []),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(<<1>>, 2, [a,b,c]),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(1, <<2>>, []),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(1, <<2>>, [a,b,c]),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(<<1>>, <<2>>, []),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(<<1>>, <<2>>, [a,b,c]),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(<<1,2,3>>),
{'EXIT', {function_clause, _}} = catch lists:enumerate(1, <<1,2,3>>),
+ {'EXIT', {function_clause, _}} = catch lists:enumerate(1, 2, <<1,2,3>>),
ok.
diff --git a/lib/stdlib/test/lists_property_test_SUITE.erl b/lib/stdlib/test/lists_property_test_SUITE.erl
new file mode 100644
index 0000000000..ecbf14309e
--- /dev/null
+++ b/lib/stdlib/test/lists_property_test_SUITE.erl
@@ -0,0 +1,441 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(lists_property_test_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-compile(export_all).
+
+all() ->
+ [
+ all_true_case, all_false_case,
+ any_true_case, any_false_case,
+ append_1_case,
+ append_2_case,
+ concat_case,
+ delete_case, delete_absent_case,
+ droplast_case,
+ dropwhile_case,
+ duplicate_case,
+ enumerate_1_case,
+ enumerate_2_case,
+ enumerate_3_case,
+ filter_case,
+ filtermap_case,
+ flatlength_case,
+ flatmap_case,
+ flatten_1_case,
+ flatten_2_case,
+ foldl_case,
+ foldr_case,
+ foreach_case,
+ join_case,
+ keydelete_case, keydelete_absent_case,
+ keyfind_case, keyfind_absent_case,
+ keymap_case,
+ keymember_case, keymember_absent_case,
+ keymerge_case, keymerge_invalid_case,
+ keyreplace_case, keyreplace_absent_case,
+ keysearch_case, keysearch_absent_case,
+ keysort_case,
+ keystore_case, keystore_absent_case,
+ keytake_case, keytake_absent_case,
+ last_case,
+ map_case,
+ mapfoldl_case,
+ mapfoldr_case,
+ max_case,
+ member_case, member_absent_case,
+ merge_1_case, merge_1_invalid_case,
+ merge_2_case, merge_2_invalid_case,
+ merge_3_case, merge_3_invalid_case,
+ merge3_case, merge3_invalid_case,
+ min_case,
+ nth_case, nth_outofrange_case,
+ nthtail_case, nthtail_outofrange_case,
+ partition_case,
+ prefix_case,
+ reverse_1_case,
+ reverse_2_case,
+ search_case, search_absent_case,
+ seq2_case,
+ seq3_case,
+ sort_1_case,
+ sort_2_case,
+ split_case, split_outofrange_case,
+ splitwith_case,
+ sublist_2_case,
+ sublist_3_case,
+ subtract_case,
+ suffix_case,
+ sum_case,
+ takewhile_case,
+ ukeymerge_case, ukeymerge_invalid_case,
+ ukeysort_case,
+ umerge_1_case, umerge_1_invalid_case,
+ umerge_2_case, umerge_2_invalid_case,
+ umerge_3_case, umerge_3_invalid_case,
+ umerge3_case, umerge3_invalid_case,
+ uniq_1_case,
+ uniq_2_case,
+ unzip_case,
+ unzip3_case,
+ usort_1_case,
+ usort_2_case,
+ zip_2_case,
+ zip_3_case,
+ zip3_3_case,
+ zip3_4_case,
+ zipwith_3_case,
+ zipwith_4_case,
+ zipwith3_4_case,
+ zipwith3_5_case
+ ].
+
+init_per_suite(Config) ->
+ ct_property_test:init_per_suite(Config).
+
+end_per_suite(Config) ->
+ persistent_term:erase({lists_prop, random_atoms}),
+ Config.
+
+do_proptest(Prop, Config) ->
+ ct_property_test:quickcheck(lists_prop:Prop(), Config).
+
+all_true_case(Config) ->
+ do_proptest(prop_all_true, Config).
+
+all_false_case(Config) ->
+ do_proptest(prop_all_false, Config).
+
+any_true_case(Config) ->
+ do_proptest(prop_any_true, Config).
+
+any_false_case(Config) ->
+ do_proptest(prop_any_false, Config).
+
+append_1_case(Config) ->
+ do_proptest(prop_append_1, Config).
+
+append_2_case(Config) ->
+ do_proptest(prop_append_2, Config).
+
+concat_case(Config) ->
+ do_proptest(prop_concat, Config).
+
+delete_case(Config) ->
+ do_proptest(prop_delete, Config).
+
+delete_absent_case(Config) ->
+ do_proptest(prop_delete_absent, Config).
+
+droplast_case(Config) ->
+ do_proptest(prop_droplast, Config).
+
+dropwhile_case(Config) ->
+ do_proptest(prop_dropwhile, Config).
+
+duplicate_case(Config) ->
+ do_proptest(prop_duplicate, Config).
+
+enumerate_1_case(Config) ->
+ do_proptest(prop_enumerate_1, Config).
+
+enumerate_2_case(Config) ->
+ do_proptest(prop_enumerate_2, Config).
+
+enumerate_3_case(Config) ->
+ do_proptest(prop_enumerate_3, Config).
+
+filter_case(Config) ->
+ do_proptest(prop_filter, Config).
+
+filtermap_case(Config) ->
+ do_proptest(prop_filtermap, Config).
+
+flatlength_case(Config) ->
+ do_proptest(prop_flatlength, Config).
+
+flatmap_case(Config) ->
+ do_proptest(prop_flatmap, Config).
+
+flatten_1_case(Config) ->
+ do_proptest(prop_flatten_1, Config).
+
+flatten_2_case(Config) ->
+ do_proptest(prop_flatten_2, Config).
+
+foldl_case(Config) ->
+ do_proptest(prop_foldl, Config).
+
+foldr_case(Config) ->
+ do_proptest(prop_foldr, Config).
+
+foreach_case(Config) ->
+ do_proptest(prop_foreach, Config).
+
+join_case(Config) ->
+ do_proptest(prop_join, Config).
+
+keydelete_case(Config) ->
+ do_proptest(prop_keydelete, Config).
+
+keydelete_absent_case(Config) ->
+ do_proptest(prop_keydelete_absent, Config).
+
+keyfind_case(Config) ->
+ do_proptest(prop_keyfind, Config).
+
+keyfind_absent_case(Config) ->
+ do_proptest(prop_keyfind_absent, Config).
+
+keymap_case(Config) ->
+ do_proptest(prop_keymap, Config).
+
+keymember_case(Config) ->
+ do_proptest(prop_keymember, Config).
+
+keymember_absent_case(Config) ->
+ do_proptest(prop_keymember_absent, Config).
+
+keymerge_case(Config) ->
+ do_proptest(prop_keymerge, Config).
+
+keymerge_invalid_case(Config) ->
+ do_proptest(prop_keymerge_invalid, Config).
+
+keyreplace_case(Config) ->
+ do_proptest(prop_keyreplace, Config).
+
+keyreplace_absent_case(Config) ->
+ do_proptest(prop_keyreplace_absent, Config).
+
+keysearch_case(Config) ->
+ do_proptest(prop_keysearch, Config).
+
+keysearch_absent_case(Config) ->
+ do_proptest(prop_keysearch_absent, Config).
+
+keysort_case(Config) ->
+ do_proptest(prop_keysort, Config).
+
+keystore_case(Config) ->
+ do_proptest(prop_keystore, Config).
+
+keystore_absent_case(Config) ->
+ do_proptest(prop_keystore_absent, Config).
+
+keytake_case(Config) ->
+ do_proptest(prop_keytake, Config).
+
+keytake_absent_case(Config) ->
+ do_proptest(prop_keytake_absent, Config).
+
+last_case(Config) ->
+ do_proptest(prop_last, Config).
+
+map_case(Config) ->
+ do_proptest(prop_map, Config).
+
+mapfoldl_case(Config) ->
+ do_proptest(prop_mapfoldl, Config).
+
+mapfoldr_case(Config) ->
+ do_proptest(prop_mapfoldr, Config).
+
+max_case(Config) ->
+ do_proptest(prop_max, Config).
+
+member_case(Config) ->
+ do_proptest(prop_member, Config).
+
+member_absent_case(Config) ->
+ do_proptest(prop_member_absent, Config).
+
+merge_1_case(Config) ->
+ do_proptest(prop_merge_1, Config).
+
+merge_1_invalid_case(Config) ->
+ do_proptest(prop_merge_1_invalid, Config).
+
+merge_2_case(Config) ->
+ do_proptest(prop_merge_2, Config).
+
+merge_2_invalid_case(Config) ->
+ do_proptest(prop_merge_2_invalid, Config).
+
+merge_3_case(Config) ->
+ do_proptest(prop_merge_3, Config).
+
+merge_3_invalid_case(Config) ->
+ do_proptest(prop_merge_3_invalid, Config).
+
+merge3_case(Config) ->
+ do_proptest(prop_merge3, Config).
+
+merge3_invalid_case(Config) ->
+ do_proptest(prop_merge3_invalid, Config).
+
+min_case(Config) ->
+ do_proptest(prop_min, Config).
+
+nth_case(Config) ->
+ do_proptest(prop_nth, Config).
+
+nth_outofrange_case(Config) ->
+ do_proptest(prop_nth_outofrange, Config).
+
+nthtail_case(Config) ->
+ do_proptest(prop_nthtail, Config).
+
+nthtail_outofrange_case(Config) ->
+ do_proptest(prop_nthtail_outofrange, Config).
+
+partition_case(Config) ->
+ do_proptest(prop_partition, Config).
+
+prefix_case(Config) ->
+ do_proptest(prop_prefix, Config).
+
+reverse_1_case(Config) ->
+ do_proptest(prop_reverse_1, Config).
+
+reverse_2_case(Config) ->
+ do_proptest(prop_reverse_2, Config).
+
+search_case(Config) ->
+ do_proptest(prop_search, Config).
+
+search_absent_case(Config) ->
+ do_proptest(prop_search_absent, Config).
+
+seq2_case(Config) ->
+ do_proptest(prop_seq2, Config).
+
+seq3_case(Config) ->
+ do_proptest(prop_seq3, Config).
+
+sort_1_case(Config) ->
+ do_proptest(prop_sort_1, Config).
+
+sort_2_case(Config) ->
+ do_proptest(prop_sort_2, Config).
+
+split_case(Config) ->
+ do_proptest(prop_split, Config).
+
+split_outofrange_case(Config) ->
+ do_proptest(prop_split_outofrange, Config).
+
+splitwith_case(Config) ->
+ do_proptest(prop_splitwith, Config).
+
+sublist_2_case(Config) ->
+ do_proptest(prop_sublist_2, Config).
+
+sublist_3_case(Config) ->
+ do_proptest(prop_sublist_3, Config).
+
+subtract_case(Config) ->
+ do_proptest(prop_subtract, Config).
+
+suffix_case(Config) ->
+ do_proptest(prop_suffix, Config).
+
+sum_case(Config) ->
+ do_proptest(prop_sum, Config).
+
+takewhile_case(Config) ->
+ do_proptest(prop_takewhile, Config).
+
+ukeymerge_case(Config) ->
+ do_proptest(prop_ukeymerge, Config).
+
+ukeymerge_invalid_case(Config) ->
+ do_proptest(prop_ukeymerge_invalid, Config).
+
+ukeysort_case(Config) ->
+ do_proptest(prop_ukeysort, Config).
+
+umerge_1_case(Config) ->
+ do_proptest(prop_umerge_1, Config).
+
+umerge_1_invalid_case(Config) ->
+ do_proptest(prop_umerge_1_invalid, Config).
+
+umerge_2_case(Config) ->
+ do_proptest(prop_umerge_2, Config).
+
+umerge_2_invalid_case(Config) ->
+ do_proptest(prop_umerge_2_invalid, Config).
+
+umerge_3_case(Config) ->
+ do_proptest(prop_umerge_3, Config).
+
+umerge_3_invalid_case(Config) ->
+ do_proptest(prop_umerge_3_invalid, Config).
+
+umerge3_case(Config) ->
+ do_proptest(prop_umerge3, Config).
+
+umerge3_invalid_case(Config) ->
+ do_proptest(prop_umerge3_invalid, Config).
+
+uniq_1_case(Config) ->
+ do_proptest(prop_uniq_1, Config).
+
+uniq_2_case(Config) ->
+ do_proptest(prop_uniq_2, Config).
+
+unzip_case(Config) ->
+ do_proptest(prop_unzip, Config).
+
+unzip3_case(Config) ->
+ do_proptest(prop_unzip3, Config).
+
+usort_1_case(Config) ->
+ do_proptest(prop_usort_1, Config).
+
+usort_2_case(Config) ->
+ do_proptest(prop_usort_2, Config).
+
+zip_2_case(Config) ->
+ do_proptest(prop_zip_2, Config).
+
+zip_3_case(Config) ->
+ do_proptest(prop_zip_3, Config).
+
+zip3_3_case(Config) ->
+ do_proptest(prop_zip3_3, Config).
+
+zip3_4_case(Config) ->
+ do_proptest(prop_zip3_4, Config).
+
+zipwith_3_case(Config) ->
+ do_proptest(prop_zipwith_3, Config).
+
+zipwith_4_case(Config) ->
+ do_proptest(prop_zipwith_4, Config).
+
+zipwith3_4_case(Config) ->
+ do_proptest(prop_zipwith3_4, Config).
+
+zipwith3_5_case(Config) ->
+ do_proptest(prop_zipwith3_5, Config).
+
diff --git a/lib/stdlib/test/maps_SUITE.erl b/lib/stdlib/test/maps_SUITE.erl
index 8d479f8211..0f20398803 100644
--- a/lib/stdlib/test/maps_SUITE.erl
+++ b/lib/stdlib/test/maps_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -30,7 +30,9 @@
-export([t_update_with_3/1, t_update_with_4/1,
t_get_3/1, t_filter_2/1, t_filtermap_2/1,
t_fold_3/1,t_map_2/1,t_size_1/1, t_foreach_2/1,
- t_iterator_1/1, t_put_opt/1, t_merge_opt/1,
+ t_iterator_1/1, t_iterator_2/1,
+ t_iterator_valid/1,
+ t_put_opt/1, t_merge_opt/1,
t_with_2/1,t_without_2/1,
t_intersect/1, t_intersect_with/1,
t_merge_with/1, t_from_keys/1,
@@ -57,7 +59,9 @@ all() ->
[t_update_with_3,t_update_with_4,
t_get_3,t_filter_2,t_filtermap_2,
t_fold_3,t_map_2,t_size_1,t_foreach_2,
- t_iterator_1,t_put_opt,t_merge_opt,
+ t_iterator_1,t_iterator_2,
+ t_iterator_valid,
+ t_put_opt,t_merge_opt,
t_with_2,t_without_2,
t_intersect, t_intersect_with,
t_merge_with, t_from_keys,
@@ -354,7 +358,8 @@ t_get_3(Config) when is_list(Config) ->
DefaultValue = maps:get(key3, Map, DefaultValue),
%% error case
- ?badmap(a,get,[[a,b],a,def]) = (catch maps:get([a,b],id(a),def)),
+ {'EXIT', {{badmap,a}, _}} = (catch maps:get([a,b],id(a),def)),
+
ok.
t_without_2(_Config) ->
@@ -394,7 +399,8 @@ t_filter_2(Config) when is_list(Config) ->
#{a := 2,c := 4} = maps:filter(Pred1,maps:iterator(M)),
#{"b" := 2,"c" := 4} = maps:filter(Pred2,maps:iterator(M)),
%% error case
- ?badmap(a,filter,[_,a]) = (catch maps:filter(fun(_,_) -> ok end,id(a))),
+ ?badmap(a,filter,[_,a]) = (catch maps:filter(fun(_,_) -> true end,id(a))),
+ ?badmap({a,b,c},filter,[_,{a,b,c}]) = (catch maps:filter(fun(_,_) -> true end,id({a,b,c}))),
?badarg(filter,[<<>>,#{}]) = (catch maps:filter(id(<<>>),#{})),
ok.
@@ -408,7 +414,8 @@ t_filtermap_2(Config) when is_list(Config) ->
false = maps:is_key(20, M1),
true = M1 =:= M2,
%% error case
- ?badmap(a,filtermap,[_,a]) = (catch maps:filtermap(fun(_,_) -> ok end,id(a))),
+ ?badmap(a,filtermap,[_,a]) = (catch maps:filtermap(fun(_,_) -> true end,id(a))),
+ ?badmap({a,b,c},filtermap,[_,{a,b,c}]) = (catch maps:filtermap(fun(_,_) -> true end,id({a,b,c}))),
?badarg(filtermap,[<<>>,#{}]) = (catch maps:filtermap(id(<<>>),#{})),
ok.
@@ -424,6 +431,7 @@ t_fold_3(Config) when is_list(Config) ->
%% error case
?badmap(a,fold,[_,0,a]) = (catch maps:fold(fun(_,_,_) -> ok end,0,id(a))),
+ ?badmap({a,b,c},fold,[_,0,{a,b,c}]) = (catch maps:fold(fun(_,_,_) -> ok end,0,id({a,b,c}))),
?badarg(fold,[<<>>,0,#{}]) = (catch maps:fold(id(<<>>),0,#{})),
ok.
@@ -438,6 +446,7 @@ t_map_2(Config) when is_list(Config) ->
%% error case
?badmap(a,map,[_,a]) = (catch maps:map(fun(_,_) -> ok end, id(a))),
+ ?badmap({a,b,c},map,[_,{a,b,c}]) = (catch maps:map(fun(_,_) -> ok end, id({a,b,c}))),
?badarg(map,[<<>>,#{}]) = (catch maps:map(id(<<>>),#{})),
ok.
@@ -448,6 +457,7 @@ t_foreach_2(Config) when is_list(Config) ->
?badmap({},foreach,[_,{}]) = (catch maps:foreach(fun(_,_) -> ok end, id({}))),
?badmap(42,foreach,[_,42]) = (catch maps:foreach(fun(_,_) -> ok end, id(42))),
?badmap(<<>>,foreach,[_,<<>>]) = (catch maps:foreach(fun(_,_) -> ok end, id(<<>>))),
+ ?badmap({a,b,c},foreach,[_,{a,b,c}]) = (catch maps:foreach(fun(_,_) -> ok end, id({a,b,c}))),
?badarg(foreach,[<<>>,#{}]) = (catch maps:foreach(id(<<>>),#{})),
F0 = fun() -> ok end,
@@ -471,12 +481,15 @@ t_iterator_1(Config) when is_list(Config) ->
KVList = lists:sort([{K1,V1},{K2,V2}]),
KVList = lists:sort(maps:to_list(M0)),
+ KList = lists:sort([K1,K2]),
+ KList = lists:sort(maps:keys(M0)),
%% Large map test
Vs2 = lists:seq(1,200),
M2 = maps:from_list([{{k,I},I}||I<-Vs2]),
KVList2 = lists:sort(iter_kv(maps:iterator(M2))),
+ KVList2 = lists:sort(maps:to_list(maps:iterator(M2))),
KVList2 = lists:sort(maps:to_list(M2)),
%% Larger map test
@@ -484,9 +497,101 @@ t_iterator_1(Config) when is_list(Config) ->
Vs3 = lists:seq(1,10000),
M3 = maps:from_list([{{k,I},I}||I<-Vs3]),
KVList3 = lists:sort(iter_kv(maps:iterator(M3))),
+ KVList3 = lists:sort(maps:to_list(maps:iterator(M3))),
KVList3 = lists:sort(maps:to_list(M3)),
ok.
+t_iterator_2(Config) when is_list(Config) ->
+
+ AOrdCmpFun = fun(A, B) -> A =< B end,
+ ARevCmpFun = fun(A, B) -> B =< A end,
+
+ %% Small map test
+ M0 = #{ a => 1, b => 2 },
+ TOrdI0 = maps:iterator(M0, ordered),
+ {K1 = a, V1 = 1, TOrdI1} = maps:next(TOrdI0),
+ {K2 = b, V2 = 2, TOrdI2} = maps:next(TOrdI1),
+ none = maps:next(TOrdI2),
+
+ TRevI0 = maps:iterator(M0, reversed),
+ {K2 = b, V2 = 2, TRevI1} = maps:next(TRevI0),
+ {K1 = a, V1 = 1, TRevI2} = maps:next(TRevI1),
+ none = maps:next(TRevI2),
+
+ AOrdI0 = maps:iterator(M0, AOrdCmpFun),
+ {K1 = a, V1 = 1, AOrdI1} = maps:next(AOrdI0),
+ {K2 = b, V2 = 2, AOrdI2} = maps:next(AOrdI1),
+ none = maps:next(AOrdI2),
+
+ ARevI0 = maps:iterator(M0, ARevCmpFun),
+ {K2 = b, V2 = 2, ARevI1} = maps:next(ARevI0),
+ {K1 = a, V1 = 1, ARevI2} = maps:next(ARevI1),
+ none = maps:next(ARevI2),
+
+ OrdKVList = [{K1, V1}, {K2, V2}],
+ OrdKVList = maps:to_list(TOrdI0),
+ OrdKVList = maps:to_list(AOrdI0),
+
+ RevKVList = [{K2, V2}, {K1, V1}],
+ RevKVList = maps:to_list(TRevI0),
+ RevKVList = maps:to_list(ARevI0),
+
+ %% Large map test
+
+ Vs2 = lists:seq(1, 200),
+ OrdKVList2 = [{{k, I}, I} || I <- Vs2],
+ M2 = maps:from_list(OrdKVList2),
+ ok = iterator_2_check_order(M2, ordered, reversed),
+ ok = iterator_2_check_order(M2, AOrdCmpFun, ARevCmpFun),
+
+ %% Larger map test
+
+ Vs3 = lists:seq(1, 10000),
+ OrdKVList3 = [{{k, I}, I} || I <- Vs3],
+ M3 = maps:from_list(OrdKVList3),
+ ok = iterator_2_check_order(M3, ordered, reversed),
+ ok = iterator_2_check_order(M3, AOrdCmpFun, ARevCmpFun),
+
+ %% Float and integer keys
+
+ M4 = #{-1.0 => a, 0.0 => b, -1 => c, 0 => d},
+ OrdIter4 = maps:iterator(M4, ordered),
+ [{-1, c}, {0, d}, {-1.0, a}, {0.0, b}] = maps:to_list(OrdIter4),
+ ok = iterator_2_check_order(M4, ordered, reversed),
+
+ ok.
+
+iterator_2_option_to_fun(ordered) ->
+ fun(A, B) -> erts_internal:cmp_term(A, B) =< 0 end;
+iterator_2_option_to_fun(reversed) ->
+ fun(A, B) -> erts_internal:cmp_term(B, A) =< 0 end;
+iterator_2_option_to_fun(F) when is_function(F, 2) ->
+ F.
+
+iterator_2_check_order(M, OrdOption, RevOption) ->
+ OrdCmpFun = iterator_2_option_to_fun(OrdOption),
+ RevCmpFun = iterator_2_option_to_fun(RevOption),
+ OrdKVCmpFun = fun({A, _}, {B, _}) -> OrdCmpFun(A, B) end,
+ RevKVCmpFun = fun({A, _}, {B, _}) -> RevCmpFun(A, B) end,
+
+ OrdKVList = lists:sort(OrdKVCmpFun, maps:to_list(M)),
+ RevKVList = lists:sort(RevKVCmpFun, maps:to_list(M)),
+ RevKVList = lists:reverse(OrdKVList),
+
+ Iter = maps:iterator(M, undefined),
+ OrdIter = maps:iterator(M, OrdOption),
+ RevIter = maps:iterator(M, RevOption),
+
+ OrdKVList = lists:sort(OrdKVCmpFun, iter_kv(Iter)),
+ OrdKVList = lists:sort(OrdKVCmpFun, maps:to_list(Iter)),
+ OrdKVList = iter_kv(OrdIter),
+ OrdKVList = maps:to_list(OrdIter),
+
+ RevKVList = iter_kv(RevIter),
+ RevKVList = maps:to_list(RevIter),
+
+ ok.
+
iter_kv(I) ->
case maps:next(I) of
none ->
@@ -495,6 +600,59 @@ iter_kv(I) ->
[{K,V} | iter_kv(NI)]
end.
+t_iterator_valid(Config) when is_list(Config) ->
+ %% good iterators created via maps:iterator
+ true = maps:is_iterator_valid(maps:iterator(#{})),
+ true = maps:is_iterator_valid(maps:iterator(#{a => b})),
+ true = maps:is_iterator_valid(maps:iterator(#{a => b, c => d})),
+ true = maps:is_iterator_valid(maps:iterator(#{}, undefined)),
+ true = maps:is_iterator_valid(maps:iterator(#{a => b}, undefined)),
+ true = maps:is_iterator_valid(maps:iterator(#{a => b, c => d}, undefined)),
+ true = maps:is_iterator_valid(maps:iterator(#{}, ordered)),
+ true = maps:is_iterator_valid(maps:iterator(#{a => b}, ordered)),
+ true = maps:is_iterator_valid(maps:iterator(#{a => b, c => d}, ordered)),
+ true = maps:is_iterator_valid(maps:iterator(#{}, reversed)),
+ true = maps:is_iterator_valid(maps:iterator(#{a => b}, reversed)),
+ true = maps:is_iterator_valid(maps:iterator(#{a => b, c => d}, reversed)),
+ true = maps:is_iterator_valid(maps:iterator(#{}, fun erlang:'=<'/2)),
+ true = maps:is_iterator_valid(maps:iterator(#{a => b}, fun erlang:'=<'/2)),
+ true = maps:is_iterator_valid(maps:iterator(#{a => b, c => d}, fun erlang:'=<'/2)),
+
+ %% good makeshift iterators
+ true = maps:is_iterator_valid(none),
+ true = maps:is_iterator_valid({a, b, none}),
+ true = maps:is_iterator_valid({a, b, {c, d, none}}),
+ true = maps:is_iterator_valid({a, b, {c, d, {e, f, none}}}),
+ true = maps:is_iterator_valid({a, b, maps:iterator(#{})}),
+ true = maps:is_iterator_valid({a, b, maps:iterator(#{c => d})}),
+
+ %% not iterators
+ false = maps:is_iterator_valid(no_iter),
+ false = maps:is_iterator_valid(1),
+ false = maps:is_iterator_valid(1.0),
+ false = maps:is_iterator_valid([]),
+ false = maps:is_iterator_valid("foo"),
+ false = maps:is_iterator_valid(<<"foo">>),
+ false = maps:is_iterator_valid(fun() -> ok end),
+ false = maps:is_iterator_valid(self()),
+ false = maps:is_iterator_valid(make_ref()),
+ false = maps:is_iterator_valid(#{}),
+ false = maps:is_iterator_valid({}),
+ false = maps:is_iterator_valid({a}),
+ false = maps:is_iterator_valid({a, b}),
+ false = maps:is_iterator_valid({a, b, c, d}),
+
+ %% bad makeshift iterators that only fail on later (ie, subsequent) calls to maps:next/1
+ %% maps:next({a, b, c}) -> {a, b, c}
+ %% maps:next(c) -> badarg
+ false = maps:is_iterator_valid({a, b, c}),
+ %% maps:next({a, b, {c, d, e}}) -> {a, b, {c, d, e}}
+ %% maps:next({c, d, e}) -> {c, d, e}
+ %% maps:next(e) -> badarg
+ false = maps:is_iterator_valid({a, b, {c, d, e}}),
+
+ ok.
+
t_put_opt(Config) when is_list(Config) ->
Value = id(#{complex => map}),
Small = id(#{a => Value}),
@@ -784,15 +942,22 @@ t_groups_from_list(_Config) ->
error_info(_Config) ->
BadIterator = [-1|#{}],
+ BadIterator2 = {x, y, z},
GoodIterator = maps:iterator(#{}),
+ BadOrder = fun(_) -> true end,
+ GoodOrder = fun(A, B) -> A =< B end,
- L = [{filter, [fun(_, _) -> true end, abc]},
+ L = [
+ {filter, [fun(_, _) -> true end, abc]},
{filter, [fun(_, _) -> true end, BadIterator]},
+ {filter, [fun(_, _) -> true end, BadIterator2]},
{filter, [bad_fun, BadIterator],[{1,".*"},{2,".*"}]},
+ {filter, [bad_fun, BadIterator2],[{1,".*"},{2,".*"}]},
{filter, [bad_fun, GoodIterator]},
{filtermap, [fun(_, _) -> true end, abc]},
{filtermap, [fun(_, _) -> true end, BadIterator]},
+ {filtermap, [fun(_, _) -> true end, BadIterator2]},
{filtermap, [fun(_) -> true end, GoodIterator]},
{filtermap, [fun(_) -> ok end, #{}]},
@@ -800,11 +965,13 @@ error_info(_Config) ->
{fold, [fun(_, _, _) -> true end, init, abc]},
{fold, [fun(_, _, _) -> true end, init, BadIterator]},
+ {fold, [fun(_, _, _) -> true end, init, BadIterator2]},
{fold, [fun(_) -> true end, init, GoodIterator]},
{fold, [fun(_) -> ok end, init, #{}]},
{foreach, [fun(_, _) -> ok end, no_map]},
{foreach, [fun(_, _) -> ok end, BadIterator]},
+ {foreach, [fun(_, _) -> ok end, BadIterator2]},
{foreach, [fun(_) -> ok end, GoodIterator]},
{foreach, [fun(_) -> ok end, #{}]},
@@ -834,14 +1001,26 @@ error_info(_Config) ->
{intersect_with, [fun(_, _, _) -> ok end, x, y],[{2,".*"},{3,".*"}]},
{intersect_with, [fun(_, _) -> ok end, #{}, #{}]},
+ {is_iterator_valid, [GoodIterator], [no_fail]},
+ {is_iterator_valid, [BadIterator], [no_fail]},
+ {is_iterator_valid, [BadIterator2], [no_fail]},
+
{is_key,[key, no_map]},
{iterator,[{no,map}]},
+ {iterator, [{no,map}, undefined], [{1, ".*"}]},
+ {iterator, [{no,map}, ordered], [{1, ".*"}]},
+ {iterator, [{no,map}, reversed], [{1, ".*"}]},
+ {iterator, [{no,map}, GoodOrder], [{1, ".*"}]},
+ {iterator, [#{a => b}, BadOrder], [{2, ".*"}]},
+ {iterator, [{no,map}, BadOrder], [{1, ".*"}, {2, ".*"}]},
+
{keys, [{no,map}]},
{map, [fun(_, _) -> true end, abc]},
{map, [fun(_, _) -> true end, BadIterator]},
+ {map, [fun(_, _) -> true end, BadIterator2]},
{map, [fun(_) -> true end, GoodIterator]},
{map, [fun(_) -> ok end, #{}]},
@@ -853,6 +1032,7 @@ error_info(_Config) ->
{merge_with, [a, b, c],[{1,".*"},{2,".*"},{3,".*"}]},
{next,[no_iterator]},
+ {next,[BadIterator]},
{put, [key, value, {no,map}]},
@@ -863,6 +1043,8 @@ error_info(_Config) ->
{take, [key, no_map]},
{to_list,[xyz]},
+ {to_list,[BadIterator]},
+ {to_list,[BadIterator2]},
{update,[key, value, no_map]},
diff --git a/lib/stdlib/test/math_SUITE.erl b/lib/stdlib/test/math_SUITE.erl
index 5804c5bb46..5e92debd69 100644
--- a/lib/stdlib/test/math_SUITE.erl
+++ b/lib/stdlib/test/math_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
init_per_testcase/2, end_per_testcase/2]).
%% Test cases
--export([floor_ceil/1, error_info/1]).
+-export([floor_ceil/1, error_info/1, constants/1]).
suite() ->
@@ -36,7 +36,7 @@ suite() ->
{timetrap,{minutes,1}}].
all() ->
- [floor_ceil, error_info].
+ [floor_ceil, error_info, constants].
groups() ->
[].
@@ -60,6 +60,11 @@ init_per_testcase(_Case, Config) ->
end_per_testcase(_Case, _Config) ->
ok.
+constants(_Config) ->
+ 3.1415926535897932 = math:pi(),
+ 6.2831853071795864 = math:tau(),
+ ok.
+
floor_ceil(_Config) ->
MinusZero = 0.0/(-1.0),
-43.0 = do_floor_ceil(-42.1),
diff --git a/lib/stdlib/test/ms_transform_SUITE.erl b/lib/stdlib/test/ms_transform_SUITE.erl
index c34c7e9e69..5e8e6076ae 100644
--- a/lib/stdlib/test/ms_transform_SUITE.erl
+++ b/lib/stdlib/test/ms_transform_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -582,6 +582,8 @@ autoimported(Config) when is_list(Config) ->
{element,2},
{hd,1},
{length,1},
+ {max,2},
+ {min,2},
{node,0},
{node,1},
{round,1},
@@ -857,6 +859,16 @@ action_function(Config) when is_list(Config) ->
"silent(true), "
"trace([send], [procs]), "
"trace(Y, [procs], [send]) end)">>),
+ [{['$1','$2'],
+ [],
+ [{caller_line},
+ {current_stacktrace},
+ {current_stacktrace,3}]}] =
+ compile_and_run
+ (<<"dbg:fun2ms(fun([X,Y]) -> "
+ "caller_line(),"
+ "current_stacktrace(),"
+ "current_stacktrace(3) end)">>),
ok.
diff --git a/lib/stdlib/test/peer_SUITE.erl b/lib/stdlib/test/peer_SUITE.erl
index 02bd16bd76..9b48f4f28e 100644
--- a/lib/stdlib/test/peer_SUITE.erl
+++ b/lib/stdlib/test/peer_SUITE.erl
@@ -13,6 +13,8 @@
suite/0,
all/0,
groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
init_per_group/2,
end_per_group/2
]).
@@ -46,6 +48,9 @@
old_release/0, old_release/1,
ssh/0, ssh/1,
docker/0, docker/1,
+ post_process_args/0, post_process_args/1,
+ attached/0, attached/1,
+ attached_cntrl_channel_handler_crash/0, attached_cntrl_channel_handler_crash/1,
cntrl_channel_handler_crash/0, cntrl_channel_handler_crash/1,
cntrl_channel_handler_crash_old_release/0, cntrl_channel_handler_crash_old_release/1
]).
@@ -53,18 +58,29 @@
suite() ->
[{timetrap, {minutes, 1}}].
+init_per_suite(Config) ->
+ %% Restrict number of schedulers so that we do not run out of
+ %% file descriptors during test
+ os:putenv("ERL_FLAGS","+S1 +SDio 1"),
+ Config.
+
+end_per_suite(_Config) ->
+ os:unsetenv("ERL_FLAGS"),
+ ok.
+
shutdown_alternatives() ->
[shutdown_halt, shutdown_halt_timeout, shutdown_stop, shutdown_stop_timeout, shutdown_close].
alternative() ->
[basic, peer_states, cast, detached, dyn_peer, stop_peer,
- io_redirect, multi_node, duplicate_name, cntrl_channel_handler_crash,
- cntrl_channel_handler_crash_old_release | shutdown_alternatives()].
+ io_redirect, multi_node, duplicate_name, attached, attached_cntrl_channel_handler_crash,
+ cntrl_channel_handler_crash, cntrl_channel_handler_crash_old_release | shutdown_alternatives()].
groups() ->
[
{dist, [parallel], [errors, dist, peer_down_crash, peer_down_continue, peer_down_boot,
- dist_up_down, dist_localhost, cntrl_channel_handler_crash,
+ dist_up_down, dist_localhost, post_process_args, attached,
+ attached_cntrl_channel_handler_crash, cntrl_channel_handler_crash,
cntrl_channel_handler_crash_old_release | shutdown_alternatives()]},
{dist_seq, [], [dist_io_redirect, %% Cannot be run in parallel in dist group
peer_down_crash_tcp]},
@@ -80,7 +96,7 @@ all() ->
init_per_group(remote, Config) ->
%% check that SSH can connect to localhost, skip the test if not
- try os:cmd("ssh localhost echo ok") of
+ try os:cmd("timeout 10s ssh localhost echo ok") of
"ok\n" -> Config;
_ -> {skip, "'ssh localhost echo ok' did not return ok"}
catch
@@ -487,6 +503,29 @@ duplicate_name(Config) when is_list(Config) ->
?assertException(exit, _, peer:start_link(#{connection => standard_io, name => ?FUNCTION_NAME})),
peer:stop(Peer).
+post_process_args() ->
+ [{doc, "Test that the post_process_args option works"}].
+
+post_process_args(Config) when is_list(Config) ->
+ case {os:type(),os:find_executable("bash")} of
+ {{win32,_}, _Bash} ->
+ {skip,"Test does not work on windows"};
+ {_, false} ->
+ {skip,"Test needs bash to run"};
+ {_, Bash} ->
+ Erl = string:split(ct:get_progname()," ",all),
+ [throw({skip, "Needs bash to run"}) || Bash =:= false],
+ {ok, Peer, _Node} =
+ peer:start_link(
+ #{ name => ?CT_PEER_NAME(),
+ exec => {Bash,["-c"|Erl]},
+ post_process_args =>
+ fun(["-c"|Args]) ->
+ ["-c", lists:flatten(lists:join($\s, Args))]
+ end }),
+ peer:stop(Peer)
+ end.
+
%% -------------------------------------------------------------------
%% Compatibility: old releases
old_release() ->
@@ -580,6 +619,54 @@ docker(Config) when is_list(Config) ->
peer:stop(Peer)
end.
+attached() ->
+ [{doc, "Test that it is possible to start a peer node using run_erl aka attached"}].
+
+attached(Config) ->
+ attached_test(false, Config).
+
+attached_cntrl_channel_handler_crash() ->
+ [{doc, "Test that peer node is halted if peer control channel handler process crashes and peer node is attached"}].
+
+attached_cntrl_channel_handler_crash(Config) ->
+ attached_test(true, Config).
+
+attached_test(CrashConnectionHandler, Config) ->
+ RunErl = os:find_executable("run_erl"),
+ [throw({skip, "Could not find run_erl"}) || RunErl =:= false],
+ Erl = string:split(ct:get_progname()," ",all),
+ RunErlDir = filename:join(proplists:get_value(priv_dir, Config),?FUNCTION_NAME),
+ filelib:ensure_path(RunErlDir),
+ Connection = proplists:get_value(connection, Config),
+ Conn = if Connection =:= undefined -> #{ name => ?CT_PEER_NAME() };
+ true ->
+ case CrashConnectionHandler of
+ false ->
+ #{ connection => Connection };
+ true ->
+ #{name => ?CT_PEER_NAME(),
+ connection => Connection}
+ end
+ end,
+ try peer:start(
+ Conn#{
+ exec => {RunErl, Erl},
+ detached => false,
+ post_process_args =>
+ fun(Args) ->
+ [RunErlDir ++ "/", RunErlDir,
+ lists:flatten(lists:join(" ",[[$',A,$'] || A <- Args]))]
+ end
+ }) of
+ {ok, Peer, Node} when Connection =:= undefined; Connection =:= 0 ->
+ case CrashConnectionHandler of
+ false -> peer:stop(Peer);
+ true -> cntrl_channel_handler_crash_test(Node)
+ end
+ catch error:{detached,_} when Connection =:= standard_io ->
+ ok
+ end.
+
cntrl_channel_handler_crash() ->
[{doc, "Test that peer node is halted if peer control channel handler process crashes"}].
diff --git a/lib/stdlib/test/property_test/base64_prop.erl b/lib/stdlib/test/property_test/base64_prop.erl
index a2e3026dcc..f4d7f264f1 100644
--- a/lib/stdlib/test/property_test/base64_prop.erl
+++ b/lib/stdlib/test/property_test/base64_prop.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2021-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2021-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -54,96 +54,200 @@
%%% Properties %%%
%%%%%%%%%%%%%%%%%%
-prop_encode() ->
+prop_encode_1() ->
?FORALL(
Str,
oneof([list(byte()), binary()]),
begin
Enc = base64:encode(Str),
Dec = base64:decode(Enc),
- is_b64_binary(Enc) andalso str_equals(Str, Dec)
+ is_b64_binary(standard, Enc) andalso str_equals(Str, Dec)
end
).
-prop_encode_to_string() ->
+prop_encode_2() ->
+ ?FORALL(
+ {Str, Mode},
+ {oneof([list(byte()), binary()]), mode()},
+ begin
+ Enc = base64:encode(Str, #{mode => Mode}),
+ Dec = base64:decode(Enc, #{mode => Mode}),
+ is_b64_binary(Mode, Enc) andalso str_equals(Str, Dec)
+ end
+ ).
+
+prop_encode_to_string_1() ->
?FORALL(
Str,
oneof([list(byte()), binary()]),
begin
Enc = base64:encode_to_string(Str),
Dec = base64:decode_to_string(Enc),
- is_b64_string(Enc) andalso str_equals(Str, Dec)
+ is_b64_string(standard, Enc) andalso str_equals(Str, Dec)
+ end
+ ).
+
+prop_encode_to_string_2() ->
+ ?FORALL(
+ {Str, Mode},
+ {oneof([list(byte()), binary()]), mode()},
+ begin
+ Enc = base64:encode_to_string(Str, #{mode => Mode}),
+ Dec = base64:decode_to_string(Enc, #{mode => Mode}),
+ is_b64_string(Mode, Enc) andalso str_equals(Str, Dec)
end
).
-prop_decode() ->
+prop_decode_1() ->
?FORALL(
{NormalizedB64, WspedB64},
- wsped_b64(),
+ wsped_b64(standard),
begin
Dec = base64:decode(WspedB64),
Enc = base64:encode(Dec),
- is_binary(Dec) andalso b64_equals(NormalizedB64, Enc)
+ is_binary(Dec) andalso b64_equals(standard, NormalizedB64, Enc)
+ end
+ ).
+
+prop_decode_2() ->
+ ?FORALL(
+ {{NormalizedB64, WspedB64}, Mode},
+ ?LET(
+ Mode,
+ mode(),
+ {wsped_b64(Mode), Mode}
+ ),
+ begin
+ Dec = base64:decode(WspedB64, #{mode => Mode}),
+ Enc = base64:encode(Dec, #{mode => Mode}),
+ is_binary(Dec) andalso b64_equals(Mode, NormalizedB64, Enc)
end
).
-prop_decode_malformed() ->
- common_decode_malformed(wsped_b64(), fun base64:decode/1).
+prop_decode_1_malformed() ->
+ common_decode_malformed(fun wsped_b64/1, standard, fun(Data, _) -> base64:decode(Data) end).
+
+prop_decode_2_malformed() ->
+ common_decode_malformed(fun wsped_b64/1, mode(), fun base64:decode/2).
-prop_decode_noisy() ->
- common_decode_noisy(fun base64:decode/1).
+prop_decode_1_noisy() ->
+ common_decode_noisy(standard, fun(Data, _) -> base64:decode(Data) end).
-prop_decode_to_string() ->
+prop_decode_2_noisy() ->
+ common_decode_noisy(mode(), fun base64:decode/2).
+
+prop_decode_to_string_1() ->
?FORALL(
{NormalizedB64, WspedB64},
- wsped_b64(),
+ wsped_b64(standard),
begin
Dec = base64:decode_to_string(WspedB64),
Enc = base64:encode(Dec),
- is_bytelist(Dec) andalso b64_equals(NormalizedB64, Enc)
+ is_bytelist(Dec) andalso b64_equals(standard, NormalizedB64, Enc)
+ end
+ ).
+
+prop_decode_to_string_2() ->
+ ?FORALL(
+ {{NormalizedB64, WspedB64}, Mode},
+ ?LET(
+ Mode,
+ mode(),
+ {wsped_b64(Mode), Mode}
+ ),
+ begin
+ Dec = base64:decode_to_string(WspedB64, #{mode => Mode}),
+ Enc = base64:encode(Dec, #{mode => Mode}),
+ is_bytelist(Dec) andalso b64_equals(Mode, NormalizedB64, Enc)
end
).
-prop_decode_to_string_malformed() ->
- common_decode_malformed(wsped_b64(), fun base64:decode_to_string/1).
+prop_decode_to_string_1_malformed() ->
+ common_decode_malformed(fun wsped_b64/1, standard, fun(Data, _) -> base64:decode_to_string(Data) end).
+
+prop_decode_to_string_2_malformed() ->
+ common_decode_malformed(fun wsped_b64/1, mode(), fun base64:decode_to_string/2).
+
+prop_decode_to_string_1_noisy() ->
+ common_decode_noisy(standard, fun(Data, _) -> base64:decode_to_string(Data) end).
-prop_decode_to_string_noisy() ->
- common_decode_noisy(fun base64:decode_to_string/1).
+prop_decode_to_string_2_noisy() ->
+ common_decode_noisy(mode(), fun base64:decode_to_string/2).
-prop_mime_decode() ->
+prop_mime_decode_1() ->
?FORALL(
{NormalizedB64, NoisyB64},
- noisy_b64(),
+ noisy_b64(standard),
begin
Dec = base64:mime_decode(NoisyB64),
Enc = base64:encode(Dec),
- is_binary(Dec) andalso b64_equals(NormalizedB64, Enc)
+ is_binary(Dec) andalso b64_equals(standard, NormalizedB64, Enc)
end
).
-prop_mime_decode_malformed() ->
- common_decode_malformed(noisy_b64(), fun base64:mime_decode/1).
+prop_mime_decode_2() ->
+ ?FORALL(
+ {{NormalizedB64, NoisyB64}, Mode},
+ ?LET(
+ Mode,
+ mode(),
+ {wsped_b64(Mode), Mode}
+ ),
+ begin
+ Dec = base64:mime_decode(NoisyB64, #{mode => Mode}),
+ Enc = base64:encode(Dec, #{mode => Mode}),
+ is_binary(Dec) andalso b64_equals(Mode, NormalizedB64, Enc)
+ end
+ ).
+
+prop_mime_decode_1_malformed() ->
+ common_decode_malformed(fun noisy_b64/1, standard, fun(Data, _) -> base64:mime_decode(Data) end).
-prop_mime_decode_to_string() ->
+prop_mime_decode_2_malformed() ->
+ common_decode_malformed(fun noisy_b64/1, mode(), fun base64:mime_decode/2).
+
+prop_mime_decode_to_string_1() ->
?FORALL(
{NormalizedB64, NoisyB64},
- noisy_b64(),
+ noisy_b64(standard),
begin
Dec = base64:mime_decode_to_string(NoisyB64),
Enc = base64:encode(Dec),
- is_bytelist(Dec) andalso b64_equals(NormalizedB64, Enc)
+ is_bytelist(Dec) andalso b64_equals(standard, NormalizedB64, Enc)
end
).
-prop_mime_decode_to_string_malformed() ->
- common_decode_malformed(noisy_b64(), fun base64:mime_decode_to_string/1).
+prop_mime_decode_to_string_2() ->
+ ?FORALL(
+ {{NormalizedB64, NoisyB64}, Mode},
+ ?LET(
+ Mode,
+ mode(),
+ {wsped_b64(Mode), Mode}
+ ),
+ begin
+ Dec = base64:mime_decode_to_string(NoisyB64, #{mode => Mode}),
+ Enc = base64:encode(Dec, #{mode => Mode}),
+ is_bytelist(Dec) andalso b64_equals(Mode, NormalizedB64, Enc)
+ end
+ ).
-common_decode_noisy(Fn) ->
+prop_mime_decode_to_string_1_malformed() ->
+ common_decode_malformed(fun noisy_b64/1, standard, fun(Data, _) -> base64:mime_decode_to_string(Data) end).
+
+prop_mime_decode_to_string_2_malformed() ->
+ common_decode_malformed(fun noisy_b64/1, mode(), fun base64:mime_decode_to_string/2).
+
+common_decode_noisy(ModeGen, Fn) ->
?FORALL(
- {_, NoisyB64},
- ?SUCHTHAT({NormalizedB64, NoisyB64}, noisy_b64(), NormalizedB64 =/= NoisyB64),
+ {{_, NoisyB64}, Mode},
+ ?LET(
+ Mode,
+ ModeGen,
+ {?SUCHTHAT({NormalizedB64, NoisyB64}, noisy_b64(Mode), NormalizedB64 =/= NoisyB64), Mode}
+ ),
try
- Fn(NoisyB64)
+ Fn(NoisyB64, #{mode => Mode})
of
_ ->
false
@@ -153,25 +257,30 @@ common_decode_noisy(Fn) ->
end
).
-common_decode_malformed(Gen, Fn) ->
+common_decode_malformed(DataGen, ModeGen, Fn) ->
?FORALL(
- MalformedB64,
+ {MalformedB64, Mode},
?LET(
- {{NormalizedB64, NoisyB64}, Malformings},
- {
- Gen,
- oneof(
- [
- [b64_char()],
- [b64_char(), b64_char()],
- [b64_char(), b64_char(), b64_char()]
- ]
- )
- },
- {NormalizedB64, insert_noise(NoisyB64, Malformings)}
+ Mode,
+ ModeGen,
+ ?LET(
+ {{NormalizedB64, NoisyB64}, Malformings, InsertFn},
+ {
+ DataGen(Mode),
+ oneof(
+ [
+ [b64_char(Mode)],
+ [b64_char(Mode), b64_char(Mode)],
+ [b64_char(Mode), b64_char(Mode), b64_char(Mode)]
+ ]
+ ),
+ function1(boolean())
+ },
+ {{NormalizedB64, insert_noise(NoisyB64, Malformings, InsertFn)}, Mode}
+ )
),
try
- Fn(MalformedB64)
+ Fn(MalformedB64, #{mode => Mode})
of
_ ->
false
@@ -185,16 +294,20 @@ common_decode_malformed(Gen, Fn) ->
%%% Generators %%%
%%%%%%%%%%%%%%%%%%
+%% Generate base64 encoding mode.
+mode() ->
+ oneof([standard, urlsafe]).
+
%% Generate a single character from the base64 alphabet.
-b64_char() ->
- oneof(b64_chars()).
+b64_char(Mode) ->
+ oneof(b64_chars(Mode)).
%% Generate a string of characters from the base64 alphabet,
%% including padding if needed.
-b64_string() ->
+b64_string(Mode) ->
?LET(
{L, Filler},
- {list(b64_char()), b64_char()},
+ {list(b64_char(Mode)), b64_char(Mode)},
case length(L) rem 4 of
0 -> L;
1 -> L ++ [Filler, $=, $=];
@@ -205,43 +318,43 @@ b64_string() ->
%% Generate a binary of characters from the base64 alphabet,
%% including padding if needed.
-b64_binary() ->
+b64_binary(Mode) ->
?LET(
L,
- b64_string(),
+ b64_string(Mode),
list_to_binary(L)
).
%% Generate a string or binary of characters from the
%% base64 alphabet, including padding if needed.
-b64() ->
- oneof([b64_string(), b64_binary()]).
+b64(Mode) ->
+ oneof([b64_string(Mode), b64_binary(Mode)]).
%% Generate a string or binary of characters from the
%% base64 alphabet, including padding if needed, with
%% whitespaces inserted at random indexes.
-wsped_b64() ->
+wsped_b64(Mode) ->
?LET(
- {B64, Wsps},
- {b64(), list(oneof([$\t, $\r, $\n, $\s]))},
- {B64, insert_noise(B64, Wsps)}
+ {B64, Wsps, InsertFn},
+ {b64(Mode), list(oneof([$\t, $\r, $\n, $\s])), function1(boolean())},
+ {B64, insert_noise(B64, Wsps, InsertFn)}
).
%% Generate a single character outside of the base64 alphabet.
%% As whitespaces are allowed but ignored in base64, this generator
%% will produce no whitespaces, either.
-non_b64_char() ->
- oneof(lists:seq(16#00, 16#FF) -- b64_allowed_chars()).
+non_b64_char(Mode) ->
+ oneof(lists:seq(16#00, 16#FF) -- b64_allowed_chars(Mode)).
%% Generate a string or binary of characters from the
%% base64 alphabet, including padding if needed, with
%% whitespaces and non-base64 ("invalid") characters
%% inserted at random indexes.
-noisy_b64() ->
+noisy_b64(Mode) ->
?LET(
- {{B64, WspedB64}, Noise},
- {wsped_b64(), non_empty(list(non_b64_char()))},
- {B64, insert_noise(WspedB64, Noise)}
+ {{B64, WspedB64}, Noise, InsertFn},
+ {wsped_b64(Mode), non_empty(list(non_b64_char(Mode))), function1(boolean())},
+ {B64, insert_noise(WspedB64, Noise, InsertFn)}
).
%%%%%%%%%%%%%%%
@@ -252,81 +365,92 @@ noisy_b64() ->
%% "=" is not included, as it is special in that it
%% may only appear at the end of a base64 encoded string
%% for padding.
-b64_chars() ->
+b64_chars_common() ->
lists:seq($0, $9) ++
lists:seq($a, $z) ++
- lists:seq($A, $Z) ++
- [$+, $/].
+ lists:seq($A, $Z).
+
+b64_chars(standard) ->
+ b64_chars_common() ++ [$+, $/];
+b64_chars(urlsafe) ->
+ b64_chars_common() ++ [$-, $_].
%% In addition to the above, the whitespace characters
%% HTAB, CR, LF and SP are allowed to appear in a base64
%% encoded string and should be ignored.
-b64_allowed_chars() ->
- [$\t, $\r, $\n, $\s | b64_chars()].
+b64_allowed_chars(Mode) ->
+ [$\t, $\r, $\n, $\s | b64_chars(Mode)].
%% Insert the given list of noise characters at random
%% places into the given base64 string.
-insert_noise(B64, []) ->
+insert_noise(B64, Noise, InsertFn) ->
+ insert_noise(B64, Noise, InsertFn, 0).
+
+insert_noise(B64, [], _, _) ->
B64;
-insert_noise([], Noise) ->
+insert_noise([], Noise, _, _) ->
Noise;
-insert_noise(<<>>, Noise) ->
+insert_noise(<<>>, Noise, _, _) ->
list_to_binary(Noise);
-insert_noise([B|Bs] = B64, [N|Ns] = Noise) ->
- case rand:uniform(2) of
- 1 ->
- [B|insert_noise(Bs, Noise)];
- 2 ->
- [N|insert_noise(B64, Ns)]
+insert_noise([B|Bs] = B64, [N|Ns] = Noise, InsertFn, Idx) ->
+ case InsertFn(Idx) of
+ true ->
+ [B|insert_noise(Bs, Noise, InsertFn, Idx + 1)];
+ false ->
+ [N|insert_noise(B64, Ns, InsertFn, Idx + 1)]
end;
-insert_noise(<<B, Bs/binary>> = B64, [N|Ns] = Noise) ->
- case rand:uniform(2) of
- 1 ->
- <<B, (insert_noise(Bs, Noise))/binary>>;
- 2 ->
- <<N, (insert_noise(B64, Ns))/binary>>
+insert_noise(<<B, Bs/binary>> = B64, [N|Ns] = Noise, InsertFn, Idx) ->
+ case InsertFn(Idx) of
+ true ->
+ <<B, (insert_noise(Bs, Noise, InsertFn, Idx + 1))/binary>>;
+ false ->
+ <<N, (insert_noise(B64, Ns, InsertFn, Idx + 1))/binary>>
end.
%% Check if the given character is in the base64 alphabet.
%% This does not include the padding character "=".
-is_b64_char($+) ->
+is_b64_char(standard, $+) ->
+ true;
+is_b64_char(standard, $/) ->
+ true;
+is_b64_char(urlsafe, $-) ->
true;
-is_b64_char($/) ->
+is_b64_char(urlsafe, $_) ->
true;
-is_b64_char(C) when C >= $0, C =< $9 ->
+is_b64_char(_, C) when C >= $0, C =< $9 ->
true;
-is_b64_char(C) when C >= $A, C =< $Z ->
+is_b64_char(_, C) when C >= $A, C =< $Z ->
true;
-is_b64_char(C) when C >= $a, C =< $z ->
+is_b64_char(_, C) when C >= $a, C =< $z ->
true;
-is_b64_char(_) ->
+is_b64_char(_, _) ->
false.
%% Check if the given argument is a base64 binary,
%% ie that it consists of quadruplets of characters
%% from the base64 alphabet, whereas the last quadruplet
%% may be padded with one or two "="s
-is_b64_binary(B) ->
- is_b64_binary(B, 0).
+is_b64_binary(Mode, B) ->
+ is_b64_binary(Mode, B, 0).
-is_b64_binary(<<>>, N) ->
+is_b64_binary(_, <<>>, N) ->
N rem 4 =:= 0;
-is_b64_binary(<<$=>>, N) ->
+is_b64_binary(_, <<$=>>, N) ->
N rem 4 =:= 3;
-is_b64_binary(<<$=, $=>>, N) ->
+is_b64_binary(_, <<$=, $=>>, N) ->
N rem 4 =:= 2;
-is_b64_binary(<<C, More/binary>>, N) ->
- case is_b64_char(C) of
+is_b64_binary(Mode, <<C, More/binary>>, N) ->
+ case is_b64_char(Mode, C) of
true ->
- is_b64_binary(More, N + 1);
+ is_b64_binary(Mode, More, N + 1);
false ->
false
end.
%% Check if the given argument is a base64 string
%% (see is_b64_binary/1)
-is_b64_string(S) ->
- is_b64_binary(list_to_binary(S)).
+is_b64_string(Mode, S) ->
+ is_b64_binary(Mode, list_to_binary(S)).
%% Check if the argument is a list of bytes.
is_bytelist(L) ->
@@ -349,23 +473,23 @@ str_equals(Str1, Str2) when is_binary(Str1), is_binary(Str2) ->
%% Assumes that the given arguments are in a normalized form,
%% ie that they consist only of characters from the base64
%% alphabet and possible padding ("=").
-b64_equals(L, B) when is_list(L) ->
- b64_equals(list_to_binary(L), B);
-b64_equals(B, L) when is_list(L) ->
- b64_equals(B, list_to_binary(L));
-b64_equals(B1, B2) when is_binary(B1), is_binary(B2) ->
- b64_equals1(B1, B2).
-
-b64_equals1(<<Eq:4/bytes>>, <<Eq:4/bytes>>) ->
- is_b64_binary(Eq);
-b64_equals1(<<Eq:4/bytes, More1/binary>>, <<Eq:4/bytes, More2/binary>>) ->
- case lists:all(fun is_b64_char/1, binary_to_list(Eq)) of
+b64_equals(Mode, L, B) when is_list(L) ->
+ b64_equals(Mode, list_to_binary(L), B);
+b64_equals(Mode, B, L) when is_list(L) ->
+ b64_equals(Mode, B, list_to_binary(L));
+b64_equals(Mode, B1, B2) when is_binary(B1), is_binary(B2) ->
+ b64_equals1(Mode, B1, B2).
+
+b64_equals1(Mode, <<Eq:4/bytes>>, <<Eq:4/bytes>>) ->
+ is_b64_binary(Mode, Eq);
+b64_equals1(Mode, <<Eq:4/bytes, More1/binary>>, <<Eq:4/bytes, More2/binary>>) ->
+ case lists:all(fun(C) -> is_b64_char(Mode, C) end, binary_to_list(Eq)) of
true ->
- b64_equals1(More1, More2);
+ b64_equals1(Mode, More1, More2);
false ->
false
end;
-b64_equals1(<<Eq, B1, $=, $=>>, <<Eq, B2, $=, $=>>) ->
+b64_equals1(Mode, <<Eq, B1, $=, $=>>, <<Eq, B2, $=, $=>>) ->
%% If the encoded string ends with "==", there exist multiple
%% possibilities for the character preceding the "==" as only the
%% 3rd and 4th bits of the encoded byte represented by that
@@ -374,7 +498,7 @@ b64_equals1(<<Eq, B1, $=, $=>>, <<Eq, B2, $=, $=>>) ->
%% For example, all of the encoded strings "QQ==", "QR==", ..., "QZ=="
%% decode to the string "A", since all the bytes represented by Q to Z
%% are the same in the significant 3rd and 4th bit.
- case is_b64_char(Eq) of
+ case is_b64_char(Mode, Eq) of
true ->
Normalize = fun
(C) when C >= $A, C =< $P -> $A;
@@ -383,20 +507,21 @@ b64_equals1(<<Eq, B1, $=, $=>>, <<Eq, B2, $=, $=>>) ->
(C) when C >= $g, C =< $v -> $g;
(C) when C >= $w, C =< $z -> $w;
(C) when C >= $0, C =< $9 -> $w;
- ($+) -> $w;
- ($/) -> $w
+ ($+) when Mode =:= standard -> $w;
+ ($-) when Mode =:= urlsafe -> $w;
+ ($/) when Mode =:= standard -> $w;
+ ($_) when Mode =:= urlsafe -> $w
end,
Normalize(B1) =:= Normalize(B2);
false ->
false
end;
-b64_equals1(<<Eq:2/bytes, B1, $=>>, <<Eq:2/bytes, B2, $=>>) ->
+b64_equals1(Mode, <<Eq1, Eq2, B1, $=>>, <<Eq1, Eq2, B2, $=>>) ->
%% Similar to the above, but with the encoded string ending with a
%% single "=" the 3rd to 6th bits of the encoded byte are significant,
%% such that, for example, all the encoded strings "QUE=" to "QUH="
%% decode to the same string "AA".
- <<Eq1, Eq2>> = Eq,
- case is_b64_char(Eq1) andalso is_b64_char(Eq2) of
+ case is_b64_char(Mode, Eq1) andalso is_b64_char(Mode, Eq2) of
true ->
Normalize = fun
(C) when C >= $A, C =< $D -> $A;
@@ -416,14 +541,16 @@ b64_equals1(<<Eq:2/bytes, B1, $=>>, <<Eq:2/bytes, B2, $=>>) ->
(C) when C >= $0, C =< $3 -> $0;
(C) when C >= $4, C =< $7 -> $4;
(C) when C >= $8, C =< $9 -> $8;
- ($+) -> $8;
- ($/) -> $8
+ ($+) when Mode =:= standard -> $8;
+ ($-) when Mode =:= urlsafe -> $8;
+ ($/) when Mode =:= standard -> $8;
+ ($_) when Mode =:= urlsafe -> $8
end,
Normalize(B1) =:= Normalize(B2);
false ->
false
end;
-b64_equals1(<<>>, <<>>) ->
+b64_equals1(_, <<>>, <<>>) ->
true;
-b64_equals1(_, _) ->
+b64_equals1(_, _, _) ->
false.
diff --git a/lib/stdlib/test/property_test/binary_prop.erl b/lib/stdlib/test/property_test/binary_prop.erl
new file mode 100644
index 0000000000..00efa77b8c
--- /dev/null
+++ b/lib/stdlib/test/property_test/binary_prop.erl
@@ -0,0 +1,37 @@
+-module(binary_prop).
+
+-export([prop_hex_encode_2/0]).
+
+-compile([export_all, nowarn_export_all]).
+
+-include_lib("common_test/include/ct_property_test.hrl").
+
+prop_hex_encode_2() ->
+ ?FORALL(Data, data(),
+ begin
+ UpperHex = binary:encode_hex(Data, uppercase),
+ LowerHex = binary:encode_hex(Data, lowercase),
+ binary:decode_hex(LowerHex) =:= Data andalso
+ binary:decode_hex(UpperHex) =:= Data andalso
+ check_hex_encoded(Data, UpperHex, LowerHex)
+ end).
+
+data() ->
+ ?SIZED(Size, resize(Size * 2, binary())).
+
+
+%% @doc Credit to the <a href="https://github.com/erlang/otp/pull/6297#discussion_r1034771450">comment</a> of @Maria-12648430
+-spec check_hex_encoded(Data :: binary(), UpperHex :: binary(), LoweHex :: binary()) -> boolean().
+check_hex_encoded(<<I1:4, I2:4, Ins/binary>>, <<U1:8, U2:8, UCs/binary>>, <<L1:8, L2:8, LCs/binary>>) ->
+ check_hex_chars_match(I1, U1, L1) andalso
+ check_hex_chars_match(I2, U2, L2) andalso
+ check_hex_encoded(Ins, UCs, LCs);
+check_hex_encoded(<<>>, <<>>, <<>>) ->
+ true;
+check_hex_encoded(_, _, _) ->
+ false.
+
+check_hex_chars_match(X, U, L) when X < 10 ->
+ (U =:= $0 + X) andalso (L =:= $0 + X);
+check_hex_chars_match(X, U, L) ->
+ (U =:= $A + X -10) andalso (L =:= $a + X -10).
diff --git a/lib/stdlib/test/property_test/lists_prop.erl b/lib/stdlib/test/property_test/lists_prop.erl
new file mode 100644
index 0000000000..68c087b76d
--- /dev/null
+++ b/lib/stdlib/test/property_test/lists_prop.erl
@@ -0,0 +1,2146 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(lists_prop).
+
+-compile([export_all, nowarn_export_all]).
+
+-proptest(eqc).
+-proptest([triq, proper]).
+
+-ifndef(EQC).
+-ifndef(PROPER).
+-ifndef(TRIQ).
+-define(EQC, true).
+-endif.
+-endif.
+-endif.
+
+-ifdef(EQC).
+-include_lib("eqc/include/eqc.hrl").
+-define(MOD_eqc,eqc).
+
+-else.
+-ifdef(PROPER).
+-include_lib("proper/include/proper.hrl").
+-define(MOD_eqc,proper).
+
+-else.
+-ifdef(TRIQ).
+-define(MOD_eqc,triq).
+-include_lib("triq/include/triq.hrl").
+
+-endif.
+-endif.
+-endif.
+
+-define(RANDOM_ATOMS, 1000).
+
+%%%%%%%%%%%%%%%%%%
+%%% Properties %%%
+%%%%%%%%%%%%%%%%%%
+
+%% all/2
+prop_all_true() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ lists:all(fun(_) -> true end, InList)
+ ).
+
+prop_all_false() ->
+ ?FORALL(
+ {InList, Elem},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), make_ref()},
+ {F ++ [E|R], E}
+ ),
+ not lists:all(fun(T) -> T =/= Elem end, InList)
+ ).
+
+%% any/2
+prop_any_true() ->
+ ?FORALL(
+ {InList, Elem},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), make_ref()},
+ {F ++ [E|R], E}
+ ),
+ lists:any(fun(T) -> T =:= Elem end, InList)
+ ).
+
+prop_any_false() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ not lists:any(fun(_) -> false end, InList)
+ ).
+
+%% append/1
+prop_append_1() ->
+ ?FORALL(
+ InLists,
+ list(gen_list()),
+ check_appended(InLists, lists:append(InLists))
+ ).
+
+%% append/2
+prop_append_2() ->
+ ?FORALL(
+ {InList1, InList2},
+ {gen_list(), gen_list()},
+ lists:append(InList1, InList2) =:= InList1 ++ InList2
+ ).
+
+%% concat/1
+prop_concat() ->
+ ?FORALL(
+ {InList, ExpString},
+ gen_list_fold(
+ oneof([gen_atom(), number(), string()]),
+ fun
+ (A, Acc) when is_atom(A) -> Acc ++ atom_to_list(A);
+ (I, Acc) when is_integer(I) -> Acc ++ integer_to_list(I);
+ (F, Acc) when is_float(F) -> Acc ++ float_to_list(F);
+ (L, Acc) when is_list(L) -> Acc ++ L
+ end,
+ []
+ ),
+ lists:concat(InList) =:= ExpString
+ ).
+
+%% delete/2
+prop_delete() ->
+ ?FORALL(
+ {InList, DelElem},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), gen_any()},
+ {F ++ [E|R], E}
+ ),
+ begin
+ DeletedList = lists:delete(DelElem, InList),
+ length(DeletedList) =:= length(InList) - 1 andalso
+ check_deleted(DelElem, InList, DeletedList)
+ end
+ ).
+
+prop_delete_absent() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ lists:delete(make_ref(), InList) =:= InList
+ ).
+
+%% droplast/1
+prop_droplast() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ try
+ lists:droplast(InList) =:= lists:reverse(tl(lists:reverse(InList)))
+ catch
+ error:_ ->
+ InList =:= []
+ end
+ ).
+
+%% dropwhile/2
+prop_dropwhile() ->
+ ?FORALL(
+ {Pred, InList, ExpList},
+ ?LET(
+ Fn,
+ function1(boolean()),
+ ?LET(
+ {L, {_, DL}},
+ gen_list_fold(
+ gen_any(),
+ fun(E, {Drop, Acc}) ->
+ case Drop andalso Fn(E) of
+ true -> {true, Acc};
+ false -> {false, Acc ++ [E]}
+ end
+ end,
+ {true, []}
+ ),
+ {Fn, L, DL}
+ )
+ ),
+ lists:dropwhile(Pred, InList) =:= ExpList
+ ).
+
+%% duplicate/2
+prop_duplicate() ->
+ ?FORALL(
+ {N, Term, ExpList},
+ ?LET(
+ T,
+ gen_any(),
+ ?LET(L, list(T), {length(L), T, L})
+ ),
+ lists:duplicate(N, Term) =:= ExpList
+ ).
+
+%% enumerate/1
+prop_enumerate_1() ->
+ ?FORALL(
+ {InList, ExpList},
+ ?LET(
+ {L, {_, EL}},
+ gen_list_fold(
+ gen_any(),
+ fun(T, {I, Acc}) ->
+ {I + 1, Acc ++ [{I, T}]}
+ end,
+ {1, []}
+ ),
+ {L, EL}
+ ),
+ lists:enumerate(InList) =:= ExpList
+ ).
+
+%% enumerate/2
+prop_enumerate_2() ->
+ ?FORALL(
+ {StartIndex, InList, ExpList},
+ ?LET(
+ N,
+ integer(),
+ ?LET(
+ {L, {_, EL}},
+ gen_list_fold(
+ gen_any(),
+ fun(T, {I, Acc}) ->
+ {I + 1, Acc ++ [{I, T}]}
+ end,
+ {N, []}
+ ),
+ {N, L, EL}
+ )
+ ),
+ lists:enumerate(StartIndex, InList) =:= ExpList
+ ).
+
+%% enumerate/3
+prop_enumerate_3() ->
+ ?FORALL(
+ {StartIndex, Step, InList, ExpList},
+ ?LET(
+ {N, S},
+ {integer(), integer()},
+ ?LET(
+ {L, {_, EL}},
+ gen_list_fold(
+ gen_any(),
+ fun(T, {I, Acc}) ->
+ {I + S, Acc ++ [{I, T}]}
+ end,
+ {N, []}
+ ),
+ {N, S, L, EL}
+ )
+ ),
+ lists:enumerate(StartIndex, Step, InList) =:= ExpList
+ ).
+
+%% filter/2
+prop_filter() ->
+ ?FORALL(
+ {Pred, InList, ExpList},
+ ?LET(
+ P,
+ function1(boolean()),
+ ?LET(
+ {L, F},
+ gen_list_fold(
+ gen_any(),
+ fun(T, Acc) ->
+ case P(T) of
+ true -> Acc ++ [T];
+ false -> Acc
+ end
+ end,
+ []
+ ),
+ {P, L, F}
+ )
+ ),
+ lists:filter(Pred, InList) =:= ExpList
+ ).
+
+%% filtermap/2
+prop_filtermap() ->
+ ?FORALL(
+ {FilterMapFn, InList, ExpList},
+ ?LET(
+ Fn,
+ function1(oneof([true, false, {true, gen_any()}])),
+ ?LET(
+ {L, FM},
+ gen_list_fold(
+ gen_any(),
+ fun(T, Acc) ->
+ case Fn(T) of
+ false -> Acc;
+ true -> Acc ++ [T];
+ {true, T1} -> Acc ++ [T1]
+ end
+ end,
+ []
+ ),
+ {Fn, L, FM}
+ )
+ ),
+ lists:filtermap(FilterMapFn, InList) =:= ExpList
+ ).
+
+%% flatlength/1
+prop_flatlength() ->
+ ?FORALL(
+ {DeepList, Len},
+ gen_list_deepfold(fun(_, _, Cnt) -> Cnt + 1 end, 0),
+ lists:flatlength(DeepList) =:= Len
+ ).
+
+%% flatmap/2
+prop_flatmap() ->
+ ?FORALL(
+ {MapFn, InList, ExpList},
+ ?LET(
+ Fn,
+ function1(gen_list()),
+ ?LET(
+ {L, FlatMapped},
+ gen_list_fold(
+ gen_any(),
+ fun(T, Acc) ->
+ Acc ++ Fn(T)
+ end,
+ []
+ ),
+ {Fn, L, FlatMapped}
+ )
+ ),
+ lists:flatmap(MapFn, InList) =:= ExpList
+ ).
+
+%% flatten/1
+prop_flatten_1() ->
+ ?FORALL(
+ {DeepList, FlatList},
+ gen_list_deepfold(fun(_, E, Acc) -> Acc ++ [E] end, []),
+ lists:flatten(DeepList) =:= FlatList
+ ).
+
+%% flatten/2
+prop_flatten_2() ->
+ ?FORALL(
+ {{DeepList, FlatList}, Tail},
+ {gen_list_deepfold(fun(_, E, Acc) -> Acc ++ [E] end, []), gen_list()},
+ lists:flatten(DeepList, Tail) =:= FlatList ++ Tail
+ ).
+
+%% foldl/3
+prop_foldl() ->
+ ?FORALL(
+ {FoldFn, InList, Acc0, Exp},
+ ?LET(
+ {Fn, Acc0},
+ {function2(gen_any()), gen_any()},
+ ?LET(
+ {L, V},
+ gen_list_fold(gen_any(), Fn, Acc0),
+ {Fn, L, Acc0, V}
+ )
+ ),
+ lists:foldl(FoldFn, Acc0, InList) =:= Exp
+ ).
+
+%% foldr/3
+prop_foldr() ->
+ ?FORALL(
+ {FoldFn, InList, Acc0, Exp},
+ ?LET(
+ {Fn, Acc0},
+ {function2(gen_any()), gen_any()},
+ ?LET(
+ {L, V},
+ gen_list_fold(gen_any(), Fn, Acc0),
+ {Fn, lists:reverse(L), Acc0, V}
+ )
+ ),
+ lists:foldr(FoldFn, Acc0, InList) =:= Exp
+ ).
+
+%% foreach/2
+prop_foreach() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ begin
+ Tag = make_ref(),
+ lists:foreach(fun(E) -> self() ! {Tag, E} end, InList),
+ [receive {Tag, T} -> T after 100 -> error(timeout) end || _ <- InList] =:= InList
+ end
+ ).
+
+%% join/2
+prop_join() ->
+ ?FORALL(
+ {Sep, InList},
+ {gen_any(), gen_list()},
+ check_joined(Sep, InList, lists:join(Sep, InList))
+ ).
+
+%% keydelete/3
+prop_keydelete() ->
+ ?FORALL(
+ {Key, N, InList},
+ ?LET(
+ {K, N},
+ {gen_any(), range(1, 5)},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), gen_keytuple(K, N, N + 3)},
+ {K, N, F ++ [E|R]}
+ )
+ ),
+ begin
+ DeletedL = lists:keydelete(Key, N, InList),
+ length(DeletedL) =:= length(InList) - 1 andalso
+ check_keydeleted(Key, N, InList, DeletedL)
+ end
+ ).
+
+prop_keydelete_absent() ->
+ ?FORALL(
+ {N, InList},
+ {pos_integer(), gen_list()},
+ lists:keydelete(make_ref(), N, InList) =:= InList
+ ).
+
+%% keyfind/3
+prop_keyfind() ->
+ ?FORALL(
+ {Key, N, InList},
+ ?LET(
+ {K, N},
+ {gen_any(), range(1, 5)},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), gen_keytuple(K, N, N + 3)},
+ {K, N, F ++ [E|R]}
+ )
+ ),
+ begin
+ Found = lists:keyfind(Key, N, InList),
+ is_tuple(Found) andalso
+ tuple_size(Found) >= N andalso
+ element(N, Found) == Key
+ end
+ ).
+
+prop_keyfind_absent() ->
+ ?FORALL(
+ {N, InList},
+ {pos_integer(), gen_list()},
+ not lists:keyfind(make_ref(), N, InList)
+ ).
+
+%% keymap/3
+prop_keymap() ->
+ ?FORALL(
+ {MapFn, N, InList, ExpList},
+ ?LET(
+ Fn,
+ function([gen_any()], gen_any()),
+ ?LET(
+ N,
+ range(1, 5),
+ ?LET(
+ {L, M},
+ gen_list_fold(
+ gen_tuple(N, N + 3),
+ fun(T, Acc) ->
+ Acc ++ [setelement(N, T, Fn(element(N, T)))]
+ end,
+ []
+ ),
+ {Fn, N, L, M}
+ )
+ )
+ ),
+ lists:keymap(MapFn, N, InList) =:= ExpList
+ ).
+
+%% keymember/3
+prop_keymember() ->
+ ?FORALL(
+ {Key, N, InList},
+ ?LET(
+ {K, N},
+ {gen_any(), range(1, 5)},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), gen_keytuple(K, N, N + 3)},
+ {K, N, F ++ [E|R]}
+ )
+ ),
+ lists:keymember(Key, N, InList)
+ ).
+
+prop_keymember_absent() ->
+ ?FORALL(
+ {N, InList},
+ {pos_integer(), gen_list()},
+ not lists:keymember(make_ref(), N, InList)
+ ).
+
+%% keymerge/3
+prop_keymerge() ->
+ ?FORALL(
+ {N, InList1, InList2},
+ ?LET(
+ N,
+ range(1, 5),
+ ?LET(
+ {L1, L2},
+ {list(gen_tuple(N, N+3)), list(gen_tuple(N, N+3))},
+ {N, lists:sort(L1), lists:sort(L2)}
+ )
+ ),
+ check_merged(
+ fun (E1, E2) -> element(N, E1) =< element(N, E2) end,
+ [InList1, InList2],
+ lists:keymerge(N, InList1, InList2)
+ )
+ ).
+
+prop_keymerge_invalid() ->
+ ?FORALL(
+ {N, InList, X, Y},
+ ?LET(
+ N,
+ range(1, 5),
+ ?LET(
+ {L, X, Y},
+ {list(gen_tuple(N, N+3)), non_list(), non_list()},
+ {N, L, X, Y}
+ )
+ ),
+ expect_error(fun lists:keymerge/3, [N, InList, Y]) andalso
+ expect_error(fun lists:keymerge/3, [N, X, InList]) andalso
+ expect_error(fun lists:keymerge/3, [N, X, Y])
+ ).
+
+%% keyreplace/4
+prop_keyreplace() ->
+ ?FORALL(
+ {Key, N, InList, Replacement},
+ ?LET(
+ {K, N},
+ {gen_any(), range(1, 5)},
+ ?LET(
+ {F, R, E0, E1},
+ {gen_list(), gen_list(), gen_keytuple(K, N, N + 3), gen_tuple()},
+ {K, N, F ++ [E0|R], E1}
+ )
+ ),
+ check_keyreplaced(Key, N, Replacement, InList, lists:keyreplace(Key, N, InList, Replacement))
+ ).
+
+prop_keyreplace_absent() ->
+ ?FORALL(
+ {N, InList, Replacement},
+ {pos_integer(), gen_list(), gen_tuple()},
+ lists:keyreplace(make_ref(), N, InList, Replacement) =:= InList
+ ).
+
+%% keysearch/3
+prop_keysearch() ->
+ ?FORALL(
+ {Key, N, InList},
+ ?LET(
+ {K, N},
+ {gen_any(), range(1, 5)},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), gen_keytuple(K, N, N + 3)},
+ {K, N, F ++ [E|R]}
+ )
+ ),
+ begin
+ {value, Found} = lists:keysearch(Key, N, InList),
+ is_tuple(Found) andalso
+ tuple_size(Found) >= N andalso
+ element(N, Found) == Key
+ end
+ ).
+
+prop_keysearch_absent() ->
+ ?FORALL(
+ {N, InList},
+ {pos_integer(), gen_list()},
+ not lists:keysearch(make_ref(), N, InList)
+ ).
+
+%% keysort/2
+prop_keysort() ->
+ ?FORALL(
+ {N, InList},
+ ?LET(
+ N,
+ range(1, 5),
+ {N, list(gen_tuple(N, N + 3))}
+ ),
+ begin
+ Sorted = lists:keysort(N, InList),
+ length(Sorted) =:= length(InList) andalso
+ check_sorted(fun(E1, E2) -> element(N, E1) =< element(N, E2) end, InList, Sorted)
+ end
+ ).
+
+%% keystore/4
+prop_keystore() ->
+ ?FORALL(
+ {Key, N, InList, ToStore},
+ ?LET(
+ {K, N},
+ {gen_any(), range(1, 5)},
+ ?LET(
+ {F, R, E0, E1},
+ {gen_list(), gen_list(), gen_keytuple(K, N, N + 3), gen_tuple()},
+ {K, N, F ++ [E0|R], E1}
+ )
+ ),
+ check_keyreplaced(Key, N, ToStore, InList, lists:keystore(Key, N, InList, ToStore))
+ ).
+
+prop_keystore_absent() ->
+ ?FORALL(
+ {N, InList, ToStore},
+ {pos_integer(), gen_list(), gen_tuple()},
+ lists:keystore(make_ref(), N, InList, ToStore) =:= InList ++ [ToStore]
+ ).
+
+%% keytake/3
+prop_keytake() ->
+ ?FORALL(
+ {Key, N, InList, ExpList, ExpElem},
+ ?LET(
+ {K, N},
+ {make_ref(), range(1, 5)},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), gen_keytuple(K, N, N + 3)},
+ {K, N, F ++ [E|R], F ++ R, E}
+ )
+ ),
+ lists:keytake(Key, N, InList) =:= {value, ExpElem, ExpList}
+ ).
+
+prop_keytake_absent() ->
+ ?FORALL(
+ {N, InList},
+ {pos_integer(), gen_list()},
+ lists:keytake(make_ref(), N, InList) =:= false
+ ).
+
+%% last/1
+prop_last() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ try
+ lists:last(InList) =:= hd(lists:reverse(InList))
+ catch
+ error:_ ->
+ InList =:= []
+ end
+ ).
+
+%% map/2
+prop_map() ->
+ ?FORALL(
+ {MapFn, InList, ExpList},
+ ?LET(
+ Fn,
+ function1(gen_any()),
+ ?LET(
+ {L, M},
+ gen_list_fold(
+ gen_any(),
+ fun(T, Acc) ->
+ Acc ++ [Fn(T)]
+ end,
+ []
+ ),
+ {Fn, L, M}
+ )
+ ),
+ lists:map(MapFn, InList) =:= ExpList
+ ).
+
+%% mapfoldl/3
+prop_mapfoldl() ->
+ ?FORALL(
+ {MapFoldFn, InList, Acc0, Exp},
+ ?LET(
+ {MapFn, FoldFn, Acc0},
+ {function1(gen_any()), function2(gen_any()), gen_any()},
+ ?LET(
+ {L, MV},
+ gen_list_fold(
+ gen_any(),
+ fun(T, {AccM, AccF}) ->
+ {AccM ++ [MapFn(T)], FoldFn(T, AccF)}
+ end,
+ {[], Acc0}
+ ),
+ {fun(T, Acc) -> {MapFn(T), FoldFn(T, Acc)} end, L, Acc0, MV}
+ )
+ ),
+ lists:mapfoldl(MapFoldFn, Acc0, InList) =:= Exp
+ ).
+
+%% mapfoldr/3
+prop_mapfoldr() ->
+ ?FORALL(
+ {MapFoldFn, InList, Acc0, Exp},
+ ?LET(
+ {MapFn, FoldFn, Acc0},
+ {function1(gen_any()), function2(gen_any()), gen_any()},
+ ?LET(
+ {L, MV},
+ gen_list_fold(
+ gen_any(),
+ fun(T, {AccM, AccF}) ->
+ {[MapFn(T)|AccM], FoldFn(T, AccF)}
+ end,
+ {[], Acc0}
+ ),
+ {fun(T, Acc) -> {MapFn(T), FoldFn(T, Acc)} end, lists:reverse(L), Acc0, MV}
+ )
+ ),
+ lists:mapfoldr(MapFoldFn, Acc0, InList) =:= Exp
+ ).
+
+%% max/1
+prop_max() ->
+ ?FORALL(
+ {InList, ExpMax},
+ gen_list_fold(gen_any(), fun erlang:max/2),
+ try
+ lists:max(InList) == ExpMax
+ catch
+ error:_ ->
+ InList =:= []
+ end
+ ).
+
+%% member/2
+prop_member() ->
+ ?FORALL(
+ {InList, Member},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), gen_any()},
+ {F ++ [E|R], E}
+ ),
+ lists:member(Member, InList)
+ ).
+
+prop_member_absent() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ not lists:member(make_ref(), InList)
+ ).
+
+%% merge/1
+prop_merge_1() ->
+ ?FORALL(
+ InLists,
+ list(?LET(L, gen_list(), lists:sort(L))),
+ check_merged(fun erlang:'=<'/2, InLists, lists:merge(InLists))
+ ).
+
+prop_merge_1_invalid() ->
+ ?FORALL(
+ InLists,
+ ?LET(
+ {L1, X, L2},
+ {list(oneof([non_list(), gen_list()])), non_list(), list(oneof([non_list(), gen_list()]))},
+ L1 ++ [X|L2]
+ ),
+ expect_error(fun lists:merge/1, [InLists])
+ ).
+
+%% merge/2
+prop_merge_2() ->
+ ?FORALL(
+ {InList1, InList2},
+ ?LET(
+ {L1, L2},
+ {gen_list(), gen_list()},
+ {lists:sort(L1), lists:sort(L2)}
+ ),
+ check_merged(fun erlang:'=<'/2, [InList1, InList2], lists:merge(InList1, InList2))
+ ).
+
+prop_merge_2_invalid() ->
+ ?FORALL(
+ {InList, X, Y},
+ {gen_list(), non_list(), non_list()},
+ expect_error(fun lists:merge/2, [InList, X]) andalso
+ expect_error(fun lists:merge/2, [X, InList]) andalso
+ expect_error(fun lists:merge/2, [X, Y])
+ ).
+
+%% merge/3
+prop_merge_3() ->
+ ?FORALL(
+ {SortFn, InList1, InList2},
+ ?LET(
+ {Fn, L1, L2},
+ {gen_ordering_fun(), gen_list(), gen_list()},
+ {Fn, lists:sort(Fn, L1), lists:sort(Fn, L2)}
+ ),
+ check_merged(SortFn, [InList1, InList2], lists:merge(SortFn, InList1, InList2))
+ ).
+
+prop_merge_3_invalid() ->
+ ?FORALL(
+ {SortFn, InList, X, Y},
+ {gen_ordering_fun(), gen_list(), non_list(), non_list()},
+ expect_error(fun lists:merge/3, [SortFn, InList, Y]) andalso
+ expect_error(fun lists:merge/3, [SortFn, X, InList]) andalso
+ expect_error(fun lists:merge/3, [SortFn, X, Y])
+ ).
+
+%% merge3/3
+prop_merge3() ->
+ ?FORALL(
+ {InList1, InList2, InList3},
+ ?LET(
+ {L1, L2, L3},
+ {gen_list(), gen_list(), gen_list()},
+ {lists:sort(L1), lists:sort(L2), lists:sort(L3)}
+ ),
+ check_merged(fun erlang:'=<'/2, [InList1, InList2, InList3], lists:merge3(InList1, InList2, InList3))
+ ).
+
+prop_merge3_invalid() ->
+ ?FORALL(
+ {InList, X, Y, Z},
+ {gen_list(), non_list(), non_list(), non_list()},
+ expect_error(fun lists:merge/3, [InList, InList, Z]) andalso
+ expect_error(fun lists:merge/3, [InList, Y, InList]) andalso
+ expect_error(fun lists:merge/3, [InList, Y, Z]) andalso
+ expect_error(fun lists:merge/3, [X, InList, Z]) andalso
+ expect_error(fun lists:merge/3, [X, Y, InList]) andalso
+ expect_error(fun lists:merge/3, [X, Y, Z])
+ ).
+
+%% min/1
+prop_min() ->
+ ?FORALL(
+ {InList, ExpMin},
+ gen_list_fold(gen_any(), fun erlang:min/2),
+ try
+ lists:min(InList) == ExpMin
+ catch
+ error:_ ->
+ InList =:= []
+ end
+ ).
+
+%% nth/2
+prop_nth() ->
+ ?FORALL(
+ {InList, N, ExpElem},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), gen_any()},
+ {F ++ [E|R], length(F)+1, E}
+ ),
+ lists:nth(N, InList) =:= ExpElem
+ ).
+
+prop_nth_outofrange() ->
+ ?FORALL(
+ {N, InList},
+ ?LET(
+ {L, Offset},
+ {gen_list(), pos_integer()},
+ {length(L) + Offset, L}
+ ),
+ try
+ lists:nth(N, InList)
+ of
+ _ ->
+ false
+ catch
+ error:_ ->
+ true
+ end
+ ).
+
+%% nthtail/2
+prop_nthtail() ->
+ ?FORALL(
+ {InList, N, ExpTail},
+ ?LET(
+ {F, R},
+ {gen_list(), gen_list()},
+ {F ++ R, length(F), R}
+ ),
+ lists:nthtail(N, InList) =:= ExpTail
+ ).
+
+prop_nthtail_outofrange() ->
+ ?FORALL(
+ {N, InList},
+ ?LET(
+ {L, Offset},
+ {gen_list(), pos_integer()},
+ {length(L) + Offset, L}
+ ),
+ try
+ lists:nthtail(N, InList)
+ of
+ _ ->
+ false
+ catch
+ error:_ ->
+ true
+ end
+ ).
+
+%% partition/2
+prop_partition() ->
+ ?FORALL(
+ {Pred, InList},
+ {function1(boolean()), gen_list()},
+ begin
+ {Group1, Group2} = lists:partition(Pred, InList),
+ check_partitioned(Pred, InList, Group1, Group2)
+ end
+ ).
+
+%% prefix/2
+prop_prefix() ->
+ ?FORALL(
+ {InList, Prefix},
+ ?LET(
+ {F, R},
+ {gen_list(), gen_list()},
+ {F ++ R, F}
+ ),
+ lists:prefix(Prefix, InList) andalso
+ not lists:prefix([make_ref()|Prefix], InList) andalso
+ not lists:prefix(Prefix ++ [make_ref()], InList) andalso
+ (not lists:prefix(Prefix, [make_ref()|InList]) orelse Prefix =:= [])
+ ).
+
+%% reverse/1
+prop_reverse_1() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ check_reversed(InList, lists:reverse(InList)) andalso
+ lists:reverse(lists:reverse(InList)) =:= InList
+ ).
+
+%% reverse/2
+prop_reverse_2() ->
+ ?FORALL(
+ {InList, InTail},
+ {gen_list(), gen_list()},
+ check_reversed(InList, lists:reverse(InList, InTail), InTail)
+ ).
+
+%% search/2
+prop_search() ->
+ ?FORALL(
+ {Pred, InList, ExpElem},
+ ?LET(
+ {F, R, E},
+ {gen_list(), gen_list(), make_ref()},
+ {fun(T) -> T =:= E end, F ++ [E|R], E}
+ ),
+ lists:search(Pred, InList) =:= {value, ExpElem}
+ ).
+
+prop_search_absent() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ lists:search(fun(_) -> false end, InList) =:= false
+ ).
+
+%% seq/2
+prop_seq2() ->
+ ?FORALL(
+ {From, To},
+ {integer(), integer()},
+ try
+ lists:seq(From, To)
+ of
+ Seq ->
+ To >= From - 1 andalso
+ check_seq(Seq, From, To, 1)
+ catch
+ error:_ ->
+ To < From - 1
+ end
+ ).
+
+%% seq/3
+prop_seq3() ->
+ ?FORALL(
+ {From, To, Step},
+ {integer(), integer(), integer()},
+ try
+ lists:seq(From, To, Step)
+ of
+ Seq when Step > 0 ->
+ To >= From - Step andalso
+ check_seq(Seq, From, To, Step);
+ Seq when Step < 0 ->
+ To =< From - Step andalso
+ check_seq(Seq, From, To, Step);
+ Seq when Step =:= 0 ->
+ From =:= To andalso
+ check_seq(Seq, From, To, Step)
+ catch
+ error:_ when Step > 0 ->
+ To < From - Step;
+ error:_ when Step < 0 ->
+ To > From - Step;
+ error:_ when Step =:= 0 ->
+ From =/= To
+ end
+ ).
+
+%% sort/1
+prop_sort_1() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ begin
+ Sorted = lists:sort(InList),
+ length(Sorted) =:= length(InList) andalso
+ check_sorted(InList, Sorted)
+ end
+ ).
+
+%% sort/2
+prop_sort_2() ->
+ ?FORALL(
+ {SortFn, InList},
+ {gen_ordering_fun(), gen_list()},
+ begin
+ Sorted = lists:sort(SortFn, InList),
+ length(Sorted) =:= length(InList) andalso
+ check_sorted(SortFn, InList, Sorted)
+ end
+ ).
+
+%% split/2
+prop_split() ->
+ ?FORALL(
+ {N, InList, ExpList1, ExpList2},
+ ?LET(
+ {F, R},
+ {gen_list(), gen_list()},
+ {length(F), F ++ R, F, R}
+ ),
+ lists:split(N, InList) =:= {ExpList1, ExpList2}
+ ).
+
+prop_split_outofrange() ->
+ ?FORALL(
+ {N, InList},
+ ?LET(
+ {L, Offset},
+ {gen_list(), pos_integer()},
+ {length(L) + Offset, L}
+ ),
+ try
+ lists:split(N, InList)
+ of
+ _ ->
+ false
+ catch
+ error:_ ->
+ true
+ end
+ ).
+
+%% splitwith/2
+prop_splitwith() ->
+ ?FORALL(
+ {Pred, InList},
+ {function1(boolean()), gen_list()},
+ begin
+ {Part1, Part2} = lists:splitwith(Pred, InList),
+ check_splitwithed(Pred, InList, Part1, Part2)
+ end
+ ).
+
+%% sublist/2
+prop_sublist_2() ->
+ ?FORALL(
+ {Len, InList, ExpList},
+ ?LET(
+ {F, R},
+ {gen_list(), gen_list()},
+ {length(F), F ++ R, F}
+ ),
+ lists:sublist(InList, Len) =:= ExpList
+ ).
+
+%% sublist/3
+prop_sublist_3() ->
+ ?FORALL(
+ {Start, Len, InList, ExpList},
+ ?LET(
+ {F, M, R},
+ {gen_list(), gen_list(), gen_list()},
+ {length(F)+1, length(M), F ++ M ++ R, M}
+ ),
+ lists:sublist(InList, Start, Len) =:= ExpList
+ ).
+
+%% subtract/2
+prop_subtract() ->
+ ?FORALL(
+ {InList, SubtractList},
+ ?LET(
+ {L, B, S},
+ {gen_list(), gen_list(), gen_list()},
+ {L ++ B, S ++ B}
+ ),
+ lists:subtract(InList, SubtractList) =:= InList -- SubtractList
+ ).
+
+%% suffix/2
+prop_suffix() ->
+ ?FORALL(
+ {InList, Suffix},
+ ?LET(
+ {F, R},
+ {gen_list(), gen_list()},
+ {F ++ R, R}
+ ),
+ lists:suffix(Suffix, InList) andalso
+ not lists:suffix([make_ref()|Suffix], InList) andalso
+ not lists:suffix(Suffix ++ [make_ref()], InList) andalso
+ (not lists:suffix(Suffix, InList ++ [make_ref()]) orelse Suffix =:= [])
+ ).
+
+%% sum/1
+prop_sum() ->
+ ?FORALL(
+ {InList, ExpSum},
+ gen_list_fold(number(), fun erlang:'+'/2, 0),
+ lists:sum(InList) =:= ExpSum
+ ).
+
+%% takewhile/2
+prop_takewhile() ->
+ ?FORALL(
+ {Pred, InList, ExpList},
+ ?LET(
+ Fn,
+ function1(boolean()),
+ ?LET(
+ {L, {_, TL}},
+ gen_list_fold(
+ gen_any(),
+ fun(E, {Take, Acc}) ->
+ case Take andalso Fn(E) of
+ true -> {true, Acc ++ [E]};
+ false -> {false, Acc}
+ end
+ end,
+ {true, []}
+ ),
+ {Fn, L, TL}
+ )
+ ),
+ lists:takewhile(Pred, InList) =:= ExpList
+ ).
+
+%% ukeymerge/3
+prop_ukeymerge() ->
+ ?FORALL(
+ {N, InList1, InList2},
+ ?LET(
+ N,
+ range(1, 5),
+ ?LET(
+ {L1, L2},
+ {list(gen_tuple(N, N+3)), list(gen_tuple(N, N+3))},
+ {N, lists:ukeysort(N, L1), lists:ukeysort(N, L2)}
+ )
+ ),
+ check_umerged(
+ fun(E1, E2) -> element(N, E1) =< element(N, E2) end,
+ [InList1, InList2],
+ lists:ukeymerge(N, InList1, InList2)
+ )
+ ).
+
+prop_ukeymerge_invalid() ->
+ ?FORALL(
+ {N, InList, X, Y},
+ ?LET(
+ N,
+ range(1, 5),
+ ?LET(
+ {L, X, Y},
+ {list(gen_tuple(N, N+3)), non_list(), non_list()},
+ {N, L, X, Y}
+ )
+ ),
+ expect_error(fun lists:ukeymerge/3, [N, InList, Y]) andalso
+ expect_error(fun lists:ukeymerge/3, [N, X, InList]) andalso
+ expect_error(fun lists:ukeymerge/3, [N, X, Y])
+ ).
+
+%% ukeysort/2
+prop_ukeysort() ->
+ ?FORALL(
+ {N, InList},
+ ?LET(
+ N,
+ range(1, 5),
+ {N, list(gen_tuple(N, N + 3))}
+ ),
+ begin
+ Sorted = lists:ukeysort(N, InList),
+ length(Sorted) =< length(InList) andalso
+ check_usorted(fun(E1, E2) -> element(N, E1) =< element(N, E2) end, InList, Sorted)
+ end
+ ).
+
+%% umerge/1
+prop_umerge_1() ->
+ ?FORALL(
+ InLists,
+ list(?LET(L, gen_list(), lists:usort(L))),
+ check_umerged(InLists, lists:umerge(InLists))
+ ).
+
+prop_umerge_1_invalid() ->
+ ?FORALL(
+ InList,
+ ?LET(
+ {L1, X, L2},
+ {list(oneof([non_list(), gen_list()])), non_list(), list(oneof([non_list(), gen_list()]))},
+ L1 ++ [X|L2]
+ ),
+ expect_error(fun lists:umerge/1, [InList])
+ ).
+
+%% umerge/2
+prop_umerge_2() ->
+ ?FORALL(
+ {InList1, InList2},
+ ?LET(
+ {L1, L2},
+ {gen_list(), gen_list()},
+ {lists:usort(L1), lists:usort(L2)}
+ ),
+ check_umerged([InList1, InList2], lists:umerge(InList1, InList2))
+ ).
+
+prop_umerge_2_invalid() ->
+ ?FORALL(
+ {InList, X, Y},
+ {gen_list(), non_list(), non_list()},
+ expect_error(fun lists:umerge/2, [InList, Y]) andalso
+ expect_error(fun lists:umerge/2, [X, InList]) andalso
+ expect_error(fun lists:umerge/2, [X, Y])
+ ).
+
+%% umerge/3
+prop_umerge_3() ->
+ ?FORALL(
+ {SortFn, InList1, InList2},
+ ?LET(
+ {Fn, L1, L2},
+ {gen_ordering_fun(), gen_list(), gen_list()},
+ {Fn, lists:usort(Fn, L1), lists:usort(Fn, L2)}
+ ),
+ check_umerged(SortFn, [InList1, InList2], lists:umerge(SortFn, InList1, InList2))
+ ).
+
+prop_umerge_3_invalid() ->
+ ?FORALL(
+ {SortFn, InList, X, Y},
+ {gen_ordering_fun(), gen_list(), non_list(), non_list()},
+ expect_error(fun lists:umerge/3, [SortFn, InList, Y]) andalso
+ expect_error(fun lists:umerge/3, [SortFn, X, InList]) andalso
+ expect_error(fun lists:umerge/3, [SortFn, X, Y])
+ ).
+
+%% umerge3/3
+prop_umerge3() ->
+ ?FORALL(
+ {InList1, InList2, InList3},
+ ?LET(
+ {L1, L2, L3},
+ {gen_list(), gen_list(), gen_list()},
+ {lists:usort(L1), lists:usort(L2), lists:usort(L3)}
+ ),
+ check_umerged([InList1, InList2, InList3], lists:umerge3(InList1, InList2, InList3))
+ ).
+
+prop_umerge3_invalid() ->
+ ?FORALL(
+ {InList, X, Y, Z},
+ {gen_list(), non_list(), non_list(), non_list()},
+ expect_error(fun lists:umerge3/3, [InList, InList, Z]) andalso
+ expect_error(fun lists:umerge3/3, [InList, Y, InList]) andalso
+ expect_error(fun lists:umerge3/3, [InList, Y, Z]) andalso
+ expect_error(fun lists:umerge3/3, [X, InList, InList]) andalso
+ expect_error(fun lists:umerge3/3, [X, InList, Z]) andalso
+ expect_error(fun lists:umerge3/3, [X, Y, InList]) andalso
+ expect_error(fun lists:umerge3/3, [X, Y, Z])
+ ).
+
+%% uniq/1
+prop_uniq_1() ->
+ ?FORALL(
+ InList,
+ ?LET(
+ {L, M},
+ {gen_list(), gen_list()},
+ ?LET(
+ S,
+ vector(length(L) + 2 * length(M), integer()),
+ [E || {_, E} <- lists:sort(lists:zip(S, L ++ M ++ M))]
+ )
+ ),
+ check_uniqed(InList, lists:uniq(InList))
+ ).
+
+%% uniq/2
+prop_uniq_2() ->
+ ?FORALL(
+ {UniqFn, InList},
+ {function1(oneof([a, b, c])), gen_list()},
+ check_uniqed(UniqFn, InList, lists:uniq(UniqFn, InList))
+ ).
+
+%% unzip/1
+prop_unzip() ->
+ ?FORALL(
+ {InList, {ExpList1, ExpList2}},
+ gen_list_fold(
+ {gen_any(), gen_any()},
+ fun({T1, T2}, {L1, L2}) ->
+ {L1 ++ [T1], L2 ++ [T2]}
+ end,
+ {[], []}
+ ),
+ lists:unzip(InList) =:= {ExpList1, ExpList2}
+ ).
+
+%% unzip3/1
+prop_unzip3() ->
+ ?FORALL(
+ {InList, {ExpList1, ExpList2, ExpList3}},
+ gen_list_fold(
+ {gen_any(), gen_any(), gen_any()},
+ fun({T1, T2, T3}, {L1, L2, L3}) ->
+ {L1 ++ [T1], L2 ++ [T2], L3 ++ [T3]}
+ end,
+ {[], [], []}
+ ),
+ lists:unzip3(InList) =:= {ExpList1, ExpList2, ExpList3}
+ ).
+
+%% usort/1
+prop_usort_1() ->
+ ?FORALL(
+ InList,
+ gen_list(),
+ begin
+ Sorted = lists:usort(InList),
+ length(Sorted) =< length(InList) andalso
+ check_usorted(InList, Sorted)
+ end
+ ).
+
+%% usort/2
+prop_usort_2() ->
+ ?FORALL(
+ {SortFn, InList},
+ {gen_ordering_fun(), gen_list()},
+ begin
+ Sorted = lists:usort(SortFn, InList),
+ length(Sorted) =< length(InList) andalso
+ check_usorted(SortFn, InList, Sorted)
+ end
+ ).
+
+%% zip/2
+prop_zip_2() ->
+ ?FORALL(
+ {ExpList, {InList1, InList2}},
+ gen_list_fold(
+ {gen_any(), gen_any()},
+ fun({T1, T2}, {L1, L2}) ->
+ {L1 ++ [T1], L2 ++ [T2]}
+ end,
+ {[], []}
+ ),
+ lists:zip(InList1, InList2) =:= ExpList
+ ).
+
+%% zip/3
+prop_zip_3() ->
+ ?FORALL(
+ {{ExpList, {InList1, InList2}}, ExtraList},
+ {
+ gen_list_fold(
+ {gen_any(), gen_any()},
+ fun({T1, T2}, {L1, L2}) ->
+ {L1 ++ [T1], L2 ++ [T2]}
+ end,
+ {[], []}
+ ),
+ non_empty(gen_list())
+ },
+ begin
+ Tag = make_ref(),
+
+ Res1 = ExpList =:= lists:zip(InList1, InList2, fail) andalso
+ ExpList =:= lists:zip(InList1, InList2, trim) andalso
+ ExpList =:= lists:zip(InList1, InList2, {pad, {Tag, Tag}}),
+
+ Res2 = try lists:zip(InList1, InList2 ++ ExtraList, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zip(InList1 ++ ExtraList, InList2, fail) of _ -> false catch error:_ -> true end,
+
+ Res3 = ExpList =:= lists:zip(InList1, InList2 ++ ExtraList, trim) andalso
+ ExpList =:= lists:zip(InList1 ++ ExtraList, InList2, trim),
+
+ Padded1 = lists:zip(InList1, InList2 ++ ExtraList, {pad, {Tag, Tag}}),
+ Padded2 = lists:zip(InList1 ++ ExtraList, InList2, {pad, {Tag, Tag}}),
+ Res4 = Padded1 =:= ExpList ++ [{Tag, X} || X <- ExtraList] andalso
+ Padded2 =:= ExpList ++ [{X, Tag} || X <- ExtraList],
+
+ Res1 andalso Res2 andalso Res3 andalso Res4
+ end
+ ).
+
+%% zip3/3
+prop_zip3_3() ->
+ ?FORALL(
+ {ExpList, {InList1, InList2, InList3}},
+ gen_list_fold(
+ {gen_any(), gen_any(), gen_any()},
+ fun({T1, T2, T3}, {L1, L2, L3}) ->
+ {L1 ++ [T1], L2 ++ [T2], L3 ++ [T3]}
+ end,
+ {[], [], []}
+ ),
+ lists:zip3(InList1, InList2, InList3) =:= ExpList
+ ).
+
+%% zip3/4
+prop_zip3_4() ->
+ ?FORALL(
+ {{ExpList, {InList1, InList2, InList3}}, ExtraList},
+ {
+ gen_list_fold(
+ {gen_any(), gen_any(), gen_any()},
+ fun({T1, T2, T3}, {L1, L2, L3}) ->
+ {L1 ++ [T1], L2 ++ [T2], L3 ++ [T3]}
+ end,
+ {[], [], []}
+ ),
+ non_empty(gen_list())
+ },
+ begin
+ Tag = make_ref(),
+
+ Res1 = ExpList =:= lists:zip3(InList1, InList2, InList3, fail) andalso
+ ExpList =:= lists:zip3(InList1, InList2, InList3, trim) andalso
+ ExpList =:= lists:zip3(InList1, InList2, InList3, {pad, {Tag, Tag, Tag}}),
+
+ Res2 = try lists:zip3(InList1, InList2, InList3 ++ ExtraList, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zip3(InList1, InList2 ++ ExtraList, InList3, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zip3(InList1, InList2 ++ ExtraList, InList3 ++ ExtraList, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zip3(InList1 ++ ExtraList, InList2, InList3, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zip3(InList1 ++ ExtraList, InList2, InList3 ++ ExtraList, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zip3(InList1 ++ ExtraList, InList2 ++ ExtraList, InList3, fail) of _ -> false catch error:_ -> true end,
+
+ Res3 = ExpList =:= lists:zip3(InList1, InList2, InList3 ++ ExtraList, trim) andalso
+ ExpList =:= lists:zip3(InList1, InList2 ++ ExtraList, InList3, trim) andalso
+ ExpList =:= lists:zip3(InList1, InList2 ++ ExtraList, InList3 ++ ExtraList, trim) andalso
+ ExpList =:= lists:zip3(InList1 ++ ExtraList, InList2, InList3, trim) andalso
+ ExpList =:= lists:zip3(InList1 ++ ExtraList, InList2, InList3 ++ ExtraList, trim) andalso
+ ExpList =:= lists:zip3(InList1 ++ ExtraList, InList2 ++ ExtraList, InList3, trim),
+
+ Padded1 = lists:zip3(InList1, InList2, InList3 ++ ExtraList, {pad, {Tag, Tag, Tag}}),
+ Padded2 = lists:zip3(InList1, InList2 ++ ExtraList, InList3, {pad, {Tag, Tag, Tag}}),
+ Padded3 = lists:zip3(InList1, InList2 ++ ExtraList, InList3 ++ ExtraList, {pad, {Tag, Tag, Tag}}),
+ Padded4 = lists:zip3(InList1 ++ ExtraList, InList2, InList3, {pad, {Tag, Tag, Tag}}),
+ Padded5 = lists:zip3(InList1 ++ ExtraList, InList2, InList3 ++ ExtraList, {pad, {Tag, Tag, Tag}}),
+ Padded6 = lists:zip3(InList1 ++ ExtraList, InList2 ++ ExtraList, InList3, {pad, {Tag, Tag, Tag}}),
+ Res4 = Padded1 =:= ExpList ++ [{Tag, Tag, X} || X <- ExtraList] andalso
+ Padded2 =:= ExpList ++ [{Tag, X, Tag} || X <- ExtraList] andalso
+ Padded3 =:= ExpList ++ [{Tag, X, X} || X <- ExtraList] andalso
+ Padded4 =:= ExpList ++ [{X, Tag, Tag} || X <- ExtraList] andalso
+ Padded5 =:= ExpList ++ [{X, Tag, X} || X <- ExtraList] andalso
+ Padded6 =:= ExpList ++ [{X, X, Tag} || X <- ExtraList],
+
+ Res1 andalso Res2 andalso Res3 andalso Res4
+ end
+ ).
+
+%% zipwith/3
+prop_zipwith_3() ->
+ ?FORALL(
+ {ZipFn, InList1, InList2, ExpList},
+ ?LET(
+ Fn,
+ function2(gen_any()),
+ ?LET(
+ {_, {L1, L2, Z}},
+ gen_list_fold(
+ {gen_any(), gen_any()},
+ fun({T1, T2}, {L1, L2, Z}) ->
+ {L1 ++ [T1], L2 ++ [T2], Z ++ [Fn(T1, T2)]}
+ end,
+ {[], [], []}
+ ),
+ {Fn, L1, L2, Z}
+ )
+ ),
+ lists:zipwith(ZipFn, InList1, InList2) =:= ExpList
+ ).
+
+%% zipwith/4
+prop_zipwith_4() ->
+ ?FORALL(
+ {ZipFn, InList1, InList2, ExpList, ExtraList},
+ ?LET(
+ {Extra, Fn},
+ {non_empty(gen_list()), function2(gen_any())},
+ ?LET(
+ {_, {L1, L2, Z}},
+ gen_list_fold(
+ {gen_any(), gen_any()},
+ fun({T1, T2}, {L1, L2, Z}) ->
+ {L1 ++ [T1], L2 ++ [T2], Z ++ [Fn(T1, T2)]}
+ end,
+ {[], [], []}
+ ),
+ {Fn, L1, L2, Z, Extra}
+ )
+ ),
+ begin
+ Tag = make_ref(),
+
+ Res1 = ExpList =:= lists:zipwith(ZipFn, InList1, InList2, fail) andalso
+ ExpList =:= lists:zipwith(ZipFn, InList1, InList2, trim) andalso
+ ExpList =:= lists:zipwith(ZipFn, InList1, InList2, {pad, {Tag, Tag}}),
+
+ Res2 = try lists:zipwith(ZipFn, InList1, InList2 ++ ExtraList, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zipwith(ZipFn, InList1 ++ ExtraList, InList2, fail) of _ -> false catch error:_ -> true end,
+
+ Res3 = ExpList =:= lists:zipwith(ZipFn, InList1, InList2 ++ ExtraList, trim) andalso
+ ExpList =:= lists:zipwith(ZipFn, InList1 ++ ExtraList, InList2, trim),
+
+ Padded1 = lists:zipwith(ZipFn, InList1, InList2 ++ ExtraList, {pad, {Tag, Tag}}),
+ Padded2 = lists:zipwith(ZipFn, InList1 ++ ExtraList, InList2, {pad, {Tag, Tag}}),
+ Res4 = Padded1 =:= ExpList ++ [ZipFn(Tag, X) || X <- ExtraList] andalso
+ Padded2 =:= ExpList ++ [ZipFn(X, Tag) || X <- ExtraList],
+
+ Res1 andalso Res2 andalso Res3 andalso Res4
+ end
+ ).
+
+%% zipwith3/4
+prop_zipwith3_4() ->
+ ?FORALL(
+ {ZipFn, InList1, InList2, InList3, ExpList},
+ ?LET(
+ Fn,
+ function3(gen_any()),
+ ?LET(
+ {_, {L1, L2, L3, Z}},
+ gen_list_fold(
+ {gen_any(), gen_any(), gen_any()},
+ fun({T1, T2, T3}, {L1, L2, L3, Z}) ->
+ {L1 ++ [T1], L2 ++ [T2], L3 ++ [T3], Z ++ [Fn(T1, T2, T3)]}
+ end,
+ {[], [], [], []}
+ ),
+ {Fn, L1, L2, L3, Z}
+ )
+ ),
+ lists:zipwith3(ZipFn, InList1, InList2, InList3) =:= ExpList
+ ).
+
+%% zipwith3/5
+prop_zipwith3_5() ->
+ ?FORALL(
+ {ZipFn, InList1, InList2, InList3, ExpList, ExtraList},
+ ?LET(
+ {Extra, Fn},
+ {non_empty(gen_list()), function3(gen_any())},
+ ?LET(
+ {_, {L1, L2, L3, Z}},
+ gen_list_fold(
+ {gen_any(), gen_any(), gen_any()},
+ fun({T1, T2, T3}, {L1, L2, L3, Z}) ->
+ {L1 ++ [T1], L2 ++ [T2], L3 ++ [T3], Z ++ [Fn(T1, T2, T3)]}
+ end,
+ {[], [], [], []}
+ ),
+ {Fn, L1, L2, L3, Z, Extra}
+ )
+ ),
+ begin
+ Tag = make_ref(),
+
+ Res1 = ExpList =:= lists:zipwith3(ZipFn, InList1, InList2, InList3, fail) andalso
+ ExpList =:= lists:zipwith3(ZipFn, InList1, InList2, InList3, trim) andalso
+ ExpList =:= lists:zipwith3(ZipFn, InList1, InList2, InList3, {pad, {Tag, Tag, Tag}}),
+
+ Res2 = try lists:zipwith3(ZipFn, InList1, InList2, InList3 ++ ExtraList, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zipwith3(ZipFn, InList1, InList2 ++ ExtraList, InList3, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zipwith3(ZipFn, InList1, InList2 ++ ExtraList, InList3 ++ ExtraList, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zipwith3(ZipFn, InList1 ++ ExtraList, InList2, InList3, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zipwith3(ZipFn, InList1 ++ ExtraList, InList2, InList3 ++ ExtraList, fail) of _ -> false catch error:_ -> true end andalso
+ try lists:zipwith3(ZipFn, InList1 ++ ExtraList, InList2 ++ ExtraList, InList3, fail) of _ -> false catch error:_ -> true end,
+
+ Res3 = ExpList =:= lists:zipwith3(ZipFn, InList1, InList2, InList3 ++ ExtraList, trim) andalso
+ ExpList =:= lists:zipwith3(ZipFn, InList1, InList2 ++ ExtraList, InList3, trim) andalso
+ ExpList =:= lists:zipwith3(ZipFn, InList1, InList2 ++ ExtraList, InList3 ++ ExtraList, trim) andalso
+ ExpList =:= lists:zipwith3(ZipFn, InList1 ++ ExtraList, InList2, InList3, trim) andalso
+ ExpList =:= lists:zipwith3(ZipFn, InList1 ++ ExtraList, InList2, InList3 ++ ExtraList, trim) andalso
+ ExpList =:= lists:zipwith3(ZipFn, InList1 ++ ExtraList, InList2 ++ ExtraList, InList3, trim),
+
+ Padded1 = lists:zipwith3(ZipFn, InList1, InList2, InList3 ++ ExtraList, {pad, {Tag, Tag, Tag}}),
+ Padded2 = lists:zipwith3(ZipFn, InList1, InList2 ++ ExtraList, InList3, {pad, {Tag, Tag, Tag}}),
+ Padded3 = lists:zipwith3(ZipFn, InList1, InList2 ++ ExtraList, InList3 ++ ExtraList, {pad, {Tag, Tag, Tag}}),
+ Padded4 = lists:zipwith3(ZipFn, InList1 ++ ExtraList, InList2, InList3, {pad, {Tag, Tag, Tag}}),
+ Padded5 = lists:zipwith3(ZipFn, InList1 ++ ExtraList, InList2, InList3 ++ ExtraList, {pad, {Tag, Tag, Tag}}),
+ Padded6 = lists:zipwith3(ZipFn, InList1 ++ ExtraList, InList2 ++ ExtraList, InList3, {pad, {Tag, Tag, Tag}}),
+ Res4 = Padded1 =:= ExpList ++ [ZipFn(Tag, Tag, X) || X <- ExtraList] andalso
+ Padded2 =:= ExpList ++ [ZipFn(Tag, X, Tag) || X <- ExtraList] andalso
+ Padded3 =:= ExpList ++ [ZipFn(Tag, X, X) || X <- ExtraList] andalso
+ Padded4 =:= ExpList ++ [ZipFn(X, Tag, Tag) || X <- ExtraList] andalso
+ Padded5 =:= ExpList ++ [ZipFn(X, Tag, X) || X <- ExtraList] andalso
+ Padded6 =:= ExpList ++ [ZipFn(X, X, Tag) || X <- ExtraList],
+
+ Res1 andalso Res2 andalso Res3 andalso Res4
+ end
+ ).
+
+%%%%%%%%%%%%%%%%%%
+%%% Generators %%%
+%%%%%%%%%%%%%%%%%%
+
+non_list() ->
+ ?SUCHTHAT(NonList, gen_any(), not is_list(NonList)).
+
+%% Generator for lists of the given type, folding the given function
+%% over values on the top level as they are generated. The first generated
+%% value serves as the initial accumulator.
+gen_list_fold(Gen, FoldFn) ->
+ ?SIZED(
+ Size,
+ ?LET(
+ T,
+ Gen,
+ if
+ Size =< 1 ->
+ {[], T};
+ true ->
+ gen_list_fold(max(0, Size - 1), Gen, [T], FoldFn, T)
+ end
+ )
+ ).
+
+%% Generator for lists of the given type, folding the given function
+%% over values on the top level as they are generated.
+gen_list_fold(Gen, FoldFn, Acc0) ->
+ ?SIZED(
+ Size,
+ gen_list_fold(max(0, Size - 1), Gen, [], FoldFn, Acc0)
+ ).
+
+gen_list_fold(0, _Gen, L, _FoldFn, Acc) ->
+ {L, Acc};
+gen_list_fold(N, Gen, L, FoldFn, Acc) ->
+ ?LET(
+ E,
+ Gen,
+ gen_list_fold(N - 1, Gen, L ++ [E], FoldFn, FoldFn(E, Acc))
+ ).
+
+%% Generator for key tuples of the given size,
+%% with the given key in the given (ie, last) position.
+gen_keytuple(Key, Size) ->
+ gen_keytuple(Key, Size, Size).
+
+%% Generator for key tuples of the given minimum and maximum
+%% sizes, with the given key in the given minimum position.
+gen_keytuple(Key, MinSize, MaxSize) ->
+ ?LET(
+ Tuple,
+ gen_tuple(MinSize, MaxSize),
+ setelement(MinSize, Tuple, Key)
+ ).
+
+%% Generator for tuples of random size.
+gen_tuple() ->
+ ?LET(
+ N,
+ non_neg_integer(),
+ gen_tuple(N)
+ ).
+
+%% Generator for tuples of the given size.
+gen_tuple(Size) ->
+ ?LET(
+ V,
+ vector(Size, gen_any()),
+ list_to_tuple(V)
+ ).
+
+%% Generator for tuples of the given minimum and
+%% maximum sizes.
+gen_tuple(MinSize, MaxSize) ->
+ ?LET(
+ N,
+ range(MinSize, MaxSize),
+ ?LET(
+ V,
+ vector(N, gen_any()),
+ list_to_tuple(V)
+ )
+ ).
+
+%% Generator for lists of anything.
+gen_list() ->
+ list(gen_any()).
+
+%% Generator for lists of anything, folding the given function
+%% over values on all levels of list-nesting as they are generated.
+gen_list_deepfold(FoldFn, Acc0) ->
+ ?SIZED(
+ Size,
+ ?LET(
+ {_, L, Acc},
+ gen_list_deepfold(max(0, Size - 1), 0, [], FoldFn, Acc0),
+ {L, Acc}
+ )
+ ).
+
+gen_list_deepfold(N, _Level, L, _FoldFn, Acc) when N =< 0 ->
+ {N, lists:reverse(L), Acc};
+gen_list_deepfold(N, Level, L, FoldFn, Acc) ->
+ ?LET(
+ X,
+ frequency([
+ {4, {term, gen_any_simple()}},
+ {1, deeplist},
+ {1, tuple},
+ {2, stop}
+ ]),
+ case X of
+ deeplist ->
+ ?LET(
+ {N1, L1, Acc1},
+ gen_list_deepfold(N, Level + 1, [], FoldFn, Acc),
+ gen_list_deepfold(N1, Level, [L1|L], FoldFn, Acc1)
+ );
+ tuple ->
+ ?LET(
+ {N1, L1, _},
+ gen_list_deepfold(N, Level + 1, [], fun(_, _, _) -> undefined end, undefined),
+ begin
+ E = list_to_tuple(L1),
+ gen_list_deepfold(N1, Level, [E|L], FoldFn, FoldFn(Level, E, Acc))
+ end
+ );
+ stop ->
+ {N, lists:reverse(L), Acc};
+ {term, E} ->
+ gen_list_deepfold(N - 1, Level, [E|L], FoldFn, FoldFn(Level, E, Acc))
+ end
+ ).
+
+%% Generator for simple and composite (lists and tuples) types.
+gen_any() ->
+ frequency(
+ [
+ {4, gen_any_simple()},
+ {1, ?LET({L, _}, gen_list_deepfold(fun(_, _, Acc) -> Acc end, undefined), L)},
+ {1, ?LET({L, _}, gen_list_deepfold(fun(_, _, Acc) -> Acc end, undefined), list_to_tuple(L))}
+ ]
+ ).
+
+%% Generator for simple types:
+%% - atoms
+%% - integers
+%% - floats
+%% - bitstrings
+gen_any_simple() ->
+ oneof([gen_atom(), integer(), float(), bitstring()]).
+
+%% Generator for interesting atoms:
+%% - well-known atoms like `ok', `undefined', `infinity'...
+%% - randomly generated "weird" atoms
+gen_atom() ->
+ oneof(
+ [
+ oneof([ok, error, true, false, undefined, infinity]),
+ oneof(['', '"', '\'', '(', ')', '()', '[', '[', '[]', '{', '}', '{}']),
+ gen_random_atom()
+ ]
+ ).
+
+%% Generator for a limited set of random atoms. The number of
+%% atoms that will be generated is set in `?RANDOM_ATOMS'.
+gen_random_atom() ->
+ ?LAZY(
+ ?LET(
+ N,
+ range(1, ?RANDOM_ATOMS),
+ try
+ persistent_term:get({?MODULE, random_atoms})
+ of
+ Atoms ->
+ maps:get(N, Atoms)
+ catch
+ error:badarg ->
+ ?LET(
+ AtomsList,
+ vector(?RANDOM_ATOMS, ?SIZED(Size, resize(Size * 100, atom()))),
+ begin
+ Fn = fun
+ F(_, [], Acc) ->
+ Acc;
+ F(Index, [A|As], Acc) ->
+ F(Index + 1, As, Acc#{Index => A})
+ end,
+ Atoms = Fn(1, AtomsList, #{}),
+ persistent_term:put({?MODULE, random_atoms}, Atoms),
+ maps:get(N, Atoms)
+ end
+ )
+ end
+ )
+ ).
+
+%% Generator for ordering functions, to be used for sorting and merging.
+%% The generated ordering functions are designed to fulfill the requirements given
+%% at the top of the `lists' documentation, namely to be antisymmetric, transitive,
+%% and total. Further, the chances that two terms compare equal, less or greater
+%% are equal.
+gen_ordering_fun() ->
+ ?LET(
+ F,
+ function1(range(1, 3)),
+ fun(T1, T2) ->
+ F(T1) =< F(T2)
+ end
+ ).
+
+%%%%%%%%%%%%%%%
+%%% Helpers %%%
+%%%%%%%%%%%%%%%
+
+%% --------------------------------------------------------------------
+expect_error(Fn, Args) when is_function(Fn, length(Args))->
+ try
+ erlang:apply(Fn, Args)
+ of
+ _ -> false
+ catch
+ error:_ -> true;
+ _:_ -> false
+ end.
+
+%% --------------------------------------------------------------------
+check_appended([], []) ->
+ true;
+check_appended([[]|Ls], AL) ->
+ check_appended(Ls, AL);
+check_appended([L], AL) ->
+ L =:= AL;
+check_appended([[E1|L]|Ls], [E2|AL]) ->
+ E1 =:= E2 andalso
+ check_appended([L|Ls], AL);
+check_appended(_Ls, _AL) ->
+ false.
+
+%% --------------------------------------------------------------------
+check_deleted(E, [E|L], DL) ->
+ L =:= DL;
+check_deleted(E, [_|L], [_|DL]) ->
+ check_deleted(E, L, DL);
+check_deleted(_E, [], []) ->
+ true;
+check_deleted(_E, _L, _DL) ->
+ false.
+
+%% --------------------------------------------------------------------
+check_joined(Sep, [E|L], [E, Sep|JL]) ->
+ check_joined(Sep, L, JL);
+check_joined(_Sep, [E], [E]) ->
+ true;
+check_joined(_Sep, [], []) ->
+ true;
+check_joined(_Sep, _L, _JL) ->
+ false.
+
+%% --------------------------------------------------------------------
+check_keydeleted(K, N, [E|L], KDL) when element(N, E) == K ->
+ L =:= KDL;
+check_keydeleted(K, N, [_|L], [_|KDL]) ->
+ check_keydeleted(K, N, L, KDL);
+check_keydeleted(_K, _N, _L, _KDL) ->
+ false.
+
+%% --------------------------------------------------------------------
+check_keyreplaced(K, N, R, [E1|L], [E2|KRL]) when element(N, E1) == K ->
+ E2 =:= R andalso L =:= KRL;
+check_keyreplaced(K, N, R, [_|L], [_|KRL]) ->
+ check_keyreplaced(K, N, R, L, KRL);
+check_keyreplaced(_K, _N, _R, _L, _KRL) ->
+ false.
+
+%% --------------------------------------------------------------------
+check_merged(Ls, ML) ->
+ check_merged(fun erlang:'=<'/2, Ls, ML).
+
+check_merged(Fn, [[]|Ls], ML) ->
+ check_merged(Fn, Ls, ML);
+check_merged(_Fn, [], ML) ->
+ ML =:= [];
+check_merged(_Fn, [L], ML) ->
+ ML =:= L;
+check_merged(Fn, Ls, [E|ML]) ->
+ case find_in_heads(Fn, E, Ls) of
+ {true, Ls1} ->
+ check_merged(Fn, Ls1, ML);
+ false ->
+ false
+ end;
+check_merged(_Fn, _Ls, _ML) ->
+ false.
+
+find_in_heads(Fn, E, Ls) ->
+ find_in_heads(Fn, E, Ls, []).
+
+find_in_heads(Fn, E, [[]|Ls], Seen) ->
+ find_in_heads(Fn, E, Ls, Seen);
+find_in_heads(Fn, E, [[E1|LRest]=L|Ls], Seen) ->
+ case Fn(E, E1) andalso Fn(E1, E) of
+ true ->
+ {true, lists:reverse(Seen, [LRest|Ls])};
+ false ->
+ find_in_heads(Fn, E, Ls, [L|Seen])
+ end;
+find_in_heads(_Fn, _E, _Ls, _Seen) ->
+ false.
+
+%% --------------------------------------------------------------------
+check_partitioned(Pred, [E|L], P1, P2) ->
+ case {Pred(E), P1, P2} of
+ {true, [E|Rest], _} ->
+ check_partitioned(Pred, L, Rest, P2);
+ {false, _, [E|Rest]} ->
+ check_partitioned(Pred, L, P1, Rest);
+ _ ->
+ false
+ end;
+check_partitioned(_Pred, [], [], []) ->
+ true;
+check_partitioned(_Pred, _L, _P1, _P2) ->
+ false.
+
+%% --------------------------------------------------------------------
+check_reversed(L1, L2) ->
+ check_reversed(L1, L2, []).
+
+check_reversed(L1, L2, Tail) ->
+ check_reversed1(L1, L2) =:= Tail.
+
+check_reversed1([], L2) ->
+ L2;
+check_reversed1([E|L1], L2) ->
+ case check_reversed1(L1, L2) of
+ [E|L2Rest] -> L2Rest;
+ _ -> false
+ end.
+
+%% --------------------------------------------------------------------
+check_seq([F|Seq], F, T, S) ->
+ check_seq(Seq, F + S, T, S);
+check_seq([], F, T, S) when S >= 0 ->
+ F >= T;
+check_seq([], F, T, S) when S < 0 ->
+ F =< T;
+check_seq(_Seq, _F, _T, _S) ->
+ false.
+
+%% --------------------------------------------------------------------
+check_sorted(L, Sorted) ->
+ check_sorted(fun erlang:'=<'/2, L, Sorted).
+
+check_sorted(SortFun, L, Sorted) ->
+ ExpElems = count_elems(L),
+ check_sorted(SortFun, Sorted, ExpElems, #{}).
+
+check_sorted(_SortFun, [], ExpElems, FoundElems) ->
+ ExpElems =:= FoundElems;
+check_sorted(SortFun, [E], ExpElems, FoundElems) ->
+ maps:is_key(E, ExpElems) andalso
+ check_sorted(SortFun, [], ExpElems, maps:update_with(E, fun(Cnt) -> Cnt + 1 end, 1, FoundElems));
+check_sorted(SortFun, [E1|[E2|_]=L], ExpElems, FoundElems) ->
+ SortFun(E1, E2) andalso
+ maps:is_key(E1, ExpElems) andalso
+ check_sorted(SortFun, L, ExpElems, maps:update_with(E1, fun(Cnt) -> Cnt + 1 end, 1, FoundElems));
+check_sorted(_SortFun, _L, _ExpElems, _FoundElems) ->
+ false.
+
+count_elems(L) ->
+ count_elems(L, #{}).
+
+count_elems([E|Es], Acc) ->
+ count_elems(Es, maps:update_with(E, fun(Cnt) -> Cnt + 1 end, 1, Acc));
+count_elems([], Acc) ->
+ Acc.
+
+%% --------------------------------------------------------------------
+check_splitwithed(Pred, [E|L], [E|P1], P2) ->
+ Pred(E) andalso
+ check_splitwithed(Pred, L, P1, P2);
+check_splitwithed(Pred, [E|_]=L, [], P2) ->
+ not Pred(E) andalso L =:= P2;
+check_splitwithed(_Pred, [], [], []) ->
+ true;
+check_splitwithed(_Pred, _L, _P1, _P2) ->
+ false.
+
+%% --------------------------------------------------------------------
+check_umerged(Ls, ML) ->
+ check_umerged(fun erlang:'=<'/2, Ls, ML).
+
+check_umerged(Fn, [[]|Ls], ML) ->
+ check_umerged(Fn, Ls, ML);
+check_umerged(_Fn, [L], ML) ->
+ ML =:= L;
+check_umerged(_Fn, [], ML) ->
+ ML =:= [];
+check_umerged(Fn, Ls, [E|ML]) ->
+ case find_and_remove_from_heads(Fn, E, Ls) of
+ {true, Ls1} ->
+ check_umerged(Fn, Ls1, ML);
+ false ->
+ false
+ end;
+check_umerged(_Fn, _Ls, _ML) ->
+ false.
+
+find_and_remove_from_heads(Fn, E, Ls) ->
+ find_and_remove_from_heads(false, Fn, E, Ls, []).
+
+find_and_remove_from_heads(Found, Fn, E, [[]|Ls], Seen) ->
+ find_and_remove_from_heads(Found, Fn, E, Ls, Seen);
+find_and_remove_from_heads(false, _Fn, _E, [], _Seen) ->
+ false;
+find_and_remove_from_heads(true, _Fn, _E, [], Seen) ->
+ {true, lists:reverse(Seen)};
+find_and_remove_from_heads(Found, Fn, E, [[E1|LRest]=L|Ls], Seen) ->
+ case Fn(E, E1) andalso Fn(E1, E) of
+ true ->
+ find_and_remove_from_heads(true, Fn, E, Ls, [LRest|Seen]);
+ false ->
+ find_and_remove_from_heads(Found, Fn, E, Ls, [L|Seen])
+ end.
+
+%% --------------------------------------------------------------------
+check_uniqed(L, UL) ->
+ check_uniqed(fun(X) -> X end, L, UL).
+
+check_uniqed(Fn, L, UL) ->
+ check_uniqed1(Fn, L, UL, sets:new([{version, 2}])).
+
+check_uniqed1(Fn, [E|L], [], Seen) ->
+ sets:is_element(Fn(E), Seen) andalso
+ check_uniqed1(Fn, L, [], Seen);
+check_uniqed1(Fn, [E1|L], [E2|URest]=U, Seen) ->
+ X1 = Fn(E1),
+ X2 = Fn(E2),
+ case sets:is_element(X1, Seen) of
+ true ->
+ X1 =/= X2 andalso
+ check_uniqed1(Fn, L, U, Seen);
+ false ->
+ X1 =:= X2 andalso
+ check_uniqed1(Fn, L, URest, sets:add_element(X1, Seen))
+ end;
+check_uniqed1(_Fn, [], [], _Seen) ->
+ true;
+check_uniqed1(_Fn, _L, _UL, _Seen) ->
+ false.
+
+%% --------------------------------------------------------------------
+check_usorted(L, Sorted) ->
+ check_usorted(fun erlang:'=<'/2, L, Sorted).
+
+check_usorted(SortFun, L, Sorted) ->
+ ExpElems = ucount_elems(SortFun, L),
+ check_sorted(SortFun, Sorted, ExpElems, #{}).
+
+ucount_elems(SortFun, L) ->
+ ucount_elems(SortFun, L, #{}).
+
+ucount_elems(SortFun, [E|Es], Acc) ->
+ K = ufind_key(SortFun, E, maps:keys(Acc)),
+ ucount_elems(SortFun, Es, maps:put(K, 1, Acc));
+ucount_elems(_SortFun, [], Acc) ->
+ Acc.
+
+ufind_key(SortFun, E, [K|Keys]) ->
+ case SortFun(E, K) andalso SortFun(K, E) of
+ true ->
+ K;
+ false ->
+ ufind_key(SortFun, E, Keys)
+ end;
+ufind_key(_SortFun, E, []) ->
+ E.
diff --git a/lib/stdlib/test/property_test/uri_string_recompose.erl b/lib/stdlib/test/property_test/uri_string_recompose.erl
index 1720ea78d3..4f37e4aa9c 100644
--- a/lib/stdlib/test/property_test/uri_string_recompose.erl
+++ b/lib/stdlib/test/property_test/uri_string_recompose.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -84,7 +84,7 @@ prop_recompose() ->
Map =:= uri_string:parse(uri_string:recompose(Map))).
prop_normalize() ->
- ?FORALL(Map, map(),
+ ?FORALL(Map, property_map(),
uri_string:percent_decode(
uri_string:normalize(Map, [return_map])) =:=
uri_string:percent_decode(
@@ -94,11 +94,11 @@ prop_normalize() ->
%% Stats
prop_map_key_length_collect() ->
- ?FORALL(List, map(),
+ ?FORALL(List, property_map(),
collect(length(maps:keys(List)), true)).
prop_map_collect() ->
- ?FORALL(List, map(),
+ ?FORALL(List, property_map(),
collect(lists:sort(maps:keys(List)), true)).
prop_scheme_collect() ->
@@ -110,7 +110,7 @@ prop_scheme_collect() ->
%%% Generators
%%%========================================================================
-map() ->
+property_map() ->
?LET(Gen, comp_proplist(), proplist_to_map(Gen)).
map_no_unicode() ->
diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl
index 74c8aabf8e..2e1b722b8e 100644
--- a/lib/stdlib/test/qlc_SUITE.erl
+++ b/lib/stdlib/test/qlc_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -401,6 +401,7 @@ nomatch(Config) when is_list(Config) ->
%% {warnings,[{{3,52},qlc,nomatch_pattern}]}},
{warnings,[{{3,37},v3_core,{nomatch,pattern}}]}},
+ %% No longer illegal in OTP 26.
{nomatch4,
<<"nomatch() ->
etsc(fun(E) ->
@@ -411,7 +412,7 @@ nomatch(Config) when is_list(Config) ->
end, [{<<34>>},{<<40>>}]).
">>,
[],
- {errors,[{{3,48},erl_lint,illegal_bin_pattern}],[]}},
+ []},
{nomatch5,
<<"nomatch() ->
@@ -6556,15 +6557,15 @@ otp_7114(Config) when is_list(Config) ->
%% OTP-7232. qlc:info() bug (pids, ports, refs, funs).
otp_7232(Config) when is_list(Config) ->
- Ts = [<<"L = [fun math:sqrt/1, list_to_pid(\"<0.4.1>\"),
+ Ts = [<<"L = [fun math:sqrt/1, list_to_pid(\"<0.4.0>\"),
erlang:make_ref()],
- \"[fun math:sqrt/1, <0.4.1>, #Ref<\" ++ _ = qlc:info(L),
+ \"[fun math:sqrt/1, <0.4.0>, #Ref<\" ++ _ = qlc:info(L),
{call,_,
{remote,_,{atom,_,qlc},{atom,_,sort}},
[{cons,_,
{'fun',_,{function,{atom,_,math},{atom,_,sqrt},_}},
{cons,_,
- {string,_,\"<0.4.1>\"}, % could use list_to_pid..
+ {string,_,\"<0.4.0>\"}, % could use list_to_pid..
{cons,_,{string,_,\"#Ref<\"++_},{nil,_}}}},
{nil,_}]} =
qlc:info(qlc:sort(L),{format,abstract_code})">>,
diff --git a/lib/stdlib/test/re_SUITE.erl b/lib/stdlib/test/re_SUITE.erl
index 09a65d8fdd..fc6e977942 100644
--- a/lib/stdlib/test/re_SUITE.erl
+++ b/lib/stdlib/test/re_SUITE.erl
@@ -22,7 +22,7 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2, pcre/1,compile_options/1,
run_options/1,combined_options/1,replace_autogen/1,
- global_capture/1,replace_input_types/1,replace_return/1,
+ global_capture/1,replace_input_types/1,replace_with_fun/1,replace_return/1,
split_autogen/1,split_options/1,split_specials/1,
error_handling/1,pcre_cve_2008_2371/1,re_version/1,
pcre_compile_workspace_overflow/1,re_infinite_loop/1,
@@ -42,7 +42,7 @@ suite() ->
all() ->
[pcre, compile_options, run_options, combined_options,
replace_autogen, global_capture, replace_input_types,
- replace_return, split_autogen, split_options,
+ replace_with_fun, replace_return, split_autogen, split_options,
split_specials, error_handling, pcre_cve_2008_2371,
pcre_compile_workspace_overflow, re_infinite_loop,
re_backwards_accented, opt_dupnames, opt_all_names,
@@ -365,6 +365,16 @@ replace_input_types(Config) when is_list(Config) ->
<<"a",208,128,"cd">> = re:replace(<<"abcd">>,"b","\x{400}",[{return,binary},unicode]),
ok.
+%% Test replace with a replacement function.
+replace_with_fun(Config) when is_list(Config) ->
+ <<"ABCD">> = re:replace("abcd", ".", fun(<<C>>, []) -> <<(C - $a + $A)>> end, [global, {return, binary}]),
+ <<"AbCd">> = re:replace("abcd", ".", fun(<<C>>, []) when (C - $a) rem 2 =:= 0 -> <<(C - $a + $A)>>; (C, []) -> C end, [global, {return, binary}]),
+ <<"b-ad-c">> = re:replace("abcd", "(.)(.)", fun(_, [A, B]) -> <<B/binary, $-, A/binary>> end, [global, {return, binary}]),
+ <<"#ab-B#cd">> = re:replace("abcd", ".(.)", fun(Whole, [<<C>>]) -> <<$#, Whole/binary, $-, (C - $a + $A), $#>> end, [{return, binary}]),
+ <<"#ab#cd">> = re:replace("abcd", ".(x)?(.)", fun(Whole, [<<>>, _]) -> <<$#, Whole/binary, $#>> end, [{return, binary}]),
+ <<"#ab#cd">> = re:replace("abcd", ".(.)(x)?", fun(Whole, [_]) -> <<$#, Whole/binary, $#>> end, [{return, binary}]),
+ ok.
+
%% Test return options of replace together with global searching.
replace_return(Config) when is_list(Config) ->
{'EXIT',{badarg,_}} = (catch re:replace("na","(a","")),
diff --git a/lib/stdlib/test/shell_SUITE.erl b/lib/stdlib/test/shell_SUITE.erl
index b38dee47e7..142f8ad445 100644
--- a/lib/stdlib/test/shell_SUITE.erl
+++ b/lib/stdlib/test/shell_SUITE.erl
@@ -18,24 +18,25 @@
%% %CopyrightEnd%
%%
-module(shell_SUITE).
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
-
-export([forget/1, records/1, known_bugs/1, otp_5226/1, otp_5327/1,
otp_5435/1, otp_5195/1, otp_5915/1, otp_5916/1,
bs_match_misc_SUITE/1, bs_match_int_SUITE/1,
bs_match_tail_SUITE/1, bs_match_bin_SUITE/1,
bs_construct_SUITE/1,
- refman_bit_syntax/1,
- progex_bit_syntax/1, progex_records/1,
+ refman_bit_syntax/1,
+ progex_bit_syntax/1, progex_records/1,
progex_lc/1, progex_funs/1,
otp_5990/1, otp_6166/1, otp_6554/1,
otp_7184/1, otp_7232/1, otp_8393/1, otp_10302/1, otp_13719/1,
- otp_14285/1, otp_14296/1, typed_records/1]).
+ otp_14285/1, otp_14296/1, typed_records/1, types/1]).
--export([ start_restricted_from_shell/1,
+-export([ start_restricted_from_shell/1,
start_restricted_on_command_line/1,restricted_local/1]).
+-export([ start_interactive/1, whereis/1 ]).
+
%% Internal export.
-export([otp_5435_2/0, prompt1/1, prompt2/1, prompt3/1, prompt4/1,
prompt5/1]).
@@ -73,19 +74,21 @@ suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,10}}].
-all() ->
+all() ->
[forget, known_bugs, otp_5226, otp_5327,
- otp_5435, otp_5195, otp_5915, otp_5916, {group, bits},
+ otp_5435, otp_5195, otp_5915, otp_5916,
+ start_interactive, whereis, {group, bits},
{group, refman}, {group, progex}, {group, tickets},
- {group, restricted}, {group, records}].
+ {group, restricted}, {group, records}, {group, definitions}].
-groups() ->
+groups() ->
[{restricted, [],
[start_restricted_from_shell,
start_restricted_on_command_line, restricted_local]},
{bits, [],
[bs_match_misc_SUITE, bs_match_tail_SUITE,
bs_match_bin_SUITE, bs_construct_SUITE]},
+ {definitions, [], [types]},
{records, [],
[records, typed_records]},
{refman, [], [refman_bit_syntax]},
@@ -300,7 +303,7 @@ restricted_local(Config) when is_list(Config) ->
application:get_env(stdlib, restricted_shell),
true = purge_and_delete(user_default),
ok.
-
+
%% f/0 and f/1.
forget(Config) when is_list(Config) ->
@@ -319,15 +322,73 @@ forget(Config) when is_list(Config) ->
comm_err(<<"f(a).">>),
ok.
+%% type definition support
+types(Config) when is_list(Config) ->
+ %% type
+ [ok] = scan(<<"-type baz() :: integer().">>),
+ %% record
+ [ok] = scan(<<"-record(foo, {bar :: baz()}).">>),
+ %% spec
+ [ok] = scan(<<"-spec foo(Bar) -> Baz when
+ Bar :: string(),
+ Baz :: integer().">>),
+ shell_attribute_test(Config),
+ ok.
+shell_attribute_test(Config) ->
+ Path = filename:join([proplists:get_value(priv_dir, Config),
+ "shell_history", "function_def"]),
+ rtnode:run(
+ [{putline, "foo(Bar) -> Bar."},
+ {expect, "ok"},
+ {putline, "fl()."},
+ {expect, "\\Q{function,{shell_default,foo,1}}\\E"},
+ {putline, "foo(1)."},
+ {expect, "1"},
+ {putline, "shell_default:foo(2)."},
+ {expect, "2"}
+ ],[],"", ["-kernel","shell_history","enabled",
+ "-kernel","shell_history_path","\"" ++ Path ++ "\"",
+ "-kernel","shell_history_drop","[\"init:stop().\"]"]),
+ receive after 1000 -> ok end,
+ rtnode:run(
+ [{putline, "-record(hej, {a = 0 :: integer()})."},
+ {expect, "ok"},
+ {putline, "rl()."},
+ {expect, "\\Q-record(hej,{a = 0 :: integer()}).\\E"},
+ {putline, "#hej{a=1}."},
+ {expect, "\\Q#hej{a = 1}\\E"}
+ ],[],"", ["-kernel","shell_history","enabled",
+ "-kernel","shell_history_path","\"" ++ Path ++ "\"",
+ "-kernel","shell_history_drop","[\"init:stop().\"]"]),
+ receive after 1000 -> ok end,
+ rtnode:run(
+ [{putline, "-spec foo(Bar) -> Bar when Bar :: integer()."},
+ {expect, "ok"},
+ {putline, "fl()."},
+ {expect, "\\Q{function_type,{shell_default,foo,1}}\\E"}
+ ],[],"", ["-kernel","shell_history","enabled",
+ "-kernel","shell_history_path","\"" ++ Path ++ "\"",
+ "-kernel","shell_history_drop","[\"init:stop().\"]"]),
+ receive after 1000 -> ok end,
+ rtnode:run(
+ [{putline, "-type my_type() :: boolean() | integer()."},
+ {expect, "ok"},
+ {putline, "fl()."},
+ {expect, "\\Q{type,my_type}\\E"}
+ ],[],"", ["-kernel","shell_history","enabled",
+ "-kernel","shell_history_path","\"" ++ Path ++ "\"",
+ "-kernel","shell_history_drop","[\"init:stop().\"]"]),
+ ok.
+
%% Test of the record support. OTP-5063.
records(Config) when is_list(Config) ->
%% rd/2
[{attribute,_,record,{bar,_}},ok] =
- scan(<<"rd(foo,{bar}),
+ scan(<<"rd(foo,{bar}),
rd(bar,{foo = (#foo{})#foo.bar}),
rl(bar).">>),
"variable 'R' is unbound" = % used to work (before OTP-5878, R11B)
- exit_string(<<"rd(foo,{bar}),
+ exit_string(<<"rd(foo,{bar}),
R = #foo{},
rd(bar,{foo = R#foo.bar}).">>),
"exception error: no function clause matching call to rd/2" =
@@ -401,7 +462,7 @@ records(Config) when is_list(Config) ->
[{attribute,A1,record,{test1,_}},ok] = scan(RR5),
RR6 = "rr(\"" ++ Test ++ "\", '_', {d,test2}), rl([test1,test2]).",
[{attribute,A1,record,{test2,_}},ok] = scan(RR6),
- RR7 = "rr(\"" ++ Test ++
+ RR7 = "rr(\"" ++ Test ++
"\", '_', [{d,test1},{d,test2,17}]), rl([test1,test2]).",
[{attribute,A1,record,{test1,_}},{attribute,A1,record,{test2,_}},ok] =
scan(RR7),
@@ -503,7 +564,7 @@ records(Config) when is_list(Config) ->
[ok] =
scan(<<"rd(a,{}), is_record({a},a) andalso true, b().">>),
-
+
%% nested record defs
"#b{a = #a{}}.\n" = t(<<"rd(a,{}), rd(b, {a = #a{}}), #b{}.">>),
@@ -672,7 +733,7 @@ otp_5435_2() ->
%% application being in the path.
%% OTP-5876.
[{attribute,_,record,{bar,_}},ok] =
- scan(<<"rd(foo,{bar}),
+ scan(<<"rd(foo,{bar}),
rd(bar,{foo = (#foo{})#foo.bar}),
rl(bar).">>),
ok.
@@ -685,7 +746,7 @@ otp_5195(Config) when is_list(Config) ->
%% An experimental shell used to translate error tuples:
%% "(qlc) \"1: generated variable 'X' must not be used in "
- %% "list expression\".\n" =
+ %% "list expression\".\n" =
%% t(<<"qlc:q([X || X <- [{a}], Y <- [X]]).">>),
%% Same as last one (if the shell does not translate error tuples):
[{error,qlc,{{1,31},qlc,{used_generator_variable,'X'}}}] =
@@ -1204,7 +1265,7 @@ bs_match_int_SUITE(Config) when is_list(Config) ->
Int -> ok;
Other ->
io:format(\"Bin = ~p,\", [Bin]),
- io:format(\"SkipBef = ~p, N = ~p\",
+ io:format(\"SkipBef = ~p, N = ~p\",
[SkipBef,N]),
io:format(\"Expected ~p, got ~p\",
[Int,Other])
@@ -1311,8 +1372,8 @@ ok = evaluate(C, []).
%% OTP-5327. Adopted from emulator/test/bs_match_bin_SUITE.erl.
bs_match_bin_SUITE(Config) when is_list(Config) ->
- ByteSplitBinary =
- <<"ByteSplit =
+ ByteSplitBinary =
+ <<"ByteSplit =
fun(L, B, Pos, Fun) when Pos >= 0 ->
Sz1 = Pos,
Sz2 = size(B) - Pos,
@@ -1343,7 +1404,7 @@ ok = evaluate(ByteSplitBinary, []),
BitSplitBinary =
<<"Mkbin = fun(L) when list(L) -> list_to_binary(L) end,
- MakeInt =
+ MakeInt =
fun(List, 0, Acc, _F) -> Acc;
([H|T], N, Acc, F) -> F(T, N-1, Acc bsl 1 bor H, F)
end,
@@ -1452,7 +1513,7 @@ bs_construct_SUITE(Config) when is_list(Config) ->
?FAIL(<<<<23,56,0,2>>:(-16)/binary>>) ","
?FAIL(<<<<23,56,0,2>>:(2.5)/binary>>) ","
?FAIL(<<<<23,56,0,2>>:(anka)>>) "
- end,
+ end,
TestF(),
NotUsed1 = fun(I, BinString) -> <<I:32,BinString/binary>>, ok end,
@@ -1512,7 +1573,7 @@ ok = evaluate(C1, []),
C2 = <<"
I = fun(X) -> X end,
- Fail = fun() ->
+ Fail = fun() ->
I_minus_777 = I(-777),
I_minus_2047 = I(-2047),
@@ -1634,8 +1695,8 @@ progex_bit_syntax(Config) when is_list(Config) ->
Fun4 = fun(Dgram) ->
DgramSize = byte_size(Dgram),
- case Dgram of
- <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
+ case Dgram of
+ <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
ID:16, Flgs:3, FragOff:13,
TTL:8, Proto:8, HdrChkSum:16,
SrcIP:32, DestIP:32,
@@ -1736,7 +1797,7 @@ triples_to_bin1(T) ->
triples_to_bin1([{X,Y,Z} | T], Acc) ->
triples_to_bin1(T, <<Acc/binary, X:32, Y:32, Z:32>>); % inefficient
-triples_to_bin1([], Acc) ->
+triples_to_bin1([], Acc) ->
Acc.
triples_to_bin2(T) ->
@@ -1744,12 +1805,12 @@ triples_to_bin2(T) ->
triples_to_bin2([{X,Y,Z} | T], Acc) ->
triples_to_bin2(T, [<<X:32, Y:32, Z:32>> | Acc]);
-triples_to_bin2([], Acc) ->
+triples_to_bin2([], Acc) ->
list_to_binary(lists:reverse(Acc)).
%% Record examples from Programming Examples. OTP-5237.
progex_records(Config) when is_list(Config) ->
- Test1 =
+ Test1 =
<<"-module(recs).
-record(person, {name = \"\", phone = [], address}).
-record(name, {first = \"Robert\", last = \"Ericsson\"}).
@@ -1786,7 +1847,7 @@ t() ->
c),
P3 = #person{name=\"Joe\", phone=[0,0,7], address=\"A street\"},
- #person{name = Name} = P3,
+ #person{name = Name} = P3,
\"Joe\" = Name,
\"Robert\" = demo(),
@@ -1854,7 +1915,7 @@ Test1_shell =
Find),
P3 = #person{name=\"Joe\", phone=[0,0,7], address=\"A street\"},
- #person{name = Name} = P3,
+ #person{name = Name} = P3,
\"Joe\" = Name,
Demo = fun() ->
@@ -1884,7 +1945,7 @@ print(#person{name = Name, age = Age,
io:format(\"Name: ~s, Age: ~w, Phone: ~w ~n\"
\"Dictionary: ~w.~n\", [Name, Age, Phone, Dict]).
- birthday(P) when record(P, person) ->
+ birthday(P) when record(P, person) ->
P#person{age = P#person.age + 1}.
register_two_hackers() ->
@@ -1902,7 +1963,7 @@ ok.
%% List comprehension examples from Programming Examples. OTP-5237.
progex_lc(Config) when is_list(Config) ->
- Test1 =
+ Test1 =
<<"-module(lc).
-export([t/0]).
@@ -2036,7 +2097,7 @@ ok.
%% Funs examples from Programming Examples. OTP-5237.
progex_funs(Config) when is_list(Config) ->
- Test1 =
+ Test1 =
<<"-module(funs).
-export([t/0]).
@@ -2273,7 +2334,7 @@ Test2_shell =
\"ERLANG\" = Upcase_word(\"Erlang\"),
[\"I\",\"LIKE\",\"ERLANG\"] = lists:map(Upcase_word, L),
{[\"I\",\"LIKE\",\"ERLANG\"],11} =
- lists:mapfoldl(fun(Word, Sum) ->
+ lists:mapfoldl(fun(Word, Sum) ->
{Upcase_word(Word), Sum + length(Word)}
end, 0, L),
[500,12,45] = lists:filter(Big, [500,12,2,45,6,7]),
@@ -2318,10 +2379,10 @@ otp_6166(Config) when is_list(Config) ->
-record(r6, {f = #r5{}}). % r6 > r0
-record(r0, {f = #r5{}, g = #r5{}}). % r0 < r5">>,
ok = file:write_file(Test2, Contents2),
-
- RR12 = "[r1,r2,r3,r4,r5] = rr(\"" ++ Test1 ++ "\"),
- [r0,r1,r2,r3,r4,r5,r6] = rr(\"" ++ Test2 ++ "\"),
- R0 = #r0{}, R6 = #r6{},
+
+ RR12 = "[r1,r2,r3,r4,r5] = rr(\"" ++ Test1 ++ "\"),
+ [r0,r1,r2,r3,r4,r5,r6] = rr(\"" ++ Test2 ++ "\"),
+ R0 = #r0{}, R6 = #r6{},
true = is_record(R0, r0),
true = is_record(R6, r6),
ok. ",
@@ -2438,7 +2499,7 @@ otp_6554(Config) when is_list(Config) ->
"exception error: undefined function math:sqrt/2" =
comm_err(<<"math:sqrt(2, 2).">>),
"exception error: limit of number of arguments to interpreted function "
- "exceeded" =
+ "exceeded" =
comm_err(<<"fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U) ->"
" a end().">>),
"exception error: bad filter a" =
@@ -2536,7 +2597,7 @@ otp_6554(Config) when is_list(Config) ->
t(<<"results(2). 1. v(2). h().">>),
application:unset_env(stdlib, shell_saved_results),
"1\nfoo\n17\nB = foo\nC = 17\nF = fun() ->\n foo"
- "\n end.\nok.\n" =
+ "\n end.\nok.\n" =
t(<<"begin F = fun() -> foo end, 1 end. B = F(). C = 17. b().">>),
"3: command not found" = comm_err(<<"#{v(3) => v}.">>),
@@ -2562,9 +2623,9 @@ otp_7184(Config) when is_list(Config) ->
t(<<"P = self(),
spawn_link(fun() -> process_flag(trap_exit,true),
P ! up,
- receive X ->
+ receive X ->
otp_7184 ! {otp_7184, X}
- end
+ end
end),
receive up -> ok end,
erlang:raise(throw, thrown, []).">>),
@@ -2574,9 +2635,9 @@ otp_7184(Config) when is_list(Config) ->
t(<<"P = self(),
spawn_link(fun() -> process_flag(trap_exit,true),
P ! up,
- receive X ->
+ receive X ->
otp_7184 ! {otp_7184, X}
- end
+ end
end),
receive up -> ok end,
erlang:raise(exit, fini, []).">>),
@@ -2586,9 +2647,9 @@ otp_7184(Config) when is_list(Config) ->
t(<<"P = self(),
spawn_link(fun() -> process_flag(trap_exit,true),
P ! up,
- receive X ->
+ receive X ->
otp_7184 ! {otp_7184,X}
- end
+ end
end),
receive up -> ok end,
erlang:raise(error, bad, []).">>),
@@ -2713,7 +2774,7 @@ exit_term(B) ->
-endif.
error_string(B) ->
- "** exception error:" ++ Reply = t(B),
+ "** exception error:" ++ Reply = t(B),
caught_string(Reply).
exit_string(B) ->
@@ -2820,7 +2881,7 @@ otp_10302(Config) when is_list(Config) ->
{ok, Es} = erl_parse:parse_exprs(Ts),
B = erl_eval:new_bindings(),
erl_eval:exprs(Es, B).">>,
-
+
"ok.\n** exception error: an error occurred when evaluating"
" an arithmetic expression\n in operator '/'/2\n"
" called as <<\"ª\">> / <<\"ª\">>.\n" = t({Node,Test7}),
@@ -3014,11 +3075,198 @@ otp_14296(Config) when is_list(Config) ->
{error, {_,_,"bad term"}} = TF("1, 2"),
ok.
+start_interactive(_Config) ->
+ start_interactive_shell([]),
+ start_interactive_shell(["-env","TERM","dumb"]).
+
+start_interactive_shell(ExtraArgs) ->
+
+ %% Basic test case
+ rtnode:run(
+ [{expect, "eval_test"},
+ {putline, "test."},
+ {eval, fun() -> shell:start_interactive() end},
+ {expect, "1>"},
+ {expect, "2>"},
+ {eval, fun() -> {error,already_started} = shell:start_interactive(), ok end}
+ ],[],"",["-noinput","-eval","io:format(\"eval_test~n\")"] ++ ExtraArgs),
+
+ %% Test that custom MFA works
+ rtnode:run(
+ [{expect, "eval_test"},
+ {putline, "test."},
+ {eval, fun() -> shell:start_interactive({shell,start,[]}) end},
+ {expect, "1>"},
+ {expect, "2>"}
+ ],[],"",["-noinput","-eval","io:format(\"eval_test~n\")"] ++ ExtraArgs),
+
+ %% Test that we can start noshell and then a shell
+ rtnode:run(
+ [{expect, "eval_test"},
+ {putline, "test."},
+ {eval, fun() -> shell:start_interactive(noshell) end},
+ {eval, fun() -> io:format(user,"~ts",[io:get_line(user, "")]) end},
+ {expect, "test\\."},
+ {eval, fun() -> shell:start_interactive() end},
+ {expect, "1>"},
+ {putline, "test."},
+ {expect, "2>"}
+ ],[],"",["-noinput","-eval","io:format(\"eval_test~n\")"] ++ ExtraArgs),
+
+ %% Test that we can start various remote shell combos
+ [ begin
+ {ok, Peer, Node} = ?CT_PEER(),
+ SNode = atom_to_list(Node),
+ rtnode:run(
+ [{expect, "eval_test"},
+ {putline, "test."},
+ {eval, fun() -> shell:start_interactive(Arg(Node)) end},
+ {expect, "\\Q("++SNode++")\\E2>"}
+ ] ++ quit_hosting_node(),
+ [],"",["-noinput","-eval","io:format(\"eval_test~n\")"] ++ ExtraArgs),
+ peer:stop(Peer)
+ end || Arg <- [fun(Node) -> {Node, {shell,start,[]}} end,
+ fun(Node) -> {remote, atom_to_list(Node)} end,
+ fun(Node) -> {remote, hd(string:split(atom_to_list(Node),"@"))} end,
+ fun(Node) -> {remote, atom_to_list(Node), {shell,start,[]}} end
+ ]],
+
+ %% Test that errors work as they should
+ {ok, Peer, Node} = ?CT_PEER(),
+ rtnode:run(
+ [{expect, "eval_test"},
+ {eval, fun() ->
+ {error,noconnection} = shell:start_interactive(
+ {remote,"invalid_node"}),
+ {error,noconnection} = shell:start_interactive(
+ {remote,"invalid_node",
+ {invalid_module, start, []}}),
+ {error,nofile} = shell:start_interactive(
+ {remote,atom_to_list(Node),
+ {invalid_module, start, []}}),
+ shell:start_interactive({remote, atom_to_list(Node)})
+ end},
+ {expect, "1> $"}
+ ] ++ quit_hosting_node(),
+ [],"",["-noinput","-eval","io:format(\"eval_test~n\")"] ++ ExtraArgs),
+ peer:stop(Peer),
+
+ ok.
+
+whereis(_Config) ->
+ Proxy = spawn_link(
+ fun() ->
+ (fun F(P) ->
+ receive
+ {set,NewPid} ->
+ F(NewPid);
+ {get,From} ->
+ From ! P,
+ F(P)
+ end
+ end)(undefined)
+ end),
+
+ %% Test that shell:whereis() works with JCL in newshell
+ rtnode:run(
+ [{expect,"1> $"},
+ {putline,"shell:whereis()."},
+ {expect,"2> $"},
+ {eval,fun() ->
+ group_leader(erlang:whereis(user),self()),
+ Proxy ! {set,shell:whereis()},
+ ok
+ end},
+ {putline,"\^g"},
+ {expect, "--> $"},
+ {putline, "s"},
+ {expect, "--> $"},
+ {putline, "c"},
+ {expect, "\r\nEshell"},
+ {putline,"shell:whereis()."},
+ {expect,"2> $"},
+ {eval,fun() ->
+ group_leader(erlang:whereis(user),self()),
+ Proxy ! {get, self()},
+ receive PrevPid -> PrevPid end,
+ io:format("~p =:= ~p~n",[PrevPid, shell:whereis()]),
+ false = PrevPid =:= shell:whereis(),
+ ok
+ end},
+ {putline,"\^g"},
+ {expect, "--> $"},
+ {putline, "c 1"},
+ {expect, "\r\n"},
+ {putline, ""},
+ {expect, "2> $"},
+ {eval,fun() ->
+ group_leader(erlang:whereis(user),self()),
+ Proxy ! {get, self()},
+ receive PrevPid -> PrevPid end,
+ true = PrevPid =:= shell:whereis(),
+ ok
+ end}]),
+
+ %% Test that shell:whereis() works in oldshell
+ rtnode:run(
+ [{expect,"1>"},
+ {eval,fun() ->
+ group_leader(erlang:whereis(user),self()),
+ true = is_pid(shell:whereis()),
+ ok
+ end}],
+ [],"",["-env","TERM","dumb"]),
+
+ %% Test that noinput and noshell gives undefined shell process
+ rtnode:run(
+ [{eval,fun() ->
+ group_leader(erlang:whereis(user),self()),
+ undefined = shell:whereis(),
+ ok
+ end}],
+ [],"",["-noinput"]),
+ rtnode:run(
+ [{eval,fun() ->
+ group_leader(erlang:whereis(user),self()),
+ undefined = shell:whereis(),
+ ok
+ end}],
+ [],"",["-noshell"]),
+
+ %% Test that remsh gives the correct shell process
+ {ok, Peer, Node} = ?CT_PEER(),
+ NodeStr = lists:flatten(io_lib:format("~w",[Node])),
+ rtnode:run(
+ [{expect, "1>"},
+ {putline,"shell:whereis()."},
+ {expect,"\n<0[.]"},
+ {expect, "2>"},
+ {eval, fun() ->
+ group_leader(erlang:whereis(user),self()),
+ true = Node =:= node(shell:whereis()),
+ ok
+ end}] ++ quit_hosting_node(),
+ peer:random_name(?FUNCTION_NAME), " ", "-remsh " ++ NodeStr ++
+ " -pa " ++ filename:dirname(code:which(?MODULE))),
+
+ peer:stop(Peer),
+
+ ok.
+
+quit_hosting_node() ->
+ [{putline, "\^g"},
+ {expect, "--> $"},
+ {putline, "s"},
+ {expect, "--> $"},
+ {putline, "c"},
+ {expect, ["Eshell"]},
+ {expect, ["1> $"]}].
+
term_to_string(T) ->
lists:flatten(io_lib:format("~w", [T])).
scan(B) ->
- F = fun(Ts) ->
+ F = fun(Ts) ->
case erl_parse:parse_term(Ts) of
{ok,Term} ->
Term;
@@ -3059,18 +3307,18 @@ t1(Parent, {Bin,Enc}, F) ->
S = #state{bin = Bin, unic = Enc, reply = [], leader = group_leader()},
group_leader(self(), self()),
_Shell = F(),
- try
+ try
server_loop(S)
catch exit:R -> Parent ! {self(), R};
throw:{?MODULE,LoopReply,latin1} ->
- L0 = binary_to_list(list_to_binary(LoopReply)),
- [$\n | L1] = lists:dropwhile(fun(X) -> X =/= $\n end, L0),
- Parent ! {self(), dotify(L1)};
+ L0 = binary_to_list(list_to_binary(LoopReply)),
+ [$\n | L1] = lists:dropwhile(fun(X) -> X =/= $\n end, L0),
+ Parent ! {self(), dotify(L1)};
throw:{?MODULE,LoopReply,_Uni} ->
- Tmp = unicode:characters_to_binary(LoopReply),
- L0 = unicode:characters_to_list(Tmp),
- [$\n | L1] = lists:dropwhile(fun(X) -> X =/= $\n end, L0),
- Parent ! {self(), dotify(L1)}
+ Tmp = unicode:characters_to_binary(LoopReply),
+ L0 = unicode:characters_to_list(Tmp),
+ [$\n | L1] = lists:dropwhile(fun(X) -> X =/= $\n end, L0),
+ Parent ! {self(), dotify(L1)}
after group_leader(S#state.leader, self())
end.
@@ -3102,20 +3350,20 @@ start_new_shell(Node) ->
%% This is a very minimal implementation of the IO protocol...
server_loop(S) ->
- receive
+ receive
{io_request, From, ReplyAs, Request} when is_pid(From) ->
- server_loop(do_io_request(Request, From, S, ReplyAs));
- NotExpected ->
+ server_loop(do_io_request(Request, From, S, ReplyAs));
+ NotExpected ->
exit(NotExpected)
end.
-
+
do_io_request(Req, From, S, ReplyAs) ->
case io_requests([Req], [], S) of
{_Status,{eof,_},S1} ->
- io_reply(From, ReplyAs, {error,terminated}),
- throw({?MODULE,S1#state.reply,S1#state.unic});
- {_Status,Reply,S1} ->
- io_reply(From, ReplyAs, Reply),
+ io_reply(From, ReplyAs, {error,terminated}),
+ throw({?MODULE,S1#state.reply,S1#state.unic});
+ {_Status,Reply,S1} ->
+ io_reply(From, ReplyAs, Reply),
S1
end.
@@ -3133,7 +3381,7 @@ io_requests([R | Rs], Cont, S) ->
end;
io_requests([], [Rs|Cont], S) ->
io_requests(Rs, Cont, S);
-io_requests([], [], S) ->
+io_requests([], [], S) ->
{ok,ok,S}.
io_request({setopts, Opts}, S) ->
@@ -3165,7 +3413,7 @@ io_request({put_chars,unicode,Chars0}, S) ->
{ok,ok,S#state{reply = [S#state.reply | Chars]}};
io_request({put_chars,Enc,Mod,Func,Args}, S) ->
case catch apply(Mod, Func, Args) of
- Chars when is_list(Chars) ->
+ Chars when is_list(Chars) ->
io_request({put_chars,Enc,Chars}, S)
end;
io_request({get_until,Enc,_Prompt,Mod,Func,ExtraArgs}, S) ->
@@ -3178,7 +3426,7 @@ get_until_loop(M, F, As, S, {more,Cont}, Enc) ->
Bin = S#state.bin,
case byte_size(Bin) of
0 ->
- get_until_loop(M, F, As, S,
+ get_until_loop(M, F, As, S,
catch apply(M, F, [Cont,eof|As]), Enc);
_ when S#state.unic =:= latin1 ->
get_until_loop(M, F, As, S#state{bin = <<>>},
@@ -3208,7 +3456,7 @@ run_file(Config, Module, Test) ->
ok = file:write_file(FileName, Test),
ok = compile_file(Config, FileName, Test, []),
code:purge(Module),
- {module, Module} = code:load_abs(LoadBeamFile),
+ {module, Module} = code:load_abs(LoadBeamFile),
ok = Module:t(),
file:delete(FileName),
file:delete(BeamFile),
diff --git a/lib/stdlib/test/shell_docs_SUITE.erl b/lib/stdlib/test/shell_docs_SUITE.erl
index 028e2c0aba..97a9a73d71 100644
--- a/lib/stdlib/test/shell_docs_SUITE.erl
+++ b/lib/stdlib/test/shell_docs_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -255,14 +255,15 @@ render_non_native(_Config) ->
beam_language = not_erlang,
format = <<"text/asciidoc">>,
module_doc = #{<<"en">> => <<"This is\n\npure text">>},
- docs= []
+ docs = []
},
<<"\n\tnot_an_erlang_module\n\n"
" This is\n"
" \n"
" pure text\n">> =
- unicode:characters_to_binary(shell_docs:render(not_an_erlang_module, Docs, #{})),
+ unicode:characters_to_binary(
+ shell_docs:render(not_an_erlang_module, Docs, #{ ansi => false })),
ok.
diff --git a/lib/stdlib/test/supervisor_SUITE.erl b/lib/stdlib/test/supervisor_SUITE.erl
index 78d7e7d7bc..a9cf48e997 100644
--- a/lib/stdlib/test/supervisor_SUITE.erl
+++ b/lib/stdlib/test/supervisor_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -200,7 +200,7 @@ start_link(InitResult) ->
%% Simulate different supervisors callback.
init(fail) ->
- erlang:error({badmatch,2});
+ erlang:error(fail);
init(InitResult) ->
InitResult.
@@ -227,7 +227,7 @@ sup_start_normal(Config) when is_list(Config) ->
sup_start_ignore_init(Config) when is_list(Config) ->
process_flag(trap_exit, true),
ignore = start_link(ignore),
- check_exit_reason(normal).
+ check_no_exit(100).
%%-------------------------------------------------------------------------
%% Tests what happens if init-callback returns ignore.
@@ -325,15 +325,20 @@ sup_start_ignore_permanent_child_start_child_simple(Config)
%% Tests what happens if init-callback returns a invalid value.
sup_start_error_return(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- {error, Term} = start_link(invalid),
- check_exit_reason(Term).
+ %% The bad return is processed in supervisor:init/1
+ InitResult = invalid,
+ {error, {bad_return, {?MODULE, init, InitResult}}} =
+ start_link(InitResult),
+ check_no_exit(100).
%%-------------------------------------------------------------------------
%% Tests what happens if init-callback fails.
sup_start_fail(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- {error, Term} = start_link(fail),
- check_exit_reason(Term).
+ %% The exception is processed in gen_server:init_it/2
+ ErrorReason = fail,
+ {error, {ErrorReason, _Stacktrace}} = start_link(ErrorReason),
+ check_no_exit(100).
%%-------------------------------------------------------------------------
%% Test what happens when the start function for a child returns
@@ -706,11 +711,15 @@ child_adm(Config) when is_list(Config) ->
process_flag(trap_exit, true),
Child = {child1, {supervisor_1, start_child, []}, permanent, 1000,
worker, []},
- {ok, _Pid} = start_link({ok, {{one_for_one, 2, 3600}, [Child]}}),
+ {ok, Pid} = start_link({ok, {{one_for_one, 2, 3600}, [Child]}}),
+
+ %% Test that supervisors of static nature are hibernated after start
+ {current_function, {erlang, hibernate, 3}} =
+ process_info(Pid, current_function),
+
[{child1, CPid, worker, []}] = supervisor:which_children(sup_test),
[1,1,0,1] = get_child_counts(sup_test),
link(CPid),
-
%% Start of an already runnig process
{error,{already_started, CPid}} =
supervisor:start_child(sup_test, Child),
@@ -771,7 +780,13 @@ child_adm(Config) when is_list(Config) ->
child_adm_simple(Config) when is_list(Config) ->
Child = {child, {supervisor_1, start_child, []}, permanent, 1000,
worker, []},
- {ok, _Pid} = start_link({ok, {{simple_one_for_one, 2, 3600}, [Child]}}),
+ {ok, Pid} = start_link({ok, {{simple_one_for_one, 2, 3600}, [Child]}}),
+
+ %% Test that supervisors of dynamic nature are not hibernated after start
+ {current_function, {_, Function, _}} =
+ process_info(Pid, current_function),
+ true = Function =/= hibernate,
+
%% In simple_one_for_one all children are added dynamically
[] = supervisor:which_children(sup_test),
[1,0,0,0] = get_child_counts(sup_test),
@@ -2618,7 +2633,7 @@ order_of_children(_Config) ->
[{ok,[_]} = dbg:p(P,procs) || P <- Expected1],
terminate(Pid3, abnormal),
receive {exited,ExitedPids1} ->
- dbg:stop_clear(),
+ dbg:stop(),
case ExitedPids1 of
Expected1 -> ok;
_ -> ct:fail({faulty_termination_order,
@@ -2626,7 +2641,7 @@ order_of_children(_Config) ->
{got,ExitedPids1}})
end
after 3000 ->
- dbg:stop_clear(),
+ dbg:stop(),
ct:fail({shutdown_fail,timeout})
end,
@@ -2647,7 +2662,7 @@ order_of_children(_Config) ->
[{ok,[_]} = dbg:p(P,procs) || P <- Expected2],
exit(SupPid,shutdown),
receive {exited,ExitedPids2} ->
- dbg:stop_clear(),
+ dbg:stop(),
case ExitedPids2 of
Expected2 -> ok;
_ -> ct:fail({faulty_termination_order,
@@ -2655,7 +2670,7 @@ order_of_children(_Config) ->
{got,ExitedPids2}})
end
after 3000 ->
- dbg:stop_clear(),
+ dbg:stop(),
ct:fail({shutdown_fail,timeout})
end,
ok.
@@ -3739,18 +3754,18 @@ check_exit([Pid | Pids], Timeout) ->
error
end.
-check_exit_reason(Reason) ->
+check_exit_reason(Pid, Reason) when is_pid(Pid) ->
receive
- {'EXIT', _, Reason} ->
+ {'EXIT', Pid, Reason} ->
ok;
- {'EXIT', _, Else} ->
+ {'EXIT', Pid, Else} ->
ct:fail({bad_exit_reason, Else})
end.
-check_exit_reason(Pid, Reason) ->
+check_no_exit(Timeout) ->
receive
- {'EXIT', Pid, Reason} ->
- ok;
- {'EXIT', Pid, Else} ->
- ct:fail({bad_exit_reason, Else})
+ {'EXIT', Pid, _} = Exit when is_pid(Pid) ->
+ ct:fail({unexpected_message, Exit})
+ after Timeout ->
+ ok
end.
diff --git a/lib/stdlib/test/timer_simple_SUITE.erl b/lib/stdlib/test/timer_simple_SUITE.erl
index 98a8dd408d..761689dc51 100644
--- a/lib/stdlib/test/timer_simple_SUITE.erl
+++ b/lib/stdlib/test/timer_simple_SUITE.erl
@@ -51,7 +51,11 @@
kill_after2/1,
kill_after3/1,
apply_interval1/1,
+ apply_interval2/1,
apply_interval_invalid_args/1,
+ apply_repeatedly1/1,
+ apply_repeatedly2/1,
+ apply_repeatedly_invalid_args/1,
send_interval1/1,
send_interval2/1,
send_interval3/1,
@@ -95,6 +99,7 @@ all() ->
{group, exit_after},
{group, kill_after},
{group, apply_interval},
+ {group, apply_repeatedly},
{group, send_interval},
{group, cancel},
{group, sleep},
@@ -152,10 +157,20 @@ groups() ->
[],
[
apply_interval1,
+ apply_interval2,
apply_interval_invalid_args
]
},
{
+ apply_repeatedly,
+ [],
+ [
+ apply_repeatedly1,
+ apply_repeatedly2,
+ apply_repeatedly_invalid_args
+ ]
+ },
+ {
send_interval,
[],
[
@@ -406,6 +421,23 @@ apply_interval1(Config) when is_list(Config) ->
{ok, cancel} = timer:cancel(Ref),
nor = get_mess(1000, Msg).
+%% Test apply_interval with the execution time of the action
+%% longer than the timer interval. The timer should not wait for
+%% the action to complete, ie start another action while the
+%% previously started action is still running.
+apply_interval2(Config) when is_list(Config) ->
+ Msg = make_ref(),
+ Self = self(),
+ {ok, Ref} = timer:apply_interval(500, erlang, apply,
+ [fun() ->
+ Self ! Msg,
+ receive after 1000 -> ok end
+ end, []]),
+ receive after 1800 -> ok end,
+ {ok, cancel} = timer:cancel(Ref),
+ ok = get_mess(1000, Msg, 3),
+ nor = get_mess(1000, Msg).
+
%% Test that apply_interval rejects invalid arguments.
apply_interval_invalid_args(Config) when is_list(Config) ->
{error, badarg} = timer:apply_interval(-1, foo, bar, []),
@@ -414,6 +446,44 @@ apply_interval_invalid_args(Config) when is_list(Config) ->
{error, badarg} = timer:apply_interval(0, foo, bar, baz),
ok.
+%% Test of apply_repeatedly by sending messages. Receive
+%% 3 messages, cancel the timer, and check that we do
+%% not get any more messages. In a case like this, ie where
+%% the execution time of the action is shorter than the timer
+%% interval, this should behave the same as apply_interval.
+apply_repeatedly1(Config) when is_list(Config) ->
+ Msg = make_ref(),
+ {ok, Ref} = timer:apply_repeatedly(1000, ?MODULE, send,
+ [self(), Msg]),
+ ok = get_mess(1500, Msg, 3),
+ {ok, cancel} = timer:cancel(Ref),
+ nor = get_mess(1000, Msg).
+
+%% Test apply_repeatedly with the execution time of the action
+%% longer than the timer interval. The timer should wait for
+%% the action to complete, ie not start another action until it
+%% has completed.
+apply_repeatedly2(Config) when is_list(Config) ->
+ Msg = make_ref(),
+ Self = self(),
+ {ok, Ref} = timer:apply_repeatedly(1, erlang, apply,
+ [fun() ->
+ Self ! Msg,
+ receive after 1000 -> ok end
+ end, []]),
+ receive after 2500 -> ok end,
+ {ok, cancel} = timer:cancel(Ref),
+ ok = get_mess(1000, Msg, 3),
+ nor = get_mess(1000, Msg).
+
+%% Test that apply_repeatedly rejects invalid arguments.
+apply_repeatedly_invalid_args(Config) when is_list(Config) ->
+ {error, badarg} = timer:apply_repeatedly(-1, foo, bar, []),
+ {error, badarg} = timer:apply_repeatedly(0, "foo", bar, []),
+ {error, badarg} = timer:apply_repeatedly(0, foo, "bar", []),
+ {error, badarg} = timer:apply_repeatedly(0, foo, bar, baz),
+ ok.
+
%% Test of send_interval/2. Receive 5 messages, cancel the timer, and
%% check that we do not get any more messages.
send_interval1(Config) when is_list(Config) ->
@@ -661,7 +731,19 @@ tc(Config) when is_list(Config) ->
true -> ok
end,
+ %% tc/4
+ {Res4, ok} = timer:tc(timer, sleep, [500], millisecond),
+ ok = if
+ Res4 < 500 -> {too_early, Res4};
+ Res4 > 800 -> {too_late, Res4};
+ true -> ok
+ end,
+
%% Check that timer:tc don't catch errors
+ ok = try timer:tc(erlang, exit, [foo], second)
+ catch exit:foo -> ok
+ end,
+
ok = try timer:tc(erlang, exit, [foo])
catch exit:foo -> ok
end,
@@ -676,6 +758,7 @@ tc(Config) when is_list(Config) ->
%% Check that return values are propageted
Self = self(),
+ {_, Self} = timer:tc(erlang, self, [], second),
{_, Self} = timer:tc(erlang, self, []),
{_, Self} = timer:tc(fun(P) -> P end, [self()]),
{_, Self} = timer:tc(fun() -> self() end),
diff --git a/lib/stdlib/test/unicode_util_SUITE.erl b/lib/stdlib/test/unicode_util_SUITE.erl
index e4f3e5f379..42a119fba9 100644
--- a/lib/stdlib/test/unicode_util_SUITE.erl
+++ b/lib/stdlib/test/unicode_util_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
nfd/1, nfc/1, nfkd/1, nfkc/1,
whitespace/1,
get/1,
+ lookup/1,
count/1]).
-export([debug/0, id/1, bin_split/1, uc_loaded_size/0,
@@ -45,6 +46,7 @@ all() ->
nfd, nfc, nfkd, nfkc,
whitespace,
get,
+ lookup,
count
].
@@ -90,7 +92,7 @@ casefold(_) ->
whitespace(_) ->
WS = unicode_util:whitespace(),
WS = lists:filter(fun unicode_util:is_whitespace/1, WS),
- %% TODO add more tests
+ false = unicode_util:is_whitespace($A),
ok.
cp(_) ->
@@ -101,6 +103,15 @@ cp(_) ->
"hejsan" = fetch(["hej"|<<"san">>], Get),
{error, <<128>>} = Get(<<128>>),
{error, [<<128>>, 0]} = Get([<<128>>, 0]),
+
+ {'EXIT', _} = catch Get([-1]),
+ {'EXIT', _} = catch Get([-1, $a]),
+ {'EXIT', _} = catch Get([foo, $a]),
+ {'EXIT', _} = catch Get([-1, $a]),
+ {'EXIT', _} = catch Get([[], -1]),
+ {'EXIT', _} = catch Get([[-1], $a]),
+ {'EXIT', _} = catch Get([[-1, $a], $a]),
+
ok.
gc(Config) ->
@@ -113,6 +124,15 @@ gc(Config) ->
{error, <<128>>} = Get(<<128>>),
{error, [<<128>>, 0]} = Get([<<128>>, 0]),
+ {'EXIT', _} = catch Get([-1]),
+ {'EXIT', _} = catch Get([-1, $a]),
+ {'EXIT', _} = catch Get([foo, $a]),
+ {'EXIT', _} = catch Get([-1, $a]),
+ {'EXIT', _} = catch Get([[], -1]),
+ {'EXIT', _} = catch Get([[-1], $a]),
+ {'EXIT', _} = catch Get([[-1, $a], $a]),
+ {'EXIT', _} = catch Get([<<$a>>, [-1, $a], $a]), %% Current impl
+
0 = fold(fun verify_gc/3, 0, DataDir ++ "/GraphemeBreakTest.txt"),
ok.
@@ -324,6 +344,29 @@ verify_nfkc(Data0, LineNo, _Acc) ->
get(_) ->
add_get_tests.
+lookup(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ {ok, Bin} = file:read_file(filename:join(DataDir, "unicode_table.bin")),
+ 0 = check_category(0, binary_to_term(Bin), 0),
+ ok.
+
+check_category(Id, [{Id, {_, _, _, What}}|Rest], Es) ->
+ case maps:get(category, unicode_util:lookup(Id)) of
+ What -> check_category(Id+1, Rest, Es);
+ _Err ->
+ io:format("~w Exp: ~w Got ~w~n",[Id, What, _Err]), exit(_Err),
+ check_category(Id+1, Rest, Es+1)
+ end;
+check_category(Id, [{Next,_}|_] = Rest, Es) ->
+ case maps:get(category, unicode_util:lookup(Id)) of
+ {other, not_assigned} -> check_category(max(Id+1,Next-1), Rest, Es);
+ Err -> io:format("~w Exp: {other, not_assigned} Got ~w~n",[Id,Err]),
+ check_category(max(Id+1,Next-1), Rest, Es+1)
+ end;
+check_category(_Id, [], Es) ->
+ Es.
+
+
count(Config) ->
Parent = self(),
Exec = fun() ->
diff --git a/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt b/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt
index eff2fd33b0..3c73f97b7b 100644
--- a/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt
+++ b/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt
@@ -1,11 +1,11 @@
-# GraphemeBreakTest-14.0.0.txt
-# Date: 2021-03-08, 06:22:32 GMT
-# © 2021 Unicode®, Inc.
+# GraphemeBreakTest-15.0.0.txt
+# Date: 2022-02-26, 00:38:37 GMT
+# © 2022 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
-# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For terms of use, see https://www.unicode.org/terms_of_use.html
#
# Unicode Character Database
-# For documentation, see http://www.unicode.org/reports/tr44/
+# For documentation, see https://www.unicode.org/reports/tr44/
#
# Default Grapheme_Cluster_Break Test
#
diff --git a/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt b/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt
index 8d1cef0f78..3122a2e21e 100644
--- a/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt
+++ b/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt
@@ -1,11 +1,11 @@
-# LineBreakTest-14.0.0.txt
-# Date: 2021-08-20, 21:08:45 GMT
-# © 2021 Unicode®, Inc.
+# LineBreakTest-15.0.0.txt
+# Date: 2022-02-26, 00:38:39 GMT
+# © 2022 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
-# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For terms of use, see https://www.unicode.org/terms_of_use.html
#
# Unicode Character Database
-# For documentation, see http://www.unicode.org/reports/tr44/
+# For documentation, see https://www.unicode.org/reports/tr44/
#
# Default Line_Break Test
#
diff --git a/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt b/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt
index 302c35f37c..e75b4801c9 100644
--- a/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt
+++ b/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt
@@ -1,11 +1,11 @@
-# NormalizationTest-14.0.0.txt
-# Date: 2021-05-28, 21:49:12 GMT
-# © 2021 Unicode®, Inc.
+# NormalizationTest-15.0.0.txt
+# Date: 2022-04-02, 01:29:09 GMT
+# © 2022 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
-# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For terms of use, see https://www.unicode.org/terms_of_use.html
#
# Unicode Character Database
-# For documentation, see http://www.unicode.org/reports/tr44/
+# For documentation, see https://www.unicode.org/reports/tr44/
#
# Normalization Test Suite
# Format:
@@ -16208,6 +16208,68 @@ FFEE;FFEE;FFEE;25CB;25CB; # (ï¿®; ï¿®; ï¿®; â—‹; â—‹; ) HALFWIDTH WHITE CIRCLE
1D7FD;1D7FD;1D7FD;0037;0037; # (ðŸ½; ðŸ½; ðŸ½; 7; 7; ) MATHEMATICAL MONOSPACE DIGIT SEVEN
1D7FE;1D7FE;1D7FE;0038;0038; # (ðŸ¾; ðŸ¾; ðŸ¾; 8; 8; ) MATHEMATICAL MONOSPACE DIGIT EIGHT
1D7FF;1D7FF;1D7FF;0039;0039; # (ðŸ¿; ðŸ¿; ðŸ¿; 9; 9; ) MATHEMATICAL MONOSPACE DIGIT NINE
+1E030;1E030;1E030;0430;0430; # (𞀰; 𞀰; 𞀰; а; а; ) MODIFIER LETTER CYRILLIC SMALL A
+1E031;1E031;1E031;0431;0431; # (𞀱; 𞀱; 𞀱; б; б; ) MODIFIER LETTER CYRILLIC SMALL BE
+1E032;1E032;1E032;0432;0432; # (𞀲; 𞀲; 𞀲; в; в; ) MODIFIER LETTER CYRILLIC SMALL VE
+1E033;1E033;1E033;0433;0433; # (𞀳; 𞀳; 𞀳; г; г; ) MODIFIER LETTER CYRILLIC SMALL GHE
+1E034;1E034;1E034;0434;0434; # (𞀴; 𞀴; 𞀴; д; д; ) MODIFIER LETTER CYRILLIC SMALL DE
+1E035;1E035;1E035;0435;0435; # (𞀵; 𞀵; 𞀵; е; е; ) MODIFIER LETTER CYRILLIC SMALL IE
+1E036;1E036;1E036;0436;0436; # (𞀶; 𞀶; 𞀶; ж; ж; ) MODIFIER LETTER CYRILLIC SMALL ZHE
+1E037;1E037;1E037;0437;0437; # (𞀷; 𞀷; 𞀷; з; з; ) MODIFIER LETTER CYRILLIC SMALL ZE
+1E038;1E038;1E038;0438;0438; # (𞀸; 𞀸; 𞀸; и; и; ) MODIFIER LETTER CYRILLIC SMALL I
+1E039;1E039;1E039;043A;043A; # (𞀹; 𞀹; 𞀹; к; к; ) MODIFIER LETTER CYRILLIC SMALL KA
+1E03A;1E03A;1E03A;043B;043B; # (𞀺; 𞀺; 𞀺; л; л; ) MODIFIER LETTER CYRILLIC SMALL EL
+1E03B;1E03B;1E03B;043C;043C; # (𞀻; 𞀻; 𞀻; м; м; ) MODIFIER LETTER CYRILLIC SMALL EM
+1E03C;1E03C;1E03C;043E;043E; # (𞀼; 𞀼; 𞀼; о; о; ) MODIFIER LETTER CYRILLIC SMALL O
+1E03D;1E03D;1E03D;043F;043F; # (𞀽; 𞀽; 𞀽; п; п; ) MODIFIER LETTER CYRILLIC SMALL PE
+1E03E;1E03E;1E03E;0440;0440; # (𞀾; 𞀾; 𞀾; р; р; ) MODIFIER LETTER CYRILLIC SMALL ER
+1E03F;1E03F;1E03F;0441;0441; # (𞀿; 𞀿; 𞀿; Ñ; Ñ; ) MODIFIER LETTER CYRILLIC SMALL ES
+1E040;1E040;1E040;0442;0442; # (ðž€; ðž€; ðž€; Ñ‚; Ñ‚; ) MODIFIER LETTER CYRILLIC SMALL TE
+1E041;1E041;1E041;0443;0443; # (ðž; ðž; ðž; у; у; ) MODIFIER LETTER CYRILLIC SMALL U
+1E042;1E042;1E042;0444;0444; # (ðž‚; ðž‚; ðž‚; Ñ„; Ñ„; ) MODIFIER LETTER CYRILLIC SMALL EF
+1E043;1E043;1E043;0445;0445; # (ðžƒ; ðžƒ; ðžƒ; Ñ…; Ñ…; ) MODIFIER LETTER CYRILLIC SMALL HA
+1E044;1E044;1E044;0446;0446; # (ðž„; ðž„; ðž„; ц; ц; ) MODIFIER LETTER CYRILLIC SMALL TSE
+1E045;1E045;1E045;0447;0447; # (ðž…; ðž…; ðž…; ч; ч; ) MODIFIER LETTER CYRILLIC SMALL CHE
+1E046;1E046;1E046;0448;0448; # (ðž†; ðž†; ðž†; ш; ш; ) MODIFIER LETTER CYRILLIC SMALL SHA
+1E047;1E047;1E047;044B;044B; # (ðž‡; ðž‡; ðž‡; Ñ‹; Ñ‹; ) MODIFIER LETTER CYRILLIC SMALL YERU
+1E048;1E048;1E048;044D;044D; # (ðžˆ; ðžˆ; ðžˆ; Ñ; Ñ; ) MODIFIER LETTER CYRILLIC SMALL E
+1E049;1E049;1E049;044E;044E; # (ðž‰; ðž‰; ðž‰; ÑŽ; ÑŽ; ) MODIFIER LETTER CYRILLIC SMALL YU
+1E04A;1E04A;1E04A;A689;A689; # (ðžŠ; ðžŠ; ðžŠ; ꚉ; ꚉ; ) MODIFIER LETTER CYRILLIC SMALL DZZE
+1E04B;1E04B;1E04B;04D9;04D9; # (ðž‹; ðž‹; ðž‹; Ó™; Ó™; ) MODIFIER LETTER CYRILLIC SMALL SCHWA
+1E04C;1E04C;1E04C;0456;0456; # (ðžŒ; ðžŒ; ðžŒ; Ñ–; Ñ–; ) MODIFIER LETTER CYRILLIC SMALL BYELORUSSIAN-UKRAINIAN I
+1E04D;1E04D;1E04D;0458;0458; # (ðž; ðž; ðž; ј; ј; ) MODIFIER LETTER CYRILLIC SMALL JE
+1E04E;1E04E;1E04E;04E9;04E9; # (ðžŽ; ðžŽ; ðžŽ; Ó©; Ó©; ) MODIFIER LETTER CYRILLIC SMALL BARRED O
+1E04F;1E04F;1E04F;04AF;04AF; # (ðž; ðž; ðž; Ò¯; Ò¯; ) MODIFIER LETTER CYRILLIC SMALL STRAIGHT U
+1E050;1E050;1E050;04CF;04CF; # (ðž; ðž; ðž; Ó; Ó; ) MODIFIER LETTER CYRILLIC SMALL PALOCHKA
+1E051;1E051;1E051;0430;0430; # (ðž‘; ðž‘; ðž‘; а; а; ) CYRILLIC SUBSCRIPT SMALL LETTER A
+1E052;1E052;1E052;0431;0431; # (ðž’; ðž’; ðž’; б; б; ) CYRILLIC SUBSCRIPT SMALL LETTER BE
+1E053;1E053;1E053;0432;0432; # (ðž“; ðž“; ðž“; в; в; ) CYRILLIC SUBSCRIPT SMALL LETTER VE
+1E054;1E054;1E054;0433;0433; # (ðž”; ðž”; ðž”; г; г; ) CYRILLIC SUBSCRIPT SMALL LETTER GHE
+1E055;1E055;1E055;0434;0434; # (ðž•; ðž•; ðž•; д; д; ) CYRILLIC SUBSCRIPT SMALL LETTER DE
+1E056;1E056;1E056;0435;0435; # (ðž–; ðž–; ðž–; е; е; ) CYRILLIC SUBSCRIPT SMALL LETTER IE
+1E057;1E057;1E057;0436;0436; # (ðž—; ðž—; ðž—; ж; ж; ) CYRILLIC SUBSCRIPT SMALL LETTER ZHE
+1E058;1E058;1E058;0437;0437; # (ðž˜; ðž˜; ðž˜; з; з; ) CYRILLIC SUBSCRIPT SMALL LETTER ZE
+1E059;1E059;1E059;0438;0438; # (ðž™; ðž™; ðž™; и; и; ) CYRILLIC SUBSCRIPT SMALL LETTER I
+1E05A;1E05A;1E05A;043A;043A; # (ðžš; ðžš; ðžš; к; к; ) CYRILLIC SUBSCRIPT SMALL LETTER KA
+1E05B;1E05B;1E05B;043B;043B; # (ðž›; ðž›; ðž›; л; л; ) CYRILLIC SUBSCRIPT SMALL LETTER EL
+1E05C;1E05C;1E05C;043E;043E; # (ðžœ; ðžœ; ðžœ; о; о; ) CYRILLIC SUBSCRIPT SMALL LETTER O
+1E05D;1E05D;1E05D;043F;043F; # (ðž; ðž; ðž; п; п; ) CYRILLIC SUBSCRIPT SMALL LETTER PE
+1E05E;1E05E;1E05E;0441;0441; # (ðžž; ðžž; ðžž; Ñ; Ñ; ) CYRILLIC SUBSCRIPT SMALL LETTER ES
+1E05F;1E05F;1E05F;0443;0443; # (ðžŸ; ðžŸ; ðžŸ; у; у; ) CYRILLIC SUBSCRIPT SMALL LETTER U
+1E060;1E060;1E060;0444;0444; # (ðž ; ðž ; ðž ; Ñ„; Ñ„; ) CYRILLIC SUBSCRIPT SMALL LETTER EF
+1E061;1E061;1E061;0445;0445; # (ðž¡; ðž¡; ðž¡; Ñ…; Ñ…; ) CYRILLIC SUBSCRIPT SMALL LETTER HA
+1E062;1E062;1E062;0446;0446; # (ðž¢; ðž¢; ðž¢; ц; ц; ) CYRILLIC SUBSCRIPT SMALL LETTER TSE
+1E063;1E063;1E063;0447;0447; # (ðž£; ðž£; ðž£; ч; ч; ) CYRILLIC SUBSCRIPT SMALL LETTER CHE
+1E064;1E064;1E064;0448;0448; # (ðž¤; ðž¤; ðž¤; ш; ш; ) CYRILLIC SUBSCRIPT SMALL LETTER SHA
+1E065;1E065;1E065;044A;044A; # (ðž¥; ðž¥; ðž¥; ÑŠ; ÑŠ; ) CYRILLIC SUBSCRIPT SMALL LETTER HARD SIGN
+1E066;1E066;1E066;044B;044B; # (ðž¦; ðž¦; ðž¦; Ñ‹; Ñ‹; ) CYRILLIC SUBSCRIPT SMALL LETTER YERU
+1E067;1E067;1E067;0491;0491; # (ðž§; ðž§; ðž§; Ò‘; Ò‘; ) CYRILLIC SUBSCRIPT SMALL LETTER GHE WITH UPTURN
+1E068;1E068;1E068;0456;0456; # (ðž¨; ðž¨; ðž¨; Ñ–; Ñ–; ) CYRILLIC SUBSCRIPT SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+1E069;1E069;1E069;0455;0455; # (ðž©; ðž©; ðž©; Ñ•; Ñ•; ) CYRILLIC SUBSCRIPT SMALL LETTER DZE
+1E06A;1E06A;1E06A;045F;045F; # (ðžª; ðžª; ðžª; ÑŸ; ÑŸ; ) CYRILLIC SUBSCRIPT SMALL LETTER DZHE
+1E06B;1E06B;1E06B;04AB;04AB; # (ðž«; ðž«; ðž«; Ò«; Ò«; ) MODIFIER LETTER CYRILLIC SMALL ES WITH DESCENDER
+1E06C;1E06C;1E06C;A651;A651; # (ðž¬; ðž¬; ðž¬; ꙑ; ꙑ; ) MODIFIER LETTER CYRILLIC SMALL YERU WITH BACK YER
+1E06D;1E06D;1E06D;04B1;04B1; # (ðž­; ðž­; ðž­; Ò±; Ò±; ) MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
1EE00;1EE00;1EE00;0627;0627; # (𞸀; 𞸀; 𞸀; ا; ا; ) ARABIC MATHEMATICAL ALEF
1EE01;1EE01;1EE01;0628;0628; # (ðž¸; ðž¸; ðž¸; ب; ب; ) ARABIC MATHEMATICAL BEH
1EE02;1EE02;1EE02;062C;062C; # (𞸂; 𞸂; 𞸂; ج; ج; ) ARABIC MATHEMATICAL JEEM
@@ -18496,6 +18558,12 @@ FFEE;FFEE;FFEE;25CB;25CB; # (ï¿®; ï¿®; ï¿®; â—‹; â—‹; ) HALFWIDTH WHITE CIRCLE
0061 10EAB 0315 0300 05AE 0062;0061 05AE 10EAB 0300 0315 0062;0061 05AE 10EAB 0300 0315 0062;0061 05AE 10EAB 0300 0315 0062;0061 05AE 10EAB 0300 0315 0062; # (aâ—Œðº«â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ðº«â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº«â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº«â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº«â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, YEZIDI COMBINING HAMZA MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
0061 0315 0300 05AE 10EAC 0062;00E0 05AE 10EAC 0315 0062;0061 05AE 0300 10EAC 0315 0062;00E0 05AE 10EAC 0315 0062;0061 05AE 0300 10EAC 0315 0062; # (a◌̕◌̀◌֮◌ðº¬b; à◌֮◌ðº¬â—ŒÌ•b; a◌֮◌̀◌ðº¬â—ŒÌ•b; à◌֮◌ðº¬â—ŒÌ•b; a◌֮◌̀◌ðº¬â—ŒÌ•b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, YEZIDI COMBINING MADDA MARK, LATIN SMALL LETTER B
0061 10EAC 0315 0300 05AE 0062;0061 05AE 10EAC 0300 0315 0062;0061 05AE 10EAC 0300 0315 0062;0061 05AE 10EAC 0300 0315 0062;0061 05AE 10EAC 0300 0315 0062; # (aâ—Œðº¬â—ŒÌ•â—ŒÌ€â—ŒÖ®b; a◌֮◌ðº¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº¬â—ŒÌ€â—ŒÌ•b; a◌֮◌ðº¬â—ŒÌ€â—ŒÌ•b; ) LATIN SMALL LETTER A, YEZIDI COMBINING MADDA MARK, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10EFD 0062;0061 1DFA 0316 10EFD 059A 0062;0061 1DFA 0316 10EFD 059A 0062;0061 1DFA 0316 10EFD 059A 0062;0061 1DFA 0316 10EFD 059A 0062; # (a◌֚◌̖◌᷺◌ð»½b; a◌᷺◌̖◌ð»½â—ŒÖšb; a◌᷺◌̖◌ð»½â—ŒÖšb; a◌᷺◌̖◌ð»½â—ŒÖšb; a◌᷺◌̖◌ð»½â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SMALL LOW WORD SAKTA, LATIN SMALL LETTER B
+0061 10EFD 059A 0316 1DFA 0062;0061 1DFA 10EFD 0316 059A 0062;0061 1DFA 10EFD 0316 059A 0062;0061 1DFA 10EFD 0316 059A 0062;0061 1DFA 10EFD 0316 059A 0062; # (aâ—Œð»½â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð»½â—ŒÌ–◌֚b; a◌᷺◌ð»½â—ŒÌ–◌֚b; a◌᷺◌ð»½â—ŒÌ–◌֚b; a◌᷺◌ð»½â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, ARABIC SMALL LOW WORD SAKTA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10EFE 0062;0061 1DFA 0316 10EFE 059A 0062;0061 1DFA 0316 10EFE 059A 0062;0061 1DFA 0316 10EFE 059A 0062;0061 1DFA 0316 10EFE 059A 0062; # (a◌֚◌̖◌᷺◌ð»¾b; a◌᷺◌̖◌ð»¾â—ŒÖšb; a◌᷺◌̖◌ð»¾â—ŒÖšb; a◌᷺◌̖◌ð»¾â—ŒÖšb; a◌᷺◌̖◌ð»¾â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SMALL LOW WORD QASR, LATIN SMALL LETTER B
+0061 10EFE 059A 0316 1DFA 0062;0061 1DFA 10EFE 0316 059A 0062;0061 1DFA 10EFE 0316 059A 0062;0061 1DFA 10EFE 0316 059A 0062;0061 1DFA 10EFE 0316 059A 0062; # (aâ—Œð»¾â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð»¾â—ŒÌ–◌֚b; a◌᷺◌ð»¾â—ŒÌ–◌֚b; a◌᷺◌ð»¾â—ŒÌ–◌֚b; a◌᷺◌ð»¾â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, ARABIC SMALL LOW WORD QASR, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 10EFF 0062;0061 1DFA 0316 10EFF 059A 0062;0061 1DFA 0316 10EFF 059A 0062;0061 1DFA 0316 10EFF 059A 0062;0061 1DFA 0316 10EFF 059A 0062; # (a◌֚◌̖◌᷺◌ð»¿b; a◌᷺◌̖◌ð»¿â—ŒÖšb; a◌᷺◌̖◌ð»¿â—ŒÖšb; a◌᷺◌̖◌ð»¿â—ŒÖšb; a◌᷺◌̖◌ð»¿â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, ARABIC SMALL LOW WORD MADDA, LATIN SMALL LETTER B
+0061 10EFF 059A 0316 1DFA 0062;0061 1DFA 10EFF 0316 059A 0062;0061 1DFA 10EFF 0316 059A 0062;0061 1DFA 10EFF 0316 059A 0062;0061 1DFA 10EFF 0316 059A 0062; # (aâ—Œð»¿â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð»¿â—ŒÌ–◌֚b; a◌᷺◌ð»¿â—ŒÌ–◌֚b; a◌᷺◌ð»¿â—ŒÌ–◌֚b; a◌᷺◌ð»¿â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, ARABIC SMALL LOW WORD MADDA, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
0061 059A 0316 1DFA 10F46 0062;0061 1DFA 0316 10F46 059A 0062;0061 1DFA 0316 10F46 059A 0062;0061 1DFA 0316 10F46 059A 0062;0061 1DFA 0316 10F46 059A 0062; # (a◌֚◌̖◌᷺◌ð½†b; a◌᷺◌̖◌ð½†â—ŒÖšb; a◌᷺◌̖◌ð½†â—ŒÖšb; a◌᷺◌̖◌ð½†â—ŒÖšb; a◌᷺◌̖◌ð½†â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SOGDIAN COMBINING DOT BELOW, LATIN SMALL LETTER B
0061 10F46 059A 0316 1DFA 0062;0061 1DFA 10F46 0316 059A 0062;0061 1DFA 10F46 0316 059A 0062;0061 1DFA 10F46 0316 059A 0062;0061 1DFA 10F46 0316 059A 0062; # (aâ—Œð½†â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ð½†â—ŒÌ–◌֚b; a◌᷺◌ð½†â—ŒÌ–◌֚b; a◌᷺◌ð½†â—ŒÌ–◌֚b; a◌᷺◌ð½†â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, SOGDIAN COMBINING DOT BELOW, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
0061 059A 0316 1DFA 10F47 0062;0061 1DFA 0316 10F47 059A 0062;0061 1DFA 0316 10F47 059A 0062;0061 1DFA 0316 10F47 059A 0062;0061 1DFA 0316 10F47 059A 0062; # (a◌֚◌̖◌᷺◌ð½‡b; a◌᷺◌̖◌ð½‡â—ŒÖšb; a◌᷺◌̖◌ð½‡â—ŒÖšb; a◌᷺◌̖◌ð½‡â—ŒÖšb; a◌᷺◌̖◌ð½‡â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, SOGDIAN COMBINING TWO DOTS BELOW, LATIN SMALL LETTER B
@@ -18640,6 +18708,10 @@ FFEE;FFEE;FFEE;25CB;25CB; # (ï¿®; ï¿®; ï¿®; â—‹; â—‹; ) HALFWIDTH WHITE CIRCLE
0061 11D45 05B0 094D 3099 0062;0061 3099 11D45 094D 05B0 0062;0061 3099 11D45 094D 05B0 0062;0061 3099 11D45 094D 05B0 0062;0061 3099 11D45 094D 05B0 0062; # (a◌𑵅◌ְ◌à¥â—Œã‚™b; a◌゙◌𑵅◌à¥â—ŒÖ°b; a◌゙◌𑵅◌à¥â—ŒÖ°b; a◌゙◌𑵅◌à¥â—ŒÖ°b; a◌゙◌𑵅◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, MASARAM GONDI VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
0061 05B0 094D 3099 11D97 0062;0061 3099 094D 11D97 05B0 0062;0061 3099 094D 11D97 05B0 0062;0061 3099 094D 11D97 05B0 0062;0061 3099 094D 11D97 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘¶—b; a◌゙◌à¥â—Œð‘¶—◌ְb; a◌゙◌à¥â—Œð‘¶—◌ְb; a◌゙◌à¥â—Œð‘¶—◌ְb; a◌゙◌à¥â—Œð‘¶—◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, GUNJALA GONDI VIRAMA, LATIN SMALL LETTER B
0061 11D97 05B0 094D 3099 0062;0061 3099 11D97 094D 05B0 0062;0061 3099 11D97 094D 05B0 0062;0061 3099 11D97 094D 05B0 0062;0061 3099 11D97 094D 05B0 0062; # (a◌𑶗◌ְ◌à¥â—Œã‚™b; a◌゙◌𑶗◌à¥â—ŒÖ°b; a◌゙◌𑶗◌à¥â—ŒÖ°b; a◌゙◌𑶗◌à¥â—ŒÖ°b; a◌゙◌𑶗◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, GUNJALA GONDI VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11F41 0062;0061 3099 094D 11F41 05B0 0062;0061 3099 094D 11F41 05B0 0062;0061 3099 094D 11F41 05B0 0062;0061 3099 094D 11F41 05B0 0062; # (a◌ְ◌à¥â—Œã‚™ð‘½b; a◌゙◌à¥ð‘½â—ŒÖ°b; a◌゙◌à¥ð‘½â—ŒÖ°b; a◌゙◌à¥ð‘½â—ŒÖ°b; a◌゙◌à¥ð‘½â—ŒÖ°b; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, KAWI SIGN KILLER, LATIN SMALL LETTER B
+0061 11F41 05B0 094D 3099 0062;0061 3099 11F41 094D 05B0 0062;0061 3099 11F41 094D 05B0 0062;0061 3099 11F41 094D 05B0 0062;0061 3099 11F41 094D 05B0 0062; # (að‘½â—ŒÖ°â—Œà¥â—Œã‚™b; a◌゙ð‘½â—Œà¥â—ŒÖ°b; a◌゙ð‘½â—Œà¥â—ŒÖ°b; a◌゙ð‘½â—Œà¥â—ŒÖ°b; a◌゙ð‘½â—Œà¥â—ŒÖ°b; ) LATIN SMALL LETTER A, KAWI SIGN KILLER, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 11F42 0062;0061 3099 094D 11F42 05B0 0062;0061 3099 094D 11F42 05B0 0062;0061 3099 094D 11F42 05B0 0062;0061 3099 094D 11F42 05B0 0062; # (a◌ְ◌à¥â—Œã‚™â—Œð‘½‚b; a◌゙◌à¥â—Œð‘½‚◌ְb; a◌゙◌à¥â—Œð‘½‚◌ְb; a◌゙◌à¥â—Œð‘½‚◌ְb; a◌゙◌à¥â—Œð‘½‚◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, KAWI CONJOINER, LATIN SMALL LETTER B
+0061 11F42 05B0 094D 3099 0062;0061 3099 11F42 094D 05B0 0062;0061 3099 11F42 094D 05B0 0062;0061 3099 11F42 094D 05B0 0062;0061 3099 11F42 094D 05B0 0062; # (a◌𑽂◌ְ◌à¥â—Œã‚™b; a◌゙◌𑽂◌à¥â—ŒÖ°b; a◌゙◌𑽂◌à¥â—ŒÖ°b; a◌゙◌𑽂◌à¥â—ŒÖ°b; a◌゙◌𑽂◌à¥â—ŒÖ°b; ) LATIN SMALL LETTER A, KAWI CONJOINER, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
0061 16FF0 0334 16AF0 0062;0061 0334 16AF0 16FF0 0062;0061 0334 16AF0 16FF0 0062;0061 0334 16AF0 16FF0 0062;0061 0334 16AF0 16FF0 0062; # (a𖿰◌̴◌𖫰b; a◌̴◌𖫰𖿰b; a◌̴◌𖫰𖿰b; a◌̴◌𖫰𖿰b; a◌̴◌𖫰𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, BASSA VAH COMBINING HIGH TONE, LATIN SMALL LETTER B
0061 16AF0 16FF0 0334 0062;0061 16AF0 0334 16FF0 0062;0061 16AF0 0334 16FF0 0062;0061 16AF0 0334 16FF0 0062;0061 16AF0 0334 16FF0 0062; # (a◌𖫰𖿰◌̴b; a◌𖫰◌̴𖿰b; a◌𖫰◌̴𖿰b; a◌𖫰◌̴𖿰b; a◌𖫰◌̴𖿰b; ) LATIN SMALL LETTER A, BASSA VAH COMBINING HIGH TONE, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
0061 16FF0 0334 16AF1 0062;0061 0334 16AF1 16FF0 0062;0061 0334 16AF1 16FF0 0062;0061 0334 16AF1 16FF0 0062;0061 0334 16AF1 16FF0 0062; # (a𖿰◌̴◌𖫱b; a◌̴◌𖫱𖿰b; a◌̴◌𖫱𖿰b; a◌̴◌𖫱𖿰b; a◌̴◌𖫱𖿰b; ) LATIN SMALL LETTER A, VIETNAMESE ALTERNATE READING MARK CA, COMBINING TILDE OVERLAY, BASSA VAH COMBINING LOW TONE, LATIN SMALL LETTER B
@@ -18812,6 +18884,8 @@ FFEE;FFEE;FFEE;25CB;25CB; # (ï¿®; ï¿®; ï¿®; â—‹; â—‹; ) HALFWIDTH WHITE CIRCLE
0061 1E029 0315 0300 05AE 0062;0061 05AE 1E029 0300 0315 0062;0061 05AE 1E029 0300 0315 0062;0061 05AE 1E029 0300 0315 0062;0061 05AE 1E029 0300 0315 0062; # (a◌𞀩◌̕◌̀◌֮b; a◌֮◌𞀩◌̀◌̕b; a◌֮◌𞀩◌̀◌̕b; a◌֮◌𞀩◌̀◌̕b; a◌֮◌𞀩◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER IOTATED BIG YUS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
0061 0315 0300 05AE 1E02A 0062;00E0 05AE 1E02A 0315 0062;0061 05AE 0300 1E02A 0315 0062;00E0 05AE 1E02A 0315 0062;0061 05AE 0300 1E02A 0315 0062; # (a◌̕◌̀◌֮◌𞀪b; à◌֮◌𞀪◌̕b; a◌֮◌̀◌𞀪◌̕b; à◌֮◌𞀪◌̕b; a◌֮◌̀◌𞀪◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER FITA, LATIN SMALL LETTER B
0061 1E02A 0315 0300 05AE 0062;0061 05AE 1E02A 0300 0315 0062;0061 05AE 1E02A 0300 0315 0062;0061 05AE 1E02A 0300 0315 0062;0061 05AE 1E02A 0300 0315 0062; # (a◌𞀪◌̕◌̀◌֮b; a◌֮◌𞀪◌̀◌̕b; a◌֮◌𞀪◌̀◌̕b; a◌֮◌𞀪◌̀◌̕b; a◌֮◌𞀪◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER FITA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E08F 0062;00E0 05AE 1E08F 0315 0062;0061 05AE 0300 1E08F 0315 0062;00E0 05AE 1E08F 0315 0062;0061 05AE 0300 1E08F 0315 0062; # (a◌̕◌̀◌֮◌ðž‚b; à◌֮◌ðž‚◌̕b; a◌֮◌̀◌ðž‚◌̕b; à◌֮◌ðž‚◌̕b; a◌֮◌̀◌ðž‚◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I, LATIN SMALL LETTER B
+0061 1E08F 0315 0300 05AE 0062;0061 05AE 1E08F 0300 0315 0062;0061 05AE 1E08F 0300 0315 0062;0061 05AE 1E08F 0300 0315 0062;0061 05AE 1E08F 0300 0315 0062; # (aâ—Œðž‚◌̕◌̀◌֮b; a◌֮◌ðž‚◌̀◌̕b; a◌֮◌ðž‚◌̀◌̕b; a◌֮◌ðž‚◌̀◌̕b; a◌֮◌ðž‚◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
0061 0315 0300 05AE 1E130 0062;00E0 05AE 1E130 0315 0062;0061 05AE 0300 1E130 0315 0062;00E0 05AE 1E130 0315 0062;0061 05AE 0300 1E130 0315 0062; # (a◌̕◌̀◌֮◌𞄰b; à◌֮◌𞄰◌̕b; a◌֮◌̀◌𞄰◌̕b; à◌֮◌𞄰◌̕b; a◌֮◌̀◌𞄰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-B, LATIN SMALL LETTER B
0061 1E130 0315 0300 05AE 0062;0061 05AE 1E130 0300 0315 0062;0061 05AE 1E130 0300 0315 0062;0061 05AE 1E130 0300 0315 0062;0061 05AE 1E130 0300 0315 0062; # (a◌𞄰◌̕◌̀◌֮b; a◌֮◌𞄰◌̀◌̕b; a◌֮◌𞄰◌̀◌̕b; a◌֮◌𞄰◌̀◌̕b; a◌֮◌𞄰◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-B, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
0061 0315 0300 05AE 1E131 0062;00E0 05AE 1E131 0315 0062;0061 05AE 0300 1E131 0315 0062;00E0 05AE 1E131 0315 0062;0061 05AE 0300 1E131 0315 0062; # (a◌̕◌̀◌֮◌𞄱b; à◌֮◌𞄱◌̕b; a◌֮◌̀◌𞄱◌̕b; à◌֮◌𞄱◌̕b; a◌֮◌̀◌𞄱◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-M, LATIN SMALL LETTER B
@@ -18836,6 +18910,14 @@ FFEE;FFEE;FFEE;25CB;25CB; # (ï¿®; ï¿®; ï¿®; â—‹; â—‹; ) HALFWIDTH WHITE CIRCLE
0061 1E2EE 0315 0300 05AE 0062;0061 05AE 1E2EE 0300 0315 0062;0061 05AE 1E2EE 0300 0315 0062;0061 05AE 1E2EE 0300 0315 0062;0061 05AE 1E2EE 0300 0315 0062; # (a◌𞋮◌̕◌̀◌֮b; a◌֮◌𞋮◌̀◌̕b; a◌֮◌𞋮◌̀◌̕b; a◌֮◌𞋮◌̀◌̕b; a◌֮◌𞋮◌̀◌̕b; ) LATIN SMALL LETTER A, WANCHO TONE KOI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
0061 0315 0300 05AE 1E2EF 0062;00E0 05AE 1E2EF 0315 0062;0061 05AE 0300 1E2EF 0315 0062;00E0 05AE 1E2EF 0315 0062;0061 05AE 0300 1E2EF 0315 0062; # (a◌̕◌̀◌֮◌𞋯b; à◌֮◌𞋯◌̕b; a◌֮◌̀◌𞋯◌̕b; à◌֮◌𞋯◌̕b; a◌֮◌̀◌𞋯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, WANCHO TONE KOINI, LATIN SMALL LETTER B
0061 1E2EF 0315 0300 05AE 0062;0061 05AE 1E2EF 0300 0315 0062;0061 05AE 1E2EF 0300 0315 0062;0061 05AE 1E2EF 0300 0315 0062;0061 05AE 1E2EF 0300 0315 0062; # (a◌𞋯◌̕◌̀◌֮b; a◌֮◌𞋯◌̀◌̕b; a◌֮◌𞋯◌̀◌̕b; a◌֮◌𞋯◌̀◌̕b; a◌֮◌𞋯◌̀◌̕b; ) LATIN SMALL LETTER A, WANCHO TONE KOINI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 035C 0315 0300 1E4EC 0062;00E0 0315 1E4EC 035C 0062;0061 0300 0315 1E4EC 035C 0062;00E0 0315 1E4EC 035C 0062;0061 0300 0315 1E4EC 035C 0062; # (a◌͜◌̕◌̀◌𞓬b; à◌̕◌𞓬◌͜b; a◌̀◌̕◌𞓬◌͜b; à◌̕◌𞓬◌͜b; a◌̀◌̕◌𞓬◌͜b; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, NAG MUNDARI SIGN MUHOR, LATIN SMALL LETTER B
+0061 1E4EC 035C 0315 0300 0062;00E0 1E4EC 0315 035C 0062;0061 0300 1E4EC 0315 035C 0062;00E0 1E4EC 0315 035C 0062;0061 0300 1E4EC 0315 035C 0062; # (a◌𞓬◌͜◌̕◌̀b; à◌𞓬◌̕◌͜b; a◌̀◌𞓬◌̕◌͜b; à◌𞓬◌̕◌͜b; a◌̀◌𞓬◌̕◌͜b; ) LATIN SMALL LETTER A, NAG MUNDARI SIGN MUHOR, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, LATIN SMALL LETTER B
+0061 035C 0315 0300 1E4ED 0062;00E0 0315 1E4ED 035C 0062;0061 0300 0315 1E4ED 035C 0062;00E0 0315 1E4ED 035C 0062;0061 0300 0315 1E4ED 035C 0062; # (a◌͜◌̕◌̀◌𞓭b; à◌̕◌𞓭◌͜b; a◌̀◌̕◌𞓭◌͜b; à◌̕◌𞓭◌͜b; a◌̀◌̕◌𞓭◌͜b; ) LATIN SMALL LETTER A, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, NAG MUNDARI SIGN TOYOR, LATIN SMALL LETTER B
+0061 1E4ED 035C 0315 0300 0062;00E0 1E4ED 0315 035C 0062;0061 0300 1E4ED 0315 035C 0062;00E0 1E4ED 0315 035C 0062;0061 0300 1E4ED 0315 035C 0062; # (a◌𞓭◌͜◌̕◌̀b; à◌𞓭◌̕◌͜b; a◌̀◌𞓭◌̕◌͜b; à◌𞓭◌̕◌͜b; a◌̀◌𞓭◌̕◌͜b; ) LATIN SMALL LETTER A, NAG MUNDARI SIGN TOYOR, COMBINING DOUBLE BREVE BELOW, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, LATIN SMALL LETTER B
+0061 059A 0316 1DFA 1E4EE 0062;0061 1DFA 0316 1E4EE 059A 0062;0061 1DFA 0316 1E4EE 059A 0062;0061 1DFA 0316 1E4EE 059A 0062;0061 1DFA 0316 1E4EE 059A 0062; # (a◌֚◌̖◌᷺◌𞓮b; a◌᷺◌̖◌𞓮◌֚b; a◌᷺◌̖◌𞓮◌֚b; a◌᷺◌̖◌𞓮◌֚b; a◌᷺◌̖◌𞓮◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, NAG MUNDARI SIGN IKIR, LATIN SMALL LETTER B
+0061 1E4EE 059A 0316 1DFA 0062;0061 1DFA 1E4EE 0316 059A 0062;0061 1DFA 1E4EE 0316 059A 0062;0061 1DFA 1E4EE 0316 059A 0062;0061 1DFA 1E4EE 0316 059A 0062; # (a◌𞓮◌֚◌̖◌᷺b; a◌᷺◌𞓮◌̖◌֚b; a◌᷺◌𞓮◌̖◌֚b; a◌᷺◌𞓮◌̖◌֚b; a◌᷺◌𞓮◌̖◌֚b; ) LATIN SMALL LETTER A, NAG MUNDARI SIGN IKIR, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E4EF 0062;00E0 05AE 1E4EF 0315 0062;0061 05AE 0300 1E4EF 0315 0062;00E0 05AE 1E4EF 0315 0062;0061 05AE 0300 1E4EF 0315 0062; # (a◌̕◌̀◌֮◌𞓯b; à◌֮◌𞓯◌̕b; a◌֮◌̀◌𞓯◌̕b; à◌֮◌𞓯◌̕b; a◌֮◌̀◌𞓯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NAG MUNDARI SIGN SUTUH, LATIN SMALL LETTER B
+0061 1E4EF 0315 0300 05AE 0062;0061 05AE 1E4EF 0300 0315 0062;0061 05AE 1E4EF 0300 0315 0062;0061 05AE 1E4EF 0300 0315 0062;0061 05AE 1E4EF 0300 0315 0062; # (a◌𞓯◌̕◌̀◌֮b; a◌֮◌𞓯◌̀◌̕b; a◌֮◌𞓯◌̀◌̕b; a◌֮◌𞓯◌̀◌̕b; a◌֮◌𞓯◌̀◌̕b; ) LATIN SMALL LETTER A, NAG MUNDARI SIGN SUTUH, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
0061 059A 0316 1DFA 1E8D0 0062;0061 1DFA 0316 1E8D0 059A 0062;0061 1DFA 0316 1E8D0 059A 0062;0061 1DFA 0316 1E8D0 059A 0062;0061 1DFA 0316 1E8D0 059A 0062; # (a◌֚◌̖◌᷺◌ðž£b; a◌᷺◌̖◌ðž£â—ŒÖšb; a◌᷺◌̖◌ðž£â—ŒÖšb; a◌᷺◌̖◌ðž£â—ŒÖšb; a◌᷺◌̖◌ðž£â—ŒÖšb; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MENDE KIKAKUI COMBINING NUMBER TEENS, LATIN SMALL LETTER B
0061 1E8D0 059A 0316 1DFA 0062;0061 1DFA 1E8D0 0316 059A 0062;0061 1DFA 1E8D0 0316 059A 0062;0061 1DFA 1E8D0 0316 059A 0062;0061 1DFA 1E8D0 0316 059A 0062; # (aâ—Œðž£â—ŒÖšâ—ŒÌ–◌᷺b; a◌᷺◌ðž£â—ŒÌ–◌֚b; a◌᷺◌ðž£â—ŒÌ–◌֚b; a◌᷺◌ðž£â—ŒÌ–◌֚b; a◌᷺◌ðž£â—ŒÌ–◌֚b; ) LATIN SMALL LETTER A, MENDE KIKAKUI COMBINING NUMBER TEENS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, LATIN SMALL LETTER B
0061 059A 0316 1DFA 1E8D1 0062;0061 1DFA 0316 1E8D1 059A 0062;0061 1DFA 0316 1E8D1 059A 0062;0061 1DFA 0316 1E8D1 059A 0062;0061 1DFA 0316 1E8D1 059A 0062; # (a◌֚◌̖◌᷺◌𞣑b; a◌᷺◌̖◌𞣑◌֚b; a◌᷺◌̖◌𞣑◌֚b; a◌᷺◌̖◌𞣑◌֚b; a◌᷺◌̖◌𞣑◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, COMBINING DOT BELOW LEFT, MENDE KIKAKUI COMBINING NUMBER TENS, LATIN SMALL LETTER B
diff --git a/lib/stdlib/test/unicode_util_SUITE_data/unicode_table.bin b/lib/stdlib/test/unicode_util_SUITE_data/unicode_table.bin
new file mode 100644
index 0000000000..9ea04b334d
--- /dev/null
+++ b/lib/stdlib/test/unicode_util_SUITE_data/unicode_table.bin
Binary files differ
diff --git a/lib/stdlib/test/zip_SUITE.erl b/lib/stdlib/test/zip_SUITE.erl
index 1709acc523..97e5c660dd 100644
--- a/lib/stdlib/test/zip_SUITE.erl
+++ b/lib/stdlib/test/zip_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -280,6 +280,8 @@ zip_api(Config) when is_list(Config) ->
Name1 = hd(Names),
{ok, Data1} = file:read_file(Name1),
{ok, {Name1, Data1}} = zip:zip_get(Name1, ZipSrv),
+ Data1Crc = erlang:crc32(Data1),
+ {ok, Data1Crc} = zip:zip_get_crc32(Name1, ZipSrv),
%% Get all files
FilesDatas = lists:map(fun(Name) -> {ok, B} = file:read_file(Name),
diff --git a/lib/stdlib/uc_spec/CaseFolding.txt b/lib/stdlib/uc_spec/CaseFolding.txt
index 932ace29e6..65aa0fcd6b 100644
--- a/lib/stdlib/uc_spec/CaseFolding.txt
+++ b/lib/stdlib/uc_spec/CaseFolding.txt
@@ -1,11 +1,11 @@
-# CaseFolding-14.0.0.txt
-# Date: 2021-03-08, 19:35:41 GMT
-# © 2021 Unicode®, Inc.
+# CaseFolding-15.0.0.txt
+# Date: 2022-02-02, 23:35:35 GMT
+# © 2022 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
-# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For terms of use, see https://www.unicode.org/terms_of_use.html
#
# Unicode Character Database
-# For documentation, see http://www.unicode.org/reports/tr44/
+# For documentation, see https://www.unicode.org/reports/tr44/
#
# Case Folding Properties
#
diff --git a/lib/stdlib/uc_spec/CompositionExclusions.txt b/lib/stdlib/uc_spec/CompositionExclusions.txt
index 74e425e2a0..bbc8bd75d8 100644
--- a/lib/stdlib/uc_spec/CompositionExclusions.txt
+++ b/lib/stdlib/uc_spec/CompositionExclusions.txt
@@ -1,6 +1,6 @@
-# CompositionExclusions-14.0.0.txt
-# Date: 2021-03-30, 23:59:00 GMT [KW, LI]
-# © 2021 Unicode®, Inc.
+# CompositionExclusions-15.0.0.txt
+# Date: 2022-05-03, 18:50:00 GMT [KW, LI]
+# © 2022 Unicode®, Inc.
# For terms of use, see https://www.unicode.org/terms_of_use.html
#
# Unicode Character Database
diff --git a/lib/stdlib/uc_spec/EastAsianWidth.txt b/lib/stdlib/uc_spec/EastAsianWidth.txt
new file mode 100644
index 0000000000..38b7076c02
--- /dev/null
+++ b/lib/stdlib/uc_spec/EastAsianWidth.txt
@@ -0,0 +1,2619 @@
+# EastAsianWidth-15.0.0.txt
+# Date: 2022-05-24, 17:40:20 GMT [KW, LI]
+# © 2022 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use, see https://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+# For documentation, see https://www.unicode.org/reports/tr44/
+#
+# East_Asian_Width Property
+#
+# This file is a normative contributory data file in the
+# Unicode Character Database.
+#
+# The format is two fields separated by a semicolon.
+# Field 0: Unicode code point value or range of code point values
+# Field 1: East_Asian_Width property, consisting of one of the following values:
+# "A", "F", "H", "N", "Na", "W"
+# - All code points, assigned or unassigned, that are not listed
+# explicitly are given the value "N".
+# - The unassigned code points in the following blocks default to "W":
+# CJK Unified Ideographs Extension A: U+3400..U+4DBF
+# CJK Unified Ideographs: U+4E00..U+9FFF
+# CJK Compatibility Ideographs: U+F900..U+FAFF
+# - All undesignated code points in Planes 2 and 3, whether inside or
+# outside of allocated blocks, default to "W":
+# Plane 2: U+20000..U+2FFFD
+# Plane 3: U+30000..U+3FFFD
+#
+# Character ranges are specified as for other property files in the
+# Unicode Character Database.
+#
+# For legacy reasons, there are no spaces before or after the semicolon
+# which separates the two fields. The comments following the number sign
+# "#" list the General_Category property value or the L& alias of the
+# derived value LC, the Unicode character name or names, and, in lines
+# with ranges of code points, the code point count in square brackets.
+#
+# For more information, see UAX #11: East Asian Width,
+# at https://www.unicode.org/reports/tr11/
+#
+# @missing: 0000..10FFFF; N
+0000..001F;N # Cc [32] <control-0000>..<control-001F>
+0020;Na # Zs SPACE
+0021..0023;Na # Po [3] EXCLAMATION MARK..NUMBER SIGN
+0024;Na # Sc DOLLAR SIGN
+0025..0027;Na # Po [3] PERCENT SIGN..APOSTROPHE
+0028;Na # Ps LEFT PARENTHESIS
+0029;Na # Pe RIGHT PARENTHESIS
+002A;Na # Po ASTERISK
+002B;Na # Sm PLUS SIGN
+002C;Na # Po COMMA
+002D;Na # Pd HYPHEN-MINUS
+002E..002F;Na # Po [2] FULL STOP..SOLIDUS
+0030..0039;Na # Nd [10] DIGIT ZERO..DIGIT NINE
+003A..003B;Na # Po [2] COLON..SEMICOLON
+003C..003E;Na # Sm [3] LESS-THAN SIGN..GREATER-THAN SIGN
+003F..0040;Na # Po [2] QUESTION MARK..COMMERCIAL AT
+0041..005A;Na # Lu [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+005B;Na # Ps LEFT SQUARE BRACKET
+005C;Na # Po REVERSE SOLIDUS
+005D;Na # Pe RIGHT SQUARE BRACKET
+005E;Na # Sk CIRCUMFLEX ACCENT
+005F;Na # Pc LOW LINE
+0060;Na # Sk GRAVE ACCENT
+0061..007A;Na # Ll [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+007B;Na # Ps LEFT CURLY BRACKET
+007C;Na # Sm VERTICAL LINE
+007D;Na # Pe RIGHT CURLY BRACKET
+007E;Na # Sm TILDE
+007F;N # Cc <control-007F>
+0080..009F;N # Cc [32] <control-0080>..<control-009F>
+00A0;N # Zs NO-BREAK SPACE
+00A1;A # Po INVERTED EXCLAMATION MARK
+00A2..00A3;Na # Sc [2] CENT SIGN..POUND SIGN
+00A4;A # Sc CURRENCY SIGN
+00A5;Na # Sc YEN SIGN
+00A6;Na # So BROKEN BAR
+00A7;A # Po SECTION SIGN
+00A8;A # Sk DIAERESIS
+00A9;N # So COPYRIGHT SIGN
+00AA;A # Lo FEMININE ORDINAL INDICATOR
+00AB;N # Pi LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+00AC;Na # Sm NOT SIGN
+00AD;A # Cf SOFT HYPHEN
+00AE;A # So REGISTERED SIGN
+00AF;Na # Sk MACRON
+00B0;A # So DEGREE SIGN
+00B1;A # Sm PLUS-MINUS SIGN
+00B2..00B3;A # No [2] SUPERSCRIPT TWO..SUPERSCRIPT THREE
+00B4;A # Sk ACUTE ACCENT
+00B5;N # Ll MICRO SIGN
+00B6..00B7;A # Po [2] PILCROW SIGN..MIDDLE DOT
+00B8;A # Sk CEDILLA
+00B9;A # No SUPERSCRIPT ONE
+00BA;A # Lo MASCULINE ORDINAL INDICATOR
+00BB;N # Pf RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+00BC..00BE;A # No [3] VULGAR FRACTION ONE QUARTER..VULGAR FRACTION THREE QUARTERS
+00BF;A # Po INVERTED QUESTION MARK
+00C0..00C5;N # Lu [6] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER A WITH RING ABOVE
+00C6;A # Lu LATIN CAPITAL LETTER AE
+00C7..00CF;N # Lu [9] LATIN CAPITAL LETTER C WITH CEDILLA..LATIN CAPITAL LETTER I WITH DIAERESIS
+00D0;A # Lu LATIN CAPITAL LETTER ETH
+00D1..00D6;N # Lu [6] LATIN CAPITAL LETTER N WITH TILDE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D7;A # Sm MULTIPLICATION SIGN
+00D8;A # Lu LATIN CAPITAL LETTER O WITH STROKE
+00D9..00DD;N # Lu [5] LATIN CAPITAL LETTER U WITH GRAVE..LATIN CAPITAL LETTER Y WITH ACUTE
+00DE..00E1;A # L& [4] LATIN CAPITAL LETTER THORN..LATIN SMALL LETTER A WITH ACUTE
+00E2..00E5;N # Ll [4] LATIN SMALL LETTER A WITH CIRCUMFLEX..LATIN SMALL LETTER A WITH RING ABOVE
+00E6;A # Ll LATIN SMALL LETTER AE
+00E7;N # Ll LATIN SMALL LETTER C WITH CEDILLA
+00E8..00EA;A # Ll [3] LATIN SMALL LETTER E WITH GRAVE..LATIN SMALL LETTER E WITH CIRCUMFLEX
+00EB;N # Ll LATIN SMALL LETTER E WITH DIAERESIS
+00EC..00ED;A # Ll [2] LATIN SMALL LETTER I WITH GRAVE..LATIN SMALL LETTER I WITH ACUTE
+00EE..00EF;N # Ll [2] LATIN SMALL LETTER I WITH CIRCUMFLEX..LATIN SMALL LETTER I WITH DIAERESIS
+00F0;A # Ll LATIN SMALL LETTER ETH
+00F1;N # Ll LATIN SMALL LETTER N WITH TILDE
+00F2..00F3;A # Ll [2] LATIN SMALL LETTER O WITH GRAVE..LATIN SMALL LETTER O WITH ACUTE
+00F4..00F6;N # Ll [3] LATIN SMALL LETTER O WITH CIRCUMFLEX..LATIN SMALL LETTER O WITH DIAERESIS
+00F7;A # Sm DIVISION SIGN
+00F8..00FA;A # Ll [3] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER U WITH ACUTE
+00FB;N # Ll LATIN SMALL LETTER U WITH CIRCUMFLEX
+00FC;A # Ll LATIN SMALL LETTER U WITH DIAERESIS
+00FD;N # Ll LATIN SMALL LETTER Y WITH ACUTE
+00FE;A # Ll LATIN SMALL LETTER THORN
+00FF;N # Ll LATIN SMALL LETTER Y WITH DIAERESIS
+0100;N # Lu LATIN CAPITAL LETTER A WITH MACRON
+0101;A # Ll LATIN SMALL LETTER A WITH MACRON
+0102..0110;N # L& [15] LATIN CAPITAL LETTER A WITH BREVE..LATIN CAPITAL LETTER D WITH STROKE
+0111;A # Ll LATIN SMALL LETTER D WITH STROKE
+0112;N # Lu LATIN CAPITAL LETTER E WITH MACRON
+0113;A # Ll LATIN SMALL LETTER E WITH MACRON
+0114..011A;N # L& [7] LATIN CAPITAL LETTER E WITH BREVE..LATIN CAPITAL LETTER E WITH CARON
+011B;A # Ll LATIN SMALL LETTER E WITH CARON
+011C..0125;N # L& [10] LATIN CAPITAL LETTER G WITH CIRCUMFLEX..LATIN SMALL LETTER H WITH CIRCUMFLEX
+0126..0127;A # L& [2] LATIN CAPITAL LETTER H WITH STROKE..LATIN SMALL LETTER H WITH STROKE
+0128..012A;N # L& [3] LATIN CAPITAL LETTER I WITH TILDE..LATIN CAPITAL LETTER I WITH MACRON
+012B;A # Ll LATIN SMALL LETTER I WITH MACRON
+012C..0130;N # L& [5] LATIN CAPITAL LETTER I WITH BREVE..LATIN CAPITAL LETTER I WITH DOT ABOVE
+0131..0133;A # L& [3] LATIN SMALL LETTER DOTLESS I..LATIN SMALL LIGATURE IJ
+0134..0137;N # L& [4] LATIN CAPITAL LETTER J WITH CIRCUMFLEX..LATIN SMALL LETTER K WITH CEDILLA
+0138;A # Ll LATIN SMALL LETTER KRA
+0139..013E;N # L& [6] LATIN CAPITAL LETTER L WITH ACUTE..LATIN SMALL LETTER L WITH CARON
+013F..0142;A # L& [4] LATIN CAPITAL LETTER L WITH MIDDLE DOT..LATIN SMALL LETTER L WITH STROKE
+0143;N # Lu LATIN CAPITAL LETTER N WITH ACUTE
+0144;A # Ll LATIN SMALL LETTER N WITH ACUTE
+0145..0147;N # L& [3] LATIN CAPITAL LETTER N WITH CEDILLA..LATIN CAPITAL LETTER N WITH CARON
+0148..014B;A # L& [4] LATIN SMALL LETTER N WITH CARON..LATIN SMALL LETTER ENG
+014C;N # Lu LATIN CAPITAL LETTER O WITH MACRON
+014D;A # Ll LATIN SMALL LETTER O WITH MACRON
+014E..0151;N # L& [4] LATIN CAPITAL LETTER O WITH BREVE..LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0152..0153;A # L& [2] LATIN CAPITAL LIGATURE OE..LATIN SMALL LIGATURE OE
+0154..0165;N # L& [18] LATIN CAPITAL LETTER R WITH ACUTE..LATIN SMALL LETTER T WITH CARON
+0166..0167;A # L& [2] LATIN CAPITAL LETTER T WITH STROKE..LATIN SMALL LETTER T WITH STROKE
+0168..016A;N # L& [3] LATIN CAPITAL LETTER U WITH TILDE..LATIN CAPITAL LETTER U WITH MACRON
+016B;A # Ll LATIN SMALL LETTER U WITH MACRON
+016C..017F;N # L& [20] LATIN CAPITAL LETTER U WITH BREVE..LATIN SMALL LETTER LONG S
+0180..01BA;N # L& [59] LATIN SMALL LETTER B WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL
+01BB;N # Lo LATIN LETTER TWO WITH STROKE
+01BC..01BF;N # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN
+01C0..01C3;N # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK
+01C4..01CD;N # L& [10] LATIN CAPITAL LETTER DZ WITH CARON..LATIN CAPITAL LETTER A WITH CARON
+01CE;A # Ll LATIN SMALL LETTER A WITH CARON
+01CF;N # Lu LATIN CAPITAL LETTER I WITH CARON
+01D0;A # Ll LATIN SMALL LETTER I WITH CARON
+01D1;N # Lu LATIN CAPITAL LETTER O WITH CARON
+01D2;A # Ll LATIN SMALL LETTER O WITH CARON
+01D3;N # Lu LATIN CAPITAL LETTER U WITH CARON
+01D4;A # Ll LATIN SMALL LETTER U WITH CARON
+01D5;N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON
+01D6;A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND MACRON
+01D7;N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE
+01D8;A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE
+01D9;N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON
+01DA;A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND CARON
+01DB;N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE
+01DC;A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE
+01DD..024F;N # L& [115] LATIN SMALL LETTER TURNED E..LATIN SMALL LETTER Y WITH STROKE
+0250;N # Ll LATIN SMALL LETTER TURNED A
+0251;A # Ll LATIN SMALL LETTER ALPHA
+0252..0260;N # Ll [15] LATIN SMALL LETTER TURNED ALPHA..LATIN SMALL LETTER G WITH HOOK
+0261;A # Ll LATIN SMALL LETTER SCRIPT G
+0262..0293;N # Ll [50] LATIN LETTER SMALL CAPITAL G..LATIN SMALL LETTER EZH WITH CURL
+0294;N # Lo LATIN LETTER GLOTTAL STOP
+0295..02AF;N # Ll [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL
+02B0..02C1;N # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP
+02C2..02C3;N # Sk [2] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER RIGHT ARROWHEAD
+02C4;A # Sk MODIFIER LETTER UP ARROWHEAD
+02C5;N # Sk MODIFIER LETTER DOWN ARROWHEAD
+02C6;N # Lm MODIFIER LETTER CIRCUMFLEX ACCENT
+02C7;A # Lm CARON
+02C8;N # Lm MODIFIER LETTER VERTICAL LINE
+02C9..02CB;A # Lm [3] MODIFIER LETTER MACRON..MODIFIER LETTER GRAVE ACCENT
+02CC;N # Lm MODIFIER LETTER LOW VERTICAL LINE
+02CD;A # Lm MODIFIER LETTER LOW MACRON
+02CE..02CF;N # Lm [2] MODIFIER LETTER LOW GRAVE ACCENT..MODIFIER LETTER LOW ACUTE ACCENT
+02D0;A # Lm MODIFIER LETTER TRIANGULAR COLON
+02D1;N # Lm MODIFIER LETTER HALF TRIANGULAR COLON
+02D2..02D7;N # Sk [6] MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER MINUS SIGN
+02D8..02DB;A # Sk [4] BREVE..OGONEK
+02DC;N # Sk SMALL TILDE
+02DD;A # Sk DOUBLE ACUTE ACCENT
+02DE;N # Sk MODIFIER LETTER RHOTIC HOOK
+02DF;A # Sk MODIFIER LETTER CROSS ACCENT
+02E0..02E4;N # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+02E5..02EB;N # Sk [7] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER YANG DEPARTING TONE MARK
+02EC;N # Lm MODIFIER LETTER VOICING
+02ED;N # Sk MODIFIER LETTER UNASPIRATED
+02EE;N # Lm MODIFIER LETTER DOUBLE APOSTROPHE
+02EF..02FF;N # Sk [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW
+0300..036F;A # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X
+0370..0373;N # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI
+0374;N # Lm GREEK NUMERAL SIGN
+0375;N # Sk GREEK LOWER NUMERAL SIGN
+0376..0377;N # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037A;N # Lm GREEK YPOGEGRAMMENI
+037B..037D;N # Ll [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+037E;N # Po GREEK QUESTION MARK
+037F;N # Lu GREEK CAPITAL LETTER YOT
+0384..0385;N # Sk [2] GREEK TONOS..GREEK DIALYTIKA TONOS
+0386;N # Lu GREEK CAPITAL LETTER ALPHA WITH TONOS
+0387;N # Po GREEK ANO TELEIA
+0388..038A;N # Lu [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C;N # Lu GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..0390;N # L& [3] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+0391..03A1;A # Lu [17] GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LETTER RHO
+03A3..03A9;A # Lu [7] GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LETTER OMEGA
+03AA..03B0;N # L& [7] GREEK CAPITAL LETTER IOTA WITH DIALYTIKA..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+03B1..03C1;A # Ll [17] GREEK SMALL LETTER ALPHA..GREEK SMALL LETTER RHO
+03C2;N # Ll GREEK SMALL LETTER FINAL SIGMA
+03C3..03C9;A # Ll [7] GREEK SMALL LETTER SIGMA..GREEK SMALL LETTER OMEGA
+03CA..03F5;N # L& [44] GREEK SMALL LETTER IOTA WITH DIALYTIKA..GREEK LUNATE EPSILON SYMBOL
+03F6;N # Sm GREEK REVERSED LUNATE EPSILON SYMBOL
+03F7..03FF;N # L& [9] GREEK CAPITAL LETTER SHO..GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL
+0400;N # Lu CYRILLIC CAPITAL LETTER IE WITH GRAVE
+0401;A # Lu CYRILLIC CAPITAL LETTER IO
+0402..040F;N # Lu [14] CYRILLIC CAPITAL LETTER DJE..CYRILLIC CAPITAL LETTER DZHE
+0410..044F;A # L& [64] CYRILLIC CAPITAL LETTER A..CYRILLIC SMALL LETTER YA
+0450;N # Ll CYRILLIC SMALL LETTER IE WITH GRAVE
+0451;A # Ll CYRILLIC SMALL LETTER IO
+0452..0481;N # L& [48] CYRILLIC SMALL LETTER DJE..CYRILLIC SMALL LETTER KOPPA
+0482;N # So CYRILLIC THOUSANDS SIGN
+0483..0487;N # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE
+0488..0489;N # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN
+048A..04FF;N # L& [118] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER HA WITH STROKE
+0500..052F;N # L& [48] CYRILLIC CAPITAL LETTER KOMI DE..CYRILLIC SMALL LETTER EL WITH DESCENDER
+0531..0556;N # Lu [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+0559;N # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING
+055A..055F;N # Po [6] ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION MARK
+0560..0588;N # Ll [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE
+0589;N # Po ARMENIAN FULL STOP
+058A;N # Pd ARMENIAN HYPHEN
+058D..058E;N # So [2] RIGHT-FACING ARMENIAN ETERNITY SIGN..LEFT-FACING ARMENIAN ETERNITY SIGN
+058F;N # Sc ARMENIAN DRAM SIGN
+0591..05BD;N # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG
+05BE;N # Pd HEBREW PUNCTUATION MAQAF
+05BF;N # Mn HEBREW POINT RAFE
+05C0;N # Po HEBREW PUNCTUATION PASEQ
+05C1..05C2;N # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT
+05C3;N # Po HEBREW PUNCTUATION SOF PASUQ
+05C4..05C5;N # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT
+05C6;N # Po HEBREW PUNCTUATION NUN HAFUKHA
+05C7;N # Mn HEBREW POINT QAMATS QATAN
+05D0..05EA;N # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV
+05EF..05F2;N # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD
+05F3..05F4;N # Po [2] HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM
+0600..0605;N # Cf [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE
+0606..0608;N # Sm [3] ARABIC-INDIC CUBE ROOT..ARABIC RAY
+0609..060A;N # Po [2] ARABIC-INDIC PER MILLE SIGN..ARABIC-INDIC PER TEN THOUSAND SIGN
+060B;N # Sc AFGHANI SIGN
+060C..060D;N # Po [2] ARABIC COMMA..ARABIC DATE SEPARATOR
+060E..060F;N # So [2] ARABIC POETIC VERSE SIGN..ARABIC SIGN MISRA
+0610..061A;N # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA
+061B;N # Po ARABIC SEMICOLON
+061C;N # Cf ARABIC LETTER MARK
+061D..061F;N # Po [3] ARABIC END OF TEXT MARK..ARABIC QUESTION MARK
+0620..063F;N # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE
+0640;N # Lm ARABIC TATWEEL
+0641..064A;N # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH
+064B..065F;N # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW
+0660..0669;N # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE
+066A..066D;N # Po [4] ARABIC PERCENT SIGN..ARABIC FIVE POINTED STAR
+066E..066F;N # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF
+0670;N # Mn ARABIC LETTER SUPERSCRIPT ALEF
+0671..06D3;N # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
+06D4;N # Po ARABIC FULL STOP
+06D5;N # Lo ARABIC LETTER AE
+06D6..06DC;N # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN
+06DD;N # Cf ARABIC END OF AYAH
+06DE;N # So ARABIC START OF RUB EL HIZB
+06DF..06E4;N # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA
+06E5..06E6;N # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH
+06E7..06E8;N # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON
+06E9;N # So ARABIC PLACE OF SAJDAH
+06EA..06ED;N # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM
+06EE..06EF;N # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V
+06F0..06F9;N # Nd [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE
+06FA..06FC;N # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW
+06FD..06FE;N # So [2] ARABIC SIGN SINDHI AMPERSAND..ARABIC SIGN SINDHI POSTPOSITION MEN
+06FF;N # Lo ARABIC LETTER HEH WITH INVERTED V
+0700..070D;N # Po [14] SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS
+070F;N # Cf SYRIAC ABBREVIATION MARK
+0710;N # Lo SYRIAC LETTER ALAPH
+0711;N # Mn SYRIAC LETTER SUPERSCRIPT ALAPH
+0712..072F;N # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH
+0730..074A;N # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH
+074D..074F;N # Lo [3] SYRIAC LETTER SOGDIAN ZHAIN..SYRIAC LETTER SOGDIAN FE
+0750..077F;N # Lo [48] ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS ABOVE
+0780..07A5;N # Lo [38] THAANA LETTER HAA..THAANA LETTER WAAVU
+07A6..07B0;N # Mn [11] THAANA ABAFILI..THAANA SUKUN
+07B1;N # Lo THAANA LETTER NAA
+07C0..07C9;N # Nd [10] NKO DIGIT ZERO..NKO DIGIT NINE
+07CA..07EA;N # Lo [33] NKO LETTER A..NKO LETTER JONA RA
+07EB..07F3;N # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE
+07F4..07F5;N # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE
+07F6;N # So NKO SYMBOL OO DENNEN
+07F7..07F9;N # Po [3] NKO SYMBOL GBAKURUNEN..NKO EXCLAMATION MARK
+07FA;N # Lm NKO LAJANYALAN
+07FD;N # Mn NKO DANTAYALAN
+07FE..07FF;N # Sc [2] NKO DOROME SIGN..NKO TAMAN SIGN
+0800..0815;N # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF
+0816..0819;N # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH
+081A;N # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT
+081B..0823;N # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
+0824;N # Lm SAMARITAN MODIFIER LETTER SHORT A
+0825..0827;N # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
+0828;N # Lm SAMARITAN MODIFIER LETTER I
+0829..082D;N # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA
+0830..083E;N # Po [15] SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUNCTUATION ANNAAU
+0840..0858;N # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN
+0859..085B;N # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK
+085E;N # Po MANDAIC PUNCTUATION
+0860..086A;N # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA
+0870..0887;N # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT
+0888;N # Sk ARABIC RAISED ROUND DOT
+0889..088E;N # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL
+0890..0891;N # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE
+0898..089F;N # Mn [8] ARABIC SMALL HIGH WORD AL-JUZ..ARABIC HALF MADDA OVER MADDA
+08A0..08C8;N # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF
+08C9;N # Lm ARABIC SMALL FARSI YEH
+08CA..08E1;N # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA
+08E2;N # Cf ARABIC DISPUTED END OF AYAH
+08E3..08FF;N # Mn [29] ARABIC TURNED DAMMA BELOW..ARABIC MARK SIDEWAYS NOON GHUNNA
+0900..0902;N # Mn [3] DEVANAGARI SIGN INVERTED CANDRABINDU..DEVANAGARI SIGN ANUSVARA
+0903;N # Mc DEVANAGARI SIGN VISARGA
+0904..0939;N # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA
+093A;N # Mn DEVANAGARI VOWEL SIGN OE
+093B;N # Mc DEVANAGARI VOWEL SIGN OOE
+093C;N # Mn DEVANAGARI SIGN NUKTA
+093D;N # Lo DEVANAGARI SIGN AVAGRAHA
+093E..0940;N # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II
+0941..0948;N # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI
+0949..094C;N # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU
+094D;N # Mn DEVANAGARI SIGN VIRAMA
+094E..094F;N # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW
+0950;N # Lo DEVANAGARI OM
+0951..0957;N # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE
+0958..0961;N # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL
+0962..0963;N # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL
+0964..0965;N # Po [2] DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA
+0966..096F;N # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE
+0970;N # Po DEVANAGARI ABBREVIATION SIGN
+0971;N # Lm DEVANAGARI SIGN HIGH SPACING DOT
+0972..097F;N # Lo [14] DEVANAGARI LETTER CANDRA A..DEVANAGARI LETTER BBA
+0980;N # Lo BENGALI ANJI
+0981;N # Mn BENGALI SIGN CANDRABINDU
+0982..0983;N # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA
+0985..098C;N # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L
+098F..0990;N # Lo [2] BENGALI LETTER E..BENGALI LETTER AI
+0993..09A8;N # Lo [22] BENGALI LETTER O..BENGALI LETTER NA
+09AA..09B0;N # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA
+09B2;N # Lo BENGALI LETTER LA
+09B6..09B9;N # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA
+09BC;N # Mn BENGALI SIGN NUKTA
+09BD;N # Lo BENGALI SIGN AVAGRAHA
+09BE..09C0;N # Mc [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II
+09C1..09C4;N # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR
+09C7..09C8;N # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI
+09CB..09CC;N # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU
+09CD;N # Mn BENGALI SIGN VIRAMA
+09CE;N # Lo BENGALI LETTER KHANDA TA
+09D7;N # Mc BENGALI AU LENGTH MARK
+09DC..09DD;N # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA
+09DF..09E1;N # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL
+09E2..09E3;N # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+09E6..09EF;N # Nd [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE
+09F0..09F1;N # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL
+09F2..09F3;N # Sc [2] BENGALI RUPEE MARK..BENGALI RUPEE SIGN
+09F4..09F9;N # No [6] BENGALI CURRENCY NUMERATOR ONE..BENGALI CURRENCY DENOMINATOR SIXTEEN
+09FA;N # So BENGALI ISSHAR
+09FB;N # Sc BENGALI GANDA MARK
+09FC;N # Lo BENGALI LETTER VEDIC ANUSVARA
+09FD;N # Po BENGALI ABBREVIATION SIGN
+09FE;N # Mn BENGALI SANDHI MARK
+0A01..0A02;N # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
+0A03;N # Mc GURMUKHI SIGN VISARGA
+0A05..0A0A;N # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU
+0A0F..0A10;N # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI
+0A13..0A28;N # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA
+0A2A..0A30;N # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA
+0A32..0A33;N # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA
+0A35..0A36;N # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA
+0A38..0A39;N # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA
+0A3C;N # Mn GURMUKHI SIGN NUKTA
+0A3E..0A40;N # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II
+0A41..0A42;N # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
+0A47..0A48;N # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI
+0A4B..0A4D;N # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA
+0A51;N # Mn GURMUKHI SIGN UDAAT
+0A59..0A5C;N # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA
+0A5E;N # Lo GURMUKHI LETTER FA
+0A66..0A6F;N # Nd [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE
+0A70..0A71;N # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK
+0A72..0A74;N # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR
+0A75;N # Mn GURMUKHI SIGN YAKASH
+0A76;N # Po GURMUKHI ABBREVIATION SIGN
+0A81..0A82;N # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA
+0A83;N # Mc GUJARATI SIGN VISARGA
+0A85..0A8D;N # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E
+0A8F..0A91;N # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O
+0A93..0AA8;N # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA
+0AAA..0AB0;N # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA
+0AB2..0AB3;N # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA
+0AB5..0AB9;N # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA
+0ABC;N # Mn GUJARATI SIGN NUKTA
+0ABD;N # Lo GUJARATI SIGN AVAGRAHA
+0ABE..0AC0;N # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II
+0AC1..0AC5;N # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E
+0AC7..0AC8;N # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI
+0AC9;N # Mc GUJARATI VOWEL SIGN CANDRA O
+0ACB..0ACC;N # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU
+0ACD;N # Mn GUJARATI SIGN VIRAMA
+0AD0;N # Lo GUJARATI OM
+0AE0..0AE1;N # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL
+0AE2..0AE3;N # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL
+0AE6..0AEF;N # Nd [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE
+0AF0;N # Po GUJARATI ABBREVIATION SIGN
+0AF1;N # Sc GUJARATI RUPEE SIGN
+0AF9;N # Lo GUJARATI LETTER ZHA
+0AFA..0AFF;N # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE
+0B01;N # Mn ORIYA SIGN CANDRABINDU
+0B02..0B03;N # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA
+0B05..0B0C;N # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L
+0B0F..0B10;N # Lo [2] ORIYA LETTER E..ORIYA LETTER AI
+0B13..0B28;N # Lo [22] ORIYA LETTER O..ORIYA LETTER NA
+0B2A..0B30;N # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA
+0B32..0B33;N # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA
+0B35..0B39;N # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA
+0B3C;N # Mn ORIYA SIGN NUKTA
+0B3D;N # Lo ORIYA SIGN AVAGRAHA
+0B3E;N # Mc ORIYA VOWEL SIGN AA
+0B3F;N # Mn ORIYA VOWEL SIGN I
+0B40;N # Mc ORIYA VOWEL SIGN II
+0B41..0B44;N # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR
+0B47..0B48;N # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI
+0B4B..0B4C;N # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU
+0B4D;N # Mn ORIYA SIGN VIRAMA
+0B55..0B56;N # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK
+0B57;N # Mc ORIYA AU LENGTH MARK
+0B5C..0B5D;N # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA
+0B5F..0B61;N # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL
+0B62..0B63;N # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL
+0B66..0B6F;N # Nd [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE
+0B70;N # So ORIYA ISSHAR
+0B71;N # Lo ORIYA LETTER WA
+0B72..0B77;N # No [6] ORIYA FRACTION ONE QUARTER..ORIYA FRACTION THREE SIXTEENTHS
+0B82;N # Mn TAMIL SIGN ANUSVARA
+0B83;N # Lo TAMIL SIGN VISARGA
+0B85..0B8A;N # Lo [6] TAMIL LETTER A..TAMIL LETTER UU
+0B8E..0B90;N # Lo [3] TAMIL LETTER E..TAMIL LETTER AI
+0B92..0B95;N # Lo [4] TAMIL LETTER O..TAMIL LETTER KA
+0B99..0B9A;N # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA
+0B9C;N # Lo TAMIL LETTER JA
+0B9E..0B9F;N # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA
+0BA3..0BA4;N # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA
+0BA8..0BAA;N # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA
+0BAE..0BB9;N # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA
+0BBE..0BBF;N # Mc [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I
+0BC0;N # Mn TAMIL VOWEL SIGN II
+0BC1..0BC2;N # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU
+0BC6..0BC8;N # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI
+0BCA..0BCC;N # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU
+0BCD;N # Mn TAMIL SIGN VIRAMA
+0BD0;N # Lo TAMIL OM
+0BD7;N # Mc TAMIL AU LENGTH MARK
+0BE6..0BEF;N # Nd [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE
+0BF0..0BF2;N # No [3] TAMIL NUMBER TEN..TAMIL NUMBER ONE THOUSAND
+0BF3..0BF8;N # So [6] TAMIL DAY SIGN..TAMIL AS ABOVE SIGN
+0BF9;N # Sc TAMIL RUPEE SIGN
+0BFA;N # So TAMIL NUMBER SIGN
+0C00;N # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+0C01..0C03;N # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA
+0C04;N # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
+0C05..0C0C;N # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L
+0C0E..0C10;N # Lo [3] TELUGU LETTER E..TELUGU LETTER AI
+0C12..0C28;N # Lo [23] TELUGU LETTER O..TELUGU LETTER NA
+0C2A..0C39;N # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA
+0C3C;N # Mn TELUGU SIGN NUKTA
+0C3D;N # Lo TELUGU SIGN AVAGRAHA
+0C3E..0C40;N # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
+0C41..0C44;N # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR
+0C46..0C48;N # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+0C4A..0C4D;N # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA
+0C55..0C56;N # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+0C58..0C5A;N # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA
+0C5D;N # Lo TELUGU LETTER NAKAARA POLLU
+0C60..0C61;N # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL
+0C62..0C63;N # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL
+0C66..0C6F;N # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE
+0C77;N # Po TELUGU SIGN SIDDHAM
+0C78..0C7E;N # No [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR
+0C7F;N # So TELUGU SIGN TUUMU
+0C80;N # Lo KANNADA SIGN SPACING CANDRABINDU
+0C81;N # Mn KANNADA SIGN CANDRABINDU
+0C82..0C83;N # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA
+0C84;N # Po KANNADA SIGN SIDDHAM
+0C85..0C8C;N # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L
+0C8E..0C90;N # Lo [3] KANNADA LETTER E..KANNADA LETTER AI
+0C92..0CA8;N # Lo [23] KANNADA LETTER O..KANNADA LETTER NA
+0CAA..0CB3;N # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA
+0CB5..0CB9;N # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA
+0CBC;N # Mn KANNADA SIGN NUKTA
+0CBD;N # Lo KANNADA SIGN AVAGRAHA
+0CBE;N # Mc KANNADA VOWEL SIGN AA
+0CBF;N # Mn KANNADA VOWEL SIGN I
+0CC0..0CC4;N # Mc [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR
+0CC6;N # Mn KANNADA VOWEL SIGN E
+0CC7..0CC8;N # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI
+0CCA..0CCB;N # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO
+0CCC..0CCD;N # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA
+0CD5..0CD6;N # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK
+0CDD..0CDE;N # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA
+0CE0..0CE1;N # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL
+0CE2..0CE3;N # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+0CE6..0CEF;N # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE
+0CF1..0CF2;N # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA
+0CF3;N # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT
+0D00..0D01;N # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
+0D02..0D03;N # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA
+0D04..0D0C;N # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L
+0D0E..0D10;N # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI
+0D12..0D3A;N # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA
+0D3B..0D3C;N # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA
+0D3D;N # Lo MALAYALAM SIGN AVAGRAHA
+0D3E..0D40;N # Mc [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II
+0D41..0D44;N # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR
+0D46..0D48;N # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI
+0D4A..0D4C;N # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU
+0D4D;N # Mn MALAYALAM SIGN VIRAMA
+0D4E;N # Lo MALAYALAM LETTER DOT REPH
+0D4F;N # So MALAYALAM SIGN PARA
+0D54..0D56;N # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL
+0D57;N # Mc MALAYALAM AU LENGTH MARK
+0D58..0D5E;N # No [7] MALAYALAM FRACTION ONE ONE-HUNDRED-AND-SIXTIETH..MALAYALAM FRACTION ONE FIFTH
+0D5F..0D61;N # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL
+0D62..0D63;N # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL
+0D66..0D6F;N # Nd [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE
+0D70..0D78;N # No [9] MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE SIXTEENTHS
+0D79;N # So MALAYALAM DATE MARK
+0D7A..0D7F;N # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K
+0D81;N # Mn SINHALA SIGN CANDRABINDU
+0D82..0D83;N # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA
+0D85..0D96;N # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA
+0D9A..0DB1;N # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA
+0DB3..0DBB;N # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA
+0DBD;N # Lo SINHALA LETTER DANTAJA LAYANNA
+0DC0..0DC6;N # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA
+0DCA;N # Mn SINHALA SIGN AL-LAKUNA
+0DCF..0DD1;N # Mc [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA
+0DD2..0DD4;N # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA
+0DD6;N # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA
+0DD8..0DDF;N # Mc [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA
+0DE6..0DEF;N # Nd [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE
+0DF2..0DF3;N # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA
+0DF4;N # Po SINHALA PUNCTUATION KUNDDALIYA
+0E01..0E30;N # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A
+0E31;N # Mn THAI CHARACTER MAI HAN-AKAT
+0E32..0E33;N # Lo [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM
+0E34..0E3A;N # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
+0E3F;N # Sc THAI CURRENCY SYMBOL BAHT
+0E40..0E45;N # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO
+0E46;N # Lm THAI CHARACTER MAIYAMOK
+0E47..0E4E;N # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
+0E4F;N # Po THAI CHARACTER FONGMAN
+0E50..0E59;N # Nd [10] THAI DIGIT ZERO..THAI DIGIT NINE
+0E5A..0E5B;N # Po [2] THAI CHARACTER ANGKHANKHU..THAI CHARACTER KHOMUT
+0E81..0E82;N # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG
+0E84;N # Lo LAO LETTER KHO TAM
+0E86..0E8A;N # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM
+0E8C..0EA3;N # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING
+0EA5;N # Lo LAO LETTER LO LOOT
+0EA7..0EB0;N # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A
+0EB1;N # Mn LAO VOWEL SIGN MAI KAN
+0EB2..0EB3;N # Lo [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM
+0EB4..0EBC;N # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
+0EBD;N # Lo LAO SEMIVOWEL SIGN NYO
+0EC0..0EC4;N # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI
+0EC6;N # Lm LAO KO LA
+0EC8..0ECE;N # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN
+0ED0..0ED9;N # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE
+0EDC..0EDF;N # Lo [4] LAO HO NO..LAO LETTER KHMU NYO
+0F00;N # Lo TIBETAN SYLLABLE OM
+0F01..0F03;N # So [3] TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA
+0F04..0F12;N # Po [15] TIBETAN MARK INITIAL YIG MGO MDUN MA..TIBETAN MARK RGYA GRAM SHAD
+0F13;N # So TIBETAN MARK CARET -DZUD RTAGS ME LONG CAN
+0F14;N # Po TIBETAN MARK GTER TSHEG
+0F15..0F17;N # So [3] TIBETAN LOGOTYPE SIGN CHAD RTAGS..TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS
+0F18..0F19;N # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
+0F1A..0F1F;N # So [6] TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RDEL DKAR RDEL NAG
+0F20..0F29;N # Nd [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE
+0F2A..0F33;N # No [10] TIBETAN DIGIT HALF ONE..TIBETAN DIGIT HALF ZERO
+0F34;N # So TIBETAN MARK BSDUS RTAGS
+0F35;N # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
+0F36;N # So TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN
+0F37;N # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS
+0F38;N # So TIBETAN MARK CHE MGO
+0F39;N # Mn TIBETAN MARK TSA -PHRU
+0F3A;N # Ps TIBETAN MARK GUG RTAGS GYON
+0F3B;N # Pe TIBETAN MARK GUG RTAGS GYAS
+0F3C;N # Ps TIBETAN MARK ANG KHANG GYON
+0F3D;N # Pe TIBETAN MARK ANG KHANG GYAS
+0F3E..0F3F;N # Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES
+0F40..0F47;N # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA
+0F49..0F6C;N # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA
+0F71..0F7E;N # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
+0F7F;N # Mc TIBETAN SIGN RNAM BCAD
+0F80..0F84;N # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA
+0F85;N # Po TIBETAN MARK PALUTA
+0F86..0F87;N # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS
+0F88..0F8C;N # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN
+0F8D..0F97;N # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
+0F99..0FBC;N # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
+0FBE..0FC5;N # So [8] TIBETAN KU RU KHA..TIBETAN SYMBOL RDO RJE
+0FC6;N # Mn TIBETAN SYMBOL PADMA GDAN
+0FC7..0FCC;N # So [6] TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SYMBOL NOR BU BZHI -KHYIL
+0FCE..0FCF;N # So [2] TIBETAN SIGN RDEL NAG RDEL DKAR..TIBETAN SIGN RDEL NAG GSUM
+0FD0..0FD4;N # Po [5] TIBETAN MARK BSKA- SHOG GI MGO RGYAN..TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA
+0FD5..0FD8;N # So [4] RIGHT-FACING SVASTI SIGN..LEFT-FACING SVASTI SIGN WITH DOTS
+0FD9..0FDA;N # Po [2] TIBETAN MARK LEADING MCHAN RTAGS..TIBETAN MARK TRAILING MCHAN RTAGS
+1000..102A;N # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU
+102B..102C;N # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA
+102D..1030;N # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU
+1031;N # Mc MYANMAR VOWEL SIGN E
+1032..1037;N # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW
+1038;N # Mc MYANMAR SIGN VISARGA
+1039..103A;N # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+103B..103C;N # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA
+103D..103E;N # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA
+103F;N # Lo MYANMAR LETTER GREAT SA
+1040..1049;N # Nd [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE
+104A..104F;N # Po [6] MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL GENITIVE
+1050..1055;N # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL
+1056..1057;N # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR
+1058..1059;N # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
+105A..105D;N # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE
+105E..1060;N # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
+1061;N # Lo MYANMAR LETTER SGAW KAREN SHA
+1062..1064;N # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO
+1065..1066;N # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA
+1067..106D;N # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5
+106E..1070;N # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA
+1071..1074;N # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
+1075..1081;N # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA
+1082;N # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+1083..1084;N # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E
+1085..1086;N # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
+1087..108C;N # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3
+108D;N # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+108E;N # Lo MYANMAR LETTER RUMAI PALAUNG FA
+108F;N # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5
+1090..1099;N # Nd [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE
+109A..109C;N # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A
+109D;N # Mn MYANMAR VOWEL SIGN AITON AI
+109E..109F;N # So [2] MYANMAR SYMBOL SHAN ONE..MYANMAR SYMBOL SHAN EXCLAMATION
+10A0..10C5;N # Lu [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7;N # Lu GEORGIAN CAPITAL LETTER YN
+10CD;N # Lu GEORGIAN CAPITAL LETTER AEN
+10D0..10FA;N # Ll [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FB;N # Po GEORGIAN PARAGRAPH SEPARATOR
+10FC;N # Lm MODIFIER LETTER GEORGIAN NAR
+10FD..10FF;N # Ll [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+1100..115F;W # Lo [96] HANGUL CHOSEONG KIYEOK..HANGUL CHOSEONG FILLER
+1160..11FF;N # Lo [160] HANGUL JUNGSEONG FILLER..HANGUL JONGSEONG SSANGNIEUN
+1200..1248;N # Lo [73] ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE QWA
+124A..124D;N # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE
+1250..1256;N # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO
+1258;N # Lo ETHIOPIC SYLLABLE QHWA
+125A..125D;N # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE
+1260..1288;N # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA
+128A..128D;N # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE
+1290..12B0;N # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA
+12B2..12B5;N # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE
+12B8..12BE;N # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO
+12C0;N # Lo ETHIOPIC SYLLABLE KXWA
+12C2..12C5;N # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE
+12C8..12D6;N # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O
+12D8..1310;N # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA
+1312..1315;N # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE
+1318..135A;N # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA
+135D..135F;N # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK
+1360..1368;N # Po [9] ETHIOPIC SECTION MARK..ETHIOPIC PARAGRAPH SEPARATOR
+1369..137C;N # No [20] ETHIOPIC DIGIT ONE..ETHIOPIC NUMBER TEN THOUSAND
+1380..138F;N # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE
+1390..1399;N # So [10] ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MARK KURT
+13A0..13F5;N # Lu [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+13F8..13FD;N # Ll [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1400;N # Pd CANADIAN SYLLABICS HYPHEN
+1401..166C;N # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA
+166D;N # So CANADIAN SYLLABICS CHI SIGN
+166E;N # Po CANADIAN SYLLABICS FULL STOP
+166F..167F;N # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W
+1680;N # Zs OGHAM SPACE MARK
+1681..169A;N # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH
+169B;N # Ps OGHAM FEATHER MARK
+169C;N # Pe OGHAM REVERSED FEATHER MARK
+16A0..16EA;N # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X
+16EB..16ED;N # Po [3] RUNIC SINGLE PUNCTUATION..RUNIC CROSS PUNCTUATION
+16EE..16F0;N # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL
+16F1..16F8;N # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC
+1700..1711;N # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA
+1712..1714;N # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA
+1715;N # Mc TAGALOG SIGN PAMUDPOD
+171F;N # Lo TAGALOG LETTER ARCHAIC RA
+1720..1731;N # Lo [18] HANUNOO LETTER A..HANUNOO LETTER HA
+1732..1733;N # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U
+1734;N # Mc HANUNOO SIGN PAMUDPOD
+1735..1736;N # Po [2] PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION
+1740..1751;N # Lo [18] BUHID LETTER A..BUHID LETTER HA
+1752..1753;N # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
+1760..176C;N # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA
+176E..1770;N # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA
+1772..1773;N # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+1780..17B3;N # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU
+17B4..17B5;N # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
+17B6;N # Mc KHMER VOWEL SIGN AA
+17B7..17BD;N # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA
+17BE..17C5;N # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU
+17C6;N # Mn KHMER SIGN NIKAHIT
+17C7..17C8;N # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU
+17C9..17D3;N # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT
+17D4..17D6;N # Po [3] KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH
+17D7;N # Lm KHMER SIGN LEK TOO
+17D8..17DA;N # Po [3] KHMER SIGN BEYYAL..KHMER SIGN KOOMUUT
+17DB;N # Sc KHMER CURRENCY SYMBOL RIEL
+17DC;N # Lo KHMER SIGN AVAKRAHASANYA
+17DD;N # Mn KHMER SIGN ATTHACAN
+17E0..17E9;N # Nd [10] KHMER DIGIT ZERO..KHMER DIGIT NINE
+17F0..17F9;N # No [10] KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK ATTAK PRAM-BUON
+1800..1805;N # Po [6] MONGOLIAN BIRGA..MONGOLIAN FOUR DOTS
+1806;N # Pd MONGOLIAN TODO SOFT HYPHEN
+1807..180A;N # Po [4] MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER..MONGOLIAN NIRUGU
+180B..180D;N # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
+180E;N # Cf MONGOLIAN VOWEL SEPARATOR
+180F;N # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR
+1810..1819;N # Nd [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE
+1820..1842;N # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI
+1843;N # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN
+1844..1878;N # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS
+1880..1884;N # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA
+1885..1886;N # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+1887..18A8;N # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA
+18A9;N # Mn MONGOLIAN LETTER ALI GALI DAGALGA
+18AA;N # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA
+18B0..18F5;N # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S
+1900..191E;N # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA
+1920..1922;N # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U
+1923..1926;N # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU
+1927..1928;N # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O
+1929..192B;N # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA
+1930..1931;N # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA
+1932;N # Mn LIMBU SMALL LETTER ANUSVARA
+1933..1938;N # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA
+1939..193B;N # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I
+1940;N # So LIMBU SIGN LOO
+1944..1945;N # Po [2] LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK
+1946..194F;N # Nd [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE
+1950..196D;N # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI
+1970..1974;N # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6
+1980..19AB;N # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA
+19B0..19C9;N # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2
+19D0..19D9;N # Nd [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE
+19DA;N # No NEW TAI LUE THAM DIGIT ONE
+19DE..19DF;N # So [2] NEW TAI LUE SIGN LAE..NEW TAI LUE SIGN LAEV
+19E0..19FF;N # So [32] KHMER SYMBOL PATHAMASAT..KHMER SYMBOL DAP-PRAM ROC
+1A00..1A16;N # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA
+1A17..1A18;N # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U
+1A19..1A1A;N # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O
+1A1B;N # Mn BUGINESE VOWEL SIGN AE
+1A1E..1A1F;N # Po [2] BUGINESE PALLAWA..BUGINESE END OF SECTION
+1A20..1A54;N # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA
+1A55;N # Mc TAI THAM CONSONANT SIGN MEDIAL RA
+1A56;N # Mn TAI THAM CONSONANT SIGN MEDIAL LA
+1A57;N # Mc TAI THAM CONSONANT SIGN LA TANG LAI
+1A58..1A5E;N # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA
+1A60;N # Mn TAI THAM SIGN SAKOT
+1A61;N # Mc TAI THAM VOWEL SIGN A
+1A62;N # Mn TAI THAM VOWEL SIGN MAI SAT
+1A63..1A64;N # Mc [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA
+1A65..1A6C;N # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW
+1A6D..1A72;N # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI
+1A73..1A7C;N # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN
+1A7F;N # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT
+1A80..1A89;N # Nd [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE
+1A90..1A99;N # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE
+1AA0..1AA6;N # Po [7] TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED ROTATED RANA
+1AA7;N # Lm TAI THAM SIGN MAI YAMOK
+1AA8..1AAD;N # Po [6] TAI THAM SIGN KAAN..TAI THAM SIGN CAANG
+1AB0..1ABD;N # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW
+1ABE;N # Me COMBINING PARENTHESES OVERLAY
+1ABF..1ACE;N # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T
+1B00..1B03;N # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
+1B04;N # Mc BALINESE SIGN BISAH
+1B05..1B33;N # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA
+1B34;N # Mn BALINESE SIGN REREKAN
+1B35;N # Mc BALINESE VOWEL SIGN TEDUNG
+1B36..1B3A;N # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
+1B3B;N # Mc BALINESE VOWEL SIGN RA REPA TEDUNG
+1B3C;N # Mn BALINESE VOWEL SIGN LA LENGA
+1B3D..1B41;N # Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG
+1B42;N # Mn BALINESE VOWEL SIGN PEPET
+1B43..1B44;N # Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG
+1B45..1B4C;N # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA
+1B50..1B59;N # Nd [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE
+1B5A..1B60;N # Po [7] BALINESE PANTI..BALINESE PAMENENG
+1B61..1B6A;N # So [10] BALINESE MUSICAL SYMBOL DONG..BALINESE MUSICAL SYMBOL DANG GEDE
+1B6B..1B73;N # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG
+1B74..1B7C;N # So [9] BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG..BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING
+1B7D..1B7E;N # Po [2] BALINESE PANTI LANTANG..BALINESE PAMADA LANTANG
+1B80..1B81;N # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR
+1B82;N # Mc SUNDANESE SIGN PANGWISAD
+1B83..1BA0;N # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA
+1BA1;N # Mc SUNDANESE CONSONANT SIGN PAMINGKAL
+1BA2..1BA5;N # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU
+1BA6..1BA7;N # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG
+1BA8..1BA9;N # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG
+1BAA;N # Mc SUNDANESE SIGN PAMAAEH
+1BAB..1BAD;N # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA
+1BAE..1BAF;N # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA
+1BB0..1BB9;N # Nd [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE
+1BBA..1BBF;N # Lo [6] SUNDANESE AVAGRAHA..SUNDANESE LETTER FINAL M
+1BC0..1BE5;N # Lo [38] BATAK LETTER A..BATAK LETTER U
+1BE6;N # Mn BATAK SIGN TOMPI
+1BE7;N # Mc BATAK VOWEL SIGN E
+1BE8..1BE9;N # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE
+1BEA..1BEC;N # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O
+1BED;N # Mn BATAK VOWEL SIGN KARO O
+1BEE;N # Mc BATAK VOWEL SIGN U
+1BEF..1BF1;N # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H
+1BF2..1BF3;N # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN
+1BFC..1BFF;N # Po [4] BATAK SYMBOL BINDU NA METEK..BATAK SYMBOL BINDU PANGOLAT
+1C00..1C23;N # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A
+1C24..1C2B;N # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU
+1C2C..1C33;N # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
+1C34..1C35;N # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG
+1C36..1C37;N # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA
+1C3B..1C3F;N # Po [5] LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATION TSHOOK
+1C40..1C49;N # Nd [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE
+1C4D..1C4F;N # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA
+1C50..1C59;N # Nd [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE
+1C5A..1C77;N # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH
+1C78..1C7D;N # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD
+1C7E..1C7F;N # Po [2] OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTUATION DOUBLE MUCAAD
+1C80..1C88;N # Ll [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK
+1C90..1CBA;N # Lu [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF;N # Lu [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1CC0..1CC7;N # Po [8] SUNDANESE PUNCTUATION BINDU SURYA..SUNDANESE PUNCTUATION BINDU BA SATANGA
+1CD0..1CD2;N # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA
+1CD3;N # Po VEDIC SIGN NIHSHVASA
+1CD4..1CE0;N # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA
+1CE1;N # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA
+1CE2..1CE8;N # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL
+1CE9..1CEC;N # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL
+1CED;N # Mn VEDIC SIGN TIRYAK
+1CEE..1CF3;N # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA
+1CF4;N # Mn VEDIC TONE CANDRA ABOVE
+1CF5..1CF6;N # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA
+1CF7;N # Mc VEDIC SIGN ATIKRAMA
+1CF8..1CF9;N # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE
+1CFA;N # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA
+1D00..1D2B;N # Ll [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL
+1D2C..1D6A;N # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
+1D6B..1D77;N # Ll [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G
+1D78;N # Lm MODIFIER LETTER CYRILLIC EN
+1D79..1D7F;N # Ll [7] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER UPSILON WITH STROKE
+1D80..1D9A;N # Ll [27] LATIN SMALL LETTER B WITH PALATAL HOOK..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK
+1D9B..1DBF;N # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
+1DC0..1DFF;N # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW
+1E00..1EFF;N # L& [256] LATIN CAPITAL LETTER A WITH RING BELOW..LATIN SMALL LETTER Y WITH LOOP
+1F00..1F15;N # L& [22] GREEK SMALL LETTER ALPHA WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F18..1F1D;N # Lu [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F45;N # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F48..1F4D;N # Lu [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57;N # Ll [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F59;N # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B;N # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D;N # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F..1F7D;N # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1FB4;N # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FBC;N # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBD;N # Sk GREEK KORONIS
+1FBE;N # Ll GREEK PROSGEGRAMMENI
+1FBF..1FC1;N # Sk [3] GREEK PSILI..GREEK DIALYTIKA AND PERISPOMENI
+1FC2..1FC4;N # Ll [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FCC;N # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FCD..1FCF;N # Sk [3] GREEK PSILI AND VARIA..GREEK PSILI AND PERISPOMENI
+1FD0..1FD3;N # Ll [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FDB;N # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FDD..1FDF;N # Sk [3] GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI
+1FE0..1FEC;N # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FED..1FEF;N # Sk [3] GREEK DIALYTIKA AND VARIA..GREEK VARIA
+1FF2..1FF4;N # Ll [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FFC;N # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+1FFD..1FFE;N # Sk [2] GREEK OXIA..GREEK DASIA
+2000..200A;N # Zs [11] EN QUAD..HAIR SPACE
+200B..200F;N # Cf [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK
+2010;A # Pd HYPHEN
+2011..2012;N # Pd [2] NON-BREAKING HYPHEN..FIGURE DASH
+2013..2015;A # Pd [3] EN DASH..HORIZONTAL BAR
+2016;A # Po DOUBLE VERTICAL LINE
+2017;N # Po DOUBLE LOW LINE
+2018;A # Pi LEFT SINGLE QUOTATION MARK
+2019;A # Pf RIGHT SINGLE QUOTATION MARK
+201A;N # Ps SINGLE LOW-9 QUOTATION MARK
+201B;N # Pi SINGLE HIGH-REVERSED-9 QUOTATION MARK
+201C;A # Pi LEFT DOUBLE QUOTATION MARK
+201D;A # Pf RIGHT DOUBLE QUOTATION MARK
+201E;N # Ps DOUBLE LOW-9 QUOTATION MARK
+201F;N # Pi DOUBLE HIGH-REVERSED-9 QUOTATION MARK
+2020..2022;A # Po [3] DAGGER..BULLET
+2023;N # Po TRIANGULAR BULLET
+2024..2027;A # Po [4] ONE DOT LEADER..HYPHENATION POINT
+2028;N # Zl LINE SEPARATOR
+2029;N # Zp PARAGRAPH SEPARATOR
+202A..202E;N # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
+202F;N # Zs NARROW NO-BREAK SPACE
+2030;A # Po PER MILLE SIGN
+2031;N # Po PER TEN THOUSAND SIGN
+2032..2033;A # Po [2] PRIME..DOUBLE PRIME
+2034;N # Po TRIPLE PRIME
+2035;A # Po REVERSED PRIME
+2036..2038;N # Po [3] REVERSED DOUBLE PRIME..CARET
+2039;N # Pi SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+203A;N # Pf SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+203B;A # Po REFERENCE MARK
+203C..203D;N # Po [2] DOUBLE EXCLAMATION MARK..INTERROBANG
+203E;A # Po OVERLINE
+203F..2040;N # Pc [2] UNDERTIE..CHARACTER TIE
+2041..2043;N # Po [3] CARET INSERTION POINT..HYPHEN BULLET
+2044;N # Sm FRACTION SLASH
+2045;N # Ps LEFT SQUARE BRACKET WITH QUILL
+2046;N # Pe RIGHT SQUARE BRACKET WITH QUILL
+2047..2051;N # Po [11] DOUBLE QUESTION MARK..TWO ASTERISKS ALIGNED VERTICALLY
+2052;N # Sm COMMERCIAL MINUS SIGN
+2053;N # Po SWUNG DASH
+2054;N # Pc INVERTED UNDERTIE
+2055..205E;N # Po [10] FLOWER PUNCTUATION MARK..VERTICAL FOUR DOTS
+205F;N # Zs MEDIUM MATHEMATICAL SPACE
+2060..2064;N # Cf [5] WORD JOINER..INVISIBLE PLUS
+2066..206F;N # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
+2070;N # No SUPERSCRIPT ZERO
+2071;N # Lm SUPERSCRIPT LATIN SMALL LETTER I
+2074;A # No SUPERSCRIPT FOUR
+2075..2079;N # No [5] SUPERSCRIPT FIVE..SUPERSCRIPT NINE
+207A..207C;N # Sm [3] SUPERSCRIPT PLUS SIGN..SUPERSCRIPT EQUALS SIGN
+207D;N # Ps SUPERSCRIPT LEFT PARENTHESIS
+207E;N # Pe SUPERSCRIPT RIGHT PARENTHESIS
+207F;A # Lm SUPERSCRIPT LATIN SMALL LETTER N
+2080;N # No SUBSCRIPT ZERO
+2081..2084;A # No [4] SUBSCRIPT ONE..SUBSCRIPT FOUR
+2085..2089;N # No [5] SUBSCRIPT FIVE..SUBSCRIPT NINE
+208A..208C;N # Sm [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN
+208D;N # Ps SUBSCRIPT LEFT PARENTHESIS
+208E;N # Pe SUBSCRIPT RIGHT PARENTHESIS
+2090..209C;N # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T
+20A0..20A8;N # Sc [9] EURO-CURRENCY SIGN..RUPEE SIGN
+20A9;H # Sc WON SIGN
+20AA..20AB;N # Sc [2] NEW SHEQEL SIGN..DONG SIGN
+20AC;A # Sc EURO SIGN
+20AD..20C0;N # Sc [20] KIP SIGN..SOM SIGN
+20D0..20DC;N # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE
+20DD..20E0;N # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH
+20E1;N # Mn COMBINING LEFT RIGHT ARROW ABOVE
+20E2..20E4;N # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE
+20E5..20F0;N # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE
+2100..2101;N # So [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT
+2102;N # Lu DOUBLE-STRUCK CAPITAL C
+2103;A # So DEGREE CELSIUS
+2104;N # So CENTRE LINE SYMBOL
+2105;A # So CARE OF
+2106;N # So CADA UNA
+2107;N # Lu EULER CONSTANT
+2108;N # So SCRUPLE
+2109;A # So DEGREE FAHRENHEIT
+210A..2112;N # L& [9] SCRIPT SMALL G..SCRIPT CAPITAL L
+2113;A # Ll SCRIPT SMALL L
+2114;N # So L B BAR SYMBOL
+2115;N # Lu DOUBLE-STRUCK CAPITAL N
+2116;A # So NUMERO SIGN
+2117;N # So SOUND RECORDING COPYRIGHT
+2118;N # Sm SCRIPT CAPITAL P
+2119..211D;N # Lu [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R
+211E..2120;N # So [3] PRESCRIPTION TAKE..SERVICE MARK
+2121..2122;A # So [2] TELEPHONE SIGN..TRADE MARK SIGN
+2123;N # So VERSICLE
+2124;N # Lu DOUBLE-STRUCK CAPITAL Z
+2125;N # So OUNCE SIGN
+2126;A # Lu OHM SIGN
+2127;N # So INVERTED OHM SIGN
+2128;N # Lu BLACK-LETTER CAPITAL Z
+2129;N # So TURNED GREEK SMALL LETTER IOTA
+212A;N # Lu KELVIN SIGN
+212B;A # Lu ANGSTROM SIGN
+212C..212D;N # Lu [2] SCRIPT CAPITAL B..BLACK-LETTER CAPITAL C
+212E;N # So ESTIMATED SYMBOL
+212F..2134;N # L& [6] SCRIPT SMALL E..SCRIPT SMALL O
+2135..2138;N # Lo [4] ALEF SYMBOL..DALET SYMBOL
+2139;N # Ll INFORMATION SOURCE
+213A..213B;N # So [2] ROTATED CAPITAL Q..FACSIMILE SIGN
+213C..213F;N # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI
+2140..2144;N # Sm [5] DOUBLE-STRUCK N-ARY SUMMATION..TURNED SANS-SERIF CAPITAL Y
+2145..2149;N # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J
+214A;N # So PROPERTY LINE
+214B;N # Sm TURNED AMPERSAND
+214C..214D;N # So [2] PER SIGN..AKTIESELSKAB
+214E;N # Ll TURNED SMALL F
+214F;N # So SYMBOL FOR SAMARITAN SOURCE
+2150..2152;N # No [3] VULGAR FRACTION ONE SEVENTH..VULGAR FRACTION ONE TENTH
+2153..2154;A # No [2] VULGAR FRACTION ONE THIRD..VULGAR FRACTION TWO THIRDS
+2155..215A;N # No [6] VULGAR FRACTION ONE FIFTH..VULGAR FRACTION FIVE SIXTHS
+215B..215E;A # No [4] VULGAR FRACTION ONE EIGHTH..VULGAR FRACTION SEVEN EIGHTHS
+215F;N # No FRACTION NUMERATOR ONE
+2160..216B;A # Nl [12] ROMAN NUMERAL ONE..ROMAN NUMERAL TWELVE
+216C..216F;N # Nl [4] ROMAN NUMERAL FIFTY..ROMAN NUMERAL ONE THOUSAND
+2170..2179;A # Nl [10] SMALL ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL TEN
+217A..2182;N # Nl [9] SMALL ROMAN NUMERAL ELEVEN..ROMAN NUMERAL TEN THOUSAND
+2183..2184;N # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C
+2185..2188;N # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND
+2189;A # No VULGAR FRACTION ZERO THIRDS
+218A..218B;N # So [2] TURNED DIGIT TWO..TURNED DIGIT THREE
+2190..2194;A # Sm [5] LEFTWARDS ARROW..LEFT RIGHT ARROW
+2195..2199;A # So [5] UP DOWN ARROW..SOUTH WEST ARROW
+219A..219B;N # Sm [2] LEFTWARDS ARROW WITH STROKE..RIGHTWARDS ARROW WITH STROKE
+219C..219F;N # So [4] LEFTWARDS WAVE ARROW..UPWARDS TWO HEADED ARROW
+21A0;N # Sm RIGHTWARDS TWO HEADED ARROW
+21A1..21A2;N # So [2] DOWNWARDS TWO HEADED ARROW..LEFTWARDS ARROW WITH TAIL
+21A3;N # Sm RIGHTWARDS ARROW WITH TAIL
+21A4..21A5;N # So [2] LEFTWARDS ARROW FROM BAR..UPWARDS ARROW FROM BAR
+21A6;N # Sm RIGHTWARDS ARROW FROM BAR
+21A7..21AD;N # So [7] DOWNWARDS ARROW FROM BAR..LEFT RIGHT WAVE ARROW
+21AE;N # Sm LEFT RIGHT ARROW WITH STROKE
+21AF..21B7;N # So [9] DOWNWARDS ZIGZAG ARROW..CLOCKWISE TOP SEMICIRCLE ARROW
+21B8..21B9;A # So [2] NORTH WEST ARROW TO LONG BAR..LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR
+21BA..21CD;N # So [20] ANTICLOCKWISE OPEN CIRCLE ARROW..LEFTWARDS DOUBLE ARROW WITH STROKE
+21CE..21CF;N # Sm [2] LEFT RIGHT DOUBLE ARROW WITH STROKE..RIGHTWARDS DOUBLE ARROW WITH STROKE
+21D0..21D1;N # So [2] LEFTWARDS DOUBLE ARROW..UPWARDS DOUBLE ARROW
+21D2;A # Sm RIGHTWARDS DOUBLE ARROW
+21D3;N # So DOWNWARDS DOUBLE ARROW
+21D4;A # Sm LEFT RIGHT DOUBLE ARROW
+21D5..21E6;N # So [18] UP DOWN DOUBLE ARROW..LEFTWARDS WHITE ARROW
+21E7;A # So UPWARDS WHITE ARROW
+21E8..21F3;N # So [12] RIGHTWARDS WHITE ARROW..UP DOWN WHITE ARROW
+21F4..21FF;N # Sm [12] RIGHT ARROW WITH SMALL CIRCLE..LEFT RIGHT OPEN-HEADED ARROW
+2200;A # Sm FOR ALL
+2201;N # Sm COMPLEMENT
+2202..2203;A # Sm [2] PARTIAL DIFFERENTIAL..THERE EXISTS
+2204..2206;N # Sm [3] THERE DOES NOT EXIST..INCREMENT
+2207..2208;A # Sm [2] NABLA..ELEMENT OF
+2209..220A;N # Sm [2] NOT AN ELEMENT OF..SMALL ELEMENT OF
+220B;A # Sm CONTAINS AS MEMBER
+220C..220E;N # Sm [3] DOES NOT CONTAIN AS MEMBER..END OF PROOF
+220F;A # Sm N-ARY PRODUCT
+2210;N # Sm N-ARY COPRODUCT
+2211;A # Sm N-ARY SUMMATION
+2212..2214;N # Sm [3] MINUS SIGN..DOT PLUS
+2215;A # Sm DIVISION SLASH
+2216..2219;N # Sm [4] SET MINUS..BULLET OPERATOR
+221A;A # Sm SQUARE ROOT
+221B..221C;N # Sm [2] CUBE ROOT..FOURTH ROOT
+221D..2220;A # Sm [4] PROPORTIONAL TO..ANGLE
+2221..2222;N # Sm [2] MEASURED ANGLE..SPHERICAL ANGLE
+2223;A # Sm DIVIDES
+2224;N # Sm DOES NOT DIVIDE
+2225;A # Sm PARALLEL TO
+2226;N # Sm NOT PARALLEL TO
+2227..222C;A # Sm [6] LOGICAL AND..DOUBLE INTEGRAL
+222D;N # Sm TRIPLE INTEGRAL
+222E;A # Sm CONTOUR INTEGRAL
+222F..2233;N # Sm [5] SURFACE INTEGRAL..ANTICLOCKWISE CONTOUR INTEGRAL
+2234..2237;A # Sm [4] THEREFORE..PROPORTION
+2238..223B;N # Sm [4] DOT MINUS..HOMOTHETIC
+223C..223D;A # Sm [2] TILDE OPERATOR..REVERSED TILDE
+223E..2247;N # Sm [10] INVERTED LAZY S..NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO
+2248;A # Sm ALMOST EQUAL TO
+2249..224B;N # Sm [3] NOT ALMOST EQUAL TO..TRIPLE TILDE
+224C;A # Sm ALL EQUAL TO
+224D..2251;N # Sm [5] EQUIVALENT TO..GEOMETRICALLY EQUAL TO
+2252;A # Sm APPROXIMATELY EQUAL TO OR THE IMAGE OF
+2253..225F;N # Sm [13] IMAGE OF OR APPROXIMATELY EQUAL TO..QUESTIONED EQUAL TO
+2260..2261;A # Sm [2] NOT EQUAL TO..IDENTICAL TO
+2262..2263;N # Sm [2] NOT IDENTICAL TO..STRICTLY EQUIVALENT TO
+2264..2267;A # Sm [4] LESS-THAN OR EQUAL TO..GREATER-THAN OVER EQUAL TO
+2268..2269;N # Sm [2] LESS-THAN BUT NOT EQUAL TO..GREATER-THAN BUT NOT EQUAL TO
+226A..226B;A # Sm [2] MUCH LESS-THAN..MUCH GREATER-THAN
+226C..226D;N # Sm [2] BETWEEN..NOT EQUIVALENT TO
+226E..226F;A # Sm [2] NOT LESS-THAN..NOT GREATER-THAN
+2270..2281;N # Sm [18] NEITHER LESS-THAN NOR EQUAL TO..DOES NOT SUCCEED
+2282..2283;A # Sm [2] SUBSET OF..SUPERSET OF
+2284..2285;N # Sm [2] NOT A SUBSET OF..NOT A SUPERSET OF
+2286..2287;A # Sm [2] SUBSET OF OR EQUAL TO..SUPERSET OF OR EQUAL TO
+2288..2294;N # Sm [13] NEITHER A SUBSET OF NOR EQUAL TO..SQUARE CUP
+2295;A # Sm CIRCLED PLUS
+2296..2298;N # Sm [3] CIRCLED MINUS..CIRCLED DIVISION SLASH
+2299;A # Sm CIRCLED DOT OPERATOR
+229A..22A4;N # Sm [11] CIRCLED RING OPERATOR..DOWN TACK
+22A5;A # Sm UP TACK
+22A6..22BE;N # Sm [25] ASSERTION..RIGHT ANGLE WITH ARC
+22BF;A # Sm RIGHT TRIANGLE
+22C0..22FF;N # Sm [64] N-ARY LOGICAL AND..Z NOTATION BAG MEMBERSHIP
+2300..2307;N # So [8] DIAMETER SIGN..WAVY LINE
+2308;N # Ps LEFT CEILING
+2309;N # Pe RIGHT CEILING
+230A;N # Ps LEFT FLOOR
+230B;N # Pe RIGHT FLOOR
+230C..2311;N # So [6] BOTTOM RIGHT CROP..SQUARE LOZENGE
+2312;A # So ARC
+2313..2319;N # So [7] SEGMENT..TURNED NOT SIGN
+231A..231B;W # So [2] WATCH..HOURGLASS
+231C..231F;N # So [4] TOP LEFT CORNER..BOTTOM RIGHT CORNER
+2320..2321;N # Sm [2] TOP HALF INTEGRAL..BOTTOM HALF INTEGRAL
+2322..2328;N # So [7] FROWN..KEYBOARD
+2329;W # Ps LEFT-POINTING ANGLE BRACKET
+232A;W # Pe RIGHT-POINTING ANGLE BRACKET
+232B..237B;N # So [81] ERASE TO THE LEFT..NOT CHECK MARK
+237C;N # Sm RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW
+237D..239A;N # So [30] SHOULDERED OPEN BOX..CLEAR SCREEN SYMBOL
+239B..23B3;N # Sm [25] LEFT PARENTHESIS UPPER HOOK..SUMMATION BOTTOM
+23B4..23DB;N # So [40] TOP SQUARE BRACKET..FUSE
+23DC..23E1;N # Sm [6] TOP PARENTHESIS..BOTTOM TORTOISE SHELL BRACKET
+23E2..23E8;N # So [7] WHITE TRAPEZIUM..DECIMAL EXPONENT SYMBOL
+23E9..23EC;W # So [4] BLACK RIGHT-POINTING DOUBLE TRIANGLE..BLACK DOWN-POINTING DOUBLE TRIANGLE
+23ED..23EF;N # So [3] BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR..BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR
+23F0;W # So ALARM CLOCK
+23F1..23F2;N # So [2] STOPWATCH..TIMER CLOCK
+23F3;W # So HOURGLASS WITH FLOWING SAND
+23F4..23FF;N # So [12] BLACK MEDIUM LEFT-POINTING TRIANGLE..OBSERVER EYE SYMBOL
+2400..2426;N # So [39] SYMBOL FOR NULL..SYMBOL FOR SUBSTITUTE FORM TWO
+2440..244A;N # So [11] OCR HOOK..OCR DOUBLE BACKSLASH
+2460..249B;A # No [60] CIRCLED DIGIT ONE..NUMBER TWENTY FULL STOP
+249C..24E9;A # So [78] PARENTHESIZED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z
+24EA;N # No CIRCLED DIGIT ZERO
+24EB..24FF;A # No [21] NEGATIVE CIRCLED NUMBER ELEVEN..NEGATIVE CIRCLED DIGIT ZERO
+2500..254B;A # So [76] BOX DRAWINGS LIGHT HORIZONTAL..BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL
+254C..254F;N # So [4] BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL..BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL
+2550..2573;A # So [36] BOX DRAWINGS DOUBLE HORIZONTAL..BOX DRAWINGS LIGHT DIAGONAL CROSS
+2574..257F;N # So [12] BOX DRAWINGS LIGHT LEFT..BOX DRAWINGS HEAVY UP AND LIGHT DOWN
+2580..258F;A # So [16] UPPER HALF BLOCK..LEFT ONE EIGHTH BLOCK
+2590..2591;N # So [2] RIGHT HALF BLOCK..LIGHT SHADE
+2592..2595;A # So [4] MEDIUM SHADE..RIGHT ONE EIGHTH BLOCK
+2596..259F;N # So [10] QUADRANT LOWER LEFT..QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT
+25A0..25A1;A # So [2] BLACK SQUARE..WHITE SQUARE
+25A2;N # So WHITE SQUARE WITH ROUNDED CORNERS
+25A3..25A9;A # So [7] WHITE SQUARE CONTAINING BLACK SMALL SQUARE..SQUARE WITH DIAGONAL CROSSHATCH FILL
+25AA..25B1;N # So [8] BLACK SMALL SQUARE..WHITE PARALLELOGRAM
+25B2..25B3;A # So [2] BLACK UP-POINTING TRIANGLE..WHITE UP-POINTING TRIANGLE
+25B4..25B5;N # So [2] BLACK UP-POINTING SMALL TRIANGLE..WHITE UP-POINTING SMALL TRIANGLE
+25B6;A # So BLACK RIGHT-POINTING TRIANGLE
+25B7;A # Sm WHITE RIGHT-POINTING TRIANGLE
+25B8..25BB;N # So [4] BLACK RIGHT-POINTING SMALL TRIANGLE..WHITE RIGHT-POINTING POINTER
+25BC..25BD;A # So [2] BLACK DOWN-POINTING TRIANGLE..WHITE DOWN-POINTING TRIANGLE
+25BE..25BF;N # So [2] BLACK DOWN-POINTING SMALL TRIANGLE..WHITE DOWN-POINTING SMALL TRIANGLE
+25C0;A # So BLACK LEFT-POINTING TRIANGLE
+25C1;A # Sm WHITE LEFT-POINTING TRIANGLE
+25C2..25C5;N # So [4] BLACK LEFT-POINTING SMALL TRIANGLE..WHITE LEFT-POINTING POINTER
+25C6..25C8;A # So [3] BLACK DIAMOND..WHITE DIAMOND CONTAINING BLACK SMALL DIAMOND
+25C9..25CA;N # So [2] FISHEYE..LOZENGE
+25CB;A # So WHITE CIRCLE
+25CC..25CD;N # So [2] DOTTED CIRCLE..CIRCLE WITH VERTICAL FILL
+25CE..25D1;A # So [4] BULLSEYE..CIRCLE WITH RIGHT HALF BLACK
+25D2..25E1;N # So [16] CIRCLE WITH LOWER HALF BLACK..LOWER HALF CIRCLE
+25E2..25E5;A # So [4] BLACK LOWER RIGHT TRIANGLE..BLACK UPPER RIGHT TRIANGLE
+25E6..25EE;N # So [9] WHITE BULLET..UP-POINTING TRIANGLE WITH RIGHT HALF BLACK
+25EF;A # So LARGE CIRCLE
+25F0..25F7;N # So [8] WHITE SQUARE WITH UPPER LEFT QUADRANT..WHITE CIRCLE WITH UPPER RIGHT QUADRANT
+25F8..25FC;N # Sm [5] UPPER LEFT TRIANGLE..BLACK MEDIUM SQUARE
+25FD..25FE;W # Sm [2] WHITE MEDIUM SMALL SQUARE..BLACK MEDIUM SMALL SQUARE
+25FF;N # Sm LOWER RIGHT TRIANGLE
+2600..2604;N # So [5] BLACK SUN WITH RAYS..COMET
+2605..2606;A # So [2] BLACK STAR..WHITE STAR
+2607..2608;N # So [2] LIGHTNING..THUNDERSTORM
+2609;A # So SUN
+260A..260D;N # So [4] ASCENDING NODE..OPPOSITION
+260E..260F;A # So [2] BLACK TELEPHONE..WHITE TELEPHONE
+2610..2613;N # So [4] BALLOT BOX..SALTIRE
+2614..2615;W # So [2] UMBRELLA WITH RAIN DROPS..HOT BEVERAGE
+2616..261B;N # So [6] WHITE SHOGI PIECE..BLACK RIGHT POINTING INDEX
+261C;A # So WHITE LEFT POINTING INDEX
+261D;N # So WHITE UP POINTING INDEX
+261E;A # So WHITE RIGHT POINTING INDEX
+261F..263F;N # So [33] WHITE DOWN POINTING INDEX..MERCURY
+2640;A # So FEMALE SIGN
+2641;N # So EARTH
+2642;A # So MALE SIGN
+2643..2647;N # So [5] JUPITER..PLUTO
+2648..2653;W # So [12] ARIES..PISCES
+2654..265F;N # So [12] WHITE CHESS KING..BLACK CHESS PAWN
+2660..2661;A # So [2] BLACK SPADE SUIT..WHITE HEART SUIT
+2662;N # So WHITE DIAMOND SUIT
+2663..2665;A # So [3] BLACK CLUB SUIT..BLACK HEART SUIT
+2666;N # So BLACK DIAMOND SUIT
+2667..266A;A # So [4] WHITE CLUB SUIT..EIGHTH NOTE
+266B;N # So BEAMED EIGHTH NOTES
+266C..266D;A # So [2] BEAMED SIXTEENTH NOTES..MUSIC FLAT SIGN
+266E;N # So MUSIC NATURAL SIGN
+266F;A # Sm MUSIC SHARP SIGN
+2670..267E;N # So [15] WEST SYRIAC CROSS..PERMANENT PAPER SIGN
+267F;W # So WHEELCHAIR SYMBOL
+2680..2692;N # So [19] DIE FACE-1..HAMMER AND PICK
+2693;W # So ANCHOR
+2694..269D;N # So [10] CROSSED SWORDS..OUTLINED WHITE STAR
+269E..269F;A # So [2] THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT
+26A0;N # So WARNING SIGN
+26A1;W # So HIGH VOLTAGE SIGN
+26A2..26A9;N # So [8] DOUBLED FEMALE SIGN..HORIZONTAL MALE WITH STROKE SIGN
+26AA..26AB;W # So [2] MEDIUM WHITE CIRCLE..MEDIUM BLACK CIRCLE
+26AC..26BC;N # So [17] MEDIUM SMALL WHITE CIRCLE..SESQUIQUADRATE
+26BD..26BE;W # So [2] SOCCER BALL..BASEBALL
+26BF;A # So SQUARED KEY
+26C0..26C3;N # So [4] WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING
+26C4..26C5;W # So [2] SNOWMAN WITHOUT SNOW..SUN BEHIND CLOUD
+26C6..26CD;A # So [8] RAIN..DISABLED CAR
+26CE;W # So OPHIUCHUS
+26CF..26D3;A # So [5] PICK..CHAINS
+26D4;W # So NO ENTRY
+26D5..26E1;A # So [13] ALTERNATE ONE-WAY LEFT WAY TRAFFIC..RESTRICTED LEFT ENTRY-2
+26E2;N # So ASTRONOMICAL SYMBOL FOR URANUS
+26E3;A # So HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE
+26E4..26E7;N # So [4] PENTAGRAM..INVERTED PENTAGRAM
+26E8..26E9;A # So [2] BLACK CROSS ON SHIELD..SHINTO SHRINE
+26EA;W # So CHURCH
+26EB..26F1;A # So [7] CASTLE..UMBRELLA ON GROUND
+26F2..26F3;W # So [2] FOUNTAIN..FLAG IN HOLE
+26F4;A # So FERRY
+26F5;W # So SAILBOAT
+26F6..26F9;A # So [4] SQUARE FOUR CORNERS..PERSON WITH BALL
+26FA;W # So TENT
+26FB..26FC;A # So [2] JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL
+26FD;W # So FUEL PUMP
+26FE..26FF;A # So [2] CUP ON BLACK SQUARE..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE
+2700..2704;N # So [5] BLACK SAFETY SCISSORS..WHITE SCISSORS
+2705;W # So WHITE HEAVY CHECK MARK
+2706..2709;N # So [4] TELEPHONE LOCATION SIGN..ENVELOPE
+270A..270B;W # So [2] RAISED FIST..RAISED HAND
+270C..2727;N # So [28] VICTORY HAND..WHITE FOUR POINTED STAR
+2728;W # So SPARKLES
+2729..273C;N # So [20] STRESS OUTLINED WHITE STAR..OPEN CENTRE TEARDROP-SPOKED ASTERISK
+273D;A # So HEAVY TEARDROP-SPOKED ASTERISK
+273E..274B;N # So [14] SIX PETALLED BLACK AND WHITE FLORETTE..HEAVY EIGHT TEARDROP-SPOKED PROPELLER ASTERISK
+274C;W # So CROSS MARK
+274D;N # So SHADOWED WHITE CIRCLE
+274E;W # So NEGATIVE SQUARED CROSS MARK
+274F..2752;N # So [4] LOWER RIGHT DROP-SHADOWED WHITE SQUARE..UPPER RIGHT SHADOWED WHITE SQUARE
+2753..2755;W # So [3] BLACK QUESTION MARK ORNAMENT..WHITE EXCLAMATION MARK ORNAMENT
+2756;N # So BLACK DIAMOND MINUS WHITE X
+2757;W # So HEAVY EXCLAMATION MARK SYMBOL
+2758..2767;N # So [16] LIGHT VERTICAL BAR..ROTATED FLORAL HEART BULLET
+2768;N # Ps MEDIUM LEFT PARENTHESIS ORNAMENT
+2769;N # Pe MEDIUM RIGHT PARENTHESIS ORNAMENT
+276A;N # Ps MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT
+276B;N # Pe MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT
+276C;N # Ps MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT
+276D;N # Pe MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT
+276E;N # Ps HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT
+276F;N # Pe HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT
+2770;N # Ps HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT
+2771;N # Pe HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT
+2772;N # Ps LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT
+2773;N # Pe LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT
+2774;N # Ps MEDIUM LEFT CURLY BRACKET ORNAMENT
+2775;N # Pe MEDIUM RIGHT CURLY BRACKET ORNAMENT
+2776..277F;A # No [10] DINGBAT NEGATIVE CIRCLED DIGIT ONE..DINGBAT NEGATIVE CIRCLED NUMBER TEN
+2780..2793;N # No [20] DINGBAT CIRCLED SANS-SERIF DIGIT ONE..DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN
+2794;N # So HEAVY WIDE-HEADED RIGHTWARDS ARROW
+2795..2797;W # So [3] HEAVY PLUS SIGN..HEAVY DIVISION SIGN
+2798..27AF;N # So [24] HEAVY SOUTH EAST ARROW..NOTCHED LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW
+27B0;W # So CURLY LOOP
+27B1..27BE;N # So [14] NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW..OPEN-OUTLINED RIGHTWARDS ARROW
+27BF;W # So DOUBLE CURLY LOOP
+27C0..27C4;N # Sm [5] THREE DIMENSIONAL ANGLE..OPEN SUPERSET
+27C5;N # Ps LEFT S-SHAPED BAG DELIMITER
+27C6;N # Pe RIGHT S-SHAPED BAG DELIMITER
+27C7..27E5;N # Sm [31] OR WITH DOT INSIDE..WHITE SQUARE WITH RIGHTWARDS TICK
+27E6;Na # Ps MATHEMATICAL LEFT WHITE SQUARE BRACKET
+27E7;Na # Pe MATHEMATICAL RIGHT WHITE SQUARE BRACKET
+27E8;Na # Ps MATHEMATICAL LEFT ANGLE BRACKET
+27E9;Na # Pe MATHEMATICAL RIGHT ANGLE BRACKET
+27EA;Na # Ps MATHEMATICAL LEFT DOUBLE ANGLE BRACKET
+27EB;Na # Pe MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET
+27EC;Na # Ps MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET
+27ED;Na # Pe MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET
+27EE;N # Ps MATHEMATICAL LEFT FLATTENED PARENTHESIS
+27EF;N # Pe MATHEMATICAL RIGHT FLATTENED PARENTHESIS
+27F0..27FF;N # Sm [16] UPWARDS QUADRUPLE ARROW..LONG RIGHTWARDS SQUIGGLE ARROW
+2800..28FF;N # So [256] BRAILLE PATTERN BLANK..BRAILLE PATTERN DOTS-12345678
+2900..297F;N # Sm [128] RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE..DOWN FISH TAIL
+2980..2982;N # Sm [3] TRIPLE VERTICAL BAR DELIMITER..Z NOTATION TYPE COLON
+2983;N # Ps LEFT WHITE CURLY BRACKET
+2984;N # Pe RIGHT WHITE CURLY BRACKET
+2985;Na # Ps LEFT WHITE PARENTHESIS
+2986;Na # Pe RIGHT WHITE PARENTHESIS
+2987;N # Ps Z NOTATION LEFT IMAGE BRACKET
+2988;N # Pe Z NOTATION RIGHT IMAGE BRACKET
+2989;N # Ps Z NOTATION LEFT BINDING BRACKET
+298A;N # Pe Z NOTATION RIGHT BINDING BRACKET
+298B;N # Ps LEFT SQUARE BRACKET WITH UNDERBAR
+298C;N # Pe RIGHT SQUARE BRACKET WITH UNDERBAR
+298D;N # Ps LEFT SQUARE BRACKET WITH TICK IN TOP CORNER
+298E;N # Pe RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
+298F;N # Ps LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
+2990;N # Pe RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER
+2991;N # Ps LEFT ANGLE BRACKET WITH DOT
+2992;N # Pe RIGHT ANGLE BRACKET WITH DOT
+2993;N # Ps LEFT ARC LESS-THAN BRACKET
+2994;N # Pe RIGHT ARC GREATER-THAN BRACKET
+2995;N # Ps DOUBLE LEFT ARC GREATER-THAN BRACKET
+2996;N # Pe DOUBLE RIGHT ARC LESS-THAN BRACKET
+2997;N # Ps LEFT BLACK TORTOISE SHELL BRACKET
+2998;N # Pe RIGHT BLACK TORTOISE SHELL BRACKET
+2999..29D7;N # Sm [63] DOTTED FENCE..BLACK HOURGLASS
+29D8;N # Ps LEFT WIGGLY FENCE
+29D9;N # Pe RIGHT WIGGLY FENCE
+29DA;N # Ps LEFT DOUBLE WIGGLY FENCE
+29DB;N # Pe RIGHT DOUBLE WIGGLY FENCE
+29DC..29FB;N # Sm [32] INCOMPLETE INFINITY..TRIPLE PLUS
+29FC;N # Ps LEFT-POINTING CURVED ANGLE BRACKET
+29FD;N # Pe RIGHT-POINTING CURVED ANGLE BRACKET
+29FE..29FF;N # Sm [2] TINY..MINY
+2A00..2AFF;N # Sm [256] N-ARY CIRCLED DOT OPERATOR..N-ARY WHITE VERTICAL BAR
+2B00..2B1A;N # So [27] NORTH EAST WHITE ARROW..DOTTED SQUARE
+2B1B..2B1C;W # So [2] BLACK LARGE SQUARE..WHITE LARGE SQUARE
+2B1D..2B2F;N # So [19] BLACK VERY SMALL SQUARE..WHITE VERTICAL ELLIPSE
+2B30..2B44;N # Sm [21] LEFT ARROW WITH SMALL CIRCLE..RIGHTWARDS ARROW THROUGH SUPERSET
+2B45..2B46;N # So [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW
+2B47..2B4C;N # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR
+2B4D..2B4F;N # So [3] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..SHORT BACKSLANTED SOUTH ARROW
+2B50;W # So WHITE MEDIUM STAR
+2B51..2B54;N # So [4] BLACK SMALL STAR..WHITE RIGHT-POINTING PENTAGON
+2B55;W # So HEAVY LARGE CIRCLE
+2B56..2B59;A # So [4] HEAVY OVAL WITH OVAL INSIDE..HEAVY CIRCLED SALTIRE
+2B5A..2B73;N # So [26] SLANTED NORTH ARROW WITH HOOKED HEAD..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR
+2B76..2B95;N # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW
+2B97..2BFF;N # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL
+2C00..2C5F;N # L& [96] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC SMALL LETTER CAUDATE CHRIVI
+2C60..2C7B;N # L& [28] LATIN CAPITAL LETTER L WITH DOUBLE BAR..LATIN LETTER SMALL CAPITAL TURNED E
+2C7C..2C7D;N # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
+2C7E..2C7F;N # Lu [2] LATIN CAPITAL LETTER S WITH SWASH TAIL..LATIN CAPITAL LETTER Z WITH SWASH TAIL
+2C80..2CE4;N # L& [101] COPTIC CAPITAL LETTER ALFA..COPTIC SYMBOL KAI
+2CE5..2CEA;N # So [6] COPTIC SYMBOL MI RO..COPTIC SYMBOL SHIMA SIMA
+2CEB..2CEE;N # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CEF..2CF1;N # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS
+2CF2..2CF3;N # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI
+2CF9..2CFC;N # Po [4] COPTIC OLD NUBIAN FULL STOP..COPTIC OLD NUBIAN VERSE DIVIDER
+2CFD;N # No COPTIC FRACTION ONE HALF
+2CFE..2CFF;N # Po [2] COPTIC FULL STOP..COPTIC MORPHOLOGICAL DIVIDER
+2D00..2D25;N # Ll [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27;N # Ll GEORGIAN SMALL LETTER YN
+2D2D;N # Ll GEORGIAN SMALL LETTER AEN
+2D30..2D67;N # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO
+2D6F;N # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK
+2D70;N # Po TIFINAGH SEPARATOR MARK
+2D7F;N # Mn TIFINAGH CONSONANT JOINER
+2D80..2D96;N # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE
+2DA0..2DA6;N # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO
+2DA8..2DAE;N # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO
+2DB0..2DB6;N # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO
+2DB8..2DBE;N # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO
+2DC0..2DC6;N # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO
+2DC8..2DCE;N # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO
+2DD0..2DD6;N # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO
+2DD8..2DDE;N # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO
+2DE0..2DFF;N # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
+2E00..2E01;N # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER
+2E02;N # Pi LEFT SUBSTITUTION BRACKET
+2E03;N # Pf RIGHT SUBSTITUTION BRACKET
+2E04;N # Pi LEFT DOTTED SUBSTITUTION BRACKET
+2E05;N # Pf RIGHT DOTTED SUBSTITUTION BRACKET
+2E06..2E08;N # Po [3] RAISED INTERPOLATION MARKER..DOTTED TRANSPOSITION MARKER
+2E09;N # Pi LEFT TRANSPOSITION BRACKET
+2E0A;N # Pf RIGHT TRANSPOSITION BRACKET
+2E0B;N # Po RAISED SQUARE
+2E0C;N # Pi LEFT RAISED OMISSION BRACKET
+2E0D;N # Pf RIGHT RAISED OMISSION BRACKET
+2E0E..2E16;N # Po [9] EDITORIAL CORONIS..DOTTED RIGHT-POINTING ANGLE
+2E17;N # Pd DOUBLE OBLIQUE HYPHEN
+2E18..2E19;N # Po [2] INVERTED INTERROBANG..PALM BRANCH
+2E1A;N # Pd HYPHEN WITH DIAERESIS
+2E1B;N # Po TILDE WITH RING ABOVE
+2E1C;N # Pi LEFT LOW PARAPHRASE BRACKET
+2E1D;N # Pf RIGHT LOW PARAPHRASE BRACKET
+2E1E..2E1F;N # Po [2] TILDE WITH DOT ABOVE..TILDE WITH DOT BELOW
+2E20;N # Pi LEFT VERTICAL BAR WITH QUILL
+2E21;N # Pf RIGHT VERTICAL BAR WITH QUILL
+2E22;N # Ps TOP LEFT HALF BRACKET
+2E23;N # Pe TOP RIGHT HALF BRACKET
+2E24;N # Ps BOTTOM LEFT HALF BRACKET
+2E25;N # Pe BOTTOM RIGHT HALF BRACKET
+2E26;N # Ps LEFT SIDEWAYS U BRACKET
+2E27;N # Pe RIGHT SIDEWAYS U BRACKET
+2E28;N # Ps LEFT DOUBLE PARENTHESIS
+2E29;N # Pe RIGHT DOUBLE PARENTHESIS
+2E2A..2E2E;N # Po [5] TWO DOTS OVER ONE DOT PUNCTUATION..REVERSED QUESTION MARK
+2E2F;N # Lm VERTICAL TILDE
+2E30..2E39;N # Po [10] RING POINT..TOP HALF SECTION SIGN
+2E3A..2E3B;N # Pd [2] TWO-EM DASH..THREE-EM DASH
+2E3C..2E3F;N # Po [4] STENOGRAPHIC FULL STOP..CAPITULUM
+2E40;N # Pd DOUBLE HYPHEN
+2E41;N # Po REVERSED COMMA
+2E42;N # Ps DOUBLE LOW-REVERSED-9 QUOTATION MARK
+2E43..2E4F;N # Po [13] DASH WITH LEFT UPTURN..CORNISH VERSE DIVIDER
+2E50..2E51;N # So [2] CROSS PATTY WITH RIGHT CROSSBAR..CROSS PATTY WITH LEFT CROSSBAR
+2E52..2E54;N # Po [3] TIRONIAN SIGN CAPITAL ET..MEDIEVAL QUESTION MARK
+2E55;N # Ps LEFT SQUARE BRACKET WITH STROKE
+2E56;N # Pe RIGHT SQUARE BRACKET WITH STROKE
+2E57;N # Ps LEFT SQUARE BRACKET WITH DOUBLE STROKE
+2E58;N # Pe RIGHT SQUARE BRACKET WITH DOUBLE STROKE
+2E59;N # Ps TOP HALF LEFT PARENTHESIS
+2E5A;N # Pe TOP HALF RIGHT PARENTHESIS
+2E5B;N # Ps BOTTOM HALF LEFT PARENTHESIS
+2E5C;N # Pe BOTTOM HALF RIGHT PARENTHESIS
+2E5D;N # Pd OBLIQUE HYPHEN
+2E80..2E99;W # So [26] CJK RADICAL REPEAT..CJK RADICAL RAP
+2E9B..2EF3;W # So [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE
+2F00..2FD5;W # So [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE
+2FF0..2FFB;W # So [12] IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER OVERLAID
+3000;F # Zs IDEOGRAPHIC SPACE
+3001..3003;W # Po [3] IDEOGRAPHIC COMMA..DITTO MARK
+3004;W # So JAPANESE INDUSTRIAL STANDARD SYMBOL
+3005;W # Lm IDEOGRAPHIC ITERATION MARK
+3006;W # Lo IDEOGRAPHIC CLOSING MARK
+3007;W # Nl IDEOGRAPHIC NUMBER ZERO
+3008;W # Ps LEFT ANGLE BRACKET
+3009;W # Pe RIGHT ANGLE BRACKET
+300A;W # Ps LEFT DOUBLE ANGLE BRACKET
+300B;W # Pe RIGHT DOUBLE ANGLE BRACKET
+300C;W # Ps LEFT CORNER BRACKET
+300D;W # Pe RIGHT CORNER BRACKET
+300E;W # Ps LEFT WHITE CORNER BRACKET
+300F;W # Pe RIGHT WHITE CORNER BRACKET
+3010;W # Ps LEFT BLACK LENTICULAR BRACKET
+3011;W # Pe RIGHT BLACK LENTICULAR BRACKET
+3012..3013;W # So [2] POSTAL MARK..GETA MARK
+3014;W # Ps LEFT TORTOISE SHELL BRACKET
+3015;W # Pe RIGHT TORTOISE SHELL BRACKET
+3016;W # Ps LEFT WHITE LENTICULAR BRACKET
+3017;W # Pe RIGHT WHITE LENTICULAR BRACKET
+3018;W # Ps LEFT WHITE TORTOISE SHELL BRACKET
+3019;W # Pe RIGHT WHITE TORTOISE SHELL BRACKET
+301A;W # Ps LEFT WHITE SQUARE BRACKET
+301B;W # Pe RIGHT WHITE SQUARE BRACKET
+301C;W # Pd WAVE DASH
+301D;W # Ps REVERSED DOUBLE PRIME QUOTATION MARK
+301E..301F;W # Pe [2] DOUBLE PRIME QUOTATION MARK..LOW DOUBLE PRIME QUOTATION MARK
+3020;W # So POSTAL MARK FACE
+3021..3029;W # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE
+302A..302D;W # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK
+302E..302F;W # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK
+3030;W # Pd WAVY DASH
+3031..3035;W # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF
+3036..3037;W # So [2] CIRCLED POSTAL MARK..IDEOGRAPHIC TELEGRAPH LINE FEED SEPARATOR SYMBOL
+3038..303A;W # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY
+303B;W # Lm VERTICAL IDEOGRAPHIC ITERATION MARK
+303C;W # Lo MASU MARK
+303D;W # Po PART ALTERNATION MARK
+303E;W # So IDEOGRAPHIC VARIATION INDICATOR
+303F;N # So IDEOGRAPHIC HALF FILL SPACE
+3041..3096;W # Lo [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE
+3099..309A;W # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+309B..309C;W # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+309D..309E;W # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK
+309F;W # Lo HIRAGANA DIGRAPH YORI
+30A0;W # Pd KATAKANA-HIRAGANA DOUBLE HYPHEN
+30A1..30FA;W # Lo [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO
+30FB;W # Po KATAKANA MIDDLE DOT
+30FC..30FE;W # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK
+30FF;W # Lo KATAKANA DIGRAPH KOTO
+3105..312F;W # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN
+3131..318E;W # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE
+3190..3191;W # So [2] IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION REVERSE MARK
+3192..3195;W # No [4] IDEOGRAPHIC ANNOTATION ONE MARK..IDEOGRAPHIC ANNOTATION FOUR MARK
+3196..319F;W # So [10] IDEOGRAPHIC ANNOTATION TOP MARK..IDEOGRAPHIC ANNOTATION MAN MARK
+31A0..31BF;W # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH
+31C0..31E3;W # So [36] CJK STROKE T..CJK STROKE Q
+31F0..31FF;W # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO
+3200..321E;W # So [31] PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU
+3220..3229;W # No [10] PARENTHESIZED IDEOGRAPH ONE..PARENTHESIZED IDEOGRAPH TEN
+322A..3247;W # So [30] PARENTHESIZED IDEOGRAPH MOON..CIRCLED IDEOGRAPH KOTO
+3248..324F;A # No [8] CIRCLED NUMBER TEN ON BLACK SQUARE..CIRCLED NUMBER EIGHTY ON BLACK SQUARE
+3250;W # So PARTNERSHIP SIGN
+3251..325F;W # No [15] CIRCLED NUMBER TWENTY ONE..CIRCLED NUMBER THIRTY FIVE
+3260..327F;W # So [32] CIRCLED HANGUL KIYEOK..KOREAN STANDARD SYMBOL
+3280..3289;W # No [10] CIRCLED IDEOGRAPH ONE..CIRCLED IDEOGRAPH TEN
+328A..32B0;W # So [39] CIRCLED IDEOGRAPH MOON..CIRCLED IDEOGRAPH NIGHT
+32B1..32BF;W # No [15] CIRCLED NUMBER THIRTY SIX..CIRCLED NUMBER FIFTY
+32C0..32FF;W # So [64] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..SQUARE ERA NAME REIWA
+3300..33FF;W # So [256] SQUARE APAATO..SQUARE GAL
+3400..4DBF;W # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF
+4DC0..4DFF;N # So [64] HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION
+4E00..9FFF;W # Lo [20992] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FFF
+A000..A014;W # Lo [21] YI SYLLABLE IT..YI SYLLABLE E
+A015;W # Lm YI SYLLABLE WU
+A016..A48C;W # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR
+A490..A4C6;W # So [55] YI RADICAL QOT..YI RADICAL KE
+A4D0..A4F7;N # Lo [40] LISU LETTER BA..LISU LETTER OE
+A4F8..A4FD;N # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU
+A4FE..A4FF;N # Po [2] LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP
+A500..A60B;N # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG
+A60C;N # Lm VAI SYLLABLE LENGTHENER
+A60D..A60F;N # Po [3] VAI COMMA..VAI QUESTION MARK
+A610..A61F;N # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG
+A620..A629;N # Nd [10] VAI DIGIT ZERO..VAI DIGIT NINE
+A62A..A62B;N # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO
+A640..A66D;N # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A66E;N # Lo CYRILLIC LETTER MULTIOCULAR O
+A66F;N # Mn COMBINING CYRILLIC VZMET
+A670..A672;N # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN
+A673;N # Po SLAVONIC ASTERISK
+A674..A67D;N # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK
+A67E;N # Po CYRILLIC KAVYKA
+A67F;N # Lm CYRILLIC PAYEROK
+A680..A69B;N # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O
+A69C..A69D;N # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
+A69E..A69F;N # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+A6A0..A6E5;N # Lo [70] BAMUM LETTER A..BAMUM LETTER KI
+A6E6..A6EF;N # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM
+A6F0..A6F1;N # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS
+A6F2..A6F7;N # Po [6] BAMUM NJAEMLI..BAMUM QUESTION MARK
+A700..A716;N # Sk [23] MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR
+A717..A71F;N # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK
+A720..A721;N # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE
+A722..A76F;N # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON
+A770;N # Lm MODIFIER LETTER US
+A771..A787;N # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T
+A788;N # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT
+A789..A78A;N # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN
+A78B..A78E;N # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT
+A78F;N # Lo LATIN LETTER SINOLOGICAL DOT
+A790..A7CA;N # L& [59] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY
+A7D0..A7D1;N # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G
+A7D3;N # Ll LATIN SMALL LETTER DOUBLE THORN
+A7D5..A7D9;N # L& [5] LATIN SMALL LETTER DOUBLE WYNN..LATIN SMALL LETTER SIGMOID S
+A7F2..A7F4;N # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
+A7F5..A7F6;N # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H
+A7F7;N # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I
+A7F8..A7F9;N # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
+A7FA;N # Ll LATIN LETTER SMALL CAPITAL TURNED M
+A7FB..A7FF;N # Lo [5] LATIN EPIGRAPHIC LETTER REVERSED F..LATIN EPIGRAPHIC LETTER ARCHAIC M
+A800..A801;N # Lo [2] SYLOTI NAGRI LETTER A..SYLOTI NAGRI LETTER I
+A802;N # Mn SYLOTI NAGRI SIGN DVISVARA
+A803..A805;N # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O
+A806;N # Mn SYLOTI NAGRI SIGN HASANTA
+A807..A80A;N # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO
+A80B;N # Mn SYLOTI NAGRI SIGN ANUSVARA
+A80C..A822;N # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO
+A823..A824;N # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I
+A825..A826;N # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
+A827;N # Mc SYLOTI NAGRI VOWEL SIGN OO
+A828..A82B;N # So [4] SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4
+A82C;N # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA
+A830..A835;N # No [6] NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC FRACTION THREE SIXTEENTHS
+A836..A837;N # So [2] NORTH INDIC QUARTER MARK..NORTH INDIC PLACEHOLDER MARK
+A838;N # Sc NORTH INDIC RUPEE MARK
+A839;N # So NORTH INDIC QUANTITY MARK
+A840..A873;N # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU
+A874..A877;N # Po [4] PHAGS-PA SINGLE HEAD MARK..PHAGS-PA MARK DOUBLE SHAD
+A880..A881;N # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA
+A882..A8B3;N # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA
+A8B4..A8C3;N # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU
+A8C4..A8C5;N # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU
+A8CE..A8CF;N # Po [2] SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA
+A8D0..A8D9;N # Nd [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE
+A8E0..A8F1;N # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
+A8F2..A8F7;N # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA
+A8F8..A8FA;N # Po [3] DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET
+A8FB;N # Lo DEVANAGARI HEADSTROKE
+A8FC;N # Po DEVANAGARI SIGN SIDDHAM
+A8FD..A8FE;N # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY
+A8FF;N # Mn DEVANAGARI VOWEL SIGN AY
+A900..A909;N # Nd [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE
+A90A..A925;N # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO
+A926..A92D;N # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU
+A92E..A92F;N # Po [2] KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA
+A930..A946;N # Lo [23] REJANG LETTER KA..REJANG LETTER A
+A947..A951;N # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
+A952..A953;N # Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA
+A95F;N # Po REJANG SECTION MARK
+A960..A97C;W # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH
+A980..A982;N # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
+A983;N # Mc JAVANESE SIGN WIGNYAN
+A984..A9B2;N # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA
+A9B3;N # Mn JAVANESE SIGN CECAK TELU
+A9B4..A9B5;N # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG
+A9B6..A9B9;N # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
+A9BA..A9BB;N # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE
+A9BC..A9BD;N # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+A9BE..A9C0;N # Mc [3] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE PANGKON
+A9C1..A9CD;N # Po [13] JAVANESE LEFT RERENGGAN..JAVANESE TURNED PADA PISELEH
+A9CF;N # Lm JAVANESE PANGRANGKEP
+A9D0..A9D9;N # Nd [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE
+A9DE..A9DF;N # Po [2] JAVANESE PADA TIRTA TUMETES..JAVANESE PADA ISEN-ISEN
+A9E0..A9E4;N # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA
+A9E5;N # Mn MYANMAR SIGN SHAN SAW
+A9E6;N # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION
+A9E7..A9EF;N # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA
+A9F0..A9F9;N # Nd [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE
+A9FA..A9FE;N # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA
+AA00..AA28;N # Lo [41] CHAM LETTER A..CHAM LETTER HA
+AA29..AA2E;N # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
+AA2F..AA30;N # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI
+AA31..AA32;N # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
+AA33..AA34;N # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA
+AA35..AA36;N # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA
+AA40..AA42;N # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG
+AA43;N # Mn CHAM CONSONANT SIGN FINAL NG
+AA44..AA4B;N # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS
+AA4C;N # Mn CHAM CONSONANT SIGN FINAL M
+AA4D;N # Mc CHAM CONSONANT SIGN FINAL H
+AA50..AA59;N # Nd [10] CHAM DIGIT ZERO..CHAM DIGIT NINE
+AA5C..AA5F;N # Po [4] CHAM PUNCTUATION SPIRAL..CHAM PUNCTUATION TRIPLE DANDA
+AA60..AA6F;N # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA
+AA70;N # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION
+AA71..AA76;N # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM
+AA77..AA79;N # So [3] MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SYMBOL AITON TWO
+AA7A;N # Lo MYANMAR LETTER AITON RA
+AA7B;N # Mc MYANMAR SIGN PAO KAREN TONE
+AA7C;N # Mn MYANMAR SIGN TAI LAING TONE-2
+AA7D;N # Mc MYANMAR SIGN TAI LAING TONE-5
+AA7E..AA7F;N # Lo [2] MYANMAR LETTER SHWE PALAUNG CHA..MYANMAR LETTER SHWE PALAUNG SHA
+AA80..AAAF;N # Lo [48] TAI VIET LETTER LOW KO..TAI VIET LETTER HIGH O
+AAB0;N # Mn TAI VIET MAI KANG
+AAB1;N # Lo TAI VIET VOWEL AA
+AAB2..AAB4;N # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
+AAB5..AAB6;N # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O
+AAB7..AAB8;N # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
+AAB9..AABD;N # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN
+AABE..AABF;N # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK
+AAC0;N # Lo TAI VIET TONE MAI NUENG
+AAC1;N # Mn TAI VIET TONE MAI THO
+AAC2;N # Lo TAI VIET TONE MAI SONG
+AADB..AADC;N # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG
+AADD;N # Lm TAI VIET SYMBOL SAM
+AADE..AADF;N # Po [2] TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI KOI
+AAE0..AAEA;N # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA
+AAEB;N # Mc MEETEI MAYEK VOWEL SIGN II
+AAEC..AAED;N # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI
+AAEE..AAEF;N # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU
+AAF0..AAF1;N # Po [2] MEETEI MAYEK CHEIKHAN..MEETEI MAYEK AHANG KHUDAM
+AAF2;N # Lo MEETEI MAYEK ANJI
+AAF3..AAF4;N # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK
+AAF5;N # Mc MEETEI MAYEK VOWEL SIGN VISARGA
+AAF6;N # Mn MEETEI MAYEK VIRAMA
+AB01..AB06;N # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO
+AB09..AB0E;N # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO
+AB11..AB16;N # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO
+AB20..AB26;N # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO
+AB28..AB2E;N # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO
+AB30..AB5A;N # Ll [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG
+AB5B;N # Sk MODIFIER BREVE WITH INVERTED BREVE
+AB5C..AB5F;N # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB60..AB68;N # Ll [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE
+AB69;N # Lm MODIFIER LETTER SMALL TURNED W
+AB6A..AB6B;N # Sk [2] MODIFIER LETTER LEFT TACK..MODIFIER LETTER RIGHT TACK
+AB70..ABBF;N # Ll [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+ABC0..ABE2;N # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM
+ABE3..ABE4;N # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP
+ABE5;N # Mn MEETEI MAYEK VOWEL SIGN ANAP
+ABE6..ABE7;N # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP
+ABE8;N # Mn MEETEI MAYEK VOWEL SIGN UNAP
+ABE9..ABEA;N # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG
+ABEB;N # Po MEETEI MAYEK CHEIKHEI
+ABEC;N # Mc MEETEI MAYEK LUM IYEK
+ABED;N # Mn MEETEI MAYEK APUN IYEK
+ABF0..ABF9;N # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE
+AC00..D7A3;W # Lo [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH
+D7B0..D7C6;N # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E
+D7CB..D7FB;N # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH
+D800..DB7F;N # Cs [896] <surrogate-D800>..<surrogate-DB7F>
+DB80..DBFF;N # Cs [128] <surrogate-DB80>..<surrogate-DBFF>
+DC00..DFFF;N # Cs [1024] <surrogate-DC00>..<surrogate-DFFF>
+E000..F8FF;A # Co [6400] <private-use-E000>..<private-use-F8FF>
+F900..FA6D;W # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D
+FA6E..FA6F;W # Cn [2] <reserved-FA6E>..<reserved-FA6F>
+FA70..FAD9;W # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9
+FADA..FAFF;W # Cn [38] <reserved-FADA>..<reserved-FAFF>
+FB00..FB06;N # Ll [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17;N # Ll [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FB1D;N # Lo HEBREW LETTER YOD WITH HIRIQ
+FB1E;N # Mn HEBREW POINT JUDEO-SPANISH VARIKA
+FB1F..FB28;N # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV
+FB29;N # Sm HEBREW LETTER ALTERNATIVE PLUS SIGN
+FB2A..FB36;N # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH
+FB38..FB3C;N # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH
+FB3E;N # Lo HEBREW LETTER MEM WITH DAGESH
+FB40..FB41;N # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH
+FB43..FB44;N # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH
+FB46..FB4F;N # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATURE ALEF LAMED
+FB50..FBB1;N # Lo [98] ARABIC LETTER ALEF WASLA ISOLATED FORM..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM
+FBB2..FBC2;N # Sk [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE
+FBD3..FD3D;N # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM
+FD3E;N # Pe ORNATE LEFT PARENTHESIS
+FD3F;N # Ps ORNATE RIGHT PARENTHESIS
+FD40..FD4F;N # So [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH
+FD50..FD8F;N # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM
+FD92..FDC7;N # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM
+FDCF;N # So ARABIC LIGATURE SALAAMUHU ALAYNAA
+FDF0..FDFB;N # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU
+FDFC;N # Sc RIAL SIGN
+FDFD..FDFF;N # So [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL
+FE00..FE0F;A # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
+FE10..FE16;W # Po [7] PRESENTATION FORM FOR VERTICAL COMMA..PRESENTATION FORM FOR VERTICAL QUESTION MARK
+FE17;W # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET
+FE18;W # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET
+FE19;W # Po PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS
+FE20..FE2F;N # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF
+FE30;W # Po PRESENTATION FORM FOR VERTICAL TWO DOT LEADER
+FE31..FE32;W # Pd [2] PRESENTATION FORM FOR VERTICAL EM DASH..PRESENTATION FORM FOR VERTICAL EN DASH
+FE33..FE34;W # Pc [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE
+FE35;W # Ps PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS
+FE36;W # Pe PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS
+FE37;W # Ps PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET
+FE38;W # Pe PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET
+FE39;W # Ps PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET
+FE3A;W # Pe PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET
+FE3B;W # Ps PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET
+FE3C;W # Pe PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET
+FE3D;W # Ps PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET
+FE3E;W # Pe PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET
+FE3F;W # Ps PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET
+FE40;W # Pe PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET
+FE41;W # Ps PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET
+FE42;W # Pe PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET
+FE43;W # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET
+FE44;W # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET
+FE45..FE46;W # Po [2] SESAME DOT..WHITE SESAME DOT
+FE47;W # Ps PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET
+FE48;W # Pe PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET
+FE49..FE4C;W # Po [4] DASHED OVERLINE..DOUBLE WAVY OVERLINE
+FE4D..FE4F;W # Pc [3] DASHED LOW LINE..WAVY LOW LINE
+FE50..FE52;W # Po [3] SMALL COMMA..SMALL FULL STOP
+FE54..FE57;W # Po [4] SMALL SEMICOLON..SMALL EXCLAMATION MARK
+FE58;W # Pd SMALL EM DASH
+FE59;W # Ps SMALL LEFT PARENTHESIS
+FE5A;W # Pe SMALL RIGHT PARENTHESIS
+FE5B;W # Ps SMALL LEFT CURLY BRACKET
+FE5C;W # Pe SMALL RIGHT CURLY BRACKET
+FE5D;W # Ps SMALL LEFT TORTOISE SHELL BRACKET
+FE5E;W # Pe SMALL RIGHT TORTOISE SHELL BRACKET
+FE5F..FE61;W # Po [3] SMALL NUMBER SIGN..SMALL ASTERISK
+FE62;W # Sm SMALL PLUS SIGN
+FE63;W # Pd SMALL HYPHEN-MINUS
+FE64..FE66;W # Sm [3] SMALL LESS-THAN SIGN..SMALL EQUALS SIGN
+FE68;W # Po SMALL REVERSE SOLIDUS
+FE69;W # Sc SMALL DOLLAR SIGN
+FE6A..FE6B;W # Po [2] SMALL PERCENT SIGN..SMALL COMMERCIAL AT
+FE70..FE74;N # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM
+FE76..FEFC;N # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM
+FEFF;N # Cf ZERO WIDTH NO-BREAK SPACE
+FF01..FF03;F # Po [3] FULLWIDTH EXCLAMATION MARK..FULLWIDTH NUMBER SIGN
+FF04;F # Sc FULLWIDTH DOLLAR SIGN
+FF05..FF07;F # Po [3] FULLWIDTH PERCENT SIGN..FULLWIDTH APOSTROPHE
+FF08;F # Ps FULLWIDTH LEFT PARENTHESIS
+FF09;F # Pe FULLWIDTH RIGHT PARENTHESIS
+FF0A;F # Po FULLWIDTH ASTERISK
+FF0B;F # Sm FULLWIDTH PLUS SIGN
+FF0C;F # Po FULLWIDTH COMMA
+FF0D;F # Pd FULLWIDTH HYPHEN-MINUS
+FF0E..FF0F;F # Po [2] FULLWIDTH FULL STOP..FULLWIDTH SOLIDUS
+FF10..FF19;F # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE
+FF1A..FF1B;F # Po [2] FULLWIDTH COLON..FULLWIDTH SEMICOLON
+FF1C..FF1E;F # Sm [3] FULLWIDTH LESS-THAN SIGN..FULLWIDTH GREATER-THAN SIGN
+FF1F..FF20;F # Po [2] FULLWIDTH QUESTION MARK..FULLWIDTH COMMERCIAL AT
+FF21..FF3A;F # Lu [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+FF3B;F # Ps FULLWIDTH LEFT SQUARE BRACKET
+FF3C;F # Po FULLWIDTH REVERSE SOLIDUS
+FF3D;F # Pe FULLWIDTH RIGHT SQUARE BRACKET
+FF3E;F # Sk FULLWIDTH CIRCUMFLEX ACCENT
+FF3F;F # Pc FULLWIDTH LOW LINE
+FF40;F # Sk FULLWIDTH GRAVE ACCENT
+FF41..FF5A;F # Ll [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+FF5B;F # Ps FULLWIDTH LEFT CURLY BRACKET
+FF5C;F # Sm FULLWIDTH VERTICAL LINE
+FF5D;F # Pe FULLWIDTH RIGHT CURLY BRACKET
+FF5E;F # Sm FULLWIDTH TILDE
+FF5F;F # Ps FULLWIDTH LEFT WHITE PARENTHESIS
+FF60;F # Pe FULLWIDTH RIGHT WHITE PARENTHESIS
+FF61;H # Po HALFWIDTH IDEOGRAPHIC FULL STOP
+FF62;H # Ps HALFWIDTH LEFT CORNER BRACKET
+FF63;H # Pe HALFWIDTH RIGHT CORNER BRACKET
+FF64..FF65;H # Po [2] HALFWIDTH IDEOGRAPHIC COMMA..HALFWIDTH KATAKANA MIDDLE DOT
+FF66..FF6F;H # Lo [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU
+FF70;H # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
+FF71..FF9D;H # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N
+FF9E..FF9F;H # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+FFA0..FFBE;H # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH
+FFC2..FFC7;H # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E
+FFCA..FFCF;H # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE
+FFD2..FFD7;H # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU
+FFDA..FFDC;H # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I
+FFE0..FFE1;F # Sc [2] FULLWIDTH CENT SIGN..FULLWIDTH POUND SIGN
+FFE2;F # Sm FULLWIDTH NOT SIGN
+FFE3;F # Sk FULLWIDTH MACRON
+FFE4;F # So FULLWIDTH BROKEN BAR
+FFE5..FFE6;F # Sc [2] FULLWIDTH YEN SIGN..FULLWIDTH WON SIGN
+FFE8;H # So HALFWIDTH FORMS LIGHT VERTICAL
+FFE9..FFEC;H # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW
+FFED..FFEE;H # So [2] HALFWIDTH BLACK SQUARE..HALFWIDTH WHITE CIRCLE
+FFF9..FFFB;N # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR
+FFFC;N # So OBJECT REPLACEMENT CHARACTER
+FFFD;A # So REPLACEMENT CHARACTER
+10000..1000B;N # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE
+1000D..10026;N # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO
+10028..1003A;N # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO
+1003C..1003D;N # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE
+1003F..1004D;N # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO
+10050..1005D;N # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089
+10080..100FA;N # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305
+10100..10102;N # Po [3] AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MARK
+10107..10133;N # No [45] AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOUSAND
+10137..1013F;N # So [9] AEGEAN WEIGHT BASE UNIT..AEGEAN MEASURE THIRD SUBUNIT
+10140..10174;N # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS
+10175..10178;N # No [4] GREEK ONE HALF SIGN..GREEK THREE QUARTERS SIGN
+10179..10189;N # So [17] GREEK YEAR SIGN..GREEK TRYBLION BASE SIGN
+1018A..1018B;N # No [2] GREEK ZERO SIGN..GREEK ONE QUARTER SIGN
+1018C..1018E;N # So [3] GREEK SINUSOID SIGN..NOMISMA SIGN
+10190..1019C;N # So [13] ROMAN SEXTANS SIGN..ASCIA SYMBOL
+101A0;N # So GREEK SYMBOL TAU RHO
+101D0..101FC;N # So [45] PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND
+101FD;N # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE
+10280..1029C;N # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X
+102A0..102D0;N # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3
+102E0;N # Mn COPTIC EPACT THOUSANDS MARK
+102E1..102FB;N # No [27] COPTIC EPACT DIGIT ONE..COPTIC EPACT NUMBER NINE HUNDRED
+10300..1031F;N # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS
+10320..10323;N # No [4] OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL FIFTY
+1032D..1032F;N # Lo [3] OLD ITALIC LETTER YE..OLD ITALIC LETTER SOUTHERN TSE
+10330..10340;N # Lo [17] GOTHIC LETTER AHSA..GOTHIC LETTER PAIRTHRA
+10341;N # Nl GOTHIC LETTER NINETY
+10342..10349;N # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL
+1034A;N # Nl GOTHIC LETTER NINE HUNDRED
+10350..10375;N # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA
+10376..1037A;N # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII
+10380..1039D;N # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU
+1039F;N # Po UGARITIC WORD DIVIDER
+103A0..103C3;N # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA
+103C8..103CF;N # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH
+103D0;N # Po OLD PERSIAN WORD DIVIDER
+103D1..103D5;N # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED
+10400..1044F;N # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW
+10450..1047F;N # Lo [48] SHAVIAN LETTER PEEP..SHAVIAN LETTER YEW
+10480..1049D;N # Lo [30] OSMANYA LETTER ALEF..OSMANYA LETTER OO
+104A0..104A9;N # Nd [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE
+104B0..104D3;N # Lu [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+104D8..104FB;N # Ll [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10500..10527;N # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE
+10530..10563;N # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW
+1056F;N # Po CAUCASIAN ALBANIAN CITATION MARK
+10570..1057A;N # Lu [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A;N # Lu [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592;N # Lu [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595;N # Lu [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10597..105A1;N # Ll [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1;N # Ll [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9;N # Ll [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC;N # Ll [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+10600..10736;N # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664
+10740..10755;N # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE
+10760..10767;N # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807
+10780..10785;N # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK
+10787..107B0;N # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2..107BA;N # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+10800..10805;N # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA
+10808;N # Lo CYPRIOT SYLLABLE JO
+1080A..10835;N # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO
+10837..10838;N # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE
+1083C;N # Lo CYPRIOT SYLLABLE ZA
+1083F;N # Lo CYPRIOT SYLLABLE ZO
+10840..10855;N # Lo [22] IMPERIAL ARAMAIC LETTER ALEPH..IMPERIAL ARAMAIC LETTER TAW
+10857;N # Po IMPERIAL ARAMAIC SECTION SIGN
+10858..1085F;N # No [8] IMPERIAL ARAMAIC NUMBER ONE..IMPERIAL ARAMAIC NUMBER TEN THOUSAND
+10860..10876;N # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW
+10877..10878;N # So [2] PALMYRENE LEFT-POINTING FLEURON..PALMYRENE RIGHT-POINTING FLEURON
+10879..1087F;N # No [7] PALMYRENE NUMBER ONE..PALMYRENE NUMBER TWENTY
+10880..1089E;N # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW
+108A7..108AF;N # No [9] NABATAEAN NUMBER ONE..NABATAEAN NUMBER ONE HUNDRED
+108E0..108F2;N # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH
+108F4..108F5;N # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW
+108FB..108FF;N # No [5] HATRAN NUMBER ONE..HATRAN NUMBER ONE HUNDRED
+10900..10915;N # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU
+10916..1091B;N # No [6] PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THREE
+1091F;N # Po PHOENICIAN WORD SEPARATOR
+10920..10939;N # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C
+1093F;N # Po LYDIAN TRIANGULAR MARK
+10980..1099F;N # Lo [32] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC HIEROGLYPHIC SYMBOL VIDJ-2
+109A0..109B7;N # Lo [24] MEROITIC CURSIVE LETTER A..MEROITIC CURSIVE LETTER DA
+109BC..109BD;N # No [2] MEROITIC CURSIVE FRACTION ELEVEN TWELFTHS..MEROITIC CURSIVE FRACTION ONE HALF
+109BE..109BF;N # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN
+109C0..109CF;N # No [16] MEROITIC CURSIVE NUMBER ONE..MEROITIC CURSIVE NUMBER SEVENTY
+109D2..109FF;N # No [46] MEROITIC CURSIVE NUMBER ONE HUNDRED..MEROITIC CURSIVE FRACTION TEN TWELFTHS
+10A00;N # Lo KHAROSHTHI LETTER A
+10A01..10A03;N # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
+10A05..10A06;N # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
+10A0C..10A0F;N # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+10A10..10A13;N # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA
+10A15..10A17;N # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA
+10A19..10A35;N # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA
+10A38..10A3A;N # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW
+10A3F;N # Mn KHAROSHTHI VIRAMA
+10A40..10A48;N # No [9] KHAROSHTHI DIGIT ONE..KHAROSHTHI FRACTION ONE HALF
+10A50..10A58;N # Po [9] KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCTUATION LINES
+10A60..10A7C;N # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH
+10A7D..10A7E;N # No [2] OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARABIAN NUMBER FIFTY
+10A7F;N # Po OLD SOUTH ARABIAN NUMERIC INDICATOR
+10A80..10A9C;N # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH
+10A9D..10A9F;N # No [3] OLD NORTH ARABIAN NUMBER ONE..OLD NORTH ARABIAN NUMBER TWENTY
+10AC0..10AC7;N # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW
+10AC8;N # So MANICHAEAN SIGN UD
+10AC9..10AE4;N # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW
+10AE5..10AE6;N # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
+10AEB..10AEF;N # No [5] MANICHAEAN NUMBER ONE..MANICHAEAN NUMBER ONE HUNDRED
+10AF0..10AF6;N # Po [7] MANICHAEAN PUNCTUATION STAR..MANICHAEAN PUNCTUATION LINE FILLER
+10B00..10B35;N # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE
+10B39..10B3F;N # Po [7] AVESTAN ABBREVIATION MARK..LARGE ONE RING OVER TWO RINGS PUNCTUATION
+10B40..10B55;N # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW
+10B58..10B5F;N # No [8] INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND
+10B60..10B72;N # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW
+10B78..10B7F;N # No [8] INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND
+10B80..10B91;N # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW
+10B99..10B9C;N # Po [4] PSALTER PAHLAVI SECTION MARK..PSALTER PAHLAVI FOUR DOTS WITH DOT
+10BA9..10BAF;N # No [7] PSALTER PAHLAVI NUMBER ONE..PSALTER PAHLAVI NUMBER ONE HUNDRED
+10C00..10C48;N # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH
+10C80..10CB2;N # Lu [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10CC0..10CF2;N # Ll [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10CFA..10CFF;N # No [6] OLD HUNGARIAN NUMBER ONE..OLD HUNGARIAN NUMBER ONE THOUSAND
+10D00..10D23;N # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA
+10D24..10D27;N # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+10D30..10D39;N # Nd [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE
+10E60..10E7E;N # No [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS
+10E80..10EA9;N # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET
+10EAB..10EAC;N # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK
+10EAD;N # Pd YEZIDI HYPHENATION MARK
+10EB0..10EB1;N # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE
+10EFD..10EFF;N # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA
+10F00..10F1C;N # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL
+10F1D..10F26;N # No [10] OLD SOGDIAN NUMBER ONE..OLD SOGDIAN FRACTION ONE HALF
+10F27;N # Lo OLD SOGDIAN LIGATURE AYIN-DALETH
+10F30..10F45;N # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN
+10F46..10F50;N # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
+10F51..10F54;N # No [4] SOGDIAN NUMBER ONE..SOGDIAN NUMBER ONE HUNDRED
+10F55..10F59;N # Po [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT
+10F70..10F81;N # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH
+10F82..10F85;N # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW
+10F86..10F89;N # Po [4] OLD UYGHUR PUNCTUATION BAR..OLD UYGHUR PUNCTUATION FOUR DOTS
+10FB0..10FC4;N # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW
+10FC5..10FCB;N # No [7] CHORASMIAN NUMBER ONE..CHORASMIAN NUMBER ONE HUNDRED
+10FE0..10FF6;N # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH
+11000;N # Mc BRAHMI SIGN CANDRABINDU
+11001;N # Mn BRAHMI SIGN ANUSVARA
+11002;N # Mc BRAHMI SIGN VISARGA
+11003..11037;N # Lo [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA
+11038..11046;N # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA
+11047..1104D;N # Po [7] BRAHMI DANDA..BRAHMI PUNCTUATION LOTUS
+11052..11065;N # No [20] BRAHMI NUMBER ONE..BRAHMI NUMBER ONE THOUSAND
+11066..1106F;N # Nd [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE
+11070;N # Mn BRAHMI SIGN OLD TAMIL VIRAMA
+11071..11072;N # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O
+11073..11074;N # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O
+11075;N # Lo BRAHMI LETTER OLD TAMIL LLA
+1107F;N # Mn BRAHMI NUMBER JOINER
+11080..11081;N # Mn [2] KAITHI SIGN CANDRABINDU..KAITHI SIGN ANUSVARA
+11082;N # Mc KAITHI SIGN VISARGA
+11083..110AF;N # Lo [45] KAITHI LETTER A..KAITHI LETTER HA
+110B0..110B2;N # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II
+110B3..110B6;N # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
+110B7..110B8;N # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU
+110B9..110BA;N # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA
+110BB..110BC;N # Po [2] KAITHI ABBREVIATION SIGN..KAITHI ENUMERATION SIGN
+110BD;N # Cf KAITHI NUMBER SIGN
+110BE..110C1;N # Po [4] KAITHI SECTION MARK..KAITHI DOUBLE DANDA
+110C2;N # Mn KAITHI VOWEL SIGN VOCALIC R
+110CD;N # Cf KAITHI NUMBER SIGN ABOVE
+110D0..110E8;N # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE
+110F0..110F9;N # Nd [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE
+11100..11102;N # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA
+11103..11126;N # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA
+11127..1112B;N # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
+1112C;N # Mc CHAKMA VOWEL SIGN E
+1112D..11134;N # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA
+11136..1113F;N # Nd [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE
+11140..11143;N # Po [4] CHAKMA SECTION MARK..CHAKMA QUESTION MARK
+11144;N # Lo CHAKMA LETTER LHAA
+11145..11146;N # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI
+11147;N # Lo CHAKMA LETTER VAA
+11150..11172;N # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA
+11173;N # Mn MAHAJANI SIGN NUKTA
+11174..11175;N # Po [2] MAHAJANI ABBREVIATION SIGN..MAHAJANI SECTION MARK
+11176;N # Lo MAHAJANI LIGATURE SHRI
+11180..11181;N # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
+11182;N # Mc SHARADA SIGN VISARGA
+11183..111B2;N # Lo [48] SHARADA LETTER A..SHARADA LETTER HA
+111B3..111B5;N # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II
+111B6..111BE;N # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
+111BF..111C0;N # Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA
+111C1..111C4;N # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM
+111C5..111C8;N # Po [4] SHARADA DANDA..SHARADA SEPARATOR
+111C9..111CC;N # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK
+111CD;N # Po SHARADA SUTRA MARK
+111CE;N # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E
+111CF;N # Mn SHARADA SIGN INVERTED CANDRABINDU
+111D0..111D9;N # Nd [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE
+111DA;N # Lo SHARADA EKAM
+111DB;N # Po SHARADA SIGN SIDDHAM
+111DC;N # Lo SHARADA HEADSTROKE
+111DD..111DF;N # Po [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2
+111E1..111F4;N # No [20] SINHALA ARCHAIC DIGIT ONE..SINHALA ARCHAIC NUMBER ONE THOUSAND
+11200..11211;N # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA
+11213..1122B;N # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA
+1122C..1122E;N # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II
+1122F..11231;N # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
+11232..11233;N # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU
+11234;N # Mn KHOJKI SIGN ANUSVARA
+11235;N # Mc KHOJKI SIGN VIRAMA
+11236..11237;N # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
+11238..1123D;N # Po [6] KHOJKI DANDA..KHOJKI ABBREVIATION SIGN
+1123E;N # Mn KHOJKI SIGN SUKUN
+1123F..11240;N # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I
+11241;N # Mn KHOJKI VOWEL SIGN VOCALIC R
+11280..11286;N # Lo [7] MULTANI LETTER A..MULTANI LETTER GA
+11288;N # Lo MULTANI LETTER GHA
+1128A..1128D;N # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA
+1128F..1129D;N # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA
+1129F..112A8;N # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA
+112A9;N # Po MULTANI SECTION MARK
+112B0..112DE;N # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA
+112DF;N # Mn KHUDAWADI SIGN ANUSVARA
+112E0..112E2;N # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II
+112E3..112EA;N # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA
+112F0..112F9;N # Nd [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE
+11300..11301;N # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
+11302..11303;N # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA
+11305..1130C;N # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L
+1130F..11310;N # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI
+11313..11328;N # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA
+1132A..11330;N # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA
+11332..11333;N # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA
+11335..11339;N # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA
+1133B..1133C;N # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA
+1133D;N # Lo GRANTHA SIGN AVAGRAHA
+1133E..1133F;N # Mc [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I
+11340;N # Mn GRANTHA VOWEL SIGN II
+11341..11344;N # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR
+11347..11348;N # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI
+1134B..1134D;N # Mc [3] GRANTHA VOWEL SIGN OO..GRANTHA SIGN VIRAMA
+11350;N # Lo GRANTHA OM
+11357;N # Mc GRANTHA AU LENGTH MARK
+1135D..11361;N # Lo [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL
+11362..11363;N # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL
+11366..1136C;N # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX
+11370..11374;N # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA
+11400..11434;N # Lo [53] NEWA LETTER A..NEWA LETTER HA
+11435..11437;N # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II
+11438..1143F;N # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
+11440..11441;N # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU
+11442..11444;N # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA
+11445;N # Mc NEWA SIGN VISARGA
+11446;N # Mn NEWA SIGN NUKTA
+11447..1144A;N # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI
+1144B..1144F;N # Po [5] NEWA DANDA..NEWA ABBREVIATION SIGN
+11450..11459;N # Nd [10] NEWA DIGIT ZERO..NEWA DIGIT NINE
+1145A..1145B;N # Po [2] NEWA DOUBLE COMMA..NEWA PLACEHOLDER MARK
+1145D;N # Po NEWA INSERTION SIGN
+1145E;N # Mn NEWA SANDHI MARK
+1145F..11461;N # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA
+11480..114AF;N # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA
+114B0..114B2;N # Mc [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II
+114B3..114B8;N # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
+114B9;N # Mc TIRHUTA VOWEL SIGN E
+114BA;N # Mn TIRHUTA VOWEL SIGN SHORT E
+114BB..114BE;N # Mc [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU
+114BF..114C0;N # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA
+114C1;N # Mc TIRHUTA SIGN VISARGA
+114C2..114C3;N # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA
+114C4..114C5;N # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG
+114C6;N # Po TIRHUTA ABBREVIATION SIGN
+114C7;N # Lo TIRHUTA OM
+114D0..114D9;N # Nd [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE
+11580..115AE;N # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA
+115AF..115B1;N # Mc [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II
+115B2..115B5;N # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR
+115B8..115BB;N # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU
+115BC..115BD;N # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA
+115BE;N # Mc SIDDHAM SIGN VISARGA
+115BF..115C0;N # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA
+115C1..115D7;N # Po [23] SIDDHAM SIGN SIDDHAM..SIDDHAM SECTION MARK WITH CIRCLES AND FOUR ENCLOSURES
+115D8..115DB;N # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U
+115DC..115DD;N # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU
+11600..1162F;N # Lo [48] MODI LETTER A..MODI LETTER LLA
+11630..11632;N # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II
+11633..1163A;N # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI
+1163B..1163C;N # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU
+1163D;N # Mn MODI SIGN ANUSVARA
+1163E;N # Mc MODI SIGN VISARGA
+1163F..11640;N # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA
+11641..11643;N # Po [3] MODI DANDA..MODI ABBREVIATION SIGN
+11644;N # Lo MODI SIGN HUVA
+11650..11659;N # Nd [10] MODI DIGIT ZERO..MODI DIGIT NINE
+11660..1166C;N # Po [13] MONGOLIAN BIRGA WITH ORNAMENT..MONGOLIAN TURNED SWIRL BIRGA WITH DOUBLE ORNAMENT
+11680..116AA;N # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA
+116AB;N # Mn TAKRI SIGN ANUSVARA
+116AC;N # Mc TAKRI SIGN VISARGA
+116AD;N # Mn TAKRI VOWEL SIGN AA
+116AE..116AF;N # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II
+116B0..116B5;N # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU
+116B6;N # Mc TAKRI SIGN VIRAMA
+116B7;N # Mn TAKRI SIGN NUKTA
+116B8;N # Lo TAKRI LETTER ARCHAIC KHA
+116B9;N # Po TAKRI ABBREVIATION SIGN
+116C0..116C9;N # Nd [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE
+11700..1171A;N # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA
+1171D..1171F;N # Mn [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA
+11720..11721;N # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA
+11722..11725;N # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
+11726;N # Mc AHOM VOWEL SIGN E
+11727..1172B;N # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
+11730..11739;N # Nd [10] AHOM DIGIT ZERO..AHOM DIGIT NINE
+1173A..1173B;N # No [2] AHOM NUMBER TEN..AHOM NUMBER TWENTY
+1173C..1173E;N # Po [3] AHOM SIGN SMALL SECTION..AHOM SIGN RULAI
+1173F;N # So AHOM SYMBOL VI
+11740..11746;N # Lo [7] AHOM LETTER CA..AHOM LETTER LLA
+11800..1182B;N # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA
+1182C..1182E;N # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
+1182F..11837;N # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11838;N # Mc DOGRA SIGN VISARGA
+11839..1183A;N # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+1183B;N # Po DOGRA ABBREVIATION SIGN
+118A0..118DF;N # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+118E0..118E9;N # Nd [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE
+118EA..118F2;N # No [9] WARANG CITI NUMBER TEN..WARANG CITI NUMBER NINETY
+118FF;N # Lo WARANG CITI OM
+11900..11906;N # Lo [7] DIVES AKURU LETTER A..DIVES AKURU LETTER E
+11909;N # Lo DIVES AKURU LETTER O
+1190C..11913;N # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA
+11915..11916;N # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA
+11918..1192F;N # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA
+11930..11935;N # Mc [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E
+11937..11938;N # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O
+1193B..1193C;N # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU
+1193D;N # Mc DIVES AKURU SIGN HALANTA
+1193E;N # Mn DIVES AKURU VIRAMA
+1193F;N # Lo DIVES AKURU PREFIXED NASAL SIGN
+11940;N # Mc DIVES AKURU MEDIAL YA
+11941;N # Lo DIVES AKURU INITIAL RA
+11942;N # Mc DIVES AKURU MEDIAL RA
+11943;N # Mn DIVES AKURU SIGN NUKTA
+11944..11946;N # Po [3] DIVES AKURU DOUBLE DANDA..DIVES AKURU END OF TEXT MARK
+11950..11959;N # Nd [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE
+119A0..119A7;N # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR
+119AA..119D0;N # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA
+119D1..119D3;N # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II
+119D4..119D7;N # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB;N # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119DC..119DF;N # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA
+119E0;N # Mn NANDINAGARI SIGN VIRAMA
+119E1;N # Lo NANDINAGARI SIGN AVAGRAHA
+119E2;N # Po NANDINAGARI SIGN SIDDHAM
+119E3;N # Lo NANDINAGARI HEADSTROKE
+119E4;N # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E
+11A00;N # Lo ZANABAZAR SQUARE LETTER A
+11A01..11A0A;N # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+11A0B..11A32;N # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA
+11A33..11A38;N # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
+11A39;N # Mc ZANABAZAR SQUARE SIGN VISARGA
+11A3A;N # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
+11A3B..11A3E;N # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
+11A3F..11A46;N # Po [8] ZANABAZAR SQUARE INITIAL HEAD MARK..ZANABAZAR SQUARE CLOSING DOUBLE-LINED HEAD MARK
+11A47;N # Mn ZANABAZAR SQUARE SUBJOINER
+11A50;N # Lo SOYOMBO LETTER A
+11A51..11A56;N # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE
+11A57..11A58;N # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU
+11A59..11A5B;N # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK
+11A5C..11A89;N # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA
+11A8A..11A96;N # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA
+11A97;N # Mc SOYOMBO SIGN VISARGA
+11A98..11A99;N # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER
+11A9A..11A9C;N # Po [3] SOYOMBO MARK TSHEG..SOYOMBO MARK DOUBLE SHAD
+11A9D;N # Lo SOYOMBO MARK PLUTA
+11A9E..11AA2;N # Po [5] SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME..SOYOMBO TERMINAL MARK-2
+11AB0..11ABF;N # Lo [16] CANADIAN SYLLABICS NATTILIK HI..CANADIAN SYLLABICS SPA
+11AC0..11AF8;N # Lo [57] PAU CIN HAU LETTER PA..PAU CIN HAU GLOTTAL STOP FINAL
+11B00..11B09;N # Po [10] DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU
+11C00..11C08;N # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L
+11C0A..11C2E;N # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA
+11C2F;N # Mc BHAIKSUKI VOWEL SIGN AA
+11C30..11C36;N # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L
+11C38..11C3D;N # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA
+11C3E;N # Mc BHAIKSUKI SIGN VISARGA
+11C3F;N # Mn BHAIKSUKI SIGN VIRAMA
+11C40;N # Lo BHAIKSUKI SIGN AVAGRAHA
+11C41..11C45;N # Po [5] BHAIKSUKI DANDA..BHAIKSUKI GAP FILLER-2
+11C50..11C59;N # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE
+11C5A..11C6C;N # No [19] BHAIKSUKI NUMBER ONE..BHAIKSUKI HUNDREDS UNIT MARK
+11C70..11C71;N # Po [2] MARCHEN HEAD MARK..MARCHEN MARK SHAD
+11C72..11C8F;N # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A
+11C92..11CA7;N # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA
+11CA9;N # Mc MARCHEN SUBJOINED LETTER YA
+11CAA..11CB0;N # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA
+11CB1;N # Mc MARCHEN VOWEL SIGN I
+11CB2..11CB3;N # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E
+11CB4;N # Mc MARCHEN VOWEL SIGN O
+11CB5..11CB6;N # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU
+11D00..11D06;N # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E
+11D08..11D09;N # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O
+11D0B..11D30;N # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA
+11D31..11D36;N # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R
+11D3A;N # Mn MASARAM GONDI VOWEL SIGN E
+11D3C..11D3D;N # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
+11D3F..11D45;N # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA
+11D46;N # Lo MASARAM GONDI REPHA
+11D47;N # Mn MASARAM GONDI RA-KARA
+11D50..11D59;N # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE
+11D60..11D65;N # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU
+11D67..11D68;N # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI
+11D6A..11D89;N # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA
+11D8A..11D8E;N # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU
+11D90..11D91;N # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+11D93..11D94;N # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU
+11D95;N # Mn GUNJALA GONDI SIGN ANUSVARA
+11D96;N # Mc GUNJALA GONDI SIGN VISARGA
+11D97;N # Mn GUNJALA GONDI VIRAMA
+11D98;N # Lo GUNJALA GONDI OM
+11DA0..11DA9;N # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE
+11EE0..11EF2;N # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA
+11EF3..11EF4;N # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+11EF5..11EF6;N # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
+11EF7..11EF8;N # Po [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION
+11F00..11F01;N # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F02;N # Lo KAWI SIGN REPHA
+11F03;N # Mc KAWI SIGN VISARGA
+11F04..11F10;N # Lo [13] KAWI LETTER A..KAWI LETTER O
+11F12..11F33;N # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA
+11F34..11F35;N # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA
+11F36..11F3A;N # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F3E..11F3F;N # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI
+11F40;N # Mn KAWI VOWEL SIGN EU
+11F41;N # Mc KAWI SIGN KILLER
+11F42;N # Mn KAWI CONJOINER
+11F43..11F4F;N # Po [13] KAWI DANDA..KAWI PUNCTUATION CLOSING SPIRAL
+11F50..11F59;N # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE
+11FB0;N # Lo LISU LETTER YHA
+11FC0..11FD4;N # No [21] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL FRACTION DOWNSCALING FACTOR KIIZH
+11FD5..11FDC;N # So [8] TAMIL SIGN NEL..TAMIL SIGN MUKKURUNI
+11FDD..11FE0;N # Sc [4] TAMIL SIGN KAACU..TAMIL SIGN VARAAKAN
+11FE1..11FF1;N # So [17] TAMIL SIGN PAARAM..TAMIL SIGN VAKAIYARAA
+11FFF;N # Po TAMIL PUNCTUATION END OF TEXT
+12000..12399;N # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U
+12400..1246E;N # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM
+12470..12474;N # Po [5] CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON
+12480..12543;N # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU
+12F90..12FF0;N # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114
+12FF1..12FF2;N # Po [2] CYPRO-MINOAN SIGN CM301..CYPRO-MINOAN SIGN CM302
+13000..1342F;N # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D
+13430..1343F;N # Cf [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE
+13440;N # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY
+13441..13446;N # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN
+13447..13455;N # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED
+14400..14646;N # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530
+16800..16A38;N # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ
+16A40..16A5E;N # Lo [31] MRO LETTER TA..MRO LETTER TEK
+16A60..16A69;N # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE
+16A6E..16A6F;N # Po [2] MRO DANDA..MRO DOUBLE DANDA
+16A70..16ABE;N # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA
+16AC0..16AC9;N # Nd [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE
+16AD0..16AED;N # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I
+16AF0..16AF4;N # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
+16AF5;N # Po BASSA VAH FULL STOP
+16B00..16B2F;N # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU
+16B30..16B36;N # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
+16B37..16B3B;N # Po [5] PAHAWH HMONG SIGN VOS THOM..PAHAWH HMONG SIGN VOS FEEM
+16B3C..16B3F;N # So [4] PAHAWH HMONG SIGN XYEEM NTXIV..PAHAWH HMONG SIGN XYEEM FAIB
+16B40..16B43;N # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM
+16B44;N # Po PAHAWH HMONG SIGN XAUS
+16B45;N # So PAHAWH HMONG SIGN CIM TSOV ROG
+16B50..16B59;N # Nd [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE
+16B5B..16B61;N # No [7] PAHAWH HMONG NUMBER TENS..PAHAWH HMONG NUMBER TRILLIONS
+16B63..16B77;N # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS
+16B7D..16B8F;N # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ
+16E40..16E7F;N # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+16E80..16E96;N # No [23] MEDEFAIDRIN DIGIT ZERO..MEDEFAIDRIN DIGIT THREE ALTERNATE FORM
+16E97..16E9A;N # Po [4] MEDEFAIDRIN COMMA..MEDEFAIDRIN EXCLAMATION OH
+16F00..16F4A;N # Lo [75] MIAO LETTER PA..MIAO LETTER RTE
+16F4F;N # Mn MIAO SIGN CONSONANT MODIFIER BAR
+16F50;N # Lo MIAO LETTER NASALIZATION
+16F51..16F87;N # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
+16F8F..16F92;N # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
+16F93..16F9F;N # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8
+16FE0..16FE1;W # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK
+16FE2;W # Po OLD CHINESE HOOK MARK
+16FE3;W # Lm OLD CHINESE ITERATION MARK
+16FE4;W # Mn KHITAN SMALL SCRIPT FILLER
+16FF0..16FF1;W # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY
+17000..187F7;W # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7
+18800..18AFF;W # Lo [768] TANGUT COMPONENT-001..TANGUT COMPONENT-768
+18B00..18CD5;W # Lo [470] KHITAN SMALL SCRIPT CHARACTER-18B00..KHITAN SMALL SCRIPT CHARACTER-18CD5
+18D00..18D08;W # Lo [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08
+1AFF0..1AFF3;W # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5
+1AFF5..1AFFB;W # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5
+1AFFD..1AFFE;W # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8
+1B000..1B0FF;W # Lo [256] KATAKANA LETTER ARCHAIC E..HENTAIGANA LETTER RE-2
+1B100..1B122;W # Lo [35] HENTAIGANA LETTER RE-3..KATAKANA LETTER ARCHAIC WU
+1B132;W # Lo HIRAGANA LETTER SMALL KO
+1B150..1B152;W # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO
+1B155;W # Lo KATAKANA LETTER SMALL KO
+1B164..1B167;W # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N
+1B170..1B2FB;W # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB
+1BC00..1BC6A;N # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M
+1BC70..1BC7C;N # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK
+1BC80..1BC88;N # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL
+1BC90..1BC99;N # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW
+1BC9C;N # So DUPLOYAN SIGN O WITH CROSS
+1BC9D..1BC9E;N # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK
+1BC9F;N # Po DUPLOYAN PUNCTUATION CHINOOK FULL STOP
+1BCA0..1BCA3;N # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
+1CF00..1CF2D;N # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT
+1CF30..1CF46;N # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG
+1CF50..1CFC3;N # So [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK
+1D000..1D0F5;N # So [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO
+1D100..1D126;N # So [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2
+1D129..1D164;N # So [60] MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH NOTE
+1D165..1D166;N # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM
+1D167..1D169;N # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3
+1D16A..1D16C;N # So [3] MUSICAL SYMBOL FINGERED TREMOLO-1..MUSICAL SYMBOL FINGERED TREMOLO-3
+1D16D..1D172;N # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5
+1D173..1D17A;N # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
+1D17B..1D182;N # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
+1D183..1D184;N # So [2] MUSICAL SYMBOL ARPEGGIATO UP..MUSICAL SYMBOL ARPEGGIATO DOWN
+1D185..1D18B;N # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
+1D18C..1D1A9;N # So [30] MUSICAL SYMBOL RINFORZANDO..MUSICAL SYMBOL DEGREE SLASH
+1D1AA..1D1AD;N # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+1D1AE..1D1EA;N # So [61] MUSICAL SYMBOL PEDAL MARK..MUSICAL SYMBOL KORON
+1D200..1D241;N # So [66] GREEK VOCAL NOTATION SYMBOL-1..GREEK INSTRUMENTAL NOTATION SYMBOL-54
+1D242..1D244;N # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME
+1D245;N # So GREEK MUSICAL LEIMMA
+1D2C0..1D2D3;N # No [20] KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN
+1D2E0..1D2F3;N # No [20] MAYAN NUMERAL ZERO..MAYAN NUMERAL NINETEEN
+1D300..1D356;N # So [87] MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING
+1D360..1D378;N # No [25] COUNTING ROD UNIT DIGIT ONE..TALLY MARK FIVE
+1D400..1D454;N # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G
+1D456..1D49C;N # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A
+1D49E..1D49F;N # Lu [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D
+1D4A2;N # Lu MATHEMATICAL SCRIPT CAPITAL G
+1D4A5..1D4A6;N # Lu [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K
+1D4A9..1D4AC;N # Lu [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE..1D4B9;N # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D
+1D4BB;N # Ll MATHEMATICAL SCRIPT SMALL F
+1D4BD..1D4C3;N # Ll [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N
+1D4C5..1D505;N # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B
+1D507..1D50A;N # Lu [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G
+1D50D..1D514;N # Lu [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q
+1D516..1D51C;N # Lu [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y
+1D51E..1D539;N # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B..1D53E;N # Lu [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540..1D544;N # Lu [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546;N # Lu MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A..1D550;N # Lu [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D552..1D6A5;N # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6A8..1D6C0;N # Lu [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA
+1D6C1;N # Sm MATHEMATICAL BOLD NABLA
+1D6C2..1D6DA;N # Ll [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA
+1D6DB;N # Sm MATHEMATICAL BOLD PARTIAL DIFFERENTIAL
+1D6DC..1D6FA;N # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA
+1D6FB;N # Sm MATHEMATICAL ITALIC NABLA
+1D6FC..1D714;N # Ll [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA
+1D715;N # Sm MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL
+1D716..1D734;N # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D735;N # Sm MATHEMATICAL BOLD ITALIC NABLA
+1D736..1D74E;N # Ll [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D74F;N # Sm MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL
+1D750..1D76E;N # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D76F;N # Sm MATHEMATICAL SANS-SERIF BOLD NABLA
+1D770..1D788;N # Ll [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D789;N # Sm MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL
+1D78A..1D7A8;N # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7A9;N # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA
+1D7AA..1D7C2;N # Ll [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C3;N # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL
+1D7C4..1D7CB;N # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA
+1D7CE..1D7FF;N # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE
+1D800..1D9FF;N # So [512] SIGNWRITING HAND-FIST INDEX..SIGNWRITING HEAD
+1DA00..1DA36;N # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN
+1DA37..1DA3A;N # So [4] SIGNWRITING AIR BLOW SMALL ROTATIONS..SIGNWRITING BREATH EXHALE
+1DA3B..1DA6C;N # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT
+1DA6D..1DA74;N # So [8] SIGNWRITING SHOULDER HIP SPINE..SIGNWRITING TORSO-FLOORPLANE TWISTING
+1DA75;N # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS
+1DA76..1DA83;N # So [14] SIGNWRITING LIMB COMBINATION..SIGNWRITING LOCATION DEPTH
+1DA84;N # Mn SIGNWRITING LOCATION HEAD NECK
+1DA85..1DA86;N # So [2] SIGNWRITING LOCATION TORSO..SIGNWRITING LOCATION LIMBS DIGITS
+1DA87..1DA8B;N # Po [5] SIGNWRITING COMMA..SIGNWRITING PARENTHESIS
+1DA9B..1DA9F;N # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6
+1DAA1..1DAAF;N # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16
+1DF00..1DF09;N # Ll [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK
+1DF0A;N # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK
+1DF0B..1DF1E;N # Ll [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL
+1DF25..1DF2A;N # Ll [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK
+1E000..1E006;N # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
+1E008..1E018;N # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
+1E01B..1E021;N # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
+1E023..1E024;N # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
+1E026..1E02A;N # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E030..1E06D;N # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
+1E08F;N # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+1E100..1E12C;N # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W
+1E130..1E136;N # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+1E137..1E13D;N # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER
+1E140..1E149;N # Nd [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE
+1E14E;N # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ
+1E14F;N # So NYIAKENG PUACHUE HMONG CIRCLED CA
+1E290..1E2AD;N # Lo [30] TOTO LETTER PA..TOTO LETTER A
+1E2AE;N # Mn TOTO SIGN RISING TONE
+1E2C0..1E2EB;N # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH
+1E2EC..1E2EF;N # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
+1E2F0..1E2F9;N # Nd [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE
+1E2FF;N # Sc WANCHO NGUN SIGN
+1E4D0..1E4EA;N # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL
+1E4EB;N # Lm NAG MUNDARI SIGN OJOD
+1E4EC..1E4EF;N # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH
+1E4F0..1E4F9;N # Nd [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE
+1E7E0..1E7E6;N # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO
+1E7E8..1E7EB;N # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE
+1E7ED..1E7EE;N # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE
+1E7F0..1E7FE;N # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE
+1E800..1E8C4;N # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON
+1E8C7..1E8CF;N # No [9] MENDE KIKAKUI DIGIT ONE..MENDE KIKAKUI DIGIT NINE
+1E8D0..1E8D6;N # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
+1E900..1E943;N # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA
+1E944..1E94A;N # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
+1E94B;N # Lm ADLAM NASALIZATION MARK
+1E950..1E959;N # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE
+1E95E..1E95F;N # Po [2] ADLAM INITIAL EXCLAMATION MARK..ADLAM INITIAL QUESTION MARK
+1EC71..1ECAB;N # No [59] INDIC SIYAQ NUMBER ONE..INDIC SIYAQ NUMBER PREFIXED NINE
+1ECAC;N # So INDIC SIYAQ PLACEHOLDER
+1ECAD..1ECAF;N # No [3] INDIC SIYAQ FRACTION ONE QUARTER..INDIC SIYAQ FRACTION THREE QUARTERS
+1ECB0;N # Sc INDIC SIYAQ RUPEE MARK
+1ECB1..1ECB4;N # No [4] INDIC SIYAQ NUMBER ALTERNATE ONE..INDIC SIYAQ ALTERNATE LAKH MARK
+1ED01..1ED2D;N # No [45] OTTOMAN SIYAQ NUMBER ONE..OTTOMAN SIYAQ NUMBER NINETY THOUSAND
+1ED2E;N # So OTTOMAN SIYAQ MARRATAN
+1ED2F..1ED3D;N # No [15] OTTOMAN SIYAQ ALTERNATE NUMBER TWO..OTTOMAN SIYAQ FRACTION ONE SIXTH
+1EE00..1EE03;N # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL
+1EE05..1EE1F;N # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF
+1EE21..1EE22;N # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM
+1EE24;N # Lo ARABIC MATHEMATICAL INITIAL HEH
+1EE27;N # Lo ARABIC MATHEMATICAL INITIAL HAH
+1EE29..1EE32;N # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF
+1EE34..1EE37;N # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH
+1EE39;N # Lo ARABIC MATHEMATICAL INITIAL DAD
+1EE3B;N # Lo ARABIC MATHEMATICAL INITIAL GHAIN
+1EE42;N # Lo ARABIC MATHEMATICAL TAILED JEEM
+1EE47;N # Lo ARABIC MATHEMATICAL TAILED HAH
+1EE49;N # Lo ARABIC MATHEMATICAL TAILED YEH
+1EE4B;N # Lo ARABIC MATHEMATICAL TAILED LAM
+1EE4D..1EE4F;N # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN
+1EE51..1EE52;N # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF
+1EE54;N # Lo ARABIC MATHEMATICAL TAILED SHEEN
+1EE57;N # Lo ARABIC MATHEMATICAL TAILED KHAH
+1EE59;N # Lo ARABIC MATHEMATICAL TAILED DAD
+1EE5B;N # Lo ARABIC MATHEMATICAL TAILED GHAIN
+1EE5D;N # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON
+1EE5F;N # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF
+1EE61..1EE62;N # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM
+1EE64;N # Lo ARABIC MATHEMATICAL STRETCHED HEH
+1EE67..1EE6A;N # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF
+1EE6C..1EE72;N # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF
+1EE74..1EE77;N # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH
+1EE79..1EE7C;N # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH
+1EE7E;N # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH
+1EE80..1EE89;N # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH
+1EE8B..1EE9B;N # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN
+1EEA1..1EEA3;N # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL
+1EEA5..1EEA9;N # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH
+1EEAB..1EEBB;N # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN
+1EEF0..1EEF1;N # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL
+1F000..1F003;N # So [4] MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND
+1F004;W # So MAHJONG TILE RED DRAGON
+1F005..1F02B;N # So [39] MAHJONG TILE GREEN DRAGON..MAHJONG TILE BACK
+1F030..1F093;N # So [100] DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06
+1F0A0..1F0AE;N # So [15] PLAYING CARD BACK..PLAYING CARD KING OF SPADES
+1F0B1..1F0BF;N # So [15] PLAYING CARD ACE OF HEARTS..PLAYING CARD RED JOKER
+1F0C1..1F0CE;N # So [14] PLAYING CARD ACE OF DIAMONDS..PLAYING CARD KING OF DIAMONDS
+1F0CF;W # So PLAYING CARD BLACK JOKER
+1F0D1..1F0F5;N # So [37] PLAYING CARD ACE OF CLUBS..PLAYING CARD TRUMP-21
+1F100..1F10A;A # No [11] DIGIT ZERO FULL STOP..DIGIT NINE COMMA
+1F10B..1F10C;N # No [2] DINGBAT CIRCLED SANS-SERIF DIGIT ZERO..DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO
+1F10D..1F10F;N # So [3] CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH
+1F110..1F12D;A # So [30] PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLED CD
+1F12E..1F12F;N # So [2] CIRCLED WZ..COPYLEFT SYMBOL
+1F130..1F169;A # So [58] SQUARED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z
+1F16A..1F16F;N # So [6] RAISED MC SIGN..CIRCLED HUMAN FIGURE
+1F170..1F18D;A # So [30] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED SA
+1F18E;W # So NEGATIVE SQUARED AB
+1F18F..1F190;A # So [2] NEGATIVE SQUARED WC..SQUARE DJ
+1F191..1F19A;W # So [10] SQUARED CL..SQUARED VS
+1F19B..1F1AC;A # So [18] SQUARED THREE D..SQUARED VOD
+1F1AD;N # So MASK WORK SYMBOL
+1F1E6..1F1FF;N # So [26] REGIONAL INDICATOR SYMBOL LETTER A..REGIONAL INDICATOR SYMBOL LETTER Z
+1F200..1F202;W # So [3] SQUARE HIRAGANA HOKA..SQUARED KATAKANA SA
+1F210..1F23B;W # So [44] SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-914D
+1F240..1F248;W # So [9] TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C..TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557
+1F250..1F251;W # So [2] CIRCLED IDEOGRAPH ADVANTAGE..CIRCLED IDEOGRAPH ACCEPT
+1F260..1F265;W # So [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI
+1F300..1F320;W # So [33] CYCLONE..SHOOTING STAR
+1F321..1F32C;N # So [12] THERMOMETER..WIND BLOWING FACE
+1F32D..1F335;W # So [9] HOT DOG..CACTUS
+1F336;N # So HOT PEPPER
+1F337..1F37C;W # So [70] TULIP..BABY BOTTLE
+1F37D;N # So FORK AND KNIFE WITH PLATE
+1F37E..1F393;W # So [22] BOTTLE WITH POPPING CORK..GRADUATION CAP
+1F394..1F39F;N # So [12] HEART WITH TIP ON THE LEFT..ADMISSION TICKETS
+1F3A0..1F3CA;W # So [43] CAROUSEL HORSE..SWIMMER
+1F3CB..1F3CE;N # So [4] WEIGHT LIFTER..RACING CAR
+1F3CF..1F3D3;W # So [5] CRICKET BAT AND BALL..TABLE TENNIS PADDLE AND BALL
+1F3D4..1F3DF;N # So [12] SNOW CAPPED MOUNTAIN..STADIUM
+1F3E0..1F3F0;W # So [17] HOUSE BUILDING..EUROPEAN CASTLE
+1F3F1..1F3F3;N # So [3] WHITE PENNANT..WAVING WHITE FLAG
+1F3F4;W # So WAVING BLACK FLAG
+1F3F5..1F3F7;N # So [3] ROSETTE..LABEL
+1F3F8..1F3FA;W # So [3] BADMINTON RACQUET AND SHUTTLECOCK..AMPHORA
+1F3FB..1F3FF;W # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6
+1F400..1F43E;W # So [63] RAT..PAW PRINTS
+1F43F;N # So CHIPMUNK
+1F440;W # So EYES
+1F441;N # So EYE
+1F442..1F4FC;W # So [187] EAR..VIDEOCASSETTE
+1F4FD..1F4FE;N # So [2] FILM PROJECTOR..PORTABLE STEREO
+1F4FF..1F53D;W # So [63] PRAYER BEADS..DOWN-POINTING SMALL RED TRIANGLE
+1F53E..1F54A;N # So [13] LOWER RIGHT SHADOWED WHITE CIRCLE..DOVE OF PEACE
+1F54B..1F54E;W # So [4] KAABA..MENORAH WITH NINE BRANCHES
+1F54F;N # So BOWL OF HYGIEIA
+1F550..1F567;W # So [24] CLOCK FACE ONE OCLOCK..CLOCK FACE TWELVE-THIRTY
+1F568..1F579;N # So [18] RIGHT SPEAKER..JOYSTICK
+1F57A;W # So MAN DANCING
+1F57B..1F594;N # So [26] LEFT HAND TELEPHONE RECEIVER..REVERSED VICTORY HAND
+1F595..1F596;W # So [2] REVERSED HAND WITH MIDDLE FINGER EXTENDED..RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS
+1F597..1F5A3;N # So [13] WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX
+1F5A4;W # So BLACK HEART
+1F5A5..1F5FA;N # So [86] DESKTOP COMPUTER..WORLD MAP
+1F5FB..1F5FF;W # So [5] MOUNT FUJI..MOYAI
+1F600..1F64F;W # So [80] GRINNING FACE..PERSON WITH FOLDED HANDS
+1F650..1F67F;N # So [48] NORTH WEST POINTING LEAF..REVERSE CHECKER BOARD
+1F680..1F6C5;W # So [70] ROCKET..LEFT LUGGAGE
+1F6C6..1F6CB;N # So [6] TRIANGLE WITH ROUNDED CORNERS..COUCH AND LAMP
+1F6CC;W # So SLEEPING ACCOMMODATION
+1F6CD..1F6CF;N # So [3] SHOPPING BAGS..BED
+1F6D0..1F6D2;W # So [3] PLACE OF WORSHIP..SHOPPING TROLLEY
+1F6D3..1F6D4;N # So [2] STUPA..PAGODA
+1F6D5..1F6D7;W # So [3] HINDU TEMPLE..ELEVATOR
+1F6DC..1F6DF;W # So [4] WIRELESS..RING BUOY
+1F6E0..1F6EA;N # So [11] HAMMER AND WRENCH..NORTHEAST-POINTING AIRPLANE
+1F6EB..1F6EC;W # So [2] AIRPLANE DEPARTURE..AIRPLANE ARRIVING
+1F6F0..1F6F3;N # So [4] SATELLITE..PASSENGER SHIP
+1F6F4..1F6FC;W # So [9] SCOOTER..ROLLER SKATE
+1F700..1F776;N # So [119] ALCHEMICAL SYMBOL FOR QUINTESSENCE..LUNAR ECLIPSE
+1F77B..1F77F;N # So [5] HAUMEA..ORCUS
+1F780..1F7D9;N # So [90] BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE..NINE POINTED WHITE STAR
+1F7E0..1F7EB;W # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE
+1F7F0;W # So HEAVY EQUALS SIGN
+1F800..1F80B;N # So [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD
+1F810..1F847;N # So [56] LEFTWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD..DOWNWARDS HEAVY ARROW
+1F850..1F859;N # So [10] LEFTWARDS SANS-SERIF ARROW..UP DOWN SANS-SERIF ARROW
+1F860..1F887;N # So [40] WIDE-HEADED LEFTWARDS LIGHT BARB ARROW..WIDE-HEADED SOUTH WEST VERY HEAVY BARB ARROW
+1F890..1F8AD;N # So [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS
+1F8B0..1F8B1;N # So [2] ARROW POINTING UPWARDS THEN NORTH WEST..ARROW POINTING RIGHTWARDS THEN CURVING SOUTH WEST
+1F900..1F90B;N # So [12] CIRCLED CROSS FORMEE WITH FOUR DOTS..DOWNWARD FACING NOTCHED HOOK WITH DOT
+1F90C..1F93A;W # So [47] PINCHED FINGERS..FENCER
+1F93B;N # So MODERN PENTATHLON
+1F93C..1F945;W # So [10] WRESTLERS..GOAL NET
+1F946;N # So RIFLE
+1F947..1F9FF;W # So [185] FIRST PLACE MEDAL..NAZAR AMULET
+1FA00..1FA53;N # So [84] NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP
+1FA60..1FA6D;N # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER
+1FA70..1FA7C;W # So [13] BALLET SHOES..CRUTCH
+1FA80..1FA88;W # So [9] YO-YO..FLUTE
+1FA90..1FABD;W # So [46] RINGED PLANET..WING
+1FABF..1FAC5;W # So [7] GOOSE..PERSON WITH CROWN
+1FACE..1FADB;W # So [14] MOOSE..PEA POD
+1FAE0..1FAE8;W # So [9] MELTING FACE..SHAKING FACE
+1FAF0..1FAF8;W # So [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND
+1FB00..1FB92;N # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK
+1FB94..1FBCA;N # So [55] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..WHITE UP-POINTING CHEVRON
+1FBF0..1FBF9;N # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE
+20000..2A6DF;W # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF
+2A6E0..2A6FF;W # Cn [32] <reserved-2A6E0>..<reserved-2A6FF>
+2A700..2B739;W # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739
+2B73A..2B73F;W # Cn [6] <reserved-2B73A>..<reserved-2B73F>
+2B740..2B81D;W # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D
+2B81E..2B81F;W # Cn [2] <reserved-2B81E>..<reserved-2B81F>
+2B820..2CEA1;W # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1
+2CEA2..2CEAF;W # Cn [14] <reserved-2CEA2>..<reserved-2CEAF>
+2CEB0..2EBE0;W # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0
+2EBE1..2F7FF;W # Cn [3103] <reserved-2EBE1>..<reserved-2F7FF>
+2F800..2FA1D;W # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D
+2FA1E..2FA1F;W # Cn [2] <reserved-2FA1E>..<reserved-2FA1F>
+2FA20..2FFFD;W # Cn [1502] <reserved-2FA20>..<reserved-2FFFD>
+30000..3134A;W # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A
+3134B..3134F;W # Cn [5] <reserved-3134B>..<reserved-3134F>
+31350..323AF;W # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF
+323B0..3FFFD;W # Cn [56398] <reserved-323B0>..<reserved-3FFFD>
+E0001;N # Cf LANGUAGE TAG
+E0020..E007F;N # Cf [96] TAG SPACE..CANCEL TAG
+E0100..E01EF;A # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
+F0000..FFFFD;A # Co [65534] <private-use-F0000>..<private-use-FFFFD>
+100000..10FFFD;A # Co [65534] <private-use-100000>..<private-use-10FFFD>
+
+# EOF
diff --git a/lib/stdlib/uc_spec/GraphemeBreakProperty.txt b/lib/stdlib/uc_spec/GraphemeBreakProperty.txt
index dd2569064a..a12b5eef1e 100644
--- a/lib/stdlib/uc_spec/GraphemeBreakProperty.txt
+++ b/lib/stdlib/uc_spec/GraphemeBreakProperty.txt
@@ -1,11 +1,11 @@
-# GraphemeBreakProperty-14.0.0.txt
-# Date: 2021-08-12, 23:13:02 GMT
-# © 2021 Unicode®, Inc.
+# GraphemeBreakProperty-15.0.0.txt
+# Date: 2022-04-27, 17:07:38 GMT
+# © 2022 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
-# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For terms of use, see https://www.unicode.org/terms_of_use.html
#
# Unicode Character Database
-# For documentation, see http://www.unicode.org/reports/tr44/
+# For documentation, see https://www.unicode.org/reports/tr44/
# ================================================
@@ -32,8 +32,9 @@
11A3A ; Prepend # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
11A84..11A89 ; Prepend # Lo [6] SOYOMBO SIGN JIHVAMULIYA..SOYOMBO CLUSTER-INITIAL LETTER SA
11D46 ; Prepend # Lo MASARAM GONDI REPHA
+11F02 ; Prepend # Lo KAWI SIGN REPHA
-# Total code points: 26
+# Total code points: 27
# ================================================
@@ -67,7 +68,7 @@
FEFF ; Control # Cf ZERO WIDTH NO-BREAK SPACE
FFF0..FFF8 ; Control # Cn [9] <reserved-FFF0>..<reserved-FFF8>
FFF9..FFFB ; Control # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR
-13430..13438 ; Control # Cf [9] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END SEGMENT
+13430..1343F ; Control # Cf [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE
1BCA0..1BCA3 ; Control # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
1D173..1D17A ; Control # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
E0000 ; Control # Cn <reserved-E0000>
@@ -76,7 +77,7 @@ E0002..E001F ; Control # Cn [30] <reserved-E0002>..<reserved-E001F>
E0080..E00FF ; Control # Cn [128] <reserved-E0080>..<reserved-E00FF>
E01F0..E0FFF ; Control # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
-# Total code points: 3886
+# Total code points: 3893
# ================================================
@@ -185,7 +186,7 @@ E01F0..E0FFF ; Control # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
0E47..0E4E ; Extend # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
0EB1 ; Extend # Mn LAO VOWEL SIGN MAI KAN
0EB4..0EBC ; Extend # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
-0EC8..0ECD ; Extend # Mn [6] LAO TONE MAI EK..LAO NIGGAHITA
+0EC8..0ECE ; Extend # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN
0F18..0F19 ; Extend # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
0F35 ; Extend # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
0F37 ; Extend # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS
@@ -324,6 +325,7 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
10AE5..10AE6 ; Extend # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
10D24..10D27 ; Extend # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
10EAB..10EAC ; Extend # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK
+10EFD..10EFF ; Extend # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA
10F46..10F50 ; Extend # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
10F82..10F85 ; Extend # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW
11001 ; Extend # Mn BRAHMI SIGN ANUSVARA
@@ -346,6 +348,7 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
11234 ; Extend # Mn KHOJKI SIGN ANUSVARA
11236..11237 ; Extend # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
1123E ; Extend # Mn KHOJKI SIGN SUKUN
+11241 ; Extend # Mn KHOJKI VOWEL SIGN VOCALIC R
112DF ; Extend # Mn KHUDAWADI SIGN ANUSVARA
112E3..112EA ; Extend # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA
11300..11301 ; Extend # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
@@ -413,6 +416,12 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
11D95 ; Extend # Mn GUNJALA GONDI SIGN ANUSVARA
11D97 ; Extend # Mn GUNJALA GONDI VIRAMA
11EF3..11EF4 ; Extend # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+11F00..11F01 ; Extend # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F36..11F3A ; Extend # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F40 ; Extend # Mn KAWI VOWEL SIGN EU
+11F42 ; Extend # Mn KAWI CONJOINER
+13440 ; Extend # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY
+13447..13455 ; Extend # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED
16AF0..16AF4 ; Extend # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
16B30..16B36 ; Extend # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
16F4F ; Extend # Mn MIAO SIGN CONSONANT MODIFIER BAR
@@ -439,16 +448,18 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
1E01B..1E021 ; Extend # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
1E023..1E024 ; Extend # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
1E026..1E02A ; Extend # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E08F ; Extend # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
1E130..1E136 ; Extend # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
1E2AE ; Extend # Mn TOTO SIGN RISING TONE
1E2EC..1E2EF ; Extend # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
+1E4EC..1E4EF ; Extend # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH
1E8D0..1E8D6 ; Extend # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
1E944..1E94A ; Extend # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
1F3FB..1F3FF ; Extend # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6
E0020..E007F ; Extend # Cf [96] TAG SPACE..CANCEL TAG
E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
-# Total code points: 2095
+# Total code points: 2130
# ================================================
@@ -489,6 +500,7 @@ E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
0CC3..0CC4 ; SpacingMark # Mc [2] KANNADA VOWEL SIGN VOCALIC R..KANNADA VOWEL SIGN VOCALIC RR
0CC7..0CC8 ; SpacingMark # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI
0CCA..0CCB ; SpacingMark # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO
+0CF3 ; SpacingMark # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT
0D02..0D03 ; SpacingMark # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA
0D3F..0D40 ; SpacingMark # Mc [2] MALAYALAM VOWEL SIGN I..MALAYALAM VOWEL SIGN II
0D46..0D48 ; SpacingMark # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI
@@ -614,12 +626,16 @@ ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK
11D93..11D94 ; SpacingMark # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU
11D96 ; SpacingMark # Mc GUNJALA GONDI SIGN VISARGA
11EF5..11EF6 ; SpacingMark # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
+11F03 ; SpacingMark # Mc KAWI SIGN VISARGA
+11F34..11F35 ; SpacingMark # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA
+11F3E..11F3F ; SpacingMark # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI
+11F41 ; SpacingMark # Mc KAWI SIGN KILLER
16F51..16F87 ; SpacingMark # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
16FF0..16FF1 ; SpacingMark # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY
1D166 ; SpacingMark # Mc MUSICAL SYMBOL COMBINING SPRECHGESANG STEM
1D16D ; SpacingMark # Mc MUSICAL SYMBOL COMBINING AUGMENTATION DOT
-# Total code points: 388
+# Total code points: 395
# ================================================
diff --git a/lib/stdlib/uc_spec/PropList.txt b/lib/stdlib/uc_spec/PropList.txt
index 0a5a934682..b49d6460c1 100644
--- a/lib/stdlib/uc_spec/PropList.txt
+++ b/lib/stdlib/uc_spec/PropList.txt
@@ -1,11 +1,11 @@
-# PropList-14.0.0.txt
-# Date: 2021-08-12, 23:13:05 GMT
-# © 2021 Unicode®, Inc.
+# PropList-15.0.0.txt
+# Date: 2022-08-05, 22:17:16 GMT
+# © 2022 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
-# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For terms of use, see https://www.unicode.org/terms_of_use.html
#
# Unicode Character Database
-# For documentation, see http://www.unicode.org/reports/tr44/
+# For documentation, see https://www.unicode.org/reports/tr44/
# ================================================
@@ -215,6 +215,7 @@ FF64 ; Terminal_Punctuation # Po HALFWIDTH IDEOGRAPHIC COMMA
11C41..11C43 ; Terminal_Punctuation # Po [3] BHAIKSUKI DANDA..BHAIKSUKI WORD SEPARATOR
11C71 ; Terminal_Punctuation # Po MARCHEN MARK SHAD
11EF7..11EF8 ; Terminal_Punctuation # Po [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION
+11F43..11F44 ; Terminal_Punctuation # Po [2] KAWI DANDA..KAWI DOUBLE DANDA
12470..12474 ; Terminal_Punctuation # Po [5] CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON
16A6E..16A6F ; Terminal_Punctuation # Po [2] MRO DANDA..MRO DOUBLE DANDA
16AF5 ; Terminal_Punctuation # Po BASSA VAH FULL STOP
@@ -224,7 +225,7 @@ FF64 ; Terminal_Punctuation # Po HALFWIDTH IDEOGRAPHIC COMMA
1BC9F ; Terminal_Punctuation # Po DUPLOYAN PUNCTUATION CHINOOK FULL STOP
1DA87..1DA8A ; Terminal_Punctuation # Po [4] SIGNWRITING COMMA..SIGNWRITING COLON
-# Total code points: 276
+# Total code points: 278
# ================================================
@@ -507,6 +508,7 @@ FF41..FF46 ; Hex_Digit # L& [6] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH L
0BD7 ; Other_Alphabetic # Mc TAMIL AU LENGTH MARK
0C00 ; Other_Alphabetic # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
0C01..0C03 ; Other_Alphabetic # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA
+0C04 ; Other_Alphabetic # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
0C3E..0C40 ; Other_Alphabetic # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
0C41..0C44 ; Other_Alphabetic # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR
0C46..0C48 ; Other_Alphabetic # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
@@ -524,6 +526,7 @@ FF41..FF46 ; Hex_Digit # L& [6] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH L
0CCC ; Other_Alphabetic # Mn KANNADA VOWEL SIGN AU
0CD5..0CD6 ; Other_Alphabetic # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK
0CE2..0CE3 ; Other_Alphabetic # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+0CF3 ; Other_Alphabetic # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT
0D00..0D01 ; Other_Alphabetic # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
0D02..0D03 ; Other_Alphabetic # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA
0D3E..0D40 ; Other_Alphabetic # Mc [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II
@@ -548,7 +551,7 @@ FF41..FF46 ; Hex_Digit # L& [6] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH L
0ECD ; Other_Alphabetic # Mn LAO NIGGAHITA
0F71..0F7E ; Other_Alphabetic # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
0F7F ; Other_Alphabetic # Mc TIBETAN SIGN RNAM BCAD
-0F80..0F81 ; Other_Alphabetic # Mn [2] TIBETAN VOWEL SIGN REVERSED I..TIBETAN VOWEL SIGN REVERSED II
+0F80..0F83 ; Other_Alphabetic # Mn [4] TIBETAN VOWEL SIGN REVERSED I..TIBETAN SIGN SNA LDAN
0F8D..0F97 ; Other_Alphabetic # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
0F99..0FBC ; Other_Alphabetic # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
102B..102C ; Other_Alphabetic # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA
@@ -692,6 +695,7 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
11002 ; Other_Alphabetic # Mc BRAHMI SIGN VISARGA
11038..11045 ; Other_Alphabetic # Mn [14] BRAHMI VOWEL SIGN AA..BRAHMI VOWEL SIGN AU
11073..11074 ; Other_Alphabetic # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O
+11080..11081 ; Other_Alphabetic # Mn [2] KAITHI SIGN CANDRABINDU..KAITHI SIGN ANUSVARA
11082 ; Other_Alphabetic # Mc KAITHI SIGN VISARGA
110B0..110B2 ; Other_Alphabetic # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II
110B3..110B6 ; Other_Alphabetic # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
@@ -715,6 +719,7 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
11234 ; Other_Alphabetic # Mn KHOJKI SIGN ANUSVARA
11237 ; Other_Alphabetic # Mn KHOJKI SIGN SHADDA
1123E ; Other_Alphabetic # Mn KHOJKI SIGN SUKUN
+11241 ; Other_Alphabetic # Mn KHOJKI VOWEL SIGN VOCALIC R
112DF ; Other_Alphabetic # Mn KHUDAWADI SIGN ANUSVARA
112E0..112E2 ; Other_Alphabetic # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II
112E3..112E8 ; Other_Alphabetic # Mn [6] KHUDAWADI VOWEL SIGN U..KHUDAWADI VOWEL SIGN AU
@@ -807,6 +812,12 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
11D96 ; Other_Alphabetic # Mc GUNJALA GONDI SIGN VISARGA
11EF3..11EF4 ; Other_Alphabetic # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
11EF5..11EF6 ; Other_Alphabetic # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
+11F00..11F01 ; Other_Alphabetic # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F03 ; Other_Alphabetic # Mc KAWI SIGN VISARGA
+11F34..11F35 ; Other_Alphabetic # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA
+11F36..11F3A ; Other_Alphabetic # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F3E..11F3F ; Other_Alphabetic # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI
+11F40 ; Other_Alphabetic # Mn KAWI VOWEL SIGN EU
16F4F ; Other_Alphabetic # Mn MIAO SIGN CONSONANT MODIFIER BAR
16F51..16F87 ; Other_Alphabetic # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
16F8F..16F92 ; Other_Alphabetic # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
@@ -817,12 +828,13 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
1E01B..1E021 ; Other_Alphabetic # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
1E023..1E024 ; Other_Alphabetic # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
1E026..1E02A ; Other_Alphabetic # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E08F ; Other_Alphabetic # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
1E947 ; Other_Alphabetic # Mn ADLAM HAMZA
1F130..1F149 ; Other_Alphabetic # So [26] SQUARED LATIN CAPITAL LETTER A..SQUARED LATIN CAPITAL LETTER Z
1F150..1F169 ; Other_Alphabetic # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z
1F170..1F189 ; Other_Alphabetic # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z
-# Total code points: 1404
+# Total code points: 1425
# ================================================
@@ -840,14 +852,15 @@ FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COM
18D00..18D08 ; Ideographic # Lo [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08
1B170..1B2FB ; Ideographic # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB
20000..2A6DF ; Ideographic # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF
-2A700..2B738 ; Ideographic # Lo [4153] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B738
+2A700..2B739 ; Ideographic # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739
2B740..2B81D ; Ideographic # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D
2B820..2CEA1 ; Ideographic # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1
2CEB0..2EBE0 ; Ideographic # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0
2F800..2FA1D ; Ideographic # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D
30000..3134A ; Ideographic # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A
+31350..323AF ; Ideographic # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF
-# Total code points: 101661
+# Total code points: 105854
# ================================================
@@ -1028,6 +1041,7 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON
10AE5..10AE6 ; Diacritic # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
10D22..10D23 ; Diacritic # Lo [2] HANIFI ROHINGYA MARK SAKIN..HANIFI ROHINGYA MARK NA KHONNA
10D24..10D27 ; Diacritic # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+10EFD..10EFF ; Diacritic # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA
10F46..10F50 ; Diacritic # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
10F82..10F85 ; Diacritic # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW
11046 ; Diacritic # Mn BRAHMI VIRAMA
@@ -1064,6 +1078,7 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON
11D42 ; Diacritic # Mn MASARAM GONDI SIGN NUKTA
11D44..11D45 ; Diacritic # Mn [2] MASARAM GONDI SIGN HALANTA..MASARAM GONDI VIRAMA
11D97 ; Diacritic # Mn GUNJALA GONDI VIRAMA
+13447..13455 ; Diacritic # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED
16AF0..16AF4 ; Diacritic # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
16B30..16B36 ; Diacritic # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
16F8F..16F92 ; Diacritic # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
@@ -1079,6 +1094,7 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON
1D17B..1D182 ; Diacritic # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
1D185..1D18B ; Diacritic # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
1D1AA..1D1AD ; Diacritic # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+1E030..1E06D ; Diacritic # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
1E130..1E136 ; Diacritic # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
1E2AE ; Diacritic # Mn TOTO SIGN RISING TONE
1E2EC..1E2EF ; Diacritic # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
@@ -1086,7 +1102,7 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON
1E944..1E946 ; Diacritic # Mn [3] ADLAM ALIF LENGTHENER..ADLAM GEMINATION MARK
1E948..1E94A ; Diacritic # Mn [3] ADLAM CONSONANT MODIFIER..ADLAM NUKTA
-# Total code points: 1064
+# Total code points: 1144
# ================================================
@@ -1135,6 +1151,7 @@ FF70 ; Extender # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND
02E0..02E4 ; Other_Lowercase # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
0345 ; Other_Lowercase # Mn COMBINING GREEK YPOGEGRAMMENI
037A ; Other_Lowercase # Lm GREEK YPOGEGRAMMENI
+10FC ; Other_Lowercase # Lm MODIFIER LETTER GEORGIAN NAR
1D2C..1D6A ; Other_Lowercase # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
1D78 ; Other_Lowercase # Lm MODIFIER LETTER CYRILLIC EN
1D9B..1DBF ; Other_Lowercase # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
@@ -1146,14 +1163,17 @@ FF70 ; Extender # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND
2C7C..2C7D ; Other_Lowercase # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
A69C..A69D ; Other_Lowercase # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
A770 ; Other_Lowercase # Lm MODIFIER LETTER US
+A7F2..A7F4 ; Other_Lowercase # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
A7F8..A7F9 ; Other_Lowercase # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
AB5C..AB5F ; Other_Lowercase # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB69 ; Other_Lowercase # Lm MODIFIER LETTER SMALL TURNED W
10780 ; Other_Lowercase # Lm MODIFIER LETTER SMALL CAPITAL AA
10783..10785 ; Other_Lowercase # Lm [3] MODIFIER LETTER SMALL AE..MODIFIER LETTER SMALL B WITH HOOK
10787..107B0 ; Other_Lowercase # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
107B2..107BA ; Other_Lowercase # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+1E030..1E06D ; Other_Lowercase # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
-# Total code points: 244
+# Total code points: 311
# ================================================
@@ -1251,13 +1271,14 @@ FA21 ; Unified_Ideograph # Lo CJK COMPATIBILITY IDEOGRAPH-FA21
FA23..FA24 ; Unified_Ideograph # Lo [2] CJK COMPATIBILITY IDEOGRAPH-FA23..CJK COMPATIBILITY IDEOGRAPH-FA24
FA27..FA29 ; Unified_Ideograph # Lo [3] CJK COMPATIBILITY IDEOGRAPH-FA27..CJK COMPATIBILITY IDEOGRAPH-FA29
20000..2A6DF ; Unified_Ideograph # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF
-2A700..2B738 ; Unified_Ideograph # Lo [4153] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B738
+2A700..2B739 ; Unified_Ideograph # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739
2B740..2B81D ; Unified_Ideograph # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D
2B820..2CEA1 ; Unified_Ideograph # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1
2CEB0..2EBE0 ; Unified_Ideograph # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0
30000..3134A ; Unified_Ideograph # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A
+31350..323AF ; Unified_Ideograph # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF
-# Total code points: 92865
+# Total code points: 97058
# ================================================
@@ -1323,8 +1344,10 @@ E0001 ; Deprecated # Cf LANGUAGE TAG
1D65E..1D65F ; Soft_Dotted # L& [2] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL I..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL J
1D692..1D693 ; Soft_Dotted # L& [2] MATHEMATICAL MONOSPACE SMALL I..MATHEMATICAL MONOSPACE SMALL J
1DF1A ; Soft_Dotted # L& LATIN SMALL LETTER I WITH STROKE AND RETROFLEX HOOK
+1E04C..1E04D ; Soft_Dotted # Lm [2] MODIFIER LETTER CYRILLIC SMALL BYELORUSSIAN-UKRAINIAN I..MODIFIER LETTER CYRILLIC SMALL JE
+1E068 ; Soft_Dotted # Lm CYRILLIC SUBSCRIPT SMALL LETTER BYELORUSSIAN-UKRAINIAN I
-# Total code points: 47
+# Total code points: 50
# ================================================
@@ -1430,6 +1453,7 @@ FF61 ; Sentence_Terminal # Po HALFWIDTH IDEOGRAPHIC FULL STOP
11A9B..11A9C ; Sentence_Terminal # Po [2] SOYOMBO MARK SHAD..SOYOMBO MARK DOUBLE SHAD
11C41..11C42 ; Sentence_Terminal # Po [2] BHAIKSUKI DANDA..BHAIKSUKI DOUBLE DANDA
11EF7..11EF8 ; Sentence_Terminal # Po [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION
+11F43..11F44 ; Sentence_Terminal # Po [2] KAWI DANDA..KAWI DOUBLE DANDA
16A6E..16A6F ; Sentence_Terminal # Po [2] MRO DANDA..MRO DOUBLE DANDA
16AF5 ; Sentence_Terminal # Po BASSA VAH FULL STOP
16B37..16B38 ; Sentence_Terminal # Po [2] PAHAWH HMONG SIGN VOS THOM..PAHAWH HMONG SIGN VOS TSHAB CEEB
@@ -1438,7 +1462,7 @@ FF61 ; Sentence_Terminal # Po HALFWIDTH IDEOGRAPHIC FULL STOP
1BC9F ; Sentence_Terminal # Po DUPLOYAN PUNCTUATION CHINOOK FULL STOP
1DA88 ; Sentence_Terminal # Po SIGNWRITING FULL STOP
-# Total code points: 152
+# Total code points: 154
# ================================================
diff --git a/lib/stdlib/uc_spec/README-UPDATE.txt b/lib/stdlib/uc_spec/README-UPDATE.txt
index 8af7b54a07..0bf6117611 100644
--- a/lib/stdlib/uc_spec/README-UPDATE.txt
+++ b/lib/stdlib/uc_spec/README-UPDATE.txt
@@ -1,7 +1,7 @@
-Unicode 14.0.0 was updated from:
-- https://www.unicode.org/Public/14.0.0/ucd/
-- https://www.unicode.org/Public/14.0.0/ucd/auxiliary/
-- https://www.unicode.org/Public/14.0.0/ucd/emoji/
+Unicode 15.0.0 was updated from:
+- https://www.unicode.org/Public/15.0.0/ucd/
+- https://www.unicode.org/Public/15.0.0/ucd/auxiliary/
+- https://www.unicode.org/Public/15.0.0/ucd/emoji/
When updating the Unicode version please follow these steps:
@@ -17,6 +17,7 @@ No subfolder should be created.
- UnicodeData.txt
- auxiliary/GraphemeBreakProperty.txt
- emoji/emoji-data.txt
+ - EastAsianWidth.txt
2. Copy the following test files to lib/stdlib/test/unicode_util_SUITE_data/
replacing existing ones. No subfolder should be created.
@@ -35,7 +36,12 @@ this very same file (lib/stdlib/uc_spec/README-UPDATE.txt).
Remember to update these instructions if a new file is added or any other change
is required for future version updates.
-6. Run the test for the Unicode suite from the OTP repository root dir.
+6. Check if the test file needs to be updated:
+ (cd $ERL_TOP/lib/stdlib/uc_spec; escript gen_unicode_mod.escript update_tests)
+ If ../test/unicode_util_SUITE_data/unicode_table.bin is updated include it in
+ the commit.
+
+7. Run the test for the Unicode suite from the OTP repository root dir.
$ export ERL_TOP=$PWD
$ export PATH=$ERL_TOP/bin:$PATH
$ ./otp_build all -a && ./otp_build tests
diff --git a/lib/stdlib/uc_spec/SpecialCasing.txt b/lib/stdlib/uc_spec/SpecialCasing.txt
index 1c2e968a8c..08d04fa942 100644
--- a/lib/stdlib/uc_spec/SpecialCasing.txt
+++ b/lib/stdlib/uc_spec/SpecialCasing.txt
@@ -1,11 +1,11 @@
-# SpecialCasing-14.0.0.txt
-# Date: 2021-03-08, 19:35:55 GMT
-# © 2021 Unicode®, Inc.
+# SpecialCasing-15.0.0.txt
+# Date: 2022-02-02, 23:35:52 GMT
+# © 2022 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
-# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For terms of use, see https://www.unicode.org/terms_of_use.html
#
# Unicode Character Database
-# For documentation, see http://www.unicode.org/reports/tr44/
+# For documentation, see https://www.unicode.org/reports/tr44/
#
# Special Casing
#
diff --git a/lib/stdlib/uc_spec/UnicodeData.txt b/lib/stdlib/uc_spec/UnicodeData.txt
index b5abef7ed4..ea963a7162 100644
--- a/lib/stdlib/uc_spec/UnicodeData.txt
+++ b/lib/stdlib/uc_spec/UnicodeData.txt
@@ -2975,6 +2975,7 @@
0CEF;KANNADA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;;
0CF1;KANNADA SIGN JIHVAMULIYA;Lo;0;L;;;;;N;;;;;
0CF2;KANNADA SIGN UPADHMANIYA;Lo;0;L;;;;;N;;;;;
+0CF3;KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT;Mc;0;L;;;;;N;;;;;
0D00;MALAYALAM SIGN COMBINING ANUSVARA ABOVE;Mn;0;NSM;;;;;N;;;;;
0D01;MALAYALAM SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;;
0D02;MALAYALAM SIGN ANUSVARA;Mc;0;L;;;;;N;;;;;
@@ -3339,6 +3340,7 @@
0ECB;LAO TONE MAI CATAWA;Mn;122;NSM;;;;;N;;;;;
0ECC;LAO CANCELLATION MARK;Mn;0;NSM;;;;;N;;;;;
0ECD;LAO NIGGAHITA;Mn;0;NSM;;;;;N;;;;;
+0ECE;LAO YAMAKKAN;Mn;0;NSM;;;;;N;;;;;
0ED0;LAO DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;;
0ED1;LAO DIGIT ONE;Nd;0;L;;1;1;1;N;;;;;
0ED2;LAO DIGIT TWO;Nd;0;L;;2;2;2;N;;;;;
@@ -19393,6 +19395,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
10EAD;YEZIDI HYPHENATION MARK;Pd;0;R;;;;;N;;;;;
10EB0;YEZIDI LETTER LAM WITH DOT ABOVE;Lo;0;R;;;;;N;;;;;
10EB1;YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE;Lo;0;R;;;;;N;;;;;
+10EFD;ARABIC SMALL LOW WORD SAKTA;Mn;220;NSM;;;;;N;;;;;
+10EFE;ARABIC SMALL LOW WORD QASR;Mn;220;NSM;;;;;N;;;;;
+10EFF;ARABIC SMALL LOW WORD MADDA;Mn;220;NSM;;;;;N;;;;;
10F00;OLD SOGDIAN LETTER ALEPH;Lo;0;R;;;;;N;;;;;
10F01;OLD SOGDIAN LETTER FINAL ALEPH;Lo;0;R;;;;;N;;;;;
10F02;OLD SOGDIAN LETTER BETH;Lo;0;R;;;;;N;;;;;
@@ -20058,6 +20063,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1123C;KHOJKI DOUBLE SECTION MARK;Po;0;L;;;;;N;;;;;
1123D;KHOJKI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;;
1123E;KHOJKI SIGN SUKUN;Mn;0;NSM;;;;;N;;;;;
+1123F;KHOJKI LETTER QA;Lo;0;L;;;;;N;;;;;
+11240;KHOJKI LETTER SHORT I;Lo;0;L;;;;;N;;;;;
+11241;KHOJKI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;;
11280;MULTANI LETTER A;Lo;0;L;;;;;N;;;;;
11281;MULTANI LETTER I;Lo;0;L;;;;;N;;;;;
11282;MULTANI LETTER U;Lo;0;L;;;;;N;;;;;
@@ -21256,6 +21264,16 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11AF6;PAU CIN HAU LOW-FALLING TONE LONG FINAL;Lo;0;L;;;;;N;;;;;
11AF7;PAU CIN HAU LOW-FALLING TONE FINAL;Lo;0;L;;;;;N;;;;;
11AF8;PAU CIN HAU GLOTTAL STOP FINAL;Lo;0;L;;;;;N;;;;;
+11B00;DEVANAGARI HEAD MARK;Po;0;L;;;;;N;;;;;
+11B01;DEVANAGARI HEAD MARK WITH HEADSTROKE;Po;0;L;;;;;N;;;;;
+11B02;DEVANAGARI SIGN BHALE;Po;0;L;;;;;N;;;;;
+11B03;DEVANAGARI SIGN BHALE WITH HOOK;Po;0;L;;;;;N;;;;;
+11B04;DEVANAGARI SIGN EXTENDED BHALE;Po;0;L;;;;;N;;;;;
+11B05;DEVANAGARI SIGN EXTENDED BHALE WITH HOOK;Po;0;L;;;;;N;;;;;
+11B06;DEVANAGARI SIGN WESTERN FIVE-LIKE BHALE;Po;0;L;;;;;N;;;;;
+11B07;DEVANAGARI SIGN WESTERN NINE-LIKE BHALE;Po;0;L;;;;;N;;;;;
+11B08;DEVANAGARI SIGN REVERSED NINE-LIKE BHALE;Po;0;L;;;;;N;;;;;
+11B09;DEVANAGARI SIGN MINDU;Po;0;L;;;;;N;;;;;
11C00;BHAIKSUKI LETTER A;Lo;0;L;;;;;N;;;;;
11C01;BHAIKSUKI LETTER AA;Lo;0;L;;;;;N;;;;;
11C02;BHAIKSUKI LETTER I;Lo;0;L;;;;;N;;;;;
@@ -21584,6 +21602,92 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11EF6;MAKASAR VOWEL SIGN O;Mc;0;L;;;;;N;;;;;
11EF7;MAKASAR PASSIMBANG;Po;0;L;;;;;N;;;;;
11EF8;MAKASAR END OF SECTION;Po;0;L;;;;;N;;;;;
+11F00;KAWI SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;;
+11F01;KAWI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;;
+11F02;KAWI SIGN REPHA;Lo;0;L;;;;;N;;;;;
+11F03;KAWI SIGN VISARGA;Mc;0;L;;;;;N;;;;;
+11F04;KAWI LETTER A;Lo;0;L;;;;;N;;;;;
+11F05;KAWI LETTER AA;Lo;0;L;;;;;N;;;;;
+11F06;KAWI LETTER I;Lo;0;L;;;;;N;;;;;
+11F07;KAWI LETTER II;Lo;0;L;;;;;N;;;;;
+11F08;KAWI LETTER U;Lo;0;L;;;;;N;;;;;
+11F09;KAWI LETTER UU;Lo;0;L;;;;;N;;;;;
+11F0A;KAWI LETTER VOCALIC R;Lo;0;L;;;;;N;;;;;
+11F0B;KAWI LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;;
+11F0C;KAWI LETTER VOCALIC L;Lo;0;L;;;;;N;;;;;
+11F0D;KAWI LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;;
+11F0E;KAWI LETTER E;Lo;0;L;;;;;N;;;;;
+11F0F;KAWI LETTER AI;Lo;0;L;;;;;N;;;;;
+11F10;KAWI LETTER O;Lo;0;L;;;;;N;;;;;
+11F12;KAWI LETTER KA;Lo;0;L;;;;;N;;;;;
+11F13;KAWI LETTER KHA;Lo;0;L;;;;;N;;;;;
+11F14;KAWI LETTER GA;Lo;0;L;;;;;N;;;;;
+11F15;KAWI LETTER GHA;Lo;0;L;;;;;N;;;;;
+11F16;KAWI LETTER NGA;Lo;0;L;;;;;N;;;;;
+11F17;KAWI LETTER CA;Lo;0;L;;;;;N;;;;;
+11F18;KAWI LETTER CHA;Lo;0;L;;;;;N;;;;;
+11F19;KAWI LETTER JA;Lo;0;L;;;;;N;;;;;
+11F1A;KAWI LETTER JHA;Lo;0;L;;;;;N;;;;;
+11F1B;KAWI LETTER NYA;Lo;0;L;;;;;N;;;;;
+11F1C;KAWI LETTER TTA;Lo;0;L;;;;;N;;;;;
+11F1D;KAWI LETTER TTHA;Lo;0;L;;;;;N;;;;;
+11F1E;KAWI LETTER DDA;Lo;0;L;;;;;N;;;;;
+11F1F;KAWI LETTER DDHA;Lo;0;L;;;;;N;;;;;
+11F20;KAWI LETTER NNA;Lo;0;L;;;;;N;;;;;
+11F21;KAWI LETTER TA;Lo;0;L;;;;;N;;;;;
+11F22;KAWI LETTER THA;Lo;0;L;;;;;N;;;;;
+11F23;KAWI LETTER DA;Lo;0;L;;;;;N;;;;;
+11F24;KAWI LETTER DHA;Lo;0;L;;;;;N;;;;;
+11F25;KAWI LETTER NA;Lo;0;L;;;;;N;;;;;
+11F26;KAWI LETTER PA;Lo;0;L;;;;;N;;;;;
+11F27;KAWI LETTER PHA;Lo;0;L;;;;;N;;;;;
+11F28;KAWI LETTER BA;Lo;0;L;;;;;N;;;;;
+11F29;KAWI LETTER BHA;Lo;0;L;;;;;N;;;;;
+11F2A;KAWI LETTER MA;Lo;0;L;;;;;N;;;;;
+11F2B;KAWI LETTER YA;Lo;0;L;;;;;N;;;;;
+11F2C;KAWI LETTER RA;Lo;0;L;;;;;N;;;;;
+11F2D;KAWI LETTER LA;Lo;0;L;;;;;N;;;;;
+11F2E;KAWI LETTER WA;Lo;0;L;;;;;N;;;;;
+11F2F;KAWI LETTER SHA;Lo;0;L;;;;;N;;;;;
+11F30;KAWI LETTER SSA;Lo;0;L;;;;;N;;;;;
+11F31;KAWI LETTER SA;Lo;0;L;;;;;N;;;;;
+11F32;KAWI LETTER HA;Lo;0;L;;;;;N;;;;;
+11F33;KAWI LETTER JNYA;Lo;0;L;;;;;N;;;;;
+11F34;KAWI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;;
+11F35;KAWI VOWEL SIGN ALTERNATE AA;Mc;0;L;;;;;N;;;;;
+11F36;KAWI VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;;
+11F37;KAWI VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;;
+11F38;KAWI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;;
+11F39;KAWI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;;
+11F3A;KAWI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;;
+11F3E;KAWI VOWEL SIGN E;Mc;0;L;;;;;N;;;;;
+11F3F;KAWI VOWEL SIGN AI;Mc;0;L;;;;;N;;;;;
+11F40;KAWI VOWEL SIGN EU;Mn;0;NSM;;;;;N;;;;;
+11F41;KAWI SIGN KILLER;Mc;9;L;;;;;N;;;;;
+11F42;KAWI CONJOINER;Mn;9;NSM;;;;;N;;;;;
+11F43;KAWI DANDA;Po;0;L;;;;;N;;;;;
+11F44;KAWI DOUBLE DANDA;Po;0;L;;;;;N;;;;;
+11F45;KAWI PUNCTUATION SECTION MARKER;Po;0;L;;;;;N;;;;;
+11F46;KAWI PUNCTUATION ALTERNATE SECTION MARKER;Po;0;L;;;;;N;;;;;
+11F47;KAWI PUNCTUATION FLOWER;Po;0;L;;;;;N;;;;;
+11F48;KAWI PUNCTUATION SPACE FILLER;Po;0;L;;;;;N;;;;;
+11F49;KAWI PUNCTUATION DOT;Po;0;L;;;;;N;;;;;
+11F4A;KAWI PUNCTUATION DOUBLE DOT;Po;0;L;;;;;N;;;;;
+11F4B;KAWI PUNCTUATION TRIPLE DOT;Po;0;L;;;;;N;;;;;
+11F4C;KAWI PUNCTUATION CIRCLE;Po;0;L;;;;;N;;;;;
+11F4D;KAWI PUNCTUATION FILLED CIRCLE;Po;0;L;;;;;N;;;;;
+11F4E;KAWI PUNCTUATION SPIRAL;Po;0;L;;;;;N;;;;;
+11F4F;KAWI PUNCTUATION CLOSING SPIRAL;Po;0;L;;;;;N;;;;;
+11F50;KAWI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;;
+11F51;KAWI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;;
+11F52;KAWI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;;
+11F53;KAWI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;;
+11F54;KAWI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;;
+11F55;KAWI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;;
+11F56;KAWI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;;
+11F57;KAWI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;;
+11F58;KAWI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;;
+11F59;KAWI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;;
11FB0;LISU LETTER YHA;Lo;0;L;;;;;N;;;;;
11FC0;TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH;No;0;L;;;;1/320;N;;;;;
11FC1;TAMIL FRACTION ONE ONE-HUNDRED-AND-SIXTIETH;No;0;L;;;;1/160;N;;;;;
@@ -24040,6 +24144,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1342C;EGYPTIAN HIEROGLYPH AA030;Lo;0;L;;;;;N;;;;;
1342D;EGYPTIAN HIEROGLYPH AA031;Lo;0;L;;;;;N;;;;;
1342E;EGYPTIAN HIEROGLYPH AA032;Lo;0;L;;;;;N;;;;;
+1342F;EGYPTIAN HIEROGLYPH V011D;Lo;0;L;;;;;N;;;;;
13430;EGYPTIAN HIEROGLYPH VERTICAL JOINER;Cf;0;L;;;;;N;;;;;
13431;EGYPTIAN HIEROGLYPH HORIZONTAL JOINER;Cf;0;L;;;;;N;;;;;
13432;EGYPTIAN HIEROGLYPH INSERT AT TOP START;Cf;0;L;;;;;N;;;;;
@@ -24049,6 +24154,35 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
13436;EGYPTIAN HIEROGLYPH OVERLAY MIDDLE;Cf;0;L;;;;;N;;;;;
13437;EGYPTIAN HIEROGLYPH BEGIN SEGMENT;Cf;0;L;;;;;N;;;;;
13438;EGYPTIAN HIEROGLYPH END SEGMENT;Cf;0;L;;;;;N;;;;;
+13439;EGYPTIAN HIEROGLYPH INSERT AT MIDDLE;Cf;0;L;;;;;N;;;;;
+1343A;EGYPTIAN HIEROGLYPH INSERT AT TOP;Cf;0;L;;;;;N;;;;;
+1343B;EGYPTIAN HIEROGLYPH INSERT AT BOTTOM;Cf;0;L;;;;;N;;;;;
+1343C;EGYPTIAN HIEROGLYPH BEGIN ENCLOSURE;Cf;0;L;;;;;N;;;;;
+1343D;EGYPTIAN HIEROGLYPH END ENCLOSURE;Cf;0;L;;;;;N;;;;;
+1343E;EGYPTIAN HIEROGLYPH BEGIN WALLED ENCLOSURE;Cf;0;L;;;;;N;;;;;
+1343F;EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE;Cf;0;L;;;;;N;;;;;
+13440;EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY;Mn;0;NSM;;;;;N;;;;;
+13441;EGYPTIAN HIEROGLYPH FULL BLANK;Lo;0;L;;;;;N;;;;;
+13442;EGYPTIAN HIEROGLYPH HALF BLANK;Lo;0;L;;;;;N;;;;;
+13443;EGYPTIAN HIEROGLYPH LOST SIGN;Lo;0;L;;;;;N;;;;;
+13444;EGYPTIAN HIEROGLYPH HALF LOST SIGN;Lo;0;L;;;;;N;;;;;
+13445;EGYPTIAN HIEROGLYPH TALL LOST SIGN;Lo;0;L;;;;;N;;;;;
+13446;EGYPTIAN HIEROGLYPH WIDE LOST SIGN;Lo;0;L;;;;;N;;;;;
+13447;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START;Mn;0;NSM;;;;;N;;;;;
+13448;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT BOTTOM START;Mn;0;NSM;;;;;N;;;;;
+13449;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT START;Mn;0;NSM;;;;;N;;;;;
+1344A;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP END;Mn;0;NSM;;;;;N;;;;;
+1344B;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP;Mn;0;NSM;;;;;N;;;;;
+1344C;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT BOTTOM START AND TOP END;Mn;0;NSM;;;;;N;;;;;
+1344D;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT START AND TOP;Mn;0;NSM;;;;;N;;;;;
+1344E;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT BOTTOM END;Mn;0;NSM;;;;;N;;;;;
+1344F;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START AND BOTTOM END;Mn;0;NSM;;;;;N;;;;;
+13450;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT BOTTOM;Mn;0;NSM;;;;;N;;;;;
+13451;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT START AND BOTTOM;Mn;0;NSM;;;;;N;;;;;
+13452;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT END;Mn;0;NSM;;;;;N;;;;;
+13453;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP AND END;Mn;0;NSM;;;;;N;;;;;
+13454;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT BOTTOM AND END;Mn;0;NSM;;;;;N;;;;;
+13455;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED;Mn;0;NSM;;;;;N;;;;;
14400;ANATOLIAN HIEROGLYPH A001;Lo;0;L;;;;;N;;;;;
14401;ANATOLIAN HIEROGLYPH A002;Lo;0;L;;;;;N;;;;;
14402;ANATOLIAN HIEROGLYPH A003;Lo;0;L;;;;;N;;;;;
@@ -27289,9 +27423,11 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1B120;KATAKANA LETTER ARCHAIC YI;Lo;0;L;;;;;N;;;;;
1B121;KATAKANA LETTER ARCHAIC YE;Lo;0;L;;;;;N;;;;;
1B122;KATAKANA LETTER ARCHAIC WU;Lo;0;L;;;;;N;;;;;
+1B132;HIRAGANA LETTER SMALL KO;Lo;0;L;;;;;N;;;;;
1B150;HIRAGANA LETTER SMALL WI;Lo;0;L;;;;;N;;;;;
1B151;HIRAGANA LETTER SMALL WE;Lo;0;L;;;;;N;;;;;
1B152;HIRAGANA LETTER SMALL WO;Lo;0;L;;;;;N;;;;;
+1B155;KATAKANA LETTER SMALL KO;Lo;0;L;;;;;N;;;;;
1B164;KATAKANA LETTER SMALL WI;Lo;0;L;;;;;N;;;;;
1B165;KATAKANA LETTER SMALL WE;Lo;0;L;;;;;N;;;;;
1B166;KATAKANA LETTER SMALL WO;Lo;0;L;;;;;N;;;;;
@@ -28573,6 +28709,26 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1D243;COMBINING GREEK MUSICAL TETRASEME;Mn;230;NSM;;;;;N;;;;;
1D244;COMBINING GREEK MUSICAL PENTASEME;Mn;230;NSM;;;;;N;;;;;
1D245;GREEK MUSICAL LEIMMA;So;0;ON;;;;;N;;;;;
+1D2C0;KAKTOVIK NUMERAL ZERO;No;0;L;;;;0;N;;;;;
+1D2C1;KAKTOVIK NUMERAL ONE;No;0;L;;;;1;N;;;;;
+1D2C2;KAKTOVIK NUMERAL TWO;No;0;L;;;;2;N;;;;;
+1D2C3;KAKTOVIK NUMERAL THREE;No;0;L;;;;3;N;;;;;
+1D2C4;KAKTOVIK NUMERAL FOUR;No;0;L;;;;4;N;;;;;
+1D2C5;KAKTOVIK NUMERAL FIVE;No;0;L;;;;5;N;;;;;
+1D2C6;KAKTOVIK NUMERAL SIX;No;0;L;;;;6;N;;;;;
+1D2C7;KAKTOVIK NUMERAL SEVEN;No;0;L;;;;7;N;;;;;
+1D2C8;KAKTOVIK NUMERAL EIGHT;No;0;L;;;;8;N;;;;;
+1D2C9;KAKTOVIK NUMERAL NINE;No;0;L;;;;9;N;;;;;
+1D2CA;KAKTOVIK NUMERAL TEN;No;0;L;;;;10;N;;;;;
+1D2CB;KAKTOVIK NUMERAL ELEVEN;No;0;L;;;;11;N;;;;;
+1D2CC;KAKTOVIK NUMERAL TWELVE;No;0;L;;;;12;N;;;;;
+1D2CD;KAKTOVIK NUMERAL THIRTEEN;No;0;L;;;;13;N;;;;;
+1D2CE;KAKTOVIK NUMERAL FOURTEEN;No;0;L;;;;14;N;;;;;
+1D2CF;KAKTOVIK NUMERAL FIFTEEN;No;0;L;;;;15;N;;;;;
+1D2D0;KAKTOVIK NUMERAL SIXTEEN;No;0;L;;;;16;N;;;;;
+1D2D1;KAKTOVIK NUMERAL SEVENTEEN;No;0;L;;;;17;N;;;;;
+1D2D2;KAKTOVIK NUMERAL EIGHTEEN;No;0;L;;;;18;N;;;;;
+1D2D3;KAKTOVIK NUMERAL NINETEEN;No;0;L;;;;19;N;;;;;
1D2E0;MAYAN NUMERAL ZERO;No;0;L;;;;0;N;;;;;
1D2E1;MAYAN NUMERAL ONE;No;0;L;;;;1;N;;;;;
1D2E2;MAYAN NUMERAL TWO;No;0;L;;;;2;N;;;;;
@@ -30404,6 +30560,12 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1DF1C;LATIN SMALL LETTER TESH DIGRAPH WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;;
1DF1D;LATIN SMALL LETTER C WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;;
1DF1E;LATIN SMALL LETTER S WITH CURL;Ll;0;L;;;;;N;;;;;
+1DF25;LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;;
+1DF26;LATIN SMALL LETTER L WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;;
+1DF27;LATIN SMALL LETTER N WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;;
+1DF28;LATIN SMALL LETTER R WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;;
+1DF29;LATIN SMALL LETTER S WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;;
+1DF2A;LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;;
1E000;COMBINING GLAGOLITIC LETTER AZU;Mn;230;NSM;;;;;N;;;;;
1E001;COMBINING GLAGOLITIC LETTER BUKY;Mn;230;NSM;;;;;N;;;;;
1E002;COMBINING GLAGOLITIC LETTER VEDE;Mn;230;NSM;;;;;N;;;;;
@@ -30442,6 +30604,69 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1E028;COMBINING GLAGOLITIC LETTER BIG YUS;Mn;230;NSM;;;;;N;;;;;
1E029;COMBINING GLAGOLITIC LETTER IOTATED BIG YUS;Mn;230;NSM;;;;;N;;;;;
1E02A;COMBINING GLAGOLITIC LETTER FITA;Mn;230;NSM;;;;;N;;;;;
+1E030;MODIFIER LETTER CYRILLIC SMALL A;Lm;0;L;<super> 0430;;;;N;;;;;
+1E031;MODIFIER LETTER CYRILLIC SMALL BE;Lm;0;L;<super> 0431;;;;N;;;;;
+1E032;MODIFIER LETTER CYRILLIC SMALL VE;Lm;0;L;<super> 0432;;;;N;;;;;
+1E033;MODIFIER LETTER CYRILLIC SMALL GHE;Lm;0;L;<super> 0433;;;;N;;;;;
+1E034;MODIFIER LETTER CYRILLIC SMALL DE;Lm;0;L;<super> 0434;;;;N;;;;;
+1E035;MODIFIER LETTER CYRILLIC SMALL IE;Lm;0;L;<super> 0435;;;;N;;;;;
+1E036;MODIFIER LETTER CYRILLIC SMALL ZHE;Lm;0;L;<super> 0436;;;;N;;;;;
+1E037;MODIFIER LETTER CYRILLIC SMALL ZE;Lm;0;L;<super> 0437;;;;N;;;;;
+1E038;MODIFIER LETTER CYRILLIC SMALL I;Lm;0;L;<super> 0438;;;;N;;;;;
+1E039;MODIFIER LETTER CYRILLIC SMALL KA;Lm;0;L;<super> 043A;;;;N;;;;;
+1E03A;MODIFIER LETTER CYRILLIC SMALL EL;Lm;0;L;<super> 043B;;;;N;;;;;
+1E03B;MODIFIER LETTER CYRILLIC SMALL EM;Lm;0;L;<super> 043C;;;;N;;;;;
+1E03C;MODIFIER LETTER CYRILLIC SMALL O;Lm;0;L;<super> 043E;;;;N;;;;;
+1E03D;MODIFIER LETTER CYRILLIC SMALL PE;Lm;0;L;<super> 043F;;;;N;;;;;
+1E03E;MODIFIER LETTER CYRILLIC SMALL ER;Lm;0;L;<super> 0440;;;;N;;;;;
+1E03F;MODIFIER LETTER CYRILLIC SMALL ES;Lm;0;L;<super> 0441;;;;N;;;;;
+1E040;MODIFIER LETTER CYRILLIC SMALL TE;Lm;0;L;<super> 0442;;;;N;;;;;
+1E041;MODIFIER LETTER CYRILLIC SMALL U;Lm;0;L;<super> 0443;;;;N;;;;;
+1E042;MODIFIER LETTER CYRILLIC SMALL EF;Lm;0;L;<super> 0444;;;;N;;;;;
+1E043;MODIFIER LETTER CYRILLIC SMALL HA;Lm;0;L;<super> 0445;;;;N;;;;;
+1E044;MODIFIER LETTER CYRILLIC SMALL TSE;Lm;0;L;<super> 0446;;;;N;;;;;
+1E045;MODIFIER LETTER CYRILLIC SMALL CHE;Lm;0;L;<super> 0447;;;;N;;;;;
+1E046;MODIFIER LETTER CYRILLIC SMALL SHA;Lm;0;L;<super> 0448;;;;N;;;;;
+1E047;MODIFIER LETTER CYRILLIC SMALL YERU;Lm;0;L;<super> 044B;;;;N;;;;;
+1E048;MODIFIER LETTER CYRILLIC SMALL E;Lm;0;L;<super> 044D;;;;N;;;;;
+1E049;MODIFIER LETTER CYRILLIC SMALL YU;Lm;0;L;<super> 044E;;;;N;;;;;
+1E04A;MODIFIER LETTER CYRILLIC SMALL DZZE;Lm;0;L;<super> A689;;;;N;;;;;
+1E04B;MODIFIER LETTER CYRILLIC SMALL SCHWA;Lm;0;L;<super> 04D9;;;;N;;;;;
+1E04C;MODIFIER LETTER CYRILLIC SMALL BYELORUSSIAN-UKRAINIAN I;Lm;0;L;<super> 0456;;;;N;;;;;
+1E04D;MODIFIER LETTER CYRILLIC SMALL JE;Lm;0;L;<super> 0458;;;;N;;;;;
+1E04E;MODIFIER LETTER CYRILLIC SMALL BARRED O;Lm;0;L;<super> 04E9;;;;N;;;;;
+1E04F;MODIFIER LETTER CYRILLIC SMALL STRAIGHT U;Lm;0;L;<super> 04AF;;;;N;;;;;
+1E050;MODIFIER LETTER CYRILLIC SMALL PALOCHKA;Lm;0;L;<super> 04CF;;;;N;;;;;
+1E051;CYRILLIC SUBSCRIPT SMALL LETTER A;Lm;0;L;<sub> 0430;;;;N;;;;;
+1E052;CYRILLIC SUBSCRIPT SMALL LETTER BE;Lm;0;L;<sub> 0431;;;;N;;;;;
+1E053;CYRILLIC SUBSCRIPT SMALL LETTER VE;Lm;0;L;<sub> 0432;;;;N;;;;;
+1E054;CYRILLIC SUBSCRIPT SMALL LETTER GHE;Lm;0;L;<sub> 0433;;;;N;;;;;
+1E055;CYRILLIC SUBSCRIPT SMALL LETTER DE;Lm;0;L;<sub> 0434;;;;N;;;;;
+1E056;CYRILLIC SUBSCRIPT SMALL LETTER IE;Lm;0;L;<sub> 0435;;;;N;;;;;
+1E057;CYRILLIC SUBSCRIPT SMALL LETTER ZHE;Lm;0;L;<sub> 0436;;;;N;;;;;
+1E058;CYRILLIC SUBSCRIPT SMALL LETTER ZE;Lm;0;L;<sub> 0437;;;;N;;;;;
+1E059;CYRILLIC SUBSCRIPT SMALL LETTER I;Lm;0;L;<sub> 0438;;;;N;;;;;
+1E05A;CYRILLIC SUBSCRIPT SMALL LETTER KA;Lm;0;L;<sub> 043A;;;;N;;;;;
+1E05B;CYRILLIC SUBSCRIPT SMALL LETTER EL;Lm;0;L;<sub> 043B;;;;N;;;;;
+1E05C;CYRILLIC SUBSCRIPT SMALL LETTER O;Lm;0;L;<sub> 043E;;;;N;;;;;
+1E05D;CYRILLIC SUBSCRIPT SMALL LETTER PE;Lm;0;L;<sub> 043F;;;;N;;;;;
+1E05E;CYRILLIC SUBSCRIPT SMALL LETTER ES;Lm;0;L;<sub> 0441;;;;N;;;;;
+1E05F;CYRILLIC SUBSCRIPT SMALL LETTER U;Lm;0;L;<sub> 0443;;;;N;;;;;
+1E060;CYRILLIC SUBSCRIPT SMALL LETTER EF;Lm;0;L;<sub> 0444;;;;N;;;;;
+1E061;CYRILLIC SUBSCRIPT SMALL LETTER HA;Lm;0;L;<sub> 0445;;;;N;;;;;
+1E062;CYRILLIC SUBSCRIPT SMALL LETTER TSE;Lm;0;L;<sub> 0446;;;;N;;;;;
+1E063;CYRILLIC SUBSCRIPT SMALL LETTER CHE;Lm;0;L;<sub> 0447;;;;N;;;;;
+1E064;CYRILLIC SUBSCRIPT SMALL LETTER SHA;Lm;0;L;<sub> 0448;;;;N;;;;;
+1E065;CYRILLIC SUBSCRIPT SMALL LETTER HARD SIGN;Lm;0;L;<sub> 044A;;;;N;;;;;
+1E066;CYRILLIC SUBSCRIPT SMALL LETTER YERU;Lm;0;L;<sub> 044B;;;;N;;;;;
+1E067;CYRILLIC SUBSCRIPT SMALL LETTER GHE WITH UPTURN;Lm;0;L;<sub> 0491;;;;N;;;;;
+1E068;CYRILLIC SUBSCRIPT SMALL LETTER BYELORUSSIAN-UKRAINIAN I;Lm;0;L;<sub> 0456;;;;N;;;;;
+1E069;CYRILLIC SUBSCRIPT SMALL LETTER DZE;Lm;0;L;<sub> 0455;;;;N;;;;;
+1E06A;CYRILLIC SUBSCRIPT SMALL LETTER DZHE;Lm;0;L;<sub> 045F;;;;N;;;;;
+1E06B;MODIFIER LETTER CYRILLIC SMALL ES WITH DESCENDER;Lm;0;L;<super> 04AB;;;;N;;;;;
+1E06C;MODIFIER LETTER CYRILLIC SMALL YERU WITH BACK YER;Lm;0;L;<super> A651;;;;N;;;;;
+1E06D;MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE;Lm;0;L;<super> 04B1;;;;N;;;;;
+1E08F;COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I;Mn;230;NSM;;;;;N;;;;;
1E100;NYIAKENG PUACHUE HMONG LETTER MA;Lo;0;L;;;;;N;;;;;
1E101;NYIAKENG PUACHUE HMONG LETTER TSA;Lo;0;L;;;;;N;;;;;
1E102;NYIAKENG PUACHUE HMONG LETTER NTA;Lo;0;L;;;;;N;;;;;
@@ -30603,6 +30828,48 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1E2F8;WANCHO DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;;
1E2F9;WANCHO DIGIT NINE;Nd;0;L;;9;9;9;N;;;;;
1E2FF;WANCHO NGUN SIGN;Sc;0;ET;;;;;N;;;;;
+1E4D0;NAG MUNDARI LETTER O;Lo;0;L;;;;;N;;;;;
+1E4D1;NAG MUNDARI LETTER OP;Lo;0;L;;;;;N;;;;;
+1E4D2;NAG MUNDARI LETTER OL;Lo;0;L;;;;;N;;;;;
+1E4D3;NAG MUNDARI LETTER OY;Lo;0;L;;;;;N;;;;;
+1E4D4;NAG MUNDARI LETTER ONG;Lo;0;L;;;;;N;;;;;
+1E4D5;NAG MUNDARI LETTER A;Lo;0;L;;;;;N;;;;;
+1E4D6;NAG MUNDARI LETTER AJ;Lo;0;L;;;;;N;;;;;
+1E4D7;NAG MUNDARI LETTER AB;Lo;0;L;;;;;N;;;;;
+1E4D8;NAG MUNDARI LETTER ANY;Lo;0;L;;;;;N;;;;;
+1E4D9;NAG MUNDARI LETTER AH;Lo;0;L;;;;;N;;;;;
+1E4DA;NAG MUNDARI LETTER I;Lo;0;L;;;;;N;;;;;
+1E4DB;NAG MUNDARI LETTER IS;Lo;0;L;;;;;N;;;;;
+1E4DC;NAG MUNDARI LETTER IDD;Lo;0;L;;;;;N;;;;;
+1E4DD;NAG MUNDARI LETTER IT;Lo;0;L;;;;;N;;;;;
+1E4DE;NAG MUNDARI LETTER IH;Lo;0;L;;;;;N;;;;;
+1E4DF;NAG MUNDARI LETTER U;Lo;0;L;;;;;N;;;;;
+1E4E0;NAG MUNDARI LETTER UC;Lo;0;L;;;;;N;;;;;
+1E4E1;NAG MUNDARI LETTER UD;Lo;0;L;;;;;N;;;;;
+1E4E2;NAG MUNDARI LETTER UK;Lo;0;L;;;;;N;;;;;
+1E4E3;NAG MUNDARI LETTER UR;Lo;0;L;;;;;N;;;;;
+1E4E4;NAG MUNDARI LETTER E;Lo;0;L;;;;;N;;;;;
+1E4E5;NAG MUNDARI LETTER ENN;Lo;0;L;;;;;N;;;;;
+1E4E6;NAG MUNDARI LETTER EG;Lo;0;L;;;;;N;;;;;
+1E4E7;NAG MUNDARI LETTER EM;Lo;0;L;;;;;N;;;;;
+1E4E8;NAG MUNDARI LETTER EN;Lo;0;L;;;;;N;;;;;
+1E4E9;NAG MUNDARI LETTER ETT;Lo;0;L;;;;;N;;;;;
+1E4EA;NAG MUNDARI LETTER ELL;Lo;0;L;;;;;N;;;;;
+1E4EB;NAG MUNDARI SIGN OJOD;Lm;0;L;;;;;N;;;;;
+1E4EC;NAG MUNDARI SIGN MUHOR;Mn;232;NSM;;;;;N;;;;;
+1E4ED;NAG MUNDARI SIGN TOYOR;Mn;232;NSM;;;;;N;;;;;
+1E4EE;NAG MUNDARI SIGN IKIR;Mn;220;NSM;;;;;N;;;;;
+1E4EF;NAG MUNDARI SIGN SUTUH;Mn;230;NSM;;;;;N;;;;;
+1E4F0;NAG MUNDARI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;;
+1E4F1;NAG MUNDARI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;;
+1E4F2;NAG MUNDARI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;;
+1E4F3;NAG MUNDARI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;;
+1E4F4;NAG MUNDARI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;;
+1E4F5;NAG MUNDARI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;;
+1E4F6;NAG MUNDARI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;;
+1E4F7;NAG MUNDARI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;;
+1E4F8;NAG MUNDARI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;;
+1E4F9;NAG MUNDARI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;;
1E7E0;ETHIOPIC SYLLABLE HHYA;Lo;0;L;;;;;N;;;;;
1E7E1;ETHIOPIC SYLLABLE HHYU;Lo;0;L;;;;;N;;;;;
1E7E2;ETHIOPIC SYLLABLE HHYI;Lo;0;L;;;;;N;;;;;
@@ -32678,6 +32945,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F6D5;HINDU TEMPLE;So;0;ON;;;;;N;;;;;
1F6D6;HUT;So;0;ON;;;;;N;;;;;
1F6D7;ELEVATOR;So;0;ON;;;;;N;;;;;
+1F6DC;WIRELESS;So;0;ON;;;;;N;;;;;
1F6DD;PLAYGROUND SLIDE;So;0;ON;;;;;N;;;;;
1F6DE;WHEEL;So;0;ON;;;;;N;;;;;
1F6DF;RING BUOY;So;0;ON;;;;;N;;;;;
@@ -32823,6 +33091,14 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F771;ALCHEMICAL SYMBOL FOR MONTH;So;0;ON;;;;;N;;;;;
1F772;ALCHEMICAL SYMBOL FOR HALF DRAM;So;0;ON;;;;;N;;;;;
1F773;ALCHEMICAL SYMBOL FOR HALF OUNCE;So;0;ON;;;;;N;;;;;
+1F774;LOT OF FORTUNE;So;0;ON;;;;;N;;;;;
+1F775;OCCULTATION;So;0;ON;;;;;N;;;;;
+1F776;LUNAR ECLIPSE;So;0;ON;;;;;N;;;;;
+1F77B;HAUMEA;So;0;ON;;;;;N;;;;;
+1F77C;MAKEMAKE;So;0;ON;;;;;N;;;;;
+1F77D;GONGGONG;So;0;ON;;;;;N;;;;;
+1F77E;QUAOAR;So;0;ON;;;;;N;;;;;
+1F77F;ORCUS;So;0;ON;;;;;N;;;;;
1F780;BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE;So;0;ON;;;;;N;;;;;
1F781;BLACK UP-POINTING ISOSCELES RIGHT TRIANGLE;So;0;ON;;;;;N;;;;;
1F782;BLACK RIGHT-POINTING ISOSCELES RIGHT TRIANGLE;So;0;ON;;;;;N;;;;;
@@ -32912,6 +33188,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F7D6;NEGATIVE CIRCLED TRIANGLE;So;0;ON;;;;;N;;;;;
1F7D7;CIRCLED SQUARE;So;0;ON;;;;;N;;;;;
1F7D8;NEGATIVE CIRCLED SQUARE;So;0;ON;;;;;N;;;;;
+1F7D9;NINE POINTED WHITE STAR;So;0;ON;;;;;N;;;;;
1F7E0;LARGE ORANGE CIRCLE;So;0;ON;;;;;N;;;;;
1F7E1;LARGE YELLOW CIRCLE;So;0;ON;;;;;N;;;;;
1F7E2;LARGE GREEN CIRCLE;So;0;ON;;;;;N;;;;;
@@ -33434,6 +33711,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1FA72;BRIEFS;So;0;ON;;;;;N;;;;;
1FA73;SHORTS;So;0;ON;;;;;N;;;;;
1FA74;THONG SANDAL;So;0;ON;;;;;N;;;;;
+1FA75;LIGHT BLUE HEART;So;0;ON;;;;;N;;;;;
+1FA76;GREY HEART;So;0;ON;;;;;N;;;;;
+1FA77;PINK HEART;So;0;ON;;;;;N;;;;;
1FA78;DROP OF BLOOD;So;0;ON;;;;;N;;;;;
1FA79;ADHESIVE BANDAGE;So;0;ON;;;;;N;;;;;
1FA7A;STETHOSCOPE;So;0;ON;;;;;N;;;;;
@@ -33446,6 +33726,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1FA84;MAGIC WAND;So;0;ON;;;;;N;;;;;
1FA85;PINATA;So;0;ON;;;;;N;;;;;
1FA86;NESTING DOLLS;So;0;ON;;;;;N;;;;;
+1FA87;MARACAS;So;0;ON;;;;;N;;;;;
+1FA88;FLUTE;So;0;ON;;;;;N;;;;;
1FA90;RINGED PLANET;So;0;ON;;;;;N;;;;;
1FA91;CHAIR;So;0;ON;;;;;N;;;;;
1FA92;RAZOR;So;0;ON;;;;;N;;;;;
@@ -33475,6 +33757,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1FAAA;IDENTIFICATION CARD;So;0;ON;;;;;N;;;;;
1FAAB;LOW BATTERY;So;0;ON;;;;;N;;;;;
1FAAC;HAMSA;So;0;ON;;;;;N;;;;;
+1FAAD;FOLDING HAND FAN;So;0;ON;;;;;N;;;;;
+1FAAE;HAIR PICK;So;0;ON;;;;;N;;;;;
+1FAAF;KHANDA;So;0;ON;;;;;N;;;;;
1FAB0;FLY;So;0;ON;;;;;N;;;;;
1FAB1;WORM;So;0;ON;;;;;N;;;;;
1FAB2;BEETLE;So;0;ON;;;;;N;;;;;
@@ -33486,12 +33771,18 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1FAB8;CORAL;So;0;ON;;;;;N;;;;;
1FAB9;EMPTY NEST;So;0;ON;;;;;N;;;;;
1FABA;NEST WITH EGGS;So;0;ON;;;;;N;;;;;
+1FABB;HYACINTH;So;0;ON;;;;;N;;;;;
+1FABC;JELLYFISH;So;0;ON;;;;;N;;;;;
+1FABD;WING;So;0;ON;;;;;N;;;;;
+1FABF;GOOSE;So;0;ON;;;;;N;;;;;
1FAC0;ANATOMICAL HEART;So;0;ON;;;;;N;;;;;
1FAC1;LUNGS;So;0;ON;;;;;N;;;;;
1FAC2;PEOPLE HUGGING;So;0;ON;;;;;N;;;;;
1FAC3;PREGNANT MAN;So;0;ON;;;;;N;;;;;
1FAC4;PREGNANT PERSON;So;0;ON;;;;;N;;;;;
1FAC5;PERSON WITH CROWN;So;0;ON;;;;;N;;;;;
+1FACE;MOOSE;So;0;ON;;;;;N;;;;;
+1FACF;DONKEY;So;0;ON;;;;;N;;;;;
1FAD0;BLUEBERRIES;So;0;ON;;;;;N;;;;;
1FAD1;BELL PEPPER;So;0;ON;;;;;N;;;;;
1FAD2;OLIVE;So;0;ON;;;;;N;;;;;
@@ -33502,6 +33793,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1FAD7;POURING LIQUID;So;0;ON;;;;;N;;;;;
1FAD8;BEANS;So;0;ON;;;;;N;;;;;
1FAD9;JAR;So;0;ON;;;;;N;;;;;
+1FADA;GINGER ROOT;So;0;ON;;;;;N;;;;;
+1FADB;PEA POD;So;0;ON;;;;;N;;;;;
1FAE0;MELTING FACE;So;0;ON;;;;;N;;;;;
1FAE1;SALUTING FACE;So;0;ON;;;;;N;;;;;
1FAE2;FACE WITH OPEN EYES AND HAND OVER MOUTH;So;0;ON;;;;;N;;;;;
@@ -33510,6 +33803,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1FAE5;DOTTED LINE FACE;So;0;ON;;;;;N;;;;;
1FAE6;BITING LIP;So;0;ON;;;;;N;;;;;
1FAE7;BUBBLES;So;0;ON;;;;;N;;;;;
+1FAE8;SHAKING FACE;So;0;ON;;;;;N;;;;;
1FAF0;HAND WITH INDEX FINGER AND THUMB CROSSED;So;0;ON;;;;;N;;;;;
1FAF1;RIGHTWARDS HAND;So;0;ON;;;;;N;;;;;
1FAF2;LEFTWARDS HAND;So;0;ON;;;;;N;;;;;
@@ -33517,6 +33811,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1FAF4;PALM UP HAND;So;0;ON;;;;;N;;;;;
1FAF5;INDEX POINTING AT THE VIEWER;So;0;ON;;;;;N;;;;;
1FAF6;HEART HANDS;So;0;ON;;;;;N;;;;;
+1FAF7;LEFTWARDS PUSHING HAND;So;0;ON;;;;;N;;;;;
+1FAF8;RIGHTWARDS PUSHING HAND;So;0;ON;;;;;N;;;;;
1FB00;BLOCK SEXTANT-1;So;0;ON;;;;;N;;;;;
1FB01;BLOCK SEXTANT-2;So;0;ON;;;;;N;;;;;
1FB02;BLOCK SEXTANT-12;So;0;ON;;;;;N;;;;;
@@ -33732,7 +34028,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
20000;<CJK Ideograph Extension B, First>;Lo;0;L;;;;;N;;;;;
2A6DF;<CJK Ideograph Extension B, Last>;Lo;0;L;;;;;N;;;;;
2A700;<CJK Ideograph Extension C, First>;Lo;0;L;;;;;N;;;;;
-2B738;<CJK Ideograph Extension C, Last>;Lo;0;L;;;;;N;;;;;
+2B739;<CJK Ideograph Extension C, Last>;Lo;0;L;;;;;N;;;;;
2B740;<CJK Ideograph Extension D, First>;Lo;0;L;;;;;N;;;;;
2B81D;<CJK Ideograph Extension D, Last>;Lo;0;L;;;;;N;;;;;
2B820;<CJK Ideograph Extension E, First>;Lo;0;L;;;;;N;;;;;
@@ -34283,6 +34579,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
2FA1D;CJK COMPATIBILITY IDEOGRAPH-2FA1D;Lo;0;L;2A600;;;;N;;;;;
30000;<CJK Ideograph Extension G, First>;Lo;0;L;;;;;N;;;;;
3134A;<CJK Ideograph Extension G, Last>;Lo;0;L;;;;;N;;;;;
+31350;<CJK Ideograph Extension H, First>;Lo;0;L;;;;;N;;;;;
+323AF;<CJK Ideograph Extension H, Last>;Lo;0;L;;;;;N;;;;;
E0001;LANGUAGE TAG;Cf;0;BN;;;;;N;;;;;
E0020;TAG SPACE;Cf;0;BN;;;;;N;;;;;
E0021;TAG EXCLAMATION MARK;Cf;0;BN;;;;;N;;;;;
diff --git a/lib/stdlib/uc_spec/emoji-data.txt b/lib/stdlib/uc_spec/emoji-data.txt
index 7806c7ab53..999a436779 100644
--- a/lib/stdlib/uc_spec/emoji-data.txt
+++ b/lib/stdlib/uc_spec/emoji-data.txt
@@ -1,13 +1,13 @@
-# emoji-data-14.0.0.txt
-# Date: 2021-08-26, 17:22:22 GMT
-# © 2021 Unicode®, Inc.
+# emoji-data.txt
+# Date: 2022-08-02, 00:26:10 GMT
+# © 2022 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
-# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For terms of use, see https://www.unicode.org/terms_of_use.html
#
# Emoji Data for UTS #51
-# Used with Emoji Version 14.0 and subsequent minor revisions (if any)
+# Used with Emoji Version 15.0 and subsequent minor revisions (if any)
#
-# For documentation and usage, see http://www.unicode.org/reports/tr51
+# For documentation and usage, see https://www.unicode.org/reports/tr51
#
# Format:
# <codepoint(s)> ; <property> # <comments>
@@ -19,8 +19,7 @@
# ================================================
-# All omitted code points have Emoji=No
-# @missing: 0000..10FFFF ; Emoji ; No
+# All omitted code points have Emoji=No
0023 ; Emoji # E0.0 [1] (#ï¸) hash sign
002A ; Emoji # E0.0 [1] (*ï¸) asterisk
@@ -341,6 +340,7 @@
1F6D1..1F6D2 ; Emoji # E3.0 [2] (🛑..🛒) stop sign..shopping cart
1F6D5 ; Emoji # E12.0 [1] (🛕) hindu temple
1F6D6..1F6D7 ; Emoji # E13.0 [2] (🛖..🛗) hut..elevator
+1F6DC ; Emoji # E15.0 [1] (🛜) wireless
1F6DD..1F6DF ; Emoji # E14.0 [3] (ðŸ›..🛟) playground slide..ring buoy
1F6E0..1F6E5 ; Emoji # E0.7 [6] (🛠ï¸..🛥ï¸) hammer and wrench..motor boat
1F6E9 ; Emoji # E0.7 [1] (🛩ï¸) small airplane
@@ -401,28 +401,36 @@
1F9E7..1F9FF ; Emoji # E11.0 [25] (🧧..🧿) red envelope..nazar amulet
1FA70..1FA73 ; Emoji # E12.0 [4] (🩰..🩳) ballet shoes..shorts
1FA74 ; Emoji # E13.0 [1] (🩴) thong sandal
+1FA75..1FA77 ; Emoji # E15.0 [3] (🩵..🩷) light blue heart..pink heart
1FA78..1FA7A ; Emoji # E12.0 [3] (🩸..🩺) drop of blood..stethoscope
1FA7B..1FA7C ; Emoji # E14.0 [2] (🩻..🩼) x-ray..crutch
1FA80..1FA82 ; Emoji # E12.0 [3] (🪀..🪂) yo-yo..parachute
1FA83..1FA86 ; Emoji # E13.0 [4] (🪃..🪆) boomerang..nesting dolls
+1FA87..1FA88 ; Emoji # E15.0 [2] (🪇..🪈) maracas..flute
1FA90..1FA95 ; Emoji # E12.0 [6] (ðŸª..🪕) ringed planet..banjo
1FA96..1FAA8 ; Emoji # E13.0 [19] (🪖..🪨) military helmet..rock
1FAA9..1FAAC ; Emoji # E14.0 [4] (🪩..🪬) mirror ball..hamsa
+1FAAD..1FAAF ; Emoji # E15.0 [3] (🪭..🪯) folding hand fan..khanda
1FAB0..1FAB6 ; Emoji # E13.0 [7] (🪰..🪶) fly..feather
1FAB7..1FABA ; Emoji # E14.0 [4] (🪷..🪺) lotus..nest with eggs
+1FABB..1FABD ; Emoji # E15.0 [3] (🪻..🪽) hyacinth..wing
+1FABF ; Emoji # E15.0 [1] (🪿) goose
1FAC0..1FAC2 ; Emoji # E13.0 [3] (🫀..🫂) anatomical heart..people hugging
1FAC3..1FAC5 ; Emoji # E14.0 [3] (🫃..🫅) pregnant man..person with crown
+1FACE..1FACF ; Emoji # E15.0 [2] (🫎..ðŸ«) moose..donkey
1FAD0..1FAD6 ; Emoji # E13.0 [7] (ðŸ«..🫖) blueberries..teapot
1FAD7..1FAD9 ; Emoji # E14.0 [3] (🫗..🫙) pouring liquid..jar
+1FADA..1FADB ; Emoji # E15.0 [2] (🫚..🫛) ginger root..pea pod
1FAE0..1FAE7 ; Emoji # E14.0 [8] (🫠..🫧) melting face..bubbles
+1FAE8 ; Emoji # E15.0 [1] (🫨) shaking face
1FAF0..1FAF6 ; Emoji # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands
+1FAF7..1FAF8 ; Emoji # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand
-# Total elements: 1404
+# Total elements: 1424
# ================================================
-# All omitted code points have Emoji_Presentation=No
-# @missing: 0000..10FFFF ; Emoji_Presentation ; No
+# All omitted code points have Emoji_Presentation=No
231A..231B ; Emoji_Presentation # E0.6 [2] (⌚..⌛) watch..hourglass done
23E9..23EC ; Emoji_Presentation # E0.6 [4] (â©..â¬) fast-forward button..fast down button
@@ -625,6 +633,7 @@
1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (🛑..🛒) stop sign..shopping cart
1F6D5 ; Emoji_Presentation # E12.0 [1] (🛕) hindu temple
1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (🛖..🛗) hut..elevator
+1F6DC ; Emoji_Presentation # E15.0 [1] (🛜) wireless
1F6DD..1F6DF ; Emoji_Presentation # E14.0 [3] (ðŸ›..🛟) playground slide..ring buoy
1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival
1F6F4..1F6F6 ; Emoji_Presentation # E3.0 [3] (🛴..🛶) kick scooter..canoe
@@ -681,28 +690,36 @@
1F9E7..1F9FF ; Emoji_Presentation # E11.0 [25] (🧧..🧿) red envelope..nazar amulet
1FA70..1FA73 ; Emoji_Presentation # E12.0 [4] (🩰..🩳) ballet shoes..shorts
1FA74 ; Emoji_Presentation # E13.0 [1] (🩴) thong sandal
+1FA75..1FA77 ; Emoji_Presentation # E15.0 [3] (🩵..🩷) light blue heart..pink heart
1FA78..1FA7A ; Emoji_Presentation # E12.0 [3] (🩸..🩺) drop of blood..stethoscope
1FA7B..1FA7C ; Emoji_Presentation # E14.0 [2] (🩻..🩼) x-ray..crutch
1FA80..1FA82 ; Emoji_Presentation # E12.0 [3] (🪀..🪂) yo-yo..parachute
1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (🪃..🪆) boomerang..nesting dolls
+1FA87..1FA88 ; Emoji_Presentation # E15.0 [2] (🪇..🪈) maracas..flute
1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (ðŸª..🪕) ringed planet..banjo
1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (🪖..🪨) military helmet..rock
1FAA9..1FAAC ; Emoji_Presentation # E14.0 [4] (🪩..🪬) mirror ball..hamsa
+1FAAD..1FAAF ; Emoji_Presentation # E15.0 [3] (🪭..🪯) folding hand fan..khanda
1FAB0..1FAB6 ; Emoji_Presentation # E13.0 [7] (🪰..🪶) fly..feather
1FAB7..1FABA ; Emoji_Presentation # E14.0 [4] (🪷..🪺) lotus..nest with eggs
+1FABB..1FABD ; Emoji_Presentation # E15.0 [3] (🪻..🪽) hyacinth..wing
+1FABF ; Emoji_Presentation # E15.0 [1] (🪿) goose
1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (🫀..🫂) anatomical heart..people hugging
1FAC3..1FAC5 ; Emoji_Presentation # E14.0 [3] (🫃..🫅) pregnant man..person with crown
+1FACE..1FACF ; Emoji_Presentation # E15.0 [2] (🫎..ðŸ«) moose..donkey
1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (ðŸ«..🫖) blueberries..teapot
1FAD7..1FAD9 ; Emoji_Presentation # E14.0 [3] (🫗..🫙) pouring liquid..jar
+1FADA..1FADB ; Emoji_Presentation # E15.0 [2] (🫚..🫛) ginger root..pea pod
1FAE0..1FAE7 ; Emoji_Presentation # E14.0 [8] (🫠..🫧) melting face..bubbles
+1FAE8 ; Emoji_Presentation # E15.0 [1] (🫨) shaking face
1FAF0..1FAF6 ; Emoji_Presentation # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands
+1FAF7..1FAF8 ; Emoji_Presentation # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand
-# Total elements: 1185
+# Total elements: 1205
# ================================================
-# All omitted code points have Emoji_Modifier=No
-# @missing: 0000..10FFFF ; Emoji_Modifier ; No
+# All omitted code points have Emoji_Modifier=No
1F3FB..1F3FF ; Emoji_Modifier # E1.0 [5] (ðŸ»..ðŸ¿) light skin tone..dark skin tone
@@ -710,8 +727,7 @@
# ================================================
-# All omitted code points have Emoji_Modifier_Base=No
-# @missing: 0000..10FFFF ; Emoji_Modifier_Base ; No
+# All omitted code points have Emoji_Modifier_Base=No
261D ; Emoji_Modifier_Base # E0.6 [1] (â˜ï¸) index pointing up
26F9 ; Emoji_Modifier_Base # E0.7 [1] (⛹ï¸) person bouncing ball
@@ -762,13 +778,13 @@
1F9D1..1F9DD ; Emoji_Modifier_Base # E5.0 [13] (🧑..ðŸ§) person..elf
1FAC3..1FAC5 ; Emoji_Modifier_Base # E14.0 [3] (🫃..🫅) pregnant man..person with crown
1FAF0..1FAF6 ; Emoji_Modifier_Base # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands
+1FAF7..1FAF8 ; Emoji_Modifier_Base # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand
-# Total elements: 132
+# Total elements: 134
# ================================================
-# All omitted code points have Emoji_Component=No
-# @missing: 0000..10FFFF ; Emoji_Component ; No
+# All omitted code points have Emoji_Component=No
0023 ; Emoji_Component # E0.0 [1] (#ï¸) hash sign
002A ; Emoji_Component # E0.0 [1] (*ï¸) asterisk
@@ -785,8 +801,7 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (ó € ..ó ¿) tag space..c
# ================================================
-# All omitted code points have Extended_Pictographic=No
-# @missing: 0000..10FFFF ; Extended_Pictographic ; No
+# All omitted code points have Extended_Pictographic=No
00A9 ; Extended_Pictographic# E0.6 [1] (©ï¸) copyright
00AE ; Extended_Pictographic# E0.6 [1] (®ï¸) registered
@@ -1190,7 +1205,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (ó € ..ó ¿) tag space..c
1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (🛓..🛔) STUPA..PAGODA
1F6D5 ; Extended_Pictographic# E12.0 [1] (🛕) hindu temple
1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (🛖..🛗) hut..elevator
-1F6D8..1F6DC ; Extended_Pictographic# E0.0 [5] (🛘..🛜) <reserved-1F6D8>..<reserved-1F6DC>
+1F6D8..1F6DB ; Extended_Pictographic# E0.0 [4] (🛘..🛛) <reserved-1F6D8>..<reserved-1F6DB>
+1F6DC ; Extended_Pictographic# E15.0 [1] (🛜) wireless
1F6DD..1F6DF ; Extended_Pictographic# E14.0 [3] (ðŸ›..🛟) playground slide..ring buoy
1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (🛠ï¸..🛥ï¸) hammer and wrench..motor boat
1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (🛦..🛨) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE
@@ -1207,7 +1223,7 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (ó € ..ó ¿) tag space..c
1F6FA ; Extended_Pictographic# E12.0 [1] (🛺) auto rickshaw
1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (🛻..🛼) pickup truck..roller skate
1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (🛽..🛿) <reserved-1F6FD>..<reserved-1F6FF>
-1F774..1F77F ; Extended_Pictographic# E0.0 [12] (ðŸ´..ðŸ¿) <reserved-1F774>..<reserved-1F77F>
+1F774..1F77F ; Extended_Pictographic# E0.0 [12] (ðŸ´..ðŸ¿) LOT OF FORTUNE..ORCUS
1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (🟕..🟟) CIRCLED TRIANGLE..<reserved-1F7DF>
1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (🟠..🟫) orange circle..brown square
1F7EC..1F7EF ; Extended_Pictographic# E0.0 [4] (🟬..🟯) <reserved-1F7EC>..<reserved-1F7EF>
@@ -1266,30 +1282,37 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (ó € ..ó ¿) tag space..c
1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (🨀..🩯) NEUTRAL CHESS KING..<reserved-1FA6F>
1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (🩰..🩳) ballet shoes..shorts
1FA74 ; Extended_Pictographic# E13.0 [1] (🩴) thong sandal
-1FA75..1FA77 ; Extended_Pictographic# E0.0 [3] (🩵..🩷) <reserved-1FA75>..<reserved-1FA77>
+1FA75..1FA77 ; Extended_Pictographic# E15.0 [3] (🩵..🩷) light blue heart..pink heart
1FA78..1FA7A ; Extended_Pictographic# E12.0 [3] (🩸..🩺) drop of blood..stethoscope
1FA7B..1FA7C ; Extended_Pictographic# E14.0 [2] (🩻..🩼) x-ray..crutch
1FA7D..1FA7F ; Extended_Pictographic# E0.0 [3] (🩽..🩿) <reserved-1FA7D>..<reserved-1FA7F>
1FA80..1FA82 ; Extended_Pictographic# E12.0 [3] (🪀..🪂) yo-yo..parachute
1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (🪃..🪆) boomerang..nesting dolls
-1FA87..1FA8F ; Extended_Pictographic# E0.0 [9] (🪇..ðŸª) <reserved-1FA87>..<reserved-1FA8F>
+1FA87..1FA88 ; Extended_Pictographic# E15.0 [2] (🪇..🪈) maracas..flute
+1FA89..1FA8F ; Extended_Pictographic# E0.0 [7] (🪉..ðŸª) <reserved-1FA89>..<reserved-1FA8F>
1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (ðŸª..🪕) ringed planet..banjo
1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (🪖..🪨) military helmet..rock
1FAA9..1FAAC ; Extended_Pictographic# E14.0 [4] (🪩..🪬) mirror ball..hamsa
-1FAAD..1FAAF ; Extended_Pictographic# E0.0 [3] (🪭..🪯) <reserved-1FAAD>..<reserved-1FAAF>
+1FAAD..1FAAF ; Extended_Pictographic# E15.0 [3] (🪭..🪯) folding hand fan..khanda
1FAB0..1FAB6 ; Extended_Pictographic# E13.0 [7] (🪰..🪶) fly..feather
1FAB7..1FABA ; Extended_Pictographic# E14.0 [4] (🪷..🪺) lotus..nest with eggs
-1FABB..1FABF ; Extended_Pictographic# E0.0 [5] (🪻..🪿) <reserved-1FABB>..<reserved-1FABF>
+1FABB..1FABD ; Extended_Pictographic# E15.0 [3] (🪻..🪽) hyacinth..wing
+1FABE ; Extended_Pictographic# E0.0 [1] (🪾) <reserved-1FABE>
+1FABF ; Extended_Pictographic# E15.0 [1] (🪿) goose
1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (🫀..🫂) anatomical heart..people hugging
1FAC3..1FAC5 ; Extended_Pictographic# E14.0 [3] (🫃..🫅) pregnant man..person with crown
-1FAC6..1FACF ; Extended_Pictographic# E0.0 [10] (🫆..ðŸ«) <reserved-1FAC6>..<reserved-1FACF>
+1FAC6..1FACD ; Extended_Pictographic# E0.0 [8] (🫆..ðŸ«) <reserved-1FAC6>..<reserved-1FACD>
+1FACE..1FACF ; Extended_Pictographic# E15.0 [2] (🫎..ðŸ«) moose..donkey
1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (ðŸ«..🫖) blueberries..teapot
1FAD7..1FAD9 ; Extended_Pictographic# E14.0 [3] (🫗..🫙) pouring liquid..jar
-1FADA..1FADF ; Extended_Pictographic# E0.0 [6] (🫚..🫟) <reserved-1FADA>..<reserved-1FADF>
+1FADA..1FADB ; Extended_Pictographic# E15.0 [2] (🫚..🫛) ginger root..pea pod
+1FADC..1FADF ; Extended_Pictographic# E0.0 [4] (🫜..🫟) <reserved-1FADC>..<reserved-1FADF>
1FAE0..1FAE7 ; Extended_Pictographic# E14.0 [8] (🫠..🫧) melting face..bubbles
-1FAE8..1FAEF ; Extended_Pictographic# E0.0 [8] (🫨..🫯) <reserved-1FAE8>..<reserved-1FAEF>
+1FAE8 ; Extended_Pictographic# E15.0 [1] (🫨) shaking face
+1FAE9..1FAEF ; Extended_Pictographic# E0.0 [7] (🫩..🫯) <reserved-1FAE9>..<reserved-1FAEF>
1FAF0..1FAF6 ; Extended_Pictographic# E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands
-1FAF7..1FAFF ; Extended_Pictographic# E0.0 [9] (🫷..🫿) <reserved-1FAF7>..<reserved-1FAFF>
+1FAF7..1FAF8 ; Extended_Pictographic# E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand
+1FAF9..1FAFF ; Extended_Pictographic# E0.0 [7] (🫹..🫿) <reserved-1FAF9>..<reserved-1FAFF>
1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (🰀..🿽) <reserved-1FC00>..<reserved-1FFFD>
# Total elements: 3537
diff --git a/lib/stdlib/uc_spec/gen_unicode_mod.escript b/lib/stdlib/uc_spec/gen_unicode_mod.escript
index ecc30b40c6..a6ac3b0e60 100644
--- a/lib/stdlib/uc_spec/gen_unicode_mod.escript
+++ b/lib/stdlib/uc_spec/gen_unicode_mod.escript
@@ -23,55 +23,69 @@
-mode(compile).
--record(cp, {name, class, dec, comp, cs}).
+-record(cp, {name, class, dec, comp, cs, cat}).
-define(MOD, "unicode_util").
-main(_) ->
+main(Args) ->
%% Parse main table
- {ok, UD} = file:open("../uc_spec/UnicodeData.txt", [read, raw, {read_ahead, 1000000}]),
+ UD = file_open("../uc_spec/UnicodeData.txt"),
Data0 = foldl(fun parse_unicode_data/2, [], UD),
Data1 = array:from_orddict(lists:reverse(Data0)),
ok = file:close(UD),
%% Special Casing table
- {ok, SC} = file:open("../uc_spec/SpecialCasing.txt", [read, raw, {read_ahead, 1000000}]),
+ SC = file_open("../uc_spec/SpecialCasing.txt"),
Data2 = foldl(fun parse_special_casing/2, Data1, SC),
ok = file:close(SC),
%% Casing Folding table
- {ok, CF} = file:open("../uc_spec/CaseFolding.txt", [read, raw, {read_ahead, 1000000}]),
+ CF = file_open("../uc_spec/CaseFolding.txt"),
Data = foldl(fun parse_case_folding/2, Data2, CF),
ok = file:close(CF),
%% Normalization
- {ok, ExclF} = file:open("../uc_spec/CompositionExclusions.txt", [read, raw, {read_ahead, 1000000}]),
+ ExclF = file_open("../uc_spec/CompositionExclusions.txt"),
ExclData = foldl(fun parse_comp_excl/2, Data, ExclF),
ok = file:close(ExclF),
%% GraphemeBreakProperty table
- {ok, Emoji} = file:open("../uc_spec/emoji-data.txt", [read, raw, {read_ahead, 1000000}]),
+ Emoji = file_open("../uc_spec/emoji-data.txt"),
Props00 = foldl(fun parse_properties/2, [], Emoji),
%% Filter Extended_Pictographic class which we are interested in.
Props0 = [EP || {extended_pictographic, _} = EP <- Props00],
ok = file:close(Emoji),
- {ok, GBPF} = file:open("../uc_spec/GraphemeBreakProperty.txt", [read, raw, {read_ahead, 1000000}]),
+ GBPF = file_open("../uc_spec/GraphemeBreakProperty.txt"),
Props1 = foldl(fun parse_properties/2, Props0, GBPF),
ok = file:close(GBPF),
- {ok, PropF} = file:open("../uc_spec/PropList.txt", [read, raw, {read_ahead, 1000000}]),
+ PropF = file_open("../uc_spec/PropList.txt"),
Props2 = foldl(fun parse_properties/2, Props1, PropF),
ok = file:close(PropF),
Props = sofs:to_external(sofs:relation_to_family(sofs:relation(Props2))),
+ WidthF = file_open("../uc_spec/EastAsianWidth.txt"),
+ WideCs = foldl(fun parse_widths/2, [], WidthF),
+ ok = file:close(WidthF),
+
%% Make module
+ UpdateTests = case Args of
+ ["update_tests"] -> true;
+ _ -> false
+ end,
+
{ok, Out} = file:open(?MOD++".erl", [write]),
- gen_file(Out, Data, ExclData, maps:from_list(Props)),
+ gen_file(Out, Data, ExclData, maps:from_list(Props), WideCs, UpdateTests),
ok = file:close(Out),
ok.
+file_open(File) ->
+ {ok, Fd} = file:open(File, [read, raw, {read_ahead, 1000000}]),
+ Fd.
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
parse_unicode_data(Line0, Acc) ->
Line = string:chomp(Line0),
- [CodePoint,Name,_Cat,Class,_BiDi,Decomp,
+ [CodePoint,Name,Cat,Class,_BiDi,Decomp,
_N1,_N2,_N3,_BDMirror,_Uni1,_Iso|Case] = tokens(Line, ";"),
{Dec,Comp} = case to_decomp(Decomp) of
{_, _} = Compabil -> {[], Compabil};
@@ -79,7 +93,7 @@ parse_unicode_data(Line0, Acc) ->
end,
[{hex_to_int(CodePoint),
#cp{name=list_to_binary(Name),class=to_class(Class),
- dec=Dec, comp=Comp, cs=to_case(Case)}}
+ dec=Dec, comp=Comp, cs=to_case(Case), cat=Cat}}
|Acc].
to_class(String) ->
@@ -152,7 +166,62 @@ parse_properties(Line0, Acc) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-gen_file(Fd, Data, ExclData, Props) ->
+%% Pick ranges that are wide when seen from a non East Asian context,
+%% That way we can minimize the data, every other code point is considered narrow.
+%% We loose information but hopefully keep the important width for a standard
+%% terminal.
+parse_widths(Line0, Acc) ->
+ [{WidthClass, {From, _To}=Range}] = parse_properties(Line0, []),
+ case is_default_width(From, WidthClass) of
+ {true, narrow} ->
+ Acc;
+ {false, narrow} ->
+ [Range|Acc];
+ {true, RuleRange} ->
+ [RuleRange|Acc]
+%%% {false, rule_execption} -> i.e. narrow codepoint in wide range
+%%% Should not happen in current specs
+ end.
+
+is_default_width(Index, WD) ->
+ if
+ 16#3400 =< Index, Index =< 16#4DBF ->
+ if WD =:= w orelse WD =:= f ->
+ {true, {16#3400, 16#4DBF}};
+ true ->
+ {false, rule_execption}
+ end;
+ 16#4E00 =< Index, Index =< 16#9FFF ->
+ if WD =:= w orelse WD =:= f ->
+ {true, {16#4E00, 16#9FFF}};
+ true ->
+ {false, rule_execption}
+ end;
+ 16#F900 =< Index, Index =< 16#FAFF ->
+ if WD =:= w orelse WD =:= f ->
+ {true, {16#F900, 16#FAFF}};
+ true ->
+ {false, rule_execption}
+ end;
+ 16#20000 =< Index, Index =< 16#2FFFD ->
+ if WD =:= w orelse WD =:= f ->
+ {true, {16#20000, 16#2FFFD}};
+ true ->
+ {false, rule_execption}
+ end;
+ 16#30000 =< Index, Index =< 16#3FFFD ->
+ if WD =:= w orelse WD =:= f ->
+ {true, {16#30000, 16#3FFFD}};
+ true ->
+ {false, rule_execption}
+ end;
+ true ->
+ {WD =:= n orelse WD =:= na orelse WD == h orelse WD =:= a, narrow}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+gen_file(Fd, Data, ExclData, Props, WideCs, UpdateTests) ->
gen_header(Fd),
gen_static(Fd),
gen_norm(Fd),
@@ -161,7 +230,8 @@ gen_file(Fd, Data, ExclData, Props) ->
gen_gc(Fd, Props),
gen_compose_pairs(Fd, ExclData, Data),
gen_case_table(Fd, Data),
- gen_unicode_table(Fd, Data),
+ gen_unicode_table(Fd, Data, UpdateTests),
+ gen_width_table(Fd, WideCs),
ok.
gen_header(Fd) ->
@@ -173,26 +243,32 @@ gen_header(Fd) ->
io:put_chars(Fd, "-export([whitespace/0, is_whitespace/1]).\n"),
io:put_chars(Fd, "-export([uppercase/1, lowercase/1, titlecase/1, casefold/1]).\n\n"),
io:put_chars(Fd, "-export([spec_version/0, lookup/1, get_case/1]).\n"),
+ io:put_chars(Fd, "-export([is_wide/1]).\n"),
io:put_chars(Fd, "-compile({inline, [class/1]}).\n"),
io:put_chars(Fd, "-compile(nowarn_unused_vars).\n"),
io:put_chars(Fd, "-dialyzer({no_improper_lists, [cp/1, gc/1, gc_prepend/2]}).\n"),
- io:put_chars(Fd, "-type gc() :: char()|[char()].\n\n\n"),
+ io:put_chars(Fd, "-type gc() :: char()|[char()].\n\n"),
+ io:put_chars(Fd, "-define(IS_CP(CP), (is_integer(CP) andalso 0 =< CP andalso CP < 16#110000)).\n\n\n"),
ok.
gen_static(Fd) ->
io:put_chars(Fd, "-spec lookup(char()) -> #{'canon':=[{byte(),char()}], 'ccc':=byte(), "
- "'compat':=[] | {atom(),[{byte(),char()}]}}.\n"),
- io:put_chars(Fd, "lookup(Codepoint) ->\n"
- " {CCC,Can,Comp} = unicode_table(Codepoint),\n"
- " #{ccc=>CCC, canon=>Can, compat=>Comp}.\n\n"),
+ "'compat':=[] | {atom(),[{byte(),char()}]}, 'category':={atom(),atom()}}.\n"),
+ io:put_chars(Fd, "lookup(Codepoint) when ?IS_CP(Codepoint) ->\n"
+ " {CCC,Can,Comp,Cat} = unicode_table(Codepoint),\n"
+ " #{ccc=>CCC, canon=>Can, compat=>Comp, category=>category(Codepoint,Cat)}.\n\n"),
+
io:put_chars(Fd, "-spec get_case(char()) -> #{'fold':=gc(), 'lower':=gc(), 'title':=gc(), 'upper':=gc()}.\n"),
- io:put_chars(Fd, "get_case(Codepoint) ->\n"
+ io:put_chars(Fd, "get_case(Codepoint) when ?IS_CP(Codepoint) ->\n"
" case case_table(Codepoint) of\n"
" {U,L} -> #{upper=>U,lower=>L,title=>U,fold=>L};\n"
" {U,L,T,F} -> #{upper=>U,lower=>L,title=>T,fold=>F}\n"
" end.\n\n"),
- io:put_chars(Fd, "spec_version() -> {14,0}.\n\n\n"),
- io:put_chars(Fd, "class(Codepoint) -> {CCC,_,_} = unicode_table(Codepoint),\n CCC.\n\n"),
+
+ io:put_chars(Fd, "spec_version() -> {15,0}.\n\n\n"),
+ io:put_chars(Fd, "class(Codepoint) when ?IS_CP(Codepoint) -> \n"
+ " {CCC,_,_,_} = unicode_table(Codepoint),\n CCC.\n\n"),
+
io:put_chars(Fd, "-spec uppercase(unicode:chardata()) -> "
"maybe_improper_list(gc(),unicode:chardata()).\n"),
io:put_chars(Fd, "uppercase(Str0) ->\n"),
@@ -217,6 +293,7 @@ gen_static(Fd) ->
io:put_chars(Fd, " [] -> [];\n"),
io:put_chars(Fd, " {error,Err} -> error({badarg, Err})\n"),
io:put_chars(Fd, " end.\n\n"),
+
io:put_chars(Fd, "-spec titlecase(unicode:chardata()) -> "
"maybe_improper_list(gc(),unicode:chardata()).\n"),
io:put_chars(Fd, "titlecase(Str0) ->\n"),
@@ -229,6 +306,7 @@ gen_static(Fd) ->
io:put_chars(Fd, " [] -> [];\n"),
io:put_chars(Fd, " {error,Err} -> error({badarg, Err})\n"),
io:put_chars(Fd, " end.\n\n"),
+
io:put_chars(Fd, "-spec casefold(unicode:chardata()) -> "
"maybe_improper_list(gc(),unicode:chardata()).\n"),
io:put_chars(Fd, "casefold(Str0) ->\n"),
@@ -242,6 +320,19 @@ gen_static(Fd) ->
io:put_chars(Fd, " {error,Err} -> error({badarg, Err})\n"),
io:put_chars(Fd, " end.\n\n"),
+ io:put_chars(Fd, "%% Returns true if the character is considered wide in non east asian context.\n"),
+ io:put_chars(Fd, "-spec is_wide(gc()) -> boolean().\n"),
+ io:put_chars(Fd, "is_wide(C) when ?IS_CP(C) ->\n"),
+ io:put_chars(Fd, " is_wide_cp(C);\n"),
+ io:put_chars(Fd, "is_wide([_, 16#FE0E|Cs]) -> true; %% Presentation sequence\n"),
+ io:put_chars(Fd, "is_wide([_, 16#FE0F|Cs]) -> true; %% Presentation sequence\n"),
+ io:put_chars(Fd, "is_wide([C|Cs]) when ?IS_CP(C) ->\n"),
+ io:put_chars(Fd, " is_wide_cp(C) orelse is_wide(Cs);\n"),
+ io:put_chars(Fd, "is_wide([]) ->\n false.\n\n"),
+
+ io:put_chars(Fd, "category(CP, lookup_category) ->\n"
+ " cat_translate(lookup_category(CP));\n"
+ "category(_, Def) -> cat_translate(Def).\n\n"),
ok.
gen_norm(Fd) ->
@@ -249,7 +340,7 @@ gen_norm(Fd) ->
"-spec nfd(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()) | {error, unicode:chardata()}.\n"
"nfd(Str0) ->\n"
" case gc(Str0) of\n"
- " [GC|R] when GC < 128 -> [GC|R];\n"
+ " [GC|R] when is_integer(GC), 0 =< GC, GC < 128 -> [GC|R];\n"
" [GC|Str] -> [decompose(GC)|Str];\n"
" [] -> [];\n"
" {error,_}=Error -> Error\n end.\n\n"
@@ -259,7 +350,7 @@ gen_norm(Fd) ->
"-spec nfkd(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()) | {error, unicode:chardata()}.\n"
"nfkd(Str0) ->\n"
" case gc(Str0) of\n"
- " [GC|R] when GC < 128 -> [GC|R];\n"
+ " [GC|R] when is_integer(GC), 0 =< GC, GC < 128 -> [GC|R];\n"
" [GC|Str] -> [decompose_compat(GC)|Str];\n"
" [] -> [];\n"
" {error,_}=Error -> Error\n end.\n\n"
@@ -269,7 +360,7 @@ gen_norm(Fd) ->
"-spec nfc(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()) | {error, unicode:chardata()}.\n"
"nfc(Str0) ->\n"
" case gc(Str0) of\n"
- " [GC|R] when GC < 256 -> [GC|R];\n"
+ " [GC|R] when is_integer(GC), 0 =< GC, GC < 256 -> [GC|R];\n"
" [GC|Str] -> [compose(decompose(GC))|Str];\n"
" [] -> [];\n"
" {error,_}=Error -> Error\n end.\n\n"
@@ -279,7 +370,7 @@ gen_norm(Fd) ->
"-spec nfkc(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()) | {error, unicode:chardata()}.\n"
"nfkc(Str0) ->\n"
" case gc(Str0) of\n"
- " [GC|R] when GC < 128 -> [GC|R];\n"
+ " [GC|R] when is_integer(GC), 0 =< GC, GC < 128 -> [GC|R];\n"
" [GC|Str] -> [compose_compat_0(decompose_compat(GC))|Str];\n"
" [] -> [];\n"
" {error,_}=Error -> Error\n end.\n\n"
@@ -288,13 +379,13 @@ gen_norm(Fd) ->
io:put_chars(Fd,
"decompose(CP) when is_integer(CP), CP < 16#AC00, 16#D7A3 > CP ->\n"
" case unicode_table(CP) of\n"
- " {_,[],_} -> CP;\n"
- " {_,CPs,_} -> canonical_order(CPs)\n"
+ " {_,[],_,_} -> CP;\n"
+ " {_,CPs,_,_} -> canonical_order(CPs)\n"
" end;\n"
"decompose(CP) ->\n"
" canonical_order(decompose_1(CP)).\n"
"\n"
- "decompose_1(CP) when 16#AC00 =< CP, CP =< 16#D7A3 ->\n"
+ "decompose_1(CP) when is_integer(CP), 16#AC00 =< CP, CP =< 16#D7A3 ->\n"
" Syll = CP-16#AC00,\n"
" T = 28,\n"
" N = 588,\n"
@@ -306,8 +397,8 @@ gen_norm(Fd) ->
" end;\n"
"decompose_1(CP) when is_integer(CP) ->\n"
" case unicode_table(CP) of\n"
- " {CCC, [],_} -> [{CCC,CP}];\n"
- " {_, CPs, _} -> CPs\n"
+ " {CCC, [],_,_} -> [{CCC,CP}];\n"
+ " {_,CPs,_,_} -> CPs\n"
" end;\n"
"decompose_1([CP|CPs]) ->\n"
" decompose_1(CP) ++ decompose_1(CPs);\n"
@@ -331,14 +422,14 @@ gen_norm(Fd) ->
io:put_chars(Fd,
"decompose_compat(CP) when is_integer(CP), CP < 16#AC00, 16#D7A3 > CP ->\n"
" case unicode_table(CP) of\n"
- " {_, [], []} -> CP;\n"
- " {_, _, {_,CPs}} -> canonical_order(CPs);\n"
- " {_, CPs, _} -> canonical_order(CPs)\n"
+ " {_, [], [], _} -> CP;\n"
+ " {_, _, {_,CPs}, _} -> canonical_order(CPs);\n"
+ " {_, CPs, _, _} -> canonical_order(CPs)\n"
" end;\n"
"decompose_compat(CP) ->\n"
" canonical_order(decompose_compat_1(CP)).\n"
"\n"
- "decompose_compat_1(CP) when 16#AC00 =< CP, CP =< 16#D7A3 ->\n"
+ "decompose_compat_1(CP) when is_integer(CP), 16#AC00 =< CP, CP =< 16#D7A3 ->\n"
" Syll = CP-16#AC00,\n"
" T = 28,\n"
" N = 588,\n"
@@ -350,23 +441,24 @@ gen_norm(Fd) ->
" end;\n"
"decompose_compat_1(CP) when is_integer(CP) ->\n"
" case unicode_table(CP) of\n"
- " {CCC, [], []} -> [{CCC,CP}];\n"
- " {_, _, {_,CPs}} -> CPs;\n"
- " {_, CPs, _} -> CPs\n"
+ " {CCC, [], [], _} -> [{CCC,CP}];\n"
+ " {_, _, {_,CPs}, _} -> CPs;\n"
+ " {_, CPs, _, _} -> CPs\n"
" end;\n"
"decompose_compat_1([CP|CPs]) ->\n"
" decompose_compat_1(CP) ++ decompose_compat_1(CPs);\n"
- "decompose_compat_1([]) -> [].\n"),
+ "decompose_compat_1([]) -> [].\n\n"),
io:put_chars(Fd,
"compose(CP) when is_integer(CP) -> CP;\n"
"compose([Lead,Vowel|Trail]) %% Hangul\n"
- " when 16#1100 =< Lead, Lead =< 16#1112 ->\n"
+ " when is_integer(Lead), 16#1100 =< Lead, Lead =< 16#1112, is_integer(Vowel) ->\n"
" if 16#1161 =< Vowel, Vowel =< 16#1175 ->\n"
" CP = 16#AC00 + ((Lead - 16#1100) * 588) + ((Vowel - 16#1161) * 28),\n"
" case Trail of\n"
- " [T|Acc] when 16#11A7 =< T, T =< 16#11C2 -> nolist(CP+T-16#11A7,Acc);\n"
+ " [T|Acc] when is_integer(T), 16#11A7 =< T, T =< 16#11C2 ->"
+ " nolist(CP+T-16#11A7,Acc);\n"
" Acc -> nolist(CP,Acc)\n"
" end;\n"
" true ->\n"
@@ -408,11 +500,12 @@ gen_norm(Fd) ->
" end.\n\n"
"compose_compat(CP) when is_integer(CP) -> CP;\n"
"compose_compat([Lead,Vowel|Trail]) %% Hangul\n"
- " when 16#1100 =< Lead, Lead =< 16#1112 ->\n"
+ " when is_integer(Lead), 16#1100 =< Lead, Lead =< 16#1112, is_integer(Vowel) ->\n"
" if 16#1161 =< Vowel, Vowel =< 16#1175 ->\n"
" CP = 16#AC00 + ((Lead - 16#1100) * 588) + ((Vowel - 16#1161) * 28),\n"
" case Trail of\n"
- " [T|Acc] when 16#11A7 =< T, T =< 16#11C2 -> nolist(CP+T-16#11A7,Acc);\n"
+ " [T|Acc] when is_integer(T), 16#11A7 =< T, T =< 16#11C2 ->"
+ " nolist(CP+T-16#11A7,Acc);\n"
" Acc -> nolist(CP,Acc)\n"
" end;\n"
" true ->\n"
@@ -462,7 +555,7 @@ gen_ws(Fd, Props) ->
gen_cp(Fd) ->
io:put_chars(Fd, "-spec cp(String::unicode:chardata()) ->"
" maybe_improper_list() | {error, unicode:chardata()}.\n"),
- io:put_chars(Fd, "cp([C|_]=L) when is_integer(C) -> L;\n"),
+ io:put_chars(Fd, "cp([C|_]=L) when ?IS_CP(C) -> L;\n"),
io:put_chars(Fd, "cp([List]) -> cp(List);\n"),
io:put_chars(Fd, "cp([List|R]) -> cpl(List, R);\n"),
io:put_chars(Fd, "cp([]) -> [];\n"),
@@ -470,8 +563,8 @@ gen_cp(Fd) ->
io:put_chars(Fd, "cp(<<>>) -> [];\n"),
io:put_chars(Fd, "cp(<<R/binary>>) -> {error,R}.\n"),
io:put_chars(Fd, "\n"),
- io:put_chars(Fd, "cpl([C], R) when is_integer(C) -> [C|cpl_1_cont(R)];\n"),
- io:put_chars(Fd, "cpl([C|T], R) when is_integer(C) -> [C|cpl_cont(T, R)];\n"),
+ io:put_chars(Fd, "cpl([C], R) when ?IS_CP(C) -> [C|cpl_1_cont(R)];\n"),
+ io:put_chars(Fd, "cpl([C|T], R) when ?IS_CP(C) -> [C|cpl_cont(T, R)];\n"),
io:put_chars(Fd, "cpl([List], R) -> cpl(List, R);\n"),
io:put_chars(Fd, "cpl([List|T], R) -> cpl(List, [T|R]);\n"),
io:put_chars(Fd, "cpl([], R) -> cp(R);\n"),
@@ -542,18 +635,18 @@ gen_gc(Fd, GBP) ->
" maybe_improper_list() | {error, unicode:chardata()}.\n"),
io:put_chars(Fd,
"gc([]=R) -> R;\n"
- "gc([CP]=R) when is_integer(CP) -> R;\n"
+ "gc([CP]=R) when ?IS_CP(CP) -> R;\n"
"gc([$\\r=CP|R0]) ->\n"
" case cp(R0) of % Don't break CRLF\n"
" [$\\n|R1] -> [[$\\r,$\\n]|R1];\n"
" T -> [CP|T]\n"
" end;\n"
- "gc([CP1|T1]=T) when CP1 < 256 ->\n"
+ "gc([CP1|T1]=T) when ?IS_CP(CP1), CP1 < 256 ->\n"
" case T1 of\n"
- " [CP2|_] when CP2 < 256 -> T; %% Ascii Fast path\n"
+ " [CP2|_] when is_integer(CP2), 0 =< CP2, CP2 < 256 -> T; %% Ascii Fast path\n"
" _ -> %% Keep the tail binary.\n"
" case cp_no_bin(T1) of\n"
- " [CP2|_]=T3 when CP2 < 256 -> [CP1|T3]; %% Asciii Fast path\n"
+ " [CP2|_]=T3 when is_integer(CP2), 0 =< CP2, CP2 < 256 -> [CP1|T3]; %% Asciii Fast path\n"
" binary_found -> gc_1(T);\n"
" T4 -> gc_1([CP1|T4])\n"
" end\n"
@@ -568,7 +661,7 @@ gen_gc(Fd, GBP) ->
" end;\n"
" true -> gc_1([CP1|Rest])\n"
" end;\n"
- "gc([CP|_]=T) when is_integer(CP) -> gc_1(T);\n"
+ "gc([CP|_]=T) when ?IS_CP(CP) -> gc_1(T);\n"
"gc(Str) ->\n"
" case cp(Str) of\n"
" {error,_}=Error -> Error;\n"
@@ -596,13 +689,14 @@ gen_gc(Fd, GBP) ->
io:put_chars(Fd, "\n%% Optimize Latin-1\n"),
[GenExtP(CP) || CP <- merge_ranges(ExtendedPictographicLow)],
- io:format(Fd,
- "gc_1([CP|R]=R0) when CP < 256 ->\n"
- " case R of\n"
- " [CP2|_] when CP2 < 256 -> R0;\n"
- " _ -> gc_extend(cp(R), R, CP)\n"
- " end;\n",
- []),
+ io:put_chars(Fd,
+ "gc_1([CP|R]=R0) when is_integer(CP), 0 =< CP, CP < 256 ->\n"
+ " case R of\n"
+ " [CP2|_] when is_integer(CP2), 0 =< CP2, CP2 < 256 -> R0;\n"
+ " _ -> gc_extend(cp(R), R, CP)\n"
+ " end;\n"
+ "gc_1([CP|_]) when not ?IS_CP(CP) ->\n"
+ " error({badarg,CP});\n"),
io:put_chars(Fd, "\n%% Continue control\n"),
[GenControl(CP) || CP <- Crs],
%% One clause per CP
@@ -623,7 +717,7 @@ gen_gc(Fd, GBP) ->
GenHangulT = fun(Range) -> io:format(Fd, "gc_1~s gc_h_T(R1,[CP]);\n", [gen_clause(Range)]) end,
[GenHangulT(CP) || CP <- merge_ranges(maps:get(t,GBP))],
io:put_chars(Fd, "%% Handle Hangul LV and LVT special, since they are large\n"),
- io:put_chars(Fd, "gc_1([CP|_]=R0) when 44000 < CP, CP < 56000 -> gc_h_lv_lvt(R0, R0, []);\n"),
+ io:put_chars(Fd, "gc_1([CP|_]=R0) when is_integer(CP), 44000 < CP, CP < 56000 -> gc_h_lv_lvt(R0, R0, []);\n"),
io:put_chars(Fd, "\n%% Handle Regional\n"),
GenRegional = fun(Range) -> io:format(Fd, "gc_1~s gc_regional(R1,CP);\n", [gen_clause(Range)]) end,
@@ -739,7 +833,9 @@ gen_gc(Fd, GBP) ->
[{RLess,RLarge}] = merge_ranges(maps:get(regional_indicator,GBP)),
io:put_chars(Fd,"gc_regional(R0, CP0) ->\n"
" case cp(R0) of\n"),
- io:format(Fd, " [CP|R1] when ~w =< CP,CP =< ~w-> gc_extend2(cp(R1),R1,[CP,CP0]);~n",[RLess, RLarge]),
+ io:format(Fd, " [CP|R1] when is_integer(CP), ~w =< CP, CP =< ~w ->\n"
+ " gc_extend2(cp(R1),R1,[CP,CP0]);~n",
+ [RLess, RLarge]),
io:put_chars(Fd," R1 -> gc_extend(R1, R0, CP0)\n"
" end.\n\n"),
@@ -780,6 +876,7 @@ gen_gc(Fd, GBP) ->
" _ -> gc_extend2(R1, R0, Acc)\n"
" end\n end.\n\n"),
io:put_chars(Fd, "%% Handle Hangul LV\n"),
+ io:put_chars(Fd, "gc_h_lv_lvt([CP|_], _R0, _Acc) when not ?IS_CP(CP) -> error(badarg);\n"),
GenHangulLV = fun(Range) -> io:format(Fd, "gc_h_lv_lvt~s gc_h_V(R1,[CP|Acc]);\n",
[gen_clause2(Range)]) end,
[GenHangulLV(CP) || CP <- merge_ranges(maps:get(lv,GBP))],
@@ -806,50 +903,241 @@ gen_compose_pairs(Fd, ExclData, Data) ->
[io:format(Fd, "compose_pair(~w,~w) -> ~w;~n", [A,B,CP]) || {[A,B],CP} <- lists:sort(DeCmp2)],
io:put_chars(Fd, "compose_pair(_,_) -> false.\n\n"),
- io:put_chars(Fd, "nolist(CP, []) -> CP;\nnolist(CP,L) -> [CP|L].\n\n"),
+ io:put_chars(Fd, "nolist(CP, []) when ?IS_CP(CP) -> CP;\n"
+ "nolist(CP, L) when ?IS_CP(CP) -> [CP|L].\n\n"),
ok.
gen_case_table(Fd, Data) ->
- Case = array:foldr(fun(CP, #cp{cs={U0,L0,T0,F0}}, Acc) ->
- U = def_cp(U0,CP),
- L = def_cp(L0,CP),
- T = def_cp(T0,CP),
- F = def_cp(F0,CP),
- case T =:= U andalso F =:= L of
- true ->
- [{CP,{U,L}}|Acc];
- false ->
- [{CP,{U,L,T,F}}|Acc]
- end;
- (_CP, _, Acc) -> Acc
- end, [], Data),
+ HC = fun(CP, #cp{cs=Cs}, Acc) ->
+ case case_data(CP, Cs) of
+ default -> Acc;
+ CaseData -> [{CP,CaseData}|Acc]
+ end
+ end,
+ Case = array:sparse_foldr(HC, [], Data),
[io:format(Fd, "case_table(~w) -> ~w;\n", [CP, Map])|| {CP,Map} <- Case],
io:format(Fd, "case_table(CP) -> {CP, CP}.\n\n",[]),
ok.
+case_data(CP, {U0,L0,T0,F0}) ->
+ U = def_cp(U0,CP),
+ L = def_cp(L0,CP),
+ T = def_cp(T0,CP),
+ F = def_cp(F0,CP),
+ case T =:= U andalso F =:= L of
+ true -> {U,L};
+ false -> {U,L,T,F}
+ end;
+case_data(_, _) ->
+ default.
+
def_cp([], CP) -> CP;
def_cp(CP, _) -> CP.
-gen_unicode_table(Fd, Data) ->
- FixCanon = fun(_, #cp{class=CCC, dec=Dec, comp=Comp}) ->
+gen_unicode_table(Fd, Data, UpdateTests) ->
+ FixCanon = fun(_, #cp{class=CCC, dec=Dec, comp=Comp, cat=Cat}) ->
Canon = decompose(Dec,Data),
- #{ccc=>CCC, canonical=>Canon, compat=>Comp}
+ #{ccc=>CCC, canonical=>Canon, compat=>Comp, cat=>Cat}
end,
AofMaps0 = array:sparse_map(FixCanon, Data),
- FixCompat = fun(_, #{ccc:=CCC, canonical:=Canon, compat:=Comp}) ->
+ FixCompat = fun(_, #{ccc:=CCC, canonical:=Canon, compat:=Comp, cat:=Cat}) ->
Compat = decompose_compat(Canon, Comp, AofMaps0),
- {CCC, Canon, Compat}
+ {CCC, Canon, Compat, category(Cat)}
end,
AofMaps1 = array:sparse_map(FixCompat, AofMaps0),
Dict0 = array:sparse_to_orddict(AofMaps1),
- Def = {0, [], []},
- Dict = lists:filter(fun({_, Map}) -> Map =/= Def end, Dict0),
+ Def = {0, [], [], lookup_category},
+ {NonDef, CatTable} = lists:partition(fun({_, {0,[],[],_Cat}}) -> false;
+ (_) -> true
+ end, Dict0),
+
+ %% Export testfile
+ case UpdateTests of
+ true ->
+ Dict1 = lists:map(fun({Id,{CCC, Canon, Compat, Cat}}) ->
+ {_, ECat} = lists:keyfind(Cat, 1, category_translate()),
+ {Id, {CCC, Canon, Compat, ECat}}
+ end, Dict0),
+ TestFile = "../test/unicode_util_SUITE_data/unicode_table.bin",
+ io:format("Updating: ~s~n", [TestFile]),
+ file:write_file(TestFile, term_to_binary(Dict1, [compressed]));
+ false ->
+ ignore
+ end,
- [io:format(Fd, "unicode_table(~w) -> ~w;~n", [CP, Map]) || {CP,Map} <- Dict],
+ [io:format(Fd, "unicode_table(~w) -> ~w;~n", [CP, Map]) || {CP,Map} <- NonDef],
io:format(Fd, "unicode_table(_) -> ~w.~n~n",[Def]),
+
+ [io:format(Fd, "cat_translate(~w) -> ~w;~n", [Cat, EC]) || {Cat,EC} <- category_translate()],
+ io:format(Fd, "cat_translate(Cat) -> error({internal_error, Cat}).~n~n",[]),
+ gen_category(Fd, CatTable, Data),
+ ok.
+
+category([C,Sub]) ->
+ list_to_atom([C-$A+$a, Sub]).
+
+category_translate() ->
+ [{lu, {letter, uppercase}}, % Letter, Uppercase
+ {ll, {letter, lowercase}}, % Letter, Lowercase
+ {lt, {letter, titlecase}}, % Letter, Titlecase
+ {mn, {mark, non_spacing}}, % Mark, Non-Spacing
+ {mc, {mark, spacing_combining}}, % Mark, Spacing Combining
+ {me, {mark, enclosing}}, % Mark, Enclosing
+ {nd, {number, decimal}}, % Number, Decimal Digit
+ {nl, {number, letter}}, % Number, Letter
+ {no, {number, other}}, % Number, Other
+ {zs, {separator, space}}, % Separator, Space
+ {zl, {separator, line}}, % Separator, Line
+ {zp, {separator, paragraph}}, % Separator, Paragraph
+ {cc, {other, control}}, % Other, Control
+ {cf, {other, format}}, % Other, Format
+ {cs, {other, surrogate}}, % Other, Surrogate
+ {co, {other, private}}, % Other, Private Use
+ {cn, {other, not_assigned}}, % Other, Not Assigned (no characters in the file have this property)
+ {lm, {letter, modifier}}, % Letter, Modifier
+ {lo, {letter, other}}, % Letter, Other
+ {pc, {punctuation, connector}}, % Punctuation, Connector
+ {pd, {punctuation, dash}}, % Punctuation, Dash
+ {ps, {punctuation, open}}, % Punctuation, Open
+ {pe, {punctuation, close}}, % Punctuation, Close
+ {pi, {punctuation, initial}}, % Punctuation, Initial quote (may behave like Ps or Pe depending on usage)
+ {pf, {punctuation, final}}, % Punctuation, Final quote (may behave like Ps or Pe depending on usage)
+ {po, {punctuation, other}}, % Punctuation, Other
+ {sm, {symbol, math}}, % Symbol, Math
+ {sc, {symbol, currency}}, % Symbol, Currency
+ {sk, {symbol, modifier}}, % Symbol, Modifier
+ {so, {symbol, other}}]. % Symbol, Other
+
+gen_category(Fd, [{CP, {_, _, _, Cat}}|Rest], All) ->
+ gen_category(Fd, Rest, Cat, CP, CP, All, []).
+
+gen_category(Fd, [{CP, {_, _, _, NextCat}}|Rest], Cat, Start, End, All, Acc)
+ when End+1 =:= CP ->
+ IsLetterCat = letter_cat(NextCat, Cat),
+ if NextCat =:= Cat ->
+ gen_category(Fd, Rest, Cat, Start, CP, All, Acc);
+ IsLetterCat ->
+ gen_category(Fd, Rest, letter, Start, CP, All, Acc);
+ Start =:= End ->
+ io:format(Fd, "lookup_category(~w) -> ~w;~n", [Start, Cat]),
+ gen_category(Fd, Rest, NextCat, CP, CP, All, Acc);
+ true ->
+ case Cat of
+ letter ->
+ io:format(Fd, "lookup_category(CP) when ~w =< CP, CP =< ~w-> subcat_letter(CP);~n",
+ [Start, End]),
+ gen_category(Fd, Rest, NextCat, CP, CP, All,
+ lists:reverse(lists:seq(Start, End)) ++ Acc);
+ _ ->
+ io:format(Fd, "lookup_category(CP) when ~w =< CP, CP =< ~w-> ~w;~n", [Start, End, Cat]),
+ gen_category(Fd, Rest, NextCat, CP, CP, All, Acc)
+ end
+ end;
+gen_category(Fd, [{CP, {_, _, _, NewCat}}|Rest]=Cont, Cat, Start, End, All, Acc) ->
+ case array:get(End+1, All) of
+ undefined ->
+ if Start =:= End ->
+ io:format(Fd, "lookup_category(~w) -> ~w;~n", [Start, Cat]),
+ gen_category(Fd, Rest, NewCat, CP, CP, All, Acc);
+ true ->
+ case Cat of
+ letter ->
+ io:format(Fd, "lookup_category(CP) when ~w =< CP, CP =< ~w-> subcat_letter(CP);~n",
+ [Start, End]),
+ gen_category(Fd, Rest, NewCat, CP, CP, All,
+ lists:reverse(lists:seq(Start, End)) ++ Acc);
+ _ ->
+ io:format(Fd, "lookup_category(CP) when ~w =< CP, CP =< ~w -> ~w;~n",
+ [Start, End, Cat]),
+ gen_category(Fd, Rest, NewCat, CP, CP, All, Acc)
+ end
+ end;
+ _ -> %% We can make ranges larger by setting already assigned category
+ gen_category(Fd, Cont, Cat, Start, End+1, All, Acc)
+ end;
+gen_category(Fd, [], Cat, Start, End, All, Acc) ->
+ case Start =:= End of
+ true ->
+ io:format(Fd, "lookup_category(~w) -> ~w;~n", [Start, Cat]);
+ false ->
+ io:format(Fd, "lookup_category(CP) when ~w =< CP, CP =< ~w -> ~w;~n", [Start, End, Cat])
+ end,
+ io:put_chars(Fd, "lookup_category(Cp) -> cn.\n\n"),
+ gen_letter(Fd, lists:reverse(Acc), All),
ok.
+letter_cat(lm, _) ->
+ false;
+letter_cat(_, lm) ->
+ false;
+letter_cat(L1, L2) ->
+ is_letter(L1) andalso (L2 =:= letter orelse is_letter(L2)).
+
+is_letter(LC) ->
+ lists:member(LC, [lu,ll,lt,lo,lm]).
+
+gen_letter(Fd, Letters, All) ->
+ gen_letter(Fd, Letters, All, []).
+gen_letter(Fd, [CP|Rest], All, Acc) ->
+ case array:get(CP, All) of
+ undefined ->
+ gen_letter(Fd, Rest, All, Acc);
+ #cp{cat=Cat0, cs=Cs} ->
+ case {category(Cat0), case_table(CP,case_data(CP, Cs))} of
+ {Sub,Sub} ->
+ gen_letter(Fd, Rest, All, Acc);
+ {lm,_} ->
+ gen_letter(Fd, Rest, All, Acc);
+ {Cat, _Dbg} ->
+ case is_letter(Cat) of
+ true ->
+ gen_letter(Fd, Rest, All, [{CP, Cat}|Acc]);
+ false ->
+ gen_letter(Fd, Rest, All, Acc)
+ end
+ end
+ end;
+gen_letter(Fd, [], _, Acc) ->
+ [{Start, Cat}|SCletters] = lists:reverse(Acc),
+ subcat_letter(Fd, SCletters, Start, Start, Cat),
+ io:put_chars(Fd,
+ "subcat_letter(CP) ->\n"
+ " case case_table(CP) of\n"
+ " {CP, CP} -> lo; %{letter,other};\n"
+ " {CP, _} -> lu; %{letter,uppercase};\n"
+ " {_, CP} -> ll; %{letter,lowercase};\n"
+ " {_, _, CP, _} -> lt; %{letter,titlecase};\n"
+ " {CP, _, _, _} -> lu; %{letter,uppercase};\n"
+ " {_,CP,_,_} -> ll %{letter,lowercase}\n"
+ " end.\n\n").
+
+subcat_letter(Fd, [{CP, Cat}|R], Start, End, Cat) when End+1 =:= CP ->
+ subcat_letter(Fd, R, Start, CP, Cat);
+subcat_letter(Fd, Rest, Start, Start, Cat) ->
+ io:format(Fd, "subcat_letter(~w) -> ~w;\n",[Start,Cat]),
+ case Rest of
+ [] -> ok;
+ [{CP, NewCat}|R] -> subcat_letter(Fd, R, CP, CP, NewCat)
+ end;
+subcat_letter(Fd, Rest, Start, End, Cat) ->
+ io:format(Fd, "subcat_letter(CP) when ~w =< CP, CP =< ~w -> ~w;\n",[Start,End,Cat]),
+ case Rest of
+ [] -> ok;
+ [{CP, NewCat}|R] -> subcat_letter(Fd, R, CP, CP, NewCat)
+ end.
+
+case_table(CP, CaseData) ->
+ case CaseData of
+ {CP, CP} -> lo;
+ {CP, _} -> lu;
+ {_, CP} -> ll;
+ {_, _, CP, _} -> lt;
+ {CP, _, _, _} -> lu;
+ {_,CP,_,_} -> ll;
+ default -> lo
+ end.
+
decompose([], _Data) -> [];
decompose([CP|CPs], Data) when is_integer(CP) ->
case array:get(CP,Data) of
@@ -883,35 +1171,41 @@ decompose_compat([{_,CP}|CPs], Data) ->
decompose_compat([CP|CPs], Data).
+gen_width_table(Fd, WideChars) ->
+ MergedWCs = merge_ranges(WideChars),
+ Write = fun(Range) -> io:format(Fd, "is_wide_cp~s true;~n", [gen_single_clause(Range)]) end,
+ [Write(Range) || Range <- MergedWCs],
+ io:format(Fd, "is_wide_cp(_) -> false.~n", []).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
gen_clause({R0, undefined}) ->
io_lib:format("([~w=CP|R1]=R0) ->", [R0]);
gen_clause({R0, R1}) ->
- io_lib:format("([CP|R1]=R0) when ~w =< CP, CP =< ~w ->", [R0,R1]).
+ io_lib:format("([CP|R1]=R0) when is_integer(CP), ~w =< CP, CP =< ~w ->", [R0,R1]).
gen_clause2({R0, undefined}) ->
io_lib:format("([~w=CP|R1], R0, Acc) ->", [R0]);
gen_clause2({R0, R1}) ->
- io_lib:format("([CP|R1], R0, Acc) when ~w =< CP, CP =< ~w ->", [R0,R1]).
+ io_lib:format("([CP|R1], R0, Acc) when is_integer(CP), ~w =< CP, CP =< ~w ->", [R0,R1]).
gen_case_clause({R0, undefined}) ->
io_lib:format("[~w=CP|R1] ->", [R0]);
gen_case_clause({R0, R1}) ->
- io_lib:format("[CP|R1] when ~w =< CP, CP =< ~w ->", [R0,R1]).
+ io_lib:format("[CP|R1] when is_integer(CP), ~w =< CP, CP =< ~w ->", [R0,R1]).
gen_single_clause({R0, undefined}) ->
io_lib:format("(~w) ->", [R0]);
gen_single_clause({R0, R1}) ->
- io_lib:format("(CP) when ~w =< CP, CP =< ~w ->", [R0,R1]).
+ io_lib:format("(CP) when is_integer(CP), ~w =< CP, CP =< ~w ->", [R0,R1]).
merge_ranges(List) ->
merge_ranges(List, true).
merge_ranges(List, Opt) ->
- Res0 = merge_ranges_1(lists:sort(List), []),
+ Res0 = merge_ranges_1(lists:usort(List), []),
case Opt of
split ->
split_ranges(Res0,[]); % One clause per CP
diff --git a/lib/syntax_tools/src/epp_dodger.erl b/lib/syntax_tools/src/epp_dodger.erl
index 2e0694c2cf..40f67b5660 100644
--- a/lib/syntax_tools/src/epp_dodger.erl
+++ b/lib/syntax_tools/src/epp_dodger.erl
@@ -517,6 +517,8 @@ quickscan_form([{'-', _Anno}, {'else', AnnoA} | _Ts]) ->
kill_form(AnnoA);
quickscan_form([{'-', _Anno}, {atom, AnnoA, endif} | _Ts]) ->
kill_form(AnnoA);
+quickscan_form([{'-', _Anno}, {atom, AnnoA, feature} | _Ts]) ->
+ kill_form(AnnoA);
quickscan_form([{'-', Anno}, {'?', _}, {Type, _, _}=N | [{'(', _} | _]=Ts])
when Type =:= atom; Type =:= var ->
%% minus, macro and open parenthesis at start of form - assume that
diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl
index d6c1c1e005..89ff4bf134 100644
--- a/lib/syntax_tools/src/erl_prettypr.erl
+++ b/lib/syntax_tools/src/erl_prettypr.erl
@@ -858,6 +858,12 @@ lay_2(Node, Ctxt) ->
D2 = lay(erl_syntax:binary_generator_body(Node), Ctxt1),
par([D1, beside(text("<= "), D2)], Ctxt1#ctxt.break_indent);
+ map_generator ->
+ Ctxt1 = reset_prec(Ctxt),
+ D1 = lay(erl_syntax:map_generator_pattern(Node), Ctxt1),
+ D2 = lay(erl_syntax:map_generator_body(Node), Ctxt1),
+ par([D1, beside(text("<- "), D2)], Ctxt1#ctxt.break_indent);
+
implicit_fun ->
D = lay(erl_syntax:implicit_fun_name(Node),
reset_prec(Ctxt)),
@@ -883,6 +889,15 @@ lay_2(Node, Ctxt) ->
par([D1, beside(floating(text(" || ")),
beside(D2, floating(text(" >>"))))]));
+ map_comp ->
+ Ctxt1 = set_prec(Ctxt, max_prec()),
+ D1 = lay(erl_syntax:map_comp_template(Node), Ctxt1),
+ D2 = par(seq(erl_syntax:map_comp_body(Node),
+ floating(text(",")), Ctxt1,
+ fun lay/2)),
+ beside(floating(text("#{")),
+ par([D1, beside(floating(text("|| ")),
+ beside(D2, floating(text("}"))))]));
macro ->
%% This is formatted similar to a normal function call, but
%% prefixed with a "?".
diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl
index 4da1635aec..1958865b3b 100644
--- a/lib/syntax_tools/src/erl_syntax.erl
+++ b/lib/syntax_tools/src/erl_syntax.erl
@@ -246,6 +246,9 @@
macro/2,
macro_arguments/1,
macro_name/1,
+ map_comp/2,
+ map_comp_template/1,
+ map_comp_body/1,
map_expr/1,
map_expr/2,
map_expr_argument/1,
@@ -256,6 +259,9 @@
map_field_exact/2,
map_field_exact_name/1,
map_field_exact_value/1,
+ map_generator/2,
+ map_generator_body/1,
+ map_generator_pattern/1,
map_type/0,
map_type/1,
map_type_fields/1,
@@ -715,8 +721,10 @@ type(Node) ->
{function, _, _, _, _} -> function;
{b_generate, _, _, _} -> binary_generator;
{generate, _, _, _} -> generator;
+ {m_generate, _, _, _} -> map_generator;
{lc, _, _, _} -> list_comp;
- {bc, _, _, _} -> binary_comp;
+ {bc, _, _, _} -> binary_comp;
+ {mc, _, _, _} -> map_comp;
{match, _, _, _} -> match_expr;
{map, _, _, _} -> map_expr;
{map, _, _} -> map_expr;
@@ -6138,6 +6146,72 @@ binary_comp_body(Node) ->
(data(Node1))#binary_comp.body
end.
+%% =====================================================================
+%% @doc Creates an abstract map comprehension. If `Body' is
+%% `[E1, ..., En]', the result represents
+%% "<code>#{<em>Template</em> || <em>E1</em>, ..., <em>En</em>}</code>".
+%%
+%% @see map_comp_template/1
+%% @see map_comp_body/1
+%% @see generator/2
+
+-record(map_comp, {template :: syntaxTree(), body :: [syntaxTree()]}).
+
+%% type(Node) = map_comp
+%% data(Node) = #map_comp{template :: Template, body :: Body}
+%%
+%% Template = Node = syntaxTree()
+%% Body = [syntaxTree()]
+%%
+%% `erl_parse' representation:
+%%
+%% {mc, Pos, Template, Body}
+%%
+%% Template = erl_parse()
+%% Body = [erl_parse()] \ []
+
+-spec map_comp(syntaxTree(), [syntaxTree()]) -> syntaxTree().
+
+map_comp(Template, Body) ->
+ tree(map_comp, #map_comp{template = Template, body = Body}).
+
+revert_map_comp(Node) ->
+ Pos = get_pos(Node),
+ Template = map_comp_template(Node),
+ Body = map_comp_body(Node),
+ {mc, Pos, Template, Body}.
+
+
+%% =====================================================================
+%% @doc Returns the template subtree of a `map_comp' node.
+%%
+%% @see map_comp/2
+
+-spec map_comp_template(syntaxTree()) -> syntaxTree().
+
+map_comp_template(Node) ->
+ case unwrap(Node) of
+ {mc, _, Template, _} ->
+ Template;
+ Node1 ->
+ (data(Node1))#map_comp.template
+ end.
+
+
+%% =====================================================================
+%% @doc Returns the list of body subtrees of a `map_comp' node.
+%%
+%% @see map_comp/2
+
+-spec map_comp_body(syntaxTree()) -> [syntaxTree()].
+
+map_comp_body(Node) ->
+ case unwrap(Node) of
+ {mc, _, _, Body} ->
+ Body;
+ Node1 ->
+ (data(Node1))#map_comp.body
+ end.
%% =====================================================================
%% @doc Creates an abstract generator. The result represents
@@ -6272,6 +6346,72 @@ binary_generator_body(Node) ->
%% =====================================================================
+%% @doc Creates an abstract map_generator. The result represents
+%% "<code><em>Pattern</em> &lt;- <em>Body</em></code>".
+%%
+%% @see map_generator_pattern/1
+%% @see map_generator_body/1
+%% @see list_comp/2
+%% @see map_comp/2
+
+-record(map_generator, {pattern :: syntaxTree(), body :: syntaxTree()}).
+
+%% type(Node) = map_generator
+%% data(Node) = #map_generator{pattern :: Pattern, body :: Body}
+%%
+%% Pattern = Argument = syntaxTree()
+%%
+%% `erl_parse' representation:
+%%
+%% {m_generate, Pos, Pattern, Body}
+%%
+%% Pattern = Body = erl_parse()
+
+-spec map_generator(syntaxTree(), syntaxTree()) -> syntaxTree().
+
+map_generator(Pattern, Body) ->
+ tree(map_generator, #map_generator{pattern = Pattern, body = Body}).
+
+revert_map_generator(Node) ->
+ Pos = get_pos(Node),
+ Pattern = map_generator_pattern(Node),
+ Body = map_generator_body(Node),
+ {m_generate, Pos, Pattern, Body}.
+
+
+%% =====================================================================
+%% @doc Returns the pattern subtree of a `generator' node.
+%%
+%% @see map_generator/2
+
+-spec map_generator_pattern(syntaxTree()) -> syntaxTree().
+
+map_generator_pattern(Node) ->
+ case unwrap(Node) of
+ {m_generate, _, Pattern, _} ->
+ Pattern;
+ Node1 ->
+ (data(Node1))#map_generator.pattern
+ end.
+
+
+%% =====================================================================
+%% @doc Returns the body subtree of a `generator' node.
+%%
+%% @see map_generator/2
+
+-spec map_generator_body(syntaxTree()) -> syntaxTree().
+
+map_generator_body(Node) ->
+ case unwrap(Node) of
+ {m_generate, _, _, Body} ->
+ Body;
+ Node1 ->
+ (data(Node1))#map_generator.body
+ end.
+
+
+%% =====================================================================
%% @doc Creates an abstract block expression. If `Body' is
%% `[B1, ..., Bn]', the result represents "<code>begin
%% <em>B1</em>, ..., <em>Bn</em> end</code>".
@@ -7724,12 +7864,16 @@ revert_root(Node) ->
revert_list(Node);
list_comp ->
revert_list_comp(Node);
+ map_comp ->
+ revert_map_comp(Node);
map_expr ->
revert_map_expr(Node);
map_field_assoc ->
revert_map_field_assoc(Node);
map_field_exact ->
revert_map_field_exact(Node);
+ map_generator ->
+ revert_map_generator(Node);
map_type ->
revert_map_type(Node);
map_type_assoc ->
@@ -8017,6 +8161,8 @@ subtrees(T) ->
As ->
[[macro_name(T)], As]
end;
+ map_comp ->
+ [[map_comp_template(T)], map_comp_body(T)];
map_expr ->
case map_expr_argument(T) of
none ->
@@ -8030,6 +8176,9 @@ subtrees(T) ->
map_field_exact ->
[[map_field_exact_name(T)],
[map_field_exact_value(T)]];
+ map_generator ->
+ [[map_generator_pattern(T)],
+ [map_generator_body(T)]];
map_type ->
[map_type_fields(T)];
map_type_assoc ->
@@ -8207,10 +8356,12 @@ make_tree(list, [P, [S]]) -> list(P, S);
make_tree(list_comp, [[T], B]) -> list_comp(T, B);
make_tree(macro, [[N]]) -> macro(N);
make_tree(macro, [[N], A]) -> macro(N, A);
+make_tree(map_comp, [[T], B]) -> map_comp(T, B);
make_tree(map_expr, [Fs]) -> map_expr(Fs);
make_tree(map_expr, [[E], Fs]) -> map_expr(E, Fs);
make_tree(map_field_assoc, [[K], [V]]) -> map_field_assoc(K, V);
make_tree(map_field_exact, [[K], [V]]) -> map_field_exact(K, V);
+make_tree(map_generator, [[P], [E]]) -> map_generator(P, E);
make_tree(map_type, [Fs]) -> map_type(Fs);
make_tree(map_type_assoc, [[N],[V]]) -> map_type_assoc(N, V);
make_tree(map_type_exact, [[N],[V]]) -> map_type_exact(N, V);
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl
index 9035139fea..daa95b6a25 100644
--- a/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl
+++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl
@@ -8,7 +8,7 @@
sub_word/2,sub_word/3,left/2,left/3,right/2,right/3,
sub_string/2,sub_string/3,centre/2,centre/3, join/2]).
-export([to_upper/1, to_lower/1]).
--export([eep49/0]).
+-export([eep49/0, eep58/0]).
-import(lists,[reverse/1,member/2]).
@@ -581,3 +581,12 @@ eep49() ->
end,
ok.
+
+%% EEP-58: Map comprehensions.
+eep58() ->
+ Seq = lists:seq(1, 10),
+ Map = #{{key,I} => I || I <- Seq},
+ MapDouble = #{K => 2 * V || K := V <- Map},
+ MapDouble = maps:from_list([{{key,I}, 2 * I} || I <- Seq]),
+
+ ok.
diff --git a/lib/tftp/src/tftp.app.src b/lib/tftp/src/tftp.app.src
index 49b95e0e7b..28040ca142 100644
--- a/lib/tftp/src/tftp.app.src
+++ b/lib/tftp/src/tftp.app.src
@@ -18,5 +18,5 @@
tftp_logger,
tftp_sup
]},
- {runtime_dependencies, ["erts-6.0","stdlib-3.5","kernel-6.0"]}
+ {runtime_dependencies, ["erts-6.0","stdlib-@OTP-18490@","kernel-6.0"]}
]}.
diff --git a/lib/tftp/src/tftp_engine.erl b/lib/tftp/src/tftp_engine.erl
index f9e5e40d34..4e55fec63e 100644
--- a/lib/tftp/src/tftp_engine.erl
+++ b/lib/tftp/src/tftp_engine.erl
@@ -714,7 +714,9 @@ pre_terminate(Config, Req, Result) ->
if
Req#tftp_msg_req.local_filename =/= undefined,
Config#config.parent_pid =/= undefined ->
- proc_lib:init_ack(Result),
+ %% Ugly trick relying on that we will exit soon;
+ %% the parent will wait for us to exit before returning Result
+ _ = catch proc_lib:init_fail(Result, {throw, ok}),
unlink(Config#config.parent_pid),
Config#config{parent_pid = undefined, polite_ack = true};
true ->
@@ -739,7 +741,9 @@ terminate(Config, Req, Result) ->
Req#tftp_msg_req.local_filename =/= undefined ->
%% Client
close_port(Config, client, Req),
- proc_lib:init_ack(Result2),
+ %% Ugly trick relying on that we will exit soon;
+ %% the parent will wait for us to exit before returning Result
+ _ = catch proc_lib:init_fail(Result2, {throw, ok}),
unlink(Config#config.parent_pid),
exit(normal);
true ->
diff --git a/lib/tools/doc/src/Makefile b/lib/tools/doc/src/Makefile
index 63218e793f..6b1e2b050f 100644
--- a/lib/tools/doc/src/Makefile
+++ b/lib/tools/doc/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2021. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -38,7 +38,6 @@ XML_REF3_FILES = \
fprof.xml \
cprof.xml \
lcnt.xml \
- instrument.xml \
make.xml \
tags.xml \
xref.xml \
diff --git a/lib/tools/doc/src/part.xml b/lib/tools/doc/src/part.xml
index 796047fe8d..13e2414001 100644
--- a/lib/tools/doc/src/part.xml
+++ b/lib/tools/doc/src/part.xml
@@ -4,7 +4,7 @@
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>1996</year><year>2016</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -53,9 +53,6 @@
Erlang programs. Uses trace to file to minimize runtime
performance impact, and displays time for calling and called
functions.</item>
- <tag><em>instrument</em></tag>
- <item>Utility functions for obtaining and analysing resource usage
- in an instrumented Erlang runtime system.</item>
<tag><em>lcnt</em></tag>
<item>A lock profiling tool for the Erlang runtime system.</item>
<tag><em>make</em></tag>
diff --git a/lib/tools/doc/src/ref_man.xml b/lib/tools/doc/src/ref_man.xml
index d2131e7a93..c2962eeb84 100644
--- a/lib/tools/doc/src/ref_man.xml
+++ b/lib/tools/doc/src/ref_man.xml
@@ -4,7 +4,7 @@
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>1996</year><year>2016</year>
+ <year>1996</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -53,10 +53,6 @@
performance impact, and displays time for calling and called
functions.</item>
- <tag><em>instrument</em></tag>
- <item>Utility functions for obtaining and analysing resource usage
- in an instrumented Erlang runtime system.</item>
-
<tag><em>lcnt</em></tag>
<item>A lock profiling tool for the Erlang runtime system.</item>
@@ -75,7 +71,6 @@
<xi:include href="eprof.xml"/>
<xi:include href="erlang_mode.xml"/>
<xi:include href="fprof.xml"/>
- <xi:include href="instrument.xml"/>
<xi:include href="lcnt.xml"/>
<xi:include href="make.xml"/>
<xi:include href="tags.xml"/>
diff --git a/lib/tools/doc/src/specs.xml b/lib/tools/doc/src/specs.xml
index 4f7223e2bc..143b54f0a9 100644
--- a/lib/tools/doc/src/specs.xml
+++ b/lib/tools/doc/src/specs.xml
@@ -8,6 +8,5 @@
<xi:include href="../specs/specs_tags.xml"/>
<xi:include href="../specs/specs_cover.xml"/>
<xi:include href="../specs/specs_xref.xml"/>
- <xi:include href="../specs/specs_instrument.xml"/>
<xi:include href="../specs/specs_erlang_mode.xml"/>
</specs>
diff --git a/lib/tools/emacs/erlang-skels.el b/lib/tools/emacs/erlang-skels.el
index 3202f43790..f61f156f9d 100644
--- a/lib/tools/emacs/erlang-skels.el
+++ b/lib/tools/emacs/erlang-skels.el
@@ -1,7 +1,7 @@
;;
;; %CopyrightBegin%
;;
-;; Copyright Ericsson AB 2010-2020. All Rights Reserved.
+;; Copyright Ericsson AB 2010-2023. All Rights Reserved.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
@@ -70,6 +70,8 @@
erlang-skel-ct-test-suite-s erlang-skel-header)
("Large Common Test suite" "ct-test-suite-l"
erlang-skel-ct-test-suite-l erlang-skel-header)
+ ("Common Test Hook" "ct-hook"
+ erlang-skel-ct-hook erlang-skel-header)
("Erlang TS test suite" "ts-test-suite"
erlang-skel-ts-test-suite erlang-skel-header)
)
@@ -1845,6 +1847,107 @@ Please see the function `tempo-define-template'.")
"*The template of a library module.
Please see the function `tempo-define-template'.")
+(defvar erlang-skel-ct-hook
+ '((erlang-skel-include erlang-skel-normal-header)
+ "%% Mandatory callbacks" n
+ "-export([init/2])." n
+ n
+ "%% Optional callbacks" n
+ "-export([id/1])." n
+ n
+ "-export([pre_init_per_suite/3])." n
+ "-export([post_init_per_suite/4])." n
+ n
+ "-export([pre_end_per_suite/3])." n
+ "-export([post_end_per_suite/4])." n
+ n
+ "-export([pre_init_per_group/4])." n
+ "-export([post_init_per_group/5])." n
+ n
+ "-export([pre_end_per_group/4])." n
+ "-export([post_end_per_group/5])." n
+ n
+ "-export([pre_init_per_testcase/4])." n
+ "-export([post_init_per_testcase/5])." n
+ n
+ "-export([pre_end_per_testcase/4])." n
+ "-export([post_end_per_testcase/5])." n
+ n
+ "-export([on_tc_fail/4])." n
+ "-export([on_tc_skip/4])." n
+ n
+ "-export([terminate/1])." n
+ n
+ "%% The hook state is threaded through all callbacks," n
+ "%% but can be anything the hook needs, replace as desired." n
+ "-record(state, { cases=0, suites=0, groups=0, skips=0, fails=0 })." n
+ n
+ "%% Return a unique id for this CTH." n
+ "id(Opts) ->" n
+ " %% A reference is the default implementation, this can be removed" n
+ " %% or some value can be read from Opts instead." n
+ " erlang:make_ref()." n
+ n
+ "%% Always called before any other callback function, once per installed hook." n
+ "init(Id, Opts) ->" n
+ " {ok, #state{}}." n
+ n
+ "pre_init_per_suite(Suite, Config, State) ->" n
+ " {Config, State}." n
+ n
+ "post_init_per_suite(Suite, Config, Return, State) ->" n
+ " {Return, State}." n
+ n
+ "pre_end_per_suite(Suite, Config, State) ->" n
+ " {Config, State}." n
+ n
+ "post_end_per_suite(Suite, Config, Return, State) ->" n
+ " {Return, State#state{suites = State#state.suites + 1}}." n
+ n
+ "pre_init_per_group(Suite, Group, Config, State) ->" n
+ " {Config, State}." n
+ n
+ "post_init_per_group(Suite, Group, Config, Return, State) ->" n
+ " {Return, State}." n
+ n
+ "pre_end_per_group(Suite, Group, Config, State) ->" n
+ " {Config, State}." n
+ n
+ "post_end_per_group(Suite, Group, Config, Return, State) ->" n
+ " {Return, State#state{groups = State#state.groups + 1}}." n
+ n
+ "pre_init_per_testcase(Suite, TC, Config, State) ->" n
+ " {Config, State}." n
+ n
+ "%% Called after each init_per_testcase (immediately before the test case)." n
+ "post_init_per_testcase(Suite, TC, Config, Return, State) ->" n
+ " {Return, State}." n
+ n
+ "%% Called before each end_per_testcase (immediately after the test case)." n
+ "pre_end_per_testcase(Suite, TC, Config, State) ->" n
+ " {Config, State}." n
+ n
+ "post_end_per_testcase(Suite, TC, Config, Return, State) ->" n
+ " {Return, State#state{cases = State#state.cases + 1}}." n
+ n
+ "%% Called after post_init_per_suite, post_end_per_suite, post_init_per_group," n
+ "%% post_end_per_group and post_end_per_testcase if the suite, group or test case failed." n
+ "on_tc_fail(Suite, TC, Reason, State) ->" n
+ " State#state{fails = State#state.fails + 1}." n
+ n
+ "%% Called when a test case is skipped by either user action" n
+ "%% or due to an init function failing." n
+ "on_tc_skip(Suite, TC, Reason, State) ->" n
+ " State#state{skips = State#state.skips + 1}." n
+ n
+ "%% Called when the scope of the CTH is done" n
+ "terminate(State) ->" n
+ " logger:notice(\"~s is done: ~p~n\", [?MODULE, State])." n
+ n
+ )
+ "*The template of a library module.
+ Please see the function `tempo-define-template'.")
+
;; Skeleton code:
;; This code is based on the package `tempo' which is part of modern
diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el
index 065e180804..a21f1df25a 100644
--- a/lib/tools/emacs/erlang.el
+++ b/lib/tools/emacs/erlang.el
@@ -9,7 +9,7 @@
;; %CopyrightBegin%
;;
-;; Copyright Ericsson AB 1996-2021. All Rights Reserved.
+;; Copyright Ericsson AB 1996-2023. All Rights Reserved.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
@@ -928,7 +928,6 @@ resulting regexp is surrounded by \\_< and \\_>."
"delay_trap"
"delete_element"
"display"
- "display_nl"
"display_string"
"dist_get_stat"
"dist_ctrl_get_data"
@@ -3054,7 +3053,7 @@ Return nil if inside string, t if in a comment."
(if (memq (following-char) '(?% ?\n))
(+ (nth 2 stack-top) erlang-indent-level)
(current-column))))))))
- ((and (eq (car stack-top) '||) (looking-at "\\(]\\|>>\\)[^_a-zA-Z0-9]"))
+ ((and (eq (car stack-top) '||) (looking-at "\\(]\\|>>\\|}\\)[^_a-zA-Z0-9]"))
(nth 2 (car (cdr stack))))
;; Real indentation, where operators create extra indentation etc.
((memq (car stack-top) '(-> || try begin maybe))
@@ -3236,8 +3235,13 @@ Return nil if inside string, t if in a comment."
;; Take parent indentation + offset,
;; else just erlang-indent-level if no parent
(if stack
- (+ (caddr (car stack))
- offset)
+ (progn
+ (goto-char (- (nth 1 (car stack)) 1))
+ (if (looking-at "#{")
+ (+ (caddr (car stack))
+ (- offset 1))
+ (+ (caddr (car stack))
+ offset)))
erlang-indent-level))
(erlang-skip-blank indent-point)
(current-column)))
diff --git a/lib/tools/src/Makefile b/lib/tools/src/Makefile
index b05ce883ec..254e0c7196 100644
--- a/lib/tools/src/Makefile
+++ b/lib/tools/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2022. All Rights Reserved.
+# Copyright Ericsson AB 1996-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -41,7 +41,6 @@ MODULES= \
fprof \
cprof \
lcnt \
- instrument \
make \
tags \
xref \
diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl
index 1f7a518dfe..fe9518b3cb 100644
--- a/lib/tools/src/cover.erl
+++ b/lib/tools/src/cover.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1865,6 +1865,10 @@ expand({bc,Anno,Expr,Qs}, Vs, N) ->
{ExpandedExpr,N2} = expand(Expr, Vs, N),
{ExpandedQs,N3} = expand_qualifiers(Qs, Vs, N2),
{{bc,Anno,ExpandedExpr,ExpandedQs},N3};
+expand({mc,Anno,Expr,Qs}, Vs, N) ->
+ {ExpandedExpr,N2} = expand(Expr, Vs, N),
+ {ExpandedQs,N3} = expand_qualifiers(Qs, Vs, N2),
+ {{mc,Anno,ExpandedExpr,ExpandedQs},N3};
expand({op,_Anno,'andalso',ExprL,ExprR}, Vs, N) ->
{ExpandedExprL,N2} = expand(ExprL, Vs, N),
{ExpandedExprR,N3} = expand(ExprR, Vs, N2),
@@ -2239,6 +2243,11 @@ munge_expr({bc,Anno,Expr,Qs}, Vars) ->
{MungedExpr,Vars2} = munge_expr(?BLOCK1(Expr), Vars),
{MungedQs, Vars3} = munge_qualifiers(Qs, Vars2),
{{bc,Anno,MungedExpr,MungedQs}, Vars3};
+munge_expr({mc,Anno,{map_field_assoc,FAnno,K,V},Qs}, Vars) ->
+ Expr = {map_field_assoc,FAnno,?BLOCK1(K),?BLOCK1(V)},
+ {MungedExpr,Vars2} = munge_expr(Expr, Vars),
+ {MungedQs, Vars3} = munge_qualifiers(Qs, Vars2),
+ {{mc,Anno,MungedExpr,MungedQs}, Vars3};
munge_expr({block,Anno,Body}, Vars) ->
{MungedBody, Vars2} = munge_body(Body, Vars),
{{block,Anno,MungedBody}, Vars2};
@@ -2311,6 +2320,10 @@ munge_qs([{b_generate,Anno,Pattern,Expr}|Qs], Vars, MQs) ->
A = element(2, Expr),
{MExpr, Vars2} = munge_expr(Expr, Vars),
munge_qs1(Qs, A, {b_generate,Anno,Pattern,MExpr}, Vars, Vars2, MQs);
+munge_qs([{m_generate,Anno,Pattern,Expr}|Qs], Vars, MQs) ->
+ A = element(2, Expr),
+ {MExpr, Vars2} = munge_expr(Expr, Vars),
+ munge_qs1(Qs, A, {m_generate,Anno,Pattern,MExpr}, Vars, Vars2, MQs);
munge_qs([Expr|Qs], Vars, MQs) ->
A = element(2, Expr),
{MungedExpr, Vars2} = munge_expr(Expr, Vars),
diff --git a/lib/tools/src/lcnt.erl b/lib/tools/src/lcnt.erl
index 978c78038d..8661df696d 100644
--- a/lib/tools/src/lcnt.erl
+++ b/lib/tools/src/lcnt.erl
@@ -893,7 +893,7 @@ clean_id_creation(Id) when is_port(Id) ->
<<131, PortTag, AtomTag>> = H,
LL = atomlen_bits(AtomTag),
CL = creation_bits(PortTag),
- <<L:LL, Node:L/binary, Ids:4/binary, _Creation/binary>> = Rest,
+ <<L:LL, Node:L/binary, Ids:8/binary, _Creation/binary>> = Rest,
Bin2 = list_to_binary([H, <<L:LL>>, Node, Ids, <<0:CL>>]),
binary_to_term(Bin2);
clean_id_creation(Id) ->
@@ -901,8 +901,7 @@ clean_id_creation(Id) ->
-define(PID_EXT, $g).
-define(NEW_PID_EXT, $X).
--define(PORT_EXT, $f).
--define(NEW_PORT_EXT, $Y).
+-define(V4_PORT_EXT, $x).
-define(ATOM_EXT, $d).
-define(SMALL_ATOM_EXT, $s).
-define(ATOM_UTF8_EXT, $v).
@@ -915,8 +914,7 @@ atomlen_bits(?SMALL_ATOM_UTF8_EXT) -> 8.
creation_bits(?PID_EXT) -> 8;
creation_bits(?NEW_PID_EXT) -> 32;
-creation_bits(?PORT_EXT) -> 8;
-creation_bits(?NEW_PORT_EXT) -> 32.
+creation_bits(?V4_PORT_EXT) -> 32.
%% serializer
diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src
index f0c7ec1ead..e3b8bb0215 100644
--- a/lib/tools/src/tools.app.src
+++ b/lib/tools/src/tools.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -24,7 +24,6 @@
cprof,
eprof,
fprof,
- instrument,
lcnt,
make,
tags,
diff --git a/lib/tools/src/xref.erl b/lib/tools/src/xref.erl
index 2da8899ec8..40549d8fc7 100644
--- a/lib/tools/src/xref.erl
+++ b/lib/tools/src/xref.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -83,11 +83,11 @@
| {'unused', [mfa()]},
NoDebugInfoResult :: {'deprecated', [xmfa()]}
| {'undefined', [xmfa()]},
- Reason :: {'cover_compiled', Module}
+ Reason :: {'cover_compiled', Module :: module()}
| {'file_error', file(), file_error()}
- | {'interpreted', Module}
+ | {'interpreted', Module :: module()}
| {'invalid_filename', term()}
- | {'no_such_module', Module}
+ | {'no_such_module', Module :: module()}
| beam_lib:chnk_rsn().
%% No user variables have been assigned digraphs, so there is no
diff --git a/lib/tools/src/xref_reader.erl b/lib/tools/src/xref_reader.erl
index 383bb5e8d3..5eeb7fa8b5 100644
--- a/lib/tools/src/xref_reader.erl
+++ b/lib/tools/src/xref_reader.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -77,10 +77,6 @@ forms([], S) ->
F ->
[{M, F, 0}]
end,
- #xrefr{def_at = DefAt,
- l_call_at = LCallAt, x_call_at = XCallAt,
- el = LC, ex = XC, x = X, df = Depr, on_load = OnLoad,
- lattrs = AL, xattrs = AX, battrs = B, unresolved = U} = S,
Attrs = {lists:reverse(AL), lists:reverse(AX), lists:reverse(B)},
{ok, M, {DefAt, LCallAt, XCallAt, LC, XC, X, Attrs, Depr, OL}, U}.
diff --git a/lib/tools/test/Makefile b/lib/tools/test/Makefile
index 984568e4c5..bfa1887445 100644
--- a/lib/tools/test/Makefile
+++ b/lib/tools/test/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2022. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@ MODULES = \
emacs_SUITE \
fprof_SUITE \
cprof_SUITE \
- instrument_SUITE \
lcnt_SUITE \
make_SUITE \
tools_SUITE \
diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl
index 9977fa8763..ac792380a9 100644
--- a/lib/tools/test/cover_SUITE.erl
+++ b/lib/tools/test/cover_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1522,6 +1522,25 @@ comprehension_8188(Cf) ->
" <<_>> <= << 1, 2 >>,\n" % 2
" true >>.\n" % 4
"two() -> 2">>, Cf), % 1
+
+ [{{t,1},1},
+ {{t,2},2},
+ {{t,3},2},
+ {{t,5},1},
+ {{t,8},3},
+ {{t,9},2},
+ {{t,10},1}] =
+ analyse_expr(<<"#{\n" %1
+ " K + 1 =>\n" %2
+ "X * 2 ||\n" %2
+ " K := X <-\n"
+ " #{1 => 1,\n" %1
+ " 2 => two(),\n"
+ " 3 => 3},\n"
+ " X > 1,\n" %3
+ " true}. \n" %2
+ " two() -> 2">>, Cf), %1
+
ok.
eep37(Config) when is_list(Config) ->
diff --git a/lib/tools/test/emacs_SUITE_data/comprehensions b/lib/tools/test/emacs_SUITE_data/comprehensions
index 45279850a5..002c9140ad 100644
--- a/lib/tools/test/emacs_SUITE_data/comprehensions
+++ b/lib/tools/test/emacs_SUITE_data/comprehensions
@@ -45,3 +45,18 @@ binary(B) ->
true = (X rem 2)
>>,
ok.
+
+maps(Map) ->
+ New1 = #{ V => K ||
+ K := V <- Map},
+
+ New2 = #{ V = K || K := V <- Map,
+ true =:= V
+ },
+
+ New3 = #{
+ V => K
+ ||
+ K := V <-
+ Map
+ }.
diff --git a/lib/wx/.gitignore b/lib/wx/.gitignore
index 09564b499e..aa95f9ad4c 100644
--- a/lib/wx/.gitignore
+++ b/lib/wx/.gitignore
@@ -3,6 +3,7 @@ wx_test_case_info
doc/html/*
api_gen/wx_doxygen.log*
api_gen/wx_*_api.dump
+priv/WebView2Loader.dll
%% Don't delete links to man src when git clean -dfX
%% api_gen/gl_man?
diff --git a/lib/wx/Makefile b/lib/wx/Makefile
index f471824a14..b77a7808c5 100644
--- a/lib/wx/Makefile
+++ b/lib/wx/Makefile
@@ -21,7 +21,7 @@
include ./vsn.mk
include ./config.mk
-ifdef TERTIARY_BOOTSTRAP
+ifdef BOOTSTRAP
SUBDIRS = src
else # Normal build
SUBDIRS = src
@@ -29,7 +29,7 @@ else # Normal build
SUBDIRS += c_src
endif
SUBDIRS += examples doc/src
-endif #TERTIARY_BOOTSTRAP
+endif #BOOTSTRAP
CLEANDIRS = $(SUBDIRS) api_gen
diff --git a/lib/wx/c_src/Makefile.in b/lib/wx/c_src/Makefile.in
index 87fc0354b6..6371f401c0 100644
--- a/lib/wx/c_src/Makefile.in
+++ b/lib/wx/c_src/Makefile.in
@@ -199,7 +199,11 @@ release_spec: opt
$(INSTALL_PROGRAM) $(TARGET_DIR)/wxe_driver$(SO_EXT) "$(RELSYSDIR)/priv/"
$(INSTALL_PROGRAM) $(TARGET_DIR)/erl_gl$(SO_EXT) "$(RELSYSDIR)/priv/"
ifneq ($(WEBVIEW_LOADER_DLL_ORIG),)
- $(INSTALL_PROGRAM) $(WEBVIEW_LOADER_DLL_DEST) "$(RELSYSDIR)/priv/"
+ $(INSTALL_PROGRAM) $(WEBVIEW_LOADER_DLL_DEST) "$(RELSYSDIR)/priv/"
+endif
+ifeq ($(SYS_TYPE),win32)
+ $(INSTALL_PROGRAM) $(TARGET_DIR)/wxe_driver.pdb "$(RELSYSDIR)/priv/"
+ $(INSTALL_PROGRAM) $(TARGET_DIR)/erl_gl.pdb "$(RELSYSDIR)/priv/"
endif
release_docs_spec:
diff --git a/lib/wx/configure b/lib/wx/configure
index d3dc12b32a..6fd467bb78 100755
--- a/lib/wx/configure
+++ b/lib/wx/configure
@@ -758,7 +758,6 @@ with_wxdir
with_wx_config
with_wx_prefix
with_wx_exec_prefix
-enable_sanitizers
'
ac_precious_vars='build_alias
host_alias
@@ -1389,13 +1388,6 @@ if test -n "$ac_init_help"; then
cat <<\_ACEOF
-Optional Features:
- --disable-option-checking ignore unrecognized --enable/--with options
- --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
- --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
- --enable-sanitizers[=comma-separated list of sanitizers]
- Default=address,undefined
-
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
@@ -7172,24 +7164,6 @@ fi
fi
-
-
-# Check whether --enable-sanitizers was given.
-if test ${enable_sanitizers+y}
-then :
- enableval=$enable_sanitizers;
-case "$enableval" in
- no) sanitizers= ;;
- yes) sanitizers="-fsanitize=address,undefined" ;;
- *) sanitizers="-fsanitize=$enableval" ;;
-esac
-CFLAGS="$CFLAGS $sanitizers"
-CXXFLAGS="$CXXFLAGS $sanitizers"
-LDFLAGS="$LDFLAGS $sanitizers"
-
-fi
-
-
#############################################################################
diff --git a/lib/wx/configure.ac b/lib/wx/configure.ac
index d49399c540..6b73ddc205 100644
--- a/lib/wx/configure.ac
+++ b/lib/wx/configure.ac
@@ -2,7 +2,7 @@ dnl Process this file with autoconf to produce a configure script. -*-m4-*-
dnl %CopyrightBegin%
dnl
-dnl Copyright Ericsson AB 2008-2022. All Rights Reserved.
+dnl Copyright Ericsson AB 2008-2023. All Rights Reserved.
dnl
dnl Licensed under the Apache License, Version 2.0 (the "License");
dnl you may not use this file except in compliance with the License.
@@ -712,27 +712,6 @@ AS_IF([test "x$GCC" = xyes],
LM_TRY_ENABLE_CFLAG([-Werror=return-type], [CXXFLAGS])
])
-dnl ----------------------------------------------------------------------
-dnl Enable -fsanitize= flags.
-dnl ----------------------------------------------------------------------
-
-m4_define(DEFAULT_SANITIZERS, [address,undefined])
-AC_ARG_ENABLE(
- sanitizers,
- AS_HELP_STRING(
- [--enable-sanitizers@<:@=comma-separated list of sanitizers@:>@],
- [Default=DEFAULT_SANITIZERS]),
-[
-case "$enableval" in
- no) sanitizers= ;;
- yes) sanitizers="-fsanitize=DEFAULT_SANITIZERS" ;;
- *) sanitizers="-fsanitize=$enableval" ;;
-esac
-CFLAGS="$CFLAGS $sanitizers"
-CXXFLAGS="$CXXFLAGS $sanitizers"
-LDFLAGS="$LDFLAGS $sanitizers"
-])
-
#############################################################################
dnl
diff --git a/lib/wx/src/Makefile b/lib/wx/src/Makefile
index ce14c0b6df..b055dfed4a 100644
--- a/lib/wx/src/Makefile
+++ b/lib/wx/src/Makefile
@@ -19,7 +19,7 @@
#
include ../vsn.mk
-ifdef TERTIARY_BOOTSTRAP
+ifdef BOOTSTRAP
VSN = $(WX_VSN)
include $(ERL_TOP)/make/target.mk
include $(ERL_TOP)/make/$(TARGET)/otp.mk
diff --git a/lib/wx/src/wx.app.src b/lib/wx/src/wx.app.src
index 91ee7a7589..228800b76a 100644
--- a/lib/wx/src/wx.app.src
+++ b/lib/wx/src/wx.app.src
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -35,5 +35,5 @@
{registered, []},
{applications, [stdlib, kernel]},
{env, []},
- {runtime_dependencies, ["stdlib-3.15","kernel-8.0","erts-12.0"]}
+ {runtime_dependencies, ["stdlib-@OTP-18490@","kernel-8.0","erts-12.0"]}
]}.
diff --git a/lib/wx/src/wx_object.erl b/lib/wx/src/wx_object.erl
index 81d188b26a..1a2d49faca 100644
--- a/lib/wx/src/wx_object.erl
+++ b/lib/wx/src/wx_object.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -235,7 +235,7 @@ start_link(Name, Mod, Args, Options) ->
gen_response(gen:start(?MODULE, link, Name, Mod, Args, [get(?WXE_IDENTIFIER)|Options])).
gen_response({ok, Pid}) ->
- receive {ack, Pid, Ref = #wx_ref{}} -> Ref end;
+ receive {started, Pid, Ref = #wx_ref{}} -> Ref end;
gen_response(Reply) ->
Reply.
@@ -407,30 +407,23 @@ init_it(Starter, Parent, Name, Mod, Args, [WxEnv|Options]) ->
{#wx_ref{} = Ref, State, Timeout} ->
init_it2(Ref, Starter, Parent, Name, State, Mod, Timeout, Debug);
{stop, Reason} ->
- proc_lib:init_ack(Starter, {error, Reason}),
exit(Reason);
ignore ->
- proc_lib:init_ack(Starter, ignore),
- exit(normal);
+ proc_lib:init_fail(Starter, ignore, {exit, normal});
{'EXIT', Reason} ->
- proc_lib:init_ack(Starter, {error, Reason}),
exit(Reason);
Else ->
- Error = {bad_return_value, Else},
- proc_lib:init_ack(Starter, {error, Error}),
- exit(Error)
+ exit({bad_return_value, Else})
end.
%% @hidden
init_it2(Ref, Starter, Parent, Name, State, Mod, Timeout, Debug) ->
ok = wxe_util:register_pid(Ref),
case ?CLASS_T(Ref#wx_ref.type, wxWindow) of
false ->
- Reason = {Ref, "not a wxWindow subclass"},
- proc_lib:init_ack(Starter, {error, Reason}),
- exit(Reason);
+ exit({Ref, "not a wxWindow subclass"});
true ->
proc_lib:init_ack(Starter, {ok, self()}),
- proc_lib:init_ack(Starter, Ref#wx_ref{state=self()}),
+ Starter ! {started, self(), Ref#wx_ref{state=self()}},
loop(Parent, Name, State, Mod, Timeout, Debug)
end.
diff --git a/make/app_targets.mk b/make/app_targets.mk
index 2bf1421f80..64a5c47dfa 100644
--- a/make/app_targets.mk
+++ b/make/app_targets.mk
@@ -22,9 +22,11 @@ APPLICATION ?= $(basename $(notdir $(PWD)))
.PHONY: test info gclean dialyzer dialyzer_plt dclean
+ifndef NO_TEST_TARGET
test:
TEST_NEEDS_RELEASE=$(TEST_NEEDS_RELEASE) TYPE=$(TYPE) \
$(ERL_TOP)/make/test_target_script.sh $(ERL_TOP)
+endif
info:
@echo "$(APPLICATION)_VSN: $(VSN)"
diff --git a/make/autoconf/otp.m4 b/make/autoconf/otp.m4
index a522fae3bd..f543101599 100644
--- a/make/autoconf/otp.m4
+++ b/make/autoconf/otp.m4
@@ -1517,27 +1517,30 @@ AC_DEFUN(ETHR_CHK_GCC_ATOMIC_OPS,
AC_DEFUN(ETHR_CHK_INTERLOCKED,
[
ilckd="$1"
- AC_MSG_CHECKING([for ${ilckd}()])
case "$2" in
"1") ilckd_call="${ilckd}(var);";;
"2") ilckd_call="${ilckd}(var, ($3) 0);";;
"3") ilckd_call="${ilckd}(var, ($3) 0, ($3) 0);";;
"4") ilckd_call="${ilckd}(var, ($3) 0, ($3) 0, arr);";;
esac
- have_interlocked_op=no
- AC_LINK_IFELSE([AC_LANG_PROGRAM([[
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <intrin.h>
- ]], [[
- volatile $3 *var;
- volatile $3 arr[2];
-
- $ilckd_call
- return 0;
- ]])],[have_interlocked_op=yes],[])
- test $have_interlocked_op = yes && $4
- AC_MSG_RESULT([$have_interlocked_op])
+ AC_CACHE_CHECK([for ${ilckd}()],ethr_cv_have_$1,
+ [ethr_cv_have_$1=no
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <intrin.h>
+ ]], [[
+ volatile $3 *var;
+ volatile $3 arr[2];
+
+ $ilckd_call
+ return 0;
+ ]])],[ethr_cv_have_$1=yes],[])])
+ if [[ "${ethr_cv_have_$1}" = "yes" ]]; then
+ $4
+ else
+ m4_default([$5], [:])
+ fi
])
dnl ----------------------------------------------------------------------
@@ -1724,13 +1727,15 @@ AS_CASE(
ETHR_CHK_INTERLOCKED([_InterlockedAnd], [2], [long], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDAND, 1, [Define if you have _InterlockedAnd()]))
ETHR_CHK_INTERLOCKED([_InterlockedOr], [2], [long], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDOR, 1, [Define if you have _InterlockedOr()]))
ETHR_CHK_INTERLOCKED([_InterlockedExchange], [2], [long], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDEXCHANGE, 1, [Define if you have _InterlockedExchange()]))
- ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange], [3], [long], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE, 1, [Define if you have _InterlockedCompareExchange()]))
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
- ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange_acq], [3], [long], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE_ACQ, 1, [Define if you have _InterlockedCompareExchange_acq()]))
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
- ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange_rel], [3], [long], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE_REL, 1, [Define if you have _InterlockedCompareExchange_rel()]))
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
-
+ ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange], [3], [long],
+ [AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE, 1, [Define if you have _InterlockedCompareExchange()])
+ ethr_have_native_atomics=yes])
+ ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange_acq], [3], [long],
+ [AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE_ACQ, 1, [Define if you have _InterlockedCompareExchange_acq()])
+ ethr_have_native_atomics=yes])
+ ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange_rel], [3], [long],
+ [AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE_REL, 1, [Define if you have _InterlockedCompareExchange_rel()])
+ ethr_have_native_atomics=yes])
ETHR_CHK_INTERLOCKED([_InterlockedDecrement64], [1], [__int64], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDDECREMENT64, 1, [Define if you have _InterlockedDecrement64()]))
ETHR_CHK_INTERLOCKED([_InterlockedDecrement64_rel], [1], [__int64], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDDECREMENT64_REL, 1, [Define if you have _InterlockedDecrement64_rel()]))
ETHR_CHK_INTERLOCKED([_InterlockedIncrement64], [1], [__int64], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDINCREMENT64, 1, [Define if you have _InterlockedIncrement64()]))
@@ -1740,13 +1745,15 @@ AS_CASE(
ETHR_CHK_INTERLOCKED([_InterlockedAnd64], [2], [__int64], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDAND64, 1, [Define if you have _InterlockedAnd64()]))
ETHR_CHK_INTERLOCKED([_InterlockedOr64], [2], [__int64], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDOR64, 1, [Define if you have _InterlockedOr64()]))
ETHR_CHK_INTERLOCKED([_InterlockedExchange64], [2], [__int64], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDEXCHANGE64, 1, [Define if you have _InterlockedExchange64()]))
- ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange64], [3], [__int64], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE64, 1, [Define if you have _InterlockedCompareExchange64()]))
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
- ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange64_acq], [3], [__int64], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE64_ACQ, 1, [Define if you have _InterlockedCompareExchange64_acq()]))
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
- ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange64_rel], [3], [__int64], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE64_REL, 1, [Define if you have _InterlockedCompareExchange64_rel()]))
- test "$have_interlocked_op" = "yes" && ethr_have_native_atomics=yes
-
+ ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange64], [3], [__int64],
+ [AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE64, 1, [Define if you have _InterlockedCompareExchange64()])
+ ethr_have_native_atomics=yes])
+ ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange64_acq], [3], [__int64],
+ [AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE64_ACQ, 1, [Define if you have _InterlockedCompareExchange64_acq()])
+ ethr_have_native_atomics=yes])
+ ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange64_rel], [3], [__int64],
+ [AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE64_REL, 1, [Define if you have _InterlockedCompareExchange64_rel()])
+ ethr_have_native_atomics=yes])
ETHR_CHK_INTERLOCKED([_InterlockedCompareExchange128], [4], [__int64], AC_DEFINE_UNQUOTED(ETHR_HAVE__INTERLOCKEDCOMPAREEXCHANGE128, 1, [Define if you have _InterlockedCompareExchange128()]))
fi
if test "$ethr_have_native_atomics" = "yes"; then
diff --git a/make/autoconf/win32.config.cache.static b/make/autoconf/win32.config.cache.static
index 790ea9ab00..8aa795cdc0 100755
--- a/make/autoconf/win32.config.cache.static
+++ b/make/autoconf/win32.config.cache.static
@@ -11,9 +11,12 @@
# loading this file, other *unset* `ac_cv_foo' will be assigned the
# following values.
+# ac_cv_build=${ac_cv_build=local-x86-pc-windows}
ac_cv_c_bigendian=${ac_cv_c_bigendian=no}
ac_cv_c_compiler_gnu=${ac_cv_c_compiler_gnu=no}
ac_cv_c_const=${ac_cv_c_const=yes}
+# ac_cv_c_double_middle_endian=${ac_cv_c_double_middle_endian=no}
+ac_cv_c_undeclared_builtin_options=${ac_cv_c_undeclared_builtin_options='none needed'}
ac_cv_cxx_compiler_gnu=${ac_cv_cxx_compiler_gnu=no}
ac_cv_decl_h_errno=${ac_cv_decl_h_errno=no}
ac_cv_decl_inaddr_loopback=${ac_cv_decl_inaddr_loopback=no}
@@ -21,10 +24,16 @@ ac_cv_decl_inaddr_loopback_rpc=${ac_cv_decl_inaddr_loopback_rpc=no}
ac_cv_decl_inaddr_loopback_winsock2=${ac_cv_decl_inaddr_loopback_winsock2=yes}
ac_cv_decl_so_bsdcompat=${ac_cv_decl_so_bsdcompat=no}
ac_cv_decl_sys_errlist=${ac_cv_decl_sys_errlist=no}
+ac_cv_env_AR_set=set
+ac_cv_env_AR_value=ar.sh
+ac_cv_env_CCC_set=
+ac_cv_env_CCC_value=
ac_cv_env_CC_set=set
ac_cv_env_CC_value=cc.sh
ac_cv_env_CFLAGS_set=
ac_cv_env_CFLAGS_value=
+# ac_cv_env_CFLAG_RUNTIME_LIBRARY_PATH_set=
+# ac_cv_env_CFLAG_RUNTIME_LIBRARY_PATH_value=
ac_cv_env_CPPFLAGS_set=
ac_cv_env_CPPFLAGS_value=
ac_cv_env_CPP_set=
@@ -33,10 +42,70 @@ ac_cv_env_CXXFLAGS_set=
ac_cv_env_CXXFLAGS_value=
ac_cv_env_CXX_set=set
ac_cv_env_CXX_value=cc.sh
-ac_cv_env_LDFLAGS_set=
-ac_cv_env_LDFLAGS_value=
+# ac_cv_env_DED_LDFLAGS_set=
+# ac_cv_env_DED_LDFLAGS_value=
+# ac_cv_env_DED_LD_FLAG_RUNTIME_LIBRARY_PATH_set=
+# ac_cv_env_DED_LD_FLAG_RUNTIME_LIBRARY_PATH_value=
+# ac_cv_env_DED_LD_set=
+# ac_cv_env_DED_LD_value=
+# ac_cv_env_ERL_TOP_set=set
+# ac_cv_env_ERL_TOP_value=$ERL_TOP
+# ac_cv_env_GETCONF_set=
+# ac_cv_env_GETCONF_value=
+# ac_cv_env_LDFLAGS_set=
+# ac_cv_env_LDFLAGS_value=
+# ac_cv_env_LD_set=
+# ac_cv_env_LD_value=
+# ac_cv_env_LFS_CFLAGS_set=
+# ac_cv_env_LFS_CFLAGS_value=
+# ac_cv_env_LFS_LDFLAGS_set=
+# ac_cv_env_LFS_LDFLAGS_value=
+# ac_cv_env_LFS_LIBS_set=
+# ac_cv_env_LFS_LIBS_value=
+# ac_cv_env_LIBS_set=
+# ac_cv_env_LIBS_value=
+# ac_cv_env_RANLIB_set=set
+# ac_cv_env_RANLIB_value=true
+# ac_cv_env_STATIC_CFLAGS_set=
+# ac_cv_env_STATIC_CFLAGS_value=
+# ac_cv_env_YACC_set=
+# ac_cv_env_YACC_value=
+# ac_cv_env_YFLAGS_set=
+# ac_cv_env_YFLAGS_value=
ac_cv_env_build_alias_set=set
ac_cv_env_build_alias_value=local-x86-pc-windows
+# ac_cv_env_erl_xcomp_after_morecore_hook_set=
+# ac_cv_env_erl_xcomp_after_morecore_hook_value=
+# ac_cv_env_erl_xcomp_bigendian_set=
+# ac_cv_env_erl_xcomp_bigendian_value=
+# ac_cv_env_erl_xcomp_clock_gettime_cpu_time_set=
+# ac_cv_env_erl_xcomp_clock_gettime_cpu_time_value=
+# ac_cv_env_erl_xcomp_dlsym_brk_wrappers_set=
+# ac_cv_env_erl_xcomp_dlsym_brk_wrappers_value=
+# ac_cv_env_erl_xcomp_double_middle_endian_set=
+# ac_cv_env_erl_xcomp_double_middle_endian_value=
+# ac_cv_env_erl_xcomp_getaddrinfo_set=
+# ac_cv_env_erl_xcomp_getaddrinfo_value=
+# ac_cv_env_erl_xcomp_gethrvtime_procfs_ioctl_set=
+# ac_cv_env_erl_xcomp_gethrvtime_procfs_ioctl_value=
+# ac_cv_env_erl_xcomp_isysroot_set=
+# ac_cv_env_erl_xcomp_isysroot_value=
+# ac_cv_env_erl_xcomp_kqueue_set=
+# ac_cv_env_erl_xcomp_kqueue_value=
+# ac_cv_env_erl_xcomp_linux_nptl_set=
+# ac_cv_env_erl_xcomp_linux_nptl_value=
+# ac_cv_env_erl_xcomp_linux_usable_sigaltstack_set=
+# ac_cv_env_erl_xcomp_linux_usable_sigaltstack_value=
+# ac_cv_env_erl_xcomp_linux_usable_sigusrx_set=
+# ac_cv_env_erl_xcomp_linux_usable_sigusrx_value=
+# ac_cv_env_erl_xcomp_poll_set=
+# ac_cv_env_erl_xcomp_poll_value=
+# ac_cv_env_erl_xcomp_putenv_copy_set=
+# ac_cv_env_erl_xcomp_putenv_copy_value=
+# ac_cv_env_erl_xcomp_reliable_fpe_set=
+# ac_cv_env_erl_xcomp_reliable_fpe_value=
+# ac_cv_env_erl_xcomp_sysroot_set=
+# ac_cv_env_erl_xcomp_sysroot_value=
ac_cv_env_host_alias_set=set
ac_cv_env_host_alias_value=local-x86-pc-windows
ac_cv_env_target_alias_set=set
@@ -47,91 +116,110 @@ ac_cv_func___sbrk=${ac_cv_func___sbrk=no}
ac_cv_func__brk=${ac_cv_func__brk=no}
ac_cv_func__doprnt=${ac_cv_func__doprnt=no}
ac_cv_func__sbrk=${ac_cv_func__sbrk=no}
-ac_cv_func_accept=${ac_cv_func_accept=no}
-ac_cv_func_alloca_works=${ac_cv_func_alloca_works=yes}
ac_cv_func_brk=${ac_cv_func_brk=no}
-ac_cv_func_clock_gettime=${ac_cv_func_clock_gettime=no}
+ac_cv_func_clock_get_attributes=${ac_cv_func_clock_get_attributes=no}
+ac_cv_func_clock_getres=${ac_cv_func_clock_getres=no}
+ac_cv_func_closefrom=${ac_cv_func_closefrom=no}
ac_cv_func_connect=${ac_cv_func_connect=no}
ac_cv_func_decl_fread=${ac_cv_func_decl_fread=no}
ac_cv_func_dlopen=${ac_cv_func_dlopen=no}
-ac_cv_func_dup2=${ac_cv_func_dup2=yes}
+ac_cv_func_dlvsym=${ac_cv_func_dlvsym=no}
+ac_cv_func_endprotoent=${ac_cv_func_endprotoent=no}
+ac_cv_func_fdatasync=${ac_cv_func_fdatasync=no}
ac_cv_func_finite=${ac_cv_func_finite=no}
ac_cv_func_flockfile=${ac_cv_func_flockfile=no}
-ac_cv_func_fork=${ac_cv_func_fork=no}
-ac_cv_func_fork_works=${ac_cv_func_fork_works=no}
ac_cv_func_fpsetmask=${ac_cv_func_fpsetmask=no}
ac_cv_func_fstat=${ac_cv_func_fstat=yes}
-ac_cv_func_gethostbyaddr=${ac_cv_func_gethostbyaddr=no}
-ac_cv_func_gethostbyaddr_r=${ac_cv_func_gethostbyaddr_r=no}
-ac_cv_func_gethostbyname=${ac_cv_func_gethostbyname=no}
ac_cv_func_gethostbyname2=${ac_cv_func_gethostbyname2=no}
+ac_cv_func_gethostbyname=${ac_cv_func_gethostbyname=no}
ac_cv_func_gethostbyname_r=${ac_cv_func_gethostbyname_r=no}
ac_cv_func_gethostname=${ac_cv_func_gethostname=no}
ac_cv_func_gethrtime=${ac_cv_func_gethrtime=no}
+ac_cv_func_getifaddrs=${ac_cv_func_getifaddrs=no}
ac_cv_func_getipnodebyaddr=${ac_cv_func_getipnodebyaddr=no}
ac_cv_func_getipnodebyname=${ac_cv_func_getipnodebyname=no}
-ac_cv_func_getpagesize=${ac_cv_func_getpagesize=no}
+ac_cv_func_getprotoent=${ac_cv_func_getprotoent=no}
+ac_cv_func_getrusage=${ac_cv_func_getrusage=no}
ac_cv_func_gettimeofday=${ac_cv_func_gettimeofday=no}
ac_cv_func_gmtime_r=${ac_cv_func_gmtime_r=no}
ac_cv_func_ieee_handler=${ac_cv_func_ieee_handler=no}
-ac_cv_func_inet_ntoa=${ac_cv_func_inet_ntoa=no}
+ac_cv_func_if_freenameindex=${ac_cv_func_if_freenameindex=no}
+ac_cv_func_if_indextoname=${ac_cv_func_if_indextoname=no}
+ac_cv_func_if_nameindex=${ac_cv_func_if_nameindex=no}
+ac_cv_func_if_nametoindex=${ac_cv_func_if_nametoindex=no}
ac_cv_func_isinf=${ac_cv_func_isinf=no}
ac_cv_func_isnan=${ac_cv_func_isnan=no}
ac_cv_func_localtime_r=${ac_cv_func_localtime_r=no}
+ac_cv_func_log2=${ac_cv_func_log2=yes}
+ac_cv_func_madvise=${ac_cv_func_madvise=no}
ac_cv_func_mallopt=${ac_cv_func_mallopt=no}
-ac_cv_func_memchr=${ac_cv_func_memchr=yes}
-ac_cv_func_memcmp_working=${ac_cv_func_memcmp_working=yes}
-ac_cv_func_memcpy=${ac_cv_func_memcpy=yes}
-ac_cv_func_memmove=${ac_cv_func_memmove=yes}
-ac_cv_func_memset=${ac_cv_func_memset=yes}
-ac_cv_func_mmap_fixed_mapped=${ac_cv_func_mmap_fixed_mapped=no}
+ac_cv_func_memcpy=${ac_cv_func_memcpy=no}
+ac_cv_func_memmove=${ac_cv_func_memmove=no}
+ac_cv_func_mlockall=${ac_cv_func_mlockall=no}
+ac_cv_func_mmap=${ac_cv_func_mmap=no}
+ac_cv_func_mprotect=${ac_cv_func_mprotect=no}
ac_cv_func_mremap=${ac_cv_func_mremap=no}
ac_cv_func_nl_langinfo=${ac_cv_func_nl_langinfo=no}
ac_cv_func_openpty=${ac_cv_func_openpty=no}
+ac_cv_func_poll=${ac_cv_func_poll=no}
ac_cv_func_posix2time=${ac_cv_func_posix2time=no}
+ac_cv_func_posix_fadvise=${ac_cv_func_posix_fadvise=no}
+ac_cv_func_posix_madvise=${ac_cv_func_posix_madvise=no}
+ac_cv_func_posix_memalign=${ac_cv_func_posix_memalign=no}
+ac_cv_func_ppoll=${ac_cv_func_ppoll=no}
ac_cv_func_pread=${ac_cv_func_pread=no}
ac_cv_func_pwrite=${ac_cv_func_pwrite=no}
ac_cv_func_res_gethostbyname=${ac_cv_func_res_gethostbyname=no}
ac_cv_func_sbrk=${ac_cv_func_sbrk=no}
-ac_cv_func_select=${ac_cv_func_select=no}
ac_cv_func_setlocale=${ac_cv_func_setlocale=yes}
+ac_cv_func_setns=${ac_cv_func_setns=no}
+ac_cv_func_setprotoent=${ac_cv_func_setprotoent=no}
ac_cv_func_setsid=${ac_cv_func_setsid=no}
-ac_cv_func_socket=${ac_cv_func_socket=no}
-ac_cv_func_strchr=${ac_cv_func_strchr=yes}
ac_cv_func_strerror=${ac_cv_func_strerror=yes}
ac_cv_func_strerror_r=${ac_cv_func_strerror_r=no}
+ac_cv_func_strftime=${ac_cv_func_strftime=yes}
ac_cv_func_strlcat=${ac_cv_func_strlcat=no}
ac_cv_func_strlcpy=${ac_cv_func_strlcpy=no}
ac_cv_func_strncasecmp=${ac_cv_func_strncasecmp=no}
-ac_cv_func_strrchr=${ac_cv_func_strrchr=yes}
-ac_cv_func_strstr=${ac_cv_func_strstr=yes}
-ac_cv_func_uname=${ac_cv_func_uname=no}
-ac_cv_func_vfork=${ac_cv_func_vfork=no}
-ac_cv_func_vfork_works=${ac_cv_func_vfork_works=no}
-ac_cv_func_vprintf=${ac_cv_func_vprintf=yes}
+ac_cv_func_time2posix=${ac_cv_func_time2posix=no}
+ac_cv_func_vprintf=${ac_cv_func_vprintf=no}
+ac_cv_func_vsyslog=${ac_cv_func_vsyslog=no}
ac_cv_func_writev=${ac_cv_func_writev=no}
-ac_cv_header_arpa_inet_h=${ac_cv_header_arpa_inet_h=no}
+ac_cv_have_decl_IN6ADDR_ANY_INIT=${ac_cv_have_decl_IN6ADDR_ANY_INIT=no}
+ac_cv_have_decl_IN6ADDR_LOOPBACK_INIT=${ac_cv_have_decl_IN6ADDR_LOOPBACK_INIT=no}
+ac_cv_have_decl_IPV6_V6ONLY=${ac_cv_have_decl_IPV6_V6ONLY=no}
+ac_cv_have_decl_posix2time=${ac_cv_have_decl_posix2time=no}
+ac_cv_have_decl_time2posix=${ac_cv_have_decl_time2posix=no}
ac_cv_header_arpa_nameser_h=${ac_cv_header_arpa_nameser_h=no}
ac_cv_header_dirent_dirent_h=${ac_cv_header_dirent_dirent_h=no}
ac_cv_header_dirent_ndir_h=${ac_cv_header_dirent_ndir_h=no}
ac_cv_header_dirent_sys_dir_h=${ac_cv_header_dirent_sys_dir_h=no}
ac_cv_header_dirent_sys_ndir_h=${ac_cv_header_dirent_sys_ndir_h=no}
ac_cv_header_dlfcn_h=${ac_cv_header_dlfcn_h=no}
+ac_cv_header_elf_h=${ac_cv_header_elf_h=no}
ac_cv_header_fcntl_h=${ac_cv_header_fcntl_h=yes}
-ac_cv_header_gl_gl_h=${ac_cv_header_gl_gl_h=yes}
ac_cv_header_ieeefp_h=${ac_cv_header_ieeefp_h=no}
-ac_cv_header_inttypes_h=${ac_cv_header_inttypes_h=no}
+ac_cv_header_ifaddrs_h=${ac_cv_header_ifaddrs_h=no}
+ac_cv_header_inttypes_h=${ac_cv_header_inttypes_h=yes}
ac_cv_header_langinfo_h=${ac_cv_header_langinfo_h=no}
+ac_cv_header_libdlpi_h=${ac_cv_header_libdlpi_h=no}
+ac_cv_header_libutil_h=${ac_cv_header_libutil_h=no}
ac_cv_header_limits_h=${ac_cv_header_limits_h=yes}
+ac_cv_header_linux_errqueue_h=${ac_cv_header_linux_errqueue_h=no}
+ac_cv_header_linux_falloc_h=${ac_cv_header_linux_falloc_h=no}
+ac_cv_header_linux_types_h=${ac_cv_header_linux_types_h=no}
ac_cv_header_malloc_h=${ac_cv_header_malloc_h=yes}
-ac_cv_header_memory_h=${ac_cv_header_memory_h=yes}
ac_cv_header_net_errno_h=${ac_cv_header_net_errno_h=no}
-ac_cv_header_netdb_h=${ac_cv_header_netdb_h=no}
-ac_cv_header_netinet_in_h=${ac_cv_header_netinet_in_h=no}
+ac_cv_header_net_if_dl_h=${ac_cv_header_net_if_dl_h=no}
+ac_cv_header_netinet_sctp_h=${ac_cv_header_netinet_sctp_h=no}
+ac_cv_header_netpacket_packet_h=${ac_cv_header_netpacket_packet_h=no}
+ac_cv_header_poll_h=${ac_cv_header_poll_h=no}
ac_cv_header_pty_h=${ac_cv_header_pty_h=no}
-ac_cv_header_stdc=${ac_cv_header_stdc=yes}
-ac_cv_header_stddef_h=${ac_cv_header_stddef_h=yes}
-ac_cv_header_stdint_h=${ac_cv_header_stdint_h=no}
+ac_cv_header_sched_h=${ac_cv_header_sched_h=no}
+ac_cv_header_sdkddkver_h=${ac_cv_header_sdkddkver_h=yes}
+ac_cv_header_setns_h=${ac_cv_header_setns_h=no}
+ac_cv_header_stdint_h=${ac_cv_header_stdint_h=yes}
+ac_cv_header_stdio_h=${ac_cv_header_stdio_h=yes}
ac_cv_header_stdlib_h=${ac_cv_header_stdlib_h=yes}
ac_cv_header_string_h=${ac_cv_header_string_h=yes}
ac_cv_header_strings_h=${ac_cv_header_strings_h=no}
@@ -139,8 +227,8 @@ ac_cv_header_sys_devpoll_h=${ac_cv_header_sys_devpoll_h=no}
ac_cv_header_sys_epoll_h=${ac_cv_header_sys_epoll_h=no}
ac_cv_header_sys_event_h=${ac_cv_header_sys_event_h=no}
ac_cv_header_sys_ioctl_h=${ac_cv_header_sys_ioctl_h=no}
-ac_cv_header_sys_param_h=${ac_cv_header_sys_param_h=no}
-ac_cv_header_sys_select_h=${ac_cv_header_sys_select_h=no}
+ac_cv_header_sys_mman_h=${ac_cv_header_sys_mman_h=no}
+ac_cv_header_sys_resource_h=${ac_cv_header_sys_resource_h=no}
ac_cv_header_sys_socket_h=${ac_cv_header_sys_socket_h=no}
ac_cv_header_sys_socketio_h=${ac_cv_header_sys_socketio_h=no}
ac_cv_header_sys_sockio_h=${ac_cv_header_sys_sockio_h=no}
@@ -148,55 +236,67 @@ ac_cv_header_sys_stat_h=${ac_cv_header_sys_stat_h=yes}
ac_cv_header_sys_stropts_h=${ac_cv_header_sys_stropts_h=no}
ac_cv_header_sys_sysctl_h=${ac_cv_header_sys_sysctl_h=no}
ac_cv_header_sys_time_h=${ac_cv_header_sys_time_h=no}
+ac_cv_header_sys_timerfd_h=${ac_cv_header_sys_timerfd_h=no}
ac_cv_header_sys_types_h=${ac_cv_header_sys_types_h=yes}
ac_cv_header_sys_uio_h=${ac_cv_header_sys_uio_h=no}
+ac_cv_header_sys_un_h=${ac_cv_header_sys_un_h=no}
ac_cv_header_sys_wait_h=${ac_cv_header_sys_wait_h=no}
ac_cv_header_syslog_h=${ac_cv_header_syslog_h=no}
-ac_cv_header_time=${ac_cv_header_time=no}
ac_cv_header_unistd_h=${ac_cv_header_unistd_h=no}
ac_cv_header_util_h=${ac_cv_header_util_h=no}
ac_cv_header_utmp_h=${ac_cv_header_utmp_h=no}
ac_cv_header_valgrind_valgrind_h=${ac_cv_header_valgrind_valgrind_h=no}
-ac_cv_header_vfork_h=${ac_cv_header_vfork_h=no}
+ac_cv_header_windows_h=${ac_cv_header_windows_h=yes}
+ac_cv_header_winsock2_h=${ac_cv_header_winsock2_h=yes}
+ac_cv_header_ws2tcpip_h=${ac_cv_header_ws2tcpip_h=yes}
+ac_cv_host=${ac_cv_host=local-x86-pc-windows}
ac_cv_lib_dl_dlopen=${ac_cv_lib_dl_dlopen=no}
+ac_cv_lib_dl_dlvsym=${ac_cv_lib_dl_dlvsym=no}
+ac_cv_lib_dlpi_dlpi_open=${ac_cv_lib_dlpi_dlpi_open=no}
ac_cv_lib_inet_main=${ac_cv_lib_inet_main=no}
ac_cv_lib_kstat_kstat_open=${ac_cv_lib_kstat_kstat_open=no}
+ac_cv_lib_kvm_kvm_open=${ac_cv_lib_kvm_kvm_open=no}
ac_cv_lib_m_sin=${ac_cv_lib_m_sin=no}
-ac_cv_lib_nsl_gethostbyname=${ac_cv_lib_nsl_gethostbyname=no}
ac_cv_lib_nsl_main=${ac_cv_lib_nsl_main=no}
-ac_cv_lib_resolv_res_gethostbyname=${ac_cv_lib_resolv_res_gethostbyname=no}
ac_cv_lib_rt_clock_gettime=${ac_cv_lib_rt_clock_gettime=no}
-ac_cv_lib_socket_getpeername=${ac_cv_lib_socket_getpeername=no}
ac_cv_lib_socket_main=${ac_cv_lib_socket_main=yes}
-ac_cv_lib_socket_socket=${ac_cv_lib_socket_socket=no}
ac_cv_lib_util_openpty=${ac_cv_lib_util_openpty=no}
-ac_cv_lib_ws2_32_main=${ac_cv_lib_ws2_32_main=yes}
-ac_cv_member_struct_ErlDrvEntry_stop_select=${ac_cv_member_struct_ErlDrvEntry_stop_select=no}
+ac_cv_member_struct_ifreq_ifr_enaddr=${ac_cv_member_struct_ifreq_ifr_enaddr=no}
+ac_cv_member_struct_ifreq_ifr_hwaddr=${ac_cv_member_struct_ifreq_ifr_hwaddr=no}
+ac_cv_member_struct_ifreq_ifr_ifindex=${ac_cv_member_struct_ifreq_ifr_ifindex=no}
+ac_cv_member_struct_ifreq_ifr_index=${ac_cv_member_struct_ifreq_ifr_index=no}
+ac_cv_member_struct_ifreq_ifr_map=${ac_cv_member_struct_ifreq_ifr_map=no}
+ac_cv_member_struct_sockaddr_dl_sdl_len=${ac_cv_member_struct_sockaddr_dl_sdl_len=no}
+ac_cv_member_struct_sockaddr_un_sun_path=${ac_cv_member_struct_sockaddr_un_sun_path=no}
ac_cv_objext=${ac_cv_objext=o}
-ac_cv_path_MKDIR=${ac_cv_path_MKDIR=/bin/mkdir}
-ac_cv_path_PERL=${ac_cv_path_PERL=/usr/bin/perl}
-ac_cv_path_RM=${ac_cv_path_RM=/bin/rm}
-ac_cv_path_install=${ac_cv_path_install='/usr/bin/install -c'}
+# ac_cv_path_CP=${ac_cv_path_CP=/bin/cp}
+# ac_cv_path_EGREP=${ac_cv_path_EGREP='/usr/bin/grep -E'}
+# ac_cv_path_GREP=${ac_cv_path_GREP=/usr/bin/grep}
+# ac_cv_path_MKDIR=${ac_cv_path_MKDIR=/bin/mkdir}
+# ac_cv_path_PERL=${ac_cv_path_PERL=/usr/bin/perl}
+# ac_cv_path_install=${ac_cv_path_install='/usr/bin/install -c'}
ac_cv_prog_AR=${ac_cv_prog_AR=ar.sh}
ac_cv_prog_CC=${ac_cv_prog_CC=cc.sh}
ac_cv_prog_CPP=${ac_cv_prog_CPP='cc.sh -E'}
-ac_cv_prog_CXX=${ac_cv_prog_CXX=cc.sh}
-ac_cv_prog_DED_LD=${ac_cv_prog_DED_LD=ld.sh}
-ac_cv_prog_ac_ct_DED_LD=${ac_cv_prog_ac_ct_DED_LD=ld.sh}
-ac_cv_prog_M4=${ac_cv_prog_M4=m4}
-ac_cv_prog_PERL=${ac_cv_prog_PERL=perl}
+ac_cv_prog_GETCONF=${ac_cv_prog_GETCONF=getconf}
+ac_cv_prog_JAVAC=${ac_cv_prog_JAVAC=javac.sh}
ac_cv_prog_RANLIB=${ac_cv_prog_RANLIB=true}
-ac_cv_prog_LD=${ac_cv_prog_LD=ld.sh}
-ac_cv_prog_ac_ct_LD=${ac_cv_prog_ac_ct_LD=ld.sh}
+ac_cv_prog_cc_c11=${ac_cv_prog_cc_c11=no}
+ac_cv_prog_cc_c89=${ac_cv_prog_cc_c89=no}
+ac_cv_prog_cc_c99=${ac_cv_prog_cc_c99=no}
ac_cv_prog_cc_g=${ac_cv_prog_cc_g=yes}
-ac_cv_prog_cc_stdc=${ac_cv_prog_cc_stdc=}
-ac_cv_prog_cxx_g=${ac_cv_prog_cxx_g=no}
-ac_cv_prog_egrep=${ac_cv_prog_egrep='grep -E'}
-ac_cv_prog_emu_cc=${ac_cv_prog_emu_cc=emu_cc.sh}
-ac_cv_prog_make_make_set=${ac_cv_prog_make_make_set=yes}
-ac_cv_prog_mkdir_p=${ac_cv_prog_mkdir_p='/usr/bin/install -c -d'}
+ac_cv_prog_cxx_11=${ac_cv_prog_cxx_11=no}
+ac_cv_prog_cxx_g=${ac_cv_prog_cxx_g=yes}
+ac_cv_prog_cxx_stdcxx=${ac_cv_prog_cxx_stdcxx=}
+# ac_cv_prog_emu_cc=${ac_cv_prog_emu_cc=$ERL_TOP/erts/etc/win32/wsl_tools/vc/emu_cc.sh}
+# ac_cv_prog_javac_ver_1_6=${ac_cv_prog_javac_ver_1_6=no}
+# ac_cv_prog_mkdir_p=${ac_cv_prog_mkdir_p='/usr/bin/install -c -d'}
+ac_cv_search_fdatasync=${ac_cv_search_fdatasync=no}
ac_cv_search_opendir=${ac_cv_search_opendir=no}
ac_cv_search_strerror=${ac_cv_search_strerror='none required'}
+ac_cv_sizeof__Float16=${ac_cv_sizeof__Float16=0}
+ac_cv_sizeof___int128_t=${ac_cv_sizeof___int128_t=0}
+ac_cv_sizeof___int64=${ac_cv_sizeof___int64=8}
ac_cv_sizeof_char=${ac_cv_sizeof_char=1}
ac_cv_sizeof_int=${ac_cv_sizeof_int=4}
ac_cv_sizeof_long=${ac_cv_sizeof_long=4}
@@ -204,26 +304,56 @@ ac_cv_sizeof_long_long=${ac_cv_sizeof_long_long=8}
ac_cv_sizeof_off_t=${ac_cv_sizeof_off_t=4}
ac_cv_sizeof_short=${ac_cv_sizeof_short=2}
ac_cv_sizeof_size_t=${ac_cv_sizeof_size_t=4}
+ac_cv_sizeof_suseconds_t=${ac_cv_sizeof_suseconds_t=0}
+ac_cv_sizeof_time_t=${ac_cv_sizeof_time_t=8}
ac_cv_sizeof_void_p=${ac_cv_sizeof_void_p=4}
-ac_cv_struct_exception=${ac_cv_struct_exception=no}
ac_cv_struct_sockaddr_sa_len=${ac_cv_struct_sockaddr_sa_len=no}
ac_cv_struct_tm=${ac_cv_struct_tm=time.h}
+# ac_cv_sys_ipv6_support=${ac_cv_sys_ipv6_support=yes}
ac_cv_sys_multicast_support=${ac_cv_sys_multicast_support=no}
-ac_cv_type_char=${ac_cv_type_char=yes}
-ac_cv_type_int=${ac_cv_type_int=yes}
-ac_cv_type_long=${ac_cv_type_long=yes}
-ac_cv_type_long_long=${ac_cv_type_long_long=yes}
+ac_cv_target=${ac_cv_target=local-x86-pc-windows}
ac_cv_type_off_t=${ac_cv_type_off_t=yes}
ac_cv_type_pid_t=${ac_cv_type_pid_t=no}
-ac_cv_type_short=${ac_cv_type_short=yes}
-ac_cv_type_signal=${ac_cv_type_signal=void}
ac_cv_type_size_t=${ac_cv_type_size_t=yes}
-ac_cv_type_uid_t=${ac_cv_type_uid_t=no}
-ac_cv_type_void_p=${ac_cv_type_void_p=yes}
-ac_cv_working_alloca_h=${ac_cv_working_alloca_h=no}
+erl_cv_clock_gettime_monotonic_default_resolution=${erl_cv_clock_gettime_monotonic_default_resolution=no}
+erl_cv_clock_gettime_monotonic_high_resolution=${erl_cv_clock_gettime_monotonic_high_resolution=no}
+erl_cv_clock_gettime_monotonic_raw=${erl_cv_clock_gettime_monotonic_raw=no}
+erl_cv_clock_gettime_monotonic_try_find_pthread_compatible=${erl_cv_clock_gettime_monotonic_try_find_pthread_compatible=no}
+erl_cv_clock_gettime_wall_default_resolution=${erl_cv_clock_gettime_wall_default_resolution=no}
+erl_cv_mach_clock_get_time_monotonic=${erl_cv_mach_clock_get_time_monotonic=no}
+erl_cv_mach_clock_get_time_wall=${erl_cv_mach_clock_get_time_wall=no}
erts_cv___after_morecore_hook_can_track_malloc=${erts_cv___after_morecore_hook_can_track_malloc=no}
erts_cv_fwrite_unlocked=${erts_cv_fwrite_unlocked=no}
erts_cv_have__end_symbol=${erts_cv_have__end_symbol=no}
erts_cv_have_end_symbol=${erts_cv_have_end_symbol=no}
+erts_cv_have_in6addr_any=${erts_cv_have_in6addr_any=no}
+erts_cv_have_in6addr_loopback=${erts_cv_have_in6addr_loopback=no}
erts_cv_putc_unlocked=${erts_cv_putc_unlocked=no}
erts_cv_windows_h_includes_winsock2_h=${erts_cv_windows_h_includes_winsock2_h=no}
+ethr_cv_have__InterlockedAnd64=${ethr_cv_have__InterlockedAnd64=no}
+ethr_cv_have__InterlockedAnd=${ethr_cv_have__InterlockedAnd=yes}
+ethr_cv_have__InterlockedCompareExchange128=${ethr_cv_have__InterlockedCompareExchange128=no}
+ethr_cv_have__InterlockedCompareExchange64=${ethr_cv_have__InterlockedCompareExchange64=yes}
+# ethr_cv_have__InterlockedCompareExchange64_acq=${ethr_cv_have__InterlockedCompareExchange64_acq=no}
+# ethr_cv_have__InterlockedCompareExchange64_rel=${ethr_cv_have__InterlockedCompareExchange64_rel=no}
+ethr_cv_have__InterlockedCompareExchange=${ethr_cv_have__InterlockedCompareExchange=yes}
+# ethr_cv_have__InterlockedCompareExchange_acq=${ethr_cv_have__InterlockedCompareExchange_acq=no}
+# ethr_cv_have__InterlockedCompareExchange_rel=${ethr_cv_have__InterlockedCompareExchange_rel=no}
+ethr_cv_have__InterlockedDecrement64=${ethr_cv_have__InterlockedDecrement64=no}
+# ethr_cv_have__InterlockedDecrement64_rel=${ethr_cv_have__InterlockedDecrement64_rel=no}
+ethr_cv_have__InterlockedDecrement=${ethr_cv_have__InterlockedDecrement=yes}
+# ethr_cv_have__InterlockedDecrement_rel=${ethr_cv_have__InterlockedDecrement_rel=no}
+ethr_cv_have__InterlockedExchange64=${ethr_cv_have__InterlockedExchange64=no}
+ethr_cv_have__InterlockedExchange=${ethr_cv_have__InterlockedExchange=yes}
+ethr_cv_have__InterlockedExchangeAdd64=${ethr_cv_have__InterlockedExchangeAdd64=no}
+# ethr_cv_have__InterlockedExchangeAdd64_acq=${ethr_cv_have__InterlockedExchangeAdd64_acq=no}
+ethr_cv_have__InterlockedExchangeAdd=${ethr_cv_have__InterlockedExchangeAdd=yes}
+# ethr_cv_have__InterlockedExchangeAdd_acq=${ethr_cv_have__InterlockedExchangeAdd_acq=no}
+ethr_cv_have__InterlockedIncrement64=${ethr_cv_have__InterlockedIncrement64=no}
+# ethr_cv_have__InterlockedIncrement64_acq=${ethr_cv_have__InterlockedIncrement64_acq=no}
+ethr_cv_have__InterlockedIncrement=${ethr_cv_have__InterlockedIncrement=yes}
+# ethr_cv_have__InterlockedIncrement_acq=${ethr_cv_have__InterlockedIncrement_acq=no}
+ethr_cv_have__InterlockedOr64=${ethr_cv_have__InterlockedOr64=no}
+ethr_cv_have__InterlockedOr=${ethr_cv_have__InterlockedOr=yes}
+i_cv_fallocate_works=${i_cv_fallocate_works=no}
+i_cv_posix_fallocate_works=${i_cv_posix_fallocate_works=no}
diff --git a/make/autoconf/win64.config.cache.static b/make/autoconf/win64.config.cache.static
index 73146cbb86..a151aa3452 100755
--- a/make/autoconf/win64.config.cache.static
+++ b/make/autoconf/win64.config.cache.static
@@ -11,9 +11,12 @@
# loading this file, other *unset* `ac_cv_foo' will be assigned the
# following values.
+# ac_cv_build=${ac_cv_build=local-x86_64-pc-windows}
ac_cv_c_bigendian=${ac_cv_c_bigendian=no}
ac_cv_c_compiler_gnu=${ac_cv_c_compiler_gnu=no}
ac_cv_c_const=${ac_cv_c_const=yes}
+# ac_cv_c_double_middle_endian=${ac_cv_c_double_middle_endian=no}
+ac_cv_c_undeclared_builtin_options=${ac_cv_c_undeclared_builtin_options='none needed'}
ac_cv_cxx_compiler_gnu=${ac_cv_cxx_compiler_gnu=no}
ac_cv_decl_h_errno=${ac_cv_decl_h_errno=no}
ac_cv_decl_inaddr_loopback=${ac_cv_decl_inaddr_loopback=no}
@@ -29,6 +32,8 @@ ac_cv_env_CC_set=set
ac_cv_env_CC_value=cc.sh
ac_cv_env_CFLAGS_set=
ac_cv_env_CFLAGS_value=
+# ac_cv_env_CFLAG_RUNTIME_LIBRARY_PATH_set=
+# ac_cv_env_CFLAG_RUNTIME_LIBRARY_PATH_value=
ac_cv_env_CPPFLAGS_set=
ac_cv_env_CPPFLAGS_value=
ac_cv_env_CPP_set=
@@ -37,10 +42,70 @@ ac_cv_env_CXXFLAGS_set=
ac_cv_env_CXXFLAGS_value=
ac_cv_env_CXX_set=set
ac_cv_env_CXX_value=cc.sh
-ac_cv_env_LDFLAGS_set=
-ac_cv_env_LDFLAGS_value=
+# ac_cv_env_DED_LDFLAGS_set=
+# ac_cv_env_DED_LDFLAGS_value=
+# ac_cv_env_DED_LD_FLAG_RUNTIME_LIBRARY_PATH_set=
+# ac_cv_env_DED_LD_FLAG_RUNTIME_LIBRARY_PATH_value=
+# ac_cv_env_DED_LD_set=
+# ac_cv_env_DED_LD_value=
+# ac_cv_env_ERL_TOP_set=set
+# ac_cv_env_ERL_TOP_value=$ERL_TOP
+# ac_cv_env_GETCONF_set=
+# ac_cv_env_GETCONF_value=
+# ac_cv_env_LDFLAGS_set=
+# ac_cv_env_LDFLAGS_value=
+# ac_cv_env_LD_set=
+# ac_cv_env_LD_value=
+# ac_cv_env_LFS_CFLAGS_set=
+# ac_cv_env_LFS_CFLAGS_value=
+# ac_cv_env_LFS_LDFLAGS_set=
+# ac_cv_env_LFS_LDFLAGS_value=
+# ac_cv_env_LFS_LIBS_set=
+# ac_cv_env_LFS_LIBS_value=
+# ac_cv_env_LIBS_set=
+# ac_cv_env_LIBS_value=
+# ac_cv_env_RANLIB_set=set
+# ac_cv_env_RANLIB_value=true
+# ac_cv_env_STATIC_CFLAGS_set=
+# ac_cv_env_STATIC_CFLAGS_value=
+# ac_cv_env_YACC_set=
+# ac_cv_env_YACC_value=
+# ac_cv_env_YFLAGS_set=
+# ac_cv_env_YFLAGS_value=
ac_cv_env_build_alias_set=set
ac_cv_env_build_alias_value=local-x86_64-pc-windows
+# ac_cv_env_erl_xcomp_after_morecore_hook_set=
+# ac_cv_env_erl_xcomp_after_morecore_hook_value=
+# ac_cv_env_erl_xcomp_bigendian_set=
+# ac_cv_env_erl_xcomp_bigendian_value=
+# ac_cv_env_erl_xcomp_clock_gettime_cpu_time_set=
+# ac_cv_env_erl_xcomp_clock_gettime_cpu_time_value=
+# ac_cv_env_erl_xcomp_dlsym_brk_wrappers_set=
+# ac_cv_env_erl_xcomp_dlsym_brk_wrappers_value=
+# ac_cv_env_erl_xcomp_double_middle_endian_set=
+# ac_cv_env_erl_xcomp_double_middle_endian_value=
+# ac_cv_env_erl_xcomp_getaddrinfo_set=
+# ac_cv_env_erl_xcomp_getaddrinfo_value=
+# ac_cv_env_erl_xcomp_gethrvtime_procfs_ioctl_set=
+# ac_cv_env_erl_xcomp_gethrvtime_procfs_ioctl_value=
+# ac_cv_env_erl_xcomp_isysroot_set=
+# ac_cv_env_erl_xcomp_isysroot_value=
+# ac_cv_env_erl_xcomp_kqueue_set=
+# ac_cv_env_erl_xcomp_kqueue_value=
+# ac_cv_env_erl_xcomp_linux_nptl_set=
+# ac_cv_env_erl_xcomp_linux_nptl_value=
+# ac_cv_env_erl_xcomp_linux_usable_sigaltstack_set=
+# ac_cv_env_erl_xcomp_linux_usable_sigaltstack_value=
+# ac_cv_env_erl_xcomp_linux_usable_sigusrx_set=
+# ac_cv_env_erl_xcomp_linux_usable_sigusrx_value=
+# ac_cv_env_erl_xcomp_poll_set=
+# ac_cv_env_erl_xcomp_poll_value=
+# ac_cv_env_erl_xcomp_putenv_copy_set=
+# ac_cv_env_erl_xcomp_putenv_copy_value=
+# ac_cv_env_erl_xcomp_reliable_fpe_set=
+# ac_cv_env_erl_xcomp_reliable_fpe_value=
+# ac_cv_env_erl_xcomp_sysroot_set=
+# ac_cv_env_erl_xcomp_sysroot_value=
ac_cv_env_host_alias_set=set
ac_cv_env_host_alias_value=local-x86_64-pc-windows
ac_cv_env_target_alias_set=set
@@ -51,134 +116,110 @@ ac_cv_func___sbrk=${ac_cv_func___sbrk=no}
ac_cv_func__brk=${ac_cv_func__brk=no}
ac_cv_func__doprnt=${ac_cv_func__doprnt=no}
ac_cv_func__sbrk=${ac_cv_func__sbrk=no}
-ac_cv_func_accept=${ac_cv_func_accept=no}
-ac_cv_func_alloca_works=${ac_cv_func_alloca_works=yes}
ac_cv_func_brk=${ac_cv_func_brk=no}
-ac_cv_func_clock_gettime=${ac_cv_func_clock_gettime=no}
+ac_cv_func_clock_get_attributes=${ac_cv_func_clock_get_attributes=no}
+ac_cv_func_clock_getres=${ac_cv_func_clock_getres=no}
+ac_cv_func_closefrom=${ac_cv_func_closefrom=no}
ac_cv_func_connect=${ac_cv_func_connect=no}
ac_cv_func_decl_fread=${ac_cv_func_decl_fread=yes}
ac_cv_func_dlopen=${ac_cv_func_dlopen=no}
-ac_cv_func_dup2=${ac_cv_func_dup2=yes}
+ac_cv_func_dlvsym=${ac_cv_func_dlvsym=no}
+ac_cv_func_endprotoent=${ac_cv_func_endprotoent=no}
ac_cv_func_fdatasync=${ac_cv_func_fdatasync=no}
ac_cv_func_finite=${ac_cv_func_finite=no}
ac_cv_func_flockfile=${ac_cv_func_flockfile=no}
-ac_cv_func_fork=${ac_cv_func_fork=no}
-ac_cv_func_fork_works=${ac_cv_func_fork_works=no}
ac_cv_func_fpsetmask=${ac_cv_func_fpsetmask=no}
ac_cv_func_fstat=${ac_cv_func_fstat=yes}
-ac_cv_func_gethostbyaddr=${ac_cv_func_gethostbyaddr=no}
-ac_cv_func_gethostbyaddr_r=${ac_cv_func_gethostbyaddr_r=no}
-ac_cv_func_gethostbyname=${ac_cv_func_gethostbyname=yes}
ac_cv_func_gethostbyname2=${ac_cv_func_gethostbyname2=no}
+ac_cv_func_gethostbyname=${ac_cv_func_gethostbyname=yes}
ac_cv_func_gethostbyname_r=${ac_cv_func_gethostbyname_r=no}
ac_cv_func_gethostname=${ac_cv_func_gethostname=no}
ac_cv_func_gethrtime=${ac_cv_func_gethrtime=no}
ac_cv_func_getifaddrs=${ac_cv_func_getifaddrs=no}
ac_cv_func_getipnodebyaddr=${ac_cv_func_getipnodebyaddr=no}
ac_cv_func_getipnodebyname=${ac_cv_func_getipnodebyname=no}
-ac_cv_func_getpagesize=${ac_cv_func_getpagesize=no}
+ac_cv_func_getprotoent=${ac_cv_func_getprotoent=no}
+ac_cv_func_getrusage=${ac_cv_func_getrusage=no}
ac_cv_func_gettimeofday=${ac_cv_func_gettimeofday=no}
ac_cv_func_gmtime_r=${ac_cv_func_gmtime_r=no}
ac_cv_func_ieee_handler=${ac_cv_func_ieee_handler=no}
-ac_cv_func_inet_ntoa=${ac_cv_func_inet_ntoa=no}
-ac_cv_func_inet_pton=${ac_cv_func_inet_pton=yes}
+ac_cv_func_if_freenameindex=${ac_cv_func_if_freenameindex=no}
+ac_cv_func_if_indextoname=${ac_cv_func_if_indextoname=no}
+ac_cv_func_if_nameindex=${ac_cv_func_if_nameindex=no}
+ac_cv_func_if_nametoindex=${ac_cv_func_if_nametoindex=no}
ac_cv_func_isinf=${ac_cv_func_isinf=no}
ac_cv_func_isnan=${ac_cv_func_isnan=no}
ac_cv_func_localtime_r=${ac_cv_func_localtime_r=no}
+ac_cv_func_log2=${ac_cv_func_log2=no}
+ac_cv_func_madvise=${ac_cv_func_madvise=no}
ac_cv_func_mallopt=${ac_cv_func_mallopt=no}
-ac_cv_func_memchr=${ac_cv_func_memchr=yes}
-ac_cv_func_memcmp_working=${ac_cv_func_memcmp_working=yes}
ac_cv_func_memcpy=${ac_cv_func_memcpy=no}
-ac_cv_func_memmove=${ac_cv_func_memmove=yes}
-ac_cv_func_memset=${ac_cv_func_memset=yes}
+ac_cv_func_memmove=${ac_cv_func_memmove=no}
+ac_cv_func_mlockall=${ac_cv_func_mlockall=no}
ac_cv_func_mmap=${ac_cv_func_mmap=no}
-ac_cv_func_mmap_fixed_mapped=${ac_cv_func_mmap_fixed_mapped=no}
+ac_cv_func_mprotect=${ac_cv_func_mprotect=no}
ac_cv_func_mremap=${ac_cv_func_mremap=no}
ac_cv_func_nl_langinfo=${ac_cv_func_nl_langinfo=no}
ac_cv_func_openpty=${ac_cv_func_openpty=no}
ac_cv_func_poll=${ac_cv_func_poll=no}
ac_cv_func_posix2time=${ac_cv_func_posix2time=no}
ac_cv_func_posix_fadvise=${ac_cv_func_posix_fadvise=no}
+ac_cv_func_posix_madvise=${ac_cv_func_posix_madvise=no}
+ac_cv_func_posix_memalign=${ac_cv_func_posix_memalign=no}
+ac_cv_func_ppoll=${ac_cv_func_ppoll=no}
ac_cv_func_pread=${ac_cv_func_pread=no}
ac_cv_func_pwrite=${ac_cv_func_pwrite=no}
ac_cv_func_res_gethostbyname=${ac_cv_func_res_gethostbyname=no}
ac_cv_func_sbrk=${ac_cv_func_sbrk=no}
-ac_cv_func_sctp_bindx=${ac_cv_func_sctp_bindx=no}
-ac_cv_func_sctp_peeloff=${ac_cv_func_sctp_peeloff=no}
-ac_cv_func_select=${ac_cv_func_select=no}
ac_cv_func_setlocale=${ac_cv_func_setlocale=yes}
+ac_cv_func_setns=${ac_cv_func_setns=no}
+ac_cv_func_setprotoent=${ac_cv_func_setprotoent=no}
ac_cv_func_setsid=${ac_cv_func_setsid=no}
-ac_cv_func_setvbuf_reversed=${ac_cv_func_setvbuf_reversed=yes}
-ac_cv_func_socket=${ac_cv_func_socket=no}
-ac_cv_func_strchr=${ac_cv_func_strchr=yes}
ac_cv_func_strerror=${ac_cv_func_strerror=yes}
ac_cv_func_strerror_r=${ac_cv_func_strerror_r=no}
+ac_cv_func_strftime=${ac_cv_func_strftime=yes}
ac_cv_func_strlcat=${ac_cv_func_strlcat=no}
ac_cv_func_strlcpy=${ac_cv_func_strlcpy=no}
ac_cv_func_strncasecmp=${ac_cv_func_strncasecmp=no}
-ac_cv_func_strrchr=${ac_cv_func_strrchr=yes}
-ac_cv_func_strstr=${ac_cv_func_strstr=yes}
-ac_cv_func_uname=${ac_cv_func_uname=no}
-ac_cv_func_vfork=${ac_cv_func_vfork=no}
-ac_cv_func_vfork_works=${ac_cv_func_vfork_works=no}
-ac_cv_func_vprintf=${ac_cv_func_vprintf=yes}
+ac_cv_func_time2posix=${ac_cv_func_time2posix=no}
+ac_cv_func_vprintf=${ac_cv_func_vprintf=no}
+ac_cv_func_vsyslog=${ac_cv_func_vsyslog=no}
ac_cv_func_writev=${ac_cv_func_writev=no}
-ac_cv_have_decl_SCTPS_BOUND=${ac_cv_have_decl_SCTPS_BOUND=no}
-ac_cv_have_decl_SCTPS_COOKIE_ECHOED=${ac_cv_have_decl_SCTPS_COOKIE_ECHOED=no}
-ac_cv_have_decl_SCTPS_COOKIE_WAIT=${ac_cv_have_decl_SCTPS_COOKIE_WAIT=no}
-ac_cv_have_decl_SCTPS_ESTABLISHED=${ac_cv_have_decl_SCTPS_ESTABLISHED=no}
-ac_cv_have_decl_SCTPS_IDLE=${ac_cv_have_decl_SCTPS_IDLE=no}
-ac_cv_have_decl_SCTPS_LISTEN=${ac_cv_have_decl_SCTPS_LISTEN=no}
-ac_cv_have_decl_SCTPS_SHUTDOWN_ACK_SENT=${ac_cv_have_decl_SCTPS_SHUTDOWN_ACK_SENT=no}
-ac_cv_have_decl_SCTPS_SHUTDOWN_PENDING=${ac_cv_have_decl_SCTPS_SHUTDOWN_PENDING=no}
-ac_cv_have_decl_SCTPS_SHUTDOWN_RECEIVED=${ac_cv_have_decl_SCTPS_SHUTDOWN_RECEIVED=no}
-ac_cv_have_decl_SCTPS_SHUTDOWN_SENT=${ac_cv_have_decl_SCTPS_SHUTDOWN_SENT=no}
-ac_cv_have_decl_SCTP_ABORT=${ac_cv_have_decl_SCTP_ABORT=no}
-ac_cv_have_decl_SCTP_ADDR_CONFIRMED=${ac_cv_have_decl_SCTP_ADDR_CONFIRMED=no}
-ac_cv_have_decl_SCTP_ADDR_OVER=${ac_cv_have_decl_SCTP_ADDR_OVER=no}
-ac_cv_have_decl_SCTP_BOUND=${ac_cv_have_decl_SCTP_BOUND=no}
-ac_cv_have_decl_SCTP_CLOSED=${ac_cv_have_decl_SCTP_CLOSED=no}
-ac_cv_have_decl_SCTP_COOKIE_ECHOED=${ac_cv_have_decl_SCTP_COOKIE_ECHOED=no}
-ac_cv_have_decl_SCTP_COOKIE_WAIT=${ac_cv_have_decl_SCTP_COOKIE_WAIT=no}
-ac_cv_have_decl_SCTP_DELAYED_ACK_TIME=${ac_cv_have_decl_SCTP_DELAYED_ACK_TIME=no}
-ac_cv_have_decl_SCTP_EMPTY=${ac_cv_have_decl_SCTP_EMPTY=no}
-ac_cv_have_decl_SCTP_EOF=${ac_cv_have_decl_SCTP_EOF=no}
-ac_cv_have_decl_SCTP_ESTABLISHED=${ac_cv_have_decl_SCTP_ESTABLISHED=no}
-ac_cv_have_decl_SCTP_LISTEN=${ac_cv_have_decl_SCTP_LISTEN=no}
-ac_cv_have_decl_SCTP_SENDALL=${ac_cv_have_decl_SCTP_SENDALL=no}
-ac_cv_have_decl_SCTP_SHUTDOWN_ACK_SENT=${ac_cv_have_decl_SCTP_SHUTDOWN_ACK_SENT=no}
-ac_cv_have_decl_SCTP_SHUTDOWN_PENDING=${ac_cv_have_decl_SCTP_SHUTDOWN_PENDING=no}
-ac_cv_have_decl_SCTP_SHUTDOWN_RECEIVED=${ac_cv_have_decl_SCTP_SHUTDOWN_RECEIVED=no}
-ac_cv_have_decl_SCTP_SHUTDOWN_SENT=${ac_cv_have_decl_SCTP_SHUTDOWN_SENT=no}
-ac_cv_have_decl_SCTP_UNORDERED=${ac_cv_have_decl_SCTP_UNORDERED=no}
+ac_cv_have_decl_IN6ADDR_ANY_INIT=${ac_cv_have_decl_IN6ADDR_ANY_INIT=no}
+ac_cv_have_decl_IN6ADDR_LOOPBACK_INIT=${ac_cv_have_decl_IN6ADDR_LOOPBACK_INIT=no}
+ac_cv_have_decl_IPV6_V6ONLY=${ac_cv_have_decl_IPV6_V6ONLY=no}
ac_cv_have_decl_posix2time=${ac_cv_have_decl_posix2time=no}
-ac_cv_header_arpa_inet_h=${ac_cv_header_arpa_inet_h=no}
+ac_cv_have_decl_time2posix=${ac_cv_have_decl_time2posix=no}
ac_cv_header_arpa_nameser_h=${ac_cv_header_arpa_nameser_h=no}
ac_cv_header_dirent_dirent_h=${ac_cv_header_dirent_dirent_h=no}
ac_cv_header_dirent_ndir_h=${ac_cv_header_dirent_ndir_h=no}
ac_cv_header_dirent_sys_dir_h=${ac_cv_header_dirent_sys_dir_h=no}
ac_cv_header_dirent_sys_ndir_h=${ac_cv_header_dirent_sys_ndir_h=no}
ac_cv_header_dlfcn_h=${ac_cv_header_dlfcn_h=no}
+ac_cv_header_elf_h=${ac_cv_header_elf_h=no}
ac_cv_header_fcntl_h=${ac_cv_header_fcntl_h=yes}
-ac_cv_header_gl_gl_h=${ac_cv_header_gl_gl_h=yes}
ac_cv_header_ieeefp_h=${ac_cv_header_ieeefp_h=no}
ac_cv_header_ifaddrs_h=${ac_cv_header_ifaddrs_h=no}
-ac_cv_header_inttypes_h=${ac_cv_header_inttypes_h=no}
+ac_cv_header_inttypes_h=${ac_cv_header_inttypes_h=yes}
ac_cv_header_langinfo_h=${ac_cv_header_langinfo_h=no}
+ac_cv_header_libdlpi_h=${ac_cv_header_libdlpi_h=no}
+ac_cv_header_libutil_h=${ac_cv_header_libutil_h=no}
ac_cv_header_limits_h=${ac_cv_header_limits_h=yes}
-ac_cv_header_mach_o_dyld_h=${ac_cv_header_mach_o_dyld_h=no}
+ac_cv_header_linux_errqueue_h=${ac_cv_header_linux_errqueue_h=no}
+ac_cv_header_linux_falloc_h=${ac_cv_header_linux_falloc_h=no}
+ac_cv_header_linux_types_h=${ac_cv_header_linux_types_h=no}
ac_cv_header_malloc_h=${ac_cv_header_malloc_h=yes}
-ac_cv_header_memory_h=${ac_cv_header_memory_h=yes}
ac_cv_header_net_errno_h=${ac_cv_header_net_errno_h=no}
ac_cv_header_net_if_dl_h=${ac_cv_header_net_if_dl_h=no}
-ac_cv_header_netdb_h=${ac_cv_header_netdb_h=no}
-ac_cv_header_netinet_in_h=${ac_cv_header_netinet_in_h=no}
+ac_cv_header_netinet_sctp_h=${ac_cv_header_netinet_sctp_h=no}
ac_cv_header_netpacket_packet_h=${ac_cv_header_netpacket_packet_h=no}
ac_cv_header_poll_h=${ac_cv_header_poll_h=no}
ac_cv_header_pty_h=${ac_cv_header_pty_h=no}
-ac_cv_header_stdc=${ac_cv_header_stdc=yes}
-ac_cv_header_stddef_h=${ac_cv_header_stddef_h=yes}
+ac_cv_header_sched_h=${ac_cv_header_sched_h=no}
+ac_cv_header_sdkddkver_h=${ac_cv_header_sdkddkver_h=yes}
+ac_cv_header_setns_h=${ac_cv_header_setns_h=no}
ac_cv_header_stdint_h=${ac_cv_header_stdint_h=yes}
+ac_cv_header_stdio_h=${ac_cv_header_stdio_h=yes}
ac_cv_header_stdlib_h=${ac_cv_header_stdlib_h=yes}
ac_cv_header_string_h=${ac_cv_header_string_h=yes}
ac_cv_header_strings_h=${ac_cv_header_strings_h=no}
@@ -186,9 +227,8 @@ ac_cv_header_sys_devpoll_h=${ac_cv_header_sys_devpoll_h=no}
ac_cv_header_sys_epoll_h=${ac_cv_header_sys_epoll_h=no}
ac_cv_header_sys_event_h=${ac_cv_header_sys_event_h=no}
ac_cv_header_sys_ioctl_h=${ac_cv_header_sys_ioctl_h=no}
-ac_cv_header_sys_param_h=${ac_cv_header_sys_param_h=no}
+ac_cv_header_sys_mman_h=${ac_cv_header_sys_mman_h=no}
ac_cv_header_sys_resource_h=${ac_cv_header_sys_resource_h=no}
-ac_cv_header_sys_select_h=${ac_cv_header_sys_select_h=no}
ac_cv_header_sys_socket_h=${ac_cv_header_sys_socket_h=no}
ac_cv_header_sys_socketio_h=${ac_cv_header_sys_socketio_h=no}
ac_cv_header_sys_sockio_h=${ac_cv_header_sys_sockio_h=no}
@@ -196,54 +236,65 @@ ac_cv_header_sys_stat_h=${ac_cv_header_sys_stat_h=yes}
ac_cv_header_sys_stropts_h=${ac_cv_header_sys_stropts_h=no}
ac_cv_header_sys_sysctl_h=${ac_cv_header_sys_sysctl_h=no}
ac_cv_header_sys_time_h=${ac_cv_header_sys_time_h=no}
+ac_cv_header_sys_timerfd_h=${ac_cv_header_sys_timerfd_h=no}
ac_cv_header_sys_types_h=${ac_cv_header_sys_types_h=yes}
ac_cv_header_sys_uio_h=${ac_cv_header_sys_uio_h=no}
+ac_cv_header_sys_un_h=${ac_cv_header_sys_un_h=no}
ac_cv_header_sys_wait_h=${ac_cv_header_sys_wait_h=no}
ac_cv_header_syslog_h=${ac_cv_header_syslog_h=no}
-ac_cv_header_time=${ac_cv_header_time=no}
ac_cv_header_unistd_h=${ac_cv_header_unistd_h=no}
ac_cv_header_util_h=${ac_cv_header_util_h=no}
ac_cv_header_utmp_h=${ac_cv_header_utmp_h=no}
ac_cv_header_valgrind_valgrind_h=${ac_cv_header_valgrind_valgrind_h=no}
-ac_cv_header_vfork_h=${ac_cv_header_vfork_h=no}
ac_cv_header_windows_h=${ac_cv_header_windows_h=yes}
ac_cv_header_winsock2_h=${ac_cv_header_winsock2_h=yes}
ac_cv_header_ws2tcpip_h=${ac_cv_header_ws2tcpip_h=yes}
+ac_cv_host=${ac_cv_host=local-x86_64-pc-windows}
ac_cv_lib_dl_dlopen=${ac_cv_lib_dl_dlopen=no}
+ac_cv_lib_dl_dlvsym=${ac_cv_lib_dl_dlvsym=no}
+ac_cv_lib_dlpi_dlpi_open=${ac_cv_lib_dlpi_dlpi_open=no}
ac_cv_lib_inet_main=${ac_cv_lib_inet_main=no}
ac_cv_lib_kstat_kstat_open=${ac_cv_lib_kstat_kstat_open=no}
+ac_cv_lib_kvm_kvm_open=${ac_cv_lib_kvm_kvm_open=no}
ac_cv_lib_m_sin=${ac_cv_lib_m_sin=no}
-ac_cv_lib_nsl_gethostbyname=${ac_cv_lib_nsl_gethostbyname=no}
-ac_cv_lib_nsl_main=${ac_cv_lib_nsl_main=no}
-ac_cv_lib_resolv_res_gethostbyname=${ac_cv_lib_resolv_res_gethostbyname=no}
ac_cv_lib_rt_clock_gettime=${ac_cv_lib_rt_clock_gettime=no}
-ac_cv_lib_socket_getpeername=${ac_cv_lib_socket_getpeername=no}
ac_cv_lib_socket_main=${ac_cv_lib_socket_main=yes}
-ac_cv_lib_socket_socket=${ac_cv_lib_socket_socket=no}
ac_cv_lib_util_openpty=${ac_cv_lib_util_openpty=no}
-ac_cv_lib_ws2_32_main=${ac_cv_lib_ws2_32_main=yes}
-ac_cv_member_struct_ErlDrvEntry_stop_select=${ac_cv_member_struct_ErlDrvEntry_stop_select=no}
-ac_cv_member_struct_sctp_paddrparams_spp_flags=${ac_cv_member_struct_sctp_paddrparams_spp_flags=no}
-ac_cv_member_struct_sctp_paddrparams_spp_pathmtu=${ac_cv_member_struct_sctp_paddrparams_spp_pathmtu=no}
-ac_cv_member_struct_sctp_paddrparams_spp_sackdelay=${ac_cv_member_struct_sctp_paddrparams_spp_sackdelay=no}
-ac_cv_member_struct_sctp_remote_error_sre_data=${ac_cv_member_struct_sctp_remote_error_sre_data=no}
-ac_cv_member_struct_sctp_send_failed_ssf_data=${ac_cv_member_struct_sctp_send_failed_ssf_data=no}
+ac_cv_member_struct_ifreq_ifr_enaddr=${ac_cv_member_struct_ifreq_ifr_enaddr=no}
+ac_cv_member_struct_ifreq_ifr_hwaddr=${ac_cv_member_struct_ifreq_ifr_hwaddr=no}
+ac_cv_member_struct_ifreq_ifr_ifindex=${ac_cv_member_struct_ifreq_ifr_ifindex=no}
+ac_cv_member_struct_ifreq_ifr_index=${ac_cv_member_struct_ifreq_ifr_index=no}
+ac_cv_member_struct_ifreq_ifr_map=${ac_cv_member_struct_ifreq_ifr_map=no}
+ac_cv_member_struct_sockaddr_dl_sdl_len=${ac_cv_member_struct_sockaddr_dl_sdl_len=no}
+ac_cv_member_struct_sockaddr_un_sun_path=${ac_cv_member_struct_sockaddr_un_sun_path=no}
ac_cv_objext=${ac_cv_objext=o}
-ac_cv_path_MKDIR=${ac_cv_path_MKDIR=/bin/mkdir}
-ac_cv_path_RM=${ac_cv_path_RM=/bin/rm}
+# ac_cv_path_CP=${ac_cv_path_CP=/bin/cp}
+# ac_cv_path_EGREP=${ac_cv_path_EGREP='/usr/bin/grep -E'}
+# ac_cv_path_GREP=${ac_cv_path_GREP=/usr/bin/grep}
+# ac_cv_path_MKDIR=${ac_cv_path_MKDIR=/bin/mkdir}
+# ac_cv_path_PERL=${ac_cv_path_PERL=/usr/bin/perl}
+# ac_cv_path_install=${ac_cv_path_install='/usr/bin/install -c'}
ac_cv_prog_AR=${ac_cv_prog_AR=ar.sh}
ac_cv_prog_CC=${ac_cv_prog_CC=cc.sh}
ac_cv_prog_CPP=${ac_cv_prog_CPP='cc.sh -E'}
-ac_cv_prog_CXX=${ac_cv_prog_CXX=cc.sh}
-ac_cv_prog_DED_LD=${ac_cv_prog_DED_LD=ld.sh}
+ac_cv_prog_GETCONF=${ac_cv_prog_GETCONF=getconf}
ac_cv_prog_JAVAC=${ac_cv_prog_JAVAC=javac.sh}
-ac_cv_prog_M4=${ac_cv_prog_M4=m4}
ac_cv_prog_RANLIB=${ac_cv_prog_RANLIB=true}
-ac_cv_prog_cc_c89=${ac_cv_prog_cc_c89=}
+ac_cv_prog_cc_c11=${ac_cv_prog_cc_c11=no}
+ac_cv_prog_cc_c89=${ac_cv_prog_cc_c89=no}
+ac_cv_prog_cc_c99=${ac_cv_prog_cc_c99=no}
ac_cv_prog_cc_g=${ac_cv_prog_cc_g=yes}
+ac_cv_prog_cxx_11=${ac_cv_prog_cxx_11=no}
+ac_cv_prog_cxx_g=${ac_cv_prog_cxx_g=yes}
+ac_cv_prog_cxx_stdcxx=${ac_cv_prog_cxx_stdcxx=}
+# ac_cv_prog_emu_cc=${ac_cv_prog_emu_cc=$ERL_TOP/git/otp/erts/etc/win32/wsl_tools/vc/emu_cc.sh}
+# ac_cv_prog_javac_ver_1_6=${ac_cv_prog_javac_ver_1_6=no}
+# ac_cv_prog_mkdir_p=${ac_cv_prog_mkdir_p='/usr/bin/install -c -d'}
ac_cv_search_fdatasync=${ac_cv_search_fdatasync=no}
ac_cv_search_opendir=${ac_cv_search_opendir=no}
ac_cv_search_strerror=${ac_cv_search_strerror='none required'}
+ac_cv_sizeof__Float16=${ac_cv_sizeof__Float16=0}
+ac_cv_sizeof___int128_t=${ac_cv_sizeof___int128_t=0}
ac_cv_sizeof___int64=${ac_cv_sizeof___int64=8}
ac_cv_sizeof_char=${ac_cv_sizeof_char=1}
ac_cv_sizeof_int=${ac_cv_sizeof_int=4}
@@ -252,19 +303,56 @@ ac_cv_sizeof_long_long=${ac_cv_sizeof_long_long=8}
ac_cv_sizeof_off_t=${ac_cv_sizeof_off_t=4}
ac_cv_sizeof_short=${ac_cv_sizeof_short=2}
ac_cv_sizeof_size_t=${ac_cv_sizeof_size_t=8}
+ac_cv_sizeof_suseconds_t=${ac_cv_sizeof_suseconds_t=0}
+ac_cv_sizeof_time_t=${ac_cv_sizeof_time_t=8}
ac_cv_sizeof_void_p=${ac_cv_sizeof_void_p=8}
-ac_cv_struct_exception=${ac_cv_struct_exception=no}
ac_cv_struct_sockaddr_sa_len=${ac_cv_struct_sockaddr_sa_len=no}
ac_cv_struct_tm=${ac_cv_struct_tm=time.h}
+# ac_cv_sys_ipv6_support=${ac_cv_sys_ipv6_support=yes}
+ac_cv_sys_multicast_support=${ac_cv_sys_multicast_support=no}
+ac_cv_target=${ac_cv_target=local-x86_64-pc-windows}
ac_cv_type_off_t=${ac_cv_type_off_t=yes}
ac_cv_type_pid_t=${ac_cv_type_pid_t=no}
-ac_cv_type_signal=${ac_cv_type_signal=void}
ac_cv_type_size_t=${ac_cv_type_size_t=yes}
-ac_cv_type_uid_t=${ac_cv_type_uid_t=no}
-ac_cv_working_alloca_h=${ac_cv_working_alloca_h=no}
+erl_cv_clock_gettime_monotonic_default_resolution=${erl_cv_clock_gettime_monotonic_default_resolution=no}
+erl_cv_clock_gettime_monotonic_high_resolution=${erl_cv_clock_gettime_monotonic_high_resolution=no}
+erl_cv_clock_gettime_monotonic_raw=${erl_cv_clock_gettime_monotonic_raw=no}
+erl_cv_clock_gettime_monotonic_try_find_pthread_compatible=${erl_cv_clock_gettime_monotonic_try_find_pthread_compatible=no}
+erl_cv_clock_gettime_wall_default_resolution=${erl_cv_clock_gettime_wall_default_resolution=no}
+erl_cv_mach_clock_get_time_monotonic=${erl_cv_mach_clock_get_time_monotonic=no}
+erl_cv_mach_clock_get_time_wall=${erl_cv_mach_clock_get_time_wall=no}
erts_cv___after_morecore_hook_can_track_malloc=${erts_cv___after_morecore_hook_can_track_malloc=no}
erts_cv_fwrite_unlocked=${erts_cv_fwrite_unlocked=no}
erts_cv_have__end_symbol=${erts_cv_have__end_symbol=no}
erts_cv_have_end_symbol=${erts_cv_have_end_symbol=no}
+erts_cv_have_in6addr_any=${erts_cv_have_in6addr_any=no}
+erts_cv_have_in6addr_loopback=${erts_cv_have_in6addr_loopback=no}
erts_cv_putc_unlocked=${erts_cv_putc_unlocked=no}
erts_cv_windows_h_includes_winsock2_h=${erts_cv_windows_h_includes_winsock2_h=no}
+ethr_cv_have__InterlockedAnd64=${ethr_cv_have__InterlockedAnd64=yes}
+ethr_cv_have__InterlockedAnd=${ethr_cv_have__InterlockedAnd=yes}
+ethr_cv_have__InterlockedCompareExchange128=${ethr_cv_have__InterlockedCompareExchange128=yes}
+ethr_cv_have__InterlockedCompareExchange64=${ethr_cv_have__InterlockedCompareExchange64=yes}
+# ethr_cv_have__InterlockedCompareExchange64_acq=${ethr_cv_have__InterlockedCompareExchange64_acq=no}
+# ethr_cv_have__InterlockedCompareExchange64_rel=${ethr_cv_have__InterlockedCompareExchange64_rel=no}
+ethr_cv_have__InterlockedCompareExchange=${ethr_cv_have__InterlockedCompareExchange=yes}
+# ethr_cv_have__InterlockedCompareExchange_acq=${ethr_cv_have__InterlockedCompareExchange_acq=no}
+# ethr_cv_have__InterlockedCompareExchange_rel=${ethr_cv_have__InterlockedCompareExchange_rel=no}
+ethr_cv_have__InterlockedDecrement64=${ethr_cv_have__InterlockedDecrement64=yes}
+# ethr_cv_have__InterlockedDecrement64_rel=${ethr_cv_have__InterlockedDecrement64_rel=no}
+ethr_cv_have__InterlockedDecrement=${ethr_cv_have__InterlockedDecrement=yes}
+# ethr_cv_have__InterlockedDecrement_rel=${ethr_cv_have__InterlockedDecrement_rel=no}
+ethr_cv_have__InterlockedExchange64=${ethr_cv_have__InterlockedExchange64=yes}
+ethr_cv_have__InterlockedExchange=${ethr_cv_have__InterlockedExchange=yes}
+ethr_cv_have__InterlockedExchangeAdd64=${ethr_cv_have__InterlockedExchangeAdd64=yes}
+# ethr_cv_have__InterlockedExchangeAdd64_acq=${ethr_cv_have__InterlockedExchangeAdd64_acq=no}
+ethr_cv_have__InterlockedExchangeAdd=${ethr_cv_have__InterlockedExchangeAdd=yes}
+# ethr_cv_have__InterlockedExchangeAdd_acq=${ethr_cv_have__InterlockedExchangeAdd_acq=no}
+ethr_cv_have__InterlockedIncrement64=${ethr_cv_have__InterlockedIncrement64=yes}
+# ethr_cv_have__InterlockedIncrement64_acq=${ethr_cv_have__InterlockedIncrement64_acq=no}
+ethr_cv_have__InterlockedIncrement=${ethr_cv_have__InterlockedIncrement=yes}
+# ethr_cv_have__InterlockedIncrement_acq=${ethr_cv_have__InterlockedIncrement_acq=no}
+ethr_cv_have__InterlockedOr64=${ethr_cv_have__InterlockedOr64=yes}
+ethr_cv_have__InterlockedOr=${ethr_cv_have__InterlockedOr=yes}
+i_cv_fallocate_works=${i_cv_fallocate_works=no}
+i_cv_posix_fallocate_works=${i_cv_posix_fallocate_works=no}
diff --git a/make/configure b/make/configure
index 169ae15fe6..8c21940de2 100755
--- a/make/configure
+++ b/make/configure
@@ -773,7 +773,6 @@ enable_option_checking
enable_bootstrap_only
enable_parallel_configure
enable_dirty_schedulers
-enable_plain_emulator
with_termcap
enable_kernel_poll
enable_sctp
@@ -796,7 +795,6 @@ enable_m64_build
enable_m32_build
enable_pie
with_libatomic_ops
-enable_sanitizers
enable_silent_rules
'
ac_precious_vars='build_alias
@@ -1466,9 +1464,6 @@ Optional Features:
disable parallel execution of configure scripts
--enable-dirty-schedulers
enable dirty scheduler support
- --enable-plain-emulator enable threaded non-smp emulator
- --disable-plain-emulator
- disable threaded non-smp emulator
--enable-kernel-poll enable kernel poll support
--disable-kernel-poll disable kernel poll support
--enable-sctp enable sctp support (default) to on demand load the
@@ -1501,8 +1496,6 @@ Optional Features:
--enable-m32-build build 32bit binaries using the -m32 flag to (g)cc
--enable-pie build position independent executables
--disable-pie do no build position independent executables
- --enable-sanitizers[=comma-separated list of sanitizers]
- Default=address,undefined
--enable-silent-rules less verbose build output (undo: "make V=1")
--disable-silent-rules verbose build output (undo: "make V=0")
@@ -5557,13 +5550,6 @@ then :
fi
-# Check whether --enable-plain-emulator was given.
-if test ${enable_plain_emulator+y}
-then :
- enableval=$enable_plain_emulator;
-fi
-
-
# Check whether --with-termcap was given.
if test ${with_termcap+y}
@@ -5738,14 +5724,6 @@ then :
fi
-
-# Check whether --enable-sanitizers was given.
-if test ${enable_sanitizers+y}
-then :
- enableval=$enable_sanitizers;
-fi
-
-
# Check whether --enable-silent-rules was given.
if test ${enable_silent_rules+y}
then :
diff --git a/make/configure.ac b/make/configure.ac
index e0d4103b6e..654d1c0bc5 100644
--- a/make/configure.ac
+++ b/make/configure.ac
@@ -2,7 +2,7 @@ dnl Process this file with autoconf to produce a configure script.
dnl %CopyrightBegin%
dnl
-dnl Copyright Ericsson AB 1998-2021. All Rights Reserved.
+dnl Copyright Ericsson AB 1998-2023. All Rights Reserved.
dnl
dnl Licensed under the Apache License, Version 2.0 (the "License");
dnl you may not use this file except in compliance with the License.
@@ -212,10 +212,6 @@ AS_HELP_STRING([--disable-parallel-configure], [disable parallel execution of co
AC_ARG_ENABLE(dirty-schedulers,
AS_HELP_STRING([--enable-dirty-schedulers], [enable dirty scheduler support]))
-AC_ARG_ENABLE(plain-emulator,
-AS_HELP_STRING([--enable-plain-emulator], [enable threaded non-smp emulator])
-AS_HELP_STRING([--disable-plain-emulator], [disable threaded non-smp emulator]))
-
AC_ARG_WITH(termcap,
AS_HELP_STRING([--with-termcap], [use termcap (default)])
AS_HELP_STRING([--without-termcap],
@@ -342,12 +338,6 @@ AC_ARG_WITH(libatomic_ops,
AS_HELP_STRING([--with-libatomic_ops=PATH],
[specify and prefer usage of libatomic_ops in the ethread library]))
-m4_define(DEFAULT_SANITIZERS, [address,undefined])
-AC_ARG_ENABLE(sanitizers,
- AS_HELP_STRING(
- [--enable-sanitizers@<:@=comma-separated list of sanitizers@:>@],
- [Default=DEFAULT_SANITIZERS]))
-
AC_ARG_ENABLE([silent-rules], [dnl
AS_HELP_STRING(
[--enable-silent-rules],
diff --git a/make/doc.mk b/make/doc.mk
index 119dc75971..247ae4b36e 100644
--- a/make/doc.mk
+++ b/make/doc.mk
@@ -156,9 +156,6 @@ clean_chunks:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-$(RELSYSDIR) $(RELSYSDIR)/doc:
- $(INSTALL_DIR) "$@"
-
release_pdf_spec: pdf
$(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
$(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
@@ -206,8 +203,8 @@ ifneq ($(MAN7_FILES),)
$(INSTALL_DATA) $(MAN7_FILES) "$(RELEASE_PATH)/man/man7"
endif
-release_docs_spec: $(RELSYSDIR)/doc $(INFO_FILE) $(DOC_TARGETS:%=release_%_spec)
- $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR)
+release_docs_spec: $(INFO_FILE) $(DOC_TARGETS:%=release_%_spec)
+ $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
ifneq ($(STANDARDS),)
$(INSTALL_DIR) "$(RELEASE_PATH)/doc/standard"
$(INSTALL_DATA) $(STANDARDS) "$(RELEASE_PATH)/doc/standard"
diff --git a/make/otp.mk.in b/make/otp.mk.in
index 61654ad5cb..1ed554ee76 100644
--- a/make/otp.mk.in
+++ b/make/otp.mk.in
@@ -101,7 +101,7 @@ EMULATOR = beam
ifeq ($(ERL_COMPILE_WARNINGS_AS_ERRORS),yes)
ERL_COMPILE_FLAGS += -Werror
endif
-ifdef BOOTSTRAP
+ifdef PRIMARY_BOOTSTRAP
ERL_COMPILE_FLAGS += +slim
else
ERL_COMPILE_FLAGS += +debug_info
diff --git a/make/otp_version_tickets_in_merge b/make/otp_version_tickets_in_merge
index 3d31d9e574..e69de29bb2 100644
--- a/make/otp_version_tickets_in_merge
+++ b/make/otp_version_tickets_in_merge
@@ -1,18 +0,0 @@
-OTP-18422
-OTP-18480
-OTP-18489
-OTP-18497
-OTP-18506
-OTP-18509
-OTP-18512
-OTP-18516
-OTP-18519
-OTP-18525
-OTP-18545
-OTP-18550
-OTP-18553
-OTP-18554
-OTP-18555
-OTP-18557
-OTP-18560
-OTP-18563
diff --git a/make/test_target_script.sh b/make/test_target_script.sh
index b21185fd06..6f854a55d4 100755
--- a/make/test_target_script.sh
+++ b/make/test_target_script.sh
@@ -114,13 +114,19 @@ EOM
}
release_erlang () {
- local RELEASE_ROOT=${1}
- if ! (cd $ERL_TOP && make release RELEASE_ROOT="${RELEASE_ROOT}"); then
+ local RELEASE_ROOT="${1}"
+ if ! (cd $ERL_TOP && make release TYPE= release_docs DOC_TARGETS=chunks RELEASE_ROOT="${RELEASE_ROOT}"); then
return 1
fi
if ! (cd "$RELEASE_ROOT" && ./Install -minimal "`pwd`"); then
return 1
fi
+ ## Need to release both TYPE= and TYPE=$TYPE for tests to work
+ if [ "$TYPE" != "" ]; then
+ if ! (cd $ERL_TOP && make release TYPE=$TYPE RELEASE_ROOT="${RELEASE_ROOT}"); then
+ return 1
+ fi
+ fi
export PATH="${RELEASE_ROOT}/bin:$PATH"
return 0
}
@@ -192,7 +198,7 @@ MAKE_TEST_CT_LOGS="$MAKE_TEST_DIR/ct_logs"
RELEASE_TEST_SPEC_LOG="$MAKE_TEST_DIR/release_tests_spec_log"
INSTALL_TEST_LOG="$MAKE_TEST_DIR/install_tests_log"
COMPILE_TEST_LOG="$MAKE_TEST_DIR/compile_tests_log"
-RELEASE_ROOT="${MAKE_TEST_DIR}/otp"
+RELEASE_ROOT=${RELEASE_ROOT:-"${MAKE_TEST_DIR}/Erlang ∅⊤℞"}
RELEASE_LOG="$MAKE_TEST_DIR/release_tests_log"
cd test
@@ -216,10 +222,10 @@ EOF
exit 1
fi
CT_RUN="${RELEASE_ROOT}/bin/ct_run"
- PATH=${RELEASE_ROOT}/bin/:${PATH}
+ PATH="${RELEASE_ROOT}/bin/":${PATH}
fi
-echo "The tests in test directory for $APPLICATION will be executed with ct_run"
+echo "The tests in test directory for $APPLICATION will be executed with ${CT_RUN}"
if [ -z "${ARGS}" ]
then
if [ ! -d "$MAKE_TEST_DIR" ]
@@ -299,28 +305,28 @@ then
CTRUN_TIMEOUT="timeout -s ABRT --foreground --preserve-status $((${CTRUN_TIMEOUT}+5))m timeout -s USR1 --foreground --preserve-status ${CTRUN_TIMEOUT}m"
fi
ERL_AFLAGS="${ERL_AFLAGS}" $CTRUN_TIMEOUT \
- $CT_RUN -logdir $MAKE_TEST_CT_LOGS\
- -pa "$ERL_TOP/lib/common_test/test_server"\
- -config "$ERL_TOP/lib/common_test/test_server/ts.config"\
- -config "$ERL_TOP/lib/common_test/test_server/ts.unix.config"\
+ "${CT_RUN}" -logdir $MAKE_TEST_CT_LOGS \
+ -pa "$ERL_TOP/lib/common_test/test_server" \
+ -config "$ERL_TOP/lib/common_test/test_server/ts.config" \
+ -config "$ERL_TOP/lib/common_test/test_server/ts.unix.config" \
-exit_status ignore_config \
- ${ARGS}\
- -erl_args\
- -env ERL_CRASH_DUMP "$MAKE_TEST_DIR/${APPLICATION}_erl_crash.dump"\
- -boot start_sasl\
- -sasl errlog_type error\
- -pz "$ERL_TOP/lib/common_test/test_server"\
- -pz "."\
- -ct_test_vars "{net_dir,\"\"}"\
- -noshell\
+ ${ARGS} \
+ -erl_args \
+ -env ERL_CRASH_DUMP "$MAKE_TEST_DIR/${APPLICATION}_erl_crash.dump" \
+ -boot start_sasl \
+ -sasl errlog_type error \
+ -pz "$ERL_TOP/lib/common_test/test_server" \
+ -pz "." \
+ -ct_test_vars "{net_dir,\"\"}" \
+ -noinput \
-sname ${CT_NODENAME}\
- -rsh ssh\
+ -rsh ssh \
${ERL_ARGS}
else
WIN_MAKE_TEST_CT_LOGS=`w32_path.sh -m "$MAKE_TEST_CT_LOGS"`
WIN_MAKE_TEST_DIR=`w32_path.sh -m "$MAKE_TEST_DIR"`
WIN_ERL_TOP=`w32_path.sh -m "$ERL_TOP"`
- $CT_RUN.exe -logdir $WIN_MAKE_TEST_CT_LOGS\
+ "$CT_RUN.exe" -logdir $WIN_MAKE_TEST_CT_LOGS\
-pa "$WIN_ERL_TOP/lib/common_test/test_server"\
-config "$WIN_ERL_TOP/lib/common_test/test_server/ts.config"\
-config "$WIN_ERL_TOP/lib/common_test/test_server/ts.win32.config"\
@@ -333,7 +339,7 @@ else
-pz "$WIN_ERL_TOP/lib/common_test/test_server"\
-pz "."\
-ct_test_vars "{net_dir,\"\"}"\
- -noshell\
+ -noinput\
-sname ${CT_NODENAME}\
-rsh ssh\
${ERL_ARGS}
diff --git a/otp_build b/otp_build
index 7b35b39fc2..c9d118c2a4 100755
--- a/otp_build
+++ b/otp_build
@@ -2,7 +2,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2002-2021. All Rights Reserved.
+# Copyright Ericsson AB 2002-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -56,6 +56,8 @@ usage ()
echo " release <target_dir> - creates a small release to <target_dir>"
echo " release [-a|-s|-t] <target_dir> - creates full release to <target_dir>"
echo " tests <dir> - Build testsuites to <dir>"
+ echo " check [--help|...] - Perform various build checks. See --help for more info"
+ echo " and options."
echo ""
echo "-a builds all applications"
echo "-s builds a small system (default)"
@@ -998,11 +1000,15 @@ do_tests ()
fi
}
+do_check ()
+{
+ exec $ERL_TOP/scripts/otp_build_check "$@"
+}
+
do_debuginfo_win32 ()
{
setup_make
- (cd erts/emulator && $MAKE MAKE="$MAKE" TARGET=$TARGET FLAVOR=smp debug &&\
- $MAKE MAKE="$MAKE" TARGET=$TARGET FLAVOR=plain debug) || exit 1
+ (cd erts/emulator && $MAKE MAKE="$MAKE" TARGET=$TARGET debug) || exit 1
if [ -z "$1" ]; then
RELDIR="$ERL_TOP/release/$TARGET"
else
@@ -1010,7 +1016,7 @@ do_debuginfo_win32 ()
fi
BINDIR="$ERL_TOP/bin/$TARGET"
EVSN=`grep '^VSN' erts/vsn.mk | sed 's,^VSN.*=[^0-9]*\([0-9].*\)$,@\1,g;s,^[^@].*,,g;s,^@,,g'`
- for f in beam.debug.smp.dll beam.smp.pdb beam.debug.smp.dll.pdb erl.pdb werl.pdb erlexec.pdb; do
+ for f in beam.debug.smp.dll beam.smp.pdb beam.debug.smp.dll.pdb erl.pdb erlexec.pdb; do
if [ -f $BINDIR/$f ]; then
rm -f $RELDIR/erts-$EVSN/bin/$f
cp $BINDIR/$f $RELDIR/erts-$EVSN/bin/$f
@@ -1218,7 +1224,7 @@ case "$1" in
do_configure "$@";;
opt)
do_boot;;
- plain|smp)
+ smp)
if [ $minus_x_flag = false ]; then
TYPE=opt
fi;
@@ -1295,6 +1301,9 @@ case "$1" in
echo_env_cross "$2";;
env_bootstrap)
echo_env_bootstrap;;
+ check)
+ shift;
+ do_check "$@";;
*)
usage;;
esac
diff --git a/otp_versions.table b/otp_versions.table
index 0032616639..9da37263b9 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -1,3 +1,4 @@
+OTP-25.3.2 : compiler-8.2.6 erts-13.2.2 os_mon-2.8.2 # asn1-5.0.21 common_test-1.24 crypto-5.1.4 debugger-5.3.1 dialyzer-5.0.5 diameter-2.2.7 edoc-1.2 eldap-1.2.11 erl_docgen-1.4 erl_interface-5.3.2 et-1.6.5 eunit-2.8.2 ftp-1.1.4 inets-8.3.1 jinterface-1.13.2 kernel-8.5.4 megaco-4.4.3 mnesia-4.21.4 observer-2.14 odbc-2.14 parsetools-2.4.1 public_key-1.13.3 reltool-0.9.1 runtime_tools-1.19 sasl-4.2 snmp-5.13.5 ssh-4.15.3 ssl-10.9.1 stdlib-4.3.1 syntax_tools-3.0.1 tftp-1.0.4 tools-3.5.3 wx-2.2.2 xmerl-1.3.31 :
OTP-25.3.1 : compiler-8.2.5 crypto-5.1.4 eldap-1.2.11 erl_interface-5.3.2 erts-13.2.1 inets-8.3.1 snmp-5.13.5 ssl-10.9.1 stdlib-4.3.1 wx-2.2.2 # asn1-5.0.21 common_test-1.24 debugger-5.3.1 dialyzer-5.0.5 diameter-2.2.7 edoc-1.2 erl_docgen-1.4 et-1.6.5 eunit-2.8.2 ftp-1.1.4 jinterface-1.13.2 kernel-8.5.4 megaco-4.4.3 mnesia-4.21.4 observer-2.14 odbc-2.14 os_mon-2.8.1 parsetools-2.4.1 public_key-1.13.3 reltool-0.9.1 runtime_tools-1.19 sasl-4.2 ssh-4.15.3 syntax_tools-3.0.1 tftp-1.0.4 tools-3.5.3 xmerl-1.3.31 :
OTP-25.3 : common_test-1.24 compiler-8.2.4 crypto-5.1.3 debugger-5.3.1 dialyzer-5.0.5 erl_interface-5.3.1 erts-13.2 eunit-2.8.2 ftp-1.1.4 inets-8.3 jinterface-1.13.2 kernel-8.5.4 megaco-4.4.3 mnesia-4.21.4 os_mon-2.8.1 public_key-1.13.3 reltool-0.9.1 snmp-5.13.4 ssh-4.15.3 ssl-10.9 stdlib-4.3 syntax_tools-3.0.1 tftp-1.0.4 xmerl-1.3.31 # asn1-5.0.21 diameter-2.2.7 edoc-1.2 eldap-1.2.10 erl_docgen-1.4 et-1.6.5 observer-2.14 odbc-2.14 parsetools-2.4.1 runtime_tools-1.19 sasl-4.2 tools-3.5.3 wx-2.2.1 :
OTP-25.2.3 : erts-13.1.5 inets-8.2.2 ssh-4.15.2 ssl-10.8.7 # asn1-5.0.21 common_test-1.23.3 compiler-8.2.3 crypto-5.1.2 debugger-5.3 dialyzer-5.0.4 diameter-2.2.7 edoc-1.2 eldap-1.2.10 erl_docgen-1.4 erl_interface-5.3 et-1.6.5 eunit-2.8.1 ftp-1.1.3 jinterface-1.13.1 kernel-8.5.3 megaco-4.4.2 mnesia-4.21.3 observer-2.14 odbc-2.14 os_mon-2.8 parsetools-2.4.1 public_key-1.13.2 reltool-0.9 runtime_tools-1.19 sasl-4.2 snmp-5.13.3 stdlib-4.2 syntax_tools-3.0 tftp-1.0.3 tools-3.5.3 wx-2.2.1 xmerl-1.3.30 :
diff --git a/prebuild.delete b/prebuild.delete
index 17efc89229..d9f7040428 100644
--- a/prebuild.delete
+++ b/prebuild.delete
@@ -1,4 +1,5 @@
bootstrap/lib
bootstrap/target
.git
+.github
scripts
diff --git a/scripts/build-otp-tar b/scripts/build-otp-tar
index fc8344c76a..6fb251f6e6 100755
--- a/scripts/build-otp-tar
+++ b/scripts/build-otp-tar
@@ -541,7 +541,13 @@ if [ ! -d $src_dir -o ! -f $src_dir/otp_build ]; then
fi
progress "Checking target directory name"
-target_dirname=`$prebld_dir/make/autoconf/config.guess`
+if [ -f "$prebld_dir/make/autoconf/config.guess" ]; then
+ target_dirname=`$prebld_dir/make/autoconf/config.guess`
+elif [ -f "$prebld_dir/erts/autoconf/config.guess" ]; then
+ target_dirname=`$prebld_dir/erts/autoconf/config.guess`
+else
+ error "Failed to find config.guess"
+fi
if [ $? -ne 0 ]; then
error "Failed to check target directory name"
fi
@@ -651,7 +657,7 @@ done
for kobj in $kobjs; do
progress "Keeping $kobj in pre-build-directory"
- copy $build_dir/../$kobj $prebld_root/$kobj
+ copy $build_dir/../$kobj $prebld_root/$(dirname $kobj)
done
cd $prebld_dir
diff --git a/scripts/otp_build_check b/scripts/otp_build_check
new file mode 100755
index 0000000000..ee6e656cdf
--- /dev/null
+++ b/scripts/otp_build_check
@@ -0,0 +1,344 @@
+#!/bin/sh
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2023. All Rights Reserved.
+#
+# 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.
+#
+# %CopyrightEnd%
+#
+
+#
+# Performs a lot of (but not always all) build checking that you want to do...
+#
+
+docs=yes
+dialyzer=yes
+tests=all
+opt_only=no
+format_check=yes
+
+have_printf=no
+
+print_error() {
+ if [ $have_printf = yes ]; then
+ printf "\033[31mERROR: %s\033[0m\n" "$1" 1>&2
+ else
+ echo "ERROR: $1" 1>&2
+ fi
+}
+
+progress() {
+ if [ $have_printf = yes ]; then
+ printf "\n\033[33m*** %s ***\033[0m\n\n" "$1" 1>&2
+ else
+ echo "\n*** $1 ***\n" 1>&2
+ fi
+}
+
+print_usage() {
+ cat <<EOF
+Usage:
+ otp_build check [--help|-h] [--only-opt|-o] [--no-docs|-d] \\
+ [--no-dialyzer|-y] [--no-tests|-n] [--no-format-check|-f] \\
+ [--tests|-t <App0> ... <AppN>]
+
+
+By default all currently implemented checks will be performed. If any of the
+currently used tools are missing, checking will fail. If libraries or tools
+needed to support certain conditional features are missing, those features
+will not be checked.
+
+If any of these checks do not pass, the code is *not* ready for testing in OTP
+daily builds. Note that this script does not check all requirements for testing
+in OTP daily builds. These checks are the bare minimum for even considering
+testing in OTP daily builds. Currently the following will be performed by
+default:
+
+ * Build all applications in optimized mode. If configure already has been run,
+ it wont be run again.
+ * Debug compile C-code in all applications.
+ * Format checking of JIT code.
+ * Run dialyzer on all applications.
+ * Build all documentation.
+ * Run xmllint on all documentation.
+ * Run html link check on all documentation.
+ * Build all test suites.
+
+Certain build checking can be disabled using the following options:
+* [--only-opt|-o] - Only build optimized system. No debug, etc.
+* [--no-format-check|-f] - No JIT format checking.
+* [--no-docs|-d] - No documentation checking.
+* [--no-dialyzer|-y] - No dialyzer checking.
+* [--no-tests|-n] - No build checking of test suites.
+* [--tests|-t <App0> ... <AppN>] - Only build checking of test suites for
+ listed applications.
+
+Only disable build checking for parts of the system that you are certain your
+changes wont effect. Note that even though you've made no changes in
+documentation source files, documentation build is effected by type changes
+in code.
+
+Environment variables used:
+* CONFIG_FLAGS - Arguments to pass to configure if it is executed.
+
+Build results will be placed under the \$ERL_TOP/release/<TARGET> directory
+
+EOF
+}
+
+usage () {
+ print_error "$1"
+ print_usage >&2
+ exit 1
+}
+
+fail () {
+ print_error "$1"
+ exit 1
+}
+
+type printf >/dev/null 2>&1
+if [ $? -eq 0 ]; then
+ have_printf=yes
+fi
+
+[ "$ERL_TOP" != "" ] || fail "ERL_TOP needs to be set"
+
+cd "$ERL_TOP" 2>&1 >/dev/null || {
+ fail "Failed to change directory into $ERL_TOP"
+}
+
+[ -f "$ERL_TOP/OTP_VERSION" ] || fail "ERL_TOP not valid"
+
+while [ $# -gt 0 ]; do
+ case $1 in
+ --help|-h)
+ print_usage
+ exit 0;;
+ --only-opt|-o)
+ opt_only=yes;;
+ --no-docs|-d)
+ docs=no;;
+ --no-dialyzer|-y)
+ dialyzer=no;;
+ --no-format-check|-f)
+ format_check=no;;
+ --no-tests|-n)
+ tests=none;;
+ --tests|-t)
+ shift
+ while [ $# -gt 0 ]; do
+ case $1 in
+ -*)
+ break;;
+ test_server|emulator|system|epmd)
+ ;;
+ *)
+ [ -d "$ERL_TOP/lib/$1/test" ] || {
+ fail "Invalid application: $1"
+ }
+ ;;
+ esac
+ tapps="$1 $tapps"
+ shift
+ done
+ [ "$tapps" != "" ] || usage "No test apps given"
+ tests="$tapps"
+ continue;;
+ *)
+ usage "Invalid option: $1"
+ esac
+ shift
+done
+
+[ -f "$ERL_TOP/make/config.status" ] || {
+ ./configure $CONFIG_FLAGS || fail "configure failed"
+}
+
+type gmake >/dev/null 2>&1
+if [ $? -eq 0 ]; then
+ export MAKE="gmake"
+else
+ type make >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ export MAKE="make"
+ fi
+fi
+
+target=`$MAKE target_configured` || fail "Failed to determine target directory"
+
+unset TESTROOT
+unset RELEASE_ROOT
+unset ERL_LIBS
+unset OTP_SMALL_BUILD
+unset OTP_TINY_BUILD
+unset TARGET
+
+progress "Building OTP in $ERL_TOP"
+$MAKE || fail "OTP build failed"
+progress "Booting optimized runtime"
+$ERL_TOP/bin/cerl -eval "erlang:halt()" || {
+ fail "Failed to boot runtime"
+}
+
+[ $opt_only = yes ] || {
+ progress "Building debug OTP in $ERL_TOP"
+ $MAKE TYPE=debug || fail "OTP debug build failed"
+ progress "Booting debug runtime"
+ $ERL_TOP/bin/cerl -debug -eval "erlang:halt()" || {
+ fail "Failed to boot debug runtime"
+ }
+}
+
+[ $format_check = no ] || {
+ clang-format --help 2>&1 >/dev/null || {
+ fail "clang-format not installed. Use --no-format-check option to skip."
+ }
+
+ progress "Checking C & C++ code formatting"
+ $MAKE format-check || {
+ fail "Failed to format C & C++ code"
+ }
+}
+
+progress "Releasing OTP"
+$MAKE release || fail "Releasing OTP failed"
+
+cd "$ERL_TOP/release/$target" 2>&1 >/dev/null || {
+ fail "Failed to change directory into release directory: $ERL_TOP/release/$target"
+}
+
+progress "Installing OTP"
+./Install -minimal `pwd` || fail "Failed to install release"
+
+progress "Booting optimized installed runtime"
+./bin/erl -eval "erlang:halt()" || {
+ fail "Failed to boot installed runtime"
+}
+
+cd "$ERL_TOP" 2>&1 >/dev/null || {
+ fail "Failed to change directory into $ERL_TOP"
+}
+
+export PATH="$ERL_TOP/release/$target/bin:$PATH"
+
+[ $docs = no ] || {
+ progress "Building OTP documentation"
+ $MAKE docs || fail "Building documentation failed"
+ progress "Releasing OTP documentation"
+ $MAKE release_docs || fail "Releasing documentation failed"
+ progress "Running xmllint on OTP documentation"
+ $MAKE xmllint || fail "xmllint of documentation failed"
+ progress "Running HTML check on OTP documentation"
+ ./scripts/otp_html_check "$ERL_TOP/release/$target/" doc/index.html || {
+ fail "HTML check of documentation failed"
+ }
+}
+
+[ $dialyzer = no ] || {
+ progress "Running dialyzer on OTP"
+ $MAKE dialyzer || fail "Dialyzing OTP failed"
+}
+
+[ "$tests" = none ] || {
+ test_dir="$ERL_TOP/release/$target/test"
+ [ -d "$test_dir" ] || mkdir "$test_dir" 2>&1 >/dev/null || {
+ fail "Failed to create $test_dir directory"
+ }
+ if [ "$tests" = all ]; then
+ progress "Releasing all OTP tests"
+ ./otp_build tests "$test_dir" || fail "Release of tests failed"
+ else
+ for tapp in $tests; do
+ case $tapp in
+ test_server)
+ tapp_dir="$ERL_TOP/lib/test_server"
+ if [ ! -d "$tapp_dir" ]; then
+ tapp_dir="$ERL_TOP/lib/common_test/test_server"
+ fi
+ ;;
+ emulator) tapp_dir="$ERL_TOP/erts/emulator/test";;
+ system) tapp_dir="$ERL_TOP/erts/test";;
+ epmd) tapp_dir="$ERL_TOP/erts/epmd/test";;
+ *) tapp_dir="$ERL_TOP/lib/$tapp/test";;
+ esac
+ cd "$tapp_dir" 2>&1 >/dev/null || {
+ fail "Failed to change directory into $tapp_dir"
+ }
+ progress "Releasing $tapp tests"
+ $MAKE "TESTROOT=$test_dir" release_tests || {
+ fail "Release of $tapp tests failed"
+ }
+ done
+
+ fi
+
+ cd "$test_dir" 2>&1 >/dev/null || {
+ fail "Failed to change directory into $test_dir"
+ }
+
+ for dir in *; do
+
+ [ -d "$test_dir/$dir" ] || continue
+
+ progress "Building $dir tests"
+
+ cd "$test_dir/$dir" 2>&1 >/dev/null || {
+ fail "Failed to change directory into $test_dir/$dir"
+ }
+
+ for mfile in *_data/Makefile.first; do
+
+ [ "$mfile" != '*_data/Makefile.first' ] || continue
+
+ ddir=`dirname $mfile` || fail "dirname on $test_dir/$dir/$mfile failed"
+
+ cd "$test_dir/$dir/$ddir" 2>&1 >/dev/null || {
+ fail "Failed to change directory into $test_dir/$dir/$ddir"
+ }
+
+ $MAKE -f Makefile.first || {
+ fail "Make of $test_dir/$dir/$ddir/Makefile.first failed"
+ }
+
+ done
+
+ cd "$test_dir/$dir" 2>&1 >/dev/null || {
+ fail "Failed to change directory into $test_dir/$dir"
+ }
+
+ if [ -f Emakefile ]; then
+ erl -noshell -eval 'case make:all() of error -> erlang:halt(1); _ -> init:stop() end.' || {
+ fail "Build of $dir failed"
+ }
+ else
+ erlc -I `erl -noshell -eval 'io:format("~ts",[code:lib_dir(common_test)]),init:stop()'`/include -I . *.erl || {
+ fail "Build of $dir failed"
+ }
+ fi
+
+ done
+
+ cd "$ERL_TOP" 2>&1 >/dev/null || {
+ fail "Failed to change directory into $ERL_TOP"
+ }
+}
+
+if [ $have_printf = yes ]; then
+ printf "\n\033[32m*** Success! ***\033[0m\n\n"
+else
+ echo "\n*** Success! ***\n"
+fi
+exit 0
diff --git a/scripts/pre-push b/scripts/pre-push
index 5759406182..8005ef80ee 100755
--- a/scripts/pre-push
+++ b/scripts/pre-push
@@ -23,14 +23,14 @@
#
# Bump this version to give users an update notification.
-PRE_PUSH_SCRIPT_VERSION=3
+PRE_PUSH_SCRIPT_VERSION=4
-NEW_RELEASES="24 23 22 21 20 19 18 17"
+NEW_RELEASES="25 24 23 22 21 20 19 18 17"
OLD_RELEASES="r16 r15 r14 r13"
RELEASES="$NEW_RELEASES $OLD_RELEASES"
# First commit on master, not allowed in other branches
-MASTER_ONLY=a20c39812082068a8b9e3b73276de41fbb0338af
+MASTER_ONLY=b0c30263a0ef698244d898a95c9b9a40e67eac9c
# Number of commits and files allowed in one push by this script
NCOMMITS_MAX=100
diff --git a/scripts/valgrind_beamasm_update.escript b/scripts/valgrind_beamasm_update.escript
index ae2dd0e170..5fa640426c 100755
--- a/scripts/valgrind_beamasm_update.escript
+++ b/scripts/valgrind_beamasm_update.escript
@@ -3,79 +3,113 @@
-mode(compile).
main([VGFile,PerfFile]) ->
+ ets:new(perf, [ordered_set, {keypos,1}, named_table]),
{ok, Perf} = file:read_file(PerfFile),
- {ok, VG} = file:read_file(VGFile),
- file:write_file(VGFile,update_vg(VG, parse_perf(Perf))).
+ {ok, VGIo} = file:open(VGFile, [read,binary]),
+ parse_perf(Perf),
+ case update_vg(VGIo) of
+ {ok, Out} ->
+ file:write_file(VGFile, Out);
+ {error, Error} ->
+ io:format(standard_error, "Error ~p", [Error]),
+ exit(1)
+ end.
parse_perf(Perf) ->
%% Example: 0x409b1c0 84 $global::arith_compare_shared
- lists:foldl(
- fun(<<>>, Acc) ->
- Acc;
- (Line, Acc) ->
+ lists:foreach(
+ fun(<<>>) ->
+ ok;
+ (Line) ->
[<<"0x",Base/binary>>, Size, Name] = string:split(Line," ",all),
- Acc#{ binary_to_integer(Base, 16) =>
- {binary_to_integer(Size, 16), Name}}
- end,#{},string:split(Perf,"\n",all)).
+ Start = binary_to_integer(Base, 16),
+ End = Start + binary_to_integer(Size, 16),
+ ets:insert(perf, [{Start, End, Name}])
+ end,string:split(Perf,"\n",all)).
+
+update_vg(VGIo) ->
+ {ok, RegularPattern} = re:compile("(?:by|at) 0x([0-9A-F]+): (\\?\\?\\?)"),
+ {ok, XmlPattern} = re:compile("(<ip>0x([0-9A-F]+)</ip>)"),
+ update_vg(VGIo, RegularPattern, XmlPattern, [], #{}).
+update_vg(VGIo, RegularPattern, XmlPattern, Acc, AddrCache) ->
+ case io:get_line(VGIo, "") of
+ eof ->
+ {ok, lists:reverse(Acc)};
+ {error, _} = Error ->
+ Error;
+ Line ->
+ {Line1, AddrCache1} = update_vg0(Line, RegularPattern, XmlPattern, AddrCache),
+ update_vg(VGIo, RegularPattern, XmlPattern, [Line1|Acc], AddrCache1)
+ end.
-update_vg(VG, Perf) ->
+update_vg0(Line, RegularPattern, XmlPattern, AddrCache) ->
%% Check if regular log file
- case re:run(VG,"(?:by|at) 0x([0-9A-F]+): (\\?\\?\\?)",[global]) of
+ case re:run(Line,RegularPattern,[global]) of
{match, Matches} ->
lists:foldl(
- fun(Match, File) ->
+ fun(Match, {File, Cache}) ->
[_,Base, Replace] = Match,
- case find_replacement(binary_to_integer(binary:part(VG,Base),16), Perf) of
- undefined ->
- File;
- Replacement ->
- replace(File,Replace,Replacement)
+ case find_replacement_cached(binary_to_integer(binary:part(Line,Base),16), Cache) of
+ {undefined, Cache1} ->
+ {File, Cache1};
+ {Replacement, Cache1} ->
+ {replace(File,Replace,Replacement), Cache1}
end
- end, VG,
+ end, {Line, AddrCache},
%% Run replacements in reverse in order to not invalidate
%% the positions as we update the contents.
lists:reverse(Matches));
_ ->
%% Check if xml log file
- case re:run(VG,"(<ip>0x([0-9A-F]+)</ip>)",[global]) of
+ case re:run(Line,XmlPattern,[global]) of
{match, Matches} ->
lists:foldl(
- fun(Match, File) ->
+ fun(Match, {File, Cache}) ->
[_,Replace,Base] = Match,
- case find_replacement(binary_to_integer(binary:part(VG,Base),16), Perf) of
- undefined ->
- File;
- Replacement ->
- Xml = ["<ip>0x",binary:part(VG,Base),"</ip>\n"
+ case find_replacement_cached(binary_to_integer(binary:part(Line,Base),16), Cache) of
+ {undefined, Cache1} ->
+ {File, Cache1};
+ {Replacement, Cache1} ->
+ Xml = ["<ip>0x",binary:part(Line,Base),"</ip>\n"
" <obj>JIT code</obj>\n"
" <fn>",Replacement,"</fn>\n"
" <dir></dir>\n"
" <file></file>\n"
" <line></line>"],
- replace(File,Replace,Xml)
+ {replace(File,Replace,Xml), Cache1}
end
- end, VG,
+ end, {Line, AddrCache},
%% Run replacements in reverse in order to not invalidate
%% the positions as we update the contents.
lists:reverse(Matches));
_ ->
- VG
+ {Line, AddrCache}
end
end.
-find_replacement(Addr, Perf) when is_map(Perf) ->
- find_replacement(Addr, maps:iterator(Perf));
-find_replacement(Addr, Iter) ->
- case maps:next(Iter) of
- {Base,{Size,Str},Next} ->
- if Base =< Addr andalso Addr < Base + Size ->
- [Str,"+",integer_to_list(Addr - Base, 16)];
- true ->
- find_replacement(Addr,Next)
- end;
- none ->
- undefined
+find_replacement_cached(Addr, Cache) ->
+ case Cache of
+ #{Addr := Result} ->
+ {Result, Cache};
+ _ ->
+ Result = find_replacement(Addr),
+ {Result, Cache#{Addr => Result}}
+ end.
+
+
+find_replacement(Addr) ->
+ MatchSpec = [{{'$1','$2','$3'},
+ [{'andalso',{'>=', Addr,'$1'},
+ {'=<', Addr,'$2'}}],
+ [{{'$1','$3'}}]}],
+ case ets:select(perf, MatchSpec, 1) of
+ [] ->
+ undefined;
+ '$end_of_table' ->
+ undefined;
+ {[{Start, Name}], _} ->
+ [Name,"+",integer_to_list(Addr - Start, 16)]
end.
replace(Bin,{Start,Len},What) ->
diff --git a/system/doc/design_principles/Makefile b/system/doc/design_principles/Makefile
index 44d5992a2e..9e42e93680 100644
--- a/system/doc/design_principles/Makefile
+++ b/system/doc/design_principles/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2021. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@ XMLDIR := $(XMLDIR)/design_principles
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/design_principles"
+RELSYSDIR = $(RELEASE_PATH)/doc/design_principles
# ----------------------------------------------------
# Target Specs
@@ -122,7 +122,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(IMAGE_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/design_principles/spec_proc.xml b/system/doc/design_principles/spec_proc.xml
index 51a1228183..fb881a95b2 100644
--- a/system/doc/design_principles/spec_proc.xml
+++ b/system/doc/design_principles/spec_proc.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2021</year>
+ <year>1997</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -267,8 +267,11 @@ init(Parent) ->
...
proc_lib:init_ack(Parent, {ok, self()}),
loop(...).</code>
- <p><c>proc_lib:start_link</c> is synchronous and does not return
- until <c>proc_lib:init_ack</c> has been called.</p>
+ <p>
+ <c>proc_lib:start_link</c> is synchronous and does not return
+ until <c>proc_lib:init_ack</c> or <c>proc_lib:init_fail</c>
+ has been called, or when the process exits.
+ </p>
</section>
<section>
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index c53d569c99..6c26bd62e4 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2016</year><year>2021</year>
+ <year>2016</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -1912,14 +1912,20 @@ do_unlock() ->
to implicitly postpone any events to the <c>locked</c> state.
</p>
<p>
- A selective receive cannot be used from a <c>gen_statem</c>
+ A catch-all receive should never be used from a <c>gen_statem</c>
behaviour (or from any <c>gen_*</c> behaviour),
as the receive statement is within the <c>gen_*</c> engine itself.
- It must be there because all
<seeerl marker="stdlib:sys"><c>sys</c></seeerl>
compatible behaviours must respond to system messages and therefore
do that in their engine receive loop,
passing non-system messages to the <em>callback module</em>.
+ Using a catch-all receive may result in system messages
+ being discarded which in turn may lead to unexpected behaviour.
+ If a selective receive must be used then great care should be taken to
+ ensure that only messages pertinent to the operation are received.
+ Likewise, a callback must return in due time to let the engine
+ receive loop handle system messages, or they might time out also
+ leading to unexpected behaviour.
</p>
<p>
The
@@ -2528,7 +2534,7 @@ terminate(_Reason, State, _Data) ->
the servers can be expected to idle for a while,
and the amount of heap memory all these servers need
is a problem, then the memory footprint of a server
- can be mimimized by hibernating it through
+ can be minimized by hibernating it through
<seemfa marker="stdlib:proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seemfa>.
</p>
<note>
diff --git a/system/doc/efficiency_guide/Makefile b/system/doc/efficiency_guide/Makefile
index 50ed66019a..d2155acdec 100644
--- a/system/doc/efficiency_guide/Makefile
+++ b/system/doc/efficiency_guide/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2001-2020. All Rights Reserved.
+# Copyright Ericsson AB 2001-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ XMLDIR := $(XMLDIR)/efficiency_guide
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/efficiency_guide"
+RELSYSDIR = $(RELEASE_PATH)/doc/efficiency_guide
# ----------------------------------------------------
# Target Specs
@@ -112,7 +112,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/efficiency_guide/advanced.xml b/system/doc/efficiency_guide/advanced.xml
index 25899f6821..7fdb154412 100644
--- a/system/doc/efficiency_guide/advanced.xml
+++ b/system/doc/efficiency_guide/advanced.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2001</year><year>2022</year>
+ <year>2001</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -163,6 +163,16 @@
manual page in ERTS.</cell>
</row>
<row>
+ <cell>
+ <marker id="unique_pids"/>Unique Local Process Identifiers on a
+ Runtime System Instance
+ </cell>
+ <cell>
+ On a 64 bit system at most <c>2â¶â° - 1</c> unique process identifiers
+ can be created and on a 32 bit system at most <c>2²⸠- 1</c>.
+ </cell>
+ </row>
+ <row>
<cell>Known nodes</cell>
<cell>A remote node Y must be known to node X if there exists
any pids, ports, references, or funs (Erlang data types) from Y
@@ -236,6 +246,16 @@
in ERTS.</cell>
</row>
<row>
+ <cell>
+ <marker id="unique_ports"/>Unique Local Port Identifiers on a
+ Runtime System Instance
+ </cell>
+ <cell>
+ On a 64 bit system at most <c>2â¶â° - 1</c> unique port identifiers
+ can be created and on a 32 bit system at most <c>2²⸠- 1</c>.
+ </cell>
+ </row>
+ <row>
<cell><marker id="files_sockets"></marker>Open files and
sockets</cell>
<cell>The maximum number of simultaneously open files and
diff --git a/system/doc/efficiency_guide/binaryhandling.xml b/system/doc/efficiency_guide/binaryhandling.xml
index 1bcbaa2405..025e705933 100644
--- a/system/doc/efficiency_guide/binaryhandling.xml
+++ b/system/doc/efficiency_guide/binaryhandling.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2007</year>
- <year>2021</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -32,7 +32,12 @@
<file>binaryhandling.xml</file>
</header>
- <p>Binaries can be efficiently built in the following way:</p>
+ <p>This section gives a few examples on how to handle binaries in an
+ efficient way. The sections that follow take an in-depth look at how
+ binaries are implemented and how to best take advantages of the
+ optimizations done by the compiler and runtime system.</p>
+
+ <p>Binaries can be efficiently <em>built</em> in the following way:</p>
<p><em>DO</em></p>
<code type="erl"><![CDATA[
@@ -44,7 +49,50 @@ my_list_to_binary([H|T], Acc) ->
my_list_to_binary([], Acc) ->
Acc.]]></code>
- <p>Binaries can be efficiently matched like this:</p>
+ <p>Appending data to a binary as in the example is efficient because
+ it is specially optimized by the runtime system to avoid copying the
+ <c>Acc</c> binary every time.</p>
+
+ <p>Prepending data to a binary in a loop is not efficient:</p>
+
+ <p><em>DO NOT</em></p>
+ <code type="erl"><![CDATA[
+rev_list_to_binary(List) ->
+ rev_list_to_binary(List, <<>>).
+
+rev_list_to_binary([H|T], Acc) ->
+ rev_list_to_binary(T, <<H,Acc/binary>>);
+rev_list_to_binary([], Acc) ->
+ Acc.]]></code>
+
+ <p>This is not efficient for long lists because the <c>Acc</c>
+ binary is copied every time. One way to make the function more
+ efficient is like this:</p>
+
+ <p><em>DO NOT</em></p>
+ <code type="erl"><![CDATA[
+rev_list_to_binary(List) ->
+ rev_list_to_binary(lists:reverse(List), <<>>).
+
+rev_list_to_binary([H|T], Acc) ->
+ rev_list_to_binary(T, <<Acc/binary,H>>);
+rev_list_to_binary([], Acc) ->
+ Acc.]]></code>
+
+ <p>Another way to avoid copying the binary each time is like this:</p>
+
+ <p><em>DO</em></p>
+ <code type="erl"><![CDATA[
+rev_list_to_binary([H|T]) ->
+ RevTail = rev_list_to_binary(T),
+ <<RevTail/binary,H>>;
+rev_list_to_binary([]) ->
+ <<>>.]]></code>
+
+ <p>Note that in each of the <em>DO</em> examples, the binary to be
+ appended to is always given as the first segment.</p>
+
+ <p>Binaries can be efficiently <em>matched</em> in the following way:</p>
<p><em>DO</em></p>
<code type="erl"><![CDATA[
@@ -141,21 +189,29 @@ my_binary_to_list(<<>>) -> [].]]></code>
<section>
<title>Constructing Binaries</title>
- <p>Appending to a binary or bitstring
- is specially optimized by the <em>runtime system</em>:</p>
+ <p>Appending to a binary or bitstring in the following way
+ is specially optimized to avoid copying the binary:</p>
- <code type="erl"><![CDATA[
+ <code type="erl"><![CDATA[
<<Binary/binary, ...>>
+%% - OR -
<<Binary/bitstring, ...>>]]></code>
- <p>As the runtime system handles the optimization (instead of
- the compiler), there are very few circumstances in which the optimization
- does not work.</p>
+ <p>This optimization is applied by the runtime system in a way
+ that makes it effective in most circumstances (for exceptions,
+ see <seeguide marker="#forced_copying">Circumstances That Force
+ Copying</seeguide>). The optimization in its basic form does not
+ need any help from the compiler. However, the compiler add hints
+ to the runtime system when it is safe to apply the optimization in
+ a more efficient way.</p>
- <p>To explain how it works, let us examine the following code line
- by line:</p>
+ <change><p>The compiler support for making the optimization more
+ efficient was added in Erlang/OTP 26.</p></change>
- <code type="erl"><![CDATA[
+ <p>To explain how the basic optimization works, let us examine the
+ following code line by line:</p>
+
+ <code type="erl"><![CDATA[
Bin0 = <<0>>, %% 1
Bin1 = <<Bin0/binary,1,2,3>>, %% 2
Bin2 = <<Bin1/binary,4,5,6>>, %% 3
@@ -163,50 +219,94 @@ Bin3 = <<Bin2/binary,7,8,9>>, %% 4
Bin4 = <<Bin1/binary,17>>, %% 5 !!!
{Bin4,Bin3} %% 6]]></code>
- <list type="bulleted">
- <item>Line 1 (marked with the <c>%% 1</c> comment), assigns
- a <seeguide marker="#heap_binary">heap binary</seeguide> to
- the <c>Bin0</c> variable.</item>
-
- <item>Line 2 is an append operation. As <c>Bin0</c>
- has not been involved in an append operation,
- a new <seeguide marker="#refc_binary">refc binary</seeguide>
- is created and the contents of <c>Bin0</c> is copied
- into it. The <em>ProcBin</em> part of the refc binary has
- its size set to the size of the data stored in the binary, while
- the binary object has extra space allocated.
- The size of the binary object is either twice the
- size of <c>Bin1</c> or 256, whichever is larger. In this case
- it is 256.</item>
-
- <item>Line 3 is more interesting.
- <c>Bin1</c> <em>has</em> been used in an append operation,
- and it has 252 bytes of unused storage at the end, so the 3 new
- bytes are stored there.</item>
-
- <item>Line 4. The same applies here. There are 249 bytes left,
- so there is no problem storing another 3 bytes.</item>
-
- <item>Line 5. Here, something <em>interesting</em> happens. Notice
- that the result is not appended to the previous result in <c>Bin3</c>,
- but to <c>Bin1</c>. It is expected that <c>Bin4</c> will be assigned
- the value <c>&lt;&lt;0,1,2,3,17&gt;&gt;</c>. It is also expected that
- <c>Bin3</c> will retain its value
- (<c>&lt;&lt;0,1,2,3,4,5,6,7,8,9&gt;&gt;</c>).
- Clearly, the runtime system cannot write byte <c>17</c> into the binary,
- because that would change the value of <c>Bin3</c> to
- <c>&lt;&lt;0,1,2,3,4,17,6,7,8,9&gt;&gt;</c>.</item>
- </list>
-
- <p>The runtime system sees that <c>Bin1</c> is the result
- from a previous append operation (not from the latest append operation),
- so it <em>copies</em> the contents of <c>Bin1</c> to a new binary,
- reserve extra storage, and so on. (Here is not explained how the
- runtime system can know that it is not allowed to write into <c>Bin1</c>;
- it is left as an exercise to the curious reader to figure out how it is
- done by reading the emulator sources, primarily <c>erl_bits.c</c>.)</p>
+ <list type="bulleted">
+ <item><p>Line 1 (marked with the <c>%% 1</c> comment), assigns
+ a <seeguide marker="#heap_binary">heap binary</seeguide> to
+ the <c>Bin0</c> variable.</p></item>
+
+ <item><p>Line 2 is an append operation. As <c>Bin0</c>
+ has not been involved in an append operation,
+ a new <seeguide marker="#refc_binary">refc binary</seeguide>
+ is created and the contents of <c>Bin0</c> is copied
+ into it. The <em>ProcBin</em> part of the refc binary has
+ its size set to the size of the data stored in the binary, while
+ the binary object has extra space allocated.
+ The size of the binary object is either twice the
+ size of <c>Bin1</c> or 256, whichever is larger. In this case
+ it is 256.</p></item>
+
+ <item><p>Line 3 is more interesting.
+ <c>Bin1</c> <em>has</em> been used in an append operation,
+ and it has 252 bytes of unused storage at the end, so the 3 new
+ bytes are stored there.</p></item>
+
+ <item><p>Line 4. The same applies here. There are 249 bytes left,
+ so there is no problem storing another 3 bytes.</p></item>
+
+ <item><p>Line 5. Here something <em>interesting</em> happens. Notice
+ that the result is not appended to the previous result in <c>Bin3</c>,
+ but to <c>Bin1</c>. It is expected that <c>Bin4</c> will be assigned
+ the value <c>&lt;&lt;0,1,2,3,17&gt;&gt;</c>. It is also expected that
+ <c>Bin3</c> will retain its value
+ (<c>&lt;&lt;0,1,2,3,4,5,6,7,8,9&gt;&gt;</c>).
+ Clearly, the runtime system cannot write byte <c>17</c> into the binary,
+ because that would change the value of <c>Bin3</c> to
+ <c>&lt;&lt;0,1,2,3,4,17,6,7,8,9&gt;&gt;</c>.</p>
+
+ <p>To ensure that the value of <c>Bin3</c> is retained, the
+ runtime system <em>copies</em> the contents of <c>Bin1</c> to a
+ new refc binary before storing the <c>17</c> byte.</p>
+
+ <p>Here is not explained how the runtime system can know that it
+ is not allowed to write into <c>Bin1</c>; it is left as an
+ exercise to the curious reader to figure out how it is done by
+ reading the emulator sources, primarily <c>erl_bits.c</c>.</p>
+ </item>
+ </list>
+ <section>
+ <title>Compiler Support For Constructing Binaries</title>
+
+ <change><p>The compiler support for making the optimization more
+ efficient was added in Erlang/OTP 26.</p></change>
+
+ <p>In the example in the previous section, it was shown that
+ the runtime system can handle an append operation to a heap
+ binary by copying it to a refc binary (line 2), and also handle
+ an append operation to a previous version of the binary by
+ copying it (line 5). The support for doing that does not come
+ for free. For example, to make it possible to know when it is
+ necessary to copy the binary, for every append operation, the
+ runtime system must create a sub binary.</p>
+
+ <p>When the compiler can determine that none of those situations
+ need to be handled and that the append operation cannot possibly
+ fail, the compiler generates code that causes the runtime system to
+ apply a more efficient variant of the optimization.</p>
+
+ <p><strong>Example:</strong></p>
+
+ <code type="erl"><![CDATA[
+-module(repack).
+-export([repack/1]).
+
+repack(Bin) when is_binary(Bin) ->
+ repack(Bin, <<>>).
+
+repack(<<C:8,T/binary>>, Result) ->
+ repack(T, <<Result/binary,C:16>>);
+repack(<<>>, Result) ->
+ Result.]]></code>
+ </section>
+
+ <p>The <c>repack/2</c> function only keeps a single version of the
+ binary, so there is never any need to copy the binary. The
+ compiler rewrites the creation of the empty binary in
+ <c>repack/1</c> to instead create a refc binary with 256 bytes
+ already reserved; thus, the append operation in <c>repack/2</c>
+ never needs to handle a binary not prepared for appending.</p>
<section>
+ <marker id="forced_copying"></marker>
<title>Circumstances That Force Copying</title>
<p>The optimization of the binary append operation requires that
diff --git a/system/doc/efficiency_guide/maps.xml b/system/doc/efficiency_guide/maps.xml
index 140bdd549a..ce78863953 100644
--- a/system/doc/efficiency_guide/maps.xml
+++ b/system/doc/efficiency_guide/maps.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2021</year><year>2021</year>
+ <year>2021</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -37,6 +37,7 @@
finally the functions in the <seeerl
marker="stdlib:maps">maps</seeerl> module.</p>
+ <marker id="terminology"/>
<p>Terminology used in this chapter:</p>
<list type="bulleted">
<item>A map with at most 32 elements will informally be called a
@@ -107,10 +108,7 @@
there are default values, sharing of keys between different
instances of the map will be less effective, and it is not
possible to match multiple elements having default values in one
- go. The <c>maps:get/3</c> function is <seeguide
- marker="#maps_get_3">implemented in Erlang</seeguide>, making it
- less efficient than <c>maps:get/2</c> or the map matching
- syntax.</p></item>
+ go.</p></item>
<item><p>To avoid having to deal with a map that may lack some keys,
<seemfa marker="stdlib:maps#merge/2">maps:merge/2</seemfa>
@@ -487,31 +485,43 @@ new() ->
<section>
<marker id="maps_get_3"/>
<title>maps:get/3</title>
- <p><seemfa marker="stdlib:maps#get/3">maps:get/3</seemfa>
- is implemented in Erlang essentially like this:</p>
+ <p>As an optimization, the compiler will rewrite a call to <seemfa
+ marker="stdlib:maps#get/3">maps:get/3</seemfa> to Erlang code similar to
+ the following:</p>
<code type="erl"><![CDATA[
-get(Key, Map, Default) ->
- case Map of
- #{Key := Value} -> Value;
- #{} -> Default
- end.]]></code>
+ Result = case Map of
+ #{Key := Value} -> Value;
+ #{} -> Default
+ end]]></code>
- <p>Therefore, a call <c>maps:get/3</c> is more expensive than a
- call to <c>maps:get/2</c>.</p>
+ <p>This is reasonably efficient, but if a small map is used as an
+ alternative to using a record it is often better not to rely on default
+ values as it prevents sharing of keys, which may in the end use more
+ memory than what you save from not storing default values in the
+ map.</p>
- <p>If a small map is used as alternative to using a record,
- instead of calling <c>maps:get/3</c> multiple times to handle
- default values, consider putting the default values in a map and
- merging that map with the other map:</p>
+ <p>If default values are nevertheless required, instead of calling
+ <c>maps:get/3</c> multiple times, consider putting the default values
+ in a map and merging that map with the other map:</p>
<code type="erl"><![CDATA[
DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)]]></code>
- <p>Whether that is faster than calling <c>maps:get/3</c>
- multiple times depends on the size of the map and the number of
- default values.</p>
+ <p>This helps share keys between the default map and the one you applied
+ defaults to, as long as the default map contains <em>all</em> the keys
+ that will ever be used and not just the ones with default values.
+ Whether this is faster than calling <c>maps:get/3</c> multiple times
+ depends on the size of the map and the number of default values.</p>
+
+ <change>
+ <p>
+ Before OTP @OTP-18502@ <c>maps:get/3</c> was implemented by calling
+ the function instead of rewriting it as an Erlang expression. It is
+ now slightly faster but can no longer be traced.
+ </p>
+ </change>
</section>
<section>
@@ -580,7 +590,17 @@ get(Key, Map, Default) ->
<section>
<title>maps:merge/2</title>
<p><seemfa marker="stdlib:maps#merge/2">maps:merge/2</seemfa>
- is implemented in C.</p>
+ is implemented in C. For <seeguide marker="#terminology">small
+ maps</seeguide>, the key tuple may be shared with any of the argument
+ maps if that argument map contains all the keys. Literal key tuples are
+ prefered if possible.</p>
+ <change>
+ <p>
+ The sharing of key tuples by <c>maps:merge/2</c> was introduced in
+ OTP @OTP-18523@. Older versions always contructed a new key tuple on
+ the callers heap.
+ </p>
+ </change>
</section>
<section>
@@ -660,7 +680,7 @@ get(Key, Map, Default) ->
<p>If the keys are constants known at compile-time, using the
map update syntax with the <c>:=</c> operator is more
efficient than multiple calls to <c>maps:update/3</c>,
- especially for small maps.</p>
+ especially for <seeguide marker="#terminology">small maps</seeguide>.</p>
</section>
<section>
diff --git a/system/doc/efficiency_guide/profiling.xml b/system/doc/efficiency_guide/profiling.xml
index ebe1a978e4..68f5ce1f71 100644
--- a/system/doc/efficiency_guide/profiling.xml
+++ b/system/doc/efficiency_guide/profiling.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2001</year><year>2021</year>
+ <year>2001</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -101,7 +101,7 @@
<p>When looking at memory usage in a running system the most basic function
to get information from is <seemfa marker="erts:erlang#memory/0"><c>
erlang:memory()</c></seemfa>. It returns the current memory usage
- of the system. <seeerl marker="tools:instrument"><c>instrument(3)</c></seeerl>
+ of the system. <seeerl marker="runtime_tools:instrument"><c>instrument(3)</c></seeerl>
can be used to get a more detailed breakdown of where memory is used.</p>
<p>Processes, ports and ets tables can then be inspected using their
respective info functions, i.e.
diff --git a/system/doc/embedded/Makefile b/system/doc/embedded/Makefile
index 29f3f74564..7089ae1f03 100644
--- a/system/doc/embedded/Makefile
+++ b/system/doc/embedded/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2020. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ XMLDIR := $(XMLDIR)/embedded
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/embedded"
+RELSYSDIR = $(RELEASE_PATH)/doc/embedded
# ----------------------------------------------------
# Target Specs
@@ -100,7 +100,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/general_info/DEPRECATIONS b/system/doc/general_info/DEPRECATIONS
index 2f83cbb9d3..a576881e5d 100644
--- a/system/doc/general_info/DEPRECATIONS
+++ b/system/doc/general_info/DEPRECATIONS
@@ -18,6 +18,13 @@
#
#
+# Added in OTP 26.
+#
+file:pid2name/1 since=26 remove=27
+disk_log:inc_wrap_file/1 since=26 remove=28
+dbg:stop_clear/0 since=26 remove=27
+
+#
# Added in OTP 25.
#
slave:_/_ since=25 remove=27
@@ -69,8 +76,8 @@ ssl:cipher_suites/0 since=21 remove=24
http_uri:parse/1 since=23 remove=25
http_uri:parse/2 since=23 remove=25
-http_uri:encode/1 since=23 remove=26
-http_uri:decode/1 since=23 remove=26
+http_uri:encode/1 since=23 remove=27
+http_uri:decode/1 since=23 remove=27
http_uri:scheme_defaults/0 since=23 remove=25
httpd:parse_query/1 since=23
pg2:_/_ since=23 remove=24
diff --git a/system/doc/general_info/Makefile b/system/doc/general_info/Makefile
index 55cac49c1f..3fadfe712d 100644
--- a/system/doc/general_info/Makefile
+++ b/system/doc/general_info/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2021. All Rights Reserved.
+# Copyright Ericsson AB 1996-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ XMLDIR := $(XMLDIR)/general_info
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/general_info"
+RELSYSDIR = $(RELEASE_PATH)/doc/general_info
# ----------------------------------------------------
# Target Specs
@@ -108,7 +108,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/general_info/deprecations_24.inc b/system/doc/general_info/deprecations_24.inc
index d508aca7d3..3786cd1c6a 100644
--- a/system/doc/general_info/deprecations_24.inc
+++ b/system/doc/general_info/deprecations_24.inc
@@ -4,20 +4,17 @@
Communication over the Erlang distribution without support for large
<seeguide marker="erts:erl_dist_protocol#DFLAG_V4_NC">node container
data types (version 4)</seeguide> is as of OTP 24 deprecated and is
- <seeguide marker="scheduled_for_removal#otp-26">scheduled for removal
- in OTP 26</seeguide>. That is, as of OTP 26, support for large
- node container data types will become mandatory.
+ scheduled for removal in OTP 26. That is, as of OTP 26, support for
+ large node container data types will become mandatory.
</p>
</section>
<section>
<title>Old Link Protocol</title>
<p>
- The <seeguide marker="erts:erl_dist_protocol#old_link_protocol">old
- link protocol</seeguide> used when communicating over the Erlang
+ The old link protocol used when communicating over the Erlang
distribution is as of OTP 24 deprecated and support for it is
- <seeguide marker="scheduled_for_removal#otp-26">scheduled for removal
- in OTP 26</seeguide>. As of OTP 26, the
+ scheduled for removal in OTP 26. As of OTP 26, the
<seeguide marker="erts:erl_dist_protocol#new_link_protocol">new
link protocol</seeguide> will become mandatory. That is, Erlang nodes
will then refuse to connect to nodes not implementing the new
diff --git a/system/doc/general_info/removed_26.inc b/system/doc/general_info/removed_26.inc
new file mode 100644
index 0000000000..ea6a73bd13
--- /dev/null
+++ b/system/doc/general_info/removed_26.inc
@@ -0,0 +1,27 @@
+ <section>
+ <title>Erlang Distribution Without Large Node Container Support</title>
+ <p>
+ Communication over the Erlang distribution without support for large
+ <seeguide marker="erts:erl_dist_protocol#DFLAG_V4_NC"> node container
+ data types (version 4)</seeguide> was as of
+ <seeguide marker="deprecations#otp-24">OTP 24 deprecated</seeguide>
+ and support for it was scheduled for removal in OTP 26. That is, as
+ of OTP 26, support for large node container data types will become
+ mandatory. This also includes external term format produced by
+ <c>term_to_binary()</c>/<c>term_to_iovec()</c>.
+ </p>
+ </section>
+
+ <section>
+ <title>Old Link Protocol</title>
+ <p>
+ The old link protocol used when communicating over the Erlang
+ distribution was as of <seeguide marker="deprecations#otp-24">
+ OTP 24 deprecated</seeguide> and support for it was scheduled for
+ removal in OTP 26. As of OTP 26 the
+ <seeguide marker="erts:erl_dist_protocol#new_link_protocol">new
+ link protocol</seeguide> became mandatory. That is, Erlang nodes
+ will refuse to connect to nodes not implementing the new
+ link protocol.
+ </p>
+ </section>
diff --git a/system/doc/general_info/scheduled_for_removal_26.inc b/system/doc/general_info/scheduled_for_removal_26.inc
deleted file mode 100644
index 1d59136ce5..0000000000
--- a/system/doc/general_info/scheduled_for_removal_26.inc
+++ /dev/null
@@ -1,30 +0,0 @@
- <section>
- <title>Erlang Distribution Without Large Node Container Support</title>
- <p>
- Communication over the Erlang distribution without support for large
- <seeguide marker="erts:erl_dist_protocol#DFLAG_V4_NC"> node container
- data types (version 4)</seeguide> is as of
- <seeguide marker="deprecations#otp-24">OTP 24 deprecated</seeguide>
- and support for it is scheduled for removal in OTP 26. That is, as
- of OTP 26, support for large node container data types will become
- mandatory.
- </p>
- </section>
-
- <section>
- <title>Old Link Protocol</title>
- <p>
- The <seeguide marker="erts:erl_dist_protocol#old_link_protocol">old
- link protocol</seeguide> used when communicating over the Erlang
- distribution is as of <seeguide marker="deprecations#otp-24">
- OTP 24 deprecated</seeguide> and support for it is scheduled for
- removal in OTP 26. As of OTP 26 the
- <seeguide marker="erts:erl_dist_protocol#new_link_protocol">new
- link protocol</seeguide> will become mandatory. That is, Erlang nodes
- will then refuse to connect to nodes not implementing the new
- link protocol. If you implement the Erlang distribution yourself, you
- are, however, encouraged to implement the new link protocol as soon as
- possible since the old protocol can cause links to enter an
- inconsistent state.
- </p>
- </section>
diff --git a/system/doc/general_info/scheduled_for_removal_27.inc b/system/doc/general_info/scheduled_for_removal_27.inc
new file mode 100644
index 0000000000..e97149d937
--- /dev/null
+++ b/system/doc/general_info/scheduled_for_removal_27.inc
@@ -0,0 +1,14 @@
+ <section>
+ <title>Vanilla Driver</title>
+ <p>
+ The old previously documented support for opening a port to an
+ external resource by passing an atom (or a string) as first
+ argument to
+ <seemfa marker="erts:erlang#open_port/2"><c>open_port()</c></seemfa>,
+ implemented by the vanilla driver, will be removed in OTP 27.
+ This functionality was marked as obsolete about two decades ago
+ and then a few years later the documentation for it was removed.
+ If this functionality is not used with care it might hang or crash
+ the runtime system.
+ </p>
+ </section>
diff --git a/system/doc/general_info/upcoming_incompatibilities.xml b/system/doc/general_info/upcoming_incompatibilities.xml
index 59003d2243..8538067b57 100644
--- a/system/doc/general_info/upcoming_incompatibilities.xml
+++ b/system/doc/general_info/upcoming_incompatibilities.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2021</year><year>2022</year>
+ <year>2021</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -36,114 +36,182 @@
</section>
<section>
- <title>OTP 25</title>
- <section>
- <title>Distribution flags will become mandatory</title>
- <p>In OTP 25, more <seeguide
- marker="erts:erl_dist_protocol#dflags">distribution
- flags</seeguide> will become mandatory. That is, Erlang nodes
- will refuse to connect to nodes not implementing all of the
- mandatory distribution flags. If you implement the Erlang
- distribution protocol yourself, you will need to implement
- support for all mandatory distribution flags in order to
- communicate with Erlang nodes running OTP 25.</p>
- <p>The following distribution flags will become mandatory in OTP
- 25:</p>
- <taglist>
- <tag><c>DFLAG_BIT_BINARIES</c></tag>
- <item>Support for bitstrings.</item>
- <tag><c>DFLAG_EXPORT_PTR_TAG</c></tag>
- <item>Support for external funs (<c>fun Module:Name/Arity</c>).</item>
- <tag><c>DFLAG_MAP_TAGS</c></tag>
- <item>Support for maps.</item>
- <tag><c>DFLAG_NEW_FLOATS</c></tag>
- <item>Support for the new encoding of floats.</item>
- <tag><c>DFLAG_FUN_TAGS</c></tag>
- <item>Support for funs, but only in the new format
- (<c>NEW_FUN_EXT</c>) because <c>DFLAG_NEW_FUN_TAGS</c> is also
- mandatory.</item>
- </taglist>
- </section>
- </section>
-
- <section>
- <title>OTP 26</title>
- <section>
- <title>The distribution flag DFLAG_V4_NC will become mandatory</title>
- <p>As of OTP 26, the distribution flag <seeguide
- marker="erts:erl_dist_protocol#DFLAG_V4_NC">DFLAG_V4_NC</seeguide>
- will become mandatory. If you implement the Erlang distribution
- protocol yourself, you will need to implement support for
- <c>DFLAG_V4_NC</c> in order to communicate with Erlang nodes
- running OTP 26.</p>
- </section>
+ <title>OTP 27</title>
<section>
- <title>The new link protocol will become mandatory</title>
+ <marker id="fun_creator_pid"/>
+ <title>Fun creator pid will always be local init process</title>
<p>
- As of OTP 26, the <seeguide
- marker="erts:erl_dist_protocol#new_link_protocol">new link
- protocol</seeguide> will become mandatory. That is, Erlang
- nodes will then refuse to connect to nodes not implementing
- the new link protocol. If you implement the Erlang
- distribution yourself, you are, however, encouraged to
- implement the new link protocol as soon as possible since the
- old protocol can cause links to enter an inconsistent state.
+ As of OTP 27, the functions
+ <seemfa marker="erts:erlang#fun_info/1">
+ <c>erlang:fun_info/1,2</c></seemfa> will always say that the local
+ <c>init</c> process created all funs, regardless of which process or
+ node the fun was originally created on.
</p>
+ <p>
+ In OTP 28, the <c>{pid,_}</c>element will be removed altogether.</p>
</section>
<section>
- <marker id="atoms_be_utf8"/>
- <title>Atoms will be encoded as UTF-8 by default</title>
+ <marker id="maybe_expr"/>
+ <title>Feature maybe_expr will be enabled by default</title>
<p>
- As of OTP 26, the functions
- <seemfa marker="erts:erlang#term_to_binary/1">
- <c>erlang:term_to_binary/1,2</c></seemfa> and
- <seemfa marker="erts:erlang#term_to_iovec/1">
- <c>erlang:term_to_iovec/1,2</c></seemfa> will encode all atoms as
- UTF-8 by default. The current default behavior is to encode atoms as
- Latin-1 if possible.
+ As of OTP 27, the <c>maybe_expr</c> feature will be approved
+ and enabled by default. That means that code that uses the
+ unquoted atom <c>maybe</c> will fail to compile. All uses of
+ <c>maybe</c> as an atom will need to be quoted. Alternatively, as a
+ short-term solution, the <c>maybe_expr</c> feature can be
+ disabled.
</p>
<p>
- If you implement your own decoding of the Erlang external format you
- must either:
+ It is recommend to quote all uses of the atom <c>maybe</c> as soon as
+ possible. The compiler option <c>warn_keywords</c> can be used to emit
+ warnings about all occurrences of <c>maybe</c> without quotes.
</p>
- <list type="bulleted">
- <item>
- <p>
- Make sure your implementation supports the UTF-8 encodings
- <seeguide marker="erts:erl_ext_dist#ATOM_UTF8_EXT">
- <c>ATOM_UTF8_EXT</c></seeguide> and
- <seeguide marker="erts:erl_ext_dist#SMALL_ATOM_UTF8_EXT">
- <c>SMALL_ATOM_UTF8_EXT</c></seeguide>.
- </p>
- </item>
- <item>
- <p>
- Call <seemfa marker="erts:erlang#term_to_binary/2">
- <c>erlang:term_to_binary/2</c></seemfa> or
- <seemfa marker="erts:erlang#term_to_iovec/2">
- <c>erlang:term_to_iovec/2</c></seemfa>
- with option <c>{minor_version,1}</c> to force Latin-1 encoding. This
- is a more short-term solution as Latin-1 encoding may be phased out
- and removed in later OTP releases.
- </p>
- </item>
+ </section>
+
+ <section>
+ <marker id="new_re_engine"/>
+ <title>The re module will use a different regular expression engine</title>
+
+ <p>The functionality of module <seeerl
+ marker="stdlib:re"><c>re</c></seeerl> is currently provided by
+ the PCRE library, which is no longer actively
+ maintained. Therefore, in OTP 27, we will switch to a different
+ regular expression library.</p>
+
+ <p>The source code for PCRE used by the <c>re</c> module has
+ been modified by the OTP team to ensure that a regular
+ expression match would yield when matching huge input binaries
+ and/or when using demanding (back-tracking) regular
+ expressions. Because of the those modifications, moving to a new
+ version of PCRE has always been a time-consuming process because
+ all of the modifications had to be applied by hand again to the
+ updated PCRE source code.</p>
+
+ <p>Most likely, the new regular expression library will be <url
+ href="https://github.com/google/re2">RE2</url>. RE2 guarantees
+ that the match time is linear in the length of input string, and
+ it also eschews recursion to avoid stack overflow. That should
+ make it possible to use RE2 without modifying its source
+ code. For more information about why RE2 is a good choice, see
+ <url
+ href="https://github.com/google/re2/wiki/WhyRE2">WhyRE2</url>.</p>
+
+ <p>Some of implications of this change are:</p>
+
+ <list>
+ <item><p>We expect that the functions in the <c>re</c> module
+ will continue to be supported, although some of the options are likely
+ to be dis-continued.</p></item>
+
+ <item><p>It is likely that only pattern matching of UTF8-encoded binaries will be
+ supported (not Latin1-encoded binaries).</p></item>
+
+ <item><p>In order to guarantee the linear-time performance,
+ RE2 does not support all the constructs in regular expression
+ patterns that PCRE do. For example, backreferences and look-around
+ assertions are not supported. See <url
+ href="https://github.com/google/re2/wiki/Syntax">Syntax</url>
+ for a description of what RE2 supports.</p></item>
+
+ <item><p>Compiling a regular expression is likely to be
+ slower, and thus more can be gained by explicitly compiling
+ the regular expression before matching with it.</p></item>
</list>
</section>
<section>
- <title>The default timewarp mode will change to multi-time warp mode</title>
+ <marker id="float_matching"/>
+ <title>0.0 and -0.0 will no longer be exactly equal</title>
+
+ <p>Currently, the floating point numbers <c>0.0</c> and <c>-0.0</c>
+ have distinct internal representations. That can be seen if they are
+ converted to binaries:</p>
+ <pre>
+1&gt; <input>&lt;&lt;0.0/float&gt;&gt;.</input>
+&lt;&lt;0,0,0,0,0,0,0,0&gt;&gt;
+2&gt; <input>&lt;&lt;-0.0/float&gt;>.</input>
+&lt;&lt;128,0,0,0,0,0,0,0>></pre>
+
+ <p>However, when they are matched against each other or compared
+ using the <c>=:=</c> operator, they are considered to be
+ equal. Thus, <c>0.0 =:= -0.0</c> currently returns
+ <c>true</c>.</p>
+
+ <p>In Erlang/OTP 27, <c>0.0 =:= -0.0</c> will return <c>false</c>, and matching
+ <c>0.0</c> against <c>-0.0</c> will fail. When used as map keys, <c>0.0</c> and
+ <c>-0.0</c> will be considered to be distinct.</p>
+
+ <p>The <c>==</c> operator will continue to return <c>true</c>
+ for <c>0.0 == -0.0</c>.</p>
+
+ <p>To help to find code that might need to be revised, in OTP 27
+ there will be a new compiler warning when matching against
+ <c>0.0</c> or comparing to that value using the <c>=:=</c>
+ operator. The warning can be suppressed by matching against
+ <c>+0.0</c> instead of <c>0.0</c>.</p>
+
+ <p>We plan to introduce the same warning in OTP 26.1, but by default it
+ will be disabled.</p>
+ </section>
+
+ <section>
+ <marker id="singleton_typevars"/>
+ <title>Singleton type variables will become a compile-time error</title>
+
+ <p>Before Erlang/OTP 26, the compiler would silenty accept the
+ following spec:</p>
+
+ <pre>
+-spec f(Opts) -> term() when
+ Opts :: {ok, Unknown} | {error, Unknown}.
+f(_) -> error.</pre>
+
+ <p>In OTP 26, the compiler emits a warning pointing out that the type variable
+ <c>Unknown</c> is unbound:</p>
+
+ <pre>
+t.erl:6:18: Warning: type variable 'Unknown' is only used once (is unbound)
+% 6| Opts :: {ok, Unknown} | {error, Unknown}.
+% | ^</pre>
+
+ <p>In OTP 27, that warning will become an error.</p>
+ </section>
+
+ </section>
+
+ <section>
+ <title>OTP 28</title>
+
+ <section>
+ <marker id="fun_creator_pid"/>
+ <title>Fun creator pid will be removed</title>
<p>
- The default <seeguide marker="erts:time_correction#Time_Warp_Modes">
- Time Warp Mode</seeguide> will be changed from
- <seeguide marker="erts:time_correction#No_Time_Warp_Mode">
- no time warp mode</seeguide> to <seeguide marker="erts:time_correction#Multi_Time_Warp_Mode">
- multi-time warp mode</seeguide>. See <seeguide marker="erts:time_correction">
- Time and Time Correction in Erlang</seeguide> for details on how this will
- effect your system.
- </p>
+ As of OTP 28, the function <seemfa
+ marker="erts:erlang#fun_info/1"><c>erlang:fun_info/1</c></seemfa>
+ will not include the <c>{pid,_}</c> element and the function
+ <seemfa marker="erts:erlang#fun_info/2"><c>erlang:fun_info/2</c></seemfa>
+ will no longer accept <c>pid</c> as the second argument.</p>
</section>
+ </section>
+ <section>
+ <title>OTP 29</title>
+
+ <section>
+ <title>It will no longer be possible to disable feature maybe_expr</title>
+ <p>
+ As of OTP 29, the <c>maybe_expr</c> feature will become
+ permanent and no longer possible to disable. All uses of
+ <c>maybe</c> as an atom will need to be quoted.
+ </p>
+ <p>
+ It is recommend to quote all uses of the atom <c>maybe</c> as soon as
+ possible. The compiler option <c>warn_keywords</c> can be used to emit
+ warnings about all occurrences of <c>maybe</c> without quotes.
+ </p>
+ </section>
</section>
+
</chapter>
diff --git a/system/doc/getting_started/Makefile b/system/doc/getting_started/Makefile
index 6a286d6df6..045e903399 100644
--- a/system/doc/getting_started/Makefile
+++ b/system/doc/getting_started/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2020. All Rights Reserved.
+# Copyright Ericsson AB 1996-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ XMLDIR := $(XMLDIR)/getting_started
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/getting_started"
+RELSYSDIR = $(RELEASE_PATH)/doc/getting_started
# ----------------------------------------------------
# Target Specs
@@ -99,7 +99,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/installation_guide/Makefile b/system/doc/installation_guide/Makefile
index 53a1cad319..17674c614c 100644
--- a/system/doc/installation_guide/Makefile
+++ b/system/doc/installation_guide/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2021. All Rights Reserved.
+# Copyright Ericsson AB 1996-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@ XMLDIR := $(XMLDIR)/installation_guide
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/installation_guide"
+RELSYSDIR = $(RELEASE_PATH)/doc/installation_guide
REDIRECT_HTML_RELSYSDIR = $(RELSYSDIR)/source
# ----------------------------------------------------
@@ -114,9 +114,8 @@ debug opt:
clean clean_docs:
rm -f $(GENERATED_XML_FILES)
rm -f $(XMLDIR)/*.xml
- rm -f $(HTMLDIR)/*.gif $(HTMLDIR)/*.html
+ rm -f $(HTMLDIR)/*/*.gif $(HTMLDIR)/*/*.html
rm -f $(XML_GEN_FILES)
- rm -rf $(HTMLDIR)
rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
rm -f errs core *~
@@ -127,9 +126,9 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
- $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html $(RELSYSDIR)
- $(INSTALL_DIR) $(REDIRECT_HTML_RELSYSDIR)
- $(INSTALL_DATA) $(REDIRECT_HTML_FILES) $(REDIRECT_HTML_RELSYSDIR)
+ $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html "$(RELSYSDIR)"
+ $(INSTALL_DIR) "$(REDIRECT_HTML_RELSYSDIR)"
+ $(INSTALL_DATA) $(REDIRECT_HTML_FILES) "$(REDIRECT_HTML_RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/oam/Makefile b/system/doc/oam/Makefile
index 40b14485da..c8e5755ad0 100644
--- a/system/doc/oam/Makefile
+++ b/system/doc/oam/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2020. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ XMLDIR := $(XMLDIR)/oam
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/oam"
+RELSYSDIR = $(RELEASE_PATH)/doc/oam
# ----------------------------------------------------
# Target Specs
@@ -101,7 +101,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/programming_examples/Makefile b/system/doc/programming_examples/Makefile
index 7099d88ebc..0a2c18fcae 100644
--- a/system/doc/programming_examples/Makefile
+++ b/system/doc/programming_examples/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2003-2020. All Rights Reserved.
+# Copyright Ericsson AB 2003-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ XMLDIR := $(XMLDIR)/programming_examples
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/programming_examples"
+RELSYSDIR = $(RELEASE_PATH)/doc/programming_examples
# ----------------------------------------------------
# Target Specs
@@ -99,7 +99,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/reference_manual/Makefile b/system/doc/reference_manual/Makefile
index 2e0cb4fbc2..c51496ca5d 100644
--- a/system/doc/reference_manual/Makefile
+++ b/system/doc/reference_manual/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2003-2020. All Rights Reserved.
+# Copyright Ericsson AB 2003-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@ XMLDIR := $(XMLDIR)/reference_manual
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/reference_manual"
+RELSYSDIR = $(RELEASE_PATH)/doc/reference_manual
# ----------------------------------------------------
# Target Specs
@@ -102,7 +102,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/reference_manual/character_set.xml b/system/doc/reference_manual/character_set.xml
index 6d37700da3..53ff4dd1bc 100644
--- a/system/doc/reference_manual/character_set.xml
+++ b/system/doc/reference_manual/character_set.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2014</year><year>2021</year>
+ <year>2014</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -41,7 +41,7 @@
shown without the escape backslash convention.</p>
</item>
<item>
- <p>Atoms and variables can use all Latin-1 letters.</p>
+ <p>Unquoted atoms and variables can use all Latin-1 letters.</p>
</item>
</list>
<table>
@@ -101,17 +101,35 @@
</row>
<tcaption>Character Classes</tcaption>
</table>
- <p>In Erlang/OTP R16B the syntax of Erlang tokens was extended to
- handle Unicode. The support was limited to
- string literals and comments.
- More about the usage of Unicode in Erlang source files
- can be found in <seeguide
- marker="stdlib:unicode_usage#unicode_in_erlang">STDLIB's User's
- Guide</seeguide>.</p>
- <p>From Erlang/OTP 20, atoms and function names are also allowed
- to contain Unicode characters outside the ISO-Latin-1 range.
- Module names, application names, and node names are still
- restricted to the ISO-Latin-1 range.</p>
+
+ <p>The following tokens are allowed to also use Unicode characters
+ outside of the Latin-1 range:</p>
+
+ <list type="bulleted">
+ <item>
+ <p>String literals. Example: <c>"√π"</c></p>
+ </item>
+ <item>
+ <p>Character literals. Example: <c>$∑</c></p>
+ </item>
+ <item>
+ <p>Comments in code.</p>
+ </item>
+ <item>
+ <p>Quoted atoms. Example: <c>'μs'</c></p>
+ </item>
+ <item>
+ <p>Function names. Example: <c>'s_to_μs'(S) -> S * 1_000_000.</c></p>
+ </item>
+ </list>
+
+ <p>Atoms used as module names, application names, and node names are
+ restricted to the Latin-1 range.</p>
+
+ <change><p>Support for Unicode in string literals, character literals,
+ and comments was introduced in Erlang/OTP R16B. Support for Unicode in
+ atom and function names was introduced in Erlang/OTP 20.</p>
+ </change>
</section>
<section>
<title>Source File Encoding</title>
@@ -123,15 +141,17 @@
the matching string is an invalid encoding, it is ignored. The
valid encodings are <c>Latin-1</c> and <c>UTF-8</c>, where the
case of the characters can be chosen freely.</p>
- <p>The following example selects UTF-8 as default encoding:</p>
- <pre>
-%% coding: utf-8</pre>
- <p>Two more examples, both selecting Latin-1 as default encoding:</p>
+
+ <p>The default Erlang source file encoding if no valid
+ <c>coding</c> comment is present is UTF-8.</p>
+
+ <p>Two examples, both selecting Latin-1 as the source file encoding:</p>
<pre>
%% For this file we have chosen encoding = Latin-1</pre>
<pre>
%% -*- coding: latin-1 -*-</pre>
- <p>The default encoding for Erlang source files is changed from
- Latin-1 to UTF-8 since Erlang/OTP 17.0.</p>
+
+ <change><p>The default encoding for Erlang source files was changed from
+ Latin-1 to UTF-8 in Erlang/OTP 17.0.</p></change>
</section>
</chapter>
diff --git a/system/doc/reference_manual/code_loading.xml b/system/doc/reference_manual/code_loading.xml
index b64b2eecf9..5bc4332c72 100644
--- a/system/doc/reference_manual/code_loading.xml
+++ b/system/doc/reference_manual/code_loading.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2021</year>
+ <year>2003</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -157,12 +157,12 @@ loop() ->
before the <c>on_load</c> function has finished will be suspended
until the <c>on_load</c> function have finished.</p>
- <note>
- <p>Before OTP 19, if the <c>on_load</c> function failed, any
+ <change>
+ <p>Before Erlang/OTP 19, if the <c>on_load</c> function failed, any
previously current code would become old, essentially leaving
the system without any working and reachable instance of the
- module. That problem has been eliminated in OTP 19.</p>
- </note>
+ module.</p>
+ </change>
<p>In embedded mode, first all modules are loaded.
Then all <c>on_load</c> functions are called. The system is
diff --git a/system/doc/reference_manual/data_types.xml b/system/doc/reference_manual/data_types.xml
index 1a4bdc5680..6cbf864a79 100644
--- a/system/doc/reference_manual/data_types.xml
+++ b/system/doc/reference_manual/data_types.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2022</year>
+ <year>2003</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -305,9 +305,10 @@ adam
in STDLIB.</p>
<p>Read more about maps in <seeguide marker="expressions#map_expressions">
Map Expressions</seeguide>.</p>
- <note>
- <p>Maps are considered to be experimental during Erlang/OTP R17.</p>
- </note>
+ <change>
+ <p>Maps were introduced as an experimental feature in Erlang/OTP R17. Their
+ functionality was extended and became fully supported in Erlang/OTP 18.</p>
+ </change>
</section>
<section>
@@ -421,75 +422,109 @@ true</pre>
<cell align="left" valign="middle"><em>Description</em></cell>
</row>
<row>
- <cell align="left" valign="middle">\b</cell>
- <cell align="left" valign="middle">Backspace</cell>
+ <cell align="left" valign="middle"><c>\b</c></cell>
+ <cell align="left" valign="middle">Backspace (ASCII code 8)</cell>
</row>
<row>
- <cell align="left" valign="middle">\d</cell>
- <cell align="left" valign="middle">Delete</cell>
+ <cell align="left" valign="middle"><c>\d</c></cell>
+ <cell align="left" valign="middle">Delete (ASCII code 127)</cell>
</row>
<row>
- <cell align="left" valign="middle">\e</cell>
- <cell align="left" valign="middle">Escape</cell>
+ <cell align="left" valign="middle"><c>\e</c></cell>
+ <cell align="left" valign="middle">Escape (ASCII code 27)</cell>
</row>
<row>
- <cell align="left" valign="middle">\f</cell>
- <cell align="left" valign="middle">Form feed</cell>
+ <cell align="left" valign="middle"><c>\f</c></cell>
+ <cell align="left" valign="middle">Form Feed (ASCII code 12)</cell>
</row>
<row>
- <cell align="left" valign="middle">\n</cell>
- <cell align="left" valign="middle">Newline</cell>
+ <cell align="left" valign="middle"><c>\n</c></cell>
+ <cell align="left" valign="middle">Line Feed/Newline (ASCII code 10)</cell>
</row>
<row>
- <cell align="left" valign="middle">\r</cell>
- <cell align="left" valign="middle">Carriage return</cell>
+ <cell align="left" valign="middle"><c>\r</c></cell>
+ <cell align="left" valign="middle">Carriage Return (ASCII code 13)</cell>
</row>
<row>
- <cell align="left" valign="middle">\s</cell>
- <cell align="left" valign="middle">Space</cell>
+ <cell align="left" valign="middle"><c>\s</c></cell>
+ <cell align="left" valign="middle">Space (ASCII code 32)</cell>
</row>
<row>
- <cell align="left" valign="middle">\t</cell>
- <cell align="left" valign="middle">Tab</cell>
+ <cell align="left" valign="middle"><c>\t</c></cell>
+ <cell align="left" valign="middle">(Horizontal) Tab (ASCII code 9)</cell>
</row>
<row>
- <cell align="left" valign="middle">\v</cell>
- <cell align="left" valign="middle">Vertical tab</cell>
+ <cell align="left" valign="middle"><c>\v</c></cell>
+ <cell align="left" valign="middle">Vertical Tab (ASCII code 11)</cell>
</row>
<row>
- <cell align="left" valign="middle">\XYZ, \YZ, \Z</cell>
+ <cell align="left" valign="middle"><c>\</c>XYZ, <c>\</c>YZ, <c>\</c>Z</cell>
<cell align="left" valign="middle">Character with octal
representation XYZ, YZ or Z</cell>
</row>
<row>
- <cell align="left" valign="middle">\xXY</cell>
+ <cell align="left" valign="middle"><c>\xXY</c></cell>
<cell align="left" valign="middle">Character with hexadecimal
representation XY</cell>
</row>
<row>
- <cell align="left" valign="middle">\x{X...}</cell>
+ <cell align="left" valign="middle"><c>\x{</c>X...<c>}</c></cell>
<cell align="left" valign="middle">Character with hexadecimal
representation; X... is one or more hexadecimal characters</cell>
</row>
<row>
- <cell align="left" valign="middle">\^a...\^z <br></br>
-\^A...\^Z</cell>
+ <cell align="left" valign="middle"><c>\^a</c>...<c>\^z</c> <br></br>
+<c>\^A</c>...<c>\^Z</c></cell>
<cell align="left" valign="middle">Control A to control Z</cell>
</row>
<row>
- <cell align="left" valign="middle">\'</cell>
+ <cell align="left" valign="middle"><c>\^@</c></cell>
+ <cell align="left" valign="middle">NUL (ASCII code 0)</cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"><c>\^[</c></cell>
+ <cell align="left" valign="middle">Escape (ASCII code 27)</cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"><c>\^\</c></cell>
+ <cell align="left" valign="middle">File Separator (ASCII code 28)</cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"><c>\^]</c></cell>
+ <cell align="left" valign="middle">Group Separator (ASCII code 29)</cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"><c>\^^</c></cell>
+ <cell align="left" valign="middle">Record Separator (ASCII code 30)</cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"><c>\^_</c></cell>
+ <cell align="left" valign="middle">Unit Separator (ASCII code 31)</cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"><c>\^?</c></cell>
+ <cell align="left" valign="middle">Delete (ASCII code 127)</cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"><c>\'</c></cell>
<cell align="left" valign="middle">Single quote</cell>
</row>
<row>
- <cell align="left" valign="middle">\"</cell>
+ <cell align="left" valign="middle"><c>\"</c></cell>
<cell align="left" valign="middle">Double quote</cell>
</row>
<row>
- <cell align="left" valign="middle">\\</cell>
+ <cell align="left" valign="middle"><c>\\</c></cell>
<cell align="left" valign="middle">Backslash</cell>
</row>
<tcaption>Recognized Escape Sequences</tcaption>
</table>
+
+
+ <change><p>As of Erlang/OTP 26, the value of <c>$\^?</c> has been
+ changed to be 127 (Delete), instead of 31. Previous releases
+ would allow any character following <c>$\^</c>; as of Erlang/OTP
+ 26, only the documented characters are allowed.</p></change>
</section>
<section>
diff --git a/system/doc/reference_manual/distributed.xml b/system/doc/reference_manual/distributed.xml
index f8fb159be3..d4cb47dd0d 100644
--- a/system/doc/reference_manual/distributed.xml
+++ b/system/doc/reference_manual/distributed.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2022</year>
+ <year>2003</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -167,14 +167,14 @@ dilbert@uab</pre>
can be made to get a new dynamic node name. The node name may
change if the distribution is dropped and then set up again.
</p>
- <note>
+ <change>
<p>
- The <em>dynamic node name</em> feature is supported from OTP 23.
+ The <em>dynamic node name</em> feature is supported from Erlang/OTP 23.
Both the temporary client node and the first connected peer node
- (supplying the dynamic node name) must be at least OTP 23 for it to
+ (supplying the dynamic node name) must be at least Erlang/OTP 23 for it to
work.
</p>
- </note>
+ </change>
</section>
<section>
diff --git a/system/doc/reference_manual/expressions.xml b/system/doc/reference_manual/expressions.xml
index af143ad4d5..6b3ba96b6b 100644
--- a/system/doc/reference_manual/expressions.xml
+++ b/system/doc/reference_manual/expressions.xml
@@ -138,18 +138,22 @@ member(_Elem, []) ->
Name1
[H|T]
{error,Reason}</pre>
- <p>Patterns are allowed in clause heads, <c>case</c> and
- <c>receive</c> expressions, and match expressions.</p>
+ <p>Patterns are allowed in clause heads,
+ <seeguide marker="#case">case expressions</seeguide>,
+ <seeguide marker="#receive">receive expressions</seeguide>,
+ and
+ <seeguide marker="#match_operator">match expressions</seeguide>.</p>
<section>
- <title>Match Operator = in Patterns</title>
+ <marker id="compound_pattern_operator"></marker>
+ <title>The Compound Pattern Operator</title>
<p>If <c>Pattern1</c> and <c>Pattern2</c> are valid patterns,
the following is also a valid pattern:</p>
<pre>
Pattern1 = Pattern2</pre>
<p>When matched against a term, both <c>Pattern1</c> and
- <c>Pattern2</c> are matched against the term. The idea
- behind this feature is to avoid reconstruction of terms.</p>
+ <c>Pattern2</c> are matched against the term. The idea behind
+ this feature is to avoid reconstruction of terms.</p>
<p><em>Example:</em></p>
<pre>
f({connect,From,To,Number,Options}, To) ->
@@ -163,6 +167,11 @@ f({connect,_,To,_,_} = Signal, To) ->
...;
f(Signal, To) ->
ignore.</pre>
+
+ <p>The compound pattern operator does not imply that its operands
+ are matched in any particular order. That means that it is not
+ legal to bind a variable in <c>Pattern1</c> and use it in <c>Pattern2</c>,
+ or vice versa.</p>
</section>
<section>
@@ -192,22 +201,121 @@ case {Value, Result} of
</section>
<section>
- <title>Match</title>
- <p>The following matches <c>Expr1</c>, a pattern, against
- <c>Expr2</c>:</p>
+ <marker id="match_operator"></marker>
+ <title>The Match Operator</title>
+ <p>The following matches <c>Pattern</c> against
+ <c>Expr</c>:</p>
<pre>
-Expr1 = Expr2</pre>
+Pattern = Expr</pre>
<p>If the matching succeeds, any unbound variable in the pattern
- becomes bound and the value of <c>Expr2</c> is returned.</p>
+ becomes bound and the value of <c>Expr</c> is returned.</p>
+ <p>If multiple match operators are applied in sequence, they will be
+ evaluated from right to left.</p>
<p>If the matching fails, a <c>badmatch</c> run-time error occurs.</p>
<p><em>Examples:</em></p>
<pre>
-1> <input>{A, B} = {answer, 42}.</input>
+1> <input>{A, B} = T = {answer, 42}.</input>
{answer,42}
2> <input>A.</input>
answer
-3> <input>{C, D} = [1, 2].</input>
+3> <input>B.</input>
+42
+4> <input>T.</input>
+{answer,42}
+5> <input>{C, D} = [1, 2].</input>
** exception error: no match of right-hand side value [1,2]</pre>
+
+ <p>Because multiple match operators are evaluated from right to left,
+ it means that:</p>
+
+ <pre>
+Pattern1 = Pattern2 = . . . = PatternN = Expression</pre>
+
+ <p>is equivalent to:</p>
+ <pre>
+Temporary = Expression,
+PatternN = Temporary,
+ .
+ .
+ .,
+Pattern2 = Temporary,
+Pattern = Temporary</pre>
+ </section>
+
+ <section>
+ <title>The Match Operator and the Compound Pattern Operator</title>
+ <note><p>This is an advanced section, which references to topics not
+ yet introduced. It can safely be skipped on a first
+ reading.</p></note>
+
+ <p>The <c>=</c> character is used to denote two similar but
+ distinct operators: the match operator and the compound pattern
+ operator. Which one is meant is determined by context.</p>
+
+ <p>The <em>compound pattern operator</em> is used to construct a
+ compound pattern from two patterns. Compound patterns are accepted
+ everywhere a pattern is accepted. A compound pattern matches if
+ all of its constituent patterns match. It is not legal for a
+ pattern that is part of a compound pattern to use variables (as
+ keys in map patterns or sizes in binary patterns) bound in other
+ sub patterns of the same compound pattern.</p>
+ <p><em>Examples:</em></p>
+
+ <pre>
+1> <input>fun(#{Key := Value} = #{key := Key}) -> Value end.</input>
+* 1:7: variable 'Key' is unbound
+2> <input>F = fun({A, B} = E) -> {E, A + B} end, F({1,2}).</input>
+{{1,2},3}
+3> <input>G = fun(&lt;&lt;A:8,B:8>> = &lt;&lt;C:16>>) -> {A, B, C} end, G(&lt;&lt;42,43>>).</input>
+{42,43,10795}</pre>
+
+ <p>The <em>match operator</em> is allowed everywhere an expression
+ is allowed. It is used to match the value of an expression to a pattern.
+ If multiple match operators are applied in sequence, they will be
+ evaluated from right to left.</p>
+
+ <p><em>Examples:</em></p>
+ <pre>
+1> <input>M = #{key => key2, key2 => value}.</input>
+#{key => key2,key2 => value}
+2> <input>f(Key), #{Key := Value} = #{key := Key} = M, Value.</input>
+value
+3> <input>f(Key), #{Key := Value} = (#{key := Key} = M), Value.</input>
+value
+4> <input>f(Key), (#{Key := Value} = #{key := Key}) = M, Value.</input>
+* 1:12: variable 'Key' is unbound
+5> <input>&lt;&lt;X:Y&gt;&gt; = begin Y = 8, &lt;&lt;42:8&gt;&gt; end, X.</input>
+42</pre>
+
+ <p>The expression at prompt <em>2&gt;</em> first matches the value of
+ variable <c>M</c> against pattern <c>#{key := Key}</c>, binding
+ variable <c>Key</c>. It then matches the value of <c>M</c> against
+ pattern <c>#{Key := Value}</c> using variable <c>Key</c> as the
+ key, binding variable <c>Value</c>.</p>
+
+ <p>The expression at prompt <em>3&gt;</em> matches expression
+ <c>(#{key := Key} = M)</c> against pattern <c>#{Key :=
+ Value}</c>. The expression inside the parentheses is evaluated
+ first. That is, <c>M</c> is matched against <c>#{key := Key}</c>,
+ and then the value of <c>M</c> is matched against pattern <c>#{Key
+ := Value}</c>. That is the same evaluation order as in <em>2</em>;
+ therefore, the parentheses are redundant.</p>
+
+ <p>In the expression at prompt <em>4&gt;</em> the expression <c>M</c>
+ is matched against a pattern inside parentheses. Since the
+ construct inside the parentheses is a pattern, the <c>=</c> that
+ separates the two patterns is the compound pattern operator
+ (<em>not</em> the match operator). The match fails because the two
+ sub patterns are matched at the same time, and the variable
+ <c>Key</c> is therefore not bound when matching against pattern
+ <c>#{Key := Value}</c>.</p>
+
+ <p>In the expression at prompt <em>5&gt;</em> the expressions
+ inside the <seeguide marker="#block_expressions">block
+ expression</seeguide> are evaluated first, binding variable
+ <c>Y</c> and creating a binary. The binary is then matched
+ against pattern <c>&lt;&lt;X:Y&gt;&gt;</c> using the value of
+ <c>Y</c> as the size of the segment.</p>
</section>
<section>
@@ -268,13 +376,13 @@ spawn(m, init, [])</code>
being auto-imported. In certain situations, such a compile-directive
is mandatory.</p>
- <warning><p>Before OTP R14A (ERTS version 5.8), an implicitly
+ <change><p>Before OTP R14A (ERTS version 5.8), an implicitly
qualified function call to a function having the same name as an
auto-imported BIF always resulted in the BIF being called. In
newer versions of the compiler, the local function is called instead.
This is to avoid that future additions to the
set of auto-imported BIFs do not silently change the behavior
- of old code.</p>
+ of old code.</p></change>
<p>However, to avoid that old (pre R14) code changed its
behavior when compiled with OTP version R14A or later, the
@@ -284,7 +392,7 @@ spawn(m, init, [])</code>
your code, you either need to explicitly remove the auto-import
using a compiler directive, or replace the call with a fully
qualified function call. Otherwise you get a compilation
- error. See the following example:</p> </warning>
+ error. See the following example:</p>
<code type="none">
-export([length/1,f/1]).
@@ -397,14 +505,13 @@ is_valid_signal(Signal) ->
<section>
<marker id="maybe"></marker>
<title>Maybe</title>
- <note><p><c>maybe</c> is an experimental new <seeguide
+ <change><p><c>maybe</c> is an experimental <seeguide
marker="system/reference_manual:features#features">feature</seeguide>
- introduced in OTP 25. By default, it is disabled. To enable
+ introduced in Erlang/OTP 25. By default, it is disabled. To enable
<c>maybe</c>, either use the <c>-feature(maybe_expr,enable)</c>
directive (from within source code), or the compiler option
- <c>{feature,maybe_expr,enable}</c>. The feature must also be enabled
- in runtime using the <c>-enable-feature</c> option to <c>erl</c>.</p>
- </note>
+ <c>{feature,maybe_expr,enable}</c>.</p>
+ </change>
<code type="erl"><![CDATA[
maybe
@@ -937,16 +1044,21 @@ case A >= -1.0 andalso math:sqrt(A+1) > B of</pre>
OnlyOne = is_atom(L) orelse
(is_list(L) andalso length(L) == 1),</pre>
- <p>From Erlang/OTP R13A, <c>Expr2</c> is no longer required to evaluate to a
- Boolean value. As a consequence, <c>andalso</c> and <c>orelse</c>
- are now tail-recursive. For instance, the following function is
- tail-recursive in Erlang/OTP R13A and later:</p>
+ <p><c>Expr2</c> is not required to evaluate to a Boolean
+ value. Because of that, <c>andalso</c> and <c>orelse</c> are
+ tail-recursive.</p>
+ <p><em>Example 3 (tail-recursive function):</em></p>
<pre>
all(Pred, [Hd|Tail]) ->
Pred(Hd) andalso all(Pred, Tail);
all(_, []) ->
true.</pre>
+
+ <change><p>Before Erlang/OTP R13A, <c>Expr2</c> was required to
+ evaluate to a Boolean value, and as consequence, <c>andalso</c>
+ and <c>orelse</c> were <strong>not</strong>
+ tail-recursive.</p></change>
</section>
<section>
@@ -1066,7 +1178,7 @@ M4 = #{{"w", 1} => f()}. % compound key associated with an evaluated expression
</p>
<p>
If key <c>K</c> does not match any existing keys in map <c>M</c>, an exception
- of type <c>badarg</c> is triggered at runtime. If a matching key <c>K</c>
+ of type <c>badkey</c> is triggered at runtime. If a matching key <c>K</c>
is present in map <c>M</c>, its associated value is replaced by the new
value <c>V</c>, and the evaluated map expression returns a new map.
</p>
@@ -1126,9 +1238,9 @@ M4 = M3#{a := 2, b := 3}. % 'a' and 'b' was added in `M1` and `M2`.</code>
with the key <c>K</c>, which must exist in the map <c>M</c>. If the variable
<c>V</c> is bound, it must match the value associated with <c>K</c> in <c>M</c>.
</p>
- <note><p>Before OTP 23, the expression defining the key
+ <change><p>Before Erlang/OTP 23, the expression defining the key
<c>K</c> was restricted to be either a single variable or a
- literal.</p></note>
+ literal.</p></change>
<p><em>Example:</em></p>
<pre>
1> <input>M = #{"tuple" => {1,2}}.</input>
@@ -1236,38 +1348,52 @@ handle_call(change, From, #{ state := start } = S) ->
<section>
<marker id="bit_syntax"></marker>
<title>Bit Syntax Expressions</title>
- <code type="none"><![CDATA[<<>>
+ <p>
+ The bit syntax operates on <em>bit strings</em>.
+ A bit string is a sequence of bits ordered
+ from the most significant bit to the least significant bit.
+ </p>
+ <code type="none"><![CDATA[<<>> % The empty bit string, zero length
+<<E1>>
<<E1,...,En>>]]></code>
- <p>Each element <c>Ei</c> specifies a <em>segment</em> of
- the bit string. Each element <c>Ei</c> is a value, followed by an
- optional <em>size expression</em> and an optional <em>type specifier list</em>.</p>
+ <p>
+ Each element <c>Ei</c> specifies a <em>segment</em> of
+ the bit string. The segments are ordered left to right
+ from the most significant bit to the least significant bit
+ of the bit string.
+ </p>
+ <p>
+ Each segment specification <c>Ei</c> is a value, followed by an
+ optional <em>size expression</em>
+ and an optional <em>type specifier list</em>.
+ </p>
<pre>
Ei = Value |
Value:Size |
Value/TypeSpecifierList |
Value:Size/TypeSpecifierList</pre>
- <p>Used in a bit string construction, <c>Value</c> is an expression
+ <p>When used in a bit string construction, <c>Value</c> is an expression
that is to evaluate to an integer, float, or bit string. If the
expression is not a single literal or variable, it
is to be enclosed in parentheses.</p>
- <p>Used in a bit string matching, <c>Value</c> must be a variable,
+ <p>When used in a bit string matching, <c>Value</c> must be a variable,
or an integer, float, or string.</p>
<p>Notice that, for example, using a string literal as in
<c><![CDATA[<<"abc">>]]></c> is syntactic sugar for
<c><![CDATA[<<$a,$b,$c>>]]></c>.</p>
- <p>Used in a bit string construction, <c>Size</c> is an expression
+ <p>When used in a bit string construction, <c>Size</c> is an expression
that is to evaluate to an integer.</p>
- <p>Used in a bit string matching, <c>Size</c> must be a
+ <p>When used in a bit string matching, <c>Size</c> must be a
<seeguide marker="#guard_expressions">guard expression</seeguide>
that evaluates to an integer. All variables in the guard expression
must be already bound.</p>
- <note><p>Before OTP 23, <c>Size</c> was restricted to be an
- integer or a variable bound to an integer.</p></note>
+ <change><p>Before Erlang/OTP 23, <c>Size</c> was restricted to be an
+ integer or a variable bound to an integer.</p></change>
<p>The value of <c>Size</c> specifies the size of the segment in
units (see below). The default value depends on the type (see
@@ -1278,14 +1404,45 @@ Ei = Value |
<item>For <c>binary</c> and <c>bitstring</c> it is
the whole binary or bit string.</item>
</list>
- <p>In matching, this default value is only
- valid for the last element. All other bit string or binary
- elements in the matching must have a size specification.</p>
+ <p>In matching, the default value for a binary or bit string
+ segment is only valid for the last element. All other bit string
+ or binary elements in the matching must have a size
+ specification.</p>
+
+ <marker id="binaries"></marker>
+ <p><strong>Binaries</strong></p>
+ <p>
+ A bit string with a length that is a multiple of 8 bits
+ is known as a <em>binary</em>, which is the most
+ common and useful type of bit string.
+ </p>
+ <p>
+ A binary has a canonical representation in memory.
+ Here follows a sequence of bytes where each byte&apos;s
+ value is its sequence number:
+ </p>
+ <pre>&lt;&lt;1, 2, 3, 4, 5, 6, 7, 8, 9, 10&gt;&gt;</pre>
+ <p>
+ Bit strings are a later generalization of binaries,
+ so many texts and much information about binaries
+ apply just as well for bit strings.
+ </p>
+
+ <p><strong>Example:</strong></p>
+ <pre>
+1> <input>&lt;&lt;A/binary, B/binary>> = &lt;&lt;"abcde">>.</input>
+* 1:3: a binary field without size is only allowed at the end of a binary pattern
+2> <input>&lt;&lt;A:3/binary, B/binary>> = &lt;&lt;"abcde">>.</input>
+&lt;&lt;"abcde">>
+3> <input>A.</input>
+&lt;&lt;"abc">>
+4> <input>B.</input>
+&lt;&lt;"de">></pre>
<p>For the <c>utf8</c>, <c>utf16</c>, and <c>utf32</c> types,
<c>Size</c> must not be given. The size of the segment is implicitly
determined by the type and value itself.</p>
-
+
<p><c>TypeSpecifierList</c> is a list of type specifiers, in any
order, separated by hyphens (-). Default values are used for any
omitted type specifiers.</p>
@@ -1303,68 +1460,172 @@ Ei = Value |
The default is <c>unsigned</c>.</item>
<tag><c>Endianness</c>= <c>big</c> | <c>little</c> | <c>native</c></tag>
- <item>Native-endian means that the endianness is resolved at load
- time to be either big-endian or little-endian, depending on
- what is native for the CPU that the Erlang machine is run on.
- Endianness only matters when the Type is either <c>integer</c>,
- <c>utf16</c>, <c>utf32</c>, or <c>float</c>. The default is <c>big</c>.
- </item>
+ <item>
+ Specifies byte level (octet level) endianness (byte order).
+ Native-endian means that the endianness is resolved at load
+ time to be either big-endian or little-endian, depending on
+ what is native for the CPU that the Erlang machine is run on.
+ Endianness only matters when the Type is either <c>integer</c>,
+ <c>utf16</c>, <c>utf32</c>, or <c>float</c>. The default is <c>big</c>.
+ <pre>&lt;&lt;16#1234:16/little>> = &lt;&lt;16#3412:16>> = &lt;&lt;16#34:8, 16#12:8>></pre>
+ </item>
<tag><c>Unit</c>= <c>unit:IntegerLiteral</c></tag>
- <item>The allowed range is 1..256. Defaults to 1 for <c>integer</c>,
- <c>float</c>, and <c>bitstring</c>, and to 8 for <c>binary</c>.
- No unit specifier must be given for the types
- <c>utf8</c>, <c>utf16</c>, and <c>utf32</c>.
- </item>
+ <item>The allowed range is 1 through 256. Defaults to 1 for <c>integer</c>,
+ <c>float</c>, and <c>bitstring</c>, and to 8 for <c>binary</c>.
+ For types <c>bitstring</c>, <c>bits</c>, and <c>bytes</c>, it is not allowed
+ to specify a unit value different from the default value.
+ No unit specifier must be given for the types <c>utf8</c>, <c>utf16</c>,
+ and <c>utf32</c>.
+ </item>
</taglist>
- <p>The value of <c>Size</c> multiplied with the unit gives
- the number of bits. A segment of type <c>binary</c> must have
- a size that is evenly divisible by 8. For a segment of type <c>float</c>
- the size must be either 64, 32, or 16.</p>
-
- <note><p>When constructing binaries, if the size <c>N</c> of an integer
- segment is too small to contain the given integer, the most significant
- bits of the integer are silently discarded and only the <c>N</c> least
- significant bits are put into the binary.</p></note>
-
- <p>The types <c>utf8</c>, <c>utf16</c>, and <c>utf32</c> specifies
- encoding/decoding of the <em>Unicode Transformation Format</em>s UTF-8, UTF-16,
- and UTF-32, respectively.</p>
-
- <p>When constructing a segment of a <c>utf</c> type, <c>Value</c>
- must be an integer in the range 0..16#D7FF or
- 16#E000....16#10FFFF. Construction
- fails with a <c>badarg</c> exception if <c>Value</c> is
- outside the allowed ranges. The size of the resulting binary
- segment depends on the type or <c>Value</c>, or both:</p>
- <list type="bulleted">
- <item>For <c>utf8</c>, <c>Value</c> is encoded in 1-4 bytes.</item>
- <item>For <c>utf16</c>, <c>Value</c> is encoded in 2 or 4 bytes.</item>
- <item>For <c>utf32</c>, <c>Value</c> is always be encoded in 4 bytes.</item>
- </list>
- <p>When constructing, a literal string can be given followed
- by one of the UTF types, for example: <c><![CDATA[<<"abc"/utf8>>]]></c>
- which is syntactic sugar for
- <c><![CDATA[<<$a/utf8,$b/utf8,$c/utf8>>]]></c>.</p>
+ <section>
+ <title>Integer segments</title>
+ <p>The value of <c>Size</c> multiplied with the unit gives the
+ size of the segment in bits.</p>
+
+ <p>When constructing bit strings, if the size <c>N</c> of an integer
+ segment is too small to contain the given integer, the most significant
+ bits of the integer are silently discarded and only the <c>N</c> least
+ significant bits are put into the bit string. For example, <c>&lt;&lt;16#ff:4&gt;&gt;</c>
+ will result in the bit string <c>&lt;&lt;15:4&gt;&gt;</c>.</p>
+ </section>
+
+ <section>
+ <title>Float segments</title>
+ <p>The value of <c>Size</c> multiplied with the unit gives
+ the size of the segment in bits. The size of a float segment in bits must be
+ one of 16, 32, or 64.</p>
+
+ <p>When constructing bit strings, if the size of a float segment is too small
+ to contain the representation of the given float value, an exception is raised.</p>
+
+ <p>When matching bit strings, matching of float segments fails if the bits of the segment
+ does not contain the representation of a finite floating point value.</p>
+ </section>
+
+ <section>
+ <title>Binary segments</title>
+ <p>In this section, the phrase "binary segment" refers to any
+ one of the segment types <c>binary</c>, <c>bitstring</c>,
+ <c>bytes</c>, and <c>bits</c>.</p>
+
+ <p>
+ See also the paragraphs about
+ <seeguide marker="#binaries">Binaries</seeguide>.
+ </p>
+
+ <p>When constructing binaries and no size is specified for a
+ binary segment, the entire binary value is interpolated into the
+ binary being constructed. However, the size in bits of the
+ binary being interpolated must be evenly divisible by the unit
+ value for the segment; otherwise an exception is raised.</p>
+
+ <p>For example, the following examples all succeed:</p>
+
+ <pre>
+1> <input>&lt;&lt;(&lt;&lt;"abc">>)/bitstring>>.</input>
+&lt;&lt;"abc">>
+2> <input>&lt;&lt;(&lt;&lt;"abc">>)/binary-unit:1>>.</input>
+&lt;&lt;"abc">>
+3> <input>&lt;&lt;(&lt;&lt;"abc">>)/binary>>.</input>
+&lt;&lt;"abc">></pre>
+
+ <p>The first two examples have a unit value of 1 for the segment,
+ while the third segment has a unit value of 8.</p>
+
+ <p>Attempting to interpolate a bit string of size 1 into a
+ binary segment with unit 8 (the default unit for <c>binary</c>)
+ fails as shown in this example:</p>
+
+ <pre>
+<input>1> &lt;&lt;(&lt;&lt;1:1&gt;&gt;)/binary&gt;&gt;.</input>
+** exception error: bad argument</pre>
+
+ <p>For the construction to succeed, the unit value of the
+ segment must be 1:</p>
+
+ <pre>
+2> <input>&lt;&lt;(&lt;&lt;1:1>>)/bitstring>>.</input>
+&lt;&lt;1:1>>
+3> <input>&lt;&lt;(&lt;&lt;1:1>>)/binary-unit:1>>.</input>
+&lt;&lt;1:1>></pre>
+
+ <p>Similarly, when matching a binary segment with no size
+ specified, the match succeeds if and only if the size in bits of
+ the rest of the binary is evenly divisible by the unit
+ value:</p>
+
+ <pre>
+1> <input>&lt;&lt;_/binary-unit:16>> = &lt;&lt;"">>.</input>
+&lt;&lt;>>
+2> <input>&lt;&lt;_/binary-unit:16>> = &lt;&lt;"a">>.</input>
+** exception error: no match of right hand side value &lt;&lt;"a">>
+3> <input>&lt;&lt;_/binary-unit:16>> = &lt;&lt;"ab">>.</input>
+&lt;&lt;"ab">>
+4> <input>&lt;&lt;_/binary-unit:16>> = &lt;&lt;"abc">>.</input>
+** exception error: no match of right hand side value &lt;&lt;"abc">>
+5> <input>&lt;&lt;_/binary-unit:16>> = &lt;&lt;"abcd">>.</input>
+&lt;&lt;"abcd">></pre>
+
+ <p>When a size is explicitly specified for a binary segment,
+ the segment size in bits is the value of <c>Size</c> multiplied
+ by the default or explicit unit value.</p>
+
+ <p>When constructing binaries, the size of the binary being interpolated
+ into the constructed binary must be at least as large as the size of
+ the binary segment.</p>
+
+ <p><strong>Examples:</strong></p>
+ <pre>
+1> <input>&lt;&lt;(&lt;&lt;"abc">>):2/binary>>.</input>
+&lt;&lt;"ab">>
+2> <input>&lt;&lt;(&lt;&lt;"a">>):2/binary>>.</input>
+** exception error: construction of binary failed
+ *** segment 1 of type 'binary': the value &lt;&lt;"a">> is shorter than the size of the segment</pre>
+ </section>
- <p>A successful match of a segment of a <c>utf</c> type, results
- in an integer in the range 0..16#D7FF or 16#E000..16#10FFFF.
- The match fails if the returned value falls outside those ranges.</p>
+ <section>
+ <title>Unicode segments</title>
+ <p>The types <c>utf8</c>, <c>utf16</c>, and <c>utf32</c> specifies
+ encoding/decoding of the <em>Unicode Transformation Format</em>s UTF-8, UTF-16,
+ and UTF-32, respectively.</p>
+
+ <p>When constructing a segment of a <c>utf</c> type,
+ <c>Value</c> must be an integer in the range 0 through 16#D7FF
+ or 16#E000 through 16#10FFFF. Construction fails with a
+ <c>badarg</c> exception if <c>Value</c> is outside the allowed
+ ranges. The sizes of the encoded values are as follows:</p>
+ <list type="bulleted">
+ <item>For <c>utf8</c>, <c>Value</c> is encoded in 1-4 bytes.</item>
+ <item>For <c>utf16</c>, <c>Value</c> is encoded in 2 or 4 bytes.</item>
+ <item>For <c>utf32</c>, <c>Value</c> is encoded in 4 bytes.</item>
+ </list>
- <p>A segment of type <c>utf8</c> matches 1-4 bytes in the binary,
- if the binary at the match position contains a valid UTF-8 sequence.
- (See RFC-3629 or the Unicode standard.)</p>
+ <p>When constructing, a literal string can be given followed
+ by one of the UTF types, for example: <c><![CDATA[<<"abc"/utf8>>]]></c>
+ which is syntactic sugar for
+ <c><![CDATA[<<$a/utf8,$b/utf8,$c/utf8>>]]></c>.</p>
- <p>A segment of type <c>utf16</c> can match 2 or 4 bytes in the binary.
- The match fails if the binary at the match position does not contain
- a legal UTF-16 encoding of a Unicode code point. (See RFC-2781 or
- the Unicode standard.)</p>
+ <p>A successful match of a segment of a <c>utf</c> type, results
+ in an integer in the range 0 through 16#D7FF or 16#E000 through 16#10FFFF.
+ The match fails if the returned value falls outside those ranges.</p>
- <p>A segment of type <c>utf32</c> can match 4 bytes in the binary in the
- same way as an <c>integer</c> segment matches 32 bits.
- The match fails if the resulting integer is outside the legal ranges
- mentioned above.</p>
+ <p>A segment of type <c>utf8</c> matches 1-4 bytes in the bit string,
+ if the bit string at the match position contains a valid UTF-8 sequence.
+ (See RFC-3629 or the Unicode standard.)</p>
+
+ <p>A segment of type <c>utf16</c> can match 2 or 4 bytes in the bit string.
+ The match fails if the bit string at the match position does not contain
+ a legal UTF-16 encoding of a Unicode code point. (See RFC-2781 or
+ the Unicode standard.)</p>
+
+ <p>A segment of type <c>utf32</c> can match 4 bytes in the bit string in the
+ same way as an <c>integer</c> segment matches 32 bits.
+ The match fails if the resulting integer is outside the legal ranges
+ previously mentioned.</p>
+ </section>
<p><em>Examples:</em></p>
<pre>
@@ -1372,6 +1633,7 @@ Ei = Value |
&lt;&lt;1,17,42&gt;&gt;
2> <input>Bin2 = &lt;&lt;"abc"&gt;&gt;.</input>
&lt;&lt;97,98,99&gt;&gt;
+
3> <input>Bin3 = &lt;&lt;1,17,42:16&gt;&gt;.</input>
&lt;&lt;1,17,0,42&gt;&gt;
4> <input>&lt;&lt;A,B,C:16&gt;&gt; = &lt;&lt;1,17,42:16&gt;&gt;.</input>
@@ -1392,8 +1654,14 @@ Ei = Value |
&lt;&lt;1,17,2,10:4&gt;&gt;
12> <input>J.</input>
&lt;&lt;17,2,10:4&gt;&gt;
+
13> <input>&lt;&lt;1024/utf8&gt;&gt;.</input>
&lt;&lt;208,128&gt;&gt;
+
+14> <input>&lt;&lt;1:1,0:7&gt;&gt;.</input>
+&lt;&lt;128&gt;&gt;
+15> <input>&lt;&lt;16#123:12/little&gt;&gt; = &lt;&lt;16#231:12&gt;&gt; = &lt;&lt;2:4, 3:4, 1:4&gt;&gt;.</input>
+&lt;&lt;35,1:4&gt;&gt;
</pre>
<p>Notice that bit string patterns cannot be nested.</p>
<p>Notice also that "<c><![CDATA[B=<<1>>]]></c>" is interpreted as
@@ -1448,14 +1716,15 @@ fun Module:Name/Arity</pre>
syntactic sugar for:</p>
<pre>
fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end</pre>
- <p>In <c>Module:Name/Arity</c>, <c>Module</c>, and <c>Name</c> are atoms
- and <c>Arity</c> is an integer. Starting from Erlang/OTP R15,
- <c>Module</c>, <c>Name</c>, and <c>Arity</c> can also be variables.
- A fun defined in this way refers to the function <c>Name</c>
- with arity <c>Arity</c> in the <em>latest</em> version of module
- <c>Module</c>. A fun defined in this way is not dependent on
- the code for the module in which it is defined.
- </p>
+ <p>In <c>Module:Name/Arity</c>, <c>Module</c>, and <c>Name</c> are
+ atoms and <c>Arity</c> is an integer. <c>Module</c>, <c>Name</c>,
+ and <c>Arity</c> can also be variables. A fun defined in this way
+ refers to the function <c>Name</c> with arity <c>Arity</c> in the
+ <em>latest</em> version of module <c>Module</c>. A fun defined in
+ this way is not dependent on the code for the module in which it
+ is defined.</p>
+ <change><p>Before Erlang/OTP R15, <c>Module</c>, <c>Name</c>, and <c>Arity</c> were
+ not allowed to be variables.</p></change>
<p>More examples are provided in
<seeguide marker="system/programming_examples:funs">
Programming Examples</seeguide>.</p>
@@ -1492,10 +1761,27 @@ catch Expr</code>
returns the value <c>Any</c>.</p>
<p><em>Example:</em></p>
<pre>
-5> <input>catch throw(hello).</input>
+3> <input>catch throw(hello).</input>
hello</pre>
<p>If <c>throw/1</c> is not evaluated within a catch, a
<c>nocatch</c> run-time error occurs.</p>
+
+ <change><p>Before Erlang/OTP 24, the <c>catch</c> operator had
+ the lowest precedence, making it necessary to add parentheses when
+ combining it with the <c>match</c> operator:</p>
+ <pre>
+1> <input>A = (catch 42).</input>
+42
+2> <input>A.</input>
+42</pre>
+
+ <p>Starting from Erlang/OTP 24, the parentheses can be omitted:</p>
+ <pre>
+1> <input>A = catch 42.</input>
+42
+2> <input>A.</input>
+42</pre>
+ </change>
</section>
<section>
@@ -1652,6 +1938,20 @@ catch
exit:Reason -> {'EXIT',Reason}
error:Reason:Stk -> {'EXIT',{Reason,Stk}}
end</code>
+
+ <p>Variables bound in the various parts of these expressions have different scopes.
+ Variables bound just after the <c>try</c> keyword are:</p>
+ <list type="bulleted">
+ <item>bound in the <c>of</c> section</item>
+ <item>unsafe in both the <c>catch</c> and <c>after</c> sections, as well as after the whole construct</item>
+ </list>
+ <p>Variables bound in <c>of</c> section are:</p>
+ <list type="bulleted">
+ <item>unbound in the <c>catch</c> section</item>
+ <item>unsafe in both the <c>after</c> section, as well as after the whole construct</item>
+ </list>
+ <p>Variables bound in the <c>catch</c> section are unsafe in the <c>after</c> section, as well as after the whole construct.</p>
+ <p>Variables bound in the <c>after</c> section are unsafe after the whole construct.</p>
</section>
<section>
@@ -1669,6 +1969,7 @@ end</code>
</section>
<section>
+ <marker id="block_expressions"></marker>
<title>Block Expressions</title>
<pre>
begin
@@ -1683,107 +1984,204 @@ end</pre>
<section>
<marker id="lcs"></marker>
- <title>List Comprehensions</title>
- <p>List comprehensions is a feature of many modern functional
- programming languages. Subject to certain rules, they provide a
- succinct notation for generating elements in a list.</p>
- <p>List comprehensions are analogous to set comprehensions in
- Zermelo-Frankel set theory and are called ZF expressions in
- Miranda. They are analogous to the <c>setof</c> and
- <c>findall</c> predicates in Prolog.</p>
- <p>List comprehensions are written with the following syntax:</p>
- <pre>
-[Expr || Qualifier1,...,QualifierN]</pre>
+ <title>Comprehensions</title>
+ <p>Comprehensions provide a succinct notation for iterating over
+ one or more terms and constructing a new term. Comprehensions come
+ in three different flavors, depending on the type of term they
+ build.</p>
+ <p>List comprehensions construct lists. They have the following syntax:</p>
+ <pre>
+[Expr || Qualifier1, . . ., QualifierN]</pre>
<p>Here, <c>Expr</c> is an arbitrary expression, and each
- <c>Qualifier</c> is either a generator or a filter.</p>
- <list type="bulleted">
- <item>A <em>generator</em> is written as: <br></br>
-
- &nbsp;&nbsp;<c><![CDATA[Pattern <- ListExpr]]></c>. <br></br>
-<c>ListExpr</c> must be an expression, which evaluates to a
- list of terms.</item>
-<item>A <em>bit string generator</em> is written as: <br></br>
-
- &nbsp;&nbsp;<c><![CDATA[BitstringPattern <= BitStringExpr]]></c>. <br></br>
-<c>BitStringExpr</c> must be an expression, which evaluates to a
- bitstring.</item>
- <item>A <em>filter</em> is an expression, which evaluates to
- <c>true</c> or <c>false</c>, or a
- <seeguide marker="#guard_expressions">guard expression</seeguide>.
- If the filter is not a guard expression and evaluates
- to a non-Boolean value <c>Val</c>, an exception
- <c>{bad_filter, Val}</c> is triggered at runtime.</item>
- </list>
+ <c>Qualifier</c> is either a <strong>generator</strong> or a
+ <strong>filter</strong>.</p>
+
+ <p>Bit string comprehensions construct bit strings or binaries.
+ They have the following syntax:</p>
+ <pre>
+&lt;&lt; BitStringExpr || Qualifier1, . . ., QualifierN &gt;&gt;</pre>
+
+ <p><c>BitStringExpr</c> is an expression that evaluates to a bit string.
+ If <c>BitStringExpr</c> is a function call, it must be
+ enclosed in parentheses. Each <c>Qualifier</c> is either a
+ <strong>generator</strong> or a <strong>filter</strong>.</p>
+
+ <p>Map comprehensions construct maps. They have the following syntax:</p>
+ <pre>
+#{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}</pre>
+ <p>Here, <c>KeyExpr</c> and <c>ValueExpr</c> are arbitrary
+ expressions, and each <c>Qualifier</c> is either a
+ <strong>generator</strong> or a <strong>filter</strong>.</p>
+
+ <change><p>Map comprehensions and map generators were introduced
+ in Erlang/OTP 26.</p></change>
+
+ <p>There are three kinds of generators.</p>
+
+ <p>A <em>list generator</em> has the following syntax:</p>
+
+<pre>
+Pattern &lt;- ListExpr</pre>
+
+ <p>where <c>ListExpr</c> is an expression that evaluates to a
+ list of terms.</p>
+
+ <p>A <em>bit string generator</em> has the following syntax:</p>
+
+ <pre>
+BitstringPattern &lt;= BitStringExpr</pre>
+
+ <p>where <c>BitStringExpr</c> is an expression that evaluates to a
+ bit string.</p>
+
+ <p>A <em>map generator</em> has the following syntax:</p>
+
+ <pre>
+KeyPattern := ValuePattern &lt;- MapExpression</pre>
+
+ <p>where <c>MapExpr</c> is an expression that evaluates to a map,
+ or a map iterator obtained by calling <seemfa
+ marker="stdlib:maps#iterator/1">maps:iterator/1</seemfa> or
+ <seemfa
+ marker="stdlib:maps#iterator/2">maps:iterator/2</seemfa>.</p>
+
+ <p>A <em>filter</em> is an expression that evaluates to
+ <c>true</c> or <c>false</c>.</p>
+
<p>The variables in the generator patterns shadow previously bound variables,
including variables bound in a previous generator pattern.</p>
- <p>A list comprehension returns a list, where the elements are the
+
+ <p>Variables bound in a generator expression are not visible outside the expression:</p>
+
+ <pre>
+1> <input>[{E,L} || E &lt;- L=[1,2,3]].</input>
+* 1:5: variable 'L' is unbound</pre>
+
+ <p>A <strong>list comprehension</strong> returns a list, where the list elements are the
result of evaluating <c>Expr</c> for each combination of generator
- list elements and bit string generator elements, for which all
+ elements for which all filters are true.</p>
+
+ <p>A <strong>bit string comprehension</strong> returns a bit string, which is
+ created by concatenating the results of evaluating <c>BitStringExpr</c> for
+ each combination of bit string generator elements for which all
filters are true.</p>
- <p><em>Example:</em></p>
+
+ <p>A <strong>map comprehension</strong> returns a map, where the
+ map elements are the result of evaluating <c>KeyExpr</c> and
+ <c>ValueExpr</c> for each combination of generator elements for
+ which all filters are true. If the key expressions are not unique,
+ the last occurrence is stored in the map.</p>
+
+ <p><strong>Examples:</strong></p>
+
+ <p>Multiplying each element in a list by two:</p>
<pre>
1> <input>[X*2 || X &lt;- [1,2,3]].</input>
[2,4,6]</pre>
- <p>When there are no generators or bit string generators, a list comprehension
- returns either a list with one element (the result of evaluating <c>Expr</c>)
- if all filters are true or an empty list otherwise.</p>
+
+ <p>Multiplying each byte in a binary by two, returning a list:</p>
+ <pre>
+1> <input>[X*2 || &lt;&lt;X&gt;&gt; &lt;= &lt;&lt;1,2,3&gt;&gt; &gt;&gt;.</input>
+[2,4,6]</pre>
+
+ <p>Multiplying each byte in a binary by two:</p>
+
+ <pre>
+1> <input>&lt;&lt; &lt;&lt;(X*2)&gt;&gt; || &lt;&lt;X&gt;&gt; &lt;= &lt;&lt;1,2,3&gt;&gt; &gt;&gt;.</input>
+&lt;&lt;2,4,6&gt;&gt;</pre>
+
+ <p>Multiplying each element in a list by two, returning a binary:</p>
+
+ <pre>
+1> <input>&lt;&lt; &lt;&lt;(X*2)&gt;&gt; || X &lt;- [1,2,3]].</input>
+&lt;&lt;2,4,6&gt;&gt;</pre>
+
+ <p>Creating a mapping from an integer to its square:</p>
+ <pre>
+1> <input>#{X => X*X || X &lt;- [1,2,3]}.</input>
+#{1 => 1,2 => 4,3 => 9}</pre>
+
+ <p>Multiplying the value of each element in a map by two:</p>
+ <pre>
+1> <input>#{K => 2*V || K := V &lt;- #{a => 1,b => 2,c => 3}}.</input>
+#{a => 2,b => 4,c => 6}</pre>
+
+ <p>Filtering a list, keeping odd numbers:</p>
+ <pre>
+1> <input>[X || X &lt;- [1,2,3,4,5], X rem 2 =:= 1].</input>
+[1,3,5]</pre>
+
+ <p>Filtering a list, keeping only elements that match:</p>
+ <pre>
+1> <input>[X || {_,_}=X &lt;- [{a,b}, [a], {x,y,z}, {1,2}]].</input>
+[{a,b},{1,2}]</pre>
+
+ <p>Combining elements from two list generators:</p>
+ <pre>
+1> <input>[{P,Q} || P &lt;- [a,b,c], Q &lt;- [1,2]].</input>
+[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]
+</pre>
+
+ <p>More examples are provided in
+ <seeguide marker="system/programming_examples:list_comprehensions">
+ Programming Examples.</seeguide></p>
+
+ <p>When there are no generators, a comprehension returns either a
+ term constructed from a single element (the result of evaluating
+ <c>Expr</c>) if all filters are true, or a term constructed from
+ no elements (that is, <c>[]</c> for list comprehension,
+ <c>&lt;&lt;&gt;&gt;</c> for a bit string comprehension, and
+ <c>#{}</c> for a map comprehension).</p>
<p><em>Example:</em></p>
<pre>
1> <input>[2 || is_integer(2)].</input>
[2]
2> <input>[x || is_integer(x)].</input>
[]</pre>
- <p>More examples are provided in
- <seeguide marker="system/programming_examples:list_comprehensions">
- Programming Examples.</seeguide></p>
- </section>
+ <p>What happens when the filter expression does not evaluate to
+ a boolean value depends on the expression:</p>
-<section>
- <title>Bit String Comprehensions</title>
-
- <p>Bit string comprehensions are
- analogous to List Comprehensions. They are used to generate bit strings
- efficiently and succinctly.</p>
- <p>Bit string comprehensions are written with
- the following syntax:</p>
- <pre>
-&lt;&lt; BitStringExpr || Qualifier1,...,QualifierN &gt;&gt;</pre>
- <p><c>BitStringExpr</c> is an expression that evaluates to a bit
- string. If <c>BitStringExpr</c> is a function call, it must be
- enclosed in parentheses. Each <c>Qualifier</c> is either a
- generator, a bit string generator or a filter.</p>
- <list type="bulleted">
- <item>A <em>generator</em> is written as: <br></br>
- &nbsp;&nbsp;<c><![CDATA[Pattern <- ListExpr]]></c>. <br></br>
- <c>ListExpr</c> must be an expression that evaluates to a
- list of terms.</item>
- <item>A <em>bit string generator</em> is written as: <br></br>
-
- &nbsp;&nbsp;<c><![CDATA[BitstringPattern <= BitStringExpr]]></c>. <br></br>
-<c>BitStringExpr</c> must be an expression that evaluates to a
- bitstring.</item>
- <item>A <em>filter</em> is an expression, which evaluates to
- <c>true</c> or <c>false</c>, or a
- <seeguide marker="#guard_expressions">guard expression</seeguide>.
- If the filter is not a guard expression and evaluates
- to a non-Boolean value <c>Val</c>, an exception
- <c>{bad_filter, Val}</c> is triggered at runtime.</item>
+ <list>
+ <item><p>If the expression is a <seeguide
+ marker="#guard_expressions">guard expression</seeguide>, failure
+ to evaluate or evaluating to a non-boolean value is equivalent
+ to evaluating to <c>false</c>.</p></item>
+
+ <item><p>If the expression is not a guard expression and
+ evaluates to a non-Boolean value <c>Val</c>, an exception
+ <c>{bad_filter, Val}</c> is triggered at runtime. If the
+ evaluation of the expression raises an exception, it is not
+ caught by the comprehension.</p></item>
</list>
- <p>The variables in the generator patterns shadow previously bound variables,
- including variables bound in a previous generator pattern.</p>
- <p>A bit string comprehension returns a bit string, which is
- created by concatenating the results of evaluating <c>BitString</c>
- for each combination of bit string generator elements, for which all
- filters are true.</p>
- <p><em>Example:</em></p>
+
+ <p><strong>Examples</strong> (using a guard expression as filter):</p>
+
<pre>
-1> <input>&lt;&lt; &lt;&lt; (X*2) &gt;&gt; ||
-&lt;&lt;X&gt;&gt; &lt;= &lt;&lt; 1,2,3 &gt;&gt; &gt;&gt;.</input>
-&lt;&lt;2,4,6&gt;&gt;</pre>
- <p>More examples are provided in
- <seeguide marker="system/programming_examples:bit_syntax">
- Programming Examples.</seeguide></p>
+1> <input>List = [1,2,a,b,c,3,4].</input>
+[1,2,a,b,c,3,4]
+2> <input>[E || E &lt;- List, E rem 2].</input>
+[]
+3> <input>[E || E &lt;- List, E rem 2 =:= 0].</input>
+[2,4]</pre>
+
+ <p><strong>Examples</strong> (using a non-guard expression as filter):</p>
+
+ <pre>
+1> <input>List = [1,2,a,b,c,3,4].</input>
+[1,2,a,b,c,3,4]
+2> <input>FaultyIsEven = fun(E) -> E rem 2 end.</input>
+#Fun&lt;erl_eval.42.17316486&gt;
+3> <input>[E || E &lt;- List, FaultyIsEven(E)].</input>
+** exception error: bad filter 1
+4> <input>IsEven = fun(E) -> E rem 2 =:= 0 end.</input>
+#Fun&lt;erl_eval.42.17316486&gt;
+5> <input>[E || E &lt;- List, IsEven(E)].</input>
+** exception error: an error occurred when evaluating an arithmetic expression
+ in operator rem/2
+ called as a rem 2
+6> <input>[E || E &lt;- List, is_integer(E), IsEven(E)].</input>
+[2,4]</pre>
</section>
<section>
@@ -1915,6 +2313,12 @@ end</pre>
<cell align="left" valign="middle"><c>map_size(Map)</c></cell>
</row>
<row>
+ <cell align="left" valign="middle"><c>max(A, B)</c></cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"><c>min(A, B)</c></cell>
+ </row>
+ <row>
<cell align="left" valign="middle"><c>node()</c></cell>
</row>
<row>
@@ -1941,6 +2345,9 @@ end</pre>
<tcaption>Other BIFs Allowed in Guard Expressions</tcaption>
</table>
+ <change><p>The <c>min/2</c> and <c>max/2</c> BIFs are allowed to be
+ used in guards from Erlang/OTP 26.</p></change>
+
<p>If an arithmetic expression, a Boolean expression, a
short-circuit expression, or a call to a guard BIF fails (because
of invalid arguments), the entire guard fails. If the guard was
@@ -1952,7 +2359,7 @@ end</pre>
<section>
<marker id="prec"></marker>
<title>Operator Precedence</title>
- <p>Operator precedence in falling priority:</p>
+ <p>Operator precedence in descending order:</p>
<table>
<row>
<cell align="left" valign="middle">:</cell>
@@ -1968,53 +2375,68 @@ end</pre>
</row>
<row>
<cell align="left" valign="middle">/ * div rem band and</cell>
- <cell align="left" valign="middle">Left associative</cell>
+ <cell align="left" valign="middle">Left-associative</cell>
</row>
<row>
<cell align="left" valign="middle">+ - bor bxor bsl bsr or xor</cell>
- <cell align="left" valign="middle">Left associative</cell>
+ <cell align="left" valign="middle">Left-associative</cell>
</row>
<row>
<cell align="left" valign="middle">++ --</cell>
- <cell align="left" valign="middle">Right associative</cell>
+ <cell align="left" valign="middle">Right-associative</cell>
</row>
<row>
<cell align="left" valign="middle">== /= =&lt; &lt; >= > =:= =/=</cell>
- <cell align="left" valign="middle">&nbsp;</cell>
+ <cell align="left" valign="middle">Non-associative</cell>
</row>
<row>
<cell align="left" valign="middle">andalso</cell>
- <cell align="left" valign="middle">&nbsp;</cell>
+ <cell align="left" valign="middle">Left-associative</cell>
</row>
<row>
<cell align="left" valign="middle">orelse</cell>
+ <cell align="left" valign="middle">Left-associative</cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle">catch</cell>
<cell align="left" valign="middle">&nbsp;</cell>
</row>
<row>
<cell align="left" valign="middle">= !</cell>
- <cell align="left" valign="middle">Right associative</cell>
+ <cell align="left" valign="middle">Right-associative</cell>
</row>
<row>
<cell align="left" valign="middle">?=</cell>
- <cell align="left" valign="middle">&nbsp;</cell>
- </row>
- <row>
- <cell align="left" valign="middle">catch</cell>
- <cell align="left" valign="middle">&nbsp;</cell>
+ <cell align="left" valign="middle">Non-associative</cell>
</row>
<tcaption>Operator Precedence</tcaption>
</table>
+ <change><p>Before Erlang/OTP 24, the <c>catch</c> operator had the lowest
+ precedence.</p></change>
+ <note><p>The <c>=</c> operator in the table is the
+ <seeguide marker="#match_operator">match operator</seeguide>.
+ The character <c>=</c> can also denote the
+ <seeguide marker="#compound_pattern_operator">compound pattern operator</seeguide>,
+ which can only be used in patterns.</p>
+ <p><c>?=</c> is restricted in that it can only be used at
+ the top-level inside a <c>maybe</c> block.</p>
+ </note>
<p>When evaluating an expression, the operator with the highest
- priority is evaluated first. Operators with the same priority
- are evaluated according to their associativity.</p>
- <p><em>Example:</em></p>
- <p>The left associative arithmetic operators are evaluated left to
+ precedence is evaluated first. Operators with the same precedence
+ are evaluated according to their associativity. Non-associative
+ operators cannot be combined with operators of the same precedence.</p>
+ <p><em>Examples:</em></p>
+ <p>The left-associative arithmetic operators are evaluated left to
right:</p>
<pre>
<input>6 + 5 * 4 - 3 / 2</input> evaluates to
<input>6 + 20 - 1.5</input> evaluates to
<input>26 - 1.5</input> evaluates to
<input>24.5</input></pre>
+
+<p>The non-associative operators cannot be combined:</p>
+ <pre>
+1> <input>1 &lt; X &lt; 10.</input>
+* 1:7: syntax error before: '&lt;'</pre>
</section>
</chapter>
-
diff --git a/system/doc/reference_manual/features.xml b/system/doc/reference_manual/features.xml
index 882cdd2582..e3b12742c6 100644
--- a/system/doc/reference_manual/features.xml
+++ b/system/doc/reference_manual/features.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2022</year><year>2022</year>
+ <year>2022</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -150,19 +150,13 @@
enable|disable)</c></seeguide> directive. This is the preferred
method of enabling and disabling features.</item>
</taglist>
- <p>
- Note that to load a module compiled with features enabled, the
- corresponding features must be enabled in the runtime. This
- is done using options <seecom
- marker="erts:erl#enable-feature"><c>-enable-feature</c></seecom>
- and <seecom
- marker="erts:erl#disable-feature"><c>-disable-feature</c></seecom>
- to <c>erl</c>. This is to allow the possibility to prevent
- the use of experimental features in, e.g., production. This
- will catch experimental features used in both own and third
- party components. An active choice to use experimental
- features must be done.
- </p>
+ <change>
+ <p>In Erlang/OTP 25, in order to load a module with a feature
+ enabled, it was necessary to also enable the feature in the runtime.
+ This was done using option <seecom
+ marker="erts:erl#enable-feature"><c>-enable-feature</c></seecom> to
+ <c>erl</c>. This requirement was removed in Erlang/OTP 26.</p>
+ </change>
</section>
<section>
diff --git a/system/doc/reference_manual/introduction.xml b/system/doc/reference_manual/introduction.xml
index f823c6921b..79b8132fb0 100644
--- a/system/doc/reference_manual/introduction.xml
+++ b/system/doc/reference_manual/introduction.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2021</year>
+ <year>2003</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -95,10 +95,15 @@
<title>Reserved Words</title>
<p>The following are reserved words in Erlang:</p>
<p><c>after and andalso band begin bnot bor bsl bsr bxor case catch
- cond div end fun if let not of or orelse receive rem try
+ cond div end fun if let maybe not of or orelse receive rem try
when xor</c></p>
<p><strong>Note</strong>: <c>cond</c> and <c>let</c>, while reserved,
- are currently not used by the language.</p>
+ are currently not used by the language.</p>
+ <change><p><c>maybe</c> is a reserved word only if feature
+ <c>maybe_expr</c> is enabled. In Erlang/OTP 25 and 26,
+ <c>maybe_expr</c> is disabled by default. Starting from
+ Erlang/OTP 27, <c>maybe_expr</c> is enabled by
+ default.</p></change>
</section>
</chapter>
diff --git a/system/doc/reference_manual/macros.xml b/system/doc/reference_manual/macros.xml
index e54e0e1d30..ab503f740e 100644
--- a/system/doc/reference_manual/macros.xml
+++ b/system/doc/reference_manual/macros.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2022</year>
+ <year>2003</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -155,18 +155,20 @@ bar(X) ->
<item>The OTP release that the currently executing ERTS
application is part of, as an integer. For details, see
<seemfa marker="erts:erlang#system_info/1"><c>erlang:system_info(otp_release)</c></seemfa>.
- This macro was introduced in OTP release 21.</item>
+ <change><p>The <c>?OTP_RELEASE</c> macro was introduced in Erlang/OTP 21.</p></change></item>
<tag><c>?FEATURE_AVAILABLE(Feature)</c></tag>
<item>Expands to <c>true</c> if the <seeguide
marker="system/reference_manual:features#features">feature</seeguide>
<c>Feature</c> is available. The feature might or might not
- be enabled. This macro was introduced with OTP release
- 25.</item>
+ be enabled.
+ <change><p>The <c>?FEATURE_AVAILABLE()</c> macro was introduced in Erlang/OTP 25.</p></change>
+ </item>
<tag><c>?FEATURE_ENABLED(Feature)</c></tag>
<item>Expands to <c>true</c> if the <seeguide
marker="system/reference_manual:features#features">feature</seeguide>
- <c>Feature</c> is enabled. This macro was introduced with OTP
- release 25.</item>
+ <c>Feature</c> is enabled.
+ <change><p>The <c>?FEATURE_ENABLED()</c> macro was introduced in Erlang/OTP 25.</p></change>
+ </item>
</taglist>
</section>
@@ -175,7 +177,8 @@ bar(X) ->
<p>It is possible to overload macros, except for predefined
macros. An overloaded macro has more than one definition,
each with a different number of arguments.</p>
- <p>The feature was added in Erlang 5.7.5/OTP R13B04.</p>
+ <change><p>Support for overloading of macros was added in Erlang
+ 5.7.5/OTP R13B04.</p></change>
<p>A macro <c>?Func(Arg1,...,ArgN)</c> with a (possibly empty)
list of arguments results in an error message if there is at
least one definition of <c>Func</c> with arguments, but none
@@ -339,8 +342,8 @@ version() -> ?VERSION.</code>
% <input>erlc t.erl</input>
t.erl:5: Warning: -warning("Macro VERSION not defined -- using default version.").</pre>
- <p>The <c>-error()</c> and <c>-warning()</c> directives were added
- in OTP 19.</p>
+ <change><p>The <c>-error()</c> and <c>-warning()</c> directives were added
+ in Erlang/OTP 19.</p></change>
</section>
diff --git a/system/doc/reference_manual/modules.xml b/system/doc/reference_manual/modules.xml
index b373e2506b..360345d4ad 100644
--- a/system/doc/reference_manual/modules.xml
+++ b/system/doc/reference_manual/modules.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2022</year>
+ <year>2003</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -141,24 +141,23 @@ fact(0) -> % |
<c>[Name1/Arity1, ..., NameN/ArityN]</c>, where each
<c>NameI</c> is an atom and <c>ArityI</c> an integer.
</p>
- <note>
- <p>
- The <c>-nifs()</c> attribute was introduced in OTP 25.0. For older
- Erlang source code without it, any functions in the module may be
- loaded as NIFs. However, it is recommended to declare the NIFs with
- the <c>-nifs</c> attribute. This allows the compiler to make better
- decisions regarding optimizations for example.
- </p>
+ <p>While not strictly necessary, it is recommended to use <c>-nifs()</c>
+ attribute in any module that load NIFs, to allow the compiler to make
+ better decisions regarding optimizations.</p>
+ <p>There is no need to add <c>-nifs([])</c> in modules that do not
+ load NIFs. The lack of any call to
+ <seemfa marker="erts:erlang#load_nif/2"><c>erlang:load_nif/2</c></seemfa>,
+ from within the module, is enough for the compiler to draw the
+ same conclusion.
+ </p>
+ <change>
<p>
- There is no need to add <c>-nifs([])</c> in modules that do not
- load NIFs. The lack of any call to
- <seemfa marker="erts:erlang#load_nif/2"><c>erlang:load_nif/2</c></seemfa>,
- from within the module, is enough for the compiler to draw the
- same conclusion.
+ The special meaning for the <c>-nifs()</c> attribute was
+ introduced in Erlang/OTP 25.0. In previous releases, the
+ <c>-nifs()</c> was accepted, but had no special meaning.
</p>
- </note>
+ </change>
</item>
-
</taglist>
</section>
@@ -344,9 +343,7 @@ behaviour_info(callbacks) -> Callbacks.</pre>
<tag><c>md5</c></tag>
<item>
- <p>Returns a binary representing the MD5 checksum of the module.
- If the module has native code loaded, this will be the MD5 of the
- native code, not the BEAM bytecode.</p>
+ <p>Returns a binary representing the MD5 checksum of the module.</p>
</item>
<tag><c>exports</c></tag>
diff --git a/system/doc/reference_manual/opaques.xml b/system/doc/reference_manual/opaques.xml
index 8fa3bae3e5..f7d633781c 100644
--- a/system/doc/reference_manual/opaques.xml
+++ b/system/doc/reference_manual/opaques.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2021</year>
- <year>2022</year>
+ <year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -33,28 +33,30 @@
<section>
<title>Opaque Type Aliases</title>
- <p>The main use case for opacity in Erlang is to hide the implementation of a data type, enabling evolving the API while minimizing the risk of breaking consumers. The runtime does not check opacity. Dialyzer provides some opacity-checking, but the rest is up to convention.
+ <p>The main use case for opacity in Erlang is to hide the
+ implementation of a data type, enabling evolving the API while
+ minimizing the risk of breaking consumers. The runtime does not
+ check opacity. Dialyzer provides some opacity-checking, but the
+ rest is up to convention.
</p>
<p>
- This document explains what Erlang opacity is (and the trade-offs involved) via the example of OTP's
- <c>sets:set()</c>
- data type. This type
- <em>was</em>
- defined in `sets` module like this:
+ This document explains what Erlang opacity is (and the
+ trade-offs involved) via the example of the <c>sets:set()</c>
+ data type. This type <em>was</em> defined in the <c>sets</c>
+ module like this:
</p>
<code type="erl">-opaque set(Element) :: #set{segs :: segs(Element)}.</code>
- <p>OTP 24 changed the definition to the following, in
- <url href="https://github.com/erlang/otp/commit/e66941e8d7c47b973dff94c0308ea85a6be1958e">this commit</url>
+ <p>OTP 24 changed the definition to the following in
+ <url href="https://github.com/erlang/otp/commit/e66941e8d7c47b973dff94c0308ea85a6be1958e">this commit</url>.
</p>
<code type="erl">-opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.</code>
<p>
- And this change was safer and more backwards-compatible than if the type had been defined with
- <c>-type</c>
- instead of
- <c>-opaque</c>
- . Here's why: when a module defines an
- <c>-opaque</c>
- , the contract is that only the defining module should rely on the definition of the type: no other modules should rely on the definition.
+ And this change was safer and more backwards-compatible than if
+ the type had been defined with <c>-type</c> instead of
+ <c>-opaque</c>. Here is why: when a module defines an
+ <c>-opaque</c>, the contract is that only the defining module
+ should rely on the definition of the type: no other modules
+ should rely on the definition.
</p>
<p>
This means that code that pattern-matched on
@@ -62,8 +64,8 @@
as a record/tuple technically broke the contract, and opted in to being potentially broken when the definition of
<c>set()</c>
changed. Before OTP 24, this code printed
- <c>ok</c>
- . In OTP 24 it may error:
+ <c>ok</c>.
+ In OTP 24 it may error:
</p>
<code type="erl">
case sets:new() of
@@ -85,12 +87,9 @@ end.
Instead, use functions provided by the module for working with the type. For example,
<c>sets</c>
module provides
- <c>sets:new/0</c>
- ,
- <c>sets:add/2</c>
- ,
- <c>sets:is_element/2</c>
- , etc.
+ <c>sets:new/0</c>,
+ <c>sets:add/2</c>,
+ <c>sets:is_element/2</c>, and so on.
</item>
<item>
<c>sets:set(a)</c>
@@ -108,34 +107,40 @@ end.
</p>
<list type="bulleted">
<item>
- Since consumers are expected to not rely on the definition of the opaque type, you must provide functions for constructing and querying/deconstructing intances of your opaque type. For example, sets can be constructed with
- <c>sets:new/0</c>, <c>sets:from_list/1</c>, <c>sets:add/2</c>, queried with <c>sets:is_element/2</c>, and deconstructed with<c>sets:to_list/1</c>.
+ Since consumers are expected to not rely on the definition of
+ the opaque type, you must provide functions for constructing,
+ querying, and deconstructing instances of your opaque type. For
+ example, sets can be constructed with <c>sets:new/0</c>,
+ <c>sets:from_list/1</c>, <c>sets:add/2</c>, queried with
+ <c>sets:is_element/2</c>, and deconstructed
+ with<c>sets:to_list/1</c>.
</item>
<item>
- Don't define an opaque with a type variable in parameter position. This breaks the normal and expected behavior that (for example)
- <c>my_type(a)</c> is a subtype of <c>my_type(a | b)</c>
+ Don't define an opaque with a type variable in parameter
+ position. This breaks the normal and expected behavior that
+ (for example) <c>my_type(a)</c> is a subtype of <c>my_type(a |
+ b)</c>
</item>
<item>
Add <seeguide marker="typespec">specs</seeguide> to exported functions that use the opaque type
</item>
</list>
- <p>Note that opaques can be harder to work with for consumers, since the consumer is expected not to pattern-match and must instead use functions that the author of the opaque type provides to use instances of the type.</p>
+ <p>Note that opaques can be harder to work with for consumers,
+ since the consumer is expected not to pattern-match and must
+ instead use functions that the author of the opaque type provides
+ to use instances of the type.</p>
<p>
- Also, opacity in Erlang is skin-deep: the runtime does not enforce opacity-checking. So now that sets are implemented in terms of maps, an
- <c>is_map</c>
- check on a set
- <em>will</em>
- pass. The opacity rules are only enforced by convention and by additional tooling such as Dialyzer. And this enforcement is not total: For example, determined consumer of
- <c>sets</c>
- can still do things that reveal the structure of the set, such as by printing, serializing, or using a set as
- <c>term()</c>
- and then inspecting via functions like
- <c>is_map</c>
- or
- <c>maps:get/2</c>
- . And Dialyzer must make some
- <url href="https://github.com/erlang/otp/issues/5118">approximations</url>
- . Opacity checking has limitations, but is still a vital tool in scalable Erlang development.
+ Also, opacity in Erlang is skin-deep: the runtime does not
+ enforce opacity-checking. So now that sets are implemented in
+ terms of maps, an <c>is_map/1</c> check on a set <em>will</em>
+ pass. The opacity rules are only enforced by convention and by
+ additional tooling such as Dialyzer, and this enforcement is not
+ total. A determined consumer of <c>sets</c> can still reveal the
+ structure of the set, for example by printing, serializing, or
+ using a set as a <c>term()</c> and inspecting it via functions
+ like <c>is_map/1</c> or <c>maps:get/2</c>. Also, Dialyzer must make
+ some <url
+ href="https://github.com/erlang/otp/issues/5118">approximations</url>.
</p>
</section>
</chapter>
diff --git a/system/doc/reference_manual/ports.xml b/system/doc/reference_manual/ports.xml
index 26ecc084c5..a0d3966f35 100644
--- a/system/doc/reference_manual/ports.xml
+++ b/system/doc/reference_manual/ports.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2021</year>
+ <year>2004</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -103,12 +103,9 @@
<c>Port</c> by sending and receiving messages. (In fact, any
process can send the messages to the port, but the port owner must
be identified in the message).</p>
- <p>As of Erlang/OTP R16, messages sent to ports are delivered truly
- asynchronously. The underlying implementation previously
- delivered messages to ports synchronously. Message passing has
- however always been documented as an asynchronous operation. Hence,
- this is not to be an issue for an Erlang program communicating
- with ports, unless false assumptions about ports have been made.</p>
+ <p>Messages sent to ports are delivered asynchronously.</p>
+ <change><p>Before Erlang/OTP 16, messages to ports were
+ delivered synchronously.</p></change>
<p>In the following tables of examples, <c>Data</c> must be an I/O list.
An I/O list is a binary or a (possibly deep) list of binaries
or integers in the range 0..255:</p>
diff --git a/system/doc/reference_manual/processes.xml b/system/doc/reference_manual/processes.xml
index 92be5b60d3..4f1228e210 100644
--- a/system/doc/reference_manual/processes.xml
+++ b/system/doc/reference_manual/processes.xml
@@ -680,7 +680,10 @@ spawn(Module, Name, Args) -> pid()
while it can be trapped if the signal was sent due to a link.
</p>
</item>
- <tag>Blocking Signaling Over Distribution</tag>
+ <tag>
+ Blocking Signaling Over Distribution
+ <marker id="blocking-signaling-over-distribution"/>
+ </tag>
<item>
<p>
When sending a signal over a distribution channel, the sending
@@ -690,23 +693,34 @@ spawn(Module, Name, Args) -> pid()
size of the output buffer for the channel reach the <i>distribution
buffer busy limit</i>, processes sending on the channel will be
suspended until the size of the buffer shrinks below the limit.
- The size of the limit can be inspected by calling
+ </p>
+ <p>
+ Depending on the reason for why the buffer got full, the time it
+ takes before suspended processes are resumed can vary <em>very
+ much</em>. A consequence of this can, for example, be that a
+ timeout in a call to
+ <seemfa marker="kernel:erpc#call/5">erpc:call()</seemfa>
+ is significantly delayed.
+ </p>
+ <p>
+ Since this functionality has been present for so long, it is not
+ possible to remove it, but it is possible to enable <i>fully
+ asynchronous distributed signaling</i> on a per process level
+ using <seeerl marker="erts:erlang#process_flag_async_dist">
+ <c>process_flag(async_dist, Bool)</c></seeerl> which can be
+ used to solve problems occuring due to blocking signaling.
+ However, note that you need to make sure that flow control for
+ data sent using <i>fully asynchronous distributed signaling</i>
+ is implemented, or that the amount of such data is known to
+ always be limited; otherwise, you may get into a situation with
+ excessive memory usage.
+ </p>
+ <p>
+ The size of the <i>distribution buffer busy limit</i> can be
+ inspected by calling
<seeerl marker="erts:erlang#system_info_dist_buf_busy_limit">
<c>erlang:system_info(dist_buf_busy_limit)</c></seeerl>.
- Since this functionality has been present for so long, it is not
- possible to remove it, but it is possible to increase the limit
- to a point where it more or less never is reached using the
- <c>erl</c> command line argument
- <seecom marker="erts:erl#+zdbbl"><c>+zdbbl</c></seecom>. Note
- that if you do raise the limit like this, you need to take care
- of flow control yourself to ensure that you do not get into a
- situation with excessive memory usage. As of OTP 25.3 it is
- also possible to enable <i>fully asynchronous distributed
- signaling</i> on a per process level using
- <seeerl marker="erts:erlang#process_flag_async_dist">
- <c>process_flag(async_dist, Bool)</c></seeerl>. Also in this case
- you need to take care of flow control yourself.
- </p>
+ </p>
</item>
</taglist>
<p>
diff --git a/system/doc/reference_manual/records.xml b/system/doc/reference_manual/records.xml
index d5712bded0..f6ff4173ae 100644
--- a/system/doc/reference_manual/records.xml
+++ b/system/doc/reference_manual/records.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2021</year>
+ <year>2003</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -151,25 +151,23 @@ is_person(_P) ->
<section>
<title>Nested Records</title>
- <p>Beginning with Erlang/OTP R14, parentheses when accessing or updating nested
- records can be omitted. Assume the following record
- definitions:</p>
+ <p>Assume the following record definitions:</p>
<pre>
-record(nrec0, {name = "nested0"}).
-record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
-record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
-N2 = #nrec2{},
- </pre>
- <p>Before R14, parentheses were needed as follows:</p>
- <pre>
-"nested0" = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name,
-N0n = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name = "nested0a"},
- </pre>
- <p>Since R14, the following can also be written:</p>
+N2 = #nrec2{},</pre>
+ <p>Accessing or updating nested records can be written without parentheses:</p>
<pre>
"nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
-N0n = N2#nrec2.nrec1#nrec1.nrec0#nrec0{name = "nested0a"},</pre>
+ N0n = N2#nrec2.nrec1#nrec1.nrec0#nrec0{name = "nested0a"},</pre>
+ <p>which is equivalent to:</p>
+ <pre>
+"nested0" = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name,
+N0n = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name = "nested0a"},</pre>
+ <change><p>Before Erlang/OTP R14, parentheses were necessary when accessing or updating nested
+ records.</p></change>
</section>
<section>
diff --git a/system/doc/reference_manual/typespec.xml b/system/doc/reference_manual/typespec.xml
index 75482e5be1..8f3ba3b25a 100644
--- a/system/doc/reference_manual/typespec.xml
+++ b/system/doc/reference_manual/typespec.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2022</year>
+ <year>2003</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -83,17 +83,32 @@
</p>
<pre> atom() | integer()</pre>
<p>
- Because of subtype relations that exist between types, types
+ Because of subtype relations that exist between types, all types,
+ except <c>dynamic()</c>,
form a lattice where the top-most element, <c>any()</c>, denotes
the set of all Erlang terms and the bottom-most element, <c>none()</c>,
denotes the empty set of terms.
</p>
<p>
+ To facilitate <url href="https://en.wikipedia.org/wiki/Gradual_typing">
+ gradual typing</url> of Erlang, the type `dynamic()` is provided.
+ It is similar to
+ <url href="https://docs.python.org/3/library/typing.html#the-any-type">Any</url>
+ in Python,
+ <url href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any">any</url>
+ in TypeScript and
+ <url href="https://docs.hhvm.com/hack/built-in-types/dynamic">dynamic</url>
+ in Hack. `any()` and `dynamic()` interact with
+ <url href="https://learnyousomeerlang.com/dialyzer#success-typing">success typing</url>
+ the same way, so Dialyzer doesn't distinguish between them.
+ </p>
+ <p>
The set of predefined types and the syntax for types follows:
</p>
<pre><![CDATA[
Type :: any() %% The top type, the set of all Erlang terms
| none() %% The bottom type, contains no terms
+ | dynamic()
| pid()
| port()
| reference()
@@ -314,11 +329,6 @@
<tcaption>Additional built-in types</tcaption>
</table>
- <p>
- Users are not allowed to define types with the same names as the
- predefined or built-in ones. This is checked by the compiler and
- its violation results in a compilation error.
- </p>
<note>
<p>
The following built-in list types also exist,
@@ -345,7 +355,35 @@
This is described in <seeguide marker="#typeinrecords">
Type Information in Record Declarations</seeguide>.
</p>
+
+ <section>
+ <title>Redefining built-in types</title>
+ <change><p>
+ Starting from Erlang/OTP 26, is is permitted to define a type
+ having the same name as a built-in type.</p></change>
+ <p>It is recommended to
+ avoid deliberately reusing built-in names because it can be
+ confusing. However, when an Erlang/OTP release introduces a new
+ type, code that happened to define its own type having the same
+ name will continue to work.
+ </p>
+
+ <p>As an example, imagine that the Erlang/OTP 42 release introduces
+ a new type <c>gadget()</c> defined like this:</p>
+
+ <pre>
+ -type gadget() :: {'gadget', reference()}.</pre>
+
+ <p>Further imagine that some code has its own (different)
+ definition of <c>gadget()</c>, for example:</p>
+
+ <pre>
+ -type gadget() :: #{}.</pre>
+
+ <p>Since redefinitions are allowed, the code will still compile (but
+ with a warning), and Dialyzer will not emit any additional warnings.</p>
</section>
+</section>
<section>
<title>Type Declarations of User-Defined Types</title>
@@ -441,7 +479,7 @@
This is checked by the compiler and results in a compilation error
if a violation is detected.
</p>
- <note>
+ <change>
<p>Before Erlang/OTP 19, for fields without initial values,
the singleton type <c>'undefined'</c> was added to all declared types.
In other words, the following two record declarations had identical
@@ -458,7 +496,7 @@
This is no longer the case. If you require <c>'undefined'</c> in your record field
type, you must explicitly add it to the typespec, as in the 2nd example.
</p>
- </note>
+ </change>
<p>
Any record, containing type information or not, once defined,
can be used as a type using the following syntax:
diff --git a/system/doc/system_architecture_intro/Makefile b/system/doc/system_architecture_intro/Makefile
index 8657da0e2c..0fdf0f6a32 100644
--- a/system/doc/system_architecture_intro/Makefile
+++ b/system/doc/system_architecture_intro/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2020. All Rights Reserved.
+# Copyright Ericsson AB 1997-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ XMLDIR := $(XMLDIR)/system_architecture_intro
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/system_architecture_intro"
+RELSYSDIR = $(RELEASE_PATH)/doc/system_architecture_intro
# ----------------------------------------------------
# Target Specs
@@ -94,7 +94,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/system_principles/Makefile b/system/doc/system_principles/Makefile
index 00b2203394..f4df54bda3 100644
--- a/system/doc/system_principles/Makefile
+++ b/system/doc/system_principles/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2020. All Rights Reserved.
+# Copyright Ericsson AB 1996-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ XMLDIR := $(XMLDIR)/system_principles
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/system_principles"
+RELSYSDIR = $(RELEASE_PATH)/doc/system_principles
# ----------------------------------------------------
# Target Specs
@@ -95,7 +95,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/system/doc/top/Makefile b/system/doc/top/Makefile
index e294d0b0a7..fd4b4cd616 100644
--- a/system/doc/top/Makefile
+++ b/system/doc/top/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2021. All Rights Reserved.
+# Copyright Ericsson AB 1999-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ APPLICATION=otp-system-documentation
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc"
+RELSYSDIR = $(RELEASE_PATH)/doc
GIF_FILES =
@@ -102,14 +102,31 @@ PDFREFDIR= pdf
TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
TOPDOC=true
+sp := $(subst ,, )
+
+## qs translates ' ' to '\ ', sq translates '\ ' to ' '
+## These function are used when the make target is a path that can
+## contain spaces. This is needed a the target 'foo bar:' needs to be
+## written as 'foo\ bar:' in order for make to interpret it as a single
+## target. Unfortunately, when this is done $@ will contain '\ ', which
+## means that we have to use sq to remove the escape again. A small example
+## looks like this:
+##
+## $(call qs, /path/with space/file.o): $(call qs, /path/with space/file.c)
+## gcc -c -o "$(call sq, $@)" "$(call sq, $^)"
+qs = $(subst $(sp),\$(sp),$1)
+sq = $(subst \$(sp),$(sp),$1)
+
ifdef RELEASE_PATH
INST_TYPE=rel
INST_TYPE_SRC_DIR=$(RELEASE_PATH)
# We build to the 'temporary' dir in order to be able to install
# results using INSTALL_DATA (in order to get correct access
# rights on installed files)
+# The temporary folder needs to be in the released system as we do
+# not know if we have write permissions in the source release.
INST_TYPE_DEST_DIR=$(RELSYSDIR)/temporary
-INST_TYPE_DEST_DIR_DEP=$(INST_TYPE_DEST_DIR)
+INST_TYPE_DEST_DIR_DEP=$(call qs,$(INST_TYPE_DEST_DIR))
INST_TYPE_JS_DEST_DIR=$(INST_TYPE_DEST_DIR)
INST_TYPE_VSN_FILE=$(INST_TYPE_DEST_DIR)/OTP_VERSION
else
@@ -131,17 +148,17 @@ EBIN = ebin
INDEX_SCRIPT = $(EBIN)/erl_html_tools.$(EMULATOR)
INDEX_SRC = src/erl_html_tools.erl
-INDEX_HTML=$(INST_TYPE_DEST_DIR)/index.html
-APPLICATIONS_HTML=$(INST_TYPE_DEST_DIR)/applications.html
+INDEX_HTML=$(call qs,$(INST_TYPE_DEST_DIR)/index.html)
+APPLICATIONS_HTML=$(call qs,$(INST_TYPE_DEST_DIR)/applications.html)
INDEX_FILES = $(INDEX_HTML) $(APPLICATIONS_HTML)
-JAVASCRIPT = $(INST_TYPE_JS_DEST_DIR)/erlresolvelinks.js
+JAVASCRIPT = $(call qs,$(INST_TYPE_JS_DEST_DIR)/erlresolvelinks.js)
JAVASCRIPT_BUILD_SCRIPT = $(EBIN)/erlresolvelinks.$(EMULATOR)
JAVASCRIPT_BUILD_SCRIPT_SRC = src/erlresolvelinks.erl
MAN_INDEX_SCRIPT = $(EBIN)/otp_man_index.$(EMULATOR)
MAN_INDEX_SRC = src/otp_man_index.erl
-MAN_INDEX = $(INST_TYPE_DEST_DIR)/man_index.html
+MAN_INDEX = $(call qs,$(INST_TYPE_DEST_DIR)/man_index.html)
GLOSSARY = $(HTMLDIR)/glossary.html
GLOSSARY_SRC = $(ERL_TOP)/system/internal_tools/doctools/src/glossary.erl
@@ -156,18 +173,18 @@ TEMPLATES = \
$(INDEX_SCRIPT): $(INDEX_SRC)
$(ERLC) -o$(EBIN) +warn_unused_vars $<
-$(INST_TYPE_DEST_DIR)/OTP_VERSION: $(INST_TYPE_DEST_DIR_DEP)
+$(call qs,$(INST_TYPE_DEST_DIR)/OTP_VERSION): $(INST_TYPE_DEST_DIR_DEP)
if test -f "$(RELEASE_PATH)/releases/$(SYSTEM_VSN)/OTP_VERSION"; then \
- $(CP) "$(RELEASE_PATH)/releases/$(SYSTEM_VSN)/OTP_VERSION" $@; \
+ $(CP) "$(RELEASE_PATH)/releases/$(SYSTEM_VSN)/OTP_VERSION" "$(call sq,$@)"; \
else \
- $(CP) $(ERL_TOP)/OTP_VERSION $@; \
+ $(CP) "$(ERL_TOP)/OTP_VERSION" "$(call sq,$@)"; \
fi
# We don't list toc_*.html as targets because we don't know
-$(INDEX_HTML) + $(APPLICATIONS_HTML): $(INST_TYPE_DEST_DIR_DEP) $(INDEX_SCRIPT) $(TEMPLATES) $(INST_TYPE_VSN_FILE)
+$(INDEX_HTML) + $(APPLICATIONS_HTML): $(INST_TYPE_DEST_DIR_DEP) $(INDEX_SCRIPT) $(TEMPLATES) $(call qs,$(INST_TYPE_VSN_FILE))
echo "Generating index $@"
- $(ERL) -noshell -pa $(EBIN) -s erl_html_tools top_index $(INST_TYPE) \
- $(INST_TYPE_SRC_DIR) $(INST_TYPE_DEST_DIR) \
+ $(ERL) +pc unicode -noshell -pa $(EBIN) -s erl_html_tools top_index $(INST_TYPE) \
+ "$(INST_TYPE_SRC_DIR)" "$(INST_TYPE_DEST_DIR)" \
`cat "$(INST_TYPE_VSN_FILE)"` -s erlang halt
@@ -177,8 +194,8 @@ $(JAVASCRIPT_BUILD_SCRIPT): $(JAVASCRIPT_BUILD_SCRIPT_SRC)
$(ERLC) -o$(EBIN) +warn_unused_vars $<
$(JAVASCRIPT): $(INST_TYPE_DEST_DIR_DEP) $(JAVASCRIPT_BUILD_SCRIPT)
- erl -noshell -pa $(EBIN) -run erlresolvelinks make $(ERL_TOP) \
- $(INST_TYPE_SRC_DIR) $(INST_TYPE_JS_DEST_DIR) -s erlang halt
+ $(ERL) +pc unicode -noshell -pa $(EBIN) -run erlresolvelinks make $(ERL_TOP) \
+ "$(INST_TYPE_SRC_DIR)" "$(INST_TYPE_JS_DEST_DIR)" -s erlang halt
#--------------------------------------------------------------------------
@@ -186,8 +203,8 @@ $(MAN_INDEX_SCRIPT): $(MAN_INDEX_SRC)
$(ERLC) -o$(EBIN) +warn_unused_vars $<
$(MAN_INDEX): $(INST_TYPE_DEST_DIR_DEP) $(MAN_INDEX_SCRIPT)
- $(ERL) -noshell -pa $(EBIN) -s otp_man_index gen $(INST_TYPE) \
- $(INST_TYPE_SRC_DIR) $@ -s erlang halt
+ $(ERL) +pc unicode -noshell -pa $(EBIN) -s otp_man_index gen $(INST_TYPE) \
+ "$(INST_TYPE_SRC_DIR)" "$(call sq,$@)" -s erlang halt
#--------------------------------------------------------------------------
@@ -280,31 +297,31 @@ clean:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-$(RELSYSDIR)/temporary:
- $(INSTALL_DIR) $(RELSYSDIR)/temporary
+$(call qs,$(RELSYSDIR)/temporary):
+ $(INSTALL_DIR) "$(RELSYSDIR)/temporary"
-$(RELSYSDIR)/docbuild:
- $(INSTALL_DIR) $(RELSYSDIR)/docbuild
+$(call qs,$(RELSYSDIR)/docbuild):
+ $(INSTALL_DIR) "$(RELSYSDIR)/docbuild"
-release_man_spec: man $(RELSYSDIR)/docbuild
+release_man_spec: man $(call qs,$(RELSYSDIR)/docbuild)
-release_html_spec: html $(RELSYSDIR)/docbuild
- $(INSTALL_DATA) $(MAN_INDEX) $(RELSYSDIR)
- $(INSTALL_DATA) $(MAN_INDEX_SRC) $(MAN_INDEX_SCRIPT) $(RELSYSDIR)/docbuild
- $(INSTALL_DIR) $(RELSYSDIR)/js
- $(INSTALL_DATA) $(JAVASCRIPT) $(RELSYSDIR)/js
- $(INSTALL_DATA) $(INDEX_FILES) $(RELSYSDIR)
+release_html_spec: html $(call qs,$(RELSYSDIR)/docbuild)
+ $(INSTALL_DATA) $(MAN_INDEX) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(MAN_INDEX_SRC) $(MAN_INDEX_SCRIPT) "$(RELSYSDIR)/docbuild"
+ $(INSTALL_DIR) "$(RELSYSDIR)/js"
+ $(INSTALL_DATA) $(JAVASCRIPT) "$(RELSYSDIR)/js"
+ $(INSTALL_DATA) $(INDEX_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(INDEX_SCRIPT) $(JAVASCRIPT_BUILD_SCRIPT) \
$(INDEX_SRC) $(JAVASCRIPT_BUILD_SCRIPT_SRC) \
- $(TEMPLATES) $(RELSYSDIR)/docbuild
+ $(TEMPLATES) "$(RELSYSDIR)/docbuild"
release_pdf_spec: pdf
- $(INSTALL_DIR) $(RELSYSDIR)/pdf
+ $(INSTALL_DIR) "$(RELSYSDIR)/pdf"
$(INSTALL_DATA) \
- $(TOP_PDF_FILE) $(RELSYSDIR)/pdf
+ $(TOP_PDF_FILE) "$(RELSYSDIR)/pdf"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec) $(INFO_FILES)
$(INSTALL_DATA) $(INFO_FILES) "$(RELEASE_PATH)"
- $(RM) -r $(RELSYSDIR)/temporary
+ $(RM) -r "$(RELSYSDIR)/temporary"
release_spec:
diff --git a/system/doc/top/src/erl_html_tools.erl b/system/doc/top/src/erl_html_tools.erl
index d609f35380..59a487ce63 100644
--- a/system/doc/top/src/erl_html_tools.erl
+++ b/system/doc/top/src/erl_html_tools.erl
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -67,18 +67,18 @@ top_index(RootDir) when is_atom(RootDir) ->
top_index(Source, RootDir, DestDir, OtpBaseVsn) ->
- report("****\nRootDir: ~p", [RootDir]),
- report("****\nDestDir: ~p", [DestDir]),
- report("****\nOtpBaseVsn: ~p", [OtpBaseVsn]),
+ report("****\nRootDir: ~tp", [RootDir]),
+ report("****\nDestDir: ~tp", [DestDir]),
+ report("****\nOtpBaseVsn: ~tp", [OtpBaseVsn]),
put(otp_base_vsn, OtpBaseVsn),
Templates = find_templates(["","templates",DestDir]),
- report("****\nTemplates: ~p", [Templates]),
+ report("****\nTemplates: ~tp", [Templates]),
Bases = [{"../lib/", filename:join(RootDir,"lib")},
{"../", RootDir}],
Groups = find_information(Source, Bases),
- report("****\nGroups: ~p", [Groups]),
+ report("****\nGroups: ~tp", [Groups]),
process_templates(Templates, DestDir, Groups).
top_index_silent(RootDir, DestDir, OtpBaseVsn) ->
@@ -97,7 +97,7 @@ top_index_silent(RootDir, DestDir, OtpBaseVsn) ->
process_templates([], _DestDir, _Groups) ->
report("\n", []);
process_templates([Template | Templates], DestDir, Groups) ->
- report("****\nIN-FILE: ~s", [Template]),
+ report("****\nIN-FILE: ~ts", [Template]),
BaseName = filename:basename(Template, ".src"),
case lists:reverse(filename:rootname(BaseName)) of
"_"++_ ->
@@ -127,7 +127,7 @@ process_multi_template_1([{Suffix,Group}|Gs], BaseName, Ext, Template, DestDir,
process_multi_template_1([], _, _, _, _, _) -> ok.
subst_file(Group, OutFile, Template, Info) ->
- report("\nOUTFILE: ~s", [OutFile]),
+ report("\nOUTFILE: ~ts", [OutFile]),
case subst_template(Group, Template, Info) of
{ok,Text,_NewInfo} ->
case file:open(OutFile, [write]) of
@@ -135,10 +135,10 @@ subst_file(Group, OutFile, Template, Info) ->
file:write(Stream, Text),
file:close(Stream);
Error ->
- local_error("Can't write to file ~s: ~w", [OutFile,Error])
+ local_error("Can't write to file ~ts: ~w", [OutFile,Error])
end;
Error ->
- local_error("Can't write to file ~s: ~w", [OutFile,Error])
+ local_error("Can't write to file ~ts: ~w", [OutFile,Error])
end.
@@ -203,11 +203,11 @@ get_app_paths(src, AppDirs, URL) ->
{match, [V]} ->
V;
nomatch ->
- exit(io_lib:format("No VSN variable found in ~s\n",
+ exit(io_lib:format("No VSN variable found in ~ts\n",
[VsnFile]))
end;
{error, Reason} ->
- exit(io_lib:format("~p : ~s\n", [Reason, VsnFile]))
+ exit(io_lib:format("~p : ~ts\n", [Reason, VsnFile]))
end,
AppURL = URL ++ App ++ "-" ++ VsnStr,
{App, VsnStr, AppPath, AppURL ++ "/" ++ Sub1}
diff --git a/system/doc/top/templates/index.html.src b/system/doc/top/templates/index.html.src
index 963766d1c5..31c604145b 100644
--- a/system/doc/top/templates/index.html.src
+++ b/system/doc/top/templates/index.html.src
@@ -2,13 +2,13 @@
<!--
%CopyrightBegin%
-Copyright Ericsson AB 2009-2021. All Rights Reserved.
+Copyright Ericsson AB 2009-2023. All Rights Reserved.
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
+ https://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,
@@ -37,7 +37,7 @@ limitations under the License.
<div class="topbar">
<div class="topbar-expand">
<button onclick="toggleDisplay();">
- <svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" width="24" height="24">
+ <svg version="1.1" id="Capa_1" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 54 54" width="24" height="24">
<g>
<path style="fill:#000000;" d="M27,54c-0.552,0-1-0.448-1-1V8c0-0.552,0.448-1,1-1s1,0.448,1,1v45C28,53.552,27.552,54,27,54z"/>
<path style="fill:#000000;" d="M11,25c-0.256,0-0.512-0.098-0.707-0.293c-0.391-0.391-0.391-1.023,0-1.414l16-16
@@ -114,11 +114,11 @@ limitations under the License.
Welcome to Erlang/OTP, a complete development environment for concurrent programming.
</p>
- <h2>Some hints that may get you started faster</h2>
+<h3>Tutorials and books</h3>
<ul>
<li class="mobile-only">
- The navigation menu can be accessed by clicking the <svg style="transform: rotate(180deg);" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" width="24" height="24">
+ The navigation menu can be accessed by clicking the <svg style="transform: rotate(180deg);" version="1.1" id="Capa_1" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 54 54" width="24" height="24">
<g>
<path style="fill:#000000;" d="M27,54c-0.552,0-1-0.448-1-1V8c0-0.552,0.448-1,1-1s1,0.448,1,1v45C28,53.552,27.552,54,27,54z"/>
<path style="fill:#000000;" d="M11,25c-0.256,0-0.512-0.098-0.707-0.293c-0.391-0.391-0.391-1.023,0-1.414l16-16
@@ -136,31 +136,35 @@ An Erlang tutorial can be found in
<a href="getting_started/users_guide.html">
Getting Started With Erlang</a>.
<p>
-In addition to the documentation here Erlang is described in several recent books like:
+In addition to the documentation here Erlang is described in several books like:
</p>
<ul>
<li>
-<a href="http://shop.oreilly.com/product/0636920025818.do">"Introducing Erlang"</a> from O'Reilly.
+<a href="https://www.oreilly.com/library/view/introducing-erlang-2nd/9781491973363">"Introducing Erlang"</a> from O'Reilly.
</li>
<li>
-<a href="http://www.nostarch.com/erlang">"Learn You Some Erlang for Great Good!"</a> from No Starch Press.
+<a href="https://www.nostarch.com/erlang">"Learn You Some Erlang for Great Good!"</a> from No Starch Press.
</li>
<li>
-<a href="http://oreilly.com/catalog/9780596518189">"Erlang Programming"</a> from O'Reilly.
+<a href="https://oreilly.com/catalog/9780596518189">"Erlang Programming"</a> from O'Reilly.
</li>
<li>
-<a href="http://www.pragprog.com/book/jaerlang2/programming-erlang">"Programming Erlang"</a> from Pragmatic.
+<a href="https://www.pragprog.com/book/jaerlang2/programming-erlang">"Programming Erlang"</a> from Pragmatic.
</li>
<li>
-<a href="http://www.manning.com/logan">"Erlang and OTP in Action"</a> from Manning.
+<a href="https://www.manning.com/logan">"Erlang and OTP in Action"</a> from Manning.
</li>
<li>
-<a href="http://shop.oreilly.com/product/0636920024149.do">"Designing for Scalability with Erlang/OTP"</a> from O'Reilly.
+<a href="https://shop.oreilly.com/product/0636920024149.do">"Designing for Scalability with Erlang/OTP"</a> from O'Reilly.
</li>
</ul>
<p>
These books are highly recommended as a start for learning Erlang.
</p>
+<p>
+ More information about learning resources, editors and other tools can be found on our
+ <a href="https://erlang.org/community">official Erlang web site</a>.
+</p>
</li>
<li>Erlang/OTP is divided into a number of OTP <a
href="applications.html">applications</a>. An application normally contains
@@ -170,51 +174,26 @@ modules.
<p></p>
</li>
-<li>On a Unix system you can view the manual pages from the command
-line using
-<pre>
- % erl -man &lt;module&gt;
-</pre>
-</li>
-
-<li> You can of course use any editor you like to write Erlang
-programs, but if you use Emacs there exists editing support such as
-indentation, syntax highlighting, electric commands, module name
-verification, comment support including paragraph filling, skeletons,
-tags support and more. See the <a href="#tools#/index.html">
-Tools</a> application for details.
<p>
-There are also Erlang plugins for other code editors
-<a href="http://github.com/vim-erlang">Vim (vim-erlang)</a> ,
-<a href="http://atom.io/packages/language-erlang"> Atom </a> ,
-<a href="http://erlide.org/index.html">Eclipse (ErlIDE)</a> and
-<a href="http://ignatov.github.io/intellij-erlang/">IntelliJ IDEA</a>.
<li>When developing with Erlang/OTP you usually test your programs
from the interactive shell (see <a href="getting_started/users_guide.html">
Getting Started With Erlang</a>) where you can call individual
functions. There is also a number of tools available, such as the graphical <a
href="#debugger#/index.html" >Debugger</a> and the <a href="#observer#/index.html" >Observer tool</a> for inspection of system information, ets and mnesia tables etc.
-<p> Also note that there are some shell features like history list
-(control-p and control-n), in line editing (Emacs key bindings) and
-module and function name completion (tab) if the module is loaded.
-<p>
+</p>
-<li>OpenSource users can ask questions
-and share experiences on the <a href="http://www.erlang.org/static/doc/mailinglist.html">
-Erlang questions mailing list</a>.
+<li>For questions and discussions about Erlang we recommend <a href="https://erlangforums.com">
+ErlangForums</a>.
<p></p>
</li>
-<li>Before asking a question you can browse the <a
-href="http://www.erlang.org/pipermail/erlang-questions/">
-mailing list archive</a> and read the <a
-href="http://www.erlang.org/faq/faq.html" >Frequently
-Asked Questions</a>.
+<li>There is also the <a href="https://www.erlang.org/faq/faq.html" >Frequently
+Asked Questions</a> which can be of interest.
<p></p>
</li>
<li>Additional information and links of interest for Erlang programmers can be found on the Erlang Open Source site
-<a href="http://www.erlang.org/">http://www.erlang.org</a>.
+<a href="https://www.erlang.org/">https://www.erlang.org</a>.
</li>
</ul>
@@ -222,7 +201,7 @@ Asked Questions</a>.
<center>
<small>
Copyright &copy; 1999-#copyrightyear#
-<a href="http://www.ericsson.com">Ericsson AB</a>
+<a href="https://www.ericsson.com">Ericsson AB</a>
</small>
</center>
</div>
diff --git a/system/doc/tutorial/Makefile b/system/doc/tutorial/Makefile
index faf06b3878..c9b7103093 100644
--- a/system/doc/tutorial/Makefile
+++ b/system/doc/tutorial/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2000-2020. All Rights Reserved.
+# Copyright Ericsson AB 2000-2023. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ XMLDIR := $(XMLDIR)/tutorial
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = "$(RELEASE_PATH)/doc/tutorial"
+RELSYSDIR = $(RELEASE_PATH)/doc/tutorial
# ----------------------------------------------------
# Target Specs
@@ -123,7 +123,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_html_spec: html
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
- $(RELSYSDIR)
+ "$(RELSYSDIR)"
release_docs_spec: $(DOC_TARGETS:%=release_%_spec)